Linux socket プログラミング/パケットモニタリングプログラム目次†概要†NIC(ネットワークインターフェースカード)が受け取ったネットワーク上のパケットを抜き出しヘッダー情報を表示するプログラムです。表示されるパケットは TCP パケットに限定しています。 プログラムのイメージ†このプログラムで使われる関数のイメージを以下に示します。
無差別受信モードへの設定の方法は、かつては、
というやり方が主流だったのですが、現在は上記のように
というやり方が主流のようです。(man packet(7) 参照) ソースコード†1 /* 2 * packet monitor program 3 * pckmon2.c 4 * cc pckmon2.c -lnsl -o pckmon2 5 */ 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <netinet/in.h> 9 #include <errno.h> 10 #include <netdb.h> 11 #include <netinet/tcp.h> 12 #include <netinet/ip.h> 13 #include <sys/socket.h> 14 #include <arpa/inet.h> 15 #include <sys/ioctl.h> 16 #include <net/if.h> 17 #include <net/ethernet.h> 18 #include <netpacket/packet.h> 19 20 void recv_pkt(); 21 int sock ; 22 23 int 24 main(int argc, char *argv[]) 25 { 26 struct ifreq ifr; 27 struct packet_mreq mreq; 28 29 if(argc != 2) { 30 printf("Usage: %s interface\n", argv[0]); 31 exit(1); 32 } 33 34 if ((sock = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL))) < 0 ){ 35 perror("socket"); 36 exit(1); 37 } 38 39 strcpy(ifr.ifr_name, argv[1]); 40 41 if(ioctl(sock, SIOCGIFINDEX, &ifr) < 0 ){ 42 perror("ioctl SIOCGIFINDEX"); 43 exit(1); 44 } 45 46 memset(&mreq,0,sizeof(mreq)); 47 mreq.mr_type = PACKET_MR_PROMISC; 48 mreq.mr_ifindex = ifr.ifr_ifindex; 49 50 if((setsockopt(sock,SOL_PACKET,PACKET_ADD_MEMBERSHIP,(void *)&mreq,sizeof(mreq))) < 0){ 51 perror("setsockopt"); 52 exit(1); 53 } 54 55 recv_pkt(); 56 } 57 58 /*********************************************** 59 * recv_pkt() 60 * Receive and display packets 61 ***********************************************/ 62 void 63 recv_pkt() 64 { 65 int rsin_size, count; 66 struct sockaddr_in rsin; 67 struct in_addr insaddr,indaddr; 68 fd_set fds; 69 70 struct buf { 71 struct iphdr ip; 72 struct tcphdr tcp; 73 unsigned char blah[65535]; 74 } buf; 75 76 rsin_size = sizeof(rsin); 77 78 FD_ZERO(&fds); 79 FD_SET(sock, &fds); 80 81 for ( count = 0 ;; count++){ 82 if( select(sock + 1, &fds , NULL, NULL, NULL) < 0 ){ 83 perror("select"); 84 exit(1); 85 } 86 87 if ( FD_ISSET(sock, &fds)){ 88 if(recvfrom(sock,&buf,sizeof(buf),0,(struct sockaddr *)&rsin,&rsin_size) < 0 ){ 89 perror("recvfrom"); 90 } 91 /* 92 * Ignore the packets other than TCP 93 */ 94 if ( buf.ip.protocol != IPPROTO_TCP) 95 continue; 96 insaddr.s_addr = buf.ip.saddr; 97 indaddr.s_addr = buf.ip.daddr; 98 99 printf("Packet number : %d\n", count); 100 printf("----IP Header--------------------\n"); 101 printf("version : %u\n",buf.ip.version); 102 printf("ihl : %u\n",buf.ip.ihl); 103 printf("tos : %u\n",buf.ip.tos); 104 printf("tot length : %u\n",ntohs(buf.ip.tot_len)); 105 printf("id : %u\n",ntohs(buf.ip.id)); 106 printf("frag_off : %u\n",ntohs(buf.ip.frag_off) & 8191); 107 printf("ttl : %u\n",buf.ip.ttl); 108 printf("protocol : %u\n",buf.ip.protocol); 109 printf("check : 0x%x\n",ntohs(buf.ip.check)); 110 printf("saddr : %s\n",inet_ntoa(insaddr)); 111 printf("daddr : %s\n",inet_ntoa(indaddr)); 112 113 printf("----TCP Header-------------------\n"); 114 printf("source port : %u\n",ntohs(buf.tcp.source)); 115 printf("dest port : %u\n",ntohs(buf.tcp.dest)); 116 printf("sequence : %u\n",ntohl(buf.tcp.seq)); 117 printf("ack seq : %u\n",ntohl(buf.tcp.ack_seq)); 118 printf("frags :"); 119 buf.tcp.fin ? printf(" FIN") : 0 ; 120 buf.tcp.syn ? printf(" SYN") : 0 ; 121 buf.tcp.rst ? printf(" RST") : 0 ; 122 buf.tcp.psh ? printf(" PSH") : 0 ; 123 buf.tcp.ack ? printf(" ACK") : 0 ; 124 buf.tcp.urg ? printf(" URG") : 0 ; 125 printf("\n"); 126 printf("window : %u\n",ntohs(buf.tcp.window)); 127 printf("check : 0x%x\n",ntohs(buf.tcp.check)); 128 printf("urt_ptr : %u\n\n\n",buf.tcp.urg_ptr); 129 } 130 } /* for() loop */ 131 } ソースファイル pckmon2.c 実行例†プログラム名の後に引数としてパケットをキャプチャする NIC を指定します。 なお、このプログラムは root ユーザで実行する必要があります。 # ./pckmon2 eth0 Packet number : 0 ----IP Header-------------------- version : 4 ihl : 5 tos : 0 tot length : 40 id : 14879 frag_off : 0 ttl : 128 protocol : 6 check : 0xd575 saddr : 172.29.73.2 daddr : 172.29.73.254 ----TCP Header------------------- source port : 1270 dest port : 22 sequence : 1671008060 ack seq : 1093140794 frags : ACK window : 17340 check : 0x4699 urt_ptr : 0 Packet number : 1 ----IP Header-------------------- version : 4 ihl : 5 tos : 16 tot length : 540 id : 11933 frag_off : 0 ttl : 64 protocol : 6 check : 0x1ef4 saddr : 172.29.73.254 daddr : 172.29.73.2 ----TCP Header------------------- source port : 22 dest port : 1270 sequence : 1093140794 ack seq : 1671008060 frags : PSH ACK window : 6432 check : 0xbbdc urt_ptr : 0 NIC に到着した TCP のパケットを書き出しています。 内容をみると、SSH のセッションのパケットであることが分かります。 解説†このプログラムではいままでのプログラムと同様に socket() を呼びソケットを作成しています。しかし、いままでのプログラムの違うのはソケットを作成するときドメインとして PF_PACKET をセットしているところです。 34 if ((sock = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL))) < 0 ){ これにより、 TCP のデータ部だけでなく、よりデバイスに近い情報である、IP や TCP のヘッダー情報までも参照できるようになります。上記プログラムでは行っていませんが、ソケットタイプとして SOCK_RAW を指定すれば Ethernet ヘッダーも見ることもできます。
また、データを受け取るときに先の POPクライアントプログラムでは recv() システムコールを使っていましたが、今回は recvfrom() システムコールを使っています。 recv() システムコールはコネクション型(=SOCK_STREAM)接続にて、コネクションの確立が終わった後に、そのソケット記述子を使ってデータを受信していましたが、recvfrom() システムコールの場合はコネクションレス型(=SOCK_DGRAM)で使われ、ソケット記述子 と socket address を使って packet を識別し、データを受信します。ようするに、コネクション型の場合は最初に相手先との専用の道を作ってしまうので、到着するパケットがだれからきたものだか分かりますが、コネクションレス型の場合は 相手を確認するためにのアドレスやポート番号を確認する必要があるという事です。
後は recvfrom で受け取ったデータ(=buf)を以下の構造体に当てはめてやります。 70 struct buf { 71 struct iphdr ip; ---> IP ヘッダの構造体 72 struct tcphdr tcp; ---> TCP ヘッダの構造体 73 unsigned char blah[65535]; ---> データ 74 } buf; IP ヘッダ、TCPヘッダは /use/include/netinet/ip.h および tcp.h でそれぞれ iphdr 、 tcphdr 構造体として宣言されています。最後のデータは 65535 byte の キャラクターとして宣言していますが、これは TCP の最大長が 65535 byte であるためで、全データがこの構造体の範囲を越える事はありません。たぶん。 最後に・・・†相変わらず自分でも不明な部分が多く、 汚いソースですが、色々応用して使えるのではないかと思います。今回は for 文にて次々にパケットを表示しておりますが、IP addres や port 番号でフィルタをかけるようにすれば必要なパケットだけをフィルタリングするようにも出来ますし、今回は利用しておりませんが,tcp のデータ部(=blah)を出力してやれば、telnet や pop などの 通信内容もモニターできるようになるはずです。 |