CUBE SUGAR CONTAINER

技術系のこと書きます。

BIRD と Network Namespace で BGP-4 を使ったダイナミックルーティングを試す

BIRD 1 (The BIRD Internet Routing Daemon) はルーティングプロトコルを実装した OSS のひとつ。 今回は、そんな BIRD を Network Namespace と組み合わせて BGP-4 を使ったダイナミックルーティングを設定をしてみる。 なお、現在 (2023-06-22) の BIRD はバージョン 1 系と 2 系が平行してメンテナンスされているが、今回使うのはバージョン 2 系になる。

今回のエントリは、以下のエントリの BGP-4 を使ったバージョンになっている。 そのため、こちらに先に目を通しておくと内容を理解しやすい。

blog.amedama.jp

BGP-4 には異なる AS (Autonomous System) 間での経路交換に使われる eBGP と、同じ AS 内での経路交換に使われる iBGP の 2 種類の使い方がある。 今回扱うのは前者の eBGP の方だけ。

使った環境は次のとおり。

$ 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-75-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 を使ってネットワークを構築する。 今回作るのは、以下のようなネットワークになる。 先のスタティックルーティングの構成と変わらない。

ネットワーク図 (論理構成)

上記で、router1198.51.100.0/24 と直接はつながっていないため経路を知らない。 同様に、router2192.0.2.0/24 と直接はつながっていないため経路を知らない。 そこで、router1router2 が BIRD の BPG-4 を使って経路を交換するのが今回の目的になる。

まずは 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 の設定ファイルになる。

$ 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 bgp {
    local as 65001;
    source address 203.0.113.1;
    neighbor 203.0.113.2 as 65002;
    ipv4 {
        import all;
        export all;
    };
}

protocol kernel {
    ipv4 {
        export filter {
            if proto = "direct1" then reject;
            accept;
        };
    };
}
EOF

スタティックルーティングの場合から変わっている部分として、まず direct プロトコルがある。 これは、自身が直接つながって知っているネットワークに関する情報を BIRD のルーティングテーブルに取り込む機能になっている。 デフォルトでは disabled; が設定されており、有効にするときは ipv4;ipv6; を指定して使う。 今回は直接つながっているネットワークの中で IPv4 の情報を取り込んで BGP-4 で広告したいので ipv4; を設定している。

protocol direct {
    ipv4;
}

そして、今回の主題となる BGP-4 を設定しているのが以下の部分。 BGP が動作する上では必ず AS 番号を設定する必要があるため local as 65001; の部分で設定している。 AS 番号は世界的に一意な番号で、経路制御をしたい組織ごとに割り振りを受けて使うもの。 ただし、今回は動作確認なのでプライベート AS 番号のレンジ (64512 ~ 65534) から番号を使っている。 プライベート AS 番号は組織内で自由に割り振って使うことができる。 また、BGP のセッションを張る際の送信元 IP アドレスを source address 203.0.113.1; で設定している。 そして、対向の BGP ピアを neighbor 203.0.113.2 as 65002; で設定している。 このとき、対向の AS 番号が自身と同じ場合には、自動で iBGP として動作する。 ipv4import all; は BGP で得られるすべての経路を BIRD のルーティングテーブルに取り込むという意味になる。 同様に export all は、BIRD の知っているすべての経路を BGP で他のルータに広告するという意味になる。

protocol bgp {
    local as 65001;
    source address 203.0.113.1;
    neighbor 203.0.113.2 as 65002;
    ipv4 {
        import all;
        export all;
    };
}

設定が終わったら BIRD を実行する。 -d オプションをつけるとデバッグログを有効にした状態で、フォアグラウンドで動作する。 オプションをつけなければ端末を切り離してデーモンとして動作する。

$ sudo ip netns exec router1 bird -d \
    -c router1.conf \
    -s router1.ctl \
    -P router1.pid

router2 の BIRD を設定する

同様に router2 も設定ファイルを用意する。

