Carpe Diem

備忘録

Network Namespaceから外部ネットワークへアクセスする

概要

前回の

christina04.hatenablog.com

の続きで、今回はネットワーク名前空間から外部ネットワークへアクセスできるようにします。

環境

以下の状態からスタートします。

f:id:quoll00:20200126220817p:plain

手順

ip_forwardの有効化

デフォルトだとIP FORWARDは無効になっているため、有効にしておきます。

# echo 1 > /proc/sys/net/ipv4/ip_forward

永続化したい場合は/etc/sysctl.conf

net.ipv4.ip_forward = 1

を設定します。

ネットワーク名前空間デフォルトゲートウェイveth1

host1host2のルーティングテーブルを確認すると、デフォルトゲートウェイが設定されていません。

$ sudo ip netns exec host1 netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
10.0.0.0        0.0.0.0         255.255.255.0   U         0 0          0 host1

なのでインターネットのアドレスでアクセスしようとするとルーティングテーブルはどこにパケットを投げていいか分からず次のようにunreachableでコケます。

root@ubuntu-bionic:~# ping 8.8.8.8
connect: Network is unreachable

そこで10.0.0.0/24サブネット以外の宛先IPの場合、10.0.0.100へ転送するようにデフォルトゲートウェイを設定します。

$ sudo ip netns exec host1 ip route add default via 10.0.0.100
$ sudo ip netns exec host2 ip route add default via 10.0.0.100

するとルーティングテーブルにデフォルトゲートウェイが設定されます。

$ sudo ip netns exec host1 netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         10.0.0.100      0.0.0.0         UG        0 0          0 host1
10.0.0.0        0.0.0.0         255.255.255.0   U         0 0          0 host1
$
$ sudo ip netns exec host2 netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         10.0.0.100      0.0.0.0         UG        0 0          0 host2
10.0.0.0        0.0.0.0         255.255.255.0   U         0 0          0 host2

これによって10.0.0.0/24サブネット以外の宛先IPの場合、パケットは10.0.0.100へ転送されます。

$ sudo ip netns exec host1 traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.0.0.100 (10.0.0.100)  0.135 ms  0.006 ms  0.005 ms
 2  * * *
 3  * * *

このように10.0.0.100まで到達できるようになりました。

NATテーブルの設定

しかしまだ10.0.0.100に届いたパケットは外部インターネットへと届きません。
なぜならこの時のパケットは以下の状態なのですが、

パケットのIPヘッダ
送信元IP 10.0.0.1
宛先IP 8.8.8.8

インターネット側からみたらプライベートIPである10.0.0.1は見えません。そういった送信元IPと宛先IPがはっきりしないパケットは破棄されてしまいます。

そこでiptablesのNATテーブルを利用します。

送信元アドレスが10.0.0.0/24サブネットのパケットの送信元IPを、外部ネットワークに繋がっているNICenp0s3のIP10.0.2.15へと書き換えます。
※今回ホストLinuxvagrantなので実際にはこのようなIPアドレス変換が何度か繰り返されます

$ sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o enp0s3 -j MASQUERADE
$ sudo iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  10.0.0.0/24          anywhere

これによってパケットのIPヘッダの送信元IPは、外部からでも疎通できるIPになります。

動作確認

では動作確認してみます。

$ sudo ip netns exec host1 traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.0.0.100 (10.0.0.100)  0.051 ms  0.013 ms  0.010 ms
 2  10.0.2.2 (10.0.2.2)  0.408 ms  0.220 ms  0.260 ms
 3  * * *
...
10  * 108.170.242.193 (108.170.242.193)  21.429 ms 108.170.242.129 (108.170.242.129)  21.179 ms
11  108.170.233.81 (108.170.233.81)  20.049 ms 8.8.8.8 (8.8.8.8)  8.280 ms 72.14.238.75 (72.14.238.75)  8.350 ms

ネットワーク名前空間から外部インターネットへと疎通することができました。

またveth1enp0s3のパケットキャプチャをするとNATによって送信元IPが上書きされていることが分かります。

f:id:quoll00:20200127035902p:plain

Q&A

MASQUERADEはホストLinuxからhost1へのパケットに影響しないのか

先ほど送信元アドレスが10.0.0.0/24サブネットのパケットの送信元IPを〜書き換えると述べました。

とするとホストLinux10.0.0.100からhost110.0.0.1へのパケットも条件に当てはまるのではないでしょうか?

ホストLinuxからhost1pingを打った時のtcpdumphost1側でとってみると特にアドレス変換は入っていません。

root@ubuntu-bionic:~# tcpdump -i host1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on host1, link-type EN10MB (Ethernet), capture size 262144 bytes
18:12:22.574303 IP 10.0.0.100 > 10.0.0.1: ICMP echo request, id 13133, seq 1, length 64
18:12:22.574316 IP 10.0.0.1 > 10.0.0.100: ICMP echo reply, id 13133, seq 1, length 64
18:12:23.591129 IP 10.0.0.100 > 10.0.0.1: ICMP echo request, id 13133, seq 2, length 64
18:12:23.591157 IP 10.0.0.1 > 10.0.0.100: ICMP echo reply, id 13133, seq 2, length 64
18:12:24.610498 IP 10.0.0.100 > 10.0.0.1: ICMP echo request, id 13133, seq 3, length 64

これはホストLinuxとネットワーク名前空間host1はL2ブリッジで繋がっているためです。
L2でつながっているのでiptablesが扱うIPパケットをカプセル化したEthernetフレームのまま状態でデータが届くのです。
つまり今回ホストLinuxとネットワーク名前空間host1はL2のブリッジ(水色)で繋がっているためiptablesのフィルタ(黄緑)を通りません。

f:id:quoll00:20200127033119p:plain

ref: ebtables/iptables interaction on a Linux-based bridge

その代わりL2=Ethernet層のフィルタが可能なebtablesというコマンドがあるので、それを使うとブリッジでもフィルタすることができます。

まとめ

NATによるアドレス変換を用いて、ネットワーク名前空間から外部ネットワークへアクセスすることができました。

ソース