DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
listener.cpp File Reference

Layer 4 (Listener) - TCP accept callback and port lifecycle. More...

#include "listener.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "lwip/tcp.h"
#include "network_drivers/tls/tls.h"
#include "lwip/ip_addr.h"
#include "lwip/priv/tcpip_priv.h"
#include "network_drivers/session/worker.h"
#include "services/clock.h"
#include <Arduino.h>

Go to the source code of this file.

Classes

struct  AcceptThrottleCtx
 
struct  IpThrottleBucket
 
struct  IpThrottleCtx
 
struct  IpAllowRule
 
struct  IpAllowCtx
 
struct  DetListenerCall
 

Functions

err_t lowlevel_recv_cb (void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
 lwIP receive callback - fires when data arrives on a connection.
 
err_t lowlevel_sent_cb (void *arg, struct tcp_pcb *tpcb, u16_t len)
 lwIP sent callback - fires after the stack acknowledges sent bytes.
 
void lowlevel_err_cb (void *arg, err_t err)
 lwIP error callback - fires when the stack detects a fatal error.
 
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 DetIp *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 DetIp *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 DetIp *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).
 
bool listener_enqueue (uint8_t listener_id, const TcpEvt *evt)
 Post evt to the queue owned by listener listener_id.
 
int32_t listener_add (uint8_t idx, uint16_t port, ConnProto proto, bool tls)
 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)
 

Variables

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

Detailed Description

Layer 4 (Listener) - TCP accept callback and port lifecycle.

listener_accept_cb is the single lwIP accept callback registered for every listener. The listener index is embedded in the PCB user-data via tcp_arg(listen_pcb, (void*)(uintptr_t)idx) so this one function handles all ports without a lookup table.

The non-static per-connection callbacks (lowlevel_recv_cb, lowlevel_sent_cb, lowlevel_err_cb) are defined in tcp.cpp and declared extern here. The transport layer's enqueue() helper calls listener_enqueue(), which is defined in this file - that indirection breaks the circular header dependency (listener.h includes tcp.h; tcp.cpp includes listener.h).

Definition in file listener.cpp.

Function Documentation

◆ lowlevel_recv_cb()

err_t lowlevel_recv_cb ( void *  arg,
struct tcp_pcb *  tpcb,
struct pbuf *  p,
err_t  err 
)
extern

lwIP receive callback - fires when data arrives on a connection.

Copies pbuf chain bytes into the ring buffer; the window is reopened later by det_conn_ack_consumed() as the worker drains (ack-on-consume), not here. If the whole segment will not fit it is refused (ERR_MEM) for lossless backpressure. A null pbuf signals graceful remote close (FIN received).

Definition at line 795 of file tcp.cpp.

References CONN_ACTIVE, CONN_CLOSING, CONN_FREE, detws_millis(), DETWS_OBS_NOTICE, DETWS_OBS_TRANSITION, EVT_DATA, EVT_DISCONNECT, TcpConn::id, TcpConn::last_activity_ms, TcpConn::pcb, RX_BUF_SIZE, TcpConn::rx_buffer, TcpConn::rx_head, TcpConn::rx_tail, and TcpConn::state.

◆ lowlevel_sent_cb()

err_t lowlevel_sent_cb ( void *  arg,
struct tcp_pcb *  tpcb,
u16_t  len 
)
extern

lwIP sent callback - fires after the stack acknowledges sent bytes.

Refreshes the idle-timeout timestamp so an active sender is not reaped while its responses are in flight, and - for a slot dwelling in ConnState::CONN_CLOSING - finalizes the close once the response has fully drained (the peer ACKed everything).

Definition at line 896 of file tcp.cpp.

References CONN_CLOSING, detws_millis(), detws_worker_wake(), TcpConn::id, TcpConn::last_activity_ms, TcpConn::owner, and TcpConn::state.

◆ lowlevel_err_cb()

