36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现
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(())
}
}
内网代理流程图:
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通道。可以在有这方面需求的人优化网络结构。
点击 [关注],[在看],[点赞] 是对作者最大的支持
36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现的更多相关文章
- Docker系列-(3) Docker-compose使用与负载均衡
上一篇文章介绍了docker镜像的制作与发布,本文主要介绍实际docker工程部署中经常用到的docker-compose工具,以及docker的网络配置和负载均衡. Docker-compose介绍 ...
- 死磕nginx系列--使用upsync模块实现负载均衡
问题描述 nginx reload是有一定损耗的,如果你使用的是长连接的话,那么当reload nginx时长连接所有的worker进程会进行优雅退出,并当该worker进程上的所有连接都释放时,进程 ...
- hbase源码系列(一)Balancer 负载均衡
看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了. ...
- office web apps安装部署,配置https,负载均衡(七)配置过程中遇到的问题详细解答
该篇文章,是这个系列文章的最后一篇文章,该篇文章将详细解答owa在安装过程中常见的问题. 如果您没有搭建好office web apps,您可以查看前面的一系列文章,查看具体步骤: office we ...
- 工具系列 | 如何在阿里云负载均衡上启用WS/WSS支持
官方文档:https://help.aliyun.com/document_detail/63421.html?spm=5176.10695662.1996646101.searchclickresu ...
- 最简单的 nginx 负载均衡,只能演示,企业中最好不用
修改nginx.conf 配置,重启nginx即可 upstream 包名{ ip_hash; #使用此功能,权重和备份都不能使用!一台机器永远只连同一台机子 server IP:端口 weight= ...
- 干货 | 亿级Web系统负载均衡几种实现方式
一个执着于技术的公众号 负载均衡(Load Balance)是集群技术(Cluster)的一种应用技术.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web ...
- 干货 | Nginx负载均衡原理及配置实例
一个执着于技术的公众号 Nginx系列导读 给小白的 Nginx 10分钟入门指南 Nginx编译安装及常用命令 完全卸载nginx的详细步骤 Nginx 配置文件详解 理解正向代理与反向代理的区别 ...
- SpringCloud系列五:Ribbon 负载均衡(Ribbon 基本使用、Ribbon 负载均衡、自定义 Ribbon 配置、禁用 Eureka 实现 Ribbon 调用)
1.概念:Ribbon 负载均衡 2.具体内容 现在所有的服务已经通过了 Eureka 进行了注册,那么使用 Eureka 注册的目的是希望所有的服务都统一归属到 Eureka 之中进 行处理,但是现 ...
- 【SpringCloud微服务实战学习系列】客户端负载均衡Spring Cloud Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.通过Spring Cloud的封装,可以让我们轻松地将面向服务的RES模板 ...
随机推荐
- API接口设计规范,看这篇就足以了
优秀的设计是产品变得卓越的原因.设计API意味着提供有效的接口,可以帮助API使用者更好地了解.使用和集成,同时帮助人们有效地维护它.每个产品都需要使用手册,API也不例外. 在API领域,可以将 ...
- 如何通过关键词搜索API接口获取1688的商品详情
如果你是一位电商运营者或者是想要进行1688平台产品调研的人员,你可能需要借助API接口来获取你所需要的信息.在这篇文章中,我们将会讨论如何通过关键词搜索API接口获取1688的商品详情. 第一步:获 ...
- Python从0到1丨详解图像锐化的Sobel、Laplacian算子
本文分享自华为云社区<[Python从零到壹] 五十八.图像增强及运算篇之图像锐化Sobel.Laplacian算子实现边缘检测>,作者: eastmount . 一.Sobel算子 So ...
- KMP字符串对比算法及next数组计算
(注:该贴主要运用python实现该算法) 先谈谈KMP算法吧.KMP算法的全称是Knuth-Morris-Pratt 算法,它是用来进行字符串查找,即在某个主字符串里面找到某个特定子字符串.但是好像 ...
- Binary String Copying
Smiling & Weeping ----第一次见你的时候, 在我的心里已经炸成了烟花, 需要用一生来打扫灰炉. 题目链接:Problem - C - Codeforces 题目大意不难,就 ...
- Teamcenter RAC 开发之《PlaceHolder》
背景 做个swing表单,有时候想实现一些网页input标签的placeHolder提示,可能本人写vue or html写多,对某些细节有强迫症,所以找小下资料 实现方法(Swingx) 看源码
- 一文带你实现云上部署轻量化定制表单Docker
本文分享自华为云社区 <[华为云云耀云服务器L实例评测|云原生]自定制轻量化表单Docker快速部署云耀云服务器 | 玩转华为云>,作者:计算机魔术师. 华为云的云耀云服务器L实例备受推崇 ...
- options has an unknown property ‘contentBase‘
options has an unknown property 'contentBase' 踩坑新版webpack-dev-serve 新版的contentBase取消了替代属性是static
- 开源项目 | 一款基于NodeJs+Vue3的强大的在线设计图片工具
一.项目概述 一款漂亮且功能强大的在线海报图片设计器,仿稿定设计.适用于海报图片生成.电商分享图.文章长图.视频/公众号封面等多种场景. 二. 技术特性 丝滑的操作体验,丰富的交互细节,基础功能完善 ...
- Building Bridges 题解
Building Bridges 题目大意 连接两根柱子 \(i,j\) 的代价是 \((h_i-h_j)^2+\sum\limits_{k=j+1}^{i-1}w_k\),连接具有传递性,求将 \( ...