summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile29
-rw-r--r--ot_socket.c256
-rw-r--r--ot_socket.h45
-rw-r--r--tests/runner.c1
-rw-r--r--tests/t_socket.c315
-rw-r--r--tests/test.h1
6 files changed, 637 insertions, 10 deletions
diff --git a/Makefile b/Makefile
index 27d635d..2587b62 100644
--- a/Makefile
+++ b/Makefile
@@ -68,22 +68,25 @@ CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread
68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread 68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread
69 69
70BINARY =opentracker 70BINARY =opentracker
71HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_scan.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 71HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.h ot_hex.h ot_ip.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
72SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.c ot_hex.c ot_ip.c ot_scan.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 72SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.c ot_hex.c ot_ip.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
73SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c 73SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
74 74
75OBJECTS = $(SOURCES:%.c=%.o) 75OBJECTS = $(SOURCES:%.c=%.o)
76OBJECTS_debug = $(SOURCES:%.c=%.debug.o) 76OBJECTS_debug = $(SOURCES:%.c=%.debug.o)
77OBJECTS_proxy = $(SOURCES_proxy:%.c=%.o) 77OBJECTS_proxy = $(SOURCES_proxy:%.c=%.proxy.o)
78OBJECTS_proxy_debug = $(SOURCES_proxy:%.c=%.debug.o) 78OBJECTS_proxy_debug = $(SOURCES_proxy:%.c=%.proxy.debug.o)
79 79
80.SUFFIXES: .debug.o .o .c 80.SUFFIXES: .proxy.debug.o .proxy.o .debug.o .o .c
81 81
82all: $(BINARY) $(BINARY).debug 82all: $(BINARY) $(BINARY).debug
83 83
84CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES) 84CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES)
85CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES) 85CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES)
86 86
87CFLAGS_proxy = $(CFLAGS_production) -DWANT_SYNC_LIVE
88CFLAGS_proxy_debug = $(CFLAGS_debug) -DWANT_SYNC_LIVE
89
87$(BINARY): $(OBJECTS) $(HEADERS) 90$(BINARY): $(OBJECTS) $(HEADERS)
88 $(CC) -o $@ $(OBJECTS) $(LDFLAGS) 91 $(CC) -o $@ $(OBJECTS) $(LDFLAGS)
89 $(STRIP) $@ 92 $(STRIP) $@
@@ -94,6 +97,12 @@ proxy: $(OBJECTS_proxy) $(HEADERS)
94proxy.debug: $(OBJECTS_proxy_debug) $(HEADERS) 97proxy.debug: $(OBJECTS_proxy_debug) $(HEADERS)
95 $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS) 98 $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS)
96 99
100.c.proxy.debug.o : $(HEADERS)
101 $(CC) -c -o $@ $(CFLAGS_proxy_debug) $(<:.proxy.debug.o=.c)
102
103.c.proxy.o : $(HEADERS)
104 $(CC) -c -o $@ $(CFLAGS_proxy) $(<:.proxy.o=.c)
105
97.c.debug.o : $(HEADERS) 106.c.debug.o : $(HEADERS)
98 $(CC) -c -o $@ $(CFLAGS_debug) $(<:.debug.o=.c) 107 $(CC) -c -o $@ $(CFLAGS_debug) $(<:.debug.o=.c)
99 108
@@ -105,12 +114,12 @@ proxy.debug: $(OBJECTS_proxy_debug) $(HEADERS)
105# has to. tests/stubs.c supplies the few symbols those modules reference but 114# has to. tests/stubs.c supplies the few symbols those modules reference but
106# the tests never reach. 115# the tests never reach.
107TESTBIN=tests/runner 116TESTBIN=tests/runner
108TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c 117TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c
109TEST_UNITS=scan_urlencoded_query.c ot_buf.c ot_ip.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c 118TEST_UNITS=scan_urlencoded_query.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c
110TEST_HEADERS=tests/test.h $(HEADERS) 119TEST_HEADERS=tests/test.h $(HEADERS)
111# Tests reach code that sits behind feature flags, so the units under test are 120# Tests reach code that sits behind feature flags, so the units under test are
112# built with those features enabled. A test binary is not a production binary. 121# built with those features enabled. A test binary is not a production binary.
113FEATURES_test=$(FEATURES) -DWANT_ACCESSLIST_BLACK 122FEATURES_test=$(FEATURES) -DWANT_ACCESSLIST_BLACK -DWANT_SYNC_LIVE
114CFLAGS_test = $(CFLAGS) -I. -g -O1 $(FEATURES_test) 123CFLAGS_test = $(CFLAGS) -I. -g -O1 $(FEATURES_test)
115 124
116$(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) 125$(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
@@ -122,7 +131,7 @@ check: $(TESTBIN)
122.PHONY: all check clean install 131.PHONY: all check clean install
123 132
124clean: 133clean:
125 rm -rf opentracker opentracker.debug $(TESTBIN) *.o *~ 134 rm -rf opentracker opentracker.debug proxy proxy.debug $(TESTBIN) *.o *~
126 135
127install: 136install:
128 install -m 755 opentracker $(DESTDIR)$(BINDIR) 137 install -m 755 opentracker $(DESTDIR)$(BINDIR)
diff --git a/ot_socket.c b/ot_socket.c
new file mode 100644
index 0000000..e26ad52
--- /dev/null
+++ b/ot_socket.c
@@ -0,0 +1,256 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <fcntl.h>
9#include <netinet/in.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14/* Opentracker */
15#include "ot_ip.h"
16#include "ot_socket.h"
17
18int g_socket_reuseport;
19
20static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) {
21 memset(sa, 0, sizeof(*sa));
22 sa->sin6_family = AF_INET6;
23 sa->sin6_port = htons(port);
24 memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6));
25}
26
27/* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped
28 sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so
29 both forms have to be read. */
30static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) {
31 if (ss->ss_family == AF_INET) {
32 const struct sockaddr_in *sa = (const struct sockaddr_in *)ss;
33
34 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
35 memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4);
36 *port = ntohs(sa->sin_port);
37 } else {
38 const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss;
39
40 memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6));
41 *port = ntohs(sa->sin6_port);
42 }
43}
44
45static int set_nonblocking(int s) {
46 int flags = fcntl(s, F_GETFL, 0);
47
48 if (flags == -1)
49 return -1;
50 return fcntl(s, F_SETFL, flags | O_NONBLOCK);
51}
52
53/* Close without letting the failure we are about to report be overwritten */
54static int close_and_fail(int s) {
55 int saved = errno;
56 close(s);
57 errno = saved;
58 return -1;
59}
60
61static int socket_v6(int type) {
62 int s = socket(PF_INET6, type, 0);
63 int zero = 0;
64
65 if (s == -1)
66 return -1;
67
68 /* A tracker that answered only one family would be half a tracker */
69 if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1)
70 return close_and_fail(s);
71
72 if (set_nonblocking(s) == -1)
73 return close_and_fail(s);
74
75 return s;
76}
77
78static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port) {
79 struct sockaddr_in6 sa;
80 int one = 1;
81
82 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
83#ifdef SO_REUSEPORT
84 if (g_socket_reuseport)
85 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
86#endif
87
88 sockaddr_from_ip(&sa, ip, port);
89 return bind(s, (struct sockaddr *)&sa, sizeof(sa));
90}
91
92int socket_tcp_listen(const ot_ip6 ip, uint16_t port) {
93 int s = socket_v6(SOCK_STREAM);
94
95 if (s == -1)
96 return -1;
97 if (socket_bind_v6(s, ip, port) == -1 || listen(s, SOMAXCONN) == -1)
98 return close_and_fail(s);
99
100 return s;
101}
102
103int socket_udp_bind(const ot_ip6 ip, uint16_t port) {
104 int s = socket_v6(SOCK_DGRAM);
105
106 if (s == -1)
107 return -1;
108 if (socket_bind_v6(s, ip, port) == -1)
109 return close_and_fail(s);
110
111 return s;
112}
113
114int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) {
115 struct sockaddr_in6 sa;
116 int s = socket_v6(SOCK_STREAM);
117
118 if (s == -1)
119 return -1;
120 if (socket_bind_v6(s, srcip, srcport) == -1)
121 return close_and_fail(s);
122
123 sockaddr_from_ip(&sa, dstip, dstport);
124 if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK)
125 return close_and_fail(s);
126
127 return s;
128}
129
130int socket_is_connected(int s) {
131 int error = 0;
132 socklen_t len = sizeof(error);
133
134 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
135 return 0;
136 if (error) {
137 errno = error;
138 return 0;
139 }
140 return 1;
141}
142
143int socket_accept(int s, ot_ip6 ip, uint16_t *port) {
144 struct sockaddr_storage ss;
145 socklen_t len = sizeof(ss);
146 int fd;
147
148 memset(&ss, 0, sizeof(ss));
149 if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1)
150 return -1;
151
152 /* Linux hands over a blocking socket whatever the listening socket was, the
153 BSDs and macOS inherit the listener's flags, so say which one we want. */
154 if (set_nonblocking(fd) == -1)
155 return close_and_fail(fd);
156
157 ip_from_sockaddr(ip, port, &ss);
158 return fd;
159}
160
161ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) {
162 struct sockaddr_storage ss;
163 socklen_t sslen = sizeof(ss);
164 ssize_t received;
165
166 memset(&ss, 0, sizeof(ss));
167 received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen);
168 if (received == -1)
169 return -1;
170
171 ip_from_sockaddr(ip, port, &ss);
172 return received;
173}
174
175ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) {
176 struct sockaddr_in6 sa;
177
178 sockaddr_from_ip(&sa, ip, port);
179 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
180}
181
182#ifdef WANT_SYNC_LIVE
183static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) {
184 memset(sa, 0, sizeof(*sa));
185 sa->sin_family = AF_INET;
186 sa->sin_port = htons(port);
187 memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4);
188}
189
190static int socket_v4_bind(const ot_ip6 ip, uint16_t port) {
191 struct sockaddr_in sa;
192 int s, one = 1;
193
194 if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
195 return -1;
196
197 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
198#ifdef SO_REUSEPORT
199 if (g_socket_reuseport)
200 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
201#endif
202
203 sockaddr4_from_ip(&sa, ip, port);
204 if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
205 return close_and_fail(s);
206
207 return s;
208}
209
210int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) {
211 struct ip_mreq mreq;
212 ot_ip6 any;
213 int s;
214
215 /* The group is what is listened to, so the socket takes the wildcard and
216 the join names the interface to receive it on */
217 memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
218 memset(any + sizeof(ip_v4mapped_prefix), 0, 4);
219
220 if ((s = socket_v4_bind(any, port)) == -1)
221 return -1;
222
223 memset(&mreq, 0, sizeof(mreq));
224 memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4);
225 memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4);
226 if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
227 return close_and_fail(s);
228
229 return s;
230}
231
232int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) {
233 /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux
234 accepts either that or an int. */
235 unsigned char ttl = 1, loop = (unsigned char)(loopback != 0);
236 int s = socket_v4_bind(interface, port);
237
238 if (s == -1)
239 return -1;
240 if (set_nonblocking(s) == -1)
241 return close_and_fail(s);
242
243 if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 ||
244 setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
245 return close_and_fail(s);
246
247 return s;
248}
249
250ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) {
251 struct sockaddr_in sa;
252
253 sockaddr4_from_ip(&sa, group, port);
254 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
255}
256#endif
diff --git a/ot_socket.h b/ot_socket.h
new file mode 100644
index 0000000..20fd839
--- /dev/null
+++ b/ot_socket.h
@@ -0,0 +1,45 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_SOCKET_H__
7#define OT_SOCKET_H__
8
9#include <stdint.h>
10#include <sys/types.h>
11
12#include "trackerlogic.h"
13
14/* Every socket opentracker opens is an AF_INET6 socket with IPV6_V6ONLY
15 cleared, so one descriptor serves both families and an IPv4 peer arrives
16 as the v4 mapped address */
17
18/* Ask the kernel to let several processes bind the same address */
19extern int g_socket_reuseport;
20
21/* Create, bind and, for TCP, listen. The returned socket is non blocking
22 Returns -1 on failure with errno set */
23int socket_tcp_listen(const ot_ip6 ip, uint16_t port);
24int socket_udp_bind(const ot_ip6 ip, uint16_t port);
25
26int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport);
27int socket_is_connected(int s);
28int socket_accept(int s, ot_ip6 ip, uint16_t *port);
29
30ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port);
31ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port);
32
33#ifdef WANT_SYNC_LIVE
34/*
35 socket_mcast_join takes the wildcard address and names the interface in the
36 join, and hands back a blocking socket, since the thread that reads it does
37 nothing else. socket_mcast_bind hands back a non blocking one, because the
38 thread that sends from it is also serving clients. Incoming datagrams are
39 read with socket_recv */
40int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port);
41int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback);
42ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port);
43#endif
44
45#endif
diff --git a/tests/runner.c b/tests/runner.c
index 10b6625..db62246 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -22,6 +22,7 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
22 {"ot_scan", test_scan}, 22 {"ot_scan", test_scan},
23 {"ot_buf", test_buf}, 23 {"ot_buf", test_buf},
24 {"ot_ip", test_ip}, 24 {"ot_ip", test_ip},
25 {"ot_socket", test_socket},
25 {"ot_accesslist", test_accesslist}, 26 {"ot_accesslist", test_accesslist},
26 {NULL, NULL}}; 27 {NULL, NULL}};
27 28
diff --git a/tests/t_socket.c b/tests/t_socket.c
new file mode 100644
index 0000000..cf7d291
--- /dev/null
+++ b/tests/t_socket.c
@@ -0,0 +1,315 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <fcntl.h>
9#include <netinet/in.h>
10#include <poll.h>
11#include <string.h>
12#include <sys/socket.h>
13#include <unistd.h>
14
15/* Opentracker */
16#include "ot_ip.h"
17#include "ot_socket.h"
18#include "test.h"
19
20/* These cases talk to the loopback interface rather than to a mock, because
21 what is worth pinning down here is the behaviour of the socket options the
22 rest of the tracker relies on, and only the kernel can report that. */
23
24static const char ip_any[16] = {0};
25static const char ip_loopback[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
26static const char ip_v4_loopback[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1};
27/* RFC 3849 reserves 2001:db8::/32 for documentation, so no host carries it */
28static const char ip_unassigned[16] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
29
30/* What a socket ended up bound to, which is not always what was asked for: a
31 FreeBSD jail, for one, rewrites a loopback bind to the address it was given.
32 A datagram a socket sends to itself must come back carrying this, so the
33 cases below compare against it rather than against the address they passed
34 to the bind. */
35static void bound_address(int s, char ip[16], uint16_t *port) {
36 struct sockaddr_storage ss;
37 socklen_t len = sizeof(ss);
38
39 memset(&ss, 0, sizeof(ss));
40 memset(ip, 0, 16);
41 *port = 0;
42 if (getsockname(s, (struct sockaddr *)&ss, &len) == -1)
43 return;
44
45 /* Spelled out here rather than borrowed from ot_socket.c, so that a wrong
46 idea about how a kernel reports an address cannot hide in both at once */
47 if (ss.ss_family == AF_INET) {
48 const struct sockaddr_in *sa = (const struct sockaddr_in *)&ss;
49
50 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
51 memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4);
52 *port = ntohs(sa->sin_port);
53 } else {
54 const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)&ss;
55
56 memcpy(ip, &sa->sin6_addr, 16);
57 *port = ntohs(sa->sin6_port);
58 }
59}
60
61static uint16_t bound_port(int s) {
62 char ip[16];
63 uint16_t port;
64
65 bound_address(s, ip, &port);
66 return port;
67}
68
69static int is_nonblocking(int s) {
70 int flags = fcntl(s, F_GETFL, 0);
71 return flags != -1 && (flags & O_NONBLOCK) != 0;
72}
73
74static int sock_flag(int s, int level, int option) {
75 int value = -1;
76 socklen_t len = sizeof(value);
77
78 if (getsockopt(s, level, option, &value, &len) == -1)
79 return -1;
80 return value != 0;
81}
82
83/* Wait for one event, so a test never blocks forever on a lost packet */
84static int wait_for(int s, short events) {
85 struct pollfd pfd;
86
87 pfd.fd = s;
88 pfd.events = events;
89 pfd.revents = 0;
90 return poll(&pfd, 1, 2000) == 1;
91}
92
93/* ------------------------------------------------------------ invariants */
94
95/* Asked for the wildcard address, because a socket bound to one specific
96 IPv6 address cannot carry IPv4 traffic anyway and Linux turns IPV6_V6ONLY
97 back on in that case. The tracker's own default is the wildcard. */
98static void case_socket_options(void) {
99 int udp, tcp;
100
101 test_case("a bound udp socket is non blocking and serves both families");
102 udp = socket_udp_bind(ip_any, 0);
103 CHECK(udp != -1);
104 CHECK_INT(is_nonblocking(udp), 1);
105 CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0);
106 close(udp);
107
108 test_case("a listening tcp socket is non blocking and serves both families");
109 tcp = socket_tcp_listen(ip_any, 0);
110 CHECK(tcp != -1);
111 CHECK_INT(is_nonblocking(tcp), 1);
112 CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0);
113 close(tcp);
114}
115
116#ifdef SO_REUSEPORT
117static void case_reuseport_is_opt_in(void) {
118 int s;
119
120 test_case("reuseport stays off unless the operator asks for it");
121 g_socket_reuseport = 0;
122 s = socket_udp_bind(ip_loopback, 0);
123 CHECK(s != -1);
124 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0);
125 close(s);
126
127 test_case("reuseport reaches the socket when it is asked for");
128 g_socket_reuseport = 1;
129 s = socket_udp_bind(ip_loopback, 0);
130 CHECK(s != -1);
131 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1);
132 close(s);
133
134 g_socket_reuseport = 0;
135}
136#endif
137
138/* ------------------------------------------------------------------- udp */
139
140static void case_udp_roundtrip(void) {
141 char buf[32], ip[16], own_ip[16];
142 uint16_t port = 0, own_port = 0;
143 int s;
144
145 test_case("a datagram comes back with the sender's address and port");
146 s = socket_udp_bind(ip_loopback, 0);
147 CHECK(s != -1);
148 bound_address(s, own_ip, &own_port);
149 CHECK(own_port != 0);
150 CHECK_INT(socket_send(s, "opentracker", 11, own_ip, own_port), 11);
151 CHECK_INT(wait_for(s, POLLIN), 1);
152 CHECK_INT(socket_recv(s, buf, sizeof(buf), ip, &port), 11);
153 CHECK_MEM(buf, "opentracker", 11);
154 CHECK_MEM(ip, own_ip, 16);
155 CHECK_INT(port, own_port);
156 close(s);
157}
158
159/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however
160 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here
161 and the BSDs fill a plain sockaddr_in, so this is the case that tells the
162 two apart. */
163static void case_udp_v4mapped(void) {
164 char buf[32], ip[16], receiver_ip[16], sender_ip[16];
165 uint16_t port = 0, receiver_port = 0, sender_port = 0;
166 int receiver, sender;
167
168 test_case("an IPv4 sender is reported in its v4 mapped form");
169 receiver = socket_udp_bind(ip_v4_loopback, 0);
170 CHECK(receiver != -1);
171 sender = socket_udp_bind(ip_v4_loopback, 0);
172 CHECK(sender != -1);
173 bound_address(receiver, receiver_ip, &receiver_port);
174 bound_address(sender, sender_ip, &sender_port);
175 CHECK_INT(ip_is_v4mapped(sender_ip), 1);
176
177 CHECK_INT(socket_send(sender, "v4", 2, receiver_ip, receiver_port), 2);
178 CHECK_INT(wait_for(receiver, POLLIN), 1);
179 CHECK_INT(socket_recv(receiver, buf, sizeof(buf), ip, &port), 2);
180 CHECK_INT(ip_is_v4mapped(ip), 1);
181 CHECK_MEM(ip, sender_ip, 16);
182 CHECK_INT(port, sender_port);
183 close(sender);
184 close(receiver);
185}
186
187/* ------------------------------------------------------------------- tcp */
188
189static void case_tcp_roundtrip(void) {
190 char ip[16], listener_ip[16], client_ip[16];
191 uint16_t port = 0, listener_port = 0, client_port = 0;
192 int listener, client, accepted;
193
194 test_case("a connection is accepted and carries the caller's address");
195 listener = socket_tcp_listen(ip_loopback, 0);
196 CHECK(listener != -1);
197 bound_address(listener, listener_ip, &listener_port);
198 CHECK(listener_port != 0);
199
200 client = socket_tcp_connect(ip_loopback, 0, listener_ip, listener_port);
201 CHECK(client != -1);
202 CHECK_INT(wait_for(client, POLLOUT), 1);
203 CHECK_INT(socket_is_connected(client), 1);
204 bound_address(client, client_ip, &client_port);
205
206 CHECK_INT(wait_for(listener, POLLIN), 1);
207 accepted = socket_accept(listener, ip, &port);
208 CHECK(accepted != -1);
209 CHECK_MEM(ip, client_ip, 16);
210 CHECK_INT(port, client_port);
211
212 test_case("the accepted socket is handed over non blocking");
213 CHECK_INT(is_nonblocking(accepted), 1);
214
215 close(accepted);
216 close(client);
217 close(listener);
218}
219
220static void case_connect_refused(void) {
221 char listener_ip[16];
222 int listener, client;
223 uint16_t port;
224
225 test_case("a refused connection is reported by socket_is_connected");
226 /* Take a port, then drop it, so nothing is listening on it any more */
227 listener = socket_tcp_listen(ip_loopback, 0);
228 CHECK(listener != -1);
229 bound_address(listener, listener_ip, &port);
230 close(listener);
231
232 client = socket_tcp_connect(ip_loopback, 0, listener_ip, port);
233 CHECK(client != -1);
234 CHECK_INT(wait_for(client, POLLOUT), 1);
235 CHECK_INT(socket_is_connected(client), 0);
236 CHECK_INT(errno, ECONNREFUSED);
237 close(client);
238}
239
240/* --------------------------------------------------------- failed binds */
241
242/* The lowest free descriptor is handed out first, so a number that stays put
243 across a failing call shows the call did not keep a socket behind. */
244static int next_descriptor(void) {
245 int fd = dup(0);
246
247 if (fd != -1)
248 close(fd);
249 return fd;
250}
251
252static void case_bind_failure(void) {
253 int before;
254
255 test_case("binding an address no interface carries fails");
256 before = next_descriptor();
257 CHECK(before != -1);
258 CHECK_INT(socket_tcp_listen(ip_unassigned, 0), -1);
259 CHECK_INT(socket_udp_bind(ip_unassigned, 0), -1);
260
261 test_case("a failed bind leaves no socket behind");
262 CHECK_INT(next_descriptor(), before);
263}
264
265#ifdef WANT_SYNC_LIVE
266/* Reading an IPv4 multicast option back: Linux answers with an int, the BSDs
267 with a single byte, so ask how much was written. */
268static int mcast_option(int s, int option) {
269 int value = 0;
270 socklen_t len = sizeof(value);
271
272 if (getsockopt(s, IPPROTO_IP, option, &value, &len) == -1)
273 return -1;
274 if (len == 1)
275 return *(unsigned char *)&value;
276 return value;
277}
278
279static void case_mcast_sender(void) {
280 int s;
281
282 test_case("the sending socket keeps its traffic on the local network");
283 s = socket_mcast_bind(ip_v4_loopback, 0, 0);
284 CHECK(s != -1);
285 CHECK_INT(mcast_option(s, IP_MULTICAST_TTL), 1);
286
287 test_case("and does not hear itself unless it is asked to");
288 CHECK_INT(mcast_option(s, IP_MULTICAST_LOOP), 0);
289
290 test_case("whoever sends from it is serving clients too, so it cannot block");
291 CHECK_INT(is_nonblocking(s), 1);
292 close(s);
293
294 test_case("the proxy asks to hear itself and gets it");
295 s = socket_mcast_bind(ip_v4_loopback, 0, 1);
296 CHECK(s != -1);
297 CHECK_INT(mcast_option(s, IP_MULTICAST_LOOP), 1);
298 close(s);
299}
300#endif
301
302void test_socket(void) {
303 case_socket_options();
304#ifdef SO_REUSEPORT
305 case_reuseport_is_opt_in();
306#endif
307 case_udp_roundtrip();
308 case_udp_v4mapped();
309 case_tcp_roundtrip();
310 case_connect_refused();
311 case_bind_failure();
312#ifdef WANT_SYNC_LIVE
313 case_mcast_sender();
314#endif
315}
diff --git a/tests/test.h b/tests/test.h
index 7449b73..d79c4d2 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -85,5 +85,6 @@ void test_accesslist(void);
85void test_scan(void); 85void test_scan(void);
86void test_buf(void); 86void test_buf(void);
87void test_ip(void); 87void test_ip(void);
88void test_socket(void);
88 89
89#endif 90#endif