Stefan Meinecke bb89714216 misc hardening: connection drop, NULL deref, OOB writes, dead option
- shovel(): fd2fd() returns FD_NODATA (-1) when a spurious wakeup
  meets an empty socket (EAGAIN), but the switch treated -1 like
  FD_CNXCLOSED and tore down a healthy connection. Only FD_CNXCLOSED
  closes now.

- decrease_forked_connection(): hash_find() can return NULL (stale
  SIGCHLD after a fork-limit rejection); dereferencing it crashed.
  Guard and log instead.

- hexdump(): sprintf() into a 76-byte stack buffer with a "%06x"
  offset that needs 12 chars once the dump passes 16 MB. Enlarge the
  buffer and bound all writes with snprintf().

- printsettings(): sprintf() of config-supplied host strings into a
  fixed buffer; use snprintf().

- echosrv: recvfrom() into data+prefix_len with size
  sizeof(data)-prefix_len lets the terminating NUL write land one
  byte past the stack buffer (verified with ASan at udp_echo, and a
  >64KB prefix overflows the memcpy). Bound the prefix, reserve one
  byte for the terminator, and skip the echo on recvfrom() error
  instead of writing data[prefix_len-1].

- msg_int_error used verbose_system_error, leaving the documented
  --verbose-int-error option dead. Point it at its own setting.

- Remove "echoѕrv-conf.h" (with Cyrillic ѕ, U+0455): a stale 2020
  sslh-conf.h copy committed under a filename that is visually
  identical to echosrv-conf.h but referenced by nothing.
2026-10-18 22:53:40 +02:00
2025-05-04 11:45:33 +02:00
2023-07-30 01:43:12 -04:00
2023-07-30 01:43:12 -04:00
2026-08-02 09:03:51 +02:00
2025-02-21 21:03:59 +01:00
2025-02-21 21:03:59 +01:00
2014-03-30 18:09:16 +02:00
2025-12-20 17:23:10 +01:00
2022-04-10 09:03:53 +02:00
2025-09-10 15:50:52 +02:00
2025-09-10 15:50:52 +02:00
2026-05-31 09:58:57 +02:00
2022-04-28 15:19:18 +02:00
2026-09-19 18:20:34 +02:00
2025-09-10 15:50:52 +02:00
2025-09-10 15:50:52 +02:00
2026-10-18 17:34:21 +02:00
2023-09-12 21:35:10 +02:00
2026-09-19 18:19:35 +02:00
2026-10-07 15:20:21 +02:00
2020-12-06 15:50:08 +01:00

sslh -- A ssl/ssh multiplexer

sslh accepts connections on specified ports, and forwards them further based on tests performed on the first data packet sent by the remote client.

Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, WireGuard, OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other protocol that can be tested using a regular expression, can be recognised. A typical use case is to allow serving several services on port 443 (e.g. to connect to SSH from inside a corporate firewall, which almost never block port 443) while still serving HTTPS on that port.

Hence sslh acts as a protocol demultiplexer, or a switchboard. With the SNI and ALPN probe, it makes a good front-end to a virtual host farm hosted behind a single IP address.

sslh has the bells and whistles expected from a mature daemon: privilege and capabilities dropping, inetd support, systemd support, transparent proxying, support for HAProxy's proxyprotocol, chroot, logging, IPv4 and IPv6, TCP and UDP, a fork-based, a select-based model, and yet another based on libev for larger installations.

Install

Please refer to the install guide.

Security

Matthias Gerstner from OpenSUSE has performed a code review of sslh from a security point of view, which revealed a number of problems, including two CVE. His findings have already been taken partly into account for the more critical ones. The full review is well worth reading if you are using sslh in production.

Part of the securing your installation involves configuring connection limits. This is described in this guide.

Configuration

Please refer to the configuration guide.

Transparent proxying

Transparent proxying allows the target server to see the original client IP address, i.e. sslh becomes invisible.

The same result can be achieved more easily by using proxyprotocol if the backend server supports it. This is a simple setting to add to the sslh protocol configuration, usually with an equivalently simple setting to add in the backend server configuration, so try that first. This is explained in a separate document.

This means services behind sslh (Apache, sshd and so on) will see the external IP and ports as if the external world connected directly to them. This simplifies IP-based access control (or makes it possible at all), and makes it possible to use IP-based banning tools such as fail2ban.

There are two methods. One uses additional virtual network interfaces. The principle and basic setup is described here, with further scenarios described there.

There is also a guide to use podman.

Another method uses iptable packet marking features, and is highly dependent on your network environment and infrastructure setup. There is no known generic approach, and if you do not find directions for your exact setup, you will probably need an extensive knowledge of network management and iptables setup".

It is described in its own document. In most cases, you will be better off following the first method.

Docker image

How to use


docker run \
  --cap-add CAP_NET_RAW \
  --cap-add CAP_NET_BIND_SERVICE \
  --rm \
  -it \
  ghcr.io/yrutschle/sslh:latest \
  --foreground \
  --listen=0.0.0.0:443 \
  --ssh=hostname:22 \
  --tls=hostname:443

docker-compose example

services:
  sslh:
    image: ghcr.io/yrutschle/sslh:latest
    hostname: sslh
    ports:
      - 443:443
    command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
    depends_on:
      - nginx
      - openvpn

  nginx:
    image: nginx

  openvpn:
    image: openvpn

Transparent mode 1: using sslh container for networking

Note: For transparent mode to work, the sslh container must be able to reach your services via localhost

services:
  sslh:
    build: https://github.com/yrutschle/sslh.git
    container_name: sslh
    environment:
      - TZ=${TZ}
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - NET_BIND_SERVICE
    sysctls:
      - net.ipv4.conf.default.route_localnet=1
      - net.ipv4.conf.all.route_localnet=1
    command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
    ports:
      - 443:443 #sslh

      - 80:80 #nginx
      - 8443:8443 #nginx

      - 1194:1194 #openvpn
    extra_hosts:
      - localbox:host-gateway
    restart: unless-stopped

  nginx:
    image: nginx:latest
    .....
    network_mode: service:sslh #set nginx container to use sslh networking.
    # ^^^ This is required. This makes nginx reachable by sslh via localhost
  
  openvpn:
    image: openvpn:latest
    .....
    network_mode: service:sslh #set openvpn container to use sslh networking

Transparent mode 2: using host networking

services:
  sslh:
    build: https://github.com/yrutschle/sslh.git
    container_name: sslh
    environment:
      - TZ=${TZ}
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - NET_BIND_SERVICE
    # must be set manually
    #sysctls:
    #  - net.ipv4.conf.default.route_localnet=1
    #  - net.ipv4.conf.all.route_localnet=1
    command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
    network_mode: host
    restart: unless-stopped
  
  nginx:
    image: nginx:latest
    .....
    ports:
      - 8443:8443 # bind to docker host on port 8443

  openvpn:
    image: openvpn:latest
    .....
    ports:
      - 1194:1194 # bind to docker host on port 1194

Comments? Questions?

You can subscribe to the sslh mailing list here: https://lists.rutschle.net/mailman/listinfo/sslh

This mailing list should be used for discussion, feature requests, and will be the preferred channel for announcements.

Of course, check the FAQ first!

S
Description
No description provided
Readme GPL-2.0
3.1 MiB
0 Stars 1 Watchers 0 Forks
Languages
C 89.8%
Perl 6.6%
Shell 2.1%
Makefile 0.8%
Scilab 0.4%
Other 0.3%