[Linux][Network] mininetをもう少し使い込んでみる

技術のお話し

どんなことをする?

mininetはネットワークエミュレータです。仮想でホストとOpenFlowスイッチを構成し、その箱庭環境の中で色々な動作確認を行うことができます。デフォルトではこんなトポロジーになってます。

コレだとやれることって結構寂しいので、こんなトポロジーは組めないかなと思い、組んでみました。コントローラ側は、前回と同じ「simple_switch.py」を使用します

トポロジーの組み方

トポロジーを組むには、どうやらPythonと言う言語を用いて、構成を書く必要があるようです。今回、4台のスイッチ、10台のホストを上図のように接続する場合、以下のように記述する必要があるようです。これを~/mininet/custom/topo-4sw-10host.pyとして保存しました。

from mininet.topo import Topo

class MyTopo( Topo ):
 "Simple topology example"

 def __init__( self ):

  # Initialize topology
  Topo.__init__( self )

  # Add hosts and switches
  Host1 = self.addHost( 'h1' , ip='192.168.10.1' , mac='00:00:00:00:00:01' )
  Host2 = self.addHost( 'h2' , ip='192.168.10.2' , mac='00:00:00:00:00:02' )
  Host3 = self.addHost( 'h3' , ip='192.168.10.3' , mac='00:00:00:00:00:03' )
  Host4 = self.addHost( 'h4' , ip='192.168.10.4' , mac='00:00:00:00:00:04' )
  Host5 = self.addHost( 'h5' , ip='192.168.10.5' , mac='00:00:00:00:00:05' )
  Host6 = self.addHost( 'h6' , ip='192.168.10.6' , mac='00:00:00:00:00:06' )
  Host7 = self.addHost( 'h7' , ip='192.168.10.7' , mac='00:00:00:00:00:07' )
  Host8 = self.addHost( 'h8' , ip='192.168.10.8' , mac='00:00:00:00:00:08' )
  Host9 = self.addHost( 'h9' , ip='192.168.10.9' , mac='00:00:00:00:00:09' )
  Host10 = self.addHost( 'h10' , ip='192.168.10.10' , mac='00:00:00:00:00:10' )
  Switch1 = self.addSwitch( 's1' , ip='192.168.10.11' , dpid='0000000000000001' )
  Switch2 = self.addSwitch( 's2' , ip='192.168.10.12' , dpid='0000000000000002' )
  Switch3 = self.addSwitch( 's3' , ip='192.168.10.13' , dpid='0000000000000003' )
  Switch4 = self.addSwitch( 's4' , ip='192.168.10.14' , dpid='0000000000000004' )

  # Add links
  self.addLink( Switch1, Switch2 )
  self.addLink( Switch2, Switch3 )
  self.addLink( Switch3, Switch4 )
  self.addLink( Host1, Switch1 )
  self.addLink( Host2, Switch1 )
  self.addLink( Host3, Switch2 )
  self.addLink( Host4, Switch2 )
  self.addLink( Host5, Switch2 )
  self.addLink( Host6, Switch2 )
  self.addLink( Host7, Switch3 )
  self.addLink( Host8, Switch3 )
  self.addLink( Host9, Switch4 )
  self.addLink( Host10, Switch4 )

topos = { 'mytopo': ( lambda: MyTopo() ) }

Pythonと言う言語自体触ったことがなかったので、トライ&エラーで書くだけ書いてみたのですが、以下の点に気をつける必要があるようです。

  • C言語で言う{}部分は「インデント」で認識される
    • つまり、ある処理系の中身を書く際は、その宣言より1段インデントを奥にずらす必要がある
    • 上記テキストでは半角スペースでインデントされていますが、実際にはTabでインデントしてます(それが正しいのかどうかはわからないけど)

