レンタルサーバー/VPSのキャンペーン情報!
お見逃しなく!!

パケットキャプチャツールのつくり方【C言語】 〜UDPヘッダー解析〜

このページはこんな方におすすめです
  • 自分でパケットキャプチャツールを作ってみたい
  • パケットの構造に興味がある

前回はパケットキャプチャでIPヘッダーの表示を行いましたので、今回はUDPヘッダーの解析と表示をおこないます。

UDPヘッダーは単純な構造のためパケットキャプチャツール作成の入門に最適です。UDPヘッダーを解析できるようになればTCPやICMPも同様に解析できるようになるでしょう。

UDPヘッダーの構造については次の記事を参照してください。

TCP/IPヘッダ構造

パケットキャプチャツールのつくり方

ヘッダーファイル取り扱いについての注意

macOS、BSDとLinuxで共通のコードを記述する場合はUDPヘッダーの構造体定義が異なるためコンパイルエラーに注意する必要があります。

幸いにもLinuxにはBSD形式の構造体が用意されていて「__FAVOR_BSD」を定義することで使用することができるようになります。

具体的にどうするのかというと、以下のようにnetinet/udp.hをincludeする前に__FAVOR_BSDを定義します。

#ifndef __FAVOR_BSD
# define __FAVOR_BSD
#endif

UDPヘッダーのマッピング方法

この方法はTCPやICMPでも同様の方法です。

パケットの中でUDPヘッダーの先頭へアクセスするためにはIPヘッダーのサイズを知る必要があります。

IPヘッダーは可変長です。そのためIPヘッダーの長さを知るために ip_hl を使用します。ip_hl はIPヘッダーのサイズを4オクテット単位で表しているためip_hlの値を4倍することによってIPヘッダーのサイズを知ることができます。

今回のサンプルコードでは以下のようにしてUDPヘッダーが格納されているメモリアドレスを計算しています。

struct udphdr *udp;

udp = (struct udphdr *)((char *)ip + (ip->ip_hl<<2));

このようにip_hlを左へ2ビットシフトした値をポインタipに加算しています。

上位プロトコルの判定

IPヘッダーの上位プロトコルはIPヘッダーの ip_p を調べることによって知ることができます。

どのような値が定義されているのかについてはヘッダーファイルのnetinet/in.hを見てください。

サンプルコードでは以下のようにしてプロトコルによる処理の振り分けをおこなっています(まだUDPしか処理していません)。

switch(ip->ip_p) {
case IPPROTO_UDP:
  print_udpheader(ip);
  break;
case IPPROTO_TCP:
case IPPROTO_ICMP:
default:
  break;
}

UDPヘッダー値を表示する

UDPヘッダーの表示はIPヘッダーでのやり方と変わりません。バイトオーダーに注意して各フィールドの値を表示するだけです。

サンプルコードでは、以下のようにしています。

printf("uh_sport = %d\n", ntohs(udp->uh_sport));
printf("uh_dport = %d\n", ntohs(udp->uh_dport));
printf("uh_ulen = %d bytes\n", ntohs(udp->uh_ulen));
printf("uh_sum = 0x%.4x\n", ntohs(udp->uh_sum));

サンプルコードと実行例

サンプルコード

サンプルコードはGitHubからも落とせます。

サンプルコード

/*
 * UDPパケットキャプチャサンプルコード
 * by shj@netwiz.jp
 */

#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <netinet/ip.h>
#ifndef __FAVOR_BSD
# define __FAVOR_BSD
#endif
#include <netinet/udp.h>
#include <net/ethernet.h>
#include <arpa/inet.h>

static void
print_udpheader(struct ip *ip)
{
    struct udphdr *udp;

    udp = (struct udphdr *)((char *)ip + (ip->ip_hl<<2));
    printf("uh_sport = %d\n", ntohs(udp->uh_sport));
    printf("uh_dport = %d\n", ntohs(udp->uh_dport));
    printf("uh_ulen = %d bytes\n", ntohs(udp->uh_ulen));
    printf("uh_sum = 0x%.4x\n", ntohs(udp->uh_sum));
    printf("\n\n");
}

