SafeAPI Backend POSIX
POSIX/Linux OAL backend for safeAPIFreamwork
Loading...
Searching...
No Matches
sapi_posix_backend_netlink.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
2
43
44#include <arpa/inet.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <netdb.h>
48#include <netinet/in.h>
49#include <poll.h>
50#include <stdbool.h>
51#include <stdint.h>
52#include <stdio.h>
53#include "safeapi/oal/memory/sapi_mem_util.h"
54#include "safeapi/oal/log/sapi_log.h"
55#include <sys/socket.h>
56#include <time.h>
57#include <unistd.h>
58
61#define POSIX_NETLINK_UDP_HELLO ((uint8_t)0xA5U)
62#define POSIX_NETLINK_UDP_HELLO_ACK ((uint8_t)0x5AU)
71#define POSIX_NETLINK_UDP_HELLO_PERIOD_MS (20U)
72
73typedef struct
74{
75 int fd;
76 size_t message_size;
78
79/* C99-compatible static assert: posix_netlink_state_t must fit inside
80 * sapi_netlink_storage_t's reserved bytes. */
81typedef char posix_netlink_storage_fits_[(sizeof(posix_netlink_state_t) <= sizeof(sapi_netlink_storage_t)) ? 1
82 : -1];
83
84static sapi_status_t set_nonblocking(int fd)
85{
86 int flags = fcntl(fd, F_GETFL, 0);
87
88 if (flags < 0)
89 {
90 return SAPI_STATUS_INTERNAL_ERROR;
91 }
92 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0)
93 {
94 return SAPI_STATUS_INTERNAL_ERROR;
95 }
96 return SAPI_STATUS_OK;
97}
98
99static void compute_deadline(sapi_duration_ms_t timeout_ms, struct timespec *out_deadline)
100{
101 (void)clock_gettime(CLOCK_MONOTONIC, out_deadline);
102 out_deadline->tv_sec += (time_t)(timeout_ms / 1000U);
103 out_deadline->tv_nsec += (long)((timeout_ms % 1000U) * 1000000L);
104 if (out_deadline->tv_nsec >= 1000000000L)
105 {
106 out_deadline->tv_sec += 1;
107 out_deadline->tv_nsec -= 1000000000L;
108 }
109}
110
113static int remaining_ms(const struct timespec *deadline)
114{
115 struct timespec now;
116 int64_t delta_ms;
117
118 if (clock_gettime(CLOCK_MONOTONIC, &now) != 0)
119 {
120 return -1;
121 }
122 delta_ms = ((int64_t)deadline->tv_sec - (int64_t)now.tv_sec) * 1000;
123 delta_ms += ((int64_t)deadline->tv_nsec - (int64_t)now.tv_nsec) / 1000000;
124 if (delta_ms < 0)
125 {
126 delta_ms = 0;
127 }
128 return (int)delta_ms;
129}
130
131static sapi_status_t open_listen_udp(const sapi_netlink_config_t *config, int *out_fd)
132{
133 int fd;
134 int reuse = 1;
135 struct sockaddr_in bind_addr;
136 struct sockaddr_in peer_addr;
137 struct timespec deadline;
138 uint8_t ack_byte = POSIX_NETLINK_UDP_HELLO_ACK;
139
140 fd = socket(AF_INET, SOCK_DGRAM, 0);
141 if (fd < 0)
142 {
143 return SAPI_STATUS_INTERNAL_ERROR;
144 }
145 (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
146
147 sapi_mem_set(&bind_addr, 0, sizeof(bind_addr));
148 bind_addr.sin_family = AF_INET;
149 bind_addr.sin_port = htons(config->port);
150 if (config->host == NULL)
151 {
152 bind_addr.sin_addr.s_addr = INADDR_ANY;
153 }
154 else if (inet_pton(AF_INET, config->host, &bind_addr.sin_addr) != 1)
155 {
156 (void)close(fd);
157 return SAPI_STATUS_INVALID_PARAM;
158 }
159
160 if (bind(fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0)
161 {
162 char msg[80];
163
164 (void)snprintf(msg, sizeof(msg), "open_listen_udp: bind port %u failed errno=%d", config->port, errno);
165 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
166 (void)close(fd);
167 return SAPI_STATUS_INTERNAL_ERROR;
168 }
169 {
170 char msg[80];
171
172 (void)snprintf(msg, sizeof(msg), "open_listen_udp: bound port %u successfully (fd=%d)", config->port, fd);
173 sapi_log_write(SAPI_LOG_LEVEL_INFO, "NETLINK", msg);
174 }
175 if (set_nonblocking(fd) != SAPI_STATUS_OK)
176 {
177 (void)close(fd);
178 return SAPI_STATUS_INTERNAL_ERROR;
179 }
180
181 compute_deadline(config->connect_timeout_ms, &deadline);
182 for (;;)
183 {
184 struct pollfd pfd;
185 int wait_ms = remaining_ms(&deadline);
186 int rc;
187 ssize_t n;
188 uint8_t hello_byte;
189 socklen_t peer_addr_len = sizeof(peer_addr);
190
191 pfd.fd = fd;
192 pfd.events = POLLIN;
193 pfd.revents = 0;
194 rc = poll(&pfd, 1, (wait_ms > 0) ? wait_ms : 0);
195 if (rc == 0)
196 {
197 (void)close(fd);
198 return SAPI_STATUS_TIMEOUT;
199 }
200 if (rc < 0)
201 {
202 if (errno == EINTR)
203 {
204 if (wait_ms <= 0)
205 {
206 (void)close(fd);
207 return SAPI_STATUS_TIMEOUT;
208 }
209 continue;
210 }
211 (void)close(fd);
212 return SAPI_STATUS_INTERNAL_ERROR;
213 }
214
215 sapi_mem_set(&peer_addr, 0, sizeof(peer_addr));
216 n = recvfrom(fd, &hello_byte, sizeof(hello_byte), 0, (struct sockaddr *)&peer_addr, &peer_addr_len);
217 if (n < 0)
218 {
219 if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))
220 {
221 continue;
222 }
223 {
224 char msg[64];
225
226 (void)snprintf(msg, sizeof(msg), "open_listen_udp: recvfrom failed errno=%d", errno);
227 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
228 }
229 (void)close(fd);
230 return SAPI_STATUS_INTERNAL_ERROR;
231 }
232 if ((n != (ssize_t)sizeof(hello_byte)) || (hello_byte != POSIX_NETLINK_UDP_HELLO))
233 {
234 char msg[64];
235
236 (void)snprintf(msg, sizeof(msg), "open_listen_udp: unexpected byte %02x, n=%ld", hello_byte, (long)n);
237 sapi_log_write(SAPI_LOG_LEVEL_DEBUG, "NETLINK", msg);
238 continue; /* not our handshake datagram - keep waiting */
239 }
240
241 if (connect(fd, (struct sockaddr *)&peer_addr, peer_addr_len) != 0)
242 {
243 char msg[64];
244
245 (void)snprintf(msg, sizeof(msg), "open_listen_udp: connect failed errno=%d", errno);
246 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
247 (void)close(fd);
248 return SAPI_STATUS_INTERNAL_ERROR;
249 }
250 sapi_log_write(SAPI_LOG_LEVEL_INFO, "NETLINK", "open_listen_udp: connected to peer, sending ACK");
251 break;
252 }
253
254 if (send(fd, &ack_byte, sizeof(ack_byte), 0) != (ssize_t)sizeof(ack_byte))
255 {
256 char msg[64];
257
258 (void)snprintf(msg, sizeof(msg), "open_listen_udp: send ACK failed errno=%d", errno);
259 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
260 (void)close(fd);
261 return SAPI_STATUS_INTERNAL_ERROR;
262 }
263 sapi_log_write(SAPI_LOG_LEVEL_INFO, "NETLINK", "open_listen_udp: ACK sent OK");
264
265 /* Deliberately no post-ACK "grace drain" wait here: an earlier version
266 * of this function blocked open() for a further fixed window to
267 * absorb any HELLO retransmits still in flight from the peer, but
268 * that unconditionally delayed every LISTEN-role open() (and, in
269 * turn, callers with a tight end-to-end budget - e.g.
270 * SAFEAPI_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS - built on the old TCP
271 * backend's much faster connection establishment). A stray duplicate
272 * HELLO that arrives after open() has already returned is instead
273 * handled cheaply, on demand, inside backend_receive() below - no
274 * fixed delay paid unless one actually shows up. */
275 *out_fd = fd;
276 return SAPI_STATUS_OK;
277}
278
279static sapi_status_t open_connect_udp(const sapi_netlink_config_t *config, int *out_fd)
280{
281 struct addrinfo hints;
282 struct addrinfo *resolved = NULL;
283 char port_str[8];
284 int fd;
285 struct timespec deadline;
286 struct timespec next_hello_deadline;
287 uint8_t hello_byte = POSIX_NETLINK_UDP_HELLO;
288
289 (void)snprintf(port_str, sizeof(port_str), "%u", (unsigned int)config->port);
290 {
291 char msg[96];
292
293 (void)snprintf(msg, sizeof(msg), "open_connect_udp: entry host=%s port=%s", config->host, port_str);
294 sapi_log_write(SAPI_LOG_LEVEL_DEBUG, "NETLINK", msg);
295 }
296
297 sapi_mem_set(&hints, 0, sizeof(hints));
298 hints.ai_family = AF_INET;
299 hints.ai_socktype = SOCK_DGRAM;
300 if (getaddrinfo(config->host, port_str, &hints, &resolved) != 0)
301 {
302 return SAPI_STATUS_INVALID_PARAM;
303 }
304
305 fd = socket(resolved->ai_family, resolved->ai_socktype, resolved->ai_protocol);
306 if (fd < 0)
307 {
308 freeaddrinfo(resolved);
309 return SAPI_STATUS_INTERNAL_ERROR;
310 }
311 if (set_nonblocking(fd) != SAPI_STATUS_OK)
312 {
313 (void)close(fd);
314 freeaddrinfo(resolved);
315 return SAPI_STATUS_INTERNAL_ERROR;
316 }
317
318 /* connect() on a UDP socket only fixes the default peer address
319 * locally - it performs no network I/O and gives no signal that the
320 * peer is actually reachable, unlike TCP's connect(). The HELLO/
321 * HELLO_ACK exchange below is what makes SAPI_STATUS_OK meaningful. */
322 if (connect(fd, resolved->ai_addr, resolved->ai_addrlen) != 0)
323 {
324 char msg[64];
325
326 (void)snprintf(msg, sizeof(msg), "open_connect_udp: connect failed errno=%d", errno);
327 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
328 (void)close(fd);
329 freeaddrinfo(resolved);
330 return SAPI_STATUS_INTERNAL_ERROR;
331 }
332 {
333 char msg[64];
334
335 (void)snprintf(msg, sizeof(msg), "open_connect_udp: connect succeeded (fd=%d)", fd);
336 sapi_log_write(SAPI_LOG_LEVEL_INFO, "NETLINK", msg);
337 }
338 freeaddrinfo(resolved);
339
340 compute_deadline(config->connect_timeout_ms, &deadline);
341 compute_deadline(0U, &next_hello_deadline); /* send the first HELLO immediately */
342 sapi_log_write(SAPI_LOG_LEVEL_DEBUG, "NETLINK", "open_connect_udp: entering loop");
343 for (;;)
344 {
345 struct pollfd pfd;
346 int wait_ms = remaining_ms(&deadline);
347 int hello_wait_ms;
348 int poll_wait_ms;
349 int rc;
350 ssize_t n;
351 uint8_t ack_byte;
352
353 /* Deliberately no per-iteration log line here: this loop's own
354 * HELLO retry cadence (POSIX_NETLINK_UDP_HELLO_PERIOD_MS, 20ms)
355 * means a per-iteration DEBUG line fires roughly 50x/second for
356 * as long as the peer isn't up yet - real overhead during a
357 * connection-establishment window that later turned out to
358 * matter for a known-fragile startup negotiation race
359 * (ab_gp_channel_crosscompare.c's NEGOTIATION MISMATCH ->
360 * REBOOT path, common_config.h's own ADR-029 comment on it) -
361 * only genuinely exceptional outcomes (an unhandled send error
362 * below) are worth a log line at this cadence. */
363 if (wait_ms <= 0)
364 {
365 (void)close(fd);
366 return SAPI_STATUS_TIMEOUT;
367 }
368
369 hello_wait_ms = remaining_ms(&next_hello_deadline);
370 if (hello_wait_ms <= 0)
371 {
372 ssize_t sres = send(fd, &hello_byte, sizeof(hello_byte), 0);
373
374 if ((sres < 0) && (errno != ECONNREFUSED) &&
375 (errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != ENOTCONN) &&
376 (errno != EHOSTUNREACH) && (errno != EADDRNOTAVAIL) && (errno != EPIPE) &&
377 (errno != ECONNRESET) && (errno != ENETUNREACH))
378 {
379 char msg[64];
380
381 (void)snprintf(msg, sizeof(msg), "open_connect_udp: unhandled send error errno=%d", errno);
382 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
383 (void)close(fd);
384 return SAPI_STATUS_INTERNAL_ERROR;
385 }
386 compute_deadline((sapi_duration_ms_t)POSIX_NETLINK_UDP_HELLO_PERIOD_MS, &next_hello_deadline);
387 hello_wait_ms = remaining_ms(&next_hello_deadline);
388 }
389
390 poll_wait_ms = (hello_wait_ms < wait_ms) ? hello_wait_ms : wait_ms;
391 pfd.fd = fd;
392 pfd.events = POLLIN;
393 pfd.revents = 0;
394 rc = poll(&pfd, 1, poll_wait_ms);
395 if (rc < 0)
396 {
397 if (errno == EINTR)
398 {
399 continue;
400 }
401 (void)close(fd);
402 return SAPI_STATUS_INTERNAL_ERROR;
403 }
404 if (rc == 0)
405 {
406 continue; /* hello-resend tick or spurious wake - re-evaluate both deadlines */
407 }
408
409 n = recv(fd, &ack_byte, sizeof(ack_byte), MSG_PEEK);
410 if (n < 0)
411 {
412 if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR) || (errno == ECONNREFUSED) ||
413 (errno == ENOTCONN) || (errno == EHOSTUNREACH) || (errno == EPIPE) || (errno == ECONNRESET) ||
414 (errno == ENETUNREACH))
415 {
416 continue; /* including ECONNREFUSED/ENOTCONN/ECONNRESET: peer not up yet */
417 }
418 {
419 char msg[64];
420
421 (void)snprintf(msg, sizeof(msg), "open_connect_udp: recv failed errno=%d", errno);
422 sapi_log_write(SAPI_LOG_LEVEL_ERROR, "NETLINK", msg);
423 }
424 (void)close(fd);
425 return SAPI_STATUS_INTERNAL_ERROR;
426 }
427 {
428 char msg[64];
429
430 (void)snprintf(msg, sizeof(msg), "open_connect_udp: peeked n=%ld byte=%02x", (long)n, ack_byte);
431 sapi_log_write(SAPI_LOG_LEVEL_DEBUG, "NETLINK", msg);
432 }
433 if ((n == (ssize_t)sizeof(ack_byte)) && (ack_byte == POSIX_NETLINK_UDP_HELLO_ACK))
434 {
435 uint8_t consumed_byte;
436
437 (void)recv(fd, &consumed_byte, sizeof(consumed_byte), 0); /* actually consume the ACK */
438 sapi_log_write(SAPI_LOG_LEVEL_INFO, "NETLINK", "open_connect_udp: consumed ACK successfully");
439 break;
440 }
441 /* Not our handshake ACK (or a duplicate HELLO we ourselves sent
442 * echoing back is impossible - this is a unidirectional wait) -
443 * leave it queued for the caller's first real receive() and keep
444 * waiting within the same deadline. */
445 }
446
447 *out_fd = fd;
448 return SAPI_STATUS_OK;
449}
450
451static sapi_status_t backend_open(sapi_netlink_storage_t *storage, const sapi_netlink_config_t *config,
452 sapi_netlink_handle_t *out_handle)
453{
455 int fd = -1;
456 sapi_status_t status;
457
458 if ((storage == NULL) || (config == NULL) || (out_handle == NULL))
459 {
460 return SAPI_STATUS_INVALID_PARAM;
461 }
462
463 {
464 char msg[96];
465
466 (void)snprintf(msg, sizeof(msg), "backend_open: role=%d host=%s port=%u", config->role,
467 config->host ? config->host : "NULL", config->port);
468 sapi_log_write(SAPI_LOG_LEVEL_DEBUG, "NETLINK", msg);
469 }
470 status =
471 (config->role == SAPI_NETLINK_ROLE_LISTEN) ? open_listen_udp(config, &fd) : open_connect_udp(config, &fd);
472 {
473 char msg[64];
474
475 (void)snprintf(msg, sizeof(msg), "backend_open (role=%d): returned status=%d", config->role, (int)status);
476 sapi_log_write(SAPI_LOG_LEVEL_DEBUG, "NETLINK", msg);
477 }
478 if (status != SAPI_STATUS_OK)
479 {
480 return status;
481 }
482
483 state = (posix_netlink_state_t *)(void *)storage;
484 state->fd = fd;
485 state->message_size = config->message_size;
486 *out_handle = (sapi_netlink_handle_t)(void *)storage;
487 return SAPI_STATUS_OK;
488}
489
490static sapi_status_t backend_send(sapi_netlink_handle_t handle, const void *message, size_t message_size,
491 sapi_duration_ms_t timeout_ms)
492{
493 posix_netlink_state_t *state = (posix_netlink_state_t *)(void *)handle;
494 struct timespec deadline;
495
496 if ((state == NULL) || (message == NULL) || (message_size == 0U))
497 {
498 return SAPI_STATUS_INVALID_PARAM;
499 }
500
501 compute_deadline(timeout_ms, &deadline);
502 for (;;)
503 {
504 struct pollfd pfd;
505 int wait_ms = remaining_ms(&deadline);
506 int rc;
507 ssize_t n;
508
509 if (wait_ms <= 0)
510 {
511 return SAPI_STATUS_TIMEOUT;
512 }
513 pfd.fd = state->fd;
514 pfd.events = POLLOUT;
515 pfd.revents = 0;
516 rc = poll(&pfd, 1, wait_ms);
517 if (rc == 0)
518 {
519 return SAPI_STATUS_TIMEOUT;
520 }
521 if (rc < 0)
522 {
523 if (errno == EINTR)
524 {
525 continue;
526 }
527 return SAPI_STATUS_INTERNAL_ERROR;
528 }
529
530 n = send(state->fd, message, message_size, 0);
531 if (n < 0)
532 {
533 if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))
534 {
535 continue;
536 }
537 if (errno == ECONNREFUSED)
538 {
539 return SAPI_STATUS_HARDWARE_FAULT;
540 }
541 return SAPI_STATUS_INTERNAL_ERROR;
542 }
543 if ((size_t)n != message_size)
544 {
545 /* A connected SOCK_DGRAM send() is documented atomic-or-fails;
546 * a short write here means an unexpected kernel condition, not
547 * a partial transfer to resume (there is nothing to loop for,
548 * unlike a TCP stream). */
549 return SAPI_STATUS_INTERNAL_ERROR;
550 }
551 return SAPI_STATUS_OK;
552 }
553}
554
555static sapi_status_t backend_receive(sapi_netlink_handle_t handle, void *out_message, size_t buffer_size,
556 sapi_duration_ms_t timeout_ms)
557{
558 posix_netlink_state_t *state = (posix_netlink_state_t *)(void *)handle;
559 struct timespec deadline;
560
561 if ((state == NULL) || (out_message == NULL) || (buffer_size == 0U))
562 {
563 return SAPI_STATUS_INVALID_PARAM;
564 }
565
566 compute_deadline(timeout_ms, &deadline);
567 for (;;)
568 {
569 struct pollfd pfd;
570 int wait_ms = remaining_ms(&deadline);
571 int rc;
572 ssize_t n;
573
574 if (wait_ms <= 0)
575 {
576 return SAPI_STATUS_TIMEOUT;
577 }
578 pfd.fd = state->fd;
579 pfd.events = POLLIN;
580 pfd.revents = 0;
581 rc = poll(&pfd, 1, wait_ms);
582 if (rc == 0)
583 {
584 return SAPI_STATUS_TIMEOUT;
585 }
586 if (rc < 0)
587 {
588 if (errno == EINTR)
589 {
590 continue;
591 }
592 return SAPI_STATUS_INTERNAL_ERROR;
593 }
594
595 /* MSG_TRUNC makes the kernel report the datagram's real length
596 * even if it exceeded buffer_size, instead of silently truncating
597 * it - required to actually detect an oversized datagram below,
598 * not just an undersized one. */
599#ifdef __APPLE__
600 n = recv(state->fd, out_message, buffer_size, 0);
601#else
602 n = recv(state->fd, out_message, buffer_size, MSG_TRUNC);
603#endif
604 if (n < 0)
605 {
606 if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))
607 {
608 continue;
609 }
610 if (errno == ECONNREFUSED)
611 {
612 return SAPI_STATUS_HARDWARE_FAULT;
613 }
614 return SAPI_STATUS_INTERNAL_ERROR;
615 }
616 if ((size_t)n != buffer_size)
617 {
618 /* A lone HELLO/HELLO_ACK byte that outlived the handshake (a
619 * retransmit already in flight when the peer stopped
620 * resending, or open()'s own final ACK arriving late) is not
621 * a wire violation - silently drop it and keep waiting within
622 * the same deadline, matching a plain "nothing new yet" wait.
623 * message_size is never 1 byte for any real link in this
624 * example (smallest is ANSWER_WIRE_SIZE/M136_WIRE_SIZE at 8),
625 * so this can never mask a genuine 1-byte application frame. */
626 if ((n == (ssize_t)sizeof(uint8_t)) &&
627 ((((const uint8_t *)out_message)[0] == POSIX_NETLINK_UDP_HELLO) ||
628 (((const uint8_t *)out_message)[0] == POSIX_NETLINK_UDP_HELLO_ACK)))
629 {
630 continue;
631 }
632 /* Anything else of the wrong length: this link's message_size
633 * is fixed by config, so a datagram of any other length -
634 * truncated, oversized, or a stray 0-byte datagram - is a
635 * genuine wire protocol violation, not a value to silently
636 * accept partial-length. */
637 return SAPI_STATUS_DATA_CORRUPTION;
638 }
639 return SAPI_STATUS_OK;
640 }
641}
642
643static sapi_status_t backend_close(sapi_netlink_handle_t handle)
644{
645 posix_netlink_state_t *state = (posix_netlink_state_t *)(void *)handle;
646
647 if (state == NULL)
648 {
649 return SAPI_STATUS_INVALID_PARAM;
650 }
651 (void)close(state->fd);
652 return SAPI_STATUS_OK;
653}
654
655static const sapi_netlink_backend_t s_posix_netlink_backend = { backend_open, backend_send, backend_receive,
656 backend_close };
657
658const sapi_netlink_backend_t *sapi_posix_backend_netlink(void)
659{
660 return &s_posix_netlink_backend;
661}
Real POSIX/Linux backend for every OAL service (ADR-018).
const sapi_netlink_backend_t * sapi_posix_backend_netlink(void)
The POSIX sapi_netlink backend (TCP sockets, LISTEN/CONNECT).
static int remaining_ms(const struct timespec *deadline)