libpcap で、保存されたキャプチャファイルを解析する方法を調べたのでメモ。

おおまかなソース

#include <pcap.h>

const u_char *pkt;
char pcap_errbuf[PCAP_ERRBUF_SIZE];
const char *pcap_path = "...";
pcap_t *pcap = pcap_open_offline_with_tstamp_precision(pcap_path,
        PCAP_TSTAMP_PRECISION_NANO, errbuf);
if (pcap == NULL) {
    fprintf(stderr, "pcap_open: error %s
", errbuf);
    exit(EXIT_FAILURE);
}

struct pcap_pkthdr pkthdr;
while ((pkt = pcap_next(pcap, &pkthdr))) {
}
pcap_close(pcap);

おおまかな流れ

  1. pcap_open または、その亜種でパケットファイルをオープンする。 パケットファイルのハンドルとして、 pcap_t が割り当てられる。

  2. パケットを処理するには、以下の選択肢がある。

    • pcap_next() でパケットを取り出し、処理する。
    • pcap_offline_read() でコールバックを指定して、libpcap内部でループさせる。user引数で任意のユーザデータを渡せる。

    pcap_next() は内部的に pcap_dispatch() を1度づつ呼び出しているので、pcap_offline_read() のほうが 効率がよさそう。ただ、コールバックベースでプログラム書くのは面倒であれば、 pcap_next() でもOK。

  3. pcap_close() でハンドルを閉じる。

pcap_next を使った場合の説明

pcapファイルのオープン

pcap_t *pcap_open_offline_with_tstamp_precision(...) で pcapファイルをオープンする。 上のサンプルでは、ナノ秒精度のタイムスタンプを保持したキャプチャファイルを読み込むので、 PCAP_TSTAMP_PRECISION_NANO を指定した。

エラー時には、NULL が返り、errbufにエラーメッセージが格納される。

パケットの読み込み

const u_char *pcap_next(pcap_t *, struct pcap_pkthdr *) でパケットを読み取る。

pcap_pkthdr にはパケットのキャプチャ時刻とパケット長が設定され、関数の戻り値にパケットのバイト列へのポインタが格納される。

struct pcap_pkthdr {
    struct timeval ts;
    bpf_u_int32 caplen;
    bpf_u_int32 len;

先頭部分は、Ethernetのフレームなので自力でパースして、IPのヘッダの先頭のポインタを計算する。 具体的な方法はTBW

(VLANなどの対応が面倒だったけど割愛。この辺面倒みてくれるライブラリはきっとあるはず・・・)

あとは、netinet/ip.hnetinet/tcp.hnetinet/udp.h の定義を元に、パースする。

pcapファイルのクローズ

pcap_close(pcap_t*) で pcap ファイルを閉じる。

フィルタの利用

tcpdumpのフィルタ式を利用できる。

フィルタ式のコンパイル

  1. struct bpf_program の変数を宣言する。
  2. pcap_compile(pcap_t *, struct bpf_program*, int, int) を呼び出す。(pcapはpcap_open済みのもの) 第2引数は最適化するかどうか、第3引数はネットマスク(キャプチャファイルの場合は 0 でも問題ないようだが・・・・)

方法1:フィルタ式の設定

pcap_setfiler(pcap_t *, struct bpf_program*) でフィルタを設定する。

後続の pcap_next の呼び出しは、フィルタにマッチするパケットだけを返す。

方法2:フィルタ式の適用

pcap_setfilter を呼び出すかわりに、pcap_next が返したパケットがフィルタ式を満たすかどうかを、 ユーザーコードで確認する。

pcap_offline_filter(struct bpf_program*, struct pcap_pkthdr*, const u_char* pkt) を呼び出す。pktにはpcap_nextの戻り値を指定する。フィルタに適合する場合は、非ゼロの値を返す。

tcpdump での vlan利用時の注意点(IEEE802.1Q)

フィルタ式にport n みたいな指定をしても、VLANタギングされているパケットがマッチしないので、 vlan and (port n) のように指定すればいい。 (tcpdump -d でパケットマッチングコードを見る限り、vlan 指定しない場合は、12-13バイト目がIPv6でもIPv4かどうかだけがチェックされるのでマッチしない)

もし、VLANタグがついているパケットと、ついていないパケットの両方にフィルタ式 expr を適用したい場合は、 "expr or (vlan and expr)" のように指定する。