X

Oracle Solaris, Oarcle ハードウェア製品に関する情報

  • Sun
    December 20, 2009

DTrace の IP プロバイダの使い方

Guest Author


はじめに

OpenSolaris の DTrace には IP プロバイダが用意されています。IP プロバイダを使用するとパケットの送受信をトレースしてアクションを実行するスクリプトを書く事が出来ます。また、snoop や tcpdump, libpcap を使わずとも IP レイヤーの分析が簡単に出来る様になります。今回はこの IP プロバイダの使い方をご紹介します。


用意されているプローブの種類

IP プロバイダに用意されているプローブの一覧は下記の通りです。プローブは大きく分けて receive と send の 2 種類があります(NAME のカラムを参照)。receive はパケットの受信時に発動するプローブで、send は送信時に発動するプローブです。これらのプローブをトリガーとした DTrace のスクリプトを書く事で、IP レイヤーの調査を行う事が可能になっています。

なお FUNCTION のカラムは該当するプローブが定義されている関数の名前です。どの関数もパケットの送受信に関わっている関数です。IP プロバイダはカーネル内のプロバイダなので、ここに挙っている関数もカーネル内の関数です。大まかに言って、関数名が ip で始まるプローブは IP モジュールの、tcp は TCP モジュールの、udp は UDP モジュールのプローブです。v6 が付いている場合は IPv6 で v4 が付いている場合は IPv4 用の関数です。関数の種類や数はリリースに依って変動します。以下は snv_127 で用意されているプローブです。

 # dtrace -ln ip:::    
ID PROVIDER MODULE FUNCTION NAME
16993 ip ip ip_wput_local_v6 receive
16994 ip ip ip_rput_v6 receive
16995 ip ip ip_wput_local receive
16996 ip ip ip_input receive
17013 ip ip ip_inject_impl send
17014 ip ip udp_xmit send
17015 ip ip tcp_lsosend_data send
17016 ip ip tcp_multisend send
17017 ip ip tcp_send_data send
17018 ip ip ip_multicast_loopback send
17019 ip ip ip_xmit_v6 send
17020 ip ip ip_wput_ire_v6 send
17021 ip ip ip_xmit_v4 send
17022 ip ip ip_wput_ipsec_out send
17023 ip ip ip_wput_ipsec_out_v6 send
17024 ip ip ip_wput_frag send
17025 ip ip ip_wput_frag_mdt send
17026 ip ip ip_wput_ire send
17027 ip ip ip_fast_forward send


用意されているデータの種類

各プローブには args[0] から args[5] までの変数が用意されており、それぞれが様々な情報への参照を保持しています。args[0] はパケット情報、args[1] はコネクションステータス情報、args[2] は IP の情報、args[3] はネットワークインターフェイスの情報、args[4] は IPv4 のヘッダ情報、args[5] は IPv6 のヘッダ情報を参照しています。DTrace スクリプトの中からこれらの変数にアクセスして必要な情報を入手してください。

 変数

参照先の型

参照している情報
args[0]

pktinfo_t \*

パケット情報
args[1]

csinfo_t \*

コネクションステータス情報
args[2]

ipinfo_t \*

IP の情報
args[3]

ifinfo_t \*

ネットワークインターフェイスの情報
args[4]

ipv4info_t \*

IPv4 のヘッダ
args[5]

ipv6info_t \*

IPv6 のヘッダ

各データ構造の詳細は こちら をご覧下さい


IP プロバイダの使用例


パケットの送受信イベントの表示

DTrace のスクリプト部分に 'ip:::send' だけを指定すると、パケットを送信する度に IP プロバイダの該当するプローブが発動した事が表示されます。'ip:::receive' を指定するとパケットを受信する度に表示されます。'ip:::' または明示的に 'ip:::send,ip:::receive' を指定すると送受信両方で該当するプローブが発動し、表示されます。CPU のカラムは送受信イベントが発生した CPU の番号を表示しています。この情報を使用して、どの CPU でパケットの送受信がハンドルされているかを判別する事が出来ます。

 # dtrace -n 'ip:::send'
