パケット解析、入門してみました。〜その2〜

前回の続き。

libpcap(続き)

pcap_close

ハンドルを閉じる関数です。

宣言

void pcap_close(pcap_t *handle);

名前の通り、ハンドルhandleを閉じます。 (前回の記事に書くべきだったかも知れません)

サンプルコード

#include <stdio.h>
#include <pcap.h>

int main(int argc, char **argv) {
    char *dev = "enp3s0";
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *handle;

    handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
    if (handle == NULL) {
        fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
        return 2;
    }
    pcap_close(handle);

    return 0;
}

開いて閉じるだけ。 devに入れるデバイス名は適当に書き換えるか、前回の記事にあるようにデフォルトのデバイス名を取得してください。

pcap_next

パケットを一つ読み込んで返す関数です。

宣言

const u_char *pcap_next(pcap_t *handle, struct pcap_pkthdr *header);

ハンドルhandleからパケットを読み込んで、パケットのヘッダ情報をheaderに書き込み、パケット本体をu_char型のポインタとして返します。

pcap_pkthdr

ついでにpcap_pkthdrの宣言も。

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

tsはタイムスタンプで、lenはパケットの長さ。caplenはパケットが分割されている場合に取得したデータの長さです(たぶん)。

パケットの取得について

実際、パケットを取得するときは連続して取得したいときがほとんどなので、ループでpcap_nextを回すことになります。ですが、毎回、自前でループを書いてpcap_nextを使うのは冗長になるので、専ら次に紹介するpcap_loopという関数を使うようです。 また、pcap_loopを使うとエラー処理を自分で書かなくて済むという利点もあるようです。

pcap_loop

連続してパケットを読み込み、処理をする関数です。

宣言

int pcap_loop(pcap_t *handle, int count, pcap_handler callback, u_char *user);

handleは毎度おなじみ。countは読み込むパケット数で、負の数を入れるとエラーが起きるまで読み込みます。 callbackはコールバック関数で、この型は後述します。usercallbackに渡す第一引数で、特に必要ないときはNULLで結構です。

この関数はhandleからパケットを読み込み、そのデータをcallbackで処理するという流れを指定回数もしくはエラーが起きるまで繰り返します。

pcap_handler

それではcallbackの型pcap_handlerを見てみましょう。以下のように宣言されています。

typedef void (*pcap_handler)(u_char *args,
                             const struct pcap_pkthdr *header,
                             const u_char *packet);

引数としてargsheaderpacketを受け取り、voidを返す関数のポインタです。 argsには先程のpcap_loopに渡したuserが渡されるようです。constがついてないので色々便利に使えるかもしれません(今回は使いませんが)。 使い方は以下のサンプルコードで。

サンプルコード

#include <stdio.h>
#include <pcap.h>

void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
    printf("packet length: %d\n", header->len);
}

int main(int argc, char **argv) {
    char *dev, errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 net, mask;
    pcap_t *handle;
    struct bpf_program fp;
    char filter_exp[] = "tcp and host x.x.x.x";
    struct pcap_pkthdr header;
    const u_char *packet;

    dev = pcap_lookupdev(errbuf);
    if (dev == NULL) {
        fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
        return 2;
    }

    if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
        fprintf(stderr, "Can't get netmask for device %s: %s\n", dev, errbuf);
        return 2;
    }

    handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
    if (handle == NULL) {
        fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
        return 2;
    }

    if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
        fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
        return 2;
    }

    if (pcap_setfilter(handle, &fp) == -1) {
        fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
        return 2;
    }

    pcap_loop(handle, 20, callback, NULL);

    pcap_close(handle);

    return 0;
}

これはパケットを20個取得し、それぞれのパケットの長さを出力します(おそらくオクテット単位)。

相変わらずx.x.x.xのところのIPアドレスは適当に補ってください。艦これサーバーのIPアドレス鎮守府によって異なるらしいので。 といっても、まだパケットの解析するわけではないので艦これサーバー以外でも大丈夫です。

パケットデータ

最後に、pcap_nextpcap_handlerで扱うpacketは、データリンク層のデータになってます。 殆どの場合はEthernetパケット1でしょう。 ここの取り扱いはまた次の機会に書くと思います。