libpcapを使って、icmpをキャプチャする


```cpp:a.cpp

#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を眺めて、使えればいいや、
よりも、実際にパケットの中身を眺めてみると、
なんでこういう定義になっているのか、というのが良く分かると思う。