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,
- }
复制代码统一的代理服务类,剥离相关代码,使代码更清晰
- /// 代理服务器类, 提供代理服务
- 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通道。可以在有这方面需求的人优化网络结构。
点击 [关注],[在看],[点赞] 是对作者最大的支持
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |