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 = true
|
||||||
mask_port = 443
|
mask_port = 443
|
||||||
# mask_host = "petrovich.ru" # Defaults to tls_domain if not set
|
# 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
|
fake_cert_len = 2048
|
||||||
|
|
||||||
# === Access Control & Users ===
|
# === Access Control & Users ===
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ tls_domain = "google.ru"
|
|||||||
mask = true
|
mask = true
|
||||||
mask_port = 443
|
mask_port = 443
|
||||||
# mask_host = "petrovich.ru" # Defaults to tls_domain if not set
|
# 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
|
fake_cert_len = 2048
|
||||||
|
|
||||||
# === Access Control & Users ===
|
# === Access Control & Users ===
|
||||||
|
|||||||
@@ -212,6 +212,9 @@ pub struct AntiCensorshipConfig {
|
|||||||
#[serde(default = "default_mask_port")]
|
#[serde(default = "default_mask_port")]
|
||||||
pub mask_port: u16,
|
pub mask_port: u16,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub mask_unix_sock: Option<String>,
|
||||||
|
|
||||||
#[serde(default = "default_fake_cert_len")]
|
#[serde(default = "default_fake_cert_len")]
|
||||||
pub fake_cert_len: usize,
|
pub fake_cert_len: usize,
|
||||||
}
|
}
|
||||||
@@ -223,6 +226,7 @@ impl Default for AntiCensorshipConfig {
|
|||||||
mask: true,
|
mask: true,
|
||||||
mask_host: None,
|
mask_host: None,
|
||||||
mask_port: default_mask_port(),
|
mask_port: default_mask_port(),
|
||||||
|
mask_unix_sock: None,
|
||||||
fake_cert_len: default_fake_cert_len(),
|
fake_cert_len: default_fake_cert_len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,8 +380,33 @@ impl ProxyConfig {
|
|||||||
return Err(ProxyError::Config("tls_domain cannot be empty".to_string()));
|
return Err(ProxyError::Config("tls_domain cannot be empty".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default mask_host to tls_domain if not set
|
// Validate mask_unix_sock
|
||||||
if config.censorship.mask_host.is_none() {
|
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());
|
config.censorship.mask_host = Some(config.censorship.tls_domain.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +458,7 @@ impl ProxyConfig {
|
|||||||
format!("Invalid tls_domain: '{}'. Must be a valid domain name", self.censorship.tls_domain)
|
format!("Invalid tls_domain: '{}'. Must be a valid domain name", self.censorship.tls_domain)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.secure,
|
||||||
config.general.modes.tls);
|
config.general.modes.tls);
|
||||||
info!("TLS domain: {}", config.censorship.tls_domain);
|
info!("TLS domain: {}", config.censorship.tls_domain);
|
||||||
info!("Mask: {} -> {}:{}",
|
if let Some(ref sock) = config.censorship.mask_unix_sock {
|
||||||
config.censorship.mask,
|
info!("Mask: {} -> unix:{}", config.censorship.mask, sock);
|
||||||
config.censorship.mask_host.as_deref().unwrap_or(&config.censorship.tls_domain),
|
if !std::path::Path::new(sock).exists() {
|
||||||
config.censorship.mask_port);
|
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" {
|
if config.censorship.tls_domain == "www.google.com" {
|
||||||
warn!("Using default tls_domain. Consider setting a custom domain.");
|
warn!("Using default tls_domain. Consider setting a custom domain.");
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::str;
|
use std::str;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use tokio::net::UnixStream;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
@@ -24,33 +26,33 @@ fn detect_client_type(data: &[u8]) -> &'static str {
|
|||||||
return "HTTP";
|
return "HTTP";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for TLS ClientHello (0x16 = handshake, 0x03 0x01-0x03 = TLS version)
|
// Check for TLS ClientHello (0x16 = handshake, 0x03 0x01-0x03 = TLS version)
|
||||||
if data.len() > 3 && data[0] == 0x16 && data[1] == 0x03 {
|
if data.len() > 3 && data[0] == 0x16 && data[1] == 0x03 {
|
||||||
return "TLS-scanner";
|
return "TLS-scanner";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for SSH
|
// Check for SSH
|
||||||
if data.starts_with(b"SSH-") {
|
if data.starts_with(b"SSH-") {
|
||||||
return "SSH";
|
return "SSH";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port scanner (very short data)
|
// Port scanner (very short data)
|
||||||
if data.len() < 10 {
|
if data.len() < 10 {
|
||||||
return "port-scanner";
|
return "port-scanner";
|
||||||
}
|
}
|
||||||
|
|
||||||
"unknown"
|
"unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a bad client by forwarding to mask host
|
/// Handle a bad client by forwarding to mask host
|
||||||
pub async fn handle_bad_client<R, W>(
|
pub async fn handle_bad_client<R, W>(
|
||||||
mut reader: R,
|
reader: R,
|
||||||
mut writer: W,
|
writer: W,
|
||||||
initial_data: &[u8],
|
initial_data: &[u8],
|
||||||
config: &ProxyConfig,
|
config: &ProxyConfig,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
R: AsyncRead + Unpin + Send + 'static,
|
R: AsyncRead + Unpin + Send + 'static,
|
||||||
W: AsyncWrite + Unpin + Send + 'static,
|
W: AsyncWrite + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
@@ -59,13 +61,41 @@ where
|
|||||||
consume_client_data(reader).await;
|
consume_client_data(reader).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let client_type = detect_client_type(initial_data);
|
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()
|
let mask_host = config.censorship.mask_host.as_deref()
|
||||||
.unwrap_or(&config.censorship.tls_domain);
|
.unwrap_or(&config.censorship.tls_domain);
|
||||||
let mask_port = config.censorship.mask_port;
|
let mask_port = config.censorship.mask_port;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
client_type = client_type,
|
client_type = client_type,
|
||||||
host = %mask_host,
|
host = %mask_host,
|
||||||
@@ -73,35 +103,45 @@ where
|
|||||||
data_len = initial_data.len(),
|
data_len = initial_data.len(),
|
||||||
"Forwarding bad client to mask host"
|
"Forwarding bad client to mask host"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Connect to mask host
|
// Connect to mask host
|
||||||
let mask_addr = format!("{}:{}", mask_host, mask_port);
|
let mask_addr = format!("{}:{}", mask_host, mask_port);
|
||||||
let connect_result = timeout(
|
let connect_result = timeout(MASK_TIMEOUT, TcpStream::connect(&mask_addr)).await;
|
||||||
MASK_TIMEOUT,
|
match connect_result {
|
||||||
TcpStream::connect(&mask_addr)
|
Ok(Ok(stream)) => {
|
||||||
).await;
|
let (mask_read, mask_write) = stream.into_split();
|
||||||
|
relay_to_mask(reader, writer, mask_read, mask_write, initial_data).await;
|
||||||
let mask_stream = match connect_result {
|
}
|
||||||
Ok(Ok(s)) => s,
|
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
debug!(error = %e, "Failed to connect to mask host");
|
debug!(error = %e, "Failed to connect to mask host");
|
||||||
consume_client_data(reader).await;
|
consume_client_data(reader).await;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
debug!("Timeout connecting to mask host");
|
debug!("Timeout connecting to mask host");
|
||||||
consume_client_data(reader).await;
|
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
|
// Send initial data to mask host
|
||||||
if mask_write.write_all(initial_data).await.is_err() {
|
if mask_write.write_all(initial_data).await.is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relay traffic
|
// Relay traffic
|
||||||
let c2m = tokio::spawn(async move {
|
let c2m = tokio::spawn(async move {
|
||||||
let mut buf = vec![0u8; MASK_BUFFER_SIZE];
|
let mut buf = vec![0u8; MASK_BUFFER_SIZE];
|
||||||
@@ -119,7 +159,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let m2c = tokio::spawn(async move {
|
let m2c = tokio::spawn(async move {
|
||||||
let mut buf = vec![0u8; MASK_BUFFER_SIZE];
|
let mut buf = vec![0u8; MASK_BUFFER_SIZE];
|
||||||
loop {
|
loop {
|
||||||
@@ -136,7 +176,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for either to complete
|
// Wait for either to complete
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = c2m => {}
|
_ = c2m => {}
|
||||||
@@ -152,4 +192,4 @@ async fn consume_client_data<R: AsyncRead + Unpin>(mut reader: R) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user