# Linux Advanced Routing & Traffic Control HOWTO

Bert
Hubert
Netherlabs BV

    bert.hubert@netherlabs.nl

Thomas Graf (Section Author)

    tgraf%suug.ch

Gregory Maxwell (Section Author)
Remco van Mook (Section Author)

    remco@virtu.nl

Martijn van Oosterhout (Section Author)

    kleptog@cupid.suninternet.com

Paul B Schroeder (Section Author)

    paulsch@us.ibm.com

Jasper Spaans (Section Author)

    jasper@spaans.ds9a.nl

Pedro Larroy (Section Author)

    piotr%member.fsf.org

中野
武雄
日本語訳

    nakano@apm.seikei.ac.jp

Revision: 1.41j1
Date: 2003/10/19
日本語翻訳版
$Revision: 1.41 $
$Date: 2003/10/12 15:51:04 $
DocBook Edition

iproute2、 トラフィック制御、および多少の netfilter に関する、極めて実践的なアプローチ。

## 献辞

この文書は多くの人に捧げるもので、 著者はこれによって何らかのお返しをしたいと考えています。 なかでも何人かの方のお名前を挙げれば:

- Rusty Russell

- Alexey N. Kuznetsov

- The good folks from Google

- The staff of Casema Internet

## はじめに

ようこそ、親愛なる読者のみなさん。

この文書は Linux 2.2/2.4 を用いて、 より上級のルーティング (経路操作) を行う方法について 述べようとするものです。 ほとんどのユーザには知られていないことですが、 スペクタクルな作業を行うツールを、あなたは既に実行しているのです。 実は `route` や `ifconfig` などは、 非常にパワフルな iproute2 というインフラストラクチャの、 ごく一部のみを使うラッパにすぎないのです。

私はこの HOWTO が、netfilter で (もちろんほかででも) 有名な Rusty Russell の HOWTO と同じくらい読み易いものであるよう、望んでいます。

HOWTO team に送信すれば、いつでも我々とコンタクトを取れます。 しかし、その質問がこの HOWTO に直接関連するものでなければ、 できればメーリングリスト (関連する節を見てください) に送ってください。 我々は無料のヘルプデスクではありませんが、 メーリングリストにおける質問には答えることが多いはずです。

この HOWTO で迷子になる前に一言。 もし簡単な帯域制限 (traffic shaping) を行いたいだけならば、 すべて飛ばして の章に向かってください。 そして CBQ.init に関する記述を読んでください。

### 免責およびライセンス

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

In short, if your STM-64 backbone breaks down and distributes pornography to your most esteemed customers - it's never our fault. Sorry.

Copyright (c) 2002 by bert hubert, Gregory Maxwell, Martijn van Oosterhout, Remco van Mook, Paul B. Schroeder and others. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/).

Please freely copy and distribute (sell or give away) this document in any format. It's requested that corrections and/or comments be forwarded to the document maintainer.

It is also requested that if you publish this HOWTO in hardcopy that you send the authors some samples for review purposes :-)

#### 日本語訳 (参考)

この文書は有益であることを願って配布されていますが、 一切の保証はありません。商用・特定用途への適合に関する、 黙示的な保証も一切ありません。

要するに、もしあなたの STM-64 バックボーンが壊れて落ちたり、 大事な顧客にポルノをばらまいてしまったとしても - それは私たちの 責任ではありません。ごめんなさい。

Copyright (c) 2002 by bert hubert, Gregory Maxwell, Martijn van Oosterhout, Remco van Mook, Paul B. Schroeder ほか。 この文書は Open Publication License, v1.0 およびそれ以降 に示されている項目と条件に従った場合に限り、配布できます (OPL の最新版は http://www.opencontent.org/openpub/ にあります)。

この文書の複写や再配布 (販売・景品) は、どのようなフォーマットでも可能です。 ただし修正やコメントが、この文書の管理者に送られるようにしてください。

またもしこの HOWTO を書籍として出版する場合は、 レビューのために、著者らに数冊のサンプルを送ってください :-)

### 事前に必要な知識

タイトルにあるように、これは Advanced な HOWTO です。 ロケット科学ほどではありませんが、 ある程度の事前知識はすでにあるものとしています。

必要な知識を得るための参考文献をいくつかあげておきます。

Rusty Russell の networking-concepts-HOWTO

  非常に良い導入文書で、ネットワークとは何か、 他のネットワークと接続するにはどうするかを説明しています。

Linux Networking-HOWTO (以前は Net-3 HOWTO)

  膨大な分量で、かつまた同時に非常に詳細です。 インターネットへの接続に関する設定 (そのほとんどは既に設定済みなのでしょうが) に関して、たくさんのことを教えてくれます。 `/usr/doc/HOWTO/NET3-4-HOWTO.txt` にあることが多いと思いますが、 オンライン でも読めます。

### Linux にできること

実現可能なことを簡単なリストにしてみました。

- 特定のコンピュータに対するバンド幅を絞る

- 特定のコンピュータに「向かう」バンド幅を絞る

- バンド幅を公平に共有するための手助けをする

- ネットワークを DoS 攻撃から守る

- インターネットをあなたの顧客から守る

- 複数あるサーバをひとつにまとめ、負荷分散や可用性の向上を実現する

- あなたのコンピュータへのアクセスを制限する

- あなたのユーザが行う他のホストへのアクセスを制限する

- 次のような情報に基づいてルーティングを行う: ユーザ id (本当)、MAC アドレス、 発信元 IP アドレス、ポート、サービスタイプ、時刻、 パケットの中味など

現在のところ、これらの先進的な機能を使っている人はあまり多くありません。 これにはいくつかの理由があります。 提供されている文書の記述は詳細ですが、あまり実践的なものではありません。 トラフィック制御はほとんど文書化されていません。

### この文書の管理についてのメモ

この文書に関して、いくつか気にとめていてほしいことがあります。 私はこの文書の大部分を書きましたが、 今後もそのようにし続けたいとは決して思っていません。 私はオープンソースの強い信奉者であり、 したがって皆さんがフィードバック、アップデート、パッチなどを 送ってくださることを、つねに歓迎します。 誤字脱字やよくある単純な間違いでも、遠慮せずに知らせてください。 私の英語がちょっとぎごちなく感じられるようでしたら、 どうか私がネイティブではないことを思い出してください。 気軽に提案を送って下さい。

ある部分のメンテナンスをより良く行う資格が自分にあると感じられたり、 あるいは新しい部分を書いてメンテナンスできると思われたら、 大いに歓迎します。この HOWTO の SGML は CVS で管理されています。 私はもっとたくさんの人たちがこの文書に関する作業を行うような将来を、 強く願っています。

この助けとなるよう、この文書には FIXME という注意書きがたくさん置かれています。 パッチはつねに歓迎します! FIXME を見つけたら、 未知の領域を歩いているのだと思ってください。 これは他の部分に間違いがないという意味ではありませんが、 そのようなところでは特に注意してください。 もしどれかの内容を確認することができたら、 私たちに知らせてください。その FIXME を除去します。

この HOWTO で、私は勝手な振る舞いをしていることもあります。 例えば、私は 10Mbit のインターネット接続を前提としていますが、 これがそれほどよくある状況では無いことはよーくわかっています。

### 取得、CVS およびアップデートの投稿

この文書の原本の置き場所は こちら です。

現在、全世界からの匿名 CVS アクセスが可能になっています。 これにはいろいろな面で利点があります。 この HOWTO のより新しい版への更新が簡単ですし、 パッチの投稿にもまったく面倒がありません。

