お腹.ヘッタ。

関数型とかセキュリティとか勉強したい。進捗つらぽよ

PyCon APAC 2023で発表してきました

Why

今後もいろんなところでCfPを出していくぞ!というメモ書きとして書き残すためにこれを書きます。

What

PyCon JP 2023はPyCon APAC 2023として開催されました。APACが日本で行われるのは10年ぶりの開催だそうです。

どうせ参加するなら発表したいなーと思ってプロポーザルを出したところ、採択していただけたのでスピーカーとして参加しました。

発表内容について

今年の私のトークタイトルは 「自作パケット処理系の性能測定と可視化&改善のPDCAを回して最強のパケット処理系の作り方を学ぼう」 でした。

speakerdeck.com

実は初参加でした。PyConJP自体は存じ上げてたのですが、今まで地方に(今も?)住んでいるので参加には大変敷居が高かったというのがあり初参加・初スピーカーという感じでドキドキでした。

PyConと言いつつ少しPython感は薄い感じになりましたが転職して戻ってきて一年でちゃんと対外発表できる感じでだったのでよかったです。

CfPを練るにあたっての所感

発表内容については実は合計二本出していて、「ExaBGPにSRv6MUPを入れた話」と今回の「PGW-Uに対して負荷計測を行った話」を出していました。

前者はIETFのネームバリューとかOSSに初めて新機能入れました!みたいなセンセーショナルさで攻めてみたCfP、後者は学びや再現性がある話を売りにして攻めてみたCfPで書いました。結果としては後者に関して拾ってもらえてよかったです。

自分はこのように大きいところで話すのは初めてだったのでおすすめタイトルは何かを調べました。

speakerdeck.com

次にトラックの選び方の話とかプロポーザルの参考例を眺めてやりました。

あとはプロポーザルの選択基準が明確に出されてたのでこのように書き出して強みってなんだろー?と思いながら書き出してました。

## どういう話にするか&どういう強みがあるか
### Python 初心者にとって自らの知見を広げる内容か
~なさそう~ ネットワークの処理系に関しても使えるんだぞ!という分野自体を伝えれるかも

### Python が社会で活用されている具体的なイメージを描けるか
SDNやNFVと言われているサービスというのは自分たちの手で実装可能なことが知られているが、どのように品質を担保するかはあまり知られていない。ネットワーク製品を良くするために使われてるというイメージを持ってもらえる

### 内容に独自性・新規性はあるか
Trexでどのようにしてデータを持ってくるとか細かい実装レベルについては書いてないので、そういうのを書いてもいいかも

### トークの発表が明確にイメージできるほど構成は詳細に書かれているか
本文の詳細を見てイメージできるように書き出してみる
- 背景とモチベ
  - 弊社ではフルMVNO事業をやっており、その中のPGW-Uと呼ばれるインターネットに出るための自作パケット処理系の通信効率の性能を上げることが求められていました
  - そのためにpython製のツールとライブラリを駆使して計測し高速化をしました
- 話す内容
  - 私たちは、XDPと呼ばれるeBPFを使った高速パケット処理技術を使って実装しています
  - 性能試験を通した改善プロセスでは、負荷試験のパケット生成・テスト結果の記録・メトリックの分析など多くの場面で Python を活用しています
  - それぞれの場面での活用事例を紹介します
    - scapy で TRex のパケット生成にモバイル網で使うプロトコルのパケットを追加しました
    - 性能試験のテスト完了後、性能指標を TRex から取得し Pushgateway で記録しました
    - Jupyter Lab 上で Grafana 経由の CSV ファイルを pandas.DataFrame に加工し、可視化や評価を行いました
  - 性能試験を通じた改善例
    - 大容量のエントリを流した際にメモリ使用量が大きくなるとコケる問題を発見して修正した事例
    - パケットのcheck sumの処理で遅くなったので処理性能を向上させるためにchecksum機能をoptoutする機能を追加した事例
    - 特定のパケットに対してマルチコア対応がされていないことを発見して、IntelのNICドライバに機能を追加してマルチコア対応させた。その結果$(nproc| CPUコア数)倍の効率アップができた事例

### コミュニティの国際交流に寄与するか
わからん。。。

### Python を使う「楽しさ・熱意」をアピールできるか
ぶっちゃけscapyがすごいんだけど、pythonで好きなパケットをホイホイ投げれるのは良い

### 聴衆が具体的に持ち帰ることができる知識や技術の利用方法・工夫があるか
自社のネットワーク製品を検証できるフレームワークやコードを提供できる

その他の雑感

参加について

スピーカーは本会議前日に特別招待されたイベントに参加できました。 が、普段のネットワークの人たちは全くいないので知り合いがいなくてそれなりにぼっちで辛かったです(かなりクラスタが違うし、コミュ障には厳しい)

IETFとかみたいに始めまして参加者限定パーティーとかそういうのがあったら嬉しかったなーと思いました

発表聴講に関して色々クラスタを見てると体感3-4割ぐらいがMLっぽい人が多いなーとか思いました。 NetworkX!?ネットワークの人がいる〜!と思ってノコノコ行ったらニューラルネットワークのネットワークでorzとなりました。

