sozu_lib/protocol/proxy_protocol/parser.rs
1//! PROXY-v2 wire parser.
2//!
3//! Decodes the 12-byte PROXY-v2 signature plus the variable address block
4//! defined by the HAProxy PROXY protocol specification. Returns a
5//! `HeaderV2` to the caller (`expect.rs` / `relay.rs`); rejects malformed
6//! framing through `nom::Err` so the session can close cleanly. No I/O.
7
8use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
9
10use nom::{
11 Err, IResult,
12 bytes::streaming::{tag, take},
13 error::{Error, ErrorKind, ParseError},
14 number::streaming::{be_u8, be_u16},
15};
16
17use crate::protocol::proxy_protocol::header::{Command, HeaderV2, ProxyAddr};
18
19const PROTOCOL_SIGNATURE_V2: [u8; 12] = [
20 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
21];
22
23fn parse_command(i: &[u8]) -> IResult<&[u8], Command> {
24 let i2 = i;
25 let (i, cmd) = be_u8(i)?;
26 // `be_u8` consumes exactly one byte on success; the remainder must be one
27 // shorter than the input we were given.
28 debug_assert_eq!(
29 i.len() + 1,
30 i2.len(),
31 "command byte parse consumes exactly one byte"
32 );
33 match cmd {
34 0x20 => Ok((i, Command::Local)),
35 0x21 => Ok((i, Command::Proxy)),
36 _ => Err(Err::Error(Error::from_error_kind(i2, ErrorKind::Switch))),
37 }
38}
39
40pub fn parse_v2_header(i: &[u8]) -> IResult<&[u8], HeaderV2> {
41 let input_len = i.len();
42 let (i, _) = tag(&PROTOCOL_SIGNATURE_V2[..])(i)?;
43 let (i, command) = parse_command(i)?;
44 let (i, family) = be_u8(i)?;
45 let (i, len) = be_u16(i)?;
46 let (i, data) = take(len)(i)?;
47 // The length field gates exactly `len` address bytes; `take` already
48 // enforces availability, so `data` is precisely that many bytes.
49 debug_assert_eq!(
50 data.len(),
51 len as usize,
52 "address block must be exactly the declared length"
53 );
54 let (data_rest, parsed_addr) = parse_addr_v2(family)(data)?;
55 // The address parser may not consume every advertised byte (TLVs / over-
56 // long length fields are tolerated by leaving a tail), but it must never
57 // read past the block it was handed.
58 debug_assert!(
59 data_rest.len() <= data.len(),
60 "address parser cannot grow its input"
61 );
62
63 // Postcondition: a parser never grows its input — the unconsumed remainder
64 // is no longer than the original, and the consumed prefix (the full v2
65 // header) is the 16-byte fixed part plus the declared address length.
66 debug_assert!(i.len() <= input_len, "parser cannot grow its input");
67 debug_assert_eq!(
68 input_len - i.len(),
69 PROTOCOL_SIGNATURE_V2.len() + 1 + 1 + 2 + len as usize,
70 "consumed header length must reconcile with the signature, fixed fields, and declared address length"
71 );
72 // The family byte is in the enumerated set the address parser accepts: a
73 // rejected family would have short-circuited via `?` above, so on this
74 // success path the high nibble is AF_UNSPEC/INET/INET6.
75 debug_assert!(
76 matches!((family >> 4) & 0x0f, 0x00..=0x02),
77 "accepted family nibble must be AF_UNSPEC, AF_INET, or AF_INET6"
78 );
79
80 // HAProxy PROXY protocol specification, §2.2: `LOCAL` describes a
81 // connection the upstream proxy originated on its own behalf (typically a
82 // health check), and the receiver must use the real socket endpoints and
83 // ignore the header's address block. Nothing on the wire ties `LOCAL` to
84 // `AF_UNSPEC` -- only HAProxy's own emitter happens to pair them -- so a
85 // crafted peer can send `LOCAL` with a fully populated `AF_INET` /
86 // `AF_INET6` block. `parse_addr_v2` switches on the family nibble alone,
87 // so that block used to reach every consumer of `HeaderV2::addr`. There are
88 // six of them, not three. Four are TCP-side and read this `ProxyAddr`
89 // directly: `expect.rs`, `relay.rs`, `tcp_preread/mod.rs`, and
90 // `TcpSession::effective_session_address` (`lib/src/tcp.rs`), which reads
91 // `ExpectProxyProtocol::addresses` / `RelayProxyProtocol::addresses` itself
92 // rather than through `into_pipe` and feeds the raw-TCP
93 // `max_connections_per_ip` gate in `TcpSession::connect_to_backend`
94 // (`lib/src/tcp.rs`). The other two are the HTTP and HTTPS expect upgrades,
95 // which read the same value back out of `ExpectProxyProtocol::addresses` in
96 // `HttpSession::upgrade_expect` (`lib/src/http.rs`) and
97 // `HttpsSession::upgrade` (`lib/src/https.rs`). All six attribute
98 // `ProxyAddr::source()` to the client: any peer could therefore forge the
99 // source address Sōzu records in its access logs, injects as `X-Real-IP`,
100 // and counts against `max_connections_per_ip`.
101 //
102 // Discarding it here rather than at each of those six keeps one decision
103 // in one place: none of them reads `command` or `family`, none
104 // re-serializes the parsed header (`relay.rs` forwards the buffered bytes
105 // verbatim), and all six obtain the address exclusively through this
106 // `ProxyAddr`.
107 //
108 // `AfUnspec` is a variant all six already handle, but they do NOT handle
109 // it alike, and that split is the operator-visible behaviour:
110 //
111 // * the four TCP-side consumers fall back to the front socket's
112 // `peer_addr`, so the session proceeds, attributed to the real peer;
113 // * the HTTP and HTTPS expect upgrades require BOTH endpoints and get
114 // neither -- `AfUnspec` returns `None` from `ProxyAddr::source()` and
115 // `ProxyAddr::destination()` alike
116 // (`lib/src/protocol/proxy_protocol/header.rs`) -- so `upgrade_expect`
117 // returns `None` and `HttpSession::upgrade` reports `SessionIsToBeClosed`
118 // (`lib/src/http.rs`). Such a session is closed at the expect stage, not
119 // re-attributed to `peer_addr`.
120 //
121 // That close is not a regression for legitimate traffic: HAProxy pairs
122 // `LOCAL` with `AF_UNSPEC`, which already parsed to `AfUnspec`, so those
123 // sessions already closed. Only the forged populated block changes, from
124 // "upgrade with attacker-chosen addresses" to "close".
125 //
126 // The block is still parsed above, not skipped: a `LOCAL` header with a
127 // malformed or unknown family must keep being rejected, and its declared
128 // length must keep delimiting the header, so framing is unchanged.
129 // Matched exhaustively on purpose: a future command must make an explicit
130 // attribution decision rather than inherit `PROXY`'s by default.
131 let addr = match command {
132 Command::Local => ProxyAddr::AfUnspec,
133 Command::Proxy => parsed_addr,
134 };
135
136 Ok((
137 i,
138 (HeaderV2 {
139 command,
140 family,
141 addr,
142 }),
143 ))
144}
145
146fn parse_addr_v2(family: u8) -> impl Fn(&[u8]) -> IResult<&[u8], ProxyAddr> {
147 move |i: &[u8]| match (family >> 4) & 0x0f {
148 0x00 => Ok((i, ProxyAddr::AfUnspec)),
149 0x01 => parse_ipv4_on_v2(i),
150 0x02 => parse_ipv6_on_v2(i),
151 _ => Err(Err::Error(Error::from_error_kind(i, ErrorKind::Switch))),
152 }
153}
154
155fn parse_ipv4_on_v2(i: &[u8]) -> IResult<&[u8], ProxyAddr> {
156 let in_len = i.len();
157 let (i, src_ip) = take(4u8)(i)?;
158 let (i, dest_ip) = take(4u8)(i)?;
159 let (i, src_port) = be_u16(i)?;
160 let (i, dest_port) = be_u16(i)?;
161 // An IPv4 v2 address block is fixed at 4 + 4 + 2 + 2 = 12 bytes; the parser
162 // must have consumed exactly that on success and never grown its input.
163 debug_assert_eq!(src_ip.len(), 4, "IPv4 source address is 4 bytes");
164 debug_assert_eq!(dest_ip.len(), 4, "IPv4 destination address is 4 bytes");
165 debug_assert!(i.len() <= in_len, "parser cannot grow its input");
166 debug_assert_eq!(
167 in_len - i.len(),
168 12,
169 "IPv4 v2 address block is exactly 12 bytes"
170 );
171
172 Ok((
173 i,
174 ProxyAddr::Ipv4Addr {
175 src_addr: SocketAddrV4::new(
176 Ipv4Addr::new(src_ip[0], src_ip[1], src_ip[2], src_ip[3]),
177 src_port,
178 ),
179 dst_addr: SocketAddrV4::new(
180 Ipv4Addr::new(dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3]),
181 dest_port,
182 ),
183 },
184 ))
185}
186
187fn parse_ipv6_on_v2(i: &[u8]) -> IResult<&[u8], ProxyAddr> {
188 let in_len = i.len();
189 let (i, src_ip) = take(16u8)(i)?;
190 let (i, dest_ip) = take(16u8)(i)?;
191 let (i, src_port) = be_u16(i)?;
192 let (i, dest_port) = be_u16(i)?;
193 // An IPv6 v2 address block is fixed at 16 + 16 + 2 + 2 = 36 bytes; `take`
194 // guarantees the slice widths fed to `slice_to_ipv6`, so its 16-byte
195 // precondition holds on this path.
196 debug_assert_eq!(src_ip.len(), 16, "IPv6 source address is 16 bytes");
197 debug_assert_eq!(dest_ip.len(), 16, "IPv6 destination address is 16 bytes");
198 debug_assert!(i.len() <= in_len, "parser cannot grow its input");
199 debug_assert_eq!(
200 in_len - i.len(),
201 36,
202 "IPv6 v2 address block is exactly 36 bytes"
203 );
204
205 Ok((
206 i,
207 ProxyAddr::Ipv6Addr {
208 src_addr: SocketAddrV6::new(slice_to_ipv6(src_ip), src_port, 0, 0),
209 dst_addr: SocketAddrV6::new(slice_to_ipv6(dest_ip), dest_port, 0, 0),
210 },
211 ))
212}
213
214// assumes the slice has 16 bytes
215pub fn slice_to_ipv6(sl: &[u8]) -> Ipv6Addr {
216 // Precondition (internal invariant): every caller hands a slice sized by a
217 // `take(16)`, so a wrong length here is a logic bug, not attacker input.
218 // `clone_from_slice` below would itself panic on a mismatch; the
219 // debug_assert names the contract loudly in debug/test/fuzz builds.
220 debug_assert_eq!(sl.len(), 16, "slice_to_ipv6 requires exactly 16 bytes");
221 let mut arr: [u8; 16] = [0; 16];
222 arr.clone_from_slice(sl);
223 Ipv6Addr::from(arr)
224}
225
226#[cfg(test)]
227mod test {
228
229 use std::net::{IpAddr, SocketAddr};
230
231 use nom::{Err, Needed};
232
233 use super::*;
234
235 /// A `LOCAL` header describes a connection the upstream proxy originated
236 /// itself (typically a health check); the HAProxy PROXY protocol
237 /// specification §2.2 requires the receiver to discard its address block.
238 /// Nothing on the wire forces a `LOCAL` header to carry `AF_UNSPEC` --
239 /// only HAProxy's own emitter happens to pair the two -- so a crafted peer
240 /// may send `LOCAL` with a fully populated `AF_INET` block. Before the fix
241 /// `parse_addr_v2` switched on the family nibble alone and handed that
242 /// block back as a real address pair, letting any peer forge the source
243 /// address Sōzu attributes to the client (access logs, `X-Real-IP`,
244 /// `max_connections_per_ip`).
245 ///
246 /// The block is still parsed -- a malformed one must still be rejected,
247 /// see `it_should_not_parse_proxy_protocol_with_unknown_family` -- but its
248 /// contents are dropped.
249 ///
250 /// To SEE THIS RED: in [`super::parse_v2_header`], replace the
251 /// `Command::Local => ProxyAddr::AfUnspec` arm of the `addr` binding with
252 /// `parsed_addr` (the pre-fix behaviour) -- the parser then returns the
253 /// forged `125.25.10.1:8080` instead of `AfUnspec`.
254 #[test]
255 fn local_command_discards_a_populated_ipv4_address_block() {
256 let input = &[
257 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
258 0x0A, // MAGIC header
259 0x20, // Version 2 and command LOCAL
260 0x11, // family AF_INET over STREAM
261 0x00, 0x0C, // address sizes = 12
262 0x7D, 0x19, 0x0A, 0x01, // forged source address
263 0x0A, 0x04, 0x05, 0x08, // forged destination address
264 0x1F, 0x90, // forged source port
265 0x10, 0x68, // forged destination port
266 ];
267
268 // The family byte is reported as it was read off the wire; only the
269 // address block it describes is discarded.
270 let expected = HeaderV2 {
271 command: Command::Local,
272 family: 0x11,
273 addr: ProxyAddr::AfUnspec,
274 };
275
276 assert_eq!(Ok((&[][..], expected)), parse_v2_header(input));
277
278 let (_, header) = parse_v2_header(input).expect("the header itself stays well-formed");
279 assert_eq!(
280 header.addr.source(),
281 None,
282 "a LOCAL header must attribute no source address, whatever its address block holds"
283 );
284 assert_eq!(
285 header.addr.destination(),
286 None,
287 "a LOCAL header must attribute no destination address either"
288 );
289 }
290
291 /// Same crafted block behind the `PROXY` command: the fix must not
292 /// silently disable PROXY-protocol address attribution.
293 ///
294 /// To SEE THIS RED: make the `addr` binding in [`super::parse_v2_header`]
295 /// unconditionally `ProxyAddr::AfUnspec`.
296 #[test]
297 fn proxy_command_still_honours_the_same_address_block() {
298 let input = &[
299 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
300 0x0A, // MAGIC header
301 0x21, // Version 2 and command PROXY
302 0x11, // family AF_INET over STREAM
303 0x00, 0x0C, // address sizes = 12
304 0x7D, 0x19, 0x0A, 0x01, // source address
305 0x0A, 0x04, 0x05, 0x08, // destination address
306 0x1F, 0x90, // source port
307 0x10, 0x68, // destination port
308 ];
309
310 let src_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(125, 25, 10, 1)), 8080);
311 let (_, header) = parse_v2_header(input).expect("a PROXY header must still parse");
312 assert_eq!(
313 header.addr.source(),
314 Some(src_addr),
315 "a PROXY header still carries the encapsulated client address"
316 );
317 }
318
319 /// The IPv6 half of `local_command_discards_a_populated_ipv4_address_block`.
320 ///
321 /// To SEE THIS RED: same mutation -- restore `parsed_addr` unconditionally
322 /// in [`super::parse_v2_header`].
323 #[test]
324 fn local_command_discards_a_populated_ipv6_address_block() {
325 let input = &[
326 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
327 0x0A, // MAGIC header
328 0x20, // Version 2 and command LOCAL
329 0x21, // family AF_INET6 over STREAM
330 0x00, 0x24, // address sizes = 36
331 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
332 0x00, 0x01, // forged source address
333 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
334 0x00, 0x02, // forged destination address
335 0x1F, 0x90, // forged source port
336 0x10, 0x68, // forged destination port
337 ];
338
339 let expected = HeaderV2 {
340 command: Command::Local,
341 family: 0x21,
342 addr: ProxyAddr::AfUnspec,
343 };
344
345 assert_eq!(Ok((&[][..], expected)), parse_v2_header(input));
346 }
347
348 #[test]
349 fn test_parse_proxy_protocol_v2_proxy_ipv4_addr_header() {
350 let input = &[
351 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
352 0x0A, // MAGIC header
353 0x21, // Version 2 and command PROXY
354 0x11, // family AF_INET over STREAM
355 0x00, 0x0C, // address sizes = 12
356 0x7D, 0x19, 0x0A, 0x01, // source address
357 0x0A, 0x04, 0x05, 0x08, // destination address
358 0x1F, 0x90, // source port
359 0x10, 0x68, // destination port
360 ];
361
362 let src_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(125, 25, 10, 1)), 8080);
363 let dst_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 4, 5, 8)), 4200);
364 let expected = HeaderV2::new(Command::Proxy, src_addr, dst_addr);
365
366 assert_eq!(Ok((&[][..], expected)), parse_v2_header(input));
367 }
368
369 #[test]
370 fn it_should_parse_proxy_protocol_v2_ipv6_addr_header() {
371 let input = &[
372 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
373 0x0A, // MAGIC header
374 0x21, // Version 2 and command PROXY
375 0x21, // family AF_INET6 over STREAM
376 0x00, 0x24, // address sizes = 36
377 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
378 0x00, 0x01, // source address
379 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
380 0x00, 0x02, // destination address
381 0x1F, 0x90, // source port
382 0x10, 0x68, // destination port
383 ];
384
385 let src_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080);
386 let dst_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2)), 4200);
387 // Driven with the PROXY command: a LOCAL header no longer yields an
388 // address pair at all (see
389 // `local_command_discards_a_populated_ipv6_address_block`), so this
390 // test would otherwise stop covering IPv6 address-block decoding.
391 let expected = HeaderV2::new(Command::Proxy, src_addr, dst_addr);
392
393 assert_eq!(Ok((&[][..], expected)), parse_v2_header(input));
394 }
395
396 #[test]
397 fn it_should_parse_proxy_protocol_v2_afunspec_header() {
398 let input = &[
399 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
400 0x0A, // MAGIC header
401 0x20, // Version 2 and command LOCAL
402 0x00, // family AF_UNSPEC and transport protocol unknown
403 0x00, 0x00, // address sizes = 0
404 ];
405
406 let expected = HeaderV2 {
407 command: Command::Local,
408 family: 0,
409 addr: ProxyAddr::AfUnspec,
410 };
411
412 assert_eq!(Ok((&[][..], expected)), parse_v2_header(input));
413 }
414
415 #[test]
416 fn it_should_not_parse_proxy_protocol_v2_with_unknown_version() {
417 let unknow_version = 0x30;
418
419 let input = &[
420 0x0D,
421 0x0A,
422 0x0D,
423 0x0A,
424 0x00,
425 0x0D,
426 0x0A,
427 0x51,
428 0x55,
429 0x49,
430 0x54,
431 0x0A, // MAGIC header
432 unknow_version, // invalid version
433 ];
434
435 assert!(parse_v2_header(input).is_err());
436 }
437
438 #[test]
439 fn it_should_not_parse_proxy_protocol_v2_with_unknown_command() {
440 let unknow_command = 0x23;
441
442 let input = &[
443 0x0D,
444 0x0A,
445 0x0D,
446 0x0A,
447 0x00,
448 0x0D,
449 0x0A,
450 0x51,
451 0x55,
452 0x49,
453 0x54,
454 0x0A, // MAGIC header
455 unknow_command, // Version 2 and invalid command
456 ];
457
458 assert!(parse_v2_header(input).is_err());
459 }
460
461 #[test]
462 fn it_should_not_parse_proxy_protocol_with_unknown_family() {
463 let unknow_family = 0x30;
464
465 let input = &[
466 0x0D,
467 0x0A,
468 0x0D,
469 0x0A,
470 0x00,
471 0x0D,
472 0x0A,
473 0x51,
474 0x55,
475 0x49,
476 0x54,
477 0x0A, // MAGIC header
478 0x20, // Version 2 and command LOCAL
479 unknow_family, // family
480 0x00,
481 0x00, // address sizes = 0
482 ];
483
484 assert!(parse_v2_header(input).is_err());
485 }
486
487 #[test]
488 fn it_should_not_parse_request_without_magic_header() {
489 let input = &[
490 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
491 0x0D, // INCORRECT MAGIC header
492 ];
493
494 assert!(parse_v2_header(input).is_err());
495 }
496
497 #[test]
498 fn it_should_not_parse_proxy_protocol_v2_ipv4_addr_header_with_missing_data() {
499 let input = &[
500 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
501 0x0A, // MAGIC header
502 0x20, // Version 2 and command LOCAL
503 0x11, // family AF_INET over STREAM
504 ];
505
506 assert_eq!(Err(Err::Incomplete(Needed::new(2))), parse_v2_header(input));
507 }
508
509 #[test]
510 fn it_should_not_parse_proxy_protocol_v2_with_invalid_length() {
511 let input = &[
512 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
513 0x0A, // MAGIC header
514 0x20, // Version 2 and command LOCAL
515 0x21, // family AF_INET6 over STREAM
516 0x00, 0x10, // address sizes = 36
517 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
518 0x00, 0x01, // source address
519 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
520 0x00, 0x02, // destination address
521 0x1F, 0x90, // source port
522 0x10, 0x68, // destination port
523 ];
524
525 assert_eq!(
526 Err(Err::Incomplete(Needed::new(16))),
527 parse_v2_header(input)
528 );
529 }
530}