Docker上のFRRoutingでMPLS-VPNを動かしてみる

# はじめに
[Docker上のFRRoutingでOSPFを動かしてみる](https://qiita.com/tk_n/items/648e225d06085a0e2530)に引き続き、個人のノートPCでお手軽に、なるべく省リソースでネットワークの勉強/検証をしたい、という動機から、Docker+FRRoutingを試してみています。今回はMPLS-VPNをやっていきます。
※誤り等ありましたらお気軽にコメントいただければと思います。

2021/1/13追記：[docker-compose版](https://github.com/tkna/docker_frrouting/tree/master/mpls-vpn)も作りました。


# 参考にしたページ
MPLSについてはこちらのページにお世話になっているので、検証構成とコンフィグ設計はこちらをベースにしました。

* [MPLSをはじめから](https://www.infraexpert.com/study/study29.html)
* [MPLS-VPN 検証構成とコンフィグ設定（PE-CE間でスタティックルートを使用）](https://www.infraeye.com/2018/01/04/mplsvpn01/)

また、FRRoutingを使ったMPLSの環境構築については、こちらを参考にさせていただきました。

* [FRRoutingでMPLS L3-VPNのようなもの](https://qiita.com/sandopan65/items/23e4645202326893254f)
* [Use Linux as an MPLS Router]
(https://blog.swineson.me/en/use-linux-as-an-mpls-router/)

# 検証環境
* Windows 10 (Core i7, 12GB RAM)
* VirtualBox 6.1
* Ubuntu 18.04 (2 Core, 4GB RAM, 10GB Disk)
* Docker 20.10.2
* FRRouting 7.5

Dockerまではインストールされた状態から始めます。

# 検証構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586231/6984d747-f3d3-352c-653a-635e355fcf18.png)

[MPLS-VPN 検証構成とコンフィグ設定（PE-CE間でスタティックルートを使用）](https://www.infraeye.com/2018/01/04/mplsvpn01/)に則った構成とアドレス設計です。違うのは、各ネットワークで.1ではなく.2を使っていること(Docker networkのbridge用アドレスを.1とするため)、PE1/PE2でコア側をeth0、ユーザ側を順にeth1, 2としている所です。

青字はdocker network(bridge)での実装におけるネットワーク名です。Dockerでは、複数のネットワークをコンテナにアタッチする際、GWやInternal等の属性が同じ場合は、ネットワーク名の辞書順にコンテナ内のeth0, 1, 2, ...に割り当てられるようです[^1] [^2]。今回、PEルータではコア側をeth0、ユーザ側を順にeth1, 2, ...と統一したかったので、上記のような命名規則で対応することにしました。

[^1]: https://github.com/moby/moby/issues/25181
[^2]: https://github.com/moby/libnetwork/issues/2093



# 全体の流れ
手順全体の流れと、設定対象機器は以下の通りです。

|順番|内容|Pルータ|PEルータ|CEルータ|
|----|---|-------|--------|--------|
|1|ホストOSの設定|-|-|-|
|2|Dockerネットワークとコンテナ作成|○|○|○|
|3|FRR daemonの設定|○|○|-|
|4|OSPFの設定|○|○|-|
|5|LDPの設定|○|○|-|
|6|MP-BGPの設定|-|○|-|
|7|VRFの設定|-|○|-|
|8|CEルータの設定|-|-|○|


# 1. ホストOSの設定
はじめに、ホストOS(Ubuntu VM)の設定をします。

[公式docs](http://docs.frrouting.org/en/latest/installation.html)に従って、MPLSのカーネルモジュールをロードします。
/etc/modules-load.d/modules.confを編集し、mpls_router、mpls_iptunnelを追加。

```:/etc/modules-load.d/modules.conf
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
mpls_router
mpls_iptunnel
```

```
# modprobe mpls-router mopls-iptunnel
modprobe: FATAL: Module mpls-router not found in directory /lib/modules/4.15.0-124-generic
```
エラーになったので、公式の記載に従って拡張パッケージをインストールします。

```
# apt install linux-modules-extra-`uname -r`
```

※[公式](http://docs.frrouting.org/projects/dev-guide/en/latest/building-frr-for-ubuntu1804.html?highlight=ubuntu%2018.04)では``apt-get install linux-modules-extra-`uname -r`-generic``となっていましたが最後の`-generic`はいらないですね。

これで改めてカーネルモジュールをロードします。

```
# modprobe mpls-router mpls-iptunnel
```
```
# lsmod | grep mpls
mpls_router            28672  0
ip_tunnel              24576  1 mpls_router
```
無事ロードされました。
試しにコンテナを作って、コンテナ内でlsmodをすると、上記2つが反映されています。

```
# docker run -dit --name test alpine
# docker exec -it test lsmod | grep mpls
mpls_router            28672  0
ip_tunnel              24576  1 mpls_router
```

MPLS転送を有効にするためのカーネルパラメータの変更は、コンテナ内でやればいいようなので、後でやることにします。

# 2. Dockerネットワークとコンテナ作成
Docker Network(bridge)を使って独自ネットワークを作り、そこにコンテナをつないでいきます。

ただし、ここで1つ問題があります。Docker Networkを使ったネットワーク作成はお手軽なのですが、コンテナをつないだ際、IPアドレスが自動で設定されます。(もしくは--ipオプションで手動指定)。こうして自動もしくは手動でコンテナkernelによって設定されたIPアドレスは、FRRoutingを使って変更・削除することはできません。また、デフォルトルートを含む複数のルートも自動で設定されてしまい、FRRoutingからは変更・削除できません(かつ、[Kernel Routeは最優先](http://docs.frrouting.org/en/latest/zebra.html#administrative-distance))。IPアドレスであればコンテナ作成の際に手動設定してしまい、FRRoutingからは触らないようにする、というのも可能ですが、最大限vtyshを使って設定したいですし、ルートの方はネットワーク設定に支障がある場合もありますので、`ip addr del`や`ip route del`コマンドで削除する必要があります。
※こうした自動設定を止める設定があるかもしれませんが見つけられていません...

地道に1つずつ手動で消していってもいいのですが、今回コンテナが7つもあるので、そういった初期設定済みのFRRイメージを作成するDockerfileを作りました。これを使って作った独自のFRRイメージを使って環境構築していきたいと思います。ついでにtcpdumpもインストールしています。
(tcpdumpに関してはホストOS側でvethを特定して...でもいいですが)

## 初期設定済みのFRRoutingイメージを作る
適当なフォルダにDockerfileを作ります。

```:Dockerfile
FROM frrouting/frr:v7.5.0
RUN apk update && apk add tcpdump
COPY docker-start /usr/lib/frr/docker-start
```
もともとのFRRoutingのイメージに、tcpdumpをインストールし、かつヘルパースクリプトを独自のものに置き換えています。

同じディレクトリにdocker-startという名前でヘルパースクリプトを作成。

```sh:docker-start
#!/bin/sh

set -e

# Delete all IP addresses on ethX
devlist=`ip -o addr | cut -d\  -f 2 | sed '1d'`
for dev in $devlist
do
  IP=`ip -o addr show $dev | cut -d\  -f 7`
  ip addr del $IP dev $dev
done

# Delete all routes
routelist=`ip route | cut -d\  -f 1`
for route in $routelist
do
  ip route del $route
done


##
# For volume mounts...
##
chown -R frr:frr /etc/frr || true
/usr/lib/frr/frrinit.sh start

# Sleep forever
exec tail -f /dev/null
```
これは、[もともとFRRoutingで使われているスクリプト](https://github.com/FRRouting/frr/blob/master/docker/alpine/docker-start)に、IPアドレスとルート全削除の処理を加えたものです。

スクリプトに実行権限を与えて、docker build

```
# chmod +x docker-start
# docker build -t frr .
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
frr                 latest              42eb8fb2ebb3        4 seconds ago       128MB

```

以降、コンテナ作成の際は`frrouting/frr:v7.5.0`ではなく、`frr`を使っていきます。


## ネットワークの作成
まずnetworkを作ります。

```
# docker network create net1 --subnet=10.1.1.0/24
# docker network create net2 --subnet=10.1.2.0/24
# docker network create net3 --subnet=172.16.1.0/24
# docker network create net4 --subnet=172.16.2.0/24
# docker network create net5 --subnet=172.16.3.0/24
# docker network create net6 --subnet=172.16.4.0/24
```
```
# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
4f627b43f43a   bridge    bridge    local
62dced673495   host      host      local
b6c924d827f5   net1      bridge    local
ac750081ae13   net2      bridge    local
f96230418508   net3      bridge    local
9553456d0ad7   net4      bridge    local
5c39ee2ea4d7   net5      bridge    local
f2e362f4eaad   net6      bridge    local
20f96b0038a3   none      null      local
```

## Pルータの作成
名前が1文字のコンテナは作れないようだったので、コンテナ名、ホスト名はPRとしました。

```
# docker create -it --name PR --hostname PR --privileged --net net1 frr
# docker network connect net2 PR
# docker start PR
```
いきなり`docker run`するのではなく、`docker create` `docker network connect`でネットワークを全部つないでからコンテナを起動します。

## PEルータの作成

```
# docker create -it --name PE1 --hostname PE1 --privileged --net net1 frr
# docker network connect net3 PE1
# docker network connect net5 PE1
# docker start PE1

# docker create -it --name PE2 --hostname PE2 --privileged --net net2 frr
# docker network connect net4 PE2
# docker network connect net6 PE2
# docker start PE2
```

## CEルータの作成

```
# docker run -dit --name CE1 --hostname CE1 --privileged --net net3 frr
# docker run -dit --name CE2 --hostname CE2 --privileged --net net4 frr
# docker run -dit --name CE3 --hostname CE3 --privileged --net net5 frr
# docker run -dit --name CE4 --hostname CE4 --privileged --net net6 frr
```
## 動作確認
```
# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
01c534d3bafa   frr       "/sbin/tini -- /usr/…"   5 seconds ago    Up 3 seconds              CE4
861ce203d498   frr       "/sbin/tini -- /usr/…"   20 seconds ago   Up 19 seconds             CE3
acd7834f981b   frr       "/sbin/tini -- /usr/…"   32 seconds ago   Up 31 seconds             CE2
5ce59162b01b   frr       "/sbin/tini -- /usr/…"   56 seconds ago   Up 54 seconds             CE1
99bb821a4cb8   frr       "/sbin/tini -- /usr/…"   4 minutes ago    Up 3 minutes              PE2
824e0a5a5337   frr       "/sbin/tini -- /usr/…"   5 minutes ago    Up 5 minutes              PE1
aeaab8e9b887   frr       "/sbin/tini -- /usr/…"   6 minutes ago    Up 6 minutes              PR
```
コンテナが7つ起動していることを確認します。

# 3. FRR Daemonの設定
FRRの設定ファイルで、起動するdaemonを指定します。`/etc/frr/daemons`を編集し、起動するdaemonを`yes`にしていきます。

各ルータにおいて(追加で)起動するdaemonは以下の通り。

|ルータ種別|起動するdaemon|
|---------|--------------|
|Pルータ|ospfd, ldpd|
|PEルータ|bgpd, ospfd, ldpd|
|CEルータ|-|

今回、CEルータとPEルータの間はStaticルーティングを使うため、CEルータでは特に追加でdaemonを起動する必要はありません。

## Pルータ
```:/etc/frr/daemons
(略)
ospfd=yes
(略)
ldpd=yes
(略)
```

## PEルータ
```:/etc/frr/daemons
(略)
bgpd=yes
(略)
ospfd=yes
(略)
ldpd=yes
(略)
``` 

それぞれ、設定ファイルの編集が終わったら設定反映のため、FRRのプロセスを再起動します。

Pルータの例：

```:PR
/ # /usr/lib/frr/frrinit.sh restart
Stopped watchfrr
Cannot stop ldpd: pid file not found
Cannot stop ospfd: pid file not found
Stopped staticdStopped zebra

Started watchfrr
```
Cannot stop ldpdとCannot stop ospfdが出ていますが、初回なので問題ありません。

## 動作確認
各コンテナ内で、指定したプロセスが立ち上がっているか確認します。
Pルータの例：

```:PR
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- /usr/lib/frr/docker-start
    7 root      0:00 tail -f /dev/null
   56 root      0:00 /bin/sh
   88 root      0:00 /usr/lib/frr/watchfrr -d -F traditional zebra ospfd ldpd staticd
  106 frr       0:00 /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
  111 frr       0:00 /usr/lib/frr/ospfd -d -F traditional -A 127.0.0.1
  114 frr       0:00 /usr/lib/frr/ldpd -L -u frr -g frr
  115 frr       0:00 /usr/lib/frr/ldpd -E -u frr -g frr
  116 frr       0:00 /usr/lib/frr/ldpd -d -F traditional -A 127.0.0.1
  120 frr       0:00 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1
  122 root      0:00 ps
```
ospfdとldpdが立ち上がっています。

# 4. OSPFの設定
まずはMPLSコア網の土台となるアンダーレイネットワークを作っていきます。今回はIGPとしてOSPFを使用。また、各ルータのループバックIFとして、dummy IFを作るやり方のページもありますが、本記事では各コンテナに最初からある`lo`を活用していきます。

## Pルータ
```:PR(vtysh)
PR# conf
PR(config)# interface lo
PR(config-if)# ip address 9.9.9.9/32
PR(config-if)# exit

PR(config)# interface eth0
PR(config-if)# ip address 10.1.1.254/24
PR(config-if)# exit

PR(config)# interface eth1
PR(config-if)# ip address 10.1.2.254/24
PR(config-if)# exit

PR(config)# router ospf
PR(config-router)# network 9.9.9.9/32 area 0
PR(config-router)# network 10.1.1.0/24 area 0
PR(config-router)# network 10.1.2.0/24 area 0
PR(config-router)# end
```

## PEルータ
```:PE1(vtysh)
PE1# conf
PE1(config)# interface lo
PE1(config-if)# ip address 1.1.1.1/32
PE1(config-if)# exit

PE1(config)# interface eth0
PE1(config-if)# ip address 10.1.1.2/24
PE1(config-if)# exit

PE1(config)# interface eth1
PE1(config-if)# ip address 172.16.1.254/24
PE1(config-if)# exit

PE1(config)# interface eth2
PE1(config-if)# ip address 172.16.3.254/24
PE1(config-if)# exit

PE1(config)# router ospf
PE1(config-router)# network 1.1.1.1/32 area 0
PE1(config-router)# network 10.1.1.0/24 area 0
PE1(config-router)# end
```

```:PE2(vtysh)
PE1# conf
PE1(config)# interface lo
PE1(config-if)# ip address 2.2.2.2/32
PE1(config-if)# exit

PE1(config)# interface eth0
PE1(config-if)# ip address 10.1.2.2/24
PE1(config-if)# exit

PE1(config)# interface eth1
PE1(config-if)# ip address 172.16.2.254/24
PE1(config-if)# exit

PE1(config)# interface eth2
PE1(config-if)# ip address 172.16.4.254/24
PE1(config-if)# exit

PE1(config)# router ospf
PE1(config-router)# network 2.2.2.2/32 area 0
PE1(config-router)# network 10.1.2.0/24 area 0
PE1(config-router)# end
```


## 動作確認
Pルータで確認してみます。

```:PR(vtysh)
PR# show ip ospf neighbor

Neighbor ID     Pri State           Dead Time Address         Interface                        RXmtL RqstL DBsmL
1.1.1.1           1 Full/Backup       36.732s 10.1.1.2        eth0:10.1.1.254                      0     0     0
2.2.2.2           1 Full/Backup       34.071s 10.1.2.2        eth1:10.1.2.254                      0     0     0
```
1.1.1.1, 2.2.2.2とneighborが確立できていることを確認。

```:PR(vtysh)
PR# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

O>* 1.1.1.1/32 [110/10] via 10.1.1.2, eth0, weight 1, 00:04:48
O>* 2.2.2.2/32 [110/10] via 10.1.2.2, eth1, weight 1, 00:01:19
O   9.9.9.9/32 [110/0] is directly connected, lo, weight 1, 00:09:18
C>* 9.9.9.9/32 is directly connected, lo, 00:12:31
O   10.1.1.0/24 [110/10] is directly connected, eth0, weight 1, 00:09:08
C>* 10.1.1.0/24 is directly connected, eth0, 00:12:00
O   10.1.2.0/24 [110/10] is directly connected, eth1, weight 1, 00:08:45
C>* 10.1.2.0/24 is directly connected, eth1, 00:11:39
```
1.1.1.1/32, 2.2.2.2/32へのルートがOSPFによって学習できており、FIBに載っていること(*がついていること)を確認します。

念のためpingも。vtyshから抜けて確認。

```:PR
/ # ping -I 9.9.9.9 1.1.1.1
PING 1.1.1.1 (1.1.1.1) from 9.9.9.9: 56 data bytes
64 bytes from 1.1.1.1: seq=0 ttl=64 time=0.093 ms
64 bytes from 1.1.1.1: seq=1 ttl=64 time=0.488 ms
64 bytes from 1.1.1.1: seq=2 ttl=64 time=0.220 ms
^C
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.093/0.267/0.488 ms

/ # ping -I 9.9.9.9 2.2.2.2
PING 2.2.2.2 (2.2.2.2) from 9.9.9.9: 56 data bytes
64 bytes from 2.2.2.2: seq=0 ttl=64 time=0.140 ms
64 bytes from 2.2.2.2: seq=1 ttl=64 time=0.340 ms
64 bytes from 2.2.2.2: seq=2 ttl=64 time=0.486 ms
^C
--- 2.2.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.140/0.322/0.486 ms
```
通りました。

ちなみに、この時点でPE1(1.1.1.1)→PE2(2.2.2.2)にpingを打つと、当然ですが普通のIPパケットとして転送されます。PルータでtcpdumpしながらPE1→PE2にpingしてみます。

```:PE1
# ping -I 1.1.1.1 2.2.2.2
PING 2.2.2.2 (2.2.2.2) from 1.1.1.1: 56 data bytes
64 bytes from 2.2.2.2: seq=0 ttl=63 time=0.146 ms
64 bytes from 2.2.2.2: seq=1 ttl=63 time=0.374 ms
64 bytes from 2.2.2.2: seq=2 ttl=63 time=0.373 ms
^C
--- 2.2.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.146/0.297/0.374 ms
```
```:PR
/ # tcpdump -n -i any icmp
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
06:51:43.879037 eth0  In  IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 44032, seq 0, length 64
06:51:43.879052 eth1  Out IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 44032, seq 0, length 64
06:51:43.879080 eth1  In  IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 44032, seq 0, length 64
06:51:43.879084 eth0  Out IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 44032, seq 0, length 64
```


# 5. LDPの設定
MPLSの設定に入っていきます。公式docsに記載の通り、事前準備としてカーネルパラメータを変更してMPLS転送を有効にする必要があります。各ルータでカーネルパラメータ設定→LDP設定の順で進めます。

## Pルータ

### MPLSの有効化
カーネルパラメータを変更してMPLSを有効にします。
まずは状態確認。

```:PR
/ # sysctl -a | grep mpls
sysctl: error reading key 'net.ipv6.conf.all.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.default.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth0.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth1.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.lo.stable_secret': I/O error
net.mpls.conf.eth0.input = 0
net.mpls.conf.eth1.input = 0
net.mpls.conf.lo.input = 0
net.mpls.default_ttl = 255
net.mpls.ip_ttl_propagate = 1
net.mpls.platform_labels = 0
```
ipv6関連のエラーが出ていますが、一旦気にしないで進めます。

Pルータではeth0, eth1の両方でMPLS転送を有効にします。`net.mpls.conf.eth0.input`と`net.mpls.conf.eth1.input`を1に、`net.mpls.platform_labels`を100000に設定するため、下記のファイルに追記。

```:/etc/sysctl.conf
# content of this file will override /etc/sysctl.d/*
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
net.mpls.platform_labels = 100000
```
設定を反映します。

```
/ # sysctl -p
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
net.mpls.platform_labels = 100000
```
```
# sysctl -a | grep mpls
sysctl: error reading key 'net.ipv6.conf.all.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.default.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth0.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.eth1.stable_secret': I/O error
sysctl: error reading key 'net.ipv6.conf.lo.stable_secret': I/O error
net.mpls.conf.eth0.input = 1
net.mpls.conf.eth1.input = 1
net.mpls.conf.lo.input = 0
net.mpls.default_ttl = 255
net.mpls.ip_ttl_propagate = 1
net.mpls.platform_labels = 100000
```
値が反映されました。

### LDPの設定
MPLSのラベル交換のため、LDPを動かします。

```:PR(vtysh)
PR# conf
PR(config)# mpls ldp
PR(config-ldp)# address-family ipv4
PR(config-ldp-af)# discovery transport-address 9.9.9.9
PR(config-ldp-af)# interface eth0
PR(config-ldp-af-if)# exit
PR(config-ldp-af)# interface eth1
PR(config-ldp-af-if)# exit
PR(config-ldp-af)# end
```
MPLS網に参加するインターフェースだけ登録します。(ループバックは不要)

## PEルータ
PE1, PE2も同様にカーネルパラメータの設定とLDPの設定をします。

### PE1

```:/etc/sysctl.conf
# content of this file will override /etc/sysctl.d/*
net.mpls.conf.eth0.input = 1
net.mpls.platform_labels = 100000
```

```:vtysh
PE1# conf
PE1(config)# mpls ldp
PE1(config-ldp)# address-family ipv4
PE1(config-ldp-af)# discovery transport-address 1.1.1.1
PE1(config-ldp-af)# interface eth0
PE1(config-ldp-af-if)# exit
PE1(config-ldp-af)# end
```

### PE2

```:/etc/sysctl.conf
# content of this file will override /etc/sysctl.d/*
net.mpls.conf.eth0.input = 1
net.mpls.platform_labels = 100000
```

```:vtysh
PE2# conf
PE2(config)# mpls ldp
PE2(config-ldp)# address-family ipv4
PE2(config-ldp-af)# discovery transport-address 2.2.2.2
PE2(config-ldp-af)# interface eth0
PE2(config-ldp-af-if)# exit
PE2(config-ldp-af)# end
```

## 動作確認
Pルータで確認します。

```:PR(vtysh)
PR# show mpls ldp neighbor
AF   ID              State       Remote Address    Uptime
ipv4 1.1.1.1         OPERATIONAL 1.1.1.1         00:04:41
ipv4 2.2.2.2         OPERATIONAL 2.2.2.2         00:00:50
```
LDPのneighborが確立できています。

```:PR(vtysh)
PR# show mpls ldp binding
AF   Destination          Nexthop         Local Label Remote Label  In Use
ipv4 1.1.1.1/32           1.1.1.1         16          imp-null         yes
ipv4 1.1.1.1/32           2.2.2.2         16          16                no
ipv4 2.2.2.2/32           1.1.1.1         17          16                no
ipv4 2.2.2.2/32           2.2.2.2         17          imp-null         yes
ipv4 9.9.9.9/32           1.1.1.1         imp-null    17                no
ipv4 9.9.9.9/32           2.2.2.2         imp-null    17                no
ipv4 10.1.1.0/24          1.1.1.1         imp-null    imp-null          no
ipv4 10.1.1.0/24          2.2.2.2         imp-null    18                no
ipv4 10.1.2.0/24          1.1.1.1         imp-null    18                no
ipv4 10.1.2.0/24          2.2.2.2         imp-null    imp-null          no
ipv4 172.16.1.0/24        1.1.1.1         -           imp-null          no
ipv4 172.16.2.0/24        2.2.2.2         -           imp-null          no
ipv4 172.16.3.0/24        1.1.1.1         -           imp-null          no
ipv4 172.16.4.0/24        2.2.2.2         -           imp-null          no
```
「LIBテーブル」の情報が確認できます。

```:PR(vtysh)
PR# show mpls table
 Inbound Label  Type  Nexthop   Outbound Label
 -----------------------------------------------
 16             LDP   10.1.2.2  implicit-null
 17             LDP   10.1.1.2  implicit-null
```
こちらは「LFIBテーブル」ですね。


先ほどと同様、PE1(1.1.1.1)→PE2(2.2.2.2)にpingを打ってみます。

```:PE1
/ # ping -c 1 -I 1.1.1.1 2.2.2.2
PING 2.2.2.2 (2.2.2.2) from 1.1.1.1: 56 data bytes
64 bytes from 2.2.2.2: seq=0 ttl=63 time=0.121 ms

--- 2.2.2.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.121/0.121/0.121 ms
```
この時、Pルータでパケットキャプチャをすると、生のIPパケットではなく、MPLSラベルのついたパケットが転送されていることがわかります。

```:PR
/ # tcpdump -n -i any -l | grep ICMP
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
07:17:28.103630 eth0  In  MPLS (label 17, exp 0, [S], ttl 64) IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 51200, seq 0, length 64
07:17:28.103636 eth1  Out IP 1.1.1.1 > 2.2.2.2: ICMP echo request, id 51200, seq 0, length 64
07:17:28.103668 eth1  In  MPLS (label 16, exp 0, [S], ttl 64) IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 51200, seq 0, length 64
07:17:28.103669 eth0  Out IP 2.2.2.2 > 1.1.1.1: ICMP echo reply, id 51200, seq 0, length 64
```
行きはlabel 17が付けられたパケットがPルータのeth0から入り、PルータのLFIBテーブル(Label 17に対してimplicit-null)に従ってラベルが剥がされ、生のIPパケットになってeth1から出ていく様子がわかります。帰りはlabel 16で同様の処理が行われています。


# 6. MP-BGPの設定
PEルータでMP-BGPの設定をします。

まずPE間でiBGPピアを張る設定と、VPN-IPv4のアドレス情報を運ぶためのAddress Familyの設定です。

```:PE1(vtysh)
PE1# conf
PE1(config)# router bgp 65000
PE1(config-router)# neighbor 2.2.2.2 remote-as 65000
PE1(config-router)# neighbor 2.2.2.2 update-source 1.1.1.1
PE1(config-router)# address-family ipv4 vpn
PE1(config-router-af)# neighbor 2.2.2.2 activate
PE1(config-router-af)# exit-address-family
PE1(config-router)# end
```

PE2も同様に設定します。

```:PE2(vtysh)
PE2# conf
PE2(config)# router bgp 65000
PE2(config-router)# neighbor 1.1.1.1 remote-as 65000
PE2(config-router)# neighbor 1.1.1.1 update-source 2.2.2.2
PE2(config-router)# address-family ipv4 vpn
PE2(config-router-af)# neighbor 1.1.1.1 activate
PE2(config-router-af)# exit-address-family
PE2(config-router)# end
```

## 動作確認
```:PE1(vtysh)
PE1# show ip bgp summary

IPv4 Unicast Summary:
BGP router identifier 1.1.1.1, local AS number 65000 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 1, using 14 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt
2.2.2.2         4      65000        13        16        0    0    0 00:01:16            0        0

Total number of neighbors 1

IPv4 VPN Summary:
BGP router identifier 1.1.1.1, local AS number 65000 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 1, using 14 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt
2.2.2.2         4      65000        13        16        0    0    0 00:01:16            0        0

Total number of neighbors 1
```
Up/Downの欄がneverになっていないことを確認します。

VPN-IPv4のBGPテーブルも見てみます。

```:PE1(vtysh)
PE1# show bgp ipv4 vpn
No BGP prefixes displayed, 0 exist
```
この時点ではまだルートを学習していないので、エントリーはありません。

# 7. VRFの設定
PE1/PE2で各ユーザごとのVRFを作り、ユーザルートの設定とルート再配送の設定をします。

## VRFの作成
各CEルータを収容するためのVRFを作成します。Linux VRFの機能を使っていきます。VRF作成はFRRoutingでできないので、ipコマンドを使って作ります。また、作ったVRFに、対応するIFを所属させます。A社用のVRF名はCUSTA、B社用はCUSTBとします。

```:PE1
/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
41: eth2@if42: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:10:03:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:0a:01:01:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
45: eth1@if46: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:10:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0

/ # ip link add CUSTA type vrf table 10
/ # ip link add CUSTB type vrf table 20
/ # ip link set CUSTA up
/ # ip link set CUSTB up
/ # ip link set eth1 master CUSTA
/ # ip link set eth2 master CUSTB

/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: CUSTA: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 12:1b:1d:12:01:7f brd ff:ff:ff:ff:ff:ff
3: CUSTB: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether de:06:68:aa:a5:ec brd ff:ff:ff:ff:ff:ff
41: eth2@if42: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTB state UP mode DEFAULT group default
    link/ether 02:42:ac:10:03:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:0a:01:01:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
45: eth1@if46: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTA state UP mode DEFAULT group default
    link/ether 02:42:ac:10:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
```
vtyshで確認できます。

```:PE1(vtysh)
PE1# show int br
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTA           up      CUSTA
eth1            up      CUSTA           172.16.1.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTB           up      CUSTB
eth2            up      CUSTB           172.16.3.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
eth0            up      default         10.1.1.2/24
lo              up      default         1.1.1.1/32
```

PE2でも同様に作成します。

```:PE2
/ # ip link add CUSTA type vrf table 10
/ # ip link add CUSTB type vrf table 20
/ # ip link set CUSTA up
/ # ip link set CUSTB up
/ # ip link set eth1 master CUSTA
/ # ip link set eth2 master CUSTB

/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: CUSTA: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:48:e9:21:1e:6b brd ff:ff:ff:ff:ff:ff
3: CUSTB: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 16:8f:42:1f:9b:84 brd ff:ff:ff:ff:ff:ff
47: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:0a:01:02:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
49: eth1@if50: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTA state UP mode DEFAULT group default
    link/ether 02:42:ac:10:02:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
51: eth2@if52: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master CUSTB state UP mode DEFAULT group default
    link/ether 02:42:ac:10:04:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
```

```:PE2(vtysh)
PE2# show int br
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTA           up      CUSTA
eth1            up      CUSTA           172.16.2.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
CUSTB           up      CUSTB
eth2            up      CUSTB           172.16.4.254/24

Interface       Status  VRF             Addresses
---------       ------  ---             ---------
eth0            up      default         10.1.2.2/24
lo              up      default         2.2.2.2/32
```

## ユーザ向けルートの設定
今回CE-PE間はスタティックルーティングを使います。VRF CUSTA, CUSTBそれぞれで設定。

```:PE1(vtysh)
PE1# conf
PE1(config)# ip route 192.168.1.0/24 172.16.1.2 vrf CUSTA
PE1(config)# ip route 192.168.3.0/24 172.16.3.2 vrf CUSTB
PE1(config)# exit

PE1# show ip route vrf CUSTA
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTA:
C>* 172.16.1.0/24 is directly connected, eth1, 04:55:40
S>* 192.168.1.0/24 [1/0] via 172.16.1.2, eth1, weight 1, 04:12:14

PE1# show ip route vrf CUSTB
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTB:
C>* 172.16.3.0/24 is directly connected, eth2, 04:55:37
S>* 192.168.3.0/24 [1/0] via 172.16.3.2, eth2, weight 1, 04:09:52
```

PE2も同様に設定。

```:PE2(vtysh)
PE2# conf
PE2(config)# ip route 192.168.2.0/24 172.16.2.2 vrf CUSTA
PE2(config)# ip route 192.168.4.0/24 172.16.4.2 vrf CUSTB
PE2(config)# exit

PE2# show ip route vrf CUSTA
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTA:
C>* 172.16.2.0/24 is directly connected, eth1, 05:00:25
S>* 192.168.2.0/24 [1/0] via 172.16.2.2, eth1, weight 1, 04:17:02

PE2# show ip route vrf CUSTB
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTB:
C>* 172.16.4.0/24 is directly connected, eth2, 05:00:26
S>* 192.168.4.0/24 [1/0] via 172.16.4.2, eth2, weight 1, 04:16:34
```

## ルート再配送の設定
VRFのルートテーブルと「グローバルルーティングテーブル」の間の、ルート再配送の設定。

```:PE1(vtysh)
PE1# conf
PE1(config)# router bgp 65000 vrf CUSTA
PE1(config-router)# address-family ipv4 unicast
PE1(config-router-af)# redistribute static
PE1(config-router-af)# label vpn export auto
PE1(config-router-af)# rd vpn export 1:100
PE1(config-router-af)# rt vpn both 10:100
PE1(config-router-af)# export vpn
PE1(config-router-af)# import vpn
PE1(config-router-af)# exit-address-family
PE1(config-router)# exit

PE1(config)# router bgp 65000 vrf CUSTB
PE1(config-router)# address-family ipv4 unicast
PE1(config-router-af)# redistribute static
PE1(config-router-af)# label vpn export auto
PE1(config-router-af)# rd vpn export 2:100
PE1(config-router-af)# rt vpn both 20:100
PE1(config-router-af)# export vpn
PE1(config-router-af)# import vpn
PE1(config-router-af)# exit-address-family
PE1(config-router)# end
```

この辺り、Ciscoと設定方法が若干違うようですが、やっていることとしては以下のように理解しています。

* `label vpn export auto`：VRFからグローバルルーティングテーブルに対してルートをexportする時に、MPLSのラベルを自動でつける
* `export|import vpn`：VRFとグローバルルーティングテーブル間でルートのimport/exportを可能にする

* VRF内のルート情報(address-family = ipv4 unicast)をグローバルルーティングテーブル側(address-family = VPNv4: RD/RT等の情報が付与されたアドレス情報)に配送する際の設定として、「MPLSのラベル付け」「RD/RT情報の付与」「そもそもVRF内のルート⇔グローバルルーティングテーブルの情報のやりとりをONにする」という設定を行っている。

また、今回CE-PE間はStaticルーティングでの設定としているため、`redistribute static`としています。

PE2も同様に設定。

```:PE2(vtysh)
PE2# conf
PE2(config)# router bgp 65000 vrf CUSTA
PE2(config-router)# address-family ipv4 unicast
PE2(config-router-af)# redistribute static
PE2(config-router-af)# label vpn export auto
PE2(config-router-af)# rd vpn export 1:100
PE2(config-router-af)# rt vpn both 10:100
PE2(config-router-af)# export vpn
PE2(config-router-af)# import vpn
PE2(config-router-af)# exit-address-family
PE2(config-router)# exit

PE2(config)# router bgp 65000 vrf CUSTB
PE2(config-router)# address-family ipv4 unicast
PE2(config-router-af)# redistribute static
PE2(config-router-af)# label vpn export auto
PE2(config-router-af)# rd vpn export 2:100
PE2(config-router-af)# rt vpn both 20:100
PE2(config-router-af)# export vpn
PE2(config-router-af)# import vpn
PE2(config-router-af)# exit-address-family
PE2(config-router)# end
```
これを設定すると、BGPでユーザルートが学習されます。

## 動作確認

PE1で確認。まずはBGPテーブル(VPN-IPv4ルート)を見てみます。

```:PE1
PE1# show ip bgp ipv4 vpn
BGP table version is 6, local router ID is 1.1.1.1, vrf id 0
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
Route Distinguisher: 1:100
*> 192.168.1.0/24   172.16.1.2@2<            0         32768 ?
    UN=172.16.1.2 EC{10:100} label=146 type=bgp, subtype=5
*>i192.168.2.0/24   2.2.2.2                  0    100      0 ?
    UN=2.2.2.2 EC{10:100} label=146 type=bgp, subtype=0
Route Distinguisher: 2:100
*> 192.168.3.0/24   172.16.3.2@3<            0         32768 ?
    UN=172.16.3.2 EC{20:100} label=147 type=bgp, subtype=5
*>i192.168.4.0/24   2.2.2.2                  0    100      0 ?
    UN=2.2.2.2 EC{20:100} label=147 type=bgp, subtype=0

Displayed  4 routes and 4 total paths
```
ユーザルートが載りました。PE2で設定した192.168.2.0/24と192.168.4.0/24もiBGPで学習されています。先ほど設定したRD/RT情報も載っています。

次に、VRFごとのBGPテーブル。

```
PE1# show ip bgp vrf CUSTA
BGP table version is 2, local router ID is 172.16.1.254, vrf id 2
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 192.168.1.0/24   172.16.1.2               0         32768 ?
*> 192.168.2.0/24   2.2.2.2@0<               0    100      0 ?

Displayed  2 routes and 2 total paths
```
```
PE1# show ip bgp vrf CUSTB
BGP table version is 4, local router ID is 172.16.3.254, vrf id 3
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 192.168.3.0/24   172.16.3.2               0         32768 ?
*> 192.168.4.0/24   2.2.2.2@0<               0    100      0 ?

Displayed  2 routes and 2 total paths
```
それぞれユーザルートが正しく学習されています。
次に、VRFごとのルートテーブルを見てみます。

```
PE1# show ip route vrf CUSTA
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTA:
C>* 172.16.1.0/24 is directly connected, eth1, 05:29:08
S>* 192.168.1.0/24 [1/0] via 172.16.1.2, eth1, weight 1, 04:45:42
B>  192.168.2.0/24 [200/0] via 2.2.2.2 (vrf default) (recursive), label 146, weight 1, 00:04:28
  *                          via 10.1.1.254, eth0 (vrf default), label 17/146, weight 1, 00:04:28
```
```
PE1# show ip route vrf CUSTB
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup

VRF CUSTB:
C>* 172.16.3.0/24 is directly connected, eth2, 05:29:07
S>* 192.168.3.0/24 [1/0] via 172.16.3.2, eth2, weight 1, 04:43:22
B>  192.168.4.0/24 [200/0] via 2.2.2.2 (vrf default) (recursive), label 147, weight 1, 00:16:20
  *                          via 10.1.1.254, eth0 (vrf default), label 17/147, weight 1, 00:16:20
```
それぞれ、BGPテーブルを介して、PE2から受信したユーザルートが載っていることがわかります。

# 8. CEルータの設定
最後に、CEの設定をしていきます。
ループバックアドレス、PE向かいのアドレス、デフォルトルートを設定します。

```:CE1(vtysh)
CE1# conf
CE1(config)# interface lo
CE1(config-if)# ip address 192.168.1.1/24
CE1(config-if)# exit
CE1(config)# interface eth0
CE1(config-if)# ip address 172.16.1.2/24
CE1(config-if)# exit
CE1(config)# ip route 0.0.0.0/0 172.16.1.254
CE1(config)# end
```

CE2～4も同様。

```:CE2(vtysh)
CE2# conf
CE2(config)# interface lo
CE2(config-if)# ip address 192.168.2.1/24
CE2(config-if)# exit
CE2(config)# interface eth0
CE2(config-if)# ip address 172.16.2.2/24
CE2(config-if)# exit
CE2(config)# ip route 0.0.0.0/0 172.16.2.254
CE2(config)# end
```
```:CE3(vtysh)
CE3# conf
CE3(config)# interface lo
CE3(config-if)# ip address 192.168.3.1/24
CE3(config-if)# exit
CE3(config)# interface eth0
CE3(config-if)# ip address 172.16.3.2/24
CE3(config-if)# exit
CE3(config)# ip route 0.0.0.0/0 172.16.3.254
CE3(config)# end
```
```:CE4(vtysh)
CE4# conf
CE4(config)# interface lo
CE4(config-if)# ip address 192.168.4.1/24
CE4(config-if)# exit
CE4(config)# interface eth0
CE4(config-if)# ip address 172.16.4.2/24
CE4(config-if)# exit
CE4(config)# ip route 0.0.0.0/0 172.16.4.254
CE4(config)# end
```

# 動作確認
これですべて完成したはずです。
CE1→CE2にpingが通ることを確認します。`-I`でSourceとしてループバックIFを指定。

```:CE1
/ # ping -I 192.168.1.1 192.168.2.1
PING 192.168.2.1 (192.168.2.1) from 192.168.1.1: 56 data bytes
64 bytes from 192.168.2.1: seq=0 ttl=62 time=0.650 ms
64 bytes from 192.168.2.1: seq=1 ttl=62 time=0.751 ms
64 bytes from 192.168.2.1: seq=2 ttl=62 time=0.675 ms
^C
--- 192.168.2.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.650/0.692/0.751 ms
```
通りました！

CE3→CE4にpingが通ることを確認します。

```:CE3
/ # ping -I 192.168.3.1 192.168.4.1
PING 192.168.4.1 (192.168.4.1) from 192.168.3.1: 56 data bytes
64 bytes from 192.168.4.1: seq=0 ttl=62 time=0.470 ms
64 bytes from 192.168.4.1: seq=1 ttl=62 time=0.157 ms
64 bytes from 192.168.4.1: seq=2 ttl=62 time=0.777 ms
^C
--- 192.168.4.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.157/0.468/0.777 ms
```
通りました！

試しに、CE1→CE4には通らないことを確認します。

```:CE1
/ # ping -I 192.168.1.1 192.168.4.1
PING 192.168.4.1 (192.168.4.1) from 192.168.1.1: 56 data bytes
^C
--- 192.168.4.1 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
```
通らないので成功です！


# パケットキャプチャしてみる
上記CE1→CE2のpingの際、PE1のeth1,0, Pルータのeth0,1, PE2のeth0,1でパケットキャプチャすると、ラベルがついたり取られたりしながらパケットが転送される様子が見えます。


```:PE1/PR/PE2
/ # tcpdump -n -i any -l | grep ICMP
```
```:CE1
/ # ping -c 1 -I 192.168.1.1 192.168.2.1
PING 192.168.2.1 (192.168.2.1) from 192.168.1.1: 56 data bytes
64 bytes from 192.168.2.1: seq=0 ttl=62 time=0.551 ms

--- 192.168.2.1 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.551/0.551/0.551 ms
```

結果を時系列で並べると以下のようになりました。

行き：

```:PE1
08:13:50.550878 eth1  In  IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550900 eth0  Out MPLS (label 17, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
```
```:PR
08:13:50.550905 eth0  In  MPLS (label 17, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550909 eth1  Out MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
```
```:PE2
08:13:50.550913 eth0  In  MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550916 CUSTA Out IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
08:13:50.550926 eth1  Out IP 192.168.1.1 > 192.168.2.1: ICMP echo request, id 20992, seq 0, length 64
```
PE1のeth0に届くのは生のIPパケットですが、そこで内側のラベル146(VPN識別用)と外側のラベル17(MPLS網内転送用)がつけられeth1から出ていきます。PRではLFIBテーブル(Label 17に対してはimplicit null)にしたがって外側のラベルが剥がされた状態でPE2に転送されています。PE2では内側のラベルも剥がされ、VRF CUSTAのルーティングテーブルに従って生のIPパケットとしてCE2に転送されています。 最後Outが2つあるのはVRF CUSTAとeth1の分ですね。[^3]

[^3]: tcpdumpのIF指定を`-i any`にすると、VRF CUSTAへのINが表示されないようです。個別にIF指定するとどちらも表示されるのですが... なぜだろう...

帰り：
帰りも同様です。内側のVPN識別用ラベルとしては146が割り当てられています。

```:PE2
08:13:50.550976 eth1  In  IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550981 eth0  Out MPLS (label 16, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
```
```:PR
08:13:50.550984 eth1  In  MPLS (label 16, exp 0, ttl 63) (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550986 eth0  Out MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
```
```:PE1
08:13:50.550988 eth0  In  MPLS (label 146, exp 0, [S], ttl 63) IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550990 CUSTA Out IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
08:13:50.550994 eth1  Out IP 192.168.2.1 > 192.168.1.1: ICMP echo reply, id 20992, seq 0, length 64
```








# 参考
* [MPLSをはじめから](https://www.infraexpert.com/study/study29.html)
* [MPLS-VPN 検証構成とコンフィグ設定（PE-CE間でスタティックルートを使用）](https://www.infraeye.com/2018/01/04/mplsvpn01/)
* [FRRoutingでMPLS L3-VPNのようなもの](https://qiita.com/sandopan65/items/23e4645202326893254f)
* [Use Linux as an MPLS Router]
(https://blog.swineson.me/en/use-linux-as-an-mpls-router/)
* [Linux VRF with L3 Master Device](http://yunazuno.hatenablog.com/entry/2017/06/17/225917)