#norelated #navi(Linux socket プログラミング) *目次 [#le4558a7] #contents *概要 [#v6eb036c] NIC(ネットワークインターフェースカード)が受け取ったネットワーク上のパケットを抜き出しヘッダー情報を表示するプログラムです。表示されるパケットは TCP パケットに限定しています。 *プログラムのイメージ [#t2621b90] このプログラムで使われる関数のイメージを以下に示します。 |>|CENTER:|c |流れ|解説|h |socket(PF_PACKET,SOCK_DGRAM, )|BGCOLOR(white):PF_PACKET ドメインの DGRAM ソケットを作成| |BGCOLOR(white):↓ |BGCOLOR(white):| |ioctl(,SIOCGIFINDEX,)|BGCOLOR(white):インターフェースのインデックスを得る| |BGCOLOR(white):↓ |BGCOLOR(white):| |setsockopt(,,PACKET_ADD_MEMBERSHIP,,)|BGCOLOR(white):無差別受信モードに設定| |BGCOLOR(white):↓ |BGCOLOR(white):| |recvfrom() |BGCOLOR(white):パケットを受信(繰り返す)| |BGCOLOR(white):↓ |BGCOLOR(white):| |printf()|BGCOLOR(white):パケットのヘッダーを表示(繰り返す)| 無差別受信モードへの設定の方法は、かつては、 >''INET ドメインの SOCK_PACKET ソケットを作成し、ifr_flags に IFF_PROMISC をセットする'' というやり方が主流だったのですが、現在は上記のように >''PF_PACKET ドメインの SOCK_RAW ソケットもしくは SOCK_DGRAM ソケットを作り、setsockopt()にて無差別受信モードをセットする'' というやり方が主流のようです。(man packet(7) 参照) *ソースコード [#p7254178] CENTER:[[pckmon2.c>source:pckmon2.c]] CENTER:[[pckmon2.c>http://github.com/kaizawa/linux-socket/raw/master/pckmon2.c]] 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>source:pckmon2.c]] ソースファイル [[pckmon2.c>http://github.com/kaizawa/linux-socket/raw/master/pckmon2.c]] *実行例 [#o54a6b2f] プログラム名の後に引数としてパケットをキャプチャする 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 のセッションのパケットであることが分かります。 *解説 [#d7af81c0] このプログラムではいままでのプログラムと同様に socket() を呼びソケットを作成しています。しかし、いままでのプログラムの違うのはソケットを作成するときドメインとして PF_PACKET をセットしているところです。 34 if ((sock = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL))) < 0 ){ これにより、 TCP のデータ部だけでなく、よりデバイスに近い情報である、IP や TCP のヘッダー情報までも参照できるようになります。上記プログラムでは行っていませんが、ソケットタイプとして SOCK_RAW を指定すれば Ethernet ヘッダーも見ることもできます。 -通常はデータしか見れない |BGCOLOR(gray):Ethernet ヘッダー| |BGCOLOR(gray):IP ヘッダー| |BGCOLOR(gray):TCP ヘッダー| |BGCOLOR(white):データ| ~ -PF_PACKET ドメインの SOCK_DGRAM を指定した場合、IP、TCP ヘッダーもみれる。本プログラムはここまで |BGCOLOR(gray):Ethernet ヘッダー| |BGCOLOR(white):IP ヘッダー| |BGCOLOR(white):TCP ヘッダー| |BGCOLOR(white):データ| ~ -PF_PACKET ドメインの SOCK_RAW を指定した場合、さらに Ethernet ヘッダーもみれる |BGCOLOR(white):Ethernet ヘッダー| |BGCOLOR(white):IP ヘッダー| |BGCOLOR(white):TCP ヘッダー| |BGCOLOR(white):データ| ~ また、データを受け取るときに先の POPクライアントプログラムでは recv() システムコールを使っていましたが、今回は recvfrom() システムコールを使っています。 recv() システムコールはコネクション型(=SOCK_STREAM)接続にて、コネクションの確立が終わった後に、そのソケット記述子を使ってデータを受信していましたが、recvfrom() システムコールの場合はコネクションレス型(=SOCK_DGRAM)で使われ、ソケット記述子 と socket address を使って packet を識別し、データを受信します。ようするに、コネクション型の場合は最初に相手先との専用の道を作ってしまうので、到着するパケットがだれからきたものだか分かりますが、コネクションレス型の場合は 相手を確認するためにのアドレスやポート番号を確認する必要があるという事です。 |>|recvfrom()|h |概要|メッセージの受信| |インクルードファイル |#include <sys/types.h>&br;#include <sys/socket.h>| |形式| int recvfrom(int s, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);| |引数 |int s : socket 記述子&br;void *buf: 受信メッセージのバッファアドレス&br;int len: 受信メッセージのサイズ&br;unsigned int flags: 受信メッセージのフラグ&br;struct sockaddr *from: socket address のポインタ&br;int *fromlen: sockaddr のサイズ| |戻り値 |成功時: 受信したバイト数&br;エラー: -1| 後は 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 であるためで、全データがこの構造体の範囲を越える事はありません。たぶん。&br; これで受け取ったデータの最初の部分が、ip ヘッダ と tcp ヘッダ として割り振られた事になるので、次は printf() にてこの構造体の各メンバを 出力しているという事です。&br; IP 、TCP ヘッダ構造体の各メンバの出力時には メンバごとに少々アレンジしていて、port 番号、window size については ntoh() 関数を用いてバイト序列をネットワーク型からホスト型へ変換してから表示しています。また、IP アドレスについては 一度 in_addr 構造体に当てはめてから(96 〜97行目) inet_ntoa() 関数を使って、おなじみの数字+ドット表記のアドレスに変換しています。inet_ntoa() を使わないと 2 進数や 16 進数での表示になってしまい、直感的にアドレスが分かりません。しかしながら、一度 in_addr に当てはめるという行為がはたして正しいのかは不明です。(そうしないとできなかったもので、、)なにか他にいい方法があるような気がしますが、分かりませんでした。 *最後に・・・ [#v5e5142f] 相変わらず自分でも不明な部分が多く、 汚いソースですが、色々応用して使えるのではないかと思います。今回は for 文にて次々にパケットを表示しておりますが、IP addres や port 番号でフィルタをかけるようにすれば必要なパケットだけをフィルタリングするようにも出来ますし、今回は利用しておりませんが,tcp のデータ部(=blah)を出力してやれば、telnet や pop などの 通信内容もモニターできるようになるはずです。