proxy_protocol_codec/v2/
model.rs

1//! PROXY Protocol v2 header models
2
3#[cfg(feature = "feat-codec-encode")]
4use alloc::vec::Vec;
5use core::net::{Ipv4Addr, Ipv6Addr};
6#[cfg(feature = "feat-codec-v2-uni-addr")]
7use std::io;
8
9#[cfg(feature = "feat-codec-decode")]
10use slicur::Reader;
11
12#[cfg(feature = "feat-codec-decode")]
13use crate::v2::DecodeError;
14
15#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
16/// Fixed version byte for PROXY Protocol v2.
17pub(crate) const BYTE_VERSION: u8 = 0x20;
18
19/// Size of the PROXY Protocol v2 header in bytes.
20pub const HEADER_SIZE: usize = 16;
21
22#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
23/// Size of addresses for IPv4
24pub(crate) const ADDR_INET_SIZE: usize = 12; // 2 * 4 bytes for IPv4 + 2 * 2 bytes for port
25
26#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
27/// Size of addresses for IPv6
28pub(crate) const ADDR_INET6_SIZE: usize = 36; // 2 * 16 bytes for IPv6 + 2 * 2 bytes for port
29
30#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
31/// Size of addresses for Unix sockets
32pub(crate) const ADDR_UNIX_SIZE: usize = 216; // 2 * 108 bytes for Unix socket addresses
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(u8)]
36/// The supported `Command`s for a PROXY protocol header.
37pub enum Command {
38    /// The connection was established on purpose by the proxy
39    /// without being relayed. The connection endpoints are the sender and the
40    /// receiver. Such connections exist when the proxy sends health-checks to
41    /// the server. The receiver must accept this connection as valid and
42    /// must use the real connection endpoints and discard the protocol
43    /// block including the family which is ignored.
44    Local = 0x00,
45
46    /// the connection was established on behalf of another node, and reflects
47    /// the original connection endpoints. The receiver must then use the
48    /// information provided in the protocol block to get original the address.
49    Proxy = 0x01,
50}
51
52#[cfg(any(feature = "feat-codec-encode", feature = "feat-codec-decode"))]
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[repr(u8)]
55/// The address family.
56pub(crate) enum Family {
57    /// The connection is forwarded for an unknown, unspecified or unsupported
58    /// protocol. The sender should use this family when sending LOCAL commands
59    /// or when dealing with unsupported protocol families. The receiver is free
60    /// to accept the connection anyway and use the real endpoint addresses or
61    /// to reject it. The receiver should ignore address information.
62    Unspecified = 0x00,
63
64    /// The forwarded connection uses the `AF_INET` address family (IPv4). The
65    /// addresses are exactly 4 bytes each in network byte order, followed by
66    /// transport protocol information (typically ports).
67    Inet = 0x10,
68
69    /// The forwarded connection uses the `AF_INET6` address family (IPv6). The
70    /// addresses are exactly 16 bytes each in network byte order, followed by
71    /// transport protocol information (typically ports).
72    Inet6 = 0x20,
73
74    /// The forwarded connection uses the `AF_UNIX` address family (UNIX). The
75    /// addresses are exactly 108 bytes each.
76    Unix = 0x30,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[repr(u8)]
81/// The transport protocol.
82pub enum Protocol {
83    /// The connection is forwarded for an unknown, unspecified or unsupported
84    /// protocol. The sender should use this protocol when sending LOCAL
85    /// commands or when dealing with unsupported protocols. The receiver is
86    /// free to accept the connection anyway and use the real endpoint
87    /// addresses or to reject it.
88    Unspecified = 0x00,
89
90    /// The forwarded connection uses a `SOCK_STREAM` protocol (eg: TCP or
91    /// `UNIX_STREAM`). When used with `AF_INET/AF_INET6` (TCP), the addresses
92    /// are followed by the source and destination ports represented on 2
93    /// bytes each in network byte order.
94    Stream = 0x01,
95
96    /// The forwarded connection uses a `SOCK_DGRAM` protocol (eg: UDP or
97    /// `UNIX_DGRAM`). When used with `AF_INET/AF_INET6` (UDP), the addresses
98    /// are followed by the source and destination ports represented on 2
99    /// bytes each in network byte order.
100    Dgram = 0x02,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
104/// The address type, which can be either an IPv4/IPv6 address or a UNIX socket
105/// address.
106pub enum AddressPair {
107    /// Address unspecified
108    Unspecified,
109
110    /// The address is an IPv4 address.
111    Inet {
112        /// SRC IPv4 address.
113        src_ip: Ipv4Addr,
114
115        /// DST IPv4 address.
116        dst_ip: Ipv4Addr,
117
118        /// SRC port.
119        src_port: u16,
120
121        /// DST port.
122        dst_port: u16,
123    },
124
125    /// The address is an IPv6 address.
126    Inet6 {
127        /// SRC IPv4 address.
128        src_ip: Ipv6Addr,
129
130        /// DST IPv4 address.
131        dst_ip: Ipv6Addr,
132
133        /// SRC port.
134        src_port: u16,
135
136        /// DST port.
137        dst_port: u16,
138    },
139
140    /// The address is a UNIX socket address.
141    Unix {
142        /// The src address bytes (with null terminator).
143        src_addr: [u8; 108],
144
145        /// The address bytes (with null terminator).
146        dst_addr: [u8; 108],
147    },
148}
149
150impl AddressPair {
151    #[cfg(feature = "feat-codec-encode")]
152    #[inline]
153    pub(crate) const fn address_family(&self) -> Family {
154        match self {
155            Self::Unspecified => Family::Unspecified,
156            Self::Inet { .. } => Family::Inet,
157            Self::Inet6 { .. } => Family::Inet6,
158            Self::Unix { .. } => Family::Unix,
159        }
160    }
161
162    #[cfg(feature = "feat-codec-v2-uni-addr")]
163    /// Returns the source address.
164    pub fn src_uni_addr(&self) -> io::Result<Option<uni_addr::SocketAddr>> {
165        use core::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
166
167        match self {
168            Self::Unspecified => Ok(None),
169            Self::Inet { src_ip, src_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V4(
170                SocketAddrV4::new(*src_ip, *src_port),
171            )))),
172            Self::Inet6 { src_ip, src_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V6(
173                SocketAddrV6::new(*src_ip, *src_port, 0, 0),
174            )))),
175            #[cfg(unix)]
176            Self::Unix { src_addr, .. } => uni_addr::unix::SocketAddr::from_bytes_until_nul(src_addr)
177                .map(uni_addr::SocketAddr::Unix)
178                .map(Some),
179            #[cfg(not(unix))]
180            Self::Unix { .. } => Err(io::Error::new(
181                io::ErrorKind::Unsupported,
182                "Unix socket addresses are not supported on this platform",
183            )),
184        }
185    }
186
187    #[cfg(feature = "feat-codec-v2-uni-addr")]
188    /// Returns the destination address.
189    pub fn dst_uni_addr(&self) -> io::Result<Option<uni_addr::SocketAddr>> {
190        use core::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
191
192        match self {
193            Self::Unspecified => Ok(None),
194            Self::Inet { dst_ip, dst_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V4(
195                SocketAddrV4::new(*dst_ip, *dst_port),
196            )))),
197            Self::Inet6 { dst_ip, dst_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V6(
198                SocketAddrV6::new(*dst_ip, *dst_port, 0, 0),
199            )))),
200            #[cfg(unix)]
201            Self::Unix { dst_addr, .. } => uni_addr::unix::SocketAddr::from_bytes_until_nul(dst_addr)
202                .map(uni_addr::SocketAddr::Unix)
203                .map(Some),
204            #[cfg(not(unix))]
205            Self::Unix { .. } => Err(io::Error::new(
206                io::ErrorKind::Unsupported,
207                "Unix socket addresses are not supported on this platform",
208            )),
209        }
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214#[repr(u8)]
215/// Supported types for `TypeLengthValue` payloads.
216pub enum ExtensionType {
217    /// Application-Layer Protocol Negotiation (ALPN). It is a byte sequence
218    /// defining the upper layer protocol in use over the connection. The most
219    /// common use case will be to pass the exact copy of the ALPN extension of
220    /// the Transport Layer Security (TLS) protocol as defined by RFC7301.
221    ALPN = 0x01,
222
223    /// Contains the host name value passed by the client, as an UTF8-encoded
224    /// string. In case of TLS being used on the client connection, this is the
225    /// exact copy of the "`server_name`" extension as defined by RFC3546,
226    /// section 3.1, often referred to as "SNI". There are probably other
227    /// situations where an authority can be mentioned on a connection without
228    /// TLS being involved at all.
229    Authority = 0x02,
230
231    /// The value of the type `PP2_TYPE_CRC32C` is a 32-bit number storing the
232    /// `CRC32c` checksum of the PROXY protocol header.
233    ///
234    /// When the checksum is supported by the sender after constructing the
235    /// header the sender MUST:
236    ///
237    ///  - Initialize the checksum field to '0's.
238    ///  - Calculate the `CRC32c` checksum of the PROXY header as described in
239    ///    RFC4960, Appendix B.
240    ///  - Put the resultant value into the checksum field, and leave the rest
241    ///    of the bits unchanged.
242    ///
243    /// If the checksum is provided as part of the PROXY header and the checksum
244    /// functionality is supported by the receiver, the receiver MUST:
245    ///
246    ///  - Store the received `CRC32c` checksum value aside.
247    ///  - Replace the 32 bits of the checksum field in the received PROXY
248    ///    header with all '0's and calculate a `CRC32c` checksum value of the
249    ///    whole PROXY header.
250    ///  - Verify that the calculated `CRC32c` checksum is the same as the
251    ///    received `CRC32c` checksum. If it is not, the receiver MUST treat the
252    ///    TCP connection providing the header as invalid.
253    ///
254    /// The default procedure for handling an invalid TCP connection is to abort
255    /// it.
256    CRC32C = 0x03,
257
258    /// The TLV of this type should be ignored when parsed. The value is zero or
259    /// more bytes. Can be used for data padding or alignment. Note that it can
260    /// be used to align only by 3 or more bytes because a TLV can not be
261    /// smaller than that.
262    NoOp = 0x04,
263
264    /// The value of the type `PP2_TYPE_UNIQUE_ID` is an opaque byte sequence of
265    /// up to 128 bytes generated by the upstream proxy that uniquely identifies
266    /// the connection.
267    ///
268    /// The unique ID can be used to easily correlate connections across
269    /// multiple layers of proxies, without needing to look up IP addresses and
270    /// port numbers.
271    UniqueId = 0x05,
272
273    /// The type `PP2_TYPE_NETNS` defines the value as the US-ASCII string
274    /// representation of the namespace's name.
275    NetworkNamespace = 0x30,
276}
277
278impl ExtensionType {
279    #[inline]
280    const fn from_u8(value: u8) -> Option<Self> {
281        Some(match value {
282            v if v == Self::ALPN as u8 => Self::ALPN,
283            v if v == Self::Authority as u8 => Self::Authority,
284            v if v == Self::CRC32C as u8 => Self::CRC32C,
285            v if v == Self::NoOp as u8 => Self::NoOp,
286            v if v == Self::UniqueId as u8 => Self::UniqueId,
287            v if v == Self::NetworkNamespace as u8 => Self::NetworkNamespace,
288            _ => return None,
289        })
290    }
291}
292
293/// A type-length-value (TLV) extension in the PROXY Protocol v2 header.
294#[derive(Debug, Clone, Copy)]
295pub struct ExtensionRef<'a> {
296    /// The type of the extension.
297    typ: u8,
298
299    #[allow(unused)]
300    /// The length of the value in bytes.
301    len: u16,
302
303    /// The value of the extension.
304    payload: &'a [u8],
305}
306
307impl<'a> ExtensionRef<'a> {
308    #[inline]
309    /// Creates a new `ExtensionRef` from the given given type and payload.
310    ///
311    /// If the length of the payload exceeds `u16::MAX`, returns `None`.
312    pub const fn new(typ: ExtensionType, payload: &'a [u8]) -> Option<Self> {
313        Self::new_custom(typ as u8, payload)
314    }
315
316    #[inline]
317    /// Creates a new `ExtensionRef` from the given custom type and payload.
318    pub const fn new_custom(typ: u8, payload: &'a [u8]) -> Option<Self> {
319        let len = payload.len();
320
321        if len > u16::MAX as usize {
322            return None; // Length exceeds maximum allowed size
323        }
324
325        Some(Self {
326            typ,
327            len: len as u16,
328            payload,
329        })
330    }
331
332    #[inline]
333    /// Returns the type of the extension.
334    ///
335    /// If the type is not recognized, returns an `Err` with the raw type byte.
336    pub const fn typ(&self) -> Result<ExtensionType, u8> {
337        match ExtensionType::from_u8(self.typ) {
338            Some(typ) => Ok(typ),
339            None => Err(self.typ),
340        }
341    }
342
343    #[inline]
344    /// Returns the payload of the extension.
345    pub const fn payload(&self) -> &'a [u8] {
346        self.payload
347    }
348
349    #[inline]
350    #[cfg(feature = "feat-codec-encode")]
351    pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
352        buf.reserve(self.len as usize);
353        buf.push(self.typ);
354        buf.extend(&self.len.to_be_bytes());
355        buf.extend(self.payload);
356    }
357
358    #[inline]
359    #[cfg(feature = "feat-codec-decode")]
360    /// Decodes a single "type-length-value" extension from the provided reader.
361    ///
362    /// # Safety
363    ///
364    /// The caller must validate the header's total length before calling this
365    /// method. Returns `Err(())` if the header is malformed or corrupted.
366    pub(crate) fn decode(reader: &mut Reader<'a>) -> Result<Option<Self>, DecodeError> {
367        let Ok(typ) = reader.read_u8() else {
368            // No more extensions to read
369            return Ok(None);
370        };
371        let Ok(len) = reader.read_u16() else {
372            return Err(DecodeError::MalformedData);
373        };
374        let Ok(payload) = reader.take(len as usize) else {
375            return Err(DecodeError::MalformedData);
376        };
377
378        Ok(Some(Self { typ, len, payload }))
379    }
380}