CUBE SUGAR CONTAINER

技術系のこと書きます。

pwgen(1) でパスワードを生成する

コマンドラインで使えるパスワードジェネレータのひとつに pwgen(1) がある。 Unix 系の環境であれば、パッケージマネージャからインストールできることが多い。 今回は、そんな pwgen(1) の使い方について見ていく。

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

$ 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 aarch64
$ dpkg -l | grep pwgen
ii  pwgen                           2.08-2build1                            arm64        Automatic Password generation

もくじ

下準備

Ubuntu であれば APT を使ってインストールできる。

$ sudo apt-get -y install pwgen

使い方

ひとまず、オプションを何もつけずに pwgen(1) を実行してみよう。 すると、デフォルトでは大小の英数字で 8 文字のパスワードがずらっと出力される。

$ pwgen
ieGhach6 fieGie1g eiPhohd3 cie8ouPi Eeratah3 Gu1paet3 phoThuj0 EiXae7Vu
OoMeez6I que9Aebi oamaes5W aw4Eang7 Ab2or2oo ooj6ohGo Ja9sekaj Weey1iep
ad5Faib5 Iekoo3bo tiet9uK8 au4aeXei Aed3Fah1 eewei5Yi oV2iehai eipiew9V
letu0ooC ut5doh6H Angech3R aey4waiZ Cahs1ahn wii2Eibe eit8ieYe Biedoo4o
piebi9Zo eeNgei8f KeGh4ugh ko8eeK4O Beth8Eiy Gia4koNg phu1Tha1 eiSoo8fa
Niush4yo wohFo9wu Duo8AhN3 aisi7Foh Fo6gee9U vieG5tod eizoo4Ue akei5Aum
EeGach7m Eethei0e aiyai1Vo Eegh3kie eemeoJa7 zi3uiSha ieh0hoLo eeth3Ieb
ieL5ibie ooCh5xoo aiZ8Soth aemied2G Ahjie0ph aiNgaa6R OeSh2Goo ohPh5iph
kak8ohCh ieThi8oo ohs7Eixi too0aoTh aid2XieM eeF8ugah Kayah1ae ooGi4ihe
Gi1Kaek3 ooWohd3c ti5faiNg Eedooph2 thoo4Ue6 Aiqui6be Ookeuth6 choo9Phe
aizie0Oo aX3dei2d Iejoim6o Ra4yooBu ohWeey3x eith1aiT Eixi8Ahk OfiVah4O
ko6Neith thaeF9ak aephoo6E Faew7Jee ue2EceTu ahr0eSie phei5Si1 UJues7ah
shaiT4Ca imu7AGh9 gie2iLee xael2Oom xeg5Ieh6 Fiwoo3ou Aiph8jie yoo6eW0h
AeJah5in Um5LoWae Cahfie9R sia9Ohph aiD6zaiX veiW5ya6 ug6quuuY eequeW4j
pei8Hae5 ub5Nokeo Kei8iePh IeFai3ai oPhis0ei Lieg8tai Ohleed5I IeD0EiWu
popaiC6v xoov3aeK Iifea7so Ishoo1je cho5oaPa ei3eth0A Ein1quoo veeXoh7t
ogh0eeTi choh1Aez sie8Wae1 uanaim0I Huja7Ahb ia0sa3Ph ophiuCh4 Aimu1she
Aim9Oov5 ju9Xohle MohM8Ay0 uuS6Negh Kie4Kaif gahgh4Oo si6oraeP tool3Ool
uvaDiev9 tho0Tui5 HaiNah9w enos1oF1 eiqu2aeW Ohreu0Ai eih6aeX6 Aew2ifud
ZeiL5Qua ya1ouBei kieTh1ch Hoo1thee Cu1Eesh5 ahgh3Ait suquooK1 aph8Keax

デフォルトで英字は "pronounceable" (発音しやすさ) を考慮した出力がなされている点に注意する必要がある。 つまり、デフォルトの出力は覚えやすい代わりに辞書攻撃に対してセキュリティ強度が低下するというデメリットがある。

もし、完全にランダムな出力を得たい場合には -s オプションをつける必要がある。

