15.10. QoS 付き nat の完全な例

私は Pedro Larroy です。 ここではたくさんのユーザがいるプライベートネットワークを、 パブリックな ip アドレスを持つ Linux ルータを通してインターネットにつなぎ、 この Linux ルータにネットワークアドレス変換 (NAT) をやらせる方法について、 よくある設定例を説明したいと思います。 ここでは QoS 設定を用いて、大学寮の 198 ユーザ (私もその一人。ただし管理者です) にインターネットアクセスを提供します。 ユーザはみなピアツーピアプログラムのヘビーユーザですので、 適切なトラフィック制御が不可欠です。これが興味を持たれた lartc 読者に対する、 実用的な例になっていることを期待します。

まず先に、順番に段階を追った実践的なアプローチを取り、 最後にその処理をブート時に自動的に行うやり方を説明します。 この例が適用されるネットワークは、 パブリック ip アドレスをひとつだけ持つ Linux ルータを介して、 インターネットにつながっているプライベート LAN です。 これを複数のパブリックアドレスに拡張することは非常に簡単で、 iptables のルールをいくつか追加するだけです。 動作環境を作るには、以降のものが必要となります。

Linux 2.4.18 以降のカーネルがインストールされていること

2.4.18 を使っている場合は、HTB パッチが必要です。

iproute

tc のバイナリが HTB に対応していること。 コンパイル済みのバイナリが HTB と一緒に配布されています。

iptables

15.10.1. まず乏しいバンド幅を最適化しましょう

まずいくつか 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 などはここに入れ、 他のサービスと干渉しないようにします。

15.10.2. パケットのクラス選別

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 で締めくくり、 ローカルのトラフィックには高めの優先度を与えるようにしました。

15.10.3. この設定を改善する

これでこの設定はすべて動作するようになりました。 グラフを見て、バンド幅がどのように使われているか、 それをどのようにしたいか考えましょう。 これには長い時間をかけましょう。私の場合は最終的に、 このインターネット接続を非常にうまく動作させられるようになりました。 これを行わなければ、常にタイムアウトに悩まされたり、 新しく生成される 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
			

15.10.4. このすべてをブート時に起動する

当然ですが、いろいろな方法があります。 私の場合は [start | stop | stop-tables | start-tables | reload-tables] といったオプションを受け付ける /etc/init.d/packetfilter というスクリプトを書き、qdisc を設定し、必要なカーネルモジュールをロードし、 デーモンのように動作するようにしました。 このスクリプトは同時に、/etc/network/iptables-rules から iptables のルールもロードします。 このファイルの内容は iptables-save で保存、 iptables-restore で復元できます。