Edited at

Wireshark dissector開発の流れ

image.png

IIJ 2018 TECHアドベントカレンダー 12/7(金)の記事です】

Wiresharkがサポートするパケットはバージョン2.6.0で2080種類となっており(View → Internals → Supported Protocols より)、他の追随を許さない数となっています。(ちなみにtcpdump 4.9.2の tcpdump/print-*.c の数は145となっており、サポートするプロトコルの数もこの程度)

これだけプロトコルが多いとバグも多くなります。wireshark/test/captures/ を見るとテストされているプロトコルはそれほど多くはなく、git log -i --grep regress epan/dissectors/ を見ると、テストされていないプロトコルでよくregressionしていることが分かります。

L2TPv3はL2のフレームをカプセル化できるプロトコルで、ethernetHDLCなどの様々なL2のプロトコルをサポートしていますが、WiresharkではL2TPv3でカプセル化されたフレームは必ずCisco HDLCとしてデコードされてしまいます。これは「IIJ Engineers Blog - NGN VPN(IPoE)のフラグメントについて調べてみた」でも確認できます。L2TPv3解析機 (dissector) の開発者はCiscoの機器を用いてテストしたようです。

そこで、L2TPv3の下のフレームをethernetとしてデコードするように修正してみました。一応修正はできたものの、情報収集に苦労し、コーディング量に対して多くの時間がかかってしまいました。本記事ではこの修正を通して得られた情報をまとめ、dissector開発の敷居を少しでも下げられたら、と思います。

// 既存のdissectorを修正するのではなく新規に開発する場合、Luaによる拡張として実装した方が楽で、情報量も多いです。"wireshark lua" でググって下さい。


ドキュメント

ソースを読む前に見てもよく分からないと思うので、ソースを読んでいて躓いたら参照すると良いと思います。


ソースコードのダウンロードとビルド


ビルドの準備 (linux)

Ubuntu 18.04の場合

sudo apt install     \

bison \
build-essential \
cmake \
flex \
git \
libgcrypt20-dev \
libpcap-dev \
libqt5svg5-dev \
qtbase5-dev \
qtmultimedia5-dev \
qttools5-dev \
wireshark-dev \


ビルドの準備 (macOS)

brew install wireshark でビルドに必要なツール・ライブラリが全て入ります。


Wiresharkのダウンロードとビルド

git clone https://code.wireshark.org/review/wireshark

cd wireshark/
mkdir build/
cd build/
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$HOME/usr/opt/wireshark -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON ../
make -j3

run/wireshark で起動してみます。右上に "Development Build" と表示されているはずです。

image.png


L2TPv3 dissectorの読解・修正

L2TPv3 dissectorのソースは epan/dissectors/packet-l2tp.c です。GitHubで tl2tp とタイプすればすぐに見つかります。

image.png


対象プロトコルのpcapの取得

dissectしたいパケットをWiresharkでキャプチャして、File → Save で保存しておきます。今回はPacketLife.netの ICMP_over_L2TPv3_Pseudowire.pcap.cap を使うことにします。


人力dissect

RFCを読みながらパケットを人力で解析する、ということをします。これをやっておくとソースが格段に読みやすくなるためです。

epan/dissectors/*.c の冒頭には準拠する仕様が列挙されていることが多いです。packet-l2tp.c の場合、

/*

* RFC 3931 for L2TPv3
* http://tools.ietf.org/html/rfc3931
*/

よりRFC 3931に準拠していることが分かります。今回必要となる仕様を抜き出してみます:


3.2.2. L2TP Data Message
...
Figure 3.2.2: L2TP Data Message Header

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| L2TP Session Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| L2-Specific Sublayer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Tunnel Payload ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
...
to setup the session. Section 4.1 defines two session headers, one
...

4.1. L2TP Over Specific Packet-Switched Networks (PSNs)
...
Session ID

A 32-bit field containing a non-zero identifier for a session.
...

4.1.1.1. L2TPv3 Session Header Over IP
...
Figure 4.1.1.1: L2TPv3 Session Header Over IP

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cookie (optional, maximum 64 bits)...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The Session ID and Cookie fields are as defined in Section 4.1. The
...

これと、ICMP_over_L2TPv3_Pseudowire.pcap.cap をWiresharkで開いた結果:

image.png

を見比べながら、人力dissectしてみます。

    0                   1                   2                   3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session ID | L2TPv3
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1 1 0 1 0 1 0 0 0 1 0|
|^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^|
| 0x00 0x00 0x96 0x52|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ----------
| Ethernet dst | Ethernet
| 0xca 0x02 0x10 0x78|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | Ethernet src |
| 0x00 0x38| 0xca 0x03|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x10 0x78 0x00 0x1c|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ----------
| ... | IPv4

今回はシンプルなパケットでしたが、より複雑な構造になるほど人力dissectの恩恵が感じられると思います。


