9.5. クラスフルなキューイング規則

クラスフルな qdisc は、 異なった種類のトラフィックに対して異なる取り扱いが必要な場合に非常に便利です。 クラスフルな qdisc のひとつに、'CBQ (Class Based Queueing)' というものがあります。これは非常に広く紹介されているので、 クラスのあるキューイングと言えば CBQ だと思っている人も多いのですが、 これは真実ではありません。

CBQ は単に最も古くからあったものにすぎません (そして、最も複雑なものでもあるのですが)。 つねにこれが望みの動作をするとは限りません。 これは「sendmail 現象」にだまされ、 文書化されていない複雑な技術が入手できる一番良いものだ、 と思っている人たちにはショックかもしれません。

CBQ とその代替品について、もうちょっと説明していきましょう。

9.5.1. クラスフルな qdiscs とクラスにおける流れ

クラスフルな qdisc に入ったトラフィックは、 その内部にあるクラスのいずれかに送らなければなりません。 よってトラフィックに「クラス選別」を適用する必要があります。 パケットに対して何を行うかを決める際には、 いわゆる「フィルタ」に問い合わせがいきます。 ここで大事なのは、フィルタは qdisc の内部から呼ばれるのであって、 他から呼ばれることはありえない、ということです。

その qdisc に属しているフィルタは、決定を返します。 すると qdisc はそれを用いてパケットをクラスのどれかひとつにエンキューします。 サブクラスがあると、さらに動作を適用するかどうか見るために、 他のフィルタが適用されることもあります。 サブクラスがなければ、そのクラスはパケットを自分の持つ qdisc にエンキューします。

多くのクラスフルな qdisc は、他のクラスを持つだけでなく、 帯域制限も行います。これはパケットのスケジューリング (例えば SFQ) と速度制御の両方を行うのに便利です。 高速のインターフェース (例えばイーサネット) の先に低速なデバイス (ケーブルモデム) をつなぐ場合などには、 これが必要になります。

SFQ だけを使うだけでは、何も起こりません。 パケットはそのルータに入り、遅延せずに出て行くだけだからです (出力インターフェースが実際のリンク速度よりずっと速いからです)。 この場合スケジュール対象となるキューは存在しません。

9.5.2. qdisc ファミリ: ルート・ハンドル・兄弟・親

各インターフェースは出口「ルート (root) qdisc」をひとつ持ちます。 これはデフォルトでは、先に紹介した pfifo_fast キューイング規則です。 各 qdisc とクラスにはハンドル (handle) が割り当てられ、 後で行う設定文からこの qdisc を参照する際にはこのハンドルが用いられます。 インターフェースには出口 qdisc だけでなく入口 qdisc もあり、 ここでは到着トラフィックの監視制限ができます。

これらの qdisc のハンドルは、メジャー (major) 番号とマイナー (minor) 番号の 2 つからなり、<major>:<minor> のように記述します。 習慣的にルート qdisc には '1:' という名前が付くことになっています。 これは '1:0' と同じです。qdisc のマイナー番号はつねに 0 です。

クラスは、親と同じメジャー番号を持たなければなりません。 メジャー番号は、出口・入口の設定の内部で、他と重なってはいけません。 マイナー番号は、qdisc およびそのクラスの内部で、他と重なってはいけません。

9.5.2.1. フィルタによるトラフィックのクラス選別

まとめると、よくある階層図は次のようになります:
                     1:   root qdisc
                      |
                     1:1    child class
                   /  |  \
                  /   |   \
                 /    |    \
                 /    |    \
              1:10  1:11  1:12   child classes
               |      |     | 
               |     11:    |    leaf class
               |            | 
               10:         12:   qdisc
              /   \       /   \
           10:1  10:2   12:1  12:2   leaf classes

しかし、だまされないでください。 カーネルがこのような逆向きの木構造 (あるいは後ほど出てくる網目構造) のもとにあると考えてはいけません。それは正しくありません。 パケットはルート qdisc にエンキューされ、デキューされるのであって、 カーネルはこの部分としか対話しないのです。

パケットは例えば次のようなチェインに沿ってクラス選別されます。

1: -> 1:1 -> 1:12 -> 12: -> 12:2

