😺

ロバストプロトコル・オープンチャレンジに参加した

2022/08/12に公開

はじめに

先日セキュリティキャンプに参加してきました。私は専門A「IoTセキュリティクラス」に参加しました。この記事では「ロバストプロトコル・オープンチャレンジ」という講義について書きます。私が実装したプロトコルを説明するだけでは味気ないので未来のセキュリティキャンプ受講生のためにも、なぜそういう実装にしたのか、大変だったころ等々、プロトコルが完成するまでの過程を含めて書いていきたいと思います。

ロバストプロトコル・オープンチャレンジについて

大会レギュレーションから引用

概要

・物理的な通信障害が発生する環境で、多数のファイルを可能な限り正しく、高速に転送すること
ができる強靭なプロトコルを各自がデザイン/実装してください

ルール

・ファイルを圧縮して送ることは禁止します
・制限時間は 60 秒です
・ノイズによるパケット損失率は 50%程度です

採点基準

・制限時間の間に受信ファイルとして受信側に保存されたファイルをすべてチェックし、以下のよ
うに採点します

  • 正しいファイル 1 つにつき、10pts 加点
  • 誤りを含むファイル 1 つにつき、10pts 減点
  • 重複ファイル(内容が等しいファイル)1 つにつき、5pts 減点

転送するファイルについて

・ファイルのチェックは、ファイルのハッシュ値(md5)により行います
・ファイル名のチェックは行いません
・転送すべきファイルは、data/data${0...999}です

LANの構成は以下の図の通り、二つのラズパイ(taroとhanako)がLANケーブルで繋がっているという構成になっています。
lan構成

立てた戦略と実装、工夫したところ

1. header

過去の参加者も今年の参加者も、多くの方がUDPの上に自作プロトコルを乗っけていましたが、大会概要を見てラズパイ同士がLANケーブルで接続されているのなら、Ethernetだけで行けるのではということを思いつきました。しかもtaroとhanakoのmacアドレスは既知だったため、送信元macアドレスの情報は必要ないと考えました。そこでethernet headerの内、送信元macアドレスの部分にファイルに関する情報を持たせることにしました。実際のコードは以下の通りです。

l2ftp.h
/* header */
struct l2ftp_hdr{
    uint8_t dest[ETH_ADDRLEN];
    uint16_t fid; /* file id */
    uint8_t segid; /* segment id */    
    uint8_t reserved[3];
    uint16_t proto;
};

これでデータ部分に1500byteをフルに使えるようになるのでファイルを分割して送る際、最小のパケット数で送信できます。ファイルサイズは1file102400byteなので、69パケットで1fileを送信できる計算になります。プロトコル番号は実験用のプロトコルのために用意されている0x88b5にし、この番号を使用してsocketにフィルタを適用しました。raw socketを使用しているため、これをしないと届いた全てのパケットが受信されてしまい、使い物になりません。

fileter.h
struct sock_filter code[]={
    { 0x28, 0, 0, 0x0000000c },
    { 0x15, 0, 1, 0x000088b5 },
    { 0x6, 0, 0, 0x00040000 },
    { 0x6, 0, 0, 0x00000000 }
};

struct sock_fprog bpf = {
        .len = sizeof(code) / sizeof(code[0]),
        .filter = code,
};
tpacket_v3.c
err = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));

2. 一時バッファ

上のルールの通り、間違ったファイルを作ってしまうと-10ptsなので間違ったファイルを作らないための仕組みが必要です。そこで受信したファイルデータをそのままファイルに書き込むのではなく、一時バッファにためるようにして、全てのデータが受信できたことを確認したらファイルに保存するようにしました。ファイルが作成されるのは全てのデータが揃ったタイミングなので、間違ったファイルが作られることは絶対にありません。

3. packet mmapを使用

高速化に使えそうなものを探していたらpacket mmapというのを発見しました。このdocumentに書かれている通り、普通のraw socketはあまり効率が良くありません。なぜなら普通のraw socketはsend()でパケットを送信する際、システムコールの呼び出し→データをkernelが管理するメモリ領域にコピー→NICに送って送信という流れになり、送信する度にシステムコールとメモリコピーが発生するためです。packet mmapが高速なのはmmapを使用することで以下の図[1]のようにkernelと通信プログラムでパケット送受信用のリングバッファを共有するため、メモリコピーの無駄がなくなるからです。また、kernelはsend()が呼ばれた際にtp_statusがTP_STATUS_SEND_REQUESTになっているframeを全て送信するため、一回のシステムコールで複数パケットが送信可能になります。
packet mmappacket mmapは日本語情報が皆無だったのでdocumentを読んだり、他の人が書いたソースコードを参考にしながら開発しました。しかし高速化の結果受信側の処理が間に合わず、多くのパケットを取りこぼすという結果になりました。;;rasult
そこで泣く泣く送信側に以下のコードを入れました。(本末転倒)

threads_v3.c
while(1){
        for(fid = 0; fid < vchnum; fid++){
            send_all(fid);
            usleep(usec);
        }
}

4. 各ルーチンの詳細

送信側

fdata_sender

  • data0-999をループでひたすら送信する。

frame_handler_s

  • 受信側からリクエストされたファイルデータを送信する。

受信側

fdata_checker

  • frame_handler_rから指示を受けた際、ファイルごとに用意されたtableを参照して抜けているidを送信側に要求する。
  • 全て受信確認出来たら一時バッファにあるデータをファイルに保存する。

frame_handler_r

  • ファイルデータを一時バッファにコピーする。ただし重複しているものはスキップする。
  • fdata_senderはdata0-999までをループで送ってくるのでfidが切り替わったタイミングでqueueにfidを入れてfdata_checkerに指示を出す。

欠点

一番の欠点はメモリ効率が最悪な点です。例えば以下は受信側のプログラムなのですが、packet mmapの送受信用のリングバッファだけで2244ページも使用しています。さらに1000ファイルそれぞれについて一時バッファ(102400byte = 25page)が必要なのでメモリ使用量がすごいことになります。

reciever_v3.c
ring.param.rblocksiz = 143360; ring.param.rframesiz = 2048; ring.param.rblocknum = 64;
ring.param.tblocksiz = 8192; ring.param.tframesiz = 128; ring.param.tblocknum = 2; 

結果と考察

結果は以下の通り、残念なものになりました。原因として競技開始後しばらくしてプログラムが止まってしまったことが挙げられます。ローカル環境では正常に動いていたののですが本番環境では予期しない動作が引き起こされるバグ(?)があり、解決しようと頑張ってデバッグしていたのですが結局最後まで解決できませんでした。フルスクラッチで開発してサンプルプログラムより良い結果が出せたのは良かったですが期待したほどの記録は出なかったです。ちなみに総合順位は2位でした。

ノイズなし: OK = 269, FAILED = 0, DUP = 0
ノイズあり: OK = 86, FAILED = 0, DUP = 0 

未来のロバストプロトコル・オープンチャレンジ参加者へ

私が実装したプロトコルのソースコードはgithubで公開しています。この「ロバストプロトコル・オープンチャレンジ」という講義は講義中に開発を行う時間はなく、各自がプロトコルを事前に作成することが求められるのですがセキュリティキャンプの全国大会は8月頭であり、大学生の場合は試験期間で開発の時間が十分にとれない可能性があります。提供されているサンプルプログラムを改良するでもいいですが、もしよければ私が解決できなかったバグを解決して、今回の私の記録を更新してみてください。そして達成できた際はぜひ私に報告してください。(私が喜びます。)

参考資料

脚注
  1. Exploiting the Linux kernel via packet socketsから引用。 ↩︎

Discussion