首页 > 其他分享 >36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现

36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现

时间:2023-12-22 09:01:35浏览次数:35  
标签:addr bind 代理 36 let proxy 中内网 mapping string

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. 首先添加一种模式
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MappingConfig {
    /// 其它字段....
    // 添加模块proxy
    pub mode: String,
}
  1. 添加内网代理监听端口
#[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>,
    -
}

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

  1. 做映射

由于代理和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(());
}
  1. 客户端处理
    客户端将映射流转化成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,
}
  1. 设计ProxyServer

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

/// 代理服务器类, 提供代理服务
pub struct ProxyServer {
    flag: Flag,
    username: Option<String>,
    password: Option<String>,
    udp_bind: Option<IpAddr>,
    headers: Vec<ConfigHeader>,
}
  1. 代理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通道。可以在有这方面需求的人优化网络结构。

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

标签:addr,bind,代理,36,let,proxy,中内网,mapping,string
From: https://www.cnblogs.com/wmproxy/p/wmproxy36.html

相关文章

  • 360沃通亮相2023年深圳市卫生健康信息学术会议,展示医疗行业商密应用方案
    2023年12月15日-16日,深圳市卫生健康信息协会举办主题为“智慧健康引领网络安全护航”的2023年深圳市卫生健康信息学术会议暨“京沪宁深连线”深圳专场,360沃通作为深圳密码领域代表性企业受邀参会,与现场知名专家学者、卫生健康信息化业内同仁、卫生健康信息产品厂商展开深入交流,并......
  • MT6785/MT6359/MT6360/MT6186/MT6631 UFS_LPDDR4X原理图
    联发科MT6785核心板是一款高度集成的基带平台,集成了蓝牙、FM、WLAN和GPS模块,旨在支持LTE/LTE-A和C2K智能手机应用。这款芯片采用了两个最高频率可达到2.05GHz的ARM®Cortex-A76核心和六个最高频率可达到2.0GHz的ARM®Cortex-A55核心,搭载ArmMali-G76MC4GPU运行速度可提升至......
  • 2002 - Can't connect to server on '54.xxx.xxx.xxx' (36)
    远程连接mysql数据库的时候显示Can'tconnecttoMySQLserver(10060)如下图所示可以从以下几个方面入手,找出错误的原因:1.网络问题网络不通时会导致这个问题检查下是不是能ping通2.mysql账户设置mysql账户是否不允许远程连接--mysql-uroot-p--showdataba......
  • EDA365 Skill找不到Cadence安装路径的原因与解决办法
    软件版本Cadence17.4参考来源:https://blog.csdn.net/weixin_42837669/article/details/119832994EDA365Skill安装,无法检测到Cadence安装路径,请确认Cadence软件是否已经安装.以下未尝试 ......
  • [LeetCode] 1362. Closest Divisors 最接近的因数
    Givenaninteger num,findtheclosesttwointegersinabsolutedifferencewhoseproductequals num+1 or num+2.Returnthetwointegersinanyorder.Example1:Input:num=8Output:[3,3]Explanation:Fornum+1=9,theclosestdivisorsare3&......
  • 短效动态http代理ip不限量套餐推荐
    短效动态http代理ip不限量套餐推荐,关于这个问题,我们首先要了解,什么是短效ip代理,什么是短效ip:短效ip又称动态ip,一个ip地址在一定的时候内会失效变更为新的ip地址,目前在市面上有二种表现形式:1,家庭宽带的光猫重启,ip会改变(日常使用,一般无感)2,通过拨号VPS服务器搭建的短效http代理,使用一......
  • JDK动态代理
    接口类:publicinterfaceHelloService{StringsayHello(Stringname);}实现类:publicclassHelloServiceImplimplementsHelloService{@OverridepublicStringsayHello(Stringname){return"你好,"+name;}}客户端client:publicstati......
  • JDK动态代理
    接口类:publicinterfaceHelloService{StringsayHello(Stringname);}实现类:publicclassHelloServiceImplimplementsHelloService{@OverridepublicStringsayHello(Stringname){return"你好,"+name;}}代理工具类:importjava.lang.......
  • 设计模式—代理模式
    介绍代码接口创建接口ImagepublicinterfaceImage{voiddisplay();}实体类创建实体类RealImagepublicclassRealImageimplementsImage{privateStringfileName;publicRealImage(StringfileName){this.fileName=fileName;......
  • C++ Qt开发:QItemDelegate 自定义代理组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍QStyledItemDelegate自定义代理组件的常用方法及灵活运用。在Qt中,QStyledItemDelegate类是用于......