これでこのパケットは、クラス 12:2 に属する qdisc に収まりました。 この例では、フィルタは木のそれぞれの「節 (node)」に属しており、 これらのフィルタが次にどちらの枝に向かうかを決定します。 これはわかりやすいですね。 しかしながら、次のようなものも可能です:

1: -> 12:2

この場合は、ルートに属するフィルタが、 パケットを直接 12:2 に送るように決めたのです。

9.5.2.2. ハードウェアへパケットをデキューする

カーネルがパケットを取り出してインターフェースに送ることを決めると、 ルート qdisc である 1: はデキュー要求を受け取り、 これは 1:1 に渡されます。これは続いて 10:, 11:, 12: へと渡されます。 これらはそれぞれ兄弟に問い合わせを行い、自分から dequeue() を行おうとします。 この場合パケットは 12:2 にしかないので、 木の全体を辿っていかなければなりません。

要するに、ネストされたクラスは親の qdisc と通信するだけで、 インターフェースとは通信しないのです。 カーネルによってデキューされるのはルート qdisc だけなのです!

結果的に、あるクラスがその親より先にデキューすることはありえません。 これは我々の望むことそのものです: このようにすれば SFQ を内側のクラスとし、 帯域制限は行わずにスケジューリングだけをさせ、 制限用の qdisc をその外に置くことが可能となります。

9.5.3. PRIO qdisc

PRIO qdisc は実際の帯域制限は行わず、 フィルタの設定に従ってトラフィックの分割のみを行います。 PRIO qdisc は、ある種の大きな pfifo_fast だと考えることができます。 ただし各バンドは単なる FIFO ではなく、別々のクラスになります。

パケットが PRIO qdisc にエンキューされると、 与えたフィルタコマンドに基づいて、あるクラスが選別されます。 デフォルトでは 3 つのクラスが生成されます。 これらのクラスはデフォルトでは純粋な FIFO qdisc のみを含み、 内部構造は持ちません。しかしこれらは別の qdisc と置き換え可能です。

パケットのデキューを要求されると、クラス :1 が最初に試されます。 数値の大きなクラスは、それより小さなバンドのどれからも パケットを取り出すことができなかった場合にのみ用いられます。

TOS フラグだけでなく、tc フィルタの能力すべてを用いて トラフィックの種類を優先付けしたい場合に、 この qdisc はとても便利です。 デフォルトで定義されている 3 つのクラスに さらに別の qdisc を追加することもできます (pfifo_fast は単純な fifo qdisc しか扱えません)。

これは実際の帯域制限は行わないので、 SFQ に対するものと同じ注意が該当します。 これを用いるのは、物理的なリンクが一杯になっている場合か、 または実際の帯域制限を行うクラスフルな qdisc の内部でにすべきです。 後者はほとんどのケーブルモデムや DSL デバイスに該当します。

公式の言葉で言うと、PRIO qdisc は処理保存的なスケジューラなのです。

9.5.3.1. PRIO のパラメータと使い方

tc は以下のパラメータを認識します:

bands

生成するバンドのクラス。各バンドは実際にはクラスです。 この番号を変更する場合には、次の項目も変更する必要があります:

priomap

トラフィックを選別する tc フィルタを与えないと、 PRIO qdisc は TP_PRIO 優先度を参照して、 どのようにトラフィックをエンキューするかを決定します。

これは以前に紹介した pfifo_fast qdisc とまったく同じように動作します。 詳細はそちらを見てください。

各バンドはクラスで、デフォルトでは major:1 から major:3 までの名前が付きます。 よって PRIO qdisc の名前が 12: なら、tc は 12:1 へのトラフィックに 最も高い優先度を与えるようにフィルタします。

繰り返しますが、バンド 0 のマイナー番号が 1 です! 同様に、バンド 1 はマイナー番号 2 となります (以下同様)。

9.5.3.2. 設定例

この木を作ることを考えましょう:
          1:   root qdisc
         / | \ 
       /   |   \
       /   |   \
     1:1  1:2  1:3    classes
      |    |    |
     10:  20:  30:    qdiscs    qdiscs
     sfq  tbf  sfq
band  0    1    2

バルク転送のトラフィックは 30: へ送り、 対話的なトラフィックは 20: または 10: へ送ります。

