2011年1月5日水曜日

FreeBSD VIMAGEを使ったTCP/IPのルーティング デモンストレーション

こんにちわ。 @hasegaw です。あけましておめでとうございます。今年もよろしくお願いいたします!

本日は カーネル/VM Advent Calendar 私の当番!ということで、 FreeBSD 8.0-RELEASE から標準装備になった VIMAGE がどんなモノで、どういう使い方ができるかについて軽く紹介したいと思います。


■ バックグラウンド: 新人研修で「ネットワークを教えることになったが……」

実は2010年4月、弊社の新人研修の一環として、丸1日かけてネットワークの基本について教えることになりました。

新人研修には、これまでコンピュータなどを専攻してきた新人もいますが、まったく違う分野からやってきた人たちもいます。そこで、インターネットを使うとどうして世界中のコンピュータと通信できるのか、ルーティングの動きを体験してもらおうと思いました。

INTEROP で NICT の nictorを度々見かけていたので、これぐらいかっこいいのを作ろう(嘘)と思いました。
さて、どうやって作ろう? VMware 等でたくさんのゲスト OS を用意したりするのもアリですが、それだと、それなりなスペックの PC なども必要になります。できれば私が普段常用しているノートパソコンで全てが完結するレベルで作りたい……と思い、「ああ、いいのが有るじゃないか」。デモ環境のベースとして、久々に VIMAGE を使ってみることにしたのです。



■ VIMAGE とはなんぞや

FreeBSD で利用できる VIMAGE という機能ですが、これは FreeBSD のネットワークスタックを多重化するための機能です。ひとつの FreeBSD カーネル上で複数のネットワークスタックを動かすことができ、それらは全て独立した形で動きます。IPアドレスなどの情報はもちろん、ルーティングテーブルも独立したものになるため、 1 つの FreeBSD カーネルで複数のノードを再現できます。しかも多重化されるのはネットワークスタックと動作中のプロセスだけ。10ノード分程度のエミュレーションならメモリ128MBのちいさなFreeBSD環境をVMware上で動かしてあげれば十分なので、大きさ的にも、今回のデモには最適です。

ところで、 FreeBSD では、 UNIX の世界で広く使われている chroot によるリソースアクセス制御のほかにも jail と呼ばれるコンテナ技術が(たしか) 4.0-RELEASE の頃から実装されています。これは、 jail 内で動作するプロセスは、その jail 内にあるプロセスしか見えなくなるというタイプのもので、今思えば Linux でいう Virtuozzo のような技術に先駆けて実装された PC UNIX 向けの仮想化技術でありました。とはいえ、 jail はあくまでも「関係のないプロセスが見えなくなる」ものであって、ネットワークスタックなどがパーティションされているわけではありませんでした。

話を VIMAGE に戻しましょう。これは元々  FreeBSD Network Stack Virtualization Project にてネットワークの研究目的で実装されたもので、 FreeBSD ベースのシステム上で複数の仮想ルータや仮想サーバを構築できます。また、 GUI 上でネットワークトポロジを作ることによって、1 台の FreeBSD Box の中で、普通にはひとつのカーネルでは構築できないような複雑なトポロジを実現し、試すことができる、というものです。

VIMAGE は当初 4.11-RELEASE 向けに実装されたコードがあり、私自身も 2005 年頃に見つけて色々と遊んでいたのですが、それが、ついに FreeBSD のメインツリーに取り込まれたという形になります。

なお VIMAGE は FreeBSD jail の機能の一部として実装されています。これまでの jail によるプロセス隔離機能、および VIMAGE によるネットワークスタックの多重化を組み合わせることによって、ひとつの FreeBSD Box の中で Virtuozzo のようなパーティションが可能になったのです。

VIMAGE について、 FreeBSD 8.0-RELEASE リリース当初に後藤大地さんがマイコミジャーナルの記事にて解説されているので、こちらも併せてご覧いただくとよいかと思います。

■ VIMAGE を使うための下準備

さて、VIMAGE を使うには、色々と準備をしなければいけません。特にカーネルですが GENERIC カーネルではなく VIMAGE が有効化されたカーネルを自分で作らないといけません。

options         VIMAGE
nooptions       SCTP

