SONiCでip vrfを使いたい

[SONiCでVRFを使う](https://qiita.com/masru0714/items/86d094f1530948354534)の続きです。
# `ip vrf`が使えない
SONiCは、Debian/GNU Linuxをベースにしたホワイトボックススイッチ用のOSです。現在はDebian 9.0(stretch)ベースとなっています。カーネルバージョンは4.9.189-3+deb9u2とのことです。

SONiCはホワイトボックススイッチ用のOSということで、ネットワーク関連機能の設定、参照、動作確認などを提供しています。しかし、VRF (Virtual Routing and Forwarding) の機能確認のため `ip vrf`コマンドを使用することはできません。

`ip vrf`コマンドを使えない理由は

- iproute2パッケージのバージョンが古く、サポートされていない
- linux kernelのバージョンが古く、サポートされていない

となります。iproute2パッケージ自体は[stretch-backports](https://packages.debian.org/stretch-backports/)により`ip vrf`をサポートするバージョンが提供されていますので、そちらをインストールすることで解決しますが、カーネルが古いままだと `ip vrf exec`を実行することができません。

# その1 単純にカーネルを入れ替えるだけ(失敗)

カーネルを更新できれば解決しそう、ということで、早速やってみることにしました。

## 新しいカーネルのインストール

```
sudo apt-get -t stretch-backports install linux-image-4.19.0-0.bpo.6-amd64
```

リブートすると、無事……元々入っていた4.9.0が起動してきます。あれ?

## GRUBで新しいカーネルを指定する

んー。GRUBかな。ということで再度リブートし、GRUBのメニュー画面で`e`を叩くと、たしかに4.9.0のまま。一時編集ですが4.19に書き換えて、起動させます。

## 動作確認

起動はしました。マネジメントポートは生きています。sshでログインしてバージョン確認。ヨシ!
`ip vrf exec`も動かしてみよう、えーとIPアドレスは、`show ip interfaces`で確認……
あれ、Ethernet*がひとつも見えてないぞ? ログを見てみます。`show logging|less`
すると、

```
INFO pmon#supervisord: 2020-02-21 07:59:43.276 INFO exited: syseepromd (exit status 1; not expected)
```

あーーー。そういえばSONiCはスイッチASICだとかSFP周りの制御のためにカーネルモジュールを用意しているので、バージョンが合わなきゃ動かないってことをすっかり忘れてました。これは標準のLinux kernelには含まれないので、ビルドするところから手を入れないとどうしようもないです。残念ですが、このやり方はここまで。

# その2 新しいカーネルを含むSONiCをビルドする(失敗)
SONiCでは、Linuxカーネルを含むイメージをソースコードから作成できます。このとき、外からバイナリイメージを取得してビルド時間を短縮することもできるのですが、通常はLinuxカーネル自体もソースコードからビルドします。その部分が4.9を対象としているので、4.19など新しいバージョンに置き換えればうまくいくのでは? と思い、試してみました。

## `src/sonic-linux-kernel/Makefile`を変更(不十分)

`sonic-linux-kernel`はsubmoduleです。この下にカーネルソースが展開されたりしていますし`Makefile`もありますので、ここを変更すれば良さげです。

```Makefile.diff
diff --git a/Makefile b/Makefile
index 4d3a28a..6bf651f 100644
--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,10 @@
 SHELL = /bin/bash
 .SHELLFLAGS += -e
 
-KERNEL_ABI_MINOR_VERSION = 2
-KVERSION_SHORT ?= 4.9.0-11-$(KERNEL_ABI_MINOR_VERSION)
+KVERSION_SHORT ?= 4.19.0-0.bpo.6
 KVERSION ?= $(KVERSION_SHORT)-amd64
-KERNEL_VERSION ?= 4.9.189
-KERNEL_SUBVERSION ?= 3+deb9u2
+KERNEL_VERSION ?= 4.19.67
+KERNEL_SUBVERSION ?= 2+deb10u2~bpo9+1
 kernel_procure_method ?= build
 
 LINUX_HEADER_COMMON = linux-headers-$(KVERSION_SHORT)-common_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_all.deb
@@ -20,11 +19,11 @@ ifneq ($(kernel_procure_method), build)
 # Downloading kernel
diff --git a/Makefile b/Makefile
index 4d3a28a..6bf651f 100644
--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,10 @@
 SHELL = /bin/bash
 .SHELLFLAGS += -e
 
-KERNEL_ABI_MINOR_VERSION = 2
-KVERSION_SHORT ?= 4.9.0-11-$(KERNEL_ABI_MINOR_VERSION)
+KVERSION_SHORT ?= 4.19.0-0.bpo.6
 KVERSION ?= $(KVERSION_SHORT)-amd64
-KERNEL_VERSION ?= 4.9.189
-KERNEL_SUBVERSION ?= 3+deb9u2
+KERNEL_VERSION ?= 4.19.67
+KERNEL_SUBVERSION ?= 2+deb10u2~bpo9+1
 kernel_procure_method ?= build
 
 LINUX_HEADER_COMMON = linux-headers-$(KVERSION_SHORT)-common_$(KERNEL_VERSION)-$(KERNEL_SUBVERSION)_all.deb
@@ -20,11 +19,11 @@ ifneq ($(kernel_procure_method), build)
 # Downloading kernel
```

これでいいんじゃないかな。`make`してみます。

```
"SONIC_DEBUGGING_ON"              : ""
"SONIC_PROFILING_ON"              : ""
"KERNEL_PROCURE_METHOD"           : "build"
"BUILD_TIMESTAMP"                 : "20200221.131016"
"BLDENV"                          : "stretch"
"VS_PREPARE_MEM"                  : "yes"
"ENABLE_SFLOW"                    : "y"

[ 01 ] [ target/debs/stretch/linux-headers-4.9.0-11-2-common_4.9.189-3+deb9u2_al
```

あれ?

## 残存する4.9指定部分を変更していく

よく見ると、`build_debian.sh`の下の方にパッケージ取得の記述があり、そこのURLが4.9を取りに行くよう書かれていました。追加のパッチを用意します。

```build_debian.sh.diff
diff --git a/build_debian.sh b/build_debian.sh
index 37db2bde..bb58d7ab 100755
--- a/build_debian.sh
+++ b/build_debian.sh
@@ -37,7 +37,7 @@ if [[ $CONFIGURED_ARCH == armhf || $CONFIGURED_ARCH == arm64 ]]; then
 else
     DOCKER_VERSION=5:18.09.8~3-0~debian-stretch
 fi
-LINUX_KERNEL_VERSION=4.9.0-11-2
+LINUX_KERNEL_VERSION=4.19.0-0.bpo.6
 
 ## Working directory to prepare the file system
 FILESYSTEM_ROOT=./fsroot
@@ -139,9 +139,7 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/initramfs-tools_*.deb || \
 sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/linux-image-${LINUX_KERNEL_VERSION}-*_${CONFIGURED_ARCH}.deb || \
     sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
 sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install acl
-if [[ $CONFIGURED_ARCH == amd64 ]]; then
-    sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install dmidecode hdparm
-fi
+[[ $CONFIGURED_ARCH == amd64 ]] && sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install dmidecode
 
 ## Update initramfs for booting with squashfs+overlay
 cat files/initramfs-tools/modules | sudo tee -a $FILESYSTEM_ROOT/etc/initramfs-tools/modules > /dev/null
```

他多数は `s/4\\.9\\.0-11-2/4.19.0-0.bpo.6/` でだいたい大丈夫なので，`perl -pi` で一気に置換します。

あとは`rules/linux-kernel.mk`です。

```linux-kernel.mk.diff
diff --git a/rules/linux-kernel.mk b/rules/linux-kernel.mk
index e6742bdf..ec36b382 100644
--- a/rules/linux-kernel.mk
+++ b/rules/linux-kernel.mk
@@ -1,9 +1,9 @@
 # linux kernel package
 
-KVERSION_SHORT = 4.9.0-11-2
+KVERSION_SHORT = 4.19.0-0.bpo.6
 KVERSION = $(KVERSION_SHORT)-$(CONFIGURED_ARCH)
-KERNEL_VERSION = 4.9.189
-KERNEL_SUBVERSION = 3+deb9u2
+KERNEL_VERSION = 4.19.67
+KERNEL_SUBVERSION = 2+deb10u2~bpo9+1
 ifeq ($(CONFIGURED_ARCH), armhf)
 # Override kernel version for ARMHF as it uses arm MP (multi-platform) for short version
 KVERSION = $(KVERSION_SHORT)-armmp
```

これで4.9を参照しに行くことはなくなったはず、ということで、再度ビルドしてみます。

```make

Initialized empty Git repository in /sonic/src/sonic-linux-kernel/linux-signed-amd64-4.19.67+2+deb10u2~bpo9+1/.git/
error: patch failed: debian/changelog:1248
error: debian/changelog: patch does not apply
stg import: Diff does not apply cleanly
Makefile:52: recipe for target '/sonic/target/debs/stretch/linux-headers-4.19.0-0.bpo.6-common_4.19.67-2+deb10u2~bpo9+1_all.deb' failed
make[1]: *** [/sonic/target/debs/stretch/linux-headers-4.19.0-0.bpo.6-common_4.19.67-2+deb10u2~bpo9+1_all.deb] Error 2
```

あれ?

## 結論: カーネルの更新は断念

`src/sonic-linux-kernel/patch` に、カーネルに当てるパッチが置かれていますが、ファイル数は105もあります。中を見ると、

- 「4.10からbackportした」(つまり当てなくてよい)
- 「5.1からbackportした」(当たるかわからないけど変更が必要)
- 「backportした」(どこから持ってきたか不明なので調査が必要)
- コメントなし(同じく調査が必要)

といったふうにどのパッチが必要なのかは個別に判断しなければいけないことがわかりました。中には「これはMellanoxのスイッチ向けだ」というのもあって、ターゲット次第では当てずに済むパッチもいくつかあるようでした。

1日1パッチ片付ければ105日で、1日10パッチ片付ければ11日で105個こなせると言えばそのとおりなのですが、パッチ整理だけにそんなに時間を使うわけにも行かないので、4.19への置き換えは断念しました。残念ですがしかたありません。

作業の残骸をgithubに置いておきますので、頑張れる方はこれを取っ掛かりにしてもいいかもしれません。
https://github.com/iMasaruOki/sonic-buildimage/tree/linux-kernel-4.19

※追加情報があります。末尾の後日談2をお読みください

# 最初の問題: `ip vrf exec`はどうなるの?

いろいろさまよっていたところ、代替手段としてcgroupsを使ってどうにかできることがわかりました。
ごそごそとシェルスクリプトを作りました。gistに放り込んでるので、コピペしてご自由にお使いください。
https://gist.github.com/iMasaruOki/a4e4cf03f4b754cb5d5f17633276a381

動作はこんな感じです。

```shell
admin@sonic:~$ ip addr show Ethernet0
8: Ethernet0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9100 qdisc pfifo_fast master Vrf1 state UP group default qlen 1000
    link/ether 6c:b9:c5:16:88:cc brd ff:ff:ff:ff:ff:ff
    inet 172.21.1.1/31 scope global Ethernet0
       valid_lft forever preferred_lft forever
    inet6 fe80::6eb9:c5ff:fe16:88cc/64 scope link
       valid_lft forever preferred_lft forever
admin@sonic:~$ ping 172.21.1.0
PING 172.21.1.0 (172.21.1.0) 56(84) bytes of data.

--- 172.21.1.0 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1006ms

admin@sonic:~$ sudo ./ip-vrf-exec Vrf1 ping 172.21.1.0
PING 172.21.1.0 (172.21.1.0) 56(84) bytes of data.
64 bytes from 172.21.1.0: icmp_seq=1 ttl=64 time=0.421 ms

--- 172.21.1.0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.421/0.421/0.421/0.000 ms

admin@sonic:~$
```

というわけで、どうにかできました。ヨシ！

# 後日談
pingでよければcgroupとかやらずに、単に `ping -I Vrf1 172.21.1.0` でできるとのこと。そういえばnetdev生えてたよなあ、なるほど盲点でした。

# 後日談2
カーネルを入れ替えると言うかベースのDebianをbusterにする開発について、[公式ページ](https://github.com/Azure/SONiC/wiki/Upgrade-SONiC-to-Debian-10)がありました。work in progressなかんじですが、こちらを参照したほうがベターと思います。