$ pwgen -s
PfxhDbL8 D60fyRe3 2PALPTTE Dxt68AQm VLKMcWF2 J9ymuqCz SCdfQ4pL ZVXKuMY1
J6uvAFjv m9T9ydll IOEkWpO5 KjyHu11o xrdZRyp6 YGJNM268 lZ35WgKr vP4SMoTa
Ysjauhb7 vaIL5njg jmzVi37R B0U4R9fw cx6yRFzC Mhg8AeRP 2RRGccgD Qcdd8LmH
T0OQO26J U664NXae e4niOosB xZjmcby9 Gsg6NYwV MdD6K18f 6hihKk78 eH2gn5SR
WWv5rCCO 0XiaTElf R6xy7KyO FiibEa8F xVeR2BvJ 6YX5nvrN KixtlDr3 fl5c2yIu
iZExiwN1 yDsA7sCT eWaR77oU 99lvthJb TJ1vpNR3 lZR0EoE5 f7BQKtU8 vZRXq758
uZxad47N S40oIx3O LwFppL7X WFxK8Fe9 8I4wLH8P d4cKsWos Zrh7Rzfs krB4YJCw
B59MiZHE K9XGjcA6 6tKAWCNm sdXn2RHR 9RmsK2wE jS6dalEA RH1vCYoW pwuqf8Zx
7DjNUfxH oStVpMA8 A21AJIbM 5R4rl33M Y8abPkY0 4PtCMWVo Pwot8b8a LT5KIXZc
j2GnMRBA HQ9sWA3m NI8iUq5U 9HxvYNqZ 5hHGcox7 NhdI4yLh m6U6Qg6G RWM6AL83
kz9J0DHP s4yOTMEk pD3uIcxR uYtsd6NK jdfcVAo1 18Nhr7t4 VGYCt2YM 5pKNubLj
TSDEjzK5 THcd2szg 3Y1KpYoj dp3u4BF6 Et5XYJCP 9gwsQUai aY6InIC7 XxiTtBA5
iITc9kJ9 qiA4goIx iwhaeg9J 4v3MOKSJ I4Jco3QJ 5UQuOhJe GXMBaL8n Zt71eEcd
uaicDa2l XD6CM7Am FFi1NUZM HPhMVtN5 gh4xIpTW yy5o0VQf oQ2r3Zyi E0nrsgBy
1R5MpIbV 7xj0JTqn nBl9nT0K VCLfvLV3 S4E7aOfq nPeFCti9 mygjUI5t gHb8Cc9I
xL47NyId l9RaLqp3 3mBIausn TZ3RbWWX ghWW1Rao w9MQl45W Bt98S11E gD3AODFn
tAoXLba3 jj66yVMv iZehQc3K qiMIJO6v E44Q0UAQ 3eMLZeep Uq9FJLPX Lfx5cEwi
x5Pcr7mN rSHY25Ht hbizDB2o MimAeV8g ZOYj5Ac9 05x8naXm PPu9IcKA tvOj5FPR
EH7SOpKo uemJope4 5QNJ89SH yPHiFVw4 3LlX9e3x 94YDtZgr vmsZYSk7 T2pEX4lK
dGoH6BFJ qkHtx50l EDD9H1qI Wpbv7xgE VrBDRtB2 rM7fiBAC Hn3VIgW9 fqmZPG8t

また、デフォルトで出力される 8 文字というパスワード長は、現在の計算機環境を考えると短い。 そのため、もっと長いパスワードが得たい場合には引数として数字を指定する。 さらに、その後続にも数字を指定すると出力する候補の数を指定できる。 たとえば 20 文字のパスワードが 6 つほしい場合は次のとおり。

$ pwgen -s 20 6
nAS10xMlrX0F3uja7g6O IbYojr0FPfKIZS4sCHqp Uh1YCdxaqSuOZ9d3fo5y
fWh7skWJBuLN9mxqZ2Mp 37gpHst5V3URI36N0w9E 9KNA9T3eNKwsy1RfAm6W

また、デフォルトではパスワードに記号が含まれない。 含まれるようにしたい場合は -y オプションをつける。

