Linuxカーネルの新機能 XDP (eXpress Data Path) を触ってみる

先日netdev 1.2に参加してみたところ,XDP(eXpress Data Path)の話題で持ち切りといった感じだった. というわけで,XDPについて一通り調べつつ,実際に触ってみた.

XDPとは何か?

誤解を恐れずに一言で言うと,「Intel DPDKのような高速パケット処理基盤をLinuxカーネル自身が用意したもの」であると理解している.このスライドでは

A programmable, high performance, specialized application, packet processor in the Linux networking data path

と言っている.

DPDKはユーザランドアプリケーションがNICを直接叩く(=カーネルのネットワークスタックをバイパスする)ことで高速処理を実現している.一方XDPは,カーネル内の最もNICドライバに近い場所でフックしてeBPFを実行することによりパケット処理を実現する.

f:id:yunazuno:20161010181639p:plain (iovisor.orgより)

eBPFはカーネル内でプログラムを実行するための汎用的な仕組みで,パフォーマンス計測やセキュリティ方面で使用されている,らしい.これまでもtc-bpf(8)のようにeBPFでパケット処理を行う仕組み自体は用意されていたが,XDPによってより早い段階でパケット処理を行えるようになった.

XDPの概要についてはiovisor.orgの説明ページや冒頭で触れたスライドが詳しい.

XDP | IO Visor Project

bpf-docs/Express_Data_Path.pdf at master · iovisor/bpf-docs · GitHub

パフォーマンス

パフォーマンスは現時点において,受信したパケットを全てドロップするケースで20Mpps, L3フォワーディングでも10Mpps程度を達成するとされている.

youtu.be (6:46あたりから)

Next steps for Linux Network stack approaching 100Gbit/s by Jesper Dangaard Brouer @ Netfilter Workshop, June 27, 2016

動作環境

XDPを利用するには,カーネル自体に加えてNICドライバでもサポートが必要となる. 先日リリースされた4.8でXDPサポートおよびMallanox mlx4ドライバサポートが取り込まれたので,少なくともこれ以降のカーネルが必要となる.

開発用としてe1000をサポートするパッチも出ているが,まだリリースされていない.

xdp-vagrantでXDPを手軽に試す

ここまでに書いたように,XDPが動作する環境を準備するのは若干敷居が高い.そこで便利なのがxdp-vagrant.

github.com

これを使うと,XDPサポート済みカーネル*1 + e1000サポートパッチ + BCC導入済みな環境がvagrantで簡単に手に入る.

BCC(BPF Compiler Collection)はユーザランドで動作するツール群で,XDPプログラムの読み込みやカーネル側のデータの操作を補助してくれる.Pythonバインディングが用意されているので,XDPプログラムはCで書きつつ*2ユーザランド側のマネジメント廻りをpythonで書く,ということが容易に実現できる.

github.com

ちなみに,ユーザランドカーネルの両方をCで書く場合のサンプルコードは下記にある.

linux/xdp1_user.c at master · torvalds/linux · GitHub

linux/xdp2_kern.c at master · torvalds/linux · GitHub

vagrant upまで

Vagrantfileがlibvirt providerを前提に書かれいているので,vagrant-libvirtプラグインが利用可能な環境を事前に作っておく必要がある.更に,vagrant-restartプラグインも必要. 加えて,オリジナルのVagrantfileだと何故か ubuntu/trusty64 boxが指定されているが,このboxはlibvirt pluginに対応していない.自分でlibvirt対応boxを用意するか,他のlibvirt対応boxで代用する必要がある.ここでは s3than/trusty64 を使うことにする.

$ git clone https://github.com/iovisor/xdp-vagrant
$ cd xdp-vagrant
$ cat <<EOF | patch -p1
diff --git a/Vagrantfile b/Vagrantfile
index 2ee7327..c4392c6 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -5,7 +5,7 @@
 end

 Vagrant.configure('2') do |config|
-  config.vm.box = "ubuntu/trusty64" # Ubuntu 14.04
+  config.vm.box = "s3than/trusty64" # Ubuntu 14.04
   config.vm.network "private_network", ip: "192.168.50.4"

   # fix issues with slow dns https://www.virtualbox.org/ticket/13002
EOF
$ vagrant plugin install vagrant-libvirt vagrant-reload
$ vagrant up
$ vagrant ssh -c "uname -a"
Linux vagrant-ubuntu-trusty-64 4.7.0-07282016-torvalds+ #28 SMP Thu Jul 28 11:52:33 PDT 2016 x86_64 x86_64 x86_64 GNU/Linux

特定ポート宛パケットをドロップする例

bccに含まれるサンプルコード xdp_drop_count.py を参考に,80/tcpと8080/tcp宛てパケットを全てドロップしつつ,ドロップした総パケット数をポート毎にカウントするプログラムを書いてみた.

XDP: drop 80/tcp and 8080/tcp

パケットが到着するたびに drop_80_8080 が呼ばれる.この関数でXDP_PASSを返せばパケットは通常のネットワークスタックに送られる.XDP_DROPでドロップ,XDP_TXでTX queueに直接送り込まれる.

上記のプログラムを実行した状態でホスト側からvagrant VMのeth1宛にncやcurlでパケットを送ると,ドロップカウンタがカウントアップしていくことが分かる.ドロップカウンタのインクリメントはカーネル側で行い,それをユーザランドpythonプログラムから参照して出力している.

vagrant$ $ ls -l 
total 8
-rw-rw-r-- 1 vagrant vagrant 2034 Oct 10 06:10 drop_80_8080.c
-rwxrwxr-x 1 vagrant vagrant  807 Oct 10 06:06 loader.py

vagrant# ./drop_packet.py eth1
In file included from /virtual/main.c:4:
In file included from include/linux/if_ether.h:23:
In file included from include/linux/skbuff.h:34:
In file included from include/linux/dma-mapping.h:6:
In file included from include/linux/device.h:24:
In file included from include/linux/pinctrl/devinfo.h:21:
In file included from include/linux/pinctrl/consumer.h:17:
In file included from include/linux/seq_file.h:10:
include/linux/fs.h:2658:9: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
        if (id < 0 || id >= READING_MAX_ID)
            ~~ ^ ~
1 warning generated.
XDP loaded.
80: 1
80: 2
80: 2
8080: 1
80: 2
8080: 2
80: 2
8080: 2
80: 2
8080: 2
host$ echo | nc 192.168.50.4 80    # timeout as no response
host$ echo | nc 192.168.50.4 8080  # timeout as no response
host$ echo | nc 192.168.50.4 81
Ncat: Connection refused.

まとめ

Linuxカーネルに最近追加された高速パケット処理基盤XDPを少しだけ触ってみた.まだリリースされて間も無いということもあって利用可能な環境や関連資料は少ないものの,MellanoxをはじめとしたNICベンダも(eBPF hardware offload含め)サポートに積極的であるように見えるので,今後徐々に盛り上がっていくと思われる.

DPDKと比較してどうか,という部分に関しては,既存のネットワークスタックとの共存やeBPF関連ツールの流用が効く,といった点がアプリケーションを書く側にとって嬉しい点になりそう.

何れにしても,今後の展開が楽しみ.

*1:4.8ではなく4.8 rc

*2:clangでeBPFにコンパイルされる