TAPデバイスでIPv6パケットキャプチャ

IPv6シリーズ第2弾(第3弾があるかどうかは未定)。
TUN/TAPを使って、IPv6の仮想デバイスを作ってそこでIPv6のパケットをキャプチャするサンプルを作ってみました。
ちなみに、単純にパケットの内容を見たい場合は、tcpdumpなどをご利用ください。
プログラムにIPv6パケットを取り込んでなんとかしたい人は参考にしてください。

ではさっそくサンプルプログラム。簡単な解説はそのあとに書きました。

ipv6dump.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>		/* O_RDWR */
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>	/* ntohs */
#include <netdb.h>		/* getprotobynumber */
#include <net/route.h>	/* struct in6_rtmsg */

#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/if_ether.h>	/* struct ethhdr */

int
tun_alloc(char *dev)
{
	struct ifreq ifr;
	int fd, err;

	if ( (fd = open("/dev/net/tun", O_RDWR)) < 0) {
		perror("open");
		return -1;
	}

	memset(&ifr, 0, sizeof(ifr));

	/* Flag: IFF_TUN   - TUN device ( no ether header )
	 *       IFF_TAP   - TAP device
	 *
	 *       IFF_NO_PI - no packet information
	 */

	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
	if (*dev)
		strncpy(ifr.ifr_name, dev, IFNAMSIZ);
	
	if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) {
		perror("TUNSETIFF");
		close(fd);
		return err;
	}
	strcpy(dev, ifr.ifr_name);
	return fd;
}

void packetdump(const unsigned char *pkt, unsigned length)
{
	struct ethhdr *pEther;
	int rest,i;
	struct ip6_hdr *pIpv6;
	struct protoent *pProto;
	
	rest = length;
	/* Ether Header */
	if (rest > sizeof(struct ethhdr)) {
		pEther = (struct ethhdr*)pkt;
		printf("--- Ether Header ---n");
		printf("MAC(dst)n  ");
		for (i=0; i<ETH_ALEN-1; ++i) {
			printf("%02x:", (unsigned char)pEther->h_dest[i]);
		}
		printf("%02xn", (unsigned char)pEther->h_dest[i]);
		printf("MAC(src)n  ");
		for (i=0; i<ETH_ALEN-1; ++i) {
			printf("%02x:", (unsigned char)pEther->h_source[i]);
		}
		printf("%02xn", (unsigned char)pEther->h_source[i]);
		printf("protocoln");
		printf("  %04xn", ntohs(pEther->h_proto));

		rest -= sizeof(struct ethhdr);
	}
	
	if ( ntohs(pEther->h_proto) != ETH_P_IPV6 ) {
		return;
	}
 	/* IPv6 */
	if (rest > sizeof(struct ip6_hdr)) {
		pIpv6 = (struct ip6_hdr*)(pkt + (length - rest));
		printf("--- IPv6 Header ---n");
		printf("Version:%xn",pIpv6->ip6_vfc >> 4);
		printf("Traffic Class:%xn",(ntohl(pIpv6->ip6_flow) >> 20) & 0xFF );
		printf("Flow Label:%05xn", ntohl(pIpv6->ip6_flow) & 0x000FFFFF );
		printf("Payload Length:%dn",ntohs(pIpv6->ip6_plen));
		if ((pProto = getprotobynumber(pIpv6->ip6_nxt)) != NULL) {
			printf("Next Header:%x(%s)n",pIpv6->ip6_nxt,pIpv6->ip6_nxt == 0 ? "HOPOPT" : pProto->p_name);
		}
		else {
			printf("Next Header:%x(unknown)n",pIpv6->ip6_nxt);
		}
		printf("Hop Limit:%xn",pIpv6->ip6_hops);
		printf("Source Addressn  ");
		for (i=0; i<sizeof(pIpv6->ip6_src.s6_addr)-1;i++) {
			printf("%02x:",pIpv6->ip6_src.s6_addr[i]);
		}
		printf("%02xn",pIpv6->ip6_src.s6_addr[i]);
		printf("Destination Addressn  ");
		for (i=0; i<sizeof(pIpv6->ip6_dst.s6_addr)-1;i++) {
			printf("%02x:",pIpv6->ip6_dst.s6_addr[i]);
		}
		printf("%02xn",pIpv6->ip6_dst.s6_addr[i]);
		
		rest -= sizeof(struct ip6_hdr);
	}

	/* payload */
	printf("--- IPv6 Payload(option header) ---");
	for (i=0;i<rest;i++) {
		if (i%16==0) {
			printf("n  ");
		}
		printf("%02x ",(unsigned char)*((char*)pkt + (length - rest) + i));
	}
	printf("nn");
}

