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.c378
-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
15 files changed, 1000 insertions, 108 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 f29a0274..e34ae919 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 1d69b22b..07fb363d 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 6c2b9ca9..2fd8cb35 100644
--- a/accel-pppd/ctrl/sstp/sstp.c
+++ b/accel-pppd/ctrl/sstp/sstp.c
@@ -4,6 +4,7 @@
#include <stdarg.h>
#include <errno.h>
#include <string.h>
+#include <inttypes.h>
#include <fcntl.h>
#include <time.h>
#include <termios.h>
@@ -25,6 +26,7 @@
*/
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/ssl.h>
+#include <openssl/dh.h>
#include <openssl/err.h>
#include "triton.h"
@@ -43,6 +45,7 @@
#include "proxy_prot.h"
#include "sstp.h"
#include "sstp_prot.h"
+#include "if_ppposeq.h"
#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
@@ -51,7 +54,6 @@
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif
-#define PPP_SYNC 0 /* buggy yet */
#define PPP_BUF_SIZE 8192
#define PPP_BUF_IOVEC 256
#define PPP_F_ESCAPE 1
@@ -75,6 +77,20 @@ enum {
STATE_FINISHED,
};
+enum {
+ HTTP_ERR_ALLOW = -1,
+ HTTP_ERR_DENY = 0,
+ HTTP_ERR_REDIRECT = 1,
+ HTTP_ERR_REDIRECT_APPEND = 2,
+};
+
+/* seqpacket needs the ppposeq module, async is the legacy pty path */
+enum {
+ PPP_MODE_AUTO = -1,
+ PPP_MODE_ASYNC = 0,
+ PPP_MODE_SEQPACKET = 1,
+};
+
struct sockaddr_t {
socklen_t len;
union {
@@ -141,6 +157,7 @@ struct sstp_conn_t {
int ppp_state;
int ppp_flags;
+ int ppp_mode;
struct buffer_t *ppp_in;
struct list_head ppp_queue;
@@ -165,6 +182,7 @@ static int conf_timeout = SSTP_NEGOTIOATION_TIMEOUT;
static int conf_hello_interval = SSTP_HELLO_TIMEOUT;
static int conf_verbose = 0;
static int conf_ppp_max_mtu = 1452;
+static int conf_ppp_mode = PPP_MODE_AUTO;
static const char *conf_ip_pool;
static const char *conf_ipv6_pool;
static const char *conf_dpv6_pool;
@@ -179,12 +197,6 @@ static struct hash_t conf_hash_sha1 = { .len = 0 };
static struct hash_t conf_hash_sha256 = { .len = 0 };
//static int conf_bypass_auth = 0;
static const char *conf_hostname = NULL;
-enum {
- HTTP_ERR_ALLOW = -1,
- HTTP_ERR_DENY = 0,
- HTTP_ERR_REDIRECT = 1,
- HTTP_ERR_REDIRECT_APPEND = 2,
-};
static int conf_http_mode = HTTP_ERR_ALLOW;
static const char *conf_http_url = NULL;
@@ -193,7 +205,6 @@ static mempool_t conn_pool;
static inline void sstp_queue(struct sstp_conn_t *conn, struct buffer_t *buf);
static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf);
static inline void sstp_queue_deferred(struct sstp_conn_t *conn, struct buffer_t *buf);
-static int sstp_write(struct triton_md_handler_t *h);
static int sstp_read_deferred(struct sstp_conn_t *conn);
static int sstp_abort(struct sstp_conn_t *conn, int disconnect);
static void sstp_disconnect(struct sstp_conn_t *conn);
@@ -867,7 +878,7 @@ static char *http_getvalue(char *line, const char *name, int len)
return sep ? line : NULL;
}
-static int http_send_response(struct sstp_conn_t *conn, char *proto, char *status, char *headers)
+static int http_send_response(struct sstp_conn_t *conn, char *proto, char *status, char *headers, u_int64_t length)
{
char datetime[sizeof("aaa, dd bbb yyyy HH:MM:SS GMT")];
char linebuf[1024], *line;
@@ -880,7 +891,12 @@ static int http_send_response(struct sstp_conn_t *conn, char *proto, char *statu
/* "Server: %s\r\n" */
"Date: %s\r\n"
"%s"
- "\r\n", proto, status, /* "accel-ppp",*/ datetime, headers ? : "");
+ "Content-Length: %" PRIu64 "\r\n"
+ "Connection: %s\r\n"
+ "\r\n",
+ proto, status, /* "accel-ppp",*/ datetime,
+ headers ? : "",
+ length, length ? "keep-alive" : "close");
if (!buf) {
log_error("sstp: no memory\n");
return -1;
@@ -895,7 +911,7 @@ static int http_send_response(struct sstp_conn_t *conn, char *proto, char *statu
}
}
- return sstp_send(conn, buf) || sstp_write(&conn->hnd);
+ return sstp_send(conn, buf);
}
static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
@@ -917,17 +933,17 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
if (vstrsep(line, " ", &method, &request, &proto) < 3) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL);
+ http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL, 0);
return -1;
}
if (strncasecmp(proto, "HTTP/1", sizeof("HTTP/1") - 1) != 0) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL);
+ http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL, 0);
return -1;
}
if (strcasecmp(method, SSTP_HTTP_METHOD) != 0 && strcasecmp(method, "GET") != 0) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, proto, "501 Not Implemented", NULL);
+ http_send_response(conn, proto, "501 Not Implemented", NULL, 0);
return -1;
}
@@ -949,7 +965,7 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
if (host_error) {
if (conf_http_mode != HTTP_ERR_DENY)
- http_send_response(conn, proto, "404 Not Found", NULL);
+ http_send_response(conn, proto, "404 Not Found", NULL, 0);
return -1;
}
@@ -958,15 +974,14 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len)
if (_asprintf(&line, "Location: %s%s\r\n",
conf_http_url, (conf_http_mode == HTTP_ERR_REDIRECT_APPEND) ? request : "") < 0)
return -1;
- http_send_response(conn, proto, "301 Moved Permanently", line);
+ http_send_response(conn, proto, "301 Moved Permanently", line, 0);
_free(line);
} else if (conf_http_mode == HTTP_ERR_ALLOW)
- http_send_response(conn, proto, "404 Not Found", NULL);
+ http_send_response(conn, proto, "404 Not Found", NULL, 0);
return -1;
}
- return http_send_response(conn, proto, "200 OK",
- "Content-Length: 18446744073709551615\r\n");
+ return http_send_response(conn, proto, "200 OK", NULL, -1);
}
static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
@@ -974,7 +989,7 @@ static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
static const char *table[] = { "\n\r\n", "\r\r\n", NULL };
const char **pptr;
uint8_t *ptr, *end = NULL;
- int n, r;
+ int n;
if (conn->sstp_state != STATE_SERVER_CALL_DISCONNECTED)
return -1;
@@ -1000,11 +1015,8 @@ static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
} else
n = end - buf->head;
- r = http_recv_request(conn, buf->head, n);
- if (r < 0)
+ if (http_recv_request(conn, buf->head, n) < 0)
return -1;
- else if (r > 0)
- return 1;
buf_pull(buf, n);
conn->sstp_state = STATE_SERVER_CONNECT_REQUEST_PENDING;
@@ -1046,24 +1058,12 @@ static int ppp_allocate_pty(int *master, int *slave, int flags)
goto error;
}
-#if PPP_SYNC
- value = N_SYNC_PPP;
-#else
value = N_PPP;
-#endif
if (ioctl(sfd, TIOCSETD, &value) < 0) {
log_ppp_error("sstp: ppp: set pty line discipline: %s\n", strerror(errno));
goto error;
}
-#if PPP_SYNC
- value = N_HDLC;
- if (ioctl(mfd, TIOCSETD, &value) < 0) {
- log_ppp_error("sstp: ppp: set pty line discipline: %s\n", strerror(errno));
- goto error;
- }
-#endif
-
if ((value = fcntl(mfd, F_GETFL)) < 0 || fcntl(mfd, F_SETFL, value | flags) < 0 ||
(value = fcntl(sfd, F_GETFL)) < 0 || fcntl(sfd, F_SETFL, value | flags) < 0) {
log_ppp_error("sstp: ppp: set pty status flags: %s\n", strerror(errno));
@@ -1080,6 +1080,59 @@ error:
return -1;
}
+/*
+ * ppposeq channel: the socket is both the ppp endpoint we exchange frames
+ * on and the fd establish_ppp() gets the channel from, as pppox_ioctl
+ * answers PPPIOCGCHAN on it. One datagram is one frame, so no framing.
+ */
+static int ppp_allocate_seq(int *master, int *slave, int flags)
+{
+ struct sockaddr_ppposeq sa = {
+ .sa_family = AF_PPPOX,
+ .sa_protocol = PX_PROTO_OSEQ,
+ };
+ int value, mfd, sfd;
+
+ mfd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ if (mfd < 0) {
+ log_ppp_error("sstp: ppp: create socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (connect(mfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+ log_ppp_error("sstp: ppp: connect socket: %s\n", strerror(errno));
+ goto error_mfd;
+ }
+
+ sfd = dup(mfd);
+ if (sfd < 0) {
+ log_ppp_error("sstp: ppp: dup socket: %s\n", strerror(errno));
+ goto error_mfd;
+ }
+
+ if (flags & O_CLOEXEC) {
+ fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC);
+ fcntl(sfd, F_SETFD, fcntl(sfd, F_GETFD) | FD_CLOEXEC);
+ flags &= ~O_CLOEXEC;
+ }
+
+ /* status flags are inherited by shared file descriptors */
+ if ((value = fcntl(mfd, F_GETFL)) < 0 || fcntl(mfd, F_SETFL, value | flags) < 0) {
+ log_ppp_error("sstp: ppp: set socket status flags: %s\n", strerror(errno));
+ goto error;
+ }
+
+ *master = mfd;
+ *slave = sfd;
+ return 0;
+
+error:
+ close(sfd);
+error_mfd:
+ close(mfd);
+ return -1;
+}
+
static void ppp_started(struct ap_session *ses)
{
struct ppp_t *ppp = container_of(ses, typeof(*ppp), ses);
@@ -1113,18 +1166,16 @@ static void ppp_finished(struct ap_session *ses)
}
}
-static int ppp_read(struct triton_md_handler_t *h)
+static int ppp_read_pty(struct triton_md_handler_t *h)
{
struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
struct buffer_t *buf;
struct sstp_hdr *hdr;
uint8_t pppbuf[PPP_BUF_SIZE], *src;
int i, n;
-#if !PPP_SYNC
uint8_t byte;
buf = conn->ppp_in;
-#endif
while (1) {
n = read(h->fd, pppbuf, sizeof(pppbuf));
if (n < 0) {
@@ -1149,29 +1200,7 @@ static int ppp_read(struct triton_md_handler_t *h)
}
src = pppbuf;
-#if PPP_SYNC
- while (n > 0) {
- if (src[0] == PPP_ALLSTATIONS)
- i = conn->ppp.mtu + 4 - (src[2] & 1);
- else
- i = conn->ppp.mtu + 2 - (src[0] & 1);
- if (i > n)
- i = n;
- buf = alloc_buf(i + sizeof(*hdr));
- if (!buf) {
- log_ppp_error("sstp: ppp: no memory\n");
- goto drop;
- }
- hdr = buf_put(buf, sizeof(*hdr));
- buf_put_data(buf, src, i);
- INIT_SSTP_DATA_HDR(hdr, buf->len);
- sstp_queue(conn, buf);
-
- n -= i;
- src += i;
- }
-#else
if (!buf) {
alloc:
conn->ppp_in = buf = alloc_buf(SSTP_MAX_PACKET_SIZE + PPP_FCSLEN);
@@ -1207,6 +1236,11 @@ static int ppp_read(struct triton_md_handler_t *h)
switch (byte) {
case PPP_FLAG:
if (buf->len <= PPP_FCSLEN || conn->ppp_flags) {
+ /* skip idle flag */
+ if (buf->len == 0 && conn->ppp_flags == 0)
+ break;
+ if (conf_verbose)
+ log_ppp_info2("sstp: ppp: read: malformed packet\n");
buf_set_length(buf, 0);
conn->ppp_flags = 0;
break;
@@ -1221,7 +1255,6 @@ static int ppp_read(struct triton_md_handler_t *h)
break;
}
}
-#endif
}
if (!list_empty(&conn->out_queue))
triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE);
@@ -1232,7 +1265,65 @@ drop:
return 1;
}
-static int ppp_write(struct triton_md_handler_t *h)
+static int ppp_read_seq(struct triton_md_handler_t *h)
+{
+ struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
+ struct buffer_t *buf;
+ struct sstp_hdr *hdr;
+ int n;
+
+ buf = conn->ppp_in;
+ while (1) {
+ if (!buf) {
+ alloc:
+ conn->ppp_in = buf = alloc_buf(conn->ppp.mtu ?
+ conn->ppp.mtu + PPP_HDRLEN + sizeof(*hdr) :
+ SSTP_MAX_PACKET_SIZE);
+ if (!buf) {
+ log_ppp_error("sstp: ppp: no memory\n");
+ goto drop;
+ }
+ buf_reserve(buf, sizeof(*hdr));
+ }
+
+ n = recv(h->fd, buf->tail, buf_tailroom(buf), MSG_TRUNC);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ break;
+ log_ppp_error("sstp: ppp: recv: %s\n", strerror(errno));
+ goto drop;
+ } else if (n > buf_tailroom(buf)) {
+ if (conf_verbose)
+ log_ppp_info2("sstp: ppp: recv: too long packet\n");
+ continue;
+ }
+
+ switch (conn->sstp_state) {
+ case STATE_SERVER_CALL_CONNECTED_PENDING:
+ case STATE_SERVER_CALL_CONNECTED:
+ break;
+ default:
+ continue;
+ }
+
+ buf_put(buf, n);
+ hdr = buf_push(buf, sizeof(*hdr));
+ INIT_SSTP_DATA_HDR(hdr, buf->len);
+ sstp_queue(conn, buf);
+ goto alloc;
+ }
+ if (!list_empty(&conn->out_queue))
+ triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE);
+ return 0;
+
+drop:
+ sstp_disconnect(conn);
+ return 1;
+}
+
+static int ppp_write_pty(struct triton_md_handler_t *h)
{
struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
struct iovec iov[PPP_BUF_IOVEC];
@@ -1257,8 +1348,9 @@ static int ppp_write(struct triton_md_handler_t *h)
goto again;
if (errno == EAGAIN)
goto defer;
- if (conf_verbose && errno != EPIPE)
- log_ppp_info2("sstp: ppp: write: %s\n", strerror(errno));
+ if (errno == EPIPE)
+ goto drop;
+ log_ppp_error("sstp: ppp: write: %s\n", strerror(errno));
goto drop;
} else if (n == 0)
goto defer;
@@ -1285,6 +1377,43 @@ drop:
return 1;
}
+static int ppp_write_seq(struct triton_md_handler_t *h)
+{
+ struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd);
+ struct buffer_t *buf;
+ ssize_t n;
+
+ while (!list_empty(&conn->ppp_queue)) {
+ buf = list_first_entry(&conn->ppp_queue, typeof(*buf), entry);
+ again:
+ n = send(conn->ppp_hnd.fd, buf->head, buf->len, 0);
+ if (n < 0) {
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ goto defer;
+ log_ppp_error("sstp: ppp: send: %s\n", strerror(errno));
+ goto drop;
+ } else if (n < buf->len) {
+ log_ppp_error("sstp: ppp: send: too short packet\n");
+ goto drop;
+ }
+
+ list_del(&buf->entry);
+ free_buf(buf);
+ }
+ triton_md_disable_handler(h, MD_MODE_WRITE);
+ return 0;
+
+defer:
+ triton_md_enable_handler(h, MD_MODE_WRITE);
+ return 0;
+
+drop:
+ triton_context_call(&conn->ctx, (triton_event_func)sstp_disconnect, conn);
+ return 1;
+}
+
static inline void ppp_queue(struct sstp_conn_t *conn, struct buffer_t *buf)
{
list_add_tail(&buf->entry, &conn->ppp_queue);
@@ -1519,13 +1648,25 @@ static int sstp_recv_msg_call_connect_request(struct sstp_conn_t *conn, struct s
return sstp_send_msg_call_connect_nak(conn);
}
- if (ppp_allocate_pty(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0)
+ switch (conn->ppp_mode) {
+ case PPP_MODE_ASYNC:
+ if (ppp_allocate_pty(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0)
+ return -1;
+ conn->ppp_hnd.read = ppp_read_pty;
+ conn->ppp_hnd.write = ppp_write_pty;
+ break;
+ case PPP_MODE_SEQPACKET:
+ if (ppp_allocate_seq(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0)
+ return -1;
+ conn->ppp_hnd.read = ppp_read_seq;
+ conn->ppp_hnd.write = ppp_write_seq;
+ break;
+ default:
+ log_ppp_error("sstp: invalid ppp-mode\n");
return -1;
+ }
conn->ppp_hnd.fd = master;
- conn->ppp_hnd.read = ppp_read;
- conn->ppp_hnd.write = ppp_write;
-
triton_md_register_handler(&conn->ctx, &conn->ppp_hnd);
triton_md_enable_handler(&conn->ppp_hnd, MD_MODE_READ);
@@ -1819,11 +1960,9 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr)
{
struct buffer_t *buf;
int size;
-#if !PPP_SYNC
uint8_t *src, *dst, byte;
uint16_t fcs;
int n;
-#endif
switch (conn->sstp_state) {
case STATE_SERVER_CALL_CONNECTED_PENDING:
@@ -1837,16 +1976,21 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr)
if (size == 0)
return 0;
-#if PPP_SYNC
- buf = alloc_buf(size);
- if (!buf) {
- log_error("sstp: no memory\n");
- return -1;
+ if (conn->ppp_mode == PPP_MODE_SEQPACKET) {
+ /* one datagram is one frame, no framing needed */
+ buf = alloc_buf(size);
+ if (!buf) {
+ log_error("sstp: no memory\n");
+ return -1;
+ }
+
+ buf_put_data(buf, hdr->data, size);
+
+ return ppp_send(conn, buf);
}
- buf_put_data(buf, hdr->data, size);
-#else
- buf = alloc_buf(size*2 + 2 + PPP_FCSLEN*2);
+ /* payload and FCS octets may both double when escaped, plus 2 flags */
+ buf = alloc_buf((size + PPP_FCSLEN) * 2 + 2);
if (!buf) {
log_error("sstp: no memory\n");
return -1;
@@ -1875,7 +2019,6 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr)
*dst++ = PPP_FLAG;
buf_put(buf, dst - buf->tail);
-#endif
return ppp_send(conn, buf);
}
@@ -1939,8 +2082,8 @@ static int sstp_handler(struct sstp_conn_t *conn, struct buffer_t *buf)
}
n = ntohs(hdr->length);
- if (n > SSTP_MAX_PACKET_SIZE) {
- log_ppp_error("recv [SSTP too long packet]\n");
+ if (n < sizeof(*hdr) || n > SSTP_MAX_PACKET_SIZE) {
+ log_ppp_error("recv [SSTP invalid packet length %d]\n", n);
return -1;
} else if (n > buf->len)
break;
@@ -1978,8 +2121,6 @@ static int sstp_read(struct triton_md_handler_t *h)
n = conn->handler(conn, buf);
if (n < 0)
goto drop;
- else if (n > 0)
- return 1;
buf_expand_tail(buf, SSTP_MAX_PACKET_SIZE);
}
@@ -2094,8 +2235,9 @@ static int sstp_write(struct triton_md_handler_t *h)
continue;
if (errno == EAGAIN)
goto defer;
- if (conf_verbose && errno != EPIPE)
- log_ppp_info2("sstp: write: %s\n", strerror(errno));
+ if (errno == EPIPE)
+ goto drop;
+ log_ppp_error("sstp: write: %s\n", strerror(errno));
goto drop;
} else if (n == 0)
goto defer;
@@ -2129,6 +2271,31 @@ static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf)
return 0;
}
+static void sstp_flush(struct sstp_conn_t *conn)
+{
+ struct buffer_t *buf;
+ int n;
+
+ while (!list_empty(&conn->out_queue)) {
+ buf = list_first_entry(&conn->out_queue, typeof(*buf), entry);
+ while (buf->len) {
+ n = conn->stream->write(conn->stream, buf->head, buf->len);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EPIPE)
+ break;
+ log_ppp_error("sstp: write: %s\n", strerror(errno));
+ break;
+ } else if (n == 0)
+ break;
+ buf_pull(buf, n);
+ }
+ list_del(&buf->entry);
+ free_buf(buf);
+ }
+}
+
static void sstp_msg_echo(struct triton_timer_t *t)
{
struct sstp_conn_t *conn = container_of(t, typeof(*conn), hello_timer);
@@ -2230,6 +2397,7 @@ static void sstp_disconnect(struct sstp_conn_t *conn)
triton_timer_del(&conn->hello_timer);
if (conn->hnd.tpd) {
+ sstp_flush(conn);
triton_md_unregister_handler(&conn->hnd, 0);
conn->stream->close(conn->stream);
}
@@ -2399,6 +2567,7 @@ static int sstp_connect(struct triton_md_handler_t *h)
conn->sstp_state = STATE_SERVER_CALL_DISCONNECTED;
conn->ppp_state = STATE_INIT;
+ conn->ppp_mode = conf_ppp_mode;
conn->handler = conf_proxyproto ? proxy_handler : http_handler;
//conn->bypass_auth = conf_bypass_auth;
@@ -2845,11 +3014,14 @@ static void load_config(void)
conf_proxyproto = opt && strhas(opt, "proxy", ',');
ssl_load_config(&serv, conf_hostname);
- opt = serv.ssl_ctx ? "enabled" : "disabled";
if (conf_verbose) {
- log_info2("sstp: SSL/TLS support %s, PROXY support %s\n",
- opt, conf_proxyproto ? "enabled" : "disabled");
+ log_info2("sstp: SSL/TLS %s, PROXY %s, PPP mode %s\n",
+ serv.ssl_ctx ? "enabled" : "disabled",
+ conf_proxyproto ? "enabled" : "disabled",
+ conf_ppp_mode == PPP_MODE_AUTO ? "AUTO" :
+ conf_ppp_mode == PPP_MODE_ASYNC ? "ASYNC" :
+ conf_ppp_mode == PPP_MODE_SEQPACKET ? "SEQPACKET" : "unknown");
}
opt = conf_get_opt("sstp", "cert-hash-sha1");
@@ -2923,7 +3095,7 @@ static void sstp_init(void)
struct sockaddr_t *addr = &serv.addr;
struct linger linger;
struct stat st;
- int port, value;
+ int port, value, fd;
char *opt;
opt = conf_get_opt("sstp", "port");
@@ -3001,6 +3173,32 @@ static void sstp_init(void)
goto error_unlink;
}
+ opt = conf_get_opt("sstp", "ppp-mode");
+ if (opt) {
+ if (!strcmp(opt, "auto"))
+ conf_ppp_mode = PPP_MODE_AUTO;
+ else if (!strcmp(opt, "seqpacket"))
+ conf_ppp_mode = PPP_MODE_SEQPACKET;
+ else if (!strcmp(opt, "async"))
+ conf_ppp_mode = PPP_MODE_ASYNC;
+ }
+ if (conf_ppp_mode != PPP_MODE_ASYNC) {
+ fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ if (fd >= 0)
+ close(fd);
+ else if (access("/sys/module/ppposeq", F_OK) && system("modprobe -q ppposeq"))
+ log_warn("failed to load ppposeq kernel module\n");
+ }
+ if (conf_ppp_mode == PPP_MODE_AUTO) {
+ fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ);
+ if (fd >= 0) {
+ conf_ppp_mode = PPP_MODE_SEQPACKET;
+ close(fd);
+ } else {
+ conf_ppp_mode = PPP_MODE_ASYNC;
+ }
+ }
+
conn_pool = mempool_create(sizeof(struct sstp_conn_t));
load_config();
diff --git a/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