【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のフレームをカプセル化できるプロトコルで、ethernetやHDLCなどの様々なL2のプロトコルをサポートしていますが、WiresharkではL2TPv3でカプセル化されたフレームは必ずCisco HDLCとしてデコードされてしまいます。これは「IIJ Engineers Blog - NGN VPN(IPoE)のフラグメントについて調べてみた」でも確認できます。L2TPv3解析機 (dissector) の開発者はCiscoの機器を用いてテストしたようです。
そこで、L2TPv3の下のフレームをethernetとしてデコードするように修正してみました。一応修正はできたものの、情報収集に苦労し、コーディング量に対して多くの時間がかかってしまいました。本記事ではこの修正を通して得られた情報をまとめ、dissector開発の敷居を少しでも下げられたら、と思います。
// 既存のdissectorを修正するのではなく新規に開発する場合、Luaによる拡張として実装した方が楽で、情報量も多いです。"wireshark lua" でググって下さい。
ドキュメント
ソースを読む前に見てもよく分からないと思うので、ソースを読んでいて躓いたら参照すると良いと思います。
- Get Involved ソースコードのダウンロード・パッチの送り方
-
Wireshark Developer’s Guide かなりしっかりまとまっていて一読の価値あり
- I. Wireshark Build Environment 開発の準備
-
II. Wireshark Development ソースコードの説明
- 6. How Wireshark Works 設計について
- 7. Introduction ソースコードの概要。短いのでざっと読んでみて下さい
- 9. Packet dissection dissectorの説明
- Wireshark wiki - Development 雑多な情報
- wireshark/README.md あまり情報は無い
- wireshark/README.{bsd,linux,macos,windows} 古い。これに従ってもビルドできない
- wireshark/doc/README.developer コーディング規約
- wireshark/doc/README.dissector dissectorの説明
ソースコードのダウンロードとビルド
ビルドの準備 (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" と表示されているはずです。
L2TPv3 dissectorの読解・修正
L2TPv3 dissectorのソースは epan/dissectors/packet-l2tp.c です。GitHubで t
→ l2tp
とタイプすればすぐに見つかります。
対象プロトコルの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で開いた結果:
を見比べながら、人力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()
でブレークしました。
ブレーク中はWiresharkが固まるので、もう1つWiresharkを立ち上げて動作確認用に使うと良いです。
ステップ実行して、tvb_get_*()
の実行に着目します。
パケットの中身は (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
Packet Details Paneで使われるデータ構造。ett_*
という変数と共に登場するが、ettはEthereal Tree Typeのアクロニム。
conversation
修正
コードをなんとなく理解したところで修正してみます。
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 にご連絡下さい。