FRRoutingをDockerで試す～OSPF編～

## この記事は

下記の記事に触発されて、自分でも試したくなった
[Docker上のFRRoutingでOSPFを動かしてみる](https://qiita.com/tk_n/items/648e225d06085a0e2530)

元記事では`compose.yml`を使っていなかったが
また後で再現するときに楽なので、今回は`compose.yml`を作る

### 前提条件

- Windows 11 Home の Docker Desktop環境で動作
- Docker
  - Client Version: 25.0.3
  - Server: Docker Desktop 4.27.2 (137060)
    - Engine Version: 25.0.3
- Docker Compose version v2.24.5-desktop.1

:::note warn
Docker Desktopは一定規模以上の企業で使う場合、有償となります
:::

#### 構成

- 両端のネットワークにPCが1台ずつ存在する
- 真ん中の19ネットワークにはルータしか存在しない

ネットワーク|PC1|ルーター1|ルーター|PC2
-|-|-|-|-
172.18.0.0/16|alpine1 (.10)|frr1 (.250)|-|- 
172.19.0.0/16| -|frr1 (.250)|frr2 (.251)|-
172.20.0.0/16| -|-|frr2 (.250)|alpine2 (.10)|

### docker compose コマンド復習

[Docker Composeコマンド集](https://qiita.com/t13801206/items/d3b9829c7b5b24adf071#%E4%BB%98%E9%8C%B2-docker-compose%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E9%9B%86)


## [気づいたらdocker-composeが色々変わっていた件](https://kojinjigyou.org/48322/)

- `dcoker-compose.yml`はもう古い。`compose.yml`を使うべし。
  - [docker docs の関連ページはこちら](https://docs.docker.com/compose/compose-application-model/#the-compose-file)
- `compose.yml`の冒頭にバージョンを書くのも、もう古い。
  - [docker docs の関連ページはこちら](https://docs.docker.com/compose/intro/history/#docker-compose-cli-versioning)
  - docker compose V2 は `compose.yml`の`version`を無視すると書かれている

## Docker Compose Up

`compose.yml`が置かれている階層（今回は`workdir`）に移動し、`docker compose up`する
`-d`オプションをつけて、デタッチしつつ、ログを画面に流す

```bash
cd workdir
docker compose up -d && docker compose logs -f
```

## alpineのデフォルトゲートウェイを変更する

alpine1コンテナの中で作業する

```bash
cd workdir
docker compose exec alpine1 bin/sh
```

alpine1のルーティングテーブルを確認すると
デフォルトゲートウェイが`172.18.0.1`となっている

>　Destinationが「0.0.0.0」，Genmask「0.0.0.0」の設定行は，標準のパケット転送先を示しています。この設定は，他の設定に該当しないすべてのパケットに適用されます。この行で設定されているGatewayは，標準で使われるゲートウエイという意味で「デフォルト・ゲートウエイ」と呼ばれます。
https://xtech.nikkei.com/it/article/COLUMN/20080520/303086/

```bash
/ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.18.0.1      0.0.0.0         UG    0      0        0 eth0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

### 既存のルートを削除し、新ルートを追加する

ルーターコンテナのIPアドレスである`172.18.0.250`に変更する

```bash
route del default gw 172.18.0.1
route add default gw 172.18.0.250
```

変更が反映されていることを確認する

```bash
/ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.18.0.250    0.0.0.0         UG    0      0        0 eth0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

#### alpine2についても同様の設定を行う

alpine1と通信する相手であるalpine2についても
同様の手順でデフォルトゲートウェイを変更する
ただし、所属するネットワークが違う点に注意する

```bash
docker compose exec alpine2 bin/sh

route del default gw 172.20.0.1
route add default gw 172.20.0.250

route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.20.0.250    0.0.0.0         UG    0      0        0 eth0
172.20.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

```

### この状態ではまだPINGは通らない

```bash
/ # ping 172.20.0.10
PING 172.20.0.10 (172.20.0.10): 56 data bytes
^C
--- 172.20.0.10 ping statistics ---
13 packets transmitted, 0 packets received, 100% packet loss
```

## FRRoutingコンテナ

続いて、FRRoutingコンテナの設定をしていく
aplineは単なる軽量Linuxなので、事前知識は不要だったが
FRRoutingについては中身を知らないと設定できないので
ひとつずつ調べて進める

### [VTYSHとは何か](https://docs.frrouting.org/projects/dev-guide/en/latest/vtysh.html)

自動翻訳によると、以下の定義だそうです

> VTYSHはFRRデーモン用のシェルである。VTYSHは各デーモンで定義されたすべてのCLIコマンドを統合し、単一のシェルでユーザーに表示します。これにより、ユーザーは各デーモンにtelnetで接続して個々のシェルを使用する手間を省くことができます。統合は、デーモンからコマンドを抽出し、ビルド時にVTYSHに注入することで達成される。

何も設定しないまま`vtysh`と入力すると、コンフィグファイルが無いとエラーが出る

```bash
/ # vtysh
% Can't open configuration file /etc/frr/vtysh.conf due to 'No such file or directory'.
```

### [コンフィグファイルの取り扱い](https://docs.frrouting.org/en/latest/basic.html#integrated-config-file)

`vtysh.conf`と`frr.conf`は何が違うのだろう

> FRRは/etc/frr/frr.confにある単一の設定ファイルを使用する。FRRがinitスクリプトやsystemdを使って起動されると、vtyshが起動され、設定ファイルを読み込み、適切な部分を興味のあるデーモンだけに送信する。実行中の設定更新は、同様にvtyshを使ってこの単一ファイルに永続化される。

この段階で、`/etc/frr/`にあるコンフィグファイルは`staticd.conf`と`zebra.conf`の２つだけ

```bash
/ # ls -la  /etc/frr/
total 16
drwxr-xr-x    1 frr      frr           4096 Mar 24 08:43 .
drwxr-xr-x    1 root     root          4096 Mar 24 08:43 ..
-rw-r--r--    1 frr      frr           3840 Nov  6  2022 daemons
-rw-------    1 frr      frr              0 Mar 24 08:43 staticd.conf
-rw-------    1 frr      frr              0 Mar 24 08:43 zebra.conf
```

#### vtysh.confをつくる

[vtysh.conf.sample](https://github.com/troglobit/quagga/blob/master/vtysh/vtysh.conf.sample)をGithubから拾ってくる
`service integrated-vtysh-config`の行だけ、行頭の`!`を取り外して有効化しておく

```bash
vi /etc/frr/vtysh.conf

!
! Sample configuration file for vtysh.
!
service integrated-vtysh-config
!hostname quagga-router
!username root nopassword
!
```

### VTYSHでいろいろコマンド確認

VTYSHの中に入るには、単純にVTYSHと打てばよいらしい

```bash
/ # vtysh
Hello, this is FRRouting (version 8.4_git).
Copyright 1996-2005 Kunihiro Ishiguro, et al.
```

現在の設定を確認する

```bash:show run
frr1# show run
Building configuration...

Current configuration:
!
frr version 8.4_git
frr defaults traditional
hostname frr1
domainname
service integrated-vtysh-config
!
end
```

その他の設定を確認しようとすると、「`zebra`が動いてないよ」と怒られる

```bash:show int brief
frr1# show int brief
zebra is not running
```

```bash:show ip route
frr1# show ip route
zebra is not running
```

### zebra起動までのトラブルシューティング

たしかに`docker compose up`した際のログを確認すると、`zebra`の起動に失敗している
これは後続のログの`frr.conf`が無いことが原因なのだろうか
ひとまず`vtysh.conf`はつくって、サービスを統合するオプションも有効にしてあるので、コンテナを再起動してみる

```bash
frr1     | Failed to start zebra!
frr1     | /etc/frr/frr.conf does not exist; skipping config apply
```

・・・ダメでした
`/etc/frr/`に`frr.conf`を作ってみたけど、それもダメ

```bash
frr1     | 2024/03/24 15:18:05 WATCHFRR: [ZG9QC-QRCJZ] failed to mkdir "/var/tmp/frr/watchfrr.11": File exists
frr1     | 2024/03/24 15:18:05 WATCHFRR: [M1DC0-ZDNYJ] crashlog and per-thread log buffering unavailable!
frr1     | 2024/03/24 15:18:05 WATCHFRR: [T83RR-8SM5G] watchfrr 8.4_git starting: vty@0
frr1     | 2024/03/24 15:18:05 WATCHFRR: [ZCJ3S-SPH5S] zebra state -> down : initial connection attempt failed
frr1     | 2024/03/24 15:18:05 WATCHFRR: [ZCJ3S-SPH5S] staticd state -> down : initial connection attempt failed
frr1     | 2024/03/24 15:18:05 WATCHFRR: [YFT0P-5Q5YX] Forked background command [pid 12]: /usr/lib/frr/watchfrr.sh restart all
frr1     | /usr/lib/frr/frrcommon.sh: line 211: kill: (86) - No such process
frr1     | Cannot stop zebra: pid file not found
frr1     | 2024/03/24 15:18:05 ZEBRA: [ZG9QC-QRCJZ] failed to mkdir "/var/tmp/frr/zebra.21": File exists
frr1     | 2024/03/24 15:18:05 ZEBRA: [M1DC0-ZDNYJ] crashlog and per-thread log buffering unavailable!
frr1     | privs_init: initial cap_set_proc failed: Operation not permitted
frr1     | Wanted caps: cap_net_admin,cap_net_raw,cap_sys_admin=p
frr1     | Have   caps: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=p
frr1     | Failed to start zebra!
frr1     | 2024/03/24 15:18:06 STATIC: [ZG9QC-QRCJZ] failed to mkdir "/var/tmp/frr/staticd.24": File exists
frr1     | 2024/03/24 15:18:06 STATIC: [M1DC0-ZDNYJ] crashlog and per-thread log buffering unavailable!
frr1     | 2024/03/24 15:18:06 WATCHFRR: [QDG3Y-BY5TN] staticd state -> up : connect succeeded
frr1     | 2024/03/24 15:19:00 WATCHFRR: [K54RW-4APGS][EC 268435457] startup did not complete within timeout (1/2 daemons running)
frr1     | 2024/03/24 15:19:06 WATCHFRR: [YFT0P-5Q5YX] Forked background command [pid 34]: /usr/lib/frr/watchfrr.sh restart all
frr1     | 2024/03/24 15:19:06 WATCHFRR: [HD38Q-0HBRT][EC 268435457] staticd state -> down : read returned EOF
frr1     | Cannot stop zebra: pid file not found
frr1     | privs_init: initial cap_set_proc failed: Operation not permitted
frr1     | Wanted caps: cap_net_admin,cap_net_raw,cap_sys_admin=p
frr1     | Have   caps: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=p
frr1     | Failed to start zebra!
frr1     | 2024/03/24 15:19:06 WATCHFRR: [QDG3Y-BY5TN] staticd state -> up : connect succeeded
```

`staticd`は動いているっぽいけど、`zebra`は起動しておらず

```bash
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- /usr/lib/frr/docker-start
    7 root      0:00 {docker-start} /bin/bash /usr/lib/frr/docker-start
   11 root      0:00 /usr/lib/frr/watchfrr zebra staticd
   27 root      0:00 bin/sh
   48 frr       0:00 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1
   51 root      0:00 ps
```

#### frr.confが空っぽなのはいかがなものか

`vtysh.conf`でコンフィグファイルを統合するように設定したものの
肝心な統合先である`frr.conf`が当初不在だったのはいかがなものか

> service integrated-vtysh-config
> vtysh will always write frr.conf.

今、`frr.conf`は空っぽであるがそれでよいのか
`zebra`が起動しない問題は、`frr.conf`とは無関係なのか？

#### 解決

`compose.yml`に`privileged: true`を追記したら動きました
`zebra`を起動するには特権が必要だったという話
とくに`frr.conf`は関係なかった様子

:::note warn
Compose.ymlを編集した際は、`build`オプションをつけないと反映されないので注意
`docker compose up --build -d`
:::

よくよく見ると、先行記事にも書いてありました

> frr1, frr2を作り、それぞれnet1とnet2、net2とnet3に接続します。
> --privilegedをつけないと動かないようです。

### OSPFを有効にする

`vi /etc/frr/daemons`でデーモンを編集し、`ospf=yes`とする

```bash
/etc/frr # cat daemons
# This file tells the frr package which daemons to start.
#
# Sample configurations for these daemons can be found in
# /usr/share/doc/frr/examples/.
#
# ATTENTION:
#
# When activating a daemon for the first time, a config file, even if it is
# empty, has to be present *and* be owned by the user and group "frr", else
# the daemon will not be started by /etc/init.d/frr. The permissions should
# be u=rw,g=r,o=.
# When using "vtysh" such a config file is also needed. It should be owned by
# group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too.
#
# The watchfrr, zebra and staticd daemons are always started.
#
bgpd=no
ospfd=yes
ospf6d=no
ripd=no
ripngd=no
isisd=no
```


`docker compose restart frr1`でコンテナを再起動し
`docker compose exec frr1 bin/sh`で再度コンテナに入る

プロセスの一覧を確認すると、`ospfd`が動いていることがわかる

```bash
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- /usr/lib/frr/docker-start
    7 root      0:00 {docker-start} /bin/bash /usr/lib/frr/docker-start
   12 root      0:00 /usr/lib/frr/watchfrr zebra ospfd staticd
   26 frr       0:00 /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
   31 frr       0:00 /usr/lib/frr/ospfd -d -F traditional -A 127.0.0.1
   34 frr       0:00 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1
   38 root      0:00 bin/sh
   44 root      0:00 ps
```

### OSPFの設定をする（VTYSH)

frr1でVTYSHを起動し、先ほど失敗したコマンドを確認する

まずはインターフェイスの要約
eth0とeth1にそれぞれ.250のIPアドレスが付与されていることがわかる

```bash:show int brief
frra1# show int brief
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
eth0            up      default         172.18.0.250/16
eth1            up      default         172.19.0.250/16
lo              up      default
```

つづいてIPルーティングテーブルを表示する
２つのネットワークとダイレクトにつながっていることがわかる
またデフォルトゲートウェイ(0.0.0.0)は172.18.0.1向けということがわかる

```bash
frra1# 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, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

K>* 0.0.0.0/0 [0/0] via 172.18.0.1, eth0, 00:03:35
C>* 172.18.0.0/16 is directly connected, eth0, 00:03:35
C>* 172.19.0.0/16 is directly connected, eth1, 00:03:35
```

#### 設定を投入していく

まずはわけもわからず、先行文献に従って設定を投入してみる

```bash
/ # vtysh
frr1# conf t
frr1(config)# interface lo
frr1(config-if)# ip address 1.1.1.1/32
frr1(config-if)# exit
frr1(config)# router ospf
frr1(config-router)# router-info area 0.0.0.0
frr1(config-router)# network 172.18.0.0/16 area 0.0.0.0
frr1(config-router)# network 172.19.0.0/16 area 0.0.0.0
frr1(config-router)# end
```

設定を確認する

```bash:show run
frra1# show run
Building configuration...

Current configuration:
!
frr version 8.4_git
frr defaults traditional
hostname frra1
no ipv6 forwarding
domainname
service integrated-vtysh-config
!
interface lo
 ip address 1.1.1.1/32
exit
!
router ospf
 network 172.18.0.0/16 area 0.0.0.0
 network 172.19.0.0/16 area 0.0.0.0
 router-info area
exit
!
end
```

IPルートが反映されていることを確認する

```bash:show ip route
frra1# 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, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

K>* 0.0.0.0/0 [0/0] via 172.18.0.1, eth0, 00:09:18
C>* 1.1.1.1/32 is directly connected, lo, 00:01:41
O   172.18.0.0/16 [110/10] is directly connected, eth0, weight 1, 00:00:58
C>* 172.18.0.0/16 is directly connected, eth0, 00:09:18
O   172.19.0.0/16 [110/10] is directly connected, eth1, weight 1, 00:00:54
C>* 172.19.0.0/16 is directly connected, eth1, 00:09:18
```

設定を永続化させる

```bash:write mem
frra1# write mem
Note: this version of vtysh never writes vtysh.conf
Building Configuration...
Integrated configuration saved to /etc/frr/frr.conf
[OK]
```

`frr.conf`に書き込まれているらしいので、確認してみる

```bash:frr.conf
/ # cat /etc/frr/frr.conf
frr version 8.4_git
frr defaults traditional
hostname frra1
no ipv6 forwarding
domainname
service integrated-vtysh-config
!
interface lo
 ip address 1.1.1.1/32
exit
!
router ospf
 network 172.18.0.0/16 area 0.0.0.0
 network 172.19.0.0/16 area 0.0.0.0
 router-info area
exit
!
```

### frr2の設定

こちらも見よう見まねで設定
まずは`daemons`ファイルを編集してOSPFを有効にする
ちなみに順序を間違えて先に設定を投入しようとすると、VTYSHのコンフィグで怒られる（ある意味親切ですね）

```bash
frr2(config)# route ospf
ospfd is not running
```

vtyshを起動して、以下のコマンドを打ち込む

```bash
frr2# conf t
frr2(config)# interface lo
frr2(config-if)# ip address 2.2.2.2/32
frr2(config-if)# exit
frr2(config)# router ospf
frr2(config-router)# router-info area 0.0.0.0
frr2(config-router)# network 172.19.0.0/16 area 0.0.0.0
frr2(config-router)# network 172.20.0.0/16 area 0.0.0.0
frr2(config-router)# end
```

こちらも永続化のために`write mem`しておく
`exit`でvtyshを抜けて、`cat /etc/frr/frr.conf`で設定が保存されていることを確認する

```bash
/ # cat /etc/frr/frr.conf
frr version 8.4_git
frr defaults traditional
hostname frr2
no ipv6 forwarding
domainname
service integrated-vtysh-config
!
interface lo
 ip address 2.2.2.2/32
exit
!
router ospf
 network 172.19.0.0/16 area 0.0.0.0
 network 172.20.0.0/16 area 0.0.0.0
 router-info area
exit
!
```

## OSPF neighborを確認する

frr1, frr2の両方の設定が完了した

frr1から見て、frr2がネイバー関係にあることがわかる

```bash
frr1# show ip ospf neighbor

Neighbor ID     Pri State           Up Time         Dead Time Address         Interface                        RXmtL RqstL DBsmL
2.2.2.2           1 Full/Backup     2m33s             35.709s 172.19.0.251    eth1:172.19.0.250                    0     0     0
```

逆もしかり

```bash
frr2# show ip ospf neighbor

Neighbor ID     Pri State           Up Time         Dead Time Address         Interface                        RXmtL RqstL DBsmL
1.1.1.1           1 Full/DR         5m18s             31.757s 172.19.0.250    eth0:172.19.0.251                    0     0     0
```

## いざ、疎通確認！！

alpine1からalpine2に向かってトレースルート

```bash
/ # traceroute -n 172.20.0.10
traceroute to 172.20.0.10 (172.20.0.10), 30 hops max, 46 byte packets
 1  172.18.0.250  0.009 ms  0.011 ms  0.012 ms
 2  172.19.0.251  0.012 ms  0.014 ms  0.018 ms
 3  172.20.0.10  0.010 ms  0.011 ms  0.018 ms
```

逆方向

```bash
/ # traceroute -n 172.18.0.10
traceroute to 172.18.0.10 (172.18.0.10), 30 hops max, 46 byte packets
 1  172.20.0.250  0.009 ms  0.007 ms  0.008 ms
 2  172.19.0.250  0.016 ms  0.010 ms  0.010 ms
 3  172.18.0.10  0.015 ms  0.010 ms  0.008 ms
```

うまくいきました！

## `compose.yml`

```yml
services:
  alpine1:
    image: alpine
    container_name: alpine1
    hostname: alpine1
    tty: true
    stdin_open: true
    privileged: true
    networks:
      net1:
        ipv4_address: 172.18.0.10
  alpine2:
    image: alpine
    container_name: alpine2
    hostname: alpine2
    tty: true
    stdin_open: true
    privileged: true
    networks:
      net3:
        ipv4_address: 172.20.0.10

  frr1:
    image: frrouting/frr
    container_name: frr1
    hostname: frr1
    privileged: true
    networks:
      net1:
        ipv4_address: 172.18.0.250
      net2:
        ipv4_address: 172.19.0.250
  frr2:
    image: frrouting/frr
    container_name: frr2
    hostname: frr2
    privileged: true
    networks:
      net2:
        ipv4_address: 172.19.0.251
      net3:
        ipv4_address: 172.20.0.250

networks:
  net1:
    ipam:
      config:
        - subnet: 172.18.0.0/16
  net2:
    ipam:
      config:
        - subnet: 172.19.0.0/16
  net3:
    ipam:
      config:
        - subnet: 172.20.0.0/16

```

## 参考

https://qiita.com/tk_n/items/648e225d06085a0e2530


## 糸冬了！！