Compare commits

...
33 Commits
Author SHA1 Message Date
Yves Rutschle d39388d1ed prepare v2.3.1 2026-03-05 19:14:57 +01:00
Yves Rutschle 70a776fb9a -V now outputs compile-time configuration (fix #519) 2026-03-04 22:35:56 +01:00
Yves Rutschle 9fadd60eed sanity check that libwrap is present when using service option 2026-03-03 22:42:52 +01:00
Yves Rutschle 83c9465c37 sanity check that proxyprotocol is compiled in when used by listening endpoints 2026-03-03 22:34:36 +01:00
Yves Rutschle 1137779b9c sanity check that libproxyprocol is compiled in when option is used 2026-03-03 22:29:01 +01:00
Yves Rutschle 4d3aa6c6e0 sanity checks that regex options are only used with the right probe and with libpcre2 support compiled in 2026-03-03 22:05:01 +01:00
Yves Rutschle 579baae363 fix syntax error in documentation example 2026-02-28 21:42:01 +01:00
Yves Rutschle e2d01a7440 close file descriptors upon forking a connection shoveler (fix #521) 2026-02-15 11:17:36 +01:00
Yves Rutschle ecc76071c1 downgrade SNI matching logs from error to info to reduce log verboseness (Fix #520) 2026-02-08 09:50:45 +01:00
Yves Rutschle e5d7cd0575 revert some useless changes to really fix #515 2026-02-06 22:08:59 +01:00
Yves Rutschle d288125ff3 test combinations of ipv4 <-> ipv6 over proxyprotocol 2026-02-06 22:05:27 +01:00
Yves Rutschle 2cc1e1f1d9 add CAP_NET_RAW in systemd configuration to support transparent proxying 2026-02-03 21:00:40 +01:00
yrutschle 77dc76a0f2 Change IPv4 to IPv6 addresses when using proxyprotocol and protocols are different (Fix #515) 2026-02-01 18:02:03 +01:00
4O4-Not-F0und 86188cdd28 build sslh with libproxyprotocol 2025-12-20 17:23:10 +01:00
Yves Rutschle c29081f71d Merge branch 'master' of github.com:yrutschle/sslh 2025-11-14 14:54:17 +01:00
Yves Rutschle 8d58164fdd Libwrap requires access to some more files when using landlock (fix #450 again) 2025-11-14 14:53:56 +01:00
yrutschle 894eacfb42 Fix sslh-fork segmentation fault (Fix #508) 2025-10-30 08:22:48 +01:00
benok 6310e6fc9c Add missing trailing "\n" to log output 2025-10-29 12:28:01 +01: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 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
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
36 changed files with 604 additions and 124 deletions

No files matched your search

+32 -2
View File
@@ -1,4 +1,33 @@
vNEXT: v2.3.1:
Fix segmentation fault in sslh-fork.
Add file access when using Landlock and Libwrap
together.
Mixed IPv4/IPv6 proxyprotocol setups will move both
addresses to IPv6 format on the server side.
Add CAP_NET_RAW to systemd config so transparent
proxying can work. Removed sslh-select systemd file:
don't repeat the same configuration file!
Changed SNI matching log messages from
`msg_probe_error` to `msg_probe_info`: beware if you
happen to use them...
Close unused file descriptors upon forking a worker
for a connection (in sslh-ev and sslh-select). In
particular, this prevents forked processes to
inherit the listen sockets and block a new main
process from starting.
More configuration sanity checks (e.g. using regex
or proxyprotocol options if libraries are not compiled in)
-V now outputs compile-time options so it is
possible to know what is compiled in.
v2.3.0:
Added `max_connections` setting to `listen` and Added `max_connections` setting to `listen` and
`protocol` configuration; see the `protocol` configuration; see the
[guide](doc/max_connections.md) for more [guide](doc/max_connections.md) for more
@@ -6,7 +35,8 @@ vNEXT:
accept() is now disabled for some time when ulimit -n is reached. accept() is now disabled for some time when ulimit -n is reached.
Support proxyprotocol on incoming connections. 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 proxyprotocol target field (Thanks to Github user 404-Not-Found)
+26 -3
View File
@@ -3,8 +3,7 @@ ARG TARGET_ARCH="library"
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
WORKDIR /sslh # Install build dependencies for sslh and libproxyprotocol
RUN apk add --no-cache \ RUN apk add --no-cache \
'gcc' \ 'gcc' \
'libconfig-dev' \ 'libconfig-dev' \
@@ -12,15 +11,39 @@ RUN apk add --no-cache \
'musl-dev' \ 'musl-dev' \
'pcre2-dev' \ 'pcre2-dev' \
'perl' \ 'perl' \
'git' \
'autoconf' \
'automake' \
'libtool' \
; ;
# Build libproxyprotocol
WORKDIR /tmp
RUN git clone https://github.com/kosmas-valianos/libproxyprotocol.git && \
cd libproxyprotocol && \
make && \
mkdir -p /usr/local/include/ && \
mv libs/libproxyprotocol.so /usr/local/lib/ && \
mv src/* /usr/local/include/
# Set environment variables for sslh to find libproxyprotocol
ENV C_INCLUDE_PATH=/usr/local/include
ENV LIBRARY_PATH=/usr/local/lib
ENV LD_LIBRARY_PATH=/usr/local/lib
WORKDIR /sslh
COPY . /sslh COPY . /sslh
# Configure and build sslh
# The configure script should automatically detect libproxyprotocol
RUN ./configure && make sslh-select && strip sslh-select RUN ./configure && make sslh-select && strip sslh-select
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
# Copy libproxyprotocol.so from the build stage
COPY --from=build /usr/local/lib/libproxyprotocol.so* /usr/local/lib/
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh" COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
RUN apk add --no-cache \ RUN apk add --no-cache \
'libconfig' \ 'libconfig' \
'pcre2' \ 'pcre2' \
@@ -35,4 +58,4 @@ COPY "./container-entrypoint.sh" "/init"
ENTRYPOINT [ "/init" ] ENTRYPOINT [ "/init" ]
# required for updating iptables # required for updating iptables
USER root:root USER root:root
+2 -2
View File
@@ -22,11 +22,11 @@ MAN=sslh.8.gz # man page name
# itself # itself
ifneq ($(strip $(ENABLE_SANITIZER)),) ifneq ($(strip $(ENABLE_SANITIZER)),)
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment CFLAGS_SAN=-DENABLE_SANITIZER -fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=alignment
endif endif
ifneq ($(strip $(COV_TEST)),) ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage CFLAGS_COV=-DCOV_TEST -fprofile-arcs -ftest-coverage
endif endif
CC ?= gcc CC ?= gcc
+10 -2
View File
@@ -28,6 +28,7 @@
/* Info to keep track of all connections */ /* Info to keep track of all connections */
struct cnx_collection { struct cnx_collection {
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */ gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
int max_fd;
}; };
/* Allocates and initialises a new collection of connections with at least /* Allocates and initialises a new collection of connections with at least
@@ -42,6 +43,7 @@ cnx_collection* collection_init(int len)
memset(collection, 0, sizeof(*collection)); memset(collection, 0, sizeof(*collection));
collection->fd2cnx = gap_init(len); collection->fd2cnx = gap_init(len);
collection->max_fd = 0;
return collection; return collection;
} }
@@ -54,10 +56,17 @@ void collection_destroy(cnx_collection* collection)
free(collection); free(collection);
} }
int collection_max_fd(cnx_collection* collection)
{
return collection->max_fd;
}
/* Points the file descriptor to the specified connection index */ /* Points the file descriptor to the specified connection index */
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd) int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
{ {
gap_set(collection->fd2cnx, fd, cnx); gap_set(collection->fd2cnx, fd, cnx);
if (fd > collection->max_fd)
collection->max_fd = fd;
return 0; return 0;
} }
@@ -74,8 +83,7 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
cnx->state = ST_PROBING; cnx->state = ST_PROBING;
cnx->probe_timeout = time(NULL) + cfg.timeout; cnx->probe_timeout = time(NULL) + cfg.timeout;
gap_set(collection->fd2cnx, fd, cnx); collection_add_fd(collection, cnx, fd);
return cnx; return cnx;
} }
+1
View File
@@ -9,6 +9,7 @@ void collection_destroy(cnx_collection* collection);
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd); struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd); int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
int collection_max_fd(cnx_collection* collection);
/* Remove a connection from the collection */ /* Remove a connection from the collection */
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx); int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
+4 -10
View File
@@ -25,12 +25,6 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#endif #endif
/* Added to make the code compilable under CYGWIN
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
/* Make use of systemd socket activation /* Make use of systemd socket activation
* */ * */
#ifdef SYSTEMD #ifdef SYSTEMD
@@ -428,9 +422,9 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
cnx->proto->port); cnx->proto->port);
} }
for (a = cnx->proto->saddr; a; a = a->ai_next) { for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent or using proxyprotocol, make sure both /* When transparent, make sure both connections use
* connections use the same address family (e.g. IP4 on both sides) */ * the same address family (e.g. IP4 on both sides) */
if ((transparent || cnx->proto->proxyprotocol_is_present) && (a->ai_family != from.ai_addr->sa_family)) if (transparent && (a->ai_family != from.ai_addr->sa_family))
continue; continue;
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n", print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
sprintaddr(buf, sizeof(buf), a), sprintaddr(buf, sizeof(buf), a),
@@ -905,7 +899,7 @@ int check_access_rights(int in_socket, const char* service)
} }
if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) { if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) {
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str); print_message(msg_connections, "connection from %s(%s): access denied\n", host, addr_str);
close(in_socket); close(in_socket);
return -1; return -1;
} }
+8 -1
View File
@@ -9,6 +9,13 @@
#define FD_SETSIZE 4096 #define FD_SETSIZE 4096
#endif #endif
/* Cygwin does not define some symbols
* */
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0
#endif
#define _GNU_SOURCE #define _GNU_SOURCE
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h> #include <fcntl.h>
@@ -197,7 +204,7 @@ extern int hosts_ctl();
#endif #endif
/* sslh-fork.c */ /* sslh-fork.c */
void start_shoveler(int); void main_inetd(void);
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen); void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
+53 -17
View File
@@ -33,6 +33,8 @@ olegstepura](https://github.com/yrutschle/sslh/issues/448)
# ... (make sure to mount config as you like) # ... (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 command: --transparent -F/etc/sslh/sslh.cfg # parameter --transparent here is needed to trigger configure_iptables in init script
state: started 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" - name: "Create the caddy container"
containers.podman.podman_container: containers.podman.podman_container:
@@ -95,26 +97,60 @@ protocols:
); );
``` ```
I omitted caddy configs here as it's not important. Some unrelated container configs were also dropped. 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 3 containers in the same pod, all listening on `localhost`. SSLH has to listen on `444` because caddy already listens on `443` and it's more complex to reconfigure caddy port because of let's encrypt (caddy itself "thinks" it is bound to your host interface).
Scheme of port mapping is (all containers share same `localhost`): 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) host 443 → pod 444 (sslh) → pod 443 (caddy)
``` ```
- podman connects host `443` port to pod's `444` port
- `sslh` listens `444` on `localhost`, reroutes tls to `caddy` on `localhost:443`
- `caddy` listens `443` on `localhost` (reverse-proxies to other apps on private network)
Reverse-proxied services get correct IP in `X-Forwarded-For` header.
Takeaways:
- `net.ipv4.conf.default.route_localnet` is setup only in container, not on host, which is nice.
- same with iptables and route rules applied by `init` script in sslh container
- `sslh` proxy to other services in the private network will not work (because transparent mode is enabled) even if that other service does not need real IP
- with transparent mode all services that `sslh` will connect to should be attached to this pod making it listen on `localhost` of the pod
- ports of containers should not clash
- ports should be published on pod level, not on containers
- containers should not be configured to connect to custom networks
- because of the above proxying ssh to host will also not work out of the box, `sslh` should connect to `localhost` and not to a random IP. While adding another proxy as a container to the same pod sounds like an overkill I don't see any other solution, so `socat` is used as an additional proxy.
- `reverse_proxy` by caddy to other containers in the same custom network (as pod is attached to) works (e.g. caddy can connect to other IPs from custom network). `socat` can also connect to host and/or other containers in custom network.
- **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.
-2
View File
@@ -27,7 +27,6 @@ protocols:
( (
{ name: "ssh"; host: "localhost"; port: "2222"; }, { name: "ssh"; host: "localhost"; port: "2222"; },
{ name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; } { name: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
);
); );
``` ```
@@ -77,7 +76,6 @@ protocols:
( (
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; }, { name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
{ name: "tls"; host: "localhost"; port: "8080"; } { name: "tls"; host: "localhost"; port: "8080"; }
);
); );
``` ```
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Fri Aug 29 20:43:29 2025. * on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs # conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle # Copyright (C) 2018-2024 Yves Rutschle
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Fri Aug 29 20:43:29 2025. * on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs # conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle # Copyright (C) 2018-2024 Yves Rutschle
+3
View File
@@ -55,6 +55,9 @@ syslog_facility: "auth";
# is_unix: listen in the UNIX socket domain (default is TCP) # is_unix: listen in the UNIX socket domain (default is TCP)
# keepalive: set TCP_KEEPALIVE # keepalive: set TCP_KEEPALIVE
# max_connections: drop incoming connections after this count # 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: listen:
( (
{ host: "thelonious"; port: "443"; max_connections: 5; }, { host: "thelonious"; port: "443"; max_connections: 5; },
+4
View File
@@ -155,6 +155,10 @@ static int distance(int current_index, hash* h, hash_item item)
return current_index - wanted_index + h->hash_size; 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) int hash_insert(hash* h, hash_item new)
{ {
+1
View File
@@ -13,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); 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_insert(hash* h, hash_item new);
int hash_remove(hash* h, hash_item item); int hash_remove(hash* h, hash_item item);
+1 -1
View File
@@ -118,7 +118,7 @@ static int add_resolv(int ruleset_fd)
static int add_libwrap(int ruleset_fd) static int add_libwrap(int ruleset_fd)
{ {
/* Files for libwrap */ /* Files for libwrap */
#ifdef LIBWRAP #ifdef HAVE_LIBWRAP
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow"); add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny"); add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
#endif #endif
-2
View File
@@ -162,8 +162,6 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
if (len < 1) if (len < 1)
return PROBE_NEXT; return PROBE_NEXT;
printf("opcode: %d\n", (p[0] & OVPN_OPCODE_MASK) >> 3);
if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 && 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 (p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V3
+20 -6
View File
@@ -46,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_read(fd_info->watchers, cnx->q[i].fd);
watchers_del_write(fd_info->watchers, cnx->q[i].fd); watchers_del_write(fd_info->watchers, cnx->q[i].fd);
close(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) if (cnx->type == SOCK_DGRAM)
@@ -152,6 +152,13 @@ void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
typedef struct pid2proto* hash_item; typedef struct pid2proto* hash_item;
#include "hash.h" #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, void remember_child_data(struct loop_info* fd_info,
struct connection* cnx, pid_t pid) struct connection* cnx, pid_t pid)
{ {
@@ -160,7 +167,10 @@ void remember_child_data(struct loop_info* fd_info,
pid2proto->proto = cnx->proto; pid2proto->proto = cnx->proto;
pid2proto->endpoint = cnx->endpoint; pid2proto->endpoint = cnx->endpoint;
if (hash_insert(fd_info->pid2proto, pid2proto)) { if (hash_insert(fd_info->pid2proto, pid2proto)) {
/* TODO something if it fails */ 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);
} }
} }
@@ -168,8 +178,11 @@ static int max_forking_connections(void)
{ {
int max_cnx = 0; int max_cnx = 0;
for (int i = 0; i < cfg.protocols_len; i++) { for (int i = 0; i < cfg.protocols_len; i++) {
if (cfg.protocols[i].fork) if (cfg.protocols[i].fork) {
max_cnx += cfg.protocols[i].max_connections; 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; return max_cnx;
} }
@@ -183,7 +196,7 @@ static int next_power_of_two(int n)
return n + 1; return n + 1;
} }
void loop_init(struct loop_info* loop, int num_addr_listen) void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen)
{ {
memset(loop, 0, sizeof(*loop)); memset(loop, 0, sizeof(*loop));
loop->collection = collection_init(0); loop->collection = collection_init(0);
@@ -193,6 +206,7 @@ void loop_init(struct loop_info* loop, int num_addr_listen)
udp_init(loop); udp_init(loop);
tcp_init(); tcp_init();
loop->listen_sockets = listen_sockets;
loop->num_addr_listen = num_addr_listen; loop->num_addr_listen = num_addr_listen;
int max_forks = next_power_of_two(max_forking_connections()); int max_forks = next_power_of_two(max_forking_connections());
+6 -1
View File
@@ -23,6 +23,8 @@ struct loop_info {
hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */ hash* pid2proto; /* to follow which forked PID is processing what protocol for connection count */
watchers* watchers; watchers* watchers;
struct listen_endpoint* listen_sockets;
int num_addr_listen; /* How many listen endpoints do we have here */ int num_addr_listen; /* How many listen endpoints do we have here */
cnx_collection* collection; /* Collection of connections linked to this loop */ cnx_collection* collection; /* Collection of connections linked to this loop */
@@ -32,11 +34,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); 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); int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
void loop_init(struct loop_info* loop, int num_addr_listen); void loop_init(struct loop_info* loop, struct listen_endpoint* listen_sockets, int num_addr_listen);
void remember_child_data(struct loop_info* fd_info, void remember_child_data(struct loop_info* fd_info,
struct connection* cnx, pid_t pid); struct connection* cnx, pid_t pid);
void decrease_forked_connection(struct loop_info* loop, 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 */ /* These must be declared in the loop handler, sslh-ev or sslh-select */
void watchers_add_read(watchers* w, int fd); void watchers_add_read(watchers* w, int fd);
@@ -46,4 +49,6 @@ void watchers_del_write(watchers* w, int fd);
void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid); void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pid);
void close_listen_endpoints(struct loop_info* loop);
#endif #endif
+2 -2
View File
@@ -112,7 +112,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
if (res == -1) return -1; if (res == -1) return -1;
pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family); pp_info_in_v1.address_family = family_to_pp(addr.ai_addr->sa_family);
res = get_info(cnx->q[1].fd, SOCK, res = get_info(cnx->q[0].fd, SOCK,
&addr, &addr,
&pp_info_in_v1.dst_addr, &pp_info_in_v1.dst_addr,
&pp_info_in_v1.dst_port &pp_info_in_v1.dst_port
@@ -122,7 +122,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error); uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error);
if (!pp1_hdr) { if (!pp1_hdr) {
print_message(msg_system_error, "pp_create_hrd:%d:%s\n", error, pp_strerror(error)); print_message(msg_system_error, "pp_create_hdr:%d:%s\n", error, pp_strerror(error));
return -1; return -1;
} }
defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len); defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len);
-27
View File
@@ -1,27 +0,0 @@
[Unit]
Description=SSL/SSH multiplexer (select mode) for %I
After=network.target
[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
MountFlags=private
NoNewPrivileges=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
DynamicUser=true
[Install]
WantedBy=multi-user.target
+3 -3
View File
@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=SSL/SSH multiplexer (fork mode) for %I Description=SSL/SSH multiplexer for %I
After=network.target After=network.target
[Service] [Service]
@@ -8,8 +8,8 @@ ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process KillMode=process
#Hardening #Hardening
PrivateTmp=true PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_SETGIT CAP_SETUID CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
SecureBits=noroot-locked SecureBits=noroot-locked
ProtectSystem=strict ProtectSystem=strict
ProtectHome=true ProtectHome=true
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Fri Aug 29 20:43:29 2025. * on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs # conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle # Copyright (C) 2018-2024 Yves Rutschle
+1 -1
View File
@@ -1,5 +1,5 @@
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
* on Fri Aug 29 20:43:29 2025. * on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs # conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024 Yves Rutschle # Copyright (C) 2018-2024 Yves Rutschle
+2 -3
View File
@@ -207,13 +207,12 @@ void watcher_sigchld(struct loop_info* fd_info, struct connection* cnx, pid_t pi
ev_child_start(EV_DEFAULT_ cw); ev_child_start(EV_DEFAULT_ cw);
} }
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{ {
struct loop_info ev_info; struct loop_info ev_info;
loop = EV_DEFAULT; loop = EV_DEFAULT;
loop_init(&ev_info, num_addr_listen); loop_init(&ev_info, listen_sockets, num_addr_listen);
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen); watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info); ev_set_userdata(EV_A_ &ev_info);
@@ -221,7 +220,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
ev_run(EV_A_ 0); ev_run(EV_A_ 0);
} }
void start_shoveler(int listen_socket) { void main_inetd(void) {
print_message(msg_config_error, "inetd mode is not supported in libev mode\n"); print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
exit(1); exit(1);
} }
+19 -2
View File
@@ -69,7 +69,7 @@ int shovel(struct connection *cnx)
/* Child process that finds out what to connect to and proxies /* Child process that finds out what to connect to and proxies
*/ */
void start_shoveler(int in_socket) static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
{ {
fd_set fds; fd_set fds;
struct timeval tv; struct timeval tv;
@@ -79,6 +79,7 @@ void start_shoveler(int in_socket)
init_cnx(&cnx); init_cnx(&cnx);
cnx.q[0].fd = in_socket; cnx.q[0].fd = in_socket;
cnx.endpoint = endpoint;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(in_socket, &fds); FD_SET(in_socket, &fds);
@@ -130,6 +131,22 @@ void start_shoveler(int in_socket)
exit(0); exit(0);
} }
void main_inetd(void)
{
struct listen_endpoint endpoint = {0};
struct sslhcfg_listen_item endpoint_cfg = {0};
/* Empty configuration: no connection limits, not proxyprotocol... */
endpoint.endpoint_cfg = &endpoint_cfg;
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0, &endpoint);
exit(0);
}
static pid_t *listener_pid; static pid_t *listener_pid;
static int listener_pid_number = 0; static int listener_pid_number = 0;
@@ -252,7 +269,7 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
/* Shoveler processes don't need to hog file descriptors */ /* Shoveler processes don't need to hog file descriptors */
for (i = 0; i < num_endpoints; i++) for (i = 0; i < num_endpoints; i++)
close(endpoint[i].socketfd); close(endpoint[i].socketfd);
start_shoveler(in_socket); start_shoveler(in_socket, endpoint);
exit(0); exit(0);
default: /* In parent process */ default: /* In parent process */
+101 -5
View File
@@ -220,6 +220,62 @@ static void config_protocols()
} }
} }
static void config_sanity_regex(const struct sslhcfg_protocols_item* prot)
{
if (prot->regex_patterns_len > 0) {
if (strcmp(prot->name, "regex") != 0) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"regex_patterns setting is only applicable to `regex' probe. Ignoring setting.\n",
prot->name, prot->host, prot->port);
}
#ifndef ENABLE_REGEX
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Uses regex_patterns, but libpcre2 support was not compiled in. Ignoring setting.\n",
prot->name, prot->host, prot->port);
#endif
}
}
/* If user specified proxyprotocol but it is not compiled in, it should not be
* ignored as the resulting configuration will most likely not work. In that
* case, die. */
static void config_sanity_proxyprotocol(const struct sslhcfg_protocols_item* prot)
{
#ifndef HAVE_PROXYPROTOCOL
if (prot->proxyprotocol_is_present) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"Uses proxyprotocol, but libproxyprotocol support was not compiled in.\n",
prot->name, prot->host, prot->port);
exit(1);
}
#endif
}
static void config_sanity_listen_proxyprotocol(const struct sslhcfg_listen_item* endpoint)
{
#ifndef HAVE_PROXYPROTOCOL
if (endpoint->proxyprotocol) {
print_message(msg_config_error, "listen on host: \"%s\"; port: \"%s\": "
"Uses proxyprotocol, but libproxyprotocol support was not compiled in.\n",
endpoint->host, endpoint->port);
exit(1);
}
#endif
}
/* If user specifies libwrap setting but it is not compiled in, it should
* result in a working configuration, but not what they expected, so warn */
static void config_sanity_libwrap(const struct sslhcfg_protocols_item* prot)
{
#ifndef HAVE_LIBWRAP
if (prot->service_is_present) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
"WARNING: `service' specified but libwrap not compiled in. Ignoring setting.\n",
prot->name, prot->host, prot->port);
}
#endif
}
void config_sanity_check(struct sslhcfg_item* cfg) void config_sanity_check(struct sslhcfg_item* cfg)
{ {
@@ -233,6 +289,10 @@ void config_sanity_check(struct sslhcfg_item* cfg)
} }
#endif #endif
for (i = 0; i < cfg->listen_len; i++) {
config_sanity_listen_proxyprotocol(&cfg->listen[i]);
}
for (i = 0; i < cfg->protocols_len; ++i) { for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "tls") != 0) { if (strcmp(cfg->protocols[i].name, "tls") != 0) {
if (cfg->protocols[i].sni_hostnames_len) { if (cfg->protocols[i].sni_hostnames_len) {
@@ -249,6 +309,10 @@ void config_sanity_check(struct sslhcfg_item* cfg)
} }
} }
config_sanity_regex(&cfg->protocols[i]);
config_sanity_proxyprotocol(&cfg->protocols[i]);
config_sanity_libwrap(&cfg->protocols[i]);
if (cfg->protocols[i].is_udp) { if (cfg->protocols[i].is_udp) {
if (cfg->protocols[i].tfo_ok) { if (cfg->protocols[i].tfo_ok) {
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": " print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
@@ -284,6 +348,41 @@ void close_std(void)
} }
} }
static void print_version(void)
{
printf("%s %s\n", server_type, VERSION);
#ifdef ENABLE_SANITIZER
printf("ENABLE_SANITIZER\n");
#endif
#ifdef ENABLE_REGEX
printf("ENABLE_REGEX\n");
#endif
#ifdef LIBCONFIG
printf("LIBCONFIG\n");
#endif
#ifdef SYSTEMD
printf("SYSTEMD\n");
#endif
#ifdef COV_TEST
printf("COV_TEST\n");
#endif
#ifdef HAVE_LIBWRAP
printf("HAVE_LIBWRAP\n");
#endif
#ifdef HAVE_LANDLOCK
printf("HAVE_LANDLOCK\n");
#endif
#ifdef HAVE_PROXYPROTOCOL
printf("HAVE_PROXYPROTOCOL\n");
#endif
#ifdef HAVE_LIBCAP
printf("HAVE_LIBCAP\n");
#endif
#ifdef HAVE_LIBBSD
printf("HAVE_LIBBSD\n");
#endif
}
int main(int argc, char *argv[], char* envp[]) int main(int argc, char *argv[], char* envp[])
{ {
int res, num_addr_listen; int res, num_addr_listen;
@@ -299,7 +398,7 @@ int main(int argc, char *argv[], char* envp[])
config_finish(&cfg); config_finish(&cfg);
if (cfg.version) { if (cfg.version) {
printf("%s %s\n", server_type, VERSION); print_version();
exit(0); exit(0);
} }
@@ -308,10 +407,7 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd) if (cfg.inetd)
{ {
close(fileno(stderr)); /* Make sure no error will go to client */ main_inetd(); /* Does not return */
tcp_init();
start_shoveler(0);
exit(0);
} }
printsettings(); printsettings();
+6 -4
View File
@@ -131,6 +131,7 @@ static void sigchld_process(struct loop_info* loop)
received_sigchld = 0; received_sigchld = 0;
do { do {
chld = waitpid(-1, NULL, WNOHANG); chld = waitpid(-1, NULL, WNOHANG);
if ((chld == -1) && (errno == ECHILD)) return;
CHECK_RES_RETURN(chld, "waitpid", ); CHECK_RES_RETURN(chld, "waitpid", );
if (chld) { if (chld) {
decrease_forked_connection(loop, chld); decrease_forked_connection(loop, chld);
@@ -203,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
int i, res; int i, res;
setup_sigalrm(); setup_sigalrm();
loop_init(&fd_info, num_addr_listen); loop_init(&fd_info, listen_sockets, num_addr_listen);
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen); watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
@@ -221,8 +222,9 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
fd_info.watchers->max_fd, fd_info.num_probing); fd_info.watchers->max_fd, fd_info.num_probing);
res = select(fd_info.watchers->max_fd, &readfds, &writefds, res = select(fd_info.watchers->max_fd, &readfds, &writefds,
NULL, fd_info.num_probing ? &tv : NULL); NULL, fd_info.num_probing ? &tv : NULL);
if (res < 0) if ((res == -1) && ((errno != EAGAIN) && (errno != EINTR)))
perror("select"); print_message(msg_system_error, "%s:%d:%d:%s\n",
__FILE__, __LINE__, errno, strerror(errno)); \
/* Check main socket for new connections */ /* Check main socket for new connections */
for (i = 0; i < num_addr_listen; i++) { for (i = 0; i < num_addr_listen; i++) {
@@ -290,7 +292,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
} }
void start_shoveler(int listen_socket) { void main_inetd(void) {
print_message(msg_config_error, "inetd mode is not supported in select mode\n"); print_message(msg_config_error, "inetd mode is not supported in select mode\n");
exit(1); exit(1);
} }
+70 -8
View File
@@ -10,6 +10,7 @@
# ./run # run all tests # ./run # run all tests
# ./run -l # list all tests # ./run -l # list all tests
# ./run 1 3 5 # run specified tests # ./run 1 3 5 # run specified tests
# ./run --binary sslh-ev # run only one binary
use strict; use strict;
use IO::Socket::INET6; use IO::Socket::INET6;
@@ -17,9 +18,13 @@ use Test::More qw/no_plan/;
use Conf::Libconfig 1.0.3; use Conf::Libconfig 1.0.3;
use Getopt::Long; use Getopt::Long;
my ($coverage, $list_tests); # --cover: request test coverage analysis (not implemented)
# --binary: specify which binary to run (default: all)
# --list: list all available tests (do not run any)
my ($coverage, $list_tests, $param_binary);
GetOptions( GetOptions(
'cover' => \$coverage, 'cover' => \$coverage,
'binary=s' => \$param_binary,
'list' => \$list_tests, 'list' => \$list_tests,
); );
@@ -192,7 +197,8 @@ sub run_ending_test
# Runs one test for one probe. Start echosrv's if required. # Runs one test for one probe. Start echosrv's if required.
# Connect to port specified in the test, otherwise to the # Connect to port specified in the test, otherwise to the
# first port in the sslh configuration. # first port in the sslh configuration; to host specified in
# the test, otherwise to localhost
# run sslh, connect, write the test pattern, read the result, # run sslh, connect, write the test pattern, read the result,
# check it connected to the right echosrv, check the data # check it connected to the right echosrv, check the data
# was transfered ok. # was transfered ok.
@@ -206,9 +212,10 @@ sub run_test_probe
my $expected = $test->{expected}; my $expected = $test->{expected};
my $sslh_host = $test->{host} // "localhost";
my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port}; my $sslh_port = $test->{port} // $conf->value("listen")->[0]->{port};
print "test_probe [$expected] $sslh_port\n"; print "test_probe [$expected] $sslh_host:$sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); my $cnx = new IO::Socket::INET(PeerHost => "$sslh_host:$sslh_port");
warn "t: $!\n" unless $cnx; warn "t: $!\n" unless $cnx;
return unless $cnx; return unless $cnx;
@@ -316,6 +323,7 @@ my @tests = (
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA", data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ \\d+\x0D\x0AINPUT_DATA",
}, },
# check socks5 does not # check socks5 does not
{ {
desc => "Server-side ProxyProtocol not active", desc => "Server-side ProxyProtocol not active",
@@ -325,6 +333,55 @@ my @tests = (
expected => "socks5", expected => "socks5",
}, },
# Check proxyprotocol from IPv4 to IPv4 works.
{
desc => "Server-side IPv4 to IPv4 ProxyProtocol active",
host => "ip4-localhost",
run => \&run_test_probe,
cfg => "test_pp3.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP4 127.0.0.1 127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# Check pp with IPv6 to IPv6 works.
{
desc => "Server-side IPv6 to IPv6 ProxyProtocol active",
host => "ip6-localhost",
run => \&run_test_probe,
cfg => "test_pp4.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# Check pp with IPv4 to IPv6 works.
{
desc => "Server-side IPv4 to IPv6 ProxyProtocol active",
host => "ip4-localhost",
run => \&run_test_probe,
cfg => "test_pp5.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# Check pp with IPv6 to IPv4 works.
{
desc => "Server-side IPv6 to IPv4 ProxyProtocol active",
host => "ip6-localhost",
run => \&run_test_probe,
cfg => "test_pp5.cfg",
data => "SSH-2.0 hello",
expected => "ssh",
data_expected => "^PROXY TCP6 ::ffff:127.0.0.1 ::ffff:127.0.0.1 \\d+ 8080\x0D\x0AINPUT_DATA",
},
# test_pp2.cfg has proxyprotocol on port 8080 # test_pp2.cfg has proxyprotocol on port 8080
{ {
desc => "Client-side ProxyProtocol active", desc => "Client-side ProxyProtocol active",
@@ -388,10 +445,15 @@ if ($list_tests) {
################################################################################ ################################################################################
# Run selected tests # Run selected tests
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n"; my @binaries = qw/sslh-fork sslh-select sslh-ev/;
my $binary = 'sslh-ev'; @binaries = $param_binary if defined $param_binary;
my $code = ($test->{run})->($binary, $test);
foreach my $binary (@binaries) {
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $code = ($test->{run})->($binary, $test);
}
} }
stop_echosrv(); stop_echosrv();
+4 -4
View File
@@ -29,15 +29,15 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
# Options: # Options:
listen: listen:
( (
{ host: "localhost"; port: "8080"; keepalive: true; } { host: "ip4-localhost"; port: "8080"; keepalive: true; }
); );
protocols: protocols:
( (
{ name: "ssh"; host: "localhost"; port: "9000"; proxyprotocol: 1; }, { name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "localhost"; port: "9001"; }, { name: "socks5"; host: "ip4-localhost"; port: "9001"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; } { name: "anyprot"; host: "ip4-localhost"; port: "9099"; }
); );
on_timeout: "ssh"; on_timeout: "ssh";
+44
View File
@@ -0,0 +1,44 @@
# Testing for server-side ProxyProtocol, IPv4 to IPv4
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: 1; # 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: "ip4-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on_timeout: "ssh";
+44
View File
@@ -0,0 +1,44 @@
# Testing for server-side ProxyProtocol, IPv6 to IPv6
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: 1; # 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: "ip6-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip6-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
);
on_timeout: "ssh";
+44
View File
@@ -0,0 +1,44 @@
# Testing for server-side ProxyProtocol, IPv4 to IPv6
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: 1; # 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: "ip4-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip6-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
);
on_timeout: "ssh";
+44
View File
@@ -0,0 +1,44 @@
# Testing for server-side ProxyProtocol, IPv6 to IPv4
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: 1; # 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: "ip6-localhost"; port: "8080"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip6-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip6-localhost"; port: "9099"; }
);
on_timeout: "ssh";
+45 -7
View File
@@ -165,6 +165,7 @@ struct connection* accept_new_connection(struct listen_endpoint* endpoint, struc
print_message(msg_fd, "accepting from %d\n", listen_socket); print_message(msg_fd, "accepting from %d\n", listen_socket);
in_socket = accept(listen_socket, 0, 0); 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); CHECK_RES_RETURN(in_socket, "accept", NULL);
res = set_nonblock(in_socket); res = set_nonblock(in_socket);
@@ -295,13 +296,50 @@ static void connect_proxy(struct connection *cnx)
} }
/* This ensures the kernel will close the listening socket when the
* parent process closes; otherwise, the listening socket is kept
* around by the inactive file descriptor in the child */
void close_listen_endpoints(struct loop_info* loop)
{
struct listen_endpoint* listen_sockets = loop->listen_sockets;
watchers* w = loop->watchers;
for (int i = 0; i < loop->num_addr_listen; i++) {
int fd = listen_sockets[i].socketfd;
watchers_del_read(w, fd);
watchers_del_write(w, fd);
close(fd);
}
}
/* Close all connections except specified.
* This saves file descriptors and allocated memory when forking */
void tidy_other_connections(struct loop_info* fd_info, struct connection* keep_cnx)
{
struct connection* cnx;
cnx_collection* cnx_coll = fd_info->collection;
for (int i = 0; i < collection_max_fd(cnx_coll); i++) {
cnx = collection_get_cnx_from_fd(cnx_coll, i);
if (cnx && (cnx != keep_cnx))
tidy_connection(cnx, fd_info);
}
}
/* Forks a shoveler process for one protocol */ /* Forks a shoveler process for one protocol */
void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) { void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
pid_t pid; 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()) { switch (pid = fork()) {
case 0: /* child */ case 0: /* child */
/* TODO: close all file descriptors except 2 */ close_listen_endpoints(fd_info);
/* free(cnx); */ tidy_other_connections(fd_info, cnx);
connect_proxy(cnx); connect_proxy(cnx);
exit(0); exit(0);
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno)); case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
@@ -311,11 +349,6 @@ void fork_shoveling_process(struct loop_info* fd_info, struct connection* cnx) {
watcher_sigchld(fd_info, cnx, pid); watcher_sigchld(fd_info, cnx, pid);
break; break;
} }
/* Free file descriptor (used only in child), but do not reduce connection
* counts */
cnx->proto = NULL;
cnx->endpoint = NULL;
tidy_connection(cnx, fd_info);
} }
/* Process read activity on a socket in probe state /* Process read activity on a socket in probe state
@@ -349,6 +382,11 @@ void probing_read_process(struct connection* cnx,
tidy_connection(cnx, fd_info); tidy_connection(cnx, fd_info);
} else if (cnx->proto->fork) { } else if (cnx->proto->fork) {
fork_shoveling_process(fd_info, cnx); 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);
} else { } else {
connect_queue(cnx, fd_info); connect_queue(cnx, fd_info);
} }
+1 -1
View File
@@ -277,7 +277,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
for (i = 0; i < list_len; i++) { for (i = 0; i < list_len; i++) {
item = &list[i]; item = &list[i];
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item); print_message(msg_probe_info, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
if(!fnmatch(*item, name_nullterminated, 0)) { if(!fnmatch(*item, name_nullterminated, 0)) {
free(name_nullterminated); free(name_nullterminated);
return 1; return 1;
-5
View File
@@ -1,5 +0,0 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION "v2.2.4-52-geea50a9-dirty"
#endif