Skip to main content

sozu_lib/protocol/udp/
proxy_protocol.rs

1//! Pure PROXY-protocol-v2 **DGRAM** header builder.
2//!
3//! No reference proxy ships PPv2-over-UDP, but the v2 spec carries a DGRAM
4//! encoding: the version+command byte (offset 12) is `0x21` (v2 + PROXY) and
5//! the family+transport byte (offset 13) is `0x12` for UDP-over-IPv4 or `0x22`
6//! for UDP-over-IPv6 (the low nibble `0x2` = `DGRAM`, vs `0x1` = `STREAM` used
7//! by the TCP serializer in `protocol/proxy_protocol/header.rs`).
8//!
9//! This builds the header for a flow's first upstream datagram with the **real
10//! (pre-NAT) client address** as the PPv2 source. The destination is the
11//! backend address. The header is a cheap prefix the core prepends in place to
12//! the owned payload [`Vec<u8>`].
13//!
14//! Layout (v2):
15//! ```text
16//!  0..12  signature  0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A
17//!  12     version+command  0x21  (v2 | PROXY)
18//!  13     family+transport 0x12 (AF_INET|DGRAM) / 0x22 (AF_INET6|DGRAM)
19//!  14..16 address length (u16 BE): 12 (v4) / 36 (v6)
20//!  16..   src ip, dst ip, src port, dst port  (BE)
21//! ```
22
23use std::net::SocketAddr;
24
25/// v2 signature (12 bytes) shared with the TCP serializer.
26const PP2_SIGNATURE: [u8; 12] = [
27    0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
28];
29
30/// v2 + PROXY command (offset 12).
31const PP2_VER_CMD_PROXY: u8 = 0x21;
32
33/// AF_INET + DGRAM (offset 13, IPv4).
34const PP2_FAM_INET_DGRAM: u8 = 0x12;
35
36/// AF_INET6 + DGRAM (offset 13, IPv6).
37const PP2_FAM_INET6_DGRAM: u8 = 0x22;
38
39/// Build the PPv2 DGRAM header bytes for a datagram whose real client source is
40/// `client` and whose backend destination is `backend`.
41///
42/// When the two addresses are mixed-family (one v4, one v6 — which the UDP
43/// datapath never produces, since the connected upstream socket matches the
44/// backend family) this falls back to an `AF_UNSPEC` / `UNSPEC` header with a
45/// zero-length address block, exactly as the TCP serializer does. The backend
46/// must then treat the connection as un-proxied, per the spec's UNSPEC rule.
47pub fn dgram_header(client: SocketAddr, backend: SocketAddr) -> Vec<u8> {
48    match (client, backend) {
49        (SocketAddr::V4(src), SocketAddr::V4(dst)) => {
50            let mut h = Vec::with_capacity(16 + 12);
51            h.extend_from_slice(&PP2_SIGNATURE);
52            h.push(PP2_VER_CMD_PROXY);
53            h.push(PP2_FAM_INET_DGRAM);
54            h.extend_from_slice(&12u16.to_be_bytes());
55            h.extend_from_slice(&src.ip().octets());
56            h.extend_from_slice(&dst.ip().octets());
57            h.extend_from_slice(&src.port().to_be_bytes());
58            h.extend_from_slice(&dst.port().to_be_bytes());
59            h
60        }
61        (SocketAddr::V6(src), SocketAddr::V6(dst)) => {
62            let mut h = Vec::with_capacity(16 + 36);
63            h.extend_from_slice(&PP2_SIGNATURE);
64            h.push(PP2_VER_CMD_PROXY);
65            h.push(PP2_FAM_INET6_DGRAM);
66            h.extend_from_slice(&36u16.to_be_bytes());
67            h.extend_from_slice(&src.ip().octets());
68            h.extend_from_slice(&dst.ip().octets());
69            h.extend_from_slice(&src.port().to_be_bytes());
70            h.extend_from_slice(&dst.port().to_be_bytes());
71            h
72        }
73        _ => {
74            // Mixed family: emit AF_UNSPEC with no address block.
75            let mut h = Vec::with_capacity(16);
76            h.extend_from_slice(&PP2_SIGNATURE);
77            h.push(PP2_VER_CMD_PROXY);
78            h.push(0x00); // AF_UNSPEC | UNSPEC
79            h.extend_from_slice(&0u16.to_be_bytes());
80            h
81        }
82    }
83}
84
85/// Prepend the PPv2 DGRAM header to an owned payload in place, returning the
86/// header length so the shell can account for the prefix bytes in metrics/logs.
87pub fn prepend_dgram_header(
88    payload: &mut Vec<u8>,
89    client: SocketAddr,
90    backend: SocketAddr,
91) -> usize {
92    let header = dgram_header(client, backend);
93    let header_len = header.len();
94    // Single splice: reserve, shift the payload right, copy the header in front.
95    let mut prefixed = Vec::with_capacity(header_len + payload.len());
96    prefixed.extend_from_slice(&header);
97    prefixed.append(payload);
98    *payload = prefixed;
99    header_len
100}
101
102#[cfg(test)]
103mod tests {
104    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
105
106    use super::*;
107
108    #[test]
109    fn dgram_header_ipv4_exact_bytes() {
110        // client 125.25.10.1:8080  →  backend 10.4.5.8:4200
111        let client = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(125, 25, 10, 1)), 8080);
112        let backend = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 4, 5, 8)), 4200);
113        let header = dgram_header(client, backend);
114        let expected: [u8; 28] = [
115            0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
116            0x0A, // signature
117            0x21, // v2 + PROXY
118            0x12, // AF_INET | DGRAM
119            0x00, 0x0C, // address length = 12
120            0x7D, 0x19, 0x0A, 0x01, // source 125.25.10.1
121            0x0A, 0x04, 0x05, 0x08, // dest 10.4.5.8
122            0x1F, 0x90, // source port 8080
123            0x10, 0x68, // dest port 4200
124        ];
125        assert_eq!(&expected[..], &header[..]);
126        assert_eq!(header.len(), 28);
127    }
128
129    #[test]
130    fn dgram_header_ipv6_exact_bytes() {
131        // client ::1:8080  →  backend ::1:4200
132        let client = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080);
133        let backend = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 4200);
134        let header = dgram_header(client, backend);
135        let expected: [u8; 52] = [
136            0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
137            0x0A, // signature
138            0x21, // v2 + PROXY
139            0x22, // AF_INET6 | DGRAM
140            0x00, 0x24, // address length = 36
141            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
142            0x00, 0x01, // source ::1
143            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
144            0x00, 0x01, // dest ::1
145            0x1F, 0x90, // source port 8080
146            0x10, 0x68, // dest port 4200
147        ];
148        assert_eq!(&expected[..], &header[..]);
149        assert_eq!(header.len(), 52);
150    }
151
152    #[test]
153    fn prepend_shifts_payload() {
154        let client = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 1234);
155        let backend = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(5, 6, 7, 8)), 5678);
156        let mut payload = b"DNSQUERY".to_vec();
157        let n = prepend_dgram_header(&mut payload, client, backend);
158        assert_eq!(n, 28);
159        assert_eq!(&payload[..12], &PP2_SIGNATURE);
160        assert_eq!(payload[12], PP2_VER_CMD_PROXY);
161        assert_eq!(payload[13], PP2_FAM_INET_DGRAM);
162        assert_eq!(&payload[28..], b"DNSQUERY");
163    }
164
165    #[test]
166    fn mixed_family_emits_unspec() {
167        let client = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 1);
168        let backend = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 2);
169        let header = dgram_header(client, backend);
170        assert_eq!(header.len(), 16);
171        assert_eq!(header[12], PP2_VER_CMD_PROXY);
172        assert_eq!(header[13], 0x00); // AF_UNSPEC
173        assert_eq!(&header[14..16], &[0x00, 0x00]); // zero address length
174    }
175}