1use async_trait::async_trait;
32use pingora::connectors::l4::Connect;
33#[cfg(unix)]
34use pingora::protocols::l4::ext::connect_uds;
35use pingora::protocols::l4::ext::{set_recv_buf, set_tcp_fastopen_connect};
36use pingora::protocols::l4::socket::SocketAddr as PeerAddr;
37use pingora::protocols::l4::stream::Stream;
38use pingora::{Error, ErrorType, OrErr, Result};
39use std::io::ErrorKind;
40use std::net::{IpAddr, SocketAddr};
41#[cfg(unix)]
42use std::os::unix::io::AsRawFd;
43#[cfg(windows)]
44use std::os::windows::io::AsRawSocket;
45use std::time::Duration;
46use tokio::io::AsyncWriteExt;
47use tokio::net::TcpSocket;
48
49pub const PROXY_PROTOCOL_VERSIONS: [&str; 2] = ["v1", "v2"];
51
52const V2_SIGNATURE: &[u8] = b"\r\n\r\n\0\r\nQUIT\n";
53const V2_LOCAL: u8 = 0x20;
56const V2_PROXY: u8 = 0x21;
57const V2_TCP4: u8 = 0x11;
59const V2_TCP6: u8 = 0x21;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum ProxyProtocolVersion {
64 V1,
66 V2,
68}
69
70impl ProxyProtocolVersion {
71 pub fn parse(value: &str) -> Option<Self> {
73 match value.trim().to_ascii_lowercase().as_str() {
74 "v1" => Some(Self::V1),
75 "v2" => Some(Self::V2),
76 _ => None,
77 }
78 }
79}
80
81fn same_family(
85 source: SocketAddr,
86 destination: SocketAddr,
87) -> (SocketAddr, SocketAddr) {
88 let canonical = |addr: SocketAddr| {
89 SocketAddr::new(addr.ip().to_canonical(), addr.port())
90 };
91 let (source, destination) = (canonical(source), canonical(destination));
92 if source.is_ipv4() == destination.is_ipv4() {
93 return (source, destination);
94 }
95 let mapped = |addr: SocketAddr| match addr.ip() {
96 IpAddr::V4(ip) => {
97 SocketAddr::new(IpAddr::V6(ip.to_ipv6_mapped()), addr.port())
98 },
99 IpAddr::V6(_) => addr,
100 };
101 (mapped(source), mapped(destination))
102}
103
104fn v1_ip(ip: IpAddr) -> String {
108 match ip {
109 IpAddr::V6(v6) if v6.to_ipv4_mapped().is_some() => {
110 let segments = v6.segments();
111 format!("::ffff:{:x}:{:x}", segments[6], segments[7])
112 },
113 _ => ip.to_string(),
114 }
115}
116
117pub fn new_proxy_protocol_header(
128 version: ProxyProtocolVersion,
129 addresses: Option<(SocketAddr, SocketAddr)>,
130) -> Vec<u8> {
131 let addresses =
132 addresses.map(|(source, destination)| same_family(source, destination));
133 match (version, addresses) {
134 (ProxyProtocolVersion::V1, None) => b"PROXY UNKNOWN\r\n".to_vec(),
135 (ProxyProtocolVersion::V1, Some((source, destination))) => {
136 let family = if source.is_ipv4() { "TCP4" } else { "TCP6" };
137 format!(
138 "PROXY {family} {} {} {} {}\r\n",
139 v1_ip(source.ip()),
140 v1_ip(destination.ip()),
141 source.port(),
142 destination.port()
143 )
144 .into_bytes()
145 },
146 (ProxyProtocolVersion::V2, None) => {
147 let mut header = V2_SIGNATURE.to_vec();
148 header.extend_from_slice(&[V2_LOCAL, 0, 0, 0]);
150 header
151 },
152 (ProxyProtocolVersion::V2, Some((source, destination))) => {
153 let mut addresses = Vec::with_capacity(36);
154 let family = match (source.ip(), destination.ip()) {
155 (IpAddr::V4(source), IpAddr::V4(destination)) => {
156 addresses.extend_from_slice(&source.octets());
157 addresses.extend_from_slice(&destination.octets());
158 V2_TCP4
159 },
160 (source, destination) => {
161 for ip in [source, destination] {
163 if let IpAddr::V6(ip) = ip {
164 addresses.extend_from_slice(&ip.octets());
165 }
166 }
167 V2_TCP6
168 },
169 };
170 addresses.extend_from_slice(&source.port().to_be_bytes());
171 addresses.extend_from_slice(&destination.port().to_be_bytes());
172 let mut header = V2_SIGNATURE.to_vec();
173 header.push(V2_PROXY);
174 header.push(family);
175 header.extend_from_slice(&(addresses.len() as u16).to_be_bytes());
176 header.extend_from_slice(&addresses);
177 header
178 },
179 }
180}
181
182#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
186pub struct ProxyProtocolConnectOptions {
187 pub connection_timeout: Option<Duration>,
189 pub tcp_recv_buf: Option<usize>,
190 pub tcp_fast_open: bool,
191}
192
193#[derive(Debug)]
196pub struct ProxyProtocolConnector {
197 header: Vec<u8>,
198 options: ProxyProtocolConnectOptions,
199}
200
201fn new_connect_error(e: std::io::Error, addr: &PeerAddr) -> Box<Error> {
205 let etype = match e.kind() {
206 ErrorKind::ConnectionRefused => ErrorType::ConnectRefused,
207 ErrorKind::TimedOut => ErrorType::ConnectTimedout,
208 ErrorKind::AddrNotAvailable
209 | ErrorKind::PermissionDenied
210 | ErrorKind::AddrInUse => ErrorType::InternalError,
211 ErrorKind::NetworkUnreachable | ErrorKind::HostUnreachable => {
212 ErrorType::ConnectNoRoute
213 },
214 _ => ErrorType::ConnectError,
215 };
216 Error::because(etype, format!("Fail to connect to {addr}"), e)
217}
218
219impl ProxyProtocolConnector {
220 pub fn new(header: Vec<u8>, options: ProxyProtocolConnectOptions) -> Self {
221 Self { header, options }
222 }
223
224 pub fn header(&self) -> &[u8] {
226 &self.header
227 }
228
229 fn set_socket(&self, socket: &TcpSocket) -> Result<()> {
230 #[cfg(unix)]
231 let raw = socket.as_raw_fd();
232 #[cfg(windows)]
233 let raw = socket.as_raw_socket();
234 if self.options.tcp_fast_open {
235 set_tcp_fastopen_connect(raw)?;
236 }
237 if let Some(size) = self.options.tcp_recv_buf {
238 set_recv_buf(raw, size)?;
239 }
240 Ok(())
241 }
242
243 async fn connect_and_send(&self, addr: &PeerAddr) -> Result<Stream> {
244 let not_sent = |e: std::io::Error| {
247 Error::because(
248 ErrorType::ConnectError,
249 format!("Fail to send the PROXY protocol header to {addr}"),
250 e,
251 )
252 };
253 match addr {
254 PeerAddr::Inet(inet) => {
255 let socket = if inet.is_ipv4() {
256 TcpSocket::new_v4()
257 } else {
258 TcpSocket::new_v6()
259 }
260 .or_err(ErrorType::SocketError, "failed to create socket")
261 .map_err(|e| {
262 Error::because(
263 ErrorType::InternalError,
264 format!("Fail to connect to {addr}"),
265 e,
266 )
267 })?;
268 self.set_socket(&socket).map_err(|e| {
271 e.more_context(format!("Fail to connect to {addr}"))
272 })?;
273 let mut stream = socket
274 .connect(*inet)
275 .await
276 .map_err(|e| new_connect_error(e, addr))?;
277 stream.write_all(&self.header).await.map_err(not_sent)?;
278 Ok(stream.into())
279 },
280 #[cfg(unix)]
281 PeerAddr::Unix(unix) => {
282 let Some(path) = unix.as_pathname() else {
283 return Error::e_explain(
284 ErrorType::InternalError,
285 format!("{addr} is not the path of a socket"),
286 );
287 };
288 let mut stream = connect_uds(path).await?;
289 stream.write_all(&self.header).await.map_err(not_sent)?;
290 Ok(stream.into())
291 },
292 }
293 }
294}
295
296async fn within<T>(
298 limit: Option<Duration>,
299 addr: &PeerAddr,
300 connecting: impl Future<Output = Result<T>>,
301) -> Result<T> {
302 let Some(limit) = limit else {
303 return connecting.await;
304 };
305 tokio::time::timeout(limit, connecting).await.map_err(|e| {
306 Error::because(
307 ErrorType::ConnectTimedout,
308 format!("timeout {limit:?} connecting to server {addr}"),
309 e,
310 )
311 })?
312}
313
314#[async_trait]
315impl Connect for ProxyProtocolConnector {
316 async fn connect(&self, addr: &PeerAddr) -> Result<Stream> {
317 within(
318 self.options.connection_timeout,
319 addr,
320 self.connect_and_send(addr),
321 )
322 .await
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use pretty_assertions::assert_eq;
330 use tokio::io::AsyncReadExt;
331 use tokio::net::TcpListener;
332
333 fn addr(value: &str) -> SocketAddr {
334 value.parse().unwrap()
335 }
336
337 #[test]
338 fn test_version() {
339 assert_eq!(
340 Some(ProxyProtocolVersion::V1),
341 ProxyProtocolVersion::parse("v1")
342 );
343 assert_eq!(
344 Some(ProxyProtocolVersion::V2),
345 ProxyProtocolVersion::parse(" V2 ")
346 );
347 for other in ["", "1", "2", "v3", "true", "on"] {
348 assert_eq!(None, ProxyProtocolVersion::parse(other), "{other}");
349 }
350 for name in PROXY_PROTOCOL_VERSIONS {
351 assert_eq!(true, ProxyProtocolVersion::parse(name).is_some());
352 }
353 }
354
355 #[test]
356 fn test_header_v1() {
357 let v1 = |source: &str, destination: &str| {
358 String::from_utf8(new_proxy_protocol_header(
359 ProxyProtocolVersion::V1,
360 Some((addr(source), addr(destination))),
361 ))
362 .unwrap()
363 };
364 assert_eq!(
365 "PROXY TCP4 192.0.2.1 198.51.100.1 56324 443\r\n",
366 v1("192.0.2.1:56324", "198.51.100.1:443")
367 );
368 assert_eq!(
369 "PROXY TCP6 2001:db8::1 2001:db8::2 4000 443\r\n",
370 v1("[2001:db8::1]:4000", "[2001:db8::2]:443")
371 );
372 assert_eq!(
375 "PROXY TCP4 192.0.2.1 198.51.100.1 56324 443\r\n",
376 v1("[::ffff:192.0.2.1]:56324", "[::ffff:198.51.100.1]:443")
377 );
378 assert_eq!(
381 "PROXY TCP6 ::ffff:c000:201 2001:db8::2 56324 443\r\n",
382 v1("192.0.2.1:56324", "[2001:db8::2]:443")
383 );
384 assert_eq!(
385 "PROXY TCP6 2001:db8::1 ::ffff:c633:6401 4000 443\r\n",
386 v1("[2001:db8::1]:4000", "198.51.100.1:443")
387 );
388 let longest = v1(
390 "[ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe]:65535",
391 "[ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffd]:65535",
392 );
393 assert_eq!(true, longest.len() <= 107, "{}", longest.len());
394
395 assert_eq!(
396 b"PROXY UNKNOWN\r\n".to_vec(),
397 new_proxy_protocol_header(ProxyProtocolVersion::V1, None)
398 );
399 }
400
401 #[test]
402 fn test_header_v2() {
403 let v2 = |source: &str, destination: &str| {
404 new_proxy_protocol_header(
405 ProxyProtocolVersion::V2,
406 Some((addr(source), addr(destination))),
407 )
408 };
409 let mut tcp4 = V2_SIGNATURE.to_vec();
410 tcp4.extend_from_slice(&[0x21, 0x11, 0, 12]);
411 tcp4.extend_from_slice(&[192, 0, 2, 1, 198, 51, 100, 1]);
412 tcp4.extend_from_slice(&56324u16.to_be_bytes());
413 tcp4.extend_from_slice(&443u16.to_be_bytes());
414 assert_eq!(28, tcp4.len());
415 assert_eq!(tcp4, v2("192.0.2.1:56324", "198.51.100.1:443"));
416 assert_eq!(
417 tcp4,
418 v2("[::ffff:192.0.2.1]:56324", "[::ffff:198.51.100.1]:443")
419 );
420
421 let mut tcp6 = V2_SIGNATURE.to_vec();
422 tcp6.extend_from_slice(&[0x21, 0x21, 0, 36]);
423 tcp6.extend_from_slice(
424 &"2001:db8::1"
425 .parse::<std::net::Ipv6Addr>()
426 .unwrap()
427 .octets(),
428 );
429 tcp6.extend_from_slice(
430 &"::ffff:198.51.100.1"
431 .parse::<std::net::Ipv6Addr>()
432 .unwrap()
433 .octets(),
434 );
435 tcp6.extend_from_slice(&4000u16.to_be_bytes());
436 tcp6.extend_from_slice(&443u16.to_be_bytes());
437 assert_eq!(52, tcp6.len());
438 assert_eq!(tcp6, v2("[2001:db8::1]:4000", "198.51.100.1:443"));
440
441 let mut local = V2_SIGNATURE.to_vec();
442 local.extend_from_slice(&[0x20, 0, 0, 0]);
443 assert_eq!(
444 local,
445 new_proxy_protocol_header(ProxyProtocolVersion::V2, None)
446 );
447 }
448
449 #[tokio::test]
452 async fn test_connector_sends_the_header_first() {
453 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
454 let local = listener.local_addr().unwrap();
455 for (version, tcp_fast_open) in [
456 (ProxyProtocolVersion::V1, false),
457 (ProxyProtocolVersion::V2, false),
458 (ProxyProtocolVersion::V2, true),
459 ] {
460 let header = new_proxy_protocol_header(
461 version,
462 Some((addr("192.0.2.1:56324"), addr("198.51.100.1:443"))),
463 );
464 let connector = ProxyProtocolConnector::new(
465 header.clone(),
466 ProxyProtocolConnectOptions {
467 connection_timeout: Some(Duration::from_secs(5)),
468 tcp_recv_buf: Some(64 * 1024),
469 tcp_fast_open,
470 },
471 );
472 assert_eq!(header, connector.header());
473 let mut stream =
474 match connector.connect(&PeerAddr::Inet(local)).await {
475 Ok(stream) => stream,
476 Err(e)
480 if tcp_fast_open
481 && e.to_string()
482 .contains("TCP_FASTOPEN_CONNECT") =>
483 {
484 continue;
485 },
486 Err(e) => panic!("{e}"),
487 };
488 stream.write_all(b"GET / HTTP/1.1\r\n\r\n").await.unwrap();
489 stream.flush().await.unwrap();
490 drop(stream);
491
492 let (mut accepted, _) = listener.accept().await.unwrap();
493 let mut received = Vec::new();
494 accepted.read_to_end(&mut received).await.unwrap();
495 let mut expected = header;
496 expected.extend_from_slice(b"GET / HTTP/1.1\r\n\r\n");
497 assert_eq!(expected, received);
498 }
499 }
500
501 #[tokio::test]
505 async fn test_connector_errors() {
506 let unused = {
508 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
509 listener.local_addr().unwrap()
510 };
511 let connector = ProxyProtocolConnector::new(
512 new_proxy_protocol_header(ProxyProtocolVersion::V2, None),
513 ProxyProtocolConnectOptions::default(),
514 );
515 let error = connector
516 .connect(&PeerAddr::Inet(unused))
517 .await
518 .err()
519 .unwrap();
520 assert_eq!(ErrorType::ConnectRefused, error.etype);
521 assert_eq!(
522 true,
523 error.to_string().contains(&unused.to_string()),
524 "{error}"
525 );
526
527 let silent = PeerAddr::Inet(unused);
530 let error = within(
531 Some(Duration::from_millis(20)),
532 &silent,
533 std::future::pending::<Result<()>>(),
534 )
535 .await
536 .err()
537 .unwrap();
538 assert_eq!(ErrorType::ConnectTimedout, error.etype);
539 assert_eq!(true, error.to_string().contains("timeout 20ms"), "{error}");
540 assert_eq!(7, within(None, &silent, async { Ok(7) }).await.unwrap());
541 }
542
543 #[cfg(unix)]
544 #[tokio::test]
545 async fn test_connector_unix_socket() {
546 let dir = tempfile::tempdir().unwrap();
547 let path = dir.path().join("backend.sock");
548 let listener = tokio::net::UnixListener::bind(&path).unwrap();
549 let header = new_proxy_protocol_header(
550 ProxyProtocolVersion::V1,
551 Some((addr("192.0.2.1:56324"), addr("198.51.100.1:443"))),
552 );
553 let connector = ProxyProtocolConnector::new(
554 header.clone(),
555 ProxyProtocolConnectOptions::default(),
556 );
557 let addr: PeerAddr =
558 format!("unix:{}", path.display()).parse().unwrap();
559 let stream = connector.connect(&addr).await.unwrap();
560 drop(stream);
561 let (mut accepted, _) = listener.accept().await.unwrap();
562 let mut received = Vec::new();
563 accepted.read_to_end(&mut received).await.unwrap();
564 assert_eq!(header, received);
565 }
566}