# ADSL Bandwidth Management HOWTO

Dan
Singletary

    dvsing@sonicspike.net

芳賀
靖史
日本語訳

    yasufumi.haga@nifty.com

1.2
2002年 9月 26日
ds
新しいメーリングリストに link 追加。 警告のセクションに小さな問題点を追加。これは特に ADSL 用に考えた、 Linux 向けの新しいリリース予定の QoS 改善策に関するもの。

1.1
2002年 08月 26日
ds
数箇所修正（指摘してくれた多くの方々に感謝します）。 実装の部分に、警告の情報を追加。
1.0
2002年 8月 21日
ds
帯域制御の向上、理論的背景の強化、2.4 カーネル対応。
0.1
2001年 8月 6日
ds
初版

本文書は ADSL モデムや、それと似た帯域特性を持った他のデバイス （ケーブルモデム、ISDN、他）で、外向きのトラフィックをもっと 効果的に管理するには、Linux ルーターをどういう設定にすればいいのか について解説したものです。本文書で重点をおいているのは、 上りや下りの帯域が飽和状態でも、 対話的なトラフィックの遅延を低減することです。

## はじめに

インターネットに接続している ADSL モデム（あるいはケーブルモデム）を通過する、 外向きのトラフィックを管理する方法を提案すること、それが本文書の目的です。 問題は、上りのデータ転送に関して、ADSL ラインの多くがほぼ 128 kbps に制限されて いることです。さらに、ADSL モデム内のパケットキューが、この問題に輪をかけて います。パケットキューが飽和状態になると、それを全部掃き出すのに 2秒から 3 秒 かかることがあるのです。これは同時に、上りの帯域が飽和してしまうと、他のパケットが インターネットに出ていくのに要する時間が、最大で 3 秒にもなりうるということでも あります。これでは、telnet やマルチプレーヤゲームといったような、対話的な アプリケーションが台無しになってしまうでしょうね。

### 本文書の新しいバージョン

本文書の最新版は、ワールド・ワイド・ウェブにある、 URL: http://www.tldp.org で、いつでも閲覧できます。

また、 http://www.tldp.org にある、 LDP のホームページを含めた、Linux の様々な WWW サイトや FTP サイトにも アップロードしておきます。

### 電子メールによる討論用リスト

ADSL Bandwidth Management に関する質問や更新情報については、 http://jared.sonicspike.net/mailman/listinfo/adsl-qos にある、ADSL Bandwidth Management のメーリングリストに、 ぜひ参加して下さい。

### 免責条項

この部分の邦訳は参考です。正式なものは英文を読んで下さい。

Neither the author nor the distributors, or any other contributor of this HOWTO are in any way responsible for physical, financial, moral or any other type of damage incurred by following the suggestions in this text.

本文書の著者とその配布元、および本 HOWTO に対する他の貢献者のいずれも、 本文書の提案内容に従った結果生じた、物理的、金銭的、道義的被害、 およびその他のどのような種類の被害に対しても、方法の如何を問わず、 一切の責任は負いません。

### 著作権とライセンス

この部分の邦訳は参考です。正式なものは英文を読んで下さい。

This document is copyright 2002 by Dan Singletary, and is released under the terms of the GNU Free Documentation License, which is hereby incorporated by reference.

本文書の著作権 (c) 2002 は Dan Singletary が有しており、 GNU フリー文書利用許諾契約書 の条件下で公開します。 当該契約書はリンクを参照することにより、添付扱いとします。

### フィードバックと訂正

本文書について分からないことや意見があれば、 dvsing@sonicspike.net 宛てで、著者まで遠慮なく申し出て下さい。

## 予備知識

### 必要条件

本文書で概説している方法は、他の Linux 構成でも動作するはずですが、 次の構成以外は未確認のままです。

- Red Hat Linux 7.3

- QoS サポートをすべて有効にし（モジュールでも構いません）、 次のパッチを当てた、2.4.18-5 カーネル（当該パッチは、 やがては将来のカーネルに取り込まれるかもしれません）。

  - HTB キュー - http://luxik.cdi.cz/~devik/qos/htb/

    注意：Mandrake (8.1, 8.2) で出荷しているバージョン 2.4.18-3 以降のカーネルには、 HTB に関する当該パッチは既に当ててあるという連絡を受けました。

  - IMQ デバイス - http://luxik.cdi.cz/~patrick/imq/

- iptables v1.2.6a 以降（Red Hat 7.3 で配布しているバージョンの iptables には、 length モジュールがありません）。

注意：本文書の前バージョンでは、 既存の sch_prio キューに対するパッチを含めた、 帯域管理手法を載せていました。ですがその後、 このパッチはまったく不要だと分かりました。 そればかりか、この文書で説明している新しい方法だと、 もっと良い結果になります （とはいっても、この文書の執筆時点では、 カーネルパッチが 2 つ必要になって いますが。:) パッチ万歳）。

### 接続構成

説明が込み入らないように、 本文書でネットワークデバイスや設定について触れる際は、 すべて以下のネットワーク接続図に従います。

                         <-- 128Kbit/s      ---------------     <--10Mbit -->
      インターネット <--------------------> | ADSL モデム | <--------------------
                          1.5Mbit/s -->     ---------------                     |
                                                                                | eth0
                                                                                V
                                                                    ------------------
                                                                    |                |
                                                                    | Linux ルーター |
                                                                    |                |
                                                                    ------------------
                                                                     | .. | eth1..ethN
                                                                     |    |
                                                                     V    V

                                                                 ローカルネットワーク

