プロミスキャスモードでパケットの受信
(簡単なパケットキャプチャ)
プロミスキャスモードでソケットを作成し、ブロードキャストされたパケットや、自分宛の全てのパケットを受信できるようにします。スイッチでミラーリングを行えば自分宛以外のパケットもキャプチャできます。

/* * sample program * promiscuous mode test program */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <getopt.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <netdb.h> #include <netpacket/packet.h> #include <net/ethernet.h> #include <net/if.h> #include <errno.h> #define DEF_IFACE "eth0" #define PACKETMAX 0xffff static void help(void); static int set_pmcs_socket(const char*); static void dumpmsg(void *, int); int main(int argc ,char *argv[]) { struct sockaddr_in from; struct iphdr *ip; char iface[IFNAMSIZ +1] = {0}; char buf[PACKETMAX] = {0}; char src[16], dst[16]; int opt = 0, sockfd = 0, len = 0, fromlen = 0; const struct option longopt[] = { {"help", 0, 0, 'h'}, {"interface", 1, 0, 'i'}, {0, 0, 0, 0}, }; /* get command option */ while((opt = getopt_long(argc, argv, "hHi:I:", longopt, NULL)) != -1){ switch(opt){ case 'i': case 'I': strncpy(iface, optarg, IFNAMSIZ); break; case 'h': case 'H': case '?': help(); return 0; } } if(iface[0] == 0){ strcpy(iface, DEF_IFACE); } fprintf(stdout, "interface:%s\n", iface); /* create socket */ sockfd = set_pmcs_socket(iface); if(sockfd < 0){ return -1; } while(1){ fromlen = sizeof(struct sockaddr_in); len = recvfrom(sockfd, buf, PACKETMAX, 0, (struct sockaddr *)&from, (socklen_t *)&fromlen); if(len < 0){ perror("recvfrom error"); break; }else if(fromlen == 0){ continue; /* unidentified address */ } ip = (struct iphdr *)buf; sprintf(src, "%s", inet_ntoa(*(struct in_addr *)&(ip->saddr))); sprintf(dst, "%s", inet_ntoa(*(struct in_addr *)&(ip->daddr))); fprintf(stdout, "IP %s > %s (%dbyte)\n",src, dst, len); dumpmsg(buf, len); memset(buf, 0, len); } close(sockfd); return 0; } static void help(void) { fprintf(stdout, "Usage: udpserver [-i interface]\n" ); return; } static int set_pmcs_socket(const char *iface) { int sockfd; struct ifreq ifr; struct packet_mreq mreq; /* create socket */ sockfd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); if(sockfd < 0){ perror("socket error"); return -1; } /* get interface index */ memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, iface, IFNAMSIZ); if(ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0){ perror("ioctl error"); close(sockfd); return -1; } /* set promiscuous mode */ memset(&mreq, 0, sizeof(mreq)); mreq.mr_type = PACKET_MR_PROMISC; mreq.mr_ifindex = ifr.ifr_ifindex; if(setsockopt(sockfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0){ perror("setsockopt error"); close(sockfd); return -1; } return sockfd; } static void dumpmsg(void *buf, int size) { unsigned char *pbuf, *px, *pc; int i = 0; char outbuf[80]; char *pout; px = pc = pbuf = (unsigned char *)buf; while((px - pbuf) < size){ memset(outbuf, 0, sizeof(outbuf)); pout = outbuf; sprintf(pout, "%04d : ", (px - pbuf)); pout += 7; /* print hex */ for(i=0 ; i<16 && (px - pbuf) < size ; i++, px++){ sprintf(pout, "%02x ", *px); pout += 3; if(i==7){ sprintf(pout++, " "); } } /* add space */ if(i != 16){ if(i < 8){ sprintf(pout++, " "); } for( ; i<16 ; i++){ sprintf(pout, " "); pout += 3; } } sprintf(pout, " "); pout += 4; /* print ascii */ for(i=0 ; i<16 && (pc - pbuf) < size ; i++, pc++){ if((*pc < 0x20) || (*pc > 0x7f)){ sprintf(pout++, "."); }else{ sprintf(pout++, "%c", *pc); } if(i==7){ sprintf(pout++, " "); } } fprintf(stdout, "%s\n", outbuf); } return; }
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

(プログラムの概要)
packetソケットを作成し、プロミスキャスモードに設定し、NICに送られてきた全てのIPパケットを受信できるようにします。パケットを受信すると、IPヘッダーから送信元と 送信先アドレスを表示し、パケット全体をhexとasciiでdump表示しています。

42L-58L:オプション分析
-iオプションでインターフェース名を取得できるようにします。-iが指定されていない場合はデフォルトのインターフェースとしてeth0を設定しています。
61L:ソケット作成
set_pmcs_socket()でソケットを作成し、プロミスキャスモードに設定しています。
66L-82L:受信ループ
recvfromでパケットを受信し、IPヘッダ部分を参照して送信元と送信先アドレスを表示しています。また、80Lのdumpmsg()ではhexとasciiによる受信パケットのdump表示を行います。

set_pmcs_socket()
101-106L:ソケットの作成
PF_PACKETモードでソケットを作成します。第2引数にはSOCK_RAWとSOCK_DGRAMが指定できます。今回はSOCK_DGRAMを指定していますが、SOCK_RAWを指定するとetherヘッダーを含めた データが受信できます。第3引数はプロトコル指定で、今回はIPプロトコルを指定しています。ETH_P_ALLとするとICMPパケットなども受信することができます。 SOCK_RAWとETH_P_ALLについては次回のRAWソケットでサンプルを作ります。
107-114L:インターフェース番号の取得
次にコマンドオプション-iで取得したインターフェース名から、カーネルが管理しているインターフェース番号を取得します。119Lのsetsockopt()で必要になります。 ioctlを使ってインターフェース番号を取得します。詳しくはmanpage(NETDEVICE)を参照してください。
115-124L:プロミスキャスモードの設定
第4引数にインターフェース番号とタイプPACKET_MR_PROMISCを設定し、setsockopt()を使ってプロミスキャスモードに設定します。manpage(PACKET)に詳しく載っています。

※わざわざインターフェース番号を指定しているのでインターフェースでマスクできそうですが、他のNICやloのパケットも拾ってしまします。正直この辺はよく分かりません。


(コンパイルと動作)
gcc -Wall -o pserver promiscuous.c
スーパーユーザーで実行してください。プログラムを実行すると、NICに送られてくるパケットを表示し続けます。頑張ればパケットキャプチャもどきのようなものも作ることができると思います。次回はRAWソケットを使ってetherフレームから受信してみます。