diff options
| -rw-r--r-- | .github/workflows/build-and-run.yml | 18 | ||||
| -rw-r--r-- | .github/workflows/run-tests-32bit.yml | 8 | ||||
| -rw-r--r-- | .github/workflows/run-tests-asan-ubsan.yml | 9 | ||||
| -rw-r--r-- | .github/workflows/run-tests-bigendian.yml | 8 | ||||
| -rw-r--r-- | .github/workflows/run-tests.yml | 32 | ||||
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | README | 182 | ||||
| -rw-r--r-- | README.md | 255 | ||||
| -rw-r--r-- | accel-pppd/accel-ppp.conf | 93 | ||||
| -rw-r--r-- | accel-pppd/accel-ppp.conf.5 | 313 | ||||
| l--------- | accel-pppd/ctrl/sstp/if_ppposeq.h | 1 | ||||
| -rw-r--r-- | accel-pppd/ctrl/sstp/sstp.c | 378 | ||||
| -rw-r--r-- | accel-pppd/extra/metrics.c | 353 | ||||
| -rw-r--r-- | cmake/cpack.cmake | 2 | ||||
| -rw-r--r-- | drivers/ppposeq/CMakeLists.txt | 19 | ||||
| -rw-r--r-- | drivers/ppposeq/Makefile | 4 | ||||
| -rw-r--r-- | drivers/ppposeq/ppposeq.c | 569 | ||||
| -rw-r--r-- | drivers/ppposeq/ppposeq.h | 39 | ||||
| -rw-r--r-- | tests/accel-pppd/general/test_metrics.py | 29 |
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) |
