From 31d2586e3296d424e51848ada963299b0da89324 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 30 Oct 2026 13:23:39 +0100 Subject: Defer accepts until some data actually is there --- opentracker.c | 1 + ot_socket.c | 20 ++++++++++++++++++++ ot_socket.h | 1 + tests/t_socket.c | 43 +++++++++++++++++++++++++++++++++++++++++-- 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) { if (entry->proto == FLAG_TCP) { if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1) panic("socket_tcp_listen"); + socket_defer_accept((int)entry->sock); } else { if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1) 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 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,25 @@ static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing return bind(s, (struct sockaddr *)&sa, sizeof(sa)); } +/* Defer the listening socket reporting it to be ready to accept until enough + sensible data is available for an accept+read pair. Where available. */ +int socket_defer_accept(int s) { +#if defined(TCP_DEFER_ACCEPT) + int patience = OT_CLIENT_TIMEOUT; + + return setsockopt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &patience, sizeof(patience)); +#elif defined(SO_ACCEPTFILTER) + struct accept_filter_arg filter; + + memset(&filter, 0, sizeof(filter)); + strcpy(filter.af_name, "httpready"); + return setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &filter, sizeof(filter)); +#else + (void)s; + return -1; +#endif +} + int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { int s = socket_v6(SOCK_STREAM); 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 int socket_is_connected(int s); int socket_accept(int s, ot_ip6 ip, uint16_t *port); int socket_set_blocking(int s, int blocking); +int socket_defer_accept(int s); ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); ssize_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) { } /* Wait for one event, so a test never blocks forever on a lost packet */ -static int wait_for(int s, short events) { +static int wait_for_ms(int s, short events, int timeout_ms) { struct pollfd pfd; pfd.fd = s; pfd.events = events; pfd.revents = 0; - return poll(&pfd, 1, 2000) == 1; + return poll(&pfd, 1, timeout_ms) == 1; } +static int wait_for(int s, short events) { return wait_for_ms(s, events, 2000); } + /* ------------------------------------------------------------ invariants */ /* 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) { close(reader_socket); } +/* What a deferred accept is for is a descriptor that already has the request + in it, so what is checked is the silence before: a client that has + connected but said nothing must not make the listener readable. Kernels + that cannot do this say so, and then there is nothing to check. */ +static void case_deferred_accept(void) { + struct sockaddr_in6 address; + char ip[16]; + uint16_t port = 0; + int listener, client; + + test_case("a connection that has sent nothing is held back from accept"); + listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE); + CHECK(listener != -1); + if (socket_defer_accept(listener) == -1) { + close(listener); + return; + } + bound_address(listener, ip, &port); + + client = socket(AF_INET6, SOCK_STREAM, 0); + CHECK(client != -1); + memset(&address, 0, sizeof(address)); + address.sin6_family = AF_INET6; + address.sin6_port = htons(port); + memcpy(&address.sin6_addr, ip_loopback, 16); + CHECK_INT(connect(client, (struct sockaddr *)&address, sizeof(address)), 0); + + CHECK_INT(wait_for_ms(listener, POLLIN, 300), 0); + + CHECK_INT((int)write(client, "GET / HTTP/1.0\r\n\r\n", 18), 18); + CHECK_INT(wait_for(listener, POLLIN), 1); + + close(client); + close(listener); +} + /* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here and the BSDs fill a plain sockaddr_in, so this is the case that tells the @@ -352,6 +390,7 @@ void test_socket(void) { case_a_signal_ends_a_blocking_read(); case_udp_v4mapped(); case_tcp_roundtrip(); + case_deferred_accept(); case_connect_refused(); case_bind_failure(); #ifdef WANT_SYNC_LIVE -- cgit v1.3