dtrace: description 'ip:::send' matched 15 probes
CPU ID FUNCTION:NAME
0 17018 ip_multicast_loopback:send
0 17021 ip_xmit_v4:send
0 17017 tcp_send_data:send
0 17017 tcp_send_data:send
0 17017 tcp_send_data:send
\^C


パケットサイズを表示する

パケットサイズは args[2]->ip_plength で参照出来ます。probename が "send" だった場合は受信パケットなので "S" と表示させています。なお、dtrace コマンドに -q オプションを付けると、どのプローブが発動したかは表示されなくなります。

 # dtrace -qn 'ip:ip::send,ip:ip::receive { printf("%s\\t\\t%s\\t%d bytes\\n", probefunc, (probename == "send") ? "S" : "R", args[2]->ip_plength)}'  
tcp_send_data S 20 bytes
ip_input R 68 bytes
tcp_send_data S 100 bytes
tcp_send_data S 100 bytes
ip_input R 20 bytes
tcp_send_data S 196 bytes
ip_input R 20 bytes
\^C


パケットの送信元アドレスと宛先アドレスを表示する

パケットの送信元アドレスは args[2]->ip_saddr に、宛先アドレスは args[2]->ip_daddr に格納されています

 # dtrace -qn 'ip:ip::send,ip:ip::receive {printf("%s\\t\\t->\\t\\t%s\\t\\t%d bytes\\n", args[2]->ip_saddr, args[2]->ip_daddr, args[2]->ip_plength)}'
10.16.62.6 -> 10.16.100.16 20 bytes
10.16.100.16 -> 10.16.62.6 68 bytes
10.16.62.6 -> 10.16.100.16 132 bytes
10.16.62.6 -> 10.16.100.16 132 bytes
10.16.100.16 -> 10.16.62.6 20 bytes
10.16.62.6 -> 10.16.100.16 292 bytes
10.16.100.16 -> 10.16.62.6 20 bytes
\^C


パケットの送受信に使用された NIC を表示する

NIC の名前は args[3]->if_name に格納されています。送受信パケット毎にインターフェイス名が表示されるので、複数の NIC を使用している場合はそれぞれのインターフェイス名が表示されます。

 # dtrace -qn 'ip:ip::send, ip:ip::receive {printf("NIC: %s\\n", args[3]->if_name)}'        
NIC: bge0
NIC: bge0
NIC: bge0
NIC: bge0
NIC: bge0
NIC: bge0


パケットの種類を表示する

args[4]->ipv4_protostr に通信プロトコル情報が格納されています。識別可能なプロトコルは ip.d.in に定義されています。具体的には TCP, UDP, IP, ICMP, IGMP, EGP, IPv6, ROUTE, ESP, AH, ICMPv6, OSPF, SCTP, RAW 等です。

 # dtrace -qn 'ip:ip::send, ip:ip::receive {printf("proto: %s\\n", args[4]->ipv4_protostr)}'
proto: TCP
proto: ICMP
proto: ICMP
proto: TCP
proto: TCP


ここまでに出て来た全ての情報を出力する

以下のコマンドはちょっと長いですが、それでもたったこれだけでちょっとしたトレースツールが作成出来てしまいました。長くなった理由の一つには、出力が見易くなる様に、スクリプト開始時に呼び出される BEGIN プローブを使用してヘッダを付けたという事もあります。

 # dtrace -qn 'BEGIN{printf("NIC\\tdir\\tproto\\tfunc\\t\\tsource\\t\\tdestination\\tbytes\\n")}
ip:ip::send,ip:ip::receive {printf("%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%d\\n",
args[3]->if_name, (probename == "send") ? "S" : "R", args[4]->ipv4_protostr,
probefunc, args[2]->ip_saddr, args[2]->ip_daddr, args[2]->ip_plength)}'
NIC dir proto func source destination bytes
bge0 S TCP tcp_send_data 10.16.62.6 10.16.100.16 148
bge0 R TCP ip_input 10.16.100.16 10.16.62.6 20
bge0 S TCP tcp_send_data 10.16.62.6 10.16.100.16 132
bge0 S TCP tcp_send_data 10.16.62.6 10.16.100.16 132
bge0 R TCP ip_input 10.16.100.16 10.16.62.6 20
\^C


