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.517
l---------accel-pppd/ctrl/sstp/if_ppposeq.h1
-rw-r--r--accel-pppd/ctrl/sstp/sstp.c313
-rw-r--r--drivers/ppposeq/CMakeLists.txt19
-rw-r--r--drivers/ppposeq/Makefile4
-rw-r--r--drivers/ppposeq/ppposeq.c538
-rw-r--r--drivers/ppposeq/ppposeq.h34
15 files changed, 919 insertions, 88 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 bfe97e38..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
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 9e0a63ee..f7cbab01 100644
--- a/accel-pppd/ctrl/sstp/sstp.c
+++ b/accel-pppd/ctrl/sstp/sstp.c
@@ -26,6 +26,7 @@
*/
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/ssl.h>
+#include <openssl/dh.h>
#include <openssl/err.h>
#include "triton.h"
@@ -44,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))
@@ -52,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
@@ -76,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 {
@@ -142,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;
@@ -166,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;
@@ -180,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;
@@ -1047,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));
@@ -1081,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;
+ }
+
+ 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);
@@ -1114,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) {
@@ -1150,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);
@@ -1208,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;
@@ -1222,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);
@@ -1233,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];
@@ -1258,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;
@@ -1286,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);
@@ -1520,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);
@@ -1820,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:
@@ -1838,15 +1976,19 @@ 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
/* payload and FCS octets may both double when escaped, plus 2 flags */
buf = alloc_buf((size + PPP_FCSLEN) * 2 + 2);
if (!buf) {
@@ -1877,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);
}
@@ -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;
@@ -2141,8 +2283,9 @@ static void sstp_flush(struct sstp_conn_t *conn)
if (n < 0) {
if (errno == EINTR)
continue;
- if (conf_verbose && errno != EPIPE)
- log_ppp_info2("sstp: flush: %s\n", strerror(errno));
+ if (errno == EPIPE)
+ break;
+ log_ppp_error("sstp: write: %s\n", strerror(errno));
break;
} else if (n == 0)
break;
@@ -2424,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;
@@ -2870,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");
@@ -2948,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");
@@ -3026,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/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..b1e9e1a5
--- /dev/null
+++ b/drivers/ppposeq/ppposeq.c
@@ -0,0 +1,538 @@
+/*
+ * ppposeq - PPP over SEQPACKET socket driver.
+ *
+ * Replaces the pty + ppp_async transport for userspace PPP terminators
+ * such as sstp. 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 framing is done at all.
+ *
+ * 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/ppp_defs.h>
+#include <linux/ppp-ioctl.h>
+#include <linux/ppp_channel.h>
+#include <linux/if_pppox.h>
+#include <linux/version.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
+
+/* poll returned unsigned int before __poll_t was introduced in 4.16 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,16,0)
+#define __poll_t unsigned int
+#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
+
+/* xmit_flags bits, as in ppp_synctty */
+#define XMIT_WAKEUP 0
+
+#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);
+
+ if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED))
+ goto drop;
+
+ /*
+ * No txmunge here: keeping the queue step free of skb mutation means a
+ * full-queue retry (return 0) re-enters start_xmit on the same skb
+ * without re-munging it. The framing is applied exactly once, on the
+ * dequeue side, in ppposeq_recvmsg.
+ */
+
+ /*
+ * 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();
+
+ if (sock_queue_rcv_skb(sk, skb) < 0) {
+ /*
+ * Receive queue full. Ask ppp_generic to retry: it requeues
+ * the skb, so do not free it here. ppposeq_recvmsg wakes us
+ * once userspace has drained something.
+ */
+ 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.
+ */
+static int ppposeq_sendmsg(struct socket *sock, struct msghdr *m,
+ size_t total_len)
+{
+ 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 *p;
+
+ 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_wmalloc(sk, NET_SKB_PAD + 2 + total_len, 0, GFP_KERNEL);
+ if (!skb) {
+ err = -ENOMEM;
+ 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 */
+ p = skb->data;
+ if (p[0] == PPP_ALLSTATIONS) {
+ /* chop off address/control */
+ if (skb->len < 3 || p[1] != PPP_UI) {
+ kfree_skb(skb);
+ err = -EINVAL;
+ goto out;
+ }
+ p = skb_pull(skb, 2);
+ }
+
+ /* decompress protocol field if compressed */
+ if (p[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;
+}
+
+static int ppposeq_recvmsg(struct socket *sock, struct msghdr *m,
+ size_t total_len, int flags)
+{
+ 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, proto, islcp;
+ u8 *p;
+
+ if (flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ skb = ppposeq_recv_datagram(sk, flags, &err);
+ if (!skb)
+ return err;
+
+ /* add the negotiated framing, like ppp_sync_txmunge */
+ if (likely(skb->len >= 2)) {
+ p = skb->data;
+ proto = (p[0] << 8) + p[1];
+
+ /* LCP codes 1..7 must be sent uncompressed */
+ islcp = (proto == PPP_LCP) && skb->len >= 3 && p[2] >= 1 && p[2] <= 7;
+
+ /* compress protocol field if PFC is in effect */
+ if ((ps->opt.flags & SC_COMP_PROT) && p[0] == 0 && !islcp)
+ skb_pull(skb, 1);
+
+ /* 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)) {
+ err = -ENOMEM;
+ goto out;
+ }
+ skb_push(skb, 2);
+ skb->data[0] = PPP_ALLSTATIONS;
+ skb->data[1] = PPP_UI;
+ }
+ }
+
+ 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;
+
+out:
+ skb_free_datagram(sk, skb);
+ smp_mb();
+
+ /* room freed: let ppp_generic push whatever it had queued */
+ 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_recvmsg 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..ecdbdfb1
--- /dev/null
+++ b/drivers/ppposeq/ppposeq.h
@@ -0,0 +1,34 @@
+#ifndef __PPPOSEQ_H
+#define __PPPOSEQ_H
+
+#include <linux/if_pppox.h>
+
+/*
+ * ppposeq - PPP over a SEQPACKET AF_PPPOX socket.
+ *
+ * Replaces the pty + ppp_async transport for userspace PPP terminators
+ * such as sstp. 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 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 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