訳あって、所定の条件を満たすパケットを監視したい事情がでてきました。で、パケット監視のためのライブラリといえばpcapということで、pcapを使うためのライブラリlibpcapについて調べてみました。
(といっても、ほぼググっただけなのですが)
環境はいつものLinux Mint 20です。build-essentialなどの普通のコンパイル環境はすでに入っているものとします。
基本的な使い方は、Programming with pcapに記載されているのですが、すでに日本語で書いていただいている方もいらっしゃるのでそちらも参考にしながら動かしてみました。参考にさせていただいたのは、
http://www.cgitest.net/memo/wiki.cgi?page=libpcap
の記事です。
環境構築
コンパイルする際にはlibpcap-devパッケージが必要なので、
$ sudo apt-get install libpcap-dev
でインストールしておきます。
ループの中でパケットを取得する
ソースコードは以下です。
#include <pcap.h>
#include <stdlib.h>
void dump(const unsigned char *data_buffer, const unsigned int length) {
unsigned char byte;
unsigned int i, j;
for(i = 0; i < length; i++) {
byte = data_buffer[i];
printf(" %02x", data_buffer[i]);
if((i % 16 == 15) || (i == length-1)) {
for(j = 0; j < 15-(i%16); j++) {
printf(" ");
}
printf(" ");
for(j = (i - (i%16)); j <= i; j++) {
byte = data_buffer[j];
if((byte > 31) && (byte < 127))
printf("%c", byte);
else
printf(".");
}
printf("\n");
}
}
}
int main() {
struct pcap_pkthdr header;
const u_char *packet;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp; /* コンパイル済みフィルタ用構造体 */
char filter_exp[] = "port 80"; /* フィルタ表記用文字配列 */
bpf_u_int32 mask; /* 監視ディバイスのネットマスク */
bpf_u_int32 net; /* 監視ディバイスのIPアドレス */
char *device;
pcap_t *pcap_handle;
int i;
device = pcap_lookupdev(errbuf);
if (device == NULL) {
printf("error\n");
return 1;
}
printf("device = %s\n", device);
if (pcap_lookupnet(device, &net, &mask, errbuf) == -1) {
fprintf(stderr, "デバイスのネットマスクを取得できませんでした:%s\n", device);
net = 0;
mask = 0;
}
pcap_handle = pcap_open_live(device, 4096, 1, 0, errbuf);
if (pcap_handle == NULL) {
fprintf(stderr, "デバイス「%s」を開けませんでした: %s\n", device, errbuf);
return 1;
}
if (pcap_compile(pcap_handle, &fp, filter_exp, 0, net) == -1) {
fprintf(stderr, "フィルタ「%s」の解析ができませんでした: %s\n", filter_exp, pcap_geterr(pcap_handle));
exit(1);
}
if (pcap_setfilter(pcap_handle, &fp) == -1) {
fprintf(stderr, "フィルタ「%s」の組み込みができませんでした: %s\n", filter_exp, pcap_geterr(pcap_handle));
exit(1);
}
for (int i = 0; i < 3; i++) {
packet = pcap_next(pcap_handle, &header);
printf("packet size = %d\n", header.len);
dump(packet, header.len);
}
pcap_close(pcap_handle);
return 0;
}
簡単に説明すると、
- pcap_lookupdev()で対象デバイスをサーチ
- pcap_open_live()で対象デバイスをオープン
- pcap_compile()とpcap_setfilter()で監視対象のフィルタを設定
- pcap_next()で対象パケットを取得
しています。ソースが準備できたら、これをコンパイルします。
$ gcc pcap.c -o ~/tmp/pcap -lpcap
共有ディレクトリ上にソースを置いているので、バイナリはローカルディレクトリを指定して出力します。このままだと、ルート権限でないとキャプチャできないので、
$ sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' ~/tmp/pcap
としてパケットキャプチャの権限をバイナリに付与して、一般ユーザーでもキャプチャができるようにします。これで、
$ ~/tmp/pcap2
としてから、httpで記述されたサイトにアクセスすると3パケット分のヘッダの部分がダンプされます。
コールバック関数でパケットを取得する
コールバック関数を使ったサンプルがProgramming with pcapの最後の方に用意されています。このサンプルのmain()関数の中の
char filter_exp[] = "ip"; /* filter expression [3] */ int num_packets = 10; /* number of packets to capture */
を修正すると、フィルタの内容や取得するパケットの数を変更することができます。
このサンプルでは、パケットキャプチャはpcap_loop()で行いますが、キャプチャしたパケットはコールバック関数got_packet()で渡されます。
これも同様に
$ gcc sniffex.c -o ~/tmp/sniffex -lpcap $ sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' ~/tmp/sniffex $ ~/tmp/sniffex
としてコンパイル・実行することができました。これを起点にほしいパケットだけをキャプチャして処理することができそうです。
おまけ
サンプルをコンパイルしてから気づいたのですが、Wikipediaのpcapの項目にはlibpcapを利用しているアプリケーションの一覧があり、この中には使えそうなツールもありそうです。先に気づけばよかった・・・・かもしれません。
device = pcap_lookupdev(errbuf);
がobsoluteで、今は
if (pcap_findalldevs(&devs, errbuf) next) {
if (dp->flags & PCAP_IF_LOOPBACK) continue;
if ((dp->flags & PCAP_IF_UP) == 0) continue;
if ((dp->flags & PCAP_IF_RUNNING) == 0) continue;
printf(“device = %s\n”, dp->name);
}
みたいにやるようです