VPPとNetwork Namespaceを使ってSRv6ネットワークを構築して遊ぶメモ

## はじめに

Vector Packet Processing (VPP) は，Ciscoが開発した，スイッチ/ルータのパケットを処理する機能を提供するフレームワークです．VPPは，様々なプラットフォームで動作でき，Linux上でも動かすことができます．多くのSRv6の機能をサポートしているので，VPPでSRv6ネットワークを作って色々遊んとでみたいな思っていました．その試験用として，Network NamespaceとVPPを使って簡単にネットワークを組んでみました．

## 作成するネットワーク

[VPP/Progressive VPP Tutorial](https://wiki.fd.io/view/VPP/Progressive_VPP_Tutorial)や[VPP/Segment Routing for IPv6](https://wiki.fd.io/view/VPP/Segment_Routing_for_IPv6:_Overlay_VPN_with_Underlay_Optimization)を参考にしながら，以下のネットワークを組んでみます．ホストがH1からH4の4台で，スイッチがS1からS4の4台です．スイッチはVPPです．S1とS4間でSRv6のVPNをはるイメージで作ります．
![simple_v4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661127/0ac74dbd-f562-f441-23a5-402bab0d3d68.png)

## VPPのインストール

VPPをインストールします．使う環境は以下です．
- OS: Ubuntu22.04
- Linux version: 5.19.0-38-generic

[公式のドキュメント](https://fd.io/docs/vpp/v2101/gettingstarted/installing/index.html)にそって，以下のコマンドを実行してインストールします．
```bash
curl -s https://packagecloud.io/install/repositories/fdio/release/script.deb.sh | sudo bash
apt-get update -y
apt-get install -y vpp vpp-plugin-core vpp-plugin-dpdk
apt-get install -y python3-vpp-api vpp-dbg vpp-dev
```

### Network Namespaceとリンクの作成

ネットワークを作るために，まず，それぞれのノードに対応するNetwork Namespaceを作成します．defaultのNamespaceでもいいですが，今回はVPP用にもNamespaceを作ります．
```bash
ip netns add h1
ip netns add h2
ip netns add h3
ip netns add h4
ip netns add s1
ip netns add s2
ip netns add s3
ip netns add s4
```

上の作成するネットワークに対応するリンクをそれぞれ作ります．
```bash
ip link add name h1_s1 type veth peer name s1_h1
ip link add name h2_s1 type veth peer name s1_h2
ip link add name h3_s4 type veth peer name s4_h3
ip link add name h4_s4 type veth peer name s4_h4
ip link set h1_s1 netns h1
ip link set s1_h1 netns s1
ip link set h2_s1 netns h2
ip link set s1_h2 netns s1
ip link set h3_s4 netns h3
ip link set s4_h3 netns s4
ip link set h4_s4 netns h4
ip link set s4_h4 netns s4

ip link add name s1_s2 type veth peer name s2_s1
ip link add name s1_s3 type veth peer name s3_s1
ip link add name s2_s4 type veth peer name s4_s2
ip link add name s3_s4 type veth peer name s4_s3
ip link set s1_s2 netns s1
ip link set s2_s1 netns s2
ip link set s1_s3 netns s1
ip link set s3_s1 netns s3
ip link set s2_s4 netns s2
ip link set s4_s2 netns s4
ip link set s3_s4 netns s3
ip link set s4_s3 netns s4
```

### VPPの起動

CLIを繋ぐ用のソケットを指定して，S1からS4のVPPインスタンスを起動します．今回定義したcli-vpp-s<number>.sockを使って，CLIとつなぎます．
```bash
ip netns exec s1 /usr/bin/vpp unix {cli-listen /run/vpp/cli-vpp-s1.sock} api-segment { prefix vpp-s1 }
ip netns exec s2 /usr/bin/vpp unix {cli-listen /run/vpp/cli-vpp-s2.sock} api-segment { prefix vpp-s2 }
ip netns exec s3 /usr/bin/vpp unix {cli-listen /run/vpp/cli-vpp-s3.sock} api-segment { prefix vpp-s3 }
ip netns exec s4 /usr/bin/vpp unix {cli-listen /run/vpp/cli-vpp-s4.sock} api-segment { prefix vpp-s4 }
```

それぞれのVPPインスタンスにCLIを繋ぐためには以下のコマンドのように，`-s`でソケットを指定して実行します．
```bash
vppctl -s /run/vpp/cli-vpp-s<number>.sock <command>
```

### VPPの設定

ソケットを通じて，VPPを設定していきます．まず，NamespaceのインターフェースをVPP用に作ります．`create host-interface`では，指定したインターフェース名に`host-`がついたインターフェースが作成されます．
```bash
vppctl -s /run/vpp/cli-vpp-s1.sock create host-interface name s1_s2
vppctl -s /run/vpp/cli-vpp-s1.sock create host-interface name s1_s3
vppctl -s /run/vpp/cli-vpp-s1.sock create host-interface name s1_h1
vppctl -s /run/vpp/cli-vpp-s1.sock create host-interface name s1_h2
vppctl -s /run/vpp/cli-vpp-s2.sock create host-interface name s2_s1
vppctl -s /run/vpp/cli-vpp-s2.sock create host-interface name s2_s4
vppctl -s /run/vpp/cli-vpp-s3.sock create host-interface name s3_s1
vppctl -s /run/vpp/cli-vpp-s3.sock create host-interface name s3_s4
vppctl -s /run/vpp/cli-vpp-s4.sock create host-interface name s4_s2
vppctl -s /run/vpp/cli-vpp-s4.sock create host-interface name s4_s3
vppctl -s /run/vpp/cli-vpp-s4.sock create host-interface name s4_h3
vppctl -s /run/vpp/cli-vpp-s4.sock create host-interface name s4_h4
vppctl -s /run/vpp/cli-vpp-s1.sock set interface state host-s1_s2 up
vppctl -s /run/vpp/cli-vpp-s1.sock set interface state host-s1_s3 up
vppctl -s /run/vpp/cli-vpp-s1.sock set interface state host-s1_h1 up
vppctl -s /run/vpp/cli-vpp-s1.sock set interface state host-s1_h2 up
vppctl -s /run/vpp/cli-vpp-s2.sock set interface state host-s2_s1 up
vppctl -s /run/vpp/cli-vpp-s2.sock set interface state host-s2_s4 up
vppctl -s /run/vpp/cli-vpp-s3.sock set interface state host-s3_s1 up
vppctl -s /run/vpp/cli-vpp-s3.sock set interface state host-s3_s4 up
vppctl -s /run/vpp/cli-vpp-s4.sock set interface state host-s4_s2 up
vppctl -s /run/vpp/cli-vpp-s4.sock set interface state host-s4_s3 up
vppctl -s /run/vpp/cli-vpp-s4.sock set interface state host-s4_h3 up
vppctl -s /run/vpp/cli-vpp-s4.sock set interface state host-s4_h4 up
```

次に，それぞれのインターフェースに対して，ネットワークの図を参考にアドレスを割り当てます．
```bash
vppctl -s /run/vpp/cli-vpp-s1.sock set interface ip address host-s1_s2 fd00:a::1/32
vppctl -s /run/vpp/cli-vpp-s1.sock set interface ip address host-s1_s3 fd00:b::1/32
vppctl -s /run/vpp/cli-vpp-s1.sock set interface ip address host-s1_h1 192.168.1.1/24
vppctl -s /run/vpp/cli-vpp-s1.sock set interface ip address host-s1_h2 192.168.2.1/24
vppctl -s /run/vpp/cli-vpp-s2.sock set interface ip address host-s2_s1 fd00:a::2/32
vppctl -s /run/vpp/cli-vpp-s2.sock set interface ip address host-s2_s4 fd00:c::1/32
vppctl -s /run/vpp/cli-vpp-s3.sock set interface ip address host-s3_s1 fd00:b::2/32
vppctl -s /run/vpp/cli-vpp-s3.sock set interface ip address host-s3_s4 fd00:d::1/32
vppctl -s /run/vpp/cli-vpp-s4.sock set interface ip address host-s4_s2 fd00:c::2/32
vppctl -s /run/vpp/cli-vpp-s4.sock set interface ip address host-s4_s3 fd00:d::2/32
vppctl -s /run/vpp/cli-vpp-s4.sock set interface ip address host-s4_h3 192.168.3.1/24
vppctl -s /run/vpp/cli-vpp-s4.sock set interface ip address host-s4_h4 192.168.4.1/24
```

### ホストの設定

ホストに設定をしていきます．それぞれ，IPアドレスとデフォルトルートを設定しておきます．
```bash
ip netns exec h1 ip addr add 192.168.1.2/24 dev h1_s1
ip netns exec h1 ip route add default via 192.168.1.1
ip netns exec h2 ip addr add 192.168.2.2/24 dev h2_s1
ip netns exec h2 ip route add default via 192.168.2.1
ip netns exec h3 ip addr add 192.168.3.2/24 dev h3_s4
ip netns exec h3 ip route add default via 192.168.3.1
ip netns exec h4 ip addr add 192.168.4.2/24 dev h4_s4
ip netns exec h4 ip route add default via 192.168.4.1
```

### SIDの設定

それぞれSIDの設定をしていきます．それぞれVPPインスタンスに，ネットワーク図を参考にendのSIDを割り当てていきます．
また，他のSIDにルーティングできるように，SIDに対するルートの設定も行います．(本当は，BGPといったルーティングプロトコルで設定すべきですが，BGPデーモンとの組み合わせ方がよくわからなかったので今回は手動で設定しています)
```bash
vppctl -s /run/vpp/cli-vpp-s1.sock set sr encaps source addr fd00:bbbb:a::
vppctl -s /run/vpp/cli-vpp-s1.sock sr localsid address fd00:bbbb:a:: behavior end
vppctl -s /run/vpp/cli-vpp-s1.sock ip route add fd00:bbbb:b::/48 via fd00:a::2 host-s1_s2
vppctl -s /run/vpp/cli-vpp-s1.sock ip route add fd00:bbbb:c::/48 via fd00:b::2 host-s1_s3
vppctl -s /run/vpp/cli-vpp-s1.sock ip route add fd00:bbbb:d::/48 via fd00:a::2 host-s1_s2
vppctl -s /run/vpp/cli-vpp-s1.sock ip route add fd00:bbbb:d::/48 via fd00:b::2 host-s1_s3

vppctl -s /run/vpp/cli-vpp-s2.sock set sr encaps source addr fd00:bbbb:b::
vppctl -s /run/vpp/cli-vpp-s2.sock sr localsid address fd00:bbbb:b:: behavior end
vppctl -s /run/vpp/cli-vpp-s2.sock ip route add fd00:bbbb:a::/48 via fd00:a::1 host-s2_s1
vppctl -s /run/vpp/cli-vpp-s2.sock ip route add fd00:bbbb:d::/48 via fd00:c::2 host-s2_s4

vppctl -s /run/vpp/cli-vpp-s3.sock set sr encaps source addr fd00:bbbb:c::
vppctl -s /run/vpp/cli-vpp-s3.sock sr localsid address fd00:bbbb:c:: behavior end
vppctl -s /run/vpp/cli-vpp-s3.sock ip route add fd00:bbbb:a::/48 via fd00:b::1 host-s3_s1
vppctl -s /run/vpp/cli-vpp-s3.sock ip route add fd00:bbbb:d::/48 via fd00:d::2 host-s3_s4

vppctl -s /run/vpp/cli-vpp-s4.sock set sr encaps source addr fd00:bbbb:d::
vppctl -s /run/vpp/cli-vpp-s4.sock sr localsid address fd00:bbbb:d:: behavior end
vppctl -s /run/vpp/cli-vpp-s4.sock ip route add fd00:bbbb:b::/48 via fd00:c::1 host-s4_s2
vppctl -s /run/vpp/cli-vpp-s4.sock ip route add fd00:bbbb:c::/48 via fd00:d::1 host-s4_s3
vppctl -s /run/vpp/cli-vpp-s4.sock ip route add fd00:bbbb:a::/48 via fd00:c::1 host-s4_s2
vppctl -s /run/vpp/cli-vpp-s4.sock ip route add fd00:bbbb:a::/48 via fd00:d::1 host-s4_s3
```

IPv4をIPv6でカプセル化してVPNをはるための設定をしていきます．
まずは，192.168.1.0/24と192.168.4.0/24のネットワークを接続できるようにします．
S1の設定は以下のとおりです．Headendの設定はencapを指定し，Endの動作はEnd.DT4を指定します．DT4では，デフォルトのルーティングテーブルのIDである0を指定しています．
```bash
vppctl -s /run/vpp/cli-vpp-s1.sock sr localsid address fd00:bbbb:a::4 behavior end.dt4 0
vppctl -s /run/vpp/cli-vpp-s1.sock sr policy add bsid fd00:bbbb:a::e4 next fd00:bbbb:d::4 encap
vppctl -s /run/vpp/cli-vpp-s1.sock sr steer l3 192.168.4.0/24 via bsid fd00:bbbb:a::e4
```

S4も同様に，encapの設定とEnd.DT4の設定を行います．
```bash
vppctl -s /run/vpp/cli-vpp-s4.sock sr localsid address fd00:bbbb:d::4 behavior end.dt4 0
vppctl -s /run/vpp/cli-vpp-s4.sock sr policy add bsid fd00:bbbb:d::e4 next fd00:bbbb:a::4 encap
vppctl -s /run/vpp/cli-vpp-s4.sock sr steer l3 192.168.1.0/24 via bsid fd00:bbbb:d::e4
```
これで，あらかたの設定は終わりました．

### 接続性の確認

それでは，接続性の確認です．実行結果から，pingが通っていることが確認できます．(ただ，最初のパケットが通ってなさそうな結果が出たんですが，理由はよくわかりません...)
```bash
$ sudo ip netns exec h1 ping 192.168.3.2 
PING 192.168.3.2 (192.168.3.2) 56(84) bytes of data.
64 bytes from 192.168.3.2: icmp_seq=2 ttl=62 time=24.0 ms
64 bytes from 192.168.3.2: icmp_seq=3 ttl=62 time=23.7 ms
64 bytes from 192.168.3.2: icmp_seq=4 ttl=62 time=21.3 ms
64 bytes from 192.168.3.2: icmp_seq=5 ttl=62 time=28.0 ms
64 bytes from 192.168.3.2: icmp_seq=6 ttl=62 time=27.0 ms
^C
--- 192.168.3.2 ping statistics ---
6 packets transmitted, 5 received, 16.6667% packet loss, time 5032ms
rtt min/avg/max/mdev = 21.349/24.809/28.006/2.414 ms
```

### 追加のルート設定

192.168.2.0/24と192.168.4.0/24のネットワークを繋ぐルートを設定していきます．先ほどとは違い，経由するノードを指定したルートを設定します．S1の設定は以下です．
```bash
vppctl -s /run/vpp/cli-vpp-s1.sock sr policy add bsid fd00:bbbb:a::3e4 next fd00:bbbb:c:: next fd00:bbbb:d::4 encap
vppctl -s /run/vpp/cli-vpp-s1.sock sr steer l3 192.168.4.0/24 via bsid fd00:bbbb:a::3e4
```

S4も同様に設定します．
```bash
vppctl -s /run/vpp/cli-vpp-s4.sock sr policy add bsid fd00:bbbb:d::2e4 next fd00:bbbb:b:: next fd00:bbbb:a::4 encap
vppctl -s /run/vpp/cli-vpp-s4.sock sr steer l3 192.168.2.0/24 via bsid fd00:bbbb:d::2e4
```

### パケットの確認

pingを実行して，接続性を確認します．また，S3でパケットをキャプチャし，パケットを確認します．以下が，pingの実行結果です．
```bash
$ sudo ip netns exec h2 ping 192.168.4.2                                             17:43:04
PING 192.168.4.2 (192.168.4.2) 56(84) bytes of data.
64 bytes from 192.168.4.2: icmp_seq=2 ttl=62 time=36.0 ms
64 bytes from 192.168.4.2: icmp_seq=3 ttl=62 time=35.5 ms
64 bytes from 192.168.4.2: icmp_seq=4 ttl=62 time=33.4 ms
64 bytes from 192.168.4.2: icmp_seq=5 ttl=62 time=40.2 ms
64 bytes from 192.168.4.2: icmp_seq=6 ttl=62 time=38.8 ms
^C
--- 192.168.4.2 ping statistics ---
6 packets transmitted, 5 received, 16.6667% packet loss, time 5027ms
rtt min/avg/max/mdev = 33.403/36.785/40.209/2.425 ms
```

以下がpingのパケットです．SIDのリストが確認できます．
![screenshot000139.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661127/019e4553-f61b-0dd5-8bbc-553b82bc1f18.jpeg)

## ネットワークの後始末

あらかたの検証が終われば，ネットワークを削除します．ネームスペースと，VPPのインスタンスを以下のコマンドで削除します．
```
ip netns delete h1
ip netns delete h2
ip netns delete h3
ip netns delete h4
ip netns delete s1
ip netns delete s2
ip netns delete s3
ip netns delete s4

kill $(ps aux | grep '[c]li-vpp-s1.sock' | awk '{print $2}')
kill $(ps aux | grep '[c]li-vpp-s2.sock' | awk '{print $2}')
kill $(ps aux | grep '[c]li-vpp-s3.sock' | awk '{print $2}')
kill $(ps aux | grep '[c]li-vpp-s4.sock' | awk '{print $2}')
```

## 終わりに
今回は，VPPとNetwork namespaceを使ってSRv6ネットワークを組んで遊んでみました．次は，ルーティングプロトコルと組み合わせて実行してみたいなと思っています．

## 参考
- [VPP/Progressive VPP Tutorial](https://wiki.fd.io/view/VPP/Progressive_VPP_Tutorial)
- [VPP/Segment Routing for IPv6](https://wiki.fd.io/view/VPP/Segment_Routing_for_IPv6:_Overlay_VPN_with_Underlay_Optimization)
- [Srv6 cli reference](https://docs.fd.io/vpp/23.06/cli-reference/clis/clicmd_src_vnet_srv6.html)