ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
listener.h File Reference

Layer 4 (Listener) - per-port TCP listener abstraction. More...

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "lwip/tcp.h"
#include "protocore_config.h"
#include "tcp.h"

Go to the source code of this file.

Classes

struct  Listener
 State for one TCP listening port. More...
 

Functions

err_t listener_accept_cb (void *arg, struct tcp_pcb *newpcb, err_t err)
 lwIP accept callback - single handler for all listener ports (defined in listener.cpp).
 
int32_t listener_add (uint8_t idx, uint16_t port, ConnProto proto, bool tls=false)
 Create a listening socket on port and register it at idx.
 
void listener_stop (uint8_t idx)
 Stop listening on the port at idx and release its resources.
 
void listener_stop_all ()
 Stop all active listeners.
 
int32_t listener_add_dynamic (uint8_t idx, uint16_t port, ConnProto proto)
 Add / stop a listener from a running task (thread-safe variant).
 
void listener_stop_dynamic (uint8_t idx)
 
bool listener_enqueue (uint8_t listener_id, const TcpEvt *evt)
 Post evt to the queue owned by listener listener_id.
 
bool listener_accept_allowed (uint32_t now_ms)
 Fixed-window global accept-rate gate (connection-flood defense).
 
void listener_accept_throttle_reset (void)
 Reset the accept-throttle window counters.
 
bool listener_accept_allowed_ip (const pc_ip *ip, uint32_t now_ms)
 Fixed-window per-IP accept-rate gate (connection-flood defense, keyed by source address).
 
void listener_per_ip_throttle_reset (void)
 Reset the per-IP throttle bucket table.
 
bool listener_ip_allow_add (const pc_ip *network, uint8_t prefix_len)
 Add a CIDR rule to the source-IP allowlist.
 
bool listener_ip_allow_add_cidr (const char *cidr)
 Add an allowlist rule from CIDR text (the ergonomic public entry point).
 
bool listener_ip_allowed (const pc_ip *ip)
 Test a source address against the allowlist (accept-time firewall).
 
void listener_ip_allowlist_reset (void)
 Clear all allowlist rules (the allowlist becomes empty = allow all).
 

Variables

Listener listener_pool [MAX_LISTENERS]
 Static pool of listener contexts. Defined in listener.cpp.
 

Detailed Description

Layer 4 (Listener) - per-port TCP listener abstraction.

Each active listener owns one lwIP listening PCB and one statically- allocated FreeRTOS queue. When a new client connects, listener_accept_cb claims a slot from the shared conn_pool, wires the standard per-connection callbacks, and posts EvtType::EVT_CONNECT to the owning listener's queue.

The session layer drains all active listener queues each server_tick(), routing events to the correct protocol handler via TcpConn::proto.

Single accept callback tcp_arg(listen_pcb, (void*)(uintptr_t)idx) embeds the listener index in the PCB user-data so a single static listener_accept_cb handles all ports.

Circular-dependency resolution tcp.cpp needs to post events to listener queues but cannot include this header (listener.h already includes tcp.h). The symbol listener_enqueue() is exported from listener.cpp; tcp.cpp calls it via a forward declaration added to tcp.h so no circular include is introduced.

Author
Douglas Quigg (dstroy0)
Date
2026

Definition in file listener.h.

Function Documentation

◆ listener_accept_cb()

err_t listener_accept_cb ( void *  arg,
struct tcp_pcb *  newpcb,
err_t  err 
)

lwIP accept callback - single handler for all listener ports (defined in listener.cpp).

Non-static so the host unit tests can call it directly with a fabricated newpcb, the same convention tcp.cpp uses for lowlevel_recv_cb / lowlevel_sent_cb / lowlevel_err_cb - production code never calls this directly, it is wired in via tcp_arg()+tcp_accept() in listener_add().

lwIP accept callback - single handler for all listener ports (defined in listener.cpp).

