全部產品
Search
文件中心

:網卡自訂RSS

更新時間:Nov 01, 2025

網卡RSS(接收端擴充)通過雜湊演算法將網路流量智能分發至多CPU核心,實現負載平衡與效能提升,避免單核過載,尤其適用於雲端運算、視頻流分發、高頻交易系統等高並發情境。在處理加密通訊或虛擬網路等特殊協議時,需結合業務特徵自訂RSS規則,解決流量不均勻問題以最大化多核資源使用率。

什麼是RSS

RSS(Receive Side Scaling,接收端擴充)是一種基於雜湊演算法的智能流量調度機制,其核心目標是通過網卡硬體與驅動的配合,基於資料包的特徵(如源/目的IP、連接埠等)計算雜湊值,將不同網路流量精準映射到多個RX隊列,再由綁定的CPU核心平行處理,從而在多核CPU架構下實現負載平衡,提升輸送量並降低單核處理延遲。

支援多隊列的彈性網卡通過為每個隊列獨立設定RX(接收)和TX(發送)通道實現平行處理,其中RX隊列負責接收並暫存來自網路的資料包。當資料包達到網卡時,網卡硬體會根據一定的RSS策略(如輪詢、基於流的分配等策略),將不同資料流動態分配到多個RX隊列,形成流量分流。預設ECS執行個體的RSS策略為VPC固定配置,執行個體內部無法查看和修改。

部分執行個體規格類型系列支援網卡自訂RSS能力,您需要在支援的執行個體規格類型系列上開啟網卡自訂RSS功能,網卡即通過預設的自訂雜湊規則進行流量分發,您還可以進一步根據實際流量特徵調整RSS配置

網卡開啟自訂RSS後,您也可以在DPDK應用情境中配置RSS,以最大化多核效能。

原理與機制

ECS執行個體網卡自訂RSS的核心實現如下:

  • 雜湊值計算:網卡根據收到的資料包的五元組(源IP、目標IP、源連接埠、目標連接埠、協議)、雜湊密鑰,基於一定的雜湊演算法計算雜湊值。

    • 雜湊密鑰:目前僅支援固定的40位元組,ECS執行個體網卡驅動在初始化載入網卡時,產生一個預設的雜湊密鑰。您可以根據實際業務需求調整雜湊密鑰,進而影響流量分布。

    • 雜湊演算法:目前僅支援預設的toeplitz演算法,不支援修改。

    • 雜湊規則:流量類型不同,計算雜湊值的規則不同。ECS網卡RSS預設雜湊計算規則如下(不支援修改):

      • IPv4/IPv6的TCP和UDP流量:按照四元組(源IP、目的IP、源連接埠、目的連接埠)+雜湊密鑰計算雜湊值。

      • IPv4/IPv6的非TCP/UDP流量(如ICMP):按照二元組(源IP、目的IP)+雜湊密鑰計算雜湊值。

      • 非IP報文(如ARP):發送到預設隊列(0號隊列),不進行雜湊計算。

  • 間接表映射:RSS間接表為一個預定義的數組,用於將雜湊值對應到目標接收隊列。每個元素(雜湊桶)儲存目標隊列編號(如 0、1、2),表示資料包應該分發到哪個隊列處理。ECS執行個體網卡驅動載入時,會基於均勻分發模式自動產生一個均勻分配流量的間接表。

    • 間接表長度:即雜湊桶總數,目前僅支援固定為128位元組。

    • 分發模式

      RSS間接表分發模式是RSS中用於將雜湊值對應到具體接收隊列的核心機制,通過預定義間接表,將雜湊計算結果轉換為隊列索引,從而實現流量在多核中靈活分發。

      不同分發模式(包括均勻分發模式、權重分發模式等)直接影響流量在多核CPU上的分布特性,進而影響系統輸送量、延遲、資源使用率及業務效能。

      您可以根據實際應用情境選擇合適的分發模式,詳細資料,請參見間接表分發模式

    • 間接表映射流程

      1. 雜湊桶索引計算索引=雜湊值%間接表長度(如88 % 128 → 索引88)。您可以參考使用RSS計算指令碼進行索引值的計算。

      2. 隊列號填充:根據分發模式,將隊列號寫入間接表的每個雜湊桶。

        間接表產生後,根據計算的索引號即可以查詢到對應的隊列號,即資料包進入該隊列處理。

  • CPU核心綁定:當對應的隊列收到資料包後,觸發對應的中斷,由綁定的CPU核心處理。

    除Red Hat Enterprise Linux以外的鏡像已預設支援網路中斷親和性,即每個隊列已關聯獨立的中斷,並且設定的中斷親和性將中斷處理分散到不同的CPU核心。詳細資料,請參見多隊列的核心機制

網卡開啟/關閉自訂RSS

重要

網卡自訂RSS功能目前在邀測中,如需使用,請提交工單申請。

在支援的執行個體規格上開啟網卡自訂RSS功能後,網卡接收到的流量會根據預設的雜湊規則被分發到多個接收隊列,由不同CPU核心平行處理,從而提升輸送量並降低單核負載。

限制條件

  • 網卡自訂RSS功能目前在按地區逐步開放中,當前支援的地區如下:

    地區名稱

    地區ID

    中國香港

    cn-hongkong

  • 目前僅部分支援網卡多隊列的執行個體規格類型系列支援啟用自訂RSS,包括:通用型執行個體規格類型系列g9i記憶體型執行個體規格類型系列r9i

    您可以通過DescribeInstanceTypes介面查詢規格支援情況,傳回值RssSupport為true表示支援,false表示不支援。

  • 推薦使用Alibaba Cloud Linux 3.2104 LTS 64位鏡像。

    • 如果使用其他公用鏡像,核心版本應大於等於6.12。

    • 如果在DPDK應用中使用自訂RSS功能,DPDK版本應大於等於21.11。

