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