前回の続き。
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
はコールバック関数で、この型は後述します。user
はcallback
に渡す第一引数で、特に必要ないときはNULL
で結構です。
この関数はhandle
からパケットを読み込み、そのデータをcallback
で処理するという流れを指定回数もしくはエラーが起きるまで繰り返します。
pcap_handler
それではcallback
の型pcap_handler
を見てみましょう。以下のように宣言されています。
typedef void (*pcap_handler)(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
引数としてargs
、header
、packet
を受け取り、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_next
やpcap_handler
で扱うpacket
は、データリンク層のデータになってます。
殆どの場合はEthernetパケット1でしょう。
ここの取り扱いはまた次の機会に書くと思います。