ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ikev2_natt.h
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 ikev2_natt.h
6 * @brief IKEv2 NAT traversal - NAT detection (RFC 7296 §2.23) + UDP-encapsulation demux (RFC 3948).
7 *
8 * When a NAT sits between two IKE peers it rewrites the outer IP address / UDP port, which breaks ESP's
9 * integrity check over the addresses and hides the peers from each other. IKEv2 detects this by each side
10 * sending, in IKE_SA_INIT, two Notify payloads whose data is a hash binding the SA to the addresses each
11 * side believes are in use:
12 *
13 * NAT_DETECTION_SOURCE_IP = SHA-1( SPIi | SPIr | source IP | source port )
14 * NAT_DETECTION_DESTINATION_IP = SHA-1( SPIi | SPIr | dest IP | dest port )
15 *
16 * The SPIs are taken in header order (initiator then responder). The receiver recomputes each hash over
17 * the addresses it actually observes: a mismatch on the SOURCE hash means the peer's source was translated
18 * (the peer is behind a NAT); a mismatch on the DESTINATION hash means our own address was translated (we
19 * are behind a NAT). When either side is behind a NAT both move ESP into UDP on port 4500 (RFC 3948) and
20 * the NATed side sends keepalives.
21 *
22 * This file is the host-testable NAT-T logic: the detection hash, the two Notify builders (over the tier-1
23 * @ref pc_ike_notify_build), the mismatch tests, and the RFC 3948 marker / keepalive demux. The device
24 * side only has to feed it the observed addresses off the socket.
25 *
26 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef PROTOCORE_IKEV2_NATT_H
31#define PROTOCORE_IKEV2_NATT_H
32
33#include "protocore_config.h"
34
35#if PC_ENABLE_IKEV2
36
38#include <stddef.h>
39#include <stdint.h>
40
41/** @brief NAT_DETECTION_SOURCE_IP notify message type (RFC 7296 §3.10.1). */
42#define PC_IKE_N_NAT_DETECTION_SOURCE_IP 16388
43/** @brief NAT_DETECTION_DESTINATION_IP notify message type (RFC 7296 §3.10.1). */
44#define PC_IKE_N_NAT_DETECTION_DESTINATION_IP 16389
45/** @brief NAT-detection hash length (SHA-1). */
46#define PC_IKE_NATD_HASH_LEN 20
47/** @brief The IKE / ESP UDP port used once NAT-T is active (RFC 3948). */
48#define PC_NATT_PORT 4500
49/** @brief The Non-ESP Marker length: IKE-in-UDP on port 4500 is prefixed with this many zero octets. */
50#define PC_NATT_NON_ESP_MARKER_LEN 4
51/** @brief A NAT-keepalive packet is this single byte (RFC 3948 §4). */
52#define PC_NATT_KEEPALIVE_BYTE 0xFF
53
54/**
55 * @brief Compute a NAT-detection hash: SHA-1( @p init_spi | @p resp_spi | @p ip | @p port ) (RFC 7296 §2.23).
56 * @param ip the address octets (big-endian), @p ip_len long.
57 * @param ip_len 4 (IPv4) or 16 (IPv6).
58 * @param port the UDP port (host order; encoded big-endian into the hash input).
59 * @param out receives @ref PC_IKE_NATD_HASH_LEN bytes.
60 * @return @ref PC_IKE_NATD_HASH_LEN on success, or 0 on a null argument / bad @p ip_len.
61 */
62size_t pc_ike_natd_hash(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
63 const uint8_t *ip, size_t ip_len, uint16_t port, uint8_t out[PC_IKE_NATD_HASH_LEN]);
64
65/**
66 * @brief Build a NAT_DETECTION_SOURCE_IP Notify (the sender's own address) - RFC 7296 §2.23.
67 * @return the payload length written, or 0 on overflow / bad argument.
68 */
69size_t pc_ike_natd_source_build(uint8_t *buf, size_t cap, IkePayloadType next_payload,
70 const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
71 const uint8_t *src_ip, size_t ip_len, uint16_t src_port);
72
73/**
74 * @brief Build a NAT_DETECTION_DESTINATION_IP Notify (the address the sender is sending to) - RFC 7296 §2.23.
75 * @return the payload length written, or 0 on overflow / bad argument.
76 */
77size_t pc_ike_natd_dest_build(uint8_t *buf, size_t cap, IkePayloadType next_payload,
78 const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
79 const uint8_t *dst_ip, size_t ip_len, uint16_t dst_port);
80
81/**
82 * @brief True iff @p hash matches the NAT-detection hash recomputed over the given SPIs and address.
83 *
84 * A NAT-detection payload verifies (no translation on that axis) when this returns true; the two detection
85 * helpers below express what a mismatch means.
86 */
87bool pc_ike_natd_match(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
88 const uint8_t *ip, size_t ip_len, uint16_t port, const uint8_t *hash);
89
90/**
91 * @brief The peer is behind a NAT: the received NAT_DETECTION_SOURCE_IP hash does not match the address the
92 * packet was actually observed to come from (its source was translated in transit).
93 * @param observed_src_ip / @p observed_src_port the packet's real source, as seen on our socket.
94 * @param received_source_hash the NAT_DETECTION_SOURCE_IP payload data from the peer.
95 */
96bool pc_ike_natd_peer_behind_nat(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
97 const uint8_t *observed_src_ip, size_t ip_len, uint16_t observed_src_port,
98 const uint8_t *received_source_hash);
99
100/**
101 * @brief We are behind a NAT: the received NAT_DETECTION_DESTINATION_IP hash does not match our own local
102 * address (the peer sent to a translated destination - our public address differs from our local one).
103 * @param local_ip / @p local_port our own address the packet arrived on.
104 * @param received_dest_hash the NAT_DETECTION_DESTINATION_IP payload data from the peer.
105 */
106bool pc_ike_natd_self_behind_nat(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
107 const uint8_t *local_ip, size_t ip_len, uint16_t local_port,
108 const uint8_t *received_dest_hash);
109
110// ── RFC 3948 UDP-encapsulation demux (port 4500 carries IKE, ESP, and keepalives) ────────────────
111
112/** @brief True iff @p p / @p len is a NAT-keepalive (a single 0xFF octet, RFC 3948 §4). */
113bool pc_natt_is_keepalive(const uint8_t *p, size_t len);
114
115/**
116 * @brief True iff a UDP-4500 payload is an IKE message (it carries the 4-octet zero Non-ESP Marker).
117 *
118 * On port 4500 an ESP packet begins with its SPI (never zero), so a leading 32-bit zero distinguishes an
119 * IKE message; the caller strips @ref PC_NATT_NON_ESP_MARKER_LEN octets before parsing it as IKE.
120 */
121bool pc_natt_is_ike(const uint8_t *p, size_t len);
122
123#endif // PC_ENABLE_IKEV2
124#endif // PROTOCORE_IKEV2_NATT_H
IKEv2 (RFC 7296) message + payload codec (PC_ENABLE_IKEV2) - a zero-heap builder / parser for the Int...
User-facing configuration for ProtoCore.