それマグで!

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

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

nftablesのv4-v4のsnat/masqueradeとiptablesの比較

nftables

linuxiptablesはそのままのコマンドで動作するのですが。

iptablesはnftablesのサブセットのような扱いになっています。

iptablesと比較しながら、超基本的な書式を学ぶことにした。

nftables で masquerade する。

iptables では一行でかけたものが3行に分かれました。

nft add table nat
nft add chain nat postrouting { type nat hook postrouting priority 100 \; }
nft add rule nat postrouting oifname "eth1" masquerade

え?三行?ですか。とおもったので、nftablesにチャレンジすることにした。

ただし、ゼロからの初期状態から追加するには、prerouting でnftにパケットを食わせる必要があります。(合計4行になります。)

nft add chain nat prerouting { type nat hook prerouting priority -100 \; }

最初にテーブル追加

最初にテーブルを追加します。

nft add table my_nat

iptablesのときは nat というテーブルは用意がありましたが、nftablesでは空っぽからスタートするので、自分で作ります。名前は自由に決められるが自由すぎて迷う。名前空間とか用意してくれればいいのに。

テーブルにフィルタチェーンを追加

次に、作ったテーブルにチェインを追加します。

nft add chain my_nat postrouting { type nat hook postrouting priority 100 \; }

masquerade する

チェインに入ったものをマスカレードするように書きます。

nft add rule my_nat postrouting oifname "eth1" masquerade

nftablesの特徴

テーブルにチェインを作って、rule書く

table -> chain -> rule

実際にやってみる。

クライアントがRouterを経由してマスカレードで出ていく構成を考えます。

home-lan - router  - lxdbr - client

次のような構成をLXDで作ります。

router としてのマシン作る

lxc launch ubuntu: router01

2本目のNICを挿し込む

lan 側のnic をmacvlanとして、router01 に挿し込む

lxc config device add router01 mytap0 nic nictype=macvlan parent=eth0
lxc exec router01 dhclient -v eth1

クライアントを作成する

lxc launch ubuntu: client01

2台分をアップデートすると時間がもったいないので、apt-cacherを使う。

echo 'Acquire::HTTP::Proxy "http://apt-cacher.lan:3142";' |sudo tee  /etc/apt/apt.conf.d/01proxy
apt update 
apt upgrade -y

nftables を確認する

# iptables --version
iptables v1.8.7 (nf_tables)

2つとも、iptablesはnftables提供のものを使っていることを確認。

client01 の通信を router 経由にする

変更前の確認

root@client01:~# ip route
default via 10.17.238.1 dev eth0 proto dhcp src 10.17.238.67 metric 100
10.17.238.1 dev eth0 proto dhcp scope link src 10.17.238.67 metric 100

変更

ROUTER01_IP=10.17.238.10
CLIENT01_IP=10.17.238.202
ip route del default 
ip route add default via $ROUTER01_IP dev eth0  src $CLIENT01_IP metric 100

変更後の確認

root@client01:~# ip route show
default via 10.17.238.127 dev eth0 src 10.17.238.67 metric 100
10.17.238.0/24 dev eth0 proto kernel scope link src 10.17.238.67 metric 100
10.17.238.1 dev eth0 proto dhcp scope link src 10.17.238.67 metric 100

router01で転送を有効にする

パケット転送を有効にする。

sysctl -a | grep -F all.forward
sysctl  -w net.ipv4.conf.all.forwarding=1

準備は完了です。

以上で準備は完了です。

最初にiptables(legacy nft)を試す

iptablesはnft です。

# iptables --version
iptables v1.8.7 (nf_tables)

nft でもiptablesコマンドはそのまま使えるので、よく知られたiptablesのコマンドでマスカレードを作ってみます。

iptables ( legacy) router01 でNAT/MASQUERADEする

LAN_DEV=eth0
LAN_NET=10.17.238.0/24
WAN_DEV=eth1
WAN_NET=0.0.0.0/0
iptables -t nat -A POSTROUTING -s $LAN_NET -d $WAN_NET -o $WAN_DEV -j MASQUERADE

iptables (legacy)として互換レイヤーで扱う。とiptablesと同じ書き方で同じ動作がするようになってるので、最初に互換レイヤーで試す。

疎通確認する

router01 でtcpdumo を見ながら、client01 からping を送信してみます。 ping が応答してきます。

root@client01:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=55 time=8.67 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=55 time=8.51 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=55 time=8.70 ms

NATで正しく通信できることがわかります。

iptablesを消す

疎通が確認できたので、iptablesのテーブルをflush して何もない状態に戻します。

iptables -t nat -F
iptables -F

iptables をリセットして 初期状態に戻した。

nftables で masquerade する。

今度は、nft コマンド(nftables)を使ってマスカレードを試みる。

## テーブル追加
nft add table nat
## テーブルにフィルタチェーンを追加
nft add chain nat postrouting { type nat hook postrouting priority 100 \; }

masquerade する

nft add rule nat postrouting oifname "eth1" masquerade

iptablesとnftablesは同じだけど、iptablesは初期テーブルを持ってるが、nftablesはすべてを自分で書く必要がある。

