すべてのプロダクト
Search
ドキュメントセンター

:ENI のカスタム RSS

最終更新日:May 16, 2026

サポートされている ENI (Elastic Network Interface) で RSS (受信側スケーリング) のハッシュルールと間接参照テーブルをカスタマイズすることで、トラフィックを CPU コア全体に均等に分散させ、シングルコアボトルネックを解消します。

RSS とは

受信側スケーリング (RSS) は、ENI のハードウェアとドライバーを使用して、パケットの特性 (ソース/デスティネーション IP アドレスとポート) からハッシュ値を計算し、異なるトラフィックストリームを複数の RX キューにマッピングします。バインドされた CPU コアがキューを並列処理することで、負荷分散を実現し、シングルコアのレイテンシーを削減します。

複数のキューをサポートする ENI は、並列処理のために、キューごとに独立した RX (受信) チャネルと TX (送信) チャネルを使用します。パケットが到着すると、ENI のハードウェアは RSS ポリシーを使用して、さまざまなデータストリームを複数の RX キューに分散させます。デフォルトでは、ECS インスタンスの RSS ポリシーは Virtual Private Cloud (VPC) で固定されており、インスタンス内から表示または変更することはできません。

一部のインスタンスファミリーは、ENI のカスタム RSS 機能をサポートしています。サポートされているインスタンスファミリーでカスタム RSS 機能を有効にすると、ENI は デフォルトのカスタムハッシュルール に基づいてトラフィックを分散します。また、実際のトラフィックの特性に基づいて RSS 設定をさらに調整することもできます。

カスタム RSS を有効にした後、DPDK シナリオで RSS を設定することで、マルチコアのパフォーマンスを最大化することもできます。

仕組み

Elastic Compute Service (ECS) インスタンス ENI のカスタム受信側スケーリング (RSS) は、次のように動作します。

  • ハッシュ値の計算:ENI は、5 タプル (送信元 IP、宛先 IP、送信元ポート、宛先ポート、プロトコル)、ハッシュキー、およびハッシュアルゴリズムからハッシュ値を計算します。

    • ハッシュキー:固定の 40 バイト値です。ENI ドライバーは、初期化時にデフォルトのハッシュキーを生成します。ハッシュキーを調整する ことで、トラフィック分散に影響を与えることができます。

    • ハッシュアルゴリズム:現在、デフォルトの toeplitz アルゴリズムのみがサポートされています。この設定は変更できません。

    • ハッシュルール:ハッシュ値を計算するルールは、トラフィックタイプによって異なります。ECS ENI RSS のデフォルトのハッシュルールは次のとおりです。

      • IPv4/IPv6 における TCP および UDP トラフィック:4 タプル (送信元 IP、宛先 IP、送信元ポート、宛先ポート) とハッシュキーに基づいてハッシュ値が計算されます。

      • ICMP などの IPv4/IPv6 における非 TCP/UDP トラフィック:2 タプル (送信元 IP と宛先 IP) とハッシュキーに基づいてハッシュ値が計算されます。

      • 非 IP メッセージ (ARP など) はデフォルトキュー (キュー 0) に送信され、ハッシュ化されません。

  • 間接参照テーブルのマッピング:RSS 間接参照テーブルは、ハッシュ値を宛先受信キューにマッピングする事前定義された配列です。各要素 (ハッシュバケット) には、0、1、2 などのキュー番号が格納されます。ENI ドライバーは、読み込み時に、トラフィックを均等に分散する間接参照テーブルを自動的に生成します

    • 間接参照テーブルの長さ:値は 128 に固定されています。

    • 分散モード

      RSS 間接参照テーブルの分散モードは、ハッシュ値を特定の受信キューにマッピングする中核となるメカニズムです。事前定義された間接参照テーブルを使用して、ハッシュ計算の結果がキューインデックスに変換されます。これにより、複数のコアにわたる柔軟なトラフィック分散が可能になります。

      一様分布や重み付き分布などの異なる分散モードは、トラフィックが CPU コア全体にどのように分散されるかに直接影響します。これにより、システムのスループット、レイテンシー、リソース使用率、およびサービスのパフォーマンスに影響を与えます。

      シナリオに基づいて、適切な分散パターンを選択してください。「間接参照テーブルの分散パターン」をご参照ください。

    • 間接参照テーブルのマッピングプロセス

      1. ハッシュバケットインデックスの計算インデックス = ハッシュ値 % 間接参照テーブルの長さ (例: 88 % 128 → インデックス 88)。RSS 計算スクリプト を使用して、インデックス値を計算できます。

      2. キュー番号の書き込み:分散モードに基づいて、各ハッシュバケットにキュー番号を書き込みます。

        間接参照テーブルが生成されると、各パケットのハッシュインデックスがキュー番号にマッピングされ、パケットはそのキューに送信されます。

  • CPU コアバインディング:キューがパケットを受信すると、割り込みが発生します。バインドされた CPU コアが割り込みを処理します。

    Red Hat Enterprise Linux 以外のイメージは、デフォルトでネットワーク割り込みアフィニティをサポートしています。各キューは独立した割り込みに関連付けられており、割り込みアフィニティが処理を異なる CPU コアに分散します。「マルチキューのコアメカニズム」をご参照ください。

