Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions net/pppoe-server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
PLUGIN_NAME= pppoe-server
PLUGIN_VERSION= 1.0
PLUGIN_COMMENT= PPPoE Access Concentrator (BRAS) based on mpd5
PLUGIN_DEPENDS= mpd5
PLUGIN_MAINTAINER= dev@veqnora.com
PLUGIN_WWW= https://veqnora.com
PLUGIN_LICENSE= BSD2CLAUSE

.include "../../Mk/plugins.mk"
13 changes: 13 additions & 0 deletions net/pppoe-server/pkg-descr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PPPoE Access Concentrator (BRAS/BNG) for OPNsense based on mpd5.

Runs PPPoE server instances on physical or VLAN interfaces with PAP, CHAP
and MS-CHAPv2 authentication against a local user database or RADIUS,
IPv4 address pools and per-user static addresses, RADIUS accounting, a
live session table with disconnect, and service diagnostics.

Also supports per-session rate limiting (via the RADIUS mpd-limit VSA or a
local shaping backend), RADIUS CoA / RFC 5176 Disconnect, dual-stack IPv6
(IPv6CP link-local, SLAAC via Router Advertisements, and static routed IPv6
prefixes per subscriber), and CARP high-availability interlock.

Maintained by VEQNORA.
146 changes: 146 additions & 0 deletions net/pppoe-server/src/etc/inc/plugins.inc.d/pppoe_server.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

/*
* Copyright (C) 2026 VEQNORA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

function pppoe_server_enabled()
{
$model = new \OPNsense\PPPoEServer\PPPoEServer();

return (string)$model->general->enabled == '1';
}

function pppoe_server_services()
{
$services = [];

if (pppoe_server_enabled()) {
$services[] = [
'description' => gettext('PPPoE Server'),
'configd' => [
'restart' => ['pppoe_server restart'],
'start' => ['pppoe_server start'],
'stop' => ['pppoe_server stop'],
],
'name' => 'pppoe_server',
'pidfile' => '/var/run/pppoe_server.pid',
];

$model = new \OPNsense\PPPoEServer\PPPoEServer();
if ((string)$model->radius->coa->enabled == '1') {
$services[] = [
'description' => gettext('PPPoE CoA listener'),
'configd' => [
'restart' => ['pppoe_server coa_restart'],
'start' => ['pppoe_server coa_start'],
'stop' => ['pppoe_server coa_stop'],
],
'name' => 'pppoe_server_coa',
'pidfile' => '/var/run/pppoe_server_coa.pid',
];
}
}

return $services;
}

/**
* expose the plugin configuration section for HA XMLRPC config sync
*/
function pppoe_server_xmlrpc_sync()
{
$result = [];

$result[] = [
'description' => gettext('PPPoE Server'),
'section' => 'OPNsense.pppoeserver',
'id' => 'pppoeserver',
'services' => ['pppoe_server'],
];

return $result;
}

/**
* dedicated syslog target for session events emitted by link scripts
*/
function pppoe_server_syslog()
{
$logfacilities = [];

$logfacilities['pppoe'] = ['facility' => ['pppoe']];

return $logfacilities;
}

/**
* expose the client networks as a virtual interface group so the
* administrator can attach firewall and NAT rules to it
*/
function pppoe_server_interfaces()
{
$interfaces = [];

if (!pppoe_server_enabled()) {
return $interfaces;
}

$model = new \OPNsense\PPPoEServer\PPPoEServer();

$networks = [];
foreach ($model->pools->pool->iterateItems() as $pool) {
if ((string)$pool->enabled != '1') {
continue;
}
$start = ip2long((string)$pool->start);
$end = ip2long((string)$pool->end);
if ($start === false || $end === false) {
continue;
}
/* smallest common prefix covering the pool range */
$mask = 32;
while ($mask > 0 && (($start & (-1 << (32 - $mask))) != ($end & (-1 << (32 - $mask))))) {
$mask--;
}
$networks[] = [
'network' => long2ip($start & (-1 << (32 - $mask))),
'mask' => $mask,
];
}

if (count($networks)) {
$interfaces['pppoe'] = [
'enable' => true,
'virtual' => true,
'networks' => $networks,
'if' => 'pppoe',
'descr' => 'PPPoE Clients',
'type' => 'group',
];
}

return $interfaces;
}
39 changes: 39 additions & 0 deletions net/pppoe-server/src/etc/rc.d/pppoe_server
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/sh
#
# PROVIDE: pppoe_server
# REQUIRE: SERVERS
# KEYWORD: shutdown
#
# mpd5 wrapper for the PPPoE Access Concentrator plugin.
# Configuration is generated by configd templates into /usr/local/etc/pppoe_server.

