2013年12月24日火曜日

Network Namespace を使って OpenVNet を動かしてみる

先日 Wakame Advent Calendar の記事として コマンドラインからだけで OpenVNet の機能を推測してみる という記事を書きましたが、その後実際に動かしていませんでした。

最初は、Ubuntu で OpenVNet を動かして、いんちきで OpenStack と接続してみようと思ったのですが、Ubuntu では 12.04 でも 13.04 でも 13.10 でも VNA が起動できなかったので、もう少し軽く動かしてみます。 Ubuntu 13.10 では wakame-edge のコンパイルも失敗しました (trema-edge では修正されています)。

今回は Network Namespace を使って OpenVNet の動作を確認してみようと思います。動作確認するのは以下の3つです。

  • 仮想ネットワークを作成
  • IPアドレスが重複する仮想ネットワークでの動作
  • 仮想ネットワーク間のルーティング

Network Namespace は 1 つの Linux ホストの中に仮想的なネットワーク環境を複数作れる機能です。これを使うと、ネットワークの実験を VM などリソースをたくさん消費する手段を使わずにできるので便利です。仮想ネットワークを複数作ってみる方法については、OpenVNet Installation Guideには、VM 環境を使って OpenVNet を動かしてみる方法が紹介されていますが、Network Namespace を使うとお手軽に実験できるので、今回はその方法を紹介します。

今回の動作確認の中でいくつかスクリプトを作りました。OpenVNet test tools with network namespace においてありますので、適当に参考にしてください。

CentOS6 で Network Namespace を使う準備

Network Namespace は最近新しめの Linux Kernel を採用している Ubuntu などではそのままで利用できますが、OpenVNet の推奨環境となっている CentOS6 では残念ながら、まだそのままでは動きません。

設定方法を CentOS6 で Linux Network Namespace を使うにまとめましたので、こちらを参照して、必要な iproute パッケージ (ip コマンド) を入れておいて下さい。

OpenVNet のインストール

OpenVNet Installation Guideにしたがって、インストールを行います。

"Let's try 1Box OpenVNet" の前までを実施します。

注意点としては、OpenVNet のサービスを開始する前に redis も開始しておく必要があります。

# chkconfig redis on
# service redis start

CentOS6.4 が推奨となっていますが、今回は CentOS6.5 を使いましたが問題なく動作しました。

仮想ネットワークの作成

準備ができたので、2つの VM が接続された仮想ネットワーク net1 を作成してみます。

  • 仮想ネットワーク net1 : 172.16.1.0/24
    • インタフェース1 (VM1 相当) : 172.16.1.1 (@netns ns1)
    • インタフェース1 (VM2 相当) : 172.16.1.2 (@netns ns2)

手順を順番に見ていきます。 Installation Guide に説明がある 1 ホストで VM を使って動かす例についている設定スクリプト db.sh を参考に整理してみました。

まず、最初に OpenFlow として動作する Open vSwitch bridge を OpenVNet に登録します。Open vSwitch bridge は br0 です。今回は 1 ホストなので、最初に一回 datapath を登録します。

https://github.com/amotoki/openvnet-test-tools/blob/master/vnet-register-datapath.sh#L17
# uuid は dp- で始める必要あり
# dpid は 0x を先頭に付けること (OVS の出力とは違うので注意)
./vnctl datapath add ¥
    --uuid dp-br0 ¥
    --display-name=br0 ¥
    --dc-segment-id=seg1 ¥
    --dpid=0x000002fdee8e0a44 ¥
    --ipv4-address=$HOST_IP192.168.122.207 ¥
    --node-id=192.168.122.207
openvnet にネットワークを作成します。
https://github.com/amotoki/openvnet-test-tools/blob/master/vnet-register-net1.sh#L20
# ネットワークの作成
# 仮想ネットワークなので network-mode は virtual を指定する
# uuid は nw- で始める必要あり
./vnctl network add ¥
  --uuid nw-net1 --display-name=net1 --ipv4-network 172.16.1.0 --ipv4-prefix=24 ¥
  --domain-name=dom1 --network-mode=virtual

# datapath に作成したネットワークを関連付ける
# broadcast-mac-address は MAC2MAC でブロードキャストパケットを転送する際に使用されるはず
./vnctl datapath networks add dp-$BRNAME nw-net1 --broadcast-mac-address=08:00:27:10:03:01

# ネットワークに接続されるインタフェースを登録する
# uuid は単なる ID ではなく、OVS に追加するポートの名前と一致している必要があるみたい。また、if- で始める必要あり。
# IPv4 address と MAC はそのインタフェースの値を指定する。VM の場合は VM の中の NIC、network namespace の場合は
# namespace 内の NIC の値になる。OVS に add-port する network device の値ではない点に注意。
./vnctl interface add --uuid=if-veth1 --ipv4-address=$IP1 --network-uuid=nw-net1 --mac-address=$MAC1
./vnctl interface add --uuid=if-veth2 --ipv4-address=$IP2 --network-uuid=nw-net1 --mac-address=$MAC2
上記で OpenVNet 側の設定は終わりです。

