ふつうのNICでハードウェアL3スイッチング; あるいはSR-IOV switchdev modeとTC hardware offloadの使用例
最近のNICの中にはTCハードウェアオフロード機能を持っているものがあり、これを使えばNICハードウェア上でパケット転送処理に手を加えられることは以前のエントリで簡単に紹介した。
今回はこれを応用し、NICハードウェア上でL3スイッチングを実現できないか検討してみる。
前置き: tcを用いたレイヤ3パケットスイッチング
L3スイッチの動作はおおまかに、 1. 受信したパケットの宛先IPアドレスをスキャンし、 2. 宛先MACアドレスを書き換えた上で、 3. 出口ポートからパケットを送信する、 というステップから構成される。これらの動作はそれぞれ tc
の flower
フィルタ、pedit
アクション、mirred
アクションで表現することができる。
tcを用いたL3スイッチ動作表現の具体例
ここでは2個のNetwork Namespace ns0
, ns1
がL3 Master Device vrf-tc
経由で接続されている状況を考える。
+--------------------+ | vrf-tc | +-----+--------+-----+ | p0a | | p1a | +--+--+ +--+--+ |.1 .1| 10.0.0.0/30| |10.0.1.0/30 |.2 .2| +--+--+ +--+--+ | p0z | | p1z | +-----+ +-----+ ns0 ns1
上図の環境は以下のコマンドで作成できる:
sudo ip link add vrf-tc type vrf table 10 sudo ip link set vrf-tc up for i in $(seq 0 1); do sudo ip netns add ns${i} sudo ip link add p${i}a type veth peer name p${i}z sudo ip link set p${i}z netns ns${i} sudo ip link set p${i}a master vrf-tc sudo ip link set p${i}a up sudo ip -n ns${i} link set p${i}z up sudo ip addr add 10.0.${i}.1/30 dev p${i}a sudo ip -n ns${i} addr add 10.0.${i}.2/30 dev p${i}z sudo ip -n ns${i} route add 0.0.0.0/0 via 10.0.${i}.1 done
このとき、ns0
からns1
の10.0.1.2
宛通信フローが存在するとして、これをtc
で処理するならば以下のようになる:
sudo tc qdisc add dev p0a ingress sudo tc filter add dev p0a ingress protocol ip \ flower dst_ip 10.0.1.2/32 \ action pedit ex munge eth dst set $(sudo ip -n ns1 -j link show dev p1z | jq -r '.[0].address') \ pipe mirred egress redirect dev p1a
この状態でns0
からns1
の10.0.1.2
宛にping
を打ったのちtc -s
コマンドの出力を確認すると、この通信がどうやらtc
によって処理されたことが確認できる:
$ sudo ip netns exec ns0 ping -c 10 10.0.1.2
$ tc -s filter show dev p0a ingress filter protocol ip pref 49152 flower chain 0 filter protocol ip pref 49152 flower chain 0 handle 0x1 eth_type ipv4 dst_ip 10.0.1.2 not_in_hw action order 1: pedit action pipe keys 2 index 1 ref 1 bind 1 installed 68 sec used 18 sec key #0 at eth+0: val ce884422 mask 00000000 key #1 at eth+4: val a5220000 mask 0000ffff Action statistics: Sent 840 bytes 10 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 action order 2: mirred (Egress Redirect to device p1a) stolen index 1 ref 1 bind 1 installed 68 sec used 18 sec Action statistics: Sent 840 bytes 10 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0
このように、tc
を使用するとL3スイッチの挙動を模擬することができる。
本題: SR-IOV switchdev mode + TCハードウェアオフロードによる、NICハードウェア上でのL3スイッチング
ここからは、SR-IOV switchdev modeとTCハードウェアオフロードを組み合わせることにより、NICハードウェア上でパケット転送の挙動を制御できることを確認する。その具体例として、前述したTCによるL3スイッチングをNICハードウェア上で実現してみることにする。
SR-IOV switchdev modeでNICハードウェアの挙動を制御する
VM環境で高パフォーマンスなネットワーク環境を実現する手段として、SR-IOVによるNIC仮想化が従来より用いられている。SR-IOV VFをVMにアタッチすればそのVMはNICのハードウェア性能を直接享受できる反面、VMのネットワークが完全にハイパーバイザのソフトウェアスタックをバイパスされるため、ハイパーバイザ側からVMのネットワークの挙動を制御することは難しかった。
この問題を解決するのがSR-IOV switchdev modeである。SR-IOV switchdev modeにおいては、通常のVFとは別にVF representorと呼ばれるインタフェースが作成される。ハイパーバイザ上からこのVF reporesentorに設定を行うと、内容に応じてそのrepresentorに対応するVFに設定が反映される。たとえば下図でVF0 repに対してtcの設定を行ったとすると、その設定がハードウェアオフロード可能であればVF0に適用される。
+---+ +---+ +---+ |PF0| |VF0| |VF1| |rep| |rep| |rep| +-+-+ +-+-+ +-+-+ VM0 VM1 ^ ^ ^ +---+ +---+ | | | |VF0| |VF1| +--+-----+-----+--+ +-+-+ +-+-+ | NIC Driver | ^ ^ +------+----------+ | | Kernel | | | +-------------------------------+ | | NIC | | | +------+-----------------------+-----+----+ | Embeded Switch | +-----+-----------------------------------+ | | v PF0
なお、SR-IOV switchdev modeではデフォルトで全てのトラフィックがソフトウェア処理される点に注意が必要である。上図VM0から送信されたポケットは、何も設定しなればVF0 repを経由してハイパーバイザに流入する。一方、ハイパーバイザからVF0 repに送信したパケットはVM0のVF0で受信される。 この特性を利用すれば、ARPやトラフィックフローの初期段階はハイパーバイザのソフトウェアパスで処理し、ハードウェア処理の準備が整ったらハードウェアパスでのパケット転送に移行する、という動作が実現できる。実例としてOpen vSwitchのハードウェアオフロード機能はこの挙動を利用している。
SR-IOV switchdev modeの設定方法
まずSR-IOV switchdev modeの設定方法を確認する。通常のSR-IOV動作モード(legacy modeと呼ばれる)からswitchdev modeに移行するにはdevlink
コマンドを使用する。ここでは2個のSR-IOV VFを作成し、switchdev modeで使用する。
PCI Bus IDやインタフェース名、ドライバ名は各環境に応じて。
なお今回の環境は以下の通り:
# enp7s0f0 = PF, 0000:07:00.0 echo 2 | sudo tee /sys/class/net/enp7s0f0/device/sriov_numvfs for i in $(seq 2 3); do echo 0000:07:00.$i | sudo tee /sys/bus/pci/drivers/mlx5_core/unbind done sudo devlink dev eswitch set pci/0000:07:00.0 mode switchdev
2個のVF representorが作成されていることが確認できる。
$ ip link (snip) 17: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether f6:60:41:6a:4d:6e brd ff:ff:ff:ff:ff:ff 18: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 9e:a5:d8:0b:b4:a5 brd ff:ff:ff:ff:ff:ff $ ethtool -i eth0 driver: mlx5e_rep (snip)
これらのVF representatorをL3 Master Device vrf-tchw
経由で接続する。また、簡単のためVFをそれぞれNetwork Namespacens2
, ns3
にアタッチする。
sudo ip link add vrf-tchw type vrf table 20 sudo ip link set vrf-tchw up for i in $(seq 2 3); do sudo ip netns add ns${i} sudo ip link set eth$(echo ${i}-2|bc) master vrf-tchw echo 0000:07:00.$i | sudo tee /sys/bus/pci/drivers/mlx5_core/bind sudo ip link set enp7s0f${i} netns ns${i} sudo ip link set eth$(echo ${i}-2|bc) up sudo ip -n ns${i} link set enp7s0f${i} up sudo ip addr add 10.0.${i}.1/30 dev eth$(echo ${i}-2|bc) sudo ip -n ns${i} addr add 10.0.${i}.2/30 dev enp7s0f${i} sudo ip -n ns${i} route add 0.0.0.0/0 via 10.0.${i}.1 done
図で表すとこのようになる:
+---------------+ | vrf-tchw | +--------+------+ | eth0 | | eth1 | +------+ +------+ ns2 ns3 |.2 .2| +----------+ +----------+ 10.0.2.0/30| |10.0.3.0/30 | enp7s0f2 | | enp7s0f3 | |.1 .1| +-----+----+ +-----+----+ +-----------------+ ^ ^ | NIC Driver | | | +------+----------+ | | Kernel | | | +-------------------------------+ | | NIC | | | +------+-------------------------+------------+----+ | Embeded Switch | +--------------------------------------------------+
この状態でns2
からns3
の10.0.3.2
宛にping
が通るようになっている。また、tcpdump -i eth0
を実行すると、この通信がどうやらVF representorを経由していることが分かる:
$ sudo ip netns exec ns2 ping 10.0.3.2
$ sudo tcpdump -i eth0 -nn tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 14:35:11.750957 ARP, Request who-has 10.0.2.1 tell 10.0.2.2, length 46 14:35:11.751018 ARP, Reply 10.0.2.1 is-at f6:60:41:6a:4d:6e, length 28 14:35:11.751119 IP 10.0.2.2 > 10.0.3.2: ICMP echo request, id 5965, seq 1, length 64 14:35:11.751503 IP 10.0.3.2 > 10.0.2.2: ICMP echo reply, id 5965, seq 1, length 64
TCでFIBをハードウェアにオフロード
ここまで準備が整えば、あとは最初の例と同様にVF representor経由でTCの設定を行えばよい。 ここではLinuxカーネルのFIBとネイバーテーブルからtcフィルタを生成して適用する簡易的なpythonスクリプトを用意した。
上記スクリプトを実行した状態でns2
からns3
の10.0.3.2
宛にping
を打ちつつtc filter show
コマンドを実行すると、tcフィルタが適用されており、かつin_hw
表示がある通りハードウェアオフロードされている様子が確認できる。
$ tc filter show block 1 filter protocol ip pref 1 flower chain 0 filter protocol ip pref 1 flower chain 0 handle 0x1 eth_type ipv4 dst_ip 10.0.3.2 in_hw action order 1: pedit action pipe keys 2 index 1 ref 1 bind 1 key #0 at eth+0: val 92ef51a2 mask 00000000 key #1 at eth+4: val 33e80000 mask 0000ffff action order 2: mirred (Egress Redirect to device eth1) stolen index 1 ref 1 bind 1 filter protocol ip pref 2 flower chain 0 filter protocol ip pref 2 flower chain 0 handle 0x1 eth_type ipv4 dst_ip 10.0.2.2 in_hw action order 1: pedit action pipe keys 2 index 2 ref 1 bind 1 key #0 at eth+0: val 4e6279b1 mask 00000000 key #1 at eth+4: val 159f0000 mask 0000ffff action order 2: mirred (Egress Redirect to device eth0) stolen index 2 ref 1 bind 1
なお、今回は簡単のためフィルタを1秒毎に更新する実装になっているが、本来はnelink
をリッスンしてイベントドリブンにフィルタを更新すべきだろう。また、ネイバーテーブル上でREACHABLE
なネイバー宛経路のみオフロードするよう実装しているが、エントリがSTELE
状態に遷移するとすぐにオフロードが中止されてしまう、という問題もある。
まとめ
SR-IOV switchdev modeとTCハードウェアオフロードを組み合わせることで、一般的なNICのハードウェア上でレイヤ3スイッチと同様の処理を実現できることを確認した。今回は簡易的なpythonエージェントでオフロード機構を実装したため実用性は低いが、もしこれらの処理がLinuxカーネルやルーティングデーモン等によって透過的に実行されるようになれば面白いかもしれない。
また、今回はL3スイッチングを例としたが、他のパケット処理、たとえばACLやトンネリング(カプセリング)処理にももちろん適用可能である。従来のSR-IOV legacy modeは柔軟性の低さがひとつのネックとなっていたが、switchdev modeとTCハードウェアオフロードの組み合わせにより、高パフォーマンスを維持しつつより多くの問題に適用範囲が広がることを期待したい。
tc flowerオフロードを利用してNICハードウェア上でパケットフィルタを実施する
Linuxのtc
には、指定した条件にマッチしたパケットをドロップしたりヘッダを書き換えたりするためのflower
フィルタが存在する。
tc-flower(8) - Linux manual page
最近のNICの中にはこの処理をハードウェアにオフロードできるものがあり、通常のソフトウェア処理と比較して高スループットを実現しつつCPU使用率低減が期待できる。ここではtc flowerのNICハードウェアオフロード機能の動作を試してみる。
対応するNICドライバ
Linuxカーネル4.16.7のソースコード上で確認する限り、以下のNICドライバでtc flowerハードウェアオフロードに対応しているようである。ちなみに対応状況はTC_SETUP_CLSFLOWERないしTC_SETUP_BLOCK*1といったキーワードで検索するとおおよそ掴める。
- Broadcom
bnxt
(BCM573xx; NetXtreme C-series/E-series) - Chelsio
cxgb4
(T4/T5/T6) - Intel
i40e
(710 series) - Mellanox
mlx5
(ConnectX-4/5) - Netronme
nfp
(Agilio) - (Mellanox
mlxsw
*2 )
ただし、オフロード可能なマッチング条件 (ヘッダのフィールド) およびアクションはNICドライバおよびハードウェアによって異なる。ドライバが対応していてもハードウェアの世代やファームウェアバージョンによっては動作しない場合もある。たとえばi40e
はtc flowerハードウェアオフロード自体には対応しているがaction drop
のオフロードに対応していない*3ため、これから述べるパケットフィルタの例は動作しない*4。
設定方法
ethtool
コマンドでtc offloadを有効にしたのち、tc
コマンドでフィルタを設定してやればよい。
$ uname -a Linux server01 4.16.5-300.fc28.x86_64 #1 SMP Fri Apr 27 17:38:36 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux $ tc -V tc utility, iproute2-ss180129 $ ethtool -i enp7s0f0 driver: mlx5_core version: 5.0-0 firmware-version: 14.22.1002 (MT_2420110004) (snip)
ethtool -K IFACE hw-tc-offload on
でtc offloadを有効にしたのち、対象インタフェースのqdisc
にingress
を追加する。qdisc
はingress
ではなくclsact
を使用してもよい。clsact
はingress
をegress方向への操作にも適用できるように一般化したもの (net, sched: add clsact qdisc)。ただしegress方向へのflower
適用に対応しているNICは現状無さそう*5なので、特に差異は無いはず。また詳しく追えていないがFedora 28でNetworkManagerが動作していると、追加したingress
がしばらく経過すると削除される事象がみられた。
$ sudo ethtool -K enp7s0f0 hw-tc-offload on $ sudo tc qdisc add dev enp7s0f0 ingress $ tc qdisc show dev enp7s0f0 qdisc mq 0: root qdisc fq_codel 0: parent :8 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :7 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :6 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :5 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :4 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :3 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc fq_codel 0: parent :1 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc ingress ffff: parent ffff:fff1 ----------------
tc filter add ... flower ... action ...
の形式でマッチ条件と動作を指定する。ここでは8080/tcp
宛のパケットをドロップするよう設定している。skip_sw
を指定するとハードウェアオフロードのみを試行し、オフロードできない場合はエラーとなる。skip_hw
を指定するとソフトウェア処理のみを試行する。何も指定しない場合はまずハードウェアオフロードを試行し、オフロードできない場合はソフトウェア処理にフォールバックする。また、実際にオフロードされていればin_hw
が、そうでなければnot_in_hw
が表示される。
$ sudo tc filter add dev enp7s0f0 ingress protocol ip \ flower skip_sw ip_proto tcp dst_port 8080 action drop $ tc filter show dev enp7s0f0 ingress filter protocol ip pref 49152 flower chain 0 filter protocol ip pref 49152 flower chain 0 handle 0x1 eth_type ipv4 ip_proto tcp dst_port 8080 skip_sw in_hw action order 1: gact action drop random type none pass val 0 index 1 ref 1 bind 1
tc
コマンドに-s
オプションを付ければ統計情報も出力される。ここでは別ホストからhping -S -p 8080 --flood
で8080/tcp
宛にパケットを送ってみたが、CPU使用率の上昇は特にみられなかった。
$ tc -s filter show dev enp7s0f0 ingress filter protocol ip pref 49152 flower chain 0 filter protocol ip pref 49152 flower chain 0 handle 0x1 eth_type ipv4 ip_proto tcp dst_port 8080 skip_sw in_hw action order 1: gact action drop random type none pass val 0 index 1 ref 1 bind 1 installed 947 sec used 0 sec Action statistics: Sent 1002736860 bytes 16712281 pkt (dropped 16712281, overlimits 0 requeues 0) backlog 0b 0p requeues 0
削除はtc filter del
コマンドで。
$ sudo tc filter del dev enp7s0f0 ingress protocol ip pref 49152 handle 1 flower
tc-flower offloadのその他の利用例
ここまでtc flower offloadを使って特定のパケットをハードウェア上でドロップする例を見てきたが、実のところ同様のことはntupleフィルタをethtool
経由で設定することで以前から実現可能である:
ethtool
を用いたntupleフィルタと比較したtc flower offloadの利点は、drop
以外のアクションをオフロードすることでNICハードウェアに多彩な動作を行わせることが(仕組み上)可能な点にある。
たとえばpedit
アクションとcsum
アクションをオフロードすれば、簡易的なstatic NATを実装できる*6:
また、mirred
アクションをオフロードすれば、NICをL2スイッチや簡易的なL3スイッチのように振る舞わせることができる。これをSR-IOV switchdev modeと組み合わせれば、SR-IOVでVM向けに高パフォーマンスなネットワーク環境を提供しつつ、ハイパーバイザ側からVM間通信の挙動を制御することができる。実際にOpen vSwitch 2.8以降はこれらを利用してVM間のL2通信をNICハードウェアにオフロードする機能が実装されている。
SR-IOV switchdev modeの概要:
Introduction to switchdev SR-IOV offloads - Netdev 1.2
OpenStackとの組み合わせ例:
OpenStack Docs: Open vSwitch hardware offloading
Getting started with Mellanox ASAP^2 | Mellanox Interconnect Community
参考資料
- TC Flower Offload - Netdev 2.2
- tc flower offloadのイントロダクション的なセッション。スライド、ビデオあり
- TC Workshop - Netdev 2.2
- 各NICベンダの対応状況など。スライド、ビデオあり
- tc flower offload関連のman pages
*1:https://github.com/torvalds/linux/commit/6529eaba33f0465fc6d228e1d05b1745f7d0e8c9
*2:NICドライバではなく、MellanoxのイーサネットスイッチASIC (spectrum) をネイティブなLinuxネットワークAPIで操作できるようにするドライバ
*3:https://github.com/torvalds/linux/commit/2f4b411a3d6766e6362ffbf00e0495a2dfe92507 https://patchwork.ozlabs.org/cover/824130/ あたりを参照
*4:正確に言うとtc filter add ... skip_sw ... action dropコマンドは通るが動作している気配が無い@Intel X710
*5:対応しているのは恐らくswitch ASICであるところのmlxswのみ https://github.com/Mellanox/mlxsw/wiki/ACLs
*6:ConnectX-4 Lxでは mlx5: parsed 0 pedit actions, can't do more と言われて設定できなかった; https://github.com/torvalds/linux/blob/v4.16/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c#L1653-L1656
FRRはRFC5549な経路をLinuxカーネルのルーティングテーブルへどうやってインストールするのか
FRRouging (FRR)という、Quaggaからフォークしたルーティングデーモンがある。
FRRはRFC 5549に対応しており、bgpdで受信したIPv6ネクストホップを持つIPv4経路をzebra経由でLinuxカーネルのルーティングテーブルへインストールすることができる。しかし、Linuxカーネルのルーティングテーブルにそのような経路をインストールすることは通常できないはずである。正確に言うと最近のiproute2
ではip route
コマンドでそのような指定自体は可能だか、実際には反映されない。
$ ip -V ip utility, iproute2-ss170905 $ sudo ip route add 192.168.254.0/24 via inet6 fe80::a00:27ff:fe25:4c23 dev eth1 $ ip route 192.168.254.0/24 dev eth1
ではFRRはこの問題をどのように解決しているのか?気になったので少し調べてみた。
観察
FRR 2.0をインストールした2台のサーバ(server1
, server2
)のネットワークインタフェース(eth1
)どうしを直結した上でBGP unnumberedピアを設定し、server1
からserver2
に対し経路広報を行った。
動作を観察してみると、ネイバーテーブルにダミーの169.254.0.1
向けエントリを作成したのち、ルーティングテーブル上でネクストホップとして本来のIPv6リンクローカルアドレスの代わりに169.254.0.1
を指定することで問題を解決しているようである。
下記の例ではserver1
からRFC 5549で広報された172.16.254.0/24
の経路がserver2
のルーティングテーブルにインストールされた際の状態を示している。
server2$ ip neigh fe80::a00:27ff:fe25:4c23 dev eth1 lladdr 08:00:27:25:4c:23 router STALE 169.254.0.1 dev eth1 lladdr 08:00:27:25:4c:23 PERMANENT server2$ ip route 172.16.254.0/24 via 169.254.0.1 dev eth1 proto zebra metric 20 onlink
ネイバーテーブル・ルーティングテーブルのメンテナンス
気になるのが、FRRはどのようにネイバーテーブルやルーティングテーブルをメンテナンスしているのか、という点。ここではFRR 2.0のコードを元に、その方法を追ってみる。
- tag: frr-2.0 https://github.com/FRRouting/frr/tree/frr-2.0
- RFC5549サポートが実装された最初のコミット
まずはコード内を "5549" というキーワードでgrepしてみると、ルーティングテーブルにエントリをインストールしている箇所がzebra/rt_netlink.c
でヒットする。ルーティングテーブルのエントリ作成はこの部分が担当しているようである。
https://github.com/FRRouting/frr/blob/frr-2.0/zebra/rt_netlink.c#L654-L678
実際にzebra
の実行時のログを確認すると、この部分が使われている様子が観測できる。
ZEBRA: zebra message comes from socket [13] ZEBRA: 0:172.16.254.0/24: Inserting route rn 0x108a4c0, rib 0x108a3f0 (type 9) existing (nil) ZEBRA: 0:172.16.254.0/24: Adding route rn 0x108a4c0, rib 0x108a3f0 (type 9) ZEBRA: netlink_route_multipath() (single hop): RTM_NEWROUTE 172.16.254.0/24 vrf 0 type IPv6 nexthop with ifindex ZEBRA: 5549: _netlink_route_build_singlepath() (single hop): nexthop via 169.254.0.1 if 3 ZEBRA: netlink_talk: netlink-cmd (NS 0) type RTM_NEWROUTE(24), len=60 seq=7 flags 0x405 ZEBRA: netlink_parse_info: netlink-cmd (NS 0) ACK: type=RTM_NEWROUTE(24), seq=7, pid=0 ZEBRA: 0:172.16.254.0/24: Redist update rib 0x108a3f0 (type 9), old (nil) (type -1)
ではネイバーテーブルはどうか?コード内を "169.254.0.1" でgrepすると、先のzebra/rt_netlink.c
とは別にzebra/interface.c
のif_nbr_ipv6ll_to_ipv4ll_neigh_update()
がヒットする。
https://github.com/FRRouting/frr/blob/frr-2.0/zebra/interface.c#L752-L765
この部分を読むと、与えられたIPv6アドレスがEUI-64方式であることを前提としてそのMACアドレスを割り出したのち、それを用いてネイバーテーブルに169.254.0.1
のエントリを作成しているようである。
ではif_nbr_ipv6ll_to_ipv4ll_neigh_update()
を呼んでいるのは誰か?逆順に辿っていくと、
- zebra/zserv.c:nbr_connected_add_ipv6()
- zebra/rtadv.c:rtadv_process_advert()
- zebra/rtadv.c:rtadv_process_packet()
- zebra/rtadv.c:rtadv_read()
- ...
という具合に、IPv6 Router Advertisement (RA) の受信処理部分に辿り着く。また、上記パスとは別に、
- zebra/interface.c:if_nbr_ipv6ll_to_ipv4ll_neigh_add_all()
- zebra/interface.c:if_up()
- ...
からも呼ばれている。
これらより、zebra
はネットワークインターフェースがUPしたとき(zebra自身の起動時を含む)とIPv6 RAを受信したときにネイバーテーブルのエントリを作成していることが読み取れる。
まとめ
…といったことを挙動やコードから追っていたところ、Cumulus Linuxのドキュメントにしっかり記述されていた*1。
Border Gateway Protocol - BGP - Cumulus Linux 3.4.1 - Cumulus Networks
BGP and Extended Next-hop Encoding:
For link-local peerings enabled by dynamically learning the other end's link-local address using IPv6 neighbor discovery router advertisements, an IPv6 next-hop is converted into an IPv4 link-local address and a static neighbor entry is installed for this IPv4 link-local address with the MAC address derived from the link-local address of the other end.
It is assumed that the IPv6 implementation on the peering device will use the MAC address as the interface ID when assigning the IPv6 link-local address, as suggested by RFC 4291.
Managing Unnumbered Interfaces:
the IPv4 link-local address 169.254.0.1 is used to install the route and static neighbor entry to facilitate proper forwarding without having to install an IPv4 prefix with IPv6 next-hop in the kernel
Linuxにおける Equal Cost Multipath (ECMP) の設定方法と挙動に関するメモ
Linuxのネットワークスタックでmultipathを使用する場合の設定方法と挙動に関するメモ。
設定方法
設定自体は ip route add
コマンドで nexthop
を複数指定するだけ。src
の指定は必要に応じて。
# ip route add 10.1.1.0/24 src 10.0.0.3 \ nexthop via 192.168.11.1 weight 1 \ nexthop via 192.168.12.1 weight 1
$ ip route (snip) 10.1.1.0/24 src 10.0.0.3 nexthop via 192.168.11.1 dev eth1 weight 1 nexthop via 192.168.12.1 dev eth2 weight 1
なお、カーネルが CONFIG_IP_ROUTE_MULTIPATH=y
でコンパイルされていることが必要。
カーネルバージョンごとの挙動の差異
カーネルバージョン毎にトラフィックのバランシング方法が異なるため、注意が必要*1。
- ~ 3.5: レイヤ3情報 (src/dst IP address) に基づく per-flow ECMP
- 3.6 ~ 4.3: per-packet ECMP
- 4.4 ~ 4.11: レイヤ3情報に基づく per-flow ECMP
- 4.12 ~: レイヤ3情報 or 5-tuple (src/dst IP addr, src/dst port, L4 proto) に基づく per-flow ECMP
カーネル3.6から4.4の間の状況の概要はredditのこのスレッドに纏まっている:
カーネル3.6で一旦消滅した per-flow ECMP は、カーネル4.4で復活した:
ちなみに RHEL 7 (CentOS 7) のカーネルは3.10ベースであるため、当初の挙動は per-packet ECMP だった。ただし、3.10.0-514 (=7.3の初期カーネルパッケージ) で上記パッチがバックポートされたため、これ以降は動作が per-flow ECMP に変わっている。
レイヤ4情報も含めた per-flow ECMP はカーネル4.12でマージされた。net.ipv4.fib_multipath_hash_policy
で 1 を指定すれば使用可能。(デフォルトは0 = L3のみ)
Linux VRF with L3 Master Device
Linux kernel 4.4から登場した、L3 Master Device (l3mdev) によるVirtual Routing and Forwading (VRF) を軽く触ってみたメモ。
そもそも何をするためのものなのか
Linuxのネットワーク廻りを触っていると、たまに「特定のネットワークインタフェースから入ってきた通信に、特定のルーティングルールを適用したい」といった場面がある。こうしたケースでは、以前より Routing Policy Database (RPDB) を利用して Policy Based Routing (PBR) を行う方法が知られている。
一方、ネットワーク機器の世界では一般的に、L3ドメインを分割する手段として Virtual Routing and Forwarding (VRF) という機能が PBR とは別に存在している。VRFは、あるネットワークインタフェース群に適用されるルーティングテーブルを分離させるためのものである。このような概念をLinux上で実現するために登場したのが、L3 Master Device である。
net: L3 master device [LWN.net]
kernel/git/davem/net-next.git - David Miller's -next networking tree
使用方法
前提として、カーネルが4.4以降かつ NET_L3_MASTER_DEV=y
でコンパイルされている必要がある。たとえばFedoraの場合、4.11以前のパッケージではこのオプションが無効であるため注意が必要。また、iproute2
のバージョンが古いと、後述するl3mdevが暗黙的に追加するポリシーが ip rule
コマンドで正しく表示されないので同様に注意する必要がある。
Bug 1428530 – Set NET_L3_MASTER_DEV=y to enable ipvlan module
設定のおおまかな流れとしては次の3ステップ。
- L3 Master Device (l3mdev) のネットワークインタフェースを作成する
- 既存のネットワークインタフェースのmasterとしてl3madevを指定する
- VRF毎にルーティングを設定する
流れ自体はbridge interfaceを作成する場合と似ている。l3mdev は通常のネットワークインタフェース (net_device) と同様に振る舞うので、l3mdev自体にIPアドレスを振ることも可能。ネットワーク機器で言うところのloopback address的な使い方が可能。
使用例
まずはl3mdevを作成する。ここでは vrf-x
と vrf-y
の2個のVRFを作成する。
# ip link add dev vrf-x type vrf table 10 # ip link set dev vrf-x up # ip link add dev vrf-y type vrf table 20 # ip link set dev vrf-y up
ip link
コマンドを実行すると、2個のインタフェースが作成されていることが分かる。
$ ip link 2: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:15:7f:1c brd ff:ff:ff:ff:ff:ff 3: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:4e:69:5d brd ff:ff:ff:ff:ff:ff 4: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:cb:ef:bd brd ff:ff:ff:ff:ff:ff 5: ens7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:f1:2e:11 brd ff:ff:ff:ff:ff:ff 6: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:83:4c:ca brd ff:ff:ff:ff:ff:ff 9: vrf-x: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 5a:b6:1d:4a:84:21 brd ff:ff:ff:ff:ff:ff 11: vrf-y: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 76:c3:e8:67:00:01 brd ff:ff:ff:ff:ff:ff
ここでは ens4
, ens5
を vrf-x
に、ens6
, ens7
を vrf-y
に所属させることにする。
# ip link set dev ens4 master vrf-x # ip link set dev ens5 master vrf-x # ip link set dev ens6 master vrf-y # ip link set dev ens7 master vrf-y
再度 ip link
コマンドを実行すると、ens[4-7]
のmasterが設定されていることが分かる。
$ ip link 2: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-x state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:15:7f:1c brd ff:ff:ff:ff:ff:ff 3: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-x state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:4e:69:5d brd ff:ff:ff:ff:ff:ff 4: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-y state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:cb:ef:bd brd ff:ff:ff:ff:ff:ff 5: ens7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-y state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:f1:2e:11 brd ff:ff:ff:ff:ff:ff 6: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:83:4c:ca brd ff:ff:ff:ff:ff:ff 9: vrf-x: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 5a:b6:1d:4a:84:21 brd ff:ff:ff:ff:ff:ff 11: vrf-y: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 76:c3:e8:67:00:01 brd ff:ff:ff:ff:ff:ff
また、ip rule
コマンドを実行すると、priority 1000に l3mdev-table
というポリシーが自動的に追加されていることが分かる。なお、この機能はkernel 4.8から実装されたもの*1なので、4.4~4.7のカーネルでl3mdevを使用する場合、手動でルールを追加する必要がある。
$ ip rule 0: from all lookup local 1000: from all lookup [l3mdev-table] 32766: from all lookup main 32767: from all lookup default
ここで、例として 172.16.0.1/32
の経路を各VRFに追加する。
$ ip addr 2: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-x state UP group default qlen 1000 link/ether 52:54:00:15:7f:1c brd ff:ff:ff:ff:ff:ff inet 192.168.1.1/24 scope global ens4 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe15:7f1c/64 scope link valid_lft forever preferred_lft forever 3: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-x state UP group default qlen 1000 link/ether 52:54:00:4e:69:5d brd ff:ff:ff:ff:ff:ff inet 192.168.2.1/24 scope global ens5 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe4e:695d/64 scope link valid_lft forever preferred_lft forever 4: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-y state UP group default qlen 1000 link/ether 52:54:00:cb:ef:bd brd ff:ff:ff:ff:ff:ff inet 10.0.1.1/24 scope global ens6 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fecb:efbd/64 scope link valid_lft forever preferred_lft forever 5: ens7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-y state UP group default qlen 1000 link/ether 52:54:00:f1:2e:11 brd ff:ff:ff:ff:ff:ff inet 10.0.2.1/24 scope global ens7 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fef1:2e11/64 scope link valid_lft forever preferred_lft forever 6: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:83:4c:ca brd ff:ff:ff:ff:ff:ff inet 10.168.20.201/24 brd 10.168.20.255 scope global ens3 valid_lft forever preferred_lft forever inet6 fe80::24fd:811e:6432:bd36/64 scope link valid_lft forever preferred_lft forever 9: vrf-x: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP group default qlen 1000 link/ether 5a:b6:1d:4a:84:21 brd ff:ff:ff:ff:ff:ff 11: vrf-y: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP group default qlen 1000 link/ether 76:c3:e8:67:00:01 brd ff:ff:ff:ff:ff:ff
# ip route add 172.16.0.1/32 via 192.168.1.2 table 10 # ip route add 172.16.0.1/32 via 10.0.1.2 table 20
ip route list
を見ると、デフォルトおよび各VRF毎に独立したルーティングテーブルが設定されていることが確認できる。
$ ip route list default via 10.168.20.1 dev ens3 proto static metric 100 10.168.20.0/24 dev ens3 proto kernel scope link src 10.168.20.201 metric 100 $ ip route list vrf vrf-x 172.16.0.1 via 192.168.1.2 dev ens4 192.168.1.0/24 dev ens4 proto kernel scope link src 192.168.1.1 192.168.2.0/24 dev ens5 proto kernel scope link src 192.168.2.1 $ ip route list vrf vrf-y 10.0.1.0/24 dev ens6 proto kernel scope link src 10.0.1.1 10.0.2.0/24 dev ens7 proto kernel scope link src 10.0.2.1 172.16.0.1 via 10.0.1.2 dev ens6
Network Namespaceとの比較
Linuxのネットワークリソースを分離・独立させる手段としては、最近はNetwork Namespaceがポピュラーである。Network Namespaceはネットワークスタック全体を分離するのに対し、l3mdevはL3のルーティングのみを分離する。l3mdevのモデルは、L3ドメインをまたいでプロセスを実行したい場合、具体的にはルーティングデーモンを実行する場合に都合が良い。このあたりの背景は、L3 Master Deviceの提案者であるDavid Ahernが書いた記事が詳しい。
アプリケーション側の挙動に関しては、カーネル付随ドキュメントの Applications セクションに記載がある通り sysctl
で net.ipv4.tcp_l3mdev_accept
/ net.ipv4.udp_l3mdev_accept
によって変化するようである。このあたりはまた別途触ってみる予定。
Reference
- https://www.kernel.org/doc/Documentation/networking/vrf.txt
- カーネルに付随するドキュメント。
- VRF for Linux — a contribution to the Linux Kernel - Cumulus Networks Blog
- l3mdevの提案者、David Ahernによるエントリ。VRF実装が必要とされるコンテキストが詳細に述べられている。
- What is an L3 Master Device?
- 同じくDavid Ahernによるnetdev 1.2での発表資料。実装面やパフォーマンスについても述べられている。
自宅のルータをAT-AR2050Vに入れ替えた & 回線をv6プラス化した
自宅マンションで光配線方式のフレッツが使えるようになったので、これを機にルータの入れ替えとv6プラス(IPv6 IPoE)化を実施したメモ。 結論から言うと、IPv4 PPPoE時代に発生していた夜間帯の輻輳が解消され、かなり快適になった。
構成変更前の環境
現住居に引っ越した時点ではフレッツのVDSL方式しか選択肢が無かったため、一旦考えるのをやめて最低限インターネットに接続できる環境だけを構築して使っていた。
ひかり電話契約は無し。VDSLモデムの下にルータが居て、そこでIPv4 PPPoEを終端するだけの単純な構成。
構成変更後の構成
回線が光配線方式に。また、Biglobe経由でJPNEのv6プラスも適用。
- 回線: NTT東日本 フレッツ 光ネクスト ギガマンション スマートタイプ 光配線方式
- ひかり電話に新規加入
- ISP: Biglobe
- v6プラスを使用
- ルータ: Allied Telesis AT-AR2050V
AT-AR2050Vなのは昨年末までキャンペーンで3万円弱で買えたため。昨年末、似たような価格でJuniper SRX300がeBayで大量に出回っていて気になったものの見送り。
v6プラスの効果
今回の構成変更で一番効果が有ったのは、v6プラス化したことにより夜間帯の輻輳が解消されたこと。
ようやく自宅回線をVDSLから光配線(光ネクスト スマートタイプ)に変更できたので,ついでにv6プラスに切り替えた.Biglobe PPPoEで恒常的に発生していた夜間帯のパケロス・高レイテンシが解消されてかなり快適になった. pic.twitter.com/R5yZVc5kvy
— yunazuno (@yunazuno) 2017年2月26日
特にDNS名前解決が速くなったのが体感速度向上に貢献しているように思う。帯域幅に関してはそれほど気にしていないが、試しに ftp.kddilabs.jp から適当なisoイメージをダウンロードすると400Mbps弱出ている。
ネットワーク構成
それほど複雑なことはしていない。HGW (PR-500MI) - ルータ (AT-AR2050V) - L2 (GS908M) というシンプルな構成。 AR2050Vでrouted VLAN interfaceを2個作成し、それぞれを日常生活用と各種実験用として使っている。VLANはGS908Mまでtag VLANで運び、そこでポート毎に解いている。
IPv4インターネット接続に関しはHGWでNAT(MAP-E)されて出ていく。AR2050Vの下に居るサブネット宛のstatic routeをHGWに設定する必要があるので注意。 IPv6インターネット接続に関しては、HGWからAR2050VにDHCPv6-PDで/60が振ってくるので、これをクライアントに振っている。
IPv6まわりに関しての設定例がアライドテレシスのサイトにあるので、それほど苦労せずに設定できた。
機器周辺はこんな感じ。横向きなのがHGWで、隙間に居るのはDNSフルリゾルバ兼DHCPサーバのRaspberry Pi. 上の段に居るのは各種実験用に使っているQuanta LB6M. 無線LANはHGW内蔵APは使用せず、Aterm WG1200HPをブリッジモードで使用。
Facebookはレイヤ4ロードバランサをIPVS(LVS)からXDPベースのものに乗り換えつつある
4月に開催されたnetdev 2.1で面白いセッションがあったのでメモ。
Facebookが使用しているレイヤ4のロードバランサに関する発表で、従来はIPVS (LVS) を使用していたが、XDPベースで自ら開発したものに移行しつつある、という内容。
XDP Production Usage: DDoS Protection and L4LB (slide)
XDP (eXpress Data Path) については以前のエントリで簡単に紹介した。
XDPを改めて簡単に紹介すると、Linuxカーネルのネットワークスタックの最下部 (NICに一番近い場所) でパケット処理を行う仕組みのことで、オーバーヘッドが非常に小さい高速パケット処理の実現を目的としている。XDPはeBPFを用いており、eBPFが提供するmapやhelper functionといった機能を用いて柔軟なプログラムが書けることに加え、JITによる高速化やカーネル自身の堅牢性をそのまま享受することができるとされている。
XDPがリリースされたのは2016年10月のカーネル4.8なので、半年程で実際の事例が出てきたことになる。
FacebookにおけるXDPのユースケース: L4LB & DDoS防御
FacebookはIPVS (LVS) ベースのレイヤ4ロードバランサ (L4LB, 通称Shiv) を利用していることが知られている。
Shivの役割はConsistent Hashによる負荷分散先の決定とIP encapsulation (IPIP) によるパケット転送である。また、ローカルにセッションキャッシュを持つことにより、Consistent Hashの弱点であるリマップ時のセッション切断問題をカバーしている。
今回の発表では、これらの機能をXDPの基盤上で実装したのちIPVSベースの既存環境を置き換えていることが明かされた。スライドでは下のグラフが示され、XDPで実装された新しいShivがIPVSベースのそれと比較して高いスループットを発揮しつつもCPU使用率が低く抑えられている、と述べられている*1。
(スライドp3より; それぞれ1本だけラインが離れているものがXDP, 固まっているものがIPVS)
また、DDoS防御のための仕組み (Dropletと呼ばれる) も併せて紹介されている。
DDoS防御にあたっては、攻撃パケットを可能な限り早い段階で検出・遮断することが望ましい。加えて、攻撃パケット検出のシグネチャはプログラマブルに構成・変更可能であることが望ましい。彼らはこれもXDP上に実装している。L4LBのプログラムの前にDDoS検出・遮断のプログラムが実行されるようにすることで、L4LBが余分なリソースを消費することを防いでいる。
(スライドp10より; #0 XDP Dumpはデバッグ用のもの)
ちなみに、XDPによるDDoS防御についてはCloudflareのエンジニアも同様の発表を行っている。こちらはよりBPFにフォーカスした内容。
XDP in practice: integrating XDP in our DDoS mitigation pipeline (slide)
実装の詳細
冒頭のビデオで発表部分が12分弱で収まっていることからも分かる通り、やっていること自体は非常にシンプルである。
ただその裏では、(発表内で直接は触れられていないものの) XDPやその基礎となるeBPFの機能がふんだんに使われていることが見て取れる。これらの機能はFacebookのエンジニア自身によって実装されたのちLinuxカーネルにマージされたものも少なくない。
BPF_MAP_TYPE_LRU_HASH: セッション維持のためのLRU cache
Consistent Hashを用いるL4LBではその特性上、分散先のリアルサーバが増減したタイミングで幾許かのTCPセッションが意図せず切断されてしまう。この弱点を補うため、いくつかのL4LB実装ではセッションテーブルを併用している。TCP SYNパケットがL4LBに到達すると同時にセッションテーブルにセッション情報を記録すれば、仮にConsistent Hashのリマップが発生したとしてもセッションテーブルに基づいてTCPセッションを維持できる、という仕組みである。ただセッションテーブルのサイズは有限であるため、何らかの方法でセッションテーブルのメンテナンスを行う必要がある。
そこで登場するのがeBPF map objectのひとつ、BPF_MAP_TYPE_LRU_HASH
である。その名前が示す通り、Least Recently Used (LRU) なハッシュテーブルである。これを利用すれば、先に述べた要件を満たすセッションテーブルを容易に実現できる。
この機能は冒頭で紹介したセッションのスピーカーであるMartin Lauによって開発され、kernel 4.10以降で利用可能である。
xdp_adjust_head(): IP encapsulation
ShivはL3DSRによるリアルサーバへのパケット転送にIPIP方式を採用している。これはリアルサーバ宛てIPヘッダで元のVIP宛てパケットをカプセル化することでパケット転送を実現するものである*2。
これをXDPで実現するため、メモリページ上であらかじめ空間 (headroom) を確保しておき、そこにデータ(ここではリアルサーバ宛てのIPヘッダ)を追加するインタフェースが用意されている。このインタフェースがeBPF helper functionのひとつ、bpf_xdp_adjust_head()
である。この機能も同じくMartin Lauによって開発され、kernel 4.10以降で利用可能である。
なお、NICドライバには通常のXDPサポートとは別にheadroom確保のサポートが要求される。
BPF_MAP_TYPE_PROG_ARRAY & tail call: 複数のeBPFプログラムを連携させる
XDPはその仕様上、1個のネットワークインタフェースに対して1個のeBPFプログラムのみアタッチすることができる。これは1個のネットワークインタフェースに対して複数の独立した処理を適用したい場合に都合が悪い。
これを解決するのがBPF_MAP_TYPE_PROG_ARRAY
と bpf_tail_call()
である。
上記エントリで紹介した通り、BPF_MAP_TYPE_PROG_ARRAY
とbpf_tail_call()
を組み合わせることで、複数のeBPFプログラムを連結して呼び出したり実行中のeBPFプログラムを動的に入れ替えたりできる。ちなみにこの機能を実装したAlexei StarovoitovもFacebookに在籍している*3。
余談: XDPの近況
2016年10月にXDPがリリースされてから約半年が経過したが、その間に様々なアップデートが行われた。
まず大きなトピックとしてNICドライバのサポート拡充が挙げられる。当初はMellanox mlx4
のみであったものが徐々に拡充され、先日遂にIntel ixgbe
サポートがnet-nextにマージされた。恐らくカーネル4.12でリリースされると思われ、XDPが利用可能な環境が大幅に広がることになる。また変わり種として、CaviumのARM SoCであるThunderXでのサポートもnet-nextにマージされている。
現時点では、下記のNICドライバで利用可能あるいは近日中に利用可能となる:
mlx4
: Mellanox ConnectX-3mlx5
: Mellanox ConnectX-4 or laternfp
: Netronome Agilioqede
: QLogic FastlinQvirtio_net
: Virtiobnxt_en
: Broadcom NetXtreme C-Seriesixgbe
: Intel X5xx (82598, 82599) (from kernel 4.12?)thunderx
: Cavium ThunderX (from kernel 4.12?)
また、NICドライバのサポートが無くてもXDPプログラムを実行できるようにする仕組み (“Generic XDP”) も登場した。
kernel/git/torvalds/linux.git - Linux kernel source tree - net: Generic XDP
これは主に開発用やお試し用を意図しており、パフォーマンスに関しては考慮されていない。これまでXDPプログラムを実行するには、対応NICが挿さったハードウェアを用意するか、あるいはe1000, virtio_netの環境を用意する必要があった。Generic XDPの登場により、より手軽に開発環境を整えられるようになる。
なお、各カーネルバージョン毎にサポートされるeBPF/XDP関連の機能やドライバの情報はiovisor/bccのドキュメントに纏まっている:
bcc/kernel-versions.md at master · iovisor/bcc · GitHub
XDPリリース直後の2016年12月にPLUMgrid*4がVMWareに買収される出来事があったものの、特に大きな影響も無くXDP周辺の発展は続いている様子である。eBPF自体を含め機能がどんどん拡充されており、かつXDPのユースケースも徐々に出始めてきたことで、今後の動向がますます楽しみ。