summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build-and-run.yml18
-rw-r--r--.github/workflows/run-tests-32bit.yml8
-rw-r--r--.github/workflows/run-tests-asan-ubsan.yml9
-rw-r--r--.github/workflows/run-tests-bigendian.yml8
-rw-r--r--.github/workflows/run-tests.yml32
-rw-r--r--CMakeLists.txt4
-rw-r--r--README.md1
-rw-r--r--accel-pppd/accel-ppp.conf1
-rw-r--r--accel-pppd/accel-ppp.conf.531
-rw-r--r--accel-pppd/cli/cli_p.h84
-rw-r--r--accel-pppd/cli/std_cmd.c41
-rw-r--r--accel-pppd/cli/tcp.c52
-rw-r--r--accel-pppd/cli/telnet.c52
-rw-r--r--accel-pppd/ctrl/ipoe/ipoe.c10
l---------accel-pppd/ctrl/sstp/if_ppposeq.h1
-rw-r--r--accel-pppd/ctrl/sstp/sstp.c374
-rw-r--r--accel-pppd/ipdb.c5
-rw-r--r--accel-pppd/ipv6/dhcpv6.c12
-rw-r--r--accel-pppd/ipv6/nd.c6
-rw-r--r--accel-pppd/main.c20
-rw-r--r--accel-pppd/ppp/ipv6cp_opt_intfid.c17
-rw-r--r--accel-pppd/ppp/ppp.c13
-rw-r--r--accel-pppd/ppp/ppp_ipv6cp.c15
-rw-r--r--accel-pppd/radius/acct.c21
-rw-r--r--accel-pppd/radius/serv.c15
-rw-r--r--accel-pppd/triton/triton.c30
-rw-r--r--accel-pppd/triton/triton.h2
-rw-r--r--drivers/ppposeq/CMakeLists.txt19
-rw-r--r--drivers/ppposeq/Makefile4
-rw-r--r--drivers/ppposeq/ppposeq.c569
-rw-r--r--drivers/ppposeq/ppposeq.h39
31 files changed, 1303 insertions, 210 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.md b/README.md
index 2ce25b5f..a78cb145 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,7 @@ 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.
diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf
index c9b2017d..56fe0a8c 100644
--- a/accel-pppd/accel-ppp.conf
+++ b/accel-pppd/accel-ppp.conf
@@ -177,6 +177,7 @@ verbose=1
#http-error=allow
#timeout=60
#hello-interval=60
+#ppp-mode=auto
#ppp-max-mtu=1452
#sndbuf=0
#rcvbuf=0
diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5
index fc9c0f9b..bcddfed4 100644
--- a/accel-pppd/accel-ppp.conf.5
+++ b/accel-pppd/accel-ppp.conf.5
@@ -995,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
@@ -1690,13 +1705,19 @@ If \fIn\fR = 2 then cli module will also log passed commands.
.TP
.BI "tcp=" host:port
Defines on which IP address and port the TCP module will listen for incoming
-connections. When \fIhost\fR is empty, the TCP module listens on all local
-interfaces. It isn't loaded if this option isn't defined.
+connections. \fIhost\fR may be an IPv4 or an IPv6 address; IPv6 addresses may
+be enclosed in square brackets (e.g. \fI[::1]:2001\fR). When \fIhost\fR is
+empty, the TCP module listens on all local interfaces (use \fI[::]:port\fR to
+listen on all interfaces over IPv6). It isn't loaded if this option isn't
+defined.
.TP
.BI "telnet=" host:port
Defines on which IP address and port the Telnet module will listen for incoming
-connections. When \fIhost\fR is empty, the Telnet module listens on all local
-interfaces. It isn't loaded if this option isn't defined.
+connections. \fIhost\fR may be an IPv4 or an IPv6 address; IPv6 addresses may
+be enclosed in square brackets (e.g. \fI[::1]:2000\fR). When \fIhost\fR is
+empty, the Telnet module listens on all local interfaces (use \fI[::]:port\fR
+to listen on all interfaces over IPv6). It isn't loaded if this option isn't
+defined.
.TP
.BI "password=" passwd
Defines the password to be used by the TCP and Telnet modules for
diff --git a/accel-pppd/cli/cli_p.h b/accel-pppd/cli/cli_p.h
index 0fcba309..79208e13 100644
--- a/accel-pppd/cli/cli_p.h
+++ b/accel-pppd/cli/cli_p.h
@@ -2,6 +2,11 @@
#define __CLI_P_H
#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
#include "triton.h"
@@ -15,6 +20,85 @@ struct cli_client_t
int cli_process_cmd(struct cli_client_t *cln);
+/* Format peer address (IPv4, IPv6 or IPv4-mapped IPv6) for logging.
+ * buf must be at least INET6_ADDRSTRLEN bytes long. */
+static inline const char *cli_addr_str(const struct sockaddr_storage *addr,
+ char *buf, size_t size)
+{
+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)addr;
+ const struct sockaddr_in *sin = (const struct sockaddr_in *)addr;
+
+ buf[0] = '\0';
+ if (addr->ss_family == AF_INET6) {
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
+ inet_ntop(AF_INET, &sin6->sin6_addr.s6_addr32[3], buf, size);
+ else
+ inet_ntop(AF_INET6, &sin6->sin6_addr, buf, size);
+ } else
+ inet_ntop(AF_INET, &sin->sin_addr, buf, size);
+
+ return buf;
+}
+
+/* Parse "host:port", "[host]:port" or ":port" listener specification.
+ * str is modified in place, *host points into str afterwards (NULL for
+ * empty host). For unbracketed hosts the last ':' separates the port,
+ * so bare IPv6 addresses like "::1:2001" are accepted too.
+ * Returns 0 on success, -1 on invalid format. */
+static inline int cli_parse_hostport(char *str, const char **host, int *port)
+{
+ char *d;
+
+ if (*str == '[') {
+ ++str;
+ d = strchr(str, ']');
+ if (!d || d[1] != ':')
+ return -1;
+ *d++ = '\0';
+ } else {
+ d = strrchr(str, ':');
+ if (!d)
+ return -1;
+ }
+
+ *d = '\0';
+ *port = atoi(d + 1);
+ if (*port <= 0)
+ return -1;
+
+ *host = *str ? str : NULL;
+
+ return 0;
+}
+
+/* Fill sockaddr for binding a CLI listener. host may be an IPv4 or IPv6
+ * address; NULL host means any IPv4 address (use "::" for IPv6 wildcard).
+ * Returns 0 on success, -1 if host is not a valid address. */
+static inline int cli_bind_addr(const char *host, int port,
+ struct sockaddr_storage *addr, socklen_t *len)
+{
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
+ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
+
+ memset(addr, 0, sizeof(*addr));
+
+ if (host && inet_pton(AF_INET6, host, &sin6->sin6_addr) > 0) {
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(port);
+ *len = sizeof(*sin6);
+ } else {
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(port);
+ if (!host)
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ else if (inet_pton(AF_INET, host, &sin->sin_addr) <= 0)
+ return -1;
+ *len = sizeof(*sin);
+ }
+
+ return 0;
+}
+
extern char *conf_cli_passwd;
extern char *conf_cli_prompt;
diff --git a/accel-pppd/cli/std_cmd.c b/accel-pppd/cli/std_cmd.c
index 951b10da..1d15a28c 100644
--- a/accel-pppd/cli/std_cmd.c
+++ b/accel-pppd/cli/std_cmd.c
@@ -340,26 +340,41 @@ static int shutdown_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
}
//==========================
-static int conf_reload_res;
-static struct triton_context_t *conf_reload_ctx;
-static void conf_reload_notify(int r)
+struct conf_reload_req {
+ struct triton_context_t *ctx;
+ int res;
+};
+static void conf_reload_notify(int r, void *arg)
{
+ struct conf_reload_req *req = arg;
+
if (!r)
triton_event_fire(EV_CONFIG_RELOAD, NULL);
- conf_reload_res = r;
- triton_context_wakeup(conf_reload_ctx);
+ req->res = r;
+ triton_context_wakeup(req->ctx);
}
static int reload_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
{
- if (f_cnt == 1) {
- conf_reload_ctx = triton_context_self();
- triton_conf_reload(conf_reload_notify);
- triton_context_schedule();
- if (conf_reload_res)
- cli_send(cli, "failed\r\n");
- return CLI_CMD_OK;
- } else
+ struct conf_reload_req *req;
+
+ if (f_cnt != 1)
return CLI_CMD_SYNTAX;
+
+ /* heap-allocated: triton_context_schedule() can migrate this
+ * context to another worker thread's stack before notify runs */
+ req = _malloc(sizeof(*req));
+ req->ctx = triton_context_self();
+
+ if (triton_conf_reload(conf_reload_notify, req)) {
+ _free(req);
+ cli_send(cli, "reload is already in progress\r\n");
+ return CLI_CMD_OK;
+ }
+ triton_context_schedule();
+ if (req->res)
+ cli_send(cli, "failed\r\n");
+ _free(req);
+ return CLI_CMD_OK;
}
static void reload_help(char * const *fields, int fields_cnt, void *client)
diff --git a/accel-pppd/cli/tcp.c b/accel-pppd/cli/tcp.c
index 8952661a..beba16ca 100644
--- a/accel-pppd/cli/tcp.c
+++ b/accel-pppd/cli/tcp.c
@@ -24,7 +24,7 @@ struct tcp_client_t {
struct cli_client_t cli_client;
struct list_head entry;
struct triton_md_handler_t hnd;
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
struct list_head xmit_queue;
struct buffer_t *xmit_buf;
uint8_t *cmdline;
@@ -174,8 +174,10 @@ static int cln_read(struct triton_md_handler_t *h)
goto disconn_hard;
cln->auth = 1;
} else {
- if (conf_verbose == 2)
- log_info2("cli: %s: %s\n", inet_ntoa(cln->addr.sin_addr), cln->cmdline);
+ if (conf_verbose == 2) {
+ char buf[INET6_ADDRSTRLEN];
+ log_info2("cli: %s: %s\n", cli_addr_str(&cln->addr, buf, sizeof(buf)), cln->cmdline);
+ }
cli_process_cmd(&cln->cli_client);
}
@@ -248,12 +250,14 @@ disconn:
static int serv_read(struct triton_md_handler_t *h)
{
- struct sockaddr_in addr;
- socklen_t size = sizeof(addr);
+ struct sockaddr_storage addr;
+ socklen_t size;
int sock;
struct tcp_client_t *conn;
+ char buf[INET6_ADDRSTRLEN];
while(1) {
+ size = sizeof(addr);
sock = accept(h->fd, (struct sockaddr *)&addr, &size);
if (sock < 0) {
if (errno == EAGAIN)
@@ -263,7 +267,7 @@ static int serv_read(struct triton_md_handler_t *h)
}
if (conf_verbose)
- log_info2("cli: tcp: new connection from %s\n", inet_ntoa(addr.sin_addr));
+ log_info2("cli: tcp: new connection from %s\n", cli_addr_str(&addr, buf, sizeof(buf)));
if (fcntl(sock, F_SETFL, O_NONBLOCK)) {
log_error("cli: tcp: failed to set nonblocking mode: %s, closing connection...\n", strerror(errno));
@@ -320,9 +324,16 @@ static struct triton_md_handler_t serv_hnd = {
static void start_server(const char *host, int port)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int f = 1;
+
+ if (cli_bind_addr(host, port, &addr, &addrlen) < 0) {
+ log_emerg("cli: tcp: invalid address '%s'\n", host);
+ return;
+ }
- serv_hnd.fd = socket(PF_INET, SOCK_STREAM, 0);
+ serv_hnd.fd = socket(addr.ss_family, SOCK_STREAM, 0);
if (serv_hnd.fd < 0) {
log_emerg("cli: tcp: failed to create server socket: %s\n", strerror(errno));
return;
@@ -330,16 +341,8 @@ static void start_server(const char *host, int port)
fcntl(serv_hnd.fd, F_SETFD, fcntl(serv_hnd.fd, F_GETFD) | FD_CLOEXEC);
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- if (host)
- addr.sin_addr.s_addr = inet_addr(host);
- else
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &serv_hnd.fd, 4);
- if (bind (serv_hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
+ setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f));
+ if (bind (serv_hnd.fd, (struct sockaddr *) &addr, addrlen) < 0) {
log_emerg("cli: tcp: failed to bind socket: %s\n", strerror(errno));
close(serv_hnd.fd);
return;
@@ -378,7 +381,8 @@ static void load_config(void)
static void init(void)
{
const char *opt;
- char *host, *d;
+ const char *addr;
+ char *host;
int port;
opt = conf_get_opt("cli", "tcp");
@@ -386,20 +390,14 @@ static void init(void)
return;
host = strdup(opt);
- d = strstr(host, ":");
- if (!d)
- goto err_fmt;
-
- *d = 0;
- port = atoi(d + 1);
- if (port <= 0)
+ if (cli_parse_hostport(host, &addr, &port) < 0)
goto err_fmt;
load_config();
temp_buf = malloc(RECV_BUF_SIZE);
- start_server(host, port);
+ start_server(addr, port);
triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config);
diff --git a/accel-pppd/cli/telnet.c b/accel-pppd/cli/telnet.c
index 7d5009c2..88a9d8b0 100644
--- a/accel-pppd/cli/telnet.c
+++ b/accel-pppd/cli/telnet.c
@@ -37,7 +37,7 @@ struct telnet_client_t {
struct cli_client_t cli_client;
struct list_head entry;
struct triton_md_handler_t hnd;
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
struct list_head xmit_queue;
struct buffer_t *xmit_buf;
int xmit_pos;
@@ -305,8 +305,10 @@ static int telnet_input_char(struct telnet_client_t *cln, uint8_t c)
list_add(&b->entry, cln->history.next);
cln->history_pos = cln->history.next;
- if (conf_verbose == 2)
- log_info2("cli: %s: %s\n", inet_ntoa(cln->addr.sin_addr), cln->cmdline);
+ if (conf_verbose == 2) {
+ char abuf[INET6_ADDRSTRLEN];
+ log_info2("cli: %s: %s\n", cli_addr_str(&cln->addr, abuf, sizeof(abuf)), cln->cmdline);
+ }
if (cli_process_cmd(&cln->cli_client))
return -1;
@@ -556,13 +558,15 @@ disconn:
static int serv_read(struct triton_md_handler_t *h)
{
- struct sockaddr_in addr;
- socklen_t size = sizeof(addr);
+ struct sockaddr_storage addr;
+ socklen_t size;
int sock;
struct telnet_client_t *conn;
struct buffer_t *b, *b2;
+ char abuf[INET6_ADDRSTRLEN];
while(1) {
+ size = sizeof(addr);
sock = accept(h->fd, (struct sockaddr *)&addr, &size);
if (sock < 0) {
if (errno == EAGAIN)
@@ -572,7 +576,7 @@ static int serv_read(struct triton_md_handler_t *h)
}
if (conf_verbose)
- log_info2("cli: telnet: new connection from %s\n", inet_ntoa(addr.sin_addr));
+ log_info2("cli: telnet: new connection from %s\n", cli_addr_str(&addr, abuf, sizeof(abuf)));
fcntl(sock, F_SETFL, O_NONBLOCK);
fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC);
@@ -657,9 +661,16 @@ static struct triton_md_handler_t serv_hnd = {
static void start_server(const char *host, int port)
{
- struct sockaddr_in addr;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int f = 1;
+
+ if (cli_bind_addr(host, port, &addr, &addrlen) < 0) {
+ log_emerg("cli: telnet: invalid address '%s'\n", host);
+ return;
+ }
- serv_hnd.fd = socket(PF_INET, SOCK_STREAM, 0);
+ serv_hnd.fd = socket(addr.ss_family, SOCK_STREAM, 0);
if (serv_hnd.fd < 0) {
log_emerg("cli: telnet: failed to create server socket: %s\n", strerror(errno));
return;
@@ -667,16 +678,8 @@ static void start_server(const char *host, int port)
fcntl(serv_hnd.fd, F_SETFD, fcntl(serv_hnd.fd, F_GETFD) | FD_CLOEXEC);
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- if (host)
- addr.sin_addr.s_addr = inet_addr(host);
- else
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &serv_hnd.fd, 4);
- if (bind (serv_hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
+ setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f));
+ if (bind (serv_hnd.fd, (struct sockaddr *) &addr, addrlen) < 0) {
log_emerg("cli: telnet: failed to bind socket: %s\n", strerror(errno));
close(serv_hnd.fd);
return;
@@ -755,7 +758,8 @@ static void load_config(void)
static void init(void)
{
const char *opt;
- char *host, *d;
+ const char *addr;
+ char *host;
int port;
opt = conf_get_opt("cli", "telnet");
@@ -763,13 +767,7 @@ static void init(void)
return;
host = strdup(opt);
- d = strstr(host, ":");
- if (!d)
- goto err_fmt;
-
- *d = 0;
- port = atoi(d + 1);
- if (port <= 0)
+ if (cli_parse_hostport(host, &addr, &port) < 0)
goto err_fmt;
opt = conf_get_opt("cli", "history-file");
@@ -783,7 +781,7 @@ static void init(void)
load_history_file();
- start_server(host, port);
+ start_server(addr, port);
atexit(save_history_file);
diff --git a/accel-pppd/ctrl/ipoe/ipoe.c b/accel-pppd/ctrl/ipoe/ipoe.c
index 4bf43434..18e9228d 100644
--- a/accel-pppd/ctrl/ipoe/ipoe.c
+++ b/accel-pppd/ctrl/ipoe/ipoe.c
@@ -783,6 +783,10 @@ static void ipoe_session_start(struct ipoe_session *ses)
return;
}
+ /* take ownership now so the string is freed by ipoe_session_free()
+ * even if the session terminates before auth_result() consumes it */
+ ses->username = username;
+
ses->ses.unit_idx = ses->serv->ifindex;
triton_event_fire(EV_CTRL_STARTING, &ses->ses);
@@ -794,7 +798,6 @@ static void ipoe_session_start(struct ipoe_session *ses)
return;
if (conf_noauth) {
- ses->username = username;
r = PWDB_SUCCESS;
} else {
#ifdef RADIUS
@@ -813,7 +816,6 @@ static void ipoe_session_start(struct ipoe_session *ses)
} else
pass = username;
- ses->username = username;
r = pwdb_check(&ses->ses, (pwdb_callback)auth_result, ses, username, PPP_PAP, pass);
if (r == PWDB_WAIT)
@@ -1242,6 +1244,9 @@ static void ipoe_session_free(struct ipoe_session *ses)
if (ses->l4_redirect_ipset)
_free(ses->l4_redirect_ipset);
+ if (ses->username)
+ _free(ses->username);
+
triton_context_unregister(&ses->ctx);
if (ses->data)
@@ -2030,6 +2035,7 @@ static void ipoe_ses_recv_dhcpv4_relay(struct dhcpv4_packet *pack)
if (!ses->dhcpv4_request) {
ses->dhcpv4_relay_reply = NULL;
+ dhcpv4_packet_free(pack);
return;
}
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..61f4e163 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);
}
@@ -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/ipdb.c b/accel-pppd/ipdb.c
index dd8c61c9..8fc08063 100644
--- a/accel-pppd/ipdb.c
+++ b/accel-pppd/ipdb.c
@@ -83,7 +83,10 @@ void __export build_ip6_addr(struct ipv6db_addr_t *a, uint64_t intf_id, struct i
if (a->prefix_len <= 64)
*(uint64_t *)(addr->s6_addr + 8) = intf_id;
else
- *(uint64_t *)(addr->s6_addr + 8) |= intf_id & htobe64((1 << (128 - a->prefix_len)) - 1);
+ /* prefix_len 65..127 means a shift of up to 63 bits: a plain
+ * int literal 1 is undefined behavior for shifts >= 31, so the
+ * host bits mask must be built from a 64-bit constant */
+ *(uint64_t *)(addr->s6_addr + 8) |= intf_id & htobe64((UINT64_C(1) << (128 - a->prefix_len)) - 1);
}
diff --git a/accel-pppd/ipv6/dhcpv6.c b/accel-pppd/ipv6/dhcpv6.c
index 92a6ed6c..b284df5f 100644
--- a/accel-pppd/ipv6/dhcpv6.c
+++ b/accel-pppd/ipv6/dhcpv6.c
@@ -870,13 +870,15 @@ static int dhcpv6_read(struct triton_md_handler_t *h)
static void add_aftr_gw(const char *val)
{
- int n = strlen(val);
+ int n;
const char *ptr;
uint8_t *buf;
- if (!val)
+ if (!val || !*val)
return;
+ n = strlen(val);
+
if (val[n - 1] == '.')
n++;
else
@@ -917,13 +919,15 @@ static void add_aftr_gw(const char *val)
static void add_dnssl(const char *val)
{
- int n = strlen(val);
+ int n;
const char *ptr;
uint8_t *buf;
- if (!val)
+ if (!val || !*val)
return;
+ n = strlen(val);
+
if (val[n - 1] == '.')
n++;
else
diff --git a/accel-pppd/ipv6/nd.c b/accel-pppd/ipv6/nd.c
index 297e4d63..6dd00ee2 100644
--- a/accel-pppd/ipv6/nd.c
+++ b/accel-pppd/ipv6/nd.c
@@ -412,13 +412,15 @@ static void ev_ses_finishing(struct ap_session *ses)
static void add_dnssl(const char *val)
{
- int n = strlen(val);
+ int n;
const char *ptr;
uint8_t *buf;
- if (!val)
+ if (!val || !*val)
return;
+ n = strlen(val);
+
if (val[n - 1] == '.')
n++;
else
diff --git a/accel-pppd/main.c b/accel-pppd/main.c
index 515b8036..221be7be 100644
--- a/accel-pppd/main.c
+++ b/accel-pppd/main.c
@@ -41,6 +41,8 @@ static int term;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static volatile sig_atomic_t need_reload;
+
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
static pthread_mutex_t *ssl_lock_cs;
@@ -100,7 +102,7 @@ static void change_limits(void)
log_emerg("main: setrlimit: %s\n", strerror(errno));
}
-static void config_reload_notify(int r)
+static void config_reload_notify(int r, void *arg)
{
if (!r)
triton_event_fire(EV_CONFIG_RELOAD, NULL);
@@ -108,7 +110,7 @@ static void config_reload_notify(int r)
static void config_reload(int num)
{
- triton_conf_reload(config_reload_notify);
+ need_reload = 1;
}
static void close_all_fd(void)
@@ -437,7 +439,19 @@ int main(int _argc, char **_argv)
backup_restore(internal);
#endif
- sigwait(&set, &sig);
+ {
+ struct timespec ts = { .tv_sec = 1 };
+ while (1) {
+ sig = sigtimedwait(&set, NULL, &ts);
+ if (sig > 0)
+ break;
+ if (need_reload) {
+ need_reload = 0;
+ if (triton_conf_reload(config_reload_notify, NULL))
+ log_warn("main: config reload is already in progress\n");
+ }
+ }
+ }
log_info1("terminate, sig = %i\n", sig);
ap_shutdown_soft(shutdown_cb, 1);
diff --git a/accel-pppd/ppp/ipv6cp_opt_intfid.c b/accel-pppd/ppp/ipv6cp_opt_intfid.c
index fff74160..cb33f024 100644
--- a/accel-pppd/ppp/ipv6cp_opt_intfid.c
+++ b/accel-pppd/ppp/ipv6cp_opt_intfid.c
@@ -1,6 +1,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
+#include <endian.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
@@ -25,9 +26,16 @@
static int conf_check_exists;
static int conf_intf_id = INTF_ID_FIXED;
-static uint64_t conf_intf_id_val = 1;
+/* Fixed intf-id values are stored in network byte order: the rest of
+ * the code (build_ip6_addr(), ifcfg.c, nd.c, ...) copies them into the
+ * low 8 bytes of the IPv6 address as-is, and parse_intfid() already
+ * produces network byte order. The old host-order defaults (1 and 2)
+ * therefore yielded ::100:0:0:0 / ::200:0:0:0 instead of ::1 / ::2 on
+ * little-endian hosts. Defaults are assigned in init() because
+ * htobe64() is not a constant expression on all libcs (e.g. musl). */
+static uint64_t conf_intf_id_val;
static int conf_peer_intf_id = INTF_ID_FIXED;
-static uint64_t conf_peer_intf_id_val = 2;
+static uint64_t conf_peer_intf_id_val;
static int conf_accept_peer_intf_id;
static struct ipv6cp_option_t *ipaddr_init(struct ppp_ipv6cp_t *ipv6cp);
@@ -354,6 +362,11 @@ static void load_config(void)
static void init()
{
+ /* network byte order, so the resulting addresses are ::1 and ::2
+ * regardless of host endianness */
+ conf_intf_id_val = htobe64(1);
+ conf_peer_intf_id_val = htobe64(2);
+
if (sock6_fd < 0)
return;
diff --git a/accel-pppd/ppp/ppp.c b/accel-pppd/ppp/ppp.c
index 55d08bb7..cd726e17 100644
--- a/accel-pppd/ppp/ppp.c
+++ b/accel-pppd/ppp/ppp.c
@@ -273,7 +273,7 @@ static void destablish_ppp(struct ppp_t *ppp)
if (ppp->ses.net != def_net) {
if (net->move_link(def_net, ppp->ses.ifindex)) {
log_ppp_warn("failed to attach to default namespace\n");
- triton_md_unregister_handler(&ppp->unit_hnd, 1);
+ triton_md_unregister_handler(&ppp->unit_hnd, 0);
goto skip;
}
ppp->ses.net = def_net;
@@ -286,7 +286,7 @@ static void destablish_ppp(struct ppp_t *ppp)
strncpy(ifr.ifr_name, ppp->ses.ifname, IFNAMSIZ);
if (net->sock_ioctl(SIOCSIFNAME, &ifr)) {
log_ppp_warn("failed to rename ppp to default name\n");
- triton_md_unregister_handler(&ppp->unit_hnd, 1);
+ triton_md_unregister_handler(&ppp->unit_hnd, 0);
goto skip;
}
}
@@ -299,11 +299,18 @@ static void destablish_ppp(struct ppp_t *ppp)
uc->fd = ppp->unit_fd;
uc->unit_idx = ppp->ses.unit_idx;
} else
- triton_md_unregister_handler(&ppp->unit_hnd, 1);
+ triton_md_unregister_handler(&ppp->unit_hnd, 0);
skip:
ap_session_finished(&ppp->ses);
+ /* The unit fd is closed only after session cleanup (ip-down scripts,
+ * radattr removal) has finished, so the kernel cannot give the same
+ * unit index (and thus ifname) to a new session while cleanup still
+ * references the ifname. */
+ if (!uc)
+ close(ppp->unit_fd);
+
ppp->unit_fd = -1;
destroy_ppp_channel(ppp);
diff --git a/accel-pppd/ppp/ppp_ipv6cp.c b/accel-pppd/ppp/ppp_ipv6cp.c
index 1194b314..7f278daa 100644
--- a/accel-pppd/ppp/ppp_ipv6cp.c
+++ b/accel-pppd/ppp/ppp_ipv6cp.c
@@ -205,8 +205,12 @@ static void ipv6cp_layer_finished(struct ppp_fsm_t *fsm)
ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0);
else
ppp_layer_passive(ipv6cp->ppp, &ipv6cp->ld);
- } else if (!ipv6cp->ppp->ses.terminating)
- ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0);
+ } else if (!ipv6cp->ppp->ses.terminating) {
+ if (conf_ipv6 == IPV6_REQUIRE)
+ ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0);
+ else
+ log_ppp_info1("ipv6cp: closed, session continues without IPv6\n");
+ }
fsm->fsm_state = FSM_Closed;
}
@@ -738,7 +742,12 @@ static void ipv6cp_recv(struct ppp_handler_t*h)
if (conf_ppp_verbose)
log_ppp_info2("recv [IPV6CP TermReq id=%x]\n", hdr->id);
ppp_fsm_recv_term_req(&ipv6cp->fsm);
- ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_REQUEST, 0);
+ /* RFC 1661 sec 3.7: closing one NCP is not sufficient reason
+ * to terminate the PPP link */
+ if (conf_ipv6 == IPV6_REQUIRE)
+ ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_REQUEST, 0);
+ else
+ ppp_layer_passive(ipv6cp->ppp, &ipv6cp->ld);
break;
case TERMACK:
if (conf_ppp_verbose)
diff --git a/accel-pppd/radius/acct.c b/accel-pppd/radius/acct.c
index 69bd3d56..d30aae7a 100644
--- a/accel-pppd/radius/acct.c
+++ b/accel-pppd/radius/acct.c
@@ -373,6 +373,16 @@ static void rad_acct_stop_sent(struct rad_req_t *req, int res)
rpd->acct_req = NULL;
} else if (req->rpd)
rad_acct_stop_defer(req->rpd);
+ else {
+ /* deferred request: the timeout timer is one-shot and
+ * nobody else references this request, re-arm it to
+ * retry later, otherwise the request and its socket
+ * leak */
+ if (req->timeout.tpd)
+ triton_timer_mod(&req->timeout, 0);
+ else
+ triton_timer_add(NULL, &req->timeout, 0);
+ }
return;
}
@@ -423,7 +433,7 @@ static void rad_acct_stop_timeout(struct triton_timer_t *t)
req->pack->id++;
}
- if (req->try == conf_max_try) {
+ if (req->try >= conf_max_try) {
if (req->rpd)
req->rpd->acct_req = NULL;
rad_req_free(req);
@@ -437,7 +447,14 @@ static void rad_acct_stop_timeout(struct triton_timer_t *t)
rad_req_free(req);
return;
}
- req->try = 0;
+ /* no server available at the moment; the timeout timer is
+ * one-shot, re-arm it to retry later, otherwise the request
+ * and its socket leak; failed attempts count towards
+ * conf_max_try so the request is freed above eventually */
+ if (req->timeout.tpd)
+ triton_timer_mod(&req->timeout, 0);
+ else
+ triton_timer_add(req->rpd ? req->rpd->ses->ctrl->ctx : NULL, &req->timeout, 0);
}
}
diff --git a/accel-pppd/radius/serv.c b/accel-pppd/radius/serv.c
index b48e53ac..71398c82 100644
--- a/accel-pppd/radius/serv.c
+++ b/accel-pppd/radius/serv.c
@@ -152,7 +152,20 @@ static void req_wakeup(struct rad_req_t *req)
}
pthread_mutex_unlock(&req->serv->lock);
- req->send(req, 1);
+ if (req->send(req, 1) == -2) {
+ /* socket setup failed: release the slot taken in
+ * rad_server_req_exit() and drive the failover path,
+ * otherwise the server's req_cnt leaks and the request
+ * is orphaned */
+ req->active = 0;
+ pthread_mutex_lock(&req->serv->lock);
+ req->serv->req_cnt--;
+ pthread_mutex_unlock(&req->serv->lock);
+
+ rad_server_fail(req->serv);
+
+ req->send(req, -1);
+ }
}
static void req_wakeup_failed(struct rad_req_t *req)
diff --git a/accel-pppd/triton/triton.c b/accel-pppd/triton/triton.c
index 5c7231b7..105f810a 100644
--- a/accel-pppd/triton/triton.c
+++ b/accel-pppd/triton/triton.c
@@ -32,8 +32,10 @@ static LIST_HEAD(init_list);
static int terminate;
static int need_terminate;
+/* 0 - idle, 1 - reload requested, 2 - reload running */
static int need_config_reload;
-static void (*config_reload_notify)(int);
+static void (*config_reload_notify)(int, void *);
+static void *config_reload_arg;
static mempool_t *ctx_pool;
static mempool_t *call_pool;
@@ -210,14 +212,14 @@ void triton_thread_wakeup(struct _triton_thread_t *thread)
pthread_kill(thread->thread, SIGUSR1);
}
-static void __config_reload(void (*notify)(int))
+static void __config_reload(void)
{
struct _triton_thread_t *t;
int r;
log_debug2("config_reload: enter\n");
r = conf_reload(NULL);
- notify(r);
+ config_reload_notify(r, config_reload_arg);
spin_lock(&threads_lock);
need_config_reload = 0;
@@ -301,9 +303,10 @@ static void* triton_thread(struct _triton_thread_t *thread)
if (!terminate)
list_add(&thread->entry2, &sleep_threads);
- if (triton_stat_thread_active_dec() == 0 && need_config_reload) {
+ if (triton_stat_thread_active_dec() == 0 && need_config_reload == 1) {
+ need_config_reload = 2;
spin_unlock(&threads_lock);
- __config_reload(config_reload_notify);
+ __config_reload();
} else
spin_unlock(&threads_lock);
@@ -856,16 +859,25 @@ int __export triton_load_modules(const char *mod_sect)
return 0;
}
-void __export triton_conf_reload(void (*notify)(int))
+int __export triton_conf_reload(void (*notify)(int, void *), void *arg)
{
spin_lock(&threads_lock);
- need_config_reload = 1;
+ if (need_config_reload) {
+ spin_unlock(&threads_lock);
+ return -1;
+ }
config_reload_notify = notify;
+ config_reload_arg = arg;
if (triton_stat_thread_active() == 0) {
+ need_config_reload = 2;
spin_unlock(&threads_lock);
- __config_reload(notify);
- } else
+ __config_reload();
+ } else {
+ need_config_reload = 1;
spin_unlock(&threads_lock);
+ }
+
+ return 0;
}
void __export triton_run()
diff --git a/accel-pppd/triton/triton.h b/accel-pppd/triton/triton.h
index aef77c2b..63dc9188 100644
--- a/accel-pppd/triton/triton.h
+++ b/accel-pppd/triton/triton.h
@@ -104,7 +104,7 @@ void triton_event_fire(int ev_id, void *arg);
struct conf_sect_t *conf_get_section(const char *name);
char *conf_get_opt(const char *sect, const char *name);
-void triton_conf_reload(void (*notify)(int));
+int triton_conf_reload(void (*notify)(int, void *), void *arg);
void triton_collect_cpu_usage(void);
void triton_stop_collect_cpu_usage(void);
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