46从零开始用Rust编写nginx,数据还能这么传,多层代理(IP多级代理)搭建

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

设计目标

通过多层代理的代理结构,构建出属于自己的网络通道。

多层代理能做什么

多层代理(也称为IP多级代理)是一种网络代理技术

  • 它通过多个代理服务器来接收和发送数据包,从而隐藏真实IP地址。每个代理服务器都可以处理一个或多个网络请求,通过这种方式,可以在防火墙后面传输数据,同时隐藏真实IP地址和用户身份。这种技术通常用于企业网络和互联网上的安全传输。

  • 当前多层代理的工作原理是在应用层上进行代理传输,从而将原始IP地址和用户身份隐藏起来。在发送数据时,发送方的IP地址会被伪装成目标IP地址的某个IP地址,而接收方的IP地址则会被隐藏在多个IP地址中。这种层层转发网络流量的方式,使得最终目标服务器无法直接获取到请求的真实IP地址,从而增加了网络的安全性和匿名性。

但是请注意,使用多层代理也可能会导致网络连接速度变慢,因为数据需要通过多个代理服务器进行传输。同时如果代理服务器被攻击或出现故障,也可能会影响网络的稳定性和可靠性。因此在选择是否使用多层代理时,需要综合考虑其优缺点,并根据实际需求做出决策。

如何部署

通常了解一个程序如何运行最快的方式除了官方文档,另一种就是查找当前程序的help,通常wmproxy --help或者wmproxy -h就可以查询到帮助信息。此时我们是子命令代理,通过wmproxy proxy -h,可以查询控制消息

Usage: wmproxy.exe proxy [-s=ARG] [-b=ARG] [-c=ARG] [--flag=ARG] [-S=ARG] [--user=ARG] [--pass=ARG] [
--udp-bind=ARG] [--map-http-bind=ARG] [--map-https-bind=ARG] [--map-tcp-bind=ARG] [--map-proxy-bind=ARG
] [--map-cert=ARG] [--map-key=ARG] [--ts] [--tc] [--two-way-tls] [--domain=ARG] [--cert=ARG] [--key=ARG
] [--mappings=ARG]... [--control=ARG] [--disable-stdout] [--disable-control] [-v] [--default-level=ARG
]

代理类, 一个代理类启动一种类型的代理
    -s, --server-id=ARG       代理id
                              [default: 0]
    -b, --bind=ARG            代理绑定端口地址
    -c, --center-addr=ARG     中心代理绑定端口地址
        --flag=ARG            代理种类, 如http https socks5
    -S, --server=ARG          连接代理服务端地址
        --user=ARG            用于socks验证及中心服务器验证
        --pass=ARG            用于socks验证及中心服务器验证
        --udp-bind=ARG        udp的绑定地址
        --map-http-bind=ARG   内网http的映射地址
        --map-https-bind=ARG  内网https的映射地址
        --map-tcp-bind=ARG    内网tcp的映射地址
        --map-proxy-bind=ARG  内网代理的映射地址
        --map-cert=ARG        内网映射的证书cert
        --map-key=ARG         内网映射的证书key
        --ts                  连接服务端是否启用tls
        --tc                  接收客户端是否启用tls
        --two-way-tls         双向认证是否启用
        --domain=ARG          tls证书所用的域名
        --cert=ARG            公开的证书公钥文件
        --key=ARG             隐私的证书私钥文件
        --mappings=ARG

此时我们绑定端口分为两种

    -b, --bind=ARG            代理绑定端口地址
    -c, --center-addr=ARG     中心代理绑定端口地址
  • 其中-b是绑定来自于第三方代理客户端发起(http/https/socks)代理请求的信息
  • 其中-c是绑定来自于客户端与服务端建立的代理请求信息。是服务器内部间构建的信息通道。
  • 其中-S或者--server表示该程序需连接到代理服务端,将数据请求发送到服务器处理。
  • 其中--ts表示连接服务端时是否启用tls
  • 其中--tc表示接收客户端时是否需要tls,这两个需要正确配对并设置相应的--cert--key

以下流程图,展示数据的处理流程,其中代理B的角色可以是0个也可以是若干个。

sequenceDiagram participant Client as 用户端 participant A as 代理端A<br/>监听-b 8090 participant B as 代理端B<br/>监听"-c 8091" participant C as 代理端C<br/>监听"-c 8092" B->>C: 主动连接127.0.0.1:8092<br/>建立内部通道 A->>B: 主动连接127.0.0.1:8091<br/>建立内部通道 Client->>A: 发起HTTP代理请求 A->>B: 构建内部通讯协议<br/>创建唯一id B->>C: 构建代理A与C中转通道<br/>双向绑定中转数据 C->>C: 解析数据,并处理代理 C->>B: 返回代理数据 B->>A: A与C的中转通道 A->>Client: 解析数据转成代理请求发送
  • 代理C的启动命令:
