それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

nftables移行のためnftコマンドとnftablesを調べたときのメモ。

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 filterpriority 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_tableip6 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*

debian / ubuntu

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のみで書く(legacy)
  • iptables(nftables)でnftablesを使う。
  • iptablesの記述をnftablesに書き換える。

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」入門 Matcher Statements manpage/ubuntu Quick Reference Transparent proxy support Map Example コンパイル・オプション・モジュール