ns3でパケットのトレース

ns3ではデータグラムやパケット、フレームは、すべてPacketオブジェクトになる。

各レイヤでそのPacketオブジェクトを受信した時や破棄した時などに、イベントを発生させるようになっていて、あらかじめ指定しておいたコールバック関数を呼び出してくれる。そこで、殆どのイベントはPacketオブジェクトのポインタをコールバック関数に渡してくれるので、各レイヤでPacketオブジェクトの中身を見ることが出来る。イベントがなくても簡単に追加できる。

そこでイベント発生時に、パケットに含まれる特定のヘッダの特定のフィールドの値で処理を分岐したいということが多々ある。このとき、どのレイヤでトレースするかによって、Packetオブジェクトのヘッダとペイロードが決まるので、目的のヘッダが見つかるまで順番にヘッダを取り除いて (パケットをコピーしてからRemoveHeaderメソッドを呼ぶ) 行けば良い。上位レイヤにパケットを渡すときも同様の処理が行われている。

WifiMacHeader header_mac;
pkt->RemoveHeader (header_mac);

Ipv4Header header_ip;
pkt->RemoveHeader (header_ip);

.
.
.

ここで面倒なのは、各プロトコルのヘッダクラスが、抽象クラスのHeaderクラスを継承して定義されていて基底クラスでインスタンス化できないので、派生クラス (Ipv4Headerなど) のオブジェクト参照を渡す必要があるというところ。そうするとトレースするときに、パケットに含まれているプロトコルの種類とその順番が既知である必要がある。多分そう。上位レイヤでは、どんなヘッダが付いているかある程度予測することができるので、この方法でもなんとかなる。これがリンクレイヤなどの下位レイヤになると、制御メッセージなどが氾濫するので、この方法では難しくなる。

そういうときはPacketMetadataを使う。PacketMetadataはパケットの中に含まれる複数のプロトコルのヘッダとペイロード、トレイラを連結リストで保持しているので、これをイテレートして目的のヘッダを見つける。たとえばIPv4ヘッダを見つけるには以下のようにする。

PacketMetadata::ItemIterator it = pkt->BeginItem ();
PacketMetadata::Item item;
while (it.HasNext ())
{
    item = it.Next ();
    if (item.type == PacketMetadata::Item::HEADER)
    {
        if (item.tid.GetName () == "ns3::Ipv4Header")
        {
            NS_LOG_INFO ("IPv4 header found.");
        }
     }
}

item.typeが、種別を表す列挙型 (enum {PAYLOAD, HEADER, TRAILER}) の変数になっている。

中身はシリアライズされているので、ヘッダのフィールドにアクセスするにはデシリアライズしてやればいい。

Ipv4Header header;
header.Deserialize (item.current);

Ipv4Address src = header.GetSource ();
Ipv4Address dst = header.GetDestination ();

というような感じで、ペイロードやトレイラの場合も、その必要があるかどうかは別として、同様にすればとりあえず判別出来る。このPacketMetadata経由でヘッダやトレイラの追加・削除、シリアライズ・デシリアライズすることもできるので、パケットを操作するときはこれを使うと良さそう。

参考
http://www.nsnam.org/doxygen/classns3_1_1_packet.html
http://www.nsnam.org/doxygen/classns3_1_1_packet_metadata.html
http://www.nsnam.org/docs/manual/html/tracing.html#using-the-config-subsystem-to-connect-to-trace-sources

広告を非表示にする