LinuxのNetns/veth/Bridge/NATで仮想ネットワーク構築
この記事はOIC ITCreate Club Advent Calendar 2018 15日目が空いてたので、そのつもりで書いている記事です。 https://adventar.org/calendars/3072
Linuxには、以下のような機能が標準で実装されており、簡単に使用可能です。 Ciscoのような機器には性能が及ばないですが、一通りの基本的な名ネットワークはもちろん、更に高度なネットワークも気軽に組むことが出来ます。 これらの機能を組み合わせてDocker等のコンテナエンジンのネットワーク技術は構築されています。
-
iptables
- ファイアウォール・NAT(SNAT/DNAT/Masquerade)等の機能を提供
- 主に、IPとTCP/UDP等のプロトコルに対して設定が書ける
iptables
コマンドで操作が可能
-
Bridge
- L2なブリッジインターフェースを作成する機能
- 複数のネットワークインターフェースを、仮想的にL2スイッチに繋げたような動作を行う
brctl
コマンドで操作が可能
-
Network Namespace
- ルーティングテーブル・インターフェース等が分離された環境を複数作るための機構
- VRFのような機能を提供する
ip netns
サブコマンドで操作が可能
-
veth
- 自由に作成できる仮想的なインターフェースで、ネームスペース間やホストマシン等への接続が可能
- 作成すると、互いに接続されたインターフェース2つが生成される
ip link add
ip link set
サブコマンドで操作が可能
この記事では、上の技術を組み合わせて、1台のマシンの中でネットワークを仮想的に構築する手順を説明します。
完成図
Namespaceを2つ作り、Bridge経由でそれぞれのNamespace・ホストマシンのインターフェイスと疎通が取れるようにします。そして、Namespaceの内部から、ホストマシンのiptablesを経由してNATを通した外部との通信も行えるようにします。
1台のマシンの内部で、仮想的にネットワークを構築するところがポイントです。
有ると良い知識
- SSH
- Linuxの基礎的なコマンド操作
- IP/Static Route
- Bridge
サーバを建てる
今回は さくらのクラウド を使用して一番安いプランで雑にサーバを立てました。 SSH経由で操作を行います。
- CPU: 1
- RAM: 1GB
- OS: Ubuntu 16.04
- Kernel: 4.4.0-116
ネットワークを構築する
依存パッケージのインストール
Bridgeを操作する brctl
コマンドを使用するため、パッケージのインストールを行います。
ubuntu@namespace:~$ sudo -s
[sudo] password for ubuntu:
root@namespace:~# apt install bridge-utils
Namespaceの作成
コンテナに見立てた host1
host2
2つのNamespaceを作成します。
最後にNamespaceが作成できているかを確認します。
root@namespace:~# ip netns add host1
root@namespace:~# ip netns add host2
root@namespace:~# ip netns ls
host2
host1
仮想リンクの作成
仮想的なLANケーブル・ポートを表すvethを作成します。 作成すると、互いに仮想的にLANケーブルで接続されたインターフェース2つが生成される感じです。
リンクを作る前に、初期状態を確認しておきます。
root@namespace:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 9c:a3:ba:30:9e:18 brd ff:ff:ff:ff:ff:ff
ipコマンドでvethを作る場合、2回登場するnameの後にインターフェイスの名前を指定します。 どちらが先でも構いません。
root@namespace:~# ip link add name veth1 type veth peer name veth1-br
root@namespace:~# ip link add name veth1-h1 type veth peer name veth2-br
root@namespace:~# ip link add name veth1-h2 type veth peer name veth3-br
作成が終わったので、確認します。 3個のvethを作成し、それぞれ2つのIFを持つので、全部で6つのリンクが登場します。
root@namespace:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 9c:a3:ba:30:9e:18 brd ff:ff:ff:ff:ff:ff
20: veth1-br@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether fa:e7:d1:31:fc:9b brd ff:ff:ff:ff:ff:ff
21: veth1@veth1-br: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 32:41:0e:b8:25:7c brd ff:ff:ff:ff:ff:ff
22: veth2-br@veth1-h1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 32:e1:04:80:40:d1 brd ff:ff:ff:ff:ff:ff
23: veth1-h1@veth2-br: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 52:71:2c:36:be:d4 brd ff:ff:ff:ff:ff:ff
24: veth3-br@veth1-h2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether d2:bf:05:28:7a:bf brd ff:ff:ff:ff:ff:ff
25: veth1-h2@veth3-br: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f6:2b:d9:be:0e:f5 brd ff:ff:ff:ff:ff:ff
仮想リンクとNamespaceを結びつける
作成した仮想リンクは、以下のようにNamespaceと紐付けることが必要です。
- Vethの
veth1-h1
はNamespaceのhost1
- Vethの
veth1-h2
はNamespaceのhost2
実行後にlink一覧を確認すると、Namespaceと紐づけた2つのIFが無くなっていることが分かります。
root@namespace:~# ip link set veth1-h1 netns host1
root@namespace:~# ip link set veth1-h2 netns host2
root@namespace:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 9c:a3:ba:30:9e:18 brd ff:ff:ff:ff:ff:ff
20: veth1-br@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether fa:e7:d1:31:fc:9b brd ff:ff:ff:ff:ff:ff
21: veth1@veth1-br: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 32:41:0e:b8:25:7c brd ff:ff:ff:ff:ff:ff
22: veth2-br@if23: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 32:e1:04:80:40:d1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
24: veth3-br@if25: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether d2:bf:05:28:7a:bf brd ff:ff:ff:ff:ff:ff link-netnsid 1
無くなったIFはどこに行ったかと言うと、 host1
host2
Namespace配下に移動しました。
ip netns exec [Namespace名]
サブコマンドの後に任意のコマンドを記述すると、そのNamespaceの内部から実行される機能が有ります。これを使用してリンクの一覧を表示します。すると、先程移動したリンクが出てきます。
root@namespace:~# ip netns exec host1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
23: veth1-h1@if22: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 52:71:2c:36:be:d4 brd ff:ff:ff:ff:ff:ff link-netnsid 0
Bridgeの作成・接続
Linux上で動作する、L2スイッチであるbridgeを作成します。
先程導入したパッケージにbrctl
が含まれているので、コマンドを実行して作成します。
作成すると、それぞれのインターフェイスが接続されていることが分かると思います。
root@namespace:~# brctl addbr br0
root@namespace:~# brctl addif br0 veth1-br
root@namespace:~# brctl addif br0 veth2-br
root@namespace:~# brctl addif br0 veth3-br
root@namespace:~#
root@namespace:~# ip link show dev br0
26: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 32:e1:04:80:40:d1 brd ff:ff:ff:ff:ff:ff
root@namespace:~# brctl show br0
bridge name bridge id STP enabled interfaces
br0 8000.32e1048040d1 no veth1-br
veth2-br
veth3-br
IFへアドレスの付与
仮想インターフェイスにまだIPアドレスが振られていないので、通信を行うことが出来ないです。ホストマシンで動作する veth1
・Namespace内で動作する veth1-h1
veth-h2
に対して、順にIPアドレスを付与しようと思います。
root@namespace:~# ip addr add 10.0.0.254/24 dev veth1
root@namespace:~# ip netns exec host1 ip addr add 10.0.0.1/24 dev veth1-h1
root@namespace:~# ip netns exec host2 ip addr add 10.0.0.2/24 dev veth1-h2
root@namespace:~#
root@namespace:~# ip netns exec host1 ip addr show dev veth1-h1
23: veth1-h1@if22: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 52:71:2c:36:be:d4 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.1/24 scope global veth1-h1
valid_lft forever preferred_lft forever
IFの起動
仮想インターフェイス・ブリッジ同士の結線が完了しました。
ですが、ip link
から確認すると、全てステータスがDOWN
となっています。
1つ1つ起動させていきます。
root@namespace:~# ip link set br0 up
root@namespace:~# ip link set veth1 up
root@namespace:~# ip link set veth1-br up
root@namespace:~# ip link set veth2-br up
root@namespace:~# ip link set veth3-br up
root@namespace:~# ip netns exec host1 ip link set veth1-h1 up
root@namespace:~# ip netns exec host2 ip link set veth1-h2 up
root@namespace:~#
root@namespace:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 9c:a3:ba:30:9e:18 brd ff:ff:ff:ff:ff:ff
20: veth1-br@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000
link/ether fa:e7:d1:31:fc:9b brd ff:ff:ff:ff:ff:ff
21: veth1@veth1-br: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 32:41:0e:b8:25:7c brd ff:ff:ff:ff:ff:ff
22: veth2-br@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000
link/ether 32:e1:04:80:40:d1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
24: veth3-br@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000
link/ether d2:bf:05:28:7a:bf brd ff:ff:ff:ff:ff:ff link-netnsid 1
26: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 32:e1:04:80:40:d1 brd ff:ff:ff:ff:ff:ff
内部の疎通確認
この状態で、ホストマシンに接続されている veth1
と、Namespaceに接続されている veth1-h1
veth1-h2
は、同一のL2スイッチ(Bridge)に接続され、同一のネットワークを持つIPアドレスを付与しています。ですので、既に疎通が取れるはずです。確かめてみましょう。
root@namespace:~# ping -c 1 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.092 ms
--- 10.0.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.092/0.092/0.092/0.000 ms
root@namespace:~# ip netns exec host1 ping -c 1 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.069 ms
--- 10.0.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.069/0.069/0.069/0.000 ms
root@namespace:~# ip netns exec host2 ping -c 1 10.0.0.254
PING 10.0.0.254 (10.0.0.254) 56(84) bytes of data.
64 bytes from 10.0.0.254: icmp_seq=1 ttl=64 time=0.067 ms
--- 10.0.0.254 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.067/0.067/0.067/0.000 ms
それぞれが疎通を取ることが出来ました。
ここで、外部のIPアドレスに向けて疎通確認を行うと、リーチしないと怒られます。
Namespace内のルーティングテーブルでは、標準でデフォルトルートが指定されていません。なので、10.0.0.0/24
のネットワーク以外の行き先は現時点で不明なのでunreachable
と表示されています。
root@namespace:~# ip netns exec host1 ping -c 1 8.8.8.8
connect: Network is unreachable
NATの設定を行う
ホストマシン(10.0.0.254)で動作するiptablesでNAT機能を有効にして、Namespace内からの通信をIPマスカレードで外に出れるように設定しましょう。
カーネル設定によるIPフォワーディングの有効化・NATテーブルの初期化・設定の追加・確認と順に行っていきます。
root@namespace:~# echo 1 > /proc/sys/net/ipv4/ip_forward
root@namespace:~# iptables --table nat --flush
root@namespace:~# iptables --table nat --append POSTROUTING --source 10.0.0.0/24 --jump MASQUERADE
root@namespace:~# iptables --table nat --list
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
ルーティングの追加
上で設定したホストマシン(10.0.0.254)に対して、デフォルトルートを向けます。 ipコマンドを使用して、StaticRouteとして指定しています。
root@namespace:~# ip netns exec host1 ip route add default via 10.0.0.254
root@namespace:~# ip netns exec host2 ip route add default via 10.0.0.254
root@namespace:~# ip netns exec host1 ip route
default via 10.0.0.254 dev veth1-h1
10.0.0.0/24 dev veth1-h1 proto kernel scope link src 10.0.0.1
外部との疎通の確認
すべての手順が完了したので、Namespace内から疎通が取れるかを確認します。
root@namespace:~# ip netns exec host1 ping -c 1 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=120 time=16.6 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 16.615/16.615/16.615/0.000 ms
root@namespace:~# ip netns exec host2 ping -c 1 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=120 time=16.4 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 16.457/16.457/16.457/0.000 ms
まとめ
ルーターで設定するような項目は、主にLinuxの標準的な機能だけで設定可能です。 気軽に使える機能なので、積極的に使っていきたいです。