以下に移行予定です。
SystemTapの使い方
SystemTapの使い方(User-Space Probing)
#1 環境
[root@master ~]# cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)
[root@master ~]# uname -r
3.10.0-514.el7.x86_64
#2 事前準備
systemtapをインストールする。
[root@master tools]# yum -y install systemtap systemtap-runtime
base-debuginfoを有効にして、kernel-debuginfoをインストールする。
[root@master ~]# yum --enablerepo=base-debuginfo -y install kernel-debuginfo-3.10.0-514.el7.x86_64
base-debuginfoを有効にして、kernel-develをインストールする。
[root@master ~]# yum --enablerepo=base-debuginfo -y kernel-devel-3.10.0-514.el7.x86_64
インスールしたパッケージを確認する。下記実行結果では、もともと入っているパッケージも一緒に表示されています。
[root@master ~]# rpm -qa|grep kernel
kernel-headers-3.10.0-514.el7.x86_64
kernel-tools-libs-3.10.0-514.el7.x86_64
kernel-3.10.0-514.el7.x86_64
kernel-tools-3.10.0-514.el7.x86_64
kernel-debuginfo-common-x86_64-3.10.0-514.el7.x86_64
kernel-devel-3.10.0-514.el7.x86_64
kernel-debuginfo-3.10.0-514.el7.x86_64
#3 サンプルの場所
/usr/share/doc/systemtap-*配下にサンプルがあります。
[root@master ~]# ls /usr/share/doc/systemtap-*
/usr/share/doc/systemtap-client-3.0:
AUTHORS NEWS README README.unprivileged SystemTap_Beginners_Guide.pdf examples langref.pdf tapsets.pdf tutorial.pdf
/usr/share/doc/systemtap-devel-3.0:
AUTHORS NEWS README README.unprivileged
/usr/share/doc/systemtap-runtime-3.0:
AUTHORS NEWS README README.security
サンプルはここからも参照できます。
他に書いた記事
SystemTapの使い方(その2)
SystemTapの使い方(グルモード編)
SystemTapの使い方(User-Space Probing)
SystemTapの使い方(tapset編)
#4 Tapsetの使い方
Tapsetの詳細説明はここを参照してください。
Tapsetには関数とプローブポイントの2種類があります。
あらかじめ、Systemtapに用意されています。
これらを使うことで、Systemtapのスクリプト作成を効率化することができます。
5章で関数の使い方、6章でプローブポイントの使い方を説明します。
#5 関数の使い方
##5.1 関数の表示方法(--dump-functions)
関数の中にpp関数が存在することがわかる。
[root@server stap]# stap --dump-functions|grep pp
pp:string () /* unprivileged */
関数の中にprint_backtrace関数が存在することがわかる。
[root@server stap]# stap --dump-functions|grep print_backtrace
print_backtrace:unknown ()
なお、関数は下記ディレクトリ配下のstpファイルに定義されています。
[root@server linux]# pwd
/usr/share/systemtap/tapset/linux
[root@server linux]# ls
arm guru-signal.stp loadavg.stp rcu.stp tcp.stp
arm64 i386 logging.stp rlimit.stp tcpmib-filter-default.stp
atomic.stp ia64 memory.stp rpc.stp tcpmib.stp
aux_syscalls.stp inet.stp nd_syscalls.stp s390 timestamp.stp
context-caller.stp inet_sock.stp nd_syscalls2.stp scheduler.stp timestamp_gtod.stp
context-envvar.stp ioblock.stp netfilter.stp scsi.stp timestamp_monotonic.stp
context-symbols.stp ioscheduler.stp networking.stp signal.stp tty.stp
context-unwind.stp ip.stp nfs.stp socket.stp tzinfo.stp
context.stp ipmib-filter-default.stp nfs_proc.stp syscalls.stp ucontext-symbols.stp
context.stpm ipmib.stp nfs_proc.stpm syscalls.stpm ucontext-unwind.stp
conversions-guru.stp irq.stp nfsd.stp syscalls2.stp ucontext.stp
conversions.stp json.stp nfsderrno.stp syscalls_cfg_trunc.stp udp.stp
ctime.stp json.stpm panic.stp target_set.stp utrace.stp
dentry.stp kprocess.stp perf.stp task.stp vfs.stp
dev.stp kretprobe.stp powerpc task.stpm x86_64
endian.stp linuxmib-filter-default.stp proc_mem.stp task_ancestry.stp
guru-delay.stp linuxmib.stp pstrace.stp task_time.stp
##5.2 関数で使える変数を調べる方法(-L)
$
が変数名、その後は変数の型を表しています。
tcp_v4_rcv関数の場合(★)、skbが変数名、skbの型はsk_buffへのポインタであることがわかります。
tcp_v4_rcv関数内で使える変数を調べる。
[root@admin stap]# stap -L 'kernel.function★("tcp_v4_rcv")'
kernel.function("tcp_v4_rcv@net/ipv4/tcp_ipv4.c:1615") $skb:struct sk_buff*
ip_rcv関数内で使える変数を調べる。
[root@admin stap]# stap -L 'kernel.function("ip_rcv")'
kernel.function("ip_rcv@net/ipv4/ip_input.c:378") $skb:struct sk_buff* $dev:struct net_device* $pt:struct packet_type* $orig_dev:struct net_device*
arp_process関数内で使える変数を調べる。
[root@admin stap]# stap -L 'kernel.function("arp_process")'
kernel.function("arp_process@net/ipv4/arp.c:735") $sk:struct sock* $skb:struct sk_buff* $in_dev:struct in_device* $sip:__be32 $tip:__be32
##5.3 時刻を表示する方法(gettimeofday_s())
スクリプトを作成する。ip_output関数を呼び出す毎に時刻を表示します。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
printf("TIME=%s\n", tz_ctime(gettimeofday_s()))
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルを開く。なにかパケットを送信する。ここでは、デフォルトGWにpingを実行しました。
[root@server stap]# ping 192.168.0.1
スクリプトの実行結果を確認する。時刻が表示されていることがわかる。
[root@server stap]# stap -v tp.stp
-中略-
TIME=Sun Sep 10 18:13:20 2017 JST
##5.4 プローブポイントの情報を表示する方法(pp())
スクリプトを作成する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
printf("TIME=%s,PP=%s\n", tz_ctime(gettimeofday_s()), pp())
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルを開く。なにかパケットを送信する。ここでは、デフォルトGWにpingを実行しました。
[root@server stap]# ping 192.168.0.1
スクリプトの実行結果を確認する。プローブポイントの情報が表示されていることがわかる(★)。
ip_output関数は、ip_output.cファイルの348行目に定義されていることがわかります。
[root@server stap]# stap -v tp.stp
-中略-
TIME=Sun Sep 10 18:24:41 2017 JST, ★PP=kernel.function("ip_output@net/ipv4/ip_output.c:348")
以下は、ip_output.cからの抜粋です。
root/net/ipv4/ip_output.c
-中略-
★348 int ip_output(struct sock *sk, struct sk_buff *skb)
349 {
350 struct net_device *dev = skb_dst(skb)->dev;
351
352 IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len);
-以下、略-
##5.5 呼び出し元の関数を表示する方法(caller())
スクリプトを作成する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
printf("TIME=%s,PP=%s,CALLER=%s\n", tz_ctime(gettimeofday_s()), pp(), caller())
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルを開く。なにかパケットを送信する。ここでは、デフォルトGWにpingを実行しました。
[root@server stap]# ping 192.168.0.1
スクリプトの実行結果を確認する。ip_output関数の呼び出し元関数(★)が表示されていることがわかる。
つまり、ip_local_out_sk 関数がip_output関数を呼び出したことがわかる。
[root@server stap]# stap -v tp.stp
-中略-
TIME=Sun Sep 10 18:28:57 2017 JST,PP=kernel.function("ip_output@net/ipv4/ip_output.c:348"),★CALLER=ip_local_out_sk 0xffffffff815b43d1
##5.6 プロセスを絞り込む方法(execname())
###5.6.1 特定のプロセスに絞り込む方法(execname() == "プロセス名")
スクリプトを作成する。pingプロセスがIPパケットを送信したときだけ、ログを出力する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if (execname() == "ping")
{
printf("TIME=%s,CPU=%d\n", tz_ctime(gettimeofday_s()), cpu())
}
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルを開く。デフォルトGWにpingを実行する。
[root@server stap]# ping -c 1 192.168.0.1
スクリプトの実行結果を確認する。pingを実行したときだけ、ログが出力されることがわかる。
[root@server stap]# stap -v tp.stp
-中略-
TIME=Sun Sep 10 19:23:16 2017 JST,CPU=1
TIME=Sun Sep 10 19:23:21 2017 JST,CPU=1
-以下、略-
###5.6.2 特定のプロセス以外に絞り込む方法(execname() != "プロセス名")
スクリプトを作成する。sshdプロセス以外のプロセスがIPパケットを送信したとき、ログを出力する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if (execname() != "sshd")
{
printf("TIME=%s,CPU=%d\n", tz_ctime(gettimeofday_s()), cpu())
}
}
##5.7 バックトレースを表示する方法(print_backtrace())
スクリプトを作成する。pingプロセスのバックトレースを表示する。
つまり、ip_output関数の呼び出し関数を表示する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if (execname() == "ping")
{
printf("TIME=%s,%s\n", tz_ctime(gettimeofday_s()), print_backtrace())
}
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルを開く。デフォルトGWにpingを実行する。
[root@server stap]# ping -c 1 192.168.0.1
スクリプトの実行結果を確認する。ping実行時のバックトレースが表示されていることがわかる。
最初にsystem_call_fastpathが呼ばれて、最後にip_outputが呼ばれていることがわかる。
[root@server stap]# stap -v tp.stp
-中略-
0xffffffff815b66a0 : ip_output+0x0/0xe0 [kernel]
0xffffffff815b43d1 : ip_local_out_sk+0x31/0x40 [kernel]
0xffffffff815b7166 : ip_send_skb+0x16/0x50 [kernel]
0xffffffff815b71d3 : ip_push_pending_frames+0x33/0x40 [kernel]
0xffffffff815dd394 : raw_sendmsg+0x814/0x9b0 [kernel]
0xffffffff815ec6c4 : inet_sendmsg+0x64/0xb0 [kernel]
0xffffffff815551d0 : sock_sendmsg+0xb0/0xf0 [kernel]
0xffffffff81555381 : SYSC_sendto+0x121/0x1c0 [kernel]
0xffffffff81555f1e : SyS_sendto+0xe/0x10 [kernel]
0xffffffff816965c9 : system_call_fastpath+0x16/0x1b [kernel]
TIME=Sun Sep 10 19:46:13 2017 JST,
##5.8 CPU番号を表示する方法(cpu())
スクリプトを作成する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if (execname() == "ping")
{
printf("TIME=%s,CPU=%d\n", tz_ctime(gettimeofday_s()), cpu())
}
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルを開く。デフォルトGWにpingを実行する。
[root@server stap]# ping -c 1 192.168.0.1
スクリプトの実行結果を確認する。pingを実行したCPUがCPU=3,次に実行したときがCPU=0であることがわかる。
[root@server stap]# stap -v tp.stp
TIME=Sun Sep 10 19:31:50 2017 JST, ★CPU=3
TIME=Sun Sep 10 19:32:04 2017 JST, ★CPU=0
##5.9 IPアドレス、ポート番号を表示する方法
スクリプトを作成する。
[root@server stap]# vi arp.stp
[root@server stap]# cat arp.stp
#!/usr/bin/stap
function printconn(skb)
{
iphdr = __get_skb_iphdr(skb)
tcphdr = __get_skb_tcphdr(skb)
proto = __ip_skb_proto(iphdr)
daddr = format_ipaddr(__ip_skb_daddr(iphdr), AF_INET())
saddr = format_ipaddr(__ip_skb_saddr(iphdr), AF_INET())
dport = __tcp_skb_dport(tcphdr)
sport = __tcp_skb_sport(tcphdr)
# caller()は呼び出し元関数、pp()はプローブポイントを表示する。
printf("TIME=%s,CALLER=%s,PP=%s,PROTO=%d %s.%d-> %s.%d\n",
tz_ctime(gettimeofday_s()), caller(),pp(),proto,saddr, sport, daddr, dport);
}
#------------------------------------------------------------------------
probe kernel.function("neigh_resolve_output@net/core/neighbour.c")
{
printconn($skb)
}
スクリプトを実行する。
[root@server stap]# stap -v arp.stp
もう1つターミナルをオープンする。digコマンドを実行する。
[root@server stap]# dig ntp.nict.jp +short
スクリプトの実行結果を確認する。
[root@server stap]# stap -v arp.stp
-中略-
TIME=Sun Sep 10 18:48:00 2017 JST,CALLER=ip_finish_output 0xffffffff815b5510,PP=kernel.function("neigh_resolve_output@net/core/neighbour.c:1307"),PROTO=17 192.168.0.100.47370-> 192.168.3.1.53
##5.10 MACアドレスを表示する方法
tapsetの中に、netfilter.stpというスクリプトがあります。
この中に、MACアドレスを表示する@__private30 function __mac_addr_to_string
という関数が定義されています。
自身で作成したスクリプトの中から__mac_addr_to_string
を呼び出すとエラーが発生
したので、少し名前を変えたものを、自身で作成したスクリプトの中に定義してみました。
上手いやり方を知っている方がいましたら、ご連絡ください!!!
[root@server stap]# vi arp.stp
[root@server stap]# cat arp.stp
#!/usr/bin/stap
function mac_addr_to_string:string(addr:long) {
return sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
kernel_char(addr)&255, kernel_char(addr+1)&255,
kernel_char(addr+2)&255, kernel_char(addr+3)&255,
kernel_char(addr+4)&255, kernel_char(addr+5)&255)
}
probe kernel.statement("arp_send_dst@net/ipv4/arp.c:341")
{
printf("pp=%s,mac=%s,dev=%s,dest_ip=%s,src_ip=%s\n",pp(),
mac_addr_to_string($src_hw),
kernel_string($dev->name),
format_ipaddr($dest_ip,AF_INET()),
format_ipaddr($src_ip,AF_INET()));
}
スクリプトを実行する。
[root@server stap]# stap -v arp.stp
-中略-
pp=kernel.statement("arp_send_dst@net/ipv4/arp.c:341"),mac=00:0c:29:9b:e6:87,dev=bond0,dest_ip=192.168.0.6,src_ip=192.168.0.10
MACアドレスを確認する。この環境はbondingを使用しているので、bond0のMACが送信元MACアドレスとして表示された。
[root@server linux]# ip link
-中略-
5: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT qlen 1000
link/ether 00:0c:29:9b:e6:87 brd ff:ff:ff:ff:ff:ff
##5.11 プロセスの名前やtask_struct構造体のアドレスを表示する方法
関数名 | 概要 |
---|---|
pid2task | PIDからtask_struct構造体のアドレスを求める |
pid2execname | PIDからプロセスの名前を求める |
task_current | 現在実行中のプロセスのtask_struct構造体のアドレスを求める |
task_execname | 現在実行中のプロセスの名前を求める |
systemdのPIDを確認する。PID=1であることがわかる。
[root@server ~]# ps -C systemd
PID TTY TIME CMD
1 ? 00:00:10 systemd
[root@server stap]# vi sk.stp
[root@server stap]# cat sk.stp
#!/usr/bin/stap
probe begin {
printf("%p\n",pid2task(1))
printf("%s\n",pid2execname(1))
printf("%p\n",task_current())
printf("%s\n",task_execname(task_current()))
exit()
}
[root@server stap]# stap -v sk.stp
-中略-
0xffff88003bec8000 ★systemdのtask_struct構造体へのポインタ
systemd ★systemdの名前
0xffff8800342d1f60 ★stapのtask_struct構造体へのポインタ
stapio ★stapの名前
#6 プローブポイントの使い方
##6.1 プローブポイントの表示方法(--dump-probe-aliases)
[root@server stap]# stap --dump-probe-aliases|grep tcp.recvmsg
tcp.recvmsg = kernel.function("tcp_recvmsg")
tcp.recvmsg.return = kernel.function("tcp_recvmsg").return
[root@server stap]# stap --dump-probe-aliases|grep netfilter.arp.out
netfilter.arp.out = netfilter.hook("NF_ARP_OUT").pf("NFPROTO_ARP")
[root@server stap]# stap --dump-probe-aliases|grep netfilter.arp.in
netfilter.arp.in = netfilter.hook("NF_ARP_IN").pf("NFPROTO_ARP")
##6.2 ネットワーク関連
###6.2.1 TCP パケット受信時にIPアドレス、ポート番号を表示する方法(tcp.recvmsg)
tcp.recvmsgプローブポイントの説明は以下のとおり
probe::tcp.recvmsg — Receiving TCP message
tcp_recvmsg()というカーネル関数が呼ばれたとき、IPアドレスやポート番号を表示するsystemtapを作成する。
[root@admin stap]# vi recvmsg.stp
[root@admin stap]# cat recvmsg.stp
#!/usr/bin/stap
probe tcp.recvmsg
{
printf("%s -> tcp.recvmsg src:%s:%d dst:%s:%d size=%d\n", thread_indent(0), saddr, sport, daddr, dport, size)
}
TCPコネクションの送信元/送信先IPアドレス、送信元/送信先ポート番号等が表示されていることがわかる。
[root@admin stap]# stap -v recvmsg.stp
-中略-
0 sshd(816): -> tcp.recvmsg src:192.168.0.100:22 dst:192.168.0.5:49401 size=16384
0 nc(7029): -> tcp.recvmsg src:192.168.0.100:11111 dst:192.168.0.100:55868 size=8192
###6.2.2 ARPパケット送信時のARPヘッダを表示する方法(netfilter.arp.out)
netfilter.arp.outプローブポイントの説明は以下のとおり
probe::netfilter.arp.out — - Called for each outgoing ARP packet
ARP送信パケットのヘッダ(ターゲットIP、ターゲットMAC、オペレーションコード等)を表示します。
なお、ARPヘッダのオペレーションコード(at_op)の意味は次のとおりです。
・ARP要求=1
・ARP応答=2
スクリプトを作成する。
[root@admin stap]# vi arp_out.stp
[root@admin stap]# cat arp_out.stp
#!/usr/bin/stap
probe netfilter.arp.out
{
printf("%s -> arp_out ar_sip=%s, ar_sha=%s, ar_tip=%s, ar_tha=%s, outdev_name=%s, ar_op=%d\n",
thread_indent(0), ar_sip, ar_sha, ar_tip, ar_tha, outdev_name, ar_op)
}
スクリプトを実行する。
[root@admin stap]# stap -v arp_out.stp
-中略-
0 swapper/3(0): -> arp_out ar_sip=192.168.0.100, ar_sha=00:0c:29:9b:e6:7d, ar_tip=192.168.0.5, ar_tha=b4:82:fe:d2:8c:1a, outdev_name=eth0, ar_op=2
0 ping(9264): -> arp_out ar_sip=192.168.0.100, ar_sha=00:0c:29:9b:e6:7d, ar_tip=192.168.0.200, ar_tha=00:00:00:00:00:00, outdev_name=eth0, ar_op=1
0 swapper/2(0): -> arp_out ar_sip=192.168.0.100, ar_sha=00:0c:29:9b:e6:7d, ar_tip=192.168.0.200, ar_tha=00:00:00:00:00:00, outdev_name=eth0, ar_op=1
0 swapper/2(0): -> arp_out ar_sip=192.168.0.100, ar_sha=00:0c:29:9b:e6:7d, ar_tip=192.168.0.200, ar_tha=00:00:00:00:00:00, outdev_name=eth0, ar_op=1
0 swapper/2(0): -> arp_out ar_sip=192.168.0.100, ar_sha=00:0c:29:9b:e6:7d, ar_tip=192.168.0.1, ar_tha=00:00:00:00:00:00, outdev_name=eth0, ar_op=1
0 swapper/3(0): -> arp_out ar_sip=192.168.0.100, ar_sha=00:0c:29:9b:e6:7d, ar_tip=192.168.0.5, ar_tha=b4:82:fe:d2:8c:1a, outdev_name=eth0, ar_op=2
-以下、略-
###6.2.3 ARPパケット受信時のARPヘッダを表示する方法(netfilter.arp.in)
netfilter.arp.inプローブポイントの説明は以下のとおり
probe::netfilter.arp.in — - Called for each incoming ARP packet
[root@admin stap]# vi arp_in.stp
[root@admin stap]# cat arp_in.stp
#!/usr/bin/stap
probe netfilter.arp.in
{
printf("%s -> arp_out ar_sip=%s, ar_sha=%s, ar_tip=%s, ar_tha=%s, outdev_name=%s, ar_op=%d\n",
thread_indent(0), ar_sip, ar_sha, ar_tip, ar_tha, outdev_name, ar_op)
}
ARP要求(ar_op=1)のパケットを2つ受信していることがわかる。
[root@admin stap]# stap -v arp_in.stp
-中略-
0 swapper/3(0): -> arp_out ar_sip=192.168.0.5, ar_sha=b4:82:fe:d2:8c:1a, ar_tip=192.168.0.1, ar_tha=00:00:00:00:00:00, outdev_name=, ar_op=1
0 swapper/3(0): -> arp_out ar_sip=192.168.0.5, ar_sha=b4:82:fe:d2:8c:1a, ar_tip=192.168.0.100, ar_tha=00:00:00:00:00:00, outdev_name=, ar_op=1
-以下、略-
###6.2.4 受信IPパケット(ルーティング前)のIP/TCPヘッダを表示する方法(netfilter.ip.pre_routing)
netfilter.ip.pre_routingプローブポイントの説明は以下のとおり
probe::netfilter.ip.pre_routing — Called before an IP packet is routed
[root@admin stap]# vi ip_pre_routing.stp
[root@admin stap]# cat ip_pre_routing.stp
#!/usr/bin/stap
probe netfilter.ip.pre_routing
{
printf("%s -> indev_name=%s, daddr:dport=%s:%d, saddr:sport=%s:%d SYN=%d:ACK=%d:FIN=%d:RST=%d\n",
thread_indent(0), indev_name, daddr, dport, saddr, sport, syn, ack, fin, rst)
}
スクリプト実行中、yumを実行してrpmパッケージをダウンロードする。
adminサーバ(192.168.0.100)からyumサーバ(37.239.255.23)へのSYNパケットの応答であるSYN+ACKパケットを受信していることがわかる。
[root@admin stap]# stap -v ip_pre_routing.stp
-中略-
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:47342, saddr:sport=103.237.168.15:80 SYN=1:ACK=1:FIN=0:RST=0
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:22, saddr:sport=192.168.0.5:64586 SYN=0:ACK=1:FIN=0:RST=0
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:47342, saddr:sport=103.237.168.15:80 SYN=0:ACK=1:FIN=1:RST=0
★0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:35490, saddr:sport=37.239.255.23:80 SYN=1:ACK=1:FIN=0:RST=0
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:35492, saddr:sport=37.239.255.23:80 SYN=1:ACK=1:FIN=0:RST=0
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:22, saddr:sport=192.168.0.5:64586 SYN=0:ACK=1:FIN=0:RST=0
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:22, saddr:sport=192.168.0.5:64586 SYN=0:ACK=1:FIN=0:RST=0
0 swapper/3(0): -> indev_name=eth0, daddr:dport=192.168.0.100:22, saddr:sport=192.168.0.5:64586 SYN=0:ACK=1:FIN=0:RST=0
-以下、略-
##6.3 ブロックIO関連
###6.3.1 ブロックI/O転送完了時に情報を表示する方法
ioblock.endプローブポイントの説明は以下のとおり
probe::ioblock.end — Fires whenever a block I/O transfer is complete.
スクリプトを作成する。ブロックI/O完了時に開始セクター番号、inode番号を表示する。
[root@admin stap]# vi block.stp
[root@admin stap]# cat block.stp
#!/usr/bin/stap
probe ioblock.end
{
printf("%s -> devname=%s, sector=%d, ino=%d\n",
thread_indent(0), devname, sector, ino)
}
stressコマンドを実行する。
[root@admin ~]# stress -d 1 -q
もう1つターミナルを起動して、stapを実行する。
[root@admin stap]# stap -v block.stp
-中略-
0 swapper/2(0): -> devname=sda, sector=25259768, ino=35348393
0 swapper/2(0): -> devname=sda, sector=25260792, ino=35348393
0 swapper/2(0): -> devname=sda, sector=25261816, ino=35348393
0 swapper/2(0): -> devname=sda, sector=25262840, ino=35348393
-以下、略-
#7 関数の引数、戻り値を表示する方法
下記inet_sendmsg関数の引数(size)、および戻り値を表示してみます。
引数sizeは送信バイト数(アプリケーションが指定した送信バイト数)、
戻り値は実際に送信したバイト数を表します。
引数は変数名に$
を付けることで、その値を参照することができます。
戻り値は$
returnを使うことで、その値を参照することができます。
727 int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
728 size_t ★size)
729 {
730 struct sock *sk = sock->sk;
731
732 sock_rps_record_flow(sk);
733
734 /* We may need to bind the socket. */
735 if (!inet_sk(sk)->inet_num && !sk->sk_prot->no_autobind &&
736 inet_autobind(sk))
737 return -EAGAIN;
738
739 return ★sk->sk_prot->sendmsg(iocb, sk, msg, size);
740 }
スクリプトを作成する。
引数sizeの値を参照するには、sizeに$を付けます。戻り値を参照するには$returnを使います。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("inet_sendmsg").call
{
if (execname() != "sshd")
printf("process=%s:size=%d\n", execname(), $size)
}
probe kernel.function("inet_sendmsg").return
{
if (execname() != "sshd")
printf("process=%s:ret=%d\n", execname(), $return)
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
もう1つターミナルをオープンする。pingコマンド、digコマンドを実行する。
[root@server ~]# ping -c 1 192.168.0.1
[root@server ~]# dig ntp.nict.jp +short
pingプロセス、digプロセスともに、送信バイト数と実際に送信できたバイト数が等しいことがわかる。
[root@server stap]# stap -v tp.stp
process=ping:size=64
process=ping:ret=64
process=dig:size=40
process=dig:ret=40
#8 指定した関数や行にプローブポイントを設定する方法
##8.1 カーネル関数名を指定する方法(kernel.function)
スクリプトを作成する。
net/socket.cファイルのすべての関数にプローブを設定します。
[root@master stap]# vi socket.stp
[root@master stap]# cat socket.stp
#!/usr/bin/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())
}
スクリプトを実行する。
[root@master stap]# stap -v socket.stp
Pass 1: parsed user script and 114 library scripts using 219864virt/32900res/3264shr/29904data kb, in 360usr/460sys/922real ms.
Pass 2: analyzed script: 200 probes, 14 functions, 5 embeds, 2 globals using 281244virt/93164res/4456shr/91284data kb, in 1550usr/960sys/3336real ms.
Pass 3: using cached /root/.systemtap/cache/a4/stap_a4b0874126e90ffe80b7c6ce45a2cfe8_54980.c
Pass 4: using cached /root/.systemtap/cache/a4/stap_a4b0874126e90ffe80b7c6ce45a2cfe8_54980.ko
Pass 5: starting run.
WARNING: probe kernel.function("sock_init@net/socket.c:2621").call (address 0xffffffff81b63485) registration error (rc -22)
WARNING: probe kernel.function("sock_init@net/socket.c:2621").return (address 0xffffffff81b63485) registration error (rc -22)
0 lvmetad(375): -> sock_poll
18 lvmetad(375): <- do_select
0 lvmetad(375): -> sock_poll
4 lvmetad(375): <- do_select
0 sedispatch(495): -> sock_aio_read
6 sedispatch(495): -> sock_aio_read.part.7
36 sedispatch(495): -> alloc_sock_iocb
40 sedispatch(495): <- sock_aio_read.part.7
0 NetworkManager(573): -> sock_poll
14 NetworkManager(573): <- do_sys_poll
-以下、略-
##8.2 カーネル関数の行数を指定する方法(kernel.statement)
例として、下記関数の386行目(★印)にsystemtapのフックポイントを設定します。
関数内の左端は、ファイル(ip_input.c)先頭からの行番号を表します。
378 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
379 {
380 const struct iphdr *iph;
381 u32 len;
382
383 /* When the interface is in promisc. mode, drop all the crap
384 * that it receives, do not try to analyse it.
385 */
386 ★if (skb->pkt_type == PACKET_OTHERHOST)
387 goto drop;
388
389
390 IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
391
392 if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
393 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
394 goto out;
395 }
396
397 if (!pskb_may_pull(skb, sizeof(struct iphdr)))
398 goto inhdr_error;
399
400 iph = ip_hdr(skb);
401
402 /*
403 * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
404 *
405 * Is the datagram acceptable?
406 *
407 * 1. Length at least the size of an ip header
408 * 2. Version of 4
409 * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
410 * 4. Doesn't have a bogus length
411 */
412
413 if (iph->ihl < 5 || iph->version != 4)
414 goto inhdr_error;
415
416 BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);
417 BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);
418 BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);
419 IP_ADD_STATS_BH(dev_net(dev),
420 IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK),
421 max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs));
422
423 if (!pskb_may_pull(skb, iph->ihl*4))
424 goto inhdr_error;
425
426 iph = ip_hdr(skb);
427
428 if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
429 goto csum_error;
430
431 len = ntohs(iph->tot_len);
432 if (skb->len < len) {
433 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
434 goto drop;
435 } else if (len < (iph->ihl*4))
436 goto inhdr_error;
437
438 /* Our transport medium may have padded the buffer out. Now we know it
439 * is IP we can trim to the true length of the frame.
440 * Note this now means skb->len holds ntohs(iph->tot_len).
441 */
442 if (pskb_trim_rcsum(skb, len)) {
443 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
444 goto drop;
445 }
446
447 skb->transport_header = skb->network_header + iph->ihl*4;
448
449 /* Remove any debris in the socket control block */
450 memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
451
452 /* Must drop socket now because of tproxy. */
453 skb_orphan(skb);
454
455 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, NULL, skb,
456 dev, NULL,
457 ip_rcv_finish);
458
459 csum_error:
460 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_CSUMERRORS);
461 inhdr_error:
462 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
463 drop:
464 kfree_skb(skb);
465 out:
466 return NET_RX_DROP;
467 }
スクリプトを作成する。
net/ipv4/ip_input.cファイルの386行目にプローブを設定します。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
global count=0
probe kernel.statement("ip_rcv@net/ipv4/ip_input.c:386")
{
count++
printf ("TIME=%s,COUNT=%d,ProbePoint=%s,FUNC=%s,DEV=%s\n",
tz_ctime(gettimeofday_s()), count, pp(),probefunc(),kernel_string($dev->name))
}
スクリプトを実行する。
[root@server stap]# stap -v tp.stp
-中略-
TIME=Wed Sep 6 21:36:05 2017 JST,COUNT=1,ProbePoint=kernel.statement("ip_rcv@net/ipv4/ip_input.c:386"),FUNC=ip_rcv,DEV=eth0
TIME=Wed Sep 6 21:36:05 2017 JST,COUNT=2,ProbePoint=kernel.statement("ip_rcv@net/ipv4/ip_input.c:386"),FUNC=ip_rcv,DEV=eth0
TIME=Wed Sep 6 21:36:06 2017 JST,COUNT=3,ProbePoint=kernel.statement("ip_rcv@net/ipv4/ip_input.c:386"),FUNC=ip_rcv,DEV=eth0
-以下、略-
##8.3 モジュール内の全ての関数を表示する方法
ここではbridgeモジュールに含まれている関数を表示してみます。
bridgeモジュールのソースコードは、カーネルソースツリーの net/bridge 配下にあります。
モジュールを確認する。ブリッジモジュールがロードされていることがわかる。
[root@master ~]# lsmod |grep bridge
bridge★ 107106 2 br_netfilter,ebtable_broute
stp 12976 1 bridge
llc 14552 2 stp,bridge
スクリプトを確認する。
bridgeモジュール内のすべての関数にプローブを設定します。
[root@master stap]# cat br.stp
#!/usr/bin/stap
probe module("bridge").function("*").call
{
printf ("%s -> %s\n", thread_indent(1), probefunc())
}
probe module("bridge").function("*").return
{
printf ("%s <- %s\n", thread_indent(-1), probefunc())
}
スクリプトを実行する。
[root@master stap]# stap -v br.stp
Pass 1: parsed user script and 114 library scripts using 219864virt/32904res/3264shr/29904data kb, in 400usr/390sys/787real ms.
Pass 2: analyzed script: 704 probes, 14 functions, 5 embeds, 2 globals using 225336virt/39540res/4448shr/35376data kb, in 1050usr/380sys/1440real ms.
Pass 3: using cached /root/.systemtap/cache/76/stap_76bc27b93a153e30dc86d2d94a4d591d_221872.c
Pass 4: using cached /root/.systemtap/cache/76/stap_76bc27b93a153e30dc86d2d94a4d591d_221872.ko
Pass 5: starting run.
別ターミナルを開いて、dockerを起動する。
[root@master ~]# systemctl start docker
[root@master ~]# systemctl is-active docker
active
スクリプトを実行する。dockerd-currentがbridgeを使っていることがわかる。
[root@master stap]# stap -v br.stp
-中略-
0 swapper/1(0): -> br_hello_timer_expired
21 swapper/1(0): -> br_config_bpdu_generation
28 swapper/1(0): <- br_hello_timer_expired
33 swapper/1(0): <- 0xffffffff81095976
0 swapper/1(0): -> br_hello_timer_expired
19 swapper/1(0): -> br_config_bpdu_generation
26 swapper/1(0): <- br_hello_timer_expired
31 swapper/1(0): <- 0xffffffff81095976
0 dockerd-current(17434): -> br_get_size
17 dockerd-current(17434): <- 0xffffffff815818a2
0 dockerd-current(17434): -> br_get_link_af_size
5 dockerd-current(17434): <- 0xffffffff8158192e
0 dockerd-current(17434): -> br_get_stats64
27 dockerd-current(17434): <- 0xffffffff81569cce
0 dockerd-current(17434): -> br_fill_info
5 dockerd-current(17434): <- 0xffffffff81582ccb
-以下、略-
##8.4 モジュール内の特定ファイルの関数を表示する方法
モジュール内の特定ファイルの関数を表示してみます。
--------------------------------
1. br_forward.cファイルの関数表示
--------------------------------
スクリプトを作成する。
net/bridge/br_forward.cファイルの全ての関数にプローブを設定します。
なお、net/bridge/br_forward.cはbridgeモジュールにあります。
[root@master stap]# vi br-forward.stp
[root@master stap]# cat br-forward.stp
#!/usr/bin/stap
probe module("bridge").function("*@net/bridge/br_forward.c").call
{
printf ("%s -> %s\n", thread_indent(1), probefunc())
}
probe module("bridge").function("*@net/bridge/br_forward.c").return
{
printf ("%s <- %s\n", thread_indent(-1), probefunc())
}
スクリプトを実行する。
[root@master stap]# stap -v br-forward.stp
Pass 1: parsed user script and 114 library scripts using 219860virt/32900res/3264shr/29900data kb, in 740usr/50sys/791real ms.
Pass 2: analyzed script: 26 probes, 14 functions, 5 embeds, 2 globals using 222592virt/36808res/4452shr/32632data kb, in 120usr/360sys/485real ms.
Pass 3: using cached /root/.systemtap/cache/36/stap_36a619b5502d06761bfc6d1c946a386c_13871.c
Pass 4: using cached /root/.systemtap/cache/36/stap_36a619b5502d06761bfc6d1c946a386c_13871.ko
Pass 5: starting run.
0 ksoftirqd/0(3): -> br_flood_forward
15 ksoftirqd/0(3): -> br_flood
21 ksoftirqd/0(3): <- br_flood_forward
24 ksoftirqd/0(3): <- br_handle_frame_finish
0 rcu_sched(9): -> br_flood_forward
14 rcu_sched(9): -> br_flood
19 rcu_sched(9): <- br_flood_forward
23 rcu_sched(9): <- br_handle_frame_finish
0 kworker/0:0(16899): -> br_flood_forward
16 kworker/0:0(16899): -> br_flood
21 kworker/0:0(16899): <- br_flood_forward
-以下、略-
--------------------------------
2. br_input.cファイルの関数表示
--------------------------------
スクリプトを確認する。
[root@master stap]# cat br-input.stp
#!/usr/bin/stap
probe module("bridge").function("*@net/bridge/br_input.c").call
{
printf ("%s -> %s\n", thread_indent(1), probefunc())
}
probe module("bridge").function("*@net/bridge/br_input.c").return
{
printf ("%s <- %s\n", thread_indent(-1), probefunc())
}
スクリプトを実行する。
[root@master stap]# stap -v br-input.stp
Pass 1: parsed user script and 114 library scripts using 219868virt/32900res/3264shr/29908data kb, in 510usr/300sys/814real ms.
Pass 2: analyzed script: 6 probes, 14 functions, 5 embeds, 2 globals using 222508virt/36732res/4452shr/32548data kb, in 80usr/440sys/525real ms.
Pass 3: translated to C into "/tmp/stap9xUr9G/stap_0dced1535fee6b508f21f14d9df84b13_7975_src.c" using 222508virt/37072res/4792shr/32548data kb, in 100usr/180sys/282real ms.
Pass 4: compiled C into "stap_0dced1535fee6b508f21f14d9df84b13_7975.ko" in 6130usr/1540sys/7340real ms.
Pass 5: starting run.
0 ksoftirqd/1(13): -> br_handle_frame
138 ksoftirqd/1(13): -> br_handle_frame_finish
182 ksoftirqd/1(13): <- 0xffffffffa066f12b
188 ksoftirqd/1(13): <- 0xffffffff8156f1e2
0 ksoftirqd/1(13): -> br_handle_frame
65 ksoftirqd/1(13): -> br_handle_frame_finish
104 ksoftirqd/1(13): <- 0xffffffffa066f12b
109 ksoftirqd/1(13): <- 0xffffffff8156f1e2
0 kworker/1:0(19498): -> br_handle_frame
39 kworker/1:0(19498): -> br_handle_frame_finish
-以下、略-
##8.5 ブリッジのデバイス名の表示
netdev_tx_t br_dev_xmit(struct sk_buff *skb, ★struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
const unsigned char *dest = skb->data;
struct net_device {
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* of the interface.
*/
char ★name[IFNAMSIZ];
スクリプトを作成する。
[root@master stap]# vi br_dev_xmit.stp
[root@master stap]# cat br_dev_xmit.stp
#!/usr/bin/stap
probe module("bridge").function("br_dev_xmit").call
{
printf ("%s -> %s,devname=%s\n", thread_indent(1), probefunc(), kernel_string($dev->name))
}
probe module("bridge").function("br_dev_xmit").return
{
printf ("%s <- %s\n", thread_indent(-1), probefunc())
}
スクリプトを実行する。ブリッジのデバイス名がcbr0であることがわかる(★印)。
[root@master stap]# stap -v br_dev_xmit.stp
Pass 1: parsed user script and 114 library scripts using 219868virt/32900res/3264shr/29908data kb, in 710usr/250sys/959real ms.
Pass 2: analyzed script: 2 probes, 16 functions, 5 embeds, 2 globals using 222312virt/36504res/4492shr/32352data kb, in 130usr/640sys/789real ms.
Pass 3: using cached /root/.systemtap/cache/45/stap_45db6ca7bf64bb005bedc2ffa6608580_8169.c
Pass 4: using cached /root/.systemtap/cache/45/stap_45db6ca7bf64bb005bedc2ffa6608580_8169.ko
Pass 5: starting run.
0 ping(21469): -> br_dev_xmit,★devname=cbr0
78 ping(21469): <- 0xffffffff8156edc1
0 ping(21469): -> br_dev_xmit,★devname=cbr0
9 ping(21469): <- 0xffffffff8156edc1
-以下、略-
ipコマンドでブリッジ(cbr0)を確認する。
[root@master ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 00:0c:29:18:5c:90 brd ff:ff:ff:ff:ff:ff
3:★cbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1430 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1000
link/ether 46:a0:23:02:55:e7 brd ff:ff:ff:ff:ff:ff
##8.6 ブリッジのポート番号表示
スクリプトを作成する。
[root@master stap]# vi br_deliver.stp
[root@master stap]# cat br_deliver.stp
#!/usr/bin/stap
probe module("bridge").function("br_deliver").call
{
printf ("%s -> %s,port_no=%d\n", thread_indent(1), probefunc(), $to->port_no)
}
probe module("bridge").function("br_deliver").return
{
printf ("%s <- %s\n", thread_indent(-1), probefunc())
}
スクリプトを実行する。
[root@master stap]# stap -v br_deliver.stp
Pass 1: parsed user script and 114 library scripts using 219860virt/32896res/3264shr/29900data kb, in 960usr/60sys/1060real ms.
Pass 2: analyzed script: 2 probes, 15 functions, 5 embeds, 2 globals using 222300virt/36500res/4492shr/32340data kb, in 130usr/480sys/613real ms.
Pass 3: using cached /root/.systemtap/cache/44/stap_44357f6da3d5ddf5f350a8b01704d3fc_7451.c
Pass 4: using cached /root/.systemtap/cache/44/stap_44357f6da3d5ddf5f350a8b01704d3fc_7451.ko
Pass 5: starting run.
コンテナを起動する。
[root@master ~]# docker run -it --name test1 busybox sh
/ # ping 172.18.10.1
コンテナ(test1)は、ブリッジのポート1番(★印)にパケットを送信していることがわかる。
[root@master stap]# stap -v br_deliver.stp
-中略-
0 ping(22217): -> br_deliver,★port_no=1
24 ping(22217): <- br_dev_xmit
0 ping(22217): -> br_deliver,★port_no=1
24 ping(22217): <- br_dev_xmit
もう1つ、コンテナを起動する。この時点で2つ起動している。
[root@master ~]# docker run -it --name test2 busybox sh
/ # ping 172.18.10.1
2つ目のコンテナ(test2)は、ブリッジのポート2番(●印)にパケットを送信していることがわかる。
[root@master stap]# stap -v br_deliver.stp
-中略-
0 ping(22218): -> br_deliver,●port_no=2
19 ping(22218): <- br_dev_xmit
0 ping(22218): -> br_deliver,●port_no=2
8 ping(22218): <- br_dev_xmit
-以下、略-
#9 Typecasting(@cast)の使い方
##9.1 スクリプトの例
[root@server ~]# vi tcp.stp
[root@server ~]# cat tcp.stp
#!/usr/bin/stap
function printconn(skb)
{
iphdr = __get_skb_iphdr(skb)
tcphdr = __get_skb_tcphdr(skb)
daddr = format_ipaddr(__ip_skb_daddr(iphdr), AF_INET())
saddr = format_ipaddr(__ip_skb_saddr(iphdr), AF_INET())
dport = __tcp_skb_dport(tcphdr)
sport = __tcp_skb_sport(tcphdr)
syn = __tcp_skb_syn(tcphdr)
ack = __tcp_skb_ack(tcphdr)
fin = __tcp_skb_fin(tcphdr)
rst = __tcp_skb_rst(tcphdr)
if(dport == 443) {
printf("TIME=%s,PP=%s,syn=%d,ack=%d,fin=%d,rst=%d [%s:%d-> %s:%d]\n",
tz_ctime(gettimeofday_s()), pp(), syn, ack, fin, rst, saddr, sport, daddr, dport);
net_device = @cast(skb, "struct sk_buff", "kernel<linux/skbuff.h>")->dev
dev = @cast(net_device, "struct net_device", "kernel<linux/netdevice.h>")->name
seq = @cast(tcphdr, "struct tcphdr", "kernel<linux/tcp.h>")->seq
ack = @cast(tcphdr, "struct tcphdr", "kernel<linux/tcp.h>")->ack_seq
printf("net_device=%p, dev=%s, seq=%d ack=%d\n", net_device, kernel_string(dev), ntohl(seq), ntohl(ack));
}
}
probe kernel.function("dev_queue_xmit@net/core/dev.c")
{
printconn($skb)
}
##9.2 実行結果
スクリプトを実行する。
[root@server icmp]# stap -v net.stp
curlコマンドを実行する。
[root@server icmp]# curl -I https://www.google.co.jp
タイプキャストしたTCPのフラグ(syn,ack,fin,rst)が表示されていることがわかる。
[root@server icmp]# stap -v net.stp
TIME=Tue Dec 5 21:59:44 2017 JST,PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"),★syn=0,ack=1,fin=0,rst=0 [192.168.0.10:51678-> 216.58.197.195:443]
-以下、略-
#10 共用体メンバへのアクセス方法
pingを実行すると、ICMPパケットが送信されます。
ICMPパケットにはsequence番号(★)の領域があります。
pingを実行するとわかるのですが、1から順に増加していくことがわかります。
[root@server ~]# tcpdump -i eth0 icmp -vv -nn
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
21:49:12.620365 IP (tos 0x0, ttl 64, id 41572, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.0.10 > 192.168.0.1: ICMP echo request, id 4417, ★seq 1, length 64
sequence番号は、icmhdr構造体内のecho共用体で定義されています。
ここでは、共用体のメンバ(sequence)の表示方法について実行例を示します。
struct icmphdr
{
u_int8_t type; /* message type */
u_int8_t code; /* type sub-code */
u_int16_t checksum;
union
{
struct
{
u_int16_t id;
u_int16_t sequence;
} echo; /* echo datagram */
u_int32_t gateway; /* gateway address */
struct
{
u_int16_t __unused;
u_int16_t mtu;
} frag; /* path mtu discovery */
} un;
};
##10.1 スクリプトの例
共用体のメンバ(sequence)にアクセスする部分は、以下になります。
sequence = @cast(tcphdr, "struct icmphdr", "kernel<linux/icmp.h>")->un->echo->sequence
以下は、共用体のメンバ表示に加え、tcpのシーケンス番号を表示する処理も入っています。
[root@server icmp]# vi net.stp
[root@server icmp]# cat net.stp
#!/usr/bin/stap
function printconn(skb)
{
iphdr = __get_skb_iphdr(skb)
tcphdr = __get_skb_tcphdr(skb)
proto = __ip_skb_proto(iphdr)
daddr = format_ipaddr(__ip_skb_daddr(iphdr), AF_INET())
saddr = format_ipaddr(__ip_skb_saddr(iphdr), AF_INET())
dport = __tcp_skb_dport(tcphdr)
sport = __tcp_skb_sport(tcphdr)
net_device = @cast(skb, "struct sk_buff", "kernel<linux/skbuff.h>")->dev
dev = @cast(net_device, "struct net_device", "kernel<linux/netdevice.h>")->name
# in case of icmp
if(proto == 1) {
sequence = @cast(tcphdr, "struct icmphdr", "kernel<linux/icmp.h>")->un->echo->sequence
printf("PP=%s, dev=%s, [%s->%s], seq=%d\n", pp(), kernel_string(dev), saddr, daddr, ntohs(sequence));
}
# in case of tcp
else if(proto == 6) {
if(dport==443) {
seq = @cast(tcphdr, "struct tcphdr", "kernel<linux/tcp.h>")->seq
printf("PP=%s, [%s:%d->%s:%d], seq=%d\n", pp(), saddr, sport, daddr, dport, ntohl(seq));
}
}
# in case of udp
else if(proto == 17){
printf("PP=%s, dev=%s, [%s:%d->%s:%d]\n", pp(), kernel_string(dev), saddr, sport, daddr, dport);
}
}
probe kernel.function("dev_queue_xmit@net/core/dev.c")
{
printconn($skb)
}
##10.1 実行結果
ping,curl,digコマンドを実行して、スクリプトの動作確認をする。
pingを実行する。私の場合、デフォルトGWに対してpingを実行しました。
sequence番号が1,2,3と増加していくことがわかるます。
[root@server ~]# ping -c 1 192.168.0.1
[root@server ~]# stap -v net.stp
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), dev=eth1, [192.168.0.10->192.168.0.1], ★seq=1
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), dev=eth1, [192.168.0.10->192.168.0.1], ★seq=2
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), dev=eth1, [192.168.0.10->192.168.0.1], ★seq=3
-以下、略-
curlコマンドを実行する。私の場合、google対してcurlを実行しました。
[root@server ~]# curl -I https://www.google.co.jp
[root@server ~]# stap -v net.stp
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), [192.168.0.10:54552->172.217.25.99:443], seq=3233275123
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), [192.168.0.10:54552->172.217.25.99:443], seq=3233275123
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), [192.168.0.10:54552->172.217.25.99:443], seq=3233275124
-以下、略-
digコマンドを実行する。私の場合、ntpの名前解決を実行しました。
[root@server ~]# dig ntp.nict.co.jp
[root@server ~]# stap -v net.stp
PP=kernel.function("dev_queue_xmit@net/core/dev.c:3122"), dev=eth1, [192.168.0.10:46506->192.168.3.1:53]
#11 非同期イベントの使い方
非同期イベントには、以下のようなものがあります。
・begin:systemtap開始時に呼ばれます。
・end:systemtap終了時に呼ばれます。
・timer.s(X):X秒毎に呼ばれます。
・timer.ms(X):Xミリ秒毎に呼ばれます。
・timer.us(X):Xマイクロ秒毎に呼ばれます。
・timer.jiffies(X):Xjiffies毎に呼ばれます。
gettimeofday_s()は、UNIX エポックからの経過時間(秒)を求めます。
tz_ctime()はエポックからの経過時間を引数にして、ローカルの時刻を求めます。
[root@admin stap]# vi async_event.stp
[root@admin stap]# cat async_event.stp
#!/usr/bin/stap
global count
probe begin
{
printf("---BEGIN---\n")
}
probe timer.s(2)
{
printf("TIME=%s, COUNT=%d\n", tz_ctime(gettimeofday_s()), ++count)
}
probe end
{
printf("---END---\n")
printf("COUNT=%d\n", count)
}
timerイベントが2秒毎に呼ばれていることがわかる。
また、endイベントの呼び出しにより、timerイベントを呼び出した回数が5回であることもわかる。
[root@admin stap]# stap -v async_event.stp
-中略-
---BEGIN---
TIME=Sun Aug 6 18:58:09 2017 JST, COUNT=1
TIME=Sun Aug 6 18:58:11 2017 JST, COUNT=2
TIME=Sun Aug 6 18:58:13 2017 JST, COUNT=3
TIME=Sun Aug 6 18:58:15 2017 JST, COUNT=4
TIME=Sun Aug 6 18:58:17 2017 JST, COUNT=5
TIME=Sun Aug 6 18:58:19 2017 JST, COUNT=6
^CTIME=Sun Aug 6 18:58:21 2017 JST, COUNT=7
---END---
COUNT=7
Pass 5: run completed in 20usr/100sys/11375real ms.
[root@admin stap]#
#12 連想配列の使い方
##12.1 呼び出したシステムコール名の回数を表示する方法
[root@admin stap]# vi tp.stp
[root@admin stap]# cat tp.stp
#!/usr/bin/stap
global syscalllist
probe syscall.*
{
if (execname() == "sshd") {
syscalllist[name]++
}
}
probe timer.s(1) {
foreach ( name in syscalllist ) {
printf("%s = %d\n", name, syscalllist[name] )
}
printf("--------------------------------\n")
}
[root@admin stap]# stap -v tp.stp
Pass 1: parsed user script and 114 library scripts using 219868virt/32916res/3280shr/29908data kb, in 790usr/40sys/843real ms.
Pass 2: analyzed script: 522 probes, 32 functions, 98 embeds, 1 global using 369756virt/184664res/4580shr/179796data kb, in 39520usr/540sys/40124real ms.
Pass 3: translated to C into "/tmp/stapf4oNrR/stap_34dd4218666e84bd3e90bdebf7819b2a_324380_src.c" using 369756virt/185024res/4940shr/179796data kb, in 260usr/160sys/421real ms.
Pass 4: compiled C into "stap_34dd4218666e84bd3e90bdebf7819b2a_324380.ko" in 20690usr/2380sys/21933real ms.
Pass 5: starting run.
--------------------------------
rt_sigprocmask = 4
clock_gettime = 4
read = 1
select = 2
write = 1
--------------------------------
rt_sigprocmask = 8
clock_gettime = 8
read = 2
select = 4
write = 2
--------------------------------
rt_sigprocmask = 12
clock_gettime = 12
read = 3
select = 6
write = 3
--------------------------------
rt_sigprocmask = 16
clock_gettime = 16
read = 4
select = 8
write = 4
--------------------------------
-以下、略-
##12.2 pingの宛先IPアドレスの集計方法
[root@server stap]# cat icmp.stp
#!/usr/bin/stap
global ipdstlist
function print_conn(skb)
{
iphdr = __get_skb_iphdr(skb)
daddr = format_ipaddr(__ip_skb_daddr(iphdr), AF_INET())
saddr = format_ipaddr(__ip_skb_saddr(iphdr), AF_INET())
ipdstlist[daddr]++
}
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if(execname() == "ping"){
print_conn($skb)
}
}
probe end
{
foreach(i in ipdstlist-)
printf("%s : %d\n", i, ipdstlist[i])
}
cパラメータで指定した回数、pingを実行してみます。
Wオプションはタイムアウト(1秒)を指定しています。
なお、pingの使い方は、ここ(pingコマンドの使い方)を参照してください。
[root@server ~]# ping -W 1 -c 1 192.168.3.1
[root@server ~]# ping -W 1 -c 2 192.168.3.2
[root@server ~]# ping -W 1 -c 3 192.168.3.3
192.168.3.3
には3回、192.168.3.2
には2回、192.168.3.1
には1回、
pingを実行していることがわかります。
[root@server stap]# stap -v icmp.stp
-中略-
^C192.168.3.3 : 3
192.168.3.2 : 2
192.168.3.1 : 1
#13 スクリプトに渡す引数の使い方
systemtapのスクリプトを実行するとき、引数を指定することができます。
ここでは、コマンド名を引数にしてスクリプトを実行します。
たとえば、digを引数に指定した場合、digプロセスのコンテキストで
dev_queue_xmit()が実行されると、printfが実行されます。
##13.1 スクリプト
スクリプトを作成する。スクリプトに渡すパラメータは@1として表されます。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe kernel.function("dev_queue_xmit@net/core/dev.c")
{
if(execname() == @1) {
printf("procname=%s,pp=%s\n", execname(), pp())
}
}
##13.2 実行結果(dig実行時)
引数に"dig"を指定して、スクリプトを実行する。
[root@server stap]# stap -v tp.stp dig
もう1つターミナルを開いて、digコマンドを実行する。
[root@server stap]# dig ntp.nict.jp +short
digコマンドを実行したときだけ、printfが実行される。
[root@server stap]# stap -v tp.stp dig
-中略-
procname=dig,pp=kernel.function("dev_queue_xmit@net/core/dev.c:3122")
procname=dig,pp=kernel.function("dev_queue_xmit@net/core/dev.c:3122")
##13.3 実行結果(ping実行時)
引数に"ping"を指定して、スクリプトを実行する。
[root@server stap]# stap -v tp.stp ping
もう1つターミナルを開いて、pingコマンドを実行する。
[root@server linux]# ping -c 1 192.168.0.1
pingコマンドを実行したときだけ、printfが実行される。
[root@server stap]# stap -v tp.stp ping
-中略-
procname=ping,pp=kernel.function("dev_queue_xmit@net/core/dev.c:3122")
procname=ping,pp=kernel.function("dev_queue_xmit@net/core/dev.c:3122")
#14 繰り返し(foreach)
##14.1 配列の中身を昇順に表示する方法
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
global test
probe begin {
for (i = 0; i < 5; i++) {
test [no++] = i
}
exit()
}
probe end {
foreach (x in test) {
printf ("test[%d] = %d\n", x, test[x])
}
delete test
}
スクリプトを実行する。配列が昇順に表示されていることがわかる。
[root@server stap]# stap -v tp.stp
test[0] = 0
test[1] = 1
test[2] = 2
test[3] = 3
test[4] = 4
##14.2 配列の中身を降順に表示する方法(-)
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
global test
probe begin {
for (i = 0; i < 5; i++) {
test [no++] = i
}
exit()
}
probe end {
foreach (x in test-) {
printf ("test[%d] = %d\n", x, test[x])
}
delete test
}
スクリプトを実行する。配列が降順に表示されていることがわかる。
[root@server stap]# stap -v tp.stp
test[4] = 4
test[3] = 3
test[2] = 2
test[1] = 1
test[0] = 0
#15 サンプル
##15.1 sock構造体からIPアドレス、ポート番号を求める方法
tcp_rcv_established関数の引数(sock構造体へのポインタsk)より、送信元IPアドレス、宛先ポート番号を求めてみます。
5165 void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
5166 const struct tcphdr *th, unsigned int len)
5167 {
5168 struct tcp_sock *tp = tcp_sk(sk);
以下、略
サンプルスクリプト内では、inet_get_local_portとinet_get_ip_sourceの
2つのtapsetを呼び出しました。tapsetはsystemtapに付属している共通関数です。
tapsetは/usr/share/systemtap/tapset/linux/inet_sock.stpで定義されています。
なお、サンプルスクリプト内の$1は、systemtapへの引数です。ここでは、ポート番号を指定します。
[root@centos74 stap]# cat sock.stp
#!/usr/bin/stap
probe kernel.function("tcp_rcv_established@net/ipv4/tcp_input.c")
{
port = inet_get_local_port($sk);
ip = inet_get_ip_source($sk);
if( port == $1) {
printf ("pp=%s,ip=%s,port=%d\n", pp(),ip,port)
}
}
サーバ側でncコマンドを実行します。ncプロセスは、11111番ポートでListenします。
[root@server ~]# nc -kl 11111
systemtapスクリプトを実行します。ここでは、ポート番号11111に対するTCPパケットを監視します。
[root@server ~]# stap -v sock.stp 11111
Pass 1: parsed user script and 470 library scripts using 229568virt/42456res/3300shr/39432data kb, in 530usr/20sys/564real ms.
Pass 2: analyzed script: 1 probe, 11 functions, 1 embed, 0 globals using 398312virt/209756res/4508shr/208176data kb, in 1410usr/440sys/1841real ms.
Pass 3: using cached /root/.systemtap/cache/56/stap_56c59a63e0c00123128ee519b6c8accb_9042.c
Pass 4: using cached /root/.systemtap/cache/56/stap_56c59a63e0c00123128ee519b6c8accb_9042.ko
Pass 5: starting run.
クライアントからサーバにTCPパケット("aaaaa")を送信する。
[root@client ~]# nc 192.168.3.20 11111
aaaaa
サーバ側のsystemtapスクリプトが下記メッセージを表示します。
[root@server ~]# stap -v sock.stp 11111
(中略)
pp=kernel.function("tcp_rcv_established@net/ipv4/tcp_input.c:5165"),ip=192.168.3.10,port=11111
##15.2 ARPパケットからMACアドレス、IPアドレスを求める方法
328 static void arp_send_dst(int type, int ptype, __be32 dest_ip,
329 struct net_device *dev, __be32 src_ip,
330 const unsigned char *dest_hw,
331 const unsigned char *src_hw,
332 const unsigned char *target_hw,
333 struct dst_entry *dst)
334 {
335 struct sk_buff *skb;
336
337 /* arp on this interface. */
338 if (dev->flags & IFF_NOARP)
339 return;
340
341 skb = arp_create(type, ptype, dest_ip, dev, src_ip,
342 dest_hw, src_hw, target_hw);
343 if (!skb)
344 return;
345
346 skb_dst_set(skb, dst_clone(dst));
347 arp_xmit(skb);
348 }
[root@server stap]# vi arp.stp
[root@server stap]# cat arp.stp
#!/usr/bin/stap
function mac_addr_to_string:string(addr:long) {
return sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
kernel_char(addr)&255, kernel_char(addr+1)&255,
kernel_char(addr+2)&255, kernel_char(addr+3)&255,
kernel_char(addr+4)&255, kernel_char(addr+5)&255)
}
probe kernel.statement("arp_send_dst@net/ipv4/arp.c:341")
{
printf("pp=%s,mac=%s,dev=%s,dest_ip=%s,src_ip=%s\n",pp(),
mac_addr_to_string($src_hw),
kernel_string($dev->name),
format_ipaddr($dest_ip,AF_INET()),
format_ipaddr($src_ip,AF_INET()));
}
スクリプトを実行する。
[root@server stap]# stap -v arp.stp
pingを実行する。宛先は実在しないipアドレスを指定する。
[root@server linux]# ping 192.168.0.111
[root@server stap]# stap -v arp.stp
-中略-
pp=kernel.statement("arp_send_dst@net/ipv4/arp.c:341"),mac=00:0c:29:9b:e6:87,dev=bond0,dest_ip=192.168.0.111,src_ip=192.168.0.10
pp=kernel.statement("arp_send_dst@net/ipv4/arp.c:341"),mac=00:0c:29:9b:e6:87,dev=bond0,dest_ip=192.168.0.111,src_ip=192.168.0.10
pp=kernel.statement("arp_send_dst@net/ipv4/arp.c:341"),mac=00:0c:29:9b:e6:87,dev=bond0,dest_ip=192.168.0.111,src_ip=192.168.0.10
[root@server stap]# ip link
5: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT qlen 1000
link/ether 00:0c:29:9b:e6:87 brd ff:ff:ff:ff:ff:ff
##15.3 イーサネットタイプの使い方
イーサネットのタイプフィールドにしたがって、アドレスを表示してみます。
イーサネットのタイプフィールドは、IPv4なら0x800、IPv6なら0x86ddと決まっています。
[root@server stap]# cat ipv6.stp
#!/usr/bin/stap
function judge_proto(skb)
{
iphdr = __get_skb_iphdr(skb)
proto = __ip_skb_proto(iphdr)
# ICMP
if(proto == 1) {
return 1
}
# UDP
else if(proto == 17) {
return 17
}
}
function judge_ethertype(protocol)
{
proto = ntohs(protocol)
if(proto == 0x800) {
return 0x800
}
else if(proto == 0x86dd) {
return 0x86dd
}
}
function print_v4_addr(skb)
{
iphdr = __get_skb_iphdr(skb)
saddr = format_ipaddr(__ip_skb_saddr(iphdr), @const("AF_INET"))
daddr = format_ipaddr(__ip_skb_daddr(iphdr), @const("AF_INET"))
printf("saddr=%s->daddr=%s\n", saddr, daddr);
}
function print_v6_addr(skb)
{
iphdr = __get_skb_iphdr(skb)
saddr = format_ipaddr(&@cast(iphdr, "ipv6hdr", "kernel<linux/ipv6.h>")->saddr,@const("AF_INET6"))
daddr = format_ipaddr(&@cast(iphdr, "ipv6hdr", "kernel<linux/ipv6.h>")->daddr,@const("AF_INET6"))
printf("saddr=%s->daddr=%s\n", saddr, daddr);
}
probe kernel.function("__netif_receive_skb_core@net/core/dev.c")
{
## in case of IPv4
if(judge_ethertype($skb->protocol) == 0x800){
if(judge_proto($skb) == 1){
print_v4_addr($skb)
}
else if(judge_proto($skb) == 17){
print_v4_addr($skb)
}
}
## in case of IPv6
else if(judge_ethertype($skb->protocol) == 0x86dd){
print_v6_addr($skb)
}
}
##15.4 sk_buff構造体やsock構造体からIPアドレスを求める関数
sk_buff構造体に格納されているIPアドレスが、指定した送信元/送信先IPアドレスかどうかを判定します。
[root@server stap]# cat mss.stp
#!/usr/bin/stap
function is_skb_conn(skb)
{
iphdr = __get_skb_iphdr(skb)
daddr = format_ipaddr(__ip_skb_daddr(iphdr), AF_INET())
saddr = format_ipaddr(__ip_skb_saddr(iphdr), AF_INET())
if((saddr == "192.168.3.30") || (daddr == "192.168.3.30")){
printf("saddr=%s, daddr=%s\n", saddr,daddr)
return 1
}
}
probe kernel.statement("tcp_parse_options@net/ipv4/tcp_input.c:3682")
{
if(is_skb_conn($skb) == 1){
printf("pp=%s,mss=%d\n",pp(),ntohs(kernel_short($ptr)));
}
}
sock構造体に格納されているIPアドレスが、指定した送信元/送信先IPアドレスかどうかを判定します。
[root@server stap]# cat mss.stp
#!/usr/bin/stap
function is_sk_conn(sk)
{
daddr = format_ipaddr(__ip_sock_daddr(sk), AF_INET())
saddr = format_ipaddr(__ip_sock_saddr(sk), AF_INET())
if((saddr == "192.168.3.30") || (daddr == "192.168.3.30")){
printf("saddr=%s, daddr=%s\n", saddr,daddr)
return 1
}
}
probe kernel.function("tcp_send_mss@net/ipv4/tcp.c").return
{
if(is_sk_conn($sk) == 1){
printf("pp=%s,ret=%d\n", pp(),$return)
}
}
#16 guruモードの使い方
systemtapのスクリプトにC言語を埋め込むことができるようです。
guruモードの説明は、ここ(SystemTap Language Reference)にあります。
##16.1 その1
pingを実行したときに、jiffiesと送信元IP/送信先IPを表示するスクリプトです。
[root@server stap]# cat ping.stp
#!/usr/bin/stap
%{
#include <linux/jiffies.h>
%}
function get_jiffies()
%{
THIS->__retvalue = jiffies;
%}
function printconn(skb)
{
iphdr = __get_skb_iphdr(skb)
proto = __ip_skb_proto(iphdr)
daddr = format_ipaddr(__ip_skb_daddr(iphdr), AF_INET())
saddr = format_ipaddr(__ip_skb_saddr(iphdr), AF_INET())
if(proto == 1) {
printf("jiffies=%d ,pp=%s, %s->%s\n", get_jiffies() ,pp(), saddr, daddr);
}
}
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
printconn($skb)
}
guruモードで実行するときは、-gオプションを付ける。
[root@server stap]# stap -vg ping.stp
-中略-
jiffies=4297312929 ,pp=kernel.function("ip_output@net/ipv4/ip_output.c:352"), 192.168.3.20->192.168.3.1
jiffies=4297313931 ,pp=kernel.function("ip_output@net/ipv4/ip_output.c:352"), 192.168.3.20->192.168.3.1
jiffies=4297314931 ,pp=kernel.function("ip_output@net/ipv4/ip_output.c:352"), 192.168.3.20->192.168.3.1
#X 参考情報
SystemTap
SystemTapの使い方(User-Space Probing)
SystemTapのお勉強
SystemTapExamples
Components of a SystemTap script
Red Hat Enterprise Linux 7 SystemTap ビギナーズガイド
Chapter 11. Networking Tapset
DTrace SystemTap cheatsheet
SYSTEM TAP リファレンス
Linux のイントロスペクションと SystemTap(IBM)
第14回「 SystemTapノススメ」
SystemTap How-to(日立)
SystemTap
SystemTap 3.0 SystemTap Beginners Guide
SystemTap:Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems(IBM)
Dynamic Tracing with DTrace SystemTap
5.6.11 Pointer typecasting
SystemTap Beginners Guide
SystemTap SystemTap
SystemTap Language Reference
第4章 便利な SYSTEMTAP スクリプト
systemtap-cheat-sheet(github)
https://mmitou.hatenadiary.org/entry/20120721/1342879187
#Y メモ
kernel_char(address)
Obtain the character at address from kernel memory.
kernel_short(address)
Obtain the short at address from kernel memory.
kernel_int(address)
Obtain the int at address from kernel memory.
kernel_long(address)
Obtain the long at address from kernel memory
kernel_string(address)
Obtain the string at address from kernel memory.
kernel_string_n(address, n)
Obtain the string at address from the kernel memory and limits the string to n bytes.
[root@client stap]# cat window.stp
#!/usr/bin/stap
probe module("nf_conntrack").statement("tcp_in_window@net/netfilter/nf_conntrack_proto_tcp.c:733")
{
source = &@cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->source
dest = &@cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->dest
sport = ntohs(kernel_short(source))
dport = ntohs(kernel_short(dest))
if((sport == 11111) || (dport == 11111)){
send = &@cast($state, "struct ip_ct_tcp", "kernel:nf_conntrack")->seen[$dir]
recv = &@cast($state, "struct ip_ct_tcp", "kernel:nf_conntrack")->seen[!$dir]
printf("-------------------------------------------------------------------------\n")
printf("sender->td_end=%d, sender->td_maxend=%d, sender->td_maxwin=%d\n",
send->td_end, send->td_maxend, send->td_maxwin)
printf("receiver->td_end=%d, receiver->td_maxend=%d, receiver->td_maxwin=%d\n",
recv->td_end, recv->td_maxend, recv->td_maxwin)
syn = @cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->syn
fin = @cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->fin
ack = @cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->ack
seq_tmp = &@cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->seq
seq = ntohl(kernel_int(seq_tmp))
ack_seq_tmp = &@cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->ack_seq
ack_seq = ntohl(kernel_int(ack_seq_tmp))
win_tmp = &@cast($tcph, "struct tcphdr", "kernel<uapi/linux/tcp.h>")->window
win = ntohs(kernel_short(win_tmp))
printf("dir=%d, sport=%d, dport=%d, syn=%d, fin=%d, ack=%d, seq=%d, ack=%d, win=%d\n",
$dir, sport, dport, syn, fin, ack, seq, ack_seq, win)
printf("\n")
}
}