ただMemrayの発表とかはなるほどーとなって面白かったです。スレッド間の話とかをしてなかったのでもうちょい深い話を聞きたかったんですが色々聞いた感じ発表内容的にはオーバービューみたいな発表が好まれるのだなーと思いました。Qiitaとかにあるチュートリアルやってみました。こんなこともできるらしいです!ぐらいの感じが一番ウケそうだなぁと思いました。

実際自分が登壇する時はクラスタ的に全然きてくれないかも・・と不安で参加前についてはTwitterで極力宣伝しまくりました。結果としては30人ちょいぐらいは来てくれてたので0人じゃなくてよかったのかなと思います。宣伝後も登壇した資料を公開してそれなりにPVしてもらうことはできました。きっとJANOGとかIWならもう少し来てくれる人が居たかもなーと思ったりしましたがこれも学びです😇

ちなみに懇親会でとあるプログラム委員と話したところ、投稿してる人が少ない分野でちゃんと描いてると通りやすいんだよねーという話を聞きました。なので今回ネットワークプログラミングに関するトラックで選択して投稿したのはよかったなと思います。

発表方法について

発表練習は個人で行い、スピーカーノートにはある程度文章でスクリプトを描きました。発表資料・言語は日本語なので緊張はしましたが話が飛んでしまうということはなかったかなと思います。

反省点として自分の発表は本当にLTみたいな早口詰め込み発表でしたので要点を絞る訓練をした方がいいと思いました。また資料に不必要な図の引用をしてたので社の先輩に指摘されてしまったというのがあり、ある程度フォーマルな場所での発表の仕方というのを身につけた方がいいなと思いました。詰め込んだことに関しては、スライドの狙いとして後から見返して嬉しくなるようにしたので仕方ないんですが果たしてここの発表でそれは適切だったのかは悩ましいです。その点で唯一良かった点は初め20-30秒QRコードを出してスライドをぜひお手元でご覧ください!という話をしたのは直前で追加した動きでしたが良かったです。

そろそろ若手社員という枠じゃ無くなりそう(今3年目😇)なので何とか身につけたいです。。。

早口なものだった話は、多少言い訳すると30分の発表で募集されてたので30分まるまる使えるものだと思っていました。

が、実際発表する時に初めて25分で残り5分は質疑と書かれており・・・この辺は登壇時に気をつけることという旨の資料上で事前アナウンスされてなかったので大変焦りました。この辺はあらかじめちゃんと確認しておくと良いなと思いました(反省)

発表後に気がついたんですが、発表に関しても pyconの運営の人たちがノウハウを出してくれてたので見ておくとよかったなーと思いました。 (全然見つけれなかったのでいろんなところでこういう資料を宣伝して欲しかったw)

tv.pycon.jp

linux kernel をbuildしてinstallしたら立ち上がらなくなった時のメモ

言ってる通り立ち上がらなくなった。 とてもつらい気持ちになったのでそれをどうやって解決しましたか?というメモです。

こういうのがネットに転がってるとマジでありがたいので感謝の意を込めて晒しておきます。

前提として

  • grubすら上がらない設定にしてしまった
  • idracとかから busybox のshellに対して書き込みができなくて終わってしまった
    • なんで idracから触れないのか... :innocent:
  • kernel build して make install したら死んだ

こんな感じの顔をして死んでた。linuxさんの色々なアヘ顔って感じです

Gave up waiting for root device. Common problems:
  — Boot args (cat /proc/cmdline)
    — Check rootdelay= (did the system wait long enough?)
    — Check root= (did the system wait for the right device?)
  — Missing modules (cat /proc/modules; ls /dev)
ALERT! /dev/mapper/ubuntu--vg-root does not exist. Dropping to a shell! 

BusyBox v.1.21.1 (Ubuntu 1:1.21.1-1ubuntu1) built-in shell (ash)   
Enter 'help' for list of built-in commands.  

(initramfs)

直し方

  • 雑にUSBライブブートをなんかでします。ubuntuとかでも良いです。
  • ライブブートからshellを触れる様にします。
  • とりあえず何も考えず ubuntu server のイメージを使いました。
# shellが触れてたら fdiskで対象のジャーナルを探す
fdisk -l 

# 雑に dev を見たら色々あるのでそれっぽいのをmountする
# sda1がefi, sda2がboot, sda3 が lvという感じ
mount /dev/ubuntu-vg/ubuntu-lv /mnt
mount /dev/sda2 /mnt/boot

# 後から chroot をするときに触りたい環境情報があるものをマウントして使える様にしておきます。これがないとFSとか触れなくて困る。
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys

# chroot いわゆるコンテナ化みたいなやつです。(雑
# これで自分の壊したSSDとかのデータにログインするみたいなことができる
chroot /mnt /bin/bash

# 起動のオプションとかインストールが失敗してるだろうからこれで入れ直し
# カーネルのバージョンは /boot/ とかに転がってるファームから分かる。
# /etc/default/grub とかに設定があるからそれを書き換えたりも出来る
grub-install /dev/sda 
update-initramfs -u -k [カーネルのバージョン]

# 後片付け
exit
umount /mnt/dev
umount /mnt/proc
umount /mnt/sys
umount /mnt/boot
umount /mnt

sudo reboot 

