LoginSignup
16

More than 1 year has passed since last update.

SystemTapの使い方

Last updated at Posted at 2017-03-23

以下に移行予定です。
https://hana-shin.hatenablog.com/entry/2023/01/16/230339

#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をインストールする。

systemtapのインストール
[root@master tools]#  yum -y install systemtap systemtap-runtime

base-debuginfoを有効にして、kernel-debuginfoをインストールする。

kernel-debuginfoのインストール
[root@master ~]# yum --enablerepo=base-debuginfo -y install kernel-debuginfo-3.10.0-514.el7.x86_64

base-debuginfoを有効にして、kernel-develをインストールする。

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コマンドの使い方)を参照してください。

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アドレス、宛先ポート番号を求めてみます。

tcp_rcv_established関数の抜粋
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します。

ターミナル1(サーバ側)
[root@server ~]# nc -kl 11111

systemtapスクリプトを実行します。ここでは、ポート番号11111に対するTCPパケットを監視します。

ターミナル2(サーバ側)
[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")を送信する。

ターミナル3(クライアント側)
[root@client ~]# nc 192.168.3.20 11111
aaaaa

サーバ側のsystemtapスクリプトが下記メッセージを表示します。

ターミナル2(サーバ側)
[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アドレスを求める方法

arp_send_dst関数の抜粋
 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アドレスかどうかを判定します。

skbの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アドレスかどうかを判定します。

skの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")
  }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16