summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-30 13:23:39 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-30 13:23:39 +0100
commit31d2586e3296d424e51848ada963299b0da89324 (patch)
tree203190931bc0d189653570d2ce526b1eb0695cb9
parentcbf4b60df6c6c8c7208dc29708677d24b2724deb (diff)
Defer accepts until some data actually is there
-rw-r--r--opentracker.c1
-rw-r--r--ot_socket.c20
-rw-r--r--ot_socket.h1
-rw-r--r--tests/t_socket.c43
4 files changed, 63 insertions, 2 deletions
diff --git a/opentracker.c b/opentracker.c
index 27b2570..ec92fe0 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -339,6 +339,7 @@ static void listen_bind_one(ot_listen *entry) {
339 if (entry->proto == FLAG_TCP) { 339 if (entry->proto == FLAG_TCP) {
340 if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1) 340 if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1)
341 panic("socket_tcp_listen"); 341 panic("socket_tcp_listen");
342 socket_defer_accept((int)entry->sock);
342 } else { 343 } else {
343 if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1) 344 if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1)
344 panic("socket_udp_bind"); 345 panic("socket_udp_bind");
diff --git a/ot_socket.c b/ot_socket.c
index 47ed26d..78a16d2 100644
--- a/ot_socket.c
+++ b/ot_socket.c
@@ -8,6 +8,7 @@
8#include <errno.h> 8#include <errno.h>
9#include <fcntl.h> 9#include <fcntl.h>
10#include <netinet/in.h> 10#include <netinet/in.h>
11#include <netinet/tcp.h>
11#include <string.h> 12#include <string.h>
12#include <sys/socket.h> 13#include <sys/socket.h>
13#include <unistd.h> 14#include <unistd.h>
@@ -120,6 +121,25 @@ static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing
120 return bind(s, (struct sockaddr *)&sa, sizeof(sa)); 121 return bind(s, (struct sockaddr *)&sa, sizeof(sa));
121} 122}
122 123
124/* Defer the listening socket reporting it to be ready to accept until enough
125 sensible data is available for an accept+read pair. Where available. */
126int socket_defer_accept(int s) {
127#if defined(TCP_DEFER_ACCEPT)
128 int patience = OT_CLIENT_TIMEOUT;
129
130 return setsockopt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &patience, sizeof(patience));
131#elif defined(SO_ACCEPTFILTER)
132 struct accept_filter_arg filter;
133
134 memset(&filter, 0, sizeof(filter));
135 strcpy(filter.af_name, "httpready");
136 return setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &filter, sizeof(filter));
137#else
138 (void)s;
139 return -1;
140#endif
141}
142
123int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { 143int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
124 int s = socket_v6(SOCK_STREAM); 144 int s = socket_v6(SOCK_STREAM);
125 145
diff --git a/ot_socket.h b/ot_socket.h
index f73a5c9..5bbffe1 100644
--- a/ot_socket.h
+++ b/ot_socket.h
@@ -38,6 +38,7 @@ int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 ds
38int socket_is_connected(int s); 38int socket_is_connected(int s);
39int socket_accept(int s, ot_ip6 ip, uint16_t *port); 39int socket_accept(int s, ot_ip6 ip, uint16_t *port);
40int socket_set_blocking(int s, int blocking); 40int socket_set_blocking(int s, int blocking);
41int socket_defer_accept(int s);
41 42
42ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); 43ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port);
43ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port); 44ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port);
diff --git a/tests/t_socket.c b/tests/t_socket.c
index ab4b785..20a0c0a 100644
--- a/tests/t_socket.c
+++ b/tests/t_socket.c
@@ -78,15 +78,17 @@ static int sock_flag(int s, int level, int option) {
78} 78}
79 79
80/* Wait for one event, so a test never blocks forever on a lost packet */ 80/* Wait for one event, so a test never blocks forever on a lost packet */
81static int wait_for(int s, short events) { 81static int wait_for_ms(int s, short events, int timeout_ms) {
82 struct pollfd pfd; 82 struct pollfd pfd;
83 83
84 pfd.fd = s; 84 pfd.fd = s;
85 pfd.events = events; 85 pfd.events = events;
86 pfd.revents = 0; 86 pfd.revents = 0;
87 return poll(&pfd, 1, 2000) == 1; 87 return poll(&pfd, 1, timeout_ms) == 1;
88} 88}
89 89
90static int wait_for(int s, short events) { return wait_for_ms(s, events, 2000); }
91
90/* ------------------------------------------------------------ invariants */ 92/* ------------------------------------------------------------ invariants */
91 93
92/* Asked for the wildcard address, because a socket bound to one specific 94/* Asked for the wildcard address, because a socket bound to one specific
@@ -200,6 +202,42 @@ static void case_a_signal_ends_a_blocking_read(void) {
200 close(reader_socket); 202 close(reader_socket);
201} 203}
202 204
205/* What a deferred accept is for is a descriptor that already has the request
206 in it, so what is checked is the silence before: a client that has
207 connected but said nothing must not make the listener readable. Kernels
208 that cannot do this say so, and then there is nothing to check. */
209static void case_deferred_accept(void) {
210 struct sockaddr_in6 address;
211 char ip[16];
212 uint16_t port = 0;
213 int listener, client;
214
215 test_case("a connection that has sent nothing is held back from accept");
216 listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE);
217 CHECK(listener != -1);
218 if (socket_defer_accept(listener) == -1) {
219 close(listener);
220 return;
221 }
222 bound_address(listener, ip, &port);
223
224 client = socket(AF_INET6, SOCK_STREAM, 0);
225 CHECK(client != -1);
226 memset(&address, 0, sizeof(address));
227 address.sin6_family = AF_INET6;
228 address.sin6_port = htons(port);
229 memcpy(&address.sin6_addr, ip_loopback, 16);
230 CHECK_INT(connect(client, (struct sockaddr *)&address, sizeof(address)), 0);
231
232 CHECK_INT(wait_for_ms(listener, POLLIN, 300), 0);
233
234 CHECK_INT((int)write(client, "GET / HTTP/1.0\r\n\r\n", 18), 18);
235 CHECK_INT(wait_for(listener, POLLIN), 1);
236
237 close(client);
238 close(listener);
239}
240
203/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however 241/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however
204 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here 242 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here
205 and the BSDs fill a plain sockaddr_in, so this is the case that tells the 243 and the BSDs fill a plain sockaddr_in, so this is the case that tells the
@@ -352,6 +390,7 @@ void test_socket(void) {
352 case_a_signal_ends_a_blocking_read(); 390 case_a_signal_ends_a_blocking_read();
353 case_udp_v4mapped(); 391 case_udp_v4mapped();
354 case_tcp_roundtrip(); 392 case_tcp_roundtrip();
393 case_deferred_accept();
355 case_connect_refused(); 394 case_connect_refused();
356 case_bind_failure(); 395 case_bind_failure();
357#ifdef WANT_SYNC_LIVE 396#ifdef WANT_SYNC_LIVE