Stories

Detail Return Return

FunProxy - 使用 Rust 構建跨平台全鏈路測試抓包代理工具 - Stories Detail

作者: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_handler:**處理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,我們只需要簡單的通過選擇,比如選擇項目,選擇環境,選擇規則,就能將全鏈路測試過程中複雜的配置流程,變得異常的簡單。同時通過雲端功能讓所有人能夠共享配置,極大的提升全鏈路測試的環境配置和抓包效率,讓全鏈路測試變得更加絲滑流暢。

user avatar u_16756731 Avatar haoqidewukong Avatar jingdongkeji Avatar wintersun Avatar littlelyon Avatar Rocokingdom2024 Avatar u_17514447 Avatar solvep Avatar junxiudetuoba Avatar huaweiyun Avatar qinwanzi Avatar shumile_5f6954c414184 Avatar
Favorites 27 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.