ENI のカスタム RSS の有効化または無効化

重要

ENI のカスタム RSS 機能は招待制プレビューです。この機能を使用するには、チケットを送信してください。

サポートされているインスタンスタイプでカスタム RSS を有効化すると、受信したトラフィックはデフォルトのハッシュルールに基づいて複数の受信キューに分散されます。異なる CPU コアがキューを並列に処理することで、スループットが向上し、シングルコアの負荷が低減します。

前提条件

  • カスタム RSS は段階的に展開されています。次のリージョンで利用できます:

    リージョン名

    リージョン ID

    中国 (香港)

    cn-hongkong

  • カスタム RSS をサポートするのは、一部のマルチキューインスタンスファミリーのみです。これには、汎用インスタンスファミリー g9iメモリ最適化インスタンスファミリー r9i が含まれます。

    サポート状況を確認するには、DescribeInstanceTypes API を呼び出します。 RssSupport=true の場合、この機能がサポートされています。

  • Alibaba Cloud Linux 3.2104 LTS 64-bit イメージを推奨します。

    • 他のパブリックイメージを使用する場合、カーネルバージョンは 6.12 以降である必要があります。

    • DPDK アプリケーションでカスタム RSS 機能を使用する場合、DPDK バージョンは 21.11 以降である必要があります。

有効化または無効化の方法

カスタム RSS は デフォルトで無効 です。ENI の作成時、または ENI の作成後に有効化または無効化できます。

  • CreateNetworkInterface API を呼び出す際に、 EnhancedNetworkEnableRss を true または false に設定します。

    カスタム RSS は、ENI がインスタンスにアタッチされた後に有効になります。

  • ModifyNetworkInterfaceAttribute API を呼び出して、 EnhancedNetworkEnableRss を true または false に設定します。 有効化または無効化 した後、変更は ENI が 再アタッチ されたときに有効になります:

  • カスタム RSS が有効かどうかを照会するには、 AttributeenhancedNetwork に設定して DescribeNetworkInterfaceAttribute API を呼び出します。 EnableRSS=true は有効、false は無効を示します。

    ENI の RSS が変更されていない場合、この API は EnableRSS パラメータを返しません。この場合、機能は有効になっていません。

ENI の RSS 設定の確認

カスタム RSS を有効にすると、ENI のリロード時に、ENI ドライバーはデフォルトのメカニズムとルールに基づいてデフォルトの RSS 設定を生成します。

Linux インスタンスにリモートでログオンし、ethtool -x eth0 を実行してプライマリ ENI の RSS 設定を表示できます。 セカンダリ ENI の場合、インターフェイス識別子 (eth1、eth2 など) を置き換えます。

