FunProxy - 使用 Rust 构建跨平台全链路测试抓包代理工具

作者:vivo 互联网大前端团队- Song Jiachao

在软件开发过程中,软件测试对于保障软件质量和用户满意度起着关键作用。为最大程度上提升软件品质,我们积极开展全链路测试实践,打造了用Rust语言开发的自研一站式抓包代理工具FunProxy,基于其跨平台、高性能、易于扩展、安全性高等特性,让全链路抓包和环境代理如丝绸般丝滑。

一、背景介绍

1.1 什么是全链路测试

全链路测试就是"验证整个软件系统在不同组件、服务和模块之间协同工作时的性能、功能和稳定性"。在这里我们举一个非常简单的例子。

比如用户在某商城购买商品。

我们是先打开商城,接着浏览商品,加入购物车,然后提交订单,支付,等待收货,最后完成。在整个购买流程中,我们其实并不是一个功能模块就完成全部步骤,而是调用了很多系统模块。

比如:

全链路测试就是验证一个流程中所有涉及的系统,协同工作时的性能、功能和稳定性。

1.2 全链路测试的现状和痛点

通过上面的购物流程我们可以梳理出全链路测试的现状和痛点:

1. 系统数量众多

全链路测试涉及到多个系统、服务和模块的协同工作,系统的多样性增加了测试的复杂性。

2. 各系统环境配置不一致

各个系统的环境配置可能完全不一样,导致各个系统对接相当麻烦。

就拿我们刚刚所举出用户购买商品的例子。涉及到商品系统、购物车系统、订单系统、支付系统、和物流系统。因为历史或者技术方案的原因,他们选择的环境管理方式各不相同。

  • 其中商品和购物车系统可能是由团队 A 负责,他们团队通过 hosts 方式来管理环境。

  • 订单和支付系统,由团队B负责,通过自定义请求头的方式,来进行环境配置。

  • 物流系统由团队 C 负责,通过域名的方式来配置环境。

这只是精简版的购物流程,其真实情况可能远比我们的例子更加复杂。这时候如果想要完整的测试一条购物全流程,环境配置还是相当复杂的。

当前测试流程

为了能够测试所有的流程:

  • 要安装一个抓包软件

  • 安装抓包软件的证书

  • 配置各种抓包的规则

  • 配置抓包的系统代理

  • 配置hosts

  • 配置终端(安装证书、设置手机代理)

这一套流程下来其实需要配置的非常复杂,并且不能共享,每个人在自己的电脑和手机上都需要重新配置一遍。

二、全链路测试的愿景及目标

因为上述背景,我们对vivo的全链路测试提出以下的愿景和目标。

我们的愿景是:让 vivo 的全链路抓包和环境代理,如丝绸般丝滑。

我们的目标是:构建一款跨平台、高性能、易于扩展、安全性高的全链路测试抓包代理工具。

2.1 技术目标详解

跨平台

能够支持几乎所有主流平台,包括但不限于Windows/macOS/Linux/Android/iOS,为什么要兼容这么多平台,因为我们的业务在这些平台上面几乎都有涉及。

高性能

面对超高并发量的接口请求,能够轻松应对,确保系统在高负载下的稳定运行。

易拓展

提供灵活的架构和工具,支持官方和用户根据个人需要对工具进行拓展。

安全性高

通过全面的安全测试,确保产品在各个层级的安全性,包括数据传输、存储和访问控制,防止潜在的安全威胁和数据泄露。

三、技术选型

3.1 为何选择 Rust

我们首选了 Rust 语言作为我们的基础语言,主要原因如下:

  • Rust 是系统级别的通用编程语言,上至web 下至OS 统统能轻松驾驭,并且能够保证内存安全和并发安全;

  • Rust有丰富的工具链,比如rustup,cargo等等,开发体验非常棒;

  • Rust有非常庞大和活跃的社区,社区贡献了非常多好用的各种库。

