15.8. 究極のトラフィック調整器: 低遅延, 高速アップロード/ダウンロード

注意: このスクリプトは最近アップグレードされました。 以前では、ネットワークの Linux クライアントに対してしか機能していませんでした! ですからネットワークに Windows マシンや Mac があり、 それらでのダウンロードが他のマシンのアップロード時に 速くないことにお気づきでしたら、更新されたほうがよいでしょう。

私は聖杯を作ろうと試みたのです:

あらゆる場合に、対話的トラフィックの遅延は小さく保つ

つまり、ファイルのダウンロードやアップロードが、SSH (あるいは telnet) には影響しない、ということです。これは最も重要な点で、 たとえ 200ms の遅延でも、作業には耐えがたいほどの遅さとなります。

アップロード・ダウンロード時にも、 それなりの速度でネットサーフィンしたい

http は「バルク」トラフィックですが、 他のトラフィックによる減速をあまり酷くはさせたくありません。

アップロードがダウンロードを劣化させること (およびその逆) がないようにする

上りのトラフィックがダウンロードの速度を落としてしまうというのは、 よくみられる現象です。

実はこれらのすべては、ほんの少々バンド幅を犠牲にすれば可能です。 アップロード・ダウンロード・ssh がお互いを妨害し合うのは、 ケーブルモデムや DSL モデムのような手元のアクセスデバイスに、 大きなキューがあるためなのです。

次の節では、何が遅延の原因なのか、どうすればそれを修正できるかを、 より詳しく説明します。もしこの魔法の動作原理に関心がなければ、 次は飛ばして直接スクリプトに向かっても問題ありません。

15.8.1. なぜデフォルトでは動作しないのか

ISP は、自分達のベンチマークがダウンロードの速度 のみによってなされることを知っています。 バンド幅以外に、ダウンロードの速度はパケットロスによっても大きく影響されます。 パケットロスは TCP/IP の性能を大きく低下させるからです。 キューを大きくするとパケットロスを防ぐのに役立ち、 ダウンロードの速度を大きくできます。 よって ISP は大きなキューを使うように設定しています。

しかしこのような大きなキューは対話性には害となります。 キーストロークはまず上りのキューを通過しなければならず、 これはリモートホストに届く前に数秒 (!) も留まることがあるのです。 その後、返信パケットが戻り、スクリーンに表示される前には、 そのパケットも (ISP にある) 下りのキューを通過しなければならないのです。

この HOWTO は、 いろいろな点でキューを修正・処理しなければならないことを教えています。 しかし悲しいことに、すべてのキューにアクセスできるとは限りません。 ISP にあるキューはおそらくまったく触れません。 一方、上りのキューはおそらく手元のケーブルモデムか DSL デバイスの内部にあるでしょう。 設定できる場合もできない場合もあるでしょうが、たいていはダメだと思います。

ではどうしましょう? これらのキューは制御できないのですから、 殺してしまわなければなりません。キューは Linux ルータに移動するのです。 ありがたいことに、これは可能です。

アップロードの速度を制限する

アップロードの速度を、実際に利用できる値よりわずかに絞ることによって、 モデムのキューにはデータが溜まりません。これによってキューは Linux に移動します。

ダウンロードの速度を制限する

インターネットのデータ転送速度を制御することは不可能なので、 こちらはやや技巧的になります。非常に頻繁にくるパケットを破棄すれば、 TCP/IP は減速して望む速度になってくれます。 不必要にトラフィックを破棄することはしたくありませんから、 「バースト (burst)」の許可サイズの設定は、大きくしておくといいでしょう。

さて、これらを行うと、下りのキューを完全に (短いバーストを除いて) 無効にでき、 下りのキューの管理にも、Linux の持つ能力をすべて利用できるようになります。

あと行わなければならないことは、 対話的なトラフィックを確実に上りキューの先頭に押し出すことです。 またアップロードがダウンロードの害にならないように、 ACK パケットもキューの先頭に出します。 これは、両方向でバルクトラフィックを発生させたときに通常見られる、 激しいスローダウンの原因なのです。 下りトラフィックの ACK (応答) は 通常の上りトラフィックより優先させる必要があり、 さもないとこの処理は遅延させられてしまいます。

これらをすべて設定すれば、 オランダの xs4all における excellent ADSL 接続では、 次のような測定結果が得られます。

基本的な遅延:
round-trip min/avg/max = 14.4/17.1/21.7 ms

