BIRD を用いて Linux サーバで BGP を試す

目次

OSS の BIRD を使って Linux サーバで BGP を試してみました。

ネットワーク構成

次の図のような3つの拠点があるネットワーク構成で試しました。

拠点同士は2点間の VPN (閉域網、IPsec, WireGuard など) で接続されているという想定です。

各拠点のルータに静的経路を入れれば各拠点のネットワーク同士は簡単に通信できるようになりますが、BGPで経路を設定しようというのが今回のブログの趣旨です。 企業の拠点だと拠点内のネットワークは複雑で、静的経路をいろんなところに書いて回るのは大変ですしね。

検証用ネットワークの構築

検証用に Linux サーバを複数用意するのは大変なので、1台の Linux サーバ (Ubuntu 24.04 LTS) 上で Network Namespace で仮想的な検証用ネットワークを用意しました。

 1$ vim setup-netns.sh
 2$ cat setup-netns.sh
 3----------
 4sudo ip netns add router1
 5sudo ip netns add router2
 6sudo ip netns add router3
 7sudo ip netns add server1
 8sudo ip netns add server2
 9sudo ip netns add server3
10
11sudo ip netns exec router1 sysctl net.ipv4.conf.all.forwarding=1
12sudo ip netns exec router2 sysctl net.ipv4.conf.all.forwarding=1
13sudo ip netns exec router3 sysctl net.ipv4.conf.all.forwarding=1
14
15sudo ip netns exec router1 ip link add name tun12 type veth peer name tun12 netns router2
16sudo ip netns exec router1 ip link set dev tun12 up
17sudo ip netns exec router2 ip link set dev tun12 up
18sudo ip netns exec router1 ip addr add 169.254.12.1/30 dev tun12
19sudo ip netns exec router2 ip addr add 169.254.12.2/30 dev tun12
20
21sudo ip netns exec router1 ip link add name tun13 type veth peer name tun13 netns router3
22sudo ip netns exec router1 ip link set dev tun13 up
23sudo ip netns exec router3 ip link set dev tun13 up
24sudo ip netns exec router1 ip addr add 169.254.13.1/30 dev tun13
25sudo ip netns exec router3 ip addr add 169.254.13.2/30 dev tun13
26
27sudo ip netns exec router2 ip link add name tun23 type veth peer name tun23 netns router3
28sudo ip netns exec router2 ip link set dev tun23 up
29sudo ip netns exec router3 ip link set dev tun23 up
30sudo ip netns exec router2 ip addr add 169.254.23.1/30 dev tun23
31sudo ip netns exec router3 ip addr add 169.254.23.2/30 dev tun23
32
33sudo ip netns exec router1 ip link add name eth0 type veth peer name eth0 netns server1
34sudo ip netns exec router1 ip link set dev eth0 up
35sudo ip netns exec server1 ip link set dev eth0 up
36sudo ip netns exec router1 ip addr add 10.1.0.1/24 dev eth0
37sudo ip netns exec server1 ip addr add 10.1.0.2/24 dev eth0
38sudo ip netns exec server1 ip route add default dev eth0 via 10.1.0.1
39
40sudo ip netns exec router2 ip link add name eth0 type veth peer name eth0 netns server2
41sudo ip netns exec router2 ip link set dev eth0 up
42sudo ip netns exec server2 ip link set dev eth0 up
43sudo ip netns exec router2 ip addr add 10.2.0.1/24 dev eth0
44sudo ip netns exec server2 ip addr add 10.2.0.2/24 dev eth0
45sudo ip netns exec server2 ip route add default dev eth0 via 10.2.0.1
46
47sudo ip netns exec router3 ip link add name eth0 type veth peer name eth0 netns server3
48sudo ip netns exec router3 ip link set dev eth0 up
49sudo ip netns exec server3 ip link set dev eth0 up
50sudo ip netns exec router3 ip addr add 10.3.0.1/24 dev eth0
51sudo ip netns exec server3 ip addr add 10.3.0.2/24 dev eth0
52sudo ip netns exec server3 ip route add default dev eth0 via 10.3.0.1
53----------
54
55$ bash setup-netns.sh

