sort target protocols as TCP or UDP, so only appropriate probes are called by the listeners

This commit is contained in:
yrutschle 2022-05-05 17:45:40 +02:00
parent 78827d75fe
commit f6fe735171
12 changed files with 148 additions and 58 deletions

View File

@ -32,7 +32,7 @@ CC ?= gcc
CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN) CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
LIBS=-lm -lpcre2-8 LIBS=-lm -lpcre2-8
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o
FORK_OBJS=$(OBJS) sslh-fork.o FORK_OBJS=$(OBJS) sslh-fork.o
SELECT_OBJS=$(OBJS) processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o SELECT_OBJS=$(OBJS) processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o
EV_OBJS=$(OBJS) processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o EV_OBJS=$(OBJS) processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o

52
probe.c
View File

@ -340,12 +340,20 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item
} }
/* Run all the probes on a buffer /* Run all the probes on a buffer
* buf, len: buffer to test on
* proto_in, proto_len: array of protocols to try
* proto_out: protocol that matched
*
* Returns * Returns
* PROBE_AGAIN if not enough data, and set *proto to NULL * PROBE_AGAIN if not enough data, and set *proto to NULL
* PROBE_MATCH if protocol is identified, in which case *proto is set to * PROBE_MATCH if protocol is identified, in which case *proto is set to
* point to the appropriate protocol * point to the appropriate protocol
* */ * */
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto) int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item** proto_in,
int proto_len,
struct sslhcfg_protocols_item** proto_out
)
{ {
struct sslhcfg_protocols_item* p; struct sslhcfg_protocols_item* p;
int i, res, again = 0; int i, res, again = 0;
@ -353,17 +361,17 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
print_message(msg_packets, "hexdump of incoming packet:\n"); print_message(msg_packets, "hexdump of incoming packet:\n");
hexdump(msg_packets, buf, len); hexdump(msg_packets, buf, len);
*proto = NULL; *proto_out = NULL;
for (i = 0; i < cfg.protocols_len; i++) { for (i = 0; i < proto_len; i++) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"}; char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
p = &cfg.protocols[i]; p = proto_in[i];
if (! p->probe) continue; if (! p->probe) continue;
print_message(msg_probe_info, "probing for %s\n", p->name); print_message(msg_probe_info, "probing for %s\n", p->name);
/* Don't probe last protocol if it is anyprot (and store last protocol) */ /* Don't probe last protocol if it is anyprot (and store last protocol) */
if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot"))) if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
break; break;
if (p->minlength_is_present && (len < p->minlength )) { if (p->minlength_is_present && (len < p->minlength )) {
@ -377,7 +385,7 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]); print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]);
if (res == PROBE_MATCH) { if (res == PROBE_MATCH) {
*proto = p; *proto_out = p;
return PROBE_MATCH; return PROBE_MATCH;
} }
if (res == PROBE_AGAIN) if (res == PROBE_AGAIN)
@ -387,37 +395,7 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto)
return PROBE_AGAIN; return PROBE_AGAIN;
/* Everything failed: match the last one */ /* Everything failed: match the last one */
*proto = &cfg.protocols[cfg.protocols_len-1]; *proto_out = proto_in[proto_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);
}
/* read() returned an error, so just connect to the last protocol to die */
cnx->proto = &cfg.protocols[cfg.protocols_len-1];
return PROBE_MATCH; return PROBE_MATCH;
} }

View File

@ -48,8 +48,12 @@ void set_protocol_list(struct sslhcfg_protocols_item*);
*/ */
int probe_client_protocol(struct connection *cnx); int probe_client_protocol(struct connection *cnx);
/* Probe, but on a buffer */ /* Probe on a buffer */
int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto); int probe_buffer(char* buf, int len,
struct sslhcfg_protocols_item** proto_in,
int proto_len,
struct sslhcfg_protocols_item** proto_out
);
/* set the protocol to connect to in case of timeout */ /* set the protocol to connect to in case of timeout */
void set_ontimeout(const char* name); void set_ontimeout(const char* name);

View File

@ -143,6 +143,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
ev_info.collection = collection_init(0); ev_info.collection = collection_init(0);
ev_info.probing_list = gap_init(0); ev_info.probing_list = gap_init(0);
udp_init(&ev_info); udp_init(&ev_info);
tcp_init();
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen); watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
ev_set_userdata(EV_A_ &ev_info); ev_set_userdata(EV_A_ &ev_info);

