目次

概要

まず最初に白状しますが、ここで書いているプログラムの コアの部分は全くもって私のオリジナルではありません。internet 上にある DLPI をつかったプログラムを参考にして切り張りして作っています。ただ、いくつかのプログラムを見たところ、ほぼおんなじ source をつかっており、ものは違ってもつかっている関数名がまったく同じだったり、関数の中見が同じだったりするので、おそらくどこかにたたき台となっている source が存在するのでしょう。(tcpdump で利用する libpcap も似たようなものです)ここは開き直ってプログラムの簡略化と、説明とに注力したいと思います。
DLPI とは Data Link Provider Interface の略で solaris が提供しているデータリンク層のプロトコルに直接アクセスするためのインターフェースです。つまり DLPI を使えば socket プログラムの SOCK_RAW で入手していたような生パケットが送受できるって事です。かつ、データリンク層に直接アクセスできるわけですから、Ethernet ヘッダーさえもを自由に送受できると言う事です。これはなかなか夢が膨らむインターフェースです。
DLPI をつかえばいろいろ面白いものが出来そうですが、ここでは例によって 400 行足らずの簡易なパケットモニター(packet scanner, packet sniffer ?) をつくってみます。

プログラムのイメージ

このプログラムの main() の流れを以下に示します。最後の getmsg と print_pkt を loop しているだけで他は一直線なプログラムなので流れは分かりやすいかと思います。 PPA と SAP ってのが出てきますが、PPA は network Interface の instance 番号( /dev/hme0 の 0)のことで、SAP ってのは Ethernet のフレームタイプです。Ethernet フレームタイプには IP(0x0800)や、ARP(0x0806)などがあります。このプログラムでは フレームタイプとして IP(0x0800)を指定しています。

流れ解説
open()device(network Interface)をオープン
dlattachreq()オープンしたデバイスの PPA(physica point of attachment)に STREAM をアタッチ(関連付け)する
dlokack()上記の要求の ACK(確認応答)を受け取る。
dlpromisconreq()promiscast(無差別受信)モード へ移行を要求
dlokack()上記の要求の ACK を受け取る。
dlbindreq()データリンク層の SAP(service access point)を STREAM に BIND(関連付け)する。
dlokack()上記の要求の ACK を受け取る。
strioctl()ioctl() を通じて device を RAW モードに移行するよう要求。
ioctl(,I_FLUSH,FLUSHR)ioctl() を通じてSTREMA の READ QUEUE のフラッシュ を要求。
getmsg()STREAM の READ QUEUE から生データをを読み込む。(以下繰り返す)
print_pkt()あとは、読み込んだデータを Ether、IP、TCP の各構造体に当てはめて表示するだけ、、

ソースコード

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

実行例

プログラム名の後に引数として NIC のデバイスパス、NIC の instance 番号 を入力します。
以下の 例では Sun の FastEthernet カードである /dev/hme0 を指定しています。
デバイスパス(/dev/hme)と instance 番号(0) は スペースで開けて指定します。
なお、このプログラムも root ユーザで実行する必要があります。
setuid(chmod 4755 sniff )してもいいですけど。

# ./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 を表示します。
内容をみると、telnet のセッションの PUSH + ACK のパケットであることが分かるとおもいます。
この例では出ていませんが、DATA 以降に text があればそれも表示します。

あと、言い忘れましたが これは TCP packet 専用です。ICMP や ARP はおろか、UDP packet も表示できません。「表示」といっているのは packet の受信は出来るけど、UDP などの HEDER を正しく表示できないって事です。 全部の header 対応の source 書くぐらいなら tcpdump や snoop つかったほうがよいです(笑)。

解説

DLPI の説明をする上で STREAMS (ストリーム)機構 の解説を避けて通る事は出来ないのですが、はっきりいって一言で説明することは出来ないですし、なにより私がそれほど理解してないので非常ーにおおざっぱ説明させていただきます。

STREAMS 機構とは Solaris をはじめとする SVR4 準拠の system で採用されているネットワークプロトコルを開発するための下部構造であり、データの構造体、関数群、設計思想を現しています。つまり、IP や X25 などの異なるネットワークプロトコルを開発する場合にも同一の土俵(枠組み)にて開発する事ができるのです。実際に Solaris の TCP や IP もこの STEREAMS 機構にて実装されています。
STREAM とは論理的なデータの通り道であり、STREAM 内で 受け渡しされるデータはメッセージという形(実際には一連の構造体群)にて伝送され、ユーザプロセスはSTREAM HEAD(ストリームヘッド)と呼ばれる窓口を通してデータのやり取りを行う事になります。STREAM には STREAM HEAD から device driverに向かう道と、device driverから STREAM HEAD に向かう道とに別れており、それぞれを「アップストリーム」、「ダウンストリーム」と呼びます。