初期テーブルnatくらい持っておけよとおもうが、natテーブルが大量になったり依存が増えて管理が大変になるだろうことを思うと、ルールセットごとに個別に指定するためには名前で分けるほうが、依存関係をなくせてスッキリするんだろうな。でも、単純なルールで使うカジュアル用途だと不便な気もする。

疎通確認

root@client01:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=55 time=8.72 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=55 time=9.42 ms
64 bytes from 1.1.1.1: icmp_seq=4 ttl=55 time=9.03 ms

iptablesの状態を確認

nftが有効な状態でiptablesの状態を確認すると・・・

root@router01:~# iptables -L -t nat
free(): double free detected in tcache 2
Aborted (core dumped)

コアダンプになった・・・ nat いう名前だとcore dump になった。なぜだ。

別の名前にすると動く、どうやらnatというテーブル名を使うべきではないようだ。

nft テーブルを確認

全ルールを見てみる。

nft list ruleset | less

全テーブルを見る。

nft list tables

テーブルを指定して中身を見る

nft list table nat

テーブル名を指定するとき、自分で決めた名前を使う。

nft テーブルを削除

作ったテーブルを削除してもとに戻します。

nft delete table nat

テーブルに名前をつける。

先程のnat作成は、テーブル名がnat なので、具体的な名前をつける。

nft add table ip my_nat
nft add chain ip my_nat postrouting { type nat hook postrouting priority 100 \; }
nft add rule  ip my_nat postrouting oifname "eth1" masquerade

ip はアドレスファミリipv4を示す。省略可能

削除のときも名前を使う。

nft delete table ip my_nat

名前をnat 以外にすると、コアダンプもなくなる。