3つのルータ (router1, router2, router3) と3つのサーバ (server1, server2, server3) のルーティングテーブルは次のような感じです。

 1$ sudo ip netns exec router1 ip route
 2----------
 310.1.0.0/24 dev eth0 proto kernel scope link src 10.1.0.1
 4169.254.12.0/30 dev tun12 proto kernel scope link src 169.254.12.1
 5169.254.13.0/30 dev tun13 proto kernel scope link src 169.254.13.1
 6----------
 7
 8$ sudo ip netns exec router2 ip route
 9----------
1010.2.0.0/24 dev eth0 proto kernel scope link src 10.2.0.1
11169.254.12.0/30 dev tun12 proto kernel scope link src 169.254.12.2
12169.254.23.0/30 dev tun23 proto kernel scope link src 169.254.23.1
13----------
14
15$ sudo ip netns exec router3 ip route
16----------
1710.3.0.0/24 dev eth0 proto kernel scope link src 10.3.0.1
18169.254.13.0/30 dev tun13 proto kernel scope link src 169.254.13.2
19169.254.23.0/30 dev tun23 proto kernel scope link src 169.254.23.2
20----------
21
22$ sudo ip netns exec server1 ip route
23----------
24default via 10.1.0.1 dev eth0
2510.1.0.0/24 dev eth0 proto kernel scope link src 10.1.0.2
26----------
27
28$ sudo ip netns exec server2 ip route
29----------
30default via 10.2.0.1 dev eth0
3110.2.0.0/24 dev eth0 proto kernel scope link src 10.2.0.2
32----------
33
34$ sudo ip netns exec server3 ip route
35----------
36default via 10.3.0.1 dev eth0
3710.3.0.0/24 dev eth0 proto kernel scope link src 10.3.0.2
38----------

この時点では各拠点はお互いのネットワークのことを知らず経路がないため、例えば拠点1のサーバから拠点2のサーバへの疎通はありません。

1$ sudo ip netns exec server1 ping 10.2.0.2
2----------
3PING 10.2.0.2 (10.2.0.2) 56(84) bytes of data.
4From 10.1.0.1 icmp_seq=1 Destination Net Unreachable
5From 10.1.0.1 icmp_seq=2 Destination Net Unreachable
6From 10.1.0.1 icmp_seq=3 Destination Net Unreachable
7----------

BIRD の設定

Ubuntu 24.04 LTS の標準の apt リポジトリには BIRD のバージョン1系と2系がありますが、今回は2系を利用することにしました。

1$ sudo apt install bird2
2$ sudo systemctl disable bird --now
3
4$ bird --version
5----------
6BIRD version 2.14
7----------

次に、BIRD User's Guide を参考に、各拠点のルータで実行する BIRD の設定を作りました。

router1 用の BIRD の設定は次のような内容です。

 1$ vim bird-router1.conf
 2$ cat bird-router1.conf
 3----------
 4debug protocols all;
 5
 6protocol device {
 7}
 8
 9protocol direct {
10	interface "-tun*", "*";
11	ipv4;
12}
13
14protocol kernel {
15	ipv4 {
16		import all;
17		export filter {
18			if proto = "direct1" then reject;
19			accept;
20		};
21	};
22}
23
24protocol bgp {
25	local 169.254.12.1 as 65001;
26	neighbor 169.254.12.2 as 65002;
27
28	ipv4 {
29		import all;
30		export all;
31		next hop self;
32	};
33}
34
35protocol bgp {
36	local 169.254.13.1 as 65001;
37	neighbor 169.254.13.2 as 65003;
38
39	ipv4 {
40		import all;
41		export all;
42		next hop self;
43	};
44}
45----------