wmproxy proxy -c :8092
  • 代理B的启动命令:
wmproxy proxy -c :8091 -S 127.0.0.1:8092
  • 代理A的启动命令:
wmproxy proxy -b :8090 -S 127.0.0.1:8091

通过如何我们就构建出了一条内部的代理通道,此时通过curl测试

curl.exe --proxy http://127.0.0.1:8090 https://www.baidu.com -I

可以得到以下正确返回200信息

HTTP/1.1 200 OK
Server: wenmeng
content-length: 0

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 277
Content-Type: text/html
Date: Fri, 26 Jan 2024 02:27:04 GMT
Etag: "575e1f71-115"
Last-Modified: Mon, 13 Jun 2016 02:50:25 GMT
Pragma: no-cache
Server: bfe/1.0.8.18

源码剥析

关于server_id

因为在构建tcp唯一映射的时候,是通过本地的自增id来进行处理。

pub fn calc_next_id(&mut self) -> u64 {
    let id = self.next_id;
    self.next_id = self.next_id.wrapping_add(2);
    Helper::calc_sock_map(self.option.server_id, id)
}

即当出口处理的服务器如果前端有多条客户端的时候存在冲突的可能的。就比如id均为2,那么服务端将无法正确处理是来自哪个客户端的数据,也无法进行相应的转发。所以此处我们引入server_id的概念,通过server_id与id进行组合运算,得出系统内的唯一id,保证数据没有冲突。

let mut map = HashMap::<u64, Sender<ProtFrame>>::new();
关于中心客户端
  • 源码:center_client
  • 类名:CenterClient
  • 功能:它负责将客户端的数据转化成服务器内部的通讯,并将内部的通讯转化成客户端的数据。
  • 构建:当仅它有-b监听第三方客户端的时候构建。
  • 核心函数:
async fn inner_serve<T>(
    option: &ProxyConfig,
    stream: T,
    sender: &mut Sender<ProtFrame>,
    receiver_work: &mut Receiver<(ProtCreate, Sender<ProtFrame>)>,
    receiver: &mut Receiver<ProtFrame>,
    mappings: &mut Vec<MappingConfig>,
) -> ProxyResult<()>
关于中心服务端
  • 源码:center_server
  • 类名:CenterServer
  • 功能:它负责将服务器内部的数据进行解析,并发起http代理的请求,此处他为出口
  • 构建:当仅他的目标上级服务器不存在时且-c参数被设置进行构建。即-S没有设置
  • 核心函数:
pub async fn inner_serve<T>(
    stream: T,
    option: ProxyConfig,
    sender: Sender<ProtFrame>,
    mut receiver: Receiver<ProtFrame>,
    mut receiver_work: Receiver<(ProtCreate, Sender<ProtFrame>)>,
    mappings: Arc<RwLock<Vec<MappingConfig>>>,
) -> ProxyResult<()>
关于中心转发服
  • 源码:center_trans
  • 类名:CenterTrans
  • 功能:它负责将来自代理客户端的请求转发给代理服务端
  • 构建:当且仅当-c被设置且-S被设置,即监听中心客户端又连接中心服务端,充当转发功能,由于不存在任何解析,性能高。
  • 核心函数:
pub async fn serve<T>(&mut self, mut stream: T) -> ProxyResult<()>
where
    T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
    let mut server = if self.tls_client.is_some() {
        let connector = TlsConnector::from(self.tls_client.clone().unwrap());
        let stream = HealthCheck::connect(&self.server).await?;
        // 这里的域名只为认证设置
        let domain = rustls::ServerName::try_from(
            &*self
                .domain
                .clone()
                .unwrap_or("soft.wm-proxy.com".to_string()),
        )
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?;

        let outbound = connector.connect(domain, stream).await?;
        MaybeHttpsStream::Https(outbound)
    } else {
        let outbound = HealthCheck::connect(&self.server).await?;
        MaybeHttpsStream::Http(outbound)
    };

    tokio::spawn(async move {
        let _ = tokio::io::copy_bidirectional(&mut stream, &mut server).await;
    });
    Ok(())
}

小结

多层代理可以帮助我们在有限的情况下构建出更稳定的代理通道,可以更好的保护源站,也可以利用该方法给企业构建出稳定的内网通道。也可以在统一出口的情况下保护内网的数据。

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

热门相关:邻居家的妻子   禁止的爱:善良的小姨子   尤金·列维的宅老爹旅行第二季   欲望之屋2:甜美情事   课后记忆