# 網路模擬與分析(3/29): 在 mininet 環境建立 switch 功能 + 應用 & mininet 去實現出 SDN 網路架構(ovs) ###### tags: `Mininet`、`SDN`、`OpenFlow`、`OpenvSwitch`、`ARP packet`、`IP packet` ## 透過 Python 程式建立你的第一個 Mininet 環境 (ep.3) ### 在 switch 的環境中實現 vlan & trunk 上一週教完了單臂路由的範例,接下來這個範例要講解當環境有2個 switch 時,要如何進行通訊? 其中 h1, h2 連接著 s1,h3, h4 連接著 s2,而 s1 與 s2 相連,而根據下方拓樸,我們需要在 s1, s2 的 link 使用 trunk,而當封包通過 trunk link 時,讓 h1, h3 的封包去打上 “10” 的標籤,以及 h2, h4 的封包打上 “20” 的標籤。並讓相同標籤的 host 能互相彼此通訊。 ![](https://i.imgur.com/4AnbDwt.png) 因此我們建立一個新的檔案`9.py`: ``` #!/usr/bin/env python from mininet.net import Mininet from mininet.cli import CLI from mininet.link import Link,TCLink,Intf if '__main__' == __name__: net = Mininet(link=TCLink) #h1 is under vlan10 h1 = net.addHost('h1') #h2 is under vlan20 h2 = net.addHost('h2') #h3 is under vlan10 h3 = net.addHost('h3') #h4 is under vlan20 h4 = net.addHost('h4') #s1 is a switch s1 = net.addHost('s1') #s2 is a switch s2 = net.addHost('s2') Link(h1, s1) Link(h2, s1) Link(h3, s2) Link(h4, s2) Link(s1, s2) net.build() s1.cmd("ifconfig s1-eth0 0") s1.cmd("ifconfig s1-eth1 0") s1.cmd("ifconfig s1-eth2 0") s2.cmd("ifconfig s2-eth0 0") s2.cmd("ifconfig s2-eth1 0") s2.cmd("ifconfig s2-eth2 0") s1.cmd("vconfig add s1-eth2 10") s1.cmd("vconfig add s1-eth2 20") s2.cmd("vconfig add s2-eth2 10") s2.cmd("vconfig add s2-eth2 20") s1.cmd("ifconfig s1-eth2.10 up") s1.cmd("ifconfig s1-eth2.20 up") s2.cmd("ifconfig s2-eth2.10 up") s2.cmd("ifconfig s2-eth2.20 up") s1.cmd("brctl addbr brvlan10") s1.cmd("brctl addbr brvlan20") s1.cmd("brctl addif brvlan10 s1-eth0") s1.cmd("brctl addif brvlan20 s1-eth1") s1.cmd("brctl addif brvlan10 s1-eth2.10") s1.cmd("brctl addif brvlan20 s1-eth2.20") s2.cmd("brctl addbr brvlan10") s2.cmd("brctl addbr brvlan20") s2.cmd("brctl addif brvlan10 s2-eth0") s2.cmd("brctl addif brvlan20 s2-eth1") s2.cmd("brctl addif brvlan10 s2-eth2.10") s2.cmd("brctl addif brvlan20 s2-eth2.20") s1.cmd("ifconfig brvlan10 up") s1.cmd("ifconfig brvlan20 up") s2.cmd("ifconfig brvlan10 up") s2.cmd("ifconfig brvlan20 up") h1.cmd("ifconfig h1-eth0 10.0.10.1 netmask 255.255.255.0") h2.cmd("ifconfig h2-eth0 10.0.10.2 netmask 255.255.255.0") h3.cmd("ifconfig h3-eth0 10.0.10.3 netmask 255.255.255.0") h4.cmd("ifconfig h4-eth0 10.0.10.4 netmask 255.255.255.0") CLI(net) net.stop() ``` > 1. 每個 host 與 switch 相連的 link 稱為 access link。 > 2. `vconfig`: 在 s1-eth2, s2-eth2 介面設定 vlan 的功能。 > 3. `brvlan10`: 對應的是 h1, h3,`brvlan20`: 對應的是 h2, h4,其中`brvlan10`以及`brvlan20`需要在 s1, s2 上面進行設定。 儲存後,我們執行`python 9.py`,並且在 mininet 的環境執行`xterm s1 s2`。 首先我們在 s1, s2 終端機執行`ifconfig`,可以看到: ![](https://i.imgur.com/X5xkHmd.png) ![](https://i.imgur.com/eENwrsh.png) s1-eth2 的介面中多了兩張虛擬介面 “.10”, “.20”,是透過程式碼中的`vconfig add s1-eth2 10`,`vconfig add s1-eth2 20`去實現的,同理 s2-eth2 的介面也是。 接下來執行`brctl show`可以看到: ![](https://i.imgur.com/r5Qrhyt.png) “brvlan10” 以及 “brvlan20” 是透過`brctl addbr brvlan10`以及`brctl addbr brvlan20`所建立起來的。 而 interface (以 “brvlan10” 為舉例) 是透過`brctl addif brvlan10 s1-eth0`以及`brctl addif brvlan10 s1-eth2.10`所連接起來的。 因此我們可以測試相同標籤的通訊以及不同標籤的通訊情形。 ``` mininet> h1 ping h3 PING 10.0.10.3 (10.0.10.3) 56(84) bytes of data. 64 bytes from 10.0.10.3: icmp_seq=1 ttl=64 time=0.132 ms ^C --- 10.0.10.3 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.132/0.132/0.132/0.000 ms mininet> h2 ping h4 PING 10.0.10.4 (10.0.10.4) 56(84) bytes of data. 64 bytes from 10.0.10.4: icmp_seq=1 ttl=64 time=0.124 ms ^C --- 10.0.10.4 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.124/0.124/0.124/0.000 ms mininet> h1 ping h2 PING 10.0.10.2 (10.0.10.2) 56(84) bytes of data. ^C --- 10.0.10.2 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> h1 ping h4 PING 10.0.10.4 (10.0.10.4) 56(84) bytes of data. ^C --- 10.0.10.4 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> h2 ping h3 PING 10.0.10.3 (10.0.10.3) 56(84) bytes of data. ^C --- 10.0.10.3 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> h3 ping h4 PING 10.0.10.4 (10.0.10.4) 56(84) bytes of data. ^C --- 10.0.10.4 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms mininet> ``` 除此之外,我們還可以在 s1 開啟 wireshark,去進行監控封包的情形。 我們分別去觀察10的標籤以及20的標籤 ![](https://i.imgur.com/incPHUx.png) ![](https://i.imgur.com/YxhPIgn.png) 可以看到說,當 h1 對 h3 進行通訊時,經過 s1 時封包會被打上10的標籤。而 h3 要回覆封包給 h1 時,也是透過同樣的方法由 s2 打上10的標籤並且回去。 而 h2 ping h4 也是同樣的道理。 --- ### 在 switch 環境中實現單臂路由 我們根據上一個實驗中去建立了2個 switch,並透過 trunk 去進行打上不同標籤以及測試彼此之間的通訊。 我們接著將上個範例延伸,該拓樸由2個 switch 變成3個,其中 s1, s2 連接著 s3,而 s3 與 r1 連接。 ![](https://i.imgur.com/CHwELHt.png) 我們新增一個檔案`10.py`: ``` #!/usr/bin/env python from mininet.net import Mininet from mininet.cli import CLI from mininet.link import Link,TCLink,Intf if '__main__' == __name__: net = Mininet(link=TCLink) #h1 is under vlan10 h1 = net.addHost('h1') #h2 is under vlan20 h2 = net.addHost('h2') #h3 is under vlan10 h3 = net.addHost('h3') #h4 is under vlan20 h4 = net.addHost('h4') #s1 is a switch s1 = net.addHost('s1') #s2 is a switch s2 = net.addHost('s2') #s3 is a switch s3 = net.addHost('s3') #r1 is a router r1 = net.addHost('r1') Link(h1, s1) Link(h2, s1) Link(h3, s2) Link(h4, s2) Link(s1, s3) Link(s2, s3) Link(s3, r1) net.build() s1.cmd("ifconfig s1-eth0 0") s1.cmd("ifconfig s1-eth1 0") s1.cmd("ifconfig s1-eth2 0") s2.cmd("ifconfig s2-eth0 0") s2.cmd("ifconfig s2-eth1 0") s2.cmd("ifconfig s2-eth2 0") s3.cmd("ifconfig s3-eth0 0") s3.cmd("ifconfig s3-eth1 0") s3.cmd("ifconfig s3-eth2 0") r1.cmd("ifconfig r1-eth1 0") s1.cmd("vconfig add s1-eth2 10") s1.cmd("vconfig add s1-eth2 20") s2.cmd("vconfig add s2-eth2 10") s2.cmd("vconfig add s2-eth2 20") s3.cmd("vconfig add s3-eth0 10") s3.cmd("vconfig add s3-eth0 20") s3.cmd("vconfig add s3-eth1 10") s3.cmd("vconfig add s3-eth1 20") s3.cmd("vconfig add s3-eth2 10") s3.cmd("vconfig add s3-eth2 20") r1.cmd("vconfig add r1-eth0 10") r1.cmd("vconfig add r1-eth0 20") s1.cmd("ifconfig s1-eth2.10 up") s1.cmd("ifconfig s1-eth2.20 up") s2.cmd("ifconfig s2-eth2.10 up") s2.cmd("ifconfig s2-eth2.20 up") s3.cmd("ifconfig s3-eth0.10 up") s3.cmd("ifconfig s3-eth0.20 up") s3.cmd("ifconfig s3-eth1.10 up") s3.cmd("ifconfig s3-eth1.20 up") s3.cmd("ifconfig s3-eth2.10 up") s3.cmd("ifconfig s3-eth2.20 up") r1.cmd("ifconfig r1-eth0.10 up") r1.cmd("ifconfig r1-eth0.20 up") s1.cmd("brctl addbr brvlan10") s1.cmd("brctl addbr brvlan20") s1.cmd("brctl addif brvlan10 s1-eth0") s1.cmd("brctl addif brvlan20 s1-eth1") s1.cmd("brctl addif brvlan10 s1-eth2.10") s1.cmd("brctl addif brvlan20 s1-eth2.20") s2.cmd("brctl addbr brvlan10") s2.cmd("brctl addbr brvlan20") s2.cmd("brctl addif brvlan10 s2-eth0") s2.cmd("brctl addif brvlan20 s2-eth1") s2.cmd("brctl addif brvlan10 s2-eth2.10") s2.cmd("brctl addif brvlan20 s2-eth2.20") s3.cmd("brctl addbr brvlan10") s3.cmd("brctl addbr brvlan20") s3.cmd("brctl addif brvlan10 s3-eth0.10") s3.cmd("brctl addif brvlan10 s3-eth1.10") s3.cmd("brctl addif brvlan10 s3-eth2.10") s3.cmd("brctl addif brvlan20 s3-eth0.20") s3.cmd("brctl addif brvlan20 s3-eth1.20") s3.cmd("brctl addif brvlan20 s3-eth2.20") s1.cmd("ifconfig brvlan10 up") s1.cmd("ifconfig brvlan20 up") s2.cmd("ifconfig brvlan10 up") s2.cmd("ifconfig brvlan20 up") s3.cmd("ifconfig brvlan10 up") s3.cmd("ifconfig brvlan20 up") r1.cmd('ifconfig r1-eth0.10 192.168.10.254 netmask 255.255.255.0') r1.cmd('ifconfig r1-eth0.20 192.168.20.254 netmask 255.255.255.0') r1.cmd("echo 1 > /proc/sys/net/ipv4/ip_forward") h1.cmd("ifconfig h1-eth0 192.168.10.1 netmask 255.255.255.0") h1.cmd("ip route add default via 192.168.10.254") h2.cmd("ifconfig h2-eth0 192.168.20.1 netmask 255.255.255.0") h2.cmd("ip route add default via 192.168.20.254") h3.cmd("ifconfig h3-eth0 192.168.10.2 netmask 255.255.255.0") h3.cmd("ip route add default via 192.168.10.254") h4.cmd("ifconfig h4-eth0 192.168.20.2 netmask 255.255.255.0") h4.cmd("ip route add default via 192.168.20.254") CLI(net) net.stop() ``` 儲存後,執行`python 10.py`進到 mininet 的環境內,分別對 host 進行通訊,或是直接執行`pingall`。 ``` mininet> h1 ping h2 PING 192.168.20.1 (192.168.20.1) 56(84) bytes of data. 64 bytes from 192.168.20.1: icmp_seq=1 ttl=63 time=0.112 ms ^C --- 192.168.20.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.112/0.112/0.112/0.000 ms mininet> h1 ping h3 PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data. 64 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=0.216 ms ^C --- 192.168.10.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.216/0.216/0.216/0.000 ms mininet> h1 ping h4 PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data. 64 bytes from 192.168.20.2: icmp_seq=1 ttl=63 time=0.148 ms ^C --- 192.168.20.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.148/0.148/0.148/0.000 ms mininet> h2 ping h3 PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data. 64 bytes from 192.168.10.2: icmp_seq=1 ttl=63 time=0.261 ms ^C --- 192.168.10.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.261/0.261/0.261/0.000 ms mininet> h2 ping h4 PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data. 64 bytes from 192.168.20.2: icmp_seq=1 ttl=64 time=0.145 ms ^C --- 192.168.20.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.145/0.145/0.145/0.000 ms mininet> h3 ping h4 PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data. 64 bytes from 192.168.20.2: icmp_seq=1 ttl=63 time=0.140 ms ^C --- 192.168.20.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.140/0.140/0.140/0.000 ms mininet> ``` 有個地方需要注意的是,`pingall`不一定每一次都能成功,當你的節點設為`net.addHost`時,它會根據每個節點的 IP 去進行通訊,但由於 switch 沒有設定 IP addr,因此在 ping 的過程就會發生丟包的情況。 ``` root@vm2:/home/user/mininet/examples# python 10.py mininet> pingall *** Ping: testing ping reachability h1 -> X X X X X X r1 h2 -> X X X X X X r1 h3 -> X X X X X X r1 h4 -> X X X X X X r1 s1 -> X X X X X X X s2 -> X X X X X X X s3 -> X X X X X X X r1 -> X X X X X X X *** Results: 92% dropped (4/56 received) mininet> ``` --- ### 在 mininet 環境中實現 Hub 功能 我們在前幾個範例實現出在 mininet 環境中設定 bridge, switch 的服務,接下來的實驗是我們在mininet 環境中實現 hub 功能。 ### hub 與 switch 的差別? 其中 hub 最大的特點是透過 **broadcast** 進行封包傳送,因此當 h1 送封包到 h3 時,其他連在這個 hub 的 hosts 是可以用 wireshark 監聽到 h1 送的封包,但只有 h3 可以將封包接收。 但是由於每次在送封包都是用 broadcast 的方式,因此會造成資源消耗很大,且當越多 host 連到同一個 hub 時,網路的速度也會變得更慢。 ![](https://i.imgur.com/xmLKgyE.png) 而 switch 與 hub 不同在於,**switch 有一個 Table 表可以紀錄 MAC Addr 以及對應的 port**,因此當有封包進來時,switch 的 Table 表會去比對封包的 MAC Addr,確認完 MAC Addr 後,將封包送到對應的 port 去完成封包傳輸的動作。 ![](https://i.imgur.com/AFPG34v.png) 值得注意的是,Table 表一開始是空值,而當封包進到 switch 時,switch 會先透過廣播的方式將封包送到每一個 host,去做 **“Address Learning”** 的動作,而確認目的地的 host MAC Address 後,會將“送到哪個 port” 的規則會跟著 Destination MAC Address 的規則一起紀錄到 Table 表中。 ![](https://i.imgur.com/cHH4pOC.png) 因此當 h1 送封包到 h3 時,switch 收到 h1 的封包時,Table 表的規則會長這樣,接著再透過封包送到每個 host 的方式去進行位置學習,而找到 h3 的位置並將封包送過去後,h3 也會回傳一個封包給 h1,而當封包在進到 switch 時,此時就不是以廣播的方式進行傳輸,因為 Table 表已經有了 h1, h3 的 MAC Address 規則。 ![](https://i.imgur.com/jZlIRS9.png) > switch 以廣播的方式進行訪問,當封包送到 h2, h4,因為不是 h3 的 MAC Address,所以封包會被 switch 給 drop 掉,而當 Table 表建立好後,switch 就不會在以廣播的方式訪問了,因此 h2, h4 也就不會再收到來自 h1 的封包了。 **而 switch 的 Table 表裡面的值會經過一個時間後,Aging-Time 會因為 timeout 而把 Table 表內的值清除掉。** 因此我們可以透過`brctl`查看所有指令,其中有一個`setageing`的參數就是控制 aging 的 timeout,當我們把這個參數設為`0`時,這樣代表 aging 會永遠保持 timeout,**也就代表 Table 表就沒有了 Address Learning 的功能,因此這樣的行為就會跟 hub 一樣了。** ``` root@vm2:/home/user/mininet/examples# brctl Usage: brctl [commands] commands: addbr <bridge> add bridge delbr <bridge> delete bridge addif <bridge> <device> add interface to bridge delif <bridge> <device> delete interface from bridge hairpin <bridge> <port> {on|off} turn hairpin on/off setageing <bridge> <time> set ageing time setbridgeprio <bridge> <prio> set bridge priority setfd <bridge> <time> set bridge forward delay sethello <bridge> <time> set hello time setmaxage <bridge> <time> set max message age setpathcost <bridge> <port> <cost> set path cost setportprio <bridge> <port> <prio> set port priority show [ <bridge> ] show a list of bridges showmacs <bridge> show a list of mac addrs showstp <bridge> show bridge stp info stp <bridge> {on|off} turn stp on/off ``` 我們先建立一個檔案`11.py`: ``` #!/usr/bin/env python from mininet.cli import CLI from mininet.net import Mininet from mininet.link import Link,TCLink,Intf if '__main__' == __name__: net = Mininet(link=TCLink) h1 = net.addHost('h1', mac='00:00:00:00:01:00') h2 = net.addHost('h2', mac='00:00:00:00:02:00') h3 = net.addHost('h3', mac='00:00:00:00:03:00') h4 = net.addHost('h4', mac='00:00:00:00:04:00') Link(h1, h4) Link(h2, h4) Link(h3, h4) net.build() h4.cmd("ifconfig h4-eth0 0") h4.cmd("ifconfig h4-eth1 0") h4.cmd("ifconfig h4-eth2 0") h4.cmd("brctl addbr br0") h4.cmd("brctl addif br0 h4-eth0") h4.cmd("brctl addif br0 h4-eth1") h4.cmd("brctl addif br0 h4-eth2") h4.cmd("brctl setageing br0 0") h4.cmd("ifconfig br0 up") CLI(net) net.stop() ``` 其中我們先把`brctl setageing br0 0`給註解掉,去驗證一下 switch 的功能。 儲存後我們執行`python 11.py`,進到 mininet 的環境後,使用`xterm h2`,並且在 h2 的終端機開啟`wireshark`去進行監聽。 而我們在 mininet 使用`h1 ping h3 -c 3`去傳輸封包。 ![](https://i.imgur.com/MozQbsC.png) 結果可以看到,只有一筆 broadcast 的資料去訪問 h2 之外,就沒有監聽到任何的封包了,因此驗證了這是 switch 的行為。 而我們在把程式碼的註解取消掉,再重新執行一次,同樣在 h2 開啟`wireshark`去進行監聽的動作。 ![](https://i.imgur.com/w22FF4P.png) 結果可以看到,當 h1 ping h3 時,h2 可以監聽到3筆來自 h1, h3 的封包,因此驗證了這是 hub 的行為。 --- ## SDN (Software-Defined Networking) ### 什麼是 SDN (Software-Defined Networking) ? SDN 就是把網路設備、平台變成開放式的網路環境,例如早期的功能式手機只有通話以及傳簡訊的功能,但當一些平台在手機上開放 (例如: Android, iOS) 之後,開發人員就可以在平台上面進行程式軟體的開發,因此當使用者在平台上下載一些 APP,例如:Apple Music, Youtube ... 的軟體,手機就不只是用來通話及傳簡訊的功能了,進而變成了可以聽音樂以及觀看影片的設備了。 ![](https://i.imgur.com/jy6mrD8.png) -------------------------------(取自網路)-------------------------------- 而 SDN 也是同樣的概念,當傳統的設備在製作時,是根據廠商需要怎樣的功能,設備就是那樣的功能。因此當設備成型後就不太可能去進行改變,就像是早期的手機一樣,只能用來打電話或傳簡訊。 那 SDN 做的就是讓平台能夠開放,因此**使用者可以把程式下放到網路設備,讓網路設備可以根據使用者的需求去靈活的變換,例如: hub, switch, router, firewall... 的功能。** --- ### Control Plane & Data Plane 在講解 SDN 之前,我們先介紹兩個名詞,分別稱為控制平面 (Control Plane) 以及資料轉發平面 (Data Plane)。 ### 分散式系統 由於早期的網路環境,是屬於分散式系統,例如: 路由器與路由器彼此會透過互相交換資訊,將路由表建立起來,例如: OSPF, RIP。 ![](https://i.imgur.com/EdFw3u4.png) 因此當資料進來時,我們可以根據路由表的規則進行轉發的動作,並且每個設備彼此獨立,且都互相傳遞資訊。 而 Control Plane 控制的就是一些**網路設備的行為、規則** (例如: 路由器的規則表),而 Data Plane 就是**根據 Control Plane 下達的指令去進行動作**,可以想成 Control Plane 是當一個人正在思考要做什麼的反應,而 Data Plane 是一個人思考完之後做出的行為。 ![](https://i.imgur.com/nACSISp.png) --- ### 傳統網路-“分散式系統” 下的 Control Plane & Data Plane 而在傳統的網路環境下,Control Plane 與 Data Plane 是合在一塊的,例如路由器在交換資訊的過程中,會有一些頻寬的佔用,因此當一個路由器在交換資訊時使用了一些頻寬後,對於其他的路由器來說,對於傳遞訊息的效果就沒有比較好了 (因為頻寬被佔用一部分了),而對於分散式架構而言也是一樣的,當路由表規則建立好後 (選擇最短路徑),而 Control Plane 將規則由 Data Plane 去進行封包轉發,因此造成每個裝置都是走相同路徑,而對於非最短路徑而言會造成閒置。 ![](https://i.imgur.com/9D1FfCj.png) --- ### SDN-使用了 “集中式架構” 的 Control Plane & Data Plane **而 SDN 捨棄了傳統網路的架構,將 Control Plane 與 Data Plane 分離,並且使用了集中式架構進行管理**,因此使用者可以在 Control Plane 先撰寫好規則 (例如: 哪一些規則在哪一台 Router 上面進行),並且透過 SDN 控制器進行下放,而 Data Plane 去實現 Control Plane 所要求的轉發功能,因此對於網路環境而言,是能更有效的去部署及管理。 ![](https://i.imgur.com/csrYNQj.png) 值得注意的是,我們的環境是透過軟體的方式去呈現,而所有的網路設備 (例如: Router, Switch, Bridge...) 將會替換成 SDN 看得懂的設備,而這種設備稱為 “OpenvSwitch”,因此未來我們在 SDN 的環境中,所稱的網路設備都叫做 “OpenvSwitch”。 --- ### OpenFlow & OpenvSwitch 1. 我們現在知道,SDN 是透過控制器進行下放規則的方式與網路設備通訊,而最廣泛使用的通訊標準 (規範) 叫做 OpenFlow。 2. 現在市面上也有很多廠商製作 SDN 的網路設備,而我們在軟體所使用的網路設備統一稱為 “OpenvSwitch” ### SDN 手動設定規則到 OpenvSwitch 因此接下來的實驗,我們會先透過手動的方式進行控制 “OpenvSwitch”,之後再將控制的規則寫到控制器。 ![](https://i.imgur.com/XQH6twG.png) 我們先在終端機使用`mn --topo single,2`去建立拓樸,但由於一開始我們會先在 switch 手動設定規則,因此我們會先將 Controller 拿掉。 ![](https://i.imgur.com/NGfUzwC.png) ``` root@vm2:/home/user/mininet/examples# mn --topo single,2 *** Creating network *** Adding controller *** Adding hosts: h1 h2 *** Adding switches: s1 *** Adding links: (h1, s1) (h2, s1) *** Configuring hosts h1 h2 *** Starting controller c0 *** Starting 1 switches s1 ... *** Starting CLI: mininet-wifi> net h1 h1-eth0:s1-eth1 h2 h2-eth0:s1-eth2 s1 lo: s1-eth1:h1-eth0 s1-eth2:h2-eth0 c0 mininet-wifi> ``` 因此我們先開啟另一個終端機,執行`ps -aux | grep controller` > 1. ps (process status): 查看 process 的狀況 > 2. -aux: 顯示所有包含其他使用者的資訊 ``` root@vm2:/home/user# ps -aux | grep controller root 8065 0.0 0.0 4492 716 pts/1 S+ 13:52 0:00 controller ptcp:6653 root 14170 0.0 0.0 14220 992 pts/21 S+ 14:08 0:00 grep --color=auto controller ``` 找到 controller 之後,使用`kill -9 12789` > 1. kill: 殺死正在執行的 process > 2. -9: 強制執行 > 3. 8065: Process ID ``` root@vm2:/home/user# ps -aux | grep controller root 8065 0.0 0.0 4492 716 pts/1 S+ 13:52 0:00 controller ptcp:6653 root 14170 0.0 0.0 14220 992 pts/21 S+ 14:08 0:00 grep --color=auto controller root@vm2:/home/user# kill -9 8065 root@vm2:/home/user# ps -aux | grep controller root 14356 0.0 0.0 14220 1012 pts/21 S+ 14:08 0:00 grep --color=auto controller ``` 我們回到 mininet 的環境中,去測試 h1 ping h2 -c 4,可以發現沒有了控制器,switch 就不知道如何進行轉發,封包自然就無法進行通訊了。 ``` mininet-wifi> h1 ping h2 -c 4 PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. From 10.0.0.1 icmp_seq=1 Destination Host Unreachable From 10.0.0.1 icmp_seq=2 Destination Host Unreachable From 10.0.0.1 icmp_seq=3 Destination Host Unreachable From 10.0.0.1 icmp_seq=4 Destination Host Unreachable --- 10.0.0.2 ping statistics --- 4 packets transmitted, 0 received, +4 errors, 100% packet loss, time 2998ms pipe 4 ``` 我們可以使用`ovs-ofctl -h`去查看 ovs 控制器的所有指令以及參數 ``` root@vm2:/home/user# ovs-ofctl -h ovs-ofctl: OpenFlow switch management utility usage: ovs-ofctl [OPTIONS] COMMAND [ARG...] For OpenFlow switches: show SWITCH show OpenFlow information dump-desc SWITCH print switch description dump-tables SWITCH print table stats dump-table-features SWITCH print table features dump-table-desc SWITCH print table description (OF1.4+) mod-port SWITCH IFACE ACT modify port behavior mod-table SWITCH MOD modify flow table behavior OF1.1/1.2 MOD: controller, continue, drop OF1.4+ MOD: evict, noevict, vacancy:low,high, novacancy get-frags SWITCH print fragment handling behavior set-frags SWITCH FRAG_MODE set fragment handling behavior FRAG_MODE: normal, drop, reassemble, nx-match dump-ports SWITCH [PORT] print port statistics dump-ports-desc SWITCH [PORT] print port descriptions dump-flows SWITCH print all flow entries dump-flows SWITCH FLOW print matching FLOWs dump-aggregate SWITCH print aggregate flow statistics dump-aggregate SWITCH FLOW print aggregate stats for FLOWs queue-stats SWITCH [PORT [QUEUE]] dump queue stats add-flow SWITCH FLOW add flow described by FLOW add-flows SWITCH FILE add flows from FILE mod-flows SWITCH FLOW modify actions of matching FLOWs del-flows SWITCH [FLOW] delete matching FLOWs replace-flows SWITCH FILE replace flows with those in FILE diff-flows SOURCE1 SOURCE2 compare flows from two sources packet-out SWITCH IN_PORT ACTIONS PACKET... execute ACTIONS on PACKET monitor SWITCH [MISSLEN] [invalid_ttl] [watch:[...]] print packets received from SWITCH snoop SWITCH snoop on SWITCH and its controller add-group SWITCH GROUP add group described by GROUP add-groups SWITCH FILE add group from FILE mod-group SWITCH GROUP modify specific group del-groups SWITCH [GROUP] delete matching GROUPs insert-buckets SWITCH [GROUP] add buckets to GROUP remove-buckets SWITCH [GROUP] remove buckets from GROUP dump-group-features SWITCH print group features dump-groups SWITCH [GROUP] print group description dump-group-stats SWITCH [GROUP] print group statistics queue-get-config SWITCH PORT print queue information for port add-meter SWITCH METER add meter described by METER mod-meter SWITCH METER modify specific METER del-meter SWITCH METER delete METER del-meters SWITCH delete all meters dump-meter SWITCH METER print METER configuration dump-meters SWITCH print all meter configuration meter-stats SWITCH [METER] print meter statistics meter-features SWITCH print meter features add-tlv-map SWITCH MAP add TLV option MAPpings del-tlv-map SWITCH [MAP] delete TLV option MAPpings dump-tlv-map SWITCH print TLV option mappings For OpenFlow switches and controllers: probe TARGET probe whether TARGET is up ping TARGET [N] latency of N-byte echos benchmark TARGET N COUNT bandwidth of COUNT N-byte echos SWITCH or TARGET is an active OpenFlow connection method. Other commands: ofp-parse FILE print messages read from FILE ofp-parse-pcap PCAP print OpenFlow read from PCAP Active OpenFlow connection methods: tcp:IP[:PORT] PORT (default: 6653) at remote IP ssl:IP[:PORT] SSL PORT (default: 6653) at remote IP unix:FILE Unix domain socket named FILE PKI configuration (required to use SSL): -p, --private-key=FILE file with private key -c, --certificate=FILE file with certificate for private key -C, --ca-cert=FILE file with peer CA certificate Daemon options: --detach run in background as daemon --no-chdir do not chdir to '/' --pidfile[=FILE] create pidfile (default: /var/run/openvswitch/ovs-ofctl.pid) --overwrite-pidfile with --pidfile, start even if already running OpenFlow version options: -V, --version display version information -O, --protocols set allowed OpenFlow versions (default: OpenFlow10, OpenFlow11, OpenFlow12, OpenFlow13) Logging options: -vSPEC, --verbose=SPEC set logging levels -v, --verbose set maximum verbosity level --log-file[=FILE] enable logging to specified FILE (default: /var/log/openvswitch/ovs-ofctl.log) --syslog-method=(libc|unix:file|udp:ip:port) specify how to send messages to syslog daemon --syslog-target=HOST:PORT also send syslog msgs to HOST:PORT via UDP Other options: --strict use strict match for flow commands --readd replace flows that haven't changed -F, --flow-format=FORMAT force particular flow format -P, --packet-in-format=FRMT force particular packet in format -m, --more be more verbose printing OpenFlow --timestamp (monitor, snoop) print timestamps -t, --timeout=SECS give up after SECS seconds --sort[=field] sort in ascending order --rsort[=field] sort in descending order --unixctl=SOCKET set control socket name -h, --help display this help message -V, --version display version information ``` 其中一個最常見的指令`ovs-ofctl show s1`,可以去查看 s1 的資訊 ``` root@vm2:/home/user# ovs-ofctl show s1 OFPT_FEATURES_REPLY (xid=0x2): dpid:0000000000000001 n_tables:254, n_buffers:256 capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP actions: output enqueue set_vlan_vid set_vlan_pcp strip_vlan mod_dl_src mod_dl_dst mod_nw_src mod_nw_dst mod_nw_tos mod_tp_src mod_tp_dst 1(s1-eth1): addr:d6:db:28:99:f6:ca config: 0 state: 0 current: 10GB-FD COPPER speed: 10000 Mbps now, 0 Mbps max 2(s1-eth2): addr:6e:29:85:2f:b5:27 config: 0 state: 0 current: 10GB-FD COPPER speed: 10000 Mbps now, 0 Mbps max LOCAL(s1): addr:32:61:c5:28:2e:44 config: PORT_DOWN state: LINK_DOWN speed: 0 Mbps now, 0 Mbps max OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0 ``` > 1. dpid (data path ID): 交換姬的 ID,而控制器透過 dpid 去控制下方的交換機。 > 2. capabilities: 交換機所支援的功能,例如:mod_tp_dst (modify transport layer destination) 是將第四層的目的端 port 號進行修改。 而我們也可以透過下方資訊看到,目前的交換機連接著兩個 host 以以及對應的 port 號。 ``` 1(s1-eth1): addr:d6:db:28:99:f6:ca config: 0 state: 0 current: 10GB-FD COPPER speed: 10000 Mbps now, 0 Mbps max 2(s1-eth2): addr:6e:29:85:2f:b5:27 config: 0 state: 0 current: 10GB-FD COPPER speed: 10000 Mbps now, 0 Mbps max ``` 我們可以得知,封包的轉發規則與對應的 port 相關的,我們可以先透過`ovs-ofctl dump-flows s1`去查看 s1 的轉發規則 ``` root@vm2:/home/user# ovs-ofctl dump-flows s1 NXST_FLOW reply (xid=0x4): ``` 可以看到說,由於 controller 已經被移除掉,因此目前 switch 內部沒有任何轉發規則。 因此我們可以先新增一個轉發的規則,透過`ovs-ofctl add-flow s1 in_port=1,actions=output:2` > 1. in_port=1: 封包從1號埠進來 > 2. actions=output:2 封包從2號埠出去 因此對於 h1 送到 h2 封包的規則已經加好了,而 h2 送封包到 h1 的規則也是透過同樣的設定,使用`ovs-ofctl add-flow s1 in_port=2,actions=output:1` ``` root@vm2:/home/user# ovs-ofctl add-flow s1 in_port=1,actions=output:2 root@vm2:/home/user# ovs-ofctl add-flow s1 in_port=2,actions=output:1 root@vm2:/home/user# ovs-ofctl dump-flows s1 NXST_FLOW reply (xid=0x4): cookie=0x0, duration=20.371s, table=0, n_packets=0, n_bytes=0, idle_age=20, in_port=1 actions=output:2 cookie=0x0, duration=3.307s, table=0, n_packets=0, n_bytes=0, idle_age=3, in_port=2 actions=output:1 ``` 因此我們可以回到 mininet 的環境進行測試`h1 ping h2 -c 3` ``` mininet-wifi> h1 ping h2 -c 3 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.605 ms 64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.142 ms 64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.078 ms --- 10.0.0.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2000ms rtt min/avg/max/mdev = 0.078/0.275/0.605/0.234 ms ``` 因此我們就完成了透過手動的方式將規則設定好。 --- **[問題1]** 其中這邊有個有趣的小實驗,當你重新執行了`mn --topo single,2`後,先去進行 h1 ping h2 -c 3,接著再把控制器刪除掉,正常來說 s1 會因為沒有控制器而無法進行封包轉發,但結果卻是可以的,為什麼? ![](https://i.imgur.com/fnLsrzm.png) 因為當在進行第一次的 ping 測試時,s1 的規則表已經有了轉發規則的紀錄,因此當控制器被刪除後,還是不影響 s1 的規則表,因此 h1 依然可以成功的 ping 到 h2。 如果我們要刪除 s1 的規則表,我們可以執行`ovs-ofctl del-flows s1`去刪除所有規則。 若是要刪除一個規則,我們可以使用`ovs-ofctl del-flows s1 [FLOW]`,例如剛剛實驗中的轉發規則有兩條,假設我們不要 h1 到 h2 的規則,我們就可以使用`ovs-ofctl del-flows s1 in_port=1` > 1. [FLOW]: 匹配,例如:in_port=1,之後的實驗中匹配的對象也可以是權重等。 --- **[問題2]** 我們將規則設回去,再回到 s1 的規則表進行查看,其中規則表內可以看到 “n_packets=0”, “n_bytes=0”,而前者是指符合規則表的封包數量,後者是指封包的大小總和。 我們在 s1 開啟 wireshark進行監聽,接著執行`h1 ping h2 -c 3`去傳送封包3次,我們在查看一次 s1 的規則表會發現到 “n_packets=5”,為什麼? ``` root@vm2:/home/user# ovs-ofctl dump-flows s1 NXST_FLOW reply (xid=0x4): cookie=0x0, duration=324.330s, table=0, n_packets=5, n_bytes=378, idle_age=3, in_port=1 actions=output:2 cookie=0x0, duration=321.387s, table=0, n_packets=5, n_bytes=378, idle_age=3, in_port=2 actions=output:1 ``` 這是因為,網路的運作一開始是先發送 ARP 封包去了解對方的網路卡卡號,之後才是 IP packet,因此封包的總和才會是5。 ![](https://i.imgur.com/2yPwYqe.png) --- ### 設定 ARP & IP 規則表 由於現在的網路環境比較單純,因此 s1 路由表沒有設定 ARP 規則是可以的,但是對於未來在進行複雜的環境時,我們就需要設定好 ARP, IP 的規則。 因此我們在終端機執行`mn --topo single,3`,其中 host 變成了3個。 我們一樣先刪除控制器,並檢查 s1 的規則表是否為空值,且封包是傳輸不了的狀態,我們就可以對規則表新增 ARP, IP 的設定了。 其中要注意的是,**ARP Request 是廣播的方式傳送,ARP Response 是單播的方式**,**IP 是以單播的方式傳輸**。但這樣去設定規則太麻煩了,因此我們的 ARP 封包全部設為廣播的方式傳輸,對於規則上設定也相對較為簡單一點。 因此 s1 的 規則可以這樣下: ``` root@vm2:/home/user# ovs-ofctl add-flow s1 in_port=1,arp,actions=output:flood root@vm2:/home/user# ovs-ofctl add-flow s1 in_port=2,arp,actions=output:flood root@vm2:/home/user# ovs-ofctl add-flow s1 in_port=3,arp,actions=output:flood root@vm2:/home/user# ovs-ofctl add-flow s1 ip,nw_dst=10.0.0.1,actions=output:1 root@vm2:/home/user# ovs-ofctl add-flow s1 ip,nw_dst=10.0.0.2,actions=output:2 root@vm2:/home/user# ovs-ofctl add-flow s1 ip,nw_dst=10.0.0.3,actions=output:3 ``` > 1. 規則設定需要先指名是哪種類型的封包,例如:ARP, IP > 2. 指名對應的 port 號 或 IP Addr > 3. nw_dst: network destination IP > 4. flood: 廣播模式 設定完之後我們接著在查看規則表 ``` root@vm2:/home/user# ovs-ofctl dump-flows s1 NXST_FLOW reply (xid=0x4): cookie=0x0, duration=90.487s, table=0, n_packets=0, n_bytes=0, idle_age=90, arp,in_port=1 actions=FLOOD cookie=0x0, duration=84.734s, table=0, n_packets=0, n_bytes=0, idle_age=84, arp,in_port=2 actions=FLOOD cookie=0x0, duration=80.776s, table=0, n_packets=0, n_bytes=0, idle_age=80, arp,in_port=3 actions=FLOOD cookie=0x0, duration=22.991s, table=0, n_packets=0, n_bytes=0, idle_age=22, ip,nw_dst=10.0.0.1 actions=output:1 cookie=0x0, duration=15.942s, table=0, n_packets=0, n_bytes=0, idle_age=15, ip,nw_dst=10.0.0.2 actions=output:2 cookie=0x0, duration=9.438s, table=0, n_packets=0, n_bytes=0, idle_age=9, ip,nw_dst=10.0.0.3 actions=output:3 ``` 確認規則都加上去後,可以進行 ping 的測試,使用`h1 ping h2 -c 3`,接著再回到規則表查看 ![](https://i.imgur.com/TWioW8Z.jpg) 可以看到說,當我們在進行 h1 ping h2 時,會根據規則表紀錄對應的 port 號,因此會看到 arp 各一筆,分別為 Request packet 以及 Response packet,而 IP 的部分可以看到只有 port 1, port 2 的規則有封包的數量以及封包大小總和,而對於 h3 而言是完全沒有任何封包進來的值,因此這樣的設定方式可以去區分 ARP, IP 的封包。 --- ## Reference 1. Youtube video (mininet-ovs1): https://www.youtube.com/watch?v=QKXuQtd37jU 2. https://bluemuta38.pixnet.net/blog/post/45543357 3. http://ccnatiy.blogspot.com/2015/02/switch.html 4. https://ithelp.ithome.com.tw/articles/10245401 5. https://www.ithome.com.tw/node/77353