目次

前書き
対象読者
書いていないこと
本書の動作環境
第1章 BGPの実装に必要な知識の学習
1.1 BGPとは、を学ぶ参考リンク紹介
1.2 イベント駆動ステートマシンとは
1.3 イベント駆動ステートマシンの実装例
第2章 実装開始
2.1 本章以降のプログラム表記について
2.2 BGPはどのようなイベント駆動ステートマシンなのか
2.3 プロジェクト作成
2.4 最初のテストの追加
2.5 Connect Stateへの遷移
2.6 main関数の追加
2.7 TCP Connectionの作成
第3章 テスト環境の作成
3.1 作成するテスト環境について
3.2 テスト環境の構築用のファイル追加
3.3 configをコマンドライン引数から作成するようにmain関数を修正する。
3.4 単体テスト、統合テスト用のスクリプトの作成
第4章 Established Stateまでの実装
4.1 OpenSent Stateに遷移する
4.2 OpenConfirm Stateに遷移する
4.3 Established Stateに遷移する
第5章 Update Messageを交換する
5.1 ルーティングテーブルの扱い方
5.2 Update Messageの送信の実装
5.3 Update Messageの受信の実装
第6章 他社実装との相互接続
6.1 FRRとの相互接続

前書き

 インターネットを支えるルーティングプロトコルである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でも問題ありません。

第1章 BGPの実装に必要な知識の学習

1.1 BGPとは、を学ぶ参考リンク紹介

 BGPとはなにか、ということについてはすでにインターネット上に良い資料が存在します。そのため、本書ではBGPとはなにか、ということについて記載しません。良い資料をふたつ紹介します。BGPについて詳しくない方は、どちらかを読み、本章以降に進んでください。すべての内容を記憶する必要はありません。ざっと目を通しておくだけで、本章以降の理解が容易になります。

 ネットワークエンジニアとして - BGPの技術1の次の章が参考になります。

 ・BGP( Border Gateway Protocol )とは

 ・BGP - 4つのメッセージ、6つのステータスと状態遷移

 ・BGP - IBGPとEBGPの違い

 ・BGP - スタブAS、トランジットAS、非トランジットAS

 ・BGP - パスアトリビュート( パス属性 )& ベストパス選択

 ・BGP - コンフィグ設定 - 基本設定

 30分間ネットワーキング2の次の章が参考になります。

 ・第16回 BGP4(1) AS

 ・第17回 BGP4(2) BGPピア

 ・第18回 BGP4(3) IBGPとEBGP

 ・第19回 BGP4(4) パスアトリビュート

 ・第20回 BGP4(5) ベストパス選択

1.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.1: テレビのステートマシン図

1.3 イベント駆動ステートマシンの実装例

 イベント駆動ステートマシンをどのよう実装すればいいのかということを学ぶため「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状態のままで音量の変動もないことがわかります。

TV_実行時のログ

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のステートマシンも前述のイベント駆動ステートマシン、テレビの実装例に似た方針で実装していきます。

1. https://www.infraexpert.com/study/study60.html

2. http://www5e.biglobe.ne.jp/aji/30min/index.html

試し読みはここまでです。
この続きは、製品版でお楽しみください。