Network-Labo

シリーズ: 環境構築(1 / 1)

学習ラボの全体像と安全性: 隔離の考え方と、戻せる状態の作り方

この記事は、ネットワークの実験を行うラボの全体像と、安全に実験するための考え方を説明します。ラボは、Windows 上の VirtualBox で動かす AlmaLinux の VM です。実験のたびにファイアウォールや経路を変えるので、「ラボの通信が外に出ないこと」と「いつでも元に戻せること」が前提になります。

まずは最初に全体の流れをまとめます

確認できたことと、確認していないことを分けて書きます。確認していないことは、末尾の「要検証」にまとめました。この記事は、実験の記録(data/)の抜粋で説明します。個人名は、<WINDOWS_USER> のようなプレースホルダーに置き換えています。

1. ラボの構成: VM と、2 種類のネットワーク

ラボは、VirtualBox 7.1.10(ホストは Windows 11 Home)上の、AlmaLinux 9.8 の VM でできています。実験によって、使う VM が違います。

VM役割
client通信の送り手
server通信の受け手
switch実験の一部で使う(L2 スイッチの役)
router、router2ルーター(R1、R2)

静的ルートがないと、何が起きるか: 経路の欠落と、戻り経路の欠落を、ping と tcpdump で見分ける以降は、client、R1、R2、server の 4 台構成です(詳しくは E3 の記事で扱います)。

   client            R1 (router)           R2 (router2)          server
 +--------+        +------------+         +------------+        +--------+
 |        |--lan-a-|            |-transit-|            |--lan-b-|        |
 +--------+        +------------+         +------------+        +--------+
    ラボの通信は、この線(VirtualBox の内部ネットワーク)だけを通る

    各 VM の enp0s3 --(NAT)--> ホスト --> インターネット   (管理用。ラボの通信には使わない)

各 VM は、次の 2 種類のネットワークにつながります。

種類VirtualBox の設定用途
管理用NAT(enp0s3。10.0.2.15/24、既定ゲートウェイ 10.0.2.2)SSH、時刻同期、dnf。ホストからは、127.0.0.1 のポートフォワードで SSH に入る
ラボ用内部ネットワーク(intnet。lan-a、transit、lan-b など)実験の通信

ここで書いた構成の値は、リポジトリの構成の文書(docs/TOPOLOGY.md)に基づいています。実機の設定の確認は、E3 の記事で扱います。

2. 隔離の方針: 内部ネットワークだけを使う

2.1 VirtualBox の仕様(公式資料)

VirtualBox 7.1 の公式マニュアル「Introduction to Networking Modes」には、内部ネットワークについて次の説明があります。ここでは該当箇所を日本語に訳しています。

内部ネットワーク: 選択した仮想マシンには見えるが、ホスト上のアプリケーションや外の世界には見えない、ソフトウェアベースの別種のネットワークを作れます。

同じ節の表では、内部ネットワークで可能なのは「VM 同士の通信」のみで、「VM → ホスト」「ホスト → VM」「VM → 外部」「外部 → VM」は不可です。NAT では「VM → ホスト」と「VM → 外部」が可能で、「ホスト → VM」と「外部 → VM」にはポートフォワードが必要です。「VM 同士」の通信はできません。 VirtualBox 7.1 の公式ガイドの表から、Internal と NAT の行を示します(取得した要約からの再構成で、原文の逐語ではありません。列の順序は申告によります)。

ネットワークモードVM→HostVM←HostVM1↔VM2VM→Net/LANVM←Net/LAN
Internal––+––
NAT+Port forward–+Port forward

上の説明文も、公式ガイドの要約の記載と一致しました(照合記録)。

2.2 PoC-1 の設定の確認

最初のラボ(PoC-1。client と server の 2 台。E3 のラボとは配線が違い、ラボ用のアダプターは NIC2 でした。E3 のラボは NIC3 を使います)で、アダプターの設定を確認した記録があります(PowerShell の出力を貼り付けた転記で、取得時刻は不明です。記録は data/poc-1-almalinux-lan/isolation-host-config.txt)。

PS C:\Users\<WINDOWS_USER>> & "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" showvminfo "Alma9mini-cli" --machinereadable | Select-String "^nic[0-9]=|^intnet[0-9]="
⋮
nic1="nat"
intnet2="poc1-lan"
nic2="intnet"
nic3="none"
nic4="none"
nic5="none"
nic6="none"
nic7="none"
nic8="none"

出典: data/poc-1-almalinux-lan/isolation-host-config.txt#L5(抜粋 L5,L7-15、全 31 行のうち 10 行を表示、21 行を省略)

