Skip to main content

relay_core_lib/capture/
original_dst.rs

1use std::net::SocketAddr;
2use tokio::net::TcpStream;
3use std::collections::BTreeSet;
4use std::io;
5use async_trait::async_trait;
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                // If failing, return None rather than error to allow fallback
57                return Ok(None);
58            }
59            
60            if addr.ss_family as i32 == libc::AF_INET {
61                let addr_in = *(&addr as *const _ as *const libc::sockaddr_in);
62                // s_addr is network byte order (big endian)
63                // from_be is correct because u32::from_be takes BE and converts to native
64                // But s_addr is u32 in C (usually), let's be careful.
65                // libc::in_addr.s_addr is u32.
66                let ip = std::net::Ipv4Addr::from(u32::from_be(addr_in.sin_addr.s_addr));
67                let port = u16::from_be(addr_in.sin_port);
68                Ok(Some(SocketAddr::new(std::net::IpAddr::V4(ip), port)))
69            } else {
70                // TODO: Support IPv6 transparent proxy
71                // Explicitly return error for IPv6 to avoid silent fallback/failures
72                Err(io::Error::new(io::ErrorKind::Unsupported, "IPv6 transparent proxy not implemented"))
73            }
74        }
75    }
76
77    fn get_listen_addrs(&self) -> BTreeSet<SocketAddr> {
78        self.listen_addrs.clone()
79    }
80}
81
82/// No-op provider for platforms without transparent proxy support or when disabled
83pub struct NoOpOriginalDstProvider {
84    listen_addrs: BTreeSet<SocketAddr>,
85}
86
87impl NoOpOriginalDstProvider {
88    pub fn new(listen_addrs: BTreeSet<SocketAddr>) -> Self {
89        Self { listen_addrs }
90    }
91}
92
93#[async_trait]
94impl OriginalDstProvider for NoOpOriginalDstProvider {
95    fn get_original_dst(&self, _stream: &TcpStream) -> io::Result<Option<SocketAddr>> {
96        Ok(None)
97    }
98    
99    fn get_listen_addrs(&self) -> BTreeSet<SocketAddr> {
100        self.listen_addrs.clone()
101    }
102}