$ pwgen 20 6 -s -y
(6!84GBprv=[}E|(h{;k k6xoEpY#o|jw*5>BN{/: }7l~Vd"C&*r.DPs^r|DE
H&QYCgW\/PrQsT~H2db! 6GoDIc7k>+e~V@kD,_C4 Rlrc{m0.RG+Bqj44wH&y

反対に、使われる文字種で大文字を含めたくない (小文字だけにしたい) 場合は -A オプションをつける。

$ pwgen 20 6 -s -A
n1o8hc5cmstr1jw4097d 7h1i89dp524govqdl3yr 0euk0nes4c2kuro1iz6w
1r52jrupu7a1dwvgjmph 79f3z9x774aat8w69hlh djc2zqi34ng5xkof056l

同様に、使われる文字種で数字を含めたくない場合は -0 オプションをつける。

$ pwgen 20 6 -s -0
pBtuZkTXfpjzvOKKDloE cetdXAYfJzMZPClOeBMI xHoLNcKPTZAFWYrvkFsQ
NZBcNrFdAbkOxOATEsob UwmTvyUoiyRUcabLMQEA dqpdUNdJbxJLttuolZtP

1l など、まぎらわしい文字を使わないようにするには -B オプションが利用できる。 ただし、文字種が減ることで視認性が上がる代わりにセキュリティ強度が低下するため、このオプションは基本的には推奨されていないようだ。

$ pwgen 20 6 -s -B
qgKkCnAPX3nVFEYFWkJ7 ikh9pnkvtWgxNb3TLzm7 sUHK4kyoLChj4VYJpNdf
EgTAfRNegJKU4Cmvbxe4 gyjsRwczWyWLdn3Xj4sq CtPsiHfV7m9qCweiXyiL

いじょう。

Lenovo ThinkCentre M75q Tiny Gen2 を買ってメモリとストレージを交換してみた

とある事情から Windows 11 の動作検証をするためのマシンが欲しくなった。 また、検証したい機能は仮想化に関係するので物理的なマシンがあった方が便利そう。 ついでに、我が家には 10 年以上前に購入したミニタワーのデスクトップマシンが残っていたのでリプレースすることにした。

マシンをリプレースする上での要件は次のように定めた。

  • OS として Windows 11 Pro が利用できる
  • 筐体のフットプリントがなるべく小さい
  • なるべくコストパフォーマンスに優れる
  • ゲームや GPGPU での利用は想定しない (NVIDIA GPU は必要ない)

当初は上記に合致するマシンとして、中国の新興メーカー (Minisforum など) が出しているミニ PC を検討していた。 最近は手のひらサイズでスペックの高いマシンが、ちょっと信じられないような価格で購入できるようだ。

ただ、スペック以外の面を考えると Lenovo の ThinkCentre シリーズの方が自分の好みに合致していると思うに至った。 スペック以外というのは、たとえばメーカーのサポートや信頼性などが挙げられる。

今回は、購入と作業に関する諸々の記録を残しておく。

Lenovo ThinkCentre M75q Tiny Gen2 について

購入した Lenovo ThinkCentre M75q Tiny Gen2 について紹介しておく。

まず、Lenovo ThinkCentre シリーズは筐体のサイズごとにいくつかのラインナップに分かれている。 その中でも、最も小さなものが今回購入したモデルの名前にも含まれる "Tiny" になる。 そして M75q Tiny Gen2 は、AMD Ryzen の CPU を搭載した、現時点 (2023-07) において最も安価な "Tiny" になる。 OS にこだわらず、最小構成で良ければ 5 万円台から手に入る。 さらに、一部の構成には標準で 3 年間のメーカー保証がついている。

www.lenovo.com

ただしデメリットもあって、搭載している CPU のアーキテクチャが Zen 3 なので、最新の Zen 4 に比べると一世代古い。 メーカーの Web サイト 1 を確認すると、発売日が 2021-06-01 なので 2 年前の CPU ということになる。 また、サポートしている PCIe のバージョンについても Gen3 なので、現在の主流である Gen4 に比べると遅くなる。 CPU の世代が新しい別のモデルは、もう少し価格が上がるので、ここは値段とのトレードオフになりそう。 とはいえ今回は、ベンチマークの結果なども考慮しつつ、用途に対して実用に耐えうると判断して購入するに至った。

なお、メモリやストレージについては、公式のカスタマイズを利用すると高くつく。 また、安価な即納モデルなどはそもそもカスタマイズできないものもあり、今回購入したのもそれに該当する。 とはいえ、Lenovo の ThinkCentre シリーズは基本的にユーザ自身でパーツを交換しやすい設計になっている場合が多い。 そして M75q Tiny Gen 2 もそうなので、メモリやストレージについては自分でカスタマイズするのが望ましい。

適合するメモリとストレージを選ぶ

本体の目星がついたら、自分でカスタマイズするために適合するメモリとストレージを選ぶ。

まず、メモリの規格は DDR4-3200 の SO-DIMM になる。 メモリスロットは 2 つあってデュアルチャンネルなので、今回は 16GB のモジュールを 2 枚で 32GB にした。 Crucial のものを選んで、今だと 1 万円しないくらい。 なお、仕様上は最大で 64GB (32GBx2) のメモリまで対応している。

続いて、ストレージとしては PCIe Gen3 以上に対応している、フォームファクタが M.2 Type 2280 の NVMe SSD を選ぶ。 PCIe のバージョンに関しては、下位互換性があるので PCIe Gen4 のモデルを購入しても問題ない。 今回は WD_Black SN770 の 1TB モデルを選んで、こちらもやはり 1 万円しないくらい。 後述の理由から、できれば片面実装の製品が望ましい。

下準備について

今回のようにストレージを交換すると、当然ながら OS が空っぽになるのでリカバリの作業が必要になる。 Lenovo は Lenovo USB Recovery Creator というリカバリ用の USB メモリを作るための Windows 向けツールを提供している。 そのため、あらかじめ容量が 32GB 以上の USB メモリを購入しておこう。

リカバリ用の USB メモリ (USB リカバリードライブ) を作成する手順は以下のページに記載されている。

pcsupport.lenovo.com

必要な手順は次のとおり。

  1. Lenovo サポートサイト 2 にアクセスしてアカウント (Lenovo ID) を作成する
  2. Lenovo リカバリーページ 3 にアクセスする
  3. 本体に記載されているシリアル番号を入力してリカバリーメディアを注文する
  4. Windows のパソコンで Lenovo USB Recovery Creator ツールをダウンロードする
  5. ダウンロードした Lenovo USB Recovery Creator ツールを実行する
  6. 手順 (1) で作成したアカウントをツール上で入力してサインインする
  7. 手順 (3) で注文したリカバリーメディアを選択して USB リカバリードライブを作成する

上記を実施して、あらかじめリカバリ用の USB メモリを作っておく。 Web には Windows のリカバリメディアを使う方法を試している人もいるけど、こちらの方が完全に製品出荷時の状態にできる。

購入したメモリとストレージに交換する

ここでは、簡単にパーツの交換方法について説明する。 なお、YouTube などを検索すれば同じ内容を動画で解説している人がいるので、そちらの方が分かりやすいかも。

まずは、先端がプラスまたはマイナスのドライバーを用意して、背面のネジを抜き取る。 抜き取ったら AMD のステッカーが貼ってある方のパネルをスライドさせて取り外す。

背面のプラスネジを抜き取ってパネルをスライドさせる

パネルを取り外すと、次のようにファンと 2.5 インチベイが露出する。

パネルを取り外すとファンと 2.5 インチベイが確認できる

筐体を裏返して、反対側のパネルもスライドさせて取り外す。

反対側のパネルもスライドさせて取り外す

すると、メモリと M.2 SSD のスロットにアクセスできる。

出荷時にインストールされているメモリと M.2 SSD

メモリと M.2 SSD を交換する。 まず、メモリはモジュールを固定している両側のラッチを広げて、上に持ち上げて斜めに引き抜く。 取り付けは、斜めに差し込んで上から力をかけるだけ。 そして M.2 SSD は、固定している青色のプラスチックを指などでつまんで引き抜いてから、上に持ち上げて斜めに引き抜く。 取り付けは、その逆をするだけ。

購入したメモリと M.2 SSD に交換したところ

ちなみに M.2 SSD は、取り外すとスペーサーを兼ねたサーマルパッドが確認できる。 両面実装の SSD を使うときは、このサーマルパッドが部品と干渉するため剥がす必要があるらしい。 購入した WD_Black SN770 は、少なくとも今のモデルは片面実装なので何もせずに設置できる。

パーツの交換が終わったら、最初にやった作業と逆の工程を実施して筐体のパネルを元の状態に戻す。

メモリの初期不良をチェックする

ここからはパーツを交換した後の作業になる。

これは念のために実施するオプション的な作業だけど、メモリに初期不良がないかチェックしておく。 ツールとして Memtest86+ などを使うと良い。

www.memtest.org

もし、この工程でエラーが見つかれば返品・交換の作業が必要になる。

ストレージをリカバリする

続いてストレージをリカバリする。

あらかじめ作っておいたリカバリ用の USB メモリを筐体のポートに差し込んで電源を入れる。 リカバリを実行する前に、ストレージのデータが消去される旨の警告が表示されるので了承する。 その後は自動で処理が進むので、完了するのをひたすら待つだけ。

なお、筐体には USB 2.0 のポートもあるので、誤ってそこに USB メモリを差さないように注意しよう。 具体的には、イーサネットポート (RJ-45) に近い 2 つの USB ポートが USB 2.0 なので、別のポートを使う。 差しても動作に支障はないけど USB 3.0 のポートを使う場合と比べてリカバリにかかる時間が伸びてしまう。

利用する

リカバリが完了したら、あとは Windows のセットアップを済ませて使うだけ。

交換した SSD のスループットを CrystalDiskMark8 で確認した結果は次のとおり。

CrystalDiskMark8を使ったベンチマーク

温度に関しては、アイドル時が大体 30 ~ 35 度くらい。 ベンチマークのソフトウェアを実行したときのピーク温度で 50 度いかないくらいだった。 リカバリ直後の状態で、特に余計なソフトウェアが入ることもなく快適に使える。

ネットワークスペシャリスト試験に合格した

今回 2023 年春期のネットワークスペシャリスト試験を受験して合格できた。 後から振り返ることができるように、受験に関する諸々について書き留めておく。

受験のモチベーションとしては、ネットワークについて広く浅く復習しておきたい気持ちになったため。 ネットワークスペシャリスト試験を受けるのは、これが初めての経験だった。 IPA1 の試験を受けるのは 2017 年秋期に情報処理安全確保支援士試験を受けて以来なので約 6 年ぶりになる。

blog.amedama.jp

試験内容について

ネットワークスペシャリスト試験の内容は午前 I, II と午後 I, II の 4 つに分かれている。 すべての内容で基準点 (60 / 100 点) を満たせば合格になる。

www.ipa.go.jp

今回の受験におけるスコアは以下のとおり。

ネットワークスペシャリスト試験のスコア

午前 I と II については四択の選択問題で、午後 I と II は記述問題になっている。 午前 I は他のスキルレベル 4 の試験と共通のため、午前 II 以降が試験で独自の問題になる。

午前 I については、別のスキルレベル 4 の試験で基準点以上を 2 年以内に取っている場合、申請すれば受験が免除される。 ただし、今回のケースでは、前述したとおり IPA の試験自体が 6 年ぶりの受験だったので関係がなかった。

勉強方法について

まず午前問題は、基本的に過去に出題された問題がほとんどそのまま出題されるため、ひたすら過去問を空き時間に解いた。 ただし、基準点を大幅に超えた点数を取っても合否には影響しないため、過去問で 80% くらい解けるようになった時点で勉強は打ち切っている。

午前問題の中でも午前 I は出題範囲が広いことから、午前対策のほとんどの時間をこちらに使った。 一方で午前 II については、特に何もしなくても最初から 80% 前後は取れることが分かった。

午後問題についても、基本的には過去問を解いて対策した。 これは問題文に慣れるのと、どういった回答が正答として扱われるのかを把握するため。

午後問題に関しては、出題される内容がそれなりに詳細なので、教科書などを眺めるだけで対策するのはなかなか難しい印象を受けた。 出題される内容について、自分が過去に業務で扱ったことがあるか、あるいは個人的に検証したことがあるかの方が重要に感じる。 もちろん、単なる知識を問う問題もそれなりに出るので、そこでの失点を防ぐためには教科書的な知識量も重要ではある。

出題範囲や、自分の中で理解が足りていない部分を確認するためには、以下の書籍を購入して読んだ。

所感について

学生時代に、夏休みを丸ごと勉強に費やした上で、なんとか基本情報技術者試験に合格できたことが今でも印象に残っている。 その頃は、IPA の高度区分に合格することは夢のまた夢のように感じていた。 それが年月を経て現実的に合格できるようになることを考えると、同じ分野を継続して学習することで得られる力を実感している。


  1. 独立行政法人 情報処理推進機構

Linux の Network Namespace と dhcpcd / dnsmasq で DHCPv4 を試す

ISC DHCP 1 のメンテナンスが 2022 年末で終了してしまった。 ISC によると DHCP サーバの実装については Kea DHCP 2 への移行が推奨されている。 ただ、ISC DHCP には DHCP クライアントの実装も含まれていた。 ところが Kea DHCP には DHCP クライアントが含まれていないようだ。 そこで、代替になる DHCP クライアントはないものかと探したところ、どうやら dhcpcd 3 が良さそうなことが分かった。 今回は dnsmasq(8) を DHCP サーバに使って、DHCP クライアントに dhcpcd(8) を使う構成を試してみる。

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

$ 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 aarch64
$ dhcpcd --version
dhcpcd 7.1.0
Copyright (c) 2006-2019 Roy Marples
Compiled in features: INET ARP ARPing IPv4LL INET6 DHCPv6 AUTH
$ dnsmasq --version
Dnsmasq version 2.86  Copyright (c) 2000-2021 Simon Kelley
Compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset auth cryptohash DNSSEC loop-detect inotify dumpfile

This software comes with ABSOLUTELY NO WARRANTY.
Dnsmasq is free software, and you are welcome to redistribute it
under the terms of the GNU General Public License, version 2 or 3.

もくじ

下準備

まずは dnsmasq と dhcpcd をインストールする。

$ sudo apt-get install dnsmasq dhcpcd5

インストールすると systemd のサービスが稼働する。 今回は systemd 経由では使わないため止める。

$ sudo systemctl stop dhcpcd
$ sudo systemctl disable dhcpcd
$ sudo systemctl stop dnsmasq
$ sudo systemctl disable dnsmasq

ネットワークを作る

続いて Network Namespace を使ってネットワークを用意する。 作るネットワークはセグメントが 1 つしかないシンプルなもの。

まずは Network Namespace を 2 つ作る。 それぞれ DHCP クライアントとサーバに対応する。

$ sudo ip netns add server
$ sudo ip netns add client

間をつなぐ Virtual Ethernet Device のインターフェイスを用意する。

$ sudo ip link add s-veth0 type veth peer name c-veth0

インターフェイスを、それぞれの Network Namespace に所属させる。

$ sudo ip link set s-veth0 netns server
$ sudo ip link set c-veth0 netns client

インターフェイスの MAC アドレスをドキュメンテーションアドレスに変更しておく。

$ sudo ip netns exec server ip link set dev s-veth0 address 00:00:5E:00:53:01
$ sudo ip netns exec client ip link set dev c-veth0 address 00:00:5E:00:53:02

インターフェイスをリンクアップさせる。

$ sudo ip netns exec server ip link set s-veth0 up
$ sudo ip netns exec client ip link set c-veth0 up

DHCP サーバのインターフェイスには IPv4 アドレスを付与しておく。

$ sudo ip netns exec server ip address add 192.0.2.254/24 dev s-veth0

DHCP サーバを起動する

ネットワークが完成したので DHCP サーバとして dnsmasq(8) を起動する。

$ sudo ip netns exec server dnsmasq \
  --dhcp-range=192.0.2.100,192.0.2.200,255.255.255.0 \
  --interface=s-veth0 \
  --port 0 \
  --no-resolv \
  --no-daemon

この段階では、当然ながらまだ DHCP クライアント側のインターフェイスには IPv4 アドレスは付与されていない。

$ sudo ip netns exec client ip address show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: c-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:5e:00:53:02 brd ff:ff:ff:ff:ff:ff link-netns server
    inet6 fe80::200:5eff:fe00:5302/64 scope link 
       valid_lft forever preferred_lft forever

DHCP クライアントを起動する

DHCP クライアント側の Netowork Namespace で dhcpcd(8) を起動する。 このとき --nobackground オプションを指定するとフォアグラウンドでプログラムが実行される。 また --ipv4only オプションを指定すると DHCPv4 だけが処理される。 これは dhcpcd(8) が DHCPv6 にも対応しているため。

$ sudo ip netns exec client dhcpcd c-veth0 --ipv4only --nobackground

少し待つと、次のように DHCP がやり取りされてアドレスやデフォルトルートが設定された旨がログに出てくる。

$ sudo ip netns exec client dhcpcd c-veth0 --ipv4only --nobackground
DUID 00:01:00:01:2c:2c:1b:c2:00:00:5e:00:53:02
c-veth0: IAID 5e:00:53:02
c-veth0: soliciting a DHCP lease
c-veth0: offered 192.0.2.177 from 192.0.2.254
c-veth0: ignoring offer of 192.0.2.177 from 192.0.2.254
c-veth0: probing address 192.0.2.177/24
c-veth0: leased 192.0.2.177 for 3600 seconds
c-veth0: adding route to 192.0.2.0/24
c-veth0: adding default route via 192.0.2.254

その後 IPv4 アドレスを確認すると、ちゃんとインターフェイスに付与されている。

$ sudo ip netns exec client ip -4 address show
3: c-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link-netns server
    inet 192.0.2.177/24 brd 192.0.2.255 scope global noprefixroute c-veth0
       valid_lft forever preferred_lft forever

ルーティングテーブルを確認するとデフォルトルートも設定されている。

$ sudo ip netns exec client ip -4 route show
default via 192.0.2.254 dev c-veth0 proto dhcp src 192.0.2.177 metric 203 
192.0.2.0/24 dev c-veth0 proto dhcp scope link src 192.0.2.177 metric 203 

めでたしめでたし。

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 を動かしてダイナミックルーティングによる経路交換を試した。

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

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

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

blog.amedama.jp

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

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

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

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

まずは 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 ospf v2 {
    ipv4 {
        import all;
        export all;
    };
    area 0 {
        interface "gw1-veth1" {
        };
    };
}

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

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

protocol direct {
    ipv4;
}

そして、今回の主題となる OSPF を設定しているのが以下の部分。 ipv4import all は OSPF で得られるすべての経路を BIRD のルーティングテーブルに取り込むという意味になる。 同様に export all は、BIRD の知っているすべての経路を OSPF で他のルータに広告するという意味になる。 area 0 は、定義しているのがいわゆるバックボーンエリアであることを示している。 その中で指定している interface "gw1-veth1" は、経路を交換するインターフェイスを指定している。

protocol ospf v2 {
    ipv4 {
        import all;
        export all;
    };
    area 0 {
        interface "gw1-veth1" {
        };
    };
}

設定ファイルができたら 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 ospf v2 {
    ipv4 {
        import all;
        export all;
    };
    area 0 {
        interface "gw2-veth0" {
        };
    };
}

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

BIRD を動かし始める前に、もしルータ同士のやり取りを最初から観察したいのであれば、あらかじめ tcpdump(1) を起動しておくと良い。 OSPFv2 はトランスポートのプロトコルを挟まずに、直接 IP のプロトコル番号 89 として動作する。 そのため、次のようにして通信を観察できる。

$ sudo ip netns exec router1 tcpdump -tnlvvv -i gw1-veth1 "ip proto 89"

そして router2 の BIRD を実行する。

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

動作を確認する

OSPFv2 では、次のようなメッセージを使って経路の情報を交換する。

  • Hello
  • Database Description (DBD)
  • Link State Request (LSR)
  • Link State Update (LSU)
  • Link State Acknowledgement (LSAck)

ここからは tcpdump でキャプチャしたパケットを時系列で見ていく。

まず、OSPF のルータは近隣のルータを検出するために Hello パケットをマルチキャスト宛に送る。 以下のパケットをキャプチャしたタイミングは router1 が先に起動していて、router2 が起動した直後の状態。 まだ router1router2 はお互いを Neighbor として認識しておらず、このパケットを契機に処理が進む。 先に起動していた router1 は、Hello パケットで自身を Designated Router として広告している。

IP (tos 0xc0, ttl 1, id 12536, offset 0, flags [none], proto OSPF (89), length 64)
    203.0.113.2 > 224.0.0.5: OSPFv2, Hello, length 44
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0)
    Options [External]
      Hello Timer 10s, Dead Timer 40s, Mask 255.255.255.0, Priority 1
IP (tos 0xc0, ttl 1, id 60951, offset 0, flags [none], proto OSPF (89), length 68)
    203.0.113.1 > 224.0.0.5: OSPFv2, Hello, length 48
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0)
    Options [External]
      Hello Timer 10s, Dead Timer 40s, Mask 255.255.255.0, Priority 1
      Designated Router 203.0.113.1
      Neighbor List:
        203.0.113.2

上記の Hello パケットで、それぞれ Neighbor として認識したため DBD を送り合っている。

IP (tos 0xc0, ttl 1, id 20778, offset 0, flags [none], proto OSPF (89), length 52)
    203.0.113.2 > 203.0.113.1: OSPFv2, Database Description, length 32
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0)
    Options [External, Opaque], DD Flags [Init, More, Master], MTU: 1500, Sequence: 0x645fe66f
IP (tos 0xc0, ttl 1, id 40524, offset 0, flags [none], proto OSPF (89), length 112)
    203.0.113.1 > 203.0.113.2: OSPFv2, Database Description, length 92
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0)
    Options [External, Opaque], DD Flags [none], MTU: 1500, Sequence: 0x645fe66f
      Advertising Router 203.0.113.1, seq 0x80000001, age 849s, length 16
        External LSA (5), LSA-ID: 192.0.2.255
        Options: [External]
      Advertising Router 203.0.113.1, seq 0x80000001, age 849s, length 16
        External LSA (5), LSA-ID: 203.0.113.0
        Options: [External]
      Advertising Router 203.0.113.1, seq 0x80000001, age 849s, length 16
        Router LSA (1), LSA-ID: 203.0.113.1
        Options: [External, Opaque]
