summaryrefslogtreecommitdiff
path: root/tests/t_socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/t_socket.c')
-rw-r--r--tests/t_socket.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/tests/t_socket.c b/tests/t_socket.c
new file mode 100644
index 0000000..bbc9e5e
--- /dev/null
+++ b/tests/t_socket.c
@@ -0,0 +1,307 @@
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 int is_nonblocking(int s) {
62 int flags = fcntl(s, F_GETFL, 0);
63 return flags != -1 && (flags & O_NONBLOCK) != 0;
64}
65
66static int sock_flag(int s, int level, int option) {
67 int value = -1;
68 socklen_t len = sizeof(value);
69
70 if (getsockopt(s, level, option, &value, &len) == -1)
71 return -1;
72 return value != 0;
73}
74
75/* Wait for one event, so a test never blocks forever on a lost packet */
76static int wait_for(int s, short events) {
77 struct pollfd pfd;
78
79 pfd.fd = s;
80 pfd.events = events;
81 pfd.revents = 0;
82 return poll(&pfd, 1, 2000) == 1;
83}
84
85/* ------------------------------------------------------------ invariants */
86
87/* Asked for the wildcard address, because a socket bound to one specific
88 IPv6 address cannot carry IPv4 traffic anyway and Linux turns IPV6_V6ONLY
89 back on in that case. The tracker's own default is the wildcard. */
90static void case_socket_options(void) {
91 int udp, tcp;
92
93 test_case("a bound udp socket is non blocking and serves both families");
94 udp = socket_udp_bind(ip_any, 0);
95 CHECK(udp != -1);
96 CHECK_INT(is_nonblocking(udp), 1);
97 CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0);
98 close(udp);
99
100 test_case("a listening tcp socket is non blocking and serves both families");
101 tcp = socket_tcp_listen(ip_any, 0);
102 CHECK(tcp != -1);
103 CHECK_INT(is_nonblocking(tcp), 1);
104 CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0);
105 close(tcp);
106}
107
108#ifdef SO_REUSEPORT
109static void case_reuseport_is_opt_in(void) {
110 int s;
111
112 test_case("reuseport stays off unless the operator asks for it");
113 g_socket_reuseport = 0;
114 s = socket_udp_bind(ip_loopback, 0);
115 CHECK(s != -1);
116 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0);
117 close(s);
118
119 test_case("reuseport reaches the socket when it is asked for");
120 g_socket_reuseport = 1;
121 s = socket_udp_bind(ip_loopback, 0);
122 CHECK(s != -1);
123 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1);
124 close(s);
125
126 g_socket_reuseport = 0;
127}
128#endif
129
130/* ------------------------------------------------------------------- udp */
131
132static void case_udp_roundtrip(void) {
133 char buf[32], ip[16], own_ip[16];
134 uint16_t port = 0, own_port = 0;
135 int s;
136
137 test_case("a datagram comes back with the sender's address and port");
138 s = socket_udp_bind(ip_loopback, 0);
139 CHECK(s != -1);
140 bound_address(s, own_ip, &own_port);
141 CHECK(own_port != 0);
142 CHECK_INT(socket_send(s, "opentracker", 11, own_ip, own_port), 11);
143 CHECK_INT(wait_for(s, POLLIN), 1);
144 CHECK_INT(socket_recv(s, buf, sizeof(buf), ip, &port), 11);
145 CHECK_MEM(buf, "opentracker", 11);
146 CHECK_MEM(ip, own_ip, 16);
147 CHECK_INT(port, own_port);
148 close(s);
149}
150
151/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however
152 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here
153 and the BSDs fill a plain sockaddr_in, so this is the case that tells the
154 two apart. */
155static void case_udp_v4mapped(void) {
156 char buf[32], ip[16], receiver_ip[16], sender_ip[16];
157 uint16_t port = 0, receiver_port = 0, sender_port = 0;
158 int receiver, sender;
159
160 test_case("an IPv4 sender is reported in its v4 mapped form");
161 receiver = socket_udp_bind(ip_v4_loopback, 0);
162 CHECK(receiver != -1);
163 sender = socket_udp_bind(ip_v4_loopback, 0);
164 CHECK(sender != -1);
165 bound_address(receiver, receiver_ip, &receiver_port);
166 bound_address(sender, sender_ip, &sender_port);
167 CHECK_INT(ip_is_v4mapped(sender_ip), 1);
168
169 CHECK_INT(socket_send(sender, "v4", 2, receiver_ip, receiver_port), 2);
170 CHECK_INT(wait_for(receiver, POLLIN), 1);
171 CHECK_INT(socket_recv(receiver, buf, sizeof(buf), ip, &port), 2);
172 CHECK_INT(ip_is_v4mapped(ip), 1);
173 CHECK_MEM(ip, sender_ip, 16);
174 CHECK_INT(port, sender_port);
175 close(sender);
176 close(receiver);
177}
178
179/* ------------------------------------------------------------------- tcp */
180
181static void case_tcp_roundtrip(void) {
182 char ip[16], listener_ip[16], client_ip[16];
183 uint16_t port = 0, listener_port = 0, client_port = 0;
184 int listener, client, accepted;
185
186 test_case("a connection is accepted and carries the caller's address");
187 listener = socket_tcp_listen(ip_loopback, 0);
188 CHECK(listener != -1);
189 bound_address(listener, listener_ip, &listener_port);
190 CHECK(listener_port != 0);
191
192 client = socket_tcp_connect(ip_loopback, 0, listener_ip, listener_port);
193 CHECK(client != -1);
194 CHECK_INT(wait_for(client, POLLOUT), 1);
195 CHECK_INT(socket_is_connected(client), 1);
196 bound_address(client, client_ip, &client_port);
197
198 CHECK_INT(wait_for(listener, POLLIN), 1);
199 accepted = socket_accept(listener, ip, &port);
200 CHECK(accepted != -1);
201 CHECK_MEM(ip, client_ip, 16);
202 CHECK_INT(port, client_port);
203
204 test_case("the accepted socket is handed over non blocking");
205 CHECK_INT(is_nonblocking(accepted), 1);
206
207 close(accepted);
208 close(client);
209 close(listener);
210}
211
212static void case_connect_refused(void) {
213 char listener_ip[16];
214 int listener, client;
215 uint16_t port;
216
217 test_case("a refused connection is reported by socket_is_connected");
218 /* Take a port, then drop it, so nothing is listening on it any more */
219 listener = socket_tcp_listen(ip_loopback, 0);
220 CHECK(listener != -1);
221 bound_address(listener, listener_ip, &port);
222 close(listener);
223
224 client = socket_tcp_connect(ip_loopback, 0, listener_ip, port);
225 CHECK(client != -1);
226 CHECK_INT(wait_for(client, POLLOUT), 1);
227 CHECK_INT(socket_is_connected(client), 0);
228 CHECK_INT(errno, ECONNREFUSED);
229 close(client);
230}
231
232/* --------------------------------------------------------- failed binds */
233
234/* The lowest free descriptor is handed out first, so a number that stays put
235 across a failing call shows the call did not keep a socket behind. */
236static int next_descriptor(void) {
237 int fd = dup(0);
238
239 if (fd != -1)
240 close(fd);
241 return fd;
242}
243
244static void case_bind_failure(void) {
245 int before;
246
247 test_case("binding an address no interface carries fails");
248 before = next_descriptor();
249 CHECK(before != -1);
250 CHECK_INT(socket_tcp_listen(ip_unassigned, 0), -1);
251 CHECK_INT(socket_udp_bind(ip_unassigned, 0), -1);
252
253 test_case("a failed bind leaves no socket behind");
254 CHECK_INT(next_descriptor(), before);
255}
256
257#ifdef WANT_SYNC_LIVE
258/* Reading an IPv4 multicast option back: Linux answers with an int, the BSDs
259 with a single byte, so ask how much was written. */
260static int mcast_option(int s, int option) {
261 int value = 0;
262 socklen_t len = sizeof(value);
263
264 if (getsockopt(s, IPPROTO_IP, option, &value, &len) == -1)
265 return -1;
266 if (len == 1)
267 return *(unsigned char *)&value;
268 return value;
269}
270
271static void case_mcast_sender(void) {
272 int s;
273
274 test_case("the sending socket keeps its traffic on the local network");
275 s = socket_mcast_bind(ip_v4_loopback, 0, 0);
276 CHECK(s != -1);
277 CHECK_INT(mcast_option(s, IP_MULTICAST_TTL), 1);
278
279 test_case("and does not hear itself unless it is asked to");
280 CHECK_INT(mcast_option(s, IP_MULTICAST_LOOP), 0);
281
282 test_case("whoever sends from it is serving clients too, so it cannot block");
283 CHECK_INT(is_nonblocking(s), 1);
284 close(s);
285
286 test_case("the proxy asks to hear itself and gets it");
287 s = socket_mcast_bind(ip_v4_loopback, 0, 1);
288 CHECK(s != -1);
289 CHECK_INT(mcast_option(s, IP_MULTICAST_LOOP), 1);
290 close(s);
291}
292#endif
293
294void test_socket(void) {
295 case_socket_options();
296#ifdef SO_REUSEPORT
297 case_reuseport_is_opt_in();
298#endif
299 case_udp_roundtrip();
300 case_udp_v4mapped();
301 case_tcp_roundtrip();
302 case_connect_refused();
303 case_bind_failure();
304#ifdef WANT_SYNC_LIVE
305 case_mcast_sender();
306#endif
307}