stream.gif

今回の場合上図の「ストリームデバイスドライバ」とは network Interface のデバイスドライバの事を指しまして、この device をデータリンクサービスプロバイダー(データリンク層のサービスを提供してくれるもの)などと呼びます。network Interface は ethenet などのデータリンク層 に該当する役割を持っている事は周知の事だと思いますので、分かりやすいと思います。
DLPI(Data Link Provider Interface)とは この network Interface ドライバーが提供しているデータリンク層に直接アクセスできるインターフェースという事になります。ユーザプロセスはストリームヘッドを通じてこの DLPI にアクセスし、必要な設定を行ったり、メッセージ(データ)を受け取ったりするわけです。

以上、簡単に DLPI と STREAM について説明させていただきました。かなりいいかげんですが、上記のような大体の構成が分かれば、以下の各関数の流れが分かりやすいとおもいます。 では source の各関数を順に見ていきましょう。

   32          union   DL_primitives *dlp;

32 行目に DL_primitives という共用体を 宣言しています。primitives というのは後々も出てくるので、ここで説明しておきます。
上の STREAMS 機構の説明のところで、STREAM(データの通り道)は「メッセージ」という形でデータを運ぶと書きましたが、このメッセージはいくつかメッセージタイプ(message type)が存在しまして、それぞれが役割を持っています。以下にこのプログラムで用いられている3つのメッセージタイプを示します。

メッセージタイプ説明
M_DATAユーザプロセスとデバイスドライバ間のデータの受け渡しに使われる。
M_PROTOユーザプロセスとデバイスドライバ間の制御情報の受け渡しに使われる
M_PCPROTOM_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 内で定義されています。
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 構造体へのポインターを指定しているというわけです。

strbuf 構造体
maxlen(最大buffer長)
len(data長)
dl_attach_req_t 構造体
DL_ATTACH_REQ
ppa

strbuf と dl_attach_req_t 両構造体の各メンバーに値の代入が終わったら、strbuf 構造体へのアドレスを putmsg() システムコールに渡します。これにより メッセージが STREAM HEAD を通じてダウンストリームに渡される事になります。

   94          if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0)
putmsg()
概要STREAM へのメッセージの送信
インクルードファイル#include <stropts.h>
形式int putmsg(int fildes, const struct strbuf *ctlptr, const struct strbuf *da taptr, int flags);
引数int filedes : ファイル記述子
const struct clptr *ctlptr: 制御用の strbuf 構造体へのポインタ
const struct strbuf *dataptr: データ用の strbuf 構造体へのポインタ
int flags: フラグ
戻り値成功時: 0
エラー: -1

putmsg() システムコールでは STREAM に対してデータバッファと制御バッファの2つのバッファを一度に送信する事が出来ます。しかしながらここでは実際のデータの送信ではなく、device に対する制御情報(ATTACH 要求)を送るだけなので、94 行目の putmsg ではデータ用の strbuf 構造体の引数としては NULL 値を渡しています。

dlpi_sequence.gif

さて、これで ATTACHの要求の送信は終了しまして、再度 main() 関数に戻りますとつづいてdlokack() 関数に呼び出す事になります。これは ATTACH 要求に対する確認応答を受け取る動作になります。 ATTACH要求に限らず、DLPI では常に以下のような要求・確認応答 という手順を踏みます。

152 行目の dlokack() 関数の主な動作は 201 行目の strgetmsg() 関数を呼び出し、確認応答のメッセージを受け取る事です。
概要が分かったところで、途中を一気に省いて 201 行目の strgetmsg() に飛びます。

  201  strgetmsg(fd, ctlp, datap, flagsp, caller)

この関数の中で今度は getmsg() というシステムコールが出てきます。

getmsg()
概要STREAM からのメッセージの受信
インクルードファイル#include <stropts.h>
形式int getmsg(int fildes, struct strbuf *ctlptr, struct strbuf *dataptr, int * flagsp);
引数int filedes : ファイル記述子
const struct clptr *ctlptr: 制御用の strbuf 構造体へのポインタ
const struct strbuf *dataptr: データ用の strbuf 構造体へのポインタ
int flags: フラグ
戻り値成功時: 実数値
0 --> すべてのメッセージの読み込み成功
MOREDATA --> まだデータが残っている
MORECTL --> まだ制御情報が残っている
エラー: -1
  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 構造体へのポインタである 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)です。

