SONiCと静的経路

# はじめに
SONiCの設定例を見ると、経路に関してはBGPにまつわる設定ばかりで静的経路について言及されているものはほとんどありません。実際のところ静的経路が扱えるのか、調査してみましたので、それを共有したいと思います。

# 設計ドキュメントでの言及

[SONiC VRF support design spec draft](https://github.com/Azure/SONiC/blob/master/doc/vrf/sonic-vrf-hld.md)によると、`config_db.json`に`"STATIC_ROUTE"`という設定項目の追加について言及されています。設定例を転記します。

```
"STATIC_ROUTE": {
    "11.11.11.0/24": {
        "distance": "10",
        "nexthop": "1.1.1.1"
    },

    "Vrf-blue|22.11.11.0/24": {
        "distance": "10",
        "nexthop-vrf": "Vrf-red",
        "nexthop": "2.1.1.1"
    },

    "Vrf-red|11.11.11.0/24": {
        "nexthop": "1.1.1.1"
    }
}
```

しかし、SONiCのソースコードを探しても`"STATIC_ROUTE"`なるキーワードはどこにも記述されていません。つまり、**上記のような設定を`config_db.json`に記述しても無視されます**。

# CLI実装
SONiCの[コマンドリファレンス](https://github.com/Azure/sonic-utilities/blob/master/doc/Command-Reference.md)には言及がありませんが、ヘルプメッセージ(下記に一部抜粋)にあるとおり、`config route`というコマンドが存在しています。

```
  qos                     QoS-related configuration tasks
  reload                  Clear current configuration and import a previous...
  route                   route-related configuration tasks
  save                    Export current config DB to a file on disk.
  sflow                   sFlow-related configuration tasks
```

`config route`コマンドの詳細を確認してみます。

```
admin@sonic:~$ sudo config route
Usage: config route [OPTIONS] COMMAND [ARGS]...

  route-related configuration tasks

Options:
  -?, -h, --help  Show this message and exit.

Commands:
  add  Add route command
  del  Del route command
admin@sonic:~$ sudo config route add
Usage: config route add [OPTIONS] prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop
                        <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>
Try "config route add -h" for help.

Error: argument is not in pattern prefix [vrf <vrf_name>] <A.B.C.D/M> nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>!
admin@sonic:~$ 

```

なにやら動いてくれそうな感じですので、実際にコマンドを実行してみます。

```
admin@sonic:~$ show ip route
admin@sonic:~$ sudo config interface ip add Ethernet0 192.168.0.1/24
admin@sonic:~$ sudo config route add prefix 172.21.0.0/24 nexthop 192.168.0.254
admin@sonic:~$ 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 route, r - rejected route

S>* 172.21.0.0/24 [1/0] via 192.168.0.254, Ethernet0, 00:00:04
C>* 192.168.0.0/24 is directly connected, Ethernet0, 00:00:15
admin@sonic:~$ 
```

**動いてくれました!**
では、設定はどうなっているのかチェックしてみます。

```
admin@sonic:~$ show runningconfiguration all|grep 172
admin@sonic:~$ 
```

あれ???

BGP dockerの中も確認してみます。

```
admin@sonic:~$ docker exec -it bgp bash
root@sonic:/# grep 172 /etc/frr/*.conf
root@sonic:/# 
```

えーと?
よくわからないので`config route`のプログラムの中身を見てみます。

<details><summary>中身</summary><div>

```Python:src/sonic-utilities/config/main.py
@route.command('add', context_settings={"ignore_unknown_options":True})
@click.argument('command_str', metavar='prefix [vrf <vrf_name>] <A.B.C.D/M> next
hop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>', nargs=-1, type=click.Path())
@click.pass_context
def add_route(ctx, command_str):
    """Add route command"""
    if len(command_str) < 4 or len(command_str) > 9:
        ctx.fail("argument is not in pattern prefix [vrf <vrf_name>] <A.B.C.D/M>
 nexthop <[vrf <vrf_name>] <A.B.C.D>>|<dev <dev_name>>!")
    if "prefix" not in command_str:
        ctx.fail("argument is incomplete, prefix not found!")
    if "nexthop" not in command_str:
        ctx.fail("argument is incomplete, nexthop not found!")
    for i in range(0, len(command_str)):
        if "nexthop" == command_str[i]:
            prefix_str = command_str[:i]
            nexthop_str = command_str[i:]
    vrf_name = ""
    cmd = 'sudo vtysh -c "configure terminal" -c "ip route'
    if prefix_str:
        if len(prefix_str) == 2:
            prefix_mask = prefix_str[1]
            cmd += ' {}'.format(prefix_mask)
        elif len(prefix_str) == 4:
            vrf_name = prefix_str[2]
            prefix_mask = prefix_str[3]
            cmd += ' {}'.format(prefix_mask)
        else:
            ctx.fail("prefix is not in pattern!")
    if nexthop_str:
        if len(nexthop_str) == 2:
            ip = nexthop_str[1]
            if vrf_name == "":
                cmd += ' {}'.format(ip)
            else:
                cmd += ' {} vrf {}'.format(ip, vrf_name)
        elif len(nexthop_str) == 3:
            dev_name = nexthop_str[2]
            if vrf_name == "":
                cmd += ' {}'.format(dev_name)
            else:
                cmd += ' {} vrf {}'.format(dev_name, vrf_name)
        elif len(nexthop_str) == 4:
            vrf_name_dst = nexthop_str[2]
            ip = nexthop_str[3]
            if vrf_name == "":
                cmd += ' {} nexthop-vrf {}'.format(ip, vrf_name_dst)
            else:
                cmd += ' {} vrf {} nexthop-vrf {}'.format(ip, vrf_name, vrf_name_dst)
        else:
            ctx.fail("nexthop is not in pattern!")
    cmd += '"'
    clicommon.run_command(cmd)
```
</div></details>

なるほど。`vtysh`コマンドで設定をFRRに叩き込んでるだけ、データベースに記録しない、つまり**静的経路は設定できるがその場限りで、再起動すると失われる**、ということのようです。将来に渡ってこのままはわかりませんが、現状はこのような状況でした。

このような実装だったので、おそらくCLI以外で静的経路を設定することは現時点では不可能と思われます。

# おわりに

静的経路の投入を必要とする場合、実装されるのを待つか、あるいは問題提起して実装を促すか、それとも自分で実装してしまうか、CLIで毎回設定投入することを許容するか、はたまたSONiC以外の利用を検討するか。悩ましいですが、個人的にはまったく使えないよりはいくぶんマシだという感想を持ちました。現場からは以上です。