かーねるさんとか

発言は個人の見解であり、所属する組織の公式見解ではありません。

高速パケット I/O フレームワーク netmap 使ってみる

netmap の使い方について、学術的なコンセプトと一緒にまとめました。
インストールの方法から、基本となるパケット生成アプリケーションである、pkt-gen の使い方までについて書きました。

netmap とは?

ピサ大学の研究グループ*1が開発しているパケット I/O フレームワークです。

何ができるのか?

高速にパケット I/O を行うアプリケーションを作ることができます。

詳細は著名な国際学会の一つ USENIX ATC'12 で発表された論文*2に記載されていますが、 ひとつの主な想定アプリケーションはネットワーク機能の仮想化( NFV : Network Function Virtualization )です。

NFV はネットワーク機能を汎用なハードウェアで実装するコンセプトです。これまで専用ハードウェアとして実装されていた、例えばファイアーウォールやルーターなどを汎用サーバーの上で実行可能なソフトウェアとして実装することによって、一つの汎用ハードウェアを複数用途で利用することが可能になり、導入、運用のコストを低下させる目的で利用できると言われています。

netmap が解決している問題

LinuxFreeBSD 等の汎用 OS を用いて、NFV アプリケーションを実装する際には、アプリケーションからのネットワーク通信(パケット I/O)が遅いという問題がありました。具体的には、アプリケーションはパケットの送受信に際して、socket のファイルデスクリプタに read( )/write( ) のシステムコールを実行する必要があり、この通信に関する API が遅い*3ということや、ペイロードのアプリケーションからカーネル空間へのコピー、パケットバッファの確保等の処理に時間がかかってしまうということが挙げられます。

つまりは、汎用 OS の socket API を使って NFV アプリケーションを作ろうとすると高い性能が発揮できないという問題がありました。

netmap で採用されている高速化手法

基本的なアイデアは、カーネル空間にあるパケットバッファを、直接、アプリケーションのメモリ空間にマップすることです。