その上で、以下のような記述を行っていきます。

  • 変数 = self.addHost ( ‘ホスト名称‘ , ‘ip=IPアドレス‘ , ‘mac=macアドレス‘ )
    • ホストの定義をする箇所です。
    • ホスト名称は任意です。mininetの中でホストを指定する際の識別子になります。
    • macアドレスは箱庭でしか稼働させないので、適当な内容で良さそうです。
    • mininetの中で定義したIPアドレス/macアドレスを用いて通信することになります。
  • 変数 = self.addSwitch ( ‘スイッチ名称‘ , ‘ip=IPアドレス‘ , ‘dpid=任意のID‘ )
    • スイッチの定義をする箇所です。
    • スイッチ名称は任意です。mininetの中でスイッチを指定する際の識別子になります。
    • mininetの中で定義したIPアドレス/IDを用いて通信することになります。
  • self.addLink ( 変数 , 変数 )
    • スイッチやホスト間のリンクを定義します。
    • 変数は先述した「self.addHost」や「self.addSwitch」で定義した変数を使用します。

動かしてみる

早速動かしてみます。まずはryu側を起動します。

[root@OpenFlow ~]# cdryu-manager ryu/app/simple_switch.py
loading app ryu/app/simple_switch.py
loading app ryu.controller.ofp_handler
instantiating app ryu.controller.ofp_handler of OFPHandler
instantiating app ryu/app/simple_switch.py of SimpleSwitch

続いて、mininetを起動します。前回とは異なり、トポロジーのpyファイルを指定した上で起動を行います。

root@mininet:~/mininet/custom# mn --controller=remote,ip=192.168.100.138,port=66 33 --custom ~/mininet/custom/topo-4sw-10host.py --topo mytopo

するとこんな感じで出力され、前回とは異なりそれなりに複雑な構成が取れていることがわかります。

*** Creating network
*** Adding controller
*** Adding hosts:
h1 h2 h3 h4 h5 h6 h7 h8 h9 h10 
*** Adding switches:
s1 s2 s3 s4 
*** Adding links:
(h1, s1) (h2, s1) (h3, s2) (h4, s2) (h5, s2) (h6, s2) (h7, s3) (h8, s3) (h9, s4) (h10, s4) (s1, s2) (s2, s3) (s3, s4) 
*** Configuring hosts
h1 h2 h3 h4 h5 h6 h7 h8 h9 h10 
*** Starting controller
c0 
*** Starting 4 switches
s1 s2 s3 s4 ...
*** Starting CLI:
mininet>

疎通確認をしてみよう

pingallコマンドで疎通確認をしてみます。

mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3 h4 h5 h6 h7 h8 h9 h10 
h2 -> h1 h3 h4 h5 h6 h7 h8 h9 h10 
h3 -> h1 h2 h4 h5 h6 h7 h8 h9 h10 
h4 -> h1 h2 h3 h5 h6 h7 h8 h9 h10 
h5 -> h1 h2 h3 h4 h6 h7 h8 h9 h10 
h6 -> h1 h2 h3 h4 h5 h7 h8 h9 h10 
h7 -> h1 h2 h3 h4 h5 h6 h8 h9 h10 
h8 -> h1 h2 h3 h4 h5 h6 h7 h9 h10 
h9 -> h1 h2 h3 h4 h5 h6 h7 h8 h10 
h10 -> h1 h2 h3 h4 h5 h6 h7 h8 h9 
*** Results: 0% dropped (90/90 received)
mininet>

仮想的にHTTP通信なんかをしてみよう

以下のようにすることで、TinyなWebサーバを指定ホストに構成することが可能みたいです。

mininet> h1 python -m SimpleHTTPServer 80&

これでHost1に対してHTTPサーバがたちました。

Host10からダウンロードを試みてみます。

mininet> h10 wget h1 -O h1
--2017-11-07 23:07:52-- http://192.168.10.1/
192.168.10.1:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 418 
`192.168.10.1' に保存中

192.168.10.1 0%[ ] 0 --.-KB/s 192.168.10.1 100%[===================>] 418 --.-KB/s in 0s

2017-11-07 23:07:52 (65.2 MB/s) - `192.168.10.1' へ保存完了 [418/418]