これで戻ってくるのを信じてもう一度トライ。 これで治らなかったらもっと頑張りましょう(完

もし無理なら /mnt が出来る時点で /home/user/ が手に入るのでそこにあるものをZIPしてどこかに投げつけるとかでレスキューしてOS入れ直しとかをすると良いと思います。

参考

VPP上の開発・デバッグ方法テクニック

ビルド方法やテストの実行テクニックに関してはVPPにSRv6 MUP Plugin APIを追加している話開発方法とパッチを出すときの Tips に書いたりしてるわけですが、もう少しフォローアップする話があってもいいかなー(書いとくと後で思い出すのに便利)と思い記事を書きました。

この記事は自分が便利だと思った物を追記してくスタイルなのでサイレントで更新されたりします。

プログラムのデバッグ方法

ここでは printf ではもう辛くなった人向けです。たとえばCに慣れてなくて初期化されてなくて困ってるとかそういう人が便利に使う想定です。

AddressSanitizer

アドレスサニタイザーってやつです。VPPはマルチスレッドな感じでvalgrindが使えない環境なので使うと便利だったというやつです。 詳しくはここを見ると良さそう。

s3-docs.fd.io

make build VPP_EXTRA_CMAKE_ARGS=-DVPP_ENABLE_SANITIZE_ADDR=ON

GDB

gdbなので使い方はググってくれ

世の中にはいい記事がいっぱいあります。 https://uguisu.skr.jp/Windows/gdb.html

make debug

Trace

コードレベルではなく、設定や構成レベルのデバッグに有用です。

# VMのNICがある部分からパケットを受け取るときはここを見る
trace add dpdk-input 10
show trace
clear trace

# VTAPなどの部分からパケットを受け取るときはここを見る
trace add virtio-input 100
show trace
clear trace

# それぞれの処理nodeへの着信カウンターを見ることでどの処理が多く動いてるかがわかる
show runtime

packet trace

https://vps3-docs.fd.io/vpp/23.06/cli-reference/clis/clicmd_src_plugins_dispatch-trace.html

# pcap をtx側のものを全て取得する
pcap trace tx on

# 取得したものを書き出し(/tmp/tx.pcapに書き込まれる。 txをrxに置き換えても成り立つ仕様)
pcap trace tx off

pluginの有効化と Linux-CPの利用法

confの中に以下のように入れることで pluginが有効化されます

plugins {
  plugin linux_cp_plugin.so { enable }
}

linux-cp設定例

tap(つまりただのL2の分岐機能です)を使うことで hostのlinuxに入れれて便利という話があったりしますが、これはそれはうまいことlinux側と協調動作するというわけではないです。

そこでlinux-cpを利用するとVPPとLinuxが協調動作します。つまりVPPを普通のlinuxっぽく利用できます。 詳しくはこの一連の記事を見るのがおすすめです。

これを利用することでLinuxでVPP+FRRを動作させてBGPを話すサーバーとして使うことができます。

ipng.ch

(ちなみに、以前はvppsbの中にrouterプラグインがあって、それは特定プロトコルだけ分離してcplaneに投げてくれるいわゆるtap-inject機能がありましたが、それはもう古いのでlinuxcpを使いましょう)

# lcp use netlink
cat << EOF > /etc/sysctl.d/81-vpp-Netlink.conf 
# Increase Netlink to 64M
net.core.rmem_default=67108864
net.core.wmem_default=67108864
net.core.rmem_max=67108864
net.core.wmem_max=67108864
EOF
sudo sysctl -p

VPP

set interface state eth1 up
set interface ip address eth1 172.0.1.2/24
set interface state eth2 up 
set interface ip address eth2 fc00:12::1/64

loopback create-interface
set interface state loop0 up
set interface ip address loop0 fc00:1::1/64

lcp lcp-sync on
lcp lcp-auto-subint on
lcp create eth1 host-if eth1
lcp create eth2 host-if eth2

linux

sudo ip link set eth1 up
sudo ip addr add 172.0.1.2/24 dev eth1
sudo ip link set eth2 up
sudo ip addr add fc00:12::1/64 dev eth2

ExaBGPにSRv6 MUPを実装をした時のメモ

Why

exaBGPに MUP-BGP を追加しました。パッチを出すのにちょくちょく困ってたのでメモを公開しておきます。

当該パッチはこちら github.com

使い方はこちらgobgpとの相互接続例をこのリンクで示してます。

追記: CML2で遊べるように cloudinitファイルを書いてみました。宜しければどうぞー

設計に関しての勘所

大きくわけて設定のためのパーサーとパケットのパーサーを書くというのが、BGPに機能を実装するということです。

具体的には前者のあたりはsrc/exabgp/configuration/を中心としたコード、後者は src/exabgp/bgp/message/update/を中心としたコードに手を加えることになります。

例えば前者のコードで announce セクションで動作するには 例えば@ParseAnnounce.register('mup', 'extend-name', 'ipv4') といったデコレーターをつけた上でパーサーコードを作り、先ほどの後者で記述されるTypeに合わせた設定を返します。

詳しくはPRにあるコードを見てください。

デバッグとtest方法

これはpythonなのでデバッグには import pdb;pdb.set_trace() を使うのが一番便利です。実質ブレークポイントなのでこれを入れて保存して実行してあげるのが良い感じです。 また、パケットの構造がミスってるかどうかを知りたい場合は受け取る方を行う方がわかりやすいです。理由としては受け取り側はstack traceが出るようになっていて尚且つ Notificationメッセージなどでわかるケースがあるからです。

他にもこのようにログとしてもヒントが出てきたりします。13:22:02 1656739 outgoing-13 >> NOTIFICATION (3,0,“invalid ipv4 mup next-hop length 16 expected 4”)

パースの方だと先ほどのような stack trace が出てこないので気をつける必要があります。

デバッグを通じて完成した場合はテストを書いてあげることもしたくなるはずです。 etc/exabgp/<confname>.conf (合わせてqa/ci/<confname>.ci<confname>.confと書いてあげる必要があります)に設定を書いてあげる必要があります。 これは実装物の設定例になる物です。

詳しくはPRを参照すると良いですが、肝は neighbor 127.0.0.1 にした上でコンフィグを設定し、exabgp -d <confname>.conf-d オプションを追加した上でログからupdateメッセージを取得して qa/ci/<confname>.msgに加筆する必要があります。

以下にgobgpの設定とexabgpの設定、先に示したようなgistに書いたネットワークを変形させたnetnsを示します。

#!/bin/bash

set -eu

if [[ $(id -u) -ne 0 ]] ; then
    echo "Please run with sudo"
    exit 1
fi

run () {
    echo "$@"
    "$@" || exit 1
}

destroy_network () {
    run ip netns del red
    run ip netns del blue
}

stop () {
    destroy_network
}

trap stop 0 1 2 3 13 14 15

# exec functions
run ip link add veth-red-blue type veth peer name veth-blue-red

run ip netns add red
run ip link set veth-red-blue netns red
run ip netns exec red ip link set up lo
run ip netns exec red ip link set up veth-red-blue 
run ip netns exec red ip addr add 10.0.0.1/24 dev veth-red-blue

status=0; $SHELL || status=$?
exit $status

gobgp.toml

[global.config]
    as = 65000
    router-id = "127.0.0.1"

[[neighbors]]
    [neighbors.config]
        peer-as = 65000
        local-as = 65000
        neighbor-address = "127.0.0.1"
    [[neighbors.afi-safis]]
        [neighbors.afi-safis.config]
            afi-safi-name = "ipv4-mup"
    [[neighbors.afi-safis]]
        [neighbors.afi-safis.config]
            afi-safi-name = "ipv6-mup"

exabgp.conf

neighbor 127.0.0.1 {
    router-id 10.0.0.1;
    local-address 127.0.0.1;
    local-as 65000;
    peer-as 65000;

    family {
            ipv4 mup;
            ipv6 mup;
    }
    announce {
        // gistを参考に所望のannouceを記述する
    }
}

これらの設定コンフィグを保存した後は以下のようにして実行します。

# red
sudo ip netns exec blue gobgpd -f ./gobgp.toml

# separate terminal exec
sudo ip netns exec red \
    /home/ubuntu/exabgp/sbin/exabgp \
    -d -v\
    ./exabgp.conf

以下のように update messageがログに流れるはずです。

23:19:14 1720215 rib             insert mup:t1st::100:100:2001:db8:1:1::1/128:12345:9:128:2001::1 extended-community target:10:10                                                    [733/1918]
23:19:14 1720215 rib             insert mup:t2st::100:100:12345:128:2001::1 extended-community target:10:10                                                                                    
23:19:14 1720215 outgoing-1      sending TCP payload ( 128) FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF 0080 0200 0000 6940 0101 0040 0200 4005 0400 0000 64C0 1008 0002 000A 0000 000A C028 2505 0022 0001 001E 0020 010D B800 0100 0100 0000 0000 0000 0000 0048 0001 0006 4018 1000 0000 800E 2500 0155 1020 0100 0000 0000 0000 0000 0000 0000 0100 0100 010C 0000 0064 0000 0064 180A 0001
(中略)
23:19:14 1720215 outgoing-1      >> 8 UPDATE(s) 

そしたらこの outgoing-1 のバイナリ列がupdate messageになっているのでそれを取得して整形します

整形する形は以下のようにします

1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0080:02:000000694001010040020040050400000064C010080002000A0000000AC028250500220001001E0020010DB800010001000000000000000000004800010006401810000000800E250001551020010000000000000000000000000001000100010C0000006400000064180A0001

肝としては、接頭子にルールナンバーとtype,あとは取得したログの空白行を消して、初めのFの連続までをコロン、0080をコロン、02をコロンにして記述します。 ざっくり中身を説明すると

  • F~F: marker
  • 0080: lenght
  • 02: update message type

あとは以下のコマンドを実行して testが動作するかを見ます。これの作業を加えることで機能のテストが無事完了します。

./qa/bin/functional encoding

追記(2023/3/20)

もっと便利な方法があったのでメモしておく。 (ついでに引っかかりポイントも) まず引っかかりポイントとして何かしらの原因で test processが動いてるゆえにテストがゾンビ化してそちらにテスト実行時に繋がってしまい大変困るケースがある。遠慮なく pkill -f python3 とかで殺しておこう。

で、テストの時にnetnsとかはだるいと思うのでどうするかというとupdate messageを見るのではなくて、テストのunexpect エラーから取ってくると楽。具体的には以下のようなイメージ。この時の B は区別されてる列記としたラベルで以下のように list オプションで確認できる。

./qa/bin/functional encoding --list

./qa/bin/functional encoding --server B

./qa/bin/functional encoding --client B

こんな感じで出てくる。エラー自体のタイミングでクライアントの接続を切断されるので、そこではupdate messageと合わせて突き合わせれば良い。

new session:                                                                                                                                                                  
open recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0047:01:04FDE800B40A0000012A02060104000100010206010400010055020641040000FDE8020C400A80B4000101800001558002020600                 
open sent   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0047:01:04FDE800B40A0000022A02060104000100010206010400010055020641040000FDE8020C400A80B4000101800001558002020600                 
msg  recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0013:04:                                                                                                                         
msg  recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0017:02:00000000                                                                                                                 
msg  recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001E:02:00000007900F0003000155                                                                                                   
msg  recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0030:02:00000015400101004002004003040A0001FE400504000000C8180A0001                                                               
msg  recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0080:02:000000694001010040020040050400000064C010080002000A0000000AC028250500220001001E0020010DB8000100010000000000000000000048000
10006401810000000800E250001551020010000000000000000000000000001000100010C0000006400000064180A0001                                                                             
msg  recv   FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001B:02:0004180A00010000                                                                                                         
                                                                                                                                                                              
unexpected message:                                                                                                                                                           
received    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001B:02:0004180A00010000      

終わりに

ドキュメントやコードは見やすいように整備されてて、迅速なレビューが素晴らしいですがciとmake fmtのルールが異なったり長い間使われてるOSSなので多少レガシーであることは事実でコードのメンテナンスは難しいなぁと思いました。

一方初めてBGPの機能実装を行いましたが比較的簡単に実装できるOSSというのは良い作品だと思ったのでぜひ今後も機会があればコントリビュートしていきたいと思いました。

EdgeRouterXが息をしてない時にやるやつ

要約

  • edgerouterx が上がってこない時は多分kernel panicを起こしてるのでリカバリーが必要。
  • USB TTL が必要になる

突然の死

なんか上がってこない

なおしかた

この記事を見て(完) yaaamaaaguuu.hatenablog.com

にならなかったので今回は書いています。

上の記事を見てもダメだった時に参考にしてください。

自分の場合はこのようなエラーが出てて、要はFSごと死んでたという話でした。この場合だと上記のような手順では復活できなくて困りました。

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

ググるとこんなのが出てきて Edgerouter X wiki points to not working .BIN and other thingies - #14 by zett - Installing and Using OpenWrt - OpenWrt Forum

どうやら公式のマニュアルがあるようです。 https://help.ui.com/hc/en-us/articles/360018189493-EdgeRouter-Manual-TFTP-Recovery#3

公式で出ているイメージ ER-e50.recovery.v2.0.6.5208541.190708.0508.16de5fdde.imgER-X-SFP / ER-X / EP-R6 / ER-10X (e50) をクリックして持ってきて記事通り焼くだけ。

同じように困ってしまった人は参考にしてください。

Go+XDPな開発を始めるときに参考になる記事/janog LT フォローアップ

これは JANOG LT のフォローアップブログです。 Go+XDPな開発何も説明できなかったことに気がついたので書きました。

SRv6,GTPv1,XDPの大まかな説明は世の中に神な資料があるので特に説明はしません。

もしebpfとはなんぞなどの疑問がある方は

などを合わせて見ていただけるといいと思います。

TL;TR

GoでXDPをやるときの選択肢と最近の利点

Goを利用する際の開発においての選択肢は以下のようにいくつかあります。

..etc

自分はもしその中であればcilium/ebpfを利用した開発をお勧めしております。

いくつかの理由がありますが、まず他が微妙というのもあります。 上記にあげたnewtools/ebpf をcloudflareの中の人が作っていて、それをciliumで利用を使うとなったことで新しく移動したのが cilium/ebpfです。なので中の人が同じであります。 また、 dropbox/goebpfもありますが直近ではあまりコントリビュートされず、そもそもdropboxの中のひともcilium/ebpfにコントリビュートしてるので恐らく方針としてはこちらはもう下火になると思われます。 iovisor/gobpfはCGoなので互換性問題を考えるときに辛いと思います(これは個人の趣味ぽいですね...)

で、なぜお勧めするかというと BTF対応が一番進んでいて ciliumのような巨大な実績のあるプロダクトを参考に開発を進めることができるからというのが一番大きいです。また大きい会社がバックでコントリビュートしてるのも大きいでしょう。

例えばBTFの対応の一番見かけるパターンの話の例としては、今の最新の ebpf map においての定義は 以下のようなxdpcapの書き換えのようにに .maps と書く必要があります。またトレーシングをやる人はCO-RE の影響を受けるのでなおさらBTFが必要になるでしょう。実際 kernelの sample/bpf を見ても全て今は .maps に書き換わっています。

// https://github.com/cloudflare/xdpcap
// old case
// struct bpf_map_def SEC("maps") xdpcap_hook = {
//     .type = BPF_MAP_TYPE_PROG_ARRAY, \
//     .key_size = sizeof(int), \
//     .value_size = sizeof(int), \
//     .max_entries = 5, \
// }

struct xdpcap_hook
{
    __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(int));
    __uint(max_entries, 5);
} xdpcap_hook SEC(".maps");

