Compare commits

...
84 Commits
Author SHA1 Message Date
Yves Rutschle 17a0425f4f incoming TLS connections get routed to GetEchClientHelloInner 2026-08-20 07:56:34 +00:00
Yves Rutschle 34645a62b2 ECH configs are build with sslh public name, not the target SNI... 2026-07-18 18:01:11 +00:00
Yves Rutschle 6d50082993 ECH configuration generation for each SNI, with DNS configuration printed in logs 2026-07-18 16:13:06 +00:00
Yves Rutschle 6219527ce4 remove warning if MAX is defined somewhere else 2026-06-20 16:31:59 +00:00
Yves Rutschle 0dd580f45b move symbol definition for Cygwin (fix #506) 2025-09-24 14:48:16 +02:00
Yves Rutschle 63e15bb55c Prepare for 2.3.0 2025-09-10 15:50:52 +02:00
Yves Rutschle 6f3dcfb365 Merge branch 'proxyprotocol' 2025-09-10 15:31:43 +02:00
Yves Rutschle f8e031ecb9 Merge branch 'master' of github.com:yrutschle/sslh 2025-09-10 15:29:59 +02:00
Yves Rutschle fcfc348aee added proxyprotocol documentation 2025-09-10 15:29:04 +02:00
Yves Rutschle 6905498d60 proxyprotocol on client-side can be removed on server side, and its info is then logged 2025-09-10 12:14:41 +02:00
Yves Rűtschlé eea50a97fc support for proxy_protocol on incoming (client-side) link 2025-09-10 09:28:54 +02:00
Yves Rutschle 387ef13f52 fix memory leak when server-side is not yet attached, and potential memory leak when forking hash is full (fix #504) 2025-09-04 23:50:17 +02:00
Yves Rűtschlé dcb2cc928a import main branch for endpoint tracking 2025-08-29 20:44:15 +02:00
Yves Rűtschlé 2106bd6527 added test for client-side proxyprotocol 2025-08-29 20:39:42 +02:00
Yves Rűtschlé 12f748fe8e include test for server-side proxyprotocol 2025-08-29 12:22:30 +02:00
yrutschle b6c8e116af verify there is space in pid followup table before forking (fix #502) 2025-08-27 22:57:04 +02:00
yrutschle 4427a9570f remove noisy logging from harmless interrupted system calls 2025-08-27 22:21:51 +02:00
yrutschle 3c237b3c9a have some minimum hash size if no protocols have a max_connections setting 2025-08-26 23:22:17 +02:00
yrutschle 2914fe7b5c fix embarassingly wrong error check 2025-08-26 22:23:30 +02:00
yrutschle 829a7299e1 remove obsolete debug statement 2025-08-26 22:18:21 +02:00
yrutschle f2e86a3df7 do not log if accept returns EAGAIN 2025-08-26 22:17:58 +02:00
yrutschle f64f0c5ba5 some markdown formating fixes 2025-08-26 22:15:49 +02:00
Oleg Stepura 6d1e95cc52 Update podman.md 2025-08-26 22:00:56 +02:00
Oleg Stepura b9de23534e Update podman.md 2025-08-26 22:00:56 +02:00
Oleg Stepura 55872427f2 Update podman.md 2025-08-26 22:00:56 +02:00
Yves Rűtschlé 9e2e02d58e print settings include proxyprotocol setting 2025-08-21 12:39:01 +02:00
yrutschle 00e8a3b084 some markdown formating fixes 2025-08-20 09:55:43 +02:00
Yves Rűtschlé 988f7135ef add link to libev documentation 2025-08-17 10:47:19 +02:00
Yves Rűtschlé 0baf23df95 fix probing on partial reception 2025-08-17 10:31:18 +02:00
Yves Rűtschlé 81e2d0e2f4 check udp timeouts on all new connections including TCP 2025-08-15 19:54:51 +02:00
Yves Rűtschlé b57b333a39 update documentation for accept() disabling 2025-08-15 18:49:56 +02:00
Yves Rűtschlé effa0f2615 sslh-select now has backup strategy when ulimit -n (file descriptor) is reached 2025-08-15 18:32:14 +02:00
Yves Rűtschlé 8769d86c04 EWOULDBLOCK should not cause accept() disable 2025-08-15 18:29:05 +02:00
Yves Rűtschlé 663e41e98c remove debug printf 2025-08-15 10:30:27 +02:00
Yves Rűtschlé d97cda1fa5 sslh-ev now has backup strategy when ulimit -f (file descriptor) is reached 2025-08-15 10:11:20 +02:00
Yves Rűtschlé c512fd5279 update doc for connections 2025-08-13 12:44:45 +02:00
Yves Rűtschlé d4de0a8859 update doc for connections 2025-08-13 12:33:45 +02:00
Yves Rűtschlé 4f7ba84e9a fix headers 2025-08-13 12:32:55 +02:00
Yves Rűtschlé e1619707f6 check allocation success 2025-08-13 12:24:15 +02:00
Yves Rűtschlé ad34bc5c5a check allocation success 2025-08-12 23:14:42 +02:00
Yves Rűtschlé 2975ae88f2 cleanup debug code 2025-08-12 23:06:47 +02:00
Yves Rűtschlé 2624877232 sslh-ev: keep track of forked processes 2025-08-12 23:05:24 +02:00
Yves Rűtschlé eb6a3c6724 zero out listen_endpoint data when allocating to ensure counters are 0 at start 2025-08-12 22:56:38 +02:00
Yves Rűtschlé 0a0392e7ee refactor: separate pid processing from child signal processing 2025-08-12 10:17:03 +02:00
Yves Rűtschlé 3817f2d728 count listen endpoint connections for forked protocols 2025-08-11 12:13:09 +02:00
Yves Rűtschlé 860631f910 refactor: move out forking code 2025-08-11 09:37:57 +02:00
Yves Rűtschlé 51ba24c1b2 count protocol connections when forking, in sslh-select 2025-08-11 09:28:43 +02:00
Yves Rűtschlé 22987e7d94 add max_connections to protocol print settings 2025-08-11 09:04:29 +02:00
Yves Rűtschlé 02ea8e7ea4 add host and port to print settings when resolving on forward 2025-08-11 09:02:13 +02:00
Yves Rűtschlé 691aa936cc add listen max_connections to print settings 2025-08-11 08:57:10 +02:00
Yves Rűtschlé 78a1ed6899 refactor: make hash dump usuable for debugging 2025-08-10 23:12:24 +02:00
Yves Rűtschlé eac34ec36f refactor: consolidate loop init between sslh-select and sslh-ev 2025-08-10 08:49:02 +02:00
yrutschle 8aa97d7118 Include OpenSUSE's security review 2025-08-06 09:55:19 +02:00
yrutschle 8ad8bfdb5d fix link 2025-08-06 09:44:15 +02:00
yrutschle 4c2e4e01f2 document new settings in ChangeLog 2025-08-06 09:42:37 +02:00
yrutschle 2ecb681277 fix warning on void return 2025-08-06 09:38:52 +02:00
yrutschle 54dc8374ab properly define function in header 2025-08-06 09:37:07 +02:00
yrutschle 8c4253f145 refactor: rename proto inc and dec functions for clarity 2025-08-06 09:36:39 +02:00
yrutschle 4483e9c7e5 support for max_connections on listen_endpoint for sslh-ev/sslh-select 2025-08-06 09:32:19 +02:00
yrutschle 65c9c4ce95 count protocol connections even if no maximum is set 2025-08-06 08:00:35 +02:00
yrutschle 5434dc59df fix tags target for Debian's version of ctags 2025-08-05 18:21:48 +02:00
yrutschle 20290bbfa6 sslh-fork can now limit the number of concurrent connections for each incoming port 2025-08-05 18:15:58 +02:00
Yves Rutschle 1dfd70c20c Merge branch 'master' of github.com:yrutschle/sslh 2025-07-24 19:49:01 +02:00
Yves Rutschle feaf528a60 add setting for maximum connection per protocol 2025-07-24 19:48:50 +02:00
Sergei Zhmylev 11da63cf4e Fix mistype in accept(2) error handling
Due to a mistype in errno checking, sslh-fork process died repeatedly
resulting to a complete denial of service on systems with no built-in
process supervisors, like FreeBSD.

Signed-off-by: Sergei Zhmylev <zhmylove@narod.ru>
2025-06-30 21:57:12 +02:00
Yves Rutschle 28ea73301e new gap feature: hardlimit, with test suite 2025-06-22 16:45:50 +02:00
Adrian Bunk cd9b85fd4f Add a missing change in the second part of the CVE-2025-46806 fix 2025-06-19 22:34:30 +02:00
Yves Rutschle bd9ed060e4 fix memory leak in regex probe (fix #500) 2025-06-19 22:31:02 +02:00
Yves Rutschle 071e7500a2 new test framework, with test classes for robustness and probe tests. Not all tests are ported yet. 2025-06-15 16:02:02 +02:00
Yves Rutschle b7556c07bd added podman transparent setup (fix #448) 2025-05-29 16:07:57 +02:00
Yves Rutschle 6b9ec3a46e remove obsolete getopt definitions (fix #489) 2025-05-29 15:46:08 +02:00
Yves Rutschle ad66e79f46 Fix proxyprotocol target field (#496) 2025-05-29 15:23:10 +02:00
Yves Rutschle b12220e640 v2.2.4 ChangeLog 2025-05-28 17:51:04 +02:00
Yves Rutschle 204305a88f fix unaligned read in OpenVPN UDP probe 2025-05-25 11:27:26 +02:00
Yves Rutschle 0f96ed8adb fix test script for new command line behaviour 2025-05-25 11:27:05 +02:00
Yves Rutschle 43e75a0a8c upgrade API to Conf::Libconfig 1.0.3 2025-05-22 22:19:00 +02:00
Yves Rutschle ad1f5d68e9 fix potential parsing of undefined data is syslog probe and make probe more robust 2025-05-09 17:08:30 +02:00
Yves Rutschle ff8206f7c8 fix segmentation fault upon too many UDP connection. Also, accepts multiple connection in one cycle 2025-05-08 17:58:51 +02:00
Yves Rutschle 8298daf686 change magic numbers for constants 2025-05-06 22:12:29 +02:00
Yves Rutschle 168477ea34 Merge branch 'master' of github.com:yrutschle/sslh 2025-05-04 11:49:45 +02:00
Yves Rutschle b5d4d4662b v2.2.3 ChangeLog 2025-05-04 11:47:44 +02:00
Yves Rutschle 6e6d94752a Revert "include version.h in repo"
This reverts commit acdbb79d43.
2025-05-04 11:45:33 +02:00
Yves Rutschle b2bcfc26b2 version.h 2025-05-04 11:33:58 +02:00
Yves Rutschle 9522799421 preliminary receive proxyprotocol support 2025-04-15 22:16:03 +02:00
84 changed files with 6065 additions and 591 deletions

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ sslh-ev
systemd-sslh-generator
sslh.8.gz
tags
version.h
/config.status
/config.log
/config.h
+70
View File
@@ -1,3 +1,73 @@
ech branch:
This is an experimental version and does not work.
Do not use it unless you are experimenting with ECH
and an experimental wolfSSL.
A lot of context is discussed here:
https://github.com/wolfSSL/wolfssl/issues/10485
What it does:
- It creates a public ECH configuration for the
first public name (configure in the `listen`
section of the configuration file). This gets
printed in a file configured in `ech_conf_sink`
(see ech_test.cfg example).
- Upon incoming connections, it will call an
imaginary wolfSSL_GetEchClientHelloInner().
The resulting packet is hexdumped.
- It will also use wolfSSL to decode the SNI and
print it (but does not yet use it to forward).
Most things happen in tls_ech.c, in particular there
is a stub of GetEchClientHelloInner() that needs to
be removed when the function becomes available in
wolfSSL.
TODO:
Change configure script to check for appropriate
wolfSSL version.
Create ECH configurations for each public name
Link decoded InnerHello to SNI extraction code
....
Error cases still to be worked out (see
conversations on github)
Profit!
v2.3.0:
Added `max_connections` setting to `listen` and
`protocol` configuration; see the
[guide](doc/max_connections.md) for more
information.
accept() is now disabled for some time when ulimit -n is reached.
Support proxyprotocol on incoming connections. See
the [guide](doc/proxyprotocol.md) for information.
Fix proxyprotocol target field (Thanks to Github user 404-Not-Found)
Fix memory leak in regex probe.
v2.2.4:
Fix CVE-2025-46806 for "Misaligned Memory Accesses in `is_openvpn_protocol()`"
Fix CVE-2025-46807 for "File Descriptor Exhaustion in sslh-select and sslh-ev"
Fix potential parsing of undefined data in syslog
probe (no CVE assigned)
Thanks to Matthias Gerstner of the SUSE security
team for the security review that found these
defects!
v2.2.3:
Reverse older commit: version.h cannot be included
without breaking the build (everything recompiles
every time) and the release archive creation (which
relies on git tags).
v2.2.2:
Fix potential vulnerability similar to CVE-2020-28935
+5 -7
View File
@@ -22,7 +22,7 @@ MAN=sslh.8.gz # man page name
# itself
ifneq ($(strip $(ENABLE_SANITIZER)),)
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
endif
ifneq ($(strip $(COV_TEST)),)
@@ -35,7 +35,7 @@ CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
LIBS=-lm -lpcre2-8 @LIBS@
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o tls_ech.o argtable3.o collection.o gap.o tcp-probe.o landlock.o proxyprotocol.o
OBJS_A=libsslh.a
FORK_OBJS=sslh-fork.o $(OBJS_A)
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
@@ -70,9 +70,8 @@ all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
$(OBJS_A): $(OBJS)
$(AR) rcs $(OBJS_A) $(OBJS)
version.h: .FORCE
version.h:
./genver.sh >version.h
.FORCE:
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
@@ -145,10 +144,9 @@ distclean: clean
clean:
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
echo "// this is a placeholder for version.h, to make code-checking editors happy" > version.h
tags:
ctags --globals -T *.[ch]
tags: *.c *.h
ctags *.[ch]
cscope:
-find . -name "*.[chS]" >cscope.files
+17 -1
View File
@@ -30,6 +30,20 @@ Install
Please refer to the [install guide](doc/INSTALL.md).
Security
========
Matthias Gerstner from OpenSUSE has performed a code review
of `sslh` from a security point of view, which revealed a
number of problems, including two CVE. His findings have
already been taken partly into account for the more critical
ones. The [full
review](https://security.opensuse.org/2025/06/13/sslh-denial-of-service-vulnerabilities.html)
is well worth reading if you are using `sslh` in production.
Part of the securing your installation involves configuring
connection limits. This is described in [this
guide](doc/max_connections.md).
Configuration
=============
@@ -46,7 +60,8 @@ The same result can be achieved more easily by using
`proxyprotocol` if the backend server supports it. This is a
simple setting to add to the `sslh` protocol configuration,
usually with an equivalently simple setting to add in
the backend server configuration, so try that first.
the backend server configuration, so try that first. This is
explained in a [separate document](doc/proxyprotocol.md).
This means services behind `sslh` (Apache, `sshd` and so on)
will see the external IP and ports as if the external world
@@ -59,6 +74,7 @@ interfaces. The principle and basic setup is described
[here](doc/simple_transparent_proxy.md), with further
scenarios described [there](doc/scenarios-for-simple-transparent-proxy.md).
There is also a guide to use [podman](doc/podman.md).
Another method uses iptable packet marking features, and is
highly dependent on your network environment and
+83 -19
View File
@@ -25,12 +25,6 @@
#include <sys/prctl.h>
#endif
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
/* Make use of systemd socket activation
* */
#ifdef SYSTEMD
@@ -87,7 +81,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
}
if (sd > 0) {
int i;
*sockfd = malloc(sd * sizeof(*sockfd[0]));
*sockfd = calloc(sd, sizeof(*sockfd[0]));
CHECK_ALLOC(*sockfd, "malloc");
for (i = 0; i < sd; i++) {
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
@@ -162,6 +156,17 @@ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
return sockfd;
}
void print_inet_listen_info(int fd, const char* addr,
struct sslhcfg_listen_item* cfg)
{
print_message(msg_config, "%d:\t%s\t[%s] [%s] [max_cnx: %d]\n",
fd,
addr,
cfg->keepalive ? "keepalive" : "",
cfg->is_udp ? "udp" : "",
cfg->max_connections);
}
/* Start listening internet sockets for configuration entry 'index'
* OUT: *sockfd[]: pointer to array of listen_endpoint object; we append new
@@ -182,12 +187,14 @@ static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, str
for (addr = start_addr; addr; addr = addr->ai_next) {
num_addr++;
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, cfg->keepalive, cfg->is_udp);
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
(*sockfd)[num_addr-1].family = AF_INET;
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
cfg->keepalive ? "keepalive" : "",
cfg->is_udp ? "udp" : "");
(*sockfd)[num_addr-1].endpoint_cfg = cfg;
print_inet_listen_info((*sockfd)[num_addr-1].socketfd,
sprintaddr(buf, sizeof(buf), addr),
cfg);
}
freeaddrinfo(start_addr);
return num_addr;
@@ -216,9 +223,11 @@ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, str
num_addr++;
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
memset(&(*sockfd)[num_addr-1], 0, sizeof((*sockfd)[num_addr-1]));
(*sockfd)[num_addr-1].socketfd = fd;
(*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM;
(*sockfd)[num_addr-1].family = AF_INET;
(*sockfd)[num_addr-1].endpoint_cfg = cfg;
return num_addr;
}
@@ -499,10 +508,21 @@ void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking
cnx->q[1].fd = fd;
if (cnx->proto->proxyprotocol_is_present) {
pp_write_header(cnx->proto->proxyprotocol, cnx);
/* If pp_write_header() fails, it already logs a message and there is
* nothing much we can do. The server side will probably close the
* connection */
if (cnx->proto->proxyprotocol) {
pp_write_header(cnx->proto->proxyprotocol, cnx);
/* If pp_write_header() fails, it already logs a message and there is
* nothing much we can do. The server side will probably close the
* connection */
} else {
/* proxyprotocol == 0 means we remove a proxyprotocol header from
* the client-side, and log PP information (as it will be lost
* after sslh) */
int pp_len = pp_log_connection(cnx->q[1].begin_deferred_data,
cnx->q[1].deferred_data_size);
if (pp_len > 0) {
defer_skip(&cnx->q[1], pp_len);
}
}
}
}
@@ -525,6 +545,13 @@ int defer_write(struct queue *q, void* data, ssize_t data_size)
return 0;
}
/* skip (remove) some data from the head of the queue */
void defer_skip(struct queue *q, ssize_t data_size)
{
q->deferred_data += data_size;
q->deferred_data_size -= data_size;
}
/* Store some data to write *before* what's already in the queue */
int defer_write_before(struct queue *q, void* data, ssize_t data_size)
{
@@ -595,6 +622,37 @@ void dump_connection(struct connection *cnx)
}
/* *cnx must have its proto field probed already.
* If required, increment the connection count for this protocol.
* Returns 1 if connection count is exceeded, 0 otherwise */
int inc_proto_connections(struct sslhcfg_protocols_item* proto)
{
proto->num_connections++;
if (proto->max_connections_is_present) {
print_message(msg_connections, "Proto %s +1: %d/%d cnx\n",
proto->name,
proto->num_connections,
proto->max_connections);
if (proto->num_connections > proto->max_connections) {
print_message(msg_connections_error, "%s: too many connections, dropping\n", proto->name);
return 1;
}
}
return 0;
}
void dec_proto_connections(struct sslhcfg_protocols_item* proto)
{
if (proto) {
proto->num_connections--;
print_message(msg_connections, "Proto %s -1: %d/%d cnx\n",
proto->name,
proto->num_connections,
proto->max_connections);
}
}
/*
* moves data from one fd to other
*
@@ -849,16 +907,23 @@ int check_access_rights(int in_socket, const char* service)
return 0;
}
volatile sig_atomic_t received_sigchld;
void sig_sigchld(int sig)
{
received_sigchld = 1;
}
void setup_signals(void)
{
int res;
struct sigaction action;
/* Request no SIGCHLD is sent upon termination of
* the children */
/* Set SIGCHLD to sig_sigchld() */
memset(&action, 0, sizeof(action));
action.sa_handler = NULL;
action.sa_flags = SA_NOCLDWAIT;
action.sa_handler = sig_sigchld;
action.sa_flags = SA_NOCLDSTOP;
res = sigaction(SIGCHLD, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
@@ -873,7 +938,6 @@ void setup_signals(void)
action.sa_handler = SIG_IGN;
res = sigaction(SIGPIPE, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
}
+17
View File
@@ -9,6 +9,13 @@
#define FD_SETSIZE 4096
#endif
/* Cygwin does not define some symbols
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
#define _GNU_SOURCE
#include <sys/types.h>
#include <fcntl.h>
@@ -41,6 +48,7 @@
#include "config.h"
#include "version.h"
#undef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
@@ -109,6 +117,7 @@ struct connection {
struct sslhcfg_protocols_item* proto; /* Where to connect to */
/* SOCK_STREAM */
struct listen_endpoint* endpoint; /* Client-facing listen fd */
enum connection_state state;
time_t probe_timeout;
@@ -138,6 +147,8 @@ struct listen_endpoint {
int socketfd; /* file descriptor of listening socket */
int type; /* SOCK_DGRAM | SOCK_STREAM */
int family; /* AF_INET | AF_UNIX */
int num_connections; /* How many active connections on this endpoint */
struct sslhcfg_listen_item* endpoint_cfg; /* the configuration item that corresponds to this endpoint */
};
#define FD_CNXCLOSED 0
@@ -174,11 +185,14 @@ void drop_privileges(const char* user_name, const char* chroot_path);
void set_capabilities(int cap_net_admin);
void write_pid_file(const char* pidfile);
void dump_connection(struct connection *cnx);
int inc_proto_connections(struct sslhcfg_protocols_item* cnx);
void dec_proto_connections(struct sslhcfg_protocols_item* cnx);
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
int start_listen_sockets(struct listen_endpoint *sockfd[]);
int defer_write(struct queue *q, void* data, ssize_t data_size);
void defer_skip(struct queue *q, ssize_t data_size);
int defer_write_before(struct queue *q, void* data, ssize_t data_size);
int flush_deferred(struct queue *q);
@@ -199,4 +213,7 @@ void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
void setup_landlock(void);
/* sslh-fork.c or processes.c, depending on the model */
void setup_sigchld(void);
#endif
+3
View File
@@ -18,4 +18,7 @@
/* libbsd, to change process name */
#undef HAVE_LIBBSD
/* wolfssl, to support Encrypted Client Hello (ECH) */
#undef HAVE_WOLFSSL
#endif
Vendored
+41
View File
@@ -3329,6 +3329,47 @@ then :
LIBS="$LIBS -lbsd"
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wolfSSL_Init in -lwolfssl" >&5
printf %s "checking for wolfSSL_Init in -lwolfssl... " >&6; }
if test ${ac_cv_lib_wolfssl_wolfSSL_Init+y}
then :
printf %s "(cached) " >&6
else $as_nop
ac_check_lib_save_LIBS=$LIBS
LIBS="-lwolfssl $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char wolfSSL_Init ();
int
main (void)
{
return wolfSSL_Init ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
ac_cv_lib_wolfssl_wolfSSL_Init=yes
else $as_nop
ac_cv_lib_wolfssl_wolfSSL_Init=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_wolfssl_wolfSSL_Init" >&5
printf "%s\n" "$ac_cv_lib_wolfssl_wolfSSL_Init" >&6; }
if test "x$ac_cv_lib_wolfssl_wolfSSL_Init" = xyes
then :
printf "%s\n" "#define HAVE_WOLFSSL 1" >>confdefs.h
LIBS="$LIBS -lwolfssl"
fi
ac_header= ac_cache=
for ac_item in $ac_header_c_list
+3
View File
@@ -11,6 +11,9 @@ AC_CHECK_LIB([wrap], [hosts_ctl], [AC_DEFINE(HAVE_LIBWRAP) LIBS="$LIBS -lwrap" ]
AC_CHECK_LIB([cap], [cap_get_proc], [AC_DEFINE(HAVE_LIBCAP) LIBS="$LIBS -lcap" ], [])
AC_CHECK_LIB([bsd], [setproctitle], [AC_DEFINE(HAVE_LIBBSD) LIBS="$LIBS -lbsd" ], [])
dnl TODO: check that wolfssl contains wolfSSL_GetEchClientHelloInner instead of just existing
AC_CHECK_LIB([wolfssl], [wolfSSL_Init], [AC_DEFINE(HAVE_WOLFSSL) LIBS="$LIBS -lwolfssl" ], [])
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
AC_CHECK_HEADERS(proxy_protocol.h, [AC_DEFINE(HAVE_PROXYPROTOCOL) LIBS="$LIBS -lproxyprotocol" ], [])
+54 -6
View File
@@ -1,7 +1,11 @@
# Daisy-Chaining-Transparency #
This documentation goes a level deeper, what happens in the operating system with IP-addresses, and why some combinations of programs are failing, when they use the same transparency method.
There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.<br>
Unfortunately you will see a lot of errors popping out: **Address already in use**<br>
There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.
Unfortunately you will see a lot of errors popping out: **Address already in use**
[This article from Cloudflare blog](https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections) explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem.
Let us look to the following example: We have sslh (S) accepting connections from a client (C) and forwarding one of those connections to a man-in-the-middle (M), which finally forwards this connection to sshd. If everything works perfectly, we would like to see those connections.
@@ -9,10 +13,15 @@ Let us look to the following example: We have sslh (S) accepting connections fro
![Dataflow-Of-Daisy-Chain](./detailed-ip-transparency.png)
But unfortunately we are receiving in many constellations errors, when M tries to open its connection to our final target sshd, here called T.
Let us look more close, why that is happening. We need for this two terminal windows on the same server.<br>
Let us look more close, why that is happening. We need for this two terminal windows on the same server.
### First example, uncooperative applications ###
As the problem has nothing to do with transparency itself, but only of reuse of same IP-addresses and ports, we avoid the overhead of the additional capablities, to keep the example easy and clear.
In the first terminal we are starting python3 and entering the following three lines:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
@@ -24,6 +33,7 @@ Type "help", "copyright", "credits" or "license" for more information.
```
Now we are going to the second terminal window, and trying just the same:
```
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
@@ -35,18 +45,26 @@ Traceback (most recent call last):
OSError: [Errno 98] Address already in use
sock.bind(("192.168.255.254", 12346)) ##this however works!!
```
Here we are getting the error, which caused many of us hours of research.
The problem is caused by the fact, that the kernel does not know at the moment of the bind()-call, how we want to use the socket. If we use this as a server socket, and will do a listen()-call as next, this will not work, as for server sockets, the two-value tuple ADDRESS:PORT needs to be unique only to the process, tied to this socket.
That is the reason, that there are port ranges, reserved for servers, where the administrator is responsible not to assign the same port to two applications.
But as server ports are coming from a range, which will not be used for client connections, a server can be sure, that if it is started at any time in the future, no outgoing client has used its port for an outbound connection.
Clients are usually using ports from a so called [ephemeral port range](https://en.wikipedia.org/wiki/Ephemeral_port).
However, for clients each connection is valid, as long as one value in the four-tuples describing the connection is different. In our example above, the two values from the destination are different, so this connection could be established (in theory) without conflicts.
To make that happen, you need to deploy a special socket option to this socket, to explain, that we "know the risks" and we will reuse the ip-port combination.
And, as we see from the second bind() in the second example, the error message: _**Address already in use**_ really means: _**Address:Port already in use**_
### Taking Care, part I ###
Ok, now we are entering our two terminals again, pressing Ctrl-D to finish python, and start a new session like this in both terminals:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
@@ -57,12 +75,15 @@ Type "help", "copyright", "credits" or "license" for more information.
>>> sock.bind(("192.168.255.254", 12345))
>>>
```
And we will see, that the problem is solved, when both applications are taking care.
### Taking Care, part II ###
Ok, now we are going back to our terminals, pressing again Ctrl-D to finish python, and start new sessions like this:
In the first terminal we repeat the input from our first example, without the setsockopt() call:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
@@ -72,6 +93,7 @@ Type "help", "copyright", "credits" or "license" for more information.
>>> sock.bind(("192.168.255.254", 12345))
>>>
```
in the second terminal, we enter our modified cooperative example:
```
@@ -87,8 +109,11 @@ Traceback (most recent call last):
OSError: [Errno 98] Address already in use
>>>
```
Oops, here is our error again. And this is the reason, why I called this method "cooperative". As the first application has bound to that IP:PORT combination, without telling, that "it knows the risk", the kernel denies us, to use this combinations, as we may break the already active application. The first application is not "cooperative" :-(<br>
Oops, here is our error again. And this is the reason, why I called this method "cooperative". As the first application has bound to that IP:PORT combination, without telling, that "it knows the risk", the kernel denies us, to use this combinations, as we may break the already active application. The first application is not "cooperative" :-(
Ok, but the kernel gives as some more possibilities: As we now get connections from a uncoperative application, we can no longer use the Client-IP-Port combination. We need to use a conflict free port for the client IP, to succeed. So lets get back to terminal 2 and continue after the error with the following commands:
```
user@host:~$ python3
Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux
@@ -105,9 +130,13 @@ OSError: [Errno 98] Address already in use
>>> sock.bind(("192.168.255.254", 0))
>>>
```
This last example makes it neccessary, that you use a recent version of the python3 socket library, as older versions, have the option _IP_BIND_ADDRESS_NO_PORT_ not yet defined.
With his behaviour, we are telling the kernel, that the kernel should assign us a conflict free port address at latest possible moment, while calling connect().
From `man ip`:
```
Inform the kernel to not reserve an ephemeral port when using bind(2)
with a port number of 0. The port will later be automatically chosen
@@ -116,7 +145,9 @@ long as the 4-tuple is unique.
```
Ok, now with those two actions an application is really ready for cooperative working in ip-transparent chains.
If you are running in problems, with any kind of application, where you are redirecting transparent traffic to from sslh, check the following:
- Are you using a recent version of sslh, having the described feature enabled
- are you using the most recent version of the other application
If you can confirm both checks, tell the maintainers of the other application, about possible fixes, and send them a link to this article.
@@ -124,6 +155,7 @@ If you can confirm both checks, tell the maintainers of the other application, a
## Practical Use Of Daisy-Chaining: Proxytunnel Endpoint ##
One reasons, why we want to combine two programs is related to the core functionality of sslh. You wish to hide your ssh connection behind a https port.
But now you would like, to reach this port via the [proxy-tunnel application](https://github.com/proxytunnel/proxytunnel), though an restrictive http(s) proxy. This is in many cases, one of the few methods, to escape from restricted private networks, like in companies, schools and universities. Unfortunately, many of those proxy-servers will check, that the protocol leaving the proxy is really tls. Therefore you need and endpoint in your system, which will terminate the tls-connection and forward the encapsulated ssh stream to the sshd. Sslh can't do tls termination, as this is not a core job of tls. One of the solutions tried here is stunnel. Stunnel can do transparency like sslh, but unfortunately belongs to the uncooperative ip-transparent programs. At the time of writing this article, you can use stunnel as the first-in-chain, and a very recent sslh as second in chain. But nginx (or openresty) is capable of this, however it prefers (at least in the tested versions), mostly the second way just selecting a new random port and not (always) preserving the original source port, what makes debugging of events much easier.
@@ -217,8 +249,11 @@ So it is not recommended for doing scp or sftp through such connections.
### Nginx Configration Explained ###
However, there are situations, where this is the only way, to reach your server.
So we have now this nginx instance helping us out.
The server stanza listening on _192.168.255.254:1443_ is the main process, accepting incoming connections forwarded from sslh. It prereads tls SNI names, to get destination information. When in the nginx-configuration a variable is used, which is set by an map action, the mapping takes place just in that moment, when the variable needs to be expanded.
So we have either default values, in case there is no SNI, or values for individual SNI.
The connection stream is than forwarded ip-transparent to a given destination, which is itself another nginx instance, defined in the configuration file.
@@ -226,17 +261,21 @@ The connection stream is than forwarded ip-transparent to a given destination, w
#### The Standard TLS-Termination ####
The server stanza listening on _192.168.255.254:444_ is a very basic TLS terminating endpoint. In most cases, a proxy is happy with a destination like this. Here nginx just terminates the TLS connection and shovels the incoming packets over to sshd in ip-transparent way. The proxytunnel configuration for this target will look like:
```
host t2.yourdomain.top
ProxyCommand /path/to/proxytunnel -e -p PROXY-IP:3128 -d t2.yourdomain.top:443
ServerAliveInterval 30
```
If you have no proxytunnel on your system, you can also use openssl. In this case the configuration looks like:
```
host t2-alias
ProxyCommand openssl s_client -proxy PROXY-IP:3128 -connect t2.yourdomain.top:443
ServerAliveInterval 30
```
`ssh ts-alias` will use this configuration.
One point from the above configuration needs further explanation, as we just refer to our common letsencrypt store, for certificates.
@@ -244,7 +283,9 @@ One point from the above configuration needs further explanation, as we just ref
##### Excurse To File ACLs #####
We are using here our common letsencrypt certificate store, as copying certificates after renewal is a pain in the as and prone to errors.
A small script makes sure, that all applications can read the certificates:
```
for i in Debian-exim dovecot mail www-data nginx ; do
setfacl -Rdm u:$i:rX ./archive/
@@ -257,6 +298,7 @@ for i in Debian-exim dovecot mail www-data nginx ; do
done
done
```
This script needs only to be run, when a new application user needs access.
The first two lines are making sure (watch the **d**), that the given options
on those directories will be the default for newly created files below.
@@ -265,19 +307,25 @@ The uppercase **X** means, that **x**-access will only be granted to directories
#### The Tricky TLS Termination ####
The server stanza listening on _192.168.255.254:445_ is a more tricky TLS terminating endpoint. Some proxies are trying to inspect the destination, before letting you go. Some of them you can fool, others not.
One trick can be to use an phantasy domain name with a self signed certificate, no dns server will ever resolve. As you are using this name as SNI name in your proxy-tunnel connection, it will work. This can be also a way, to hide your sshd, if someone, who knows you are using sslh tries to find your sshd. As long, as this person does not get access to proxies you are using, this may help in some situations.
Another trick is, requiring a client certificate for authentication. This is in each case the much better approach, as you can combine this also with official connected domain names. Without the client certificate no sshd!
The client certificate does not increase the above mentioned ratio between payload and headers, as this is only used while establishing the TLS connection.
It prevents however a proxy, doing a parallel sneak to your destination, to figure out what is behind. I have seen proxies letting you connect with this method, others are denying access.
To access this endpoint, the proxytunnel configuration inside `~/.ssh/config`
will look like:
```
host cryptic.foo.notexists
ProxyCommand /path/to/proxytunnel -e -c ~/public.cert -k ~/private.pem -C ~/myOwnCA.cert -p PROXY-IP:3128 -o cryptic.foo.notexists -d t1.yourdomain.top:443
ServerAliveInterval 30
```
-C gives the certfile to your certificate used for selfsigning the server certificate. You can also set -z for not verifying the certificate, but that is not recommended! `t1.yourdomain.top` represents a valid domain name, where the listening sslh can be reached. This name is not taken for SNI, because **-o** sets our hidden SNI name. Whenever you enter `ssh cryptic.foo.notexists` you will get connected to your server!
The best reommendation however is: Avoid self signed certs, if you are not really sure, what you are doing.
`-C` gives the certfile to your certificate used for selfsigning the server certificate. You can also set `-z` for not verifying the certificate, but that is not recommended! `t1.yourdomain.top` represents a valid domain name, where the listening sslh can be reached. This name is not taken for SNI, because **-o** sets our hidden SNI name. Whenever you enter `ssh cryptic.foo.notexists` you will get connected to your server!
The best recommendation however is: Avoid self-signed certs, if you are not really sure what you are doing.
+14
View File
@@ -168,3 +168,17 @@ protocols: (
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] }
);
```
I get an error "Too many open files"
====================================
`sslh` used up all the file descriptors it can (1024 by default in Linux),
which means it cannot create new connections. On some
systems, you can increase the process limit with `ulimit -n
2048` (for example). Maybe more usefully, you can limit the
connection usage based on protocols or listening address, as
described in the [max_connections](max_connections.md)
guide.
+1 -1
View File
@@ -163,7 +163,7 @@ Installation
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
```
You might need to create links in /etc/rc<x>.d so that the server
You might need to create links in `/etc/rc<x>.d` so that the server
start automatically at boot-up, e.g. under Debian:
```sh
update-rc.d sslh defaults
+9
View File
@@ -200,3 +200,12 @@ process both single-datagram protocols such as DNS, and
connection-based protocols such as QUIC.
An example for supporting QUIC is shown in `example.cfg`.
Limiting file descriptor consumption
------------------------------------
There are various mechanisms to limit the number of
concurrent connections, which allows to limit the usage of
file descriptor. This is described in a separate
[guide](max_connections.md).
+64
View File
@@ -0,0 +1,64 @@
Limiting the number of connections
----------------------------------
As a network-facing service, `sslh` can be attacked by very
simple denial-of-service attacks by creating a lot of
concurrent connections. Protecting against such attacks is
complicated from the server side. This attack will cause
`sslh` to create a large number of file handles, which can
cause exgagerated resource consumption.
In particular, when `sslh` runs into its file descriptor
limit (as defined with `ulimit -n`), bad things happen: It
becomes impossible to accept() new sockets efficiently
(refer to libev's documentation, ["The special problem of
accept()ing when you can't"](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#The_special_problem_of_accept_ing_wh)). Currently, when `sslh-ev` or
`sslh-select` reaches its limit of file descriptors, it just
stops accepting new connections altogether for a few
seconds, hoping for the situation to get better soon. It is
better to configure `sslh` to limit connections so the
`ulimit` is never reached.
Keep in mind that limiting the number of connections means
that in case of an attack, the server resources are
protected, but this might be at the expense of serving
legitimate connections. In particular, in some cases this
might mean SSH is no longer available.
There are several mechanisms to limit the number of
connections:
- Use `ulimit` (see bash(3) or your shell's man page) to
limit the number of file descriptors. (but then accepting
new connections might fail for a while)
Then, `sslh` provides several mechanisms to limit the number
of concurrent connections, which in turns limits the number
of file descriptors used.
Essentially there are two ways to do this:
- you can set `max_connections` per protocol, and `sslh`
will drop connections after probing if the count is
exceeded. This should help in keeping SSH connections
available even if an attacker is stuffing other protocols.
- you can set `max_connections` for each `listen` entry. So
the `sslh` process for each port will limit the number of
concurrent connections to that port. This is similar to the
`udp_max_connections` setting, but for TCP.
`sslh-select` and `sslh-ev` support both limit types.
`sslh-fork` has an explicit design goal to be as simple as
possible, which makes it impossible to implement limits per
protocol (because protocol probing is performed after the
process has forked). Limits will only work for `listen`
entries.
As of sslh 2.2.5, this is an experimental feature and not
all use cases have been tested. If 2.2.5 does not exist yet,
you must be on the Git's head, so it is even more
experimtental! Do not hesitate to send feedback if you
notice inconsistent behaviour.
+156
View File
@@ -0,0 +1,156 @@
Here is an example of deployment of sslh in transparent mode
using ansible and podman, [submitted by Github user
olegstepura](https://github.com/yrutschle/sslh/issues/448)
```yaml
# ansible podman task
- name: "Create sslh-co pod"
containers.podman.podman_pod:
name: sslh-co # read as "sslh and company"
state: started
ports:
- "80:80"
- "443:444"
# ... (more ports if needed)
network:
- '{{ containers.config.network }}' # other services from this network can access containers in this network for example prometheus can read caddy metrics at sslh-co:2020, also caddy itself can connect to other services to act as a reverse proxy
- name: "Create the sslh container"
containers.podman.podman_container:
name: sslh
image: "yrutschle/sslh:latest"
pod: sslh-co
capabilities:
- NET_RAW
- NET_BIND_SERVICE
- NET_ADMIN
sysctl:
net.ipv4.conf.default.route_localnet: 1
net.ipv4.conf.all.route_localnet: 1
expose:
- 444
volume:
# ... (make sure to mount config as you like)
command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
state: started
# ⚠️ Important: Never add a `network:` here, otherwise this setup will NOT work.
# Adding a network disconnects the sslh container from the pod's shared localhost.
- name: "Create the caddy container"
containers.podman.podman_container:
name: caddy
image: "lucaslorentz/caddy-docker-proxy:alpine" # regular caddy or nginx image will also work
pod: sslh-co
expose:
- 80
- 443
- 2020 # metrics, since caddy-docker-proxy uses :2019 internally
volume:
# ... (mount your configs and other stuff here)
- "/var/run/podman/podman.sock:/var/run/docker.sock"
state: started
notify: podman restart sslh
- name: "Create the SSH proxy to host container"
containers.podman.podman_container:
name: ssh-proxy
image: "alpine/socat:latest"
pod: sslh-co
expose:
- 222
command: TCP-LISTEN:222,fork TCP:host.containers.internal:22
state: started
```
```ini
# sslh config
foreground: true;
inetd: false;
numeric: true;
transparent: true;
timeout: 5;
listen:
(
{ host: "0.0.0.0"; port: "444"; keepalive: true; },
);
protocols:
(
{
name: "ssh";
service: "ssh";
host: "localhost";
port: "222";
fork: true;
},
{
name: "http";
host: "localhost";
port: "80";
},
{
name: "tls";
host: "localhost";
port: "443";
},
);
```
I omitted Caddy configs here since they're irrelevant, and some unrelated container configs were also dropped for clarity.
In the example above, `sslh`, `caddy`, and `ssh-proxy` are **three containers inside the same Podman pod**, all sharing the same **pod-local `localhost`**.
`sslh` listens on port **444** instead of **443**, because `caddy` already binds to port `443`, and reconfiguring Caddy is more complex due to its Let's Encrypt integration (Caddy itself "thinks" it’s bound to your host interface).
#### Port mapping scheme
All containers **share the same `localhost`** inside the pod:
```
host 443 → pod 444 (sslh) → pod 443 (caddy)
```
- **Podman** maps the host’s `443` → pod’s `444`.
- **SSHL** listens on `localhost:444` inside the pod and forwards TLS traffic to `caddy` on `localhost:443`.
- **Caddy** listens on `localhost:443` and reverse-proxies to other services in the private network.
This ensures that **reverse-proxied services** receive the correct client IP in the `X-Forwarded-For` header.
---
### Important note
⚠️ Do **not** set a `network:` parameter for the `sslh` container when it belongs to a Podman pod.
Setting a custom network **forces Podman to detach the container from the pod’s shared network namespace** and creates a **separate `localhost`** for that container, breaking transparent routing.
For example:
```yaml
command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
state: started
# ⚠️ Never add a `network:` here!
# Adding a network disconnects the sslh container from the pod's shared localhost,
# causing transparent routing to fail.
```
### Key takeaways
- `net.ipv4.conf.default.route_localnet` and iptables rules are applied **inside the `sslh` container only**, not on the host.
- The `sslh` **init** script manages all required `iptables` and routing rules inside of the container.
- **Transparent mode restrictions**:
- Any service `sslh` proxies traffic to **must** be inside the same pod and bound to its **pod-local `localhost`**.
- Container ports must not conflict within the pod.
- Ports should be published **at the pod level**, **not per-container**.
- Containers inside this pod **must not** be attached to any custom networks.
- Because of transparent routing, `sslh` cannot directly proxy SSH to the host IP.
A workaround is to run a lightweight **socat** container in the same pod:
`TCP-LISTEN:222 → TCP:host.containers.internal:22`
- Caddy’s `reverse_proxy` **to other containers** in the **custom network attached to the pod** still works properly.
- `socat` can also connect to both the **host** and **other containers** inside the custom network without issues.
### Tested setup
- Confirmed working on **Debian Trixie**
- Using **Podman 5.4.2**
- Pinned `podman` and related dependencies (`conmon`, `slirp4netns`, `aardvark-dns`, etc.) from **Debian testing**,
since **stable** often ships outdated versions that can break this setup.
+120
View File
@@ -0,0 +1,120 @@
Using Proxyprotocol with sslh
=============================
HAProxy defined the [Proxy protocol](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/proxying-essentials/client-ip-preservation/enable-proxy-protocol/), where a proxy adds external connection information directly inside the TCP connection, for the backend server to get. This achieves the same goal as transparent proxying, while requiring no networking knowledge and no administration rights.
Instead, the protocol needs to be supported in the servers.
Many do, e.g. Apache (with the `RemoteIPProxyProtocol` setting).
`sslh` supports this protocol both on the client and the
server side.
Proxyprotocol on the server side
--------------------------------
Presumably that's the most common setup, with `sslh` being
the client-facing proxy in front of the backend server.
Support is enabled by adding the `proxyprotocol: [1|2]`
setting in the target protocol:
```
listen:
(
{ host: "localhost"; port: "443"; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "2222"; },
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
);
);
```
Here `sslh` listens on port 443. It forwards `ssh`
connections to port 2222 without touching them (which means sshd does not see the external data) (Apparently openssh does not as of september 2025 support Proxyprotocol).
It forwards `tls` to port 8080, adding a proxy-protocol v2
header which allows Apache (configured with RemoteIPProxyProtocol) to log the external IP and port.
```
203.0.113.4:443 :2222
Client -----------------> sslh -------------> sshd
198.51.100.42:2342 | logs: 127.0.0.1:<local port>
|
| :8080
\--pp v2-------> apache
logs: 198.51.100.42:2342
```
The `proxyprotocol` setting can be set to 1 or 2, which
selects the appropriate protocol version. Version 2 being
more efficient, this is what you should use unless you have
good reasons to use version 1 (e.g. your backend server only
supports v1, an unlikely scenario).
Proxyprotocol on the client side
--------------------------------
`sslh` also supports Proxy-protocol on incoming connections.
These are so-called "client side", but you should not enable
proxy-protocol on an Internet-facing server. Instead, this
can be useful if `sslh` is located behind another proxy that
adds proxy-protocol headers, e.g. HAProxy.
This is enabled by adding `proxyprotocol: true` to a
`listen` statement:
```
listen:
(
{ host: "localhost"; port: "443"; proxyprotocol: true; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
{ name: "tls"; host: "localhost"; port: "8080"; }
);
);
```
In that case, `sslh` expects to find a proxyprotocol header
on all incoming connections. It ignores the header to
perform its usual protocol detection.
If the target protocol
specified nothing, the connection is passed untouched, i.e.
the proxyprotocol header is forwarded as usual: here, Apache
on 8080 will receive the proxyprotocol header.
If the target protocol specifies `proxyprotocol: 0`, then
`sslh` removes the proxyprotocol header, and logs an
additional message with the connection information extracted
from the header. Here, `sslh` logs the external information
for ssh connections, removes the header, and forwards the
connection to sshd. `sshd` does not support proxyprotocol,
and logs a connection coming from the local IP address.
```
203.0.113.4:443 :2222
Client ---------> HaProxy ----pp v2---------> sslh -------------> sshd
198.51.100.42:2342 | logs: 127.0.0.1:<local port>
| sslh logs: 198.51.100.42:2342 to localhost:2222
|
| :8080
\--pp v2-------> apache
logs: 198.51.100.42:2342
sslh logs: local connection
```
`sslh` always logs the 'real' network information, here the
local addresses, and only logs the proxyprotocol information
when it removes the header. It is not necessary to log the
proxyprotocol information when the target server will
receive it (as it is then the target server's job to log the
external IP address).
+1
View File
@@ -9,6 +9,7 @@ This chapter is a little excurse to the dataflow. First point of all is somethin
Packets from local application talking to other local applications are routed through the loopback-interface. They leave postrouting to lo and reentering from there prerouting, without passing ingress/egress.
This has nothing to do with the "**route_localnet = 1**" trick, which only makes, that the the local ip range 10.0.0.0/8 gets routed!
As you can read in many articles, this is nothing you should do, as you may bring your system at risk, because it allows to leak packets from outside to applications, which feel themselves secure, by using those unroutable addresses.
#### A Simple Simulation ####
You can prove this behaviour with a simple test:
```
+1 -1
View File
@@ -1,7 +1,7 @@
# Transparent proxy using packet marking
Before reading further, make sure you try to set up
transparent proxying using [IP routing](doc/simple_transparent_proxy.md).
transparent proxying using [IP routing](simple_transparent_proxy.md).
It is conceptually easier to understand, cleaner, and more
portable.
+45
View File
@@ -0,0 +1,45 @@
# sslh configuration for saaz:
# ssh to localhost
# https to nextcloud
foreground: true;
inetd: false;
numeric: false;
transparent: false;
user: "yves";
pidfile: "/tmp/sslh.pid";
syslog_facility: "auth";
# Where to write the ECH configurations
ech_conf_sink: "/tmp/ech";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
verbose: 1;
# Change hostname with your external address name.
listen:
(
{ host: "inet"; port: "4443"; },
{ host: "chaton"; port: "4445"; }
);
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
{ name: "tls"; sni_hostnames: [
"inet.rutschle.net", "lists.rutschle.net"
];
host: "localhost"; port: "4453"; },
{ name: "tls"; sni_hostnames: [
"chaton.rutschle.net"
];
host: "localhost"; port: "4455"; }
);
on-timeout: "ssh";
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:59 2025.
* on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:59 2025.
* on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
-4
View File
@@ -26,7 +26,6 @@
#include <pwd.h>
#include <syslog.h>
#include <libgen.h>
#include <getopt.h>
#include <errno.h>
#define cfg sslhcfg
@@ -323,9 +322,6 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
int main(int argc, char *argv[])
{
extern char *optarg;
extern int optind;
int num_addr_listen;
struct listen_endpoint *listen_sockets;
+14 -2
View File
@@ -49,9 +49,18 @@ syslog_facility: "auth";
# List of interfaces on which we should listen
# Options:
# host: name of the interface to listen to
# port: name of the service to listen to
# is_udp: listen in the UDP domain (default is TCP)
# is_unix: listen in the UNIX socket domain (default is TCP)
# keepalive: set TCP_KEEPALIVE
# max_connections: drop incoming connections after this count
# proxyprotocol: [true|false]: this endpoint expects
# proxyprotocol headers on incoming connections. Refer to
# doc/proxyprotocol.md
listen:
(
{ host: "thelonious"; port: "443"; },
{ host: "thelonious"; port: "443"; max_connections: 5; },
{ host: "thelonious"; port: "8080"; keepalive: true; },
{ host: "thelonious"; is_udp: true; port: "443"; },
{ host: "/tmp/unix_socket"; is_unix: true; port: ""; }
@@ -70,7 +79,10 @@ listen:
# keepalive: Should TCP keepalive be on or off for that
# connection (default is off)
# fork: Should a new process be forked for this protocol?
# (only useful for sslh-select)
# (only useful for sslh-select and sslh-ev)
# max_connections: further connections for this protocol
# will be rejected once the limit is reached. Does not
# work with sslh-fork.
# tfo_ok: Set to true if the server supports TCP FAST OPEN
# resolve_on_forward: Set to true if server address should be resolved on
# (every) newly incoming connection (again)
+13 -1
View File
@@ -59,6 +59,11 @@ gap_array* gap_init(int len)
return gap;
}
void gap_set_hardlimit(gap_array* gap, int index)
{
gap->hardlimit = index;
}
int gap_extend(gap_array* gap)
{
int elem_size = sizeof(gap->array[0]);
@@ -88,7 +93,11 @@ void gap_destroy(gap_array* gap)
* is considered len elements long.
* A poor man's list, if you will. Currently only used to remove probing
* connections, so it only copies a few pointers at most.
* Returns -1 if ptr was not found */
* Returns:
* -1 if ptr was not found
* -2 if `len` is inconsistent with gap->len (i.e. trying to shift beyond the
* end of the array)
* */
int gap_remove_ptr(gap_array* gap, void* ptr, int len)
{
int start, i;
@@ -102,6 +111,9 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len)
else
return -1;
if (gap->len < len)
return -2;
for (i = start; i < len - 1; i++) {
gap->array[i] = gap->array[i+1];
}
+8 -1
View File
@@ -4,6 +4,7 @@
typedef struct gap_array gap_array;
gap_array* gap_init(int len);
void gap_set_hardlimit(gap_array* ga, int index);
static void* gap_get(gap_array* gap, int index);
static int gap_set(gap_array* gap, int index, void* ptr);
void gap_destroy(gap_array* gap);
@@ -13,7 +14,8 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len);
/* Private declarations to allow inlining.
* Don't assume my implementation. */
typedef struct gap_array {
int len; /* Number of elements in array */
int len; /* Number of elements in array (corresponds to the number of pages allocated) */
int hardlimit; /* Maximum index allowed after which sets will fail; 0 means no limit */
void** array;
} gap_array;
@@ -21,6 +23,9 @@ int gap_extend(gap_array* gap);
static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, void* ptr)
{
if (gap->hardlimit && (index > gap->hardlimit))
return -1;
while (index >= gap->len) {
int res = gap_extend(gap);
if (res == -1) return -1;
@@ -37,6 +42,8 @@ static inline void* __attribute__((unused)) gap_get(gap_array* gap, int index)
* gap_get()'s job. This will do for now */
if (index >= gap->len) return NULL;
if (gap->hardlimit && (index > gap->hardlimit)) return NULL;
return gap->array[index];
}
+44 -18
View File
@@ -37,13 +37,35 @@
#include <stdlib.h>
#include <stddef.h>
#if HASH_DEBUG
#include <stdio.h>
#include <string.h>
#endif
#include "gap.h"
#if 0
#define DEBUG 1
#include <stdio.h>
#endif
typedef void* hash_item;
typedef struct hash hash;
#include "hash.h"
static void* const FREE = NULL;
/* Define DEBUG adds two functions:
* hash_dump() that prints the hash to a file as text
* hash_item_print() takes a pointer to a function that prints a hash item
* See test suite in hashtest/ for an example
* */
#if 0
#define HASH_DEBUG 1
#endif
struct hash {
int hash_size; /* Max number of items in the hash */
int item_cnt; /* Number of items in the hash */
@@ -51,9 +73,11 @@ struct hash {
hash_make_key_fn hash_make_key;
hash_cmp_item_fn cmp_item;
#ifdef HASH_DEBUG
void (*hash_item_print)(FILE* out, hash_item item);
#endif
};
typedef struct hash hash;
static int hash_make_key(hash* h, hash_item item)
@@ -131,6 +155,10 @@ static int distance(int current_index, hash* h, hash_item item)
return current_index - wanted_index + h->hash_size;
}
int hash_insertable(hash* h)
{
return (h->item_cnt < h->hash_size);
}
int hash_insert(hash* h, hash_item new)
{
@@ -146,7 +174,7 @@ int hash_insert(hash* h, hash_item new)
if (distance(index, h, curr_item) < distance(index, h, new)) {
gap_set(h->data, index, new);
#if DEBUG
fprintf(stderr, "intermediate insert [%s] at %d\n", &new->client_addr, index);
fprintf(stderr, "intermediate insert at %d\n", index);
#endif
new = curr_item;
}
@@ -188,17 +216,16 @@ int hash_remove(hash* h, hash_item item)
return 0;
}
#if HASH_TESTING
#include <stdio.h>
#include <string.h>
#define STR_LENGTH 16
struct hash_item {
int wanted_index;
char str[STR_LENGTH];
};
/******************************************************************************/
#if HASH_DEBUG
void hash_item_print(hash* h, void fn(FILE*, hash_item))
{
h->hash_item_print = fn;
}
void hash_dump(hash* h, char* filename)
{
char str[STR_LENGTH];
FILE* out = fopen(filename, "w");
if (!out) {
@@ -209,14 +236,13 @@ void hash_dump(hash* h, char* filename)
fprintf(out, "<hash elem=%d>\n", h->item_cnt);
for (int i = 0; i < h->hash_size; i++) {
hash_item item = gap_get(h->data, i);
int idx = 0;
memset(str, 0, STR_LENGTH);
if (item) {
idx = hash_make_key(h, item);
memcpy(str, ((struct hash_item*)item)->str, STR_LENGTH);
}
fprintf(out, "\t%d:%d:%s\n", i, idx, str);
fprintf(out, "\t%d:", i);
if (item)
h->hash_item_print(out, item);
else
fprintf(out, "<empty>");
fprintf(out, "\n");
}
fprintf(out, "</hash>\n");
fclose(out);
+1 -2
View File
@@ -4,8 +4,6 @@
/* You will need to typedef a pointer type to hash_item before including this
* .h */
typedef struct hash hash;
/* Function that returns a key (index) for a given item. The key must be always
* the same for an item. It doesn't need to be bounded (hash.c masks it for you) */
typedef int (*hash_make_key_fn)(hash_item item);
@@ -15,6 +13,7 @@ typedef int (*hash_cmp_item_fn)(hash_item item1, hash_item item2);
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item);
int hash_insertable(hash* h); /* Is it possible to insert: any space left? */
int hash_insert(hash* h, hash_item new);
int hash_remove(hash* h, hash_item item);
+1 -1
View File
@@ -1,5 +1,5 @@
CFLAGS=-DHASH_TESTING -O2 -Wall
CFLAGS=-DHASH_DEBUG -O2 -Wall
OBJ=../hash.o ../gap.o htest.o
htest: $(OBJ)
+28 -28
View File
@@ -1,34 +1,34 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:10:aa
11:10:ab
12:10:ac
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:20:ba
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+28 -28
View File
@@ -1,34 +1,34 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:10:aa
11:10:ab
12:12:ac
13:13:ad
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+27 -27
View File
@@ -1,34 +1,34 @@
<hash elem=5>
0:30:ad
1:0:
1:<empty>
2:2:ba
3:2:bb
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ac
</hash>
+28 -28
View File
@@ -1,34 +1,34 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:10:aa
11:10:bc
12:12:ac
13:0:
13:<empty>
14:14:ad
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+32 -32
View File
@@ -1,34 +1,34 @@
<hash elem=0>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+1 -1
View File
@@ -20,7 +20,7 @@
18:18:bi
19:19:bj
20:20:ca
21:0:
21:<empty>
22:22:cd
23:23:ce
24:24:cf
+28 -28
View File
@@ -1,34 +1,34 @@
<hash elem=4>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:10:ab
11:0:
11:<empty>
12:12:ac
13:13:ad
14:14:ae
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+28 -28
View File
@@ -1,34 +1,34 @@
<hash elem=4>
0:30:ba
1:30:bb
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ab
</hash>
+27 -27
View File
@@ -1,34 +1,34 @@
<hash elem=5>
0:30:ac
1:1:ad
2:0:
2:<empty>
3:3:ae
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ab
</hash>
+29 -29
View File
@@ -1,34 +1,34 @@
<hash elem=3>
0:30:ad
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ac
</hash>
+27 -27
View File
@@ -1,34 +1,34 @@
<hash elem=5>
0:31:ad
1:0:
1:<empty>
2:2:ba
3:3:bb
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ac
</hash>
BIN
View File
Binary file not shown.
+7
View File
@@ -55,6 +55,11 @@ static void htest_next_key(FILE* f, char* action, int* key, char str[STR_LENGTH]
if (res == EOF) exit(0);
}
static void item_print(FILE* out, hash_item item)
{
fprintf(out, "%d:%s", item->wanted_index, item->str);
}
int main(int argc, char* argv[])
{
hash* h = hash_init(HASH_SIZE, &hash_make_key, &cmp_item);
@@ -63,6 +68,8 @@ int main(int argc, char* argv[])
int line = 0;
FILE* f;
hash_item_print(h, item_print);
if (argc != 3) {
fprintf(stderr, "Usage: htest <script file> <dump file>\n");
exit(1);
+27 -27
View File
@@ -1,34 +1,34 @@
<hash elem=5>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:10:aa
11:10:ab
12:10:ac
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:20:ba
21:21:bb
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+27 -27
View File
@@ -1,34 +1,34 @@
<hash elem=5>
0:0:
1:0:
2:0:
3:0:
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
0:<empty>
1:<empty>
2:<empty>
3:<empty>
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:10:aa
11:10:bc
12:11:ab
13:12:ac
14:14:ad
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
30:0:
31:0:
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:<empty>
31:<empty>
</hash>
+26 -26
View File
@@ -3,32 +3,32 @@
1:30:ad
2:2:ba
3:2:bb
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ab
</hash>
+26 -26
View File
@@ -3,32 +3,32 @@
1:30:ad
2:0:ba
3:0:bb
4:0:
5:0:
6:0:
7:0:
8:0:
9:0:
10:0:
11:0:
12:0:
13:0:
14:0:
15:0:
16:0:
17:0:
18:0:
19:0:
20:0:
21:0:
22:0:
23:0:
24:0:
25:0:
26:0:
27:0:
28:0:
29:0:
4:<empty>
5:<empty>
6:<empty>
7:<empty>
8:<empty>
9:<empty>
10:<empty>
11:<empty>
12:<empty>
13:<empty>
14:<empty>
15:<empty>
16:<empty>
17:<empty>
18:<empty>
19:<empty>
20:<empty>
21:<empty>
22:<empty>
23:<empty>
24:<empty>
25:<empty>
26:<empty>
27:<empty>
28:<empty>
29:<empty>
30:30:aa
31:30:ab
</hash>
+47 -21
View File
@@ -21,10 +21,7 @@
#define _GNU_SOURCE
#include <stdio.h>
#ifdef ENABLE_REGEX
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#endif
#include <regex.h>
#include <ctype.h>
#include "probe.h"
#include "log.h"
@@ -146,6 +143,7 @@ static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
#define OVPN_OPCODE_MASK 0xF8
#define OVPN_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3)
#define OVPN_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3)
#define OVPN_CONTROL_HARD_RESET_CLIENT_V3 (0x0A << 3)
#define OVPN_HMAC_128 16
#define OVPN_HMAC_160 20
#define OVPN_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
@@ -165,7 +163,9 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
return PROBE_NEXT;
if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 &&
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2)
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2 &&
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V3
)
return PROBE_NEXT;
/* The detection pattern above may not be reliable enough.
@@ -176,13 +176,19 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128) + sizeof(uint32_t))
return PROBE_NEXT;
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))) <= 5u)
uint32_t i;
/* OVPN_HMAC_128 is unaligned, which requires special care e.g. on ARM */
memcpy(&i, (p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128)), sizeof(i));
i = ntohl(i);
if (i <= 5u)
return PROBE_MATCH;
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160) + sizeof(uint32_t))
return PROBE_NEXT;
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u)
memcpy(&i, (p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160)), sizeof(i));
i = ntohl(i);
if (i <= 5u)
return PROBE_MATCH;
return PROBE_NEXT;
@@ -362,17 +368,38 @@ static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_prot
return PROBE_MATCH;
}
/* ******************
* is_syslog_protocol
* */
static regex_t syslog_preg;
static int configured_syslog_regex = 0;
static void config_syslog_regex(void)
{
/* two patterns for syslog messages:
* <12> My message
* 15 <12> My message
* 12 is 'priority', 1 to 3 digits (RFC4234)
* 15 is 'message length', a TCP-only option (RFC6587)
*/
int res = regcomp(&syslog_preg, "^([0-9]{1,3} )?<[0-9]{1,3}>", REG_EXTENDED);
if (res) {
print_message(msg_system_error, "regcomp");
exit(1);
}
configured_syslog_regex = 1;
}
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
int res, i, j;
char buf[len+1];
res = sscanf(p, "<%d>", &i);
if (res == 1) return 1;
if (!configured_syslog_regex) config_syslog_regex();
res = sscanf(p, "%d <%d>", &i, &j);
if (res == 2) return 1;
strncpy(buf, p, len);
buf[len] = 0;
return 0;
return (regexec(&syslog_preg, buf, (size_t)0, NULL, 0) == 0);
}
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
@@ -396,16 +423,18 @@ static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocol
return packet_len == len;
}
#ifdef ENABLE_REGEX
pcre2_match_data* probe_regex_matches;
#endif
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
{
#ifdef ENABLE_REGEX
pcre2_code**probe = (pcre2_code**)proto->data;
pcre2_match_data* matches;
matches = pcre2_match_data_create(1, NULL);
for (; *probe; probe++) {
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL);
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, probe_regex_matches, NULL);
if (res >= 0) return 1;
}
@@ -423,8 +452,8 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item
* proto_out: protocol that matched
*
* Returns
* PROBE_AGAIN if not enough data, and set *proto to NULL
* PROBE_MATCH if protocol is identified, in which case *proto is set to
* PROBE_AGAIN if not enough data, and set *proto_out to NULL
* PROBE_MATCH if protocol is identified, in which case *proto_out is set to
* point to the appropriate protocol
* */
int probe_buffer(char* buf, int len,
@@ -436,9 +465,6 @@ int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item* p;
int i, res, again = 0;
print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, buf, len);
*proto_out = NULL;
for (i = 0; i < proto_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
+8
View File
@@ -6,6 +6,7 @@
#include "common.h"
#include "tls.h"
#include "log.h"
#include "proxyprotocol.h"
typedef enum {
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
@@ -66,4 +67,11 @@ struct sslhcfg_protocols_item* timeout_protocol(void);
void hexdump(msg_info, const char*, unsigned int);
#ifdef ENABLE_REGEX
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
extern pcre2_match_data* probe_regex_matches;
#endif
#endif
+105 -4
View File
@@ -1,7 +1,7 @@
/*
Processes that are common to sslh-ev and sslh-select
# Copyright (C) 2021 Yves Rutschle
# Copyright (C) 2021-2025 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -26,6 +26,14 @@
#include "probe.h"
#include "log.h"
/* Struct to keep track of an association of a forked PID to its
* listening socket and its protocol */
struct pid2proto {
pid_t pid;
struct listen_endpoint* endpoint;
struct sslhcfg_protocols_item* proto;
};
int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
{
@@ -38,9 +46,9 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
watchers_del_read(fd_info->watchers, cnx->q[i].fd);
watchers_del_write(fd_info->watchers, cnx->q[i].fd);
close(cnx->q[i].fd);
if (cnx->q[i].deferred_data)
free(cnx->q[i].deferred_data);
}
if (cnx->q[i].begin_deferred_data)
free(cnx->q[i].begin_deferred_data);
}
if (cnx->type == SOCK_DGRAM)
@@ -49,6 +57,9 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
if (gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing) != -1)
fd_info->num_probing--;
dec_proto_connections(cnx->proto);
dec_listen_connections(cnx->endpoint);
collection_remove_cnx(fd_info->collection, cnx);
return 0;
}
@@ -88,9 +99,11 @@ struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_e
int type = listen_socket->type;
struct connection* cnx;
udp_timeouts(fd_info);
switch (type) {
case SOCK_STREAM:
cnx = accept_new_connection(fd, fd_info);
cnx = accept_new_connection(listen_socket, fd_info);
if (!cnx) return NULL;
break;
@@ -112,4 +125,92 @@ struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_e
typedef struct pid2proto* hash_item;
#include "hash.h"
static int pid_make_key(hash_item item)
{
return item->pid;
}
static int pid_cmp(hash_item item1, hash_item item2)
{
return item1->pid != item2->pid;
}
/* When a child has died, update the counts appropriately */
void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
struct pid2proto p2p = { .pid = pid };
struct pid2proto* found = hash_find(loop->pid2proto, &p2p);
dec_proto_connections(found->proto);
dec_listen_connections(found->endpoint);
hash_remove(loop->pid2proto, found);
free(found);
}
typedef struct pid2proto* hash_item;
#include "hash.h"
int has_space_to_fork(struct loop_info* fd_info)
{
return hash_insertable(fd_info->pid2proto);
}
void remember_child_data(struct loop_info* fd_info,
struct connection* cnx, pid_t pid)
{
struct pid2proto* pid2proto = malloc(sizeof(*pid2proto));
pid2proto->pid = pid;
pid2proto->proto = cnx->proto;
pid2proto->endpoint = cnx->endpoint;
if (hash_insert(fd_info->pid2proto, pid2proto)) {
print_message(msg_int_error, "hash_insert for pid %d failed\n", pid);
/* This should not happen as called is supposed to check that space is
* available in the hash */
exit(1);
}
}
static int max_forking_connections(void)
{
int max_cnx = 0;
for (int i = 0; i < cfg.protocols_len; i++) {
if (cfg.protocols[i].fork) {
if (cfg.protocols[i].max_connections_is_present)
max_cnx += cfg.protocols[i].max_connections;
else max_cnx += 512; /* A magic number that's hopefully high enough */
}
}
return max_cnx;
}
static int next_power_of_two(int n)
{
n |= (n >> 16);
n |= (n >> 4);
n |= (n >> 2);
n |= (n >> 1);
return n + 1;
}
void loop_init(struct loop_info* loop, int num_addr_listen)
{
memset(loop, 0, sizeof(*loop));
loop->collection = collection_init(0);
loop->probing_list = gap_init(0);
CHECK_ALLOC(loop->probing_list, "gap_init");
udp_init(loop);
tcp_init();
loop->num_addr_listen = num_addr_listen;
int max_forks = next_power_of_two(max_forking_connections());
loop->pid2proto = hash_init(2 * max_forks, pid_make_key, pid_cmp);
CHECK_ALLOC(loop->pid2proto, "hash_init");
}
+12 -3
View File
@@ -5,13 +5,12 @@
#include "collection.h"
#include "gap.h"
typedef struct connection* hash_item;
#include "hash.h"
/* Provided by event loop, sslh-ev or sslh-select, for implementation-dependant
* data */
typedef struct watchers watchers;
typedef void hash;
/* Global state for a loop */
struct loop_info {
int num_probing; /* Number of connections currently probing
@@ -21,7 +20,10 @@ struct loop_info {
hash* hash_sources; /* UDP remote sources previously encountered */
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
watchers* watchers;
int num_addr_listen; /* How many listen endpoints do we have here */
cnx_collection* collection; /* Collection of connections linked to this loop */
};
@@ -30,7 +32,12 @@ void cnx_read_process(struct loop_info* fd_info, int fd);
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket);
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
void loop_init(struct loop_info* loop, int num_addr_listen);
void remember_child_data(struct loop_info* fd_info,
struct connection* cnx, pid_t pid);
void decrease_forked_connection(struct loop_info* loop, pid_t pid);
int has_space_to_fork(struct loop_info* loop);
/* These must be declared in the loop handler, sslh-ev or sslh-select */
void watchers_add_read(watchers* w, int fd);
@@ -38,4 +45,6 @@ void watchers_del_read(watchers* w, int fd);
void watchers_add_write(watchers* w, int fd);
void watchers_del_write(watchers* w, int fd);
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
#endif
+56 -7
View File
@@ -50,22 +50,38 @@ static int family_to_pp(int af_family)
typedef char libpp_addr[108]; /* This is hardcoded in libproxyprotocol/proxy_protocol.h */
typedef enum {
PEER,
SOCK
} sockpeer_t;
/* Fills *addr, *host and *serv with the connection information corresponding
* to fd. *host is the IP address as string and *serv is the service (port)
* to fd. sockpeer indicates if if is filled with the remote, or local, part of
* the socket.
* *host is the IP address as string and *serv is the service (port)
* */
static int get_info(int fd, struct addrinfo* addr, libpp_addr* host, uint16_t* serv)
static int get_info(int fd, sockpeer_t sockpeer, struct addrinfo* addr, libpp_addr* host, uint16_t* serv)
{
char serv_str[NI_MAXSERV];
int res;
res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen);
CHECK_RES_RETURN(res, "getpeername", -1);
if (sockpeer == PEER) {
res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen);
CHECK_RES_RETURN(res, "getpeername", -1);
} else {
res = getsockname(fd, addr->ai_addr, &addr->ai_addrlen);
CHECK_RES_RETURN(res, "getsockname", -1);
}
res = getnameinfo(addr->ai_addr, addr->ai_addrlen,
(char*)host, sizeof(*host),
serv_str, sizeof(serv_str),
NI_NUMERICHOST | NI_NUMERICSERV );
CHECK_RES_RETURN(res, "getnameinfo", -1);
if (res != 0) {
print_message(msg_system_error, "getnameinfo: %s\n", gai_strerror(res));
return -1;
}
*serv = atoi(serv_str);
@@ -89,14 +105,14 @@ int pp_write_header(int pp_version, struct connection* cnx)
addr.ai_addr = (struct sockaddr*)&ss;
addr.ai_addrlen = sizeof(ss);
res = get_info(cnx->q[0].fd,
res = get_info(cnx->q[0].fd, PEER,
&addr,
&pp_info_in_v1.src_addr,
&pp_info_in_v1.src_port);
if (res == -1) return -1;
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
res = get_info(cnx->q[1].fd,
res = get_info(cnx->q[1].fd, SOCK,
&addr,
&pp_info_in_v1.dst_addr,
&pp_info_in_v1.dst_port
@@ -117,4 +133,37 @@ int pp_write_header(int pp_version, struct connection* cnx)
return 0;
}
/* Logs the connection data in the pp header and return header length */
int pp_log_connection(char* buffer, ssize_t buffer_len)
{
pp_info_t pp_info;
int header_len = pp_parse_hdr((uint8_t*)buffer, buffer_len, &pp_info);
if (header_len > 0) {
print_message(msg_connections, "client had proxyprotocol info: %s:%d to %s:%d not forwarded to server\n",
pp_info.src_addr, pp_info.src_port,
pp_info.dst_addr, pp_info.dst_port);
} else {
print_message(msg_connections, "no proxyprotocol data found on client side, forwarding as is\n");
header_len = 0;
}
return header_len;
}
int pp_header_len(char* buffer, int buffer_len)
{
pp_info_t pp_info;
int header_len = pp_parse_hdr((uint8_t*)buffer, buffer_len, &pp_info);
print_message(msg_probe_info, "proxyprotocol header %d bytes found\n", header_len);
if (header_len < 0) header_len = 0;
return header_len;
}
#endif /* HAVE_PROXYPROTOCOL */
+6
View File
@@ -3,12 +3,18 @@
#if HAVE_PROXYPROTOCOL
int pp_write_header(int pp_version, struct connection* cnx);
int pp_log_connection(char* buffer, ssize_t buffer_len);
int pp_header_len(char* buffer, int len);
#else /* HAVE_PROXYPROTOCOL */
static inline int pp_write_header(int pp_version, struct connection* cnx) { return 0; }
static int pp_log_connection(char* buffer, ssize_t buffer_len) { return 0; }
static inline int pp_header_len(char*, int) { return 0; }
#endif /* HAVE_PROXYPROTOCOL */
+125 -18
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:58 2025.
* on Sat Jul 18 17:34:37 2026.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
@@ -482,6 +482,8 @@ struct arg_file* sslhcfg_conffile;
struct arg_str* sslhcfg_chroot;
struct arg_str* sslhcfg_syslog_facility;
struct arg_str* sslhcfg_logfile;
struct arg_str* sslhcfg_ech_conf_sink;
struct arg_str* sslhcfg_ech_public_name;
struct arg_str* sslhcfg_on_timeout;
struct arg_str* sslhcfg_prefix;
struct arg_str* sslhcfg_listen;
@@ -500,7 +502,7 @@ struct arg_file* sslhcfg_conffile;
struct arg_str* sslhcfg_anyprot;
struct arg_end* sslhcfg_end;
static struct config_desc table_sslhcfg_protocols[] = {
@@ -712,6 +714,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
/* default_val*/ .default_val.def_bool = 0
},
{
/* name */ "max_connections",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ NULL,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_protocols_item, max_connections),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_protocols_item, max_connections_is_present),
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 1,
/* default_val*/ .default_val.def_int = 0
},
{
/* name */ "sni_hostnames",
/* type */ CFG_ARRAY,
@@ -793,7 +811,7 @@ static struct config_desc table_sslhcfg_protocols[] = {
},
{ 0 }
};
static struct config_desc table_sslhcfg_listen[] = {
@@ -829,6 +847,22 @@ static struct config_desc table_sslhcfg_listen[] = {
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "max_connections",
/* type */ CFG_INT,
/* sub_group*/ NULL,
/* arg_cl */ NULL,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_listen_item, max_connections),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_listen_item, max_connections_is_present),
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 1,
/* default_val*/ .default_val.def_int = 0
},
{
/* name */ "is_udp",
/* type */ CFG_BOOL,
@@ -876,9 +910,25 @@ static struct config_desc table_sslhcfg_listen[] = {
/* optional */ 0,
/* default_val*/ .default_val.def_bool = 0
},
{
/* name */ "proxyprotocol",
/* type */ CFG_BOOL,
/* sub_group*/ NULL,
/* arg_cl */ NULL,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_listen_item, proxyprotocol),
/* offset_len */ 0,
/* offset_present */ 0,
/* size */ sizeof(int),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 0,
/* default_val*/ .default_val.def_bool = 0
},
{ 0 }
};
static struct config_desc table_sslhcfg[] = {
@@ -1266,6 +1316,38 @@ static struct config_desc table_sslhcfg[] = {
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "ech_conf_sink",
/* type */ CFG_STRING,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_ech_conf_sink,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, ech_conf_sink),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_item, ech_conf_sink_is_present),
/* size */ sizeof(char*),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 1,
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "ech_public_name",
/* type */ CFG_STRING,
/* sub_group*/ NULL,
/* arg_cl */ & sslhcfg_ech_public_name,
/* base_addr */ NULL,
/* offset */ offsetof(struct sslhcfg_item, ech_public_name),
/* offset_len */ 0,
/* offset_present */ offsetof(struct sslhcfg_item, ech_public_name_is_present),
/* size */ sizeof(char*),
/* array_type */ -1,
/* mandatory */ 0,
/* optional */ 1,
/* default_val*/ .default_val.def_string = NULL
},
{
/* name */ "on_timeout",
/* type */ CFG_STRING,
@@ -1452,7 +1534,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: listen */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_listen,
.base_entry = & table_sslhcfg [26],
.base_entry = & table_sslhcfg [28],
.targets = sslhcfg_listen_targets,
@@ -1464,7 +1546,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: ssh */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssh,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_ssh_targets,
@@ -1476,7 +1558,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: tls */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_tls,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_tls_targets,
@@ -1488,7 +1570,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: ssl */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_ssl,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_ssl_targets,
@@ -1500,7 +1582,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: openvpn */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_openvpn,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_openvpn_targets,
@@ -1512,7 +1594,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: tinc */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_tinc,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_tinc_targets,
@@ -1524,7 +1606,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: wireguard */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_wireguard,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_wireguard_targets,
@@ -1536,7 +1618,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: xmpp */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_xmpp,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_xmpp_targets,
@@ -1548,7 +1630,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: http */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_http,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_http_targets,
@@ -1560,7 +1642,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: adb */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_adb,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_adb_targets,
@@ -1572,7 +1654,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: socks5 */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_socks5,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_socks5_targets,
@@ -1584,7 +1666,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: syslog */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_syslog,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_syslog_targets,
@@ -1596,7 +1678,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: msrdp */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_msrdp,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_msrdp_targets,
@@ -1608,7 +1690,7 @@ static struct compound_cl_arg compound_cl_args[] = {
{ /* arg: anyprot */
.regex = "(.+):(\\w+)",
.arg_cl = & sslhcfg_anyprot,
.base_entry = & table_sslhcfg [27],
.base_entry = & table_sslhcfg [29],
.targets = sslhcfg_anyprot_targets,
@@ -2297,6 +2379,8 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
sslhcfg_ech_conf_sink = arg_strn(NULL, "ech-conf-sink", "<str>", 0, 1, "File where to write ECH configurations"),
sslhcfg_ech_public_name = arg_strn(NULL, "ech-public-name", "<str>", 0, 1, "Public name for ECH client-facing proxy (usually, sslh's address)"),
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
@@ -2421,6 +2505,11 @@ static void sslhcfg_protocols_fprint(
fprintf(out, "keepalive: %d", sslhcfg_protocols->keepalive);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "max_connections: %d", sslhcfg_protocols->max_connections);
if (! sslhcfg_protocols->max_connections_is_present)
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "sni_hostnames [%zu]:\n", sslhcfg_protocols->sni_hostnames_len);
for (i = 0; i < sslhcfg_protocols->sni_hostnames_len; i++) {
indent(out, depth+1);
@@ -2463,6 +2552,11 @@ static void sslhcfg_listen_fprint(
fprintf(out, "port: %s", sslhcfg_listen->port);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "max_connections: %d", sslhcfg_listen->max_connections);
if (! sslhcfg_listen->max_connections_is_present)
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "is_udp: %d", sslhcfg_listen->is_udp);
fprintf(out, "\n");
indent(out, depth);
@@ -2471,6 +2565,9 @@ static void sslhcfg_listen_fprint(
indent(out, depth);
fprintf(out, "keepalive: %d", sslhcfg_listen->keepalive);
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "proxyprotocol: %d", sslhcfg_listen->proxyprotocol);
fprintf(out, "\n");
}
void sslhcfg_fprint(
@@ -2560,6 +2657,16 @@ void sslhcfg_fprint(
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "ech_conf_sink: %s", sslhcfg->ech_conf_sink);
if (! sslhcfg->ech_conf_sink_is_present)
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "ech_public_name: %s", sslhcfg->ech_public_name);
if (! sslhcfg->ech_public_name_is_present)
fprintf(out, " <unset>");
fprintf(out, "\n");
indent(out, depth);
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
fprintf(out, "\n");
indent(out, depth);
+12 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Sun Apr 6 11:44:58 2025.
* on Sat Jul 18 17:34:37 2026.
# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle
@@ -38,14 +38,18 @@
#include "probe.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <wolfssl/ssl.h>
#include <netdb.h>
struct sslhcfg_listen_item {
char* host;
char* port;
int max_connections_is_present;
int max_connections;
int is_udp;
int is_unix;
int keepalive;
int proxyprotocol;
};
struct sslhcfg_protocols_item {
@@ -63,6 +67,8 @@ struct sslhcfg_protocols_item {
int resolve_on_forward;
int log_level;
int keepalive;
int max_connections_is_present;
int max_connections;
size_t sni_hostnames_len;
char** sni_hostnames;
size_t alpn_protocols_len;
@@ -76,6 +82,7 @@ struct sslhcfg_protocols_item {
T_PROBE* probe;
struct addrinfo* saddr;
void* data;
unsigned int num_connections;
dl_list timeouts;
};
@@ -108,6 +115,10 @@ struct sslhcfg_item {
char* syslog_facility;
int logfile_is_present;
char* logfile;
int ech_conf_sink_is_present;
char* ech_conf_sink;
int ech_public_name_is_present;
char* ech_public_name;
char* on_timeout;
char* prefix;
size_t listen_len;
+83 -7
View File
@@ -45,6 +45,28 @@ static void cnx_read_cb(EV_P_ ev_io *w, int revents);
static void cnx_write_cb(EV_P_ ev_io *w, int wevents);
static void cnx_accept_cb(EV_P_ ev_io *w, int revents);
/*******************************************************************************/
/* Helper functions */
/* Enable all accept() watchers */
static void start_accept_watchers(watchers* w, int num_addr_listen)
{
for (int i = 0; i < num_addr_listen; i++) {
ev_io* io = gap_get(w->ev_ior, i);
ev_io_start(EV_A_ io);
}
}
/* Disable all accept() watchers */
static void stop_accept_watchers(watchers* w, int num_addr_listen)
{
for (int i = 0; i < num_addr_listen; i++) {
ev_io* io = gap_get(w->ev_ior, i);
ev_io_stop(EV_A_ io);
}
}
/*******************************************************************************/
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
int num_addr_listen)
@@ -60,11 +82,11 @@ static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
ev_io* io = malloc(sizeof(*io));
ev_io_init(io, &cnx_accept_cb, listen_sockets[i].socketfd, EV_READ);
ev_io_start(EV_A_ io);
gap_set((*w)->ev_ior, i, io);
gap_set((*w)->fd2ls, listen_sockets[i].socketfd, &listen_sockets[i]);
set_nonblock(listen_sockets[i].socketfd);
}
start_accept_watchers(*w, num_addr_listen);
}
void watchers_add_read(watchers* w, int fd)
@@ -122,22 +144,76 @@ static void cnx_write_cb(EV_P_ ev_io *w, int wevents)
cnx_write_process(info, w->fd);
}
/* Timer expired: destroy timer, restart the accept watchers */
static void restart_accept_cb( EV_P_ ev_timer *timer, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
ev_timer_stop(EV_A_ timer);
free(timer);
start_accept_watchers(info->watchers, info->num_addr_listen);
}
/* Stop the accept() io watchers, start a timer
*/
static void temporary_stop_accept_watchers(ev_io* io)
{
struct loop_info* info = ev_userdata(EV_A);
ev_timer* timer = malloc(sizeof(*timer));
stop_accept_watchers(info->watchers, info->num_addr_listen);
ev_timer_init(timer, &restart_accept_cb, 1, 0);
ev_timer_start(EV_A_ timer);
}
/* The accept() callback, called when there is READ activity.
*
* accept()ing when the process as reached its file descriptor limit (ulimit -n) is can of worms with no solution (see libev's manual "The special problem of accept()ing when you can't").
* When that happens, the strategy is: Disable all listening sockets, start a 1
* second timer, enable all listening sockets. This is terrible, but apparently
* the best we can do */
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
if (!cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd))) {
switch (errno) {
case EMFILE:
case ENFILE: /* Not sure ENFILE should be included here */
/* cancel accepting for a while */
temporary_stop_accept_watchers(w);
default:
break;
}
}
}
void sigchld_cb(EV_P_ ev_child *w, int revents)
{
struct loop_info* info = ev_userdata(EV_A);
ev_child_stop(EV_A_ w);
decrease_forked_connection(info, w->pid);
free(w);
}
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid)
{
ev_child* cw = malloc(sizeof(*cw));
ev_child_init(cw, sigchld_cb, pid, 0);
ev_child_start(EV_DEFAULT_ cw);
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
struct loop_info ev_info = {0};
struct loop_info ev_info;
loop = EV_DEFAULT;
ev_info.collection = collection_init(0);
ev_info.probing_list = gap_init(0);
udp_init(&ev_info);
tcp_init();
loop_init(&ev_info, num_addr_listen);
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info);
+49 -5
View File
@@ -1,7 +1,7 @@
/*
sslh-fork: forking server
# Copyright (C) 2007-2021 Yves Rutschle
# Copyright (C) 2007-2025 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@@ -160,6 +160,35 @@ void set_listen_procname(struct listen_endpoint *listen_socket)
#endif
}
static void mask_sigchld(void)
{
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = NULL;
action.sa_flags = SA_NOCLDWAIT;
int res = sigaction(SIGCHLD, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
}
extern volatile sig_atomic_t received_sigchld;
/* EINTR, so we probably received a signal; check if it's SIGCHLD */
static void process_signals(struct listen_endpoint* endpoint)
{
int chld;
if (received_sigchld) {
received_sigchld = 0;
do {
chld = waitpid(-1, NULL, WNOHANG);
CHECK_RES_RETURN(chld, "waitpid", );
if (chld) {
endpoint->num_connections--;
print_message(msg_fd, "child died, %d concurrent connections remaining\n", endpoint->num_connections);
}
} while (chld);
}
}
/* At least MacOS does not know these two options, so define them to something
* equivalent for our use case */
@@ -182,9 +211,9 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
while (1) {
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
if (in_socket == -1) {
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
__FILE__, __LINE__, "accept", errno, strerror(errno));
switch(in_socket) {
switch(errno) {
case ENETDOWN: /* accept(2) cites all these errnos as "you should retry" */
case EPROTO:
case ENOPROTOOPT:
@@ -196,11 +225,24 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
case ECONNABORTED:
continue;
case EINTR:
process_signals(endpoint);
continue;
default: /* Otherwise, it's something wrong in our parameters, we fail */
return;
}
}
print_message(msg_fd, "accepted fd %d\n", in_socket);
endpoint->num_connections++;
print_message(msg_fd, "accepted fd %d (%d concurrent connections)\n", in_socket, endpoint->num_connections);
if ((endpoint->endpoint_cfg->max_connections_is_present)
&& (endpoint->num_connections > endpoint->endpoint_cfg->max_connections))
{
print_message(msg_fd, "too many connection, reclosing fd %d\n", in_socket);
endpoint->num_connections--;
close(in_socket);
continue;
}
switch(fork()) {
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
@@ -250,8 +292,10 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
exit(0);
break;
/* We're in the parent, we don't need to do anything */
/* We're in the parent, which does nothing but wait to be killed by
* SIGTERM, which it will send to its children */
default:
mask_sigchld();
break;
}
}
+12 -7
View File
@@ -74,8 +74,11 @@ static void printsettings(void)
if (p->is_unix) {
sprintf(buf, "unix socket: %s", p->host);
} else {
strcpy(buf, "resolve on forward");
if (!p->resolve_on_forward) {
if (p->resolve_on_forward) {
strcpy(buf, "resolve on forward: ");
size_t len = strlen(buf);
sprintf(buf+len, "%s:%s", p->host, p->port);
} else {
sprintaddr(buf, sizeof(buf), p->saddr);
size_t len = strlen(buf);
sprintf(buf+len, " family %d %d",
@@ -84,14 +87,16 @@ static void printsettings(void)
}
}
print_message(msg_config,
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
"%s addr: %s. max_cnx: %d libwrap service: %s log_level: %d [%s] [%s] [%s] pp:%d\n",
p->name,
buf,
p->max_connections,
p->service,
p->log_level,
p->keepalive ? "keepalive" : "",
p->fork ? "fork" : "",
p->transparent ? "transparent" : ""
p->transparent ? "transparent" : "",
p->proxyprotocol_is_present ? p->proxyprotocol : -1
);
}
print_message(msg_config,
@@ -130,6 +135,7 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
exit(1);
}
}
probe_regex_matches = pcre2_match_data_create(1, NULL);
}
#else
{
@@ -280,9 +286,6 @@ void close_std(void)
int main(int argc, char *argv[], char* envp[])
{
extern char *optarg;
extern int optind;
int res, num_addr_listen;
struct listen_endpoint *listen_sockets;
@@ -344,6 +347,8 @@ int main(int argc, char *argv[], char* envp[])
/* Open log file for writing */
setup_logfile();
ech_init(listen_sockets, num_addr_listen);
if (cfg.user || cfg.chroot)
drop_privileges(cfg.user, cfg.chroot);
setup_landlock();
+100 -12
View File
@@ -88,9 +88,83 @@ void watchers_del_write(watchers* w, int fd)
FD_CLR(fd, &w->fds_w);
}
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid)
{
/* Nothing to do, watcher is processed in the event loop when select() is
* interrupted by the signal */
}
/* /end watchers */
/* Enable all accept() watchers */
static void start_accept_watchers(watchers* w, struct listen_endpoint* listen_sockets, int num_addr_listen)
{
for (int i = 0; i < num_addr_listen; i++) {
watchers_add_read(w, listen_sockets[i].socketfd);
}
}
/* Disable all accept() watchers */
static void stop_accept_watchers(watchers* w, struct listen_endpoint* listen_sockets, int num_addr_listen)
{
for (int i = 0; i < num_addr_listen; i++) {
watchers_del_read(w, listen_sockets[i].socketfd);
}
}
static void temporary_stop_accept_watchers(
watchers* w,
struct listen_endpoint* listen_sockets,
int num_addr_listen)
{
stop_accept_watchers(w, listen_sockets, num_addr_listen);
alarm(2);
}
/* Check if SIGCHLD was received, find which PID and reap appropriately */
extern volatile sig_atomic_t received_sigchld;
static void sigchld_process(struct loop_info* loop)
{
int chld;
if (received_sigchld) {
received_sigchld = 0;
do {
chld = waitpid(-1, NULL, WNOHANG);
if ((chld == -1) && (errno == ECHILD)) return;
CHECK_RES_RETURN(chld, "waitpid", );
if (chld) {
decrease_forked_connection(loop, chld);
}
} while (chld);
}
}
volatile sig_atomic_t received_sigalrm;
void sig_sigalrm(int sig)
{
received_sigalrm = 1;
}
static void setup_sigalrm(void)
{
int res;
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = sig_sigalrm;
res = sigaction(SIGALRM, &action, NULL);
CHECK_RES_DIE(res, "sigaction");
}
static void sigalrm_process(watchers* w, struct listen_endpoint* listen_sockets, int num_addr_listen)
{
if (received_sigalrm) {
received_sigalrm = 0;
start_accept_watchers(w, listen_sockets, num_addr_listen);
}
}
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
@@ -124,15 +198,13 @@ static int fd_out_of_range(int fd) {
*/
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
struct loop_info fd_info = {0};
struct loop_info fd_info;
fd_set readfds, writefds; /* working read and write fd sets */
struct timeval tv;
int i, res;
fd_info.num_probing = 0;
fd_info.probing_list = gap_init(0);
udp_init(&fd_info);
tcp_init();
setup_sigalrm();
loop_init(&fd_info, num_addr_listen);
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
@@ -150,19 +222,31 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
fd_info.watchers->max_fd, fd_info.num_probing);
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
NULL, fd_info.num_probing ? &tv : NULL);
if (res < 0)
perror("select");
if ((res == -1) && ((errno != EAGAIN) && (errno != EINTR)))
print_message(msg_system_error, "%s:%d:%d:%s\n",
__FILE__, __LINE__, errno, strerror(errno)); \
/* Check main socket for new connections */
for (i = 0; i < num_addr_listen; i++) {
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
struct connection* new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]);
if (fd_out_of_range(new_cnx->q[0].fd))
tidy_connection(new_cnx, &fd_info);
/* don't also process it as a read socket */
FD_CLR(listen_sockets[i].socketfd, &readfds);
struct connection* new_cnx;
while ((new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]))) {
if (fd_out_of_range(new_cnx->q[0].fd))
tidy_connection(new_cnx, &fd_info);
}
/* if accept() returns with ENFILE, disable listen for a second */
switch (errno) {
case EMFILE:
case ENFILE: /* Not sure ENFILE should be included here */
temporary_stop_accept_watchers(fd_info.watchers, listen_sockets, num_addr_listen);
break;
default: break;
}
}
}
@@ -200,6 +284,10 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
cnx_read_process(&fd_info, i);
}
}
/* Process if signals occured */
sigchld_process(&fd_info);
sigalrm_process(fd_info.watchers, listen_sockets, num_addr_listen);
}
}
+8 -1
View File
@@ -18,6 +18,7 @@ includes: (
"probe.h",
" <sys/types.h>",
" <sys/socket.h>",
" <wolfssl/ssl.h>",
" <netdb.h>"
);
@@ -85,6 +86,8 @@ config: {
description: "Facility to syslog to"; },
{ name: "logfile"; type: "string"; optional: true;
description: "Log messages to a file" },
{ name: "ech_conf_sink"; type: "string"; optional: true;
description: "File where to write ECH configurations" },
{ name: "on-timeout"; type: "string"; default: "ssh";
description: "Target to connect to when timing out"; },
@@ -97,9 +100,11 @@ config: {
items: (
{ name: "host"; type: "string"; var: true; },
{ name: "port"; type: "string"; var: true; },
{ name: "max_connections"; type: "int"; optional: true; },
{ name: "is_udp"; type: "bool"; default: false },
{ name: "is_unix"; type: "bool"; default: false },
{ name: "keepalive"; type: "bool"; default: false; }
{ name: "keepalive"; type: "bool"; default: false; },
{ name: "proxyprotocol"; type: "bool"; default: false; }
)
},
@@ -123,6 +128,7 @@ config: {
description: "Set to true if server address should be resolved on (every) newly incoming connection (again)" },
{ name: "log_level"; type: "int"; default: 1 },
{ name: "keepalive"; type: "bool"; default: false },
{ name: "max_connections"; type: "int"; optional: true },
{ name: "sni_hostnames",
type: "array",
element_type: "string"
@@ -142,6 +148,7 @@ config: {
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
{ name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" },
{ name: "data"; type: "runtime"; c_type: "void*" },
{ name: "num_connections"; type: "runtime"; c_type: "unsigned int" },
{ name: "timeouts"; type: "runtime"; c_type: "dl_list" }
)
}
Executable
+415
View File
@@ -0,0 +1,415 @@
#! /usr/bin/perl -w
# Test suite for sslh
# Uses Conf::Libconfig to read sslh config file: install
# with:
# cpan Conf::Libconfig
# Usage:
# ./run # run all tests
# ./run -l # list all tests
# ./run 1 3 5 # run specified tests
use strict;
use IO::Socket::INET6;
use Test::More qw/no_plan/;
use Conf::Libconfig 1.0.3;
use Getopt::Long;
my ($coverage, $list_tests);
GetOptions(
'cover' => \$coverage,
'list' => \$list_tests,
);
use Data::Dumper;
################################################################################
# Global setup
################################################################################
my $no_listen = 8083; # Port on which no-one listens
my $user = (getpwuid $<)[0]; # Run under current username
################################################################################
# Helper functions
################################################################################
sub get_conf {
my ($filename) = @_;
my $conf = new Conf::Libconfig;
$conf->read_file($filename) or die "$filename: $!";
return $conf;
}
# We want to keep track of tests to print a report at the
# end, so we centralise all calls to Test::More::is here
my $cnt = 1; # test counter
my @results;
sub my_is {
my ($a, $b, $desc) = @_;
my $res = is($a, $b, $desc);
push @results, [$cnt++, $desc, $res];
}
sub my_like {
my ($a, $b, $desc) = @_;
my $res = like($a, $b, $desc);
push @results, [$cnt++, $desc, $res];
}
sub verbose_exec
{
my ($cmd) = @_;
warn "$cmd\n";
if (!fork) {
exec $cmd;
}
}
# Returns a 'host:port' address targetting ssh from the configuration file
sub ssh_address {
my ($conf) = @_;
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->value("protocols")})[0];
return $ssh_conf->{host} . ":" . $ssh_conf->{port};
}
# Returns the last TLS target from the configuration file
sub ssl_address {
my ($conf) = @_;
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->value ("protocols")})[-1];
return $ssl_conf->{host} . ":" . $ssl_conf->{port};
}
# For SNI/ALPN, build a protocol name as such:
# tls:sni1,sni2,...;alpn1,alpn2,...
# input: a protocol entry from Libconfig
sub make_sni_alpn_name {
my ($prot) = @_;
return "tls:" . (join ",", @{$prot->{sni_hostnames} // []})
. ";" . (join ",", @{$prot->{alpn_protocols} // [] });
}
# Start an echoserver for each service
my ($started_echosrvs);
sub start_echosrv
{
my ($conf) = @_;
return if defined $started_echosrvs;
foreach my $s (@{$conf->value("protocols")}) {
my $prefix = $s->{name};
$prefix =~ s/^ssl/tls/;
if ($s->{sni_hostnames} or $s->{alpn_protocols}) {
$prefix = make_sni_alpn_name($s);
}
verbose_exec "../echosrv --listen $s->{host}:$s->{port} --prefix '$prefix: '";
}
$started_echosrvs = 1;
}
sub stop_echosrv
{
`killall echosrv` if $started_echosrvs;
undef $started_echosrvs;
}
# Runs sslh, restart it if configuration has changed
my ($sslh_pid, $sslh_conf);
sub start_sslh
{
my ($exe, $conf) = @_;
stop_sslh() if (defined $sslh_conf and ($conf ne $sslh_conf));
return if defined $sslh_pid;
$sslh_conf = $conf;
my ($valgrind);
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $cmd = "../$exe -F $conf";
print "****CMDLINE [$cmd]\n";
#$valgrind = 1;
#$cmd = "valgrind --leak-check=full $cmd";
verbose_exec $cmd;
exit 0;
}
sleep 1;
warn "spawned $sslh_pid\n";
sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds
}
#stop sslh
sub stop_sslh
{
if (defined $sslh_pid) {
kill TERM => $sslh_pid or warn "kill process: $!\n";
`killall sslh-ev sslh-select sslh-fork`;
undef $sslh_pid;
}
}
################################################################################
# Test methods
################################################################################
# runs a test that ends in sslh exiting early
# $cmdline: which command line to run
# $test: test object
# Returns: sslh exit code
sub run_ending_test
{
my ($exe, $params, $test) = @_;
my $conf = get_conf($test->{cfg});
my $ssh_address = ssh_address($conf);
my $ssl_address = ssl_address($conf);
my $pidfile = $conf->lookup_value("pidfile");
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $cmdline = "../$exe $params";
warn "eval `$cmdline`\n";
$cmdline = eval "\"$cmdline\"";
warn "running `$cmdline`\n";
exec "$cmdline";
}
warn "spawned $sslh_pid\n";
waitpid $sslh_pid, 0;
my $code = $? >> 8;
warn "exited with $code\n";
my_is($code, $test->{exit}, "$exe: $test->{desc}");
return $code;
}
# Runs one test for one probe. Start echosrv's if required.
# Connect to port specified in the test, otherwise to the
# first port in the sslh configuration.
# run sslh, connect, write the test pattern, read the result,
# check it connected to the right echosrv, check the data
# was transfered ok.
sub run_test_probe
{
my ($exe, $test) = @_;
my $conf = get_conf($test->{cfg});
start_echosrv($conf);
start_sslh($exe, $test->{cfg});
my $expected = $test->{expected};
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
print "test_probe [$expected] $sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "t: $!\n" unless $cnx;
return unless $cnx;
syswrite $cnx, $test->{data};
my $data;
my $n = sysread $cnx, $data, 1024;
$data =~ /^(.*?): /;
my $prefix = $1;
$data =~ s/$prefix: //g;
print "Received $n bytes: protocol $prefix data [$data]\n";
close $cnx;
my_is($prefix, $expected, "$exe:$test->{desc}:probe");
my $data_expected = $test->{data_expected} // "^$test->{data}\$";
$data_expected =~ s/INPUT_DATA/$test->{data}/;
my_like($data, qr/$data_expected/s, "$exe:$test->{desc}:shoveled");
}
################################################################################
# Test bodies
# Functions here each perform one test, and will be called with:
# - exe: path the executable to test
# - test: test object
################################################################################
sub test_no_host
{
my ($exe, $test) = @_;
run_ending_test(
$exe,
"-v 3 -f -u $user --listen $no_listen --ssh \$ssh_address --tls \$ssl_address -P \$pidfile",
$test,
);
}
sub test_wrong_user
{
my ($exe, $test) = @_;
run_ending_test(
$exe,
"-F $test->{cfg} -u ${user}_doesnt_exist --listen localhost:$no_listen --ssh \$ssh_address --tls \$ssl_address -P \$pidfile",
$test,
);
}
################################################################################
# Each test is decribed with an entry containing the
# following fields:
#
# * `run`: which test method to use
#
# * `expected`: protocol prompt added by the echosrv (should be the protocol name)
#
# * `data`: what is sent to sslh, as a client
#
# * `data_expected` is a regex for the expected return (which can be
# different when using Proxyprotocol). INPUT_DATA gets
# replaced with $_->{data}, i.e. data sent originally,
# before matching. Use `data` if not specified.
#
my @tests = (
{
desc => "No hostname in address",
run => \&test_no_host,
cfg => "test1.cfg",
exit => 6,
},
{
desc => "Changing to non-existant username",
run => \&test_wrong_user,
cfg => "test1.cfg",
exit => 2,
},
{
desc => "ssh probe",
run => \&run_test_probe,
cfg => "test1.cfg",
data => "SSH-2.0 Tester banner\n", # Sent to echosrv
expected => "ssh", # prefix expected from echosrv
},
{
desc => "socks5 probe",
run => \&run_test_probe,
cfg => "test1.cfg",
data => "\x05\x04\x01\x02\x03\x04",
expected => "socks5", # prefix expected from echosrv
},
# test_pp1.cfg has proxyprotocol on ssh but not socks5.
# Check ssh has pp
{
desc => "Server-side ProxyProtocol active",
run => \&run_test_probe,
cfg => "test_pp1.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
},
# check socks5 does not
{
desc => "Server-side ProxyProtocol not active",
run => \&run_test_probe,
cfg => "test_pp1.cfg",
data => "\x05\x04\x01\x02\x03\x04",
expected => "socks5",
},
# test_pp2.cfg has proxyprotocol on port 8080
{
desc => "Client-side ProxyProtocol active",
run => \&run_test_probe,
cfg => "test_pp2.cfg",
data => "PROXY TCP4 127.0.0.1 127.0.0.1 8080 8081\x0D\x0ASSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0ASSH-2.0 hello",
},
# test_pp2.cfg has no proxyprotocol on port 8081, so ssh
# should pass untouched
{
desc => "Client-side ProxyProtocol not active",
run => \&run_test_probe,
cfg => "test_pp2.cfg",
port => 8081,
data => "SSH-2.0 hello",
expected => "ssh",
},
# test_pp2.cfg has proxyprotocol on port 8080 but
# removes it towards "regex_remove_pp" tarbet
# TODO: Log the addresses!
{
desc => "Client-side ProxyProtocol active but removed",
run => \&run_test_probe,
cfg => "test_pp2.cfg",
data => "PROXY TCP4 127.0.0.1 127.0.0.1 8080 8081\x0D\x0Aregex_remove_pp hello",
expected => "regex",
data_expected => "^regex_remove_pp hello",
},
);
# Number tests
my $i = 1;
map { $_->{'index'} = $i++ } @tests;
# Only keep selected tests
@tests = @tests[ map { $_ - 1 } @ARGV ] if @ARGV;
################################################################################
#####List all tests
format test_list_top =
ID | Description
----+-------------------------------------------------------------------------
.
format test_list =
@>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$_->{'index'}, $_->{'desc'};
.
if ($list_tests) {
format_name STDOUT "test_list";
format_top_name STDOUT "test_list_top";
map { write; } @tests;
exit 0;
}
#####/list
################################################################################
# Run selected tests
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $binary = 'sslh-ev';
my $code = ($test->{run})->($binary, $test);
}
stop_echosrv();
stop_sslh();
done_testing();
format test_results_top =
ID | Description | Status
----+-------------------------------------------------------------------+-------
.
format test_results =
@>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @>>
$_->[0], $_->[1], $_->[2] ? "OK" : "NOK"
.
format_name STDOUT "test_results";
format_top_name STDOUT "test_results_top";
map { write; } @results;
+79
View File
@@ -0,0 +1,79 @@
# Configuration file for testing (use both by sslh under
# test and the test script `t`)
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; proxyprotocol: true; },
{ host: "localhost"; port: "8081"; keepalive: true; },
{ host: "ip4-localhost"; is_udp: true; port: "8086"; },
{ host: "/tmp/sslh.sock"; is_unix: true; port: ""; }
);
# Tester beware: when using fork, the forked process loses
# track of buffers of other, concurrent connections. Memory
# leak tools thus complain each time a forked process stops.
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "tinc"; host: "localhost"; port: "9003"; },
{ name: "openvpn"; host: "localhost"; port: "9004"; },
{ name: "xmpp"; host: "localhost"; port: "9009"; },
{ name: "adb"; host: "localhost"; port: "9010"; },
{ name: "syslog"; host: "localhost"; port: "9013"; },
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
udp_timeout: 30;
regex_patterns: [ "^foo" ];
resolve_on_forward: true;
},
{ name: "regex"; host: "localhost"; port: "9011";
regex_patterns: [ "^foo", "^bar" ];
minlength: 4;
test_patterns: ( # this is used by the test script, not by sslh
{ pattern: "foo"; result: "ssh"; }, # After timeout
{ pattern: "fooo"; result: "regex"; },
{ pattern: "bar"; result: "ssh"; },
{ pattern: "barr"; result: "regex"; },
{ pattern: "barrrr"; result: "regex"; }
);
},
{ name: "tls"; host: "localhost"; port: "9021"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni1" ]; },
{ name: "tls"; host: "localhost"; port: "9022"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni2", "sni3" ]; },
{ name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; },
{ name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; },
{ name: "tls"; host: "localhost"; port: "9025"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
+44
View File
@@ -0,0 +1,44 @@
# Testing for server-side ProxyProtocol
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
+47
View File
@@ -0,0 +1,47 @@
# Testing for client-side ProxyProtocol
foreground: true;
inetd: false;
numeric: true;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# Logging configuration
# Value: 1: stdout; 2: syslog; 3: both
# Defaults should be sensible. Generally, you want *-error
# to be always enabled, to know if something is going wrong.
verbose-config: 1; # print configuration at startup
verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; proxyprotocol: true; },
{ host: "localhost"; port: "8081"; keepalive: true; proxyprotocol: false; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "regex"; host: "localhost"; port: "9002";
regex_patterns: [ "^regex_remove_pp" ]; proxyprotocol: 0; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
+9 -8
View File
@@ -9,7 +9,7 @@
use strict;
use IO::Socket::INET6;
use Test::More qw/no_plan/;
use Conf::Libconfig;
use Conf::Libconfig 1.0.3;
my $conf = new Conf::Libconfig;
$conf->read_file("test.cfg");
@@ -17,7 +17,7 @@ $conf->read_file("test.cfg");
my $no_listen = 8083; # Port on which no-one listens
my $pidfile = $conf->lookup_value("pidfile");
my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
my $sslh_port = $conf->value("listen")->[0]->{port};
my $user = (getpwuid $<)[0]; # Run under current username
# Which tests do we run
@@ -84,8 +84,9 @@ sub make_sni_alpn_name {
sub test_probe {
my (%opts) = @_;
print "test_probe [$opts{expected}] $sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx;
warn "t: $!\n" unless $cnx;
return unless $cnx;
my $pattern = $opts{data};
@@ -119,7 +120,7 @@ sub test_probe {
sub test_probes {
my (%in_opts) = @_;
my @probes = @{$conf->fetch_array("protocols")};
my @probes = @{$conf->value("protocols")};
foreach my $p (@probes) {
my %protocols = (
'ssh' => { data => "SSH-2.0 tester" },
@@ -194,7 +195,7 @@ sub test_probes {
# Start an echoserver for each service
foreach my $s (@{$conf->fetch_array("protocols")}) {
foreach my $s (@{$conf->value("protocols")}) {
my $prefix = $s->{name};
$prefix =~ s/^ssl/tls/; # To remove in 1.21
@@ -217,7 +218,7 @@ for my $binary (@binaries) {
my ($sslh_pid, $valgrind);
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
my $cmd = "./$binary -v 4 -f -u $user -F test.cfg";
my $cmd = "./$binary -u $user -F test.cfg";
#$valgrind = 1;
#$cmd = "valgrind --leak-check=full $cmd";
verbose_exec $cmd;
@@ -339,11 +340,11 @@ if ($RB_CNX_NOSERVER) {
}
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0];
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->value("protocols")})[0];
my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port};
# Use the last TLS echoserv (no SNI/ALPN)
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1];
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->value ("protocols")})[-1];
my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port};
+82 -21
View File
@@ -25,6 +25,8 @@
#include "probe.h"
#include "log.h"
/* Removes cnx from probing list */
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
{
@@ -118,16 +120,52 @@ void tcp_read_process(struct loop_info* fd_info,
}
}
/* *cnx must have its endpoint field filled;
* Increment the connection count for the listen endpoint.
* Return 1 if connection count is exceeded, 0 otherwise */
static int inc_listen_connections(struct listen_endpoint* endpoint)
{
endpoint->num_connections++;
if (endpoint->endpoint_cfg->max_connections_is_present) {
int num_cnx = endpoint->num_connections;
int max_cnx = endpoint->endpoint_cfg->max_connections;
print_message(msg_connections, "Endpoint %d +1: %d/%d cnx\n",
endpoint->socketfd, num_cnx, max_cnx);
if (num_cnx > max_cnx) {
print_message(msg_connections_error, "Endpoint %d: too many connections, dropping\n", endpoint->socketfd);
return 1;
}
}
return 0;
}
void dec_listen_connections(struct listen_endpoint* endpoint)
{
if (endpoint) {
endpoint->num_connections--;
print_message(msg_connections, "Endpoint %d -1: %d/%d cnx\n",
endpoint->socketfd,
endpoint->num_connections,
endpoint->endpoint_cfg->max_connections);
}
}
/* Accepts a connection from the main socket and assigns it to an empty slot.
* If no slots are available, allocate another few. If that fails, drop the
* connexion */
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info)
struct connection* accept_new_connection(struct listen_endpoint* endpoint, struct loop_info* fd_info)
{
int in_socket, res;
int listen_socket = endpoint->socketfd;
print_message(msg_fd, "accepting from %d\n", listen_socket);
in_socket = accept(listen_socket, 0, 0);
if ((in_socket == -1) && (errno == EAGAIN)) return NULL; /* Do not log if we're just retring next iteration */
CHECK_RES_RETURN(in_socket, "accept", NULL);
res = set_nonblock(in_socket);
@@ -141,12 +179,16 @@ struct connection* accept_new_connection(int listen_socket, struct loop_info* fd
close(in_socket);
return NULL;
}
cnx->endpoint = endpoint;
if (inc_listen_connections(endpoint)) {
tidy_connection(cnx, fd_info);
return NULL;
}
add_probing_cnx(fd_info, cnx);
return cnx;
}
/* Connect queue 1 of connection to SSL; returns new file descriptor */
static int connect_queue(struct connection* cnx,
struct loop_info* fd_info)
@@ -253,6 +295,31 @@ static void connect_proxy(struct connection *cnx)
exit(0);
}
/* Forks a shoveler process for one protocol */
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
pid_t pid;
if (!has_space_to_fork(fd_info)) {
print_message(msg_connections_error, "forking for %s: out of hash space\n", cnx->proto->name);
return;
}
switch (pid = fork()) {
case 0: /* child */
/* TODO: close all file descriptors except 2 */
/* free(cnx); */
connect_proxy(cnx);
exit(0);
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
default: /* parent */
remember_child_data(fd_info, cnx, pid);
watcher_sigchld(fd_info, cnx, pid);
break;
}
}
/* Process read activity on a socket in probe state
* IN/OUT cnx: connection data, updated if connected
* IN/OUT info: updated if connected
@@ -260,43 +327,37 @@ static void connect_proxy(struct connection *cnx)
void probing_read_process(struct connection* cnx,
struct loop_info* fd_info)
{
int res;
/* If timed out it's SSH, otherwise the client sent
* data so probe the protocol */
if ((cnx->probe_timeout < time(NULL))) {
cnx->proto = timeout_protocol();
print_message(msg_fd, "timed out, connect to %s\n", cnx->proto->name);
} else {
res = probe_client_protocol(cnx);
if (res == PROBE_AGAIN)
return;
if (probe_client_protocol(cnx) == PROBE_AGAIN)
return; /* Not enough data, wait for more */
}
remove_probing_cnx(fd_info, cnx);
cnx->state = ST_SHOVELING;
if (inc_proto_connections(cnx->proto)) {
tidy_connection(cnx, fd_info);
return;
}
/* libwrap check if required for this protocol */
if (cnx->proto->service &&
check_access_rights(cnx->q[0].fd, cnx->proto->service)) {
tidy_connection(cnx, fd_info);
res = -1;
} else if (cnx->proto->fork) {
switch (fork()) {
case 0: /* child */
/* TODO: close all file descriptors except 2 */
/* free(cnx); */
connect_proxy(cnx);
exit(0);
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
break;
default: /* parent */
break;
}
fork_shoveling_process(fd_info, cnx);
/* Free file descriptor (used only in child), but do not reduce connection
* counts */
cnx->proto = NULL;
cnx->endpoint = NULL;
tidy_connection(cnx, fd_info);
res = -1;
} else {
res = connect_queue(cnx, fd_info);
connect_queue(cnx, fd_info);
}
}
+2 -1
View File
@@ -6,8 +6,9 @@
#include "tcp-probe.h"
void tcp_read_process(struct loop_info* fd_info, int fd);
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info);
struct connection* accept_new_connection(struct listen_endpoint* endpoint, struct loop_info* fd_info);
void probing_read_process(struct connection* cnx, struct loop_info* fd_info);
void cnx_write_process(struct loop_info* fd_info, int fd);
void dec_listen_connections(struct listen_endpoint*);
#endif
+13 -2
View File
@@ -45,8 +45,19 @@ int probe_client_protocol(struct connection *cnx)
if (n > 0) {
defer_write(&cnx->q[1], buffer, n);
return probe_buffer(cnx->q[1].begin_deferred_data,
cnx->q[1].deferred_data_size,
print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size);
size_t pp_len = 0;
if (cnx->endpoint->endpoint_cfg->proxyprotocol) {
pp_len = pp_header_len(cnx->q[1].begin_deferred_data,
cnx->q[1].deferred_data_size);
}
return probe_buffer(cnx->q[1].begin_deferred_data + pp_len,
cnx->q[1].deferred_data_size - pp_len,
tcp_protocols, tcp_protocols_len,
&cnx->proto
);
+14 -11
View File
@@ -19,9 +19,9 @@ verbose-config-error: 1; # print configuration errors
verbose-connections: 1; # trace established incoming address to forward address
verbose-connections-error: 1; # connection errors
verbose-connections-try: 1; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-fd: 1; # file descriptor activity, open/close/whatnot
verbose-packets: 1; # hexdump packets on which probing is done
verbose-probe-info: 0; # what's happening during the probe process
verbose-probe-info: 1; # what's happening during the probe process
verbose-probe-error: 1; # failures and problems during probing
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
verbose-int-error: 1; # internal errors, the kind that should never happen
@@ -30,9 +30,10 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; },
{ host: "localhost"; port: "8080"; keepalive: true;
max_connections: 2; proxyprotocol: true; },
{ host: "localhost"; port: "8081"; keepalive: true; },
{ host: "ip4-localhost"; is_udp: true; port: "8086"; },
# { host: "ip4-localhost"; is_udp: true; port: "8086"; },
{ host: "/tmp/sslh.sock"; is_unix: true; port: ""; }
);
@@ -43,19 +44,20 @@ listen:
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; max_connections: 4; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "http"; host: "localhost"; port: "80"; proxyprotocol: 2; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "tinc"; host: "localhost"; port: "9003"; },
{ name: "openvpn"; host: "localhost"; port: "9004"; },
{ name: "xmpp"; host: "localhost"; port: "9009"; },
{ name: "adb"; host: "localhost"; port: "9010"; },
{ name: "syslog"; host: "localhost"; port: "9013"; },
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
udp_timeout: 30;
regex_patterns: [ "^foo" ];
resolve_on_forward: true;
},
# { name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
# max_connections: 1;
# udp_timeout: 30;
# regex_patterns: [ "^foo" ];
# resolve_on_forward: true;
# },
{ name: "regex"; host: "localhost"; port: "9011";
regex_patterns: [ "^foo", "^bar" ];
minlength: 4;
@@ -72,6 +74,7 @@ protocols:
{ name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; },
{ name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; },
{ name: "tls"; host: "localhost"; port: "9025"; },
# { name: "anyprot"; host: "ip4-localhost"; is_udp: true; port: "9999"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
+7
View File
@@ -0,0 +1,7 @@
CFLAGS=-DHASH_TESTING -O2 -Wall
OBJ=../gap.o gtest.o
gtest: $(OBJ)
$(CC) -o gtest $(OBJ)
+102
View File
@@ -0,0 +1,102 @@
/* Wee testing program from the gap code, this is very similar to the hash
* testing code.
* Instead of pointers, we use integers
*
* script language:
* a 5 12 # adds value 12 at index 5
* d 512 42 # remove value 42, 512 is the length of the array
* h 16 0 # set hard limit to 16 (value is not used)
*/
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "../gap.h"
static void gtest_next_line(FILE* f, char* action, int* index, void** value)
{
int res = 0;
while ((res != 3) && (res != EOF))
res = fscanf(f, "%c %d %p\n", action, index, value);
if (res == EOF) exit(0);
}
static void gap_dump(gap_array* gap, char* filename)
{
int i;
FILE* out = fopen(filename, "w");
if (!out) {
perror(filename);
exit(1);
}
fprintf(out, "<gap len=%d hardlimit=%d>\n", gap->len, gap->hardlimit);
for (i = 0; i < gap->len; i++) {
void* value = gap_get(gap, i);
fprintf(out, "[%d] = %p\n", i, value);
}
fprintf(out, "</gap>\n");
fclose(out);
}
int main(int argc, char* argv[])
{
gap_array* gap = gap_init(0);
char action;
int line = 0, index;
void* value;
FILE* f;
if (argc != 3) {
fprintf(stderr, "Usage: gtest <script file> <dump file>\n");
exit(1);
}
char* script_file = argv[1];
char* dump_file = argv[2];
f = fopen(argv[1], "r");
if (!f) {
perror(script_file);
exit(1);
}
while (1) {
action = ' ';
line++;
gtest_next_line(f, &action, &index, &value);
fprintf(stderr, "action %d: %c %d %p\n", line, action, index, value);
switch (action) {
case 'a': /* add */
fprintf(stderr, "[%d] = %p\n", index, value);
gap_set(gap, index, value);
break;
case 'd': /* del */
fprintf(stderr, "removing %p\n", value);
int res = gap_remove_ptr(gap, (void*)value, index);
fprintf(stderr, "remove: %d\n", res);
break;
case 'h': /* hardlimit */
fprintf(stderr, "setting hard limit %d\n", index);
gap_set_hardlimit(gap, index);
case 'g': /* get */
fprintf(stderr, "searching\n");
value = gap_get(gap, index);
fprintf(stderr, "got [%d]= %p\n", index, value);
break;
}
gap_dump(gap, dump_file);
}
return 0;
}
+11
View File
@@ -0,0 +1,11 @@
# Test hard limit
h 15 0 # set hard limit
a 5 42
a 6 43
a 15 44
a 16 45
a 17 46
+514
View File
@@ -0,0 +1,514 @@
<gap len=512 hardlimit=15>
[0] = (nil)
[1] = (nil)
[2] = (nil)
[3] = (nil)
[4] = (nil)
[5] = 0x42
[6] = 0x43
[7] = (nil)
[8] = (nil)
[9] = (nil)
[10] = (nil)
[11] = (nil)
[12] = (nil)
[13] = (nil)
[14] = (nil)
[15] = 0x44
[16] = (nil)
[17] = (nil)
[18] = (nil)
[19] = (nil)
[20] = (nil)
[21] = (nil)
[22] = (nil)
[23] = (nil)
[24] = (nil)
[25] = (nil)
[26] = (nil)
[27] = (nil)
[28] = (nil)
[29] = (nil)
[30] = (nil)
[31] = (nil)
[32] = (nil)
[33] = (nil)
[34] = (nil)
[35] = (nil)
[36] = (nil)
[37] = (nil)
[38] = (nil)
[39] = (nil)
[40] = (nil)
[41] = (nil)
[42] = (nil)
[43] = (nil)
[44] = (nil)
[45] = (nil)
[46] = (nil)
[47] = (nil)
[48] = (nil)
[49] = (nil)
[50] = (nil)
[51] = (nil)
[52] = (nil)
[53] = (nil)
[54] = (nil)
[55] = (nil)
[56] = (nil)
[57] = (nil)
[58] = (nil)
[59] = (nil)
[60] = (nil)
[61] = (nil)
[62] = (nil)
[63] = (nil)
[64] = (nil)
[65] = (nil)
[66] = (nil)
[67] = (nil)
[68] = (nil)
[69] = (nil)
[70] = (nil)
[71] = (nil)
[72] = (nil)
[73] = (nil)
[74] = (nil)
[75] = (nil)
[76] = (nil)
[77] = (nil)
[78] = (nil)
[79] = (nil)
[80] = (nil)
[81] = (nil)
[82] = (nil)
[83] = (nil)
[84] = (nil)
[85] = (nil)
[86] = (nil)
[87] = (nil)
[88] = (nil)
[89] = (nil)
[90] = (nil)
[91] = (nil)
[92] = (nil)
[93] = (nil)
[94] = (nil)
[95] = (nil)
[96] = (nil)
[97] = (nil)
[98] = (nil)
[99] = (nil)
[100] = (nil)
[101] = (nil)
[102] = (nil)
[103] = (nil)
[104] = (nil)
[105] = (nil)
[106] = (nil)
[107] = (nil)
[108] = (nil)
[109] = (nil)
[110] = (nil)
[111] = (nil)
[112] = (nil)
[113] = (nil)
[114] = (nil)
[115] = (nil)
[116] = (nil)
[117] = (nil)
[118] = (nil)
[119] = (nil)
[120] = (nil)
[121] = (nil)
[122] = (nil)
[123] = (nil)
[124] = (nil)
[125] = (nil)
[126] = (nil)
[127] = (nil)
[128] = (nil)
[129] = (nil)
[130] = (nil)
[131] = (nil)
[132] = (nil)
[133] = (nil)
[134] = (nil)
[135] = (nil)
[136] = (nil)
[137] = (nil)
[138] = (nil)
[139] = (nil)
[140] = (nil)
[141] = (nil)
[142] = (nil)
[143] = (nil)
[144] = (nil)
[145] = (nil)
[146] = (nil)
[147] = (nil)
[148] = (nil)
[149] = (nil)
[150] = (nil)
[151] = (nil)
[152] = (nil)
[153] = (nil)
[154] = (nil)
[155] = (nil)
[156] = (nil)
[157] = (nil)
[158] = (nil)
[159] = (nil)
[160] = (nil)
[161] = (nil)
[162] = (nil)
[163] = (nil)
[164] = (nil)
[165] = (nil)
[166] = (nil)
[167] = (nil)
[168] = (nil)
[169] = (nil)
[170] = (nil)
[171] = (nil)
[172] = (nil)
[173] = (nil)
[174] = (nil)
[175] = (nil)
[176] = (nil)
[177] = (nil)
[178] = (nil)
[179] = (nil)
[180] = (nil)
[181] = (nil)
[182] = (nil)
[183] = (nil)
[184] = (nil)
[185] = (nil)
[186] = (nil)
[187] = (nil)
[188] = (nil)
[189] = (nil)
[190] = (nil)
[191] = (nil)
[192] = (nil)
[193] = (nil)
[194] = (nil)
[195] = (nil)
[196] = (nil)
[197] = (nil)
[198] = (nil)
[199] = (nil)
[200] = (nil)
[201] = (nil)
[202] = (nil)
[203] = (nil)
[204] = (nil)
[205] = (nil)
[206] = (nil)
[207] = (nil)
[208] = (nil)
[209] = (nil)
[210] = (nil)
[211] = (nil)
[212] = (nil)
[213] = (nil)
[214] = (nil)
[215] = (nil)
[216] = (nil)
[217] = (nil)
[218] = (nil)
[219] = (nil)
[220] = (nil)
[221] = (nil)
[222] = (nil)
[223] = (nil)
[224] = (nil)
[225] = (nil)
[226] = (nil)
[227] = (nil)
[228] = (nil)
[229] = (nil)
[230] = (nil)
[231] = (nil)
[232] = (nil)
[233] = (nil)
[234] = (nil)
[235] = (nil)
[236] = (nil)
[237] = (nil)
[238] = (nil)
[239] = (nil)
[240] = (nil)
[241] = (nil)
[242] = (nil)
[243] = (nil)
[244] = (nil)
[245] = (nil)
[246] = (nil)
[247] = (nil)
[248] = (nil)
[249] = (nil)
[250] = (nil)
[251] = (nil)
[252] = (nil)
[253] = (nil)
[254] = (nil)
[255] = (nil)
[256] = (nil)
[257] = (nil)
[258] = (nil)
[259] = (nil)
[260] = (nil)
[261] = (nil)
[262] = (nil)
[263] = (nil)
[264] = (nil)
[265] = (nil)
[266] = (nil)
[267] = (nil)
[268] = (nil)
[269] = (nil)
[270] = (nil)
[271] = (nil)
[272] = (nil)
[273] = (nil)
[274] = (nil)
[275] = (nil)
[276] = (nil)
[277] = (nil)
[278] = (nil)
[279] = (nil)
[280] = (nil)
[281] = (nil)
[282] = (nil)
[283] = (nil)
[284] = (nil)
[285] = (nil)
[286] = (nil)
[287] = (nil)
[288] = (nil)
[289] = (nil)
[290] = (nil)
[291] = (nil)
[292] = (nil)
[293] = (nil)
[294] = (nil)
[295] = (nil)
[296] = (nil)
[297] = (nil)
[298] = (nil)
[299] = (nil)
[300] = (nil)
[301] = (nil)
[302] = (nil)
[303] = (nil)
[304] = (nil)
[305] = (nil)
[306] = (nil)
[307] = (nil)
[308] = (nil)
[309] = (nil)
[310] = (nil)
[311] = (nil)
[312] = (nil)
[313] = (nil)
[314] = (nil)
[315] = (nil)
[316] = (nil)
[317] = (nil)
[318] = (nil)
[319] = (nil)
[320] = (nil)
[321] = (nil)
[322] = (nil)
[323] = (nil)
[324] = (nil)
[325] = (nil)
[326] = (nil)
[327] = (nil)
[328] = (nil)
[329] = (nil)
[330] = (nil)
[331] = (nil)
[332] = (nil)
[333] = (nil)
[334] = (nil)
[335] = (nil)
[336] = (nil)
[337] = (nil)
[338] = (nil)
[339] = (nil)
[340] = (nil)
[341] = (nil)
[342] = (nil)
[343] = (nil)
[344] = (nil)
[345] = (nil)
[346] = (nil)
[347] = (nil)
[348] = (nil)
[349] = (nil)
[350] = (nil)
[351] = (nil)
[352] = (nil)
[353] = (nil)
[354] = (nil)
[355] = (nil)
[356] = (nil)
[357] = (nil)
[358] = (nil)
[359] = (nil)
[360] = (nil)
[361] = (nil)
[362] = (nil)
[363] = (nil)
[364] = (nil)
[365] = (nil)
[366] = (nil)
[367] = (nil)
[368] = (nil)
[369] = (nil)
[370] = (nil)
[371] = (nil)
[372] = (nil)
[373] = (nil)
[374] = (nil)
[375] = (nil)
[376] = (nil)
[377] = (nil)
[378] = (nil)
[379] = (nil)
[380] = (nil)
[381] = (nil)
[382] = (nil)
[383] = (nil)
[384] = (nil)
[385] = (nil)
[386] = (nil)
[387] = (nil)
[388] = (nil)
[389] = (nil)
[390] = (nil)
[391] = (nil)
[392] = (nil)
[393] = (nil)
[394] = (nil)
[395] = (nil)
[396] = (nil)
[397] = (nil)
[398] = (nil)
[399] = (nil)
[400] = (nil)
[401] = (nil)
[402] = (nil)
[403] = (nil)
[404] = (nil)
[405] = (nil)
[406] = (nil)
[407] = (nil)
[408] = (nil)
[409] = (nil)
[410] = (nil)
[411] = (nil)
[412] = (nil)
[413] = (nil)
[414] = (nil)
[415] = (nil)
[416] = (nil)
[417] = (nil)
[418] = (nil)
[419] = (nil)
[420] = (nil)
[421] = (nil)
[422] = (nil)
[423] = (nil)
[424] = (nil)
[425] = (nil)
[426] = (nil)
[427] = (nil)
[428] = (nil)
[429] = (nil)
[430] = (nil)
[431] = (nil)
[432] = (nil)
[433] = (nil)
[434] = (nil)
[435] = (nil)
[436] = (nil)
[437] = (nil)
[438] = (nil)
[439] = (nil)
[440] = (nil)
[441] = (nil)
[442] = (nil)
[443] = (nil)
[444] = (nil)
[445] = (nil)
[446] = (nil)
[447] = (nil)
[448] = (nil)
[449] = (nil)
[450] = (nil)
[451] = (nil)
[452] = (nil)
[453] = (nil)
[454] = (nil)
[455] = (nil)
[456] = (nil)
[457] = (nil)
[458] = (nil)
[459] = (nil)
[460] = (nil)
[461] = (nil)
[462] = (nil)
[463] = (nil)
[464] = (nil)
[465] = (nil)
[466] = (nil)
[467] = (nil)
[468] = (nil)
[469] = (nil)
[470] = (nil)
[471] = (nil)
[472] = (nil)
[473] = (nil)
[474] = (nil)
[475] = (nil)
[476] = (nil)
[477] = (nil)
[478] = (nil)
[479] = (nil)
[480] = (nil)
[481] = (nil)
[482] = (nil)
[483] = (nil)
[484] = (nil)
[485] = (nil)
[486] = (nil)
[487] = (nil)
[488] = (nil)
[489] = (nil)
[490] = (nil)
[491] = (nil)
[492] = (nil)
[493] = (nil)
[494] = (nil)
[495] = (nil)
[496] = (nil)
[497] = (nil)
[498] = (nil)
[499] = (nil)
[500] = (nil)
[501] = (nil)
[502] = (nil)
[503] = (nil)
[504] = (nil)
[505] = (nil)
[506] = (nil)
[507] = (nil)
[508] = (nil)
[509] = (nil)
[510] = (nil)
[511] = (nil)
</gap>
+5
View File
@@ -0,0 +1,5 @@
# Insertions after the first page, to extend the gap
a 5 42
a 1024 43
a 1028 44
File diff suppressed because it is too large. Load diff
+6
View File
@@ -0,0 +1,6 @@
# Simple insertions in the first page
a 5 42
a 10 43
+514
View File
@@ -0,0 +1,514 @@
<gap len=512 hardlimit=0>
[0] = (nil)
[1] = (nil)
[2] = (nil)
[3] = (nil)
[4] = (nil)
[5] = 0x42
[6] = (nil)
[7] = (nil)
[8] = (nil)
[9] = (nil)
[10] = 0x43
[11] = (nil)
[12] = (nil)
[13] = (nil)
[14] = (nil)
[15] = (nil)
[16] = (nil)
[17] = (nil)
[18] = (nil)
[19] = (nil)
[20] = (nil)
[21] = (nil)
[22] = (nil)
[23] = (nil)
[24] = (nil)
[25] = (nil)
[26] = (nil)
[27] = (nil)
[28] = (nil)
[29] = (nil)
[30] = (nil)
[31] = (nil)
[32] = (nil)
[33] = (nil)
[34] = (nil)
[35] = (nil)
[36] = (nil)
[37] = (nil)
[38] = (nil)
[39] = (nil)
[40] = (nil)
[41] = (nil)
[42] = (nil)
[43] = (nil)
[44] = (nil)
[45] = (nil)
[46] = (nil)
[47] = (nil)
[48] = (nil)
[49] = (nil)
[50] = (nil)
[51] = (nil)
[52] = (nil)
[53] = (nil)
[54] = (nil)
[55] = (nil)
[56] = (nil)
[57] = (nil)
[58] = (nil)
[59] = (nil)
[60] = (nil)
[61] = (nil)
[62] = (nil)
[63] = (nil)
[64] = (nil)
[65] = (nil)
[66] = (nil)
[67] = (nil)
[68] = (nil)
[69] = (nil)
[70] = (nil)
[71] = (nil)
[72] = (nil)
[73] = (nil)
[74] = (nil)
[75] = (nil)
[76] = (nil)
[77] = (nil)
[78] = (nil)
[79] = (nil)
[80] = (nil)
[81] = (nil)
[82] = (nil)
[83] = (nil)
[84] = (nil)
[85] = (nil)
[86] = (nil)
[87] = (nil)
[88] = (nil)
[89] = (nil)
[90] = (nil)
[91] = (nil)
[92] = (nil)
[93] = (nil)
[94] = (nil)
[95] = (nil)
[96] = (nil)
[97] = (nil)
[98] = (nil)
[99] = (nil)
[100] = (nil)
[101] = (nil)
[102] = (nil)
[103] = (nil)
[104] = (nil)
[105] = (nil)
[106] = (nil)
[107] = (nil)
[108] = (nil)
[109] = (nil)
[110] = (nil)
[111] = (nil)
[112] = (nil)
[113] = (nil)
[114] = (nil)
[115] = (nil)
[116] = (nil)
[117] = (nil)
[118] = (nil)
[119] = (nil)
[120] = (nil)
[121] = (nil)
[122] = (nil)
[123] = (nil)
[124] = (nil)
[125] = (nil)
[126] = (nil)
[127] = (nil)
[128] = (nil)
[129] = (nil)
[130] = (nil)
[131] = (nil)
[132] = (nil)
[133] = (nil)
[134] = (nil)
[135] = (nil)
[136] = (nil)
[137] = (nil)
[138] = (nil)
[139] = (nil)
[140] = (nil)
[141] = (nil)
[142] = (nil)
[143] = (nil)
[144] = (nil)
[145] = (nil)
[146] = (nil)
[147] = (nil)
[148] = (nil)
[149] = (nil)
[150] = (nil)
[151] = (nil)
[152] = (nil)
[153] = (nil)
[154] = (nil)
[155] = (nil)
[156] = (nil)
[157] = (nil)
[158] = (nil)
[159] = (nil)
[160] = (nil)
[161] = (nil)
[162] = (nil)
[163] = (nil)
[164] = (nil)
[165] = (nil)
[166] = (nil)
[167] = (nil)
[168] = (nil)
[169] = (nil)
[170] = (nil)
[171] = (nil)
[172] = (nil)
[173] = (nil)
[174] = (nil)
[175] = (nil)
[176] = (nil)
[177] = (nil)
[178] = (nil)
[179] = (nil)
[180] = (nil)
[181] = (nil)
[182] = (nil)
[183] = (nil)
[184] = (nil)
[185] = (nil)
[186] = (nil)
[187] = (nil)
[188] = (nil)
[189] = (nil)
[190] = (nil)
[191] = (nil)
[192] = (nil)
[193] = (nil)
[194] = (nil)
[195] = (nil)
[196] = (nil)
[197] = (nil)
[198] = (nil)
[199] = (nil)
[200] = (nil)
[201] = (nil)
[202] = (nil)
[203] = (nil)
[204] = (nil)
[205] = (nil)
[206] = (nil)
[207] = (nil)
[208] = (nil)
[209] = (nil)
[210] = (nil)
[211] = (nil)
[212] = (nil)
[213] = (nil)
[214] = (nil)
[215] = (nil)
[216] = (nil)
[217] = (nil)
[218] = (nil)
[219] = (nil)
[220] = (nil)
[221] = (nil)
[222] = (nil)
[223] = (nil)
[224] = (nil)
[225] = (nil)
[226] = (nil)
[227] = (nil)
[228] = (nil)
[229] = (nil)
[230] = (nil)
[231] = (nil)
[232] = (nil)
[233] = (nil)
[234] = (nil)
[235] = (nil)
[236] = (nil)
[237] = (nil)
[238] = (nil)
[239] = (nil)
[240] = (nil)
[241] = (nil)
[242] = (nil)
[243] = (nil)
[244] = (nil)
[245] = (nil)
[246] = (nil)
[247] = (nil)
[248] = (nil)
[249] = (nil)
[250] = (nil)
[251] = (nil)
[252] = (nil)
[253] = (nil)
[254] = (nil)
[255] = (nil)
[256] = (nil)
[257] = (nil)
[258] = (nil)
[259] = (nil)
[260] = (nil)
[261] = (nil)
[262] = (nil)
[263] = (nil)
[264] = (nil)
[265] = (nil)
[266] = (nil)
[267] = (nil)
[268] = (nil)
[269] = (nil)
[270] = (nil)
[271] = (nil)
[272] = (nil)
[273] = (nil)
[274] = (nil)
[275] = (nil)
[276] = (nil)
[277] = (nil)
[278] = (nil)
[279] = (nil)
[280] = (nil)
[281] = (nil)
[282] = (nil)
[283] = (nil)
[284] = (nil)
[285] = (nil)
[286] = (nil)
[287] = (nil)
[288] = (nil)
[289] = (nil)
[290] = (nil)
[291] = (nil)
[292] = (nil)
[293] = (nil)
[294] = (nil)
[295] = (nil)
[296] = (nil)
[297] = (nil)
[298] = (nil)
[299] = (nil)
[300] = (nil)
[301] = (nil)
[302] = (nil)
[303] = (nil)
[304] = (nil)
[305] = (nil)
[306] = (nil)
[307] = (nil)
[308] = (nil)
[309] = (nil)
[310] = (nil)
[311] = (nil)
[312] = (nil)
[313] = (nil)
[314] = (nil)
[315] = (nil)
[316] = (nil)
[317] = (nil)
[318] = (nil)
[319] = (nil)
[320] = (nil)
[321] = (nil)
[322] = (nil)
[323] = (nil)
[324] = (nil)
[325] = (nil)
[326] = (nil)
[327] = (nil)
[328] = (nil)
[329] = (nil)
[330] = (nil)
[331] = (nil)
[332] = (nil)
[333] = (nil)
[334] = (nil)
[335] = (nil)
[336] = (nil)
[337] = (nil)
[338] = (nil)
[339] = (nil)
[340] = (nil)
[341] = (nil)
[342] = (nil)
[343] = (nil)
[344] = (nil)
[345] = (nil)
[346] = (nil)
[347] = (nil)
[348] = (nil)
[349] = (nil)
[350] = (nil)
[351] = (nil)
[352] = (nil)
[353] = (nil)
[354] = (nil)
[355] = (nil)
[356] = (nil)
[357] = (nil)
[358] = (nil)
[359] = (nil)
[360] = (nil)
[361] = (nil)
[362] = (nil)
[363] = (nil)
[364] = (nil)
[365] = (nil)
[366] = (nil)
[367] = (nil)
[368] = (nil)
[369] = (nil)
[370] = (nil)
[371] = (nil)
[372] = (nil)
[373] = (nil)
[374] = (nil)
[375] = (nil)
[376] = (nil)
[377] = (nil)
[378] = (nil)
[379] = (nil)
[380] = (nil)
[381] = (nil)
[382] = (nil)
[383] = (nil)
[384] = (nil)
[385] = (nil)
[386] = (nil)
[387] = (nil)
[388] = (nil)
[389] = (nil)
[390] = (nil)
[391] = (nil)
[392] = (nil)
[393] = (nil)
[394] = (nil)
[395] = (nil)
[396] = (nil)
[397] = (nil)
[398] = (nil)
[399] = (nil)
[400] = (nil)
[401] = (nil)
[402] = (nil)
[403] = (nil)
[404] = (nil)
[405] = (nil)
[406] = (nil)
[407] = (nil)
[408] = (nil)
[409] = (nil)
[410] = (nil)
[411] = (nil)
[412] = (nil)
[413] = (nil)
[414] = (nil)
[415] = (nil)
[416] = (nil)
[417] = (nil)
[418] = (nil)
[419] = (nil)
[420] = (nil)
[421] = (nil)
[422] = (nil)
[423] = (nil)
[424] = (nil)
[425] = (nil)
[426] = (nil)
[427] = (nil)
[428] = (nil)
[429] = (nil)
[430] = (nil)
[431] = (nil)
[432] = (nil)
[433] = (nil)
[434] = (nil)
[435] = (nil)
[436] = (nil)
[437] = (nil)
[438] = (nil)
[439] = (nil)
[440] = (nil)
[441] = (nil)
[442] = (nil)
[443] = (nil)
[444] = (nil)
[445] = (nil)
[446] = (nil)
[447] = (nil)
[448] = (nil)
[449] = (nil)
[450] = (nil)
[451] = (nil)
[452] = (nil)
[453] = (nil)
[454] = (nil)
[455] = (nil)
[456] = (nil)
[457] = (nil)
[458] = (nil)
[459] = (nil)
[460] = (nil)
[461] = (nil)
[462] = (nil)
[463] = (nil)
[464] = (nil)
[465] = (nil)
[466] = (nil)
[467] = (nil)
[468] = (nil)
[469] = (nil)
[470] = (nil)
[471] = (nil)
[472] = (nil)
[473] = (nil)
[474] = (nil)
[475] = (nil)
[476] = (nil)
[477] = (nil)
[478] = (nil)
[479] = (nil)
[480] = (nil)
[481] = (nil)
[482] = (nil)
[483] = (nil)
[484] = (nil)
[485] = (nil)
[486] = (nil)
[487] = (nil)
[488] = (nil)
[489] = (nil)
[490] = (nil)
[491] = (nil)
[492] = (nil)
[493] = (nil)
[494] = (nil)
[495] = (nil)
[496] = (nil)
[497] = (nil)
[498] = (nil)
[499] = (nil)
[500] = (nil)
[501] = (nil)
[502] = (nil)
[503] = (nil)
[504] = (nil)
[505] = (nil)
[506] = (nil)
[507] = (nil)
[508] = (nil)
[509] = (nil)
[510] = (nil)
[511] = (nil)
</gap>
+10
View File
@@ -0,0 +1,10 @@
# Remove values
a 5 42
a 10 43
a 13 44
a 510 45
a 511 46
d 512 43
+514
View File
@@ -0,0 +1,514 @@
<gap len=512 hardlimit=0>
[0] = (nil)
[1] = (nil)
[2] = (nil)
[3] = (nil)
[4] = (nil)
[5] = 0x42
[6] = (nil)
[7] = (nil)
[8] = (nil)
[9] = (nil)
[10] = (nil)
[11] = (nil)
[12] = 0x44
[13] = (nil)
[14] = (nil)
[15] = (nil)
[16] = (nil)
[17] = (nil)
[18] = (nil)
[19] = (nil)
[20] = (nil)
[21] = (nil)
[22] = (nil)
[23] = (nil)
[24] = (nil)
[25] = (nil)
[26] = (nil)
[27] = (nil)
[28] = (nil)
[29] = (nil)
[30] = (nil)
[31] = (nil)
[32] = (nil)
[33] = (nil)
[34] = (nil)
[35] = (nil)
[36] = (nil)
[37] = (nil)
[38] = (nil)
[39] = (nil)
[40] = (nil)
[41] = (nil)
[42] = (nil)
[43] = (nil)
[44] = (nil)
[45] = (nil)
[46] = (nil)
[47] = (nil)
[48] = (nil)
[49] = (nil)
[50] = (nil)
[51] = (nil)
[52] = (nil)
[53] = (nil)
[54] = (nil)
[55] = (nil)
[56] = (nil)
[57] = (nil)
[58] = (nil)
[59] = (nil)
[60] = (nil)
[61] = (nil)
[62] = (nil)
[63] = (nil)
[64] = (nil)
[65] = (nil)
[66] = (nil)
[67] = (nil)
[68] = (nil)
[69] = (nil)
[70] = (nil)
[71] = (nil)
[72] = (nil)
[73] = (nil)
[74] = (nil)
[75] = (nil)
[76] = (nil)
[77] = (nil)
[78] = (nil)
[79] = (nil)
[80] = (nil)
[81] = (nil)
[82] = (nil)
[83] = (nil)
[84] = (nil)
[85] = (nil)
[86] = (nil)
[87] = (nil)
[88] = (nil)
[89] = (nil)
[90] = (nil)
[91] = (nil)
[92] = (nil)
[93] = (nil)
[94] = (nil)
[95] = (nil)
[96] = (nil)
[97] = (nil)
[98] = (nil)
[99] = (nil)
[100] = (nil)
[101] = (nil)
[102] = (nil)
[103] = (nil)
[104] = (nil)
[105] = (nil)
[106] = (nil)
[107] = (nil)
[108] = (nil)
[109] = (nil)
[110] = (nil)
[111] = (nil)
[112] = (nil)
[113] = (nil)
[114] = (nil)
[115] = (nil)
[116] = (nil)
[117] = (nil)
[118] = (nil)
[119] = (nil)
[120] = (nil)
[121] = (nil)
[122] = (nil)
[123] = (nil)
[124] = (nil)
[125] = (nil)
[126] = (nil)
[127] = (nil)
[128] = (nil)
[129] = (nil)
[130] = (nil)
[131] = (nil)
[132] = (nil)
[133] = (nil)
[134] = (nil)
[135] = (nil)
[136] = (nil)
[137] = (nil)
[138] = (nil)
[139] = (nil)
[140] = (nil)
[141] = (nil)
[142] = (nil)
[143] = (nil)
[144] = (nil)
[145] = (nil)
[146] = (nil)
[147] = (nil)
[148] = (nil)
[149] = (nil)
[150] = (nil)
[151] = (nil)
[152] = (nil)
[153] = (nil)
[154] = (nil)
[155] = (nil)
[156] = (nil)
[157] = (nil)
[158] = (nil)
[159] = (nil)
[160] = (nil)
[161] = (nil)
[162] = (nil)
[163] = (nil)
[164] = (nil)
[165] = (nil)
[166] = (nil)
[167] = (nil)
[168] = (nil)
[169] = (nil)
[170] = (nil)
[171] = (nil)
[172] = (nil)
[173] = (nil)
[174] = (nil)
[175] = (nil)
[176] = (nil)
[177] = (nil)
[178] = (nil)
[179] = (nil)
[180] = (nil)
[181] = (nil)
[182] = (nil)
[183] = (nil)
[184] = (nil)
[185] = (nil)
[186] = (nil)
[187] = (nil)
[188] = (nil)
[189] = (nil)
[190] = (nil)
[191] = (nil)
[192] = (nil)
[193] = (nil)
[194] = (nil)
[195] = (nil)
[196] = (nil)
[197] = (nil)
[198] = (nil)
[199] = (nil)
[200] = (nil)
[201] = (nil)
[202] = (nil)
[203] = (nil)
[204] = (nil)
[205] = (nil)
[206] = (nil)
[207] = (nil)
[208] = (nil)
[209] = (nil)
[210] = (nil)
[211] = (nil)
[212] = (nil)
[213] = (nil)
[214] = (nil)
[215] = (nil)
[216] = (nil)
[217] = (nil)
[218] = (nil)
[219] = (nil)
[220] = (nil)
[221] = (nil)
[222] = (nil)
[223] = (nil)
[224] = (nil)
[225] = (nil)
[226] = (nil)
[227] = (nil)
[228] = (nil)
[229] = (nil)
[230] = (nil)
[231] = (nil)
[232] = (nil)
[233] = (nil)
[234] = (nil)
[235] = (nil)
[236] = (nil)
[237] = (nil)
[238] = (nil)
[239] = (nil)
[240] = (nil)
[241] = (nil)
[242] = (nil)
[243] = (nil)
[244] = (nil)
[245] = (nil)
[246] = (nil)
[247] = (nil)
[248] = (nil)
[249] = (nil)
[250] = (nil)
[251] = (nil)
[252] = (nil)
[253] = (nil)
[254] = (nil)
[255] = (nil)
[256] = (nil)
[257] = (nil)
[258] = (nil)
[259] = (nil)
[260] = (nil)
[261] = (nil)
[262] = (nil)
[263] = (nil)
[264] = (nil)
[265] = (nil)
[266] = (nil)
[267] = (nil)
[268] = (nil)
[269] = (nil)
[270] = (nil)
[271] = (nil)
[272] = (nil)
[273] = (nil)
[274] = (nil)
[275] = (nil)
[276] = (nil)
[277] = (nil)
[278] = (nil)
[279] = (nil)
[280] = (nil)
[281] = (nil)
[282] = (nil)
[283] = (nil)
[284] = (nil)
[285] = (nil)
[286] = (nil)
[287] = (nil)
[288] = (nil)
[289] = (nil)
[290] = (nil)
[291] = (nil)
[292] = (nil)
[293] = (nil)
[294] = (nil)
[295] = (nil)
[296] = (nil)
[297] = (nil)
[298] = (nil)
[299] = (nil)
[300] = (nil)
[301] = (nil)
[302] = (nil)
[303] = (nil)
[304] = (nil)
[305] = (nil)
[306] = (nil)
[307] = (nil)
[308] = (nil)
[309] = (nil)
[310] = (nil)
[311] = (nil)
[312] = (nil)
[313] = (nil)
[314] = (nil)
[315] = (nil)
[316] = (nil)
[317] = (nil)
[318] = (nil)
[319] = (nil)
[320] = (nil)
[321] = (nil)
[322] = (nil)
[323] = (nil)
[324] = (nil)
[325] = (nil)
[326] = (nil)
[327] = (nil)
[328] = (nil)
[329] = (nil)
[330] = (nil)
[331] = (nil)
[332] = (nil)
[333] = (nil)
[334] = (nil)
[335] = (nil)
[336] = (nil)
[337] = (nil)
[338] = (nil)
[339] = (nil)
[340] = (nil)
[341] = (nil)
[342] = (nil)
[343] = (nil)
[344] = (nil)
[345] = (nil)
[346] = (nil)
[347] = (nil)
[348] = (nil)
[349] = (nil)
[350] = (nil)
[351] = (nil)
[352] = (nil)
[353] = (nil)
[354] = (nil)
[355] = (nil)
[356] = (nil)
[357] = (nil)
[358] = (nil)
[359] = (nil)
[360] = (nil)
[361] = (nil)
[362] = (nil)
[363] = (nil)
[364] = (nil)
[365] = (nil)
[366] = (nil)
[367] = (nil)
[368] = (nil)
[369] = (nil)
[370] = (nil)
[371] = (nil)
[372] = (nil)
[373] = (nil)
[374] = (nil)
[375] = (nil)
[376] = (nil)
[377] = (nil)
[378] = (nil)
[379] = (nil)
[380] = (nil)
[381] = (nil)
[382] = (nil)
[383] = (nil)
[384] = (nil)
[385] = (nil)
[386] = (nil)
[387] = (nil)
[388] = (nil)
[389] = (nil)
[390] = (nil)
[391] = (nil)
[392] = (nil)
[393] = (nil)
[394] = (nil)
[395] = (nil)
[396] = (nil)
[397] = (nil)
[398] = (nil)
[399] = (nil)
[400] = (nil)
[401] = (nil)
[402] = (nil)
[403] = (nil)
[404] = (nil)
[405] = (nil)
[406] = (nil)
[407] = (nil)
[408] = (nil)
[409] = (nil)
[410] = (nil)
[411] = (nil)
[412] = (nil)
[413] = (nil)
[414] = (nil)
[415] = (nil)
[416] = (nil)
[417] = (nil)
[418] = (nil)
[419] = (nil)
[420] = (nil)
[421] = (nil)
[422] = (nil)
[423] = (nil)
[424] = (nil)
[425] = (nil)
[426] = (nil)
[427] = (nil)
[428] = (nil)
[429] = (nil)
[430] = (nil)
[431] = (nil)
[432] = (nil)
[433] = (nil)
[434] = (nil)
[435] = (nil)
[436] = (nil)
[437] = (nil)
[438] = (nil)
[439] = (nil)
[440] = (nil)
[441] = (nil)
[442] = (nil)
[443] = (nil)
[444] = (nil)
[445] = (nil)
[446] = (nil)
[447] = (nil)
[448] = (nil)
[449] = (nil)
[450] = (nil)
[451] = (nil)
[452] = (nil)
[453] = (nil)
[454] = (nil)
[455] = (nil)
[456] = (nil)
[457] = (nil)
[458] = (nil)
[459] = (nil)
[460] = (nil)
[461] = (nil)
[462] = (nil)
[463] = (nil)
[464] = (nil)
[465] = (nil)
[466] = (nil)
[467] = (nil)
[468] = (nil)
[469] = (nil)
[470] = (nil)
[471] = (nil)
[472] = (nil)
[473] = (nil)
[474] = (nil)
[475] = (nil)
[476] = (nil)
[477] = (nil)
[478] = (nil)
[479] = (nil)
[480] = (nil)
[481] = (nil)
[482] = (nil)
[483] = (nil)
[484] = (nil)
[485] = (nil)
[486] = (nil)
[487] = (nil)
[488] = (nil)
[489] = (nil)
[490] = (nil)
[491] = (nil)
[492] = (nil)
[493] = (nil)
[494] = (nil)
[495] = (nil)
[496] = (nil)
[497] = (nil)
[498] = (nil)
[499] = (nil)
[500] = (nil)
[501] = (nil)
[502] = (nil)
[503] = (nil)
[504] = (nil)
[505] = (nil)
[506] = (nil)
[507] = (nil)
[508] = (nil)
[509] = 0x45
[510] = 0x46
[511] = 0x46
</gap>
Executable
+30
View File
@@ -0,0 +1,30 @@
#! /usr/bin/perl -w
# This runs all the tests.
# Tests scripts are in *.tst files.
# Corresponding output is put in *.out.
# Reference output is put in *.ref.
# Any discrepancy will be reported!
use strict;
my @res;
foreach my $fn (`ls *.tst`) {
chomp $fn;
my $cmd = "./gtest $fn $fn.out";
print "$cmd\n";
`$cmd`;
my $res = system("diff -u $fn.ref $fn.out");
push @res, [$fn, ($res == 0 ? "OK" : "*KO*")];
}
format =
@<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>
$_->[0], $_->[1]
.
#format_name STDOUT test_result;
map { write; } @res;
+6
View File
@@ -61,6 +61,7 @@ static int parse_server_name_extension(const struct TLSProtocol *, const char *,
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
static int has_match(const char**, size_t, const char*, size_t);
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
* hello handshake, returning a status code
*
@@ -81,6 +82,11 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
if (data_len < TLS_HEADER_LEN)
return TLS_ELENGTH;
/* If if it is an ECH, decrypt and match */
ech_match_sni(data, data_len);
tls_content_type = data[0];
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
+1
View File
@@ -44,5 +44,6 @@ struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, s
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
#define TLS_EPROTOCOL -5 /* Protocol error */
void ech_match_sni(const char* buffer_in, size_t buffer_len);
#endif
+284
View File
@@ -0,0 +1,284 @@
/*
# tls_ech.c: support for RFC9849 TLS Encrypted Client Hello (ECH)
#
# Copyright (C) 2026 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# The full text for the General Public License is here:
# http://www.gnu.org/licenses/gpl.html
*/
#define WOLFSSL_USE_OPTION_H
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/wolfcrypt/random.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "config.h"
#include "log.h"
#include "sslh-conf.h"
#include "tls.h"
WOLFSSL_CTX* wolfssl_ctx;
// Hard-coded server certificate (PEM format)
// from snakeoil -- these serves no purpose beyond initialising the context
static const char* SERVER_CERT_PEM =
"-----BEGIN CERTIFICATE-----\n"
"MIICyjCCAbKgAwIBAgIUZrCzD4tEIu1r9EhkOn96raiupe4wDQYJKoZIhvcNAQEL\n"
"BQAwDzENMAsGA1UEAwwEc2FhejAeFw0yMTA1MjgyMDM0MjRaFw0zMTA1MjYyMDM0\n"
"MjRaMA8xDTALBgNVBAMMBHNhYXowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
"AoIBAQC5s1UhjcmsrRnSdJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6J\n"
"MAL1MbPtX0pfR+yBILWUdn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoe\n"
"gD9FE3lE895GIhEv7Jd9bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bK\n"
"fJzuOxi7oIeINLEiR0Z4hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2\n"
"QFMByfkEmAGp9Br+2tquRUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd\n"
"6G6TLenPbQPEaac5UgGZ7rJvL9idAgMBAAGjHjAcMAkGA1UdEwQCMAAwDwYDVR0R\n"
"BAgwBoIEc2FhejANBgkqhkiG9w0BAQsFAAOCAQEAZPasi1Hu7q4S9wJsbIzcGj7m\n"
"ZCAxNdm3AINeV3mxfhToeqtXSoa2sujCEgwCuiYi0OE8wmseLO8e1xIsRc/NEpOl\n"
"h9u5OupV2Xbf7nuKzPl31YQ7kv+90mnixizf6CFpHXAdU3WEZ33+fMtS+lV/ZBYo\n"
"Nh9GGNJ7Grv5KIKu/b2cR403pRF1hGrjWABrIDZO3ZCtNgRp/My1Qr+/04rTs8VF\n"
"WyDshOe0u6Xo//M3sCMbx5+62BopCMmAu2TxiPew0OTM8eDa3IBbc2kLGxp8c4J+\n"
"EYpAKTZbkhs/6SleNMC4BOEmZQ1tk6D0N0u4Y0b1IKEjgx7HqwZ6mIdmbU1lZQ==\n"
"-----END CERTIFICATE-----\n";
static const char* SERVER_KEY_PEM =
"-----BEGIN PRIVATE KEY-----\n"
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5s1UhjcmsrRnS\n"
"dJMH82NxRQ3hlwMxGZz5NnI0MukXN5kptAYSge4BQc6JMAL1MbPtX0pfR+yBILWU\n"
"dn6JCnDqhv9ipO8OpeVeh+RKa2kGiEdeoQhBbKNZdsoegD9FE3lE895GIhEv7Jd9\n"
"bt/r8CcCpmOLuhJF6md0cLWGPrAp4l+iCV/ixyE0z0bKfJzuOxi7oIeINLEiR0Z4\n"
"hJsM2XAEXlBEsI4oh2KdDJiU6ow3w25IY7+/nYVDDLD2QFMByfkEmAGp9Br+2tqu\n"
"RUmR8qmSvZPMtWpOzMv8+iKTrxUcAUCtCpDMjHqNR8fd6G6TLenPbQPEaac5UgGZ\n"
"7rJvL9idAgMBAAECggEAbXNQctViD596/7rryhze+KztsI/UNsVU6uH3T+LN1XxU\n"
"jurnRVNFV4JU3DDrHV9tIDQw3pXCxJzlyRpKHDWGNgTZU2fI6sZGuX+4w1Apd8ss\n"
"3Y5mEkGYyaslF/MY13rR2B0wXf/IR4nYA/ssiaBjLcGqHHnQOVseHNgtdXFPXW9n\n"
"5IXJ2PSeaFW0Z13vbTQm9Cx2kCBKL4J3TymhkuSW3DUHxmlDfWUK14lkyXCZIZ3i\n"
"TbXQAXhwTrz3wqbG++iEP4MZM0Ka7/+WXqrsxA/yCirb+HnMewxwBKzGvkym2I2H\n"
"3BkjDn9exDgTePD7i0w22UYCF5uezDB9YOiPVZ634QKBgQDyKIgxD7XMMJezeg0Y\n"
"PaG+Eig/hE27mm2pyTQSd6MMZ1HpbDUpLQ/GO3Npy+bKS0/5eefNT4PWVcmpRL4W\n"
"blNIfixkcZq1a6wax9Nhk89jXZdhBinO8gfXFOiANFDS7B4fEXeTrfhmuryqUw9m\n"
"L6GJ0jk/H6fs6ads15y+O7A6tQKBgQDEUKlZg4qXxNMNKq7l+v7R6IH8VrNLt0I/\n"
"f4fhpW3IhmPJCRuOFOS0UpDPYjTeu14i3EFcpe0D+DVoQiggBTAh3I/qMHvRko5g\n"
"DT83YHp2/ec0AY0ocwnLwye224t6GF7S8R8vhG1FkIBk6DTJgWFZLDtY+dekZugz\n"
"IHvgtxSPSQKBgHWD7fPBJ/xbaIMUq94jqqZOsXGBhyePncBTgA2mOV3/leStOm8t\n"
"CwasOyoQZYOuYLU0z6T2/Ye6Qg7+6TCBgbEgafKknuuDwRWN+6rSzEXwWVIgZ5Gi\n"
"KuPZparxuHdjVorFMz5borxXys7tV//DBaWYe2eCuT9jdHiBefNni/IxAoGAWHTn\n"
"+deVqVEcsAAdkq8IBtk3SNZgL6vBhA8Y0QnTb9luOPWLnve3HbFeYrOjkwDmJ9sK\n"
"8I4rP/ClT/cPUW5FA9z4U9PI1uOsl4cghvlH9Tnu5bYVPranIVMsH+7I6Bj3ESFo\n"
"peaLvh6gW9dtgaZ0kNOnYi4hhoh/9Bmc9+JGt1ECgYAbGSQLa21L4Hx3NvMparVQ\n"
"bGjN26igvqKmhBaKrH8YolOlhEw/mCwTlg9zI7jrhdO0VClPrB8IVBoL+6HvrT21\n"
"LJM31J3ddqME9/5fQgKMQsD60kRDD6iV/iK2S6TOUjxJn1f6MuLmGSOHZYcbdBPZ\n"
"DyBKgbz/xGV6katjXvg74A==\n"
"-----END PRIVATE KEY-----\n";
#if 1
/* Will be provided by wolfSSL */
int wolfSSL_GetEchClientHelloInner(
WOLFSSL* ssl, /* Connected to an existing context with the ECH configs. Stores hpke state */
const byte* buffer_in, /* The full ClientHelloOuter */
word32 buffer_len, /* How many bytes in the buffer */
byte* buffer_out,
word32* buffer_out_len
)
{
char* inner = "some clienthelloinner";
*buffer_out_len = strlen(inner);
memcpy(buffer_out, inner, *buffer_out_len);
print_message(msg_fd, "GetEchClientHelloInner set\n");
return 0;
}
#endif
void ech_get_client_hello_inner(WOLFSSL_CTX* ctx, const byte* data, size_t datalen)
{
WOLFSSL* ssl = NULL;
printf("wolfSSL_new(%p)\n", ctx);
ssl = wolfSSL_new(ctx);
if (ssl == NULL) {
fprintf(stderr, "Failed to create WOLFSSL object\n");
return;
}
// Disable certificate verification on this connection
wolfSSL_set_verify(ssl, WOLFSSL_VERIFY_NONE, NULL);
byte client_hello_inner[1024];
word32 ch_len = sizeof(client_hello_inner);;
int res = wolfSSL_GetEchClientHelloInner(
ssl,
data, /* The full ClientHelloOuter */
datalen, /* How many bytes in the buffer */
(byte*)&client_hello_inner,
&ch_len
);
client_hello_inner[ch_len] = 0;
if (!res) {
print_message(msg_fd, "GetEchClientHelloInner: Innerlen: %d\n", ch_len);
hexdump(msg_fd, (char*)client_hello_inner, ch_len);
print_message(msg_fd, "/GetEchClientHelloInner\n");
}
byte sni_data[1024];
unsigned int sni_len = sizeof(sni_data);
memset(sni_data, 'A', sni_len);
printf("SNI_GetFromBuffer\n");
wolfSSL_SNI_GetFromBuffer(data, datalen, 0, (byte*)&sni_data, &sni_len);
sni_data[sni_len] = 0;
printf("sni_len = %d\n", sni_len);
printf("sni: %s\n", sni_data);
}
/* See if the context matches */
void ech_match_sni(const char* buffer_in, size_t buffer_len)
{
ech_get_client_hello_inner(wolfssl_ctx, (byte*)buffer_in, buffer_len);
}
/* Appends a configuration to the DNS configuration file */
static void append_conf(const char* sni, const char* ech)
{
FILE* f = fopen(cfg.ech_conf_sink, "a");
if (!f) {
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
return;
}
int res = fprintf(f, "%s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\"\n", sni, ech);
printf("append: %s IN HTTPS 1 . alpn=\"h2\" ech=\"%s\" : %d\n", sni, ech, res);
fclose(f);
}
/* Erases the configuration file */
static void reset_conf(void)
{
FILE* f = fopen(cfg.ech_conf_sink, "w");
if (!f) {
print_message(msg_config_error, "Unable to open %s:%d:%s\n", cfg.ech_conf_sink, errno, strerror(errno));
return;
}
printf("erased and opend %s\n", cfg.ech_conf_sink);
}
/* Generate a keypair for a config; later this will be moved to a separate
* process, and sslh will just read it */
static void ech_genkey(WOLFSSL_CTX* ctx, const char* sni)
{
int ret;
printf("ech_genkey for ctx %p\n", ctx);
print_message(msg_config, "Generating ECH config for outer SNI: %s\n", sni);
ret = wolfSSL_CTX_GenerateEchConfig(ctx, sni, 0, 0, 0);
if (ret != WOLFSSL_SUCCESS) {
print_message(msg_system_error, "Failed to generate ECH config: %d\n", ret);
exit(1);
}
byte echConfig[512];
word32 echConfigLen = sizeof(echConfig);
ret = wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen);
if (ret != WOLFSSL_SUCCESS) {
print_message(msg_system_error, "Failed to get ECH config: %d\n", ret);
exit(1);
}
char out[512];
word32 outLen = sizeof(out);
if (Base64_Encode_NoNl(echConfig, echConfigLen, (byte*)out, &outLen) != 0) {
print_message(msg_system_error, "Failed to encode to base64: %d\n", ret);
exit(1);
}
append_conf(sni, out);
}
/* For each public names, create a keypair and configuration.
* (Later, this will load the configurations from some place else */
static void ech_create_configs(WOLFSSL_CTX** ctx)
{
int ret;
reset_conf();
*ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
if (!*ctx) {
print_message(msg_system_error, "Failed to create WOLFSSL_CTX\n");
exit(1);
}
// Load certificate from memory
ret = wolfSSL_CTX_use_certificate_buffer(*ctx,
(const unsigned char*)SERVER_CERT_PEM,
strlen(SERVER_CERT_PEM),
WOLFSSL_FILETYPE_PEM);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Failed to load certificate: %d\n", ret);
fprintf(stderr, "Please replace SERVER_CERT_PEM with a valid certificate\n");
wolfSSL_CTX_free(*ctx);
exit(1);
}
// Load private key from memory
ret = wolfSSL_CTX_use_PrivateKey_buffer(*ctx,
(const unsigned char*)SERVER_KEY_PEM,
strlen(SERVER_KEY_PEM),
WOLFSSL_FILETYPE_PEM);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Failed to load private key: %d\n", ret);
fprintf(stderr, "Please replace SERVER_KEY_PEM with a valid private key\n");
wolfSSL_CTX_free(*ctx);
exit(1);
}
printf("Certificate and key loaded successfully\n");
ech_genkey(*ctx, cfg.listen[0].host); /* For now this only works on the first host */
}
/* Initialises the ECH subsystem: create wolfSSL context, create ECH configs,
* etc */
void ech_init()
{
wolfSSL_Init();
ech_create_configs(&wolfssl_ctx);
}
+10 -2
View File
@@ -27,12 +27,16 @@
#include "sslh-conf.h"
#include "udp-listener.h"
/* Incoming connections are of course all received on a single socket. Create a
* hash that associates (incoming sockaddr) => struct connection*, so finding
* the connection related to an incoming packet is fast.
*/
typedef struct connection* hash_item;
#include "hash.h"
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
{
@@ -120,6 +124,7 @@ static void udp_sanity_check(void)
void udp_init(struct loop_info* fd_info)
{
fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp);
CHECK_ALLOC(fd_info->hash_sources, "hash_init");
udp_protocol_list_init();
udp_sanity_check();
@@ -266,8 +271,6 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
malloc/free overhead for each packet, when really 64K is not that much */
udp_timeouts(fd_info);
addrlen = sizeof(src_addr);
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
if (len < 0) {
@@ -302,6 +305,11 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
cnx->addrlen = addrlen;
cnx->local_endpoint = sockfd;
if (inc_proto_connections(cnx->proto)) {
tidy_connection(cnx, fd_info);
return NULL;
}
res = new_source(fd_info->hash_sources, cnx);
if (res == -1) {
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increase udp_max_connections");
+1
View File
@@ -25,5 +25,6 @@ void udp_s2c_forward(struct connection* cnx);
void udp_init(struct loop_info* fd_info);
void udp_tidy(struct connection* cnx, struct loop_info* fd_info);
void udp_timeouts(struct loop_info* fd_info);
#endif /* UDPLISTENER_H */
-5
View File
@@ -1,5 +0,0 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION "v2.2.0-dirty"
#endif