6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

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

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?