arg carries the listener index cast to a pointer via tcp_arg(listen_pcb, (void*)(uintptr_t)idx). Finds a free TcpConn slot, sets its protocol, wires the per-connection callbacks, and posts EvtType::EVT_CONNECT to the owning listener's queue. Rejects the connection with ERR_ABRT when the pool is full - ERR_ABRT tells lwIP the PCB is already gone from our side.

Non-static (like tcp.cpp's lowlevel_*_cb) so the host unit tests can call it directly with a fabricated newpcb: on native there is no real lwIP accept event to drive it through tcp_accept(), whose mock (test/mocks/lwip/tcp.h) does not store or invoke the registered callback at all.

Definition at line 386 of file listener.cpp.

References CONN_ACTIVE, CONN_FREE, conn_pool, EVT_CONNECT, pc_ip::family, TcpConn::iface, TcpConn::last_activity_ms, listener_accept_allowed(), listener_accept_allowed_ip(), listener_enqueue(), TcpConn::listener_id, listener_ip_allowed(), listener_pool, lowlevel_err_cb(), lowlevel_recv_cb(), lowlevel_sent_cb(), MAX_LISTENERS, TcpConn::owner, pc_ap_ip, pc_conn_alloc_free(), pc_conn_set_state(), PC_IFACE_ANY, PC_IFACE_AP, PC_IFACE_STA, PC_IP_NONE, pc_lwip_to_ip(), pc_millis(), PC_OBS_NOTICE, PC_OBS_TRANSITION, PC_WORKER_COUNT, TcpConn::pcb, Listener::proto, TcpConn::proto, TcpConn::req_start_ms, TcpConn::rx_acked, TcpConn::rx_head, TcpConn::rx_tail, Listener::tls, and TcpConn::tls.

Referenced by listener_add().

◆ listener_add()

int32_t listener_add ( uint8_t  idx,
uint16_t  port,
ConnProto  proto,
bool  tls = false 
)

Create a listening socket on port and register it at idx.

If the slot at idx is already active it is stopped first. Creates a per-listener FreeRTOS static queue and an lwIP listening PCB. Wires listener_accept_cb as the accept handler with the listener index embedded as the PCB user-data argument.

Parameters
idxSlot in listener_pool[] (0 … MAX_LISTENERS-1).
portTCP port to bind and listen on.
protoApplication protocol spoken on connections from this port.
tlsWhen true, connections accepted here start a TLS handshake.
Returns
Positive value on success; -1 on failure (pool full or lwIP error).

Definition at line 552 of file listener.cpp.

References Listener::_queue_storage, Listener::_queue_struct, Listener::active, EVT_QUEUE_DEPTH, Listener::listen_pcb, listener_accept_cb(), listener_pool, listener_stop(), MAX_CONNS, MAX_LISTENERS, Listener::port, Listener::proto, Listener::queue, and Listener::tls.

Referenced by PC::begin().

◆ listener_stop()

void listener_stop ( uint8_t  idx)

Stop listening on the port at idx and release its resources.

Idempotent - safe to call on an already-stopped slot. Closes the lwIP listening PCB and deletes the FreeRTOS queue. Does not close any connections already accepted on this port.

Parameters
idxSlot in listener_pool[].

Definition at line 620 of file listener.cpp.

References Listener::active, Listener::listen_pcb, listener_pool, MAX_LISTENERS, and Listener::queue.

Referenced by listener_add(), and listener_stop_all().

◆ listener_stop_all()

void listener_stop_all ( )

Stop all active listeners.

Convenience wrapper that calls listener_stop() for every slot in listener_pool[]. Called by PC::stop().

Definition at line 644 of file listener.cpp.

References listener_stop(), and MAX_LISTENERS.

Referenced by PC::stop().

◆ listener_add_dynamic()

int32_t listener_add_dynamic ( uint8_t  idx,
uint16_t  port,
ConnProto  proto 
)

Add / stop a listener from a running task (thread-safe variant).

listener_add() runs the raw lwIP tcp_bind/tcp_listen inline, which is only safe at setup (before tcpip_thread is servicing our sockets). These variants marshal the lwIP operations onto tcpip_thread via tcpip_api_call(), so a listener may be created or torn down dynamically from a worker/session task - used by the SSH remote-forward owner (ssh -R), which opens a listener when a client requests one. TLS listeners are not supported here (forwarded ports are plaintext bridges). On the native host (no lwIP) these behave like the inline path for unit tests.

Returns
listener_add_dynamic: 1 on success, -1 on failure (bad idx, bind in use, or lwIP error). listener_stop_dynamic: void, idempotent.

Definition at line 741 of file listener.cpp.

References Listener::_queue_storage, Listener::_queue_struct, Listener::active, EVT_QUEUE_DEPTH, Listener::listen_pcb, listener_pool, listener_stop_dynamic(), MAX_LISTENERS, Listener::port, Listener::proto, Listener::queue, and Listener::tls.

◆ listener_stop_dynamic()

void listener_stop_dynamic ( uint8_t  idx)

◆ listener_enqueue()

bool listener_enqueue ( uint8_t  listener_id,
const TcpEvt evt 
)

Post evt to the queue owned by listener listener_id.

Called from tcp.cpp callbacks (running in tcpip_thread context) to deliver connection events to the session layer. Uses xQueueSend with timeout=0 - a full queue means the application is not calling server_tick() fast enough; the dropped event is recoverable via connection timeout.

Parameters
listener_idIndex into listener_pool[]; must be < MAX_LISTENERS.
evtEvent to copy into the queue.
Returns
true if queued; false if dropped (full queue / inactive listener).

Definition at line 334 of file listener.cpp.

◆ listener_accept_allowed()

bool listener_accept_allowed ( uint32_t  now_ms)

Fixed-window global accept-rate gate (connection-flood defense).

Returns true if a new connection accepted at now_ms is within the PC_ACCEPT_THROTTLE_MAX-per-PC_ACCEPT_THROTTLE_WINDOW_MS budget (and counts it), false if the budget for the current window is exhausted. State is two static counters shared across all listeners. The accept callback consults this only when PC_ENABLE_ACCEPT_THROTTLE is set; the function is always compiled so it can be unit-tested. Call listener_accept_throttle_reset() to clear the window (e.g. between tests).

Definition at line 56 of file listener.cpp.

References AcceptThrottleCtx::count, PC_ACCEPT_THROTTLE_MAX, PC_ACCEPT_THROTTLE_WINDOW_MS, and AcceptThrottleCtx::window_start.

Referenced by listener_accept_cb().

◆ listener_accept_throttle_reset()

void listener_accept_throttle_reset ( void  )

Reset the accept-throttle window counters.

Definition at line 72 of file listener.cpp.

References AcceptThrottleCtx::count, and AcceptThrottleCtx::window_start.

◆ listener_accept_allowed_ip()

bool listener_accept_allowed_ip ( const pc_ip ip,
uint32_t  now_ms 
)

Fixed-window per-IP accept-rate gate (connection-flood defense, keyed by source address).

Returns true if a connection from source address ip accepted at now_ms is within that address's PC_PER_IP_THROTTLE_MAX-per-PC_PER_IP_THROTTLE_WINDOW_MS budget (and counts it), false once that address has exhausted its budget for the current window. The key is the full family-tagged address (pc_ip): an IPv4 and an IPv6 peer are always distinct buckets, and an IPv6 attacker cannot fold many addresses onto one bucket (or evict a victim's) through a lossy hash. State is a fixed BSS table of PC_PER_IP_THROTTLE_SLOTS buckets; a new address reuses an empty, expired, or least-recently-started bucket so memory stays bounded. An unspecified ip (family pc_ip_family::PC_IP_NONE) is passed through (allowed) since it cannot be tracked. The accept callback consults this only when PC_ENABLE_PER_IP_THROTTLE is set; the function is always compiled so it can be unit-tested. Call listener_per_ip_throttle_reset() to clear the table.