### パケットキュー

パケットキューというのは、 データがすぐにはネットワークデバイスに向けて送れない場合に、 そのデータを保持しておくバケツです。たいていのパケットキューは、 特に他の方法を使うように設定されていない限り、 FIFO（ファースト・イン、ファースト・アウト）という方式を使っています。 これが何を意味するかというと、 あるデバイス用のパケットキューが完全に飽和している場合に、 一番新しくキューに置かれたパケットが送出されるのは、 その時点でキューにある他のすべてのパケットが送出されてからだということです。

#### 上りの伝送

ADSL モデムでは、帯域は下りが通常 1.5Mbit/s、 上りが通常 128Kbit/s というように、 非対称になっています。これは回線速度のことですが、 Linux ルーター PC と ADSL モデム間のインタフェースは、 普通 10Mbit/s かそれ以上になっています。 ローカルネットワーク側のインタフェースも 10Mbit/s だったら、 ローカルネットワークからインターネットへ送るパケットは、 普通ルーターでは「キューイングしません」。 パケットは、ローカルネットワークから受けとった時と同じ速度で、 eth0 から送出されます。でも、反対向きのパケットは ADSL モデムでキューに溜ります。 というのは、パケットは 10Mbit/s でやってきますが、 出ていくのは 128Kbit/s にすぎないからです。 やがては ADSL モデムのパケットキューは飽和し、 そこにやってくるどんなパケットも、 それ以上は知らない間に落ちてしまいます。 TCP はこの問題を処理するよう設計されており、 適宜転送ウィンドウサイズを調整して、 可能な帯域をフルに活用します。

TCP とパケットキューを組み合せると、 帯域がもっとも効果的に使えるようになる一方で、 副作用として FIFO キューにたまるデータ量が増えるので、 対話的なトラフィックの遅延が増大しないとも限りません。

FIFO に幾分似てはいますが別の種類のキューに、 n バンドの優先順位づけキューというのがあります。 パケットを並べるキューが一つしかないのではなく、 n バンドの優先順位づけキューには n 個の FIFO キューがあり、 パケットはその種類に従って、どれかにキューイングされます。 各々のキューには優先順位があり、 パケットは常にもっとも優先順位の高いキューから取り出します。 この方式を使うと、 FTP のパケットを telnet のパケットよりも優先順位の低いキューに置けるので、 FTP のアップロード中でも、 telnet のパケットはたった一つでもキューに割り込んで、 即座に送出されます。

本文書では、階層化トークンバケット(HTB) という、 Linux の新しいキューを使うように改訂しました。 HTB キューは先に述べた n バンドのキューとたいへん似ていますが、 各クラスで転送率を制限できるようになっています。 さらに、他のクラスの下位にトラフィックのクラスを設定し、 クラス階層が作れるようになっています。 しかし HTB を余すところなく解説するというのは、 本文書で扱うべき事ではありません。 詳細な情報は http://www.lartc.org を参照して下さい。

#### 下りの転送

ADSL モデムに対する内向きのトラフィックは、 外向きのトラフィックとほとんど同じ方法でキューに溜りますが、 そのキューは ISP 側にあります。 このため、パケットのキューイング方法とか、 どういった種類のトラフィックを優先的に扱うかといったことに関して、 直接制御することはおそらくできません。 遅延を低く抑える唯一の方法は、データをあまり高速で送らないようにすることです。 ですが、残念なことに、パケットの到着速度を直接制御する方法はありません。 でも、トラフィックの大多数はおそらく TCP ですから、 送信側の速度を抑える方法はいくつかあります。

- 意図的に内向きのパケットを落とす - TCP はリンクの輻輳を避ける一方で、 可能な帯域をフルに活用するよう設計されています。 つまり、バルクデータの転送中、TCP は最終的にパケットが落ちるまで、 次々とデータを送出します。すると、TCP はパケットが落ちたのを検出して、 転送ウィンドウを縮小します。転送が続いている間は、 ずっとこのサイクルを繰り返し、できる限りデータの高速転送を保証するのです。

- 通知された受信ウィンドウサイズを操作する - TCP の転送中、 受信側は確認応答 (ACK) パケットを返し続けます。 この ACK パケットには、通知されたウィンドウサイズが入っています。 これによって、未受信データの最大量を送信側に明示します。 ですから、外向きの ACK パケットのウィンドウサイズを操作すれば、 送信側を意図的にスピードダウンさせることができるのです。 現時点では、Linux でこの種のフロー制御を実装した（フリーな）ものはありません （でも筆者はこの機能の実装作業を続けると思います！）。

## 動作内容

上りの帯域を最適化するには、基本的な段階が二つあります。 ADSL モデムがキューを扱う方法を制御することはできないのですから、 まずは、パケットが ADSL モデムにキューイングされないようにする必要があります。 そのためには、ルーターが eth0 に送出するデータ量を、 ADSL モデムの上り方向の帯域全体よりも若干低めに抑えます。 この結果、許容されている送出速度よりも速い速度でローカルネットワークから到達するパケットを、 ルーターでキューイングする必要が出てきます。

二番目の段階は、ルーターに優先順位付きのキューイング方式を設定することです。 これについては、telnet やマルチプレーヤゲームといった、 対話的なトラフィックに優先順位をつけるように設定できるキューを調査します。