また、ebpfを使う弱点の一例としてはバイナリ一つに落とすことができないのがあったと思います。これではせっかくのGoの利点が台無しです。

なので今まではなんとか github.com/rakyll/statik などでシリアライズをしてバイナリをgoのコードにつめてワンバイナリで嬉しいですね?とか言ってたわけです。(しかもビルドのタイミングは自分でmakeなりに書く必要があって本当に面倒)

しかし cilium/ebpfには bpf2goが入り go:generate を使ってclangを叩いてビルドをしてコードに詰めて、それらをマップした構造体まで用意してくれるという便利機能が入りました。 bpf2goは今回のgoxdp-templateでは go:generate の例で動いてるので参考になると思います。

ちなみにですが、激推ししてるけどまだまだダメなところもあります。例えばnewtoolsではできた innermap をelfから生成したり、ebpfmapに書き込む際に構造体のアライメント周りが粗雑になってたりしてるので定義して使うときにはいくつかのワークアラウンドを必要とするケースがありますのでそこは今後に期待というところでしょうか....

もしこれを使ってDplaneを開発するぞ!となったらxdp-tutorial などを見てDplane開発を進めるのが良いでしょう。

終わりに

今回JANOG初参加だったんですが(オンラインだとこれは参加したと言えるのかという気持ちですが...😇)勢いで出したLTがギリギリ通っててよかったです。