また VIMAGE は jail の機能の一部であり、 jail が使えるようなイメージの準備をしておかないといけません。平たく言えば chroot 利用時のように、ファイルシステム上のどこかに jail 環境のルートディレクトリとなるディレクトリを準備しなければいけません。(ただ試すだけなら、 FreeBSD box の / を使うこともできます。この場合は、プロセス間の干渉などはできませんが、当然 jail 間でファイルは自由にアクセスできてしまいます)

FreeBSD では jail 環境は現状 /usr/jails/$JAIL_NAME/ というパスで作るのが一般的?なようです。 ports コレクションにある ezjail などを利用すると jail 環境を割と簡単に作れるみたいです。私も ezjail を使ってテスト用 jail 環境を作りました(が、一度デモで使うだけのやっつけ環境だったので後から強引に各 jail 内の構成をごりごり弄りました)。

■ VMAGE jail を起動する

あらかじめ準備した jail 用ツリーを使って、 VIMAGE jail を起動するために下記のスクリプトを書きました。本当は ezjail などの管理機能に任せるのが正しいのでしょうが、作業した時点の ezjail は VIMAGE を正しく扱えなかったため断念したように思います。

#!/usr/local/bin/bash

sysctl -w security.jail.socket_unixiproute_only=0
sysctl -w security.jail.allow_raw_sockets=1

for i in 1 2 3 4 5 6 7 8 9; do
        jail -c vnet host.hostname=vm${i} name=vm${i} path=/usr/jails/vm${i} persist
        jexec vm${i} sysctl -w net.inet.ip.forwarding=1
        jexec vm${i} ifconfig lo0 127.0.0.1/24 up
        jexec vm${i} /etc/rc.d/sshd onestart
        jexec vm${i} routed -s -m
        cp /root/master.passwd /usr/jails/vm${i}/etc/master.passwd
        jexec vm${i} pwd_mkdb /etc/master.passwd
        jexec vm${i} pwd_mkdb -p /etc/master.passwd
done

上記スクリプトが何をしているかは、見ていただければ判ると思いますが

  • VIMAGE Jail 「vmX 」を作成

  • TCP/IPのルーティングを許可(ルータ化)

  • とりあえず強引にループバックデバイスを立ち上げる

  • OpenSSHのサーバを立ち上げる

  • 動的ルーティングデーモン(routed)を立ち上げる

  • 親環境の /etc/master.passwd ファイルを jail 内にコピーし、パスワードデータベースを更新する

これで各 VIMAGE Jail で sshd, routed が起動した状態となりました。

netdemo# ps -auxww | grep J
root     1032  0.0  1.5  6676  3680  ??  IsJ   9:27AM   0:00.00 /usr/sbin/sshd
root     1034  0.0  0.5  3348  1368  ??  SsJ   9:27AM   0:00.64 routed -s -m
root     1047  0.0  1.6  6676  4000  ??  IsJ   9:27AM   0:00.01 /usr/sbin/sshd
root     1049  0.0  0.5  3348  1368  ??  SsJ   9:27AM   0:00.32 routed -s -m
root     1062  0.0  1.6  6676  4000  ??  IsJ   9:27AM   0:00.00 /usr/sbin/sshd
root     1064  0.0  0.5  3348  1368  ??  SsJ   9:27AM   0:00.17 routed -s -m
root     1077  0.0  1.6  6676  4008  ??  IsJ   9:27AM   0:00.00 /usr/sbin/sshd
root     1079  0.0  0.5  3348  1360  ??  SsJ   9:28AM   0:00.11 routed -s -m
root     1092  0.0  1.6  6676  3996  ??  IsJ   9:28AM   0:00.00 /usr/sbin/sshd
root     1094  0.0  0.5  3348  1360  ??  SsJ   9:28AM   0:00.16 routed -s -m
root     1107  0.0  1.6  6676  3996  ??  IsJ   9:28AM   0:00.00 /usr/sbin/sshd
root     1109  0.0  0.5  3348  1360  ??  SsJ   9:28AM   0:00.16 routed -s -m
root     1122  0.0  1.6  6676  3996  ??  IsJ   9:28AM   0:00.00 /usr/sbin/sshd
root     1124  0.0  0.5  3348  1360  ??  SsJ   9:28AM   0:00.18 routed -s -m
root     1137  0.0  1.6  6676  4000  ??  IsJ   9:28AM   0:00.00 /usr/sbin/sshd
root     1139  0.0  0.5  3348  1360  ??  SsJ   9:28AM   0:00.17 routed -s -m
root     1152  0.0  1.6  6676  3996  ??  IsJ   9:28AM   0:00.00 /usr/sbin/sshd
root     1154  0.0  0.5  3348  1360  ??  SsJ   9:28AM   0:00.17 routed -s -m