dissectorの読解

run/wireshark を立ち上げて、ICMP_over_L2TPv3_Pseudowire.pcap.cap を開き、デバッガでattachします。デバッグツールとして、本記事ではCLionを使いました。packet-l2tp.c 内の dissect_*() にブレークポイントを設置しておきます。L2TPv3が含まれるパケット#16をクリックした瞬間、packet-l2tp.c のL2TPv3 dissectorが走り、dissect_l2tp_ip() でブレークしました。

image.png

ブレーク中はWiresharkが固まるので、もう1つWiresharkを立ち上げて動作確認用に使うと良いです。

ステップ実行して、tvb_get_*() の実行に着目します。

image.png

パケットの中身は (tvbuff_t *tvb)->real_data (testy, virtual(-izable) buffer) に格納されていて、図のように tvb_get_*(tvb, offset) で取得できます。「人力dissect」と見比べると、L2TPv3 Session IDは38482となっていることが分かります。このように tvb の処理が理解できると、大まかなコードの意図も理解できるようになるはずです。

他に重要な用語に以下のようなものがあります。

Ethereal

Wiresharkの旧名。

epan

Enhanced Packet ANalyzer(これは後付けで、元々はEthereal Packet ANalyzerだった)。6.2. Overviewの図にあるように、Wiresharkの機能のうちパケット解析を担当する部分。

tree

image.png

Packet Details Paneで使われるデータ構造。ett_* という変数と共に登場するが、ettはEthereal Tree Typeのアクロニム

conversation

8.5. Conversations参照。


修正

コードをなんとなく理解したところで修正してみます。

diff --git a/epan/dissectors/packet-l2tp.c b/epan/dissectors/packet-l2tp.c

index 14881a7e03..eff682564a 100644
--- a/epan/dissectors/packet-l2tp.c
+++ b/epan/dissectors/packet-l2tp.c
@@ -306,7 +306,7 @@ static const enum_val_t l2tpv3_cookies[] = {
};

#define L2TPv3_COOKIE_DEFAULT 0
-#define L2TPv3_PROTOCOL_DEFAULT L2TPv3_PROTOCOL_CHDLC
+#define L2TPv3_PROTOCOL_DEFAULT L2TPv3_PROTOCOL_ETH

#define L2TPv3_L2_SPECIFIC_NONE 0
#define L2TPv3_L2_SPECIFIC_DEFAULT 1
@@ -957,6 +957,7 @@ static const true_false_string tfs_new_existing = { "New", "Existing" };

static dissector_handle_t ppp_hdlc_handle;
static dissector_handle_t ppp_lcp_options_handle;
+static dissector_handle_t ethertype_handle;

static dissector_handle_t atm_oam_handle;
static dissector_handle_t llc_handle;
@@ -3113,7 +3114,7 @@ dissect_l2tp_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data
/* If we have data, signified by having a length bit, dissect it */
if (tvb_offset_exists(tvb, idx)) {
next_tvb = tvb_new_subset_remaining(tvb, idx);
- call_dissector(ppp_hdlc_handle, next_tvb, pinfo, tree);
+ call_dissector(ethertype_handle, next_tvb, pinfo, tree);
}
return tvb_reported_length(tvb);
}
@@ -3791,6 +3792,11 @@ proto_reg_handoff_l2tp(void)
ppp_hdlc_handle = find_dissector_add_dependency("ppp_hdlc", proto_l2tp);
ppp_lcp_options_handle = find_dissector_add_dependency("ppp_lcp_options", proto_l2tp);

+ /*
+ * Get a handle for the Ethernet-framing dissector.
+ */
+ ethertype_handle = find_dissector_add_dependency("ethertype", proto_l2tp);
+
/* Register vendor AVP dissector(s)*/
dissector_add_uint("l2tp.vendor_avp", VENDOR_CABLELABS, create_dissector_handle(dissect_l2tp_vnd_cablelabs_avps, proto_l2tp));

ビルドし直してWiresharkを起動すると、無事冒頭の画像のようにethernetとしてdecodeしてくれました。ただし、この修正だとCisco HDLCであってもethernetとしてデコードされてしまいます。プロトコルを自動判別するか、選べるようにきちんと修正する予定です。


おわりに

dissectorの修正を行うのに必要な情報源や手順を紹介してみました。開発の敷居を下げることに貢献になれば幸いです。

Wiresharkのコード管理にはGerritが使われていて、Gerritに関しても情報が少ないです。近いうち本家へのコミットを行い、その手順も紹介したいと思います。


ライセンス

クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。

関連するソフトウェアの公式ドキュメントのメンテナの方へ:この記事の内容を公式ドキュメントに記載して頂ける場合、この記事にコメントして頂くか、twitter @wata_ash にご連絡下さい。