image

  • ハッシュテーブル (RX フローハッシュインダイレクションテーブル):ハッシュ値から 64 個の受信キューへのマッピングルールを定義します。

    • ハッシュテーブルの各行は、開始インデックスで示されるハッシュ値の範囲を表します。たとえば、0 はハッシュ値 0~7 を示し、8 は 8~15 を示します。

    • テーブル内の数字 (0、1、2 など) は、対応する受信キューの ID を表します。この例では、64 個のキュー (0~63) があります。

    • 現在の構成: インダイレクションテーブルは、シーケンス 0,1,2,...,63,0,1,2...63 で周期的に埋められます。これにより、ハッシュ値が 64 個のキューに均等にマッピングされ、トラフィックが均等に分散されることが保証されます。

  • RSS ハッシュキー:ハッシュ値を計算するために使用するキーです。16 進数の文字は 40 バイトのキーを表します。

  • RSS ハッシュ関数:ハッシュ値を計算するためのアルゴリズムを定義します。

    • toeplitz:デフォルトのアルゴリズムであり、現在有効です。5 タプルに基づく対称ハッシュをサポートしており、汎用トラフィックに適しています。

    • xor/crc32: これらは、通常、特定のシナリオで使用されるその他のオプションのアルゴリズムです。 ECS は現在、toeplitz アルゴリズムのみをサポートしており、その他のアルゴリズムはサポートされていません。

  • ハッシュルール:プライマリ ENI が受信するトラフィックのハッシュフィールド設定の表示

    ethtool -n eth0 rx-flow-hash tcp4
    • tcp4 を、udp4tcp6udp6 など、クエリしたいプロトコルタイプに置き換えることができます。

    • ECS ENI RSS の デフォルト のハッシュルールは次のとおりです。これらのルールは変更できません。

      • IPv4/IPv6 における TCP および UDP トラフィック:4 タプル (送信元 IP、宛先 IP、送信元ポート、宛先ポート) とハッシュキーに基づいてハッシュ値が計算されます。

      • ICMP などの IPv4/IPv6 における非 TCP/UDP トラフィック:2 タプル (送信元 IP と宛先 IP) とハッシュキーに基づいてハッシュ値が計算されます。

      • 非 IP メッセージ (ARP など) はデフォルトキュー (キュー 0) に送信され、ハッシュ化されません。

    • tcp4 を例に取ると、IPv4 TCP トラフィックの場合、ハッシュ値はデフォルトで 4 タプル (送信元 IP、宛先 IP アドレス、送信元ポート、宛先ポート) とハッシュキーに基づいて計算されます。

      image

      予想されるトラフィックの特性は次のとおりです:

      • 同じ TCP 接続:すべてのパケットが同じハッシュ値を持ち、同じキューに分散されます。これにより、パケットの順序が保証されます。

      • 異なる TCP 接続:4 タプルが異なれば、ハッシュ値も異なります。トラフィックは異なるキューに分散されます。これにより、複数の CPU コアが異なる接続を並列処理でき、スループットが向上します。

次のメッセージが返された場合、カスタム RSS は 有効になっていない か、インスタンスタイプがそれを サポートしていませんインスタンスタイプがサポートしていることを確認した後、カスタム RSS を有効にしてください

image

ENI のカスタム RSS の設定

デフォルトの RSS 設定はほとんどの要件を満たします。次のような状況では、ハッシュキーまたは間接参照テーブルの調整が必要な場合があります。

  • 不均一なトラフィック:デフォルトのハッシュ値により、特定のトラフィックが少数のキューに集中する可能性があります。

  • パフォーマンスの最適化:UDP の割合が高い場合など、トラフィックの特性に基づいてハッシュルールを調整し、キューの使用率を向上させます。

  • セキュリティ要件:カスタムハッシュキーを使用して、攻撃者がトラフィック分散を予測できないようにします。

重要
  • この例では、インスタンスは Alibaba Cloud Linux 3.2104 LTS 64-bit イメージで構成されています。

  • この例では、プライマリ ENI eth0 を使用しています。対応するセカンダリ ENI の RSS を変更する場合は、インターフェイス識別子を eth1 または eth2 に置き換えてください。

  • RSS 設定がトラフィックの特性と一致しない場合、不均一な分散やコア間の状態競合が発生し、パフォーマンスに影響を与える可能性があります。実際のトラフィック特性に基づいて調整し、監視ツールを使用してキューの分散をリアルタイムで観察してください。

ハッシュキーの設定

トラフィックが不均一に分散されている場合 (たとえば、一部のキューの rx_packets が他のキューよりも著しく多い場合)、または攻撃者がハッシュ値をリバースエンジニアリングしてトラフィックパターンを推測するのを防ぐために、RSS ハッシュキーを再生成します。

重要

キーを変更すると、既存の接続のハッシュ値が変更され、一時的なパケットリオーダリングまたは再送が発生する可能性があります。オフピーク時に実行してください。

  1. ENI の現在のハッシュキーを表示します。

    ENI ドライバーは、ENI の読み込み時に 40 バイトのデフォルトハッシュキーを生成します。

    ethtool -x eth0

    image

  2. OpenSSL を使用して新しいランダムキーを生成します。

    openssl rand -hex 40 | fold -w2 | paste -sd: -
  3. 新しいキーを ENI に適用します。

    重要

    これは一時的な設定です。インスタンスの再起動または ENI の再アタッチ後は無効になります。ENI ドライバーは、ランダムなデフォルト設定を自動的に初期化します。

    sudo ethtool -X eth0 hkey <hash key>

    <hash key> を、前の手順で生成した新しいキーに置き換えてください。

  4. 新しいキーが有効になったことを確認します。

    ethtool -x eth0

    出力には、新しいキーが有効になったことが示されます。

    image

間接参照テーブルの設定

RSS 間接参照テーブルの分散モードは、ハッシュ値を特定の受信キューにマッピングする中核となるメカニズムです。事前定義された間接参照テーブルを使用して、ハッシュ計算の結果がキューインデックスに変換されます。これにより、複数のコアにわたる柔軟なトラフィック分散が可能になります。