学生生活最後だったので現地開催でいきたかったんですが、、、行けるときに行っておくのは重要だなぁと思いました。

あとどこかの機会で自作SRv6+XDP Dplane: Vinberoで使ってるワークアラウンドとかブログネタにしたいなぁと思いました。 例えば

  • 可変スタック上に乗る場合は ebpfmap を変数にして使うと便利
  • loopsizeにおいての上限値制限は and演算 をすると便利

とかどこのブログにも載ってなさそうなネタだと思うので、もしきになる人がいたらコードを読んでください。

また、l3vpn exampleとかあるのでよければ気が向いた時にでも遊んでみてください。まだまだ荒削りなのでバグが(多分)あると思うんですが優しくissueに書いてくれると助かります。

ではでは。

linux kernelでパケットがなぜかルーティングされないとかドロップされるときにみる記事

本記事は 2020linuxアドベントカレンダーで書こうと思ってた話の供養です。 卒論で書けなかったんですすいません....

本人的にはいい感じに設定を入れてるのにどーーーしてなのか全くいい感じにパケットが通らない時がある。 そんな時にロジカルにトラブルシュートをできるようになりたくありませんか?

ここではパケットが通らない時の実際の事例2つを挙げてそれをどのようにして探し出して解決したかを紹介します。

対象読者は iproute2をググれば使える人たちぐらいを想定してます。簡単な記事です:)