IP (tos 0xc0, ttl 1, id 20779, offset 0, flags [none], proto OSPF (89), length 112)
    203.0.113.2 > 203.0.113.1: OSPFv2, Database Description, length 92
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0)
    Options [External, Opaque], DD Flags [Master], MTU: 1500, Sequence: 0x645fe670
      Advertising Router 203.0.113.2, seq 0x80000001, age 4s, length 16
        External LSA (5), LSA-ID: 203.0.113.0
        Options: [External]
      Advertising Router 203.0.113.2, seq 0x80000001, age 4s, length 16
        External LSA (5), LSA-ID: 198.51.100.255
        Options: [External]
      Advertising Router 203.0.113.2, seq 0x80000001, age 4s, length 16
        Router LSA (1), LSA-ID: 203.0.113.2
        Options: [External, Opaque]
IP (tos 0xc0, ttl 1, id 40525, offset 0, flags [none], proto OSPF (89), length 52)
    203.0.113.1 > 203.0.113.2: OSPFv2, Database Description, length 32
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0)
    Options [External, Opaque], DD Flags [none], MTU: 1500, Sequence: 0x645fe670

DBD を見て、自身が持っていない LSA (Link State Advertisement) について LSR を送っている。

IP (tos 0xc0, ttl 1, id 40526, offset 0, flags [none], proto OSPF (89), length 80)
    203.0.113.1 > 203.0.113.2: OSPFv2, LS-Request, length 60
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0)
      Advertising Router: 203.0.113.2, External LSA (5), LSA-ID: 203.0.113.0
      Advertising Router: 203.0.113.2, External LSA (5), LSA-ID: 198.51.100.255
      Advertising Router: 203.0.113.2, Router LSA (1), LSA-ID: 203.0.113.2