一様分布や重み付き分布などの異なる分散モードは、トラフィックが CPU コア全体にどのように分散されるかに直接影響します。これにより、システムのスループット、レイテンシー、リソース使用率、およびサービスのパフォーマンスに影響を与えます。

シナリオに基づいて分散モードを選択してください。

重要

以下の設定は一時的なものです。インスタンスの再起動または ENI の再アタッチ後は無効になります。ENI ドライバーは、ランダムなデフォルト設定を再初期化します。

  • 一様分布モード:最初の N 個のキューがハッシュバケットをループで埋めます。一般的な高並行性 シナリオに適しています。

    sudo ethtool -X eth0 equal <Number of queues N>

    値が 64 に設定されている場合、間接参照テーブルは 0、1、2、...、63、0、1、2、...、63 でループ状に埋められます。

    image

  • 重み付き分布:重み比率に基づいてハッシュバケットを割り当てます。差別化されたビジネス優先度 のシナリオ (たとえば、キュー 0 がリアルタイムトラフィックを処理し、キュー 1 がバックグラウンドタスクを処理する場合) や、混合 CPU パフォーマンスに適しています。

    ethtool -X eth0 weight <queue 0 weight> <queue 1 weight> ...

    次の例では、キュー 0 に 60%、キュー 1 に 40% が割り当てられます。

    説明

    合計で 4 つのキューがある場合でも、2 つのキューにのみ重みを設定した場合、間接参照テーブルはキュー 0 とキュー 1 のマッピングのみを生成します。

    sudo ethtool -X eth0 weight 6 4

    image

  • 部分キュー分散:指定されたキューから開始して、連続するキューを使用してハッシュバケットをループで埋めます。NUMA ノードなど、特定の CPU 範囲にトラフィックを誘導する 場合に適しています。

    ethtool -X eth0 start <start queue> equal <number of queues>

    次の例では、キュー 2~41 (キュー 2 から開始する 40 個のキュー) がハッシュバケットを埋めます。

    sudo ethtool -X eth0 start 2 equal 40

    image

watch コマンドを使用したトラフィック分散の観察

ENI でカスタム RSS を有効にした後、hping3 でさまざまな特性を持つトラフィックを生成し、watch でキューの割り込み分散を監視することで、トラフィックが期待どおりに複数のコアに分散されているかどうかを確認します。

前提条件

次の構成で 2 つの Elastic Compute Service (ECS) インスタンスを用意します。

  • 送信側 ECS インスタンス (10.0.0.252): さまざまな特性を持つトラフィックを生成するためにhping3 がインストールされたインスタンス。

  • 受信側 ECS インスタンスENI のマルチキューをサポートするインスタンスタイプで、セカンダリ ENI がアタッチされ (10.0.0.5) 、セカンダリ ENI eth1 でカスタム RSS が有効化されています。

    説明
    • この例では、テスト用に 4 つのキューを使用します。

    • プライマリ ENI への SSH ログインによるトラフィックの影響を避けるため、テストはセカンダリ ENI eth1 でカスタム RSS を有効にして実施します。

  • ネットワーク接続: 2 つの ECS インスタンスは同じセキュリティグループ内にあり、内部ネットワーク経由で相互に通信できます。

操作手順

  1. 受信側 ECS インスタンスにログインし、セカンダリ ENI の RSS 設定と、期待されるトラフィック分散を確認します。詳細については、「ENI の RSS 設定の確認」をご参照ください。

    image

  2. 送信側 ECS インスタンスにログインし、hping3 をインストールします。

    yum install -y hping3
  3. 受信側 ECS インスタンスで、キューのパケット数をリアルタイムで監視します。

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

    キュー番号の設定を、受信側 ENI の実際の設定に置き換えてください。

  4. 送信側 ECS インスタンスにログインし、さまざまなトラフィックパターンをシミュレートします。

    • シナリオ 1ランダムな宛先 IP アドレスを使用して、受信側 IP アドレスに 10,000 個の 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: ランダムな宛先 IP アドレス

      • -p 0rand-dest と併用

      • --baseport 10000: 送信元ポートの開始値。

      • -c 10000: 10,000 個のパケットを送信

      • -i u100: 高速送信のため、パケット間の間隔を 100 マイクロ秒に指定します。

      • -I eth0rand-dest と併用して、送信元のネットワークインターフェースを指定

      受信側インスタンスでは、4 つのキューのパケット数が均等に増加します。

      image

    • シナリオ 2送信元 IP アドレスとポートを固定してトラフィックを送信します。これにより、同じトラフィック が同じキューにマッピングされるかをテストし、ハッシュの一貫性を検証します。

      次のコマンドは、送信元 IP を10.0.0.252、送信元ポートを12345 に固定し、ターゲット10.0.0.5:80 に 10,000 個の TCP SYN パケットを送信します。

      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 の設定と使用