如何開啟/關閉

網卡自訂RSS功能預設關閉,您可以在建立網卡時或者網卡建立後,開啟、關閉網卡自訂RSS功能:

  • 您可以在調用CreateNetworkInterface介面時,設定EnhancedNetwork參數中的EnableRss為true、false,實現建立開啟、關閉自訂RSS功能的彈性網卡。

    網卡自訂RSS功能在網卡綁定到執行個體上後生效。

  • 您也可以通過調用ModifyNetworkInterfaceAttribute,設定EnhancedNetwork參數中的EnableRss為true、false修改網卡的自訂RSS功能,開啟/關閉後,網卡自訂RSS功能在重新綁定到執行個體的時候生效:

  • 您可以通過DescribeNetworkInterfaceAttribute,指定AttributeenhancedNetwork查詢網卡是否啟用自訂RSS,返回的EnableRSS為true表示啟用,false表示未啟用。

    當網卡沒有修改過RSS功能時,此介面不返回EnableRSS參數,即未啟用。

查看網卡RSS

啟用網卡自訂RSS後,網卡驅動在重新載入網卡的時候,會根據預設機制與規則產生預設的RSS配置。

您可以遠程登入Linux執行個體後,執行ethtool -x eth0,查看主網卡的RSS配置。如果是輔助網卡,修改網卡標識即可,如eth1、eth2。

image

  • 雜湊表(RX flow hash indirection table):定義了雜湊值到接收隊列(64個)的映射規則:

    • 雜湊表中每行表示雜湊值的範圍,即起始索引,如0標識雜湊值0-7,8標識8-15,依次類推。

    • 表中的數字(0、1、2)表示對應的接收隊列的編號,本樣本中64個隊列(0-63)。

    • 當前配置:間接表按 0,1,2,...,63,0,1,2...63 迴圈填充,雜湊值會被均勻映射到64個隊列,即流量均勻分配。

  • RSS雜湊密鑰(RSS hash key):計算資料包雜湊值的密鑰,影響流量分發的均勻性。樣本中的十六進位字元,表示40位元組的密鑰,用於雜湊計算。

  • RSS雜湊演算法(RSS hash function):定義計算雜湊值的演算法。

    • toeplitz:當前啟用的預設演算法,支援基於五元組的對稱雜湊,適合通用流量。

    • xor/crc32:其他可選演算法,通常用於特定情境,目前ECS僅支援toeplitz,其他均不支援,未啟用。

  • 雜湊規則:您可以進一步執行以下命令,查看主網卡接收流量的雜湊欄位配置。

    ethtool  -n eth0 rx-flow-hash tcp4
    • 您可以將tcp4替換成您希望查詢的協議類型,如udp4tcp6udp6  。

    • ECS網卡RSS預設雜湊計算規則如下(不支援修改):

      • IPv4/IPv6的TCP和UDP流量:按照四元組(源IP、目的IP、源連接埠、目的連接埠)+雜湊密鑰計算雜湊值。

      • IPv4/IPv6的非TCP/UDP流量(如ICMP):按照二元組(源IP、目的IP)+雜湊密鑰計算雜湊值。

      • 非IP報文(如ARP):發送到預設隊列(0號隊列),不進行雜湊計算。

    • tcp4為例,對於IPv4的TCP流量,預設會按照四元組(源IP、目的IP、源連接埠、目的連接埠)+雜湊密鑰計算雜湊值:

      image

      預期流量特徵如下:

      • 同一TCP串連:所有資料包的雜湊值相同,會被分發到同一隊列,確保資料包的順序性。

      • 不同TCP串連,如果四元組不同,雜湊值不同,流量會被分發到不同隊列,實現多核CPU平行處理不同串連,提升輸送量。

如果返回如下所示資訊,表示網卡未開啟自訂RSS或者網卡綁定的規格不支援自訂RSS,您需要確認執行個體規格支援後啟用網卡自訂RSS功能

image

配置網卡自訂RSS

ECS執行個體的網卡開啟自訂RSS以後,預設的RSS配置通常能夠滿足大部分情境,但在以下情況下您可能需要通過調整雜湊密鑰、配置間接表等方式進行最佳化與調整:

  • 流量不均衡:預設雜湊值可能導致特定流量集中到少數隊列。

  • 效能調優:根據業務流量特徵(如UDP佔比高)調整雜湊規則,最佳化隊列利用率。

  • 安全需求:通過自訂雜湊密鑰防止潛在的駭客預測流量分發路徑。

重要
  • 本樣本中執行個體鏡像以Alibaba Cloud Linux 3.2104 LTS 64位為例進行配置。

  • 本樣本中以主網卡eth0為例說明手動設定RSS的步驟,您可以根據實際情況,替換網卡標識,如eth1、eth2,從而修改對應輔助網卡的RSS配置。

  • 當網卡RSS的配置不匹配業務流量特徵時,可能導致流量分發不均、跨核狀態競爭等問題,影響網路效能和應用穩定性,您需要根據實際業務流量特徵和應用需求進行調整,調整後可通過監控工具即時觀察各隊列接收資料包情況,及時調整配置。

配置雜湊密鑰

如果您發現流量分布不均勻,如發現某些隊列的 rx_packets 顯著高於其他隊列,或者為防止潛在攻擊者通過逆向雜湊值推斷流量模式,您可能需要重建RSS雜湊密鑰。

重要