IP (tos 0xc0, ttl 1, id 20780, offset 0, flags [none], proto OSPF (89), length 80)
    203.0.113.2 > 203.0.113.1: OSPFv2, LS-Request, length 60
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0)
      Advertising Router: 203.0.113.1, External LSA (5), LSA-ID: 192.0.2.255
      Advertising Router: 203.0.113.1, External LSA (5), LSA-ID: 203.0.113.0
      Advertising Router: 203.0.113.1, Router LSA (1), LSA-ID: 203.0.113.1

そして LSR で要求された LSA を含んだ LSU を返している。

IP (tos 0xc0, ttl 1, id 40527, offset 0, flags [none], proto OSPF (89), length 156)
    203.0.113.1 > 203.0.113.2: OSPFv2, LS-Update, length 136
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0), 3 LSAs
      LSA #1
      Advertising Router 203.0.113.1, seq 0x80000001, age 850s, length 16
        External LSA (5), LSA-ID: 192.0.2.255
        Options: [External]
        Mask 255.255.255.0
        topology default (0), type 2, metric 10000
        0x0000:  ffff ff00 8000 2710 0000 0000 0000 0000
      LSA #2
      Advertising Router 203.0.113.1, seq 0x80000001, age 850s, length 16
        External LSA (5), LSA-ID: 203.0.113.0
        Options: [External]
        Mask 255.255.255.0
        topology default (0), type 2, metric 10000
        0x0000:  ffff ff00 8000 2710 0000 0000 0000 0000
      LSA #3
      Advertising Router 203.0.113.1, seq 0x80000001, age 850s, length 16
        Router LSA (1), LSA-ID: 203.0.113.1
        Options: [External, Opaque]
        Router LSA Options: [ASBR]
          Stub Network: 203.0.113.0, Mask: 255.255.255.0
        topology default (0), metric 10
        0x0000:  0200 0001 cb00 7100 ffff ff00 0300 000a
