CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
Filters

Data Structures

struct  NetFilter
 Socket-level factory for per-flow filters. More...
 
struct  NetFlowFilter
 Shared lifecycle base for all flow filters. More...
 
struct  NetStreamFilter
 Base for byte-stream filters (attached between the application and a stream socket) More...
 
struct  NetDatagramFilter
 Base for datagram filters (attached between the application and a datagram flow) More...
 

Macros

#define netfilterCreateFlow(self, type)   (self)->_->createFlow(NetFilter(self), type)
 
#define netfilterCanFilter(self, type)   (self)->_->canFilter(NetFilter(self), type)
 
#define netflowfilterNotify(self, note)   NetFlowFilter_notify(NetFlowFilter(self), note)
 
#define netflowfilterShutdown(self)   (self)->_->shutdown(NetFlowFilter(self))
 
#define netstreamfilterNotify(self, note)   NetFlowFilter_notify(NetFlowFilter(self), note)
 
#define netstreamfilterShutdown(self)   (self)->_->shutdown(NetStreamFilter(self))
 
#define netstreamfilterEncode(self, src)   (self)->_->encode(NetStreamFilter(self), src)
 
#define netstreamfilterDecode(self, src)   (self)->_->decode(NetStreamFilter(self), src)
 
#define netdatagramfilterNotify(self, note)   NetFlowFilter_notify(NetFlowFilter(self), note)
 
#define netdatagramfilterShutdown(self)   (self)->_->shutdown(NetDatagramFilter(self))
 
#define netdatagramfilterEncodeMsg(self, src)   (self)->_->encodeMsg(NetDatagramFilter(self), src)
 
#define netdatagramfilterDecodeMsg(self, src)   (self)->_->decodeMsg(NetDatagramFilter(self), src)
 

Typedefs

typedef struct NetFilter NetFilter
 Socket-level factory for per-flow filters.
 
typedef struct NetFlowFilter NetFlowFilter
 Shared lifecycle base for all flow filters.
 
typedef struct NetStreamFilter NetStreamFilter
 Base for byte-stream filters (attached between the application and a stream socket)
 
typedef struct NetDatagramFilter NetDatagramFilter
 Base for datagram filters (attached between the application and a datagram flow)
 

Detailed Description

Chainable interception hooks that transform data on its way to and from the wire.

The filter system has two levels. A NetFilter is a factory attached to a NetSocket; it creates one NetFlowFilter per flow the socket owns (one for a stream socket, one per peer for a datagram socket). The NetFlowFilter instances sit on the flow and do the actual byte or message transformation. The typical use is a TLS/DTLS session, but the same mechanism works for compression, framing, or logging.

Mental model

Attach a NetFilter to a socket and it handles the rest: it creates a NetFlowFilter for every flow already open, and the socket calls it again each time a new flow opens. Applications never create or manage flow filters directly. A listening socket passes its filters on to every connection it accepts, before that connection becomes reachable, so one filter attached to a listener covers the whole server.

Both levels are plain arrays, ordered from the application toward the wire:

app <-> sock->filters.a[0] <-> ... <-> sock->filters.a[N-1] <-> wire (factories)
app <-> flow->filters.a[0] <-> ... <-> flow->filters.a[N-1] <-> wire (per-flow stages)

Element 0 is the stage closest to the application; the last element touches the wire. The common case is a chain of length one. A NetFilter holds no per-socket state of its own, so the same instance can be attached to many sockets – for example, one filter holding a TLS certificate and key, shared by every connection a server accepts.

Each stage owns its own state – a TLS session lives on the filter object, never on flow->user, which stays entirely the application's.

Lifecycle

There is no separate handshake phase. A filter that needs to negotiate (a TLS session, say) does it entirely inside encode()/decode() (or encodeMsg()/decodeMsg() for a datagram filter): it withholds application-side output until its own logic decides the channel is ready, and may produce wire output at any time, even from empty input. That's what the priming pass is for: the driver calls encode() once with nothing queued, so a filter gets a chance to open a negotiation it initiates. A datagram flow is primed as soon as its chain is built; a stream flow is primed once the transport comes up.