Data Plane Development Kit (DPDK) は、ユーザーモードドライバー、ゼロコピー、ポーリングモードを使用してほぼライン速度のパケット処理を実現する、オープンソースのユーザーモードデータプレーン高速化フレームワークです。通信クラウド、金融テクノロジー、エッジコンピューティングなど、スループットとレイテンシーが重視される領域に適しています。詳細については、「Data Plane Development Kit (DPDK*)」をご参照ください。

Elastic Network Interface (ENI) でカスタム受信側スケーリング (RSS) を有効にした後、DPDK の testpmdl3fwd を使用して、RSS 分散をテストおよび検証できます。

ECS インスタンスへの DPDK のインストールと設定

説明
  • DPDK アプリケーションでカスタム RSS 機能を使用する場合、DPDK バージョンは 21.11 以降である必要があります。

  • このトピックでは、Alibaba Cloud Linux 3.2104 LTS 64-bit イメージを実行する ecs.r9i.16xlarge インスタンス (64 キュー) を使用して、DPDK バージョン 22.11.3 のインストールを例として説明します。

  • この例では、プライマリ ENI eth0 を使用しています。対応するセカンダリ ENI の RSS を変更する場合は、インターフェイス識別子を eth1 または eth2 に置き換えてください。

ステップ1: 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 はカーネルプロトコルスタックをバイパスして ENI を直接操作します。TLB ミスを減らし、メモリアクセス速度を向上させるために、4 KB ページの代わりにヒュージページ (通常 2 MB) を使用します。

    • ヒュージページを過度に割り当てると、OS で使用可能な通常のメモリが減少します。これにより、他のアプリケーションやシステムサービスが失敗する可能性があります。過度な割り当ては、インスタンスの接続を妨げる可能性もあります。

    • アプリケーションのメモリ要件に基づいて、必要なヒュージページ数を計算します。

      ヒュージページ数 = アプリケーションメモリ / ページサイズ。デフォルトのページサイズは 2 MB です。たとえば、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 をコンパイルします。

    # ビルドディレクトリを初期化し、プロジェクトオプションを設定して、l3fwd レイヤー 3 転送のサンプルをビルドするように指定します。
    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

ステップ2: カーネルモジュールの読み込み

DPDK は、ユーザーモードデバイスアクセスのために UIO や Virtual Function I/O (VFIO) などのカーネルモジュールを必要とします。VFIO はセキュリティ (Input-Output Memory Management Unit (IOMMU) に依存するため) の観点から推奨されますが、UIO は迅速なテストに適しています。この例では VFIO を使用します。

  1. IOMMU を有効にします。

    VFIO は、安全なユーザーモードデバイスバインディングと DMA マッピングのために IOMMU に依存しています。

    1. ファイルを開きます。

      sudo vim /etc/default/grub
    2. i を押して挿入モードに入ります。intel_iommu=onGRUB_CMDLINE_LINUX パラメーターに追加します。ファイルを保存して閉じます。

      変更後の設定例: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'

ステップ3: ENI を DPDK ドライバーにバインド

  1. ENI でカスタム RSS を有効にします

    DPDK が引き継ぐ ENI でカスタム RSS が有効になっており、変更を有効にするために ENI が再アタッチされていることを確認してください。

  2. VNC を使用してインスタンスに接続します

    • この例ではプライマリ ENI を使用します。後続の操作で ENI がデタッチされるため、SSH セッションが中断されます。

    • セカンダリ ENI を操作する場合は、代わりにWorkbench (プライマリ ENI) を使用して接続してください。

  3. PCI デバイスドライバーのバインディング状態を表示します。デフォルトでは、ENI はカーネルによって管理されています。

    dpdk-devbind.py --status

    image

    • デバイス 0000:00:05.0virtio-pci カーネルドライバーによって管理されており、eth0 はアクティブです。

    • デバイスは、DPDK ユーザーモードのテイクオーバー用に vfio-pci に切り替えることができます。まず、インターフェイスを無効にし、カーネルドライバーをデタッチします。

  4. eth0 を非アクティブ化します。

    sudo ip link set dev eth0 down

    この手順をスキップすると、VFIO へのバインディングが失敗します。

    image

  5. カーネルドライバーをアンバインドし、VFIO にバインドします。

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

    PCI デバイス識別子を、お使いの ENI について照会した値に置き換えてください。

    説明

    カーネルドライバーを再バインドするには、sudo pkill dpdk-app で DPDK アプリケーションを停止し、dpdk-devbind.py -b virtio-pci 0000:00:05.0 を実行します。

  6. PCI デバイスドライバーのバインディング状態を再度表示します。ENI は DPDK によって引き継がれているはずです。

    重要

    ENI が DPDK ユーザーモードドライバーに引き継がれると、カーネルはそれを制御しなくなります。ip a などのコマンドでは、その情報を表示できません。

    dpdk-devbind.py --status

    image

    デバイス 0000:00:05.0vfio-pci にバインドされました。

