インターネットを支えるルーティングプロトコルであるBGP(Border Gateway Protocol)をRFCからRustで実装する方法を解説します。小さなプログラムから始め、Update Messageを交換しルーティングテーブルの更新を行うまで、ステップバイステップで実装を追うことができるように解説します。
本書では正常系のみ実装します。また可能な限り最小限の実装をします。
本書で作成するBGPのサンプル実装は以下リポジトリに公開しております。
・https://github.com/Miyoshi-Ryota/mrbgpdv2
GitHub Starをいただけたらとっても嬉しいです!
RFCに定義されているプロトコルを一度自分の手で実装することで、BGPに限らず、その他のプロトコルについてもRFCから実装できるようになるように思います。
本書がBGPを実装する手助け、ひいてはRFCを読めるようになること、RFCからプロトコルを実装できるようになることの手助けになることを祈っております。
ぜひ楽しんでいただけたら幸いです。
読者に以下の前提知識を求めています。
・ルーティングプロトコルは聞いたことがある。
・スタティックルートとは何か分かる。
・ルーティングテーブルを見て意味が分かる。
・何らかのプログラミング言語でのプログラミング経験がある。
・Rustを知っている。
以下の経験があるとよりわかりやすいかもしれません。
・BGPの運用経験
・Docker、docker-composeを用いた開発経験
・wiresharkを用いてパケットキャプチャを行った経験
本書には以下のことは書かれておりません。
・BGPとは何かという説明
─ただし、参考になるWebページを紹介しています。そのためBGPについて知らなくても問題ありません。またBGPのRFC内で説明されていることについても書内で説明しきらずURLや項目番号の紹介にとどめている部分もあります。RFCだけでは理解が難しい部分については解説しています。
・Rustの書き方の解説
・Rust / Docker / docker-compose / wiresharkなど本書に登場するツールのインストール方法
筆者は以下の環境で本書のサンプルプログラムの作成・動作環境を行っております。
・Ubuntu 20.04 LTS / Pop!_OS 20.04 LTS
・cargo 1.53.0-nightly (f3e13226d 2021-04-30)
・rustc 1.54.0-nightly (676ee1472 2021-05-06)
・Docker version 20.10.13, build a224086
cargo / rustcのバージョンは2018 Edition以降であれば何でも動作すると考えております。
ただしOSについては注意点があります。ルーティングプロトコルを実装するため、ルーティングテーブルを読み書きする処理が存在します。ルーティングテーブルの読み書きの方法はOSによって異なります。そのため、本書のプログラムを動作させるためには、Linuxの環境が必要になります。仮想マシンやDockerでも問題ありません。
BGPとはなにか、ということについてはすでにインターネット上に良い資料が存在します。そのため、本書ではBGPとはなにか、ということについて記載しません。良い資料をふたつ紹介します。BGPについて詳しくない方は、どちらかを読み、本章以降に進んでください。すべての内容を記憶する必要はありません。ざっと目を通しておくだけで、本章以降の理解が容易になります。
ネットワークエンジニアとして - BGPの技術1の次の章が参考になります。
・BGP( Border Gateway Protocol )とは
・BGP - スタブAS、トランジットAS、非トランジットAS
・BGP - パスアトリビュート( パス属性 )& ベストパス選択
30分間ネットワーキング2の次の章が参考になります。
BGPはひとつのピアをひとつのイベント駆動ステートマシンとして実装できます。イベント駆動ステートマシンとは、現在の状態(ステート)と入力(イベント)によって動作が決定するモノのモデルです。
例として、テレビをステートマシンとして表現します。テレビの状態として、1. 電源ON、2. 電源OFFの2状態が存在し、入力としてa. 電源ボタンの押下、b. 音量増加ボタンの押下、c. 音量減少ボタンの押下の3つが存在するとします。本来のテレビはもっと多数の状態やイベントを持っていますが、ここでは例示のためにシンプルにしています。
テレビの状態が1. 電源OFFのときにa. 電源ボタンの押下が発生した場合はテレビの状態が2. 電源ONに遷移します。テレビの状態が1. 電源OFFのときに、b. 音量増加ボタンの押下、c. 音量減少ボタンの押下が発生した場合はテレビの状態は1. 電源OFFのままで何も起こりません。
テレビの状態が2. 電源ONのときにa. 電源ボタンの押下が発生した場合はテレビの状態が1. 電源OFFに遷移します。テレビの状態が2. 電源ONのときb. 音量増加ボタンの押下、c. 音量減少ボタンの押下が発生した場合は、テレビの状態は2. 電源ONのまま音量が増減します。これを図示すると図1.1になります。
このように、テレビは現在の状態と入力によって、動作が決定するモノとして表現することが可能です。
このような現在の状態(ステート)と入力(イベント)によって動作が決定するモノのモデルを、イベント駆動ステートマシンといいます。
イベント駆動ステートマシンをどのよう実装すればいいのかということを学ぶため「1.2 イベント駆動ステートマシンとは」で例示したテレビをコードにします。
use rand::Rng;
use std::collections::VecDeque;
use std::thread;
use std::time::Duration;
#[derive(Debug)]
enum State {
PowerOn,
PowerOff,
}
#[derive(Debug)]
enum Event {
PushedPowerButton,
PushedVolumeIncreaseButton,
PushedVolumeDecreaseButton,
}
struct TV {
now_state: State,
event_queue: EventQueue,
volume: u8,
}
impl TV {
pub fn new() -> Self {
let now_state = State::PowerOff;
let event_queue = EventQueue::new();
let volume = 10;
Self {
now_state,
event_queue,
volume,
}
}
pub fn be_pushed_power_button(&mut self) {
self.event_queue.enqueue(Event::PushedPowerButton);
}
pub fn be_pushed_volume_increase_button(&mut self) {
self.event_queue.enqueue(Event::PushedVolumeIncreaseButton);
}
pub fn be_pushed_volume_decrease_button(&mut self) {
self.event_queue.enqueue(Event::PushedVolumeDecreaseButton);
}
pub fn handle_event(&mut self, event: Event) {
match &self.now_state {
&State::PowerOn => match event {
Event::PushedPowerButton => {
self.now_state = State::PowerOff;
}
Event::PushedVolumeIncreaseButton => {
self.volume += 1;
}
Event::PushedVolumeDecreaseButton => {
self.volume -= 1;
}
},
&State::PowerOff => match event {
Event::PushedPowerButton => {
self.now_state = State::PowerOn;
}
_ => (),
},
}
}
}
struct EventQueue(VecDeque<Event>);
impl EventQueue {
pub fn new() -> Self {
let d = VecDeque::new();
EventQueue(d)
}
pub fn dequeue(&mut self) -> Option<Event> {
self.0.pop_front()
}
pub fn enqueue(&mut self, event: Event) {
self.0.push_back(event);
}
}
fn push_random_button_of_tv(tv: &mut TV) {
let mut rng = rand::thread_rng();
match rng.gen_range(0..4) {
1 => tv.be_pushed_power_button(),
2 => tv.be_pushed_volume_increase_button(),
3 => tv.be_pushed_volume_decrease_button(),
_ => (),
};
}
fn main() {
let mut tv = TV::new();
tv.be_pushed_power_button();
loop {
push_random_button_of_tv(&mut tv);
if let Some(event) = tv.event_queue.dequeue() {
println!(
"tv information: {{ now_state={:?}, volume={} }}\n \
input_event: {:?}",
tv.now_state, tv.volume, event
);
tv.handle_event(event);
}
thread::sleep(Duration::from_secs(2));
}
}
103行目、main関数内のpush_random_button_of_tv(&mut tv);でTVのランダムなボタンを押下し、TVにEvent(入力)を送信しています。送信されたEventはイベントキュー、tv.event_queueにエンキューします。 104行目でイベントキューに保存されているEventを取り出します。TVの現在の状態(State)はTVのインスタンスに保存されています。110行目でEventを扱います。49行目〜69行目を見ると分かるように、`tv.handle_event(event)`はEventとtvインスタンスに保存されている現在の状態に応じて動作し、次の状態を決定します。それはイベント駆動ステートマシン、そのものでした。このようにしてイベント駆動ステートマシンを実装することができました。
TV_実行時のログが実行時のログです。
ログの4行目を見ると、電源OFFの状態であることがわかります。次にログの5行目を見ると、電源ボタンが押されたことがわかります。次にログの6行目を見ると、電源ONの状態に遷移したことがわかります。次にログの7行目を見ると、音量増加ボタンが押されたことがわかります。次にログの8行目を見ると、電源ON状態のまま、音量が11に増加していることがわかります。
一方でログの16、17、18行目を見ると、電源OFF状態のときに音量増加ボタンが押されても、電源OFF状態のままで音量の変動もないことがわかります。
mrcsce@pop-os:~/programming/rustProjects/samplecode$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/samplecode`
tv information: { now_state=PowerOff, volume=10 }
input_event: PushedPowerButton
tv information: { now_state=PowerOn, volume=10 }
input_event: PushedVolumeIncreaseButton
tv information: { now_state=PowerOn, volume=11 }
input_event: PushedVolumeDecreaseButton
tv information: { now_state=PowerOn, volume=10 }
input_event: PushedVolumeIncreaseButton
tv information: { now_state=PowerOn, volume=11 }
input_event: PushedVolumeDecreaseButton
tv information: { now_state=PowerOn, volume=10 }
input_event: PushedPowerButton
tv information: { now_state=PowerOff, volume=10 }
input_event: PushedVolumeIncreaseButton
tv information: { now_state=PowerOff, volume=10 }
input_event: PushedPowerButton
tv information: { now_state=PowerOn, volume=10 }
input_event: PushedVolumeIncreaseButton
tv information: { now_state=PowerOn, volume=11 }
input_event: PushedVolumeIncreaseButton
^C
mrcsce@pop-os:~/programming/rustProjects/samplecode$
これは「1.2 イベント駆動ステートマシンとは」の章で例示した通りの動作です。例示したイベント駆動ステートマシンを実装できていることが確かめられました。BGPのステートマシンも前述のイベント駆動ステートマシン、テレビの実装例に似た方針で実装していきます。