BIRD はネットワークインターフェースやカーネルのルーティングテーブルなどを読み取って独自のルーティングテーブルを持ち、それを BGP などで交換したりし、カーネルのルーティングテーブルに変更を書き込んだりしてくれます。

上記の設定の場合、direct プロトコルによってネットワークインターフェースが持つIPアドレスをもとに BIRD は経路を自身のルーティングテーブルに追加します。 ただし、"tun" から始まる名前のインターフェースは VPN 接続の両端としており経路を交換する必要がないので、direct プロトコルの対象外としており、10.1.0.0/24 などの各拠点のネットワークの経路だけが BIRD に追加されるようにしています。

また、kernel プロトコルでは、BIRD が持つ経路のうち direct プロトコルによって追加した経路以外をカーネルのルーティングテーブルに書き込むようにしています。 そうすることで BGP で入手した経路を使ってカーネルがルーティングできるようになります。

bgp プロトコルでは、ピアとの接続情報を設定し、自身が持つ全ての経路をピアに広報し、ピアから受け取った全ての経路を自身でも持つようにしています。

BIRD の仕組みや設定方法の詳細については、BIRD User's Guide も参照ください。

BIRD の実行

まず、router1 だけで BIRD を起動してみます。 確認のためデバッグログも出力させておきます。

 1$ sudo ip netns exec router1 bird -f -c bird-router1.conf -s bird-router1.ctl -d
 2----------
 3bird: device1: Initializing
 4bird: direct1: Initializing
 5bird: kernel1: Initializing
 6bird: bgp1: Initializing
 7bird: bgp2: Initializing
 8bird: device1: Starting
 9bird: device1: Scanning interfaces
10bird: device1: State changed to up
11bird: Chosen router ID 10.1.0.1 according to interface eth0
12bird: direct1: Starting
13bird: direct1: State changed to up
14bird: kernel1: Starting
15bird: kernel1: State changed to up
16bird: bgp1: Starting
17bird: bgp1: State changed to start
18bird: bgp2: Starting
19bird: bgp2: State changed to start
20bird: Started
21bird: direct1 < interface lo created
22bird: direct1 < interface tun12 goes up
23bird: direct1 < address 169.254.12.0/30 on interface tun12 added
24bird: direct1 < address fe80::/64 on interface tun12 added
25bird: direct1 < interface tun13 goes up
26bird: direct1 < address 169.254.13.0/30 on interface tun13 added
27bird: direct1 < address fe80::/64 on interface tun13 added
28bird: direct1 < interface eth0 goes up
29bird: direct1 < address 10.1.0.0/24 on interface eth0 added
30bird: direct1.ipv4 > added [best] 10.1.0.0/24 4L 4G unicast
31bird: kernel1.ipv4 < filtered out 10.1.0.0/24 4L 4G unicast
32bird: direct1 < address fe80::/64 on interface eth0 added
33bird: kernel1 < interface lo created
34bird: kernel1 < interface tun12 goes up
35bird: kernel1 < interface tun13 goes up
36bird: kernel1 < interface eth0 goes up
37bird: bgp2: Started
38bird: bgp2: Connect delayed by 5 seconds
39bird: bgp1: Started
40bird: bgp1: Connect delayed by 5 seconds
41bird: kernel1.ipv4 < filtered out 10.1.0.0/24 4L 4G unicast
42bird: kernel1: Scanning routing table
43bird: kernel1: 10.1.0.0/24: ignored
44bird: kernel1: 169.254.12.0/30: ignored
45bird: kernel1: 169.254.13.0/30: ignored
46bird: kernel1: Pruning table master4
47bird: bgp2: Connecting to 169.254.13.2 from local address 169.254.13.1
48bird: bgp2: Connection lost (Connection refused)
49bird: bgp2: Connect delayed by 5 seconds
50bird: bgp1: Connecting to 169.254.12.2 from local address 169.254.12.1
51bird: bgp1: Connection lost (Connection refused)
52bird: bgp1: Connect delayed by 5 seconds
53以下接続を試行し続ける
54----------

