diff --git a/config.full.toml b/config.full.toml index 44db620..ac55167 100644 --- a/config.full.toml +++ b/config.full.toml @@ -135,6 +135,7 @@ mask = true # mask_host = "www.google.com" # example, defaults to tls_domain when both mask_host/mask_unix_sock are unset # mask_unix_sock = "/var/run/nginx.sock" # example, mutually exclusive with mask_host mask_port = 443 +# mask_proxy_protocol = 0 # Send PROXY protocol header to mask_host: 0 = off, 1 = v1 (text), 2 = v2 (binary) fake_cert_len = 2048 # if tls_emulation=false and default value is used, loader may randomize this value at runtime tls_emulation = true tls_front_dir = "tlsfront" diff --git a/src/config/types.rs b/src/config/types.rs index 1302a97..7859385 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -611,6 +611,12 @@ pub struct AntiCensorshipConfig { /// Enforce ALPN echo of client preference. #[serde(default = "default_alpn_enforce")] pub alpn_enforce: bool, + + /// Send PROXY protocol header when connecting to mask_host. + /// 0 = disabled, 1 = v1 (text), 2 = v2 (binary). + /// Allows the backend to see the real client IP. + #[serde(default)] + pub mask_proxy_protocol: u8, } impl Default for AntiCensorshipConfig { diff --git a/src/proxy/client.rs b/src/proxy/client.rs index c598023..d8bbc48 100644 --- a/src/proxy/client.rs +++ b/src/proxy/client.rs @@ -143,7 +143,7 @@ where reader, writer, &first_bytes, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -168,7 +168,7 @@ where reader, writer, &handshake, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -212,7 +212,7 @@ where reader, writer, &first_bytes, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -237,7 +237,7 @@ where reader, writer, &handshake, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -462,7 +462,7 @@ impl RunningClientHandler { reader, writer, &first_bytes, - peer.ip(), + peer, &self.config, &self.beobachten, ) @@ -501,7 +501,7 @@ impl RunningClientHandler { reader, writer, &handshake, - peer.ip(), + peer, &config, &self.beobachten, ) @@ -570,7 +570,7 @@ impl RunningClientHandler { reader, writer, &first_bytes, - peer.ip(), + peer, &self.config, &self.beobachten, ) @@ -608,7 +608,7 @@ impl RunningClientHandler { reader, writer, &handshake, - peer.ip(), + peer, &config, &self.beobachten, ) diff --git a/src/proxy/masking.rs b/src/proxy/masking.rs index cdb6cf9..ccb1e91 100644 --- a/src/proxy/masking.rs +++ b/src/proxy/masking.rs @@ -1,7 +1,7 @@ //! Masking - forward unrecognized traffic to mask host use std::str; -use std::net::IpAddr; +use std::net::SocketAddr; use std::time::Duration; use tokio::net::TcpStream; #[cfg(unix)] @@ -11,6 +11,7 @@ use tokio::time::timeout; use tracing::debug; use crate::config::ProxyConfig; use crate::stats::beobachten::BeobachtenStore; +use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder}; const MASK_TIMEOUT: Duration = Duration::from_secs(5); /// Maximum duration for the entire masking relay. @@ -52,7 +53,7 @@ pub async fn handle_bad_client( reader: R, writer: W, initial_data: &[u8], - peer_ip: IpAddr, + peer: SocketAddr, config: &ProxyConfig, beobachten: &BeobachtenStore, ) @@ -63,7 +64,7 @@ where let client_type = detect_client_type(initial_data); if config.general.beobachten { let ttl = Duration::from_secs(config.general.beobachten_minutes.saturating_mul(60)); - beobachten.record(client_type, peer_ip, ttl); + beobachten.record(client_type, peer.ip(), ttl); } if !config.censorship.mask { @@ -119,7 +120,44 @@ where let connect_result = timeout(MASK_TIMEOUT, TcpStream::connect(&mask_addr)).await; match connect_result { Ok(Ok(stream)) => { - let (mask_read, mask_write) = stream.into_split(); + let proxy_header: Option> = match config.censorship.mask_proxy_protocol { + 0 => None, + version => { + let header = if let Ok(local_addr) = stream.local_addr() { + match version { + 2 => match (peer, local_addr) { + (SocketAddr::V4(src), SocketAddr::V4(dst)) => + ProxyProtocolV2Builder::new().with_addrs(src.into(), dst.into()).build(), + (SocketAddr::V6(src), SocketAddr::V6(dst)) => + ProxyProtocolV2Builder::new().with_addrs(src.into(), dst.into()).build(), + _ => + ProxyProtocolV2Builder::new().build(), + }, + _ => match (peer, local_addr) { + (SocketAddr::V4(src), SocketAddr::V4(dst)) => + ProxyProtocolV1Builder::new().tcp4(src.into(), dst.into()).build(), + (SocketAddr::V6(src), SocketAddr::V6(dst)) => + ProxyProtocolV1Builder::new().tcp6(src.into(), dst.into()).build(), + _ => + ProxyProtocolV1Builder::new().build(), + }, + } + } else { + match version { + 2 => ProxyProtocolV2Builder::new().build(), + _ => ProxyProtocolV1Builder::new().build(), + } + }; + Some(header) + } + }; + + let (mask_read, mut mask_write) = stream.into_split(); + if let Some(header) = proxy_header { + if mask_write.write_all(&header).await.is_err() { + return; + } + } if timeout(MASK_RELAY_TIMEOUT, relay_to_mask(reader, writer, mask_read, mask_write, initial_data)).await.is_err() { debug!("Mask relay timed out"); } diff --git a/src/transport/proxy_protocol.rs b/src/transport/proxy_protocol.rs index 770be7e..13278c3 100644 --- a/src/transport/proxy_protocol.rs +++ b/src/transport/proxy_protocol.rs @@ -233,14 +233,12 @@ async fn parse_v2( } /// Builder for PROXY protocol v1 header -#[allow(dead_code)] pub struct ProxyProtocolV1Builder { family: &'static str, src_addr: Option, dst_addr: Option, } -#[allow(dead_code)] impl ProxyProtocolV1Builder { pub fn new() -> Self { Self { @@ -288,13 +286,11 @@ impl Default for ProxyProtocolV1Builder { } /// Builder for PROXY protocol v2 header -#[allow(dead_code)] pub struct ProxyProtocolV2Builder { src: Option, dst: Option, } -#[allow(dead_code)] impl ProxyProtocolV2Builder { pub fn new() -> Self { Self { src: None, dst: None }