moved UDP support from sslh-fork to sslh-select

This commit is contained in:
yrutschle 2021-07-01 22:44:35 +02:00
parent 24e7f46a43
commit 862e33cfec
10 changed files with 245 additions and 125 deletions

View File

@ -1,8 +1,6 @@
vNEXT: vNEXT:
UDP support now works. It only works with sslh-fork, UDP support now works. It only works with sslh-select.
which will create a single process that handles all Probes specified in the `protocols` configuration entry are tried on
UDP connections with select(). Probes specified in
the `protocols` configuration entry are tried on
incoming packets, TCP or UDP, and forwarded based on incoming packets, TCP or UDP, and forwarded based on
the input protocol (an incoming TCP connection will the input protocol (an incoming TCP connection will
be forwarded as TCP, and same with UDP). be forwarded as TCP, and same with UDP).
@ -11,6 +9,14 @@ vNEXT:
assumed to be a DNS request and forwarded assumed to be a DNS request and forwarded
accordingly. Note this could cause problems if accordingly. Note this could cause problems if
combined with incoming TLS with SNI. combined with incoming TLS with SNI.
UDP clients and servers need to agree on the
IPv4/IPv6 they use: use the same protocol on all
sides! Often, this means explicitely using
'ip4-localhost'.
Currently there is a hard limit of MAX_UDP_SRC
(1024) UDP connections tracked at once, which get
forgotten after a hardcoded timeout of UDP_TIMEOUT
(60s), all defined in udp-listener.c.
sslh-select refactored to remove linear searches. sslh-select refactored to remove linear searches.

View File

@ -68,6 +68,7 @@ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collectio
if (!cnx) return NULL; if (!cnx) return NULL;
init_cnx(cnx); init_cnx(cnx);
cnx->type = SOCK_STREAM;
cnx->q[0].fd = fd; cnx->q[0].fd = fd;
cnx->state = ST_PROBING; cnx->state = ST_PROBING;
cnx->probe_timeout = time(NULL) + cfg.timeout; cnx->probe_timeout = time(NULL) + cfg.timeout;

View File

