From 518d26b25fa5f28e8ac87c6129781119f470e3ed Mon Sep 17 00:00:00 2001 From: Mg Pig Date: Wed, 28 Jan 2026 14:48:45 +0800 Subject: [PATCH] feat: add X-Network-Name header to HTTP connector requests (#1839) This allows HTTP redirect servers to provide network-specific node lists based on the client's network identity. Updated unit tests to verify the header is correctly sent. --- easytier/src/connector/http_connector.rs | 52 ++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/easytier/src/connector/http_connector.rs b/easytier/src/connector/http_connector.rs index daefebbf..0c83f05b 100644 --- a/easytier/src/connector/http_connector.rs +++ b/easytier/src/connector/http_connector.rs @@ -169,13 +169,19 @@ impl HttpTunnelConnector { let original_url_clone = original_url.to_string(); let body_clone = body.clone(); + let network_name = self.global_ctx.network.network_name.clone(); let res = tokio::task::spawn_blocking(move || { let uri = http_req::uri::Uri::try_from(original_url_clone.as_ref()) .with_context(|| format!("parsing url failed. url: {}", original_url_clone))?; - tracing::info!("sending http request to {}", uri); + tracing::info!( + "sending http request to {}, network_name: {}", + uri, + network_name + ); Request::new(&uri) + .header("X-Network-Name", &network_name) .redirect_policy(RedirectPolicy::Limit(0)) .timeout(std::time::Duration::from_secs(20)) .send(&mut *body_clone.write().unwrap()) @@ -244,19 +250,39 @@ impl super::TunnelConnector for HttpTunnelConnector { #[cfg(test)] mod tests { - use tokio::{io::AsyncWriteExt as _, net::TcpListener}; + use tokio::{io::AsyncReadExt as _, io::AsyncWriteExt as _, net::TcpListener}; use crate::{ - common::global_ctx::tests::get_mock_global_ctx, + common::global_ctx::tests::get_mock_global_ctx_with_network, tunnel::{tcp::TcpTunnelListener, TunnelConnector, TunnelListener}, }; use super::*; - async fn run_http_redirect_server(port: u16, test_type: HttpRedirectType) -> Result<(), Error> { - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?; + async fn run_http_redirect_server( + port: u16, + test_type: HttpRedirectType, + ) -> Result { + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?; let (mut stream, _) = listener.accept().await?; + let mut buf = [0u8; 4096]; + let n = stream.read(&mut buf).await?; + let req = String::from_utf8_lossy(&buf[..n]); + + let mut captured_network_name = String::new(); + for line in req.lines() { + if line.to_lowercase().starts_with("x-network-name:") { + captured_network_name = line + .split_once(':') + .map(|x| x.1) + .unwrap_or_default() + .trim() + .to_string(); + break; + } + } + match test_type { HttpRedirectType::RedirectToQuery => { let resp = "HTTP/1.1 301 Moved Permanently\r\nLocation: http://test.com/?url=tcp://127.0.0.1:25888\r\n\r\n"; @@ -276,7 +302,7 @@ mod tests { } } - Ok(()) + Ok(captured_network_name) } #[rstest::rstest] @@ -292,10 +318,17 @@ mod tests { )] test_type: HttpRedirectType, ) { + let network_name = format!("net_{}", rand::random::()); let http_task = tokio::spawn(run_http_redirect_server(35888, test_type)); tokio::time::sleep(std::time::Duration::from_millis(10)).await; let test_url: url::Url = "http://127.0.0.1:35888".parse().unwrap(); - let global_ctx = get_mock_global_ctx(); + + let identity = crate::common::config::NetworkIdentity { + network_name: network_name.clone(), + ..Default::default() + }; + let global_ctx = get_mock_global_ctx_with_network(Some(identity)); + let mut flags = global_ctx.config.get_flags(); flags.bind_device = false; global_ctx.config.set_flags(flags); @@ -310,11 +343,14 @@ mod tests { let t = connector.connect().await.unwrap(); assert_eq!(connector.redirect_type, test_type); + + let captured_name = http_task.await.unwrap().unwrap(); + assert_eq!(captured_name, network_name); + let info = t.info().unwrap(); let remote_addr = info.remote_addr.unwrap(); assert_eq!(remote_addr, test_url.into()); tokio::join!(task).0.unwrap(); - tokio::join!(http_task).0.unwrap().unwrap(); } }