私は低レイテンシと可変帯域幅リンクを提供するトンネリングアプリケーションを開発しています。これはトラフィック優先順位付けが必要なシステムで機能します。しかし、 tun デバイスへのトラフィックは明らかにカーネルによってキューに入れられていますが、デバイスにどの qdisc を適用しても、デフォルトの pfifo_fast を含む追加の効果はありません。つまり、高い優先順位と見なされるトラフィックは、トンデバイスとは別に処理されません。通常の交通。
問題を示すために小さなテストアプリケーションを作成しました。これは2つのトンデバイスを作成し、2つのスレッドを持ち、各スレッドにはあるインターフェイスから別のインターフェイスにパケットを転送するループがあります。受信と送信の間で、各バイトは1usずつ遅延され、約8Mbpsの双方向リンクをシミュレートします。
void forward_traffic(int src_fd, int dest_fd) {
char buf[BUFSIZE];
ssize_t nbytes = 0;
while (nbytes >= 0) {
nbytes = read(src_fd, buf, sizeof(buf));
if (nbytes >= 0) {
usleep(nbytes);
nbytes = write(dest_fd, buf, nbytes);
}
}
perror("Read/write TUN device");
exit(EXIT_FAILURE);
}
各トンインターフェイスを独自のネームスペースに配置することで、iperf3を実行して約8Mbpsのスループットを実現できます。 ip linkによって報告されるデフォルトのtxqlenは500パケットであり、iperf3(-P 20)とpingの両方を実行すると、約670-770msのRTTが表示されます。これは約500 x 1500バイトのキューと同じです。実際に txqlen を変更すると、遅延時間もそれに比例して変化します。今まではそんなに良くなった。
デフォルトのpfifo_fast qdiscを使用して、正しいToSタグを持つpingが通常のキューをスキップし、ping -Q 0x10などの低いRTTを持つ必要があると思いましたが、そうではありません。他の ToS/DSCP 値も同じです。すべて約700msの同じRTTを持ちます。また、同じ結果でさまざまな異なるqdiscを試しました。たとえば、fq_codel は待ち時間に大きな影響を与えず、tc -s qdisc は qdisc が何であれ常に表示します。バックログはリンク輻輳の有無にかかわらずゼロです(ただし、輻輳時にパケット損失を示すip -sリンクが表示されます)。
私がここで何か根本的に誤解しているのでしょうか、それともqdiscを動作させるには何をすべきですか?
答え1
したがって、カーネルのソースコードを読んで掘り下げた後、tunドライバがネットワークスタックで使用されていることを知らないため、qdiscが機能しないようです。単に独自のローカルキュー(txqlenによってサイズ設定)にパケットを保存し、キューがいっぱいになると余分なパケットを破棄します。
以下は、スタックがパケットを送信しようとしたときに呼び出されるdrivers / net / tun.cのトランスポート関数の関連ビットです。
/* Net device start xmit */
static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct tun_struct *tun = netdev_priv(dev);
int txq = skb->queue_mapping;
struct tun_file *tfile;
int len = skb->len;
rcu_read_lock();
tfile = rcu_dereference(tun->tfiles[txq]);
....... Various unrelated things omitted .......
if (ptr_ring_produce(&tfile->tx_ring, skb))
goto drop;
/* Notify and wake up reader process */
if (tfile->flags & TUN_FASYNC)
kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
tfile->socket.sk->sk_data_ready(tfile->socket.sk);
rcu_read_unlock();
return NETDEV_TX_OK;
drop:
this_cpu_inc(tun->pcpu_stats->tx_dropped);
skb_tx_error(skb);
kfree_skb(skb);
rcu_read_unlock();
return NET_XMIT_DROP;
}
}
一般的なネットワークインタフェースドライバは、netif_stop_queue()およびnetif_wake_queue()関数を呼び出して、ネットワークスタック内のパケットフローを停止して開始する必要があります。トラフィックが停止すると、追加のキュールールに従ってパケットがキューに追加されるため、ユーザーはトラフィックをより柔軟に管理して優先順位を付けることができます。
何らかの理由でタブ/チューニングドライバはこれを行いません。それはおそらく、ほとんどのトンネルが追加のフロー制御なしで単にパケットをカプセル化して実際のネットワークインターフェイスに送信するからです。
私の結果を確認するために、上記の関数でフロー制御を停止して簡単なテストを試しました。
if (ptr_ring_produce(&tfile->tx_ring, skb)) {
netif_stop_queue(dev);
goto drop;
} else if (ptr_ring_full(&tfile->tx_ring)) {
netif_stop_queue(dev);
tun_debug(KERN_NOTICE, tun, "tun_net_xmit stop %lx\n", (size_t)skb);
}
そして、パケットがキューから削除された後、キューが空であるかどうかによってキューを停止/覚醒させるために、tun_ring_recvに同様の追加を行いました。
empty = __ptr_ring_empty(&tfile->tx_ring);
if (empty)
netif_wake_queue(tun->dev);
else
netif_stop_queue(tun->dev);
優れたシステムではなく、マルチキュートンネルでは機能しませんが、qdiscレポートバックログを表示するのに十分に機能し、さまざまなToSレベルでpfifo_fastを使用して接続すると、ping時間と損失率に顕著な違いがあります。違いが完全に読み込まれました。