コマンドラインです:
# tc qdisc add dev eth0 root handle 1: prio 
## これによって直ちにクラス 1:1, 1:2, 1:3 ができます。
  
# tc qdisc add dev eth0 parent 1:1 handle 10: sfq
# tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
# tc qdisc add dev eth0 parent 1:3 handle 30: sfq                                

では、いま作った内容を見てみましょう:
# tc -s qdisc ls dev eth0 
qdisc sfq 30: quantum 1514b 
 Sent 0 bytes 0 pkts (dropped 0, overlimits 0) 

 qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms 
 Sent 0 bytes 0 pkts (dropped 0, overlimits 0) 

 qdisc sfq 10: quantum 1514b 
 Sent 132 bytes 2 pkts (dropped 0, overlimits 0) 

 qdisc prio 1: bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 174 bytes 3 pkts (dropped 0, overlimits 0) 
おわかりのように、バンド 0 には既にトラフィックが入っています。 そしてこのコマンドの実行中に、パケットがひとつ送られました!

ここで TOS フラグを適切に設定するようなツールを用いて バルク転送トラフィックを流し、再び見てみましょう。
# scp tc ahu@10.0.0.11:./
ahu@10.0.0.11's password: 
tc                   100% |*****************************|   353 KB    00:00    
# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b 
 Sent 384228 bytes 274 pkts (dropped 0, overlimits 0) 

 qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms 
 Sent 2640 bytes 20 pkts (dropped 0, overlimits 0) 

 qdisc sfq 10: quantum 1514b 
 Sent 2230 bytes 31 pkts (dropped 0, overlimits 0) 

 qdisc prio 1: bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 389140 bytes 326 pkts (dropped 0, overlimits 0) 
おわかりのように、すべてのトラフィックはハンドル 30: に向かいました。 意図したとおり、これが最も優先度の低いバンドです。 では、対話的なトラフィックがより優先されるか確認するために、 そのようなトラフィックを生成してみましょう:

# tc -s qdisc ls dev eth0
qdisc sfq 30: quantum 1514b 
 Sent 384228 bytes 274 pkts (dropped 0, overlimits 0) 

 qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms 
 Sent 2640 bytes 20 pkts (dropped 0, overlimits 0) 

 qdisc sfq 10: quantum 1514b 
 Sent 14926 bytes 193 pkts (dropped 0, overlimits 0) 

 qdisc prio 1: bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 401836 bytes 488 pkts (dropped 0, overlimits 0) 

うまくいっています。追加したトラフィックはすべて 10: へ、 つまり最も優先度の高い qdisc へ行っています。 先に scp を受けとった、優先度の低いバンドへは一切向かっていません。

9.5.4. (有名な) CBQ qdisc

既に述べたように、CBQ は現在ある中で最も複雑な、最もすごい、 そして最も理解されておらず、 おそらくは正しく動作させるのに最も手がかかる qdisc です。 これは作者が悪辣だったり無能だったわけでは全然なく、 単に CBQ アルゴリズムが几帳面なものでなく、 また Linux の動作との相性が悪いからなのです。

クラスフルであるという以外に、CBQ は帯域制限の動作も行います。 そしてうまく動かないのは、まさにこの点にあるのです。 例えば 10mbit/s の接続を 1mbit/s に絞りたい場合、 全時間の 90% のあいだ回線はアイドルになるはずです。 そうでなければ、そうなるように絞る必要があります。

これを測定するのはかなり難しいので、 代わりに CBQ は、ハードウェア層からのデータ要求の間隔をマイクロ秒単位で測定し、 そこからアイドル時間を導出しようとします。 これを用いると、接続がどのくらい一杯か、空いているかを概算できます。

これはどちらかというともってまわったやり方で、 つねに正しい結果になるとは限りません。 例えば、インターフェースの実際の速度が、 (おそらくはドライバの実装が悪いために) 100mbit/s のデータをフルには通せないとしたらどうでしょう? PCMCIA のネットワークカードも、バスの設計上 100mbit/s に達することはあり得ません。 これらの場合、アイドル時間はどのように考えればいいのでしょうか?

