ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ipsec_db.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ipsec_db.cpp
6 * @brief IPsec SPD + SAD (RFC 4301) - see ipsec_db.h.
7 */
8
10
11#if PC_ENABLE_IKEV2
12
13#include <string.h>
14
15namespace
16{
17// addr is within the inclusive [lo, hi] range. Addresses are big-endian, so a byte-wise unsigned compare
18// (memcmp) is the numeric compare.
19bool in_range(const uint8_t *addr, const uint8_t *lo, const uint8_t *hi, uint8_t len)
20{
21 return memcmp(addr, lo, len) >= 0 && memcmp(addr, hi, len) <= 0;
22}
23bool port_in(uint16_t p, uint16_t lo, uint16_t hi)
24{
25 return p >= lo && p <= hi;
26}
27} // namespace
28
29// ── SPD ─────────────────────────────────────────────────────────────────────────────────────────
30
31void pc_ipsec_spd_init(IpsecSpd *spd)
32{
33 if (!spd)
34 {
35 return;
36 }
37 spd->count = 0;
38}
39
40bool pc_ipsec_spd_add(IpsecSpd *spd, const IpsecSelector *sel, IpsecAction action, uint32_t sa_spi)
41{
42 if (!spd || !sel || spd->count >= PC_IPSEC_SPD_MAX)
43 {
44 return false;
45 }
46 IpsecPolicy *p = &spd->entries[spd->count];
47 p->sel = *sel;
48 p->action = action;
49 p->sa_spi = (action == IpsecAction::PROTECT) ? sa_spi : 0;
50 spd->count++;
51 return true;
52}
53
54bool pc_ipsec_selector_match(const IpsecSelector *sel, const IpsecFlow *flow)
55{
56 if (!sel || !flow || !flow->src || !flow->dst)
57 {
58 return false;
59 }
60 if (sel->addr_len != flow->addr_len) // different address family
61 {
62 return false;
63 }
64 if (sel->addr_len != 4 && sel->addr_len != 16)
65 {
66 return false;
67 }
68 if (sel->ip_protocol != 0 && sel->ip_protocol != flow->ip_protocol)
69 {
70 return false;
71 }
72 if (!in_range(flow->src, sel->src_lo, sel->src_hi, sel->addr_len))
73 {
74 return false;
75 }
76 if (!in_range(flow->dst, sel->dst_lo, sel->dst_hi, sel->addr_len))
77 {
78 return false;
79 }
80 if (!port_in(flow->src_port, sel->src_port_lo, sel->src_port_hi))
81 {
82 return false;
83 }
84 if (!port_in(flow->dst_port, sel->dst_port_lo, sel->dst_port_hi))
85 {
86 return false;
87 }
88 return true;
89}
90
91const IpsecPolicy *pc_ipsec_spd_lookup(const IpsecSpd *spd, const IpsecFlow *flow)
92{
93 if (!spd || !flow)
94 {
95 return nullptr;
96 }
97 for (size_t i = 0; i < spd->count; i++) // first match wins (order is significant)
98 {
99 if (pc_ipsec_selector_match(&spd->entries[i].sel, flow))
100 {
101 return &spd->entries[i];
102 }
103 }
104 return nullptr;
105}
106
107bool pc_ipsec_selector_from_ts(IpsecSelector *out, const IkeTrafficSelector *ts_src, const IkeTrafficSelector *ts_dst)
108{
109 if (!out || !ts_src || !ts_dst)
110 {
111 return false;
112 }
113 if (ts_src->ts_type != ts_dst->ts_type)
114 {
115 return false;
116 }
117 if (ts_src->addr_len != ts_dst->addr_len || (ts_src->addr_len != 4 && ts_src->addr_len != 16))
118 {
119 return false;
120 }
121 if (!ts_src->start_addr || !ts_src->end_addr || !ts_dst->start_addr || !ts_dst->end_addr)
122 {
123 return false;
124 }
125 // Protocol: honor "any" (0) on either side; if both name a protocol they must agree.
126 if (ts_src->ip_protocol != 0 && ts_dst->ip_protocol != 0 && ts_src->ip_protocol != ts_dst->ip_protocol)
127 {
128 return false;
129 }
130
131 memset(out, 0, sizeof(*out));
132 uint8_t len = (uint8_t)ts_src->addr_len; // addr_len is an IP address length (<= 16), fits a uint8_t
133 out->addr_len = len;
134 out->ip_protocol = ts_src->ip_protocol ? ts_src->ip_protocol : ts_dst->ip_protocol;
135 memcpy(out->src_lo, ts_src->start_addr, len);
136 memcpy(out->src_hi, ts_src->end_addr, len);
137 memcpy(out->dst_lo, ts_dst->start_addr, len);
138 memcpy(out->dst_hi, ts_dst->end_addr, len);
139 out->src_port_lo = ts_src->start_port;
140 out->src_port_hi = ts_src->end_port;
141 out->dst_port_lo = ts_dst->start_port;
142 out->dst_port_hi = ts_dst->end_port;
143 return true;
144}
145
146// ── SAD ─────────────────────────────────────────────────────────────────────────────────────────
147
148void pc_ipsec_sad_init(IpsecSad *sad)
149{
150 if (!sad)
151 {
152 return;
153 }
154 sad->count = 0;
155 for (size_t i = 0; i < PC_IPSEC_SAD_MAX; i++)
156 {
157 sad->entries[i].valid = false;
158 }
159}
160
161IpsecSaEntry *pc_ipsec_sad_add(IpsecSad *sad, uint32_t spi, const uint8_t *dst, uint8_t addr_len,
162 const uint8_t key[PC_ESP_KEY_LEN], const uint8_t salt[PC_ESP_SALT_LEN], bool inbound)
163{
164 if (!sad || !dst || !key || !salt || (addr_len != 4 && addr_len != 16))
165 {
166 return nullptr;
167 }
168 if (pc_ipsec_sad_find(sad, spi)) // SPIs are unique within a SAD
169 {
170 return nullptr;
171 }
172 IpsecSaEntry *e = nullptr;
173 for (size_t i = 0; i < PC_IPSEC_SAD_MAX; i++)
174 {
175 if (!sad->entries[i].valid)
176 {
177 e = &sad->entries[i];
178 break;
179 }
180 }
181 if (!e) // full
182 {
183 return nullptr;
184 }
185
186 memset(e, 0, sizeof(*e));
187 e->spi = spi;
188 e->addr_len = addr_len;
189 memcpy(e->dst, dst, addr_len);
190 memcpy(e->key, key, PC_ESP_KEY_LEN);
191 memcpy(e->salt, salt, PC_ESP_SALT_LEN);
192 e->seq = 0;
193 e->inbound = inbound;
194 if (inbound)
195 {
196 pc_esp_replay_init(&e->replay);
197 }
198 e->valid = true;
199 sad->count++;
200 return e;
201}
202
203IpsecSaEntry *pc_ipsec_sad_find(IpsecSad *sad, uint32_t spi)
204{
205 if (!sad)
206 {
207 return nullptr;
208 }
209 for (size_t i = 0; i < PC_IPSEC_SAD_MAX; i++)
210 {
211 if (sad->entries[i].valid && sad->entries[i].spi == spi)
212 {
213 return &sad->entries[i];
214 }
215 }
216 return nullptr;
217}
218
219bool pc_ipsec_sad_remove(IpsecSad *sad, uint32_t spi)
220{
221 IpsecSaEntry *e = pc_ipsec_sad_find(sad, spi);
222 if (!e)
223 {
224 return false;
225 }
226 memset(e, 0, sizeof(*e)); // wipe the key material with the slot
227 e->valid = false;
228 if (sad->count)
229 {
230 sad->count--;
231 }
232 return true;
233}
234
235bool pc_ipsec_sad_next_seq(IpsecSaEntry *sa, uint32_t *seq_out)
236{
237 if (!sa || !seq_out)
238 {
239 return false;
240 }
241 if (sa->seq == 0xFFFFFFFFu) // counter exhausted - must rekey before sending more (RFC 4303 §3.3.3)
242 {
243 return false;
244 }
245 sa->seq++; // pre-increment: the first packet uses sequence number 1
246 *seq_out = sa->seq;
247 return true;
248}
249
250#endif // PC_ENABLE_IKEV2
IPsec Security Policy Database (SPD) + Security Association Database (SAD) - RFC 4301.