RPCAPクライアントの実装
RPCAPクライアントは、先にも紹介したとおり、通常のWinPcap対応プログラムです。
リモートでRPCAPサーバに接続してパケットを受け取るために考慮しなければならない事項は、次の3点のみです。
- アダプタの指定
- 認証
- 転送パケットのフィルタ
アダプタの指定
ローカルのアダプタだけに対応したパケットモニターの場合、pcap_findalldevs()
を使用してローカルのアダプタ一覧を取得し、そのアダプタ名をpcap_open_live()
に与えてキャプチャを開始しました(「WinPcapを使用したパケットモニターの作成 」を参照)。
これをリモート対応にするには、新しく用意されたpcap_findalldevs_ex()
でアダプタ一覧を取得し、このアダプタ名をpcap_open()
に与えます。
アダプタ一覧の取得
リモート上のアダプタに対応したpcap_findalldevs_ex()
は、pcap_findalldevs()
のラッパー関数であり、ローカルマシン上の情報取得には、既存のpcap_findalldevs()
が使用されています。
pcap_findalldevs_ex()
は、次のように定義されており、pcap_findalldevs()
と比較すると、第1パラメータおよび第2パラメータが増えています。
int pcap_findalldevs_ex( char * source, struct pcap_rmtauth * auth, pcap_if_t ** alldevs, char * errbuf )
第1パラメータのsource
には、次の文字列を指定します。
- ローカル対応の場合――
"rpcap://"
- リモート対応の場合――
"rpcap://RPCAPサーバのアドレス:RPCAPサーバのポート番号"
第2パラメータのauth
は認証情報であり、詳しくは次項で解説します。
pcap_findalldevs_ex()
を使用したアダプタ一覧取得のサンプルコードは次のようになります。source
に与える文字列を工夫することで、ローカルにもリモートにも対応できることが分かります。
public static bool EnumAdapter(out ArrayList adapters, bool remote,string server,int port,pcap_rmtauth auth){ adapters = new ArrayList(); IntPtr alldevs = new IntPtr(); // 情報取得用のバッファ StringBuilder errbuf = new StringBuilder(PCAP_ERRBUF_SIZE); string source = "rpcap://"; // ローカルの場合 if(remotee) // リモートの場合 source = string.Format("rpcap://{0}:{1}",server,port); if (pcap_findalldevs_ex(source,ref auth,ref alldevs,errbuf) == -1){ // エラー (エラーの詳細は、errbufに格納されている) return false; } IntPtr p = alldevs; while(!p.Equals(IntPtr.Zero)){ // pcap_if構造体にコピーする pcap_if i = (pcap_if)Marshal.PtrToStructure(p,typeof(pcap_if)); // この時点で、pcap_if構造体である i に // アダプタの情報が取得できている。 // i.name アダプタ名 // i.ipAddress IPアドレス p = i.next; // 次のアダプタ情報に移動 } // 情報取得用のバッファの開放 pcap_freealldevs(alldevs); return true; }
キャプチャの開始
リモート上のアダプタに対応したpcap_open()
は、次のように定義されており、pcap_open_live()
と比較すると、第5パラメータの認証情報が増えています。
pcap_t* pcap_open( const char * source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth * auth, char * errbuf )
pcap_open()
を使用したキャプチャ開始のサンプルコードは次のようになります。なお、pcap_open()
の第1パラメータに指定するアダプタ名は、ローカルおよびリモートを区別せず、どちらもそのまま渡すことができます。このため旧形式のpcap_open_live()
を完全に置き換えて使用できます。
public static bool Start(string adapterName,pcap_rmtauth auth){ int timeout = 20; int Promiscast = 1; StringBuilder errbuf = new StringBuilder(PCAP_ERRBUF_SIZE); handle = pcap_open(adapterName,MAX_RECV_SIZE, Promiscast,timeout,ref auth,errbuf); if(handle.Equals(IntPtr.Zero)){ // エラー (エラーの詳細は、errbufに格納されている) return false; } return true; }
認証
前項で説明を省略した認証情報は、RPCAPサーバに接続する際に利用する、ユーザ名およびパスワードのセットです。
このパラメータにはpcap_rmtauth
構造体へのポインタが使用されます。
#define RPCAP_RMTAUTH_NULL 0 // NULL認証 #define RPCAP_RMTAUTH_PWD 1 // ユーザ名・パスワード認証 struct pcap_rmtauth { int type; char *username; char *password; };
NULL認証
RPCAPサーバの起動時のスイッチで、-nを指定すると、NULL認証でRPCAPサーバの利用が可能になります。この場合、クライアントでは、pcap_rmtauth
構造体のtype
をRPCAP_RMTAUTH_NULL(0)
に指定したり、またauth
パラメータにNULLで与えることが可能です。しかし、NULL認証でRPCAPサーバを運用すると不正利用の危険があるため、このような運用はお勧めできません。
ユーザ名・パスワード認証
ユーザ名・パスワード認証を使用する場合、pcap_rmtauth
構造体のtype
にはRPCAP_RMTAUTH_PWD(1)
を指定し、username
およびpassword
にユーザ名およびパスワードの文字列をセットします。
ここで使用するユーザ名およびパスワードは、ドキュメントには詳しく解説されていませんが、「daemon.c」のコードを確認すると、daemon_AuthUserPwd()
の中でLogonUser()
が使用されているため、RPCAPサーバとなっているコンピュータにローカルログオンできるアカウントということになります。
なお参考までに、linux の場合はgetpwnam()
となっていますので、サーバのアカウントということになります。
転送パケットのフィルタ
RPCAPクライアントは、RPCAPサーバで取得したパケット情報をリアルタイムで受け取る必要があります。しかし、この転送データもTCP/IPのパケットであるため、RPCAPサーバにとってはネットワークを流れるパケットの一部です。このため、転送データもモニタして転送されてくるということになります。
ある意味、正しい動作ではありますが、通常この転送データのパケットはモニタしたい対象ではありません。そこで、RPCAPクライアントでは、この転送パケットを無視して表示するというオプションが必要になります。
サンプルプログラムでは、キャプチャ開始時に表示されるダイアログ(図2)で、[転送パケットを表示しない]にチェックすることで、この転送データを表示しなくなります。チェックが入っている時と入っていない時の表示を見比べると、このオプションの必要性を実感することと思います。
なお、このような機能は、本来無駄なトラフィックを避けるためにもRPCAPサーバ側で実装するべきものであると考えられるのですが、現在のバージョンでは、このようなオプションは存在しないようです。
クライアント・サーバ間の通信
RPCAPプロトコルにおけるクライアント・サーバ間の通信は、TCPによる制御接続と、TCP(デフォルト)またはUDPによるデータ接続の、2つの接続が存在します。
データ接続は、転送データそのものであり、制御接続はキャプチャをリモート制御(キャプチャ開始や停止など)するために使用されています。
したがって、非表示の対象となるパケットは、この2つの接続ということになります。
RPCAPでは、パッシブモード(デフォルト)と、ファイアウォールの制限などに対応するためのアクティブモードの、2つの接続モードをサポートしています。
今回は、デフォルトであるパッシブモードで接続し、TCPでデータ接続を行う場合に限定して、この2つの接続をフィルタする機能を実装してみました。他のモードにも対応するためには実装を拡張する必要があるので、注意してください。
TCPパケットでは、下記の4項目が同じ(パケットが向かう方向が逆のとき、接続元と先が入れ替わります)であれば、1つの固有接続であると判別することができます。
- 接続元のIPアドレス
- 接続元のポート番号
- 接続先のIPアドレス
- 接続先のポート番号
2つの接続(制御接続とデータ接続)の上記4項目を取得できれば、この項目と一致するパケットをフィルタすることで、転送データを非表示にするという目的を達成することが可能になります。
サーバへの接続シーケンス
RPCAPクライアントがRPCAPサーバに接続する際のシーケンスを解読すると、先の2つの接続を捕らえることができます。図6は、RPCAPクライアントがRPCAPサーバに接続する際のシーケンスを、別のパケットモニターでモニタして作成したものです。RPCAPクライアントでモニタできるのは、パケットNo.9以降であり、パケットNo.1からNo.8は見ることができません。
パケットNo.1からNo.3は、RPCAPクライアントからRPCAPサーバへの接続のための3ウエイハンドシェークです。接続が確立するとクライアントから認証情報が送られ(パケットNo.4)、サーバから認証の可否返答(パケットNo.5)が返されます。認証が成功するとクライアントからは、対象アダプタ名が送られ(パケットNo.6)、サーバからアダプタの利用可否(パケットNo.7)が返されます。続いてクライアントが接続モードに関する情報を送ると(パケットNo.8)、サーバからは、データ接続のために待機するポート番号(パケットNo.9)が返されます。
RPCAPクライアントでモニタを開始すると、このパケットNo.9が最初のパケットとして受信されます。パケットNo.9は既に確立されている制御接続であるため、このパケットから、制御接続のクライアントおよびサーバのIPアドレスとポート番号を取得できます。また、No.9パケットのTCPデータ部分には、これから接続が開始されるデータ接続のサーバ側ポート番号が検出可能です。
パケットNo.10からNo.12は、データ接続を開始する3ウエイハンドシェークですが、このNo.10パケットを見ることで、取得が必要な最後の項目であるデータ接続のクライアント側ポート番号が判明し、必要な項目の取得が完了します。なお、パケットNo.13以降がRPCAPサーバから送られてくるデータです。
RPCAPクライアントでは、フィルタのための項目を取得できるまで全てのパケットを表示対象外とし、フィルタ項目が取得できた後は、それがマッチしたものだけを表示対象外とします。
サンプルプログラムでは、このフィルタの機能をFilter
クラスに実装し、IsDrop
メソッドでフィルタ項目の取得を行っていますので参照してみてください。
フィルタの作成
TCPパケットのIPアドレス・ポート番号が、フィルタと一致しているかどうかの判断はできるだけシンプルに行いたいものです。図7を見ると分かりますが、ここで対象としている4項目は、TCPパケットの26オクテットから37オクテットに連続していることが分かります。
そこで、取得した項目を同じ連続したメモリ配置で作成し、12バイトのメモリ比較を行うことでフィルタにマッチしたかどうかの判断を行うことにします。
図5の接続例で使用されているポートは、制御接続のサーバ側が2002番、クライアント側が2595番です。また、データ接続は、それぞれ1255番と2596番です。このポート番号とIPアドレスの2接続分の(送り元・送り先が入れ替わった分も含めて合計4種類)のフィルタパターンは、図8のようになります。
フィルタにマッチしているかどうかのメモリ比較も、Filter
クラスのIsDrop
メソッドで実装されています。
まとめ
本稿では、RPCAPの利用方法とリモートキャプチャ実装上の技術について紹介しました。本稿が、わずかながらでもWinPcapを利用するプログラムの作成に、参考になれば幸いです。
参考資料
- WinPcap: the Free Packet Capture Library for Windows
- WinPcapドキュメント
- WinPcap解説資料(日本語)
- Remote Capture
- リモートキャプチャ(日本語翻訳 Ver3.0)
- MSDNライブラリ 『COM 相互運用性 - 第 1 部 : C# クライアント チュートリアル』
- CodeZine 『WinPcapを使用したパケットモニターの作成 』、古谷誠進 著、2005年7月
- CodeZine 『WinSock2を使用したパケットモニターの作成 』、古谷誠進 著、2005年7月