- 追加された行はこの色です。
- 削除された行はこの色です。
[[Solaris DLPI プログラミング/パケットモニター]]
#norelated
*目次 [#m0d7cb0e]
#contents
*概要 [#me4bb584]
まず最初に白状しますが、ここで書いているプログラムの コアの部分は全くもって私のオリジナルではありません。internet 上にある DLPI をつかったプログラムを参考にして切り張りして作っています。ただ、いくつかのプログラムを見たところ、ほぼおんなじ source をつかっており、ものは違ってもつかっている関数名がまったく同じだったり、関数の中見が同じだったりするので、おそらくどこかにたたき台となっている source が存在するのでしょう。(tcpdump で利用する libpcap も似たようなものです)ここは開き直ってプログラムの簡略化と、説明とに注力したいと思います。
&br;
DLPI とは Data Link Provider Interface の略で solaris が提供しているデータリンク層のプロトコルに直接アクセスするためのインターフェースです。つまり DLPI を使えば socket プログラムの SOCK_RAW で入手していたような生パケットが送受できるって事です。かつ、データリンク層に直接アクセスできるわけですから、Ethernet ヘッダーさえもを自由に送受できると言う事です。これはなかなか夢が膨らむインターフェースです。&br;
DLPI をつかえばいろいろ面白いものが出来そうですが、ここでは例によって 400 行足らずの簡易なパケットモニター(packet scanner, packet sniffer ?) をつくってみます。
*プログラムのイメージ [#scd6b08d]
このプログラムの main() の流れを以下に示します。最後の getmsg と print_pkt を loop しているだけで他は一直線なプログラムなので流れは分かりやすいかと思います。
PPA と SAP ってのが出てきますが、PPA は network Interface の instance 番号( /dev/hme0 の 0)のことで、SAP ってのは Ethernet のフレームタイプです。Ethernet フレームタイプには IP(0x0800)や、ARP(0x0806)などがあります。このプログラムでは フレームタイプとして IP(0x0800)を指定しています。
|CENTER:流れ|CENTER: 解説|h
|CENTER:open()| device(network Interface)をオープン|
|CENTER:↓ ||
|CENTER:dlattachreq()| オープンしたデバイスの PPA(physica point of attachment)に STREAM をアタッチ(関連付け)する|
|CENTER:↓ ||
|CENTER:dlokack() |上記の要求の ACK(確認応答)を受け取る。|
|CENTER:↓ ||
|CENTER:dlpromisconreq() | promiscast(無差別受信)モード へ移行を要求|
|CENTER:↓ ||
|CENTER:dlokack()| 上記の要求の ACK を受け取る。|
|CENTER:↓ ||
|CENTER:dlbindreq()| データリンク層の SAP(service access point)を STREAM に BIND(関連付け)する。|
|CENTER:↓ ||
|CENTER:dlokack()| 上記の要求の ACK を受け取る。|
|CENTER:↓||
|CENTER:strioctl() | ioctl() を通じて device を RAW モードに移行するよう要求。|
|CENTER:↓ ||
|CENTER:ioctl(,I_FLUSH,FLUSHR)| ioctl() を通じてSTREMA の READ QUEUE のフラッシュ を要求。|
|CENTER:↓ ||
|CENTER:getmsg() |STREAM の READ QUEUE から生データをを読み込む。(以下繰り返す)|
|CENTER:↓ ||
|CENTER:print_pkt()| あとは、読み込んだデータを Ether、IP、TCP の各構造体に当てはめて表示するだけ、、|
*ソースコード [#teaa7bd0]
CENTER:[[sniff.c>source:sniff.c]]
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>source:sniff.c]]
*実行例 [#i0fb3299]
プログラム名の後に引数としてインターフェース名を入力します。&br;
以下の 例では Sun の FastEthernet カードである hme0 を指定しています。&br;
なお、このプログラムも root ユーザで実行する必要があります。&br;
setuid(chmod 4755 sniff )してもいいですけど。&br;
# ./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 を表示します。&br;
内容をみると、telnet のセッションのパケットであることが分かるとおもいます。&br;
この例では出ていませんが、データ部に文字列があればそれも--DATA--以降に表示します。
あと、言い忘れましたが これは TCP パケット専用です。ICMP や ARP はおろか、UDP パケットも表示できません。「表示」といっているのは packet の受信は出来るけど、UDP などのヘッダーを正しく表示できないって事です。
全部のヘッダー対応の source 書くぐらいなら tcpdump や snoop つかったほうがよいです(笑)。
*解説 [#q678dd85]
DLPI の説明をする上で STREAMS (ストリーム)機構 の解説を避けて通る事は出来ないのですが、はっきりいって一言で説明することは出来ないですし、なにより私がそれほど理解してないので非常ーにおおざっぱ説明させていただきます。
STREAMS 機構とは Solaris をはじめとする SVR4 準拠の system で採用されているネットワークプロトコルを開発するための下部構造であり、データの構造体、関数群、設計思想を現しています。つまり、IP や X25 などの異なるネットワークプロトコルを開発する場合にも同一の土俵(枠組み)にて開発する事ができるのです。実際に Solaris の TCP や IP もこの STEREAMS 機構にて実装されています。&br;
STREAM とは論理的なデータの通り道であり、STREAM 内で 受け渡しされるデータはメッセージという形(実際には一連の構造体群)にて伝送され、ユーザプロセスはSTREAM HEAD(ストリームヘッド)と呼ばれる窓口を通してデータのやり取りを行う事になります。STREAM には STREAM HEAD から device driverに向かう道と、device driverから STREAM HEAD に向かう道とに別れており、それぞれを「アップストリーム」、「ダウンストリーム」と呼びます。
#ref(http://www.whiteboard.ne.jp/~admin2/pict/stream.gif)
今回の場合上図の「ストリームデバイスドライバ」とは network Interface のデバイスドライバの事を指しまして、この device をデータリンクサービスプロバイダー(データリンク層のサービスを提供してくれるもの)などと呼びます。network Interface は ethenet などのデータリンク層 に該当する役割を持っている事は周知の事だと思いますので、分かりやすいと思います。&br;
DLPI(Data Link Provider Interface)とは この network Interface ドライバーが提供しているデータリンク層に直接アクセスできるインターフェースという事になります。ユーザプロセスはストリームヘッドを通じてこの DLPI にアクセスし、必要な設定を行ったり、メッセージ(データ)を受け取ったりするわけです。&br;
以上、簡単に DLPI と STREAM について説明させていただきました。かなりいいかげんですが、上記のような大体の構成が分かれば、以下の各関数の流れが分かりやすいとおもいます。 では source の各関数を順に見ていきましょう。
55 union DL_primitives *dlp;
55 行目に DL_primitives という共用体を 宣言しています。primitives というのは後々も出てくるので、ここで説明しておきます。&br;
上の STREAMS 機構の説明のところで、STREAM(データの通り道)は「メッセージ」という形でデータを運ぶと書きましたが、このメッセージはいくつかメッセージタイプ(message type)が存在しまして、それぞれが役割を持っています。以下にこのプログラムで用いられている3つのメッセージタイプを示します。&br;
|メッセージタイプ|説明|h
|M_DATA| ユーザプロセスとデバイスドライバ間のデータの受け渡しに使われる。|
|M_PROTO |ユーザプロセスとデバイスドライバ間の制御情報の受け渡しに使われる|
|M_PCPROTO | M_PROTO と同じだが、より優先度の高いメッセージタイプ|
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 内で定義されています。&br;
dl_attach_req_t 構造体は まさに STREAM と PPA を attach するのを要求する primitive であり、STREAM メッセージタイプでは M_PROTO タイプになります。
/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 構造体へのポインターを指定しているというわけです。
|>|CENTER:strbuf 構造体|h
|>|CENTER:maxlen(最大buffer長)|
|>|CENTER:len(data長)|
|>|CENTER:dl_attach_req_t 構造体|
||CENTER:DL_ATTACH_REQ|
||CENTER:ppa|
strbuf と dl_attach_req_t 両構造体の各メンバーに値の代入が終わったら、strbuf 構造体へのアドレスを putmsg() システムコールに渡します。これにより メッセージが STREAM HEAD を通じてダウンストリームに渡される事になります。
324 if (putmsg(fd, &ctlbuf, (struct strbuf*) NULL, flags) < 0){
|>|&size(20){putmsg()};|h
|概要| STREAM へのメッセージの送信|
|インクルードファイル |#include <stropts.h>|
|形式| int putmsg(int fildes, const struct strbuf *ctlptr, const struct strbuf *da taptr, int flags);|
|引数 |int filedes : ファイル記述子&br;const struct clptr *ctlptr: 制御用の strbuf 構造体へのポインタ&br;const struct strbuf *dataptr: データ用の strbuf 構造体へのポインタ&br;int flags: フラグ|
|戻り値 |成功時: 0&br;エラー: -1|
putmsg() システムコールでは STREAM に対してデータバッファと制御バッファの2つのバッファを一度に送信する事が出来ます。しかしながらここでは実際のデータの送信ではなく、device に対する制御情報(ATTACH 要求)を送るだけなので、324 行目の putmsg ではデータ用の strbuf 構造体の引数としては NULL 値を渡しています。&br;
さて、これで ATTACHの要求の送信は終了しまして、つづいてATTACH 要求に対する確認応答を受け取る動作になります。 ATTACH要求に限らず、DLPI では常に以下のような要求・確認応答 という手順を踏みます。
#ref(http://www.whiteboard.ne.jp/~admin2/pict/dlpi_sequence.gif)
333 if ((ret = getmsg(fd, &ctlbuf, (struct strbuf *)NULL, &flags)) < 0) {
ここで今度は getmsg() というシステムコールが出てきます。
|>|&size(20){getmsg()};|h
|概要| STREAM からのメッセージの受信|
|インクルードファイル |#include <stropts.h>|
|形式| int getmsg(int fildes, struct strbuf *ctlptr, struct strbuf *dataptr, int * flagsp);|
|引数 |int filedes : ファイル記述子&br;const struct clptr *ctlptr: 制御用の strbuf 構造体へのポインタ&br;const struct strbuf *dataptr: データ用の strbuf 構造体へのポインタ&br;int flags: フラグ|
|戻り値 |成功時: 実数値&br; 0 --> すべてのメッセージの読み込み成功&br;MOREDATA --> まだデータが残っている&br;MORECTL --> まだ制御情報が残っている&br;エラー: -1&br;|
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)です。
|dlpromisconreq|DL_PROMISCON_REQ primitive にて promiscast (無差別受信)モード への移行を要求 (追記参照)|
|dlpromisconreq|DL_PROMISCON_REQ primitive にて promiscast (無差別受信)モード への移行を要求 ([[追記>#tsuiki]]参照)|
|dlbindreq |DL_BIND_REQ primitive にて データリンク層の SAPを STREAM に BIND(関連付け)を要求|
さて、main() のつづきですが、つぎはいままでの処理とはちょっと違っています。
120 if(ioctl(fd, I_STR, &strioc) < 0){
121 perror("ioctl: I_STR: DLIOCRAW");
122 exit(1);
123 }
ここで呼んでいる ioctl() システムコールがまた重要な処理になっています。
|>|&size(20){ioctl()};|h
|概要|STREAM デバイスの制御|
|インクルードファイル |#include <unistd.h>&br;#include <stropts.h>&br;#include <sys/conf.h>|
|形式| int ioctl(int fildes, int command, /* arg */ ...);|
|引数 |int filedes : ファイル記述子&br;int command: デバイスに実行させるコマンド&br;arg: strioctl 構造体へのポインタ|
|戻り値 |成功時: 実数値&br;エラー: -1|
STREAMS ioctl() は ioctl() のサプセットで、ストリーム上の様々な機能を実行するために利用されます。120 行目にあるように command として I_STR を指定した場合、arg で指定しているstrioctl 構造体を STREAM のメッセージとしてDOWN STREAM をつうじて、デバイスへと伝えます。&br;
では、この ioctl にてどのような内容のメッセージが DOWN STREAM に渡されているかを見てみます。以下は ioctl の3番目の引数として渡されている strioctl 構造体です。
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 から受け取ったメッセージを格納する場所になります。&br;
getmsg はすでにお話しているのでさらっと流しますが、第二引数には NULL を指定していますので、制御情報の取得は行わず、データのみを受け取るようにしています。&br;
STREAM HEAD から受け取った生データは strbuf 構造体 data の メンバである databuf.buf に格納されていますのでこのデータを 139 行目で最後の最後の処理になる print_packet() 関数に渡しています。
print_packet() 内で行っている事は 先ほど getmsg() にて受け取った生データを Ethernet、IP、TCP の各ヘッダー構造体に当てはめ、それぞれの構造体のメンバー、つまりヘッダーの内容をプリントしているだけです。行っている処理事体は「ソケットプログラミング」のところで書いている「パケットモニタープログラム」をほぼそのまま持ってきています。&br;
print_packet() 内で行っている事は 先ほど getmsg() にて受け取った生データを Ethernet、IP、TCP の各ヘッダー構造体に当てはめ、それぞれの構造体のメンバー、つまりヘッダーの内容をプリントしているだけです。行っている処理事体は「Linux socket プログラミング/パケットモニタリングプログラム」で書いたものをほぼそのまま持ってきています。&br;
ただ違うのは 今回は Ethernet の ヘッダーも入るので、データを当てはめる構造体として ether_header 構造体を追加している事と、Solaris と Linux で、IP、TCP のヘッダー構造体のメンバーが若干違っていたので、printf のところでメンバー名の指定を Solaris 様に書き直したくらいです。ヘッダーの中で一番違ったのは TCP のフラグの表現の仕方です。Linux では各フラグが 1 bit づつの個別のメンバーだったのですが、Solaris では一つの tcp.th_flags という 8 bit のメンバーになっており、 どのフラグが立っているかどうかの判定は、各 bit を比較してやらなければなりませんでした。
*最後に[#l186476e]
というわけで非常ーに長々と、かつおおざっぱに解説させていただきました。
私自身本当に分からないところだらけで(だったら書くなという声が聞こえてきそうですが)苦しいのですが、DLPI の大体の概要が分かればコアの部分はそのまま使ってもいろいろ応用が出来るのではないかと思います。ただし、この手のパケットのキャプチャーを行うプログラムは使い方によっては不正利用にもつながりますのでお取り扱いは注意しましょう。まぁ、Solaris には端から snoop という packet capture tool がついているんだから問題ないでしょうけどね。
*参考資料 [#gd0c4185]
このページを書く上で参考にした資料をご紹介いたします。
|参考資料名 |メモ|h
|UNIX カーネルの魔法&br;(プレンティスホール出版)| SVR4 の カーネル関連の本。しかしながら STREAM の部分しか読んでません。というか他の部分読んでも分からない、、(泣)|
|UNIX ネットワークプログラミング&br;(トッパン)| まさに UNIX 上でのネットワークプログラミングの本。情報がちょっと古いのが難点ですが、基本は押さえられます。|
|詳解TCP/IP&br;(ソフトバンク) | TCP/IP のプロトコル上の話に関してはバイブル的な本です。|
|man page| 一番身近な情報源&br;dlpi(7P), le(7D), streamio(7I), getmsg(2), putmsg(2)|
|docs.sun.com | リンクしているのは Solaris 8 の hme の man ページですが、日本語で DLPI 周りの解説が書いてある数少ない情報の一つかと、、|
&aname(tsuiki);
*追記 [#b1795512]
**すべての フレームタイプを受信する [#ma102d9d]
先に書きましたように、このプログラムでは 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 を受信することが出来ます。&br;
名前のイメージ的には DL_PROMISC_PHYS が すべて受信できて DL_PROMISC_SAP が SAP でのフィルターを行うようにも思えるのですが、試してみると「DL_PROMISC_SAP」にセットすることによってすべてのフレームタイプを受信できるようになりました。&br;
「DL_PROMISC_SAP」にセットすることによって ARP や REVARP の packet も受信できるようになりますが、当然上のプログラムの 100 行目を書き換えただけでは packet を受け取れるようになるだけなので、ヘッダーの表示はされません。
**さらに追記 [#h2618cba]
DL_PROMISC_PHYS(PHYが レベル)ではローカルホストによって生成されたフレームを含む、メディア上のすべてのフレームの受信を有効になり、DL_PROMISC_SAP(SAPレベル)ではすべての SAP 値 (フレームタイプ) の受信を有効します。