summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/runner.c1
-rw-r--r--tests/t_socket.c315
-rw-r--r--tests/test.h1
3 files changed, 317 insertions, 0 deletions
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