libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_net.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability stable
4 * Use of this source code is governed by the ISC License,
5 * a copy of which is in the file LICENSE in the top-level directory
6 * of this distribution.
7 *
8 * src/inc/xtc_net.h
9 * Networking helpers built on top of xtc_io. These are the
10 * first slice of M19.1: TCP knobs + Unix domain sockets +
11 * cred-passing. Future passes will add accept scaling
12 * (SO_REUSEPORT distribution), TCP_USER_TIMEOUT, congestion
13 * algorithm selection, etc.
14 *
15 * The socket family is identified by xtc_net_family_t; xtc owns
16 * the fd and exposes it for direct use with xtc_io_reg_fd /
17 * xtc_io_poll. None of these calls block -- caller-driven I/O
18 * is the contract.
19 */
20
21#ifndef XTC_NET_H
22#define XTC_NET_H
23
24#include "xtc_export.h"
25
26#include <stddef.h>
27#include <stdint.h>
28
29#include "xtc.h"
30
31typedef enum xtc_net_family {
32 XTC_NET_INET = 0,
33 XTC_NET_INET6 = 1,
34 XTC_NET_UNIX = 2
35} xtc_net_family_t;
36
37/* TCP-level knobs. Defaults pick reasonable server-side values:
38 * TCP_NODELAY=1, SO_REUSEADDR=1, KEEPALIVE off (caller opts in). */
39typedef struct xtc_tcp_opts {
40 size_t size; /* sizeof(xtc_tcp_opts_t): XTC_TCP_OPTS_DEFAULT */
41 int nodelay; /* TCP_NODELAY: 1 = disable Nagle */
42 int reuseaddr; /* SO_REUSEADDR */
43 int reuseport; /* SO_REUSEPORT (Linux/BSD; falls back to no-op) */
44 int keepalive; /* SO_KEEPALIVE */
45 int keepidle_s; /* TCP_KEEPIDLE in seconds (Linux); 0 = OS default */
46 int keepintvl_s; /* TCP_KEEPINTVL in seconds (Linux); 0 = OS default */
47 int keepcnt; /* TCP_KEEPCNT (Linux); 0 = OS default */
48 int user_timeout_ms; /* TCP_USER_TIMEOUT in ms (Linux); 0 = OS default */
50
51#define XTC_TCP_OPTS_DEFAULT { \
52 .size = sizeof(xtc_tcp_opts_t), \
53 .nodelay = 1, .reuseaddr = 1, .reuseport = 0, .keepalive = 0, \
54 .keepidle_s = 0, .keepintvl_s = 0, .keepcnt = 0, .user_timeout_ms = 0 \
55}
56
57/*
58 * PUBLIC: int xtc_net_listen __P((xtc_net_family_t, const char *, int, const xtc_tcp_opts_t *, int *));
59 * PUBLIC: int xtc_net_listen_backlog __P((xtc_net_family_t, const char *, int, const xtc_tcp_opts_t *, int, int *));
60 * PUBLIC: int xtc_net_dial __P((xtc_net_family_t, const char *, int, const xtc_tcp_opts_t *, int *));
61 * PUBLIC: int xtc_net_accept __P((int, int *));
62 * PUBLIC: int xtc_net_apply_tcp_opts __P((int, const xtc_tcp_opts_t *));
63 * PUBLIC: int xtc_net_setnonblock __P((int));
64 * PUBLIC: void xtc_net_close __P((int));
65 *
66 * PUBLIC: int xtc_net_unix_listen __P((const char *, int *));
67 * PUBLIC: int xtc_net_unix_dial __P((const char *, int *));
68 * PUBLIC: int xtc_net_unix_send_creds __P((int, const void *, size_t));
69 * PUBLIC: int xtc_net_unix_recv_creds __P((int, void *, size_t, uint32_t *, uint32_t *, size_t *));
70 *
71 * PUBLIC: int xtc_net_udp_socket __P((xtc_net_family_t, const char *, int, int *));
72 * PUBLIC: int xtc_net_udp_sendto __P((int, const void *, size_t, const char *, int));
73 * PUBLIC: int xtc_net_udp_recvfrom __P((int, void *, size_t, char *, size_t, int *, size_t *));
74 *
75 * PUBLIC: int xtc_dns_resolve __P((const char *, int, xtc_net_family_t, char *, size_t));
76 */
77
78/* TCP listen socket. `host` may be NULL/"" for any-address. Returns
79 * the listening fd; caller registers it with xtc_io_reg_fd for accept
80 * readiness. The accept-queue depth is the default: SOMAXCONN, or 4096
81 * if that is smaller, clamped by the kernel to its own limit
82 * (net.core.somaxconn on Linux). It was a fixed 128 through 1.55.0. */
83XTC_API int xtc_net_listen(xtc_net_family_t fam, const char *host, int port,
84 const xtc_tcp_opts_t *opts, int *out_fd);
85
86/* xtc_net_listen with an explicit accept-queue depth. backlog 0 means
87 * the default above; negative is XTC_E_INVAL. The kernel clamps it. */
88XTC_API int xtc_net_listen_backlog(xtc_net_family_t fam, const char *host,
89 int port, const xtc_tcp_opts_t *opts,
90 int backlog, int *out_fd);
91
92/* TCP connect to host:port. Returns the fd in out_fd. The connect
93 * is non-blocking; caller polls for writability to detect completion. */
94/* Accept one pending connection on listen_fd as a non-blocking, CLOEXEC
95 * fd. XTC_E_AGAIN when none is pending. Charged against XTC_RES_FDS when
96 * xtc_res_attach_net is in effect (XTC_E_RESOURCE at the cap; the
97 * connection stays in the backlog); xtc_net_close refunds it. */
98XTC_API int xtc_net_accept(int listen_fd, int *out_fd);
99XTC_API int xtc_net_dial(xtc_net_family_t fam, const char *host, int port,
100 const xtc_tcp_opts_t *opts, int *out_fd);
101
102/* Apply TCP knobs to an already-open socket. */
103XTC_API int xtc_net_apply_tcp_opts(int fd, const xtc_tcp_opts_t *opts);
104
105/* Set O_NONBLOCK + FD_CLOEXEC. */
106XTC_API int xtc_net_setnonblock(int fd);
107
108/* Close (always succeeds). */
109XTC_API void xtc_net_close(int fd);
110
111/* Unix domain sockets -- SOCK_STREAM. The listener's backlog is the
112 * same default as xtc_net_listen (it was 64 through 1.55.0); to change
113 * it, call listen(2) again on the returned fd with the depth you want. */
114XTC_API int xtc_net_unix_listen(const char *path, int *out_fd);
115XTC_API int xtc_net_unix_dial (const char *path, int *out_fd);
116
117/* Send a message + the sender's credentials (uid/gid/pid) over a
118 * UNIX socket via SCM_CREDENTIALS / LOCAL_PEERCRED. The receiver
119 * extracts the credentials with xtc_net_unix_recv_creds. */
120XTC_API int xtc_net_unix_send_creds(int fd, const void *buf, size_t buflen);
121XTC_API int xtc_net_unix_recv_creds(int fd, void *buf, size_t buflen,
122 uint32_t *out_uid, uint32_t *out_gid,
123 size_t *out_n);
124
125/* ---- UDP ----------------------------------------------------- */
126
127/* Open a UDP datagram socket bound to (host, port). host=NULL
128 * means INADDR_ANY / in6addr_any. port=0 means kernel-assigned.
129 * Returns the fd in non-blocking + cloexec mode. */
130XTC_API int xtc_net_udp_socket(xtc_net_family_t fam, const char *host,
131 int port, int *out_fd);
132
133/* Send a single datagram to host:port. Returns XTC_OK on full
134 * send, XTC_E_AGAIN if the kernel buffer is full (caller should
135 * poll for writability). */
136XTC_API int xtc_net_udp_sendto(int fd, const void *buf, size_t len,
137 const char *host, int port);
138
139/* Receive a single datagram. out_host/out_host_size receive the
140 * peer's address as a printable string. out_port and out_n are
141 * filled in. Returns XTC_OK or XTC_E_AGAIN. */
142XTC_API int xtc_net_udp_recvfrom(int fd, void *buf, size_t buflen,
143 char *out_host, size_t out_host_size,
144 int *out_port, size_t *out_n);
145
146/* ---- DNS resolution ------------------------------------------
147 *
148 * xtc_dns_resolve resolves a hostname to a printable address
149 * string. The current implementation uses getaddrinfo(3) on a
150 * blocking thread; the calling proc / fiber blocks during the
151 * lookup. For genuine async resolution -- and to avoid blocking
152 * the loop -- run the call from a dedicated lookup proc whose
153 * thread is allowed to block. A future revision will integrate
154 * a c-ares-style fully async resolver.
155 */
156XTC_API int xtc_dns_resolve(const char *hostname, int port,
157 xtc_net_family_t fam,
158 char *out_addr, size_t out_addr_size);
159
160/* ---- length-framed transport --------------------------------
161 *
162 * A 4-byte big-endian length prefix + payload. On a non-blocking fd
163 * these yield the calling fiber (via the loop) on partial I/O instead
164 * of blocking, so many connections share one loop; off a loop they
165 * fall back to poll(2). Set the fd non-blocking (xtc_net_setnonblock)
166 * for the loop-friendly behaviour.
167 *
168 * xtc_net_recv_frame allocates the frame with the xtc allocator (free
169 * it with xtc_free) and rejects a claimed length above max_len
170 * (max_len 0 = no cap) with XTC_E_RANGE -- so a peer cannot force an
171 * unbounded allocation. Returns XTC_E_AGAIN on timeout, XTC_E_INVAL
172 * if the peer closed mid-frame. A zero-length frame returns XTC_OK
173 * with *out == NULL.
174 *
175 * Neither copies the payload in user space: send_frame hands the
176 * caller's buffer to the kernel together with the 4-byte prefix in one
177 * sendmsg(2) (one TCP segment for a small frame), and recv_frame reads
178 * straight into the frame it returns.
179 */
180int xtc_net_send_frame(int fd, const void *buf, size_t len);
181int xtc_net_recv_frame(int fd, void **out, size_t *out_len,
182 size_t max_len, int64_t timeout_ns);
183
184#if !defined(_WIN32)
185#include <sys/uio.h> /* struct iovec */
186/*
187 * PUBLIC: int xtc_net_send_framev __P((int, const struct iovec *, int));
188 *
189 * Scatter-gather send_frame: one frame whose payload is the
190 * concatenation of iov[0..iovcnt), sent straight from those buffers
191 * (no assembly copy into a contiguous message). Use it for a header
192 * plus a body that live apart -- a reply header on the stack and a
193 * value in a cache, a record batch in several slabs. The receiver sees
194 * an ordinary frame (xtc_net_recv_frame). Zero-length pieces are
195 * skipped; iovcnt 0 sends an empty frame. The caller's iovec array is
196 * not modified. iovcnt is at most 63 (XTC_E_INVAL above that); the
197 * total at most 0xFFFFFFFF bytes. Same waiting and return codes as
198 * xtc_net_send_frame. POSIX only.
199 */
200XTC_API int xtc_net_send_framev(int fd, const struct iovec *iov, int iovcnt);
201#endif
202
203#endif /* XTC_NET_H */