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をつけ忘れていたので修正しました