次に、実際のデバイスを作成します。
./ovs-create.sh
./if-create-net1.sh
ovs-create.sh は OVS bridge を作成し、datapath ID 設定、OpenFlow 設定などを行っています。今回は OpenVNet の設定を行った後でないと、うまく動かなかったので、このタイミングで行っています。

/var/log/wakame-vnet/vna.log を見ると、OVS から接続や、スイッチポートが追加されていることが分かります。

ping を実行してみましょう。
network namespace ns1 の 172.16.1.1 側から ns2 の 172.16.1.2 に ping を送ります。
# ip netns
ns2
ns1
# ip netns exec ns1 ip -o addr
207: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN \    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
207: lo    inet 127.0.0.1/8 scope host lo
207: lo    inet6 ::1/128 scope host \       valid_lft forever preferred_lft forever
208: ns-veth1:  mtu 1500 qdisc pfifo_fast state UP qlen 1000\    link/ether 52:54:00:0d:84:01 brd ff:ff:ff:ff:ff:ff
208: ns-veth1    inet 172.16.1.1/24 brd 172.16.1.255 scope global ns-veth1
208: ns-veth1    inet6 fe80::5054:ff:fe0d:8401/64 scope link \       valid_lft forever preferred_lft forever
# ip netns exec ns1 ping -c 5 172.16.1.2
PING 172.16.1.2 (172.16.1.2) 56(84) bytes of data.
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=0.466 ms
64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=0.134 ms
64 bytes from 172.16.1.2: icmp_seq=3 ttl=64 time=0.058 ms
64 bytes from 172.16.1.2: icmp_seq=4 ttl=64 time=0.066 ms
64 bytes from 172.16.1.2: icmp_seq=5 ttl=64 time=0.063 ms

--- 172.16.1.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4000ms
rtt min/avg/max/mdev = 0.058/0.157/0.466/0.157 ms
# ip netns exec ns1 arp -na
? (172.16.1.2) at 52:54:00:0d:84:02 [ether] on ns-veth1
ns2 の 172.16.1.2 側で tcpdump を仕掛けておくとパケットが見えます。
# ip netns exec ns2 tcpdump -i ns-veth2
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns-veth2, link-type EN10MB (Ethernet), capture size 65535 bytes
01:16:35.234573 ARP, Request who-has 172.16.1.2 tell 172.16.1.1, length 28
01:16:35.234610 ARP, Reply 172.16.1.2 is-at 52:54:00:0d:84:02 (oui Unknown), length 28
01:16:35.234709 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 12927, seq 1, length 64
01:16:35.234758 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 12927, seq 1, length 64
01:16:36.234855 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 12927, seq 2, length 64
01:16:36.234917 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 12927, seq 2, length 64
01:16:37.234633 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 12927, seq 3, length 64
01:16:37.234651 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 12927, seq 3, length 64
01:16:38.234501 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 12927, seq 4, length 64
01:16:38.234522 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 12927, seq 4, length 64
01:16:39.234528 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 12927, seq 5, length 64
01:16:39.234550 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 12927, seq 5, length 64
01:16:40.234515 ARP, Request who-has 172.16.1.1 tell 172.16.1.2, length 28
01:16:40.234793 ARP, Reply 172.16.1.1 is-at 52:54:00:0d:84:01 (oui Unknown), length 28
^C
14 packets captured
14 packets received by filter
0 packets dropped by kernel

IPアドレスが重複する仮想ネットワークでの動作


仮想ネットワークといえば、IP アドレス重複に対応できるか!です。もちろん試してみましょう。
先ほどの net1 に追加で、同じ IP アドレスを持つ net3 を作成します。
  • 仮想ネットワーク net1 : 172.16.1.0/24
    • インタフェース : 172.16.1.1 (@netns ns1)
    • インタフェース : 172.16.1.2 (@netns ns2)
  • 仮想ネットワーク net3 : 172.16.1.0/24
    • インタフェース : 172.16.1.1 (@netns ns5)
    • インタフェース : 172.16.1.2 (@netns ns6)