Definition at line 98 of file listener.cpp.

References IpThrottleBucket::addr, IpThrottleCtx::buckets, IpThrottleBucket::count, pc_ip::family, pc_ip_equal(), pc_ip_is_unspecified(), PC_IP_NONE, PC_PER_IP_THROTTLE_MAX, PC_PER_IP_THROTTLE_SLOTS, PC_PER_IP_THROTTLE_WINDOW_MS, and IpThrottleBucket::window_start.

Referenced by listener_accept_cb().

◆ listener_per_ip_throttle_reset()

void listener_per_ip_throttle_reset ( void  )

◆ listener_ip_allow_add()

bool listener_ip_allow_add ( const pc_ip network,
uint8_t  prefix_len 
)

Add a CIDR rule to the source-IP allowlist.

Parameters
networkFamily-tagged network address (pc_ip, IPv4 or IPv6). Host bits outside the prefix do not need to be pre-masked; matching masks them at compare time.
prefix_lenCIDR prefix length: 0..32 for IPv4, 0..128 for IPv6 (32 / 128 for a single host, 0 to match every address of that family).
Returns
true if the rule was stored; false if network is unspecified, the prefix length exceeds the family width, or the table (PC_IP_ALLOWLIST_SLOTS entries) is full.

Definition at line 188 of file listener.cpp.

