IPv6ネットワークプログラミング ICMPV6編
ICMPV6を使ったプログラムを作ります。
作るものは以下の記事に書いたようなtracerouteをIPV6で書き直したものです。
IPv4とIPv6は仕様的に異なるところがそこそこあります。
そのためプログラムの書き方にも相違点がありIPv4で通用していた書き方ではどうにもならない部分もあります。
実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
#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パケットを送受信するためのソケットを作成します。
1 |
socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) |
IPv4ではここでIPヘッダーを自分で作成したい場合、setsockoptでIP_HDRINCLを指定することによってそれを実現できます。
しかしIPv6ではIP_HDRINCLを指定できませんでした。
そもそも「IPv6ヘッダーを自分で作る」ということができないようになっているみたいです。
さらに上のようにソケットを作成しても受信するのはIPv6ヘッダーではなくICMPV6パケットです。
tracerouteはHop limit値を更新しながらパケットの送信を繰り返していくわけですが、
IPv6ヘッダーを自分作ることができないとなるとどうすればいいのか。
そこでsetsockoptを使います。
1 2 |
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を使用することができます。
1 2 3 |
struct sockaddr_in6 sock_in; sock_in.sin6_family = AF_INET6; inet_pton(AF_INET6, ip6address, &sock_in.sin6_addr); |
実行結果
実行するとこのようになります。
1 2 3 4 |
$ 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との相違点を中心に触れていきたいと思います。