← 一覧へ戻る

Claude が書いた記事

netns の島から外に出る ── 物理 NIC の出口で送信元を書き換える (NAT / masquerade)

bridge の中では ping が通るのに、なぜ外に出る瞬間だけ NAT が要るのか。netns の島から物理 NIC を通ってインターネットに出るまでを、root netns = ホストを主役に掘った学習ログ。

俺の最初の疑問

前回 (veth と bridge でコンテナに「線」を引く) で、red / blue / greenbr0(bridge) にぶら下げて、同じ Subnet で互いに ping が通るところまで来た。でもこの状態、よく見ると閉じた島や。bridge の内側で完結してて、物理 NIC の外(インターネット)には一歩も出られない。

そこで今回潰すのはこれ。

  1. bridge の中では ping が通るのに、なぜ外に出る瞬間だけ NAT が要るんや?
  2. いつ・どこで物理 NIC が出てくるんや?
  3. そもそも「ホスト」って何やねん? 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) の中の住人はデバイス (br0・eth0) だけ。子 netns (離れ) から veth で br0 に入り、ホスト内ルーティングで eth0 へ抜け、その出口で送信元が 192.168.1.50 に書き換わる (SNAT)。SNAT はルール、パケット札はパケットの状態で、どちらも netns の構成要素ではないので枠の外に出してある。

混乱しやすいポイント

① 「ホスト」とは root netns のこと

ずっと「ホスト本体」と呼んでたものの正体は、vibe を普通に起動したときに最初からいるデフォルトの network namespaceだ。これを root netns (init netns / default netns とも) と呼ぶ。

前回 red / blue / greenip 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 スイッチ) というだけで、eth0veth と同じ「中身」仲間。
  • 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 は汎用の接続追跡基盤で、その上に NATstateful firewall の両方が乗る。後者は運営者がもう手で触ってる ── Azure の NSG がステートフル (outbound を許可したら戻り inbound を自動で通す) なのの土台が、これと同じ思想や。「NSG の戻りが勝手に通る理由」と「SNAT の戻りが元に戻る理由」は、同じ conntrack という 1 個の台帳から出てる

⑤ br0 と eth0 は、放っといては繋がらない

同じ root netns (同じ部屋) にいるからといって、br0eth0 は自動ではつながらない。ここが前回 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 ↔ bluebr0 の中 (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 に出ない
SNATSource NAT。送信元アドレスを書き換える。外に出る netns の戻りを成立させる本体
masqueradeSNAT の特殊版。送信元を「出口 NIC が今持つ IP」に自動で合わせる。Docker / 家庭用ルータの定番
NAPT / PATポート番号も使って多重化する NAT。世間の「NAT」はほぼこれ。1 接続でポートを 1 個消費
conntrack接続追跡の台帳。NAT の戻りと stateful firewall (NSG の戻り自動許可) の共通の土台
ip_forward2 枚の足の間でパケットを転送する許可。0 だと他人宛を捨てる。ルータになるには 1
POSTROUTINGnetfilter のフック地点。「ルーティング後 = 出る直前」。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 のどこに化けるかが一気につながる。