IP (tos 0xc0, ttl 1, id 20781, offset 0, flags [none], proto OSPF (89), length 156)
    203.0.113.2 > 203.0.113.1: OSPFv2, LS-Update, length 136
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0), 3 LSAs
      LSA #1
      Advertising Router 203.0.113.2, seq 0x80000001, age 5s, length 16
        External LSA (5), LSA-ID: 203.0.113.0
        Options: [External]
        Mask 255.255.255.0
        topology default (0), type 2, metric 10000
        0x0000:  ffff ff00 8000 2710 0000 0000 0000 0000
      LSA #2
      Advertising Router 203.0.113.2, seq 0x80000001, age 5s, length 16
        External LSA (5), LSA-ID: 198.51.100.255
        Options: [External]
        Mask 255.255.255.0
        topology default (0), type 2, metric 10000
        0x0000:  ffff ff00 8000 2710 0000 0000 0000 0000
      LSA #3
      Advertising Router 203.0.113.2, seq 0x80000001, age 5s, length 16
        Router LSA (1), LSA-ID: 203.0.113.2
        Options: [External, Opaque]
        Router LSA Options: [ASBR]
          Stub Network: 203.0.113.0, Mask: 255.255.255.0
        topology default (0), metric 10
        0x0000:  0200 0001 cb00 7100 ffff ff00 0300 000a