HTB キューを使えば、 どの優先順位のクラスも、 他のクラスと同じようにパケットがキューイングされることが保証できるし、 帯域を調整し、同時にキューイングに優先順位をつけることもできます。 この文書のバージョン 0.1 で述べた方法を使っても、 キューが空になるのは避けられません。

そして最後に、 fwmark を使ってパケットに優先順位を与えるように、 ファイアウォールを設定します。

### Linux HTB を使って、外向きのトラフィックを抑える

ルーター・モデム間は 10Mbit/s で接続されていますが、 モデムは 128Kbit/s でしか送出できません。 この速度以上に送出されるデータは、いずれもモデムでキューに溜ります。 したがって、ルーターから出た ping パケットはすぐモデムに届くかもしれません。 でもそのモデムのキューになんらかのパケットがあれば、 その ping パケットが実際にインターネットに出ていくには数秒かかるかもしれません。 あいにく大半の ADSL モデムには、 パケットの取り出し方法やキューの大きさを指定する仕組みがありません。 ですから、ここでの最初の目標は、キューをもっと管理できるような場所に移して、 そこに外向きのパケットをキューイングするようにすることです。

この場合は、HTB キューを使って、 ADSL モデムにパケットを送出する速度を制限します。 上りの帯域は 128Kbit/s だとしても、 パケットの送出速度はそれより若干低めに制限しなければなりません。 遅延を抑えたければ、 モデムのキューには、一個たりともパケットが溜らないようにする必要があります。 実験を通じて分かったことですが、 外向きのトラフィックを約 90kbit/s に制限すれば、 HTB の速度制御を使わなくても、 帯域の 95% が使えるようになります。 この速度で HTB を有効にすれば、 ADSL モデムがパケットをキューイングしないようになりました。

### HTB を使った優先順位付きキューイング

注意：本節で先に主張したこと （もともとは N バンドの優先順位付きキューイングという名称だったもの）は、 後に間違っていることが分かりました。 実際は、パケットを優先順位付きキューの個々のバンドに分類できるようにするには、 fwmark フィールドを使うしかなかったんです。 でも、本文書のバージョン 0.1 を作成している時点では、 説明が不充分でした。

しかしこの時点では、性能の変化はまだ分かりません。 FIFO キューを ADSL モデムからルーターに移しただけですから。 実際、キューサイズをディフォルトの 100 パケットに設定した Linux では、 この時点で問題がたぶん悪化しています。 でももう少しです。

HTB キューの各隣接クラスには優先順位を割り当てることができます。 異なる種類のトラフィックを別々のクラスに置き、 それからこのクラスに別々の優先順位を割り当てれば、 パケットを取り出して送出する順序が管理できます。 HTB キューを使うと、このようなことも可能になるし、 どのクラスも沈み込まずに済みます。 というのは、各クラスに対して、最低保証速度を明記できるからです。 この他に、HTB を使えば、 ある上限までなら他のクラスの未使用帯域を使ってもいいと、 クラスに指示できるようにもなります。

まずクラスを設定し、 次にクラスにトラフィックを配置するためのフィルターを設定します。 これにはいくつか方法がありますが、本文書で解説する方法は、 fwmark でパケットに印をつけるのに、 慣れ親しんだ iptables/ipchains を使用します。 このフィルターは fwmark に基づいて、 HTB キューのクラスにトラフィックを配置します。 こうすれば、iptables にマッチングルールを設定し、 種類に応じてトラフィックを一定のクラスに送ることができます。

### ibtables を使った外向きパケットの分類

もともと本文書ではパケットを分類するのに ipchains を使っていました。 ここではもっと新しい iptables を使います。

ルーターを設定して、 対話的なトラフィックに優先順位を付ける最後の段階は、 ファイアウォールを設定して、 どうやってトラフィックを分類すればいいのかを定義することです。 これには、パケットの fwmark フィールドを設定します。

あまり詳細に立ち入らず、 ここでは優先順位のもっとも高いクラスが 0x00 になっている四つのクラスに、 外向きのパケットがどう分類されるのかを簡単に説明します。

1. 全パケットに 0x03 という印をつけます。 これで、パケットはすべて、 ディフォルトで一番優先順位が低いキューに置かれます。

2. ICMP パケットに 0x00 という印をつけます。 ping には遅延を示して欲しいので、 このパケットの優先順位は一番高くなります。

3. 宛先のポートが 1024 以下になっているすべてのパケットに 0x01 という印をつけます。 これは Telnet や SSH といったシステムサービスに優先順位をつけているんです。 FTP の制御ポートもこの範囲に入りますが、 FTP のデータ転送は高位のポートで行なうので、 0x03 のバンドのままになります。

4. 宛先のポートが 25 (SMTP) になっているすべてのパケットに 0x03 という印をつけます。 誰かが大きな添付ファイルをつけて電子メールを出しても、 それで対話的なトラフィックを埋め尽くしてほしくはないですからね。

5. マルチプレーヤゲームサーバーに向かうパケットには、 すべて 0x02 という印をつけます。 これでゲーマーに対する遅延は低くなりますが、 同時にゲーマーのパケットが、低い遅延が必要なシステムアプリケーションを 埋め尽くさないようにもしています。

   他の「細かい」パケットには 0x02 という印をつけます。 内向きのダウンロードに対する外向きの ACK パケットはすぐに送出して、 効率的にダウンロードできるように保証したほうがいいですからね。 これは、iptables の length モジュールを使えば可能です。

