FunProxy - 使用 Rust 構建跨平臺全鏈路測試抓包代理工具
一、背景介紹
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 構建的跨平臺應用框架。
主要有以下幾個優勢:
- 獨立的前端工程:Tauri 支持任何前端框架,所以你不需要改變你的技術棧;
- 最小化體積:使用操作系統本地的網頁渲染器,Tauri 應用的體積可以達到最小 600KB;
- 跨平臺:同一套代碼可以編譯運行到幾乎所有的OS系統中;
- 高安全性:Tauri 團隊的首要目標,推動 Tauri 的首要任務和最大的創新。
- 進程間通訊:用 JavaScript 編寫前端,用 Rust 編寫應用程序邏輯,并使用 Swift 和 Kotlin 在系統中深入集成;
- 由 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 中如何通過調用系統的原生的能力,來去掉這個頭部。
- 我們通過 cargo new macos --lib 創建一個lib庫。在lib庫中新建一個window.swift文件,主要使用window.titlebarAppearsTransparent方法來設置透明度。
- 接著在lib中通過swift_rs中的swift宏來調用這個函數,并將這個函數設置為pub給其他方法調用。
- 給這個 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,我們只需要簡單的通過選擇,比如選擇項目,選擇環境,選擇規則,就能將全鏈路測試過程中復雜的配置流程,變得異常的簡單。同時通過云端功能讓所有人能夠共享配置,極大的提升全鏈路測試的環境配置和抓包效率,讓全鏈路測試變得更加絲滑流暢。