mirror of
https://github.com/yrutschle/sslh.git
synced 2025-04-13 07:37:15 +03:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
04f258e705
63
.github/workflows/container-build.yaml
vendored
Normal file
63
.github/workflows/container-build.yaml
vendored
Normal file
@ -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 }}
|
47
ChangeLog
47
ChangeLog
@ -1,3 +1,32 @@
|
|||||||
|
v2.0:
|
||||||
|
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.
|
||||||
|
|
||||||
|
New log system: instead of --verbose with arbitrary
|
||||||
|
levels, there are now several message classes. Each
|
||||||
|
message class can be set to go to stderr, syslog, or
|
||||||
|
both. Classes are documented in example.cfg.
|
||||||
|
|
||||||
|
UDP connections are now managed in a hash to avoid
|
||||||
|
linear searches. The downside is that the number of
|
||||||
|
UDP connections is a hard limit, configurable with
|
||||||
|
the 'udp_max_connections', which defaults to 1024.
|
||||||
|
Timeouts are managed with lists.
|
||||||
|
|
||||||
|
inetd merges stderr output to what is sent to the
|
||||||
|
client, which is a security issue as it might give
|
||||||
|
information to an attacker. When inetd is activated,
|
||||||
|
stderr is forcibly closed.
|
||||||
|
|
||||||
|
New protocol-level option `resolve_on_forward`,
|
||||||
|
requests that target names are resolved at each
|
||||||
|
connection instead of at startup. Useful for dynamic
|
||||||
|
DNS situations. (Paul Schroeder/milkpirate)
|
||||||
|
|
||||||
|
New probe for MSRDP (akappner).
|
||||||
|
|
||||||
v1.22: 17AUG2021
|
v1.22: 17AUG2021
|
||||||
sslh-select now supports UDP protocols.
|
sslh-select now supports UDP protocols.
|
||||||
Probes specified in the `protocols`
|
Probes specified in the `protocols`
|
||||||
@ -12,7 +41,7 @@ v1.22: 17AUG2021
|
|||||||
combined with incoming TLS with SNI. UDP clients
|
combined with incoming TLS with SNI. UDP clients
|
||||||
and servers need to agree on the IPv4/IPv6 they use:
|
and servers need to agree on the IPv4/IPv6 they use:
|
||||||
use the same protocol on all sides! Often, this
|
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)
|
UDP sender-receiver pairs (connections, so to speak)
|
||||||
are kept for 60s, which can be changed with
|
are kept for 60s, which can be changed with
|
||||||
`udp_timeout` in the configuration.
|
`udp_timeout` in the configuration.
|
||||||
@ -54,7 +83,7 @@ v1.21: 11JUL2020
|
|||||||
|
|
||||||
Added TCP_FASTOPEN support for client sockets (if
|
Added TCP_FASTOPEN support for client sockets (if
|
||||||
tfo_ok is specified in their configuration) and for
|
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)
|
(Craig Andrews)
|
||||||
|
|
||||||
Added 'minlength' option to skip a probe if less
|
Added 'minlength' option to skip a probe if less
|
||||||
@ -80,8 +109,8 @@ v1.20: 20NOV2018
|
|||||||
Before, probes were tried in order, repeating on the
|
Before, probes were tried in order, repeating on the
|
||||||
same probe as long it returned PROBE_AGAIN before
|
same probe as long it returned PROBE_AGAIN before
|
||||||
moving to the next one. This means a probe which
|
moving to the next one. This means a probe which
|
||||||
requires a lot of data (i.e. returne PROBE_AGAIN for
|
requires a lot of data (i.e. return PROBE_AGAIN for
|
||||||
a long time) could prevent sucessful matches from
|
a long time) could prevent successful matches from
|
||||||
subsequent probes. The configuration file needed to
|
subsequent probes. The configuration file needed to
|
||||||
take that into account.
|
take that into account.
|
||||||
|
|
||||||
@ -142,7 +171,7 @@ v1.18: 29MAR2016
|
|||||||
v1.17: 09MAR2015
|
v1.17: 09MAR2015
|
||||||
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
|
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
|
||||||
|
|
||||||
Transparant proxy support for FreeBSD.
|
Transparent proxy support for FreeBSD.
|
||||||
(Ruben van Staveren)
|
(Ruben van Staveren)
|
||||||
|
|
||||||
Using -F with no argument will try
|
Using -F with no argument will try
|
||||||
@ -171,7 +200,7 @@ v1.16: 11FEB2014
|
|||||||
|
|
||||||
Libcap support: Keep only CAP_NET_ADMIN if started
|
Libcap support: Keep only CAP_NET_ADMIN if started
|
||||||
as root with transparent proxying and dropping
|
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.
|
avoids having to mess with filesystem capabilities.
|
||||||
(Sebastian Schmidt/yath)
|
(Sebastian Schmidt/yath)
|
||||||
|
|
||||||
@ -180,7 +209,7 @@ v1.16: 11FEB2014
|
|||||||
actual errors if connections are dropped before
|
actual errors if connections are dropped before
|
||||||
getting to getpeername).
|
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.
|
that don't yet exist.
|
||||||
|
|
||||||
v1.15: 27JUL2013
|
v1.15: 27JUL2013
|
||||||
@ -265,7 +294,7 @@ v1.11: 21APR2012
|
|||||||
--user isn't specified, just run as current user.
|
--user isn't specified, just run as current user.
|
||||||
|
|
||||||
No longer create PID file by default, it should be
|
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
|
No longer log to syslog if in foreground. Logs are
|
||||||
instead output to stderr.
|
instead output to stderr.
|
||||||
@ -356,7 +385,7 @@ v1.8: 15JUL2011
|
|||||||
v1.7: 01FEB2010
|
v1.7: 01FEB2010
|
||||||
Added CentOS init.d script (Andre Krajnik).
|
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
|
defaults to "localhost:443" and fixed documentation
|
||||||
accordingly (pointed by Markus Schalke).
|
accordingly (pointed by Markus Schalke).
|
||||||
|
|
||||||
|
49
Dockerfile
49
Dockerfile
@ -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 \
|
WORKDIR /sslh
|
||||||
apk add \
|
|
||||||
gcc \
|
|
||||||
libconfig-dev \
|
|
||||||
make \
|
|
||||||
musl-dev \
|
|
||||||
pcre-dev \
|
|
||||||
perl && \
|
|
||||||
cd /sslh && \
|
|
||||||
make sslh-select && \
|
|
||||||
strip sslh-select
|
|
||||||
|
|
||||||
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 pcre
|
RUN 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
|
||||||
|
50
Makefile
50
Makefile
@ -3,6 +3,7 @@ VERSION=$(shell ./genver.sh -r)
|
|||||||
|
|
||||||
# Configuration -- you probably need to `make clean` if you
|
# Configuration -- you probably need to `make clean` if you
|
||||||
# change any of these
|
# change any of these
|
||||||
|
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||||
ENABLE_REGEX=1 # Enable regex probes
|
ENABLE_REGEX=1 # Enable regex probes
|
||||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||||
USELIBWRAP?= # Use libwrap?
|
USELIBWRAP?= # Use libwrap?
|
||||||
@ -19,15 +20,25 @@ MAN=sslh.8.gz # man page name
|
|||||||
# End of configuration -- the rest should take care of
|
# End of configuration -- the rest should take care of
|
||||||
# itself
|
# itself
|
||||||
|
|
||||||
|
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
||||||
|
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq ($(strip $(COV_TEST)),)
|
ifneq ($(strip $(COV_TEST)),)
|
||||||
CFLAGS_COV=-fprofile-arcs -ftest-coverage
|
CFLAGS_COV=-fprofile-arcs -ftest-coverage
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
CFLAGS +=-Wall -DLIBPCRE -g $(CFLAGS_COV)
|
AR ?= ar
|
||||||
|
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||||
|
|
||||||
|
|
||||||
LIBS=-lm -lpcre2-8
|
LIBS=-lm -lpcre2-8
|
||||||
OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o udp-listener.o collection.o gap.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
|
||||||
|
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)
|
||||||
|
EV_OBJS=processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o $(OBJS_A)
|
||||||
|
|
||||||
CONDITIONAL_TARGETS=
|
CONDITIONAL_TARGETS=
|
||||||
|
|
||||||
@ -64,32 +75,41 @@ endif
|
|||||||
|
|
||||||
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||||
|
|
||||||
.c.o: *.h version.h
|
%.o: %.c %.h version.h
|
||||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
|
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(OBJS_A): $(OBJS)
|
||||||
|
$(AR) rcs $(OBJS_A) $(OBJS)
|
||||||
|
|
||||||
version.h:
|
version.h:
|
||||||
./genver.sh >version.h
|
./genver.sh >version.h
|
||||||
|
|
||||||
sslh: sslh-fork sslh-select
|
sslh: sslh-fork sslh-select sslh-ev
|
||||||
|
|
||||||
$(OBJS): version.h common.h collection.h sslh-conf.h gap.h
|
$(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
|
||||||
|
|
||||||
|
|
||||||
|
c2s:
|
||||||
|
conf2struct sslhconf.cfg
|
||||||
|
conf2struct echosrv.cfg
|
||||||
|
|
||||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||||
conf2struct sslhconf.cfg
|
$(warning "sslhconf.cfg is more recent than sslh-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
|
||||||
|
|
||||||
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile
|
sslh-fork: version.h Makefile $(FORK_OBJS)
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
|
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
|
||||||
#strip sslh-fork
|
|
||||||
|
|
||||||
sslh-select: version.h $(OBJS) sslh-select.o Makefile
|
sslh-select: version.h $(SELECT_OBJS) Makefile
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
|
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select $(SELECT_OBJS) $(LIBS)
|
||||||
#strip sslh-select
|
|
||||||
|
sslh-ev: version.h $(EV_OBJS) Makefile
|
||||||
|
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-ev $(EV_OBJS) $(LIBS) -lev
|
||||||
|
|
||||||
systemd-sslh-generator: systemd-sslh-generator.o
|
systemd-sslh-generator: systemd-sslh-generator.o
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
||||||
|
|
||||||
echosrv-conf.c echosrv-conf.h: echosrv.cfg
|
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
|
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)
|
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
|
||||||
@ -133,7 +153,7 @@ distclean: clean
|
|||||||
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
rm -f sslh-fork sslh-select sslh-ev echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
ctags --globals -T *.[ch]
|
ctags --globals -T *.[ch]
|
||||||
|
107
README.md
107
README.md
@ -11,7 +11,7 @@ protocol that can be tested using a regular expression, can
|
|||||||
be recognised. A typical use case is to allow serving
|
be recognised. A typical use case is to allow serving
|
||||||
several services on port 443 (e.g. to connect to SSH from
|
several services on port 443 (e.g. to connect to SSH from
|
||||||
inside a corporate firewall, which almost never block port
|
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
|
Hence `sslh` acts as a protocol demultiplexer, or a
|
||||||
switchboard. With the SNI and ALPN probe, it makes a good
|
switchboard. With the SNI and ALPN probe, it makes a good
|
||||||
@ -20,8 +20,8 @@ address.
|
|||||||
|
|
||||||
`sslh` has the bells and whistles expected from a mature
|
`sslh` has the bells and whistles expected from a mature
|
||||||
daemon: privilege and capabilities dropping, inetd support,
|
daemon: privilege and capabilities dropping, inetd support,
|
||||||
systemd support, transparent proxying, chroot, logging,
|
systemd support, transparent proxying, chroot, logging,
|
||||||
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
|
IPv4 and IPv6, TCP and UDP, a fork-based and a select-based
|
||||||
model, and more.
|
model, and more.
|
||||||
|
|
||||||
Install
|
Install
|
||||||
@ -44,25 +44,23 @@ How to use
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Build docker image
|
|
||||||
|
|
||||||
make docker
|
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker container run \
|
docker run \
|
||||||
|
--cap-add CAP_NET_RAW \
|
||||||
|
--cap-add CAP_NET_BIND_SERVICE \
|
||||||
--rm \
|
--rm \
|
||||||
-it \
|
-it \
|
||||||
|
ghcr.io/yrutschle/sslh:latest \
|
||||||
|
--foreground \
|
||||||
--listen=0.0.0.0:443 \
|
--listen=0.0.0.0:443 \
|
||||||
--ssh=hostname:22 \
|
--ssh=hostname:22 \
|
||||||
--tlshostname:443 \
|
--tls=hostname:443
|
||||||
sslh:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
docker-compose example
|
docker-compose example
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
---
|
|
||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@ -70,19 +68,96 @@ services:
|
|||||||
image: sslh:latest
|
image: sslh:latest
|
||||||
hostname: sslh
|
hostname: sslh
|
||||||
ports:
|
ports:
|
||||||
- 443:443/tcp
|
- 443:443
|
||||||
command: --listen=0.0.0.0:443 --tlshostname:443 --openvpn=openvpn:1194
|
command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
|
||||||
depends_on:
|
depends_on:
|
||||||
- nginx
|
- nginx
|
||||||
- openvpn
|
- openvpn
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx
|
image: nginx
|
||||||
hostname: nginx
|
|
||||||
|
openvpn:
|
||||||
|
image: openvpn
|
||||||
|
```
|
||||||
|
|
||||||
|
Transparent mode 1: using sslh container for networking
|
||||||
|
|
||||||
|
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
|
||||||
|
```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:
|
openvpn:
|
||||||
image: openvpn:latest
|
image: openvpn:latest
|
||||||
hostname: openvpn
|
.....
|
||||||
|
ports:
|
||||||
|
- 1194:1194 # bind to docker host on port 1194
|
||||||
```
|
```
|
||||||
|
|
||||||
Comments? Questions?
|
Comments? Questions?
|
||||||
|
26
argtable3.c
26
argtable3.c
@ -2876,9 +2876,9 @@ static void arg_file_resetfn(struct arg_file* parent) {
|
|||||||
static const char* arg_basename(const char* filename) {
|
static const char* arg_basename(const char* filename) {
|
||||||
const char *result = NULL, *result1, *result2;
|
const char *result = NULL, *result1, *result2;
|
||||||
|
|
||||||
/* Find the last occurrence of eother file separator character. */
|
/* Find the last occurrence of other file separator character. */
|
||||||
/* Two alternative file separator chars are supported as legal */
|
/* Two alternative file separator chars are supported as legal */
|
||||||
/* file separators but not both together in the same filename. */
|
/* file separators but not both together in the same filename. */
|
||||||
result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL);
|
result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL);
|
||||||
result2 = (filename ? strrchr(filename, FILESEPARATOR2) : 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) {
|
} else if (!argval) {
|
||||||
/* a valid argument with no argument value was given. */
|
/* a valid argument with no argument value was given. */
|
||||||
/* This happens when an optional argument value was invoked. */
|
/* 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++;
|
parent->count++;
|
||||||
} else {
|
} else {
|
||||||
parent->filename[parent->count] = argval;
|
parent->filename[parent->count] = argval;
|
||||||
@ -3173,7 +3173,7 @@ static int arg_int_scanfn(struct arg_int* parent, const char* argval) {
|
|||||||
} else if (!argval) {
|
} else if (!argval) {
|
||||||
/* a valid argument with no argument value was given. */
|
/* a valid argument with no argument value was given. */
|
||||||
/* This happens when an optional argument value was invoked. */
|
/* 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++;
|
parent->count++;
|
||||||
} else {
|
} else {
|
||||||
long int val;
|
long int val;
|
||||||
@ -3813,8 +3813,8 @@ static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_O
|
|||||||
#endif
|
#endif
|
||||||
#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */
|
#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */
|
||||||
#define OP_OR (MAX_CHAR + 2)
|
#define OP_OR (MAX_CHAR + 2)
|
||||||
#define OP_EXPR (MAX_CHAR + 3) /* parentesis () */
|
#define OP_EXPR (MAX_CHAR + 3) /* parenthesis () */
|
||||||
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */
|
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parenthesis (?:) */
|
||||||
#define OP_DOT (MAX_CHAR + 5)
|
#define OP_DOT (MAX_CHAR + 5)
|
||||||
#define OP_CLASS (MAX_CHAR + 6)
|
#define OP_CLASS (MAX_CHAR + 6)
|
||||||
#define OP_CCLASS (MAX_CHAR + 7)
|
#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) {
|
if (errorlast) {
|
||||||
arg_register_error(endtable, parentlast, errorlast, optarglast);
|
arg_register_error(endtable, parentlast, errorlast, optarglast);
|
||||||
optind++;
|
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
|
Fill in the local copy of argv[]. We need a local copy
|
||||||
because getopt rearranges argv[] which adversely affects
|
because getopt rearranges argv[] which adversely affects
|
||||||
susbsequent parsing attempts.
|
subsequent parsing attempts.
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < argc; i++)
|
for (i = 0; i < argc; i++)
|
||||||
argvcopy[i] = argv[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) {
|
if (shortopts) {
|
||||||
char option[3];
|
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. */
|
/* a deficiency in the watcom compiler wrt static array initializers. */
|
||||||
option[0] = '-';
|
option[0] = '-';
|
||||||
option[1] = shortopts[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" */
|
/* "-a|-b|-c" */
|
||||||
char shortopt[3];
|
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. */
|
/* a deficiency in the watcom compiler wrt static array initializers. */
|
||||||
shortopt[0] = '-';
|
shortopt[0] = '-';
|
||||||
shortopt[1] = *c;
|
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.
|
* Prints the glossary in strict GNU format.
|
||||||
* Differences to arg_print_glossary() are:
|
* Differences to arg_print_glossary() are:
|
||||||
* - wraps lines after 80 chars
|
* - wraps lines after 80 chars
|
||||||
* - indents lines without shortops
|
* - indents lines without shortopts
|
||||||
* - does not accept formatstrings
|
* - does not accept formatstrings
|
||||||
*
|
*
|
||||||
* Contributed by Uli Fouquet
|
* 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
|
* that entry were still allocated ok. Those subsequent allocations will not be
|
||||||
* deallocated by arg_free().
|
* deallocated by arg_free().
|
||||||
* Despite the unlikeliness of the problem occurring, and the even unlikelier event
|
* 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.
|
* with the newer arg_freetable() function.
|
||||||
* We still keep arg_free() for backwards compatibility.
|
* We still keep arg_free() for backwards compatibility.
|
||||||
*/
|
*/
|
||||||
|
@ -87,7 +87,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
|
|||||||
* that particular arg_xxx arguments, performing post-parse checks, and
|
* that particular arg_xxx arguments, performing post-parse checks, and
|
||||||
* reporting errors.
|
* reporting errors.
|
||||||
* These functions are private to the individual arg_xxx source code
|
* 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
|
* constructor function. The user could alter them after construction
|
||||||
* if desired, but the original intention is for them to be set by the
|
* if desired, but the original intention is for them to be set by the
|
||||||
* constructor and left unaltered.
|
* constructor and left unaltered.
|
||||||
@ -95,7 +95,7 @@ typedef int(arg_comparefn)(const void* k1, const void* k2);
|
|||||||
typedef struct arg_hdr {
|
typedef struct arg_hdr {
|
||||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||||
const char* shortopts; /* String defining the short options */
|
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* datatype; /* Description of the argument data type */
|
||||||
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
|
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
|
||||||
int mincount; /* Minimum number of occurences of this option accepted */
|
int mincount; /* Minimum number of occurences of this option accepted */
|
||||||
|
16
basic.cfg
16
basic.cfg
@ -1,23 +1,25 @@
|
|||||||
# This is a basic configuration file that should provide
|
# This is a basic configuration file that should provide
|
||||||
# sensible values for "standard" setup.
|
# sensible values for "standard" setup.
|
||||||
|
|
||||||
verbose: 0;
|
# You will find extensive examples with explanations in
|
||||||
foreground: false;
|
# example.cfg
|
||||||
inetd: false;
|
|
||||||
numeric: false;
|
|
||||||
transparent: false;
|
|
||||||
timeout: 2;
|
timeout: 2;
|
||||||
user: "nobody";
|
user: "nobody";
|
||||||
pidfile: "/var/run/sslh.pid";
|
pidfile: "/var/run/sslh.pid";
|
||||||
chroot: "/var/empty";
|
|
||||||
|
|
||||||
|
|
||||||
# Change hostname with your external address name.
|
# Change hostname with your external address name, or the IP
|
||||||
|
# of the interface that receives connections
|
||||||
listen:
|
listen:
|
||||||
(
|
(
|
||||||
{ host: "thelonious"; port: "443"; }
|
{ host: "thelonious"; port: "443"; }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
# Change to the protocols you want to forward to. The
|
||||||
|
# defaults here are sensible for services running on
|
||||||
|
# localhost
|
||||||
protocols:
|
protocols:
|
||||||
(
|
(
|
||||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||||
|
@ -30,8 +30,9 @@ struct cnx_collection {
|
|||||||
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Allocates and initialises a new collection of connections. */
|
/* Allocates and initialises a new collection of connections with at least
|
||||||
cnx_collection* collection_init(void)
|
* `len` elements. */
|
||||||
|
cnx_collection* collection_init(int len)
|
||||||
{
|
{
|
||||||
cnx_collection* collection;
|
cnx_collection* collection;
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ cnx_collection* collection_init(void)
|
|||||||
|
|
||||||
memset(collection, 0, sizeof(*collection));
|
memset(collection, 0, sizeof(*collection));
|
||||||
|
|
||||||
collection->fd2cnx = gap_init();
|
collection->fd2cnx = gap_init(len);
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
typedef struct cnx_collection cnx_collection;
|
typedef struct cnx_collection cnx_collection;
|
||||||
|
|
||||||
|
|
||||||
cnx_collection* collection_init(void);
|
cnx_collection* collection_init(int len);
|
||||||
void collection_destroy(cnx_collection* collection);
|
void collection_destroy(cnx_collection* collection);
|
||||||
|
|
||||||
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
||||||
|
169
common.c
169
common.c
@ -4,7 +4,6 @@
|
|||||||
* No code here should assume whether sockets are blocking or not.
|
* No code here should assume whether sockets are blocking or not.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
#define SYSLOG_NAMES
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
@ -16,6 +15,7 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
|
#include "log.h"
|
||||||
#include "sslh-conf.h"
|
#include "sslh-conf.h"
|
||||||
|
|
||||||
/* Added to make the code compilable under CYGWIN
|
/* Added to make the code compilable under CYGWIN
|
||||||
@ -42,8 +42,6 @@ struct sslhcfg_item cfg;
|
|||||||
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
|
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
|
||||||
|
|
||||||
|
|
||||||
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
|
|
||||||
|
|
||||||
#ifdef LIBWRAP
|
#ifdef LIBWRAP
|
||||||
#include <tcpd.h>
|
#include <tcpd.h>
|
||||||
int allow_severity =0, deny_severity = 0;
|
int allow_severity =0, deny_severity = 0;
|
||||||
@ -60,7 +58,7 @@ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall
|
|||||||
char buf[NI_MAXHOST];
|
char buf[NI_MAXHOST];
|
||||||
|
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
fprintf(stderr, "%s:%s: %s\n",
|
print_message(msg_system_error, "%s:%s: %s\n",
|
||||||
sprintaddr(buf, sizeof(buf), addr),
|
sprintaddr(buf, sizeof(buf), addr),
|
||||||
syscall,
|
syscall,
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
@ -77,7 +75,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
|
|||||||
#ifdef SYSTEMD
|
#ifdef SYSTEMD
|
||||||
sd = sd_listen_fds(0);
|
sd = sd_listen_fds(0);
|
||||||
if (sd < 0) {
|
if (sd < 0) {
|
||||||
fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd));
|
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (sd > 0) {
|
if (sd > 0) {
|
||||||
@ -177,7 +175,7 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
|||||||
|
|
||||||
*sockfd = NULL;
|
*sockfd = NULL;
|
||||||
|
|
||||||
if (cfg.verbose) fprintf(stderr, "Listening to:\n");
|
print_message(msg_config, "Listening to:\n");
|
||||||
|
|
||||||
for (i = 0; i < cfg.listen_len; i++) {
|
for (i = 0; i < cfg.listen_len; i++) {
|
||||||
keepalive = cfg.listen[i].keepalive;
|
keepalive = cfg.listen[i].keepalive;
|
||||||
@ -188,13 +186,12 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
|||||||
|
|
||||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||||
num_addr++;
|
num_addr++;
|
||||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd));
|
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||||
if (cfg.verbose)
|
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
||||||
fprintf(stderr, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
cfg.listen[i].keepalive ? "keepalive" : "",
|
||||||
cfg.listen[i].keepalive ? "keepalive" : "",
|
cfg.listen[i].is_udp ? "udp" : "");
|
||||||
cfg.listen[i].is_udp ? "udp" : "");
|
|
||||||
}
|
}
|
||||||
freeaddrinfo(start_addr);
|
freeaddrinfo(start_addr);
|
||||||
}
|
}
|
||||||
@ -322,19 +319,23 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
|||||||
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
|
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
|
||||||
CHECK_RES_RETURN(res, "getpeername", res);
|
CHECK_RES_RETURN(res, "getpeername", res);
|
||||||
|
|
||||||
|
if (cnx->proto->resolve_on_forward) {
|
||||||
|
resolve_split_name(&(cnx->proto->saddr), cnx->proto->host,
|
||||||
|
cnx->proto->port);
|
||||||
|
}
|
||||||
|
|
||||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||||
/* When transparent, make sure both connections use the same address family */
|
/* When transparent, make sure both connections use the same address family */
|
||||||
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
||||||
continue;
|
continue;
|
||||||
if (cfg.verbose)
|
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||||
fprintf(stderr, "connecting to %s family %d len %d\n",
|
|
||||||
sprintaddr(buf, sizeof(buf), a),
|
sprintaddr(buf, sizeof(buf), a),
|
||||||
a->ai_addr->sa_family, a->ai_addrlen);
|
a->ai_addr->sa_family, a->ai_addrlen);
|
||||||
|
|
||||||
/* XXX Needs to match ai_family from fd_from when being transparent! */
|
/* XXX Needs to match ai_family from fd_from when being transparent! */
|
||||||
fd = socket(a->ai_family, SOCK_STREAM, 0);
|
fd = socket(a->ai_family, SOCK_STREAM, 0);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
|
print_message(msg_connections_error, "forward to %s failed:socket: %s\n",
|
||||||
cnx->proto->name, strerror(errno));
|
cnx->proto->name, strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
one = 1;
|
one = 1;
|
||||||
@ -347,6 +348,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
|||||||
|
|
||||||
if (transparent) {
|
if (transparent) {
|
||||||
res = bind_peer(fd, fd_from);
|
res = bind_peer(fd, fd_from);
|
||||||
|
if (res == -1) close(fd);
|
||||||
CHECK_RES_RETURN(res, "bind_peer", res);
|
CHECK_RES_RETURN(res, "bind_peer", res);
|
||||||
}
|
}
|
||||||
res = connect(fd, a->ai_addr, a->ai_addrlen);
|
res = connect(fd, a->ai_addr, a->ai_addrlen);
|
||||||
@ -354,7 +356,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
|||||||
/* EINPROGRESS indicates it might take time. If it eventually
|
/* EINPROGRESS indicates it might take time. If it eventually
|
||||||
* fails, it'll be caught as a failed read */
|
* fails, it'll be caught as a failed read */
|
||||||
if ((res == -1) && (errno != EINPROGRESS)) {
|
if ((res == -1) && (errno != EINPROGRESS)) {
|
||||||
log_message(LOG_ERR, "forward to %s failed:connect: %s\n",
|
print_message(msg_connections_error, "forward to %s failed:connect: %s\n",
|
||||||
cnx->proto->name, strerror(errno));
|
cnx->proto->name, strerror(errno));
|
||||||
close(fd);
|
close(fd);
|
||||||
continue; /* Try the next address */
|
continue; /* Try the next address */
|
||||||
@ -374,9 +376,8 @@ int defer_write(struct queue *q, void* data, int data_size)
|
|||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
|
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
|
||||||
if (cfg.verbose)
|
|
||||||
fprintf(stderr, "**** writing deferred on fd %d\n", q->fd);
|
|
||||||
|
|
||||||
|
print_message(msg_fd, "writing deferred on fd %d\n", q->fd);
|
||||||
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
|
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
|
||||||
CHECK_ALLOC(p, "realloc");
|
CHECK_ALLOC(p, "realloc");
|
||||||
|
|
||||||
@ -397,8 +398,7 @@ int flush_deferred(struct queue *q)
|
|||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if (cfg.verbose)
|
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
|
||||||
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
|
|
||||||
|
|
||||||
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
||||||
if (n == -1)
|
if (n == -1)
|
||||||
@ -430,11 +430,12 @@ void init_cnx(struct connection *cnx)
|
|||||||
|
|
||||||
void dump_connection(struct connection *cnx)
|
void dump_connection(struct connection *cnx)
|
||||||
{
|
{
|
||||||
printf("state: %d\n", cnx->state);
|
print_message(msg_int_error, "type: %s\n", cnx->type == SOCK_DGRAM ? "UDP" : "TCP");
|
||||||
printf("0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
print_message(msg_int_error, "state: %d\n", cnx->state);
|
||||||
hexdump(cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
|
print_message(msg_int_error, "0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||||
printf("1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
hexdump(msg_int_error, cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
|
||||||
hexdump(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
|
print_message(msg_int_error, "1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
||||||
|
hexdump(msg_int_error, cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -459,8 +460,6 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
|||||||
if (size_r == -1) {
|
if (size_r == -1) {
|
||||||
switch (errno) {
|
switch (errno) {
|
||||||
case EAGAIN:
|
case EAGAIN:
|
||||||
if (cfg.verbose)
|
|
||||||
fprintf(stderr, "reading 0 from %d\n", from);
|
|
||||||
return FD_NODATA;
|
return FD_NODATA;
|
||||||
|
|
||||||
case ECONNRESET:
|
case ECONNRESET:
|
||||||
@ -511,7 +510,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
|||||||
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
|
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
|
print_message(msg_system_error, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
|
||||||
/* Name resolution failed: do it numerically instead */
|
/* Name resolution failed: do it numerically instead */
|
||||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||||
host, sizeof(host),
|
host, sizeof(host),
|
||||||
@ -519,7 +518,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
|||||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||||
/* should not fail but... */
|
/* should not fail but... */
|
||||||
if (res) {
|
if (res) {
|
||||||
log_message(LOG_ERR, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
|
print_message(msg_system_error, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
|
||||||
strcpy(host, "?");
|
strcpy(host, "?");
|
||||||
strcpy(serv, "?");
|
strcpy(serv, "?");
|
||||||
}
|
}
|
||||||
@ -548,7 +547,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
|||||||
if (host[0] == '[') {
|
if (host[0] == '[') {
|
||||||
end = strrchr(host, ']');
|
end = strrchr(host, ']');
|
||||||
if (!end) {
|
if (!end) {
|
||||||
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
|
print_message(msg_config_error, "%s: no closing bracket in IPv6 address?\n", host);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
host++; /* skip first bracket */
|
host++; /* skip first bracket */
|
||||||
@ -557,7 +556,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
|||||||
|
|
||||||
res = getaddrinfo(host, serv, &hint, out);
|
res = getaddrinfo(host, serv, &hint, out);
|
||||||
if (res)
|
if (res)
|
||||||
log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
print_message(msg_system_error, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,7 +572,7 @@ void resolve_name(struct addrinfo **out, char* fullname)
|
|||||||
/* Find port */
|
/* Find port */
|
||||||
char *sep = strrchr(fullname, ':');
|
char *sep = strrchr(fullname, ':');
|
||||||
if (!sep) { /* No separator: parameter is just a port */
|
if (!sep) { /* No separator: parameter is just a port */
|
||||||
fprintf(stderr, "%s: names must be fully specified as hostname:port\n", fullname);
|
print_message(msg_config_error, "%s: names must be fully specified as hostname:port\n", fullname);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
serv = sep+1;
|
serv = sep+1;
|
||||||
@ -583,30 +582,13 @@ void resolve_name(struct addrinfo **out, char* fullname)
|
|||||||
|
|
||||||
res = resolve_split_name(out, host, serv);
|
res = resolve_split_name(out, host, serv);
|
||||||
if (res) {
|
if (res) {
|
||||||
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);
|
print_message(msg_config_error, "%s `%s'\n", gai_strerror(res), fullname);
|
||||||
if (res == EAI_SERVICE)
|
if (res == EAI_SERVICE)
|
||||||
fprintf(stderr, "(Check you have specified all ports)\n");
|
print_message(msg_config_error, "(Check you have specified all ports)\n");
|
||||||
exit(4);
|
exit(4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Log to syslog or stderr if foreground */
|
|
||||||
void log_message(int type, const char* msg, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, msg);
|
|
||||||
if (cfg.foreground)
|
|
||||||
vfprintf(stderr, msg, ap);
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
if (do_syslog) {
|
|
||||||
va_start(ap, msg);
|
|
||||||
vsyslog(type, msg, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Fills a connection description; returns 0 on failure */
|
/* Fills a connection description; returns 0 on failure */
|
||||||
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx)
|
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx)
|
||||||
@ -641,30 +623,6 @@ int get_connection_desc(struct connection_desc* desc, const struct connection *c
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* syslogs who connected to where
|
|
||||||
* desc: string description of the connection. if NULL, log_connection will
|
|
||||||
* manage on its own
|
|
||||||
* cnx: connection descriptor
|
|
||||||
* */
|
|
||||||
void log_connection(struct connection_desc* desc, const struct connection *cnx)
|
|
||||||
{
|
|
||||||
struct connection_desc d;
|
|
||||||
|
|
||||||
if (cnx->proto->log_level < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!desc) {
|
|
||||||
desc = &d;
|
|
||||||
get_connection_desc(desc, cnx);
|
|
||||||
}
|
|
||||||
|
|
||||||
log_message(LOG_INFO, "%s:connection from %s to %s forwarded from %s to %s\n",
|
|
||||||
cnx->proto->name,
|
|
||||||
desc->peer,
|
|
||||||
desc->service,
|
|
||||||
desc->local,
|
|
||||||
desc->target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
|
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
|
||||||
{
|
{
|
||||||
@ -708,8 +666,7 @@ int check_access_rights(int in_socket, const char* service)
|
|||||||
/* extract peer address */
|
/* extract peer address */
|
||||||
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
|
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
|
||||||
if (res) {
|
if (res) {
|
||||||
if (cfg.verbose)
|
print_message(msg_system_error, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
||||||
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
|
||||||
strcpy(addr_str, STRING_UNKNOWN);
|
strcpy(addr_str, STRING_UNKNOWN);
|
||||||
}
|
}
|
||||||
/* extract peer name */
|
/* extract peer name */
|
||||||
@ -717,15 +674,12 @@ int check_access_rights(int in_socket, const char* service)
|
|||||||
if (!cfg.numeric) {
|
if (!cfg.numeric) {
|
||||||
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
|
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
|
||||||
if (res) {
|
if (res) {
|
||||||
if (cfg.verbose)
|
print_message(msg_system_error, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
||||||
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
|
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
|
||||||
if (cfg.verbose)
|
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
|
||||||
fprintf(stderr, "access denied\n");
|
|
||||||
log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str);
|
|
||||||
close(in_socket);
|
close(in_socket);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -760,35 +714,6 @@ void setup_signals(void)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open syslog connection with appropriate banner;
|
|
||||||
* banner is made up of basename(bin_name)+"[pid]" */
|
|
||||||
void setup_syslog(const char* bin_name) {
|
|
||||||
char *name1, *name2;
|
|
||||||
int res, fn;
|
|
||||||
|
|
||||||
if (!strcmp(cfg.syslog_facility, "none")) {
|
|
||||||
do_syslog = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name1 = strdup(bin_name);
|
|
||||||
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
|
|
||||||
CHECK_RES_DIE(res, "asprintf");
|
|
||||||
|
|
||||||
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
|
|
||||||
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
|
|
||||||
break;
|
|
||||||
if (facilitynames[fn].c_val == -1) {
|
|
||||||
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
|
|
||||||
free(name1);
|
|
||||||
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
|
|
||||||
|
|
||||||
log_message(LOG_INFO, "%s %s started\n", server_type, VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ask OS to keep capabilities over a setuid(nonzero) */
|
/* Ask OS to keep capabilities over a setuid(nonzero) */
|
||||||
void set_keepcaps(int val) {
|
void set_keepcaps(int val) {
|
||||||
@ -866,16 +791,14 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
|||||||
if (user_name) {
|
if (user_name) {
|
||||||
pw = getpwnam(user_name);
|
pw = getpwnam(user_name);
|
||||||
if (!pw) {
|
if (!pw) {
|
||||||
fprintf(stderr, "%s: not found\n", user_name);
|
print_message(msg_config_error, "%s: not found\n", user_name);
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
if (cfg.verbose)
|
print_message(msg_config, "turning into %s\n", user_name);
|
||||||
fprintf(stderr, "turning into %s\n", user_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chroot_path) {
|
if (chroot_path) {
|
||||||
if (cfg.verbose)
|
print_message(msg_config, "chrooting into %s\n", chroot_path);
|
||||||
fprintf(stderr, "chrooting into %s\n", chroot_path);
|
|
||||||
|
|
||||||
res = chroot(chroot_path);
|
res = chroot(chroot_path);
|
||||||
CHECK_RES_DIE(res, "chroot");
|
CHECK_RES_DIE(res, "chroot");
|
||||||
@ -905,14 +828,24 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
|||||||
void write_pid_file(const char* pidfile)
|
void write_pid_file(const char* pidfile)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
|
int res;
|
||||||
|
|
||||||
f = fopen(pidfile, "w");
|
f = fopen(pidfile, "w");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
perror(pidfile);
|
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
|
||||||
exit(3);
|
exit(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(f, "%d\n", getpid());
|
res = fprintf(f, "%d\n", getpid());
|
||||||
fclose(f);
|
if (res < 0) {
|
||||||
|
print_message(msg_system_error, "write_pid_file:fprintf:%s", strerror(errno));
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = fclose(f);
|
||||||
|
if (res == EOF) {
|
||||||
|
print_message(msg_system_error, "write_pid_file:fclose:%s", strerror(errno));
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
common.h
22
common.h
@ -40,20 +40,20 @@
|
|||||||
|
|
||||||
#define CHECK_RES_DIE(res, str) \
|
#define CHECK_RES_DIE(res, str) \
|
||||||
if (res == -1) { \
|
if (res == -1) { \
|
||||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||||
perror(str); \
|
perror(str); \
|
||||||
exit(1); \
|
exit(1); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_RES_RETURN(res, str, ret) \
|
#define CHECK_RES_RETURN(res, str, ret) \
|
||||||
if (res == -1) { \
|
if (res == -1) { \
|
||||||
log_message(LOG_CRIT, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
|
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
|
||||||
return ret; \
|
return ret; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_ALLOC(a, str) \
|
#define CHECK_ALLOC(a, str) \
|
||||||
if (!a) { \
|
if (!a) { \
|
||||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||||
perror(str); \
|
perror(str); \
|
||||||
exit(1); \
|
exit(1); \
|
||||||
}
|
}
|
||||||
@ -83,9 +83,6 @@ enum connection_state {
|
|||||||
ST_SHOVELING /* Connexion is established */
|
ST_SHOVELING /* Connexion is established */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* this is used to pass protocols through the command-line parameter parsing */
|
|
||||||
#define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */
|
|
||||||
|
|
||||||
/* A 'queue' is composed of a file descriptor (which can be read from or
|
/* A 'queue' is composed of a file descriptor (which can be read from or
|
||||||
* written to), and a queue for deferred write data */
|
* written to), and a queue for deferred write data */
|
||||||
struct queue {
|
struct queue {
|
||||||
@ -95,6 +92,12 @@ struct queue {
|
|||||||
int deferred_data_size;
|
int deferred_data_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Double linked list for timeout management */
|
||||||
|
typedef struct {
|
||||||
|
struct connection* head;
|
||||||
|
struct connection* tail;
|
||||||
|
} dl_list;
|
||||||
|
|
||||||
struct connection {
|
struct connection {
|
||||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||||
struct sslhcfg_protocols_item* proto; /* Where to connect to */
|
struct sslhcfg_protocols_item* proto; /* Where to connect to */
|
||||||
@ -109,13 +112,16 @@ struct connection {
|
|||||||
struct queue q[2];
|
struct queue q[2];
|
||||||
|
|
||||||
/* SOCK_DGRAM */
|
/* SOCK_DGRAM */
|
||||||
struct sockaddr client_addr; /* Contains the remote client address */
|
struct sockaddr_storage client_addr; /* Contains the remote client address */
|
||||||
socklen_t addrlen;
|
socklen_t addrlen;
|
||||||
|
|
||||||
int local_endpoint; /* Contains the local address */
|
int local_endpoint; /* Contains the local address */
|
||||||
|
|
||||||
time_t last_active;
|
time_t last_active;
|
||||||
|
|
||||||
|
/* double linked list of timeouts */
|
||||||
|
struct connection *timeout_prev, *timeout_next;
|
||||||
|
|
||||||
/* We need one local socket for each target server, so we know where to
|
/* We need one local socket for each target server, so we know where to
|
||||||
* forward server responses */
|
* forward server responses */
|
||||||
int target_sock;
|
int target_sock;
|
||||||
@ -160,7 +166,6 @@ void setup_syslog(const char* bin_name);
|
|||||||
void drop_privileges(const char* user_name, const char* chroot_path);
|
void drop_privileges(const char* user_name, const char* chroot_path);
|
||||||
void set_capabilities(int cap_net_admin);
|
void set_capabilities(int cap_net_admin);
|
||||||
void write_pid_file(const char* pidfile);
|
void write_pid_file(const char* pidfile);
|
||||||
void log_message(int type, const char* msg, ...);
|
|
||||||
void dump_connection(struct connection *cnx);
|
void dump_connection(struct connection *cnx);
|
||||||
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||||
|
|
||||||
@ -171,7 +176,6 @@ int flush_deferred(struct queue *q);
|
|||||||
|
|
||||||
extern struct sslhcfg_item cfg;
|
extern struct sslhcfg_item cfg;
|
||||||
extern struct addrinfo *addr_listen;
|
extern struct addrinfo *addr_listen;
|
||||||
extern const char* USAGE_STRING;
|
|
||||||
extern const char* server_type;
|
extern const char* server_type;
|
||||||
|
|
||||||
/* sslh-fork.c */
|
/* sslh-fork.c */
|
||||||
|
97
container-entrypoint.sh
Executable file
97
container-entrypoint.sh
Executable file
@ -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
|
@ -7,7 +7,7 @@ doesn't work, report how what was suggested here went.
|
|||||||
It's also worth reading [how to ask
|
It's also worth reading [how to ask
|
||||||
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
|
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
|
||||||
before posting on the mailing list or opening an issue in
|
before posting on the mailing list or opening an issue in
|
||||||
Github.
|
GitHub.
|
||||||
|
|
||||||
Getting more info
|
Getting more info
|
||||||
=================
|
=================
|
||||||
|
@ -7,7 +7,7 @@ Dependencies
|
|||||||
`sslh` uses:
|
`sslh` uses:
|
||||||
|
|
||||||
* [libconfig](http://www.hyperrealm.com/libconfig/). For
|
* [libconfig](http://www.hyperrealm.com/libconfig/). For
|
||||||
Debian this is contained in package `libconfig8-dev`. You
|
Debian this is contained in package `libconfig-dev`. You
|
||||||
can compile with or without it using USELIBCONFIG in the
|
can compile with or without it using USELIBCONFIG in the
|
||||||
Makefile.
|
Makefile.
|
||||||
|
|
||||||
@ -29,6 +29,13 @@ Makefile.
|
|||||||
which requires `libbsd` at runtime, and `libbsd-dev` at
|
which requires `libbsd` at runtime, and `libbsd-dev` at
|
||||||
compile-time.
|
compile-time.
|
||||||
|
|
||||||
|
* libpcre2, in package `libpcre-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.
|
||||||
|
|
||||||
|
|
||||||
For OpenSUSE, these are contained in packages libconfig9 and
|
For OpenSUSE, these are contained in packages libconfig9 and
|
||||||
libconfig-dev in repository
|
libconfig-dev in repository
|
||||||
@ -44,6 +51,10 @@ distclean` for example), you will also need to add
|
|||||||
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||||
(v1.5) to your path.
|
(v1.5) to your path.
|
||||||
|
|
||||||
|
|
||||||
|
The test scripts are written in Perl, and will require
|
||||||
|
IO::Socket::INET6 (libio-socket-inet6-perl in Debian).
|
||||||
|
|
||||||
Compilation
|
Compilation
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@ -68,11 +79,25 @@ of the Makefile:
|
|||||||
* `USELIBBSD` compiles support for updating the process name (as shown
|
* `USELIBBSD` compiles support for updating the process name (as shown
|
||||||
by `ps`).
|
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`.
|
||||||
|
|
||||||
Binaries
|
Binaries
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The Makefile produces two different executables: `sslh-fork`
|
The Makefile produces three different executables: `sslh-fork`,
|
||||||
and `sslh-select`:
|
`sslh-select` and `sslh-ev`:
|
||||||
|
|
||||||
* `sslh-fork` forks a new process for each incoming connection.
|
* `sslh-fork` forks a new process for each incoming connection.
|
||||||
It is well-tested and very reliable, but incurs the overhead
|
It is well-tested and very reliable, but incurs the overhead
|
||||||
@ -81,18 +106,17 @@ If you are going to use `sslh` for a "small" setup (less than
|
|||||||
a dozen ssh connections and a low-traffic https server) then
|
a dozen ssh connections and a low-traffic https server) then
|
||||||
`sslh-fork` is probably more suited for you.
|
`sslh-fork` is probably more suited for you.
|
||||||
|
|
||||||
* `sslh-select` uses only one thread, which monitors all connections
|
* `sslh-select` uses only one thread, which monitors all
|
||||||
at once. It is more recent and less tested, but only incurs a 16
|
connections at once. It only incurs a 16 byte overhead per
|
||||||
byte overhead per connection. Also, if it stops, you'll lose all
|
connection. Also, if it stops, you'll lose all connections,
|
||||||
connections, which means you can't upgrade it remotely.
|
which means you can't upgrade it remotely. If you are going
|
||||||
If you are going to use `sslh` on a "medium" setup (a few thousand ssh
|
to use `sslh` on a "medium" setup (a few hundreds of
|
||||||
connections, and another few thousand ssl connections),
|
connections), or if you are on a system where forking is
|
||||||
`sslh-select` will be better.
|
expensive (e.g. Windows), `sslh-select` will be better.
|
||||||
|
|
||||||
If you have a very large site (tens of thousands of connections),
|
|
||||||
you'll need a vapourware version that would use libevent or
|
|
||||||
something like that.
|
|
||||||
|
|
||||||
|
* `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
|
Installation
|
||||||
------------
|
------------
|
||||||
|
@ -33,7 +33,7 @@ with launchctl or simply reboot.
|
|||||||
<string>0.0.0.0:443</string>
|
<string>0.0.0.0:443</string>
|
||||||
<string>--ssh</string>
|
<string>--ssh</string>
|
||||||
<string>localhost:22</string>
|
<string>localhost:22</string>
|
||||||
<string>--ssl</string>
|
<string>--tls</string>
|
||||||
<string>localhost:443</string>
|
<string>localhost:443</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QueueDirectories</key>
|
<key>QueueDirectories</key>
|
||||||
|
172
doc/config.md
172
doc/config.md
@ -65,13 +65,13 @@ Configuration goes like this on the server side, using `stunnel3`:
|
|||||||
* `-f` for foreground/debugging
|
* `-f` for foreground/debugging
|
||||||
* `-p` for specifying the key and certificate
|
* `-p` for specifying the key and certificate
|
||||||
* `-d` for specifying which interface and port
|
* `-d` for specifying which interface and port
|
||||||
we're listening to for incoming connexions
|
we're listening to for incoming connections
|
||||||
* `-l` summons `sslh` in inetd mode.
|
* `-l` summons `sslh` in inetd mode.
|
||||||
|
|
||||||
* sslh options:
|
* sslh options:
|
||||||
* `-i` for inetd mode
|
* `-i` for inetd mode
|
||||||
* `--http` to forward HTTP connexions to port 80,
|
* `--http` to forward HTTP connections to port 80,
|
||||||
and SSH connexions to port 22.
|
and SSH connections to port 22.
|
||||||
|
|
||||||
Capabilities support
|
Capabilities support
|
||||||
--------------------
|
--------------------
|
||||||
@ -92,165 +92,19 @@ to the executable:
|
|||||||
|
|
||||||
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
|
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
|
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
|
||||||
|
|
||||||
Transparent proxy support
|
Transparent proxy support
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
On Linux and FreeBSD you can use the `--transparent` option to
|
Transparent proxying allows the target server to see the
|
||||||
request transparent proxying. This means services behind `sslh`
|
original client IP address, i.e. `sslh` becomes invisible.
|
||||||
(Apache, `sshd` and so on) will see the external IP and ports
|
This makes it easier to use the server's logs, and potential
|
||||||
as if the external world connected directly to them. This
|
IP-based banning ability.
|
||||||
simplifies IP-based access control (or makes it possible at
|
|
||||||
all).
|
|
||||||
|
|
||||||
You can refer to Sean Warn'ѕ [tutorial](tproxy.md) for a
|
Set up can get complicated, so it has its own [document](tproxy.md).
|
||||||
different set-up which enables transparent proxying between
|
|
||||||
two different machines. The following may only work if
|
|
||||||
`sslh` and the final servers are on the same machine.
|
|
||||||
|
|
||||||
Note that getting this to work 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
|
|
||||||
required network configuration somewhat. If it doesn't work,
|
|
||||||
it's almost certain that the problem is not linked to `sslh`
|
|
||||||
but to the network setup that surrounds it.
|
|
||||||
|
|
||||||
Linux:
|
|
||||||
|
|
||||||
`sslh` needs extended rights to perform this: you'll need to
|
|
||||||
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
|
|
||||||
or run it as root (but don't do that).
|
|
||||||
|
|
||||||
The firewalling tables also need to be adjusted as follows.
|
|
||||||
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
|
|
||||||
this scheme -- let me know if you manage that:
|
|
||||||
|
|
||||||
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
|
|
||||||
sysctl -w net.ipv4.conf.default.route_localnet=1
|
|
||||||
sysctl -w net.ipv4.conf.all.route_localnet=1
|
|
||||||
|
|
||||||
# DROP martian packets as they would have been if route_localnet was zero
|
|
||||||
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
|
|
||||||
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
|
|
||||||
|
|
||||||
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
|
|
||||||
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
|
|
||||||
|
|
||||||
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
|
|
||||||
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
|
||||||
|
|
||||||
# Configure routing for those marked packets
|
|
||||||
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:
|
|
||||||
|
|
||||||
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
|
|
||||||
# Not sure if this is needed for ipv6 though
|
|
||||||
sysctl -w net.ipv4.conf.default.route_localnet=1
|
|
||||||
sysctl -w net.ipv4.conf.all.route_localnet=1
|
|
||||||
|
|
||||||
# DROP martian packets as they would have been if route_localnet was zero
|
|
||||||
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
|
|
||||||
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
|
|
||||||
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
|
|
||||||
|
|
||||||
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
|
|
||||||
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
|
|
||||||
|
|
||||||
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
|
|
||||||
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
|
||||||
|
|
||||||
# Configure routing for those marked packets
|
|
||||||
ip -6 rule add fwmark 0x1 lookup 100
|
|
||||||
ip -6 route add local ::/0 dev lo table 100
|
|
||||||
|
|
||||||
Explanation:
|
|
||||||
To be able to use `localhost` as destination in your sslh config along with transparent proxying
|
|
||||||
you have to allow routing of loopback addresses as done above.
|
|
||||||
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
|
|
||||||
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
|
|
||||||
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
|
|
||||||
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
|
|
||||||
showing how packets will traverse the iptables chains.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
|
|
||||||
These rules will allow you to connect directly to ssh on port
|
|
||||||
22 (or to any other service behind sslh) as well as through sslh on port 443.
|
|
||||||
|
|
||||||
Also remember that iptables configuration and ip routes and
|
|
||||||
rules won't be necessarily persisted after you reboot. Make
|
|
||||||
sure to save them properly. For example in CentOS7, you would
|
|
||||||
do `iptables-save > /etc/sysconfig/iptables`, and add both
|
|
||||||
`ip` commands to your `/etc/rc.local`.
|
|
||||||
|
|
||||||
FreeBSD:
|
|
||||||
|
|
||||||
Given you have no firewall defined yet, you can use the following configuration
|
|
||||||
to have ipfw properly redirect traffic back to sslh
|
|
||||||
|
|
||||||
/etc/rc.conf
|
|
||||||
firewall_enable="YES"
|
|
||||||
firewall_type="open"
|
|
||||||
firewall_logif="YES"
|
|
||||||
firewall_coscripts="/etc/ipfw/sslh.rules"
|
|
||||||
|
|
||||||
|
|
||||||
/etc/ipfw/sslh.rules
|
|
||||||
|
|
||||||
#! /bin/sh
|
|
||||||
|
|
||||||
# ssl
|
|
||||||
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
|
|
||||||
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
|
|
||||||
|
|
||||||
# ssh
|
|
||||||
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
|
|
||||||
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
|
|
||||||
|
|
||||||
# xmpp
|
|
||||||
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
|
|
||||||
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
|
|
||||||
|
|
||||||
# openvpn (running on other internal system)
|
|
||||||
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
|
|
||||||
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
|
|
||||||
|
|
||||||
General notes:
|
|
||||||
|
|
||||||
|
|
||||||
This will only work if `sslh` does not use any loopback
|
|
||||||
addresses (no `127.0.0.1` or `localhost`), you'll need to use
|
|
||||||
explicit IP addresses (or names):
|
|
||||||
|
|
||||||
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
|
|
||||||
|
|
||||||
This will not work:
|
|
||||||
|
|
||||||
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
|
|
||||||
|
|
||||||
Transparent proxying means the target server sees the real
|
|
||||||
origin address, so it means if the client connects using
|
|
||||||
IPv6, the server must also support IPv6. It is easy to
|
|
||||||
support both IPv4 and IPv6 by configuring the server
|
|
||||||
accordingly, and setting `sslh` to connect to a name that
|
|
||||||
resolves to both IPv4 and IPv6, e.g.:
|
|
||||||
|
|
||||||
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
|
|
||||||
|
|
||||||
/etc/hosts:
|
|
||||||
192.168.0.1 insideaddr
|
|
||||||
201::::2 insideaddr
|
|
||||||
|
|
||||||
Upon incoming IPv6 connection, `sslh` will first try to
|
|
||||||
connect to the IPv4 address (which will fail), then connect
|
|
||||||
to the IPv6 address.
|
|
||||||
|
|
||||||
Systemd Socket Activation
|
Systemd Socket Activation
|
||||||
-------------------------
|
-------------------------
|
||||||
@ -313,7 +167,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.
|
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
|
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
|
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
|
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
|
||||||
@ -335,13 +189,15 @@ UDP
|
|||||||
---
|
---
|
||||||
|
|
||||||
`sslh` can perform demultiplexing on UDP packets as well.
|
`sslh` can perform demultiplexing on UDP packets as well.
|
||||||
This only works with `sslh-select` (it is not possible to
|
This does not work with `sslh-fork` (it is not possible to
|
||||||
support UDP with a forking model). Specify a listening
|
support UDP with a forking model). Specify a listening
|
||||||
address and target protocols with `is_udp: true`. `sslh`
|
address and target protocols with `is_udp: true`. `sslh`
|
||||||
will wait for incoming UDP packets, run the probes in the
|
will wait for incoming UDP packets, run the probes in the
|
||||||
usual fashion, and forward packets to the appropriate
|
usual fashion, and forward packets to the appropriate
|
||||||
target. `sslh` will then remember the association between
|
target. `sslh` will then remember the association between
|
||||||
remote host to target server for 60 seconds by default,
|
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
|
process both single-datagram protocols such as DNS, and
|
||||||
connection-based protocols such as QUIC.
|
connection-based protocols such as QUIC.
|
||||||
|
|
||||||
|
An example for supporting QUIC is shown in `example.cfg`.
|
||||||
|
198
doc/tproxy.md
198
doc/tproxy.md
@ -1,17 +1,181 @@
|
|||||||
Transparent Proxy to Two Hosts
|
# Transparent proxy
|
||||||
==============================
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
This document shows recipes that may help to do that.
|
||||||
|
|
||||||
|
Note that getting this to work 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
|
||||||
|
required network configuration somewhat. If it doesn't work,
|
||||||
|
it's almost certain that the problem is not linked to `sslh`
|
||||||
|
but to the network setup that surrounds it. If in trouble,
|
||||||
|
it might be worth trying to set up the network rules
|
||||||
|
with a simpler server than `sslh`, such as
|
||||||
|
[`socat`](http://www.dest-unreach.org/socat/)
|
||||||
|
|
||||||
|
|
||||||
|
Users have tried to do at least the following:
|
||||||
|
|
||||||
|
* `sslh` and the target servers run on the same host (see [below](#transparent-proxy-to-one-host))
|
||||||
|
* `sslh` runs on a host, and the target servers run on LXC or dockers running on the same host. No known working setup.
|
||||||
|
* `sslh` runs on a host, and the target servers run on different hosts on the same local network(see [below](#transparent-proxy-to-two-hosts))
|
||||||
|
* `sslh` runs on a host, and the target servers run on a different host on a different network (there is a [usecase](https://github.com/yrutschle/sslh/issues/295) for this). No known working setup, and it's unclear it is possible.
|
||||||
|
|
||||||
|
|
||||||
|
## Transparent proxy to one host
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
`sslh` needs extended rights to perform this: you'll need to
|
||||||
|
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
|
||||||
|
or run it as root (but don't do that).
|
||||||
|
|
||||||
|
The firewalling tables also need to be adjusted as follows.
|
||||||
|
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
|
||||||
|
this scheme -- let me know if you manage that:
|
||||||
|
|
||||||
|
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
|
||||||
|
sysctl -w net.ipv4.conf.default.route_localnet=1
|
||||||
|
sysctl -w net.ipv4.conf.all.route_localnet=1
|
||||||
|
|
||||||
|
# DROP martian packets as they would have been if route_localnet was zero
|
||||||
|
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
|
||||||
|
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
|
||||||
|
|
||||||
|
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
|
||||||
|
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
|
||||||
|
|
||||||
|
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
|
||||||
|
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||||
|
|
||||||
|
# Configure routing for those marked packets
|
||||||
|
ip rule add fwmark 0x1 lookup 100
|
||||||
|
ip route add local 0.0.0.0/0 dev lo table 100
|
||||||
|
|
||||||
|
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
|
||||||
|
sysctl -w net.ipv4.conf.default.route_localnet=1
|
||||||
|
sysctl -w net.ipv4.conf.all.route_localnet=1
|
||||||
|
|
||||||
|
# DROP martian packets as they would have been if route_localnet was zero
|
||||||
|
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
|
||||||
|
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
|
||||||
|
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
|
||||||
|
|
||||||
|
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
|
||||||
|
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
|
||||||
|
|
||||||
|
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
|
||||||
|
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||||
|
|
||||||
|
# Configure routing for those marked packets
|
||||||
|
ip -6 rule add fwmark 0x1 lookup 100
|
||||||
|
ip -6 route add local ::/0 dev lo table 100
|
||||||
|
|
||||||
|
Explanation:
|
||||||
|
To be able to use `localhost` as destination in your sslh config along with transparent proxying
|
||||||
|
you have to allow routing of loopback addresses as done above.
|
||||||
|
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
|
||||||
|
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
|
||||||
|
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
|
||||||
|
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
|
||||||
|
showing how packets will traverse the iptables chains.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
|
||||||
|
These rules will allow you to connect directly to ssh on port
|
||||||
|
22 (or to any other service behind sslh) as well as through sslh on port 443.
|
||||||
|
|
||||||
|
Also remember that iptables configuration and ip routes and
|
||||||
|
rules won't be necessarily persisted after you reboot. Make
|
||||||
|
sure to save them properly. For example in CentOS7, you would
|
||||||
|
do `iptables-save > /etc/sysconfig/iptables`, and add both
|
||||||
|
`ip` commands to your `/etc/rc.local`.
|
||||||
|
|
||||||
|
### FreeBSD
|
||||||
|
|
||||||
|
Given you have no firewall defined yet, you can use the following configuration
|
||||||
|
to have ipfw properly redirect traffic back to sslh
|
||||||
|
|
||||||
|
/etc/rc.conf
|
||||||
|
firewall_enable="YES"
|
||||||
|
firewall_type="open"
|
||||||
|
firewall_logif="YES"
|
||||||
|
firewall_coscripts="/etc/ipfw/sslh.rules"
|
||||||
|
|
||||||
|
|
||||||
|
/etc/ipfw/sslh.rules
|
||||||
|
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# ssl
|
||||||
|
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
|
||||||
|
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
|
||||||
|
|
||||||
|
# ssh
|
||||||
|
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
|
||||||
|
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
|
||||||
|
|
||||||
|
# xmpp
|
||||||
|
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
|
||||||
|
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
|
||||||
|
|
||||||
|
# openvpn (running on other internal system)
|
||||||
|
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
|
||||||
|
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
|
||||||
|
|
||||||
|
General notes:
|
||||||
|
|
||||||
|
|
||||||
|
This will only work if `sslh` does not use any loopback
|
||||||
|
addresses (no `127.0.0.1` or `localhost`), you'll need to use
|
||||||
|
explicit IP addresses (or names):
|
||||||
|
|
||||||
|
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
|
||||||
|
|
||||||
|
This will not work:
|
||||||
|
|
||||||
|
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
|
||||||
|
|
||||||
|
Transparent proxying means the target server sees the real
|
||||||
|
origin address, so it means if the client connects using
|
||||||
|
IPv6, the server must also support IPv6. It is easy to
|
||||||
|
support both IPv4 and IPv6 by configuring the server
|
||||||
|
accordingly, and setting `sslh` to connect to a name that
|
||||||
|
resolves to both IPv4 and IPv6, e.g.:
|
||||||
|
|
||||||
|
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
|
||||||
|
|
||||||
|
/etc/hosts:
|
||||||
|
192.168.0.1 insideaddr
|
||||||
|
201::::2 insideaddr
|
||||||
|
|
||||||
|
Upon incoming IPv6 connection, `sslh` will first try to
|
||||||
|
connect to the IPv4 address (which will fail), then connect
|
||||||
|
to the IPv6 address.
|
||||||
|
|
||||||
|
|
||||||
|
## Transparent Proxy to Two Hosts
|
||||||
|
|
||||||
Tutorial by Sean Warner. 19 June 2019 20:35
|
Tutorial by Sean Warner. 19 June 2019 20:35
|
||||||
|
|
||||||
Aim
|
### Aim
|
||||||
---
|
|
||||||
|
|
||||||
* Show that `sslh` can transparently proxy requests from the internet to services on two separate hosts that are both on the same LAN.
|
* Show that `sslh` can transparently proxy requests from the internet to services on two separate hosts that are both on the same LAN.
|
||||||
* The IP address of the client initiating the request is what the destination should see… and not the IP address of the host that `sslh` is running on, which is what happens when `sslh` is not running in transparent mode.
|
* The IP address of the client initiating the request is what the destination should see… and not the IP address of the host that `sslh` is running on, which is what happens when `sslh` is not running in transparent mode.
|
||||||
* The solution here only works for my very specific use-case but hopefully others can adapt it to suits their needs.
|
* The solution here only works for my very specific use-case but hopefully others can adapt it to suits their needs.
|
||||||
|
|
||||||
Overview of my Network
|
### Overview of my Network
|
||||||
----------------------
|
|
||||||
|
|
||||||
Two Raspberry Pis on my home LAN:
|
Two Raspberry Pis on my home LAN:
|
||||||
* Pi A: 192.168.1.124 – `sslh` (Port 4433), Apache2 web server for https (port 443), `stunnel` (port 4480) to decrypt ssh traffic and forward to SSH server (also on Pi A at Port 1022)
|
* Pi A: 192.168.1.124 – `sslh` (Port 4433), Apache2 web server for https (port 443), `stunnel` (port 4480) to decrypt ssh traffic and forward to SSH server (also on Pi A at Port 1022)
|
||||||
@ -20,8 +184,7 @@ Two Raspberry Pis on my home LAN:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
`sslh` build
|
### `sslh` build
|
||||||
------------
|
|
||||||
|
|
||||||
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
|
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
|
||||||
|
|
||||||
@ -47,8 +210,7 @@ MAN=sslh.8.gz # man page name
|
|||||||
# itself
|
# itself
|
||||||
```
|
```
|
||||||
|
|
||||||
systemd setup
|
### systemd setup
|
||||||
-------------
|
|
||||||
|
|
||||||
Create an sslh systemd service file...
|
Create an sslh systemd service file...
|
||||||
```
|
```
|
||||||
@ -83,8 +245,7 @@ Start it again to test…
|
|||||||
# systemctl start sslh
|
# systemctl start sslh
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure `sslh`
|
### Configure `sslh`
|
||||||
----------------
|
|
||||||
|
|
||||||
First stop `sslh` then open the config file and replace with below, save and start `sslh` again
|
First stop `sslh` then open the config file and replace with below, save and start `sslh` again
|
||||||
```
|
```
|
||||||
@ -123,8 +284,7 @@ protocols:
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure `stunnel`
|
### Configure `stunnel`
|
||||||
-------------------
|
|
||||||
|
|
||||||
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
|
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
|
||||||
```
|
```
|
||||||
@ -151,8 +311,7 @@ connect = 192.168.1.124:1022
|
|||||||
TIMEOUTclose = 0
|
TIMEOUTclose = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure iptables for Pi A
|
### Configure iptables for Pi A
|
||||||
--------------------------
|
|
||||||
|
|
||||||
The `_add.sh` script creates the rules, the `_rm.sh` script removes the rules.
|
The `_add.sh` script creates the rules, the `_rm.sh` script removes the rules.
|
||||||
They will be lost if you reboot but there are ways to make them load again on start-up..
|
They will be lost if you reboot but there are ways to make them load again on start-up..
|
||||||
@ -194,8 +353,7 @@ Now run the "add" script on Pi A!
|
|||||||
# piA_tproxy_rm.sh
|
# piA_tproxy_rm.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Configure iptables for Pi B
|
## Configure iptables for Pi B
|
||||||
--------------------------
|
|
||||||
|
|
||||||
```
|
```
|
||||||
# nano /usr/local/sbin/piB_tproxy_add.sh
|
# nano /usr/local/sbin/piB_tproxy_add.sh
|
||||||
@ -235,8 +393,8 @@ Now run the "add" script on Pi B!
|
|||||||
# piB_tproxy_rm.sh
|
# piB_tproxy_rm.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Testing
|
### Testing
|
||||||
-------
|
|
||||||
* Getting to sshd on PiA
|
* Getting to sshd on PiA
|
||||||
|
|
||||||
I did this test using 4G from my phone (outside the LAN)
|
I did this test using 4G from my phone (outside the LAN)
|
||||||
|
12
echo_test.cfg
Normal file
12
echo_test.cfg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
# TODO: c2s does not warn if udp: 1 (instead of 'true')
|
||||||
|
|
||||||
|
udp: true;
|
||||||
|
|
||||||
|
prefix: "hello";
|
||||||
|
|
||||||
|
listen: "localhost:9000";
|
||||||
|
|
||||||
|
listen-host: "localhost";
|
||||||
|
listen-port: "9000";
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||||
* on Fri Aug 13 18:03:20 2021.
|
* on Sat Apr 30 09:55:03 2022.
|
||||||
|
|
||||||
# conf2struct: generate libconf parsers that read to structs
|
# conf2struct: generate libconf parsers that read to structs
|
||||||
# Copyright (C) 2018-2021 Yves Rutschle
|
# Copyright (C) 2018-2021 Yves Rutschle
|
||||||
@ -365,7 +365,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
|||||||
return 0;
|
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
|
* location that must be large enough, converting on the way
|
||||||
* (i.e. CFG_INT gets atoi() and so on) */
|
* (i.e. CFG_INT gets atoi() and so on) */
|
||||||
/* 0: success
|
/* 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) {
|
if (pmatch[pmatch_cnt].rm_so == -1) {
|
||||||
/* This should not happen as regexec() did
|
/* This should not happen as regexec() did
|
||||||
* match before, unless there is a
|
* match before, unless there is a
|
||||||
* discrepency between the regex and the
|
* discrepancy between the regex and the
|
||||||
* number of backreferences */
|
* number of backreferences */
|
||||||
return 0;
|
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
|
/* Typesets all the settings in a configuration as a
|
||||||
* newly-allocated string. The string management is caller's
|
* newly-allocated string. The string management is caller's
|
||||||
* responsability.
|
* responsibility.
|
||||||
* Returns the number of scalars in the configuration */
|
* Returns the number of scalars in the configuration */
|
||||||
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||||
* on Fri Aug 13 18:03:20 2021.
|
* on Sat Apr 30 09:55:03 2022.
|
||||||
|
|
||||||
# conf2struct: generate libconf parsers that read to structs
|
# conf2struct: generate libconf parsers that read to structs
|
||||||
# Copyright (C) 2018-2021 Yves Rutschle
|
# Copyright (C) 2018-2021 Yves Rutschle
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* echosrv: a simple line echo server with optional prefix adding.
|
/* 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
|
* 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,
|
* used for testing: we create several such servers with different prefixes,
|
||||||
@ -100,7 +100,10 @@ void tcp_echo(struct listen_endpoint* listen_socket)
|
|||||||
{
|
{
|
||||||
while (1) {
|
while (1) {
|
||||||
int in_socket = accept(listen_socket->socketfd, 0, 0);
|
int in_socket = accept(listen_socket->socketfd, 0, 0);
|
||||||
CHECK_RES_DIE(in_socket, "accept");
|
if (in_socket == -1) {
|
||||||
|
perror("tcp_echo:accept");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (!fork())
|
if (!fork())
|
||||||
{
|
{
|
||||||
@ -109,6 +112,7 @@ void tcp_echo(struct listen_endpoint* listen_socket)
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
close(in_socket);
|
close(in_socket);
|
||||||
|
waitpid(-1, NULL, WNOHANG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
109
echoѕrv-conf.h
Normal file
109
echoѕrv-conf.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||||
|
* on Sat Nov 7 09:19:26 2020.
|
||||||
|
|
||||||
|
# conf2struct: generate libconf parsers that read to structs
|
||||||
|
# Copyright (C) 2018-2019 Yves Rutschle
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef C2S_SSLHCFG_H
|
||||||
|
#define C2S_SSLHCFG_H
|
||||||
|
#ifdef LIBCONFIG
|
||||||
|
# include <libconfig.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#include "probe.h"
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
struct sslhcfg_listen_item {
|
||||||
|
char* host;
|
||||||
|
char* port;
|
||||||
|
int keepalive;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sslhcfg_protocols_item {
|
||||||
|
char* name;
|
||||||
|
char* host;
|
||||||
|
char* port;
|
||||||
|
int service_is_present;
|
||||||
|
char* service;
|
||||||
|
int fork;
|
||||||
|
int tfo_ok;
|
||||||
|
int log_level;
|
||||||
|
int keepalive;
|
||||||
|
size_t sni_hostnames_len;
|
||||||
|
char** sni_hostnames;
|
||||||
|
size_t alpn_protocols_len;
|
||||||
|
char** alpn_protocols;
|
||||||
|
size_t regex_patterns_len;
|
||||||
|
char** regex_patterns;
|
||||||
|
int minlength_is_present;
|
||||||
|
int minlength;
|
||||||
|
T_PROBE* probe;
|
||||||
|
struct addrinfo* saddr;
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sslhcfg_item {
|
||||||
|
char* prefix;
|
||||||
|
int verbose;
|
||||||
|
int foreground;
|
||||||
|
int inetd;
|
||||||
|
int numeric;
|
||||||
|
int transparent;
|
||||||
|
int timeout;
|
||||||
|
int user_is_present;
|
||||||
|
char* user;
|
||||||
|
int pidfile_is_present;
|
||||||
|
char* pidfile;
|
||||||
|
int chroot_is_present;
|
||||||
|
char* chroot;
|
||||||
|
char* syslog_facility;
|
||||||
|
char* on_timeout;
|
||||||
|
size_t listen_len;
|
||||||
|
struct sslhcfg_listen_item* listen;
|
||||||
|
size_t protocols_len;
|
||||||
|
struct sslhcfg_protocols_item* protocols;
|
||||||
|
};
|
||||||
|
|
||||||
|
int sslhcfg_parse_file(
|
||||||
|
const char* filename,
|
||||||
|
struct sslhcfg_item* sslhcfg,
|
||||||
|
const char** errmsg);
|
||||||
|
|
||||||
|
void sslhcfg_fprint(
|
||||||
|
FILE* out,
|
||||||
|
struct sslhcfg_item *sslhcfg,
|
||||||
|
int depth);
|
||||||
|
|
||||||
|
int sslhcfg_cl_parse(
|
||||||
|
int argc,
|
||||||
|
char* argv[],
|
||||||
|
struct sslhcfg_item *sslhcfg);
|
||||||
|
|
||||||
|
#endif
|
29
example.cfg
29
example.cfg
@ -3,7 +3,6 @@
|
|||||||
# not be used as a starting point for a working
|
# not be used as a starting point for a working
|
||||||
# configuration. Instead use basic.cfg.
|
# configuration. Instead use basic.cfg.
|
||||||
|
|
||||||
verbose: 0;
|
|
||||||
foreground: true;
|
foreground: true;
|
||||||
inetd: false;
|
inetd: false;
|
||||||
numeric: false;
|
numeric: false;
|
||||||
@ -13,6 +12,30 @@ user: "nobody";
|
|||||||
pidfile: "/var/run/sslh.pid";
|
pidfile: "/var/run/sslh.pid";
|
||||||
chroot: "/var/empty";
|
chroot: "/var/empty";
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||||
|
# Defaults are indicated here, and should be sensible. Generally, you want *-error
|
||||||
|
# to be always enabled, to know if something is going wrong.
|
||||||
|
verbose-config: 0; # print configuration at startup
|
||||||
|
verbose-config-error: 3; # print configuration errors
|
||||||
|
verbose-connections: 3; # trace established incoming address to forward address
|
||||||
|
verbose-connections-error: 3; # connection errors
|
||||||
|
verbose-connections-try: 0; # connection attempts towards targets
|
||||||
|
verbose-fd: 0; # file descriptor activity, open/close/whatnot
|
||||||
|
verbose-packets: 0; # hexdump packets on which probing is done
|
||||||
|
verbose-probe-info: 0; # what's happening during the probe process
|
||||||
|
verbose-probe-error: 3; # failures and problems during probing
|
||||||
|
verbose-system-error: 3; # system call problem, i.e. malloc, fork, failing
|
||||||
|
verbose-int-error: 3; # internal errors, the kind that should never happen
|
||||||
|
|
||||||
|
|
||||||
|
# Specify a path to the logfile.
|
||||||
|
#logfile: "/var/log/sslh.log"
|
||||||
|
|
||||||
|
# Specify the number of concurrent UDP connection that can
|
||||||
|
# be managed (default 1024)
|
||||||
|
udp_max_connections: 16;
|
||||||
|
|
||||||
# Specify which syslog facility to use (names for your
|
# Specify which syslog facility to use (names for your
|
||||||
# system are usually defined in /usr/include/*/sys/syslog.h
|
# system are usually defined in /usr/include/*/sys/syslog.h
|
||||||
# or equivalent)
|
# or equivalent)
|
||||||
@ -44,6 +67,8 @@ listen:
|
|||||||
# fork: Should a new process be forked for this protocol?
|
# fork: Should a new process be forked for this protocol?
|
||||||
# (only useful for sslh-select)
|
# (only useful for sslh-select)
|
||||||
# tfo_ok: Set to true if the server supports TCP FAST OPEN
|
# tfo_ok: Set to true if the server supports TCP FAST OPEN
|
||||||
|
# resolve_on_forward: Set to true if server address should be resolved on
|
||||||
|
# (every) newly incoming connection (again)
|
||||||
# transparent: Set to true to proxy this protocol
|
# transparent: Set to true to proxy this protocol
|
||||||
# transparently (server sees the remote client IP
|
# transparently (server sees the remote client IP
|
||||||
# address). Same as the global option, but per-protocol
|
# address). Same as the global option, but per-protocol
|
||||||
@ -98,7 +123,7 @@ protocols:
|
|||||||
udp_timeout: 20; # Time after which the "connection" is forgotten
|
udp_timeout: 20; # Time after which the "connection" is forgotten
|
||||||
regex_patterns: [ "hello" ]; },
|
regex_patterns: [ "hello" ]; },
|
||||||
# Forward Teamspeak3 (Voice only)
|
# Forward Teamspeak3 (Voice only)
|
||||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "9987"; regex_patterns: [ "TS3INIT1" ]; },
|
{ name: "teamspeak"; host: "localhost"; is_udp: true; port: "9987"; },
|
||||||
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
|
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
|
||||||
# Remember that the regex needs to be adjusted for every supported QUIC version.
|
# Remember that the regex needs to be adjusted for every supported QUIC version.
|
||||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
|
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
|
||||||
|
35
gap.c
35
gap.c
@ -31,19 +31,14 @@
|
|||||||
#include "gap.h"
|
#include "gap.h"
|
||||||
|
|
||||||
|
|
||||||
typedef struct gap_array {
|
|
||||||
int len; /* Number of elements in array */
|
|
||||||
void** array;
|
|
||||||
} gap_array;
|
|
||||||
|
|
||||||
/* Allocate one page-worth of elements */
|
/* Allocate one page-worth of elements */
|
||||||
static int gap_len_alloc(int elem_size)
|
static int gap_len_alloc(int elem_size)
|
||||||
{
|
{
|
||||||
return getpagesize() / elem_size;
|
return getpagesize() / elem_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Creates a new gap, all pointers are initialised at NULL */
|
/* Creates a new gap at least `len` big, all pointers are initialised at NULL */
|
||||||
gap_array* gap_init(void)
|
gap_array* gap_init(int len)
|
||||||
{
|
{
|
||||||
gap_array* gap = malloc(sizeof(*gap));
|
gap_array* gap = malloc(sizeof(*gap));
|
||||||
if (!gap) return NULL;
|
if (!gap) return NULL;
|
||||||
@ -51,8 +46,12 @@ gap_array* gap_init(void)
|
|||||||
|
|
||||||
int elem_size = sizeof(gap->array[0]);
|
int elem_size = sizeof(gap->array[0]);
|
||||||
gap->len = gap_len_alloc(elem_size);
|
gap->len = gap_len_alloc(elem_size);
|
||||||
|
if (gap->len < len) gap->len = len;
|
||||||
gap->array = malloc(gap->len * elem_size);
|
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++)
|
for (int i = 0; i < gap->len; i++)
|
||||||
gap->array[i] = NULL;
|
gap->array[i] = NULL;
|
||||||
@ -60,12 +59,7 @@ gap_array* gap_init(void)
|
|||||||
return gap;
|
return gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* gap_get(gap_array* gap, int index)
|
int gap_extend(gap_array* gap)
|
||||||
{
|
|
||||||
return gap->array[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
static int gap_extend(gap_array* gap)
|
|
||||||
{
|
{
|
||||||
int elem_size = sizeof(gap->array[0]);
|
int elem_size = sizeof(gap->array[0]);
|
||||||
int new_length = gap->len + gap_len_alloc(elem_size);
|
int new_length = gap->len + gap_len_alloc(elem_size);
|
||||||
@ -83,17 +77,6 @@ static int gap_extend(gap_array* gap)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int gap_set(gap_array* gap, int index, void* ptr)
|
|
||||||
{
|
|
||||||
if (index >= gap->len) {
|
|
||||||
int res = gap_extend(gap);
|
|
||||||
if (res == -1) return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
gap->array[index] = ptr;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void gap_destroy(gap_array* gap)
|
void gap_destroy(gap_array* gap)
|
||||||
{
|
{
|
||||||
free(gap->array);
|
free(gap->array);
|
||||||
@ -119,7 +102,7 @@ int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
|||||||
else
|
else
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
for (i = start; i < len; i++) {
|
for (i = start; i < len - 1; i++) {
|
||||||
gap->array[i] = gap->array[i+1];
|
gap->array[i] = gap->array[i+1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
gap.h
36
gap.h
@ -3,11 +3,41 @@
|
|||||||
|
|
||||||
typedef struct gap_array gap_array;
|
typedef struct gap_array gap_array;
|
||||||
|
|
||||||
gap_array* gap_init();
|
gap_array* gap_init(int len);
|
||||||
void* gap_get(gap_array* gap, int index);
|
static void* gap_get(gap_array* gap, int index);
|
||||||
int gap_set(gap_array* gap, int index, void* ptr);
|
static int gap_set(gap_array* gap, int index, void* ptr);
|
||||||
void gap_destroy(gap_array* gap);
|
void gap_destroy(gap_array* gap);
|
||||||
|
|
||||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len);
|
int gap_remove_ptr(gap_array* gap, void* ptr, int len);
|
||||||
|
|
||||||
|
/* Private declarations to allow inlining.
|
||||||
|
* Don't assume my implementation. */
|
||||||
|
typedef struct gap_array {
|
||||||
|
int len; /* Number of elements in array */
|
||||||
|
void** array;
|
||||||
|
} gap_array;
|
||||||
|
|
||||||
|
int gap_extend(gap_array* gap);
|
||||||
|
|
||||||
|
static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, void* ptr)
|
||||||
|
{
|
||||||
|
while (index >= gap->len) {
|
||||||
|
int res = gap_extend(gap);
|
||||||
|
if (res == -1) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
gap->array[index] = ptr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
10
genver.sh
10
genver.sh
@ -10,14 +10,14 @@ fi
|
|||||||
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
|
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
|
# If we don't have git, we can't work out what
|
||||||
# version this is. It must have been downloaded as a
|
# version this is. It must have been downloaded as a
|
||||||
# zip file.
|
# zip file.
|
||||||
|
|
||||||
# If downloaded from the release page, the directory
|
# If downloaded from the release page, the directory
|
||||||
# has the version number.
|
# has the version number.
|
||||||
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
|
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
|
||||||
|
|
||||||
if [ "x$release" = "x" ]; then
|
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
|
# zip file with all files dated from the last
|
||||||
# change: use the Makefile's modification time as a
|
# change: use the Makefile's modification time as a
|
||||||
# release number
|
# release number
|
||||||
@ -28,7 +28,7 @@ fi
|
|||||||
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
|
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
|
||||||
# generate the version info based on the tag
|
# generate the version info based on the tag
|
||||||
release=`(git describe --tags || git --describe || git describe --all --long) \
|
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?
|
# Are there uncommitted changes?
|
||||||
git update-index --refresh --unmerged > /dev/null
|
git update-index --refresh --unmerged > /dev/null
|
||||||
|
224
hash.c
Normal file
224
hash.c
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* a fixed-sized hash
|
||||||
|
*
|
||||||
|
# Copyright (C) 2022 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
|
||||||
|
#
|
||||||
|
# */
|
||||||
|
|
||||||
|
|
||||||
|
/* * The hash is open-addressing, linear search, robin-hood insertion, with
|
||||||
|
* backward shift deletion. References:
|
||||||
|
* https://codecapsule.com/2013/11/11/robin-hood-hashing/
|
||||||
|
* https://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
|
||||||
|
* This means items are reordered upon insertion and deletion, and the hash
|
||||||
|
* is well-ordered at all times with no tombstones.
|
||||||
|
*
|
||||||
|
* Each pointer is either:
|
||||||
|
* - to a connection struct
|
||||||
|
* - FREE (NULL) if not allocated
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "gap.h"
|
||||||
|
|
||||||
|
typedef void* hash_item;
|
||||||
|
#include "hash.h"
|
||||||
|
|
||||||
|
static void* const FREE = NULL;
|
||||||
|
|
||||||
|
struct hash {
|
||||||
|
int hash_size; /* Max number of items in the hash */
|
||||||
|
int item_cnt; /* Number of items in the hash */
|
||||||
|
gap_array* data;
|
||||||
|
|
||||||
|
hash_make_key_fn hash_make_key;
|
||||||
|
hash_cmp_item_fn cmp_item;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct hash hash;
|
||||||
|
|
||||||
|
|
||||||
|
static int hash_make_key(hash* h, hash_item item)
|
||||||
|
{
|
||||||
|
return h->hash_make_key(item) % h->hash_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item)
|
||||||
|
{
|
||||||
|
hash* h = malloc(sizeof(*h));
|
||||||
|
if (!h) return NULL;
|
||||||
|
|
||||||
|
h->hash_size = hash_size;
|
||||||
|
h->item_cnt = 0;
|
||||||
|
h->data = gap_init(hash_size);
|
||||||
|
h->hash_make_key = make_key;
|
||||||
|
h->cmp_item = cmp_item;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the index following i in h */
|
||||||
|
static int hash_next_index(hash* h, int i)
|
||||||
|
{
|
||||||
|
return (i + 1) % h->hash_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the index in h of specified address, -1 if not found
|
||||||
|
* item is an item object that must return the target wanted index and for
|
||||||
|
* which comparison with the searched object will succeed.
|
||||||
|
* */
|
||||||
|
static int hash_find_index(hash* h, hash_item item)
|
||||||
|
{
|
||||||
|
hash_item cnx;
|
||||||
|
int index = hash_make_key(h, item);
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
cnx = gap_get(h->data, index);
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "searching %d\n", index);
|
||||||
|
#endif
|
||||||
|
while (cnx != FREE) {
|
||||||
|
if (cnt++ > h->hash_size) return -1;
|
||||||
|
|
||||||
|
if (!h->cmp_item(cnx, item))
|
||||||
|
break;
|
||||||
|
|
||||||
|
index = hash_next_index(h, index);
|
||||||
|
cnx = gap_get(h->data, index);
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "searching %d\n", index);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (cnx == FREE) return -1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_item hash_find(hash* h, hash_item item)
|
||||||
|
{
|
||||||
|
int index = hash_find_index(h, item);
|
||||||
|
if (index == -1) return NULL;
|
||||||
|
hash_item out = gap_get(h->data, index);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Returns DIB: distance to initial bucket */
|
||||||
|
static int distance(int current_index, hash* h, hash_item item)
|
||||||
|
{
|
||||||
|
int wanted_index = hash_make_key(h, item);
|
||||||
|
if (wanted_index <= current_index)
|
||||||
|
return current_index - wanted_index;
|
||||||
|
else
|
||||||
|
return current_index - wanted_index + h->hash_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int hash_insert(hash* h, hash_item new)
|
||||||
|
{
|
||||||
|
int bubble_wanted_index = hash_make_key(h, new);
|
||||||
|
int index = bubble_wanted_index;
|
||||||
|
gap_array* hash = h->data;
|
||||||
|
|
||||||
|
if (h->item_cnt == h->hash_size)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
hash_item curr_item = gap_get(hash, index);
|
||||||
|
while (curr_item) {
|
||||||
|
if (distance(index, h, curr_item) < distance(index, h, new)) {
|
||||||
|
gap_set(h->data, index, new);
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "intermediate insert [%s] at %d\n", &new->client_addr, index);
|
||||||
|
#endif
|
||||||
|
new = curr_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = hash_next_index(h, index);
|
||||||
|
curr_item = gap_get(hash, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
fprintf(stderr, "final insert at %d\n", index);
|
||||||
|
#endif
|
||||||
|
gap_set(hash, index, new);
|
||||||
|
h->item_cnt++;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove cnx from the hash */
|
||||||
|
int hash_remove(hash* h, hash_item item)
|
||||||
|
{
|
||||||
|
gap_array* hash = h->data;
|
||||||
|
|
||||||
|
int index = hash_find_index(h, item);
|
||||||
|
if (index == -1) return -1; /* Tried to remove something that isn't there */
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int next_index = hash_next_index(h, index);
|
||||||
|
hash_item next = gap_get(h->data, next_index);
|
||||||
|
if ((next == FREE) || (distance(next_index, h, next) == 0)) {
|
||||||
|
h->item_cnt--;
|
||||||
|
gap_set(hash, index, FREE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gap_set(hash, index, next);
|
||||||
|
|
||||||
|
index = hash_next_index(h, index);;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HASH_TESTING
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#define STR_LENGTH 16
|
||||||
|
struct hash_item {
|
||||||
|
int wanted_index;
|
||||||
|
char str[STR_LENGTH];
|
||||||
|
};
|
||||||
|
void hash_dump(hash* h, char* filename)
|
||||||
|
{
|
||||||
|
char str[STR_LENGTH];
|
||||||
|
FILE* out = fopen(filename, "w");
|
||||||
|
|
||||||
|
if (!out) {
|
||||||
|
perror(filename);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(out, "<hash elem=%d>\n", h->item_cnt);
|
||||||
|
for (int i = 0; i < h->hash_size; i++) {
|
||||||
|
hash_item item = gap_get(h->data, i);
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
memset(str, 0, STR_LENGTH);
|
||||||
|
if (item) {
|
||||||
|
idx = hash_make_key(h, item);
|
||||||
|
memcpy(str, ((struct hash_item*)item)->str, STR_LENGTH);
|
||||||
|
}
|
||||||
|
fprintf(out, "\t%d:%d:%s\n", i, idx, str);
|
||||||
|
}
|
||||||
|
fprintf(out, "</hash>\n");
|
||||||
|
fclose(out);
|
||||||
|
}
|
||||||
|
#endif
|
28
hash.h
Normal file
28
hash.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef HASH_H
|
||||||
|
#define HASH_H
|
||||||
|
|
||||||
|
/* You will need to typedef a pointer type to hash_item before including this
|
||||||
|
* .h */
|
||||||
|
|
||||||
|
typedef struct hash hash;
|
||||||
|
|
||||||
|
/* Function that returns a key (index) for a given item. The key must be always
|
||||||
|
* the same for an item. It doesn't need to be bounded (hash.c masks it for you) */
|
||||||
|
typedef int (*hash_make_key_fn)(hash_item item);
|
||||||
|
|
||||||
|
/* Function that compares two items: returns 0 if they are the same */
|
||||||
|
typedef int (*hash_cmp_item_fn)(hash_item item1, hash_item item2);
|
||||||
|
|
||||||
|
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item);
|
||||||
|
|
||||||
|
int hash_insert(hash* h, hash_item new);
|
||||||
|
int hash_remove(hash* h, hash_item item);
|
||||||
|
|
||||||
|
/* Returns the hash item that matches specification (meaning the
|
||||||
|
* comparison function returns true for cmp(x, item), or NULL if not found */
|
||||||
|
hash_item hash_find(hash* h, hash_item item);
|
||||||
|
|
||||||
|
|
||||||
|
void hash_dump(hash* h, char* filename); /* For development only */
|
||||||
|
|
||||||
|
#endif
|
7
hashtest/Makefile
Normal file
7
hashtest/Makefile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
CFLAGS=-DHASH_TESTING -O2 -Wall
|
||||||
|
OBJ=../hash.o ../gap.o htest.o
|
||||||
|
|
||||||
|
htest: $(OBJ)
|
||||||
|
$(CC) -o htest $(OBJ)
|
||||||
|
|
8
hashtest/delete.tst
Normal file
8
hashtest/delete.tst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Basic delete
|
||||||
|
a 10 aa
|
||||||
|
a 10 ab
|
||||||
|
a 10 ac
|
||||||
|
a 20 ba
|
||||||
|
a 21 bb
|
||||||
|
|
||||||
|
d 21 bb
|
34
hashtest/delete.tst.ref
Normal file
34
hashtest/delete.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=4>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:10:aa
|
||||||
|
11:10:ab
|
||||||
|
12:10:ac
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:20:ba
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
9
hashtest/delete_at_end.tst
Normal file
9
hashtest/delete_at_end.tst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Delete inside a block with nothing after
|
||||||
|
|
||||||
|
a 10 aa
|
||||||
|
a 10 ab
|
||||||
|
a 12 ac
|
||||||
|
a 13 ad
|
||||||
|
a 14 ae
|
||||||
|
|
||||||
|
d 14 ae
|
34
hashtest/delete_at_end.tst.ref
Normal file
34
hashtest/delete_at_end.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=4>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:10:aa
|
||||||
|
11:10:ab
|
||||||
|
12:12:ac
|
||||||
|
13:13:ad
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
9
hashtest/delete_below_floor.tst
Normal file
9
hashtest/delete_below_floor.tst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# wrap-around and delete below floor
|
||||||
|
a 2 ba
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 30 ad
|
||||||
|
a 2 bb
|
||||||
|
|
||||||
|
d 30 ab
|
34
hashtest/delete_below_floor.tst.ref
Normal file
34
hashtest/delete_below_floor.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=5>
|
||||||
|
0:30:ad
|
||||||
|
1:0:
|
||||||
|
2:2:ba
|
||||||
|
3:2:bb
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ac
|
||||||
|
</hash>
|
10
hashtest/delete_discont.tst
Normal file
10
hashtest/delete_discont.tst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# delete in a discontinuous block
|
||||||
|
|
||||||
|
a 10 aa
|
||||||
|
a 11 ab
|
||||||
|
a 12 ac
|
||||||
|
a 14 ad
|
||||||
|
a 10 bc
|
||||||
|
|
||||||
|
d 11 ab
|
||||||
|
|
34
hashtest/delete_discont.tst.ref
Normal file
34
hashtest/delete_discont.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=4>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:10:aa
|
||||||
|
11:10:bc
|
||||||
|
12:12:ac
|
||||||
|
13:0:
|
||||||
|
14:14:ad
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
11
hashtest/delete_empty.tst
Normal file
11
hashtest/delete_empty.tst
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Delete an unexisting element. And on an empty hash
|
||||||
|
|
||||||
|
a 10 aa
|
||||||
|
|
||||||
|
d 10 ab
|
||||||
|
d 12 bc
|
||||||
|
|
||||||
|
# Empty for real
|
||||||
|
d 10 aa
|
||||||
|
|
||||||
|
d 10 aa
|
34
hashtest/delete_empty.tst.ref
Normal file
34
hashtest/delete_empty.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=0>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
39
hashtest/delete_full.tst
Normal file
39
hashtest/delete_full.tst
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# delete on a full hash
|
||||||
|
|
||||||
|
# First, fill the hash :-)
|
||||||
|
|
||||||
|
a 0 aa
|
||||||
|
a 1 ab
|
||||||
|
a 2 ac
|
||||||
|
a 3 ad
|
||||||
|
a 4 ae
|
||||||
|
a 5 af
|
||||||
|
a 6 ag
|
||||||
|
a 7 ah
|
||||||
|
a 8 ai
|
||||||
|
a 9 af
|
||||||
|
a 10 ba
|
||||||
|
a 11 bb
|
||||||
|
a 12 bc
|
||||||
|
a 13 bd
|
||||||
|
a 14 be
|
||||||
|
a 15 bf
|
||||||
|
a 16 bg
|
||||||
|
a 17 bh
|
||||||
|
a 18 bi
|
||||||
|
a 19 bj
|
||||||
|
a 20 ca
|
||||||
|
a 21 cb
|
||||||
|
a 22 cd
|
||||||
|
a 23 ce
|
||||||
|
a 24 cf
|
||||||
|
a 25 cg
|
||||||
|
a 26 ch
|
||||||
|
a 27 ci
|
||||||
|
a 28 cj
|
||||||
|
a 29 ck
|
||||||
|
a 30 da
|
||||||
|
a 31 db
|
||||||
|
|
||||||
|
|
||||||
|
d 21 cb
|
34
hashtest/delete_full.tst.ref
Normal file
34
hashtest/delete_full.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=31>
|
||||||
|
0:0:aa
|
||||||
|
1:1:ab
|
||||||
|
2:2:ac
|
||||||
|
3:3:ad
|
||||||
|
4:4:ae
|
||||||
|
5:5:af
|
||||||
|
6:6:ag
|
||||||
|
7:7:ah
|
||||||
|
8:8:ai
|
||||||
|
9:9:af
|
||||||
|
10:10:ba
|
||||||
|
11:11:bb
|
||||||
|
12:12:bc
|
||||||
|
13:13:bd
|
||||||
|
14:14:be
|
||||||
|
15:15:bf
|
||||||
|
16:16:bg
|
||||||
|
17:17:bh
|
||||||
|
18:18:bi
|
||||||
|
19:19:bj
|
||||||
|
20:20:ca
|
||||||
|
21:0:
|
||||||
|
22:22:cd
|
||||||
|
23:23:ce
|
||||||
|
24:24:cf
|
||||||
|
25:25:cg
|
||||||
|
26:26:ch
|
||||||
|
27:27:ci
|
||||||
|
28:28:cj
|
||||||
|
29:29:ck
|
||||||
|
30:30:da
|
||||||
|
31:31:db
|
||||||
|
</hash>
|
10
hashtest/delete_middle.tst
Normal file
10
hashtest/delete_middle.tst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Delete inside a block with something discontinuous
|
||||||
|
|
||||||
|
a 10 aa
|
||||||
|
a 10 ab
|
||||||
|
a 12 ac
|
||||||
|
a 13 ad
|
||||||
|
a 14 ae
|
||||||
|
|
||||||
|
# ab shifts, ac and next doesn't
|
||||||
|
d 10 aa
|
34
hashtest/delete_middle.tst.ref
Normal file
34
hashtest/delete_middle.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=4>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:10:ab
|
||||||
|
11:0:
|
||||||
|
12:12:ac
|
||||||
|
13:13:ad
|
||||||
|
14:14:ae
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
8
hashtest/delete_wrap.tst
Normal file
8
hashtest/delete_wrap.tst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Basic delete when wrapping, between wrap and floor
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 30 ba
|
||||||
|
a 30 bb
|
||||||
|
|
||||||
|
d 30 ac
|
34
hashtest/delete_wrap.tst.ref
Normal file
34
hashtest/delete_wrap.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=4>
|
||||||
|
0:30:ba
|
||||||
|
1:30:bb
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ab
|
||||||
|
</hash>
|
10
hashtest/delete_wrap_at_end.tst
Normal file
10
hashtest/delete_wrap_at_end.tst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Delete inside a block with wrapping, with something after
|
||||||
|
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 1 ad
|
||||||
|
a 3 ae
|
||||||
|
|
||||||
|
# shift ad but not ae
|
||||||
|
d 14 ae
|
34
hashtest/delete_wrap_at_end.tst.ref
Normal file
34
hashtest/delete_wrap_at_end.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=5>
|
||||||
|
0:30:ac
|
||||||
|
1:1:ad
|
||||||
|
2:0:
|
||||||
|
3:3:ae
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ab
|
||||||
|
</hash>
|
8
hashtest/delete_wrap_below_floor.tst
Normal file
8
hashtest/delete_wrap_below_floor.tst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# delete before wrap
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 30 ad
|
||||||
|
|
||||||
|
# shift ac and ad
|
||||||
|
d 30 ab
|
34
hashtest/delete_wrap_below_floor.tst.ref
Normal file
34
hashtest/delete_wrap_below_floor.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=3>
|
||||||
|
0:30:ad
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ac
|
||||||
|
</hash>
|
11
hashtest/delete_wrap_discont.tst
Normal file
11
hashtest/delete_wrap_discont.tst
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Delete with wrapping in discontinuous group
|
||||||
|
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 31 ad
|
||||||
|
a 2 ba
|
||||||
|
a 3 bb
|
||||||
|
|
||||||
|
# shift ac and ad but not ba and bb
|
||||||
|
d 30 ab
|
34
hashtest/delete_wrap_discont.tst.ref
Normal file
34
hashtest/delete_wrap_discont.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=5>
|
||||||
|
0:31:ad
|
||||||
|
1:0:
|
||||||
|
2:2:ba
|
||||||
|
3:3:bb
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ac
|
||||||
|
</hash>
|
BIN
hashtest/htest
Executable file
BIN
hashtest/htest
Executable file
Binary file not shown.
109
hashtest/htest.c
Normal file
109
hashtest/htest.c
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/* Wee testing program from the hash code:
|
||||||
|
* htest <script> <dump>
|
||||||
|
*
|
||||||
|
* scripts are a list of operations:
|
||||||
|
* a $index $string
|
||||||
|
* => add an element at specified index
|
||||||
|
* d $index $string
|
||||||
|
* => remove an element
|
||||||
|
* s $index $string
|
||||||
|
* => prints the actual element index, if it's there
|
||||||
|
*
|
||||||
|
* The hash is dumped to the dump file at each iteration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* tests have been written for a hash that holds 32 items */
|
||||||
|
#define HASH_SIZE 32
|
||||||
|
|
||||||
|
#define STR_LENGTH 16
|
||||||
|
struct hash_item {
|
||||||
|
int wanted_index;
|
||||||
|
char str[STR_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct hash_item* hash_item;
|
||||||
|
|
||||||
|
#include "../hash.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int cmp_item(hash_item item1, hash_item item2)
|
||||||
|
{
|
||||||
|
return strcmp(item1->str, item2->str);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int hash_make_key(hash_item item)
|
||||||
|
{
|
||||||
|
return item->wanted_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void htest_next_key(FILE* f, char* action, int* key, char str[STR_LENGTH])
|
||||||
|
{
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
while ((res != 3) && (res != EOF))
|
||||||
|
res = fscanf(f, "%c %d %s\n", action, key, str);
|
||||||
|
if (res == EOF) exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
hash* h = hash_init(HASH_SIZE, &hash_make_key, &cmp_item);
|
||||||
|
char action;
|
||||||
|
hash_item item;
|
||||||
|
int line = 0;
|
||||||
|
FILE* f;
|
||||||
|
|
||||||
|
if (argc != 3) {
|
||||||
|
fprintf(stderr, "Usage: htest <script file> <dump file>\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
char* script_file = argv[1];
|
||||||
|
char* dump_file = argv[2];
|
||||||
|
f = fopen(argv[1], "r");
|
||||||
|
if (!f) {
|
||||||
|
perror(script_file);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
item= malloc(sizeof(*item));
|
||||||
|
action = ' ';
|
||||||
|
|
||||||
|
line++;
|
||||||
|
htest_next_key(f, &action, &item->wanted_index, item->str);
|
||||||
|
fprintf(stderr, "action %d: %c %d %s\n", line, action, item->wanted_index, item->str);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'a': /* add */
|
||||||
|
fprintf(stderr, "inserting [%s] at %d\n", item->str, item->wanted_index);
|
||||||
|
hash_insert(h, item);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd': /* del */
|
||||||
|
fprintf(stderr, "removing [%s] at %d\n", item->str, item->wanted_index);
|
||||||
|
hash_remove(h, item);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's': /* search */
|
||||||
|
fprintf(stderr, "searching\n");
|
||||||
|
struct hash_item* found = hash_find(h, item);
|
||||||
|
fprintf(stderr, "searching %d[%s]: %p\n", item->wanted_index, item->str, found);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'q': /* quit */
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
hash_dump(h, dump_file);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
6
hashtest/insert.tst
Normal file
6
hashtest/insert.tst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Basic insertions
|
||||||
|
a 10 aa
|
||||||
|
a 10 ab
|
||||||
|
a 10 ac
|
||||||
|
a 20 ba
|
||||||
|
a 21 bb
|
34
hashtest/insert.tst.ref
Normal file
34
hashtest/insert.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=5>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:10:aa
|
||||||
|
11:10:ab
|
||||||
|
12:10:ac
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:20:ba
|
||||||
|
21:21:bb
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
8
hashtest/insert_discont.tst
Normal file
8
hashtest/insert_discont.tst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# insert and bubble with single empty space
|
||||||
|
|
||||||
|
a 10 aa
|
||||||
|
a 11 ab
|
||||||
|
a 12 ac
|
||||||
|
a 14 ad
|
||||||
|
a 10 bc
|
||||||
|
|
34
hashtest/insert_discont.tst.ref
Normal file
34
hashtest/insert_discont.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=5>
|
||||||
|
0:0:
|
||||||
|
1:0:
|
||||||
|
2:0:
|
||||||
|
3:0:
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:10:aa
|
||||||
|
11:10:bc
|
||||||
|
12:11:ab
|
||||||
|
13:12:ac
|
||||||
|
14:14:ad
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:0:
|
||||||
|
31:0:
|
||||||
|
</hash>
|
40
hashtest/insert_full.tst
Normal file
40
hashtest/insert_full.tst
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Insert on a full hash
|
||||||
|
|
||||||
|
# First, fill the hash :-)
|
||||||
|
|
||||||
|
a 0 aa
|
||||||
|
a 1 ab
|
||||||
|
a 2 ac
|
||||||
|
a 3 ad
|
||||||
|
a 4 ae
|
||||||
|
a 5 af
|
||||||
|
a 6 ag
|
||||||
|
a 7 ah
|
||||||
|
a 8 ai
|
||||||
|
a 9 af
|
||||||
|
a 10 ba
|
||||||
|
a 11 bb
|
||||||
|
a 12 bc
|
||||||
|
a 13 bd
|
||||||
|
a 14 be
|
||||||
|
a 15 bf
|
||||||
|
a 16 bg
|
||||||
|
a 17 bh
|
||||||
|
a 18 bi
|
||||||
|
a 19 bj
|
||||||
|
a 20 ca
|
||||||
|
a 21 cb
|
||||||
|
a 22 cd
|
||||||
|
a 23 ce
|
||||||
|
a 24 cf
|
||||||
|
a 25 cg
|
||||||
|
a 26 ch
|
||||||
|
a 27 ci
|
||||||
|
a 28 cj
|
||||||
|
a 29 ck
|
||||||
|
a 30 da
|
||||||
|
a 31 db
|
||||||
|
|
||||||
|
# it's full!
|
||||||
|
a 20 zz
|
||||||
|
a 31 za
|
34
hashtest/insert_full.tst.ref
Normal file
34
hashtest/insert_full.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=32>
|
||||||
|
0:0:aa
|
||||||
|
1:1:ab
|
||||||
|
2:2:ac
|
||||||
|
3:3:ad
|
||||||
|
4:4:ae
|
||||||
|
5:5:af
|
||||||
|
6:6:ag
|
||||||
|
7:7:ah
|
||||||
|
8:8:ai
|
||||||
|
9:9:af
|
||||||
|
10:10:ba
|
||||||
|
11:11:bb
|
||||||
|
12:12:bc
|
||||||
|
13:13:bd
|
||||||
|
14:14:be
|
||||||
|
15:15:bf
|
||||||
|
16:16:bg
|
||||||
|
17:17:bh
|
||||||
|
18:18:bi
|
||||||
|
19:19:bj
|
||||||
|
20:20:ca
|
||||||
|
21:21:cb
|
||||||
|
22:22:cd
|
||||||
|
23:23:ce
|
||||||
|
24:24:cf
|
||||||
|
25:25:cg
|
||||||
|
26:26:ch
|
||||||
|
27:27:ci
|
||||||
|
28:28:cj
|
||||||
|
29:29:ck
|
||||||
|
30:30:da
|
||||||
|
31:31:db
|
||||||
|
</hash>
|
7
hashtest/insert_full_floor.tst
Normal file
7
hashtest/insert_full_floor.tst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# wrap-around and insert at full floor
|
||||||
|
a 2 ba
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 30 ad
|
||||||
|
a 2 bb
|
34
hashtest/insert_full_floor.tst.ref
Normal file
34
hashtest/insert_full_floor.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=6>
|
||||||
|
0:30:ac
|
||||||
|
1:30:ad
|
||||||
|
2:2:ba
|
||||||
|
3:2:bb
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ab
|
||||||
|
</hash>
|
7
hashtest/insert_wrap.tst
Normal file
7
hashtest/insert_wrap.tst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# wrap-around and insert above floor
|
||||||
|
a 30 aa
|
||||||
|
a 30 ab
|
||||||
|
a 30 ac
|
||||||
|
a 30 ad
|
||||||
|
a 0 ba
|
||||||
|
a 0 bb
|
34
hashtest/insert_wrap.tst.ref
Normal file
34
hashtest/insert_wrap.tst.ref
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<hash elem=6>
|
||||||
|
0:30:ac
|
||||||
|
1:30:ad
|
||||||
|
2:0:ba
|
||||||
|
3:0:bb
|
||||||
|
4:0:
|
||||||
|
5:0:
|
||||||
|
6:0:
|
||||||
|
7:0:
|
||||||
|
8:0:
|
||||||
|
9:0:
|
||||||
|
10:0:
|
||||||
|
11:0:
|
||||||
|
12:0:
|
||||||
|
13:0:
|
||||||
|
14:0:
|
||||||
|
15:0:
|
||||||
|
16:0:
|
||||||
|
17:0:
|
||||||
|
18:0:
|
||||||
|
19:0:
|
||||||
|
20:0:
|
||||||
|
21:0:
|
||||||
|
22:0:
|
||||||
|
23:0:
|
||||||
|
24:0:
|
||||||
|
25:0:
|
||||||
|
26:0:
|
||||||
|
27:0:
|
||||||
|
28:0:
|
||||||
|
29:0:
|
||||||
|
30:30:aa
|
||||||
|
31:30:ab
|
||||||
|
</hash>
|
41
hashtest/mkrand.pl
Executable file
41
hashtest/mkrand.pl
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#! /usr/bin/perl
|
||||||
|
|
||||||
|
# Creates a script of random accesses and deletes
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
|
||||||
|
my $i = 0;
|
||||||
|
sub mkstr {
|
||||||
|
$i++;
|
||||||
|
return chr(ord('a') + ($i / 26) % 26) . chr(ord('a') + $i % 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
my @elems;
|
||||||
|
|
||||||
|
|
||||||
|
sub add_elem {
|
||||||
|
my $val = int(rand(32));
|
||||||
|
my $str = mkstr($val);
|
||||||
|
push @elems, "$val $str";
|
||||||
|
print "a $val $str\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub del_elem {
|
||||||
|
my $remove = splice(@elems, rand @elems, 1);
|
||||||
|
print "d $remove\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (@elems < 5) {
|
||||||
|
add_elem;
|
||||||
|
} elsif (@elems > 28) {
|
||||||
|
del_elem;
|
||||||
|
} else {
|
||||||
|
if (rand() < .5) {
|
||||||
|
add_elem;
|
||||||
|
} else {
|
||||||
|
del_elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
hashtest/run
Executable file
30
hashtest/run
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#! /usr/bin/perl -w
|
||||||
|
|
||||||
|
# This runs all the tests.
|
||||||
|
|
||||||
|
# Tests scripts are in *.tst files.
|
||||||
|
# Corresponding output is put in *.out.
|
||||||
|
# Reference output is put in *.ref.
|
||||||
|
# Any discrepancy will be reported!
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
my @res;
|
||||||
|
foreach my $fn (`ls *.tst`) {
|
||||||
|
chomp $fn;
|
||||||
|
my $cmd = "./htest $fn $fn.out";
|
||||||
|
print "$cmd\n";
|
||||||
|
`$cmd`;
|
||||||
|
my $res = system("diff -u $fn.ref $fn.out");
|
||||||
|
push @res, [$fn, ($res == 0 ? "OK" : "*KO*")];
|
||||||
|
}
|
||||||
|
|
||||||
|
format =
|
||||||
|
@<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>
|
||||||
|
$_->[0], $_->[1]
|
||||||
|
.
|
||||||
|
|
||||||
|
|
||||||
|
#format_name STDOUT test_result;
|
||||||
|
map { write; } @res;
|
||||||
|
|
211
log.c
Normal file
211
log.c
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
# log: processing of all outgoing messages
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007-2021 Yves Rutschle
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it
|
||||||
|
# and/or modify it under the terms of the GNU General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be
|
||||||
|
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
# PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# The full text for the General Public License is here:
|
||||||
|
# http://www.gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define SYSLOG_NAMES
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "sslh-conf.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
msg_info msg_config = {
|
||||||
|
LOG_INFO,
|
||||||
|
&cfg.verbose_config
|
||||||
|
};
|
||||||
|
|
||||||
|
msg_info msg_config_error = {
|
||||||
|
LOG_ERR,
|
||||||
|
&cfg.verbose_config_error
|
||||||
|
};
|
||||||
|
|
||||||
|
msg_info msg_fd = {
|
||||||
|
LOG_DEBUG,
|
||||||
|
&cfg.verbose_fd
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
|
||||||
|
msg_info msg_int_error = {
|
||||||
|
LOG_CRIT,
|
||||||
|
&cfg.verbose_system_error
|
||||||
|
};
|
||||||
|
|
||||||
|
/* System errors: when the system around us fails us: memory allocation, fork, ... */
|
||||||
|
msg_info msg_system_error = {
|
||||||
|
LOG_ERR,
|
||||||
|
&cfg.verbose_system_error
|
||||||
|
};
|
||||||
|
|
||||||
|
msg_info msg_packets = {
|
||||||
|
LOG_INFO,
|
||||||
|
&cfg.verbose_packets
|
||||||
|
};
|
||||||
|
|
||||||
|
/* additional info when attempting outgoing connections */
|
||||||
|
msg_info msg_connections_try = {
|
||||||
|
LOG_DEBUG,
|
||||||
|
&cfg.verbose_connections_try
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Connection information and failures (e.g. forbidden by policy) */
|
||||||
|
msg_info msg_connections = {
|
||||||
|
LOG_INFO,
|
||||||
|
&cfg.verbose_connections
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Connection failures, e.g. target server not present */
|
||||||
|
msg_info msg_connections_error = {
|
||||||
|
LOG_ERR,
|
||||||
|
&cfg.verbose_connections_error
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* comment the probing process */
|
||||||
|
msg_info msg_probe_info = {
|
||||||
|
LOG_INFO,
|
||||||
|
&cfg.verbose_probe_info
|
||||||
|
};
|
||||||
|
|
||||||
|
/* probing errors, e.g. inconsistent data in connections */
|
||||||
|
msg_info msg_probe_error = {
|
||||||
|
LOG_ERR,
|
||||||
|
&cfg.verbose_probe_error
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Bitmasks in verbose-* values */
|
||||||
|
#define MSG_STDOUT 1
|
||||||
|
#define MSG_SYSLOG 2
|
||||||
|
#define MSG_FILE 4
|
||||||
|
|
||||||
|
static FILE* logfile_fp = NULL;
|
||||||
|
|
||||||
|
/* Prints a message to stderr and/or syslog if appropriate */
|
||||||
|
void print_message(msg_info info, const char* str, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
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);
|
||||||
|
vsyslog(info.log_level, str, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*info.verbose & MSG_FILE && logfile_fp != NULL) {
|
||||||
|
va_start(ap, str);
|
||||||
|
vfprintf(logfile_fp, str, ap);
|
||||||
|
fflush(logfile_fp);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
|
||||||
|
|
||||||
|
/* Open syslog connection with appropriate banner;
|
||||||
|
* banner is made up of basename(bin_name)+"[pid]" */
|
||||||
|
void setup_syslog(const char* bin_name) {
|
||||||
|
char *name1, *name2;
|
||||||
|
int res, fn;
|
||||||
|
|
||||||
|
if (!strcmp(cfg.syslog_facility, "none")) {
|
||||||
|
do_syslog = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
name1 = strdup(bin_name);
|
||||||
|
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
|
||||||
|
CHECK_RES_DIE(res, "asprintf");
|
||||||
|
|
||||||
|
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
|
||||||
|
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
|
||||||
|
break;
|
||||||
|
if (facilitynames[fn].c_val == -1) {
|
||||||
|
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
|
||||||
|
free(name1);
|
||||||
|
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup_logfile()
|
||||||
|
{
|
||||||
|
if (cfg.logfile == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logfile_fp = fopen(cfg.logfile, "a");
|
||||||
|
if (logfile_fp == NULL)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Could not open logfile %s for writing: %s\n", cfg.logfile, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_logfile()
|
||||||
|
{
|
||||||
|
if (logfile_fp != NULL)
|
||||||
|
{
|
||||||
|
fclose(logfile_fp);
|
||||||
|
logfile_fp = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* syslogs who connected to where
|
||||||
|
* desc: string description of the connection. if NULL, log_connection will
|
||||||
|
* manage on its own
|
||||||
|
* cnx: connection descriptor
|
||||||
|
* */
|
||||||
|
void log_connection(struct connection_desc* desc, const struct connection *cnx)
|
||||||
|
{
|
||||||
|
struct connection_desc d;
|
||||||
|
|
||||||
|
if (cnx->proto->log_level < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!desc) {
|
||||||
|
desc = &d;
|
||||||
|
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",
|
||||||
|
cnx->proto->name,
|
||||||
|
desc->peer,
|
||||||
|
desc->service,
|
||||||
|
desc->local,
|
||||||
|
desc->target);
|
||||||
|
}
|
36
log.h
Normal file
36
log.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#ifndef LOG_H
|
||||||
|
#define LOG_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
void setup_syslog(const char* bin_name);
|
||||||
|
|
||||||
|
void setup_logfile();
|
||||||
|
|
||||||
|
void close_logfile();
|
||||||
|
|
||||||
|
void log_connection(struct connection_desc* desc, const struct connection *cnx);
|
||||||
|
|
||||||
|
typedef struct s_msg_info{
|
||||||
|
int log_level;
|
||||||
|
int *verbose;
|
||||||
|
} msg_info;
|
||||||
|
|
||||||
|
void print_message(msg_info info, const char* str, ...);
|
||||||
|
extern msg_info msg_config;
|
||||||
|
extern msg_info msg_config_error;
|
||||||
|
|
||||||
|
extern msg_info msg_fd;
|
||||||
|
extern msg_info msg_packets;
|
||||||
|
|
||||||
|
extern msg_info msg_int_error;
|
||||||
|
extern msg_info msg_system_error;
|
||||||
|
|
||||||
|
extern msg_info msg_connections_try;
|
||||||
|
extern msg_info msg_connections_error;
|
||||||
|
extern msg_info msg_connections;
|
||||||
|
|
||||||
|
extern msg_info msg_probe_info;
|
||||||
|
extern msg_info msg_probe_error;
|
||||||
|
|
||||||
|
#endif /* LOG_H */
|
180
probe.c
180
probe.c
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
# probe.c: Code for probing protocols
|
# probe.c: Code for probing protocols
|
||||||
#
|
#
|
||||||
# Copyright (C) 2007-2019 Yves Rutschle
|
# Copyright (C) 2007-2021 Yves Rutschle
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it
|
# This program is free software; you can redistribute it
|
||||||
# and/or modify it under the terms of the GNU General Public
|
# and/or modify it under the terms of the GNU General Public
|
||||||
@ -27,11 +27,13 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
|
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
@ -39,6 +41,8 @@ static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
|
|||||||
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
|
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
|
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||||
static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; }
|
static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; }
|
||||||
|
|
||||||
/* Table of protocols that have a built-in probe
|
/* Table of protocols that have a built-in probe
|
||||||
@ -47,6 +51,7 @@ static struct protocol_probe_desc builtins[] = {
|
|||||||
/* description probe */
|
/* description probe */
|
||||||
{ "ssh", is_ssh_protocol},
|
{ "ssh", is_ssh_protocol},
|
||||||
{ "openvpn", is_openvpn_protocol },
|
{ "openvpn", is_openvpn_protocol },
|
||||||
|
{ "wireguard", is_wireguard_protocol },
|
||||||
{ "tinc", is_tinc_protocol },
|
{ "tinc", is_tinc_protocol },
|
||||||
{ "xmpp", is_xmpp_protocol },
|
{ "xmpp", is_xmpp_protocol },
|
||||||
{ "http", is_http_protocol },
|
{ "http", is_http_protocol },
|
||||||
@ -54,6 +59,8 @@ static struct protocol_probe_desc builtins[] = {
|
|||||||
{ "adb", is_adb_protocol },
|
{ "adb", is_adb_protocol },
|
||||||
{ "socks5", is_socks5_protocol },
|
{ "socks5", is_socks5_protocol },
|
||||||
{ "syslog", is_syslog_protocol },
|
{ "syslog", is_syslog_protocol },
|
||||||
|
{ "teamspeak", is_teamspeak_protocol },
|
||||||
|
{ "msrdp", is_msrdp_protocol },
|
||||||
{ "anyprot", is_true }
|
{ "anyprot", is_true }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,33 +88,38 @@ struct sslhcfg_protocols_item* timeout_protocol(void)
|
|||||||
|
|
||||||
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
|
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
|
||||||
#define HEXDUMP_COLS 16
|
#define HEXDUMP_COLS 16
|
||||||
void hexdump(const char *mem, unsigned int len)
|
void hexdump(msg_info msg_info, const char *mem, unsigned int len)
|
||||||
{
|
{
|
||||||
unsigned int i, j;
|
unsigned int i, j;
|
||||||
|
char str[10 + HEXDUMP_COLS * 4 + 2];
|
||||||
|
int c = 0; /* index in str */
|
||||||
|
|
||||||
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
|
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
|
||||||
{
|
{
|
||||||
/* print offset */
|
/* print offset */
|
||||||
if(i % HEXDUMP_COLS == 0)
|
if(i % HEXDUMP_COLS == 0)
|
||||||
fprintf(stderr, "0x%06x: ", i);
|
c += sprintf(&str[c], "0x%06x: ", i);
|
||||||
|
|
||||||
/* print hex data */
|
/* print hex data */
|
||||||
if(i < len)
|
if(i < len)
|
||||||
fprintf(stderr, "%02x ", 0xFF & mem[i]);
|
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]);
|
||||||
else /* end of block, just aligning for ASCII dump */
|
else /* end of block, just aligning for ASCII dump */
|
||||||
fprintf(stderr, " ");
|
c+= sprintf(&str[c], " ");
|
||||||
|
|
||||||
/* print ASCII dump */
|
/* print ASCII dump */
|
||||||
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
||||||
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
|
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
|
||||||
if(j >= len) /* end of block, not really printing */
|
if(j >= len) /* end of block, not really printing */
|
||||||
fputc(' ', stderr);
|
str[c++] = ' ';
|
||||||
else if(isprint(mem[j])) /* printable char */
|
else if(isprint(mem[j])) /* printable char */
|
||||||
fputc(0xFF & mem[j], stderr);
|
str[c++] = 0xFF & mem[j];
|
||||||
else /* other char */
|
else /* other char */
|
||||||
fputc('.', stderr);
|
str[c++] = '.';
|
||||||
}
|
}
|
||||||
fputc('\n', stderr);
|
str[c++] = '\n';
|
||||||
|
str[c++] = 0;
|
||||||
|
print_message(msg_info, "%s", str);
|
||||||
|
c = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,15 +143,66 @@ static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_
|
|||||||
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
|
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
|
||||||
* and OpenVPN ssl.c, ssl.h and options.c
|
* and OpenVPN ssl.c, ssl.h and options.c
|
||||||
*/
|
*/
|
||||||
|
#define OVPN_OPCODE_MASK 0xF8
|
||||||
|
#define OVPN_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3)
|
||||||
|
#define OVPN_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3)
|
||||||
|
#define OVPN_HMAC_128 16
|
||||||
|
#define OVPN_HMAC_160 20
|
||||||
|
#define OVPN_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
|
||||||
static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto)
|
static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||||
{
|
{
|
||||||
int packet_len;
|
int packet_len;
|
||||||
|
|
||||||
if (len < 2)
|
if (proto->is_udp == 0)
|
||||||
return PROBE_AGAIN;
|
{
|
||||||
|
if (len < 2)
|
||||||
|
return PROBE_AGAIN;
|
||||||
|
|
||||||
packet_len = ntohs(*(uint16_t*)p);
|
packet_len = ntohs(*(uint16_t*)p);
|
||||||
return packet_len == len - 2;
|
return packet_len == len - 2;
|
||||||
|
} else {
|
||||||
|
if (len < 1)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 &&
|
||||||
|
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
/* The detection pattern above may not be reliable enough.
|
||||||
|
* Check the packet id: OpenVPN sents five initial packets
|
||||||
|
* whereas the packet id is increased with every transmitted datagram.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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) + sizeof(uint32_t))
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u)
|
||||||
|
return PROBE_MATCH;
|
||||||
|
|
||||||
|
return PROBE_NEXT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||||
|
{
|
||||||
|
if (proto->is_udp == 0)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
// Handshake Init: 148 bytes
|
||||||
|
if (len != 148)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
|
||||||
|
if (ntohl(*(uint32_t*)p) != 0x01000000)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
return PROBE_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the buffer the beginning of a tinc connections?
|
/* Is the buffer the beginning of a tinc connections?
|
||||||
@ -312,6 +375,27 @@ static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protoco
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||||
|
{
|
||||||
|
if (len < 8)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
|
||||||
|
return !strncmp(p, "TS3INIT1", len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||||
|
{
|
||||||
|
char version;
|
||||||
|
char packet_len;
|
||||||
|
if (len < 7)
|
||||||
|
return PROBE_NEXT;
|
||||||
|
version=*p;
|
||||||
|
if (version!=0x03)
|
||||||
|
return 0;
|
||||||
|
packet_len = ntohs(*(uint16_t*)(p+2));
|
||||||
|
return packet_len == len;
|
||||||
|
}
|
||||||
|
|
||||||
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_REGEX
|
#ifdef ENABLE_REGEX
|
||||||
@ -328,51 +412,58 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item
|
|||||||
return 0;
|
return 0;
|
||||||
#else
|
#else
|
||||||
/* Should never happen as we check when loading config file */
|
/* Should never happen as we check when loading config file */
|
||||||
fprintf(stderr, "FATAL: regex probe called but not built in\n");
|
print_message(msg_int_error, "FATAL: regex probe called but not built in\n");
|
||||||
exit(5);
|
exit(5);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Run all the probes on a buffer
|
/* Run all the probes on a buffer
|
||||||
|
* buf, len: buffer to test on
|
||||||
|
* proto_in, proto_len: array of protocols to try
|
||||||
|
* proto_out: protocol that matched
|
||||||
|
*
|
||||||
* Returns
|
* Returns
|
||||||
* PROBE_AGAIN if not enough data, and set *proto to NULL
|
* PROBE_AGAIN if not enough data, and set *proto to NULL
|
||||||
* PROBE_MATCH if protocol is identified, in which case *proto is set to
|
* PROBE_MATCH if protocol is identified, in which case *proto is set to
|
||||||
* point to the appropriate protocol
|
* point to the appropriate protocol
|
||||||
* */
|
* */
|
||||||
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
|
int probe_buffer(char* buf, int len,
|
||||||
|
struct sslhcfg_protocols_item** proto_in,
|
||||||
|
int proto_len,
|
||||||
|
struct sslhcfg_protocols_item** proto_out
|
||||||
|
)
|
||||||
{
|
{
|
||||||
struct sslhcfg_protocols_item* p;
|
struct sslhcfg_protocols_item* p;
|
||||||
int i, res, again = 0;
|
int i, res, again = 0;
|
||||||
|
|
||||||
if (cfg.verbose > 1) {
|
print_message(msg_packets, "hexdump of incoming packet:\n");
|
||||||
fprintf(stderr, "hexdump of incoming packet:\n");
|
hexdump(msg_packets, buf, len);
|
||||||
hexdump(buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
*proto = NULL;
|
*proto_out = NULL;
|
||||||
for (i = 0; i < cfg.protocols_len; i++) {
|
for (i = 0; i < proto_len; i++) {
|
||||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||||
p = &cfg.protocols[i];
|
p = proto_in[i];
|
||||||
|
|
||||||
if (! p->probe) continue;
|
if (! p->probe) continue;
|
||||||
|
|
||||||
if (cfg.verbose) fprintf(stderr, "probing for %s\n", p->name);
|
print_message(msg_probe_info, "probing for %s\n", p->name);
|
||||||
|
|
||||||
/* Don't probe last protocol if it is anyprot (and store last protocol) */
|
/* Don't probe last protocol if it is anyprot (and store last protocol) */
|
||||||
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot")))
|
if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (p->minlength_is_present && (len < p->minlength )) {
|
if (p->minlength_is_present && (len < p->minlength )) {
|
||||||
fprintf(stderr, "input too short, %d bytes but need %d\n", len , p->minlength);
|
print_message(msg_probe_info, "input too short, %d bytes but need %d\n",
|
||||||
|
len , p->minlength);
|
||||||
again++;
|
again++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = p->probe(buf, len, p);
|
res = p->probe(buf, len, p);
|
||||||
if (cfg.verbose) fprintf(stderr, "probed for %s: %s\n", p->name, probe_str[res]);
|
print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]);
|
||||||
|
|
||||||
if (res == PROBE_MATCH) {
|
if (res == PROBE_MATCH) {
|
||||||
*proto = p;
|
*proto_out = p;
|
||||||
return PROBE_MATCH;
|
return PROBE_MATCH;
|
||||||
}
|
}
|
||||||
if (res == PROBE_AGAIN)
|
if (res == PROBE_AGAIN)
|
||||||
@ -382,37 +473,14 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
|
|||||||
return PROBE_AGAIN;
|
return PROBE_AGAIN;
|
||||||
|
|
||||||
/* Everything failed: match the last one */
|
/* Everything failed: match the last one */
|
||||||
*proto = &cfg.protocols[cfg.protocols_len-1];
|
|
||||||
return PROBE_MATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
if (proto_len == 0) {
|
||||||
* Read the beginning of data coming from the client connection and check if
|
/* This should be caught by configuration sanity checks, but just in
|
||||||
* it's a known protocol.
|
* case, die gracefully rather than segfaulting */
|
||||||
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
|
print_message(msg_int_error, "Received traffic on transport that has no target\n");
|
||||||
* which case cnx->proto is set to the appropriate protocol.
|
exit(0);
|
||||||
*/
|
|
||||||
int probe_client_protocol(struct connection *cnx)
|
|
||||||
{
|
|
||||||
char buffer[BUFSIZ];
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
|
|
||||||
/* It's possible that read() returns an error, e.g. if the client
|
|
||||||
* disconnected between the previous call to select() and now. If that
|
|
||||||
* happens, we just connect to the default protocol so the caller of this
|
|
||||||
* function does not have to deal with a specific failure condition (the
|
|
||||||
* connection will just fail later normally). */
|
|
||||||
|
|
||||||
if (n > 0) {
|
|
||||||
defer_write(&cnx->q[1], buffer, n);
|
|
||||||
return probe_buffer(cnx->q[1].begin_deferred_data,
|
|
||||||
cnx->q[1].deferred_data_size,
|
|
||||||
&cnx->proto);
|
|
||||||
}
|
}
|
||||||
|
*proto_out = proto_in[proto_len-1];
|
||||||
/* read() returned an error, so just connect to the last protocol to die */
|
|
||||||
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
|
|
||||||
return PROBE_MATCH;
|
return PROBE_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
probe.h
11
probe.h
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
||||||
@ -47,8 +48,12 @@ void set_protocol_list(struct sslhcfg_protocols_item*);
|
|||||||
*/
|
*/
|
||||||
int probe_client_protocol(struct connection *cnx);
|
int probe_client_protocol(struct connection *cnx);
|
||||||
|
|
||||||
/* Probe, but on a buffer */
|
/* Probe on a buffer */
|
||||||
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto);
|
int probe_buffer(char* buf, int len,
|
||||||
|
struct sslhcfg_protocols_item** proto_in,
|
||||||
|
int proto_len,
|
||||||
|
struct sslhcfg_protocols_item** proto_out
|
||||||
|
);
|
||||||
|
|
||||||
/* set the protocol to connect to in case of timeout */
|
/* set the protocol to connect to in case of timeout */
|
||||||
void set_ontimeout(const char* name);
|
void set_ontimeout(const char* name);
|
||||||
@ -59,6 +64,6 @@ void set_ontimeout(const char* name);
|
|||||||
*/
|
*/
|
||||||
struct sslhcfg_protocols_item* timeout_protocol(void);
|
struct sslhcfg_protocols_item* timeout_protocol(void);
|
||||||
|
|
||||||
void hexdump(const char*, unsigned int);
|
void hexdump(msg_info, const char*, unsigned int);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
115
processes.c
Normal file
115
processes.c
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
Processes that are common to sslh-ev and sslh-select
|
||||||
|
|
||||||
|
# Copyright (C) 2021 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 "udp-listener.h"
|
||||||
|
#include "tcp-listener.h"
|
||||||
|
#include "processes.h"
|
||||||
|
#include "probe.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
|
||||||
|
int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
if (cnx->q[i].fd != -1) {
|
||||||
|
print_message(msg_fd, "closing fd %d\n", cnx->q[i].fd);
|
||||||
|
|
||||||
|
watchers_del_read(fd_info->watchers, cnx->q[i].fd);
|
||||||
|
watchers_del_write(fd_info->watchers, cnx->q[i].fd);
|
||||||
|
close(cnx->q[i].fd);
|
||||||
|
if (cnx->q[i].deferred_data)
|
||||||
|
free(cnx->q[i].deferred_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cnx->type == SOCK_DGRAM)
|
||||||
|
udp_tidy(cnx, fd_info);
|
||||||
|
|
||||||
|
if (gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing) != -1)
|
||||||
|
fd_info->num_probing--;
|
||||||
|
|
||||||
|
collection_remove_cnx(fd_info->collection, cnx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Process a connection that is active in read */
|
||||||
|
|
||||||
|
void cnx_read_process(struct loop_info* fd_info, int fd)
|
||||||
|
{
|
||||||
|
cnx_collection* collection = fd_info->collection;
|
||||||
|
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
||||||
|
switch (cnx->type) {
|
||||||
|
case SOCK_STREAM:
|
||||||
|
tcp_read_process(fd_info, fd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOCK_DGRAM:
|
||||||
|
udp_s2c_forward(cnx);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
print_message(msg_int_error, "cnx_read_process: Illegal connection type %d\n", cnx->type);
|
||||||
|
dump_connection(cnx);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Process a connection that accepts a socket
|
||||||
|
* (For UDP, this means all traffic coming from remote clients)
|
||||||
|
* Returns new connection object, or NULL
|
||||||
|
* */
|
||||||
|
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket)
|
||||||
|
{
|
||||||
|
int fd = listen_socket->socketfd;
|
||||||
|
int type = listen_socket->type;
|
||||||
|
struct connection* cnx;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case SOCK_STREAM:
|
||||||
|
cnx = accept_new_connection(fd, fd_info);
|
||||||
|
if (!cnx) return NULL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOCK_DGRAM:
|
||||||
|
cnx = udp_c2s_forward(fd, fd_info);
|
||||||
|
if (!cnx) return NULL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
print_message(msg_int_error, "Inconsistent cnx type: %d\n", type);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_fd = cnx->q[0].fd;
|
||||||
|
watchers_add_read(fd_info->watchers, new_fd);
|
||||||
|
return cnx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
41
processes.h
Normal file
41
processes.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef PROCESSES_H
|
||||||
|
#define PROCESSES_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "collection.h"
|
||||||
|
#include "gap.h"
|
||||||
|
|
||||||
|
typedef struct connection* hash_item;
|
||||||
|
#include "hash.h"
|
||||||
|
|
||||||
|
/* Provided by event loop, sslh-ev or sslh-select, for implementation-dependant
|
||||||
|
* data */
|
||||||
|
typedef struct watchers watchers;
|
||||||
|
|
||||||
|
/* Global state for a loop */
|
||||||
|
struct loop_info {
|
||||||
|
int num_probing; /* Number of connections currently probing
|
||||||
|
* We use this to know if we need to time out of
|
||||||
|
* select() */
|
||||||
|
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
|
||||||
|
|
||||||
|
hash* hash_sources; /* UDP remote sources previously encountered */
|
||||||
|
|
||||||
|
watchers* watchers;
|
||||||
|
|
||||||
|
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||||
|
};
|
||||||
|
|
||||||
|
void cnx_read_process(struct loop_info* fd_info, int fd);
|
||||||
|
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket);
|
||||||
|
|
||||||
|
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
|
||||||
|
|
||||||
|
|
||||||
|
/* These must be declared in the loop handler, sslh-ev or sslh-select */
|
||||||
|
void watchers_add_read(watchers* w, int fd);
|
||||||
|
void watchers_del_read(watchers* w, int fd);
|
||||||
|
void watchers_add_write(watchers* w, int fd);
|
||||||
|
void watchers_del_write(watchers* w, int fd);
|
||||||
|
|
||||||
|
#endif
|
@ -18,7 +18,7 @@
|
|||||||
#CONFIG=/etc/sslh.cfg
|
#CONFIG=/etc/sslh.cfg
|
||||||
|
|
||||||
#
|
#
|
||||||
# Extra option to pass on comand line
|
# Extra option to pass on command line
|
||||||
# Those can supersede configuration file settings
|
# Those can supersede configuration file settings
|
||||||
#
|
#
|
||||||
#OPTIONS=
|
#OPTIONS=
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
# but many connection attempts from the same
|
# but many connection attempts from the same
|
||||||
# origin is reason enough to block.
|
# origin is reason enough to block.
|
||||||
#
|
#
|
||||||
# Verion: 2014-03-28
|
# Version: 2014-03-28
|
||||||
|
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
failregex = ^.+ sslh\[.+\]: connection from <HOST>:.+ to .+ forwarded
|
failregex = ^.+ sslh\[.+\]: ssh:connection from <HOST>:.+ to .+ forwarded
|
||||||
from .+ to .+:ssh\s*$
|
from .+ to .+:ssh\s*$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
405
sslh-conf.c
405
sslh-conf.c
@ -1,5 +1,5 @@
|
|||||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||||
* on Fri Aug 13 18:03:18 2021.
|
* on Sun Sep 11 21:43:25 2022.
|
||||||
|
|
||||||
# conf2struct: generate libconf parsers that read to structs
|
# conf2struct: generate libconf parsers that read to structs
|
||||||
# Copyright (C) 2018-2021 Yves Rutschle
|
# Copyright (C) 2018-2021 Yves Rutschle
|
||||||
@ -365,7 +365,7 @@ static int clcpy(config_type type, void* target, const void* cl_arg)
|
|||||||
return 0;
|
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
|
* location that must be large enough, converting on the way
|
||||||
* (i.e. CFG_INT gets atoi() and so on) */
|
* (i.e. CFG_INT gets atoi() and so on) */
|
||||||
/* 0: success
|
/* 0: success
|
||||||
@ -443,16 +443,29 @@ struct compound_cl_arg {
|
|||||||
|
|
||||||
|
|
||||||
struct arg_file* sslhcfg_conffile;
|
struct arg_file* sslhcfg_conffile;
|
||||||
struct arg_int* sslhcfg_verbose;
|
struct arg_int* sslhcfg_verbose_config;
|
||||||
|
struct arg_int* sslhcfg_verbose_config_error;
|
||||||
|
struct arg_int* sslhcfg_verbose_connections;
|
||||||
|
struct arg_int* sslhcfg_verbose_connections_try;
|
||||||
|
struct arg_int* sslhcfg_verbose_connections_error;
|
||||||
|
struct arg_int* sslhcfg_verbose_fd;
|
||||||
|
struct arg_int* sslhcfg_verbose_packets;
|
||||||
|
struct arg_int* sslhcfg_verbose_probe_info;
|
||||||
|
struct arg_int* sslhcfg_verbose_probe_error;
|
||||||
|
struct arg_int* sslhcfg_verbose_system_error;
|
||||||
|
struct arg_int* sslhcfg_verbose_int_error;
|
||||||
|
struct arg_lit* sslhcfg_version;
|
||||||
struct arg_lit* sslhcfg_foreground;
|
struct arg_lit* sslhcfg_foreground;
|
||||||
struct arg_lit* sslhcfg_inetd;
|
struct arg_lit* sslhcfg_inetd;
|
||||||
struct arg_lit* sslhcfg_numeric;
|
struct arg_lit* sslhcfg_numeric;
|
||||||
struct arg_lit* sslhcfg_transparent;
|
struct arg_lit* sslhcfg_transparent;
|
||||||
struct arg_int* sslhcfg_timeout;
|
struct arg_int* sslhcfg_timeout;
|
||||||
|
struct arg_int* sslhcfg_udp_max_connections;
|
||||||
struct arg_str* sslhcfg_user;
|
struct arg_str* sslhcfg_user;
|
||||||
struct arg_str* sslhcfg_pidfile;
|
struct arg_str* sslhcfg_pidfile;
|
||||||
struct arg_str* sslhcfg_chroot;
|
struct arg_str* sslhcfg_chroot;
|
||||||
struct arg_str* sslhcfg_syslog_facility;
|
struct arg_str* sslhcfg_syslog_facility;
|
||||||
|
struct arg_str* sslhcfg_logfile;
|
||||||
struct arg_str* sslhcfg_on_timeout;
|
struct arg_str* sslhcfg_on_timeout;
|
||||||
struct arg_str* sslhcfg_prefix;
|
struct arg_str* sslhcfg_prefix;
|
||||||
struct arg_str* sslhcfg_listen;
|
struct arg_str* sslhcfg_listen;
|
||||||
@ -460,15 +473,17 @@ struct arg_file* sslhcfg_conffile;
|
|||||||
struct arg_str* sslhcfg_tls;
|
struct arg_str* sslhcfg_tls;
|
||||||
struct arg_str* sslhcfg_openvpn;
|
struct arg_str* sslhcfg_openvpn;
|
||||||
struct arg_str* sslhcfg_tinc;
|
struct arg_str* sslhcfg_tinc;
|
||||||
|
struct arg_str* sslhcfg_wireguard;
|
||||||
struct arg_str* sslhcfg_xmpp;
|
struct arg_str* sslhcfg_xmpp;
|
||||||
struct arg_str* sslhcfg_http;
|
struct arg_str* sslhcfg_http;
|
||||||
struct arg_str* sslhcfg_adb;
|
struct arg_str* sslhcfg_adb;
|
||||||
struct arg_str* sslhcfg_socks5;
|
struct arg_str* sslhcfg_socks5;
|
||||||
struct arg_str* sslhcfg_syslog;
|
struct arg_str* sslhcfg_syslog;
|
||||||
|
struct arg_str* sslhcfg_msrdp;
|
||||||
struct arg_str* sslhcfg_anyprot;
|
struct arg_str* sslhcfg_anyprot;
|
||||||
struct arg_end* sslhcfg_end;
|
struct arg_end* sslhcfg_end;
|
||||||
|
|
||||||
|
|
||||||
static struct config_desc table_sslhcfg_protocols[] = {
|
static struct config_desc table_sslhcfg_protocols[] = {
|
||||||
|
|
||||||
|
|
||||||
@ -616,6 +631,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
|||||||
/* default_val*/ .default_val.def_bool = 0
|
/* default_val*/ .default_val.def_bool = 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "resolve_on_forward",
|
||||||
|
/* type */ CFG_BOOL,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ NULL,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_protocols_item, resolve_on_forward),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_bool = 0
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
/* name */ "log_level",
|
/* name */ "log_level",
|
||||||
/* type */ CFG_INT,
|
/* type */ CFG_INT,
|
||||||
@ -782,17 +813,17 @@ static struct config_desc table_sslhcfg_listen[] = {
|
|||||||
},
|
},
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct config_desc table_sslhcfg[] = {
|
static struct config_desc table_sslhcfg[] = {
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* name */ "verbose",
|
/* name */ "verbose_config",
|
||||||
/* type */ CFG_INT,
|
/* type */ CFG_INT,
|
||||||
/* sub_group*/ NULL,
|
/* sub_group*/ NULL,
|
||||||
/* arg_cl */ & sslhcfg_verbose,
|
/* arg_cl */ & sslhcfg_verbose_config,
|
||||||
/* base_addr */ NULL,
|
/* base_addr */ NULL,
|
||||||
/* offset */ offsetof(struct sslhcfg_item, verbose),
|
/* offset */ offsetof(struct sslhcfg_item, verbose_config),
|
||||||
/* offset_len */ 0,
|
/* offset_len */ 0,
|
||||||
/* offset_present */ 0,
|
/* offset_present */ 0,
|
||||||
/* size */ sizeof(int),
|
/* size */ sizeof(int),
|
||||||
@ -802,6 +833,182 @@ static struct config_desc table_sslhcfg[] = {
|
|||||||
/* default_val*/ .default_val.def_int = 0
|
/* default_val*/ .default_val.def_int = 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_config_error",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_config_error,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_config_error),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_connections",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_connections,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_connections),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_connections_try",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_connections_try,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_connections_try),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_connections_error",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_connections_error,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_connections_error),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_fd",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_fd,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_fd),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_packets",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_packets,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_packets),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_probe_info",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_probe_info,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_probe_info),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_probe_error",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_probe_error,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_probe_error),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_system_error",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_system_error,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_system_error),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "verbose_int_error",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_verbose_int_error,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, verbose_int_error),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 3
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "version",
|
||||||
|
/* type */ CFG_BOOL,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_version,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, version),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_bool = 0
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
/* name */ "foreground",
|
/* name */ "foreground",
|
||||||
/* type */ CFG_BOOL,
|
/* type */ CFG_BOOL,
|
||||||
@ -882,6 +1089,22 @@ static struct config_desc table_sslhcfg[] = {
|
|||||||
/* default_val*/ .default_val.def_int = 5
|
/* default_val*/ .default_val.def_int = 5
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "udp_max_connections",
|
||||||
|
/* type */ CFG_INT,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_udp_max_connections,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, udp_max_connections),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ 0,
|
||||||
|
/* size */ sizeof(int),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 0,
|
||||||
|
/* default_val*/ .default_val.def_int = 1024
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
/* name */ "user",
|
/* name */ "user",
|
||||||
/* type */ CFG_STRING,
|
/* type */ CFG_STRING,
|
||||||
@ -946,6 +1169,22 @@ static struct config_desc table_sslhcfg[] = {
|
|||||||
/* default_val*/ .default_val.def_string = "auth"
|
/* default_val*/ .default_val.def_string = "auth"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
/* name */ "logfile",
|
||||||
|
/* type */ CFG_STRING,
|
||||||
|
/* sub_group*/ NULL,
|
||||||
|
/* arg_cl */ & sslhcfg_logfile,
|
||||||
|
/* base_addr */ NULL,
|
||||||
|
/* offset */ offsetof(struct sslhcfg_item, logfile),
|
||||||
|
/* offset_len */ 0,
|
||||||
|
/* offset_present */ offsetof(struct sslhcfg_item, logfile_is_present),
|
||||||
|
/* size */ sizeof(char*),
|
||||||
|
/* array_type */ -1,
|
||||||
|
/* mandatory */ 0,
|
||||||
|
/* optional */ 1,
|
||||||
|
/* default_val*/ .default_val.def_string = NULL
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
/* name */ "on_timeout",
|
/* name */ "on_timeout",
|
||||||
/* type */ CFG_STRING,
|
/* type */ CFG_STRING,
|
||||||
@ -1015,7 +1254,15 @@ static struct compound_cl_target sslhcfg_anyprot_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct compound_cl_target sslhcfg_msrdp_targets [] = {
|
||||||
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "msrdp" },
|
||||||
|
{ & 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 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1023,7 +1270,7 @@ static struct compound_cl_target sslhcfg_syslog_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1031,7 +1278,7 @@ static struct compound_cl_target sslhcfg_socks5_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1039,7 +1286,7 @@ static struct compound_cl_target sslhcfg_adb_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1047,7 +1294,7 @@ static struct compound_cl_target sslhcfg_http_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "http" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "http" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1055,7 +1302,16 @@ static struct compound_cl_target sslhcfg_xmpp_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct compound_cl_target sslhcfg_wireguard_targets [] = {
|
||||||
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "wireguard" },
|
||||||
|
{ & 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 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1063,7 +1319,7 @@ static struct compound_cl_target sslhcfg_tinc_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
@ -1072,7 +1328,7 @@ static struct compound_cl_target sslhcfg_openvpn_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
@ -1081,7 +1337,7 @@ static struct compound_cl_target sslhcfg_tls_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
@ -1091,7 +1347,7 @@ static struct compound_cl_target sslhcfg_ssh_targets [] = {
|
|||||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
|
||||||
{ & table_sslhcfg_protocols[6], 0, .value.def_bool = 1 },
|
{ & table_sslhcfg_protocols[6], 0, .value.def_bool = 1 },
|
||||||
{ & table_sslhcfg_protocols[9], 0, .value.def_int = 1 },
|
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||||
{ 0 }
|
{ 0 }
|
||||||
};
|
};
|
||||||
@ -1106,7 +1362,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: listen */
|
{ /* arg: listen */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_listen,
|
.arg_cl = & sslhcfg_listen,
|
||||||
.base_entry = & table_sslhcfg [12],
|
.base_entry = & table_sslhcfg [25],
|
||||||
.targets = sslhcfg_listen_targets,
|
.targets = sslhcfg_listen_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1118,7 +1374,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: ssh */
|
{ /* arg: ssh */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_ssh,
|
.arg_cl = & sslhcfg_ssh,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_ssh_targets,
|
.targets = sslhcfg_ssh_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1130,7 +1386,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: tls */
|
{ /* arg: tls */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_tls,
|
.arg_cl = & sslhcfg_tls,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_tls_targets,
|
.targets = sslhcfg_tls_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1142,7 +1398,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: openvpn */
|
{ /* arg: openvpn */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_openvpn,
|
.arg_cl = & sslhcfg_openvpn,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_openvpn_targets,
|
.targets = sslhcfg_openvpn_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1154,7 +1410,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: tinc */
|
{ /* arg: tinc */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_tinc,
|
.arg_cl = & sslhcfg_tinc,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_tinc_targets,
|
.targets = sslhcfg_tinc_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1163,10 +1419,22 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
.override_const = "tinc",
|
.override_const = "tinc",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ /* arg: wireguard */
|
||||||
|
.regex = "(.+):(\\w+)",
|
||||||
|
.arg_cl = & sslhcfg_wireguard,
|
||||||
|
.base_entry = & table_sslhcfg [26],
|
||||||
|
.targets = sslhcfg_wireguard_targets,
|
||||||
|
|
||||||
|
|
||||||
|
.override_desc = & table_sslhcfg_protocols [0],
|
||||||
|
.override_matchindex = 0,
|
||||||
|
.override_const = "wireguard",
|
||||||
|
},
|
||||||
|
|
||||||
{ /* arg: xmpp */
|
{ /* arg: xmpp */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_xmpp,
|
.arg_cl = & sslhcfg_xmpp,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_xmpp_targets,
|
.targets = sslhcfg_xmpp_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1178,7 +1446,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: http */
|
{ /* arg: http */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_http,
|
.arg_cl = & sslhcfg_http,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_http_targets,
|
.targets = sslhcfg_http_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1190,7 +1458,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: adb */
|
{ /* arg: adb */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_adb,
|
.arg_cl = & sslhcfg_adb,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_adb_targets,
|
.targets = sslhcfg_adb_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1202,7 +1470,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: socks5 */
|
{ /* arg: socks5 */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_socks5,
|
.arg_cl = & sslhcfg_socks5,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_socks5_targets,
|
.targets = sslhcfg_socks5_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1214,7 +1482,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
{ /* arg: syslog */
|
{ /* arg: syslog */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_syslog,
|
.arg_cl = & sslhcfg_syslog,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_syslog_targets,
|
.targets = sslhcfg_syslog_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1223,10 +1491,22 @@ static struct compound_cl_arg compound_cl_args[] = {
|
|||||||
.override_const = "syslog",
|
.override_const = "syslog",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ /* arg: msrdp */
|
||||||
|
.regex = "(.+):(\\w+)",
|
||||||
|
.arg_cl = & sslhcfg_msrdp,
|
||||||
|
.base_entry = & table_sslhcfg [26],
|
||||||
|
.targets = sslhcfg_msrdp_targets,
|
||||||
|
|
||||||
|
|
||||||
|
.override_desc = & table_sslhcfg_protocols [0],
|
||||||
|
.override_matchindex = 0,
|
||||||
|
.override_const = "msrdp",
|
||||||
|
},
|
||||||
|
|
||||||
{ /* arg: anyprot */
|
{ /* arg: anyprot */
|
||||||
.regex = "(.+):(\\w+)",
|
.regex = "(.+):(\\w+)",
|
||||||
.arg_cl = & sslhcfg_anyprot,
|
.arg_cl = & sslhcfg_anyprot,
|
||||||
.base_entry = & table_sslhcfg [13],
|
.base_entry = & table_sslhcfg [26],
|
||||||
.targets = sslhcfg_anyprot_targets,
|
.targets = sslhcfg_anyprot_targets,
|
||||||
|
|
||||||
|
|
||||||
@ -1538,7 +1818,7 @@ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, con
|
|||||||
if (pmatch[pmatch_cnt].rm_so == -1) {
|
if (pmatch[pmatch_cnt].rm_so == -1) {
|
||||||
/* This should not happen as regexec() did
|
/* This should not happen as regexec() did
|
||||||
* match before, unless there is a
|
* match before, unless there is a
|
||||||
* discrepency between the regex and the
|
* discrepancy between the regex and the
|
||||||
* number of backreferences */
|
* number of backreferences */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1831,7 +2111,7 @@ static void scalar_to_string(char** strp, config_setting_t* s)
|
|||||||
|
|
||||||
/* Typesets all the settings in a configuration as a
|
/* Typesets all the settings in a configuration as a
|
||||||
* newly-allocated string. The string management is caller's
|
* newly-allocated string. The string management is caller's
|
||||||
* responsability.
|
* responsibility.
|
||||||
* Returns the number of scalars in the configuration */
|
* Returns the number of scalars in the configuration */
|
||||||
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
|
||||||
{
|
{
|
||||||
@ -1891,16 +2171,29 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
|||||||
#ifdef LIBCONFIG
|
#ifdef LIBCONFIG
|
||||||
sslhcfg_conffile = arg_filen("F", "config", "<file>", 0, 1, "Specify configuration file"),
|
sslhcfg_conffile = arg_filen("F", "config", "<file>", 0, 1, "Specify configuration file"),
|
||||||
#endif
|
#endif
|
||||||
sslhcfg_verbose = arg_intn("v", "verbose", "<n>", 0, 1, ""),
|
sslhcfg_verbose_config = arg_intn(NULL, "verbose-config", "<n>", 0, 1, "Print configuration at startup"),
|
||||||
|
sslhcfg_verbose_config_error = arg_intn(NULL, "verbose-config-error", "<n>", 0, 1, "Print configuration errors"),
|
||||||
|
sslhcfg_verbose_connections = arg_intn(NULL, "verbose-connections", "<n>", 0, 1, "Trace established incoming address to forward address"),
|
||||||
|
sslhcfg_verbose_connections_try = arg_intn(NULL, "verbose-connections-try", "<n>", 0, 1, "Connection errors"),
|
||||||
|
sslhcfg_verbose_connections_error = arg_intn(NULL, "verbose-connections-error", "<n>", 0, 1, "Connection attempts towards targets"),
|
||||||
|
sslhcfg_verbose_fd = arg_intn(NULL, "verbose-fd", "<n>", 0, 1, "File descriptor activity, open/close/whatnot"),
|
||||||
|
sslhcfg_verbose_packets = arg_intn(NULL, "verbose-packets", "<n>", 0, 1, "Hexdump packets on which probing is done"),
|
||||||
|
sslhcfg_verbose_probe_info = arg_intn(NULL, "verbose-probe-info", "<n>", 0, 1, "Trace the probe process"),
|
||||||
|
sslhcfg_verbose_probe_error = arg_intn(NULL, "verbose-probe-error", "<n>", 0, 1, "Failures and problems during probing"),
|
||||||
|
sslhcfg_verbose_system_error = arg_intn(NULL, "verbose-system-error", "<n>", 0, 1, "System call failures"),
|
||||||
|
sslhcfg_verbose_int_error = arg_intn(NULL, "verbose-int-error", "<n>", 0, 1, "Internal errors that should never happen"),
|
||||||
|
sslhcfg_version = arg_litn("V", "version", 0, 1, "Print version information and exit"),
|
||||||
sslhcfg_foreground = arg_litn("f", "foreground", 0, 1, "Run in foreground instead of as a daemon"),
|
sslhcfg_foreground = arg_litn("f", "foreground", 0, 1, "Run in foreground instead of as a daemon"),
|
||||||
sslhcfg_inetd = arg_litn("i", "inetd", 0, 1, "Run in inetd mode: use stdin/stdout instead of network listen"),
|
sslhcfg_inetd = arg_litn("i", "inetd", 0, 1, "Run in inetd mode: use stdin/stdout instead of network listen"),
|
||||||
sslhcfg_numeric = arg_litn("n", "numeric", 0, 1, "Print IP addresses and ports as numbers"),
|
sslhcfg_numeric = arg_litn("n", "numeric", 0, 1, "Print IP addresses and ports as numbers"),
|
||||||
sslhcfg_transparent = arg_litn(NULL, "transparent", 0, 1, "Set up as a transparent proxy"),
|
sslhcfg_transparent = arg_litn(NULL, "transparent", 0, 1, "Set up as a transparent proxy"),
|
||||||
sslhcfg_timeout = arg_intn("t", "timeout", "<n>", 0, 1, "Set up timeout before connecting to default target"),
|
sslhcfg_timeout = arg_intn("t", "timeout", "<n>", 0, 1, "Set up timeout before connecting to default target"),
|
||||||
|
sslhcfg_udp_max_connections = arg_intn(NULL, "udp-max-connections", "<n>", 0, 1, "Number of concurrent UDP connections"),
|
||||||
sslhcfg_user = arg_strn("u", "user", "<str>", 0, 1, "Username to change to after set-up"),
|
sslhcfg_user = arg_strn("u", "user", "<str>", 0, 1, "Username to change to after set-up"),
|
||||||
sslhcfg_pidfile = arg_strn("P", "pidfile", "<file>", 0, 1, "Path to file to store PID of current instance"),
|
sslhcfg_pidfile = arg_strn("P", "pidfile", "<file>", 0, 1, "Path to file to store PID of current instance"),
|
||||||
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
|
sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
|
||||||
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
|
sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
|
||||||
|
sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
|
||||||
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
|
sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
|
||||||
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
|
sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
|
||||||
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
|
sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
|
||||||
@ -1908,11 +2201,13 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
|||||||
sslhcfg_tls = arg_strn(NULL, "tls", "<host:port>", 0, 10, "Set up TLS/SSL target"),
|
sslhcfg_tls = arg_strn(NULL, "tls", "<host:port>", 0, 10, "Set up TLS/SSL target"),
|
||||||
sslhcfg_openvpn = arg_strn(NULL, "openvpn", "<host:port>", 0, 10, "Set up OpenVPN 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_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"),
|
||||||
sslhcfg_xmpp = arg_strn(NULL, "xmpp", "<host:port>", 0, 10, "Set up XMPP target"),
|
sslhcfg_xmpp = arg_strn(NULL, "xmpp", "<host:port>", 0, 10, "Set up XMPP target"),
|
||||||
sslhcfg_http = arg_strn(NULL, "http", "<host:port>", 0, 10, "Set up HTTP (plain) target"),
|
sslhcfg_http = arg_strn(NULL, "http", "<host:port>", 0, 10, "Set up HTTP (plain) target"),
|
||||||
sslhcfg_adb = arg_strn(NULL, "adb", "<host:port>", 0, 10, "Set up ADB (Android Debug) target"),
|
sslhcfg_adb = arg_strn(NULL, "adb", "<host:port>", 0, 10, "Set up ADB (Android Debug) target"),
|
||||||
sslhcfg_socks5 = arg_strn(NULL, "socks5", "<host:port>", 0, 10, "Set up socks5 target"),
|
sslhcfg_socks5 = arg_strn(NULL, "socks5", "<host:port>", 0, 10, "Set up socks5 target"),
|
||||||
sslhcfg_syslog = arg_strn(NULL, "syslog", "<host:port>", 0, 10, "Set up syslog target"),
|
sslhcfg_syslog = arg_strn(NULL, "syslog", "<host:port>", 0, 10, "Set up syslog target"),
|
||||||
|
sslhcfg_msrdp = arg_strn(NULL, "msrdp", "<host:port>", 0, 10, "Set up msrdp target"),
|
||||||
sslhcfg_anyprot = arg_strn(NULL, "anyprot", "<host:port>", 0, 10, "Set up default target"),
|
sslhcfg_anyprot = arg_strn(NULL, "anyprot", "<host:port>", 0, 10, "Set up default target"),
|
||||||
sslhcfg_end = arg_end(10)
|
sslhcfg_end = arg_end(10)
|
||||||
|
|
||||||
@ -2002,6 +2297,9 @@ static void sslhcfg_protocols_fprint(
|
|||||||
fprintf(out, "transparent: %d", sslhcfg_protocols->transparent);
|
fprintf(out, "transparent: %d", sslhcfg_protocols->transparent);
|
||||||
fprintf(out, "\n");
|
fprintf(out, "\n");
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
|
fprintf(out, "resolve_on_forward: %d", sslhcfg_protocols->resolve_on_forward);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
fprintf(out, "log_level: %d", sslhcfg_protocols->log_level);
|
fprintf(out, "log_level: %d", sslhcfg_protocols->log_level);
|
||||||
fprintf(out, "\n");
|
fprintf(out, "\n");
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
@ -2059,7 +2357,40 @@ void sslhcfg_fprint(
|
|||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
fprintf(out, "verbose: %d", sslhcfg->verbose);
|
fprintf(out, "verbose_config: %d", sslhcfg->verbose_config);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_config_error: %d", sslhcfg->verbose_config_error);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_connections: %d", sslhcfg->verbose_connections);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_connections_try: %d", sslhcfg->verbose_connections_try);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_connections_error: %d", sslhcfg->verbose_connections_error);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_fd: %d", sslhcfg->verbose_fd);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_packets: %d", sslhcfg->verbose_packets);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_probe_info: %d", sslhcfg->verbose_probe_info);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_probe_error: %d", sslhcfg->verbose_probe_error);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_system_error: %d", sslhcfg->verbose_system_error);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "verbose_int_error: %d", sslhcfg->verbose_int_error);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
|
fprintf(out, "version: %d", sslhcfg->version);
|
||||||
fprintf(out, "\n");
|
fprintf(out, "\n");
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
fprintf(out, "foreground: %d", sslhcfg->foreground);
|
fprintf(out, "foreground: %d", sslhcfg->foreground);
|
||||||
@ -2077,6 +2408,9 @@ void sslhcfg_fprint(
|
|||||||
fprintf(out, "timeout: %d", sslhcfg->timeout);
|
fprintf(out, "timeout: %d", sslhcfg->timeout);
|
||||||
fprintf(out, "\n");
|
fprintf(out, "\n");
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
|
fprintf(out, "udp_max_connections: %d", sslhcfg->udp_max_connections);
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
fprintf(out, "user: %s", sslhcfg->user);
|
fprintf(out, "user: %s", sslhcfg->user);
|
||||||
if (! sslhcfg->user_is_present)
|
if (! sslhcfg->user_is_present)
|
||||||
fprintf(out, " <unset>");
|
fprintf(out, " <unset>");
|
||||||
@ -2095,6 +2429,11 @@ void sslhcfg_fprint(
|
|||||||
fprintf(out, "syslog_facility: %s", sslhcfg->syslog_facility);
|
fprintf(out, "syslog_facility: %s", sslhcfg->syslog_facility);
|
||||||
fprintf(out, "\n");
|
fprintf(out, "\n");
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
|
fprintf(out, "logfile: %s", sslhcfg->logfile);
|
||||||
|
if (! sslhcfg->logfile_is_present)
|
||||||
|
fprintf(out, " <unset>");
|
||||||
|
fprintf(out, "\n");
|
||||||
|
indent(out, depth);
|
||||||
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
|
fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
|
||||||
fprintf(out, "\n");
|
fprintf(out, "\n");
|
||||||
indent(out, depth);
|
indent(out, depth);
|
||||||
|
20
sslh-conf.h
20
sslh-conf.h
@ -1,5 +1,5 @@
|
|||||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||||
* on Fri Aug 13 18:03:18 2021.
|
* on Sun Sep 11 21:43:25 2022.
|
||||||
|
|
||||||
# conf2struct: generate libconf parsers that read to structs
|
# conf2struct: generate libconf parsers that read to structs
|
||||||
# Copyright (C) 2018-2021 Yves Rutschle
|
# Copyright (C) 2018-2021 Yves Rutschle
|
||||||
@ -58,6 +58,7 @@ struct sslhcfg_protocols_item {
|
|||||||
int fork;
|
int fork;
|
||||||
int tfo_ok;
|
int tfo_ok;
|
||||||
int transparent;
|
int transparent;
|
||||||
|
int resolve_on_forward;
|
||||||
int log_level;
|
int log_level;
|
||||||
int keepalive;
|
int keepalive;
|
||||||
size_t sni_hostnames_len;
|
size_t sni_hostnames_len;
|
||||||
@ -71,15 +72,28 @@ struct sslhcfg_protocols_item {
|
|||||||
T_PROBE* probe;
|
T_PROBE* probe;
|
||||||
struct addrinfo* saddr;
|
struct addrinfo* saddr;
|
||||||
void* data;
|
void* data;
|
||||||
|
dl_list timeouts;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sslhcfg_item {
|
struct sslhcfg_item {
|
||||||
int verbose;
|
int verbose_config;
|
||||||
|
int verbose_config_error;
|
||||||
|
int verbose_connections;
|
||||||
|
int verbose_connections_try;
|
||||||
|
int verbose_connections_error;
|
||||||
|
int verbose_fd;
|
||||||
|
int verbose_packets;
|
||||||
|
int verbose_probe_info;
|
||||||
|
int verbose_probe_error;
|
||||||
|
int verbose_system_error;
|
||||||
|
int verbose_int_error;
|
||||||
|
int version;
|
||||||
int foreground;
|
int foreground;
|
||||||
int inetd;
|
int inetd;
|
||||||
int numeric;
|
int numeric;
|
||||||
int transparent;
|
int transparent;
|
||||||
int timeout;
|
int timeout;
|
||||||
|
int udp_max_connections;
|
||||||
int user_is_present;
|
int user_is_present;
|
||||||
char* user;
|
char* user;
|
||||||
int pidfile_is_present;
|
int pidfile_is_present;
|
||||||
@ -87,6 +101,8 @@ struct sslhcfg_item {
|
|||||||
int chroot_is_present;
|
int chroot_is_present;
|
||||||
char* chroot;
|
char* chroot;
|
||||||
char* syslog_facility;
|
char* syslog_facility;
|
||||||
|
int logfile_is_present;
|
||||||
|
char* logfile;
|
||||||
char* on_timeout;
|
char* on_timeout;
|
||||||
char* prefix;
|
char* prefix;
|
||||||
size_t listen_len;
|
size_t listen_len;
|
||||||
|
153
sslh-ev.c
Normal file
153
sslh-ev.c
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
sslh-ev: mono-processus server based on libev
|
||||||
|
|
||||||
|
# Copyright (C) 2021 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 <stdlib.h>
|
||||||
|
#include <ev.h>
|
||||||
|
#include "gap.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "udp-listener.h"
|
||||||
|
#include "tcp-listener.h"
|
||||||
|
|
||||||
|
|
||||||
|
const char* server_type = "sslh-ev";
|
||||||
|
|
||||||
|
static struct ev_loop* loop;
|
||||||
|
|
||||||
|
/* Libev watchers */
|
||||||
|
struct watchers {
|
||||||
|
/* one set of ev_io for read, one for write, indexed by file descriptor */
|
||||||
|
gap_array *ev_ior, *ev_iow;
|
||||||
|
|
||||||
|
struct listen_endpoint* listen_sockets;
|
||||||
|
gap_array* fd2ls; /* Array indexed by file descriptor, pointing to listen_sockets */
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cnx_read_cb(EV_P_ ev_io *w, int revents);
|
||||||
|
static void cnx_write_cb(EV_P_ ev_io *w, int wevents);
|
||||||
|
static void cnx_accept_cb(EV_P_ ev_io *w, int revents);
|
||||||
|
|
||||||
|
|
||||||
|
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||||
|
int num_addr_listen)
|
||||||
|
{
|
||||||
|
*w = malloc(sizeof(**w));
|
||||||
|
(*w)->ev_ior = gap_init(num_addr_listen);
|
||||||
|
(*w)->ev_iow = gap_init(num_addr_listen);
|
||||||
|
(*w)->listen_sockets = listen_sockets;
|
||||||
|
(*w)->fd2ls = gap_init(0);
|
||||||
|
|
||||||
|
/* Create watchers for listen sockets */
|
||||||
|
for (int i = 0; i < num_addr_listen; i++) {
|
||||||
|
ev_io* io = malloc(sizeof(*io));
|
||||||
|
|
||||||
|
ev_io_init(io, &cnx_accept_cb, listen_sockets[i].socketfd, EV_READ);
|
||||||
|
ev_io_start(EV_A_ io);
|
||||||
|
gap_set((*w)->ev_ior, i, io);
|
||||||
|
gap_set((*w)->fd2ls, listen_sockets[i].socketfd, &listen_sockets[i]);
|
||||||
|
set_nonblock(listen_sockets[i].socketfd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_add_read(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
ev_io* io = gap_get(w->ev_ior, fd);
|
||||||
|
if (!io) {
|
||||||
|
io = malloc(sizeof(*io));
|
||||||
|
ev_io_init(io, &cnx_read_cb, fd, EV_READ);
|
||||||
|
ev_io_set(io, fd, EV_READ);
|
||||||
|
|
||||||
|
gap_set(w->ev_ior, fd, io);
|
||||||
|
}
|
||||||
|
ev_io_start(loop, io);
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_del_read(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
ev_io* io = gap_get(w->ev_ior, fd);
|
||||||
|
if (io) ev_io_stop(EV_A_ io);
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_add_write(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
ev_io* io = gap_get(w->ev_iow, fd);
|
||||||
|
if (!io) {
|
||||||
|
io = malloc(sizeof(*io));
|
||||||
|
ev_io_init(io, &cnx_write_cb, fd, EV_WRITE);
|
||||||
|
ev_io_set(io, fd, EV_WRITE);
|
||||||
|
|
||||||
|
gap_set(w->ev_iow, fd, io);
|
||||||
|
}
|
||||||
|
ev_io_start(loop, io);
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_del_write(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
ev_io* io = gap_get(w->ev_iow, fd);
|
||||||
|
if (io) ev_io_stop(EV_A_ io);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* /watchers */
|
||||||
|
|
||||||
|
#include "processes.h"
|
||||||
|
|
||||||
|
/* Libev callbacks */
|
||||||
|
static void cnx_read_cb(EV_P_ ev_io *w, int revents)
|
||||||
|
{
|
||||||
|
struct loop_info* info = ev_userdata(EV_A);
|
||||||
|
cnx_read_process(info, w->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cnx_write_cb(EV_P_ ev_io *w, int wevents)
|
||||||
|
{
|
||||||
|
struct loop_info* info = ev_userdata(EV_A);
|
||||||
|
cnx_write_process(info, w->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
|
||||||
|
{
|
||||||
|
struct loop_info* info = ev_userdata(EV_A);
|
||||||
|
cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||||
|
{
|
||||||
|
struct loop_info ev_info = {0};
|
||||||
|
loop = EV_DEFAULT;
|
||||||
|
|
||||||
|
ev_info.collection = collection_init(0);
|
||||||
|
ev_info.probing_list = gap_init(0);
|
||||||
|
udp_init(&ev_info);
|
||||||
|
tcp_init();
|
||||||
|
|
||||||
|
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
|
||||||
|
ev_set_userdata(EV_A_ &ev_info);
|
||||||
|
|
||||||
|
ev_run(EV_A_ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_shoveler(int listen_socket) {
|
||||||
|
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
23
sslh-fork.c
23
sslh-fork.c
@ -23,7 +23,8 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
#include "sslh-conf.h"
|
#include "sslh-conf.h"
|
||||||
#include "udp-listener.h"
|
#include "tcp-probe.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#ifdef LIBBSD
|
#ifdef LIBBSD
|
||||||
#include <bsd/unistd.h>
|
#include <bsd/unistd.h>
|
||||||
@ -58,8 +59,7 @@ int shovel(struct connection *cnx)
|
|||||||
if (FD_ISSET(cnx->q[i].fd, &fds)) {
|
if (FD_ISSET(cnx->q[i].fd, &fds)) {
|
||||||
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
||||||
if (res == FD_CNXCLOSED) {
|
if (res == FD_CNXCLOSED) {
|
||||||
if (cfg.verbose)
|
print_message(msg_fd, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||||
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,8 +99,7 @@ void start_shoveler(int in_socket)
|
|||||||
} else {
|
} else {
|
||||||
/* Timed out: it's necessarily SSH */
|
/* Timed out: it's necessarily SSH */
|
||||||
cnx.proto = timeout_protocol();
|
cnx.proto = timeout_protocol();
|
||||||
if (cfg.verbose)
|
print_message(msg_fd, "timed out, connect to %s\n", cnx.proto->name);
|
||||||
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->name);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,8 +128,7 @@ void start_shoveler(int in_socket)
|
|||||||
close(in_socket);
|
close(in_socket);
|
||||||
close(out_socket);
|
close(out_socket);
|
||||||
|
|
||||||
if (cfg.verbose)
|
print_message(msg_fd, "connection closed down\n");
|
||||||
fprintf(stderr, "connection closed down\n");
|
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@ -179,10 +177,11 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
|||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||||
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
CHECK_RES_RETURN(in_socket, "accept", /*void*/ );
|
||||||
|
print_message(msg_fd, "accepted fd %d\n", in_socket);
|
||||||
|
|
||||||
switch(fork()) {
|
switch(fork()) {
|
||||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0: /* In child process */
|
case 0: /* In child process */
|
||||||
@ -209,18 +208,20 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
|||||||
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
|
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
|
||||||
CHECK_ALLOC(listener_pid, "malloc");
|
CHECK_ALLOC(listener_pid, "malloc");
|
||||||
|
|
||||||
|
tcp_init();
|
||||||
|
|
||||||
/* Start one process for each listening address */
|
/* Start one process for each listening address */
|
||||||
for (i = 0; i < num_addr_listen; i++) {
|
for (i = 0; i < num_addr_listen; i++) {
|
||||||
listener_pid[i] = fork();
|
listener_pid[i] = fork();
|
||||||
switch(listener_pid[i]) {
|
switch(listener_pid[i]) {
|
||||||
/* Log if fork() fails for some reason */
|
/* Log if fork() fails for some reason */
|
||||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||||
break;
|
break;
|
||||||
/* We're in the child, we have work to do */
|
/* We're in the child, we have work to do */
|
||||||
case 0:
|
case 0:
|
||||||
set_listen_procname(&listen_sockets[i]);
|
set_listen_procname(&listen_sockets[i]);
|
||||||
if (listen_sockets[i].type == SOCK_DGRAM)
|
if (listen_sockets[i].type == SOCK_DGRAM)
|
||||||
log_message(LOG_ERR, "UDP not (yet?) supported in sslh-fork\n");
|
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
|
||||||
else
|
else
|
||||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||||
break;
|
break;
|
||||||
|
143
sslh-main.c
143
sslh-main.c
@ -36,29 +36,7 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
|
#include "log.h"
|
||||||
const char* USAGE_STRING =
|
|
||||||
"sslh " VERSION "\n" \
|
|
||||||
"usage:\n" \
|
|
||||||
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
|
|
||||||
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <addr> [-p <addr> ...] \n" \
|
|
||||||
"%s\n\n" /* Dynamically built list of builtin protocols */ \
|
|
||||||
"\t[--on-timeout <addr>]\n" \
|
|
||||||
"-v: verbose\n" \
|
|
||||||
"-V: version\n" \
|
|
||||||
"-f: foreground\n" \
|
|
||||||
"-n: numeric output\n" \
|
|
||||||
"-u: specify under which user to run\n" \
|
|
||||||
"-C: specify under which chroot path to run\n" \
|
|
||||||
"--transparent: behave as a transparent proxy\n" \
|
|
||||||
"-F: use configuration file (warning: no space between -F and file name!)\n" \
|
|
||||||
"--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \
|
|
||||||
"-t: seconds to wait before connecting to --on-timeout address.\n" \
|
|
||||||
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
|
|
||||||
"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
|
|
||||||
"-P: PID file.\n" \
|
|
||||||
"-i: Run as a inetd service.\n" \
|
|
||||||
"";
|
|
||||||
|
|
||||||
/* Constants for options that have no one-character shorthand */
|
/* Constants for options that have no one-character shorthand */
|
||||||
#define OPT_ONTIMEOUT 257
|
#define OPT_ONTIMEOUT 257
|
||||||
@ -73,7 +51,7 @@ static void printcaps(void) {
|
|||||||
|
|
||||||
desc = cap_to_text(caps, &len);
|
desc = cap_to_text(caps, &len);
|
||||||
|
|
||||||
fprintf(stderr, "capabilities: %s\n", desc);
|
print_message(msg_config, "capabilities: %s\n", desc);
|
||||||
|
|
||||||
cap_free(caps);
|
cap_free(caps);
|
||||||
cap_free(desc);
|
cap_free(desc);
|
||||||
@ -88,21 +66,26 @@ static void printsettings(void)
|
|||||||
|
|
||||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||||
p = &cfg.protocols[i];
|
p = &cfg.protocols[i];
|
||||||
fprintf(stderr,
|
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 family %d %d [%s] [%s] [%s]\n",
|
||||||
p->name,
|
p->name,
|
||||||
sprintaddr(buf, sizeof(buf), p->saddr),
|
sprintaddr(buf, sizeof(buf), p->saddr),
|
||||||
p->service,
|
p->service,
|
||||||
p->log_level,
|
p->log_level,
|
||||||
p->saddr->ai_family,
|
p->saddr->ai_family,
|
||||||
p->saddr->ai_addr->sa_family,
|
p->saddr->ai_addr->sa_family,
|
||||||
p->keepalive ? "keepalive" : "",
|
p->keepalive ? "keepalive" : "",
|
||||||
p->fork ? "fork" : "",
|
p->fork ? "fork" : "",
|
||||||
p->transparent ? "transparent" : ""
|
p->transparent ? "transparent" : ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout,
|
print_message(msg_config,
|
||||||
timeout_protocol()->name);
|
"timeout: %d\n"
|
||||||
|
"on-timeout: %s\n"
|
||||||
|
"UDP hash size: %d\n",
|
||||||
|
cfg.timeout,
|
||||||
|
timeout_protocol()->name,
|
||||||
|
cfg.udp_max_connections);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -126,7 +109,7 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
|||||||
&error, &error_offset, NULL);
|
&error, &error_offset, NULL);
|
||||||
if (!pattern_list[i]) {
|
if (!pattern_list[i]) {
|
||||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||||
fprintf(stderr, "compiling pattern /%s/:%d:%s at offset %ld\n",
|
print_message(msg_config_error, "compiling pattern /%s/:%d:%s at offset %ld\n",
|
||||||
p->regex_patterns[i], error, err_str, error_offset);
|
p->regex_patterns[i], error, err_str, error_offset);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -146,14 +129,19 @@ static void config_protocols()
|
|||||||
int i;
|
int i;
|
||||||
for (i = 0; i < cfg.protocols_len; i++) {
|
for (i = 0; i < cfg.protocols_len; i++) {
|
||||||
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
||||||
if (resolve_split_name(&(p->saddr), p->host, p->port)) {
|
|
||||||
fprintf(stderr, "cannot resolve %s:%s\n", p->host, p->port);
|
if (
|
||||||
|
!p->resolve_on_forward &&
|
||||||
|
resolve_split_name(&(p->saddr), p->host, p->port)
|
||||||
|
) {
|
||||||
|
print_message(msg_config_error, "cannot resolve %s:%s\n",
|
||||||
|
p->host, p->port);
|
||||||
exit(4);
|
exit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
p->probe = get_probe(p->name);
|
p->probe = get_probe(p->name);
|
||||||
if (!p->probe) {
|
if (!p->probe) {
|
||||||
fprintf(stderr, "%s: probe unknown\n", p->name);
|
print_message(msg_config_error, "%s: probe unknown\n", p->name);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,23 +160,55 @@ static void config_protocols()
|
|||||||
(const char**) cfg.protocols[i].alpn_protocols,
|
(const char**) cfg.protocols[i].alpn_protocols,
|
||||||
cfg.protocols[i].alpn_protocols_len);
|
cfg.protocols[i].alpn_protocols_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->timeouts.head = NULL;
|
||||||
|
p->timeouts.tail = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void config_sanity_check(struct sslhcfg_item* cfg) {
|
void config_sanity_check(struct sslhcfg_item* cfg)
|
||||||
if (!cfg->protocols_len) {
|
{
|
||||||
fprintf(stderr, "At least one target protocol must be specified.\n");
|
size_t i;
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If compiling with systemd socket support no need to require listen address */
|
/* If compiling with systemd socket support no need to require listen address */
|
||||||
#ifndef SYSTEMD
|
#ifndef SYSTEMD
|
||||||
if (!cfg->listen_len && !cfg->inetd) {
|
if (!cfg->listen_len && !cfg->inetd) {
|
||||||
fprintf(stderr, "No listening address specified; use at least one -p option\n");
|
print_message(msg_config_error, "No listening address specified; use at least one -p option\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
for (i = 0; i < cfg->protocols_len; ++i) {
|
||||||
|
if (strcmp(cfg->protocols[i].name, "tls")) {
|
||||||
|
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",
|
||||||
|
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (cfg->protocols[i].alpn_protocols_len) {
|
||||||
|
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||||
|
"Config option alpn_protocols is only applicable for tls\n",
|
||||||
|
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg->protocols[i].is_udp) {
|
||||||
|
if (cfg->protocols[i].tfo_ok) {
|
||||||
|
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||||
|
"Config option tfo_ok is not applicable for udp connections\n",
|
||||||
|
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!strcmp(cfg->protocols[i].name, "wireguard")) {
|
||||||
|
print_message(msg_config_error, "Wireguard works only with UDP\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -207,26 +227,29 @@ int main(int argc, char *argv[], char* envp[])
|
|||||||
memset(&cfg, 0, sizeof(cfg));
|
memset(&cfg, 0, sizeof(cfg));
|
||||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||||
if (res) exit(6);
|
if (res) exit(6);
|
||||||
if (cfg.verbose > 3)
|
|
||||||
sslhcfg_fprint(stderr, &cfg, 0);
|
if (cfg.version) {
|
||||||
|
printf("%s %s\n", server_type, VERSION);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
config_protocols();
|
config_protocols();
|
||||||
config_sanity_check(&cfg);
|
config_sanity_check(&cfg);
|
||||||
|
|
||||||
if (cfg.inetd)
|
if (cfg.inetd)
|
||||||
{
|
{
|
||||||
cfg.verbose = 0;
|
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||||
start_shoveler(0);
|
start_shoveler(0);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.verbose)
|
printsettings();
|
||||||
printsettings();
|
|
||||||
|
|
||||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||||
|
|
||||||
#ifdef SYSTEMD
|
#ifdef SYSTEMD
|
||||||
if (num_addr_listen < 1) {
|
if (num_addr_listen < 1) {
|
||||||
fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n");
|
print_message(msg_config_error, "No listening sockets found, restart sockets or specify addresses in config\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -249,13 +272,21 @@ int main(int argc, char *argv[], char* envp[])
|
|||||||
/* Open syslog connection before we drop privs/chroot */
|
/* Open syslog connection before we drop privs/chroot */
|
||||||
setup_syslog(argv[0]);
|
setup_syslog(argv[0]);
|
||||||
|
|
||||||
|
/* Open log file for writing */
|
||||||
|
setup_logfile();
|
||||||
|
|
||||||
if (cfg.user || cfg.chroot)
|
if (cfg.user || cfg.chroot)
|
||||||
drop_privileges(cfg.user, cfg.chroot);
|
drop_privileges(cfg.user, cfg.chroot);
|
||||||
|
|
||||||
if (cfg.verbose)
|
printcaps();
|
||||||
printcaps();
|
|
||||||
|
print_message(msg_config, "%s %s started\n", server_type, VERSION);
|
||||||
|
|
||||||
main_loop(listen_sockets, num_addr_listen);
|
main_loop(listen_sockets, num_addr_listen);
|
||||||
|
|
||||||
|
close_logfile();
|
||||||
|
|
||||||
|
free(listen_sockets);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
552
sslh-select.c
552
sslh-select.c
@ -25,482 +25,88 @@
|
|||||||
* https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few
|
* https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few
|
||||||
* hundred file descriptors, both become very slow, so there is little
|
* hundred file descriptors, both become very slow, so there is little
|
||||||
* incentive to move to poll() to support more than FD_SETSIZE (which is 1024
|
* incentive to move to poll() to support more than FD_SETSIZE (which is 1024
|
||||||
* on many Linux. To support large numbers of descriptors, either use the fork
|
* on many Linux. To support large numbers of descriptors efficiently, either use sslh-fork
|
||||||
* version, or we'll have to write a new version based on libev. */
|
* or sslh-ev. */
|
||||||
|
|
||||||
#define __LINUX__
|
#define __LINUX__
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
|
#include "tcp-listener.h"
|
||||||
#include "udp-listener.h"
|
#include "udp-listener.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
|
#include "processes.h"
|
||||||
#include "gap.h"
|
#include "gap.h"
|
||||||
|
#include "log.h"
|
||||||
static int debug = 0;
|
|
||||||
|
|
||||||
const char* server_type = "sslh-select";
|
const char* server_type = "sslh-select";
|
||||||
|
|
||||||
/* Global state for a select() loop */
|
/* watcher type for a select() loop */
|
||||||
struct select_info {
|
struct watchers {
|
||||||
int max_fd; /* Highest fd number to pass to select() */
|
|
||||||
|
|
||||||
int num_probing; /* Number of connections currently probing
|
|
||||||
* We use this to know if we need to time out of
|
|
||||||
* select() */
|
|
||||||
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
|
|
||||||
|
|
||||||
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
|
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
|
||||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
int max_fd; /* Highest fd number to pass to select() */
|
||||||
|
|
||||||
time_t next_timeout; /* time at which next UDP connection times out */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||||
static int tidy_connection(struct connection *cnx, struct select_info* fd_info)
|
int num_addr_listen)
|
||||||
{
|
{
|
||||||
int i;
|
*w = malloc(sizeof(**w));
|
||||||
fd_set* fds = &fd_info->fds_r;
|
CHECK_ALLOC(*w, "malloc");
|
||||||
fd_set* fds2 = &fd_info->fds_w;
|
|
||||||
|
|
||||||
for (i = 0; i < 2; i++) {
|
memset(*w, 0, sizeof(**w));
|
||||||
if (cnx->q[i].fd != -1) {
|
FD_ZERO(&(*w)->fds_r);
|
||||||
if (cfg.verbose)
|
FD_ZERO(&(*w)->fds_w);
|
||||||
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
|
|
||||||
|
|
||||||
FD_CLR(cnx->q[i].fd, fds);
|
for (int i = 0; i < num_addr_listen; i++) {
|
||||||
FD_CLR(cnx->q[i].fd, fds2);
|
watchers_add_read(*w, listen_sockets[i].socketfd);
|
||||||
close(cnx->q[i].fd);
|
set_nonblock(listen_sockets[i].socketfd);
|
||||||
if (cnx->q[i].deferred_data)
|
|
||||||
free(cnx->q[i].deferred_data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
collection_remove_cnx(fd_info->collection, cnx);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void watchers_add_read(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
FD_SET(fd, &w->fds_r);
|
||||||
|
if (fd > w->max_fd)
|
||||||
|
w->max_fd = fd + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_del_read(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
FD_CLR(fd, &w->fds_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_add_write(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
FD_SET(fd, &w->fds_w);
|
||||||
|
if (fd > w->max_fd)
|
||||||
|
w->max_fd = fd + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchers_del_write(watchers* w, int fd)
|
||||||
|
{
|
||||||
|
FD_CLR(fd, &w->fds_w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* /end watchers */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
|
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
|
||||||
* and FD_CLR. Need to drop connections if we go above that limit */
|
* and FD_CLR. Need to drop connections if we go above that limit */
|
||||||
static int fd_is_in_range(int fd) {
|
static int fd_out_of_range(int fd) {
|
||||||
if (fd >= FD_SETSIZE) {
|
if (fd >= FD_SETSIZE) {
|
||||||
log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n");
|
print_message(msg_system_error, "too many open file descriptor to monitor them all -- dropping connection\n");
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
return 1;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
/* Accepts a connection from the main socket and assigns it to an empty slot.
|
|
||||||
* If no slots are available, allocate another few. If that fails, drop the
|
|
||||||
* connexion */
|
|
||||||
static struct connection* accept_new_connection(int listen_socket, struct cnx_collection *collection)
|
|
||||||
{
|
|
||||||
int in_socket, res;
|
|
||||||
|
|
||||||
|
|
||||||
if (cfg.verbose) fprintf(stderr, "accepting from %d\n", listen_socket);
|
|
||||||
|
|
||||||
in_socket = accept(listen_socket, 0, 0);
|
|
||||||
CHECK_RES_RETURN(in_socket, "accept", NULL);
|
|
||||||
|
|
||||||
if (!fd_is_in_range(in_socket)) {
|
|
||||||
close(in_socket);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = set_nonblock(in_socket);
|
|
||||||
if (res == -1) {
|
|
||||||
close(in_socket);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, in_socket);
|
|
||||||
if (!cnx) {
|
|
||||||
close(in_socket);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cnx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Connect queue 1 of connection to SSL; returns new file descriptor */
|
|
||||||
static int connect_queue(struct connection* cnx,
|
|
||||||
struct select_info* fd_info)
|
|
||||||
{
|
|
||||||
struct queue *q = &cnx->q[1];
|
|
||||||
|
|
||||||
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
|
||||||
if ((q->fd != -1) && fd_is_in_range(q->fd)) {
|
|
||||||
log_connection(NULL, cnx);
|
|
||||||
flush_deferred(q);
|
|
||||||
if (q->deferred_data) {
|
|
||||||
FD_SET(q->fd, &fd_info->fds_w);
|
|
||||||
FD_CLR(cnx->q[0].fd, &fd_info->fds_r);
|
|
||||||
}
|
|
||||||
FD_SET(q->fd, &fd_info->fds_r);
|
|
||||||
collection_add_fd(fd_info->collection, cnx, q->fd);
|
|
||||||
return q->fd;
|
|
||||||
} else {
|
|
||||||
tidy_connection(cnx, fd_info);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* shovels data from active fd to the other
|
|
||||||
returns after one socket closed or operation would block
|
|
||||||
*/
|
|
||||||
static void shovel(struct connection *cnx, int active_fd, struct select_info* fd_info)
|
|
||||||
{
|
|
||||||
struct queue *read_q, *write_q;
|
|
||||||
|
|
||||||
read_q = &cnx->q[active_fd];
|
|
||||||
write_q = &cnx->q[1-active_fd];
|
|
||||||
|
|
||||||
if (cfg.verbose)
|
|
||||||
fprintf(stderr, "activity on fd%d\n", read_q->fd);
|
|
||||||
|
|
||||||
switch(fd2fd(write_q, read_q)) {
|
|
||||||
case -1:
|
|
||||||
case FD_CNXCLOSED:
|
|
||||||
tidy_connection(cnx, fd_info);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FD_STALLED:
|
|
||||||
FD_SET(write_q->fd, &fd_info->fds_w);
|
|
||||||
FD_CLR(read_q->fd, &fd_info->fds_r);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: /* Nothing */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* shovels data from one fd to the other and vice-versa
|
|
||||||
returns after one socket closed
|
|
||||||
*/
|
|
||||||
static void shovel_single(struct connection *cnx)
|
|
||||||
{
|
|
||||||
fd_set fds_r, fds_w;
|
|
||||||
int res, i;
|
|
||||||
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
|
|
||||||
|
|
||||||
FD_ZERO(&fds_r);
|
|
||||||
FD_ZERO(&fds_w);
|
|
||||||
while (1) {
|
|
||||||
for (i = 0; i < 2; i++) {
|
|
||||||
if (cnx->q[i].deferred_data_size) {
|
|
||||||
FD_SET(cnx->q[i].fd, &fds_w);
|
|
||||||
FD_CLR(cnx->q[1-i].fd, &fds_r);
|
|
||||||
} else {
|
|
||||||
FD_CLR(cnx->q[i].fd, &fds_w);
|
|
||||||
FD_SET(cnx->q[1-i].fd, &fds_r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res = select(
|
|
||||||
max_fd,
|
|
||||||
&fds_r,
|
|
||||||
&fds_w,
|
|
||||||
NULL,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
CHECK_RES_DIE(res, "select");
|
|
||||||
|
|
||||||
for (i = 0; i < 2; i++) {
|
|
||||||
if (FD_ISSET(cnx->q[i].fd, &fds_w)) {
|
|
||||||
res = flush_deferred(&cnx->q[i]);
|
|
||||||
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
|
||||||
if (cfg.verbose)
|
|
||||||
fprintf(stderr, "%s socket closed\n", i ? "server" : "client");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (FD_ISSET(cnx->q[i].fd, &fds_r)) {
|
|
||||||
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
|
||||||
if (!res) {
|
|
||||||
if (cfg.verbose)
|
|
||||||
fprintf(stderr, "socket closed\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Child process that makes internal connection and proxies
|
|
||||||
*/
|
|
||||||
static void connect_proxy(struct connection *cnx)
|
|
||||||
{
|
|
||||||
int in_socket;
|
|
||||||
int out_socket;
|
|
||||||
|
|
||||||
/* Minimize the file descriptor value to help select() */
|
|
||||||
in_socket = dup(cnx->q[0].fd);
|
|
||||||
if (in_socket == -1) {
|
|
||||||
in_socket = cnx->q[0].fd;
|
|
||||||
} else {
|
|
||||||
close(cnx->q[0].fd);
|
|
||||||
cnx->q[0].fd = in_socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connect the target socket */
|
|
||||||
out_socket = connect_addr(cnx, in_socket, BLOCKING);
|
|
||||||
CHECK_RES_DIE(out_socket, "connect");
|
|
||||||
|
|
||||||
cnx->q[1].fd = out_socket;
|
|
||||||
|
|
||||||
log_connection(NULL, cnx);
|
|
||||||
|
|
||||||
shovel_single(cnx);
|
|
||||||
|
|
||||||
close(in_socket);
|
|
||||||
close(out_socket);
|
|
||||||
|
|
||||||
if (cfg.verbose)
|
|
||||||
fprintf(stderr, "connection closed down\n");
|
|
||||||
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Removes cnx from probing list */
|
|
||||||
static void remove_probing_cnx(struct select_info* fd_info, struct connection* cnx)
|
|
||||||
{
|
|
||||||
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
|
|
||||||
fd_info->num_probing--;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_probing_cnx(struct select_info* fd_info, struct connection* cnx)
|
|
||||||
{
|
|
||||||
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
|
|
||||||
fd_info->num_probing++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Process read activity on a socket in probe state
|
|
||||||
* IN/OUT cnx: connection data, updated if connected
|
|
||||||
* IN/OUT info: updated if connected
|
|
||||||
* */
|
|
||||||
|
|
||||||
static void probing_read_process(struct connection* cnx,
|
|
||||||
struct select_info* fd_info)
|
|
||||||
{
|
|
||||||
int res;
|
|
||||||
|
|
||||||
/* If timed out it's SSH, otherwise the client sent
|
|
||||||
* data so probe the protocol */
|
|
||||||
if ((cnx->probe_timeout < time(NULL))) {
|
|
||||||
cnx->proto = timeout_protocol();
|
|
||||||
if (cfg.verbose)
|
|
||||||
log_message(LOG_INFO,
|
|
||||||
"timed out, connect to %s\n",
|
|
||||||
cnx->proto->name);
|
|
||||||
} else {
|
|
||||||
res = probe_client_protocol(cnx);
|
|
||||||
if (res == PROBE_AGAIN)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_probing_cnx(fd_info, cnx);
|
|
||||||
cnx->state = ST_SHOVELING;
|
|
||||||
|
|
||||||
/* libwrap check if required for this protocol */
|
|
||||||
if (cnx->proto->service &&
|
|
||||||
check_access_rights(cnx->q[0].fd, cnx->proto->service)) {
|
|
||||||
tidy_connection(cnx, fd_info);
|
|
||||||
res = -1;
|
|
||||||
} else if (cnx->proto->fork) {
|
|
||||||
switch (fork()) {
|
|
||||||
case 0: /* child */
|
|
||||||
/* TODO: close all file descriptors except 2 */
|
|
||||||
/* free(cnx); */
|
|
||||||
connect_proxy(cnx);
|
|
||||||
exit(0);
|
|
||||||
case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno));
|
|
||||||
break;
|
|
||||||
default: /* parent */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tidy_connection(cnx, fd_info);
|
|
||||||
res = -1;
|
|
||||||
} else {
|
|
||||||
res = connect_queue(cnx, fd_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res >= fd_info->max_fd)
|
|
||||||
fd_info->max_fd = res + 1;;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Returns the queue index that contains the specified file descriptor */
|
|
||||||
int active_queue(struct connection* cnx, int fd)
|
|
||||||
{
|
|
||||||
if (cnx->q[0].fd == fd) return 0;
|
|
||||||
if (cnx->q[1].fd == fd) return 1;
|
|
||||||
|
|
||||||
log_message(LOG_ERR, "file descriptor %d not found in connection object\n", fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process a connection that is active in read */
|
|
||||||
static void tcp_read_process(struct select_info* fd_info,
|
|
||||||
int fd)
|
|
||||||
{
|
|
||||||
if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd);
|
|
||||||
|
|
||||||
cnx_collection* collection = fd_info->collection;
|
|
||||||
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
|
||||||
/* Determine active queue (0 or 1): if fd is that of q[1], active_q = 1,
|
|
||||||
* otherwise it's 0 */
|
|
||||||
int active_q = active_queue(cnx, fd);
|
|
||||||
|
|
||||||
switch (cnx->state) {
|
|
||||||
|
|
||||||
case ST_PROBING:
|
|
||||||
if (active_q == 1) {
|
|
||||||
fprintf(stderr, "Activity on fd2 while probing, impossible\n");
|
|
||||||
dump_connection(cnx);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
probing_read_process(cnx, fd_info);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ST_SHOVELING:
|
|
||||||
shovel(cnx, active_q, fd_info);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: /* illegal */
|
|
||||||
log_message(LOG_ERR, "Illegal connection state %d\n", cnx->state);
|
|
||||||
dump_connection(cnx);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cnx_read_process(struct select_info* fd_info, int fd)
|
|
||||||
{
|
|
||||||
cnx_collection* collection = fd_info->collection;
|
|
||||||
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
|
||||||
switch (cnx->type) {
|
|
||||||
case SOCK_STREAM:
|
|
||||||
tcp_read_process(fd_info, fd);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SOCK_DGRAM:
|
|
||||||
udp_s2c_forward(cnx);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
log_message(LOG_ERR, "cnx_read_process: Illegal connection type %d\n", cnx->type);
|
|
||||||
dump_connection(cnx);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process a connection that is active in write */
|
|
||||||
static void cnx_write_process(struct select_info* fd_info, int fd)
|
|
||||||
{
|
|
||||||
if (debug) fprintf(stderr, "cnx_write_process fd %d\n", fd);
|
|
||||||
|
|
||||||
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd);
|
|
||||||
int res;
|
|
||||||
int queue = active_queue(cnx, fd);
|
|
||||||
|
|
||||||
res = flush_deferred(&cnx->q[queue]);
|
|
||||||
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
|
||||||
if (cnx->state == ST_PROBING) remove_probing_cnx(fd_info, cnx);
|
|
||||||
tidy_connection(cnx, fd_info);
|
|
||||||
} else {
|
|
||||||
/* If no deferred data is left, stop monitoring the fd
|
|
||||||
* for write, and restart monitoring the other one for reads*/
|
|
||||||
if (!cnx->q[queue].deferred_data_size) {
|
|
||||||
FD_CLR(cnx->q[queue].fd, &fd_info->fds_w);
|
|
||||||
FD_SET(cnx->q[1-queue].fd, &fd_info->fds_r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process a connection that accepts a socket
|
|
||||||
* (For UDP, this means all traffic coming from remote clients)
|
|
||||||
* */
|
|
||||||
void cnx_accept_process(struct select_info* fd_info, struct listen_endpoint* listen_socket)
|
|
||||||
{
|
|
||||||
int fd = listen_socket->socketfd;
|
|
||||||
int type = listen_socket->type;
|
|
||||||
struct connection* cnx;
|
|
||||||
int new_fd;
|
|
||||||
|
|
||||||
if (debug) fprintf(stderr, "cnx_accept_process fd %d\n", fd);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case SOCK_STREAM:
|
|
||||||
cnx = accept_new_connection(fd, fd_info->collection);
|
|
||||||
|
|
||||||
if (cnx) {
|
|
||||||
add_probing_cnx(fd_info, cnx);
|
|
||||||
new_fd = cnx->q[0].fd;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SOCK_DGRAM:
|
|
||||||
new_fd = udp_c2s_forward(fd, fd_info->collection, fd_info->max_fd);
|
|
||||||
fprintf(stderr, "new_fd %d\n", new_fd);
|
|
||||||
if (new_fd == -1)
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
log_message(LOG_ERR, "Inconsistent cnx type: %d\n", type);
|
|
||||||
exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FD_SET(new_fd, &fd_info->fds_r);
|
|
||||||
if (new_fd >= fd_info->max_fd)
|
|
||||||
fd_info->max_fd = new_fd + 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check all connections to see if a UDP connections has timed out, then free
|
|
||||||
* it. At the same time, keep track of the closest, next timeout. Only do the
|
|
||||||
* search through connections if that timeout actually happened. If the
|
|
||||||
* connection that would have timed out has had activity, it doesn't matter: we
|
|
||||||
* go through connections to find the next timeout, which was needed anyway. */
|
|
||||||
static void udp_timeouts(struct select_info* fd_info)
|
|
||||||
{
|
|
||||||
time_t now = time(NULL);
|
|
||||||
|
|
||||||
if (now < fd_info->next_timeout) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < fd_info->max_fd; i++) {
|
|
||||||
time_t next_timeout = INT_MAX;
|
|
||||||
|
|
||||||
/* if it's either in read or write set, there is a connection
|
|
||||||
* behind that file descriptor */
|
|
||||||
if (FD_ISSET(i, &fd_info->fds_r) || FD_ISSET(i, &fd_info->fds_w)) {
|
|
||||||
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, i);
|
|
||||||
if (cnx) {
|
|
||||||
time_t timeout = udp_timeout(cnx);
|
|
||||||
if (cnx && (timeout <= now)) {
|
|
||||||
if (cfg.verbose > 3)
|
|
||||||
fprintf(stderr, "timed out UDP %d\n", cnx->target_sock);
|
|
||||||
close(cnx->target_sock);
|
|
||||||
FD_CLR(i, &fd_info->fds_r);
|
|
||||||
FD_CLR(i, &fd_info->fds_w);
|
|
||||||
collection_remove_cnx(fd_info->collection, cnx);
|
|
||||||
} else {
|
|
||||||
if (timeout < next_timeout) next_timeout = timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next_timeout != INT_MAX)
|
|
||||||
fd_info->next_timeout = next_timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main loop: the idea is as follow:
|
/* Main loop: the idea is as follow:
|
||||||
* - fds_r and fds_w contain the file descriptors to monitor in read and write
|
* - fds_r and fds_w contain the file descriptors to monitor in read and write
|
||||||
@ -518,48 +124,42 @@ static void udp_timeouts(struct select_info* fd_info)
|
|||||||
*/
|
*/
|
||||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||||
{
|
{
|
||||||
struct select_info fd_info = {0};
|
struct loop_info fd_info = {0};
|
||||||
fd_set readfds, writefds; /* working read and write fd sets */
|
fd_set readfds, writefds; /* working read and write fd sets */
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
int i, res;
|
int i, res;
|
||||||
|
|
||||||
fd_info.num_probing = 0;
|
fd_info.num_probing = 0;
|
||||||
FD_ZERO(&fd_info.fds_r);
|
fd_info.probing_list = gap_init(0);
|
||||||
FD_ZERO(&fd_info.fds_w);
|
udp_init(&fd_info);
|
||||||
fd_info.probing_list = gap_init();
|
tcp_init();
|
||||||
|
|
||||||
for (i = 0; i < num_addr_listen; i++) {
|
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
|
||||||
FD_SET(listen_sockets[i].socketfd, &fd_info.fds_r);
|
|
||||||
set_nonblock(listen_sockets[i].socketfd);
|
|
||||||
}
|
|
||||||
fd_info.max_fd = listen_sockets[num_addr_listen-1].socketfd + 1;
|
|
||||||
|
|
||||||
fd_info.collection = collection_init();
|
fd_info.collection = collection_init(fd_info.watchers->max_fd);
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
memset(&tv, 0, sizeof(tv));
|
memset(&tv, 0, sizeof(tv));
|
||||||
tv.tv_sec = cfg.timeout;
|
tv.tv_sec = cfg.timeout;
|
||||||
|
|
||||||
memcpy(&readfds, &fd_info.fds_r, sizeof(readfds));
|
memcpy(&readfds, &fd_info.watchers->fds_r, sizeof(readfds));
|
||||||
memcpy(&writefds, &fd_info.fds_w, sizeof(writefds));
|
memcpy(&writefds, &fd_info.watchers->fds_w, sizeof(writefds));
|
||||||
|
|
||||||
if (cfg.verbose)
|
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
|
||||||
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n",
|
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||||
fd_info.max_fd, fd_info.num_probing);
|
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
|
||||||
res = select(fd_info.max_fd, &readfds, &writefds,
|
|
||||||
NULL, fd_info.num_probing ? &tv : NULL);
|
NULL, fd_info.num_probing ? &tv : NULL);
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
perror("select");
|
perror("select");
|
||||||
|
|
||||||
|
|
||||||
/* UDP timeouts: clear out connections after some idle time */
|
|
||||||
udp_timeouts(&fd_info);
|
|
||||||
|
|
||||||
/* Check main socket for new connections */
|
/* Check main socket for new connections */
|
||||||
for (i = 0; i < num_addr_listen; i++) {
|
for (i = 0; i < num_addr_listen; i++) {
|
||||||
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
|
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
|
||||||
cnx_accept_process(&fd_info, &listen_sockets[i]);
|
struct connection* new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]);
|
||||||
|
|
||||||
|
if (fd_out_of_range(new_cnx->q[0].fd))
|
||||||
|
tidy_connection(new_cnx, &fd_info);
|
||||||
|
|
||||||
/* don't also process it as a read socket */
|
/* don't also process it as a read socket */
|
||||||
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
||||||
@ -567,8 +167,11 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check all sockets for write activity */
|
/* Check all sockets for write activity */
|
||||||
for (i = 0; i < fd_info.max_fd; i++) {
|
for (i = 0; i < fd_info.watchers->max_fd; i++) {
|
||||||
if (FD_ISSET(i, &writefds)) {
|
/* Check if it's active AND currently monitored (if a connection
|
||||||
|
* died, it gets tidied, which closes both sockets, but writefs does
|
||||||
|
* not know about that */
|
||||||
|
if (FD_ISSET(i, &writefds) && FD_ISSET(i, &fd_info.watchers->fds_w)) {
|
||||||
cnx_write_process(&fd_info, i);
|
cnx_write_process(&fd_info, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,24 +180,23 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
|||||||
for (i = 0; i < fd_info.num_probing; i++) {
|
for (i = 0; i < fd_info.num_probing; i++) {
|
||||||
struct connection* cnx = gap_get(fd_info.probing_list, i);
|
struct connection* cnx = gap_get(fd_info.probing_list, i);
|
||||||
if (!cnx || cnx->state != ST_PROBING) {
|
if (!cnx || cnx->state != ST_PROBING) {
|
||||||
log_message(LOG_ERR, "Inconsistent probing: cnx=%0xp\n", cnx);
|
print_message(msg_int_error, "Inconsistent probing: cnx=0x%p\n", cnx);
|
||||||
if (cnx)
|
if (cnx)
|
||||||
log_message(LOG_ERR, "Inconsistent probing: state=%d\n", cnx);
|
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx->state);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (cnx->probe_timeout < time(NULL)) {
|
if (cnx->probe_timeout < time(NULL)) {
|
||||||
if (cfg.verbose)
|
print_message(msg_fd, "timeout slot %d\n", i);
|
||||||
fprintf(stderr, "timeout slot %d\n", i);
|
|
||||||
probing_read_process(cnx, &fd_info);
|
probing_read_process(cnx, &fd_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check all sockets for read activity */
|
/* Check all sockets for read activity */
|
||||||
for (i = 0; i < fd_info.max_fd; i++) {
|
for (i = 0; i < fd_info.watchers->max_fd; i++) {
|
||||||
/* Check if it's active AND currently monitored (if a connection
|
/* Check if it's active AND currently monitored (if a connection
|
||||||
* died, it gets tidied, which closes both sockets, but readfs does
|
* died, it gets tidied, which closes both sockets, but readfs does
|
||||||
* not know about that */
|
* not know about that */
|
||||||
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.fds_r)) {
|
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.watchers->fds_r)) {
|
||||||
cnx_read_process(&fd_info, i);
|
cnx_read_process(&fd_info, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -603,7 +205,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
|||||||
|
|
||||||
|
|
||||||
void start_shoveler(int listen_socket) {
|
void start_shoveler(int listen_socket) {
|
||||||
fprintf(stderr, "inetd mode is not supported in select mode\n");
|
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
63
sslhconf.cfg
63
sslhconf.cfg
@ -25,7 +25,34 @@ config: {
|
|||||||
name : "sslhcfg",
|
name : "sslhcfg",
|
||||||
type: "list",
|
type: "list",
|
||||||
items: (
|
items: (
|
||||||
{ name: "verbose"; type: "int"; default: 0; short: "v"; },
|
{ name: "verbose-config"; type: "int"; default: 0;
|
||||||
|
description: "Print configuration at startup" },
|
||||||
|
{ name: "verbose-config-error"; type: "int"; default: 3;
|
||||||
|
description: "Print configuration errors" },
|
||||||
|
{ name: "verbose-connections"; type: "int"; default: 3;
|
||||||
|
description: "Trace established incoming address to forward address" },
|
||||||
|
{ name: "verbose-connections-try"; type: "int"; default: 0;
|
||||||
|
description: "Connection errors" },
|
||||||
|
{ name: "verbose-connections-error"; type: "int"; default: 3;
|
||||||
|
description: "Connection attempts towards targets" },
|
||||||
|
{ name: "verbose-fd"; type: "int"; default: 0;
|
||||||
|
description: "File descriptor activity, open/close/whatnot" },
|
||||||
|
{ name: "verbose-packets"; type: "int"; default: 0;
|
||||||
|
description: "Hexdump packets on which probing is done" },
|
||||||
|
|
||||||
|
{ name: "verbose-probe-info"; type: "int"; default: 0;
|
||||||
|
description: "Trace the probe process" },
|
||||||
|
{ name: "verbose-probe-error"; type: "int"; default: 3;
|
||||||
|
description: "Failures and problems during probing" },
|
||||||
|
|
||||||
|
{ name: "verbose-system-error"; type: "int"; default: 3;
|
||||||
|
description: "System call failures" },
|
||||||
|
{ name: "verbose-int-error"; type: "int"; default: 3;
|
||||||
|
description: "Internal errors that should never happen" },
|
||||||
|
|
||||||
|
{ name: "version"; type: "bool"; default: false;
|
||||||
|
short: "V";
|
||||||
|
description: "Print version information and exit"; },
|
||||||
{ name: "foreground"; type: "bool"; default: false;
|
{ name: "foreground"; type: "bool"; default: false;
|
||||||
short: "f";
|
short: "f";
|
||||||
description: "Run in foreground instead of as a daemon"; },
|
description: "Run in foreground instead of as a daemon"; },
|
||||||
@ -40,6 +67,8 @@ config: {
|
|||||||
{ name: "timeout"; type: "int"; default: 5;
|
{ name: "timeout"; type: "int"; default: 5;
|
||||||
short: "t";
|
short: "t";
|
||||||
description: "Set up timeout before connecting to default target"; },
|
description: "Set up timeout before connecting to default target"; },
|
||||||
|
{ name: "udp_max_connections"; type: "int"; default: 1024;
|
||||||
|
description: "Number of concurrent UDP connections"; },
|
||||||
{ name: "user"; type: "string"; optional: true;
|
{ name: "user"; type: "string"; optional: true;
|
||||||
short: "u";
|
short: "u";
|
||||||
description: "Username to change to after set-up"; },
|
description: "Username to change to after set-up"; },
|
||||||
@ -51,6 +80,8 @@ config: {
|
|||||||
description: "Root to change to after set-up"; },
|
description: "Root to change to after set-up"; },
|
||||||
{ name: "syslog_facility"; type: "string"; default: "auth";
|
{ name: "syslog_facility"; type: "string"; default: "auth";
|
||||||
description: "Facility to syslog to"; },
|
description: "Facility to syslog to"; },
|
||||||
|
{ name: "logfile"; type: "string"; optional: true;
|
||||||
|
description: "Log messages to a file" },
|
||||||
|
|
||||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||||
description: "Target to connect to when timing out"; },
|
description: "Target to connect to when timing out"; },
|
||||||
@ -81,8 +112,10 @@ config: {
|
|||||||
{ name: "fork"; type: "bool"; default: false },
|
{ name: "fork"; type: "bool"; default: false },
|
||||||
{ name: "tfo_ok"; type: "bool"; default: false;
|
{ name: "tfo_ok"; type: "bool"; default: false;
|
||||||
description: "Set to true if this protocol supports TCP FAST OPEN" },
|
description: "Set to true if this protocol supports TCP FAST OPEN" },
|
||||||
{ name: "transparent"; type: "bool"; default: false;
|
{ name: "transparent"; type: "bool"; default: false;
|
||||||
description: "Set to proxy this protocol transparently" },
|
description: "Set to proxy this protocol transparently" },
|
||||||
|
{ name: "resolve_on_forward"; type: "bool"; default: false;
|
||||||
|
description: "Set to true if server address should be resolved on (every) newly incoming connection (again)" },
|
||||||
{ name: "log_level"; type: "int"; default: 1 },
|
{ name: "log_level"; type: "int"; default: 1 },
|
||||||
{ name: "keepalive"; type: "bool"; default: false },
|
{ name: "keepalive"; type: "bool"; default: false },
|
||||||
{ name: "sni_hostnames",
|
{ name: "sni_hostnames",
|
||||||
@ -102,7 +135,8 @@ config: {
|
|||||||
# Runtime data
|
# Runtime data
|
||||||
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
|
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
|
||||||
{ name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" },
|
{ name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" },
|
||||||
{ name: "data"; type: "runtime"; c_type: "void*" }
|
{ name: "data"; type: "runtime"; c_type: "void*" },
|
||||||
|
{ name: "timeouts"; type: "runtime"; c_type: "dl_list" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -176,6 +210,18 @@ cl_groups: (
|
|||||||
{ path: "tfo_ok"; value: 1 }
|
{ path: "tfo_ok"; value: 1 }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
{ name: "wireguard"; pattern: "(.+):(\w+)"; description: "Set up WireGuard target";
|
||||||
|
list: "protocols";
|
||||||
|
override: "name";
|
||||||
|
argdesc: "<host:port>";
|
||||||
|
targets: (
|
||||||
|
{ path: "name"; value: "wireguard" },
|
||||||
|
{ path: "host"; value: "$1" },
|
||||||
|
{ path: "port"; value: "$2" },
|
||||||
|
{ path: "log_level"; value: 1 },
|
||||||
|
{ path: "tfo_ok"; value: 1 }
|
||||||
|
);
|
||||||
|
},
|
||||||
{ name: "xmpp"; pattern: "(.+):(\w+)"; description: "Set up XMPP target";
|
{ name: "xmpp"; pattern: "(.+):(\w+)"; description: "Set up XMPP target";
|
||||||
list: "protocols";
|
list: "protocols";
|
||||||
override: "name";
|
override: "name";
|
||||||
@ -231,6 +277,17 @@ cl_groups: (
|
|||||||
{ path: "log_level"; value: 1 }
|
{ path: "log_level"; value: 1 }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
{ name: "msrdp"; pattern: "(.+):(\w+)"; description: "Set up msrdp target";
|
||||||
|
list: "protocols";
|
||||||
|
override: "name";
|
||||||
|
argdesc: "<host:port>";
|
||||||
|
targets: (
|
||||||
|
{ path: "name"; value: "msrdp" },
|
||||||
|
{ path: "host"; value: "$1" },
|
||||||
|
{ path: "port"; value: "$2" },
|
||||||
|
{ path: "log_level"; value: 1 }
|
||||||
|
);
|
||||||
|
},
|
||||||
{ name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target";
|
{ name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target";
|
||||||
list: "protocols";
|
list: "protocols";
|
||||||
override: "name";
|
override: "name";
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||||
|
|
||||||
static char* resolve_listen(const char *hostname, const char *port) {
|
static char* resolve_listen(const char *hostname, const char *port) {
|
||||||
/* Need room in the strcat for \0 and :
|
/* Need room in the strcat for \0 and :
|
||||||
* the format in the socket unit file is hostname:port */
|
* the format in the socket unit file is hostname:port */
|
||||||
@ -53,9 +55,12 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
|
|||||||
config_setting_source_line(addr));
|
config_setting_source_line(addr));
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
|
char *resolved_listen = resolve_listen(hostname, port);
|
||||||
|
|
||||||
|
(*listen)[i] = malloc(strlen(resolved_listen));
|
||||||
CHECK_ALLOC((*listen)[i], "malloc");
|
CHECK_ALLOC((*listen)[i], "malloc");
|
||||||
strcpy((*listen)[i], resolve_listen(hostname, port));
|
strcpy((*listen)[i], resolved_listen);
|
||||||
|
free(resolved_listen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,6 +128,7 @@ static int gen_sslh_config(char *runtime_unit_dir) {
|
|||||||
strcpy(runtime_conf, runtime_unit_dir);
|
strcpy(runtime_conf, runtime_unit_dir);
|
||||||
strcat(runtime_conf, unit_file);
|
strcat(runtime_conf, unit_file);
|
||||||
runtime_conf_fd = fopen(runtime_conf, "w");
|
runtime_conf_fd = fopen(runtime_conf, "w");
|
||||||
|
free(runtime_conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
16
t
16
t
@ -26,6 +26,7 @@ my $PROBES_NOFRAG = 1;
|
|||||||
my $PROBES_AGAIN = 1;
|
my $PROBES_AGAIN = 1;
|
||||||
my $SSL_MIX_SSH = 1;
|
my $SSL_MIX_SSH = 1;
|
||||||
my $SSH_MIX_SSL = 1;
|
my $SSH_MIX_SSL = 1;
|
||||||
|
my $DROP_CNX = 1;
|
||||||
|
|
||||||
# Robustness tests. These are mostly to achieve full test
|
# Robustness tests. These are mostly to achieve full test
|
||||||
# coverage, but do not necessarily result in an actual test
|
# coverage, but do not necessarily result in an actual test
|
||||||
@ -285,6 +286,19 @@ for my $binary (@binaries) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Test: Drop connection without writing anything
|
||||||
|
if ($DROP_CNX) {
|
||||||
|
print "***Test: Connect but don't write anything\n";
|
||||||
|
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||||
|
warn "$!\n" unless $cnx_h;
|
||||||
|
if ($cnx_h) {
|
||||||
|
close $cnx_h;
|
||||||
|
my_is(1, "$binary: Connect and write nothing");
|
||||||
|
# The goal of the test is to check sslh doesn't
|
||||||
|
# crash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($PROBES_NOFRAG) {
|
if ($PROBES_NOFRAG) {
|
||||||
test_probes(no_frag => 1, binary => $binary);
|
test_probes(no_frag => 1, binary => $binary);
|
||||||
@ -382,7 +396,7 @@ if ($RB_RESOLVE_ADDRESS) {
|
|||||||
my $sslh_pid;
|
my $sslh_pid;
|
||||||
if (!($sslh_pid = fork)) {
|
if (!($sslh_pid = fork)) {
|
||||||
my $user = (getpwuid $<)[0]; # Run under current username
|
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";
|
warn "spawned $sslh_pid\n";
|
||||||
waitpid $sslh_pid, 0;
|
waitpid $sslh_pid, 0;
|
||||||
|
8
t_load
8
t_load
@ -20,7 +20,7 @@ use Conf::Libconfig;
|
|||||||
|
|
||||||
# How many total clients to we start? Each client will pick
|
# How many total clients to we start? Each client will pick
|
||||||
# a new protocol among what's in test.cfg.
|
# a new protocol among what's in test.cfg.
|
||||||
my $NUM_CNX = 50;
|
my $NUM_CNX = 16;
|
||||||
|
|
||||||
# Delay between starting new processes when starting up. If
|
# Delay between starting new processes when starting up. If
|
||||||
# you start 200 processes in under a second, things go wrong
|
# you start 200 processes in under a second, things go wrong
|
||||||
@ -35,7 +35,7 @@ my $block_rpt = 5;
|
|||||||
# Probability to stop a client after a message (e.g. with
|
# Probability to stop a client after a message (e.g. with
|
||||||
# .01 a client will send an average of 100 messages before
|
# .01 a client will send an average of 100 messages before
|
||||||
# disconnecting).
|
# disconnecting).
|
||||||
my $stop_client_probability = .001;
|
my $stop_client_probability = .0001;
|
||||||
|
|
||||||
##END CONFIG
|
##END CONFIG
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ my %connect_params = (
|
|||||||
test_data => "foo bar",
|
test_data => "foo bar",
|
||||||
resp_len => 12,
|
resp_len => 12,
|
||||||
},
|
},
|
||||||
ssh => {
|
ssh => {
|
||||||
sleep => 20, # So it times out 50% of connections
|
sleep => 20, # So it times out 50% of connections
|
||||||
test_data => "SSH-2.0 hello",
|
test_data => "SSH-2.0 hello",
|
||||||
resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
|
resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
|
||||||
@ -73,7 +73,7 @@ my %connect_params = (
|
|||||||
tinc => {
|
tinc => {
|
||||||
sleep => 0,
|
sleep => 0,
|
||||||
test_data => "0 ",
|
test_data => "0 ",
|
||||||
resp_len => 8, # length "tinc: 0 " => 10
|
resp_len => 8, # length "tinc: 0 " => 10
|
||||||
},
|
},
|
||||||
openvpn => {
|
openvpn => {
|
||||||
sleep => 0,
|
sleep => 0,
|
||||||
|
333
tcp-listener.c
Normal file
333
tcp-listener.c
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/*
|
||||||
|
tcp-listener.c: handles demultiplexing TCP protocols
|
||||||
|
|
||||||
|
# Copyright (C) 2022 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 <errno.h>
|
||||||
|
#include "tcp-listener.h"
|
||||||
|
#include "probe.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
/* Removes cnx from probing list */
|
||||||
|
static void remove_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||||
|
{
|
||||||
|
gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing);
|
||||||
|
fd_info->num_probing--;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_probing_cnx(struct loop_info* fd_info, struct connection* cnx)
|
||||||
|
{
|
||||||
|
gap_set(fd_info->probing_list, fd_info->num_probing, cnx);
|
||||||
|
fd_info->num_probing++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shovels data from active fd to the other
|
||||||
|
returns after one socket closed or operation would block
|
||||||
|
*/
|
||||||
|
static void shovel(struct connection *cnx, int active_fd, struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
struct queue *read_q, *write_q;
|
||||||
|
|
||||||
|
read_q = &cnx->q[active_fd];
|
||||||
|
write_q = &cnx->q[1-active_fd];
|
||||||
|
|
||||||
|
print_message(msg_fd, "activity on fd%d\n", read_q->fd);
|
||||||
|
|
||||||
|
switch(fd2fd(write_q, read_q)) {
|
||||||
|
case -1:
|
||||||
|
case FD_CNXCLOSED:
|
||||||
|
tidy_connection(cnx, fd_info);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FD_STALLED:
|
||||||
|
watchers_add_write(fd_info->watchers, write_q->fd);
|
||||||
|
watchers_del_read(fd_info->watchers, read_q->fd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* Nothing */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process a TCP read event on the specified file descriptor */
|
||||||
|
void tcp_read_process(struct loop_info* fd_info,
|
||||||
|
int fd)
|
||||||
|
{
|
||||||
|
cnx_collection* collection = fd_info->collection;
|
||||||
|
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
||||||
|
|
||||||
|
/* connection can get tidied if there is an error on the other file
|
||||||
|
* descriptor -- then cnx is NULL */
|
||||||
|
if (!cnx) return;
|
||||||
|
|
||||||
|
/* Determine active queue (0 or 1): if fd is that of q[1], active_q = 1,
|
||||||
|
* otherwise it's 0 */
|
||||||
|
int active_q = active_queue(cnx, fd);
|
||||||
|
|
||||||
|
switch (cnx->state) {
|
||||||
|
|
||||||
|
case ST_PROBING:
|
||||||
|
if (active_q == 1) {
|
||||||
|
print_message(msg_int_error, "Activity on fd2 while probing, impossible\n");
|
||||||
|
dump_connection(cnx);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
probing_read_process(cnx, fd_info);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ST_SHOVELING:
|
||||||
|
shovel(cnx, active_q, fd_info);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* illegal */
|
||||||
|
print_message(msg_int_error, "Illegal connection state %d\n", cnx->state);
|
||||||
|
dump_connection(cnx);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accepts a connection from the main socket and assigns it to an empty slot.
|
||||||
|
* If no slots are available, allocate another few. If that fails, drop the
|
||||||
|
* connexion */
|
||||||
|
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
int in_socket, res;
|
||||||
|
|
||||||
|
print_message(msg_fd, "accepting from %d\n", listen_socket);
|
||||||
|
|
||||||
|
in_socket = accept(listen_socket, 0, 0);
|
||||||
|
CHECK_RES_RETURN(in_socket, "accept", NULL);
|
||||||
|
|
||||||
|
res = set_nonblock(in_socket);
|
||||||
|
if (res == -1) {
|
||||||
|
close(in_socket);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct connection* cnx = collection_alloc_cnx_from_fd(fd_info->collection, in_socket);
|
||||||
|
if (!cnx) {
|
||||||
|
close(in_socket);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_probing_cnx(fd_info, cnx);
|
||||||
|
return cnx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Connect queue 1 of connection to SSL; returns new file descriptor */
|
||||||
|
static int connect_queue(struct connection* cnx,
|
||||||
|
struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
struct queue *q = &cnx->q[1];
|
||||||
|
|
||||||
|
q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING);
|
||||||
|
if (q->fd != -1) {
|
||||||
|
log_connection(NULL, cnx);
|
||||||
|
flush_deferred(q);
|
||||||
|
if (q->deferred_data) {
|
||||||
|
watchers_add_write(fd_info->watchers, q->fd);
|
||||||
|
watchers_del_read(fd_info->watchers, cnx->q[0].fd);
|
||||||
|
}
|
||||||
|
watchers_add_read(fd_info->watchers, q->fd);
|
||||||
|
collection_add_fd(fd_info->collection, cnx, q->fd);
|
||||||
|
return q->fd;
|
||||||
|
} else {
|
||||||
|
tidy_connection(cnx, fd_info);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shovels data from one fd to the other and vice-versa
|
||||||
|
returns after one socket closed
|
||||||
|
*/
|
||||||
|
static void shovel_single(struct connection *cnx)
|
||||||
|
{
|
||||||
|
fd_set fds_r, fds_w;
|
||||||
|
int res, i;
|
||||||
|
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
|
||||||
|
|
||||||
|
FD_ZERO(&fds_r);
|
||||||
|
FD_ZERO(&fds_w);
|
||||||
|
while (1) {
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
if (cnx->q[i].deferred_data_size) {
|
||||||
|
FD_SET(cnx->q[i].fd, &fds_w);
|
||||||
|
FD_CLR(cnx->q[1-i].fd, &fds_r);
|
||||||
|
} else {
|
||||||
|
FD_CLR(cnx->q[i].fd, &fds_w);
|
||||||
|
FD_SET(cnx->q[1-i].fd, &fds_r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = select(
|
||||||
|
max_fd,
|
||||||
|
&fds_r,
|
||||||
|
&fds_w,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
CHECK_RES_DIE(res, "select");
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
if (FD_ISSET(cnx->q[i].fd, &fds_w)) {
|
||||||
|
res = flush_deferred(&cnx->q[i]);
|
||||||
|
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
||||||
|
print_message(msg_fd, "%s socket closed\n", i ? "server" : "client");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FD_ISSET(cnx->q[i].fd, &fds_r)) {
|
||||||
|
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
||||||
|
if (!res) {
|
||||||
|
print_message(msg_fd, "socket closed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Child process that makes internal connection and proxies
|
||||||
|
*/
|
||||||
|
static void connect_proxy(struct connection *cnx)
|
||||||
|
{
|
||||||
|
int in_socket;
|
||||||
|
int out_socket;
|
||||||
|
|
||||||
|
/* Minimize the file descriptor value to help select() */
|
||||||
|
in_socket = dup(cnx->q[0].fd);
|
||||||
|
if (in_socket == -1) {
|
||||||
|
in_socket = cnx->q[0].fd;
|
||||||
|
} else {
|
||||||
|
close(cnx->q[0].fd);
|
||||||
|
cnx->q[0].fd = in_socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect the target socket */
|
||||||
|
out_socket = connect_addr(cnx, in_socket, BLOCKING);
|
||||||
|
CHECK_RES_DIE(out_socket, "connect");
|
||||||
|
|
||||||
|
cnx->q[1].fd = out_socket;
|
||||||
|
|
||||||
|
log_connection(NULL, cnx);
|
||||||
|
|
||||||
|
shovel_single(cnx);
|
||||||
|
|
||||||
|
close(in_socket);
|
||||||
|
close(out_socket);
|
||||||
|
|
||||||
|
print_message(msg_fd, "connection closed down\n");
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process read activity on a socket in probe state
|
||||||
|
* IN/OUT cnx: connection data, updated if connected
|
||||||
|
* IN/OUT info: updated if connected
|
||||||
|
* */
|
||||||
|
void probing_read_process(struct connection* cnx,
|
||||||
|
struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
|
||||||
|
/* If timed out it's SSH, otherwise the client sent
|
||||||
|
* data so probe the protocol */
|
||||||
|
if ((cnx->probe_timeout < time(NULL))) {
|
||||||
|
cnx->proto = timeout_protocol();
|
||||||
|
print_message(msg_fd, "timed out, connect to %s\n", cnx->proto->name);
|
||||||
|
} else {
|
||||||
|
res = probe_client_protocol(cnx);
|
||||||
|
if (res == PROBE_AGAIN)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_probing_cnx(fd_info, cnx);
|
||||||
|
cnx->state = ST_SHOVELING;
|
||||||
|
|
||||||
|
/* libwrap check if required for this protocol */
|
||||||
|
if (cnx->proto->service &&
|
||||||
|
check_access_rights(cnx->q[0].fd, cnx->proto->service)) {
|
||||||
|
tidy_connection(cnx, fd_info);
|
||||||
|
res = -1;
|
||||||
|
} else if (cnx->proto->fork) {
|
||||||
|
switch (fork()) {
|
||||||
|
case 0: /* child */
|
||||||
|
/* TODO: close all file descriptors except 2 */
|
||||||
|
/* free(cnx); */
|
||||||
|
connect_proxy(cnx);
|
||||||
|
exit(0);
|
||||||
|
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||||
|
break;
|
||||||
|
default: /* parent */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tidy_connection(cnx, fd_info);
|
||||||
|
res = -1;
|
||||||
|
} else {
|
||||||
|
res = connect_queue(cnx, fd_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Process a connection that is active in write (this is TCP only, as
|
||||||
|
* UDP sockets are never "full" or deferred or whatever) */
|
||||||
|
void cnx_write_process(struct loop_info* fd_info, int fd)
|
||||||
|
{
|
||||||
|
struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd);
|
||||||
|
|
||||||
|
/* connection can get tidied if there is an error on the other file
|
||||||
|
* descriptor -- then cnx is NULL */
|
||||||
|
if (!cnx) return;
|
||||||
|
|
||||||
|
int res;
|
||||||
|
int queue = active_queue(cnx, fd);
|
||||||
|
|
||||||
|
res = flush_deferred(&cnx->q[queue]);
|
||||||
|
if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) {
|
||||||
|
if (cnx->state == ST_PROBING) remove_probing_cnx(fd_info, cnx);
|
||||||
|
tidy_connection(cnx, fd_info);
|
||||||
|
} else {
|
||||||
|
/* If no deferred data is left, stop monitoring the fd
|
||||||
|
* for write, and restart monitoring the other one for reads*/
|
||||||
|
if (!cnx->q[queue].deferred_data_size) {
|
||||||
|
watchers_del_write(fd_info->watchers, cnx->q[queue].fd);
|
||||||
|
watchers_add_read(fd_info->watchers, cnx->q[1-queue].fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
13
tcp-listener.h
Normal file
13
tcp-listener.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef TCP_LISTENER_H
|
||||||
|
#define TCP_LISTENER_H
|
||||||
|
|
||||||
|
#include "processes.h"
|
||||||
|
#include "collection.h"
|
||||||
|
#include "tcp-probe.h"
|
||||||
|
|
||||||
|
void tcp_read_process(struct loop_info* fd_info, int fd);
|
||||||
|
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info);
|
||||||
|
void probing_read_process(struct connection* cnx, struct loop_info* fd_info);
|
||||||
|
void cnx_write_process(struct loop_info* fd_info, int fd);
|
||||||
|
|
||||||
|
#endif
|
99
tcp-probe.c
Normal file
99
tcp-probe.c
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
# tcp-probe.c: TCP code that is common to the sslh-fork and sslh-[ev|select]
|
||||||
|
#
|
||||||
|
# Copyright (C) 2022 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 "probe.h"
|
||||||
|
|
||||||
|
static struct sslhcfg_protocols_item** tcp_protocols;
|
||||||
|
static int tcp_protocols_len = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the beginning of data coming from the client connection and check if
|
||||||
|
* it's a known protocol.
|
||||||
|
* Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in
|
||||||
|
* which case cnx->proto is set to the appropriate protocol.
|
||||||
|
*/
|
||||||
|
int probe_client_protocol(struct connection *cnx)
|
||||||
|
{
|
||||||
|
char buffer[BUFSIZ];
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
|
||||||
|
/* It's possible that read() returns an error, e.g. if the client
|
||||||
|
* disconnected between the previous call to select() and now. If that
|
||||||
|
* happens, we just connect to the default protocol so the caller of this
|
||||||
|
* function does not have to deal with a specific failure condition (the
|
||||||
|
* connection will just fail later normally). */
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
defer_write(&cnx->q[1], buffer, n);
|
||||||
|
return probe_buffer(cnx->q[1].begin_deferred_data,
|
||||||
|
cnx->q[1].deferred_data_size,
|
||||||
|
tcp_protocols, tcp_protocols_len,
|
||||||
|
&cnx->proto
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read() returned an error, so just connect to the last protocol to die */
|
||||||
|
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
|
||||||
|
return PROBE_MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void tcp_protocol_list_init(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||||
|
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
|
||||||
|
if (!p->is_udp) {
|
||||||
|
tcp_protocols_len++;
|
||||||
|
tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols));
|
||||||
|
CHECK_ALLOC(tcp_protocols, "realloc");
|
||||||
|
tcp_protocols[tcp_protocols_len-1] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configuration sanity check for TCP:
|
||||||
|
* - If there is a listening socket, there must be at least one target
|
||||||
|
*/
|
||||||
|
static void tcp_sanity_check(void)
|
||||||
|
{
|
||||||
|
int tcp_present = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < cfg.listen_len; i++) {
|
||||||
|
struct sslhcfg_listen_item* p = &cfg.listen[i];
|
||||||
|
if (!p->is_udp) {
|
||||||
|
tcp_present = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcp_present && !tcp_protocols_len) {
|
||||||
|
print_message(msg_config_error, "At least one TCP target protocol must be specified.\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_init(void)
|
||||||
|
{
|
||||||
|
tcp_protocol_list_init();
|
||||||
|
tcp_sanity_check();
|
||||||
|
}
|
6
tcp-probe.h
Normal file
6
tcp-probe.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#ifndef TCP_PROBE_H
|
||||||
|
#define TCP_PROBE_H
|
||||||
|
|
||||||
|
void tcp_init(void);
|
||||||
|
|
||||||
|
#endif
|
20
test.cfg
20
test.cfg
@ -1,7 +1,6 @@
|
|||||||
# Configuration file for testing (use both by sslh under
|
# Configuration file for testing (use both by sslh under
|
||||||
# test and the test script `t`)
|
# test and the test script `t`)
|
||||||
|
|
||||||
verbose: 4;
|
|
||||||
foreground: true;
|
foreground: true;
|
||||||
inetd: false;
|
inetd: false;
|
||||||
numeric: true;
|
numeric: true;
|
||||||
@ -11,6 +10,21 @@ pidfile: "/tmp/sslh_test.pid";
|
|||||||
|
|
||||||
syslog_facility: "auth";
|
syslog_facility: "auth";
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
# Value: 1: stdout; 2: syslog; 3: both
|
||||||
|
# Defaults should be sensible. Generally, you want *-error
|
||||||
|
# to be always enabled, to know if something is going wrong.
|
||||||
|
verbose-config: 1; # print configuration at startup
|
||||||
|
verbose-config-error: 1; # print configuration errors
|
||||||
|
verbose-connections: 1; # trace established incoming address to forward address
|
||||||
|
verbose-connections-error: 1; # connection errors
|
||||||
|
verbose-connections-try: 1; # connection attempts towards targets
|
||||||
|
verbose-fd: 0; # file descriptor activity, open/close/whatnot
|
||||||
|
verbose-packets: 1; # hexdump packets on which probing is done
|
||||||
|
verbose-probe-info: 0; # what's happening during the probe process
|
||||||
|
verbose-probe-error: 1; # failures and problems during probing
|
||||||
|
verbose-system-error: 1; # system call problem, i.e. malloc, fork, failing
|
||||||
|
verbose-int-error: 1; # internal errors, the kind that should never happen
|
||||||
|
|
||||||
# List of interfaces on which we should listen
|
# List of interfaces on which we should listen
|
||||||
# Options:
|
# Options:
|
||||||
@ -21,6 +35,10 @@ listen:
|
|||||||
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
|
{ host: "ip4-localhost"; is_udp: true; port: "8086"; }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
# Tester beware: when using fork, the forked process loses
|
||||||
|
# track of buffers of other, concurrent connections. Memory
|
||||||
|
# leak tools thus complain each time a forked process stops.
|
||||||
|
|
||||||
protocols:
|
protocols:
|
||||||
(
|
(
|
||||||
|
15
tls.c
15
tls.c
@ -33,6 +33,7 @@
|
|||||||
#include <fnmatch.h> /* fnmatch() */
|
#include <fnmatch.h> /* fnmatch() */
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
#include "sslh-conf.h"
|
#include "sslh-conf.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#define TLS_HEADER_LEN 5
|
#define TLS_HEADER_LEN 5
|
||||||
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
|
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
|
||||||
@ -82,14 +83,14 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
|||||||
|
|
||||||
tls_content_type = data[0];
|
tls_content_type = data[0];
|
||||||
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||||
if (cfg.verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
|
print_message(msg_probe_info, "Request did not begin with TLS handshake.\n");
|
||||||
return TLS_EPROTOCOL;
|
return TLS_EPROTOCOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
tls_version_major = data[1];
|
tls_version_major = data[1];
|
||||||
tls_version_minor = data[2];
|
tls_version_minor = data[2];
|
||||||
if (tls_version_major < 3) {
|
if (tls_version_major < 3) {
|
||||||
if (cfg.verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
|
print_message(msg_probe_error, "Received SSL %d.%d handshake which cannot be parsed.\n",
|
||||||
tls_version_major, tls_version_minor);
|
tls_version_major, tls_version_minor);
|
||||||
|
|
||||||
return TLS_EVERSION;
|
return TLS_EVERSION;
|
||||||
@ -111,7 +112,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
|||||||
return TLS_EPROTOCOL;
|
return TLS_EPROTOCOL;
|
||||||
}
|
}
|
||||||
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
|
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
|
||||||
if (cfg.verbose) fprintf(stderr, "Not a client hello\n");
|
print_message(msg_probe_error, "Not a client hello\n");
|
||||||
|
|
||||||
return TLS_EPROTOCOL;
|
return TLS_EPROTOCOL;
|
||||||
}
|
}
|
||||||
@ -144,7 +145,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
|||||||
pos += 1 + len;
|
pos += 1 + len;
|
||||||
|
|
||||||
if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
|
if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
|
||||||
if (cfg.verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n");
|
print_message(msg_probe_error, "Received SSL 3.0 handshake without extensions\n");
|
||||||
return TLS_EVERSION;
|
return TLS_EVERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +229,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
|
|||||||
return TLS_ENOEXT;
|
return TLS_ENOEXT;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (cfg.verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
|
print_message(msg_probe_error, "Unknown server name extension name type: %d\n",
|
||||||
data[pos]);
|
data[pos]);
|
||||||
}
|
}
|
||||||
pos += 3 + len;
|
pos += 3 + len;
|
||||||
@ -254,7 +255,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
|
|||||||
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
|
if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) {
|
||||||
return len;
|
return len;
|
||||||
} else if (len > 0) {
|
} else if (len > 0) {
|
||||||
if (cfg.verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
print_message(msg_probe_error, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
||||||
}
|
}
|
||||||
pos += 1 + len;
|
pos += 1 + len;
|
||||||
}
|
}
|
||||||
@ -276,7 +277,7 @@ has_match(const char** list, size_t list_len, const char* name, size_t name_len)
|
|||||||
|
|
||||||
for (i = 0; i < list_len; i++) {
|
for (i = 0; i < list_len; i++) {
|
||||||
item = &list[i];
|
item = &list[i];
|
||||||
if (cfg.verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
print_message(msg_probe_error, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
||||||
if(!fnmatch(*item, name_nullterminated, 0)) {
|
if(!fnmatch(*item, name_nullterminated, 0)) {
|
||||||
free(name_nullterminated);
|
free(name_nullterminated);
|
||||||
return 1;
|
return 1;
|
||||||
|
290
udp-listener.c
290
udp-listener.c
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
udp-listener.c: handles demultplexing UDP protocols
|
udp-listener.c: handles demultiplexing UDP protocols
|
||||||
|
|
||||||
# Copyright (C) 2020-2021 Yves Rutschle
|
# Copyright (C) 2020-2022 Yves Rutschle
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it
|
# This program is free software; you can redistribute it
|
||||||
# and/or modify it under the terms of the GNU General Public
|
# and/or modify it under the terms of the GNU General Public
|
||||||
@ -20,78 +20,271 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
#include "sslh-conf.h"
|
#include "sslh-conf.h"
|
||||||
#include "udp-listener.h"
|
#include "udp-listener.h"
|
||||||
|
|
||||||
|
/* Incoming connections are of course all received on a single socket. Create a
|
||||||
|
* hash that associates (incoming sockaddr) => struct connection*, so finding
|
||||||
|
* the connection related to an incoming packet is fast.
|
||||||
|
*/
|
||||||
|
|
||||||
/* Find if the specified source has been seen before. -1 if not found
|
|
||||||
*
|
|
||||||
* TODO This is linear search and needs to be changed to something better for
|
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
|
||||||
* production if we have more than a dozen sources
|
|
||||||
* Also, this assumes src_addr from recvfrom() are repeatable for a specific
|
|
||||||
* source...
|
|
||||||
* */
|
|
||||||
static int known_source(cnx_collection* collection, int max_fd, struct sockaddr* addr, socklen_t addrlen)
|
|
||||||
{
|
{
|
||||||
int i;
|
struct sockaddr* addr1 = &cnx1->client_addr;
|
||||||
|
socklen_t addrlen1 = cnx1->addrlen;
|
||||||
|
|
||||||
for (i = 0; i < max_fd; i++) {
|
struct sockaddr* addr2 = &cnx2->client_addr;
|
||||||
struct connection* cnx = collection_get_cnx_from_fd(collection, i);
|
socklen_t addrlen2 = cnx2->addrlen;
|
||||||
if (cnx && (cnx->type == SOCK_DGRAM) && cnx->target_sock) {
|
|
||||||
if (!memcmp(&cnx->client_addr, addr, addrlen)) {
|
if (addrlen1 != addrlen2) return -1;
|
||||||
return i;
|
|
||||||
}
|
return memcmp(addr1, addr2, addrlen1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From an IP address, create something that's useable as a hash key.
|
||||||
|
* Currently:
|
||||||
|
* lowest bytes of remote port */
|
||||||
|
static int hash_make_key(hash_item new)
|
||||||
|
{
|
||||||
|
struct sockaddr* addr = &new->client_addr;
|
||||||
|
//socklen_t addrlen = new->addrlen;
|
||||||
|
struct sockaddr_in* addr4;
|
||||||
|
struct sockaddr_in6* addr6;
|
||||||
|
int out;
|
||||||
|
|
||||||
|
switch (addr->sa_family) {
|
||||||
|
case AF_INET:
|
||||||
|
addr4 = (struct sockaddr_in*)addr;
|
||||||
|
out = addr4->sin_port;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AF_INET6:
|
||||||
|
addr6 = (struct sockaddr_in6*)addr;
|
||||||
|
out = addr6->sin6_port;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* Just use the first bytes, skipping the address family */
|
||||||
|
out = ((char*)addr)[2];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sslhcfg_protocols_item** udp_protocols;
|
||||||
|
static int udp_protocols_len = 0;
|
||||||
|
|
||||||
|
static void udp_protocol_list_init(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||||
|
struct sslhcfg_protocols_item* p = &cfg.protocols[i];
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Configuration sanity check for UDP:
|
||||||
|
* - If there is a listening address, there must be at least one target
|
||||||
|
*/
|
||||||
|
static void udp_sanity_check(void)
|
||||||
|
{
|
||||||
|
int udp_present = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < cfg.listen_len; i++) {
|
||||||
|
struct sslhcfg_listen_item* p = &cfg.listen[i];
|
||||||
|
if (p->is_udp) {
|
||||||
|
udp_present = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (udp_present && !udp_protocols_len) {
|
||||||
|
print_message(msg_config_error, "At least one UDP target protocol must be specified.\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init the UDP subsystem.
|
||||||
|
* - Initialise the hash
|
||||||
|
* - that's all, folks
|
||||||
|
* */
|
||||||
|
void udp_init(struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp);
|
||||||
|
|
||||||
|
udp_protocol_list_init();
|
||||||
|
udp_sanity_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Find if the specified source has been seen before.
|
||||||
|
* If yes, returns file descriptor of connection
|
||||||
|
* If not, returns -1
|
||||||
|
* */
|
||||||
|
static int known_source(hash* h, struct sockaddr_storage* addr, socklen_t addrlen)
|
||||||
|
{
|
||||||
|
struct connection search;
|
||||||
|
search.client_addr = *addr;
|
||||||
|
search.addrlen = addrlen;
|
||||||
|
|
||||||
|
struct connection* cnx = hash_find(h, &search);
|
||||||
|
if (!cnx) return -1;
|
||||||
|
return cnx->q[0].fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int new_source(hash* h, struct connection* new)
|
||||||
|
{
|
||||||
|
return hash_insert(h, new);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Double linked list utilities: push element at tail of list */
|
||||||
|
static void list_push(dl_list* list, struct connection* cnx)
|
||||||
|
{
|
||||||
|
cnx->timeout_next = NULL;
|
||||||
|
|
||||||
|
if (!list->head) {
|
||||||
|
cnx->timeout_prev = NULL;
|
||||||
|
list->head = cnx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list->tail) {
|
||||||
|
list->tail->timeout_next = cnx;
|
||||||
|
cnx->timeout_prev = list->tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
list->tail = cnx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Double linked list utilities: remove element */
|
||||||
|
static void list_remove(dl_list* list, struct connection* cnx)
|
||||||
|
{
|
||||||
|
if (list->head == cnx) list->head = cnx->timeout_next;
|
||||||
|
if (list->tail == cnx) list->tail = cnx->timeout_prev;
|
||||||
|
|
||||||
|
if (cnx->timeout_prev)
|
||||||
|
cnx->timeout_prev->timeout_next = cnx->timeout_next;
|
||||||
|
|
||||||
|
if (cnx->timeout_next)
|
||||||
|
cnx->timeout_next->timeout_prev = cnx->timeout_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Timeouts are managed with one list for each protocol. Whenever a connection
|
||||||
|
* is active, it gets moved to the end of the list. Each call will pop the
|
||||||
|
* first elements that have timed out and free their resources.
|
||||||
|
*
|
||||||
|
* This gets called every time a UDP packet is received from the outside, i.e.
|
||||||
|
* every time we might need to free up resources. If no packets come in, we
|
||||||
|
* don't time out anything, as we don't need the resources.
|
||||||
|
* */
|
||||||
|
void udp_timeouts(struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL);
|
||||||
|
|
||||||
|
for (int i = 0; i < cfg.protocols_len; i++) {
|
||||||
|
struct connection *cnx = cfg.protocols[i].timeouts.head;
|
||||||
|
while (cnx && (now - cnx->last_active > cfg.protocols[i].udp_timeout)) {
|
||||||
|
print_message(msg_fd, "timed out UDP %d\n", cnx->target_sock);
|
||||||
|
tidy_connection(cnx, fd_info);
|
||||||
|
|
||||||
|
cnx = cfg.protocols[i].timeouts.head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void udp_tidy(struct connection* cnx, struct loop_info* fd_info)
|
||||||
|
{
|
||||||
|
close(cnx->target_sock);
|
||||||
|
hash_remove(fd_info->hash_sources, cnx);
|
||||||
|
list_remove(&cnx->proto->timeouts, cnx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the connection was active */
|
||||||
|
static void mark_active(struct connection* cnx)
|
||||||
|
{
|
||||||
|
cnx->last_active = time(NULL);
|
||||||
|
|
||||||
|
dl_list* list = &cnx->proto->timeouts;
|
||||||
|
|
||||||
|
list_remove(list, cnx);
|
||||||
|
list_push(list, cnx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Creates a new non-blocking socket */
|
||||||
|
static int nonblocking_socket(struct sslhcfg_protocols_item* proto)
|
||||||
|
{
|
||||||
|
int out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||||
|
int 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)
|
/* Process UDP coming from outside (client towards server)
|
||||||
* If it's a new source, probe; otherwise, forward to previous target
|
* If it's a new source, probe; otherwise, forward to previous target
|
||||||
* Returns: >= 0 sockfd of newly allocated socket, for new connections
|
* Returns: newly allocate connections, for new connections
|
||||||
* -1 otherwise
|
* NULL otherwise
|
||||||
* */
|
* */
|
||||||
int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd)
|
struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info)
|
||||||
{
|
{
|
||||||
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
|
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||||
struct sockaddr src_addr;
|
struct sockaddr_storage src_addr;
|
||||||
struct addrinfo addrinfo;
|
struct addrinfo addrinfo;
|
||||||
struct sslhcfg_protocols_item* proto;
|
struct sslhcfg_protocols_item* proto;
|
||||||
|
cnx_collection* collection = fd_info->collection;
|
||||||
struct connection* cnx;
|
struct connection* cnx;
|
||||||
ssize_t len;
|
ssize_t len;
|
||||||
socklen_t addrlen;
|
socklen_t addrlen;
|
||||||
int res, target, out = -1;
|
int res, target, out = -1;
|
||||||
char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol).
|
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
|
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 */
|
malloc/free overhead for each packet, when really 64K is not that much */
|
||||||
|
|
||||||
|
|
||||||
|
udp_timeouts(fd_info);
|
||||||
|
|
||||||
addrlen = sizeof(src_addr);
|
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) {
|
if (len < 0) {
|
||||||
perror("recvfrom");
|
perror("recvfrom");
|
||||||
return -1;
|
return NULL;
|
||||||
}
|
}
|
||||||
target = known_source(collection, max_fd, &src_addr, addrlen);
|
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;
|
addrinfo.ai_addrlen = addrlen;
|
||||||
if (cfg.verbose)
|
print_message(msg_probe_info, "received %ld UDP from %d:%s\n",
|
||||||
fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||||
|
|
||||||
if (target == -1) {
|
if (target == -1) {
|
||||||
res = probe_buffer(data, len, &proto);
|
res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
|
||||||
/* First version: if we can't work out the protocol from the first
|
/* First version: if we can't work out the protocol from the first
|
||||||
* packet, drop it. Conceivably, we could store several packets to
|
* packet, drop it. Conceivably, we could store several packets to
|
||||||
* run probes on packet sets */
|
* run probes on packet sets */
|
||||||
if (cfg.verbose) fprintf(stderr, "UDP probed: %d\n", res);
|
print_message(msg_probe_info, "UDP probed: %d\n", res);
|
||||||
if (res != PROBE_MATCH) {
|
if (res != PROBE_MATCH) {
|
||||||
return -1;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
out = nonblocking_socket(proto);
|
||||||
|
if (out == -1) return NULL;
|
||||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
|
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
|
||||||
if (!cnx) return -1;
|
if (!cnx) return NULL;
|
||||||
target = out;
|
target = out;
|
||||||
cnx->target_sock = out;
|
cnx->target_sock = out;
|
||||||
cnx->proto = proto;
|
cnx->proto = proto;
|
||||||
@ -99,18 +292,25 @@ int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd)
|
|||||||
cnx->client_addr = src_addr;
|
cnx->client_addr = src_addr;
|
||||||
cnx->addrlen = addrlen;
|
cnx->addrlen = addrlen;
|
||||||
cnx->local_endpoint = sockfd;
|
cnx->local_endpoint = sockfd;
|
||||||
|
|
||||||
|
res = new_source(fd_info->hash_sources, cnx);
|
||||||
|
if (res == -1) {
|
||||||
|
print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increase udp_max_connections");
|
||||||
|
collection_remove_cnx(collection, cnx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cnx = collection_get_cnx_from_fd(collection, target);
|
cnx = collection_get_cnx_from_fd(collection, target);
|
||||||
|
|
||||||
/* at this point src is the UDP connection */
|
/* at this point src is the UDP connection */
|
||||||
res = sendto(cnx->target_sock, data, len, 0,
|
res = sendto(cnx->target_sock, data, len, 0,
|
||||||
cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen);
|
cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen);
|
||||||
cnx->last_active = time(NULL);
|
mark_active(cnx);
|
||||||
fprintf(stderr, "sending %d to %s\n",
|
print_message(msg_fd, "sending %d to %s\n",
|
||||||
res, sprintaddr(data, sizeof(data), cnx->proto->saddr));
|
res, sprintaddr(data, sizeof(data), cnx->proto->saddr));
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return cnx;
|
||||||
|
}
|
||||||
|
|
||||||
void udp_s2c_forward(struct connection* cnx)
|
void udp_s2c_forward(struct connection* cnx)
|
||||||
{
|
{
|
||||||
@ -119,20 +319,10 @@ void udp_s2c_forward(struct connection* cnx)
|
|||||||
int res;
|
int res;
|
||||||
|
|
||||||
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
|
||||||
fprintf(stderr, "recvfrom %d\n", res);
|
if ((res == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) return;
|
||||||
CHECK_RES_DIE(res, "udp_listener/recvfrom");
|
CHECK_RES_DIE(res, "udp_listener/recvfrom");
|
||||||
res = sendto(cnx->local_endpoint, data, res, 0,
|
res = sendto(cnx->local_endpoint, data, res, 0,
|
||||||
&cnx->client_addr, cnx->addrlen);
|
&cnx->client_addr, cnx->addrlen);
|
||||||
cnx->last_active = time(NULL);
|
mark_active(cnx);
|
||||||
fprintf(stderr, "sendto %d to\n", res);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* returns date at which this socket times out. */
|
|
||||||
int udp_timeout(struct connection* cnx)
|
|
||||||
{
|
|
||||||
if (cnx->type != SOCK_DGRAM) return 0; /* Not a UDP connection */
|
|
||||||
|
|
||||||
return cnx->proto->udp_timeout + cnx->last_active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
#ifndef UDPLISTENER_H
|
#ifndef UDPLISTENER_H
|
||||||
#define UDPLISTENER_H
|
#define UDPLISTENER_H
|
||||||
|
|
||||||
|
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
|
#include "processes.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
/* UDP listener: upon incoming packet, find where it should go
|
/* UDP listener: upon incoming packet, find where it should go
|
||||||
* This is run in its own process and never returns.
|
* This is run in its own process and never returns.
|
||||||
@ -11,18 +14,16 @@ void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
|||||||
|
|
||||||
/* Process UDP coming from outside (client towards server)
|
/* Process UDP coming from outside (client towards server)
|
||||||
* If it's a new source, probe; otherwise, forward to previous target
|
* If it's a new source, probe; otherwise, forward to previous target
|
||||||
* Returns: >= 0 sockfd of newly allocated socket, for new connections
|
* Returns: newly allocate connections, for new connections
|
||||||
* -1 otherwise
|
* -1 otherwise
|
||||||
* */
|
* */
|
||||||
int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd);
|
struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info);
|
||||||
|
|
||||||
/* Process UDP coming from inside (server towards client) */
|
/* Process UDP coming from inside (server towards client) */
|
||||||
void udp_s2c_forward(struct connection* cnx);
|
void udp_s2c_forward(struct connection* cnx);
|
||||||
|
|
||||||
|
|
||||||
/* returns how many seconds before socket times out. Negative if timed out
|
void udp_init(struct loop_info* fd_info);
|
||||||
* already.
|
void udp_tidy(struct connection* cnx, struct loop_info* fd_info);
|
||||||
*/
|
|
||||||
int udp_timeout(struct connection* cnx);
|
|
||||||
|
|
||||||
#endif /* UDPLISTENER_H */
|
#endif /* UDPLISTENER_H */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user