A stage need not consume everything it's handed – declining to consume application input is how a filter mid-handshake blocks app data from reaching the wire; no separate gate is needed. A negative return from encode()/decode() signals a fatal failure. shutdown() begins an orderly close and always runs before a filter is freed, regardless of what phase it was in.

A stage is never re-entered concurrently: decode() runs on the worker holding the flow's claim, and encode() may be driven from any thread that calls netsocketSend(), but the flow serializes every driver pass, so a filter needs no synchronization of its own.

Out-of-band notifications

The framework never infers application-visible events from a filter's behavior. When a filter reaches a milestone the application should know about – a TLS session becoming ready, say – it calls netflowfilterNotify() with a NetFilterNotify code, and the driver delivers it as a NET_FilterNotify event. Only a filter with such a milestone needs to call it; a transform like compression or framing simply never does.

Macro Definition Documentation

◆ netdatagramfilterDecodeMsg

#define netdatagramfilterDecodeMsg (   self,
  src 
)    (self)->_->decodeMsg(NetDatagramFilter(self), src)

intptr netdatagramfilterDecodeMsg(NetDatagramFilter* self, NetMsgQueue* src);

Consume from src and append transformed output toward the app into decOut.

A filter still negotiating consumes its handshake records from src here (a reply may be allocated from pool and pushed onto this stage's own encOut as a side effect) while producing nothing into decOut until it decides the channel is ready – typically the same point it calls notify(NFN_Secured). A stage that reassembles several wire fragments into one application message consumes several from src and produces one into decOut; the reverse is equally valid.

Parameters
srcInput queue to consume from (the receive queue, or the next stage's decOut)
Returns
Messages produced into decOut, or negative on a fatal transform error.

Definition at line 496 of file filter.h.

◆ netdatagramfilterEncodeMsg

#define netdatagramfilterEncodeMsg (   self,
  src 
)    (self)->_->encodeMsg(NetDatagramFilter(self), src)

intptr netdatagramfilterEncodeMsg(NetDatagramFilter* self, NetMsgQueue* src);

Consume from src and append transformed output toward the wire into encOut.

This is also where a filter drives any handshake it needs: it may append to encOut regardless of what (if anything) it consumes from src, which is how a client emits its first handshake flight – the driver calls this once right after the flow opens even with src empty, purely to give the filter the opportunity. Fragmenting one oversized message into several wire-sized ones is just appending more than one message to encOut for the one consumed from src. A filter still negotiating declines to consume application messages from src until its own logic decides the channel is ready.

Allocate any message appended to encOut (a handshake flight, or a fragment) through netpoolAllocMsg() on this stage's own pool.

Parameters
srcInput queue to consume from (the app-submitted queue, or the previous stage's encOut)
Returns
Messages produced into encOut, or negative on a fatal transform error.

Definition at line 482 of file filter.h.

◆ netdatagramfilterNotify

#define netdatagramfilterNotify (   self,
  note 
)    NetFlowFilter_notify(NetFlowFilter(self), note)

void netdatagramfilterNotify(NetDatagramFilter* self, NetFilterNotify note);

Raise an out-of-band notification for the application, delivered by the data-plane driver.

Call this from encode()/decode() (or encodeMsg()/decodeMsg()) when the filter reaches a milestone the application should hear about – most commonly NFN_Secured, once a handshake decides the channel is ready. The driver delivers it as a NET_FilterNotify event right after the filter returns, ahead of any application data produced on the same pass. A transform with no such milestone (compression, framing) never needs to call this.

Parameters
noteThe NetFilterNotify code to deliver (NFN_Secured, or a custom NFN_AppCustom+).

Definition at line 451 of file filter.h.

◆ netdatagramfilterShutdown

#define netdatagramfilterShutdown (   self)    (self)->_->shutdown(NetDatagramFilter(self))

void netdatagramfilterShutdown(NetDatagramFilter* self);

Begin an orderly close of the filter (for TLS, emit close_notify).

Called once, before the filter is freed, regardless of what phase it was in – so any close record it needs to send is produced while the wire is still available. Whatever this pushes into the boundary buffers is drained by one more encode()/encodeMsg() pass before the filter is destroyed.

Definition at line 461 of file filter.h.

◆ netfilterCanFilter

#define netfilterCanFilter (   self,
  type 
)    (self)->_->canFilter(NetFilter(self), type)

