先週の一週間でセキュリティキャンプ2025全国大会に参加していた.ゼミはY2: CDN自作ゼミで,開発ゼミの中でもセキュリティとは若干遠い分野をやっていた.
事前課題
全国大会そのものは8月11-16日の5日間,現地での開発は実質3日であったが,選考通過が発表された6月中旬から本番にかけて事前課題に取り組んでいた.
6月中旬から7月初旬にかけてはGSLBを作っていた.講師が用意した環境に何もしないGSLBの雛形が置いてあり,クエリに対して好きに応答するように関数を実装するというもの.自分は,クエリ元のIPアドレスをカバーするリージョンで最も負荷の少ないPoPを返すようなシンプルな関数を書いた.
7月以降は実際に現地で使うソフトウェアを書いていった.L4LBとキャッシュサーバー,そして何らかのオリジンサーバーを実装するというのが課題だったが,実はL4LBは講師が用意したXDPで動作する良い実装がすでにあり,期末試験や研究発表で時間を割けなかったこともあってキャッシュサーバーの実装に多くの時間を費やした.終わってみて,もっと事前にL4LBに手を入れておくべきだったという反省が残ったが,使える時間は全て使っていたので仕方なし.
自分が作ったキャッシュサーバー実装はkyacheという名前で公開している.いわゆる公開キャッシュサーバーという位置付けで,ブラウザに乗るようなプライベートキャッシュでの使用は想定していない.自分は数年前から勝手にQUIC Enthusiastをやっているので,今回のCDN自作ゼミでもHTTP/3を喋れるCDNを作ろうという目標を一つ立てていた.というわけでこのkyacheもHTTP/3のリクエストを捌くように実装した.また,ある程度世の中の実装と近いものを作りたかったため,RFC9111に準拠するようにレスポンスのキャッシュ可否,リクエストに対するキャッシュ使用可否の制御を行った.どれだけRFC9111に準拠しているかはHTTP WGのテストケースを走らせることで確かめられ,結果はkota-yata.github.ioで公開している.nginxよりはちゃんと準拠しているかなという感じ.
現地での開発
ゼミの序盤はひたすら実機でネットワークを構築することに時間を費やしていた.受講生は3人だったが,それぞれがルーターを持ち,部屋にある上流のルーターにつながる.L4LB,キャッシュサーバー,オリジンサーバーはそれぞれ講師に用意いただいたラズパイなりミニPCなりで動かし,各マシンにIPアドレスを振ったりアドレスレンジをBGPで上流に広報していたら1日目が終わっていた.VIPとIPIPインターフェースの設定なんかは慣れていなかったこともあって特に難しく,設定中に今自分が何をやっているのか分からなくなるという状態に何度か陥った.自分が打ち込んだ訳のわからない設定を指摘・修正いただいた講師,チューター,受講生には感謝である.
(↑こうなってほしくて頑張っていた)
2日目に各マシンがなんとかインターネットに疎通し,手を入れてないL4LBとkyache,適当な文字列を返すHTTPオリジンサーバーを持ってしてCDNらしき何かが完成した.これをもって「私はCDNを自作した」と堂々と主張する権利を得た. これからは「私はCDNを自作した」と堂々と主張していく.
とはいえここからが本番である.疎通はしたが,現状HTTP/1で適当なレスポンスが返るだけで,キャッシュサーバーも1つしか設置していないためL4LBもやる仕事がない.2日目はここから,L4LBをHTTP/3に対応させ,複数のキャッシュサーバーを立ててロードバランスさせるべく開発を加速させた.
QUIC-LB
さて,QUICに対応するL4LBの仕様としてQUIC-LBがある.QUICは通信を4-tupleではなくConnection ID(CID)で識別することで,クライアントのIPアドレス変更時にも通信を継続できるという「コネクションマイグレーション」機能を持つが,第三者からのUnlinkabilityを担保するために,新しいアドレスを使用する際はCIDも新しいものを使用するように実装されることが多い.IPアドレスが変わってCIDも変わったら通信を識別できないじゃないかと思うかもしれないが,そこはNEW_CONNECTION_IDフレームなるメッセージで,あらかじめ使用予定のCIDを相手に通知しておく.
重要なのが,ここでL4LBはNEW_CONNECTION_IDフレームを見ることができない点である.QUICのフレームはペイロードに含まれるため,当然暗号化されている.L7LBで通信を終端する場合はLBでも使用予定のCIDプールを管理できるが,L4LBではそれは不可能である.ではどうするのか.QUIC-LBでは,サーバーが生成するCIDに,L4LBに共有された何らかのフォーマットでサーバーIDを埋め込んでおき,L4LBでは暗号化されていないヘッダー部分のCIDをそのフォーマットでデコードすることでサーバーIDを取得し,振り分けるサーバーを認識する.例えば,事前にサーバーとL4LBで「CIDの末尾16ビットがサーバーのIDである」という取り決めをしておいて,サーバーは接続開始時に生成するCID,またNEW_CONNECTION_IDで生成するCID全てにおいて,末尾に自身のサーバーIDを埋め込んでおくのである.
先に述べたようにNEW_CONNECTION_IDフレームを使う理由はUnlinkabilityの担保であるが,このQUIC-LBの仕組みを平文でやると割と簡単に2つのCIDをlinkできてしまう(例えば毎回CIDの末尾16ビットが同じだったらバレやすいよね).これに対応するために,QUIC-LBではサーバーIDを埋め込んだ上で暗号化する仕組みも定められている.暗号化にはいくつかの選択肢が存在し,CIDのビット長によっては効率的に暗号化できたりできなかったりする訳だが,ここでは省略する.
というわけで長々とQUIC-LBの説明をしてきたが,結論から言うとQUIC-LBの実装は終わらなかった.CIDを読み,それをベースに振り分ける実装は完了したが,CIDが変わった際には今のところ新しい通信と認識してしまい,別のサーバーに振り分ける可能性がある実装になっている.同じゼミの受講生が完璧なQUIC-LBをXDP上で実装しており,すごいなぁと思いながら自分も開発していたわけだが,間に合わなかった.無念.
ただL4LBが一応QUICを認識できるようになり,オリジンサーバーもHTTP/3を喋るようになったため,最終的にはHTTP/3に対応するCDNの自作に成功した.
悪魔のrp_filter
1日目に1つ目のキャッシュサーバーのセットアップをしており,3日目にLBの動作確認のために2つ目のキャッシュサーバーを構築した.1つ目のサーバーはラズパイに乗せ,2つ目はミニPCに乗せることになったが,どちらもUbuntuだし,ネットワークについても同じ設定を入れるだけだし,さっさと済ませて動作確認をして飯を食べようと思っていたのだが,これが全く上手くいかず,結局3日目をほぼフルで費やしてしまった.
観察された症状として,サーバーにパケットは届いているが,サーバープロセスがそれを認識していなかった.外部からVIP宛にリクエストを送り,ルーター,サーバーともにtcpdumpでパケットキャプチャをしたところ,ルーターではリクエストパケット,サーバーでは対応するIPIPパケットが確認されたがアプリケーションプロセスにはパケットが到達していない.つまり,libpcapがパケットをキャプチャして以降,ネットワークスタックのどこかでパケットがドロップされているということになる.
結論から言うと,Linuxカーネル内のrp_filterが原因であった.ftraceコマンドで確認したところ,IPIPパケットが軒並みreason: IP_RPFILTER
でドロップされていた.
rp_filterはIPスプーフィング対策のためのフィルターで,あるネットワークインターフェースから受信したパケットが異なるインターフェースを経由して出ていくように設定されている場合にパケットをドロップするというものである.今回のケースでは,戻りパケットがL4LBを経由せず直接ルーターに向かう,Direct Server Returnを実現するような設定を行なっていたことでこのフィルターに引っかかった.LBからのパケットはipipモードを有効にしたipip0インターフェースで受け,デカップリングして中身のIPパケットを処理し,VIPがアサインされているループバックインターフェースから応答パケットが出ていくような経路になっていたのである.
最終的にrp_filterを無効化することで何とかパケットを通した.3日目はゼミとしては最終日であり,17時からゼミ内成果発表があったわけだが,16時過ぎにこの問題を特定し,ギリギリで2つ目のキャッシュサーバーが疎通した.進捗としては他の受講生に比べると劣っていたが,当初の目標は達成され新たな知見を得たので,身のある開発ができたのではないかと思う.
まとめ
最終日は一人でネットワークトラブルシューティング入門をやっていたわけだが,開発ゼミは全体的に開発時間が自由だったので,時たま別のゼミを見に行ったりできて楽しかった.また開発ゼミの部屋は定期的に悲鳴,叫び声,助けを求める声(from TEEゼミ),爆発音(from 探査機自作ゼミ)が聞こえてきており,学生の限界開発という感じがして味わい深かった.
今回は全国大会だったが,来年以降はチューターやネクスト受講生にもチャレンジしていきたい.
おまけ
LT大会では「結局QUICで通信は速くなるの?」というテーマでLTをやった.結論から言うと時と場合による.