ダウンロードできたようです。ファイルとして存在するか、確認してみます。

mininet> h10 ls |grep 192.168.10.1
192.168.10.1
mininet> 
mininet> h10 cat 192.168.10.1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href="192.168.10.1">192.168.10.1</a>
<li><a href="README">README</a>
<li><a href="topo-2sw-2host.py">topo-2sw-2host.py</a>
<li><a href="topo-3sw-8host.py">topo-3sw-8host.py</a>
<li><a href="topo-4sw-10host.py">topo-4sw-10host.py</a>
</ul>
<hr>
</body>
</html>
mininet>

おお、ダウンロードできてますなぁ。

ホストのプロセスリストを見てみる

Host1のプロセスリストを見てみましょう。以下の24079プロセスがTiny HTTP Serverプロセスですね。

mininet> h1 ps -a
Serving HTTP on 0.0.0.0 port 80 ...
192.168.10.10 - - [07/Nov/2017 23:07:52] "GET / HTTP/1.1" 200 -
 PID TTY TIME CMD
22065 pts/8 00:00:00 sudo
22066 pts/8 00:00:00 su
22067 pts/8 00:00:00 bash
22216 pts/9 00:00:00 sudo
22217 pts/9 00:00:00 su
22218 pts/9 00:00:00 bash
23704 pts/8 00:00:00 mn
24079 pts/11 00:00:00 python
24090 pts/11 00:00:00 ps
mininet>

構成情報をダンプする

dumpコマンドを使用します。こんなふうに出ます。

mininet> dump
<Host h1: h1-eth0:192.168.10.1 pid=23714> 
<Host h2: h2-eth0:192.168.10.2 pid=23717> 
<Host h3: h3-eth0:192.168.10.3 pid=23720> 
<Host h4: h4-eth0:192.168.10.4 pid=23723> 
<Host h5: h5-eth0:192.168.10.5 pid=23726> 
<Host h6: h6-eth0:192.168.10.6 pid=23729> 
<Host h7: h7-eth0:192.168.10.7 pid=23732> 
<Host h8: h8-eth0:192.168.10.8 pid=23735> 
<Host h9: h9-eth0:192.168.10.9 pid=23738> 
<Host h10: h10-eth0:192.168.10.10 pid=23741> 
<OVSSwitch s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None,s1-eth3:None pid=23747> 
<OVSSwitch s2: lo:127.0.0.1,s2-eth1:None,s2-eth2:None,s2-eth3:None,s2-eth4:None,s2-eth5:None,s2-eth6:None pid=23750> 
<OVSSwitch s3: lo:127.0.0.1,s3-eth1:None,s3-eth2:None,s3-eth3:None,s3-eth4:None pid=23753> 
<OVSSwitch s4: lo:127.0.0.1,s4-eth1:None,s4-eth2:None,s4-eth3:None pid=23756> 
<RemoteController{'ip': '192.168.100.138', 'port': 6633} c0: 192.168.100.138:6633 pid=23708> 
mininet>

リンク状態の確認

linksコマンドを使用します。どれがどの機器の何のポートに接続されているかが確認できます。

mininet> links
h1-eth0<->s1-eth2 (OK OK)
h2-eth0<->s1-eth3 (OK OK)
h3-eth0<->s2-eth3 (OK OK)
h4-eth0<->s2-eth4 (OK OK)
h5-eth0<->s2-eth5 (OK OK)
h6-eth0<->s2-eth6 (OK OK)
h7-eth0<->s3-eth3 (OK OK)
h8-eth0<->s3-eth4 (OK OK)
h9-eth0<->s4-eth2 (OK OK)
h10-eth0<->s4-eth3 (OK OK)
s1-eth1<->s2-eth1 (OK OK)
s2-eth2<->s3-eth1 (OK OK)
s3-eth2<->s4-eth1 (OK OK)
mininet>

インタフェースリストの出力

intfsコマンドを使用します。どうやらスイッチに関しては、1ポートは管理用ポートとして、あとは接続されている分だけポートが準備されるようですね。