パケットサイズ毎の分布を調べる

DTrace の集計機能を使って、受信パケットを送信元アドレスとパケットサイズで分類してみます。どのノードからどんなサイズでパケットが送られて来ているかも簡単に調査する事が出来ます。出力の中の IP Address が送信元アドレスで、value のカラムの数字がパケットサイズ、count のカラムがパケット数です。'@' の数がパケットサイズ毎の分布を表しています。

 # dtrace -qn 'ip:::receive {@size[args[2]->ip_saddr] = quantize(args[2]->ip_plength)}'
\^C
10.16.100.254
value ------------- Distribution ------------- count
4 | 0
8 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1
16 | 0
10.16.62.6
value ------------- Distribution ------------- count
4 | 0
8 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1
16 | 0
127.0.0.1
value ------------- Distribution ------------- count
8 | 0
16 |@@@@@@@@@@@@@@@@@@@@ 6
32 |@@@@@@@@@@@@@@@@@@@@ 6
64 | 0
10.16.62.7
value ------------- Distribution ------------- count
32 | 0
64 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 7
128 | 0
10.16.62.5
value ------------- Distribution ------------- count
8 | 0
16 |@@@@@@@@@@@@ 11
32 |@@@@@@ 6
64 |@@@@@@@@@@@@@@@@@@@ 18
128 | 0
256 |@@@ 3
512 | 0


パケットを送受信した時間を表示する

下記スクリプトでは DTrace に用意されている walltimestamp 変数を使用して、パケットを送受信した時間を表示させています。%Y の代わりに %d 等で出力すれば秒単位以下の細かい時間を表示させる事も可能です。walltimestamp 以外にも DTrace に用意されている豊富な変数を利用して様々なスクリプトを作成する事が出来ます。

 # dtrace -qn 'ip:::send,ip:::receive { printf("%s\\t->\\t%s\\t%Y\\n", args[2]->ip_saddr, args[2]->ip_daddr, walltimestamp) }'
10.16.62.6 -> 10.16.62.7 2009 Dec 15 16:29:25
10.16.62.7 -> 10.16.62.6 2009 Dec 15 16:29:25
10.16.62.6 -> 10.16.62.7 2009 Dec 15 16:29:25
10.16.62.6 -> 10.16.62.7 2009 Dec 15 16:29:25
10.16.62.7 -> 10.16.62.6 2009 Dec 15 16:29:25
10.16.62.6 -> 10.16.62.7 2009 Dec 15 16:29:26
10.16.62.6 -> 10.16.62.7 2009 Dec 15 16:29:26
10.16.62.7 -> 10.16.62.6 2009 Dec 15 16:29:26
\^C


1 秒間にどのくらいのパケットを送受信しているかを測定する

DTrace の Profile プロバイダと組み合わせると、毎秒何パケット送受信したかを調査する事が出来ます。tick-1sec プローブは Profile プロバイダのプローブで、1 秒毎に発動します。Profile プロバイダ以外にも DTrace の豊富なプローブと組み合わせて様々なスクリプトを作成する事が可能です。

 # dtrace -qn 'ip:::send { send++ } ip:::receive { recv++ } tick-1sec { printf("There is/are %d packet(s) sent and %d packet(s) received.\\n", send, recv); send = 0; recv = 0 }'
There is/are 1 packet(s) sent and 0 packet(s) received.
There is/are 5 packet(s) sent and 5 packet(s) received.
There is/are 4 packet(s) sent and 4 packet(s) received.
There is/are 4 packet(s) sent and 4 packet(s) received.
There is/are 3 packet(s) sent and 3 packet(s) received.
There is/are 1 packet(s) sent and 1 packet(s) received.
\^C


NIC の詳細デバッグ情報を調査する

args[3]->if_addr は NIC に対応するカーネル内データ構造へのポインタになっています。ill_t 構造体の定義は ip.h にあります。ここでは NIC の名前を表示していますが、ill_t 構造体には他にも色々な情報が格納されています。

 # dtrace -qn 'ip:ip::send, ip:ip::receive {printf("NIC: %s\\n", stringof(((ill_t \*)(args[3]->if_addr))->ill_name))}'