■ VIMAGE Jail にネットワークインターフェイスを

ただ、この状態ではまだ各 VIMAGE Jail はループバックインターフェイスしか持っていません。 jexec コマンドを使って VIMAGE Jail 内に入ることはできますが、たとえ親環境からでも TCP/IP 的に到達することはできません。

続けて、ネットワークインターフェイスを各 VM に割り当てていきます。各 VIMAGE 間を接続するために epair インターフェイスを作成し、それを各 VM に割り当てていきます。

# vm1 - vm2

ifconfig epair create
ifconfig epair0a vnet vm1
ifconfig epair0b vnet vm2
jexec vm1 ifconfig epair0a 10.0.10.1/24 up
jexec vm2 ifconfig epair0b 10.0.10.2/24 up

# vm1 - vm3
ifconfig epair create
ifconfig epair1a vnet vm1
ifconfig epair1b vnet vm3
jexec vm1 ifconfig epair1a 10.0.20.1/24 up
jexec vm3 ifconfig epair1b 10.0.20.2/24 up

# vm2 - vm3
ifconfig epair create
ifconfig epair2a vnet vm2
ifconfig epair2b vnet vm3
jexec vm2 ifconfig epair2a 10.0.30.1/24 up
jexec vm3 ifconfig epair2b 10.0.30.2/24 up

# vm1 - vm4
ifconfig epair create
ifconfig epair3a vnet vm1
ifconfig epair3b vnet vm4
jexec vm1 ifconfig epair3a 10.0.40.1/24 up
jexec vm4 ifconfig epair3b 10.0.40.2/24 up

# vm1 - vm5
ifconfig epair create
ifconfig epair4a vnet vm1
ifconfig epair4b vnet vm5
jexec vm1 ifconfig epair4a 10.0.50.1/24 up
jexec vm5 ifconfig epair4b 10.0.50.2/24 up

# vm2 - vm6
ifconfig epair create
ifconfig epair5a vnet vm2
ifconfig epair5b vnet vm6
jexec vm2 ifconfig epair5a 10.0.60.1/24 up
jexec vm6 ifconfig epair5b 10.0.60.2/24 up

# vm2 - vm7
ifconfig epair create
ifconfig epair6a vnet vm2
ifconfig epair6b vnet vm7
jexec vm2 ifconfig epair6a 10.0.70.1/24 up
jexec vm7 ifconfig epair6b 10.0.70.2/24 up

# vm3 - vm8
ifconfig epair create
ifconfig epair7a vnet vm3
ifconfig epair7b vnet vm8
jexec vm3 ifconfig epair7a 10.0.80.1/24 up
jexec vm8 ifconfig epair7b 10.0.80.2/24 up

# vm3 - vm9
ifconfig epair create
ifconfig epair8a vnet vm3
ifconfig epair8b vnet vm9
jexec vm3 ifconfig epair8a 10.0.90.1/24 up
jexec vm9 ifconfig epair8b 10.0.90.2/24 up

これで、 vm1~9 に Peer to Peer な経路がいくつか設定されました。

各 VIMAGE Jail の中では routed が動いていますので、 routed が自動的に隣接ノードを見つけて、ルーティングテーブルを作成してくれます。

netdemo# jexec vm1 netstat -r
Routing tables

Internet:
Destination        Gateway            Flags    Refs      Use  Netif Expire
10.0.10.0          link#2             U           1        0 epair0
vm1                link#2             UHS         0        0    lo0
10.0.20.0          link#3             U           1        0 epair1
vm1                link#3             UHS         0        0    lo0
10.0.30.0          vm2                UG          0        0 epair0
10.0.40.0          link#4             U           1        0 epair3
vm1                link#4             UHS         0        0    lo0
10.0.50.0          link#5             U           1        0 epair4
vm1                link#5             UHS         0        0    lo0
10.0.60.0          vm2                UG          0        0 epair0
10.0.70.0          vm2                UG          0        0 epair0
10.0.80.0          vm3                UG          0        0 epair1
10.0.90.0          vm3                UG          0        0 epair1
localhost          link#1             UH          0        0    lo0

