Open Mobile Network Infra Meetup #4 XDPのテストとCI

2.5K Views

October 12, 21

スライド概要

https://omni-jp.connpass.com/event/219087/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

XDPのテストとCI 日下部雄也 (@higebu) BBSakura Networks, Inc ※育児休業中 2021/10/12 Open Mobile Network Infra Meetup #4

2.

アジェンダ ● なぜ #omni_jp でXDPのテストの話をするのか ● XDPのテストの概要 ● XDPのテストで気をつけた方がいいところ ● 世の中で行われているXDP(eBPF)のテストのやり方 ● BBSakuraでのXDPのテストのやり方

3.

なぜ #omni_jp でXDPのテストの話? 1. 2. さくらのセキュアモバイルコネクトのUプレーンで使っているから ○ 2019年12月から本番環境で XDPを使ったPGWの運用をしている ○ 参考:ENOG63 モバイルネットワークのデータプレーンを XDPで作る話 XDPでUPFを実装している人を見かけるから ○ 3. navarrothiago/upf-bpf パケット処理のテストは面倒だけどテスト書かないわけにはいかないので…

4.

XDPのテストの概要

5.

XDPのテスト方法 1. 2. BPF_PROG_TEST_RUN 実際にパケットを送受信

6.

XDPのテスト方法 1. BPF_PROG_TEST_RUN ○ eBPF Syscall — The Linux Kernel documentation ○ ロードしたプログラムの fdと実行回数、パケットデータを渡すと、指定された実行回数プログラムを 実行し、プログラムのリターンコード( XDP_PASSなど)、修正されたパケットデータ、実行時間を返 してくれるbpf(2)のサブコマンド ○ 5.15からxdp_mdが指定可能になり、 XDP metadataのテストもできるようになる ■ ○ https://lore.kernel.org/bpf/20210707221657.3985075-1-zeffron@riotgames.com/ オフラインかつ 1つのマシン内でテスト可能なので比較的手軽 ■ ただ、IPやMACアドレスを書き換えたり、ルートテーブルを参照するようなプログラムでは、結 局環境を整える必要がある

7.

XDPのテスト方法 2. 実際にパケットを送受信 ○ よくある性能検証のようにパケット送受信をするテスターを用意し、 XDPのプログラムをロード、ア タッチしたテスト対象に対して、実際にパケットを送受信する ○ TRexでやっているのを見かける ○ テスターとDUTは別のサーバなのが普通なので手間がかかる ■ VMでやれば多少マシそう DUT

8.

XDPのテストで 気をつけた方がいいところ

9.

XDPのテストで気をつけた方がいいところ 1. カーネルのバージョンを本番と同じにする ○ カーネルのバージョンが違うと XDP関連の機能がサポートされていなかったり、バグが治っていな かったりするし、機能が同じでも挙動が違う可能性がある 2. NICもできれば本番と同じにする ○ 3. XDP関連の機能は NICのドライバ毎に実装されているため、ドライバが違うとry native/genericも本番と同じにする ○ native modeだったらnative modeでテストする まとめると、「カーネル内のコードをテストと本番で同じにする」ということ

10.

世の中で行われている XDP(eBPF)のテストのやり方

11.

世の中で行われているXDPのテストのやり方 ● libbpf ○ Linux公式のCでeBPFするためのライブラリ ○ travis-ci/vmtest にテスト用のスクリプトがある ○ 2ヵ月くらい前に Travis CIからGitHub Actionsに移行したように見える ○ vmtestというローカルの Actionからrun_vmtest.sh → run.sh と呼ばれて、最小限の Linuxイメージ を作りつつ、qemuでVMを起動してテストしている ○ mkrootfs.sh を見ると最小限の Arch Linuxのイメージを作っているように見えるが、現在はこれは使 われていなそう ○ Linux本体のselftests/bpfを全部実行している

12.

世の中で行われているXDPのテストのやり方 ● Linux kernel ○ KernelCI があるが、bpf-nextはここではやっていなくて kernel-patches/bpf でやっている様子 ○ v5.12から tools/testing/selftests/bpf/vmtest.sh というスクリプトが入っている ■ [PATCH bpf-next v5 0/2] BPF selftest helper script ○ libbpfのテストを移植していて、同様に qemuでVMを起動してテストを実行する方式 ○ 移植時にきれいになっているようで読みやすい(個人の感想です)

13.

世の中で行われているXDPのテストのやり方 ● cilium/ebpf ○ GoでeBPFするときのライブラリ ○ Semaphore CI でCIしている(.semaphore/semaphore.yml) ○ run-tests.sh が実行されると virtme 経由でVM上で go test が走る仕組み ○ テスト時に起動する VM用のカーネルイメージは cilium/ci-kernels に置いてある ○ BBSakuraではこれを真似しました

