元になった文書:
iproute2 配布にある例
White Paper-QoS protocols and architectures and IP QoS Frequently Asked Questions both by Quality of Service Forum.
この章は Esteve Camps <esteve@hades.udg.es> が執筆しました。
まず最初に、このテーマについて書かれた RFC (RFC2474, RFC2475, RFC2597, RFC2598) を読むことを強くお奨めします。 これらは IETF DiffServ working Group web site および Werner Almesberger web site にあります (彼は Linux で Differentiated Services の機能をサポートするコードを書きました)。
Dsmark は、Differentiated Services (DiffServ とか、 単に DS とも呼ばれます) で必要となる機能を提供するキューイング規則です。 DiffServ は 2 つある実際の QoS 機構のうちのひとつ (もうひとつは Integrated Services と呼ばれています) で、 パケットの IP ヘッダの DS フィールドに含まれている値に基づいて動作します。
IP で、何らかの QoS レベルのサービスを得るために設計された最初の解は、 IP ヘッダの Type of Service フィールド (TOS バイト) でした。 この値を変えることにより、 スループット・遅延・信頼性などのレベルの高低を選択できたのでした。 しかしこれは、最近のサービス (リアルタイムアプリケーション、 対話的アプリケーションなど) で必要とされる、 十分な柔軟性を持っていませんでした。その後、新たな機構が現れました。 そのひとつが DiffServ で、これは TOS の各ビットを占有し、 DS フィールドとして再定義して使用します。
DiffServ はグループ指向です。 つまり、フローについては何も関知しないのです (これは Integrated Services の目的となります)。 対象にするのはフローの集合で、 パケットがどの集合に属しているかによって、 異なる動作を適用することになります。
パケットが境界ノード (DiffServ ドメインのエントリノード) に到着すると、 DiffServ ドメインへ入る際に、これらのパケットには制限・帯域制限・マーキング などを行う必要があります (マーキングとは、DS フィールドに値を代入することです。 牛みたいなもんです :-) )。 DiffServ ドメインの内部 (コア) ノードで、 どのような動作や QoS レベルを適用するかは、 このマーク (値) を見て判断されます。
ご想像の通り、Differentiated Services にはドメインがあり、 すべての DS 規則はここで適用されます。 実際、ドメインに入ってきたすべてのパケットをクラス選別する、 と考えることも可能です。パケットはドメインに入ると、 クラス選別機構が命ずる規則に従うことになり、 経由するノードのすべてでその QoS レベルが適用されるのです。
実のところ、自分のローカルなドメインには自前の制限を適用できますが、 他の DS ドメインと接続する際には、 Service Level Agreements を考慮しなければなりません。
この段階では、おそらくたくさんの疑問があるでしょう。 DiffServ は、ここで説明した以上の内容を含んでいます。 3 つの RFC を 50 行で要約するのが不可能であることは、 理解していただけるでしょう :-)
DiffServe の文献が示す通り、我々は境界ノードと内部ノードを差別化します。 これらはトラフィック経路のうち、重要な 2 つのポイントです。 いずれにおいても、パケットが到着するとクラス選別が行われます。 その結果は、DS プロセスがパケットをネットワークに放つにあたって、 どこか別の場所で用いられることになるでしょう。 diffserv のコードに skb->tc_index というフィールドを含む sk_buff という構造体があるのは、要するにこのためなのです。 ここには初期クラス選別の結果が保存され、 これが DS 処理のいくつかの場所で用いられることになります。
最初 skb->tc_index の値は、 すべての到着パケットの IP ヘッダの DS フィールドから抽出され、 DSMARK qdisk によって設定されます。 また、cls_tcindex 選別器は、 skb->tcindex の全体を読み、その結果をクラス選別に用います。
しかし、まず最初に、DSMARK qdisc のコマンドとパラメータを見てみましょう。
... dsmark indices INDICES [ default_index DEFAULT_INDEX ] [ set_tc_index ] |
indices: (mask,value) ペアからなるテーブルのサイズ。 最大値は 2^n (ただし n>=0) です。
Default_index: クラス選別器がまったくマッチを見つけられなかった場合のデフォルトとなる、 テーブルエントリのインデックス。
Set_tc_index: dsmark qdisc に、DS フィールドの値を取得して skb->tc_index へ保存するよう指示します。
この qdisc は次のような段階からなります:
qdisc コマンドで set_tc_index を宣言していた場合は、 DS フィールドが抽出されて skb->tc_index 変数に保存される。
クラス選別器が起動する。 このクラス選別器はクラス ID を返し、これは skb->tc_index 変数に保存される。 マッチするフィルタが見つからないと、 default_index オプションを見て、どの classID を保存するか決める。 set_tc_index と default_index のいずれも指定していなかった場合の結果は 未定義となる。
内部の qdisc に送られた後も、このフィルタの結果を再利用できる。 内部 qdisc が返した classid は skb->tc_index に保存される。 この値は、将来 mask-value テーブルのインデックスとして利用できる。 最終的にこのパケットに適用される結果は、次で示す操作の結果となる。
New_Ds_field = ( Old_DS_field & mask ) | value |
よって新たな値は、 ds_field とマスク値の AND を取り、 続いてその結果を value パラメータと OR したものになる。 このプロセスを理解するには次の図を見てください。
skb->ihp->tos - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - > | | ^ | -- If you declare set_tc_index, we set DS | | <-----May change | value into skb->tc_index variable | |O DS field | A| |R +-|-+ +------+ +---+-+ Internal +-+ +---N|-----|----+ | | | | tc |--->| | |--> . . . -->| | | D| | | | | |----->|index |--->| | | Qdisc | |---->| v | | | | | |filter|--->| | | +---------------+ | ---->(mask,value) | -->| O | +------+ +-|-+--------------^----+ / | (. , .) | | | | ^ | | | | (. , .) | | | +----------|---------|----------------|-------|--+ (. , .) | | | sch_dsmark | | | | | +-|------------|---------|----------------|-------|------------------+ | | | <- tc_index -> | | | |(read) | may change | | <--------------Index to the | | | | | (mask,value) v | v v | pairs table - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -> skb->tc_index |
マーキングはどのように行うのでしょうか。 単に再マークしたいクラスのマスクと値を変えるだけです。 次のコードを見てください。
tc class change dev eth0 classid 1:1 dsmark mask 0x3 value 0xb8 |
これで、TC_INDEX フィルタの動作と、 これをどのように利用するかを説明しました。 なおまた、TC_INDEX フィルタは DS サービスに含まれているのとは 別の設定で用いることも可能です。
TC_INDEX フィルタを宣言する基本的なコマンドは次の通りです:
... tcindex [ hash SIZE ] [ mask MASK ] [ shift SHIFT ] [ pass_on | fall_through ] [ classid CLASSID ] [ police POLICE_SPEC ] |
次に、TC_INDEX の動作モードを説明する例を示します。 強調された単語に注意してください:
tc qdisc add dev eth0 handle 1:0 root dsmark indices 64 set_tc_index tc filter add dev eth0 parent 1:0 protocol ip prio 1 tcindex mask 0xfc shift 2 tc qdisc add dev eth0 parent 1:0 handle 2:0 cbq bandwidth 10Mbit cell 8 avpkt 1000 mpu 64 # EF トラフィッククラス tc class add dev eth0 parent 2:0 classid 2:1 cbq bandwidth 10Mbit rate 1500Kbit avpkt 1000 prio 1 bounded isolated allot 1514 weight 1 maxburst 10 # EF トラフック用の pfifo qdisc tc qdisc add dev eth0 parent 2:1 pfifo limit 5 tc filter add dev eth0 parent 2:0 protocol ip prio 1 handle 0x2e tcindex classid 2:1 pass_on |
まず、EF とマークされたパケットが到着したとしましょう。 RFC2598 を読むと、EF トラフィックに推奨される DSCP の値は 101110 です。 つまり DS フィールドは 10111000 (TOS バイトの最低位ビットは DS では用いられないことに注意)、 あるいは 16 進では 0xb8 となります。
TC INDEX FILTER +---+ +-------+ +---+-+ +------+ +-+ +-------+ | | | | | | | |FILTER| +-+ +-+ | | | | | |----->| MASK | -> | | | -> |HANDLE|->| | | | -> | | -> | | | | . | =0xfc | | | | |0x2E | | +----+ | | | | | | | . | | | | | +------+ +--------+ | | | | | | . | | | | | | | | | -->| | . | SHIFT | | | | | | | |--> | | . | =2 | | | +----------------------------+ | | | | | | | | | CBQ 2:0 | | | | | +-------+ +---+--------------------------------+ | | | | | | | +-------------------------------------------------------------+ | | DSMARK 1:0 | +-------------------------------------------------------------------------+ |
パケットが到着すると、DS フィールドには 0xb8 という値が設定されます。 先に説明した通り、1:0 という id の dsmark qdisc は、 DS フィールドを抽出して skb->tc_index 変数に保存します。 この例における次の段階は、この qdisc に関連付けられたフィルタに対応します (例の 2 行目)。これは次の操作を実行します。
Value1 = skb->tc_index & MASK Key = Value1 >> SHIFT |
この例では MASC=0xFC で SHIFT=2 です。
Value1 = 10111000 & 11111100 = 10111000 Key = 10111000 >> 2 = 00101110 -> 0x2E (16 進) |
返り値は qdisc の内部フィルタハンドル (この例では id が 2:0 のもの) に対応します。この id を持ったフィルタが実際に存在すれば、 制限条件と測定条件が (フィルタにこれらが含まれていれば) 調査され、 classid が返され (我々の例では 2:1)、 skb->tc_index 変数に保存されます。
しかしこの id を持つフィルタが見つからなければ、 結果は fall_through フラグの宣言に依存します。 宣言されていれば、value キーが class id として返されます。 宣言されていないとエラーが返され、プロセスは残りのフィルタへ続きます。 fall_through フラグの利用には注意が必要です。 skb->tc_index 変数の値と クラス id の間に単純な関係があれば、 これは行われてしまいます。
説明が必要な残りのパラメータは、hash と pass_on でしょうか。 hash はハッシュテーブルサイズに関連しています。 pass_on は、 このフィルタの結果と等しい class id が無い場合は次のフィルタを用いる、 ということを示すために用いられます。 デフォルトの動作は fall_through です (次表を参照)。
最後に、TCINDEX の各パラメータに設定できる値を一覧しておきましょう:
TC Name Value Default ----------------------------------------------------------------- Hash 1...0x10000 Implementation dependent Mask 0...0xffff 0xffff Shift 0...15 0 Fall through / Pass_on Flag Fall_through Classid Major:minor None Police ..... None |
このフィルタは非常に強力です。 あらゆる可能性を探るためには、この力が必要なのです。 ところで、このフィルタは DiffServ の設定でのみ用いられるわけではなく、 他の形式におけるフィルタとしても利用できます。
iproute2 配布に含まれている DiffServ の例をすべて見ておくことをお奨めします。 ここの文章も、できるだけ早く補完するつもりです。 ところで、ここで説明した内容は、多くのテストの結果です。 もしどこかで間違っていたら、指摘してくださると幸いです。