testpmd を使用した RSS の設定

Testpmd は、ENI ドライバー機能の検証とデータプレーンアプリケーションのデバッグのための DPDK テストツールです。詳細については、「Testpmd Runtime Functions」をご参照ください。

重要

ENI 設定 (キュー数、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 の ENI を DPDK にアタッチします。 PCI アドレスを検索するには、dpdk-devbind.py --status を実行します。

    • --socket-mem 1024: NUMA ノードごとに 1024 MB のヒュージページメモリを事前割り当てします。

    • -i: 対話型コマンドラインモードを開始し、動的に設定を調整したり、統計情報を表示したりできるようにします。quit と入力して終了します。

    • --portmask=0x1:ポートマスク 0x1 (2進数 0001) を有効にし、最初の ENI (PCI アドレス 0000:00:05.0) のみを使用します。

      • portmask の各バイナリビットはポートに対応します (0x1 はポート 0 を有効にし、0x3 はポート 0 と 1 を有効にします)。

      • デバイス (0000:00:05.0) が 1 つだけアタッチされているため、その DPDK ポート番号は 0 です。

    • --rxq=64 / --txq=64: マルチキュー処理のために、ポートごとの RX および TX キュー数を 64 に設定します。64 を実際の ENI キュー数に置き換えます。

    • --forward-mode=rxonly:受信専用モード(パケットは受信後に破棄されます)。ENI の受信パフォーマンスのテスト、またはパケットキャプチャに使用されます。

  3. 対話モードに入り、現在の RSS 設定を照会します。

    • クエリ ハッシュ設定情報: show port info <port_id>

      port_id: ポート。この例では、ポート 0 です。

      クリックして出力例を表示

      image

    • クエリハッシュキーshow port <port_id> rss-hash key

      image

    • 間接テーブル構成の照会: show port <port_id> rss reta <size> <mask0, mask1...>

      • size: クエリするインダイレクションテーブルのエントリー数。値は 128 に固定されています。

      • mask0, mask1:表示するハッシュインデックスの範囲を絞り込むためのマスクで、16 進数形式で指定されます。 例えば、mask0=0xff は、ハッシュインデックス 0 から 7 のエントリが表示されることを示します。

        間接テーブルのサイズは 128 に固定されています。2 つのマスクが必要で、それぞれが 64 個のインデックスブロックをカバーします。128 個すべてのインデックスとキューのマッピングを返すには、show port 0 rss reta 128 (0xffffffffffffffff,0xffffffffffffffff) を実行します:

        クリックして出力例を表示

        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)>)

      IPv4 経由の TCP の場合、port config 0 rss-hash-key ipv4 6D5A56DA255B0EC24167253D43A38FB0D0CA2BCBAE7B30B477CB2DA38030F20C6A42B73BBEAC01FC を実行してハッシュキーを設定し、検証します:

      image

    • ハッシュ値を指定されたキューにマッピングするために、RSS インダイレクションテーブルを設定します。

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

      実際のキュー数に基づいて設定してください。

      • hash: ハッシュインデックス。範囲はインダイレクションテーブルのサイズによって決まります (たとえば、64 エントリの場合は 0~63)。

      • queue: 対象の受信キュー番号。

l3fwd での RSS の適用

DPDK アプリケーションで RSS を有効にするには、コードにハッシュキーとインダイレクションテーブルの設定を実装します。以下では、L3FWD を例として使用します。

