Skip to main content

relay_core_lib/capture/
original_dst.rs

1use async_trait::async_trait;
2use std::collections::BTreeSet;
3use std::io;
4use std::net::SocketAddr;
5use tokio::net::TcpStream;
6
7/// Platform-agnostic interface for retrieving original destination address
8#[async_trait]
9pub trait OriginalDstProvider: Send + Sync {
10    /// Returns the original destination address if available
11    fn get_original_dst(&self, stream: &TcpStream) -> io::Result<Option<SocketAddr>>;
12
13    /// Returns all addresses this proxy is listening on (for loop detection)
14    fn get_listen_addrs(&self) -> BTreeSet<SocketAddr>;
15}
16
17#[cfg(all(target_os = "macos", feature = "transparent-macos"))]
18pub use crate::capture::macos_pf::MacOsOriginalDstProvider;
19
20#[cfg(target_os = "windows")]
21pub use crate::capture::windows::WindowsOriginalDstProvider;
22
23/// Linux implementation using SO_ORIGINAL_DST
24#[cfg(all(target_os = "linux", feature = "transparent-linux"))]
25pub struct LinuxOriginalDstProvider {
26    listen_addrs: BTreeSet<SocketAddr>,
27}
28
29#[cfg(all(target_os = "linux", feature = "transparent-linux"))]
30impl LinuxOriginalDstProvider {
31    pub fn new(listen_addrs: BTreeSet<SocketAddr>) -> Self {
32        Self { listen_addrs }
33    }
34}
35
36#[cfg(all(target_os = "linux", feature = "transparent-linux"))]
37#[async_trait]
38impl OriginalDstProvider for LinuxOriginalDstProvider {
39    fn get_original_dst(&self, stream: &TcpStream) -> io::Result<Option<SocketAddr>> {
40        use std::os::unix::io::AsRawFd;
41        let fd = stream.as_raw_fd();
42
43        unsafe {
44            let mut addr: libc::sockaddr_storage = std::mem::zeroed();
45            let mut len = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
46
47            // SO_ORIGINAL_DST = 80
48            // Note: This is specific to IPv4. IPv6 uses IP6T_SO_ORIGINAL_DST (80) with SOL_IPV6.
49            if libc::getsockopt(
50                fd,
51                libc::SOL_IP,
52                80,
53                &mut addr as *mut _ as *mut libc::c_void,
54                &mut len,
55            ) != 0
56            {
57                // If failing, return None rather than error to allow fallback
58                return Ok(None);
59            }
60
61            if addr.ss_family as i32 == libc::AF_INET {
62                let addr_in = *(&addr as *const _ as *const libc::sockaddr_in);
63                // s_addr is network byte order (big endian)
64                // from_be is correct because u32::from_be takes BE and converts to native
65                // But s_addr is u32 in C (usually), let's be careful.
66                // libc::in_addr.s_addr is u32.
67                let ip = std::net::Ipv4Addr::from(u32::from_be(addr_in.sin_addr.s_addr));
68                let port = u16::from_be(addr_in.sin_port);
69                Ok(Some(SocketAddr::new(std::net::IpAddr::V4(ip), port)))
70            } else {
71                // TODO: Support IPv6 transparent proxy
72                // Explicitly return error for IPv6 to avoid silent fallback/failures
73                Err(io::Error::new(
74                    io::ErrorKind::Unsupported,
75                    "IPv6 transparent proxy not implemented",
76                ))
77            }
78        }
79    }
80
81    fn get_listen_addrs(&self) -> BTreeSet<SocketAddr> {
82        self.listen_addrs.clone()
83    }
84}
85
86/// No-op provider for platforms without transparent proxy support or when disabled
87pub struct NoOpOriginalDstProvider {
88    listen_addrs: BTreeSet<SocketAddr>,
89}
90
91impl NoOpOriginalDstProvider {
92    pub fn new(listen_addrs: BTreeSet<SocketAddr>) -> Self {
93        Self { listen_addrs }
94    }
95}
96
97#[async_trait]
98impl OriginalDstProvider for NoOpOriginalDstProvider {
99    fn get_original_dst(&self, _stream: &TcpStream) -> io::Result<Option<SocketAddr>> {
100        Ok(None)
101    }
102
103    fn get_listen_addrs(&self) -> BTreeSet<SocketAddr> {
104        self.listen_addrs.clone()
105    }
106}