并且最近使用 Rust 构建应用的公司越来越多,比如我们公司的 Blue OS ,就是行业首个系统框架由 Rust 语言编写的操作系统,这也是我们坚定不移的选择Rust的重要原因之一。

3.2 为何选择 Tauri

选择了 Rust ,我们顺其自然的选择了 Rust 多端开发框架 Tauri。Tauri 是一个基于 Rust 构建的跨平台应用框架。

主要有以下几个优势:

  1. 独立的前端工程:Tauri 支持任何前端框架,所以你不需要改变你的技术栈;

  2. **最小化体积:**使用操作系统本地的网页渲染器,Tauri 应用的体积可以达到最小 600KB;

  3. **跨平台:**同一套代码可以编译运行到几乎所有的OS系统中;

  4. **高安全性:**Tauri 团队的首要目标,推动 Tauri 的首要任务和最大的创新。

  5. **进程间通讯:**用 JavaScript 编写前端,用 Rust 编写应用程序逻辑,并使用 Swift 和 Kotlin 在系统中深入集成;

  6. **由 Rust 驱动:**以性能和安全性为中心,Rust 是下一代应用程序的语言。

使用Tauri框架,让我们不仅能获得Rust的安全性高效性,还能享受Web开发的熟悉感和灵活性。

四、方案介绍

通过刚才的技术方案,我们打造出了FunProxy 一站式代理工具。FunProxy 是一款用 Rust 开发的纯自研的一站式抓包 & 代理工具,全平台支持,天生更安全,天生更流畅!!!

主打一个方便、快捷和无边界。

4.1 优势及亮点

FunProxy 主要的优势及亮点如下:

  • 全功能抓包能力

  • 全平台独立应用支持

  • 云端 hosts

  • 云端 Rules

  • 协同抓包

  • 极致性能

4.1.1 全功能抓包能力

协议

支持HTTP/1.x和HTTP/2协议、WebSocket、TLS 1.1-1.3的加密协议

工具

支持重写、断点、数据对比、模拟超时、全局搜索、筛选、对比

文件

支持导入导出会话、支持Fun格式的文件,HAR格式文件、Charles格式文件

4.1.2 全平台独立应用支持

我们支持几乎所有的主流操作系统,例如Windows/macOS/Linux/Android/iOS/还有网页版本,并且多操作系统可以相互配合,FunProxy 让代理打破边界。

4.1.3 云端 hosts

hosts维护是一个大的痛点,因为各个系统的hosts会经常变化,导致有些同学的hosts因为没有及时同步变更,而出现问题。所以我们提供了云端hosts功能。云端hosts 可以让项目中的hosts由一人设置,人人共享。保证hosts能够实时同步到FunProxy的各个用户。

云端hosts支持静态远程两种hosts。

**静态 hosts:**系统中的hosts文件格式一样,一一对应。

**远程hosts:**可以提供一个链接,我们会远程去拉取这个hosts,在FunProxy运行的时候再去加载这个Hosts。

顺便说一句:FunProxy 所有的hosts都是虚拟hosts,并不会修改系统hosts,保证系统hosts的干净

4.1.4 云端规则

同云端 hosts 一样,规则也可以配置到云端让所有人共享。

所有的规则都有一些公共的配置。比如++规则名称++ 、++匹配模式++ 和++匹配链接++。

我们的匹配模式支持++通配符++ 和++正则表达式++。几乎可以适配所有的链接。

同时我们也提供了检测工具,帮助大家检测链接是否被匹配上。

目前规则主要有三个分类:分别是远程映射,本地映射,和解密。

远程映射在远程映射这个功能上,我们可以配置远程主机的相关信息,这样就可以将需要转发的链接转发到对应的服务器上面。

本地映射在本地映射模式中,支持直接修改本地映射的内容,同时我们提供了强大的代码编辑器功能,帮助大家在编写本地映射内容的时候, 能够有比较好的代码提示。

