# 網路模擬與分析(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 能互相彼此通訊。

因此我們建立一個新的檔案`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`,可以看到:


s1-eth2 的介面中多了兩張虛擬介面 “.10”, “.20”,是透過程式碼中的`vconfig add s1-eth2 10`,`vconfig add s1-eth2 20`去實現的,同理 s2-eth2 的介面也是。
接下來執行`brctl show`可以看到:

“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的標籤


可以看到說,當 h1 對 h3 進行通訊時,經過 s1 時封包會被打上10的標籤。而 h3 要回覆封包給 h1 時,也是透過同樣的方法由 s2 打上10的標籤並且回去。
而 h2 ping h4 也是同樣的道理。
---
### 在 switch 環境中實現單臂路由
我們根據上一個實驗中去建立了2個 switch,並透過 trunk 去進行打上不同標籤以及測試彼此之間的通訊。
我們接著將上個範例延伸,該拓樸由2個 switch 變成3個,其中 s1, s2 連接著 s3,而 s3 與 r1 連接。

我們新增一個檔案`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 時,網路的速度也會變得更慢。

而 switch 與 hub 不同在於,**switch 有一個 Table 表可以紀錄 MAC Addr 以及對應的 port**,因此當有封包進來時,switch 的 Table 表會去比對封包的 MAC Addr,確認完 MAC Addr 後,將封包送到對應的 port 去完成封包傳輸的動作。

值得注意的是,Table 表一開始是空值,而當封包進到 switch 時,switch 會先透過廣播的方式將封包送到每一個 host,去做 **“Address Learning”** 的動作,而確認目的地的 host MAC Address 後,會將“送到哪個 port” 的規則會跟著 Destination MAC Address 的規則一起紀錄到 Table 表中。

因此當 h1 送封包到 h3 時,switch 收到 h1 的封包時,Table 表的規則會長這樣,接著再透過封包送到每個 host 的方式去進行位置學習,而找到 h3 的位置並將封包送過去後,h3 也會回傳一個封包給 h1,而當封包在進到 switch 時,此時就不是以廣播的方式進行傳輸,因為 Table 表已經有了 h1, h3 的 MAC Address 規則。

> 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`去傳輸封包。

結果可以看到,只有一筆 broadcast 的資料去訪問 h2 之外,就沒有監聽到任何的封包了,因此驗證了這是 switch 的行為。
而我們在把程式碼的註解取消掉,再重新執行一次,同樣在 h2 開啟`wireshark`去進行監聽的動作。

結果可以看到,當 h1 ping h3 時,h2 可以監聽到3筆來自 h1, h3 的封包,因此驗證了這是 hub 的行為。
---
## SDN (Software-Defined Networking)
### 什麼是 SDN (Software-Defined Networking) ?
SDN 就是把網路設備、平台變成開放式的網路環境,例如早期的功能式手機只有通話以及傳簡訊的功能,但當一些平台在手機上開放 (例如: Android, iOS) 之後,開發人員就可以在平台上面進行程式軟體的開發,因此當使用者在平台上下載一些 APP,例如:Apple Music, Youtube ... 的軟體,手機就不只是用來通話及傳簡訊的功能了,進而變成了可以聽音樂以及觀看影片的設備了。

-------------------------------(取自網路)--------------------------------
而 SDN 也是同樣的概念,當傳統的設備在製作時,是根據廠商需要怎樣的功能,設備就是那樣的功能。因此當設備成型後就不太可能去進行改變,就像是早期的手機一樣,只能用來打電話或傳簡訊。
那 SDN 做的就是讓平台能夠開放,因此**使用者可以把程式下放到網路設備,讓網路設備可以根據使用者的需求去靈活的變換,例如: hub, switch, router, firewall... 的功能。**
---
### Control Plane & Data Plane
在講解 SDN 之前,我們先介紹兩個名詞,分別稱為控制平面 (Control Plane) 以及資料轉發平面 (Data Plane)。
### 分散式系統
由於早期的網路環境,是屬於分散式系統,例如: 路由器與路由器彼此會透過互相交換資訊,將路由表建立起來,例如: OSPF, RIP。

因此當資料進來時,我們可以根據路由表的規則進行轉發的動作,並且每個設備彼此獨立,且都互相傳遞資訊。
而 Control Plane 控制的就是一些**網路設備的行為、規則** (例如: 路由器的規則表),而 Data Plane 就是**根據 Control Plane 下達的指令去進行動作**,可以想成 Control Plane 是當一個人正在思考要做什麼的反應,而 Data Plane 是一個人思考完之後做出的行為。

---
### 傳統網路-“分散式系統” 下的 Control Plane & Data Plane
而在傳統的網路環境下,Control Plane 與 Data Plane 是合在一塊的,例如路由器在交換資訊的過程中,會有一些頻寬的佔用,因此當一個路由器在交換資訊時使用了一些頻寬後,對於其他的路由器來說,對於傳遞訊息的效果就沒有比較好了 (因為頻寬被佔用一部分了),而對於分散式架構而言也是一樣的,當路由表規則建立好後 (選擇最短路徑),而 Control Plane 將規則由 Data Plane 去進行封包轉發,因此造成每個裝置都是走相同路徑,而對於非最短路徑而言會造成閒置。

---
### SDN-使用了 “集中式架構” 的 Control Plane & Data Plane
**而 SDN 捨棄了傳統網路的架構,將 Control Plane 與 Data Plane 分離,並且使用了集中式架構進行管理**,因此使用者可以在 Control Plane 先撰寫好規則 (例如: 哪一些規則在哪一台 Router 上面進行),並且透過 SDN 控制器進行下放,而 Data Plane 去實現 Control Plane 所要求的轉發功能,因此對於網路環境而言,是能更有效的去部署及管理。

值得注意的是,我們的環境是透過軟體的方式去呈現,而所有的網路設備 (例如: Router, Switch, Bridge...) 將會替換成 SDN 看得懂的設備,而這種設備稱為 “OpenvSwitch”,因此未來我們在 SDN 的環境中,所稱的網路設備都叫做 “OpenvSwitch”。
---
### OpenFlow & OpenvSwitch
1. 我們現在知道,SDN 是透過控制器進行下放規則的方式與網路設備通訊,而最廣泛使用的通訊標準 (規範) 叫做 OpenFlow。
2. 現在市面上也有很多廠商製作 SDN 的網路設備,而我們在軟體所使用的網路設備統一稱為 “OpenvSwitch”
### SDN 手動設定規則到 OpenvSwitch
因此接下來的實驗,我們會先透過手動的方式進行控制 “OpenvSwitch”,之後再將控制的規則寫到控制器。

我們先在終端機使用`mn --topo single,2`去建立拓樸,但由於一開始我們會先在 switch 手動設定規則,因此我們會先將 Controller 拿掉。

```
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 會因為沒有控制器而無法進行封包轉發,但結果卻是可以的,為什麼?

因為當在進行第一次的 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。

---
### 設定 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`,接著再回到規則表查看

可以看到說,當我們在進行 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