APRESIA Technical Blog

IPv4/IPv6の両方の通信を試験可能なFRRoutingのdocker composeサンプル

はじめに

FRRouting(https://frrouting.org/)はオープンソースのルーティングプロトコルソフトウェアで、SONiCの中にも組み込まれるなど幅広く活用されています。FRRoutingはソースコードが公開されていますので、自分でビルドして使用する、あるいはコンテナイメージを作成してデプロイすることも容易です。ただ、配布されているFRRoutingのDockerイメージですと、IPv6の通信の試験をする際に様々な問題が発生しました。そこで、IPv4/IPv6の両方の通信確認が可能なdocker composeを作成しましたので、この記事にてサンプルを共有したいと思います。このページでは、以下のネットワークをdocker composeにて実現してみます。

docker compose 含めたファイルの準備

今回の実験は、以下のホスト環境にて実施しています。
  • Ubuntu 20.04
  • Docker version 27.3.1
  • Docker Compose version v2.29.7
トップディレクトリとしてfrr_dockerを作成し、その下に以下のようにファイルを配置しました。
frr_docker
+- docker-compose.yml
+- config/
|  +- frr1/
|  |  +- daemons  
|  |  +- frr.conf
|  |  +- vtysh.conf
|  |
|  +- frr2/
|     +- daemons  
|     +- frr.conf
|     +- vtysh.conf
|
+- frr/
用意した docker-compose.yml は以下です。いくつか細工をした点がありますので、そちらは後述します。
networks:
  host1_net:
    driver: macvlan
    enable_ipv6: true
    ipam:
      config:
        - subnet: 10.0.1.0/24
          gateway: 10.0.1.2
        - subnet: 2001:db8:0:1::/64
          gateway: 2001:db8:0:1::2
  host2_net:
    driver: macvlan
    enable_ipv6: true
    ipam:
      config:
        - subnet: 10.0.2.0/24
          gateway: 10.0.2.2
        - subnet: 2001:db8:0:2::/64
          gateway: 2001:db8:0:2::2
  frr_net:
    driver: macvlan
    enable_ipv6: true
    ipam:
      config:
        - subnet: 10.0.3.0/24
          gateway: 10.0.3.3
        - subnet: 2001:db8:0:3::/64
          gateway: 2001:db8:0:3::3
services:
  host1:
    container_name: host1
    image: alpine
    cap_add:
      - CAP_NET_RAW
      - NET_ADMIN
    command: /bin/sh -c "ip route add default via 10.0.1.1 && ip route add default via 2001:db8:0:1::1 && tail -f /dev/null"
    networks:
      host1_net:
        ipv4_address: 10.0.1.11
        ipv6_address: 2001:db8:0:1::11
  host2:
    container_name: host2
    image: alpine
    cap_add:
      - CAP_NET_RAW
      - NET_ADMIN
    command: /bin/sh -c "ip route add default via 10.0.2.1 && ip route add default via 2001:db8:0:2::1 && tail -f /dev/null"
    networks:
      host2_net:
        ipv4_address: 10.0.2.11
        ipv6_address: 2001:db8:0:2::11
  frr1:
    container_name: frr1
    image: local/frr
    cap_add:
      - CAP_NET_RAW
      - NET_ADMIN
      - SYS_ADMIN
    build:
      context: ./frr/docker/debian/
      dockerfile: Dockerfile
    volumes:
      - ./config/frr1/daemons:/etc/frr/daemons
      - ./config/frr1/frr.conf:/etc/frr/frr.conf
      - ./config/frr1/vtysh.conf:/etc/frr/vtysh.conf
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
    networks:
      frr_net:
        ipv4_address: 10.0.3.1
        ipv6_address: 2001:db8:0:3::1
      host1_net:
        ipv4_address: 10.0.1.1
        ipv6_address: 2001:db8:0:1::1
  frr2:
    container_name: frr2
    image: local/frr
    cap_add:
      - CAP_NET_RAW
      - NET_ADMIN
      - SYS_ADMIN
    build:
      context: ./frr/docker/debian/
      dockerfile: Dockerfile
    volumes:
      - ./config/frr2/daemons:/etc/frr/daemons
      - ./config/frr2/frr.conf:/etc/frr/frr.conf
      - ./config/frr2/vtysh.conf:/etc/frr/vtysh.conf
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
    networks:
      frr_net:
        ipv4_address: 10.0.3.2
        ipv6_address: 2001:db8:0:3::2
      host2_net:
        ipv4_address: 10.0.2.1
        ipv6_address: 2001:db8:0:2::1
"config/frr1/frr.conf"は、frr1コンテナのFRRoutingの設定で、内容は以下の通りです。
frr defaults traditional
hostname frr1
service integrated-vtysh-config
!
interface lo
 ip address 1.1.1.1/32
exit
!
router bgp 65001
 bgp router-id 1.1.1.1
 no bgp ebgp-requires-policy
 no bgp default ipv4-unicast
 bgp bestpath as-path multipath-relax
 bgp bestpath compare-routerid
 bgp bestpath peer-type multipath-relax
 neighbor 10.0.3.2 remote-as 65002
 neighbor 2001:db8:0:3::2 remote-as 65002
 !
 address-family ipv4 unicast
  redistribute connected
  neighbor 10.0.3.2 activate
 exit-address-family
 !
 address-family ipv6 unicast
  redistribute connected
  neighbor 2001:db8:0:3::2 activate
 exit-address-family
exit
!
同様に、"config/frr2/frr.conf"は、frr2コンテナのFRRoutingの設定で、内容は以下の通りです。
frr defaults traditional
hostname frr2
service integrated-vtysh-config
!
interface lo
 ip address 2.2.2.2/32
exit
!
router bgp 65002
 bgp router-id 2.2.2.2
 no bgp ebgp-requires-policy
 no bgp default ipv4-unicast
 bgp bestpath as-path multipath-relax
 bgp bestpath compare-routerid
 bgp bestpath peer-type multipath-relax
 neighbor 10.0.3.1 remote-as 65001
 neighbor 2001:db8:0:3::1 remote-as 65001
 !
 address-family ipv4 unicast
  redistribute connected
  neighbor 10.0.3.1 activate
 exit-address-family
 !
 address-family ipv6 unicast
  redistribute connected
  neighbor 2001:db8:0:3::1 activate
 exit-address-family
exit
!
また、FRRoutingの設定のvtysh.confは以下で、
service integrated-vtysh-config
daemonsは以下です。
bgpd=yes
ospfd=no
ospf6d=no
ripd=no
ripngd=no
isisd=no
pimd=no
ldpd=no
nhrpd=no
eigrpd=no
babeld=no
sharpd=no
pbrd=no
bfdd=yes
fabricd=no
vrrpd=no
pathd=no

vtysh_enable=yes
zebra_options="  -A 127.0.0.1 -s 90000000"
bgpd_options="   -A 127.0.0.1"
ospfd_options="  -A 127.0.0.1"
ospf6d_options=" -A ::1"
ripd_options="   -A 127.0.0.1"
ripngd_options=" -A ::1"
isisd_options="  -A 127.0.0.1"
pimd_options="   -A 127.0.0.1"
ldpd_options="   -A 127.0.0.1"
nhrpd_options="  -A 127.0.0.1"
eigrpd_options=" -A 127.0.0.1"
babeld_options=" -A 127.0.0.1"
sharpd_options=" -A 127.0.0.1"
pbrd_options="   -A 127.0.0.1"
staticd_options="-A 127.0.0.1"
bfdd_options="   -A 127.0.0.1"
fabricd_options="-A 127.0.0.1"
vrrpd_options="  -A 127.0.0.1"
pathd_options="  -A 127.0.0.1"
このdokcer composeでは、FRRoutingのソースコードから、dockerイメージを作成して使用する内容にしています。ですので、トップディレクトリのfrr_dockerにて以下のコマンドを実施して、FRRoutingのソースコードを配置します(ここでは、FRRoutingのバージョンは10.2.1を指定しました)。
git clone https://github.com/FRRouting/frr.git
cd frr 
git checkout frr-10.2.1
以上で、必要なファイルの準備は完了です。

docker compose の実行と動作確認

トップディレクトリのfrr_dockerにて、docker composeの以下のコマンドを実行して、実験環境を起動します。
docker compose up -d
初回のdocker composeの実行時は、FRRoutingのソースコードからDockerイメージのビルドを実行するため、少し時間がかかります。Dockerイメージのビルドが完了しましたら、作成したイメージを使って各コンテナが作成されます。Dockerコンテナの起動状態を確認すると、以下のようにfrr1、frr2、host1、host2のコンテナが起動しています。
~/frr_docker$ docker compose ps
NAME      IMAGE       COMMAND                  SERVICE   CREATED              STATUS              PORTS
frr1      local/frr   "/usr/bin/tini -- /u…"   frr1      About a minute ago   Up About a minute
frr2      local/frr   "/usr/bin/tini -- /u…"   frr2      About a minute ago   Up About a minute
host1     alpine      "/bin/sh -c 'ip rout…"   host1     About a minute ago   Up About a minute
host2     alpine      "/bin/sh -c 'ip rout…"   host2     About a minute ago   Up About a minute
まず、frr1コンテナにてBGPのネイバー接続の状態を確認すると、以下の通り、IPv4とIPv6の両方について、BGPのセッションが確立されていることが確認できます。
~/frr_docker$ docker exec frr1 vtysh -c "show bgp summary"

IPv4 Unicast Summary:
BGP router identifier 1.1.1.1, local AS number 65001 VRF default vrf-id 0
BGP table version 5
RIB entries 9, using 1152 bytes of memory
Peers 1, using 24 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
10.0.3.2        4      65002         9         9        5    0    0 00:02:24            3        5 N/A

Total number of neighbors 1

IPv6 Unicast Summary:
BGP router identifier 1.1.1.1, local AS number 65001 VRF default vrf-id 0
BGP table version 3
RIB entries 5, using 640 bytes of memory
Peers 1, using 24 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
2001:db8:0:3::2 4      65002         8         9        3    0    0 00:02:22            2        3 N/A

Total number of neighbors 1
次に、IPv4とIPv6のルーティング情報を確認すると、以下の通り、BGPにてルーティング情報が交換されていることが分かります。
~/frr_docker$ docker exec frr1 vtysh -c "show ip route"
Codes: K - kernel route, C - connected, L - local, 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, t - Table-Direct,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

L * 1.1.1.1/32 is directly connected, lo, weight 1, 00:03:49
C>* 1.1.1.1/32 is directly connected, lo, weight 1, 00:03:49
B>* 2.2.2.2/32 [20/0] via 10.0.3.2, eth0, weight 1, 00:03:47
C>* 10.0.1.0/24 is directly connected, eth1, weight 1, 00:03:49
L>* 10.0.1.1/32 is directly connected, eth1, weight 1, 00:03:49
B>* 10.0.2.0/24 [20/0] via 10.0.3.2, eth0, weight 1, 00:03:47
C>* 10.0.3.0/24 is directly connected, eth0, weight 1, 00:03:49
L>* 10.0.3.1/32 is directly connected, eth0, weight 1, 00:03:49

~/frr_docker$ docker exec frr1 vtysh -c "show ipv6 route"
Codes: K - kernel route, C - connected, L - local, S - static,
       R - RIPng, O - OSPFv3, I - IS-IS, B - BGP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric, t - Table-Direct,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

C>* 2001:db8:0:1::/64 is directly connected, eth1, weight 1, 00:04:01
L>* 2001:db8:0:1::1/128 is directly connected, eth1, weight 1, 00:04:01
B>* 2001:db8:0:2::/64 [20/0] via fe80::42:aff:fe00:302, eth0, weight 1, 00:03:57
C>* 2001:db8:0:3::/64 is directly connected, eth0, weight 1, 00:04:01
L>* 2001:db8:0:3::1/128 is directly connected, eth0, weight 1, 00:04:01
C * fe80::/64 is directly connected, eth1, weight 1, 00:03:59
C>* fe80::/64 is directly connected, eth0, weight 1, 00:03:59
試しにhost1からhost2にIPv4とIPv6の両方でpingを実行すると、以下のように疎通を確認できます。
~/frr_docker$ docker exec host1 ping 10.0.2.11
PING 10.0.2.11 (10.0.2.11): 56 data bytes
64 bytes from 10.0.2.11: seq=0 ttl=62 time=0.167 ms
64 bytes from 10.0.2.11: seq=1 ttl=62 time=0.134 ms

~/frr_docker$ docker exec host1 ping 2001:db8:0:2::11
PING 2001:db8:0:2::11 (2001:db8:0:2::11): 56 data bytes
64 bytes from 2001:db8:0:2::11: seq=0 ttl=62 time=0.237 ms
64 bytes from 2001:db8:0:2::11: seq=1 ttl=62 time=0.152 ms
同様にhost2からhost1も以下のようにpingが成功します。
~/frr_docker$ docker exec host2 ping 10.0.1.11
PING 10.0.1.11 (10.0.1.11): 56 data bytes
64 bytes from 10.0.1.11: seq=0 ttl=62 time=0.111 ms
64 bytes from 10.0.1.11: seq=1 ttl=62 time=0.132 ms

~/frr_docker$ docker exec host2 ping 2001:db8:0:1::11
PING 2001:db8:0:1::11 (2001:db8:0:1::11): 56 data bytes
64 bytes from 2001:db8:0:1::11: seq=0 ttl=62 time=0.096 ms
64 bytes from 2001:db8:0:1::11: seq=1 ttl=62 time=0.142 ms

docker composeの工夫した点

docker composeを使って、FRRoutingの実験環境を作成する際に、いくつか工夫した点がありますので、ここでいくつか紹介いたします。

1) docker networkのデフォルトゲートウェイ