解密功能这个功能对于很多加密传输网站测试尤为重要。因为每个网站的加解密方式可能不一样,所有我们提供了运行时函数。用户可以自己编写JavaScript或者Python运行函数,动态的根据传入的参数进行对应的加解密计算。

动态函数解析让 FunProxy 几乎能解所有的密文,这样我们在查看接口的时候就非常的方便,不用再去解密工具中查看。

同时也请大家放心,我们的解密工具有着非常高的安全性,保证只有项目管理员授权的人才能查看,大家也不用担心自己的解密方法被别人盗取。

4.1.5 协同抓包

通过 FunProxy 左下角提供的分享功能,可以让当前抓包的所有数据实时同步给所有拥有这个链接的人。所有的操作都能同步到所有端,无需下载,让大家更加方便的进行数据包的共享。

4.1.6 极致性能

我们对比了市场上主流的抓包软件,无论从安装包大小、启动时间、安装空间、使用内存来说,FunProxy 都有非常大的优势。

4.2 技术架构

FunProxy是基于Tauri框架进行跨平台的开发,我们封装了fun-core 和fun-mitm两个核心库。

fun-core主要是所有端的公共能力,比如配置文件,存储等。

fun-mitm主要负责所有端的man in the middle 也就是中间人的功能,提供抓包的核心能力。

tauri-plugin-funproxy因为有些端的功能可能需要调用系统特定的能力,在移动端我们封装了tauri-plugin-funproxy来调用移动端的特定能力,比如VPN。

其他在各个桌面端我们封装了各个系统的核心crates来调用桌面端的特定能力。其他能力则是通过Rust直接调用。

为了提供云端能力,我们使用Go语言搭建了一个服务。包括:项目管理、用户管理、升级管理、健康检查、云端Host、云端规则、云端工具等等功能。服务只是一个增值功能,所有的FunProxy都可以离线运行。

同时为了保证各端APP核心能力的正常,使用了playwright搭建了自动化测试能力。重点测试的功能有:登录、代理、抓包、升级、权限、兼容性、性能等。

最后通过vitepress搭建FunProxy的文档功能。方便用户使用和查看。包括:介绍、使用说明、常见问题、更新日志、API、贡献指南、问题反馈等。

五、核心实现

5.1 MITM

这是FunProxy的MITM(Man-in-the-Middle,中间人)方案。所谓的中间人,就是在服务端和客户端之间加入一个节点,这个节点负责接收客户端发送的内容,通过自己的规则然后再转发到服务端。我们这里用HTTP和HTTPS举例:

HTTP中间人方案:

客户端通过发送HTTP内容到FunProxy,FunProxy拿到HTTP内容后,在转发到服务器中。服务器处理完成后再将信息发送给FunProxy,FunProxy在转发给客户端。这就完成了一个请求的中间人接收和转发方案。

由于HTTP都是明文传输,所以比较简单。HTTPS就比较复杂了,涉及到SSL证书的认证,具体的大家可以看图上的流程。

了解完了中间人的过程。下面我们根据刚才分析的过程,可以设计出Fun-MITM的模块需要包含哪些模块。

**CA模块:**负责根证书的生成和加载,以及各个网站证书的认证

**Client:**这个Client就是模拟真实用户发送请求的那个客户端

**Server:**这个Server就是拦截用户请求的那个服务端

**http_handler:**更改请求和响应的内容

**socket_handker:**处理socket请求

复制代码
let mitm = Proxy::builder()
    .with_addr(addr)
    .with_client(client)
    .with_ca(ca)
    .with_http_handler(custom_handler.clone())
    .with_websocket_handler(custom_handler.clone())
    .with_graceful_shutdown(async {
        close_rx.await.unwrap_or_default();
    })
    .build();

pub struct WantsHandlers<C, CA, H, W, F> {
  al: AddrOrListener,
  client: Client<C, Body>,
  ca: CA,
  http_handler: H,
  websocket_handler: W,
  websocket_connector: Option<Connector>,
  server: Option<Builder<TokioExecutor>>,
  graceful_shutdown: F,
}

