SystemTapの使い方

以下に移行予定です。
[SystemTapの使い方](https://hana-shin.hatenablog.com/entry/2023/01/16/230339)
[SystemTapの使い方(User-Space Probing)](https://hana-shin.hatenablog.com/entry/2023/01/20/202125)

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

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

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

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

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

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

インスールしたパッケージを確認する。下記実行結果では、もともと入っているパッケージも一緒に表示されています。

```console:インストールしたパッケージの確認
[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
```
サンプルは[ここ](https://sourceware.org/systemtap/examples/)からも参照できます。

他に書いた記事
[SystemTapの使い方(その2)](https://qiita.com/hana_shin/items/13fc31204426f7187e60)
[SystemTapの使い方(グルモード編)](https://qiita.com/hana_shin/items/f2806e68493c89fbbef8)
[SystemTapの使い方（User-Space Probing)](https://qiita.com/hana_shin/items/6bca693206bf5f887cb3)
[SystemTapの使い方(tapset編)](https://qiita.com/hana_shin/items/0f82ea9d8bc8ed5fd70b)


#4 Tapsetの使い方
Tapsetの詳細説明は[ここ](https://sourceware.org/systemtap/tapsets/)を参照してください。
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プローブポイントの[説明](https://sourceware.org/systemtap/tapsets/API-tcp-recvmsg.html)は以下のとおり
>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プローブポイントの[説明](https://sourceware.org/systemtap/tapsets/API-netfilter-arp-out.html)は以下のとおり
>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プローブポイントの[説明](https://sourceware.org/systemtap/tapsets/API-netfilter-arp-in.html)は以下のとおり
>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プローブポイントの[説明](https://sourceware.org/systemtap/tapsets/API-netfilter-ip-pre-routing.html)は以下のとおり
>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プローブポイントの[説明](https://sourceware.org/systemtap/tapsets/API-ioblock-end.html)は以下のとおり
>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アドレスの集計方法


```console:スクリプト
[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コマンドの使い方)](https://qiita.com/hana_shin/items/c31b0d05a91244c4db83)を参照してください。

```console: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を実行していることがわかります。

```console:スクリプトの実行結果
[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アドレス、宛先ポート番号を求めてみます。

```console: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への引数です。ここでは、ポート番号を指定します。

```console:サンプルスクリプト
[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します。

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

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

```console:ターミナル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")を送信する。

```console:ターミナル3（クライアント側）
[root@client ~]# nc 192.168.3.20 11111
aaaaa
```

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

```console:ターミナル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アドレスを求める方法

```console: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 }
```

```console:サンプルスクリプト
[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()));
}
```

```console:実行結果
スクリプトを実行する。
[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アドレスかどうかを判定します。

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


```console: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)](https://sourceware.org/systemtap/langref.pdf)にあります。

##16.1 その1
pingを実行したときに、jiffiesと送信元IP／送信先IPを表示するスクリプトです。

```console:サンプル
[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オプションを付ける。

```console:実行結果
[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](http://hagio.org/wiki/index.php/SystemTap)
[SystemTapの使い方（User-Space Probing)](https://qiita.com/hana_shin/items/6bca693206bf5f887cb3#_reference-973c7b7e843676186a10)

[SystemTapのお勉強](http://syuu1228.hatenablog.com/entry/20111109/1320839903)
[SystemTapExamples](https://sourceware.org/systemtap/examples/keyword-index.html)
[Components of a SystemTap script](https://sourceware.org/systemtap/langref/Components_SystemTap_script.html)
[Red Hat Enterprise Linux 7 SystemTap ビギナーズガイド](https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/pdf/SystemTap_Beginners_Guide/Red_Hat_Enterprise_Linux-7-SystemTap_Beginners_Guide-ja-JP.pdf)
[⁠Chapter 11. Networking Tapset](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/SystemTap_Tapset_Reference/networking.stp.html#API-tcp-recvmsg)
[DTrace SystemTap cheatsheet](http://myaut.github.io/dtrace-stap-book/dtrace-stap-cheatsheet.pdf)
[SYSTEM TAP リファレンス](https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/SystemTap_Tapset_Reference/index.html)
[Linux のイントロスペクションと SystemTap(IBM)](https://www.ibm.com/developerworks/jp/linux/library/l-systemtap/index.html)
[第14回「 SystemTapノススメ」](http://www.intellilink.co.jp/article/column/oss14.html)
[SystemTap How-to(日立)](https://www.linuxfoundation.jp/jp_uploads/seminar20061109/MHiramatsu.pdf)

[SystemTap](https://wikitech.wikimedia.org/wiki/SystemTap)
[SystemTap 3.0 SystemTap Beginners Guide](https://sourceware.org/systemtap/SystemTap_Beginners_Guide.pdf)
[SystemTap:Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems(IBM)](http://www.redbooks.ibm.com/redpapers/pdfs/redp4469.pdf)
[Dynamic Tracing with DTrace SystemTap](https://myaut.github.io/dtrace-stap-book/dtrace-stap-book.pdf)
[5.6.11 Pointer typecasting](https://sourceware.org/systemtap/langref/Language_elements.html)
[SystemTap Beginners Guide](https://sourceware.org/systemtap/SystemTap_Beginners_Guide/typecasting.html)
[SystemTap SystemTap](http://d3s.mff.cuni.cz/teaching/crash_dump_analysis/slides/11-systemtap.pdf)
[SystemTap Language Reference](https://sourceware.org/systemtap/langref.pdf)
[第4章 便利な SYSTEMTAP スクリプト](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/systemtap_beginners_guide/useful-systemtap-scripts#nettopsect)
[systemtap-cheat-sheet(github)](https://github.com/calio/systemtap-cheat-sheet)
[https://mmitou.hatenadiary.org/entry/20120721/1342879187](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")
  }
}
```