References IpAllowCtx::count, pc_ip::family, IpAllowRule::network, PC_IP_ALLOWLIST_SLOTS, PC_IP_V4, PC_IP_V6, IpAllowRule::prefix_len, and IpAllowCtx::rules.

Referenced by listener_ip_allow_add_cidr().

◆ listener_ip_allow_add_cidr()

bool listener_ip_allow_add_cidr ( const char *  cidr)

Add an allowlist rule from CIDR text (the ergonomic public entry point).

Accepts IPv4 or IPv6 in address/prefix form (e.g. "192.168.1.0/24", "2001:db8::/32") or a bare address (e.g. "10.0.0.5", "::1") which is treated as a host route (/32 for v4, /128 for v6). The address is parsed with pc_ip_parse so every RFC 4291 v6 text form is accepted.

Returns
true if the rule was stored; false if cidr is malformed, the prefix is out of range for the family, or the table is full.

Definition at line 210 of file listener.cpp.

References pc_ip::family, listener_ip_allow_add(), PC_IP_NONE, pc_ip_parse(), PC_IP_STR_MAX, and PC_IP_V4.

◆ listener_ip_allowed()

bool listener_ip_allowed ( const pc_ip ip)

Test a source address against the allowlist (accept-time firewall).

Parameters
ipFamily-tagged source address (pc_ip).
Returns
true if the address is allowed: always true while the allowlist is empty (so enabling the feature without rules never locks the device out), otherwise true only if ip is contained in at least one CIDR rule of the same family (prefix match on the full address, never a hash). The accept callback consults this only when PC_ENABLE_IP_ALLOWLIST is set; the function is always compiled so it can be unit-tested.

Definition at line 273 of file listener.cpp.

References IpAllowCtx::count, IpAllowRule::network, pc_ip_prefix_match(), IpAllowRule::prefix_len, and IpAllowCtx::rules.

Referenced by listener_accept_cb().

◆ listener_ip_allowlist_reset()

void listener_ip_allowlist_reset ( void  )

Clear all allowlist rules (the allowlist becomes empty = allow all).

Definition at line 290 of file listener.cpp.

References IpAllowCtx::count, pc_ip::family, IpAllowRule::network, PC_IP_ALLOWLIST_SLOTS, PC_IP_NONE, and IpAllowCtx::rules.

Variable Documentation

◆ listener_pool

Listener listener_pool[MAX_LISTENERS]
extern

Static pool of listener contexts. Defined in listener.cpp.

Definition at line 35 of file listener.cpp.

Referenced by listener_accept_cb(), listener_add(), listener_add_dynamic(), listener_enqueue(), listener_stop(), listener_stop_dynamic(), and server_tick().