トラフィック調整器なし、ダウンロード中:
round-trip min/avg/max = 560.9/573.6/586.4 ms

トラフィック調整器なし、アップロード中:
round-trip min/avg/max = 2041.4/2332.1/2427.6 ms

トラフィック調整器あり、220kbit/s のアップロード中:
round-trip min/avg/max = 15.7/51.8/79.9 ms

トラフィック調整器あり、850kbit/s のダウンロード中:
round-trip min/avg/max = 20.4/46.9/74.0 ms
アップロード時のダウンロードは、最高速度の約 80% になります。 アップロードは約 90% になります。ただし遅延は 850 ms に跳ね上がり、 この原因はまだ調査中です。

このスクリプトから得られる効果は、実際の接続速度に大きく依存します。 最高速度でアップロードしていると、 キーストロークのパケットの前には必ず 1 パケットが存在します。 これが達成できる遅延の下限値です。MTU を上り速度で割れば計算できます。 通常はこの値よりは少々大きくなります。 より効果を上げたければ MTU を小さくしてください!

次に、このスクリプトの 2 つの版を示します。 ひとつは Devik の優れた HTB を使ったもので、 もうひとつは Linux カーネルに (HTB とは違い) 最初から入っている CBQ を使ったものです。 両方ともテスト済みで、うまく動作します。

15.8.2. 実際のスクリプト (CBQ)

すべてのカーネルで動作します。 CBQ qdisc の内部に 2 つの確率的不偏キューを置き、 複数のバルクストリームがお互いを完全に殺してしまわないようにしています。

下りトラフィックはトークンバケツフィルタを用いた tc フィルタで制限しています。

'tc class add .. classid 1:20' で始まる行に 'bounded' を追加すると、 このスクリプトを改善できるかも知れません。 MTU を小さくする場合は、allot と avpkt の数も減らすこと!

#!/bin/bash 

# 自宅のインターネット接続用の究極設定
#
#
# 次のパラメータを、実際のダウンロード・アップロード速度より
# 少々小さくしてください (キロバイト単位)
DOWNLINK=800
UPLINK=220
DEV=ppp0

# 既存の下り・上りの qdisc を削除。エラーは隠す。
tc qdisc del dev $DEV root    2> /dev/null > /dev/null
tc qdisc del dev $DEV ingress 2> /dev/null > /dev/null

###### 上り

# root CBQ をインストール

tc qdisc add dev $DEV root handle 1: cbq avpkt 1000 bandwidth 10mbit 

# あらゆる物を $UPLINK 速度の内部に収める - これは DSL モデムの
# 巨大なキューを無効にし、遅延が出ないようにします:
# メインクラス

tc class add dev $DEV parent 1: classid 1:1 cbq rate ${UPLINK}kbit \
allot 1500 prio 5 bounded isolated 

# 高優先度クラス 1:10

tc class add dev $DEV parent 1:1 classid 1:10 cbq rate ${UPLINK}kbit \
   allot 1600 prio 1 avpkt 1000

# バルクおよびデフォルトのクラス 1:20。ややトラフィックが少なく、
# 優先度が低くなります

tc class add dev $DEV parent 1:1 classid 1:20 cbq rate $[9*$UPLINK/10]kbit \
   allot 1600 prio 2 avpkt 1000

# 両者に確率的不偏キューをあてがいます
tc qdisc add dev $DEV parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10

# ここからフィルタ
# TOS が Minimum Delay (scp 以外の ssh) のものを 1:10 へ:
tc filter add dev $DEV parent 1:0 protocol ip prio 10 u32 \
      match ip tos 0x10 0xff  flowid 1:10

# ICMP (ip プロトコル 1) を対話的クラス 1:10 へ。
# 測定と相手への通知ができるようになります:
tc filter add dev $DEV parent 1:0 protocol ip prio 11 u32 \
	match ip protocol 1 0xff flowid 1:10

# アップロード時のダウンロードを高速化するために、ACK パケットを
# 対話的クラスへ:

tc filter add dev $DEV parent 1: protocol ip prio 12 u32 \
   match ip protocol 6 0xff \
   match u8 0x05 0x0f at 0 \
   match u16 0x0000 0xffc0 at 2 \
   match u8 0x10 0xff at 33 \
   flowid 1:10

# 残りは「非対話的」つまり「バルク」なので 1:20 へ

