NameSpace 機能を使ったネットワークからインターネットに出れなかった話

コンテナのネットワーク周りを理解するためにカーネルの NameSpace 機能を使って Ubuntu20.04 サーバ上に仮想的なネットワークを構築してみた.その際,作成したネットワークからインターネットに出るのに四苦八苦したのでメモとして残しておく.この記事では,ネットワークの構築の詳細は話さず構築後のトラブルシュートをメインとする.

目次

概要

NameSpace 機能を使ったコンテナネットワークの実験をした.同じような実験についてはインターネットにいくつか記事があるが,今回は以下の記事を参考にした.

christina04.hatenablog.com

christina04.hatenablog.com

構築した環境は図のようになっている.

f:id:a-mochan:20211123122417p:plain
全体のネットワーク

上記ネットワークを構築するコマンドをまとめておいた.もしよければどうぞ.

クリックすると展開されます

#!/bin/bash

ip netns add host1
ip netns add host2
ip netns exec host1 ip link set lo up
ip netns exec host2 ip link set lo up
ip link add name veth1 type veth peer name br-veth1
ip link add name host1 type veth peer name br-host1
ip link add name host2 type veth peer name br-host2
ip link set host1 netns host1
ip link set host2 netns host2
ip link add br0 type bridge
ip link set dev br-veth1 master br0
ip link set dev br-host1 master br0
ip link set dev br-host2 master br0
ip addr add 10.0.0.100/24 dev veth1
ip netns exec host1 ip addr add 10.0.0.1/24 dev host1
ip netns exec host2 ip addr add 10.0.0.2/24 dev host2
ip netns exec host1 ip link set host1 up
ip netns exec host2 ip link set host2 up
ip link set veth1 up
ip link set br-veth1 up
ip link set br-host1 up
ip link set br-host2 up
ip link set br0 up

echo 1 > /proc/sys/net/ipv4/ip_forward
ip netns exec host1 ip route add default via 10.0.0.100
ip netns exec host2 ip route add default via 10.0.0.100

また,環境としては EC2 の Ubuntu20.04 を使ったので環境構築として以下の記事も参考にさせていただいた.また,EC2 の設定としてインターネットと通信できる状態にしておくことが前提となるのでご注意ください.

dev.classmethod.jp

こちらも Ubuntu で実行できるようコマンドをまとめておいた.

クリックすると展開されます

#!/bin/bash

apt-get update
apt-get install -y apt-transport-https ca-certificates curl software-properties-common jq
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get install -y docker-ce cgdb cgroup-tools uuid-runtime tree iputils-ping make gcc
git clone git://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git /usr/src/libcap
cd /usr/src/libcap
make
make install

NameSpace を使って隔離したネットワークからインターネットへ出ていけない

先に紹介した記事 にもある通り,NameSpace 機能で作成した Host1 や Host2 からインターネットに出ていくためには iptables による Nat を設定しなければならないのでホスト Linux 上で以下のように設定した.

iptables -t nat -I POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

この状態で Host1 から ping を打ってみるが,インターネットへ疎通できなかった.

ip netns exec host1 ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
...

試しに eth0,veth1,br-veth1,br-host1 のそれぞれで tcpdump をしてみてどこまでパケットが届いているか確認すると br-host1 のキャプチャでしかパケットが表示されなかった.

tcpdump -i eth0 -p icmp -n
...
tcpdump -i veth1 -p icmp -n
...
tcpdump -i br-veth1 -p icmp -n
...
tcpdump -i br-host1 -p icmp -n
01:49:58.420877 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 8735, seq 203, length 64
01:49:59.444910 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 8735, seq 204, length 64

f:id:a-mochan:20211123122524p:plain
Nat設定状態でパケットが到達したNIC

どうやら bridge 内の通信がまずうまくいっていないので以下の記事を参考にして bridge 内通信を修正.今回は Netfilter で iptables を呼ばない設定をすることにした.記事の最後にこのカーネルパラメータを 1 の状態で解決できなかった理由も書いておく.

sysctl -w net.bridge.bridge-nf-call-iptables = 0

qiita.com

再度各 NIC に対して tcpdump をしてみたところ veth1 までパケットが届いていることがわかった.

tcpdump -i veth1 -p icmp -n
03:06:17.104353 IP 10.0.0.2 > 8.8.8.8: ICMP echo request, id 9766, seq 1, length 64

f:id:a-mochan:20211123122701p:plain
Netfilter の設定変更後にパケットが到達した NIC

tcpdump より veth1 から eth0 へのパケットが落ちていることが分かるので,iptables で veth1 から eth0 への FORWARD を設定した.

iptables -t filter -I FORWARD -s 10.0.0.0/24 -d 0.0.0.0/0 -j ACCEPT

eth0 の NICtcpdump すると Nat の設定も効いていて,インターネットから eth0 まで通信が返ってきていることを確認できた.

tcpdump -i eth0 -p icmp -n
03:12:11.326420 IP 172.30.20.98 > 8.8.8.8: ICMP echo request, id 9779, seq 1, length 64
03:12:11.328700 IP 8.8.8.8 > 172.30.20.98: ICMP echo reply, id 9779, seq 1, length 64

最後に eht0 から veth1 への戻りの FORWARD 設定をする.セキュリティも考慮してソース IP は 8.8.8.8 だけにしておく.

iptables -t filter -I FORWARD -d 10.0.0.0/24 -s 8.8.8.8/32 -j ACCEPT

そうすると Host1 からインターネットへの接続を確立できる.

ip netns exec host2 ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=104 time=2.51 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=104 time=2.36 ms

余談

今回は bridge ネットワーク通信を正常にするために net.bridge.bridge-nf-call-iptables を 0 に設定した対応を取ったが,先の記事 にある iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPTiptables に設定する方法で進めてみた.
うまくいかない箇所は同じで,まずは veth1 から eth0 への通信が失敗するので iptablesiptables -t filter -I FORWARD -s 10.0.0.0/24 -d 0.0.0.0/0 -j ACCEPT を設定する.そうすると eth0 にパケットが到達するようになるが,なぜか Nat が効かない状態となる.この Nat をどうにか効かせようと色々調べて設定も変えてみたがうまくいかなかった.結果として net.bridge.bridge-nf-call-iptables=0 にする方法でうまくいったのでそちらをまとめたのだが,もし誰かこの原因をご存知の方がいたらコメント欄に書いていただけると助かります.

まとめ

  • トラブルシュートしながらコンテナネットワークからインターネットへ出ていく時の設定を見つけた
  • 改めてネットワークトラブルは tcpdump などでパケットを取り続けるしかないことを学んだ
  • net.bridge.bridge-nf-call-iptables の設定をそのままにした状態でうまくいかない原因は明確となっていない