L3FWD (Layer 3 Forwarding) は、ゼロコピーとポーリングモードドライバー (PMD) を使用した高性能 IP ベースのパケットルーティングを実証する DPDK サンプルアプリケーションです。

  1. DPDK の L3FWD ソースコード(examples/l3fwd/main.c)を変更します。

    • L3FWD サンプルコードのポート初期化セクション static struct rte_eth_conf port_conf修正してください。

      クリックしてコードの変更を表示

      // RSS ハッシュキーの長さ
      #define RSS_HASH_KEY_LENGTH 	40
      // RSS リダイレクションテーブルのサイズ
      #define RSS_RETA_SIZE 			128
      
      // 対称的な RSS ハッシュを実現するための静的ハッシュキー
      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, // 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, // IP ヘッダーに基づいてハッシュを計算
      			.rss_key_len = RSS_HASH_KEY_LENGTH, // ハッシュキーの長さ
      		},
      	},
      	.txmode = {
      		.mq_mode = RTE_ETH_MQ_TX_NONE,
      	},
      };
    • ハッシュインダイレクションテーブルとハッシュキーを設定する関数を追加します。

      クリックして追加されたコードを表示

      /**
       * @brief 指定されたポートの RSS RETA (リダイレクションテーブル) を設定します。
       * 
       * @param port_id 設定するポートの ID。
       */
      
      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;
      	}
      
          // RETA テーブルを初期化します
          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);
          }
      
          // RETA テーブルマスクを設定します
          for (i = 0; i < reta_size; i += RTE_ETH_RETA_GROUP_SIZE) {
              reta_conf[i / RTE_ETH_RETA_GROUP_SIZE].mask = UINT64_MAX;
          }
      
      	// RSS RETA テーブルをデバイスに更新します
      	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 指定されたポートの RSS ハッシュキーを設定します。
       * 
       * @param port_id 設定するポートの ID。
       */
      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);
          
          // RSS ハッシュキーをデバイスに更新します
          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)
                      /* デバイスを起動します */
      		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
    # ビルドディレクトリを初期化し、プロジェクトオプションを設定して、l3fwd レイヤー 3 転送のサンプルをビルドするように指定します。
    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: 各 3 つ組は、以下を指定します。

      • PORT_ID: ポート ID (0 から開始)。

      • QUEUE_ID: 受信キュー ID (0 から開始)。

      • LCORE_ID: 論理コア ID (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
      • 最初の 3つ組 (0,0,0) では、論理コア 0 (lcore0) がポート 0 のキュー 0 を処理します。

      • 2つ目の 3つ組 (0,1,1):論理コア 1 (lcore1) がポート 0 のキュー 1 を処理します。

      image

RSS スクリプトによるハッシュインデックスの計算

この Python スクリプトを使用して、4 タプルとハッシュキーを指定し、パケットがマッピングされる受信キューを計算します。この計算結果は、特定のフローを指定のキューに誘導するためのインダイレクションテーブル設定のガイドとなります。

クリックして ali_ecs_rss_calc.py スクリプトの内容を表示

#!/usr/bin/python

import sys
import argparse
import re
import ipaddress

prog_name = sys.argv[0]
USAGE_EXAMPLE = """
使用例:
    1.2.3.4 から 1.2.3.5 に送信されるパケット (送信元ポートと宛先ポートはともに 7000) の Toeplitz ハッシュを計算します。

    - virtio-net ドライバーはすべての NIC に対してランダムなハッシュキーを作成するため、
      ハッシュキー引数が必要です。

    $ {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
    
    - TCP または UDP 以外のプロトコルのパケットの RSS 値を計算する場合は、送信元ポートと
      宛先ポートを指定しないでください。スクリプトは IP アドレスのみに基づいてハッシュ値を計算します。
      
    $ {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
    
    - IPv6 パケットの RSS 値を計算するには、「--ipv6」を使用します。
    
    $ {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

    注意:ハッシュ関数とキー設定をサポートするには、Linux カーネル 5.9 以降が必要です。
    
    また、ANCK 5.10-018 より古い Linux カーネルには、RSS 設定を手動で変更するまで、ethtool で表示される
    デフォルトのハッシュキーがデバイスで使用されているキーと一致しないというバグがありました。
    ハッシュキーを指定しない場合、このスクリプトは古い Alinux カーネル (< ANCK 5.10-018) 用のデフォルトキーを使用して計算します。これは、ethtool で表示されるキーが正しくないことがある特定の古いカーネルのバグに対応するためです。
""".format(prog_name = prog_name)

# 古い alinux カーネル (< ANCK 5.10-018) のインスタンスのデフォルトキーは以下のとおりです。"ethtool -x " から取得したものではありません。
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,
]
# 新しい alinux カーネル (>= ANCK 5.10-018) のインスタンスのデフォルトキーはランダムに生成されます。

TOEPLITZ_KEY_SIZE = 40
BITS_IN_BYTE = 8