PPP over Ethernet, PPTP over TCP/IP のような、 実際のデバイスではないネットワークデバイスを考えると、 さらに状況は悪くなります。これらのような場合の実効的なバンド幅は、 おそらくユーザ空間のパイプの効率によって決まります。 これは非常に大きくなるでしょう。

測定を行った人たちは、CBQ が必ずしも常に正確であるとは限らず、 ときには指定とまったく異なる結果となることに気づいています。

しかし、多くの環境では CBQ はうまく動作します。 ここに提供した記述を用いれば、 ほとんどの場合、うまく動作するような設定が可能だと思います。

9.5.4.1. CBQ による帯域制限の詳細

先に述べたように、CBQ の動作では、 ちょうど実際のバンド幅が設定した速度に落ちるよう、 接続を適切な時間アイドルにします。 これを行うため、CBQ では平均的なパケットを渡す際の時間間隔を計算します。

動作中、実効的なアイドル時間は、指数重み付け移動平均 (Exponential Weighted Moving Average: EWMA) を用いて測定されます。 これは最近のパケットを、過去のパケットに比べて、 指数関数の重みでより重要だと考えるものです。UNIX の平均負荷も、 この方法で計算されています。

計算したアイドル時間を、この EWMA 測定値から差し引いた、 その結果の数値を 'avgidle' と呼びます。 負荷で一杯の接続における avgidle はゼロです。 このときパケットは、まさに計算された間隔につきひとつずつ到着しています。

過負荷の接続では avgidle は負の値になります。 負になりすぎると CBQ はしばらく遮断し、'overlimit' 状態になります。

逆に、アイドルな接続では大量の avgidle が蓄積され、 数時間沈黙した後のバンド幅は無限大になってしまいます。 これを防ぐため、avgidle には maxidle という上限が設けられています。

overlimit になると、原理的には、 CBQ は自分自身をちょうどパケット送信間隔の計算値の間だけ絞ります。 そしてパケットをひとつだけ送り、再び絞ります。 ただし以下の 'minburst' パラメータも参照してください。

以降に、帯域制限を設定するために指定できるパラメータを示します:

avpkt

パケットの平均サイズ (バイト単位)。 maxidle を maxburst から計算する際に必要です (maxburst はパケット単位で指定されます)。

bandwidth

デバイスの物理的なバンド幅。アイドル時間の計算に必要です。

cell

パケットをデバイスを通して送信するのにかかる時間は、 パケットのサイズに応じていくつか異なる値になることがあります。 例えばサイズが 800 と 806 のパケットを送るのにかかる時間が ちょうど同じだったとすると、これが粒度を決めます。 通常は 8 を指定します。2 の整数べきで指定しなければなりません。

maxburst

maxidle の計算時に、このパケット数が用いられます。 avgidle が maxidle から 0 になるとき、 平均的なサイズのパケットをこの数だけバースト送信できる、 というのが条件になります。 この数を大きくすると、よりバーストに対する耐性が高まります。 maxidle を直接設定することはできず、 このパラメータを通して調整するしかありません。

minburst

前述の通り、CBQ は overlimit の際に絞り動作が必要です。 この際の理想的な動作は、ちょうど計算されたアイドル時間だけ絞り、 そして 1 つのパケットを通すことです。 しかし UNIX カーネルは、10ms より短い間隔でイベントを スケジュールすることは難しいので、少々長い間隔絞り、 そして minburst 分のパケットを一度に通し、 そして minburst をかけた時間だけスリープする方がより良く動作します。

この待ち時間は offtime と呼ばれています。 minburst の値を大きくすると、長い目では正確な帯域制限につながりますが、 ミリ秒のタイムスケールではより大きなバーストが起きることになります。

minidle

avgidle が 0 以下になると overlimit 状態となり、 avgidle が 1 つのパケットの送信を許す値にまで大きくなるのを 待たなければなりません。 接続を長い事遮断したあとに、突然大きなバーストが生じるのを防ぐには、 avgidle が小さくなりすぎたとき、 これを minidle にリセットしなければなりません。

minidle は負のマイクロ秒で指定します。 したがって 10 を指定すると、avgidle の下限を -10us としたことになります。

mpu

最小パケットサイズ (Minimum Packet Size) です。 データサイズが 0 のパケットでもイーサネットでは 64 バイトとなり、 送信にある時間がかかるので、この値が必要です。 CBQ がアイドル時間を正確に計算するには、この値を知っている必要があります。

