- 追加された行はこの色です。
- 削除された行はこの色です。
[[Solaris DLPI プログラミング/パケットモニター]]
#norelated
*目次 [#u9ef2a1e]
*目次 [#m0d7cb0e]
#contents
*概要 [#kad89755]
*概要 [#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 ?) をつくってみます。
*プログラムのイメージ [#bc25ba74]
*プログラムのイメージ [#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 の各構造体に当てはめて表示するだけ、、|
*ソースコード [#m121902e]
*ソースコード [#teaa7bd0]
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");
CENTER:[[dlpi-sniff-0.1.tar.gz>http://github.com/downloads/kaizawa/dlpi-sniff/dlpi-sniff-0.1.tar.gz]]
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 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 }
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 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;
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 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");
168 /*
169 * 全フレームサイズがデフォルト(最小)の ether, ip, tcp
170 * ヘッダー長の合計よりも小さかったら不正パケットとみなして
171 * リターン。
172 */
173 if(len < etherlen + sizeof(struct ip) + sizeof(struct tcphdr) )
174 return;
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];
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 (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;
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 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 }
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]]
*実行例 [#xee077ab]
プログラム名の後に引数として NIC のデバイスパス、NIC の instance 番号 を入力します。&br;
以下の 例では Sun の FastEthernet カードである /dev/hme0 を指定しています。&br;
デバイスパス(/dev/hme)と instance 番号(0) は スペースで開けて指定します。&br;
*実行例 [#i0fb3299]
プログラム名の後に引数としてインターフェース名を入力します。&br;
以下の 例では Sun の FastEthernet カードである hme0 を指定しています。&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-------
# ./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 のセッションの PUSH + ACK のパケットであることが分かるとおもいます。&br;
この例では出ていませんが、DATA 以降に text があればそれも表示します。
内容をみると、telnet のセッションのパケットであることが分かるとおもいます。&br;
この例では出ていませんが、データ部に文字列があればそれも--DATA--以降に表示します。
あと、言い忘れましたが これは TCP packet 専用です。ICMP や ARP はおろか、UDP packet も表示できません。「表示」といっているのは packet の受信は出来るけど、UDP などの HEDER を正しく表示できないって事です。
全部の header 対応の source 書くぐらいなら tcpdump や snoop つかったほうがよいです(笑)。
あと、言い忘れましたが これは TCP パケット専用です。ICMP や ARP はおろか、UDP パケットも表示できません。「表示」といっているのは packet の受信は出来るけど、UDP などのヘッダーを正しく表示できないって事です。
全部のヘッダー対応の source 書くぐらいなら tcpdump や snoop つかったほうがよいです(笑)。
*解説 [#p3abc845]
*解説 [#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 の各関数を順に見ていきましょう。
32 union DL_primitives *dlp;
55 union DL_primitives *dlp;
32 行目に DL_primitives という共用体を 宣言しています。primitives というのは後々も出てくるので、ここで説明しておきます。&br;
55 行目に 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 )
84 if((fd = open (devpath , O_RDWR)) < 0 ){
48 行目で device、つまり network Interface を open します。これにより上図の STREAM が新規に作成されるされることになります。 STREAM というのはあくまで概念ですので、実際には kernel memory 領域に STREAM のための 構造体群の領域が確保される事になります。(この辺りになると私も正直言ってかなり苦しいですが、、)
84 行目で device、つまり network Interface を open します。これにより上図の STREAM が新規に作成されるされることになります。 STREAM というのはあくまで概念ですので、実際には kernel memory 領域に STREAM のための 構造体群の領域が確保される事になります。(この辺りになると私も正直言ってかなり苦しいですが、、)
51 dlattachreq(fd, ppa);
92 if(dlattachreq(fd, ppa, buf) < 0){
48 行目で open した STREAM と、物理的な device を結び付けます。つまり system に接続されたどの物理 network Interface とやり取りを行うのかを指定しているわけです。ここで 77 行目の dlattachreq() に飛ぶわけですが、この関数の中では
84 行目で open した STREAM と、物理的な device を結び付けます。つまり system に接続されたどの物理 network Interface とやり取りを行うのかを指定しているわけです。ここで 309 行目の dlattachreq() に飛ぶわけですが、この関数の中では
85 attach_req.dl_primitive = DL_ATTACH_REQ;
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 を通じてダウンストリームに渡される事になります。
94 if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0)
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 要求)を送るだけなので、94 行目の putmsg ではデータ用の strbuf 構造体の引数としては NULL 値を渡しています。
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)
さて、これで ATTACHの要求の送信は終了しまして、再度 main() 関数に戻りますとつづいてdlokack() 関数に呼び出す事になります。これは ATTACH 要求に対する確認応答を受け取る動作になります。 ATTACH要求に限らず、DLPI では常に以下のような要求・確認応答 という手順を踏みます。
333 if ((ret = getmsg(fd, &ctlbuf, (struct strbuf *)NULL, &flags)) < 0) {
152 行目の dlokack() 関数の主な動作は 201 行目の strgetmsg() 関数を呼び出し、確認応答のメッセージを受け取る事です。&br;
概要が分かったところで、途中を一気に省いて 201 行目の strgetmsg() に飛びます。
ここで今度は getmsg() というシステムコールが出てきます。
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 を通じてアップストリームからメッセージを読み込みという動作を行います。正常にいった場合は strbuf 構造体へのポインタである ctlbuf の各メンバーに値が入ることになるわけです。
getmsg() は putmsg() とは逆に STREAM HEAD を通じてアップストリームからメッセージを読み込みという動作を行います。&br;
いろいろなエラー処理がありますが、要するに正常にいった場合は strbuf 構造体へのポインタである ctlp の各メンバーに値が入り(かなりいいかげんですが) ここで、dlokack() 関数に戻りますと、strbuf 構造体である ctl の各メンバに値が入った事になるわけです。
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 }
164 strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack");
165
166 dlp = (union DL_primitives *) ctl.buf;
167
168 expecting(DL_OK_ACK, dlp);
ctlbuf は「buf」 というメンバーを持ち、「buf」は DLPI primitive 構造体であるはずです。(上図および、strbuf 構造体参照) われわれは、この primitive が DL_OK_ACK である事を期待しているわけですから、この DLPI primitive が DL_OK_ACK であるかどうかを判定しています。(339行目)ここまででエラーがなければ、main() に戻ってきます。
ctl は「buf」 というメンバーを持ち、「buf」は DLPI primitive 構造体であるはずです。(上図および、strbuf 構造体参照) われわれは、この primitive が DL__OK_ACK である事を期待しているわけですから、expecting() という関数にて、この DLPI primitive が DL__OK_ACK であるかどうかを判定しています。(168行目) expecting() にてエラーが無く、 dlokack() のほかの部分にもエラーがなければ、main() に戻ってきます。
100 if(dlpromisconreq(fd, DL_PROMISC_PHYS, buf) < 0){
...
108 if(dlbindreq (fd, ETHERTYPE_IP, 0, DL_CLDLS, 0, 0, buf) < 0){
54 dlpromisconreq(fd, DL_PROMISC_PHYS);
55 dlokack(fd, buf);
56
57 dlbindreq (fd, FLAMETYPE, 0, DL_CLDLS, 0, 0);
58 dlbindack (fd, buf);
100 行目の dlpromisconreq() 及び、108 行目の dlbindreq() も動作の流れ的には上述の dlattachreq() と同じです。上図 のように 要求と確認応答というやり取りを行っています。違うのは要求内容(DLPI primitive)です。
54 〜55 行目の dlpromisconreq() 、dlokack() 及び、57 〜 58 行目の dlbindreq() 、dlbindack() も動作の流れ的には上述の 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() のつづきですが、つぎはいままでの処理とはちょっと違っています。
60 if (strioctl(fd, DLIOCRAW, -1, 0, NULL) < 0)
61 syserr("DLIOCRAW");
120 if(ioctl(fd, I_STR, &strioc) < 0){
121 perror("ioctl: I_STR: DLIOCRAW");
122 exit(1);
123 }
ここでは 270 行目の strioctl() を呼んでいるわけですが、この strioctl() 内で呼んでいる ioctl() システムコールがまた重要な処理になっています。
ここで呼んでいる 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);
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; /* データへのポインタ */
};
280 〜 283 行目を見ていただければ分かりますが、上記の構造体の各メンバーに代入されているのは、以下のようになります。
116 〜 119 行目を見ていただければ分かりますが、上記の構造体の各メンバーに代入されているのは、以下のようになります。
cmd=DLIOCRAW
ic_timeout=-1
ic_len=0
ic_dp=NULL
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() にもどります。
RAW モードへの移行がおわったところで、さらに main() の処理がつづきます。
63 if (ioctl(fd, I_FLUSH, FLUSHR) < 0)
128 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 に定義してありまして、128 行目で指定しているのは 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) {
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 }
「data」というのは strbuf 構造体で、38 行目に宣言しています。これは先の getmsg() のところで説明したように、STREAM から受け取ったメッセージを格納する場所になります。&br;
「databuf」というのは strbuf 構造体で、54 行目に宣言しています。これは先の getmsg() のところで説明したように、STREAM から受け取ったメッセージを格納する場所になります。&br;
getmsg はすでにお話しているのでさらっと流しますが、第二引数には NULL を指定していますので、制御情報の取得は行わず、データのみを受け取るようにしています。&br;
STREAM HEAD から受け取った生データは strbuf 構造体 data の メンバである data.buf に格納されていますのでこのデータを(実際にはアドレスですが、、)69 行目で最後の最後の処理になる print_pkt() 関数に渡しています。
STREAM HEAD から受け取った生データは strbuf 構造体 data の メンバである databuf.buf に格納されていますのでこのデータを 139 行目で最後の最後の処理になる print_packet() 関数に渡しています。
print_pkt() 内で行っている事は 先ほど 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 を比較してやらなければなりませんでした。
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]
*最後に[#l186476e]
というわけで非常ーに長々と、かつおおざっぱに解説させていただきました。
私自身本当に分からないところだらけで(だったら書くなという声が聞こえてきそうですが)苦しいのですが、DLPI の大体の概要が分かればコアの部分はそのまま使ってもいろいろ応用が出来るのではないかと思います。ただし、この手のパケットのキャプチャーを行うプログラムは使い方によっては不正利用にもつながりますのでお取り扱いは注意しましょう。まぁ、Solaris には端から snoop という packet capture tool がついているんだから問題ないでしょうけどね。
*参考資料 [#r4c836e7]
*参考資料 [#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 周りの解説が書いてある数少ない情報の一つかと、、|
*追記 [#g3fa4042]
**すべての フレームタイプを受信する [#y1bb3923]
先に書きましたように、このプログラムでは IP のpacket しか受信しません。すべてのフレームタイプを受信するためには、54 行目の dlpromiscast を以下のように変更します。
&aname(tsuiki);
*追記 [#b1795512]
**すべての フレームタイプを受信する [#ma102d9d]
54 dlpromisconreq(fd, DL_PROMISC_SAP);
先に書きましたように、このプログラムでは 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 も受信できるようになりますが、当然上のプログラムの 54 行目を書き換えただけでは packet を受け取れるようになるだけなので、ヘッダーの表示はめちゃくちゃになります。
「DL_PROMISC_SAP」にセットすることによって ARP や REVARP の packet も受信できるようになりますが、当然上のプログラムの 100 行目を書き換えただけでは packet を受け取れるようになるだけなので、ヘッダーの表示はされません。
**さらに追記 [#s39a5fb0]
**さらに追記 [#h2618cba]
DL_PROMISC_PHYS(PHYが レベル)ではローカルホストによって生成されたフレームを含む、メディア上のすべてのフレームの受信を有効になり、DL_PROMISC_SAP(SAPレベル)ではすべての SAP 値 (フレームタイプ) の受信を有効します。