From 4856e6537fb32776e12236478426ffc58f2fbf22 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 22 Apr 2025 11:07:46 -0400 Subject: [PATCH] MT#62181 add testing framework Only some very basic tests included for now Change-Id: I84c3c9dc73c23e4b1aa486fd0fa10ea80bea9bb6 --- Makefile | 12 + t/auto-test-helper | 18 + t/basic.conf | 13 + t/basic/sbc.conf | 2 + t/basic/xmlrpc2di.conf | 6 + t/register-cache.conf | 15 + t/register-cache/registrar.sbcprofile.conf | 6 + t/register-cache/sbc.conf | 3 + t/sems_tester.py | 113 +++ t/test-basic.py | 70 ++ t/test-register-cache.py | 222 ++++++ t/tests-preload.c | 859 +++++++++++++++++++++ 12 files changed, 1339 insertions(+) create mode 100755 t/auto-test-helper create mode 100644 t/basic.conf create mode 100644 t/basic/sbc.conf create mode 100644 t/basic/xmlrpc2di.conf create mode 100644 t/register-cache.conf create mode 100644 t/register-cache/registrar.sbcprofile.conf create mode 100644 t/register-cache/sbc.conf create mode 100644 t/sems_tester.py create mode 100755 t/test-basic.py create mode 100755 t/test-register-cache.py create mode 100644 t/tests-preload.c diff --git a/Makefile b/Makefile index 2445f05d..aeb2e58d 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,8 @@ clean: $(MAKE) -C $$r clean ; \ fi ; \ done + rm -f t/*.so + rm -rf t/run .PHONY: modules modules: @@ -119,3 +121,13 @@ doc: .PHONY: fulldoc fulldoc: $(MAKE) -C doc/ fulldoc + +.PHONY: check +check: + $(MAKE) t/tests-preload.so + rm -rf t/run + mkdir t/run + for X in t/test-*.py; do t/auto-test-helper $$(basename "$$X") "core/$(NAME)" "$$X"; done + +t/tests-preload.so: t/tests-preload.c + $(CC) -g -pthread -D_GNU_SOURCE -std=c11 -o $@ -shared -fPIC $< -ldl -lm diff --git a/t/auto-test-helper b/t/auto-test-helper new file mode 100755 index 00000000..e1beb2ea --- /dev/null +++ b/t/auto-test-helper @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e +set -x + +NAME=$1 +shift +BINARY=$1 +shift + +export BINARY + +rm -rf fake-"$NAME"-sockets +mkdir fake-"$NAME"-sockets +LD_PRELOAD=t/tests-preload.so TEST_SOCKET_PATH=./fake-"$NAME"-sockets \ + G_DEBUG=fatal-warnings "$@" +test "$(ls fake-"$NAME"-sockets)" = "" +rmdir fake-"$NAME"-sockets diff --git a/t/basic.conf b/t/basic.conf new file mode 100644 index 00000000..f428bb98 --- /dev/null +++ b/t/basic.conf @@ -0,0 +1,13 @@ +fork=no +stderr=yes +loglevel=3 + +sip_ip=127.0.0.1 +media_ip=127.0.0.1 +sip_port=5080 +rtp_low_port=10000 +rtp_high_port=20000 + +plugin_path=core/lib +plugin_config_path=t/basic/ +load_plugins=wav;gsm;opus;xmlrpc2di;sbc diff --git a/t/basic/sbc.conf b/t/basic/sbc.conf new file mode 100644 index 00000000..9de48086 --- /dev/null +++ b/t/basic/sbc.conf @@ -0,0 +1,2 @@ +profiles=ngcp,ngcp_cf,ngcp_push +active_profile=$(paramhdr),ngcp diff --git a/t/basic/xmlrpc2di.conf b/t/basic/xmlrpc2di.conf new file mode 100644 index 00000000..d7e0099b --- /dev/null +++ b/t/basic/xmlrpc2di.conf @@ -0,0 +1,6 @@ +xmlrpc_port=8090 +server_ip=127.0.0.1 +export_di=yes +debug_server_params=yes +debug_server_result=yes +debug_log_level=3 diff --git a/t/register-cache.conf b/t/register-cache.conf new file mode 100644 index 00000000..2db9b83e --- /dev/null +++ b/t/register-cache.conf @@ -0,0 +1,15 @@ +fork=no +stderr=yes +loglevel=3 + +sip_ip=127.0.0.1 +media_ip=127.0.0.1 +sip_port=5060 +rtp_low_port=10000 +rtp_high_port=20000 + +plugin_path=core/lib +plugin_config_path=t/register-cache/ +load_plugins=wav;gsm;opus;sbc + +application=sbc diff --git a/t/register-cache/registrar.sbcprofile.conf b/t/register-cache/registrar.sbcprofile.conf new file mode 100644 index 00000000..4da70dea --- /dev/null +++ b/t/register-cache/registrar.sbcprofile.conf @@ -0,0 +1,6 @@ +enable_auth=no +force_outbound_proxy=no +call_control=registrar +registrar_module=cc_registrar +registrar_sticky_iface=yes +registrar_nat_handling=yes diff --git a/t/register-cache/sbc.conf b/t/register-cache/sbc.conf new file mode 100644 index 00000000..d8ca488a --- /dev/null +++ b/t/register-cache/sbc.conf @@ -0,0 +1,3 @@ +load_cc_plugins=cc_registrar +profiles=registrar +active_profile=registrar diff --git a/t/sems_tester.py b/t/sems_tester.py new file mode 100644 index 00000000..9c9b2d26 --- /dev/null +++ b/t/sems_tester.py @@ -0,0 +1,113 @@ +import os +import time +import re +import socket +import subprocess +import tempfile +import typing +import unittest + + +class TestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + tmpdir = tempfile.TemporaryDirectory() + tmpsock = tmpdir.name + "/notify.sock" + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + sock.bind(tmpsock) + + env = {"NOTIFY_SOCKET": tmpsock} + if os.environ.get("LD_PRELOAD"): + env["LD_PRELOAD"] = os.environ.get("LD_PRELOAD") + if os.environ.get("TEST_SOCKET_PATH"): + env["TEST_SOCKET_PATH"] = os.environ.get("TEST_SOCKET_PATH") + + binary = os.environ.get('BINARY', 'core/sems') + + cls._proc = subprocess.Popen( + [binary, "-f", "t/" + cls._config_base + ".conf", "-P", "t/run/" + str(os.getpid()) + ".pid"], + env=env, + ) + + cls._sd_msg = sock.recv(1000) + + sock.close() + os.unlink(tmpsock) + tmpdir.cleanup() + + @classmethod + def tearDownClass(cls): + cls._proc.terminate() + cls._proc.wait() + + def testSdNotify(self): + self.assertEqual(self._sd_msg, b"READY=1") + + def testPreload(self): + self.assertEqual(os.environ.get("RTPE_PRELOAD_TEST_ACTIVE"), "1") + + @classmethod + def makeSIPSocket(cls): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(3) + sock.connect(("127.0.0.1", cls._sip_port)) + return sock + + @classmethod + def makeXMLRPCSocket(cls): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + sock.connect(("127.0.0.1", cls._xmlrpc_port)) + return sock + + def sendSIP(self, msg: bytes, sock: socket = None): + s = sock + if not s: + s = self.makeSIPSocket() + msg = msg.replace(b"\n", b"\r\n") + s.send(msg) + if not sock: + s.close() + + def sendRecvSIP(self, msg: bytes, exp: bytes, sock: socket = None): + s = sock + if not s: + s = self.makeSIPSocket() + r = re.compile(exp.replace(b"\n", b"[\r\n]{1,2}"), re.DOTALL) + self.sendSIP(msg, s) + m = s.recv(1000) + self.assertRegex(m, r) + if not sock: + s.close() + + def sendRecvXMLRPC(self, req: bytes, exp: bytes, sock: socket = None): + s = sock + if not s: + s = self.makeXMLRPCSocket() + l = len(req) + req = ( + b"POST / HTTP/1.1\r\nConnection: close\r\nHost: 127.0.0.1:" + + bytes(self._xmlrpc_port) + + b"\r\nUser-Agent: Tester\r\nContent-Type: text/xml\r\nContent-Length: " + + bytes(str(l), "latin1") + + b"\r\n\r\n" + + req + ) + head_r = re.compile( + b"^HTTP/1.1 200 OK[\r\n]{1,2}Server: XMLRPC\\+\\+ 0.8[\r\n]{1,2}Content-Type: text/xml[\r\n]{1,2}Content-length: \\d+[\r\n]{2,4}", + re.DOTALL, + ) + r = re.compile(exp.replace(b"\n", b"[\r\n]{1,2}"), re.DOTALL) + s.send(req) + m = s.recv(1000) + matches = head_r.match(m) + self.assertTrue(matches) + m = m.removeprefix(matches[0]) + self.assertRegex(m, r) + if not sock: + s.close() + + +def main(): + unittest.main() diff --git a/t/test-basic.py b/t/test-basic.py new file mode 100755 index 00000000..58250f65 --- /dev/null +++ b/t/test-basic.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +import sems_tester +import socket + + +class TestBasic(sems_tester.TestCase): + _config_base = "basic" + _sip_port = 5080 + _xmlrpc_port = 8090 + + def testOptions(self): + self.sendRecvSIP( + b"""OPTIONS sip:monitoring@voip.sipwise.local SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63104 OPTIONS +Contact: +Accept: application/sdp +Content-Length: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2\\.0/UDP 127\\.0\\.0\\.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=1;received=[\\d.]+ +To: ;tag=[A-Z0-9-]+ +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63104 OPTIONS +Content-Length: 0 + +$""", + ) + + def testXmlRpc(self): + self.sendRecvXMLRPC( + b""" + + postDSMEvent + + + sw_audio + + + + + cmd + clearSets + + + sound_sets + 1 + + + + + """, + b"""^<\\?xml version="1.0"\\?> + + faultCode-1faultStringpostDSMEvent: unknown method name + +$""", + ) + + +if __name__ == "__main__": + sems_tester.main() diff --git a/t/test-register-cache.py b/t/test-register-cache.py new file mode 100755 index 00000000..9b779fbf --- /dev/null +++ b/t/test-register-cache.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 + +import sems_tester +import socket +import time + + +class TestBasic(sems_tester.TestCase): + _config_base = "register-cache" + _sip_port = 5060 + + def testRegister(self): + # query + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63102 REGISTER +Content-Length: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=1;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63102 REGISTER +Content-Length: 0 + +$""", + ) + + # register + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63104 REGISTER +Contact: +Content-Length: 0 +Expires: 5 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=2;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63104 REGISTER +Contact: ;expires=[45] +Content-Length: 0 + +$""", + ) + + # wait for expire + time.sleep(7) + + # query + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63105 REGISTER +Content-Length: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=3;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63105 REGISTER +Content-Length: 0 + +$""", + ) + + # wait for garbage collect + time.sleep(10) + + # query again + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63106 REGISTER +Content-Length: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=4;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63106 REGISTER +Content-Length: 0 + +$""", + ) + + # update + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58egsdfbb +CSeq: 63107 REGISTER +Contact: +Content-Length: 0 +Expires: 5 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=5;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58egsdfbb +CSeq: 63107 REGISTER +Contact: ;expires=[45] +Content-Length: 0 + +$""", + ) + + # query + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63108 REGISTER +Content-Length: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=6;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63108 REGISTER +Contact: sip:127.0.0.1:57715;transport=udp;expires=[45] +Content-Length: 0 + +$""", + ) + + # unregister + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48cdfbb +CSeq: 63109 REGISTER +Contact: * +Content-Length: 0 +Expires: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=7;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48cdfbb +CSeq: 63109 REGISTER +Content-Length: 0 + +$""", + ) + + # query + self.sendRecvSIP( + b"""REGISTER sip:127.0.0.1 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport +Max-Forwards: 6 +To: +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63110 REGISTER +Content-Length: 0 +User-Agent: tester/5.33.0 + +""", + b"""^SIP/2.0 200 OK +Via: SIP/2.0/UDP 127.0.0.1:57715;branch=z9hG4bKhedcec8e2a445fd80;rport=8;received=.*? +To: ;tag=.*? +From: monit ;tag=8667fc7c8b14e846 +Call-ID: 1eb48ca58e6dbdbb +CSeq: 63110 REGISTER +Content-Length: 0 + +$""", + ) + + +if __name__ == "__main__": + sems_tester.main() diff --git a/t/tests-preload.c b/t/tests-preload.c new file mode 100644 index 00000000..101a5625 --- /dev/null +++ b/t/tests-preload.c @@ -0,0 +1,859 @@ +#include +#include +#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, + peername; + unsigned int open:1, + bound:1, + connected:1, + pktinfo:1; +} socket_t; + +typedef struct { + struct sockaddr_un path; + struct sockaddr_storage address; + socklen_t addrlen; +} peer_t; + +#define MAX_SOCKETS 4096 + +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 socklen_t anon_addr(int domain, struct sockaddr_storage *sst, unsigned int id, unsigned int id2); + +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 & 0xff, + .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 type, + int allow_anon) +{ + const char *err; + char sockname[64]; + const char *any_name; + unsigned int port; + const char *prefix = "unk"; + + switch (type) { + case SOCK_STREAM: + prefix = "tcp"; + break; + case SOCK_DGRAM: + prefix = "udp"; + break; + } + + 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:[%s]:%u", path_prefix(), + 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:[%s]:%u", path_prefix(), + prefix, sockname, port) + >= sizeof(sun->sun_path)) + goto err; + } + + sun->sun_family = AF_UNIX; + return NULL; +skip: + return ""; // special return value +err: + return err; +} + +void addr_translate_reverse(struct sockaddr_storage *sst, socklen_t *socklen, int wanted_domain, + const struct sockaddr_un *sun) +{ + assert(sun->sun_family == AF_UNIX); + const char *path = sun->sun_path; + assert(strlen(path) > 0); + const char *pref = path_prefix(); + if (strncmp(path, pref, strlen(pref))) { + fprintf(stderr, "preload addr_translate_reverse(): received from unknown peer '%s'\n", path); + return; + } + path += strlen(pref); + if (path[0] != '/') { + fprintf(stderr, "preload addr_translate_reverse(): received from unknown peer '%s'\n", path); + return; + } + path++; + + if (path[0] == '\0' || path[1] == '\0' || path[2] == '\0' || path[3] != ':') { + fprintf(stderr, "preload addr_translate_reverse(): missing prefix '%s'\n", path); + return; + } + path += 4; + + struct sockaddr_in sin = {0,}; + struct sockaddr_in6 sin6 = {0,}; + socklen_t addrlen; + struct sockaddr *sa = NULL; + + 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; + p->addrlen = anon_addr(wanted_domain, &p->address, anon_peer_inc, getpid()); +got_peer: + pthread_mutex_unlock(&remote_peers_lock); + addrlen = p->addrlen; + sa = (struct sockaddr *) &p->address; + } + 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); + + 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(); + } + else + abort(); + + assert(addrlen <= sizeof(*sst)); + memset(sst, 0, sizeof(*sst)); + memcpy(sst, sa, addrlen); + *socklen = addrlen; +} + +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; + + if (s->used_domain != AF_UNIX) + goto do_bind; + assert(s->wanted_domain == addr->sa_family); + + struct sockaddr_un sun; + err = addr_translate(&sun, addr, addrlen, s->type, 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 socklen_t anon_addr(int domain, struct sockaddr_storage *sst, unsigned int id, unsigned int id2) { + memset(sst, 0, sizeof(*sst)); + socklen_t ret = -1; + 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)); + ret = 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)); + ret = sizeof(sin6); + break; + } + return ret; +} + +static void check_bind(int fd) { + const char *prefix = "unk"; + // 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; + + switch (s->type) { + case SOCK_STREAM: + prefix = "tcp"; + break; + case SOCK_DGRAM: + prefix = "udp"; + break; + } + + 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/%s:ANON.%u.%u", path_prefix(), 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; + s->connected = 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, struct sockaddr *, socklen_t *) = 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, addr, addrlen); +} + +int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { + check_bind(fd); + + const char *err; + int (*real_getpeername)(int, struct sockaddr *, socklen_t *) = dlsym(RTLD_NEXT, "getpeername"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_getpeername_warn; + socket_t *s = &real_sockets[fd]; + if (!s->open) + goto do_getpeername; + if (s->used_domain != AF_UNIX || s->wanted_domain == AF_UNIX || !s->bound) + goto do_getpeername; + if (!s->connected) + goto do_getpeername; + + switch (s->wanted_domain) { + case AF_INET: + if (*addrlen < sizeof(struct sockaddr_in)) + memcpy(addr, &s->peername, *addrlen); + else + memcpy(addr, &s->peername, sizeof(struct sockaddr_in)); + *addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + if (*addrlen < sizeof(struct sockaddr_in6)) + memcpy(addr, &s->peername, *addrlen); + else + memcpy(addr, &s->peername, sizeof(struct sockaddr_in6)); + *addrlen = sizeof(struct sockaddr_in6); + break; + default: + goto do_getpeername; + } + + return 0; + +do_getpeername_warn: + fprintf(stderr, "preload getpeername(): %s (fd %i)\n", err, fd); +do_getpeername: + return real_getpeername(fd, addr, addrlen); +} + +int connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { + check_bind(fd); + + socket_t *s = NULL; + 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; + 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, s->type, 1); + if (err) { + if (!err[0]) + goto do_connect; + goto do_connect_warn; + } + + struct sockaddr_storage sst = {0,}; + if (addrlen > sizeof(sst)) + goto do_connect_warn; + memcpy(&sst, addr, addrlen); + s->peername = sst; + + addr = (void *) &sun; + addrlen = sizeof(sun); + + goto do_connect; + +do_connect_warn: + fprintf(stderr, "preload connect(): %s (fd %i)\n", err, fd); +do_connect:; + int ret = real_connect(fd, addr, addrlen); + if (ret == 0 && s) + s->connected = 1; + return ret; +} + +int accept4(int fd, struct sockaddr *addr, socklen_t *addrlen, int flags) { + const char *err; + int (*real_accept4)(int, struct sockaddr *, socklen_t *, int) = dlsym(RTLD_NEXT, "accept4"); + + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_accept_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_accept_warn; + + assert(s->used_domain == AF_UNIX); + + goto do_accept; + +do_accept_warn: + fprintf(stderr, "preload accept(): %s (fd %i)\n", err, fd); +do_accept:; + struct sockaddr_un sun; + socklen_t sun_len = sizeof(sun); + int new_fd = real_accept4(fd, (struct sockaddr *) &sun, &sun_len, flags); + if (new_fd == -1) + return -1; + if (new_fd < 0 || new_fd >= MAX_SOCKETS || real_sockets[new_fd].open) { + fprintf(stderr, "preload accept(): new_fd out of bounds (%i/%i)\n", fd, new_fd); + return -1; + } + + assert(sun.sun_family == AF_UNIX); + socket_t *new_s = &real_sockets[new_fd]; + *new_s = *s; + assert(sun_len < sizeof(new_s->sockname)); + assert(sizeof(new_s->unix_path) >= strlen(sun.sun_path)); + strcpy(new_s->unix_path, sun.sun_path); + memset(&new_s->sockname, 0, sizeof(new_s->sockname)); + new_s->open = 1; + new_s->connected = 1; + + struct sockaddr_storage sst; + socklen_t socklen; + addr_translate_reverse(&sst, &socklen, new_s->wanted_domain, &sun); + assert(socklen <= *addrlen); + memset(addr, 0, *addrlen); + memcpy(addr, &sst, socklen); + *addrlen = socklen; + assert(s->wanted_domain == addr->sa_family); + new_s->peername = sst; + + return new_fd; +} + +int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { + return accept4(fd, addr, addrlen, 0); +} + +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 recvfrom(int fd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t *socklen) { + const char *err; + ssize_t (*real_recvfrom)(int, void *, size_t, int, struct sockaddr *, socklen_t *) + = dlsym(RTLD_NEXT, "recvfrom"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_recvfrom_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_recvfrom_warn; + if (s->used_domain != AF_UNIX || s->wanted_domain == AF_UNIX) + goto do_recvfrom; + + struct sockaddr_un sun; + socklen_t sl = sizeof(sun); + ssize_t ret = real_recvfrom(fd, buf, len, flags, (struct sockaddr *) &sun, &sl); + + if (ret <= 0) + goto out; + + if (addr) { + struct sockaddr_storage sst; + socklen_t addrlen; + addr_translate_reverse(&sst, &addrlen, s->wanted_domain, &sun); + assert(addrlen <= *socklen); + memcpy(addr, &sst, addrlen); + *socklen = addrlen; + } + + goto out; + +out: + return ret; + +do_recvfrom_warn: + fprintf(stderr, "preload recvfrom(): %s (fd %i)\n", err, fd); +do_recvfrom: + return real_recvfrom(fd, buf, len, flags, addr, socklen); +} + +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) { + struct sockaddr_storage sst; + socklen_t addrlen; + addr_translate_reverse(&sst, &addrlen, s->wanted_domain, &sun); + assert(addrlen <= sa_len); + memcpy(sa_orig, &sst, addrlen); + + msg->msg_name = sa_orig; + msg->msg_namelen = sa_len; + } + + goto out; + +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, int type, 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, type, 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) { + const char *err; + check_bind(fd); + ssize_t (*real_sendto)(int, const void *, size_t, int, const struct sockaddr *, socklen_t) + = dlsym(RTLD_NEXT, "sendto"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_send_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_send_warn; + addr = addr_send_translate(addr, s->type, &addrlen); + goto do_send; +do_send_warn: + fprintf(stderr, "preload sendto(): %s (fd %i)\n", err, fd); +do_send: + return real_sendto(fd, buf, len, flags, addr, addrlen); +} + +ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { + const char *err; + check_bind(fd); + ssize_t (*real_sendmsg)(int, const struct msghdr *, int) = dlsym(RTLD_NEXT, "sendmsg"); + err = "fd out of bounds"; + if (fd < 0 || fd >= MAX_SOCKETS) + goto do_send_warn; + socket_t *s = &real_sockets[fd]; + err = "fd not open"; + if (!s->open) + goto do_send_warn; + struct msghdr msg2 = *msg; + if (msg2.msg_name) + msg2.msg_name = (void *) addr_send_translate(msg2.msg_name, s->type, &msg2.msg_namelen); + goto do_send; +do_send_warn: + fprintf(stderr, "preload sendmsg(): %s (fd %i)\n", err, fd); +do_send: + 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; + if (level == IPPROTO_TCP && optname == TCP_NODELAY) + return 0; + if (level == SOL_IP && optname == IP_PKTINFO) { + s->pktinfo = 1; + return 0; + } + break; + + case AF_INET6: + if (level == SOL_IPV6 && optname == IPV6_V6ONLY) + return 0; + if (level == SOL_IPV6 && optname == IPV6_TCLASS) + return 0; + if (level == IPPROTO_TCP && optname == TCP_NODELAY) + return 0; + if (level == SOL_IPV6 && optname == IPV6_RECVPKTINFO) { + s->pktinfo = 1; + 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); +}