1use crate::protocol::{HeaderResult, PartialResult, v1, v2};
2use rama_core::error::BoxErrorExt as _;
3use rama_core::{
4 Layer, Service,
5 bytes::Bytes,
6 error::{BoxError, ErrorContext, ErrorExt},
7 extensions::{Extension, ExtensionsRef},
8 io::{HeapReader, Io, PrefixedIo},
9 telemetry::tracing,
10};
11use rama_net::address::Domain;
12use rama_net::forwarded::{Forwarded, ForwardedElement};
13use rama_utils::macros::generate_set_and_with;
14use std::net::SocketAddr;
15use tokio::io::AsyncReadExt;
16
17const MAX_HEADER_LENGTH: usize = 16 + u16::MAX as usize;
22
23const INITIAL_BUFFER_CAPACITY: usize = 128;
27
28#[derive(Debug, Clone, Copy)]
41pub struct HaProxyStrictness {
42 pub max_header_length: usize,
45 pub verify_crc32c_when_present: bool,
49 pub require_crc32c: bool,
52 pub reject_unknown_address_family: bool,
55 pub reject_local_command: bool,
61 pub fail_on_malformed_tlv: bool,
65}
66
67impl HaProxyStrictness {
68 const DEFAULT: Self = Self {
71 max_header_length: MAX_HEADER_LENGTH,
72 verify_crc32c_when_present: true,
73 require_crc32c: false,
74 reject_unknown_address_family: false,
75 reject_local_command: false,
76 fail_on_malformed_tlv: false,
77 };
78}
79
80impl Default for HaProxyStrictness {
81 fn default() -> Self {
82 Self::DEFAULT
83 }
84}
85
86impl HaProxyStrictness {
87 #[must_use]
91 pub const fn lenient() -> Self {
92 Self {
93 max_header_length: MAX_HEADER_LENGTH,
94 verify_crc32c_when_present: false,
95 require_crc32c: false,
96 reject_unknown_address_family: false,
97 reject_local_command: false,
98 fail_on_malformed_tlv: false,
99 }
100 }
101
102 #[must_use]
111 pub const fn strict() -> Self {
112 Self {
113 max_header_length: MAX_HEADER_LENGTH,
114 verify_crc32c_when_present: true,
115 require_crc32c: true,
116 reject_unknown_address_family: true,
117 reject_local_command: false,
118 fail_on_malformed_tlv: true,
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
130pub struct HaProxyTlv {
131 pub kind: v2::Type,
133 pub value: Bytes,
135}
136
137#[derive(Debug, Clone, Default, Extension)]
145#[extension(tags(proxy))]
146pub struct HaProxyTlvs {
147 entries: Vec<HaProxyTlv>,
148}
149
150impl HaProxyTlvs {
151 #[must_use]
153 pub fn entries(&self) -> &[HaProxyTlv] {
154 &self.entries
155 }
156
157 #[must_use]
159 pub fn get(&self, kind: v2::Type) -> Option<&Bytes> {
160 self.entries
161 .iter()
162 .find(|tlv| tlv.kind == kind)
163 .map(|tlv| &tlv.value)
164 }
165
166 #[must_use]
180 pub fn authority(&self) -> Option<Domain> {
181 self.get(v2::Type::Authority)
182 .and_then(|v| Domain::try_from(v.as_ref()).ok())
183 }
184
185 #[must_use]
187 pub fn unique_id(&self) -> Option<&Bytes> {
188 self.get(v2::Type::UniqueId)
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Extension)]
196#[extension(tags(proxy))]
197pub enum HaProxyCommand {
198 Local,
202 Proxy,
204}
205
206impl From<v2::Command> for HaProxyCommand {
207 fn from(c: v2::Command) -> Self {
208 match c {
209 v2::Command::Local => Self::Local,
210 v2::Command::Proxy => Self::Proxy,
211 }
212 }
213}
214
215#[derive(Debug, Default, Clone)]
217#[non_exhaustive]
218pub struct HaProxyLayer {
219 peek: bool,
220 strictness: HaProxyStrictness,
221}
222
223impl HaProxyLayer {
224 #[must_use]
226 pub const fn new() -> Self {
227 Self {
228 peek: false,
229 strictness: HaProxyStrictness::DEFAULT,
230 }
231 }
232
233 generate_set_and_with!(
234 pub fn peek(mut self, value: bool) -> Self {
240 self.peek = value;
241 self
242 }
243 );
244
245 generate_set_and_with!(
246 pub fn strictness(mut self, value: HaProxyStrictness) -> Self {
248 self.strictness = value;
249 self
250 }
251 );
252}
253
254impl<S> Layer<S> for HaProxyLayer {
255 type Service = HaProxyService<S>;
256
257 fn layer(&self, inner: S) -> Self::Service {
258 HaProxyService {
259 inner,
260 peek: self.peek,
261 strictness: self.strictness,
262 }
263 }
264}
265
266#[derive(Debug, Clone)]
271pub struct HaProxyService<S> {
272 inner: S,
273 peek: bool,
274 strictness: HaProxyStrictness,
275}
276
277impl<S> HaProxyService<S> {
278 pub const fn new(inner: S) -> Self {
280 Self {
281 inner,
282 peek: false,
283 strictness: HaProxyStrictness::DEFAULT,
284 }
285 }
286
287 generate_set_and_with!(
288 pub fn peek(mut self, value: bool) -> Self {
294 self.peek = value;
295 self
296 }
297 );
298
299 generate_set_and_with!(
300 pub fn strictness(mut self, value: HaProxyStrictness) -> Self {
302 self.strictness = value;
303 self
304 }
305 );
306}
307
308enum PeekOutcome {
311 V1,
313 V2,
315 NotProxy,
318}
319
320async fn peek_disambiguate<IO: tokio::io::AsyncRead + Unpin>(
329 stream: &mut IO,
330 buffer: &mut Vec<u8>,
331) -> Result<PeekOutcome, BoxError> {
332 let v1_prefix = v1::PROTOCOL_PREFIX.as_bytes();
336 let v2_prefix = v2::PROTOCOL_PREFIX;
337
338 let mut scratch = [0u8; 16];
340 while buffer.is_empty() {
341 let n = stream
342 .read(&mut scratch)
343 .await
344 .context("haproxy peek: initial read")?;
345 if n == 0 {
346 return Ok(PeekOutcome::NotProxy);
348 }
349 buffer.extend_from_slice(&scratch[..n]);
350 }
351
352 let candidate_v1 = buffer[0] == v1_prefix[0];
354 let candidate_v2 = buffer[0] == v2_prefix[0];
355 if !candidate_v1 && !candidate_v2 {
356 return Ok(PeekOutcome::NotProxy);
357 }
358
359 let target_len = if candidate_v1 {
360 v1_prefix.len()
361 } else {
362 v2_prefix.len()
363 };
364
365 while buffer.len() < target_len {
366 let n = stream
367 .read(&mut scratch)
368 .await
369 .context("haproxy peek: prefix read")?;
370 if n == 0 {
371 return Ok(PeekOutcome::NotProxy);
374 }
375 buffer.extend_from_slice(&scratch[..n]);
376
377 let expected = if candidate_v1 { v1_prefix } else { v2_prefix };
380 let cmp_len = buffer.len().min(expected.len());
381 if buffer[..cmp_len] != expected[..cmp_len] {
382 return Ok(PeekOutcome::NotProxy);
383 }
384 }
385
386 if candidate_v1 && &buffer[..v1_prefix.len()] == v1_prefix {
387 Ok(PeekOutcome::V1)
388 } else if candidate_v2 && &buffer[..v2_prefix.len()] == v2_prefix {
389 Ok(PeekOutcome::V2)
390 } else {
391 Ok(PeekOutcome::NotProxy)
392 }
393}
394
395impl<S, IO> Service<IO> for HaProxyService<S>
396where
397 S: Service<PrefixedIo<HeapReader, IO>, Error: Into<BoxError>>,
398 IO: Io + Unpin + ExtensionsRef,
399{
400 type Output = S::Output;
401 type Error = BoxError;
402
403 async fn serve(&self, mut stream: IO) -> Result<Self::Output, Self::Error> {
404 let max_header_length = self
405 .strictness
406 .max_header_length
407 .min(MAX_HEADER_LENGTH)
408 .max(v1::PROTOCOL_PREFIX.len());
409
410 let mut buffer: Vec<u8> = Vec::with_capacity(INITIAL_BUFFER_CAPACITY);
411
412 if self.peek {
413 tracing::trace!("haproxy protocol peeking enabled: start detection");
414 match peek_disambiguate(&mut stream, &mut buffer).await? {
415 PeekOutcome::V1 => {
416 tracing::trace!(
417 "haproxy protocol peeked: v1 detected: continue with haproxy handling"
418 );
419 }
420 PeekOutcome::V2 => {
421 tracing::trace!(
422 "haproxy protocol peeked: v2 detected: continue with haproxy handling"
423 );
424 }
425 PeekOutcome::NotProxy => {
426 tracing::trace!(
427 "no haproxy protocol detected... delegating immediately to inner..."
428 );
429 let mem = HeapReader::new(buffer);
430 let stream = PrefixedIo::new(mem, stream);
431 return self.inner.serve(stream).await.into_box_error();
432 }
433 }
434 } else {
435 tracing::trace!("haproxy protocol enforced: skip peeking");
436 }
437
438 let header = loop {
439 let header = HeaderResult::parse(&buffer);
443 if header.is_complete() {
444 break header;
445 }
446
447 if buffer.len() >= max_header_length {
448 return Err(format!(
449 "haproxy: buffer exhausted (read {} bytes, configured max {max_header_length}) before parsing completed",
450 buffer.len()
451 )
452 .into());
453 }
454
455 let old_len = buffer.len();
457 let mut next_cap = old_len.saturating_mul(2).max(INITIAL_BUFFER_CAPACITY);
458 if next_cap > max_header_length {
459 next_cap = max_header_length;
460 }
461 buffer.resize(next_cap, 0);
462
463 let n = stream.read(&mut buffer[old_len..]).await?;
464 buffer.truncate(old_len + n);
465
466 if n == 0 {
467 return Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof)
468 .context("HaProxy header incomplete"));
469 }
470
471 tracing::debug!("Incomplete header. Read {} bytes so far.", buffer.len());
472 };
473
474 let consumed = match header {
475 HeaderResult::V1(Ok(ref header)) => self.apply_v1(&stream, header)?,
476 HeaderResult::V2(Ok(ref header)) => self.apply_v2(&stream, header)?,
477 HeaderResult::V1(Err(error)) => return Err(error.into()),
478 HeaderResult::V2(Err(error)) => return Err(error.into()),
479 };
480
481 let mem: HeapReader = buffer[consumed..].into();
483 let stream = PrefixedIo::new(mem, stream);
484
485 self.inner.serve(stream).await.into_box_error()
487 }
488}
489
490impl<S> HaProxyService<S> {
491 fn apply_v1<IO: ExtensionsRef>(
492 &self,
493 stream: &IO,
494 header: &v1::Header<'_>,
495 ) -> Result<usize, BoxError> {
496 if self.strictness.reject_unknown_address_family
497 && matches!(header.addresses, v1::Addresses::Unknown)
498 {
499 return Err(BoxError::from_static_str(
500 "haproxy v1: UNKNOWN address family rejected by strictness configuration",
501 ));
502 }
503
504 match header.addresses {
505 v1::Addresses::Tcp4(info) => {
506 let peer_addr: SocketAddr = (info.source_address, info.source_port).into();
507 insert_forwarded(stream, peer_addr);
508 }
509 v1::Addresses::Tcp6(info) => {
510 let peer_addr: SocketAddr = (info.source_address, info.source_port).into();
511 insert_forwarded(stream, peer_addr);
512 }
513 v1::Addresses::Unknown => (),
514 }
515
516 Ok(header.header.len())
517 }
518
519 fn apply_v2<IO: ExtensionsRef>(
520 &self,
521 stream: &IO,
522 header: &v2::Header<'_>,
523 ) -> Result<usize, BoxError> {
524 if self.strictness.reject_unknown_address_family
525 && matches!(header.addresses, v2::Addresses::Unspecified)
526 {
527 return Err(BoxError::from_static_str(
528 "haproxy v2: AF_UNSPEC rejected by strictness configuration",
529 ));
530 }
531
532 if self.strictness.reject_local_command && header.command == v2::Command::Local {
533 return Err(BoxError::from_static_str(
534 "haproxy v2: LOCAL command rejected by strictness configuration",
535 ));
536 }
537
538 let crc_status = header.verify_crc32c();
546 if self.strictness.verify_crc32c_when_present && crc_status == v2::Crc32cStatus::Invalid {
547 return Err(BoxError::from_static_str(
548 "haproxy v2: CRC32C TLV present but invalid",
549 ));
550 }
551 if self.strictness.require_crc32c
552 && !matches!(
553 crc_status,
554 v2::Crc32cStatus::Valid | v2::Crc32cStatus::Invalid
555 )
556 {
557 return Err(BoxError::from_static_str(
558 "haproxy v2: required CRC32C TLV missing",
559 ));
560 }
561
562 let consumed_len = header.header.len();
564 let mut tlv_entries: Vec<HaProxyTlv> = Vec::new();
565 let mut tlv_malformed = false;
566 for tlv in header.tlvs() {
567 match tlv {
568 Ok(tlv) => tlv_entries.push(HaProxyTlv {
569 kind: tlv.kind,
570 value: Bytes::copy_from_slice(tlv.value.as_ref()),
571 }),
572 Err(e) => {
573 tracing::debug!(error = %e, "haproxy v2: malformed TLV in header");
574 tlv_malformed = true;
575 break;
576 }
577 }
578 }
579
580 if self.strictness.fail_on_malformed_tlv && tlv_malformed {
581 return Err(BoxError::from_static_str(
582 "haproxy v2: malformed TLV area rejected by strictness configuration",
583 ));
584 }
585
586 if header.command == v2::Command::Proxy {
590 match header.addresses {
591 v2::Addresses::IPv4(info) => {
592 let peer_addr: SocketAddr = (info.source_address, info.source_port).into();
593 insert_forwarded(stream, peer_addr);
594 }
595 v2::Addresses::IPv6(info) => {
596 let peer_addr: SocketAddr = (info.source_address, info.source_port).into();
597 insert_forwarded(stream, peer_addr);
598 }
599 v2::Addresses::Unix(_) | v2::Addresses::Unspecified => (),
600 }
601 }
602
603 stream
605 .extensions()
606 .insert(HaProxyCommand::from(header.command));
607 if !tlv_entries.is_empty() {
608 stream.extensions().insert(HaProxyTlvs {
609 entries: tlv_entries,
610 });
611 }
612
613 Ok(consumed_len)
614 }
615}
616
617fn insert_forwarded<IO: ExtensionsRef>(stream: &IO, peer_addr: SocketAddr) {
618 let el = ForwardedElement::new_forwarded_for(peer_addr);
619 let forwarded = if let Some(mut forwarded) = stream.extensions().get_ref::<Forwarded>().cloned()
620 {
621 forwarded.append(el);
622 forwarded
623 } else {
624 Forwarded::new(el)
625 };
626 stream.extensions().insert(forwarded);
627}
628
629#[cfg(test)]
630mod test {
631 use rama_core::{ServiceInput, service::service_fn};
632
633 use super::*;
634
635 async fn echo(mut stream: impl Io + Unpin) -> Result<Vec<u8>, BoxError> {
636 let mut v = Vec::default();
637 _ = stream.read_to_end(&mut v).await?;
638 Ok(v)
639 }
640
641 #[tokio::test]
642 async fn test_haproxy_peek_direct() {
643 let proxy_svc = HaProxyService::new(service_fn(echo)).with_peek(true);
644
645 let request = ServiceInput::new(std::io::Cursor::new(b"foo".to_vec()));
646 let response = proxy_svc.serve(request).await.unwrap();
647
648 assert_eq!("foo", String::from_utf8(response).unwrap());
649
650 let request = ServiceInput::new(std::io::Cursor::new(
651 b"Hello, this is a test to check if it works.".to_vec(),
652 ));
653 let response = proxy_svc.serve(request).await.unwrap();
654
655 assert_eq!(
656 "Hello, this is a test to check if it works.",
657 String::from_utf8(response).unwrap()
658 );
659 }
660
661 #[tokio::test]
662 async fn test_haproxy_peek_with_haproxy_v1() {
663 let proxy_svc = HaProxyService::new(service_fn(echo));
664
665 let request = ServiceInput::new(std::io::Cursor::new(
666 b"PROXY TCP4 192.0.2.1 198.51.100.1 12345 80\r\n".to_vec(),
667 ));
668 let response = proxy_svc.serve(request).await.unwrap();
669
670 assert_eq!("", String::from_utf8(response).unwrap());
671
672 let request = ServiceInput::new(std::io::Cursor::new(
673 b"PROXY TCP4 192.0.2.1 198.51.100.1 12345 80\r\nfoo".to_vec(),
674 ));
675 let response = proxy_svc.serve(request).await.unwrap();
676
677 assert_eq!("foo", String::from_utf8(response).unwrap());
678
679 let proxy_svc = proxy_svc.with_peek(true);
680
681 let request = ServiceInput::new(std::io::Cursor::new(
682 b"PROXY TCP4 192.0.2.1 198.51.100.1 12345 80\r\n".to_vec(),
683 ));
684 let response = proxy_svc.serve(request).await.unwrap();
685
686 assert_eq!("", String::from_utf8(response).unwrap());
687
688 let request = ServiceInput::new(std::io::Cursor::new(
689 b"PROXY TCP4 192.0.2.1 198.51.100.1 12345 80\r\nfoo".to_vec(),
690 ));
691 let response = proxy_svc.serve(request).await.unwrap();
692
693 assert_eq!("foo", String::from_utf8(response).unwrap());
694 }
695
696 #[tokio::test]
697 async fn test_haproxy_peek_with_haproxy_v2() {
698 const DATA: &[u8] = &[
699 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
700 0x0A, 0x21, 0x11, 0x00, 0x0C, 0xC0, 0x00, 0x02, 0x01, 0xC6, 0x33, 0x64, 0x01, 0x30, 0x39, 0x01, 0xBB, 0x66, 0x6F, 0x6F,
710 ];
711
712 let proxy_svc = HaProxyService::new(service_fn(echo));
713 let request = ServiceInput::new(std::io::Cursor::new(DATA.to_vec()));
714 let response = proxy_svc.serve(request).await.unwrap();
715 assert_eq!("foo", String::from_utf8(response).unwrap());
716
717 let proxy_svc = proxy_svc.with_peek(true);
718 let request = ServiceInput::new(std::io::Cursor::new(DATA.to_vec()));
719 let response = proxy_svc.serve(request).await.unwrap();
720 assert_eq!("foo", String::from_utf8(response).unwrap());
721 }
722
723 #[tokio::test]
728 async fn test_haproxy_peek_v2_fragmented() {
729 const DATA: &[u8] = &[
730 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A, 0x21, 0x11, 0x00, 0x0C, 0xC0, 0x00, 0x02, 0x01, 0xC6, 0x33, 0x64, 0x01, 0x30, 0x39,
732 0x01, 0xBB, b'b', b'a', b'r', ];
735
736 struct Chunked {
737 chunks: std::collections::VecDeque<Vec<u8>>,
738 }
739 impl tokio::io::AsyncRead for Chunked {
740 fn poll_read(
741 mut self: std::pin::Pin<&mut Self>,
742 _cx: &mut std::task::Context<'_>,
743 buf: &mut tokio::io::ReadBuf<'_>,
744 ) -> std::task::Poll<std::io::Result<()>> {
745 if let Some(chunk) = self.chunks.pop_front() {
746 let n = chunk.len().min(buf.remaining());
747 buf.put_slice(&chunk[..n]);
748 if n < chunk.len() {
749 self.chunks.push_front(chunk[n..].to_vec());
750 }
751 }
752 std::task::Poll::Ready(Ok(()))
753 }
754 }
755 impl tokio::io::AsyncWrite for Chunked {
756 fn poll_write(
757 self: std::pin::Pin<&mut Self>,
758 _cx: &mut std::task::Context<'_>,
759 buf: &[u8],
760 ) -> std::task::Poll<std::io::Result<usize>> {
761 std::task::Poll::Ready(Ok(buf.len()))
762 }
763 fn poll_flush(
764 self: std::pin::Pin<&mut Self>,
765 _cx: &mut std::task::Context<'_>,
766 ) -> std::task::Poll<std::io::Result<()>> {
767 std::task::Poll::Ready(Ok(()))
768 }
769 fn poll_shutdown(
770 self: std::pin::Pin<&mut Self>,
771 _cx: &mut std::task::Context<'_>,
772 ) -> std::task::Poll<std::io::Result<()>> {
773 std::task::Poll::Ready(Ok(()))
774 }
775 }
776
777 let chunked = Chunked {
778 chunks: vec![
779 DATA[..4].to_vec(),
780 DATA[4..12].to_vec(),
781 DATA[12..].to_vec(),
782 ]
783 .into(),
784 };
785
786 let input = ServiceInput::new(chunked);
787 let proxy_svc = HaProxyService::new(service_fn(echo)).with_peek(true);
788 let response = proxy_svc.serve(input).await.unwrap();
789 assert_eq!("bar", String::from_utf8(response).unwrap());
790 }
791
792 #[tokio::test]
794 async fn test_haproxy_v2_local_command_strict_reject() {
795 const DATA: &[u8] = &[
796 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
797 0x0A, 0x20, 0x00, 0x00, 0x00, ];
802 let strictness = HaProxyStrictness {
803 reject_local_command: true,
804 ..HaProxyStrictness::default()
805 };
806 let proxy_svc = HaProxyService::new(service_fn(echo)).with_strictness(strictness);
807 let request = ServiceInput::new(std::io::Cursor::new(DATA.to_vec()));
808 proxy_svc.serve(request).await.unwrap_err();
809 }
810
811 #[tokio::test]
815 async fn test_haproxy_v2_local_command_ignores_addresses() {
816 const DATA: &[u8] = &[
819 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
820 0x0A, 0x20, 0x11, 0x00, 0x0C, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0x30, 0x39, 0x01, 0xBB, b'h', b'i',
828 ];
829
830 let cmd = capture_command(DATA).await;
831 let fwd = capture_forwarded(DATA).await;
832 assert_eq!(cmd, Some(HaProxyCommand::Local));
833 assert!(
834 fwd.is_none(),
835 "LOCAL command must not inject Forwarded element"
836 );
837 }
838
839 #[tokio::test]
841 async fn test_haproxy_v2_proxy_command_injects_forwarded() {
842 const DATA: &[u8] = &[
843 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
844 0x0A, 0x21, 0x11, 0x00, 0x0C, 0xC0, 0x00, 0x02, 0x01, 0xC6, 0x33, 0x64, 0x01, 0x30, 0x39, 0x01, 0xBB, ];
852
853 let cmd = capture_command(DATA).await;
854 let fwd = capture_forwarded(DATA).await;
855 assert_eq!(cmd, Some(HaProxyCommand::Proxy));
856 assert!(fwd.is_some());
857 }
858
859 async fn capture_forwarded(data: &[u8]) -> Option<Forwarded> {
862 let captured: std::sync::Arc<parking_lot::Mutex<Option<Forwarded>>> =
863 std::sync::Arc::new(parking_lot::Mutex::new(None));
864 let captured_clone = captured.clone();
865 let inner = service_fn(move |stream| {
866 let captured = captured_clone.clone();
867 async move {
868 let fwd = <_ as ExtensionsRef>::extensions(&stream)
869 .get_ref::<Forwarded>()
870 .cloned();
871 *captured.lock() = fwd;
872 Ok::<_, BoxError>(())
873 }
874 });
875
876 let proxy_svc = HaProxyService::new(inner);
877 let request = ServiceInput::new(std::io::Cursor::new(data.to_vec()));
878 proxy_svc.serve(request).await.unwrap();
879 let guard = captured.lock();
880 guard.clone()
881 }
882
883 async fn capture_command(data: &[u8]) -> Option<HaProxyCommand> {
886 let captured: std::sync::Arc<parking_lot::Mutex<Option<HaProxyCommand>>> =
887 std::sync::Arc::new(parking_lot::Mutex::new(None));
888 let captured_clone = captured.clone();
889 let inner = service_fn(move |stream| {
890 let captured = captured_clone.clone();
891 async move {
892 let cmd = <_ as ExtensionsRef>::extensions(&stream)
893 .get_ref::<HaProxyCommand>()
894 .copied();
895 *captured.lock() = cmd;
896 Ok::<_, BoxError>(())
897 }
898 });
899
900 let proxy_svc = HaProxyService::new(inner);
901 let request = ServiceInput::new(std::io::Cursor::new(data.to_vec()));
902 proxy_svc.serve(request).await.unwrap();
903 let guard = captured.lock();
904 *guard
905 }
906
907 #[tokio::test]
909 async fn test_haproxy_v2_crc32c_invalid_is_rejected() {
910 let mut header = Vec::from(v2::PROTOCOL_PREFIX);
912 header.push(0x21); header.push(0x11); header.extend([0x00, 0x13]); header.extend([192, 0, 2, 1]);
916 header.extend([198, 51, 100, 1]);
917 header.extend([0x30, 0x39]);
918 header.extend([0x01, 0xBB]);
919 header.extend([0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00]);
921
922 let proxy_svc = HaProxyService::new(service_fn(echo));
923 let request = ServiceInput::new(std::io::Cursor::new(header));
924 proxy_svc.serve(request).await.unwrap_err();
925 }
926
927 #[tokio::test]
929 async fn test_haproxy_v2_crc32c_valid_is_accepted() {
930 let mut header = Vec::from(v2::PROTOCOL_PREFIX);
931 header.push(0x21);
932 header.push(0x11);
933 header.extend([0x00, 0x13]); header.extend([192, 0, 2, 1]);
935 header.extend([198, 51, 100, 1]);
936 header.extend([0x30, 0x39]);
937 header.extend([0x01, 0xBB]);
938 header.extend([0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00]);
940 let crc = {
943 let mut h = crate::protocol::v2::Crc32cHasher::new();
944 h.update(&header);
945 h.finalize()
946 };
947 let crc_value_offset = header.len() - 4;
948 header[crc_value_offset..crc_value_offset + 4].copy_from_slice(&crc.to_be_bytes());
949
950 header.extend(b"ok");
952
953 let proxy_svc = HaProxyService::new(service_fn(echo));
954 let request = ServiceInput::new(std::io::Cursor::new(header));
955 let response = proxy_svc.serve(request).await.unwrap();
956 assert_eq!("ok", String::from_utf8(response).unwrap());
957 }
958
959 #[tokio::test]
961 async fn test_haproxy_v2_require_crc32c() {
962 const DATA: &[u8] = &[
963 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
964 0x0A, 0x21, 0x11, 0x00, 0x0C, 0xC0, 0x00, 0x02, 0x01, 0xC6, 0x33, 0x64, 0x01, 0x30, 0x39, 0x01, 0xBB,
967 ];
968 let strictness = HaProxyStrictness {
969 require_crc32c: true,
970 ..HaProxyStrictness::default()
971 };
972 let proxy_svc = HaProxyService::new(service_fn(echo)).with_strictness(strictness);
973 let request = ServiceInput::new(std::io::Cursor::new(DATA.to_vec()));
974 proxy_svc.serve(request).await.unwrap_err();
975 }
976
977 #[tokio::test]
981 async fn test_haproxy_v2_require_crc32c_with_malformed_before_crc_rejects() {
982 let mut header = Vec::from(v2::PROTOCOL_PREFIX);
985 header.push(0x21);
986 header.push(0x11);
987 header.extend([0x00, 0x10]); header.extend([192, 0, 2, 1]);
989 header.extend([198, 51, 100, 1]);
990 header.extend([0x30, 0x39]);
991 header.extend([0x01, 0xBB]);
992 header.extend([0x04, 0x00, 0x63, 0xAA]); let strictness = HaProxyStrictness {
995 require_crc32c: true,
996 ..HaProxyStrictness::default()
997 };
998 let proxy_svc = HaProxyService::new(service_fn(echo)).with_strictness(strictness);
999 let request = ServiceInput::new(std::io::Cursor::new(header));
1000 let err = proxy_svc.serve(request).await.unwrap_err();
1001 assert!(
1002 err.to_string().contains("CRC32C"),
1003 "expected CRC32C-missing-style error, got: {err}",
1004 );
1005 }
1006
1007 #[tokio::test]
1014 async fn test_haproxy_v2_malformed_tlv_without_crc_is_accepted_by_default() {
1015 let mut header = Vec::from(v2::PROTOCOL_PREFIX);
1018 header.push(0x21);
1019 header.push(0x11);
1020 header.extend([0x00, 0x10]); header.extend([192, 0, 2, 1]);
1022 header.extend([198, 51, 100, 1]);
1023 header.extend([0x30, 0x39]);
1024 header.extend([0x01, 0xBB]);
1025 header.extend([0x04, 0x00, 0x63, 0xAA]);
1027
1028 let proxy_svc = HaProxyService::new(service_fn(echo));
1031 let request = ServiceInput::new(std::io::Cursor::new(header.clone()));
1032 proxy_svc.serve(request).await.expect("default accepts");
1033
1034 let strict_svc = HaProxyService::new(service_fn(echo)).with_strictness(HaProxyStrictness {
1036 fail_on_malformed_tlv: true,
1037 ..HaProxyStrictness::default()
1038 });
1039 let request = ServiceInput::new(std::io::Cursor::new(header));
1040 strict_svc.serve(request).await.unwrap_err();
1041 }
1042}