rate

この qdisc から送信されるトラフィックの速度の設定値。 これは「スピードつまみ」です。

内部的には、CBQ にはたくさんの微調整をしています。 例えばエンキューされたデータを持っていないことがわかっているクラスには、 問い合わせは行きません。overlimit のクラスにはペナルティがつき、 実効的な優先度が下がります。非常に賢くかつ複雑になっています。

9.5.4.2. CBQ のクラスフル動作

CBQ は帯域制限だけでなく、 先に紹介したアイドル時間の近似を用いて PRIO キューのような動作もできます。 つまりクラスに異なる優先度を与え、優先度の数値が小さい方を、 大きな方より先にポーリングできるのです。

ハードウェア層からパケットをネットワークに送信するよう要求されると、 重み付きラウンドロビン (Weighted Round Robin: WRR) プロセスが、 優先度の数値が小さいクラスから開始されます。

これらはグループ化されており、 データがあるか問い合わせを受け、 データがあればそれを返します。 あるクラスがあるバイト数のデキューを許されると、 その優先度にある別のクラスが試行を受けます。

この WRR プロセスは、以降のパラメータによって制御されます。

allot

外部の CBQ がパケットをインターフェースに送るよう依頼されると、 この依頼は (クラスに属する) 内部の qdisc へ、'priority' パラメータの順に送られます。自分の順番が来たクラスは、 ある制限された量のデータを送信できます。'Allot' はこの量の単位となる量です。 より詳しくは 'weight' パラメータを見てください。

prio

CBQ は PRIO デバイスのように動作できます。 優先度の高い内部クラスが先に試行され、 これらがトラフィックを保持していれば、 他のクラスはポーリングされません。

weight

weight は重み付けラウンドロビンプロセスの補助をします。 各クラスは、送信の機会を順に与えられます。 あるクラスが、他に比べて明らかに大きなバンド幅を持つ場合、 このクラスには、1 回あたりにより多くのデータ送信を許すのが妥当でしょう。

CBQ はクラス以下の weight を全て足してこれらを正規化します。 したがって任意の数を用いることができ、比率だけが問題になります。 たいていの人は、おおよそ 'rate/10' を目安にしているようで、 これでだいたいうまく行っているようです。 正規化された weight には 'allot' パラメータがかけられ、 1 ラウンドあたりに送信できるデータ量が決定されます。

ある CBQ 階層に属するクラスは、 すべて同じメジャー番号を持たなければならないことに注意してください。

9.5.4.3. 接続共有・貸借を決める CBQ パラメータ

ある種のトラフィックを純粋に制御するだけでなく、 どのクラスが他のクラスから回線容量を借りられるか、 また逆にバンド幅を貸せるか、ということも CBQ では指定できます。

isolated/sharing

クラスを 'isolated' と設定すると、 そのクラスは兄弟クラスへのバンド幅の貸し出しができなくなります。 ある回線を、ライバル関係にある (あるいは互いに仲の悪い) 組織が使用していて、 余剰を互いに融通させたくないようであれば、これを用いてください。

制御プログラムである tc では、'isolated' の逆意である 'sharing' も使えます。

bounded/borrow

クラスは 'bounded' にすることもできます。 こうすると兄弟クラスからバンド幅を借りようとはしなくなります。 tc では 'bounded' の逆意である 'borrow' も使えます。

典型的な状況は、回線を 2 つの組織が使用していて、 その両方が 'isolated' かつ 'bounded' であるような場合でしょう。 この場合は両者ともそれぞれに割り当てられた速度に留まり、 お互いに貸し借りも行いません。

このような組織クラスの内部には、 バンド幅を貸し借りするような他のクラス群が置かれることもあるでしょう。

9.5.4.4. 設定例


               1:           root qdisc
               |
              1:1           child class
             /   \
            /     \
          1:3     1:4       leaf classes
           |       |
          30:     40:       qdiscs
         (sfq)   (sfq)

この設定では web サーバのトラフィックを 5mbit に、 SMTP トラフィックを 3mbit に制限します。 両者の合計は 6mbit を越えないものとします。 ハードウェアは 100mbit NIC で、 これらのクラスは互いにバンド幅を貸し借りできるものとします。
# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit         \
  avpkt 1000 cell 8
# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit  \
  rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20      \
  avpkt 1000 bounded
この部分では root およびいつもの 1:1 クラスをインストールしています。 この 1:1 クラスは bounded なので、合計のバンド幅は 6mbit を越えません。

前述の通り、CBQ では非常にたくさんのつまみを調整しなければなりません。 しかしこれらのパラメータは、すべてこれまでに説明してあります。 これに対応する HTB での設定 (後述) は、もっとずっと単純になります。

# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit  \
  rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20      \
  avpkt 1000                       
# tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit  \
  rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20      \
  avpkt 1000

これらは 2 つの葉クラスになります。 設定したい速度に対して、重みをどのようにかけているかに注目してください。 これら両クラスは bounded ではありませんが、bounded であるクラス 1:1 に接続しています。 よってこれら 2 クラスの合計バンド幅は、決して 6mbit を越えません。 ところで、クラス id のメジャー番号は、親の qdisc と同じでなければなりません!

# tc qdisc add dev eth0 parent 1:3 handle 30: sfq
# tc qdisc add dev eth0 parent 1:4 handle 40: sfq

両クラスは、デフォルトでは FIFO qdisc を持っています。 しかしここでは、これらを SFQ で置き換え、 両データフローが同じように扱われるようにします。
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip \
  sport 80 0xffff flowid 1:3
# tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip \
  sport 25 0xffff flowid 1:4

これらのコマンドでは、直接 root に追加しており、 トラフィックを正しい qdisc に送ります。

ここでは 'tc class add' を、qdisc 内部にクラスを「生成」するために用いています。 一方 'tc qdisc add' は、これらのクラスに qdisc を文字通り追加するのに用いています。

これら 2 つの規則でクラス選別できないトラフィックが来たらどうなるでしょうか。 この場合は、データは 1:0 の内部で処理されるので、制限を受けません。

web と SMTP が両方合わせて 6mbit/s を越えそうになったときは、 バンド幅は weight パラメータに比例するかたちで分割されます。 つまり 5/8 が web サーバに、3/8 がメールサーバに向かいます。

またこの設定からは、web サーバのトラフィックとして、 最低 5/8 * 6 mbit = 3.75 mbit が常に保証されることもわかります。

9.5.4.5. 他の CBQ パラメータ: split と defmap

先に述べたとおり、クラスフルな qdisc では、 どのクラスにパケットをエンキューするかを決めるために フィルタを呼ぶ必要があります。

フィルタを呼ぶほかに、CBQ には他の選択肢もあります。 それが defmap と split です。 これは極めて理解するのが面倒で、またそれほど重要でもありません。 しかし他に defmap と split を適切に説明しているところを知りませんので、 著者なりに最善を尽くしてみたいと思います。

Type of Service フィールドのみでフィルタを行いたい局面が多いため、 特殊な文法が提供されています。CBQ はパケットのエンキュー先を決めるとき、 このノードが 'split' ノードであるかどうかをチェックします。 もしそのような場合には、サブの qdisc のひとつは、 ある設定された優先度 (これは TOS フィールドから決まるでしょう) や、 あるいはアプリケーションから設定されたソケットオプションを持つ、 すべてのパケットを受信するよう指示されています。

そのパケットの優先度ビット列は defmap フィールドと and 演算され、 マッチしたかどうかが判断されます。 つまりこれは、ある優先度にだけマッチする、 非常に高速なフィルタを手軽に作る方法なのです。 ff (16 進) という defmap はすべてにマッチし、0 は何にもマッチしません。 設定例を見れば、少しはわかりやすいでしょう:

# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 \
  cell 8 avpkt 1000 mpu 64
 
# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit    \
  rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20        \
  avpkt 1000
標準的な CBQ の初期段階設定です。 しかしこの設定パラメータの多さには、どうあっても慣れることはありませんね!

defmap は TCP_PRIO ビット列を参照します。 これは以下のように定義されています。

TC_PRIO..          Num  Corresponds to TOS
-------------------------------------------------
BESTEFFORT         0    Maximize Reliablity        
FILLER             1    Minimize Cost              
BULK               2    Maximize Throughput (0x8)  
INTERACTIVE_BULK   4                               
INTERACTIVE        6    Minimize Delay (0x10)      
CONTROL            7                               