TL;DR

こういう時使えるツール類は以下の通りなので気合で使ってにゃん

  • trace-cmd: なんの関数が呼ばれたかわかる便利な相棒。XDPでもお世話になったりするし便利
  • perf: さっきと同じ感じ。
    • こいつらを眺めるときは kernelshark を使うとグラフィカルで目に優しい
  • ipftrace2: skbbuffにmetadataをつけてやることでパケットがkernelのどこを通ったがわかる便利ツール
  • dropwatch: udpパケットがどこでドロップしてるかわかる。しかしL2L3などのレイアーは共通なので十分に使えるので初手これを使っておくと楽そう。
  • bpftrace: そもそも関数の返り値とか眺めたいときはこれを使うと良い。ただしプローブが書かれてないときは自分で書く必要がある。

    • 今回は取り扱わないが機会があれば書く。
  • どんな事例の前でも共通して見ておく&やっておくといいこと

    • 正しくプロトコルとiproute2などの設定を入れるコマンドを理解してるかを見ておく。RFCやドラフトは読みましたか?manページは見ましたか?
    • tcpdump -i eth1 -vvv とかで期待してるパケットが来てるか
    • ethtool -S eth1 でRX or TXでドロップされてるのかとか。どの辺で死んでそうかをイメージしておく
    • ip n とかでL2が解決してるかを確認

事例1: どうしてか SRv6 End が動いてくれない

EndというのはSRv6のパケットを受けてSRHにあるipv6アドレスのポインター次に進めてそれに合わせたにルーティングする技術のことです。 前提として使ってたのは

  • fedora33
  • iproute2

となります。また どんな事例の前でも共通して見ておくといいこと で書いたことは全て行ったあとです。

今回は正しいパケットが来てるのにリダイレクトされないというところが問題でした。

この場合ですととりあえずどんな関数コールをされているのかをあたりをつける必要があります。 なのでそもそもSRv6のEnd動作をするkernelの関数コード (input_action_end) まで到達してるかをtrace-cmdで確認しました。

具体的にはまず https://github.com/torvalds/linux/blob/master/net/ipv6/seg6_local.cのコードを眺めます。 みるとわかるのですが input_action_end* のような形で一定のパターンで実装が記述されています。SRv6自体は複数のlocal functionをRFCにて定義されてるため、同じようなノリで書かれてる関数がいくつかあるのではないかと推論されるため、同じようなパターンを持つコードにかかれるのではないかと想定されます。

またトレース可能かどうかを調べるのも良いでしょう。trace-cmdの場合は以下のようにすると所望の関数へのプローブが対応してるかが分かります。

cat /sys/kernel/debug/tracing/available_filter_functions |grep input_action_end
input_action_end_dt6
input_action_end_dx6
input_action_end_dx2
input_action_end_dx4
input_action_end
input_action_end_x
input_action_end_t
input_action_end_b6
input_action_end_b6_encap
input_action_end_bpf