docker composeにてnetworkを作成し、そのIPセグメントを設定すると、dockerにて自動的にデフォルトゲートウェイのIPアドレスが設定されます。ただ、実際にFRRoutingやホストの通信に使いたいデフォルトゲートウェイのIPアドレスを重複することもあるため、docker composeの以下のように、実験で使用しないIPアドレスをdocker networkのデフォルトゲートウェイに設定しました。具体的には、以下の箇所で設定しているゲートウェイアドレスの10.0.1.2と2001:db8:0:1::2は、実験に使用しないダミーのアドレスです。
networks:
  host1_net:
    driver: macvlan
    enable_ipv6: true
    ipam:
      config:
        - subnet: 10.0.1.0/24
          gateway: 10.0.1.2
        - subnet: 2001:db8:0:1::/64
          gateway: 2001:db8:0:1::2
hostコンテナには、上記とは別にデフォルトゲートウェイアドレスを明示的に設定する必要があります。そのため、host1とhost2のコンテナには以下のようにデフォルトゲートウェイを設定するip routeコマンドを追加しています(以下はhost1の例)。
    command: /bin/sh -c "ip route add default via 10.0.1.1 && ip route add default via 2001:db8:0:1::1 && tail -f /dev/null"

2) FRRoutingのDockerイメージのビルド

