IPv6ネットワークプログラミング ICMPV6編
ICMPV6を使ったプログラムを作ります。
作るものは以下の記事に書いたようなtracerouteをIPV6で書き直したものです。
IPv4とIPv6は仕様的に異なるところがそこそこあります。
そのためプログラムの書き方にも相違点がありIPv4で通用していた書き方ではどうにもならない部分もあります。
実装
#include <sys/socket.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
enum {
COMMAND, DST
};
int extract(struct icmp6_hdr *icmp6);
uint16_t checksum(uint16_t *data, int size);
int make_socket();
void send_recv_packet(int soc, char *dst);
uint16_t checksum(uint16_t *data, int size) {
uint32_t sum = 0;
for (;size > 1; size -= 2) {
sum += *data++;
if (sum & 0x80000000) {
sum = (sum >> 16) + (sum & 0xffff);
}
}
if (size == 1) {
uint8_t odd;
odd = *(uint8_t *)data;
sum += odd;
}
while(sum >> 16) {
sum = (sum >> 16) + (sum & 0xffff);
}
return ~sum;
}
// パケット送受信
void send_recv_packet(int soc, char *dst) {
// 受信パケット解析用
struct icmp6_hdr *icmp6, send_icmp6;
struct sockaddr_in6 sock_in;
memset(&sock_in, 0, sizeof(sock_in));
memset(&send_icmp6, 0, sizeof(send_icmp6));
send_icmp6.icmp6_type = ICMP6_ECHO_REQUEST;
send_icmp6.icmp6_code = 0;
send_icmp6.icmp6_cksum = 0;
send_icmp6.icmp6_id = htons(666);
send_icmp6.icmp6_seq = htons(888);
// アドレス情報
sock_in.sin6_family = AF_INET6;
inet_pton(AF_INET6, dst, &sock_in.sin6_addr);
int reach = 0;
// 一応255まで
for (int i=1; i<256; i++) {
char res[512], *p;
// 3回
for (int j=0; j<3; j++) {
fd_set ready;
struct timeval tv;
FD_ZERO(&ready);
FD_SET(soc, &ready);
tv.tv_sec = 3;
tv.tv_usec = 0;
int hop = i;
if (setsockopt(soc, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop, sizeof(hop)) == -1) {
perror("setsockopt");
}
int r = sendto(
soc,
(char *)&send_icmp6,
sizeof(send_icmp6),
0,
(struct sockaddr *)&sock_in,
sizeof(sock_in)
);
if (r == -1) {
perror("sendto");
return;
}
int sel_res = select( (soc+1), (fd_set *)&ready, NULL, NULL, &tv);
if (sel_res == 0 ) {
break;
} else if (sel_res == -1) {
break;
} else {
if (FD_ISSET(soc, &ready)) {
struct sockaddr_in6 recv_soc;
socklen_t from = sizeof(recv_soc);
if (recvfrom(soc, res, sizeof(res), 0, (struct sockaddr *)&recv_soc, &from) == -1) {
perror("recvfrom");
}
p = res;
char ip6_addr[128];
icmp6 = (struct icmp6_hdr *)p;
int result = extract(icmp6);
inet_ntop(AF_INET6, &recv_soc.sin6_addr, ip6_addr, sizeof(ip6_addr));
if (result == 1) {
printf("- Reach Hop Limit %d from %s\n",i, ip6_addr);
reach = 1;
break;
} else if (result == 0) {
printf("- Hop Limit %d from %s\n",i, ip6_addr);
break;
}
}
}
}
if (reach == 1) {
break;
}
}
}
int extract(struct icmp6_hdr *icmp6) {
switch(icmp6->icmp6_type) {
case ICMP6_ECHO_REPLY:
return 1;
break;
case ICMP6_TIME_EXCEEDED:
if (icmp6->icmp6_code == ICMP6_TIME_EXCEED_TRANSIT) {
return 0;
}
break;
default:
break;
printf("%d\n", icmp6->icmp6_type);
}
return 2;
}
// ICMPV6 ソケット作成
int make_socket() {
int soc;
if ( (soc = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1) {
perror("socket");
return -1;
}
return soc;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Arg [Destination IPV6 Address]\n");
exit(1);
}
int soc;
if ((soc = make_socket()) == -1) {
printf("[*] Error in make_socket\n");
exit(1);
}
send_recv_packet(soc, argv[DST]);
return 0;
}
IPv4との相違点
IPv4でプログラミングしていた時との相違点を可能な限りまとめます。
ソケット
ICMPV6パケットを送受信するためのソケットを作成します。
socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)
IPv4ではここでIPヘッダーを自分で作成したい場合、setsockoptでIP_HDRINCLを指定することによってそれを実現できます。
しかしIPv6ではIP_HDRINCLを指定できませんでした。
そもそも「IPv6ヘッダーを自分で作る」ということができないようになっているみたいです。
さらに上のようにソケットを作成しても受信するのはIPv6ヘッダーではなくICMPV6パケットです。
tracerouteはHop limit値を更新しながらパケットの送信を繰り返していくわけですが、
IPv6ヘッダーを自分作ることができないとなるとどうすればいいのか。
そこでsetsockoptを使います。
int hop = 1;
setsockopt(soc, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop, sizeof(hop));
このようにHop limitを指定することができます。
アドレス情報構造体
IPv4ではパケットを送信する際にsockaddr_in構造体にアドレスファミリと宛先IPアドレスを指定してsendtoに構造体を渡します。
IPv6でもこれは同じです。
構造体がsockaddr_in6に変わっただけです。
アドレスファミリに指定するのはAF_INET6です。
アドレスの文字列からの変換はIpv4の場合と同じようにinet_ptonを使用することができます。
struct sockaddr_in6 sock_in;
sock_in.sin6_family = AF_INET6;
inet_pton(AF_INET6, ip6address, &sock_in.sin6_addr);
実行結果
実行するとこのようになります。
$ sudo ./RouteScanV6 2001:db8:0:3:f8b9:c0da:6aa8:e5c9
- Hop Limit 1 from 2001:db8:0:1::1
- Hop Limit 2 from 2001:db8:0:2::2
- Reach Hop Limit 3 from 2001:db8:0:3:f8b9:c0da:6aa8:e5c9
IPv6はまだまだ底がありそうなのでIPv4との相違点を中心に触れていきたいと思います。