@ -95,7 +95,12 @@ struct queue {
int deferred_data_size; int deferred_data_size;
}; };
struct known_udp_source;
struct connection { struct connection {
int type; /* SOCK_DGRAM | SOCK_STREAM */
/* SOCK_STREAM */
enum connection_state state; enum connection_state state;
time_t probe_timeout; time_t probe_timeout;
struct sslhcfg_protocols_item* proto; struct sslhcfg_protocols_item* proto;
@ -104,8 +109,12 @@ struct connection {
* q[1]: queue for internal connection (httpd or sshd); * q[1]: queue for internal connection (httpd or sshd);
* */ * */
struct queue q[2]; struct queue q[2];
/* SOCK_DGRAM */
struct known_udp_source* udp_source;
}; };
struct listen_endpoint { struct listen_endpoint {
int socketfd; /* file descriptor of listening socket */ int socketfd; /* file descriptor of listening socket */
int type; /* SOCK_DGRAM | SOCK_STREAM */ int type; /* SOCK_DGRAM | SOCK_STREAM */

View File

@ -135,7 +135,7 @@ void print_udp_xchange(int sockfd, struct sockaddr* addr, socklen_t addrlen)
void udp_echo(struct listen_endpoint* listen_socket) void udp_echo(struct listen_endpoint* listen_socket)
{ {
char data[65536]; char data[65536];
struct sockaddr src_addr, sock_name; struct sockaddr src_addr;
socklen_t addrlen; socklen_t addrlen;
memset(data, 0, sizeof(data)); memset(data, 0, sizeof(data));

View File

@ -220,7 +220,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
case 0: case 0:
set_listen_procname(&listen_sockets[i]); set_listen_procname(&listen_sockets[i]);
if (listen_sockets[i].type == SOCK_DGRAM) if (listen_sockets[i].type == SOCK_DGRAM)
udp_listener(listen_sockets, num_addr_listen, i); log_message(LOG_ERR, "UDP not (yet?) supported in sslh-fork\n");
else else
tcp_listener(listen_sockets, num_addr_listen, i); tcp_listener(listen_sockets, num_addr_listen, i);
break; break;

View File

@ -32,6 +32,7 @@
#include "common.h" #include "common.h"
#include "probe.h" #include "probe.h"
#include "udp-listener.h"
#include "collection.h" #include "collection.h"
#include "gap.h" #include "gap.h"
@ -339,7 +340,7 @@ int active_queue(struct connection* cnx, int fd)
} }
/* Process a connection that is active in read */ /* Process a connection that is active in read */
static void cnx_read_process(struct select_info* fd_info, static void tcp_read_process(struct select_info* fd_info,
int fd) int fd)
{ {
if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd); if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd);
@ -374,6 +375,26 @@ static void cnx_read_process(struct select_info* fd_info,
} }
} }
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->udp_source);
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 */ /* Process a connection that is active in write */
static void cnx_write_process(struct select_info* fd_info, int fd) static void cnx_write_process(struct select_info* fd_info, int fd)
@ -398,20 +419,45 @@ static void cnx_write_process(struct select_info* fd_info, int fd)
} }
} }
/* Process a connection that accepts a socket */ /* Process a connection that accepts a socket
void cnx_accept_process(struct select_info* fd_info, int fd) * (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); if (debug) fprintf(stderr, "cnx_accept_process fd %d\n", fd);
struct connection* cnx = accept_new_connection(fd, fd_info->collection); switch (type) {
case SOCK_STREAM:
cnx = accept_new_connection(fd, fd_info->collection);
if (cnx) { if (cnx) {
add_probing_cnx(fd_info, cnx); add_probing_cnx(fd_info, cnx);
int new_socket = cnx->q[0].fd; new_fd = cnx->q[0].fd;
FD_SET(new_socket, &fd_info->fds_r); }
if (new_socket >= fd_info->max_fd) break;
fd_info->max_fd = new_socket + 1;
case SOCK_DGRAM:
new_fd = udp_c2s_forward(fd, fd_info->collection);
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;
} }
@ -465,10 +511,25 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
if (res < 0) if (res < 0)
perror("select"); perror("select");
/* UDP timeouts: clear out connections after some idle time */
for (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 && udp_timedout(cnx)) {
FD_CLR(i, &fd_info.fds_r);
FD_CLR(i, &fd_info.fds_w);
collection_remove_cnx(fd_info.collection, cnx);
}
}
}
/* Check main socket for new connections */ /* Check main socket for new connections */
for (i = 0; i < num_addr_listen; i++) { for (i = 0; i < num_addr_listen; i++) {
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) { if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
cnx_accept_process(&fd_info, listen_sockets[i].socketfd); cnx_accept_process(&fd_info, &listen_sockets[i]);
/* don't also process it as a read socket */ /* don't also process it as a read socket */
FD_CLR(listen_sockets[i].socketfd, &readfds); FD_CLR(listen_sockets[i].socketfd, &readfds);
@ -507,7 +568,6 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
cnx_read_process(&fd_info, i); cnx_read_process(&fd_info, i);
} }
} }
} }
} }

110
t_load
View File