修改密鑰會導致現有串連的雜湊值變化,可能引起短暫亂序或重傳,建議在業務低峰期操作。

  1. 執行以下命令,查看當前網卡的雜湊密鑰。

    ECS執行個體網卡驅動在初始化載入網卡時,會產生一個40位元組的預設雜湊密鑰。

    ethtool -x eth0

    image

  2. 執行以下命令,使用OpenSSL產生新的隨機密鑰。

    openssl rand -hex 40 | fold -w2 | paste -sd: -
  3. 執行以下命令,應用新密鑰到網卡。

    重要

    此方式為臨時設定方式,在執行個體重啟、網卡重新插拔後會失效,網卡驅動會自動初始化一個隨機的預設配置。

    ethtool -X eth0 hkey <hash key>

    替換<hash key>為上一步產生的新的密鑰。

  4. 執行以下命令,驗證新密鑰是否生效。

    ethtool -x eth0

    可以看到新的密鑰已經生效:

    image

配置間接表

RSS間接表分發模式是RSS中用於將雜湊值對應到具體接收隊列的核心機制,通過預定義間接表,將雜湊計算結果轉換為隊列索引,從而實現流量在多核中靈活分發。

不同分發模式(包括均勻分發模式、權重分發模式等)直接影響流量在多核CPU上的分布特性,進而影響系統輸送量、延遲、資源使用率及業務效能。

不同分發模式適用於不同的業務情境,您可以根據實際需求進行配置調整。

重要

以下樣本配置為臨時設定,在執行個體重啟、網卡重新插拔後會失效,網卡驅動會自動初始化一個隨機的預設配置。

  • 均勻分發模式:將RSS間接表配置為均勻分發模式,使用前N個隊列迴圈填充雜湊桶,適用於通用的高並發業務情境。

    ethtool -X eth0 equal <隊列數N>

    若設定為64,間接表會按 0,1,2,...,63,0,1,2...63,迴圈填充。

    image

  • 按權重分發:按權重比例分配雜湊桶,支援非對稱負載。適用於業務優先順序分級(如隊列0處理即時資料流量、隊列1處理背景工作)、混合CPU效能等情境。

    ethtool -X eth0 weight <隊列0權重> <隊列1權重> ...

    如下樣本,隊列0為60%,隊列1為60%,隊列2為40%:

    說明

    如果一共4個隊列,您只設定了兩個隊列權重,那麼間接表中只會產生和隊列0,1的映射。

    ethtool -X eth0 weight 6 4

    image

  • 部分隊列分發:從指定隊列開始,使用連續隊列迴圈填充雜湊桶,適用於定向流量到特定 CPU 範圍(如 NUMA 節點)的情境。

    ethtool -X eth0 start <起始隊列> equal <隊列數>

    如下樣本,使用隊列2開始共40個隊列(隊列2-41)迴圈填充雜湊桶:

    ethtool -X eth0 start 2 equal 40

    image

通過watch觀察流量分布

執行個體網卡啟用並配置自訂RSS後,您可以通過hping3產生不同特徵的流量,並結合watch監控網卡隊列的中斷分布,可以驗證當前RSS配置下,是否按預期分發流量到多核。

準備工作

購買兩台ECS執行個體,配置如下:

  • 發送端ECS執行個體(10.0.0.252):安裝hping3,用於產生不同特徵的流量。

  • 接收端ECS執行個體:執行個體規格支援網卡多隊列綁定輔助網卡(10.0.0.5),並且輔助網卡eth1啟用自訂RSS

    說明
    • 本樣本中以4個隊列為例測試。

    • 為排除遠程SSH登入接收端執行個體(主網卡)的流量影響,我們在輔助網卡eth1上開啟自訂RSS測試。

  • 網路互連:兩台ECS執行個體在同一安全性群組內網互連。

操作步驟

  1. 遠程登入接收端ECS執行個體,查看輔助網卡的RSS配置,確認預期流量分布。RSS配置說明,請參見查看網卡RSS

    image

  2. 遠程登入發送端ECS執行個體,執行以下命令,安裝hping3

    yum install -y hping3
  3. 在接收端ECS執行個體執行以下命令,即時監控隊列包計數。

    watch -n 1 "ethtool -S eth1 | grep rx[0,1,2,3]_packets"

    根據實際情況,替換接收端網卡的隊列數配置。

  4. 遠程登入發送端ECS執行個體,執行以下命令,類比不同流量模式。

    • 情境一向接收端IP高速發送1萬個SYN包,隨機目標連接埠,確保流量雜湊分散,驗證雜湊均衡性。

      sudo hping3 10.0.0.5 -S -a 10.0.0.252 --rand-dest -p 0 --baseport 10000 -c 10000 -i u100 -I eth0
      • rand-dest:隨機目標連接埠

      • -p 0:與rand-dest配合使用

      • --baseport 10000:源連接埠起始值

      • -c 10000:發送1萬個包

      • -i u100:發包間隔100微秒,高速發送

      • -I eth0:與rand-dest配合使用,指定發送端的網路介面

      觀察接收端ECS執行個體的輸出,預期四個隊列的包計數接近均衡增長:

      image

    • 情境二固定源IP和連接埠發送流量,測試同一流量是否映射到同一隊列,驗證雜湊一致性。

      以下命令表示向目標 10.0.0.5:80 發送1萬個源IP固定(10.0.0.252)的 TCP SYN 包,源連接埠固定為 12345

      sudo hping3 10.0.0.5 -S -p 80 -c 10000 -s 12345 -a 10.0.0.252 --keep -i u100
      • 根據RSS指令碼計算雜湊索引值,預期流量進入隊列0處理:

        image

      • 實際接收端ECS執行個體(10.0.0.5)上觀察結果:

        image

  5. 如果查看業務流量分布結果不符合預期,如某個隊列負載顯著偏高,負載不均衡,您可以嘗試通過調整雜湊密鑰配置間接表等方式最佳化。

DPDK中配置並使用RSS

