|
| 1 | +// Copyright 2025 Cloudflare, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +//! Proxy Protocol support for preserving client connection information |
| 16 | +//! |
| 17 | +//! This module provides the [`ProxyProtocolReceiver`] trait and related types for implementing |
| 18 | +//! [HAProxy's Proxy Protocol](https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt). |
| 19 | +//! The protocol allows intermediaries (like load balancers) to pass original client connection |
| 20 | +//! information to backend servers. |
| 21 | +//! |
| 22 | +//! # Feature Flag |
| 23 | +//! |
| 24 | +//! This functionality requires the `proxy_protocol` feature to be enabled: |
| 25 | +//! ```toml |
| 26 | +//! [dependencies] |
| 27 | +//! pingora-core = { version = "0.6", features = ["proxy_protocol"] } |
| 28 | +//! ``` |
| 29 | +//! |
| 30 | +//! # Protocol Versions |
| 31 | +//! |
| 32 | +//! Both Proxy Protocol v1 (human-readable text format) and v2 (binary format with TLV support) |
| 33 | +//! are supported through the [`ProxyProtocolHeader`] enum. |
| 34 | +//! |
| 35 | +//! # Example |
| 36 | +//! |
| 37 | +//! ```rust,no_run |
| 38 | +//! use async_trait::async_trait; |
| 39 | +//! use pingora_core::protocols::proxy_protocol::{ |
| 40 | +//! ProxyProtocolReceiver, ProxyProtocolHeader, HeaderV2, |
| 41 | +//! Command, Transport, Addresses |
| 42 | +//! }; |
| 43 | +//! use pingora_core::protocols::l4::stream::Stream; |
| 44 | +//! use pingora_error::Result; |
| 45 | +//! |
| 46 | +//! struct MyProxyProtocolParser; |
| 47 | +//! |
| 48 | +//! #[async_trait] |
| 49 | +//! impl ProxyProtocolReceiver for MyProxyProtocolParser { |
| 50 | +//! async fn accept(&self, stream: &mut Stream) -> Result<(ProxyProtocolHeader, Vec<u8>)> { |
| 51 | +//! // Parse the Proxy Protocol header from the stream |
| 52 | +//! // Return the parsed header and any remaining bytes |
| 53 | +//! todo!("Implement parsing logic") |
| 54 | +//! } |
| 55 | +//! } |
| 56 | +//! ``` |
| 57 | +
|
| 58 | +use async_trait::async_trait; |
| 59 | +use std::borrow::Cow; |
| 60 | +use std::net::SocketAddr; |
| 61 | + |
| 62 | +use super::l4::stream::Stream; |
| 63 | +use pingora_error::Result; |
| 64 | + |
| 65 | +/// A trait for parsing Proxy Protocol headers from incoming connections. |
| 66 | +/// |
| 67 | +/// Implementations of this trait handle reading and parsing Proxy Protocol headers |
| 68 | +/// (v1 or v2) from a stream. The trait is designed to be flexible, allowing different |
| 69 | +/// parsing strategies or third-party parser libraries to be used. |
| 70 | +/// |
| 71 | +/// # Example |
| 72 | +/// |
| 73 | +/// ```rust,no_run |
| 74 | +/// use async_trait::async_trait; |
| 75 | +/// use pingora_core::protocols::proxy_protocol::{ |
| 76 | +/// ProxyProtocolReceiver, ProxyProtocolHeader, HeaderV1, |
| 77 | +/// Transport, Addresses |
| 78 | +/// }; |
| 79 | +/// use pingora_core::protocols::l4::stream::Stream; |
| 80 | +/// use pingora_error::Result; |
| 81 | +/// use tokio::io::AsyncReadExt; |
| 82 | +/// |
| 83 | +/// struct SimpleV1Parser; |
| 84 | +/// |
| 85 | +/// #[async_trait] |
| 86 | +/// impl ProxyProtocolReceiver for SimpleV1Parser { |
| 87 | +/// async fn accept(&self, stream: &mut Stream) -> Result<(ProxyProtocolHeader, Vec<u8>)> { |
| 88 | +/// let mut buffer = Vec::new(); |
| 89 | +/// // Read and parse v1 header |
| 90 | +/// stream.read_buf(&mut buffer).await?; |
| 91 | +/// // Parse logic here... |
| 92 | +/// todo!("Parse v1 header and return result") |
| 93 | +/// } |
| 94 | +/// } |
| 95 | +/// ``` |
| 96 | +/// |
| 97 | +/// # Performance Considerations |
| 98 | +/// |
| 99 | +/// This method is called once per connection that uses Proxy Protocol. Implementations |
| 100 | +/// should efficiently read only the necessary bytes from the stream to parse the header, |
| 101 | +/// returning any excess bytes for subsequent processing. |
| 102 | +#[async_trait] |
| 103 | +pub trait ProxyProtocolReceiver: Send + Sync { |
| 104 | + /// Parses the Proxy Protocol header from an accepted connection stream. |
| 105 | + /// |
| 106 | + /// This method is called after a TCP connection is accepted on a Proxy Protocol endpoint. |
| 107 | + /// Implementors should read from the stream to parse the header according to either |
| 108 | + /// v1 (text) or v2 (binary) format specifications. |
| 109 | + /// |
| 110 | + /// # Arguments |
| 111 | + /// |
| 112 | + /// * `stream` - A mutable reference to the accepted connection stream |
| 113 | + /// |
| 114 | + /// # Returns |
| 115 | + /// |
| 116 | + /// A tuple containing: |
| 117 | + /// * The parsed [`ProxyProtocolHeader`] (v1 or v2) |
| 118 | + /// * Any remaining bytes read from the stream after the header (to be processed by the application) |
| 119 | + /// |
| 120 | + /// # Errors |
| 121 | + /// |
| 122 | + /// Returns an error if: |
| 123 | + /// * The stream cannot be read |
| 124 | + /// * The header format is invalid |
| 125 | + /// * The connection is closed unexpectedly |
| 126 | + /// |
| 127 | + /// # Example |
| 128 | + /// |
| 129 | + /// ```rust,no_run |
| 130 | + /// async fn accept(&self, stream: &mut Stream) -> Result<(ProxyProtocolHeader, Vec<u8>)> { |
| 131 | + /// // Read bytes from stream |
| 132 | + /// let mut buffer = Vec::new(); |
| 133 | + /// stream.read_buf(&mut buffer).await?; |
| 134 | + /// |
| 135 | + /// // Parse header and determine remaining bytes |
| 136 | + /// let (header, remaining) = parse_proxy_header(&buffer)?; |
| 137 | + /// Ok((header, remaining)) |
| 138 | + /// } |
| 139 | + /// ``` |
| 140 | + async fn accept(&self, stream: &mut Stream) -> Result<(ProxyProtocolHeader, Vec<u8>)>; |
| 141 | +} |
| 142 | + |
| 143 | +/// Parsed Proxy Protocol header containing connection information. |
| 144 | +/// |
| 145 | +/// This enum represents either a v1 (text) or v2 (binary) Proxy Protocol header. |
| 146 | +/// The version is determined by the parser implementation. |
| 147 | +#[derive(Debug)] |
| 148 | +pub enum ProxyProtocolHeader { |
| 149 | + /// Proxy Protocol version 1 (human-readable text format) |
| 150 | + V1(HeaderV1), |
| 151 | + /// Proxy Protocol version 2 (binary format with TLV extension support) |
| 152 | + V2(HeaderV2), |
| 153 | +} |
| 154 | + |
| 155 | +/// Proxy Protocol version 1 header information. |
| 156 | +/// |
| 157 | +/// Version 1 uses a human-readable text format. It contains basic transport |
| 158 | +/// and address information but does not support the command field or TLV extensions. |
| 159 | +#[derive(Debug)] |
| 160 | +pub struct HeaderV1 { |
| 161 | + /// The transport protocol used for the proxied connection |
| 162 | + pub transport: Transport, |
| 163 | + /// Source and destination addresses, if available. |
| 164 | + /// `None` indicates an unknown or local connection. |
| 165 | + pub addresses: Option<Addresses>, |
| 166 | +} |
| 167 | + |
| 168 | +/// Proxy Protocol version 2 header information. |
| 169 | +/// |
| 170 | +/// Version 2 uses a binary format and supports additional features including |
| 171 | +/// the command field (LOCAL vs PROXY) and optional TLV (Type-Length-Value) extensions |
| 172 | +/// for passing custom metadata. |
| 173 | +#[derive(Debug)] |
| 174 | +pub struct HeaderV2 { |
| 175 | + /// Indicates whether this is a proxied connection or a local health check |
| 176 | + pub command: Command, |
| 177 | + /// The transport protocol used for the proxied connection |
| 178 | + pub transport: Transport, |
| 179 | + /// Source and destination addresses, if available. |
| 180 | + /// `None` for LOCAL command or unknown connections. |
| 181 | + pub addresses: Option<Addresses>, |
| 182 | + /// Optional TLV (Type-Length-Value) data for protocol extensions. |
| 183 | + /// May contain additional metadata such as SSL information, unique IDs, etc. |
| 184 | + pub tlvs: Option<Cow<'static, [u8]>>, |
| 185 | +} |
| 186 | + |
| 187 | +/// Transport protocol family for the proxied connection. |
| 188 | +/// |
| 189 | +/// Indicates the network protocol (IPv4 or IPv6 over TCP) or unknown/unspecified transport. |
| 190 | +#[derive(Debug)] |
| 191 | +pub enum Transport { |
| 192 | + /// TCP over IPv4 |
| 193 | + Tcp4, |
| 194 | + /// TCP over IPv6 |
| 195 | + Tcp6, |
| 196 | + /// Unknown or unspecified transport protocol |
| 197 | + Unknown, |
| 198 | +} |
| 199 | + |
| 200 | +/// Source and destination socket addresses for a proxied connection. |
| 201 | +/// |
| 202 | +/// Contains the original client address and the destination address |
| 203 | +/// as seen by the proxy/load balancer. |
| 204 | +#[derive(Debug)] |
| 205 | +pub struct Addresses { |
| 206 | + /// The original source address (client) |
| 207 | + pub source: SocketAddr, |
| 208 | + /// The destination address as seen by the proxy |
| 209 | + pub destination: SocketAddr, |
| 210 | +} |
| 211 | + |
| 212 | +/// Proxy Protocol v2 command type. |
| 213 | +/// |
| 214 | +/// Distinguishes between actual proxied connections and local connections |
| 215 | +/// (typically used for health checks). |
| 216 | +#[derive(Debug)] |
| 217 | +pub enum Command { |
| 218 | + /// LOCAL command: indicates a health check or non-proxied connection. |
| 219 | + /// Receivers should not use any address information from LOCAL connections. |
| 220 | + Local, |
| 221 | + /// PROXY command: indicates a proxied connection with valid address information. |
| 222 | + Proxy, |
| 223 | +} |
0 commit comments