OpenWrtでポリシールーティングを入れる
ポリシールーティングを入れると何が嬉しいのか。
OpenWrtの nftables (nft) では inet fw4
のテーブルに全部入っている。
しかしLuciのZONE転送の許可拒否とTraffic Acceptをうまく両立するのが大変だった。
Zone 転送とNAT許可設定でforwardがゴチャゴチャするからシンプルにしたい。
OpenWrtのZONE転送設定はdefault が reject である。
nft で別テーブルを作ってacceptを追加しても、デフォルトのテーブル inet fw4 でZONE転送にマッチしてreject される。コマンドでinet fw4 の forward チェインに許可する設定を追加して forward を acceptする。
しかし、nft table inet fw4 テーブルのforward チェイン記述が大量になる。追いかけるのが面倒になり諦めた。
特に、forwardチェインにIPアドレスを直接記述ルールを大量に書いて全体を見失い、パケット疎通ミスを引き起こしまくった。
そこで、forwardチェインをシンプル制御をうまくやる。このためにマークを使ったポリシールーティングを活用する。
全体の方針
- nftables で パケットにマークを付ける
- openwrt のfw4 にマーク済パケットのFowardをAcceptする設定をいれる。
- ip rule でマーク済パケット用のルーティングテーブルを作る。
パケットをマークしなくても目的は達成できるものの、上記で書いた問題(fowardがあふれかえる)ということがあるので、実験用のネットワークはnftablesの別テーブルに切り出しを実現する。別テーブルでマークする設定を書いておけば、グチャグチャにしても影響範囲は抑えられる。
今回作るネットワーク
ルータ間の転送としてはありふれている
マークしたパケットを別のGWを流したい。
ただ、マークしたパケットを、別の経路で流そうということである。
nftables の処理。
このとき、FowardでAcceptするのはマーク済みのパケットだけとする。
nftables では、prerouting で条件マッチしたパケットにマークを付ける。fowardでマーク済パケットの転送をAcceptする。
ルーティングテーブルやFOWARDチェインに細かい条件を書いていくと数が増えてしまい煩雑になる。
その防止のため、prerouting で仕訳処理を先に済ませ、マークする。
nft のテーブルを分けて管理する。
nftables にはチェインを別テーブルに分割できるので、prerouting を別テーブルに分けてしまう。
パケットをマークする条件を別テーブルにして条件に特化したテーブルを用意して、そこだけ見て管理するようにする。
実際の処理
実際に作った作業は次のとおりになる
OpenWrt でforwardへマーク済みAcceptを入れる。
acceptするルールは、シンプルにする。MARK されたパケットを許可する。
MARK=666
FW4='inet fw4'
HANDLE_REJECT=$(nft -a list chain $FW4 forward | grep handle_reject | grep -oP '(?<=handle )+\d+' )
COMMENT=accept_sample
nft insert rule "$FW4" forward position $HANDLE_REJECT \
mark $MARK counter accept comment "\"takuya: ${COMMENT} \""
結果を確認する。
> nft list chain inet fw4 forward
table inet fw4 {
chain forward {
type filter hook forward priority filter; policy drop;
## Luci で入れたルール
rule ...
# 末尾に追加された
meta mark 0x0000029a counter packets 250 bytes 21272 accept comment "takuya: accept_v6_sample "
jump handle_reject
}
}
末尾(handle_rejectの直前)に、ルールが追加された。
OpenWRT が作るfw4 のforwardテーブルは、default が DROP になっていて、且つ、未マッチはREJECTに送られる。そのためforward中にACCEPTを差込む必要があった。
パケットをマークするnftテーブルを作る
マーキングする条件を書くため専用テーブルを作る。
先ほどマークしたパケットを「Accept」するように書いた。ここでは、指定条件パケットをマーク済にするための新しく別テーブルを作り、その中にチェインを作ったうえで、ルールをいれる。
次の名前で、テーブルを作ってprerouting のチェインを作る。
TABLE='ip6 marking_sample'
nft create table $TABLE
nft create chain "$TBL" prerouting {\
type filter hook prerouting priority filter \; policy accept \; \
}
prerouting で マーク条件を作る。
TABLE='ip6 marking_sample'
IIF=br-lan
MARK=666
DNET=2404:6800:400a:80b::/64
PC=fd01:b::3/64
nft insert rule $TABLE prerouting \
iifname $IIF ip6 daddr $DNET \
ip6 saddr $PC \
counter meta mark set $MARK
今回書いたサンプル条件は、google.com AAAA 2404:6800:400a:80b::200e
(2024-08-16現在)のパケットはマークとした。
この記述で、マッチしたパケットはマーキングされ、inet fw4 forwardを通過するときに、マーク済なのでforward ACCEPTされる。
masquerade の追加
また、マークされたパケットが出ていくときにmasquerade しておく
TABLE='ip6 marking_sample'
OIF=wg0
MARK=666
DNET=2404:6800:400a:80b::/64
nft create chain $TABLE postrouting { \
type nat hook postrouting priority filter\; policy accept \; \
}
nft add rule $TABLE postrouting \
oifname $OIF ip6 daddr $DNET counter masquerade
OpenWrtの場合は、ZONE設定でチェックをいれるとMASQURADEは記入しなくても大丈夫ですが、念の為。
ip rule を作る(ポリシールーティング)
マーク済パケット専用のルーティングが必要。
ここで専用のルーティングテーブルを作る。
MARK=666
GW2=fd00:aaa:afac:1919::1
ip -6 rule del table $MARK >& /dev/null
ip -6 rule add fwmark $MARK table $MARK
ip -6 route add $DNET dev $OIF via $GW2 table $MARK
ルーティングをテストする
MARK=666
GG=2404:6800:400a:80b::200e
ip -6 route get fibmatch $GG mark $MARK
上記のfibmatch
を使うと、マッチしたrouting ルールをそのまま表示してくれて便利。add したルールがそのまま表示されればチェック・オッケ。
削除するときは、次のコマンドで
MARK=666
ip -6 rule del table $MARK >& /dev/null
ip -6 route flush table $MARK
ミスったらflushしたりdeleteすれば良い。
今回は、google.com AAAA
のv6アドレスからサブネット指定した。この宛先アドレスをDNET='2000::/3'
にすることで、v6のグローバルIP空間をすべてをルーティング対象にできるはず。2000::/3
は::/0
やdefault
よりもわかりやすいかと思う。デフォルト使うとリンクローカルとかも混じって面倒かも。
mark をacceptするメリット。
フォワード・チェインが溢れずに済んだ。
nftablesは、jump で書いてもいい。しかし、jump jump であふれかえるとjumpのjumpで追跡が煩雑だった。そしてフォワードでジャンプすると、管理が OpenWrt のLuci生成のfw4 と混在してしまう。Openwrt のデフォルトであるfw4はluci の画面設定に依存する。fw4にコマンドで追記していると、どこで追記されたのか、ジャンプ追跡が煩雑すぎた。luci由来のfw4をあまりゴチャゴチャさせたくない。
また、ルートを変えたいときに、専用のテーブルを触るだけで済むし、一時的に止めたければ、マーク付与テーブルをdelete すれば済むのが嬉しい。
更にnft はhandleを使うのが面倒。jump でルールを書いたり、delete add したり、add flag dormant とかも、ちょっとした作業に毎回変わるHANDLEを探すのが手間で仕方ない。今回用にマークをつけるテーブル消す・テーブル作るで代用すると遊びやすくなった。
そういうことで、nft のHANDLEを使わずに、マークをつかうことにした。見るべき箇所がOpenWrtから切り離した別テーブル(マークを付けるテーブル)に限定できて管理がだいぶ楽になりました。
v6 NAT
今回の例は、v6 で NAT66 する例になるのだけど、v6 でNATをする意味ないって思うかもしれないけど、v6 NATも使えると割と便利なんですよね。
「IPv6ではNATをしない(不要)」などと教科書には書いてるけど、NATあったほうが絶対便利。好きなIPoEの接続点を出口に据えて、任意ISPから出て回線テストもしやすい。やっぱりv6 NATはアリじゃないかな。
セキュリティについてもNATは防波堤になり得る気がする。グローバルIPv6で通信するv6は、OSのファイアウォール機能に依存するわけで。OSに脆弱性、つまりLinuxのnftablesやWindowsファイアウォールに脆弱性があった場合は、大事故になると思うんですね。またユーザーがコピペで安易な許可設定をしてしまう可能性も怖い。
NAT66するべきだと思う理由にゼロデイ攻撃がある。
グローバルIPが割り当てられていると、OSの脆弱性の影響をもろに受ける。
OSのFWに脆弱性があると、グローバルIPが割あたっているすべての端末に影響が出る。
そう思っていたのだが、出てしまった。出てしまったのです。脆弱性が
CVE-2024-38063
でWindowsのTCP/IPのスタック脆弱性でリモートコードの実行されるゼロデイ発覚していた。
パッチでるまでv6 オフにするレベル。ってやつです。
緊急パッチはでたものの・・・やっぱり、NAT66でNATもありなかって考えました。端末のIPを管理するにしてもULAのほうが何かと楽かも・・・
TCPなのでFWでCONNECTEDを見てれば大丈夫なはずではある。v6関連のチェックが甘くてstate new が通っちゃったりしてないですよね。
今回はv6 NAT とポリシールーティングで指定PCのGWを変えた。
今回は、OpenWrtのfw4 やLuciを使わずに、指定PCや指定サブネットだけは、v6 空間に出られるようにする設定を試してみた。
wireguardやStrongswanのようなVPNと組み合わせて、好きな箇所からv6アドレス出ていけるし設定をシンプルにする目処がたった
Linux箱をルータにしておくとちょっとした思いつきが試せて良いね。