经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Rust » 查看文章
36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现
来源:cnblogs  作者:问蒙服务框架  时间:2023/12/22 16:21:52  对本文有异议

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篇,有相关的内网穿透的细节。

内网代理的实现

  1. 首先添加一种模式
  1. #[serde_as]
  2. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
  3. pub struct MappingConfig {
  4. /// 其它字段....
  5. // 添加模块proxy
  6. pub mode: String,
  7. }
  1. 添加内网代理监听端口
  1. #[serde_as]
  2. #[derive(Debug, Clone, Serialize, Deserialize)]
  3. pub struct ProxyConfig {
  4. /// 其它字段....
  5. pub(crate) map_http_bind: Option<SocketAddr>,
  6. pub(crate) map_https_bind: Option<SocketAddr>,
  7. pub(crate) map_tcp_bind: Option<SocketAddr>,
  8. // 新加代理接口监听字段
  9. pub(crate) map_proxy_bind: Option<SocketAddr>,
  10. -
  11. }

目前端口做唯一绑定,后续可根据配置动态响应相应的数据。

  1. 做映射

由于代理和tcp类似,服务端均不做任务处理,只需将数据完全转发给客户端处理即可

  1. pub async fn server_new_prxoy(&mut self, stream: TcpStream) -> ProxyResult<()> {
  2. let trans = TransTcp::new(
  3. self.sender(),
  4. self.sender_work(),
  5. self.calc_next_id(),
  6. self.mappings.clone(),
  7. );
  8. tokio::spawn(async move {
  9. if let Err(e) = trans.process(stream, "proxy").await {
  10. log::warn!("内网穿透:转发Proxy转发时发生错误:{:?}", e);
  11. }
  12. });
  13. return Ok(());
  14. }
  1. 客户端处理
    客户端将映射流转化成VirtualStream,把它当成一个虚拟流,然后逻辑均用代理的来处理
  1. let (virtual_sender, virtual_receiver) = channel::<ProtFrame>(10);
  2. map.insert(p.sock_map(), virtual_sender);
  3. if mapping.as_ref().unwrap().is_proxy() {
  4. let stream = VirtualStream::new(
  5. p.sock_map(),
  6. sender.clone(),
  7. virtual_receiver,
  8. );
  9. let (flag, username, password, udp_bind) = (
  10. option.flag,
  11. option.username.clone(),
  12. option.password.clone(),
  13. option.udp_bind.clone(),
  14. );
  15. tokio::spawn(async move {
  16. // 处理代理的能力
  17. let _ = WMCore::deal_proxy(
  18. stream, flag, username, password, udp_bind,
  19. )
  20. .await;
  21. });
  22. }

VirtualStream是一个虚拟出一个流连接,并实现AsyncRead及AsyncRead,可以和流一样正常操作,这也是Trait而不是继承的好处之一,定义就可以比较简单:

  1. pub struct VirtualStream
  2. {
  3. // sock绑定的句柄
  4. id: u32,
  5. // 收到数据通过sender发送给中心端
  6. sender: PollSender<ProtFrame>,
  7. // 收到中心端的写入请求,转成write
  8. receiver: Receiver<ProtFrame>,
  9. // 读取的数据缓存,将转发成ProtFrame
  10. read: BinaryMut,
  11. // 写的数据缓存,直接写入到stream下,从ProtFrame转化而来
  12. write: BinaryMut,
  13. }
  1. 设计ProxyServer

统一的代理服务类,剥离相关代码,使代码更清晰

  1. /// 代理服务器类, 提供代理服务
  2. pub struct ProxyServer {
  3. flag: Flag,
  4. username: Option<String>,
  5. password: Option<String>,
  6. udp_bind: Option<IpAddr>,
  7. headers: Vec<ConfigHeader>,
  8. }
  1. 代理HTTP头信息的重写
    HTTP中添加相关代码以支持头信息重写
  1. impl Operate {
  2. fn deal_request(&self, req: &mut RecvRequest) -> ProtResult<()> {
  3. if let Some(headers) = &self.headers {
  4. // 复写Request的头文件信息
  5. Helper::rewrite_request(req, headers);
  6. }
  7. Ok(())
  8. }
  9. fn deal_response(&self, res: &mut RecvResponse) -> ProtResult<()> {
  10. if let Some(headers) = &self.headers {
  11. // 复写Request的头文件信息
  12. Helper::rewrite_response(res, headers);
  13. }
  14. Ok(())
  15. }
  16. }