bool netfilterCanFilter(NetFilter* self, NetSocketType type);

Reports whether this NetFilter supports attaching to a socket of the given type.

Checked once, when the filter is attached to a socket – before any flow exists and before createFlow() is ever called for it.

Parameters
typeThe socket type the filter would be attached to (NST_Stream or NST_Datagram).
Returns
true if this filter can be attached to a socket of type.

Definition at line 235 of file filter.h.

◆ netfilterCreateFlow

#define netfilterCreateFlow (   self,
  type 
)    (self)->_->createFlow(NetFilter(self), type)

NetFlowFilter* netfilterCreateFlow(NetFilter* self, NetSocketType type);

Create a per-flow filter instance for a flow of the given socket type.

Called once per flow: immediately for every flow already open when this NetFilter is attached, and again each time the socket creates a new flow afterward. The returned NetFlowFilter takes this NetFilter's position in the flow's chain, so the flow's stages end up in the same application-to-wire order as the socket's filters. Use type to choose between building a NetStreamFilter or a NetDatagramFilter if this filter supports both; a single-type filter can ignore it. Keep a reference to this NetFilter (self) on the created flow filter if it needs shared, socket-wide configuration such as a TLS certificate and key.

Parameters
typeThe socket type the new flow belongs to (NST_Stream or NST_Datagram).
Returns
A new NetFlowFilter to attach to the flow, or NULL to leave the flow unfiltered.

Definition at line 225 of file filter.h.

◆ netflowfilterNotify

#define netflowfilterNotify (   self,
  note 
)    NetFlowFilter_notify(NetFlowFilter(self), note)

void netflowfilterNotify(NetFlowFilter* self, NetFilterNotify note);

Raise an out-of-band notification for the application, delivered by the data-plane driver.

Call this from encode()/decode() (or encodeMsg()/decodeMsg()) when the filter reaches a milestone the application should hear about – most commonly NFN_Secured, once a handshake decides the channel is ready. The driver delivers it as a NET_FilterNotify event right after the filter returns, ahead of any application data produced on the same pass. A transform with no such milestone (compression, framing) never needs to call this.

Parameters
noteThe NetFilterNotify code to deliver (NFN_Secured, or a custom NFN_AppCustom+).

Definition at line 285 of file filter.h.

◆ netflowfilterShutdown

#define netflowfilterShutdown (   self)    (self)->_->shutdown(NetFlowFilter(self))

void netflowfilterShutdown(NetFlowFilter* self);

Begin an orderly close of the filter (for TLS, emit close_notify).

Called once, before the filter is freed, regardless of what phase it was in – so any close record it needs to send is produced while the wire is still available. Whatever this pushes into the boundary buffers is drained by one more encode()/encodeMsg() pass before the filter is destroyed.

Definition at line 295 of file filter.h.

◆ netstreamfilterDecode

#define netstreamfilterDecode (   self,
  src 
)    (self)->_->decode(NetStreamFilter(self), src)

intptr netstreamfilterDecode(NetStreamFilter* self, BufRing* src);

Consume from src and append transformed output toward the app into decOut.

A filter still negotiating consumes its handshake records from src here (a response may be pushed onto its own encOut as a side effect) while producing nothing into decOut until it decides the channel is ready – typically the same point it calls notify(NFN_Secured).

Parameters
srcInput ring to consume from (the receive ring, or the next stage's decOut)
Returns
Bytes produced into decOut, or negative on a fatal transform error.

Definition at line 390 of file filter.h.

◆ netstreamfilterEncode

#define netstreamfilterEncode (   self,
  src 
)    (self)->_->encode(NetStreamFilter(self), src)

intptr netstreamfilterEncode(NetStreamFilter* self, BufRing* src);

Consume from src and append transformed output toward the wire into encOut.

This is also where a filter drives any handshake it needs: it may append to encOut regardless of what (if anything) it consumes from src, which is how a client emits its first handshake flight – the driver calls this once right after connect even with src empty, purely to give the filter the opportunity. A stage need not consume all of src; unconsumed bytes stay in the boundary ring and resume on the next pass. A filter still negotiating simply declines to consume application data until its own logic decides the channel is ready.

