haproxy_protocol/
lib.rs

1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![deny(clippy::todo)]
4#![deny(clippy::unimplemented)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::expect_used)]
7#![deny(clippy::panic)]
8#![deny(clippy::unreachable)]
9#![deny(clippy::await_holding_lock)]
10#![deny(clippy::needless_pass_by_value)]
11#![deny(clippy::trivially_copy_pass_by_ref)]
12
13use crate::parse::{parse_proxy_hdr_v1, parse_proxy_hdr_v2};
14use std::num::NonZeroUsize;
15
16const NZ_ONE: NonZeroUsize = NonZeroUsize::new(1).expect("Invalid compile time constant");
17
18mod parse;
19
20#[derive(Debug, PartialEq, Eq, Clone, Copy)]
21#[repr(u8)]
22enum Protocol {
23    Unspec = 0x00,
24    TcpV4 = 0x11,
25    UdpV4 = 0x12,
26    TcpV6 = 0x21,
27    UdpV6 = 0x22,
28    // UnixStream = 0x31,
29    // UnixDgram = 0x32,
30}
31
32#[derive(Debug, PartialEq, Eq, Clone, Copy)]
33#[repr(u8)]
34enum Command {
35    Local = 0x00,
36    Proxy = 0x01,
37}
38
39#[derive(Debug, PartialEq, Eq, Clone)]
40enum Address {
41    None,
42    V4 {
43        src: std::net::SocketAddrV4,
44        dst: std::net::SocketAddrV4,
45    },
46    V6 {
47        src: std::net::SocketAddrV6,
48        dst: std::net::SocketAddrV6,
49    },
50    // Unix {
51    //     src: PathBuf,
52    //     dst: PathBuf,
53    // }
54}
55
56#[derive(Debug, Clone)]
57pub enum RemoteAddress {
58    Local,
59    Invalid,
60    TcpV4 {
61        src: std::net::SocketAddrV4,
62        dst: std::net::SocketAddrV4,
63    },
64    UdpV4 {
65        src: std::net::SocketAddrV4,
66        dst: std::net::SocketAddrV4,
67    },
68    TcpV6 {
69        src: std::net::SocketAddrV6,
70        dst: std::net::SocketAddrV6,
71    },
72    UdpV6 {
73        src: std::net::SocketAddrV6,
74        dst: std::net::SocketAddrV6,
75    },
76}
77
78#[derive(Debug)]
79pub enum Error {
80    Incomplete { need: NonZeroUsize },
81    Invalid,
82    UnableToComplete,
83}
84
85#[derive(Debug, Clone)]
86pub struct ProxyHdrV2 {
87    command: Command,
88    protocol: Protocol,
89    // address_family: AddressFamily,
90    // length: u16,
91    address: Address,
92}
93
94impl ProxyHdrV2 {
95    pub fn parse(input_data: &[u8]) -> Result<(usize, Self), Error> {
96        match parse_proxy_hdr_v2(input_data) {
97            Ok((remainder, hdr)) => {
98                let took = input_data.len() - remainder.len();
99                Ok((took, hdr))
100            }
101            Err(nom::Err::Incomplete(nom::Needed::Size(need))) => Err(Error::Incomplete { need }),
102            // We always know exactly how much is needed for hdr v2
103            Err(nom::Err::Incomplete(nom::Needed::Unknown)) => Err(Error::UnableToComplete),
104
105            Err(nom::Err::Error(err)) => {
106                tracing::error!(?err);
107                Err(Error::Invalid)
108            }
109            Err(nom::Err::Failure(err)) => {
110                tracing::error!(?err);
111                Err(Error::Invalid)
112            }
113        }
114    }
115
116    pub fn to_remote_addr(self) -> RemoteAddress {
117        match (self.command, self.protocol, self.address) {
118            (Command::Local, _, _) => RemoteAddress::Local,
119            (Command::Proxy, Protocol::TcpV4, Address::V4 { src, dst }) => {
120                RemoteAddress::TcpV4 { src, dst }
121            }
122            (Command::Proxy, Protocol::UdpV4, Address::V4 { src, dst }) => {
123                RemoteAddress::UdpV4 { src, dst }
124            }
125            (Command::Proxy, Protocol::TcpV6, Address::V6 { src, dst }) => {
126                RemoteAddress::TcpV6 { src, dst }
127            }
128            (Command::Proxy, Protocol::UdpV6, Address::V6 { src, dst }) => {
129                RemoteAddress::UdpV6 { src, dst }
130            }
131            _ => RemoteAddress::Invalid,
132        }
133    }
134}
135
136#[derive(Debug, Clone)]
137pub struct ProxyHdrV1 {
138    protocol: Protocol,
139    address: Address,
140}
141
142impl ProxyHdrV1 {
143    pub fn parse(input_data: &[u8]) -> Result<(usize, Self), Error> {
144        match parse_proxy_hdr_v1(input_data) {
145            Ok((remainder, hdr)) => {
146                let took = input_data.len() - remainder.len();
147                Ok((took, hdr))
148            }
149            Err(nom::Err::Incomplete(nom::Needed::Size(need))) => Err(Error::Incomplete { need }),
150            // We aren't sure how much we need but we need *something*.
151            Err(nom::Err::Incomplete(nom::Needed::Unknown)) => {
152                Err(Error::Incomplete { need: NZ_ONE })
153            }
154
155            Err(nom::Err::Error(err)) => {
156                tracing::error!(?err);
157                Err(Error::Invalid)
158            }
159            Err(nom::Err::Failure(err)) => {
160                tracing::error!(?err);
161                Err(Error::Invalid)
162            }
163        }
164    }
165
166    pub fn to_remote_addr(self) -> RemoteAddress {
167        match (self.protocol, self.address) {
168            (Protocol::TcpV4, Address::V4 { src, dst }) => RemoteAddress::TcpV4 { src, dst },
169            (Protocol::UdpV4, Address::V4 { src, dst }) => RemoteAddress::UdpV4 { src, dst },
170            (Protocol::TcpV6, Address::V6 { src, dst }) => RemoteAddress::TcpV6 { src, dst },
171            (Protocol::UdpV6, Address::V6 { src, dst }) => RemoteAddress::UdpV6 { src, dst },
172            _ => RemoteAddress::Invalid,
173        }
174    }
175}
176
177#[cfg(feature = "tokio")]
178#[derive(Debug)]
179pub enum AsyncReadError {
180    Io(std::io::Error),
181    Invalid,
182    UnableToComplete,
183    RequestTooLarge,
184    InconsistentRead,
185}
186
187#[cfg(feature = "tokio")]
188impl ProxyHdrV2 {
189    pub async fn parse_from_read<S>(mut stream: S) -> Result<(S, ProxyHdrV2), AsyncReadError>
190    where
191        S: tokio::io::AsyncReadExt + std::marker::Unpin,
192    {
193        use tracing::{debug, error};
194
195        const HDR_SIZE_LIMIT: usize = 512;
196
197        let mut buf = vec![0; 16];
198
199        // First we need to read the exact amount to get up to the *length* field. This will
200        // let us then proceed to parse the early header and return how much we need to continue
201        // to read.
202        let mut took = stream
203            .read_exact(&mut buf)
204            .await
205            .map_err(AsyncReadError::Io)?;
206
207        match ProxyHdrV2::parse(&buf) {
208            // Okay, we got a valid header - this can occur with proxy for local conditions.
209            Ok((_, hdr)) => return Ok((stream, hdr)),
210            // We need more bytes, this is the precise amount we need.
211            Err(Error::Incomplete { need }) => {
212                let resize_to = buf.len() + usize::from(need);
213                // Limit the amount so that we don't overflow anything or allocate a buffer that
214                // is too large. Nice try hackers.
215                if resize_to > HDR_SIZE_LIMIT {
216                    error!(
217                        "proxy header request was larger than {} bytes, refusing to proceed.",
218                        HDR_SIZE_LIMIT
219                    );
220                    return Err(AsyncReadError::RequestTooLarge);
221                }
222                buf.resize(resize_to, 0);
223            }
224            Err(Error::Invalid) => {
225                debug!(proxy_binary_dump = %hex::encode(&buf));
226                error!("proxy header was invalid");
227                return Err(AsyncReadError::Invalid);
228            }
229            Err(Error::UnableToComplete) => {
230                debug!(proxy_binary_dump = %hex::encode(&buf));
231                error!("proxy header was incomplete");
232                return Err(AsyncReadError::UnableToComplete);
233            }
234        };
235
236        // Now read any remaining bytes into the buffer.
237        took += stream
238            .read_exact(&mut buf[16..])
239            .await
240            .map_err(AsyncReadError::Io)?;
241
242        match ProxyHdrV2::parse(&buf) {
243            Ok((hdr_took, _)) if hdr_took != took => {
244                // We took inconsistent byte amounts, error.
245                error!("proxy header read an inconsistent amount from stream.");
246                Err(AsyncReadError::InconsistentRead)
247            }
248            Ok((_, hdr)) =>
249            // HAPPY!!!!!
250            {
251                Ok((stream, hdr))
252            }
253            Err(Error::Incomplete { need: _ }) => {
254                error!("proxy header could not be read to the end.");
255                Err(AsyncReadError::UnableToComplete)
256            }
257            Err(Error::Invalid) => {
258                debug!(proxy_binary_dump = %hex::encode(&buf));
259                error!("proxy header was invalid");
260                Err(AsyncReadError::Invalid)
261            }
262            Err(Error::UnableToComplete) => {
263                debug!(proxy_binary_dump = %hex::encode(&buf));
264                error!("proxy header was incomplete");
265                Err(AsyncReadError::UnableToComplete)
266            }
267        }
268    }
269}