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