Parameters
srcInput ring to consume from (the staging ring, or the previous stage's encOut)
Returns
Bytes produced into encOut, or negative on a fatal transform error.

Definition at line 378 of file filter.h.

◆ netstreamfilterNotify

#define netstreamfilterNotify (   self,
  note 
)    NetFlowFilter_notify(NetFlowFilter(self), note)

void netstreamfilterNotify(NetStreamFilter* self, NetFilterNotify note);

Raise an out-of-band notification for the application, delivered by the data-plane driver.

Call this from encode()/decode() (or encodeMsg()/decodeMsg()) when the filter reaches a milestone the application should hear about – most commonly NFN_Secured, once a handshake decides the channel is ready. The driver delivers it as a NET_FilterNotify event right after the filter returns, ahead of any application data produced on the same pass. A transform with no such milestone (compression, framing) never needs to call this.

Parameters
noteThe NetFilterNotify code to deliver (NFN_Secured, or a custom NFN_AppCustom+).

Definition at line 350 of file filter.h.

◆ netstreamfilterShutdown

#define netstreamfilterShutdown (   self)    (self)->_->shutdown(NetStreamFilter(self))

void netstreamfilterShutdown(NetStreamFilter* self);

Begin an orderly close of the filter (for TLS, emit close_notify).

Called once, before the filter is freed, regardless of what phase it was in – so any close record it needs to send is produced while the wire is still available. Whatever this pushes into the boundary buffers is drained by one more encode()/encodeMsg() pass before the filter is destroyed.

Definition at line 360 of file filter.h.

Typedef Documentation

◆ NetDatagramFilter

Base for datagram filters (attached between the application and a datagram flow)

A datagram filter transforms whole NetMessages at message boundaries – DTLS is one datagram per record. Each stage owns a pair of boundary queues, encOut/decOut, playing the same role NetStreamFilter's boundary rings play for a byte stream: a stage need not produce exactly one output per input. Consuming a handshake record and producing nothing is how a stage swallows it; producing more messages than consumed is how a stage fragments an oversized message for the wire's MTU.

Definition at line 81 of file filter.h.

◆ NetFilter

typedef struct NetFilter NetFilter

Socket-level factory for per-flow filters.

A NetFilter is attached to a NetSocket and acts as a factory for NetFlowFilter instances. When attached, the socket immediately calls createFlow() for every flow already open, and calls createFlow() again each time a new flow opens. For a stream socket this produces exactly one NetFlowFilter; for a datagram socket, one per peer flow.

A NetFilter may carry socket-wide configuration shared by all of its created flow filters – TLS certificate and key material, for example – which a created flow filter accesses by keeping its own reference to self (this NetFilter), captured inside createFlow(). One instance may be attached to as many sockets as needed; each socket acquires its own reference. That is what attaching one to a listener does: every accepted connection inherits it and acquires a reference of its own, so a single filter holding a certificate and key serves the whole server.

A factory that cannot build a usable stage should still return one, in a state that fails on its first pass. Returning NULL leaves the flow unfiltered, which for a transform is harmless and for a security filter means traffic the application believes is protected going out in the clear.

Definition at line 75 of file filter.h.

◆ NetFlowFilter

typedef struct NetFlowFilter NetFlowFilter

Shared lifecycle base for all flow filters.

Holds the notification slot and the one lifecycle verb every concrete filter must implement besides its data-plane transform. A stage carries no chain link: the flow owns the chain as an ordered array and releases every stage when it is torn down. Not instantiable directly; use a concrete stream or datagram filter.

Definition at line 77 of file filter.h.

◆ NetStreamFilter

Base for byte-stream filters (attached between the application and a stream socket)

A stream filter transforms a byte stream in each direction. It owns two boundary ring buffers: encOut collects this stage's output toward the wire, decOut its output toward the application. The driver feeds each stage from the previous stage's boundary ring – at the ends of the chain, from the flow's staging ring going out and the socket's receive ring coming in – so a stage holding a partial framing/record unit simply produces nothing that pass, without losing the buffered remainder.

Definition at line 79 of file filter.h.