BIRD 1 は The BIRD Internet Routing Daemon の略で、ルーティングプロトコルを実装した OSS のひとつ。 今回は、そんな BIRD を Network Namespace と組み合わせて RIPv2 を使ったダイナミックルーティングを設定をしてみる。 なお、現在 (2023-06-16) の BIRD はバージョン 1 系と 2 系が平行してメンテナンスされているが、今回使うのはバージョン 2 系である。
今回のエントリは、以下のエントリの RIPv2 を使ったバージョンになっている。 そのため、こちらに先に目を通しておくと内容を理解しやすい。
使った環境は次のとおり。
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=22.04 DISTRIB_CODENAME=jammy DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS" $ uname -srm Linux 5.15.0-73-generic x86_64 $ bird --version BIRD version 2.0.8
もくじ
下準備
まずは BIRD のバージョン 2 系をインストールする。
$ sudo apt-get update $ sudo apt-get install bird2
インストールすると同時に BIRD のサービスが動作し始める。 しかし、今回は systemd 経由では BIRD を使わないため止めておく。
$ sudo systemctl stop bird
$ sudo systemctl disable bird
ネットワークを構築する
はじめに Network Namespace を使ってネットワークを構築する。 今回作るのは、以下のようなネットワークになる。 先のスタティックルーティングの構成と変わらない。
上記で、router1
は 198.51.100.0/24
と直接はつながっていないため経路を知らない。
同様に、router2
は 192.0.2.0/24
と直接はつながっていないため経路を知らない。
そこで、BIRD の RIPv2 を使って経路を交換するのが今回の目的になる。
まずは Network Namespace を用意する。
$ sudo ip netns add ns1 $ sudo ip netns add router1 $ sudo ip netns add router2 $ sudo ip netns add ns2
Network Namespace 間をつなぐ Virtual Ethernet Device のインターフェイスを用意する。
$ sudo ip link add ns1-veth0 type veth peer name gw1-veth0 $ sudo ip link add gw1-veth1 type veth peer name gw2-veth0 $ sudo ip link add gw2-veth1 type veth peer name ns2-veth0
作成したインターフェイスを Network Namespace に所属させる。
$ sudo ip link set ns1-veth0 netns ns1 $ sudo ip link set gw1-veth0 netns router1 $ sudo ip link set gw1-veth1 netns router1 $ sudo ip link set gw2-veth0 netns router2 $ sudo ip link set gw2-veth1 netns router2 $ sudo ip link set ns2-veth0 netns ns2
それぞれのインターフェイスをリンクアップさせる。
$ sudo ip netns exec ns1 ip link set ns1-veth0 up $ sudo ip netns exec router1 ip link set gw1-veth0 up $ sudo ip netns exec router1 ip link set gw1-veth1 up $ sudo ip netns exec router2 ip link set gw2-veth0 up $ sudo ip netns exec router2 ip link set gw2-veth1 up $ sudo ip netns exec ns2 ip link set ns2-veth0 up
それぞれのインターフェイスに IP アドレスを付与する。
$ sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 $ sudo ip netns exec router1 ip address add 192.0.2.254/24 dev gw1-veth0 $ sudo ip netns exec router1 ip address add 203.0.113.1/24 dev gw1-veth1 $ sudo ip netns exec router2 ip address add 203.0.113.2/24 dev gw2-veth0 $ sudo ip netns exec router2 ip address add 198.51.100.254/24 dev gw2-veth1 $ sudo ip netns exec ns2 ip address add 198.51.100.1/24 dev ns2-veth0
ホストに相当する Network Namespace にはデフォルトルートを設定しておく。
$ sudo ip netns exec ns1 ip route add default via 192.0.2.254 $ sudo ip netns exec ns2 ip route add default via 198.51.100.254
そして、ルータになる Network Namespace は IPv4 のルーティングを有効にする。
$ sudo ip netns exec router1 sysctl net.ipv4.ip_forward=1 $ sudo ip netns exec router2 sysctl net.ipv4.ip_forward=1
この状態で、それぞれのルータは自身が直接つながっているセグメントへの経路は把握している。
$ sudo ip netns exec router1 ip route show 192.0.2.0/24 dev gw1-veth0 proto kernel scope link src 192.0.2.254 203.0.113.0/24 dev gw1-veth1 proto kernel scope link src 203.0.113.1
しかし、直接つながっていないセグメントへの経路がないため ns1
から ns2
の間で ping(8) は通らない。
$ sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1 PING 198.51.100.1 (198.51.100.1) from 192.0.2.1 : 56(84) bytes of data. From 192.0.2.254 icmp_seq=1 Destination Net Unreachable From 192.0.2.254 icmp_seq=2 Destination Net Unreachable From 192.0.2.254 icmp_seq=3 Destination Net Unreachable --- 198.51.100.1 ping statistics --- 3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2041ms
router1 の BIRD を設定する
まずは router1
の BIRD を設定する。
BIRD は 1 つの設定ファイルだけで動作する。
以下が、その設定ファイルになる。
$ cat << 'EOF' > router1.conf log syslog all; log stderr all; log "/var/log/bird-router1.log" all; debug protocols all; router id 203.0.113.1; protocol device { } protocol direct { ipv4; } protocol rip { ipv4 { import all; export all; }; interface "gw1-veth1" { update time 10; }; } protocol kernel { ipv4 { export filter { if proto = "direct1" then reject; accept; }; }; } EOF
スタティックルーティングの場合から変わっている部分として、まず direct プロトコルがある。
これは、自身が直接つながって知っているネットワークに関する情報を BIRD のルーティングテーブルに取り込む機能になっている。
デフォルトでは disabled;
が設定されており、有効にするときは ipv4;
や ipv6;
を指定して使う。
今回は直接つながっているネットワークの中で IPv4 の情報を取り込んで RIP で広告したいので ipv4;
を有効にしている。
protocol direct { ipv4; }
rip プロトコルに関しては、今回の主題となる部分。
ipv4
の import all
は、RIP から得られた経路情報をすべて BIRD のルーティングテーブルに取り込む、という意味になる。
同様に export all
は、BIRD の知っている経路情報をすべて RIP で伝達する、という意味になる。
interface "gw1-veth1"
は RIP を使うインターフェイスを指定している。
RIP は定期的に経路情報を広告するプロトコルであり update time 10;
で、その広告する間隔を変更している。
デフォルトは 30 秒だが 10 秒まで短くしているのは動作確認がやりやすくなるように。
protocol rip { ipv4 { import all; export all; }; interface "gw1-veth1" { update time 10; }; }
あとは BIRD を起動する。
-d
オプションを指定すると、フォアグラウンドでデバッグログを標準エラー出力に出すように起動する。
オプションをつけなければ、端末を切り離してデーモンとして動作する。
$ sudo ip netns exec router1 bird -d \ -c router1.conf \ -s router1.ctl \ -P router1.pid
起動したら別のターミナルを開いてパケットをキャプチャしてみよう。 RIP はマルチキャストアドレス宛てに UDP の 520 番ポートで経路情報をやり取りする。
$ sudo ip netns exec router1 tcpdump -tnlvvv -i gw1-veth1 "udp port 520 and ip multicast" tcpdump: listening on gw1-veth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes IP (tos 0xc0, ttl 1, id 35535, offset 0, flags [none], proto UDP (17), length 72) 203.0.113.1.520 > 224.0.0.9.520: [bad udp cksum 0x1c51 -> 0xe15e!] RIPv2, Response, length: 44, routes: 2 or less AFI IPv4, 192.0.2.0/24, tag 0x0000, metric: 1, next-hop: self AFI IPv4, 203.0.113.0/24, tag 0x0000, metric: 1, next-hop: self 0x0000: 0202 0000 0002 0000 c000 0200 ffff ff00 0x0010: 0000 0000 0000 0001 0002 0000 cb00 7100 0x0020: ffff ff00 0000 0000 0000 0001 ^C 1 packet captured 1 packet received by filter 0 packets dropped by kernel
上記から、自身が直接つながって知っている経路を広告していることが確認できる。
この段階では、まだ他のルータからの経路情報を受け取っていないのでカーネルのルーティングテーブルに変化はない。
$ sudo ip netns exec router1 ip route show 192.0.2.0/24 dev gw1-veth0 proto kernel scope link src 192.0.2.254 203.0.113.0/24 dev gw1-veth1 proto kernel scope link src 203.0.113.1
router2 の BIRD を設定する
同じように router2
についても BIRD の設定ファイルを用意しよう。
$ cat << 'EOF' > router2.conf log syslog all; log stderr all; log "/var/log/bird-router2.log" all; debug protocols all; router id 203.0.113.2; protocol device { } protocol direct { ipv4; } protocol rip { ipv4 { import all; export all; }; interface "gw2-veth0" { update time 10; }; } protocol kernel { ipv4 { export filter { if proto = "direct1" then reject; accept; }; }; } EOF
そして、別のターミナルを開いて BIRD を起動する。
$ sudo ip netns exec router2 bird -d \ -c router2.conf \ -s router2.ctl \ -P router2.pid
動作を確認する
少し待ってから動作を確認してみよう。
まずは router1
の BIRD のルーティングテーブルを確認してみる。
$ sudo birdc show route -s router1.ctl BIRD 2.0.8 ready. Table master4: 198.51.100.0/24 unicast [rip1 02:44:56.441] * (120/2) via 203.0.113.2 on gw1-veth1 192.0.2.0/24 unicast [direct1 02:44:35.774] * (240) dev gw1-veth0 203.0.113.0/24 unicast [direct1 02:44:35.774] * (240) dev gw1-veth1 unicast [rip1 02:44:56.441] (120/2) via 203.0.113.2 on gw1-veth1
router1
が直接つながっておらず、知らないはずの 198.51.100.0/24
がルーティングテーブルに登場している。
そして、このネットワークには 203.0.113.2
を経由して到達できるとあり、これは router2
の IP アドレスである。
カーネルのルーティングテーブルを確認しても、同様の経路がちゃんと追加されている。
$ sudo ip netns exec router1 ip route show 192.0.2.0/24 dev gw1-veth0 proto kernel scope link src 192.0.2.254 198.51.100.0/24 via 203.0.113.2 dev gw1-veth1 proto bird metric 32 203.0.113.0/24 dev gw1-veth1 proto kernel scope link src 203.0.113.1
BIRD のプロトコルの稼働状況は次のとおり。 ちゃんとRIP を使った経路交換ができているようだ。
$ sudo birdc show protocols all -s router1.ctl BIRD 2.0.8 ready. Name Proto Table State Since Info device1 Device --- up 02:44:35.772 direct1 Direct --- up 02:44:35.772 Channel ipv4 State: UP Table: master4 Preference: 240 Input filter: ACCEPT Output filter: REJECT Routes: 2 imported, 0 exported, 2 preferred Route change stats: received rejected filtered ignored accepted Import updates: 2 0 0 0 2 Import withdraws: 0 0 --- 0 0 Export updates: 0 0 0 --- 0 Export withdraws: 0 --- --- --- 0 rip1 RIP master4 up 02:44:35.772 Channel ipv4 State: UP Table: master4 Preference: 120 Input filter: ACCEPT Output filter: ACCEPT Routes: 2 imported, 3 exported, 1 preferred Route change stats: received rejected filtered ignored accepted Import updates: 2 0 0 0 2 Import withdraws: 0 0 --- 0 0 Export updates: 5 0 0 --- 5 Export withdraws: 0 --- --- --- 0 kernel1 Kernel master4 up 02:44:35.772 Channel ipv4 State: UP Table: master4 Preference: 10 Input filter: ACCEPT Output filter: (unnamed) Routes: 0 imported, 1 exported, 0 preferred Route change stats: received rejected filtered ignored accepted Import updates: 0 0 0 0 0 Import withdraws: 0 0 --- 0 0 Export updates: 5 0 4 --- 1 Export withdraws: 0 --- --- --- 0
あらためてパケットをキャプチャすると、内容に変化がある。
まず、当然ながら router2
も RIPv2 で経路を広告するようになっている。
そして、router1
と router2
が広告する内容には、今回登場するすべてのセグメントが含まれている。
たとえば router1
は 198.51.100.0/24
の経路を metric: 16
として広告しているようだ。
RIP において、メトリックが 16 というのはデフォルトでは無限遠や到達不能といった意味を持っている。
このような経路が広告されているのは、経路のループを防ぐスプリットホライズンとポイズンリバースが BIRD ではデフォルトで有効になっているため。
まず、スプリットホライズンは経路のループを防ぐために、あるインターフェイスから受け取った経路を、そのインターフェイスには広告しない機能である。
ポイズンリバースはスプリットホライズンのオプションで、あるインターフェイスから受け取った経路を、そのインターフェイスから到達不能という形で広告する。
$ sudo ip netns exec router1 tcpdump -tnlvvv -i gw1-veth1 "udp port 520 and ip multicast" tcpdump: listening on gw1-veth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes IP (tos 0xc0, ttl 1, id 26403, offset 0, flags [none], proto UDP (17), length 92) 203.0.113.1.520 > 224.0.0.9.520: [bad udp cksum 0x1c65 -> 0xb7ef!] RIPv2, Response, length: 64, routes: 3 or less AFI IPv4, 198.51.100.0/24, tag 0x0000, metric: 16, next-hop: self AFI IPv4, 192.0.2.0/24, tag 0x0000, metric: 1, next-hop: self AFI IPv4, 203.0.113.0/24, tag 0x0000, metric: 1, next-hop: self 0x0000: 0202 0000 0002 0000 c633 6400 ffff ff00 0x0010: 0000 0000 0000 0010 0002 0000 c000 0200 0x0020: ffff ff00 0000 0000 0000 0001 0002 0000 0x0030: cb00 7100 ffff ff00 0000 0000 0000 0001 IP (tos 0xc0, ttl 1, id 11531, offset 0, flags [none], proto UDP (17), length 92) 203.0.113.2.520 > 224.0.0.9.520: [bad udp cksum 0x1c66 -> 0xb7ee!] RIPv2, Response, length: 64, routes: 3 or less AFI IPv4, 198.51.100.0/24, tag 0x0000, metric: 1, next-hop: self AFI IPv4, 192.0.2.0/24, tag 0x0000, metric: 16, next-hop: self AFI IPv4, 203.0.113.0/24, tag 0x0000, metric: 1, next-hop: self 0x0000: 0202 0000 0002 0000 c633 6400 ffff ff00 0x0010: 0000 0000 0000 0001 0002 0000 c000 0200 0x0020: ffff ff00 0000 0000 0000 0010 0002 0000 0x0030: cb00 7100 ffff ff00 0000 0000 0000 0001
どうやら経路の交換がうまくいっているようなので ns1
から ns2
へ ping を打ってみよう。
$ sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1 PING 198.51.100.1 (198.51.100.1) from 192.0.2.1 : 56(84) bytes of data. 64 bytes from 198.51.100.1: icmp_seq=1 ttl=62 time=0.129 ms 64 bytes from 198.51.100.1: icmp_seq=2 ttl=62 time=0.097 ms 64 bytes from 198.51.100.1: icmp_seq=3 ttl=62 time=0.112 ms --- 198.51.100.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2045ms rtt min/avg/max/mdev = 0.097/0.112/0.129/0.013 ms
ちゃんと疎通があることが確認できた。
まとめ
今回は Network Namespace で構築したネットワーク上で BIRD を動かし RIPv2 を使ったダイナミックルーティングを試してみた。