int
main(int argc, char **argv)
{
	char dev[IFNAMSIZ];
	unsigned char buf[BUFSIZ];
	int fd;
	fd_set fdset;
	struct ifreq ifr;
	int fd6;
	struct in6_rtmsg rt;

	if (argc != 2) {
		fprintf(stderr,"Usage:%s {devicename}n",argv[0]);
		return 1;
	}
	strcpy(dev,argv[1]);
	fd = tun_alloc(dev);
	if (fd < 0) {
		return 1;
	}
	/* ifup */
	strncpy(ifr.ifr_name,dev,IFNAMSIZ);
	fd6 = socket(PF_INET6,SOCK_DGRAM,0);
	if (fd6 < 0) {
		return 1;
	}
	if (ioctl(fd6, SIOCGIFFLAGS, &ifr) != 0) {
		perror("SIOCGIFFLAGS");
		return 1;
	}
	ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
	if (ioctl(fd6, SIOCSIFFLAGS, &ifr) != 0) {
		perror("SIOCSIFFLAGS");
		return 1;
	}
	
	memset(&rt,0,sizeof(rt));
	/* get ifindex */
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name,dev,IFNAMSIZ);
	if (ioctl(fd6, SIOGIFINDEX, &ifr) <0) {
		perror("SIOGIFINDEX");
		return 1;
	}
	/* route -A inet6 add default dev {devname} */
	rt.rtmsg_flags = RTF_UP;
	rt.rtmsg_metric = 1;
	rt.rtmsg_dst_len = 0;
	rt.rtmsg_ifindex = ifr.ifr_ifindex;
	if (ioctl(fd6, SIOCADDRT, &rt) < 0) {
		perror("SIOCADDRT");
		return 1;
	}
	close(fd6);
		
	while (1) {
		int ret;
		FD_ZERO(&fdset);
		FD_SET(STDIN_FILENO, &fdset);
		FD_SET(fd, &fdset);
		ret = select(fd + 1, &fdset, NULL, NULL, NULL);
		if (ret == -1) {
			perror("select");
			return 1;
		}
		if (FD_ISSET(fd, &fdset)) {
			ret = read(fd, buf, BUFSIZ);
			if (ret == -1)
				perror("read");
			packetdump(buf, ret);
		}
		if (FD_ISSET(STDIN_FILENO, &fdset)) {
			char buffer[256];
			ret = read(STDIN_FILENO,buffer,sizeof(buffer));
			if (ret < 0 || buffer[0] == 'q')
				break;
		}
	}
	close(fd);
	return 0;
}
確認環境
Ubuntu 8.04 (kernel 2.6.24-19) gcc 4.2.3
Fedora8 (kernel 2.6.25.11) gcc 4.1.2

ビルド方法
make ipv6dumpとかgcc -o ipv6dump ipv6dump.cとかでできます。

実行方法
# ipv6dump tap%d
などとスーパーユーザで実行してください。
引数は仮想デバイスの名前。%dとかprintfライクな書式を指定してあげるとOSで勝手に番号を振ってくれます。(他の書式がどれくらい使えるのかは知りません。)
ping6 2001:db8:1::10などIPv6のパケットが発生するコマンドを別のコンソールから入力するとそのパケット(というか近隣探索のICMPv6パケット)が見られます。

動作の説明
仮想デバイスをIPv6パケットのデフォルトルートにしてしまって、そこに流れ込んできたIPv6パケットのヘッダ部分(オプション含まず)とペイロードを表示するというものです。
(他のノードと通信中のパケットをキャプチャする機能はありません。)
他にIPv6なネットワークデバイスがある場合、そちらのネットワーク向けのパケットはそちらのデバイスに行くので表示されません。また、別にデフォルトルートが存在するときの動作は未確認です。
コンソール上でq[Enter]を入力すると終了します。

ソースの説明
22行目~
TAPデバイスを作る処理です。参考URLに記載したサイトにあるものを参考にさせていただきました。
TUNデバイスで生のIPv6パケットをとれれば楽だったのですが、うまくいかなかったのでTAPデバイスにてetherヘッダから解析しています。

53行目~
Etherフレーム – IPv6パケットを解析する処理です。
linux/ipv6.hにもipv6_hdrというIPv6パケットの構造体の定義があったのですが、netinet/in.hをincludeすると定義が重複してコンパイルエラーになります。
コンパイルエラーは回避できるのですが、RFC3542ではnetinet/ip6.hに定義が書いてあることになっているので、今回はそちらの方に合わせました。
オプションヘッダは解析していません。(ちゃんと書くと大変そうだったのでやめました。。)

144行目~
IPv6のソケットでifupすると、リンクローカルアドレスが(OSによって)割り当てられるようです。
いちいち外部コマンドでupするのもあれなので、プログラムの中に書きました。

168行目~
作ったデバイスをIPv6のデフォルトルートにする処理です。これもいちいち外部コマンドで設定するのがあれだったので、プログラムの中に書きました。

参考URL
インターフェースをup状態にする
ユニバーサル TUN/TAP デバイスドライバについて
IPv6のRFC

12/15訂正:ソースコードのpIpv6->ip6_flowにntohlをつけ忘れていたので修正しました

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*