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.
This commit is contained in:
Mg Pig
2026-01-28 14:48:45 +08:00
committed by GitHub
parent 101f416268
commit 518d26b25f
+44 -8
View File
@@ -169,13 +169,19 @@ impl HttpTunnelConnector {
let original_url_clone = original_url.to_string(); let original_url_clone = original_url.to_string();
let body_clone = body.clone(); let body_clone = body.clone();
let network_name = self.global_ctx.network.network_name.clone();
let res = tokio::task::spawn_blocking(move || { let res = tokio::task::spawn_blocking(move || {
let uri = http_req::uri::Uri::try_from(original_url_clone.as_ref()) let uri = http_req::uri::Uri::try_from(original_url_clone.as_ref())
.with_context(|| format!("parsing url failed. url: {}", original_url_clone))?; .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) Request::new(&uri)
.header("X-Network-Name", &network_name)
.redirect_policy(RedirectPolicy::Limit(0)) .redirect_policy(RedirectPolicy::Limit(0))
.timeout(std::time::Duration::from_secs(20)) .timeout(std::time::Duration::from_secs(20))
.send(&mut *body_clone.write().unwrap()) .send(&mut *body_clone.write().unwrap())
@@ -244,19 +250,39 @@ impl super::TunnelConnector for HttpTunnelConnector {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use tokio::{io::AsyncWriteExt as _, net::TcpListener}; use tokio::{io::AsyncReadExt as _, io::AsyncWriteExt as _, net::TcpListener};
use crate::{ 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}, tunnel::{tcp::TcpTunnelListener, TunnelConnector, TunnelListener},
}; };
use super::*; use super::*;
async fn run_http_redirect_server(port: u16, test_type: HttpRedirectType) -> Result<(), Error> { async fn run_http_redirect_server(
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?; port: u16,
test_type: HttpRedirectType,
) -> Result<String, Error> {
let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?;
let (mut stream, _) = listener.accept().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 { match test_type {
HttpRedirectType::RedirectToQuery => { 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"; 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] #[rstest::rstest]
@@ -292,10 +318,17 @@ mod tests {
)] )]
test_type: HttpRedirectType, test_type: HttpRedirectType,
) { ) {
let network_name = format!("net_{}", rand::random::<u32>());
let http_task = tokio::spawn(run_http_redirect_server(35888, test_type)); let http_task = tokio::spawn(run_http_redirect_server(35888, test_type));
tokio::time::sleep(std::time::Duration::from_millis(10)).await; tokio::time::sleep(std::time::Duration::from_millis(10)).await;
let test_url: url::Url = "http://127.0.0.1:35888".parse().unwrap(); 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(); let mut flags = global_ctx.config.get_flags();
flags.bind_device = false; flags.bind_device = false;
global_ctx.config.set_flags(flags); global_ctx.config.set_flags(flags);
@@ -310,11 +343,14 @@ mod tests {
let t = connector.connect().await.unwrap(); let t = connector.connect().await.unwrap();
assert_eq!(connector.redirect_type, test_type); 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 info = t.info().unwrap();
let remote_addr = info.remote_addr.unwrap(); let remote_addr = info.remote_addr.unwrap();
assert_eq!(remote_addr, test_url.into()); assert_eq!(remote_addr, test_url.into());
tokio::join!(task).0.unwrap(); tokio::join!(task).0.unwrap();
tokio::join!(http_task).0.unwrap().unwrap();
} }
} }