カーネル空間のパケットバッファは、ネットワークカード ( NIC ) が直接参照しており、送信に際しては、アプリケーションはマップされたパケットバッファへ、ペイロードを書き込むだけでよく、あとは NIC のドライバが転送開始の命令を送るとパケットが送信されます。netmap はアプリケーションから NIC ドライバの転送開始命令までの処理へたどり着くための高速なパスを実装しており、それがこれまでにパケットが通らなければならなかったカーネル内部の処理( UDP パケット処理等 ) を迂回することになるので、”カーネルバイパス”という技術の一つとして位置づけられています。(アカデミアの領域では、カーネルバイパスネットワーキングに関するワークショップ*4が SIGCOMM'17 の併設で開催されたりとホットなテーマの一つのようです。)

受信側についても同じく、パケットバッファがアプリケーションのメモリ空間にマップされているため、受信パケットが NIC に辿り着いた時には、既にアプリケーションから受信ペイロードが見えるようになっています。ですが、アプリケーションは、マップされたパケットバッファのどこに新しいデータが着いたのかを知らないため、受信場所についての情報はカーネル側から提供してもらう必要があります。netmap の API はこの受信場所を効率良くアプリケーションへ伝えるための実装がなされています。

netmap の実装

netmap はカーネルモジュールとして実装されており、 FreeBSD にマージされています。Linux では、ソースコードをダウンロードしてインストールする必要があります。

試してみる

今回は、Linux を使ってサンプルを動かしてみます。
手元では、仮想マシン上の Ubuntu 17.04 を使いました。カーネルのバージョンはデフォルトのまま 4.10.0-19 です。

下準備

Ubuntu 等をインストールした状態では、カーネルモジュールのビルドに必要なヘッダファイル等が不足しており、ビルドに失敗してしまうことがあります。
以下のようなパッケージをインストールすることで、ビルドが成功するはずです。
カーネルのバージョンは環境に合わせてください。

$ apt-get install linux-headers-4.10.0-19
ソースコードのダウンロード
$ git clone https://github.com/luigirizzo/netmap.git
ビルド
$ cd netmap
$ make

上記のコマンドを実行すると、対応可能な NIC のドライバのソースコードをダウンロードして、必要なパッチを当てた上で、netmap 自体のカーネルモジュールをビルドしてくれます。

netmap は NIC のドライバのパケット入出力周辺に変更が必要なため、デフォルトのドライバにパッチをあてて、そのドライバを netmap のカーネルモジュールと一緒にインストールする必要があります。

コマンド実行完了後、netmap.ko というファイルが生成されていれば成功です。

モジュールのインストール
insmod netmap.ko

NIC に対して netmap を有効にできるようにするためには、パッチのあたった NIC ドライバのモジュールをロードしなおす必要があります。
パッチのあたったモジュールは netmap のディレクトリの内部に見つかるはずです。

今回は、NIC は使わずに、仮想ポートを使ってサンプルプログラムを動かします。

注意事項

netmap を使うと、輻輳制御等関係なくパケットをバーストできます。
結果、学校や会社のネットワーク機器に多大な負荷をかけ、障害を起こすことができる可能性があります。インターネットとの疎通のために利用しているインターフェースに対しては netmap を使わない方が良いかもしれません。
例えば、1Gbps NIC e1000 でインターネットにつないでおり、10 Gbps NIC ixgbe をテストに使う場合などのように、インターネットと繋がっているインターフェースの NIC のドライバがテスト用 NIC のドライバと異なる場合は、e1000 ドライバを netmap のパッチが当たったものと交換しないことで、意図しないパケットの放出を回避できると思います。

pkt-gen アプリケーションを試す

pkt-gen アプリケーションは netmap を使ってパケットを送受信できるプログラムです。

今回は NIC からのパケット転送ではなく、仮想ポートを使ったパケット送信を試します。
仮想ポートは、netmap では、netmap API の上で動作する VALE スイッチに接続される VALE ポートとなります。
VALE スイッチはとても高速なスイッチで、カーネル内部でパケットをフォワードしてくれます。
VALE の詳細については、国際学会 CoNEXT'12 で発表された論文*5に書かれています。

アプリケーションをビルドします。

$ cd apps/pkt-gen
$ make

受信側アプリケーションを実行します。
ターミナルで以下のコマンドを実行してください。

$ ./pkt-gen -i vale0:rx -f rx

送信側アプリケーションを実行します。
新しくターミナルを開いて、以下のコマンドを実行してください。
秒間に1パケットずつ、送信側から受信側へ送信されます。

$ ./pkt-gen -i vale0:tx -f tx -R 1

以下のような表示が受信側で見られたら成功です。

218.693791 main_thread [2494] 1.000 pps (1.000 pkts 480.000 bps in 1001100 usec) 1.00 avg_batch 1023 min_space
219.695181 main_thread [2494] 1.000 pps (1.000 pkts 480.000 bps in 1001390 usec) 1.00 avg_batch 1023 min_space
220.695994 main_thread [2494] 1.000 pps (1.000 pkts 480.000 bps in 1000814 usec) 1.00 avg_batch 1023 min_space

簡単にオプションの説明をします。

-i vale0:rx

-i では、pkt-gen のアプリケーションで利用するポートを指定します。
表記は "仮想スイッチ名":"仮想ポート名" となり、重要なのは、VALE スイッチに接続する場合は、必ず仮想スイッチ名は vale で始まる必要があります。
valex, valey, valez は問題ありませんが、valx, valy, valz はアプリケーションの起動に失敗します。
これは netmap の内部実装に起因します。

-f rx

-f では、機能を指定します。tx, rx で転送、受信を指定できます。
ping, pong 等も実装されており、遅延の評価ができます。

-R 1

-R では、秒間の転送パケット数を指定できます。
1を指定したので、1秒に1パケットが送られたはずです。
このオプションを指定しない場合には、できるだけ高速にパケットが送信されます。

もう一つ、今回は試していませんが、NIC に対して、同じく pkt-gen のアプリケーションでパケットの送受信をしたい場合は、この -i オプションで netmap:"物理 NIC 名" のように指定します。物理 NIC 名は eth0 のような ifconfig で確認できるものです。

eth0 から pkt-gen でデータを受信したい場合は、以下のようになるはずです。(パッチのあたった NIC のドライバをロードしたのち試してみてください。)

$ ./pkt-gen -i netmap:eth0 -f rx

最後に、-R オプションを外して送受信を行ってみてください。
十数 million packet / sec の性能がでるはずです。

pkt-gen のアプリケーションはパケットの長さ、送受信のMACアドレスの指定等、多くのオプションをサポートしているので、ヘルプをみて他にも是非試してみてください。