@ -18,12 +18,9 @@ use Conf::Libconfig;
## BEGIN TEST CONFIG ## BEGIN TEST CONFIG
# Do we test sslh-select or sslh-fork?
my $sslh_binary = "./sslh-select";
# How many total clients to we start? Each client will pick # How many total clients to we start? Each client will pick
# a new protocol among what's in test.cfg. # a new protocol among what's in test.cfg.
my $NUM_CNX = 20; my $NUM_CNX = 50;
# Delay between starting new processes when starting up. If # Delay between starting new processes when starting up. If
# you start 200 processes in under a second, things go wrong # you start 200 processes in under a second, things go wrong
@ -33,7 +30,7 @@ my $start_time_delay = .5;
# Max times we repeat the test string: allows to test for # Max times we repeat the test string: allows to test for
# large messages. # large messages.
my $block_rpt = 4096; my $block_rpt = 5;
# Probability to stop a client after a message (e.g. with # Probability to stop a client after a message (e.g. with
# .01 a client will send an average of 100 messages before # .01 a client will send an average of 100 messages before
@ -53,28 +50,33 @@ my ($sslh_tcp_address, $sslh_udp_address);
foreach my $l (@listen) { foreach my $l (@listen) {
if ($l->{is_udp}) { if ($l->{is_udp}) {
$sslh_udp_address //= "$l->{host}:$l->{port}"; $sslh_udp_address //= "$l->{host}:$l->{port}";
last if defined $sslh_tcp_address;
} else { } else {
$sslh_tcp_address //= "$l->{host}:$l->{port}"; $sslh_tcp_address //= "$l->{host}:$l->{port}";
last if defined $sslh_udp_address;
} }
last if defined $sslh_tcp_address and defined $sslh_udp_address;
} }
# code snippets to connect to each protocol # code snippets to connect to each protocol
my %connect_params = ( my %connect_params = (
ssh => { regex => {
is_udp => 1,
sleep => 0,
test_data => "foo bar",
resp_len => 12,
},
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 => 10, 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 => 10, 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
}, },
@ -103,13 +105,13 @@ sub client {
while (1) { while (1) {
my $r; my $r;
warn "$client_id: connect $sslh_tcp_address\n"; #warn "$client_id: connect $sslh_tcp_address\n";
my $cnx = new IO::Socket::INET(PeerHost => $sslh_tcp_address); my $cnx = new IO::Socket::INET(PeerHost => $sslh_tcp_address);
die "$@\n" if (!$cnx); die "$@\n" if (!$cnx);
my $cnt = 0; my $cnt = 0;
warn "$client_id: connecting $service\n"; #warn "$client_id: connecting $service\n";
if (not connect_service($cnx, $service)) { if (not connect_service($cnx, $service)) {
print $fd_out "$client_id\t0\tC\n"; print $fd_out "$client_id\t0\tC\n";
@ -117,7 +119,7 @@ sub client {
exit; exit;
} }
warn "$client_id: shoveling $service\n"; #warn "$client_id: shoveling $service\n";
while (1) { while (1) {
my $test_data = "$service $cnt" x int(rand($block_rpt)+1) . "\n"; my $test_data = "$service $cnt" x int(rand($block_rpt)+1) . "\n";
@ -141,26 +143,60 @@ sub client {
exit 0; exit 0;
} }
# For now, a simple regex client
sub udp_client {
my ($protocol, $client_id, $fd_out) = @_;
warn "UDP client starts\n";
while (1) {
my $cnx = new IO::Socket::INET(Proto => 'udp', PeerHost => $sslh_udp_address);
# my $cnx; socket $cnx, PF_INET, SOCK_DGRAM, 0 or die "socket: $!\n";
die "$@\n" if (!$cnx);
my $cnt = 0;
while (1) {
my $test_data = "foo udp $cnt"x int(rand($block_rpt)+1). "\n";
my $ipaddr = inet_aton("localhost");
my $portaddr = sockaddr_in(8086, $ipaddr);
my $res = send($cnx, $test_data, 0, $portaddr);
if ($res != length($test_data)) {
die "cannot sendto: $!";
}
my $expected= "$protocol->{name}: $test_data";
my $r;
defined(recv($cnx, $r, length $expected, 0)) or die "recv: $!\n";
my $r_l = length $r;
my $e_l = length $expected;
$fd_out->autoflush;
my $error = "";
$error = "M" if $r ne $expected;
print $fd_out ("$client_id\t$r_l\t$error\n");
($? = 1, die "udp got [$r] expected [$expected]\n") if ($r ne $expected);
if (rand(1) < $stop_client_probability) {
print $fd_out ("$client_id\t$r_l\tD\n");
last;
}
$cnt++;
}
}
}
foreach my $p (@{$conf->fetch_array("protocols")}) { foreach my $p (@{$conf->fetch_array("protocols")}) {
if (!fork) { if (!fork) {
my $udp = $p->{is_udp} ? "--udp" : ""; my $udp = $p->{is_udp} ? "--udp" : "";
my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: '"; my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null";
warn "$cmd\n"; warn "$cmd\n";
exec $cmd; exec $cmd;
exit; exit;
} }
} }
# Start sslh with the right plumbing warn "Don't forget to run sslh -F test.cfg!\n";
my $sslh_pid;
if (0) {
if (!($sslh_pid = fork)) {
my $cmd = "$sslh_binary -F test.cfg";
warn "$cmd\n";
exec $cmd;
}
warn "spawned $sslh_pid\n";
}
sleep 2; # Let echosrv's and sslh start sleep 2; # Let echosrv's and sslh start
@ -178,6 +214,7 @@ if (!fork) {
my @p = grep { $_->{name} eq $p_name } @protocols; my @p = grep { $_->{name} eq $p_name } @protocols;
my $p = shift @p; my $p = shift @p;
if ($p->{is_udp}) { if ($p->{is_udp}) {
udp_client($p, "$p->{name}$client_num", $c_out);
} else { } else {
client($p, "$p->{name}$client_num", $c_out); client($p, "$p->{name}$client_num", $c_out);
} }
@ -194,22 +231,31 @@ if (!fork) {
# The condition here selects between pretty output or # The condition here selects between pretty output or
# raw output # raw output
if (1) { if (1) {
my $CLEAR_LINE = "\033[2K";
my $CURSOR_HOME = "\033[1;1H";
my $CLEAR_SCREEN = "\033[2J";
# Process that retrieves client output to pretty print # Process that retrieves client output to pretty print
print "\033[2J"; print $CLEAR_SCREEN; # Clear screen
while (<$c_in>) { while (<$c_in>) {
chop; chop;
my ($client_id, $r_l, $error, @rest) = split /\t/, $_; my ($client_id, $r_l, $error, @rest) = split /\t/, $_;
my ($curr_rcv) = ${$data{$client_id}}[0]; $data{$client_id} = [ 0, ""] if not exists $data{$client_id};
my ($curr_error) = ${$data{$client_id}}[1] // ""; my ($curr_rcv) = ${$data{$client_id}}[0] + $r_l;;
$error //= ""; $error //= "";
my ($curr_error) = "${$data{$client_id}}[1]$error";
$data{$client_id} = [ $r_l + $curr_rcv, "$curr_error$error" ]; $data{$client_id} = [ $r_l + $curr_rcv, "$curr_error$error" ];
print "\033[0;0H";
foreach my $i (sort keys %data) { $client_id =~ /(\d+)/;
($r_l, $error) = @{$data{$i}}; my $i = $1;
print "\033[2K$i\t$r_l\t$error\n"; # print $CURSOR_HOME;
} print "\033[$i;1H$CLEAR_LINE$client_id\t$curr_rcv\t$curr_error\n";
#foreach my $i (sort keys %data) {
# ($r_l, $error) = @{$data{$i}};
# print "$CLEAR_LINE$i\t$r_l\t$error\n";
} }
} else { } else {
# Just print the client outputs # Just print the client outputs

View File

@ -4,7 +4,7 @@
verbose: 3; verbose: 3;
foreground: true; foreground: true;
inetd: false; inetd: false;
numeric: false; numeric: true;
transparent: false; transparent: false;
timeout: 10; # Probe test writes slowly timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid"; pidfile: "/tmp/sslh_test.pid";
@ -17,8 +17,8 @@ syslog_facility: "auth";
listen: listen:
( (
{ host: "localhost"; port: "8080"; keepalive: true; }, { host: "localhost"; port: "8080"; keepalive: true; },
{ host: "localhost"; port: "8081"; keepalive: true; } { host: "localhost"; port: "8081"; keepalive: true; },
# { host: "localhost"; is_udp: true; port: "4443"; } { host: "ip4-localhost"; is_udp: true; port: "8086"; }
); );
@ -31,7 +31,9 @@ protocols:
{ name: "openvpn"; host: "localhost"; port: "9004"; }, { name: "openvpn"; host: "localhost"; port: "9004"; },
{ name: "xmpp"; host: "localhost"; port: "9009"; }, { name: "xmpp"; host: "localhost"; port: "9009"; },
{ name: "adb"; host: "localhost"; port: "9010"; }, { name: "adb"; host: "localhost"; port: "9010"; },
{ name: "regex"; host: "localhost"; is_udp: true; port: "2020"; }, { name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020";
regex_patterns: [ "^foo" ];
},
{ name: "regex"; host: "localhost"; port: "9011"; { name: "regex"; host: "localhost"; port: "9011";
regex_patterns: [ "^foo", "^bar" ]; regex_patterns: [ "^foo", "^bar" ];
minlength: 4; minlength: 4;

View File

@ -23,13 +23,17 @@
#include "common.h" #include "common.h"
#include "probe.h" #include "probe.h"
#include "sslh-conf.h" #include "sslh-conf.h"
#include "udp-listener.h"
/* UDP support types and stuff */ /* UDP support types and stuff */
struct known_udp_source { struct known_udp_source {
int allocated; int allocated;
struct sockaddr client_addr; struct sockaddr client_addr; /* Contains the remote client address */
socklen_t addrlen; socklen_t addrlen;
int local_endpoint; /* Contains the local address */
time_t last_active; time_t last_active;
struct sslhcfg_protocols_item* proto; /* Where to connect it to */ struct sslhcfg_protocols_item* proto; /* Where to connect it to */
@ -73,13 +77,13 @@ static int get_empty_source(struct known_udp_source* ks, int ks_len)
/* Array to keep the UDP sources we have seen before */ /* Array to keep the UDP sources we have seen before */
struct known_udp_source udp_known_sources[MAX_UDP_SRC]; struct known_udp_source udp_known_sources[MAX_UDP_SRC];
/* Process UDP coming from outside (client towards server) /* Process UDP coming from outside (client towards server)
* If it's a new source, probe; otherwise, forward to previous target * If it's a new source, probe; otherwise, forward to previous target
* Returns: >= 0 sockfd of newly allocated socket, for new connections * Returns: >= 0 sockfd of newly allocated socket, for new connections
* -1 otherwise * -1 otherwise
* */ * */
static int udp_c2s_forward(int sockfd) { int udp_c2s_forward(int sockfd, cnx_collection* collection)
{
char addr_str[NI_MAXHOST+1+NI_MAXSERV+1]; char addr_str[NI_MAXHOST+1+NI_MAXSERV+1];
struct sockaddr src_addr; struct sockaddr src_addr;
struct addrinfo addrinfo; struct addrinfo addrinfo;
@ -91,7 +95,6 @@ static int udp_c2s_forward(int sockfd) {
This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply
malloc/free overhead for each packet, when really 64K is not that much */ malloc/free overhead for each packet, when really 64K is not that much */
fprintf(stderr, "recvfrom(%d)\n", getpid());
addrlen = sizeof(src_addr); addrlen = sizeof(src_addr);
len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen); len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen);
if (len < 0) { if (len < 0) {
@ -104,107 +107,85 @@ static int udp_c2s_forward(int sockfd) {
addrinfo.ai_addrlen = addrlen; addrinfo.ai_addrlen = addrlen;
if (cfg.verbose) if (cfg.verbose)
fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo)); fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo));
if (target == -1) { if (target == -1) {
target = get_empty_source(udp_known_sources, ARRAY_SIZE(udp_known_sources)); target = get_empty_source(udp_known_sources, ARRAY_SIZE(udp_known_sources));
fprintf(stderr, "source target index %d\n", target); fprintf(stderr, "source target index %d\n", target);
if (target == -1) exit(0); /* TODO handle this properly */ if (target == -1) {
/* A probe worked: save this as an active connection */ fprintf(stderr, "Out of UDP structs\n");
exit(0); /* TODO handle this properly */
}
/* save this as an active connection */
src = &udp_known_sources[target]; src = &udp_known_sources[target];
src->allocated = 1; src->allocated = 1;
src->client_addr = src_addr; src->client_addr = src_addr;
src->addrlen = addrlen; src->addrlen = addrlen;
src->last_active = time(NULL); src->local_endpoint = sockfd;
res = probe_buffer(data, len, &src->proto); res = probe_buffer(data, len, &src->proto);
/* First version: if we can't work out the protocol from the first /* First version: if we can't work out the protocol from the first
* packet, drop it. Conceivably, we could store several packets to * packet, drop it. Conceivably, we could store several packets to
* run probes on packet sets */ * run probes on packet sets */
if (cfg.verbose) fprintf(stderr, "UDP probed: %d\n", res); if (cfg.verbose) fprintf(stderr, "UDP probed: %d\n", res);
if (res != PROBE_MATCH) return -1; if (res != PROBE_MATCH) {
src->allocated = 0;
return -1;
}
src->target_sock = socket(src->proto->saddr->ai_family, SOCK_DGRAM, 0); src->target_sock = socket(src->proto->saddr->ai_family, SOCK_DGRAM, 0);
out = src->target_sock; out = src->target_sock;
struct connection* cnx = collection_alloc_cnx_from_fd(collection, out);
cnx->type = SOCK_DGRAM;
cnx->udp_source = &udp_known_sources[target];
} }
src = &udp_known_sources[target]; src = &udp_known_sources[target];
/* at this point src is the UDP connection */ /* at this point src is the UDP connection */
res = sendto(src->target_sock, data, len, 0, res = sendto(src->target_sock, data, len, 0,
src->proto->saddr->ai_addr, src->proto->saddr->ai_addrlen); src->proto->saddr->ai_addr, src->proto->saddr->ai_addrlen);
src->last_active = time(NULL); src->last_active = time(NULL);
fprintf(stderr, "sending %d to %s", fprintf(stderr, "sending %d to %s\n",
res, sprintaddr(data, sizeof(data), src->proto->saddr)); res, sprintaddr(data, sizeof(data), src->proto->saddr));
return out; return out;
} }
void udp_s2c_forward(struct known_udp_source* src)
{
int sockfd = src->target_sock;
char data[65536];
int res;
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
fprintf(stderr, "recvfrom %d\n", res);
CHECK_RES_DIE(res, "udp_listener/recvfrom");
res = sendto(src->local_endpoint, data, res, 0,
&src->client_addr, src->addrlen);
src->last_active = time(NULL);
fprintf(stderr, "sendto %d to\n", res);
}
/* Clears old connections from udp_known_sources, and from passed fd_set */ /* Clears old connections from udp_known_sources, and from passed fd_set */
#define UDP_TIMEOUT 60 /* Timeout before forgetting the connection, in seconds */ #define UDP_TIMEOUT 60 /* Timeout before forgetting the connection, in seconds */
static void reap_timeouts(struct known_udp_source* sources, int n_src, fd_set* fd) int udp_timedout(struct connection* cnx)
{ {
int i; int i;
time_t now = time(NULL); time_t now = time(NULL);
struct known_udp_source* src; struct known_udp_source* src = cnx->udp_source;
for (i = 0; i < n_src; i++) { if (!cnx->udp_source) return 0; /* Not a UDP connection */
src = &sources[i];
if (src->allocated && (now - src->last_active > UDP_TIMEOUT)) { if (src->allocated && (now - src->last_active > UDP_TIMEOUT)) {
close(src->target_sock); close(src->target_sock);
FD_CLR(src->target_sock, fd); memset(src, 0, sizeof(*src));
memset(&sources[i], 0, sizeof(sources[i])); if (cfg.verbose > 3)
if (cfg.verbose > 3) fprintf(stderr, "disconnect timed out UDP %d\n", i);
fprintf(stderr, "disconnect %d\n", i); return 1;
}
} }
return 0;
} }
/* UDP listener: upon incoming packet, find where it should go
* This is run in its own process and never returns.
*/
void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
{
fd_set fds_r, fds_r_tmp;
char data[65536]; /* TODO what? */
int max_fd, res, sockfd, i;
struct known_udp_source* src;
struct timeval tv;
FD_ZERO(&fds_r);
FD_SET(endpoint[active_endpoint].socketfd, &fds_r);
max_fd = endpoint[active_endpoint].socketfd + 1;
while (1) {
fds_r_tmp = fds_r;
tv.tv_sec = 1;
tv.tv_usec = 0;
res = select(max_fd + 1, &fds_r_tmp, NULL, NULL, &tv);
CHECK_RES_DIE(res, "select");
if (res) {
if (FD_ISSET(endpoint[active_endpoint].socketfd, &fds_r_tmp)) {
sockfd = udp_c2s_forward(endpoint[active_endpoint].socketfd);
if (sockfd >= 0) {
FD_SET(sockfd, &fds_r);
max_fd = MAX(max_fd, sockfd);
}
} else {
for (i = 0; i < ARRAY_SIZE(udp_known_sources); i++) {
src = &udp_known_sources[i];
if (src->allocated) {
sockfd = src->target_sock;
if (FD_ISSET(sockfd, &fds_r_tmp)) {
res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL);
fprintf(stderr, "recvfrom %d\n", res);
CHECK_RES_DIE(res, "udp_listener/recvfrom");
res = sendto(endpoint[active_endpoint].socketfd, data, res, 0,
&src->client_addr, src->addrlen);
src->last_active = time(NULL);
fprintf(stderr, "sendto %d to\n", res);
}
}
}
}
}
reap_timeouts(udp_known_sources, ARRAY_SIZE(udp_known_sources), &fds_r);
}
}

View File

@ -1,11 +1,26 @@
#ifndef UDPLISTENER_H #ifndef UDPLISTENER_H
#define UDPLISTENER_H #define UDPLISTENER_H
#include "collection.h"
/* UDP listener: upon incoming packet, find where it should go /* UDP listener: upon incoming packet, find where it should go
* This is run in its own process and never returns. * This is run in its own process and never returns.
*/ */
void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint); void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint);
/* 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
* */
int udp_c2s_forward(int sockfd, cnx_collection* collection);
/* Process UDP coming from inside (server towards client) */
void udp_s2c_forward(struct known_udp_source* src);
/* Checks if a connection timed out, in which case clear it. */
int udp_timedout(struct connection* cnx);
#endif /* UDPLISTENER_H */ #endif /* UDPLISTENER_H */