router2, router3 ではまだ BIRD を起動していないので、BGP 接続は行えていません。

BIRD は設定に従って自身のルーティングテーブルを作るのですが、> が含まれている次の行は BIRD のルーティングテーブルに経路が追加されたことを示しています。

1bird: direct1.ipv4 > added [best] 10.1.0.0/24 4L 4G unicast

BIRD が持つルーティングテーブルは birdc show route コマンドでも確認でき、設定どおりです。

1$ sudo birdc -s bird-router1.ctl show route
2----------
3BIRD 2.14 ready.
4Table master4:
510.1.0.0/24          unicast [direct1 16:36:34.324] * (240)
6        dev eth0
7----------

次に router2, router3 でも BIRD を起動してみます。

1$ sudo ip netns exec router2 bird -f -c bird-router2.conf -s bird-router2.ctl -d
1$ sudo ip netns exec router3 bird -f -c bird-router3.conf -s bird-router3.ctl -d

router1, router2, router3 の間の BGP 接続が確立して経路の交換が行われ、router1 の BIRD が持つルーティングテーブルは次のように変わっていました。

 1$ sudo birdc -s bird-router1.ctl show route
 2----------
 3BIRD 2.14 ready.
 4Table master4:
 510.3.0.0/24          unicast [bgp2 20:17:45.344] * (100) [AS65003i]
 6        via 169.254.13.2 on tun13
 7                     unicast [bgp1 20:17:46.986] (100) [AS65003i]
 8        via 169.254.12.2 on tun12
 910.1.0.0/24          unicast [direct1 20:17:18.766] * (240)
10        dev eth0
1110.2.0.0/24          unicast [bgp1 20:17:34.824] * (100) [AS65002i]
12        via 169.254.12.2 on tun12
13                     unicast [bgp2 20:17:46.987] (100) [AS65002i]
14        via 169.254.13.2 on tun13
15----------

拠点2や拠点3への経路が追加されており、直接の経路だけでなく、拠点3経由の拠点2への経路や、拠点2経由の拠点3への経路も追加されていますね。

router1 のデバッグログにも次の4つの経路追加ログが出力されていました。 bgp1 (router2 との BGP セッション) によって得た拠点2への経路や、bgp2 (router3 との BGP セッション) によって得た拠点3への経路が best として選択されています。

1bird: bgp1.ipv4 > added [best] 10.2.0.0/24 0L 5G unicast
2bird: bgp2.ipv4 > added [best] 10.3.0.0/24 0L 6G unicast
3bird: bgp1.ipv4 > added 10.3.0.0/24 0L 5G unicast
4bird: bgp2.ipv4 > added 10.2.0.0/24 0L 6G unicast

router1 のカーネルのルーティングテーブルも想定通りでいい感じです。

1$ sudo ip netns exec router1 ip route
2----------
310.1.0.0/24 dev eth0 proto kernel scope link src 10.1.0.1
410.2.0.0/24 via 169.254.12.2 dev tun12 proto bird metric 32
510.3.0.0/24 via 169.254.13.2 dev tun13 proto bird metric 32
6169.254.12.0/30 dev tun12 proto kernel scope link src 169.254.12.1
7169.254.13.0/30 dev tun13 proto kernel scope link src 169.254.13.1
8----------

拠点1のサーバから拠点2のサーバへの疎通もとれるようになりました。

1$ sudo ip netns exec server1 ping 10.2.0.2
2----------
3PING 10.2.0.2 (10.2.0.2) 56(84) bytes of data.
464 bytes from 10.2.0.2: icmp_seq=1 ttl=62 time=0.102 ms
564 bytes from 10.2.0.2: icmp_seq=2 ttl=62 time=0.123 ms
664 bytes from 10.2.0.2: icmp_seq=3 ttl=62 time=0.140 ms
7----------

簡易的な障害試験

拠点間の VPN 接続で障害が発生した場合を想定した簡易的な試験をしてみます。

