The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
bio_fd_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for the FD bio
18 *
19 * @file src/lib/bio/test/bio_fd_tests.c
20 *
21 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
22 */
23#include <freeradius-devel/util/test/acutest_common_init.h>
24#include <freeradius-devel/util/test/acutest_helpers.h>
25
26#include <freeradius-devel/bio/bio_priv.h>
27#include <freeradius-devel/bio/fd.h>
28#include <freeradius-devel/util/syserror.h>
29
30#include <poll.h>
31
32static int connected_count;
33static int error_count;
34static int timeout_count;
35
36static void cb_connected(fr_bio_t *bio) { (void) bio; connected_count++; }
37static void cb_error(fr_bio_t *bio) { (void) bio; error_count++; }
38static void cb_timeout(fr_bio_t *bio) { (void) bio; timeout_count++; }
39
40/** Open a TCP socket listening on loopback
41 *
42 * The kernel completes the TCP handshake for any listening socket, and the test needs only the
43 * handshake. The test never calls accept(), so the kernel holds the new connection in the backlog of
44 * the listening socket.
45 *
46 * @param[out] port the port which the kernel picked.
47 * @return
48 * - >=0 the listening socket.
49 * - <0 on error.
50 */
51static int loopback_listen(uint16_t *port)
52{
53 struct sockaddr_in sin = {
54 .sin_family = AF_INET,
55 .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
56 };
57 socklen_t len = sizeof(sin);
58 int fd;
59
60 fd = socket(AF_INET, SOCK_STREAM, 0);
61 if (fd < 0) return -1;
62
63 if ((bind(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) ||
64 (listen(fd, 5) < 0) ||
65 (getsockname(fd, (struct sockaddr *) &sin, &len) < 0)) {
66 close(fd);
67 return -1;
68 }
69
70 *port = ntohs(sin.sin_port);
71 return fd;
72}
73
74/** A deferred connect which succeeds has to call the connected callback
75 *
76 * A non-blocking connect() to a listening loopback port can return EINPROGRESS. On EINPROGRESS,
77 * fr_bio_fd_connect_full() defers the connect, and the event loop calls fr_bio_fd_el_connect() once
78 * the socket is writable. fr_bio_fd_el_connect() then calls connect() a second time. If the
79 * handshake has finished, the second connect() can fail with EISCONN, so fr_bio_fd_try_connect() has
80 * to treat EISCONN as success.
81 *
82 * The test needs a connect which succeeds, because a connect which fails may not reach
83 * fr_bio_fd_el_connect(). kqueue can report a refused connect with EV_EOF, and event.c passes EV_EOF
84 * to the error callback, fr_bio_fd_el_error().
85 */
87{
88 TALLOC_CTX *ctx = talloc_init_const("test");
90 fr_bio_t *bio;
93 uint16_t port;
94 int listen_fd;
95 int rcode;
96
98 error_count = 0;
99 timeout_count = 0;
100
101 listen_fd = loopback_listen(&port);
102 TEST_CHECK(listen_fd >= 0);
103 if (listen_fd < 0) goto done;
104
105 el = fr_event_list_alloc(ctx, NULL, NULL);
106 TEST_CHECK(el != NULL);
107 if (!el) goto done;
108
109 cfg = (fr_bio_fd_config_t) {
111 .socket_type = SOCK_STREAM,
112 .transport_type = FR_BIO_FD_TRANSPORT_TCP,
113 .async = true,
114 .src_ipaddr = {
115 .af = AF_INET,
116 .addr.v4.s_addr = htonl(INADDR_LOOPBACK),
117 .prefix = 32,
118 },
119 .dst_port = port,
120 };
121 cfg.dst_ipaddr = cfg.src_ipaddr;
122
123 bio = fr_bio_fd_alloc(ctx, &cfg, 0);
124 TEST_CHECK(bio != NULL);
125 if (!bio) goto done;
126
127 rcode = fr_bio_fd_connect_full(bio, el, cb_connected, cb_error, &timeout, cb_timeout);
128
129 /*
130 * FreeBSD completes a loopback TCP handshake inside connect(), so the connect can
131 * finish at once (rcode 1). Linux and macOS return EINPROGRESS and defer it (rcode 0).
132 */
133 TEST_CHECK(rcode >= 0);
134 TEST_MSG("connect_full returned %d. 0 means deferred, 1 means connected at once, <0 means failed", rcode);
135 if (rcode < 0) goto done;
136
137 /*
138 * Wait in the event loop until one of the three callbacks runs. The connect timeout
139 * is a timer, so a wait always ends, and a connect which never finishes cannot hang
140 * the test.
141 */
142 while (!connected_count && !error_count && !timeout_count) {
143 if (fr_event_corral(el, fr_time(), true) < 0) break;
145 }
146
147 TEST_CASE("the connected callback ran");
149 TEST_MSG("connected_count = %d, error_count = %d, timeout_count = %d, connect_errno = %d",
151
152 TEST_CASE("neither the error callback nor the timeout callback ran");
155
156 TEST_CASE("the bio is open");
158
159done:
160 if (listen_fd >= 0) close(listen_fd);
161 talloc_free(ctx);
162}
163
164/** See if the IPv6 loopback address can be used
165 *
166 * Some hosts and containers turn IPv6 off. The socket() or bind() fails there.
167 *
168 * @return
169 * - 0 the IPv6 loopback address can be used.
170 * - the errno from the socket() or bind() which failed. The errno is saved before close() can
171 * change it.
172 */
173static int ipv6_loopback_usable(void)
174{
175 struct sockaddr_in6 sin6 = {
176 .sin6_family = AF_INET6,
177 .sin6_addr = IN6ADDR_LOOPBACK_INIT,
178 };
179 int fd;
180 int error = 0;
181
182 fd = socket(AF_INET6, SOCK_DGRAM, 0);
183 if (fd < 0) return errno;
184
185 if (bind(fd, (struct sockaddr *) &sin6, sizeof(sin6)) < 0) error = errno;
186 close(fd);
187
188 return error;
189}
190
191/** Read one packet through an unconnected UDP bio bound to the wildcard address
192 *
193 * The local address of a bio bound to the wildcard address does not identify the address that a
194 * packet was sent to. fr_bio_fd_init_common() therefore installs fr_bio_fd_recvfromto4() or
195 * fr_bio_fd_recvfromto6(). Those two functions read the destination address from a control message
196 * (data which recvmsg() returns beside the packet), and copy the destination port from the port of
197 * the bio.
198 *
199 * fr_bio_fd_common_datagram() also enables receive timestamps, so each packet carries two control
200 * messages. If cbuf in fr_bio_fd_t is too small for both control messages, then the bio drops every
201 * packet.
202 */
203static void recvfromto_test(int af)
204{
205 TALLOC_CTX *ctx = talloc_init_const("test");
207 fr_bio_t *bio;
208 fr_bio_fd_info_t const *info;
210 fr_ipaddr_t loopback;
211 struct sockaddr_storage to;
212 socklen_t to_len;
213 uint8_t buffer[16];
214 ssize_t rcode;
215 int fd = -1;
216 struct timeval recv_timeout = { .tv_sec = 5 };
217 int error;
218
219 if ((af == AF_INET6) && ((error = ipv6_loopback_usable()) != 0)) {
220 TEST_SKIP("the IPv6 loopback address is not available: %s", fr_syserror(error));
221 talloc_free(ctx);
222 return;
223 }
224
225 cfg = (fr_bio_fd_config_t) {
227 .socket_type = SOCK_DGRAM,
228 .transport_type = FR_BIO_FD_TRANSPORT_UDP,
229 .src_ipaddr = {
230 .af = af,
231 .prefix = (af == AF_INET) ? 32 : 128,
232 },
233 };
234
235 bio = fr_bio_fd_alloc(ctx, &cfg, 0);
236 TEST_CHECK(bio != NULL);
237 TEST_MSG("fr_bio_fd_alloc failed: %s", fr_strerror());
238 if (!bio) goto done;
239
240 info = fr_bio_fd_info(bio);
241 TEST_CHECK(info->socket.inet.src_port != 0);
242 TEST_MSG("the bio did not learn the port which the kernel picked");
243
244 /*
245 * Send one packet to the loopback address and the port of the bio.
246 */
247 if (af == AF_INET) {
248 loopback = (fr_ipaddr_t) { .af = AF_INET, .prefix = 32, .addr.v4.s_addr = htonl(INADDR_LOOPBACK) };
249 } else {
250 loopback = (fr_ipaddr_t) { .af = AF_INET6, .prefix = 128, .addr.v6 = in6addr_loopback };
251 }
252
253 TEST_CHECK(fr_ipaddr_to_sockaddr(&to, &to_len, &loopback, info->socket.inet.src_port) == 0);
254
255 fd = socket(af, SOCK_DGRAM, 0);
256 TEST_CHECK(fd >= 0);
257 if (fd < 0) goto done;
258
259 /*
260 * A failed TEST_CHECK() calls printf(), which may change errno, so save errno first.
261 */
262 rcode = sendto(fd, "x", 1, 0, (struct sockaddr *) &to, to_len);
263 error = errno;
264 TEST_CHECK(rcode == 1);
265 TEST_MSG("sendto failed: %s", fr_syserror(error));
266 if (rcode != 1) goto done;
267
268 /*
269 * The socket of the bio is blocking, so fr_bio_read() waits until the packet arrives. The
270 * receive timeout stops the test from waiting forever if the packet never arrives.
271 */
272 TEST_CHECK(setsockopt(info->socket.fd, SOL_SOCKET, SO_RCVTIMEO, &recv_timeout, sizeof(recv_timeout)) == 0);
273
274 rcode = fr_bio_read(bio, &packet_ctx, buffer, sizeof(buffer));
275
276 TEST_CASE("the packet is read, not dropped");
277 TEST_CHECK(rcode == 1);
278 TEST_MSG("fr_bio_read returned %zd", rcode);
279 if (rcode != 1) goto done;
280
281 TEST_CASE("the destination is the loopback address and the port of the bio");
282 TEST_CHECK(fr_ipaddr_cmp(&packet_ctx.socket.inet.dst_ipaddr, &loopback) == 0);
283 TEST_CHECK(packet_ctx.socket.inet.dst_port == info->socket.inet.src_port);
284 TEST_MSG("dst_port = %u, expected %u", packet_ctx.socket.inet.dst_port, info->socket.inet.src_port);
285
286done:
287 if (fd >= 0) close(fd);
288 talloc_free(ctx);
289}
290
291static void test_recvfromto4(void)
292{
293 recvfromto_test(AF_INET);
294}
295
296static void test_recvfromto6(void)
297{
298 recvfromto_test(AF_INET6);
299}
300
301/** Accept a TCP connection through a listening bio
302 *
303 * fr_bio_fd_accept() returns 0 when no connection is waiting, and 1 with a new connected bio when one
304 * is. The listening bio must not change: the new socket, its name, and its read and write routines
305 * all belong to the new bio.
306 */
307static void test_listen_accept(void)
308{
309 TALLOC_CTX *ctx = talloc_init_const("test");
311 fr_bio_t *listener, *accepted = NULL;
312 fr_bio_fd_info_t const *linfo, *ainfo;
314 fr_ipaddr_t loopback = { .af = AF_INET, .prefix = 32, .addr.v4.s_addr = htonl(INADDR_LOOPBACK) };
315 struct sockaddr_storage to;
316 socklen_t to_len;
317 struct sockaddr_in client_addr = {};
318 socklen_t client_len = sizeof(client_addr);
319 struct pollfd pfd;
320 char buffer[16];
321 ssize_t rcode;
322 int client = -1;
323 int fd;
324 uint16_t port;
325
326 /*
327 * A server socket needs a port. Ask the kernel for a free one, and release it.
328 */
329 fd = loopback_listen(&port);
330 TEST_CHECK(fd >= 0);
331 if (fd < 0) goto done;
332 close(fd);
333
334 cfg = (fr_bio_fd_config_t) {
336 .socket_type = SOCK_STREAM,
337 .transport_type = FR_BIO_FD_TRANSPORT_TCP,
338 .async = true, /* so that an accept() with nothing waiting does not block */
339 .src_ipaddr = loopback,
340 .src_port = port,
341 };
342
343 listener = fr_bio_fd_alloc(ctx, &cfg, 0);
344 TEST_CHECK(listener != NULL);
345 TEST_MSG("fr_bio_fd_alloc failed: %s", fr_strerror());
346 if (!listener) goto done;
347
348 linfo = fr_bio_fd_info(listener);
349 TEST_CASE("an accept with no connection waiting returns 0, and leaves the listener open");
350 rcode = fr_bio_fd_accept(ctx, &accepted, listener);
351 TEST_CHECK(rcode == 0);
352 TEST_MSG("fr_bio_fd_accept returned %zd", rcode);
353 TEST_CHECK(accepted == NULL);
354 TEST_CHECK(fcntl(linfo->socket.fd, F_GETFD) >= 0);
355 if ((rcode != 0) || accepted) goto done;
356
357 TEST_CHECK(fr_ipaddr_to_sockaddr(&to, &to_len, &loopback, linfo->socket.inet.src_port) == 0);
358 client = socket(AF_INET, SOCK_STREAM, 0);
359 TEST_CHECK(client >= 0);
360 if (client < 0) goto done;
361 TEST_CHECK(connect(client, (struct sockaddr *) &to, to_len) == 0);
362 TEST_CHECK(getsockname(client, (struct sockaddr *) &client_addr, &client_len) == 0);
363
364 /*
365 * connect() can return before the connection is queued on the listener. A listening
366 * socket becomes readable once a connection is waiting, so wait for that.
367 */
368 pfd = (struct pollfd) { .fd = linfo->socket.fd, .events = POLLIN };
369 TEST_CHECK(poll(&pfd, 1, 5000) == 1);
370
371 TEST_CASE("an accept with a connection waiting returns 1 and a connected bio");
372 rcode = fr_bio_fd_accept(ctx, &accepted, listener);
373 TEST_CHECK(rcode == 1);
374 TEST_MSG("fr_bio_fd_accept returned %zd", rcode);
375 TEST_CHECK(accepted != NULL);
376 if ((rcode != 1) || !accepted) goto done;
377
378 ainfo = fr_bio_fd_info(accepted);
381 TEST_CHECK((ainfo->socket.fd >= 0) && (ainfo->socket.fd != linfo->socket.fd));
382 TEST_CHECK(ainfo->name != NULL);
383
384 TEST_CASE("the remote end of the new bio is the client");
385 TEST_CHECK(fr_ipaddr_cmp(&ainfo->socket.inet.dst_ipaddr, &loopback) == 0);
386 TEST_CHECK(ainfo->socket.inet.dst_port == ntohs(client_addr.sin_port));
387 TEST_MSG("dst_port = %u, expected %u", ainfo->socket.inet.dst_port, ntohs(client_addr.sin_port));
388
389 TEST_CASE("the listener is unchanged");
391 TEST_CHECK(fr_bio_read(listener, &packet_ctx, buffer, sizeof(buffer)) < 0);
392
393 TEST_CASE("the new bio carries data in both directions");
394 TEST_CHECK(fr_bio_write(accepted, &packet_ctx, "x", 1) == 1);
395 TEST_CHECK(recv(client, buffer, sizeof(buffer), 0) == 1);
396 TEST_CHECK(buffer[0] == 'x');
397
398 TEST_CHECK(send(client, "y", 1, 0) == 1);
399
400 /*
401 * The new socket is non-blocking, so wait until the byte has arrived.
402 */
403 pfd = (struct pollfd) { .fd = ainfo->socket.fd, .events = POLLIN };
404 TEST_CHECK(poll(&pfd, 1, 5000) == 1);
405 TEST_CHECK(fr_bio_read(accepted, &packet_ctx, buffer, sizeof(buffer)) == 1);
406 TEST_CHECK(buffer[0] == 'y');
407
408done:
409 if (client >= 0) close(client);
410 talloc_free(ctx);
411}
412
414 { "deferred_connect_success_calls_connected_cb", test_deferred_connect_success_calls_connected_cb },
415 { "recvfromto4", test_recvfromto4 },
416 { "recvfromto6", test_recvfromto6 },
417 { "listen_accept", test_listen_accept },
419};
static int const char char buffer[256]
Definition acutest.h:641
#define TEST_CHECK(cond)
Definition acutest.h:101
#define TEST_CASE(name)
Definition acutest.h:203
#define TEST_SKIP(...)
Definition acutest.h:290
#define TEST_TERMINATOR
Definition acutest.h:77
#define TEST_MSG(...)
Definition acutest.h:234
static ssize_t fr_bio_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write raw data to a bio.
Definition base.h:184
static ssize_t fr_bio_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
Read raw data from a bio.
Definition base.h:161
static int packet_ctx
the tests pass only the address of packet_ctx
TEST_LIST
static void cb_timeout(fr_bio_t *bio)
static void recvfromto_test(int af)
Read one packet through an unconnected UDP bio bound to the wildcard address.
static void test_deferred_connect_success_calls_connected_cb(void)
A deferred connect which succeeds has to call the connected callback.
static void test_recvfromto6(void)
static int loopback_listen(uint16_t *port)
Open a TCP socket listening on loopback.
static void cb_error(fr_bio_t *bio)
static int connected_count
static void test_recvfromto4(void)
static void test_listen_accept(void)
Accept a TCP connection through a listening bio.
static int error_count
static int ipv6_loopback_usable(void)
See if the IPv6 loopback address can be used.
static void cb_connected(fr_bio_t *bio)
static int timeout_count
int fr_bio_fd_connect_full(fr_bio_t *bio, fr_event_list_t *el, fr_bio_callback_t connected_cb, fr_bio_callback_t error_cb, fr_time_delta_t *timeout, fr_bio_callback_t timeout_cb)
Finalize a connect()
Definition fd.c:1341
fr_bio_t * fr_bio_fd_alloc(TALLOC_CTX *ctx, fr_bio_fd_config_t const *cfg, size_t offset)
Allocate a FD bio.
Definition fd.c:1108
int fr_bio_fd_accept(TALLOC_CTX *ctx, fr_bio_t **out_p, fr_bio_t *bio)
Accept a new connection on a socket.
Definition fd.c:1850
fr_bio_fd_info_t const * fr_bio_fd_info(fr_bio_t *bio)
Returns a pointer to the bio-specific information.
Definition fd.c:1438
fr_socket_t socket
as connected socket
Definition fd.h:145
char const * name
printable name of this BIO
Definition fd.h:150
fr_bio_fd_type_t type
type of the socket
Definition fd.h:147
fr_bio_fd_state_t state
connecting, open, closed, etc.
Definition fd.h:148
fr_ipaddr_t dst_ipaddr
their IP address
Definition fd.h:102
fr_bio_fd_type_t type
accept, connected, unconnected, etc.
Definition fd.h:93
@ FR_BIO_FD_CONNECTED
connected client sockets (UDP or TCP)
Definition fd.h:63
@ FR_BIO_FD_UNCONNECTED
unconnected UDP / datagram only
Definition fd.h:60
@ FR_BIO_FD_LISTEN
call fr_bio_fd_accept() for each new connection
Definition fd.h:64
@ FR_BIO_FD_TRANSPORT_TCP
TCP over IPv4 or IPv6.
Definition fd.h:79
@ FR_BIO_FD_TRANSPORT_UDP
UDP over IPv4 or IPv6.
Definition fd.h:80
@ FR_BIO_FD_STATE_OPEN
error states must be before this
Definition fd.h:54
fr_ipaddr_t src_ipaddr
our IP address
Definition fd.h:101
Configuration for sockets.
Definition fd.h:92
Run-time status of the socket.
Definition fd.h:144
Per-packet context.
Definition fd.h:46
talloc_free(hp)
int fr_ipaddr_to_sockaddr(struct sockaddr_storage *sa, socklen_t *salen, fr_ipaddr_t const *ipaddr, uint16_t port)
Convert our internal ip address representation to a sockaddr.
Definition inet.c:1399
fr_cmp_ret_t fr_ipaddr_cmp(fr_ipaddr_t const *a, fr_ipaddr_t const *b)
Compare two ip addresses.
Definition inet.c:1353
int af
Address family.
Definition inet.h:64
IPv4/6 prefix.
void fr_event_service(fr_event_list_t *el)
Service any outstanding timer or file descriptor events.
Definition event.c:2220
int fr_event_corral(fr_event_list_t *el, fr_time_t now, bool wait)
Gather outstanding timer and file descriptor events.
Definition event.c:2088
#define fr_time()
Definition event.c:58
fr_event_list_t * fr_event_list_alloc(TALLOC_CTX *ctx, fr_event_status_cb_t status, void *status_uctx)
Initialise a new event list.
Definition event.c:2551
Stores all information relating to an event list.
Definition event.c:375
unsigned short uint16_t
long int ssize_t
unsigned char uint8_t
static bool done
Definition radclient.c:80
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition talloc.h:127
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:599
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
static fr_event_list_t * el
int fd
File descriptor if this is a live socket.
Definition socket.h:86
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:558
static fr_sbuff_err_t char size_t * len
Definition value.h:1061