で、実際やってみるのは以下のようにします。

trace-cmd record -p function_graph -g input_action_end
trace-cmd report

しかし実際には動いておらずレコードがされませんでした。

このことから endを行うコードまでは到達してないことがわかります。ということでその前にdropしてるということがわかります。 では次にそもそもどこを通ってどのようなコールが行われるのかを考えたいですよね? 次に使ったのは ipftrace2を利用しました。これはskbというパケットが含まれる構造体にあるmetadataの領域に特定の値を仕込んでおくことでそのパケットがどこを通るのか出力します。

sudo ip6tables -t raw -A PREROUTING -d fc00:2::2 -j MARK --set-mark 0xdeadbeed
sudo ipft -m 0xdeadbeed

これを使うと以下のような出力が出ます。

677084806878622 001          __skb_checksum_complete
677084806882860 001                        kfree_skb
677084806887163 001           skb_release_head_state
677084806892984 001                 skb_release_data
677084806897402 001                     kfree_skbmem
677084836856575 001                  ip6_route_input
677084836879043 001                        ip6_input
677084836881744 001                     nf_hook_slow
677084836895052 001                  nf_ip6_checksum
677084836899705 001          __skb_checksum_complete
677084836903818 001                        kfree_skb
677084836907878 001           skb_release_head_state
677084836913613 001                 skb_release_data
677084836918676 001                     kfree_skbmem
677084865837153 001                  ip6_route_input
677084865860250 001                        ip6_input
677084865863010 001                     nf_hook_slow
677084865876901 001                  nf_ip6_checksum
677084865881458 001          __skb_checksum_complete
677084865885631 001                        kfree_skb
677084865889919 001           skb_release_head_state
677084865895676 001                 skb_release_data
677084865900353 001                     kfree_skbmem
...

大体どこを通ってるかを理解することができました。

ちなみにiptables以外にもtcを利用することでマークをつけることもできます。以下の例はtun1を通るパケットにマーク1, eth1を通るパケットにマーク2を与えている例です。

sudo tc qdisc add dev tun1 ingress
sudo tc filter add dev tun1 parent ffff: protocol ip matchall action skbedit mark 1
sudo tc qdisc add dev eth1 ingress
sudo tc filter add dev eth1 parent ffff: protocol ip matchall action skbedit mark 2

ではこの辺を起点に調べると良いでしょう。ただこの状態だとiptablesを使ってるのでnfがどこにかかってるのかわかりません。一旦切りましょう ここからは二通りの方法があります。

一つはtrace-cmdやperfで虱潰しに眺めることです。以下のようなコマンドで調べれたりします。

sudo perf record -g -a -e skb:kfree_skb
sudo perf script

他にもtrace-cmdで ip6_input をみるとこんな感じになります https://gist.github.com/takehaya/8e07656af34bc6b7b132843147f364c5

二つ目は dropwatch を使うことです。 初手これでもいいぐらいだったんですが どこでdropしてるかが即座にわかります。

以下のようにしたらinstallできるはずです

sudo apt install -y libnl-3-dev libnl-genl-3-dev libreadline-dev libpcap-dev binutils-dev make automake libtool m4 autoconf clang pkg-config
git clone https://github.com/nhorman/dropwatch.git
cd dropwatch
./autogen.sh
./configure
make
sudo make install

今回はこれで最後に落とし込んでしまいましょう。

sudo dropwatch -l kas
dropwatch> start

30 drops at nf_hook_slow
...

さて、先ほどのトレースにも出てきましたが nf_hook_slow というのが出てきました。 雑にぐぐるこのような記事がありました。 nfというのはnetfilter系のコードでiptables周りとかをコールすると動くものであると理解できると思います。

あとはちょっとした連想ゲームです。

iptablesとは?=>linux 組み込みのfirewallfedoraにおいてfirewallを司るものは??

そう、原因は firewalldでした。

ということで解決方法は以下の通りになります。

systemctl stop firewalld

事例2: どうしてか SRv6 End.DX4 が動いてくれない

End.DX4というのはSRv6のパケットでなおかつinnerがipv4のパケットを受けてouterをdecapした上で任意のNexthopにルーティングする技術のことです。 前提として使ってたのは

  • ubuntu18.04
  • iproute2

となります。また どんな事例の前でも共通して見ておくといいこと で書いたことは全て行ったあとです。 今回の問題はなぜかnexthopにパケットが飛ばないでした。

さて先ほどと同じようなノリで呼び出されるべきな関数のトレースから見てみましょう。

trace-cmd record -p function_graph -g input_action_end_dx4
  plugin 'function_graph'
Hit Ctrl^C to stop recording
^CCPU0 data recorded at offset=0x4b5000
    4096 bytes in size
