IPv6シリーズ第3弾。
前回、TAPデバイスを使ってIPv6パケットをキャプチャする方法をご紹介しましたが、今回はPacketソケットを使ってみます。
TAPデバイスの場合は、キャプチャしたパケットはそのままではその後ネットワークにもアプリケーションにも流れないのですが、Packetソケットではネットワークから受け取ったパケットをキャプチャできて、同じパケットはOS(プロトコルハンドラ)にも渡されます。(OSとか環境によると思います。)
ちなみに送信パケットはキャプチャしません。受信したものだけです。
ではさっそくソースコード。
ipv6dump2.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 <netpacket/packet.h> #include <net/ethernet.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> /* ntohs */ #include <netdb.h> /* getprotobynumber */ #include <linux/if.h> struct icmp6_message { struct ip6_hdr ip6; struct icmp6_hdr icmp6; }; struct icmp6_message sendData; unsigned short icmpv6checksum(struct in6_addr *src, struct in6_addr *dst, unsigned long dataLength, unsigned short *data) { unsigned long sum = 0; int pos; /* src */ for (pos=0;pos < sizeof(struct in6_addr)/2;pos++) { sum += ntohs(src->s6_addr16[pos]); } /* dst */ for (pos=0;pos < sizeof(struct in6_addr)/2;pos++) { sum += ntohs(dst->s6_addr16[pos]); } /* Upper-Layer Packet Length */ sum += dataLength >> 16; sum += dataLength & 0x0000FFFF; /* Next Header */ sum += IPPROTO_ICMPV6; /* ICMP */ for (pos=0; pos < dataLength/2; pos++) { sum += ntohs(data[pos]); } if (dataLength % 2) sum += *(u_int8_t *)&(data[pos]); sum = (sum & 0xffff) + (sum >> 16); /* add overflow counts */ sum = (sum & 0xffff) + (sum >> 16); /* once again */ return ~sum; } void initIcmpEcho() { /* IPv6 header */ memset(&sendData,0,sizeof(sendData)); sendData.ip6.ip6_vfc = 6 << 4 | 0; sendData.ip6.ip6_plen = htons(sizeof(sendData.icmp6)); sendData.ip6.ip6_nxt = IPPROTO_ICMPV6; sendData.ip6.ip6_hlim = 0x40; /* ICMPv6 header */ sendData.icmp6.icmp6_type = ICMP6_ECHO_REQUEST; sendData.icmp6.icmp6_code = 0; sendData.icmp6.icmp6_cksum = 0; sendData.icmp6.icmp6_id = htons(0xca35); sendData.icmp6.icmp6_seq = htons(1); } void sendIcmpEcho(int fd,struct sockaddr *sock,int socklen) { printf("sendIcmpEchon"); /* id change */ sendData.icmp6.icmp6_id = htons(ntohs(sendData.icmp6.icmp6_id) + 1); /* calculate checksum */ sendData.icmp6.icmp6_cksum = 0; sendData.icmp6.icmp6_cksum = htons(icmpv6checksum(&sendData.ip6.ip6_src,&sendData.ip6.ip6_dst,sizeof(sendData.icmp6),(unsigned short*)&(sendData.icmp6))); printf("checksum = %04xn",sendData.icmp6.icmp6_cksum); sendto(fd,(void*)&sendData,sizeof(sendData),0,sock,socklen); } void packetdump(const unsigned char *pkt, unsigned length) { int rest,i; struct ip6_hdr *pIpv6; struct protoent *pProto; rest = length; /* 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"); /* keep addresses for sending data */ sendData.ip6.ip6_src = pIpv6->ip6_dst; sendData.ip6.ip6_dst = pIpv6->ip6_src; } int main(int argc, char **argv) { char dev[IFNAMSIZ]; unsigned char buf[BUFSIZ]; fd_set fdset; struct ifreq ifr; int fd6; struct sockaddr_ll sll; int i; int ifindex; if (argc != 2) { fprintf(stderr,"Usage:%s {devicename}n",argv[0]); return 1; } strcpy(dev,argv[1]); fd6 = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)); if (fd6 < 0) { perror("socket AF_PACKET"); return 1; } /* get ifindex */ memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name,dev,IFNAMSIZ); if (ioctl(fd6, SIOGIFINDEX, &ifr) <0) { perror("SIOGIFINDEX"); close(fd6); return 1; } ifindex = ifr.ifr_ifindex; memset(&sll,0xff,sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_IPV6); sll.sll_ifindex = ifindex; if (bind(fd6,(struct sockaddr*)&sll,sizeof(sll)) == -1) { perror("bind"); close(fd6); return 1; } /* Hardware address for sending */ sll.sll_halen = IFHWADDRLEN; memset(sll.sll_addr,0xff,IFHWADDRLEN); /* flush */ do { struct timeval t; FD_ZERO(&fdset); FD_SET(fd6, &fdset); memset(&t,0,sizeof(t)); i = select(fd6+1,&fdset,NULL,NULL,&t); if (i > 0) { recv(fd6,buf,i,0); } }while(i); /* initialize ICMPv6 Echo */ initIcmpEcho(); while (1) { int ret; FD_ZERO(&fdset); FD_SET(STDIN_FILENO, &fdset); FD_SET(fd6, &fdset); ret = select(fd6 + 1, &fdset, NULL, NULL, NULL); if (ret == -1) { perror("select"); return 1; } if (FD_ISSET(fd6, &fdset)) { ret = read(fd6, 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; if (buffer[0] == 'e') { sendIcmpEcho(fd6,(struct sockaddr *)&sll,sizeof(sll)); } } } close(fd6); return 0; }
- 確認環境
- ビルド方法
- 基本的に前回と同じです。
- 実行方法
- # ./ipv6dump2 eth0
などとスーパーユーザで実行してください。
引数はネットワークデバイスの名前。そのネットワークデバイスに到着したIPv6パケットの内容が表示されます。
なお、コンソール上でe[Enter]を入力すると直前のIPv6パケットの送信元と送信先のアドレスを入れ替えてICMPv6 Echo Requestパケットを指定したネットワークデバイスから送信します。 - ソースの説明
-
- 27行目~
- IP,ICMP,UDP checksum calculationを参考にしました。ちょっと見苦しいですがICMPv6専用の関数にしました。
ICMPv6のチェックサムの計算方法は、RFC2463 2.3、RFC2460 8.1を見てください。 - 58行目~
- ICMPv6のヘッダを作っています。今回のサンプルでは(ほぼ)固定の情報を使っています。
- その他参考にしたサイト
- Linux のためのイーサネット・ハブもどきのプログラム
[ds6-devel] NDP
補足:前回のソースコードのパケットの表示部分で、ntohlし忘れた部分があったので修正しました。