impl<C, CA, H, W, F> ProxyBuilder<WantsHandlers<C, CA, H, W, F>> {
  /// Set the HTTP handler.
  pub fn with_http_handler<H2: HttpHandler>(
      self,
      http_handler: H2,
  ) -> ProxyBuilder<WantsHandlers<C, CA, H2, W, F>> {
  }

  /// Set the WebSocket handler.
  pub fn with_websocket_handler<W2: WebSocketHandler>(
      self,
      websocket_handler: W2,
  ) -> ProxyBuilder<WantsHandlers<C, CA, H, W2, F>> {
  }

  /// Set the connector to use when connecting to WebSocket servers.
  pub fn with_websocket_connector(self, connector: Connector) -> Self {
    
  }

  /// Set a custom server builder to use for the proxy server.
  pub fn with_server(self, server: Builder<TokioExecutor>) -> Self {

  }

  /// Set a future that when ready will gracefully shutdown the proxy server.
  pub fn with_graceful_shutdown<F2: Future<Output = ()> + Send + 'static>(
      self,
      graceful_shutdown: F2,
  ) -> ProxyBuilder<WantsHandlers<C, CA, H, W, F2>> {
    
  }

  /// Build the proxy.
  pub fn build(self) -> Proxy<C, CA, H, W, F> {
  }
}

5.2 虚拟 hosts

在中间人方案中如何实现虚拟Hosts能力?我们在定义client的时候,可以定义一个resolver。这个resolver在每次hosts解析的时候都会调用。

首先他会去云端获取当前hosts的配置的IP是哪个,如果有的话,直接返回。如果没有配置,那么调用系统的loopup_ip函数来直接解析hosts。最后将解析好的hosts返回。

在定义HttpConnector的时候,使用new_with_resolver加载这个dns-resolver。这就完成了一个简单的自定义dns-resolver。

复制代码
let resolver = tower::service_fn(move |name: Name| async move {
    let ip_option = host::get_ip(name.as_str());
    match ip_option {
        Some(ip) => {
            let ip: IpAddr = ip.parse().unwrap();
            host::add(name.as_str(), ip.to_string().as_str());
            Ok::<_, Infallible>(iter::once(SocketAddr::from((ip, 80))))
        }
        None => {
            let sys_resolver = Resolver::from_system_conf().unwrap();
            let future = spawn_blocking(move || {
                let lookup_ip = sys_resolver.lookup_ip(name.as_str()).unwrap();
                if let Some(ip) = lookup_ip.iter().next() {
                    host::add(name.as_str(), ip.to_string().as_str());
                    Ok(SocketAddr::from((ip, 80)))
                } else {
                    Err("No IP address found")
                }
            });

            let addrs = future.await.unwrap().unwrap();
            Ok::<_, Infallible>(iter::once(addrs))
        }
    }
});

let mut http_connector = HttpConnector::new_with_resolver(resolver);

5.3 自动安装证书

在使用FunProxy中可以自动安装并设置证书为信任。具体的是在macOS中调用security add-trusted-cert。在Windows中主要调用certutil方法具体的命令行和rust代码如图片所示:

5.4 流量拦截

接下来给大家分享一下我们是怎么拦截流量的。首先是PC系统,比如Windows/macOS/Linux这些系统上,APP授权后可以获取很高的权限,所以我们直接调用系统提供的接口能力。

比如在macOS上面就是通过networksetup这个命令行工具实现对系统代理的设置,在Windows上面可以通过ProxyServer设置。

而在移动端APP上面,一般APP的权限特别低,APP没有直接设置系统代理的能力,所以我们采用的是通过VPN的方式来实现流量的拦截与转发。

我们通过VpnService来创建VPN服务。然后调用VpnService的各种能力。