mininet> intfs
h1: h1-eth0
h2: h2-eth0
h3: h3-eth0
h4: h4-eth0
h5: h5-eth0
h6: h6-eth0
h7: h7-eth0
h8: h8-eth0
h9: h9-eth0
h10: h10-eth0
s1: lo,s1-eth1,s1-eth2,s1-eth3
s2: lo,s2-eth1,s2-eth2,s2-eth3,s2-eth4,s2-eth5,s2-eth6
s3: lo,s3-eth1,s3-eth2,s3-eth3,s3-eth4
s4: lo,s4-eth1,s4-eth2,s4-eth3
c0: 
mininet>

フロー状態のダンプ

dpctl dump-flows コマンドを使用します。各スイッチのフロー状態がダンプされるみたいです。

mininet> dpctl dump-flows
*** s1 ------------------------------------------------------------------------
NXST_FLOW reply (xid=0x4):
 cookie=0x0, duration=305.981s, table=0, n_packets=3, n_bytes=238, idle_age=300, in_port=3,dl_dst=00:00:00:00:00:01 actions=output:2
 cookie=0x0, duration=305.979s, table=0, n_packets=2, n_bytes=140, idle_age=300, in_port=2,dl_dst=00:00:00:00:00:02 actions=output:3
 cookie=0x0, duration=305.969s, table=0, n_packets=275778, n_bytes=18213819, idle_age=156, in_port=1,dl_dst=00:00:00:00:00:01 actions=output:2
 cookie=0x0, duration=305.967s, table=0, n_packets=2, n_bytes=140, idle_age=300, in_port=2,dl_dst=00:00:00:00:00:03 actions=output:1
 cookie=0x0, duration=305.955s, table=0, n_packets=2, n_bytes=140, idle_age=300, in_port=2,dl_dst=00:00:00:00:00:04 actions=output:1
 cookie=0x0, duration=305.944s, table=0, n_packets=2, n_bytes=140, idle_age=300, in_port=2,dl_dst=00:00:00:00:00:05 actions=output:1
 cookie=0x0, duration=305.933s, table=0, n_packets=2, n_bytes=140, idle_age=300, in_port=2,dl_dst=00:00:00:00:00:06 actions=output:1
         :
     :
*** s2 ------------------------------------------------------------------------
NXST_FLOW reply (xid=0x4):
 cookie=0x0, duration=305.974s, table=0, n_packets=3, n_bytes=238, idle_age=300, in_port=3,dl_dst=00:00:00:00:00:01 actions=output:1
 cookie=0x0, duration=305.969s, table=0, n_packets=5, n_bytes=378, idle_age=300, in_port=1,dl_dst=00:00:00:00:00:03 actions=output:3
 cookie=0x0, duration=305.961s, table=0, n_packets=3, n_bytes=238, idle_age=300, in_port=4,dl_dst=00:00:00:00:00:01 actions=output:1
     :
     :

pingall詳細結果出力

pingallfull コマンドを使用することで、単純な到達性だけではなく、応答時間を測ることができます。