14.

世の中で行われているXDPのテスト ● cilium (本体) ○ GitHub Actionsのworkflowで bpf/Makefile の go_prog_test が実行されている ○ bpf/tests/prog_test にBPF_PROG_TEST_RUNを使ったテストがある ■ gopacketでパケットを作っている

15.

世の中で行われているXDPのテスト ● katran ○ GitHub ActionsのWorkflowがあるが、テストは実行していなそう ○ katran/lib/testing/BpfTester.cpp にテストが書いてある ○ pcapからパケットデータを作って BPF_PROG_TEST_RUN ○ DEVELOPING.md にテストのやり方が書いてあり、 os_run_tester.sh を実行すると katran_tester.cpp が走る ○ IPが固定っぽいので VMでやった方がよさそう ○ Migrate some Katran Tests to VMTests というコミットがあるので VMでテストしてそうに見える

16.

世の中で行われているXDPのテスト ● navarrothiago/upf-bpf ○ XDPを使ったUPF ○ Create a build for CI #40 というIssueがあって、CIはまだやっていなそう ○ tests フォルダ配下にテストコードがある ■ TRexで実際にパケットを送受信する方式

17.

BBSakuraでやっている XDPのテストのやり方

18.

テスト対象のPGW-Uの概要 ● ユーザスペースのGoのプログラムがXDPのプログラムをロードしてNICにアタッチ したり、eBPF Mapを読み書きしている ● XDPのプログラムはeBPF Mapに書かれたセッション情報を元に受信したパケット を書き換えて送信(XDP_TX)したり、ドロップ(XDP_DROP)したり、パス (XDP_PASS)したりしている ● さくらのクラウド(つまりKVM)上で動いている ● 詳しくは ENOG63 モバイルネットワークのデータプレーンをXDPで作る話

19.

CIの流れ VM go test run-tests.sh GitHub Actions Hosted Runner さくらのクラウドの専有ホスト 上のサーバ Nested Virtualizationが使える

20.

BBSakuraでのXDPのテストのやり方 ● cilium/ebpf と同様 ● CIはGitHub Actions(with Hosted Runner) ● テストに必要なバージョンの最低限のカーネルイメージを予めビルドしてGitHubリポジトリに置 いておく ● テストスクリプトでは、カーネルイメージを取ってきて、virtmeでVMを立ち上げ、VM上でgo test を実行 ● ○ パケットの生成はgopacket ○ 入力用のパケットデータと確認用のパケットデータを用意してBPF_PROG_TEST_RUN VMが立ち上がる環境がなるべく本番に近づくようにいろいろお膳建てしている

21.

最低限のカーネルイメージ ● ● ci-kernels/config at master · cilium/ci-kernels · GitHub ○ シンプルなプログラムのテストであればこれで十分 ○ TCと組み合わせたい場合などに足りない TCなどが使えるconfigをgistに置いておいたので参考にしてください ○ ● https://gist.github.com/higebu/145f9e4071258819ba1ad905ce0483ac カーネルのビルド方法はここでは説明しませんが、cilium/ci-kernelsのmake.shが参考になります

22.

お膳立ての構成 VM ここにXDPのプログラムを アタッチする 192.168.10.5/24 192.168.20.5/24 192.168.30.5/24 enp0s9.10 enp0s9.20 enp0s9.30 enp0s9 enp0s10 192.168.1.5/24 tap XDPを通さない通信が 必要なテスト用の Userネットワーク br0 user0 192.168.1.1/24 br0.10 br0.20 br0.30 192.168.10.1/24 192.168.20.1/24 192.168.30.1/24 ローミング網方面 インターネット方面 ユーザのプライベートネットワーク方面 を疑似 ※VRFなどもあるけどややこしいので省いています

23.

virtme ● カーネルのgitリポジトリにも入っている、VMでカーネルのテストをするための Python製のツール ○ ● https://git.kernel.org/pub/scm/utils/kernel/virtme/virtme.git virtme-configkernel: virtmeで使うために最低限必要なconfigを足してくれるコマン ド ● virtme-run: テストを実行するコマンド(qemuのラッパー) ○ script-shオプションにスクリプトを渡すと VM内で実行してくれる

24.
[beta]
テストスクリプトの中のvirtme-run
sudo virtme-run --kimg "${tmp_dir}/${kernel}" --name ${name} --cpus 4 --memory 8192M --pwd \
--rw \
--show-command \
--show-boot-console \
--force-initramfs \
--rwdir=/run/input="${input}" \

