The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
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#define _BIO_PRIVATE 1
27#include <freeradius-devel/bio/bio_priv.h>
28#include <freeradius-devel/bio/fd.h>
29#include <freeradius-devel/util/syserror.h>
30
31static int connected_count;
32static int error_count;
33static int timeout_count;
34
35static void cb_connected(fr_bio_t *bio) { (void) bio; connected_count++; }
36static void cb_error(fr_bio_t *bio) { (void) bio; error_count++; }
37static void cb_timeout(fr_bio_t *bio) { (void) bio; timeout_count++; }
38
39/** Open a TCP socket listening on loopback
40 *
41 * The kernel completes the TCP handshake for any listening socket, and the test needs only the
42 * handshake. The test never calls accept(), so the kernel holds the new connection in the backlog of
43 * the listening socket.
44 *
45 * @param[out] port the port which the kernel picked.
46 * @return
47 * - >=0 the listening socket.
48 * - <0 on error.
49 */
50static int loopback_listen(uint16_t *port)
51{
52 struct sockaddr_in sin = {
53 .sin_family = AF_INET,
54 .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
55 };
56 socklen_t len = sizeof(sin);
57 int fd;
58
59 fd = socket(AF_INET, SOCK_STREAM, 0);
60 if (fd < 0) return -1;
61
62 if ((bind(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) ||
63 (listen(fd, 5) < 0) ||
64 (getsockname(fd, (struct sockaddr *) &sin, &len) < 0)) {
65 close(fd);
66 return -1;
67 }
68
69 *port = ntohs(sin.sin_port);
70 return fd;
71}
72
73/** A deferred connect which succeeds has to call the connected callback
74 *
75 * A non-blocking connect() to a listening loopback port can return EINPROGRESS. On EINPROGRESS,
76 * fr_bio_fd_connect_full() defers the connect, and the event loop calls fr_bio_fd_el_connect() once
77 * the socket is writable. fr_bio_fd_el_connect() then calls connect() a second time. If the
78 * handshake has finished, the second connect() can fail with EISCONN, so fr_bio_fd_try_connect() has
79 * to treat EISCONN as success.
80 *
81 * The test needs a connect which succeeds, because a connect which fails may not reach
82 * fr_bio_fd_el_connect(). kqueue can report a refused connect with EV_EOF, and event.c passes EV_EOF
83 * to the error callback, fr_bio_fd_el_error().
84 */
86{
87 TALLOC_CTX *ctx = talloc_init_const("test");
89 fr_bio_t *bio;
92 uint16_t port;
93 int listen_fd;
94 int rcode;
95
97 error_count = 0;
98 timeout_count = 0;
99
100 listen_fd = loopback_listen(&port);
101 TEST_CHECK(listen_fd >= 0);
102 if (listen_fd < 0) goto done;
103
104 el = fr_event_list_alloc(ctx, NULL, NULL);
105 TEST_CHECK(el != NULL);
106 if (!el) goto done;
107
108 cfg = (fr_bio_fd_config_t) {
110 .socket_type = SOCK_STREAM,
111 .transport_type = FR_BIO_FD_TRANSPORT_TCP,
112 .async = true,
113 .src_ipaddr = {
114 .af = AF_INET,
115 .addr.v4.s_addr = htonl(INADDR_LOOPBACK),
116 .prefix = 32,
117 },
118 .dst_port = port,
119 };
120 cfg.dst_ipaddr = cfg.src_ipaddr;
121
122 bio = fr_bio_fd_alloc(ctx, &cfg, 0);
123 TEST_CHECK(bio != NULL);
124 if (!bio) goto done;
125
126 rcode = fr_bio_fd_connect_full(bio, el, cb_connected, cb_error, &timeout, cb_timeout);
127
128 /*
129 * FreeBSD completes a loopback TCP handshake inside connect(), so the connect can
130 * finish at once (rcode 1). Linux and macOS return EINPROGRESS and defer it (rcode 0).
131 */
132 TEST_MSG("connect_full returned %d. 0 means deferred, 1 means connected at once, <0 means failed", rcode);
133 TEST_CHECK(rcode >= 0);
134 if (rcode < 0) goto done;
135
136 /*
137 * Wait in the event loop until one of the three callbacks runs. The connect timeout
138 * is a timer, so a wait always ends, and a connect which never finishes cannot hang
139 * the test.
140 */
141 while (!connected_count && !error_count && !timeout_count) {
142 if (fr_event_corral(el, fr_time(), true) < 0) break;
144 }
145
146 TEST_CASE("the connected callback ran");
148 TEST_MSG("connected_count = %d, error_count = %d, timeout_count = %d, connect_errno = %d",
150
151 TEST_CASE("neither the error callback nor the timeout callback ran");
154
155 TEST_CASE("the bio is open");
157
158done:
159 if (listen_fd >= 0) close(listen_fd);
160 talloc_free(ctx);
161}
162
163/** Read one packet through an unconnected UDP bio bound to the wildcard address
164 *
165 * The local address of a bio bound to the wildcard address does not identify the address that a
166 * packet was sent to. fr_bio_fd_init_common() therefore installs fr_bio_fd_recvfromto4() or
167 * fr_bio_fd_recvfromto6(). Those two functions read the destination address from a control message
168 * (data which recvmsg() returns beside the packet), and copy the destination port from the port of
169 * the bio.
170 *
171 * fr_bio_fd_common_datagram() also enables receive timestamps, so each packet carries two control
172 * messages. If cbuf in fr_bio_fd_t is too small for both control messages, then the bio drops every
173 * packet.
174 */
175/** See if the IPv6 loopback address can be used
176 *
177 * Some hosts and containers turn IPv6 off. The bind() fails there, and errno says why.
178 */
179static bool ipv6_loopback_usable(void)
180{
181 struct sockaddr_in6 sin6 = {
182 .sin6_family = AF_INET6,
183 .sin6_addr = IN6ADDR_LOOPBACK_INIT,
184 };
185 int fd;
186 bool usable;
187
188 fd = socket(AF_INET6, SOCK_DGRAM, 0);
189 if (fd < 0) return false;
190
191 usable = (bind(fd, (struct sockaddr *) &sin6, sizeof(sin6)) == 0);
192 close(fd);
193
194 return usable;
195}
196
197static void recvfromto_test(int af)
198{
199 TALLOC_CTX *ctx = talloc_init_const("test");
201 fr_bio_t *bio;
202 fr_bio_fd_info_t const *info;
204 fr_ipaddr_t loopback;
205 struct sockaddr_storage to;
206 socklen_t to_len;
207 uint8_t buffer[16];
208 ssize_t rcode;
209 int fd = -1;
210 struct timeval recv_timeout = { .tv_sec = 5 };
211
212 if ((af == AF_INET6) && !ipv6_loopback_usable()) {
213 TEST_SKIP("the IPv6 loopback address is not available: %s", fr_syserror(errno));
214 talloc_free(ctx);
215 return;
216 }
217
218 cfg = (fr_bio_fd_config_t) {
220 .socket_type = SOCK_DGRAM,
221 .transport_type = FR_BIO_FD_TRANSPORT_UDP,
222 .src_ipaddr = {
223 .af = af,
224 .prefix = (af == AF_INET) ? 32 : 128,
225 },
226 };
227
228 bio = fr_bio_fd_alloc(ctx, &cfg, 0);
229 TEST_CHECK(bio != NULL);
230 TEST_MSG("fr_bio_fd_alloc failed: %s", fr_strerror());
231 if (!bio) goto done;
232
233 info = fr_bio_fd_info(bio);
234 TEST_CHECK(info->socket.inet.src_port != 0);
235 TEST_MSG("the bio did not learn the port which the kernel picked");
236
237 /*
238 * Send one packet to the loopback address and the port of the bio.
239 */
240 if (af == AF_INET) {
241 loopback = (fr_ipaddr_t) { .af = AF_INET, .prefix = 32, .addr.v4.s_addr = htonl(INADDR_LOOPBACK) };
242 } else {
243 loopback = (fr_ipaddr_t) { .af = AF_INET6, .prefix = 128, .addr.v6 = in6addr_loopback };
244 }
245
246 TEST_CHECK(fr_ipaddr_to_sockaddr(&to, &to_len, &loopback, info->socket.inet.src_port) == 0);
247
248 fd = socket(af, SOCK_DGRAM, 0);
249 TEST_CHECK(fd >= 0);
250 if (fd < 0) goto done;
251
252 rcode = sendto(fd, "x", 1, 0, (struct sockaddr *) &to, to_len);
253 TEST_CHECK(rcode == 1);
254 TEST_MSG("sendto failed: %s", fr_syserror(errno));
255 if (rcode != 1) goto done;
256
257 /*
258 * The socket of the bio is blocking, so fr_bio_read() waits until the packet arrives. The
259 * receive timeout stops the test from waiting forever if the packet never arrives.
260 */
261 TEST_CHECK(setsockopt(info->socket.fd, SOL_SOCKET, SO_RCVTIMEO, &recv_timeout, sizeof(recv_timeout)) == 0);
262
263 rcode = fr_bio_read(bio, &packet_ctx, buffer, sizeof(buffer));
264
265 TEST_CASE("the packet is read, not dropped");
266 TEST_CHECK(rcode == 1);
267 TEST_MSG("fr_bio_read returned %zd", rcode);
268 if (rcode != 1) goto done;
269
270 TEST_CASE("the destination is the loopback address and the port of the bio");
271 TEST_CHECK(fr_ipaddr_cmp(&packet_ctx.socket.inet.dst_ipaddr, &loopback) == 0);
272 TEST_CHECK(packet_ctx.socket.inet.dst_port == info->socket.inet.src_port);
273 TEST_MSG("dst_port = %u, expected %u", packet_ctx.socket.inet.dst_port, info->socket.inet.src_port);
274
275done:
276 if (fd >= 0) close(fd);
277 talloc_free(ctx);
278}
279
280static void test_recvfromto4(void)
281{
282 recvfromto_test(AF_INET);
283}
284
285static void test_recvfromto6(void)
286{
287 recvfromto_test(AF_INET6);
288}
289
291 { "deferred_connect_success_calls_connected_cb", test_deferred_connect_success_calls_connected_cb },
292 { "recvfromto4", test_recvfromto4 },
293 { "recvfromto6", test_recvfromto6 },
295};
static int const char char buffer[256]
Definition acutest.h:635
#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_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)
static void test_deferred_connect_success_calls_connected_cb(void)
A deferred connect which succeeds has to call the connected callback.
static bool ipv6_loopback_usable(void)
Read one packet through an unconnected UDP bio bound to the wildcard address.
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 int error_count
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:1280
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:1047
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:1377
fr_socket_t socket
as connected socket
Definition fd.h:145
@ 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_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_STATE_OPEN
error states must be before this
Definition fd.h:54
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_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:2222
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:2090
#define fr_time()
Definition event.c:60
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:2553
Stores all information relating to an event list.
Definition event.c:377
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:590
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