Solaris DLPI プログラミング/パケットモニター目次†概要†まず最初に白状しますが、ここで書いているプログラムの コアの部分は全くもって私のオリジナルではありません。internet 上にある DLPI をつかったプログラムを参考にして切り張りして作っています。ただ、いくつかのプログラムを見たところ、ほぼおんなじ source をつかっており、ものは違ってもつかっている関数名がまったく同じだったり、関数の中見が同じだったりするので、おそらくどこかにたたき台となっている source が存在するのでしょう。(tcpdump で利用する libpcap も似たようなものです)ここは開き直ってプログラムの簡略化と、説明とに注力したいと思います。
プログラムのイメージ†このプログラムの main() の流れを以下に示します。最後の getmsg と print_pkt を loop しているだけで他は一直線なプログラムなので流れは分かりやすいかと思います。 PPA と SAP ってのが出てきますが、PPA は network Interface の instance 番号( /dev/hme0 の 0)のことで、SAP ってのは Ethernet のフレームタイプです。Ethernet フレームタイプには IP(0x0800)や、ARP(0x0806)などがあります。このプログラムでは フレームタイプとして IP(0x0800)を指定しています。
ソースコード†1 /********************************************* 2 * sniff.c 3 * 4 * packet monitor using DLPI 5 * 6 * gcc sniff.c -lnsl -o sniff 7 * 8 ********************************************/ 9 #Include <netinet/in.h> 10 #include <sys/types.h> 11 #include <sys/socket.h> 12 #include <sys/stropts.h> 13 #include <sys/dlpi.h> 14 #include <fcntl.h> 15 #include <stdio.h> 16 #include <sys/signal.h> 17 #include <sys/stream.h> 18 #include <string.h> 19 #include <net/if.h> 20 #include <netinet/if_ether.h> 21 #include <netinet/in_systm.h> 22 #include <netinet/tcp.h> 23 #include <netinet/ip.h> 24 #include <sys/varargs.h> 25 #include <errno.h> 26 #include <unistd.h> 27 28 #define ERR_MSG_MAX 300 29 #define MAXDLBUFSIZE 8192 30 #define DLBUFSIZE 8192 31 #define TCP_DATA_PRINT_LENGTH 100 32 #define PRINT_MAC_ADDR(ether_addr_octet) { \ 33 int i; \ 34 for ( i =0; i < 6; i++){ \ 35 printf("%02x",(ether_addr_octet[i])); \ 36 if(i != 5) \ 37 printf(":"); \ 38 } \ 39 } 40 41 void print_packet(caddr_t, int); 42 void print_usage(char *); 43 int dlattachreq(int, t_uscalar_t, caddr_t ); 44 int dlpromisconreq(int, t_uscalar_t, caddr_t); 45 int dlbindreq(int, t_uscalar_t, t_uscalar_t, uint16_t, uint16_t, t_uscalar_t, caddr_t); 46 void print_err(int , char *, ...); 47 int dldetachreq(int , caddr_t); 48 int dlpromiscoffreq(int, t_uscalar_t, caddr_t); 49 50 int 51 main(int argc, char *argv[]) 52 { 53 char buf[DLBUFSIZE]; // getmsg(2) で利用するバッファ 54 struct strbuf databuf; // getmsg(2) に引数として渡す構造体 55 union DL_primitives *dlp; 56 int flags = 0; 57 int ppa; // NIC のインスタンス番号に相当(Physical Point ofAttachment) 58 int sap; // Ethernet フレームタイプに相当(Service Access Point) 59 int fd; // デバイスをオープンしたファイル記述子 60 struct strioctl strioc; // stream デバイス用の ioctl(2) のコマンドを指定する構造体 61 char *interface; // 引数で渡されたインターフェース名 (例: hme0) 62 char devname[30] = {0}; // interface 名から instance を取ったもの (例:hme) 63 char devpath[30] = {0}; // open() 時に指定する device file への path (例: /dev/hme) 64 char *instance; // interface の instance 番号。ppa となる (例: 0) 65 66 if (argc != 2) 67 print_usage(argv[0]); 68 69 interface = argv[1]; 70 if ((instance = strpbrk(interface, "0123456789")) == NULL){ 71 fprintf(stderr, "%s: no instance specified\n", interface); 72 print_usage(argv[0]); 73 } 74 75 ppa = atoi(instance); 76 77 strncpy(devname, interface, instance - interface); 78 79 sprintf(devpath, "/dev/%s",devname); 80 81 /* 82 * デバイスをオープンする 83 */ 84 if((fd = open (devpath , O_RDWR)) < 0 ){ 85 perror("open"); 86 exit(1); 87 } 88 89 /* 90 * PPA(instance) にアッタチする 91 */ 92 if(dlattachreq(fd, ppa, buf) < 0){ 93 fprintf(stderr, "%s: no such instance\n", interface); 94 print_usage(argv[0]); 95 } 96 97 /* 98 * プロミスキャスモードをセット 99 */ 100 if(dlpromisconreq(fd, DL_PROMISC_PHYS, buf) < 0){ 101 fprintf(stderr, "%s: Cannot set promiscuous mode\n", interface); 102 exit(1); 103 } 104 105 /* 106 * SAP(flame type) にバインド 107 */ 108 if(dlbindreq (fd, ETHERTYPE_IP, 0, DL_CLDLS, 0, 0, buf) < 0){ 109 fprintf(stderr, "%s: Cannot bind to ETHERTYPE_IP\n", interface); 110 exit(1); 111 } 112 113 /* 114 * RAW モードにセット 115 */ 116 strioc.ic_cmd = DLIOCRAW; 117 strioc.ic_timout = -1; 118 strioc.ic_len = 0; 119 strioc.ic_dp = NULL; 120 if(ioctl(fd, I_STR, &strioc) < 0){ 121 perror("ioctl: I_STR: DLIOCRAW"); 122 exit(1); 123 } 124 125 /* 126 * キューをフラッシュ 127 */ 128 if (ioctl(fd, I_FLUSH, FLUSHR) < 0){ 129 perror("ioctl: I_FLUSH"); 130 exit(1); 131 } 132 133 databuf.maxlen = MAXDLBUFSIZE; 134 databuf.len = 0; 135 databuf.buf = (caddr_t)buf; 136 137 while (getmsg(fd, NULL, &databuf, &flags) == 0) { 138 if (databuf.len > 0) 139 print_packet(databuf.buf, databuf.len); 140 } 141 142 perror("getmsg"); 143 exit(1); 144 } 145 146 void 147 print_packet(caddr_t buf, int len) 148 { 149 struct ether_header *ether; 150 struct tcphdr *tcp; 151 struct ip *ip; 152 u_char *tcpdata; 153 int etherlen; 154 int iphlen; 155 int iptotlen; 156 int tcphlen; 157 int tcpdatalen; 158 159 etherlen = sizeof(struct ether_header); 160 ether = (struct ether_header *)buf; 161 162 /* 163 * Ether Type が IP(0x800) じゃなかったらリターン 164 */ 165 if(ether->ether_type != ETHERTYPE_IP) 166 return; 167 168 /* 169 * 全フレームサイズがデフォルト(最小)の ether, ip, tcp 170 * ヘッダー長の合計よりも小さかったら不正パケットとみなして 171 * リターン。 172 */ 173 if(len < etherlen + sizeof(struct ip) + sizeof(struct tcphdr) ) 174 return; 175 176 /* 177 * アライメントエラーを避けるため、IP ヘッダー以降を、別途確保 178 * したメモリにコピーする。 179 */ 180 ip = (struct ip *)malloc(len); 181 memcpy(ip, buf + etherlen, len); 182 183 /* 184 * TCP でなければリターン 185 */ 186 if(ip->ip_p != IPPROTO_TCP) 187 goto error; 188 189 iphlen = ip->ip_hl << 2; 190 191 /* 192 * パケット内で申告されている IP ヘッダー長が分かった 193 * ので改めて、フレーム長を確認。 194 * もし小さかったら不正パケットとみなして無視。 195 */ 196 if(len < etherlen + iphlen + sizeof(struct tcphdr) ) 197 goto error; 198 199 tcp = (struct tcphdr *)((u_char *)ip + iphlen); 200 tcphlen = tcp->th_off << 2; 201 202 /* 203 * パケット内で申告されている TCP ヘッダー長が分かった 204 * ので改めて、フレーム長を確認。 205 * もし小さかったら不正パケットとみなして無視。 206 */ 207 if(len < etherlen + iphlen + tcphlen ) 208 goto error; 209 210 printf("\n----Ether Header----\n"); 211 printf("src addr : "); 212 PRINT_MAC_ADDR(ether->ether_shost.ether_addr_octet); 213 printf("\n"); 214 printf("dest addr : "); 215 PRINT_MAC_ADDR(ether->ether_dhost.ether_addr_octet); 216 printf("\n"); 217 printf("ether type : 0x%x\n",ether->ether_type); 218 219 printf("----IP Header----\n"); 220 printf("version : %d\n",ip->ip_v); 221 printf("header len : %d (%d bytes)\n",ip->ip_hl, ip->ip_hl <<2); 222 printf("tos : %d\n",ip->ip_tos); 223 printf("total len : %d\n",ntohs(ip->ip_len)); 224 printf("id : %d\n",ntohs(ip->ip_id)); 225 printf("frag offset : %d\n",ip->ip_off); 226 printf("ttl : %d\n",ip->ip_ttl); 227 printf("protocol : %d\n",ip->ip_p); 228 printf("checksum : 0x%x\n",ip->ip_sum); 229 printf("src address : %s\n",inet_ntoa(ip->ip_src)); 230 printf("dst address : %s\n",inet_ntoa(ip->ip_dst)); 231 232 printf("----TCP Header----\n"); 233 printf("source port : %d\n",ntohs(tcp->th_sport)); 234 printf("dest port : %d\n",ntohs(tcp->th_dport)); 235 printf("seq : %u\n",ntohl(tcp->th_seq)); 236 printf("ack : %u\n",ntohl(tcp->th_ack)); 237 printf("data offset : %d (%d bytes)\n",tcp->th_off, tcp->th_off <<2); 238 printf("flags : "); 239 if((tcp->th_flags | TH_FIN) == tcp->th_flags) 240 printf("FIN "); 241 if((tcp->th_flags | TH_SYN) == tcp->th_flags) 242 printf("SIN "); 243 if((tcp->th_flags | TH_RST) == tcp->th_flags) 244 printf("RST "); 245 if((tcp->th_flags | TH_PUSH) == tcp->th_flags) 246 printf("PUSH "); 247 if((tcp->th_flags | TH_ACK) == tcp->th_flags) 248 printf("ACK "); 249 if((tcp->th_flags | TH_URG) == tcp->th_flags) 250 printf("URG "); 251 printf("\n"); 252 printf("window : %d\n",ntohs(tcp->th_win)); 253 printf("check sum : 0x%x\n",tcp->th_sum); 254 printf("urt_ptr : %d\n",tcp->th_urp); 255 256 /* 257 * ヘッダ情報から TCP データサイズを計算 258 * もしヘッダ情報から求めたTCPデータ長が残りの読み込み可能 259 * バイト数より大きかったら、残りのバイト数をデータ長とみなす。 260 */ 261 iptotlen = ntohs(ip->ip_len); 262 tcpdatalen = iptotlen - iphlen - tcphlen; 263 if( tcpdatalen > len - etherlen - iphlen - tcphlen) 264 tcpdatalen = len - etherlen - iphlen - tcphlen; 265 266 if( tcpdatalen > 0){ 267 int i = 0; 268 269 tcpdata = (u_char *)tcp + tcphlen; 270 printf("------DATA-------\n"); 271 printf("data length : %d\n", tcpdatalen); 272 /* 273 * 表示可能データであれば最初の 100 文字だけ表示 274 */ 275 while ( i < tcpdatalen && i < TCP_DATA_PRINT_LENGTH){ 276 if(isprint(tcpdata[i])) 277 printf("%c",tcpdata[i]); 278 i++; 279 } 280 } 281 282 printf("\n\n"); 283 284 error: 285 free(ip); 286 return; 287 } 288 289 /***************************************************************************** 290 * print_usage() 291 * 292 * Usage を表示し、終了する。 293 *****************************************************************************/ 294 void 295 print_usage(char *argv) 296 { 297 printf("Usage: %s ifname \n",argv); 298 printf(" Example) %s eri0\n", argv); 299 exit(1); 300 } 301 302 /***************************************************************************** 303 * dlattachreq() 304 * 305 * DLPI のルーチン。putmsg(9F) を使って DL_ATTACH_REQ をドライバに送る 306 * 307 *****************************************************************************/ 308 int 309 dlattachreq(int fd, t_uscalar_t ppa ,caddr_t buf) 310 { 311 union DL_primitives *primitive; 312 dl_attach_req_t attachreq; 313 struct strbuf ctlbuf; 314 int flags = 0; 315 int ret; 316 317 attachreq.dl_primitive = DL_ATTACH_REQ; 318 attachreq.dl_ppa = ppa; 319 320 ctlbuf.maxlen = 0; 321 ctlbuf.len = sizeof(attachreq); 322 ctlbuf.buf = (caddr_t)&attachreq; 323 324 if (putmsg(fd, &ctlbuf, (struct strbuf*) NULL, flags) < 0){ 325 fprintf(stderr, "dlattachreq: putmsg: %s", strerror(errno)); 326 return(-1); 327 } 328 329 ctlbuf.maxlen = MAXDLBUFSIZE; 330 ctlbuf.len = 0; 331 ctlbuf.buf = (caddr_t)buf; 332 333 if ((ret = getmsg(fd, &ctlbuf, (struct strbuf *)NULL, &flags)) < 0) { 334 fprintf(stderr, "dlattachreq: getmsg: %s\n", strerror(errno)); 335 return(-1); 336 } 337 338 primitive = (union DL_primitives *) ctlbuf.buf; 339 if ( primitive->dl_primitive != DL_OK_ACK){ 340 fprintf(stderr, "dlattachreq: not DL_OK_ACK\n"); 341 return(-1); 342 } 343 344 return(0); 345 } 346 347 /***************************************************************************** 348 * dlpromisconreq() 349 * 350 * DLPI のルーチン。 putmsg(9F) を使って DL_PROMISCON_REQ をドライバに送る 351 * 352 *****************************************************************************/ 353 int 354 dlpromisconreq(int fd, t_uscalar_t level, caddr_t buf) 355 { 356 union DL_primitives *primitive; 357 dl_promiscon_req_t promisconreq; 358 struct strbuf ctlbuf; 359 int flags = 0; 360 int ret; 361 362 promisconreq.dl_primitive = DL_PROMISCON_REQ; 363 promisconreq.dl_level = level; 364 365 ctlbuf.maxlen = 0; 366 ctlbuf.len = sizeof (promisconreq); 367 ctlbuf.buf = (caddr_t)&promisconreq; 368 369 if (putmsg(fd, &ctlbuf, (struct strbuf*) NULL, flags) < 0){ 370 fprintf(stderr, "dlpromisconreq: putmsg: %s", strerror(errno)); 371 return(-1); 372 } 373 374 ctlbuf.maxlen = MAXDLBUFSIZE; 375 ctlbuf.len = 0; 376 ctlbuf.buf = (caddr_t)buf; 377 378 if ((ret = getmsg(fd, &ctlbuf, (struct strbuf *)NULL, &flags)) < 0) { 379 fprintf(stderr, "dlpromisconreq: getmsg: %s\n", strerror(errno)); 380 return(-1); 381 } 382 383 primitive = (union DL_primitives *) ctlbuf.buf; 384 if ( primitive->dl_primitive != DL_OK_ACK){ 385 fprintf(stderr, "dlpromisconreq: not DL_OK_ACK\n"); 386 return(-1); 387 } 388 389 return(0); 390 } 391 392 /***************************************************************************** 393 * dlbindreq() 394 * 395 * DLPI のルーチン。 putmsg(9F) を使って DL_BIND_REQ をドライバに送る 396 * 397 *****************************************************************************/ 398 int 399 dlbindreq( 400 int fd, 401 t_uscalar_t sap, 402 t_uscalar_t max_conind, 403 uint16_t service_mode, 404 uint16_t conn_mgmt, 405 t_uscalar_t xidtest_flg, 406 caddr_t buf 407 ) 408 { 409 union DL_primitives *primitive; 410 dl_bind_req_t bindreq; 411 struct strbuf ctlbuf; 412 int flags = 0; 413 int ret; 414 415 bindreq.dl_primitive = DL_BIND_REQ; 416 bindreq.dl_sap = sap; 417 bindreq.dl_max_conind = max_conind; 418 bindreq.dl_service_mode = service_mode; 419 bindreq.dl_conn_mgmt = conn_mgmt; 420 bindreq.dl_xidtest_flg = xidtest_flg; 421 422 ctlbuf.maxlen = 0; 423 ctlbuf.len = sizeof(bindreq); 424 ctlbuf.buf = (caddr_t)&bindreq; 425 426 if (putmsg(fd, &ctlbuf, (struct strbuf*) NULL, flags) < 0){ 427 fprintf(stderr, "dlbindreq: putmsg: %s", strerror(errno)); 428 return(-1); 429 } 430 431 ctlbuf.maxlen = MAXDLBUFSIZE; 432 ctlbuf.len = 0; 433 ctlbuf.buf = (caddr_t)buf; 434 435 if ((ret = getmsg(fd, &ctlbuf, (struct strbuf *)NULL, &flags)) < 0) { 436 fprintf(stderr, "dlbindreq: getmsg: %s\n", strerror(errno)); 437 return(-1); 438 } 439 440 primitive = (union DL_primitives *) ctlbuf.buf; 441 if ( primitive->dl_primitive != DL_BIND_ACK){ 442 fprintf(stderr, "dlbindreq: not DL_BIND_ACK\n"); 443 return(-1); 444 } 445 446 return(0); 447 } ソースファイル sniff.c 実行例†プログラム名の後に引数としてインターフェース名を入力します。 # ./sniff hme0 ----Ether Header---- src addr : 08:00:20:c6:69:c7 dest addr : 00:13:d3:a9:8f:40 ether type : 0x800 ----IP Header---- version : 4 header len : 5 (20 bytes) tos : 0 total len : 1292 id : 51935 frag offset : 16384 ttl : 60 protocol : 6 checksum : 0x8497 src address : 172.29.73.55 dst address : 172.29.73.3 ----TCP Header---- source port : 23 dest port : 1277 seq : 371774688 ack : 3757417225 data offset : 5 (20 bytes) flags : ACK window : 50400 check sum : 0x6420 urt_ptr : 0 ------DATA------- NIC に到着したすべての packet を表示します。 あと、言い忘れましたが これは TCP パケット専用です。ICMP や ARP はおろか、UDP パケットも表示できません。「表示」といっているのは packet の受信は出来るけど、UDP などのヘッダーを正しく表示できないって事です。 全部のヘッダー対応の source 書くぐらいなら tcpdump や snoop つかったほうがよいです(笑)。 解説†DLPI の説明をする上で STREAMS (ストリーム)機構 の解説を避けて通る事は出来ないのですが、はっきりいって一言で説明することは出来ないですし、なにより私がそれほど理解してないので非常ーにおおざっぱ説明させていただきます。 STREAMS 機構とは Solaris をはじめとする SVR4 準拠の system で採用されているネットワークプロトコルを開発するための下部構造であり、データの構造体、関数群、設計思想を現しています。つまり、IP や X25 などの異なるネットワークプロトコルを開発する場合にも同一の土俵(枠組み)にて開発する事ができるのです。実際に Solaris の TCP や IP もこの STEREAMS 機構にて実装されています。 今回の場合上図の「ストリームデバイスドライバ」とは network Interface のデバイスドライバの事を指しまして、この device をデータリンクサービスプロバイダー(データリンク層のサービスを提供してくれるもの)などと呼びます。network Interface は ethenet などのデータリンク層 に該当する役割を持っている事は周知の事だと思いますので、分かりやすいと思います。 以上、簡単に DLPI と STREAM について説明させていただきました。かなりいいかげんですが、上記のような大体の構成が分かれば、以下の各関数の流れが分かりやすいとおもいます。 では source の各関数を順に見ていきましょう。 55 union DL_primitives *dlp; 55 行目に DL_primitives という共用体を 宣言しています。primitives というのは後々も出てくるので、ここで説明しておきます。
DLPI で受け渡しされるメッセージは primitive という構造体の形をしており、どのような内容のメッセージを運ぶかによってその構造体の中で宣言されているメンバーが違います。そして primitive は 上記のストリームのメッセージタイプのどれかに該当しています。 84 if((fd = open (devpath , O_RDWR)) < 0 ){ 84 行目で device、つまり network Interface を open します。これにより上図の STREAM が新規に作成されるされることになります。 STREAM というのはあくまで概念ですので、実際には kernel memory 領域に STREAM のための 構造体群の領域が確保される事になります。(この辺りになると私も正直言ってかなり苦しいですが、、) 92 if(dlattachreq(fd, ppa, buf) < 0){ 84 行目で open した STREAM と、物理的な device を結び付けます。つまり system に接続されたどの物理 network Interface とやり取りを行うのかを指定しているわけです。ここで 309 行目の dlattachreq() に飛ぶわけですが、この関数の中では 317 attachreq.dl_primitive = DL_ATTACH_REQ; というのが宣言されています。これは上で説明した DLPI primitive でして、/usr/include/sys/dlpi.h 内で定義されています。 /usr/include/sys/dlpi.h より typedef struct { t_uscalar_t dl_primitive; /* set to DL_ATTACH_REQ */ t_uscalar_t dl_ppa; /* id of the PPA */ } dl_attach_req_t; ここでさらに strbuf 構造体(STREAM Buffer 構造体?)というのが出てきます。strbuf 構造体はユーザプロセスが直接 STREAM HEAD へメッセージを送受する際につかう putmsg()、getmsg() システムコールの引数として使われます。 /usr/include/sys/stropts.h より struct strbuf { int maxlen; /* no. of bytes in buffer */ int len; /* no. of bytes returned */ caddr_t buf; /* pointer to data */ }; strbuf 構造体の中の data のポインターとして dl_attach_req_t 構造体へのポインターを指定しているというわけです。
strbuf と dl_attach_req_t 両構造体の各メンバーに値の代入が終わったら、strbuf 構造体へのアドレスを putmsg() システムコールに渡します。これにより メッセージが STREAM HEAD を通じてダウンストリームに渡される事になります。 324 if (putmsg(fd, &ctlbuf, (struct strbuf*) NULL, flags) < 0){
putmsg() システムコールでは STREAM に対してデータバッファと制御バッファの2つのバッファを一度に送信する事が出来ます。しかしながらここでは実際のデータの送信ではなく、device に対する制御情報(ATTACH 要求)を送るだけなので、324 行目の putmsg ではデータ用の strbuf 構造体の引数としては NULL 値を渡しています。 333 if ((ret = getmsg(fd, &ctlbuf, (struct strbuf *)NULL, &flags)) < 0) { ここで今度は getmsg() というシステムコールが出てきます。
getmsg() は putmsg() とは逆に STREAM HEAD を通じてアップストリームからメッセージを読み込みという動作を行います。正常にいった場合は strbuf 構造体へのポインタである ctlbuf の各メンバーに値が入ることになるわけです。 338 primitive = (union DL_primitives *) ctlbuf.buf; 339 if ( primitive->dl_primitive != DL_OK_ACK){ 340 fprintf(stderr, "dlattachreq: not DL_OK_ACK\n"); 341 return(-1); 342 } ctlbuf は「buf」 というメンバーを持ち、「buf」は DLPI primitive 構造体であるはずです。(上図および、strbuf 構造体参照) われわれは、この primitive が DL_OK_ACK である事を期待しているわけですから、この DLPI primitive が DL_OK_ACK であるかどうかを判定しています。(339行目)ここまででエラーがなければ、main() に戻ってきます。 100 if(dlpromisconreq(fd, DL_PROMISC_PHYS, buf) < 0){ ... 108 if(dlbindreq (fd, ETHERTYPE_IP, 0, DL_CLDLS, 0, 0, buf) < 0){ 100 行目の dlpromisconreq() 及び、108 行目の dlbindreq() も動作の流れ的には上述の dlattachreq() と同じです。上図 のように 要求と確認応答というやり取りを行っています。違うのは要求内容(DLPI primitive)です。
さて、main() のつづきですが、つぎはいままでの処理とはちょっと違っています。 120 if(ioctl(fd, I_STR, &strioc) < 0){ 121 perror("ioctl: I_STR: DLIOCRAW"); 122 exit(1); 123 } ここで呼んでいる ioctl() システムコールがまた重要な処理になっています。
STREAMS ioctl() は ioctl() のサプセットで、ストリーム上の様々な機能を実行するために利用されます。120 行目にあるように command として I_STR を指定した場合、arg で指定しているstrioctl 構造体を STREAM のメッセージとしてDOWN STREAM をつうじて、デバイスへと伝えます。 struct strioctl { int ic_cmd; /* コマンド*/ int ic_timout; /*タイムアウト値 */ int ic_len; /* データ長 */ char *ic_dp; /* データへのポインタ */ }; 116 〜 119 行目を見ていただければ分かりますが、上記の構造体の各メンバーに代入されているのは、以下のようになります。 116 strioc.ic_cmd = DLIOCRAW; 117 strioc.ic_timout = -1; 118 strioc.ic_len = 0; 119 strioc.ic_dp = NULL; これは M_DATA タイプのメッセージに対して RAW モード、つまり、Ethernet ヘッダーを含んだ形での生のパケットを受け取るためのモード(M_DATA メッセージに対して何の変更も行わない)に移行するための設定になります。 RAW モードへの移行がおわったところで、さらに main() の処理がつづきます。 128 if (ioctl(fd, I_FLUSH, FLUSHR) < 0){ ioctl() システムコール(ここで言っているのは STREAMS ioctl() です。詳しくは man streamio(7I))は前述したとおり、STREMA 上の様々な機能を実行するためのシステムコールですが、コマンドとして I_FLUSH を指定した場合は、第三引数で指定したオプションに従い、STERAM 上の queue をフラッシュ(消去)する事が出来ます。 第三引数のオプションは sys/stropts.h に定義してありまして、128 行目で指定しているのは READ queue(読み取りキュー)をフラッシュする事です。 sys/stropts.h より抜粋 #define FLUSHR 0x01 /* flush read queue */ queue(キュー) という言葉が初めて出てきましたが、これも STREAM 機構の中では重要な用語の一つです。STREAM 機構とはデータを格納する構造体の集まりであると述べましたが、queue とは STREAM HEAD やデバイスが個々に持っているデータの一時保管場所のようなものです。つまり、STREAM HEAD にデータを渡すという事は、STREAM HEAD の queue にデータが(実際にはデータへのポインタが)格納されるという事です。 ここではその読み取り用の格納庫を明示的に空にしているという事になります。 さて、ここまでで packet のキャプチャーの準備が整いました。ここからが最後の データの獲得と、データの解釈、及び出力になります。 133 databuf.maxlen = MAXDLBUFSIZE; 134 databuf.len = 0; 135 databuf.buf = (caddr_t)buf; 136 137 while (getmsg(fd, NULL, &databuf, &flags) == 0) { 138 if (databuf.len > 0) 139 print_packet(databuf.buf, databuf.len); 140 } 「databuf」というのは strbuf 構造体で、54 行目に宣言しています。これは先の getmsg() のところで説明したように、STREAM から受け取ったメッセージを格納する場所になります。 print_packet() 内で行っている事は 先ほど getmsg() にて受け取った生データを Ethernet、IP、TCP の各ヘッダー構造体に当てはめ、それぞれの構造体のメンバー、つまりヘッダーの内容をプリントしているだけです。行っている処理事体は「Linux socket プログラミング/パケットモニタリングプログラム」で書いたものをほぼそのまま持ってきています。 最後に†というわけで非常ーに長々と、かつおおざっぱに解説させていただきました。 私自身本当に分からないところだらけで(だったら書くなという声が聞こえてきそうですが)苦しいのですが、DLPI の大体の概要が分かればコアの部分はそのまま使ってもいろいろ応用が出来るのではないかと思います。ただし、この手のパケットのキャプチャーを行うプログラムは使い方によっては不正利用にもつながりますのでお取り扱いは注意しましょう。まぁ、Solaris には端から snoop という packet capture tool がついているんだから問題ないでしょうけどね。 参考資料†このページを書く上で参考にした資料をご紹介いたします。
追記†すべての フレームタイプを受信する†先に書きましたように、このプログラムでは IP のpacket しか受信しません。すべてのフレームタイプを受信するためには、100 行目の dlpromiscast を以下のように変更します。 100 if(dlpromisconreq(fd, DL_PROMISC_SAP, buf) < 0){ dlpromisconreq() 関数の中で、この第二引数の DL_PROMISC_SAP は promiscon_req プリミティブの 「dl_level 」という変数に割り当てられています。 この promiscon_req プリミティブの dl_level に使われる値は /usr/include/sys/dlpi.h の中で以下のように定義されています。 /* * DLPI promiscuous mode definitions */ #define DL_PROMISC_PHYS 0x01 /* promiscuous mode at phys level */ #define DL_PROMISC_SAP 0x02 /* promiscous mode at sap level */ #define DL_PROMISC_MULTI 0x03 /* promiscuous mode for multicast */ これは promiscuous mode(無差別受信モード)をどのレベルで実行するかという事を設定するもので、上のプログラムでは phys レベルを指定しており、このレベルでは SAP(フレームタイプ) の種類によってフィルタリングが行われ、指定した SAP 以外の packet はうけとりません。ここを上記のように sap レベルとする事ですべてのフレームタイプの packet を受信することが出来ます。 さらに追記†DL_PROMISC_PHYS(PHYが レベル)ではローカルホストによって生成されたフレームを含む、メディア上のすべてのフレームの受信を有効になり、DL_PROMISC_SAP(SAPレベル)ではすべての SAP 値 (フレームタイプ) の受信を有効します。 |