FRRoutingの配布されているDockerイメージはalpineをベースにしているのですが、IPv6の通信が正常に動作しないケースがありました。ですので、今回はFRRoutingのソースコードを用意して、local/frrのタグがついたDebianベースのDockerイメージを作成しています。具体的には、docker composeでは以下の通り記載しています。
    image: local/frr
...
    build:
      context: ./frr/docker/debian/
      dockerfile: Dockerfile
上記の通りに記載することで、docker compose upコマンドを実行時に、frr/docker/debian/にあるDockerfileを使って、local/frrのDockerイメージがビルドされます。

3) FRRoutingコンテナのIPv6中継の有効化

FRRoutingコンテナにてIPv6通信を中継できるようにするために、sysctlにてIPv6の中継を有効にしておく必要があります。そのため、FRRoutingコンテナにて以下のsysctlの定義をdocker composeに記載しています。
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1

最後に

以上、docker composeを使って、FRRoutingのIPv4/IPv6通信試験環境の構築方法を説明いたしました。この記事では記載していませんが、docker composeでは、docker networkに物理サーバのNICを割り当てることで、サーバの外の物理装置とも接続する試験環境を用意することが可能です。そうすることで、docker composeで用意したFRRoutingを使って、物理装置に対してL3ルーティングプロトコルの試験を容易に実施することが可能になります。この記事が何かの役に立てれば幸いです。