Ubuntu Weekly Recipe

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

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

第584回ではカーネルデバッグツールのひとつSystemTapを紹介しました。今回はより実践的な例として,Ethernetフレームがドロップした際のMACアドレスを出力してみましょう。

droppedになったフレームのMACアドレスを調べる

Linuxは予期しないEthernetフレームを受け取ったとき,その内容によっては意図的に破棄し,ネットワークインターフェースのdroppedカウンターをインクリメントします。通常のネットワーク環境だとほぼ0なのですが,特定の機器や構成によっては恒常的にdroppedカウンターが増えることもあります。

そこでこのdroppedカウンターが上昇した理由を調べるために,droppedカウンター増加時のEthernetフレームの送信元・送信先のMACアドレスを,今回はSystemTapを用いて確認してみましょう。

予期しないEthernetフレームを送る仕組みを作る

まずはテスト用に予期しないEthernetフレームを送る仕組みを作ります。今回はテスト用なのでloインターフェースを対象にします。

droppedカウンターはip -s link showコマンドで確認できます。たとえばローカルのループバックインターフェースは次のとおりです。

$ ip -s link show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    RX: bytes  packets  errors  dropped overrun mcast
    25919416   262042   0       0       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    25919416   262042   0       0       0       0

上記の「dorpped」の部分ですね。今回の例では受信フレームのみを扱いますので,⁠RX:」で始まっているほうを確認してください。現時点では0です。

「予期しないEthernetフレーム」にはいろいろありますが,代表的なのはEtherTypeフィールド「サポートしていないEtherType」だった場合です。つまりカーネル側が届いたフレームのEtherTypeをどう扱うか不明な場合にdroppedとなります。IPv6のフレームならEtherTypeには0x86DDが入ります。このEtherTypeが使用しているカーネルでサポートしていない値(たとえば0x4321など)になっているフレームを受信するとdroppedが増えるというわけです※1)⁠

※1
たとえばBuffaloのスイッチはループ防止機能として2秒に1回検出用フレームをブロードキャストする機能が存在します。そのフレームには,IEEEにはRealtek用として割り当てられているRRCP(Realtek Remote Control Protocol)用の「0x8899」なEtherTypeをなぜか設定するようです。結果としてRRCPに対応していないカーネルで,ユーザーランド側でもEtherType=0x8899を見るソフトウェアが存在しない場合,Baffaloのループ防止機能のついたスイッチを接続すると,ひたすら「RX dropped」カウンターが増えていくことになります。また,ユーザーランド側でSOCK_RAW/ETH_P_ALLsocket()を作成した場合,すべてのEtherTypeが一度ユーザーランドにあがる(=カーネル側から見るとサポートしていないEtherTypeが存在しない)ため,droppedなフレームとしては表示されません。

任意のバイト列のEthernetフレームを送信する方法はいろいろ存在しますが,今回は次のようなPythonスクリプト(errframe.py)を作成し,利用することにします。

#!/usr/bin/env python3

from socket import *

dst = b'\x00\x00\x00\x00\x00\x00'
src = b'\x00\x00\x5E\x00\x53\x00'
etype = b'\x43\x21'
payload = bytes(46)
interface = 'lo'

sock = socket(AF_PACKET, SOCK_RAW)
sock.bind((interface, 0))
sock.send(dst + src + etype + payload)

コード自体は特に難しいものではないと思います。送信元のMACアドレスsrcとしてRFC7042に記載のある文書用のMACアドレスを指定しているのは,特に大きな理由はありません。今回は送信元も送信先もローカルループバックなので,送信先MACアドレスであるdstも送信元インターフェースであるinterfaceもそれに合わせています。payloadには最小フレームサイズを満たすよう0で埋めたバイト列を作っているだけです。

後半はSOCK_RAWsocket()を作成して,インターフェースにbind()し,send()で生のEthernetフレームを送っています。送信元と送信先が一緒になっているため多少わかりにくいかもしれません。実際にdrooppedが起きるのは実デバイスに紐付いたインターフェースのはずなので,srcdstinterfaceはそれらに合わせて適宜変更してください。

実際にこのコードを実行してから,再度loの情報を表示してみましょう。

$ chmod +x errframe.py
$ sudo ./errframe.py
$ ip -s link show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    RX: bytes  packets  errors  dropped overrun mcast
    25959740   262444   0       1       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    25959740   262444   0       0       0       0

無事にdroppedが1増えました。

droppedカウンターが更新されたことを表示する

この受信用のdroppedカウンターはいくつかの箇所で更新されうるのですが,今回はそのあたりの話をすっとばして,具体的なコードの場所に移動します。

net/core/dev.cの__netif_receive_skb_core()では,いくつかの条件において関数内のdropラベルにジャンプし,ネットワークインターフェースのrx_droppedを更新します。

4523 | drop:
4524 |         if (!deliver_exact)
4525 |             atomic_long_inc(&skb->dev->rx_dropped);
4526 |         else
4527 |             atomic_long_inc(&skb->dev->rx_nohandler);
4528 |         kfree_skb(skb);
4529 |         /* Jamal, now you will not able to escape explaining
4530 |          * me how you were going to use this. :-)
4531 |          */
4532 |         ret = NET_RX_DROP;

atomic_long_inc(&skb->dev->rx_dropped)がまさにその場所です。コードを見るとわかるように,条件によってはrx_droppedではなくrx_nohandlerが上がることもあるようです。というわけでこのラベルにジャンプするかどうかを確認してみましょう。幸い,SystemTapには.label("FOO")というフィルタリングルールがあるため,簡単にプローブポイントを指定できます。

#!/usr/bin/env stap

probe kernel.function("__netif_receive_skb_core").label("drop")
{
  printf("pakcet dropped\n");
}

早速,このSTPスクリプトを実行しつつ,先ほどのPythonスクリプトを動かしてください。ちなみにこれ以降,STPスクリプトのファイル名は「drop.stp」であるとします。

$ sudo stap drop.stp
pakcet dropped

裏でerrframe.pyを実行するたびに,上記が表示されるはずです。Ctrl-Cを入力してstapコマンドを終了しておきましょう。

著者プロフィール

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

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