dlpromisconreqDL_PROMISCON_REQ primitive にて promiscast (無差別受信)モード への移行を要求 (追記参照)
dlbindreqDL_BIND_REQ primitive にて データリンク層の SAPを STREAM に BIND(関連付け)を要求

さて、main() のつづきですが、つぎはいままでの処理とはちょっと違っています。

   60          if (strioctl(fd, DLIOCRAW, -1, 0, NULL) < 0)
   61                  syserr("DLIOCRAW");

ここでは 270 行目の strioctl() を呼んでいるわけですが、この strioctl() 内で呼んでいる ioctl() システムコールがまた重要な処理になっています。

ioctl()
概要STREAM デバイスの制御
インクルードファイル#include <unistd.h>
#include <stropts.h>
#include <sys/conf.h>
形式int ioctl(int fildes, int command, /* arg */ ...);
引数int filedes : ファイル記述子
int command: デバイスに実行させるコマンド
arg: strioctl 構造体へのポインタ
戻り値成功時: 実数値
エラー: -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 から受け取ったメッセージを格納する場所になります。
getmsg はすでにお話しているのでさらっと流しますが、第二引数には NULL を指定していますので、制御情報の取得は行わず、データのみを受け取るようにしています。
STREAM HEAD から受け取った生データは strbuf 構造体 data の メンバである data.buf に格納されていますのでこのデータを(実際にはアドレスですが、、)69 行目で最後の最後の処理になる print_pkt() 関数に渡しています。

print_pkt() 内で行っている事は 先ほど getmsg() にて受け取った生データを Ethernet、IP、TCP の各ヘッダー構造体に当てはめ、それぞれの構造体のメンバー、つまりヘッダーの内容をプリントしているだけです。行っている処理事体は「ソケットプログラミング」のところで書いている「パケットモニタープログラム」をほぼそのまま持ってきています。
ただ違うのは 今回は 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 のヘッダーを表示させるようにしました。
Ethernet ヘッダーの大きさが 14 bit であるところが問題のようなのですが、普通はこういう場合どうするんでしょう?(libcap のソースを見れば分かるのかもしれませんが、面倒臭かったので確認してません)

最後に、、、

というわけで非常ーに長々と、かつおおざっぱに解説させていただきました。 私自身本当に分からないところだらけで(だったら書くなという声が聞こえてきそうですが)苦しいのですが、DLPI の大体の概要が分かればコアの部分はそのまま使ってもいろいろ応用が出来るのではないかと思います。ただし、この手のパケットのキャプチャーを行うプログラムは使い方によっては不正利用にもつながりますのでお取り扱いは注意しましょう。まぁ、Solaris には端から snoop という packet capture tool がついているんだから問題ないでしょうけどね。

参考資料

このページを書く上で参考にした資料をご紹介いたします。

参考資料名メモ
UNIX カーネルの魔法
(プレンティスホール出版)
SVR4 の カーネル関連の本。しかしながら STREAM の部分しか読んでません。というか他の部分読んでも分からない、、(泣)
UNIX ネットワークプログラミング
(トッパン)
まさに UNIX 上でのネットワークプログラミングの本。情報がちょっと古いのが難点ですが、基本は押さえられます。
詳解TCP/IP
(ソフトバンク)
TCP/IP のプロトコル上の話に関してはバイブル的な本です。
man page一番身近な情報源
dlpi(7P), le(7D), streamio(7I), getmsg(2), putmsg(2)
docs.sun.comリンクしているのは Solaris 8 の hme の man ページですが、日本語で DLPI 周りの解説が書いてある数少ない情報の一つかと、、

 

追記

すべての フレームタイプを受信する

先に書きましたように、このプログラムでは 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 を受信することが出来ます。
名前のイメージ的には DL_PROMISC_PHYS が すべて受信できて DL_PROMISC_SAP が SAP でのフィルターを行うようにも思えるのですが、試してみると「DL_PROMISC_SAP」にセットすることによってすべてのフレームタイプを受信できるようになりました。
「DL_PROMISC_SAP」にセットすることによって ARP や REVARP の packet も受信できるようになりますが、当然上のプログラムの 54 行目を書き換えただけでは packet を受け取れるようになるだけなので、ヘッダーの表示はめちゃくちゃになります。

さらに追記

DL_PROMISC_PHYS(PHYが レベル)ではローカルホストによって生成されたフレームを含む、メディア上のすべてのフレームの受信を有効になり、DL_PROMISC_SAP(SAPレベル)ではすべての SAP 値 (フレームタイプ) の受信を有効します。



トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS