Network namespaceによるネットワークテスト環境の構築
「LinuxのUDPソケットとPath MTU Discovery」の記事で簡単なネットワークの構成図を示してPath MTU Discoveryの解説を行いました。大昔ならこの手の動作確認をしようとするとPCを複数台用意する必要がありましたが、今では、Linuxホストが一台あればテストできます。
Linuxにはコンテナごとのリソースを分離するためにnamespaceという機能がありますが、この中のnetwork namespaceを使うことで、Linuxホスト上に仮想的なホストを作成してネットワークを構築することができます。例えば、LinuxのUDPソケットとPath MTU Discoveryの記事で使った図1のようなネットワーク構成は以下の手順で作成できます。

図1のネットワークを作成するシェルスクリプト
#!/bin/bash # create namespace ip netns add ns1 ip netns add ns2 ip netns add ns3 # global <-> ns1 veth ip link add veth0 type veth peer name veth0 netns ns1 ip addr add 10.0.1.1/24 dev veth0 ip netns exec ns1 ip addr add 10.0.1.10/24 dev veth0 ip link set veth0 up ip netns exec ns1 ip link set veth0 up # ns1 <-> ns2 veth ip link add veth1 mtu 1280 netns ns1 type veth peer name veth1 mtu 1280 netns ns2 ip netns exec ns1 ip addr add 10.0.2.10/24 dev veth1 ip netns exec ns2 ip addr add 10.0.2.20/24 dev veth1 ip netns exec ns1 ip link set veth1 up ip netns exec ns2 ip link set veth1 up # ns2 <-> ns3 veth ip link add veth2 netns ns2 type veth peer name veth2 netns ns3 ip netns exec ns2 ip addr add 10.0.3.20/24 dev veth2 ip netns exec ns3 ip addr add 10.0.3.30/24 dev veth2 ip netns exec ns2 ip link set veth2 up ip netns exec ns3 ip link set veth2 up # ns1,ns2ではforwardingをする ip netns exec ns1 sysctl -w net.ipv4.ip_forward=1 ip netns exec ns2 sysctl -w net.ipv4.ip_forward=1 # routing ip route add 10.0.2.0/24 via 10.0.1.10 ip route add 10.0.3.0/24 via 10.0.1.10 ip netns exec ns1 ip route add 10.0.3.0/24 via 10.0.2.20 ip netns exec ns2 ip route add 10.0.1.0/24 via 10.0.2.10 ip netns exec ns3 ip route add 10.0.1.0/24 via 10.0.3.20 ip netns exec ns3 ip route add 10.0.2.0/24 via 10.0.3.20
スクリプトの内容を簡単に説明します。network namespaceに関する設定はip netnsで行います。まず最初にnetwork namespaceを3つ(ns1~ns3)作成します。
ip netns add ns1 ip netns add ns2 ip netns add ns3
各namespace(nsX)がそれぞれ仮想的なホストになります(*1)。namespace内にそれぞれ独立したネットワーク機能(*2)が提供されます。
namespace内のネットワーク設定を行う場合は、"ip netns exec <namespace名> <コマンド>"のようにexecオプションでnamespaceとコマンドを指定することで行います。
namespace内のインターフェースにIPアドレスを設定する例
ip netns exec ns1 ip addr add 10.0.2.10/24 dev veth1
namespaceを作成した後は、ネットワークインターフェースの作成とIPアドレスの設定を行っています。
# ネットワークインターフェース(veth)の作成 ip link add veth0 type veth peer name veth0 netns ns1 # IPアドレスの設定(global namespaceのveth0) ip addr add 10.0.1.1/24 dev veth0 # IPアドレスの設定(ns1 namespaceのveth0) ip netns exec ns1 ip addr add 10.0.1.10/24 dev veth0 : :
ここで出てくるtype vethのインターフェースは仮想Ethernetデバイスと呼ばれるものです。network namespaceで隔離したネットワーク環境からは外部とは通信できません。何らかの方法でnamespace外と接続する必要があります。そこで使われるのが、namespace間を接続する仮想Ethernetデバイス(veth)です。
vethは必ずペアで作成され、network namespace間を接続します。 man vethによる説明を借りるとpipeのような動作をするのですが、あくまでEthernetなので、POINTOPOINTではなくBROADCASTデバイスとして動作します。
各namepsaceのvethとIPアドレスの設定ができれば、後はIPパケットの中継に関する設定を行えば、各namepsace間で通信できるようになります。
ip netns exec ns1 sysctl -w net.ipv4.ip_forward=1 ip netns exec ns2 sysctl -w net.ipv4.ip_forward=1 # routing ip route add 10.0.2.0/24 via 10.0.1.10 ip route add 10.0.3.0/24 via 10.0.1.10 : :
これでglobal nsからのns3の10.0.3.30にpingが通るようになります。
$ ping 10.0.3.30 PING 10.0.3.30 (10.0.3.30) 56(84) bytes of data. 64 bytes from 10.0.3.30: icmp_seq=1 ttl=62 time=0.047 ms 64 bytes from 10.0.3.30: icmp_seq=2 ttl=62 time=0.054 ms
ip netns execを使えば逆にns3からglobal nsにpingすることもできます。
$ sudo ip netns exec ns3 ping 10.0.1.1 PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data. 64 bytes from 10.0.1.1: icmp_seq=1 ttl=62 time=0.040 ms 64 bytes from 10.0.1.1: icmp_seq=2 ttl=62 time=0.052 ms
ns3のveth2のパケットをキャプチャしたい場合は以下のように行えます。
$ sudo ip netns exec ns3 wireshark
しつこいですが、ns3で何らかのサーバープロセスを立ち上げてテストしたければ以下のようにできます。
$ sudo ip netns exec ns3 ./server_process
ip netns execを使えば、作成したネットワークに対していろいろなことが出来るのがわかると思います。
vethの接続先の確認の仕方
namespaceを使ってvethを大量に作成すると、どのインターフェースがどこと繋がっているかわかり辛くなります。私はvethのペアは必ず同じ名前か同じprefixを持たせるようにして識別できるようにしていますが、一応、接続先の確認方法も説明しておきます。
接続先はip linkで確認することができます。 vethの場合、link-netnsに接続先のnamespaceが表示されています。 また、インターフェース名はveth0@if2のように@if<数字>という情報が付与されており、この数字は接続先nemaspaceにおけるインターフェースのifindex(*3)になります。
global namepsaceのip link
$ ip link 1: lo:mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: ens33: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 00:0c:29:fd:e2:46 brd ff:ff:ff:ff:ff:ff altname enp2s1 3: veth0@if2: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether ee:82:47:1d:71:ab brd ff:ff:ff:ff:ff:ff link-netns ns1
ns1のip link
$ sudo ip netns exec ns1 ip link 1: lo:mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: veth0@if3: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 6e:e3:84:cc:64:01 brd ff:ff:ff:ff:ff:ff link-netnsid 0 3: veth1@if2:mtu 1280 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 6e:0d:d8:b7:f8:2d brd ff:ff:ff:ff:ff:ff link-netns ns2
例えば、global namespaceのveth0は"link-netns ns1"、"veth0@if2" なので、ns1のifindex2のインターフェース、つまりveth0@if3に繋がっていることがわかります。
vethのTSO/GSO
vethでは、TSO(TCP Segmentation Offload)/GSO(Generic Receive Offload)機能が有効になっているようです。TCPを使ってPMTU Discoveryの挙動を見たい場合など、これらを無効にしておいた方がいいかもしれません。
TSO/GSOの無効化
$ sudo ethtool -K veth0 generic-segmentation-offload off $ sudo ethtool -K veth0 tcp-segmentation-offload off
やりたいことに応じて設定してみてください。
昔話
以上で、Linuxホスト一台あれば様々なネットワークを構築してテストなどができることが分かったかと思います。一方、この方法ではホストがLinuxに限られるので、FreeBSDとかも含めた構成を組みたい時はVMWareなどを使うことになります。
VMWareもネットワーク設定機能は充実しており、linked clone機能を使えば一瞬で仮想マシンを複製できるので、ネットワーク関連のテストを行うのに重宝します。
以前はこのようなテストをしようとすると、それぞれPCを用意し、OSをインストールして、Ethernetをつなげ、同僚と電源を奪い合っていたので、それを考えるととても楽になったと感じます。一方、今ではRaspberry Piのような小型コンピューターが簡単に手に入るようになったので、それらを実際に接続して何か環境を作るというのもまた楽しいですが。
[参考]
- man namespaces
- man network_namespaces
- man veth
- man ip-netns
(*1) namespaceの名前はns1とかよりもvhost1とかにした方がわかりやすかったかもしれません。
(*2) ネットワークインターフェース、IPアドレス、ルーティングテーブル、ファイアウォール等
(*3) Kernel内でネットワークインターフェースに振られている連番。1から始まる。ip linkで表示されているコロンの前の番号はifindex。ifindexもちゃんとnamespaceごとに独立して採番されるようだ。
投稿日:2023/03/15 16:15
タグ: ネットワーク