はじめに
こんにちは、今回は送信パケットと受信パケットでゲートウェイを分ける方法を解説します。具体的には、パケットマーキングとポリシーベースルーティング(PBR)を使って、条件を満たすACKパケットだけが別のルーティングテーブルに従うようなルーティングを実装します。
想定環境
PCとルーターがローカルネットワーク(192.168.0.0/24
)で繋がっています。ルーターはローカルネットワークの他に、二つの外部ネットワーク(X.X.X.0/24
、Y.Y.Y.0/24
)に属しています。各ネットワークのゲートウェイは192.168.0.1
、X.X.X.1
、Y.Y.Y.1
です。
今回解説するのは、ローカルネットワークに属するPCが外部から接続を待ち受けている状況において、PCから確立するセッションと外部から確立するセッションでゲートウェイを分ける方法です。
ルーティングテーブル
デフォルトゲートウェイをX.X.X.1
、外部ネットワークに接続しているNIC名をethX
・ethY
、外部ネットワークにおけるDHCPで割り当てられたIPアドレスをX.X.X.2
・Y.Y.Y.2
とすると、ルーターのルーティングテーブルは下記のようになります。
$ ip route show
defult via X.X.X.1 dev ethX #経路1
X.X.X.0/24 dev ethX proto kernel scope link src X.X.X.2 #経路2
Y.Y.Y.0/24 dev ethY proto kernel scope link src Y.Y.Y.2 #経路3
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.1 #経路4
このルーティングテーブルは、4つの経路を定義しています。
- 経路1:全てのパケットは
ethX
を経由してX.X.X.1
へ送信する - 経路2:
X.X.X.0/24
(に含まれるIPアドレス)宛のパケットはethX
を経由して送信する - 経路3:
Y.Y.Y.0/24
(に含まれるIPアドレス)宛のパケットはethY
を経由して送信する - 経路4:
192.168.0.0/24
(に含まれるIPアドレス)宛のパケットはeth0
を経由して送信する
経路1はデフォルトゲートウェイの定義、経路2・経路3・経路4は隣接ネットワーク(ルーターが属しているネットワーク)の定義です。宛先IPアドレスの条件がより厳しい経路が優先的に選択されるため、適用順序は「経路2→経路3→経路4→経路1」となります。つまり、このルーティングテーブルが意味するところは、
- 隣接ネットワークに含まれるIPアドレス宛のパケットは、そのIPアドレスへ直接送信する
- それ以外のパケットは、
X.X.X.1
へ送信する
これだけです。
問題点
前節で示したルーティングテーブルの問題点は、PCがY.Y.Y.0/24
以外のIPアドレスからSYNパケットを受信した時に、セッションの確立に失敗することです。
例として、外部のホスト(Z.Z.Z.2
)がSYNパケットを送信した場合を考えます。
SYNパケットを受信したルーターは、ポートフォワードにより宛先IPアドレスをPCのプライベートアドレスへ変換してPCへ送信します。SYNパケットを受信したPCは、Z.Z.Z.2
宛のACKパケットを作成し、デフォルトゲートウェイであるルーターへ送信します。ACKパケットを受信したルーターは、宛先Z.Z.Z.2
を見て経路1を選択しX.X.X.1
へ送信します。その結果、外部ホストはY.Y.Y.1
へ送ったSYNパケットのACKをX.X.X.2
から受信したことで、不整合をおこします。
解決策
PCがZ.Z.Z.2
へ送信したACKパケットがY.Y.Y.2
から送信されるように、ルーティングテーブルの追加・パケットマーキング・ポリシーベースルーティング(PBR)を実施します。
具体的には、下記の通りです。
-
Y.Y.Y.1
をデフォルトゲートウェイにもつ専用のルーティングテーブルを追加する -
Y.Y.Y.2
へ届いたSYNパケットに関連するパケットをマークするルールを書く - マークされたパケットが追加したテーブルを選択するようにポリシーを書く
手順1:ルーティングテーブルの追加
Y.Y.Y.1
をデフォルトゲートウェイにもつ専用のルーティングテーブルを作成します。
/etc/iproute2/rt_tables
を編集し、ルーティングテーブルを追加します。
#
# reserved values
#
255 local
254 main
253 default
100 tableY #追加
#
# local
#
Linuxではルーティングテーブルを複数定義することができます。デフォルトでは3つのテーブル(local
, main
, defult
)が定義されており、それぞれ下記の役割を担っています。
-
local
:自分自身のローカルアドレスや直接接続されたアドレスに関する経路が含まれるテーブルで、ユーザーが直接操作することはない -
main
:通常のルーティングエントリーが含まれる標準的なテーブルで、ipコマンドのtableオプションを省略した場合に選択される -
defult
:デフォルトのルート情報が含まれるテーブルで、全ての通信が行き先を明確にできない場合に使用される
パケットがどのルーティングテーブルに従うかは、ルーティングポリシーにより決定されます。
追加したルーティングテーブルに経路を設定します。経路5はPCが送信したACKパケットを外部ホスト(Z.Z.Z.2
)へ送信するための経路で、経路6はY.Y.Y.2
へ届きポートフォワードされたSYNパケットをPCへ送信するための経路です。
$ ip route show table tableY
defult via Y.Y.Y.1 dev ethY #経路5
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.1 #経路6
このテーブルを選択するためのポリシーは後で書きます。
手順2:パケットマーキング
ACKパケットを識別できるように、iptables
を用いてマーキングします。ただし、PCはローカルネットワーク(192.168.0.0/24
)にある機器からもSYNパケットを受信する可能性があるため、全てのACKパケットをマーキングする訳にはいきません。
そこで、Y.Y.Y.2
へ届いたSYNパケットをマーキングした後、conntrack
で関連パケットを追跡します。conntrack
モジュールを用いると、既にマーキングされたSYNパケットの関連パケットに対して、SYNと同じマークを付けることができます。
iptables -t mangle -A PREROUTING -i ethY -p tcp --syn -d Y.Y.Y.2 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j MARK --set-mark 1
これにより、Y.Y.Y.2
へ届いたSYNパケットに紐づくパケットだけがマーキングされた状態になります。
iptables
により付与されたマークは、iptables
を実行しているマシンの内部で管理されているため、他マシンから参照することはできません。
手順3:ポリシーベースルーティング(PBR)
マークされたパケットが専用のルーティングテーブルを参照するように、ルーティングポリシーを追加します。プライオリティが低いポリシーが優先して適用されるため、main
テーブルよりも低い値を設定します。
$ ip rule add fwmark 1 lookup tableY priority 32765
$ ip rule show
0: from all lookup local
32765: from all fwmark 1 lookup tableY
32766: from all lookup main
32767: from all lookup default
まとめ
以上で、PCから確立するセッションと外部から確立するセッションでゲートウェイを分けることができました。ハニーポットの構築やマルウェア解析など、変則的なパケット制御が求められる場面でお役に立てれば幸いです。