ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ipsec_db.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 ipsec_db.h
6 * @brief IPsec Security Policy Database (SPD) + Security Association Database (SAD) - RFC 4301.
7 *
8 * The ESP datapath (esp.h) is the crypto transform; this file is the two databases that decide, for a
9 * given packet, WHETHER and WITH WHICH SA to apply it. Both are pure, host-testable data structures with
10 * no heap and no lwIP dependency - the remaining device-side piece is only the IP input/output hook that
11 * feeds packets through these lookups.
12 *
13 * - SPD (RFC 4301 §4.4.1): an ordered list of policies matched against a packet's selectors (source /
14 * destination address ranges, protocol, port ranges); the FIRST matching policy wins and names an
15 * action - PROTECT (apply ESP with a bound SA), BYPASS (send in the clear), or DISCARD (drop).
16 * - SAD (RFC 4301 §4.4.2): the active Security Associations keyed by SPI. An inbound ESP packet is
17 * demuxed to its SA by SPI; an outbound PROTECT policy names the SA to encapsulate with. Each SA
18 * carries its key / salt, its outbound sequence counter, and its inbound anti-replay window.
19 *
20 * Selectors are value types (addresses stored inline, big-endian) so the databases persist independently
21 * of any wire buffer. @ref protocore_ipsec_selector_from_ts bridges an IKEv2-negotiated TSi/TSr pair (the
22 * traffic selectors carried in ikev2.h) into an SPD selector, per RFC 4301 §4.4.1.
23 *
24 * @author Douglas Quigg (dstroy0)
25 * @date 2026
26 */
27
28#ifndef PROTOCORE_IPSEC_DB_H
29#define PROTOCORE_IPSEC_DB_H
30
31#include "protocore_config.h" // the entry point: protocore_types.h for the widths
32
33#if PROTOCORE_ENABLE_IKEV2
34
35#include "services/security/ikev2/ikev2/ikev2.h" // the complete type a public struct below holds by value
36#include "services/system/esp/esp/esp.h" // the complete type a public struct below holds by value
37
39
40// This module holds nothing between calls, so it carves no borrow and states none. An entry
41// takes one all the same, and never reads it, so every namespace in the tree is invoked the
42// same way.
43
44/** @brief Longest selector address (IPv6). IPv4 uses the low 4 bytes. */
45#define PROTOCORE_IPSEC_ADDR_MAX 16
46
47/** @brief Maximum policies in one SPD. */
48#define PROTOCORE_IPSEC_SPD_MAX 8
49
50/** @brief Maximum Security Associations in one SAD. */
51#define PROTOCORE_IPSEC_SAD_MAX 8
52
53/** @brief SPD policy action (RFC 4301 §4.4.1). */
54typedef enum PROTO_ENUM_PACKED
55{
56 IPSEC_ACTION_DISCARD = 0, ///< drop the packet
57 IPSEC_ACTION_BYPASS = 1, ///< forward without IPsec
58 IPSEC_ACTION_PROTECT = 2, ///< apply ESP with the bound SA
59} IpsecAction;
60
61/**
62 * @brief A traffic selector as an SPD range (value type, addresses inline big-endian).
63 *
64 * A packet matches when its family and protocol agree and its source / destination addresses and ports
65 * each fall within the inclusive [lo, hi] ranges. A protocol of 0 or a port range of [0, 65535] is "any".
66 */
67typedef struct
68{
69 uint8_t addr_len; ///< 4 (IPv4) or 16 (IPv6); also selects the family
70 uint8_t ip_protocol; ///< 0 = any
71 uint8_t src_lo[PROTOCORE_IPSEC_ADDR_MAX];
72 uint8_t src_hi[PROTOCORE_IPSEC_ADDR_MAX];
73 uint8_t dst_lo[PROTOCORE_IPSEC_ADDR_MAX];
74 uint8_t dst_hi[PROTOCORE_IPSEC_ADDR_MAX];
75 uint16_t src_port_lo;
76 uint16_t src_port_hi;
77 uint16_t dst_port_lo;
78 uint16_t dst_port_hi;
79} IpsecSelector;
80
81/** @brief One SPD policy: a selector, its action, and (for PROTECT) the outbound SA's SPI. */
82typedef struct
83{
84 IpsecSelector sel;
85 IpsecAction action;
86 uint32_t sa_spi; ///< PROTECT: the SAD entry to encapsulate with (0 = not yet bound)
87} IpsecPolicy;
88
89/** @brief An ordered Security Policy Database (first match wins). */
90typedef struct
91{
92 IpsecPolicy entries[PROTOCORE_IPSEC_SPD_MAX];
93 size_t count;
94} IpsecSpd;
95
96/** @brief A concrete packet's 5-tuple, looked up against the SPD. Addresses point at big-endian octets. */
97typedef struct
98{
99 uint8_t addr_len; ///< 4 or 16 (must match the selector family)
100 uint8_t ip_protocol;
101 const uint8_t *src; ///< @ref addr_len octets
102 const uint8_t *dst; ///< @ref addr_len octets
103 uint16_t src_port;
104 uint16_t dst_port;
105} IpsecFlow;
106
107/** @brief One Security Association (RFC 4301 §4.4.2). */
108typedef struct
109{
110 uint32_t spi; ///< the SA's SPI (its SAD key)
111 uint8_t dst[PROTOCORE_IPSEC_ADDR_MAX]; ///< SA destination address
112 uint8_t addr_len; ///< 4 or 16
113 uint8_t key[PROTOCORE_ESP_KEY_LEN]; ///< AES-256 key (SK_ei / SK_er without salt)
114 uint8_t salt[PROTOCORE_ESP_SALT_LEN]; ///< AES-GCM salt (the key's tail)
115 uint32_t seq; ///< outbound: last sequence number issued (0 = none yet)
116 EspReplay replay; ///< inbound: anti-replay window
117 proto_bool inbound; ///< true = receive SA, false = send SA
118 proto_bool valid; ///< false = free slot
119} IpsecSaEntry;
120
121/** @brief The active Security Association Database, keyed by SPI. */
122typedef struct
123{
124 IpsecSaEntry entries[PROTOCORE_IPSEC_SAD_MAX];
125 size_t count;
126} IpsecSad;
127
128/** @brief What protocore_ipsec_spd_init takes: spd. */
129typedef struct
130{
131 IpsecSpd *spd;
132} IpsecDbProtocoreIpsecSpdInitArgs;
133
134/** @brief What protocore_ipsec_spd_add takes: spd, sel, action, sa_spi. */
135typedef struct
136{
137 IpsecSpd *spd;
138 const IpsecSelector *sel;
139 IpsecAction action;
140 uint32_t sa_spi; ///< for a PROTECT action, the SAD SPI to bind (ignored otherwise)
141} IpsecDbProtocoreIpsecSpdAddArgs;
142
143/** @brief What protocore_ipsec_spd_lookup takes: spd, flow. */
144typedef struct
145{
146 const IpsecSpd *spd;
147 const IpsecFlow *flow;
148} IpsecDbProtocoreIpsecSpdLookupArgs;
149
150/** @brief What protocore_ipsec_selector_match takes: sel, flow. */
151typedef struct
152{
153 const IpsecSelector *sel;
154 const IpsecFlow *flow;
155} IpsecDbProtocoreIpsecSelectorMatchArgs;
156
157/** @brief What protocore_ipsec_selector_from_ts takes: out, ts_src, ... */
158typedef struct
159{
160 IpsecSelector *out;
161 const IkeTrafficSelector *ts_src;
162 const IkeTrafficSelector *ts_dst;
163} IpsecDbProtocoreIpsecSelectorFromTsArgs;
164
165/** @brief What protocore_ipsec_sad_init takes: sad. */
166typedef struct
167{
168 IpsecSad *sad;
169} IpsecDbProtocoreIpsecSadInitArgs;
170
171/** @brief What protocore_ipsec_sad_add takes: sad, spi, dst, ... */
172typedef struct
173{
174 IpsecSad *sad;
175 uint32_t spi;
176 const uint8_t *dst;
177 uint8_t addr_len;
178 const uint8_t *key; ///< PROTOCORE_ESP_KEY_LEN bytes.
179 const uint8_t *salt; ///< PROTOCORE_ESP_SALT_LEN bytes.
180 proto_bool inbound;
181} IpsecDbProtocoreIpsecSadAddArgs;
182
183/** @brief What protocore_ipsec_sad_find takes: sad, spi. */
184typedef struct
185{
186 IpsecSad *sad;
187 uint32_t spi;
188} IpsecDbProtocoreIpsecSadFindArgs;
189
190/** @brief What protocore_ipsec_sad_remove takes: sad, spi. */
191typedef struct
192{
193 IpsecSad *sad;
194 uint32_t spi;
195} IpsecDbProtocoreIpsecSadRemoveArgs;
196
197/** @brief What protocore_ipsec_sad_next_seq takes: sa, seq_out. */
198typedef struct
199{
200 IpsecSaEntry *sa;
201 uint32_t *seq_out; ///< receives the sequence number to place in the packet
202} IpsecDbProtocoreIpsecSadNextSeqArgs;
203
204/**
205 * @brief IPsec Security Policy Database (SPD) + Security Association Database (SAD) - RFC 4301.
206 *
207 * A caller sets the members a call takes, invokes it through ::IpsecDb with the bytes it runs
208 * out of, and reads the outcome off the same handle.
209 *
210 * IpsecDb.protocore_ipsec_spd_init_args.spd = ...;
211 * IpsecDb.protocore_ipsec_spd_init(work);
212 *
213 * @var IpsecDbNs::protocore_ipsec_spd_init_args what protocore_ipsec_spd_init takes: spd
214 * @var IpsecDbNs::protocore_ipsec_spd_add_args what protocore_ipsec_spd_add takes: spd, sel, action, sa_spi
215 * @var IpsecDbNs::protocore_ipsec_spd_lookup_args what protocore_ipsec_spd_lookup takes: spd, flow
216 * @var IpsecDbNs::protocore_ipsec_selector_match_args what protocore_ipsec_selector_match takes: sel, flow
217 * @var IpsecDbNs::protocore_ipsec_selector_from_ts_args what protocore_ipsec_selector_from_ts takes: out, ts_src,
218 * @var IpsecDbNs::protocore_ipsec_sad_init_args what protocore_ipsec_sad_init takes: sad
219 * @var IpsecDbNs::protocore_ipsec_sad_add_args what protocore_ipsec_sad_add takes: sad, spi, dst,
220 * @var IpsecDbNs::protocore_ipsec_sad_find_args what protocore_ipsec_sad_find takes: sad, spi
221 * @var IpsecDbNs::protocore_ipsec_sad_remove_args what protocore_ipsec_sad_remove takes: sad, spi
222 * @var IpsecDbNs::protocore_ipsec_sad_next_seq_args what protocore_ipsec_sad_next_seq takes: sa, seq_out
223 * @var IpsecDbNs::ok true on success, false if spd is full or an argument is null
224 * @var IpsecDbNs::ptr the matching policy, or nullptr if none matches (the caller drops, ...
225 * @var IpsecDbNs::sa the SAD slot a find matched or an add filled, or nullptr
226 * @var IpsecDbNs::protocore_ipsec_spd_init empty an SPD (no policies)
227 * @var IpsecDbNs::protocore_ipsec_spd_add append a policy to the SPD (order is significant - first match wins ...
228 * @var IpsecDbNs::protocore_ipsec_spd_lookup find the first SPD policy whose selector matches flow (RFC 4301 ...
229 * @var IpsecDbNs::protocore_ipsec_selector_match true iff flow falls inside sel (family, protocol, address ranges, ...
230 * @var IpsecDbNs::protocore_ipsec_selector_from_ts fill out from an IKEv2-negotiated TSi / TSr pair (RFC 4301 §4.4.1
231 * ...
232 * @var IpsecDbNs::protocore_ipsec_sad_init empty a SAD (no SAs)
233 * @var IpsecDbNs::protocore_ipsec_sad_add install a Security Association keyed by spi. An inbound SA's ...
234 * @var IpsecDbNs::protocore_ipsec_sad_find look up a valid SA by SPI (inbound ESP demux, RFC 4301 §4.1). ...
235 * @var IpsecDbNs::protocore_ipsec_sad_remove remove the SA with spi (e.g. on an IKE DELETE). true if one was ...
236 * @var IpsecDbNs::protocore_ipsec_sad_next_seq allocate the next outbound sequence number for sa (RFC 4303 §3.3.3, ...
237 *
238 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
239 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
240 * a caller drives every namespace the same way.
241 */
242typedef struct
243{
244 IpsecDbProtocoreIpsecSpdInitArgs protocore_ipsec_spd_init_args;
245 IpsecDbProtocoreIpsecSpdAddArgs protocore_ipsec_spd_add_args;
246 IpsecDbProtocoreIpsecSpdLookupArgs protocore_ipsec_spd_lookup_args;
247 IpsecDbProtocoreIpsecSelectorMatchArgs protocore_ipsec_selector_match_args;
248 IpsecDbProtocoreIpsecSelectorFromTsArgs protocore_ipsec_selector_from_ts_args;
249 IpsecDbProtocoreIpsecSadInitArgs protocore_ipsec_sad_init_args;
250 IpsecDbProtocoreIpsecSadAddArgs protocore_ipsec_sad_add_args;
251 IpsecDbProtocoreIpsecSadFindArgs protocore_ipsec_sad_find_args;
252 IpsecDbProtocoreIpsecSadRemoveArgs protocore_ipsec_sad_remove_args;
253 IpsecDbProtocoreIpsecSadNextSeqArgs protocore_ipsec_sad_next_seq_args;
254 proto_bool ok;
255 const IpsecPolicy *ptr;
256 IpsecSaEntry *sa;
257} IpsecDbVars;
258
259/** @brief The operands and the outcome. */
260extern IpsecDbVars IpsecDbV;
261
262/** @brief The entries. */
263typedef struct
264{
265 void (*const protocore_ipsec_spd_init)(uint8_t *work);
266 void (*const protocore_ipsec_spd_add)(uint8_t *work);
267 void (*const protocore_ipsec_spd_lookup)(uint8_t *work);
268 void (*const protocore_ipsec_selector_match)(uint8_t *work);
269 void (*const protocore_ipsec_selector_from_ts)(uint8_t *work);
270 void (*const protocore_ipsec_sad_init)(uint8_t *work);
271 void (*const protocore_ipsec_sad_add)(uint8_t *work);
272 void (*const protocore_ipsec_sad_find)(uint8_t *work);
273 void (*const protocore_ipsec_sad_remove)(uint8_t *work);
274 void (*const protocore_ipsec_sad_next_seq)(uint8_t *work);
275} IpsecDbNs;
276
277// What the table binds, defined once in the .c and taking one parameter each: everything
278// else an entry needs is an operand in IpsecDbV or a region of the borrow at a fixed offset.
279void protocore_ipsec_db_protocore_ipsec_spd_init(uint8_t *work);
280void protocore_ipsec_db_protocore_ipsec_spd_add(uint8_t *work);
281void protocore_ipsec_db_protocore_ipsec_spd_lookup(uint8_t *work);
282void protocore_ipsec_db_protocore_ipsec_selector_match(uint8_t *work);
283void protocore_ipsec_db_protocore_ipsec_selector_from_ts(uint8_t *work);
284void protocore_ipsec_db_protocore_ipsec_sad_init(uint8_t *work);
285void protocore_ipsec_db_protocore_ipsec_sad_add(uint8_t *work);
286void protocore_ipsec_db_protocore_ipsec_sad_find(uint8_t *work);
287void protocore_ipsec_db_protocore_ipsec_sad_remove(uint8_t *work);
288void protocore_ipsec_db_protocore_ipsec_sad_next_seq(uint8_t *work);
289
290// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
291// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
292// `IpsecDb.protocore_ipsec_spd_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
293// leaves the call indirect and the symbol live at every level, -O2 -flto included.
294static const IpsecDbNs IpsecDb __attribute__((unused)) = {
295 .protocore_ipsec_spd_init = protocore_ipsec_db_protocore_ipsec_spd_init,
296 .protocore_ipsec_spd_add = protocore_ipsec_db_protocore_ipsec_spd_add,
297 .protocore_ipsec_spd_lookup = protocore_ipsec_db_protocore_ipsec_spd_lookup,
298 .protocore_ipsec_selector_match = protocore_ipsec_db_protocore_ipsec_selector_match,
299 .protocore_ipsec_selector_from_ts = protocore_ipsec_db_protocore_ipsec_selector_from_ts,
300 .protocore_ipsec_sad_init = protocore_ipsec_db_protocore_ipsec_sad_init,
301 .protocore_ipsec_sad_add = protocore_ipsec_db_protocore_ipsec_sad_add,
302 .protocore_ipsec_sad_find = protocore_ipsec_db_protocore_ipsec_sad_find,
303 .protocore_ipsec_sad_remove = protocore_ipsec_db_protocore_ipsec_sad_remove,
304 .protocore_ipsec_sad_next_seq = protocore_ipsec_db_protocore_ipsec_sad_next_seq,
305};
306
308
309#endif // PROTOCORE_ENABLE_IKEV2
310
311#endif // PROTOCORE_IPSEC_DB_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
ESP (RFC 4303) packet transform with AES-256-GCM (RFC 4106) - the IPsec datapath's crypto core.
#define PROTOCORE_ESP_SALT_LEN
Implicit salt length (the tail of the ESP key, not on the wire).
Definition esp.h:47
#define PROTOCORE_ESP_KEY_LEN
AES-256 key length.
Definition esp.h:53
IKEv2 (RFC 7296): the message and payload codec, the key schedule, and the handshake driver.
Anti-replay sliding-window state for one inbound SA (zero-heap).
Definition esp.h:60
#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