もちろん、必要に応じてカスタマイズしてもかまいません。

### もう少しチューニング

遅延改善のためにできることがあと二つあります。 まず、最大伝送ユニット (mtu) がディフォルトの 1500 バイト未満になるように設定できます。 この値を小さくすれば、既にフルサイズで優先順位の低い送出パケットがある場合に、 優先順位の高いパケットの送出を待つ平均時間が短くなります。 でも、この値を小さくすると、スループットが若干低下することにもなります。 なぜなら、各パケットには IP ヘッダーと TCP ヘッダーの情報が、 最低でも 40 バイト分含まれているからです。

優先順位の低いトラフィックでも、 遅延改善のためにできることがもう一つあります。それは、 キューの長さをディフォルトの 100 よりも小さくすることです。 ADSL ラインでは、キューの長さがディフォルトのままだと、 mtu が 1500 バイトのパケットを送出してしまうのに、 10 秒もかかってしまうことがあります。

### 内向きのトラフィックを抑えてみる

Intermediate Queuing Device (IMQ) を使えば、 外向きのパケットをキューイングするのと同じ方法で、 内向きのパケットをすべてキューに入れるができます。 この場合、パケットの優先順位ははるかに単純です。 できる（やろうとしている）のは、 内向きの TCP のトラフィックを制御することだけですから、 TCP 以外のトラフィックは、すべて 0x00 クラスに配置し、 TCP トラフィックは、すべて 0x01 クラスに配置します。 他の「細かい」TCP パケットは、 おそらく送出済みの外向きデータに対する ACK パケットですから、 それらも 0x00 クラスに配置します。 0x00 クラスには標準的な FIFO キューを設定し、 0x01 クラスには Random Early Drop (RED) キューを設定します。 RED は TCP を制御する上で、 FIFO (tail-drop) キューよりも優れたキューです。 というのは、制御できなくなりそうな伝送をスピードダウンさせようとして、 キューがオーバーフローする前にパケットを落とすからです。 さらに、この 2 つのクラスの内向きの最大速度を制限します。 この速度は ADSL モデム上の実際の内向きの速度よりも低くします。

#### 内向きのトラフィックを制限しても、さほど効果がない理由

ISP 側でキューが飽和しないように、内向きのトラフィックを制限したいわけです。 でもこうすると、5 秒間分ものデータがバッファされることがあります。 問題は、内向きの TCP のトラフィックを制限する唯一の方法によって、 全く問題ないパケットまで落ちてしまうことです。 こういったパケットは既に ADSL モデム上の帯域をある程度占有していたものですが、 結果としてそれ以後のパケットをスピードダウンさせようとして、 Linux 機が落としてしまうのです。 これらの落ちたパケットは結局は再送され、 もっと帯域を消費してしまいます。 トラフィックを制限すると、 自分たちのネットワークで受け入れるパケット速度を制限することになります。 実際の内向きのデータ速度は、 パケットを落とした分だけ増加しますから、 実際は、確実に遅延を低くするには、ADSL モデムの実際の速度よりも、 下りの速度をずっと低く抑えなければならないんです。 実際には、 同時に五つのダウンロードをしながら遅延を許容範囲にしておくには、 ADSL の下り 1.5Mbit/s を 700kbit/s に落とす必要がありました。 TCP セッションが増えればもっとパケットが落ちるので、 帯域の無駄がさらに大きくなります。 だから速度の上限はもっと低く設定する必要があるでしょうね。

内向きの TCP のトラフィックを制御するもっと優れた方法は、 TCP ウィンドウサイズを操作することでしょうが、 本文書の作成時点では、 Linux 用にこれを実装した（フリーな）ものはありません （筆者は知りません）。

## 実装

では、説明はここまでにしておいて、 Linux で帯域管理を実装することにしましょう。

### 警告

DSL モデムへ送出するデータの、実際の速度を制限するのは、 思ったほど簡単ではありません。ほとんどの DSL モデムは実際はイーサネットブリッジにすぎず、 Linux 機 と ISP のゲートウェイとの間で、データを互いにブリッジしあっているのです。 ほとんどの DSL モデムは、データ送出のため、リンク層に ATM を使っています。 ATM は常に 53 バイト長のデータをセル単位で送出しています。 そのうち 5 バイトはヘッダー情報で、残りの 48 バイトはデータに利用できます。 送出するのが 1 バイトでも、ATM のセルは常に 53 バイト長なので、 全体で 53 バイトの帯域を消費してしまいます。 つまり、データが 0 バイト ＋ TCP ヘッダー が 20 バイト ＋ IP ヘッダーが 20 バイト ＋ イーサネットヘッダーが 18 バイト という典型的な TCP の ACK パケットを送出している場合にあたります。 現実には、送出しているイーサネットパケットが （TCP ヘッダーと IP ヘッダーしかない） 40 バイトの負荷しかない場合でも、 イーサネットパケットの最低負荷は 46 バイトのデータになり、残りの 6 バイトには ヌルが詰まります。 つまり、イーサネットパケットの実際の長さにヘッダー長を加えると、18 + 46 = 64 バイトになるということです。 ATM で 64 バイトを送出するためには、ATM セルを二つ送出し、106 バイトの帯域を消費しなければなりません。 これは、どの TCP ACK パケットについても、42 バイトの帯域を浪費しているという意味です。 DSL モデムが使用しているカプセル化に、Linux が責任を持つなら良いのですが、 Linux が責任を持っているのは、TCP ヘッダーと IP ヘッダー、それに 14 バイトの MACアドレスだけです （4 バイトの CRC はハードウェアレベルで処理するので、Linux ではこの 4 バイトは含めていません）。 Linux では、46 バイトという最小イーサネットパケットを勘定に入れていないし、 ATM の固定サイズセルも考慮していないのです。

