背景と導入の理由
標準的なLinuxのルーティングは、宛先IPアドレスのみを参照して処理を行います。基本的な環境であれば、カーネルはパケットを検査し、mainルーティングテーブルを確認したうえで、プライマリのデフォルトゲートウェイからトラフィックを送出します。この仕組みは、シンプルなホームルーターや単一NICのクラウドインスタンスであれば問題なく機能しますが、ネットワーク構成が複雑になると破綻してしまいます。
たとえば、異なるISPから提供された2本の1Gbps光回線に接続されたマルチホームサーバーや、SSH・監視用には1GbE NICを使い、レプリケーションのトラフィックには10GbEインターフェースを割り当てているデータベースサーバーを想定してみてください。クライアントがセカンダリインターフェース(eth1)に接続した場合、Linuxは応答パケットをプライマリインターフェース(eth0)から送出しようとすることがよくあります。このような非対称ルーティングが発生すると通信が成立せず、上位のファイアウォールやISPのIPスプーフィング防止フィルターによってパケットが無言で破棄されてしまいます。
ポリシーベースルーティング(PBR: Policy-Based Routing)は、この問題を根本から解決します。カーネルは宛先だけでなく、送信元IP、プロトコル、トランスポート層ポート、さらにはiptablesやnftablesで付与されたパケットマーク(fwmark)に基づいてルーティングを決定できるようになります。これにより、送信経路をピンポイントで制御でき、追加の外部ハードウェアを導入することなく、遅延やジッターにシビアなAPI呼び出しと500MB超の重いデータベースバックアップの経路をきれいに分離することが可能です。
インストール
最近のLinuxディストリビューションには、標準でiproute2スイートが同梱されています。中心となるipコマンドは、routeやifconfigといった旧世代のツールを完全に置き換えるものであり、カーネルのルーティングポリシーデータベース(RPDB)への直接アクセスを提供します。
システムにiproute2およびファイアウォール関連ツールがインストールされているか確認してください。
# Debian、Ubuntu、およびその派生ディストリビューション
sudo apt update
sudo apt install -y iproute2 iptables
# RHEL、Rocky Linux、Fedora
sudo dnf install -y iproute iptables-services
以下のコマンド1行で、既存のポリシーデータベースを確認できます。
ip rule show
優先度(priority)0、32766、32767の3つのデフォルトルールが表示されます。これらのルールはそれぞれlocal、main、defaultのルーティングテーブルを参照しています。
設定手順
PBRを適切に運用するには、4つのステップを踏みます。カスタムテーブルの命名、ルーティングエントリの追加、ポリシーのルール作成、そしてカーネルのスプーフィングフィルターの調整です。
1. カスタムルーティングテーブル名の定義
Linuxカーネルは最大232 – 1個のルーティングテーブルをサポートしていますが、管理者がカスタム経路用に使用するのは通常1から252までのIDです。スクリプト内で生の数値を指定しているとすぐに可読性が低下するため、代わりに/etc/iproute2/rt_tablesでわかりやすいエイリアスを定義します。
一意のIDとともにカスタムテーブル名を追記します。
# テーブルID 100と200にわかりやすい名前を割り当て
sudo bash -c 'echo "100 isp2_traffic" >> /etc/iproute2/rt_tables'
sudo bash -c 'echo "200 vpn_traffic" >> /etc/iproute2/rt_tables'
これで、ip routeやip ruleの引数にisp2_trafficやvpn_trafficを直接指定できるようになります。
2. 送信元IPアドレスによるルーティング
デュアルホーム構成のホストを具体例として見てみましょう。インターフェースeth0はIP192.168.1.50/24(ゲートウェイ192.168.1.1)を使用し、インターフェースeth1はIP10.0.0.50/24(ゲートウェイ10.0.0.1)を使用しています。10.0.0.50に届いた接続への応答は、必ず10.0.0.1を経由して返送しなければなりません。
セカンダリインターフェース用のデフォルトゲートウェイをカスタムテーブルに追加します。
sudo ip route add default via 10.0.0.1 dev eth1 table isp2_traffic
次に、カーネルがそのテーブルを使用する条件をルールとして定義します。
sudo ip rule add from 10.0.0.50/32 table isp2_traffic priority 1000
優先度は必ず明示的に設定してください。数値が小さいほど優先されるため、優先度1000は32766のデフォルトルールよりも大幅に前に評価されます。
3. サービスポートとfwmarkによるルーティング
最近のip ruleコマンドはsportやdport構文を使用してL4ポートを直接マッチングできます。しかし、より複雑な条件分岐が必要な場合は、ファイアウォールマーク(fwmark)とiptablesまたはnftablesを組み合わせる手法が依然としてデファクトスタンダードです。
ここでは、パブリックIPを隠蔽するため、TCPポート443宛てのアウトバウンドHTTPSトラフィックをWireGuardトンネル(tun0、ゲートウェイ10.10.10.1)経由でルーティングしたいとします。
まずvpn_trafficテーブルにゲートウェイを追加します。
sudo ip route add default via 10.10.10.1 dev tun0 table vpn_traffic
mangleテーブル内でアウトバウンドHTTPSパケットにマークを付与します。
sudo iptables -t mangle -A OUTPUT -p tcp --dport 443 -j MARK --set-mark 0x1
マークされたパケットを専用テーブルでルックアップするようカーネルに指示します。
sudo ip rule add fwmark 0x1 table vpn_traffic priority 1100
ローカルサービスが外部のHTTPSエンドポイントにリクエストを送ると、mangleテーブルがパケットにマーク0x1を付与します。RPDBがそのマークを検知するとmainテーブルをバイパスし、ペイロードをtun0経由でルーティングします。
4. リバースパスフィルタリング(rp_filter)の調整
非対称ルーティングのトラブルは、まさにこの設定で起きがちです。Linuxのデフォルト設定では、リバースパスフィルタリングがStrictモード(rp_filter = 1)で動作しています。パケットがeth1から入ってきたにもかかわらず、カーネルのデフォルトルートがeth0を指している場合、ネットワークスタックは受信パケットをスプーフィングされた不正パケットと判断し、無言でドロップしてしまいます。
/etc/sysctl.d/99-pbr.confを作成して、対象インターフェースをLooseモード(rp_filter = 2)に切り替えます。
sudo tee /etc/sysctl.d/99-pbr.conf <<EOF
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.eth0.rp_filter = 2
net.ipv4.conf.eth1.rp_filter = 2
EOF
sudo sysctl --system
Looseモードでも無効な送信元アドレスからの保護は維持されますが、マシンのいずれかのインターフェースを通じて到達可能な送信元アドレスであれば、どのインターフェースからのパケット着信も許可されるようになります。
5. 再起動後も設定を永続化する
CLIから直接追加したルールはカーネルメモリ上にのみ保持されるため、再起動すると消去されます。永続化するには、お使いのネットワークマネージャーの設定ファイルに記述する必要があります。
Netplan(Ubuntu 20.04/22.04/24.04): /etc/netplan/01-netcfg.yamlなどのYAML設定ファイルに直接ルールを記述します。
network:
version: 2
ethernets:
eth1:
addresses:
- 10.0.0.50/24
routing-policy:
- from: 10.0.0.50/32
table: 100
priority: 1000
routes:
- to: default
via: 10.0.0.1
table: 100
変更を反映するにはsudo netplan applyを実行します。
NetworkManager(RHEL、Rocky Linux、AlmaLinux): nmcliを使用してセカンダリ接続を設定します。
nmcli connection modify eth1 +ipv4.routing-rules "priority 1000 from 10.0.0.50/32 table 100"
nmcli connection modify eth1 +ipv4.routes "0.0.0.0/0 10.0.0.1 100"
nmcli connection up eth1
検証とモニタリング
コマンドが終了コード0を返したからといって、正しくルーティングされていると思い込んではいけません。想定した経路が使われているか明示的にテストしましょう。
1. 有効なルールとテーブルの確認
現在有効なルーティングルールと割り当てられた優先度の一覧を確認します。
ip rule show
セカンダリテーブル内のルートが想定どおりか確認します。
ip route show table isp2_traffic
2. ルート検索のシミュレーション
ip route getコマンドを使用すると、実際のトラフィックを送信することなくカーネルのFIB(Forwarding Information Base)を照会できます。
# 送信元IPルーティングの検証
ip route get 8.8.8.8 from 10.0.0.50
# マーク0x1のポリシールーティングの検証
ip route get 1.1.1.1 mark 0x1
カーネルは、選択されたテーブルと送出インターフェースを正確に返します。
8.8.8.8 from 10.0.0.50 via 10.0.0.1 dev eth1 table isp2_traffic uid 0
cache
3. パケットカウンターの監視
バイト数とパケットカウンターを確認して、トラフィックが実際にファイアウォールルールにヒットしているか確認します。
sudo iptables -t mangle -L OUTPUT -v -n
一致する接続が開始されるたびに、パケットカウンターが増加していくはずです。セカンダリインターフェースを通過する生のフレームをリアルタイムでキャプチャするには、tcpdumpを実行します。
sudo tcpdump -nn -i eth1 host 10.0.0.50
カスタムテーブル、明示的なルール優先度、そして適切なリバースパスフィルタリング(Looseモード)を設定することで、複数NICを備えたLinuxホストでも複雑なデュアルホームトラフィックを確実かつクリーンに処理できるようになります。

