今回はICMPパケットに絞ってみていこうと思います。
ICMPはタイプとコードの値を持っています。タイプによっては複数のコードを持つものもあります。なので、タイプやコードの値を表示するだけなら、icmp構造体のメンバにアクセスすらだけで済みます。またicmp構造体はLinuxの場合、/usr/include/netinet/ip_icmp.hに定義されています。
今回はタイプやコードの値に応じて、その値が何を示すのか、名前みたいなものまで表示してみます。オプションのIDやSequesnce Numberまでは表示していません。
やっていることはシンプルで、指定したインターフェースを通るパケットの中身を確認します。まずはethernetヘッダのプロトコルタイプを見ます。もしそれが、IPなら次はIPヘッダのプロトコルを見ます。そして、それがICMPであれば、ICMPパケットを表示します。ICMP構造体で各要素が定義されているので、メンバアクセスをして、標準出力していきます。あとは、タイプやコードの名前の格納先アドレスを示すポインタ配列を自分で用意して、表示していくだけです。
ICMPパケットをキャプチャするコードはこちらです。
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 |
#include<stdio.h> #include<string.h> #include<sys/ioctl.h> #include<sys/socket.h> #include<net/if.h> #include<net/ethernet.h> #include<netpacket/packet.h> #include<arpa/inet.h> #include<linux/if.h> #include<unistd.h> #include<netinet/ip.h> #include<netinet/ip_icmp.h> /*ICMPパケットの中身を表示する関数*/ void icmp_packet(struct icmp *icmp) { /*機能名を格納したポインタ配列*/ char *type[] = { "ECHO REPLY", "undefined", "undefined", "DESTINATION UNREACHABLE", "SOURCE QUENCH", "REDIRECT", "undefined", "undefined", "ECHO REQUEST", "ROUTER ADVERTISMENT", "ROUTER SELECTION", "TIME_EXCEEDED", "PARAMETER PROBLEM", "TIMESTAMP REQUEST", "TIMESTAMP REPLY", "INFORMATION REQUEST", "INFORMATION REPLY", "ADDRESS MASK REQUEST", "ADDRESS MASK REPLY" }; /*タイプ3のコード名を格納したポインタ配列*/ char *type3_code[] = { "Network unreachable", "Host unreachable", "Protocol unreachable", "Port unreachable", "Fragmenttation needed anf DF set", "Source route falied", "Destination network unknown", "Destination host unknown", "Source host isolated", "Destination network administratively prohibited", "Destination host administratively prohibited", "Network unreachable for TOS", "Communication administratively prohibited by filtering", "Host precedence violation", "Precedence cutoff in effect" }; /*タイプ5のコード名を格納したポインタ配列*/ char *type5_code[] = { "Redirect for network", "Redirect for host", "Redirect for TOS and network", "Redirect for TOS and host" }; /*タイプ11のコード名を格納したポインタ配列*/ char *type11_code[] = { "TTL exceedded in transit", "Fragment reassenbly time exceeded" }; /*タイプ12のコード名を格納したポインタ配列*/ char *type12_code[] = { "Pointer indicates the error", "Missing a reqired option", "Bad length" }; /*ICMPパケットを表示*/ printf("-------icmp-------\n"); printf("type = %u ", icmp->icmp_type);//タイプ if (icmp->icmp_type <=18) printf("(%s)\n", type[icmp->icmp_type]); else printf("(undefined)\n");//タイプ19以上はポインタ配列に格納していない printf("code = %u ", icmp->icmp_code);//コード switch (icmp->icmp_type){ case 3: if(icmp->icmp_code <= 15){printf("(%s)", type3_code[icmp->icmp_code]);} else{printf("(undefined)\n");}//タイプ15のコード16以上は未定義 break; case 5: if(icmp->icmp_code <= 3){printf("(%s)", type5_code[icmp->icmp_code]);} else{printf("(undefined)\n");}//タイプ3のコード4以上は未定義 break; case 11: if(icmp->icmp_code <= 1){printf("(%s)", type11_code[icmp->icmp_code]);} else{printf("(undefined)\n");}//タイプ11のコード2以上は未定義 break; case 12: if(icmp->icmp_code <= 2){printf("(%s)", type12_code[icmp->icmp_code]);} else{printf("(undefined)\n");}//タイプ12のコード3以上は未定義 break; default: break; } printf("\nchecksum = %u\n", ntohs(icmp->icmp_cksum));//チェックサム } int main(void) { struct sockaddr_ll sa; struct ifreq ifreq; u_char data[1500]; u_char *buf; int len; int s=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if(s<0) perror("socket"); memset(&ifreq, 0, sizeof(struct ifreq)); strncpy(ifreq.ifr_name, "enp0s8", sizeof(ifreq.ifr_name)-1); if(ioctl(s, SIOCGIFINDEX, &ifreq)!=0) perror("ioctl"); sa.sll_ifindex = ifreq.ifr_ifindex; sa.sll_family = PF_PACKET; sa.sll_protocol = htons(ETH_P_ALL); if(bind(s, (struct sockaddr *)&sa, sizeof(sa))!=0) perror("bind"); while(1){ if((len=recv(s, data, sizeof(data), 0))<=0) perror("recv"); else{ buf = data; printf("\nData Size =%d\n", len); struct ether_header *eh = (struct ether_header *)data; buf += sizeof(struct ether_header); if(ntohs(eh->ether_type)==ETHERTYPE_IP){ struct iphdr *ip = (struct iphdr *)buf; buf += sizeof(struct iphdr); if(ip->protocol == IPPROTO_ICMP){ struct icmp *ip_icmp = (struct icmp *)buf; icmp_packet(ip_icmp); } } } } close(s); return 0; } |
実行結果はこんな感じです。traceroute してみます。指定したインターフェースを通る( 受信もしくは送信 )パケットがキャプチャされます。
tracerouteをしている最中に流れたパケットの一部を表示しています。実行画面は、データの受けて側です。また、全てのパケットにデータサイズは表示します。もしパケットにICMPが含まれていたら、その中身もチェックします。

tracerouteはOSにもよりますが、Linuxの場合はUDPデータに、使われていないポート番号で送信します。なので、もしデータが受けて側に届いたら、タイプ3(到達不能メッセージ)のコード3(ポート到達不能)を返します。windowsで動かしたら、ping同様、tracertにエコー要求が使われるので、受けて側はエコー応答を返すかと思います。
最後まで読んでいただきありがとうございました。