. /etc/rc.subr

name=pppoe_server
rcvar=pppoe_server_enable

load_rc_config ${name}

: ${pppoe_server_enable:="NO"}

confdir="/usr/local/etc/pppoe_server"
pidfile="/var/run/pppoe_server.pid"
command="/usr/local/sbin/mpd5"
command_args="-b -d ${confdir} -p ${pidfile} -s pppoe pppoe_server"
required_files="${confdir}/mpd.conf"
start_precmd="pppoe_server_precmd"

pppoe_server_precmd()
{
# both files carry secrets (user passwords, RADIUS shared secrets)
for f in mpd.secret mpd.conf console.auth users.meta users.ext; do
if [ -f "${confdir}/${f}" ]; then
chown root:wheel "${confdir}/${f}"
chmod 0600 "${confdir}/${f}"
fi
done
# drop expired accounts from mpd.secret before the daemon starts
/usr/local/opnsense/scripts/OPNsense/PPPoEServer/expire_users.py --prune-only > /dev/null 2>&1 || :
}

run_rc_command "$1"
34 changes: 34 additions & 0 deletions net/pppoe-server/src/etc/rc.d/pppoe_server_coa
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh
#
# PROVIDE: pppoe_server_coa
# REQUIRE: pppoe_server
# KEYWORD: shutdown
#
# RFC 5176 Disconnect/CoA adapter for the PPPoE Access Concentrator plugin.

. /etc/rc.subr

name=pppoe_server_coa
rcvar=pppoe_server_coa_enable

load_rc_config ${name}

: ${pppoe_server_coa_enable:="NO"}

pidfile="/var/run/pppoe_server_coa.pid"
command="/usr/sbin/daemon"
coa_script="/usr/local/opnsense/scripts/OPNsense/PPPoEServer/coa_daemon.py"
command_args="-f -P ${pidfile} -r -t pppoe_server_coa ${coa_script}"
required_files="/usr/local/etc/pppoe_server/coa.conf"
start_precmd="pppoe_server_coa_precmd"

pppoe_server_coa_precmd()
{
# the config carries the CoA shared secret
if [ -f "/usr/local/etc/pppoe_server/coa.conf" ]; then
chown root:wheel "/usr/local/etc/pppoe_server/coa.conf"
chmod 0600 "/usr/local/etc/pppoe_server/coa.conf"
fi
}

run_rc_command "$1"
53 changes: 53 additions & 0 deletions net/pppoe-server/src/etc/rc.syshook.d/carp/50-pppoe-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/local/bin/php
<?php

/*
* Copyright (C) 2026 VEQNORA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

require_once('script/load_phalcon.php');
require_once('util.inc');

use OPNsense\PPPoEServer\PPPoEServer;

$subsystem = $argv[1] ?? '';
$type = $argv[2] ?? '';
$model = new PPPoEServer();

if (
!in_array($type, ['MASTER', 'BACKUP'], true) || /* exclude INIT */
strpos($subsystem, '@') === false || /* only react to real CARP events */
(string)$model->general->enabled != '1' ||
(string)$model->general->carpdependent != '1'
) {
exit(0);
}

$actions = [
'MASTER' => 'start',
'BACKUP' => 'stop',
];

mwexecfm('/usr/local/etc/rc.d/pppoe_server ' . $actions[$type]);
Loading