こういったことから、 （使用している様々な種類のカプセル化を考慮できるパケットスケジューラが見つかるまでは） 外向きの帯域は、 実際の帯域容量よりも若干低めに制限しなければならないことがわかります。 お使いの帯域をうまく制限できる値を捜し当てたと思っても、 大きなファイルをダウンロードすると、遅延は 3 秒以上に跳ね上がり始めることがよくあります。 これはたいてい、小さな ACK パケットが消費する帯域を、Linux が計算違いしているからです。

この問題の解決策を、筆者は何カ月かの間ずっと検討してきましが、ほぼメドが立ちました。 さらにテストを行なうために、もうすぐ一般にリリースします。 この方法では、Linux の 速度制限パケットに対する QoS の代わりに、 ユーザー空間にあるキューを使います。 基本的には Linux のユーザー空間にあるキューを使って、 HTB キューを実装しました。 （これまでのところ）この方法を使うと、 外向きのトラフィックをかなりうまく調整できているので、 大量のバルクデータの（同時に数個の）ダウンロードや （gnutella を用いて、同時に数個の）アップロードの最中でも、 約 15ms という、トラフィックの無い状態のわずかな遅延を越えたのは、 400ms が最大でした。 この QoS の方法に関する詳細に、 メーリングリストに参加して更新情報を受け取るか、 あるいはこの HOWTO の更新状況をチェックして下さい。

### スクリプト：myshaper

次のリストは、筆者が自分の Linux ルーターで帯域管理をするのに使っているスクリプトです。 このスクリプトでは、本文書でカバーした考え方のいくつかを使っています。 外向きのトラフィックは、種類に応じて七つのキューのどれかに配置されます。 内向きのトラフィックは、速度が超過している場合に、 TCP パケットを最初に落とすようになっている（優先順位が一番低い）、 二つのキューに配置されます。 このスクリプトで指定してある速度は、 筆者の構成ではうまくいくようですが、 読者の皆さんの結果は違うかもしれません。

