內網代理可以實現不想暴露太多信息給外部,但是又能提供內部的完整信息支持,相當於建立了一條可用的HTTP通道。可以在有這方面需求的人優化網路結構。 ...
wmproxy
wmproxy
已用Rust
實現http/https
代理, socks5
代理, 反向代理, 靜態文件伺服器,四層TCP/UDP轉發,七層負載均衡,內網穿透,後續將實現websocket
代理等,會將實現過程分享出來,感興趣的可以一起造個輪子
項目地址
國內: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
項目設計目標
- HTTP轉發
- HTTPS轉發(證書在伺服器,內網為HTTP)
- TCP轉發(純粹的TCP轉發,保持原樣的協議)
- PROXY轉發(服務端接收數據,內網的客戶端當成PROXY客戶端,相當於逆向訪問內網伺服器,[新增])
實現方案
服務端提供客戶端的連接埠,可加密Tls
,可雙向加密mTls
,可賬號密碼認證,客戶端連接服務端的埠等待數據的處理。主要有兩個類服務端CenterServer
及客戶端CenterClient
一些細節可以參考第5篇,第6篇,第10篇,第12篇,有相關的內網穿透的細節。
內網代理的實現
- 首先添加一種模式
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MappingConfig {
/// 其它欄位....
// 添加模塊proxy
pub mode: String,
}
- 添加內網代理監聽埠
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
/// 其它欄位....
pub(crate) map_http_bind: Option<SocketAddr>,
pub(crate) map_https_bind: Option<SocketAddr>,
pub(crate) map_tcp_bind: Option<SocketAddr>,
// 新加代理介面監聽欄位
pub(crate) map_proxy_bind: Option<SocketAddr>,
-
}
目前埠做唯一綁定,後續可根據配置動態響應相應的數據。
- 做映射
由於代理和tcp類似,服務端均不做任務處理,只需將數據完全轉發給客戶端處理即可
pub async fn server_new_prxoy(&mut self, stream: TcpStream) -> ProxyResult<()> {
let trans = TransTcp::new(
self.sender(),
self.sender_work(),
self.calc_next_id(),
self.mappings.clone(),
);
tokio::spawn(async move {
if let Err(e) = trans.process(stream, "proxy").await {
log::warn!("內網穿透:轉發Proxy轉發時發生錯誤:{:?}", e);
}
});
return Ok(());
}
- 客戶端處理
客戶端將映射流轉化成VirtualStream
,把它當成一個虛擬流,然後邏輯均用代理的來處理
let (virtual_sender, virtual_receiver) = channel::<ProtFrame>(10);
map.insert(p.sock_map(), virtual_sender);
if mapping.as_ref().unwrap().is_proxy() {
let stream = VirtualStream::new(
p.sock_map(),
sender.clone(),
virtual_receiver,
);
let (flag, username, password, udp_bind) = (
option.flag,
option.username.clone(),
option.password.clone(),
option.udp_bind.clone(),
);
tokio::spawn(async move {
// 處理代理的能力
let _ = WMCore::deal_proxy(
stream, flag, username, password, udp_bind,
)
.await;
});
}
VirtualStream
是一個虛擬出一個流連接,並實現AsyncRead及AsyncRead,可以和流一樣正常操作,這也是Trait
而不是繼承的好處之一,定義就可以比較簡單:
pub struct VirtualStream
{
// sock綁定的句柄
id: u32,
// 收到數據通過sender發送給中心端
sender: PollSender<ProtFrame>,
// 收到中心端的寫入請求,轉成write
receiver: Receiver<ProtFrame>,
// 讀取的數據緩存,將轉發成ProtFrame
read: BinaryMut,
// 寫的數據緩存,直接寫入到stream下,從ProtFrame轉化而來
write: BinaryMut,
}
- 設計
ProxyServer
類
統一的代理服務類,剝離相關代碼,使代碼更清晰
/// 代理伺服器類, 提供代理服務
pub struct ProxyServer {
flag: Flag,
username: Option<String>,
password: Option<String>,
udp_bind: Option<IpAddr>,
headers: Vec<ConfigHeader>,
}
- 代理
HTTP
頭信息的重寫
在HTTP
類中添加相關代碼以支持頭信息重寫
impl Operate {
fn deal_request(&self, req: &mut RecvRequest) -> ProtResult<()> {
if let Some(headers) = &self.headers {
// 覆寫Request的頭文件信息
Helper::rewrite_request(req, headers);
}
Ok(())
}
fn deal_response(&self, res: &mut RecvResponse) -> ProtResult<()> {
if let Some(headers) = &self.headers {
// 覆寫Request的頭文件信息
Helper::rewrite_response(res, headers);
}
Ok(())
}
}
內網代理流程圖:
flowchart TD A[外部客戶端] -->|以代理方式訪問|B B[服務端監聽Proxy] <-->|數據轉發| C[中心服務端CenterServer] C <-->|協議傳輸|D[中心客戶端CenterClient] D <-->|虛擬數據流|E[虛擬客戶端] E <-->|處理數據|F[內網代理服務,可完全訪問內網]這樣子我們就以代理的方式擁有了所有的內網HTTP相關服務的訪問許可權。可以簡化我們網路的結構。
自動化測試
內網穿透的自動化測試在 tests/mapping
將自動構建內網客戶端服務,外網服務端服務做測試,以下部分代碼節選:
#[tokio::test]
async fn run_test() {
let local_server_addr = run_server().await.unwrap();
let addr = "127.0.0.1:0".parse().unwrap();
let proxy = ProxyConfig::builder()
.bind_addr(addr)
.map_http_bind(Some(addr))
.map_https_bind(Some(addr))
.map_tcp_bind(Some(addr))
.map_proxy_bind(Some(addr))
.center(true)
.mode("server".to_string())
.into_value()
.unwrap();
let (server_addr, http_addr, https_addr, tcp_addr, proxy_addr, _sender) =
run_mapping_server(proxy).await.unwrap();
let mut mapping = MappingConfig::new(
"test".to_string(),
"http".to_string(),
"soft.wm-proxy.com".to_string(),
vec![],
);
mapping.local_addr = Some(local_server_addr);
let mut mapping_tcp = MappingConfig::new(
"tcp".to_string(),
"tcp".to_string(),
"soft.wm-proxy.com".to_string(),
vec![],
);
mapping_tcp.local_addr = Some(local_server_addr);
let mut mapping_proxy = MappingConfig::new(
"proxy".to_string(),
"proxy".to_string(),
"soft.wm-proxy.com1".to_string(),
vec![
ConfigHeader::new(wmproxy::HeaderOper::Add, false, "from_proxy".to_string(), "mapping".to_string())
],
);
mapping_proxy.local_addr = Some(local_server_addr);
let proxy = ProxyConfig::builder()
.bind_addr(addr)
.server(Some(server_addr))
.center(true)
.mode("client".to_string())
.mapping(mapping)
.mapping(mapping_tcp)
.mapping(mapping_proxy)
.into_value()
.unwrap();
let _client_sender = run_mapping_client(proxy).await.unwrap();
fn do_build_req(url: &str, method: &str, body: &Vec<u8>) -> Request<Body> {
let body = BinaryMut::from(body.clone());
Request::builder()
.method(method)
.url(&*url)
.body(Body::new_binary(body))
.unwrap()
}
{
let url = &*format!("http://{}/", local_server_addr);
let client = Client::builder()
// .http2(false)
.http2_only(true)
.add_proxy(&*format!("http://{}", proxy_addr.unwrap())).unwrap()
.connect(&*url)
.await
.unwrap();
let mut res = client
.send_now(do_build_req(url, "GET", &vec![]))
.await
.unwrap();
let mut result = BinaryMut::new();
res.body_mut().read_all(&mut result).await;
// 測試頭信息來確認是否來源於代理
assert_eq!(res.headers().get_value(&"from_proxy"), &"mapping");
assert_eq!(result.remaining(), HELLO_WORLD.as_bytes().len());
assert_eq!(result.as_slice(), HELLO_WORLD.as_bytes());
assert_eq!(res.version(), Version::Http2);
}
}
小結
內網代理可以實現不想暴露太多信息給外部,但是又能提供內部的完整信息支持,相當於建立了一條可用的HTTP通道。可以在有這方面需求的人優化網路結構。
點擊 [關註],[在看],[點贊] 是對作者最大的支持