NAT でプライベート網をインターネットへ出す
このラボはVM上で動きます
今回はVM自身がルーターです。ネームスペースlanがプライベートネットワークのホストで、VMのuplink(enp1s0、10.0.2.2)がインターネット側です。プライベートアドレスの10.30.0.2は、外から戻ってくる道がないので、VMが自分のアドレスに書き換えて送り出す必要があります。それがマスカレードです。このVMが到達できるのは、パブリックの80/443とDNSだけです。
目標
マスカレード(SNAT)でプライベートネットワークのホストをインターネットに出し、パケットの送信元が実際に書き換わる様子を両側のインターフェースで捕まえ、DNATで外から来た接続を内側のサーバーへ転送します。家庭用ルーターとDockerの-pが行っているのは、まさにこれです。
なぜ重要なのか
プライベートアドレスは、インターネット上ではルーティングされません。ところが、家・会社・クラウドVPC・KubernetesのPodは、すべてプライベートアドレスです。これらが外と通信する方法は1つしかありません。境界でアドレスを書き換え、レスポンスが来たら元に戻すことです。その巻き戻しを可能にするのが接続追跡(conntrack)で、そのためNAT装置が再起動するとすべての接続が切れます。「中からは通るのに外からは通らない」という報告の答えは、ほとんどいつもこの章にあります。
ステップ
- ネームスペース
lanとvethlan0(lan、10.30.0.2/24)↔lan-r(ルート、10.30.0.1/24)を作成し、lanのデフォルトルートを10.30.0.1に、ルートのnet.ipv4.ip_forwardを1にしてください。 lanからuplinkのゲートウェイ10.0.2.1へのpingが通らない結果を、/root/nat/before.txtに保存してください。- テーブル
ip labnatのpostroutingフックのチェーンに、10.30.0.0/24がuplinkへ出ていくときcounter masqueradeするルールを入れて、pingが通るようにしてください。 - uplinkと
lan-rで同時にICMPをキャプチャしながらlanからpingを送り、外から見た送信元が書き換わっている様子を/root/nat/outside.txtに、内側の元の送信元を/root/nat/inside.txtに保存してください。 /etc/netns/lan/resolv.confに上流DNSサーバーを書いて、lan内でgetent hosts archive.ubuntu.comが通るようにし、その結果を/root/nat/dns.txtに保存してください。lanからcurl -sI http://archive.ubuntu.com/を実行した応答ヘッダーを/root/nat/web.txtに保存してください。nft list table ip labnatを/root/nat/counter.txtに保存してください。マスカレードのルールのcounter packetsが0より大きい必要があります。lan内に10.30.0.2:80のWebサーバーを起動し、ネームスペースguest(vethguest010.31.0.2/24 ↔guest-r10.31.0.1/24)を作成して、preroutingフックのチェーンのiifname "guest-r" tcp dport 8080 dnat to 10.30.0.2:80ルールで、guestからhttp://10.31.0.1:8080/が通るようにし、その応答の1行目を/root/nat/dnat.txtに保存してください。
参考
- uplinkの名前は
ip route show defaultのdevです(このVMではenp1s0)。 - マスカレード:
nft add table ip labnat; nft 'add chain ip labnat post { type nat hook postrouting priority 100; }'; nft add rule ip labnat post ip saddr 10.30.0.0/24 oifname "enp1s0" counter masquerade。 - ネームスペースは
/etc/resolv.confも別です。/etc/netns/<이름>/resolv.confがあれば、ip netns execがそれを上書きして適用してくれます。上流サーバーのアドレスはresolvectl dns enp1s0で確認できます(プレースホルダーはネームスペース名です)。 - DNAT:
nft 'add chain ip labnat pre { type nat hook prerouting priority -100; }'。 - よくある間違い:
ip_forwardを忘れることです。NATルールがあっても、転送しなければ意味がありません。
プライベートネットワークのホスト1台
ネームスペースlanとvethlan0(lan、10.30.0.2/24)↔lan-r(ルート、10.30.0.1/24)を作成し、lanのデフォルトルートを10.30.0.1に、ルートのnet.ipv4.ip_forwardを1にしてください。
今回はvethの片側(lan-r)を移さずに、ルートに置きます。ip addr add 10.30.0.1/24 dev lan-r; ip link set lan-r up。ip -n lan route add default via 10.30.0.1。sysctl -w net.ipv4.ip_forward=1。
NATなしでは
lanからuplinkのゲートウェイ10.0.2.1へのpingが通らない結果を、/root/nat/before.txt に保存してください。
ip netns exec lan ping -c 1 -W 2 10.0.2.1 > /root/nat/before.txt 2>&1。リクエストはVMを経由して出ていきますが、レスポンスが10.30.0.2へ戻る道がないため、100% packet lossになります。経路がないとき(unreachable)とは違う症状です。
マスカレード
テーブルip labnatのpostroutingフックのチェーンに、10.30.0.0/24がuplinkへ出ていくときcounter masqueradeするルールを入れて、pingが通るようにしてください。
nft add table ip labnat、nft 'add chain ip labnat post { type nat hook postrouting priority 100; }'、nft add rule ip labnat post ip saddr 10.30.0.0/24 oifname "enp1s0" counter masqueradeを実行します。これでlanのpingが通ります。
アドレスが書き換わる瞬間
uplinkとlan-rで同時にICMPをキャプチャしながらlanからpingを送り、外から見た送信元が書き換わっている様子を /root/nat/outside.txt に、内側の元の送信元を /root/nat/inside.txt に保存してください。
2つのキャプチャを先に起動しておきます: timeout 5 tcpdump -i enp1s0 -nn -l icmp > /root/nat/outside.txt 2>&1 &とtimeout 5 tcpdump -i lan-r -nn -l icmp > /root/nat/inside.txt 2>&1 &。そのあとip netns exec lan ping -c 1 10.0.2.1を実行します。外側のファイルは10.0.2.2 > 10.0.2.1、内側のファイルは10.30.0.2 > 10.0.2.1になっている必要があります。
ネームスペースのresolv.conf
/etc/netns/lan/resolv.conf に上流DNSサーバーを書いて、lan内でgetent hosts archive.ubuntu.comが通るようにし、その結果を /root/nat/dns.txt に保存してください。
mkdir -p /etc/netns/lan; echo 'nameserver <상류>' > /etc/netns/lan/resolv.conf(プレースホルダーは上流サーバーのアドレスです)。上流のアドレスはresolvectl dns enp1s0で確認します。ネームスペース内には127.0.0.53のスタブがないので、本物のサーバーを直接書く必要があります。クエリはマスカレードを通って出ていきます。
インターネットに届く
lanからcurl -sI http://archive.ubuntu.com/を実行した応答ヘッダーを /root/nat/web.txt に保存してください。
ip netns exec lan curl -sI -m 10 http://archive.ubuntu.com/ > /root/nat/web.txt。1行目がHTTP/1.1 200 OKになっている必要があります。プライベートアドレスのホストが、インターネット上のサーバーとTCPを確立したのです。
数えてみる
nft list table ip labnatを /root/nat/counter.txt に保存してください。マスカレードのルールのcounter packetsが0より大きい必要があります。
ステップ3でcounterを入れてあるので、nft list table ip labnatにcounter packets N bytes M masqueradeが表示されます。Nは新しい接続の最初のパケット数です。以降のパケットはconntrackが処理するので、NATチェーンを再び通りません。
ポートフォワーディング
lan内に10.30.0.2:80のWebサーバーを起動し、ネームスペースguest(veth guest0 10.31.0.2/24 ↔ guest-r 10.31.0.1/24)を作成して、preroutingフックのチェーンのiifname "guest-r" tcp dport 8080 dnat to 10.30.0.2:80ルールで、guestからhttp://10.31.0.1:8080/が通るようにし、その応答の1行目を /root/nat/dnat.txt に保存してください。
サーバー: ip netns exec lan setsid python3 -m http.server 80 --bind 10.30.0.2 --directory /root/nat > /root/nat/http.log 2>&1 < /dev/null &。guestはステップ1と同じ方法(デフォルトルート10.31.0.1)で作ります。チェーン: nft 'add chain ip labnat pre { type nat hook prerouting priority -100; }'。確認: ip netns exec guest curl -sI http://10.31.0.1:8080/ | head -1。