さらに、著者達がソースに対して独立に作業できるのも良いところです。

    $ export CVSROOT=:pserver:anon@outpost.ds9a.nl:/var/cvsroot
    $ cvs login
    CVS password: [enter 'cvs' (without 's)]
    $ cvs co 2.4routing
    cvs server: Updating 2.4routing
    U 2.4routing/lartc.db

変更を行い、それらを提供したいと思われましたら、 `cvs -z3 diff -uBb` を実行し、その出力を howto@ds9a.nl にメールしてください。 こうすると、我々は楽に反映作業ができます。よろしく。 どうか編集は必ず .db ファイルに対して行ってください。 ちなみに他のファイルは、この .db ファイルから生成されたものです。

Makefile を用いると、postscript, dvi, pdf, html, プレインテキストの生成が楽にできます。 これらのフォーマットすべてを生成するには、 docbook, docbook-utils, ghostscript, tetex のインストールが必要です。

2.4routing.sgml は編集しないようにご注意ください! この中味は本 HOWTO の古い版です。 正しいファイルは lartc.db です。

### メーリングリスト

筆者たちが受け取る、この HOWTO に関するメールは、 だんだん増えてきています。コミュニティからの興味が明らかになったので、 メーリングリストを開始して、Advanced ルーティングと トラフィック制御に関する議論を皆でできるようにしました。 リストを購読するのは こちら からできます。

申し訳ありませんが、 著者達はリスト以外で尋ねられた質問に対しては、 ほとんど回答することがありません。私たちはリストのアーカイブを、 ある種の知識データベースにしたいと考えているのです。 質問がありましたら、まずアーカイブを検索し、 そしてメーリングリストに投稿してください。

### この文書の構成

我々は自分たちが面白いと思った内容はほぼ直ちに作業しますが、 これは逆に、最初は説明が不十分だったり、 完成していない部分があるということでもあります。 すみませんがそのようなところは目をつぶっていただき、 そのうちすべてが明らかになる、と思っておいてください。

ルーティングとフィルタリングは 2 つの別々のことです。 フィルタリングは Rusty の HOWTO で非常に良く説明されています。 次の場所にあります。

- Rusty's Remarkably Unreliable Guides

私たちは、 netfilter と iproute2 を組み合わせて可能になることについて 集中するつもりです。

## iproute2 入門

### なぜ iproute2 なのか?

ほとんどの Linux ディストリビューション (およびほとんどの UNIX) では、伝統ある `arp`, `ifconfig`, `route` といったコマンドを現在でも用いています。 これらのツールは動作はするものの、 Linux 2.2 以降ではときどき期待通りの働きをしないことがあります。 例えば、GRE トンネルは今日ではルーティングの一部となっていますが、 これには完全に別のツールが必要です。

iproute2 を用いれば、 トンネルの部分もツールセットに組み込まれます。

2.2 以降の Linux カーネルには、 完全に再設計されたネットワークサブシステムが含まれています。 この新しいネットワークコードによって、 Linux には性能と機能との (特に機能に関しては一般の OS のなかにほとんど競争者がいないくらいの) 向上がもたらされました。 実際のところ、新しいルーティング・フィルタリング・クラス付けのコードは、 ルータやファイアウォール、 帯域制限などの専用製品の多くに比べても、 より高機能になっています。

ネットワーキングに関する新たな概念が発明されるにつれ、 それらは既存の OS の既存のフレームワークの上に塗り固められてきました。 このようなシステムの多層化が進むことによって、 ネットワーキングコードは (人間の言語の多くに非常によく似た) たくさんの奇妙な振る舞いだらけになってしまいました。 過去には、Linux は SunOS の処理の多くをエミュレートしてきましたが、 これは理想的なこととは言えませんでした。

この新しいフレームワークは、 以前の Linux が及ばなかった機能を、 明確に表現することを可能としたのです。

### iproute2 の概略

Linux はバンド幅の供給に関して洗練されたシステムを備えており、 これは Traffic Control と呼ばれています。 このシステムは、クラス分け・優先付け、共有化、制限といった 様々な手法を、内向き・外向きのトラフィック両方でサポートしています。

では、iproute2 の可能性を手短にご紹介することにしましょう。

### 事前の必要条件

まずユーザランドのツールがインストールされていることを確認する必要があります。 このパッケージは RedHat と Debian の両者では 'iproute' という名前になっています。 それ以外の場合でも `ftp://ftp.inr.ac.ru/ip-routing/iproute2-2.2.4-now-ss??????.tar.gz"` から取得できます。

最新版を入手するには このリンク も利用できます。

iproute の一部には、 適切なカーネルオプションを有効にしなければ使えないものもあります。 なお RedHat のリリース 6.2 以前のカーネルでは、 トラフィック制御機能がデフォルトのカーネルには組み込まれていません。

RedHat 7.2 には、すべてがデフォルトで組み込まれています。

また自分でカーネルを用意する場合には、 netlink サポートが含まれていることを確認してください。 iproute2 ではこれが必要です。

### 現在の設定を調べてみる

驚かれるかもしれませんが、iproute2 は既に設定済みなのです! 現状のコマンドである `ifconfig` と `route` も、 既に advanced なシステムコールを用いているのです。 ただし非常に基本的な (退屈な) 設定のみですが。

中心となるコマンドは `ip` です。 ではこのコマンドに、我々のインターフェースの状態を表示させてみましょう。

#### `ip` にリンクを表示させる

    [ahu@home ahu]$ ip link list
    1: lo: <LOOPBACK,UP> mtu 3924 qdisc noqueue
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    2: dummy: <BROADCAST,NOARP> mtu 1500 qdisc noop
        link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    3: eth0: <BROADCAST,MULTICAST,PROMISC,UP> mtu 1400 qdisc pfifo_fast qlen 100
        link/ether 48:54:e8:2a:47:16 brd ff:ff:ff:ff:ff:ff
    4: eth1: <BROADCAST,MULTICAST,PROMISC,UP> mtu 1500 qdisc pfifo_fast qlen 100
        link/ether 00:e0:4c:39:24:78 brd ff:ff:ff:ff:ff:ff
    3764: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP> mtu 1492 qdisc pfifo_fast qlen 10
        link/ppp

環境によって表示は異なりますが、 私の自宅の NAT ルータで `ip` が表示したのは このような内容でした。ここではすべてが直接関係しているわけではないので、 一部のみを説明します。

まず loopback インターフェースがあります。 これがなくても動作しているコンピュータもあるかもしれませんが、 そのような状況はまずい、というのが私のアドバイスです。 MTU サイズ (最大転送単位) は 3924 オクテットで、 キューイングは行いません。 loopback インターフェースはカーネルによる一種の虚構ですから、 これは当然です。

いまは dummy インターフェースは飛ばします。 これはあなたのコンピュータにはないでしょうから。 続いて物理的なネットワークインターフェースが 2 つあります。 ひとつはケーブルモデムの側に、 もう一つは自宅イーサネットセグメントの側にあります。 さらに、ppp0 インターフェースがあるのもわかります。

IP アドレスが表示されていないことに気づかれたでしょうか。 iproute は「リンク」と「IP アドレス」という二つの概念を切り離しています。 IP エイリアスを用いると、「特定の」IP アドレスという概念は、 どのみち極めて不適切なものになってしまいます。

そのかわり MAC アドレスは表示されています。 これはイーサネットインターフェースのハードウェア識別子です。

#### `ip` に IP アドレスを表示させる

    [ahu@home ahu]$ ip address show
    1: lo: <LOOPBACK,UP> mtu 3924 qdisc noqueue
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 brd 127.255.255.255 scope host lo
    2: dummy: <BROADCAST,NOARP> mtu 1500 qdisc noop
        link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    3: eth0: <BROADCAST,MULTICAST,PROMISC,UP> mtu 1400 qdisc pfifo_fast qlen 100
        link/ether 48:54:e8:2a:47:16 brd ff:ff:ff:ff:ff:ff
        inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
    4: eth1: <BROADCAST,MULTICAST,PROMISC,UP> mtu 1500 qdisc pfifo_fast qlen 100
        link/ether 00:e0:4c:39:24:78 brd ff:ff:ff:ff:ff:ff
    3764: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP> mtu 1492 qdisc pfifo_fast qlen 10
        link/ppp
        inet 212.64.94.251 peer 212.64.94.1/32 scope global ppp0

今回は情報が多くなりました。すべてのアドレスと、 それがどのカードに属しているかが表示されています。'inet' は Internet (IPv4) の意味です。ほかにもたくさんのアドレスファミリーがありますが、 現在のところはまだ出てきていません。

eth0 をもうちょっと詳しく調べてみましょう。 これには inet アドレス '10.0.0.1/8' が関連付けられています。 これは何を意味するのでしょう? /8 はネットワークアドレス部の ビット数を表しています。全部で 32 ビットありますから、 残りの 24 ビットがネットワークの内部で使えるわけです。 10.0.0.1 の最初の 8 ビットは 10.0.0.0 となりますので、 これが我々のネットワークアドレスです。また我々のネットマスクは 255.0.0.0 です。

他のビットはこのインターフェースと接続しています。 例えば 10.250.3.13 には、 (現在 10.0.0.1 である) eth0 から直接に接続できます。

ppp0 にも同じ考えが適用できますが、数値が違います。 ppp0 のアドレスは 212.64.94.251 で、サブネットマスクはありません。 これは point-to-point 接続であることを意味しており、 212.64.94.251 を除いては、すべてのアドレスがリモートです。 ただし、他にも情報があります。この表示によれば、接続の他端には、 (またもや) 単独のアドレス 212.64.94.1 のみがあります。この /32 は、 ネットワークビットが一切存在しないことを意味しています。

これらの概念を把握することは、決定的に重要です。もし問題があるようなら、 この HOWTO の最初のほうで紹介した文書を参照して下さい。

さらに 'qdisc' にも気づいたでしょうか。これは Queueing Discipline を表しています。これも後ほど非常に重要となります。

#### `ip` に経路を表示させる

さて、これで 10.x.y.z というアドレスの探し方はわかりました。 また 212.64.94.1 に到達することもできます。 しかしこれだけでは十分ではありません。全世界へと到達するための方法を 知る必要があります。インターネットには ppp 接続経由でつながっており、 よって 212.64.94.1 は我々から世界に向けたパケットを送る先で、 またその結果を私たちのところに戻してくる場所であることがわかります。

    [ahu@home ahu]$ ip route show
    212.64.94.1 dev ppp0  proto kernel  scope link  src 212.64.94.251
    10.0.0.0/8 dev eth0  proto kernel  scope link  src 10.0.0.1
    127.0.0.0/8 dev lo  scope link
    default via 212.64.94.1 dev ppp0

これはほぼ見ただけでわかるでしょう。 最初の 3 行は、既に `ip address show` によって示された状態を表示しています。 最後の行は、残りの世界が 212.64.94.1 の先に見つかること、 これが我々のデフォルトゲートウェイであることを示しています。 これがまさに「玄関口」であることは、言葉からもわかります。 すなわち我々は 212.64.94.1 にパケットを送れば、 あとの残りはそちらで面倒を見てもらえるのです。

参照のために、古い `route` ユーティリティの結果もお見せしておきましょう。

    [ahu@home ahu]$ route -n
    Kernel IP routing table
    Destination     Gateway         Genmask         Flags Metric Ref    Use
    Iface
    212.64.94.1     0.0.0.0         255.255.255.255 UH    0      0        0 ppp0
    10.0.0.0        0.0.0.0         255.0.0.0       U     0      0        0 eth0
    127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo
    0.0.0.0         212.64.94.1     0.0.0.0         UG    0      0        0 ppp0

### ARP

ARP はアドレス解決プロトコル (Address Resolution Protocol) の意味で、 RFC 826 に記述されています。ARP はネットワークにあるマシンが、 同じローカルネットワークにある他のマシンのハードウェア位置 (アドレス) を解決するために用います。インターネットにあるマシンは、 一般に名前で知られており、この名前が IP アドレスへと解決されます。 これが、foo.com ネットワークにあるマシンが bar.net ネットワークにある他のマシンと通信できる理由です。 しかし IP アドレスは、マシンの物理的な位置を伝えてはくれません。 そこで ARP が登場するわけです。

非常に簡単な例を取り上げましょう。いくつかのマシンからなるネットワークを 想像してください。いま私のネットワークにある 2 台のマシンが、 IP アドレス 10.0.0.1 の foo と IP アドレス 10.0.0.2 の bar であるとします。 いま foo が、bar が生きているかどうか ping で確かめたいと思いました。 しかし残念なことに、foo は どこに bar がいるかがわかりません。 よって foo は bar に ping するまえに、ARP 要求を送る必要があります。 この ARP 要求は、「bar (10.0.0.2)! どこにいるんだ?」と foo が叫ぶようなものです。この結果ネットワークのすべてのマシンは foo が叫んでいるのを聞きますが、bar (10.0.0.2) のみがこれに応えます。 bar はこのとき ARP 返信を直接 foo に返します。これは 「foo (10.0.0.1)、私はここ 00:60:94:E9:08:12 にいるよ」 と bar が言うのに似ています。この簡単な、 友達がネットワークのどこにいるかを知るためのやり取りの後に、 foo は bar と通信できるようになります。 これは foo (foo の arp キャッシュ) が bar の場所を忘れるまで (通常 Unix では 15 分) 有効です。

では、実際にどのように動作するかを見てみましょう。 あなたのマシンの現在の arp/近隣 キャッシュ/テーブル は、次のようにしてみることができます。

    [root@espa041 /home/src/iputils]# ip neigh show
    9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
    9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud reachable

みてわかるとおり私のマシン espa041 (9.3.76.41) は、 espa042 (9.3.76.42) および espagate (9.3.76.1) の位置を知っています。ここで別のマシンを arp キャッシュに追加してみましょう。

    [root@espa041 /home/paulsch/.gnome-desktop]# ping -c 1 espa043
    PING espa043.austin.ibm.com (9.3.76.43) from 9.3.76.41 : 56(84) bytes of data.
    64 bytes from 9.3.76.43: icmp_seq=0 ttl=255 time=0.9 ms

    --- espa043.austin.ibm.com ping statistics ---
    1 packets transmitted, 1 packets received, 0% packet loss
    round-trip min/avg/max = 0.9/0.9/0.9 ms

    [root@espa041 /home/src/iputils]# ip neigh show
    9.3.76.43 dev eth0 lladdr 00:06:29:21:80:20 nud reachable
    9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
    9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud reachable

espa041 が espa043 に接続を試みた結果、 espa043 のハードウェアアドレス/位置が arp/近隣 キャッシュに追加されました。 よって espa043 のエントリが (その間 2 台の間に通信がなく) 時間切れになるまで、 espa041 は espa043 の場所を知っており、ARP 要求を送る必要はありません。

では espa043 を arp キャッシュから削除してみましょう。

    [root@espa041 /home/src/iputils]# ip neigh delete 9.3.76.43 dev eth0
    [root@espa041 /home/src/iputils]# ip neigh show
    9.3.76.43 dev eth0  nud failed
    9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
    9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud stale

これで espa041 は再び espa043 がどこにあるかを忘れてしまったので、 次に espa043 と通信しようと思ったときには再度 ARP 要求を送る必要があります。 上記の出力には、espagate (9.3.76.1) が "stale" 状態に変わったことも示されています。これが意味するのは、 位置情報はまだ有効であるものの、 そのマシンと最初にやり取りをする際には確認が必要である、ということです。

## Rules - ルーティングポリシーデータベース

大規模なルータを管理していると、利用者それぞれに応じて、 違ったやり方でサービスを提供しなければならないことがよくあります。 ルーティングポリシーデータベースで複数のルーティングテーブルセットを 用いると、これが可能となります。

この機能を利用したい場合は、カーネルのコンパイルの際に "IP: advanced router" と "IP: policy routing" の各機能を 有効にする必要があります。

カーネルはルーティングの判断をする際に、 どのテーブルにあたるべきかをまず決定します。 デフォルトでは 3 つのテーブルがあります。 古い 'route' ツールは、main テーブルと local テーブルを変更します。 デフォルトでは ip ツールも同様です。

デフォルトのルールは:

    [ahu@home ahu]$ ip rule list
    0:	from all lookup local
    32766:	from all lookup main
    32767:	from all lookup default

これはすべてのルールの優先度の一覧です。 すべてのルールがすべてのパケット ('from all') に適用されることがわかります。 以前にも 'main' テーブルは見ています (`ip route ls` で出力されたものです)。 しかし 'local' テーブルと 'default' テーブルは今回が初めてです。

凝ったことをするには、別のテーブルを指すルールを生成します。 これによってシステムワイドのルーティング規則を上書きできるのです。

複数のマッチルールがあるときにカーネルがどのように動作するかに関する 正確な仕組みについては、Alexey の ip-cref 文書を見てください。

### 簡単なソースポリシールーティング

ここでまた実際の例を見てみることにしましょう。 私は 2 台 (実際にはそろそろ返さないといけないのも含めると 3 台) の ケーブルモデムを持っていて、それらは Linux の NAT (マスカレード) ルータにつながっています。ここに住んでいる人達は、 インターネット接続の対価を私に支払っています。 私の同居人の一人が、hotmail しか見に行かないので額を減らしてほしい、 と言ってきたとしましょう。私に異存はありませんが、 彼らにはローエンドの方のケーブルモデムを使ってもらうことにします。

「速い」ケーブルモデムは 212.64.94.251 で、 212.64.94.1 に PPP 接続されています。 「遅い」ケーブルモデムはいろいろな IP アドレスを取りますが、 この例では 212.64.78.148 とし、195.96.98.253 に接続されているとします。

local テーブルは次の通り:

    [ahu@home ahu]$ ip route list table local
    broadcast 127.255.255.255 dev lo  proto kernel  scope link  src 127.0.0.1
    local 10.0.0.1 dev eth0  proto kernel  scope host  src 10.0.0.1
    broadcast 10.0.0.0 dev eth0  proto kernel  scope link  src 10.0.0.1
    local 212.64.94.251 dev ppp0  proto kernel  scope host  src 212.64.94.251
    broadcast 10.255.255.255 dev eth0  proto kernel  scope link  src 10.0.0.1
    broadcast 127.0.0.0 dev lo  proto kernel  scope link  src 127.0.0.1
    local 212.64.78.148 dev ppp2  proto kernel  scope host  src 212.64.78.148
    local 127.0.0.1 dev lo  proto kernel  scope host  src 127.0.0.1
    local 127.0.0.0/8 dev lo  proto kernel  scope host  src 127.0.0.1

自明ですが、どこかで指定しておかなければならない内容です。 で、それがこれらです。default テーブルは空です。

では 'main' テーブルをみましょう:

    [ahu@home ahu]$ ip route list table main
    195.96.98.253 dev ppp2  proto kernel  scope link  src 212.64.78.148
    212.64.94.1 dev ppp0  proto kernel  scope link  src 212.64.94.251
    10.0.0.0/8 dev eth0  proto kernel  scope link  src 10.0.0.1
    127.0.0.0/8 dev lo  scope link
    default via 212.64.94.1 dev ppp0

ここで 'John' という新しいテーブルを、さきほど仮定した同居人のために作ります。 数値だけで設定を行うこともできますが、このテーブルを /etc/iproute2/rt_tables に追加するほうがずっと楽になります。

    # echo 200 John >> /etc/iproute2/rt_tables
    # ip rule add from 10.0.0.10 table John
    # ip rule ls
    0:	from all lookup local
    32765:	from 10.0.0.10 lookup John
    32766:	from all lookup main
    32767:	from all lookup default

これであと必要なのは John のテーブルを作って、 ルートキャッシュをフラッシュ (クリア) するだけです。

    # ip route add default via 195.96.98.253 dev ppp2 table John
    # ip route flush cache

これで完了です。これを ip-up に対して実装するのは、 読者の課題としておきましょう。

### 複数のアップリンク/プロバイダに対するルーティング

以下のようなよくある場合を考えてみましょう。 ローカルなネットワーク (1 台のマシンかもしれませんが) をインターネットへと接続しているプロバイダが 2 つあるような場合です。

                                                                     ________
                                              +------------+        /
                                              |            |       |
                                +-------------+ Provider 1 +-------
            __                  |             |            |     /
        ___/  \_         +------+-------+     +------------+    |
      _/        \__      |     if1      |                      /
     /             \     |              |                      |
    | Local network -----+ Linux router |                      |     Internet
     \_           __/    |              |                      |
       \__     __/       |     if2      |                      \
          \___/          +------+-------+     +------------+    |
                                |             |            |     \
                                +-------------+ Provider 2 +-------
                                              |            |       |
                                              +------------+        \________

この設定においては、通常 2 つの問題点があります。

#### アクセス分割

最初の問題点は、特定のプロバイダからやって来たパケットに対して、 応答をどのようにルーティングするかです。 例えば Provider 1 からのパケットに対して、 返信を再び同じプロバイダに戻すにはどうすればいいでしょう。

まず記号的な名前を設定しましょう。 `$IF1` を第一インターフェース (上記の絵の if1)、 `$IF2` を第二インターフェースとします。次に `$IP1` は `$IF1` に関連付けられた IP アドレス、 `$IP2` は `$IF2` に関連付けられた IP アドレスとします。 さらに、 `$P1` を Provider 1 のゲートウェイの IP アドレス、 `$P2` を Provider 2 のゲートウェイの IP アドレスとします。 最後に、`$P1_NET` を `$P1` の所属する IP ネットワーク、 `$P2_NET` を `$P2` の所属する IP ネットワークとします。

2 つのルーティングテーブルを追加します。 ここでは `T1` および `T2` とします。 これらは /etc/iproute2/rt_tables に追加します。 そしてこれらのテーブルでのルーティングを次のように設定します。

    	  ip route add $P1_NET dev $IF1 src $IP1 table T1
    	  ip route add default via $P1 table T1
    	  ip route add $P2_NET dev $IF2 src $IP2 table T2
    	  ip route add default via $P2 table T2

特別なことは何もしておらず、ゲートウェイへの経路と、 そのゲートウェイを経由する default 経路を作っただけです。 これは上流のプロバイダが 1 つだけの場合と同じですが、 経路をプロバイダごとに別々のテーブルに置いています。 ここではネットワークの経路だけで十分です。 なぜならネットワークを指定すれば、そこにあるすべてのホストに到達でき、 前述のようにゲートウェイもそのうちのひとつだからです。

次に main ルーティングテーブルを設定します。 パケットを直接の近隣に送る際には、 その近隣に接続しているインターフェースからにするのが良い考えです。 ここで `src' 引数によって、 確実に正しい起点 IP アドレスが選択されていることにも注目してください。

    	    ip route add $P1_NET dev $IF1 src $IP1
    	    ip route add $P2_NET dev $IF2 src $IP2

ここで default ルートを好きな方に設定します。

    	    ip route add default via $P1

次にルーティングの rule を設定します。 これらは実際には、どのルーティングテーブルを使うかの選択です。 既にソースアドレスが付けられている場合には、 そのインターフェースから出ていくように経路を選びたいはずです。

    	    ip rule add from $IP1 table T1
    	    ip rule add from $IP2 table T2

この一連のコマンドによって、 特定のインターフェースからやって来たトラフィックが、 必ずそのインターフェースを通して応答されることが確実となりました。

読者である Rod Roark によると: 「$P0_NET がローカルネットワークで $IF0 をそのインターフェースとすると、 次のようなエントリも追加しておくほうが良いでしょう

    ip route add $P0_NET     dev $IF0 table T1
    ip route add $P2_NET     dev $IF2 table T1
    ip route add 127.0.0.0/8 dev lo   table T1
    ip route add $P0_NET     dev $IF0 table T2
    ip route add $P1_NET     dev $IF1 table T2
    ip route add 127.0.0.0/8 dev lo   table T2

」

さて、これは非常に基本的な設定にすぎません。 これはルータで動作しているプロセスと、 マスカレードされている場合にはローカルネットワークに対しても動作するでしょう。 それ以外としては、両方のプロバイダから IP 空間をもらっている場合と、 片方のプロバイダに対してのみマスカレードをしている場合とが考えられます。 このいずれにおいても、 どちらのプロバイダを通して発信するかを、 ローカルネットワークのマシンの IP アドレスから決めるような ルールを追加することになるでしょう。

#### 負荷分散

二番目の問題点は、二つのプロバイダを通して出て行くトラフィックを バランスさせるやり方です。これは実際には、 前述のようなアクセス分割を既に行っていれば、難しくありません。

default の経路として片方のプロバイダを選ぶ代わりに、 default 経路を multipath 経路として設定します。 デフォルトのカーネルでは、これで両者のプロバイダへの経路がバランスされます。 これは次のようにして行います (ここでもアクセス分割の節における例での場合を考えます)。

    	    ip route add default scope global nexthop via $P1 dev $IF1 weight 1 \
    	    nexthop via $P2 dev $IF2 weight 1

これで両方のプロバイダへの経路がバランスされます。 `weight` パラメータは、 一方のプロバイダをもう一方より優先するよう調節します。

このバランス化は経路に基づいたもので、経路はキャッシュされるため、 完全とは言えません。なぜかというと、良く利用されるサイトに対する経路は、 つねに同じプロバイダから出て行くことになるからです。

さらに、本当にこれを実行した場合は、 Julian Anastasov のページ にあるパッチをみるとよいでしょう。 これらを用いると、より良好な動作が期待できます。

## GRE トンネル、その他のトンネル

Linux では 3 種類のトンネルを利用できます。 IP in IP トンネル、GRE トンネル、 そしてカーネル外でのトンネル (例えば PPTP のようなもの) です。

### トンネルに関する一般的な事柄

トンネルを用いると、非常に例外的な、また非常にスマートな状況を実現できます。 またトンネルは、設定を正しく行わないと、状況をメチャメチャにすることもあります。 意図していることを「正確に」理解していない限り、 default 経路をトンネルデバイスに向けてはいけません :-) また、トンネルを通すとオーバーヘッドが増えます。 なぜならトンネルでは IP ヘッダに余分なデータセットを必要とするからです。 通常これはパケットあたり 20 バイトなので、 仮にネットワークの通常のパケットサイズ (MTU) を 1500 バイトとすれば、 トンネルを通して送られるパケットは 1480 バイトの大きさしか持たないことになります。 これは必ずしも問題になるとは限りませんが、 大きなネットワークをトンネルで接続しようとする際には、 IP パケットのフラグメント化/再構成について、よく勉強しておくことです。 あ、それからもちろん、トンネルを掘る最速の方法は、 両側から掘り進めることです。

### IP in IP トンネリング

Linux では、このトンネルの利用はかなり昔から可能でした。 2 つのカーネルモジュール、ipip.o と new_tunnel.o が必要です。

いま 3 つのネットワークがあるとしましょう。 内部ネットワーク A と B、そしてその中間にあるネットワーク C (あるいはインターネット) です。 ネットワーク A は:

    network 10.0.1.0
    netmask 255.255.255.0
    router  10.0.1.1

このルータのネットワーク C 側のアドレスは 172.16.17.18 です。

ネットワーク B では:

    network 10.0.2.0
    netmask 255.255.255.0
    router  10.0.2.1

このルータのネットワーク C 側のアドレスは 172.19.20.21 です。

ここではネットワーク C は、 A から B へのパケット (およびその逆) をすべて通すとします。 ここにはインターネットを利用することもできます。

では作業に入ります:

まず最初にモジュールをインストールします。

    modprobe ipip.o
    modprobe new_tunnel.o

続いてネットワーク A のルータで次の内容を実行します:

    ifconfig tunl0 10.0.1.1 pointopoint 172.19.20.21
    route add -net 10.0.2.0 netmask 255.255.255.0 dev tunl0

そしてネットワーク B のルータでは:

    ifconfig tunl0 10.0.2.1 pointopoint 172.16.17.18
    route add -net 10.0.1.0 netmask 255.255.255.0 dev tunl0

トンネルの利用を終わるには:

    ifconfig tunl0 down

あっという間に終わりました。ただし、ブロードキャストや IPv6 のトラフィックを IP-in-IP トンネルを通してフォワードすることはできません。 単に通常では通信ができない 2 つの IPv4 ネットワークを接続しただけ、です。 互換性に関して言いますと、このコードは長いこと存在しているため、 ずっと昔の 1.3 カーネルくらいまでは互換です。しかし私が知る限り、 Linux の IP-in-IP トンネルは他の OS やルータと一緒には動作しません。 これはシンプルで、ちゃんと働きます。これでなければならない理由があるなら 使えばいいですし、そうでなければ GRE を使うといいでしょう。

### GRE トンネリング

GRE は元々 Cisco によって開発されたトンネリングプロトコルで、 IP-in-IP トンネリングよりはもうちょっと高機能です。 例えばマルチキャストトラフィックや IPv6 も、GRE トンネルを通して転送できます。

Linux では、ip_gre.o モジュールが必要です。

#### IPv4 トンネリング

まず先に IPv4 のトンネリングから行きましょう。

いま 3 つのネットワークがあるとしましょう。 内部ネットワーク A と B、そしてその中間にあるネットワーク C (あるいはインターネット) です。

ネットワーク A は:

    network 10.0.1.0
    netmask 255.255.255.0
    router  10.0.1.1

このルータのネットワーク C 側のアドレスは 172.16.17.18 です。 このネットワークを neta と呼びましょう (ええ、独創的な名前でないのはわかってます)。

そしてネットワーク B は:

    network 10.0.2.0
    netmask 255.255.255.0
    router  10.0.2.1

このルータのネットワーク C 側のアドレスは 172.19.20.21 です。 このネットワークを netb と呼びましょう (これも独創的ではありませんな)。

ネットワーク C は、 A から B へのパケット (およびその逆) はすべて通すとします。 ここではその方法・理由に関しては気にしないことにします。

ネットワーク A のルータで、次を実行します。

    ip tunnel add netb mode gre remote 172.19.20.21 local 172.16.17.18 ttl 255
    ip link set netb up
    ip addr add 10.0.1.1 dev netb
    ip route add 10.0.2.0/24 dev netb

この内容について少々説明しましょう。1 行目ではトンネルデバイスを追加し、 それを netb と呼ぶことにしました (理由は明らかでしょうが、 行き先の名前だからです)。さらにここでは、 GRE プロトコルを用いること (mode gre)、 リモートアドレスは 172.19.20.21 であること (他端のルータ)、 トンネリングパケットの発信元は 172.16.17.18 であること (これによってルータにはネットワーク C の複数の IP アドレスを与えることができ、 そのうちのどれをトンネルに用いるかを決定できます)、 そしてパケットの TTL フィールドを 255 にすること (ttl 255)、 などを指定しています。

2 行目ではデバイスを有効にしています。

3 行目では、新たに生まれたインターフェース netb に、 アドレス 10.0.1.1 を与えています。 小さいネットワークならこれで OK ですが、 いまから鉱山探検 (たくさんのトンネル掘り) をはじめようとしている場合は、 トンネリングインターフェースには別の領域の IP アドレスを与えるほうが良いかもしれません (この例なら 10.0.3.0 が使えます)。

4 行目では、ネットワーク B への経路を設定しています。 ネットワークの指定が異なっていることに注意してください。 この書式に慣れていない人に、簡単な原理を説明します: ネットワークを 2 進形式で書き、1 の数を数えてください。 やり方がわからなければ、255.0.0.0 が /8、 255.255.0.0 が /16、255.255.255.0 が /24 であると暗記しましょう。 あ、それから 255.255.254.0 は /23 です。念のため。

ここはこれで十分でしょう。ではネットワーク B のルータに行きましょう。

    ip tunnel add neta mode gre remote 172.16.17.18 local 172.19.20.21 ttl 255
    ip link set neta up
    ip addr add 10.0.2.1 dev neta
    ip route add 10.0.1.0/24 dev neta

そしてトンネルを削除したくなったら、ルータ A で:

    ip link set netb down
    ip tunnel del netb

もちろんルータ B では netb を neta に置き換えてください。

#### IPv6 トンネリング

IPv6 アドレスに関する説明は Section 6 を見てください。

ではトンネルについて:

次のような IPv6 ネットワークがあるとします。 そしてこれを 6bone や友達のネットワークに接続したいとしましょう。

    Network 3ffe:406:5:1:5:a:2:1/96

IPv4 アドレスは 172.16.17.18 で、6bone ルータの IPv4 アドレスは 172.22.23.24 とします。

    ip tunnel add sixbone mode sit remote 172.22.23.24 local 172.16.17.18 ttl 255
    ip link set sixbone up
    ip addr add 3ffe:406:5:1:5:a:2:1/96 dev sixbone
    ip route add 3ffe::/15 dev sixbone

説明しましょう。1 行目では、 sixbone という名前のトンネルデバイスを生成しています。 これには mode sit (IPv6 を IPv4 にトンネリングする) を与え、 行き先 (remote) と発信元 (local) を指定しています。 TTL は最大の 255 にしています。 次に、このデバイスをアクティブにしています (up)。 その後、我々のネットワークのアドレスを追加し、 3ffe::/15 (現在のところ 6bone の全体) への経路をこのトンネルにしています。

GRE トンネルは現在のところ好んで用いられているトンネリング形式です。 これは Linux コミュニティ以外でも広く採用されている標準で、 よって良いものであると言えます。

### ユーザランドのトンネル

カーネル外でのトンネリングの実装も、文字通りあまた存在しています。 最もよく知られているのはもちろん PPP と PPTP でしょうが、 他にもたくさんあります (商用のもの、安全なもの、IP を使わないものさえあります)。 ただしこれらはこの HOWTO の範囲から大きく外れています。

## Cisco や 6bone との IPv6 トンネル

By Marco Davids <marco@sara.nl>

メンテナへのメモ:

わたしの知る限り、この IPv6-IPv4 トンネリングは GRE トンネリングの定義には当てはまりません。 GRE トンネルデバイスを用いて IPv6 を IPv4 にトンネルすることはできます (GRE は「なんでも」IPv4 に通せます) が、 ここで用いているデバイス ("sit") は IPv6 のみを IPv4 に通しますので、 ちょっと違います。

### IPv6 トンネリング

これは Linux のトンネリング機能の、また別の利用例です。 これは IPv6 を初期に導入した人 (あるいは「先駆者」) たちに人気があります。以降で記述されている「実践的」な説明は、 もちろん IPv6 トンネリングを実現する唯一の方法というわけではありません。 しかしこれは、IPv6 機能を持った Cisco ルータと Linux とをトンネルするのによく用いられる方法ですし、 我々の経験によれば、多くの人々が従っているやり方でもあります。 これがあなたにも当てはまるであろうことは、まず間違いないと思いますね ;-)

IPv6 アドレスに関して少々:

IPv6 アドレスは、IPv4 のアドレスに比べると、非常に巨大 (128 ビット対 32 ビット) です。これによって、我々の望むものが提供されます。 すなわち、たくさんの、たくさんの IP アドレス、正確に言えば 340,2822,6692,0938,4634,6337,4607,4317,6821,1465 個のアドレスです。 これ以外にも、IPv6 (あるいは IPng: IP Next Generation) では、 インターネットのバックボーンルータにおける経路テーブルが小さくなり、 機器の設定がより単純になり、IP レベルでのセキュリティが より堅固になると期待されています。

例: 2002:836b:9820:0000:0000:0000:836b:9886

IPv6 をすべて書き下すのは、かなりの重荷です。 よって、生活を楽にするための規則がいくつかあります。

- 先頭から続く 0 は使いません。IPv4 と同じです。

- 16 ビットごと (2 バイトごと) にコロンで区切ります。

- 0 がたくさん連続するところは :: のように書けます。 ただしこれが行えるのはアドレスのうちの一箇所だけで、 また 16 ビットの単位に対してのみ使えます。

アドレス 2002:836b:9820:0000:0000:0000:836b:9886 は、 2002:836b:9820::836b:9886 のようにも書けます。 後者のほうがちょっとは親しみやすいでしょう。

別の例、アドレス 3ffe:0000:0000:0000:0000:0020:34A1:F32C は 3ffe::20:34A1:F32C とも書けます。ずっと短いですね。

IPv6 は現在の IPv4 の後継者となるべく開発されました。 しかし IPv6 はやや新しい技術なので、 まだ世界規模のネイティブな IPv6 ネットワークはありません。 6bone は、この移行を速やかにするために導入されました。

ネイティブな IPv6 ネットワーク同士を接続するのに、 IPv6 プロトコルを IPv4 パケットにカプセルし、 既存の IPv4 インフラを通して、 相手の IPv6 サイトに送るのです。

ここでまさにトンネルを利用することになるわけです。

IPv6 を有効にするには、カーネルのサポートが必要です。 これには多くの良質な文書が存在していますが、 結局のところはいくつかの段階に分割できます。

- 適切な glibc を備えた、 最近の Linux ディストリビューションを入手します。

- 最新のカーネルソースを入手します。

準備が整ったら、IPv6 機能を持つカーネルをコンパイルします。

- /usr/src/linux に移動し、 "make menuconfig" と入力します。

- "Networking Options" を選びます。

- "The IPv6 protocol", "IPv6: enable EUI-64 token format", "IPv6: disable provider based addresses" を選択します。

ヒント: モジュールにはしないほうがいいでしょう。 うまくいかないことがよくあります。

別の言い方をすれば、IPv6 をカーネルの「組み込み」にしてください。 続いて通常のように設定を保存し、カーネルのコンパイルに移ってください。

ヒント: その前に、Makefile を編集して EXTRAVERSION = -x ; --> ; EXTRAVERSION = -x-IPv6 のようにしておくといいかもしれません。

カーネルのコンパイルとインストールにあたっては良い文書がたくさんありますが、 この文書はそこまでは書きません。もしこの段階でトラブルが生じたら、 自分で設定した Linux カーネルのコンパイルに関する文書を探して読んでください。 最初は /usr/src/linux/README ファイルから見てみるといいでしょう。

これらがすんだら、新しいカーネルで再起動してください。 ここで '/sbin/ifconfig -a' とすると、'sit0' という新しいデバイスができていることに気づくでしょう。 SIT は Simple Internet Transition を意味します。 自分を誉めてあげましょう。あなたは次世代の IP へと向かう、 大きな一歩をいま踏み出したのです。

では次の一歩に向かいましょう。自分のホスト (あるいは LAN 全体) を IPv6 機能を持った別のネットワークに接続します。 この相手は、この目的のために特別に設定された "6bone" となるでしょう。

あなたの IPv6 ネットワークが 3ffe:604:6:8::/64 で、 6bone (あるいは友人) に接続することを意図しているとします。 ここで /64 というサブネット表記は、通常の IP アドレスの場合とちょうど同じような意味を持ちます。

あなたの持っている IPv4 アドレスは 145.100.24.181 で、 6bone ルータの IPv4 アドレスは 145.100.1.5 だとしましょう。

    # ip tunnel add sixbone mode sit remote 145.100.1.5 [local 145.100.24.181 ttl 255]
    # ip link set sixbone up
    # ip addr add 3FFE:604:6:7::2/126 dev sixbone
    # ip route add 3ffe::0/16 dev sixbone

説明しましょう。最初の行では sixbone という名前の トンネルデバイスを生成しています。 これには mode sit (IPv6 を IPv4 にトンネリングする) を与え、 行き先 (remote) と発信元 (local) を指定しています。 TTL は最大の 255 にしています。

次に、このデバイスをアクティブにしています (up)。 その後、我々のネットワークのアドレスを追加し、 3ffe::/15 (現在のところ 6bone の全体) への経路をこのトンネルにしています。 もしこれを実行したマシンが、あなたの IPv6 ゲートウェイなら、 次の内容を実行するといいでしょう。

    # echo 1 >/proc/sys/net/ipv6/conf/all/forwarding
    # /usr/local/sbin/radvd

二番目の行の radvd は (zebra のような) ルータ広報デーモンで、 IPv6 の自動設定機能をサポートしています。 必要なら、お好みの検索エンジンで調べてみてください。 では次のようにしてチェックを行いましょう。

    # /sbin/ip -f inet6 addr

radvd が IPv6 ゲートウェイで動作しており、 IPv6 機能を持った Linux が LAN 上のマシンで起動していれば、 IPv6 自動設定の恩恵を楽しめるはずです。

    # /sbin/ip -f inet6 addr
    1: lo: <LOOPBACK,UP> mtu 3924 qdisc noqueue inet6 ::1/128 scope host

    3: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 100
    inet6 3ffe:604:6:8:5054:4cff:fe01:e3d6/64 scope global dynamic
    valid_lft forever preferred_lft 604646sec inet6 fe80::5054:4cff:fe01:e3d6/10
    scope link

では引き続き IPv6 アドレスに対して bind の設定を行いましょう。 A タイプは IPv6 では AAAA となります。 in-addr.arpa は ip.int となります。 この話題に関しては、たくさんの情報が得られるでしょう。

IPv6 が使えるアプリケーションの数は増え続けています。 secure shell, telnet, inetd, Mozilla ブラウザ, webserver Apache, ほかにもたくさんあります。しかしそれは、 この「経路」に関する文書の範疇を超えています :-)

Cisco 側での設定は、次のような感じになります。

    !
    interface Tunnel1
    description IPv6 tunnel
    no ip address
    no ip directed-broadcast
    ipv6 address 3FFE:604:6:7::1/126
    tunnel source Serial0
    tunnel destination 145.100.24.181
    tunnel mode ipv6ip
    !
    ipv6 route 3FFE:604:6:8::/64 Tunnel1

もし思い通りになる Cisco がなければ、インターネットにたくさんある IPv6 トンネルブローカーのどれかを試してみましょう。 きっと Cisco の設定を行い、あなた用のトンネルを掘ってくれるでしょう。 たいていの場合は、使いやすい web インターフェースがあるはずです。 お好みの検索エンジンで、"ipv6 tunnel broker" をキーワードにして 探してみてください。

## IPSEC: インターネット越しの安全な IP

最近の Linux には 2 種類の IPSEC が存在します。 2.2 と 2.4 には FreeS/WAN があり、これが最初のメジャーな実装でした。 公式サイトと 非公式サイト があり、活発にメンテナンスされています。FreeS/WAN は様々な理由から、 これまでメインのカーネルにはマージされてきませんでした。 最も良く言及されてきたのはアメリカ人開発者の「政治的」な理由で、 暗号化に関して作業すると輸出規制がかかってしまうからでした。 さらに FreeS/WAN は Linux カーネルにあまりうまく統合することができず、 実際のマージにあたって、良い候補にはなりませんでした。

また、コードの品質に関しても多くの 団体から 懸念の声 があげられました。 FreeS/WAN の設定に関しては、多くの文書 が利用できます。

Linux 2.5.47 の頃には、カーネルのネイティブな IPSEC 実装が存在していました。 これは Alexey Kuznetsov と Dave Miller によるもので、 USAGI IPv6 グループの成果から刺激を受けたものでした。 このマージにあたっては、James Morris の CryptoAPI もカーネルの一部となりました (これが実際の暗号化を行います)。

この HOWTO では、2.5+ 版の IPSEC のみを扱います。 Linux 2.4 のユーザには、現在のところは FreeS/WAN をお奨めしますが、 この設定はネイティブの IPSEC のものとは異なります。 関連ニュースですが、いまでは FreeS/WAN のユーザ空間のコードを、ネイティブな Linux IPSEC と動作させるための パッチ があるそうです。

2.5.49 からは、IPSEC はパッチを必要せずに動作できるようになりました。

ユーザ空間のツールは こちら から入手できます。複数のプログラムが使えますが、 ここでリンクしたのは Racoon ベースのものです。

カーネルのコンパイルにあたっては、'PF_KEY', 'AH', 'ESP' その他、 CryptoAPI のすべてを有効にしてください!

この章の著者は、IPSEC に関してはど素人です! 必ずあるであろうミスをもし見つけたら、どうか bert hubert ahu@ds9a.nl にメールしてください。

まず最初に、安全な通信を 2 つのホストの間で行う方法を示します。 この処理の大部分は自動化できますが、ここでは手動で行ってみて、 「箱の中で」なにが行われているのかについて、よく理解することにしましょう。

自動での鍵処理 (keying) にだけ興味があるのでしたら、 次の節は飛ばしてください。 しかし手動での鍵処理に関する理解は、きっと有益です。

### 入門編: 手動での鍵処理

IPSEC は複雑な題材です。たくさんの情報がネットワーク上にありますが、 この HOWTO では動作できるようにすること、基本的な原理を 説明することに集中したいと考えています。 すべての例は、上記のリンクにある Racoon をもとにしています。

iptables のたいていの設定では、IPSEC パケットを落としてしまいます! IPSEC を通すには、'iptables -A xxx -p 50 -j ACCEPT' と 'iptables -A xxx -p 51 -j ACCEPT' を使ってください。

IPSEC はセキュリティに優れたインターネットプロトコルです。 ここでの「セキュリティ」には、2 つの意味があります。 暗号化および認証です。わかってない人は、セキュリティすなわち暗号化であると 考えがちですが、それだけでは十分でないことは簡単に示せます。 通信は暗号化していても、通信先が自分の期待している通りかどうかの保証が 一切ないとしたらどうでしょうか?

IPSEC は暗号化には 'Encapsulated Security Payload (ESP)' を、 そしてリモートの通信先の認証には 'Authentication Header (AH)' を用います。 両方を使うようにも設定できますし、片方だけを使うこともできます。

ESP も AH も、security assosiation (SA) に依存しています。 SA は、発信元・送信先・手続きからなります。 例えば認証の SA は次のようになります。

    	  add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";

これは「10.0.0.11 から 10.0.0.216 に向かうトラフィックのうち AH を必要とするものは、秘密鍵 123456789123456 を用いた HMAC-MD5 で署名できる」という意味です。 この手続きには SPI ('Security Parameter Index') の id 15700 が付けられています。 これについては後ほど詳しく説明します。 SA の興味深い点は、対称的だという点にあります。 通話を共有している両端は、まったく同じ SA を共有し、 他端の鏡映にはなりません。しかしながら、ここでは 'autoreserve' ルールがないことに注意しましょう。 つまりこの SA は、10.0.0.11 から 10.0.0.216 への認証が可能であることだけを示しています。 双方向のトラフィックに対しては、2 つの SA が必要となります。

ESP SA の例を挙げましょう。

    add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";

これは「10.0.0.11 から 10.0.0.216 に向かうトラフィックのうち 暗号化を必要とするものは、鍵 123456789012123456789012 を用いて暗号化できる」 という意味です。この SPI id は '15701' です。

ここまでのところ、SA が可能な手続きを記述することを見てきましたが、 しかし実は SA は、いつそれが必要になるかに関するポリシーを記述していません。 実際のところ、SPI id のみが異なるほとんど同じ SA を、好きなだけ置くこともできるのです (先ほども述べましたが SPI は Security Parameter Index の意味です)。 実際の暗号化を行うには、ポリシーを記述しなければなりません。 このポリシーには、「可能なら ipsec を使う」とか 「ipsec が使えなければトラフィックを捨てる」とかいった内容を記述できます。

典型的かつ簡単な Security Policy (SP) は次のようなものです。

    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
       esp/transport//require
       ah/transport//require;

ホスト 10.0.0.216 でこれが入力されると、 10.0.0.11 に向かうすべてのトラフィックは暗号化されなければならず、 かつ AH (authenticating header: 認証ヘッダ) でラップされなければなりません。 ただしここではどの SA を使うべきかは記述されていません。 これを決定するのはカーネルに残された仕事なのです。

別の言い方をすれば、Security Policy は我々が「何を」 必要とするかを指定するのです。Security Association は それを「どのように」必要とするかを記述します。

送り出されるパケットは、SA SPI (「どのように」) でラベル付けされます。 カーネルはこれを使って暗号化と認証を行い、 リモートからはそれに対応する検証と復号化の手続きを調べることができます。

以降に示すのは、ホスト 10.0.0.216 から 10.0.0.11 に対して 暗号化と認証を用いて通信する場合の、非常に簡単な設定です。 ただし最初の版では逆向きは平文ですので、 これは実際に用いてはなりません。

ホスト 10.0.0.216 では:

    #!/sbin/setkey -f
    add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";
    add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";

    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
       esp/transport//require
       ah/transport//require;

ホスト 10.0.0.11 では同じ Security Association を用い、 Security Policy は指定しません。

    #!/sbin/setkey -f
    add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";
    add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";

以上の設定を行えば (これらのファイルは 'setkey' が /sbin にインストールされていれば実行できます)、 10.0.0.126 から行った 'ping 10.0.0.11' は、tcpdump では次のように見えます。

    22:37:52 10.0.0.216 > 10.0.0.11: AH(spi=0x00005fb4,seq=0xa): ESP(spi=0x00005fb5,seq=0xa) (DF)
    22:37:52 10.0.0.11 > 10.0.0.216: icmp: echo reply

10.0.0.11 からの ping の戻りが、実際に平文で見えることに注目。 フォワードの ping は、もちろん tcpdump では見えません。 しかし 10.0.0.11 に伝えている AH と ESP の Security Parameter Index、 すなわちパケットの認証確認と複号化の方法、は見えています。

しかし、ここでいくつかの点に触れておく必要があります。 上記の設定は、多くの IPSEC の例で示されているものですが、 非常に危険なのです。問題は次の点にあります。上記におけるポリシーでは、 10.0.0.216 が 10.0.0.11 に向かうパケットをどう取り扱うかと、 10.0.0.11 がこれらのパケットをどう扱うかは定めていますが、 しかし 10.0.0.11 が認証できない/複号化できないトラフィックを どのように捨てるかを示していないのです!

これでは、誰かが途中でパケットを盗み、 完全に複号化されたパケットで置き換えると、 10.0.0.11 はそれを受け入れてしまいます。 これを避けるには、到着パケットに対する Security Policy が 10.0.0.11 に必要です。次のようにします。

    #!/sbin/setkey -f
    spdadd 10.0.0.216 10.0.0.11 any -P IN ipsec
       esp/transport//require
       ah/transport//require;

これは 10.0.0.11 に対して、 10.0.0.216 から来たトラフィックに対して、 正しい ESP と AH が必要である、と指定しています。

さて、設定を完了するには、 もちろん帰りのトラフィックにも同様な暗号化・認証が必要です。 10.0.0.216 における完全な設定は:

    #!/sbin/setkey -f
    flush;
    spdflush;

    # AH
    add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
    add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";

    # ESP
    add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
    add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";

    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
               esp/transport//require
               ah/transport//require;

    spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
               esp/transport//require
               ah/transport//require;

そして 10.0.0.11 では:

    #!/sbin/setkey -f
    flush;
    spdflush;

    # AH
    add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
    add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";

    # ESP
    add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
    add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";

    spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
               esp/transport//require
               ah/transport//require;

    spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
               esp/transport//require
               ah/transport//require;

この例では、同一の鍵を両方向のトラフィックに用いています。 しかしこれは、必要条件ではまったくありません。

いま行った設定を確認するには、 `setkey -D` を実行して Security Association を表示するか、 `setkey -DP` を実行して設定されたポリシーを表示してください。

### 自動での鍵処理

一つ前の節では、暗号化は単純な共有鍵によって行われました。 したがって、安全を確保するには、 この暗号化設定を信頼できる経路で転送しなければいけません。 リモートのホストを telnet で設定しなければならないような場合には、 第三者にこの共有鍵が覗けてしまうので、その設定が安全であるとは言えません。

さらに、鍵が共有されているということは、秘匿性がないということです。 リモートはこの鍵を用いてもたいした悪さはできませんが、 しかし別々の相手と通信する際には、必ずそれぞれ別々の鍵を使う必要があります。 これには鍵の数が非常にたくさん必要です。 もし 10 組のパートナーを組むなら、最低 50 種類の鍵が必要です (訳注 10_C_2 なら 45 だと思いますが…)。

対称鍵の問題に加え、鍵の更新にも問題があります。 第三者が十分な量のトラフィックを盗聴すると、 鍵をリバースエンジニアリングすることが不可能ではなくなります。 これは時々鍵を新しくすることによって避けられますが、 これは自動化が必要な処理です。

もうひとつの問題として、前述したような手動の鍵処理では、 アルゴリズムと鍵の長さを正確に定義しなければならず、 リモートとの調整の手間がかかります。 もっと幅広い鍵ポリシーを記述できるほうが望ましいです。例えば 「こちらでは 3DES と Blowfish が使えて、鍵の長さには次のようなのが使えます」 のように。

これらの問題を解決するために、IPSEC では Internet Key Exchange が使えます。 これはアルゴリズムの詳細をネゴシエートして、 ランダムに生成された鍵を自動的に交換し、 非対称暗号化技術を用いて鍵を転送します。

Linux 2.5 の IPSEC 実装では、KAME の 'racoon' IKE デーモンが動作します。 11 月 9 日 (訳注: 2002 年) の段階では、Alexey の iptools 配布に入っている racoon は、コンパイルはできますが、2 つのファイルで #include <net/route.h> を削除する必要があります。 または、私も コンパイル済バイナリ を提供しています。

IKE は UDP ポート 500 にアクセスできる必要があります。 iptables でブロックしていないことを確認しましょう。

#### 理論

先に説明した通り、自動鍵処理では多くの作業をしてくれます。 特に、Security Associations を動作の途中で生成してくれます。 しかしポリシーの設定は、希望するようには行ってくれません。

よって、IKE の恩恵を享受するには、ポリシーは設定して、 SA は設定しない、ということになります。 カーネルは SA の無い IPSEC ポリシーを発見するとそれを IKE デーモンに知らせ、 そしてデーモンがネゴシエーションの作業を開始します。

繰り返しますが、Security Policy は我々が「何を」必要とするかの指定です。 Security Association は、それを「どのように」実現するかを記述します。 自動鍵処理を用いれば、我々は何をしたいかだけを指定すればよくなるのです。

#### 例

Kame の racoon には非常にたくさんのオプションがありますが、 そのほとんどは、デフォルトが非常に良く考えられた値になっています。 よってほとんどは変更する必要がありません。 前述のように、オペレータは Security Policy を定義すればよく、 Security Associations は必要ありません。 ネゴシエーションは IKE デーモンに任せます。

この例では、再び 10.0.0.11 と 10.0.0.216 が安全な通信を確立しようとしています。 しかし今回は racoon の助けを借ります。 簡単のために、この設定には事前に共有した、 嫌われ者の「共有暗号鍵」があるとしましょう。 X.509 認証は別の節で議論します。 をみてください。

ほぼデフォルトの設定でいきましょう。 両方のホストで同じです。

    path pre_shared_key "/usr/local/etc/racoon/psk.txt";

    remote anonymous
    {
     	exchange_mode aggressive,main;
     	doi ipsec_doi;
     	situation identity_only;

    	my_identifier address;

    	lifetime time 2 min;   # sec,min,hour
    	initial_contact on;
    	proposal_check obey;	# obey, strict or claim

    	proposal {
    	        encryption_algorithm 3des;
    	        hash_algorithm sha1;
    	        authentication_method pre_shared_key;
    	        dh_group 2 ;
    	}
    }

    sainfo anonymous
    {
     	pfs_group 1;
     	lifetime time 2 min;
     	encryption_algorithm 3des ;
     	authentication_algorithm hmac_sha1;
    		compression_algorithm deflate ;
    }

たくさんの設定がありますね。デフォルトの設定にさらに近づければ、 もうちょっと減らせると思いますが。いくつか注目すべき点を。 anonymous (匿名) 設定を 2 つ行っていますが、 これはすべてのリモートに適用され、 以降の設定が簡単になります。ホストごとの記述は、 特に必要としなければ、書かなくてかまいません。

さらに、この設定では自分自身の指定は IP アドレスによって行う ('my_identifier address') ように指定しました。 またこちらで行えるのは 3des, sha1 で、 psk.txt にある事前共有鍵を使う、と宣言しています。

psk.txt には 2 つのエントリを設定しておきます。 これは各ホストで異なります。10.0.0.11 では:

    10.0.0.216	password2

10.0.0.216 では:

    10.0.0.11	password2

これらのファイルは root の所有にし、モードを 0600 にすることを忘れずに。 さもないと racoon はこのファイルの内容を信用しません。 これらのファイルはそれぞれの鏡映対称になっていますね。

これで望むポリシーを設定する準備ができました。 これも簡単で、10.0.0.216 では:

    #!/sbin/setkey -f
    flush;
    spdflush;

    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
    	esp/transport//require;

    spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
    	esp/transport//require;

そして 10.0.0.11 では:

    #!/sbin/setkey -f
    flush;
    spdflush;

    spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
    	esp/transport//require;

    spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
    	esp/transport//require;

ここも、これらのポリシーは鏡映になっています。

これで racoon を起動する準備ができました! 一度起動すれば、10.0.0.11 から 10.0.0.216 に telnet しようとしたとき、 あるいは他の接続を試みたとき、racoon はネゴシエーションを開始します:

    12:18:44: INFO: isakmp.c:1689:isakmp_post_acquire(): IPsec-SA
      request for 10.0.0.11 queued due to no phase1 found.
    12:18:44: INFO: isakmp.c:794:isakmp_ph1begin_i(): initiate new
      phase 1 negotiation: 10.0.0.216[500]<=>10.0.0.11[500]
    12:18:44: INFO: isakmp.c:799:isakmp_ph1begin_i(): begin Aggressive mode.
    12:18:44: INFO: vendorid.c:128:check_vendorid(): received Vendor ID:
      KAME/racoon
    12:18:44: NOTIFY: oakley.c:2037:oakley_skeyid(): couldn't find
      the proper pskey, try to get one by the peer's address.
    12:18:44: INFO: isakmp.c:2417:log_ph1established(): ISAKMP-SA
      established 10.0.0.216[500]-10.0.0.11[500] spi:044d25dede78a4d1:ff01e5b4804f0680
    12:18:45: INFO: isakmp.c:938:isakmp_ph2begin_i(): initiate new phase 2
      negotiation: 10.0.0.216[0]<=>10.0.0.11[0]
    12:18:45: INFO: pfkey.c:1106:pk_recvupdate(): IPsec-SA established:
      ESP/Transport 10.0.0.11->10.0.0.216 spi=44556347(0x2a7e03b)
    12:18:45: INFO: pfkey.c:1318:pk_recvadd(): IPsec-SA established:
      ESP/Transport 10.0.0.216->10.0.0.11 spi=15863890(0xf21052)

ここで setkey -D を実行して Security Associations を表示すると、 実際に存在していることがわかります:

    10.0.0.216 10.0.0.11
    	esp mode=transport spi=224162611(0x0d5c7333) reqid=0(0x00000000)
    	E: 3des-cbc  5d421c1b d33b2a9f 4e9055e3 857db9fc 211d9c95 ebaead04
    	A: hmac-sha1  c5537d66 f3c5d869 bd736ae2 08d22133 27f7aa99
    	seq=0x00000000 replay=4 flags=0x00000000 state=mature
    	created: Nov 11 12:28:45 2002	current: Nov 11 12:29:16 2002
    	diff: 31(s)	hard: 600(s)	soft: 480(s)
    	last: Nov 11 12:29:12 2002	hard: 0(s)	soft: 0(s)
    	current: 304(bytes)	hard: 0(bytes)	soft: 0(bytes)
    	allocated: 3	hard: 0	soft: 0
    	sadb_seq=1 pid=17112 refcnt=0
    10.0.0.11 10.0.0.216
    	esp mode=transport spi=165123736(0x09d79698) reqid=0(0x00000000)
    	E: 3des-cbc  d7af8466 acd4f14c 872c5443 ec45a719 d4b3fde1 8d239d6a
    	A: hmac-sha1  41ccc388 4568ac49 19e4e024 628e240c 141ffe2f
    	seq=0x00000000 replay=4 flags=0x00000000 state=mature
    	created: Nov 11 12:28:45 2002	current: Nov 11 12:29:16 2002
    	diff: 31(s)	hard: 600(s)	soft: 480(s)
    	last:                     	hard: 0(s)	soft: 0(s)
    	current: 231(bytes)	hard: 0(bytes)	soft: 0(bytes)
    	allocated: 2	hard: 0	soft: 0
    	sadb_seq=0 pid=17112 refcnt=0

Security Policy は設定したとおりです:

    10.0.0.11[any] 10.0.0.216[any] tcp
    	in ipsec
    	esp/transport//require
    	created:Nov 11 12:28:28 2002 lastused:Nov 11 12:29:12 2002
    	lifetime:0(s) validtime:0(s)
    	spid=3616 seq=5 pid=17134
    	refcnt=3
    10.0.0.216[any] 10.0.0.11[any] tcp
    	out ipsec
    	esp/transport//require
    	created:Nov 11 12:28:28 2002 lastused:Nov 11 12:28:44 2002
    	lifetime:0(s) validtime:0(s)
    	spid=3609 seq=4 pid=17134
    	refcnt=3

##### 問題と既知の欠陥

これが動作しないときは、 設定ファイルがすべて root の所有であるか、 root からしか読めないようになっているかを確認してください。 racoon をフォアグラウンドで起動するには '-F' を用います。 コンパイル時に設定されたファイルの代わりに 特定の設定ファイルを読み込ませるには、'-f' を用います。 詳細についてこと細かに知りたければ、racoon.conf に 'log debug' という行を追加してください。

#### X.509 証明書を用いた自動での鍵処理

先に説明した通り、共有鍵の利用には難しい点があります。 共有は面倒で、それに一度共有されてしまうとそれはもう秘密ではないからです。 ありがたいことに、非対称暗号技術を利用すればこの問題を解決できます。

IPSEC への各参加者が公開鍵と秘密鍵を作れば、 通信の両者が公開鍵を発行し、ポリシーを設定すれば、 安全な通信を確立できます。

鍵の生成は割に簡単ですが、しかし多少の作業が必要です。 以下では 'openssl' のツールをもとにした作業を説明します。

##### 自分のホストの X.509 証明書を作る

OpenSSL には鍵に関する膨大なインフラストラクチャがあり、 CA による署名を行うことも行わないこともできます。 ここではインフラの大部分は回避し、 CA 無しで済ませる、 お馴染みのいんちきセキュリティを適用しましょう。

まず私たちのホストの「証明請求書 (certificate request)」を発行します。 名前は 'laptop' とします。

    $ openssl req -new -nodes -newkey rsa:1024 -sha1 -keyform PEM -keyout \
      laptop.private -outform PEM -out request.pem

するといくつか質問されます。

    Country Name (2 letter code) [AU]:NL
    State or Province Name (full name) [Some-State]:.
    Locality Name (eg, city) []:Delft
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:Linux Advanced
    Routing & Traffic Control
    Organizational Unit Name (eg, section) []:laptop
    Common Name (eg, YOUR name) []:bert hubert
    Email Address []:ahu@ds9a.nl

    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:
    An optional company name []:

どのくらいまじめに答えるかは、あなたの判断です。 セキュリティの必要に応じて、 ここにホスト名を入れることを望む人も、望まない人もいるでしょう。 この例では入れてあります。

ここでこの請求書に、「自己署名」します:

    $ openssl x509 -req -in request.pem -signkey laptop.private -out \
      laptop.public
    Signature ok
    subject=/C=NL/L=Delft/O=Linux Advanced Routing & Traffic \
      Control/OU=laptop/CN=bert hubert/Email=ahu@ds9a.nl
    Getting Private key

これで 'request.pem' ファイルは捨ててかまいません。

この手続きを鍵の必要なホストのすべてで繰り返します。 この '.public' 鍵は問題なく配布できますが、'.private' 鍵の方は 秘匿しておくように!

##### 設定と起動

ホストの公開鍵・秘密鍵ができたら、これらを使うよう racoon に伝えます。

先ほどの設定に戻りましょう。 2 つのホストを 10.0.0.11 ('upstairs') と 10.0.0.216 ('laptop') とします。

10.0.0.11 の `racoon.conf` ファイルに、以下を追加します:

    path certificate "/usr/local/etc/racoon/certs";

    remote 10.0.0.216
    {
     	exchange_mode aggressive,main;
    	my_identifier asn1dn;
    	peers_identifier asn1dn;

    	certificate_type x509 "upstairs.public" "upstairs.private";

    	peers_certfile "laptop.public";
    	proposal {
                    encryption_algorithm 3des;
    		hash_algorithm sha1;
    		authentication_method rsasig;
    		dh_group 2 ;
    	}
    }

これは racoon に `/usr/local/etc/racoon/certs/` にある証明を用いるよう指示しています。 さらに 10.0.0.216 用の設定項目も含まれています。

ここの 'asn1dn' の行は、ローカル・リモート両側の識別子としては、 公開鍵から取り出されたものを用いるよう、racoon に伝えています。 これは前述の 'subject=/C=NL/L=Delft/O=Linux Advanced Routing & Traffic Control/OU=laptop/CN=bert hubert/Email=ahu@ds9a.nl' という出力です。

`certificate_type` の行は、 ローカルの公開鍵・秘密鍵を設定しています。 `peers_certfile` 文は、 リモートの接続先の公開鍵を `laptop.public` というファイルから読み込むよう racoon に伝えています。

`proposal` ブロックは先ほどのものと変わっていません。 ただし今回は `authentication_method` が `rsasig` になっており、 認証に RSA 公開鍵/秘密鍵を用いることが示してあります。

10.0.0.216 の設定への追加もだいたい同じで、 ただしいつもの通り鏡映の関係にあります。

    path certificate "/usr/local/etc/racoon/certs";

    remote 10.0.0.11
    {
     	exchange_mode aggressive,main;
    	my_identifier asn1dn;
            peers_identifier asn1dn;

            certificate_type x509 "laptop.public" "laptop.private";

     	peers_certfile "upstairs.public";

    	proposal {
                    encryption_algorithm 3des;
    	        hash_algorithm sha1;
    		authentication_method rsasig;
    	        dh_group 2 ;
    	}
    }

両方のホストにこれらの文を追加したら、 あとは鍵をその場所に置くだけです。'upstairs' マシンでは、 `/usr/local/etc/racoon/certs` に `upstairs.private`, `upstairs.public`, `laptop.public` が必要です。 このディレクトリが root の保有になっており、 モードが 0700 になっていることを確認のこと。 さもないと racoon は読み込みを行いません!

マシン 'laptop' では、 `/usr/local/etc/racoon/certs` に `laptop.private`, `laptop.public`, `upstairs.public` が必要です。 要するに、各ホストでは自分の公開鍵と秘密鍵、 さらにリモートの公開鍵を必要とします。

Security Policy が設定されていることを確認します ( の 'spdadd' 行を実行)。 続いて racoon を起動すれば、すべて動作するはずです。

##### トンネルを安全に設定するには

リモートの接続先との間で安全な通信を行うには、 公開鍵を交換しなければなりません。 公開鍵そのものは秘匿しておかなくて良いのですが、 一方、その鍵が本当に改竄されていない鍵である保証が非常に大切です。 要するに、「途中の奴」がいないことが確かでなければなりません。

この作業を簡単にするため、OpenSSL には 'digest' コマンドがあります。

    $ openssl dgst upstairs.public
    MD5(upstairs.public)= 78a3bddafb4d681c1ca8ed4d23da4ff1

これで、リモートのパートナーとの間で、 ダイジェストが同じであることを確認するだけですみます。 これを行うには実際に会うか、あるいは電話をすればいいでしょう。 リモート側でのこの番号が、実は鍵と同じメールで送られて来た、 というのではダメですよ!

これを行うもう一つの方法は、CA を行っている 信頼できる第三者 (Trusted Third Party) を用いることです。 この CA に、(先ほどは自分たちで行った) 署名をしてもらうのです。

### IPSEC トンネル

これまでのところは、 通信の両端が直接 IPSEC を理解している、 いわゆる IPSEC の「転送 (transport)」モードのみを見てきました。 しかし通常は IPSEC を理解するマシンばかりではないので、 ルータのみに IPSEC を喋らせ、 その後ろにあるネットワークの作業を肩代わりさせることが必要となります。 こちらは「トンネルモード」と呼ばれています。

この設定はとても簡単です。10.0.0.216 から 130.161.0.0/16 へ向かうトラフィックを すべて 10.0.0.11 経由でトンネルさせるには、 以下を 10.0.0.216 で実行するだけです。

    #!/sbin/setkey -f
    flush;
    spdflush;

    add 10.0.0.216 10.0.0.11 esp 34501
    	-m tunnel
    	-E 3des-cbc "123456789012123456789012";

    spdadd 10.0.0.0/24 130.161.0.0/16 any -P out ipsec
               esp/tunnel/10.0.0.216-10.0.0.11/require;

ここでは '-m tunnel' に注目してください。これが最重要の部分です。 これは最初に ESP 暗号化された SA をトンネルの両端で設定します。

次に実際のトンネルを設定します。これはカーネルに対し、 10.0.0.24 から 130.161.0.0 に向かう経路のトラフィックを 暗号化するよう指定しています。 さらに、続いてこのトラフィックは 10.0.0.11 に出荷されるのです。

10.0.0.11 でも少々設定が必要です。

    #!/sbin/setkey -f
    flush;
    spdflush;

    add 10.0.0.216 10.0.0.11 esp 34501
    	-m tunnel
    	-E 3des-cbc "123456789012123456789012";

    spdadd 10.0.0.0/24 130.161.0.0/16 any -P in ipsec
               esp/tunnel/10.0.0.216-10.0.0.11/require;

これは、'-P out' が '-P in' に変わったことを除けば、まったく同じものです。 これまでの例と同様、ここでの例も一方向のトラフィックのみを設定しました。 トンネルの反対方向を完成させるのは、読者の課題としておきます。

この設定は別名「代理 ESP」とも呼ばれています。 こちらのほうが少々わかりやすいでしょうか。

IPSEC トンネルには、カーネルで IP Forwarding を有効にしておく必要があります!

### 他の IPSEC ソフトウェア

Thomas Walpuski は、OpenBSD の isakpmd を Linux 2.5 IPSEC で動作するようにしたパッチを書いたと報告してくれました。 さらに、現在は isakpmd のメインの CVS リポジトリにこのコードが入っています。 彼のページ にも記述があります。

isakpmd は前述の racoon とはかなり違いますが、こちらを好む人もたくさんいます。 こちら から入手できます。より詳しくは、 OpenBSD CVS を見てください。 Thomas は CVS やパッチが面倒な人向けに tarball も用意してくれています。

さらに、FreeS/WAN のユーザ空間のツールを Linux 2.5 のネイティブな IPSEC で使えるようにするパッチもあります。こちら にあります。

### 他のシステムとの IPSEC の相互運用

FIXME: 書いてください。

#### Windows

Andreas Jellinghaus <aj@dungeon.inka.de> のレポートによれば: 「win2k: 動作します。ip アドレスと事前共有鍵を組み合わせた認証が使えます (windows は fqdn や userfqdn 文字列はサポートしていないと思います)。 証明書もつかえると思いますが、試していません」

## マルチキャストのルーティング

FIXME: 編集者がいません!

Multicast-HOWTO は (あえて言えば) 古代のもので、 そのため各所に不正確な部分や間違いがあります。

どのようなものであれ、マルチキャストルーティングを行う前には、 カーネルを設定して行いたいルーティングのタイプをサポートさせる必要があります。 これは逆に、どのようなマルチキャストルーティングを使うのかは、 自分で決めなければならない、ということでもあります。 「通常」のタイプは、基本的に 4 つあります。 DVMRP (RIP ユニキャストプロトコルのマルチキャスト版)、 MOSPF (同じく OSPF のマルチキャスト版)、 PIM-SM ("Protocol Independent Multicasting - Sparse Mode": マルチキャストグループのユーザが集中しておらず、分散していると仮定しています)、 PIM-DM (上記の "Dense Mode" で、同じマルチキャストグループに属するユーザは、 ある程度まとまっていると仮定しています)、です。

Linux カーネルには、これらの選択肢は登場しません。 プロトコルそのものは、Zebra, mrouted, pimd などの ルーティングアプリケーションが扱うからです。 しかしそれでも、どれを用いるかはしっかり決めておかないと、 カーネルで正しいオプションを選ぶことができません。

すべてのマルチキャストルーティングにおいて、 まず何はおいても "multicasting" と "multicast routing" は必ず 有効にしておかなければなりません。 DVMRP と MOSPF では、これだけで OK です。 PIM を用いるつもりなら、接続しているネットワークにおける PIM のバージョンに応じて、PIMv1 か PIMv2 かのどちらかも 有効にしておかなければなりません。

前述の選択を行ったら、新しい Linux カーネルをコンパイルします。 するとブート時に IP プロトコルがリストされ、 ここでは IGMP が含まれているはずです。 これはマルチキャストグループを管理するプロトコルです。 これを書いている現在では、Linux は IGMP の バージョン 1 と 2 だけしかサポートしていませんが、 バージョン 3 も既に存在し、文書化されています。 しかしバージョン 3 はかなり新しく、 IGMPv3 の拡張機能はそれほど利用されていないので、 これはあまり大きな問題にはなりません。 IGMP はグループを扱うので、グループ全体のうち、 最もシンプルなバージョンの IGMP における機能だけが 用いられることになります。 ほとんどの場合これは IGMPv2 になるでしょうが、 IGMPv1 になることもまだあるでしょう。

ここまではいいですね。マルチキャスティングを有効にできました。 さて、ルーティングを開始するには マルチキャストで実際に何らかの作業を行うよう Linux カーネルに伝える必要があります。 これは、マルチキャスト仮想ネットワークを経路テーブルに追加することを意味します。

    ip route add 224.0.0.0/4 dev eth0

(当然ですが、ここではマルチキャスティングを eth0 で行っているとしています! ほかのデバイスを使いたかったらここを置き換えてください。)

では、パケットをフォワードするよう指示しましょう…

    echo 1 > /proc/sys/net/ipv4/ip_forward

ここまでのところで、 これがいったい何を行うのか、不思議に思っているかもしれません。 そこで、接続テストとして、デフォルトのグループ 224.0.0.1 に ping して、 誰かが生きているか見てみることにしましょう。 マルチキャストを有効にした LAN のマシンはすべて応答するはずですが、 ほかには何も起こりません。どのマシンも 224.0.0.1 という IP アドレスは持っていません。なんと不思議なことでしょう! :) これはグループアドレス (購読者への「ブロードキャスト」)であって、 グループのすべてのマシンは、グループアドレスではなく、 すべて自分自身のアドレスで応答するのです。

    ping -c 2 224.0.0.1

これで、実際のマルチキャストルーティングを行う準備ができました。 さて、2 つのネットワークがあり、この間でルーティングを行いたいとしましょう。

(続く!)

## キューイング規則とバンド幅管理

さて、わたしがこれに気づいたときには、ほんとうにたまげました。 Linux 2.2/2.4 には、 ハイエンドの専用システムに比肩するほどの、 様々なバンド幅管理機能が含まれているのです。

Linux はフレームリレーや ATM よりもずっと進んだ処理を行えます。

混乱を避けるため、`tc` におけるバンド幅の指定では、 以下の規則を用いることにします。

    mbps = 1024 kbps = 1024 * 1024 bps => byte/s
    mbit = 1024 kbit => kilo bit/s.
    mb = 1024 kb = 1024 * 1024 b => byte
    mbit = 1024 kbit => kilo bit.

内部では、数値は bps および b で保持されています。

しかし `tc` が速度を表示するときには、 以下を用います。

    1Mbit = 1024 Kbit = 1024 * 1024 bps => byte/s

### キューおよびキューイング規則の説明

キューイングを用いると、データをどのように「送る」かを決定できます。 大切なのは、我々が変更できるのは送信データだけ、という認識です。

インターネットの動作原理からして、 他の人が送ってくる内容を直接制御することはできません。 これはあなたの自宅の (物理的な!) 郵便箱にちょっと似ています。 世の中を変更して、送られてくる郵便物の量に影響を与えることはできません (全員に連絡を取れば別ですが)。

しかしながら、Internet の大部分は TCP/IP で動作しているので、 このいくつかの機能が利用できます。 TCP/IP は 2 つのホスト間のネットワークの容量を知る方法を持たないので、 単に開始時にデータをだんだん速く送ろうとし ('slow start')、 送信できる限界を越えてパケットがロストし始めたときに、減速します。 実際はこの説明よりもう少し賢いのですが、それらについてはまた後ほど触れます。

これはつまり、到着する郵便の半分を読まずに捨て、 それ以上送ってこないよう祈るのと同じです。 インターネットではうまくいく、というところが違いますが :-)

自分のネットワークのあるホストに対して、 あまり高速にダウンロードできないようにしたい場合を考えましょう (そのネットワークのルータもあるとします)。 この場合はルータの「内側」のインターフェース、 すなわちコンピュータにデータを送るところ、 で帯域制限を行います。

接続のボトルネックの部分で制御を行うようにすることも重要です。 NIC が 100Mbit で、ルータの接続が 256kbit だったら、 ルータが処理できる以上のデータは送らないようにしなければなりません。 さもないと、接続を制御しバンド幅を絞るのはそのルータ、 ということになります。 我々は、言うなれば「自前のキュー」が必要なわけで、 それは連鎖のなかで最も遅い回線に存在しなければいけません。 ありがたいことに、これは簡単に可能となります。

### シンプルな、クラスレスのキューイング規則

既に述べたように、キューイング規則を用いると、 データの送信のされ方を変更できます。 クラスのない (クラスレスな) キューイング規則とは、 おおむねデータを受信し、それを再スケジュール・遅延・破棄するようなものです。

これを用いると、再分割 (subdivision) を用いずに、 インターフェース全体のトラフィックを絞れます。 クラスのある (クラスフルな) 「qdisc を含む qdisc」に進む前に、 この部分を理解しておくことはとても大切です。

これまでのところ、最も広く用いられている規則は pfifo_fast qdisc です (これがデフォルトです)。これは、 なぜこれらの先進的な機能の信頼性がこれほど高いのかも説明してくれます。 これらは「ひとつ余分のキュー」以上のものではないのです。

これらのキューには、それぞれ長所と短所があります。 これらのすべてが、完全にテストされているというわけではありません。

#### pfifo_fast

このキューは、名前からわかるとおり、「先入れ先出し (First In, First Out)」 で、つまりどの受信パケットも特殊な扱いは (少なくともそれほどは) されません。 このキューには 3 つのいわゆる「バンド」があります。 各バンドには FIFO のルールが適用されます。 しかし、バンド 0 に待ちパケットがあると、バンド 1 は処理されません。 バンド 1 と バンド 2 も同じ関係にあります。

カーネルは、パケットのいわゆる「サービスのタイプ (Type of Service)」フラグを 尊重し、「最小遅延 (minimum delay)」パケットをバンド 0 に入れるよう計らいます。

このクラスレスかつシンプルな qdisc を、 クラスフルな PRIO qdisc と混同しないこと。 両者は同じように動作しますが、pfifo_fast はクラスレスですから、 tc コマンドで他の qdisc を追加することはできません。

##### パラメータと使い方

pfifo_fast qdisc は出来合いのデフォルトですから、 ユーザによる設定はできません。 ここではデフォルトでどのように設定されているかを示します。

priomap

  パケットの優先具合が、カーネルによって、どのバンドに対応付けられるかを決めます。 対応付けはパケットの TOS に従って決まります。TOS は以下のようなものです。

         0     1     2     3     4     5     6     7
      +-----+-----+-----+-----+-----+-----+-----+-----+
      |                 |                       |     |
      |   PRECEDENCE    |          TOS          | MBZ |
      |                 |                       |     |
      +-----+-----+-----+-----+-----+-----+-----+-----+

  TOS の 4 ビット (「TOS フィールド」) は次のように定義されています。

      Binary Decimcal  Meaning
      -----------------------------------------
      1000   8         Minimize delay (md)
      0100   4         Maximize throughput (mt)
      0010   2         Maximize reliability (mr)
      0001   1         Minimize monetary cost (mmc)
      0000   0         Normal Service

  この 4 ビットの右にはもう 1 ビットありますから、 TOS フィールドの実際の値はこれらの TOS ビットから決まる値の 2 倍になります。 tcpdump -v -v としたときは、この 4 ビット列の値ではなく、 TOS フィールド全体の値を表示します。 これは、次の表の第一列にある値になります。

      TOS     Bits  Means                    Linux Priority    Band
      ------------------------------------------------------------
      0x0     0     Normal Service           0 Best Effort     1
      0x2     1     Minimize Monetary Cost   1 Filler          2
      0x4     2     Maximize Reliability     0 Best Effort     1
      0x6     3     mmc+mr                   0 Best Effort     1
      0x8     4     Maximize Throughput      2 Bulk            2
      0xa     5     mmc+mt                   2 Bulk            2
      0xc     6     mr+mt                    2 Bulk            2
      0xe     7     mmc+mr+mt                2 Bulk            2
      0x10    8     Minimize Delay           6 Interactive     0
      0x12    9     mmc+md                   6 Interactive     0
      0x14    10    mr+md                    6 Interactive     0
      0x16    11    mmc+mr+md                6 Interactive     0
      0x18    12    mt+md                    4 Int. Bulk       1
      0x1a    13    mmc+mt+md                4 Int. Bulk       1
      0x1c    14    mr+mt+md                 4 Int. Bulk       1
      0x1e    15    mmc+mr+mt+md             4 Int. Bulk       1

  たくさん数字が並んでいますね。第二列は、 これらに対応する 4 つの TOS ビット列の値です。 続いてそれらの意味する内容が示されています。 例えば 15 は、このパケットは Minimal Monetary Cost, Maximum Reliability, Maximum Throughput, Minimum Delay のすべてを期待しているパケットを意味しています。 私はこれを「強欲なパケット (Dutch Packet)」と呼びたいと思います。 [訳注: 著者の Hubert さんもオランダ人 :-)]

  第 4 列は Linux カーネルによる TOS ビット列の解釈です。 どの優先度にマップされるかを示しています。

  最後の列は、デフォルトの優先度マップ (priomap) の結果です。 コマンドラインでは、デフォルトの優先度マップは次のようになります。

      1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1

  つまり例えば優先度 4 のパケットは、バンド番号 1 へとマップされます (訳注: 優先度は 0 から始まることに注意)。 優先度マップでは高い優先度 (> 7) をリストすることもできます。 これらは TOS マッピングには対応していませんが、 他の方法によって設定できます。

  この表は RFC1349 (詳細は直接こちらを) から取ったもので、 各アプリケーションがどのように TOS ビットを設定すると良いかについて示してあります。

      TELNET                   1000           (minimize delay)
      FTP
      	Control          1000           (minimize delay)
              Data             0100           (maximize throughput)

      TFTP                     1000           (minimize delay)

      SMTP
      	Command phase    1000           (minimize delay)
              DATA phase       0100           (maximize throughput)

      Domain Name Service
      	UDP Query        1000           (minimize delay)
      	TCP Query        0000
      	Zone Transfer    0100           (maximize throughput)

      NNTP                     0001           (minimize monetary cost)

      ICMP
      	Errors           0000
      	Requests         0000 (mostly)
      	Responses        <same as request> (mostly)

txqueuelen

  このキューの長さはインターフェースの設定から決まります。 インターフェース設定は ifconfig や ip で閲覧・設定できます。 キューの長さを 10 にするには、 "ifconfig eth0 txqueuelen 10" と実行します。

  このパラメータは tc では設定できません。

#### トークンバケツフィルタ (Token Bucket Filter)

トークンバケツフィルタ (Token Bucket Filter: TBF) は 単純な qdisc で、管理者が設定した速度を越えない範囲で到着パケットを通します。 ただし短い時間の突発的なものなら、この値を越えることを許す可能性があります。

TBF は非常に正確で、ネットワークとプロセッサへの負荷も軽いです。 単純にインターフェースの速度を落としたいと思ったときには、 まず最初にこの利用を考えてみるべきです。

TBF の実装はバッファ (バケツ) であり、 これは定期的に仮想的な情報の断片 (トークンと呼ばれます) によって、 一定の割合 (トークン速度) で満たされていきます。 バケツの最も重要なパラメータはサイズで、 保持できるトークンの数を意味します。

バケツに入った各トークンは、 それぞれひとつの受信データパケットをデータキューから拾い、 そしてバケツからは削除されます。 この 2 つの流れ (トークンとデータ) からなるアルゴリズムには、 3 つのシナリオが考えられます。

- トークンと「同じ」割合で、TBF にデータが到着する。 この場合各受信パケットは、それぞれ対応するトークンがあるので、 遅延することなしにキューを通過する。

- トークンの速度よりも「遅い」割合で、TBF にデータが到着する。 キューに入った受信データパケットの出力に応じて 削除されるトークンは一部分のみなので、 トークンはバケツサイズ一杯にまで溜まっていく。 使われなかったトークンは、 突発的なデータのバーストが起こったような場合に利用でき、 トークンの標準流入速度を越えたデータが送信できる。

- トークンの速度よりも「大きい」割合で、TBF にデータが到着する。 この場合、バケツのトークンはすぐに空になってしまい、 TBF はしばらくの間入力を絞る。 これは「過負荷状態 (overlimit situation)」と呼ばれる。 パケットがそのまま入り続けてくる場合には、 パケットは破棄され始める。

後ろの 2 シナリオがとても重要です。 これらは、このフィルタを通過するデータのバンド幅を、 管理者が制御できることを意味しているからです。

トークンが溜まっていくと、制限を越えたデータバーストも、 短いものならロスなしに通過できますが、 過負荷状態が続くとパケットはだんだん遅延していき、ついには破棄されます。

ただし実際の実装では、トークンが対応しているのはバイトであり、 パケットではありません。

##### パラメータと使い方

ほとんど変更の必要はないでしょうが、 TBF にも調整つまみはついています。 まず、つねに指定できるパラメータです:

limit または latency

  limit はトークン待ち状態でキューに入れるバイト数の制限値です。 これは latency パラメータ (パケットが TBF に留まれる時間の最大値) を設定することによっても指定できます。 こちらを計算する際には、バケツのサイズ、トークンの追加速度、 および (指定されていれば) ピーク速度を考慮します。

burst/buffer/maxburst

  バイト単位のバケツのサイズです。 これはある瞬間に利用できるトークンの最大値 (バイト数) です。 一般に、絞りたい通信速度が大きい場合には、大きなバッファがいります。 Intel で 10mbit/s を使う場合、 この設定速度にするには少なくとも 10kbyte のバッファが必要です。

  バッファが小さすぎると、 時間単位あたりに到着するトークンだけでバケツが溢れてしまうので、 パケットが破棄されてしまうかもしれません。

mpu

  サイズが 0 のパケットも、使うバンド幅は 0 ではありません。 イーサネットでは、64 バイト以下のパケットはありません。 最小パケット単位 (Minimum Packet Unit) は、 ひとつのパケットが用いるトークンの最小値を定めます。

rate

  スピードつまみです。制限については上記を参照のこと。

バケツにトークンが入っていて、これを空にすることが許されるとき、 デフォルトではこの作業は無限の速度で行われます。 これがまずい場合には、以下のパラメータを使って下さい。

peakrate

  トークンが使える状態でパケットが到着すると、 デフォルトではそのパケットはすぐさま、いうならば「光速」で送り出されます。 これは、特に大きなバケツを使っている場合には、 望ましいことではないかもしれません。

  peakrate は、バケツを空ける際に許される速さを決めるために用います。 すべてが本に書いてある通りなら、 この動作は「パケットを送って、十分長い間待って、そして次のパケットを送る」 ことによって実現されています。 待ち時間は、ちょうど peakrate で送られるように計算してあります。

  しかしながら、Unix でのデフォルトの時間単位は 10ms なので、 パケットサイズの平均を 10.000 ビットとすると、 peakrate は 1mbit/s にしかできません!

mtu/minburst

  1mbit/s の peakrate は、これが通常の速度より遅ければ、 あまり便利とは言えません。 時間単位ごとに送れるパケットの数を増やせば、 より peakrate は大きくできます。 これは実質的には 2 番目のバケツを作るのと同じことです。

  この 2 番目のバケツは、デフォルトでは 1 つのパケットでしかなく、 つまり実際にはバケツではありません。

  peakrate の最大許容値を計算するには、 mtu の設定値に 100 (あるいは正確には HZ。これは Intel では 100, Alpha では 1024) をかけてください。

##### 設定例

シンプルですが、とっても便利な設定を示します。

    # tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540

はい、なぜ便利なのか説明しましょう。 DSL モデムやケーブルモデムのように、 キューの大きなネットワークデバイスがあるとして、 そのデバイスには (イーサネットインターフェースのような) 高速なデバイスで通信しているとしましょう。 するとアップロードの際に、対話通信が完全にダメになってしまいます。

この理由は、アップロードがモデムのキューを一杯にしてしまうからです。 このキューはおそらく非常に大きく、 実際これによってアップロードのスループットが向上しています。 しかしこれは望ましいことではないでしょう。 キューをあまり大きくせずにおいて、 データの送信時にも他のことができるよう、 対話通信を可能としておきたいことが多いはずです。

上記の実行例では、モデムのキューが必要ない程度にまで、 送信速度を落としています。これでキューは Linux 上にあり、 これに対して制限を課すことができます。

220kbit は、あなたの環境の「実際の」速度 (マイナス数パーセント) にしてください。手持ちのモデムが非常に高速なら、'burst' を少々増やしてください。

#### 確率的不偏キューイング (Stochastic Fairness Queueing)

確率的不偏キューイング (Stochastic Fairness Queueing: SFQ) は、不偏キューイングのアルゴリズムをシンプルに実装したものです。 これは他のものほど正確ではありませんが、 あまり計算量が必要なく、それでいて非常に公平です。

SFQ でのキーワードは「会話 (または流れ)」で、 これは TCP セッションや UDP ストリームとほとんど同じ意味です。 トラフィックはかなり多い数の FIFO キューに分割され、 各キューがそれぞれの会話に対応します。 そしてトラフィックはラウンドロビン的に送られます。 すなわちデータを送る機会が各セッションに順番に与えられます。

これは非常に公平な動作につながり、 ある特定の会話が残りをかき消してしまうようなことはなくなります。 SFQ には「確率的」と名前がついていますが、 これは SFQ が実際には各セッションごとにキューを割り当てるのではなく、 トラフィックをある限られた数のキューに、 ハッシュアルゴリズムを使って分割するからです。

このハッシュのため、複数のセッションが同じバケツに入ることもあり得ます。 各セッションがパケットを送信する機会を握っているのはこのバケツなので、 実効的な速度は半分になってしまいます。 この状況が目立つのを防ぐために、 SFQ はハッシュアルゴリズムを極めて頻繁に変更し、 あるセッション同士の衝突が、短い時間しか続かないようにしています。

これは重要なポイントですが、 SFQ が有効なのは、実際の出力インターフェースが、 本当に一杯一杯になっている場合に限られます。 そうでなければ、あなたの linux マシンのキューはほぼ空っぽで、 よって何の効果も持ちません。 SFQ を他の qdisc と組み合わせ、 「両方にいい顔をしたい」状況を実現するための方法については後ほど議論します。

特に、ケーブルモデムや DSL ルータに向かうイーサネットに対して SFQ を設定するのは、他の制限を行わないならば的外れです。

##### パラメータと使い方

SFQ はほとんどチューニングの必要がありません。

perturb

  ハッシュコードの再設定を行う時間間隔。 設定しないとハッシュは変更されませんが、これはお勧めできません。 たぶん 10 秒で良いでしょう。

quantum

  次のキューに順番を回すまえに、 あるストリームのキューから吐き出すデータのバイト量。 デフォルトは最も大きなサイズ (MTU サイズ) のパケット 1 つ分です。 MTU よりも小さな値にはしないように!

limit

  この SFQ がキューイングするパケットの総数 (これを越えるとパケットを落とし始めます)。

##### 設定例

接続速度と実効速度とが同じデバイス (電話線のモデム) に対しては、 この設定を行うと公平性の向上が期待できます:

    # tc qdisc add dev ppp0 root sfq perturb 10
    # tc -s -d qdisc ls
    qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb 10sec
     Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)

800c: というのは、自動的に割り当てられるハンドル番号です。 limit は、このキューでは 128 個のパケットが待機できることを意味しています。 1024 個のハッシュバケツが割り振りに利用でき、 そのうち同時にアクティブになれるのは 128 個です (これ以上の数はキューに入れません!)。 10 秒ごとにハッシュが再設定されます。

### いつどんなキューを使うべきか

まとめると、これらは単純なキューで、 実際にパケットを並び換えたり、 遅延・破棄したりしてトラフィックを管理するものでした。

以下に、どのキューを用いるべきかの選択にあたって助けとなるコツを示します。 ここでは、 の章で 解説されている qdisc も含まれています。

- 単に送信されるトラフィックを遅くしたければ、 トークンバケツフィルタを用いましょう。 バケツの大きさを調節すれば、非常に大きなバンド幅に対してまで使えます。

- 現在の接続がほぼ一杯で、 特定のセッションが出力バンド幅を支配しないようにしたい場合は、 確率的不偏キューイングを用いましょう。

- バックボーンが大きく、目的がはっきりしているなら、 ランダム初期検知 (高度なフィルタの章を見てください) を検討しましょう。

- フォワードしない到着トラフィックを「絞り」たければ、 入口監視制限 (Ingress Policer) を使いましょう。 なお、到着トラフィックを絞る場合には 「監視制限 (policing)」という言葉が使われます。

- パケットをフォワードする場合は、 データのフォワード先インターフェースで TBF を使いましょう。 ただし複数のインターフェースに向かいうるトラフィックを絞りたい (その場合共通要素は到着インターフェースだけになります) 場合は別で、 その際には入口監視制限を使いましょう。

- 帯域制限は行いたくないが、 インターフェースの負荷が高いか (キューが必要かどうか) を監視したい場合は、pfifo キュー (pfifo_fast ではありません) を使いましょう。 これには内部のバンドはありませんが、パケットのサイズを記録します。

- 最後に - 「社会的帯域制限」も行えるはずです。 つねに望みの状態を実現できる技術が利用できるとは限りません。 またユーザは技術的な制限を敵視するものです。 親切な言葉は、バンド幅の適切な分配にも役立つでしょう!

### 用語説明

より複雑な設定を正しく理解するには、 いくつかの概念を先に説明しておく必要があるでしょう。 このテーマは込み入っていて、また比較的新しいため、 実は同じ概念に異なる言葉が用いられることが多々あります。

以下では An Informal Management Model for Diffserv Routers (`draft-ietf-diffserv-model-06.txt`) を少々参考にしています。 こちら にあります。

用語の正確な定義を知りたい場合は、この原本も読んでください。

キューイング規則 (Queueing Discipline: qdisc)

  入力側 (ingress: 入口)、出力側 (egress: 出口) いずれかの デバイスのキューを管理するアルゴリズムのこと。

root qdisc

  デバイスに直接割り当てられた qdisc のこと。

クラスレスな qdisc (classless qdisc)

  設定可能な内部の再分配ルールが存在しない qdisc のこと。

クラスフルな qdisc (classful qdisc)

  クラスフルな qdisc には複数のクラスが含まれます。 これらのクラスのいくつかはさらに qdisc を保持し、 それがまたクラスフルであることもあります (そうでないこともあります)。 厳密な定義では、pfifo_fast はクラスフルです。 なぜなら 3 つのバンドが含まれ、これらは実はクラスだからです。 しかし、ユーザ設定という面から見ると、 これらのクラスは tc ツールで変更できないのでクラスレスです。

クラス (class)

  クラスフルな qdisc は多くのクラスを持つことができ、 それらのクラスはその qdisc の内部に属します。 またクラスも、自らに属する複数のクラスを持つことがあります。 したがってクラスは親として qdisc か別のクラスかを持ちます。

  葉クラス (leaf class) は子のクラスを持たないクラスです。 このクラスには qdisc がひとつ所属します。 この qdisc はそのクラスからのデータ送信を決定します。 クラスを作ると、fifo qdisc が所属します。 子クラスを追加すると、この qdisc は削除されます。 葉クラスのこの fifo qdisc は、より適切な他の qdisc と置き換え可能です。 この qdisc をクラスフルな qdisc と置き換えれば、 さらにクラスを追加することもできます。

クラス選別器 (classifier)

  クラスフルな qdisc では、どのクラスにパケットを送るかを決める必要があります。 これはクラス選別器を用いて行われます。

フィルタ (filter)

  クラス選別はフィルタを用いて行うこともできます。 フィルタには多数の条件が含まれ、 これらのどれかにマッチすると、フィルタがマッチしたことになります。

スケジューリング (scheduling)

  クラス選別器を用いると、 qdisc は特定のパケットを他より先に送信するような決定を下すことができます。 この処理はスケジューリングと呼ばれ、 例えば前に紹介した pfifo_fast qdisc で行われていたものです。 スケジューリングは「並び換え (reordering)」と呼ばれることもありますが、 これはやや紛らわしいです。

帯域制限 (shaping)

  パケットを送信前に遅延させ、 トラフィックが設定された最高速度を越えないようにする処理のことです。 帯域制限は出口で行われます。 話し言葉では、パケットを破棄してトラフィックを遅くすることも、 帯域制限と呼ばれることが多いです。

監視制限 (policing)

  パケットを遅延または破棄して、 トラフィックを設定したバンド幅に収めることです。 Linux における監視制限ではパケットの破棄のみが可能で、 遅延はできません。「入口キュー」は存在しないのです。

処理保存的 (Work-Conserving)

  処理保存的な qdisc では、可能な場合にはつねにパケットを配送します。 つまり、ネットワークアダプタが送信可能 (出力 qdisc の場合) な状態にあれば、 決してパケットを遅延させません。

処理非保存的 (non-Work-Conserving)

  例えばトークンバケツフィルタのように、 パケットをある時間保持してバンド幅を制限するようなキューもあります。 これはつまり、 可能な場合であってもパケットの引き渡しを拒否するようなこともある、 ということです。

ではここでこれらの用語を整理するため、 これらがどの場所にあるのかを見てみることにしましょう。

                    Userspace programs
                         ^
                         |
         +---------------+-----------------------------------------+
         |               Y                                         |
         |    -------> IP Stack                                    |
         |   |              |                                      |
         |   |              Y                                      |
         |   |              Y                                      |
         |   ^              |                                      |
         |   |  / ----------> Forwarding ->                        |
         |   ^ /                           |                       |
         |   |/                            Y                       |
         |   |                             |                       |
         |   ^                             Y          /-qdisc1-\   |
         |   |                            Egress     /--qdisc2--\  |
      --->->Ingress                       Classifier ---qdisc3---- | ->
         |   Qdisc                                   \__qdisc4__/  |
         |                                            \-qdiscN_/   |
         |                                                         |
         +----------------------------------------------------------+

この ASCII 図は Jamal Hadi Salim によるものです。

大きなブロックはカーネルを表しています。 一番左にある矢印は、ネットワークからこのマシンに入ってきたトラフィックです。 これはまず入口 (ingress) の qdisc に入ります。 ここではパケットにフィルタを適用し、 破棄するかどうかを決定できます。これは「監視制限」と呼ばれます。

これは非常に初期の段階で、カーネルの大部分がパケットを見る前です。 ですので、CPU パワーをあまり使わずに トラフィックをごく初期に捨てたければ、 ここが非常に良い場所です。

継続処理を許されたパケットには、 ローカルなアプリケーションに向かうものがあります。 この場合そのパケットは処理のために IP スタックに入り、 ユーザ空間のプログラムに渡されます。 パケットはアプリケーションには入らずにフォワードされることもあり、 この場合は出口 (egress) に向かいます。 ユーザー空間のプログラムがデータを配送することもありますが、 このデータは検査され、出口のクラス選別器へとフォワードされます。

ここでパケットは調査され、たくさんある qdisc のいずれかへキューされます。 設定されていないデフォルトでは、 ひとつの出口 qdisc だけがインストールされています。 これは pfifo_fast で、これが常にパケットを受信します。 これは「エンキューイング (enqueueing)」と呼ばれます。

ここでパケットは qdisc に収まり、 ネットワークインターフェースを通して送信するようカーネルが指令するのを待ちます。 このように送信されることは「デキューイング (dequeueing)」と呼ばれます。

この絵はネットワークアダプタがひとつしかない場合でも成立します。 カーネルを出入りしている矢印は、そのまま絵の通りには取らないで下さい。 各ネットワークアダプタには、入口と出口両方のフックがあるのです。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

まとめると、よくある階層図は次のようになります:

                         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 に送るように決めたのです。

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

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

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

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

#### 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 は処理保存的なスケジューラなのです。

##### 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 となります (以下同様)。

##### 設定例

この木を作ることを考えましょう:

              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 を受けとった、優先度の低いバンドへは一切向かっていません。

#### (有名な) 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 はうまく動作します。 ここに提供した記述を用いれば、 ほとんどの場合、うまく動作するような設定が可能だと思います。

##### 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 のクラスにはペナルティがつき、 実効的な優先度が下がります。非常に賢くかつ複雑になっています。

##### 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 階層に属するクラスは、 すべて同じメジャー番号を持たなければならないことに注意してください。

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

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

isolated/sharing

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

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

bounded/borrow

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

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

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

##### 設定例

                   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 が常に保証されることもわかります。

##### 他の 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' はソースを見ただけで試してません。

#### 階層的トークンバケツ (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 の利用を考えましょう。

##### 設定例

前述の 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 を利用するようにしたので、公平性もタダで実現できています!

### フィルタによるパケットのクラス選別

あるパケットをどのクラスが処理すべきかを決めるには、 いわゆる「クラス選別チェイン」がその度ごとに呼び出されます。 このチェインは、その決断を行うクラスフルな qdisc に属する すべてのフィルタからなります。

ではまた木を。実際には木ではないのですが。

                        root 1:
                          |
                        _1:1_
                       /  |  \
                      /   |   \
                     /    |    \
                   10:   11:   12:
                  /   \       /   \
               10:1  10:2   12:1  12:2

パケットのエンキューイングをする際、 それぞれの枝でどのような命令をすべきか、 フィルタチェインに問い合わせがいきます。 よくある設定は、1:1 のフィルタがパケットを 12: に向け、 12: のフィルタがそのパケットを 12:2 に送る、といったようなものです。

この後者の規則を直接 1:1 に所属させることもできますが、 より特殊なテストはチェインの先に置く方が、効率的になります。

なお、パケットを「上向き」にフィルタすることはできません。 また HTB では、すべてのフィルタは root に所属させなければなりません。

もう一度。パケットのエンキューは下に向かってのみ行われます。 それらのパケットがデキューされる際には、 インターフェースのある上の方に上がっていくわけです。 枝の先の方に行ってもネットワークアダプタはありません。

#### シンプルなフィルタの例

クラス選別器の章で説明する通り、 非常に複雑な文法が利用でき、文字通りあらゆるものにマッチが可能です。 まずは、わかりやすい例から始めましょう。 幸いこれらは簡単です。

ここでは '10:' という PRIO qdisc があるといます。ここには 3 つのクラスがあり、 ポート 22 からのトラフィックを最も優先度の高いバンドに割り当てたいとしましょう。 このときのフィルタは次のようになります。

    # tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match \
      ip dport 22 0xffff flowid 10:1
    # tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match \
      ip sport 80 0xffff flowid 10:1
    # tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2

何をやってるのでしょうか? ここでは: eth0 の node 10: に、優先度 1 の u32 フィルタを加えています。 このフィルタは IP 送信先ポートが 22 (だけ) にマッチし、バンド 10:1 へ送ります。 次の行では発信元ポートが 80 のパケットについて同じことをやっています。 最後の行では、マッチしなかったものはすべて、 次に優先度の高いバンド 10:2 に送っています。 (訳注: 0xffff はマッチの前に取る AND マスクです)

ここには 'eth0' またはそのインターフェースの名前が必要です。 各インターフェースは、それぞれ他と重ならないかたちで割り当てられた、 ハンドルの名前空間を保持しているからです。

IP アドレスで選別するには、次のようにします。

    # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 \
      match ip dst 4.3.2.1/32 flowid 10:1
    # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 \
      match ip src 1.2.3.4/32 flowid 10:1
    # tc filter add dev eth0 protocol ip parent 10: prio 2      \
      flowid 10:2

これは 4.3.2.1 へのトラフィックと、1.2.3.4 からのトラフィックに 最高の優先度のキューに入れ、その他を 2 番目のキューに入れています。

マッチ条件は連結できます。1.2.3.4 のポート 80 から来たトラフィックに マッチさせるには、次のようにします。

    # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 \
      match ip sport 80 0xffff flowid 10:1

#### 通常必要なフィルタコマンドすべて

ここで説明する帯域制限コマンドは、たいていこの前置きから始まっています:

    # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 ..

これらはいわゆる 'u32' マッチで、パケットのどの部分にもマッチします。

発信元/送信先アドレスによるマッチ

  発信元マスクは 'match ip src 1.2.3.0/24'、 送信先マスクは 'match ip src 4.3.2.0/24' です。 単一のホストにマッチさせるには、/32 を用いるかマスクを省略します。

発信元/送信先ポートによるマッチ (ip プロトコルは問わない)

  発信元は 'match ip sport 80 0xffff'、 送信先は 'match ip dport 0xffff'。

ip プロトコル (tcp, udp, icmp, gre, ipsec)

  /etc/protocols の番号を用います。 例えば icmp は 1 ですから、'match ip protocol 1 0xff'。

fwmark によるマッチ

  ipchains か iptables でパケットに印を付けて、 その印をインターフェース間のルーティングで利用することもできます。 これは例えば、eth0 から入ってきたパケットだけを eth1 で絞る場合などにとても便利です。文法は次の通り:

      # tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1

  これは u32 マッチではないことに注意。

  印を付けるには次のようにします。

      # iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6

  この番号 6 は任意に変更できます。

  tc フィルタの文法全部を理解するのが面倒なら、 iptables を使うことにして、fwmark による選別だけを覚えてください。

TOS フィールドによるマッチ

  対話的な、minimum delay のトラフィックを選別するには:

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

  バルクトラフィックには 0x08 0xff を用います。

フィルタコマンドについてもっと知りたい人は、 高度なフィルタの章を見てください。

### 中間キューイングデバイス (Intermediate queueing device :IMQ)

中間キューデバイスは qdisc ではないのですが、 その利用法は qdisc と深く関連しています。 linux では qdisc はネットワークデバイスに属しており、 デバイスにキューされたものは、すべてこの qdisc にキューされます。 このコンセプトには、2 つの限界があります。

1. 出口での帯域制限しかできない (入口 qdisc もありますが、 クラスフルな qdisc に比べると非常に機能が限られています)。

2. ある qdisc はひとつのインターフェースにおけるトラフィックしか見ないので、 グローバルな制限を置くことはできません。

IMQ はこれら 2 つの制限を解決するためにあるのです。 簡単にいうと、qdisc で選んだものすべてを IMQ に置けるのです。 専用の印がついたパケットは netfilter の NF_IP_PRE_ROUTING と NF_IP_POST_ROUTING 各フックで割り込みを受け、 imq デバイスに属する qdisc へと渡されます。 パケットに印を付けるには、iptables のターゲットを用います。

これによって、 つまりある場所から来たパケットに印を付けて入口での帯域制限もできますし、 また複数のインターフェースをクラスとみなしてグローバルな制限を設定できます。 他にも例えば http トラフィックを qdisc に入れたり、 新しい接続要求を qdisc に入れたり、などいろいろなことができます。

#### 設定例

最初に思い浮かぶのは、入口での帯域制限を用いて 確実に保証されたバンド幅を使えるようにすることでしょう ;) 設定は他のインターフェースでのものと似ています。

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

    tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k

    tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
    tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit

    tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
    tc qdisc add dev imq0 parent 1:20 handle 20: sfq

    tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match \
    		ip dst 10.0.0.230/32 flowid 1:10

この例では u32 をクラス選別に用いています。 他のクラス選別器も、期待通りに動作します。 次に、トラフィックを選別して imq0 にエンキューされるよう印を付けます。

    iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0

    ip link set imq0 up

iptables の IMQ ターゲットは、mangle テーブルの PREROUTING チェインと POSTROUTING チェインで使えます。 文法は以下の通り:

    IMQ [ --todev n ]	n : imq デバイスの番号

ip6tables ターゲットも提供されています。

トラフィックがエンキューされるのは、ターゲットに当たったときではなく、 それ以降になります。実際にトラフィックが imq デバイスに入る場所は、 トラフィックの方向 (in/out) によって異なります。 これらは iptables で用いられる、定義済みの netfilter フックです。

    enum nf_ip_hook_priorities {
            NF_IP_PRI_FIRST = INT_MIN,
            NF_IP_PRI_CONNTRACK = -200,
            NF_IP_PRI_MANGLE = -150,
            NF_IP_PRI_NAT_DST = -100,
            NF_IP_PRI_FILTER = 0,
            NF_IP_PRI_NAT_SRC = 100,
            NF_IP_PRI_LAST = INT_MAX,
    };

入口トラフィックでは、imq は自分自身を NF_IP_PRI_MANGLE + 1 の 優先度を持つとして登録します。つまりパケットは、 mangle テーブルの PREROUTING チェインを通過した後に、 直接 imq デバイスに入るのです。

出口の imq では、NF_IP_PRI_LAST を用います。 つまり、filter テーブルが破棄したパケットはバンド幅を占有すべきでない、 という事情を尊重しています。

パッチとやや詳しい情報が、 imq のサイト にあります。

## 複数のインターフェースを用いた負荷分散

これを実現する方法はいくつか存在します。 簡単で直截的なのは、'TEQL' - "True" (または "trivial") link equalizer です。 キューイングに絡むものはたいていそうですが、負荷分散も両方向に動作します。 完全な効果を得るには、接続の両端の関与が必要です。

このような状況を考えてください。

                     +-------+   eth1   +-------+
                     |       |==========|       |
     'network 1' ----|   A   |          |   B   |---- 'network 2'
                     |       |==========|       |
                     +-------+   eth2   +-------+

A と B はルータで、いまは両方で Linux が動作しているとしましょう。 トラフィックが network 1 から network 2 に向かうと、 ルータ A は B に向かう 2 つの接続に、パケットを分散させる必要があります。 ルータ B では、これを受信できるような設定が必要です。 逆方向についても同じで、パケットが network 2 から network 1 に向かうとき、 ルータ B はそれらのパケットを eth1 と eth2 の両方を使って送る必要があります。

この分散させる部分が、'TEQL' デバイスによってなされます。 次のようになります (いちばん簡単な設定):

    # tc qdisc add dev eth1 root teql0
    # tc qdisc add dev eth2 root teql0
    # ip link set dev teql0 up

最後の 'ip link set up' コマンドを忘れないこと!

これは両方のホストで実行する必要があります。 このデバイス teql0 は、送信するパケットを、 基本的にラウンドロビンで eth1 と eth2 に分散させます。 データが teql デバイスから入ってくることは決してありません。 データは '生 (raw)' の eth1 と eth2 に到着します。

まだデバイスができただけですから、これらに適切なルーティング設定が必要です。 これを行う方法のひとつは、/31 のネットワークを両接続に割り当て、 teql デバイスにも同じように /31 のネットワークを割り当てるやり方です:

ルータ A にて:

    # ip addr add dev eth1 10.0.0.0/31
    # ip addr add dev eth2 10.0.0.2/31
    # ip addr add dev teql0 10.0.0.4/31

ルータ B にて:

    # ip addr add dev eth1 10.0.0.1/31
    # ip addr add dev eth2 10.0.0.3/31
    # ip addr add dev teql0 10.0.0.5/31

これでルータ A は 10.0.0.1, 10.0.0.3, 10.0.0.5 に対し、 2 つの実際の接続と、1 つの TEQL デバイス経由で ping できるようになったはずです。ルータ B も、 10.0.0.0, 10.0.0.2, 10.0.0.4 にこれらの接続経由で ping できるはずです。

ここまでが動作したら、ルータ A では 10.0.0.5 を network 2 に向かう経路にし、 ルータ B では 10.0.0.4 を network 1 に向かう経路にします。 特殊な場合として、network 1 が自宅のネットワーク、 network 2 がインターネットとしますと、 ルータ A では 10.0.0.5 を自分のデフォルトゲートウェイになるようにします。

### 注意

何事も見てくれほど簡単ではありません。 ルータ A, B 両方で、eth1 と eth2 の return path filtering (戻り経路によるフィルタリング) は無効にしなければなりません。 こうしないと、これらのインターフェースは 自分自身以外に向かっている ip パケットを落としてしまいます。

    # echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter
    # echo 0 > /proc/sys/net/ipv4/conf/eth2/rp_filter

さて、次にはパケットの並び替え (reordering) に関する面倒な問題があります。 仮に 6 つのパケットを A から B に送らなければならないとしましょう。 ルータ B がこれを順番どおりに 1, 2, 3, 4, 5, 6 と受け取れば理想的です。 しかし実際には、 カーネルがこれを 2, 1, 4, 3, 6, 5 のように受け取る可能性も非常に高いのです。 問題は、これによって TCP/IP が混乱してしまう、ということです。 たくさんの TCP/IP セッションを別々に運んでいる回線では問題になりませんが、 複数の回線をまとめて、ひとつのファイルの ftp を高速化することはできません。 ただし受信か送信を行っている OS が Linux なら別で、 Linux は単純な並び替えでは簡単には混乱しません。

いずれにしても、多くのアプリケーションでは、 回線の負荷分散は非常に良い考えです。

### 他の可能性

William Stearns は、高度なトンネル設定を用いて、 複数の相関のないインターネット接続を効率的に利用しています。 やり方は 彼のトンネリングページ に書かれています。

この HOWTO でも、将来これについて取り上げるかもしれません。

## Netfilter と iproute でパケットに印を付ける

ここまで、iproute がどのように動作するかについて見てきましたが、 netfilter についても何回か言及しました。 このあたりで、Rusty's Remarkably Unreliable Guides を通して眺めておくといいかもしれません。 netfilter そのものは こちら にあります。

netfilter を用いるとパケットをフィルタしたり、 ヘッダを書き換えたりできます。 特殊な機能として、パケットに数値の印を付けることもできます。 これには --set-mark オプションを使います。

例として、以下のコマンドはポート 25 に向けた、 送信メールのパケットにすべて印を付けています。

    # iptables -A PREROUTING -i eth0 -t mangle -p tcp --dport 25 \
     -j MARK --set-mark 1

いま複数の回線があるとして、 ひとつは高速ですが従量制で高く、 もうひとつは低速ですが定額で安いとしましょう。 ほとんどの場合、送信メールは安いほうの経路で送りたいでしょう。

既にパケットには '1' という印を付けました。 ではここで経路ポリシーデータベースに、 このような動作をするよう指示しましょう。

    # echo 201 mail.out >> /etc/iproute2/rt_tables
    # ip rule add fwmark 1 table mail.out
    # ip rule ls
    0:	from all lookup local
    32764:	from all fwmark        1 lookup mail.out
    32766:	from all lookup main
    32767:	from all lookup default

ここで mail.out テーブルに、 遅いが安い回線に向かう経路を作ります。

    # /sbin/ip route add default via 195.96.98.253 dev ppp0 table mail.out

これで終わりです。例外を設けたい場合、やり方はたくさんあります。 netfilter の文を修正して特定のホストを除外してもいいですし、 除外ホストに対して、main テーブルを指すような、 優先度が高いルールを挿入する方法もあります。

この機能は、TOS ビットを尊重するのにも使えます。 Type of Service の違いによってパケットに印を付け、 それらに対して作用するルールを作ればよいのです。 こうすれば、例えば ISDN 接続を、 対話的なセッション専用にすることもできます。

言うまでもありませんが、これは NAT (マスカレード) を行っているホストでも問題なく動作します。

重要: パケットへの印付けは、少なくとも MASQ と SNAT とに衝突してしまう、 という報告を受けました。これに関する Rusty Russell の説明は この投稿で与えられています。 正しく動作させるには、reverse path filter を無効にしてください。

注意: パケットに印を付けるには、 いくつかのカーネルオプションを有効にする必要があります。

    IP: advanced router (CONFIG_IP_ADVANCED_ROUTER) [Y/n/?]
    IP: policy routing (CONFIG_IP_MULTIPLE_TABLES) [Y/n/?]
    IP: use netfilter MARK value as routing key (CONFIG_IP_ROUTE_FWMARK) [Y/n/?]

の も参照してください。

## 高度なフィルタによるパケットのクラス(再)選別

クラスフルなキューイング規則の節で説明したように、 パケットを選別してサブキューに送る際にはフィルタが必要です。 これらのフィルタはクラスフルな qdisc の内部から呼ばれます。

不完全ではありますが、利用できるクラス選別器のリストです。

fw

  ファイアウォールがパケットをどのように印付けしたかに基づいて決定を行います。 tc フィルタの文法を勉強したくない人には、安易な解決法です。 詳細はキューイングの章を見てください。

u32

  パケット内部のフィールド (発信元 IP アドレスなど) に基づいて決定を行います。

route

  パケットが通ることになる経路に基づいて決定を行います。

rsvp, rsvp6

  RSVP に基づいてパケットをルーティングします。 これが有用なのは管理下にあるネットワークだけです。 インターネットは RSVP をちゃんと見ません。

tcindex

  DSMARK qdisc で用いられます。関連する節を見てください。

パケットのクラス選別を行う方法は一般には何種類か存在し、 そのどれを用いるかは、通常は好みの問題に帰着します。

クラス選別器はたいてい、いくつかの共通な引き数を取ります。 参考のためにここにリストしておきます。

protocol

  このクラス選別器が受け付けるプロトコル。 通常は IP トラフィックだけを受け付けることになるでしょう。必須です。

parent

  このクラス選別器が属しているハンドル。 このハンドルは、すでに存在しているクラスでなければなりません。必須です。

prio

  クラス選別器の優先度。小さい数値が先にテストされます。

handle

  フィルタを一意に識別するためのものです。

以降の節では、HostA に向かうトラフィックを絞ることを考えます。 ルートクラスは 1: と設定されていて、 選別されたトラフィックの送り先となるクラスは 1: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 に限らず、すべてのフィルタに適用されます。

#### 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 のネットワークのどこかになっているものです。 以上、いくつか例を解析したところで、 学んだことをまとめてみることにしましょう。

#### 汎用セレクタ (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

#### 固有セレクタ (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 フィルタの内部を垣間見せてくれます。 つまり固有なルールは常に汎用ルールに書き換えられ、 そしてカーネルのメモリにはこのかたちで保存されるのです。 ここから別の結論も導けます。 すなわち `tcp` と `udp` はまったく同じで、 これが `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

### `route` クラス選別器

このクラス選別器は、経路テーブルの結果に基づいてフィルタ動作をします。 パケットがクラス間を遷移して "route" フィルタでマークされたクラスに到達すると、 このフィルタは経路テーブルの情報に基づいてパケットを振り分けます。

    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 route

ここでは route クラス選別器を親ノード 1:0 に priority 100 で追加しています。 パケットがこのノードに到達すると (ここでは root なのですぐに到達します)、 この選別器は経路テーブルを見ます。 マッチしたらこのパケットは指定されたクラスに送られ、 priority 100 が与えられます。 続いて、これを動作させるために、 適切なルーティングエントリを追加します。

ここでは、送信先または発信元によって 'realm' を定義します。 やり方は、例えば次のようになります。

    # ip route add Host/Network via Gateway dev Device realm RealmNumber

例えば、送信先のネットワークが 192.168.10.0 の場合に realm 番号 10 を与えるには次のようにします:

    # ip route add 192.168.10.0/24 via 192.168.10.1 dev eth1 realm 10

route フィルタを追加する際には、 ネットワークやホストを意味する realm 番号が利用でき、 これによって経路をフィルタにどのようにマッチさせるかを指定できます。

    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
      route to 10 classid 1:10

このルールは、ネットワーク 192.168.10.0 に向かうパケットにマッチし、 id が 1:10 のクラスに送ります。

route フィルタは経路の発信元を用いてもマッチできます。 例えば Linux ルータの eth2 にサブネットワークが接続されているとします。

    # ip route add 192.168.2.0/24 dev eth2 realm 2
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
      route from 2 classid 1:2

このフィルタでは 192.168.2.0 サブネットワークから来た (realm 2) パケットにマッチし、id が 1:2 のクラスに送ります。

### 監視制限 (policing) フィルタ

さらに複雑な設定を可能にするため、 あるバンド幅を越えた場合にマッチするフィルタを導入することもできます。 ある速度を越えたら一切マッチをやめてしまうようなフィルタを宣言することも、 あるいはある速度を越えた分に対してのみマッチさせないようにもできます。

つまり監視制限を 4mbit/s としたときに 5mbit/s のトラフィックがあった場合、 5mbit/s 全部に対してマッチさせないこともできますし、 あるいは 1mbit/s にのみマッチさせず 4mbit/s 分は設定されたクラスに送るようにすることもできます。

バンド幅が設定速度を越えた場合の選択肢としては、 パケットを破棄する、再クラス選別する、 別のフィルタにマッチを試みさせる、などがあります。

#### 監視制限のやり方

監視制限のやり方は、基本的に 2 つあります。 カーネルに「評価器 (estimator)」を組み込むと、 そのカーネルは各フィルタの依頼を受け、 通過させたトラフィック量が多いか少ないかを測定できます。 これらの評価器は CPU では非常に容易で、一秒に 25 回ずつ、 通過したデータ量を測定してそれらのビットレートを計算します。

別の方法では、再びトークンバケツフィルタが登場します。 こんどはフィルタの内部に置くわけです。 TBF は、設定したバンド幅までのトラフィックにマッチします。 それ以上のパケットがくると、越えた分だけが、 制限越えに対して設定した動作の対象となります。

##### カーネル評価器を用いる

これはとてもシンプルで、パラメータは avrate のひとつだけです。 流量が avrate 以内に留まっていると、フィルタはこのトラフィックを 設定された classid にクラス選別します。 流量がこれを越えると、指定された動作 (このデフォルトは「再クラス選別 (reclassify)」) を行います。

カーネルはバンド幅に指数重み付け移動平均を用います。 これは短期間のバーストにはあまり敏感ではありません。

##### トークンバケツフィルタを用いる

次のパラメータを用います:

- burst/buffer/maxburst

- mtu/minburst

- mpu

- rate

これらの動作は、トークンバケツフィルタの節で説明したものとほぼ同じです。 しかし注意してほしいのですが、TBF 制限器の mtu を小さくしすぎると、 まったくパケットが通りません。これに対し、出口 (egress) での TBF qdisc では、 パケットの通過を遅くするだけです。

別の違いは、制限器ではパケットを通すか破棄するかしかできないことです。 パケットを保持して遅延させることはできません。

#### 制限を越えたときの動作

制限を越えたと判断すると、フィルタは指定された「動作」を行います。 現在 4 つの動作が利用できます。

continue

  このフィルタはマッチしませんが、他のフィルタにマッチを試みさせます。

drop

  これは極めて厳しい選択肢で、ある速度を越えたトラフィックを単に破棄します。 これは入口の監視制限で良く用いられ、利用を制限します。 例えば、5mbit/s を越えるパケットを送ると落ちてしまうような ネームサーバがある場合、入口フィルタを用いれば、 これ以上のパケットを送らないようにできます。

Pass/OK

  トラフィックを問題なく通します。複雑なフィルタを無効にしたいけれども、 そのまま配置はしておきたい場合に使います。

reclassify

  ほとんどの場合はベストエフォートの再クラス選別になります。 これがデフォルトの動作です。

#### 例

現在知られている唯一の実例は、 の節で説明しています。

到着する icmp トラフィックを 2kbit に制限し、 これを越えたパケットを置とします。

    tc filter add dev $DEV parent ffff: \
        protocol ip prio 20 \
        u32 match ip protocol 1 0xff \
        police rate 2kbit buffer 10k drop \
        flowid :1

パケットのサイズを適当な値に制限します (ここでは 84 バイトより大きなパケットはすべて落とします)。

    tc filter add dev $DEV parent ffff: \
       protocol ip prio 20 \
       u32 match tos 0 0 \
       police mtu 84 drop \
       flowid :1

この方法を用いると、すべてのパケットを落とせます。

    tc filter add dev $DEV parent ffff: \
       protocol ip prio 20 \
       u32 match ip protocol 1 0xff \
       police mtu 1 drop \
       flowid :1

これは実際には 1 バイトより大きな icmp パケットを落とします。 サイズ 1 バイトのパケットは理論的にはあり得ますが、 実際のネットワークではまず存在しないでしょう。

### ハッシュフィルタ: 超高速大量フィルタリング

たくさんのクライアントやコンピュータがあって数千ものルールを必要とし、 そのすべてに別々の QoS 指定を行うと、カーネルはこれらのルールへのマッチに、 多くの時間を費やすことになるでしょう。

デフォルトでは、すべてのフィルタはひとつの大きなチェインに共存しており、 priority の順にマッチされていきます。1000 のルールがあったら、 パケットに対して何を行うか決めるには、 1000 回のチェックが必要となるかもしれません。

それぞれ 4 つのルールからなる 256 個のチェインのほうが、 マッチの速度はずっと素早くなります。 ただしパケットを、これら 256 のチェインうち、 適切なルールがあるものに分散させることが必要になります。

これはハッシュを用いると可能になります。いまケーブルモデムの顧客が ネットワークに 1024 名いるとしましょう。IP アドレスは 1.2.0.0 から 1.2.3.255 までで、それぞれ 'lite', 'regular', 'premium' などの、 別々のカテゴリに属しているものとします。 この場合は以下のような 1024 のルールが必要になるでしょう。

    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.0.0 classid 1:1
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.0.1 classid 1:1
    ...
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.3.254 classid 1:3
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.3.255 classid 1:2

IP アドレスの末尾部分を「ハッシュキー」として利用すれば、 これを高速化できます。こうすると 256 のテーブルができ、 その最初のものはこのようになります。

    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.0.0 classid 1:1
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.1.0 classid 1:1
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.2.0 classid 1:3
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.3.0 classid 1:2

次のテーブルはこのようになります。

    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src \
      1.2.0.1 classid 1:1
    ...

このようにして、最大で 4 つのチェック、 平均で 2 つのチェックしか必要なくなります。

設定は極めて複雑ですが、これだけ多くのルールがあるような状況では、 十分作業に見合います。まず root にフィルタを作り、 256 のエントリがあるテーブルを作ります。

    # tc filter add dev eth1 parent 1:0 prio 5 protocol ip u32
    # tc filter add dev eth1 parent 1:0 prio 5 handle 2: protocol ip u32 divisor 256

ここでいま作ったテーブルのエントリにいくつかルールを追加します。

    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
            match ip src 1.2.0.123 flowid 1:1
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
            match ip src 1.2.1.123 flowid 1:2
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
            match ip src 1.2.3.123 flowid 1:3
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: \
            match ip src 1.2.4.123 flowid 1:2

これはエントリ 123 で、1.2.0.123, 1.2.1.123, 1.2.2.123, 1.2.3.123 へのマッチを含んでおり、それぞれを 1:1, 1:2, 1:3, 1:2 に送ります。 ここでハッシュバケツは 16 進数で指定しなければならないことにご注意ください。 0x7b は 123 です。

次に「ハッシュフィルタ」を作ります。これはトラフィックを、 ハッシュテーブルの正しいエントリに向けます。

    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 800:: \
            match ip src 1.2.0.0/16 \
            hashkey mask 0x000000ff at 12 \
            link 2:

はい、ここで数字のいくつかについては説明が必要でしょう。 デフォルトのハッシュテーブルは 800:: という名前になります。 すべてのフィルタはここから始まります。次に IP ヘッダの 12, 13, 14, 15 バイト目にある発信元アドレスを選び、 この最後の部分にのみ注目していることを指定しています。 これは先に作ったハッシュテーブル 2: に送っています。

かなり複雑ですが、実際に動作しますし性能向上は驚異的です。 この例はさらに改良が可能で、 理想的には各チェインに 1 つのフィルタしか入っていない状況にできます!

### IPv6 トラフィックのフィルタリング

#### なぜ IPv6 では tc フィルタが動かないのか

Linux カーネルでは、 経路ポリシーデータベース (Routing Policy Database: RPDB) が IPv4 のルーティング・アドレッシング構造を置き換え、 この HOWTO で説明している素晴らしい機能の源となっています。 残念ながら、Linux での IPv6 構造は、コアの構造の外側で実装されています。 共有している機能もあるのですが、基本的には RPDB の構造は、 IPv6 のルーティング・アドレッシング構造とは別々なのです。

この状況はもちろん変わるでしょう。少々待てば良いだけです。

FIXME: 既に作業している人がいれば、何かアイデアは? 計画は?

#### IPv6 パケットに ip6tables で印を付ける

ipv6tables はパケットに印を付け、数値を割り当てることができます。

    # ip6tables -A PREROUTING -i eth0 -t mangle -p tcp -j MARK --mark 1

しかしこうしても、このパケットは RPDB 構造には渡されないので、 役に立ちません。

#### u32 選別器を使って IPv6 パケットにマッチさせる

IPv6 は通常 SIT トンネルにカプセルされて IPv4 ネットワークを輸送されます。 このようなトンネルの設定については、IPv6 トンネリングの節を参照してください。 この場合、IPv6 パケットをペイロードに持つ IPv4 パケットに対して、 フィルタを適用することが可能です。

次のフィルタは、IPv6 をカプセルした IPv4 パケットすべてにマッチします。

    # tc filter add dev $DEV parent 10:0 protocol ip prio 10 u32 \
                match ip protocol 41 0xff flowid 42:42

この方向ですすめましょう。IPv6 パケットが IPv4 経由で送信されたとし、 これらのパケットには何のオプションもセットされていないとします。 次のフィルタを用いると、オプションのない、 IPv4 中の IPv6 中の ICMPv6 にマッチします。 0x3a (58) は ICMPv6 の next ヘッダタイプです。

    # tc filter add dev $DEV parent 10:0 protocol ip prio 10 u32 \
               match ip protocol 41 0xff \
               match u8 0x05 0x0f at 0 \
               match u8 0x3a 0xff at 26 \
               flowid 42:42

送信先 IPv6 アドレスにマッチさせるには、もう少々作業が必要です。 次のフィルタは送信先アドレスが 3ffe:202c:ffff:32:230:4fff:fe08:358d: である場合にマッチします。

    # tc filter add dev $DEV parent 10:0 protocol ip prio 10 u32 \
                match ip protocol 41 0xff \
                match u8 0x05 0x0f at 0 \
                match u8 0x3f 0xff at 44 \
                match u8 0xfe 0xff at 45 \
                match u8 0x20 0xff at 46 \
                match u8 0x2c 0xff at 47 \
                match u8 0xff 0xff at 48 \
                match u8 0xff 0xff at 49 \
                match u8 0x00 0xff at 50 \
                match u8 0x32 0xff at 51 \
                match u8 0x02 0xff at 52 \
                match u8 0x30 0xff at 53 \
                match u8 0x4f 0xff at 54 \
                match u8 0xff 0xff at 55 \
                match u8 0xfe 0xff at 56 \
                match u8 0x08 0xff at 57 \
                match u8 0x35 0xff at 58 \
                match u8 0x8d 0xff at 59 \
                flowid 10:13

同じテクニックはサブネットへのマッチにも使えます。 例えば 2001:: の場合は次のようになります。

    # tc filter add dev $DEV parent 10:0 protocol ip prio 10 u32 \
                match ip protocol 41 0xff \
                match u8 0x05 0x0f at 0 \
                match u8 0x20 0xff at 28 \
                match u8 0x01 0xff at 29 \
                flowid 10:13

## カーネルのネットワークパラメータ

カーネルにはたくさんのパラメータがあり、環境に応じて調整できます。 これまでの通り、99% はデフォルトのパラメータでまったく問題ないのですが、 しかしこの文書はだてに Advanced HOWTO と名乗っているわけではありません!

ここでの興味の対象は /proc/sys/net です。ちょっとご覧になってみてください。 このすべてが最初からここで全て文書化できるわけではありませんが、 作業は継続していくつもりです。

それまでの間は、Linux のカーネルソースをご覧になり、 Documentation/filesystems/proc.txt を読んでください。 ほとんどの機能はこのファイルで説明されています。

(FIXME)

### 戻り経路フィルタ (Reverse Path Filtering)

デフォルトでは、ルータはすべてをルーティングします。 パケットが「明らかに」自分のネットワークには属していなくてもです。 よくある例は、プライベートの IP 空間がインターネットに漏れてしまう問題です。 195.96.96.0/24 に向かう経路があるインターフェースに対しては、 212.64.94.1 から発したパケットは、本来到着しないはずです。

ほとんどの人はこの機能を無効にしたいと思うはずですから、 カーネルハッカー達はこれを簡単できるようにしてくれました。 /proc 以下にあるファイルを使うと、カーネルに対してこの指示ができます。 この方法は戻り経路フィルタ (Reverse Path Filtering) と呼ばれています。基本的には、あるパケットに対する返信が、 そのパケットの入ってきたインターフェースに向かわない場合、 このパケットはインチキだとみなされて無視されることになります。

以下のコマンドを使うと、現在存在するインターフェース (と将来作られるインターフェース) すべてに対して、 この機能を有効にできます。

    # for i in /proc/sys/net/ipv4/conf/*/rp_filter ; do
    >  echo 2 > $i
    > done

上記の例を実行しておくと、Linux ルータの ISP 側インターフェースに到着したパケットが、 オフィスからやって来たよ、と称していると、そのパケットは破棄されます。 同様に、オフィスのサブネットからきたパケットが、 ファイアウォールの外側からやって来た、と称していると、 それも同じように破棄されます。

これは完全な戻り経路フィルタです。 デフォルトでは、直接接続しているネットワークの IP だけに基づいてフィルタを行います。 完全なフィルタを行うと、非対称なルーティングで問題が出るからです (パケットの入ってくる経路と出て行く経路が違う場合です。 例えば衛星通信や、動的 (bgp, ospf, rip) 経路がネットワークにある場合など。 衛星のパラボラからやって来たデータには、 通常の地上回線を通して返信することになります)。

もしこの例外があなたの環境に当てはまる (実際にそうかは、おそらくご存じでしょう) 場合は、 衛星データがやって来るインターフェースで rp_filter を無効にすれば良いです。 パケットが破棄されているかを確認したければ、 同じディレクトリにある log_martians ファイルを使って、 それらのイベントを syslog に記録するようカーネルに伝えます。

    # echo 1 >/proc/sys/net/ipv4/conf/<interfacename>/log_martians

FIXME: conf/&lcub;default,all&rcub;/* ファイルに設定するだけでいいのか? - martijn

### あまり知られていない設定

はい、変更できるパラメータはとてもたくさんあります。 ここではそれらすべてをリストしたいと考えています。 一部は Documentation/ip-sysctl.txt でも説明されています。

カーネルのコンパイル時に 'Configure as router and not host' に 'Yes' と答えていると、これらの設定のデフォルトが、 ここに示すものとは異なっているかもしれません。

Oskar Andreasson も、これらのフラグに関するページを公開しています。 ここのものより良いように思えますので、 彼のページ もチェックしてみてください。

訳注: この部分の訳出にあたっては、 2.2 カーネル付属文書 proc.txt の翻訳 を参考にさせていただきました。

#### ipv4 全体

一般的な注意ですが、ほとんどの速度制限機能は loopback に対しては効きません。 ですので、ローカルでのテストはしないでください。 制限は 'jiffies' 単位で与えられ、先に紹介したトークンバケツフィルタによって 適用されています。

カーネル内部の時計は、1 秒あたり 'HZ' 刻み (あるいは 'jiffies') で動作します。 Intel では 'HZ' は大抵の場合 100 です。よって *_rate ファイルを、 例えば 50 と設定すると、一秒当たり 2 パケットを許可することになります。 トークンバケツフィルタは、十分なトークンが溜まった場合、 最大 6 パケットまでのバーストを許すような設定になっています。

以下のリストの一部は、Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> と Andi Kleen <ak@muc.de> による /usr/src/linux/Documentation/networking/ip-sysctl.txt からコピーしてきたものです。

/proc/sys/net/ipv4/icmp_destunreach_rate

  カーネルは、パケットを配送できないと判断すると、そのパケットを破棄します。 そしてそのパケットの発信元に、ICMP メッセージをこの速度で送ります。

/proc/sys/net/ipv4/icmp_echo_ignore_all

  echo パケットに一切反応しません。 これはデフォルトでは設定しないでください。 しかし DoS 攻撃の中継に利用されてしまった場合には有用です。

/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts &lsqb;便利&rsqb;

  ネットワークのブロードキャストアドレスに ping すると、 すべてのホストが反応することになっています。 これを使うと洒落た利用不能攻撃ツールになります。 ので、これは 1 にして、このようなブロードキャストメッセージは無視してください。

/proc/sys/net/ipv4/icmp_echoreply_rate

  ある 1 つの目的地に対する echo リプライの送信速度。

/proc/sys/net/ipv4/icmp_ignore_bogus_error_responses

  これを設定すると、 ネットワーク上のホストが、 ブロードキャストアドレス向けとみなしたフレームに対して不正に反応したために発した ICMP error を無視します。

/proc/sys/net/ipv4/icmp_paramprob_rate

  あまり知られていない ICMP メッセージですが、 IP ないし TCP ヘッダが壊れている、不正なパケットに対する返信として送られます。 このファイルを用いると、このメッセージの送信速度を制御できます。

/proc/sys/net/ipv4/icmp_timeexceed_rate

  これは traceroute における「Solaris の真ん中の *」の原因として有名です。 ICMP Time Exceeded メッセージの送信速度を制限します。

/proc/sys/net/ipv4/igmp_max_memberships

  このホストで待ち受けする igmp (マルチキャスト) ソケットの最大数。 FIXME: 正しいか?

/proc/sys/net/ipv4/inet_peer_gc_maxtime

  FIXME: inet peer storage について少々説明を追加?

  ガベージコレクションを行う最大間隔。この間隔は、プール上の メモリ負荷が低い (あるいは無い) 場合に効力を持ちます。 jiffies 単位です。

/proc/sys/net/ipv4/inet_peer_gc_mintime

  ガベージコレクションを行う最小間隔。この間隔は、プール上の メモリ負荷が高い場合に効力を持ちます。 jiffies 単位です。

/proc/sys/net/ipv4/inet_peer_maxttl

  エントリの time-to-live の最大値。 使われなかったエントリは、プールにメモリ負荷がない場合 (つまりプールのエントリ数が非常に小さい場合)、 この期間がすぎると期限切れとなります。 jiffies 単位です。

/proc/sys/net/ipv4/inet_peer_minttl

  エントリの time-to-live の最小値。 パケットの再構成を行う側では、 フラグメントの time-to-live をカバーできる十分な大きさにしなければなりません。 この time-to-live の最小値は、プールのサイズが inet_peer_threshold より小さい場合に保証されます。 jiffies 単位です。

/proc/sys/net/ipv4/inet_peer_threshold

  INET peer storage の概算サイズです。 この閾値を越えると、エントリは積極的に削除され始めます。 この閾値は、エントリの time-to-live と、 ガベージコレクションの間隔にも影響します。 エントリが多くなると、time-to-live は短くなり、GC の間隔も短くなります。

/proc/sys/net/ipv4/ip_autoconfig

  このホストが IP 設定を RARP, BOOTP, DHCP などの機構で取得した場合、 このファイルの内容は 1 になります。そうでなければ 0 です。

/proc/sys/net/ipv4/ip_default_ttl

  パケットの Time To Live 値です。 64 にしておけば安全でしょう。巨大なネットワークでは増やしてください。 ただし興味だけでそうしないように。 経路のループがあった場合の被害が大きくなります。 場合によっては減らすことを考えてもよいでしょう。

/proc/sys/net/ipv4/ip_dynaddr

  インターフェースアドレスが動的に決まるダイヤルオンデマンドを用いている場合は これを設定しておく必要があります。オンデマンドのインターフェースが起動すると、 返信を見ていなかったローカルの TCP ソケットが、 正しいアドレスにバインドしなおします。 これによってインターフェースを起動させた接続が動作しないが、 二番目からは大丈夫になる、という問題が解決します。

/proc/sys/net/ipv4/ip_forward

  カーネルにパケットのフォワードをさせたいかどうか。 デフォルトでは無効になっています。

/proc/sys/net/ipv4/ip_local_port_range

  外に向かう接続における、ローカルポートの範囲。 実際のデフォルトは極めて小さく、1024 から 4999 です。

/proc/sys/net/ipv4/ip_no_pmtu_disc

  Path MTU discovery を無効にしたければこれを設定してください。 これは経路の Maximum Transfer Unit の最大値を決定する方法です。 の章における Path MTU discovery の節もご覧ください。

/proc/sys/net/ipv4/ipfrag_high_thresh

  IP フラグメントを再構成する際に用いる最大メモリ。 ipfrag_high_thresh バイトのメモリがこの目的に割り当てられると、 フラグメントハンドラは ipfrag_low_thresh になるまでパケットを投げ込みます。

/proc/sys/net/ipv4/ip_nonlocal_bind

  アプリケーションを自分のシステムには属していないデバイスにバインドさせたければ、 これを設定してください。 これはマシンの接続が永続的でない (あるいは動的な) 場合、 接続が切れたときにもサービスが起動して特定のアドレスに バインドできるようにするのに便利です。

/proc/sys/net/ipv4/ipfrag_low_thresh

  IP フラグメントの再構成に用いられるメモリの最小値。

/proc/sys/net/ipv4/ipfrag_time

  IP フラグメントをメモリに保持する時間 (秒単位)。

/proc/sys/net/ipv4/tcp_abort_on_overflow

  ブール値のフラグで、接続要求がたくさん来たときの動作を制御します。 有効になっていると、サービスが過負荷になったとき、 カーネルは積極的に RST パケットを送るようになります。

/proc/sys/net/ipv4/tcp_fin_timeout

  ソケットをこちら側からクローズしたときに、FIN-WAIT-2 状態に保つ時間。 通信先が壊れていると、先方でのクローズをずっと行わなかったり、 あるいは予期しない形で死んでしまうかもしれません。 デフォルトの値は 60 秒です。2.2 で用いられていた通常の値は 180 秒で、 こちらにすることもできますが、 マシンが (必ずしも負荷が高くなくても) WEB サーバだったりすると、 膨大な FIN-WAIT-2 ソケットのせいでメモリが溢れる危険もあります。 FIN-WAIT-2 ソケットは 1.5K のメモリしか食わないので FIN-WAIT-1 ほど危険ではありませんが、しかし長く生きる傾向があります。 参照: tcp_max_orphans

/proc/sys/net/ipv4/tcp_keepalive_time

  keepalive が有効になっているとき、 TCP が keepalive メッセージを送る頻度。デフォルトは 2 時間です。

/proc/sys/net/ipv4/tcp_keepalive_intvl

  プローブに対する確認が返信されなかったときにプローブを再送する頻度。 デフォルトは 75 秒です。

/proc/sys/net/ipv4/tcp_keepalive_probes

  TCP が keepalive プローブを送る数。この数に達すると、 その接続が壊れたとみなします。デフォルトの値は 9 です。 この値に tcp_keepalive_intvl をかけると、 ある keepalive が送られた後に許される無反応時間が得られます。

/proc/sys/net/ipv4/tcp_max_orphans

  システムが、どのユーザファイルハンドルにもアタッチしていない TCP ソケットを保有できる最大数。この数を越えると、 みなしごの接続は直ちにリセットされ、警告が表示されます。 この制限は単純な DoS 攻撃を防ぐためにあります。 この機能に頼りすぎたり、わざと制限を小さくしたりしてはいけません。 ネットワークの状況によって必要な場合は、 (できればメモリを追加してから) デフォルトより増やすのはかまいません。 また、ぐずぐずするネットワークサービスを調整して、 そのような状態を積極的に殺すようにしてください。 再度強調しておきますが、みなしごの接続は、 それぞれスワップ不可能なメモリを最大 64K 消費するのです。

/proc/sys/net/ipv4/tcp_orphan_retries

  こちら側からクローズした TCP コネクションを終了する前の再送回数。 デフォルト値は 7 で、これは RTO によりますが、50 秒から 16 分です。 マシンで WEB サーバを動作している場合、 このようなソケットはリソースを著しく消費するかもしれないので、 この値を低くすることを考えてください。 参照: tcp_max_orphans

/proc/sys/net/ipv4/tcp_max_syn_backlog

  送信した接続要求のうち、 まだ接続先から ACK を受け取っていないものを記憶しておく最大数です。 デフォルト値は、128Mb 以上のメモリを搭載しているマシンで 1024、 メモリの少ないマシンでは 128 です。 サーバが過負荷に苦しんでいる場合は、この数を増加させてみてください。 警告! 1024 以上にする場合は、 include/net/tcp.h の TCP_SYNQ_HSIZE を変更して TCP_SYNQ_HSIZE*16<=tcp_max_syn_backlog が満足されるようにし、 カーネルを再コンパイルした方がいいでしょう。

/proc/sys/net/ipv4/tcp_max_tw_buckets

  システムが同時に保持する time-wait ソケットの最大数。 この数を越えると、time-wait ソケットは直ちに破棄され、警告が表示されます。 この制限は単純な DoS 攻撃を防ぐためにあります。 わざと制限を小さくしてはいけません。 ネットワークの状況によって必要な場合は、 (できればメモリを追加してから) デフォルトより増やすのはかまいません。

/proc/sys/net/ipv4/tcp_retrans_collapse

  問題のあるプリンタのバグ-バグ互換性を保つためのもの。 特定の TCP スタックのバグに対処するため、 再送時に大きなパケットを送ります。

/proc/sys/net/ipv4/tcp_retries1

  再送回数で、この数を越えると何か問題があると判断し、 その疑いをネットワーク層に報告する必要があります。 RFC での最小値は 3 で、これがデフォルトです。 RTO によりますが、3 秒から 8 分に相当します。

/proc/sys/net/ipv4/tcp_retries2

  接続中の TCP セッションを殺すまえに再送を試みる回数。 RFC 1122 によれば、この時間は 100 秒以上にならなければいけません。 これは実際には短すぎる。 デフォルト値は 15 で、RTO の値によりますが 13-30 分に相当します。

/proc/sys/net/ipv4/tcp_rfc1337

  このブール値は RFC1337 で説明されている 'time-wait assassination hazards in tcp' の修正です。有効になっていると、 カーネルは time-wait 状態にあるソケットへの RST パケットを破棄します。

/proc/sys/net/ipv4/tcp_sack

  Selective ACK を用います。特定のパケットが失われているかを調査でき、 よって回復が速やかになります。

/proc/sys/net/ipv4/tcp_stdurg

  TCP urg ポインタフィールドを、Host requirements に従って解釈します。 多くのホストは古い BSD の解釈を使っているので、 Linux でこれをセットすると正しく通信ができないかもしれません。 既定値: FALSE

/proc/sys/net/ipv4/tcp_syn_retries

  新しい接続を開始するとき、カーネルはこの回数 SYN パケットを送っても駄目ならばあきらめます。

/proc/sys/net/ipv4/tcp_synack_retries

  接続を受付側としてオープンするとき、 カーネルは SYN に ACK を詰め込んで送り、先に受けとった SYN を確認します。 これは 3 方向ハンドシェークの 2 番目の部分です。 この設定は、カーネルが接続をあきらめるまでに送る、 SYN+ACK パケットの再送数を指定します。

/proc/sys/net/ipv4/tcp_timestamps

  タイムスタンプは、特に、シーケンス番号の重なりを防ぐために使用されます。 1 ギガビットの接続では、おそらく順序通りでない、 古いシーケンス番号に出くわすことがあります (前の世代の番号であるため)。 タイムスタンプを用いると、これを「昔のパケット」と認識できます。 シークエンス番号がオーバーフローして、また0から始まることを言ってる? -->

/proc/sys/net/ipv4/tcp_tw_recycle

  TIME-WAIT ソケットを高速にリサイクルできるようにします。 デフォルト値は 1 です。 これは専門技術者のアドバイスか要求がなければ、変更すべきではありません。

/proc/sys/net/ipv4/tcp_window_scaling

  通常 TCP/IP では、最大 65535 バイトのウィンドウが可能です。 本当に高速なネットワークでは、これでは十分でないかもしれません。 ウィンドウ拡大オプションを用いると、 ほぼギガバイトのウィンドウが利用できます。 これはバンド幅と遅延時間の積が大きいような製品に適しています。

#### デバイスごとの設定

DEV は実際のインターフェースか、あるいは 'all' または 'default' を表します。 default は、まだ作成されていないインターフェースの設定にも影響します。

/proc/sys/net/ipv4/conf/DEV/accept_redirects

  ルータは、自分が間違った目的で利用されている (つまり受けとったパケットを同じインターフェースに再送しなければならない) と判断すると、そのパケットの発信元に ICMP Redirect を送信します。 しかしこれは少々セキュリティ上の問題があるので、 この受信を拒否したり、あるいは安全なリダイレクトを利用できます。

/proc/sys/net/ipv4/conf/DEV/accept_source_route

  もうあまり用いられません。以前はパケットに、 途中で訪れるべき IP アドレスのリストを与えることが可能でした。 Linux はこの IP オプションを尊重するような動作が可能です。

/proc/sys/net/ipv4/conf/DEV/bootp_relay

  発信元アドレスが 0.b.c.d で、 送信先がこのホストでないパケットを、ローカルなパケットとして許可します。 これは BOOTP リレーデーモンが、 そのようなパケットを受信・フォワードしているのだと考えられます。

  この機能はまだ (カーネルバージョン 2.2.12 では) 実装されていないので、デフォルトは 0 です。

/proc/sys/net/ipv4/conf/DEV/forwarding

  このインターフェースでの IP フォワーディングを有効/無効にします。

/proc/sys/net/ipv4/conf/DEV/log_martians

  の節を見てください。

/proc/sys/net/ipv4/conf/DEV/mc_forwarding

  このインターフェースでマルチキャストフォワーディングを行うかどうかです。

/proc/sys/net/ipv4/conf/DEV/proxy_arp

  これを 1 にすると、このインターフェースは カーネルが経路を知っているアドレスに対する ARP 要求に返信します。 「疑似 ip ブリッジ」を構築するのに非常に便利に使えます。 これを有効にする前には、ネットマスクが本当に正しいかをしっかり確認してください! また rp_filter (別のところで紹介しています) が、 ARP 問い合わせに対して動作することも覚えておいてください!

/proc/sys/net/ipv4/conf/DEV/rp_filter

  の節を見てください。

/proc/sys/net/ipv4/conf/DEV/secure_redirects

  ICMP redirect メッセージを、デフォルトゲートウェイにリストされている ゲートウェイに対してのみ許可します。デフォルトで有効です。

/proc/sys/net/ipv4/conf/DEV/send_redirects

  このホストが上述の redirect を送信するかどうかです。

/proc/sys/net/ipv4/conf/DEV/shared_media

  設定されていないと、カーネルはこのデバイスにつながっている 別々のサブネットは、直接通信できないとみなします。 デフォルトの設定は 'yes' です。

/proc/sys/net/ipv4/conf/DEV/tag

  FIXME: ここを埋めてください。

#### 近隣ポリシー

DEV は実際のインターフェースか、あるいは 'all' または 'default' を表します。 default は、まだ作成されていないインターフェースの設定にも影響します。

/proc/sys/net/ipv4/neigh/DEV/anycast_delay

  近隣確認メッセージへの返答のランダム遅延の最大値 (jiffies [1/100 秒] 単位)。 まだ実装されていません (Linux はまだ anycast をサポートしていません)。

/proc/sys/net/ipv4/neigh/DEV/app_solicit

  ユーザレベルの ARP デーモンに送るリクエストの数を定義します。 無効にするには 0 にします。

/proc/sys/net/ipv4/neigh/DEV/base_reachable_time

  RFC2461 で規定されているランダム到達可能時間の計算のベースになる値です。

/proc/sys/net/ipv4/neigh/DEV/delay_first_probe_time

  近隣が到達可能かどうかのプローブを最初に行うまでの遅延時間です (gc_stale_time を参照)。

/proc/sys/net/ipv4/neigh/DEV/gc_stale_time

  古い ARP エントリをどの程度の頻度でチェックするかを決めます。 古くなった ARP エントリは再び解決されます (これは IP アドレスが他のマシンへ移る時に役に立ちます)。 ucast_solicit が 0 より大きければ、 まず既知のホストに直接 ARP パケットを送信します。 これが失敗したら、mcast_solicit が 0 より大きければ ARP 要求をブロードキャストします。

/proc/sys/net/ipv4/neigh/DEV/locktime

  ARP/近隣エントリは、最低でも locktime だけ経過しないと 新しいものと置き換えできません。 これは ARP キャッシュのスラッシングを防ぎます。

/proc/sys/net/ipv4/neigh/DEV/mcast_solicit

  マルチキャスト確認の最大再試行回数です。

/proc/sys/net/ipv4/neigh/DEV/proxy_delay

  代理 ARP エントリを持っている場合に、 その ARP 要求に答えるまでの最大時間です。 実際の時間は 0 以上 proxy_delay 以下の乱数になります。 ある場合には、これはネットワークの溢れを防ぐために使用されます。

/proc/sys/net/ipv4/neigh/DEV/proxy_qlen

  遅延した代理 ARP タイマのキューの最大長です (proxy_delay を参照)。

/proc/sys/net/ipv4/neigh/DEV/retrans_time

  近隣確認メッセージを再送信するまでの時間 (単位は jiffy = 1/100 秒) です。 アドレスを解決したり、近隣が到着不能かどうかを確認したりする際に使用します。

/proc/sys/net/ipv4/neigh/DEV/ucast_solicit

  ユニキャスト確認の最大再試行回数です。

/proc/sys/net/ipv4/neigh/DEV/unres_qlen

  待ち状態の ARP 要求を保持するキューの最大長です。 ARP アドレスが解決されている最中に、 他の層からの要求パケットを受け入れる数です。

#### ルーティング設定

/proc/sys/net/ipv4/route/error_burst

  このパラメータと次の error_cost はルーティングのコードから カーネルのログに書き出される警告メッセージの制限に使用されます。 error_costを大きくすると書き出されるメッセージは少なくなります。 error_burst はメッセージを捨てる際の制御を行います。 デフォルトでは警告メッセージを 5 秒間に一回までに制限しています。

/proc/sys/net/ipv4/route/error_cost

  /proc/sys/net/ipv4/route/error_burst の項を参照してください。

/proc/sys/net/ipv4/route/flush

  このファイルに書き込むとルーティングキャッシュをフラッシュします。

/proc/sys/net/ipv4/route/gc_elasticity

  ルーティングキャッシュのガベージコレクションの頻度と動作を決める値です。 これはフェイルオーバー動作で重要となります。 ある経路が死んだとき、Linux が別の経路に移るまでには、 最低 gc_timeout 秒が経過しなければなりません。 デフォルトでは 300 に設定してありますが、 フェイルオーバーを早く行いたければ、もっと小さい値にすると良いでしょう。

  Ard van Breemen の この投稿 も参照してください。

/proc/sys/net/ipv4/route/gc_interval

  /proc/sys/net/ipv4/route/gc_elasticity の項をご覧ください。

/proc/sys/net/ipv4/route/gc_min_interval

  /proc/sys/net/ipv4/route/gc_elasticity の項をご覧ください。

/proc/sys/net/ipv4/route/gc_thresh

  /proc/sys/net/ipv4/route/gc_elasticity の項をご覧ください。

/proc/sys/net/ipv4/route/gc_timeout

  /proc/sys/net/ipv4/route/gc_elasticity の項をご覧ください。

/proc/sys/net/ipv4/route/max_delay

  ルーティングキャッシュがフラッシュされるまでの最大時間です。

/proc/sys/net/ipv4/route/max_size

  ルーティングキャッシュの最大サイズです。 キャッシュがこのサイズに達すると、古いエントリは破棄されます。

/proc/sys/net/ipv4/route/min_adv_mss

  FIXME: ここを埋めてください。

/proc/sys/net/ipv4/route/min_delay

  ルーティングキャッシュがフラッシュされるまでの最小時間です。

/proc/sys/net/ipv4/route/min_pmtu

  FIXME: ここを埋めてください。

/proc/sys/net/ipv4/route/mtu_expires

  FIXME: ここを埋めてください。

/proc/sys/net/ipv4/route/redirect_load

  特定のホストに対して ICMP リダイレクトをより多く送るべきかを決定するための因子です。 負荷がここで指定した数に達した場合には、 それ以上のリダイレクトは送信されません。

/proc/sys/net/ipv4/route/redirect_number

  /proc/sys/net/ipv4/route/redirect_load の項目を参照してください。 こちらはリダイレクト数の制限です。

/proc/sys/net/ipv4/route/redirect_silence

  リダイレクトの時間切れ。 負荷やリダイレクト数が制限に達してリダイレクトが停止されていた場合でも、 この期間が過ぎるとリダイレクトを再び送り始めます。

## 知られざる高度なキューイング規則

これまで紹介してきたキューでは解決できない要望がある人のために、 ここではカーネルに含まれている、より特殊なキューを紹介します。

### `bfifo`/`pfifo`

これらのクラスレスなキューは、pfifo_fast に比べてもシンプルです。 内部バンドが無く、すべてのトラフィックがまったく等価なのです。 しかしこれらにはひとつ、重要な利点があります。統計が取れるのです。 よって帯域制限や優先付けが必要ない場合でも、 これらの qdisc を用いると、インターフェースの backlog を決定するのに役立ちます。

pfifo の長さはパケット単位で、bfifo ではバイト単位で指定します。

#### パラメータと使い方

limit

  キューの長さを指定します。bfifo ではバイト単位、pfifo ではパケット単位です。 デフォルトは pfifo の場合インターフェースの txqueuelen 分のパケットで、 bfifo では txqueuelen*mtu バイトです (txqueuelen については pfifo_fast の章を参照)。

### Clark-Shenker-Zhang アルゴリズム (CSZ)

これは非常に理論的なので、Alexey (CBQ の主要な作者) ですら理解することを求めていません。Alexey のソースにあった参考文献です:

> David D. Clark, Scott Shenker and Lixia Zhang Supporting Real-Time Applications in an Integrated Services Packet Network: Architecture and Mechanism.

> 私の理解では、このアイディアの肝は、 保証するサービスそれぞれに WFQ フローを生成し、 残りのバンド幅をダミーの flow-0 に割り当てるところにあります。 flow-0 は予測的サービス (predictive services) とベストエフォートのトラフィックとからなります。 これは優先度スケジューラによって管理され、 予測的サービスに最も高い優先度が割り当てられます。 残りはベストエフォートのパケットになります。

> CSZ はバンド幅の制限を「行いません」。 フローはすでに QoS ネットワークの境界で流入管理をされたとみなしており、 それ以上の帯域制限は行いません。 経路途中 (intermediate hop) で、 フローを向上させたりトークンバケツフィルタによる絞りを行おうとすると、 望ましくない遅延が起きたり、ジッターの原因になります。

> 現在のところ CSZ は本物の「保証付きサービス」を提供する唯一のスケジューラです。 (CBQ も含む) 他の機構は、保証付き遅延やランダムなジッターを提供しません。

> 現在のところは、先に紹介した記事を読んで理解できない限り、 あまり実際の利用には向いていないようです。

### DSMARK

Esteve Camps

    marvin@grn.es

この文章は、2000 年 9 月の私の論文 QoS Support in Linux から抜粋したものです。

元になった文書:

- Draft-almesberger-wajhak-diffserv-linux-01.txt.

- 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 の対象

Dsmark は、Differentiated Services (DiffServ とか、 単に DS とも呼ばれます) で必要となる機能を提供するキューイング規則です。 DiffServ は 2 つある実際の QoS 機構のうちのひとつ (もうひとつは Integrated Services と呼ばれています) で、 パケットの IP ヘッダの DS フィールドに含まれている値に基づいて動作します。

IP で、何らかの QoS レベルのサービスを得るために設計された最初の解は、 IP ヘッダの Type of Service フィールド (TOS バイト) でした。 この値を変えることにより、 スループット・遅延・信頼性などのレベルの高低を選択できたのでした。 しかしこれは、最近のサービス (リアルタイムアプリケーション、 対話的アプリケーションなど) で必要とされる、 十分な柔軟性を持っていませんでした。その後、新たな機構が現れました。 そのひとつが DiffServ で、これは TOS の各ビットを占有し、 DS フィールドとして再定義して使用します。

#### Differentiated Services のガイドライン

DiffServ はグループ指向です。 つまり、フローについては何も関知しないのです (これは Integrated Services の目的となります)。 対象にするのはフローの集合で、 パケットがどの集合に属しているかによって、 異なる動作を適用することになります。

パケットが境界ノード (DiffServ ドメインのエントリノード) に到着すると、 DiffServ ドメインへ入る際に、これらのパケットには制限・帯域制限・マーキング などを行う必要があります (マーキングとは、DS フィールドに値を代入することです。 牛みたいなもんです :-) )。 DiffServ ドメインの内部 (コア) ノードで、 どのような動作や QoS レベルを適用するかは、 このマーク (値) を見て判断されます。

ご想像の通り、Differentiated Services にはドメインがあり、 すべての DS 規則はここで適用されます。 実際、ドメインに入ってきたすべてのパケットをクラス選別する、 と考えることも可能です。パケットはドメインに入ると、 クラス選別機構が命ずる規則に従うことになり、 経由するノードのすべてでその QoS レベルが適用されるのです。

実のところ、自分のローカルなドメインには自前の制限を適用できますが、 他の DS ドメインと接続する際には、 Service Level Agreements を考慮しなければなりません。

この段階では、おそらくたくさんの疑問があるでしょう。 DiffServ は、ここで説明した以上の内容を含んでいます。 3 つの RFC を 50 行で要約するのが不可能であることは、 理解していただけるでしょう :-)

#### Dsmark を利用する

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 へ保存するよう指示します。

では DSMARK のプロセスを見てみましょう。

#### SCH_DSMARK の動作

この 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

これはハッシュテーブルの (mask,value) ペアを変更し、 class 1:1 に属しているパケットを再マークします。 (mask,value) は最初デフォルトの値を取る (以下の表を参照) ので、 この値は「変更」しなければなりません。

これで、TC_INDEX フィルタの動作と、 これをどのように利用するかを説明しました。 なおまた、TC_INDEX フィルタは DS サービスに含まれているのとは 別の設定で用いることも可能です。

#### TC_INDEX フィルタ

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

(このコードは完全ではありません。 iproute2 配布にある EFCBQ の例から抜粋したものにすぎません)。

まず、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 の例をすべて見ておくことをお奨めします。 ここの文章も、できるだけ早く補完するつもりです。 ところで、ここで説明した内容は、多くのテストの結果です。 もしどこかで間違っていたら、指摘してくださると幸いです。

### 入口 (ingress) qdisc

ここまで説明してきた qdisc は、すべて出口 (egress) qdisc です。 しかしインターフェースには入口 (ingress) qdisc を持たせることもできます。 これは、パケットをネットワークアダプタに送るのではなく、 アダプタを経由して到着したパケットに tc フィルタをかけるためのものです。 パケットがローカル宛のものか、フォワードされるものかは問われません。

tc フィルタにはトークンバケツフィルタが完全に実装されており、 またカーネルの流量評価器によってマッチを行うこともできますから、 たくさんの機能が実現可能です。 実際に、流入トラフィックを IP スタックに入る以前のところで 制限することさえできます。

#### パラメータと使い方

入口 qdisc 自体は、どのようなパラメータも要求しません。 この qdisc は他とは異なり、デバイスの root を占有しません。 次のようにしてアタッチします。

    # tc qdisc add dev eth0 ingress

よってこの入口 qdisc と同時に、 他の (送信で用いる) qdisc をこのデバイスに適用することもできます。

入口 qdisc の (ややわざとらしい) 利用例については、 クックブックを参照してください。

### ランダム初期検知 (Random Early Detection: RED)

この節は、バンド幅が 100メガビット以上になるような、 バックボーンの経路に関する解説を意図して書かれました。 自宅の ADSL モデムなどとは異なるアプローチが必要になります。

インターネットにおけるルータのキューの通常の動作は、 tail-drop と呼ばれます。 tail-drop は、ある量まではキューを行い、 「溢れた」トラフィックをすべて破棄します。これは非常に不公平であり、 再送の衝突 (retransmit synchronization) の原因にもなります。 再送の衝突が起こると、限界に達したルータで突然の破棄がバースト的に生じ、 それがまたしばらく後に再送のバーストを生じさせ、 混雑したルータはまた限界に達することになります。

接続における一時的な混雑に対処するため、 バックボーンルータでは大きなキューが用意されていることが多いです。 しかし残念ながら、このようなキューはスループットには良いのですが、 遅延 (latency) が大きくなりますし、 TCP 接続では混雑時のバーストが非常に大きくなってしまいます。

この tail-drop の特性は、 インターネットにおいてだんだん厄介な問題になってきています。 ネットワークに優しくないアプリケーションの利用が増加してきたからです。 このため Linux カーネルでは RED が提供されています (RED は Random Early Detection の略。またその動作から、 Random Early Drop とも呼ばれます)。

RED はこの問題についての万能薬ではありません。 指数的な backoff を正しく実装していないアプリケーションは、 いずれにしても不公平にバンド幅を獲得してしまうことになります。 しかし RED を用いれば、スループットや遅延に関して、 他の接続に大きな害を与えることはなくなります。

RED は、ハードリミットに達する前に、フローからパケットを統計的に破棄します。 こうすると混雑したバックボーンの接続を、より穏やかに減速でき、 再送の衝突を防ぐことができます。 これはキューのサイズを小さく、遅延を一定に保ちつつ、 早めにパケットを破棄するので、TCP が自分の「公正な」速度を より早く発見するのにも役立ちます。 ある特定の接続においてパケットが破棄される確率は、 送ったパケットの数にではなく、バンド幅の利用率に比例します。

公平なキューイングで必要とされる、 複雑なセッションごとの状態追跡が無理であるようなバックボーンに対し、 RED は適したキューです。

RED を使うには、3 つのパラメータ min, max, burst を決める必要があります。 min は破棄を始める際のキューサイズ (の最小値)、 max はこのアルゴリズムで留めておきたいソフトリミット (最大値)、 burst は「バースト通過」が可能な最大パケット数です。

min を計算するには、基本キューイング遅延 (base queueing latency) の最大許容値にバンド幅をかけます。例えば 64kbit/s の ISDN 接続に対し、 基本キューイング遅延を 200ms まで認める場合、min は 1600 バイトとなります。 min を小さくしすぎるとスループットは悪化しますし、 大きくしすぎると latency が劣化します。なお min を小さくしても、 遅い接続で対話的セッションの反応を良くするために MTU を小さくするのと同じ効果は得られません。

衝突を防ぐには、max は少なくとも min の 2 倍にしなければなりません。 遅い接続で min も小さいときは、おそらく max は min の 4 倍以上にするほうが良いでしょう。

burst は RED アルゴリズムのバーストに対する反応を決めます。 burst は min/avpkt より大きくしなければなりません。 私が実験したところでは、(min+min+max)/(3*avpkt) でうまく動作するようです。

また、limit と avpkt の設定も必要です。 limit は安全弁となる値で、キューの中身が limit バイトを越えると、 RED は tail-drop に「変化」します。私は大体の場合、limit を max の 8 倍に設定します。avpkt はパケットサイズの平均値です。 MTU が 1500 バイトの高速なインターネット接続では、 1000 にしておけばよいでしょう。

技術的な情報は、 Sally Floyd と Van Jacobson による RED キューイングの論文をご覧ください。

### 汎用ランダム初期検知 (Generic Random Early Detection)

GRED については、あまりわかっていることはありません。 GRED はいくつか内部キューを持っているようで、 それらは DiffServ の tcindex に基づいて選択されているようです。 こちら のスライドによれば、GRED は Cisco の 'Distributed Weighted RED' の機能や、Dave Clark の RIO のような機能を持っているようです。

各仮想キューには、それぞれ固有の破棄パラメータを指定できます。

FIXME: Jamel か Werner に、もうちょっと教えてもらわないと。

### VC/ATM エミュレーション

これは Werner Almesberger による割に有名なプロジェクトで、 仮想回路 (Virtual Circuits) を TCP/IP ソケット上に構築しようというものです。 Virtual Circuit は、ATM ネットワーク理論の概念です。

より詳しくは、ATM on Linux ホームページを参照してください。

### 重み付きラウンドロビン (Weighted Round Robin: WRR)

この qdisc は標準のカーネルには入っていませんが、 こちらから ダウンロードできます。 現在この qdisc は Linux 2.2 でのみテストされていますが、 おそらく 2.4/2.5 カーネルでの作業もなされるでしょう。

WRR qdisc は、重み付きラウンドロビンの仕組みを使って、 バンド幅を自分の持つ複数のクラスに分散させます。 つまり WRR qdisc は、CBQ qdisc のように、自分の配下にクラスを持ち、 それぞれには任意の qdisc を接続できます。 要求を行ったクラスには、それぞれに付けられた重みに 比例したバンド幅があてがわれます。重みは `tc` プログラムを用いて手動で設定できます。しかし、 大量のデータを転送させたクラスの重みを自動的に減らすようにもできます。

この qdisc には組み込みのクラス選別器があり、 異なるマシンとの間で送信・受信したパケットを、 それぞれ別々のクラスに割り当てます。 MAC または IP と、発信元アドレスまたは送信先アドレスが利用できます。 ただし MAC アドレスは Linux マシンがイーサネットブリッジとして 動作している場合でのみ利用できます。 各クラスは、あるマシンからのパケットが現れると、 自動的にそのマシンに対して割り当てられます。

この qdisc は、寮のように、たくさんのそれぞれ独立したマシンが ひとつのインターネット接続を共有している場合には、とても便利でしょう。 このようなサイトで、望む動作をさせるためのスクリプト群は、 WRR の配布アーカイブに含まれています。

## クックブック

この章は、問題解決の助けとなる「クックブック」が収められています。 ただしクックブックは理解の代わりになるものではありません。 ですので試したら何が起きているかを理解するように心がけてください。

### SLA の異なる複数のサイトを動作させる

いくつかのやり方があります。 Apache でもこの機能をモジュールでサポートしていますが、 ここでは Linux を使ってできること、 他のサービスでも同様に適用できることを示します。 これらのコマンドは、この文書の末尾に示す Jamal Hadi の発表から拝借してきたものです。

http, ftp, ストリーミングオーディオを使っている 2 つの顧客があり、 それぞれに限られた量のバンド幅を販売したいとしましょう。 これはサーバ自身で行います。

顧客 A には最大 2 メガビットを、顧客 B には 5 メガビットを割り当てます。 これらの顧客は、サーバに仮想 IP アドレスを作って分離します。

    # ip address add 188.177.166.1 dev eth0
    # ip address add 188.177.166.2 dev eth0

それぞれのサーバに適切な IP アドレスを割り当てるのは読者にお任せします。 有名なデーモンは、ほぼ間違いなくこの機能をサポートしています。

まず eth0 に CBQ qdisc をアタッチします。

    # tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit cell 8 avpkt 1000 \
      mpu 64

続いて各顧客のクラスを作ります。

    # tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate \
      2MBit avpkt 1000 prio 5 bounded isolated allot 1514 weight 1 maxburst 21
    # tc class add dev eth0 parent 1:0 classid 1:2 cbq bandwidth 10Mbit rate \
      5Mbit avpkt 1000 prio 5 bounded isolated allot 1514 weight 1 maxburst 21

ここで 2 つのクラスそれぞれにフィルタを追加します。

    ##FIXME: この行の意味と動作は? divisor とは?
    ##FIXME: divisor はハッシュテーブルとバケツの数に何か関係があるようだ -ahu
    # tc filter add dev eth0 parent 1:0 protocol ip prio 5 handle 1: u32 divisor 1
    # tc filter add dev eth0 parent 1:0 prio 5 u32 match ip src 188.177.166.1
      flowid 1:1
    # tc filter add dev eth0 parent 1:0 prio 5 u32 match ip src 188.177.166.2
      flowid 1:2

これで終了です。

FIXME: なぜトークンバケツフィルタが不要なのか? どこかでデフォルトの pfifo_fast に落ちているのか?

### ホストを SYN フラッドから守る

Alexey の iproute 文書からとった内容を netfilter に合わせ、 パスを調整したものです。これを用いる場合には、 数値をご自分のシステムにあわせて修正してください。

ネットワーク全体を守りたい場合は、このスクリプトは読み飛ばしてください。 これは単一のホストを守るためのものです。

これを 2.4.0 で動作させるには、最新版の iproute2 が必要なようです。

    #! /bin/sh -x
    #

    # 入口 (ingress) の機能を用いたサンプルスクリプト
    # このスクリプトでは、到着する SYN を制限する例を示します。
    # TCP-SYN 攻撃に対する防御として有用です。SYN に対してより
    # 強力な機能 (例えばサブネットを追加するなど) を追加した
    # ければ IPchains を使えます。
    #

    # いろいろなユーティリティのパス。
    # 自分の環境にあわせてください。
    #
    TC=/sbin/tc
    IP=/sbin/ip
    IPTABLES=/sbin/iptables
    INDEV=eth2
    #

    # $INDEV に到着した SYN パケットに MARK が 1 のタグを付けます。
    ############################################################
    $iptables -A PREROUTING -i $INDEV -t mangle -p tcp --syn \
      -j MARK --set-mark 1
    ############################################################
    #

    # 入口 qdisc を、この入口インターフェースにインストールします
    ############################################################
    $TC qdisc add dev $INDEV handle ffff: ingress
    ############################################################

    #
    #

    # SYN パケットは 40 バイト (320 ビット) なので、3 つの SYN
    # は 960 ビット (およそ 1kビット) になります。よって帯域制限
    # として、到着 SYN は 3/秒 にします (実はあまり便利ではあり
    # ません。しかし要点は押さえています -JHS
    ############################################################
    $TC filter add dev $INDEV parent ffff: protocol ip prio 50 handle 1 fw \
    police rate 1kbit burst 40 mtu 9k drop flowid :1
    ############################################################

    #
    echo "---- qdisc parameters Ingress  ----------"
    $TC qdisc ls dev $INDEV
    echo "---- Class parameters Ingress  ----------"
    $TC class ls dev $INDEV
    echo "---- filter parameters Ingress ----------"
    $TC filter ls dev $INDEV parent ffff:

    # 入口 qdisc を削除します。
    #$TC qdisc del $INDEV ingress

### ICMP を帯域制限して dDoS を防ぐ

昨今のインターネットでは、分散利用不能攻撃 (distributed denaial of service attack: dDoS) が頭の痛い問題になっています。 ネットワークに正しくフィルタリングと帯域制限をかけると、 このような攻撃の対象 (および原因) となることをいずれも防げます。

ネットワークにフィルタをかけ、送信元アドレスがローカルでない IP パケットは、 ネットワークを出て行けないようにします。 こうすると、匿名でゴミをインターネットに送ることができなくなります。

帯域制限は、先に示したものと同様です。 読者のメモリをリフレッシュするために、再び ASCII 図を示しましょう。

    [The Internet] ---<E3, T3, whatever>--- [Linux router] --- [Office+ISP]
                                          eth1          eth0

まず、あらかじめ必要な部分の設定です。

    # tc qdisc add dev eth0 root handle 10: cbq bandwidth 10Mbit avpkt 1000
    # tc class add dev eth0 parent 10:0 classid 10:1 cbq bandwidth 10Mbit rate \
      10Mbit allot 1514 prio 5 maxburst 20 avpkt 1000

インターフェースが 100Mビット (あるいはそれ以上) だったら、 3 つの数値を調整してください。次に、ICMP トラフィックを どの程度許可するかを決めます。tcpdump で測定を行ってみて、 しばらく結果をファイルに記録し、 どの程度の ICMP がネットワークを流通しているか見てみましょう。 snapshot の長さを増やすのを忘れないように。

測定ができない場合は、利用できるバンド幅の 5% にしてみるといいでしょう。 ではクラスを設定しましょう。

    # tc class add dev eth0 parent 10:1 classid 10:100 cbq bandwidth 10Mbit rate \
      100Kbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 250 \
      bounded

これで 100Kビット に制限しました。 ではフィルタを追加して、 ICMP トラフィックをこのクラスに割り当てるようにしましょう。

    # tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip
      protocol 1 0xFF flowid 10:100

### 対話的トラフィックを優先する

たくさんのデータが接続から出て行ったり入ってきたりしていると、 telnet や ssh で何らかのメンテナンス作業を行う際の反応が悪くなります。 他のパケットがキー入力をブロックするのです。 このような対話的なパケットを、こっそりバルク転送トラフィックとは別の経路で 通すことができたらすごいと思いませんか? Linux ならできるのです。

前述のように、両方向のトラフィックを扱う必要があります。 当然ながら、接続の両端がいずれも Linux マシンであれば最高ですが、 他の UNIX でも可能です。お近くの Solaris/BSD グルに尋ねてみましょう。

標準の pfifo_fast スケジューラには 3 つの「バンド」があります。 バンド 0 のトラフィックは先に送信され、 その後バンド 1 と 2 のトラフィックが考慮されます。 よって、私たちの対話的トラフィックをバンド 0 に割り当てるのがミソです!

これらは (そろそろ obsolete になる) ipchains HOWTO からパクってきました。

IP ヘッダには、あまり用いられない 4 つのビット、 Type of Service (TOS) ビットと呼ばれる領域があります。 これらはパケットの扱いに影響します。4 つのビットはそれぞれ "Minimum Delay", "Maximum Throughput", "Maximum Reliability", "Minimum Cost" を意味します。これらのビットのうち、ひとつだけが設定できます。 ipchains の TOS 修正コードの著者である Rob van Nieuwkerk は、 次のように言っています。

> 私にとっては "Minimum Delay" が特に重要です。 私は「対話的な」パケットには、上流のルータ (Linux) でこれを有効にしました。 自分は 33.6k モデム接続の下流にいます。 Linux はパケットを 3 つのキューに優先付けします。 このようにして、私は大きなダウンロードをしている最中にも、 まあまあの対話性能を得ることができています。

最もよくある設定は、telnet と ftp の制御接続に "Minimum Delay" を用い、FTP データに "Maximum Throughput" を用いるやり方です。 これには上流のルータで、次のようにします。

    # iptables -A PREROUTING -t mangle -p tcp --sport telnet \
      -j TOS --set-tos Minimize-Delay
    # iptables -A PREROUTING -t mangle -p tcp --sport ftp \
      -j TOS --set-tos Minimize-Delay
    # iptables -A PREROUTING -t mangle -p tcp --sport ftp-data \
      -j TOS --set-tos Maximize-Throughput

これだと、telnet 先のホストから ローカルの PC へと向かうパケットにしか作用しません。 しかし逆方向はすでに設定されているのです。つまり telnet や ssh などでは、 送信パケットの TOS フィールドを自動的に設定しているのです。

これを行っていないアプリケーションがあっても、 netfilter を用いれば同じことができます。 ローカルマシンで次のようにします。

    # iptables -A OUTPUT -t mangle -p tcp --dport telnet \
      -j TOS --set-tos Minimize-Delay
    # iptables -A OUTPUT -t mangle -p tcp --dport ftp \
      -j TOS --set-tos Minimize-Delay
    # iptables -A OUTPUT -t mangle -p tcp --dport ftp-data \
      -j TOS --set-tos Maximize-Throughput

### netfilter, iproute2, ipchains, squid を用いた透過 Web キャッシュ

この節は読者の Ram Narula (Internet for Education, Thailand) から寄せられました。

Linux でこの目的を満たすための通常のテクニックは、 おそらくポート 80 (web) の「外向き」トラフィックが squid の動作しているサーバにルーティングされるようにした 「後で」、ipchains を用いる方法でしょう。

外向きのポート 80 トラフィックを squid の動作しているサーバに ルーティングするには、3 つの方法が知られています。 ここでは 4 番目の方法を紹介します。

ゲートウェイルータにやらせる

  ゲートウェイルータに、外向きで送信先ポートが 80 のパケットを squid サーバの IP アドレスに送らせることが可能な場合。

  しかし

  これはルータに負荷をかけることになりますし、 商用のルータではサポートしていないこともあります。

レイヤ 4 スイッチを用いる

  レイヤ 4 スイッチは、この作業をまったく問題なく処理できます。

  しかし

  レイヤ 4 スイッチの価格は非常に高く、 たいていの場合は、(一般的なルータ + 良い Linux サーバ) の合計よりも高いです。

キャッシュサーバをネットワークのゲートウェイにする

  すべてのトラフィックにキャッシュサーバを経由させることもできます。

  しかし

  squid はかなりの CPU パワーを消費するため、これはかなりリスクが高いです。 つまりネットワーク全体の性能が遅くなるかもしれませんし、 万一サーバがクラッシュしたら、誰もネットワークにアクセスできなくなります。

Linux+NetFilter ルータ

  NetFilter を用いると、別のテクニックが実装できます。 つまり NetFilter に到達先ポートが 80 のパケットをマーキングさせ、 iproute2 を使って印のついたパケットを squid サーバにルーティングさせるのです。

    |----------------|

    |      実装      |
    |----------------|

     用いたアドレス
     10.0.0.1 naret (NetFilter サーバ)
     10.0.0.2 silom (Squid サーバ)
     10.0.0.3 donmuang (インターネットに接続しているルータ)
     10.0.0.4 kaosarn (ネットワークの別のサーバ)
     10.0.0.5 RAS
     10.0.0.0/24 メインネットワーク
     10.0.0.0/19 ネットワーク全体

    |---------------|

    | ネットワーク図|
    |---------------|

    Internet
    |
    donmuang
    |
    ------------hub/switch----------
    |        |             |       |
    naret   silom        kaosarn  RAS etc.

まず naret を silom 以外のマシンのデフォルトゲートウェイにして、 すべてのトラフィックが naret を経由するようにします。 silom のデフォルトゲートウェイは donmuang (10.0.0.3) にします。 さもないとこの環境では web トラフィックがループします。

(以前はこのネットワークのすべてのサーバでは 10.0.0.1 がデフォルトゲートウェイで、 これは donmuang ルータの以前の IP アドレスでした。 よってここでは donmuang の IP アドレスを 10.0.0.3 に変更し、 naret の IP アドレスを 10.0.0.1 にしたのです)

    Silom
    -----

    -squid と ipchains を設定する

silom で squid サーバを設定し、 透過キャッシュ/プロクシをサポートするようにします。 デフォルトのポートは通常 3128 なので、ポート 80 へのトラフィックは ローカルの 3128 にリダイレクトしなければ鳴りません。 これは ipchains を用いて次のように書けます:

    silom# ipchains -N allow1
    silom# ipchains -A allow1 -p TCP -s 10.0.0.0/19 -d 0/0 80 -j REDIRECT 3128
    silom# ipchains -I input -j allow1

あるいは netfilter の言葉では:

    silom# iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3128

(注意: 他のエントリもあるかもしれません)

squid サーバの設定に関する詳しい情報は、 Squid FAQ のページ を参照してください。

このサーバでは ip forwarding を有効にしておいてください。 またこのサーバのデフォルトゲートウェイは、 donmuang ルータにしてください (naret ではありません)。

    Naret
    -----

    -iptables と iproute2 の設定
    - icmp REDIRECT メッセージを無効にする (必要な場合)

1. 送信先ポートが 80 のパケットに、値 2 でマークします。

       naret# iptables -A PREROUTING -i eth0 -t mangle -p tcp --dport 80 \
        -j MARK --set-mark 2

2. iproute2 を設定し、2 というマークのついたパケットを silom にルーティングします。

       naret# echo 202 www.out >> /etc/iproute2/rt_tables
       naret# ip rule add fwmark 2 table www.out
       naret# ip route add default via 10.0.0.2 dev eth0 table www.out
       naret# ip route flush cache

   donmuang と naret が同じサブネットにある場合は、 naret に icmp REDIRECT メッセージを送信させてはいけません。 この場合はそうだったので、次のコマンドで icmp REDIRECT を無効化しました。

       naret# echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
       naret# echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects
       naret# echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects

これで設定は完成です。調べてみましょう。

    naret にて:

    naret# iptables -t mangle -L
    Chain PREROUTING (policy ACCEPT)
    target     prot opt source               destination
    MARK       tcp  --  anywhere             anywhere           tcp dpt:www MARK set 0x2

    Chain OUTPUT (policy ACCEPT)
    target     prot opt source               destination

    naret# ip rule ls
    0:      from all lookup local
    32765:  from all fwmark        2 lookup www.out
    32766:  from all lookup main
    32767:  from all lookup default

    naret# ip route list table www.out
    default via 203.114.224.8 dev eth0

    naret# ip route
    10.0.0.1 dev eth0  scope link
    10.0.0.0/24 dev eth0  proto kernel  scope link  src 10.0.0.1
    127.0.0.0/8 dev lo  scope link
    default via 10.0.0.3 dev eth0

    (silom は上述の行のどれかに属していることを確認のこと。
    この場合は 10.0.0.0/24 の行です)

    |------|
    |-DONE-|
    |------|

#### 実装後のトラフィックフロー図

    |-----------------------------------------|

    |       実装後のトラフィックフロー図      |
    |-----------------------------------------|

    INTERNET
    /\
    ||
    \/
    -----------------donmuang router---------------------
    /\                                      /\         ||
    ||                                      ||         ||
    ||                                      \/         ||
    naret                                  silom       ||
    *destination port 80 traffic=========>(cache)      ||
    /\                                      ||         ||
    ||                                      \/         \/
    \\===================================kaosarn, RAS, etc.

通常の外向きの経路には余計に 1 hop が挟まるので、 ネットワークは非対称になっています。

ここには kaosarn とインターネットの間で送受信される パケットを追跡してみましょう。

web/http トラフィック

           kaosarn の http リクエスト→naret→silom→donumuang→インターネット
           インターネットから到着したデータ→donmuang→kaosarn

web/http 以外のトラフィック (telnet など)

           kaosarn の外向きデータ→naret→donumuang→インターネット
           インターネットから到着したデータ→donmuang→kaosarn

### 経路ごとに MTU を設定し Path MTU Discovery 問題を回避する

インターネットで大量のデータを送信するとき、 一般的に大きなパケットを利用するほうが効率よく動作します。 各パケットは経路の決定を反映しますが、 1 メガバイトのファイルを送信する場合、 できるだけ大きなパケットを使えば約 700 パケットになりますし、 デフォルトの最小サイズを使うと約 4000 になります。

1 パケットあたりのペイロードの最大値は 1460 バイトですが、 この値はインターネットのあらゆる部分でサポートされているわけではありません。 よって接続を最適化するには、 適用できる最大のパケットサイズを試行を通して見つける必要があります。

このプロセスは 'Path MTU Discovery' と呼ばれています。 MTU は 'Maximum Transfer Unit' の意味です。

ルータがひとまとめで送るには大きすぎるパケットを受信し、 かつそのパケットで「フラグメント不可」ビットが立っていると、 ルータは ICMP メッセージを送り、 この理由でパケットを破棄せざるを得なかったことを伝えます。 送信元のホストはこのヒントに対応し、より小さいパケットを送ります。 これを繰り返すことで、ある経路を経由する接続における、 最適なパケットサイズを発見できるのです。

これはうまく動作していたのですが、 通信を混乱させることに心血を注ぐフーリガン達がインターネットを発見すると、 状況が変わりました。 管理者達は、インターネットサービスの安全性・堅牢性の向上を行うため、 ICMP トラフィックを (間違ったやり方で) ブロックしたり絞ったりするようになってしまったのです。

これによって現在では、Path MTU Discovery が正しく動作する状況はどんどん少なくなり、 特定の経路では失敗するようになってしまいました。 この場合 TCP/IP セッションがしばらくすると落ちるなど、 妙な動作をします。

証拠はないのですが、私の経験では、この問題が生じた 2 つのサイトでは、 いずれもそのシステムの手前で Alteon Acedirectors を使っていました。 この原因については、より詳しいどなたかが手がかりを与えてくれると期待しています。

#### 解決法

このような問題のあるサイトに行き当たったら、 手動設定で Path MTU discovery を無効にしなければなりません。 以下は Koo van den Hout の書いたものです (少々編集しています):

> 問題はこのようなものです: 私は借りている ppp 接続の mtu/mru を 296 にしました。 この接続は 33.6k しかなく、接続先のキューイングは操作できなかったからです。 296 にすると、キーを押したときの反応が妥当な時間に収まるようになりました。

> また私の側では、(当然) Linux の、マスカレードルータが動作しています。

> 最近私は「サーバ」と「ルータ」を別にしました。 よってほとんどのアプリケーションは、ルーティングを行っているのとは 別のマシンで動作するようになりました。

> すると irc へのログインに問題が出るようになりました。たいへんです! あれこれ調べたところ、irc への接続はできており、'connected' も 表示されているとわかったのですが、 irc からの motd を受信できないのです。 何がおかしいかをチェックしたところ、以前から特定の web サイトへの接続に、 MTU 関連で問題があったことを思い出しました。 これは MTU を 296 に設定したときに出現したものでした。 irc サーバは、直接の動作に関係のないトラフィックはすべてブロックしていました。 icmp もブロックの対象でした。

> 私は web サーバのオペレータに対しては、 これが問題の原因になるのだと納得させることができましたが、 irc サーバのオペレータはこの問題を修正しようとはしませんでした。

> そこで、私は外に出て行くマスカレードトラフィックに対して、 より小さな mtu を与えるようにしなければなりませんでした。 ただしローカルのイーサネットトラフィックでは、 通常の mtu を使いたかったのです (例えば nfs のトラフィックなど)。

> 解決法:

>     ip route add default via 10.0.0.1 mtu 296

> (10.0.0.1 はデフォルトゲートウェイで、マスカレードルータの 内側のアドレス)

一般に、PMTU Discovery の設定を特定の経路に対して変更することは可能です。 例えば、あるサブネットでのみ問題があるのなら、次のようにするとよいです。

    ip route add 195.96.96.0/24 via 10.0.0.1 mtu 1000

### MSS クランプによって Path MTU Discovery 問題を回避する (ADSL, ケーブル, PPPoE, PPtP ユーザ向け)

先に説明があったように、Path MTU Discovery は、 もう以前のようにはうまく動作しません。 ネットワークの経路のどこかに、MTU の制限 (<1500) があるとわかっているような場合に、 PMTU Discovery がこれを発見してくれると期待することはできません。

MTU 以外にも、パケットの最大サイズを設定する方法はあります。 これは Maximum Segment Size と呼ばれています。 これは SYN パケットの一部、TCP オプションのフィールドです。

最近の Linux カーネルと、いくつかの PPPoE ドライバ (特に Roaring Penguin のが素晴らしい) は、 この MSS を「クランプする」機能を持っています。

この方法の長所は、MSS の値を設定すると、 リモート側にも「この値より大きなパケットを送るな」ということを はっきり伝えられることです。この機構の動作には ICMP トラフィックは必要ありません。

一方欠点は、これは明らかにその場しのぎだということです。 この方法はパケットを修正しており、end-to-end の関係を保存しません。 この点を除けば、この小技は多くの場合で使えますし、魔法のように効きます。

これを行うには、iptables-1.2.1a 以降と Linux 2.4.3 以降が必要です。 基本的なコマンドラインは次の通り:

    # iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS  --clamp-mss-to-pmtu

これは接続に対する適切な MSS を計算します。 もうちょっと勇気がある方、あるいは自分の知識に確信がある方は、 次のようにすることもできます。

    # iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 128

これは先方に渡す SYN パケットの MSS を 128 にします。 パケットの小さな VoIP を使っていて、 http の巨大なパケットが来ると音声通話が途切れてしまうような場合には、 これを用いると良いでしょう。

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

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

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

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

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

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

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

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

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

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

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

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

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 を使ったものです。 両方ともテスト済みで、うまく動作します。

#### 実際のスクリプト (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 ツールを新しいバージョンにアップデートしてください!

#### 実際のスクリプト (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 ツールを新しいバージョンにアップデートしてください!

### 単一のホストまたはネットワークの速度制限

この問題は他の場所や私たちの man ページで非常に詳細に解説してあるのですが、 質問がたくさん寄せられますし、 幸いなことにトラフィック制御の完全な理解を必要としない簡単な答えが存在します。

この 3 行のスクリプトが仕事をしてくれます:

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

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

    # tc filter add dev $DEV parent 1: protocol ip prio 16 u32 \
    match ip dst 195.96.96.97 flowid 1:1

最初の行はクラスベースのキューをインターフェースにインストールし、 これが 10mbit のインターフェースであることを (計算のため) カーネルに伝えています。 ここを間違えても実害はありません。しかし正しく設定すると、 全体がより正確になります。

二番目の行は 512kbit のクラスを作り、妥当なデフォルト値を与えています。 詳細は cbq の man ページと をご覧ください。

最後の行は、この帯域制限クラスに向けるべきトラフィックを指定しています。 このルールにマッチしないトラフィックは制限されません。 より複雑なマッチ (サブネット、発信元ポート、送信先ポート) をさせたい場合は、 をご覧ください。

何かを変更してスクリプトをリロードする際には、 'tc qdisc del dev $DEV root' を実行して現在の設定を削除してください。

このスクリプトの最後に 'tc qdisc add dev $DEV parent 1:1 sfq perturb 10' という行を追加すると、さらに効果的です。この行が何を行うのかについては、 をご覧ください。

### QoS 付き nat の完全な例

私は Pedro Larroy piotr%member.fsf.org です。 ここではたくさんのユーザがいるプライベートネットワークを、 パブリックな ip アドレスを持つ Linux ルータを通してインターネットにつなぎ、 この Linux ルータにネットワークアドレス変換 (NAT) をやらせる方法について、 よくある設定例を説明したいと思います。 ここでは QoS 設定を用いて、大学寮の 198 ユーザ (私もその一人。ただし管理者です) にインターネットアクセスを提供します。 ユーザはみなピアツーピアプログラムのヘビーユーザですので、 適切なトラフィック制御が不可欠です。これが興味を持たれた lartc 読者に対する、 実用的な例になっていることを期待します。

まず先に、順番に段階を追った実践的なアプローチを取り、 最後にその処理をブート時に自動的に行うやり方を説明します。 この例が適用されるネットワークは、 パブリック ip アドレスをひとつだけ持つ Linux ルータを介して、 インターネットにつながっているプライベート LAN です。 これを複数のパブリックアドレスに拡張することは非常に簡単で、 iptables のルールをいくつか追加するだけです。 動作環境を作るには、以降のものが必要となります。

Linux 2.4.18 以降のカーネルがインストールされていること

  2.4.18 を使っている場合は、HTB パッチが必要です。

iproute

  `tc` のバイナリが HTB に対応していること。 コンパイル済みのバイナリが HTB と一緒に配布されています。

iptables

#### まず乏しいバンド幅を最適化しましょう

まずいくつか qdisc を設定して、トラフィックをクラス選別します。 htb qdisc を作り、昇順の優先度を持つ 6 つのクラスを付属させます。 次に、必ず割り当てられた速度を使え、 他のクラスが不要としているバンド幅も使えるクラスを作ります。 優先度を高く (つまり prio 番号を小さく) したクラスは、 余ったバンド幅を先に利用できます。 私たちの接続は下り 2Mb 上り 300kbit/s の ADSL です。 私は 240kbit/s を上限速度としました。これ以上にすると、 おそらく接続のどこかのバッファが効くためでしょうが、 遅延が大きくなり始めるからです。 このパラメータは実験的に測定して、近くのホストに対する遅延を見ながら 増減してください。

CEIL を上りバンド幅上限値の 75% に調整してください。 eth0 になっているところは、インターネットのアクセスに使っている パブリックなインターフェースに変更してください。 まず手始めに、以降を root のシェルで実行します。

    CEIL=240
    tc qdisc add dev eth0 root handle 1: htb default 15
    tc class add dev eth0 parent 1: classid 1:1 htb rate ${CEIL}kbit ceil ${CEIL}kbit
    tc class add dev eth0 parent 1:1 classid 1:10 htb rate 80kbit ceil 80kbit prio 0
    tc class add dev eth0 parent 1:1 classid 1:11 htb rate 80kbit ceil ${CEIL}kbit prio 1
    tc class add dev eth0 parent 1:1 classid 1:12 htb rate 20kbit ceil ${CEIL}kbit prio 2
    tc class add dev eth0 parent 1:1 classid 1:13 htb rate 20kbit ceil ${CEIL}kbit prio 2
    tc class add dev eth0 parent 1:1 classid 1:14 htb rate 10kbit ceil ${CEIL}kbit prio 3
    tc class add dev eth0 parent 1:1 classid 1:15 htb rate 30kbit ceil ${CEIL}kbit prio 3
    tc qdisc add dev eth0 parent 1:12 handle 120: sfq perturb 10
    tc qdisc add dev eth0 parent 1:13 handle 130: sfq perturb 10
    tc qdisc add dev eth0 parent 1:14 handle 140: sfq perturb 10
    tc qdisc add dev eth0 parent 1:15 handle 150: sfq perturb 10

ここではまず、深さが 1 レベルの htb ツリーを作りました。 次のような感じです。

    +---------+
    | root 1: |
    +---------+
         |
    +---------------------------------------+
    | class 1:1                             |
    +---------------------------------------+
      |      |      |      |      |      |
    +----+ +----+ +----+ +----+ +----+ +----+
    |1:10| |1:11| |1:12| |1:13| |1:14| |1:15|
    +----+ +----+ +----+ +----+ +----+ +----+

classid 1:10 htb rate 80kbit ceil 80kbit prio 0

  これが優先度が最高のクラスです。このクラスのパケットは、遅延が最も小さく、 余ったバンド幅を最初に割り当てられます。 よってこのクラスの ceil は抑え目に設定しておくのが良いでしょう。 対話的トラフィックのように、遅延が小さいことによる利益が大きいパケットは、 このクラスを使って送ります。具体的には ssh, telnet, dns, quake3, irc, SYN フラグの立ったパケット です。

classid 1:11 htb rate 80kbit ceil ${CEIL}kbit prio 1

  これがバルクトラフィックをあてがう最初のクラスです。 この例では、ローカルの web サーバから発するトラフィック (発信元ポートが 80) と、web ページのリクエスト (送信先ポートが 80) です。

classid 1:12 htb rate 20kbit ceil ${CEIL}kbit prio 2

  このクラスには、TOS フィールドで Maximize-Throughput ビットが立っている トラフィックと、ルータの「ローカルプロセス」から インターネットに向けて発するトラフィックをおきます。 よって以降のクラスは、このマシンを「経由する」トラフィックだけになります。

classid 1:13 htb rate 20kbit ceil ${CEIL}kbit prio 2

  このクラスは、他の NAT されるマシンで、 高い優先度を必要とするバルクトラフィックのためのものです。

classid 1:14 htb rate 10kbit ceil ${CEIL}kbit prio 3

  ここにはメール関連のトラフィック (SMTP, pop3 など) と、 TOS フィールドの Minimize-Cost ビットが立ったパケットを入れます。

classid 1:15 htb rate 30kbit ceil ${CEIL}kbit prio 3

  最後に、ここにはルータの背後に置かれた、NAT されたマシンからの トラフィックを入れます。 kazaa, edonkey などはここに入れ、 他のサービスと干渉しないようにします。

#### パケットのクラス選別

qdisc 設定は行いましたが、パケットのクラス選別はまだです。 ですので現在は、送信されるパケットはすべて 1:15 に入ります (なぜなら tc qdisc add dev eth0 root handle 1: htb default 15 を用いたから)。ここで、どのパケットがどこに行くのかを伝える必要があります。 ここが最も重要な部分です。

ではフィルタを設定し、パケットを iptables でクラス選別できるようにします。 私はこの作業には、まずほとんどの場合 iptables を用います。 iptables は柔軟ですし、各ルールでのパケットの計数もできるからです。 また RETURN ターゲットを用いれば、 パケットにすべてのルールを適用しなくて済みます。 次のコマンドを実行します。

    tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 1 fw classid 1:10
    tc filter add dev eth0 parent 1:0 protocol ip prio 2 handle 2 fw classid 1:11
    tc filter add dev eth0 parent 1:0 protocol ip prio 3 handle 3 fw classid 1:12
    tc filter add dev eth0 parent 1:0 protocol ip prio 4 handle 4 fw classid 1:13
    tc filter add dev eth0 parent 1:0 protocol ip prio 5 handle 5 fw classid 1:14
    tc filter add dev eth0 parent 1:0 protocol ip prio 6 handle 6 fw classid 1:15

ここでは単に、特定の FWMARK 値 (handle x fw) を持った各パケットを 対応するクラス (classid x:x) に送るようカーネルに伝えただけです。 次は、パケットへのマーク付けを iptables を使って行う方法です。

まず、パケットが iptables のフィルタを どのように通るのかを理解しなければなりません。

            +------------+                +---------+               +-------------+
    Packet -| PREROUTING |--- routing-----| FORWARD |-------+-------| POSTROUTING |- Packets
    input   +------------+    decision    +---------+       |       +-------------+    out
                                 |                          |
                            +-------+                    +--------+
                            | INPUT |---- Local process -| OUTPUT |
                            +-------+                    +--------+

すべてのテーブルが存在し、デフォルトのポリシーが ACCEPT (-P ACCEPT) になっているとします。まだ iptables に触ったことがなければ、 デフォルトで ok のはずです。 私たちのプライベートネットワークはクラス B のアドレス 172.17.0.0/16 を持ち、パブリック ip は 212.170.21.172 です。

次にカーネルに実際に NAT を行うよう指示し、 プライベートネットワークのクライアントが外部と通信を開始できるようにします。

    echo 1 > /proc/sys/net/ipv4/ip_forward
    iptables -t nat -A POSTROUTING -s 172.17.0.0/255.255.0.0 -o eth0 -j SNAT --to-source 212.170.21.172

ここでパケットが 1:15 経由で流れていることを確認しましょう:

    tc -s class show dev eth0

パケットへの印付けを開始するには、mangle テーブルの PREROUTING チェインにルールを追加します。

    iptables -t mangle -A PREROUTING -p icmp -j MARK --set-mark 0x1
    iptables -t mangle -A PREROUTING -p icmp -j RETURN

これでプライベートネットワークからインターネットのどこかに ping を行うと、 1:10 のパケット数が増加するのがわかるはずです。見てみましょう:

    tc -s class show dev eth0

ここでは -j RETURN を行って、パケットが他のルールには行かないようにしました。 icmp パケットは RETURN 以降のルールのマッチ動作の対象にはなりません。 覚えておいてください。では適切に TOS を処理するよう、 他にもルールを追加しましょう。

    iptables -t mangle -A PREROUTING -m tos --tos Minimize-Delay -j MARK --set-mark 0x1
    iptables -t mangle -A PREROUTING -m tos --tos Minimize-Delay -j RETURN
    iptables -t mangle -A PREROUTING -m tos --tos Minimize-Cost -j MARK --set-mark 0x5
    iptables -t mangle -A PREROUTING -m tos --tos Minimize-Cost -j RETURN
    iptables -t mangle -A PREROUTING -m tos --tos Maximize-Throughput -j MARK --set-mark 0x6
    iptables -t mangle -A PREROUTING -m tos --tos Maximize-Throughput -j RETURN

では ssh パケットを優先付けします:

    iptables -t mangle -A PREROUTING -p tcp -m tcp --sport 22 -j MARK --set-mark 0x1
    iptables -t mangle -A PREROUTING -p tcp -m tcp --sport 22 -j RETURN

tcp 接続を開始するパケット、つまり SYN フラグの立ったパケットは、 優先しましょう。

    iptables -t mangle -I PREROUTING -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j MARK --set-mark 0x1
    iptables -t mangle -I PREROUTING -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j RETURN

などなど。mangle の PREROUTING へのルール追加が終わったら、 次のコマンドで PREROUTING テーブルを締めくくりましょう。

    iptables -t mangle -A PREROUTING -j MARK --set-mark 0x6

これで、ここまで印付けされなかったトラフィックは 1:15 に向かいます。 実はデフォルトのクラスは 1:15 なので、この最終ステップは不必要です。 ですが設定全体の整合性を保つため、またこのルールのカウンタを見るために、 ここでは印付けを行っています。

同様の作業を OUTPUT ルールに対しても行うといいでしょう。 よってこれらのコマンドを、-A PREROUTING の代わりに -A OUTPUT とおいて繰り返します (s/PREROUTING/OUTPUT/)。 こうするとローカル (この Linux ルータ) で生成されたトラフィックも クラス選別できます。 OUTPUT チェインの最後は、-j MARK --set-mark 0x3 で締めくくり、 ローカルのトラフィックには高めの優先度を与えるようにしました。

#### この設定を改善する

これでこの設定はすべて動作するようになりました。 グラフを見て、バンド幅がどのように使われているか、 それをどのようにしたいか考えましょう。 これには長い時間をかけましょう。私の場合は最終的に、 このインターネット接続を非常にうまく動作させられるようになりました。 これを行わなければ、常にタイムアウトに悩まされたり、 新しく生成される tcp 接続にまったくバンド幅の配分がなされなかったり、 という状態だったでしょう。

特定のクラスが、ほとんどの間一杯になっているような状況でしたら、 他のキューイング規則をそこにあてがって、 バンド幅の共有をより公平にしてあげるといいでしょう。

    tc qdisc add dev eth0 parent 1:13 handle 130: sfq perturb 10
    tc qdisc add dev eth0 parent 1:14 handle 140: sfq perturb 10
    tc qdisc add dev eth0 parent 1:15 handle 150: sfq perturb 10

#### このすべてをブート時に起動する

当然ですが、いろいろな方法があります。 私の場合は [start | stop | stop-tables | start-tables | reload-tables] といったオプションを受け付ける /etc/init.d/packetfilter というスクリプトを書き、qdisc を設定し、必要なカーネルモジュールをロードし、 デーモンのように動作するようにしました。 このスクリプトは同時に、/etc/network/iptables-rules から iptables のルールもロードします。 このファイルの内容は iptables-save で保存、 iptables-restore で復元できます。

## ブリッジと、代理 ARP を用いた擬似ブリッジの構築

ブリッジは設定を変更せずにネットワークへ挿入できるデバイスです。 ネットワークスイッチは、基本的には多ポートのブリッジです。 ブリッジはそういう意味では 2 ポートのスイッチです。 しかし Linux では複数のインターフェースを用いたブリッジをサポートしており、 ほんとうの意味でのスイッチにできます。

ブリッジは、壊れたネットワークを、他には一切変更を与えずに直したいような状況で、 よく用いられます。ブリッジはレイヤ 2 のデバイスですから IP より一層下で、ルータやサービスはその存在に気づきません。 つまり透過的に特定のパケットをブロック・修正したり、絞ったりできるのです。

もうひとつの利点、ブリッジは (もし壊れた場合には) クロスケーブルやハブに置き換え可能です。

悪い知らせですが、ブリッジは非常に良く文書化されているにも関わらず、 大きな混乱を引き起こすことがあります。 ブリッジは traceroute に現れませんが、なぜか パケットが A 地点から B 地点までの間で消えたり変化したりするのです (「このネットワークは呪われている!」)。また、 「何も変えたくない」という組織が正しいことをしているかどうかにも、 議論の余地があるかもしれません。

Linux 2.4/2.5 のブリッジは、 このページ で説明されています。

### ブリッジと iptables の状況

Linux 2.4.20 では、ブリッジと iptables は何もしないと お互いを「見る」ことができません。 ブリッジのパケットが eth0 から eth1 に向かうとき、 そのパケットは iptables を経由しないのです。 つまりフィルタリング、NAT、修正 (mangling) などは行えません。 Linux 2.5.45 以降では、この点は修正されています。

あるいはまた別のプロジェクトである 'ebtables' が紹介されているのを 見かけた人もいるかもしれません。 これは MACNAT とか「ブルーティング」などの荒業ができます。 非常におそろしいものです。

### ブリッジと帯域制限

これは宣伝どおりに動作します。 各インターフェースがそれぞれどちら側についているかを確認しておいてください。 さもないと外向きのトラフィックを 内側のインターフェースで絞りかねません。これは動作しません。 必要に応じて tcpdump を使いましょう。

### 代理 ARP を用いた擬似ブリッジ

単に擬似ブリッジを実装したいだけでしたら、 数節飛ばして「実装する」まで進んでください。 しかし実際の動作に関して少々読んでおくのは、 賢いことだと思います。

擬似ブリッジは少々違った動作をします。 ブリッジでは、デフォルトだとパケットは変更されずに、 あるインターフェースから別のインターフェースへと渡ります。 ブリッジが見るのはパケットのハードウェアアドレスだけで、 それで行き先を決めます。 これはつまり、Linux が理解できないトラフィックであっても、 ハードウェアアドレスを持っていさえすればブリッジできるわけです。

「擬似ブリッジ」は異なる動作をし、 ブリッジというよりは見えないルータに近いのですが、 しかしブリッジと同様、ネットワークの設計にはほとんど影響を与えません。

実際にはブリッジではない、ということの利点は、 パケットが実際にカーネルを通り、 したがってフィルタ・修正・リダイレクト・経路変更などができる、 というところにあります。

実際のブリッジにもこれらの機能を持たせることはできますが、 イーサネットフレームダイバータのような特殊なコードが必要だったり、 あるいは前述したようなパッチが必要です。

擬似ブリッジのもうひとつの利点は、理解しないパケットは通さないことです。 よって汚物まみれのネットワークを掃除できるのです。 このような汚物 (SAP パケットとか Netbeui とか) が必要な場合は、 本物のブリッジを使ってください。

#### ARP と代理 ARP

あるホストが、同じ物理ネットワークセグメントに存在する 別のホストと通信したくなると、 そのホストはアドレス解決プロトコルのパケットを送ります。これは簡単にいうと、 「誰が 10.0.0.1 を持ってるか 10.0.0.7 に教えてくれ」というようなものです。 これに対する反応として、10.0.0.1 は「ここだよ」という短いパケットを返します。

すると 10.0.0.7 は、「ここだよ」パケットが示すハードウェアアドレスへと パケットを送ります。またこのハードウェアアドレスは比較的長い間キャッシュされ、 キャッシュの期限が切れると、再び質問が発せられます。

擬似ブリッジを作るときは、ブリッジに対してこのような ARP パケットに返信するよう命じるのです。 よってネットワークのホストは、その後のパケットをこのブリッジに対して送信します。 するとブリッジはこれらのパケットを処理し、 適切なインターフェースへと送るのです。

つまり、簡単にいえば、ブリッジの一方にあるホストが、 逆側にあるホストのハードウェアアドレスを尋ねると、 ブリッジが「俺に渡せ」というパケットで答えるのです。

このようにして、すべてのデータトラフィックは正しい場所に配達され、 常にブリッジを経由するのです。

#### 実装する

以前あまり状況の良くなかった頃は、Linux カーネルに「代理 ARP」 の動作をするよう命令できるのは、あるサブネットに対してだけでした。 よって擬似ブリッジを設定するには、 ブリッジの両側に両方への正しい経路を指定して、 代理 ARP のマッチングルールを作る必要がありました。 これには大量の入力が必要で、また間違いも起こりやすく、 ブリッジが経路を知らないネットワークへの ARP 要求に応答することも起こりがちでした。

Linux 2.4/2.5 (おそらくは 2.2 も) では、この機能はなくなり、 /proc ディレクトリの 'proxy_arp' というフラグが取って代わりました。 よって擬似ブリッジを構築する手続きは次のようになります:

1. 両側のインターフェース (ここでは 'left' と 'right' とします) に IP アドレスを割り当てる

2. 経路を作り、left 側にどのホストがあり、 right 側にどのホストがあるのか、わかるようにする。

3. echo 1 > /proc/sys/net/ipv4/conf/ethL/proxy_arp, echo 1 > /proc/sys/net/ipv4/conf/ethR/proxy_arp, によって両インターフェースの代理 ARP を有効にする。 ただし L と R は left 側、right 側各インターフェースの番号

また、ip_forwarding フラグを ON にするのも忘れないように! 本物のブリッジにはこのフラグは必要ないため、 移行してきたときにはこのフラグは OFF になっているかもしれません。

もうひとつ移行の際に注意しなければならないのは、 ネットワークの各コンピュータの ARP キャッシュをクリアしなければならない、 ということです。ARP キャッシュには、 前の古いブリッジのハードウェアアドレスが残っている (そしてそれはもう正しくない) でしょうから。

Cisco では、これは 'clear arp-cache' コマンドで実行できます。 Linux では 'arp -d ip.address' です。 キャッシュが期限切れになるのを待っていても良いですが、 これは比較的時間がかかります。

これを高速化するには、素晴らしいツール 'arping' が使えます。 これは多くのディストリビューションで 'iputils' パッケージに入っています。 arping を用いると、要求のなかった (unsolicited な) ARP メッセージを送りつけることができ、 リモートの ARP キャッシュを更新できます。

これは非常に強力なテクニックで、悪者がルーティングを崩壊させるのにも使えます!

Linux 2.4 では、この「要求のなかった ARP メッセージ」を送信する前には、 'echo 1 > /proc/sys/net/ipv4/ip_nonlocal_bind' を実行する必要があるかもしれません。

また、ネットマスクを省略して経路を指定するクセのある (あった) 人は、 作業の結果ネットワーク設定が壊れてしまうかもしれません。 特定のバージョンの route はネットマスクを正しく推量していたかもしれませんし、 何も表示することなく間違っていたかもしれません。 前述したような外科手術的なルーティングをするときには、 ネットマスクをチェックするのは「生死」に関わります。

## 動的ルーティング - OSPF と BGP

ネットワークが本当に大きくなり始めたり、 またこれを「インターネット」と考える必要が生じてきたら、 データを動的にルーティングするツールが必要です。 サイト同士はたいてい複数の経路でつながっており、 またたくさんの経路が常に生まれています。

インターネットはほぼ OSPF (RFC 2328) と BGP4 (RFC 1771) で標準化されています。Linux はこの両者を、 gated と zebra によってサポートしています。

現在のところはこの文書の守備範囲ではありませんが、 最も信頼できる著作を示しておきます。

概論:

Cisco Systems Designing large-scale IP Internetworks

OFPF について:

Moy, John T. "OSPF. The anatomy of an Internet routing protocol" Addison Wesley. Reading, MA. 1998.

Halabi 氏も OSPF のルーティング設計に関する良いガイドを書いています。 しかしこれは Cisco の web サイトから消されてしまったようです。

BGP について:

Halabi, Bassam "Internet routing architectures" Cisco Press (New Riders Publishing). Indianapolis, IN. 1997.

また

Cisco Systems Using the Border Gateway Protocol for interdomain routing

例は Cisco 指向ですが、これらは Zebra での設定言語と大変良く似ています :-)

Pedro
Larroy Tovar

    		piotr%member.fsf.org

### Zebra による OSPF の設定

もし以降の情報に不正確な部分があったり、他にも提案があったら、 どうか私に知らせてください。 Zebra は 石黒邦宏、Toshiaki Takada, Yasuhiro Ohara によって書かれた 偉大な動的ルーティングソフトウェアです。 Zebra を使うと、OSPF の設定は速やかでシンプルになりますが、 しかしその裏には、特殊な用途に向けた多くのパラメータがあるのです。 OSPF は Open Shortest Path First (最短経路を先に開く) を表しています。 その基本的な機能としては:

階層的

  ネットワークは area によってグループ化され、 それぞれは area 0 と名付けられた バックボーン area によって接続されます。 すべてのトラフィックは area 0 を通り、 area 0 のすべてのルータは、その他の area すべてに対する経路情報を持っています。

早い収束

  経路情報は、例えば RIP に比べて、非常に速く伝播します。

バンド幅を効率的に使う

  ブロードキャストではなくマルチキャストを使うので、 経路情報には興味がないであろう他のホストをこの情報で溢れさせることはなく、 ネットワークのオーバーヘッドが減少します。 また 内部ルータ (ひとつの area に属するインターフェースしか持っていないルータ) は他の area の経路情報を持つことがありません。 複数の別々の area に属するインターフェースを持っているルータは area 境界ルータ と呼ばれ、 接続している area に関するトポロジー情報を保持します。

CPU 負荷が高い

  OSPF は Dijkstra の Shortest Path First algorithm を基にしており、これは他のルーティングアルゴリズムに比べ高価です。 しかし実際には、最短経路は各 area の内部でのみ計算されますので、 それほどは悪くありません。 また中小サイズのネットワークではこれは問題にならないでしょう。 気づくこともないかもしれません。

接続状態

  OSPF はネットワークとインターフェースの特殊な特性 (バンド幅、断線状態、価格など) も考慮します。

オープンなプロトコルと GPL のソフトウェア

  OSPF はオープンなプロトコルで、Zebra は GPL ソフトウェアです。 これは独占的なソフトウェアやプロトコルに対して、 明らかな優位点です。

#### 必要条件

Linux カーネル

  CONFIG_NETLINK_DEV と CONFIG_IP_MULTICAST を組み込む必要があります (他に必要とされるものは、著者はちゃんと理解していません)。

iproute

Zebra

  お好みのパッケージマネージャか、 zebra.org から入手しましょう。

#### Zebra の設定

このようなネットワークを例に取りましょう:

    ----------------------------------------------------
    | 192.168.0.0/24                                   |
    |                                                  |
    |      Area 0    100BaseTX Switched                |
    |     Backbone     Ethernet                        |
    ----------------------------------------------------
      |           |                |              |
      |           |                |              |
      |eth1       |eth1            |eth0          |
      |100BaseTX  |100BaseTX       |100BaseTX     |100BaseTX
      |.1         |.2              |.253          |
     ---------   ------------   -----------      ----------------
     |R Omega|   |R Atlantis|   |R Legolas|      |R Frodo       |
     ---------   ------------   -----------      ----------------
      |eth0         |eth0             |             |          |
      |             |                 |             |          |
      |2MbDSL/ATM   |100BaseTX        |10BaseT      |10BaseT   |10BaseT
    ------------   ------------------------------------       -------------------------------
    | Internet |   | 172.17.0.0/16        Area 1      |       |  192.168.1.0/24 wlan  Area 2|
    ------------   |         Student network (dorm)   |       |       barcelonawireless     |
                   ------------------------------------       -------------------------------

この図に驚かないでください。 zebra はほとんどを自動的に処理してくれますので、 これらの経路すべてを設定するのも簡単です。 これらの経路を毎日維持するのは苦痛を伴う作業です。 把握しておくべき最も重要な点は、ネットワークのトポロジーです。 また area 0 は最も重要な area なので、細心の注意を払ってください。 まず zebra.conf を要求にあわせて編集し、zebra を設定します。

    hostname omega
    password xxx
    enable password xxx
    !
    ! Interface's description.
    !
    !interface lo
    ! description test of desc.
    !
    interface eth1
    multicast
    !
    ! Static default route
    !
    ip route 0.0.0.0/0 212.170.21.129
    !
    log file /var/log/zebra/zebra.log

Debian では、/etc/zebra/daemons も編集して、 ブート時に起動するように設定する必要もありました。

    zebra=yes
    ospfd=yes

次に、まだ IPv4 を使っているなら ospfd.conf を、 IPv6 なら ospf6d.conf を編集します。私の ospfd.conf は次のような感じです:

    hostname omega
    password xxx
    enable password xxx
    !
    router ospf
      network 192.168.0.0/24 area 0
      network 172.17.0.0/16 area 1
    !
    ! log stdout
    log file /var/log/zebra/ospfd.log

ここでは我々のネットワークトポロジーを ospf に教えています。

#### Zebra を実行する

では Zebra を実行します。 手動で "zebra -d" とタイプするか、"/etc/init.d/zebra start" のようなスクリプトを使います。 起動したら ospfd のログを注意深く監視しましょう。 次のような内容が現れるはずです。

    2002/12/13 22:46:24 OSPF: interface 192.168.0.1 join AllSPFRouters Multicast group.
    2002/12/13 22:46:34 OSPF: SMUX_CLOSE with reason: 5
    2002/12/13 22:46:44 OSPF: SMUX_CLOSE with reason: 5
    2002/12/13 22:46:54 OSPF: SMUX_CLOSE with reason: 5
    2002/12/13 22:47:04 OSPF: SMUX_CLOSE with reason: 5
    2002/12/13 22:47:04 OSPF: DR-Election[1st]: Backup 192.168.0.1
    2002/12/13 22:47:04 OSPF: DR-Election[1st]: DR     192.168.0.1
    2002/12/13 22:47:04 OSPF: DR-Election[2nd]: Backup 0.0.0.0
    2002/12/13 22:47:04 OSPF: DR-Election[2nd]: DR     192.168.0.1
    2002/12/13 22:47:04 OSPF: interface 192.168.0.1 join AllDRouters Multicast group.
    2002/12/13 22:47:06 OSPF: DR-Election[1st]: Backup 192.168.0.2
    2002/12/13 22:47:06 OSPF: DR-Election[1st]: DR     192.168.0.1
    2002/12/13 22:47:06 OSPF: Packet[DD]: Negotiation done (Slave).
    2002/12/13 22:47:06 OSPF: nsm_change_status(): scheduling new router-LSA origination
    2002/12/13 22:47:11 OSPF: ospf_intra_add_router: Start

SMUX_CLOSE メッセージは SNMP 関連のものなので、いまは無視しましょう。 192.168.0.1 が Designated ルータ で、 192.168.0.2 が バックアップ Designated ルータ となっていることがわかります。

zebra や ospfd インターフェースとは、次のようなコマンドで対話できます。

    $ telnet localhost zebra
    $ telnet localhost ospfd

経路がどのように伝播しているか見てみましょう。 zebra にログインして次を入力します:

    root@atlantis:~# telnet localhost zebra
    Trying 127.0.0.1...
    Connected to atlantis.
    Escape character is '^]'.

    Hello, this is zebra (version 0.92a).
    Copyright 1996-2001 Kunihiro Ishiguro.

    User Access Verification

    Password:
    atlantis> show ip route
    Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
           B - BGP, > - selected route, * - FIB route

    K>* 0.0.0.0/0 via 192.168.0.1, eth1
    C>* 127.0.0.0/8 is directly connected, lo
    O   172.17.0.0/16 [110/10] is directly connected, eth0, 06:21:53
    C>* 172.17.0.0/16 is directly connected, eth0
    O   192.168.0.0/24 [110/10] is directly connected, eth1, 06:21:53
    C>* 192.168.0.0/24 is directly connected, eth1
    atlantis> show ip ospf border-routers
    ============ OSPF router routing table =============
    R    192.168.0.253         [10] area: (0.0.0.0), ABR
    			   via 192.168.0.253, eth1
    				 [10] area: (0.0.0.1), ABR
    			   via 172.17.0.2, eth0

あるいは直接 iproute を使って:

    root@omega:~# ip route
    212.170.21.128/26 dev eth0  proto kernel  scope link  src 212.170.21.172
    192.168.0.0/24 dev eth1  proto kernel  scope link  src 192.168.0.1
    172.17.0.0/16 via 192.168.0.2 dev eth1  proto zebra  metric 20
    default via 212.170.21.129 dev eth0  proto zebra
    root@omega:~#

このように zebra の経路をみることができます。 以前には存在していなかったものです。 zebra と ospfd を起動して数秒後には経路が現れているのを見るのは、 とても気分のいいものです。他のホストへの接続状態は ping で確認できます。 zebra は自動的にルーティングを行いますから、 単に他のルータをネットワークに加え、zebra を設定すれば。どうです!

ヒント: OSPF パケットをキャプチャして解析するには次が使えます:

    tcpdump -i eth1 ip[9] == 89

OSPF の ip プロトコル番号は 89 で、 プロトコルフィールドは ip ヘッダの第 9 オクテットです。

OSPF には、特に大きなネットワーク向けに、 調整できるパラメータがたくさんあります。この howto をさらに拡充できれば、 OSPF のファインチューニングの方法論を示せると思います。

### Zebra による BGP4 の設定

Border Gateway Protocol Version 4 (BGP4) は、 RFC 1771 で記述されている動的ルーティングプロトコルです。 BGP4 を使うと、到達性情報すなわち経路テーブルを、 他の BGP4 ノードに配布できます。EGP か IGP が使え、 IGP モードでは各ノードに固有の Autonomous System (AS) 番号がつきます。 BGP4 は、クラスレスインターネットドメインルーティング (CIDR) と、経路集合 (複数の経路をひとつにまとめる) とをサポートします。

#### ネットワーク図 (例)

次のネットワーク図を以降での例として用います。 AS 1 と 50 には他にも近隣ノードがありますが、 ここでは 1 と 50 だけを私たちの近隣として設定すれば良いだけです。 この例では、各ノードはトンネルを経由して通信しますが、 これは必ずしも必要ではありません。

注意: この例での AS 番号は予約済みですので、 公式な接続を設定する場合は、RIPE から AS を取得してください。

              --------------------
              | 192.168.23.12/24 |
              |    AS: 23        |
              --------------------
                /             \
               /               \
              /                 \
    ------------------       ------------------
    | 192.168.1.1/24 |-------| 10.10.1.1/16   |
    |    AS: 1       |       |    AS: 50      |
    ------------------       ------------------

#### 設定 (例)

次の設定は 192.168.23.12/24 のものです。 他のノードにあわせるのも簡単でしょう。

まずホスト名、パスワード、デバッグスイッチなどの一般情報から始まります。

    ! hostname
    hostname anakin

    ! login password
    password xxx

    ! enable password (super user mode)
    enable password xxx

    ! path to logfile
    log file /var/log/zebra/bgpd.log

    ! debugging: be verbose (can be removed afterwards)
    debug bgp events
    debug bgp filters
    debug bgp fsm
    debug bgp keepalives
    debug bgp updates

プライベートネットワーク (RFC 1918) の再配布を制限するための アクセスリストです。

    ! RFC 1918 networks
    access-list local_nets permit 192.168.0.0/16
    access-list local_nets permit 172.16.0.0/12
    access-list local_nets permit 10.0.0.0/8
    access-list local_nets deny any

次のステップは AS ごとの設定です。

    ! Own AS number
    router bgp 23

        ! IP address of the router
        bgp router-id 192.168.23.12

        ! announce our own network to other neighbors
        network 192.168.23.0/24

        ! advertise all connected routes (= directly attached interfaces)
        redistribute connected

        ! advertise kernel routes (= manually inserted routes)
        redistribute kernel

'router bgp' ブロックには、必ずルータが接続する近隣のリストが含まれます。

        neighbor 192.168.1.1 remote-as 1
        neighbor 192.168.1.1 distribute-list local_nets in
        neighbor 10.10.1.1   remote-as 50
        neighbor 10.10.1.1   distribute-list local_nets in

#### 設定の確認

注意: vtysh はマルチプレクサで、 Zebra インターフェースをすべてひとまとめに接続します。

    anakin# sh ip bgp summary
    BGP router identifier 192.168.23.12, local AS number 23
    2 BGP AS-PATH entries
    0 BGP community entries

    Neighbor        V    AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
    10.10.0.1       4    50      35      40        0    0    0 00:28:40        1
    192.168.1.1     4     1   27574   27644        0    0    0 03:26:04       14

    Total number of neighbors 2
    anakin#
    anakin# sh ip bgp neighbors 10.10.0.1
    BGP neighbor is 10.10.0.1, remote AS 50, local AS 23, external link
      BGP version 4, remote router ID 10.10.0.1
      BGP state = Established, up for 00:29:01
      ....
    anakin#

近隣から受けとった経路を見てみましょう。

    anakin# sh ip ro bgp
    Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
           B - BGP, > - selected route, * - FIB route

    B>* 172.16.0.0/14 [20/0] via 192.168.1.1, tun0, 2d10h19m
    B>* 172.30.0.0/16 [20/0] via 192.168.1.1, tun0, 10:09:24
    B>* 192.168.5.10/32 [20/0] via 192.168.1.1, tun0, 2d10h27m
    B>* 192.168.5.26/32 [20/0] via 192.168.1.1, tun0, 10:09:24
    B>* 192.168.5.36/32 [20/0] via 192.168.1.1, tun0, 2d10h19m
    B>* 192.168.17.0/24 [20/0] via 192.168.1.1, tun0, 3d05h07m
    B>* 192.168.17.1/32 [20/0] via 192.168.1.1, tun0, 3d05h07m
    B>* 192.168.32.0/24 [20/0] via 192.168.1.1, tun0, 2d10h27m
    anakin#

## その他の候補

この章では、高度な Linux ルーティングとトラフィックの帯域制限に 関連のあるプロジェクトを紹介します。 このうちのいくつかは、これまでの章で既に紹介していたかもしれません。 あるいは他に HOWTO を必要としないくらい、 自前で非常に良く文書化されているものもあります。

Linux 用の 802.1Q VLAN 実装

  VLAN はネットワークを物理的ではなく仮想的に集合させる、 非常に賢いやり方です。VLAN に関する優れた情報は こちら にあります。この実装を使うと、Linux に VLAN をしゃべらせ、 Cisco Catalyst, 3Com: &lcub;Corebuilder, Netbuilder II, SuperStack II switch 630&rcub;, Extreme Ntwks Summit 48, Foundry: &lcub;ServerIronXL, FastIron&rcub; などと会話させることができます。

  VLAN に関する素晴らしい HOWTO が こちら にあります。

  更新: 2.4.14 (多分 13) ではカーネルに含まれています。

Linux 用 802.1Q VLAN の別の実装

  Linux 用 VLAN の別の実装です。 このプロジェクトは「権威ある」VLAN プロジェクトのアーキテクチャや コーディングスタイルに対する意見の相違からスタートし、 より全体の設計を見通し良くすることを狙ったものです。

Linux Virtual Server

  この人たちは最高です。Linux Virtual Server は、 本物のサーバのクラスタ上に、 Linux OS で動作するロードバランサを使って構築する、 非常にスケーラブルかつ高品位なサーバです。 クラスタのアーキテクチャはエンドユーザに対して透過的で、 エンドユーザは単に一台の仮想サーバを見ることになります。

  要するに、何か負荷分散したいものがあったら、 それがどんなレベルのトラフィックでも、LVS にはそれを行う能力があるのです。 彼らのテクニックは良い意味で邪悪です! 例えば、複数のマシンにあるセグメント内で同じ IP アドレスを持たせ、 それらに対する ARP を無効にするのです。LVS マシンだけが ARP を行い - そして到着パケットの処理をどのバックエンドホストが行うかを決め、 直接そのバックエンドサーバの MAC アドレスへとパケットを送るのです。 外向きのトラフィックは直接ルータへ流れ、LVS マシンは経由しません。 よって LVS マシンは世界へ向けた 5Gbit/s のコンテンツのフローを 見る必要はなく、よってボトルネックにもなりません。

  LVS は Linux 2.0, 2.2 ではカーネルパッチとして実装されていますが、 2.4/2.5 では netfilter のモジュールになっていますので、 カーネルパッチ必要はありません。 2.4 サポートはまだ開発の初期段階ですので、是非いじくり回して、 フィードバックやパッチを送ってください。

CBQ.init

  CBQ の設定は、ちょっとやる気をくじかれるところがあります。 特に、やりたいことが単にルータの背後のコンピュータをいくつか絞りたいだけ、 というような場合にはなおさらです。 CBQ.init は、Linux の設定をより単純な文法で可能にしてくれます。

  例えば、(10mbit eth1 につながった) 192.168.1.0/24 サブネットの すべてのコンピュータに対し、ダウンロードの速度を 28kbit/s に制限したければ、 次の内容を CBQ.init の設定ファイルに書くだけです:

      DEVICE=eth1,10Mbit,1Mbit
      RATE=28Kbit
      WEIGHT=2Kbit
      PRIO=5
      RULE=192.168.1.0/24

  「なぜ、どのように」という内容に興味がなければ、 絶対にこのプログラムを使うべきです。私たちは製品で CBQ.init を使っていますが、 非常にうまく動作しています。 時刻に依存した帯域制限といったような、より高度な設定も可能です。 文書はスクリプトに埋め込まれており、これが README が見つからない理由です。

Chronox 手軽な帯域制限スクリプト

  Stephan Mueller (smueller@chronox.de) は 2 つの便利なスクリプトを書きました。 'limit.conn' と 'shaper' です。'limit.conn' を使うと、 あるひとつのダウンロードセッションを手軽に絞れます。 こんな感じです:

      # limit.conn -s SERVERIP -p SERVERPORT -l LIMIT

  Linux 2.2 と 2.4/2.5 で動作します。

  'shaper' の方はより複雑で、 たくさんの異なるキューを、iptables のルールをもとにして作れます。 iptables は絞りたいパケットに印を付けるのに使います。

Virtual Router Redundancy プロトコルの実装 (サイト 1, サイト 2)

  これは純粋に冗長性 (redundancy) のためのものです。 IP アドレスと MAC アドレスが同じ 2 台のマシンを用いて、 さらに別の IP アドレスと MAC アドレスを持った、仮想的なマシンを作ります。 もともとは (常に同じ MAC アドレスを必要とする) ルータ向けのものだったのですが、他のサーバでも使えます。

  このアプローチの美点は、信じられないくらい設定が簡単なことです。 カーネルのコンパイルもパッチ当ても必要なく、すべてはユーザ空間です。

  次のコマンドを、サービスに参加するすべてのマシンで実行するだけです:

      # vrrpd -i eth0 -v 50 10.0.0.22

  これで出来上がり! この時点で 10.0.0.22 はどれかひとつのサーバ (多分最初に vrrp デーモンを実行したやつ) が保有します。 ここでそのコンピュータをネットワークから切り離すと、 非常にすばやく他のコンピュータが 10.0.0.22 と その MAC アドレスとを引き継ぎます。

  私は手元で試してみて、1 分で動作させることができました。 何かおかしな理由で、デフォルトゲートウェイの情報が消えてしまったのですが、 -n フラグを付ければこれは回避できました。

  これはフェイルオーバーの「ライブ画像」です:

      64 bytes from 10.0.0.22: icmp_seq=3 ttl=255 time=0.2 ms
      64 bytes from 10.0.0.22: icmp_seq=4 ttl=255 time=0.2 ms
      64 bytes from 10.0.0.22: icmp_seq=5 ttl=255 time=16.8 ms
      64 bytes from 10.0.0.22: icmp_seq=6 ttl=255 time=1.8 ms
      64 bytes from 10.0.0.22: icmp_seq=7 ttl=255 time=1.7 ms

  ping パケットはひとつも失われていません! 4 番目のパケットの後、私は P200 をネットワークから切り離したのですが、 486 が引き継ぎました。よって遅延が大きくなっていることがわかります。

tc-config (サイト)

  tc_config は linux 2.4 以降でのトラフィック制御を RedHat システム と (おそらく) その派生システムで行うためのスクリプト集です (linux 2.2.X と ipchains とでのものは古くなっています)。 ルートには cbq qdisc を、葉には sfq qdisc を使っています。

  snmp でトラフィック制御の統計を取るため、 snmp_pass ユーティリティが含まれています。

## さらに先に進むために

http://snafu.freedom.org/linux2.2/iproute-notes.html

  カーネルからのたくさんの情報とコメント

http://www.davin.ottawa.on.ca/ols/

  Linux トラフィック制御の著者の一人である Jamal Hadi Salim のスライド

http://defiant.coinet.com/iproute2/ip-cref/

  Alexey の LaTeX 文書の HTML 版 - iproute2 の一部を非常に詳細に説明しています

http://www.aciri.org/floyd/cbq.html

  Sally Floyd の CBQ に関する優れたページ。 彼女のオリジナルの論文もあります。Linux 固有ではありませんが、 CBQ の理論と利用について、良い議論をしています。 非常に技術的な内容ですが、尖った人たちには良い読み物でしょう。

Differentiated Services on Linux

  Werner Almesberger, Jamal Hadi Salim, Alexey Kuzunetsov によるこの 文書 は、Linux カーネルにおける DiffServe の機能、なかでも TBF, GRED, DSMARK qdisc と tcindex クラス選別器について説明しています。

http://ceti.pl/~kravietz/cbq/NET4_tc.html

  また別の HOWTO ですが、こちらはポーランド語です! でもコマンドラインをコピー/ペーストすれば、どの言語でも同じように動作します。 著者は我々とも共同作業をしているので、 近いうちにこの HOWTO にも書いてくれるでしょう。

IOS Committed Access Rate

  賞賛すべき気質を持った、Cisco の協力的な人たちが、 彼らの文書をオンラインに載せてくれました。 Cisco では文法は異なりますが、概念は同じです。 ただし我々のほうができることは多く、 また車に等しい価格のルータは不要ですが :-)

Docum experimental site(site)

  Stef Coene は、Linux サポートを販売するよう彼の上司を納得させるのに忙しく、 よって、特にバンド幅の管理に関して、たくさんの実験を行っています。 彼のサイトには実用的な情報・例・テストが満載されており、 また CBQ/tc のいくつかのバグについても指摘があります。

TCP/IP Illustrated, volume 1, W. Richard Stevens, ISBN 0-201-63346-9

  TCP/IP を本当に理解したければ、読まなければならない本です。 面白い本でもあります。

Policy Routing Using Linux, Matthew G. Marsh, ISBN 0-672-32052-5

  ポリシールーティングの入門書で、たくさん例があります。

## 謝辞

最終的な目的は、この HOWTO に貢献してくれた人、 また物事の動作原理に対する我々の理解を助けてくれた人、 のすべてをリストすることです。 いまのところ Netfilter のようなスコアボードを置く計画はありませんが、 協力してくださった人たちは是非覚えておきたいと思っています。

- Junk Alins

      juanjo@mat.upc.es

- Joe Van Andel

- Michael T. Babcock

      mbabcock@fibrespeed.net

- Christopher Barton

      cpbarton%uiuc.edu

- Ard van Breemen

      ard%kwaak.net

- Ron Brinker

      service%emcis.com

- ?ukasz Bromirski

      l.bromirski@mr0vka.eu.org

- Lennert Buytenhek

      buytenh@gnu.org

- Esteve Camps

      esteve@hades.udg.es

- Ricardo Javier Cardenes

      ricardo%conysis.com

- Stef Coene

      stef.coene@docum.org

- Don Cohen

      don-lartc%isis.cs3-inc.com

- Jonathan Corbet

      lwn%lwn.net

- Gerry Creager N5JXS

      gerry%cs.tamu.edu

- Marco Davids

      marco@sara.nl

- Jonathan Day

      jd9812@my-deja.com

- Martin Devera aka devik

      devik@cdi.cz

- Hannes Ebner

      he%fli4l.de

- Derek Fawcus

      dfawcus%cisco.com

- David Fries

      dfries%mail.win.org

- Stephan"Kobold" Gehring

      Stephan.Gehring@bechtle.de

- Jacek Glinkowski

      jglinkow%hns.com

- Andrea Glorioso

      sama%perchetopi.org

- Thomas Graf

      tgraf%suug.ch

- Sandy Harris

      sandy%storm.ca

- Nadeem Hasan

      nhasan@usa.net

- Erik Hensema

      erik%hensema.xs4all.nl

- Vik Heyndrickx

      vik.heyndrickx@edchq.com

- Spauldo Da Hippie

      spauldo%usa.net

- Koos van den Hout

      koos@kzdoos.xs4all.nl

- Stefan Huelbrock <shuelbrock%datasystems.de>

- Alexander W. Janssen <yalla%ynfonatic.de>

- Andreas Jellinghaus <aj%dungeon.inka.de>

- Gareth John <gdjohn%zepler.org>

- Dave Johnson

      dj@www.uk.linux.org

- Martin Josefsson <gandalf%wlug.westbo.se>

- Andi Kleen <ak%suse.de>

- Andreas J. Koenig <andreas.koenig%anima.de>

- Pawel Krawczyk <kravietz%alfa.ceti.pl>

- Amit Kucheria <amitk@ittc.ku.edu>

- Pedro Larroy

      piotr%member.fsf.org

  - Chapter 15, section 10: Example of a full nat solution with QoS

  - Chapter 17, section 1: Setting up OSPF with Zebra

- Edmund Lau <edlau%ucf.ics.uci.edu>

- Philippe Latu <philippe.latu%linux-france.org>

- Arthur van Leeuwen <arthurvl%sci.kun.nl>

- Jose Luis Domingo Lopez

      jdomingo@24x7linux.com

- Robert Lowe

      robert.h.lowe@lawrence.edu

- Jason Lunz <j@cc.gatech.edu>

- Stuart Lynne <sl@fireplug.net>

- Alexey Mahotkin <alexm@formulabez.ru>

- Predrag Malicevic <pmalic@ieee.org>

- Patrick McHardy <kaber@trash.net>

- Andreas Mohr <andi%lisas.de>

- James Morris <jmorris@intercode.com.au>

- Andrew Morton <akpm%zip.com.au>

- Wim van der Most

- Stephan Mueller <smueller@chronox.de>

- Togan Muftuoglu <toganm%yahoo.com>

- Chris Murray <cmurray@stargate.ca>

- Patrick Nagelschmidt <dto%gmx.net>

- Ram Narula <ram@princess1.net>

- Jorge Novo <jnovo@educanet.net>

- Patrik <ph@kurd.nu>

- P?l Osgy?ny <oplab%westel900.net>

- Lutz Preßler <Lutz.Pressler%SerNet.DE>

- Jason Pyeron <jason%pyeron.com>

- Rod Roark <rod%sunsetsystems.com>

- Pavel Roskin <proski@gnu.org>

- Rusty Russell <rusty%rustcorp.com.au>

- Mihai RUSU <dizzy%roedu.net>

- Rob Pitman <rob%pitman.co.za>

- Jamal Hadi Salim <hadi%cyberus.ca>

- Ren? Serral <rserral%ac.upc.es>

- David Sauer <davids%penguin.cz>

- Sheharyar Suleman Shaikh <sss23@drexel.edu>

- Stewart Shields <MourningBlade%bigfoot.com>

- Nick Silberstein <nhsilber%yahoo.com>

- Konrads Smelkov <konrads@interbaltika.com>

- William Stearns

      wstearns@pobox.com

- Andreas Steinmetz <ast%domdv.de>

- Matthew Strait <straitm%mathcs.carleton.edu>

- Jason Tackaberry <tack@linux.com>

- Charles Tassell <ctassell%isn.net>

- Glen Turner <glen.turner%aarnet.edu.au>

- Tea Sponsor: Eric Veldhuyzen <eric%terra.nu>

- Thomas Walpuski <thomas%bender.thinknerd.de>

- Song Wang <wsong@ece.uci.edu>

- Frank v Waveren <fvw@var.cx>

- Chris Wilson

      chris@netservers.co.uk

- Lazar Yanackiev

      Lyanackiev%gmx.net

## 日本語訳について

本文書は中野武雄が翻訳しました。ライセンスは原文と同じく Open Publication License の v1.0 およびそれ以降に従います。

本文書の翻訳にあたっては、JF Project のメーリングリストを 利用させていただきました。 杉山友章さんには、全文にわたるチェックをしていただきました。 石塚 進さんには、多くの技術的なコメントをいただきました。 ここに記して感謝します。