linuxで単純なルータ機能を作る
マスカレードやDNAT/SNATをして、ルータ機能を作ってみる。
先のiptablesと比較してnft でどうなるのかを見たいので、試した。
ネットワーク構成図
以下のようなネットワーク構成を作る
実際に作った構成
ipコマンドには netns
の機能があるので、これを利用して内部で名前空間(namespace)で分割して簡単にネットワーク構成を作ってみた。
作成用のコマンド
## コンテナ作成 lxc launch ubuntu:22.04 t01 lxc config set t01 security.nesting true lxc restart t01 lxc shell t01 ## デバイス追加 ip link add name veth0 type veth peer name veth0-c01 ip netns add c01 ip link set veth0-c01 netns c01 ip netns exec c01 ip link set veth0-c01 name eth0 ip link set veth0 name eth1 ## アドレス追加 ip netns exec c01 ip addr add 10.2.0.2/24 dev eth0 ip addr add 10.2.0.1/24 dev eth1 ## リンクアップ ip netns exec c01 ip link set eth0 up ip link set eth1 up ## ルーティング ip netns exec c01 ip route add default via 10.2.0.1 dev eth0
マスカレードを試す。
ルータ機能で一番使い勝手のいいマスカレードを試してみる。
実際のコマンド
## マスカレード nft add table mynat nft add chain mynat postrouting { \ type nat hook postrouting priority srcnat \; \ } nft add rule mynat postrouting oifname "eth0" masquerade ## 後片付け nft delete table mynat
出来上がったルール
nft list table テーブル名
でルールが確認できる。
root@t01:~# nft list table mynat
table ip mynat { chain postrouting { type nat hook postrouting priority srcnat; policy accept; oifname "eth0" masquerade } }
accept は自動で書かれた。
SNATをする
ソースアドレスを自分のものに書き換えてNATする。マスカレードで間に合わないときに使う。
実際のコマンド
## LAN_IP=10.17.238.236 LAN_IP=$(ip a s eth0 | grep -oP '(?<=inet )([\d+\.]+)(?=/)') ## SNAT nft add table mynat nft add chain mynat postrouting { \ type nat hook postrouting priority srcnat \; \ } nft add rule mynat postrouting oifname "eth0" snat to $LAN_IP
出来上がったルール
root@t01:~# nft list table mynat
table ip mynat { chain postrouting { type nat hook postrouting priority srcnat; policy accept; oifname "eth0" snat to 10.17.238.236 } }
DNAT
DNATで宛先を変更する。SNAT・MASQURADEと違い、ルーティング機能より前に挟み込まれるのでpreroutingへフックする。
実験なので、すべてのパケットをeth0/10.17.238.236
宛に向ける。ICMP(ping)が通ればいいやレベル。
実際のコマンド
## LAN_IP=10.17.238.236 LAN_IP=$(ip a s eth0 | grep -oP '(?<=inet )([\d+\.]+)(?=/)') ## SNAT nft add table mynat nft add chain mynat prerouting { \ type nat hook prerouting priority dstnat \; \ } nft add rule mynat prerouting iifname "eth1" dnat to $LAN_IP ## 後片付け nft delete table mynat
実際に出来上がったルール
nft list table mynat
table ip mynat { chain prerouting { type nat hook prerouting priority dstnat; policy accept; iifname "eth1" dnat to 10.17.238.236 } }
DNAT(宛先IPを指定、プロトコル指定)
先程のDNATはザックリし過ぎなので、具体的に宛先IPとプロトコル(ICMP)を決めてパケットを変換する。
実際のコマンド
## LAN_IP=10.17.238.236 LAN_IP=$(ip a s eth0 | grep -oP '(?<=inet )([\d+\.]+)(?=/)') ## SNAT nft add table mynat nft add chain mynat prerouting { \ type nat hook prerouting priority dstnat \; \ } nft add rule mynat prerouting \ iifname "eth1" \ ip daddr 1.1.1.1 \ ip protocol icmp \ dnat to $LAN_IP ## 後片付け nft delete table mynat
実験する
ip netns exec c01 ping 1.1.1.1 # DNATでeth0が応答する ip netns exec c01 ping 1.0.0.1 # 戻り経路がないので応答しない
実際に出来上がったルール
root@t01:~# nft list table mynat table ip mynat { chain prerouting { type nat hook prerouting priority dstnat; policy accept; iifname "eth1" ip daddr 1.1.1.1 ip protocol icmp dnat to 10.17.238.236 } }
DNAT( IP / port / tcp を指定)
DNATでもうちょっと具体的に、TCPポートを指定する。
## LAN_IP=10.17.238.236 TARGET_IP=$( dig +short g.co ) ## DNAT 1.1.1.1"80 を$TARGET_IP:80へ nft add table mynat nft add chain mynat prerouting { \ type nat hook prerouting priority dstnat \; \ } nft add rule mynat prerouting \ iifname "eth1" \ tcp dport 80 \ ip daddr 1.1.1.1 \ dnat to $TARGET_IP ## SNAT 戻り経路 LAN_IP=$(ip a s eth0 | grep -oP '(?<=inet )([\d+\.]+)(?=/)') nft add chain mynat postrouting { type nat hook postrouting priority srcnat \; } nft add rule mynat postrouting oifname "eth0" snat to $LAN_IP ## 後片付け nft delete table mynat
実験コマンドと、その結果
ip netns exec c01 curl http://1.1.1.1
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.com/">here</A>. </BODY></HTML>
1.1.1.1 がDNATされて、g.co へ飛ばされているのがわかる。
実際に出来上がったルール
root@t01:~# nft list table mynat table ip mynat { chain prerouting { type nat hook prerouting priority dstnat; policy accept; iifname "eth1" tcp dport 80 ip daddr 1.1.1.1 dnat to 172.217.25.174 } chain postrouting { type nat hook postrouting priority srcnat; policy accept; oifname "eth0" snat to 10.17.238.236 } }
nftables の hook とチェインについて
nft には、「ベースチェイン」「レギュラーチェイン」がある。
名称 | 役割 |
---|---|
ベースチェイン | hook ポイントへフックする |
レギュラーチェイン | 単なる関数として動作 |
ベースチェインでhook ポイントへフックさせてつかい、条件にマッチしたらレギュラーチェインへjump させる。という使い方になる。
主に使うのはベースチェイン
ここままで、使ってきたチェインはすべてベースチェインであり、hook で フックポイントへ登録している。
nft add chain mynat prerouting { type nat hook prerouting priority dstnat \; } nft add chain mynat postrouting { type nat hook postrouting priority srcnat \; }
登録は次のように解釈できる。
type nat
が主に使うタイプなのだが、パケットをフィルタリングするときは、 type filter
を使い、パケットをルーティングするときは type route
を使う。もちろん用途があるので、natをforwarding に登録したり、routeを inputに登録するような無意味なことはできない。filter はfilterの名前のとおりであるが、「条件にマッチさせる」ことが目的でありマッチ後に何をするかは自由なので、何でも書ける。フィルタは疎通決定というより、マッチさせるという意味で解釈するとわかりやすいかもしれない。
type | フックするポイント |
---|---|
filter | prerouting , forwarding, input , outout. postrouting |
nat | prerouting , input , output , postrouting |
route | output |
優先度に付いて
優先度は、もともと決まっている。
どの(レイヤ)のどこ(フック)で何をするか(mangle、dstnat 、srcnat )である程度決まっている。フックに登録するときにデフォルト設定が適用されて、それを±1して調整するっぽい。
まとめ・感想
iptablesで作ったときと同等の処理をnftで記述してみた。
どう考えてもnft のほうが覚えることが多くて冗長になりがちで。覚え直すことある。ついつい敬遠しがちである。
ただ、nftほうが複雑化するルールを整理整頓でき、影響範囲を限定できる。ルールが思わぬ混線をすること減らせる。またテーブル名が一種の名前空間になっているため、不要なルールを除くオペレーションをやりやすい。ミスのリカバリを速くできる。nftが使えるときはnftコマンドを使ったほうがいいかもしれない。
ただ、nft はiptablesより整理されてるとはいえ、nftそれ自体がコマンド完結で使いづらいので、今後また大規模な進化が起こる予感がする。init.d のかわりに upstartが提案されたけど結局はsystemdになったように、chrootのかわりにdockerが作られたけど、podmanになったように。lxcがlxdになったように、ifconfigがip a s になったように。
nftも進化の途中っぽい感じはびんびんする。(似たようなキーワードが多くて分かりづらい)
個人的にはこの辺の基礎部分をイジってほしくは無いのですが・・・
ブリッジモードも試そうと思って、iptablesでは作りかけていたが力尽きたので諦めた。
iptables / nf_tablesの変換表
今回使ったルールで、iptablesとnft でのフィルタルール例の早見表(チートシート)にまとめておいた。
nftables 早見用
iptables | nftables | モジュール |
---|---|---|
--protocol tcp -p tcp |
protocol tcp | xt_tcp |
---source 192.168.1.0/24 -s 192.168.1.0/24 |
ip saddr 192.168.1.0/24 | |
---destination 192.168.1.0/24 -d 192.168.1.0/24 |
ip daddr 192.168.1.0/24 | |
-o eth0 --out-interface eth0 |
oifname eth0 | |
-i eth0 --i-interface eth0 |
iifname eth0 | |
--dport 443 | tcp dport 443 | |
--jump MARK --set-mark 0x2 |
mark 0x2 | xt_MARK |
--jump SNAT -to-source 192.168.1.1 | snat to 192.168.1.1 | xt_SNAT |
モジュールは iptables(nftコマンド内蔵)で作ったルールで適用されたモジュールのメモである。