nftablesへの移行
openwrt をアップグレードしたら、fw3 が fw4 にアップグレードされて、nftablesがデフォルトになった。
そして、nftablesを使わないルールがあると、注意・警告が出るようになった。とても煩わしいし、ルールの確認のために見る箇所が増えて不便になった。残念だが、iptables利用をやめてnftablesに書き直す必要が出てしまった。
nftablesについて
nftables は テーブルー>チェーンー>ルールの順に管理されている。
テーブルを作ってその中にチェーンを作成、チェーンの中にルールを作成
チェーンは、Hookポイント(prerouting , forward , postrouting , input , output ) に登録して使う。登録しないときは、単なる関数のように振る舞うチェーン(Jump用)となる。テーブル内のチェーンは優先度に従って適用される。Dropされると終了。
iptables-translate で nftablesに変換できる。
iptables, ip6tables,ebtablesの3つの類似機能がnftablesに統合できるようになった。
追加と削除
テーブル追加・削除・無効
nft add table <?family> <name>
例
nft add table ip my_ssh_mangle nft delete table ip my_ssh_mangle
add / create
nft add table ip my_ssh_mangle nft add table ip my_ssh_mangle # エラーにならない nft create table ip my_ssh_mangle # エラー
既存のテーブルのチェックしてから消す
nft delete table ip myA # 未存在でエラーに ## チェックしてから消す。 nft list tables | grep -wq "table ip myA " && nft delete table ip myA
テーブルを消すと、内包されるチェインも消える。チェインに内包されるルールも消える。
チェイン追加・削除(ベースチェインあり)
nft add chain ip sslh_mangle prerouting { type filter hook prerouting priority mangle \;} nft delete chain ip sslh_mangle prerouting
hook はチェインが実行されるタイミング。type は何をするか。
ベースチェインに書いてるものは類似キーワードが多く初見殺しなので、以下をよく覚えておく。
type <type_name> hook <hook_name> priority <num|name>
type_name や hook_nameは固定(netfilter由来)である、これについては後述の図を参考にする。
チェイン追加・削除(ベースチェインなし、単なる関数)
nft add chain ip my_sslh_mangle my_chain
ルール追加・削除
nft add rule <?family> <chain> conditions statements;
例
nft add rule ip sslh_mangle output oifname eth0 tcp sport 443 meta mark set 1 counter accept comment '"takuya:fw1234"'
counter / accept/commet が 処理(statements)。その前につくのがマッチ条件
削除は、handle経由でやる
# handle IDを調べて消す nft -a list ruleset | grep 'takuya:fw1234' nft delete rule ip my_ssh_table my_chain handle 9999 # サクッとforループで for i in $( nft -a list ruleset | grep takuya | grep -oE '[0-9]+$'); do nft delete rule inet my_table my_forward handle $i done
コメントを付けておくと、一つずつ消しやすい。ただ、テーブル全消しのほうが速いし楽。テーブル毎に分かれるのがnftablesなので。
コメントの付け方
コメント文字列は、bashスクリプトで解釈されないように適宜エスケープする必要がある。また空白があるコメントはクォートで囲む必要がある。
nft add rule ip tableX chainY oif eth0 accept comment mycomment nft add rule ip tableX chainY oif eth0 accept comment '"my comment"' nft add rule ip tableX chainY oif eth0 accept comment '"my: comment"'
これは、bashでクォートが処理後に。nftに引数を1つで渡すためでもある。融通が利かない。
comment my_comment comment 'my_comment' comment "'my comment'" comment "my comment" # comment my commentに解釈されエラーに
一覧
うえから、全部表示・テーブル一覧・テーブル指定
nft list ruleset nft list tables nft list table mytable
チェインの中身を見る
nft list table mytable nft list chain mytable mychain
ハンドルを付与してルールを確認する。nft -> nft -a
にする
nft -a list ruleset nft -a list chain mytable mychain
テーブルの休止
テーブルの休止・休眠状態を作る
使い所がないが、休止機能がある。
nft add table inet mytable { flags dormant \; }
dormant
フラグを設定したら、休止状態になる。
flagを解除する方法が特に無いみたい。nft delete flag inet mytable dormant
が使えればいいが、公式に見あたら無い。
フラグ解除ないので、今のところ使い所が無い。
次の用途だと活用できる。
- シンタックスチェック的にテーブルへ突っ込む
- 試行錯誤の途中で、あと保存することを前提に、いったん無効にする。
休止を解除する方法がサポートされてないので、用途が限られる。シンタックステスト目的以外で使わないほうがベター。個人的な見解でいえば、tcp flagと紛らわしいので、将来的に別の名前になるんじゃね。(ていうか別名にしてほしい)
dormantはITではあまり見ない単語。英語で、休眠・休止の意味。dormire 、睡眠薬のドリエルや、寮(ドーム・ドミトリ)や何かと同じ語幹で睡眠を表す。ドリーム(dream)も仲間か思いきやdreamは違うらしい。
nat するサンプル
単純にMaqueradeするサンプル
nft add table $TABLE nft add chain $TABLE prerouting { type nat hook prerouting priority 0 \; } nft add chain $TABLE postrouting { type nat hook postrouting priority srcnat \; } nft add rule $TABLE postrouting oifname $VPNIF masquerade
注意点。preroutingとpostroutingは、ペアで記入する必要がある。(最初から入ってたりするので気づかない事がある。)
ただし、このマスカレードは、FORWARDがAcceptされていることが条件。
ディストリビューションのデフォルトでDropされているなら、Acceptを入れてあげる。たとえば、OpenWrtではデフォルトがDropなので、FORWARDできない。転送許可してあげる。
デフォルトDROP(OpenWrtのfw4)
# nft list chain inet fw4 forward table inet fw4 { chain forward { type filter hook forward priority filter; policy drop;
OpenWrtでの許可例
nft insert rule inet fw4 forward oifname $VPNIF accept nft insert rule inet fw4 forward iifname $VPNIF accept
map/set を使う
iptablesみたいに、何行にも同じことを数字を変えて書く必要がない。
単純なSet
## 例1 meta nfproto ipv4 iifname { "wg0", "wg1", "wg3","tun1" } ## 例2 ip saddr { 172.16.3.0-172.16.4.255, 192.168.12.0/24 } ## 例3 ip daddr { 192.168.12.5, 192.168.12.21 }
巨大なセット
セットに名前をつけて参照できる。
set public_dns { type ipv4_addr elements = { 1.0.0.1, 1.0.0.2, 1.0.0.3, 1.1.1.1, 8.8.8.8, ... }
@を使って使える。
oifname "wg1" ip daddr @public_dns drop
map
同じことを3回ペアで書くときに、キーをつかったMapを書く
これが
iptables -t nat -A PREROUTING -p tcp --dport 1000 -j DNAT --to-destination 1.1.1.1:1234 iptables -t nat -A PREROUTING -p udp --dport 2000 -j DNAT --to-destination 2.2.2.2:2345 iptables -t nat -A PREROUTING -p tcp --dport 3000 -j DNAT --to-destination 3.3.3.3:3456
こう
nft add rule nat prerouting dnat to \ tcp dport map { 1000 : 1.1.1.1, 2000 : 2.2.2.2, 3000 : 3.3.3.3} \ : tcp dport map { 1000 : 1234, 2000 : 2345, 3000 : 3456 }
nftables/netfilterの処理図
次のような処理を順番にしている。
ここから、prerouting 、forward と input がわかる。またrouting が ip route を使ってることがよく分かる。
https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg
nftables の処理の図。
nftables は次のような hook ポイントを使ってnetfilterに処理を入れているとよく分かる図。
https://people.netfilter.org/pablo/nf-hooks.png
たとえば、add rule {type nat hokk prerouting \;}
とした場合、この図のprerouting hook
にあたり、preroutingが行われていることがわかる。type nat
は前図(netfilter)に記載があり、netfilterの<nat>/prerouting
に処理を入れていると読み取れる
デフォルトの優先度(priority)
hook ごとに、priority が数字が決まっていて、それぞれのデフォルト値になっていることがよく分かる。
この図から、priority filter
は priority 0
と同等であるとわかる。名前や数字が登場してわかりにくい。紛らわしい。
表のように、名前は数字の別名であると覚えておえけば良い。あとは名前から分かる。名前が同じなら同じ優先度・近い優先度である。
https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks
実際に使ってみて気づいたのだが、次のように、1や-1を設定しても実際に1になるわけでない
type filter hook forward priority 1; type filter hook forward priority -1;
次のようになる。
type filter hook forward priority filter +1; type filter hook forward priority filter -1;
つまり、優先度は type からの固定値があり固有値 ± 設定値
であるようだ。上記の例だとfilter+1
となっていた。同タイプのチェイン間で前後を調整する目的で使うのかもしれない。
nftables の優先度の大小のついて (2023-09-05 追記)
nftables の数字の大小はそのまま優先度である。小さい方が優先
でマイナスもある
が、基本的には既定値±1で操作する。
https://stackoverflow.com/questions/38162173/how-is-the-order-of-tables-chains-in-nftables-arranged
Here higher priority value means lower priority and vice-a-versa.
数字が大きいほど優先度は低い。その逆も然り。
-1 と 0 だと、-1 が先に処理される。
nft add table ip filter2 nft add chain ip filter2 forward {type filter hook forward priority 0 \;}
nft add table inet filter nft add chain inet filter '{type filter hook forward priority -1 }'
この場合は、forward
の既定値より±1されるだけであり、静的に-1 が入るわけではない。
テーブル・ルールが見つからない・指定できない(ip / inet 省略)
テーブルが見つからないというときに一番多いのが、 ip / inet の省略の有無。
次のように、テーブルを造ったとき。そのまま消せる
nft add table my_table nft delete table my_table
IPを省略せずに作った場合
nft add table ip my_table nft delete table my_table ## O.K. 省略形 nft delete table ip my_table ## O.K.
inetを指定した場合
nft add table inet my_table nft delete table my_table # エラー 省略形 nft delete table inet my_table # O.K.
ip6を指定した場合
nft add table ip6 my_table nft delete table my_table # エラー省略形 nft delete table ip6 my_table # O.K.
このように、inet my_table
や ip6 my_table
とした場合は必ずinet/ip6を入れないとエラーになる。delete などするときに忘れがちなので注意。
openwrt のnftables
kmod-nft-socketがなかったりでsocket
が使えなくてハマった。
kmod-nft-*
で 検索して必要なモジュールを入れてあげないと動かないことがある。
openwrtでの例。
気づかずに、2時間使ってしまった。
nftablesのモジュール
コンパイル方法(公式)を参考にすると、モジュールはコンパイル・オプションで入ってるはず
% grep CONFIG_NFT_ /boot/config-4.2.0-1-amd64 CONFIG_NFT_EXTHDR=m CONFIG_NFT_META=m CONFIG_NFT_CT=m CONFIG_NFT_RBTREE=m CONFIG_NFT_HASH=m CONFIG_NFT_COUNTER=m CONFIG_NFT_LOG=m CONFIG_NFT_LIMIT=m CONFIG_NFT_MASQ=m CONFIG_NFT_REDIR=m CONFIG_NFT_NAT=m CONFIG_NFT_QUEUE=m CONFIG_NFT_REJECT=m CONFIG_NFT_REJECT_INET=m CONFIG_NFT_COMPAT=m CONFIG_NFT_CHAIN_ROUTE_IPV4=m CONFIG_NFT_REJECT_IPV4=m 略
ただ、通常は、ディストリビューション提供のパッケージを使ってるはずなので。ディストリビューションのパッケージ管理をよく見ること。
openwrt の場合
opkg find kmod-nft*
apt list *nft*
パッケージ管理で入ってるはずのモジュールは、一部がカーネル・モジュールなので、カーネルモジュールを確認する
## openWrt lsmod | grep ^nf_tables | awk '{ print $3 }' | xargs echo | tr ',' '\n' | sort ## ubuntu lsmod | grep ^nf_tables | awk '{ print $4 }' | xargs echo | tr ',' '\n' | sort
意外なところでシンタックスエラー( unexpteced )が出るときは、モジュールを疑っても損しない。
iptablesで入れたルールの行き先。
nftablesが提供する iptablesコマンドがある。
追加したルールは暗黙的にnftablesに登録される
iptables(nftables提供)で条件を入れると、iptablesは、nftablesとして投入される。
iptables(オリジナル)は初期テーブルが存在した、iptables(nftables提供)でも同じように暗黙的に初期テーブル名が用意されている。初期テーブルは自動的に作られて名前も固定である。
iptablesで追加した場合
iptables -I FORWARD -i tun2 -j REJECT
一覧を見てみる。
iptables -L
iptables(オリジナル)と変わらない結果が得られるが
# Warning: iptables-legacy tables present, use iptables-legacy to see them Chain INPUT (policy ACCEPT) target prot opt source destination Chain FORWARD (policy ACCEPT) target prot opt source destination REJECT all -- anywhere anywhere reject-with icmp-port-unreachable Chain OUTPUT (policy ACCEPT) target prot opt source destination
nftablesとしてルールは存在している。
table ip filter { # handle 6 chain FORWARD { # handle 1 type filter hook forward priority filter; policy accept; oifname "tun2" counter packets 0 bytes 0 accept # handle 2 } }
上記のような nftablesのテーブル名とチェーンが作られている。
このことから、iptables(nftables提供)はnftablesのサブセットのような扱いを受ける。
iptables追加したでルールは、処理にnftablesが使われる。nftablesルールが適用されてから、iptables追加ルールが適用される、またはその逆が起きる。それゆえにiptablesだけでパケット処理完結しない。テーブルを見てわかる通りiptables処理後にnftablesルールが待ち構えてることがあるため、iptable/nftablesと混ぜて書くと思わぬ通信切断を招くかもしれない。
iptables-legacy と iptables(nftables)
少々ややこしいが、iptables(nftables)とiptables-legacy の2つのコマンドがある。
iptables-legacy コマンド
# iptables-legacy --version iptables v1.8.7 (legacy)
iptables(nf_tables)コマンド
# iptables --version iptables v1.8.7 (nf_tables)
iptables-legacyは、従来のiptablesである。nftablesとは別物である。
iptables(nftables)で記載されたルールはnftables内にサブセットのように登録がされる。一方で iptables-legacyは、いままで通りiptablesのみに登録される。繰り返すと、nftablesモードではなない、従来モードのiptablesがほしいときはlegacyを使う。
同じコマンドが2つあるので、誤解して時間を浪費しないように。
iptablesからnftablesには、この3つの選択肢があり、組み合わせると6通りに移行状態が存在することになる。
コマンド | iptablees-legacy | iptables(nf_tables) | nft |
---|---|---|---|
none | ― | ― | ― |
pure | ✓ | ― | ― |
mixed | ✓ | ✓ | ― |
mixed | ✓ | ― | ✓ |
mixed | ✓ | ✓ | ✓ |
mixed | ― | ✓ | ― |
mixed | ― | ✓ | ✓ |
pure | ― | ― | ✓ |
3種類(nft/iptable(nf)/iptables-legacy)のルールを同時に混ぜるとカオスだろうし、多分動かかない。
nft に全面移行するか、iptables-legacyで頑張る(nftを使わない)、nftのみ(iptables/nft+nftables)にする、この3つが現実的なんだろうね。
私は、ルールが30本程度だったので、全部をnft に書き換えた。
iptables-legacyは書き換え方法が見つからないときの最終手段なのかもしれない。
iptables-translate
iptables-translate を使うと、ntablesに変換できる。ただし、テーブル名とチェインは選べない。iptables(nf_tables)で追加したときとそのまま同じ暗黙的テーブル名が使われる。
translateの例
# iptables-translate -I FORWARD -i wg0 -j REJECT nft insert rule ip filter FORWARD iifname "wg0" counter reject
コマンドiptablesは、nft_tablesを利用している。
# iptables --version iptables v1.8.7 (nf_tables)
translate では iptablesコマンドでnftablesに登録されるルールが出力される
iptablesコマンドの実行
iptables-I FORWARD -i wg0 -j REJECT
この結果は次のようになっていて。
# nft list table ip filter table ip filter { chain FORWARD { type filter hook forward priority filter; policy accept; iifname "wg0" counter packets 16 bytes 8478 # xt_REJECT } }
これは、transate で出力されるものと一致する(テーブル名・チェイン名)
# iptables-translate -I FORWARD -i wg0 -j REJECT nft insert rule ip filter FORWARD iifname "wg0" counter reject
つまり、translate は iptables互換モードでnftablesに作成されるべきテーブルをコマンド形式で出力している、と考えられる。
iptables-translate
の結果をそのまま使うと、iptablesコマンドを実行するのと同じ結果になる。ルールは何ら変わりがない。translate変換結果を参考に、自分で書く。そのために使うようにしておく。つまり、translateの結果を見ながら必要に応じてテーブル名・チェインを追加する。
検索キーワード nft
nft
で検索すると、ゴミしか出ないので注意する。必ず netables nft
で検索する。
NFTだと一攫千金を夢見たゴミ記事ばかりでちょっと困るよね。
紛らわしい単語
nftables だめなところ
set セット
いくつもある。とてもめんどくさい。
- ipset 後継の 配列としての
SET
- パケットを更新するための
set
- mark をつけるための
mark set
(ct)
ct 系には、他にも label set
event set
とか無数にある。
紛らわしい statement と expression
nft は ルールを次のように書く
tcp dport 443 mark set 0x3
これは、次のようになっていて
<expression> <statement> <式> <処理>
また、式と処理は並べて書くことができる。
<式>,<式>,<式>... <処理>,<処理><処理>...
この並べて書くのが曲者なので、初見プレイでは迷路にハマることになる。
tcp dport 443 mark set 0x3 counter # マークを付けてカウントする tcp dport 443 mark 0x3 counter # マークがあるものをカウント
nft はこのように、「条件式」と「実行処理内容」の記述に明確な区切りがないので、慌てていると勘違いを生み出しやすい。(処理と条件の間に do とかキーワードを入れればいいけど、入れられないので、実験中はcounter をよく入れる。)
counter を区切りに使う例。
tcp dport 443 counter mark set 0x3 # マークを付けてカウントする tcp dport 443 mark 0x3 counter # マークがあるものをカウント
また、ルール実験しているとパケットがちゃんとマッチしているか見失いがちなので、counter が反応しているかを通してルールが効いているチェックになる。
2023/09/06 追記 ルールの削除について
nftables で追加・削除をスクリプト化するばあいは、コメントをつかう。 将来的にはAddと同じ方法でrule の削除ができるらしいが、今はできない。
nft insert rule inet fw4 forward iifname "br-lan" ip daddr 1.1.1.1 accept
これは出来ない
nft delete rule inet fw4 forward iifname "br-lan" ip daddr 1.1.1.1 accept
今はできないので、代わりにどうするか。 削除するルールを入れたチェインまるごと消す。
nft insert rule mychain myaccept iifname "br-lan" ip daddr 1.1.1.1 accept
チェインごと消す
nft delete chain inet mychain myaccept
削除するルールを入れたチェインを入れたテーブルをまるごと消す。
nft insert rule mychain myaccept iifname "br-lan" ip daddr 1.1.1.1 accept テーブルを消す nft delete table mychain
それでも駄目なときもある。既存のテーブルの既存のチェインにいれたAcceptを消したいときとか。 その場合は、ハンドルIDを使って消す。
nft delete rule inet fw4 forward handle $HANDLE_ID
しかし、ハンドルIDを探すのが煩雑である。 そこで、*コメント*を使ってユニーク・キーを入れておく。
UNIQUE_COMMENT='takuya:vpn accept' nft insert rule inet fw4 forward iifname "br-lan" ip daddr 1.1.1.1 accept comment "\"{$UNIQUE_COMMENT}\""
HANDLE IDを探して削除
HANDLE=$(nft -a list table inet fw4 | grep "{$UNIQUE_COMMENT}" | grep -oP '(?<=handle )+\d+') nft delete rule inet fw4 forward handle $HANDLE
コメントを使ってユニークIDとして識別してしまえば、HADLEを探すのも簡単である。 ### 参考にした資料 [Linuxにおける新たなパケットフィルタリングツール「nftables」入門](https://knowledge.sakura.ad.jp/22636/) [Matcher](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Matches) [Statements](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Statements) [manpage/ubuntu](https://manpages.ubuntu.com/manpages/impish/man8/nft.8.html) [Quick Reference ](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes) [Transparent proxy support](https://www.kernel.org/doc/html/latest/networking/tproxy.html) [Map Example](https://wiki.nftables.org/wiki-nftables/index.php/Multiple_NATs_using_nftables_maps) [コンパイル・オプション・モジュール](https://wiki.nftables.org/wiki-nftables/index.php/Building_and_installing_nftables_from_sources#Validating_your_installation)