- 追加された行はこの色です。
- 削除された行はこの色です。
#norelated
*目次 [#u9ef2a1e]
#contents
*概要 [#kad89755]
まず最初に白状しますが、ここで書いているプログラムの コアの部分は全くもって私のオリジナルではありません。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 ?) をつくってみます。
*プログラムのイメージ [#bc25ba74]
このプログラムの 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 の各構造体に当てはめて表示するだけ、、|
*ソースコード [#m121902e]
CENTER:[[sniff.c>source:sniff.c]]
1 /*
2 * packet monitor
3 * gcc sniff.c -lsocket -lnsl -o sniff
4 */
5 #include <netinet/in.h>
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <sys/stropts.h>
9 #include <sys/dlpi.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <sys/signal.h>
13 #include <sys/stream.h>
14 #include <string.h>
15 #include <net/if.h>
16 #include <netinet/if_ether.h>
17 #include <netinet/in_systm.h>
18 #include <netinet/tcp.h>
19 #include <netinet/ip.h>
20
21 #define MAXDLBUF 32768
22 #define MAXWAIT 15
23 #define FLAMETYPE 0x800
24
25 long databuf[MAXDLBUF];
26
27 main(argc, argv)
28 int argc;
29 char *argv[];
30 {
31 long buf[MAXDLBUF];
32 union DL_primitives *dlp;
33 int flags;
34 int ppa, sap;
35 int fd;
36 int i;
37 char *device;
38 struct strbuf data;
39
40 dlp = (union DL_primitives*) buf;
41
42 if (argc != 3)
43 printf(" Usage: %s device instance \n",argv[0]);
44
45 device = argv[1];
46 ppa = atoi (argv[2]);
47
48 if((fd = open (device , O_RDWR)) < 0 )
49 syserr(device);
50
51 dlattachreq(fd, ppa);
52 dlokack(fd, buf);
53
54 dlpromisconreq(fd, DL_PROMISC_PHYS);
55 dlokack(fd, buf);
56
57 dlbindreq (fd, FLAMETYPE, 0, DL_CLDLS, 0, 0);
58 dlbindack (fd, buf);
59
60 if (strioctl(fd, DLIOCRAW, -1, 0, NULL) < 0)
61 syserr("DLIOCRAW");
62
63 if (ioctl(fd, I_FLUSH, FLUSHR) < 0)
64 syserr("I_FLUSH");
65
66 data.buf = (char *) databuf;
67 data.maxlen = MAXDLBUF;
68 data.len = 0;
69
70 while (getmsg(fd, NULL, &data, &flags) == 0) {
71 print_pkt(data.buf);
72 }
73
74 return (0);
75 }
76
77 dlattachreq(fd, ppa)
78 int fd;
79 u_long ppa;
80 {
81 dl_attach_req_t attach_req;
82 struct strbuf ctl;
83 int flags;
84
85 attach_req.dl_primitive = DL_ATTACH_REQ;
86 attach_req.dl_ppa = ppa;
87
88 ctl.maxlen = 0;
89 ctl.len = sizeof (attach_req);
90 ctl.buf = (char *) &attach_req;
91
92 flags = 0;
93
94 if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0)
95 syserr("dlattachreq: putmsg");
96 }
97
98 dlbindreq(fd, sap, max_conind, service_mode, conn_mgmt, xidtest)
99 int fd;
100 u_long sap;
101 u_long max_conind;
102 u_long service_mode;
103 u_long conn_mgmt;
104 u_long xidtest;
105 {
106 dl_bind_req_t bind_req;
107 struct strbuf ctl;
108 int flags;
109
110 bind_req.dl_primitive = DL_BIND_REQ;
111 bind_req.dl_sap = sap;
112 bind_req.dl_max_conind = max_conind;
113 bind_req.dl_service_mode = service_mode;
114 bind_req.dl_conn_mgmt = conn_mgmt;
115 bind_req.dl_xidtest_flg = xidtest;
116
117 ctl.maxlen = 0;
118 ctl.len = sizeof (bind_req);
119 ctl.buf = (char *) &bind_req;
120
121 flags = 0;
122
123 if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0)
124 syserr("dlbindreq: putmsg");
125 }
126
127 dlbindack(fd, bufp)
128 int fd;
129 char *bufp;
130 {
131 union DL_primitives *dlp;
132 struct strbuf ctl;
133 int flags;
134
135 ctl.maxlen = MAXDLBUF;
136 ctl.len = 0;
137 ctl.buf = bufp;
138
139 strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlbindack");
140
141 dlp = (union DL_primitives *) ctl.buf;
142
143 expecting(DL_BIND_ACK, dlp);
144
145 if (flags != RS_HIPRI)
146 err("dlbindack: DL_OK_ACK was not M_PCPROTO");
147
148 if (ctl.len < sizeof (dl_bind_ack_t))
149 err("dlbindack: short response ctl.len: %d", ctl.len);
150 }
151
152 dlokack(fd, bufp)
153 int fd;
154 char *bufp;
155 {
156 union DL_primitives *dlp;
157 struct strbuf ctl;
158 int flags;
159
160 ctl.maxlen = MAXDLBUF;
161 ctl.len = 0;
162 ctl.buf = bufp;
163
164 strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack");
165
166 dlp = (union DL_primitives *) ctl.buf;
167
168 expecting(DL_OK_ACK, dlp);
169
170 if (ctl.len < sizeof (dl_ok_ack_t))
171 err("dlokack: response ctl.len too short: %d", ctl.len);
172
173 if (flags != RS_HIPRI)
174 err("dlokack: DL_OK_ACK was not M_PCPROTO");
175
176 if (ctl.len < sizeof (dl_ok_ack_t))
177 err("dlokack: short response ctl.len: %d", ctl.len);
178 }
179
180 syserr(s)
181 char *s;
182 {
183 (void) perror(s);
184 exit(1);
185 }
186
187 err(fmt, a1, a2, a3, a4)
188 char *fmt;
189 char *a1, *a2, *a3, *a4;
190 {
191 (void) fprintf(stderr, fmt, a1, a2, a3, a4);
192 (void) fprintf(stderr, "\n");
193 (void) exit(1);
194 }
195
196 void sigalrm()
197 {
198 (void) err("sigalrm: TIMEOUT");
199 }
200
201 strgetmsg(fd, ctlp, datap, flagsp, caller)
202 int fd;
203 struct strbuf *ctlp, *datap;
204 int *flagsp;
205 char *caller;
206 {
207 int rc;
208 static char errmsg[80];
209
210 (void) signal(SIGALRM, sigalrm);
211 if (alarm(MAXWAIT) < 0) {
212 (void) sprintf(errmsg, "%s: alarm", caller);
213 syserr(errmsg);
214 }
215
216 *flagsp = 0;
217 if ((rc = getmsg(fd, ctlp, datap, flagsp)) < 0) {
218 (void) sprintf(errmsg, "%s: getmsg", caller);
219 syserr(errmsg);
220 }
221
222 if (alarm(0) < 0) {
223 (void) sprintf(errmsg, "%s: alarm", caller);
224 syserr(errmsg);
225 }
226
227 if ((rc & (MORECTL | MOREDATA)) == (MORECTL | MOREDATA))
228 err("%s: MORECTL|MOREDATA", caller);
229 if (rc & MORECTL)
230 err("%s: MORECTL", caller);
231 if (rc & MOREDATA)
232 err("%s: MOREDATA", caller);
233
234 if (ctlp->len < sizeof (long))
235 err("getmsg: control portion length < sizeof (long): %d", ctlp->len);
236 }
237
238 expecting(prim, dlp)
239 int prim;
240 union DL_primitives *dlp;
241 {
242 if (dlp->dl_primitive != (u_long)prim) {
243 err("unexpected dlprim error\n");
244 exit(1);
245 }
246 }
247
248 dlpromisconreq(fd, level)
249 int fd;
250 u_long level;
251 {
252 dl_promiscon_req_t promiscon_req;
253 struct strbuf ctl;
254 int flags;
255
256 promiscon_req.dl_primitive = DL_PROMISCON_REQ;
257 promiscon_req.dl_level = level;
258
259 ctl.maxlen = 0;
260 ctl.len = sizeof (promiscon_req);
261 ctl.buf = (char *) &promiscon_req;
262
263 flags = 0;
264
265 if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0)
266 syserr("dlpromiscon: putmsg");
267
268 }
269
270 strioctl(fd, cmd, timout, len, dp)
271 int fd;
272 int cmd;
273 int timout;
274 int len;
275 char *dp;
276 {
277 struct strioctl sioc;
278 int rc;
279
280 sioc.ic_cmd = cmd;
281 sioc.ic_timout = timout;
282 sioc.ic_len = len;
283 sioc.ic_dp = dp;
284
285 rc = ioctl(fd, I_STR, &sioc);
286
287 if (rc < 0)
288 return (rc);
289 else
290 return (sioc.ic_len);
291 }
292
293 print_pkt(data)
294 char *data;
295 {
296 struct in_addr insaddr, indaddr;
297 int i, j;
298
299 struct dgram
300 {
301 struct ether_header ether;
302 struct ip ip;
303 struct tcphdr tcp;
304 unsigned char blah[65535];
305 } buf_ether, buf;
306
307 buf_ether = *(struct dgram *) data; /* ethernet header 表示用のデータ*/
308 data-=2; /* ether header 以降何故か pointer の address がずれる、ので修正 */
309 buf = *(struct dgram *) data; /* TCP/IP header 表示用のデータ*/
310
311 printf("\n----Ether Header----\n");
312 printf("src addr : ");
313 for ( i =0;i<6;i++)
314 printf("%x:",(buf_ether.ether.ether_shost.ether_addr_octet[i]));
315 printf("\n");
316 printf("dest addr : ");
317 for ( i =0;i<6;i++)
318 printf("%x:",(buf_ether.ether.ether_dhost.ether_addr_octet[i]));
319 printf("\n");
320 printf("ether type : %x\n",buf_ether.ether.ether_type);
321
322 insaddr.s_addr = buf.ip.ip_src._S_un._S_addr;
323 indaddr.s_addr = buf.ip.ip_dst._S_un._S_addr;
324
325
326 printf("----IP Header----\n");
327 printf("version : %d\n",buf.ip.ip_v);
328 printf("ihl : %d\n",buf.ip.ip_hl);
329 printf("tos : %d\n",buf.ip.ip_tos);
330 printf("tot length : %d\n",buf.ip.ip_len);
331 printf("id : %d\n",ntohs(buf.ip.ip_id));
332 printf("frag_off : %d\n",buf.ip.ip_off);
333 printf("ttl : %d\n",buf.ip.ip_ttl);
334 printf("protocol : %d\n",buf.ip.ip_p);
335 printf("check : %d\n",buf.ip.ip_sum);
336 printf("saddr : %s\n",inet_ntoa(insaddr));
337 printf("daddr : %s\n",inet_ntoa(indaddr));
338
339 printf("----TCP Header----\n");
340 printf("source port : %d\n",ntohs(buf.tcp.th_sport));
341 printf("dest port : %d\n",ntohs(buf.tcp.th_dport));
342 printf("sequence : %u\n",ntohl(buf.tcp.th_seq));
343 printf("ack seq : %u\n",ntohl(buf.tcp.th_ack));
344 printf("data offset : %d\n",ntohl(buf.tcp.th_off));
345 printf("flags : ");
346 if((buf.tcp.th_flags | TH_FIN) == buf.tcp.th_flags)
347 printf("FIN ");
348 if((buf.tcp.th_flags | TH_SYN) == buf.tcp.th_flags)
349 printf("SIN ");
350 if((buf.tcp.th_flags | TH_RST) == buf.tcp.th_flags)
351 printf("RST ");
352 if((buf.tcp.th_flags | TH_PUSH) == buf.tcp.th_flags)
353 printf("PUSH ");
354 if((buf.tcp.th_flags | TH_ACK) == buf.tcp.th_flags)
355 printf("ACK ");
356 if((buf.tcp.th_flags | TH_URG) == buf.tcp.th_flags)
357 printf("URG ");
358 printf("\n");
359 printf("window : %d\n",ntohs(buf.tcp.th_win));
360 printf("check : %d\n",buf.tcp.th_sum);
361 printf("urt_ptr : %d\n",buf.tcp.th_urp);
362
363 printf("------DATA-------\n");
364 printf("%s\n\n\n",(buf.blah));
365 }
ソースファイル [[sniff.c>source:sniff.c]]
*実行例 [#xee077ab]
プログラム名の後に引数として NIC のデバイスパス、NIC の instance 番号 を入力します。&br;
以下の 例では Sun の FastEthernet カードである /dev/hme0 を指定しています。&br;
デバイスパス(/dev/hme)と instance 番号(0) は スペースで開けて指定します。&br;
なお、このプログラムも root ユーザで実行する必要があります。&br;
setuid(chmod 4755 sniff )してもいいですけど。&br;
# ./sniff /dev/hme 0
----Ether Header----
src addr : 8:0:20:91:a6:90:
dest addr : 0:0:f4:5d:6a:a9:
ether type : 800
----IP Header----
version : 4
ihl : 5
tos : 0
tot length : 40
id : 59439
frag_off : 16384
ttl : 64
protocol : 6
check : 26633
saddr : 172.29.73.90
daddr : 172.29.73.2
----TCP Header----
source port : 1021
dest port : 2049
sequence : 1137017927
ack seq : 1486710309
data offset : 5
flags : ACK
window : 50400
check : 28047
urt_ptr : 0
------DATA-------
NIC に到着したすべての packet を表示します。&br;
内容をみると、telnet のセッションの PUSH + ACK のパケットであることが分かるとおもいます。&br;
この例では出ていませんが、DATA 以降に text があればそれも表示します。
あと、言い忘れましたが これは TCP packet 専用です。ICMP や ARP はおろか、UDP packet も表示できません。「表示」といっているのは packet の受信は出来るけど、UDP などの HEDER を正しく表示できないって事です。
全部の header 対応の source 書くぐらいなら tcpdump や snoop つかったほうがよいです(笑)。
*解説 [#p3abc845]
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 の各関数を順に見ていきましょう。
32 union DL_primitives *dlp;
32 行目に DL_primitives という共用体を 宣言しています。primitives というのは後々も出てくるので、ここで説明しておきます。&br;
上の STREAMS 機構の説明のところで、STREAM(データの通り道)は「メッセージ」という形でデータを運ぶと書きましたが、このメッセージはいくつかメッセージタイプ(message type)が存在しまして、それぞれが役割を持っています。以下にこのプログラムで用いられている3つのメッセージタイプを示します。&br;
|メッセージタイプ|説明|h
|M_DATA| ユーザプロセスとデバイスドライバ間のデータの受け渡しに使われる。|
|M_PROTO |ユーザプロセスとデバイスドライバ間の制御情報の受け渡しに使われる|
|M_PCPROTO | M_PROTO と同じだが、より優先度の高いメッセージタイプ|
DLPI で受け渡しされるメッセージは primitive という構造体の形をしており、どのような内容のメッセージを運ぶかによってその構造体の中で宣言されているメンバーが違います。そして primitive は 上記のストリームのメッセージタイプのどれかに該当しています。
48 if((fd = open (device , O_RDWR)) < 0 )
48 行目で device、つまり network Interface を open します。これにより上図の STREAM が新規に作成されるされることになります。 STREAM というのはあくまで概念ですので、実際には kernel memory 領域に STREAM のための 構造体群の領域が確保される事になります。(この辺りになると私も正直言ってかなり苦しいですが、、)
51 dlattachreq(fd, ppa);
48 行目で open した STREAM と、物理的な device を結び付けます。つまり system に接続されたどの物理 network Interface とやり取りを行うのかを指定しているわけです。ここで 77 行目の dlattachreq() に飛ぶわけですが、この関数の中では
85 attach_req.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 を通じてダウンストリームに渡される事になります。
94 if (putmsg(fd, &ctl, (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 要求)を送るだけなので、94 行目の putmsg ではデータ用の strbuf 構造体の引数としては NULL 値を渡しています。
#ref(http://www.whiteboard.ne.jp/~admin2/pict/dlpi_sequence.gif)
さて、これで ATTACHの要求の送信は終了しまして、再度 main() 関数に戻りますとつづいてdlokack() 関数に呼び出す事になります。これは ATTACH 要求に対する確認応答を受け取る動作になります。 ATTACH要求に限らず、DLPI では常に以下のような要求・確認応答 という手順を踏みます。
152 行目の dlokack() 関数の主な動作は 201 行目の strgetmsg() 関数を呼び出し、確認応答のメッセージを受け取る事です。&br;
概要が分かったところで、途中を一気に省いて 201 行目の strgetmsg() に飛びます。
201 strgetmsg(fd, ctlp, datap, flagsp, caller)
この関数の中で今度は 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;|
217 if ((rc = getmsg(fd, ctlp, datap, flagsp)) < 0) {
218 (void) sprintf(errmsg, "%s: getmsg", caller);
219 syserr(errmsg);
220 }
getmsg() は putmsg() とは逆に STREAM HEAD を通じてアップストリームからメッセージを読み込みという動作を行います。&br;
いろいろなエラー処理がありますが、要するに正常にいった場合は strbuf 構造体へのポインタである ctlp の各メンバーに値が入り(かなりいいかげんですが) ここで、dlokack() 関数に戻りますと、strbuf 構造体である ctl の各メンバに値が入った事になるわけです。
164 strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack");
165
166 dlp = (union DL_primitives *) ctl.buf;
167
168 expecting(DL_OK_ACK, dlp);
ctl は「buf」 というメンバーを持ち、「buf」は DLPI primitive 構造体であるはずです。(上図および、strbuf 構造体参照) われわれは、この primitive が DL__OK_ACK である事を期待しているわけですから、expecting() という関数にて、この DLPI primitive が DL__OK_ACK であるかどうかを判定しています。(168行目) expecting() にてエラーが無く、 dlokack() のほかの部分にもエラーがなければ、main() に戻ってきます。
54 dlpromisconreq(fd, DL_PROMISC_PHYS);
55 dlokack(fd, buf);
56
57 dlbindreq (fd, FLAMETYPE, 0, DL_CLDLS, 0, 0);
58 dlbindack (fd, buf);
54 〜55 行目の dlpromisconreq() 、dlokack() 及び、57 〜 58 行目の dlbindreq() 、dlbindack() も動作の流れ的には上述の dlattachreq() と同じです。上図 のように 要求と確認応答というやり取りを行っています。違うのは要求内容(DLPI primitive)です。
|dlpromisconreq|DL_PROMISCON_REQ primitive にて promiscast (無差別受信)モード への移行を要求 (追記参照)|
|dlbindreq |DL_BIND_REQ primitive にて データリンク層の SAPを STREAM に BIND(関連付け)を要求|
さて、main() のつづきですが、つぎはいままでの処理とはちょっと違っています。
60 if (strioctl(fd, DLIOCRAW, -1, 0, NULL) < 0)
61 syserr("DLIOCRAW");
ここでは 270 行目の strioctl() を呼んでいるわけですが、この strioctl() 内で呼んでいる 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() のサプセットで、ストリーム上の様々な機能を実行するために利用されます。285 行目にあるように command として I_STR を指定した場合、arg で指定しているstrioctl 構造体を STREAM のメッセージとしてDOWN STREAM をつうじて、デバイスへと伝えます。
285 rc = ioctl(fd, I_STR, &sioc);
では、この ioctl にてどのような内容のメッセージが DOWN STREAM に渡されているかを見てみます。以下は ioctl の3番目の引数として渡されている strioctl 構造体です。
struct strioctl {
int ic_cmd; /* コマンド*/
int ic_timout; /*タイムアウト値 */
int ic_len; /* データ長 */
char *ic_dp; /* データへのポインタ */
};
280 〜 283 行目を見ていただければ分かりますが、上記の構造体の各メンバーに代入されているのは、以下のようになります。
cmd=DLIOCRAW
ic_timeout=-1
ic_len=0
ic_dp=NULL
これは M_DATA タイプのメッセージに対して RAW モード、つまり、Ethernet ヘッダーを含んだ形での生のパケットを受け取るためのモード(M_DATA メッセージに対して何の変更も行わない)に移行するための設定になります。
RAW モードへの移行がおわったところで、再び main() にもどります。
63 if (ioctl(fd, I_FLUSH, FLUSHR) < 0)
ioctl() システムコール(ここで言っているのは STREAMS ioctl() です。詳しくは man streamio(7I))は前述したとおり、STREMA 上の様々な機能を実行するためのシステムコールですが、コマンドとして I_FLUSH を指定した場合は、第三引数で指定したオプションに従い、STERAM 上の queue をフラッシュ(消去)する事が出来ます。
第三引数のオプションは sys/stropts.h に定義してありまして、63 行目で指定しているのは READ queue(読み取りキュー)をフラッシュする事です。
sys/stropts.h より抜粋
#define FLUSHR 0x01 /* flush read queue */
queue(キュー) という言葉が初めて出てきましたが、これも STREAM 機構の中では重要な用語の一つです。STREAM 機構とはデータを格納する構造体の集まりであると述べましたが、queue とは STREAM HEAD やデバイスが個々に持っているデータの一時保管場所のようなものです。つまり、STREAM HEAD にデータを渡すという事は、STREAM HEAD の queue にデータが(実際にはデータへのポインタが)格納されるという事です。
ここではその読み取り用の格納庫を明示的に空にしているという事になります。
さて、ここまでで packet のキャプチャーの準備が整いました。ここからが最後の データの獲得と、データの解釈、及び出力になります。
66 data.buf = (char *) databuf;
67 data.maxlen = MAXDLBUF;
68 data.len = 0;
69
70 while (getmsg(fd, NULL, &data, &flags) == 0) {
「data」というのは strbuf 構造体で、38 行目に宣言しています。これは先の getmsg() のところで説明したように、STREAM から受け取ったメッセージを格納する場所になります。&br;
getmsg はすでにお話しているのでさらっと流しますが、第二引数には NULL を指定していますので、制御情報の取得は行わず、データのみを受け取るようにしています。&br;
STREAM HEAD から受け取った生データは strbuf 構造体 data の メンバである data.buf に格納されていますのでこのデータを(実際にはアドレスですが、、)69 行目で最後の最後の処理になる print_pkt() 関数に渡しています。
print_pkt() 内で行っている事は 先ほど getmsg() にて受け取った生データを Ethernet、IP、TCP の各ヘッダー構造体に当てはめ、それぞれの構造体のメンバー、つまりヘッダーの内容をプリントしているだけです。行っている処理事体は「ソケットプログラミング」のところで書いている「パケットモニタープログラム」をほぼそのまま持ってきています。&br;
ただ違うのは 今回は Ethernet の ヘッダーも入るので、データを当てはめる構造体として ether_header 構造体を追加している事と、Solaris と Linux で、IP、TCP のヘッダー構造体のメンバーが若干違っていたので、printf のところでメンバー名の指定を Solaris 様に書き直したくらいです。ヘッダーの中で一番違ったのは TCP のフラグの表現の仕方です。Linux では各フラグが 1 bit づつの個別のメンバーだったのですが、Solaris では一つの tcp.th_flags という 8 bit のメンバーになっており、 どのフラグが立っているかどうかの判定は、各 bit を比較してやらなければなりませんでした。
307 buf_ether = *(struct dgram *) data; /* ethernet header 表示用のデータ*/
308 data-=2; /* ether header 以降何故か pointer の address がずれる、ので修正 */
309 buf = *(struct dgram *) data; /* TCP/IP header 表示用のデータ*/
あと、どーにもカッコ悪いのがこの部分なのですが、Ethernet + IP + TCP という風に各プロトコルのヘッダーを足しているのですが、何故か IP のヘッダー以降のアドレスがずれてしまうという現象が発生したので、少々力技ですが Ethernet ヘッダーを表示した後、データのアドレスを 2 byte ずらしてから IP、TCP のヘッダーを表示させるようにしました。&br;
Ethernet ヘッダーの大きさが 14 bit であるところが問題のようなのですが、普通はこういう場合どうするんでしょう?(libcap のソースを見れば分かるのかもしれませんが、面倒臭かったので確認してません)
*最後に、、、 [#w60f98c8]
というわけで非常ーに長々と、かつおおざっぱに解説させていただきました。
私自身本当に分からないところだらけで(だったら書くなという声が聞こえてきそうですが)苦しいのですが、DLPI の大体の概要が分かればコアの部分はそのまま使ってもいろいろ応用が出来るのではないかと思います。ただし、この手のパケットのキャプチャーを行うプログラムは使い方によっては不正利用にもつながりますのでお取り扱いは注意しましょう。まぁ、Solaris には端から snoop という packet capture tool がついているんだから問題ないでしょうけどね。
*参考資料 [#r4c836e7]
このページを書く上で参考にした資料をご紹介いたします。
|参考資料名 |メモ|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 周りの解説が書いてある数少ない情報の一つかと、、|
*追記 [#g3fa4042]
**すべての フレームタイプを受信する [#y1bb3923]
先に書きましたように、このプログラムでは IP のpacket しか受信しません。すべてのフレームタイプを受信するためには、54 行目の dlpromiscast を以下のように変更します。
54 dlpromisconreq(fd, DL_PROMISC_SAP);
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 も受信できるようになりますが、当然上のプログラムの 54 行目を書き換えただけでは packet を受け取れるようになるだけなので、ヘッダーの表示はめちゃくちゃになります。
**さらに追記 [#s39a5fb0]
DL_PROMISC_PHYS(PHYが レベル)ではローカルホストによって生成されたフレームを含む、メディア上のすべてのフレームの受信を有効になり、DL_PROMISC_SAP(SAPレベル)ではすべての SAP 値 (フレームタイプ) の受信を有効します。