IP (tos 0xc0, ttl 1, id 61162, offset 0, flags [none], proto OSPF (89), length 116)
    203.0.113.1 > 224.0.0.5: OSPFv2, LS-Update, length 96
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0), 2 LSAs
      LSA #1
      Advertising Router 203.0.113.1, seq 0x80000002, age 1s, length 16
        Router LSA (1), LSA-ID: 203.0.113.1
        Options: [External, Opaque]
        Router LSA Options: [ASBR]
          Neighbor Network-ID: 203.0.113.1, Interface Address: 203.0.113.1
        topology default (0), metric 10
        0x0000:  0200 0001 cb00 7101 cb00 7101 0200 000a
      LSA #2
      Advertising Router 203.0.113.1, seq 0x80000001, age 1s, length 12
        Network LSA (2), LSA-ID: 203.0.113.1
        Options: [External, Opaque]
        Mask 255.255.255.0
        Connected Routers:
          203.0.113.1
          203.0.113.2
        0x0000:  ffff ff00 cb00 7101 cb00 7102
IP (tos 0xc0, ttl 1, id 12621, offset 0, flags [none], proto OSPF (89), length 84)
    203.0.113.2 > 224.0.0.5: OSPFv2, LS-Update, length 64
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0), 1 LSA
      LSA #1
      Advertising Router 203.0.113.2, seq 0x80000002, age 1s, length 16
        Router LSA (1), LSA-ID: 203.0.113.2
        Options: [External, Opaque]
        Router LSA Options: [ASBR]
              Neighbor Network-ID: 203.0.113.1, Interface Address: 203.0.113.2
        topology default (0), metric 10
        0x0000:  0200 0001 cb00 7101 cb00 7102 0200 000a