Internet6:
(省略)

■ VIMAGE Jail 内から PING してみる

各 jail の中はクローズドな FreeBSD 環境になっています。もちろん PING などの操作が可能です。

netdemo# jexec vm1 ping -c 3 vm5
Version 1.4a12+FreeBSD
Usage: traceroute [-adDeFInrSvx] [-f first_ttl] [-g gateway] [-i iface]
        [-m max_ttl] [-p port] [-P proto] [-q nqueries] [-s src_addr]
        [-t tos] [-w waittime] [-A as_server] [-z pausemsecs] host [packetlen]
PING vm5 (10.0.50.2): 56 data bytes
64 bytes from 10.0.50.2: icmp_seq=0 ttl=64 time=1.204 ms
64 bytes from 10.0.50.2: icmp_seq=1 ttl=64 time=0.438 ms
64 bytes from 10.0.50.2: icmp_seq=2 ttl=64 time=0.147 ms

--- vm5 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.147/0.596/1.204/0.446 ms

■ VIMAGE Jail 内から traceroute... あれれ?

しかし残念ながら VMAGE jail 内の traceroute は(8.0-RELEASEの段階では)正しく?動作しませんでした。 traceroute がソースアドレスを決定しようとする際に VIMAGE Jail 内でエラーになる処理をするのが原因でした。 traceroute 時に毎回ソースアドレスを指定すれば問題なく動くのですが、新人研修用のテスト環境で traceroute が動かないとなると、それではちょっとアレです。

というわけで、当時ググりまくったら、この問題を改善するパッチが send-pr されていたため、 traceroute にパッチをあててコンパイルし直すと、普通に traceroute できるようになりました。このパッチですが、すでに -STABLE にも入っていると思いますので、 8.1-RELEASE 以降であれば問題は発生しないでしょう。

参考情報: kern/139454: [jail] traceroute does not work inside jail
http://www.freebsd.org/cgi/query-pr.cgi?pr=139454

netdemo# jexec vm1 traceroute vm6
traceroute to vm6 (10.0.60.2), 64 hops max, 40 byte packets
 1  vm2 (10.0.10.2)  1.274 ms  0.294 ms  0.214 ms
 2  vm6 (10.0.60.2)  1.919 ms  0.404 ms  0.438 ms

■ 一度 VIMAGE のことは忘れて……デモグラフィックをどうやって作るか

