ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
forwarded_trust.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
5
6#if PC_ENABLE_FORWARDED_TRUST
7
8namespace
9{
10
11struct pc_forwarded_trust_rule
12{
13 pc_ip network; // network address (family V4/V6; PC_NONE marks unused).
14 uint8_t prefix_len; // CIDR prefix length: 0..32 for v4, 0..128 for v6.
15};
16
17// Trusted-upstream state, owned by one instance (internal linkage): the CIDR rule table and its
18// count (empty = trust no forwarded header). One named owner, unreachable from any other unit.
19struct pc_forwarded_trust_ctx
20{
21 pc_forwarded_trust_rule rules[PC_TRUSTED_PROXY_MAX];
22 uint8_t count = 0;
23};
24pc_forwarded_trust_ctx s_trust;
25
26} // namespace
27
28void pc_forwarded_trust_reset(void)
29{
30 s_trust.count = 0;
31}
32
33bool pc_forwarded_trust_add(const pc_ip *network, uint8_t prefix_len)
34{
35 if (!network)
36 {
37 return false;
38 }
39 int bits = -1; // stays negative for a family we do not recognize
40 if (network->family == pc_ip_family::PC_IP_V4)
41 {
42 bits = 32;
43 }
44 else if (network->family == pc_ip_family::PC_IP_V6)
45 {
46 bits = 128;
47 }
48 if (bits < 0 || prefix_len > (uint8_t)bits)
49 {
50 return false; // reject a malformed family or an over-long prefix
51 }
52 if (s_trust.count >= PC_TRUSTED_PROXY_MAX)
53 {
54 return false;
55 }
56 s_trust.rules[s_trust.count].network = *network;
57 s_trust.rules[s_trust.count].prefix_len = prefix_len;
58 s_trust.count++;
59 return true;
60}
61
62bool pc_forwarded_trust_add_cidr(const char *cidr)
63{
64 if (!cidr)
65 {
66 return false;
67 }
68
69 // Split "address/prefix" at the slash. The address half is copied into a bounded buffer (a CIDR
70 // string is never longer than an address plus "/128") for the parser.
71 char addr[PC_IP_STR_MAX];
72 const char *slash = nullptr;
73 size_t n = 0;
74 for (const char *p = cidr; *p; p++)
75 {
76 if (*p == '/')
77 {
78 slash = p;
79 break;
80 }
81 if (n + 1 >= sizeof(addr))
82 {
83 return false; // address text too long to be valid
84 }
85 addr[n++] = *p;
86 }
87 addr[n] = '\0';
88
89 pc_ip net;
91 if (!pc_ip_parse(addr, &net))
92 {
93 return false;
94 }
95
96 uint8_t width = (net.family == pc_ip_family::PC_IP_V4) ? 32 : 128;
97 uint8_t prefix = width; // bare address -> host route
98 if (slash)
99 {
100 // Parse the decimal prefix by hand (no stdlib in src/); reject empty or non-digit.
101 uint32_t v = 0;
102 const char *p = slash + 1;
103 if (!*p)
104 {
105 return false;
106 }
107 for (; *p; p++)
108 {
109 if (*p < '0' || *p > '9')
110 {
111 return false;
112 }
113 v = v * 10 + (uint32_t)(*p - '0');
114 if (v > width)
115 {
116 return false; // out of range for the family
117 }
118 }
119 prefix = (uint8_t)v;
120 }
121
122 return pc_forwarded_trust_add(&net, prefix);
123}
124
125bool pc_forwarded_trust_contains(const pc_ip *peer)
126{
127 if (!peer)
128 {
129 return false;
130 }
131 for (uint8_t i = 0; i < s_trust.count; i++)
132 {
133 if (pc_ip_prefix_match(peer, &s_trust.rules[i].network, s_trust.rules[i].prefix_len))
134 {
135 return true;
136 }
137 }
138 return false;
139}
140
141bool pc_forwarded_effective_ip(const pc_ip *peer, const char *fwd_ip_str, pc_ip *out)
142{
143 if (!out)
144 {
145 return false;
146 }
147 if (peer)
148 {
149 *out = *peer; // default: the real TCP source
150 }
151 else
152 {
154 }
155
156 if (!peer || !pc_forwarded_trust_contains(peer))
157 {
158 return false; // peer is not a trusted upstream -> ignore the spoofable header
159 }
160 if (!fwd_ip_str || !fwd_ip_str[0])
161 {
162 return false; // no forwarded client present
163 }
164
165 pc_ip fip;
167 if (!pc_ip_parse(fwd_ip_str, &fip) || pc_ip_is_unspecified(&fip))
168 {
169 return false; // malformed / obfuscated / unspecified -> keep the proxy's address
170 }
171
172 *out = fip;
173 return true;
174}
175
176#endif // PC_ENABLE_FORWARDED_TRUST
Trusted-reverse-proxy resolution of a forwarded client address (PC_ENABLE_FORWARDED_TRUST).
bool pc_ip_prefix_match(const pc_ip *addr, const pc_ip *net, uint8_t prefix_len)
CIDR containment: is addr inside the net / prefix_len block?
Definition ip.cpp:662
bool pc_ip_parse(const char *s, pc_ip *out)
Parse an IPv4 or IPv6 textual address (RFC 4291 ยง2.2) into out.
Definition ip.cpp:436
bool pc_ip_is_unspecified(const pc_ip *ip)
True if ip is empty (pc_ip_family::PC_IP_NONE) or the all-zero unspecified address (0....
Definition ip.cpp:645
@ PC_IP_NONE
empty / unparsed
@ PC_IP_V6
IPv6 (bytes[0..15])
@ PC_IP_V4
IPv4 (bytes[0..3])
#define PC_IP_STR_MAX
Longest text an pc_ip_format can produce, including the NUL (RFC 5952 v4-mapped).
Definition ip.h:58
#define PC_TRUSTED_PROXY_MAX
Number of trusted-upstream CIDR rules the forwarded-client resolver holds (BSS table).
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52
pc_ip_family family
address family tag
Definition ip.h:53