rp_filter が静的ルーティング下の traceroute に及ぼす影響
静的ルーティングを設定した Windows と Linux (RHEL6.4) で、それぞれ tracert と traceroute を実行したとき不思議な差が出た。解明までのプロセスが自分の好みにドンピシャだったので順を追って書く。
環境
環境を大まかに書くと下図のようになる。外部との通信用のデフォルトゲートウェイと、内部のシステムである targethost と通信するための内部ネットワークに接続するゲートウェイ A がある。Windows Server 2012 のサーバと RHEL 6.4 のサーバそれぞれに、targethost 向けの静的ルートを入れ、ゲートウェイにはゲートウェイ A を設定した。
設定が終わったので、「さて、正しいルートを通っているかな♪」と tracert と traceroute を叩く。すると違いが。
Windows Server 2012 での実行結果は希望通りのものだ。何の問題もない。
> tracert -d t.t.t.t Tracing route to t.t.t.t over a maximum of 30 hops 1 1 ms <1 ms <1 ms a.a.a.a 2 27 ms 23 ms 29 ms x.x.x.x 3 10 ms 17 ms 9 ms y.y.y.y 4 11 ms 11 ms 12 ms z.z.z.z 5 18 ms 12 ms 10 ms t.t.t.t Trace complete.
RHEL6.4 での実行結果は最初と最後以外が * (応答無しを示すアスタリスク) になってしまう。
# traceroute -n t.t.t.t traceroute to t.t.t.t (t.t.t.t), 30 hops max, 60 byte packets 1 a.a.a.a 0.658 ms 1.111 ms 1.558 ms 2 * * * 3 * * * 4 * * * 5 t.t.t.t 17.492 ms 18.005 ms 18.002 ms
同じネットワーク設定で、同じ静的ルーティングを設定しているのに、tracert と traceroute の結果には差がある。応答があるため、targethost と通信はできているが、経路が正しいかわからない(実際にはゲートウェイ指定までがサーバの責任ではあるのだが)。
最初の着眼点: tracert と traceroute の違い
まずこの差を見た時に思い出された古い経験があった。tracert と traceroute は機能は同じでも通信に使うプロトコルが違うのだ。
Windows の tracert は ICMP を使う。traceroute は TCP, UDP, ICMP を使えるが、デフォルトでは UDP のパケットを送出する。Windows Server 2012 と RHEL6.4 のサーバで違いが出た原因はこれだろうと推測。traceroute に ICMP を使わせればよいと考えて -I オプションをつけて実行。
状況変わらず。tracert と traceroute のプロトコルの違いが原因ではなかったことになる。
# traceroute -I -n t.t.t.t traceroute to t.t.t.t (t.t.t.t), 30 hops max, 60 byte packets 1 a.a.a.a 0.824 ms 1.276 ms 1.739 ms 2 * * * 3 * * * 4 * * * 5 t.t.t.t 23.943 ms 25.183 ms 25.400 ms
次の着眼点: タイムアウト
traceroute の man を眺めて、traceroute の原理を思い返して、次に試したのがタイムアウトのオプション指定だった。ルーターからの Time Exceeded の通知が遅いのかと思い -w 60 オプションをつけて実行。
60秒待たされたが状況変わらず。冷静に考えれば Windows Server 2012 側のレスポンス速度はミリ秒オーダーであり、デフォルトの5秒を超えるとは考え難かった。
# traceroute -I -n -w 60 t.t.t.t traceroute to t.t.t.t (t.t.t.t), 30 hops max, 60 byte packets 1 a.a.a.a 9.642 ms 9.684 ms 9.576 ms 2 * * * 3 * * * 4 * * * 5 t.t.t.t 20.978 ms 21.206 ms 20.643 ms
急展開の発見: ルーターからの Time Exceeded はサーバーまで返っている!
ルーターからの応答が本当に無いのか、それとも Time Exceeded ではない何かなど、traceroute の想定していない応答になってしまっているのか、それを確認したくて tcpdump を実行してみた。するとなんと、全てのルーターからちゃんと ICMP Time Exceeded が返って来ているではないか。
tcpdump の見やすさのため、以下のオプションを指定して実行した。
- -q 1: 各TTLごとに1パケットずつ(通常は3パケット)
- -z 1: 1秒に1パケットずつ(通常は16パケット同時)
# traceroute -I -n -q 1 -z 1 t.t.t.t traceroute to t.t.t.t (t.t.t.t), 30 hops max, 60 byte packets 1 a.a.a.a 1.963 ms 2 * 3 * 4 * 5 t.t.t.t 10.490 ms
別のターミナルで実行しておいた tcpdump の結果はこうなる。見事に ICMP time exceeded が確認できる。
# tcpdump -n -i eth1 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes 06:14:41.565256 IP rhel6.4 > t.t.t.t: ICMP echo request, id 15728, seq 1, length 40 06:14:41.566080 IP a.a.a.a > rhel6.4: ICMP time exceeded in-transit, length 68 06:14:42.564260 IP rhel6.4 > t.t.t.t: ICMP echo request, id 15728, seq 2, length 40 06:14:42.598026 IP x.x.x.x > rhel6.4: ICMP time exceeded in-transit, length 36 06:14:43.565176 IP rhel6.4 > t.t.t.t: ICMP echo request, id 15728, seq 3, length 40 06:14:43.573244 IP y.y.y.y > rhel6.4: ICMP time exceeded in-transit, length 36 06:14:44.565501 IP rhel6.4 > t.t.t.t: ICMP echo request, id 15728, seq 4, length 40 06:14:44.579159 IP z.z.z.z > rhel6.4: ICMP time exceeded in-transit, length 76 06:14:45.566333 IP rhel6.4 > t.t.t.t: ICMP echo request, id 15728, seq 5, length 40 06:14:45.576810 IP t.t.t.t > rhel6.4: ICMP echo reply, id 15728, seq 5, length 40
サーバまで ICMP Time Exceeded が返っているとなると、それが traceroute には伝わっていないことになる。そんな芸当ができるのはカーネルかファイアウォールぐらいしかない。しかし、iptables は利用していないため、ファイアウォール要因ではない・・・
記憶のリンク: rp_filter カーネルパラメータ
そこまで推測が進んだ時にあるカーネルパラメータの存在が(大袈裟に言えば電撃的に)思い出された。「rp_filter」だ。この rp_filter は Oracle RAC を組む為の Oracle Grid Infrastructure のインストレーション・ガイドに登場するカーネルパラメータで、「よくわからんけど経路云々でパケットをドロップするかもしれない危険なものだから(RACの相互接続はプライベードで閉じているネットワークなので)無効化すべき」程度には認識していた。
2.7.9 複数のプライベート・インターコネクトとEnterprise Linux
カーネル2.6.31以上(Oracle Unbreakable Enterprise Kernel 2.6.32を含む)では、戻り経路フィルタのバグが修正されています。この修正の結果、プライベート・インターコネクトに複数のNICを使用するOracle RACシステムでは、現在、rp_filterパラメータに固有の設定が必要です。この要件は、Linuxカーネル2.6.32以上を実行しているすべてのExadataシステムにも適用されます。rp_filterパラメータにこれらの設定を行わないと、インターコネクト・パケットが遮断または破棄される可能性があります。
rp_filterの値で、戻り経路フィルタがフィルタなし(0)、厳密なフィルタ(1)または緩いフィルタ(2)に設定されます。プライベート・インターコネクトの場合は、rp_filterの値を0または2に設定します。プライベート・インターコネクトNICを1に設定すると、プライベート・インターコネクトで接続の問題が発生する可能性があります。プライベート・インターコネクトは、分離されたプライベートのネットワーク上にあるはずなので、このフィルタを無効または解放することは危険だとは考えられていません。
たとえば、eth1およびeth2がプライベート・インターコネクトNICで、eth0がパブリック・ネットワークNICの場合、/etc/sysctl.confで次のエントリを使用して、プライベート・アドレスのrp_filterを2(緩いフィルタ)に設定し、パブリック・アドレスを1(厳密なフィルタ)に設定します。
net.ipv4.conf.eth2.rp_filter = 2
net.ipv4.conf.eth1.rp_filter = 2
net.ipv4.conf.eth0.rp_filter = 1
Enterprise Linux 5.6(Enterprise Linux 5 Update 6)にはinitscripts-8.45.33-1.0.4.el5.i386.rpmを使用した修正が含まれ、これによって、カーネル・パラメータnet.ipv4.conf.default.rp_filterが2(解放モード)に設定されます。そのため、Unbreakable Linux KernelをEnterprise Linux 5.6の最上位に適用した後、すべてのNICのrp_filter値が2に設定されているため、手動での変更が不要になる場合があります。パブリック・ネットワークでより厳密な戻り経路フィルタが必要な場合は、パブリックNICのrp_filterを1に設定します。
犯人はお前だ!: rp_filter カーネルパラメータ
パケットをドロップする可能性があるということで真面目に rp_filter を調べてみる。以下の説明が一番わかりやすい。
Linux Advanced Routing & Traffic Control HOWTO 13.1. 戻り経路フィルタ (Reverse Path Filtering)
デフォルトでは、ルータはすべてをルーティングします。 パケットが「明らかに」自分のネットワークには属していなくてもです。 よくある例は、プライベートの IP 空間がインターネットに漏れてしまう問題です。 195.96.96.0/24 に向かう経路があるインターフェースに対しては、 212.64.94.1 から発したパケットは、本来到着しないはずです。
ほとんどの人はこの機能を無効にしたいと思うはずですから、 カーネルハッカー達はこれを簡単できるようにしてくれました。 /proc 以下にあるファイルを使うと、カーネルに対してこの指示ができます。 この方法は戻り経路フィルタ (Reverse Path Filtering) と呼ばれています。基本的には、あるパケットに対する返信が、 そのパケットの入ってきたインターフェースに向かわない場合、 このパケットはインチキだとみなされて無視されることになります。
tcpdump の結果を見ているから、ルータ X, Y, Z からの ICMP Time Exceeded パケットのソースアドレスがそれぞれ x.x.x.x, y.y.y.y, z.z.z.z であることは明らかである。そして、targethost (t.t.t.t) への静的ルーティングは定義しているが、x.x.x.x, y.y.y.y, z.z.z.z にもそれらが所属するネットワーク向けにも静的ルーティングは定義していない。つまり、仮に x.x.x.x, y.y.y.y, z.z.z.z に返信しようとしても、それらが入って来たインターフェースは使われず、デフォルトゲートウェイに向いているインターフェースを使用する。まさに rp_filter が「インチキ」と判定するパケットだ。
targethost (t.t.t.t) との通信に使うインターフェース(eth1)の rp_filter を無効(0) にして traceroute を実行する。
# cat /proc/sys/net/ipv4/conf/eth1/rp_filter 1 # echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter # cat /proc/sys/net/ipv4/conf/eth1/rp_filter 0 # traceroute -I -n t.t.t.t traceroute to t.t.t.t (t.t.t.t), 30 hops max, 60 byte packets 1 a.a.a.a 0.658 ms 1.111 ms 1.558 ms 2 x.x.x.x 26.490 ms 27.348 ms 28.381 ms 3 y.y.y.y 19.065 ms 19.799 ms 20.006 ms 4 z.z.z.z 21.561 ms 21.791 ms 21.974 ms 5 t.t.t.t 17.492 ms 18.005 ms 18.002 ms
キタ━━━━(゚∀゚)━━━━!!
これでこれまでの事象の説明がつくことになる。
Windows Server 2012
Windows には rp_filter のような動作はないのだろう。
まとめ
覚えておくべきことは以下の通りと思う。
- Windows の tracert と Linux の traceroute はデフォルトで使用するプロトコルが異なる。
- tracert は ICMP を使う。
- traceroute は UDP を使う。
- traceroute で ICMP を使うには -I オプションを使用する。
- RHEL 6 (7も) では静的ルーティングしているホスト・ネットワークに対する traceroute では途中のホップが無応答(*)表示になる。
今ある知識を動員してあがきまくっているうちに、単なる情報として記憶されていたもの(今回だと「rp_filter」)が呼び覚まされて急激に理解、利用されていくというプロセスは個人的にとても心地よい。水溶液に解けていた物質がなんらかのショックにより急激に結晶化するような。