手を動かして学ぶネットワーク実験環境入門
これは、「フィヨルドブートキャンプ Part 2 Advent Calendar 2021」25日目の記事です。
- はじめに
- 自作するネットワーク環境の構成
- 仮想環境(Linux VM)の準備
- Network Namespaceを使ったネットワーク環境の構築
- [発展編]Dockerコンテナを使ったネットワーク環境の構築
- おわりに
- 参考資料
- 参考コード
はじめに
今年は、仮想環境のコンテナ周りを学習していましたが、仮想環境のコンテナを隔離する技術のNamespaceについて知ることができました。 そのNemespaceのうちの一つにNetwork Namespaceというコンテナのネットワーク環境を隔離するLinuxカーネルの機能があります。
ある日、このNetwork Namespaceを使ってTCP/IPのネットワークを手を動かして学ぶ方法が書いてある Linuxで手を動かして学ぶTCP/IPネットワーク入門 という 書籍を読んで実際にネットワークを作ってみました。すると、書籍を読んで理解した気になるのとは違って、実際に手を動かすことで、 ネットワークのパケットやルーターの動きなどをより深く理解できるようなりました。また、ネットワーク関連のコマンドやiptablesコマンドなどのLinuxコマンドにも 触ることになったので、Linuxを慣れ親しむ上でも有益になりましたし、ネットワーク技術を今後学ぶ上での知見(ヒント)を多少得ることができました。
この書籍では、ルーターやブリッジ、Source NATやDestination NATなどのネットワーク技術について、Network Namespaceを使って仮想ネットワークを作ることを通して、手を動かしながら学ぶことができます。そこで、これらの個別の要素を組み合わせることで、Dockerのデフォルトのbridgeネットワークのような仮想ネットワークを作る方法について共有したいと思います。
自作するネットワーク環境の構成
Linux VM上のDockerをインストールすると、decker0
という名称の仮想ブリッジが作成されます。
また、この時、複数のDockerコンテナを起動した場合、コンテナの仮想インターフェースがLinux VM上の仮想ブリッジに接続されます。
例えば、3つコンテナを起動してみます。
$ docker run -it -d --name c1 nicolaka/netshoot $ docker run -it -d --name c2 nicolaka/netshoot $ docker run -it -d --name c3 nicolaka/netshoot
そうすると、docker0
のブリッジ以外に vethc881c6a
、vethaaf049a
、veth6a8c5f9
といった仮想インターフェースを確認できます。
$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:87:21:22 brd ff:ff:ff:ff:ff:ff inet 192.168.64.24/24 brd 192.168.64.255 scope global dynamic enp0s1 valid_lft 63744sec preferred_lft 63744sec inet6 fda6:a15e:b806:8445:5054:ff:fe87:2122/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 2591952sec preferred_lft 604752sec inet6 fe80::5054:ff:fe87:2122/64 scope link valid_lft forever preferred_lft forever 35: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:e7:ab:b9:da brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:e7ff:feab:b9da/64 scope link valid_lft forever preferred_lft forever 81: vethc881c6a@if80: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 0e:48:32:47:5b:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::c48:32ff:fe47:5bab/64 scope link valid_lft forever preferred_lft forever 83: vethaaf049a@if82: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 2a:62:d0:50:13:ba brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::2862:d0ff:fe50:13ba/64 scope link valid_lft forever preferred_lft forever 85: veth6a8c5f9@if84: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether ca:a2:94:e1:5c:c6 brd ff:ff:ff:ff:ff:ff link-netnsid 2 inet6 fe80::c8a2:94ff:fee1:5cc6/64 scope link valid_lft forever preferred_lft forever
docker0の仮想ブリッジの設定を確認すると、これらの仮想インターフェースがブリッジに接続されているのが確認できます。
$ brctl show docker0 bridge name bridge id STP enabled interfaces docker0 8000.0242e7abb9da no veth6a8c5f9 vethaaf049a vethc881c6a
また、コンテナの仮想インターフェースのIPアドレスは、以下のコマンドで、172.17.0.2
、172.17.0.3
、172.17.0.4
が割り当てられています。
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' c1 172.17.0.2 $ docker inspect --format '{{ .NetworkSettings.IPAddress }}' c2 172.17.0.3 $ docker inspect --format '{{ .NetworkSettings.IPAddress }}' c3 172.17.0.4
このネットワーク環境の構成をネットワーク図にすると、以下のような感じになると思います。
172.17.0.0/16
がコンテナのセグメントで、192.168.64.0/24
がおそらくMacのホストとLinux VM間のセグメントになるかと思います。
このネットワーク図のrouterのホストに該当するのは、Linux VMで、wan
に該当するのがMacのホストを想定しています。
ここで、Multipassを使って、別のLinux VMを起動すると、192.168.64.0/24
のセグメント内のIPアドレス(例えば、192.168.64.21/24
)がLinux VMの
インターフェースに割り当てられます。これらのLinux VMもまた、bridge100
という名称の仮想ブリッジに接続されているのでしょう。
前置が長くなりましたが、上記のネットワーク図のような構成をNetwork Namespaceを使ってLinux VM上に構築して、ネットワークの学習をしたいというのが 本記事の目的になります。
実際に作成するネットワーク構成は以下になります。
br0
がdocker0
のような仮想ブリッジで、c1
、c2
、c3
がDockerコンテナのように仮想ブリッジに接続するホスト(Network Namespace)になります。
これらのc1
、c2
、c3
のホストからwan
のホストに通信するためには、router
のホストでiptablesを使ってSource NAT(IPマスカレード)を定義し、
プライベートアドレスからグローバルアドレスにIPアドレスの変換を行います。
参考までに、Dockerでは、iptablesのnatテーブルに 172.17.0.0/16
のSource NATが定義されています。
sudo iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere !localhost/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 anywhere Chain DOCKER (2 references) target prot opt source destination RETURN all -- anywhere anywhere
また、wan
側のホストからc2
のホストに対して通信するために、router
のホストでiptablesを使ってDestination NATを定義します。
同様に、Dockerで -p 80:8080
のようにポートを指定して起動した場合は、ホストの80ポートをc2
のホストの80ポートに転送する
Destination NATが定義されます。
$ docker run -it -d -p 80:80 --name c2 nicolaka/netshoot
$ sudo iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- anywhere !localhost/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 anywhere MASQUERADE tcp -- 172.17.0.3 172.17.0.3 tcp dpt:http-alt Chain DOCKER (2 references) target prot opt source destination RETURN all -- anywhere anywhere DNAT tcp -- anywhere anywhere tcp dpt:http to:172.17.0.3:8080
仮想環境(Linux VM)の準備
MacのLinux VM上でネットワーク環境の構築を行います。 Mac上でLinux VMを動かす方法としては、VirtualBoxを使った方法が有名だと思いますが、 Intel MacのmacOS Montereyで動作しなかったり、M1 Macに対応していないため、別の方法を 使うのが良いかと思います。
手軽にLinux VMを動作させる方法には、主にMultipass と Limaの2つがあります。今回は、私がよく使うMultipass を使った方法について書きます。
ちなみに、Mac上のLinux VMについては、以下の記事も参考になるかと思います。
Multipassを使った仮想環境の準備
HomeBrewでインストール
$ brew install --cask multipass
UbuntuのVMの起動
$ multipass launch 20.04 --name tcpip --mem 5GB --disk 50GB Launched: tcpip
UbuntuのVMにログイン
$ multipass shell tcpip Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic aarch64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sat Dec 25 13:45:11 JST 2021 System load: 0.22 Usage of /: 2.6% of 48.29GB Memory usage: 3% Swap usage: 0% Processes: 110 Users logged in: 0 IPv4 address for enp0s1: 192.168.64.24 IPv6 address for enp0s1: fda6:a15e:b806:8445:5054:ff:fe87:2122 0 updates can be applied immediately.
IPフォワーディングの有効化
$ sudo sysctl -w net.ipv4.ip_forward=1 net.ipv4.ip_forward = 1
Network Namespaceを使ったネットワーク環境の構築
Dockerのインストール
Dockerコンテナを活用するため、パッケージマネージャーのapt経由でDockerをインストールします。
$ sudo apt update $ sudo apt install docker.io
Docker用のパッケージインストール後に、sudo なしでdockerコマンドを実行できるように、ユーザーをdockerグループ に追加します。
$ sudo gpasswd -a $USER docker
追加後は、一旦設定を有効化するために、multipassのLinux VMからログアウトし、再ログインしてください。
スクリプトの構築手順
Network Namespaceの作成
前述した各ホストのNetwork Namespaceを作成します。
$ sudo ip netns add ns1 $ sudo ip netns add ns2 $ sudo ip netns add ns3 $ sudo ip netns add router $ sudo ip netns add wan
仮想インターフェースの作成
ペアの仮想インターフェースを作成します。
例えば、ns1
の仮想インターフェースns1-veth0
とns1-veth0
とペアになる仮想ブリッジbr0
に接続する仮想インターフェースns1-br
が
作成されます。
$ sudo ip link add ns1-veth0 type veth peer name ns1-br0 $ sudo ip link add ns2-veth0 type veth peer name ns2-br0 $ sudo ip link add ns3-veth0 type veth peer name ns3-br0 $ sudo ip link add gw-veth1 type veth peer name wan-veth0
仮想インターフェースをNetwork Namespaceに割り当て
作成したペアの仮想インターフェースを各ホストのNetwork Namespaceに設定します。
$ sudo ip link set ns1-veth0 netns ns1 $ sudo ip link set ns2-veth0 netns ns2 $ sudo ip link set ns3-veth0 netns ns3 $ sudo ip link set ns1-br0 netns router $ sudo ip link set ns2-br0 netns router $ sudo ip link set ns3-br0 netns router $ sudo ip link set gw-veth1 netns router $ sudo ip link set wan-veth0 netns wan
ブリッジの作成
routerのNetwork Namespace上に仮想ブリッジbr0
を作成します。
また、仮想ブリッジに、ns1
、ns2
、ns3
のペアとなる仮想インターフェースを割り当てます。
$ sudo ip netns exec router ip link add dev br0 type bridge $ sudo ip netns exec router ip link set ns1-br0 master br0 $ sudo ip netns exec router ip link set ns2-br0 master br0 $ sudo ip netns exec router ip link set ns3-br0 master br0
仮想インターフェースの有効化
デフォルトで仮想インターフェースのステータスがDOWNになっているため、ステータスをUPに変更します。
$ sudo ip netns exec ns1 ip link set ns1-veth0 up $ sudo ip netns exec ns1 ip link set lo up $ sudo ip netns exec ns2 ip link set ns2-veth0 up $ sudo ip netns exec ns2 ip link set lo up $ sudo ip netns exec ns3 ip link set ns3-veth0 up $ sudo ip netns exec ns3 ip link set lo up $ sudo ip netns exec router ip link set ns1-br0 up $ sudo ip netns exec router ip link set ns2-br0 up $ sudo ip netns exec router ip link set ns3-br0 up $ sudo ip netns exec router ip link set br0 up $ sudo ip netns exec router ip link set lo up $ sudo ip netns exec router ip link set gw-veth1 up $ sudo ip netns exec wan ip link set wan-veth0 up $ sudo ip netns exec wan ip link set lo up
仮想インターフェースにIPアドレスやルーティングの割り当て
各ホストのNetwork Namespaceの仮想インターフェースに対して、アドレスの設定を行います。
$ sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 $ sudo ip netns exec ns1 ip route add default via 192.0.2.254 $ sudo ip netns exec ns1 ip link set ns1-veth0 address 00:00:5E:00:53:01 $ sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0 $ sudo ip netns exec ns2 ip route add default via 192.0.2.254 $ sudo ip netns exec ns2 ip link set ns2-veth0 address 00:00:5E:00:53:02 $ sudo ip netns exec ns3 ip address add 192.0.2.3/24 dev ns3-veth0 $ sudo ip netns exec ns3 ip route add default via 192.0.2.254 $ sudo ip netns exec ns3 ip link set ns3-veth0 address 00:00:5E:00:53:03 $ sudo ip netns exec router ip address add 192.0.2.254/24 dev br0 $ sudo ip netns exec router ip address add 203.0.113.254/24 dev gw-veth1 $ sudo ip netns exec wan ip address add 203.0.113.1/24 dev wan-veth0 $ sudo ip netns exec wan ip route add default via 203.0.113.254
ルーターのIPフォワーディングの有効化
パケットの転送を行うために、routerとなるNetwork Namespace上で、net.ipv4.ip_forwardの
カーネルパラメータを1
に設定します。
$ sudo ip netns exec router sysctl net.ipv4.ip_forward=1
ルーターのループバックアドレスの転送設定の有効化
routerのNetwork Namespaceからlocalhost経由でホストに転送するために使用します。
$ sudo ip netns exec router sysctl net.ipv4.conf.all.route_localnet=1
ただ、正直この設定がよく理解できていません。
route_localnet - BOOLEAN Do not consider loopback addresses as martian source or destination while routing. This enables the use of 127/8 for local routing purposes. default FALSE
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
iptablesのSource NAT(IPマスカレード)の設定
routerのホスト側からwan側のネットワークにパケットを転送するために、Source NATを使って、 プライベートアドレスからグローバルアドレスにIPアドレスの変換を行います。
$ sudo ip netns exec router iptables -t nat \ -A POSTROUTING \ -s 192.0.2.0/24 \ -o gw-veth1 \ -j MASQUERADE
iptablesのDestination NATの設定
routerのwan側やlocalhostから内部のホストns2
の80ポートにパケットを転送するために、Destination NATを使って
IPアドレスの変換を行います。
$ sudo ip netns exec router iptables -t nat \ -A PREROUTING \ --dst 203.0.113.254 \ -p tcp \ --dport 80 \ -j DNAT \ --to-destination 192.0.2.2:80 $ sudo ip netns exec router iptables -t nat \ -A OUTPUT \ --dst 203.0.113.254 \ -p tcp \ --dport 80 \ -j DNAT \ --to-destination 192.0.2.2:80 $ sudo ip netns exec router iptables -t nat \ -A OUTPUT \ --dst 127.0.0.1 \ -p tcp \ --dport 80 \ -j DNAT \ --to-destination 192.0.2.2:80 $ sudo ip netns exec router iptables -t nat \ -A POSTROUTING \ -p tcp \ --dst 192.0.2.2 \ --dport 80 \ -j SNAT \ --to-source 203.0.113.254
スクリプトの動作検証
スクリプト全文 (シェルスクリプト)
create_docker_like_bridge_network.sh
というスクリプトとして保存します。
#!/usr/bin/env bash # Network Namespaceの作成 sudo ip netns add ns1 sudo ip netns add ns2 sudo ip netns add ns3 sudo ip netns add router sudo ip netns add wan # 仮想インターフェースをNetwork Namespaceに割り当て sudo ip link add ns1-veth0 type veth peer name ns1-br0 sudo ip link add ns2-veth0 type veth peer name ns2-br0 sudo ip link add ns3-veth0 type veth peer name ns3-br0 sudo ip link add gw-veth1 type veth peer name wan-veth0 # 仮想インターフェースをNetwork Namespaceに割り当て sudo ip link set ns1-veth0 netns ns1 sudo ip link set ns2-veth0 netns ns2 sudo ip link set ns3-veth0 netns ns3 sudo ip link set ns1-br0 netns router sudo ip link set ns2-br0 netns router sudo ip link set ns3-br0 netns router sudo ip link set gw-veth1 netns router sudo ip link set wan-veth0 netns wan # ブリッジの作成 sudo ip netns exec router ip link add dev br0 type bridge sudo ip netns exec router ip link set ns1-br0 master br0 sudo ip netns exec router ip link set ns2-br0 master br0 sudo ip netns exec router ip link set ns3-br0 master br0 # 仮想インターフェースの有効化 ## ns1 sudo ip netns exec ns1 ip link set ns1-veth0 up sudo ip netns exec ns1 ip link set lo up ## ns2 sudo ip netns exec ns2 ip link set ns2-veth0 up sudo ip netns exec ns2 ip link set lo up ## ns3 sudo ip netns exec ns3 ip link set ns3-veth0 up sudo ip netns exec ns3 ip link set lo up ## router sudo ip netns exec router ip link set ns1-br0 up sudo ip netns exec router ip link set ns2-br0 up sudo ip netns exec router ip link set ns3-br0 up sudo ip netns exec router ip link set br0 up sudo ip netns exec router ip link set lo up sudo ip netns exec router ip link set gw-veth1 up ## wan sudo ip netns exec wan ip link set wan-veth0 up sudo ip netns exec wan ip link set lo up # 仮想インターフェースにIPアドレスやルーティングの割り当て ## ns1 sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 sudo ip netns exec ns1 ip route add default via 192.0.2.254 sudo ip netns exec ns1 ip link set ns1-veth0 address 00:00:5E:00:53:01 ## ns2 sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0 sudo ip netns exec ns2 ip route add default via 192.0.2.254 sudo ip netns exec ns2 ip link set ns2-veth0 address 00:00:5E:00:53:02 ## ns3 sudo ip netns exec ns3 ip address add 192.0.2.3/24 dev ns3-veth0 sudo ip netns exec ns3 ip route add default via 192.0.2.254 sudo ip netns exec ns3 ip link set ns3-veth0 address 00:00:5E:00:53:03 ## router sudo ip netns exec router ip address add 192.0.2.254/24 dev br0 sudo ip netns exec router ip address add 203.0.113.254/24 dev gw-veth1 ## wan sudo ip netns exec wan ip address add 203.0.113.1/24 dev wan-veth0 sudo ip netns exec wan ip route add default via 203.0.113.254 # ルーターのIPフォワーディングの有効化 sudo ip netns exec router sysctl net.ipv4.ip_forward=1 # ルーターのループバックアドレスの転送設定の有効化 sudo ip netns exec router sysctl net.ipv4.conf.all.route_localnet=1 # iptablesのSource NAT(IPマスカレード)の設定 sudo ip netns exec router iptables -t nat \ -A POSTROUTING \ -s 192.0.2.0/24 \ -o gw-veth1 \ -j MASQUERADE # iptablesのDestination NATの設定 sudo ip netns exec router iptables -t nat \ -A PREROUTING \ --dst 203.0.113.254 \ -p tcp \ --dport 80 \ -j DNAT \ --to-destination 192.0.2.2:80 sudo ip netns exec router iptables -t nat \ -A OUTPUT \ --dst 203.0.113.254 \ -p tcp \ --dport 80 \ -j DNAT \ --to-destination 192.0.2.2:80 sudo ip netns exec router iptables -t nat \ -A OUTPUT \ --dst 127.0.0.1 \ -p tcp \ --dport 80 \ -j DNAT \ --to-destination 192.0.2.2:80 sudo ip netns exec router iptables -t nat \ -A POSTROUTING \ -p tcp \ --dst 192.0.2.2 \ --dport 80 \ -j SNAT \ --to-source 203.0.113.254
スクリプトの実行
作成済みの`create_docker_like_bridge_network.sh`を実行します。
$ chmod + x create_docker_like_bridge_network.sh $ ./create_docker_like_bridge_network.sh net.ipv4.ip_forward = 1 net.ipv4.conf.all.route_localnet = 1
作成済みのNetwork Namespaceの確認
$ ip netns ls wan (id: 4) router (id: 3) ns3 (id: 2) ns2 (id: 1) ns1 (id: 0)
ns1の確認
$ sudo ip netns exec ns1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 4: ns1-veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff link-netns router inet 192.0.2.1/24 scope global ns1-veth0 valid_lft forever preferred_lft forever inet6 fe80::8014:38ff:fea0:f294/64 scope link valid_lft forever preferred_lft forever $ sudo ip netns exec ns1 ip route default via 192.0.2.254 dev ns1-veth0 192.0.2.0/24 dev ns1-veth0 proto kernel scope link src 192.0.2.1
ns2の確認
$ sudo ip netns exec ns2 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 6: ns2-veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:02 brd ff:ff:ff:ff:ff:ff link-netns router inet 192.0.2.2/24 scope global ns2-veth0 valid_lft forever preferred_lft forever inet6 fe80::ecb7:c5ff:fe7a:ccad/64 scope link valid_lft forever preferred_lft forever $ sudo ip netns exec ns2 ip route default via 192.0.2.254 dev ns2-veth0 192.0.2.0/24 dev ns2-veth0 proto kernel scope link src 192.0.2.2
ns3の確認
$ sudo ip netns exec ns3 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 8: ns3-veth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:03 brd ff:ff:ff:ff:ff:ff link-netns router inet 192.0.2.3/24 scope global ns3-veth0 valid_lft forever preferred_lft forever inet6 fe80::e4f8:aeff:fed1:8039/64 scope link $ sudo ip netns exec ns3 ip route default via 192.0.2.254 dev ns3-veth0 192.0.2.0/24 dev ns3-veth0 proto kernel scope link src 192.0.2.3
routerの確認
$ sudo ip netns exec router ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 42:07:e4:03:4a:a1 brd ff:ff:ff:ff:ff:ff inet 192.0.2.254/24 scope global br0 valid_lft forever preferred_lft forever inet6 fe80::4007:e4ff:fe03:4aa1/64 scope link valid_lft forever preferred_lft forever 3: ns1-br0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000 link/ether be:4a:4e:40:6e:e1 brd ff:ff:ff:ff:ff:ff link-netns ns1 inet6 fe80::bc4a:4eff:fe40:6ee1/64 scope link valid_lft forever preferred_lft forever 5: ns2-br0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000 link/ether 42:07:e4:03:4a:a1 brd ff:ff:ff:ff:ff:ff link-netns ns2 inet6 fe80::4007:e4ff:fe03:4aa1/64 scope link valid_lft forever preferred_lft forever 7: ns3-br0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP group default qlen 1000 link/ether 8a:87:6d:1d:78:f5 brd ff:ff:ff:ff:ff:ff link-netns ns3 inet6 fe80::8887:6dff:fe1d:78f5/64 scope link valid_lft forever preferred_lft forever 10: gw-veth1@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 4e:2f:cb:84:ee:a2 brd ff:ff:ff:ff:ff:ff link-netns wan inet 203.0.113.254/24 scope global gw-veth1 valid_lft forever preferred_lft forever inet6 fe80::4c2f:cbff:fe84:eea2/64 scope link valid_lft forever preferred_lft forever $ sudo ip netns exec router ip route 192.0.2.0/24 dev br0 proto kernel scope link src 192.0.2.254 203.0.113.0/24 dev gw-veth1 proto kernel scope link src 203.0.113.254
wanの確認
$ sudo ip netns exec wan ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 9: wan-veth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 26:f9:80:46:ed:0e brd ff:ff:ff:ff:ff:ff link-netns router inet 203.0.113.1/24 scope global wan-veth0 valid_lft forever preferred_lft forever inet6 fe80::24f9:80ff:fe46:ed0e/64 scope link valid_lft forever preferred_lft forever $ sudo ip netns exec wan ip route default via 203.0.113.254 dev wan-veth0 203.0.113.0/24 dev wan-veth0 proto kernel scope link src 203.0.113.1
疎通確認
ローカルホストからwan側への疎通
$ sudo ip netns exec ns1 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.170 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.170/0.170/0.170/0.000 ms $ sudo ip netns exec ns2 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.103 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.103/0.103/0.103/0.000 ms $ sudo ip netns exec ns3 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.154 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.154/0.154/0.154/0.000 ms
wan側からホストへの疎通
wan側からホスト側のns2(192.0.2.2)への疎通ができているかを確認するために、 ns2のNetwork Namespaceに接続し、netcatコマンドで80番ポートを待ち受けしておきます。
$ sudo ip netns exec ns2 bash root@tcpip:/home/ubuntu# nc -lvn 80 Listening on 0.0.0.0 80
同様に、wan側のNetwork Namespaceに接続します。(別ターミナルを使います)
$ sudo ip netns exec wan curl 203.0.113.254
上記の実行後に、ns2の標準出力に、接続受信の出力が表示されたことを確認できます。
root@tcpip:/home/ubuntu# nc -lvn 80 Listening on 0.0.0.0 80 Connection received on 203.0.113.254 60020 GET / HTTP/1.1 Host: 203.0.113.254 User-Agent: curl/7.68.0 Accept: */*
また、routerのlocalhost経由のアクセスでも、ns2のホストにアクセスできることが確認できます。
$ sudo ip netns exec router curl localhost
$ root@tcpip:/home/ubuntu# nc -lvn 80 Listening on 0.0.0.0 80 Connection received on 203.0.113.254 36414 GET / HTTP/1.1 Host: localhost User-Agent: curl/7.68.0 Accept: */*
[発展編]Dockerコンテナを使ったネットワーク環境の構築
Network Namespaceを使ってネットワーク環境を構築することができました。 さらに、既存のDockerコンテナのNetwork Namespaceを 活用することで、Dockerコンテナをベースとしたネットワーク環境も構築することができます。
Dockerコンテナのネットワークを構築する方法として有名なのは、Docker Composeだと思います。 ただし、ネットワークの学習を目的とする上では、Namework NamespaceのコマンドをYAMLの設定ファイルに記述できる tinetという便利なツールを使うことができます。
tinetのリポジトリには以下のように書かれています。
TiNET is network emulator environment for network function developer, routing software developer and networking educator. this is very simple tool that generate just shell script to construct virtual network.
DeepLで翻訳すると、以下のようになります。ということで、ネットワーク屋の人以外にも、 ネットワーク学習の教育向けにも活用できるのかと思います。
TiNETは、ネットワーク機能開発者、ルーティングソフトウェア開発者、 ネットワーク教育者向けのネットワークエミュレータ環境です。
また、tinetの作者の方のツイートですが、参考になるかと思います。
ちなみに勉強のアプローチですが, 「network namespaceを生で叩いて, 仮想ネットワークを作ってみて, 色々な設定をする」というのが現時点での最良の勉強方法だと確信しています.
— slankdev (@slankdev) 2021年11月2日
ちなみにnetwork namespaceをいちいち作るのが大変なので我々のチームや, WIDEprojectの学生はこのtinetというツールを使ってその構築を効率化しています.https://t.co/78e8W5vkNz
— slankdev (@slankdev) 2021年11月2日
ホスト用のDockerコンテナのイメージ作成
各ネットワークユーティリティ用のコマンドをインストールしたコンテナイメージとnginxをインストールしたコンテナイメージの 2つを作成します。
ネットワークユーティリティ用のコマンドをインストールしたコンテナイメージ
Dockerfile_network
という名称のDockerfileを作成します。
FROM ubuntu RUN apt-get update && \ apt-get install -y iputils-ping && \ apt-get install -y iptables && \ apt-get install -y vim && \ apt-get install -y iproute2 && \ apt-get install -y tcpdump && \ apt-get install -y netcat && \ apt-get install -y curl
Dockerfileを指定し、netutilsという名称のコンテナイメージを作成します。
$ docker build -f Dockerfile_network -t netutils . Sending build context to Docker daemon 31.74kB Step 1/2 : FROM ubuntu ---> d5ca7a445605 Step 2/2 : RUN apt-get update && apt-get install -y iputils-ping && apt-get install -y iptables && apt-get install -y vim && apt-get install -y iproute2 && apt-get install -y tcpdump && apt-get install -y netcat && apt-get install -y curl
nginxをインストールしたコンテナイメージ
上記のコンテナイメージをベースに作成します。
ここでは、Dockerfile_nginx
という名称のDockerfileを作成します。
FROM netutils RUN apt-get update && \ apt-get install -y nginx RUN echo "Hello Docker nginx container!" > /var/www/html/index.nginx-debian.html ENTRYPOINT /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
上記設定により、コンテナ起動時にENTRYPOINT命令にて 80ポートでnginxが起動されます。
また、疎通確認のために、nginxのHTTPサーバが返すhtmlファイルに対して、
Hello Docker nginx container!
の文字列を返すように設定しています。
次に、Dockerfileを指定し、netutilsという名称のコンテナイメージを作成します。
$ docker build -f Dockerfile_nginx -t mynginx .
tinetのインストール
Intel Macを使っている場合
tinetのリポジトリのREADMEに記載のQuick Install
通りに実行するとインストールされます。
$ curl -Lo /usr/bin/tinet https://github.com/tinynetwork/tinet/releases/download/v0.0.2/tinet chmod +x /usr/bin/tinet tinet --version
M1 Macを使っている場合
READMEに記載のQuick Install
通りに実行しても、インストールに失敗します。おそらく curlでダウンロードしたバイナリが x86-64のアーキテクチャであるため
だと思います。
$ sudo file /usr/bin/tinet /usr/bin/tinet: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=jrOQWb6v99DQpWDhefnP/nkrgg-bbZ4nDD4NHSrOM/nEL8ZQgelBKEONQiNAIx/Ql1cZnv4FmBvVWzIiIFU, not stripped
そのため、READMEに記載のBuild
の手順に従ってビルドを実行します。
ただし、READMEに記載されているgolang:1.12
のバージョンでは、ビルドに失敗するため、
回避策として golang:1.17
を指定します。
$ git clone https://github.com/tinynetwork/tinet tinet && cd $_ $ docker run --rm -i -t -v $PWD:/v -w /v golang:1.17 go build $ sudo mv tinet /usr/local/bin
上記を実行すると、バイナリがarm64で作成されます。
$ sudo file /usr/local/bin/tinet /usr/local/bin/tinet: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, Go BuildID=LdJoSueuwc0karvXq8PX/cNk60_xkM-SFqx0DM3mt/a7XFmysuOm95ONK51OlB/kriiaVa9SJwzq5fFkrNt, not stripped
スクリプトの構築手順
Network Namespaceのネットワーク構築を使ったスクリプトを利用しますが、スクリプトの設定をtinetの設定ファイルの YAMLファイルに記述します。
この設定ファイルには、主に以下のような3つの設定項目があります
nodes 各ホストのDockerコンテナのイメージや仮想インターフェースを定義します。
node_configs 各ホスト上でコマンドを実行し、仮想インターフェースやルーティングテーブルなどを設定します。
test ネットワーク検証用のテストコマンドを記述します。
nodes項目の定義
各ホストのノードのコンテナイメージや仮想インターフェースを定義します。 ここでは、各ホストでnetuitlsのコンテナイメージを指定しますが、ns2のホストのみwan側からの疎通確認のために、 nginxをインインストール済みのmynginxのコンテナイメージを指定します。
nodes: - name: ns1 image: netutils interfaces: - { name: ns1-veth0, type: direct, args: router#ns1-br0 } - name: ns2 image: mynginx interfaces: - { name: ns2-veth0, type: direct, args: router#ns2-br0 } - name: ns3 image: netutils interfaces: - { name: ns3-veth0, type: direct, args: router#ns3-br0 } - name: router image: netutils interfaces: - { name: ns1-br0, type: direct, args: ns1#ns1-veth0 } - { name: ns2-br0, type: direct, args: ns2#ns2-veth0 } - { name: ns3-br0, type: direct, args: ns3#ns3-veth0 } - { name: gw-veth1, type: direct, args: wan#wan-veth0 } - name: wan image: netutils interfaces: - { name: wan-veth0, type: direct, args: router#gw-veth1 }
node_configs項目の定義
node_configs: - name: ns1 cmds: - cmd: ip addr add 192.0.2.1/24 dev ns1-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns1-veth0 address 00:00:5E:00:53:01 - name: ns2 cmds: - cmd: ip addr add 192.0.2.2/24 dev ns2-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns2-veth0 address 00:00:5E:00:53:02 - name: ns3 cmds: - cmd: ip addr add 192.0.2.3/24 dev ns3-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns3-veth0 address 00:00:5E:00:53:03 - name: router cmds: - cmd: ip link add dev br0 type bridge - cmd: ip link set br0 up - cmd: ip link set ns1-br0 master br0 - cmd: ip link set ns2-br0 master br0 - cmd: ip link set ns3-br0 master br0 - cmd: ip addr add 192.0.2.254/24 dev br0 - cmd: ip addr add 203.0.113.254/24 dev gw-veth1 - cmd: sysctl net.ipv4.ip_forward=1 - cmd: sysctl net.ipv4.conf.all.route_localnet=1 - cmd: >- iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE - cmd: >- iptables -t nat -A PREROUTING --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 127.0.0.1 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A POSTROUTING -p tcp --dst 192.0.2.2 --dport 80 -j SNAT --to-source 203.0.113.254 - name: wan cmds: - cmd: ip addr add 203.0.113.1/24 dev wan-veth0 - cmd: ip route add default via 203.0.113.254
test項目の定義
ここでは、単純にホスト側からwan側への疎通用のテストコマンドを定義しています。
test: cmds: - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns1 (192.0.2.1)" - cmd: echo "==========================================" - cmd: docker exec ns1 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns2 (192.0.2.2)" - cmd: echo "==========================================" - cmd: docker exec ns2 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns3 (192.0.2.3)" - cmd: echo "==========================================" - cmd: docker exec ns3 ping -c 1 203.0.113.1
スクリプトの動作検証
スクリプトの設定ファイル全文 (yaml)
docker_bridge_like_network_spec.yaml
という名称でスクリプトを実行します。
--- nodes: - name: ns1 image: netutils interfaces: - { name: ns1-veth0, type: direct, args: router#ns1-br0 } - name: ns2 image: mynginx interfaces: - { name: ns2-veth0, type: direct, args: router#ns2-br0 } - name: ns3 image: netutils interfaces: - { name: ns3-veth0, type: direct, args: router#ns3-br0 } - name: router image: netutils interfaces: - { name: ns1-br0, type: direct, args: ns1#ns1-veth0 } - { name: ns2-br0, type: direct, args: ns2#ns2-veth0 } - { name: ns3-br0, type: direct, args: ns3#ns3-veth0 } - { name: gw-veth1, type: direct, args: wan#wan-veth0 } - name: wan image: netutils interfaces: - { name: wan-veth0, type: direct, args: router#gw-veth1 } node_configs: - name: ns1 cmds: - cmd: ip addr add 192.0.2.1/24 dev ns1-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns1-veth0 address 00:00:5E:00:53:01 - name: ns2 cmds: - cmd: ip addr add 192.0.2.2/24 dev ns2-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns2-veth0 address 00:00:5E:00:53:02 - name: ns3 cmds: - cmd: ip addr add 192.0.2.3/24 dev ns3-veth0 - cmd: ip route add default via 192.0.2.254 - cmd: ip link set ns3-veth0 address 00:00:5E:00:53:03 - name: router cmds: - cmd: ip link add dev br0 type bridge - cmd: ip link set br0 up - cmd: ip link set ns1-br0 master br0 - cmd: ip link set ns2-br0 master br0 - cmd: ip link set ns3-br0 master br0 - cmd: ip addr add 192.0.2.254/24 dev br0 - cmd: ip addr add 203.0.113.254/24 dev gw-veth1 - cmd: sysctl net.ipv4.ip_forward=1 - cmd: sysctl net.ipv4.conf.all.route_localnet=1 - cmd: >- iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE - cmd: >- iptables -t nat -A PREROUTING --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A OUTPUT --dst 127.0.0.1 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 - cmd: >- iptables -t nat -A POSTROUTING -p tcp --dst 192.0.2.2 --dport 80 -j SNAT --to-source 203.0.113.254 - name: wan cmds: - cmd: ip addr add 203.0.113.1/24 dev wan-veth0 - cmd: ip route add default via 203.0.113.254 test: cmds: - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns1 (192.0.2.1)" - cmd: echo "==========================================" - cmd: docker exec ns1 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns2 (192.0.2.2)" - cmd: echo "==========================================" - cmd: docker exec ns2 ping -c 1 203.0.113.1 - cmd: echo "==========================================" - cmd: echo "Connectivity test from ns3 (192.0.2.3)" - cmd: echo "==========================================" - cmd: docker exec ns3 ping -c 1 203.0.113.1
スクリプトの実行
docker_bridge_like_network_spec.yaml
の設定ファイルを指定し、tinetのupコマンドとconfコマンドを実行します。
upコマンドはDockerコンテナの起動を行います。confコマンドは、起動中のDockerコンテナに対して仮想インターフェースの
設定などを行います。
これらを同時に実行するための upconf コマンドもあります。
tinet upコマンドの実行
upコマンドでコンテナを起動します。
$ tinet up -c docker_bridge_like_network_spec.yaml | sudo sh -x + docker run -td --net none --name ns1 --rm --privileged --hostname ns1 -v /tmp/tinet:/tinet netutils + mkdir -p /var/run/netns + docker inspect ns1 --format {{.State.Pid}} + PID=24154 + ln -s /proc/24154/ns/net /var/run/netns/ns1 + docker run -td --net none --name ns2 --rm --privileged --hostname ns2 -v /tmp/tinet:/tinet mynginx + mkdir -p /var/run/netns + docker inspect ns2 --format {{.State.Pid}} + PID=24233 + ln -s /proc/24233/ns/net /var/run/netns/ns2 + docker run -td --net none --name ns3 --rm --privileged --hostname ns3 -v /tmp/tinet:/tinet netutils + mkdir -p /var/run/netns + docker inspect ns3 --format {{.State.Pid}} + PID=24311 + ln -s /proc/24311/ns/net /var/run/netns/ns3 + docker run -td --net none --name router --rm --privileged --hostname router -v /tmp/tinet:/tinet netutils + mkdir -p /var/run/netns + docker inspect router --format {{.State.Pid}} + PID=24387 + ln -s /proc/24387/ns/net /var/run/netns/router + docker run -td --net none --name wan --rm --privileged --hostname wan -v /tmp/tinet:/tinet netutils + mkdir -p /var/run/netns + docker inspect wan --format {{.State.Pid}} + PID=24463 + ln -s /proc/24463/ns/net /var/run/netns/wan + ip link add ns1-veth0 netns ns1 type veth peer name ns1-br0 netns router + ip netns exec ns1 ip link set ns1-veth0 up + ip netns exec router ip link set ns1-br0 up + ip link add ns2-veth0 netns ns2 type veth peer name ns2-br0 netns router + ip netns exec ns2 ip link set ns2-veth0 up + ip netns exec router ip link set ns2-br0 up + ip link add ns3-veth0 netns ns3 type veth peer name ns3-br0 netns router + ip netns exec ns3 ip link set ns3-veth0 up + ip netns exec router ip link set ns3-br0 up + ip link add gw-veth1 netns router type veth peer name wan-veth0 netns wan + ip netns exec router ip link set gw-veth1 up + ip netns exec wan ip link set wan-veth0 up + ip netns del ns1 + ip netns del ns2 + ip netns del ns3 + ip netns del router + ip netns del wan
tinet confコマンドの実行
起動中のコンテナに対して、仮想インターフェースのアドレスやルーティングの設定などを行います。
$ tinet conf -c docker_bridge_like_network_spec.yaml | sudo sh -x + docker exec ns1 ip addr add 192.0.2.1/24 dev ns1-veth0 + docker exec ns1 ip route add default via 192.0.2.254 + docker exec ns1 ip link set ns1-veth0 address 00:00:5E:00:53:01 + docker exec ns2 ip addr add 192.0.2.2/24 dev ns2-veth0 + docker exec ns2 ip route add default via 192.0.2.254 + docker exec ns2 ip link set ns2-veth0 address 00:00:5E:00:53:02 + docker exec ns3 ip addr add 192.0.2.3/24 dev ns3-veth0 + docker exec ns3 ip route add default via 192.0.2.254 + docker exec ns3 ip link set ns3-veth0 address 00:00:5E:00:53:03 + docker exec router ip link add dev br0 type bridge + docker exec router ip link set br0 up + docker exec router ip link set ns1-br0 master br0 + docker exec router ip link set ns2-br0 master br0 + docker exec router ip link set ns3-br0 master br0 + docker exec router ip addr add 192.0.2.254/24 dev br0 + docker exec router ip addr add 203.0.113.254/24 dev gw-veth1 + docker exec router sysctl net.ipv4.ip_forward=1 + docker exec router sysctl net.ipv4.conf.all.route_localnet=1 + docker exec router iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE + docker exec router iptables -t nat -A PREROUTING --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 + docker exec router iptables -t nat -A OUTPUT --dst 203.0.113.254 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 + docker exec router iptables -t nat -A OUTPUT --dst 127.0.0.1 -p tcp --dport 80 -j DNAT --to-destination 192.0.2.2:80 + docker exec router iptables -t nat -A POSTROUTING -p tcp --dst 192.0.2.2 --dport 80 -j SNAT --to-source 203.0.113.254 + docker exec wan ip addr add 203.0.113.1/24 dev wan-veth0 + docker exec wan ip route add default via 203.0.113.254
作成済みのDockerコンテナの確認
docker ps
コマンドを実行すると、各コンテナが起動していることを確認できます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7cfd223d072b netutils "bash" 3 minutes ago Up 3 minutes wan 898004063e9a netutils "bash" 3 minutes ago Up 3 minutes router 6f3a6412d755 netutils "bash" 3 minutes ago Up 3 minutes ns3 39da406646fc mynginx "/bin/sh -c '/usr/sb…" 3 minutes ago Up 3 minutes ns2 e5160a7057f6 netutils "bash" 3 minutes ago Up 3 minutes ns1
ns1の確認
$ docker exec -it ns1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: ns1-veth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:01 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.0.2.1/24 scope global ns1-veth0 valid_lft forever preferred_lft forever $ docker exec -it ns1 ip route default via 192.0.2.254 dev ns1-veth0 192.0.2.0/24 dev ns1-veth0 proto kernel scope link src 192.0.2.1
ns2の確認
$ docker exec -it ns2 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: ns2-veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.0.2.2/24 scope global ns2-veth0 valid_lft forever preferred_lft forever $ docker exec -it ns2 ip route default via 192.0.2.254 dev ns2-veth0 192.0.2.0/24 dev ns2-veth0 proto kernel scope link src 192.0.2.2
ns3の確認
$ docker exec -it ns3 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: ns3-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.0.2.3/24 scope global ns3-veth0 valid_lft forever preferred_lft forever $ docker exec -it ns3 ip route default via 192.0.2.254 dev ns3-veth0 192.0.2.0/24 dev ns3-veth0 proto kernel scope link src 192.0.2.3
routerの確認
$ docker exec -it ns3 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: ns3-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:00:5e:00:53:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.0.2.3/24 scope global ns3-veth0 valid_lft forever preferred_lft forever $ docker exec -it ns3 ip route default via 192.0.2.254 dev ns3-veth0 192.0.2.0/24 dev ns3-veth0 proto kernel scope link src 192.0.2.3
wanの確認
$ docker exec -it wan ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: wan-veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether ce:11:e6:78:60:57 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 203.0.113.1/24 scope global wan-veth0 valid_lft forever preferred_lft forever $ docker exec -it wan ip route default via 203.0.113.254 dev wan-veth0 203.0.113.0/24 dev wan-veth0 proto kernel scope link src 203.0.113.1
疎通確認
ローカルホストからwan側への疎通
$ docker exec -i ns1 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.070 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.070/0.070/0.070/0.000 ms $ docker exec -i ns2 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.065 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.065/0.065/0.065/0.000 ms docker exec -i ns3 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.063 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.063/0.063/0.063/0.000 ms
tinet testを使っても検証できます。
tinet test -c docker_bridge_like_network_spec.yaml | sudo sh -x + echo ========================================== ========================================== + echo Connectivity test from ns1 (192.0.2.1) Connectivity test from ns1 (192.0.2.1) + echo ========================================== ========================================== + docker exec ns1 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.058 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms + echo ========================================== ========================================== + echo Connectivity test from ns2 (192.0.2.2) Connectivity test from ns2 (192.0.2.2) + echo ========================================== ========================================== + docker exec ns2 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.055 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.055/0.055/0.055/0.000 ms + echo ========================================== ========================================== + echo Connectivity test from ns3 (192.0.2.3) Connectivity test from ns3 (192.0.2.3) + echo ========================================== ========================================== + docker exec ns3 ping -c 1 203.0.113.1 PING 203.0.113.1 (203.0.113.1) 56(84) bytes of data. 64 bytes from 203.0.113.1: icmp_seq=1 ttl=63 time=0.058 ms --- 203.0.113.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms
wan側からホストへの疎通
wan側からホスト側のns2(192.0.2.2)への疎通ができているかを確認するために、
wan側から 203.0.113.254
に対して、curlコマンドを実行します。
$ docker exec -i wan curl 203.0.113.254 Hello Docker nginx container!
上記コマンドで、ns2のコンテナ上で起動しているnginxからHTMLのレスポンスが返ってきたことを確認できます。 同様に、routerのコンテナ上からlocalhostに対してcurlコマンドを実行すると同様の結果が確認できます。
$ docker exec -i router curl localhost Hello Docker nginx container!
おわりに
いかがだったでしょうか(笑)。 Linuxで手を動かして学ぶTCP/IPネットワーク入門 という書籍で作りながら学んだブリッジやNATなどの学習した個別の要素を組み合わせて、Dockerのようなブリッジネットワークを作ってみるのは面白そうだなと思い、この記事のテーマを思いつきました。
実際に作ってみることで、Dockerの裏側の仕組みや便利さをより一層実感できるようになりました。
また、Network NamespaceやDockerコンテナを活用して、手を動かしながら仮想ネットワークを構築することで、 書籍で理論を学ぶことと異なり、ネットワーク技術についてより深く理解できるようになった気がしています。
今後は、アプリケーションレベルでこのようなネットワーク実験環境を活用できないか模索したいともいます。 例えば、Web配信の技術の書籍に記載されているようなHTTPキャッシュ・リバースプロキシなどの動作も手元のローカル環境で手軽に学習できたらいいなと思います。
最後に、M1 MaxをMacBookも入手したということもあり(笑)、仮想環境を存分に使いこなしつつ、今後も作って壊してを高速に回して楽しんで学習していきたいと思います。
参考資料
ネットワークを学ぶ上で参考になる資料(読んで面白かった書籍など)もあわせて載せておきます。
Dockerのブリッジネットワークの構成については、以下の記事がめちゃくちゃ分かりやすいので、ぜひ読んでほしいと思います。
Dockerのiptabesの仕組みについては、Software Desigin 2021年9月号とSoftware Desigin 2021年10月号の体系的に学ぶDockerネットワークのしくみ
のiptablesの回が参考
になるかと思います。
tinetを使う上では、以下のYouTube動画やtinetのリポジトリ上のexamplesにある設定例が参考になるかと思います。