経緯
今月は個人開発をやっています。全容が気になる方はブログの方で公開していますので、ぜひご覧ください。プログラミングサークルやイベントのお誘いも歓迎です。お待ちしています。
前回はAPIの利用やフロントエンドとバックエンドの概念について初めて触れることで開発の初歩について学びました。今回はネットワークの知識をこれまで以上に生かした何かを作りたい、できればパケットを可視化してアニメーションのようなものを作りたいというのが発端でした。
作ったもの
- Packet Aquarium
制作の裏側
今回の製作にあたり最初に考えなければならなかったのは、外部公開に際して内部ネットワークの情報をそもそも公開できるようなプロダクトがセキュリティ上いかがなものなのかという部分でした。ただ、作ってみないことには何も始まらないので、先にプロトタイプを作成してから、その中で最終的な着地点は考えていくことにしました。
まずパケットキャプチャにはpythonのscapyというツールを使うと良さそうということが分かりました。ただしこれを使うにはsudo環境が必要なため、今回は先にVSCode上からWSLを使用できるような環境構築から始めました。WSLの導入は特に難しいことはなく、VSCodeの左「拡張機能」からインストールするだけです。ただ私の場合は2ヵ月前に別プロジェクトで仮想環境をいじっていた為、一度クリーンアップする必要がありました。
次にPython環境を作ります。作法の上ではpythonにはvenvという仮想開発環境の構築機能があり、特定のディレクトリ(転じてコンテナやリポジトリ)の配下を自分の指定したpython開発環境にすることができます。ネーミングしたvenvの管理下でしかインストールしたライブラリは使用されず、これによってホスト側のpythonモジュールとの競合、および依存関係の不整合、またこれらによるファイル破損を防ぐことができます。
$ cd [project dir]
$ python3 -m venv [newenvname]
$ source [newenvname]/bin/activate
$ deactivate
しかしvenv配下のモジュールはsudoでは自動的に読み込んではくれない為、下記の様にパスを指定して実行する必要があります。
sudo env "PATH=$PATH" python server.py
ちなみにWSL上のファイルであってもエクスプローラから開くことはできます。特に今回の私の場合、windowsのドキュメントフォルダ配下に以前製作したアプリが入っていました。そのためクイックアクセスからどちらにもワンクリックで入れるようにし、プロダクト管理の簡素化を目指しました。
これでVScode上からWSLのマシンにアクセスして常時開発ができるようになり、今回の目的を一つ達成しました。なおパスは下記のようになっています。
C:\Users\Username\Documents\VSCode-Product
\\wsl.localhost\Ubuntu\home\Username\VSCode-Product-Wsl
また、今回は寄り道してDockerコンテナ上でアプリを動かすチャレンジもやることにしました。dockerデスクトップ自体は何度かインストールしたことはありますが、コードを動作させるのは初めてです。改めて最新版のdocker desktop(x64)をインストールします。Dockerfileやdocker-compose.ymlを記入してディレクトリ構成を整理したり、必要なライブラリを引っ張ってこれるようにします。
キャプチャ部のロジックです。今回は最初にjson的な形式でサマリやプロトコル、送信元/宛先MACアドレスなどを返すようにしました。初期段階ではpythonをターミナルで実行して実際に動作するか確認していました。ただこれをそのままブラウザ表示することは不可能なので、Flaskを導入してサーバを起動。capture.pyをAPI化してブラウザ出力する方式としました。これは基本ですがFlaskはDjangoと対をなすPythonのフレームワークです。
from scapy.all import sniff, Packet
import json
# パケットをパースしてJSONっぽい形式にする関数
def packet_to_dict(pkt: Packet):
return {
"summary": pkt.summary(),
"src": pkt[0].src if hasattr(pkt[0], 'src') else None,
"dst": pkt[0].dst if hasattr(pkt[0], 'dst') else None,
"protocol": pkt.name,
"length": len(pkt)
}
# キャプチャ関数
def capture_packets(count=5):
print(f"Capturing {count} packets...")
packets = sniff(count=count)
parsed = [packet_to_dict(pkt) for pkt in packets]
return parsed
if __name__ == "__main__":
capture_packets()
以下、Flaskの画面表示部とAPIコール部です。Flaskではindex.htmlをそのまま読みに行ってくれることはありません。staticディレクトリに格納するか、templatesディレクトリに格納した後、render_templateで呼び出す必要があります。静的HTMLではないので今回は後者を使います。APIコール部ではエラー処理も入れます。
@app.route("/")
def index():
return render_template("index.html")
@app.route("/capture")
def capture():
print("== /capture accessed ==")
try:
packet_info = capture_packets(count=5)
print(f"Captured: {packet_info}")
return jsonify(packet_info)
except Exception as e:
print(f"Error during capture: {e}")
return jsonify({"error": str(e)}), 500
docker-compose.ymlにbuildするディレクトリを明示しておけば、下記のコマンドで一発でビルドとコンテナの起動が済みます。ちなみに私はよくわかっていなくてビルドと起動を別々にしていました。本来ならばymlを記述していればdocker runコマンドをコンテナの起動手段にする必要もないですし、コード修正の度にぼんぼんコンテナを作り直してdocker desktopがごちゃつくこともありません。とはいえ勝手にコマンドを打ったらデスクトップアプリが連携してくれるのは感動しました。加えて、明示しなければコンテナ名は自動でランダムに生成されます。practical_torvaldsとかもありました。
docker compose up --build
UIについては、最初はjsonのまま出力していましたが、キャプチャしたパケットのfetchが確認できたと同時にCSSの表形式(tr,td)に各要素を入れ込んでいきました(下記)。本当はパケットの各要素が何byteなのかを内訳表示してレンダリングしたかったのですが、むやみに描画処理を走らせると背景のグラデーションに不整合が起きたりするので、機能改善はここで一旦ストップすることにしました。
data.forEach(pkt => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${pkt.summary}</td>
<td>${pkt.src}</td>
<td>${pkt.dst}</td>
<td>${pkt.protocol}</td>
<td>${pkt.length}</td>
`;
最終段階にて、このアプリを外部公開するにはどうしたらよいか再検討しました。最初はgithub pagesなどで動作している様子を一目で見てもらいたかったのですが、基本的にパケットキャプチャのような低層のプログラムの動作はホスティングサービスでは許可されていません。私のドメインやマイコンを調達して自宅ネットワークを構築する手もあったのですが、セキュリティレベルの担保やコスト面の問題など、すぐには解決できない課題に見舞われたことから、今回はソースコードのみの公開となりました。
所感とまとめ
今回はパケットをキャプチャしてブラウザに表示するアプリを開発しました。全体を通して過去の勉強や研究を思い出しながら作成を進めることができましたが、新しい学びも多く、特にWSLとVSCodeの連携や、自前APIの呼び出し、フレームワークのFlaskやDockerの正しい利用法など、アプリ開発の基礎となるスキルに幅広く触れることができました。また本アプリは今後のサービスの拡張に対し、実験ネットワークの構築による生のパケットの観測、Reactへの書き換え、canvasを用いたパケットのレンダリング、プロトコル毎のフィルタ表示など様々な可能性を秘めています。アプリのアイデアが尽きた時にはここに回帰して様々なアップデートが施せるのも良い点だなと思いました。今回触れた基本を今後訪れる様々な場面でより深く理解し、開発の土台を作っていきたいと思います。