ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
forward.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file forward.h
6 * @brief Layer 3 (Network) - the interface forwarding plane (PROTOCORE_ENABLE_FORWARD).
7 *
8 * RFC 1812 sec 5 "INTERNET LAYER - FORWARDING". A frame that arrives on one interface runs the
9 * forwarding walk-through (sec 5.2) and leaves on every interface it is permitted to leave on. The
10 * steps this plane runs over opaque bytes are the access list (sec 5.3.9 "Packet Filtering and
11 * Access Lists"), the next hop (sec 5.2.4.3 "Next Hop Address"), the controls on forwarding
12 * (sec 5.3.11 "Controls on Forwarding"), and the drop under load (sec 5.3.6 "Congestion Control").
13 *
14 * The interfaces are layer 1's, over PROTOCORE_PHY_MAX_IFACES rows; this plane reads that registry
15 * and calls its send. A (src, dst) pair forwards when an ALLOW control matches and no DENY does; a
16 * frame never leaves on the interface it arrived on. An exceeded rate cap, or an interface that
17 * refuses the bytes, drops the frame for that next hop and counts it. Storage is static:
18 * PROTOCORE_FWD_MAX_RULES controls, PROTOCORE_FWD_MAX_ACL access-list entries,
19 * PROTOCORE_FWD_MAX_ROUTES policy routes.
20 *
21 * A policy route matches a frame by the same byte pattern the access list uses - any field at a
22 * known offset, an EtherType, an IP protocol, a port, an address prefix - and binds it to one
23 * egress interface ahead of the (src, dst) fan-out; the first matching route wins. The access list
24 * runs first, and the rate cap, the never-reflect step and the counted drop apply to the chosen
25 * next hop.
26 *
27 * The inspection hook (PROTOCORE_FWD_INSPECT) runs an application callback on every frame after the
28 * access list and before the route lookup; the callback returns a verdict that passes or drops it.
29 *
30 * The rate cap reads Clock.millis (server/clock/clock.h), the library's one time source; Clock.set_ms
31 * moves the window for every module at once.
32 *
33 * @author Douglas Quigg (dstroy0)
34 * @date 2026
35 */
36
37#ifndef PROTOCORE_FORWARD_H
38#define PROTOCORE_FORWARD_H
39
41
42#include "protocore_config.h"
43// An interface is a physical thing: its id, its kind, and how bytes reach the wire all live at L1.
44// This plane picks the interface a frame leaves on and asks L1 to put it there.
45
47
48#if PROTOCORE_ENABLE_FORWARD
49
50/**
51 * @brief What an access-list entry or a control on forwarding does to a frame.
52 * RFC 1812 sec 5.3.9, sec 5.3.11.
53 */
54typedef enum PROTO_ENUM_PACKED
55{
56 PROTOCORE_FWD_DENY = 0,
57 PROTOCORE_FWD_ALLOW = 1,
58} protocore_fwd_action;
59
60/** @brief Wildcard source interface: an entry carrying it matches a frame from any interface. */
61#define PROTOCORE_FWD_IF_ANY 0xFF
62
63/** @brief Forwarding counters, monotonic since the last reset. */
64typedef struct
65{
66 uint32_t frames_in; ///< frames offered to the forwarding algorithm (RFC 1812 sec 5.2.1)
67 uint32_t forwarded; ///< next hops that took the frame (RFC 1812 sec 5.2.4.3)
68 uint32_t blocked; ///< next hops refused by a DENY or by default-deny (RFC 1812 sec 5.3.11)
69 uint32_t rate_dropped; ///< frames dropped at a rate cap (RFC 1812 sec 5.3.6)
70 uint32_t send_fail; ///< next hops whose interface refused the bytes
71 uint32_t acl_denied; ///< frames dropped by the access list (RFC 1812 sec 5.3.9)
72 uint32_t policy_routed; ///< frames a policy route bound to one next hop
73 uint32_t inspect_dropped; ///< frames dropped by the inspection hook (PROTOCORE_FWD_INSPECT)
74} protocore_forward_stats;
75
76#if PROTOCORE_FWD_INSPECT
77/** @brief The verdict an inspection hook returns for a frame. */
78typedef enum PROTO_ENUM_PACKED
79{
80 PROTOCORE_FWD_INSPECT_PASS = 0, ///< the frame continues to the route lookup and the fan-out
81 PROTOCORE_FWD_INSPECT_DROP = 1, ///< the frame is dropped and counted as inspect_dropped
82} protocore_fwd_verdict;
83
84/**
85 * @brief Ingress inspection hook: reads @p len bytes at @p data arriving on @p src_if and returns a
86 * ::protocore_fwd_verdict. Runs after the access list and before the route lookup.
87 */
88typedef protocore_fwd_verdict (*protocore_fwd_inspect_fn)(uint8_t src_if, const uint8_t *data, uint16_t len, void *ctx);
89#endif
90
91/** @brief One control on forwarding: the destination of a (src, dst) pair. RFC 1812 sec 5.3.11. */
92typedef struct
93{
94 uint8_t dst_if; ///< the destination interface the pair names
95 protocore_fwd_action action; ///< ALLOW forwards the pair, DENY blocks it
96 uint16_t rate_cap_per_sec; ///< frames per second the pair takes; 0 is uncapped (RFC 1812 sec 5.3.6)
97} FwdRuleArgs;
98
99/**
100 * @brief The byte pattern an access-list entry or a policy route matches on: @c patlen bytes at
101 * @c offset, compared under @c mask. RFC 1812 sec 5.3.9.
102 */
103typedef struct
104{
105 const uint8_t *pattern; ///< the bytes to match, read only during the call that stores them
106 const uint8_t *mask; ///< the bits of each pattern byte that are compared
107 uint16_t offset; ///< byte offset into the frame where the pattern starts
108 uint8_t patlen; ///< pattern length, up to PROTOCORE_FWD_ACL_PATLEN; 0 matches any content
109} FwdMatchArgs;
110
111/** @brief The access list's two verdicts: one entry's, and the one a frame matching none takes. */
112typedef struct
113{
114 protocore_fwd_action action; ///< what the entry being added does to a matching frame
115 protocore_fwd_action fallback; ///< what a frame matching no entry takes
116} FwdAclArgs;
117
118/** @brief The next hop a policy route binds a matched frame to. RFC 1812 sec 5.2.4.3. */
119typedef struct
120{
121 uint8_t egress_if; ///< the one interface a matched frame leaves on
122 uint16_t rate_cap_per_sec; ///< frames per second to that next hop; 0 is uncapped
123} FwdRouteArgs;
124
125/** @brief The received frame the forwarding algorithm runs on. RFC 1812 sec 5.2.1. */
126typedef struct
127{
128 const uint8_t *data; ///< the frame bytes, valid for the duration of the call
129 uint16_t len; ///< how many of them there are
130} FwdFrameArgs;
131
132#if PROTOCORE_FWD_INSPECT
133/** @brief The inspection hook and the pointer it is handed back. */
134typedef struct
135{
136 protocore_fwd_inspect_fn fn; ///< what runs on every frame; NULL leaves the hook off
137 void *ctx; ///< passed to @c fn unread by this module
138} FwdInspectArgs;
139#endif
140
141/** @brief The plane's own tables and the calls that reach them, described only in forward.c. */
142
143/**
144 * @brief The interface forwarding plane. RFC 1812 sec 5.2 "FORWARDING WALK-THROUGH".
145 *
146 * A caller sets the members a call takes, invokes it through ::Forward, and reads the outcome off
147 * the same handle. The tables are behind @ref internal.
148 *
149 * @var ForwardNs::src_if the interface a frame arrives on, and the one an entry scopes to;
150 * PROTOCORE_FWD_IF_ANY on an entry matches every interface
151 * @var ForwardNs::rule the destination of a (src, dst) pair (RFC 1812 sec 5.3.11)
152 * @var ForwardNs::match the byte pattern an entry or a route matches on (RFC 1812 sec 5.3.9)
153 * @var ForwardNs::acl the access list's entry verdict and its fallback (RFC 1812 sec 5.3.9)
154 * @var ForwardNs::route the next hop a policy route binds to (RFC 1812 sec 5.2.4.3)
155 * @var ForwardNs::frame the received frame the forwarding algorithm runs on
156 * @var ForwardNs::inspect the inspection hook and the pointer it is handed back
157 * @var ForwardNs::ok a call's true/false outcome
158 * @var ForwardNs::n next hops a frame reached
159 * @var ForwardNs::stats the counters a read reports
160 * @var ForwardNs::reset empty every control, entry and route, and zero the counters
161 * @var ForwardNs::add_rule add a control on forwarding for one (src, dst) pair
162 * @var ForwardNs::acl_set_default set what a frame matching no access-list entry takes
163 * @var ForwardNs::acl_add add an access-list entry; the first match decides
164 * @var ForwardNs::route_add add a policy route, taken ahead of the (src, dst) fan-out
165 * @var ForwardNs::set_inspector install the ingress inspection hook
166 * @var ForwardNs::ingress run one received frame through the forwarding walk-through
167 * @var ForwardNs::get_stats copy the counters onto the handle
168 */
169typedef struct
170{
171 uint8_t src_if; ///< the source interface a forwarding call names
172 FwdRuleArgs rule; ///< the (src, dst) pair a control governs
173 FwdMatchArgs match; ///< the byte pattern an entry or a route matches on
174 FwdAclArgs acl; ///< the access list's verdicts
175 FwdRouteArgs route; ///< the next hop a policy route binds to
176 FwdFrameArgs frame; ///< the frame the forwarding algorithm runs on
177#if PROTOCORE_FWD_INSPECT
178 FwdInspectArgs inspect; ///< the ingress inspection hook
179#endif
180 proto_bool ok;
181 uint8_t n;
182 protocore_forward_stats stats;
183#if PROTOCORE_FWD_INSPECT
184#endif
185} ForwardVars;
186
187/** @brief The operands and the outcome. */
188extern ForwardVars ForwardV;
189
190/** @brief The entries. */
191typedef struct
192{
193 void (*const reset)(uint8_t *work);
194 void (*const add_rule)(uint8_t *work);
195 void (*const acl_set_default)(uint8_t *work);
196 void (*const acl_add)(uint8_t *work);
197 void (*const route_add)(uint8_t *work);
198 void (*const set_inspector)(uint8_t *work);
199 void (*const ingress)(uint8_t *work);
200 void (*const get_stats)(uint8_t *work);
201} ForwardNs;
202
203// What the table binds, defined once in the .c and taking one parameter each: everything
204// else an entry needs is an operand in ForwardV or a region of the borrow at a fixed offset.
205void protocore_forward_reset(uint8_t *work);
206void protocore_forward_add_rule(uint8_t *work);
207void protocore_forward_acl_set_default(uint8_t *work);
208void protocore_forward_acl_add(uint8_t *work);
209void protocore_forward_route_add(uint8_t *work);
210#if PROTOCORE_FWD_INSPECT
211void protocore_forward_set_inspector(uint8_t *work);
212#endif
213void protocore_forward_ingress(uint8_t *work);
214void protocore_forward_get_stats(uint8_t *work);
215
216// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
217// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
218// `Forward.reset(work)` resolves to a named function and becomes a DIRECT call. An extern table
219// leaves the call indirect and the symbol live at every level, -O2 -flto included.
220static const ForwardNs Forward __attribute__((unused)) = {
221 .reset = protocore_forward_reset,
222 .add_rule = protocore_forward_add_rule,
223 .acl_set_default = protocore_forward_acl_set_default,
224 .acl_add = protocore_forward_acl_add,
225 .route_add = protocore_forward_route_add,
226#if PROTOCORE_FWD_INSPECT
227 .set_inspector = protocore_forward_set_inspector,
228#endif
229 .ingress = protocore_forward_ingress,
230 .get_stats = protocore_forward_get_stats,
231};
232
233/**
234 * @brief The PROTOCORE_FORWARD_BORROW bytes this module's state lives in.
235 *
236 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
237 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
238 * walks, so the state lasts the life of the program.
239 *
240 * @return the span.
241 */
242uint8_t *protocore_forward_span(void);
243
244#endif // PROTOCORE_ENABLE_FORWARD
245
247
248#endif // PROTOCORE_FORWARD_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
Layer 1 (Physical) - link bring-up, the interface registry, and live egress reporting.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97