root@router01:~# iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
(略
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
root@router01:~# iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
(略

nftables で作った my_nat テーブルは、iptablesから見えない。期待通りの動作ですね。

root@router01:~# iptables -L -t my_nat
iptables v1.8.7 (nf_tables): table 'my_nat' does not exist
Perhaps iptables or your kernel needs to be upgraded.

natという名前以外を使うとコアダンプしなくなったので、名前には注意したほうがいいかもしれない。

名前にドット( . ) が使えるようなので、簡易的な名前空間っぽいものが作れるかもしれない。大文字小文字は区別しない(はず)なのでいい感じに記号を使う必要がある。

SNATする。

SNAT も iptables で旧来スタイルは、次のようになる。

## 外向きのIPでSNATする
ROUTER01_IP=192.168.2.183
iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source $ROUTER01_IP
## 削除する
iptables -t nat -D POSTROUTING -o eth1 -j SNAT --to-source $ROUTER01_IP

同じことを nftables で書くには、次のようになる。

ROUTER01_IP=192.168.2.183
nft add table ip my_snat
nft add chain ip my_snat postrouting { type nat hook postrouting priority 100 \; }
nft add rule ip my_snat postrouting oifname "eth1" snat to $ROUTER01_IP

削除する。 ipipv4の意味で、省略可能。

nft delete table ip my_snat

iptables-translate

iptables-translate をつかうと、iptablesの書式をnftablesに変換できる。

最初に書いた iptables の書き方をtranslate してみる。

LAN_DEV=eth0
LAN_NET=10.17.238.0/24
WAN_DEV=eth1
WAN_NET=192.168.2.0/24

iptables-translate -t nat -A POSTROUTING -s $LAN_NET -d $WAN_NET -o $WAN_DEV -j MASQUERADE
nft add rule ip nat POSTROUTING oifname "eth1" ip saddr 10.17.238.0/24 counter masquerade

変換できるが、そのままコピペでは動かない。上記をよく見ると、iptables-d $WANが欠損している。tableはnatであるが、ruleは大文字POSTROUTINGである。chainが無いので動かない。nftablesに必要な、table->rule->chain の包含関係が無い。上記のpostrouting を動かすためには、prerouting でpostrouting にjumpしなくちゃいけない。そのためiptablesルールをnftablesでゼロから書くと3行必要なのだが、translateでは1行になっている。なので、このままで動かない。

このように、nftを少しでも知らないと変換結果を判断できない。またテーブル名称がnatになってしまうのでnft の良さを活かせないと思う。 「iptablesのあれはnftablesではどう書くんだろう。」ってときにサンプルが出てくる。translateの結果は参考程度にとどめたほうがい無難なのではないか。

同じことをnftablesで書くと次のようになる。

WAN_NET=eth1
nft add table nat
nft add chain nat postrouting { type nat hook postrouting priority 100\; }
nft add rule nat postrouting ip saddr $LAN_NET  ip daddr $WAN_NET oifname eth1 masquerade
## 削除
nft delete table ip nat

nat は自分で決める名前、oifoifnameの略、

iptables-translate は、「ルール」を変換するのであって、ルールの入れ物である、チェインやテーブルは自分で作る必要がある。

-s -d のようにSRC・DSTのアドレスを指定する場合

次のように、SRC・DSTのアドレスを指定する場合はよくある。

iptables -t nat -A POSTROUTING -s 1.1.1.1 -d 192.168.2.1 -o eth1 -j MASQUERADE

これをtranslate すると次のようになる。

$ iptables-translate -t nat -A POSTROUTING -s 1.1.1.1 -d 192.168.2.1 -o eth1 -j MASQUERADE

translate 結果

nft add rule ip nat POSTROUTING oifname "eth1" ip saddr 1.1.1.1 ip daddr 192.168.2.1 counter masquerade

先程までのテーブルとルールの話を踏まえると、translateの結果をそのまま使えない。POSTROUTINGが大文字なのも慣習的すぎて理解しにくい。counter も初見ですよね。

nftables で記述するとtanslate の結果を次のように書き換えれば、同じように動くはずである。

WAN_DEV=eth1
LAN_IP=10.17.238.0/24
WAN_IP=1.1.1.1

nft add table my_nat
nft add chain my_nat postrouting { type nat hook postrouting priority 100\; }
nft add rule my_nat postrouting oif $WAN_DEV ip saddr $LAN_IP ip daddr $WAN_IP masquerade

oifnameoif と略称にできる。oifnameoutput interface name の略だと思われる。 ipIPv4 のことでip は省略可能。ただし、ip daddrip saddrにつくip 省略不可である。

このように、translate した結果は、そのままコピペしてもえらーになり、参考程度にするのがいいかと思っ割れる。

戻りパケットを識別するとき、次のように書く

nft add rule filter input ip daddr 192.168.2.1 ct state established,related accept

戻りパケットを受け入れるように設定する。

上記の例は、次のような構造になっていた。

ADDR=192.168.2.1
MATCH=ct state established,related 
ACTION=accept
nft add rule filter input ip daddr $ADDR $MATCH $ACTION

ACTIONの決め方 ACCEPT/DENY/REJECT

iptablesには、デフォルト・ポリシーがあったが、nftablesにはデフォルトが無い。iptablesでデフォルトDENYで、必要なもの通すといった書き方はなくなってた。

なので、ルールを順番に追加して最終チェックでDENYをアクション登録する

全部拒否して22(ssh)だけを許可する。

input の 22 をACCEPTする(デフォルトポリシーがACCEPT)

iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -j DROP

input の 22 をACCEPTする(デフォルトポリシーがDROP

iptables -A INPUT -p tcp --dport 22 -j ACCEPT

nftablesにはデフォルト・ポリシーはACCEPTなので、次のように書く。

nft add table filter
nft add chain filter input { type filter hook input priority 0 \; }
nft add rule filter input tcp dport 22 accept
nft add rule filter input drop

デフォルト・ポリシーは、チェインごとに存在するのでベースチェインに書く。

icmp を許可する

nft 書いてて躓いたのがICMPだった。

v4 icmp は echo-request を明示しないとだめだった。

nft add table filter
nft add chain filter input { type filter hook input priority 0 \; }
nft insert rule filter input icmp type echo-request accept

ちなみに、上記のinsertiptables -I に相当する。INSERTで先頭に追加する。addは末尾に追加。

ルールをすべて表示して、個別に削除する

いくつかルールを足して試していて、ミスったものだけを削除したいとき。

iptablesなら IをDに変えればよかった。

iptables -I xxxx
iptables -D xxxx

nftablesの場合は、iptablesのように単純でははい。

つぎのように、filter テーブルに chain input を作った場合

nft add table filter
nft add chain filter input { type filter hook input priority 0 \; }
nft add rule filter input tcp dport 22 accept
nft insert rule filter input icmp type echo-request accept
nft add rule filter input drop

チェインの中身を表示するとき、handleを明示する。

root@router01:~# nft --handle list chain filter input
table ip filter {
        chain input { # handle 1
                type filter hook input priority filter; policy accept;
                icmp type echo-request accept # handle 3
                tcp dport 22 accept # handle 2
                drop # handle 4
        }
}

この --handleが大事で、追加したルールを個別に削除するときは、ハンドルを使う

個別に、IDを指定で削除

root@router01:~# nft delete rule filter input handle 3
root@router01:~# nft --handle list chain filter input
table ip filter {
        chain input { # handle 1
                type filter hook input priority filter; policy accept;
                tcp dport 22 accept # handle 2
                drop # handle 4
        }
}

削除後の番号を見ると、欠番が生じているのがわかる。インデックスでありながらも欠番がでるので、採番は連番とは限らない。番号には注意を払うこと。

nft --handle list chain filter input
nft -a list chain filter input

記述するとhandle は長いので、--handle-a でも代用可能である。

## 次は、できない
## 中身を記述して削除は、将来的なサポート予定らしい
nft delete rule filter input tcp dport 22 accept 

add をdelete にかえただけの削除はサポートされる「予定」らしい。

参考資料

https://wiki.archlinux.jp/index.php/Nftables#.E3.83.86.E3.83.BC.E3.83.96.E3.83.AB.E3.81.AE.E4.BD.9C.E6.88.90

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/securing_networks/configuring-nat-using-nftables_getting-started-with-nftables

https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management#:~:text=0.0.8%20drop-,Removing%20rules,it%20uniquely%20identifies%20the%20rule.&text=but%20this%20is%20not%20yet,until%20that%20feature%20is%20implemented.