tc filter add dev $DEV parent 1: protocol ip prio 13 u32 \
   match ip dst 0.0.0.0/0 flowid 1:20

###### 下り
# ダウンロードを実際の速度よりも少々遅くして、ISP でのキューを
# 無効にする。調整してできるだけ大きくしてください。
# ISP は大きなサイズのダウンロードを高速化するために「巨大」な
# キューを持つことが多い
#
# 入口監視制限を追加:

tc qdisc add dev $DEV handle ffff: ingress

# 「すべて」(0.0.0.0/0) をこちらへフィルタし、速すぎるものは
# 破棄する:

tc filter add dev $DEV parent ffff: protocol ip prio 50 u32 match ip src \
   0.0.0.0/0 police rate ${DOWNLINK}kbit burst 10k drop flowid :1
このスクリプトを ppp の接続時に実行したければ、 /etc/ppp/ip-up.d にコピーしてください。

最後の 2 行でエラーになる場合は、 tc ツールを新しいバージョンにアップデートしてください!

15.8.3. 実際のスクリプト (HTB)

このスクリプトは素晴らしい HTB キューを用い、すべての目的を達成しています。 HTB の章を見てください。カーネルへのパッチ当て作業に十分見合います!
#!/bin/bash

# 自宅のインターネット接続用の究極設定
#
#
# 次のパラメータを、実際のダウンロード・アップロード速度より
# 少々小さくしてください (キロバイト単位)
DOWNLINK=800
UPLINK=220
DEV=ppp0

# 既存の下り・上りの qdisc を削除。エラーは隠す。
tc qdisc del dev $DEV root    2> /dev/null > /dev/null
tc qdisc del dev $DEV ingress 2> /dev/null > /dev/null

###### 上り

# root HTB をインストールし、デフォルトのトラフィックを 1:20 へ:

tc qdisc add dev $DEV root handle 1: htb default 20

# あらゆる物を $UPLINK 速度の内部に収める - これは DSL モデムの
# 巨大なキューを無効にし、遅延が出ないようにします:

tc class add dev $DEV parent 1: classid 1:1 htb rate ${UPLINK}kbit burst 6k

# 高優先度クラス 1:10

tc class add dev $DEV parent 1:1 classid 1:10 htb rate ${UPLINK}kbit \
   burst 6k prio 1

# バルクおよびデフォルトのクラス 1:20。ややトラフィックが少なく、
# 優先度が低くなります

tc class add dev $DEV parent 1:1 classid 1:20 htb rate $[9*$UPLINK/10]kbit \
   burst 6k prio 2

# 両者に確率的不偏キューをあてがいます
tc qdisc add dev $DEV parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10

# TOS が Minimum Delay (scp 以外の ssh) のものを 1:10 へ:
tc filter add dev $DEV parent 1:0 protocol ip prio 10 u32 \
      match ip tos 0x10 0xff  flowid 1:10

# ICMP (ip プロトコル 1) を対話的クラス 1:10 へ。
# 測定と相手への通知ができるようになります:
tc filter add dev $DEV parent 1:0 protocol ip prio 10 u32 \
	match ip protocol 1 0xff flowid 1:10

# アップロード時のダウンロードを高速化するために、ACK パケットを
# 対話的クラスへ:

tc filter add dev $DEV parent 1: protocol ip prio 10 u32 \
   match ip protocol 6 0xff \
   match u8 0x05 0x0f at 0 \
   match u16 0x0000 0xffc0 at 2 \
   match u8 0x10 0xff at 33 \
   flowid 1:10

# 残りは「非対話的」つまり「バルク」なので 1:20 へ


###### 下り
# ダウンロードを実際の速度よりも少々遅くして、ISP でのキューを
# 無効にする。調整してできるだけ大きくしてください。
# ISP は大きなサイズのダウンロードを高速化するために「巨大」な
# キューを持つことが多い
#
# 入口監視制限を追加:

tc qdisc add dev $DEV handle ffff: ingress

# 「すべて」(0.0.0.0/0) をこちらへフィルタし、速すぎるものは
# 破棄する:

tc filter add dev $DEV parent ffff: protocol ip prio 50 u32 match ip src \
   0.0.0.0/0 police rate ${DOWNLINK}kbit burst 10k drop flowid :1

このスクリプトを ppp の接続時に実行したければ、 /etc/ppp/ip-up.d にコピーしてください。

最後の 2 行でエラーになる場合は、 tc ツールを新しいバージョンにアップデートしてください!