読者です 読者をやめる 読者になる 読者になる

SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

F#によるパケットモニタの作成(WinPcap)


本記事は、「F# Advent Calendar 2011 」の12月12日分です。

F#の街さっぽろ」に住む、札幌ワークスのSINです。F#を始めたばかりの超初心者です。近くの方々に色々やさしく教えて頂きながら楽しく勉強中です。

「F#のここがよくわからない」とかってエントリを書くと、やさしいお兄さんたちがやさしく教えて・・・の甘い言葉に乗せられて、ついついエントリーしてしまいましたが、連日、皆さんのハイレベルな記事を拝見して、完璧に失敗したなと猛反省してます。まーハードルをガッツリ下げる担当という事で、どうかお許しください。

1 WindowsでのF#によるパケットモニタ

本日の記事は、「WindowsでのF#によるパケットモニタ」です。
WindowsAPIでは、IP層以下のパケットを処理する事ができないため、通常、NICをモニタするためのデバイスドライバを作成します。そして、その代表的なものが、フリーで公開されているWinPcapです。
WinPcapは、もともとC,C++から使用するライブラリ(DLL形式)となっていますが、C#から使用するサンプルも多数公開されています。しかし、現時点で、F#から使用する例がまだ検索できないようなので(ええっ世界初?@o@まじ)、今回、作成してみる事にしました。

全部のコード及び、実行画面は下記のとおりです。
WinPcap.fs
Program.fs

デバイス選択中


実行中


※サンプルは、WinPcapがインストールされていないと実行できません。
WinPcap http://www.winpcap.org/

2 WinPcap定義

.NET Framework上で動作するプログラムから、.NETアセンブリ以外のDLLがエクスポートしている関数を呼び出すには、DllImport属性を使用して、あらかじめ宣言するようになっています。通常、C#のプログラムでよく見かけるこの手法は、F#でも大きな問題はなく利用できました。

手順としては、WinPcapのDeveloperResourcesのページからWinPcap version4.1.2のソースコードをダウンロードし、winpcapwpcaplibpcappcapに含まれる下記のヘッダファイルのうち、通常利用される関数と構造体をDllImport及びStructで宣言しました。
pcap.h、pcap1.h、bpf.h、Win32-Extensions.h、remote-ext.h
指定した定義は、まとめてmodule WinPcapとし、WinPcap.fsにまとめました。


3 メイン処理

プログラムの主要な部分は、下記のとおりです。


主要な流れは、以下のとおりで、強制終了されるまで(4)を繰り返し実行しています。
(1) デバイスの一覧取得 pcap_findalldevs_ex()
(2)デバイスの選択
(3)デバースのオープン pcap_open()
(4)パケット受信 pcap_next_ex()

(3)及び(4)の繰り返し部分は、次のようになっています。


4 パケットの解析

受信パケットはWinPcapからbyte[]データとして取得できます。パケットモニタでは、このバイト列を先頭から読みすすめ、プロトコルに当てはめて解析します。
通常この処理は、プロトコルを構造体で表現し、取得したデータに構造体のポインタを当てて解析するのが普通なのですが、F#では、fixedやunsafeが使用できないため、マーシャリングを必要とするやや煩雑な処理になってしまいました。

それでは、解析の一例としてIPv6ヘッダを処理しているコードを例に処理パターンを紹介します。

処理の流れは、以下のとおりです。
(1) プロトコルヘッダを表現する構造体を定義
(2) 構造体サイズ分のメモリ確保
(3) 構造体へのマーシャリング
(4) プロトコル項目の取得
(5) 表示
(6) メモリ解放

他のプロトコルも概ね同様の処理です。なお、全てのプロトコルの解析をサポートすると膨大な量になりますが、本記事では、Ether ARP IPv4 IPv6のヘッダ解析のみを行っています。

5 PInvokeStackImbalanceの警告が発生する


DllImportで宣言した関数を使用すると「PInvokeStackImbalance」の警告が発生しました。
C#では、同じ定義でも問題ないのですが・・・F#では問題と認識するようです。
最終的に、原因・回避方法は、よく分かってないのですが、呼び出し規約をcdeclと明記することで環境によっては警告が出なくなるよう