12.1. u32 クラス選別器

U32 フィルタは現在実装されている中では最も高度なフィルタです。 このフィルタ全体はハッシュテーブルに基づいており、 多くのフィルタルールがある時でも確実な動作をします。

最もシンプルな形では、U32 フィルタはレコードのリストであり、 各レコードは 2 つのフィールド、セレクタとアクションからなります。 セレクタは (以降で詳述しますが)、現在処理している IP パケットと比較され、 最初にマッチしたものに関連付けられたアクションが実行されます。 最もシンプルなアクションはパケットを定義済みのクラスに向けるものです。

フィルタを設定するには、コマンドラインから tc filter を実行します。 これは 3 つの部分からなります。フィルタ指定部、 セレクタ、アクションです。フィルタ指定部は次のように定義されます:

tc filter add dev IF [ protocol PROTO ]
                     [ (preference|priority) PRIO ]
                     [ parent CBQ ]

protocol フィールドはフィルタが適用されるプロトコルを示します。 以降では ip の場合についてのみ解説します。 preference フィールドは、 ここで定義するフィルタの優先度を指定します (priority も同じ意味で使えます)。 各フィルタはルールのリストという形を取りますが、 複数のフィルタをいろいろな優先度で設定できるので、 この数値は重要です。 各リストではルールが追加された順に渡され、 リスト全体は優先度の小さい (preference の数値が大きい) ものから処理されます。 parent フィールドは、フィルタの属する ツリーのトップ (例えば 1:0) を指定します。

これらのオプションは、U32 に限らず、すべてのフィルタに適用されます。

12.1.1. U32 セレクタ

U32 セレクタにはパターン定義が含まれており、 これが現在処理しているパケットにマッチします。 正確にはこれは、どのビットがパケットヘッダにマッチするかであって、 それ以上のものではありません。 しかしこの単純な方法は非常に強力です。 以降の例をご覧になって下さい。 これらは実際に存在している、極めて複雑なフィルタから直接採ったものです。

# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
  match u32 00100000 00ff0000 at 0 flowid 1:10

ここでは、最初の行は気にしないでください。 これらのパラメータは、すべてフィルタのハッシュテーブルを記述するものです。 match キーワードを含む、セレクタの行に注目しましょう。 このセレクタは、2 番目のバイトが 0x10 (0010) である IP ヘッダにマッチします。 ご想像の通り、次の 00ff という数値はマッチのマスクで、 どのビットが本当にマッチしなければならないかを指定します。 ここではマスクは 0xff なので、実際に 0x10 であるバイトのみがマッチします。 at キーワードは、 このマッチを始めるのが指定したオフセット (バイト単位) からであることを示しています。 この場合はパケットの先頭です。 以上を人間の言葉に翻訳すると、 このセレクタは Type of Service フィールドで 'low delay' ビットだけが立っているパケットにマッチします。 別の規則も解析してみましょう。

# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
  match u32 00000016 0000ffff at nexthdr+0 flowid 1:10

nexthdr オプションは、 この IP パケットにカプセルされている別のヘッダ (next ヘッダ)、 つまり上層プロトコルのヘッダを意味します。 このマッチングも、next ヘッダの先頭から開始されています。 マッチはそのヘッダの 2 番目の 32 ビットワードに対して行われます。 TCP と UDP 各プロトコルでは、 このフィールドにはパケットの送信先ポートが含まれます。 数値は big エンディアン形式 (大きい桁が先) で与えられています。 よって 0x0016 はそのまま 10 進数で 22 となりますから、 これが TCP なら SSH サービスを表しています。 はい、このマッチは前提がないと一意でないですね。 この点についてはまた後ほど説明します。

以上をご理解いただけていれば、次のセレクタも簡単に読めるでしょう 「match c0a80100 ffffff00 at 16」 ここでは IP ヘッダの先頭から 17 番目以降の 3 バイト分をマッチさせています。 これは送信先アドレスが 192.168.1/24 のネットワークのどこかになっているものです。 以上、いくつか例を解析したところで、 学んだことをまとめてみることにしましょう。

12.1.2. 汎用セレクタ (general selector)

汎用セレクタは、パターン、マスク、 パケットでそのパターンをマッチさせる部分のオフセット、を定義します。 この汎用セレクタを用いると、ほぼ IP ヘッダ (および上層のヘッダ) のどんなビットにもマッチが可能です。 ただしこれらは、次で説明する特殊セレクタに比べると、読み書きが面倒です。 汎用セレクタの文法は以下の通り:

match [ u32 | u16 | u8 ] PATTERN MASK [ at OFFSET | nexthdr+OFFSET]

キーワード u32, u16 u8 はどれかひとつを指定し、パターンの長さをビット単位で表します。 PATTERN と MASK は、このキーワードで指定した長さでなければなりません。 OFFSET パラメータはバイト単位のオフセットで、マッチ動作を始める位置です。 nexthdr+ キーワードが指定されると、 オフセットは上層ヘッダの先頭からの相対位置になります。

いくつか例を示します。 次の例では Time to Live (TTL) が 64 であるパケットがマッチします。 TTL フィールドは IP ヘッダの 8 番目のバイトの直後から始まります。

# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
     match u8 64 0xff at 8 \
     flowid 1:4

次の例は ACK ビットが設定されているすべての TCP パケットにマッチします:

# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
     match ip protocol 6 0xff \
     match u8 0x10 0xff at nexthdr+13 \
     flowid 1:3 

64 バイトより小さいパケットの ACK にマッチさせたいときは次を用いましょう:

## match acks the hard way,
## IP protocol 6,
## IP header length 0x5(32 bit words),
## IP Total length 0x34 (ACK + 12 bytes of TCP options)
## TCP ack set (bit 5, offset 33)
# tc filter add dev ppp14 parent 1:0 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:3

この規則は、ACK ビットがセットされていて、 その他にペイロードは持たないような TCP パケットにのみマッチします。 これは複数のセレクタを使う例になっています。 最終的な結果は、これらの結果の論理積になります。 TCP ヘッダのダイアグラムを見ると、ACK ビットは TCP ヘッダの 14 バイト目 (at nexthdr+13) の第二ビット (0x10) になっています。 最初のセレクタは、もし難しい方がお好みなら、 固有セレクタ (specific selector) を使って ip protocol 6 0xff と書く代わりに、 match u8 0x06 0xff at 9 とも書けます。 6 は TCP のプロトコル番号で、 これは IP ヘッダの 10 番目のバイトに書かれます。 一方逆にこの例では、四番目のマッチには固有セレクタを使っていません。 これは単に、TCP ACK ビットにマッチする固有セレクタが存在しないからです。

次のフィルタは、このフィルタの修正版です。 違いは ip ヘッダの長さをチェックしていない点です。 理由? 前述のヘッダは、32 ビットシステムでしか動作しないからです。

tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 \
     match ip protocol 6 0xff \
     match u8 0x10 0xff at nexthdr+13 \
     match u16 0x0000 0xffc0 at 2 \
     flowid 1:3

12.1.3. 固有セレクタ (specific selector)

次の表は、この節の著者が tc プログラムのソースコードから発見した、 すべての固有セレクタをリストしたものです。 これらは単にフィルタ設定の可読性を増し、暮らしを楽にするためのものです。

FIXME: 表はここに: 現在表は別ファイル "selector.html" にあります。

FIXME: まだこのファイルはポーランド語です :-(

FIXME: sgml 化も必要です。

いくつか例を示します:

# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
     match ip tos 0x10 0xff \
     flowid 1:4

FIXME: tcp dport のマッチは、以降で説明されているようには動作しません。

このルールは TOS フィールドが 0x10 になっているパケットにマッチします。 TOS フィールドはパケットの第二バイトから始まり、大きさは 1 バイトです。 よって等価な汎用セレクタは match u8 0x10 0xff at 1 と書けます。これは U32 フィルタの内部を垣間見せてくれます。 つまり固有なルールは常に汎用ルールに書き換えられ、 そしてカーネルのメモリにはこのかたちで保存されるのです。 ここから別の結論も導けます。 すなわち tcpudp はまったく同じで、 これが match tcp dport 53 0xffff という 1 つのセレクタだけでは、 このポートに送られた TCP パケットにマッチできない理由なのです (このポートに送られた UDP パケットにもマッチしてしまいます)。 同時にプロトコルも指定しなければならないことを覚えねばなりません。 結局最終的には次のようなルールになります。

# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
        match tcp dport 53 0xffff \
        match ip protocol 0x6 0xff \
        flowid 1:2