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 pub fn src_uni_addr(&self) -> io::Result<Option<uni_addr::SocketAddr>> {
205 use core::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
206
207 match self {
208 Self::Unspecified => Ok(None),
209 Self::Inet { src_ip, src_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V4(
210 SocketAddrV4::new(*src_ip, *src_port),
211 )))),
212 Self::Inet6 { src_ip, src_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V6(
213 SocketAddrV6::new(*src_ip, *src_port, 0, 0),
214 )))),
215 #[cfg(unix)]
216 Self::Unix { src_addr, .. } => uni_addr::unix::SocketAddr::from_bytes_until_nul(src_addr)
217 .map(uni_addr::SocketAddr::Unix)
218 .map(Some),
219 #[cfg(not(unix))]
220 Self::Unix { .. } => Err(io::Error::new(
221 io::ErrorKind::Unsupported,
222 "Unix socket addresses are not supported on this platform",
223 )),
224 }
225 }
226
227 #[cfg(feature = "feat-uni-addr")]
228 /// Returns the destination address.
229 pub fn dst_uni_addr(&self) -> io::Result<Option<uni_addr::SocketAddr>> {
230 use core::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
231
232 match self {
233 Self::Unspecified => Ok(None),
234 Self::Inet { dst_ip, dst_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V4(
235 SocketAddrV4::new(*dst_ip, *dst_port),
236 )))),
237 Self::Inet6 { dst_ip, dst_port, .. } => Ok(Some(uni_addr::SocketAddr::Inet(SocketAddr::V6(
238 SocketAddrV6::new(*dst_ip, *dst_port, 0, 0),
239 )))),
240 #[cfg(unix)]
241 Self::Unix { dst_addr, .. } => uni_addr::unix::SocketAddr::from_bytes_until_nul(dst_addr)
242 .map(uni_addr::SocketAddr::Unix)
243 .map(Some),
244 #[cfg(not(unix))]
245 Self::Unix { .. } => Err(io::Error::new(
246 io::ErrorKind::Unsupported,
247 "Unix socket addresses are not supported on this platform",
248 )),
249 }
250 }
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254#[repr(u8)]
255/// Supported types for `TypeLengthValue` payloads.
256pub enum ExtensionType {
257 /// Application-Layer Protocol Negotiation (ALPN). It is a byte sequence
258 /// defining the upper layer protocol in use over the connection. The most
259 /// common use case will be to pass the exact copy of the ALPN extension of
260 /// the Transport Layer Security (TLS) protocol as defined by RFC7301.
261 ALPN = 0x01,
262
263 /// Contains the host name value passed by the client, as an UTF8-encoded
264 /// string. In case of TLS being used on the client connection, this is the
265 /// exact copy of the "`server_name`" extension as defined by RFC3546,
266 /// section 3.1, often referred to as "SNI". There are probably other
267 /// situations where an authority can be mentioned on a connection without
268 /// TLS being involved at all.
269 Authority = 0x02,
270
271 /// The value of the type `PP2_TYPE_CRC32C` is a 32-bit number storing the
272 /// `CRC32c` checksum of the PROXY protocol header.
273 ///
274 /// When the checksum is supported by the sender after constructing the
275 /// header the sender MUST:
276 ///
277 /// - Initialize the checksum field to '0's.
278 /// - Calculate the `CRC32c` checksum of the PROXY header as described in
279 /// RFC4960, Appendix B.
280 /// - Put the resultant value into the checksum field, and leave the rest
281 /// of the bits unchanged.
282 ///
283 /// If the checksum is provided as part of the PROXY header and the checksum
284 /// functionality is supported by the receiver, the receiver MUST:
285 ///
286 /// - Store the received `CRC32c` checksum value aside.
287 /// - Replace the 32 bits of the checksum field in the received PROXY
288 /// header with all '0's and calculate a `CRC32c` checksum value of the
289 /// whole PROXY header.
290 /// - Verify that the calculated `CRC32c` checksum is the same as the
291 /// received `CRC32c` checksum. If it is not, the receiver MUST treat the
292 /// TCP connection providing the header as invalid.
293 ///
294 /// The default procedure for handling an invalid TCP connection is to abort
295 /// it.
296 CRC32C = 0x03,
297
298 /// The TLV of this type should be ignored when parsed. The value is zero or
299 /// more bytes. Can be used for data padding or alignment. Note that it can
300 /// be used to align only by 3 or more bytes because a TLV can not be
301 /// smaller than that.
302 NoOp = 0x04,
303
304 /// The value of the type `PP2_TYPE_UNIQUE_ID` is an opaque byte sequence of
305 /// up to 128 bytes generated by the upstream proxy that uniquely identifies
306 /// the connection.
307 ///
308 /// The unique ID can be used to easily correlate connections across
309 /// multiple layers of proxies, without needing to look up IP addresses and
310 /// port numbers.
311 UniqueId = 0x05,
312
313 /// The type `PP2_TYPE_NETNS` defines the value as the US-ASCII string
314 /// representation of the namespace's name.
315 NetworkNamespace = 0x30,
316}
317
318impl ExtensionType {
319 #[inline]
320 const fn from_u8(value: u8) -> Option<Self> {
321 Some(match value {
322 v if v == Self::ALPN as u8 => Self::ALPN,
323 v if v == Self::Authority as u8 => Self::Authority,
324 v if v == Self::CRC32C as u8 => Self::CRC32C,
325 v if v == Self::NoOp as u8 => Self::NoOp,
326 v if v == Self::UniqueId as u8 => Self::UniqueId,
327 v if v == Self::NetworkNamespace as u8 => Self::NetworkNamespace,
328 _ => return None,
329 })
330 }
331}
332
333/// A type-length-value (TLV) extension in the PROXY Protocol v2 header.
334#[derive(Debug, Clone, Copy)]
335pub struct ExtensionRef<'a> {
336 /// The type of the extension.
337 typ: u8,
338
339 #[allow(unused)]
340 /// The length of the value in bytes.
341 len: u16,
342
343 /// The value of the extension.
344 payload: &'a [u8],
345}
346
347impl<'a> ExtensionRef<'a> {
348 #[inline]
349 /// Creates a new `ExtensionRef` from the given given type and payload.
350 ///
351 /// If the length of the payload exceeds `u16::MAX`, returns `None`.
352 pub const fn new(typ: ExtensionType, payload: &'a [u8]) -> Option<Self> {
353 Self::new_custom(typ as u8, payload)
354 }
355
356 #[inline]
357 /// Creates a new `ExtensionRef` from the given custom type and payload.
358 pub const fn new_custom(typ: u8, payload: &'a [u8]) -> Option<Self> {
359 let len = payload.len();
360
361 if len > u16::MAX as usize {
362 return None; // Length exceeds maximum allowed size
363 }
364
365 Some(Self {
366 typ,
367 len: len as u16,
368 payload,
369 })
370 }
371
372 #[inline]
373 /// Returns the type of the extension.
374 ///
375 /// If the type is not recognized, returns an `Err` with the raw type byte.
376 pub const fn typ(&self) -> Result<ExtensionType, u8> {
377 match ExtensionType::from_u8(self.typ) {
378 Some(typ) => Ok(typ),
379 None => Err(self.typ),
380 }
381 }
382
383 #[inline]
384 /// Returns the payload of the extension.
385 pub const fn payload(&self) -> &'a [u8] {
386 self.payload
387 }
388
389 #[inline]
390 #[cfg(feature = "feat-codec-encode")]
391 pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
392 buf.reserve(self.len as usize);
393 buf.push(self.typ);
394 buf.extend(&self.len.to_be_bytes());
395 buf.extend(self.payload);
396 }
397
398 #[inline]
399 #[cfg(feature = "feat-codec-decode")]
400 /// Decodes a single "type-length-value" extension from the provided reader.
401 ///
402 /// # Safety
403 ///
404 /// The caller must validate the header's total length before calling this
405 /// method. Returns `Err(())` if the header is malformed or corrupted.
406 pub(crate) fn decode(reader: &mut Reader<'a>) -> Result<Option<Self>, DecodeError> {
407 let Ok(typ) = reader.read_u8() else {
408 // No more extensions to read
409 return Ok(None);
410 };
411 let Ok(len) = reader.read_u16() else {
412 return Err(DecodeError::MalformedData);
413 };
414 let Ok(payload) = reader.take(len as usize) else {
415 return Err(DecodeError::MalformedData);
416 };
417
418 Ok(Some(Self { typ, len, payload }))
419 }
420}