CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
net_private.h
1#pragma once
2
3#include "addr.h"
4#include "net/flow.h"
5#include "net/queue.h"
6#include "net/socket.h"
7#include <cx/time/clock.h>
8#include <cx/utils/lazyinit.h>
9
10extern LazyInitState _netInit_done;
11void _netInit(void* unused);
12
13bool netPlatformInit(void);
14NetQueue* netPlatformCreateQueue(_In_ const NetQueueConfig* conf);
15
16#if defined(_PLATFORM_WIN)
17// Create the native completion-port backend directly, bypassing the netPlatformCreateQueue backend
18// selection. Returns NULL if IOCP declines (under Wine, where it is emulated over the readiness
19// path with no benefit -- see osIsWine() in cx/platform/win/win_os.h). Used by the IOCP test suite
20// to exercise the backend explicitly regardless of what the default selection would pick. Windows-
21// only: there is no completion-port concept on Unix, so this does not exist there even as a stub --
22// code that needs to run on both must go through netPlatformCreateQueue() and NQ_SelectOnly instead.
23_Ret_maybenull_ NetQueue* netPlatformCreateIOCP(_In_ const NetQueueConfig* conf);
24#endif
25
26// Create a platform socket of the given type (NetSocketWin, NetSocketPosix, ...). The shared
27// NetQueueSelect calls this for its socket() factory so it does not have to know the concrete
28// per-platform socket class.
29_Ret_maybenull_ NetSocket* netPlatformCreateSocket(NetSocketType type);
30
31// ---------------------------------------------------------------------------------------------
32// Platform socket shims
33//
34// Thin wrappers over the OS socket calls, implemented per platform (win_net.c / unix_net.c).
35// They exist so the backends in cx/net/ can move bytes and classify errors without including
36// winsock2.h or sys/socket.h -- SOCKET vs int and WSAGetLastError vs errno are the whole of the
37// difference, and it lives here rather than being duplicated in every backend.
38// ---------------------------------------------------------------------------------------------
39
40// Map the last socket error the OS recorded on this thread to a NetErrorCode. NERR_WouldBlock is
41// the common, non-fatal one on a readiness backend; NERR_Interrupted asks for a retry. Call
42// immediately after a failed socket operation, before anything else can overwrite it.
43NetErrorCode netLastError(void);
44
45// Outcome of starting a non-blocking connect(), classified so the portable state machine does not
46// have to test WSAEWOULDBLOCK / EINPROGRESS itself.
47typedef enum {
48 NETCONN_Connected = 0, // connect completed synchronously (common on loopback)
49 NETCONN_InProgress = 1, // connect is pending; wait for writability / completion
50 NETCONN_Failed = 2, // connect failed synchronously; *err carries the reason
51} NetConnectStatus;
52
53// Begin a non-blocking connect() on an already-reset handle of the right family. Returns whether
54// the connect resolved immediately, is pending, or failed; *err is set on failure.
55NetConnectStatus netSockConnect(NetSockHandle h, _In_ const NetAddr* addr, _Out_ NetErrorCode* err);
56
57// Read the result of a completed non-blocking connect via getsockopt(SO_ERROR), mapped to a
58// NetErrorCode (NERR_None on success). Used by readiness backends when a connecting socket signals
59// writable or excepted.
60NetErrorCode netSockConnectResult(NetSockHandle h);
61
62// Close a socket's current OS handle and replace it with a fresh non-blocking handle of `family`,
63// updating sock->handle. `bindAny` binds the new handle to the wildcard address of that family,
64// which ConnectEx requires. A connect attempt needs a fresh handle because a socket with a
65// failed/pending connect cannot be reliably reconnected, and a resolved list can mix families.
66// Returns false on failure (handle left INVALID). Implemented per platform (win_net_socket.c).
67bool netPlatformResetSocket(_Inout_ NetSocket* sock, NetAddrType family, bool bindAny);
68
69// Resolve a host/port to a list of addresses via the platform name service (getaddrinfo). Runs on
70// a resolver worker thread, never a net I/O thread. Pushes each result into `out` in the order the
71// resolver returned them, with the port filled in. Returns NERR_None on success. Implemented per
72// platform (win_net.c).
73NetErrorCode netPlatformResolve(_In_opt_ strref host, uint16 port, _Inout_ sa_NetAddr* out);
74
75// Look up the OS interface index for a named network interface, for IPv6 zone IDs like
76// "fe80::1%eth0". Returns 0 if the name is unknown or the platform cannot do the lookup (numeric
77// zone IDs never come through here; netAddrFromStr parses those itself). Implemented per platform
78// (win_net.c).
79uint32 netPlatformIfNameToIndex(_In_z_ const char* name);
80
81// Receive from a connected (stream) socket into buf. Returns the byte count, 0 on an orderly peer
82// shutdown, or -1 on error -- in which case *err carries the classified reason (NERR_WouldBlock
83// when nothing was ready after all). Never blocks; the socket is non-blocking.
84intptr netSockRecv(NetSockHandle h, _Out_writes_bytes_(len) void* buf, size_t len,
85 _Out_ NetErrorCode* err);
86
87// Receive one datagram into buf and report its source address. Same return convention as
88// netSockRecv(), except 0 is a legitimate zero-length datagram rather than a shutdown.
89intptr netSockRecvFrom(NetSockHandle h, _Out_writes_bytes_(len) void* buf, size_t len,
90 _Out_ NetAddr* from, _Out_ NetErrorCode* err);
91
92// Largest scatter/gather array the send path builds on the stack before a syscall. A neutral bound
93// shared by the portable gather in socket.c and each platform's vector translation; the platform
94// NetPlatIov arrays are sized to match.
95#define NET_MAX_IOV 64
96
97// Send from a scatter/gather vector on a connected (stream) socket. Returns the number of bytes the
98// OS accepted (>= 0, possibly a partial write), or -1 with *err set -- NERR_WouldBlock when the send
99// buffer is full. Never blocks. The neutral BufIov entries are translated into the platform's own
100// vector type inside the shim, so no winsock/uio type escapes cx/net/.
101intptr netSockSendv(NetSockHandle h, _In_reads_(niov) const BufIov* iov, size_t niov,
102 _Out_ NetErrorCode* err);
103
104// Send one datagram to dest. Returns bytes sent, or -1 with *err set (NERR_WouldBlock when the send
105// buffer is full). Never blocks.
106intptr netSockSendTo(NetSockHandle h, _In_reads_bytes_(len) const void* buf, size_t len,
107 _In_ const NetAddr* dest, _Out_ NetErrorCode* err);
108
109// Receive one datagram, also reporting whatever the IP layer carried with it: the local address it
110// arrived on, and its ECN codepoint. Same return convention as netSockRecvFrom().
111//
112// `info` is always written. Its `have` flags say which fields the platform actually produced --
113// nothing arrives unless netSockRecvInfo() enabled it on this socket, and a platform that cannot
114// report a field leaves its flag clear on every datagram.
115intptr netSockRecvFromEx(NetSockHandle h, _Out_writes_bytes_(len) void* buf, size_t len,
116 _Out_ NetAddr* from, _Out_ NetPktInfo* info, _Out_ NetErrorCode* err);
117
118// Send one datagram to dest, asking the IP layer for the local address it leaves from and the ECN
119// codepoint it carries. Same return convention as netSockSendTo().
120//
121// A request the platform cannot honour is dropped rather than failing the send: a datagram that
122// went out unmarked is worth far more than one that did not go out. A caller that needs to know
123// whether marking works finds out from the peer, which is what ECN validation is for.
124intptr netSockSendToEx(NetSockHandle h, _In_reads_bytes_(len) const void* buf, size_t len,
125 _In_ const NetAddr* dest, _In_opt_ const NetPktInfo* info,
126 _Out_ NetErrorCode* err);
127
128// Ask the OS to report the local address and ECN codepoint of datagrams received on this socket,
129// which is what makes netSockRecvFromEx() produce anything. Returns false if the platform cannot.
130bool netSockRecvInfo(NetSockHandle h, bool enable);
131
132// Set the don't-fragment bit on datagrams leaving this socket, so one larger than the path allows
133// is dropped rather than fragmented. That is the signal packetization layer path MTU discovery
134// measures the path with. Returns false if the platform cannot.
135bool netSockDontFragment(NetSockHandle h, bool enable);
136
137// ---------------------------------------------------------------------------------------------
138// Select set (NetSelectSet)
139//
140// The one genuinely select-specific platform primitive behind the shared NetQueueSelect. Windows
141// fd_set is an array of handles, Unix fd_set a bitmask indexed by fd; the API iterates ready
142// handles (O(n) on both) rather than querying per socket (O(n^2) on Windows), and hides the wake
143// mechanism, which has no portable shape -- a loopback UDP pair on Windows, a self-pipe/eventfd on
144// Unix. Implemented in win_net_select.c / unix_net_select.c. Opaque by design; only the platform
145// file sees the fd_set members.
146// ---------------------------------------------------------------------------------------------
147
148typedef struct NetSelectSet NetSelectSet;
149
150// Create an empty select set with its wake mechanism armed, or NULL on failure.
151// The socket's single flow -- a stream socket's, or a QUIC connection's control flow -- with a
152// reference for the caller, or NULL. Read under the socket's flowLock, which is also where the
153// field is dropped: a send resolving the flow on one thread and a teardown dropping it on another
154// must not overlap, or the send acquires a flow whose last reference has just gone.
155_Ret_maybenull_ NetFlow* _netSocketFlowRef(_In_ NetSocket* sock);
156
157_Ret_maybenull_ NetSelectSet* nselCreate(void);
158
159// Destroy a select set and NULL the handle.
160void nselDestroy(_Inout_ NetSelectSet** set);
161
162// Drop every socket from the set (but keep the wake mechanism). Called at the top of each loop
163// before re-adding the current sockets.
164void nselClear(_Inout_ NetSelectSet* set);
165
166// Add a socket to the set, watching it for readability, writability, or both.
167void nselAdd(_Inout_ NetSelectSet* set, NetSockHandle h, bool read, bool write);
168
169// Wait until at least one socket is ready, the timeout elapses, or nselWake() interrupts. The
170// timeout is in cx microseconds, which select's timeval carries exactly; timeForever blocks until
171// something happens. Returns the number of ready sockets, 0 on timeout, or -1 on error.
172int nselWait(_Inout_ NetSelectSet* set, int64 timeoutUs);
173
174// Iterate the sockets reported ready by the last nselWait(). Writes the handle and its
175// read/write readiness and returns true, or returns false when iteration is exhausted. Each ready
176// handle is reported exactly once.
177_Success_(return) bool nselNext(_Inout_ NetSelectSet* set, _Out_ NetSockHandle* h, _Out_ bool* r,
178 _Out_ bool* w);
179
180// Interrupt a blocked nselWait() from another thread. Idempotent; extra wakes coalesce.
181void nselWake(_Inout_ NetSelectSet* set);
182
183// ---------------------------------------------------------------------------------------------
184// Messages
185//
186// One NetMessage header per packet in flight, drawn from the queue's NetPool (see pool.cxh) so
187// that the steady-state datagram path allocates nothing. The kinds and flags below are internal;
188// the allocate/retire API is public on the pool object.
189//
190// The rest of the internal net API lives on the classes themselves, as underscore-prefixed private
191// methods -- see the PRIVATE IMPLEMENTATION DETAILS sections of flow.cxh, socket.cxh, and
192// queue.cxh. Only the platform shims above, the standalone helpers below, and the resolver remain
193// here.
194// ---------------------------------------------------------------------------------------------
195
196// What a NetMessage represents (stored in NetMessage.kind). Everything except NMSG_Data is
197// internal plumbing riding the flow inbox so its event lands on a worker, ordered behind the
198// packets already queued for the flow; drainFlow() translates each into a NetEvent and retires
199// the message before any handler runs. Applications only ever see an NMSG_Data message, and only
200// as the datagram container on NetEvent.recv.msg / refused.msg.
201typedef enum {
202 NMSG_Data = 0, // an ordinary packet or chunk of stream data
203 NMSG_Terminal = 1, // flow teardown marker; deliver NET_FlowClosed (cause in `reason`)
204 NMSG_SendReady = 2, // send buffer drained below the low watermark; deliver NET_SendReady
205 NMSG_Connect = 3, // connect attempt resolved; deliver NET_Connection (NetErrorCode in `bytes`)
206 NMSG_Accept = 4, // connection accepted on a listener; deliver NET_Accepted (socket in `asock`)
207 NMSG_Error = 5, // a queued send failed asynchronously; deliver NET_Error (NetErrorCode in `bytes`)
208 NMSG_FlowOpen = 6, // the flow was just created; deliver NET_FlowOpen ahead of its first packet
209 NMSG_FilterNotify = 7, // a filter raised a notification off-worker; deliver NET_FilterNotify
210 // (NetFilterNotify in `bytes`)
211 NMSG_Timer = 8 // an application timer reached its deadline; deliver NET_Timer
212 // (NetTimerId in `timerId`)
213} NetMessageKind;
214
215// Bits in NetMessage.flags. The only thing the message layer needs to remember about a payload is
216// where it has to go back to: a packet from the wire and a filter's own output both come from the
217// queue's buffer pool, while an oversized send payload is a plain heap buffer. Getting this wrong is
218// not a crash but a slow leak of pool capacity (bufDestroy on a pooled buffer shrinks the pool for
219// good), so it travels with the message rather than being inferred from which path frees it.
220typedef enum {
221 NMF_PoolBuf = 0x01 // `buf` came from the queue's receive pool; return it there
222} NetMessageFlags;
223
224// Most messages a filter chain stages are small (a handshake flight, an MTU-sized fragment), so the
225// staging queue in front of a datagram chain is bounded by count rather than bytes. A stage that
226// refuses to consume this many application messages is negotiating (or wedged); either way the
227// honest answer to the next send is the same refusal the send watermark gives.
228#define NET_FLOW_ENCQ_MAX 64
229
230// ---------------------------------------------------------------------------------------------
231// Shared inline helpers
232//
233// Small enough that the indirect call would cost more than the body, so they stay here as inlines
234// rather than becoming private methods on the classes.
235// ---------------------------------------------------------------------------------------------
236
237// The queue's message pool, or NULL when there is no queue -- which is the state of a socket that
238// was never added to one. Every NetPool entry point takes a NULL pool and falls back to the
239// heap, so paths that can run either way need no branch of their own.
240_Ret_maybenull_ _meta_inline NetPool* _netqueuePool(_In_opt_ NetQueue* q)
241{
242 return q ? q->pool : NULL;
243}
244
245// Coarse monotonic tick (~1.05s units) for flow->lastActive. 32 bits so the per-packet relaxed
246// store stays a plain atomic on x86, which has no 64-bit atomics. Direct < comparison is safe --
247// the value does not wrap until clockTimer() passes 2^52 microseconds (~143 years).
248_meta_inline uint32 _netLruTick(void)
249{
250 return (uint32)(clockTimer() >> 20);
251}
252
253// True once shutdown has begun. Ingest paths check this to stop producing new work.
254_meta_inline bool _netqueueShuttingDown(_In_ NetQueue* q)
255{
256 return atomicLoad(uint32, &q->shutdownReq, Acquire) != 0;
257}
258
259// ---------------------------------------------------------------------------------------------
260// Backend poll timing (see queue.c)
261// ---------------------------------------------------------------------------------------------
262
263// Work out how long a backend's poll call may sleep, given the wait its caller asked for. Both
264// take and return cx microseconds unless the name says otherwise: 0 means return immediately and
265// timeForever means block until something happens.
266//
267// The answer is capped to the queue's nearest armed timer deadline, so a timer fires close to when
268// it is due rather than on the next unrelated wakeup, and to a little under 24 days otherwise so
269// every backend's own timeout type can hold it. Call either one immediately before the poll, since
270// the cap is computed against the clock as of the call.
271//
272// Use this one for select and kqueue, whose timeout structs carry sub-millisecond resolution.
273int64 netqueue_pollTimeout(_In_ NetQueue* q, int64 waitUs);
274
275// Same, but in milliseconds, with -1 meaning block forever. For epoll and IOCP, which take a plain
276// millisecond count and cannot express anything finer. A nonzero wait rounds up, never down, since
277// truncating a short sleep to 0 would turn the caller's loop into a spin.
278int64 netqueue_pollTimeoutMsec(_In_ NetQueue* q, int64 waitUs);
279
280// ---------------------------------------------------------------------------------------------
281// Name resolution (see resolver.c)
282// ---------------------------------------------------------------------------------------------
283
284// Callback delivered on a resolver worker thread once getaddrinfo finishes. `addrs` is the resolved
285// list (in returned order) or NULL/empty on failure; `err` is NERR_None on success. The callback
286// must not run application code inline -- it feeds the result back into the connect state machine,
287// which delivers events through the flow.
288typedef void (*NetResolveCB)(_In_opt_ sa_NetAddr* addrs, NetErrorCode err, _In_opt_ void* ctx);
289
290// Submit an async resolution to the dedicated, bounded resolver queue (lazily created on first
291// use, capped at a few concurrent getaddrinfo calls, torn down at exit). Returns false if the
292// request could not be queued, in which case the callback will not run.
293_Check_return_ bool _netResolveSubmit(_In_opt_ strref host, uint16 port, _In_ NetResolveCB cb,
294 _In_opt_ void* ctx);
295
296// ---------------------------------------------------------------------------------------------
297// Accept (see socket.c)
298// ---------------------------------------------------------------------------------------------
299
300// Accept one pending connection off a listening socket's backlog. On success wraps the accepted OS
301// handle in a platform NetSocket (NST_Stream, NS_Connected), returns it through `*out` owning one
302// reference, fills `*peer` with the remote address, and returns NERR_None. Returns NERR_WouldBlock
303// when the backlog is drained (nothing more to accept this readiness), or another code on a real
304// error. Implemented per platform (win_net_socket.c); the readiness backend drains it in a loop.
305NetErrorCode netPlatformAccept(NetSockHandle listener, _Outptr_result_maybenull_ NetSocket** out,
306 _Out_ NetAddr* peer);
Network address structures.
System clock functions.
NetAddrType
Network address types.
Definition net_shared.h:269
intptr NetSockHandle
Platform-neutral OS socket handle: a Windows SOCKET or a Unix file descriptor.
Definition net_shared.h:17
NetErrorCode
Network error codes.
Definition net_shared.h:243
NetSocketType
Socket types.
Definition net_shared.h:134
int64 clockTimer()
Thread-safe lazy initialization.
State tracker for lazy initialization.
Definition lazyinit.h:40
A single ordering domain: one connection, or one datagram peer.
Definition flow.h:116
Per-datagram information the IP layer carries alongside the payload.
Definition net_shared.h:328
Shared, capped pool of network message buffers and headers.
Definition pool.h:51
NetQueue manages one or more sockets and a thread pool of workers.
Definition queue.h:79