xtc_net(3)

---

xtc_net(3)

networking helpers

XTC_NET(3) Library Functions Manual XTC_NET(3)

xtc_net_listen, xtc_net_listen_backlog, xtc_net_dial, xtc_net_accept, xtc_net_apply_tcp_opts, xtc_net_setnonblock, xtc_net_close, xtc_net_unix_listen, xtc_net_unix_dial, xtc_net_unix_send_creds, xtc_net_unix_recv_creds, xtc_net_send_frame, xtc_net_send_framev, xtc_net_recv_frame, xtc_net_udp_socket, xtc_net_udp_sendto, xtc_net_udp_recvfrom, xtc_dns_resolve — networking helpers

#include <xtc.h>
#include <xtc_net.h>

int
xtc_net_listen(xtc_net_family_t fam, const char *host, int port, const xtc_tcp_opts_t *opts, int *out_fd);

int
xtc_net_listen_backlog(xtc_net_family_t fam, const char *host, int port, const xtc_tcp_opts_t *opts, int backlog, int *out_fd);

int
xtc_net_dial(xtc_net_family_t fam, const char *host, int port, const xtc_tcp_opts_t *opts, int *out_fd);

int
xtc_net_accept(int listen_fd, int *out_fd);

int
xtc_net_apply_tcp_opts(int fd, const xtc_tcp_opts_t *opts);

int
xtc_net_setnonblock(int fd);

void
xtc_net_close(int fd);

int
xtc_net_unix_listen(const char *path, int *out_fd);

int
xtc_net_unix_dial(const char *path, int *out_fd);

int
xtc_net_unix_send_creds(int fd, const void *buf, size_t buflen);

int
xtc_net_unix_recv_creds(int fd, void *buf, size_t buflen, uint32_t *out_uid, uint32_t *out_gid, size_t *out_n);

int
xtc_net_send_frame(int fd, const void *buf, size_t len);

int
xtc_net_send_framev(int fd, const struct iovec *iov, int iovcnt);

int
xtc_net_recv_frame(int fd, void **out, size_t *out_len, size_t max_len, int64_t timeout_ns);

int
xtc_net_udp_socket(xtc_net_family_t fam, const char *host, int port, int *out_fd);

int
xtc_net_udp_sendto(int fd, const void *buf, size_t len, const char *host, int port);

int
xtc_net_udp_recvfrom(int fd, void *buf, size_t buflen, char *out_host, size_t out_host_size, int *out_port, size_t *out_n);

int
xtc_dns_resolve(const char *hostname, int port, xtc_net_family_t fam, char *out_addr, size_t out_addr_size);

xtc_net provides convenience wrappers around the standard networking syscalls. The actual I/O multiplexing happens through xtc_io(3) and xtc_proc_wait_fd(3);

() and () exchange a 4-byte big-endian length prefix followed by the payload. On a non-blocking fd they yield the calling fiber on partial I/O instead of blocking the loop, so many connections share one loop; off a loop they fall back to poll(2). xtc_net_recv_frame() allocates the frame (free it with ()), and rejects a claimed length above max_len (0 = no cap) with XTC_E_RANGE so a peer cannot force an unbounded allocation; it returns XTC_E_AGAIN on timeout and XTC_E_INVAL if the peer closed mid-frame. A zero-length frame yields XTC_OK with *out set to NULL. Neither copies the payload in user space: xtc_net_send_frame() hands the caller's buffer to the kernel with the prefix in the same sendmsg(2) (one TCP segment for a small frame), and xtc_net_recv_frame() reads straight into the frame it returns.

() sends one frame whose payload is the concatenation of iov[0 .. iovcnt], straight from those buffers, so a header and a body that live apart need no assembly copy. The receiver sees an ordinary frame. Zero-length pieces are skipped, iovcnt 0 sends an empty frame, and the caller's iovec array is not modified. iovcnt is at most 63 (XTC_E_INVAL above that) and the total at most 0xFFFFFFFF bytes. It is not available on Windows. these helpers handle socket creation, address resolution, knob setting, and credential passing.

() opens a UDP datagram socket bound to host:port (host NULL means wildcard, port 0 means kernel-assigned) in non-blocking, close-on-exec mode. () sends one datagram to host:port, returning XTC_E_AGAIN if the kernel buffer is full. () receives one datagram, filling out_host (of out_host_size bytes), out_port, and out_n with the peer address and byte count.

