eBPF Powered Dodger Game!!
eBPFを使って、障害物を避けるゲームを作りました。
ゲームの特徴
- 落ちてくる障害物を避けるゲームです
- 受信パケットを元に障害物を生成します
- eBPFを使ってパケットの受信を監視しました
受信パケットを元にした障害物の生成
障害物はランダムに落ちてくる必要があります。
通常は擬似乱数を用いたりして、新たに追加する障害物の数を決定していると思います。
この障害物の生成数を受信パケットの数を元に決定すると面白いのではないかと思いました。
ゲームの面白さを考えると障害物の数はある程度ランダムでないといけません。
しかし、厳密にランダムである必要もありません。
そこでパケットの受信のランダム性を障害物の生成に使うことを考えました。
パケットの受信には以下の特徴があるので、ある程度のランダム性があり、どきどきできます。
・ある一定期間に届くかどうかはランダム
・時には大量に届く可能性がある
(※完全に予測不可能なことを求められないこのようなゲームの場合はうまくいきそうだと思いました。)
eBPFを用いたパケット受信の監視
パケットの到着数の計測に、XDPにアタッチしたeBPFプログラムを使用しました。
今回はパケットの種類をTCP/UDP/ICMPのみに絞ったため、それ以外は以下のように弾きます。
if ((ip->protocol != ICMP_PROTO) && (ip->protocol != TCP_PROTO) && (ip->protocol != UDP_PROTO))
return XDP_PASS;
eBPFマップを使用して、プロトコル種類とその数を保存します。eBPFマップの種類はBPF_MAP_TYPE_HASHを用いました。BCCではBPF_HASH(mapname, key_size, value_size)のように宣言して使用できます。
BPF_HASH(packet_count, u8, u32);
受信パケットを保存する箇所は以下のようにしました。
プロトコル種類をキーとして、BCCの用意してくれているeBPFプログラム内用の関数を用いて変更していきます。これでプロトコル毎のパケット数をカウントできます。
u32 *type_count = packet_count.lookup_or_try_init(&ip->protocol, &zero);
if (type_count) {
(*type_count)++;
ip_count.update(&ip->protocol, type_count);
}
ちなみにこれをlibbpfでやろうとするとコード量が結構増えます。
ただ、eBPFプログラム内とユーザ側のプログラムで共通したヘッダーを使えるなど利点もたくさんあります。
今回はユーザ空間側でpygameを使いたかったことから、eBPFプログラムとやり取りをするユーザ空間側のプログラムをPythonで書けるBCCが都合が良かったのでそちらを使いました。
ゲーム内でのパケット情報の利用
ユーザ空間側のプログラムからは、pygameのループ毎にeBPFマップからプロトコル毎の受信パケット数を読み出した後、カウントをリセットします。
icmp_cnt = tcp_cnt = udp_cnt = 0
for ip_type,cnt in bpfo.get_table("packet_count").items():
ip_type = int.from_bytes(ip_type, byteorder='little')
cnt = int.from_bytes(cnt, byteorder='little')
if ip_type == 0x01:
icmp_cnt = cnt
elif ip_type == 0x06:
tcp_cnt = cnt
elif ip_type == 0x11:
udp_cnt = cnt
bpfo.get_table("packet_count").clear()
降ってくる障害物が同じ大きさだと味気ないので、プロトコルに応じて変化させることにしました。
3つのサイズがあります。
- ICMP = 最も小さい
- TCP = 中くらい
- UDP = 大きい
デモ
以下がデモです。裏でホストに向けてpingを打っています。あとVPNで繋いでいるのでUDPが多めなのかもしれません。
まとめ
DoSの恐ろしさを体感できます。
また、どんな種類のパケットがコンピュータに届いているのかを知ることができます。
突如小さな障害物が増えたら誰かがあなたの存在を探索しているのかもしれません。
Discussion