DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
proxy_protocol.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 proxy_protocol.cpp
6 * @brief HAProxy PROXY protocol v1 / v2 parser + builder (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_PROXY_PROTOCOL
12
13#include <stdio.h> // snprintf for the v1 dotted-quad text
14#include <string.h>
15
16static const uint8_t kV2Sig[PROXY_V2_SIG_LEN] = {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D,
17 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A};
18
19static uint16_t rd16(const uint8_t *p)
20{
21 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
22}
23
24static uint32_t rd32(const uint8_t *p)
25{
26 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
27}
28
29// Parse a dotted-quad IPv4 in [s, s+n) into a host-order uint32; false on malformed.
30static bool parse_ipv4(const char *s, size_t n, uint32_t *out)
31{
32 uint32_t v = 0;
33 int octets = 0;
34 size_t i = 0;
35 while (octets < 4)
36 {
37 if (i >= n || s[i] < '0' || s[i] > '9')
38 return false;
39 uint32_t o = 0;
40 size_t digits = 0;
41 while (i < n && s[i] >= '0' && s[i] <= '9')
42 {
43 o = o * 10 + (uint32_t)(s[i] - '0');
44 i++;
45 if (++digits > 3 || o > 255)
46 return false;
47 }
48 v = (v << 8) | o;
49 octets++;
50 if (octets < 4)
51 {
52 if (i >= n || s[i] != '.')
53 return false;
54 i++;
55 }
56 }
57 if (i != n) // trailing junk
58 return false;
59 *out = v;
60 return true;
61}
62
63static bool parse_u16(const char *s, size_t n, uint16_t *out)
64{
65 if (n == 0 || n > 5)
66 return false;
67 uint32_t v = 0;
68 for (size_t i = 0; i < n; i++)
69 {
70 if (s[i] < '0' || s[i] > '9')
71 return false;
72 v = v * 10 + (uint32_t)(s[i] - '0');
73 }
74 if (v > 0xFFFF)
75 return false;
76 *out = (uint16_t)v;
77 return true;
78}
79
80// Parse the v1 text header (already known to start with "PROXY ").
81static bool parse_v1(const uint8_t *buf, size_t len, ProxyInfo *out, size_t *consumed)
82{
83 // Find the terminating CRLF (the line is bounded at 107 octets).
84 size_t crlf = len;
85 size_t scan = len < 108 ? len : 108;
86 for (size_t i = 0; i + 1 < scan; i++)
87 if (buf[i] == '\r' && buf[i + 1] == '\n')
88 {
89 crlf = i;
90 break;
91 }
92 if (crlf == len)
93 return false; // line not complete
94
95 const char *s = (const char *)buf;
96 // Tokenize the line by single spaces.
97 const char *tok[6];
98 size_t tlen[6];
99 size_t ntok = 0;
100 size_t i = 0;
101 while (i < crlf && ntok < 6)
102 {
103 while (i < crlf && s[i] == ' ')
104 i++;
105 if (i >= crlf)
106 break;
107 size_t start = i;
108 while (i < crlf && s[i] != ' ')
109 i++;
110 tok[ntok] = s + start;
111 tlen[ntok] = i - start;
112 ntok++;
113 }
114 out->version = 1;
115 out->has_addr = false;
116 out->src_addr = out->dst_addr = 0;
117 out->src_port = out->dst_port = 0;
118 *consumed = crlf + 2;
119 // "PROXY TCP4 <src> <dst> <sport> <dport>"; anything else (UNKNOWN/TCP6) yields no addr.
120 if (ntok == 6 && tlen[1] == 4 && memcmp(tok[1], "TCP4", 4) == 0)
121 {
122 if (parse_ipv4(tok[2], tlen[2], &out->src_addr) && parse_ipv4(tok[3], tlen[3], &out->dst_addr) &&
123 parse_u16(tok[4], tlen[4], &out->src_port) && parse_u16(tok[5], tlen[5], &out->dst_port))
124 out->has_addr = true;
125 }
126 return true;
127}
128
129bool proxy_parse(const uint8_t *buf, size_t len, ProxyInfo *out, size_t *consumed)
130{
131 if (!buf || !out || !consumed)
132 return false;
133
134 // v2: the 12-octet binary signature.
135 if (len >= PROXY_V2_SIG_LEN && memcmp(buf, kV2Sig, PROXY_V2_SIG_LEN) == 0)
136 {
137 if (len < 16) // signature + ver_cmd + fam + 2-octet length
138 return false;
139 uint8_t ver_cmd = buf[12];
140 uint8_t fam = buf[13];
141 uint16_t addr_len = rd16(buf + 14);
142 size_t total = 16 + (size_t)addr_len;
143 if (total > len)
144 return false; // address block not fully buffered
145 if ((ver_cmd & 0xF0) != 0x20) // must be version 2
146 return false;
147 out->version = 2;
148 out->has_addr = false;
149 out->src_addr = out->dst_addr = 0;
150 out->src_port = out->dst_port = 0;
151 if (ver_cmd == PROXY_V2_VER_CMD_PROXY && fam == PROXY_V2_FAM_TCP4 && addr_len >= 12)
152 {
153 out->src_addr = rd32(buf + 16);
154 out->dst_addr = rd32(buf + 20);
155 out->src_port = rd16(buf + 24);
156 out->dst_port = rd16(buf + 26);
157 out->has_addr = true;
158 }
159 *consumed = total;
160 return true;
161 }
162
163 // v1: the "PROXY " text prefix.
164 if (len >= 6 && memcmp(buf, "PROXY ", 6) == 0)
165 return parse_v1(buf, len, out, consumed);
166
167 return false; // no PROXY header present
168}
169
170size_t proxy_v1_build(char *buf, size_t cap, uint32_t src_addr, uint32_t dst_addr, uint16_t src_port, uint16_t dst_port)
171{
172 if (!buf)
173 return 0;
174 int n = snprintf(buf, cap, "PROXY TCP4 %u.%u.%u.%u %u.%u.%u.%u %u %u\r\n", (unsigned)((src_addr >> 24) & 0xFF),
175 (unsigned)((src_addr >> 16) & 0xFF), (unsigned)((src_addr >> 8) & 0xFF),
176 (unsigned)(src_addr & 0xFF), (unsigned)((dst_addr >> 24) & 0xFF),
177 (unsigned)((dst_addr >> 16) & 0xFF), (unsigned)((dst_addr >> 8) & 0xFF),
178 (unsigned)(dst_addr & 0xFF), (unsigned)src_port, (unsigned)dst_port);
179 if (n < 0 || (size_t)n >= cap) // snprintf truncated (no room for the content + NUL)
180 return 0;
181 return (size_t)n;
182}
183
184size_t proxy_v2_build(uint8_t *buf, size_t cap, uint32_t src_addr, uint32_t dst_addr, uint16_t src_port,
185 uint16_t dst_port)
186{
187 const size_t total = 16 + 12; // header + TCP/IPv4 address block
188 if (!buf || cap < total)
189 return 0;
190 memcpy(buf, kV2Sig, PROXY_V2_SIG_LEN);
191 buf[12] = PROXY_V2_VER_CMD_PROXY;
192 buf[13] = PROXY_V2_FAM_TCP4;
193 buf[14] = 0x00; // address-block length (12), big-endian
194 buf[15] = 0x0C;
195 buf[16] = (uint8_t)(src_addr >> 24);
196 buf[17] = (uint8_t)(src_addr >> 16);
197 buf[18] = (uint8_t)(src_addr >> 8);
198 buf[19] = (uint8_t)(src_addr);
199 buf[20] = (uint8_t)(dst_addr >> 24);
200 buf[21] = (uint8_t)(dst_addr >> 16);
201 buf[22] = (uint8_t)(dst_addr >> 8);
202 buf[23] = (uint8_t)(dst_addr);
203 buf[24] = (uint8_t)(src_port >> 8);
204 buf[25] = (uint8_t)(src_port);
205 buf[26] = (uint8_t)(dst_port >> 8);
206 buf[27] = (uint8_t)(dst_port);
207 return total;
208}
209
210#endif // DETWS_ENABLE_PROXY_PROTOCOL
HAProxy PROXY protocol codec (DETWS_ENABLE_PROXY_PROTOCOL) - zero-heap parser + builder for the v1 (t...