() resolves hostname:port for family fam to a printable address string in out_addr (of out_addr_size bytes). It uses getaddrinfo(3) on a blocking thread, so run it from a proc whose thread is allowed to block.

controlled by xtc_tcp_opts_t include:

nodelay
: disable Nagle's algorithm. Default 1.
reuseaddr
.
reuseport
on Linux/BSD; no-op elsewhere.
keepalive
plus optional keepidle_s, keepintvl_s, keepcnt on Linux.
user_timeout_ms
on Linux.

() opens a non-blocking listening socket bound to host:port. fam selects address family (XTC_NET_INET, XTC_NET_INET6, or XTC_NET_UNIX). The fd is non-blocking with FD_CLOEXEC; caller can register it with xtc_io_reg_fd(3) or pass to xtc_proc_wait_fd(3). The accept queue is as deep as the host allows: the call asks for SOMAXCONN, or 4096 where that is smaller, and the kernel clamps the request to its own limit (net.core.somaxconn on Linux). Through 1.55.0 it was a fixed 128, which overflowed under a connection burst (20000 clients: TcpExtListenOverflows rose and 5926 had not completed one echo after 10 s).

() is xtc_net_listen() with an explicit queue depth. backlog 0 means the default above; a negative value returns XTC_E_INVAL.

() opens a non-blocking TCP connection. The connect is in flight when the call returns; caller polls for XTC_IO_WRITABLE to detect completion.

() accepts one pending connection on a listening socket as a non-blocking, close-on-exec descriptor, or returns XTC_E_AGAIN when none is pending (wait for XTC_IO_READABLE on listen_fd and retry). With () in effect the new descriptor is charged against XTC_RES_FDS before the accept, so at the cap it returns XTC_E_RESOURCE and the connection stays in the kernel backlog; () refunds it. Releases before 1.51 had no accept here, so inbound connections could not be metered. xtc_net_close() also closes a socket carrying kernel TLS (xtc_tls_enable_ktls(3)) without letting the kernel's flush of a queued record to a departed peer raise SIGPIPE in the caller.

() applies the knobs to an already-open socket. Useful for sockets returned by accept(2).

() sets O_NONBLOCK + FD_CLOEXEC on a fd (POSIX path or) FIONBIO (Windows path).

() and () open AF_UNIX SOCK_STREAM sockets at the given filesystem path. Listeners auto-unlink the path on bind failure. A UNIX listener gets the same default backlog as xtc_net_listen() (it was 64 through 1.55.0); to change it, call listen(2) again on the returned descriptor.

on Unix sockets: () sends a buffer; the receiver, via (), gets the buffer plus the sender's UID and GID using SO_PEERCRED (Linux) or LOCAL_PEERCRED (BSD). On platforms where neither is available, UID/GID return as 0 without error.

Linux
, io_uring + epoll backends.
FreeBSD/macOS
, kqueue backend.
illumos
emulated via getpeerucred 3C; event ports backend.
Windows
Winsock2; UDS not supported. TCP only.

A send to a peer that has closed never raises SIGPIPE, so the library is safe without any process-wide signal setup: it returns an error instead. Every send in this API passes MSG_NOSIGNAL where the platform has it (Linux, the BSDs). On macOS, which lacks it, the stream sockets these functions create carry SO_NOSIGPIPE; a descriptor the caller obtained elsewhere (for example from its own accept(2)) and passes to xtc_net_send_frame() must have SO_NOSIGPIPE set by the caller. Releases before 1.50 let a send to a closed peer kill the process.

xtc_tcp_opts_t opts = XTC_TCP_OPTS_DEFAULT;
opts.keepalive = 1;
opts.keepidle_s = 60;

int listen_fd;
xtc_net_listen(XTC_NET_INET, "0.0.0.0", 8080, &opts, &listen_fd);

for (;;) {
    uint32_t revents;
    int conn_fd;
    xtc_proc_wait_fd(listen_fd, XTC_IO_READABLE, -1, &revents);
    conn_fd = accept(listen_fd, NULL, NULL);
    if (conn_fd < 0) continue;
    xtc_net_setnonblock(conn_fd);
    xtc_net_apply_tcp_opts(conn_fd, &opts);
    /* spawn a per-conn xtc_proc... */
}

xtc_io(3), xtc_proc(3), xtc_tls(3), xtc(7)

Appeared in xtc 0.1.

October 23, 2026 Debian

View the mdoc source