diff --git a/debian/control b/debian/control index b7f4a76fe..c1649416f 100644 --- a/debian/control +++ b/debian/control @@ -14,14 +14,24 @@ Build-Depends: libavformat-dev (>= 6:10), libavutil-dev (>= 6:10), libbcg729-dev , + libbencode-perl, + libcrypt-openssl-rsa-perl, + libcrypt-rijndael-perl, libcurl4-openssl-dev | libcurl4-gnutls-dev, + libdigest-crc-perl, + libdigest-hmac-perl, libevent-dev (>= 2.0), libglib2.0-dev (>= 2.30), libhiredis-dev, + libio-multiplex-perl, + libio-socket-inet6-perl, + libio-socket-ip-perl, libiptc-dev, libjson-glib-dev, + libnet-interface-perl, libpcap0.8-dev, libpcre3-dev, + libsocket6-perl, libssl-dev (>= 1.0.1), libswresample-dev (>= 6:10), libsystemd-dev, diff --git a/perl/NGCP/Rtpengine/Test.pm b/perl/NGCP/Rtpengine/Test.pm index ef7d76b8d..84e400ae5 100644 --- a/perl/NGCP/Rtpengine/Test.pm +++ b/perl/NGCP/Rtpengine/Test.pm @@ -26,19 +26,32 @@ sub new { my $self = {}; bless $self, $class; + srand(1234) if $ENV{RTPE_TEST_PSEUDO_RAND}; + # detect local interfaces + my (@v4, @v6); my @intfs = Net::Interface->interfaces(); - my @v4 = map {$_->address(&AF_INET)} @intfs; - @v4 = map {Socket6::inet_ntop(&AF_INET, $_)} @v4; - @v4 = grep {$_ !~ /^127\./} @v4; + if ($ENV{RTPE_TEST_V4_ADDRS}) { + @v4 = split(/ /, $ENV{RTPE_TEST_V4_ADDRS}); + } + else { + @v4 = map {$_->address(&AF_INET)} @intfs; + @v4 = map {Socket6::inet_ntop(&AF_INET, $_)} @v4; + @v4 = grep {$_ !~ /^127\./} @v4; + } @v4 = map { { address => $_, sockdomain => &AF_INET } } @v4; @v4 or die("no IPv4 addresses found"); - my @v6 = map {$_->address(&AF_INET6)} @intfs; - @v6 = map {Socket6::inet_ntop(&AF_INET6, $_)} @v6; - @v6 = grep {$_ !~ /^::|^fe80:/} @v6; + if ($ENV{RTPE_TEST_V6_ADDRS}) { + @v6 = split(/ /, $ENV{RTPE_TEST_V6_ADDRS}); + } + else { + @v6 = map {$_->address(&AF_INET6)} @intfs; + @v6 = map {Socket6::inet_ntop(&AF_INET6, $_)} @v6; + @v6 = grep {$_ !~ /^::|^fe80:/} @v6; + } @v6 = map { { address => $_, sockdomain => &AF_INET6 } } @v6; @v6 or die("no IPv6 addresses found"); @@ -51,12 +64,12 @@ sub new { $self->{mux} = IO::Multiplex->new(); $self->{mux}->set_callback_object($self); - $self->{media_port} = $args{media_port} // 2000; + $self->{media_port} = $args{media_port} // $ENV{RTPE_TEST_MEDIA_PORT} // 2000; $self->{timers} = []; $self->{clients} = []; $self->{control} = NGCP::Rtpengine->new($args{host} // $ENV{RTPENGINE_HOST} // 'localhost', - $args{port} // $ENV{RTPENGINE_POR} // 2223); + $args{port} // $ENV{RTPENGINE_PORT} // 2223); $self->{callid} = rand(); return $self; diff --git a/t/.gitignore b/t/.gitignore index 003bc046d..84d496f41 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -45,3 +45,4 @@ udp_listener.c payload-tracker-test dtmf.c const_str_hash-test.strhash +tests-preload.so diff --git a/t/Makefile b/t/Makefile index 6b086d953..6786cb7ec 100644 --- a/t/Makefile +++ b/t/Makefile @@ -1,4 +1,4 @@ -TARGET= unit-tests +TARGET= all-tests with_transcoding ?= yes @@ -71,18 +71,30 @@ include ../lib/common.Makefile include .depend -.PHONY: unit-tests +.PHONY: all-tests unit-tests daemon-tests TESTS= bitstr-test aes-crypt payload-tracker-test const_str_hash-test.strhash ifeq ($(with_transcoding),yes) TESTS+= amr-decode-test amr-encode-test transcode-test endif -ADD_CLEAN= $(TESTS) +ADD_CLEAN= tests-preload.so $(TESTS) + +all-tests: unit-tests daemon-tests + true # override linking recipe from common.Makefile unit-tests: $(TESTS) for x in $(TESTS); do echo testing: $$x; G_DEBUG=fatal-warnings ./$$x || exit 1; done +daemon-tests: tests-preload.so + $(MAKE) -C ../daemon + rm -rf fake-sockets + mkdir fake-sockets + LD_PRELOAD=../t/tests-preload.so RTPE_BIN=../daemon/rtpengine TEST_SOCKET_PATH=./fake-sockets \ + perl -I../perl auto-daemon-tests.pl + test "$$(ls fake-sockets)" = "" + rmdir fake-sockets + bitstr-test: bitstr-test.o amr-decode-test: amr-decode-test.o $(COMMONOBJS) codeclib.o resample.o @@ -100,3 +112,6 @@ transcode-test: transcode-test.o $(COMMONOBJS) codeclib.o resample.o codec.o ssr payload-tracker-test: payload-tracker-test.o $(COMMONOBJS) ssrc.o aux.o auxlib.o rtp.o crypto.o const_str_hash-test.strhash: const_str_hash-test.strhash.o $(COMMONOBJS) + +tests-preload.so: tests-preload.c + $(CC) -g -D_GNU_SOURCE -std=c99 -o $@ -Wall -shared -fPIC $< diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl new file mode 100755 index 000000000..17ccc7857 --- /dev/null +++ b/t/auto-daemon-tests.pl @@ -0,0 +1,485 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use NGCP::Rtpengine::Test; +use Test::More; +use File::Temp; +use IPC::Open3; +use Time::HiRes; +use POSIX ":sys_wait_h"; + +like $ENV{LD_PRELOAD}, qr/tests-preload/, 'LD_PRELOAD present'; +is $ENV{RTPE_PRELOAD_TEST_ACTIVE}, '1', 'preload library is active'; +ok -x $ENV{RTPE_BIN}, 'RTPE_BIN points to executable'; + +my $rtpe_stdout = File::Temp::tempfile() or die; +my $rtpe_stderr = File::Temp::tempfile() or die; +my $rtpe_pid = open3(undef, $rtpe_stdout, $rtpe_stderr, + $ENV{RTPE_BIN}, qw(-t -1 -i 203.0.113.1 -i 2001:db8:4321::1 -n 2223 -c 12345 -f -L 7 -E -u 2222)); +ok $rtpe_pid, 'daemon launched in background'; + +# keep trying to connect to the control socket while daemon is starting up +my $c; +for (1 .. 300) { + $c = NGCP::Rtpengine->new($ENV{RTPENGINE_HOST} // 'localhost', $ENV{RTPENGINE_PORT} // 2223); + last if $c->{socket}; + Time::HiRes::usleep(100000); # 100 ms x 300 = 30 sec +} + +1; +$c->{socket} or die; + +my ($cid, $ft, $tt, $r); + +sub new_call { + undef($r); + $cid = rand(); + $ft = rand(); + $tt = rand(); + return; +} +sub crlf { + my ($s) = @_; + $s =~ s/\r\n/\n/gs; + return $s; +} +sub sdp_split { + my ($s) = @_; + return split(/--------*\n/, $s); +} +sub offer_answer { + my ($cmd, $name, $req, $sdps) = @_; + my ($sdp_in, $exp_sdp_out) = sdp_split($sdps); + $req->{command} = $cmd; + $req->{'call-id'} = $cid; + $req->{'from-tag'} = $ft; + $req->{sdp} = $sdp_in; + my $resp = $c->req($req); + is $resp->{result}, 'ok', "$name - $cmd status"; + my $regexp = "^\Q$exp_sdp_out\E\$"; + $regexp =~ s/PORT/(\\d{1,5})/gs; + $regexp =~ s/ICEBASE/([0-9a-zA-Z]{16})/gs; + $regexp =~ s/ICEUFRAG/([0-9a-zA-Z]{8})/gs; + $regexp =~ s/ICEPWD/([0-9a-zA-Z]{26})/gs; + like crlf($resp->{sdp}), qr/$regexp/s, "$name - output $cmd SDP"; + return; +} +sub offer { + return offer_answer('offer', @_); +} +sub answer { + my ($name, $req, $sdps) = @_; + $req->{'to-tag'} = $tt; + return offer_answer('answer', $name, $req, $sdps); +} + +$r = $c->req({command => 'ping'}); +ok $r->{result} eq 'pong', 'ping works, daemon operational'; + +# SDP in/out tests, various ICE options + +new_call; + +offer('plain SDP, no ICE', { ICE => 'remove' }, < 'remove' }, < 'remove' }, < 'remove' }, < 'force' }, < 'force' }, < 'force' }, < 'force' }, <new(); +my ($a, $b) = $r->client_pair( + {sockdomain => &Socket::AF_INET}, + {sockdomain => &Socket::AF_INET6} +); + +$r->timer_once(3, sub { + $b->answer($a, ICE => 'remove', label => "callee"); + $a->start_rtp(); + $a->start_rtcp(); + }); +$r->timer_once(10, sub { $r->stop(); }); + +$a->offer($b, ICE => 'remove', label => "caller", 'address-family' => 'IP6'); +$b->start_rtp(); +$b->start_rtcp(); + +$r->run(); + +$a->teardown(dump => 1); diff --git a/t/test-basic.pl b/t/test-basic-ipv4.pl similarity index 100% rename from t/test-basic.pl rename to t/test-basic-ipv4.pl diff --git a/t/test-basic-ipv6-4.pl b/t/test-basic-ipv6-4.pl new file mode 100755 index 000000000..43c9ab3c8 --- /dev/null +++ b/t/test-basic-ipv6-4.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use NGCP::Rtpengine::Test; +use IO::Socket; + +my $r = NGCP::Rtpengine::Test->new(); +my ($a, $b) = $r->client_pair( + {sockdomain => &Socket::AF_INET6}, + {sockdomain => &Socket::AF_INET} +); + +$r->timer_once(3, sub { + $b->answer($a, ICE => 'remove', label => "callee"); + $a->start_rtp(); + $a->start_rtcp(); + }); +$r->timer_once(10, sub { $r->stop(); }); + +$a->offer($b, ICE => 'remove', label => "caller", 'address-family' => 'IP4'); +$b->start_rtp(); +$b->start_rtcp(); + +$r->run(); + +$a->teardown(dump => 1); diff --git a/t/test-basic-ipv6.pl b/t/test-basic-ipv6.pl new file mode 100755 index 000000000..e57239a16 --- /dev/null +++ b/t/test-basic-ipv6.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use NGCP::Rtpengine::Test; +use IO::Socket; + +my $r = NGCP::Rtpengine::Test->new(); +my ($a, $b) = $r->client_pair( + {sockdomain => &Socket::AF_INET6}, + {sockdomain => &Socket::AF_INET6} +); + +$r->timer_once(3, sub { + $b->answer($a, ICE => 'remove', label => "callee"); + $a->start_rtp(); + $a->start_rtcp(); + }); +$r->timer_once(10, sub { $r->stop(); }); + +$a->offer($b, ICE => 'remove', label => "caller"); +$b->start_rtp(); +$b->start_rtcp(); + +$r->run(); + +$a->teardown(dump => 1); diff --git a/t/tests-preload.c b/t/tests-preload.c new file mode 100644 index 000000000..ffa4db5f0 --- /dev/null +++ b/t/tests-preload.c @@ -0,0 +1,615 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + int used_domain, + wanted_domain, + type, + used_protocol, + wanted_protocol; + char unix_path[256]; + struct sockaddr_storage sockname; + int open:1, + bound:1; +} socket_t; + +typedef struct { + struct sockaddr_un path; + struct sockaddr_storage address; +} peer_t; + +#define MAX_SOCKETS 1024 + +static socket_t real_sockets[MAX_SOCKETS]; +static unsigned int anon_sock_inc; + +static peer_t remote_peers[MAX_SOCKETS]; +static unsigned int anon_peer_inc; +static pthread_mutex_t remote_peers_lock = PTHREAD_MUTEX_INITIALIZER; + +static void do_init(void) __attribute__((constructor)); +static void do_exit(void) __attribute__((destructor)); + +static void do_init(void) { + setenv("RTPE_PRELOAD_TEST_ACTIVE", "1", 1); +} +static void do_exit(void) { + for (int i = 0; i < MAX_SOCKETS; i++) { + socket_t *s = &real_sockets[i]; + if (!s->open) + continue; + if (s->used_domain != AF_UNIX) + continue; + if (s->wanted_domain == AF_UNIX) + continue; + unlink(s->unix_path); + } +} + +static const char *path_prefix(void) { + char *ret = getenv("TEST_SOCKET_PATH"); + if (ret) + return ret; + return "/tmp"; +} + +int socket(int domain, int type, int protocol) { + int use_domain = domain; + int use_protocol = protocol; + + if (domain == AF_INET || domain == AF_INET6) { + use_domain = AF_UNIX; + use_protocol = 0; + } + + int (*real_socket)(int, int, int) = dlsym(RTLD_NEXT, "socket"); + int fd = real_socket(use_domain, type, use_protocol); + if (fd < 0 || fd >= MAX_SOCKETS) { + fprintf(stderr, "preload socket(): fd out of bounds (fd %i)\n", fd); + return fd; + } + real_sockets[fd] = (socket_t) { + .used_domain = use_domain, + .wanted_domain = domain, + .type = type, + .used_protocol = use_protocol, + .wanted_protocol = protocol, + .open = 1, + }; + + return fd; +} + +static const char *addr_translate(struct sockaddr_un *sun, const struct sockaddr *addr, + socklen_t addrlen, + int allow_anon) +{ + const char *err; + char sockname[64]; + const char *any_name; + unsigned int port; + + switch (addr->sa_family) { + case AF_INET:; + struct sockaddr_in *sin = (void *) addr; + err = "addrlen too short"; + if (addrlen < sizeof(*sin)) + goto err; + err = "failed to print network address"; + if (!inet_ntop(addr->sa_family, &sin->sin_addr, sockname, sizeof(sockname))) + goto err; + any_name = "0.0.0.0"; + port = ntohs(sin->sin_port); + break; + case AF_INET6:; + struct sockaddr_in6 *sin6 = (void *) addr; + err = "addrlen too short"; + if (addrlen < sizeof(*sin6)) + goto err; + err = "failed to print network address"; + if (!inet_ntop(addr->sa_family, &sin6->sin6_addr, sockname, sizeof(sockname))) + goto err; + any_name = "::"; + port = ntohs(sin6->sin6_port); + break; + default: + goto skip; + } + + int do_specific = 1; + + if (allow_anon) { + err = "Unix socket path truncated"; + if (snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/[%s]:%u", path_prefix(), any_name, port) + >= sizeof(sun->sun_path)) + goto err; + + struct stat sb; + int ret = stat(sun->sun_path, &sb); + if (ret == 0 && sb.st_mode & S_IFSOCK) + do_specific = 0; + } + + if (do_specific) { + err = "Unix socket path truncated"; + if (snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/[%s]:%u", path_prefix(), sockname, port) + >= sizeof(sun->sun_path)) + goto err; + } + + sun->sun_family = AF_UNIX; + return NULL; +skip: + return ""; // special return value +err: + return err; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { + const char *err; + int (*real_bind)(int, const struct sockaddr *, socklen_t) = dlsym(RTLD_NEXT, "bind"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_bind_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_bind_warn; + + assert(s->used_domain == AF_UNIX); + assert(s->wanted_domain == addr->sa_family); + + struct sockaddr_un sun; + err = addr_translate(&sun, addr, addrlen, 0); + if (err) { + if (!err[0]) + goto do_bind; + goto do_bind_warn; + } + + struct sockaddr_storage sst = {0,}; + if (addrlen > sizeof(sst)) + goto do_bind_warn; + memcpy(&sst, addr, addrlen); + + addr = (void *) &sun; + addrlen = sizeof(sun); + + if (s->unix_path[0]) + unlink(s->unix_path); + + assert(sizeof(s->unix_path) >= strlen(sun.sun_path)); + strcpy(s->unix_path, sun.sun_path); + s->sockname = sst; + s->bound = 1; + + goto do_bind; + +do_bind_warn: + fprintf(stderr, "preload bind(): %s (fd %i)\n", err, fd); +do_bind: + return real_bind(fd, addr, addrlen); +} + +static void anon_addr(int domain, struct sockaddr_storage *sst, unsigned int id, unsigned int id2) { + memset(sst, 0, sizeof(*sst)); + switch (domain) { + case AF_INET:; + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons(id); + sin.sin_addr.s_addr = id2; + memcpy(sst, &sin, sizeof(sin)); + break; + case AF_INET6:; + struct sockaddr_in6 sin6; + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(id); + memset(&sin6.sin6_addr, -1, sizeof(sin6.sin6_addr)); + sin6.sin6_addr.s6_addr16[4] = id2; + memcpy(sst, &sin6, sizeof(sin6)); + break; + } +} + +static void check_bind(int fd) { + // to make inspecting the peer address on the receiving end possible, we must bind + // to some unix path name + + if (fd < 0 || fd >= MAX_SOCKETS) + return; + socket_t *s = &real_sockets[fd]; + if (!s->open) + return; + if (s->bound) + return; + if (s->wanted_domain == AF_UNIX || s->used_domain != AF_UNIX) + return; + + struct sockaddr_storage sst; + unsigned int auto_inc = __sync_fetch_and_add(&anon_sock_inc, 1); + anon_addr(s->wanted_domain, &sst, auto_inc, getpid()); + + struct sockaddr_un sun; + sun.sun_family = AF_UNIX; + if (snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/ANON.%u.%u", path_prefix(), getpid(), + auto_inc) + >= sizeof(sun.sun_path)) + fprintf(stderr, "preload socket(): failed to print anon (fd %i)\n", fd); + + assert(sizeof(real_sockets[fd].unix_path) >= strlen(sun.sun_path)); + strcpy(real_sockets[fd].unix_path, sun.sun_path); + + int (*real_bind)(int, const struct sockaddr *, socklen_t) = dlsym(RTLD_NEXT, "bind"); + if (real_bind(fd, (struct sockaddr *) &sun, sizeof(sun))) + fprintf(stderr, "preload socket(): failed to bind to anon (fd %i): %s\n", + fd, strerror(errno)); + + s->bound = 1; + +} + +int close(int fd) { + const char *err; + int (*real_close)(int) = dlsym(RTLD_NEXT, "close"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_close_warn; + socket_t *s = &real_sockets[fd]; + if (!s->open) + goto do_close; + + s->open = 0; + if (s->used_domain == AF_UNIX && s->wanted_domain != AF_UNIX && s->unix_path[0]) + unlink(s->unix_path); + goto do_close; + +do_close_warn: + fprintf(stderr, "preload close(): %s (fd %i)\n", err, fd); +do_close: + return real_close(fd); +} + +int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { + check_bind(fd); + + const char *err; + int (*real_getsockname)(int) = dlsym(RTLD_NEXT, "getsockname"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_getsockname_warn; + socket_t *s = &real_sockets[fd]; + if (!s->open) + goto do_getsockname; + if (s->used_domain != AF_UNIX || s->wanted_domain == AF_UNIX || !s->bound) + goto do_getsockname; + + switch (s->wanted_domain) { + case AF_INET: + if (*addrlen < sizeof(struct sockaddr_in)) + memcpy(addr, &s->sockname, *addrlen); + else + memcpy(addr, &s->sockname, sizeof(struct sockaddr_in)); + *addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + if (*addrlen < sizeof(struct sockaddr_in6)) + memcpy(addr, &s->sockname, *addrlen); + else + memcpy(addr, &s->sockname, sizeof(struct sockaddr_in6)); + *addrlen = sizeof(struct sockaddr_in6); + break; + default: + goto do_getsockname; + } + + return 0; + +do_getsockname_warn: + fprintf(stderr, "preload getsockname(): %s (fd %i)\n", err, fd); +do_getsockname: + return real_getsockname(fd); +} + +int connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { + check_bind(fd); + + const char *err; + int (*real_connect)(int, const struct sockaddr *, socklen_t) = dlsym(RTLD_NEXT, "connect"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_connect_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_connect_warn; + + assert(s->used_domain == AF_UNIX); + assert(s->wanted_domain == addr->sa_family); + + struct sockaddr_un sun; + err = addr_translate(&sun, addr, addrlen, 1); + if (err) { + if (!err[0]) + goto do_connect; + goto do_connect_warn; + } + + addr = (void *) &sun; + addrlen = sizeof(sun); + + goto do_connect; + +do_connect_warn: + fprintf(stderr, "preload connect(): %s (fd %i)\n", err, fd); +do_connect: + return real_connect(fd, addr, addrlen); +} + +int dup(int fd) { + int (*real_dup)(int) = dlsym(RTLD_NEXT, "dup"); + int ret = real_dup(fd); + if (fd < 0 || fd >= MAX_SOCKETS || ret < 0 || ret >= MAX_SOCKETS) { + fprintf(stderr, "preload dup(): fd out of bounds (%i/%i)\n", fd, ret); + return ret; + } + real_sockets[ret] = real_sockets[fd]; + return ret; +} + +int dup2(int oldfd, int newfd) { + int (*real_dup2)(int, int) = dlsym(RTLD_NEXT, "dup2"); + int ret = real_dup2(oldfd, newfd); + if (ret != newfd || oldfd < 0 || oldfd >= MAX_SOCKETS || newfd < 0 || newfd >= MAX_SOCKETS) { + fprintf(stderr, "preload dup(): fd out of bounds (%i/%i/%i)\n", oldfd, newfd, ret); + return ret; + } + if (real_sockets[newfd].open) { + if (real_sockets[newfd].used_domain == AF_UNIX && real_sockets[newfd].unix_path[0]) + unlink(real_sockets[newfd].unix_path); + } + real_sockets[newfd] = real_sockets[oldfd]; + return ret; +} + +ssize_t recvmsg(int fd, struct msghdr *msg, int flags) { + const char *err; + ssize_t (*real_recvmsg)(int, struct msghdr *, int) = dlsym(RTLD_NEXT, "recvmsg"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_recvmsg_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_recvmsg_warn; + if (s->used_domain != AF_UNIX || s->wanted_domain == AF_UNIX) + goto do_recvmsg; + + struct sockaddr_un sun; + struct sockaddr *sa_orig = NULL; + socklen_t sa_len; + if (msg->msg_name) { + sa_orig = msg->msg_name; + sa_len = msg->msg_namelen; + msg->msg_name = &sun; + msg->msg_namelen = sizeof(sun); + } + + ssize_t ret = real_recvmsg(fd, msg, flags); + + if (ret <= 0) + goto out; + + if (sa_orig && msg->msg_name) { + assert(sun.sun_family == AF_UNIX); + char *path = sun.sun_path; + assert(strlen(path) > 0); + const char *pref = path_prefix(); + err = "received from unknown peer"; + if (strncmp(path, pref, strlen(pref))) + goto out_warn; + path += strlen(pref); + if (path[0] != '/') + goto out_warn; + path++; + if (!strncmp(path, "ANON.", 5)) { + pthread_mutex_lock(&remote_peers_lock); + peer_t *p = NULL; + for (unsigned int i = 0; i < anon_peer_inc; i++) { + p = &remote_peers[i]; + if (!strcmp(p->path.sun_path, path)) + goto got_peer; + } + assert(anon_peer_inc < MAX_SOCKETS); + // generate new fake remote response address + p = &remote_peers[anon_peer_inc++]; + p->path = sun; + anon_addr(s->wanted_domain, &p->address, anon_peer_inc, getpid()); +got_peer: + pthread_mutex_unlock(&remote_peers_lock); + assert(sizeof(p->address) >= sa_len); + memcpy(sa_orig, &p->address, sa_len); + } + else if (path[0] == '[') { + path++; + char *end = strchr(path, ']'); + assert(end != NULL); + char addr[64]; + if (snprintf(addr, sizeof(addr), "%.*s", (int) (end - path), path) >= sizeof(addr)) + abort(); + end++; + assert(*end == ':'); + end++; + int port = atoi(end); + assert(port != 0); + + struct sockaddr_in sin = {0,}; + struct sockaddr_in6 sin6 = {0,}; + socklen_t addrlen; + struct sockaddr *sa = NULL; + + if (inet_pton(AF_INET, addr, &sin.sin_addr)) { + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sa = (struct sockaddr *) &sin; + addrlen = sizeof(sin); + } + else if (inet_pton(AF_INET6, addr, &sin6.sin6_addr)) { + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + sa = (struct sockaddr *) &sin6; + addrlen = sizeof(sin6); + } + else + abort(); + + assert(addrlen >= sa_len); + memcpy(sa_orig, sa, sa_len); + } + else + abort(); + + msg->msg_name = sa_orig; + msg->msg_namelen = sa_len; + } + + goto out; + +out_warn: + fprintf(stderr, "preload recvmsg(): %s (fd %i)\n", err, fd); +out: + return ret; + +do_recvmsg_warn: + fprintf(stderr, "preload recvmsg(): %s (fd %i)\n", err, fd); +do_recvmsg: + return real_recvmsg(fd, msg, flags); +} + +ssize_t send(int fd, const void *buf, size_t len, int flags) { + check_bind(fd); + ssize_t (*real_send)(int, const void *, size_t, int) = dlsym(RTLD_NEXT, "send"); + return real_send(fd, buf, len, flags); +} + +static const struct sockaddr *addr_find(const struct sockaddr *addr, socklen_t *addrlen) { + pthread_mutex_lock(&remote_peers_lock); + for (unsigned int i = 0; i < anon_peer_inc; i++) { + peer_t *p = &remote_peers[i]; + if (p->address.ss_family != addr->sa_family) + continue; + switch (p->address.ss_family) { + case AF_INET:{ + struct sockaddr_in *a = (struct sockaddr_in *) addr, + *b = (struct sockaddr_in *) &p->address; + if (a->sin_port != b->sin_port) + continue; + if (a->sin_addr.s_addr != b->sin_addr.s_addr) + continue; + break; + } + + case AF_INET6:{ + struct sockaddr_in6 *a = (struct sockaddr_in6 *) addr, + *b = (struct sockaddr_in6 *) &p->address; + if (a->sin6_port != b->sin6_port) + continue; + if (memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(a->sin6_addr))) + continue; + break; + } + + default: + continue; + } + + // match + *addrlen = sizeof(p->path); + pthread_mutex_unlock(&remote_peers_lock); + return (struct sockaddr *) &p->path; + } + pthread_mutex_unlock(&remote_peers_lock); + return NULL; +} + +static const struct sockaddr *addr_send_translate(const struct sockaddr *addr, socklen_t *addrlen) { + const struct sockaddr *ret = addr_find(addr, addrlen); + if (ret) + return ret; + + static __thread struct sockaddr_un sun; + const char *err = addr_translate(&sun, addr, *addrlen, 0); + if (!err) { + *addrlen = sizeof(sun); + return (void *) &sun; + } + + if (err[0]) + fprintf(stderr, "preload addr_send_translate(): %s\n", err); + + return addr; +} + +ssize_t sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen) { + check_bind(fd); + ssize_t (*real_sendto)(int, const void *, size_t, int, const struct sockaddr *, socklen_t) + = dlsym(RTLD_NEXT, "sendto"); + addr = addr_send_translate(addr, &addrlen); + return real_sendto(fd, buf, len, flags, addr, addrlen); +} + +ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { + check_bind(fd); + ssize_t (*real_sendmsg)(int, const struct msghdr *, int) = dlsym(RTLD_NEXT, "sendmsg"); + struct msghdr msg2 = *msg; + if (msg2.msg_name) + msg2.msg_name = (void *) addr_send_translate(msg2.msg_name, &msg2.msg_namelen); + return real_sendmsg(fd, &msg2, flags); +} + +int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { + const char *err; + int (*real_setsockopt)(int, int, int, const void *, socklen_t) = dlsym(RTLD_NEXT, "setsockopt"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_set_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_set_warn; + + assert(s->used_domain == AF_UNIX); + + switch (s->wanted_domain) { + case AF_INET: + if (level == SOL_IP && optname == IP_TOS) + return 0; + break; + + case AF_INET6: + if (level == SOL_IPV6 && optname == IPV6_V6ONLY) + return 0; + if (level == SOL_IPV6 && optname == IPV6_TCLASS) + return 0; + break; + } + + goto do_set; + +do_set_warn: + fprintf(stderr, "preload setsockopt(): %s (fd %i)\n", err, fd); +do_set: + return real_setsockopt(fd, level, optname, optval, optlen); +}