SystemTap Beginners Guide 5.1 のメモ

Useful SystemTap Scripts

この章では、SystemTap スクリプトを紹介する。
これらはsystemtap-testsuite RPMをインストールすると、/usr/share/systemtap/testsuite/にインストールされる。

5.1 Network

以下にネットワークに関するスクリプトを紹介する。

5.1.1 ネットワークプロファイリング

このセクションでは、どのようにしてネットワークの活動状況をプロファイルするのかについて詳述する。
nettop.stpは計算機上のプロセスがどの程度のネットワークトラフィックを生成しているのかについて、
各瞬間での状況を周期的に出力する。

nettop.stp

#! /usr/bin/env stap

global ifxmit, ifrecv
global ifmerged

probe netdev.transmit
{
	ifxmit[pid(), dev_name, execname(), uid()] <<< length
}

probe netdev.receive
{
	ifrecv[pid(), dev_name, execname(), uid()] <<< length
}

function print_activity()
{
	printf("%5s %5s %-7s %7s %7s %7s %7s %-15s\n",
		"PID", "UID", "DEV", "XMIT_PK", "RECV_PK",
		"XMIT_KB", "RECV_KB", "COMMAND")
	foreach ([pid, dev, exec, uid] in ifrecv) {
		ifmerged[pid, dev, exec, uid] += @count(ifrecv[pid,dev,exec,uid]);
	}
	foreach ([pid, dev, exec, uid] in ifxmit) {
		ifmerged[pid, dev, exec, uid] += @count(ifxmit[pid,dev,exec,uid]);
	}
	foreach ([pid, dev, exec, uid] in ifmerged-) {
		n_xmit = @count(ifxmit[pid, dev, exec, uid])
		n_recv = @count(ifrecv[pid, dev, exec, uid])
		printf("%5d %5d %-7s %7d %7d %7d %7d %-15s\n",
			pid, uid, dev, n_xmit, n_recv,
			n_xmit ? @sum(ifxmit[pid, dev, exec, uid])/1024 : 0,
			n_recv ? @sum(ifrecv[pid, dev, exec, uid])/1024 : 0,
			exec)
	}
	print("\n")
	delete ifxmit
	delete ifrecv
	delete ifmerged
}

probe timer.ms(5000), end, error
{
	print_activity()
}

function print_activity() では、以下の表現を使っている。
これはC言語三項演算子と同じである。

n_xmit ? @sum(ifxmit[pid, dev, exec, uid])/1024 : 0
n_recv ? @sum(ifrecv[pid, dev, exec, uid])/1024 : 0

nettop.stpは以下の情報をプロセス毎に出力する。

  • PID プロセスID
  • UID ユーザーID。ユーザーID 0はrootである。
  • DEV プロセスが送受信に使っているデバイス (例: eth0, eth1)
  • XMIT_PK プロセスの送信パケット数
  • RECV_PK プロセスの受信パケット数
  • XMIT_KB 送信データ量(単位 kbytes)
  • RECV_KB 受信データ量(単位 kbytes)

実行結果は以下のようになる。

$ sudo /usr/share/systemtap/testsuite/systemtap.examples/network/nettop.stp
  PID   UID DEV     XMIT_PK RECV_PK XMIT_KB RECV_KB COMMAND        

  PID   UID DEV     XMIT_PK RECV_PK XMIT_KB RECV_KB COMMAND        
    0     0 p20p1         0       2       0       0 swapper/0      
 1283  1000 p20p1         1       0       0       0 firefox        

  PID   UID DEV     XMIT_PK RECV_PK XMIT_KB RECV_KB COMMAND

5.1.2 ネットワークソケットの関数呼び出しのトレース

このセクションでは、net/socket.cの関数呼び出しをどのようにトレースするか説明する。

#! /usr/bin/env stap

probe kernel.function("*@net/socket.c").call {
	printf ("%s -> %s\n", thread_indent(1), probefunc())
}
probe kernel.function("*@net/socket.c").return {
	printf ("%s <- %s\n", thread_indent(-1), probefunc())
}

3.6で紹介したthread_indent()を使えば、以下のようにトレース出来る。

[...]
0 Xorg(3611): -> sock_poll
3 Xorg(3611): <- sock_poll
0 Xorg(3611): -> sock_poll
3 Xorg(3611): <- sock_poll
0 gnome-terminal(11106): -> sock_poll
5 gnome-terminal(11106): <- sock_poll
0 scim-bridge(3883): -> sock_poll
3 scim-bridge(3883): <- sock_poll
0 scim-bridge(3883): -> sys_socketcall
4 scim-bridge(3883): -> sys_recv
8 scim-bridge(3883):
-> sys_recvfrom
12 scim-bridge(3883):-> sock_from_file
16 scim-bridge(3883):<- sock_from_file
20 scim-bridge(3883):-> sock_recvmsg
24 scim-bridge(3883):<- sock_recvmsg
28 scim-bridge(3883):
<- sys_recvfrom
31 scim-bridge(3883): <- sys_recv
35 scim-bridge(3883): <- sys_socketcall
[...]

5.1.3 受信しているTCPコネクションの監視

このセクションでは、TCPコネクションの受信を監視する方法を説明する。
このタスクは、不正なネットワークアクセス要求をリアルタイムに特定する。

#! /usr/bin/env stap
probe begin {
  printf("%6s %16s %6s %6s %16s\n",
         "UID", "CMD", "PID", "PORT", "IP_SOURCE")
}

probe kernel.function("tcp_accept").return?,
      kernel.function("inet_csk_accept").return? {
  sock = $return
  if (sock != 0)
    printf("%6d %16s %6d %6d %16s\n", uid(), execname(), pid(),
           inet_get_local_port(sock), inet_get_ip_source(sock))
}

