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