trace-cmd report
cpus=1
          <idle>-0     [000]  4724.772017: funcgraph_entry:                   |  input_action_end_dx4() {
          <idle>-0     [000]  4724.772043: funcgraph_entry:                   |    decap_and_validate() {
          <idle>-0     [000]  4724.772043: funcgraph_entry:                   |      get_srh() {
          <idle>-0     [000]  4724.772044: funcgraph_entry:        0.698 us   |        ipv6_find_hdr();
          <idle>-0     [000]  4724.772045: funcgraph_entry:        0.082 us   |        seg6_validate_srh();
          <idle>-0     [000]  4724.772046: funcgraph_exit:         2.053 us   |      }
          <idle>-0     [000]  4724.772046: funcgraph_entry:        0.171 us   |      seg6_hmac_validate_skb();
          <idle>-0     [000]  4724.772046: funcgraph_entry:        0.203 us   |      ipv6_find_hdr();
          <idle>-0     [000]  4724.772047: funcgraph_exit:         3.766 us   |    }
          <idle>-0     [000]  4724.772047: funcgraph_entry:        0.058 us   |    dst_release();
          <idle>-0     [000]  4724.772049: funcgraph_entry:                   |    ip_route_input_noref() {
          <idle>-0     [000]  4724.772049: funcgraph_entry:                   |      ip_route_input_rcu() {
          <idle>-0     [000]  4724.772051: funcgraph_entry:                   |        make_kuid() {
          <idle>-0     [000]  4724.772051: funcgraph_entry:        0.472 us   |          map_id_range_down();
          <idle>-0     [000]  4724.772052: funcgraph_exit:         1.000 us   |        }
          <idle>-0     [000]  4724.772052: funcgraph_entry:        3.247 us   |        fib_table_lookup();
          <idle>-0     [000]  4724.772056: funcgraph_entry:                   |        fib_validate_source() {
          <idle>-0     [000]  4724.772056: funcgraph_entry:        0.053 us   |          l3mdev_master_ifindex_rcu();
          <idle>-0     [000]  4724.772057: funcgraph_entry:                   |          make_kuid() {
          <idle>-0     [000]  4724.772057: funcgraph_entry:        0.055 us   |            map_id_range_down();
          <idle>-0     [000]  4724.772057: funcgraph_exit:         0.457 us   |          }
          <idle>-0     [000]  4724.772058: funcgraph_entry:        0.820 us   |          fib_table_lookup();
          <idle>-0     [000]  4724.772059: funcgraph_entry:        0.105 us   |          l3mdev_master_ifindex_rcu();
          <idle>-0     [000]  4724.772059: funcgraph_exit:         3.146 us   |        }
          <idle>-0     [000]  4724.772060: funcgraph_entry:        0.066 us   |        ip_handle_martian_source.isra.40();
          <idle>-0     [000]  4724.772060: funcgraph_exit:       + 10.579 us  |      }
          <idle>-0     [000]  4724.772060: funcgraph_exit:       + 11.295 us  |    }
          <idle>-0     [000]  4724.772060: funcgraph_entry:                   |    kfree_skb() {
          <idle>-0     [000]  4724.772061: funcgraph_entry:                   |      skb_release_all() {
          <idle>-0     [000]  4724.772061: funcgraph_entry:        0.098 us   |        skb_release_head_state();
          <idle>-0     [000]  4724.772061: funcgraph_entry:                   |        skb_release_data() {
          <idle>-0     [000]  4724.772062: funcgraph_entry:                   |          skb_free_head() {
          <idle>-0     [000]  4724.772062: funcgraph_entry:        0.665 us   |            page_frag_free();
          <idle>-0     [000]  4724.772063: funcgraph_exit:         1.184 us   |          }
          <idle>-0     [000]  4724.772063: funcgraph_exit:         1.788 us   |        }
          <idle>-0     [000]  4724.772063: funcgraph_exit:         2.793 us   |      }
          <idle>-0     [000]  4724.772064: funcgraph_entry:                   |      kfree_skbmem() {
          <idle>-0     [000]  4724.772064: funcgraph_entry:        0.240 us   |        kmem_cache_free();
          <idle>-0     [000]  4724.772065: funcgraph_exit:         0.758 us   |      }
          <idle>-0     [000]  4724.772065: funcgraph_exit:         4.366 us   |    }
          <idle>-0     [000]  4724.772065: funcgraph_exit:       + 23.213 us  |  }

おや?今回は上手いこと到達してるのがわかりますね。 上から順番に調べていくと ip_handle_martian_source.isra.40(); という他と比べると何に使ってるのかわからないのがあることに気がつくと思います。 (残念ながらnetworkに詳しくないとエスパー気味かもしれません)

ちょろっと調べるとMartian packet: https://en.wikipedia.org/wiki/Martian_packet と言うものであると理解できます。

IANAの場合は martian packetを interfaceがそのネットワークを利用してないのに到着するパケットと言ってます。詰まるところサブネットを構成してないのに到着するパケットのことです。 詳しくはぐぐるといろいろ出るのでそこに任せます。これとかみると良いでしょう https://www.thegeekdiary.com/how-to-interpret-linux-martian-source-messages/

さて、以上のことから答えは出ました。RPfilterを無効にしましょう

sysctl net.ipv4.conf.eth0.rp_filter=0

簡単だったでしょう?

まとめ

事例がSRv6だけなのは自分の趣味ですが(すいません)、解決方法を見ればなーーんだwって思いましたよね?

さらにハマるパターンというのは少ないので大体

  • rpfilter
  • firewalld
  • フォワード忘れてる
  • arpが解決されてない、

とかそう言うレベルなのでとりあえず当てずっぽうで入れてしまえるんですよね。

しかし初見だとそんなkernelパラメーター知らんがなとなってどう解決すればいいか頭に出てこないと思います。なのでロジカルに解決できるというのは非常に重要なことだと自分は思っています。 本記事が皆さんの毎日生き生きlinux networking生活に役立てば幸いです。