Add mask_unix_sock for censorship masking via Unix socket
This commit is contained in:
@@ -188,6 +188,7 @@ tls_domain = "petrovich.ru"
|
||||
mask = true
|
||||
mask_port = 443
|
||||
# mask_host = "petrovich.ru" # Defaults to tls_domain if not set
|
||||
# mask_unix_sock = "/var/run/nginx.sock" # Unix socket (mutually exclusive with mask_host)
|
||||
fake_cert_len = 2048
|
||||
|
||||
# === Access Control & Users ===
|
||||
|
||||
@@ -48,6 +48,7 @@ tls_domain = "google.ru"
|
||||
mask = true
|
||||
mask_port = 443
|
||||
# mask_host = "petrovich.ru" # Defaults to tls_domain if not set
|
||||
# mask_unix_sock = "/var/run/nginx.sock" # Unix socket (mutually exclusive with mask_host)
|
||||
fake_cert_len = 2048
|
||||
|
||||
# === Access Control & Users ===
|
||||
|
||||
@@ -212,6 +212,9 @@ pub struct AntiCensorshipConfig {
|
||||
#[serde(default = "default_mask_port")]
|
||||
pub mask_port: u16,
|
||||
|
||||
#[serde(default)]
|
||||
pub mask_unix_sock: Option<String>,
|
||||
|
||||
#[serde(default = "default_fake_cert_len")]
|
||||
pub fake_cert_len: usize,
|
||||
}
|
||||
@@ -223,6 +226,7 @@ impl Default for AntiCensorshipConfig {
|
||||
mask: true,
|
||||
mask_host: None,
|
||||
mask_port: default_mask_port(),
|
||||
mask_unix_sock: None,
|
||||
fake_cert_len: default_fake_cert_len(),
|
||||
}
|
||||
}
|
||||
@@ -376,8 +380,33 @@ impl ProxyConfig {
|
||||
return Err(ProxyError::Config("tls_domain cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
// Default mask_host to tls_domain if not set
|
||||
if config.censorship.mask_host.is_none() {
|
||||
// Validate mask_unix_sock
|
||||
if let Some(ref sock_path) = config.censorship.mask_unix_sock {
|
||||
if sock_path.is_empty() {
|
||||
return Err(ProxyError::Config(
|
||||
"mask_unix_sock cannot be empty".to_string()
|
||||
));
|
||||
}
|
||||
#[cfg(unix)]
|
||||
if sock_path.len() > 107 {
|
||||
return Err(ProxyError::Config(
|
||||
format!("mask_unix_sock path too long: {} bytes (max 107)", sock_path.len())
|
||||
));
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
return Err(ProxyError::Config(
|
||||
"mask_unix_sock is only supported on Unix platforms".to_string()
|
||||
));
|
||||
|
||||
if config.censorship.mask_host.is_some() {
|
||||
return Err(ProxyError::Config(
|
||||
"mask_unix_sock and mask_host are mutually exclusive".to_string()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Default mask_host to tls_domain if not set and no unix socket configured
|
||||
if config.censorship.mask_host.is_none() && config.censorship.mask_unix_sock.is_none() {
|
||||
config.censorship.mask_host = Some(config.censorship.tls_domain.clone());
|
||||
}
|
||||
|
||||
|
||||
15
src/main.rs
15
src/main.rs
@@ -130,10 +130,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
config.general.modes.secure,
|
||||
config.general.modes.tls);
|
||||
info!("TLS domain: {}", config.censorship.tls_domain);
|
||||
info!("Mask: {} -> {}:{}",
|
||||
config.censorship.mask,
|
||||
config.censorship.mask_host.as_deref().unwrap_or(&config.censorship.tls_domain),
|
||||
config.censorship.mask_port);
|
||||
if let Some(ref sock) = config.censorship.mask_unix_sock {
|
||||
info!("Mask: {} -> unix:{}", config.censorship.mask, sock);
|
||||
if !std::path::Path::new(sock).exists() {
|
||||
warn!("Unix socket '{}' does not exist yet. Masking will fail until it appears.", sock);
|
||||
}
|
||||
} else {
|
||||
info!("Mask: {} -> {}:{}",
|
||||
config.censorship.mask,
|
||||
config.censorship.mask_host.as_deref().unwrap_or(&config.censorship.tls_domain),
|
||||
config.censorship.mask_port);
|
||||
}
|
||||
|
||||
if config.censorship.tls_domain == "www.google.com" {
|
||||
warn!("Using default tls_domain. Consider setting a custom domain.");
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
use std::time::Duration;
|
||||
use std::str;
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixStream;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::time::timeout;
|
||||
use tracing::debug;
|
||||
@@ -45,8 +47,8 @@ fn detect_client_type(data: &[u8]) -> &'static str {
|
||||
|
||||
/// Handle a bad client by forwarding to mask host
|
||||
pub async fn handle_bad_client<R, W>(
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
reader: R,
|
||||
writer: W,
|
||||
initial_data: &[u8],
|
||||
config: &ProxyConfig,
|
||||
)
|
||||
@@ -62,6 +64,34 @@ where
|
||||
|
||||
let client_type = detect_client_type(initial_data);
|
||||
|
||||
// Connect via Unix socket or TCP
|
||||
#[cfg(unix)]
|
||||
if let Some(ref sock_path) = config.censorship.mask_unix_sock {
|
||||
debug!(
|
||||
client_type = client_type,
|
||||
sock = %sock_path,
|
||||
data_len = initial_data.len(),
|
||||
"Forwarding bad client to mask unix socket"
|
||||
);
|
||||
|
||||
let connect_result = timeout(MASK_TIMEOUT, UnixStream::connect(sock_path)).await;
|
||||
match connect_result {
|
||||
Ok(Ok(stream)) => {
|
||||
let (mask_read, mask_write) = stream.into_split();
|
||||
relay_to_mask(reader, writer, mask_read, mask_write, initial_data).await;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
debug!(error = %e, "Failed to connect to mask unix socket");
|
||||
consume_client_data(reader).await;
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Timeout connecting to mask unix socket");
|
||||
consume_client_data(reader).await;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mask_host = config.censorship.mask_host.as_deref()
|
||||
.unwrap_or(&config.censorship.tls_domain);
|
||||
let mask_port = config.censorship.mask_port;
|
||||
@@ -76,27 +106,37 @@ where
|
||||
|
||||
// Connect to mask host
|
||||
let mask_addr = format!("{}:{}", mask_host, mask_port);
|
||||
let connect_result = timeout(
|
||||
MASK_TIMEOUT,
|
||||
TcpStream::connect(&mask_addr)
|
||||
).await;
|
||||
|
||||
let mask_stream = match connect_result {
|
||||
Ok(Ok(s)) => s,
|
||||
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();
|
||||
relay_to_mask(reader, writer, mask_read, mask_write, initial_data).await;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
debug!(error = %e, "Failed to connect to mask host");
|
||||
consume_client_data(reader).await;
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Timeout connecting to mask host");
|
||||
consume_client_data(reader).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (mut mask_read, mut mask_write) = mask_stream.into_split();
|
||||
}
|
||||
}
|
||||
|
||||
/// Relay traffic between client and mask backend
|
||||
async fn relay_to_mask<R, W, MR, MW>(
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
mut mask_read: MR,
|
||||
mut mask_write: MW,
|
||||
initial_data: &[u8],
|
||||
)
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + 'static,
|
||||
W: AsyncWrite + Unpin + Send + 'static,
|
||||
MR: AsyncRead + Unpin + Send + 'static,
|
||||
MW: AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
// Send initial data to mask host
|
||||
if mask_write.write_all(initial_data).await.is_err() {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user