内网代理流程图:

flowchart TD A[外部客户端] -->|以代理方式访问|B B[服务端监听Proxy] <-->|数据转发| C[中心服务端CenterServer] C <-->|协议传输|D[中心客户端CenterClient] D <-->|虚拟数据流|E[虚拟客户端] E <-->|处理数据|F[内网代理服务,可完全访问内网]

这样子我们就以代理的方式拥有了所有的内网HTTP相关服务的访问权限。可以简化我们网络的结构。

自动化测试

内网穿透的自动化测试在 tests/mapping
将自动构建内网客户端服务,外网服务端服务做测试,以下部分代码节选:

  1. #[tokio::test]
  2. async fn run_test() {
  3. let local_server_addr = run_server().await.unwrap();
  4. let addr = "127.0.0.1:0".parse().unwrap();
  5. let proxy = ProxyConfig::builder()
  6. .bind_addr(addr)
  7. .map_http_bind(Some(addr))
  8. .map_https_bind(Some(addr))
  9. .map_tcp_bind(Some(addr))
  10. .map_proxy_bind(Some(addr))
  11. .center(true)
  12. .mode("server".to_string())
  13. .into_value()
  14. .unwrap();
  15. let (server_addr, http_addr, https_addr, tcp_addr, proxy_addr, _sender) =
  16. run_mapping_server(proxy).await.unwrap();
  17. let mut mapping = MappingConfig::new(
  18. "test".to_string(),
  19. "http".to_string(),
  20. "soft.wm-proxy.com".to_string(),
  21. vec![],
  22. );
  23. mapping.local_addr = Some(local_server_addr);
  24. let mut mapping_tcp = MappingConfig::new(
  25. "tcp".to_string(),
  26. "tcp".to_string(),
  27. "soft.wm-proxy.com".to_string(),
  28. vec![],
  29. );
  30. mapping_tcp.local_addr = Some(local_server_addr);
  31. let mut mapping_proxy = MappingConfig::new(
  32. "proxy".to_string(),
  33. "proxy".to_string(),
  34. "soft.wm-proxy.com1".to_string(),
  35. vec![
  36. ConfigHeader::new(wmproxy::HeaderOper::Add, false, "from_proxy".to_string(), "mapping".to_string())
  37. ],
  38. );
  39. mapping_proxy.local_addr = Some(local_server_addr);
  40. let proxy = ProxyConfig::builder()
  41. .bind_addr(addr)
  42. .server(Some(server_addr))
  43. .center(true)
  44. .mode("client".to_string())
  45. .mapping(mapping)
  46. .mapping(mapping_tcp)
  47. .mapping(mapping_proxy)
  48. .into_value()
  49. .unwrap();
  50. let _client_sender = run_mapping_client(proxy).await.unwrap();
  51. fn do_build_req(url: &str, method: &str, body: &Vec<u8>) -> Request<Body> {
  52. let body = BinaryMut::from(body.clone());
  53. Request::builder()
  54. .method(method)
  55. .url(&*url)
  56. .body(Body::new_binary(body))
  57. .unwrap()
  58. }
  59. {
  60. let url = &*format!("http://{}/", local_server_addr);
  61. let client = Client::builder()
  62. // .http2(false)
  63. .http2_only(true)
  64. .add_proxy(&*format!("http://{}", proxy_addr.unwrap())).unwrap()
  65. .connect(&*url)
  66. .await
  67. .unwrap();
  68. let mut res = client
  69. .send_now(do_build_req(url, "GET", &vec![]))
  70. .await
  71. .unwrap();
  72. let mut result = BinaryMut::new();
  73. res.body_mut().read_all(&mut result).await;
  74. // 测试头信息来确认是否来源于代理
  75. assert_eq!(res.headers().get_value(&"from_proxy"), &"mapping");
  76. assert_eq!(result.remaining(), HELLO_WORLD.as_bytes().len());
  77. assert_eq!(result.as_slice(), HELLO_WORLD.as_bytes());
  78. assert_eq!(res.version(), Version::Http2);
  79. }
  80. }

小结

内网代理可以实现不想暴露太多信息给外部,但是又能提供内部的完整信息支持,相当于建立了一条可用的HTTP通道。可以在有这方面需求的人优化网络结构。

点击 [关注][在看][点赞] 是对作者最大的支持

原文链接:https://www.cnblogs.com/wmproxy/p/wmproxy36.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号