コンテナ上で動く簡易BGPソフトウェアをc言語で実装した話 [SecCamp2021]
セキュリティキャンプ2021のZ4トラックでBGPルーターのサブセットを自作したときのメモを書いていきます。
僕がこれを曲がりなりにも作ることができたのは、講師の城倉さんと齋藤さんの手厚いサポートのおかげです。本当にありがとうございました。
概要
FRRoutingというソフトウェアルータとBGPでのやりとりができるプログラムをLinux上で開発しました。
開発はC言語で行いました。
実装の都合上、1つのピアとしか繋がれなくて再接続もできないものです。
他にも経路情報の取り消しができなかったり、4バイトASに対応していなかったりと、必要な機能が全然備わっていませんが許してください。
BGPのしくみ
www.infraexpert.com
この通りBGPピアは状態遷移を行いながら、それに合わせた種類のパケットを送り合うことで繋がりを保ちながら必要な経路情報をやり取りします。
この部分をswitch文を利用しました。
void bgp_process(struct BGP *bgp,struct Peer *p,char *bgp_msg,int sock) { switch (p->state) { case OpenSent: bgp_process_open_sent(p, bgp_msg,sock); break; case OpenConfirm: bgp_process_open_confirm(bgp,p, bgp_msg,sock); break; case Established: bgp_process_established(bgp,p, bgp_msg,sock); break; default: break; } }
繋がりが切れたときの再接続や複数台とピアを張ることを考えていないので、だいぶ簡素な作りになっています。(真似しないでね)
BGPパケットを作り込む
BGPのパケットの構造を掴むためにRFCなどやその解説書とかを読むのもいいですが、大枠を知るためには実際のBGPのやり取りをパケットキャプチャして観察するのが早いです。
このような中身のパケットを送れるようにプログラムを作っていきます。
BGPはTCPの上で動くプロトコルなので、TCPソケットを作ってそこにBGPメッセージに相当するバイナリ列を送ればよいです。
struct bgp_update { uint8_t marker[16]; uint16_t len; uint8_t type; uint16_t withdrawn_len; uint8_t contents[64]; }__attribute__((__packed__));
パケットの中身をこのような構造体で埋めていきます。(これはupdateメッセージに相当する構造体です。)
packedをつけておかないとコンパイルの過程で構造体が意図しない形に変わってしまうので注意が必要です。
また、ビッグエンディアンとリトルエンディアンの違いにも要注意です。
1バイト(8bit)を超える大きさの数値のビット演算をする際には必ず意識する必要があります。
ベストパスセレクション
ピアから広告された経路情報はリストにして保持してます。
本来は木構造にするのが鉄則ですが、これも僕の実装力不足です。
そしてそのリストをチェックして、一番経由するASパスが短い経路をベストパスにするというアルゴリズムを実装しました。
この選んだパスの情報(宛先とネクストホップ)を、route addコマンドを用いてルーティングテーブルに反映するようにしています。
まとめ
githubにソースコードを載せています。
https://github.com/yushoyamaguchi/bgp1/tree/main/dev5
ちなみにこのBGPのサブセットのプログラムは実際にインターネット上では動かすことができません。
(僕の怠慢です、ごめんなさい。)
tinetというネットワークシュミレーションツール上で動かしながら実装をしました。