Network Namespace上でVRFを試してみる

この記事は [検索エンジンプロダクトを一緒に開発してた同窓会 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/alumni) の11日目の記事です。

昨日は [kazunee](https://qiita.com/kazunee) さんの [React、D3、GeoJSONを使ってスペインの自治州をタップして覚えるマップクイズを作る](https://qiita.com/kazunee/items/55a9f4fc2f29249be812) でした。

# はじめに
Linux上で複数のネットワークインターフェースを使用して外部通信したい場合、PBR(Policy Base Routing)やVRF(Virtual Routing and Forwarding)を用いると思います。
直接設定すると間違ったときに通信が出来なくなったりしてちょっと怖いなと思い、network namespacesを使って練習が出来たらよいなと思って試してみることにしました。
あまりVRFの記事がないので、VRFを使った構成で記事を書いてみることにしました。

# 構成イメージ
ちょっと雑ですけど、構成イメージを書いてみました。

実際のネットワーク機器に当てはめると下記のような構成になります。（VPIはvirtual raspberrypiの省略を表しています。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321756/fb1e3eff-3787-362a-ba5a-dedbc5e30587.png)

network namespaceで作成するtypeごとに記載すると下記のような下記のような構成になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321756/f5365963-b843-6e23-bc73-f686b7b4d6b2.png)


# セットアップ
上記のイメージで登場した機器をnetwork namespaceで作成していき、諸々設定をしてきます。

```sh
# 機器の用意
ip netns add vpi
ip netns add r1
ip netns add r2
ip link add sw type bridge
ip netns add r3

# 機器の起動
ip netns exec vpi ip link set lo up
ip netns exec r1 ip link set lo up
ip netns exec r2 ip link set lo up
ip netns exec r3 ip link set lo up

# NIC作成
ip link add vpi-veth-to-r1 type veth peer name r1-veth-frm-vpi
ip link add vpi-veth-to-r2 type veth peer name r2-veth-frm-vpi
ip link add r1-veth-to-sw type veth peer name sw-veth-frm-r1
ip link add r2-veth-to-sw type veth peer name sw-veth-frm-r2
ip link add sw-veth-to-r3 type veth peer name r3-veth-frm-sw

# 各機器にNICを紐付ける
ip link set vpi-veth-to-r1 netns vpi
ip link set vpi-veth-to-r2 netns vpi
ip link set r1-veth-frm-vpi netns r1
ip link set r1-veth-to-sw netns r1
ip link set r2-veth-frm-vpi netns r2
ip link set r2-veth-to-sw netns r2
ip link set dev sw-veth-frm-r1 master sw
ip link set dev sw-veth-frm-r2 master sw
ip link set dev sw-veth-to-r3 master sw
ip link set r3-veth-frm-sw netns r3

# vethとswitchの起動
ip netns exec vpi ip link set vpi-veth-to-r1 up
ip netns exec vpi ip link set vpi-veth-to-r2 up
ip netns exec r1 ip link set r1-veth-frm-vpi up
ip netns exec r1 ip link set r1-veth-to-sw up
ip netns exec r2 ip link set r2-veth-frm-vpi up
ip netns exec r2 ip link set r2-veth-to-sw up
ip link set sw-veth-frm-r1 up
ip link set sw-veth-frm-r2 up
ip link set sw-veth-to-r3 up
ip netns exec r3 ip link set r3-veth-frm-sw up
ip link set dev sw promisc on
ip link set dev sw up

# IPアドレスの付与
ip netns exec vpi ip address add 192.168.101.11/24 dev vpi-veth-to-r1
ip netns exec vpi ip address add 192.168.102.22/24 dev vpi-veth-to-r2
ip netns exec r1 ip address add 192.168.101.1/24 dev r1-veth-frm-vpi
ip netns exec r2 ip address add 192.168.102.1/24 dev r2-veth-frm-vpi
ip netns exec r1 ip address add 192.168.100.11/24 dev r1-veth-to-sw
ip netns exec r2 ip address add 192.168.100.22/24 dev r2-veth-to-sw
ip netns exec r3 ip addr add 192.168.100.1/24 dev r3-veth-frm-sw

# ルートテーブルの追加
VPI="ip netns exec vpi"
${VPI} ip route add default via 192.168.101.1 dev vpi-veth-to-r1

# Router1
R1="ip netns exec r1"
${R1} sysctl net.ipv4.ip_forward
${R1} sysctl net.ipv4.ip_forward=1
${R1} ip route add default via 192.168.100.1 dev r1-veth-to-sw
${R1} iptables -t nat -L
${R1} iptables -t nat \
               -A POSTROUTING \
               -s 192.168.101.0/24 \
               -o r1-veth-to-sw \
               -j MASQUERADE

# Router2
R2="ip netns exec r2"
${R2} sysctl net.ipv4.ip_forward
${R2} sysctl net.ipv4.ip_forward=1
${R2} ip route add default via 192.168.100.1 dev r2-veth-to-sw
${R2} iptables -t nat -L
${R2} iptables -t nat \
               -A POSTROUTING \
               -s 192.168.102.0/24 \
               -o r2-veth-to-sw \
               -j MASQUERADE

# VPIから動作確認
# デフォルトゲートウェイの設定をしているので、こちらの疎通は通る
${VPI} ping -I 192.168.101.11 192.168.100.1
64 bytes from 192.168.101.1: icmp_seq=1 ttl=64 time=0.112 ms
64 bytes from 192.168.101.1: icmp_seq=2 ttl=64 time=0.154 ms

# デフォルトゲートウェイの設定をしてないので、こちらの疎通は通らない
${VPI} ping -I 192.168.102.22 192.168.100.1
ping: bind: Cannot assign requested address
```

