LinuxのPacketソケットでIPv6パケットをキャプチャ&送信する

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.3RFC2460 8.1を見てください。

58行目~
ICMPv6のヘッダを作っています。今回のサンプルでは(ほぼ)固定の情報を使っています。
その他参考にしたサイト
Linux のためのイーサネット・ハブもどきのプログラム
[ds6-devel] NDP

補足:前回のソースコードのパケットの表示部分で、ntohlし忘れた部分があったので修正しました。

コメントを残す

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

*