diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/runner.c | 1 | ||||
| -rw-r--r-- | tests/t_scan_urlencoded_query.c | 9 | ||||
| -rw-r--r-- | tests/t_socket.c | 307 | ||||
| -rw-r--r-- | tests/test.h | 1 |
4 files changed, 309 insertions, 9 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_scan_urlencoded_query.c b/tests/t_scan_urlencoded_query.c index 6fa34e0..f660ea3 100644 --- a/tests/t_scan_urlencoded_query.c +++ b/tests/t_scan_urlencoded_query.c | |||
| @@ -295,14 +295,6 @@ static void case_fmt_hex_is_lower_case(void) { | |||
| 295 | CHECK_STR(dest, "%00%ab%ff"); | 295 | CHECK_STR(dest, "%00%ab%ff"); |
| 296 | } | 296 | } |
| 297 | 297 | ||
| 298 | static void case_fmt_length_only(void) { | ||
| 299 | const uint8_t src[] = {'a', 0xff, 'b'}; | ||
| 300 | |||
| 301 | test_case("a null destination reports the length without writing"); | ||
| 302 | CHECK_INT(fmt_urlencoded(NULL, src, sizeof(src)), 5); | ||
| 303 | CHECK_INT(fmt_urlencoded(NULL, src, 0), 0); | ||
| 304 | } | ||
| 305 | |||
| 306 | static void case_fmt_zero_length(void) { | 298 | static void case_fmt_zero_length(void) { |
| 307 | char dest[4]; | 299 | char dest[4]; |
| 308 | 300 | ||
| @@ -374,7 +366,6 @@ void test_scan_urlencoded_query(void) { | |||
| 374 | case_fmt_passes_safe_bytes(); | 366 | case_fmt_passes_safe_bytes(); |
| 375 | case_fmt_escapes_the_rest(); | 367 | case_fmt_escapes_the_rest(); |
| 376 | case_fmt_hex_is_lower_case(); | 368 | case_fmt_hex_is_lower_case(); |
| 377 | case_fmt_length_only(); | ||
| 378 | case_fmt_zero_length(); | 369 | case_fmt_zero_length(); |
| 379 | case_fmt_worst_case_fits(); | 370 | case_fmt_worst_case_fits(); |
| 380 | case_fmt_decoder_disagrees_on_dollar(); | 371 | case_fmt_decoder_disagrees_on_dollar(); |
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 | |||
| 24 | static const char ip_any[16] = {0}; | ||
| 25 | static const char ip_loopback[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; | ||
| 26 | static 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 */ | ||
| 28 | static 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. */ | ||
| 35 | static 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 | |||
| 61 | static int is_nonblocking(int s) { | ||
| 62 | int flags = fcntl(s, F_GETFL, 0); | ||
| 63 | return flags != -1 && (flags & O_NONBLOCK) != 0; | ||
| 64 | } | ||
| 65 | |||
| 66 | static 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 */ | ||
| 76 | static 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. */ | ||
| 90 | static 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 | ||
| 109 | static 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 | |||
| 132 | static 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. */ | ||
| 155 | static 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 | |||
| 181 | static 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 | |||
| 212 | static 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. */ | ||
| 236 | static int next_descriptor(void) { | ||
| 237 | int fd = dup(0); | ||
| 238 | |||
| 239 | if (fd != -1) | ||
| 240 | close(fd); | ||
| 241 | return fd; | ||
| 242 | } | ||
| 243 | |||
| 244 | static 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. */ | ||
| 260 | static 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 | |||
| 271 | static 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 | |||
| 294 | void 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 | } | ||
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); | |||
| 85 | void test_scan(void); | 85 | void test_scan(void); |
| 86 | void test_buf(void); | 86 | void test_buf(void); |
| 87 | void test_ip(void); | 87 | void test_ip(void); |
| 88 | void test_socket(void); | ||
| 88 | 89 | ||
| 89 | #endif | 90 | #endif |