tcp_connections.stpは以下の情報を出力する。

  • UID 現在のUID
  • CMD コネクションを受け付けているコマンド
  • PID プロセスID
  • コネクションに使われているポート
  • IP_SOURCE TCPコネクション要求元のIPアドレス

以下は出力例

UID  CMD  PID PORT   IP_SOURCE
0   sshd 3165   22 10.64.0.227
0   sshd 3165   22 10.64.0.227

5.1.4 TCPパケットの監視

このセクションでは、システムに届くTCPパケットの監視方法について説明する。
これはネットワークトラフィックの解析に有効である。

tcpdumplike.stp

#! /usr/bin/env stap
// A TCP dump like example
probe begin, timer.s(1) {
  printf("-----------------------------------------------------------------\n")
  printf("Source IP Dest IP SPort DPort U A P R S F \n")
  printf("-----------------------------------------------------------------\n")
}
probe tcp.receive {
  printf(%15s %15s %5d %5d %d %d %d %d %d %d\n",
         saddr, daddr, sport, dport, urg, ack, psh, rst, syn, fin)
}

tcpdumplike.stpはリアルタイムに受信したTCPパケットに関して、以下の情報を出力する。

パケットの情報を特定するために、以下の関数を使用している。

  • urg urgent
  • ack acknowledgement
  • psh push
  • rst reset
  • syn synchronize
  • fin finished

これらの関数は、パケットの使用しているフラグを1か0で返す。

-----------------------------------------------------------------
Source IP
Dest IP SPort DPort U A P R S F
-----------------------------------------------------------------
209.85.229.147 10.0.2.15 80 20373 0 1 1 0 0 0
92.122.126.240 10.0.2.15 80 53214 0 1 0 0 1 0
92.122.126.240 10.0.2.15 80 53214 0 1 0 0 0 0
209.85.229.118 10.0.2.15 80 63433 0 1 0 0 1 0
209.85.229.118 10.0.2.15 80 63433 0 1 0 0 0 0
209.85.229.147 10.0.2.15 80 21141 0 1 1 0 0 0
209.85.229.147 10.0.2.15 80 21141 0 1 1 0 0 0
209.85.229.147 10.0.2.15 80 21141 0 1 1 0 0 0
209.85.229.147 10.0.2.15 80 21141 0 1 1 0 0 0
209.85.229.147 10.0.2.15 80 21141 0 1 1 0 0 0
209.85.229.118 10.0.2.15 80 63433 0 1 1 0 0 0
[...]

5.1.5 カーネルでのネットワークパケットの取りこぼしを監視する

Linuxでは様々な理由から、ネットワークスタックでパケットを捨てる事がある。
いくつかのLinuxカーネルではkernel.trace("kfree_skb")という、
捨てられたパケットを簡単に追跡するためのトレースポイントが含まれている。
dropwatch.stpはkernel.trace("kfree_skb")を使ってパケットが捨てられた事を追跡する。
スクリプトは5秒間隔で捨てられたパケットの位置を統計する。

#! /usr/bin/env stap
############################################################
# Dropwatch.stp
# Author: Neil Horman <nhorman@redhat.com>
# An example script to mimic the behavior of the dropwatch utility
# http://fedorahosted.org/dropwatch
############################################################
# Array to hold the list of drop points we find
global locations
# Note when we turn the monitor on and off
probe begin { printf("Monitoring for dropped packets\n") }
probe end { printf("Stopping dropped packet monitor\n") }
# increment a drop counter for every location we drop at
probe kernel.trace("kfree_skb") { locations[$location] <<< 1 }
# Every 5 seconds report our drop locations
probe timer.sec(5)
{
  printf("\n")
  foreach (l in locations-) {
    printf("%d packets dropped at %s\n",
           @count(locations[l]), symname(l))
  }
  delete locations
}
||>

kernel.trace("kfree_skb")はカーネルがネットワークパケットを取りこぼした箇所を追跡する。
kernel.trace("kfree_skb")は2つの引数を取る。一つは開放されたバッファを示すポインタ($skb)で、
もう一つはカーネルコードでの位置を示す($location)である。
dropwatch.stpスクリプトは、有効な$locationの位置にある関数を提供する。
関数名と$locationをマッピングする機能はデフォルトでは含まれていないため、
SystemTap 1.4では--all-modulesオプションを使う必要がある。

>||
stap --all-modules dropwatch.stp

古いバージョンのSystemTapを使う場合には、以下のようにして--all-modules を
模擬する必要がある。

stap -dkernel \
`cat /proc/modules | awk 'BEGIN { ORS = " " } {print "-d"$1}'` \
dropwatch.stp

dropwatch.stpは15秒毎に結果を出力する。
実行例を以下に示す。

Monitoring for dropped packets
1762 packets dropped at unix_stream_recvmsg
4 packets dropped at tun_do_read
2 packets dropped at nf_hook_slow
467 packets dropped at unix_stream_recvmsg
20 packets dropped at nf_hook_slow
6 packets dropped at tun_do_read
446 packets dropped at unix_stream_recvmsg
4 packets dropped at tun_do_read
4 packets dropped at nf_hook_slow
Stopping dropped packet monitor

スクリプトが、あるマシンでコンパイルされて、別のマシンで実行された場合に、--all-modulesも/proc/modulesディレクトリも有効出なかった場合には、symname関数は生のアドレスを返す。
/boot/System.map-`uname -r` を参照し、生のアドレスを手動でマッピングすることも出来る。