View File

@ -23,7 +23,7 @@
#include "common.h" #include "common.h"
#include "probe.h" #include "probe.h"
#include "sslh-conf.h" #include "sslh-conf.h"
#include "udp-listener.h" #include "tcp-probe.h"
#include "log.h" #include "log.h"
#ifdef LIBBSD #ifdef LIBBSD
@ -207,6 +207,8 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0])); listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
CHECK_ALLOC(listener_pid, "malloc"); CHECK_ALLOC(listener_pid, "malloc");
tcp_init();
/* Start one process for each listening address */ /* Start one process for each listening address */
for (i = 0; i < num_addr_listen; i++) { for (i = 0; i < num_addr_listen; i++) {
listener_pid[i] = fork(); listener_pid[i] = fork();

View File

@ -131,6 +131,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
fd_info.num_probing = 0; fd_info.num_probing = 0;
fd_info.probing_list = gap_init(0); fd_info.probing_list = gap_init(0);
udp_init(&fd_info); udp_init(&fd_info);
tcp_init();
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen); watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);

30
t_load
View File

@ -65,21 +65,21 @@ my %connect_params = (
test_data => "foo bar", test_data => "foo bar",
resp_len => 12, resp_len => 12,
}, },
# ssh => { ssh => {
# sleep => 20, # So it times out 50% of connections sleep => 20, # So it times out 50% of connections
# test_data => "SSH-2.0 hello", test_data => "SSH-2.0 hello",
# resp_len => 18, # length "ssh: SSH-2.0 hello" => 18 resp_len => 18, # length "ssh: SSH-2.0 hello" => 18
# }, },
# tinc => { tinc => {
# sleep => 0, sleep => 0,
# test_data => "0 ", test_data => "0 ",
# resp_len => 8, # length "tinc: 0 " => 10 resp_len => 8, # length "tinc: 0 " => 10
# }, },
# openvpn => { openvpn => {
# sleep => 0, sleep => 0,
# test_data => "\x00\x00", test_data => "\x00\x00",
# resp_len => 11, # length "openvpn: \x0\x0" => 11 resp_len => 11, # length "openvpn: \x0\x0" => 11
# }, },
); );
sub connect_service { sub connect_service {

View File

@ -3,6 +3,7 @@
#include "processes.h" #include "processes.h"
#include "collection.h" #include "collection.h"
#include "tcp-probe.h"
void tcp_read_process(struct loop_info* fd_info, int fd); void tcp_read_process(struct loop_info* fd_info, int fd);
struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info); struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info);

76
tcp-probe.c Normal file
View File

@ -0,0 +1,76 @@
/*
# 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;
}
}
}
void tcp_init(void)
{
tcp_protocol_list_init();
}

6
tcp-probe.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef TCP_PROBE_H
#define TCP_PROBE_H
void tcp_init(void);
#endif

View File

@ -36,6 +36,10 @@ listen:
); );
# Tester beware: when using fork, the forked process loses
# track of buffers of other, concurrent connections. Memory
# leak tools thus complain each time a forked process stops.
protocols: protocols:
( (
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; }, { name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; },

View File

@ -76,6 +76,21 @@ static int hash_make_key(hash_item new)
return out; 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;
}
}
}
/* Init the UDP subsystem. /* Init the UDP subsystem.
* - Initialise the hash * - Initialise the hash
* - that's all, folks * - that's all, folks
@ -83,6 +98,8 @@ static int hash_make_key(hash_item new)
void udp_init(struct loop_info* fd_info) void udp_init(struct loop_info* fd_info)
{ {
fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp); fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp);
udp_protocol_list_init();
} }
@ -215,7 +232,7 @@ int udp_c2s_forward(int sockfd, struct loop_info* fd_info)
len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo)); len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
if (target == -1) { if (target == -1) {
res = probe_buffer(data, len, &proto); res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto);
/* First version: if we can't work out the protocol from the first /* First version: if we can't work out the protocol from the first
* packet, drop it. Conceivably, we could store several packets to * packet, drop it. Conceivably, we could store several packets to
* run probes on packet sets */ * run probes on packet sets */