最後に、LSU の受信確認として LSAck を返している。

IP (tos 0xc0, ttl 1, id 12883, offset 0, flags [none], proto OSPF (89), length 144)
    203.0.113.2 > 224.0.0.5: OSPFv2, LS-Ack, length 124
    Router-ID 203.0.113.2, Backbone Area, Authentication Type: none (0)
      Advertising Router 203.0.113.1, seq 0x80000001, age 850s, length 16
        External LSA (5), LSA-ID: 192.0.2.255
        Options: [External]
      Advertising Router 203.0.113.1, seq 0x80000001, age 850s, length 16
        External LSA (5), LSA-ID: 203.0.113.0
        Options: [External]
      Advertising Router 203.0.113.1, seq 0x80000001, age 850s, length 16
        Router LSA (1), LSA-ID: 203.0.113.1
        Options: [External, Opaque]
      Advertising Router 203.0.113.1, seq 0x80000002, age 1s, length 16
        Router LSA (1), LSA-ID: 203.0.113.1
        Options: [External, Opaque]
      Advertising Router 203.0.113.1, seq 0x80000001, age 1s, length 12
        Network LSA (2), LSA-ID: 203.0.113.1
        Options: [External, Opaque] [|ospf2]
IP (tos 0xc0, ttl 1, id 61508, offset 0, flags [none], proto OSPF (89), length 124)
    203.0.113.1 > 224.0.0.5: OSPFv2, LS-Ack, length 104
    Router-ID 203.0.113.1, Backbone Area, Authentication Type: none (0)
      Advertising Router 203.0.113.2, seq 0x80000001, age 5s, length 16
        External LSA (5), LSA-ID: 203.0.113.0
        Options: [External]
      Advertising Router 203.0.113.2, seq 0x80000001, age 5s, length 16
        External LSA (5), LSA-ID: 198.51.100.255
        Options: [External]
      Advertising Router 203.0.113.2, seq 0x80000001, age 5s, length 16
        Router LSA (1), LSA-ID: 203.0.113.2
        Options: [External, Opaque]
      Advertising Router 203.0.113.2, seq 0x80000002, age 1s, length 16
        Router LSA (1), LSA-ID: 203.0.113.2
        Options: [External, Opaque] [|ospf2]

さて、BIRD のルーティングテーブルを確認すると、ちゃんと経路交換で得られた経路が入っている。 router1 は、直接つながっていないために知らないはずの 198.51.100.0/24203.0.113.2 経由で到達できるとある。

$ sudo birdc show route -s router1.ctl
BIRD 2.0.8 ready.
Table master4:
198.51.100.0/24      unicast [ospf1 06:32:48.103] * E2 (150/10/10000) [203.0.113.2]
    via 203.0.113.2 on gw1-veth1
192.0.2.0/24         unicast [direct1 06:18:36.005] * (240)
    dev gw1-veth0
203.0.113.0/24       unicast [direct1 06:18:36.005] * (240)
    dev gw1-veth1
                     unicast [ospf1 06:18:36.103] I (150/10) [203.0.113.1]
    dev gw1-veth1

次のようにカーネルのルーティングテーブルにも、それが反映されている。

$ 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     06:18:36.002  

direct1    Direct     ---        up     06:18:36.002  
  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

ospf1      OSPF       master4    up     06:18:36.002  Running
  Channel ipv4
    State:          UP
    Table:          master4
    Preference:     150
    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:              5          1          0        ---          4
      Export withdraws:            0        ---        ---        ---          0

kernel1    Kernel     master4    up     06:18:36.002  
  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

最後に、最初は経路が足りなくて動作しなかった 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.145 ms
64 bytes from 198.51.100.1: icmp_seq=2 ttl=62 time=0.104 ms
64 bytes from 198.51.100.1: icmp_seq=3 ttl=62 time=0.099 ms

--- 198.51.100.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2026ms
rtt min/avg/max/mdev = 0.099/0.116/0.145/0.020 ms

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

まとめ

今回は Network Namespace で構築したネットワーク上で BIRD を動かし、OSPFv2 を使ったダイナミックルーティングを試してみた。

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

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

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

blog.amedama.jp

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

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

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

上記で、router1198.51.100.0/24 と直接はつながっていないため経路を知らない。 同様に、router2192.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 プロトコルに関しては、今回の主題となる部分。 ipv4import 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 で経路を広告するようになっている。 そして、router1router2 が広告する内容には、今回登場するすべてのセグメントが含まれている。 たとえば router1198.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 を使ったダイナミックルーティングを試してみた。