ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ikev2_natt.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 ikev2_natt.h
6 * @brief IKEv2 NAT traversal: NAT detection (RFC 7296 sec 2.23) and the UDP encapsulation demux
7 * (RFC 3948 sec 2).
8 *
9 * RFC 7296 sec 2.23: both peers put NAT_DETECTION_SOURCE_IP and NAT_DETECTION_DESTINATION_IP Notify
10 * payloads in their IKE_SA_INIT messages, just after Ni and Nr. The data of the first is a SHA-1
11 * digest of the SPIs in the order they appear in the header, the IP address, and the port the packet
12 * was sent from; the data of the second is the same digest over the address and port it was sent to.
13 * The Notify Message Types are 16388 and 16389 (sec 3.10.1).
14 *
15 * A recipient recomputes each digest over the addresses it actually observes. No match on any
16 * received NAT_DETECTION_SOURCE_IP means the peer's source was translated, so the peer is behind a
17 * NAT. A mismatching NAT_DETECTION_DESTINATION_IP means this system is behind a NAT and should send
18 * the keepalives of RFC 3948. Once a NAT is detected both peers move to port 4500 and encapsulate
19 * ESP in UDP.
20 *
21 * RFC 3948 sec 2.2: an IKE message on port 4500 is prefixed with the Non-ESP Marker, four zero
22 * octets aligned with the SPI field of an ESP packet, and sec 2.1 requires that SPI to be non-zero,
23 * so the marker separates IKE from ESP. RFC 3948 sec 2.3: a NAT-keepalive is a one octet payload
24 * with the value 0xFF.
25 *
26 * The module exports one symbol, @ref IkeNatt. Everything in ikev2_natt.c has internal linkage.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef PROTOCORE_IKEV2_NATT_H
33#define PROTOCORE_IKEV2_NATT_H
34
35#include "protocore_config.h" // the entry point: protocore_types.h for the widths
36
37#if PROTOCORE_ENABLE_IKEV2
38
40
41#include "services/security/ikev2/ikev2/ikev2.h" // IkePayloadType: the Next Payload a detection Notify carries
42// ---------------------------------------------------------------------------
43// Literals
44// ---------------------------------------------------------------------------
45
46/** @brief NAT_DETECTION_SOURCE_IP Notify Message Type (RFC 7296 sec 3.10.1). */
47#define PROTOCORE_IKE_N_NAT_DETECTION_SOURCE_IP 16388
48/** @brief NAT_DETECTION_DESTINATION_IP Notify Message Type (RFC 7296 sec 3.10.1). */
49#define PROTOCORE_IKE_N_NAT_DETECTION_DESTINATION_IP 16389
50/** @brief Length of the SHA-1 digest a detection payload carries (RFC 7296 sec 2.23). */
51#define PROTOCORE_IKE_NATD_HASH_LEN 20
52/** @brief The UDP port reserved for UDP-encapsulated ESP and IKE (RFC 3948 sec 2.1, sec 2.2). */
53#define PROTOCORE_NATT_PORT 4500
54/** @brief Non-ESP Marker length: four zero octets before an IKE message on port 4500 (RFC 3948 sec 2.2). */
55#define PROTOCORE_NATT_NON_ESP_MARKER_LEN 4
56/** @brief The single octet a NAT-keepalive carries (RFC 3948 sec 2.3). */
57#define PROTOCORE_NATT_KEEPALIVE_BYTE 0xFF
58
59// ---------------------------------------------------------------------------
60// Typedefs
61// ---------------------------------------------------------------------------
62
63/** @brief The SPIs a digest covers, in the order they appear in the header (RFC 7296 sec 2.23). */
64typedef struct
65{
66 const uint8_t *init_spi; ///< IKE SA Initiator's SPI
67 const uint8_t *resp_spi; ///< IKE SA Responder's SPI
68} IkeNattSpiArgs;
69
70/** @brief The address and port a digest covers (RFC 7296 sec 2.23). */
71typedef struct
72{
73 const uint8_t *ip; ///< the address octets, big endian
74 size_t ip_len; ///< 4 for IPv4 or 16 for IPv6
75 uint16_t port; ///< the UDP port, host order, encoded big endian into the digest
76} IkeNattAddrArgs;
77
78/** @brief Where a digest lands, and the one a compare judges (RFC 7296 sec 2.23). */
79typedef struct
80{
81 uint8_t *out; ///< receives PROTOCORE_IKE_NATD_HASH_LEN octets
82 const uint8_t *received; ///< the Notification Data from the peer
83} IkeNattDigestArgs;
84
85/** @brief Where a Notify payload is written (RFC 7296 sec 3.10). */
86typedef struct
87{
88 uint8_t *buf; ///< where the payload is written
89 size_t cap; ///< room there
90 IkePayloadType next_payload; ///< Next Payload: the type of the payload that follows this one
91} IkeNattOutArgs;
92
93/** @brief The UDP payload the port 4500 demux judges (RFC 3948 sec 2.1, 2.2, 2.3). */
94typedef struct
95{
96 const uint8_t *p; ///< the datagram payload
97 size_t len; ///< its length
98} IkeNattPktArgs;
99
100/**
101 * @brief The IKEv2 NAT traversal handle (RFC 7296 sec 2.23, RFC 3948).
102 *
103 * A caller sets the members a call takes, invokes it through ::IkeNatt, and reads the outcome off
104 * the same handle.
105 *
106 * No storage member: the addresses come off the socket and the payload buffers are the caller's, so
107 * nothing survives a call.
108 *
109 * @var IkeNattNs::spi the SPIs a digest covers, in header order
110 * @var IkeNattNs::addr the address and port a digest covers
111 * @var IkeNattNs::digest where a digest lands, and the one a compare judges
112 * @var IkeNattNs::out where a Notify payload is written
113 * @var IkeNattNs::pkt the UDP payload the port 4500 demux judges
114 * @var IkeNattNs::ok a call's true/false outcome
115 * @var IkeNattNs::n octets written, zero on failure
116 * @var IkeNattNs::hash SHA-1(SPIi | SPIr | IP | Port) into @c digest.out (sec 2.23)
117 * @var IkeNattNs::source_build write a NAT_DETECTION_SOURCE_IP Notify over the sender's own address
118 * @var IkeNattNs::dest_build write a NAT_DETECTION_DESTINATION_IP Notify over the address sent to
119 * @var IkeNattNs::match @c digest.received equals the digest over @c spi and @c addr
120 * @var IkeNattNs::peer_behind_nat the received source digest does not match the observed source
121 * @var IkeNattNs::self_behind_nat the received destination digest does not match our own address
122 * @var IkeNattNs::is_keepalive the payload is the one octet 0xFF (RFC 3948 sec 2.3)
123 * @var IkeNattNs::is_ike the payload carries the Non-ESP Marker (RFC 3948 sec 2.2)
124 */
125typedef struct
126{
127 IkeNattSpiArgs spi; ///< the SPIs a digest covers (sec 2.23)
128 IkeNattAddrArgs addr; ///< the address and port a digest covers (sec 2.23)
129 IkeNattDigestArgs digest; ///< where a digest lands and the one a compare judges
130 IkeNattOutArgs out; ///< where a Notify payload is written (sec 3.10)
131 IkeNattPktArgs pkt; ///< the UDP payload the demux judges (RFC 3948 sec 2)
132 proto_bool ok;
133 size_t n;
134} IkeNattVars;
135
136/** @brief The operands and the outcome. */
137extern IkeNattVars IkeNattV;
138
139/** @brief The entries. */
140typedef struct
141{
142 void (*const hash)(uint8_t *work);
143 void (*const source_build)(uint8_t *work);
144 void (*const dest_build)(uint8_t *work);
145 void (*const match)(uint8_t *work);
146 void (*const peer_behind_nat)(uint8_t *work);
147 void (*const self_behind_nat)(uint8_t *work);
148 void (*const is_keepalive)(uint8_t *work);
149 void (*const is_ike)(uint8_t *work);
150} IkeNattNs;
151
152// What the table binds, defined once in the .c and taking one parameter each: everything
153// else an entry needs is an operand in IkeNattV or a region of the borrow at a fixed offset.
154void protocore_ike_natt_hash(uint8_t *work);
155void protocore_ike_natt_source_build(uint8_t *work);
156void protocore_ike_natt_dest_build(uint8_t *work);
157void protocore_ike_natt_match(uint8_t *work);
158void protocore_ike_natt_peer_behind_nat(uint8_t *work);
159void protocore_ike_natt_self_behind_nat(uint8_t *work);
160void protocore_ike_natt_is_keepalive(uint8_t *work);
161void protocore_ike_natt_is_ike(uint8_t *work);
162
163// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
164// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
165// `IkeNatt.hash(work)` resolves to a named function and becomes a DIRECT call. An extern table
166// leaves the call indirect and the symbol live at every level, -O2 -flto included.
167static const IkeNattNs IkeNatt __attribute__((unused)) = {
168 .hash = protocore_ike_natt_hash,
169 .source_build = protocore_ike_natt_source_build,
170 .dest_build = protocore_ike_natt_dest_build,
171 .match = protocore_ike_natt_match,
172 .peer_behind_nat = protocore_ike_natt_peer_behind_nat,
173 .self_behind_nat = protocore_ike_natt_self_behind_nat,
174 .is_keepalive = protocore_ike_natt_is_keepalive,
175 .is_ike = protocore_ike_natt_is_ike,
176};
177
179
180#endif // PROTOCORE_ENABLE_IKEV2
181
182#endif // PROTOCORE_IKEV2_NATT_H
IKEv2 (RFC 7296): the message and payload codec, the key schedule, and the handshake driver.
#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