OpenVNet にネットワークとインタフェースを登録して、その後実際のデバイスを作成します。net1 のときと全く同じなので、説明は省略します。
./vnet-register-net3.sh
./if-create-net3.sh
ns1 の 172.16.1.1 と ns5 の 172.16.1.1 からそれぞれ 172.16.1.2 に ping を送ってみます。ns1 から送った時は同じ net1 側の ns2 の 172.16.1.2で、ns5 から送った時は net3 側の ns6 の 172.16.1.2 にパケットが届いていることが確認できます。片方の結果だけ載せておきます。
(net3 側の 172.16.1.1 から ping)
# ip netns exec ns5 ping -c 3 172.16.1.2
PING 172.16.1.2 (172.16.1.2) 56(84) bytes of data.
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=1.68 ms
64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 172.16.1.2: icmp_seq=3 ttl=64 time=0.048 ms

--- 172.16.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.048/0.596/1.683/0.768 ms

(net1 側の 172.16.1.2)
# ip netns exec ns2 tcpdump -i ns-veth2
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns-veth2, link-type EN10MB (Ethernet), capture size 65535 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

(net3 側の 172.16.1.2)
# ip netns exec ns5 tcpdump -i ns-veth5
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns-veth5, link-type EN10MB (Ethernet), capture size 65535 bytes
01:34:38.493450 ARP, Request who-has 172.16.1.2 tell 172.16.1.1, length 28
01:34:38.493998 ARP, Reply 172.16.1.2 is-at 52:54:00:0d:84:06 (oui Unknown), length 28
01:34:38.494005 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 57983, seq 1, length 64
01:34:38.494093 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 57983, seq 1, length 64
01:34:39.493507 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 57983, seq 2, length 64
01:34:39.493546 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 57983, seq 2, length 64
01:34:40.493471 IP 172.16.1.1 > 172.16.1.2: ICMP echo request, id 57983, seq 3, length 64
01:34:40.493503 IP 172.16.1.2 > 172.16.1.1: ICMP echo reply, id 57983, seq 3, length 64
01:34:43.493577 ARP, Request who-has 172.16.1.1 tell 172.16.1.2, length 28
01:34:43.493588 ARP, Reply 172.16.1.1 is-at 52:54:00:0d:84:05 (oui Unknown), length 28
^C
10 packets captured
10 packets received by filter
0 packets dropped by kernel
IP アドレス帳服がある場合でも、期待通り仮想ネットワークが機能しています。

仮想ネットワーク間のルーティング

最後に、仮想ネットワーク間でのルーティングをさせてみたいと思います。先日、コマンドラインからだけで OpenVNet の機能を推測してみる記事を書いた時にはいまいちよく分からなかったのですが、サンプルスクリプトの db.sh などと睨めったことしつつなんとなく分かって来ましたので、試してみました。

IP アドレスの違う論理ネットワークを2つ用意し、それらを論理ルータで接続する構成です。論理ルータは 172.16.1.254 と 172.16.2.254 という2つのインタフェースを持ちます。
  • 仮想ネットワーク net1 : 172.16.1.0/24
    • インタフェース : 172.16.1.1 (@netns ns1)
    • インタフェース : 172.16.1.2 (@netns ns2)
    • ルータインタフェース : 172.16.1.254
  • 仮想ネットワーク net2 : 172.16.2.0/24
    • インタフェース : 172.16.2.3 (@netns ns3)
    • インタフェース : 172.16.2.4 (@netns ns4)
    • ルータインタフェース : 172.16.2.254
まず、net2 を作成しておきます。手順はこれまでと全く同じです。net2 で ping などを行って、導通を確認しておきましょう。
./vnet-register-net2.sh
./if-create-net2.sh
OpenVNet 側にルータを作成していきます。次のような手順になります。
  1. ルータインタフェースの作成
  2. ルータインタフェースに対して network_service を関連付け (なくても動く)
  3. route_link の作成
  4. route_link の datapath への関連付け (なくても動く)
  5. route 情報の登録
最初の2つがルータのインタフェースに関する操作、後ろの3つがルータの宣言のようなものです。
ざっくり言うと、route_link が論理ルータに対応します。 route は論理ルータの経路情報です。名前が分かりにくいですね。

サンプルスクリプト db.sh に書かれている手順を参考にしていますが、network_service の作成、route_link の datapath への関連付けの2つはなくても動きました。どういうことでしょう?

実際のコマンドベースで説明します。一部のコマンドは CLI では提供されていないので、curl を直接叩いています。
https://github.com/amotoki/openvnet-test-tools/blob/master/vnet-register-router.sh#L29
# net1 の論理ルータに対応するインタフェースを作成
# OpenVNet が機能を提供するので mode=simulated を指定する
# vnctl CLI では mode が指定できないので、curl を使う
curl -s -X POST --data-urlencode uuid=if-vnet1gw ¥
  --data-urlencode network_uuid=nw-net1 ¥
  --data-urlencode mac_address=52:54:00:74:00:00 ¥
  --data-urlencode ipv4_address=172.16.1.254 ¥
  --data-urlencode mode=simulated ¥
  http://localhost:9090/api/interfaces

