pktgenとパケットキャプチャとLinux kernel 4.4
Posted on 2017/01/05(Thu) 03:05 in technical
はじめに
Linuxにパケットジェネレータ機能を提供するpktgenは、非常に高速にパケット送信を行うことができるカーネルモジュールである。
pktgen自体については PCとpktgenで行くショートパケットワイヤレートの旅 を参照。
pktgenで送信中のパケットがtcpdumpで確認できないという話があったため、その説明のため簡単にコールスタックを追った結果を残しておく。
賢明な諸氏のことなので、図を見れば説明は不要なことと思う。
Figure 1. Linux kernel 4.4におけるパケット送信時のコールスタック(一部省略)
参照すべきドキュメントと部位
今回はUbuntu 16.04が手元にあったので、それを参考に話を進める。
最初に参考にすべきドキュメントは、おそらくLinux foundationのnetworkingだと思われる。
ネットワークの重要な関数についての概要が説明されていて、おそらく読むべきは dev_hard_start_xmit() , dev_queue_xmit_nit の記述じゃないかと思う。
とはいえ、上記の説明で使用されている関数と、Linux kernel 4.4は少し状況が違う気がするので、何かを探すときのフックポイントくらいに思っておいて良いんじゃないかと思う。
とりあえずpktgenのソースコードのうち、送信に関する部分はこの辺りだ。
pktgenの送信関数 pktgen_xmit()
3450 xmit_more: 3451 ret = netdev_start_xmit(pkt_dev->skb, odev, txq, --burst > 0);
のように netdev_start_xmit が呼ばれている。
tcpdumpがパケットのコピー対象としているのが netdev_start_xmit の前なのか後なのかが分かれば、疑問に答えることができるだろう。
static int __dev_queue_xmit()
struct sk_buff *dev_hard_start_xmit()
static int xmit_one() __ここ分岐点__
void dev_queue_xmit_nit()
最終的に dev_queue_xmit_nit が呼ばれるポイントに到達することで、パケットのコピーが行われる。
static int xmit_one() を見ると dev_queue_xmit_nit() が呼ばれた後に netdev_start_xmit() が呼ばれていることが分かる。
2702 static int xmit_one(struct sk_buff *skb, struct net_device *dev, 2703 struct netdev_queue *txq, bool more) 2704 { 2705 unsigned int len; 2706 int rc; 2707 2708 if (!list_empty(&ptype_all) || !list_empty(&dev->ptype_all)) 2709 dev_queue_xmit_nit(skb, dev); 2710 2711 len = skb->len; 2712 trace_net_dev_start_xmit(skb, dev); 2713 rc = netdev_start_xmit(skb, dev, txq, more); 2714 trace_net_dev_xmit(skb, rc, dev, len); 2715 2716 return rc; 2717 }
従って pktgen_xmit() が呼び出している netdev_start_xmit() は、パケットキャプチャの対象となる処理をバイパスしていると考えられる。
その辺りを図示したのが、冒頭の図になる。
まとめ
pktgenで送信中のパケットはtcpdumpで確認することはできません。 (一部例外除く)
当然、tcpdumpに限らず、同様のパケットキャプチャポイントを持つwireshark, tshark(即ち、htons(ETH_P_ALL)を指定した socket(AF_PACKET, int socket_type, int protocol); を用いるもの全て)は、同様にpktgenの送信パケットを観測することはできません。
現場からは以上です。
おまけ
当初、pktgenには送信モードは存在しなかったのだが、途中から M_NETIF_RECEIVE(4.2以降) や M_QUEUE_XMIT(4.8以降) が導入された。
これを使用した場合は netdev_start_xmit() を直接呼ぶ以外の処理経路が生まれるため、必ずしもパケットキャプチャできないわけではなくなる。
恐らく M_QUEUE_XMIT 辺りを使えば dev_queue_xmit() が呼ばれるので dev_queue_xmit_nit() を通過できるはずだ。
また、今回は Linux kernel 4.4 をベースに調査しているが、Ubuntu 14.04で使用される Linux kernel 3.16 の場合は odev->netdev_ops->ndo_start_xmit が本体となってしまうため、使用しているドライバごとの送信処理にいきなり飛んでしまう。
興味のある人は(もちろん何でも良いんだけど) e1000 ドライバなどを読むと良いんじゃないだろうか。この辺から追えると思う。