DPDK(Data Plane Development Kit)是由英特爾發起、現由Linux基金會維護的開源使用者態資料平面加速架構,旨在最佳化網路資料包處理效能。DPDK通過使用者態驅動、零拷貝和輪詢模式等技術,將網路資料包處理效能提升至接近線速水平,成為構建高效能網路應用的基石。適用於對吞吐和延遲敏感的領域,如電信雲、金融科技及邊緣計算。關於DPDK的更多資訊,請參見Data Plane Development Kit (DPDK*)

RSS作為網卡的硬體特性,可以將流量分發到不同的隊列,進而由不同的CPU核心處理,這對於DPDK的多核處理模型非常關鍵。網卡啟用自訂RSS後,您可以通過DPDK中的testpmdl3fwd進行RSS功能的測試及驗證。

ECS執行個體上安裝並配置DPDK

說明
  • 如果在DPDK應用中使用自訂RSS功能,DPDK版本應大於等於21.11。

  • 本樣本中以在規格為ecs.r9i.16xlarge(64個隊列),鏡像為Alibaba Cloud Linux 3.2104 LTS 64位的執行個體上安裝22.11.3版本的DPDK為例說明。

  • 本樣本中以主網卡eth0為例說明手動設定RSS的步驟,您可以根據實際情況,替換網卡標識,如eth1、eth2,從而修改對應輔助網卡的RSS配置。

步驟一:安裝DPDK

點擊查看安裝DPDK樣本步驟

  1. 更新系統並安裝基礎工具。

    sudo yum update -y
    sudo yum install -y git wget gcc make kernel-devel-$(uname -r) numactl-devel python3 pciutils
  2. 安裝DPDK依賴。

    sudo yum install -y libpcap-devel meson ninja-build
  3. 配置大頁記憶體。

    • DPDK 繞過核心協議棧直接操作網卡,需高效管理記憶體以減少 TLB(Translation Lookaside Buffer)未命中率,大頁記憶體(Huge Pages)通過使用比傳統4 KB頁面更大的記憶體頁面(通常是2MB),減少地址轉換過程中TLB(Translation Lookaside Buffer)的缺失次數,從而提高記憶體訪問速度。

    • 大頁記憶體如果分配過多,會減少留給作業系統動態分配的普通記憶體空間,可能導致其他非大頁的應用程式或系統服務因記憶體不足而運行不暢或失敗。當執行個體的大頁記憶體設定過大,可能會導致執行個體無法串連等問題。

    • 您可以基於應用的實際記憶體需求和系統總記憶體,計算所需的大頁數量。

      所需大頁數量 = (應用所需記憶體大小 / 大頁的實際大小)。Linux系統中,預設的大頁大小通常是2 MB。例如,如果一個應用需要16 GB的大頁記憶體,且大頁大小為2MB,則所需的大頁數量為16 GB / 2 MB = 8192。

    echo "vm.nr_hugepages = 8192" | sudo tee -a /etc/sysctl.conf
    sudo sysctl -p
  4. 建立並掛載大頁目錄。

    sudo mkdir -p /dev/hugepages
    sudo mount -t hugetlbfs hugetlbfs /dev/hugepages
  5. 下載DPDK源碼(需開通公網)。

    cd ~
    wget https://fast.dpdk.org/rel/dpdk-22.11.3.tar.xz
    tar xf dpdk-22.11.3.tar.xz
    cd dpdk-stable-22.11.3
  6. 編譯DPDK。

    # 初始化構建目錄並設定項目選項,指定構建l3fw三層轉寄樣本
    meson setup -Dexamples=l3fwd build
    cd build
    #編譯
    ninja
    #將編譯好的檔案安裝到系統目錄
    sudo ninja install
    #更新系統的共用庫緩衝
    sudo ldconfig

    編譯時間候遇到如下圖所示missing python module: elftools異常,

    image

    您需要指定當前使用的python版本,安裝pyelftools庫後再次重新編譯即可:

    sudo /usr/bin/python3.8 -m pip install pyelftools

步驟二:載入核心模組

DPDK需要核心模組如UIO或VFIO來支援使用者態裝置訪問,通常優先選擇VFIO以提高安全性(依賴IOMMU),而UIO適用於快速測試。本文以VFIO驅動為例說明配置。

  1. 啟用IOMMU。

    VFIO依賴IOMMU實現安全的使用者態裝置綁定和DMA映射,需要啟用IOMMU。

    1. 開啟設定檔。

      sudo vim /etc/default/grub
    2. i切換到編輯模式,在GRUB_CMDLINE_LINUX中添加intel_iommu=on,然後儲存設定檔。

      修改完成後的樣本如下圖所示。grub-config

    3. 應用修改後的配置。

      sudo grub2-mkconfig -o /boot/grub2/grub.cfg

      image.png

    4. 執行以下命令,重啟執行個體並再次遠端連線執行個體。

      reboot
      警告

      重啟執行個體會造成您的執行個體停止工作,可能導致業務中斷,建議您在非業務高峰期時執行該操作。

  2. 執行以下命令,安裝VFIO和VFIO-PCI驅動。

    sudo modprobe vfio && \
    sudo modprobe vfio-pci
  3. 配置noiommu_mode。

    sudo bash -c 'echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode'