这里我们用了一个安卓Vpn的setHttpProxy方法,就可以将所有的流量主动打到我们的fun-mitm 服务器上面。fun-mitm 在通过自己的规则对所有的流量进行处理,包括DNS解析或者请求响应的修改等等。

5.5 调用系统能力

我们以macOS应用沉浸式头部为例给大家做个分享。在macOS应用设计中,其实更加通用性的设计是沉浸式的头部,而Tauri生成的应用,默认都会有一个头部,如屏幕上黄色所示,这个头部其实没多少作用。接下来我将用这个例子,给大家分享一下在 Rust 中如何通过调用系统的原生的能力,来去掉这个头部。

  1. 我们通过 cargo new macos --lib 创建一个lib库。在lib库中新建一个window.swift文件,主要使用window.titlebarAppearsTransparent方法来设置透明度。

  2. 接着在lib中通过swift_rs中的swift宏来调用这个函数,并将这个函数设置为pub给其他方法调用。

  3. 给这个 lib 添加 build.rs 来编译这个库。可以指定macOS的版本等其他内容。最后我们在自己的项目中就可以直接引入这个lib库。直接调用里面的set_titlebar_style方法。从而通过swift_rs调用macOS中的原生能力。

5.6 协同抓包

协同抓包能够让一端的抓包数据,通过链接分享的方式,实时同步给其他所有拥有这个链接的人。

主要核心是我们的fun-core这个库。通过fun-core这个库启动的时候我们会启动两个服务,一个是api,负责接口服务。

另一个就是会启动websocket服务。用户打开分享过来的链接,会自动通浏览器的websocket方法来直接对fun-core的websocket进行通讯。当有新的抓包数据产生的时候,fun-core会通过websocket推送给所有当前连接的用户。从而达到一端抓包,多端同步的功能。

六、规划总结

6.1 规划

接下来我们重点投入的是workflow这个功能,支持对请求响应的全生命周期管理。用户可以直观的选择想要的工具,并构建工作流对请求响应进行处理。工作流不仅支持请求响应的处理,还支持逻辑的判断,比如将符合某种条件下的请求高亮显示出来。等等,可玩性更高。

6.2 总结

通过上面我们对FunProxy的介绍,相信大家对FunProxy有了比较不错的了解。FunProxy使用 Rust 构建的跨平台全链路测试&抓包代理工具。通过FunProxy,我们只需要简单的通过选择,比如选择项目,选择环境,选择规则,就能将全链路测试过程中复杂的配置流程,变得异常的简单。同时通过云端功能让所有人能够共享配置,极大的提升全链路测试的环境配置和抓包效率,让全链路测试变得更加丝滑流畅。

相关推荐
m0_480502647 小时前
Rust 登堂 之 类型转换(三)
开发语言·后端·rust
ftpeak9 小时前
Rust Web开发指南 第六章(动态网页模板技术-MiniJinja速成教程)
开发语言·前端·后端·rust·web
编码浪子9 小时前
趣味学Rust基础篇(数据类型)
开发语言·后端·rust
测试老哥11 小时前
如何用Postman做接口测试?
自动化测试·软件测试·python·测试工具·测试用例·接口测试·postman
编码浪子14 小时前
趣味学习Rust基础篇(用Rust做一个猜数字游戏)
学习·rust
柒柒的代码学习日记1 天前
Web 自动化测试常用函数实战(一)
自动化测试·软件测试·自动化测试函数
love530love1 天前
怎么更新 cargo.exe ?(Rust 工具链)
人工智能·windows·python·rust·r语言
Source.Liu1 天前
【typenum】 23 倒序存储的无符号整数(private.rs片段)
rust
咸甜适中1 天前
rust语言(1.88.0)sqlite数据库rusqlite库(0.37.0)学习笔记
数据库·rust·sqlite·rusqlite
jinlei20091 天前
在python 代码中调用rust 源码库操作步骤
开发语言·python·rust