mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-25 05:16:18 +02:00
refactor: better error handling
This commit is contained in:
Vendored
+2
@@ -112,6 +112,7 @@
|
||||
"nobrowse",
|
||||
"noconfirm",
|
||||
"nodecar",
|
||||
"NODELAY",
|
||||
"nodemon",
|
||||
"norestart",
|
||||
"NSIS",
|
||||
@@ -199,6 +200,7 @@
|
||||
"unrs",
|
||||
"urlencoding",
|
||||
"urllib",
|
||||
"utoipa",
|
||||
"venv",
|
||||
"vercel",
|
||||
"VERYSILENT",
|
||||
|
||||
Generated
+53
@@ -1306,6 +1306,7 @@ dependencies = [
|
||||
"clap",
|
||||
"core-foundation 0.10.1",
|
||||
"directories",
|
||||
"env_logger",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http-body-util",
|
||||
@@ -1458,6 +1459,19 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -2524,6 +2538,30 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -3728,6 +3766,21 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
|
||||
@@ -39,6 +39,7 @@ tauri-plugin-dialog = "2"
|
||||
tauri-plugin-macos-permissions = "2"
|
||||
tauri-plugin-log = "2"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
|
||||
directories = "6"
|
||||
reqwest = { version = "0.12", features = ["json", "stream", "socks"] }
|
||||
|
||||
@@ -82,6 +82,12 @@ fn build_proxy_url(
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() {
|
||||
// Initialize logger to write to stderr (which will be redirected to file)
|
||||
env_logger::Builder::from_default_env()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.format_timestamp_millis()
|
||||
.init();
|
||||
|
||||
// Set up panic handler to log panics before process exits
|
||||
std::panic::set_hook(Box::new(|panic_info| {
|
||||
log::error!("PANIC in proxy worker: {:?}", panic_info);
|
||||
|
||||
+10
-25
@@ -624,22 +624,9 @@ impl Browser for FirefoxBrowser {
|
||||
args.push("--headless".to_string());
|
||||
}
|
||||
|
||||
// Use -no-remote for browsers that require it for security (Mullvad, Tor) or when remote debugging
|
||||
match self.browser_type {
|
||||
BrowserType::MullvadBrowser | BrowserType::TorBrowser => {
|
||||
args.push("-no-remote".to_string());
|
||||
}
|
||||
BrowserType::Firefox
|
||||
| BrowserType::FirefoxDeveloper
|
||||
| BrowserType::Zen
|
||||
| BrowserType::Camoufox => {
|
||||
// Use -no-remote when remote debugging to avoid conflicts
|
||||
if remote_debugging_port.is_some() {
|
||||
args.push("-no-remote".to_string());
|
||||
}
|
||||
// Don't use -no-remote for normal launches so we can communicate with existing instances
|
||||
}
|
||||
_ => {}
|
||||
// Use -no-remote when remote debugging to avoid conflicts with existing instances
|
||||
if remote_debugging_port.is_some() {
|
||||
args.push("-no-remote".to_string());
|
||||
}
|
||||
|
||||
// Firefox-based browsers use profile directory and user.js for proxy configuration
|
||||
@@ -741,6 +728,8 @@ impl Browser for ChromiumBrowser {
|
||||
"--disable-session-crashed-bubble".to_string(),
|
||||
"--hide-crash-restore-bubble".to_string(),
|
||||
"--disable-infobars".to_string(),
|
||||
// Disable QUIC/HTTP3 to ensure traffic goes through HTTP proxy
|
||||
"--disable-quic".to_string(),
|
||||
];
|
||||
|
||||
// Add remote debugging if requested
|
||||
@@ -1100,30 +1089,26 @@ mod tests {
|
||||
"Firefox should include debugging port"
|
||||
);
|
||||
|
||||
// Test Mullvad Browser (should always use -no-remote)
|
||||
// Test Mullvad Browser (no special flags without remote debugging)
|
||||
let browser = FirefoxBrowser::new(BrowserType::MullvadBrowser);
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None, None, false)
|
||||
.expect("Failed to create launch args for Mullvad Browser");
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile", "-no-remote"]);
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile"]);
|
||||
|
||||
// Test Tor Browser (should always use -no-remote)
|
||||
// Test Tor Browser (no special flags without remote debugging)
|
||||
let browser = FirefoxBrowser::new(BrowserType::TorBrowser);
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None, None, false)
|
||||
.expect("Failed to create launch args for Tor Browser");
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile", "-no-remote"]);
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile"]);
|
||||
|
||||
// Test Zen Browser (should not use -no-remote for normal launch)
|
||||
// Test Zen Browser (no special flags without remote debugging)
|
||||
let browser = FirefoxBrowser::new(BrowserType::Zen);
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None, None, false)
|
||||
.expect("Failed to create launch args for Zen Browser");
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile"]);
|
||||
assert!(
|
||||
!args.contains(&"-no-remote".to_string()),
|
||||
"Zen Browser should not use -no-remote for normal launch"
|
||||
);
|
||||
|
||||
// Test headless mode
|
||||
let args = browser
|
||||
|
||||
@@ -1159,114 +1159,76 @@ impl ProfileManager {
|
||||
|
||||
let mut preferences = Vec::new();
|
||||
|
||||
// Get the UUID directory (parent of profile data directory)
|
||||
let uuid_dir = profile_data_path
|
||||
.parent()
|
||||
.ok_or("Invalid profile path - cannot find UUID directory")?;
|
||||
|
||||
// Add common Firefox preferences (like disabling default browser check)
|
||||
preferences.extend(self.get_common_firefox_preferences());
|
||||
|
||||
// Use embedded PAC template instead of reading from file
|
||||
const PAC_TEMPLATE: &str = r#"function FindProxyForURL(url, host) {
|
||||
return "{{proxy_url}}";
|
||||
}"#;
|
||||
// Determine which proxy settings to use
|
||||
let effective_proxy = internal_proxy.unwrap_or(proxy);
|
||||
let proxy_host = &effective_proxy.host;
|
||||
let proxy_port = effective_proxy.port;
|
||||
|
||||
// Format proxy URL based on type and whether we have an internal proxy
|
||||
let proxy_url = if let Some(internal) = internal_proxy {
|
||||
// Use internal proxy (local proxy) as the primary proxy
|
||||
// This is the local proxy that forwards to the upstream proxy
|
||||
log::info!(
|
||||
"Applying local proxy settings to Firefox profile: {}:{}",
|
||||
internal.host,
|
||||
internal.port
|
||||
);
|
||||
format!("HTTP {}:{}", internal.host, internal.port)
|
||||
} else {
|
||||
// Use user-configured proxy directly (upstream proxy)
|
||||
log::info!(
|
||||
"Applying upstream proxy settings to Firefox profile: {}:{} ({})",
|
||||
proxy.host,
|
||||
proxy.port,
|
||||
proxy.proxy_type
|
||||
);
|
||||
match proxy.proxy_type.as_str() {
|
||||
"http" => format!("HTTP {}:{}", proxy.host, proxy.port),
|
||||
"https" => format!("HTTPS {}:{}", proxy.host, proxy.port),
|
||||
"socks4" => format!("SOCKS4 {}:{}", proxy.host, proxy.port),
|
||||
"socks5" => format!("SOCKS5 {}:{}", proxy.host, proxy.port),
|
||||
_ => return Err(format!("Unsupported proxy type: {}", proxy.proxy_type).into()),
|
||||
}
|
||||
};
|
||||
// Check if this is a SOCKS proxy (only possible when using upstream directly)
|
||||
let is_socks =
|
||||
internal_proxy.is_none() && (proxy.proxy_type == "socks4" || proxy.proxy_type == "socks5");
|
||||
|
||||
// Replace placeholders in PAC file
|
||||
let pac_content = PAC_TEMPLATE
|
||||
.replace("{{proxy_url}}", &proxy_url)
|
||||
.replace("{{proxy_credentials}}", ""); // Credentials are now handled by the PAC file
|
||||
|
||||
// Save PAC file in UUID directory
|
||||
let pac_path = uuid_dir.join("proxy.pac");
|
||||
log::info!(
|
||||
"Creating PAC file at: {} with proxy: {}",
|
||||
pac_path.display(),
|
||||
proxy_url
|
||||
);
|
||||
fs::write(&pac_path, &pac_content)?;
|
||||
log::info!(
|
||||
"Created PAC file at: {} with content: {}",
|
||||
pac_path.display(),
|
||||
pac_content
|
||||
"Applying manual proxy settings to Firefox profile: {}:{} (is_internal: {}, is_socks: {})",
|
||||
proxy_host,
|
||||
proxy_port,
|
||||
internal_proxy.is_some(),
|
||||
is_socks
|
||||
);
|
||||
|
||||
// Configure Firefox to use the PAC file
|
||||
// Convert path to absolute and properly format for file:// URL
|
||||
let pac_path_absolute = pac_path.canonicalize().unwrap_or_else(|_| pac_path.clone());
|
||||
let pac_url = if cfg!(windows) {
|
||||
// Windows: file:///C:/path/to/file.pac
|
||||
format!(
|
||||
"file:///{}",
|
||||
pac_path_absolute.to_string_lossy().replace('\\', "/")
|
||||
)
|
||||
// Use MANUAL proxy configuration (type 1) instead of PAC file (type 2)
|
||||
// PAC files with file:// URLs are blocked by privacy-focused browsers like Zen and Mullvad
|
||||
// Manual proxy configuration works reliably across all Firefox variants
|
||||
preferences.push("user_pref(\"network.proxy.type\", 1);".to_string());
|
||||
|
||||
if is_socks {
|
||||
// SOCKS proxy configuration
|
||||
preferences.extend([
|
||||
format!("user_pref(\"network.proxy.socks\", \"{}\");", proxy_host),
|
||||
format!("user_pref(\"network.proxy.socks_port\", {});", proxy_port),
|
||||
format!(
|
||||
"user_pref(\"network.proxy.socks_version\", {});",
|
||||
if proxy.proxy_type == "socks5" { 5 } else { 4 }
|
||||
),
|
||||
"user_pref(\"network.proxy.http\", \"\");".to_string(),
|
||||
"user_pref(\"network.proxy.http_port\", 0);".to_string(),
|
||||
"user_pref(\"network.proxy.ssl\", \"\");".to_string(),
|
||||
"user_pref(\"network.proxy.ssl_port\", 0);".to_string(),
|
||||
]);
|
||||
} else {
|
||||
// Unix/macOS: file:///absolute/path/to/file.pac (three slashes for absolute path)
|
||||
format!("file://{}", pac_path_absolute.to_string_lossy())
|
||||
};
|
||||
|
||||
log::info!("PAC file path (absolute): {}", pac_path_absolute.display());
|
||||
log::info!("PAC file URL for Firefox: {}", pac_url);
|
||||
// HTTP/HTTPS proxy configuration (including our internal local proxy)
|
||||
preferences.extend([
|
||||
format!("user_pref(\"network.proxy.http\", \"{}\");", proxy_host),
|
||||
format!("user_pref(\"network.proxy.http_port\", {});", proxy_port),
|
||||
format!("user_pref(\"network.proxy.ssl\", \"{}\");", proxy_host),
|
||||
format!("user_pref(\"network.proxy.ssl_port\", {});", proxy_port),
|
||||
format!("user_pref(\"network.proxy.ftp\", \"{}\");", proxy_host),
|
||||
format!("user_pref(\"network.proxy.ftp_port\", {});", proxy_port),
|
||||
"user_pref(\"network.proxy.socks\", \"\");".to_string(),
|
||||
"user_pref(\"network.proxy.socks_port\", 0);".to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Common proxy settings - keep it simple like proxy-chain expected
|
||||
preferences.extend([
|
||||
"user_pref(\"network.proxy.type\", 2);".to_string(),
|
||||
format!(
|
||||
"user_pref(\"network.proxy.autoconfig_url\", \"{}\");",
|
||||
pac_url
|
||||
),
|
||||
"user_pref(\"network.proxy.failover_direct\", false);".to_string(),
|
||||
"user_pref(\"network.proxy.socks_remote_dns\", true);".to_string(),
|
||||
"user_pref(\"network.proxy.no_proxies_on\", \"\");".to_string(),
|
||||
"user_pref(\"signon.autologin.proxy\", true);".to_string(),
|
||||
"user_pref(\"network.proxy.share_proxy_settings\", false);".to_string(),
|
||||
"user_pref(\"network.automatic-ntlm-auth.allow-proxies\", false);".to_string(),
|
||||
"user_pref(\"network.auth-use-sspi\", false);".to_string(),
|
||||
"user_pref(\"network.proxy.autoconfig_url\", \"\");".to_string(),
|
||||
// Disable QUIC/HTTP3 - it bypasses HTTP proxy
|
||||
"user_pref(\"network.http.http3.enable\", false);".to_string(),
|
||||
"user_pref(\"network.http.http3.enabled\", false);".to_string(),
|
||||
]);
|
||||
|
||||
// Write settings to user.js file
|
||||
let user_js_content = preferences.join("\n");
|
||||
fs::write(user_js_path, &user_js_content)?;
|
||||
log::info!("Updated user.js with proxy settings. PAC URL: {}", pac_url);
|
||||
if let Some(internal) = internal_proxy {
|
||||
log::info!(
|
||||
"Firefox will use LOCAL proxy: {}:{} (which forwards to upstream)",
|
||||
internal.host,
|
||||
internal.port
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"Firefox will use UPSTREAM proxy directly: {}:{}",
|
||||
proxy.host,
|
||||
proxy.port
|
||||
);
|
||||
}
|
||||
log::info!(
|
||||
"Updated user.js with manual proxy settings: {}:{}",
|
||||
proxy_host,
|
||||
proxy_port
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1428,20 +1390,28 @@ mod tests {
|
||||
assert!(user_js_path.exists(), "user.js should be created");
|
||||
|
||||
let content = fs::read_to_string(&user_js_path).expect("Should read user.js");
|
||||
|
||||
// Check for manual proxy configuration (type 1) instead of PAC (type 2)
|
||||
// Manual proxy is used because PAC file:// URLs are blocked by privacy browsers like Zen
|
||||
assert!(
|
||||
content.contains("network.proxy.type"),
|
||||
"Should contain proxy type setting"
|
||||
content.contains("network.proxy.type\", 1"),
|
||||
"Should set proxy type to 1 (manual)"
|
||||
);
|
||||
assert!(content.contains("2"), "Should set proxy type to 2 (PAC)");
|
||||
|
||||
// Check that PAC file was created
|
||||
let pac_path = uuid_dir.join("proxy.pac");
|
||||
assert!(pac_path.exists(), "proxy.pac should be created");
|
||||
|
||||
let pac_content = fs::read_to_string(&pac_path).expect("Should read proxy.pac");
|
||||
assert!(
|
||||
pac_content.contains("FindProxyForURL"),
|
||||
"PAC file should contain FindProxyForURL function"
|
||||
content.contains("network.proxy.http\", \"proxy.example.com\""),
|
||||
"Should set HTTP proxy host"
|
||||
);
|
||||
assert!(
|
||||
content.contains("network.proxy.http_port\", 8080"),
|
||||
"Should set HTTP proxy port"
|
||||
);
|
||||
assert!(
|
||||
content.contains("network.proxy.ssl\", \"proxy.example.com\""),
|
||||
"Should set SSL proxy host"
|
||||
);
|
||||
assert!(
|
||||
content.contains("network.proxy.ssl_port\", 8080"),
|
||||
"Should set SSL proxy port"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,18 +59,12 @@ pub async fn start_proxy_process_with_profile(
|
||||
cmd.stdin(Stdio::null());
|
||||
cmd.stdout(Stdio::null());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let log_path = std::path::PathBuf::from("/tmp").join(format!("donut-proxy-{}.log", id));
|
||||
if let Ok(file) = std::fs::File::create(&log_path) {
|
||||
log::error!("Proxy worker stderr will be logged to: {:?}", log_path);
|
||||
cmd.stderr(Stdio::from(file));
|
||||
} else {
|
||||
cmd.stderr(Stdio::null());
|
||||
}
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
// Always log to file for diagnostics (both debug and release builds)
|
||||
let log_path = std::path::PathBuf::from("/tmp").join(format!("donut-proxy-{}.log", id));
|
||||
if let Ok(file) = std::fs::File::create(&log_path) {
|
||||
log::info!("Proxy worker stderr will be logged to: {:?}", log_path);
|
||||
cmd.stderr(Stdio::from(file));
|
||||
} else {
|
||||
cmd.stderr(Stdio::null());
|
||||
}
|
||||
|
||||
|
||||
@@ -367,6 +367,13 @@ async fn handle_http(
|
||||
// This is faster and more reliable than trying to use hyper-proxy with version conflicts
|
||||
use reqwest::Client;
|
||||
|
||||
log::error!(
|
||||
"DEBUG: Handling HTTP request: {} {} (host: {:?})",
|
||||
req.method(),
|
||||
req.uri(),
|
||||
req.uri().host()
|
||||
);
|
||||
|
||||
// Extract domain for traffic tracking
|
||||
let domain = req
|
||||
.uri()
|
||||
@@ -609,7 +616,12 @@ pub async fn run_proxy_server(config: ProxyConfig) -> Result<(), Box<dyn std::er
|
||||
// This ensures the process doesn't exit even if there are no active connections
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((mut stream, _)) => {
|
||||
Ok((mut stream, peer_addr)) => {
|
||||
// Enable TCP_NODELAY to ensure small packets are sent immediately
|
||||
// This is critical for CONNECT responses to be sent before tunneling begins
|
||||
let _ = stream.set_nodelay(true);
|
||||
log::error!("DEBUG: Accepted connection from {:?}", peer_addr);
|
||||
|
||||
let upstream = upstream_url.clone();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
@@ -617,9 +629,13 @@ pub async fn run_proxy_server(config: ProxyConfig) -> Result<(), Box<dyn std::er
|
||||
// CONNECT requests need special handling for tunneling
|
||||
let mut peek_buffer = [0u8; 8];
|
||||
match stream.read(&mut peek_buffer).await {
|
||||
Ok(n) if n >= 7 => {
|
||||
Ok(0) => {
|
||||
log::error!("DEBUG: Connection closed immediately (0 bytes read)");
|
||||
}
|
||||
Ok(n) => {
|
||||
let request_start = String::from_utf8_lossy(&peek_buffer[..n.min(7)]);
|
||||
if request_start.starts_with("CONNECT") {
|
||||
log::error!("DEBUG: Read {} bytes, starts with: {:?}", n, request_start);
|
||||
if n >= 7 && request_start.starts_with("CONNECT") {
|
||||
// Handle CONNECT request manually for tunneling
|
||||
let mut full_request = Vec::with_capacity(4096);
|
||||
full_request.extend_from_slice(&peek_buffer[..n]);
|
||||
@@ -651,7 +667,14 @@ pub async fn run_proxy_server(config: ProxyConfig) -> Result<(), Box<dyn std::er
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Not CONNECT - reconstruct stream with consumed bytes prepended
|
||||
|
||||
// Not CONNECT (or partial read) - reconstruct stream with consumed bytes prepended
|
||||
// This is critical: we MUST prepend any bytes we consumed, even if < 7 bytes
|
||||
log::error!(
|
||||
"DEBUG: Non-CONNECT request, first {} bytes: {:?}",
|
||||
n,
|
||||
String::from_utf8_lossy(&peek_buffer[..n])
|
||||
);
|
||||
let prepended_bytes = peek_buffer[..n].to_vec();
|
||||
let prepended_reader = PrependReader {
|
||||
prepended: prepended_bytes,
|
||||
@@ -664,17 +687,10 @@ pub async fn run_proxy_server(config: ProxyConfig) -> Result<(), Box<dyn std::er
|
||||
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
|
||||
log::error!("Error serving connection: {:?}", err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// For non-CONNECT requests, use hyper's HTTP handling
|
||||
let io = TokioIo::new(stream);
|
||||
let service = service_fn(move |req| handle_request(req, upstream.clone()));
|
||||
|
||||
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
|
||||
log::error!("Error serving connection: {:?}", err);
|
||||
Err(e) => {
|
||||
log::error!("Error reading from connection: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -807,6 +823,9 @@ async fn handle_connect_from_buffer(
|
||||
}
|
||||
};
|
||||
|
||||
// Enable TCP_NODELAY on target stream for immediate data transfer
|
||||
let _ = target_stream.set_nodelay(true);
|
||||
|
||||
// Send 200 Connection Established response to client
|
||||
// CRITICAL: Must flush after writing to ensure response is sent before tunneling
|
||||
client_stream
|
||||
|
||||
Reference in New Issue
Block a user