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し忘れた部分があったので修正しました。