TC_PRIO.. の数値は右から数えたビット列に対応しています。 TOS ビットの優先度への変換に関する詳細は、 pfifo_fast の節を見てください。

では対話的なクラスとバルク転送のクラスです:

# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit     \
  rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20        \
  avpkt 1000 split 1:0 defmap c0

# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit     \
  rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20        \
  avpkt 1000 split 1:0 defmap 3f

この split qdisc は 1:0 で、ここで選別がなされます。 C0 は 2 進で 11000000 で、3F は 00111111 です。 よってこれら 2 つをあわせると、すべてにマッチします。 最初のクラスはビット 7 と 6 にマッチし、 よって「対話的」かつ「制御」トラフィックにマッチします。 二番目のクラスは残りにマッチします。

これでノード 1:0 は、次のようなテーブルを持つことになります。
priority	send to
0		1:3
1		1:3
2		1:3
3		1:3
4		1:3
5		1:3
6		1:2
7		1:2

もうちょっと遊びたい人は、'change mask' を渡すこともできます。 これは変更したい優先度を厳密に指定するものです。 これを使う必要があるのは、'tc class change' を実行する場合だけです。 例えば best effort トラフィックを 1:2 に追加するには、 次のようにすればできます。

# tc class change dev eth1 classid 1:2 cbq defmap 01/01

これで 1:0 の優先度マップは次のようになります:

priority	send to
0		1:2
1		1:3
2		1:3
3		1:3
4		1:3
5		1:3
6		1:2
7		1:2

FIXME: 'tc class change' はソースを見ただけで試してません。

9.5.5. 階層的トークンバケツ (Hierarchical Token Bucket: HTB)

Martin Devera (<devik>) は正しくも、 CBQ が複雑で、多くの状況において最適なものではなさそうだ、 という認識に至りました。彼の階層的なアプローチは、 固定されたバンド幅があり、それを別々の目的に分割し、 各目的にバンド幅を保証し、 またどのくらいのバンド幅を借りることを定義できるようにしたい、 というような場合に適しています。

HTB は CBQ と同じように動作しますが、 帯域制限にアイドル時間の計算を必要としません。 そのかわり、HTB はクラスフルなトークンバケツフィルタになっています (すなわちこれが名前の由来)。 パラメータは数個で、彼の サイト で良く文書化されています。

設定を複雑にしたいときも、HTB はちゃんと対応してくれます。 CBQ では、シンプルなクラスの設定も既に複雑でした! HTB3 (HTB のバージョンについては HTB のホームページ を見てください) は、公式のカーネルソースの一部になっています (2.4.20-pre1 及び 2.5.31 以降)。 しかし、HTB3 パッチの当たった 'tc' を入手する必要があるかもしれません。 HTB のカーネルとユーザ空間の部分とは、同じメジャー番号でなければなりません。 さもないと 'tc' は HTB に対して動作しません。

最近のカーネルを持っている人、 あるいはカーネルにパッチできる立場の人は、 ぜひとも HTB の利用を考えましょう。

9.5.5.1. 設定例

前述の CBQ の設定例と、機能的にはほぼ同一のものです:

# tc qdisc add dev eth0 root handle 1: htb default 30

# tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k

# tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
# tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k

続いて、これらのクラスの下に SFQ を置くと良いでしょう。
# 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

トラフィックを適切なクラスに向けるフィルタを追加します:
# U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
# $U32 match ip dport 80 0xffff flowid 1:10
# $U32 match ip sport 25 0xffff flowid 1:20
これでおしまい。わかりにくく、説明されていない数値もなければ、 文書化されていないパラメータもありません。

HTB は実にすばらしく見えます。10: と 20: の両方が保証されたバンド幅を取り、 そしてまだ残りがあれば、これらのクラスはその残りを 5:3 の比率で借りることになります。これは期待した通りの動作でしょう。

クラス付けされなかったトラフィックは 30: に向かいます。 このクラスは自分自身のバンド幅はほとんど持っていませんが、 余っている分はすべて借りることができます。 内部で SFQ を利用するようにしたので、公平性もタダで実現できています!