こんにちは、インフラエンジニアのryuです。
Linuxサーバーに2枚目のNICを足して、別のネットワークにもIPアドレスを持たせた。同じネットワークからは届くのに、離れたネットワークからは2枚目のIPアドレスにだけつながらない。
こういう相談は、現場ではかなりよくあります。設定を見直しても、IPアドレスもサブネットマスクも合っていて、どこが悪いのか分からない、という状態になりがちです。
この症状の原因は、多くの場合「行きの通信」ではなく「戻りの通信」にあります。
今日は、2枚のNICを持つLinuxサーバーで起きる非対称ルーティングと、それを止めるカーネルの仕組み rp_filter について、コマンドで確かめながら整理していきます。
片方のIPアドレスにだけつながらないのはなぜ?¶
まずは、よくある構成を例にします。サーバーにNICが2枚あり、それぞれ別のネットワークにつながっている形です。
| NIC | IPアドレス | ネットワーク | 役割 |
|---|---|---|---|
eth0 |
192.168.10.10 |
192.168.10.0/24 |
サービス用。デフォルトゲートウェイ 192.168.10.1 |
eth1 |
192.168.20.10 |
192.168.20.0/24 |
管理用。ゲートウェイ 192.168.20.1 |
ここで、別のネットワーク 10.0.0.0/8 にいる端末 10.1.1.5 から、管理用の 192.168.20.10 にSSHしようとします。すると、つながらずにタイムアウトします。
一方、192.168.10.10 にはつながります。192.168.20.0/24 の中にいる端末からなら、192.168.20.10 にもつながります。
この「遠くから、片方のIPアドレスにだけ」という条件が、原因を探す手がかりになります。
Linuxは「来たNIC」ではなく「宛先」で返事の出口を決める¶
ポイントは、Linuxが返事のパケットを送るときの出口の選び方です。
Linuxは、パケットを送るときにルーティングテーブルを宛先で引きます。受け取ったパケットがどのNICから入ってきたかは、基本的に考慮しません。
先ほどの例で流れを追ってみます。
10.1.1.5からの要求は、ゲートウェイ192.168.20.1を通ってeth1に届く- サーバーは返事を
10.1.1.5宛てに送ろうとする - ルーティングテーブルには
10.0.0.0/8向けの経路がないので、デフォルトルートが選ばれる - デフォルトルートは
eth0側なので、返事はeth0から出ていく
行きは eth1、戻りは eth0 です。このように行きと戻りで通る道が違う状態を、非対称ルーティングと呼びます。
ルーティングテーブルの基本的な考え方は、ルーティングとは?で解説しています。
戻りの通信が別のNICから出ると何が起きるか¶
戻りの通信が eth0 から出ていくと、途中のどこかで捨てられることがあります。代表的なのは次の2つです。
| 捨てられる場所 | 理由 |
|---|---|
| 途中のファイアウォールやルーター | 行きの通信を見ていないので、返事だけ届いても不正な通信として扱う。送信元が 192.168.20.10 なのに 192.168.10.0/24 側から来るので、なりすましと判断されることもある |
| サーバー自身のカーネル | rp_filter の設定によっては、そもそも eth1 に届いた要求の時点で捨てる |
2つ目は、返事を送る以前の話です。要求が届いているのに、カーネルが受け取りを拒否しているので、返事は一切出ていきません。ここで出てくるのが rp_filter です。
rp_filterとは?¶
rp_filter は、受け取ったパケットの送信元IPアドレスを検証するカーネルの設定です。Reverse Path Filter(逆経路フィルター)の略です。
考え方はシンプルです。受け取ったパケットの送信元に、もし自分が返事を送るとしたら、どのNICから出すかを調べます。その結果と、パケットが入ってきたNICが一致するかを見ます。
Linuxカーネルのドキュメントでは、設定値を次のように説明しています。
0 - 送信元の検証をしない 1 - RFC3704で定義された厳格モード。受け取ったパケットごとにルーティングテーブルを引き、入ってきたインターフェースが最適な逆経路でなければ検証失敗とする 2 - RFC3704で定義された緩和モード。送信元アドレスがどのインターフェースからも到達できない場合に検証失敗とする (Linux kernel documentation "IP Sysctl" の rp_filter より意訳)
先ほどの例で厳格モード(1)だと、10.1.1.5 への最適な経路は eth0 です。ところが要求は eth1 から入ってきたので、検証失敗としてパケットは捨てられます。
同じドキュメントには、厳格モードはIPアドレスのなりすましを防ぐための推奨であり、非対称ルーティングなど複雑な経路を使う場合は緩和モードが推奨される、という説明もあります。
all とインターフェースごとの値は「大きいほう」が使われる¶
rp_filter には、全体の設定 net.ipv4.conf.all.rp_filter と、NICごとの設定 net.ipv4.conf.eth1.rp_filter があります。
ここで間違えやすいのが、どちらが優先されるかです。カーネルのドキュメントでは、conf/all と conf/{インターフェース} のうち大きいほうの値が、そのインターフェースでの検証に使われると説明されています。
つまり、eth1 だけ 0 にしても、all が 1 のままなら厳格モードで動きます。「NICごとに無効にしたのに効かない」というときは、まずここを疑います。
なお、カーネル自体の初期値は 0 ですが、ディストリビューションによっては起動時に 1 や 2 を設定しています。自分の環境の値は、必ず実機で確かめてください。
原因をコマンドで確かめてみよう¶
ここからは、実際に切り分けるときの手順です。上から順に確かめていくと、どこで止まっているかが分かります。
コマンドの基本的な使い方は、ipコマンドが使えない?iproute2をインストールする方法やrouteコマンドの使い方も参考にしてください。
手順1:NICとルーティングテーブルを見る¶
まずは、IPアドレスとルーティングテーブルの状態を確認します。
# NICごとのIPアドレスを一覧で見る
ip -br addr
# ルーティングテーブル(main)を見る
ip route
出力の例は次のとおりです。
default via 192.168.10.1 dev eth0 proto static metric 100
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.10 metric 100
192.168.20.0/24 dev eth1 proto kernel scope link src 192.168.20.10 metric 101
デフォルトルートが eth0 側にしかなく、eth1 側には直接つながっているネットワークの経路しかありません。この時点で、遠くのネットワークへの返事は eth0 から出ていくことが予想できます。
手順2:ip route get で戻りの出口を確かめる¶
予想が合っているかは、ip route get で確かめられます。宛先を指定すると、カーネルが実際にどの経路を選ぶかを教えてくれます。
# 10.1.1.5 宛てに、送信元を 192.168.20.10 として送るとしたら
ip route get 10.1.1.5 from 192.168.20.10
10.1.1.5 from 192.168.20.10 via 192.168.10.1 dev eth0 uid 0
送信元が eth1 のIPアドレスなのに、出口が dev eth0 になっています。これが非対称ルーティングが起きている証拠です。
手順3:rp_filter の値を見る¶
次に、rp_filter の値を確認します。all と該当NICの両方を見るのがポイントです。
sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.eth1.rp_filter
どちらかが 1 なら、eth1 は厳格モードで動いています。この場合、要求は返事を作る前にカーネルで捨てられています。
捨てられたパケットを記録したいときは、log_martians を有効にするとカーネルのログに出るようになります。調査が終わったら元に戻しておきましょう。
# 一時的に有効化(再起動で元に戻る)
sudo sysctl -w net.ipv4.conf.eth1.log_martians=1
# カーネルログを見る
journalctl -k -f
ログを有効にするのが難しい環境なら、カーネルの統計カウンターを見る方法もあります。rp_filter で捨てたパケットの数は、nstat で確認できます。
# rp_filter で捨てたパケット数(-a で累計、-z で0の項目も表示)
nstat -az TcpExtIPReversePathFilter
接続を試すたびにこの数が増えていれば、rp_filter で捨てられていると判断できます。ログと違って設定を変えずに見られるので、本番のサーバーでも確認しやすい方法です。
手順4:tcpdump で「届いているか」と「どこから返しているか」を見る¶
最後に、パケットを直接見て確定させます。2つのNICを別々のターミナルで見ると分かりやすいです。
# ターミナル1:要求が eth1 に届いているか
sudo tcpdump -ni eth1 host 10.1.1.5
# ターミナル2:返事が eth0 から出ていないか
sudo tcpdump -ni eth0 host 10.1.1.5
結果の読み方を表にまとめます。
eth1 の様子 |
eth0 の様子 |
考えられる原因 |
|---|---|---|
| 要求が見えない | 何も見えない | サーバーより手前で止まっている。ネットワーク機器側を確認 |
| 要求が見える | 何も見えない | rp_filter で捨てられている可能性が高い |
| 要求が見える | 返事が見える | 非対称ルーティング。返事が途中の機器で捨てられている |
| 要求も返事も見える | 何も見えない | サーバー側は正常。戻りの経路の先を確認 |
tcpdump はカーネルが rp_filter で判断する前の段階でパケットを見ているので、捨てられるパケットも画面には表示されます。「届いているのに返事がない」という見え方になるのはこのためです。
層ごとの切り分けの進め方は、ping・ss・tcpdumpで疎通トラブルを層ごとに切り分けるでも解説しています。
非対称ルーティングの直し方¶
原因が分かったら、直し方を選びます。大きく分けて3つの方法があります。
| 方法 | 内容 | 向いている場面 |
|---|---|---|
| 静的ルートを足す | 10.0.0.0/8 宛ては eth1 側のゲートウェイを使う、と経路を追加する |
管理用のネットワークがはっきり決まっている |
| ポリシールーティング | 送信元IPアドレスごとに、使うルーティングテーブルを分ける | どこから来るか決まっていない。来たNICから必ず返したい |
| rp_filter を緩める | 2(緩和モード)にする |
サーバーでの破棄だけが原因で、途中の機器は通る構成 |
3つ目の方法だけでは、戻りの通信は eth0 から出たままです。途中にファイアウォールがある構成では、今度はそちらで捨てられます。根本的には、1つ目か2つ目で戻りの経路をそろえるのが基本です。
静的ルートで直す場合¶
管理用の端末が 10.0.0.0/8 の中にしかいない、と決まっているなら、静的ルートを1本足すのがいちばん簡単です。
# 10.0.0.0/8 宛ては eth1 側のゲートウェイを使う
sudo ip route add 10.0.0.0/8 via 192.168.20.1 dev eth1
ルーティングテーブルは、宛先がより細かく一致する経路を優先します。10.0.0.0/8 はデフォルトルート(0.0.0.0/0)より細かいので、10.1.1.5 宛ての返事は eth1 から出るようになります。
ただし、この方法には注意点があります。10.0.0.0/8 から eth0 側のサービス用IPアドレスにアクセスしたときも、返事は eth1 から出ていきます。今度はサービス用の通信が非対称になるので、両方のNICに同じネットワークから通信が来る構成では、次のポリシールーティングを選びます。
デフォルトゲートウェイを2つ書いてしまう失敗¶
ここでよくある失敗にも触れておきます。eth1 側にもデフォルトゲートウェイを設定して、両方のNICにデフォルトルートを持たせてしまうパターンです。
この場合、main テーブルにはデフォルトルートが2本入り、メトリックの小さいほうだけが使われます。結局、返事はどちらか一方のNICからしか出ていかないので、症状は直りません。むしろ、NICの起動順などでどちらが使われるかが変わり、日によってつながる側が入れ替わるという分かりにくい状態になることがあります。
デフォルトゲートウェイは main テーブルに1本だけにして、もう片方は専用のテーブルに入れる。これが次に紹介するポリシールーティングの考え方です。
ポリシールーティングで「来たNICから返す」¶
ポリシールーティングは、宛先だけでなく送信元などの条件で、使うルーティングテーブルを切り替える仕組みです。
Linuxには最初から複数のルーティングテーブルがあり、ip rule でどのテーブルを引くかを決めています。初期状態を見てみます。
ip rule show
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
普段 ip route で見ているのは、main というテーブルです。ここに、eth1 専用のテーブルを足して、送信元が 192.168.20.10 のときだけそちらを引くようにします。
# テーブル100に、eth1 側の経路とデフォルトルートを入れる
sudo ip route add 192.168.20.0/24 dev eth1 src 192.168.20.10 table 100
sudo ip route add default via 192.168.20.1 dev eth1 table 100
# 送信元が 192.168.20.10 のパケットはテーブル100を引く
sudo ip rule add from 192.168.20.10 table 100 priority 1000
設定したら、手順2の ip route get をもう一度実行します。出口が dev eth1 に変わっていれば成功です。
ip route get 10.1.1.5 from 192.168.20.10
送信元が eth1 のIPアドレスなら、出口も eth1 になります。これで行きと戻りの道がそろいます。
設定を再起動後も残すには¶
ip コマンドで入れた設定は、再起動すると消えます。永続化の方法は、ネットワークを管理しているツールによって違います。
NetworkManager を使っている環境なら、たとえば次のように接続設定に書き込めます。接続名は環境に合わせて置き換えてください。
sudo nmcli connection modify eth1 \
ipv4.routes "0.0.0.0/0 192.168.20.1 table=100" \
ipv4.routing-rules "priority 1000 from 192.168.20.10 table 100"
sudo nmcli connection up eth1
netplan や systemd-networkd を使っている環境では、それぞれの設定ファイルにルートとルールを書きます。どのツールで管理しているかを先に確認してから設定しましょう。
rp_filter の値を変える場合も、sysctl -w だけでは再起動で戻ります。/etc/sysctl.d/ 配下に設定ファイルを置いて永続化します。
まとめ¶
2枚のNICを持つLinuxサーバーで、片方のIPアドレスにだけつながらないときの原因と切り分けを見てきました。
要点を振り返ります。
- Linuxは返事の出口を、要求が来たNICではなく宛先で決める
- そのため、行きと戻りで違うNICを通る非対称ルーティングが起きやすい
rp_filterが1だと、要求はカーネルで捨てられ、返事は出ないrp_filterはallとNICごとの値のうち、大きいほうが使われるip route getとtcpdumpで、どこで止まっているかを確かめる- 直すときは、静的ルートかポリシールーティングで戻りの経路をそろえる
NICが1枚のうちは意識しなくてよかった戻りの経路が、2枚になると急に問題として出てきます。ルーティングの基本を押さえておくと、こうした症状にも落ち着いて向き合えます。
ネットワークの仕組みを体系的に学び直したい方は、InfraAcademyのネットワークのロードマップも活用してみてください。経路がどう決まるかを、手を動かしながら確かめられます。
参考¶
- IP Sysctl — The Linux Kernel documentation
- RFC 3704: Ingress Filtering for Multihomed Networks
- When RHEL has multiple IPs configured, only one is reachable from a remote network(Red Hat Customer Portal)
- Reverse Path Forwarding(Red Hat Enterprise Linux 6 Security Guide)
- ip-rule(8) — Linux manual page