def circular_shift_key_one_left(key):
    """キー全体を循環左シフトします。
    40 バイトすべてを循環シフトするため、この関数は
    隣接するバイト間のビットを一度に 1 つずつシフトします。"""

    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):
    """指定されたパラメータに基づいて Toeplitz ハッシュを計算します。
    注意:この実装は ENA に固有であり、
    標準的な Toeplitz 実装と互換性がない場合があります。"""

    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):
            # (8 - i - 1) ビットが設定されているか
            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):
    """IPv4 文字列を整数のリストに変換する argparse の型関数。"""
    if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", str):
        raise argparse.ArgumentTypeError("IP アドレスは 1.2.3.4 の形式である必要があります。")

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

def ipv6_addr_type(str):
    """IPv6 文字列を整数のリストに変換する argparse の型関数。"""
    
    try:
        ip_str = ipaddress.IPv6Address(str)
    except ValueError as e:
        raise argparse.ArgumentTypeError("無効な IPv6 アドレス形式: %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("無効な IPv6 アドレス形式: %s" % e)
    
    return [item for sublist in bytes_list for item in sublist]

def toeplitz_key_type(str):
    """Toeplitz キー文字列を 16 進数値のリストに変換する argparse の型関数。"""
    if not re.match(r"^([0-9a-zA-Z]{1,2}:){39}[0-9a-zA-Z]{1,2}$", str):
        raise argparse.ArgumentTypeError("Toeplitz キーの形式が無効です。コロンで区切られた 40 個の 16 進数値にする必要があります。")

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

def main():

    parser = argparse.ArgumentParser(description='virtio-net Toeplitz ハッシュ計算ツール',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog=USAGE_EXAMPLE)

    parser.add_argument('-r', '--rx-ip', help='受信側 IP', dest='rx_ip', nargs='?',
                        required=True, type=str)
    parser.add_argument('-R', '--rx-port', help='受信側ポート', dest='rx_port', nargs='?', type=int)
    parser.add_argument('-t', '--tx-ip', help='送信側 IP', dest='tx_ip', nargs='?', 
                        required=True, type=str)
    parser.add_argument('-T', '--tx-port', help='送信側ポート', dest='tx_port', nargs='?', type=int)
    parser.add_argument('-k', '--toeplitz-key',
                        help='Toeplitz キー (キー変更をサポートするインスタンスのみ)',
                        dest='toeplitz_key', nargs='?', required=False, type=toeplitz_key_type)
    parser.add_argument('-i', '--ipv6',  action='store_true', help='IP 引数に IPv6 アドレスタイプを使用')

    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:
        # ポート番号を2バイト表現に分割
        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
    if key is None:
        # ハッシュキーが指定されていない場合、古い Alinux カーネル (< ANCK 5.10-018) 用のデフォルトキーを使用します。
        # これは、ethtool で表示されるキーが正しくないことがある特定の古いカーネルのバグに対応するためです。
        print("警告: --toeplitz-key が指定されていません。古い Alinux カーネル (< ANCK 5.10-018) 用のデフォルトキーを使用します。", file=sys.stderr)
        key = RSS_DEFAULT_KEY

    # 初期値0でハッシュを計算
    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("[{}]:{} から [{}]:{} へトラフィックを送信".format(args.tx_ip, args.tx_port, args.rx_ip, args.rx_port))
    else:
        print("{}:{} から {}:{} へトラフィックを送信".format(args.tx_ip, args.tx_port, args.rx_ip, args.rx_port))
    print("""ハッシュは次のフィールドから計算します:
    送信元 IP アドレス
    宛先 IP アドレス""")
    if args.rx_port and args.tx_port:
        print("""    送信元ポート
    宛先ポート""")
    print("すべてのドライバーで期待されるハッシュ値:".ljust(50) + "{}".format(hex(hash)))
    print("RSS テーブルエントリ (テーブル長: 128):".ljust(50) + "{}".format(rss_table_entry_128))
    print("RSS テーブルエントリ (テーブル長: 256):".ljust(50) + "{}".format(rss_table_entry_256))
    return

if __name__ == '__main__':
    main()

クリックしてスクリプトの使用方法を表示

スクリプトの使用方法を表示: python ali_ecs_rss_calc.py -h

image

次の例では、キュー数が 64 の ENI の RSS 設定を使用します。

image

次の 5 タプル情報を使用し、スクリプトで RSS ハッシュ値を計算します。値は必要に応じて変更してください。

  • 宛先 IP アドレス (-r):10.0.0.1。これは、RSS が設定された受信側インスタンスの ENI の IP アドレスです。

  • 送信元 IP アドレス (-t):10.0.0.251。これは、送信側の ENI の 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 の場合、パケットはインデックス 117 のキューにマップされます。

image

インダイレクションテーブルを見ると、インデックス 117 はキュー 53 にマッピングされているため、パケットはキュー 53 で処理されます。

image