summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build-and-run.yml18
-rw-r--r--.github/workflows/run-tests-32bit.yml8
-rw-r--r--.github/workflows/run-tests-asan-ubsan.yml9
-rw-r--r--.github/workflows/run-tests-bigendian.yml8
-rw-r--r--.github/workflows/run-tests.yml32
-rw-r--r--CMakeLists.txt4
-rw-r--r--README182
-rw-r--r--README.md255
-rw-r--r--accel-pppd/accel-ppp.conf93
-rw-r--r--accel-pppd/accel-ppp.conf.5313
l---------accel-pppd/ctrl/sstp/if_ppposeq.h1
-rw-r--r--accel-pppd/ctrl/sstp/sstp.c378
-rw-r--r--accel-pppd/extra/metrics.c353
-rw-r--r--cmake/cpack.cmake2
-rw-r--r--drivers/ppposeq/CMakeLists.txt19
-rw-r--r--drivers/ppposeq/Makefile4
-rw-r--r--drivers/ppposeq/ppposeq.c569
-rw-r--r--drivers/ppposeq/ppposeq.h39
-rw-r--r--tests/accel-pppd/general/test_metrics.py29
19 files changed, 1978 insertions, 338 deletions
diff --git a/.github/workflows/build-and-run.yml b/.github/workflows/build-and-run.yml
index 80d419c7..9caf31cf 100644
--- a/.github/workflows/build-and-run.yml
+++ b/.github/workflows/build-and-run.yml
@@ -41,7 +41,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-`uname -r`
-DLUA=TRUE -DSHAPER=FALSE -DRADIUS=TRUE
-DCPACK_TYPE=${{ matrix.cpack-type }} ..
@@ -134,7 +135,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-${{ env.KERNEL_NAME }}
-DMODULES_KDIR=${{ env.KERNEL_NAME }}
-DLUA=TRUE -DSHAPER=FALSE -DRADIUS=TRUE
@@ -199,7 +201,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DCMAKE_INSTALL_SYSCONFDIR=/etc
-DKDIR=/usr/src/linux-headers-${{ env.KERNEL_NAME }}
-DMODULES_KDIR=${{ env.KERNEL_NAME }}
@@ -233,7 +236,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=FALSE -DBUILD_VLAN_MON_DRIVER=FALSE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=FALSE -DBUILD_VLAN_MON_DRIVER=FALSE -DBUILD_PPPOSEQ_DRIVER=FALSE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-`uname -r`
-DLUA=TRUE -DSHAPER=FALSE -DRADIUS=TRUE ..
- name: make and install
@@ -270,7 +274,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DCMAKE_INSTALL_SYSCONFDIR=/etc
-DKDIR=/usr/src/linux
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE ..
@@ -305,7 +310,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DCMAKE_INSTALL_SYSCONFDIR=/etc
-DKDIR=/usr/src/kernels/`ls -1 -t /usr/src/kernels | head -n 1`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE ..
diff --git a/.github/workflows/run-tests-32bit.yml b/.github/workflows/run-tests-32bit.yml
index 8157f3fb..207a624b 100644
--- a/.github/workflows/run-tests-32bit.yml
+++ b/.github/workflows/run-tests-32bit.yml
@@ -122,7 +122,8 @@ jobs:
run: >
ssh -i ssh-key -p2222 root@localhost "cd accel-ppp && git config --global --add safe.directory '*' &&
mkdir build && cd build &&
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-\`uname -r\`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE .. &&
make && make install"
@@ -152,6 +153,11 @@ jobs:
ssh -i ssh-key -p2222 root@localhost "cd accel-ppp &&
insmod build/drivers/vlan_mon/driver/vlan_mon.ko &&
lsmod | grep vlan_mon"
+ - name: Insert ppposeq kernel module
+ run: >
+ ssh -i ssh-key -p2222 root@localhost "cd accel-ppp &&
+ modprobe pppox && insmod build/drivers/ppposeq/driver/ppposeq.ko &&
+ lsmod | grep ppposeq"
- name: Run tests (all)
timeout-minutes: 5
run: >
diff --git a/.github/workflows/run-tests-asan-ubsan.yml b/.github/workflows/run-tests-asan-ubsan.yml
index 8be22b67..12bf748d 100644
--- a/.github/workflows/run-tests-asan-ubsan.yml
+++ b/.github/workflows/run-tests-asan-ubsan.yml
@@ -51,21 +51,24 @@ jobs:
run: >
CFLAGS="-fsanitize=${{ matrix.sanitizer }} -fno-sanitize-recover=all -fno-omit-frame-pointer -O2 -g"
LDFLAGS="-fsanitize=${{ matrix.sanitizer }}"
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
- -DKDIR=/usr/src/linux-headers-`uname -r`
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
+ -DKDIR=/usr/src/linux-headers-`uname -r`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE ..
- name: make && make install
working-directory: ./build
run: make && sudo make install
- - name: Insert and check kernel modules (ipoe and vlan-mon)
+ - name: Insert and check kernel modules (ipoe, vlan-mon, ppposeq)
# if: ${{ false }}
run: |
sudo insmod build/drivers/vlan_mon/driver/vlan_mon.ko
sudo insmod build/drivers/ipoe/driver/ipoe.ko
+ sudo modprobe pppox && sudo insmod build/drivers/ppposeq/driver/ppposeq.ko
lsmod | grep ipoe
lsmod | grep vlan_mon
+ lsmod | grep ppposeq
- name: Run tests
timeout-minutes: 5
diff --git a/.github/workflows/run-tests-bigendian.yml b/.github/workflows/run-tests-bigendian.yml
index 389cd2a7..6808900d 100644
--- a/.github/workflows/run-tests-bigendian.yml
+++ b/.github/workflows/run-tests-bigendian.yml
@@ -143,7 +143,8 @@ jobs:
run: >
ssh -i ssh-key -p2222 root@localhost "cd accel-ppp && git config --global --add safe.directory '*' &&
mkdir build && cd build &&
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-\`uname -r\`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE .. &&
make && make install"
@@ -173,6 +174,11 @@ jobs:
ssh -i ssh-key -p2222 root@localhost "cd accel-ppp &&
insmod build/drivers/vlan_mon/driver/vlan_mon.ko &&
lsmod | grep vlan_mon"
+ - name: Insert ppposeq kernel module
+ run: >
+ ssh -i ssh-key -p2222 root@localhost "cd accel-ppp &&
+ modprobe pppox && insmod build/drivers/ppposeq/driver/ppposeq.ko &&
+ lsmod | grep ppposeq"
- name: Run tests (all)
timeout-minutes: 5
run: >
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 71b3820b..9cd47a00 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -127,7 +127,8 @@ jobs:
run: >
ssh -i ssh-key -p2222 user@localhost "cd accel-ppp &&
mkdir build && cd build &&
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-\`uname -r\`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE .. &&
make && sudo make install"
@@ -167,6 +168,12 @@ jobs:
sudo insmod build/drivers/vlan_mon/driver/vlan_mon.ko &&
lsmod | grep vlan_mon"
+ - name: Insert ppposeq kernel module
+ run: >
+ ssh -i ssh-key -p2222 user@localhost "cd accel-ppp &&
+ sudo modprobe pppox && sudo insmod build/drivers/ppposeq/driver/ppposeq.ko &&
+ lsmod | grep ppposeq"
+
- name: Run tests (all)
timeout-minutes: 5
run: >
@@ -265,7 +272,8 @@ jobs:
run: >
ssh -i ssh-key -p2222 alpine@localhost "cd accel-ppp &&
mkdir build && cd build &&
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-\`uname -r\`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE .. &&
make && doas make install"
@@ -305,6 +313,12 @@ jobs:
doas insmod build/drivers/vlan_mon/driver/vlan_mon.ko &&
lsmod | grep vlan_mon"
+ - name: Insert ppposeq kernel module
+ run: >
+ ssh -i ssh-key -p2222 alpine@localhost "cd accel-ppp &&
+ doas modprobe pppox && doas insmod build/drivers/ppposeq/driver/ppposeq.ko &&
+ lsmod | grep ppposeq"
+
- name: Run tests (all)
timeout-minutes: 5
run: >
@@ -360,7 +374,8 @@ jobs:
- name: cmake
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-`uname -r`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE ..
@@ -368,13 +383,15 @@ jobs:
working-directory: ./build
run: make && sudo make install
- - name: Insert and check kernel modules (ipoe and vlan-mon)
+ - name: Insert and check kernel modules (ipoe, vlan-mon, ppposeq)
# if: ${{ false }}
run: |
sudo insmod build/drivers/vlan_mon/driver/vlan_mon.ko
sudo insmod build/drivers/ipoe/driver/ipoe.ko
+ sudo modprobe pppox && sudo insmod build/drivers/ppposeq/driver/ppposeq.ko
lsmod | grep ipoe
lsmod | grep vlan_mon
+ lsmod | grep ppposeq
- name: Run tests
timeout-minutes: 5
@@ -424,7 +441,8 @@ jobs:
- name: cmake (with coverage)
working-directory: ./build
run: >
- cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr
+ cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DBUILD_PPPOSEQ_DRIVER=TRUE
+ -DCMAKE_INSTALL_PREFIX=/usr
-DKDIR=/usr/src/linux-headers-`uname -r`
-DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE
-DCMAKE_C_FLAGS="--coverage -O0" ..
@@ -433,13 +451,15 @@ jobs:
working-directory: ./build
run: make && sudo make install
- - name: Insert and check kernel modules (ipoe and vlan-mon)
+ - name: Insert and check kernel modules (ipoe, vlan-mon, ppposeq)
# if: ${{ false }}
run: |
sudo insmod build/drivers/vlan_mon/driver/vlan_mon.ko
sudo insmod build/drivers/ipoe/driver/ipoe.ko
+ sudo modprobe pppox && sudo insmod build/drivers/ppposeq/driver/ppposeq.ko
lsmod | grep ipoe
lsmod | grep vlan_mon
+ lsmod | grep ppposeq
- name: Run tests (for coverage report) (fail is ok)
timeout-minutes: 5
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9aee13be..c71bbbd9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -96,6 +96,10 @@ if (BUILD_IPOE_DRIVER)
add_subdirectory(drivers/ipoe)
endif (BUILD_IPOE_DRIVER)
+if (BUILD_PPPOSEQ_DRIVER)
+ add_subdirectory(drivers/ppposeq)
+endif (BUILD_PPPOSEQ_DRIVER)
+
if (BUILD_VLAN_MON_DRIVER)
add_subdirectory(drivers/vlan_mon)
endif ()
diff --git a/README b/README
deleted file mode 100644
index c91a4e0b..00000000
--- a/README
+++ /dev/null
@@ -1,182 +0,0 @@
-Overview
---------
-The ACCEL-PPP v1.0 is completly new implementation of PPTP/PPPoE/L2TP/SSTP which was written from scratch.
-Userspace daemon has its own PPP implementation, so it does not uses pppd and one process (multi-threaded) manages all connections.
-ACCEL-PPP uses kernel-mode implementations of pptp/l2tp/pppoe and user-mode of sstp.
-
-
-Features
---------
-1. Modular architecture
-2. High-performance multi-threaded I/O core
-3. Supported PPTP
-4. Supported PPPoE (including TR-101 extension)
-5. Supported L2TPv2 (without IPsec)
-5. Radius authentication/accounting
-6. Radius DM/CoA extention
-7. Supported authentication types: PAP, CHAP (md5), Microsoft CHAP Extentions (including version 2), not supported - EAP
-8. Supported MPPE
-9. Compression is not supported
-10. Extensible logging engine with per session logging support, implemented log to file, log to remote host and log to PostgreSQL targets
-11. Extensible user/password database, implemented Radius, pppd compatible chap-secrets sources
-12. Extensible IP pool, implemented Radius, chap-secrets and static pools
-13. Supported pppd compatible ip-up/ip-down scripts
-14. Builtin tbf/htb shaper and clsact policer manager
-15. Command line interface via telnet
-16. SNMP support (master or subagent via AgentX)
-17. Supported SSTP
-
-
-Requirment
-----------
-1. modern linux distribution
-2. kernel-2.6.25 or later
-4. cmake-3.5 or later
-5. libnl-2.0 or probably later (required for builtin shaper)
-6. libcrypto-0.9.8 or probably later (openssl-0.9.8)
-7. libpcre2
-8. net-snmp-5.x
-9. libssl-0.9.8 or probably later (openssl-0.9.8)
-
-
-Compilation and instalation
------------
-Make sure you have configured kernel headers in /usr/src/linux,
-or specify other location via KDIR.
-1. cd /path/to/accel-ppp-1.3.5
-2. mkdir build
-3. cd build
-4. cmake [-DBUILD_DRIVER=FALSE] [-DKDIR=/usr/src/linux] [-DCMAKE_INSTALL_PREFIX=/usr/local] [-DCMAKE_BUILD_TYPE=Release] [-DLOG_PGSQL=FALSE] [-DSHAPER=FALSE] [-DRADIUS=TRUE] [-DNETSNMP=FALSE] ..
- Please note that the double dot record in the end of the command is essential. You'll probably get error or misconfigured sources if you miss it.
- BUILD_DRIVER, KDIR, CMAKE_INSTALL_PREFIX, CMAKE_BUILD_TYPE, LOG_PGSQL, SHAPER, RADIUS are optional,
- But while pptp is not present in mainline kernel you probably need BUILD_DRIVER.
- For example:
- cmake -DBUILD_DRIVER=TRUE ..
- will configure sources to build pptp driver, search kernel headers at /usr/src/linux, install to /usr/local,
- build with no debug, pgsql and shaper support, build with radius support.
-5. If you want to use chap-secrets for authentication purpose then you need to disable radius support, configure as following:
- cmake -DBUILD_DRIVER=TRUE -DRADIUS=FALSE ..
- of course you can include additional options if needed.
-6. make
-7. make install
-
-
-Configuration
--------------
-read man accel-ppp.conf
-For DM/CoA deployments, use the `dae-allowed` option to restrict source IPs.
-
-
-Built-in shaper
---------------
-accel-ppp supports tbf and htb based shaper manager. It also supports clsact policer manager.
-To enable it uncomment shaper in [modules] section.
-It accepts radius attributes in various formats: rate, down-rate/up-rate and cisco-like. Values have to be in kilobits except cisco-like.
-For example:
-Filter-Id=1000 (means 1000Kbit both up-stream and down-stream rate)
-Filter-Id=2000/3000 (means 2000Kbit down-stream rate and 3000Kbit up-stream rate)
-To change radius attribute which containes rate information use 'attr' option, for example:
-[shaper]
-attr=My-Custom-Rate-Attribute
-of course this attribute have to be in radius dictionary.
-To specify different attributes for down-stream and up-stream rates use 'attr-down' and 'attr-up' options, for example:
-[shaper]
-attr-down=PPPD-Downstream-Speed
-attr-up=PPPD-Upstream-Speed
-
-If you want to use cisco-like format configure accel-ppp as following:
-[shaper]
-vendor=Cisco
-attr=Cisco-AVPair
-and send two attributes:
-Cisco-AVPair=lcp:interface-config#1=rate-limit input 2000000 8000 8000 conform-action transmit exceed-action drop (which means 2000Kbit up-stream rate and 8Kb burst)
-Cisco-AVPair=lcp:interface-config#1=rate-limit output 2000000 8000 8000 conform-action transmit exceed-action drop (which means 2000Kbit down-stream rate and 8Kb burst)
-
-
-Advanced shaper using
----------------------
-1. Burst configuration.
-If you not using cisco-like format then burst calculates from rate and specified burst factors.
-To specify burst factors use 'down-burst-factor' and 'up-burst-factor' options, for example:
-[shaper]
-down-burst-factor=1.0
-up-burst-factor=10.0
-which means that burst for tbf/htb qdisc will be calculated as down-stream rate multiply to 1.0 and burst for policer/htb will be calculated as up-stream rate multiply to 10.0.
-
-2. Time ranges.
-You can specify time ranges to authomatic rate reconfiguration.
-To specify time ranges use following sample configuration:
-[shaper]
-time-range=1,1:00-3:00
-time-range=2,3:00-5:00
-time-range=3,5:00-7:00
-first number is time range identifier.
-To specify time range specific rates use following format of radius attributes: range-id,rate, range-id,down-rate/up-rate or cisco-like, for example:
-Filter-Id=1000
-Filter-Id=1,2000
-Filter-Id=2,3000
-Filter-Id=3,4000
-which means: set 1000Kbit by default, set 2000Kbit in time range 1, set 3000Kbit in time range 2, set 4000Kbit in time range 3.
-You have to pass multiple Filter-Id attributes to utilize this functionality.
-Or cisco-like:
-Cisco-AVPair=lcp:interface-config#1=rate-limit output access-group 1 1000000 8000 8000 conform-action transmit exceed-action drop
-Cisco-AVPair=lcp:interface-config#1=rate-limit input access-group 1 1000000 8000 8000 conform-action transmit exceed-action drop
-and so on...
-
-3. chap-secrets.
-If you use chap-secrets instead of radius then there is way to utilize built-in shaper too.
-The optional fifth column in chap-secrets file is used to pass rate information to shaper.
-Its format is same as for radius attributes, except you cann't utilize time ranges functionality.
-
-
-SNMP
-----
-SNMP is implemented using net-snmp libraries. By default accel-ppp starts in subagent mode,
-so make sure that net-snmp configured with subagent control turned on (read net-snmp's README.agentx for more details).
-Also you can start accel-ppp as master agent using following configuration:
-[snmp]
-master=1
-
-Usage:
-Place accel-pppd/extra/net-snmp/ACCEL-PPP-MIB.txt to your mibs directory.
-Also you can find used numerical oids in this file.
-1. Requesting statistics:
-snmpwalk -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::accelPPPStat
-2. Requesting sessions:
-snmptable -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::sessionsTable
-3. Terminate session by session identifier (Acct-Session-ID):
-snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termBySID.0 = 0000000000000001
-4. Terminate session by interface name:
-snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termByIfName.0 = ppp2
-5. Terminaten session by IP address (Framed-IP-Address):
-snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termByIP.0 = 192.168.10.10
-6. Terminate session by username:
-snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termByUsername.0 = user1
-7. Execute cli command:
-snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::cli.0 = "shaper change all 1024 temp"
-
-
-chap-secrets encryption
------------------------
-To enable chap-secrets encryption ablity accel-ppp must be compiled with -DCRYPTO=OPENSSL (which is default).
-Username field may be kept as cleartext or hashed through some hash chain. To specify hash chain use username-hash option.
-For example, username-hash=md5,sha1 means hash username through md5 and then binary result hash through sha1.
-Username have to be specified as hexadecimal dump of digest result.
-Password field have to be encrypted using smbencrypt (NT Hash part).
-Encryption is incompatible with auth_chap_md5 module.
-
-
-Warning !!!
------------
-1. The pptp driver conflicts with ip_gre driver (in kernel), so make sure that ip_gre is not built-in or loaded at run time
- (don't matter if you have 2.6.37 or later kernel).
-2. Don't mix connections of accel-ppp and poptop's pptpd, before starting accel-ppp make sure that no connections
- of pptpd exists.
-
-
-Contacts
---------
-http://accel-ppp.org/
-mail: contact@accel-ppp.org
-ICQ: 337258064
-Jabber: dima@accel-ppp.org
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..a78cb145
--- /dev/null
+++ b/README.md
@@ -0,0 +1,255 @@
+ACCEL-PPP
+=========
+
+ACCEL-PPP is a high-performance, multi-threaded VPN and broadband access
+concentrator for Linux. It has its own userspace PPP implementation, so one
+daemon can manage all connections without relying on pppd. The implementation
+was developed from scratch rather than as a wrapper around pppd. It uses Linux
+kernel interfaces for PPTP, L2TP, and PPPoE data paths, while SSTP is handled in
+userspace.
+
+
+Features
+========
+
+* Modular architecture and a multi-threaded I/O core
+* PPTP, PPPoE (including TR-101), L2TPv2, SSTP, and IPoE. ACCEL-PPP does not
+ provide integrated IPsec for L2TPv2; deploy IPsec separately when required.
+* RADIUS authentication and accounting, including Disconnect Messages and
+ Change of Authorization (DM/CoA)
+* PAP, CHAP-MD5, MS-CHAPv1, and MS-CHAPv2 authentication
+* Microsoft Point-to-Point Encryption (MPPE)
+* File, syslog, TCP, and optional PostgreSQL logging, including per-session logs
+* Extensible authentication sources, including RADIUS and pppd-compatible
+ chap-secrets files
+* Extensible IP address pools populated by RADIUS, chap-secrets, or static
+ configuration
+* pppd-compatible ip-up and ip-down scripts
+* TBF/HTB shaping and clsact policing
+* Telnet and TCP command-line interfaces
+* Optional SNMP support as a master agent or AgentX subagent
+
+EAP authentication and PPP compression are not supported.
+
+
+Requirements
+============
+
+Building the daemon requires:
+
+* Linux
+* A C compiler and standard build tools
+* CMake 3.10 or newer
+* OpenSSL development files
+* PCRE2 development files
+
+Kernel headers are also required when building the optional PPTP, IPoE, or VLAN
+monitoring kernel modules. Optional features require their corresponding
+development libraries:
+
+* Net-SNMP for NETSNMP=TRUE
+* PostgreSQL client libraries for LOG_PGSQL=TRUE
+* Lua for LUA=TRUE or a specific Lua version such as LUA=5.3
+
+
+Building and installing
+=======================
+
+Use an out-of-tree build directory:
+
+ cmake -S . -B build \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX=/usr/local
+ cmake --build build
+ sudo cmake --install build
+
+Useful build options:
+
+* BUILD_PPTP_DRIVER=TRUE builds the PPTP kernel module.
+* BUILD_IPOE_DRIVER=TRUE builds the IPoE kernel module.
+* BUILD_VLAN_MON_DRIVER=TRUE builds the VLAN monitoring kernel module.
+* BUILD_PPPOSEQ_DRIVER=TRUE builds the PPPoSEQ kernel module.
+* BUILD_DRIVER_ONLY=TRUE builds only the selected kernel modules.
+* KDIR=/path/to/kernel/build sets the kernel build directory.
+* RADIUS=FALSE omits RADIUS support.
+* SHAPER=FALSE omits the traffic-shaping module.
+* NETSNMP=TRUE builds SNMP support.
+* LOG_PGSQL=TRUE builds PostgreSQL logging support.
+
+For example, to build the IPoE and VLAN monitoring modules for the running
+kernel:
+
+ cmake -S . -B build \
+ -DBUILD_IPOE_DRIVER=TRUE \
+ -DBUILD_VLAN_MON_DRIVER=TRUE \
+ -DKDIR="/usr/src/linux-headers-$(uname -r)"
+ cmake --build build
+
+
+Configuration
+=============
+
+The sample configuration is installed as accel-ppp.conf.dist. See
+"man 5 accel-ppp.conf" for the complete configuration reference.
+
+Enable or disable functionality in the [modules] section. To authenticate from
+a pppd-compatible secrets file, enable chap-secrets instead of radius. RADIUS
+may remain compiled in; RADIUS=FALSE is only needed when it should be omitted
+from the build. Loading both providers does not provide dependable automatic
+fallback from RADIUS to chap-secrets because authentication providers are
+consulted in module registration order.
+
+For DM/CoA deployments, configure dae-allowed in the [radius] section to
+restrict permitted source addresses.
+
+
+Built-in shaper
+===============
+
+The shaper supports TBF and HTB queueing disciplines and a clsact policer.
+Build it with SHAPER=TRUE (the default), then enable shaper in the
+configuration's [modules] section.
+
+RADIUS rate attributes accept a single rate or separate downstream/upstream
+rates. The default attribute is Filter-Id. Values are in Kbit/s unless
+Cisco-style attributes are used:
+
+ Filter-Id=1000
+ Filter-Id=2000/3000
+
+The first example sets both directions to 1000 Kbit/s. The second sets the
+downstream rate to 2000 Kbit/s and the upstream rate to 3000 Kbit/s.
+
+Set a custom rate attribute with attr. The named attribute must exist in the
+RADIUS dictionary:
+
+ [shaper]
+ attr=My-Custom-Rate-Attribute
+
+Alternatively, use separate attributes for each direction:
+
+ [shaper]
+ attr-down=PPPD-Downstream-Speed
+ attr-up=PPPD-Upstream-Speed
+
+For Cisco-style attributes:
+
+ [shaper]
+ vendor=Cisco
+ attr=Cisco-AVPair
+
+Send input and output attributes to set both directions. In Cisco-style
+attributes, input controls upstream traffic and output controls downstream
+traffic:
+
+ Cisco-AVPair=lcp:interface-config#1=rate-limit input 2000000 8000 8000 conform-action transmit exceed-action drop
+ Cisco-AVPair=lcp:interface-config#1=rate-limit output 2000000 8000 8000 conform-action transmit exceed-action drop
+
+These examples set a 2000 Kbit/s rate and an 8 KB burst in each direction.
+
+
+Burst configuration
+-------------------
+
+For non-Cisco attributes, configure the factors used to calculate bursts from
+the rate. down-burst-factor applies to downstream TBF/HTB shaping;
+up-burst-factor applies to upstream policing/HTB shaping:
+
+ [shaper]
+ down-burst-factor=1.0
+ up-burst-factor=10.0
+
+
+Time ranges
+-----------
+
+Time ranges can change rates automatically:
+
+ [shaper]
+ time-range=1,1:00-3:00
+ time-range=2,3:00-5:00
+ time-range=3,5:00-7:00
+
+Prefix a rate with its range ID and supply multiple RADIUS attributes:
+
+ Filter-Id=1000
+ Filter-Id=1,2000
+ Filter-Id=2,3000
+ Filter-Id=3,4000
+
+This sets a default of 1000 Kbit/s and rates of 2000, 3000, and 4000 Kbit/s in
+ranges 1, 2, and 3 respectively.
+
+For Cisco-style time ranges, the access-group value is the range ID:
+
+ Cisco-AVPair=lcp:interface-config#1=rate-limit output access-group 1 1000000 8000 8000 conform-action transmit exceed-action drop
+ Cisco-AVPair=lcp:interface-config#1=rate-limit input access-group 1 1000000 8000 8000 conform-action transmit exceed-action drop
+
+When using chap-secrets, an optional fifth column can provide rate information
+in the same format. Time ranges are not supported in chap-secrets.
+
+
+SNMP
+====
+
+Build SNMP support with NETSNMP=TRUE and enable net-snmp in [modules]. ACCEL-PPP
+starts as an AgentX subagent by default, so the Net-SNMP master agent must have
+AgentX enabled. Consult the Net-SNMP AgentX documentation when configuring the
+master agent. To run ACCEL-PPP as the master agent instead:
+
+ [snmp]
+ master=1
+
+Install accel-pppd/extra/net-snmp/ACCEL-PPP-MIB.txt in the local MIB directory.
+The file also contains the numerical OIDs used by ACCEL-PPP. Examples:
+
+ # Read statistics and sessions.
+ snmpwalk -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::accelPPPStat
+ snmptable -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::sessionsTable
+
+ # Terminate sessions by accounting ID, interface, address, or username.
+ snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termBySID.0 = 0000000000000001
+ snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termByIfName.0 = ppp2
+ snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termByIP.0 = 192.0.2.1
+ snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::termByUsername.0 = user1
+
+ # Run a CLI command.
+ snmpset -m +ACCEL-PPP-MIB -v 2c -c local 127.0.0.1 ACCEL-PPP-MIB::cli.0 = "shaper change all 1024 temp"
+
+
+Encrypted chap-secrets
+======================
+
+The chap-secrets module supports encrypted passwords through OpenSSL. Set
+encrypted=1 in the [chap-secrets] section. Usernames may remain in cleartext or
+be transformed through a hash chain configured with username-hash, for example:
+
+ [chap-secrets]
+ encrypted=1
+ username-hash=md5,sha1
+
+Hashed usernames must be hexadecimal digest values. Passwords must contain the
+NT hash produced by smbencrypt. Encrypted secrets are incompatible with the
+auth_chap_md5 module. Hash chains are applied from left to right; for
+username-hash=md5,sha1, the binary MD5 result is passed to SHA-1 and the final
+digest is stored as hexadecimal.
+
+
+Kernel module warning
+=====================
+
+The out-of-tree PPTP module conflicts with the kernel's ip_gre module. Do not
+build ip_gre into the kernel or load it at runtime when using that PPTP module.
+Do not mix ACCEL-PPP PPTP connections with poptop's pptpd; stop existing pptpd
+sessions before starting ACCEL-PPP.
+
+
+More information
+================
+
+* Project website: https://accel-ppp.org/
+* Source and issue tracker: https://github.com/accel-ppp/accel-ppp
+* Additional RADIUS notes: docs/
+* Email: contact@accel-ppp.org
+* ICQ: 337258064
+* Jabber: dima@accel-ppp.org
diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf
index 591a8ca7..e34ae919 100644
--- a/accel-pppd/accel-ppp.conf
+++ b/accel-pppd/accel-ppp.conf
@@ -36,6 +36,7 @@ pppd_compat
[core]
log-error=/var/log/accel-ppp/core.log
+#log-debug=/var/log/accel-ppp/debug.log
thread-count=4
[common]
@@ -43,11 +44,14 @@ thread-count=4
#single-session-ignore-case=0
#sid-case=upper
#sid-source=seq
+#seq-file=/var/lib/accel-ppp/seq
#max-sessions=1000
#max-starting=0
+#session-timeout=0
#check-ip=0
#nl-snd-buffer=32768
#nl-rcv-buffer=1048576
+#netns-run-dir=/var/run/netns
[ppp]
verbose=1
@@ -57,7 +61,14 @@ mru=1400
#accomp=deny
#pcomp=deny
#ccp=0
+#ccp-max-configure=3
#mppe=require
+#timeout=3
+#max-configure=10
+#max-failure=10
+#max-terminate=2
+#max-mtu=1500
+#check-ip=0
ipv4=require
ipv6=deny
ipv6-intf-id=0:0:0:1
@@ -79,7 +90,14 @@ unit-cache=1
[pptp]
verbose=1
+#bind=0.0.0.0
+#port=1723
#echo-interval=30
+#echo-failure=3
+#timeout=5
+#mppe=allow
+#ppp-max-mtu=1436
+#session-timeout=0
#ip-pool=pptp
#ipv6-pool=pptp
#ipv6-pool-delegate=pptp
@@ -89,16 +107,23 @@ verbose=1
verbose=1
#ac-name=xxx
#service-name=yyy
+#accept-any-service=0
+#accept-blank-service=0
#pado-delay=0
#pado-delay=0,100:100,200:200,-1:500
called-sid=mac
#tr101=1
#padi-limit=0
+#mac-filter=/path/to/mac-filter,allow
+#mppe=allow
+#session-timeout=0
#ip-pool=pppoe
#ipv6-pool=pppoe
#ipv6-pool-delegate=pppoe
#ifname=pppoe%d
+#ifname-in-sid=0
#sid-uppercase=0
+#cookie-timeout=5
#vlan-mon=eth0,10-200
#vlan-timeout=60
#vlan-name=%I.%N
@@ -107,6 +132,8 @@ interface=eth0
[l2tp]
verbose=1
+#bind=0.0.0.0
+#port=1701
#dictionary=/usr/local/share/accel-ppp/l2tp/dictionary
#hello-interval=60
#timeout=60
@@ -117,8 +144,14 @@ verbose=1
#host-name=accel-ppp
#dir300_quirk=0
#secret=
+#hide-avps=0
#dataseq=allow
#reorder-timeout=0
+#use-ephemeral-ports=0
+#ppp-max-mtu=1420
+#mppe=allow
+#session-timeout=0
+#avp_permissive=0
#ip-pool=l2tp
#ipv6-pool=l2tp
#ipv6-pool-delegate=l2tp
@@ -126,6 +159,8 @@ verbose=1
[sstp]
verbose=1
+#bind=0.0.0.0
+#port=443
#cert-hash-proto=sha1,sha256
#cert-hash-sha1=
#cert-hash-sha256=
@@ -142,6 +177,11 @@ verbose=1
#http-error=allow
#timeout=60
#hello-interval=60
+#ppp-mode=auto
+#ppp-max-mtu=1452
+#sndbuf=0
+#rcvbuf=0
+#session-timeout=0
#ip-pool=sstp
#ipv6-pool=sstp
#ipv6-pool-delegate=sstp
@@ -151,6 +191,7 @@ verbose=1
verbose=1
username=ifname
#password=username
+#netmask=24
lease-time=600
#renew-time=300
#rebind-time=525
@@ -160,6 +201,7 @@ max-lease-time=3600
#l4-redirect-ipset=l4
#l4-redirect-on-reject=300
#l4-redirect-ip-pool=pool1
+#noauth=0
shared=0
ifcfg=1
mode=L2
@@ -169,7 +211,12 @@ start=dhcpv4
#proxy-arp=0
#nat=0
#proto=100
+#src=192.0.2.1
#relay=10.10.10.10
+#relay-timeout=3
+#relay-retransmit=3
+#agent-remote-id=accel-ppp
+#link-selection=0.0.0.0
#vendor=Custom
#weight=0
#attr-dhcp-client-ip=DHCP-Client-IP-Address
@@ -186,6 +233,7 @@ start=dhcpv4
#attr-l4-redirect-ipset=l4-redirect
#lua-file=/etc/accel-ppp.lua
#offer-delay=0,100:100,200:200,-1:1000
+#offer-timeout=10
#vlan-mon=eth0,10-200
#vlan-timeout=60
#vlan-name=%I.%N
@@ -197,6 +245,8 @@ start=dhcpv4
#soft-terminate=0
#check-mac-change=1
#calling-sid=mac
+#ipv6=0
+#check-ip=0
#local-net=192.168.0.0/16
interface=eth0
@@ -221,6 +271,10 @@ dae-server=127.0.0.1:3799,testing123
verbose=1
#timeout=3
#max-try=3
+#req-limit=0
+#fail-timeout=0
+#max-fail=10
+#interim-verbose=0
#acct-timeout=120
#acct-delay-time=0
#acct-delay-start=0
@@ -230,7 +284,10 @@ verbose=1
#default-realm=
#strip-realm=0
#attr-tunnel-type=My-Tunnel-Type
+#sid-in-auth=0
#nas-port-id-in-req=1
+#bind=0.0.0.0
+#require-nas-identification=0
blast-protection=1
[client-ip-range]
@@ -267,6 +324,8 @@ level=3
[log-pgsql]
conninfo=user=log
+#connect-inteval=5
+#log-query=insert into log (timestamp, username, sessionid, msg) values ($1, $2, $3, $4)
log-table=log
[pppd-compat]
@@ -286,6 +345,10 @@ gw-ip-address=192.168.100.1
[shaper]
#attr=Filter-Id
+#vendor=Cisco
+#attr-down=PPPD-Downstream-Speed
+#attr-up=PPPD-Upstream-Speed
+#burst-factor=0.1
#down-burst-factor=0.1
#up-burst-factor=1.0
#latency=50
@@ -310,6 +373,8 @@ verbose=1
telnet=127.0.0.1:2000
tcp=127.0.0.1:2001
#password=123
+#prompt=accel-ppp
+#history-file=/var/lib/accel-ppp/cli-history
#sessions-columns=ifname,username,ip,ip6,ip6-dp,type,state,uptime,uptime-raw,calling-sid,called-sid,sid,comp,inbound-if,service-name,rx-bytes,tx-bytes,rx-bytes-raw,tx-bytes-raw,rx-pkts,tx-pkts,netns,vrf
[snmp]
@@ -327,6 +392,14 @@ timeout=60
#format=prometheus
#address=127.0.0.1:8080
#allowed_ips=["127.0.0.1/32"]
+#read_timeout=5
+#max_clients=64
+# Include per-session identity and traffic data in JSON output. This has no
+# effect with format=prometheus. Disabled by default because usernames and
+# station IDs may be sensitive: restrict allowed_ips before enabling it.
+# Traffic counters are the ones last sampled by accounting, so their freshness
+# follows radius interim-interval / idle-timeout.
+#sessions=0
[ipv6-pool]
#gw-ip6-address=fc00:0:1::1
@@ -347,9 +420,29 @@ delegate=fc00:3::/36,48,name=pool4,next=pool3
#fc00:1::3
#dnssl=suffix1.local.net
#dnssl=suffix2.local.net.
+#lifetime=600
[ipv6-dhcp]
verbose=1
pref-lifetime=604800
valid-lifetime=2592000
route-via-gw=1
+#aftr-gw=aftr.example.net
+#server-id=0:0:0:1
+
+[ipv6-nd]
+#MaxRtrAdvInterval=600
+#MinRtrAdvInterval=198
+#MaxInitialRtrAdvCount=5
+#MaxInitialRtrAdvInterval=3
+#AdvManagedFlag=0
+#AdvOtherConfigFlag=0
+#AdvLinkMTU=0
+#AdvReachableTime=0
+#AdvRetransTimer=0
+#AdvCurHopLimit=64
+#AdvDefaultLifetime=1800
+#AdvValidLifetime=2592000
+#AdvPreferredLifetime=604800
+#AdvOnLinkFlag=1
+#AdvAutonomousFlag=1
diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5
index 7c72233e..07fb363d 100644
--- a/accel-pppd/accel-ppp.conf.5
+++ b/accel-pppd/accel-ppp.conf.5
@@ -49,6 +49,14 @@ PPPoE discovery stage handling module.
.br
SSTP controlling connection handling module.
.TP
+.BI l2tp
+.br
+L2TPv2 connection handling module.
+.TP
+.BI ipoe
+.br
+IPoE connection handling module.
+.TP
.BI auth_pap
PAP authentication module.
.TP
@@ -65,6 +73,10 @@ Microsoft CHAP (version 2) authentication module.
.br
RADIUS interaction module.
.TP
+.BI chap-secrets
+.br
+Authentication and address assignment from a pppd-compatible chap-secrets file.
+.TP
.BI ippool
.br
IPv4 address assigning module.
@@ -77,6 +89,26 @@ IPv6 address assigning module.
.br
IPv6 DHCP module.
.TP
+.BI ipv6_nd
+.br
+IPv6 Neighbor Discovery and Router Advertisement module.
+.TP
+.BI shaper
+.br
+Traffic shaping and policing module.
+.TP
+.BI net-snmp
+.br
+SNMP agent module. It must be built with NETSNMP enabled.
+.TP
+.BI logwtmp
+.br
+Module that writes session records to wtmp.
+.TP
+.BI metrics
+.br
+HTTP statistics endpoint module.
+.TP
.BI sigchld
Helper module to manage child processes, required by pppd_compat
.TP
@@ -91,6 +123,9 @@ Configuration of core module
.BI "log-error=" path
Path to file for core module error logging.
.TP
+.BI "log-debug=" path
+Path to file for core module debug logging.
+.TP
.BI "thread-count=" n
number of working threads, optimal - number of processors/cores
.SH [common]
@@ -116,7 +151,7 @@ Specifies whether accel-ppp should ignore the case when comparing username withi
Specifies in which case generate session identifier (default lower).
.TP
.BI "sid-source=" urandom|seq
-Specifies method assign session id (default urandom).
+Specifies method used to assign session IDs (default seq).
.br
.B urandom
Assign session id by random method
@@ -143,6 +178,9 @@ Specifies netlink maximum send buffer size (SO_SNDBUF option) (default 32768).
.BI "nl-rcv-buffer=" n
Specifies netlink maximum receive buffer size (SO_RCVBUF option) (default 1048576).
.TP
+.BI "netns-run-dir=" path
+Directory containing named network namespace handles (default /var/run/netns).
+.TP
.BI "session-timeout=" n
Specifies max sessions time in seconds, after this time session will be terminated.
.br
@@ -185,7 +223,10 @@ Protocol field compression negotiation.
Disable CCP negotiation if this parameter is zero.
.TP
.BI "ccp-max-configure=" n
-Specifies the maximum number of CCP Configure-Request packets sent without receiving a valid Configure-Ack, Configure-Nak or Configure-Reject before assuming the peer is unable to respond (default 10).
+Specifies the maximum number of CCP Configure-Request packets sent without receiving a valid Configure-Ack, Configure-Nak or Configure-Reject before assuming the peer is unable to respond (default 3).
+.TP
+.BI "timeout=" n
+Specifies the timeout, in seconds, used by PPP finite-state machines (default 3).
.TP
.BI "mppe=" require|prefer|deny
Specifies mppe negotiation preference.
@@ -540,6 +581,81 @@ If enabled accel-ppp will terminate session when detects change of mac address o
.TP
.BI "calling-sid=" mac|ip
Specifies value of Calling-Station-Id attribute.
+.TP
+.BI "netmask=" n
+Specifies the IPv4 prefix length assigned to IPoE sessions (1 through 32).
+.TP
+.BI "nat=" 0|1
+Marks IPoE sessions as requiring NAT.
+.TP
+.BI "noauth=" 0|1
+Authorizes IPoE sessions without consulting a password database. The default is 0.
+.TP
+.BI "check-ip=" 0|1
+Overrides the common duplicate-address check for IPoE sessions.
+.TP
+.BI "ipv6=" 0|1
+Enables IPv6 on IPoE interfaces by default.
+.TP
+.BI "src=" x.x.x.x
+Specifies the source address used by the IPoE service.
+.TP
+.BI "relay-timeout=" n
+Specifies the DHCP relay reply timeout in seconds (default 3).
+.TP
+.BI "relay-retransmit=" n
+Specifies the maximum number of DHCP relay retransmissions (default 3).
+.TP
+.BI "ip-pool=" pool_name
+Specifies the default IPv4 address pool.
+.TP
+.BI "ipv6-pool=" pool_name
+Specifies the default IPv6 address pool.
+.TP
+.BI "ipv6-pool-delegate=" pool_name
+Specifies the default delegated IPv6 prefix pool.
+.TP
+.BI "lua-file=" path
+Loads the Lua file that supplies IPoE Lua hooks. Lua support must be built in.
+.TP
+.BI "vendor=" vendor
+Specifies the RADIUS vendor used when resolving the IPoE attribute options below.
+.TP
+.BI "attr-dhcp-client-ip=" attribute
+Specifies the RADIUS attribute containing the DHCP client address.
+.TP
+.BI "attr-dhcp-router-ip=" attribute
+Specifies the RADIUS attribute containing the DHCP router address.
+.TP
+.BI "attr-dhcp-mask=" attribute
+Specifies the RADIUS attribute containing the DHCP subnet mask.
+.TP
+.BI "attr-dhcp-lease-time=" attribute
+Specifies the RADIUS attribute containing the DHCP lease time.
+.TP
+.BI "attr-dhcp-renew-time=" attribute
+Specifies the RADIUS attribute containing the DHCP renewal time.
+.TP
+.BI "attr-dhcp-rebind-time=" attribute
+Specifies the RADIUS attribute containing the DHCP rebinding time.
+.TP
+.BI "attr-dhcp-opt82=" attribute
+Specifies the RADIUS attribute used for the complete DHCP Option 82 value.
+.TP
+.BI "attr-dhcp-opt82-remote-id=" attribute
+Specifies the RADIUS attribute used for the Option 82 remote ID.
+.TP
+.BI "attr-dhcp-opt82-circuit-id=" attribute
+Specifies the RADIUS attribute used for the Option 82 circuit ID.
+.TP
+.BI "attr-l4-redirect=" attribute
+Specifies the RADIUS attribute that enables Layer-4 redirection.
+.TP
+.BI "attr-l4-redirect-table=" attribute
+Specifies the RADIUS attribute containing the redirection routing-table ID.
+.TP
+.BI "attr-l4-redirect-ipset=" attribute
+Specifies the RADIUS attribute containing the redirection ipset name.
.SH [dns]
.TP
.BI "dns1=" x.x.x.x
@@ -561,6 +677,9 @@ Specifies IPv6 DNS to be sent to peer. You may specify up to 3 dns options.
.TP
.BI "dnssl=" name
Specify DNS Search List. You may specify multiple dns and dnssl options.
+.TP
+.BI "lifetime=" n
+Specifies the RDNSS and DNSSL lifetime advertised by the ipv6_nd module.
.SH [client-ip-range]
You have to explicitly specify range of ip address from which clients can connect to server in form:
.br
@@ -610,6 +729,15 @@ sessions. Default value is 1436.
Specifies max sessions time in seconds, after this time session will be terminated.
.br
This timeout can be redefined with radius attribute Session-Timeout. 0 value means infinite timeout.
+.TP
+.BI "ip-pool=" pool_name
+Specifies the name of the IPv4 pool used by PPTP sessions.
+.TP
+.BI "ipv6-pool=" pool_name
+Specifies the name of the IPv6 pool used by PPTP sessions.
+.TP
+.BI "ipv6-pool-delegate=" pool_name
+Specifies the name of the delegated IPv6 prefix pool used by PPTP sessions.
.SH [pppoe]
.br
Configuration of PPPoE module.
@@ -653,9 +781,11 @@ By default vlan-name=%I.%N.
.TP
.BI "ac-name=" ac-name
Specifies AC-Name tag value. If absent tag will not be sent.
+The compatibility alias AC-Name=ac-name is also accepted.
.TP
.BI "service-name=" service-name
Specifies Service-Name to respond. If absent any Service-Name is acceptable and client's Service-Name will be sent back.
+The compatibility alias Service-Name=service-name is also accepted.
.TP
.BI "accept-any-service=" n
If service-name specified still will answer with service names, but accepts any service name in PADR request. Useful
@@ -667,6 +797,7 @@ Useful when needs to allow only Service-Names described in the config and also b
.TP
.BI "pado-delay=" delay[,delay1:count1[,delay2:count2[,...]]]
Specifies delays (also in condition of connection count) to send PADO (ms).
+The compatibility alias PADO-Delay=delay is also accepted.
Last delay in list may be -1 which means don't accept new connections.
List have to be sorted by count key.
.TP
@@ -732,6 +863,9 @@ Specifies the timeout in seconds for the PPPoE cookie (PADO/PADR stage).
.br
Configuration of L2TP module.
.TP
+.BI "dictionary=" path
+Specifies an alternate L2TP attribute dictionary.
+.TP
.BI "bind=" x.x.x.x
Specifies IP address to bind.
.TP
@@ -861,11 +995,26 @@ Timeout waiting reply from client in seconds.
Default is 60.
.TP
.BI "hello-interval=" n
-If this option is given and greater than zero then sstp will send echo-request every
+If this option is given and greater than zero then sstp will send echo-request every
.B n
seconds and drop connection without a reply.
Default is 60.
.TP
+.BI "ppp-mode=" auto|seqpacket|async
+Specifies transport between sstp and the kernel ppp layer.
+.br
+.B seqpacket
+- use the ppposeq module. One datagram carries one ppp frame, so no
+HDLC framing is done in userspace.
+.br
+.B async
+- use a pty with ppp_async and frame in userspace. Works on any kernel.
+.br
+.B auto
+- use seqpacket if the module is available, otherwise async.
+.br
+Default is auto.
+.TP
.BI "accept=" ssl,proxy
Specifies incoming connection acceptance mode.
.br
@@ -882,6 +1031,9 @@ This is useful when accel-ppp is running behind a load balancer (like HAProxy or
- \fBCalling-Station-Id\fR attribute in RADIUS and logs.
.br
.TP
+.BI "ssl=" 0|1
+Deprecated compatibility option that enables legacy SSL mode. Use accept=ssl instead.
+.TP
.BI "ssl-protocol=" ssl2|ssl3|tls1|tls1.1|tls1.2|tls1.3
Specifies the enabled SSL/TLS protocols supported by OpenSSL library.
.TP
@@ -915,6 +1067,9 @@ If not set, secret key will be loaded from the
.BI pemfile
parameter.
.TP
+.BI "ssl-ca-file=" file
+Specifies a CA bundle loaded into the SSTP TLS context.
+.TP
.BI "cert-hash-proto=" sha1,sha256
Specifies hashing methods that can be used to compute the Compound MAC in the Crypto Binding attribute.
Default is sha1 and sha256 both.
@@ -981,6 +1136,9 @@ This timeout can be redefined with radius attribute Session-Timeout. 0 value mea
.br
Configuration of RADIUS module.
.TP
+.BI "dictionary=" path
+Specifies an alternate RADIUS dictionary.
+.TP
.BI "nas-identifier=" identifier
Specifies value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests.
.TP
@@ -1059,6 +1217,18 @@ Timeout to wait response from server (sec)
.BI "max-try=" n
Specifies number of tries to send Access-Request/Accounting-Request queries.
.TP
+.BI "req-limit=" n
+Specifies the global limit of simultaneous requests per RADIUS server. Zero means unlimited.
+.TP
+.BI "fail-timeout=" n
+Specifies how long, in seconds, an unresponsive RADIUS server remains unavailable.
+.TP
+.BI "max-fail=" n
+Specifies the number of consecutive unanswered requests before a RADIUS server is marked unavailable.
+.TP
+.BI "fail-time=" n
+Deprecated alias for fail-timeout.
+.TP
.BI "acct-timeout=" n
Specifies timeout of accounting interim update.
.TP
@@ -1117,6 +1287,12 @@ Path to file to write emergency messages.
.BI "log-fail-file=" file
Path to file to write authentication failed session log.
.TP
+.BI "log-debug=" file
+Path to file to write debug messages.
+.TP
+.BI "color=" 0|1
+Enables ANSI color sequences in file log output.
+.TP
.BI "log-tcp=" x.x.x.x:port
Send logs to specified host.
.TP
@@ -1165,6 +1341,14 @@ Configuration of log_pgsql module.
.BI "conninfo=" conninfo
Conninfo to connect to PostgreSQL server.
.TP
+.BI "connect-inteval=" n
+Specifies the PostgreSQL reconnect interval in seconds (default 5).
+The option name intentionally preserves its historical spelling.
+.TP
+.BI "log-query=" query
+Specifies a custom parameterized INSERT query. Parameters $1 through $4 are
+timestamp, username, session ID, and message respectively.
+.TP
.BI "log-table=" table
Table to send log messages. Table must contain following field:
.br
@@ -1179,7 +1363,7 @@ text
.br
.B msg
text
-.SH [pppd_compat]
+.SH [pppd-compat]
.br
Configuration of pppd_compat module.
.TP
@@ -1200,6 +1384,10 @@ Prefix of radattr files (for example /var/run/radattr, resulting files will be /
.TP
.BI "verbose=" n
If specified and greater than zero pppd_module will produce verbose logging.
+.TP
+.BI "fork-limit=" n
+Limits the number of concurrently running compatibility scripts. The default is
+twice the number of online processors.
.SH [chap-secrets]
.br
Configuration of chap-secrets module.
@@ -1211,7 +1399,7 @@ Specifies address to use as local address of ppp interfaces if chap-secrets is u
Specifies alternate chap-secrets file location (default is /etc/ppp/chap-secrets).
.TP
.BI "encrypted=" 0|1
-Specifies either chap-secrets is encrypted (read README).
+Specifies whether chap-secrets is encrypted (see README.md).
.TP
.BI "username-hash=" hash1[,hash2]
Specifies hash chain to calculate username hash.
@@ -1268,8 +1456,71 @@ If attribute is vendor-specific then specify vendor name in this option.
Note: overlapping ranges in different pools are not de-duplicated. If two pools cover the same address, that IP can be allocated from each pool independently. Use non-overlapping ranges (or a single pool with next= chaining) to avoid duplicate assignments.
.SH [ipv6-dhcp]
.TP
+.BI "verbose=" 0|1
+Enables verbose DHCPv6 logging.
+.TP
+.BI "pref-lifetime=" n
+Specifies the preferred address and prefix lifetime in seconds (default 604800).
+.TP
+.BI "valid-lifetime=" n
+Specifies the valid address and prefix lifetime in seconds (default 2592000).
+.TP
+.BI "route-via-gw=" 0|1
+Controls whether delegated IPv6 routes are installed through the session gateway (default 1).
+.TP
+.BI "server-id=" x:x:x:x
+Specifies the four 16-bit hexadecimal components used as the DHCPv6 server identifier.
+.TP
.BI "aftr-gw=" name
Specifies the AFTR-Name option value to be returned on DHCPv6 replies upon request from the client.
+.SH [ipv6-nd]
+.br
+Configuration of IPv6 Neighbor Discovery and Router Advertisements.
+.TP
+.BI "MaxRtrAdvInterval=" n
+Maximum unsolicited Router Advertisement interval in seconds (default 600).
+.TP
+.BI "MinRtrAdvInterval=" n
+Minimum unsolicited Router Advertisement interval in seconds (default one third of MaxRtrAdvInterval).
+.TP
+.BI "MaxInitialRtrAdvCount=" n
+Maximum number of initial Router Advertisements (default 5).
+.TP
+.BI "MaxInitialRtrAdvInterval=" n
+Maximum interval between initial Router Advertisements in seconds (default 3).
+.TP
+.BI "AdvManagedFlag=" 0|1
+Sets the Managed Address Configuration flag. By default it follows whether ipv6_dhcp is loaded.
+.TP
+.BI "AdvOtherConfigFlag=" 0|1
+Sets the Other Configuration flag. By default it follows whether ipv6_dhcp is loaded.
+.TP
+.BI "AdvLinkMTU=" n
+Specifies the MTU advertised in Router Advertisements. Zero omits the MTU option.
+.TP
+.BI "AdvReachableTime=" n
+Specifies the advertised reachable time in milliseconds.
+.TP
+.BI "AdvRetransTimer=" n
+Specifies the advertised retransmission timer in milliseconds.
+.TP
+.BI "AdvCurHopLimit=" n
+Specifies the advertised current hop limit (default 64).
+.TP
+.BI "AdvDefaultLifetime=" n
+Specifies the router lifetime in seconds (default three times MaxRtrAdvInterval).
+.TP
+.BI "AdvValidLifetime=" n
+Specifies the advertised prefix valid lifetime in seconds (default 2592000).
+.TP
+.BI "AdvPreferredLifetime=" n
+Specifies the advertised prefix preferred lifetime in seconds (default 604800).
+.TP
+.BI "AdvOnLinkFlag=" 0|1
+Sets the advertised prefix on-link flag (default 1).
+.TP
+.BI "AdvAutonomousFlag=" 0|1
+Sets the autonomous address-configuration flag. By default it is disabled when ipv6_dhcp is loaded and enabled otherwise.
.SH [ipv6-pool]
.br
Configuration of ipv6pool module.
@@ -1321,7 +1572,9 @@ section. The same numbers shown by
.B "accel-cmd show stat"
are returned for every GET on
.B /metrics
-\&. Any other path responds with 404, and any method other than GET
+\&, optionally followed by per session details in the JSON format, see
+.BR sessions .
+Any other path responds with 404, and any method other than GET
responds with 405.
.TP
.BI "format=" prometheus|json
@@ -1350,7 +1603,7 @@ after
.BI "read_timeout=" seconds
Maximum time a client may take to deliver a complete HTTP request.
Connections that have not produced a terminating
-.BR \(dq\\r\\n\\r\\n\(dq
+.B \(dq\er\en\er\en\(dq
within this deadline are closed. Defaults to 5 seconds. Set to
.B 0
to disable the deadline.
@@ -1360,6 +1613,28 @@ Maximum number of in-flight HTTP clients. Excess connections are
accepted then immediately closed. Defaults to 64. Set to
.B 0
to disable the cap.
+.TP
+.BI "sessions=" 0|1
+Adds a
+.B session_details
+array to the JSON body holding per session identity, addressing,
+protocol state, interface context, uptime and traffic counters. Has no
+effect with
+.BR format = prometheus ,
+which stays aggregate only. Defaults to
+.BR 0 .
+.br
+Two things to weigh before enabling it. Usernames, calling and called
+station identifiers become readable by every peer
+.B allowed_ips
+permits, which is every peer when that option is unset. And the traffic
+counters are the ones last sampled by accounting rather than a fresh
+read of the interface, so they are only as current as the
+.B radius
+.B interim-interval
+or the session
+.B idle-timeout
+makes them.
.SH [connlimit]
.br
This module limits connection rate from single source.
@@ -1378,6 +1653,9 @@ This module controls shaper.
.BI "attr=" name
Specifies which radius attribute contains rate information. Default - Filter-ID.
.TP
+.BI "vendor=" vendor
+Specifies the RADIUS vendor for vendor-specific rate attributes.
+.TP
.BI "attr-up=" name
.TP
.BI "attr-down=" name
@@ -1397,6 +1675,9 @@ Specifies latency (in milliseconds) parameter of tbf qdisc.
.BI "mpu=" n
Specifies mpu parameter of tbf qdisc and policer.
.TP
+.BI "mtu=" n
+Specifies the MTU used in shaper calculations.
+.TP
.BI "r2q=" n
Specifies r2q parameter of root htb qdisc.
.TP
@@ -1406,6 +1687,12 @@ Specifies quantum parameter of htb classes.
.BI "moderate-quantum=" 1|0
If fixed quantum is not specified and this option is specified then shaper module will check for quantum value is valid (in range 1000-200000).
.TP
+.BI "cburst=" n
+Specifies the HTB class ceil burst in bytes (default 1534).
+.TP
+.BI "ifb=" ifname
+Specifies the IFB interface used for upstream HTB shaping. Without it, upstream HTB falls back to policing.
+.TP
.BI "fwmark=" n
Specifies the fwmark for traffic that won't be passed through shaper.
.TP
@@ -1428,6 +1715,9 @@ Due to accel-ppp operates with rates in kilobit basis if you send rates in diffe
.TP
.BI "rate-limit=" download_speed/upload_speed
Specifies, should accel-ppp set default rate-limit for clients. Clients rate-limit will be overwritten by RADIUS filter attributes or chap-secrets rate-limit params.
+.TP
+.BI "verbose=" 0|1
+Enables verbose shaper logging.
.SH [cli]
.br
Configuration of the command line interface.
@@ -1543,3 +1833,12 @@ command (defaults to
- virtual routing and forwarding
.br
Invalid column names are silently discarded.
+.SH [snmp]
+.br
+Configuration of the optional net-snmp module.
+.TP
+.BI "master=" 0|1
+Runs as an AgentX subagent when set to 0 (the default), or as the SNMP master agent when set to 1.
+.TP
+.BI "agent-name=" name
+Specifies the AgentX subagent name (default accel-ppp).
diff --git a/accel-pppd/ctrl/sstp/if_ppposeq.h b/accel-pppd/ctrl/sstp/if_ppposeq.h
new file mode 120000
index 00000000..f525ccdb
--- /dev/null
+++ b/accel-pppd/ctrl/sstp/if_ppposeq.h
@@ -0,0 +1 @@
+../../../drivers/ppposeq/ppposeq.h \ No newline at end of file
diff --git a/accel-pppd/ctrl/sstp/sstp.c b/accel-pppd/ctrl/sstp/sstp.c
index 6c2b9ca9..2fd8cb35 100644
--- a/accel-pppd/ctrl/sstp/sstp.c
+++ b/accel-pppd/ctrl/sstp/sstp.c
@@ -4,6 +4,7 @@
#include <stdarg.h>
#include <errno.h>
#include <string.h>
+#include <inttypes.h>
#include <fcntl.h>
#include <time.h>
#include <termios.h>
@@ -25,6 +26,7 @@
*/
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/ssl.h>
+#include <openssl/dh.h>
#include <openssl/err.h>
#include "triton.h"
@@ -43,6 +45,7 @@
#include "proxy_prot.h"
#include "sstp.h"
#include "sstp_prot.h"
+#include "if_ppposeq.h"
#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
@@ -51,7 +54,6 @@
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif
-#define PPP_SYNC 0 /* buggy yet */
#define PPP_BUF_SIZE 8192
#define PPP_BUF_IOVEC 256
#define PPP_F_ESCAPE 1
@@ -75,6 +77,20 @@ enum {
STATE_FINISHED,
};
+enum {
+ HTTP_ERR_ALLOW = -1,
+ HTTP_ERR_DENY = 0,
+ HTTP_ERR_REDIRECT = 1,
+ HTTP_ERR_REDIRECT_APPEND = 2,
+};
+
+/* seqpacket needs the ppposeq module, async is the legacy pty path */
+enum {
+ PPP_MODE_AUTO = -1,
+ PPP_MODE_ASYNC = 0,
+ PPP_MODE_SEQPACKET = 1,
+};
+
struct sockaddr_t {
socklen_t len;
union {
@@ -141,6 +157,7 @@ struct sstp_conn_t {
int ppp_state;
int ppp_flags;
+ int ppp_mode;
struct buffer_t *ppp_in;
struct list_head ppp_queue;
@@ -165,6 +182,7 @@ static int conf_timeout = SSTP_NEGOTIOATION_TIMEOUT;
static int conf_hello_interval = SSTP_HELLO_TIMEOUT;
static int conf_verbose = 0;
static int conf_ppp_max_mtu = 1452;
+static int conf_ppp_mode = PPP_MODE_AUTO;
static const char *conf_ip_pool;
static const char *conf_ipv6_pool;
static const char *conf_dpv6_pool;
@@ -179,12 +197,6 @@ static struct hash_t conf_hash_sha1 = { .len = 0 };
static struct hash_t conf_hash_sha256 = { .len = 0 };
//static int conf_bypass_auth = 0;
static const char *conf_hostname = NULL;
-enum {
- HTTP_ERR_ALLOW = -1,
- HTTP_ERR_DENY = 0,
- HTTP_ERR_REDIRECT = 1,
- HTTP_ERR_REDIRECT_APPEND = 2,
-};
static int conf_http_mode = HTTP_ERR_ALLOW;
static const char *conf_http_url = NULL;
@@ -193,7 +205,6 @@ static mempool_t conn_pool;
static inline void sstp_queue(struct sstp_conn_t *conn, struct buffer_t *buf);
static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf);
static inline void sstp_queue_deferred(struct sstp_conn_t *conn, struct buffer_t *buf);
-static int sstp_write(struct triton_md_handler_t *h);
static int sstp_read_deferred(struct sstp_conn_t *conn);
static int sstp_abort(struct sstp_conn_t *conn, int disconnect);
static void sstp_disconnect(struct sstp_conn_t *conn);
@@ -867,7 +878,7 @@ static char *http_getvalue(char *line, const char *name, int len)
return sep ? line : NULL;
}
-static int http_send_response(struct sstp_conn_t *conn, char *proto, char *status, char *headers)
+static int http_send_response(struct sstp_conn_t *conn, char *proto, char *status, char *headers, u_int64_t length)
{
char datetime[sizeof("aaa, dd bbb yyyy HH:MM:SS GMT")];
char linebuf[1024], *line;
@@ -880,7 +891,12 @@ static int http_send_response(struct sstp_conn_t *conn, char *proto, char *statu
/* "Server: %s\r\n" */
"Date: %s\r\n"
"%s"
- "\r\n", proto, status, /* "accel-ppp",*/ datetime, headers ? : "");
+ "Content-Length: %" PRIu64 "\r\n"
+ "Connection: %s\r\n"
+ "\r\n",
+ proto, status, /* "accel-ppp",*/ datetime,
+ headers ? : "",
+ length, length ? "keep-alive" : "close");
if (!buf) {
log_error("sstp: no memory\n");
return -1;
@@ -895,7 +911,7 @@ static int http_send_response(struct sstp_conn_t *conn, char *proto, char *statu
}
}
- return sstp_send(conn, buf) || sstp_write(&conn->hnd);
+ return sstp_send(conn, buf);
}
static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
@@ -917,17 +933,17 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
if (vstrsep(line, " ", &method, &request, &proto) < 3) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL);
+ http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL, 0);
return -1;
}
if (strncasecmp(proto, "HTTP/1", sizeof("HTTP/1") - 1) != 0) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL);
+ http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL, 0);
return -1;
}
if (strcasecmp(method, SSTP_HTTP_METHOD) != 0 && strcasecmp(method, "GET") != 0) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, proto, "501 Not Implemented", NULL);
+ http_send_response(conn, proto, "501 Not Implemented", NULL, 0);
return -1;
}
@@ -949,7 +965,7 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
if (host_error) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, proto, "404 Not Found", NULL);
+ http_send_response(conn, proto, "404 Not Found", NULL, 0);
return -1;
}
@@ -958,15 +974,14 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
if (_asprintf(&line, "Location: %s%s\r\n",
conf_http_url, (conf_http_mode == HTTP_ERR_REDIRECT_APPEND) ? request : "") < 0)
return -1;
- http_send_response(conn, proto, "301 Moved Permanently", line);
+ http_send_response(conn, proto, "301 Moved Permanently", line, 0);
_free(line);
} else if (conf_http_mode == HTTP_ERR_ALLOW)
- http_send_response(conn, proto, "404 Not Found", NULL);
+ http_send_response(conn, proto, "404 Not Found", NULL, 0);
return -1;
}
- return http_send_response(conn, proto, "200 OK",
- "Content-Length: 18446744073709551615\r\n");
+ return http_send_response(conn, proto, "200 OK", NULL, -1);
}
static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
@@ -974,7 +989,7 @@ static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
static const char *table[] = { "\n\r\n", "\r\r\n", NULL };
const char **pptr;
uint8_t *ptr, *end = NULL;
- int n, r;
+ int n;
if (conn->sstp_state != STATE_SERVER_CALL_DISCONNECTED)
return -1;
@@ -1000,11 +1015,8 @@ static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
} else
n = end - buf->head;
- r = http_recv_request(conn, buf->head, n);
- if (r < 0)
+ if (http_recv_request(conn, buf->head, n) < 0)
return -1;
- else if (r > 0)
- return 1;
buf_pull(buf, n);
conn->sstp_state = STATE_SERVER_CONNECT_REQUEST_PENDING;
@@ -1046,24 +1058,12 @@ static int ppp_allocate_pty(int *master, int *slave, int flags)
goto error;
}
-#if PPP_SYNC
- value = N_SYNC_PPP;
-#else
value = N_PPP;
-#endif
if (ioctl(sfd, TIOCSETD, &value) < 0) {
log_ppp_error("sstp: ppp: set pty line discipline: %s\n", strerror(errno));
goto error;
}
-#if PPP_SYNC
- value = N_HDLC;
- if (ioctl(mfd, TIOCSETD, &value) < 0) {
- log_ppp_error("sstp: ppp: set pty line discipline: %s\n", strerror(errno));
- goto error;
- }
-#endif
-
if ((value = fcntl(mfd, F_GETFL)) < 0 || fcntl(mfd, F_SETFL, value | flags) < 0 ||
(value = fcntl(sfd, F_GETFL)) < 0 || fcntl(sfd, F_SETFL, value | flags) < 0) {
log_ppp_error("sstp: ppp: set pty status flags: %s\n", strerror(errno));
@@ -1080,6 +1080,59 @@ error:
return -1;
}
+/*
+ * ppposeq channel: the socket is both the ppp endpoint we exchange frames
+ * on and the fd establish_ppp() gets the channel from, as pppox_ioctl
+ * answers PPPIOCGCHAN on it. One datagram is one frame, so no framing.
+ */
+static int ppp_allocate_seq(int *master, int *slave, int flags)
+{
+ struct sockaddr_ppposeq sa = {
+ .sa_family = AF_PPPOX,
+ .sa_protocol = PX_PROTO_OSEQ,
+ };
+ int value, mfd, sfd;
+
+ mfd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ if (mfd < 0) {
+ log_ppp_error("sstp: ppp: create socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (connect(mfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+ log_ppp_error("sstp: ppp: connect socket: %s\n", strerror(errno));
+ goto error_mfd;
+ }
+
+ sfd = dup(mfd);
+ if (sfd < 0) {
+ log_ppp_error("sstp: ppp: dup socket: %s\n", strerror(errno));
+ goto error_mfd;
+ }
+
+ if (flags & O_CLOEXEC) {
+ fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC);
+ fcntl(sfd, F_SETFD, fcntl(sfd, F_GETFD) | FD_CLOEXEC);
+ flags &= ~O_CLOEXEC;
+ }
+
+ /* status flags are inherited by shared file descriptors */
+ if ((value = fcntl(mfd, F_GETFL)) < 0 || fcntl(mfd, F_SETFL, value | flags) < 0) {
+ log_ppp_error("sstp: ppp: set socket status flags: %s\n", strerror(errno));
+ goto error;
+ }
+
+ *master = mfd;
+ *slave = sfd;
+ return 0;
+
+error:
+ close(sfd);
+error_mfd:
+ close(mfd);
+ return -1;
+}
+
static void ppp_started(struct ap_session *ses)
{
struct ppp_t *ppp = container_of(ses, typeof(*ppp), ses);
@@ -1113,18 +1166,16 @@ static void ppp_finished(struct ap_session *ses)
}
}
-static int ppp_read(struct triton_md_handler_t *h)
+static int ppp_read_pty(struct triton_md_handler_t *h)
{
struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
struct buffer_t *buf;
struct sstp_hdr *hdr;
uint8_t pppbuf[PPP_BUF_SIZE], *src;
int i, n;
-#if !PPP_SYNC
uint8_t byte;
buf = conn->ppp_in;
-#endif
while (1) {
n = read(h->fd, pppbuf, sizeof(pppbuf));
if (n < 0) {
@@ -1149,29 +1200,7 @@ static int ppp_read(struct triton_md_handler_t *h)
}
src = pppbuf;
-#if PPP_SYNC
- while (n > 0) {
- if (src[0] == PPP_ALLSTATIONS)
- i = conn->ppp.mtu + 4 - (src[2] & 1);
- else
- i = conn->ppp.mtu + 2 - (src[0] & 1);
- if (i > n)
- i = n;
- buf = alloc_buf(i + sizeof(*hdr));
- if (!buf) {
- log_ppp_error("sstp: ppp: no memory\n");
- goto drop;
- }
- hdr = buf_put(buf, sizeof(*hdr));
- buf_put_data(buf, src, i);
- INIT_SSTP_DATA_HDR(hdr, buf->len);
- sstp_queue(conn, buf);
-
- n -= i;
- src += i;
- }
-#else
if (!buf) {
alloc:
conn->ppp_in = buf = alloc_buf(SSTP_MAX_PACKET_SIZE + PPP_FCSLEN);
@@ -1207,6 +1236,11 @@ static int ppp_read(struct triton_md_handler_t *h)
switch (byte) {
case PPP_FLAG:
if (buf->len <= PPP_FCSLEN || conn->ppp_flags) {
+ /* skip idle flag */
+ if (buf->len == 0 && conn->ppp_flags == 0)
+ break;
+ if (conf_verbose)
+ log_ppp_info2("sstp: ppp: read: malformed packet\n");
buf_set_length(buf, 0);
conn->ppp_flags = 0;
break;
@@ -1221,7 +1255,6 @@ static int ppp_read(struct triton_md_handler_t *h)
break;
}
}
-#endif
}
if (!list_empty(&conn->out_queue))
triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE);
@@ -1232,7 +1265,65 @@ drop:
return 1;
}
-static int ppp_write(struct triton_md_handler_t *h)
+static int ppp_read_seq(struct triton_md_handler_t *h)
+{
+ struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
+ struct buffer_t *buf;
+ struct sstp_hdr *hdr;
+ int n;
+
+ buf = conn->ppp_in;
+ while (1) {
+ if (!buf) {
+ alloc:
+ conn->ppp_in = buf = alloc_buf(conn->ppp.mtu ?
+ conn->ppp.mtu + PPP_HDRLEN + sizeof(*hdr) :
+ SSTP_MAX_PACKET_SIZE);
+ if (!buf) {
+ log_ppp_error("sstp: ppp: no memory\n");
+ goto drop;
+ }
+ buf_reserve(buf, sizeof(*hdr));
+ }
+
+ n = recv(h->fd, buf->tail, buf_tailroom(buf), MSG_TRUNC);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ break;
+ log_ppp_error("sstp: ppp: recv: %s\n", strerror(errno));
+ goto drop;
+ } else if (n > buf_tailroom(buf)) {
+ if (conf_verbose)
+ log_ppp_info2("sstp: ppp: recv: too long packet\n");
+ continue;
+ }
+
+ switch (conn->sstp_state) {
+ case STATE_SERVER_CALL_CONNECTED_PENDING:
+ case STATE_SERVER_CALL_CONNECTED:
+ break;
+ default:
+ continue;
+ }
+
+ buf_put(buf, n);
+ hdr = buf_push(buf, sizeof(*hdr));
+ INIT_SSTP_DATA_HDR(hdr, buf->len);
+ sstp_queue(conn, buf);
+ goto alloc;
+ }
+ if (!list_empty(&conn->out_queue))
+ triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE);
+ return 0;
+
+drop:
+ sstp_disconnect(conn);
+ return 1;
+}
+
+static int ppp_write_pty(struct triton_md_handler_t *h)
{
struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
struct iovec iov[PPP_BUF_IOVEC];
@@ -1257,8 +1348,9 @@ static int ppp_write(struct triton_md_handler_t *h)
goto again;
if (errno == EAGAIN)
goto defer;
- if (conf_verbose && errno != EPIPE)
- log_ppp_info2("sstp: ppp: write: %s\n", strerror(errno));
+ if (errno == EPIPE)
+ goto drop;
+ log_ppp_error("sstp: ppp: write: %s\n", strerror(errno));
goto drop;
} else if (n == 0)
goto defer;
@@ -1285,6 +1377,43 @@ drop:
return 1;
}
+static int ppp_write_seq(struct triton_md_handler_t *h)
+{
+ struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
+ struct buffer_t *buf;
+ ssize_t n;
+
+ while (!list_empty(&conn->ppp_queue)) {
+ buf = list_first_entry(&conn->ppp_queue, typeof(*buf), entry);
+ again:
+ n = send(conn->ppp_hnd.fd, buf->head, buf->len, 0);
+ if (n < 0) {
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ goto defer;
+ log_ppp_error("sstp: ppp: send: %s\n", strerror(errno));
+ goto drop;
+ } else if (n < buf->len) {
+ log_ppp_error("sstp: ppp: send: too short packet\n");
+ goto drop;
+ }
+
+ list_del(&buf->entry);
+ free_buf(buf);
+ }
+ triton_md_disable_handler(h, MD_MODE_WRITE);
+ return 0;
+
+defer:
+ triton_md_enable_handler(h, MD_MODE_WRITE);
+ return 0;
+
+drop:
+ triton_context_call(&conn->ctx, (triton_event_func)sstp_disconnect, conn);
+ return 1;
+}
+
static inline void ppp_queue(struct sstp_conn_t *conn, struct buffer_t *buf)
{
list_add_tail(&buf->entry, &conn->ppp_queue);
@@ -1519,13 +1648,25 @@ static int sstp_recv_msg_call_connect_request(struct sstp_conn_t *conn, struct s
return sstp_send_msg_call_connect_nak(conn);
}
- if (ppp_allocate_pty(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0)
+ switch (conn->ppp_mode) {
+ case PPP_MODE_ASYNC:
+ if (ppp_allocate_pty(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0)
+ return -1;
+ conn->ppp_hnd.read = ppp_read_pty;
+ conn->ppp_hnd.write = ppp_write_pty;
+ break;
+ case PPP_MODE_SEQPACKET:
+ if (ppp_allocate_seq(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0)
+ return -1;
+ conn->ppp_hnd.read = ppp_read_seq;
+ conn->ppp_hnd.write = ppp_write_seq;
+ break;
+ default:
+ log_ppp_error("sstp: invalid ppp-mode\n");
return -1;
+ }
conn->ppp_hnd.fd = master;
- conn->ppp_hnd.read = ppp_read;
- conn->ppp_hnd.write = ppp_write;
-
triton_md_register_handler(&conn->ctx, &conn->ppp_hnd);
triton_md_enable_handler(&conn->ppp_hnd, MD_MODE_READ);
@@ -1819,11 +1960,9 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr)
{
struct buffer_t *buf;
int size;
-#if !PPP_SYNC
uint8_t *src, *dst, byte;
uint16_t fcs;
int n;
-#endif
switch (conn->sstp_state) {
case STATE_SERVER_CALL_CONNECTED_PENDING:
@@ -1837,16 +1976,21 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr)
if (size == 0)
return 0;
-#if PPP_SYNC
- buf = alloc_buf(size);
- if (!buf) {
- log_error("sstp: no memory\n");
- return -1;
+ if (conn->ppp_mode == PPP_MODE_SEQPACKET) {
+ /* one datagram is one frame, no framing needed */
+ buf = alloc_buf(size);
+ if (!buf) {
+ log_error("sstp: no memory\n");
+ return -1;
+ }
+
+ buf_put_data(buf, hdr->data, size);
+
+ return ppp_send(conn, buf);
}
- buf_put_data(buf, hdr->data, size);
-#else
- buf = alloc_buf(size*2 + 2 + PPP_FCSLEN*2);
+ /* payload and FCS octets may both double when escaped, plus 2 flags */
+ buf = alloc_buf((size + PPP_FCSLEN) * 2 + 2);
if (!buf) {
log_error("sstp: no memory\n");
return -1;
@@ -1875,7 +2019,6 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr)
*dst++ = PPP_FLAG;
buf_put(buf, dst - buf->tail);
-#endif
return ppp_send(conn, buf);
}
@@ -1939,8 +2082,8 @@ static int sstp_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
}
n = ntohs(hdr->length);
- if (n > SSTP_MAX_PACKET_SIZE) {
- log_ppp_error("recv [SSTP too long packet]\n");
+ if (n < sizeof(*hdr) || n > SSTP_MAX_PACKET_SIZE) {
+ log_ppp_error("recv [SSTP invalid packet length %d]\n", n);
return -1;
} else if (n > buf->len)
break;
@@ -1978,8 +2121,6 @@ static int sstp_read(struct triton_md_handler_t *h)
n = conn->handler(conn, buf);
if (n < 0)
goto drop;
- else if (n > 0)
- return 1;
buf_expand_tail(buf, SSTP_MAX_PACKET_SIZE);
}
@@ -2094,8 +2235,9 @@ static int sstp_write(struct triton_md_handler_t *h)
continue;
if (errno == EAGAIN)
goto defer;
- if (conf_verbose && errno != EPIPE)
- log_ppp_info2("sstp: write: %s\n", strerror(errno));
+ if (errno == EPIPE)
+ goto drop;
+ log_ppp_error("sstp: write: %s\n", strerror(errno));
goto drop;
} else if (n == 0)
goto defer;
@@ -2129,6 +2271,31 @@ static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf)
return 0;
}
+static void sstp_flush(struct sstp_conn_t *conn)
+{
+ struct buffer_t *buf;
+ int n;
+
+ while (!list_empty(&conn->out_queue)) {
+ buf = list_first_entry(&conn->out_queue, typeof(*buf), entry);
+ while (buf->len) {
+ n = conn->stream->write(conn->stream, buf->head, buf->len);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EPIPE)
+ break;
+ log_ppp_error("sstp: write: %s\n", strerror(errno));
+ break;
+ } else if (n == 0)
+ break;
+ buf_pull(buf, n);
+ }
+ list_del(&buf->entry);
+ free_buf(buf);
+ }
+}
+
static void sstp_msg_echo(struct triton_timer_t *t)
{
struct sstp_conn_t *conn = container_of(t, typeof(*conn), hello_timer);
@@ -2230,6 +2397,7 @@ static void sstp_disconnect(struct sstp_conn_t *conn)
triton_timer_del(&conn->hello_timer);
if (conn->hnd.tpd) {
+ sstp_flush(conn);
triton_md_unregister_handler(&conn->hnd, 0);
conn->stream->close(conn->stream);
}
@@ -2399,6 +2567,7 @@ static int sstp_connect(struct triton_md_handler_t *h)
conn->sstp_state = STATE_SERVER_CALL_DISCONNECTED;
conn->ppp_state = STATE_INIT;
+ conn->ppp_mode = conf_ppp_mode;
conn->handler = conf_proxyproto ? proxy_handler : http_handler;
//conn->bypass_auth = conf_bypass_auth;
@@ -2845,11 +3014,14 @@ static void load_config(void)
conf_proxyproto = opt && strhas(opt, "proxy", ',');
ssl_load_config(&serv, conf_hostname);
- opt = serv.ssl_ctx ? "enabled" : "disabled";
if (conf_verbose) {
- log_info2("sstp: SSL/TLS support %s, PROXY support %s\n",
- opt, conf_proxyproto ? "enabled" : "disabled");
+ log_info2("sstp: SSL/TLS %s, PROXY %s, PPP mode %s\n",
+ serv.ssl_ctx ? "enabled" : "disabled",
+ conf_proxyproto ? "enabled" : "disabled",
+ conf_ppp_mode == PPP_MODE_AUTO ? "AUTO" :
+ conf_ppp_mode == PPP_MODE_ASYNC ? "ASYNC" :
+ conf_ppp_mode == PPP_MODE_SEQPACKET ? "SEQPACKET" : "unknown");
}
opt = conf_get_opt("sstp", "cert-hash-sha1");
@@ -2923,7 +3095,7 @@ static void sstp_init(void)
struct sockaddr_t *addr = &serv.addr;
struct linger linger;
struct stat st;
- int port, value;
+ int port, value, fd;
char *opt;
opt = conf_get_opt("sstp", "port");
@@ -3001,6 +3173,32 @@ static void sstp_init(void)
goto error_unlink;
}
+ opt = conf_get_opt("sstp", "ppp-mode");
+ if (opt) {
+ if (!strcmp(opt, "auto"))
+ conf_ppp_mode = PPP_MODE_AUTO;
+ else if (!strcmp(opt, "seqpacket"))
+ conf_ppp_mode = PPP_MODE_SEQPACKET;
+ else if (!strcmp(opt, "async"))
+ conf_ppp_mode = PPP_MODE_ASYNC;
+ }
+ if (conf_ppp_mode != PPP_MODE_ASYNC) {
+ fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ if (fd >= 0)
+ close(fd);
+ else if (access("/sys/module/ppposeq", F_OK) && system("modprobe -q ppposeq"))
+ log_warn("failed to load ppposeq kernel module\n");
+ }
+ if (conf_ppp_mode == PPP_MODE_AUTO) {
+ fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ if (fd >= 0) {
+ conf_ppp_mode = PPP_MODE_SEQPACKET;
+ close(fd);
+ } else {
+ conf_ppp_mode = PPP_MODE_ASYNC;
+ }
+ }
+
conn_pool = mempool_create(sizeof(struct sstp_conn_t));
load_config();
diff --git a/accel-pppd/extra/metrics.c b/accel-pppd/extra/metrics.c
index 14e007f8..3d422289 100644
--- a/accel-pppd/extra/metrics.c
+++ b/accel-pppd/extra/metrics.c
@@ -6,6 +6,8 @@
#include <inttypes.h>
#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
@@ -19,6 +21,7 @@
#include "list.h"
#include "utils.h"
#include "ap_session.h"
+#include "ipdb.h"
#include "version.h"
#include "memdebug.h"
@@ -65,6 +68,7 @@ enum metrics_format {
};
#define METRICS_RECV_BUF_SIZE 2048
+#define METRICS_HDR_RESERVE 256 /* room reserved for the response header */
#define METRICS_DEFAULT_READ_TIMEOUT 5 /* seconds */
#define METRICS_DEFAULT_MAX_CLIENTS 64
@@ -88,10 +92,14 @@ struct metrics_acl_t {
};
static enum metrics_format conf_format = METRICS_FORMAT_PROMETHEUS;
+/* TODO: Support simultaneous Prometheus and JSON output, selected by endpoint
+ * (for example, /metrics and /metrics.json) instead of a process-wide format.
+ */
static char *conf_address;
static LIST_HEAD(conf_allowed);
static int conf_read_timeout = METRICS_DEFAULT_READ_TIMEOUT;
static int conf_max_clients = METRICS_DEFAULT_MAX_CLIENTS;
+static int conf_sessions;
#define METRICS_ACCEPT_BACKOFF 1 /* seconds */
#define METRICS_ACCEPT_BATCH 16 /* max accept()s per serv_read tick */
@@ -350,6 +358,9 @@ static int load_config(void)
conf_max_clients = n > 0 ? n : 0;
}
+ opt = conf_get_opt("metrics", "sessions");
+ conf_sessions = opt ? atoi(opt) != 0 : 0;
+
return 0;
}
@@ -497,6 +508,81 @@ static void emit_prom_gauge(struct strbuf *sb, const char *name,
strbuf_appendf(sb, "%s %llu\n", name, value);
}
+static const char *session_state_name(int state)
+{
+ switch (state) {
+ case AP_STATE_STARTING:
+ return "starting";
+ case AP_STATE_ACTIVE:
+ return "active";
+ case AP_STATE_FINISHING:
+ return "finishing";
+ case AP_STATE_RESTORE:
+ return "restore";
+ default:
+ return "unknown";
+ }
+}
+
+static void append_prefix_len(char *buf, size_t len, int prefix_len)
+{
+ size_t pos = strlen(buf);
+
+ if (pos < len)
+ snprintf(buf + pos, len - pos, "/%i", prefix_len);
+}
+
+static void session_ipv6(struct ap_session *ses, char *buf, size_t len, int with_plen)
+{
+ struct ipv6db_addr_t *a;
+ struct in6_addr addr;
+
+ buf[0] = 0;
+ if (!ses->ipv6 || list_empty(&ses->ipv6->addr_list))
+ return;
+
+ a = list_first_entry(&ses->ipv6->addr_list, typeof(*a), entry);
+ if (!a->prefix_len)
+ return;
+ build_ip6_addr(a, ses->ipv6->peer_intf_id, &addr);
+ if (!inet_ntop(AF_INET6, &addr, buf, len))
+ return;
+ if (with_plen)
+ append_prefix_len(buf, len, a->prefix_len);
+}
+
+static void session_ipv6_dp(struct ap_session *ses, char *buf, size_t len)
+{
+ struct ipv6db_addr_t *a;
+
+ buf[0] = 0;
+ if (!ses->ipv6_dp || list_empty(&ses->ipv6_dp->prefix_list))
+ return;
+
+ a = list_first_entry(&ses->ipv6_dp->prefix_list, typeof(*a), entry);
+ if (!inet_ntop(AF_INET6, &a->addr, buf, len))
+ return;
+ append_prefix_len(buf, len, a->prefix_len);
+}
+
+/* Bare peer address, no prefix length: this is the address the peer is
+ * reachable at, mirroring the "ip" column of "accel-cmd show sessions". */
+static void session_ip(struct ap_session *ses, char *buf, size_t len)
+{
+ if (ses->ipv4) {
+ inet_ntop(AF_INET, &ses->ipv4->peer_addr, buf, len);
+ return;
+ }
+ session_ipv6(ses, buf, len, 0);
+}
+
+static unsigned long long session_uptime(struct ap_session *ses, time_t now)
+{
+ time_t end = ses->stop_time ? ses->stop_time : now;
+
+ return end > ses->start_time ? (unsigned long long)(end - ses->start_time) : 0;
+}
+
static void render_prometheus(struct strbuf *sb)
{
struct accel_stats s;
@@ -574,39 +660,101 @@ static void render_prometheus(struct strbuf *sb)
}
}
-static void append_json_string(struct strbuf *sb, const char *s)
+/* Length of the well formed UTF-8 sequence starting at s, 0 if the bytes
+ * there are not one. Overlong forms, surrogates and out of range code
+ * points are rejected. */
+static int utf8_seq_len(const unsigned char *s)
+{
+ unsigned int cp, min;
+ int n, i;
+
+ if (s[0] < 0x80)
+ return 1;
+
+ if ((s[0] & 0xe0) == 0xc0) {
+ n = 2;
+ min = 0x80;
+ cp = s[0] & 0x1f;
+ } else if ((s[0] & 0xf0) == 0xe0) {
+ n = 3;
+ min = 0x800;
+ cp = s[0] & 0x0f;
+ } else if ((s[0] & 0xf8) == 0xf0) {
+ n = 4;
+ min = 0x10000;
+ cp = s[0] & 0x07;
+ } else
+ return 0;
+
+ for (i = 1; i < n; i++) {
+ if ((s[i] & 0xc0) != 0x80)
+ return 0;
+ cp = (cp << 6) | (s[i] & 0x3f);
+ }
+
+ if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff))
+ return 0;
+
+ return n;
+}
+
+static void append_json_string(struct strbuf *sb, const char *str)
{
+ const unsigned char *s = (const unsigned char *)str;
+ const char *esc;
+ int n;
+
strbuf_appendf(sb, "\"");
- for (; *s; s++) {
+ while (*s) {
+ esc = NULL;
switch (*s) {
case '"':
- strbuf_appendf(sb, "\\\"");
+ esc = "\\\"";
break;
case '\\':
- strbuf_appendf(sb, "\\\\");
+ esc = "\\\\";
break;
case '\b':
- strbuf_appendf(sb, "\\b");
+ esc = "\\b";
break;
case '\f':
- strbuf_appendf(sb, "\\f");
+ esc = "\\f";
break;
case '\n':
- strbuf_appendf(sb, "\\n");
+ esc = "\\n";
break;
case '\r':
- strbuf_appendf(sb, "\\r");
+ esc = "\\r";
break;
case '\t':
- strbuf_appendf(sb, "\\t");
- break;
- default:
- if ((unsigned char)*s < 0x20)
- strbuf_appendf(sb, "\\u%04x", (unsigned)*s);
- else
- strbuf_appendf(sb, "%c", *s);
+ esc = "\\t";
break;
}
+
+ if (esc) {
+ strbuf_appendf(sb, "%s", esc);
+ s++;
+ continue;
+ }
+
+ if (*s < 0x20) {
+ strbuf_appendf(sb, "\\u%04x", *s);
+ s++;
+ continue;
+ }
+
+ /* Usernames and station ids come from the peer and are not
+ * validated anywhere, so a single malformed sequence would
+ * otherwise make the whole document undecodable. */
+ n = utf8_seq_len(s);
+ if (!n) {
+ strbuf_appendf(sb, "\\ufffd");
+ s++;
+ continue;
+ }
+
+ strbuf_appendf(sb, "%.*s", n, (const char *)s);
+ s += n;
}
strbuf_appendf(sb, "\"");
}
@@ -621,6 +769,76 @@ static void emit_json_proto(struct strbuf *sb, const char *name, int *first,
name, starting, active);
}
+static void append_json_field(struct strbuf *sb, int *first, const char *name,
+ const char *value)
+{
+ if (!*first)
+ strbuf_appendf(sb, ",");
+ *first = 0;
+ append_json_string(sb, name);
+ strbuf_appendf(sb, ":");
+ append_json_string(sb, value ? value : "");
+}
+
+/* TODO: Copy a bounded snapshot of the required fields under ses_lock, then
+ * serialize it after unlocking. This would shorten lock hold time and make it
+ * practical to add a response-size limit or pagination for large deployments.
+ */
+/* The whole list is walked with ses_lock held, so nothing in here may
+ * block or touch the session. In particular ap_session_read_stats() is
+ * not used: it issues a synchronous netlink round trip per session,
+ * which would stall every session setup and teardown for the duration
+ * of a scrape, it writes back into the session while only the read lock
+ * is held, and it needs the thread local "net" of the session's
+ * namespace, which this context does not have. The accounting counters
+ * last sampled by the session itself are reported instead. */
+static void render_json_sessions(struct strbuf *sb)
+{
+ struct ap_session *ses;
+ time_t now = _time();
+ char ip[INET6_ADDRSTRLEN];
+ char ipv6[INET6_ADDRSTRLEN + 5];
+ char ipv6_dp[INET6_ADDRSTRLEN + 5];
+ int first = 1;
+ int f;
+
+ strbuf_appendf(sb, ",\"session_details\":[");
+ pthread_rwlock_rdlock(&ses_lock);
+ list_for_each_entry(ses, &ses_list, entry) {
+ session_ip(ses, ip, sizeof(ip));
+ session_ipv6(ses, ipv6, sizeof(ipv6), 1);
+ session_ipv6_dp(ses, ipv6_dp, sizeof(ipv6_dp));
+
+ strbuf_appendf(sb, "%s", first ? "{" : ",{");
+ first = 0;
+ f = 1;
+ append_json_field(sb, &f, "session_id", ses->sessionid);
+ append_json_field(sb, &f, "ifname", ses->ifname);
+ append_json_field(sb, &f, "username", ses->username);
+ append_json_field(sb, &f, "ip", ip);
+ append_json_field(sb, &f, "ipv6", ipv6);
+ append_json_field(sb, &f, "delegated_ipv6_prefix", ipv6_dp);
+ append_json_field(sb, &f, "protocol", ses->ctrl ? ses->ctrl->name : NULL);
+ append_json_field(sb, &f, "state", session_state_name(ses->state));
+ append_json_field(sb, &f, "calling_station_id", ses->ctrl ? ses->ctrl->calling_station_id : NULL);
+ append_json_field(sb, &f, "called_station_id", ses->ctrl ? ses->ctrl->called_station_id : NULL);
+ append_json_field(sb, &f, "service_name", ses->ctrl ? ses->ctrl->service_name : NULL);
+ append_json_field(sb, &f, "inbound_if", ses->ctrl ? ses->ctrl->ifname : NULL);
+ append_json_field(sb, &f, "compression", ses->comp);
+ append_json_field(sb, &f, "vrf", ses->vrf_name);
+ append_json_field(sb, &f, "netns", ses->net ? ses->net->name : NULL);
+ strbuf_appendf(sb,
+ ",\"uptime_seconds\":%llu,\"rx_bytes\":%" PRIu64
+ ",\"tx_bytes\":%" PRIu64 ",\"rx_packets\":%" PRIu64
+ ",\"tx_packets\":%" PRIu64 "}",
+ session_uptime(ses, now), ses->acct_rx_bytes,
+ ses->acct_tx_bytes, ses->acct_rx_packets,
+ ses->acct_tx_packets);
+ }
+ pthread_rwlock_unlock(&ses_lock);
+ strbuf_appendf(sb, "]");
+}
+
static void render_json(struct strbuf *sb)
{
struct accel_stats s;
@@ -665,6 +883,8 @@ static void render_json(struct strbuf *sb)
emit_json_proto(sb, p->module, &first, p->starting(), p->active());
}
strbuf_appendf(sb, "}");
+ if (conf_sessions)
+ render_json_sessions(sb);
strbuf_appendf(sb, "}\n");
}
@@ -696,25 +916,47 @@ static int xmit_flush(struct metrics_client_t *cln)
return 0;
}
-static void send_response(struct metrics_client_t *cln, int status, const char *reason,
- const char *ctype, const char *body, int body_len)
+static int format_header(char *buf, size_t size, int status, const char *reason,
+ const char *ctype, size_t body_len)
{
- char header[256];
- int hlen, total;
- int rc;
-
- if (cln->xmit_buf)
- return; /* response already in flight */
+ int hlen;
- hlen = snprintf(header, sizeof(header),
+ hlen = snprintf(buf, size,
"HTTP/1.1 %d %s\r\n"
"Server: accel-ppp\r\n"
"Content-Type: %s\r\n"
- "Content-Length: %d\r\n"
+ "Content-Length: %zu\r\n"
"Connection: close\r\n"
"\r\n",
status, reason, ctype, body_len);
- if (hlen <= 0 || hlen >= (int)sizeof(header)) {
+ if (hlen <= 0 || hlen >= (int)size)
+ return -1;
+
+ return hlen;
+}
+
+static void start_xmit(struct metrics_client_t *cln)
+{
+ int rc = xmit_flush(cln);
+
+ if (rc < 0)
+ cln->disconnect = 1;
+ else if (rc == 0)
+ cln->disconnect = 1; /* fully flushed, ready to close */
+}
+
+static void send_response(struct metrics_client_t *cln, int status, const char *reason,
+ const char *ctype, const char *body, int body_len)
+{
+ char header[METRICS_HDR_RESERVE];
+ int hlen, total;
+
+ if (cln->xmit_buf)
+ return; /* response already in flight */
+
+ hlen = format_header(header, sizeof(header), status, reason, ctype,
+ body_len > 0 ? (size_t)body_len : 0);
+ if (hlen < 0) {
cln->disconnect = 1;
return;
}
@@ -731,11 +973,7 @@ static void send_response(struct metrics_client_t *cln, int status, const char *
cln->xmit_pos = 0;
cln->xmit_len = total;
- rc = xmit_flush(cln);
- if (rc < 0)
- cln->disconnect = 1;
- else if (rc == 0)
- cln->disconnect = 1; /* fully flushed, ready to close */
+ start_xmit(cln);
}
static void send_simple(struct metrics_client_t *cln, int status, const char *reason)
@@ -747,10 +985,46 @@ static void send_simple(struct metrics_client_t *cln, int status, const char *re
send_response(cln, status, reason, "text/plain; charset=utf-8", body, len);
}
+/* Sends a body rendered into sb and takes ownership of its buffer. The
+ * first METRICS_HDR_RESERVE bytes of sb are unused padding the header is
+ * written into, so a body that can be several megabytes with sessions=1
+ * is not copied a second time. */
+static void send_rendered(struct metrics_client_t *cln, const char *ctype,
+ struct strbuf *sb)
+{
+ char header[METRICS_HDR_RESERVE];
+ size_t body_len = sb->len - METRICS_HDR_RESERVE;
+ int hlen;
+
+ if (cln->xmit_buf)
+ return; /* response already in flight */
+
+ hlen = format_header(header, sizeof(header), 200, "OK", ctype, body_len);
+ if (hlen < 0 || sb->len > INT_MAX) {
+ send_simple(cln, 500, "Internal Server Error");
+ return;
+ }
+
+ memcpy(sb->data + METRICS_HDR_RESERVE - hlen, header, hlen);
+ cln->xmit_buf = sb->data;
+ cln->xmit_pos = METRICS_HDR_RESERVE - hlen;
+ cln->xmit_len = (int)sb->len;
+ sb->data = NULL;
+ sb->len = sb->cap = 0;
+
+ start_xmit(cln);
+}
+
static void serve_metrics(struct metrics_client_t *cln)
{
struct strbuf sb = {0};
+ /* Reserve room for the response header in front of the body so it
+ * can be handed to the client without another copy. */
+ if (strbuf_reserve(&sb, METRICS_HDR_RESERVE))
+ goto err;
+ sb.len = METRICS_HDR_RESERVE;
+
switch (conf_format) {
case METRICS_FORMAT_PROMETHEUS:
render_prometheus(&sb);
@@ -760,13 +1034,15 @@ static void serve_metrics(struct metrics_client_t *cln)
break;
}
- if (sb.oom || !sb.data) {
- send_simple(cln, 500, "Internal Server Error");
- goto out;
- }
+ if (sb.oom || !sb.data)
+ goto err;
- send_response(cln, 200, "OK", content_type(), sb.data, (int)sb.len);
-out:
+ send_rendered(cln, content_type(), &sb);
+ strbuf_free(&sb);
+ return;
+
+err:
+ send_simple(cln, 500, "Internal Server Error");
strbuf_free(&sb);
}
@@ -1063,6 +1339,9 @@ err:
static void init(void)
{
+ /* TODO: Add optional HTTP authentication before exposing session_details;
+ * allowed_ips limits network reachability but does not identify callers.
+ */
if (load_config() < 0)
return;
diff --git a/cmake/cpack.cmake b/cmake/cpack.cmake
index ee2a0391..b0c4823d 100644
--- a/cmake/cpack.cmake
+++ b/cmake/cpack.cmake
@@ -17,7 +17,7 @@ SET(CPACK_PACKAGE_CONTACT "Dmitry Kozlov <xeb@mail.ru>")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "PPtP/L2TP/PPPoE/SSTP server for Linux")
SET(CPACK_PACKAGE_VENDOR "Dmitry Kozlov")
-SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README")
+SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING")
IF(CPACK_TYPE STREQUAL Debian9)
diff --git a/drivers/ppposeq/CMakeLists.txt b/drivers/ppposeq/CMakeLists.txt
new file mode 100644
index 00000000..08c45c76
--- /dev/null
+++ b/drivers/ppposeq/CMakeLists.txt
@@ -0,0 +1,19 @@
+if (NOT DEFINED KDIR)
+ set(KDIR "/usr/src/linux")
+endif (NOT DEFINED KDIR)
+
+ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/driver/ppposeq.ko
+ COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/driver
+ COMMAND mkdir ${CMAKE_CURRENT_BINARY_DIR}/driver
+ COMMAND ln -sf ${CMAKE_CURRENT_SOURCE_DIR}/* ${CMAKE_CURRENT_BINARY_DIR}/driver
+ COMMAND make -C ${KDIR} M=${CMAKE_CURRENT_BINARY_DIR}/driver modules
+ DEPENDS ppposeq.c ppposeq.h
+)
+
+ADD_CUSTOM_TARGET(ppposeq_drv ALL
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/driver/ppposeq.ko
+)
+
+IF (NOT DEFINED CPACK_TYPE)
+ INSTALL(CODE "EXECUTE_PROCESS(COMMAND make -C ${KDIR} M=${CMAKE_CURRENT_BINARY_DIR}/driver modules_install)")
+ENDIF()
diff --git a/drivers/ppposeq/Makefile b/drivers/ppposeq/Makefile
new file mode 100644
index 00000000..f66096c6
--- /dev/null
+++ b/drivers/ppposeq/Makefile
@@ -0,0 +1,4 @@
+obj-m += ppposeq.o
+
+default:
+ make -C $(KDIR) M=$(PWD) modules
diff --git a/drivers/ppposeq/ppposeq.c b/drivers/ppposeq/ppposeq.c
new file mode 100644
index 00000000..b4c5718d
--- /dev/null
+++ b/drivers/ppposeq/ppposeq.c
@@ -0,0 +1,569 @@
+/*
+ * ppposeq - PPP over SEQPACKET socket driver.
+ *
+ * Replaces the pty + ppp_async transport for userspace PPP terminators.
+ * A pty is a byte stream: the tty flip buffer merges frames written
+ * back to back (flush_to_ldisc hands receive_buf everything committed
+ * since the last flush in one call), so PPP over a pty needs HDLC
+ * framing to re-delimit frames. Here the socket is the PPP endpoint
+ * and one datagram is one PPP frame, so no HDLC framing is needed.
+ *
+ * Copyright (C) 2026 Vladislav Grishenko
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/version.h>
+#include <linux/ppp_defs.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,4,0)
+#include <linux/if.h>
+#include <linux/if_ppp.h>
+#else
+#include <linux/ppp-ioctl.h>
+#endif
+#include <linux/ppp_channel.h>
+#include <linux/if_pppox.h>
+
+#include <net/sock.h>
+
+#include "ppposeq.h"
+
+/* proto_ops connect/bind signatures changed to sockaddr_unsized in 6.19 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6,19,0)
+#define sockaddr_unsized sockaddr
+#endif
+
+/* the noblock argument was folded into flags in 5.19 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,19,0)
+#define ppposeq_recv_datagram(sk, flags, err) \
+ skb_recv_datagram(sk, (flags), (flags) & MSG_DONTWAIT, err)
+#else
+#define ppposeq_recv_datagram(sk, flags, err) \
+ skb_recv_datagram(sk, flags, err)
+#endif
+
+/* __sock_queue_rcv_skb was introduced in 4.7 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
+#define ppposeq_queue_rcv_skb(sk, skb) \
+ sock_queue_rcv_skb(sk, skb)
+#else
+#define ppposeq_queue_rcv_skb(sk, skb) \
+ __sock_queue_rcv_skb(sk, skb)
+#endif
+
+/* sk_alloc gained a trailing kern argument in 4.2 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0)
+#define ppposeq_sk_alloc(net, fam, prio, prot, kern) \
+ sk_alloc(net, fam, prio, prot)
+#else
+#define ppposeq_sk_alloc(net, fam, prio, prot, kern) \
+ sk_alloc(net, fam, prio, prot, kern)
+#endif
+
+/* memcpy_from_msg appeared in 3.19, replacing memcpy_fromiovec */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0)
+#define memcpy_from_msg(data, msg, len) \
+ memcpy_fromiovec(data, (msg)->msg_iov, len)
+#define skb_copy_datagram_msg(skb, off, msg, len) \
+ skb_copy_datagram_iovec(skb, off, (msg)->msg_iov, len)
+#endif
+
+/* smp_mb__after_atomic was introduced in 3.16 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,16,0)
+#define smp_mb__after_atomic() smp_mb()
+#endif
+
+/* U16_MAX was introduced in 3.14 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
+#define U16_MAX ((u16)~0U)
+#endif
+
+#define XMIT_WAKEUP 0
+
+#define XMIT_PULL_PROT (1 << 0)
+#define XMIT_PUSH_AC (1 << 1)
+
+#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
+
+struct ppposeq_opt {
+ int mru;
+ unsigned int flags;
+ unsigned long xmit_flags;
+};
+
+/*
+ * pppox_sock's proto union is fixed by the core, so keep our state
+ * alongside the socket rather than in it.
+ */
+struct ppposeq_sock {
+ struct pppox_sock po;
+ struct ppposeq_opt opt;
+};
+
+static inline struct ppposeq_sock *ppposeq_sk(struct sock *sk)
+{
+ return (struct ppposeq_sock *)sk;
+}
+
+static const struct proto_ops ppposeq_ops;
+
+static struct proto ppposeq_sk_proto = {
+ .name = "PPPOSEQ",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct ppposeq_sock),
+};
+
+/*
+ * Transmit: kernel -> userspace. Called from ppp_generic with
+ * spin_lock(&pch->downl) held, so this must not sleep. Queue the frame on
+ * the socket's receive queue; userspace picks it up with recvmsg. One skb
+ * in, one datagram out.
+ */
+static int ppposeq_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct sock *sk = (struct sock *)chan->private;
+ struct ppposeq_sock *ps = ppposeq_sk(sk);
+ int err, proto, islcp, flags = 0;
+ u8 *data;
+
+ if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED))
+ goto drop;
+
+ /* Ensure we can safely access protocol field and LCP code */
+ if (!pskb_may_pull(skb, 3))
+ goto drop;
+
+ /* Apply negotiated PFC/ACFC, like ppp_sync_txmunge. */
+ data = skb->data;
+ proto = (data[0] << 8) + data[1];
+
+ /* LCP codes 1..7 must be sent uncompressed. */
+ islcp = (proto == PPP_LCP) && data[2] >= 1 && data[2] <= 7;
+
+ /* compress protocol field if PFC is in effect */
+ if ((ps->opt.flags & SC_COMP_PROT) && data[0] == 0 && !islcp) {
+ skb_pull(skb, 1);
+ flags |= XMIT_PULL_PROT;
+ }
+
+ /* prepend address/control unless ACFC is in effect (or it's LCP) */
+ if ((ps->opt.flags & SC_COMP_AC) == 0 || islcp) {
+ if (skb_cow_head(skb, 2))
+ goto drop;
+ skb_push(skb, 2);
+ skb->data[0] = PPP_ALLSTATIONS;
+ skb->data[1] = PPP_UI;
+ flags |= XMIT_PUSH_AC;
+ }
+
+ /*
+ * Set the wakeup flag before attempting to queue and clear on success,
+ * so a concurrent ppposeq_recvmsg that frees space cannot miss it.
+ * Spurious wakeups may only happen during the brief queue window,
+ * not on every frame.
+ */
+ set_bit(XMIT_WAKEUP, &ps->opt.xmit_flags);
+ smp_mb__after_atomic();
+
+ /* Bypass receive filter machinery where the helper is available. */
+ err = ppposeq_queue_rcv_skb(sk, skb);
+ if (unlikely(err < 0)) {
+ /*
+ * Receive queue full. Restore the frame and ask ppp_generic to
+ * retry: it requeues the skb, so do not free it here.
+ */
+ if (flags & XMIT_PUSH_AC)
+ __skb_pull(skb, 2);
+ if (flags & XMIT_PULL_PROT)
+ *(u8 *)skb_push(skb, 1) = 0;
+ return 0;
+ }
+
+ clear_bit(XMIT_WAKEUP, &ps->opt.xmit_flags);
+ return 1;
+
+drop:
+ kfree_skb(skb);
+ return 1;
+}
+
+/*
+ * Channel ioctls. The framing-related ones ppp_synctty implements
+ * (PPPIOC[GS]ASYNCMAP, PPPIOC[GS]RASYNCMAP, PPPIOC[GS]XASYNCMAP) have no
+ * meaning without async framing, so only flags and MRU carry over.
+ */
+static int ppposeq_chan_ioctl(struct ppp_channel *chan, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sock *sk = (struct sock *)chan->private;
+ struct ppposeq_sock *ps = ppposeq_sk(sk);
+ void __user *argp = (void __user *)arg;
+ int err, val;
+
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCGFLAGS:
+ if (put_user(ps->opt.flags, (int __user *)argp))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSFLAGS:
+ if (get_user(val, (int __user *)argp))
+ break;
+ ps->opt.flags = val & ~SC_RCV_BITS;
+ err = 0;
+ break;
+ case PPPIOCGMRU:
+ if (put_user(ps->opt.mru, (int __user *)argp))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSMRU:
+ if (get_user(val, (int __user *)argp))
+ break;
+ if (val > U16_MAX) {
+ err = -EINVAL;
+ break;
+ }
+ if (val < PPP_MRU)
+ val = PPP_MRU;
+ ps->opt.mru = val;
+ err = 0;
+ break;
+ default:
+ err = -ENOTTY;
+ break;
+ }
+
+ return err;
+}
+
+static const struct ppp_channel_ops ppposeq_chan_ops = {
+ .start_xmit = ppposeq_xmit,
+ .ioctl = ppposeq_chan_ioctl,
+};
+
+/*
+ * Receive: userspace -> kernel. One sendmsg is one frame, so there is no
+ * reassembly to do -- just validate and hand it to the ppp layer.
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,0)
+static int ppposeq_sendmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *m, size_t total_len)
+#else
+static int ppposeq_sendmsg(struct socket *sock, struct msghdr *m,
+ size_t total_len)
+#endif
+{
+ struct sock *sk = sock->sk;
+ struct ppposeq_sock *ps = ppposeq_sk(sk);
+ struct pppox_sock *po = pppox_sk(sk);
+ struct sk_buff *skb;
+ int err;
+ u8 *data;
+
+ if (total_len == 0)
+ return 0;
+
+ lock_sock(sk);
+
+ if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED)) {
+ err = -ENOTCONN;
+ goto out;
+ }
+
+ if (total_len > ps->opt.mru + PPP_HDRLEN) {
+ err = -EMSGSIZE;
+ goto out;
+ }
+
+ /* plus headroom for network and PFC decompression */
+ skb = sock_alloc_send_skb(sk, NET_SKB_PAD + 2 + total_len,
+ m->msg_flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ goto out;
+ skb_reserve(skb, NET_SKB_PAD + 2);
+
+ err = memcpy_from_msg(skb_put(skb, total_len), m, total_len);
+ if (err) {
+ kfree_skb(skb);
+ goto out;
+ }
+
+ /* strip address/control field if present */
+ data = skb->data;
+ if (data[0] == PPP_ALLSTATIONS) {
+ /* chop off address/control */
+ if (skb->len < 3 || data[1] != PPP_UI) {
+ kfree_skb(skb);
+ err = -EINVAL;
+ goto out;
+ }
+ data = skb_pull(skb, 2);
+ }
+
+ /* decompress protocol field if compressed */
+ if (data[0] & 0x01) {
+ *(u8 *)skb_push(skb, 1) = 0;
+ } else if (skb->len < 2) {
+ kfree_skb(skb);
+ err = -EINVAL;
+ goto out;
+ }
+
+ ppp_input(&po->chan, skb);
+ err = total_len;
+
+out:
+ release_sock(sk);
+ return err;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,0)
+static int ppposeq_recvmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *m, size_t total_len, int flags)
+#else
+static int ppposeq_recvmsg(struct socket *sock, struct msghdr *m,
+ size_t total_len, int flags)
+#endif
+{
+ struct sock *sk = sock->sk;
+ struct ppposeq_sock *ps = ppposeq_sk(sk);
+ struct pppox_sock *po = pppox_sk(sk);
+ struct sk_buff *skb;
+ int err;
+
+ if (flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ skb = ppposeq_recv_datagram(sk, flags, &err);
+ if (!skb)
+ return err;
+
+ if (total_len > skb->len)
+ total_len = skb->len;
+ else if (total_len < skb->len)
+ m->msg_flags |= MSG_TRUNC;
+
+ err = skb_copy_datagram_msg(skb, 0, m, total_len);
+ if (likely(err == 0))
+ err = (flags & MSG_TRUNC) ? skb->len : total_len;
+
+ skb_free_datagram(sk, skb);
+ if (flags & MSG_PEEK)
+ return err;
+
+ /* Pair with the barrier after XMIT_WAKEUP is set in xmit. */
+ smp_mb();
+
+ /* Room freed: let ppp_generic retry the restored PPP frame. */
+ if (test_bit(XMIT_WAKEUP, &ps->opt.xmit_flags))
+ ppp_output_wakeup(&po->chan);
+
+ return err;
+}
+
+/*
+ * connect() registers the ppp channel. There is no transport to look up --
+ * this socket is the endpoint -- so the address carries nothing but the
+ * family and protocol.
+ */
+static int ppposeq_connect(struct socket *sock, struct sockaddr_unsized *uservaddr,
+ int sockaddr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_pppox *sp = (struct sockaddr_pppox *)uservaddr;
+ struct ppposeq_sock *ps = ppposeq_sk(sk);
+ struct pppox_sock *po = pppox_sk(sk);
+ int err;
+
+ if (sockaddr_len < sizeof(struct sockaddr_ppposeq))
+ return -EINVAL;
+
+ if (sp->sa_protocol != PX_PROTO_OSEQ)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->sk_state & PPPOX_CONNECTED) {
+ err = -EBUSY;
+ goto out;
+ }
+
+ if (sk->sk_state & PPPOX_DEAD) {
+ err = -EALREADY;
+ goto out;
+ }
+
+ po->chan.private = sk;
+ po->chan.ops = &ppposeq_chan_ops;
+ po->chan.mtu = ps->opt.mru;
+ /* reserve the address/control bytes ppposeq_xmit prepends, so the
+ * core leaves us the headroom to skb_push them without a copy */
+ po->chan.hdrlen = 2;
+
+ err = ppp_register_net_channel(sock_net(sk), &po->chan);
+ if (err)
+ goto out;
+
+ sk->sk_state = PPPOX_CONNECTED;
+ sock->state = SS_CONNECTED;
+
+out:
+ release_sock(sk);
+ return err;
+}
+
+static int ppposeq_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ return 0;
+
+ lock_sock(sk);
+
+ if (sock_flag(sk, SOCK_DEAD)) {
+ release_sock(sk);
+ return -EBADF;
+ }
+
+ if (sk->sk_state & PPPOX_CONNECTED)
+ pppox_unbind_sock(sk);
+
+ /* signal the death of the socket before dropping the lock */
+ sk->sk_state = PPPOX_DEAD;
+ sock_orphan(sk);
+ sock->sk = NULL;
+
+ skb_queue_purge(&sk->sk_receive_queue);
+ release_sock(sk);
+ sock_put(sk);
+
+ return 0;
+}
+
+/* getname returned the length via *len until 4.17, by return value after */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)
+static int ppposeq_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *len, int peer)
+#else
+static int ppposeq_getname(struct socket *sock, struct sockaddr *uaddr,
+ int peer)
+#endif
+{
+ struct sockaddr_ppposeq sp;
+
+ memset(&sp, 0, sizeof(sp));
+ sp.sa_family = AF_PPPOX;
+ sp.sa_protocol = PX_PROTO_OSEQ;
+ memcpy(uaddr, &sp, sizeof(sp));
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)
+ *len = sizeof(sp);
+ return 0;
+#else
+ return sizeof(sp);
+#endif
+}
+
+/* pppox_proto.create gained a trailing kern argument in 4.2 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0)
+static int ppposeq_create(struct net *net, struct socket *sock)
+#else
+static int ppposeq_create(struct net *net, struct socket *sock, int kern)
+#endif
+{
+ struct sock *sk;
+
+ sk = ppposeq_sk_alloc(net, PF_PPPOX, GFP_KERNEL, &ppposeq_sk_proto, kern);
+ if (!sk)
+ return -ENOMEM;
+
+ sock_init_data(sock, sk);
+
+ sock->state = SS_UNCONNECTED;
+ sock->ops = &ppposeq_ops;
+
+ sk->sk_state = PPPOX_NONE;
+ sk->sk_type = SOCK_SEQPACKET;
+ sk->sk_family = PF_PPPOX;
+ sk->sk_protocol = PX_PROTO_OSEQ;
+
+ ppposeq_sk(sk)->opt.mru = PPP_MRU;
+
+ return 0;
+}
+
+static const struct proto_ops ppposeq_ops = {
+ .family = AF_PPPOX,
+ .owner = THIS_MODULE,
+ .release = ppposeq_release,
+ .bind = sock_no_bind,
+ .connect = ppposeq_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = sock_no_accept,
+ .getname = ppposeq_getname,
+ .poll = datagram_poll,
+ .listen = sock_no_listen,
+ .shutdown = sock_no_shutdown,
+ /* sock_no_setsockopt/getsockopt were removed and the proto_ops
+ * signatures changed to sockptr_t in 5.9 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,9,0)
+ .setsockopt = sock_no_setsockopt,
+ .getsockopt = sock_no_getsockopt,
+#endif
+ .sendmsg = ppposeq_sendmsg,
+ .recvmsg = ppposeq_recvmsg,
+ .mmap = sock_no_mmap,
+ .ioctl = pppox_ioctl,
+ /* pppox_compat_ioctl was added in 5.3; before that the core
+ * routed compat ioctls through .ioctl itself */
+#if defined(CONFIG_COMPAT) && LINUX_VERSION_CODE >= KERNEL_VERSION(5,3,0)
+ .compat_ioctl = pppox_compat_ioctl,
+#endif
+};
+
+static const struct pppox_proto ppposeq_proto = {
+ .create = ppposeq_create,
+ .ioctl = NULL, /* pppox_ioctl handles PPPIOCGCHAN for us */
+ .owner = THIS_MODULE,
+};
+
+static int __init ppposeq_init(void)
+{
+ int err;
+
+ err = proto_register(&ppposeq_sk_proto, 0);
+ if (err)
+ return err;
+
+ err = register_pppox_proto(PX_PROTO_OSEQ, &ppposeq_proto);
+ if (err)
+ goto out_unregister_proto;
+
+ pr_info("PPP over SEQPACKET socket driver\n");
+ return 0;
+
+out_unregister_proto:
+ proto_unregister(&ppposeq_sk_proto);
+ return err;
+}
+
+static void __exit ppposeq_exit(void)
+{
+ unregister_pppox_proto(PX_PROTO_OSEQ);
+ proto_unregister(&ppposeq_sk_proto);
+}
+
+module_init(ppposeq_init);
+module_exit(ppposeq_exit);
+
+MODULE_DESCRIPTION("PPP over SEQPACKET socket driver");
+MODULE_AUTHOR("Vladislav Grishenko");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_NET_PF_PROTO(PF_PPPOX, PX_PROTO_OSEQ);
diff --git a/drivers/ppposeq/ppposeq.h b/drivers/ppposeq/ppposeq.h
new file mode 100644
index 00000000..b16fdce1
--- /dev/null
+++ b/drivers/ppposeq/ppposeq.h
@@ -0,0 +1,39 @@
+/*
+ * ppposeq - PPP over a SEQPACKET AF_PPPOX socket.
+ *
+ * Replaces the pty + ppp_async transport for userspace PPP terminators.
+ * A pty is a byte stream, so frame boundaries are lost in the tty flip
+ * buffer and have to be rebuilt with HDLC escape+FCS framing.
+ * Here the socket itself is the PPP endpoint and each datagram carries
+ * exactly one PPP frame, so no HDLC framing is needed on either side.
+ *
+ * fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ * connect(fd, &sa, sizeof(sa)); // registers the channel
+ * ioctl(fd, PPPIOCGCHAN, &idx);
+ * chan = open("/dev/ppp"); ioctl(chan, PPPIOCATTCHAN, &idx);
+ * // frames flow over fd with send()/recv()
+ *
+ * Only the protocol number is new; everything else uses the common
+ * AF_PPPOX and PPPIOC* interfaces.
+ */
+
+#ifndef __PPPOSEQ_H
+#define __PPPOSEQ_H
+
+#include <linux/if_pppox.h>
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,1,0)
+typedef sa_family_t __kernel_sa_family_t;
+#endif
+
+#ifndef PX_PROTO_OSEQ
+#define PX_PROTO_OSEQ 3
+#endif
+
+struct sockaddr_ppposeq {
+ __kernel_sa_family_t sa_family; /* AF_PPPOX */
+ unsigned int sa_protocol; /* PX_PROTO_OSEQ */
+} __attribute__((packed));
+
+#endif
diff --git a/tests/accel-pppd/general/test_metrics.py b/tests/accel-pppd/general/test_metrics.py
index 6344d3d0..aa90a13a 100644
--- a/tests/accel-pppd/general/test_metrics.py
+++ b/tests/accel-pppd/general/test_metrics.py
@@ -7,7 +7,7 @@ import pytest
PROM_PORT = 9099
-def _config(fmt):
+def _config(fmt, sessions=False):
return f"""
[modules]
metrics
@@ -25,6 +25,7 @@ def _config(fmt):
[metrics]
address=127.0.0.1:{PROM_PORT}
format={fmt}
+ sessions={int(sessions)}
"""
@@ -33,7 +34,8 @@ def _request(path, method="GET"):
try:
conn.request(method, path)
resp = conn.getresponse()
- body = resp.read().decode("utf-8", "replace")
+ # strict: the renderer must never emit a body that is not valid UTF-8
+ body = resp.read().decode("utf-8")
headers = {k.lower(): v for k, v in resp.getheaders()}
return resp.status, headers, body
finally:
@@ -43,7 +45,8 @@ def _request(path, method="GET"):
class TestPrometheus:
@pytest.fixture()
def accel_pppd_config(self):
- return _config("prometheus")
+ # sessions=1 must stay a no-op here: prometheus output is aggregate only
+ return _config("prometheus", sessions=True)
def test_metrics_prometheus(self, accel_pppd_instance):
assert accel_pppd_instance
@@ -55,6 +58,7 @@ class TestPrometheus:
assert "accel_ppp_build_info{version=" in body
assert "# TYPE accel_ppp_uptime_seconds gauge" in body
assert 'accel_ppp_sessions{state="active"}' in body
+ assert "session_details" not in body
def test_metrics_404_unknown_path(self, accel_pppd_instance):
assert accel_pppd_instance
@@ -74,7 +78,7 @@ class TestPrometheus:
class TestJson:
@pytest.fixture()
def accel_pppd_config(self):
- return _config("json")
+ return _config("json", sessions=True)
def test_metrics_json(self, accel_pppd_instance):
assert accel_pppd_instance
@@ -84,8 +88,25 @@ class TestJson:
assert status == 200
assert headers.get("content-type") == "application/json"
+ assert int(headers["content-length"]) == len(body.encode("utf-8"))
+
doc = json.loads(body)
assert "build" in doc and "version" in doc["build"]
assert "uptime_seconds" in doc
assert "active" in doc["sessions"]
assert "threads" in doc["core"]
+ assert doc["session_details"] == []
+
+
+class TestJsonNoSessions:
+ @pytest.fixture()
+ def accel_pppd_config(self):
+ return _config("json")
+
+ def test_metrics_json_without_sessions(self, accel_pppd_instance):
+ assert accel_pppd_instance
+
+ status, _, body = _request("/metrics")
+
+ assert status == 200
+ assert "session_details" not in json.loads(body)