VM内からはインターネットに出られないので、VM起動前
に go mod download しておいて、GOPATHと
GOCACHEをVMにマウントしている
go testでは “GOFLAGS=-mod=readonly” しておく

--rwdir=/run/output="${output}" \
--rodir=/run/go-path="$(go env GOPATH)" \
--rwdir=/run/go-cache="$(go env GOCACHE)" \

vnet_hdr=offにしてるので、いろいろなオフロードの
offがないが、vnet_hdr=onにするならいろいろなオフ
ロードのoffをしないとXDPが使えないので注意
詳細: Virtio-netでXDPを動かすにはqemuのオプ
ション変更が必要 - yunazuno.log
あとmrg_rxbufをon/offするとvirtio_net内で通るコー
ドがかなり変わるので注意
receive_small() or receive_mergeable()
詳しくは “drivers/net/virtio_net.c” 参照

--script-sh "PATH=\"$PATH\" $(realpath "$0") --in-vm /run/output" \
--qemu-opts -enable-kvm \
-netdev tap,vhost=on,vnet_hdr=off,queues=8,id=${nic},ifname=${nic},script=${ifup_script},downscript=${ifdown_script} \
-device virtio-net-pci,mq=on,vectors=12,netdev=${nic},mac=${mac} \
-netdev user,id=${user_nic},ipv6=off,net=192.168.1.5/24,host=192.168.1.1 -device virtio-net-pci,netdev=${user_nic} < /dev/zero

●

cilium/ebpfのrun-tests.shをベースにしているので詳しくはそちら。。。

●

GitHub Actions等で動かしたいときに最後の < /dev/zero が必要になる
○

--script-sh breaks with /dev/null or closed stdin · Issue #33 · amluto/virtme

25.

gopacketでGTPv1-Uのパケット生成 icmpPayload := []byte{...} 長さとチェックサム計算を任せると少し楽 opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true} buf := gopacket.NewSerializeBuffer() iph := &layers.IPv4{ Version: 4, Protocol: layers.IPProtocolUDP, Flags: layers.IPv4DontFragment, TTL: 64, IHL: 5, Id: 1212, SrcIP: net.IP{192, 168, 10, 1}, DstIP: net.IP{192, 168, 10, 5}, } udp := &layers.UDP{SrcPort: 2152, DstPort: 2152} udp.SetNetworkLayerForChecksum(iph) gopacket.SerializeLayers(buf, opts, &layers.Ethernet{DstMAC: []byte{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, SrcMAC: []byte{0x00, 0x00, 0x5e, 0x00, 0x53, 0x02}, EthernetType: layers.EthernetTypeDot1Q}, &layers.Dot1Q{VLANIdentifier: 100, Type: layers.EthernetTypeIPv4}, iph, udp, &layers.GTPv1U{Version: 1, ProtocolType: 1, Reserved: 0, ExtensionHeaderFlag: false, SequenceNumberFlag: false, NPDUFlag: false, MessageType: 255, MessageLength: 76, TEID: 2}, &layers.IPv4{ Version: 4, Protocol: layers.IPProtocolICMPv4, Flags: layers.IPv4DontFragment, TTL: 64, IHL: 5, Id: 1160, SrcIP: net.IP{192, 168, 100, 200}, DstIP: net.IP{192, 168, 30, 1}, }, &layers.ICMPv4{TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoRequest, 0), Id: 1, Seq: 1}, gopacket.Payload(icmpPayload), ) フルバージョン: https://gist.github.com/higebu/9503a3b90c047d5bbf677c0d3eb156df これを入力用、確認用で全テストケース分書く。。。

26.
[beta]
GoでBPF_PROG_TEST_RUN
cilium/ebpf を使うとこんな感じになる
objs := &ExampleObjects{}
err := LoadExampleObjects(objs, nil)
if err != nil {
t.Fatal(err)
}
defer objs.Close()
ret, got, err := objs.ExamplePrograms.XdpProg.Test(generateInput(t))
if err != nil {
t.Error(err)
}
// retern code should be XDP_TX
if ret != 3 {
t.Errorf("got %d want %d", ret, 3)
}
// check output
want := generateOutput(t)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("output mismatch (-want +got):\n%s", diff)
}

フルバージョン : https://github.com/higebu/xdp-example

27.

まとめ? ● XDPでもテストはやれるがお膳立てが大変 ● もう少しハイレベルなテストフレームワークっぽいものがあると良いのかもしれな い。。。 ● 今のところカバレッジ計測が実現できていない ● 当たり前だがCIできるようにしておくとプログラムの変更時に絶大な安心感がある のでやりましょう

28.

EOF