wmproxy
已用Rust
实现http/https
代理, socks5
代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket
代理等,会将实现过程分享出来,感兴趣的可以一起造个*
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
通过简单配置方便用户快速使用tcp转websocket及websocket转tcp,也可支持http升级到websocket协议。
因为负载均衡的不确定性,在未读取数据前,未能确定当前的处理逻辑
reverse/http.rs
let timeout = oper.servers[0].comm.build_client_timeout(); let mut server = Server::builder() .addr(addr) .timeout_layer(timeout) .stream(inbound); // 设置HTTP回调 server.set_callback_http(Box::new(Operate { inner: oper })); // 设置websocket回调,客户端有可能升级到websocket协议 server.set_callback_ws(Box::new(ServerWsOperate::new(servers))); if let Err(e) = server.incoming().await { if server.get_req_num() == 0 { log::info!("反向代理:未处理任何请求时发生错误:{:?}", e); } else { if !e.is_io() { log::info!("反向代理:处理信息时发生错误:{:?}", e); } } }
在ServerWsOperate中定义了服务的内部信息,及向远程websocket发送的sender,以做绑定
pub struct ServerWsOperate { inner: InnerWsOper, sender: Option<Sender<OwnedMessage>>, }
在on_open的时候建立和远程websocket的双向绑定:
#[async_trait] impl WsTrait for ServerWsOperate { /// 握手完成后之后的回调,服务端返回了Response之后就认为握手成功 async fn on_open(&mut self, shake: WsHandshake) -> ProtResult<Option<WsOption>> { if shake.request.is_none() { return Err(ProtError::Extension("miss request")); } let mut option = WsOption::new(); if let Some(location) = ReverseHelper::get_location_by_req(&self.inner.servers, shake.request.as_ref().unwrap()) { if !location.is_ws { return Err(ProtError::Extension("Not Support Ws")); } if let Ok((url, domain)) = location.get_reverse_url() { println!("connect url = {}, domain = {:?}", url, domain); let mut client = Client::builder() .url(url)? .connect_with_domain(&domain) .await?; let (serv_sender, serv_receiver) = channel::<OwnedMessage>(10); let (cli_sender, cli_receiver) = channel::<OwnedMessage>(10); option.set_receiver(serv_receiver); self.sender = Some(cli_sender); client.set_callback_ws(Box::new(ClientWsOperate { sender: Some(serv_sender), receiver: Some(cli_receiver), })); tokio::spawn(async move { if let Err(e) = client .wait_ws_operate_with_req(shake.request.unwrap()) .await { println!("error = {:?}", e); }; println!("client close!!!!!!!!!!"); }); } return Ok(Some(option)); } return Err(ProtError::Extension("miss match")); } }
在此地方,我们是用负载均衡来做配置location.get_reverse_url()
,远程端的域名和ip要映射成本地的ip,所以这边可能要读取负载的ip而不是从dns中解析ip。
获取正确的连接域名和ip地址。
pub fn get_reverse_url(&self) -> ProtResult<(Url, String)> { if let Some(addr) = self.get_upstream_addr() { if let Some(r) = &self.comm.proxy_url { let mut url = r.clone(); let domain = url.domain.clone().unwrap_or(String::new()); url.domain = Some(format!("{}", addr.ip())); url.port = Some(addr.port()); Ok((url, domain)) } else { let url = Url::parse(format!("http://{}/", addr).into_bytes())?; let domain = format!("{}", addr.ip()); Ok((url, domain)) } } else { Err(ProtError::Extension("error")) } }
此处的处理方式与nginx不同,nginx是将所有升级请求的头信息全部删除,再根据配置的过行补充
Upgrade: websocket Connection: Upgrade
所以在nginx中配置支持websocket通常如下配置,也就是通常配置的时候需要查找资料进行copy
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
在wmproxy中并不会对客户端的请求做特殊的处理,也就是发了升级远程的websocket服务器接受了升级,我们当前协议就会升级。所以我们在配置中加入了一个字段is_ws
,如果升级成websocket但是并不支持websocket的时候直接进行报错,告知不支持协议
[[http.server.location]] rule = "/ws" is_ws = true reverse_proxy = "http://ws"
如此在该问该url的时候就可以转websocket了,比如websocat ws://127.0.0.1/ws
利用上章讲述的StreamToWs
,且利用stream
流的转发,将转发类型配置tcp2ws
非安全的ws,或者tcp2wss
带tls的wss,实现源码在reverse/stream.rs
[[stream.server]] bind_addr = "0.0.0.0:85" proxy_url = "ws://127.0.0.1:8081/" bind_mode = "tcp2ws"
这样子,我们就可以将本地监听的85端口的地址,流量转发成8081的websocket远程地址。如果远程端验证域名可以配置上相应的domain = "wmproxy.com"
if s.bind_mode == "tcp2ws" { let mut stream_to_ws = StreamToWs::new(inbound, format!("ws://{}", addr))?; if domain.is_some() { stream_to_ws.set_domain(domain.unwrap()); } let _ = stream_to_ws.copy_bidirectional().await; }
如此我们就可以轻松的获取tcp流量转websocket的能力。
利用上章讲述的WsToStream
,且利用stream
流的转发,将转发类型配置ws2tcp
转发为tcp,实现源码在reverse/stream.rs
[[stream.server]] bind_addr = "0.0.0.0:86" up_name = "ws1" proxy_url = "tcp://127.0.0.1:8082" bind_mode = "ws2tcp"
这样子,我们就可以将本地监听的86端口websocket的地址,流量转发成8082的tcp远程地址。如果远程端验证域名可以配置上相应的domain = "wmproxy.com"
if s.bind_mode == "ws2tcp" { let mut ws_to_stream = WsToStream::new(inbound, addr)?; if domain.is_some() { ws_to_stream.set_domain(domain.unwrap()); } let _ = ws_to_stream.copy_bidirectional().await; }
如此我们就可以轻松的获取websocket流量转tcp的能力。
利用wmproxy可以轻松的转化tcp到websocket的流量互转,配置简单。可以利用现成的websocket高速通道辅助我们的tcp程序获取更稳定的流量通道。
点击 [关注],[在看],[点赞] 是对作者最大的支持