步驟三:綁定網卡到DPDK驅動

  1. 開啟網卡自訂RSS

    請確認待DPDK驅動接管的網卡已開啟自訂RSS且已重新掛載到執行個體上生效,網卡驅動在重新載入時候會產生預設的RSS配置。

  2. 通過VNC串連執行個體

    • 本樣本以主網卡為例,後續有卸載網卡的操作,如果通過SSH會話串連,會導致串連中斷。

    • 您如果在輔助網卡上進行操作,則依然可以通過Workbench串連執行個體(主網卡)後,進行配置。

  3. 執行以下命令,查看網卡的PCI裝置驅動綁定狀態,預設情況下網卡被系統核心佔用中。

    dpdk-devbind.py --status

    image

    • 裝置 0000:00:05.0 (PCI裝置標識符)當前由核心的 virtio-pci 驅動管理,對應的網路介面是 eth0,且該介面處於 活動狀態(已配置 IP 或正在傳輸資料)。

    • 該裝置可切換至 vfio-pci 驅動(用於 DPDK 使用者態接管),但需先停用介面並解除綁定核心驅動。

  4. 執行以下命令,停用eth0。

    sudo ip link set dev eth0 down

    如果不停用,綁定到VFIO時候會提示異常資訊:

    image

  5. 執行以下命令,解除綁定核心驅動並綁定到VFIO。

    dpdk-devbind.py -b vfio-pci 0000:00:05.0

    您需要根據實際情況,替換查詢到的網卡的PCI裝置標識符。

    說明

    您可以通過sudo pkill dpdk-app停止DPDK應用後,再dpdk-devbind.py -b virtio-pci 0000:00:05.0綁回核心驅動。

  6. 再次執行以下命令,查看網卡的PCI裝置驅動綁定狀態,預期網卡已經被DPDK接管。

    重要

    當網卡被DPDK使用者態驅動接管後,核心不再控制該裝置,因此無法通過 ip a 等命令查看其資訊。

    dpdk-devbind.py --status

    image

    可以看到,裝置 0000:00:05.0當前已綁定到使用者態驅動 vfio-pci

通過testpmd配置RSS

testpmd(Test Packet Mode Driver)是DPDK中的一個核心測試載入器,主要用於快速驗證DPDK的功能、測試網卡驅動的效能,以及調試資料平面應用。關於更多使用testpmd的資訊,請參見Testpmd Runtime Functions

重要

對網卡的配置修改(如隊列數、RSS規則、RETA表等),僅在啟動或重啟資料包轉寄後生效。

  1. 通過VNC串連執行個體

  2. 執行以下命令,啟動DPDK的testpmd資料包轉寄測試載入器。

    dpdk-testpmd -a 0000:00:05.0 --socket-mem 1024 -- -i --portmask=0x1 --rxq=64 --txq=64  --forward-mode=rxonly
    • -a 0000:00:05.0:綁定PCI地址為0000:00:05.0的網卡到DPDK,您可以通過dpdk-devbind.py --status查看PCI地址。

    • --socket-mem 1024:為每個NUMA節點預分配1024MB大頁記憶體。

    • -i:啟動後進入互動式命令列模式,允許動態調整配置(如修改轉寄模式、查看統計資訊)。輸入quit退出。

    • --portmask=0x1:啟用連接埠掩碼 0x1(二進位 0001),表示僅使用第一個網卡(即PCI地址0000:00:05.0 對應的網卡)。

      • 在DPDK中,portmask的每個二進位位對應一個連接埠號碼(例如 0x1 表示啟用連接埠0,0x3 表示啟用連接埠0和1)。

      • 本樣本中,只有1個裝置(0000:00:05.0)被綁定到DPDK,所以所在DPDK中的連接埠號碼是0。

    • --rxq=64 / --txq=64:設定每個連接埠的接收隊列(RXQ)和發送隊列(TXQ)數量為 64,支援多隊列平行處理。您可以替換為實際查詢到的彈性網卡的隊列數

    • --forward-mode=rxonly:指定轉寄模式為僅接收(接收資料包後丟棄,不發送),用於測試網卡接收效能或抓包。

  3. 進入互動模式,查詢網卡當前RSS配置。

    • 查詢雜湊配置資訊show port info <port_id>

      port_id:指定連接埠,本樣本中為port 0。

      點擊查看樣本輸出

      image

    • 查詢雜湊密鑰show port <port_id> rss-hash key

      image

    • 查詢間接表配置show port <port_id> rss reta <size> <mask0, mask1...>

      • size:查詢的間接表條目數量,當前固定為128位元組。

      • mask0,mask1:掩碼,篩選要顯示的雜湊索引範圍(十六進位格式),如掩碼0=0xff 表示顯示雜湊索引 0-7 的條目。

        當前間接表大小為固定的128,需要2個掩碼,每個掩碼覆蓋 64 個索引塊,則輸入show port 0 rss reta 128 (0xffffffffffffffff,0xffffffffffffffff)返回所有128個索引塊和隊列的映射資訊:

        點擊查看樣本輸出

        image

  4. 根據不同的需求,進行RSS配置。

    • 執行以下命令,配置新產生的雜湊密鑰。

      您可以使用OpenSSL產生新的隨機密鑰

      port config <port_id> rss-hash-key (ipv4|ipv4-frag|\
                        ipv4-tcp|ipv4-udp|ipv4-sctp|ipv4-other|\
                        ipv6|ipv6-frag|ipv6-tcp|ipv6-udp|ipv6-sctp|\
                        ipv6-other|l2-payload|ipv6-ex|ipv6-tcp-ex|\
                        ipv6-udp-ex <string of hex digits \
                        (variable length, NIC dependent)>)

      以TCP over IPv4為,可執行port config 0 rss-hash-key ipv4 6D5A56DA255B0EC24167253D43A38FB0D0CA2BCBAE7B30B477CB2DA38030F20C6A42B73BBEAC01FC 配置雜湊密鑰並確定生效:

      image

    • 執行以下命令,配置RSS間接表,將特定的雜湊值對應到指定的隊列。

      port config all rss reta <hash,queue>,<hash,queue>..

      您需要根據實際隊列數進行配置:

      • hash:雜湊值的索引,範圍取決於間接表大小(例如 0-63 對應 64 條目表)。

      • queue:目標接收隊列號,如隊列0,隊列1。