ここでは拠点1と拠点2の VPN 接続が全断しパケットが全てロスする状況を模擬してみます。 今回は router2 で tun12 インターフェースを通るパケットを iptables でドロップさせました。

1sudo ip netns exec router2 iptables -A FORWARD -i tun12 -j DROP
2sudo ip netns exec router2 iptables -A FORWARD -o tun12 -j DROP
3sudo ip netns exec router2 iptables -A INPUT -i tun12 -j DROP
4sudo ip netns exec router2 iptables -A OUTPUT -o tun12 -j DROP

server1 から server2 への ping を見ていると、200秒ほど疎通がなくなり、その後回復しました。

router1 の BIRD のデバッグログには次のように出力されており、Hold timer により router2 との間の BGP セッションが失われたと判断され、代わりの経路がカーネルのルーティングテーブルに書き込まれたことを示しています。

 1bird: bgp1: Error: Hold timer expired
 2bird: bgp1: BGP session closed
 3bird: bgp1: State changed to stop
 4bird: bgp1.ipv4 > removed 10.3.0.0/24 0L 5G unicast
 5bird: bgp1.ipv4 > removed [replaced] 10.2.0.0/24 0L 5G unicast
 6bird: kernel1.ipv4 < replaced 10.2.0.0/24 0L 6G unicast
 7bird: bgp2.ipv4 < rejected by protocol 10.2.0.0/24 0L 6G unicast
 8bird: bgp2.ipv4 < removed 10.2.0.0/24 0L 5G unicast
 9bird: bgp1: Sending NOTIFICATION(code=4.0)
10bird: bgp2: Sending UPDATE
11bird: bgp1: Down
12bird: bgp1: State changed to flush
13bird: bgp1: State changed to down
14bird: bgp1: Starting
15bird: bgp1: State changed to start
16bird: bgp1: Startup delayed by 60 seconds due to errors

router1 のカーネルや BIRD のルーティングテーブルは次のように変わっており、拠点1から拠点2へは拠点3を経由するようになっていますね。

 1$ sudo ip netns exec router1 ip route
 2----------
 310.1.0.0/24 dev eth0 proto kernel scope link src 10.1.0.1
 410.2.0.0/24 via 169.254.13.2 dev tun13 proto bird metric 32
 510.3.0.0/24 via 169.254.13.2 dev tun13 proto bird metric 32
 6169.254.12.0/30 dev tun12 proto kernel scope link src 169.254.12.1
 7169.254.13.0/30 dev tun13 proto kernel scope link src 169.254.13.1
 8----------
 9
10
11$ sudo birdc -s bird-router1.ctl show route
12----------
13BIRD 2.14 ready.
14Table master4:
1510.3.0.0/24          unicast [bgp2 17:10:10.454] * (100) [AS65003i]
16        via 169.254.13.2 on tun13
1710.1.0.0/24          unicast [direct1 17:10:06.473] * (240)
18        dev eth0
1910.2.0.0/24          unicast [bgp2 17:10:12.174] * (100) [AS65002i]
20        via 169.254.13.2 on tun13
21----------

障害を回復させた (iptables の DROP ルールを削除した) ら、BGP 接続が再確立してルーティングテーブルも元通りになりました。 障害試験としては期待通りの結果です。

BGP セッションが失われたと判断するまでに時間がかかりすぎですが、これは BIRD の標準設定では Hold timer が240秒だからです。 bgp プロトコルの設定に hold time 30; を追加して30秒に変更すれば、30秒程度で経路が切り替わりましたので、これで良しです。

まとめ

BIRD の動作の仕組みが概ね理解でき、BGP で経路交換が行えることも確認できました。

実際の WAN は今回のようなシンプルなものではないですが、あとは応用でどうにでもなりそうです。 VPN 接続の部分も WireGuard や Libreswan などでやってしまえば、ルータ機器を使わなくても Linux の汎用OSだけで WAN が作れそうですね。

企業ユースだとそんなことをする必要性はないので完全に趣味でしかありませんけど。。