「高度なトラフィック制御」の版間の差分
(相違点なし)
|
2024年4月15日 (月) 22:25時点における最新版
Linux カーネルのネットワークスタックにはネットワークのトラフィック制御・トラフィックシェーピング機能が備わっています。iproute2 パッケージでインストールされる tc
コマンドを使うことでコマンドラインで制御できます。
この記事ではキューイング規則によってトラフィックをシェーピングする方法を説明します。例えば、ユーザーが帯域を占有しないように、管理しているネットワークのダウンロードや torrent を制限したい場合、キューイング規則を使ってトラフィック自体は許可しつつもネットワーク全体に負担をかけるような操作を制限できます。
上級者向けの記事としてネットワークデバイスや iptables の知識が必要です。
目次
前提条件
ネットワークアダプターで TCP segmentation offload を無効にすることが重要です。無効にしないと、CPU を節約するためにトラフィックシェーパーがバイパスされます。[1]
# ethtool -K eth0 tso off
キューイング
キューイングはデータの送信を制御します。データの受信は受動的なものでありネットワークで制御できる部分は限られます。ただし、TCP/IP パケットは遅延実行を使って送信されるため、パケットが転送される前にルーターに到達した時点でパケットを破棄することで LAN で受信されるトラフィックの量を制限することは可能です。ただし、キューイングロジックには直接には関係ありません。
トラフィックシェーピングを完全に制御するには、チェインの一番遅いリンクが必要です。接続の最大ダウンロード速度が 500k の場合、出力を 450k 以下に制限しないとモデムがトラフィックシェーピングを代わりに行ってしまいます。
各ネットワークデバイスには qdisc が設定できるルート (root) が存在します。デフォルトではルートには fq_codel qdisc が使われます (下を参照)。
キューイング規則にはクラスフルとクラスレスの2種類あります。
クラスフル qdisc はクラスを作成することができ、ツリーのブランチとして機能します。各クラスに対してルールを設定したりパケットをフィルタリングすることができます。各クラスには他のクラスフルあるいはクラスレス qdisc を設定することも可能です。
クラスレス qdisc では他の qdisc を追加することはできません。
qdisc の設定を始める前に、既存の qdisc をルートから削除する必要があります。以下のコマンドは eth0 デバイスから qdisc を削除します:
# tc qdisc del root dev eth0
クラスレス Qdisc
パケットの順序を変えたり遅延・破棄することでトラフィックを管理するキューです。クラスを作成することはできません。
fifo_fast
systemd 217 まではデフォルトの qdisc でした。qdisc のカスタム設定がないネットワークでは、fifo_fast がルートに設定される qdisc です。fifo は First In First Out を意味しており、最初にキューに入ったパケットが最初に送信されます。つまり特別な扱いは何もしません。
Token Bucket Filter (TBF)
特定のレート制限に達するまでパケットの送信を許可します。
仮想バケットを作成してバケットが満杯になったときに特定の速度でトークンを破棄することで機能します。各パッケージはバケットから仮想トークンを取得して、トークンを使って送信許可を得ます。大量のパケットが来た場合、バケットはトークンを発行することができず新しいトークンができるまで待機することになります。トークンが十分高速に到達しない場合、パケットが破棄されます。逆の場合 (送信されるパケットが少なすぎる場合)、トークンを使うことで一時的に通信速度を爆発させることができます。
上記より、インターフェイスを遅くしたい場合に役立つ qdisc です。例:
アップロードによってモデムのキューが満杯になってしまうことがあるため、巨大なファイルをアップロードしようとすると通信が破壊されます。
# tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
上記のアップロード速度はチェインの一番遅いリンクとなるように数パーセント減じた値に変更してください。ppp0
デバイスに TBF を設定して、アップロード速度が 220k に制限されパッケージが破棄されるまでの遅延が 50ms に、バーストが 1540 に設定されます。モデムの代わりに Linux マシンのキューイングを使うことで動作します。
Stochastic Fairness Queueing (SFQ)
ラウンドロビンの qdisc です。会話は fifo キューにセットされ、各ラウンドで、会話はデータを送信できる可能性があります。これが "Fairness" と呼ばれる所以です。"Stochastic" とも呼ばれる理由は、実際には会話に対してキューを作成するのではなくハッシュアルゴリズムを使っているためです。ハッシュは同一のバケットで複数のセッションが作成される可能性があります。この問題を解決するために、SFQ はハッシュアルゴリズムを頻繁に変更して問題が発現しにくいようにしています。例:
以下のコマンドは eth0 デバイスのルートに SFQ を設定して、10秒ごとにハッシュアルゴリズムを変更します:
# tc qdisc add dev eth0 root sfq perturb 10
CoDel と Fair Queueing CoDel
systemd 217 から fq_codel がデフォルトとなっています。CoDel (Controlled Delay) は遅くて邪魔な悪いクエリとすぐに空になる良いクエリを区別することで飽和状態にあるネットワークリンクにおけるバッファブロートや遅延を抑えます。Fair Queueing Codel は公平なキューを利用して Codel のフローに帯域を分配します。動的なネットワークでも使えるようにアルゴリズムが設計されているため、設定できるオプションは意図的に少なくなっています。稀なケースとして、バッファブロート wiki で議論されているような大規模なスイッチとメガビット未満の接続での問題などがあります。
詳しい情報は man tc-codel
や man tc-fq_codel
を読んでください。
クラスフル Qdisc
扱いを変える必要がある様々な種類のトラフィックが混在している場合はクラスフル qdisc が非常に有用です。クラスフル qdisc ではブランチを作成することができます。ブランチはクラスと呼ばれます。
クラスフル qdisc を設定するには各クラスに名前を付ける必要があります。クラスに名前を付けるときは classid
パラメータを使います。parent
パラメータはクラスの親を指定します。
名前は全て x:y
という形式で設定する必要があり、x
はルートの名前、y
はクラスの名前になります。通常、ルートは 1:
として子クラスは 1:10
などとなります。
Hierarchical Token Bucket (HTB)
帯域幅が固定されていて、様々な用途にあわせて使用できる帯域を保証して帯域幅を分割したい場合に HTB は有用です。設定例:
# This line sets a HTB qdisc on the root of eth0, and it specifies that the class 1:30 is used by default. It sets the name of the root as 1:, for future references. tc qdisc add dev eth0 root handle 1: htb default 30 # This creates a class called 1:1, which is direct descendant of root (the parent is 1:), this class gets assigned also an HTB qdisc, and then it sets a max rate of 6mbits, with a burst of 15k tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k # The previous class has this branches: # Class 1:10, which has a rate of 5mbit tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k # Class 1:20, which has a rate of 3mbit tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k # Class 1:30, which has a rate of 1kbit. This one is the default class. tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k # Martin Devera, author of HTB, then recommends SFQ for beneath these classes: tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10 tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10 tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
フィルター
クラスフル qdisc をルートに設定した場合、フィルターを使ってどのクラスによってどのパッケージを処理するのか指定する必要があります。
クラスレスしか使わない環境では、フィルターは不要です。
tc あるいは tc + iptables を使ってパケットをフィルタリングできます。
tc だけを使う
フィルターの例:
# This command adds a filter to the qdisc 1: of dev eth0, set the # priority of the filter to 1, matches packets with a # destination port 22, and make the class 1:10 process the # packets that match. tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dport 22 0xffff flowid 1:10 # This filter is attached to the qdisc 1: of dev eth0, has a # priority of 2, and matches the ip address 4.3.2.1 exactly, and # matches packets with a source port of 80, then makes class # 1:11 process the packets that match tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip src 4.3.2.1/32 match ip sport 80 0xffff flowid 1:11
tc + iptables を使う
iptables には fwmark と呼ばれる機能がありインターフェイスを跨いでパケットに印を付けることができます。
まず、以下のコマンドで 6 という番号が付けられたパケットが 1:30 クラスで処理されるようにします:
# tc filter add dev eth0 protocol ip parent 1: prio 1 handle 6 fw flowid 1:30
そして iptables を使って 6 という番号を付けます:
# iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
iptables を使ってパケットをマッチさせて fwmark で印を付けてください。
SNAT を使用した入力トラフィックシェーピングの例
受信トラフィックの Qdisc は、シェーピングなしでポリシングのみを提供します。受信トラフィックを形成するには、IFB (Intermediate Functional Block) デバイスを使用する必要があります。ただし、SNAT または MASQUERADE が使用されている場合は、すべての受信トラフィックが同じ宛先アドレスを持つため、別の問題が発生します。Qdisc は、逆 NAT 変換の前に外部インターフェイス上の受信トラフィックをインターセプトするため、ルーターの IP のみがパケットの宛先として認識されます。
次のソリューションは OpenWRT に実装されており、Arch Linux に適用できます。まず、発信パケットが MARK でマークされ、対応する接続 (および関連する接続) が CONNMARK でマークされます。受信パケットでは、入力 u32 フィルターがトラフィックを IFB にリダイレクトし (action mirred)、さらに CONNTRACK からパケットのマークを取得します (action connmark) これにより、NAT の背後にあるどの IP がトラフィックを開始したかに関する情報が提供されます。
この関数は、linux-3.19 以降はカーネルに統合され、4.1 以降は iproute2 に統合されています。
以下は、それを示すための、受信トラフィックで 2 つの HTB クラスのみを含む小さなスクリプトです。トラフィックのデフォルトはクラス 3:30 です。192.168.1.50 (NAT の背後) からインターネットへの送信トラフィックには "3" のマークが付けられるため、インターネットから 192.168.1.50 に向かう受信パケットも "3" のマークが付けられ、3:33 に分類されます。
#!/bin/sh -x # Maximum allowed downlink. Set to 90% of the achievable downlink in kbits/s DOWNLINK=1800 # Interface facing the Internet EXTDEV=enp0s3 # Load IFB, all other modules all loaded automatically modprobe ifb ip link set dev ifb0 down # Clear old queuing disciplines (qdisc) on the interfaces and the MANGLE table tc qdisc del dev $EXTDEV root 2> /dev/null > /dev/null tc qdisc del dev $EXTDEV ingress 2> /dev/null > /dev/null tc qdisc del dev ifb0 root 2> /dev/null > /dev/null tc qdisc del dev ifb0 ingress 2> /dev/null > /dev/null iptables -t mangle -F iptables -t mangle -X QOS # appending "stop" (without quotes) after the name of the script stops here. if [ "$1" = "stop" ] then echo "Shaping removed on $EXTDEV." exit fi ip link set dev ifb0 up # HTB classes on IFB with rate limiting tc qdisc add dev ifb0 root handle 3: htb default 30 tc class add dev ifb0 parent 3: classid 3:3 htb rate ${DOWNLINK}kbit tc class add dev ifb0 parent 3:3 classid 3:30 htb rate 400kbit ceil ${DOWNLINK}kbit tc class add dev ifb0 parent 3:3 classid 3:33 htb rate 1400kbit ceil ${DOWNLINK}kbit # Packets marked with "3" on IFB flow through class 3:33 tc filter add dev ifb0 parent 3:0 protocol ip handle 3 fw flowid 3:33 # Outgoing traffic from 192.168.1.50 is marked with "3" iptables -t mangle -N QOS iptables -t mangle -A FORWARD -o $EXTDEV -j QOS iptables -t mangle -A OUTPUT -o $EXTDEV -j QOS iptables -t mangle -A QOS -j CONNMARK --restore-mark iptables -t mangle -A QOS -s 192.168.1.50 -m mark --mark 0 -j MARK --set-mark 3 iptables -t mangle -A QOS -j CONNMARK --save-mark # Forward all ingress traffic on internet interface to the IFB device tc qdisc add dev $EXTDEV ingress handle ffff: tc filter add dev $EXTDEV parent ffff: protocol ip \ u32 match u32 0 0 \ action connmark \ action mirred egress redirect dev ifb0 \ flowid ffff:1 exit 0