mininet> pingallfull
*** Ping: testing ping reachability
h1 -> h2 h3 h4 h5 h6 h7 h8 h9 h10 
h2 -> h1 h3 h4 h5 h6 h7 h8 h9 h10 
h3 -> h1 h2 h4 h5 h6 h7 h8 h9 h10 
h4 -> h1 h2 h3 h5 h6 h7 h8 h9 h10 
h5 -> h1 h2 h3 h4 h6 h7 h8 h9 h10 
h6 -> h1 h2 h3 h4 h5 h7 h8 h9 h10 
h7 -> h1 h2 h3 h4 h5 h6 h8 h9 h10 
h8 -> h1 h2 h3 h4 h5 h6 h7 h9 h10 
h9 -> h1 h2 h3 h4 h5 h6 h7 h8 h10 
h10 -> h1 h2 h3 h4 h5 h6 h7 h8 h9 
*** Results: 
 h1->h2: 1/1, rtt min/avg/max/mdev 0.401/0.401/0.401/0.000 ms
 h1->h3: 1/1, rtt min/avg/max/mdev 0.440/0.440/0.440/0.000 ms
 h1->h4: 1/1, rtt min/avg/max/mdev 0.339/0.339/0.339/0.000 ms
 h1->h5: 1/1, rtt min/avg/max/mdev 0.313/0.313/0.313/0.000 ms
 h1->h6: 1/1, rtt min/avg/max/mdev 0.254/0.254/0.254/0.000 ms
 h1->h7: 1/1, rtt min/avg/max/mdev 0.629/0.629/0.629/0.000 ms
 h1->h8: 1/1, rtt min/avg/max/mdev 0.274/0.274/0.274/0.000 ms
 h1->h9: 1/1, rtt min/avg/max/mdev 0.472/0.472/0.472/0.000 ms
 h1->h10: 1/1, rtt min/avg/max/mdev 0.347/0.347/0.347/0.000 ms
 h2->h1: 1/1, rtt min/avg/max/mdev 0.027/0.027/0.027/0.000 ms
                 :
          :
 h10->h6: 1/1, rtt min/avg/max/mdev 0.027/0.027/0.027/0.000 ms
 h10->h7: 1/1, rtt min/avg/max/mdev 0.025/0.025/0.025/0.000 ms
 h10->h8: 1/1, rtt min/avg/max/mdev 0.029/0.029/0.029/0.000 ms
 h10->h9: 1/1, rtt min/avg/max/mdev 0.024/0.024/0.024/0.000 ms
mininet>

性能測定(TCP)

iperf 通信元 通信先 で実行します。TCP帯域の測定ができます。

mininet> iperf h1 h10
*** Iperf: testing TCP bandwidth between h1 and h10 
*** Results: ['22.1 Gbits/sec', '22.1 Gbits/sec']
mininet>

性能測定(UDP)

iperfudp 帯域 通信元 通信先 で実行します。UDP帯域の上限を設定できるみたいです。その中でどれほどの帯域を使っているかが計測できるようです。

mininet> iperfudp 1000M h2 h10
*** Iperf: testing UDP bandwidth between h2 and h10 
*** Results: ['1000M', '801 Mbits/sec', '801 Mbits/sec']
mininet>

後片付け

プロンプトで exit コマンドを実行することですべてのスイッチ・ホストを畳み、プログラムを終了します。

mininet> exit
*** Stopping 1 controllers
c0 
*** Stopping 13 links
.............
*** Stopping 4 switches
s1 s2 s3 s4 
*** Stopping 10 hosts
h1 h2 h3 h4 h5 h6 h7 h8 h9 h10 
*** Done
completed in 362.308 seconds
root@mininet:~/mininet/custom#

コントローラ側でもこんな出力が出ます。

port deleted 2
port deleted 3
port deleted 3
port deleted 4
port deleted 3
port deleted 6
port deleted 5
port deleted 4
port deleted 1
port deleted 1
port deleted 3
port deleted 2
port deleted 1
port deleted 2
port deleted 1
port deleted 2

コントローラ側では、ryu-managerはフォアグラウンドで動いているので、Ctrl+Cで終了します。

まとめ

mininetは使ってみると比較的簡単にネットワークトポロジーを組んで遊べるので、こりゃぁいいなぁ~って感じです。ただ、やっぱりPythonと言う言語をちゃんと理解してないのと、OpenFlowコントローラが各スイッチに異なる役割を与えられるのか、実際の運用としてどう管理するのかはよく分かってません。

まだまだ理解が足りてないなと感じるものの、結構こりゃァ面白いなぁとも感じたりしていて、もう少し触ってみようそうしようってな気分です。

<追記:20171108>

Mininet Python API Referenceなるものを見つけましたヽ(=´▽`=)ノ。これ読んでもう少し理解を深めようそうしよう。

Tags:

Comments are closed

PAGE TOP