ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ikev2_natt.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 ikev2_natt.cpp
6 * @brief IKEv2 NAT traversal (RFC 7296 §2.23 / RFC 3948) - see ikev2_natt.h.
7 */
8
10
11#if PC_ENABLE_IKEV2
12
13#include "crypto/hash/sha1.h"
14#include <string.h>
15
16namespace
17{
18// SPIi(8) | SPIr(8) | IP(4 or 16) | Port(2) - the largest NAT-detection hash input.
19#define PC_NATD_INPUT_MAX (PC_IKE_SPI_LEN + PC_IKE_SPI_LEN + 16 + 2)
20
21// Assemble the hash input; returns its length, or 0 on a bad address length.
22size_t natd_input(uint8_t *buf, const uint8_t *init_spi, const uint8_t *resp_spi, const uint8_t *ip, size_t ip_len,
23 uint16_t port)
24{
25 if (!init_spi || !resp_spi || !ip || (ip_len != 4 && ip_len != 16))
26 {
27 return 0;
28 }
29 size_t n = 0;
30 memcpy(buf + n, init_spi, PC_IKE_SPI_LEN);
31 n += PC_IKE_SPI_LEN;
32 memcpy(buf + n, resp_spi, PC_IKE_SPI_LEN);
33 n += PC_IKE_SPI_LEN;
34 memcpy(buf + n, ip, ip_len);
35 n += ip_len;
36 buf[n++] = (uint8_t)(port >> 8);
37 buf[n++] = (uint8_t)port;
38 return n;
39}
40} // namespace
41
42size_t pc_ike_natd_hash(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
43 const uint8_t *ip, size_t ip_len, uint16_t port, uint8_t out[PC_IKE_NATD_HASH_LEN])
44{
45 if (!out)
46 {
47 return 0;
48 }
49 uint8_t in[PC_NATD_INPUT_MAX];
50 size_t n = natd_input(in, init_spi, resp_spi, ip, ip_len, port);
51 if (n == 0)
52 {
53 return 0;
54 }
55 pc_sha1(in, n, out);
56 return PC_IKE_NATD_HASH_LEN;
57}
58
59size_t pc_ike_natd_source_build(uint8_t *buf, size_t cap, IkePayloadType next_payload,
60 const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
61 const uint8_t *src_ip, size_t ip_len, uint16_t src_port)
62{
63 uint8_t hash[PC_IKE_NATD_HASH_LEN];
64 if (pc_ike_natd_hash(init_spi, resp_spi, src_ip, ip_len, src_port, hash) == 0)
65 {
66 return 0;
67 }
68 return pc_ike_notify_build(buf, cap, next_payload, IkeProtocol::IKE_PROTO_NONE, nullptr, 0,
69 PC_IKE_N_NAT_DETECTION_SOURCE_IP, hash, PC_IKE_NATD_HASH_LEN);
70}
71
72size_t pc_ike_natd_dest_build(uint8_t *buf, size_t cap, IkePayloadType next_payload,
73 const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
74 const uint8_t *dst_ip, size_t ip_len, uint16_t dst_port)
75{
76 uint8_t hash[PC_IKE_NATD_HASH_LEN];
77 if (pc_ike_natd_hash(init_spi, resp_spi, dst_ip, ip_len, dst_port, hash) == 0)
78 {
79 return 0;
80 }
81 return pc_ike_notify_build(buf, cap, next_payload, IkeProtocol::IKE_PROTO_NONE, nullptr, 0,
82 PC_IKE_N_NAT_DETECTION_DESTINATION_IP, hash, PC_IKE_NATD_HASH_LEN);
83}
84
85bool pc_ike_natd_match(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
86 const uint8_t *ip, size_t ip_len, uint16_t port, const uint8_t *hash)
87{
88 if (!hash)
89 {
90 return false;
91 }
92 uint8_t expect[PC_IKE_NATD_HASH_LEN];
93 if (pc_ike_natd_hash(init_spi, resp_spi, ip, ip_len, port, expect) == 0)
94 {
95 return false;
96 }
97 return memcmp(expect, hash, PC_IKE_NATD_HASH_LEN) == 0;
98}
99
100bool pc_ike_natd_peer_behind_nat(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
101 const uint8_t *observed_src_ip, size_t ip_len, uint16_t observed_src_port,
102 const uint8_t *received_source_hash)
103{
104 return !pc_ike_natd_match(init_spi, resp_spi, observed_src_ip, ip_len, observed_src_port, received_source_hash);
105}
106
107bool pc_ike_natd_self_behind_nat(const uint8_t init_spi[PC_IKE_SPI_LEN], const uint8_t resp_spi[PC_IKE_SPI_LEN],
108 const uint8_t *local_ip, size_t ip_len, uint16_t local_port,
109 const uint8_t *received_dest_hash)
110{
111 return !pc_ike_natd_match(init_spi, resp_spi, local_ip, ip_len, local_port, received_dest_hash);
112}
113
114bool pc_natt_is_keepalive(const uint8_t *p, size_t len)
115{
116 return p && len == 1 && p[0] == PC_NATT_KEEPALIVE_BYTE;
117}
118
119bool pc_natt_is_ike(const uint8_t *p, size_t len)
120{
121 if (!p || len < PC_NATT_NON_ESP_MARKER_LEN)
122 {
123 return false;
124 }
125 // The 4-octet Non-ESP Marker is all zero; a real ESP packet starts with a nonzero SPI.
126 return p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0;
127}
128
129#endif // PC_ENABLE_IKEV2
IKEv2 NAT traversal - NAT detection (RFC 7296 §2.23) + UDP-encapsulation demux (RFC 3948).
PC_CRYPTO_HOT void pc_sha1(const uint8_t *data, size_t len, uint8_t digest[PC_SHA1_DIGEST_LEN])
Compute a SHA-1 digest over an arbitrary byte buffer.
Definition sha1.cpp:26
SHA-1 (FIPS 180-4) - one-shot digest.