# VRFの設定
ルートテーブル名を指定してVRFデバイスを作成します。作成後にVRFデバイスとNICを紐づければ設定完了です。

```sh
VPI="ip netns exec vpi"

# defaultのルールを確認する
${VPI} ip rule
0:	from all lookup local
32766:	from all lookup main
32767:	from all lookup default

# vpi-veth-to-r1の状態を確認する
${VPI} ip link
6: vpi-veth-to-r1@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 42:93:b5:dc:f0:f6 brd ff:ff:ff:ff:ff:ff link-netns r1

# vrfの作成
${VPI} ip link add dev r2-vrf type vrf table 10
${VPI} ip link set dev r2-vrf up

# vrfとnicの紐付け
${VPI} ip link set dev vpi-veth-to-r2 master r2-vrf

# route tableが追加されていることを確認
${VPI} ip rule
0:	from all lookup local
1000:	from all lookup [l3mdev-table] # ここが増えた
32766:	from all lookup main
32767:	from all lookup default

# 追加されたroute tableの中身を確認
${VPI} ip route list table 10
broadcast 192.168.102.0 dev vpi-veth-to-r2 proto kernel scope link src 192.168.102.22
192.168.102.0/24 dev vpi-veth-to-r2 proto kernel scope link src 192.168.102.22
local 192.168.102.22 dev vpi-veth-to-r2 proto kernel scope host src 192.168.102.22
broadcast 192.168.102.255 dev vpi-veth-to-r2 proto kernel scope link src 192.168.102.22

# 追加されたroute tableにvpi-veth-to-r2のデフォルトゲートウェイを追加
${VPI} ip route add default via 192.168.102.1 dev vpi-veth-to-r2 table 10

# もう一度ルートテーブルを確認
${VPI} ip route list table 10
default via 192.168.102.1 dev vpi-veth-to-r2 # ここが追加された
broadcast 192.168.102.0 dev vpi-veth-to-r2 proto kernel scope link src 192.168.102.22
192.168.102.0/24 dev vpi-veth-to-r2 proto kernel scope link src 192.168.102.22
local 192.168.102.22 dev vpi-veth-to-r2 proto kernel scope host src 192.168.102.22
broadcast 192.168.102.255 dev vpi-veth-to-r2 proto kernel scope link src 192.168.102.22
```

# 動作確認
router3に向けて、192.168.102.22(vpi-veth-to-r2)を直接指定してリクエストするも失敗します。
これはvpi-veth-to-r2がr2-vrf配下に属しているため、egressあてのpacketはr2-vrf deviceに送信しないと、vpi-veth-to-r2に届かないためです。
```
${VPI} ping -I 192.168.102.22 192.168.100.1
ping: bind: Cannot assign requested address
```

router3に向けて、r2-vrfデバイス経由でリクエストを実行すると無事成功することが確認できました。
```
${VPI} ping -I r2-vrf 192.168.100.1
ping: Warning: source address might be selected on device other than: r2-vrf
PING 192.168.102.1 (192.168.102.1) from 192.168.102.22 r2-vrf: 56(84) bytes of data.
64 bytes from 192.168.102.1: icmp_seq=1 ttl=64 time=0.112 ms
64 bytes from 192.168.102.1: icmp_seq=2 ttl=64 time=0.154 ms
```

# 終わりに
作りたい構成を作る過程でいろんなトラブルシューティングができ、知識が深まっていくことを実感できますし、仮想環境なので何度も試すことが出来るのが良いなと思いました。

# 参考
- [Linux Networking Documentation » Virtual Routing and Forwarding (VRF)](https://www.kernel.org/doc/html/v5.9/networking/vrf.html)
- [ubuntu で VRF 使ってみました。](https://running-dog.net/2017/07/post_2062.html)
- [Linux VRF with L3 Master Device](https://yunazuno.hatenablog.com/entry/2017/06/17/225917)