ARPパケットにターゲットを絞って、パケットキャプチャしていこうと思います。
ARPパケットを扱うには、linuxで定義されているether_arp構造体とarphdr構造体を使用します。そのためには"netinet/if_ether.h"と"net/if_arp.h"をインクルードする必要があります。
ether_arp構造体は/usr/include/netinet/if_ether.hに定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* * Ethernet Address Resolution Protocol. * * See RFC 826 for protocol description. Structure below is adapted * to resolving internet addresses. Field names used correspond to * RFC 826. */ struct ether_arp { struct arphdr ea_hdr; /* fixed-size header */ u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */ u_int8_t arp_spa[4]; /* sender protocol address */ u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */ u_int8_t arp_tpa[4]; /* target protocol address */ }; #define arp_hrd ea_hdr.ar_hrd #define arp_pro ea_hdr.ar_pro #define arp_hln ea_hdr.ar_hln #define arp_pln ea_hdr.ar_pln #define arp_op ea_hdr.ar_op |
arphdr構造体は /usr/include/net/if_arp.hに定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* See RFC 826 for protocol description. ARP packets are variable in size; the arphdr structure defines the fixed-length portion. Protocol type values are the same as those for 10 Mb/s Ethernet. It is followed by the variable-sized fields ar_sha, arp_spa, arp_tha and arp_tpa in that order, according to the lengths specified. Field names used correspond to RFC 826. */ struct arphdr { unsigned short int ar_hrd; /* Format of hardware address. */ unsigned short int ar_pro; /* Format of protocol address. */ unsigned char ar_hln; /* Length of hardware address. */ unsigned char ar_pln; /* Length of protocol address. */ unsigned short int ar_op; /* ARP opcode (command). */ #if 0 /* Ethernet looks like this : This bit is variable sized however... */ unsigned char __ar_sha[ETH_ALEN]; /* Sender hardware address. */ unsigned char __ar_sip[4]; /* Sender IP address. */ unsigned char __ar_tha[ETH_ALEN]; /* Target hardware address. */ unsigned char __ar_tip[4]; /* Target IP address. */ #endif }; |
初見では少し分かりにくいかもしれません。
ARPは、ehternet/IP以外にも対応しています。規格によって、アドレスバイト数が異なります。なのでarphdrという汎用的な構造体でアドレス以外のパラメータを定義して、そしてethernet/IP用にアドレスが定義された構造体がether_arpです。
構造体の中身を見てみると、ether_arp構造体の最初に、struct arphdr がさらに定義されています。その次にアドレスが宣言されています。このarphdr構造体は、ARPパケットのアドレス以外のパラメータ(オペレーションコードなど)をメンバに持ちます。arphdr構造体に#ifから#end ifの間に、アドレスが定義されている感じがしますが、実際にアクセスは出来ないです。なのでアドレスは、規格に対応した形で別に定義してあげる必要があります。
ether_arp構造体をさらに見てみると、arp_hrd構造体がes_hrdという名前で宣言されています。そして、#defineでes_hrdのメンバ"ar_hrd"へのアクセスをarp_hrdで定義するとしています。これでether_arp構造体には、arp_hrdというメンバが定義されているかのようになります。
つまり、実際にコードを書くときはarphdr構造体を気にすることなく、ether_arp構造体だけで全てのパラメータを触れるようになります。
ARPパケットをキャプチャするコードは以下のようになります。
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<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<net/if_arp.h> #include<netinet/if_ether.h> char *operation(u_short code); char *hrd_type(u_short hardtype); void prt_type(u_short type); /*MACアドレスを文字列に変換する関数*/ char *mac2str(u_char *hwaddr, char *mac, size_t size) { snprintf(mac, size, "%02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); return mac; } /*IPアドレスを文字列に変換する関数*/ char *ip2str(u_int8_t *prtaddr, char *ip, size_t size) { snprintf(ip, size, "%u.%u.%u.%u", prtaddr[0], prtaddr[1], prtaddr[2], prtaddr[3]); return ip; } /*Ethernetヘッダを表示する関数*/ void ethernet_header(struct ether_header *ehdr) { char buf[32]; printf("-----Ethernet-----\n"); printf("dst MAC = %s\n", mac2str(ehdr->ether_dhost, buf, sizeof(buf))); printf("src MAC = %s\n", mac2str(ehdr->ether_shost, buf, sizeof(buf))); printf("ether_type = %x\n", ntohs(ehdr->ether_type)); } /*ARPパケットを表示する関数*/ void arp(struct ether_arp *arp) { char buf[32]; printf("-------ARP--------\n"); printf("hrd type = %u (%s)\n", ntohs(arp->arp_hrd), hrd_type(ntohs(arp->arp_hrd))); printf("prt type = %u ", ntohs(arp->arp_pro)); prt_type(ntohs(arp->arp_pro)); printf("hrd len = %u\n", arp->arp_hln); printf("prt len = %u\n", arp->arp_pln); printf("operstion = %u (%s)\n", ntohs(arp->arp_op), operation(ntohs(arp->arp_op))); printf("src mac = %s\n", mac2str(arp->arp_sha, buf, sizeof(buf))); printf("src ip = %s\n", ip2str(arp->arp_spa, buf, sizeof(buf))); printf("dst mac = %s\n", mac2str(arp->arp_tha, buf, sizeof(buf))); printf("dst ip = %s\n", ip2str(arp->arp_tpa, buf, sizeof(buf))); } /*ハードタイプの名前を返す関数*/ char *hrd_type(u_short type) { char *hw[] = { "From KA9Q: NET/ROM pseudo", "Ethernet 10/100Mbps", "Experimental Ethernet", "AX.25 Level2", "PROnet token ring", "Chaosnet", "IEEE802.2 Ethernet/TB/TB", "ARCnet", "APPLEtalk", "undefined", "undefined", "undefined", "undefined", "undefined", "undefined", "Frame Relay DLCI", "undefined", "undefined", "undefined", "ATM", "undefined", "undefined", "undefined", "Metricom STRIP(new IANA id)", "IEEE 1394 IPv4 - RFC 2734", "undefined", "undefined", "EUI-64", "undefined", "undefined", "undefined", "undefined", "InfiniBand"}; return hw[type]; } /*プロトコルタイプの名前を表示する関数*/ void prt_type(u_short type) { switch (type){ case ETHERTYPE_PUP: printf("(Xerox POP)\n"); break; case ETHERTYPE_IP: printf("(IP)\n"); break; case ETHERTYPE_IPV6: printf("(IPv6)\n"); break; case ETHERTYPE_ARP: printf("(ARP)\n"); break; case ETHERTYPE_REVARP: printf("(Reverse ARP)\n"); break; default: printf("(unknown)\n"); break; } } /*オペレーションコードの名前を返す関数*/ char *operation(u_short code) { char *op[] = { "undefined", "ARP request", "ARP reply", "RARP request", "RARP reply", "unfefined", "unfefined", "unfefined", "InARP request", "InARP reply", "(ATM)ARP NAK."}; return op[code]; } int main(void) { struct sockaddr_ll sa; struct ifreq ifreq; u_char data[1500];//パケットを格納 u_char *buf;//パケットの任意の位置(アドレス)を格納 int len; /*scoketの生成*/ int s=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if(s<0) perror("socket"); /*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; ethernet_header(eh); buf += sizeof(struct ether_header); if(ntohs(eh->ether_type)==ETHERTYPE_ARP){ struct ether_arp *eth_arp = (struct ether_arp *)buf; arp(eth_arp); } } } return 0; } |
ハードタイプやオペレーションコードの名前を返す関数などは一応書かなくても大丈夫です。しかしその際は、例えばハードタイプの値だけが表示されるだけです。あくまで、その値が何を示すかということを表示しているだけなので、名前と値の対応を覚えている方は必要ないかと思います。
実際に動かすとこんな感じです。

最後まで読んでいただきありがとうございました。