在l3fwd中應用RSS

如果您需要在DPDK應用程式中啟用RSS,需要在相關代碼中實現RSS雜湊密鑰、間接表等配置,以下以L3FWD為例說明。

L3FWD(Layer 3 Forwarding)是 DPDK(Data Plane Development Kit)中的一個三層網路轉寄樣本應用,用於示範如何基於 IP 位址 實現高效能的資料包路由和轉寄。它通過 DPDK 的零拷貝、輪詢模式驅動(PMD)等技術,實現接近線速的三層轉寄效能。

  1. 修改DPDK中L3FWD源碼(examples/l3fwd/main.c)。

    • 修改L3FWD範例程式碼中連接埠初始化部分static struct rte_eth_conf port_conf

      點擊查看修改代碼

      #define RSS_HASH_KEY_LENGTH 	40
      #define RSS_RETA_SIZE 			128
      
      static uint8_t hash_key[RSS_HASH_KEY_LENGTH] = {
          0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A,
          0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A,
          0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A,
          0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A,
      };
      
      static struct rte_eth_conf port_conf = {
      	.rxmode = {
      		.mq_mode = RTE_ETH_MQ_RX_RSS,
      		.offloads = RTE_ETH_RX_OFFLOAD_UDP_CKSUM | RTE_ETH_RX_OFFLOAD_TCP_CKSUM,
      	},
      	.rx_adv_conf = {
      		.rss_conf = {
      			.rss_key = hash_key,
      			.rss_hf = RTE_ETH_RSS_IP,
      			.rss_key_len = RSS_HASH_KEY_LENGTH,
      		},
      	},
      	.txmode = {
      		.mq_mode = RTE_ETH_MQ_TX_NONE,
      	},
      };
    • 新增配置雜湊間接表函數和配置雜湊密鑰函數。

      點擊查看新增代碼

      /**
       * @brief Configure the RSS RETA (Redirection Table Array) for a given port.
       * 
       * @param port_id The ID of the port to configure.
       */
      
      void configure_rss_reta(uint16_t port_id) {
          struct rte_eth_rss_reta_entry64 reta_conf[RSS_RETA_SIZE / RTE_ETH_RETA_GROUP_SIZE];
          unsigned int i;
          uint16_t reta_size;
      	uint16_t nb_queues;
      	struct rte_eth_dev_info dev_info;
      
      	if (port_id >= RTE_MAX_ETHPORTS) {
              printf("port_id %d exceed max eth ports\n", port_id);
              return;
          }
      
          if (rte_eth_dev_info_get(port_id, &dev_info) != 0) {
      		printf("Failed to get device info for port %d\n", port_id);
      		return;
      	}
      
          reta_size = dev_info.reta_size;
          if (reta_size == 0) {
              printf("Device does not support RSS RETA configuration.\n");
              return;
          }
      
      	nb_queues = dev_info.nb_rx_queues;
      	if (nb_queues == 0) {
      		printf("port %d RX queues = 0\n", port_id);
      		return;
      	}
      
          // Initialize RETA table
          memset(reta_conf, 0, sizeof(reta_conf));
          for (i = 0; i < reta_size; i++) {
              reta_conf[i / RTE_ETH_RETA_GROUP_SIZE].reta[i % RTE_ETH_RETA_GROUP_SIZE] = (uint16_t)(i % nb_queues);
          }
      
          // Configure RETA table mask
          for (i = 0; i < reta_size; i += RTE_ETH_RETA_GROUP_SIZE) {
              reta_conf[i / RTE_ETH_RETA_GROUP_SIZE].mask = UINT64_MAX;
          }
      
      	// Update RSS RETA table to device
      	if (rte_eth_dev_rss_reta_update(port_id, reta_conf, reta_size) != 0) {
      		printf("Failed to update RSS RETA table for port %u\n", port_id);
      		return;
      	}
      }
      
      /**
       * @brief Configure the RSS hash key for a given port.
       * 
       * @param port_id The ID of the port to configure.
       */
      void configure_rss_hash_key(uint16_t port_id) {
          struct rte_eth_rss_conf rss_conf;
      
      	if (port_id >= RTE_MAX_ETHPORTS) {
              printf("port_id %d exceed max eth ports\n", port_id);
              return;
          }
      
          memset(&rss_conf, 0, sizeof(rss_conf));
          rss_conf.rss_key = hash_key;
          rss_conf.rss_key_len = sizeof(hash_key);
          
          // Update RSS hash key to device
          if (rte_eth_dev_rss_hash_update(port_id, &rss_conf) != 0) {
              printf("Failed to update RSS hash key for port %u\n", port_id);
      		return;
          }
      }
    • rte_eth_dev_start之後調用新增的配置函數。

      點擊查看main中調用部分代碼

      @@ -1472,12 +1576,15 @@ main(int argc, char **argv)
                      /* Start device */
      		ret = rte_eth_dev_start(portid);
      		if (ret < 0)
      			rte_exit(EXIT_FAILURE,
      				"rte_eth_dev_start: err=%d, port=%d\n",
      				ret, portid);
      				
      		configure_rss_reta(portid);
      		configure_rss_hash_key(portid);
  2. 修改源碼後,重新編譯L3FWD。

    cd ~/dpdk-stable-22.11.3/
    rm -rf build
    # 初始化構建目錄並設定項目選項,指定構建l3fw三層轉寄樣本
    meson setup -Dexamples=l3fwd build
    cd build
    #編譯
    ninja
    #將編譯好的檔案安裝到系統目錄
    sudo ninja install
    #更新系統的共用庫緩衝
    sudo ldconfig
  3. 執行以下命令,指定連接埠、隊列與核心的綁定關係,啟動L3FWD。

    cd ~/dpdk-stable-22.11.3/build/examples
    ./dpdk-l3fwd --legacy-mem -a 0000:00:05.0 --socket-mem 1024 -- -p 0x1 --config="(PORT_ID, QUEUE_ID, LCORE_ID), (PORT_ID, QUEUE_ID, LCORE_ID), ..." --parse-ptype
    • --config:每個三元組表示:

      • PORT_ID:綁定的連接埠號碼(從 0 開始)。

      • QUEUE_ID:該連接埠的接收隊列號(從 0 開始)。

      • LCORE_ID:處理該隊列的邏輯核心(lcore)號(從 0 開始)。

    • 以2個核心、2個隊列為例測試:

      ./dpdk-l3fwd --legacy-mem -a 0000:00:05.0 --socket-mem 1024 -- -p 0x1 --config="(0,0,0),(0,1,1)"  --parse-ptype
      • 第一個三元組 (0,0,0):連接埠0 的隊列0 由邏輯核心0(lcore0)處理。

      • 第二個三元組 (0,1,1):連接埠0 的隊列1 由邏輯核心1(lcore1)處理。

      image

