# include <sys/wait.h>
# include <unistd.h>
# include <stdio.h>
# include <string.h>
# include <arpa/inet.h>
# include <pcap.h>
# define ether_header_length 14
# define ipv6_header_length 40
// pcap_breakloopをシグナルハンドラで呼ぶときに、
// pcap_tを渡す必要があるので、グローバルで宣言しておく。
pcap_t *Pcap = nullptr;
bool Debug = false;
struct ether_header {
u_char dst_mac[6];
u_char src_mac[6];
uint16_t type;
};
struct ipv4_header {
u_char ip_ver_len;
u_char service_type;
uint16_t packet_len;
uint16_t id;
uint16_t fragment;
u_char ttl;
u_char protocol;
uint16_t header_checksum;
u_char src_ip[4];
u_char dst_ip[4];
};
struct ipv6_header {
u_char prefix[4]; // バージョン, トラフィッククラス, フローラベルからなる。特に見なくても。。。
uint16_t payload_length;
u_char next_header; // 拡張ヘッダ部分のプロトコル番号が入っている、はず。icmpv6であれば58番なので、0x3aとなっている、はず。
u_char hop_limit;
u_char src_ip[16];
u_char dst_ip[16];
};
struct icmp_message {
u_char type;
u_char code;
uint16_t checksum;
uint16_t id;
uint16_t seq;
// 残りは何かしらデータが入っているけど、特に使わない。。
};
static void pcap_finish (int) {
if (Pcap) {
pcap_breakloop(Pcap);
}
}
void set_sigaction_pcap(int sig) {
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = pcap_finish;
sigemptyset(&sa.sa_mask);
sigaction(sig, &sa, nullptr);
}
void print_packet(const struct pcap_pkthdr *pkthdr, const unsigned char *packet) {
printf("--------------------------------------------\n");
printf("Received Packet Size: %d\n", pkthdr->len);
printf("Payload:\n");
for (int i = 0, len = static_cast<int>(pkthdr->len), return_count = 0; i < len; ++i) {
printf("%02x ", packet[i]);
if (++return_count == 16) {
printf("\n");
return_count = 0;
}
}
printf("\n");
}
void receive_packet(
unsigned char *,
const struct pcap_pkthdr *pkthdr,
const unsigned char *packet)
{
if (Debug) {
print_packet(pkthdr, packet);
}
const void *ether_header_point = packet;
const struct ether_header *eth = static_cast<const struct ether_header*>(ether_header_point);
if (ntohs(eth->type) == 0x0800) {
// ipv4
const void *ipv4_header_point = packet + ether_header_length; // ether_header分ずらす。
const struct ipv4_header *ipv4 = static_cast<const struct ipv4_header*>(ipv4_header_point) ;
// IPv4ヘッダは、最後に可変長の値を持つため、
// IPv4ヘッダの長さから、データ部の開始位置を知る必要がある。
// ip_ver_lenの、1バイト中の上位4ビットはipのバージョン。下位4ビットがヘッダー長。
// ヘッダー長の値は、4オクテット単位の値。つまりバイト数にするには、得られた値を4倍する必要がある。
int ip_header_length = (ipv4->ip_ver_len & 0x0F) * 4;
const void *icmp_message_point = packet + ether_header_length + ip_header_length;
const struct icmp_message *icmp = static_cast<const struct icmp_message*>(icmp_message_point);
printf(
"dst_mac=%02x:%02x:%02x:%02x:%02x:%02x, src_mac=%02x:%02x:%02x:%02x:%02x:%02x, dst_ip=%d.%d.%d.%d, src_ip=%d.%d.%d.%d, icmp_type=%s, icmp_id=%d, icmp_seq=%d\n",
eth->dst_mac[0],
eth->dst_mac[1],
eth->dst_mac[2],
eth->dst_mac[3],
eth->dst_mac[4],
eth->dst_mac[5],
eth->src_mac[0],
eth->src_mac[1],
eth->src_mac[2],
eth->src_mac[3],
eth->src_mac[4],
eth->src_mac[5],
ipv4->dst_ip[0],
ipv4->dst_ip[1],
ipv4->dst_ip[2],
ipv4->dst_ip[3],
ipv4->src_ip[0],
ipv4->src_ip[1],
ipv4->src_ip[2],
ipv4->src_ip[3],
icmp->type == 0x08 ? "Echo Request" : icmp->type == 0x00 ? "Echo Reply" : "other",
ntohs(icmp->id),
ntohs(icmp->seq)
);
} else if (ntohs(eth->type) == 0x86dd) {
// ipv6
const void *ipv6_header_point = packet + ether_header_length; // ether_header分ずらす。
const struct ipv6_header *ipv6 = static_cast<const struct ipv6_header*>(ipv6_header_point) ;
// ipv6の場合、基本ヘッダー長は、40byte固定となる、はず。
const void *icmp_message_point = packet + ether_header_length + ipv6_header_length;
const struct icmp_message *icmp = static_cast<const struct icmp_message*>(icmp_message_point);
printf(
"dst_mac=%02x:%02x:%02x:%02x:%02x:%02x, src_mac=%02x:%02x:%02x:%02x:%02x:%02x, dst_ip=%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x, src_ip=%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x, icmp_type=%s, icmp_id=%d, icmp_seq=%d\n",
eth->dst_mac[0],
eth->dst_mac[1],
eth->dst_mac[2],
eth->dst_mac[3],
eth->dst_mac[4],
eth->dst_mac[5],
eth->src_mac[0],
eth->src_mac[1],
eth->src_mac[2],
eth->src_mac[3],
eth->src_mac[4],
eth->src_mac[5],
ipv6->dst_ip[0],
ipv6->dst_ip[1],
ipv6->dst_ip[2],
ipv6->dst_ip[3],
ipv6->dst_ip[4],
ipv6->dst_ip[5],
ipv6->dst_ip[6],
ipv6->dst_ip[7],
ipv6->dst_ip[8],
ipv6->dst_ip[9],
ipv6->dst_ip[10],
ipv6->dst_ip[11],
ipv6->dst_ip[12],
ipv6->dst_ip[13],
ipv6->dst_ip[14],
ipv6->dst_ip[15],
ipv6->src_ip[0],
ipv6->src_ip[1],
ipv6->src_ip[2],
ipv6->src_ip[3],
ipv6->src_ip[4],
ipv6->src_ip[5],
ipv6->src_ip[6],
ipv6->src_ip[7],
ipv6->src_ip[8],
ipv6->src_ip[9],
ipv6->src_ip[10],
ipv6->src_ip[11],
ipv6->src_ip[12],
ipv6->src_ip[13],
ipv6->src_ip[14],
ipv6->src_ip[15],
icmp->type == 0x80 ? "Echo Request" : icmp->type == 0x81 ? "Echo Reply" : "other",
ntohs(icmp->id),
ntohs(icmp->seq)
);
} else {
// other
printf("other ehter type.\n");
}
}
int main(int argc, char **argv) {
struct bpf_program fp;
char errbuf[PCAP_ERRBUF_SIZE];
int to_ms = 30000; // とりあえず30秒までは、パケットの到着を待つ。
char interface[256] = "eth0";
if (argc >= 2) {
// 不正な文字列を入れた場合は、pcap_open_liveでエラーになるだろう。
strcpy(interface, argv[1]);
}
if (argc >= 3 && !strcmp(argv[2], "1")) {
Debug = true;
}
Pcap = pcap_open_live(interface, 2048, 1, to_ms, errbuf);
if (Pcap == nullptr) {
fprintf(stderr, "Could not open device. interface=%s, error=%s\n", interface, errbuf);
return 1;
}
// icmpをキャプチャする。
char filter[256] = "";
sprintf(filter, "icmp or icmp6");
if (pcap_compile(Pcap, &fp, filter, 0, PCAP_NETMASK_UNKNOWN) == -1) {
fprintf(stderr, "Could not parse filter. interface=%s, error=%s\n", interface, pcap_geterr(Pcap));
pcap_close(Pcap);
return 1;
}
if (pcap_setfilter(Pcap, &fp) == -1) {
fprintf(stderr, "Could not install filter. interface=%s, error=%s\n", interface, pcap_geterr(Pcap));
pcap_freecode(&fp);
pcap_close(Pcap);
return 1;
}
// シグナルハンドラ準備。
// pcap_breakloopを呼ぶために。
set_sigaction_pcap(SIGHUP);
set_sigaction_pcap(SIGQUIT);
set_sigaction_pcap(SIGINT);
set_sigaction_pcap(SIGTERM);
fprintf(stderr, "pcap_loop start. interface=%s\n", interface);
pcap_loop(Pcap, 0, receive_packet, nullptr);
fprintf(stderr, "pcap_loop end. interface=%s\n", interface);
pcap_freecode(&fp);
pcap_close(Pcap);
return 0;
}
const unsigned char *packetに、キャプチャしたバイナリデータが入ってくるので、
それを、自分の扱いやすいように扱ってあげれば良い。
自分の扱いやすい形で構造体を書いて、その構造体にデータを沿わせて扱うでもいいし、
愚直に、ポインタを1バイトずつ、ずらして扱っても良い。
必ずしも、誰かが用意した何かしらの構造体を使わなければならない、という話では無いので、
単なるバイナリデータをいじるんだって思えば、割と理解が早くなると思う。
前提条件として、Ethernet Ⅱフレーム、IPヘッダー、ICMPメッセージの、
それぞれのフォーマットを知っておくか、ちゃちゃっとググって理解できるだけの
知識は必要になる。
IPv6、ICMPv6もキャプチャできるようにした。
構造体の中で、2バイトの要素は、uint16_tにした。
uint16_tを扱う場合、ネットワークバイトオーダーを変換する必要がある。
ntohsでホストバイトオーダーにする。
printfしている部分はシンプルイズベスト(見づらい)
そして自分で構造体を作ってみたあと、
netinet/if_ether.h
とか眺めてみると、大体あってた。という具合に。
最初からnetinet/if_ether.hを眺めて、使えればいいや、
よりも、実際にパケットの中身を眺めてみると、
なんでこういう定義になっているのか、というのが良く分かると思う。