$ 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 bgp {
    local as 65002;
    source address 203.0.113.2;
    neighbor 203.0.113.1 as 65001;
    ipv4 {
        import all;
        export all;
    };
}

protocol kernel {
    ipv4 {
        export filter {
            if proto = "direct1" then reject;
            accept;
        };
    };
}
EOF

もし経路交換の様子を観察したいときは router2 の BIRD を起動する前に tcpdump(1) を実行しておいたほうが良い。 BGP-4 は TCP の 179 番ポートで動作するため、次のようにする。

$ sudo ip netns exec router1 tcpdump -tnlvvv -i gw1-veth1 "tcp port 179"

準備ができたら、新しいターミナルで BIRD を実行する。

$ sudo ip netns exec router2 bird -d \
    -c router2.conf \
    -s router2.ctl \
    -P router2.pid

動作を確認する

両方のルータで BIRD を実行してしばらくすると経路交換が終わる。 試しに router1 の方で BIRD のルーティングテーブルを確認してみよう。

$ sudo birdc show route -s router1.ctl
BIRD 2.0.8 ready.
Table master4:
198.51.100.0/24      unicast [bgp1 08:55:16.462] * (100) [AS65002i]
    via 203.0.113.2 on gw1-veth1
192.0.2.0/24         unicast [direct1 08:55:08.802] * (240)
    dev gw1-veth0
203.0.113.0/24       unicast [direct1 08:55:08.802] * (240)
    dev gw1-veth1
                     unicast [bgp1 08:55:16.462] (100) [AS65002i]
    via 203.0.113.2 on gw1-veth1

198.51.100.0/24 の経路が追加されていることが確認できる。

また、同じ経路はカーネルのルーティングテーブルにも追加されている。

$ 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 の各プロトコルの稼働状況は次のとおり。

$ sudo birdc show protocols all -s router1.ctl
BIRD 2.0.8 ready.
Name       Proto      Table      State  Since         Info
device1    Device     ---        up     08:55:08.802  

direct1    Direct     ---        up     08:55:08.802  
  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

bgp1       BGP        ---        up     08:55:13.573  Established   
  BGP state:          Established
    Neighbor address: 203.0.113.2
    Neighbor AS:      65002
    Local AS:         65001
    Neighbor ID:      203.0.113.2
    Local capabilities
      Multiprotocol
        AF announced: ipv4
      Route refresh
      Graceful restart
      4-octet AS numbers
      Enhanced refresh
      Long-lived graceful restart
    Neighbor capabilities
      Multiprotocol
        AF announced: ipv4
      Route refresh
      Graceful restart
      4-octet AS numbers
      Enhanced refresh
      Long-lived graceful restart
    Session:          external AS4
    Source address:   203.0.113.1
    Hold timer:       173.117/240
    Keepalive timer:  14.466/80
  Channel ipv4
    State:          UP
    Table:          master4
    Preference:     100
    Input filter:   ACCEPT
    Output filter:  ACCEPT
    Routes:         2 imported, 2 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:              3          1          0        ---          2
      Export withdraws:            0        ---        ---        ---          0
    BGP Next hop:   203.0.113.1

kernel1    Kernel     master4    up     08:55:08.802  
  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

さて、経路交換の様子はどうなっただろうか。 tcpdump(1) の結果を確認してみよう。

$ sudo ip netns exec router1 tcpdump -tnlvvv -i gw1-veth1 "tcp port 179"

まず、BGP スピーカー同士で TCP のセッションを張る。 以下は、そのスリーウェイハンドシェイクの部分。