使用RSS指令碼計算雜湊索引

當RSS規則配置完成後,您可以通過我們提供的python指令碼,通過輸入四元組、hash key等資訊計算接收隊列。使用該指令碼可計算特定四元組報文在配置了指定hash key時 ,ECS後端計算得到的間接表的索引值,可用於指導配置間接表在指定隊列上接收特定報文流。

點擊查看ali_ecs_rss_calc.py指令碼內容

#!/usr/bin/python

import sys
import argparse
import re
import ipaddress

prog_name = sys.argv[0]
USAGE_EXAMPLE = """
Usage example:
    Calculate the Toeplitz hash of a packet sent from 1.2.3.4 to 1.2.3.5 with source/destination
    port of 7000:

    - Argument hash key is required since virtio-net driver creates a random hash key for all your NICs:

    $ {prog_name} -t 1.2.3.4 -T 7000 -r 1.2.3.5 -R 7000 -k 77:d1:c9:34:a4:c9:bd:87:6e:35:dd:17:b2:e3:23:9e:39:6d:8a:93:2a:95:b4:72:3a:b3:7f:56:8e:de:b6:01:97:af:3b:2f:3a:70:e7:04
    
    - If you want to calculate the RSS value of a packet whose protocol is not TCP or UDP, don't specify
      the source port and destination port, the script will calculate the hash value based on only the IP addresses:
      
    $ {prog_name} -t 1.2.3.4 -r 1.2.3.5 -k 77:d1:c9:34:a4:c9:bd:87:6e:35:dd:17:b2:e3:23:9e:39:6d:8a:93:2a:95:b4:72:3a:b3:7f:56:8e:de:b6:01:97:af:3b:2f:3a:70:e7:04
    
    - Use "--ipv6" to calculate RSS value for IPv6 packets:
    
    $ {prog_name} -t 2001:250:250:250:250:250:250:1 -T 7000 -r 2001:250:250:250:250:250:250:2 -R 7000 -k 77:d1:c9:34:a4:c9:bd:87:6e:35:dd:17:b2:e3:23:9e:39:6d:8a:93:2a:95:b4:72:3a:b3:7f:56:8e:de:b6:01:97:af:3b:2f:3a:70:e7:04 --ipv6

    Please note that Linux kernel 5.9 or newer is required for hash function/key configuration support
    
    Also the Linux kernel older than ANCK 5.10-018 had a bug in which the default hash key showed by ethtool is not 
    equal to the one used by the device before changing any RSS configuration mannually. The script will print the
    right hash value in this case if you don't specify the hash key while calculating.
""".format(prog_name = prog_name)

# The default key on instances for old alinux kernel(< ANCK 5.10-018) should be as below, not the one you get from "ethtool -x <nic_name>".
RSS_DEFAULT_KEY = [
	0x6D, 0x5A, 0x56, 0xDA, 0x25, 0x5B, 0x0E, 0xC2,
	0x41, 0x67, 0x25, 0x3D, 0x43, 0xA3, 0x8F, 0xB0,
	0xD0, 0xCA, 0x2B, 0xCB, 0xAE, 0x7B, 0x30, 0xB4,
	0x77, 0xCB, 0x2D, 0xA3, 0x80, 0x30, 0xF2, 0x0C,
	0x6A, 0x42, 0xB7, 0x3B, 0xBE, 0xAC, 0x01, 0xFA,
]
# The default key on instances for new alinux kernel(>= ANCK 5.10-018) is randomly generated.

TOEPLITZ_KEY_SIZE = 128
BITS_IN_BYTE = 8

def circular_shift_key_one_left(key):
    """The function does a cyclic shift left of the whole key.
    To be able to shift the whole 40 bytes left in a cyclic manner, the function
    shifts the bits between two adjacent bytes each time"""

    l = len(key)
    return [ ((key[i] << 1) & 0xff) | ((key[(i + 1) % l] & 0x80) >> 7) for i in range(0, l) ]

def or_32msb_bits_of_key(key):
    return (key[0] << 24) | (key[1] << 16) | (key[2] << 8) | key[3]

def calculate_hash(rx_ip, rx_port, tx_ip, tx_port, initial_value, key):
    """Calculate the Toeplitz hash based on the given parameters. Note that this
    implementation is relevant for ENA, and doesn't claim to be compatible with
    the standard implementation"""

    hash_result = initial_value
    input_bytes = list()
    input_bytes += tx_ip + rx_ip + tx_port + rx_port

    for input_byte in input_bytes:
        for i in range(BITS_IN_BYTE):
            # is the (8 - i -1) bit set
            if (input_byte & (1 << (BITS_IN_BYTE - i - 1))):
                hash_result ^= or_32msb_bits_of_key(key)

            key = circular_shift_key_one_left(key)

    return hash_result

