mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-25 01:06:20 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a222ea2c99 | ||
|
|
eb84c6a55b | ||
|
|
93ab4f1e3a | ||
|
|
5e252bb3f6 | ||
|
|
d6265e2c50 | ||
|
|
e1229dca28 | ||
|
|
06e697e029 | ||
|
|
0bb3286a7d | ||
|
|
2fd9ea600a | ||
|
|
f4eea875e2 | ||
|
|
e8934f7a81 | ||
|
|
8271db2d9d | ||
|
|
995916c923 | ||
|
|
799d75413f | ||
|
|
8166be1a09 | ||
|
|
684374f353 | ||
|
|
de7351fd84 | ||
|
|
d2ca706f86 | ||
|
|
c859e341aa | ||
|
|
717fe8fae5 | ||
|
|
1ddf45bf52 | ||
|
|
ae7530e33f | ||
|
|
d0a016221c | ||
|
|
9286b55702 | ||
|
|
31c9e19abb | ||
|
|
ef8f3d1419 | ||
|
|
2759c223be | ||
|
|
b3c770898a | ||
|
|
fee8491a8e | ||
|
|
a80d79fd40 | ||
|
|
4b921be69d | ||
|
|
1799a81079 | ||
|
|
5f1c1b1b61 | ||
|
|
58783af410 | ||
|
|
1957be1dc3 | ||
|
|
ecca78bde7 | ||
|
|
b94060ad76 | ||
|
|
736b108a75 | ||
|
|
7ca567fcd9 | ||
|
|
3117c15fbd | ||
|
|
e428fc505c | ||
|
|
4dfb4d300a | ||
|
|
39184b5622 | ||
|
|
940461de18 | ||
|
|
6f949419d1 | ||
|
|
dab5df7409 | ||
|
|
402ca5219b | ||
|
|
046401148d | ||
|
|
780e536aeb | ||
|
|
ed0ab12a16 | ||
|
|
b65f1e8b26 | ||
|
|
91b649daa0 | ||
|
|
7499c26e9e | ||
|
|
90a55b6f9d | ||
|
|
1f66e2e093 | ||
|
|
92d2326016 | ||
|
|
81eed9d56a | ||
|
|
490a44723b | ||
|
|
23fb1eba6f | ||
|
|
be66848e2d | ||
|
|
3e93c1d43d | ||
|
|
1b26eb50a5 | ||
|
|
e0f15a31b7 | ||
|
|
c2551c011e | ||
|
|
e2c3ed61a8 | ||
|
|
1b0c6d0b8d | ||
|
|
0562eb4b07 | ||
|
|
93600d1fb1 | ||
|
|
04f258e705 | ||
|
|
1fb888bbf2 | ||
|
|
1f0cab2aee | ||
|
|
ff9328fa6c | ||
|
|
e941e8dd2e | ||
|
|
8930ec395e | ||
|
|
54fe4b2f47 | ||
|
|
33129481cf | ||
|
|
4cc0867753 | ||
|
|
4728730abc | ||
|
|
9e7b4b751f | ||
|
|
b11f2620ab | ||
|
|
056c283145 | ||
|
|
00beb9595d | ||
|
|
27f3e9075a | ||
|
|
3912330040 | ||
|
|
4cbff962db | ||
|
|
29c949e051 | ||
|
|
e8a84e6c22 | ||
|
|
42476d3cdc | ||
|
|
f48f74c004 | ||
|
|
3af02d5c44 | ||
|
|
9e6d5cc13a | ||
|
|
74fe57147b | ||
|
|
9acec69b9a | ||
|
|
2a81ec0650 | ||
|
|
596a4ee5b4 | ||
|
|
dbfeb480fe | ||
|
|
6b8a203691 | ||
|
|
95e351e150 | ||
|
|
4b4c585107 | ||
|
|
0217e842d2 | ||
|
|
877ef1d27c | ||
|
|
b1bfd5aee3 | ||
|
|
af38544316 | ||
|
|
d4908178bf | ||
|
|
9deedec029 | ||
|
|
8ef3e8ddd8 | ||
|
|
1eeba07396 | ||
|
|
2c48b8d83b | ||
|
|
44bac28718 | ||
|
|
f520c616cc | ||
|
|
39de82ae13 | ||
|
|
08bea0e15d | ||
|
|
cb52f3cdb4 | ||
|
|
306164531f | ||
|
|
40ddc4900a | ||
|
|
32aaacf4e7 | ||
|
|
a6a73f4d32 | ||
|
|
3d959eb425 | ||
|
|
b2fd9d9daf | ||
|
|
8ff27e931f | ||
|
|
ecbc33ba29 | ||
|
|
5635dc5142 | ||
|
|
bb76bc1d31 | ||
|
|
4a5ccb75b2 | ||
|
|
6672cc0f86 | ||
|
|
9dd560493a | ||
|
|
db5ed29fa2 | ||
|
|
295dba93b5 | ||
|
|
f23da1fc36 | ||
|
|
20764074cb | ||
|
|
7b7c9231b0 | ||
|
|
397f672248 | ||
|
|
3707c5b8a6 | ||
|
|
5666a1bb9d | ||
|
|
7b9c7f0fb2 | ||
|
|
eec2446723 | ||
|
|
d29c9524bd | ||
|
|
db4ae0ef9d | ||
|
|
ee48dae8c5 | ||
|
|
842f6b0473 | ||
|
|
1f64a71cde | ||
|
|
00fc8e5d95 | ||
|
|
b9602ab98b | ||
|
|
486f8a0090 | ||
|
|
3a1ac6c8d7 | ||
|
|
9dc3e3ce56 | ||
|
|
61af0b2e09 | ||
|
|
c7ddee0409 | ||
|
|
a3640775bb |
No files matched your search
@@ -0,0 +1,63 @@
|
||||
name: Create and publish Container image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=edge
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -3,8 +3,15 @@
|
||||
*.o
|
||||
cscope.*
|
||||
echosrv
|
||||
libsslh.a
|
||||
sslh-fork
|
||||
sslh-select
|
||||
sslh-ev
|
||||
systemd-sslh-generator
|
||||
sslh.8.gz
|
||||
tags
|
||||
version.h
|
||||
/config.status
|
||||
/config.log
|
||||
/config.h
|
||||
/Makefile
|
||||
@@ -1,5 +1,31 @@
|
||||
v2.1.3:
|
||||
Fix Landlock access to /etc/hosts.deny and
|
||||
/etc/hosts.allow.
|
||||
|
||||
v2.1.2:
|
||||
Fix inetd mode.
|
||||
|
||||
v2.1.1:
|
||||
Various minor fixes.
|
||||
|
||||
v2.1.0:
|
||||
Support for the Landlock LSM. After initial setup,
|
||||
sslh gives up all local file access rights.
|
||||
|
||||
Reintroduced --ssl as an alias to --tls.
|
||||
|
||||
Introduce autoconf to adapt to landlock presence.
|
||||
|
||||
Close connexion without error message if remote
|
||||
client forcefully closes connexion, for Windows.
|
||||
|
||||
v2.0.1:
|
||||
Fix resolve_on_forward setting, which would crash
|
||||
sslh reliably.
|
||||
|
||||
v2.0.0:
|
||||
v2.0:
|
||||
New sslh-ev: this is functionaly equivalent to
|
||||
New sslh-ev: this is functionally equivalent to
|
||||
sslh-select (mono-process, only forks for specified
|
||||
protocols), but based on libev, which should make it
|
||||
scalable to large numbers of connections.
|
||||
@@ -41,7 +67,7 @@ v1.22: 17AUG2021
|
||||
combined with incoming TLS with SNI. UDP clients
|
||||
and servers need to agree on the IPv4/IPv6 they use:
|
||||
use the same protocol on all sides! Often, this
|
||||
means explicitely using 'ip4-localhost'.
|
||||
means explicitly using 'ip4-localhost'.
|
||||
UDP sender-receiver pairs (connections, so to speak)
|
||||
are kept for 60s, which can be changed with
|
||||
`udp_timeout` in the configuration.
|
||||
@@ -83,7 +109,7 @@ v1.21: 11JUL2020
|
||||
|
||||
Added TCP_FASTOPEN support for client sockets (if
|
||||
tfo_ok is specified in their configuration) and for
|
||||
listenint socket, if all client protocols support it.
|
||||
listening socket, if all client protocols support it.
|
||||
(Craig Andrews)
|
||||
|
||||
Added 'minlength' option to skip a probe if less
|
||||
@@ -109,8 +135,8 @@ v1.20: 20NOV2018
|
||||
Before, probes were tried in order, repeating on the
|
||||
same probe as long it returned PROBE_AGAIN before
|
||||
moving to the next one. This means a probe which
|
||||
requires a lot of data (i.e. returne PROBE_AGAIN for
|
||||
a long time) could prevent sucessful matches from
|
||||
requires a lot of data (i.e. return PROBE_AGAIN for
|
||||
a long time) could prevent successful matches from
|
||||
subsequent probes. The configuration file needed to
|
||||
take that into account.
|
||||
|
||||
@@ -171,7 +197,7 @@ v1.18: 29MAR2016
|
||||
v1.17: 09MAR2015
|
||||
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
|
||||
|
||||
Transparant proxy support for FreeBSD.
|
||||
Transparent proxy support for FreeBSD.
|
||||
(Ruben van Staveren)
|
||||
|
||||
Using -F with no argument will try
|
||||
@@ -200,7 +226,7 @@ v1.16: 11FEB2014
|
||||
|
||||
Libcap support: Keep only CAP_NET_ADMIN if started
|
||||
as root with transparent proxying and dropping
|
||||
priviledges (enable USELIBCAP in Makefile). This
|
||||
privileges (enable USELIBCAP in Makefile). This
|
||||
avoids having to mess with filesystem capabilities.
|
||||
(Sebastian Schmidt/yath)
|
||||
|
||||
@@ -209,7 +235,7 @@ v1.16: 11FEB2014
|
||||
actual errors if connections are dropped before
|
||||
getting to getpeername).
|
||||
|
||||
Set IP_FREEDBIND if available to bind to addresses
|
||||
Set IP_FREEBIND if available to bind to addresses
|
||||
that don't yet exist.
|
||||
|
||||
v1.15: 27JUL2013
|
||||
@@ -294,7 +320,7 @@ v1.11: 21APR2012
|
||||
--user isn't specified, just run as current user.
|
||||
|
||||
No longer create PID file by default, it should be
|
||||
explicitely set with --pidfile.
|
||||
explicitly set with --pidfile.
|
||||
|
||||
No longer log to syslog if in foreground. Logs are
|
||||
instead output to stderr.
|
||||
@@ -385,7 +411,7 @@ v1.8: 15JUL2011
|
||||
v1.7: 01FEB2010
|
||||
Added CentOS init.d script (Andre Krajnik).
|
||||
|
||||
Fixed default ssl address inconsistancy, now
|
||||
Fixed default ssl address inconsistency, now
|
||||
defaults to "localhost:443" and fixed documentation
|
||||
accordingly (pointed by Markus Schalke).
|
||||
|
||||
|
||||
+32
-17
@@ -1,23 +1,38 @@
|
||||
FROM alpine:latest as build
|
||||
ARG ALPINE_VERSION="latest"
|
||||
ARG TARGET_ARCH="library"
|
||||
|
||||
ADD . /sslh
|
||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
|
||||
|
||||
RUN \
|
||||
apk add \
|
||||
gcc \
|
||||
libconfig-dev \
|
||||
make \
|
||||
musl-dev \
|
||||
pcre2-dev \
|
||||
perl && \
|
||||
cd /sslh && \
|
||||
make sslh-select && \
|
||||
strip sslh-select
|
||||
WORKDIR /sslh
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache \
|
||||
'gcc' \
|
||||
'libconfig-dev' \
|
||||
'make' \
|
||||
'musl-dev' \
|
||||
'pcre2-dev' \
|
||||
'perl' \
|
||||
;
|
||||
|
||||
COPY --from=build /sslh/sslh-select /sslh
|
||||
COPY . /sslh
|
||||
|
||||
RUN apk --no-cache add libconfig pcre2
|
||||
RUN ./configure && make sslh-select && strip sslh-select
|
||||
|
||||
ENTRYPOINT [ "/sslh", "--foreground"]
|
||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
|
||||
|
||||
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
|
||||
RUN apk add --no-cache \
|
||||
'libconfig' \
|
||||
'pcre2' \
|
||||
'iptables' \
|
||||
'ip6tables' \
|
||||
'libcap' \
|
||||
&& \
|
||||
adduser -s '/bin/sh' -S -D sslh && \
|
||||
setcap cap_net_bind_service,cap_net_raw+ep /usr/local/bin/sslh
|
||||
|
||||
COPY "./container-entrypoint.sh" "/init"
|
||||
ENTRYPOINT [ "/init" ]
|
||||
|
||||
# required for updating iptables
|
||||
USER root:root
|
||||
+20
-9
@@ -3,9 +3,11 @@ VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
# landlock is checked by `configure` and set in config.h
|
||||
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBEV=1 # Use libev?
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
USELIBCAP= # Use libcap?
|
||||
USESYSTEMD= # Make use of systemd socket activation
|
||||
@@ -30,10 +32,11 @@ endif
|
||||
|
||||
CC ?= gcc
|
||||
AR ?= ar
|
||||
CFLAGS ?=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
|
||||
LIBS=-lm -lpcre2-8
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o
|
||||
OBJS_A=libsslh.a
|
||||
FORK_OBJS=sslh-fork.o $(OBJS_A)
|
||||
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
|
||||
@@ -71,8 +74,11 @@ ifneq ($(strip $(USELIBBSD)),)
|
||||
CPPFLAGS+=-DLIBBSD
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBEV)),)
|
||||
CONDITIONAL_TARGETS+=sslh-ev
|
||||
endif
|
||||
|
||||
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||
all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||
|
||||
%.o: %.c %.h version.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
@@ -83,13 +89,15 @@ $(OBJS_A): $(OBJS)
|
||||
version.h:
|
||||
./genver.sh >version.h
|
||||
|
||||
sslh: sslh-fork sslh-select sslh-ev
|
||||
|
||||
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
|
||||
|
||||
|
||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||
c2s:
|
||||
conf2struct sslhconf.cfg
|
||||
conf2struct echosrv.cfg
|
||||
|
||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||
$(warning "sslhconf.cfg is more recent than sslh-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
|
||||
|
||||
sslh-fork: version.h Makefile $(FORK_OBJS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
|
||||
@@ -104,11 +112,14 @@ systemd-sslh-generator: systemd-sslh-generator.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
||||
|
||||
echosrv-conf.c echosrv-conf.h: echosrv.cfg
|
||||
conf2struct echosrv.cfg
|
||||
$(warning "echosrv.cfg is more recent than echosrv-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
|
||||
|
||||
echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
|
||||
|
||||
|
||||
landlock.o: config.h
|
||||
|
||||
$(MAN): sslh.pod Makefile
|
||||
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
|
||||
|
||||
@@ -128,7 +139,7 @@ docker-clean:
|
||||
yes | docker image prune
|
||||
|
||||
# generic install: install binary and man page
|
||||
install: sslh $(MAN)
|
||||
install: sslh-fork $(MAN)
|
||||
mkdir -p $(DESTDIR)/$(BINDIR)
|
||||
mkdir -p $(DESTDIR)/$(MANDIR)
|
||||
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
|
||||
@@ -148,7 +159,7 @@ distclean: clean
|
||||
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
||||
|
||||
clean:
|
||||
rm -f sslh-fork sslh-select sslh-ev echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
|
||||
tags:
|
||||
ctags --globals -T *.[ch]
|
||||
@@ -11,7 +11,7 @@ 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.
|
||||
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
|
||||
@@ -20,9 +20,10 @@ address.
|
||||
|
||||
`sslh` has the bells and whistles expected from a mature
|
||||
daemon: privilege and capabilities dropping, inetd support,
|
||||
systemd support, transparent proxying, chroot, logging,
|
||||
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
|
||||
model, and more.
|
||||
systemd support, transparent proxying, 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
|
||||
=======
|
||||
@@ -35,6 +36,34 @@ Configuration
|
||||
|
||||
Please refer to the [configuration guide](doc/config.md).
|
||||
|
||||
Transparent proxying
|
||||
--------------------
|
||||
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
|
||||
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](doc/simple_transparent_proxy.md), with further
|
||||
scenarios described [there](doc/scenarios-for-simple-transparent-proxy.md).
|
||||
|
||||
|
||||
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](doc/tproxy.md).
|
||||
In most cases, you will be better off following the first
|
||||
method.
|
||||
|
||||
|
||||
Docker image
|
||||
@@ -44,16 +73,15 @@ How to use
|
||||
|
||||
---
|
||||
|
||||
Build docker image
|
||||
|
||||
make docker
|
||||
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--cap-add CAP_NET_RAW \
|
||||
--cap-add CAP_NET_BIND_SERVICE \
|
||||
--rm \
|
||||
-it \
|
||||
sslh:latest \
|
||||
ghcr.io/yrutschle/sslh:latest \
|
||||
--foreground \
|
||||
--listen=0.0.0.0:443 \
|
||||
--ssh=hostname:22 \
|
||||
--tls=hostname:443
|
||||
@@ -61,16 +89,16 @@ docker run \
|
||||
|
||||
docker-compose example
|
||||
|
||||
```
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sslh:
|
||||
image: sslh:latest
|
||||
image: ghcr.io/yrutschle/sslh:latest
|
||||
hostname: sslh
|
||||
ports:
|
||||
- 443:443
|
||||
command: --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
|
||||
command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
|
||||
depends_on:
|
||||
- nginx
|
||||
- openvpn
|
||||
@@ -82,6 +110,85 @@ services:
|
||||
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**_
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
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
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
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?
|
||||
====================
|
||||
|
||||
|
||||
+13
-13
@@ -2876,9 +2876,9 @@ static void arg_file_resetfn(struct arg_file* parent) {
|
||||
static const char* arg_basename(const char* filename) {
|
||||
const char *result = NULL, *result1, *result2;
|
||||
|
||||
/* Find the last occurrence of eother file separator character. */
|
||||
/* Two alternative file separator chars are supported as legal */
|
||||
/* file separators but not both together in the same filename. */
|
||||
/* Find the last occurrence of other file separator character. */
|
||||
/* Two alternative file separator chars are supported as legal */
|
||||
/* file separators but not both together in the same filename. */
|
||||
result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL);
|
||||
result2 = (filename ? strrchr(filename, FILESEPARATOR2) : NULL);
|
||||
|
||||
@@ -2927,7 +2927,7 @@ static int arg_file_scanfn(struct arg_file* parent, const char* argval) {
|
||||
} else if (!argval) {
|
||||
/* a valid argument with no argument value was given. */
|
||||
/* This happens when an optional argument value was invoked. */
|
||||
/* leave parent arguiment value unaltered but still count the argument. */
|
||||
/* leave parent argument value unaltered but still count the argument. */
|
||||
parent->count++;
|
||||
} else {
|
||||
parent->filename[parent->count] = argval;
|
||||
@@ -3173,7 +3173,7 @@ static int arg_int_scanfn(struct arg_int* parent, const char* argval) {
|
||||
} else if (!argval) {
|
||||
/* a valid argument with no argument value was given. */
|
||||
/* This happens when an optional argument value was invoked. */
|
||||
/* leave parent arguiment value unaltered but still count the argument. */
|
||||
/* leave parent argument value unaltered but still count the argument. */
|
||||
parent->count++;
|
||||
} else {
|
||||
long int val;
|
||||
@@ -3813,8 +3813,8 @@ static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_O
|
||||
#endif
|
||||
#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */
|
||||
#define OP_OR (MAX_CHAR + 2)
|
||||
#define OP_EXPR (MAX_CHAR + 3) /* parentesis () */
|
||||
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */
|
||||
#define OP_EXPR (MAX_CHAR + 3) /* parenthesis () */
|
||||
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parenthesis (?:) */
|
||||
#define OP_DOT (MAX_CHAR + 5)
|
||||
#define OP_CLASS (MAX_CHAR + 6)
|
||||
#define OP_CCLASS (MAX_CHAR + 7)
|
||||
@@ -5313,7 +5313,7 @@ static void arg_parse_untagged(int argc, char** argv, struct arg_hdr** table, st
|
||||
}
|
||||
}
|
||||
|
||||
/* if a tenative error still remains at this point then register it as a proper error */
|
||||
/* if a tentative error still remains at this point then register it as a proper error */
|
||||
if (errorlast) {
|
||||
arg_register_error(endtable, parentlast, errorlast, optarglast);
|
||||
optind++;
|
||||
@@ -5384,7 +5384,7 @@ int arg_parse(int argc, char** argv, void** argtable) {
|
||||
/*
|
||||
Fill in the local copy of argv[]. We need a local copy
|
||||
because getopt rearranges argv[] which adversely affects
|
||||
susbsequent parsing attempts.
|
||||
subsequent parsing attempts.
|
||||
*/
|
||||
for (i = 0; i < argc; i++)
|
||||
argvcopy[i] = argv[i];
|
||||
@@ -5451,7 +5451,7 @@ static void arg_cat_option(char* dest, size_t ndest, const char* shortopts, cons
|
||||
if (shortopts) {
|
||||
char option[3];
|
||||
|
||||
/* note: option array[] is initialiazed dynamically here to satisfy */
|
||||
/* note: option array[] is initialized dynamically here to satisfy */
|
||||
/* a deficiency in the watcom compiler wrt static array initializers. */
|
||||
option[0] = '-';
|
||||
option[1] = shortopts[0];
|
||||
@@ -5509,7 +5509,7 @@ static void arg_cat_optionv(char* dest, size_t ndest, const char* shortopts, con
|
||||
/* "-a|-b|-c" */
|
||||
char shortopt[3];
|
||||
|
||||
/* note: shortopt array[] is initialiazed dynamically here to satisfy */
|
||||
/* note: shortopt array[] is initialized dynamically here to satisfy */
|
||||
/* a deficiency in the watcom compiler wrt static array initializers. */
|
||||
shortopt[0] = '-';
|
||||
shortopt[1] = *c;
|
||||
@@ -5881,7 +5881,7 @@ static void arg_print_formatted_ds(arg_dstr_t ds, const unsigned lmargin, const
|
||||
* Prints the glossary in strict GNU format.
|
||||
* Differences to arg_print_glossary() are:
|
||||
* - wraps lines after 80 chars
|
||||
* - indents lines without shortops
|
||||
* - indents lines without shortopts
|
||||
* - does not accept formatstrings
|
||||
*
|
||||
* Contributed by Uli Fouquet
|
||||
@@ -5956,7 +5956,7 @@ int arg_nullcheck(void** argtable) {
|
||||
* that entry were still allocated ok. Those subsequent allocations will not be
|
||||
* deallocated by arg_free().
|
||||
* Despite the unlikeliness of the problem occurring, and the even unlikelier event
|
||||
* that it has any deliterious effect, it is fixed regardless by replacing arg_free()
|
||||
* that it has any deleterious effect, it is fixed regardless by replacing arg_free()
|
||||
* with the newer arg_freetable() function.
|
||||
* We still keep arg_free() for backwards compatibility.
|
||||
*/
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
|
||||
* that particular arg_xxx arguments, performing post-parse checks, and
|
||||
* reporting errors.
|
||||
* These functions are private to the individual arg_xxx source code
|
||||
* and are the pointer to them are initiliased by that arg_xxx struct's
|
||||
* and are the pointer to them are initialised by that arg_xxx struct's
|
||||
* constructor function. The user could alter them after construction
|
||||
* if desired, but the original intention is for them to be set by the
|
||||
* constructor and left unaltered.
|
||||
@@ -95,7 +95,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
|
||||
typedef struct arg_hdr {
|
||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||
const char* shortopts; /* String defining the short options */
|
||||
const char* longopts; /* String defiing the long options */
|
||||
const char* longopts; /* String defining the long options */
|
||||
const char* datatype; /* Description of the argument data type */
|
||||
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
|
||||
int mincount; /* Minimum number of occurences of this option accepted */
|
||||
|
||||
@@ -11,9 +11,13 @@ pidfile: "/var/run/sslh.pid";
|
||||
|
||||
# Change hostname with your external address name, or the IP
|
||||
# of the interface that receives connections
|
||||
# Default is to bind all interfaces. httpd can be started
|
||||
# first to bind on localhost, in which case sslh will bind
|
||||
# only other interfaces.
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; }
|
||||
{ host: "0.0.0.0"; port: "443"; },
|
||||
{ host: "[::]"; port: "443"; }
|
||||
);
|
||||
|
||||
|
||||
@@ -24,8 +28,6 @@ protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "1194"; },
|
||||
{ name: "xmpp"; host: "localhost"; port: "5222"; },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "443"; }
|
||||
);
|
||||
|
||||
@@ -110,7 +110,7 @@ int make_listen_tfo(int s)
|
||||
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
|
||||
}
|
||||
|
||||
/* Starts listening on a single address
|
||||
/* Starts listening on a single address
|
||||
* Returns a socket filehandle, or dies with message in case of major error */
|
||||
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
{
|
||||
@@ -262,7 +262,7 @@ int bind_peer(int fd, int fd_from)
|
||||
/* if the destination is the same machine, there's no need to do bind */
|
||||
if (is_same_machine(&from))
|
||||
return 0;
|
||||
|
||||
|
||||
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
|
||||
CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT");
|
||||
@@ -278,7 +278,26 @@ int bind_peer(int fd, int fd_from)
|
||||
}
|
||||
#endif /* IP_TRANSPARENT / IP_BINDANY */
|
||||
res = bind(fd, from.ai_addr, from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "bind", res);
|
||||
if (res == -1) {
|
||||
if (errno != EADDRINUSE) {
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__,
|
||||
"bind", errno, strerror(errno));
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is more than one transparent mode proxy going on, such as
|
||||
* using sslh as the target of stunnel also in transparent mode, then
|
||||
* the (ip,port) combination will already be bound for the previous application.
|
||||
* In that case, the best we can do is bind with a different port.
|
||||
* This does mean the local server can't use the ident protocol as the port will
|
||||
* have changed, but most people won't care.
|
||||
* Also note that stunnel uses the same logic for the same situation.
|
||||
*/
|
||||
((struct sockaddr_in *)from.ai_addr)->sin_port = 0;
|
||||
res = bind(fd, from.ai_addr, from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "bind", res);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -348,6 +367,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
|
||||
if (transparent) {
|
||||
res = bind_peer(fd, fd_from);
|
||||
if (res == -1) close(fd);
|
||||
CHECK_RES_RETURN(res, "bind_peer", res);
|
||||
}
|
||||
res = connect(fd, a->ai_addr, a->ai_addrlen);
|
||||
@@ -371,7 +391,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
}
|
||||
|
||||
/* Store some data to write to the queue later */
|
||||
int defer_write(struct queue *q, void* data, int data_size)
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size)
|
||||
{
|
||||
char *p;
|
||||
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
|
||||
@@ -383,7 +403,7 @@ int defer_write(struct queue *q, void* data, int data_size)
|
||||
q->begin_deferred_data = p;
|
||||
q->deferred_data = p + data_offset;
|
||||
p += data_offset + q->deferred_data_size;
|
||||
q->deferred_data_size += data_size;
|
||||
q->deferred_data_size += (int)data_size;
|
||||
memcpy(p, data, data_size);
|
||||
|
||||
return 0;
|
||||
@@ -395,13 +415,13 @@ int defer_write(struct queue *q, void* data, int data_size)
|
||||
* */
|
||||
int flush_deferred(struct queue *q)
|
||||
{
|
||||
int n;
|
||||
ssize_t n;
|
||||
|
||||
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
|
||||
|
||||
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
||||
if (n == -1)
|
||||
return n;
|
||||
return (int)n;
|
||||
|
||||
if (n == q->deferred_data_size) {
|
||||
/* All has been written -- release the memory */
|
||||
@@ -412,10 +432,10 @@ int flush_deferred(struct queue *q)
|
||||
} else {
|
||||
/* There is data left */
|
||||
q->deferred_data += n;
|
||||
q->deferred_data_size -= n;
|
||||
q->deferred_data_size -= (int)n;
|
||||
}
|
||||
|
||||
return n;
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
|
||||
@@ -450,7 +470,8 @@ void dump_connection(struct connection *cnx)
|
||||
int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
int target, from, size_r, size_w;
|
||||
int target, from;
|
||||
ssize_t size_r, size_w;
|
||||
|
||||
target = target_q->fd;
|
||||
from = from_q->fd;
|
||||
@@ -462,6 +483,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
return FD_NODATA;
|
||||
|
||||
case ECONNRESET:
|
||||
case ENOTSOCK:
|
||||
case EPIPE:
|
||||
return FD_CNXCLOSED;
|
||||
}
|
||||
@@ -494,7 +516,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
|
||||
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
|
||||
|
||||
return size_w;
|
||||
return (int)size_w;
|
||||
}
|
||||
|
||||
/* returns a string that prints the IP and port of the sockaddr */
|
||||
@@ -529,7 +551,8 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
}
|
||||
|
||||
/* Turns a hostname and port (or service) into a list of struct addrinfo
|
||||
* returns 0 on success, -1 otherwise and logs error
|
||||
* On success, returns 0
|
||||
* On failure, returns -1 or one of getaddrinfo() codes
|
||||
*/
|
||||
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
{
|
||||
@@ -555,7 +578,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
if (res)
|
||||
print_message(msg_system_error, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
print_message(msg_system_error, "resolve_split_name: %s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -831,20 +854,19 @@ void write_pid_file(const char* pidfile)
|
||||
|
||||
f = fopen(pidfile, "w");
|
||||
if (!f) {
|
||||
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
|
||||
exit(3);
|
||||
print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
res = fprintf(f, "%d\n", getpid());
|
||||
if (res < 0) {
|
||||
print_message(msg_system_error, "write_pid_file:fprintf:%s", strerror(errno));
|
||||
exit(3);
|
||||
print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
res = fclose(f);
|
||||
if (res == EOF) {
|
||||
print_message(msg_system_error, "write_pid_file:fclose:%s", strerror(errno));
|
||||
exit(3);
|
||||
print_message(msg_system_error, "write_pid_file: fclose: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* enough for the macros to adapt (http://support.microsoft.com/kb/111855)
|
||||
*/
|
||||
#ifdef __CYGWIN__
|
||||
#undef FD_SETSIZE
|
||||
#define FD_SETSIZE 4096
|
||||
#endif
|
||||
|
||||
@@ -112,7 +113,7 @@ struct connection {
|
||||
struct queue q[2];
|
||||
|
||||
/* SOCK_DGRAM */
|
||||
struct sockaddr client_addr; /* Contains the remote client address */
|
||||
struct sockaddr_storage client_addr; /* Contains the remote client address */
|
||||
socklen_t addrlen;
|
||||
|
||||
int local_endpoint; /* Contains the local address */
|
||||
@@ -171,7 +172,7 @@ int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[]);
|
||||
|
||||
int defer_write(struct queue *q, void* data, int data_size);
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size);
|
||||
int flush_deferred(struct queue *q);
|
||||
|
||||
extern struct sslhcfg_item cfg;
|
||||
@@ -183,4 +184,8 @@ void start_shoveler(int);
|
||||
|
||||
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||
|
||||
/* landlock.c */
|
||||
void setup_landlock(void);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
#ifndef CONFIG_H
|
||||
/* Template for config.h, filled by `configure`. */
|
||||
|
||||
/* Landlock sandboxing Linux LSM */
|
||||
#undef HAVE_LANDLOCK
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,9 @@
|
||||
|
||||
dnl Use autoconf to generate the `configure` script from this and Makefile.in
|
||||
|
||||
AC_INIT
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
|
||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
||||
AC_OUTPUT
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL2-or-later
|
||||
#
|
||||
# Copyright (C) 2023 Olliver Schinagl <oliver@schinagl.nl>
|
||||
#
|
||||
# A beginning user should be able to docker run image bash (or sh) without
|
||||
# needing to learn about --entrypoint
|
||||
# https://github.com/docker-library/official-images#consistency
|
||||
|
||||
set -eu
|
||||
|
||||
bin='sslh'
|
||||
|
||||
# run command if it is not starting with a "-" and is an executable in PATH
|
||||
if [ "${#}" -le 0 ] || \
|
||||
[ "${1#-}" != "${1}" ] || \
|
||||
[ -d "${1}" ] || \
|
||||
! command -v "${1}" > '/dev/null' 2>&1; then
|
||||
entrypoint='true'
|
||||
fi
|
||||
|
||||
unconfigure_iptables() {
|
||||
echo "Received SIG TERM/INT/KILL. Removing iptables / routing changes"
|
||||
|
||||
set +e # Don't exit if got error
|
||||
set -x
|
||||
|
||||
iptables -t raw -D PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
|
||||
iptables -t mangle -D POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
|
||||
|
||||
iptables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
iptables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip rule del fwmark 0x1 lookup 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
|
||||
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
|
||||
ip6tables -t raw -D PREROUTING ! -i lo -d ::1/128 -j DROP
|
||||
ip6tables -t mangle -D POSTROUTING ! -o lo -s ::1/128 -j DROP
|
||||
ip6tables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
ip6tables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip -6 rule del fwmark 0x1 lookup 100
|
||||
ip -6 route del local ::/0 dev lo table 100
|
||||
fi
|
||||
|
||||
set -e
|
||||
set +x
|
||||
}
|
||||
|
||||
configure_iptables() {
|
||||
echo "Configuring iptables and routing..."
|
||||
|
||||
set +e # Don't exit if got error
|
||||
set -x
|
||||
|
||||
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
|
||||
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
|
||||
|
||||
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
|
||||
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
|
||||
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
|
||||
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip -6 rule add fwmark 0x1 lookup 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
fi
|
||||
|
||||
set -e
|
||||
set +x
|
||||
}
|
||||
|
||||
for _args in "${@}" ; do
|
||||
if [ "${_args:-}" = '--transparent' ] ; then
|
||||
echo '--transparent flag is set'
|
||||
configure_iptables
|
||||
trap unconfigure_iptables TERM INT KILL
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Drop privileges and run as sslh user
|
||||
sslh_cmd="${entrypoint:+${bin}} ${@}"
|
||||
echo "Executing with user 'sslh': ${sslh_cmd}"
|
||||
|
||||
exec su - sslh -c "${sslh_cmd}" &
|
||||
wait "${!}"
|
||||
|
||||
exit 0
|
||||
+1
-1
@@ -7,7 +7,7 @@ doesn't work, report how what was suggested here went.
|
||||
It's also worth reading [how to ask
|
||||
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
|
||||
before posting on the mailing list or opening an issue in
|
||||
Github.
|
||||
GitHub.
|
||||
|
||||
Getting more info
|
||||
=================
|
||||
|
||||
+89
-74
@@ -1,3 +1,13 @@
|
||||
Pre-built binaries
|
||||
==================
|
||||
|
||||
Docker images of `master` and of the tagged versions are
|
||||
available directly from [Github](https://github.com/yrutschle/sslh/pkgs/container/sslh).
|
||||
|
||||
Windows binaries for Cygwin are graciously produced by
|
||||
nono303 on his [repository](https://github.com/nono303/sslh).
|
||||
|
||||
|
||||
Compile and install
|
||||
===================
|
||||
|
||||
@@ -6,125 +16,130 @@ Dependencies
|
||||
|
||||
`sslh` uses:
|
||||
|
||||
* [libconfig](http://www.hyperrealm.com/libconfig/). For
|
||||
Debian this is contained in package `libconfig-dev`. You
|
||||
can compile with or without it using USELIBCONFIG in the
|
||||
Makefile.
|
||||
* [libconfig](http://www.hyperrealm.com/libconfig/).
|
||||
For Debian this is contained in package `libconfig-dev`.
|
||||
You can compile with or without it using USELIBCONFIG in the Makefile.
|
||||
|
||||
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
|
||||
For Debian, this is contained in packages
|
||||
`libwrap0-dev`. You
|
||||
can compile with or without it using USELIBWRAP in the
|
||||
Makefile.
|
||||
For Debian, this is contained in packages `libwrap0-dev`.
|
||||
You can compile with or without it using USELIBWRAP in the Makefile.
|
||||
|
||||
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`. You
|
||||
can compile with or without it using USESYSTEMD in the
|
||||
Makefile.
|
||||
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
|
||||
You can compile with or without it using USESYSTEMD in the Makefile.
|
||||
|
||||
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`. You can compile with or without it using USELIBCAP in the Makefile
|
||||
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
|
||||
You can compile with or without it using USELIBCAP in the Makefile
|
||||
|
||||
* libbsd, to enable to change the process name (as shown in
|
||||
`ps`, so each forked process shows what protocol and what
|
||||
connection it is serving),
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at
|
||||
compile-time.
|
||||
* libbsd, to enable to change the process name (as shown in `ps`,
|
||||
so each forked process shows what protocol and what connection it is serving),
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
|
||||
|
||||
* libpcre2, in package `libpcre-dev`. You can compile
|
||||
with or without it using ENABLE_REGEX in the Makefile.
|
||||
* libpcre2, in package `libpcre2-dev`.
|
||||
You can compile with or without it using ENABLE_REGEX in the Makefile.
|
||||
|
||||
* libev-dev, in package `libev-dev`. If you build a binary
|
||||
specifically and do not build `sslh-ev`, you don't need
|
||||
this.
|
||||
* libev-dev, in package `libev-dev`.
|
||||
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
|
||||
|
||||
|
||||
For OpenSUSE, these are contained in packages libconfig9 and
|
||||
libconfig-dev in repository
|
||||
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
|
||||
|
||||
For Fedora, you'll need packages `libconfig` and
|
||||
`libconfig-devel`:
|
||||
For Fedora, you'll need packages `libconfig` and `libconfig-devel`:
|
||||
|
||||
yum install libconfig libconfig-devel
|
||||
yum install libconfig libconfig-devel
|
||||
|
||||
If you want to rebuild `sslh-conf.c` (after a `make
|
||||
distclean` for example), you will also need to add
|
||||
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
If you want to rebuild `sslh-conf.c` (after a `make distclean` for example),
|
||||
you will also need to add [conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
(v1.5) to your path.
|
||||
|
||||
|
||||
The test scripts are written in Perl, and will require
|
||||
IO::Socket::INET6 (libio-socket-inet6-perl in Debian).
|
||||
`IO::Socket::INET6` (`libio-socket-inet6-perl` in Debian).
|
||||
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
After this, the Makefile should work:
|
||||
|
||||
make install
|
||||
make install
|
||||
|
||||
There are a couple of configuration options at the beginning
|
||||
of the Makefile:
|
||||
There are a couple of configuration options at the beginning of the Makefile:
|
||||
|
||||
* `USELIBWRAP` compiles support for host access control (see
|
||||
`hosts_access(3)`), you will need `libwrap` headers and
|
||||
library to compile (`libwrap0-dev` in Debian).
|
||||
* `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`),
|
||||
you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian).
|
||||
|
||||
* `USELIBCONFIG` compiles support for the configuration
|
||||
file. You will need `libconfig` headers to compile
|
||||
(`libconfig8-dev` in Debian).
|
||||
* `USELIBCONFIG` compiles support for the configuration file.
|
||||
You will need `libconfig` headers to compile (`libconfig8-dev` in Debian).
|
||||
|
||||
* `USESYSTEMD` compiles support for using systemd socket activation.
|
||||
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
|
||||
* `USESYSTEMD` compiles support for using systemd socket activation.
|
||||
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
|
||||
|
||||
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
|
||||
|
||||
|
||||
Generating the configuration parser
|
||||
-----------------------------------
|
||||
|
||||
The configuration file and command line parser is generated by `conf2struct`,
|
||||
from `sslhconf.cfg`, which generates `sslh-conf.c` and `sslh-conf.h`.
|
||||
The resulting files are included in the source
|
||||
so `sslh` can be built without `conf2struct` installed.
|
||||
|
||||
Further, to prevent build issues,
|
||||
`sslh-conf.[ch]` has no dependency to `sslhconf.cfg` in the Makefile.
|
||||
In the event of adding configuration settings,
|
||||
they need to be regenerated using `make c2s`.
|
||||
|
||||
* `USELIBBSD` compiles support for updating the process name (as shown
|
||||
by `ps`).
|
||||
|
||||
Binaries
|
||||
--------
|
||||
|
||||
The Makefile produces three different executables: `sslh-fork`,
|
||||
`sslh-select` and `sslh-ev`:
|
||||
The Makefile produces three different executables:
|
||||
`sslh-fork`, `sslh-select` and `sslh-ev`:
|
||||
|
||||
* `sslh-fork` forks a new process for each incoming connection.
|
||||
It is well-tested and very reliable, but incurs the overhead
|
||||
of many processes.
|
||||
If you are going to use `sslh` for a "small" setup (less than
|
||||
a dozen ssh connections and a low-traffic https server) then
|
||||
`sslh-fork` is probably more suited for you.
|
||||
It is well-tested and very reliable, but incurs the overhead of many processes.
|
||||
If you are going to use `sslh` for a "small" setup
|
||||
(less than a dozen ssh connections and a low-traffic https server)
|
||||
then `sslh-fork` is probably more suited for you.
|
||||
|
||||
* `sslh-select` uses only one thread, which monitors all
|
||||
connections at once. It only incurs a 16 byte overhead per
|
||||
connection. Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely. If you are going
|
||||
to use `sslh` on a "medium" setup (a few hundreds of
|
||||
connections), or if you are on a system where forking is
|
||||
expensive (e.g. Windows), `sslh-select` will be better.
|
||||
* `sslh-select` uses only one thread, which monitors all connections at once.
|
||||
It only incurs a 16 byte overhead per connection.
|
||||
Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely.
|
||||
If you are going to use `sslh` on a "medium" setup (a few hundreds of connections),
|
||||
or if you are on a system where forking is expensive (e.g. Windows),
|
||||
`sslh-select` will be better.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as a backend.
|
||||
This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as
|
||||
a backend. This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
* In general:
|
||||
|
||||
make
|
||||
cp sslh-fork /usr/local/sbin/sslh
|
||||
cp basic.cfg /etc/sslh.cfg
|
||||
vi /etc/sslh.cfg
|
||||
|
||||
```sh
|
||||
./configure
|
||||
make
|
||||
cp sslh-fork /usr/local/sbin/sslh
|
||||
cp basic.cfg /etc/sslh.cfg
|
||||
vi /etc/sslh.cfg
|
||||
```
|
||||
* For Debian:
|
||||
|
||||
cp scripts/etc.init.d.sslh /etc/init.d/sslh
|
||||
|
||||
```sh
|
||||
cp scripts/etc.init.d.sslh /etc/init.d/sslh
|
||||
```
|
||||
* For CentOS:
|
||||
|
||||
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
|
||||
|
||||
```sh
|
||||
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
|
||||
```
|
||||
|
||||
You might need to create links in /etc/rc<x>.d so that the server
|
||||
start automatically at boot-up, e.g. under Debian:
|
||||
|
||||
update-rc.d sslh defaults
|
||||
```sh
|
||||
update-rc.d sslh defaults
|
||||
```
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
It is possible to run `sslh` on Windows. The `fork` model
|
||||
should be avoided as it is very inefficient on Windows, but
|
||||
`sslh-select` and `sslh-ev` both work with good performance
|
||||
(prefer the latter, however).
|
||||
|
||||
|
||||
The following script downloads the latest cygwin, the latest version of sslh, and then compiles and copies the binaries with dependancies to an output folder.
|
||||
|
||||
It may be needed to correct it from time to time, but it works. I use it in a virtual machine.
|
||||
Just retrieve WGET.EXE from https://eternallybored.org/misc/wget/ or git binaries.
|
||||
|
||||
Copy the 3 files
|
||||
|
||||
GO.cmd
|
||||
wget.exe
|
||||
compile.sh
|
||||
|
||||
to C root folder, then execute **GO.cmd** with administrative rights.
|
||||
|
||||
with **GO.cmd**
|
||||
|
||||
@ECHO OFF
|
||||
CD /D "%~dp0"
|
||||
|
||||
NET SESSION >NUL 2>&1
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
ECHO Permission denied. This script must be run as an Administrator.
|
||||
ECHO:
|
||||
GOTO FIN
|
||||
) ELSE (
|
||||
ECHO Running as Administrator.
|
||||
TIMEOUT /T 2 >NUL
|
||||
wget --no-check-certificate https://www.cygwin.com/setup-x86_64.exe
|
||||
IF NOT EXIST setup-x86_64.exe GOTO FIN
|
||||
MKDIR C:\Z
|
||||
setup-x86_64.exe -l C:\Z -s ftp://ftp.funet.fi/pub/mirrors/sourceware.org/pub/cygwin/ -q -P make -P git -P gcc-g++ -P autoconf -P automake -P libtool -P libpcre-devel -P libpcre2-devel -P bison -P libev-devel
|
||||
MKDIR C:\cygwin64\home\user
|
||||
COPY COMPILE.SH C:\cygwin64\home\user
|
||||
START C:\cygwin64\bin\mintty.exe /bin/bash --login -i ~/compile.sh
|
||||
START EXPLORER C:\zzSORTIE
|
||||
)
|
||||
:FIN
|
||||
PAUSE
|
||||
EXIT
|
||||
|
||||
|
||||
and **compile.sh**
|
||||
|
||||
# SAVE FILE TO UNIX FORMAT
|
||||
# COPY IT IN C cygwin64 home user
|
||||
git clone https://github.com/hyperrealm/libconfig.git
|
||||
cd libconfig
|
||||
autoreconf -fi
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
cp /usr/local/lib/libconfig.* /usr/lib
|
||||
git clone https://github.com/yrutschle/sslh.git
|
||||
cd sslh
|
||||
make
|
||||
cd ..
|
||||
mkdir /cygdrive/c/zzSORTIE
|
||||
cp ./sslh/sslh*.exe /cygdrive/c/zzSORTIE
|
||||
cp /usr/local/bin/cygconfig-11.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygwin1.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygpcreposix-0.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygpcre-1.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygev-4.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygpcre2-8-0.dll /cygdrive/c/zzSORTIE
|
||||
|
||||
This method was contributed by lerenardo on [github](https://github.com/yrutschle/sslh/issues/196#issuecomment-1692805639).
|
||||
+7
-9
@@ -92,19 +92,15 @@ to the executable:
|
||||
|
||||
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
|
||||
|
||||
Then you can run sslh-select as an unpriviledged user, e.g.:
|
||||
Then you can run sslh-select as an unprivileged user, e.g.:
|
||||
|
||||
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
|
||||
|
||||
Transparent proxy support
|
||||
-------------------------
|
||||
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
This makes it easier to use the server's logs, and potential
|
||||
IP-based banning ability.
|
||||
|
||||
Set up can get complicated, so it has its own [document](tproxy.md).
|
||||
Transparent proxying is described in its own
|
||||
[document](tproxy.md).
|
||||
|
||||
Systemd Socket Activation
|
||||
-------------------------
|
||||
@@ -167,7 +163,7 @@ This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
|
||||
instead) configuration file and dynamically generates a socket file to use.
|
||||
|
||||
This will also merge with any sslh.socket.d drop in configuration but will be
|
||||
overriden by a /etc/systemd/system/sslh.socket file.
|
||||
overridden by a /etc/systemd/system/sslh.socket file.
|
||||
|
||||
To use the generator place it in /usr/lib/systemd/system-generators and then
|
||||
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
|
||||
@@ -196,6 +192,8 @@ will wait for incoming UDP packets, run the probes in the
|
||||
usual fashion, and forward packets to the appropriate
|
||||
target. `sslh` will then remember the association between
|
||||
remote host to target server for 60 seconds by default,
|
||||
which can be overriden with `udp_timeout`. This allows to
|
||||
which can be overridden with `udp_timeout`. This allows to
|
||||
process both single-datagram protocols such as DNS, and
|
||||
connection-based protocols such as QUIC.
|
||||
|
||||
An example for supporting QUIC is shown in `example.cfg`.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Three Scenarios for the simple transparent proxy setup #
|
||||
|
||||

|
||||
|
||||
## Introduction ##
|
||||
The first example is the configuration, which was described in the previousd document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture.
|
||||
The connections have two different endings, showing the direction of the opening connection (SYN flag) and the answer connection (SYN-ACK flags). This is important, as the traffic in the transparent proxy setup flows somewhat unexpected.
|
||||
|
||||
## Example 1 ##
|
||||
The first example shows the setup, which is described in the [previous document](./simple_transparent_proxy.md). You see the Client connecting to sslh (red connection). When sslh accepts this connection, the SYN-ACK packet is send back to the client, which sends the first data packet(s) together with the ACK for the SYN-ACK. So the bidirectional tcp connection is fully open.
|
||||
Sslh opens now the blue connection to sshd and needs for that elevation rights, as it uses the clients IP address as its own address for opening this connection. Now things are becoming complicated: Sshd send back the first packet with SYN-ACK flags (green line), addressed to the clients IP (dotted line). As already described, that would go wrong, so our routing trick makes this packet beeing deflected back to sslh, so this tcp connection is also opened. But we have here now an asymetric behaviour, that the read and write pathes of the tcp connection are going different routes.
|
||||
The sslh process shuffles now all the bytes coming from sshd from the green line to the red line, vice versa for the packets from the client.
|
||||
|
||||
## Example 2 ##
|
||||
In this example sshd is running on another server. No matter, if this is docker, kvm, virtualbox or another physical host, connected with an ethernet cable. Here we need no dummy interface, so we need another way, to configure our routing deflection. The principle is the same: We need to force packets coming back from sshd going to sslh and not directly back to the client.
|
||||
In this case its your decision, where those rules will be tied in, options are:
|
||||
|
||||
* the startscript of sslh
|
||||
* the docker or kvm configuration
|
||||
* the configuration of the outgoing interface
|
||||
|
||||
Its two lines you need:
|
||||
```
|
||||
ip rule add from SSHD_ADDRESS/32 table sslh
|
||||
ip route add local 0.0.0.0/0 dev lo table sslh
|
||||
```
|
||||
On the sshd host, we need no additional rules, as all traffic is coming back to our sslh host, because this is in this setting the default gateway. The only thing, we need to do: Assign a unique IP address only for sshd and all other services, you wish to hide behind sslh and host on this device.
|
||||
|
||||
There are two ways, how you can add multiple ip addresses to one device. The new _**ip addr add**_ supports multiple add statements to one and the same interface name. So you can just duplicate the interface stancas in the _**/etc/network/interfaces**_ configuration. The problem with this method is, that some older managment tools, like ifconfig are unable to show the additional addresses. So when you are used to some older tools, you may configure sub-interfaces like eth0:1.
|
||||
However my recommendation is, migrate to new tools, get used to it, as old tools don't show you the whole configuration!
|
||||
|
||||
## Example 3 ##
|
||||
This is now the extended version of the previous example. The target host has another path back to the client, as there is a default route to another interface. Now we need **TWO** routing deflections, one on the sslh host, like in scenario 2, and one on the sshd target host.
|
||||
The routing setup on the target host looks like:
|
||||
* Add an routing table name for the deflection table in _**/etc/iproute2/rt_tables**_
|
||||
* Find a location, where you will hook the two routing rules in
|
||||
```
|
||||
ip rule add from SSHD_ADDRESS/32 table sslh_routeback
|
||||
ip route add default via SERVER1_ETH1_IP dev eth0 table sslh_routeback
|
||||
|
||||
```
|
||||
This is setting up a default route for all traffic, originating from the ip address sshd (or any other service) is using, back to the host, hosting sslh. On that host, those packets will be deflected again with the same rule from scenario 2.
|
||||
|
||||
## Modifications ##
|
||||
Now you can think about many modifications, but the tools will be the same, for all other thinkable scenarios. You must always make sure, that packets from foreign hosts, will find their way back to the sslh host. So if the chain consists of three or four servers, all need the deflection rules.
|
||||
|
||||
## Important Finding On Routing ##
|
||||
When I went ahead and wrote in my first drawings the warning, that the kernel in scenario 2 and 3 needs to have forwarding in place, I finally tested, that this is not true. **Both scenarios are working without kernel forwarding beeing activated!**
|
||||
The background: The deflecting routing table cames into the game, before the kernel has to made the decision, that packets with non local ip addresses in source and destination must be forwarded. After the routing rule deliveres the packet to sslh and sslh rewrites the source ip, the packet is treated as local, and can pass the system.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Transparent Proxy Configuration Using IP Routing#
|
||||
This documentation is another explanation of the transparent proxy with the goal, beeing secure and minimalistic. Besides this documentation will explain, how and why this configuration works.
|
||||
The explanation will only describe the connection to sshd, so the target sshd can be replace with any other target service, sslh supports.
|
||||
|
||||
## Introduction in the data flow ##
|
||||
This chapter can be skipped, if you just like to configure things fast.
|
||||
This chapter is a little excurse to the dataflow. First point of all is something, which you will unfortunately not see in the nice routing diagrams for iptables or netfilter-tables (nft) like: [Iptables at wikipedia](https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Netfilter-packet-flow.svg/2560px-Netfilter-packet-flow.svg.png), [Netfilter Flow](https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Netfilter_hooks_into_Linux_networking_packet_flows).
|
||||
|
||||
Packets from local application talking to other local applications are routed through the loopback-interface. They leave postrouting to lo and reentering from there prerouting, without passing ingress/egress.
|
||||
This has nothing to do with the "**route_localnet = 1**" trick, which only makes, that the the local ip range 10.0.0.0/8 gets routed!
|
||||
As you can read in many articles, this is nothing you should do, as you may bring your system at risk, because it allows to leak packets from outside to applications, which feel themselves secure, by using those unroutable addresses.
|
||||
#### A Simple Simulation ####
|
||||
You can prove this behaviour with a simple test:
|
||||
```
|
||||
# In one terminal start socat as a local echo server
|
||||
# this is simulating sshd
|
||||
|
||||
socat TCP4-LISTEN:2000,bind=SERVER_IP,fork EXEC:cat
|
||||
|
||||
# In the next terminal start another instance of socat,
|
||||
# simulating sslh
|
||||
|
||||
socat TCP-LISTEN:3000,bind=SERVER_IP,fork TCP:SERVER_IP:2000
|
||||
|
||||
# In another terminal you can watch the traffic on lo
|
||||
|
||||
tcpdump -i lo port 2000
|
||||
|
||||
# In the last terminal talk to the echo server
|
||||
telnet SERVER_IP 2000
|
||||
```
|
||||
You will see your traffic on lo, but not on eth0, if you retry the tcpdump there.
|
||||
|
||||
If you setup sslh as non transparent proxy, it will just work, as what we have seen.
|
||||
|
||||
#### Going Transparent ####
|
||||
|
||||
In case of transparent proxy however, sslh uses some tricks, to reuse the clients IP on its outgoing interface to sshd. It opens the interface in raw mode, so it either needs to be started as root and drop privileges after binding, or you will need to give some capabilities to the sslh binary (cap_net_bind_service,cap_net_raw+ep), if you will start it as restricted user.
|
||||
In this setup we continue, with dropping priviledges.
|
||||
|
||||
Doing so, you can send packets to the sshd, listening on another interface, but, the answer packets from sshd will get routed back to the client. This however will not work, as the client would refuse those packets, because they don't belong to a tcp session, the client opened. In most cases those packets would even not reach the client, as source ip addresses from private address space, will be blocked by most internet routers and connection providers.
|
||||
|
||||
So its mandatory, to use some tricks, to get those packets back to sslh. All configurations, I have seen so far, are using two components for that. They bind sshd to lo, and than they introduce some firewall rules, to mark packets, originating from the sshd port on lo, so that those packets can be routed in a next step -based on that marking- back to sslh.
|
||||
|
||||
##### Drawbacks From Using loopback #####
|
||||
This idea has some serious drawbacks: First, you need to allow routing of the local address space, 127.0.0.0/8, with kernel configurations. Search for the string "net.ipv4.conf.default.route_localnet" and you will find lot of articles, why you should not do this.
|
||||
By allowing this, you need additional firewall rules, dropping martian packets, which otherwise would get routed to the internet from other applications, running on lo, not aware, that their traffic could be routed. You need further firewall rules, blocking incoming packets to loopback addresses, as otherwise some applications (especially udp) could be the goal of some bad traffic.
|
||||
|
||||
##### Using A Dedicated Interface #####
|
||||
So this configuration makes use of a own interface, just for the services, where sslh should hide the traffic for. We use a interface of the dummy kernel module, which was designed just for this case. It is an interface, beeing there, having no cable connection or whatsever, but applications can bind to it. We assign to this interface just a /32 private address, as this interface is not part of any network.
|
||||
|
||||
Doing so, we can avoid all the hassle with marking certain packets, coming from the single applications, sslh has to hide, as we now just route ALL traffic from this specific interface by its ip to sslh.
|
||||
We need one routing rule and one routing table, this covers as many targets sslh will serve on this interface, without adding additional rules for adding apache, openvpn and others.
|
||||
|
||||
We need no firewall rules, preventing martians, as this single routing rule will deadroute all traffic from this interface, if sslh is not catching it up.
|
||||
|
||||
We only need firewall protection for this specific ip address, when we have activated ip forwarding on that system. If the system is no router and needs no forwarding, there is no protection needed.
|
||||
|
||||
## Finally The Configuration ##
|
||||
|
||||
As described, we need as a first step a dedicated interface, just for the services, sslh should hide. Its possible, to generate individual interfaces for different configurations, however, that makes things again more complex and has no advantages seen so far.
|
||||
|
||||
### Named Routing Table ###
|
||||
As we configure the needed routing rules in the interface configuration, we need to define a name for the sslh routing table first.
|
||||
Using named routing tables helps, understanding the routing configuration, as a name indicates, why this routing table is configured.
|
||||
To do so go to _**/etc/iproute2/rt_tables**_ and add a line
|
||||
|
||||
```
|
||||
111 sslh
|
||||
```
|
||||
|
||||
### Dummy Interface ###
|
||||
Now we configure our dedicatet interface.
|
||||
In the file _**/etc/network/interfaces**_, we place this entry:
|
||||
```
|
||||
auto dummy0
|
||||
iface dummy0 inet static
|
||||
address 192.168.255.254/32
|
||||
pre-up modprobe dummy
|
||||
## Attention! with kernels, not automatically creating a dummy0
|
||||
## interface after module loading the following line should be:
|
||||
## pre-up modprobe dummy; if [ ! -e /sys/class/net/dummy0 ]; then ip link add dummy0 type dummy ; fi
|
||||
post-up ip rule add from 192.168.255.254 table sslh
|
||||
post-up ip route add local 0.0.0.0/0 dev dummy0 table sslh
|
||||
pre-down ip route del local 0.0.0.0/0 dev dummy0 table sslh
|
||||
pre-down ip rule del from 192.168.255.254 table sslh
|
||||
```
|
||||
As long, as your system has no other interfaces with private address-space, or is routing such addresses, you can continue with the given example. Otherwise you need to select a conflict free address.
|
||||
If you are updating a older current configuration, make sure, that you have no longer insecure localnet routing in place:
|
||||
```
|
||||
sysctl net.ipv4.conf.default.route_localnet
|
||||
sysctl net.ipv4.conf.all.route_localnet
|
||||
```
|
||||
should both report "0"!
|
||||
|
||||
|
||||
### Explanation Of The Routing Rules ###
|
||||
The two routing rules in the dummy0 interface configuration are the key for this configuration.
|
||||
|
||||
The first line is an routing rule entry, routing everything coming from the dummy0 ip source address to a special routing table _**sslh**_.
|
||||
|
||||
The next line generates this table implicitly, by inserting a single rule, routing everything from that ip address to dummy0.
|
||||
|
||||
Opposite to other firewall based configurations, we have those rules now tied to the dummy0 device, dedicated to the hidden services.
|
||||
When this interface comes up, the routing rules are making sure, that no martian packets can leave the system, by some processes using this IP address. When the interface goes down, we delete those rules.
|
||||
Also the startup script needs lo longer special treatment for the transparent mode.
|
||||
|
||||
|
||||
#### SSLH Default Configuration ####
|
||||
And finally you need to configute _**/etc/default/sslh**_ with the right settings for all the services, sslh should work for.
|
||||
```
|
||||
DAEMON_OPTS="--user sslh --listen SERVER_IP:443 --transparent \
|
||||
--ssh 192.168.255.254:22 --tls 192.168.255.254:443 \
|
||||
--pidfile /var/run/sslh/sslh.pid"
|
||||
```
|
||||
|
||||
#### Systemd ####
|
||||
This setup is now startup agnostic. As we don't need special treatment in the startup script, the sysV based init scripts will just work, like the systemd scripts. Nothing needs to be modified, when going transparent.
|
||||
|
||||
#### Remote Setups ####
|
||||
This concept can also be adapted for several setups, where the sshd (or any other target service) is running in a container, kvm-virtual machine, etc.
|
||||
Precondition is, that the target system is the next hop and uses the sslh-hosting system as default gateway. In addition you need to bind an additional ip-address, solely used for sshd on the corresponding interface.
|
||||
Than you can adapt the routing rule, routing traffic coming back from this ip to the sslh-routing-table.
|
||||
Its also possible, to forward to an next hop system, which has its own default gateway back, bypassing the sslh-host.
|
||||
In this case, you need to add a special route back to the sslh host, for all traffic with the sshd source ip address. This can be done similar to the two rules described above:
|
||||
```
|
||||
# first define a name for the table in /etc/iproute2/rt_tables e.g. sslh-routeback
|
||||
ip rule add from IPADRESS-OF-SERVIE table sslh-routeback
|
||||
ip route add default via IPADDRESS-OF_SSLH-HOST dev eth0 table sslh-routeback
|
||||
```
|
||||
The details are depending on your network settings. Als long, as the forward chain to the hidden service passes systems under your control, you can add backroutes on each system in that route. Precondition: The used ip address produces no conflict on those systems.
|
||||
|
||||
[I added a second document](./scenarios-for-simple-transparent-proxy.md), describing three possible scenarios in detail. Those three scenarios should cover all setups related to transparent proxying.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
File diff suppressed because it is too large.
Load diff
|
After Width: | Height: | Size: 158 KiB |
+7
-11
@@ -1,15 +1,11 @@
|
||||
# Transparent proxy
|
||||
# Transparent proxy using packet marking
|
||||
|
||||
On Linux and FreeBSD you can use the `--transparent` option to
|
||||
request transparent proxying. 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).
|
||||
Before reading further, make sure you try to set up
|
||||
transparent proxying using [IP routing](doc/simple_transparent_proxy.md).
|
||||
It is conceptually easier to understand, cleaner, and more
|
||||
portable.
|
||||
|
||||
This document shows recipes that may help to do that.
|
||||
|
||||
Note that getting this to work is very tricky and
|
||||
Using this method is very tricky and
|
||||
detail-dependant: depending on whether the target server and
|
||||
sslh are on the same machine, different machines, or
|
||||
different dockers, and tool versions, all seem to change the
|
||||
@@ -60,7 +56,7 @@ this scheme -- let me know if you manage that:
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
Tranparent proxying with IPv6 is similarly set up as follows:
|
||||
Transparent proxying with IPv6 is similarly set up as follows:
|
||||
|
||||
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
|
||||
# Not sure if this is needed for ipv6 though
|
||||
|
||||
+3
-3
@@ -365,7 +365,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy the value of a string argument to arbitary memory
|
||||
/* Copy the value of a string argument to arbitrary memory
|
||||
* location that must be large enough, converting on the way
|
||||
* (i.e. CFG_INT gets atoi() and so on) */
|
||||
/* 0: success
|
||||
@@ -862,7 +862,7 @@ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, con
|
||||
if (pmatch[pmatch_cnt].rm_so == -1) {
|
||||
/* This should not happen as regexec() did
|
||||
* match before, unless there is a
|
||||
* discrepency between the regex and the
|
||||
* discrepancy between the regex and the
|
||||
* number of backreferences */
|
||||
return 0;
|
||||
}
|
||||
@@ -1155,7 +1155,7 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
||||
|
||||
/* Typesets all the settings in a configuration as a
|
||||
* newly-allocated string. The string management is caller's
|
||||
* responsability.
|
||||
* responsibility.
|
||||
* Returns the number of scalars in the configuration */
|
||||
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* echosrv: a simple line echo server with optional prefix adding.
|
||||
*
|
||||
* echsrv --listen localhost6:1234 --prefix "ssl: "
|
||||
* echosrv --listen localhost6:1234 --prefix "ssl: "
|
||||
*
|
||||
* This will bind to 1234, and echo every line pre-pending "ssl: ". This is
|
||||
* used for testing: we create several such servers with different prefixes,
|
||||
@@ -62,9 +62,10 @@ void check_res_dump(int res, struct addrinfo *addr, char* syscall)
|
||||
|
||||
void start_echo(int fd)
|
||||
{
|
||||
int res;
|
||||
ssize_t res;
|
||||
char buffer[1 << 20];
|
||||
int ret, prefix_len;
|
||||
ssize_t ret;
|
||||
size_t prefix_len;
|
||||
int first = 1;
|
||||
|
||||
prefix_len = strlen(cfg.prefix);
|
||||
@@ -151,7 +152,7 @@ void udp_echo(struct listen_endpoint* listen_socket)
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(src_addr);
|
||||
size_t len = recvfrom(listen_socket->socketfd,
|
||||
ssize_t len = recvfrom(listen_socket->socketfd,
|
||||
data + prefix_len,
|
||||
sizeof(data) - prefix_len,
|
||||
0,
|
||||
@@ -162,11 +163,11 @@ void udp_echo(struct listen_endpoint* listen_socket)
|
||||
perror("recvfrom");
|
||||
}
|
||||
*(data + prefix_len + len) = 0;
|
||||
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
|
||||
fprintf(stderr, "%zd %s\n", len, data + prefix_len);
|
||||
|
||||
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
|
||||
|
||||
int res = sendto(listen_socket->socketfd,
|
||||
ssize_t res = sendto(listen_socket->socketfd,
|
||||
data,
|
||||
len + prefix_len,
|
||||
0,
|
||||
|
||||
+3
-1
@@ -78,7 +78,9 @@ listen:
|
||||
# connect to the first that matches.)
|
||||
#
|
||||
# tls:
|
||||
# sni_hostnames: list of FQDN for that target
|
||||
# sni_hostnames: list of FQDN for that target. Each name can
|
||||
# include wildcard following glob(7) rules.
|
||||
|
||||
# alpn_protocols: list of ALPN protocols for that target, see:
|
||||
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||
#
|
||||
|
||||
@@ -48,7 +48,10 @@ gap_array* gap_init(int len)
|
||||
gap->len = gap_len_alloc(elem_size);
|
||||
if (gap->len < len) gap->len = len;
|
||||
gap->array = malloc(gap->len * elem_size);
|
||||
if (!gap->array) return NULL;
|
||||
if (!gap->array) {
|
||||
free(gap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < gap->len; i++)
|
||||
gap->array[i] = NULL;
|
||||
@@ -99,7 +102,7 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
||||
else
|
||||
return -1;
|
||||
|
||||
for (i = start; i < len; i++) {
|
||||
for (i = start; i < len - 1; i++) {
|
||||
gap->array[i] = gap->array[i+1];
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,11 @@ static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, voi
|
||||
|
||||
static inline void* __attribute__((unused)) gap_get(gap_array* gap, int index)
|
||||
{
|
||||
/* sslh-ev routinely reads before it writes. It's not clear if it should be
|
||||
* its job to check the length (and add a gap_getlen()), or if it should be
|
||||
* gap_get()'s job. This will do for now */
|
||||
if (index >= gap->len) return NULL;
|
||||
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ fi
|
||||
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
|
||||
# If we don't have git, we can't work out what
|
||||
# version this is. It must have been downloaded as a
|
||||
# zip file.
|
||||
|
||||
# zip file.
|
||||
|
||||
# If downloaded from the release page, the directory
|
||||
# has the version number.
|
||||
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
|
||||
|
||||
|
||||
if [ "x$release" = "x" ]; then
|
||||
# If downloaded from the head, Github creates the
|
||||
# If downloaded from the head, GitHub creates the
|
||||
# zip file with all files dated from the last
|
||||
# change: use the Makefile's modification time as a
|
||||
# release number
|
||||
@@ -28,7 +28,7 @@ fi
|
||||
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
|
||||
# generate the version info based on the tag
|
||||
release=`(git describe --tags || git --describe || git describe --all --long) \
|
||||
2>/dev/null | tr -d '\n'`
|
||||
2>/dev/null | tr -s '/' '-' | tr -d '\n'`
|
||||
|
||||
# Are there uncommitted changes?
|
||||
git update-index --refresh --unmerged > /dev/null
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
# Tests scripts are in *.tst files.
|
||||
# Corresponding output is put in *.out.
|
||||
# Reference output is put in *.ref.
|
||||
# Any discrepency will be reported!
|
||||
# Any discrepancy will be reported!
|
||||
|
||||
use strict;
|
||||
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Setup a sandbox using the Landlock LSM, if available.
|
||||
|
||||
# Copyright (C) 2023 Yves Rutschle
|
||||
#
|
||||
# This program is free software; you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
#
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef HAVE_LANDLOCK
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <linux/landlock.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
/* Ubuntu 22.04 does not have this symbol */
|
||||
#ifndef LANDLOCK_ACCESS_FS_REFER
|
||||
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
|
||||
#endif
|
||||
|
||||
#ifndef landlock_create_ruleset
|
||||
static inline int
|
||||
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
|
||||
const size_t size, const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef landlock_add_rule
|
||||
static inline int landlock_add_rule(const int ruleset_fd,
|
||||
const enum landlock_rule_type rule_type,
|
||||
const void *const rule_attr,
|
||||
const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
|
||||
flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef landlock_restrict_self
|
||||
static inline int landlock_restrict_self(const int ruleset_fd,
|
||||
const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LL_TREE,
|
||||
LL_FILE
|
||||
} ll_obj_type;
|
||||
|
||||
static int add_path_ro(int ruleset_fd, ll_obj_type otype, const char* path)
|
||||
{
|
||||
int fd = open(path, O_PATH | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
print_message(msg_config_error, "Landlock: Failed to open %s: %s\n", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct landlock_path_beneath_attr path_beneath = {
|
||||
.allowed_access = (otype == LL_TREE ? LANDLOCK_ACCESS_FS_READ_DIR : 0 ) |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
.parent_fd = fd,
|
||||
};
|
||||
|
||||
int res = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
|
||||
if (res) {
|
||||
print_message(msg_config_error, "Landlock: Failed to update the ruleset with \"%s\": %s\n",
|
||||
path, strerror(errno));
|
||||
close(path_beneath.parent_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int add_libs(int ruleset_fd)
|
||||
{
|
||||
/* Access to libraries, to be able to fork */
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/lib");
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/usr/lib");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/ld.so.cache"); /* To avoid searching all libs... */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_resolv(int ruleset_fd)
|
||||
{
|
||||
/* Files to resolve names (required when dynamic resolution is used) */
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/resolv.conf");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/nsswitch.conf");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_libwrap(int ruleset_fd)
|
||||
{
|
||||
/* Files for libwrap */
|
||||
#ifdef LIBWRAP
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.allow");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts.deny");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup_landlock(void)
|
||||
{
|
||||
__u64 restrict_rules =
|
||||
LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR |
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
||||
LANDLOCK_ACCESS_FS_MAKE_DIR |
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||
LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
||||
LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||
LANDLOCK_ACCESS_FS_REFER;
|
||||
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = restrict_rules
|
||||
};
|
||||
|
||||
/* ruleset_addr.handled_access_fs contains all rights that will be restricted
|
||||
* unless explicitly added */
|
||||
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0) {
|
||||
print_message(msg_config_error, "Landlock: Failed to create a ruleset");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Add all the paths we need */
|
||||
add_libs(ruleset_fd);
|
||||
add_resolv(ruleset_fd);
|
||||
add_libwrap(ruleset_fd);
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
|
||||
print_message(msg_config_error, "Landlock: Failed to restrict privileges");
|
||||
return;
|
||||
}
|
||||
if (landlock_restrict_self(ruleset_fd, 0)) {
|
||||
print_message(msg_config_error, "Landlock: Failed to enforce ruleset");
|
||||
return;
|
||||
}
|
||||
close(ruleset_fd);
|
||||
|
||||
print_message(msg_config, "Landlock: all restricted\n");
|
||||
}
|
||||
|
||||
#else /* HAVE_LANDLOCK */
|
||||
void setup_landlock(void)
|
||||
{
|
||||
print_message(msg_config, "Landlock: not built in\n");
|
||||
return;
|
||||
}
|
||||
#endif /* HAVE_LANDLOCK */
|
||||
@@ -107,10 +107,11 @@ void print_message(msg_info info, const char* str, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, str);
|
||||
|
||||
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd)
|
||||
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
|
||||
va_start(ap, str);
|
||||
vfprintf(stderr, str, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (*info.verbose & MSG_SYSLOG) {
|
||||
va_start(ap, str);
|
||||
@@ -194,7 +195,11 @@ void log_connection(struct connection_desc* desc, const struct connection *cnx)
|
||||
|
||||
if (!desc) {
|
||||
desc = &d;
|
||||
get_connection_desc(desc, cnx);
|
||||
if (!get_connection_desc(desc, cnx)) {
|
||||
print_message(msg_connections, "%s: lost incoming connection\n",
|
||||
cnx->proto->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
print_message(msg_connections, "%s:connection from %s to %s forwarded from %s to %s\n",
|
||||
|
||||
@@ -173,13 +173,13 @@ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protoco
|
||||
* whereas the packet id is increased with every transmitted datagram.
|
||||
*/
|
||||
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128) + sizeof(uint32_t))
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))) <= 5u)
|
||||
return PROBE_MATCH;
|
||||
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160) + sizeof(uint32_t))
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u)
|
||||
@@ -323,7 +323,7 @@ static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
|
||||
if (len < min_data_packet_size + sizeof(empty_message))
|
||||
return PROBE_AGAIN;
|
||||
|
||||
if (memcmp(&p[0], empty_message, sizeof(empty_message)))
|
||||
if (memcmp(&p[0], empty_message, sizeof(empty_message)) != 0)
|
||||
return PROBE_NEXT;
|
||||
|
||||
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#CONFIG=/etc/sslh.cfg
|
||||
|
||||
#
|
||||
# Extra option to pass on comand line
|
||||
# Extra option to pass on command line
|
||||
# Those can supersede configuration file settings
|
||||
#
|
||||
#OPTIONS=
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# but many connection attempts from the same
|
||||
# origin is reason enough to block.
|
||||
#
|
||||
# Verion: 2014-03-28
|
||||
# Version: 2014-03-28
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[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
|
||||
@@ -1,10 +1,10 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer
|
||||
Description=SSL/SSH multiplexer (fork mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/conf.d/sslh
|
||||
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
|
||||
EnvironmentFile=/etc/default/sslh
|
||||
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
+29
-3
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sun Sep 11 21:43:25 2022.
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
@@ -471,6 +471,7 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_listen;
|
||||
struct arg_str* sslhcfg_ssh;
|
||||
struct arg_str* sslhcfg_tls;
|
||||
struct arg_str* sslhcfg_ssl;
|
||||
struct arg_str* sslhcfg_openvpn;
|
||||
struct arg_str* sslhcfg_tinc;
|
||||
struct arg_str* sslhcfg_wireguard;
|
||||
@@ -1333,6 +1334,15 @@ static struct compound_cl_target sslhcfg_openvpn_targets [] = {
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static struct compound_cl_target sslhcfg_ssl_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static struct compound_cl_target sslhcfg_tls_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
@@ -1395,6 +1405,18 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
.override_const = "tls",
|
||||
},
|
||||
|
||||
{ /* arg: ssl */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssl,
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_ssl_targets,
|
||||
|
||||
|
||||
.override_desc = & table_sslhcfg_protocols [0],
|
||||
.override_matchindex = 0,
|
||||
.override_const = "tls",
|
||||
},
|
||||
|
||||
{ /* arg: openvpn */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_openvpn,
|
||||
@@ -2199,6 +2221,7 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
|
||||
sslhcfg_ssh = arg_strn(NULL, "ssh", "<host:port>", 0, 10, "Set up ssh target"),
|
||||
sslhcfg_tls = arg_strn(NULL, "tls", "<host:port>", 0, 10, "Set up TLS/SSL target"),
|
||||
sslhcfg_ssl = arg_strn(NULL, "ssl", "<host:port>", 0, 10, "Set up TLS/SSL target"),
|
||||
sslhcfg_openvpn = arg_strn(NULL, "openvpn", "<host:port>", 0, 10, "Set up OpenVPN target"),
|
||||
sslhcfg_tinc = arg_strn(NULL, "tinc", "<host:port>", 0, 10, "Set up tinc target"),
|
||||
sslhcfg_wireguard = arg_strn(NULL, "wireguard", "<host:port>", 0, 10, "Set up WireGuard target"),
|
||||
@@ -2216,10 +2239,13 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
/* Parse command line */
|
||||
nerrors = arg_parse(argc, argv, argtable);
|
||||
if (nerrors) {
|
||||
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
|
||||
// print bad args
|
||||
arg_print_errors(stdout, sslhcfg_end, "sslhcfg");
|
||||
// print usage
|
||||
arg_print_syntax(stdout, argtable, "\n");
|
||||
// print options
|
||||
arg_print_glossary(stdout, argtable, " %-25s\t%s\n");
|
||||
return -1;
|
||||
fprintf(stderr, "Invalid args are ignored, please fix them\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sun Sep 11 21:43:25 2022.
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
|
||||
@@ -39,8 +39,6 @@ struct watchers {
|
||||
|
||||
struct listen_endpoint* listen_sockets;
|
||||
gap_array* fd2ls; /* Array indexed by file descriptor, pointing to listen_sockets */
|
||||
|
||||
int max_fd; /* legacy to be removed, still required for UDP */
|
||||
};
|
||||
|
||||
static void cnx_read_cb(EV_P_ ev_io *w, int revents);
|
||||
@@ -80,8 +78,6 @@ void watchers_add_read(watchers* w, int fd)
|
||||
gap_set(w->ev_ior, fd, io);
|
||||
}
|
||||
ev_io_start(loop, io);
|
||||
|
||||
if (fd > w->max_fd) w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
void watchers_del_read(watchers* w, int fd)
|
||||
@@ -101,8 +97,6 @@ void watchers_add_write(watchers* w, int fd)
|
||||
gap_set(w->ev_iow, fd, io);
|
||||
}
|
||||
ev_io_start(loop, io);
|
||||
|
||||
if (fd > w->max_fd) w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
void watchers_del_write(watchers* w, int fd)
|
||||
|
||||
+29
@@ -164,6 +164,13 @@ void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
}
|
||||
|
||||
|
||||
/* At least MacOS does not know these two options, so define them to something
|
||||
* equivalent for our use case */
|
||||
#ifndef ENONET
|
||||
#define ENONET EWOULDBLOCK
|
||||
#endif
|
||||
/* /MacOS kludge */
|
||||
|
||||
/* TCP listener: connections, fork a child for each new connection
|
||||
* IN:
|
||||
* endpoint: array of listening endpoint objects
|
||||
@@ -177,6 +184,26 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", /*void*/ );
|
||||
if (in_socket == -1) {
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
|
||||
__FILE__, __LINE__, "accept", errno, strerror(errno));
|
||||
switch(in_socket) {
|
||||
case ENETDOWN: /* accept(2) cites all these errnos as "you should retry" */
|
||||
case EPROTO:
|
||||
case ENOPROTOOPT:
|
||||
case EHOSTDOWN:
|
||||
case ENONET:
|
||||
case EHOSTUNREACH:
|
||||
case EOPNOTSUPP:
|
||||
case ENETUNREACH:
|
||||
case ECONNABORTED:
|
||||
continue;
|
||||
|
||||
default: /* Otherwise, it's something wrong in our parameters, we fail */
|
||||
return;
|
||||
}
|
||||
}
|
||||
print_message(msg_fd, "accepted fd %d\n", in_socket);
|
||||
|
||||
switch(fork()) {
|
||||
@@ -223,6 +250,8 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
|
||||
else
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
/* We're in the parent, we don't need to do anything */
|
||||
|
||||
+17
-7
@@ -37,6 +37,7 @@
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "tcp-probe.h"
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
@@ -60,20 +61,26 @@ static void printcaps(void) {
|
||||
|
||||
static void printsettings(void)
|
||||
{
|
||||
char buf[NI_MAXHOST];
|
||||
char buf[NI_MAXHOST + 256]; /* 256 > " family %d %d" for reasonable ints */
|
||||
int i;
|
||||
struct sslhcfg_protocols_item *p;
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
strcpy(buf, "resolve on forward");
|
||||
if (!p->resolve_on_forward) {
|
||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||
size_t len = strlen(buf);
|
||||
sprintf(buf+len, " family %d %d",
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family);
|
||||
}
|
||||
print_message(msg_config,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n",
|
||||
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
|
||||
p->name,
|
||||
sprintaddr(buf, sizeof(buf), p->saddr),
|
||||
buf,
|
||||
p->service,
|
||||
p->log_level,
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family,
|
||||
p->keepalive ? "keepalive" : "",
|
||||
p->fork ? "fork" : "",
|
||||
p->transparent ? "transparent" : ""
|
||||
@@ -92,7 +99,8 @@ static void printsettings(void)
|
||||
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
#ifdef ENABLE_REGEX
|
||||
{
|
||||
int num_patterns, i, error;
|
||||
size_t num_patterns, i;
|
||||
int error;
|
||||
pcre2_code** pattern_list;
|
||||
PCRE2_SIZE error_offset;
|
||||
PCRE2_UCHAR8 err_str[120];
|
||||
@@ -180,7 +188,7 @@ void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
#endif
|
||||
|
||||
for (i = 0; i < cfg->protocols_len; ++i) {
|
||||
if (strcmp(cfg->protocols[i].name, "tls")) {
|
||||
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
|
||||
if (cfg->protocols[i].sni_hostnames_len) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Config option sni_hostnames is only applicable for tls\n",
|
||||
@@ -239,6 +247,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);
|
||||
}
|
||||
@@ -277,6 +286,7 @@ int main(int argc, char *argv[], char* envp[])
|
||||
|
||||
if (cfg.user || cfg.chroot)
|
||||
drop_privileges(cfg.user, cfg.chroot);
|
||||
setup_landlock();
|
||||
|
||||
printcaps();
|
||||
|
||||
|
||||
+4
-4
@@ -67,7 +67,7 @@ static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
void watchers_add_read(watchers* w, int fd)
|
||||
{
|
||||
FD_SET(fd, &w->fds_r);
|
||||
if (fd > w->max_fd)
|
||||
if (fd + 1 > w->max_fd)
|
||||
w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
|
||||
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
|
||||
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
@@ -180,9 +180,9 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
for (i = 0; i < fd_info.num_probing; i++) {
|
||||
struct connection* cnx = gap_get(fd_info.probing_list, i);
|
||||
if (!cnx || cnx->state != ST_PROBING) {
|
||||
print_message(msg_int_error, "Inconsistent probing: cnx=%0xp\n", cnx);
|
||||
print_message(msg_int_error, "Inconsistent probing: cnx=0x%p\n", cnx);
|
||||
if (cnx)
|
||||
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx);
|
||||
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx->state);
|
||||
exit(1);
|
||||
}
|
||||
if (cnx->probe_timeout < time(NULL)) {
|
||||
|
||||
@@ -186,6 +186,19 @@ cl_groups: (
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
# Redundant with the --tls setting before, for backwards compatibility
|
||||
{ name: "ssl"; pattern: "(.+):(\w+)"; description: "Set up TLS/SSL target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "tls" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "openvpn"; pattern: "(.+):(\w+)"; description: "Set up OpenVPN target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
|
||||
+251
-85
@@ -1,39 +1,72 @@
|
||||
#include <libconfig.h>
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include "common.h"
|
||||
|
||||
|
||||
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||
|
||||
static char* resolve_listen(const char *hostname, const char *port) {
|
||||
/* Need room in the strcat for \0 and :
|
||||
* the format in the socket unit file is hostname:port */
|
||||
char *conn = malloc(strlen(hostname)+strlen(port)+2);
|
||||
CHECK_ALLOC(conn, "malloc");
|
||||
typedef struct FileList FileList;
|
||||
|
||||
struct FileList {
|
||||
char *name;
|
||||
struct FileList *prev;
|
||||
};
|
||||
|
||||
static void free_file_list(FileList *fl) {
|
||||
while (fl != NULL) {
|
||||
FileList *prev = fl->prev;
|
||||
free(fl->name);
|
||||
free(fl);
|
||||
fl = prev;
|
||||
}
|
||||
}
|
||||
|
||||
static FILE *err_log;
|
||||
static bool systemd_invoked = false;
|
||||
static const char
|
||||
*socket_ext = ".socket",
|
||||
*dropin_ext = ".conf",
|
||||
*service_ext = ".service.d/",
|
||||
*fork_unit_file = "/sslh@",
|
||||
*select_unit_file = "/sslh-select@",
|
||||
*fork_unit = "# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Requires=sslh@%s.socket\n"
|
||||
"Conflicts=sslh-select@%s.service\n"
|
||||
"PartOf=sslh@%s.socket\n",
|
||||
*select_unit = "# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Requires=sslh@%s.socket\n"
|
||||
"Conflicts=sslh@%s.service\n"
|
||||
"PartOf=sslh@%s.socket\n";
|
||||
|
||||
static char *resolve_listen(const char *hostname, const char *port) {
|
||||
char *conn = calloc(1, strlen(hostname) + strlen(port) + 2);
|
||||
CHECK_ALLOC(conn, "malloc")
|
||||
strcpy(conn, hostname);
|
||||
strcat(conn, ":");
|
||||
strcat(conn, port);
|
||||
|
||||
return conn;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int get_listen_from_conf(const char *filename, char **listen[]) {
|
||||
config_t config;
|
||||
config_setting_t *setting, *addr;
|
||||
const char *hostname, *port;
|
||||
int len = 0;
|
||||
|
||||
/* look up the listen stanzas in the config file so these
|
||||
* can be used in the socket file generated */
|
||||
config_init(&config);
|
||||
if (config_read_file(&config, filename) == CONFIG_FALSE) {
|
||||
/* we don't care if file is missing, skip it */
|
||||
if (config_error_line(&config) != 0) {
|
||||
fprintf(stderr, "%s:%d:%s\n",
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: %s%d%s\n",
|
||||
filename,
|
||||
config_error_line(&config),
|
||||
config_error_text(&config));
|
||||
@@ -42,114 +75,247 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
|
||||
} else {
|
||||
setting = config_lookup(&config, "listen");
|
||||
if (setting) {
|
||||
int i;
|
||||
len = config_setting_length(setting);
|
||||
*listen = malloc(len * sizeof(**listen));
|
||||
CHECK_ALLOC(*listen, "malloc");
|
||||
for (i = 0; i < len; i++) {
|
||||
CHECK_ALLOC(*listen, "malloc")
|
||||
for (int i = 0; i < len; i++) {
|
||||
addr = config_setting_get_elem(setting, i);
|
||||
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
|
||||
config_setting_lookup_string(addr, "port", &port))) {
|
||||
fprintf(stderr,
|
||||
"line %d:Incomplete specification (hostname and port required)\n",
|
||||
if (!(config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n",
|
||||
config_setting_source_line(addr));
|
||||
return -1;
|
||||
} else {
|
||||
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
|
||||
CHECK_ALLOC((*listen)[i], "malloc");
|
||||
strcpy((*listen)[i], resolve_listen(hostname, port));
|
||||
(*listen)[i] = resolve_listen(hostname, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
|
||||
int i;
|
||||
|
||||
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
|
||||
fprintf(socket,
|
||||
"# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Before=sslh.service\n"
|
||||
"Before=sslh@%s.service\n"
|
||||
"SourcePath=%s\n"
|
||||
"PartOf=sslh@%s.service\n"
|
||||
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
|
||||
"[Socket]\n"
|
||||
"FreeBind=true\n",
|
||||
source);
|
||||
|
||||
for (i = 0; i < num_addr; i++) {
|
||||
cfg,
|
||||
source,
|
||||
cfg);
|
||||
for (int i = 0; i < num_addr; i++) {
|
||||
fprintf(socket, "ListenStream=%s\n", listen[i]);
|
||||
}
|
||||
fprintf(socket,
|
||||
"\n[Install]\n"
|
||||
"WantedBy=sockets.target\n");
|
||||
}
|
||||
|
||||
static int write_unit_dropin(const char *runtime_unit_dir, const bool is_fork, const char *cfg) {
|
||||
const char
|
||||
*unit_file = is_fork ? fork_unit_file : select_unit_file,
|
||||
*unit_string = is_fork ? fork_unit : select_unit;
|
||||
FILE *dropin_fd = stdout;
|
||||
if (systemd_invoked) {
|
||||
//Systemd drop-in configuration for the base select service sslh@%I.service
|
||||
const size_t runtime_len = strlen(runtime_unit_dir);
|
||||
size_t len = strlen(unit_file) + strlen(cfg) + strlen(service_ext) + strlen(dropin_ext);
|
||||
char dropin_dir[runtime_len + len + 1];
|
||||
strcpy(dropin_dir, runtime_unit_dir);
|
||||
strcat(dropin_dir, unit_file);
|
||||
strcat(dropin_dir, cfg);
|
||||
strcat(dropin_dir, service_ext);
|
||||
|
||||
if (mkdir(dropin_dir, S_IRWXU | S_IRWXG | S_IROTH)) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not create directory '%s' to generate drop-in configuration: %s\n",
|
||||
dropin_dir,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
len = len + strlen(cfg) + strlen(dropin_ext);
|
||||
char dropin_path[len + 1];
|
||||
strcpy(dropin_path, dropin_dir);
|
||||
strcat(dropin_path, cfg);
|
||||
strcat(dropin_path, dropin_ext);
|
||||
dropin_fd = fopen(dropin_path, "w");
|
||||
if (!dropin_fd) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open '%s' to generate drop-in configuration: %s\n",
|
||||
dropin_path,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
fprintf(dropin_fd, unit_string, cfg, cfg, cfg);
|
||||
if (systemd_invoked) {
|
||||
fclose(dropin_fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gen_sslh_config(char *runtime_unit_dir) {
|
||||
char *sslh_conf;
|
||||
int num_addr;
|
||||
FILE *config;
|
||||
int status = 0;
|
||||
const char *config_dir = "/etc/sslh/";
|
||||
char **listen;
|
||||
FILE *runtime_conf_fd = stdout;
|
||||
const char *unit_file;
|
||||
DIR *d = opendir(config_dir);
|
||||
FileList *fa = NULL;
|
||||
|
||||
/* There are two default locations so check both with first given preference */
|
||||
sslh_conf = "/etc/sslh.cfg";
|
||||
|
||||
config = fopen(sslh_conf, "r");
|
||||
if (config == NULL) {
|
||||
sslh_conf="/etc/sslh/sslh.cfg";
|
||||
config = fopen(sslh_conf, "r");
|
||||
if (config == NULL) {
|
||||
return -1;
|
||||
if (d) {
|
||||
struct dirent *dir;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
|
||||
continue;
|
||||
}
|
||||
FileList *lfa = malloc(sizeof(FileList));
|
||||
CHECK_ALLOC(lfa, "malloc")
|
||||
lfa->name = malloc(strlen(dir->d_name) + 1);
|
||||
CHECK_ALLOC(lfa->name, "malloc")
|
||||
strcpy(lfa->name, dir->d_name);
|
||||
lfa->prev = NULL;
|
||||
if (fa) {
|
||||
lfa->prev = fa;
|
||||
}
|
||||
fa = lfa;
|
||||
}
|
||||
closedir(d);
|
||||
} else {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Configuration directory '/etc/sslh/' does not exist! No units generated.\n");
|
||||
//Config directory /etc/sslh/ does not exist
|
||||
return 0;
|
||||
}
|
||||
|
||||
fclose(config);
|
||||
|
||||
num_addr = get_listen_from_conf(sslh_conf, &listen);
|
||||
if (num_addr < 0)
|
||||
return -1;
|
||||
|
||||
/* If this is run by systemd directly write to the location told to
|
||||
* otherwise write to standard out so that it's trivial to check what
|
||||
* will be written */
|
||||
if (runtime_unit_dir && *runtime_unit_dir) {
|
||||
unit_file = "/sslh.socket";
|
||||
size_t uf_len = strlen(unit_file);
|
||||
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
|
||||
char *runtime_conf = malloc(runtime_len);
|
||||
CHECK_ALLOC(runtime_conf, "malloc");
|
||||
strcpy(runtime_conf, runtime_unit_dir);
|
||||
strcat(runtime_conf, unit_file);
|
||||
runtime_conf_fd = fopen(runtime_conf, "w");
|
||||
if (!fa) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Configuration directory '/etc/sslh/' is empty! No units generated.\n");
|
||||
//No configuration files in /etc/sslh/
|
||||
return 0;
|
||||
}
|
||||
FileList *fa_ref = fa;
|
||||
// size_t num_listen_addresses = 0;
|
||||
// char **listen_addresses = NULL;
|
||||
//Process all config files
|
||||
do {
|
||||
char *end = strstr(fa->name, ".cfg");
|
||||
if (!end) {
|
||||
continue;
|
||||
}
|
||||
//Current sslh config name
|
||||
const size_t end_count = end - fa->name;
|
||||
char config_name[end_count + 1];
|
||||
memcpy(config_name, fa->name, end_count);
|
||||
config_name[end_count] = '\0';
|
||||
|
||||
|
||||
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
|
||||
//Full path to current sslh config
|
||||
char full_path[strlen(config_dir) + strlen(fa->name) + 1];
|
||||
strcpy(full_path, config_dir);
|
||||
strcat(full_path, fa->name);
|
||||
FILE *config = fopen(full_path, "r");
|
||||
if (!config) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open config file '%s': %s\n",
|
||||
full_path,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
} else {
|
||||
fclose(config);
|
||||
int num_addr = get_listen_from_conf(full_path, &listen);
|
||||
if (num_addr <= 0) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: sslh config '%s' contains no valid listen configurations!\n",
|
||||
fa->name);
|
||||
status |= -1;
|
||||
continue;
|
||||
}
|
||||
FILE *socket_fd = stdout;
|
||||
if (systemd_invoked) {
|
||||
//Systemd socket for the current sslh config
|
||||
const size_t len = strlen(runtime_unit_dir) + strlen(fork_unit_file) + strlen(config_name) + strlen(socket_ext);
|
||||
char socket_path[len + 1];
|
||||
strcpy(socket_path, runtime_unit_dir);
|
||||
strcat(socket_path, fork_unit_file);
|
||||
strcat(socket_path, config_name);
|
||||
strcat(socket_path, socket_ext);
|
||||
socket_fd = fopen(socket_path, "w");
|
||||
if (!socket_fd) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open '%s' to generate socket configuration: %s\n",
|
||||
socket_path,
|
||||
strerror(errno));
|
||||
status |= errno;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Write socket unit
|
||||
write_socket_unit(socket_fd, listen, num_addr, config_name, full_path);
|
||||
//Write forking drop-in config
|
||||
status |= write_unit_dropin(runtime_unit_dir, false, config_name);
|
||||
//Write select drop-in config
|
||||
status |= write_unit_dropin(runtime_unit_dir, true, config_name);
|
||||
if (systemd_invoked) {
|
||||
fclose(socket_fd);
|
||||
}
|
||||
// if (listen_addresses) {
|
||||
// //Check for overlapping addresses
|
||||
// for (size_t i = 0; i < num_listen_addresses; i++) {
|
||||
// for (size_t j = 0; j < num_addr; j++) {
|
||||
// if (strcmp(*(listen_addresses + i), *(listen + j)) == 0) {
|
||||
// fprintf(err_log, "systemd-sslh-generator: Overlapping listen addresses across sslh configurations!");
|
||||
// return -1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// char *listen_addresses_copy[num_listen_addresses + num_addr];
|
||||
// if (listen_addresses) {
|
||||
// //Copy previous configurations' listen addresses into temp
|
||||
// memcpy(listen_addresses_copy, listen_addresses, num_listen_addresses);
|
||||
// //Free global list of listen addresses
|
||||
// free(listen_addresses);
|
||||
// }
|
||||
// //Copy listen addresses from current configuration into temp
|
||||
// memcpy(listen_addresses_copy + num_listen_addresses, listen, num_addr);
|
||||
// num_listen_addresses += num_addr;
|
||||
// listen_addresses = malloc(num_listen_addresses * sizeof(char *));
|
||||
// CHECK_ALLOC(listen_addresses, "malloc")
|
||||
// //Append all addresses to global list
|
||||
// memcpy(listen_addresses, listen_addresses_copy, num_listen_addresses);
|
||||
//Free all allocated listen strings
|
||||
for (size_t i = 0; i < num_addr; i++) {
|
||||
free(*(listen + i));
|
||||
}
|
||||
}
|
||||
} while((fa = fa->prev));
|
||||
free_file_list(fa_ref);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
int r = 0;
|
||||
int k;
|
||||
char *runtime_unit_dest = "";
|
||||
|
||||
if (argc > 1 && (argc != 4) ) {
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc == 1 || argc == 4) {
|
||||
systemd_invoked = argc == 4;
|
||||
if (systemd_invoked) {
|
||||
err_log = fopen("/dev/kmsg", "w");
|
||||
if (!err_log) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
err_log = stderr;
|
||||
}
|
||||
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
|
||||
if (systemd_invoked) {
|
||||
fclose(err_log);
|
||||
}
|
||||
if (!r) {
|
||||
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
|
||||
}
|
||||
return r < 0 ? -1 : 0;
|
||||
} else {
|
||||
printf("This program takes three or no arguments.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argc > 1)
|
||||
runtime_unit_dest = argv[1];
|
||||
|
||||
k = gen_sslh_config(runtime_unit_dest);
|
||||
if (k < 0)
|
||||
r = k;
|
||||
|
||||
return r < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ if ($RB_RESOLVE_ADDRESS) {
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
exec "./sslh-select -v 3 -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
|
||||
exec "./sslh-select -v 3 -f -u $user --listen blahblah.nonexistent:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
waitpid $sslh_pid, 0;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
use Data::Dumper;
|
||||
use Conf::Libconfig;
|
||||
use Conf::Libconfig 1.0.3;
|
||||
|
||||
## BEGIN TEST CONFIG
|
||||
|
||||
@@ -44,7 +44,7 @@ $conf->read_file("test.cfg");
|
||||
|
||||
|
||||
# Pick one address for TCP and one for UDP
|
||||
my @listen = @{$conf->fetch_array("listen")};
|
||||
my @listen = @{$conf->value("listen")};
|
||||
my ($sslh_tcp_address, $sslh_udp_address);
|
||||
|
||||
foreach my $l (@listen) {
|
||||
@@ -186,7 +186,7 @@ sub udp_client {
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (@{$conf->fetch_array("protocols")}) {
|
||||
foreach my $p (@{$conf->value("protocols")}) {
|
||||
if (!fork) {
|
||||
my $udp = $p->{is_udp} ? "--udp" : "";
|
||||
my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null";
|
||||
@@ -203,7 +203,7 @@ sleep 2; # Let echosrv's and sslh start
|
||||
my ($c_in, $c_out);
|
||||
pipe $c_in, $c_out;
|
||||
|
||||
my @protocols = @{$conf->fetch_array("protocols")};
|
||||
my @protocols = @{$conf->value("protocols")};
|
||||
|
||||
if (!fork) {
|
||||
# Process that starts all the clients
|
||||
|
||||
+4
-6
@@ -67,14 +67,16 @@ static void shovel(struct connection *cnx, int active_fd, struct loop_info* fd_i
|
||||
}
|
||||
|
||||
|
||||
/* Returns the queue index that contains the specified file descriptor */
|
||||
/* Returns the queue index that contains the specified file descriptor.
|
||||
* This is called by the *_process functions, which got cnx from the fd, so it
|
||||
* is impossible for fd to not be cnx, hence we die if it happens */
|
||||
static int active_queue(struct connection* cnx, int fd)
|
||||
{
|
||||
if (cnx->q[0].fd == fd) return 0;
|
||||
if (cnx->q[1].fd == fd) return 1;
|
||||
|
||||
print_message(msg_int_error, "file descriptor %d not found in connection object\n", fd);
|
||||
return -1;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Process a TCP read event on the specified file descriptor */
|
||||
@@ -156,13 +158,9 @@ static int connect_queue(struct connection* cnx,
|
||||
log_connection(NULL, cnx);
|
||||
flush_deferred(q);
|
||||
if (q->deferred_data) {
|
||||
/*
|
||||
FD_SET(q->fd, &fd_info->watchers->fds_w);
|
||||
FD_CLR(cnx->q[0].fd, &fd_info->watchers->fds_r); */
|
||||
watchers_add_write(fd_info->watchers, q->fd);
|
||||
watchers_del_read(fd_info->watchers, cnx->q[0].fd);
|
||||
}
|
||||
/* FD_SET(q->fd, &fd_info->watchers->fds_r); */
|
||||
watchers_add_read(fd_info->watchers, q->fd);
|
||||
collection_add_fd(fd_info->collection, cnx, q->fd);
|
||||
return q->fd;
|
||||
|
||||
+3
-2
@@ -60,12 +60,13 @@ int probe_client_protocol(struct connection *cnx)
|
||||
|
||||
static void tcp_protocol_list_init(void)
|
||||
{
|
||||
tcp_protocols = calloc(cfg.protocols_len, sizeof(tcp_protocols));
|
||||
CHECK_ALLOC(tcp_protocols, "tcp_protocols");
|
||||
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
|
||||
if (!p->is_udp) {
|
||||
tcp_protocols[tcp_protocols_len] = p;
|
||||
tcp_protocols_len++;
|
||||
tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols));
|
||||
tcp_protocols[tcp_protocols_len-1] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ listen:
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; },
|
||||
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; },
|
||||
{ name: "socks5"; host: "localhost"; port: "9001"; },
|
||||
{ name: "http"; host: "localhost"; port: "9002"; },
|
||||
{ name: "tinc"; host: "localhost"; port: "9003"; },
|
||||
@@ -53,6 +53,7 @@ protocols:
|
||||
{ name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
|
||||
udp_timeout: 30;
|
||||
regex_patterns: [ "^foo" ];
|
||||
resolve_on_forward: true;
|
||||
},
|
||||
{ name: "regex"; host: "localhost"; port: "9011";
|
||||
regex_patterns: [ "^foo", "^bar" ];
|
||||
|
||||
@@ -224,7 +224,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
|
||||
switch (data[pos]) { /* name type */
|
||||
case 0x00: /* host_name */
|
||||
if(has_match(tls_data->sni_hostname_list, tls_data->sni_list_len, data + pos + 3, len)) {
|
||||
return len;
|
||||
return (int)len;
|
||||
} else {
|
||||
return TLS_ENOEXT;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
|
||||
return TLS_EPROTOCOL;
|
||||
|
||||
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
|
||||
return len;
|
||||
return (int)len;
|
||||
} else if (len > 0) {
|
||||
print_message(msg_probe_error, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
||||
}
|
||||
@@ -301,11 +301,11 @@ struct TLSProtocol *
|
||||
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, const char** list, size_t list_len) {
|
||||
if (alpn) {
|
||||
tls_data->alpn_protocol_list = list;
|
||||
tls_data->alpn_list_len = list_len;
|
||||
tls_data->alpn_list_len = (int)list_len;
|
||||
tls_data->match_mode.tls_match_alpn = 1;
|
||||
} else {
|
||||
tls_data->sni_hostname_list = list;
|
||||
tls_data->sni_list_len = list_len;
|
||||
tls_data->sni_list_len = (int)list_len;
|
||||
tls_data->match_mode.tls_match_sni = 1;
|
||||
}
|
||||
|
||||
|
||||
+41
-18
@@ -36,10 +36,10 @@
|
||||
|
||||
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
|
||||
{
|
||||
struct sockaddr* addr1 = &cnx1->client_addr;
|
||||
struct sockaddr_storage* addr1 = &cnx1->client_addr;
|
||||
socklen_t addrlen1 = cnx1->addrlen;
|
||||
|
||||
struct sockaddr* addr2 = &cnx2->client_addr;
|
||||
struct sockaddr_storage* addr2 = &cnx2->client_addr;
|
||||
socklen_t addrlen2 = cnx2->addrlen;
|
||||
|
||||
if (addrlen1 != addrlen2) return -1;
|
||||
@@ -52,13 +52,13 @@ static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
|
||||
* lowest bytes of remote port */
|
||||
static int hash_make_key(hash_item new)
|
||||
{
|
||||
struct sockaddr* addr = &new->client_addr;
|
||||
struct sockaddr_storage* addr = &new->client_addr;
|
||||
//socklen_t addrlen = new->addrlen;
|
||||
struct sockaddr_in* addr4;
|
||||
struct sockaddr_in6* addr6;
|
||||
int out;
|
||||
|
||||
switch (addr->sa_family) {
|
||||
switch (((struct sockaddr*)addr)->sa_family) {
|
||||
case AF_INET:
|
||||
addr4 = (struct sockaddr_in*)addr;
|
||||
out = addr4->sin_port;
|
||||
@@ -86,13 +86,14 @@ static void udp_protocol_list_init(void)
|
||||
if (p->is_udp) {
|
||||
udp_protocols_len++;
|
||||
udp_protocols = realloc(udp_protocols, udp_protocols_len * sizeof(*udp_protocols));
|
||||
CHECK_ALLOC(udp_protocols, "realloc");
|
||||
udp_protocols[udp_protocols_len-1] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Configuration sanity check for UDP:
|
||||
* - If there is a listening addres, there must be at least one target
|
||||
* - If there is a listening address, there must be at least one target
|
||||
*/
|
||||
static void udp_sanity_check(void)
|
||||
{
|
||||
@@ -129,7 +130,7 @@ void udp_init(struct loop_info* fd_info)
|
||||
* If yes, returns file descriptor of connection
|
||||
* If not, returns -1
|
||||
* */
|
||||
static int known_source(hash* h, struct sockaddr* addr, socklen_t addrlen)
|
||||
static int known_source(hash* h, struct sockaddr_storage* addr, socklen_t addrlen)
|
||||
{
|
||||
struct connection search;
|
||||
search.client_addr = *addr;
|
||||
@@ -221,6 +222,28 @@ static void mark_active(struct connection* cnx)
|
||||
}
|
||||
|
||||
|
||||
/* Creates a new non-blocking socket */
|
||||
static int nonblocking_socket(struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (proto->resolve_on_forward) {
|
||||
res = resolve_split_name(&(proto->saddr), proto->host,
|
||||
proto->port);
|
||||
if (res) return -1;
|
||||
}
|
||||
|
||||
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||
res = set_nonblock(out);
|
||||
if (res == -1) {
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, "udp:socket:nonblock", errno, strerror(errno));
|
||||
close(out);
|
||||
return -1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/* Process UDP coming from outside (client towards server)
|
||||
* If it's a new source, probe; otherwise, forward to previous target
|
||||
* Returns: newly allocate connections, for new connections
|
||||
@@ -229,15 +252,16 @@ static void mark_active(struct connection* cnx)
|
||||
struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
{
|
||||
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
struct sockaddr src_addr;
|
||||
struct sockaddr_storage src_addr;
|
||||
struct addrinfo addrinfo;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
cnx_collection* collection = fd_info->collection;
|
||||
struct connection* cnx;
|
||||
ssize_t len;
|
||||
socklen_t addrlen;
|
||||
int res, target, out = -1;
|
||||
char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
|
||||
ssize_t res;
|
||||
int target, out = -1;
|
||||
char data[65536]; /* Theoretical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
|
||||
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
|
||||
malloc/free overhead for each packet, when really 64K is not that much */
|
||||
|
||||
@@ -245,19 +269,19 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
udp_timeouts(fd_info);
|
||||
|
||||
addrlen = sizeof(src_addr);
|
||||
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
|
||||
len = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr*) &src_addr, &addrlen);
|
||||
if (len < 0) {
|
||||
perror("recvfrom");
|
||||
return NULL;
|
||||
}
|
||||
target = known_source(fd_info->hash_sources, &src_addr, addrlen);
|
||||
addrinfo.ai_addr = &src_addr;
|
||||
addrinfo.ai_addr = (struct sockaddr*) &src_addr;
|
||||
addrinfo.ai_addrlen = addrlen;
|
||||
print_message(msg_probe_info, "received %ld UDP from %d:%s\n",
|
||||
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||
|
||||
if (target == -1) {
|
||||
res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
|
||||
res = probe_buffer(data, (int)len, udp_protocols, udp_protocols_len, &proto);
|
||||
/* First version: if we can't work out the protocol from the first
|
||||
* packet, drop it. Conceivably, we could store several packets to
|
||||
* run probes on packet sets */
|
||||
@@ -266,9 +290,8 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||
res = set_nonblock(out);
|
||||
CHECK_RES_RETURN(res, "udp:socket:nonblock", NULL);
|
||||
out = nonblocking_socket(proto);
|
||||
if (out == -1) return NULL;
|
||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
|
||||
if (!cnx) return NULL;
|
||||
target = out;
|
||||
@@ -281,7 +304,7 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||
|
||||
res = new_source(fd_info->hash_sources, cnx);
|
||||
if (res == -1) {
|
||||
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increaѕe udp_max_connections");
|
||||
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increase udp_max_connections");
|
||||
collection_remove_cnx(collection, cnx);
|
||||
return NULL;
|
||||
}
|
||||
@@ -302,13 +325,13 @@ void udp_s2c_forward(struct connection* cnx)
|
||||
{
|
||||
int sockfd = cnx->target_sock;
|
||||
char data[65536];
|
||||
int res;
|
||||
ssize_t res;
|
||||
|
||||
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
||||
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
|
||||
CHECK_RES_DIE(res, "udp_listener/recvfrom");
|
||||
res = sendto(cnx->local_endpoint, data, res, 0,
|
||||
&cnx->client_addr, cnx->addrlen);
|
||||
(struct sockaddr*)&cnx->client_addr, cnx->addrlen);
|
||||
mark_active(cnx);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user