void lowlevel_err_cb ( void *  arg,
err_t  err 
)
extern

lwIP error callback - fires when the stack detects a fatal error.

By the time this fires the PCB is already gone internally, so we must NOT call tcp_close() or tcp_abort(). Null out the slot's pcb pointer and post EvtType::EVT_ERROR so the session layer resets the protocol state.

Definition at line 922 of file tcp.cpp.

References CONN_ACTIVE, CONN_CLOSING, CONN_FREE, DETWS_OBS_TRANSITION, EVT_ERROR, TcpConn::id, TcpConn::pcb, and TcpConn::state.

◆ 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 DETWS_ACCEPT_THROTTLE_MAX-per-DETWS_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 DETWS_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 55 of file listener.cpp.

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

◆ listener_accept_throttle_reset()

void listener_accept_throttle_reset ( void  )

Reset the accept-throttle window counters.

Definition at line 69 of file listener.cpp.

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

◆ listener_accept_allowed_ip()

bool listener_accept_allowed_ip ( const DetIp 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 DETWS_PER_IP_THROTTLE_MAX-per-DETWS_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 (DetIp): 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 DETWS_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 DetIpFamily::DET_IP_NONE) is passed through (allowed) since it cannot be tracked. The accept callback consults this only when DETWS_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 95 of file listener.cpp.

References IpThrottleBucket::addr, IpThrottleCtx::buckets, IpThrottleBucket::count, det_ip_equal(), det_ip_is_unspecified(), DET_IP_NONE, DETWS_PER_IP_THROTTLE_MAX, DETWS_PER_IP_THROTTLE_SLOTS, DETWS_PER_IP_THROTTLE_WINDOW_MS, DetIp::family, and IpThrottleBucket::window_start.

◆ listener_per_ip_throttle_reset()

void listener_per_ip_throttle_reset ( void  )

◆ listener_ip_allow_add()

bool listener_ip_allow_add ( const DetIp network,
uint8_t  prefix_len 
)

Add a CIDR rule to the source-IP allowlist.

Parameters
networkFamily-tagged network address (DetIp, 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 (DETWS_IP_ALLOWLIST_SLOTS entries) is full.

Definition at line 175 of file listener.cpp.

References IpAllowCtx::count, DET_IP_V4, DET_IP_V6, DETWS_IP_ALLOWLIST_SLOTS, DetIp::family, IpAllowRule::network, 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 det_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 191 of file listener.cpp.

References DET_IP_NONE, det_ip_parse(), DET_IP_STR_MAX, DET_IP_V4, DetIp::family, and listener_ip_allow_add().

◆ listener_ip_allowed()

bool listener_ip_allowed ( const DetIp ip)

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

Parameters
ipFamily-tagged source address (DetIp).
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 DETWS_ENABLE_IP_ALLOWLIST is set; the function is always compiled so it can be unit-tested.

Definition at line 242 of file listener.cpp.

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

◆ listener_ip_allowlist_reset()

void listener_ip_allowlist_reset ( void  )

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

Definition at line 255 of file listener.cpp.

References IpAllowCtx::count, DET_IP_NONE, DETWS_IP_ALLOWLIST_SLOTS, DetIp::family, IpAllowRule::network, and IpAllowCtx::rules.

◆ 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 291 of file listener.cpp.

References Listener::active, conn_pool, DETWS_WORKER_COUNT, detws_worker_wake(), listener_pool, MAX_LISTENERS, TcpConn::owner, Listener::queue, and TcpEvt::slot_id.

◆ 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 466 of file listener.cpp.

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

Referenced by DetWebServer::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 525 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 DetWebServer::stop().

Definition at line 545 of file listener.cpp.

References listener_stop(), and MAX_LISTENERS.

Referenced by DetWebServer::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 619 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)

Variable Documentation

◆ listener_pool

Listener listener_pool[MAX_LISTENERS]

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

Definition at line 34 of file listener.cpp.

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