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
`protocol` configuration; see the
[guide](doc/max_connections.md) for more
@@ -6,7 +35,8 @@ vNEXT:
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)
+26 -3
View File
@@ -3,8 +3,7 @@ ARG TARGET_ARCH="library"
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
WORKDIR /sslh
# Install build dependencies for sslh and libproxyprotocol
RUN apk add --no-cache \
'gcc' \
'libconfig-dev' \
@@ -12,15 +11,39 @@ RUN apk add --no-cache \
'musl-dev' \
'pcre2-dev' \
'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
# Configure and build sslh
# The configure script should automatically detect libproxyprotocol
RUN ./configure && make sslh-select && strip sslh-select
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"
RUN apk add --no-cache \
'libconfig' \
'pcre2' \
@@ -35,4 +58,4 @@ COPY "./container-entrypoint.sh" "/init"
ENTRYPOINT [ "/init" ]
# 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
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
ifneq ($(strip $(COV_TEST)),)
CFLAGS_COV=-fprofile-arcs -ftest-coverage
CFLAGS_COV=-DCOV_TEST -fprofile-arcs -ftest-coverage
endif
CC ?= gcc
+10 -2
View File
@@ -28,6 +28,7 @@
/* Info to keep track of all connections */
struct cnx_collection {
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
@@ -42,6 +43,7 @@ cnx_collection* collection_init(int len)
memset(collection, 0, sizeof(*collection));
collection->fd2cnx = gap_init(len);
collection->max_fd = 0;
return collection;
}
@@ -54,10 +56,17 @@ void collection_destroy(cnx_collection* collection)
free(collection);
}
int collection_max_fd(cnx_collection* collection)
{
return collection->max_fd;
}
/* Points the file descriptor to the specified connection index */
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
{
gap_set(collection->fd2cnx, fd, cnx);
if (fd > collection->max_fd)
collection->max_fd = fd;
return 0;
}
@@ -74,8 +83,7 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
cnx->state = ST_PROBING;
cnx->probe_timeout = time(NULL) + cfg.timeout;
gap_set(collection->fd2cnx, fd, cnx);
collection_add_fd(collection, cnx, fd);
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);
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 */
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
+4 -10
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
@@ -428,9 +422,9 @@ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking bl
cnx->proto->port);
}
for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent or using proxyprotocol, make sure both
* connections use 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))
/* When transparent, make sure both connections use
* the same address family (e.g. IP4 on both sides) */
if (transparent && (a->ai_family != from.ai_addr->sa_family))
continue;
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
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)) {
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);
return -1;
}
+8 -1
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>
@@ -197,7 +204,7 @@ extern int hosts_ctl();
#endif
/* sslh-fork.c */
void start_shoveler(int);
void main_inetd(void);
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)
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:
@@ -95,26 +97,60 @@ protocols:
);
```
I omitted caddy configs here as it's not important. Some unrelated container configs were also dropped.
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).
I omitted Caddy configs here since they're irrelevant, and some unrelated container configs were also dropped for clarity.
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)
```
- 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: "tls"; host: "localhost"; port: "8080"; proxyprotocol: 2; }
);
);
```
@@ -77,7 +76,6 @@ protocols:
(
{ name: "ssh"; host: "localhost"; port: "2222"; proxyprotocol: 0; },
{ name: "tls"; host: "localhost"; port: "8080"; }
);
);
```
+1 -1
View File
@@ -1,5 +1,5 @@
/* 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
# 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 Fri Aug 29 20:43:29 2025.
* on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs
# 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)
# 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"; 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;
}
int hash_insertable(hash* h)
{
return (h->item_cnt < h->hash_size);
}
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);
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
@@ -118,7 +118,7 @@ static int add_resolv(int ruleset_fd)
static int add_libwrap(int ruleset_fd)
{
/* 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.deny");
#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)
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 &&
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2 &&
(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_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)
@@ -152,6 +152,13 @@ void decrease_forked_connection(struct loop_info* loop, pid_t pid) {
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)
{
@@ -160,7 +167,10 @@ void remember_child_data(struct loop_info* fd_info,
pid2proto->proto = cnx->proto;
pid2proto->endpoint = cnx->endpoint;
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;
for (int i = 0; i < cfg.protocols_len; i++) {
if (cfg.protocols[i].fork)
max_cnx += cfg.protocols[i].max_connections;
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;
}
@@ -183,7 +196,7 @@ static int next_power_of_two(int n)
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));
loop->collection = collection_init(0);
@@ -193,6 +206,7 @@ void loop_init(struct loop_info* loop, int num_addr_listen)
udp_init(loop);
tcp_init();
loop->listen_sockets = listen_sockets;
loop->num_addr_listen = num_addr_listen;
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 */
watchers* watchers;
struct listen_endpoint* listen_sockets;
int num_addr_listen; /* How many listen endpoints do we have here */
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);
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,
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);
@@ -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 close_listen_endpoints(struct loop_info* loop);
#endif
+2 -2
View File
@@ -112,7 +112,7 @@ int pp_write_header(int pp_version, struct connection* cnx)
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, SOCK,
res = get_info(cnx->q[0].fd, SOCK,
&addr,
&pp_info_in_v1.dst_addr,
&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);
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;
}
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]
Description=SSL/SSH multiplexer (fork mode) for %I
Description=SSL/SSH multiplexer for %I
After=network.target
[Service]
@@ -8,8 +8,8 @@ ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_SETGIT CAP_SETUID CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
+1 -1
View File
@@ -1,5 +1,5 @@
/* 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
# 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 Fri Aug 29 20:43:29 2025.
* on Wed Sep 10 15:49:49 2025.
# conf2struct: generate libconf parsers that read to structs
# 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);
}
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
{
struct loop_info ev_info;
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);
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);
}
void start_shoveler(int listen_socket) {
void main_inetd(void) {
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
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
*/
void start_shoveler(int in_socket)
static void start_shoveler(int in_socket, struct listen_endpoint* endpoint)
{
fd_set fds;
struct timeval tv;
@@ -79,6 +79,7 @@ void start_shoveler(int in_socket)
init_cnx(&cnx);
cnx.q[0].fd = in_socket;
cnx.endpoint = endpoint;
FD_ZERO(&fds);
FD_SET(in_socket, &fds);
@@ -130,6 +131,22 @@ void start_shoveler(int in_socket)
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 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 */
for (i = 0; i < num_endpoints; i++)
close(endpoint[i].socketfd);
start_shoveler(in_socket);
start_shoveler(in_socket, endpoint);
exit(0);
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)
{
@@ -233,6 +289,10 @@ void config_sanity_check(struct sslhcfg_item* cfg)
}
#endif
for (i = 0; i < cfg->listen_len; i++) {
config_sanity_listen_proxyprotocol(&cfg->listen[i]);
}
for (i = 0; i < cfg->protocols_len; ++i) {
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
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].tfo_ok) {
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 res, num_addr_listen;
@@ -299,7 +398,7 @@ int main(int argc, char *argv[], char* envp[])
config_finish(&cfg);
if (cfg.version) {
printf("%s %s\n", server_type, VERSION);
print_version();
exit(0);
}
@@ -308,10 +407,7 @@ int main(int argc, char *argv[], char* envp[])
if (cfg.inetd)
{
close(fileno(stderr)); /* Make sure no error will go to client */
tcp_init();
start_shoveler(0);
exit(0);
main_inetd(); /* Does not return */
}
printsettings();
+6 -4
View File
@@ -131,6 +131,7 @@ static void sigchld_process(struct loop_info* loop)
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);
@@ -203,7 +204,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
int i, res;
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);
@@ -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);
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++) {
@@ -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");
exit(1);
}
+70 -8
View File
@@ -10,6 +10,7 @@
# ./run # run all tests
# ./run -l # list all tests
# ./run 1 3 5 # run specified tests
# ./run --binary sslh-ev # run only one binary
use strict;
use IO::Socket::INET6;
@@ -17,9 +18,13 @@ use Test::More qw/no_plan/;
use Conf::Libconfig 1.0.3;
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(
'cover' => \$coverage,
'binary=s' => \$param_binary,
'list' => \$list_tests,
);
@@ -192,7 +197,8 @@ sub run_ending_test
# 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.
# 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,
# check it connected to the right echosrv, check the data
# was transfered ok.
@@ -206,9 +212,10 @@ sub run_test_probe
my $expected = $test->{expected};
my $sslh_host = $test->{host} // "localhost";
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");
print "test_probe [$expected] $sslh_host:$sslh_port\n";
my $cnx = new IO::Socket::INET(PeerHost => "$sslh_host:$sslh_port");
warn "t: $!\n" 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",
},
# check socks5 does not
{
desc => "Server-side ProxyProtocol not active",
@@ -325,6 +333,55 @@ my @tests = (
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
{
desc => "Client-side ProxyProtocol active",
@@ -388,10 +445,15 @@ if ($list_tests) {
################################################################################
# Run selected tests
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $binary = 'sslh-ev';
my $code = ($test->{run})->($binary, $test);
my @binaries = qw/sslh-fork sslh-select sslh-ev/;
@binaries = $param_binary if defined $param_binary;
foreach my $binary (@binaries) {
foreach my $test (@tests) {
warn "Running test: $test->{desc}\n";
my $code = ($test->{run})->($binary, $test);
}
}
stop_echosrv();
+4 -4
View File
@@ -29,15 +29,15 @@ verbose-int-error: 1; # internal errors, the kind that should never happen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; }
{ host: "ip4-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"; }
{ name: "ssh"; host: "ip4-localhost"; port: "9000"; proxyprotocol: 1; },
{ name: "socks5"; host: "ip4-localhost"; port: "9001"; },
{ name: "anyprot"; host: "ip4-localhost"; port: "9099"; }
);
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);
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);
@@ -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 */
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); */
close_listen_endpoints(fd_info);
tidy_other_connections(fd_info, cnx);
connect_proxy(cnx);
exit(0);
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);
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
@@ -349,6 +382,11 @@ void probing_read_process(struct connection* cnx,
tidy_connection(cnx, fd_info);
} else if (cnx->proto->fork) {
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 {
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++) {
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)) {
free(name_nullterminated);
return 1;
-5
View File
@@ -1,5 +0,0 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION "v2.2.4-52-geea50a9-dirty"
#endif