static void
print_ipheader(char *p)
{
    struct ip *ip;

    ip = (struct ip *)p;
    printf("ip_v = 0x%x\n", ip->ip_v);
    printf("ip_hl = 0x%x\n", ip->ip_hl);
    printf("ip_tos = 0x%.2x\n", ip->ip_tos);
    printf("ip_len = %d bytes\n", ntohs(ip->ip_len));
    printf("ip_id = 0x%.4x\n", ntohs(ip->ip_id));
    printf("ip_off = 0x%.4x\n", ntohs(ip->ip_off));
    printf("ip_ttl = 0x%.2x\n", ip->ip_ttl);
    printf("ip_p = 0x%.2x\n", ip->ip_p);
    printf("ip_sum = 0x%.4x\n", ntohs(ip->ip_sum));
    printf("ip_src = %s\n", inet_ntoa(ip->ip_src));
    printf("ip_dst = %s\n", inet_ntoa(ip->ip_dst));
    printf("\n");

    switch(ip->ip_p) {
    case IPPROTO_UDP:
        print_udpheader(ip);
        break;
    case IPPROTO_TCP:
    case IPPROTO_ICMP:
    default:
        break;
    }
}

static void
usage(char *prog)
{

    fprintf(stderr, "Usage: %s <device> <packet filter>\n", prog);
    exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
    pcap_t *handle;
    const unsigned char *packet;
    char *dev, *filter;
    char errbuf[PCAP_ERRBUF_SIZE];
    struct pcap_pkthdr header;
    struct bpf_program fp;
    bpf_u_int32 net;

    if ((dev = argv[1]) == NULL)
        usage(argv[0]);

    if ((filter = argv[2]) == NULL)
        usage(argv[0]);

    /* 受信用のデバイスを開く */
    if ((handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf)) == NULL) {
        fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
        exit(EXIT_FAILURE);
    }
    /* イーサネットのみ */
    if (pcap_datalink(handle) != DLT_EN10MB) {
        fprintf(stderr, "Device not support: %s\n", dev);
        exit(EXIT_FAILURE);
    }    

    /* パケットフィルター設定 */
    if (pcap_compile(handle, &fp, filter, 0, net) == -1) {
        fprintf(stderr, "Couldn't parse filter: %s\n", pcap_geterr(handle));
        return -1;
    }
    if (pcap_setfilter(handle, &fp) == -1) {
        fprintf(stderr, "Couldn't install filter: %s\n", pcap_geterr(handle));
        return -1;
    }

    /* ループでパケットを受信 */
    while (1) {
        if ((packet = pcap_next(handle, &header)) == NULL)
            continue;

        /* イーサネットヘッダーとIPヘッダーの合計サイズに満たなければ無視 */
        if (header.len < sizeof(struct ether_header)+sizeof(struct ip))
            continue;
        print_ipheader((char *)(packet+sizeof(struct ether_header)));
    }

    /* ここに到達することはない */
    pcap_close(handle);
    return 0;
}

コンパイルは以下のようにします。libpcapとリンクするために”-l pcap”が必要であることに注意してください。

$ cc packet_capture_udp.c -lpcap
$ sudo ./a.out en1 'udp and port 53'
Password:
ip_v = 0x4
ip_hl = 0x5
ip_tos = 0x00
ip_len = 57 bytes
ip_id = 0xc54e
ip_off = 0x0000
ip_ttl = 0x40
ip_p = 0x11
ip_sum = 0xe2a9
ip_src = 192.168.2.4
ip_dst = 8.8.8.8

uh_sport = 62290
uh_dport = 53
uh_ulen = 37 bytes
uh_sum = 0x7c27

今回のサンプルコードはパケットフィルターを引数で指定できるようにしているので、実行時に「udp and port 53」を引数として渡すことで、DNSパケットのみ受信しています。

まとめ

UDPヘッダーは単純なため、UDPヘッダーを表示させられるようになればTCPやICMPも簡単に対応できますから興味のある方は是非UDPヘッダーを自在に表示できるようになってください。

更に詳細な知識を得たい場合は「UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI・ソケットとXTI」がおすすめです。

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)