Claude が書いた記事
netns の島から外に出る ── 物理 NIC の出口で送信元を書き換える (NAT / masquerade)
bridge の中では ping が通るのに、なぜ外に出る瞬間だけ NAT が要るのか。netns の島から物理 NIC を通ってインターネットに出るまでを、root netns = ホストを主役に掘った学習ログ。
俺の最初の疑問
前回 (veth と bridge でコンテナに「線」を引く) で、red / blue / green を br0(bridge) にぶら下げて、同じ Subnet で互いに ping が通るところまで来た。でもこの状態、よく見ると閉じた島や。bridge の内側で完結してて、物理 NIC の外(インターネット)には一歩も出られない。
そこで今回潰すのはこれ。
- bridge の中では ping が通るのに、なぜ外に出る瞬間だけ NAT が要るんや?
- いつ・どこで物理 NIC が出てくるんや?
- そもそも「ホスト」って何やねん? br0 や eth0 は何者なんや?
まず一言でいうと
- 島から外に出るには、root netns = ホストが「2 枚 NIC のルータ」になる。内の足
br0と外の足eth0(物理 NIC) を持ち、その間を転送する。 - NAT が起きるのは、物理 NIC (eth0) から出るただ 1 点。送信元アドレスを
eth0の IP に書き換える (SNAT / masquerade)。 - これは Azure の NAT Gateway / LB の SNAT がやってる仕事を、Linux マシン 1 台の中で手回しクランクで再現してるだけ。
なぜ NAT が要るのか ── 効くのは「行き」やなく「戻り」
最初「同じアドレスレンジを使ってたら相互通信できへんから」と考えた。半分正解や。でも「相互通信できへん」を一段ほどくと、行きと戻りで顔が違う。
- 行き:物理 NIC からパケットを出すこと自体はできる。止められはせん。
- 戻り:相手サーバが返事を書こうとした瞬間に詰む。送信元が
10.0.0.2のままやと、相手は「世界中にある 10.0.0.2 のどれやねん」となって返せない。
private IP (10.x や 192.168.x) は世界中の家庭・netns が使い回してるから、グローバルでは一意やない。だから外に出る瞬間、送信元を「世界で一意な住所」=ホストの public IP に書き換える。これが SNAT (Source NAT) の本体や。NAT は「行きを通す」ためより、「戻ってこれるようにする」ためにある。
戻りはどう振り分ける? ── L4 のポート番号 (NAPT)
送信元を全部ホストの public IP に書き換えると、外からの返事は red の分も blue の分もまとめて「ホストの public IP 宛」で帰ってくる。これをどう見分けて元の netns に戻すか ── L4 のポート番号で多重化する。これが NAPT (世間で「NAT」と呼ぶのはほぼこれ)。
red 10.0.0.2:12345 → ホスト public IP : 40001
blue 10.0.0.3:12345 → ホスト public IP : 40002
ホストは「40001 はさっきの red の接続」という対応表を持ち、public IP : 40001 宛の返事を逆引きして 10.0.0.2:12345 に戻す。
図で見る
混乱しやすいポイント
① 「ホスト」とは root netns のこと
ずっと「ホスト本体」と呼んでたものの正体は、vibe を普通に起動したときに最初からいるデフォルトの network namespaceだ。これを root netns (init netns / default netns とも) と呼ぶ。
前回 red / blue / green は ip netns add で後から作った netns やった。それに対して ホスト = 何も作らなくても元からある親玉の netns。つまり特別な「4 番目の箱」やない。子 netns と同じ仲間で、ただ “最初からある親” というだけ。証拠に、ip netns list を打つと red/blue/green は出るけど root netns は出てこない。「名前を持たない、デフォルトの居場所」やからや。
そして物理 NIC (eth0) も br0 も、この root netns に属してる。veth を作ったとき、片割れだけを子 netns に移して、もう片割れと br0・eth0 は root netns に残る。だから root netns だけが「内の足 (br0)」と「外の足 (eth0)」の両方が見える → 2 枚 NIC のルータになれる。
② 登場人物は 3 レイヤーに分かれる
「br0 は namespace か?」「SNAT は何者か?」で詰まったら、3 レイヤーに分けると一発で片づく。
| レイヤー | 何者 | 今回の例 |
|---|---|---|
| 入れ物 | 景色を仕切る箱・部屋 | netns (root / red / blue / green) |
| 中身(装置) | 部屋に置く物 | eth0(物理 NIC), veth(仮想 NIC), br0(仮想スイッチ), lo |
| 処理(ルール) | パケットへの加工指示 | SNAT / masquerade, フィルタ, forward 判定 |
br0は namespace やなくデバイス。種類が bridge (仮想 L2 スイッチ) というだけで、eth0やvethと同じ「中身」仲間。- SNAT / masquerade は namespace でもデバイスでもなくルール。「eth0 から出る瞬間に送信元を書き換えろ」という netfilter (iptables / nftables) の加工指示で、物やなく動作。だから図でも root netns の枠の外に出してある。
③ masquerade は SNAT の特殊版
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# nat テーブル 出る直前 eth0 から 送信元を eth0 の IP に
- SNAT = 送信元を指定した IP に書き換えろ(例:
192.168.1.50と決め打ち) - masquerade = 送信元を、出ていく NIC (eth0) が今持ってる IP に自動で合わせて書き換えろ
eth0 の IP をハードコードせず「出口の NIC のに勝手に合わせる」のが masquerade。DHCP で IP が変わる環境 (家庭用ルータ, Docker) で楽やからこれが定番。名前の由来そのままで、masquerade = 仮面舞踏会。正体 (private IP) を隠して eth0 の仮面 (public IP) を被って外に出る。キモは POSTROUTING =「ルーティング後 = 出る直前」というフック地点。SNAT は場所というより、パケットの旅の中のタイミング (出ていく寸前) に仕掛けた加工や。
④ conntrack ── 戻りを覚える台帳
書き換えた変換を「戻り用に覚えておく」のが conntrack (connection tracking)。今どんな接続が通過中かを 1 接続 1 行で覚えてるカーネルの台帳だ。行きの 1 個目で変換を刻み、戻りを逆引きして元の netns に返す。これが NAT が状態を持つ (stateful) 理由。
効くのは「NAT 専用やない」とこ。conntrack は汎用の接続追跡基盤で、その上に NAT と stateful firewall の両方が乗る。後者は運営者がもう手で触ってる ── Azure の NSG がステートフル (outbound を許可したら戻り inbound を自動で通す) なのの土台が、これと同じ思想や。「NSG の戻りが勝手に通る理由」と「SNAT の戻りが元に戻る理由」は、同じ conntrack という 1 個の台帳から出てる。
⑤ br0 と eth0 は、放っといては繋がらない
同じ root netns (同じ部屋) にいるからといって、br0 と eth0 は自動ではつながらない。ここが前回 red↔blue が設定なしで通ったのと差が出るとこや。
まず誤解を 1 個潰すと、今回の構成では eth0 は br0 に挿さってない。br0(10.0.0.1) と eth0(192.168.1.50) は別々のサブネットを向いた独立した 2 枚の足で、L2 (スイッチの自動転送) では繋がってない。両者を橋渡しするのはホストの L3 ルーティング ──「br0 で受けたパケットを、自分宛やなくても eth0 へ転送する」動作で、これがデフォルトではオフ (net.ipv4.ip_forward = 0)。このままやとホストは「自分宛やない他人宛」のパケットを捨てるので、eth0 まで届かない。
sysctl -w net.ipv4.ip_forward=1 # 2 枚の足の間で転送する「ルータ」になる
| 経路 | レイヤー | 放っといて通る? | |
|---|---|---|---|
red ↔ blue | br0 の中 (veth 同士) | L2 (br0 がスイッチとして自動転送) | 通る (前回これ) |
red → 外 | br0 → eth0 (足を跨ぐ) | L3 (ルーティング + 転送) | 通らない (ip_forward=1 が要る) |
前回 red↔blue が何もせず通ったのは同じ br0 にぶら下がった L2 の世界やったから。外行きは br0 と eth0 という別インターフェースを跨ぐ L3 の話なので、スイッチの自動転送は効かず、ルータ機能 (転送許可) が要る。
たとえ話
- root netns = 母屋、子 netns = 離れ。母屋は元から建ってて、外の道路 (eth0) に面した玄関を持つ。離れは道路に直接面してないので、外に出るには必ず母屋を通る → 母屋の玄関 (eth0 の出口) で名札が書き換わる (SNAT)。Docker のコンテナが外に出るとき必ずホストで SNAT されるのは、この「離れは母屋を通るしかない」構造やから。
- br0 = タコ足タップ。挿さってる機器 (veth) 同士はタップが勝手に通す (L2)。eth0 = 玄関の扉。タップには挿さってない別物。タップと扉の間でモノを運ぶには間に立つ運び屋 (
ip_forward) が要る。 - conntrack = 玄関の係員が切る番号札の台帳。名札を書き換えて (SNAT) 送り出すとき番号札を控え、返事が帰ってきたら番号札で照合して元の持ち主に返す。
出てきた言葉の変換表
| 出てきた言葉 | つまり何の話? |
|---|---|
| root netns | ホスト本体。最初からある親の netns。物理 NIC や br0 が属する。ip netns list に出ない |
| SNAT | Source NAT。送信元アドレスを書き換える。外に出る netns の戻りを成立させる本体 |
| masquerade | SNAT の特殊版。送信元を「出口 NIC が今持つ IP」に自動で合わせる。Docker / 家庭用ルータの定番 |
| NAPT / PAT | ポート番号も使って多重化する NAT。世間の「NAT」はほぼこれ。1 接続でポートを 1 個消費 |
| conntrack | 接続追跡の台帳。NAT の戻りと stateful firewall (NSG の戻り自動許可) の共通の土台 |
ip_forward | 2 枚の足の間でパケットを転送する許可。0 だと他人宛を捨てる。ルータになるには 1 |
| POSTROUTING | netfilter のフック地点。「ルーティング後 = 出る直前」。masquerade はここで効く |
| SNAT ポート枯渇 | 同じ public IP で outbound を張りすぎ、ポートを使い切って新規接続が張れない状態 |
今回で「島から外洋に出る」配線まで底が見えた。残りは、この上にネットワーク屋の本領 (Azure の守備範囲) を足していく段。
- 別 Subnet 同士を router netns でつなぐ ── netns を 1 個ルータ役にして別 Subnet を中継する。Azure の UDR (ユーザー定義ルート) を手で組むのと同じ構造。
- パケットフィルタ / ACL を挟む ── netns 間の通信を
iptables/nftablesで許可・拒否する。今回出てきた conntrack の応用で、NSG の「ルール評価順」「戻り通信の自動許可」を生で 1 回触ると腹に落ちる。次はこれが効くと思う。 - Pod の IP が 1 個しかない理由 ── 「Pod = 複数コンテナで network namespace を 1 個共有する単位」を pause コンテナで追うと、今回の root netns / veth / NAT が k8s のどこに化けるかが一気につながる。