IP (tos 0xc0, ttl 1, id 27581, offset 0, flags [DF], proto TCP (6), length 60)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [S], cksum 0x7833 (incorrect -> 0x0fb2), seq 147705789, win 64240, options [mss 1460,sackOK,TS val 1023139905 ecr 0,nop,wscale 7], length 0
IP (tos 0xc0, ttl 255, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    203.0.113.2.179 > 203.0.113.1.52957: Flags [S.], cksum 0x7833 (incorrect -> 0xb7ef), seq 1708540707, ack 147705790, win 65160, options [mss 1460,sackOK,TS val 1883847382 ecr 1023139905,nop,wscale 7], length 0
IP (tos 0xc0, ttl 1, id 27582, offset 0, flags [DF], proto TCP (6), length 52)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [.], cksum 0x782b (incorrect -> 0xe34d), seq 1, ack 1, win 502, options [nop,nop,TS val 1023139906 ecr 1883847382], length 0

最初に Open メッセージがやり取りされる。 このメッセージで、自分の素性やどういった機能を有しているか示す。 BGP-4 は機能拡張がたくさんあるプロトコルなので、こういったやり取りが必要になる。 ここでやり取りした内容は、先ほど birdc show protocols all した内容の Local / Neighbor capabilities で確認できる。

IP (tos 0xc0, ttl 1, id 27583, offset 0, flags [DF], proto TCP (6), length 105)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [P.], cksum 0x7860 (incorrect -> 0x09ea), seq 1:54, ack 1, win 502, options [nop,nop,TS val 1023139906 ecr 1883847382], length 53: BGP
    Open Message (1), length: 53
      Version 4, my AS 65001, Holdtime 240s, ID 203.0.113.1
      Optional parameters, length: 24
        Option Capabilities Advertisement (2), length: 22
          Multiprotocol Extensions (1), length: 4
        AFI IPv4 (1), SAFI Unicast (1)
        0x0000:  0001 0001
          Route Refresh (2), length: 0
          Graceful Restart (64), length: 2
        Restart Flags: [none], Restart Time 120s
        0x0000:  0078
          32-Bit AS Number (65), length: 4
         4 Byte AS 65001
        0x0000:  0000 fde9
          Enhanced Route Refresh (70), length: 0
        no decoder for Capability 70
          Long-lived Graceful Restart (71), length: 0
IP (tos 0xc0, ttl 255, id 3014, offset 0, flags [DF], proto TCP (6), length 52)
    203.0.113.2.179 > 203.0.113.1.52957: Flags [.], cksum 0x782b (incorrect -> 0xe311), seq 1, ack 54, win 509, options [nop,nop,TS val 1883847382 ecr 1023139906], length 0
IP (tos 0xc0, ttl 1, id 3015, offset 0, flags [DF], proto TCP (6), length 105)
    203.0.113.2.179 > 203.0.113.1.52957: Flags [P.], cksum 0x7860 (incorrect -> 0x08ab), seq 1:54, ack 54, win 509, options [nop,nop,TS val 1883847383 ecr 1023139906], length 53: BGP
    Open Message (1), length: 53
      Version 4, my AS 65002, Holdtime 240s, ID 203.0.113.2
      Optional parameters, length: 24
        Option Capabilities Advertisement (2), length: 22
          Multiprotocol Extensions (1), length: 4
        AFI IPv4 (1), SAFI Unicast (1)
        0x0000:  0001 0001
          Route Refresh (2), length: 0
          Graceful Restart (64), length: 2
        Restart Flags: [none], Restart Time 120s
        0x0000:  0078
          32-Bit AS Number (65), length: 4
         4 Byte AS 65002
        0x0000:  0000 fdea
          Enhanced Route Refresh (70), length: 0
        no decoder for Capability 70
          Long-lived Graceful Restart (71), length: 0
IP (tos 0xc0, ttl 1, id 27584, offset 0, flags [DF], proto TCP (6), length 52)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [.], cksum 0x782b (incorrect -> 0xe2e1), seq 54, ack 54, win 502, options [nop,nop,TS val 1023139907 ecr 1883847383], length 0

TCP のセッションが切れたりしないように定期的に Keepalive メッセージを流す。

IP (tos 0xc0, ttl 1, id 27585, offset 0, flags [DF], proto TCP (6), length 71)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [P.], cksum 0x783e (incorrect -> 0xdeb3), seq 54:73, ack 54, win 502, options [nop,nop,TS val 1023139907 ecr 1883847383], length 19: BGP
    Keepalive Message (4), length: 19
IP (tos 0xc0, ttl 1, id 3016, offset 0, flags [DF], proto TCP (6), length 71)
    203.0.113.2.179 > 203.0.113.1.52957: Flags [P.], cksum 0x783e (incorrect -> 0xde98), seq 54:73, ack 73, win 509, options [nop,nop,TS val 1883847384 ecr 1023139907], length 19: BGP
    Keepalive Message (4), length: 19
IP (tos 0xc0, ttl 1, id 27586, offset 0, flags [DF], proto TCP (6), length 52)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [.], cksum 0x782b (incorrect -> 0xe291), seq 73, ack 73, win 502, options [nop,nop,TS val 1023139948 ecr 1883847384], length 0

そして、実際の経路交換が Update メッセージで行われる。

IP (tos 0xc0, ttl 1, id 27587, offset 0, flags [DF], proto TCP (6), length 103)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [P.], cksum 0x785e (incorrect -> 0x78a6), seq 73:124, ack 73, win 502, options [nop,nop,TS val 1023142918 ecr 1883847384], length 51: BGP
    Update Message (2), length: 51
      Origin (1), length: 1, Flags [T]: IGP
        0x0000:  00
      AS Path (2), length: 6, Flags [T]: 65001 
        0x0000:  0201 0000 fde9
      Next Hop (3), length: 4, Flags [T]: 203.0.113.1
        0x0000:  cb00 7101
      Updated routes:
        192.0.2.0/24
        203.0.113.0/24
IP (tos 0xc0, ttl 1, id 3017, offset 0, flags [DF], proto TCP (6), length 103)
    203.0.113.2.179 > 203.0.113.1.52957: Flags [P.], cksum 0x785e (incorrect -> 0x0376), seq 73:124, ack 124, win 509, options [nop,nop,TS val 1883850394 ecr 1023142918], length 51: BGP
    Update Message (2), length: 51
      Origin (1), length: 1, Flags [T]: IGP
        0x0000:  00
      AS Path (2), length: 6, Flags [T]: 65002 
        0x0000:  0201 0000 fdea
      Next Hop (3), length: 4, Flags [T]: 203.0.113.2
        0x0000:  cb00 7102
      Updated routes:
        198.51.100.0/24
        203.0.113.0/24
IP (tos 0xc0, ttl 1, id 27588, offset 0, flags [DF], proto TCP (6), length 75)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [P.], cksum 0x7842 (incorrect -> 0xc899), seq 124:147, ack 124, win 502, options [nop,nop,TS val 1023142918 ecr 1883850394], length 23: BGP
    Update Message (2), length: 23
      End-of-Rib Marker (empty NLRI)
IP (tos 0xc0, ttl 1, id 3018, offset 0, flags [DF], proto TCP (6), length 75)
    203.0.113.2.179 > 203.0.113.1.52957: Flags [P.], cksum 0x7842 (incorrect -> 0xc87b), seq 124:147, ack 147, win 509, options [nop,nop,TS val 1883850394 ecr 1023142918], length 23: BGP
    Update Message (2), length: 23
      End-of-Rib Marker (empty NLRI)
IP (tos 0xc0, ttl 1, id 27589, offset 0, flags [DF], proto TCP (6), length 52)
    203.0.113.1.52957 > 203.0.113.2.179: Flags [.], cksum 0x782b (incorrect -> 0xca6f), seq 147, ack 147, win 502, options [nop,nop,TS val 1023142968 ecr 1883850394], length 0

さて、それでは最初に動作しなかった ns1ns2 の間で 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.123 ms
64 bytes from 198.51.100.1: icmp_seq=2 ttl=62 time=0.096 ms
64 bytes from 198.51.100.1: icmp_seq=3 ttl=62 time=0.094 ms

--- 198.51.100.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2093ms
rtt min/avg/max/mdev = 0.094/0.104/0.123/0.013 ms

ちゃんと疎通が得られた。

まとめ

今回は Network Namespace を使って構築したネットワーク上で、BIRD の BGP-4 を動かしてダイナミックルーティングによる経路交換を試した。