# ルータインタフェースの終端を OpenVNet が行うことの宣言のようです。
# ★ 1 ホストでのテストでは、なくても動きました。
./vnctl network_service add --uuid=ns-vnet1gw --interface-uuid=if-vnet1gw --display-name=router

# net2 の論理ルータに対応するインタフェースを作成
# OpenVNet が機能を提供するので mode=simulated を指定する
# vnctl CLI では mode が指定できないので、curl を使う
curl -s -X POST --data-urlencode uuid=if-vnet2gw ¥
  --data-urlencode network_uuid=nw-net2 ¥
  --data-urlencode mac_address=52:54:00:74:22:22 ¥
  --data-urlencode ipv4_address=172.16.2.254 ¥
  --data-urlencode mode=simulated ¥
  http://localhost:9090/api/interfaces

# ルータインタフェースの終端を OpenVNet が行うことの宣言のようです。
# ★ 1 ホストでのテストでは、なくても動きました。
./vnctl network_service add --uuid=ns-vnet2gw --interface-uuid=if-vnet2gw --display-name=router

# 論理ルータの作成
# OpenVNet では route_link という概念として表現される
# MAC address が何に利用されるのかは不明
./vnctl route_link add --uuid=rl-vnetlink1 --mac-address=52:54:00:60:11:11

# 作成した route_link を datapath に登録する
# ★ 1 ホストでのテストでは、なくても動きました。
curl -s -X POST --data-urlencode route_link_uuid=rl-vnetlink1 \
  --data-urlencode mac_address=08:00:27:20:01:01 \
  http://localhost:9090/api/datapaths/dp-br0/route_links/rl-vnetlink1

# 論理ルータでの経路情報を登録する
curl -s -X POST --data-urlencode uuid=r-vnet1 ¥
  --data-urlencode interface_uuid=if-vnet1gw ¥
  --data-urlencode route_link_uuid=rl-vnetlink1 ¥
  --data-urlencode ipv4_network=172.16.1.0 ¥
  http://localhost:9090/api/routes
curl -s -X POST --data-urlencode uuid=r-vnet2 ¥
  --data-urlencode interface_uuid=if-vnet2gw ¥
  --data-urlencode route_link_uuid=rl-vnetlink1 ¥
  --data-urlencode ipv4_network=172.16.2.0 ¥
  http://localhost:9090/api/routes
上記を実行して、OpenVNet に登録します。ルータ機能は OpenVNet が提供するので、実際のデバイスの設定は必要ありません。
./vnet-register-router.sh
net1 と net2 の間で ping を行ってみます。 net1 の 172.16.1.1 (@ns1) から net2 の 172.16.2.3 (@ns3) に ping を実行しています。ちゃんと ping が通りました。成功です。

# ip netns exec ns1 ping 172.16.2.3
PING 172.16.2.3 (172.16.2.3) 56(84) bytes of data.
64 bytes from 172.16.2.3: icmp_seq=1 ttl=64 time=20.0 ms
64 bytes from 172.16.2.3: icmp_seq=2 ttl=64 time=0.060 ms
64 bytes from 172.16.2.3: icmp_seq=3 ttl=64 time=0.056 ms
^C
--- 172.16.2.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2550ms
rtt min/avg/max/mdev = 0.056/6.711/20.018/9.409 ms
/var/log/wakame-vnet/vna.log を見ると、vna が ARP に応答している記録もあります。

I, [2013-12-24T02:41:50.725318 #2463]  INFO -- : 0x000002fdee8e0a44 interfaces/simulated: simulated arp reply (arp_tpa:172.16.1.254)
I, [2013-12-24T02:41:50.735063 #2463]  INFO -- : 0x000002fdee8e0a44 interfaces/simulated: simulated arp reply (arp_tpa:172.16.2.254)
ちなみに、ルータインタフェースへの ping は通りませんでした。

# ip netns exec ns1 ping 172.16.1.254
PING 172.16.1.254 (172.16.1.254) 56(84) bytes of data.
^C
--- 172.16.1.254 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4590ms

まとめ

今回は OpenVNet の基本的な使い方を Network Namespace を使って実験してみました。仮想ネットワークの作成、重複 IP アドレスの扱い、仮想ルータを使った通信をどうやって行うのかは分かりました。外部の物理ネットワークとの通信方法は時間があれば調査してみようと思います。

テスト中には、コマンドの投入順序の違いなどで動かず、やり直してみたら動いた、といった点もありました。全体的に、OpenVNet に情報 (datapath や interface) を登録してから、OVS から VNA への接続やインタフェースの OVS への追加を行う必要があるみたいです。今後そのあたりは改善されていくと思います。また、一部の機能は CLI からは制御できないようです。そのうち対応されるのではないかと思います。