nic1 が NAT、nic2 が内部ネットワーク poc1-lan、nic3 以降は none です。ブリッジ(ホストの LAN に直接つなぐ設定)とホストオンリーのアダプターは、ありません。server も同じ設定でした(同じファイルの #L18 以降)。この 2 台の記録の NIC 名は、enp0s8 が内部ネットワーク(NIC2)です。E3 のラボの client と server では、内部ネットワークは enp0s9(NIC3)になります(E3 の 2.6)。隔離の考え方は、どちらの配線でも同じです。

2.3 WSL2 から VirtualBox を操作する準備

この連載の VirtualBox の操作は、WSL2 の bash から行います。WSL2 からは、Windows 側の VBoxManage.exe を、パスを指定して呼べます。ただし、パスに空白(Program Files)があり、毎回書くのは長いので、~/.bashrc に vb という関数を登録します(E2b で使った方法と同じです)。

作業の内容(対象: WSL2 の対話シェル、一般ユーザー):

  1. ~/.bashrc の末尾に、次の 1 行を足します(エディターで開くか、echo '…' >> ~/.bashrc で追記します。>> は追記で、> だと上書きして中身が消えるので、使いません)。
vb() { "/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe" "$@"; }
  1. 現在のシェルに読み込みます: source ~/.bashrc(新しい端末を開いても読み込まれます)。
  2. 動作を確認します: vb --version。

この記事の PoC-1 の記録(2.2 と 5.1)は、当時 PowerShell で実行した出力で、記録のままです(PS C:\…> で始まる行が、それです)。同じ引数を、WSL2 では vb に続けて書きます。

3. 確認: NAT を切っても、ラボの通信は続く

アダプターの設定が正しくても、実際に通信が分離されているかは別の問題です。PoC-1では、2 台の NAT のリンクを切断して、ラボの通信が内部ネットワークだけで続くかを確認しました。切断は VirtualBox の setlinkstate1 off で行いました(申告。コマンドの出力は記録がありません)。

VirtualBox の操作(対象: VM、WSL2 の bash、一般ユーザー。上述 の vb を使います): vb controlvm "<VM名>" setlinkstate1 off

その状態の client の記録です。

$ ip -br link
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP> 
enp0s3           DOWN           08:00:27:1f:fa:7c <NO-CARRIER,BROADCAST,MULTICAST,UP> 
enp0s8           UP             08:00:27:af:87:fe <BROADCAST,MULTICAST,UP,LOWER_UP> 
exit=0
$ ip -br addr
lo               UNKNOWN        127.0.0.1/8 ::1/128 
enp0s3           DOWN           
enp0s8           UP             192.168.10.10/24 
exit=0
$ ip route
192.168.10.0/24 dev enp0s8 proto kernel scope link src 192.168.10.10 metric 101 
exit=0

出典: data/poc-1-almalinux-lan/isolation-client.txt#L3-L4(抜粋 L3-4,L10,L15、全 40 行のうち 13 行を表示、27 行を省略)

enp0s3(NAT)は DOWN(NO-CARRIER)で、アドレスがなく、既定ルートもありません。経路は、内部ネットワークの 192.168.10.0/24 だけです。この状態で、client から server への ping は、4 回とも成功しました。

$ ping -c 4 -W 2 192.168.10.20
PING 192.168.10.20 (192.168.10.20) 56(84) bytes of data.
64 bytes from 192.168.10.20: icmp_seq=1 ttl=64 time=1.82 ms
64 bytes from 192.168.10.20: icmp_seq=2 ttl=64 time=1.70 ms
64 bytes from 192.168.10.20: icmp_seq=3 ttl=64 time=1.35 ms
64 bytes from 192.168.10.20: icmp_seq=4 ttl=64 time=1.20 ms

--- 192.168.10.20 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3007ms
rtt min/avg/max/mdev = 1.203/1.515/1.819/0.250 ms
exit=0

出典: data/poc-1-almalinux-lan/isolation-client.txt#L25-L35(抜粋 L25-35、全 40 行のうち 11 行を表示、29 行を省略)

NAT 側の 10.0.2.2 への ping は、失敗しました。

$ ping -c 2 -W 2 10.0.2.2
ping: connect: Network is unreachable
exit=2

出典: data/poc-1-almalinux-lan/isolation-client.txt#L37-L39(抜粋 L37-39、全 40 行のうち 3 行を表示、37 行を省略)

この結果から言えるのは、NAT を切っても、ラボの通信は続くことと、NAT 側への経路は、その時点でなかったことです。「内部ネットワークから外部へ出られないことの、直接の証明」ではありません。外へ出られない根拠は、主にアダプターの設定(2.2)と、VirtualBox の仕様(2.1)です。この確認の限界は、PoC-1 の README にも書かれています。

4. 失敗例: アダプターを分けても、通信が NAT 側へ出た

アダプターの設定が正しくても、VM の中の設定で、ラボの通信が NAT 側へ出ることがあります。静的ルートがないと、何が起きるか: 経路の欠落と、戻り経路の欠落を、ping と tcpdump で見分けるで起きた例です。

状況は、次のとおりです。R1(router)では、ファイアウォール(firewalld)のゾーンを使い、ラボ側の enp0s8、enp0s9 を internal に、NAT 側の enp0s3 を public に入れようとしました。しかし、enp0s8 が最初から public に入っていました。

ファイアウォールの設定(対象: R1 router、sudo、firewalld の実行時の設定):

$ sudo firewall-cmd --zone=internal --add-interface=enp0s8
Error: ZONE_CONFLICT: 'enp0s8' already bound to 'public'
exit=18
$ sudo firewall-cmd --get-active-zones
internal
  interfaces: enp0s9
public
  interfaces: enp0s3 enp0s8
exit=0

出典: data/exp-static-route/from-router/exp4-router-fw-zone-on-20261023T003629.txt#L3-L5(抜粋 L3-5,L11-16、全 17 行のうち 9 行を表示、8 行を省略)

--add-interface は、すでに別のゾーンにあるインターフェースではエラーになりました(ZONE_CONFLICT。この挙動は、firewalld のマニュアルに記載がなく、記録による観察です)。enp0s9 だけが internal に移り、enp0s8 は enp0s3 と同じ public に残りました。

その状態で、client から server への ping(192.168.31.10 → 192.168.32.20)を行い、R1 で tcpdump -i any を取りました。R1 には、192.168.32.0/24 の経路がなく、NAT の既定ルート(default via 10.0.2.2 dev enp0s3)があった、というのが実験の記録 data/exp-static-route/README.md の記述です(漏れた時点の ip route の出力の記録は、ありません)。

$ tcpdump -n -r exp4-router-20261023T004926.pcap
⋮
00:49:39.013697 enp0s8 In  IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 3, seq 1, length 64
00:49:39.013803 enp0s3 Out IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 3, seq 1, length 64
00:49:40.043212 enp0s8 In  IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 3, seq 2, length 64
00:49:40.043303 enp0s3 Out IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 3, seq 2, length 64
00:49:41.065708 enp0s8 In  IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 3, seq 3, length 64
00:49:41.065750 enp0s3 Out IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 3, seq 3, length 64

出典: data/exp-static-route/from-router/exp4-router-capture-20261023T004926.txt#L249-L254(抜粋 L249-254、全 256 行のうち 7 行を表示、249 行を省略)

client の echo request が、enp0s8 から入り、enp0s3(NAT 側)から出ています(3 回とも)。client の ping は、100% loss でした。応答は観測していません。

なぜ出たか、という理由は、候補です(未検証)。R1 に 192.168.32.0/24 への経路がなく、NAT の既定ルートに従って転送されたこと。そして、enp0s8 と enp0s3 が同じ public ゾーンだったため、ゾーン内の転送が許可されたことです。

対処と、確認できたこと

対処は、enp0s8 も internal へ移すことでした。--add-interface ではなく、--change-interface を使います(firewalld のマニュアルでは、--change-interface は、--remove-interface のあとに --add-interface を行うのと同じです。照合の記録の O10)。次の記録は、スクリプトを修正した後の、同じ 2026-10-23 の 02:44 の実行です。最初の対処を行ったときの実行記録は、個別のファイルにありません。

ファイアウォールの設定(対象: R1 router、sudo、実行時の設定。--permanent は付けない):

$ sudo firewall-cmd --zone=internal --change-interface=enp0s8
success
exit=0
$ sudo firewall-cmd --zone=internal --change-interface=enp0s9
success
exit=0
$ sudo firewall-cmd --get-active-zones
internal
  interfaces: enp0s8 enp0s9
public
  interfaces: enp0s3
exit=0

出典: data/exp-fault-ip/from-router/exp4-router-fw-zone-on-20261023T024435.txt#L3-L16(抜粋 L3-16、全 17 行のうち 12 行を表示、5 行を省略)

対処後、ラボの通信が NAT 側へ出なくなったかを、R1 の capture で見ました。

$ tcpdump -n -r exp4-router-20261023T011508.pcap
⋮
01:15:19.086099 enp0s8 In  IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 4, seq 1, length 64
01:15:19.086276 enp0s8 Out IP 192.168.31.1 > 192.168.31.10: ICMP net 192.168.32.20 unreachable, length 92
01:15:20.088838 enp0s8 In  IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 4, seq 2, length 64
01:15:20.088961 enp0s8 Out IP 192.168.31.1 > 192.168.31.10: ICMP net 192.168.32.20 unreachable, length 92
01:15:21.091182 enp0s8 In  IP 192.168.31.10 > 192.168.32.20: ICMP echo request, id 4, seq 3, length 64
01:15:21.091270 enp0s8 Out IP 192.168.31.1 > 192.168.31.10: ICMP net 192.168.32.20 unreachable, length 92

出典: data/exp-static-route/from-router/exp4-router-capture-20261023T011508.txt#L14-L19(抜粋 L14-19、全 23 行のうち 7 行を表示、16 行を省略)

echo request は enp0s8 から入りますが、enp0s3 から出る行はありません(R1 が net unreachable を返しています)。ただし、この結果を、ゾーンの修正だけの効果とは言い切れません。この capture の直前の 01:13:55 に、NAT の既定ルートも外していたためです(defroute off)。経路がなければ、転送されないのは当然だからです。

$ sudo ip route del default via 10.0.2.2 dev enp0s3
exit=0

出典: data/exp-static-route/from-router/exp4-router-defroute-off-20261023T011355.txt#L3-L4(抜粋 L3-4、全 11 行のうち 2 行を表示、9 行を省略)

さらに、外した既定ルートは、R2 で、外してから約 7 分後の確認の時点で復活していました(復活した時刻は不明。復活の原因は未検証で、候補は NetworkManager の DHCP リースの更新です)。既定ルートを一時的に外す方法は、確実ではありません。実験では、ゾーンの設定で隔離を担保することにしました。

2026-10-31 時点のゾーンの割り当ては、R1 と R2 で同じでした(確認記録)。

## [1] router(R1)のゾーン(読み取りのみ)
<WSL_USER>:~$ ssh -t -p 2225 user@127.0.0.1 'hostname -s; sudo firewall-cmd --get-active-zones; sudo firewall-cmd --zone=internal --list-all'
router
internal
  interfaces: enp0s8 enp0s9
public
  interfaces: enp0s3

出典: data/lab-state/user-supplied-20261031.txt#L5-L11(抜粋 L5-11、全 103 行のうち 7 行を表示、96 行を省略)

この失敗例から言えることは、次の 2 つです。

  1. 隔離は、アダプターの設定(VirtualBox 側)だけでなく、VM の中の設定(経路、ゾーン)にも依存する。
  2. 「NAT 側へ出ていないか」は、ルーターで tcpdump を取って確認できる(読み取りだけで行える)。この実験では、そのように確認した。

5. 戻せる状態を作る: スナップショットと、構成の再現

5.1 スナップショット

VM の状態を保存して、あとで戻す機能が、スナップショットです。PoC-1 では、client と server に poc1-ip-configured を取りました(転記。PowerShell の出力で、取得時刻は不明です)。

VirtualBox の操作(対象: VM、一般ユーザー)。次の記録は、PoC-1 で PowerShell から実行した出力です。WSL2 では、vb snapshot "<VM名>" take "<名前>" と書きます(E2b と E3 の記録に、WSL2 の vb snapshot … take の実行があります):

PS C:\Users\<WINDOWS_USER>> & "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" snapshot "Alma9mini-cli" take "poc1-ip-configured" --description "IP設定済み・時刻同期済み"
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Snapshot taken. UUID: db8f8727-1221-4bd5-bbeb-0ceed52f2c95

出典: data/poc-1-almalinux-lan/snapshot-and-reboot-raw.txt#L7-L9(抜粋 L7-9、全 102 行のうち 3 行を表示、99 行を省略)

複数の VM に、同名のスナップショットを一度に取るスクリプト(labs/common/snapshot-all.ps1)があります。PowerShell 用のスクリプトで、WSL2 で同じことをするスクリプトは、リポジトリにありません(baseline.sh、transfer.sh などには、.sh 版があります)。スクリプトの先頭の説明によると、次のように動きます。

復元の注意: スナップショットを復元すると、取得後に VM の中に作ったファイル(未回収の記録)は失われます(VirtualBox 7.1 のガイドに、現在の状態が失われ、仮想ハードディスクが元に戻るため、スナップショットの後に作ったファイルと、ファイルの変更が失われる、とあります。復元の前に、新しいスナップショットを取れば、現在の状態を失わずに済む、ともあります)。復元する前に、記録を回収してください。また、復元すると、VM の設定(メモリなど)も、取得時のものに戻ります。

スナップショットを取った後の、最初の起動が失敗することがありました(再起動すると成功。原因は未特定ですが一度に複数起動すると失敗することが多く一つずつ時間を置いて起動すると成功しました。

5.2 構成の再現: baseline.sh

ラボの構成(アドレス、経路、ゾーン)は、スクリプト labs/common/baseline.sh で、一括で作ったり外したりできます(up、down、status)。変更は、各 VM の実行時の設定だけで、永続設定は変えません。NAT(enp0s3)と SSH は変えません。

ラボの構築(対象: 4 台の VM、WSL2、一般ユーザー。sudo のパスワードを聞かれる): labs/common/baseline.sh up

up の後は、client から server への ping が成功し、ttl=62 になるのが基準です(ルーターを 2 台通るので、64 から 2 減ります)。記録の例です(2026-10-30)。

  ### $ ping -c 3 -W 2 192.168.32.20
  PING 192.168.32.20 (192.168.32.20) 56(84) bytes of data.
  64 bytes from 192.168.32.20: icmp_seq=1 ttl=62 time=7.22 ms
  64 bytes from 192.168.32.20: icmp_seq=2 ttl=62 time=3.64 ms
  64 bytes from 192.168.32.20: icmp_seq=3 ttl=62 time=3.41 ms

出典: data/baseline/baseline-sh-up-20261030T014404.txt#L24-L28(抜粋 L24-28、全 42 行のうち 5 行を表示、37 行を省略)

ラボの撤去(対象: 4 台の VM、WSL2、一般ユーザー。破壊的な操作): labs/common/baseline.sh down

6. まとめ

要検証(未確認・記録なし)

付録: 操作の一覧

操作対象・権限内部で実行されるコマンド状態変更復旧
NAT のリンクを切るWSL2(vb)、一般ユーザーvb controlvm "<VM>" setlinkstate1 offVM のアダプター 1 の状態が変わるsetlinkstate1 on
ゾーンの割り当てVM の中、sudofirewall-cmd --zone=internal --change-interface=<IF>実行時のゾーンの割り当てが変わるfirewall-cmd --reload(永続設定の状態に戻る。実行時の変更はすべて消える。ただし静的ルートがないと、何が起きるか: 経路の欠落と、戻り経路の欠落を、ping と tcpdump で見分けるの環境では、永続設定が enp0s8 を public にしていて、漏れた状態に戻ると書かれている(確認の記録なし)。新しい VM(2026-11-04)では、down の約 1 分後に、enp0s8、enp0s9 が public に入っていた(別の回では、表示されないこともあった。E3)。route-test.sh の fw-zone off の扱いは、静的ルートがないと、何が起きるか: 経路の欠落と、戻り経路の欠落を、ping と tcpdump で見分けるの観測)
既定ルートを外すVM の中、sudoip route del default via 10.0.2.2 dev enp0s3実行時の経路が変わる(NAT 経由の通信が止まる)ip route add default via 10.0.2.2 dev enp0s3 metric 100、または VM の再起動
スナップショットの取得WSL2(vb)、一般ユーザーvb snapshot "<VM>" take "<名前>"スナップショットが増えるvb snapshot "<VM>" delete "<名前>"
スナップショットの復元WSL2(vb)、VM は電源オフvb snapshot "<VM>" restore "<名前>"(この記事では実行例なし)取得後の VM の変更がすべて失われる復元の前に記録を回収する。復元の前に、現在の状態のスナップショットを取っておけば、現在の状態を失わずに済む(VirtualBox 7.1 のガイドによる)
複数 VM のスナップショットPowerShell(WSL2 用のスクリプトはない)、一般ユーザーlabs/common/snapshot-all.ps1(VM を ACPI 停止してから取得)VM が停止する。スナップショットが増える不要なスナップショットは削除する
ラボの構築WSL2、一般ユーザー(sudo)labs/common/baseline.sh up各 VM の実行時の設定が変わる(アドレス、NetworkManager の管理、ゾーン、ip_forward、guard)down、または VM の再起動
ラボの撤去(破壊的)WSL2、一般ユーザー(sudo)labs/common/baseline.sh down実行時の設定が消える(--reload、ip addr flush、ip_forward=0 など)。ラボの通信が止まるup、または VM の再起動