diff options
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 () @@ -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 |