このスクリプトは、もともと LARTC website にある、 ADSL Wonder Shaper を基にしたものです。

    #!/bin/bash
    #

    # myshaper - DSL/ケーブル モデム用外向きトラフィックシェーパ ＆ 優先順位付け
    #            元は ADSL/Cable wondershaper (www.lartc.org)
    #

    # Dan Singletary 作 (8/7/02)
    #

    # 注意！！ - 本スクリプトは、以下のサイトから利用できる
    #            HTB キューと IMQ の適切なパッチがカーネルに
    #            あたっていることが前提です。
    #            （将来のカーネルでは、パッチは不要になるかもしれません）
    #
    #       http://luxik.cdi.cz/~devik/qos/htb/
    #       http://luxik.cdi.cz/~patrick/imq/
    #

    # myshaper 用設定オプション
    #  DEV    - DSL/ケーブル モデムと接続するデバイスを ethX に設定する
    #  RATEUP - DSL/ケーブル モデムの外向き帯域より、この値を若干低めに
    #           設定する。
    #           筆者のラインは 1500/128 で、RATEUP=90 にすると、
    #           上り 128kbps ではうまく動作します。
    #           でも皆さんのやり方は違うかもしれません。
    #  RATEDN - DSL/ケーブル モデムの内向きの帯域よりも、若干低めに
    #           設定します。
    #
    #

    #  imq を使って内向きのトラフィックをスリムにする理論
    #

    #    インターネット上の他のホストから送出されるデータの速度を、
    #  直接制限することは不可能です。内向きのトラフィックの速度を
    #  削るには、TCP の輻輳回避アルゴリズムに頼るしかありません。
    #  このため、「できそうなのは TCP コネクションで内向きのトラ
    #  フィックをスリムにしてみることだけです。」つまり tcp 以外の
    #  トラフィックはどれでも優先順位の高いクラスに配置するという
    #  ことです。というのは、tcp 以外のパケットを落としてし
    #  まうと、結果としてはたぶん再送が行なわれて、単に不要に帯域を消
    #  費してしまうだけとなってしまうからです。

    #    HTB キューがオーバーフローする際、tcp パケットを落として、
    #  内向きの TCP のトラフィックを削ってみます。このキューは、実際に
    #  内向き側のデバイスが出せる速度よりも若干低めの所定の速度 (RATEDN) で、
    #  パケットを通すだけです。この速度を越える TCP パケットを落と
    #  すことで、ISP 側のキューがオーバーフローしているせいで、この
    #  パケットが落ちているように、見せかけようとしています。
    #  このようにする利点は、ISP 側のキューが実際には満杯にはなって
    #  いないけれども、一杯になったように見せかければパケットが落ち、それに
    #  反応して TCP が転送速度を落とすので、ISP 側のキューがけっして飽和
    #  しないことです。

    #    優先順位に基づくキューイング方式を使うと、より優先順位が
    #  高いバケツ（クラス）に配置する、ある種のパケット（ssh、telnet、等々）
    #  は「落とさない」という選択が明確にできるという利点があります。
    #  なぜかといえば、パケットはどのクラスからも、最低限の速度で
    #  公平にデキューされるという条件になっており、常に優先順位がもっとも低い
    #  クラスから取り出されるからです。（このスクリプトでは、各
    #  バケツは最低でも、帯域の七分の一という公平な割り当てで配送
    #  します）。
    #

    #  重要な点を繰り返します。
    #   ＊接続中に tcp パケットを落とすと、輻輳回避アルゴリズムによって、
    #     受信側の速度が低下することになります。
    #   ＊TCP 以外のパケットを落としても、何も得るものはありません。事実
    #     そのパケットが重要なものだったら、どのみちおそらく再送されるで
    #     しょう。ですからこういったパケットはけっして落とさないようにす
    #     る必要があります。こうすれば、TCP コネクションが飽和しても
    #     TCP のように再送の仕組みがないプロトコルに対して、悪影響
    #     を及ぼすことはありません。
    #   ＊内向きの速度全体を、実際にデバイス（ADSL モデム/ケーブルモデム）
    #     が出せる速度よりも低くするように、内向きの TCP コネクショ
    #     ンをスピードダウンすると、結果として ISP 側のキュー（DSLAM、ケー
    #     ブル接続、等々）にはほとんどパケットが溜らなくなる「はずです」。
    #     ISP 側のキューには、1500Kbps で4秒分のデータ、つまり 6 メガビッ
    #     トのデータが溜っているのがわかりました。ですからパケットがキュー
    #     に溜らなければ、遅延は低下することになります。
    #

    #  注意（テスト前に持ち上がった疑問）：
    #    ＊このやり方で内向きのトラフィックを制限すると、TCP のバルク転送の性能
    #      が悪くなってしまうのではないか？
    #      - 当座の答えは、そんなことはない、です。（64 バイト未満の小さい）
    #        ACK パケットに優先順位をつければ、パケットの再送で持っていた帯
    #        域を消費しなくても、スループットが最大になります。
    #

    # 注意：次の設定は、筆者の環境ではうまく機能しています：
    # 1.5M/128K ADSL （Pacific Bell Internet (SBC Global Services) 経由）

    DEV=eth0
    RATEUP=90

    RATEDN=700  # この値が 1500 (1.5Mbps) という容量よりかなり低いことに注意して下さい。
                # このため、TCP ウィンドウを操作するといった、もっと優れた
                # ものが実装されて使えるようになるまで、わざわざ内向きのトラフィック
                # を制限する必要はないかもしれません。

    #
    # 設定オプションの終り
    #

    if [ "$1" = "status" ]
    then
            echo "[qdisc]"
            tc -s qdisc show dev $DEV
            tc -s qdisc show dev imq0
            echo "[class]"
            tc -s class show dev $DEV
            tc -s class show dev imq0
            echo "[filter]"
            tc -s filter show dev $DEV
            tc -s filter show dev imq0
            echo "[iptables]"
            iptables -t mangle -L MYSHAPER-OUT -v -x 2> /dev/null
            iptables -t mangle -L MYSHAPER-IN -v -x 2> /dev/null
            exit
    fi

    # すべてを既知の状態にリセットする（クリアする）
    tc qdisc del dev $DEV root    2> /dev/null > /dev/null
    tc qdisc del dev imq0 root 2> /dev/null > /dev/null
    iptables -t mangle -D POSTROUTING -o $DEV -j MYSHAPER-OUT 2> /dev/null > /dev/null
    iptables -t mangle -F MYSHAPER-OUT 2> /dev/null > /dev/null
    iptables -t mangle -X MYSHAPER-OUT 2> /dev/null > /dev/null
    iptables -t mangle -D PREROUTING -i $DEV -j MYSHAPER-IN 2> /dev/null > /dev/null
    iptables -t mangle -F MYSHAPER-IN 2> /dev/null > /dev/null
    iptables -t mangle -X MYSHAPER-IN 2> /dev/null > /dev/null
    ip link set imq0 down 2> /dev/null > /dev/null
    rmmod imq 2> /dev/null > /dev/null

    if [ "$1" = "stop" ]
    then
            echo "Shaping removed on $DEV."
            exit
    fi

    ###########################################################
    #

    # 外向きのスリム化（帯域全体を RATEUP に制限する）

    # 優先順位が低いパケットでは、遅延が約 2 秒になるように、
    # キューサイズを設定する。
    ip link set dev $DEV qlen 30

    # 外向きデバイスの mtu を変更する。mtu を小さくすると遅延は低下するが、
    # IP と TCP のプロトコルオーバーヘッドのため、スループットは若干低下
    # することになる。
    ip link set dev $DEV mtu 1000

    # HTB の root qdisc を追加
    tc qdisc add dev $DEV root handle 1: htb default 26

    # 主要な速度制限クラスを追加
    tc class add dev $DEV parent 1: classid 1:1 htb rate ${RATEUP}kbit

    # リーフクラスを追加 - 「最低でも」帯域を「公平に分ける」ことを各クラスに認める。
    #                      こうすれば、どのクラスも他のクラスのせいでキューが空になる
    #                      ことはけっしてない。他のどのクラスも未使用なら、使える帯域
    #                      はすべて使っても良い。
    tc class add dev $DEV parent 1:1 classid 1:20 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 0
    tc class add dev $DEV parent 1:1 classid 1:21 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 1
    tc class add dev $DEV parent 1:1 classid 1:22 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 2
    tc class add dev $DEV parent 1:1 classid 1:23 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 3
    tc class add dev $DEV parent 1:1 classid 1:24 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 4
    tc class add dev $DEV parent 1:1 classid 1:25 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 5
    tc class add dev $DEV parent 1:1 classid 1:26 htb rate $[$RATEUP/7]kbit ceil ${RATEUP}kbit prio 6

    # qdisc を リーフクラスに接続 - 各優先順位のクラスに SFQ する。SFQ しておけば、各クラス内で
    #                               コネクションを（ほとんど）公平に扱うことが保証される。
    tc qdisc add dev $DEV parent 1:20 handle 20: sfq perturb 10
    tc qdisc add dev $DEV parent 1:21 handle 21: sfq perturb 10
    tc qdisc add dev $DEV parent 1:22 handle 22: sfq perturb 10
    tc qdisc add dev $DEV parent 1:23 handle 23: sfq perturb 10
    tc qdisc add dev $DEV parent 1:24 handle 24: sfq perturb 10
    tc qdisc add dev $DEV parent 1:25 handle 25: sfq perturb 10
    tc qdisc add dev $DEV parent 1:26 handle 26: sfq perturb 10

    # fwmark で、フィルタがクラスに振り分ける - ここでパケットにセットした fwmark に従って、直接
    #                                           優先順位の付いたクラスに振り分ける（fwmark はあとで、
    #                                           iptables を使ってセットする）。先に、ディフォルトの
    #                                           優先順位のクラスを 1 から 26 までとしたので、マーキングが無い
    #                                           パケット（あるいは見慣れない ID のパケット）は、
    #                                           ディフォルトで一番優先順位の低いクラスに入る。
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 20 fw flowid 1:20
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 21 fw flowid 1:21
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 22 fw flowid 1:22
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 23 fw flowid 1:23
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 24 fw flowid 1:24
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 25 fw flowid 1:25
    tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 26 fw flowid 1:26

    # 一連の MYSHAPER-OUT を iptables の mangle テーブルに追加 - これでテーブルを設定し、パケットの
    #                                                            フィルターとマーキングに使う。
    iptables -t mangle -N MYSHAPER-OUT
    iptables -t mangle -I POSTROUTING -o $DEV -j MYSHAPER-OUT

    # fwmark エントリを追加して、トラフィックの種類ごとに分類 - 必要なクラスに従って、fwmark を 20
    #                                                           から 26 に設定。20 が最高の優先順位。

    iptables -t mangle -A MYSHAPER-OUT -p tcp --sport 0:1024 -j MARK --set-mark 23 # 低位ポートのトラフィック用ディフォルト
    iptables -t mangle -A MYSHAPER-OUT -p tcp --dport 0:1024 -j MARK --set-mark 23 # ""
    iptables -t mangle -A MYSHAPER-OUT -p tcp --dport 20 -j MARK --set-mark 26     # ftp のデータポート。優先順位は低い
    iptables -t mangle -A MYSHAPER-OUT -p tcp --dport 5190 -j MARK --set-mark 23   # aol のインスタントマネージャ
    iptables -t mangle -A MYSHAPER-OUT -p icmp -j MARK --set-mark 20               # ICMP (ping) - 優先順位は高い、友人たちに印象づけよう
    iptables -t mangle -A MYSHAPER-OUT -p udp -j MARK --set-mark 21                # DNS 名前解決（パケットは小さい）
    iptables -t mangle -A MYSHAPER-OUT -p tcp --dport ssh -j MARK --set-mark 22    # secure shell
    iptables -t mangle -A MYSHAPER-OUT -p tcp --sport ssh -j MARK --set-mark 22    # secure shell
    iptables -t mangle -A MYSHAPER-OUT -p tcp --dport telnet -j MARK --set-mark 22 # telnet (ew...)
    iptables -t mangle -A MYSHAPER-OUT -p tcp --sport telnet -j MARK --set-mark 22 # telnet (ew...)
    iptables -t mangle -A MYSHAPER-OUT -p ipv6-crypt -j MARK --set-mark 24         # IPSec - 負荷がどんなものかは知らないんだけど ...
    iptables -t mangle -A MYSHAPER-OUT -p tcp --sport http -j MARK --set-mark 25   # ローカルのウェブサーバー
    iptables -t mangle -A MYSHAPER-OUT -p tcp -m length --length :64 -j MARK --set-mark 21 # こまごましたパケット（たぶん ACK だけ）
    iptables -t mangle -A MYSHAPER-OUT -m mark --mark 0 -j MARK --set-mark 26      # 冗長化 - マーキング無しパケットは、なんでも 26（優先順位は低い）

    # 外向きのスリム化終了
    #
    ####################################################

    echo "Outbound shaping added to $DEV.  Rate: ${RATEUP}Kbit/sec."

    # 上りのトラフィックをスリムにしたいだけのときは、以下の行のコメントをはずすこと。
    # exit

    ####################################################
    #

    # 内向きのスリム化（帯域全体を RATEDN に制限する）

    # imq モジュールがロードされたことを確認

    modprobe imq numdevs=1

    ip link set imq0 up

    # qdisc 追加 - ディフォルトの低位優先順位クラス 1 から 21

    tc qdisc add dev imq0 handle 1: root htb default 21

    # 主要な速度制限クラスを追加
    tc class add dev imq0 parent 1: classid 1:1 htb rate ${RATEDN}kbit

    # リーフクラス追加 - TCP は 21 に、TCP 以外は 20に、それぞれ振り分け。
    #
    tc class add dev imq0 parent 1:1 classid 1:20 htb rate $[$RATEDN/2]kbit ceil ${RATEDN}kbit prio 0
    tc class add dev imq0 parent 1:1 classid 1:21 htb rate $[$RATEDN/2]kbit ceil ${RATEDN}kbit prio 1

    # qdisc をリーフクラスに接続 - 各優先順位のクラスに SFQ する。SFQ しておけば、各クラス内で
    #                               コネクションを（ほとんど）公平に扱うことが保証される。
    tc qdisc add dev imq0 parent 1:20 handle 20: sfq perturb 10
    tc qdisc add dev imq0 parent 1:21 handle 21: red limit 1000000 min 5000 max 100000 avpkt 1000 burst 50

    # fwmark で、フィルタがクラスに振り分ける - ここでパケットにセットした fwmark に従って、直接
    #                                           優先順位の付いたクラスに振り分ける（fwmark はあとで、
    #                                           iptables を使ってセットする）。先に、ディフォルトの
    #                                           優先順位のクラスを 1 から 26 までとしたので、マーキングが無い
    #                                           パケット（あるいは見慣れない ID のパケット）は、
    #                                           ディフォルトで一番優先順位の低いクラスに入る。

    tc filter add dev imq0 parent 1:0 prio 0 protocol ip handle 20 fw flowid 1:20
    tc filter add dev imq0 parent 1:0 prio 0 protocol ip handle 21 fw flowid 1:21

    # 一連の MYSHAPER-IN を iptables の mangle テーブルに追加 - これでテーブルを設定し、パケットの
    #                                                            フィルターとマーキングに使う。
    iptables -t mangle -N MYSHAPER-IN
    iptables -t mangle -I PREROUTING -i $DEV -j MYSHAPER-IN

    # fwmark エントリを追加して、トラフィックの種類ごとに分類 - 必要なクラスに従って、fwmark を 20
    #                                                           から 26 に設定。20 が最高の優先順位。

    iptables -t mangle -A MYSHAPER-IN -p ! tcp -j MARK --set-mark 20              # tcp 以外のパケットの優先順位を最高にする
    iptables -t mangle -A MYSHAPER-IN -p tcp -m length --length :64 -j MARK --set-mark 20 # 短い TCP パケットは、たぶん ACK
    iptables -t mangle -A MYSHAPER-IN -p tcp --dport ssh -j MARK --set-mark 20    # secure shell
    iptables -t mangle -A MYSHAPER-IN -p tcp --sport ssh -j MARK --set-mark 20    # secure shell
    iptables -t mangle -A MYSHAPER-IN -p tcp --dport telnet -j MARK --set-mark 20 # telnet (ew...)
    iptables -t mangle -A MYSHAPER-IN -p tcp --sport telnet -j MARK --set-mark 20 # telnet (ew...)
    iptables -t mangle -A MYSHAPER-IN -m mark --mark 0 -j MARK --set-mark 21              # 冗長化 - マーキング無しパケットは、なんでも 26（優先順位は低い）

    # 最後に、これらのパケットが先に設定した imq0 を通るよう指示する。
    iptables -t mangle -A MYSHAPER-IN -j IMQ

    # 内向きのスリム化終了
    #
    ####################################################

    echo "Inbound shaping added to $DEV.  Rate: ${RATEDN}Kbit/sec."

