mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-22 18:12:10 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40c616e94c | ||
|
|
555717e345 | ||
|
|
02573eb44b | ||
|
|
d166b8977c | ||
|
|
4c3b52dda5 | ||
|
|
c981ae9853 | ||
|
|
8a0de7b628 | ||
|
|
5154630fe0 | ||
|
|
63d5ecddca | ||
|
|
e412811ff1 | ||
|
|
7e3f723699 | ||
|
|
a1db2e8a92 | ||
|
|
72c743e1e1 | ||
|
|
5cba44f5fa | ||
|
|
9a36854ed3 | ||
|
|
b19f8a6046 | ||
|
|
bb685f8467 | ||
|
|
f418ae6128 | ||
|
|
a6df18527c | ||
|
|
e690cb5622 | ||
|
|
64c3e0ed1e | ||
|
|
b971f3edcd | ||
|
|
d2ec01c072 | ||
|
|
fb8fe57bd8 | ||
|
|
79c8af6ed1 | ||
|
|
c4e7261a51 | ||
|
|
7e2bb7f01f | ||
|
|
4cf3749e73 | ||
|
|
8d124e1085 | ||
|
|
a6c5e07d69 | ||
|
|
aa17061e26 | ||
|
|
9d10989d55 | ||
|
|
8b604a3db7 | ||
|
|
5168fe081a | ||
|
|
7b923f793e | ||
|
|
a4db163a69 | ||
|
|
b36486bb6d | ||
|
|
d16d132f0e | ||
|
|
3fffce323d | ||
|
|
83478818df | ||
|
|
750e828d49 | ||
|
|
82aeedefcd | ||
|
|
f6fe735171 | ||
|
|
78827d75fe | ||
|
|
7228c0ebc3 | ||
|
|
da194f15f0 | ||
|
|
d23a537d62 | ||
|
|
aa14090bcc | ||
|
|
006706a901 | ||
|
|
f9831df8bc | ||
|
|
c78a50c1d7 | ||
|
|
92e8a3e256 | ||
|
|
cd664574f1 | ||
|
|
449fabba51 | ||
|
|
cd5d75fed9 | ||
|
|
50f5af394b | ||
|
|
8ac93c3e9d | ||
|
|
43bd660df2 | ||
|
|
35036c94c7 | ||
|
|
21d00bd29d | ||
|
|
32b065d895 | ||
|
|
cd7afaa00d | ||
|
|
53ae8bb913 | ||
|
|
97a67500ea | ||
|
|
33d73dd514 | ||
|
|
63b503e27f | ||
|
|
2e0d6b6e8e | ||
|
|
4625883b7f | ||
|
|
07ceb99280 | ||
|
|
5fa03ec9a3 | ||
|
|
9ce9b5cd82 | ||
|
|
97810cf0b2 | ||
|
|
f8684fc9d6 | ||
|
|
5def70dff5 | ||
|
|
478e1fc92f | ||
|
|
96b1aa86d8 | ||
|
|
b1517f27a8 | ||
|
|
454a261c95 | ||
|
|
9c3274359a | ||
|
|
a71de786d6 | ||
|
|
dd167d13a3 | ||
|
|
759e68c8d2 | ||
|
|
78bc954769 | ||
|
|
3f5c81d2f6 | ||
|
|
ff810d41b0 | ||
|
|
d922086f53 | ||
|
|
1e0578c082 | ||
|
|
87577ae5f6 | ||
|
|
875fa488c9 | ||
|
|
91d148f66c | ||
|
|
66f4b18121 | ||
|
|
bccb78e1a3 | ||
|
|
6f497b6c8b | ||
|
|
4e6145576e | ||
|
|
58ed185316 | ||
|
|
122649ce3c | ||
|
|
31b096a0f4 | ||
|
|
adaf407b8a | ||
|
|
4e72800f2e | ||
|
|
18eeaa579a | ||
|
|
80f2d758a4 | ||
|
|
9798bdcaa0 | ||
|
|
93df6975ed | ||
|
|
44a78bc515 | ||
|
|
711c11c820 | ||
|
|
ae117097ea | ||
|
|
0a23ca133e | ||
|
|
207d482189 | ||
|
|
d4d9dbb8e7 | ||
|
|
8ddff5e388 | ||
|
|
a80e2ceb27 | ||
|
|
e28fa91b0f | ||
|
|
17313100b5 | ||
|
|
4cd3ab8958 | ||
|
|
5ec1f7eb98 | ||
|
|
5b21375087 | ||
|
|
d57a155bf4 | ||
|
|
ec033efbbc | ||
|
|
3389000ff3 | ||
|
|
25abd765cb | ||
|
|
2cdd60dd18 | ||
|
|
b0aeeff465 | ||
|
|
c9eff6e38d | ||
|
|
0cde3d794a | ||
|
|
ed48d3964f | ||
|
|
7b0d486d3d | ||
|
|
0e118a109c | ||
|
|
9955cc6560 | ||
|
|
caa62875c1 | ||
|
|
c8fce0a02f | ||
|
|
4277d27063 | ||
|
|
16bf1a6aca | ||
|
|
4f0f5017bc | ||
|
|
70b31a48d9 | ||
|
|
66caf8a31b | ||
|
|
4d3cc9c925 | ||
|
|
2e11001087 | ||
|
|
6ea7d48f86 | ||
|
|
3fb1201b3f | ||
|
|
e9e7ada069 | ||
|
|
e6cbbe9511 | ||
|
|
e5f16b93ce | ||
|
|
673c40954e | ||
|
|
f7b6f669a4 | ||
|
|
dbad46a358 | ||
|
|
098a55fd1d | ||
|
|
5e27806545 | ||
|
|
317c08604b |
No files matched your search
@@ -1,3 +1,32 @@
|
||||
v2.0:
|
||||
New sslh-ev: this is functionaly 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
|
||||
sslh-select now supports UDP protocols.
|
||||
Probes specified in the `protocols`
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ RUN \
|
||||
libconfig-dev \
|
||||
make \
|
||||
musl-dev \
|
||||
pcre-dev \
|
||||
pcre2-dev \
|
||||
perl && \
|
||||
cd /sslh && \
|
||||
make sslh-select && \
|
||||
@@ -18,6 +18,6 @@ FROM alpine:latest
|
||||
|
||||
COPY --from=build /sslh/sslh-select /sslh
|
||||
|
||||
RUN apk --no-cache add libconfig pcre
|
||||
RUN apk --no-cache add libconfig pcre2
|
||||
|
||||
ENTRYPOINT [ "/sslh", "--foreground"]
|
||||
@@ -3,6 +3,7 @@ VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
@@ -19,15 +20,24 @@ MAN=sslh.8.gz # man page name
|
||||
# End of configuration -- the rest should take care of
|
||||
# itself
|
||||
|
||||
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
||||
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(COV_TEST)),)
|
||||
CFLAGS_COV=-fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV)
|
||||
AR ?= ar
|
||||
CFLAGS ?=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
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=
|
||||
|
||||
@@ -64,26 +74,31 @@ endif
|
||||
|
||||
all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||
|
||||
.c.o: *.h version.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
|
||||
%.o: %.c %.h version.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
$(OBJS_A): $(OBJS)
|
||||
$(AR) rcs $(OBJS_A) $(OBJS)
|
||||
|
||||
version.h:
|
||||
./genver.sh >version.h
|
||||
|
||||
sslh: sslh-fork sslh-select
|
||||
sslh: sslh-fork sslh-select sslh-ev
|
||||
|
||||
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
|
||||
|
||||
$(OBJS): version.h common.h collection.h sslh-conf.h gap.h
|
||||
|
||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||
conf2struct sslhconf.cfg
|
||||
|
||||
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
|
||||
#strip sslh-fork
|
||||
sslh-fork: version.h Makefile $(FORK_OBJS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
|
||||
|
||||
sslh-select: version.h $(OBJS) sslh-select.o Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
|
||||
#strip sslh-select
|
||||
sslh-select: version.h $(SELECT_OBJS) Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select $(SELECT_OBJS) $(LIBS)
|
||||
|
||||
sslh-ev: version.h $(EV_OBJS) Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-ev $(EV_OBJS) $(LIBS) -lev
|
||||
|
||||
systemd-sslh-generator: systemd-sslh-generator.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
||||
@@ -133,7 +148,7 @@ distclean: clean
|
||||
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
||||
|
||||
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:
|
||||
ctags --globals -T *.[ch]
|
||||
|
||||
@@ -50,19 +50,18 @@ Build docker image
|
||||
|
||||
|
||||
```bash
|
||||
docker container run \
|
||||
docker run \
|
||||
--rm \
|
||||
-it \
|
||||
sslh:latest \
|
||||
--listen=0.0.0.0:443 \
|
||||
--ssh=hostname:22 \
|
||||
--tlshostname:443 \
|
||||
sslh:latest
|
||||
--tls=hostname:443
|
||||
```
|
||||
|
||||
docker-compose example
|
||||
|
||||
```
|
||||
---
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
@@ -70,19 +69,17 @@ services:
|
||||
image: sslh:latest
|
||||
hostname: sslh
|
||||
ports:
|
||||
- 443:443/tcp
|
||||
command: --listen=0.0.0.0:443 --tlshostname:443 --openvpn=openvpn:1194
|
||||
- 443:443
|
||||
command: --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
|
||||
depends_on:
|
||||
- nginx
|
||||
- openvpn
|
||||
|
||||
nginx:
|
||||
image: nginx
|
||||
hostname: nginx
|
||||
|
||||
openvpn:
|
||||
image: openvpn:latest
|
||||
hostname: openvpn
|
||||
image: openvpn
|
||||
```
|
||||
|
||||
Comments? Questions?
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# This is a basic configuration file that should provide
|
||||
# sensible values for "standard" setup.
|
||||
|
||||
verbose: 0;
|
||||
foreground: false;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
# You will find extensive examples with explanations in
|
||||
# example.cfg
|
||||
|
||||
timeout: 2;
|
||||
user: "nobody";
|
||||
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:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; }
|
||||
);
|
||||
|
||||
|
||||
# Change to the protocols you want to forward to. The
|
||||
# defaults here are sensible for services running on
|
||||
# localhost
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* No code here should assume whether sockets are blocking or not.
|
||||
**/
|
||||
|
||||
#define SYSLOG_NAMES
|
||||
#define _GNU_SOURCE
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
@@ -16,6 +15,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
|
||||
/* 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? */
|
||||
|
||||
|
||||
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
|
||||
|
||||
#ifdef LIBWRAP
|
||||
#include <tcpd.h>
|
||||
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];
|
||||
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s:%s: %s\n",
|
||||
print_message(msg_system_error, "%s:%s: %s\n",
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
syscall,
|
||||
strerror(errno));
|
||||
@@ -77,7 +75,7 @@ int get_fd_sockets(struct listen_endpoint *sockfd[])
|
||||
#ifdef SYSTEMD
|
||||
sd = sd_listen_fds(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);
|
||||
}
|
||||
if (sd > 0) {
|
||||
@@ -177,7 +175,7 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
|
||||
*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++) {
|
||||
keepalive = cfg.listen[i].keepalive;
|
||||
@@ -191,10 +189,9 @@ int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
if (cfg.verbose)
|
||||
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].is_udp ? "udp" : "");
|
||||
print_message(msg_config, "%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].is_udp ? "udp" : "");
|
||||
}
|
||||
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);
|
||||
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) {
|
||||
/* When transparent, make sure both connections use the same address family */
|
||||
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
continue;
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "connecting to %s family %d len %d\n",
|
||||
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
a->ai_addr->sa_family, a->ai_addrlen);
|
||||
|
||||
/* XXX Needs to match ai_family from fd_from when being transparent! */
|
||||
fd = socket(a->ai_family, SOCK_STREAM, 0);
|
||||
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));
|
||||
} else {
|
||||
one = 1;
|
||||
@@ -354,7 +355,7 @@ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
/* EINPROGRESS indicates it might take time. If it eventually
|
||||
* fails, it'll be caught as a failed read */
|
||||
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));
|
||||
close(fd);
|
||||
continue; /* Try the next address */
|
||||
@@ -374,9 +375,8 @@ int defer_write(struct queue *q, void* data, int data_size)
|
||||
{
|
||||
char *p;
|
||||
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);
|
||||
CHECK_ALLOC(p, "realloc");
|
||||
|
||||
@@ -397,8 +397,7 @@ int flush_deferred(struct queue *q)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "flushing deferred data to fd %d\n", q->fd);
|
||||
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
|
||||
|
||||
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
||||
if (n == -1)
|
||||
@@ -430,11 +429,12 @@ void init_cnx(struct connection *cnx)
|
||||
|
||||
void dump_connection(struct connection *cnx)
|
||||
{
|
||||
printf("state: %d\n", cnx->state);
|
||||
printf("0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||
hexdump(cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
|
||||
printf("1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
||||
hexdump(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
|
||||
print_message(msg_int_error, "type: %s\n", cnx->type == SOCK_DGRAM ? "UDP" : "TCP");
|
||||
print_message(msg_int_error, "state: %d\n", cnx->state);
|
||||
print_message(msg_int_error, "0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||
hexdump(msg_int_error, cnx->q[0].deferred_data, cnx->q[0].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 +459,6 @@ int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
if (size_r == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "reading 0 from %d\n", from);
|
||||
return FD_NODATA;
|
||||
|
||||
case ECONNRESET:
|
||||
@@ -511,7 +509,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
|
||||
|
||||
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 */
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
@@ -519,7 +517,7 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
/* should not fail but... */
|
||||
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(serv, "?");
|
||||
}
|
||||
@@ -548,7 +546,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
if (host[0] == '[') {
|
||||
end = strrchr(host, ']');
|
||||
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;
|
||||
}
|
||||
host++; /* skip first bracket */
|
||||
@@ -557,7 +555,7 @@ int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -573,7 +571,7 @@ void resolve_name(struct addrinfo **out, char* fullname)
|
||||
/* Find port */
|
||||
char *sep = strrchr(fullname, ':');
|
||||
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);
|
||||
}
|
||||
serv = sep+1;
|
||||
@@ -583,30 +581,13 @@ void resolve_name(struct addrinfo **out, char* fullname)
|
||||
|
||||
res = resolve_split_name(out, host, serv);
|
||||
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)
|
||||
fprintf(stderr, "(Check you have specified all ports)\n");
|
||||
print_message(msg_config_error, "(Check you have specified all ports)\n");
|
||||
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 */
|
||||
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx)
|
||||
@@ -641,30 +622,6 @@ int get_connection_desc(struct connection_desc* desc, const struct connection *c
|
||||
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)
|
||||
{
|
||||
@@ -708,8 +665,7 @@ int check_access_rights(int in_socket, const char* service)
|
||||
/* extract peer address */
|
||||
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
|
||||
if (res) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
||||
print_message(msg_system_error, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
||||
strcpy(addr_str, STRING_UNKNOWN);
|
||||
}
|
||||
/* extract peer name */
|
||||
@@ -717,15 +673,12 @@ int check_access_rights(int in_socket, const char* service)
|
||||
if (!cfg.numeric) {
|
||||
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
|
||||
if (res) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
||||
print_message(msg_system_error, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
||||
}
|
||||
}
|
||||
|
||||
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "access denied\n");
|
||||
log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str);
|
||||
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
@@ -760,35 +713,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) */
|
||||
void set_keepcaps(int val) {
|
||||
@@ -866,16 +790,14 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
if (user_name) {
|
||||
pw = getpwnam(user_name);
|
||||
if (!pw) {
|
||||
fprintf(stderr, "%s: not found\n", user_name);
|
||||
print_message(msg_config_error, "%s: not found\n", user_name);
|
||||
exit(2);
|
||||
}
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "turning into %s\n", user_name);
|
||||
print_message(msg_config, "turning into %s\n", user_name);
|
||||
}
|
||||
|
||||
if (chroot_path) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "chrooting into %s\n", chroot_path);
|
||||
print_message(msg_config, "chrooting into %s\n", chroot_path);
|
||||
|
||||
res = chroot(chroot_path);
|
||||
CHECK_RES_DIE(res, "chroot");
|
||||
@@ -905,14 +827,24 @@ void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
void write_pid_file(const char* pidfile)
|
||||
{
|
||||
FILE *f;
|
||||
int res;
|
||||
|
||||
f = fopen(pidfile, "w");
|
||||
if (!f) {
|
||||
perror(pidfile);
|
||||
print_message(msg_system_error, "write_pid_file:%s:%s", pidfile, strerror(errno));
|
||||
exit(3);
|
||||
}
|
||||
|
||||
fprintf(f, "%d\n", getpid());
|
||||
fclose(f);
|
||||
res = fprintf(f, "%d\n", getpid());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,20 +40,20 @@
|
||||
|
||||
#define CHECK_RES_DIE(res, str) \
|
||||
if (res == -1) { \
|
||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
||||
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define CHECK_RES_RETURN(res, str, ret) \
|
||||
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; \
|
||||
}
|
||||
|
||||
#define CHECK_ALLOC(a, str) \
|
||||
if (!a) { \
|
||||
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
|
||||
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
@@ -83,9 +83,6 @@ enum connection_state {
|
||||
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
|
||||
* written to), and a queue for deferred write data */
|
||||
struct queue {
|
||||
@@ -95,6 +92,12 @@ struct queue {
|
||||
int deferred_data_size;
|
||||
};
|
||||
|
||||
/* Double linked list for timeout management */
|
||||
typedef struct {
|
||||
struct connection* head;
|
||||
struct connection* tail;
|
||||
} dl_list;
|
||||
|
||||
struct connection {
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
struct sslhcfg_protocols_item* proto; /* Where to connect to */
|
||||
@@ -116,6 +119,9 @@ struct connection {
|
||||
|
||||
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
|
||||
* forward server responses */
|
||||
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 set_capabilities(int cap_net_admin);
|
||||
void write_pid_file(const char* pidfile);
|
||||
void log_message(int type, const char* msg, ...);
|
||||
void dump_connection(struct connection *cnx);
|
||||
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||
|
||||
|
||||
+24
-14
@@ -7,7 +7,7 @@ Dependencies
|
||||
`sslh` uses:
|
||||
|
||||
* [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
|
||||
Makefile.
|
||||
|
||||
@@ -29,6 +29,13 @@ Makefile.
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at
|
||||
compile-time.
|
||||
|
||||
* libpcre2, in package `libpcre-dev`. You can compile
|
||||
with or without it using ENABLE_REGEX in the Makefile.
|
||||
|
||||
* libev-dev, in package `libev-dev`. If you build a binary
|
||||
specifically and do not build `sslh-ev`, you don't need
|
||||
this.
|
||||
|
||||
|
||||
For OpenSUSE, these are contained in packages libconfig9 and
|
||||
libconfig-dev in repository
|
||||
@@ -44,6 +51,10 @@ distclean` for example), you will also need to add
|
||||
[conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
(v1.5) to your path.
|
||||
|
||||
|
||||
The test scripts are written in Perl, and will require
|
||||
IO::Socket::INET6 (libio-socket-inet6-perl in Debian).
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
@@ -71,8 +82,8 @@ of the Makefile:
|
||||
Binaries
|
||||
--------
|
||||
|
||||
The Makefile produces two different executables: `sslh-fork`
|
||||
and `sslh-select`:
|
||||
The Makefile produces three different executables: `sslh-fork`,
|
||||
`sslh-select` and `sslh-ev`:
|
||||
|
||||
* `sslh-fork` forks a new process for each incoming connection.
|
||||
It is well-tested and very reliable, but incurs the overhead
|
||||
@@ -81,18 +92,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
|
||||
`sslh-fork` is probably more suited for you.
|
||||
|
||||
* `sslh-select` uses only one thread, which monitors all connections
|
||||
at once. It is more recent and less tested, but only incurs a 16
|
||||
byte overhead per connection. Also, if it stops, you'll lose all
|
||||
connections, which means you can't upgrade it remotely.
|
||||
If you are going to use `sslh` on a "medium" setup (a few thousand ssh
|
||||
connections, and another few thousand ssl connections),
|
||||
`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-select` uses only one thread, which monitors all
|
||||
connections at once. It only incurs a 16 byte overhead per
|
||||
connection. Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely. If you are going
|
||||
to use `sslh` on a "medium" setup (a few hundreds of
|
||||
connections), or if you are on a system where forking is
|
||||
expensive (e.g. Windows), `sslh-select` will be better.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as
|
||||
a backend. This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ with launchctl or simply reboot.
|
||||
<string>0.0.0.0:443</string>
|
||||
<string>--ssh</string>
|
||||
<string>localhost:22</string>
|
||||
<string>--ssl</string>
|
||||
<string>--tls</string>
|
||||
<string>localhost:443</string>
|
||||
</array>
|
||||
<key>QueueDirectories</key>
|
||||
|
||||
+9
-155
@@ -65,13 +65,13 @@ Configuration goes like this on the server side, using `stunnel3`:
|
||||
* `-f` for foreground/debugging
|
||||
* `-p` for specifying the key and certificate
|
||||
* `-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.
|
||||
|
||||
* sslh options:
|
||||
* `-i` for inetd mode
|
||||
* `--http` to forward HTTP connexions to port 80,
|
||||
and SSH connexions to port 22.
|
||||
* `--http` to forward HTTP connections to port 80,
|
||||
and SSH connections to port 22.
|
||||
|
||||
Capabilities support
|
||||
--------------------
|
||||
@@ -99,158 +99,12 @@ Then you can run sslh-select as an unpriviledged user, e.g.:
|
||||
Transparent proxy support
|
||||
-------------------------
|
||||
|
||||
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).
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
This makes it easier to use the server's logs, and potential
|
||||
IP-based banning ability.
|
||||
|
||||
You can refer to Sean Warn'ѕ [tutorial](tproxy.md) for a
|
||||
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.
|
||||
Set up can get complicated, so it has its own [document](tproxy.md).
|
||||
|
||||
Systemd Socket Activation
|
||||
-------------------------
|
||||
@@ -335,7 +189,7 @@ UDP
|
||||
---
|
||||
|
||||
`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
|
||||
address and target protocols with `is_udp: true`. `sslh`
|
||||
will wait for incoming UDP packets, run the probes in the
|
||||
|
||||
+178
-20
@@ -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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Transparent Proxy to Two Hosts
|
||||
|
||||
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.
|
||||
* 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.
|
||||
|
||||
Overview of my Network
|
||||
----------------------
|
||||
### Overview of my Network
|
||||
|
||||
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)
|
||||
@@ -20,8 +184,7 @@ Two Raspberry Pis on my home LAN:
|
||||
|
||||

|
||||
|
||||
`sslh` build
|
||||
------------
|
||||
### `sslh` build
|
||||
|
||||
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
|
||||
|
||||
@@ -47,8 +210,7 @@ MAN=sslh.8.gz # man page name
|
||||
# itself
|
||||
```
|
||||
|
||||
systemd setup
|
||||
-------------
|
||||
### systemd setup
|
||||
|
||||
Create an sslh systemd service file...
|
||||
```
|
||||
@@ -83,8 +245,7 @@ Start it again to test…
|
||||
# systemctl start sslh
|
||||
```
|
||||
|
||||
Configure `sslh`
|
||||
----------------
|
||||
### Configure `sslh`
|
||||
|
||||
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
|
||||
```
|
||||
@@ -151,8 +311,7 @@ connect = 192.168.1.124:1022
|
||||
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.
|
||||
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
|
||||
```
|
||||
|
||||
Configure iptables for Pi B
|
||||
--------------------------
|
||||
## Configure iptables for Pi B
|
||||
|
||||
```
|
||||
# nano /usr/local/sbin/piB_tproxy_add.sh
|
||||
@@ -235,8 +393,8 @@ Now run the "add" script on Pi B!
|
||||
# piB_tproxy_rm.sh
|
||||
```
|
||||
|
||||
Testing
|
||||
-------
|
||||
### Testing
|
||||
|
||||
* Getting to sshd on PiA
|
||||
|
||||
I did this test using 4G from my phone (outside the LAN)
|
||||
|
||||
@@ -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
-1
@@ -1,5 +1,5 @@
|
||||
/* 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
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/* 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
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
|
||||
@@ -100,7 +100,10 @@ void tcp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
while (1) {
|
||||
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())
|
||||
{
|
||||
@@ -109,6 +112,7 @@ void tcp_echo(struct listen_endpoint* listen_socket)
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
waitpid(-1, NULL, WNOHANG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+109
@@ -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
|
||||
+27
-2
@@ -3,7 +3,6 @@
|
||||
# not be used as a starting point for a working
|
||||
# configuration. Instead use basic.cfg.
|
||||
|
||||
verbose: 0;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
@@ -13,6 +12,30 @@ user: "nobody";
|
||||
pidfile: "/var/run/sslh.pid";
|
||||
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
|
||||
# system are usually defined in /usr/include/*/sys/syslog.h
|
||||
# or equivalent)
|
||||
@@ -44,6 +67,8 @@ listen:
|
||||
# fork: Should a new process be forked for this protocol?
|
||||
# (only useful for sslh-select)
|
||||
# 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
|
||||
# transparently (server sees the remote client IP
|
||||
# address). Same as the global option, but per-protocol
|
||||
@@ -98,7 +123,7 @@ protocols:
|
||||
udp_timeout: 20; # Time after which the "connection" is forgotten
|
||||
regex_patterns: [ "hello" ]; },
|
||||
# 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")
|
||||
# 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" ]; },
|
||||
|
||||
@@ -31,11 +31,6 @@
|
||||
#include "gap.h"
|
||||
|
||||
|
||||
typedef struct gap_array {
|
||||
int len; /* Number of elements in array */
|
||||
void** array;
|
||||
} gap_array;
|
||||
|
||||
/* Allocate one page-worth of elements */
|
||||
static int gap_len_alloc(int elem_size)
|
||||
{
|
||||
@@ -61,12 +56,7 @@ gap_array* gap_init(int len)
|
||||
return gap;
|
||||
}
|
||||
|
||||
void* gap_get(gap_array* gap, int index)
|
||||
{
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
static int gap_extend(gap_array* gap)
|
||||
int gap_extend(gap_array* gap)
|
||||
{
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
int new_length = gap->len + gap_len_alloc(elem_size);
|
||||
@@ -84,17 +74,6 @@ static int gap_extend(gap_array* gap)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int 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;
|
||||
}
|
||||
|
||||
void gap_destroy(gap_array* gap)
|
||||
{
|
||||
free(gap->array);
|
||||
|
||||
@@ -4,10 +4,35 @@
|
||||
typedef struct gap_array gap_array;
|
||||
|
||||
gap_array* gap_init(int len);
|
||||
void* gap_get(gap_array* gap, int index);
|
||||
int gap_set(gap_array* gap, int index, void* ptr);
|
||||
static void* gap_get(gap_array* gap, int index);
|
||||
static int gap_set(gap_array* gap, int index, void* ptr);
|
||||
void gap_destroy(gap_array* gap);
|
||||
|
||||
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)
|
||||
{
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
CFLAGS=-DHASH_TESTING -O2 -Wall
|
||||
OBJ=../hash.o ../gap.o htest.o
|
||||
|
||||
htest: $(OBJ)
|
||||
$(CC) -o htest $(OBJ)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Basic delete
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 10 ac
|
||||
a 20 ba
|
||||
a 21 bb
|
||||
|
||||
d 21 bb
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Basic insertions
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 10 ac
|
||||
a 20 ba
|
||||
a 21 bb
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Executable
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+30
@@ -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 discrepency 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;
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
# 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;
|
||||
|
||||
va_start(ap, str);
|
||||
|
||||
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd)
|
||||
vfprintf(stderr, str, 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;
|
||||
get_connection_desc(desc, cnx);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
# 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
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
@@ -27,11 +27,13 @@
|
||||
#endif
|
||||
#include <ctype.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_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_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*);
|
||||
@@ -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_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_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; }
|
||||
|
||||
/* Table of protocols that have a built-in probe
|
||||
@@ -47,6 +51,7 @@ static struct protocol_probe_desc builtins[] = {
|
||||
/* description probe */
|
||||
{ "ssh", is_ssh_protocol},
|
||||
{ "openvpn", is_openvpn_protocol },
|
||||
{ "wireguard", is_wireguard_protocol },
|
||||
{ "tinc", is_tinc_protocol },
|
||||
{ "xmpp", is_xmpp_protocol },
|
||||
{ "http", is_http_protocol },
|
||||
@@ -54,6 +59,8 @@ static struct protocol_probe_desc builtins[] = {
|
||||
{ "adb", is_adb_protocol },
|
||||
{ "socks5", is_socks5_protocol },
|
||||
{ "syslog", is_syslog_protocol },
|
||||
{ "teamspeak", is_teamspeak_protocol },
|
||||
{ "msrdp", is_msrdp_protocol },
|
||||
{ "anyprot", is_true }
|
||||
};
|
||||
|
||||
@@ -81,33 +88,38 @@ struct sslhcfg_protocols_item* timeout_protocol(void)
|
||||
|
||||
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
|
||||
#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;
|
||||
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++)
|
||||
{
|
||||
/* print offset */
|
||||
if(i % HEXDUMP_COLS == 0)
|
||||
fprintf(stderr, "0x%06x: ", i);
|
||||
c += sprintf(&str[c], "0x%06x: ", i);
|
||||
|
||||
/* print hex data */
|
||||
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 */
|
||||
fprintf(stderr, " ");
|
||||
c+= sprintf(&str[c], " ");
|
||||
|
||||
/* print ASCII dump */
|
||||
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
||||
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
|
||||
if(j >= len) /* end of block, not really printing */
|
||||
fputc(' ', stderr);
|
||||
str[c++] = ' ';
|
||||
else if(isprint(mem[j])) /* printable char */
|
||||
fputc(0xFF & mem[j], stderr);
|
||||
str[c++] = 0xFF & mem[j];
|
||||
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
|
||||
* 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)
|
||||
{
|
||||
int packet_len;
|
||||
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
if (proto->is_udp == 0)
|
||||
{
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
packet_len = ntohs(*(uint16_t*)p);
|
||||
return packet_len == len - 2;
|
||||
packet_len = ntohs(*(uint16_t*)p);
|
||||
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))
|
||||
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))
|
||||
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?
|
||||
@@ -312,6 +375,27 @@ static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protoco
|
||||
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)
|
||||
{
|
||||
#ifdef ENABLE_REGEX
|
||||
@@ -328,51 +412,58 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item
|
||||
return 0;
|
||||
#else
|
||||
/* 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);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 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
|
||||
* PROBE_AGAIN if not enough data, and set *proto to NULL
|
||||
* PROBE_MATCH if protocol is identified, in which case *proto is set to
|
||||
* 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;
|
||||
int i, res, again = 0;
|
||||
|
||||
if (cfg.verbose > 1) {
|
||||
fprintf(stderr, "hexdump of incoming packet:\n");
|
||||
hexdump(buf, len);
|
||||
}
|
||||
print_message(msg_packets, "hexdump of incoming packet:\n");
|
||||
hexdump(msg_packets, buf, len);
|
||||
|
||||
*proto = NULL;
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
*proto_out = NULL;
|
||||
for (i = 0; i < proto_len; i++) {
|
||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||
p = &cfg.protocols[i];
|
||||
p = proto_in[i];
|
||||
|
||||
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) */
|
||||
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||
if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||
break;
|
||||
|
||||
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++;
|
||||
continue;
|
||||
}
|
||||
|
||||
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) {
|
||||
*proto = p;
|
||||
*proto_out = p;
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
if (res == PROBE_AGAIN)
|
||||
@@ -382,37 +473,14 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
/* Everything failed: match the last one */
|
||||
*proto = &cfg.protocols[cfg.protocols_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
&cnx->proto);
|
||||
if (proto_len == 0) {
|
||||
/* This should be caught by configuration sanity checks, but just in
|
||||
* case, die gracefully rather than segfaulting */
|
||||
print_message(msg_int_error, "Received traffic on transport that has no target\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* read() returned an error, so just connect to the last protocol to die */
|
||||
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
|
||||
*proto_out = proto_in[proto_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "tls.h"
|
||||
#include "log.h"
|
||||
|
||||
typedef enum {
|
||||
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);
|
||||
|
||||
/* Probe, but on a buffer */
|
||||
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto);
|
||||
/* Probe on a buffer */
|
||||
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 */
|
||||
void set_ontimeout(const char* name);
|
||||
@@ -59,6 +64,6 @@ void set_ontimeout(const char* name);
|
||||
*/
|
||||
struct sslhcfg_protocols_item* timeout_protocol(void);
|
||||
|
||||
void hexdump(const char*, unsigned int);
|
||||
void hexdump(msg_info, const char*, unsigned int);
|
||||
|
||||
#endif
|
||||
+115
@@ -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
@@ -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
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^.+ sslh\[.+\]: connection from <HOST>:.+ to .+ forwarded
|
||||
failregex = ^.+ sslh\[.+\]: ssh:connection from <HOST>:.+ to .+ forwarded
|
||||
from .+ to .+:ssh\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
+348
-30
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Aug 24 13:53:04 2021.
|
||||
* on Sun Sep 11 21:43:25 2022.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
@@ -443,17 +443,29 @@ struct compound_cl_arg {
|
||||
|
||||
|
||||
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_inetd;
|
||||
struct arg_lit* sslhcfg_numeric;
|
||||
struct arg_lit* sslhcfg_transparent;
|
||||
struct arg_int* sslhcfg_timeout;
|
||||
struct arg_int* sslhcfg_udp_max_connections;
|
||||
struct arg_str* sslhcfg_user;
|
||||
struct arg_str* sslhcfg_pidfile;
|
||||
struct arg_str* sslhcfg_chroot;
|
||||
struct arg_str* sslhcfg_syslog_facility;
|
||||
struct arg_str* sslhcfg_logfile;
|
||||
struct arg_str* sslhcfg_on_timeout;
|
||||
struct arg_str* sslhcfg_prefix;
|
||||
struct arg_str* sslhcfg_listen;
|
||||
@@ -461,15 +473,17 @@ struct arg_file* sslhcfg_conffile;
|
||||
struct arg_str* sslhcfg_tls;
|
||||
struct arg_str* sslhcfg_openvpn;
|
||||
struct arg_str* sslhcfg_tinc;
|
||||
struct arg_str* sslhcfg_wireguard;
|
||||
struct arg_str* sslhcfg_xmpp;
|
||||
struct arg_str* sslhcfg_http;
|
||||
struct arg_str* sslhcfg_adb;
|
||||
struct arg_str* sslhcfg_socks5;
|
||||
struct arg_str* sslhcfg_syslog;
|
||||
struct arg_str* sslhcfg_msrdp;
|
||||
struct arg_str* sslhcfg_anyprot;
|
||||
struct arg_end* sslhcfg_end;
|
||||
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg_protocols[] = {
|
||||
|
||||
|
||||
@@ -617,6 +631,22 @@ static struct config_desc table_sslhcfg_protocols[] = {
|
||||
/* 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",
|
||||
/* type */ CFG_INT,
|
||||
@@ -783,17 +813,17 @@ static struct config_desc table_sslhcfg_listen[] = {
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
static struct config_desc table_sslhcfg[] = {
|
||||
|
||||
|
||||
{
|
||||
/* name */ "verbose",
|
||||
/* name */ "verbose_config",
|
||||
/* type */ CFG_INT,
|
||||
/* sub_group*/ NULL,
|
||||
/* arg_cl */ & sslhcfg_verbose,
|
||||
/* arg_cl */ & sslhcfg_verbose_config,
|
||||
/* base_addr */ NULL,
|
||||
/* offset */ offsetof(struct sslhcfg_item, verbose),
|
||||
/* offset */ offsetof(struct sslhcfg_item, verbose_config),
|
||||
/* offset_len */ 0,
|
||||
/* offset_present */ 0,
|
||||
/* size */ sizeof(int),
|
||||
@@ -803,6 +833,166 @@ static struct config_desc table_sslhcfg[] = {
|
||||
/* 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,
|
||||
@@ -899,6 +1089,22 @@ static struct config_desc table_sslhcfg[] = {
|
||||
/* 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",
|
||||
/* type */ CFG_STRING,
|
||||
@@ -963,6 +1169,22 @@ static struct config_desc table_sslhcfg[] = {
|
||||
/* 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",
|
||||
/* type */ CFG_STRING,
|
||||
@@ -1032,7 +1254,15 @@ static struct compound_cl_target sslhcfg_anyprot_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 }
|
||||
};
|
||||
|
||||
@@ -1040,7 +1270,7 @@ static struct compound_cl_target sslhcfg_syslog_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 }
|
||||
};
|
||||
|
||||
@@ -1048,7 +1278,7 @@ static struct compound_cl_target sslhcfg_socks5_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 }
|
||||
};
|
||||
|
||||
@@ -1056,7 +1286,7 @@ static struct compound_cl_target sslhcfg_adb_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 }
|
||||
};
|
||||
|
||||
@@ -1064,7 +1294,7 @@ static struct compound_cl_target sslhcfg_http_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "http" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 }
|
||||
};
|
||||
|
||||
@@ -1072,7 +1302,16 @@ static struct compound_cl_target sslhcfg_xmpp_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 }
|
||||
};
|
||||
|
||||
@@ -1080,7 +1319,7 @@ static struct compound_cl_target sslhcfg_tinc_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 },
|
||||
{ 0 }
|
||||
};
|
||||
@@ -1089,7 +1328,7 @@ static struct compound_cl_target sslhcfg_openvpn_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .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 },
|
||||
{ 0 }
|
||||
};
|
||||
@@ -1098,7 +1337,7 @@ static struct compound_cl_target sslhcfg_tls_targets [] = {
|
||||
{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
|
||||
{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
|
||||
{ & 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 },
|
||||
{ 0 }
|
||||
};
|
||||
@@ -1108,7 +1347,7 @@ static struct compound_cl_target sslhcfg_ssh_targets [] = {
|
||||
{ & table_sslhcfg_protocols[1], 1, .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[9], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[10], 0, .value.def_int = 1 },
|
||||
{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
|
||||
{ 0 }
|
||||
};
|
||||
@@ -1123,7 +1362,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: listen */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_listen,
|
||||
.base_entry = & table_sslhcfg [13],
|
||||
.base_entry = & table_sslhcfg [25],
|
||||
.targets = sslhcfg_listen_targets,
|
||||
|
||||
|
||||
@@ -1135,7 +1374,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: ssh */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_ssh,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_ssh_targets,
|
||||
|
||||
|
||||
@@ -1147,7 +1386,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tls */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tls,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_tls_targets,
|
||||
|
||||
|
||||
@@ -1159,7 +1398,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: openvpn */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_openvpn,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_openvpn_targets,
|
||||
|
||||
|
||||
@@ -1171,7 +1410,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: tinc */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_tinc,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_tinc_targets,
|
||||
|
||||
|
||||
@@ -1180,10 +1419,22 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
.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 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_xmpp,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_xmpp_targets,
|
||||
|
||||
|
||||
@@ -1195,7 +1446,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: http */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_http,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_http_targets,
|
||||
|
||||
|
||||
@@ -1207,7 +1458,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: adb */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_adb,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_adb_targets,
|
||||
|
||||
|
||||
@@ -1219,7 +1470,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: socks5 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_socks5,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_socks5_targets,
|
||||
|
||||
|
||||
@@ -1231,7 +1482,7 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
{ /* arg: syslog */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_syslog,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_syslog_targets,
|
||||
|
||||
|
||||
@@ -1240,10 +1491,22 @@ static struct compound_cl_arg compound_cl_args[] = {
|
||||
.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 */
|
||||
.regex = "(.+):(\\w+)",
|
||||
.arg_cl = & sslhcfg_anyprot,
|
||||
.base_entry = & table_sslhcfg [14],
|
||||
.base_entry = & table_sslhcfg [26],
|
||||
.targets = sslhcfg_anyprot_targets,
|
||||
|
||||
|
||||
@@ -1908,17 +2171,29 @@ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
|
||||
#ifdef LIBCONFIG
|
||||
sslhcfg_conffile = arg_filen("F", "config", "<file>", 0, 1, "Specify configuration file"),
|
||||
#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_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_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_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_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_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_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"),
|
||||
@@ -1926,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_openvpn = arg_strn(NULL, "openvpn", "<host:port>", 0, 10, "Set up OpenVPN target"),
|
||||
sslhcfg_tinc = arg_strn(NULL, "tinc", "<host:port>", 0, 10, "Set up tinc target"),
|
||||
sslhcfg_wireguard = arg_strn(NULL, "wireguard", "<host:port>", 0, 10, "Set up WireGuard target"),
|
||||
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_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_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_end = arg_end(10)
|
||||
|
||||
@@ -2020,6 +2297,9 @@ static void sslhcfg_protocols_fprint(
|
||||
fprintf(out, "transparent: %d", sslhcfg_protocols->transparent);
|
||||
fprintf(out, "\n");
|
||||
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, "\n");
|
||||
indent(out, depth);
|
||||
@@ -2077,7 +2357,37 @@ void sslhcfg_fprint(
|
||||
{
|
||||
int i;
|
||||
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);
|
||||
@@ -2098,6 +2408,9 @@ void sslhcfg_fprint(
|
||||
fprintf(out, "timeout: %d", sslhcfg->timeout);
|
||||
fprintf(out, "\n");
|
||||
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);
|
||||
if (! sslhcfg->user_is_present)
|
||||
fprintf(out, " <unset>");
|
||||
@@ -2116,6 +2429,11 @@ void sslhcfg_fprint(
|
||||
fprintf(out, "syslog_facility: %s", sslhcfg->syslog_facility);
|
||||
fprintf(out, "\n");
|
||||
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, "\n");
|
||||
indent(out, depth);
|
||||
|
||||
+17
-2
@@ -1,5 +1,5 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Aug 24 13:53:04 2021.
|
||||
* on Sun Sep 11 21:43:25 2022.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
@@ -58,6 +58,7 @@ struct sslhcfg_protocols_item {
|
||||
int fork;
|
||||
int tfo_ok;
|
||||
int transparent;
|
||||
int resolve_on_forward;
|
||||
int log_level;
|
||||
int keepalive;
|
||||
size_t sni_hostnames_len;
|
||||
@@ -71,16 +72,28 @@ struct sslhcfg_protocols_item {
|
||||
T_PROBE* probe;
|
||||
struct addrinfo* saddr;
|
||||
void* data;
|
||||
dl_list timeouts;
|
||||
};
|
||||
|
||||
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 inetd;
|
||||
int numeric;
|
||||
int transparent;
|
||||
int timeout;
|
||||
int udp_max_connections;
|
||||
int user_is_present;
|
||||
char* user;
|
||||
int pidfile_is_present;
|
||||
@@ -88,6 +101,8 @@ struct sslhcfg_item {
|
||||
int chroot_is_present;
|
||||
char* chroot;
|
||||
char* syslog_facility;
|
||||
int logfile_is_present;
|
||||
char* logfile;
|
||||
char* on_timeout;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
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 */
|
||||
|
||||
int max_fd; /* legacy to be removed, still required for UDP */
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (fd > w->max_fd) w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (fd > w->max_fd) w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -23,7 +23,8 @@
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "udp-listener.h"
|
||||
#include "tcp-probe.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
@@ -58,8 +59,7 @@ int shovel(struct connection *cnx)
|
||||
if (FD_ISSET(cnx->q[i].fd, &fds)) {
|
||||
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
||||
if (res == FD_CNXCLOSED) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||
print_message(msg_fd, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,7 @@ void start_shoveler(int in_socket)
|
||||
} else {
|
||||
/* Timed out: it's necessarily SSH */
|
||||
cnx.proto = timeout_protocol();
|
||||
if (cfg.verbose)
|
||||
log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->name);
|
||||
print_message(msg_fd, "timed out, connect to %s\n", cnx.proto->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,8 +128,7 @@ void start_shoveler(int in_socket)
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "connection closed down\n");
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
@@ -179,10 +177,10 @@ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int activ
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
print_message(msg_fd, "accepted fd %d\n", in_socket);
|
||||
|
||||
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;
|
||||
|
||||
case 0: /* In child process */
|
||||
@@ -209,18 +207,20 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
|
||||
CHECK_ALLOC(listener_pid, "malloc");
|
||||
|
||||
tcp_init();
|
||||
|
||||
/* Start one process for each listening address */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
listener_pid[i] = fork();
|
||||
switch(listener_pid[i]) {
|
||||
/* 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;
|
||||
/* We're in the child, we have work to do */
|
||||
case 0:
|
||||
set_listen_procname(&listen_sockets[i]);
|
||||
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
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
break;
|
||||
|
||||
+81
-33
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
@@ -50,7 +51,7 @@ static void printcaps(void) {
|
||||
|
||||
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(desc);
|
||||
@@ -65,21 +66,26 @@ static void printsettings(void)
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
fprintf(stderr,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n",
|
||||
p->name,
|
||||
sprintaddr(buf, sizeof(buf), p->saddr),
|
||||
p->service,
|
||||
p->log_level,
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family,
|
||||
p->keepalive ? "keepalive" : "",
|
||||
p->fork ? "fork" : "",
|
||||
p->transparent ? "transparent" : ""
|
||||
);
|
||||
print_message(msg_config,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n",
|
||||
p->name,
|
||||
sprintaddr(buf, sizeof(buf), p->saddr),
|
||||
p->service,
|
||||
p->log_level,
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family,
|
||||
p->keepalive ? "keepalive" : "",
|
||||
p->fork ? "fork" : "",
|
||||
p->transparent ? "transparent" : ""
|
||||
);
|
||||
}
|
||||
fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout,
|
||||
timeout_protocol()->name);
|
||||
print_message(msg_config,
|
||||
"timeout: %d\n"
|
||||
"on-timeout: %s\n"
|
||||
"UDP hash size: %d\n",
|
||||
cfg.timeout,
|
||||
timeout_protocol()->name,
|
||||
cfg.udp_max_connections);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +109,7 @@ static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
&error, &error_offset, NULL);
|
||||
if (!pattern_list[i]) {
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
@@ -123,14 +129,19 @@ static void config_protocols()
|
||||
int i;
|
||||
for (i = 0; i < cfg.protocols_len; 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);
|
||||
}
|
||||
|
||||
p->probe = get_probe(p->name);
|
||||
if (!p->probe) {
|
||||
fprintf(stderr, "%s: probe unknown\n", p->name);
|
||||
print_message(msg_config_error, "%s: probe unknown\n", p->name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -149,23 +160,55 @@ static void config_protocols()
|
||||
(const char**) cfg.protocols[i].alpn_protocols,
|
||||
cfg.protocols[i].alpn_protocols_len);
|
||||
}
|
||||
|
||||
p->timeouts.head = NULL;
|
||||
p->timeouts.tail = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void config_sanity_check(struct sslhcfg_item* cfg) {
|
||||
if (!cfg->protocols_len) {
|
||||
fprintf(stderr, "At least one target protocol must be specified.\n");
|
||||
exit(2);
|
||||
}
|
||||
void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
/* If compiling with systemd socket support no need to require listen address */
|
||||
#ifndef SYSTEMD
|
||||
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);
|
||||
}
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -184,8 +227,6 @@ int main(int argc, char *argv[], char* envp[])
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||
if (res) exit(6);
|
||||
if (cfg.verbose > 3)
|
||||
sslhcfg_fprint(stderr, &cfg, 0);
|
||||
|
||||
if (cfg.version) {
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
@@ -197,19 +238,18 @@ int main(int argc, char *argv[], char* envp[])
|
||||
|
||||
if (cfg.inetd)
|
||||
{
|
||||
cfg.verbose = 0;
|
||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||
start_shoveler(0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (cfg.verbose)
|
||||
printsettings();
|
||||
printsettings();
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
#ifdef SYSTEMD
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
@@ -232,13 +272,21 @@ int main(int argc, char *argv[], char* envp[])
|
||||
/* Open syslog connection before we drop privs/chroot */
|
||||
setup_syslog(argv[0]);
|
||||
|
||||
/* Open log file for writing */
|
||||
setup_logfile();
|
||||
|
||||
if (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);
|
||||
|
||||
close_logfile();
|
||||
|
||||
free(listen_sockets);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+76
-475
@@ -25,483 +25,88 @@
|
||||
* 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
|
||||
* 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
|
||||
* version, or we'll have to write a new version based on libev. */
|
||||
* on many Linux. To support large numbers of descriptors efficiently, either use sslh-fork
|
||||
* or sslh-ev. */
|
||||
|
||||
#define __LINUX__
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "tcp-listener.h"
|
||||
#include "udp-listener.h"
|
||||
#include "collection.h"
|
||||
#include "processes.h"
|
||||
#include "gap.h"
|
||||
|
||||
static int debug = 0;
|
||||
#include "log.h"
|
||||
|
||||
const char* server_type = "sslh-select";
|
||||
|
||||
/* Global state for a select() loop */
|
||||
struct select_info {
|
||||
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 */
|
||||
|
||||
/* watcher type for a select() loop */
|
||||
struct watchers {
|
||||
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
|
||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||
|
||||
time_t next_timeout; /* time at which next UDP connection times out */
|
||||
int max_fd; /* Highest fd number to pass to select() */
|
||||
};
|
||||
|
||||
|
||||
|
||||
static int tidy_connection(struct connection *cnx, struct select_info* fd_info)
|
||||
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
int num_addr_listen)
|
||||
{
|
||||
int i;
|
||||
fd_set* fds = &fd_info->fds_r;
|
||||
fd_set* fds2 = &fd_info->fds_w;
|
||||
*w = malloc(sizeof(**w));
|
||||
CHECK_ALLOC(*w, "malloc");
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (cnx->q[i].fd != -1) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "closing fd %d\n", cnx->q[i].fd);
|
||||
memset(*w, 0, sizeof(**w));
|
||||
FD_ZERO(&(*w)->fds_r);
|
||||
FD_ZERO(&(*w)->fds_w);
|
||||
|
||||
FD_CLR(cnx->q[i].fd, fds);
|
||||
FD_CLR(cnx->q[i].fd, fds2);
|
||||
close(cnx->q[i].fd);
|
||||
if (cnx->q[i].deferred_data)
|
||||
free(cnx->q[i].deferred_data);
|
||||
}
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
watchers_add_read(*w, listen_sockets[i].socketfd);
|
||||
set_nonblock(listen_sockets[i].socketfd);
|
||||
}
|
||||
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
|
||||
* 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) {
|
||||
log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n");
|
||||
return 0;
|
||||
print_message(msg_system_error, "too many open file descriptor to monitor them all -- dropping connection\n");
|
||||
return 1;
|
||||
}
|
||||
return 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 */
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* 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;
|
||||
|
||||
time_t next_timeout = INT_MAX;
|
||||
|
||||
for (int i = 0; i < fd_info->max_fd; i++) {
|
||||
/* 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 (!timeout) continue; /* Not a UDP connection */
|
||||
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:
|
||||
* - fds_r and fds_w contain the file descriptors to monitor in read and write
|
||||
@@ -519,48 +124,42 @@ static void udp_timeouts(struct select_info* fd_info)
|
||||
*/
|
||||
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 */
|
||||
struct timeval tv;
|
||||
int i, res;
|
||||
|
||||
fd_info.num_probing = 0;
|
||||
FD_ZERO(&fd_info.fds_r);
|
||||
FD_ZERO(&fd_info.fds_w);
|
||||
fd_info.probing_list = gap_init(0);
|
||||
udp_init(&fd_info);
|
||||
tcp_init();
|
||||
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
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;
|
||||
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
|
||||
fd_info.collection = collection_init(fd_info.max_fd);
|
||||
fd_info.collection = collection_init(fd_info.watchers->max_fd);
|
||||
|
||||
while (1)
|
||||
{
|
||||
memset(&tv, 0, sizeof(tv));
|
||||
tv.tv_sec = cfg.timeout;
|
||||
|
||||
memcpy(&readfds, &fd_info.fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fd_info.fds_w, sizeof(writefds));
|
||||
memcpy(&readfds, &fd_info.watchers->fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fd_info.watchers->fds_w, sizeof(writefds));
|
||||
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.max_fd, &readfds, &writefds,
|
||||
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.watchers->max_fd + 1, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
|
||||
|
||||
/* UDP timeouts: clear out connections after some idle time */
|
||||
udp_timeouts(&fd_info);
|
||||
|
||||
/* Check main socket for new connections */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
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 */
|
||||
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
||||
@@ -568,8 +167,11 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
}
|
||||
|
||||
/* Check all sockets for write activity */
|
||||
for (i = 0; i < fd_info.max_fd; i++) {
|
||||
if (FD_ISSET(i, &writefds)) {
|
||||
for (i = 0; i < fd_info.watchers->max_fd; i++) {
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
@@ -578,24 +180,23 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
for (i = 0; i < fd_info.num_probing; i++) {
|
||||
struct connection* cnx = gap_get(fd_info.probing_list, i);
|
||||
if (!cnx || cnx->state != ST_PROBING) {
|
||||
log_message(LOG_ERR, "Inconsistent probing: cnx=%0xp\n", cnx);
|
||||
print_message(msg_int_error, "Inconsistent probing: cnx=%0xp\n", cnx);
|
||||
if (cnx)
|
||||
log_message(LOG_ERR, "Inconsistent probing: state=%d\n", cnx);
|
||||
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx);
|
||||
exit(1);
|
||||
}
|
||||
if (cnx->probe_timeout < time(NULL)) {
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "timeout slot %d\n", i);
|
||||
print_message(msg_fd, "timeout slot %d\n", i);
|
||||
probing_read_process(cnx, &fd_info);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
* died, it gets tidied, which closes both sockets, but readfs does
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -604,7 +205,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
+57
-3
@@ -25,7 +25,31 @@ config: {
|
||||
name : "sslhcfg",
|
||||
type: "list",
|
||||
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"; },
|
||||
@@ -43,6 +67,8 @@ config: {
|
||||
{ name: "timeout"; type: "int"; default: 5;
|
||||
short: "t";
|
||||
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;
|
||||
short: "u";
|
||||
description: "Username to change to after set-up"; },
|
||||
@@ -54,6 +80,8 @@ config: {
|
||||
description: "Root to change to after set-up"; },
|
||||
{ name: "syslog_facility"; type: "string"; default: "auth";
|
||||
description: "Facility to syslog to"; },
|
||||
{ name: "logfile"; type: "string"; optional: true;
|
||||
description: "Log messages to a file" },
|
||||
|
||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||
description: "Target to connect to when timing out"; },
|
||||
@@ -84,8 +112,10 @@ config: {
|
||||
{ name: "fork"; type: "bool"; default: false },
|
||||
{ name: "tfo_ok"; type: "bool"; default: false;
|
||||
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" },
|
||||
{ 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: "keepalive"; type: "bool"; default: false },
|
||||
{ name: "sni_hostnames",
|
||||
@@ -105,7 +135,8 @@ config: {
|
||||
# Runtime data
|
||||
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
|
||||
{ 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" }
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -179,6 +210,18 @@ cl_groups: (
|
||||
{ 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";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
@@ -234,6 +277,17 @@ cl_groups: (
|
||||
{ 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";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#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) {
|
||||
/* Need room in the strcat for \0 and :
|
||||
* the format in the socket unit file is hostname:port */
|
||||
|
||||
@@ -20,7 +20,7 @@ use Conf::Libconfig;
|
||||
|
||||
# How many total clients to we start? Each client will pick
|
||||
# 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
|
||||
# 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
|
||||
# .01 a client will send an average of 100 messages before
|
||||
# disconnecting).
|
||||
my $stop_client_probability = .001;
|
||||
my $stop_client_probability = .0001;
|
||||
|
||||
##END CONFIG
|
||||
|
||||
@@ -65,7 +65,7 @@ my %connect_params = (
|
||||
test_data => "foo bar",
|
||||
resp_len => 12,
|
||||
},
|
||||
ssh => {
|
||||
ssh => {
|
||||
sleep => 20, # So it times out 50% of connections
|
||||
test_data => "SSH-2.0 hello",
|
||||
resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
|
||||
@@ -73,7 +73,7 @@ my %connect_params = (
|
||||
tinc => {
|
||||
sleep => 0,
|
||||
test_data => "0 ",
|
||||
resp_len => 8, # length "tinc: 0 " => 10
|
||||
resp_len => 8, # length "tinc: 0 " => 10
|
||||
},
|
||||
openvpn => {
|
||||
sleep => 0,
|
||||
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
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 */
|
||||
static int active_queue(struct connection* cnx, int fd)
|
||||
{
|
||||
if (cnx->q[0].fd == fd) return 0;
|
||||
if (cnx->q[1].fd == fd) return 1;
|
||||
|
||||
print_message(msg_int_error, "file descriptor %d not found in connection object\n", fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
/*
|
||||
FD_SET(q->fd, &fd_info->watchers->fds_w);
|
||||
FD_CLR(cnx->q[0].fd, &fd_info->watchers->fds_r); */
|
||||
watchers_add_write(fd_info->watchers, q->fd);
|
||||
watchers_del_read(fd_info->watchers, cnx->q[0].fd);
|
||||
}
|
||||
/* FD_SET(q->fd, &fd_info->watchers->fds_r); */
|
||||
watchers_add_read(fd_info->watchers, q->fd);
|
||||
collection_add_fd(fd_info->collection, cnx, q->fd);
|
||||
return q->fd;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
# 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));
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TCP_PROBE_H
|
||||
#define TCP_PROBE_H
|
||||
|
||||
void tcp_init(void);
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
# Configuration file for testing (use both by sslh under
|
||||
# test and the test script `t`)
|
||||
|
||||
verbose: 4;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
@@ -11,6 +10,21 @@ pidfile: "/tmp/sslh_test.pid";
|
||||
|
||||
syslog_facility: "auth";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: both
|
||||
# Defaults should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 1; # print configuration at startup
|
||||
verbose-config-error: 1; # print configuration errors
|
||||
verbose-connections: 1; # trace established incoming address to forward address
|
||||
verbose-connections-error: 1; # connection errors
|
||||
verbose-connections-try: 1; # connection attempts towards targets
|
||||
verbose-fd: 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
|
||||
# Options:
|
||||
@@ -21,6 +35,10 @@ listen:
|
||||
{ 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:
|
||||
(
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <fnmatch.h> /* fnmatch() */
|
||||
#include "tls.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "log.h"
|
||||
|
||||
#define TLS_HEADER_LEN 5
|
||||
#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];
|
||||
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;
|
||||
}
|
||||
|
||||
tls_version_major = data[1];
|
||||
tls_version_minor = data[2];
|
||||
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);
|
||||
|
||||
return TLS_EVERSION;
|
||||
@@ -111,7 +112,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
return TLS_EPROTOCOL;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -144,7 +145,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
|
||||
pos += 1 + len;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -228,7 +229,7 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
|
||||
return TLS_ENOEXT;
|
||||
}
|
||||
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]);
|
||||
}
|
||||
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)) {
|
||||
return len;
|
||||
} 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;
|
||||
}
|
||||
@@ -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++) {
|
||||
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)) {
|
||||
free(name_nullterminated);
|
||||
return 1;
|
||||
|
||||
+221
-45
@@ -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
|
||||
# and/or modify it under the terms of the GNU General Public
|
||||
@@ -20,45 +20,219 @@
|
||||
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.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
|
||||
* 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)
|
||||
|
||||
|
||||
static int cnx_cmp(struct connection* cnx1, struct connection* cnx2)
|
||||
{
|
||||
int i;
|
||||
struct sockaddr* addr1 = &cnx1->client_addr;
|
||||
socklen_t addrlen1 = cnx1->addrlen;
|
||||
|
||||
for (i = 0; i < max_fd; i++) {
|
||||
struct connection* cnx = collection_get_cnx_from_fd(collection, i);
|
||||
if (cnx && (cnx->type == SOCK_DGRAM) && cnx->target_sock) {
|
||||
if (!memcmp(&cnx->client_addr, addr, addrlen)) {
|
||||
return i;
|
||||
}
|
||||
struct sockaddr* addr2 = &cnx2->client_addr;
|
||||
socklen_t addrlen2 = cnx2->addrlen;
|
||||
|
||||
if (addrlen1 != addrlen2) return -1;
|
||||
|
||||
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));
|
||||
udp_protocols[udp_protocols_len-1] = p;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Configuration sanity check for UDP:
|
||||
* - If there is a listening addres, 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* 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);
|
||||
}
|
||||
|
||||
|
||||
/* Process UDP coming from outside (client towards server)
|
||||
* If it's a new source, probe; otherwise, forward to previous target
|
||||
* Returns: >= 0 sockfd of newly allocated socket, for new connections
|
||||
* -1 otherwise
|
||||
* Returns: newly allocate connections, for new connections
|
||||
* 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];
|
||||
struct sockaddr src_addr;
|
||||
struct addrinfo addrinfo;
|
||||
struct sslhcfg_protocols_item* proto;
|
||||
cnx_collection* collection = fd_info->collection;
|
||||
struct connection* cnx;
|
||||
ssize_t len;
|
||||
socklen_t addrlen;
|
||||
@@ -67,31 +241,36 @@ int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd)
|
||||
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 */
|
||||
|
||||
|
||||
udp_timeouts(fd_info);
|
||||
|
||||
addrlen = sizeof(src_addr);
|
||||
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
|
||||
if (len < 0) {
|
||||
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_addrlen = addrlen;
|
||||
if (cfg.verbose)
|
||||
fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||
print_message(msg_probe_info, "received %ld UDP from %d:%s\n",
|
||||
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
|
||||
|
||||
if (target == -1) {
|
||||
res = probe_buffer(data, len, &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
|
||||
* packet, drop it. Conceivably, we could store several packets to
|
||||
* 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) {
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0);
|
||||
res = set_nonblock(out);
|
||||
CHECK_RES_RETURN(res, "udp:socket:nonblock", NULL);
|
||||
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
|
||||
if (!cnx) return -1;
|
||||
if (!cnx) return NULL;
|
||||
target = out;
|
||||
cnx->target_sock = out;
|
||||
cnx->proto = proto;
|
||||
@@ -99,18 +278,25 @@ int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd)
|
||||
cnx->client_addr = src_addr;
|
||||
cnx->addrlen = addrlen;
|
||||
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 -- increaѕe udp_max_connections");
|
||||
collection_remove_cnx(collection, cnx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
cnx = collection_get_cnx_from_fd(collection, target);
|
||||
|
||||
/* at this point src is the UDP connection */
|
||||
res = sendto(cnx->target_sock, data, len, 0,
|
||||
cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen);
|
||||
cnx->last_active = time(NULL);
|
||||
fprintf(stderr, "sending %d to %s\n",
|
||||
mark_active(cnx);
|
||||
print_message(msg_fd, "sending %d to %s\n",
|
||||
res, sprintaddr(data, sizeof(data), cnx->proto->saddr));
|
||||
return out;
|
||||
}
|
||||
|
||||
return cnx;
|
||||
}
|
||||
|
||||
void udp_s2c_forward(struct connection* cnx)
|
||||
{
|
||||
@@ -119,20 +305,10 @@ void udp_s2c_forward(struct connection* cnx)
|
||||
int res;
|
||||
|
||||
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");
|
||||
res = sendto(cnx->local_endpoint, data, res, 0,
|
||||
&cnx->client_addr, cnx->addrlen);
|
||||
cnx->last_active = time(NULL);
|
||||
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;
|
||||
mark_active(cnx);
|
||||
}
|
||||
|
||||
+7
-6
@@ -1,7 +1,10 @@
|
||||
#ifndef UDPLISTENER_H
|
||||
#define UDPLISTENER_H
|
||||
|
||||
|
||||
#include "collection.h"
|
||||
#include "processes.h"
|
||||
#include "common.h"
|
||||
|
||||
/* UDP listener: upon incoming packet, find where it should go
|
||||
* 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)
|
||||
* 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
|
||||
* */
|
||||
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) */
|
||||
void udp_s2c_forward(struct connection* cnx);
|
||||
|
||||
|
||||
/* returns how many seconds before socket times out. Negative if timed out
|
||||
* already.
|
||||
*/
|
||||
int udp_timeout(struct connection* cnx);
|
||||
void udp_init(struct loop_info* fd_info);
|
||||
void udp_tidy(struct connection* cnx, struct loop_info* fd_info);
|
||||
|
||||
#endif /* UDPLISTENER_H */
|
||||
Reference in new issue
Block a user