TT Lab
はじめる
学ぶ 学習パス コース

ネットワーク基礎 — Linux VM で手を動かす

NAT でプライベート網をインターネットへ出す

TT Labで続きを見る

このラボは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装置が再起動するとすべての接続が切れます。「中からは通るのに外からは通らない」という報告の答えは、ほとんどいつもこの章にあります。

ステップ

  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にしてください。
  2. lanからuplinkのゲートウェイ10.0.2.1へのpingが通らない結果を、/root/nat/before.txt に保存してください。
  3. テーブルip labnatのpostroutingフックのチェーンに、10.30.0.0/24がuplinkへ出ていくときcounter masqueradeするルールを入れて、pingが通るようにしてください。
  4. uplinkとlan-rで同時にICMPをキャプチャしながらlanからpingを送り、外から見た送信元が書き換わっている様子を /root/nat/outside.txt に、内側の元の送信元を /root/nat/inside.txt に保存してください。
  5. /etc/netns/lan/resolv.conf に上流DNSサーバーを書いて、lan内でgetent hosts archive.ubuntu.comが通るようにし、その結果を /root/nat/dns.txt に保存してください。
  6. lanからcurl -sI http://archive.ubuntu.com/を実行した応答ヘッダーを /root/nat/web.txt に保存してください。
  7. nft list table ip labnatを /root/nat/counter.txt に保存してください。マスカレードのルールのcounter packetsが0より大きい必要があります。
  8. 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 に保存してください。

参考

プライベートネットワークのホスト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。