さて、ここまで動けばとりあえず最低限のデモはできるでしょう。でも、もっと視覚的に訴えるようなモノを作りたいと思っていたので、どうやって実現するかについて考え始めました。もちろん目標は nicter です。(まだ言うか

どうやってアニメーションさせるかを色々考えたのですが(Delphi + Canvasで描くとかDelphi + OpenGLで描くとか)、せいぜい30分も使わないであろうデモのためにあまり時間をかけるわけにはいきません。そこで、このデモを作ろうと思った頃に話題となっていた、 HTML5 Canvas を使ってお手軽に作ってみることにしました。

…描けました。 nicter ほど格好良くないですが、上等です!さらに、出発点、中継点、終点を渡すとアニメーションを開始するための関数、定期的に画面を更新しアニメーションを進めるための関数も書いて、なんなく動き始めました。
 



■ VIMAGE と デモをどうやって連携させるか……? すみません、フェイクです

さて、ここまでできたら、あとはパケットの流れをもとにアニメーションにどう反映させるか、です。本当なら送信元ホスト、送信先ホストを調べて、ルーティングテーブルなどを解析して、どのようにルーティングするかをシミュレーションするべきでしょう。しかし、どうせ30分使うかどうかのデモにそこまでの時間をかけるのはちょっと厳しいので、tracerouteの出力を解析することにしました。 :p

vm1# cat /usr/sbin/traceroute
#!/bin/sh
export PATH=/usr/sbin/orig:$PATH
# mkdir -p /tmp/log
LOG=/tmp/log/traceroute_log.$$
traceroute $* | tee $LOG

/usr/sbin/traceroute を実はスクリプトにおきかえて、 traceroute された時にはその内容を同時に各 jail 環境の /tmp/log/traceroute_log.$$ に吐き出すようにしておきます。こうしておけば traceroute するたびにログファイルが生成されるので、それを親環境側の Perl プロセスが回収し、どこからどこに traceroute されたかを確認して、先の HTML 5 Canvas な JavaScript に食わせるデータを生成することにしました。

続いて ping コマンド。これは traceroute と違って途中の経路情報が表示されません。困った。

vm1# cat /sbin/ping
#!/bin/sh
export PATH=/usr/sbin/orig:$PATH
# mkdir -p /tmp/log
LOG=/tmp/log/traceroute_log.$$
( traceroute $* > $LOG ) &
ping $*

よし、これでOK (w /tmp/log/配下にログがどんどん溜まっていきます。さらに、誰の traceroute(ping) かは uid を見れば判断できそうです。この情報を使って、 ping/traceroute の実行者が誰なのかわかるようにします。

$ cd /tmp/log
$ ls -l
total 8
-rw-r--r--  1 root     wheel    0 Dec 29 00:54 traceroute_log.1436
-rw-r--r--  1 root     wheel   50 Dec 29 00:55 traceroute_log.1453
-rw-r--r--  1 root     wheel   50 Dec 29 00:58 traceroute_log.1458
-rw-r--r--  1 root     wheel  100 Dec 29 00:58 traceroute_log.1461
-rw-r--r--  1 hasegaw  wheel  100 Dec 29 01:36 traceroute_log.1584

Perl で作ったログ回収&出力デーモンには、パケットを発射する関数を叩く Javascript を生成させて、それを HTML 5 Canvas な Javascript が差分ダウンロードしてはひたすら eval するクソ仕様としました。本当なら JSON などを使うべきでしょうが。

■ 完成!
実際の動作デモ
http://ysr.jp/~doggie/routedemo/


よし!完成です。あまり見た目とか良くないですが、新人に、このデモ環境に実際にログインしてもらい、思い思いのホストに ping したり traceroute したりしてもらいました。 HTML5 Canvas の Javascript は会場のスクリーンに投影し、誰がどこからどこへパケットを飛ばしているかがわかるようにセッティングして、しばらく遊んでもらいました。

また、これまでの流れのとおり routed が動いているので、途中で経路をダウンさせるとそのうちに違う経路にルーティングが変更されることも確認でき、動的ルーティングというものがどういうものなのかを新人に説明しました。

作業量的には、 VMware 上の FreeBSD を立ち上げるのに手こずったのと(構築環境とデモ環境で VMware のバージョンが違ってハマった)、 VIMAGE の traceroute まわりでハマったのと Javascript は普段書き慣れていないのとで、結局数日かかってしまったのですが、割と面白いおもちゃができたかなという感じです。

■ まとめ

さて、そんなわけで、 FreeBSD のネットワーク仮想化機能である VIMAGE の利用例として 、ひとつの FreeBSD で 9 台分のノードをたてて、 TCP/IP のルーティングをデモンストレーションした事例を紹介しました。あまり広く使われていない機能だと思いますが、このように FreeBSD の VIMAGE を利用すると、OSのインスタンスひとつで、ほぼ最小限のリソースだけでも、いろんなネットワークトポロジを再現できるようになっています。これを使って、いわゆる Virtual Router 的なものを作ってもよいでしょうし、色々と楽しめる機能です。

ただ、 8.0-RELEASE からリリースにはいったばかりの機能であることもあり、以下の点には注意が必要です。


  • カーネルの再コンパイルが必要(GENERICカーネルではサポートされない)

  • 一部コマンドが期待通りに動作しない可能性がある(今回であれば traceroute )

まだ私が気づかなかった問題点などもあるのでしょうか、VIMAGEの機能が今後どのように発展しているのかは興味深く思っており、今後とも注視していきたいと思って居ます。

最後に、カーネル/VM Advent Calendar に誘ってくださった @syuu1228 さん、ありがとうございました。おかげで久々に真面目にテキストを書いた気がします。^^;

1 件のコメント:

  1. ここで記載されている、HTML5CanvasのソースやPerl で作ったログ回収&出力デーモンを共有させていただくことは可能でしょうか。
    似たようなデモをやりたいのです。
    よろしくお願いいたします。

    返信削除