Ubuntu Weekly Recipe

第585回 SystemTapを活用してネットワークの動作を確認する

この記事を読むのに必要な時間:およそ 5 分

rx_droppedの値を表示する

次にrx_droppedの内容を表示してみましょう。

SystemTapではいくつかのローカル変数にアクセスできます。たとえばdropラベル付近の変数であれば,次のように確認できます。

$ stap -L 'kernel.function("__netif_receive_skb_core").label("drop")'
kernel.function("__netif_receive_skb_core@/build/linux-hwVdeu/linux-4.15.0/net/core/dev.c:4523").label("drop")
  $skb:struct sk_buff* $pfmemalloc:bool $orig_dev:struct net_device* $ret:int $type:__be16

上記の結果だと,$skbstruct sk_buff*の変数にアクセスできるわけです。

さらに@cast()を使うことで,特定の変数を指定した構造体にキャストしてそのメンバーにアクセスしたり,その値を読むことが可能です。具体的に見ていきましょう。

#!/usr/bin/env stap

probe kernel.function("__netif_receive_skb_core").label("drop")
{
  net_device = @cast($skb, "struct sk_buff", "kernel<linux/skbuff.h>")->dev
  dropped = atomic_long_read(&@cast(net_device, "struct net_device", "kernel<linux/netdevice.h>")->rx_dropped)
  printf("dropped1 = %lu\n", dropped)
}

上記では$skblinux/skbuff.hstruct sk_buffであるという情報を与えた上で,そのdevメンバーにアクセスしています。さらにdevstruct net_deviceであり,そのrx_droppedメンバーの値をatomic_long_read()dropped変数に保存,表示しています。

実際に実行してみると次のような結果になります。

$ sudo stap drop.stp
dropped1 = 2

どうやら値が読めているようです。

ちなみにip -s link showの結果と比べてみると,dropped1の値は1つ少ないことがわかります。これは「dropラベル」rx_droppedカウンターの内容を見てしまっているためです。コードをよく見るとわかりますが,rx_droppedはdropラベルにジャンプしたあとに更新されるので,本来は更新されたあとにチェックする必要があります。

ラベルのある箇所と違って,コード上の任意の場所をプローブポイントにするのは少し調整が必要です。先ほどstap -Lコマンドを実行したときに,ラベルのある具体的なコードの箇所が表示されていました。今回はその表示を元にコードの行番号をkfree_skb(skb);が実行される4528行目「付近の」プローブポイントに設定してみましょう。STPスクリプトの末尾に,次のコードブロックを追加してください。

probe kernel.statement("__netif_receive_skb_core@/build/linux-hwVdeu/linux-4.15.0/net/core/dev.c:4532").nearest
{
  net_device = @cast($skb, "struct sk_buff", "kernel<linux/skbuff.h>")->dev
  dropped = atomic_long_read(&@cast(net_device, "struct net_device", "kernel<linux/netdevice.h>")->rx_dropped)
  printf("dropped2 = %lu\n", dropped)
}

statement("関数名@ファイル名:行番号").nearestを使うと,指定した行番号付近のもっとも近いところをプローブポイントに設定してくれます。必ずしも期待通りの場所になるとは限りませんが,うまくプローブポイントを指定できないときに役に立つでしょう。

$ sudo stap drop.stp
dropped1 = 3
dropped2 = 4

dropped2のほうは,きちんと「増えたあと」rx_droppedが表示されましたね。

関数を定義する

SystemTapではfunctionを指定することで自前の関数を定義します。さきほどの例ではrx_droppedの増加前後で同じようなコードをコピーしていたので,関数化してしまいましょう。

#!/usr/bin/env stap

function print_dropped(skb, label)
{
  net_device = @cast(skb, "struct sk_buff", "kernel<linux/skbuff.h>")->dev
  dropped = atomic_long_read(&@cast(net_device, "struct net_device", "kernel<linux/netdevice.h>")->rx_dropped)
  printf("%s = %lu\n", label, dropped)
}

probe kernel.function("__netif_receive_skb_core").label("drop")
{
  print_dropped($skb, "dropped1")
}
probe kernel.statement("__netif_receive_skb_core@/build/linux-hwVdeu/linux-4.15.0/net/core/dev.c:4528").nearest
{
  print_dropped($skb, "dropped2")
}

これだけです。説明するまでもないぐらいわかりやすいですね。

MACアドレスを表示する

最後にMACアドレスを表示してみましょう。

skbからMACアドレスを取り出すには,カーネルの中だとskb_mac_header(struct sk_buff *)でEthernetフレームのヘッダーを示すポインタを取り出します。そのメンバーであるh_desth_sourceが送信先・送信元のMACアドレスです。

実はSystemTapのスクリプトの中でC言語の関数を呼び出すことも可能です。先に完成形のスクリプトを提示しておきましょう。

#!/usr/bin/env stap
%{
#include <linux/skbuff.h>
#include <uapi/linux/if_ether.h>
%}

function get_ethhdr:string(skb:long)
%{
  struct sk_buff *skb;
  struct ethhdr *ehdr;
  skb = (struct sk_buff *)(long)STAP_ARG_skb;
  ehdr = (struct ethhdr *)skb_mac_header(skb);
  snprintf(STAP_RETVALUE, MAXSTRINGLEN, "  src=%pM\n  dst=%pM\n  proto=%#x\n",
           ehdr->h_source, ehdr->h_dest, ntohs(ehdr->h_proto));
  CATCH_DEREF_FAULT();
%}

function print_dropped(skb, label)
{
  net_device = @cast(skb, "struct sk_buff", "kernel<linux/skbuff.h>")->dev
  dropped = atomic_long_read(&@cast(net_device, "struct net_device", "kernel<linux/netdevice.h>")->rx_dropped)
  printf("%s = %lu\n", label, dropped)
}

probe kernel.function("__netif_receive_skb_core").label("drop")
{
  print_dropped($skb, "dropped1")
}
probe kernel.statement("__netif_receive_skb_core@/build/linux-hwVdeu/linux-4.15.0/net/core/dev.c:4528").nearest
{
  print_dropped($skb, "dropped2")
  printf("%s", get_ethhdr($skb))
}

最初のポイントはC言語の部分は%{...%}でかこうことです。またC言語部分については別途カーネルヘッダーが必要になりますので,明示的に必要なヘッダーファイルをインクルードしています。

get_ethhdr()では,skbのポインタを渡して,Ethernetフレームのヘッダーを示すポインターに変換し,文字列として送信元・送信先のMACアドレスとEtherTypeを保存します。STAP_ARG_fooはSTPスクリプト上の変数をC言語で解釈するためのマクロであり,STAP_RETVALUEは関数から値を返すためのマクロです。

カーネルのprintf関連のフォーマットには便利なフォーマット変換子(上記で言うとMACアドレス表記にする%pMなど)があるため,C言語側で文字列に変換しています。ただしMAXSTRINGLENは128バイトと小さめなので,もしいろいろ出力するなら別途調整したほうが良いでしょう。

あとは作成した文字列をそのまま出力しているだけです。実際の出力結果は次のようになります。

$ sudo stap -g drop.stp
dropped1 = 4
dropped2 = 5
  src=00:00:5e:00:53:00
  dst=00:00:00:00:00:00
  proto=0x4321

stapコマンドは-gオプションを付けることでguruモードで動作します。C言語を埋め込む場合はその内容によって-gが必要になるようです。

errframe.pyで設定した値に従って,MACアドレスやEtherTypeが設定されていることがわかります。

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。