## 新しいキューのテスト

新しい設定をテストする一番簡単な方法は、 優先順位の低いトラフィックで上りの伝送を飽和状態にすることです。 これは優先順位をどう設定したかによって違ってきます。 例をあげれば、telnet のトラフィックと ping のトラフィックの 優先順位を、他の高位ポート（FTPの転送に使っているもの、等々） のトラフィックより高く（fwmark を小さく）したとしましょう。

FTP のアップロードを開始して、上りの帯域を飽和させると、 ゲートウェイ（DSL ラインの他端）までにかかる ping の時間の増加が、 優先順位付きキューイングを使っていない時に比べて、 わずかなのに気づくはずです。

ping の時間が 100ms を下回るのは、設定の仕方次第では、 普通のことですが、1 秒ないし 2 秒を上回るようなら、 たぶんちゃんと動作していないことになります。

## よし、動いた！！ で、次は？

さあ、帯域管理がうまく始まりましたね。 次は、その使い方を考え始めて下さい。 結局、その責任は皆さんにあるのでしょうから！

- 自分のネットワークの性能に悪影響を与えずに、 Gnutella のクライアントを使って、「自分のファイルを共有」しよう。

- ウェブページがヒットしても、Quake が遅くならないウェブサーバーを走らせよう。

## 日本語版謝辞

校正にあたり、JF プロジェクトの皆様にご協力いただきました。 特に、Seiji Kaneko さんには、貴重なアドバイスをいただきました。 この場を借りて、御礼申し上げます。