pktgenとパケットキャプチャとLinux kernel 4.4

Posted on 2017/01/05(Thu) 03:05 in technical

はじめに

Linuxにパケットジェネレータ機能を提供するpktgenは、非常に高速にパケット送信を行うことができるカーネルモジュールである。

pktgen自体については PCとpktgenで行くショートパケットワイヤレートの旅 を参照。

pktgenで送信中のパケットがtcpdumpで確認できないという話があったため、その説明のため簡単にコールスタックを追った結果を残しておく。

賢明な諸氏のことなので、図を見れば説明は不要なことと思う。

0105a_pktgen_tcpdump_001.png

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 ドライバなどを読むと良いんじゃないだろうか。この辺から追えると思う。