1#include <cx/net/queue.cxh>
2#include <cx/net/filter.cxh>
3#include <cx/net/net_shared.h>
4#include <cx/buffer/bufchain.h>
5#include <cx/buffer/bufring.h>
10/// @addtogroup net_socket
12/// Class that encapsulates a network socket.
14/// Callback for receiving messages from a socket.
16/// The callback receives ownership of the buffer in the NetMessage. To keep the buffer,
17/// set msg->buf to NULL before returning; otherwise it will be automatically destroyed.
19/// @param sock Socket the message was received from
20/// @param msg Message containing data buffer and source address (for datagrams)
21/// @param ctx User-defined context pointer
22/// @return true to continue receiving more messages, false to stop
23typedef bool (*socketRecvCB)(NetSocket* sock, NetMessage* msg, void* ctx);
26/// This is a base class; the actual socket implemention will be a derived class provided
27/// by the OS abstraction layer.
28abstract class NetSocket
30 weak[NetQueue] queue; ///< NetQueue this socket is registered to (may be NULL)
31 NetSocketType type; ///< Connection or connectionless, immutable
33 /// @brief Platform-neutral OS handle, set by the platform factory
35 /// Lets shared code in cx/net/ read a socket's handle to add it to a select set or issue a
36 /// recv without knowing the derived platform type. NET_INVALID_HANDLE until the factory
38 // Stays open until the socket is destroyed, not just closed, except on a listener: anything that
39 // can use the handle holds a reference, so the number cannot be handed to another socket while
40 // it still might. See the platform socket's close().
43 atomic[uint32] state; ///< See NetSocketState enum
44 atomic[bool] canSend; ///< Send buffer empty; can send immediately
46 // An accepted socket the application has not been handed yet. While it is set, no worker
47 // delivers anything of this socket's to the application: NET_Accepted travels on the
48 // *listener's* flow, so nothing orders it against events on the accepted socket's own flows,
49 // and a QUIC connection has streams opening on flows of their own the moment its handshake
50 // finishes. Without this, the NET_FlowOpen for a peer's first stream can be delivered before
51 // the application has seen the connection, and it goes to the handlers the socket inherited
52 // from the listener rather than the ones the application installs when it does. Cleared once
53 // NET_Accepted has been delivered, which is also when those flows are put back on the
56 // A QUIC connection's control flow still runs while this is set, because it is what answers
57 // the handshake that raises the accept -- but only as far as its first application-facing
58 // message, which waits like everything else. See drainFlow().
59 atomic[uint32] awaitingAccept;
61 /// @brief Local end, filled in by bind()
63 /// Read back from the socket rather than copied from what was asked for, so a socket bound to
64 /// port 0 reports the port the OS actually chose.
66 NetAddr remote; ///< Remote address (for connected sockets only)
68 // There is deliberately no per-socket `user` array. flow->user needs no locking because the
69 // flow is the ordering domain -- only one worker is ever inside it at a time. A socket has
70 // no such guarantee: a single datagram socket can have thousands of flows running on every
71 // worker at once, so an array here would need its own synchronization. Per-socket state
72 // should go in the ctx captured at handler registration instead.
74 uint32 mru; ///< Maximum size of received datagrams
76 /// @brief The OS is reporting per-datagram IP information; see setRecvInfo()
78 /// Datagram sockets only. While set, the ingest path asks the OS for the local address and ECN
79 /// mark of every datagram and delivers them on NetMessage::info.
82 /// @brief Type-specific send/receive buffers; which arm is live depends on `type`
84 /// A stream socket has a receive ring and a send chain; a datagram socket has neither and
85 /// just a send queue (its packets are pooled buffers on flow inboxes instead). See
86 /// NetSocketBufs -- constructed and torn down by hand in NetSocket_init() / _destroy(),
87 /// since generated code cannot see into a union.
88 [noinit] NetSocketBufs bufs;
90 Mutex recvLock; ///< Exclusive access to the socket's receive buffer
91 Mutex sendLock; ///< Exclusive access to send data on this socket
93 /// @brief Send fails over this many bytes queued
95 /// On a QUIC socket this is per stream rather than per socket: how much one stream will hold
96 /// for the application before netflowSend() starts refusing.
99 /// @brief NET_SendReady fires when the queue drains below this
101 /// On a QUIC socket this reads the other way round, since a stream's backlog is bounded by
102 /// what the peer allows rather than by the queue: NET_SendReady fires once the stream has this
103 /// many bytes of room. A refused send always sets the bar at least as high as what it asked
104 /// for, so this only matters when it asked for less.
107 /// @brief Bytes waiting in the datagram send queue, guarded by sendLock
109 /// Unused for stream sockets, which track their backlog on the send chain itself.
112 /// @brief A send returned false at the high watermark and is waiting for NET_SendReady
114 /// Set when netsocketSend() refuses because the backlog is over sendHigh, cleared once the
115 /// queue drains back below sendLow and the event fires. Guarded by sendLock.
118 /// @brief A completion backend has an overlapped send in flight for this socket
120 /// Set by the IOCP backend while a WSASend/WSASendTo it posted is outstanding, cleared when
121 /// that completion arrives. While set, the normal synchronous flush is suppressed so it can't
122 /// touch bytes the in-flight send already owns -- doing so could send the same bytes twice
123 /// and corrupt the connection. Always false on readiness backends. Guarded by sendLock.
126 /// @brief Per-socket handler overrides, optional
128 /// Resolved per field between the flow's set and the queue-wide set; see @ref net_handlers.
129 NetHandlers *handlers;
130 void *handlerCtx; ///< Context passed to per-socket handlers, set by setHandlers()
132 /// @brief Context passed to per-socket handlers, set by setHandlersObj() -- NULL when
133 /// handlerCtx is in use instead
134 weak[ObjInst] handlerWeak;
136 /// @brief Guards handlers/handlerCtx/handlerWeak against a concurrent setHandlers()/setHandlersObj()
139 /// @brief Flows keyed by peer address (datagram) or stream id (QUIC). Not used by stream
142 /// Holds strong references -- the socket owns its flows, and the flow's back-pointer to the
143 /// socket is weak to avoid a reference cycle. Which key a socket's table uses is fixed by
144 /// `type`, so NetFlow::key is only meaningful on a QUIC socket's flows.
145 [noinit] hashtable flows;
147 RWLock flowLock; ///< Protects the datagram flow table
149 /// @brief Stream: the single flow for this connection. QUIC: the connection control flow.
150 /// Not used by datagram sockets.
152 /// Created automatically during socket init, so stream consumers never construct a flow
153 /// themselves. A QUIC socket has this *and* a flow table: connection-level work -- packet
154 /// decryption, ACK generation, loss recovery, the handshake -- is ordered on this one flow,
155 /// while each of its streams gets a flow of its own that can run on another worker.
156 object[NetFlow] flow;
158 // Hook that takes arriving datagrams instead of the flow table, or NULL for the normal path.
159 // Installed by cxquic on the UDP endpoint socket behind a QUIC connection or listener; nothing
160 // else installs one. See NetDatagramRouteFn and _setRoute().
162 // Written only under flowLock, which the ingest path holds just long enough to copy the hook
163 // and turn the weak context into a reference. `route` alone is also read without it, as a hint
164 // of whether to take the lock at all.
165 NetDatagramRouteFn route;
166 weak[ObjInst] routeCtx;
167 // Who installed the hook, compared and never dereferenced -- the weak reference above cannot
168 // answer that once its object is being destroyed, which is one of the times it is asked.
171 /// @brief Filters attached to this socket, ordered application -> wire, or empty for none
173 /// These are factories, not data-plane stages: each one creates a NetFlowFilter for every flow
174 /// the socket owns, and the transform itself happens on the flow. See @ref net_filter.
175 [noinit] sarray[object[NetFilter]] filters;
177 /// @brief Per-socket address-family preference override; NCP_Default inherits the queue's
178 /// setting when the socket is added (see NetQueue::connectPref)
179 NetConnectPref connectPref;
181 /// @brief List of fallback addresses to try to connect to if pending connect fails
183 /// Filled in by the resolver, then reordered by family per connectPref. The connect state
184 /// machine tries each address in turn, falling through to the next entry whenever an attempt
185 /// fails or times out.
186 [noinit] sarray[NetAddr] connQueue;
188 int32 connectIdx; ///< Index of the next connQueue address to try
190 /// @brief Timer arming the in-flight connect attempt's deadline, or 0 when none is armed
192 /// Three things can complete one connect attempt -- the backend, netsocketClose(), and the
193 /// attempt timing out -- and cancelling this timer is what arbitrates between them. A cancel
194 /// succeeds only if the timer had not already been popped for delivery, so exactly one of the
195 /// three wins and the losers see either a zeroed field or a failed cancel. Set when an attempt
196 /// begins, cleared when it resolves.
197 NetTimerId connectTimer;
199 /// @brief Attempt generation, bumped once per connect attempt
201 /// Lets a completion whose attempt already timed out and was superseded recognize that and
202 /// just clean itself up instead of acting on a stale result.
203 atomic[uint32] connectGen;
205 /// @brief Serializes the connect-advance transition; see connectTimer
207 /// Lock order is connectLock -> the queue's timerLock, never the reverse.
210 /// Read buffered stream data out of the socket receive buffer
212 /// This does not actively try to receive; it only drains bytes already buffered in the
213 /// socket's receive ring by the queue's ingest thread (or by tick() in polled mode). With a
214 /// filter attached it drains decoded application bytes from the end of the flow's filter chain
215 /// instead -- the raw wire bytes in the receive ring were already consumed by the decode pass
216 /// that ran before NET_DataReceived was delivered.
218 /// @note Stream sockets only. A datagram is delivered whole on the NET_DataReceived event as
219 /// `event->recv.msg` instead of being buffered here. Calling recv() on a datagram socket
222 /// @param buf Buffer to store received data
223 /// @param bufsz Size of the buffer
224 /// @param src Unused; retained for signature compatibility. Datagram source addresses arrive
225 /// on the event, not here.
226 /// @param flags Optional flags to control receive behavior
227 /// @return Amount of data actually received (0 on a datagram socket, or when the ring is empty)
228 size_t recv([out] uint8* buf, size_t bufsz, [out] [opt] NetAddr* src, flags_t flags);
230 /// Drain buffered stream data as a series of zero-copy messages
232 /// Invokes the callback for each chunk of data available in the receive ring, repeatedly,
233 /// until it returns false or the ring is exhausted. More efficient than recv() when draining
234 /// a lot of data, since it hands out the ring's own segments instead of copying into a
235 /// caller-supplied buffer.
237 /// @note Stream sockets only, for the same reason as recv(): datagrams arrive on the event
238 /// with their buffer attached, so there's nothing here to drain. Returns false on a datagram
241 /// The callback receives ownership of the buffer in the NetMessage. It may keep the buffer by
242 /// setting msg->buf to NULL, otherwise the buffer is automatically destroyed after the
243 /// callback returns.
245 /// @param cb Callback invoked for each message (return false to stop)
246 /// @param ctx User-defined context pointer passed to callback
247 /// @return true if at least one message was processed, false if the ring was empty or the
248 /// socket is a datagram socket
249 bool recvMsgs(socketRecvCB cb, [in] [opt] void* ctx);
251 /// Send data on the socket
253 /// Data the OS won't accept immediately is queued and flushed later when the socket becomes
254 /// writable. The same logic is shared by both socket types: a stream socket copies the data
255 /// into its outbound chain and sends it with scatter/gather; a datagram socket queues whole
256 /// messages, each with its own destination.
258 /// @note Backpressure: once more than `sendHigh` bytes are already queued, the call returns
259 /// false and queues nothing; back off until NET_SendReady fires, which happens once the
260 /// backlog drains below `sendLow`. `NSO_Immediate` never queues -- it sends what the OS takes
261 /// right now and returns true only if the whole payload went out.
263 /// For connected (stream) sockets, dest is ignored. For connectionless (datagram) sockets, dest
264 /// must be a valid address.
266 /// @note Filters: with a filter attached the payload is run through the destination flow's
267 /// filter chain on its way out, so what reaches the wire is whatever the chain produced -- which
268 /// may be nothing at all if a stage is still negotiating and chose to buffer the payload. A
269 /// datagram sent to a peer with no flow yet opens one (firing NET_FlowOpen) so the chain exists
270 /// to encode it; if the queue is at its flow cap the send is refused. Either way the call
271 /// returns true when the payload was accepted, not when bytes reached the wire. `NSO_Immediate`
272 /// is ignored on a filtered socket -- the filter owns the framing, and a payload jumping the
273 /// chain would land in the middle of whatever the chain is producing.
275 /// @param data Pointer to data to send (copied; need not outlive the call)
276 /// @param len Length of data in bytes
277 /// @param dest Destination address (required for connectionless, ignored for connected)
278 /// @param flags Optional flags to control send behavior
279 /// @return true if the data was sent or queued, false if it was refused (over the high watermark,
280 /// not connected, or a fatal socket error)
281 [extern] bool send([in] const uint8* data, size_t len, [in] [opt] const NetAddr* dest,
284 /// Send a datagram, asking the IP layer for a specific local address and ECN mark
286 /// Everything send() does, plus the two things a plain send cannot express: which of this
287 /// machine's addresses the datagram leaves from, and the ECN codepoint it carries. Both are
288 /// requests -- a platform that cannot honour one sends the datagram without it rather than
289 /// failing, so a caller that needs to know whether the mark survived has to ask the peer.
291 /// Datagram sockets only, and unfiltered ones: a filter chain owns what reaches the wire, so
292 /// there is nothing here for one to attach per-datagram information to.
294 /// @param data Pointer to data to send (copied; need not outlive the call)
295 /// @param len Length of data in bytes
296 /// @param dest Destination address
297 /// @param info Local address and ECN mark to ask for, or NULL for neither
298 /// @param flags Optional flags to control send behavior
299 /// @return true if the data was sent or queued, false if it was refused
300 [extern] unbound bool sendEx([in] const uint8* data, size_t len, [in] const NetAddr* dest,
301 [in] [opt] const NetPktInfo* info, flags_t flags);
303 /// Ask the OS to report the local address and ECN mark of each received datagram
305 /// While enabled, every NetMessage delivered from this socket carries them on NetMessage::info.
306 /// Call it after bind(): the options are per address family, and a socket has no family until
309 /// @param enable true to start reporting, false to stop
310 /// @return true if the platform can report them, false if it cannot -- in which case
311 /// NetMessage::info stays empty and a caller that needed ECN should not mark its sends
312 unbound bool setRecvInfo(bool enable);
314 /// Set the don't-fragment bit on datagrams leaving this socket
316 /// A datagram larger than the path allows is then dropped instead of being fragmented, which
317 /// is what turns an oversized send into a measurement rather than a slow success. Call it after
318 /// bind(), for the same reason as setRecvInfo().
320 /// @param enable true to set the bit, false to leave fragmentation to the OS
321 /// @return true if the platform could set it
322 unbound bool setDontFragment(bool enable);
324 /// Open an outbound connection to a host and port
326 /// Stream sockets only. This call is asynchronous: it returns immediately after starting the
327 /// process, and the result arrives later as a NET_Connection event on the socket's flow. The
328 /// host is resolved on a dedicated resolver thread, never on a net I/O thread, and the socket
329 /// passes through NS_Resolving and NS_Connecting on the way. Each resolved address is tried
330 /// in turn with a fresh OS handle of the right family, falling through on failure or timeout,
331 /// until one connects or the list is exhausted.
333 /// The socket must already be registered with a queue (that is where the resolver, backend,
334 /// and flow live). A literal address is accepted directly without a DNS lookup.
336 /// @param host Hostname or literal address to connect to (NULL/empty means loopback)
337 /// @param port Port number, host byte order
338 /// @return true if the connect was started, false if it could not be (wrong socket type, no
339 /// queue, or already connecting/connected)
340 [extern] bool connect([in] strref host, uint16 port);
342 [abstract] bool bind(const NetAddr* addr);
344 // backlog 0 means use a platform-specific default
345 [abstract] bool listen(int backlog);
349 /// Will also remove from a NetQueue if it is registered to one. The socket cannot
350 /// be reused once closed.
352 /// A connection is shut down at once, but its OS handle -- and the local address it is bound
353 /// to -- is only released when the last reference to the socket is. A listening socket is the
354 /// exception and releases its handle immediately, so its address can be listened on again.
356 /// @return true if the socket was successfully disconnected, false otherwise.
359 /// Register per-socket handler overrides
361 /// Fields left NULL fall through to the queue-wide set. The handler struct is not copied --
362 /// it must outlive the socket, which is why it is usually `static const`.
364 /// @param handlers Handler set, or NULL to clear
365 /// @param ctx Context passed to these handlers on NetEvent.ctx
366 unbound void setHandlers([in] [opt] const NetHandlers *handlers, [in] [opt] void *ctx);
368 /// Register per-socket handler overrides, with an object as the context
370 /// Same as setHandlers(), except ctx is held weakly rather than borrowed.
372 /// @param handlers Handler set, or NULL to clear
373 /// @param ctx Object passed to these handlers on NetEvent.ctx, held weakly; NULL to clear
374 unbound void setHandlersObj([in] [opt] const NetHandlers *handlers, [in] [opt] ObjInst *ctx);
376 /// Attach a filter to the socket, applying it to every flow
378 /// The filter is appended to the socket's list, so the first one attached is the stage closest
379 /// to the application and the last is the one nearest the wire. A per-flow NetFlowFilter is
380 /// created immediately for every flow the socket already has -- the single flow of a stream
381 /// socket, or every peer flow of a datagram socket -- and for every flow it opens afterwards.
383 /// The socket acquires its own reference, so the caller keeps theirs and should release it when
384 /// done; the same filter may be attached to any number of sockets, which is how one filter
385 /// holding a certificate and key serves every accepted connection on a server.
387 /// A filter attached to a **listening** socket is inherited by every connection it accepts, and
388 /// is installed on the accepted socket before that socket becomes reachable -- so no byte can
389 /// arrive on a new connection ahead of its chain. That is the way to secure a server: attach
390 /// once to the listener, not per connection from the NET_Accepted handler, which races the
391 /// worker already servicing the new socket.
393 /// Install filters before data starts moving -- right after creating the socket, and before
394 /// connect or listen.
396 /// @param filter Filter to attach
397 /// @return true if the filter was attached, false if it declined this socket type (see
398 /// netfilterCanFilter) or the socket is closed
399 unbound bool addFilter([in] NetFilter *filter);
401 /// Detach and release every filter on the socket
403 /// Drops the socket's filter list and tears down the corresponding chain on every flow it owns,
404 /// discarding anything those stages still had buffered. After this the socket's send/recv paths
405 /// are back on the unfiltered fast path.
406 unbound void removeFilters();
408 // -----------------------------------------------------------------------------------------
409 // PRIVATE IMPLEMENTATION DETAILS
411 // Everything below this point is internal plumbing between the net module's own translation
412 // units and the platform backends. It is not a stable API and carries no compatibility
413 // promise -- signatures and semantics change whenever the implementation needs them to.
414 // -----------------------------------------------------------------------------------------
416 // Send path (dataplane.c)
418 // The send logic is backend-independent and lives on the base socket; only the syscall differs
419 // and it is hidden behind netSockSendv/netSockSendTo. A readiness backend watches sockets that
420 // have queued data for write-readiness and flushes them from its ingest loop.
422 // True when the socket has outbound data queued and should be watched for write-readiness. A
423 // hint read without the send lock -- a stale value costs at most one empty flush or one poll
425 [extern] unbound bool _wantWrite();
427 // Flush as much queued outbound data as the OS will currently accept, updating canSend and
428 // firing NET_SendReady on the flow when the backlog drains back below the low watermark.
429 // Called by the backend on write-readiness; `q` may be NULL if the socket has no queue
430 // (nothing to deliver on).
431 [extern] unbound void _flushSend([in] [opt] NetQueue *q);
433 // Report a fatal error from the async send path: the call that queued the bytes already
434 // returned success, so an event is the only way the application can hear about the loss.
435 // Delivers NET_Error through the affected flow like everything else -- on a worker, ordered. A
436 // stream's byte stream is broken by bytes vanishing from the middle of it, so its flow is
437 // closed behind the event: NET_Error lands first carrying the code, and NET_FlowClosed
438 // (NCR_Error) follows. A datagram error cost only the one datagram, so the peer's flow (`peer`
439 // is the failed datagram's destination; ignored for streams) hears NET_Error and stays open --
440 // and since sending alone never creates a flow, an error for a peer that has no flow has
441 // nobody to tell and is dropped. Do not call with sendLock held.
442 [extern] unbound void _sendError([in] [opt] NetQueue *q, NetErrorCode err, [in] NetAddr *peer);
444 // Connect (connect.c)
446 // Report the outcome of the in-flight connect attempt and advance the state machine: on
447 // success the socket goes NS_Connected and its NET_Connection event is queued; on failure the
448 // next resolved address is tried, or a failing NET_Connection is delivered when the list is
449 // exhausted. Claims the per-attempt transition by cancelling connectTimer, so the backend
450 // completion and the attempt's own timeout cannot both advance it. Safe to call from either;
451 // the loser is a no-op.
452 [extern] unbound void _connectResult(NetErrorCode err);
454 // Shared readiness-backend connectBegin body: reset the handle to the address's family, issue
455 // a non-blocking connect(), and drive the immediate outcome through netsocket_connectResult().
456 // A pending connect is left for the select loop to complete on writability/except. Returns
457 // true once the attempt has been initiated (the state machine has been advanced or is now
459 [extern] unbound bool _readinessConnect([inout] NetQueue *q, [in] const NetAddr *addr);
461 // Abort an in-flight connect when the socket is being closed. Claims the current attempt the
462 // same way the backend completion and the timeout do; if won, drops the connect's resources
463 // without starting a new attempt or delivering an event. A no-op if there is no armed attempt
464 // -- either nothing is connecting, or the completion/timeout already finished it, or the
465 // connect is still resolving (that window is handled in the resolver callback, which checks
467 [extern] unbound void _connectCancel();
469 // Accept and listen (socket.c)
471 // Deliver a freshly accepted connection. `newSock` arrives owning one reference (the platform
472 // factory's), which this call takes: with NQ_AutoAccept the socket is first added to the
473 // listener's queue (which acquires its own reference), then an NMSG_Accept carrying the socket
474 // is queued on the listener's flow so NET_Accepted runs on a worker, ordered. If the listener
475 // has no queue or flow to deliver on, the socket is dropped (released, closing its handle).
476 // Called by both backends once they have pulled a connection off the backlog and wrapped it in
477 // a platform NetSocket. `peer` is the remote address, or NULL if it could not be determined.
478 unbound void _accepted([inout] NetSocket *newSock, [in] [opt] const NetAddr *peer);
480 // Kick the backend into servicing a socket that has just started listening. Resolves the
481 // socket's queue and calls its acceptArm hook (select wakes its loop to read-watch the
482 // listener; IOCP posts the initial AcceptEx batch). A no-op if the socket is not yet on a
483 // queue -- the backend's addSocket picks up an already-listening socket instead. Called by the
484 // platform listen() once the socket is in NS_Listening.
485 unbound void _listenArm();
487 // Install a datagram route hook, replacing whatever was installed before. `ctx` is held weakly
488 // and passed to the hook as a reference for the length of each call, so a datagram that
489 // arrives after it is gone is dropped instead of routed to freed memory.
490 unbound void _setRoute([in] NetDatagramRouteFn fn, [in] ObjInst *ctx);
492 // Remove the route hook, but only if `owner` is what installed it. Returns true if it did.
493 // Safe to call from `owner`'s destroy, when it can no longer be referenced.
494 unbound bool _clearRoute([in] ObjInst *owner);
496 // Flow table (flow.c)
498 // Remove a flow from this socket's table, releasing the socket's reference. Called once the
499 // terminal event has been delivered.
500 [extern] unbound void _dropFlow([inout] NetFlow *flow);
502 // Close every flow on this socket with the given reason.
503 [extern] unbound void _closeFlows(NetCloseReason reason);