NIC: bge0
NIC: bge0
NIC: bge0
NIC: bge0
NIC: bge0
\^C


既存のサンプル

DTrace の デモスクリプトipio.dipproto.d が用意されています。

ipio.d はパケットの送受信に使用した CPU, 直前にパケットを送受信した時間からの経過時間、送信元アドレス、宛先アドレス、使用したインターフェイス、送受信バイト数を表示するスクリプトです。

 # dtrace -s /usr/demo/dtrace/ipio.d 
CPU DELTA(us) SOURCE DEST INT BYTES
1 613 10.16.62.6 -> 10.16.62.7 bge0 132
1 150 10.16.62.6 -> 10.16.62.7 bge0 132
3 270 10.16.62.6 <- 10.16.62.7 bge0 20
1 99507 10.16.62.6 -> 10.16.62.7 bge0 132
1 109 10.16.62.6 -> 10.16.62.7 bge0 132
3 197 10.16.62.6 <- 10.16.62.7 bge0 20
1 99450 10.16.62.6 -> 10.16.62.7 bge0 132
\^C

ipproto.d は送信元アドレス、宛先アドレス、プロトコル毎に何パケット送受信されたかを表示するスクリプトです。

 # dtrace -s /usr/demo/dtrace/ipproto.d 
Tracing... Hit Ctrl-C to end.
\^C
SADDR DADDR PROTO COUNT
10.16.62.6 10.16.62.7 TCP 2
10.16.62.7 10.16.62.6 TCP 2


IP プロバイダの実装

IP プロバイダの実装は通常の SDT (静的定義トレース) プロバイダの実装と殆ど変わりありません。SDT プロバイダ用の DTRACE_PROBEn マクロをラップする形で、IP プロバイダ用に DTRACE_IP ~ DTRACE_IP7 までのマクロが用意されています。これらのマクロの第 1 引数がプローブ名 (send/receive) になっています。

現時点では、この内の DTRACE_IP7 だけが使用されています。以下のリンクから DTRACE_IP7 が使用されている場所を探す事が出来ます。

以下は DTrace IP プロバイダが OpenSolaris にマージされた際の ip.c の diff です。DTRACE_IP7 が加えられている事が分かります。

IP プロバイダで使用するデータ型やトランスレータは ip.d.in に定義されています。


その他の IP 関連のプローブ

DTrace には他にも IP に関連するプローブが沢山用意されています。これらも是非お試し下さい。

 # dtrace -ln :ip:: | head
ID PROVIDER MODULE FUNCTION NAME
16469 sdt ip tsol_ip_forward tx-ip-log-drop-forward-nodst
16470 sdt ip tsol_ip_forward tx-ip-log-drop-forward-nogw
16471 sdt ip tsol_ip_forward tx-ip-log-info-forward-adjust
16472 sdt ip tsol_ip_forward tx-ip-log-drop-forward-mac
16473 sdt ip tsol_ire_match_gwattr tx-ip-log-drop-irematch-nogwsec
16474 sdt ip tsol_ire_match_gwattr tx-ip-log-drop-irematch-nogwtmpl
16475 sdt ip tsol_ire_match_gwattr tx-ip-log-drop-irematch-deftmpl
16476 sdt ip tsol_ire_match_gwattr tx-ip-log-drop-irematch-nogcmatched
16477 sdt ip tsol_receive_local tx-ip-log-drop-receivelocal-no-tnr


参考資料


リファレンス


IP プロバイダに関する議論のログ


TCP プロバイダも予定されています


その他


おわりに

以上、DTrace の IP プロバイダの使用方法をご紹介しました。IP プロバイダは小粒な機能ですが、使い方に依っては色々な可能性を秘めたプロバイダだと思います。ご覧頂いた様な簡単なワンライナーで自分の欲しい情報だけを引き出せるのが DTrace の魅力の一つです。是非ご活用下さい。また、この様な小さなエンハンスも含め、大小様々なアップデートが入っているのが OpenSolaris の魅力です。是非、お手元にインストールして最新環境をお試し下さい。

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.