def ipv4_addr_type(str):
    """type function to argparse which transforms an
    ipv4 string into its hexadecimal number"""
    if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", str):
        raise argparse.ArgumentTypeError("IP address needs to have the format 1.2.3.4")

    return [int(octet) for octet in str.split('.')]

def ipv6_addr_type(str):
    """type function to argparse which transforms an
    ipv6 string into its hexadecimal number"""
    
    try:
        ip_str = ipaddress.IPv6Address(unicode(str))
    except ValueError as e:
        raise argparse.ArgumentTypeError("Invalid IPv6 address format, %s" % e)
    
    parts = ip_str.exploded.split(':')
    try:
        bytes_list = [int(part, 16) for part in parts]
        bytes_list = [(byte >> 8, byte & 0xff) for byte in bytes_list]
    except ValueError as e:
        raise argparse.ArgumentTypeError("Invalid IPv6 address format, %s" % e)
    
    return [item for sublist in bytes_list for item in sublist]

def toeplitz_key_type(str):
    """type function to argparse which transforms a
    Toeplits key string into an array of hexadecimal values"""
    if not re.match(r"^([0-9a-zA-Z]{1,2}:){39}[0-9a-zA-Z]{1,2}$", str):
        raise argparse.ArgumentTypeError("Toeplitz key hash format is invalid (should be 40 hex values delimeted with columns)")

    return [int(key_elem, 16) for key_elem in str.split(':')]

def main():

    parser = argparse.ArgumentParser(description='virtio-net Toeplitz hash calculator',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog=USAGE_EXAMPLE)

    parser.add_argument('-r', '--rx-ip', help='Receiving side ipv4', dest='rx_ip', nargs='?',
                        required=True, type=str)
    parser.add_argument('-R', '--rx-port', help='Receiving side port', dest='rx_port', nargs='?', type=int)
    parser.add_argument('-t', '--tx-ip', help='Transmitting side ipv4', dest='tx_ip', nargs='?', 
                        required=True, type=str)
    parser.add_argument('-T', '--tx-port', help='Transmitting side port', dest='tx_port', nargs='?', type=int)
    parser.add_argument('-k', '--toeplitz-key',
                        help='The Toeplitz key (only in instances that support changing it)',
                        dest='toeplitz_key', nargs='?', required=True, type=toeplitz_key_type)
    parser.add_argument('-i', '--ipv6',  action='store_true', help='Use IPv6 address type for --rx-ip')

    args = parser.parse_args()

    if args.ipv6:
        rx_ip   = ipv6_addr_type(args.rx_ip)
        tx_ip   = ipv6_addr_type(args.tx_ip)
    else:
        rx_ip   = ipv4_addr_type(args.rx_ip)
        tx_ip   = ipv4_addr_type(args.tx_ip)
    
    if args.rx_port and args.tx_port:
        # "break" port number into two byte representation
        rx_port = [(args.rx_port & 0xff00) >> 8, args.rx_port & 0x00ff]
        tx_port = [(args.tx_port & 0xff00) >> 8, args.tx_port & 0x00ff]
    else:
        rx_port = tx_port = []
    
    key = args.toeplitz_key

    # calculate the hash with inital value of 0x0xffffffff
    hash = calculate_hash(rx_ip, rx_port, tx_ip, tx_port, 0, key)
    rss_table_entry_128 = hash % 128
    rss_table_entry_256 = hash % 256

    if args.ipv6:
        print("Sending traffic from [{}]:{} to [{}]:{}".format(args.tx_ip, args.tx_port, args.rx_ip, args.rx_port))
    else:
        print("Sending traffic from {}:{} to {}:{}".format(args.tx_ip, args.tx_port, args.rx_ip, args.rx_port))
    print("""the hash is calculated over the following fields:
    Source IP address
    Destination IP address""")
    if args.rx_port and args.tx_port:
        print("""    Source port
    Destination port""")
    print("Should result in the hash for all drivers:".ljust(50) + "{}".format(hex(hash)))
    print("RSS table entry (total length 128):".ljust(50) + "{}".format(rss_table_entry_128))
    print("RSS table entry (total length 256):".ljust(50) + "{}".format(rss_table_entry_256))
    return

if __name__ == '__main__':
    main()

點擊查看指令碼使用方式

您可以通過python ali_ecs_rss_calc.py -h查看指令碼使用方式

image

以如下64個隊列的網卡RSS配置為例說明:

image

基於以下五元組資訊(您可以根據實際情況修改),通過RSS計算指令碼計算RSS雜湊值:

  • 目標IP地址(-r):10.0.0.1,即配置了RSS的接收端執行個體網卡的IP

  • 源IP地址(-t):10.0.0.251,即發送端網卡的IP

  • 目標連接埠(-R):26000

  • 源連接埠(-T):18042

  • 雜湊密鑰:從RSS間接表配置擷取

python ali_ecs_rss_calc.py -r 10.0.0.1 -t 10.0.0.251 -R 26000 -T 18042 -k 69:e8:7c:56:bf:03:9f:63:d7:c5:e5:96:b3:00:36:93:02:8c:d2:8f:cc:a9:00:65:fd:c8:94:71:5f:fd:c8:de:7a:30:a9:73:b3:33:0c:c6

指令碼根據傳入的以上參數,基於toeplitz演算法進行雜湊計算,如下圖返回結果所示,當前僅支援長度為128的間接表,那麼此次計算結果雜湊索引值為117,即此次串連的資料包由RSS間接表中的117號索引對應的隊列處理。

image

再次查看RSS間接表配置,可以看到117號索引對應的隊列為53,即資料包由隊列53處理。

image