---
# System prepended metadata

title: 網路模擬與分析(3/08)：透過 Python 進行自動化運維 ＆ 在 Mininet 中使用 Iperf (封包測量工具) + gnuplot (繪圖套件)
tags: [gnuplot, Iperf, Ansible, EVE-NG, Mininet]

---

# 網路模擬與分析(3/08)：透過 Python 進行自動化運維 ＆ 在 Mininet 中使用 Iperf (封包測量工具) + gnuplot (繪圖套件)

###### tags: `Mininet`、`Iperf`、`Ansible`、`EVE-NG`、`gnuplot`

## 安裝 python 套件 : pip & 安裝 paramiko

安裝 ppa
```
add-apt-repository ppa:deadsnakes/ppa
```

更新套件＆升級套件
```
apt-get update && apt-get upgrade -y
```

檢查 python 版本
```
root@vm2:/home/user# python3 --version
Python 3.5.2
```

使用`curl`下載 pip 安裝包
```
root@vm2:/home/user# curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1863k  100 1863k    0     0   283k      0  0:00:06  0:00:06 --:--:--  460k
```

並使用`sudo python3 get-pip.py --force-reinstall`進行安裝

**若有出現以下問題，代表你下載的套件包與 python 版本不符，需要下載正確的版本**

```
root@vm2:/home/user# sudo python3 get-pip.py --force-reinstall
ERROR: This script does not work on Python 3.5 The minimum supported Python version is 3.6. Please use https://bootstrap.pypa.io/pip/3.5/get-pip.py instead.
```

下載 paramiko

```
pip install paramiko
```

---
## 使用 EVE-NG 建立拓樸

在VMWare 開啟 EVE 虛擬機，並登入給予的網址`192.168.102.158`

![](https://i.imgur.com/LCB50cX.png)


在瀏覽器輸入該網址並建立一個 lab，即可看到以下畫面。

![](https://i.imgur.com/pVrREdS.png)

新增一個節點 Router，其配置如下

![](https://i.imgur.com/utIdJ9e.png)

新增一個網路，其配置如下

![](https://i.imgur.com/QTD3z88.png)

最後我們在新增2個節點，而該拓樸圖如下

![](https://i.imgur.com/qFD6mjH.png)

---

### 設定 R1 的 dhcp/ssh 功能

將 Router 設定成 dhcp，並且查看該 IP
```
Router>en
Router#conf t
Router(config)#hostname R1
R1(config)#int e0/2
R1(config-if)#ip addr dhcp
R1(config-if)#no sh
R1(config-if)#
*Dec 31 23:01:48.082: %DHCP-6-ADDRESS_ASSIGN: Interface Ethernet0/2 assigned DHCP address 192.168.102.159, mask 255.255.255.0, hostname R1

R1(config-if)#do show ip int brief
Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                unassigned      YES unset  administratively down down
Ethernet0/1                unassigned      YES unset  administratively down down
Ethernet0/2                192.168.102.159 YES DHCP   up                    up
Ethernet0/3                unassigned      YES unset  administratively down down
R1(config-if)#
Translating "pnpntpserver.localdomain"...domain server (192.168.102.2)
```

開啟一台虛擬機，與 R1 進行 ping 的傳輸

```
root@vm2:/home/user# ping 192.168.102.159
PING 192.168.102.159 (192.168.102.159) 56(84) bytes of data.
64 bytes from 192.168.102.159: icmp_seq=1 ttl=255 time=0.903 ms
64 bytes from 192.168.102.159: icmp_seq=2 ttl=255 time=2.87 ms
^C
--- 192.168.102.159 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.903/1.888/2.874/0.986 ms
root@vm2:/home/user# 
```

回到 R1 設定 ssh 的帳號、密碼

```
R1(config-if)#username cisco privilege 15 password ci
sco
R1(config)#ip domain-name test.com
R1(config)#crypto key generate rsa
The name for the keys will be: R1.test.com
Choose the size of the key modulus in the range of 360 to 4096 for your
  General Purpose Keys. Choosing a key modulus greater than 512 may take
  a few minutes.

How many bits in the modulus [512]: 1024
% Generating 1024 bit RSA keys, keys will be non-exportable...
[OK] (elapsed time was 0 seconds)

R1(config)#
*Dec 31 23:11:23.368: %SSH-5-ENABLED: SSH 1.99 has been enabled
R1(config)#ip ssh version 2
R1(config)#line vty 0 4
R1(config-line)#login local
R1(config-line)#transport input ssh
R1(config-line)#
```

設定完後，可以回到虛擬機進行 ssh 測試

```
root@vm2:/home/user# ssh cisco@192.168.102.159
The authenticity of host '192.168.102.159 (192.168.102.159)' can't be established.
RSA key fingerprint is SHA256:RWmcEorbsP+ib7reGDGQNFogzE3rFRt8GMev9E36eMQ.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.102.159' (RSA) to the list of known hosts.
Password: 
Password: 
R1#
```

---

## 透過 ansible 進行多台 host 的部署
### 在 ubuntu 下載 Ansible

下載 ansible 相關指令

```
$ sudo apt update
$ sudo apt install software-properties-common
$ sudo add-apt-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible
```

下載完後，在 EVE 建立該拓樸，由於上一個實驗有先建立相同的拓樸，我們只需要重啟 R1，並重新設定 ssh 規則

![](https://i.imgur.com/Ynx2xXd.png)


設定完後，再重新進行 ssh 連線，接著就會發生以下問題。

```
root@vm2:/home/user# ssh cisco@192.168.102.159
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:qeGdNCj60VTspA1GqXjpny0PGmd0vPO+IO6TWQsH9B8.
Please contact your system administrator.
Add correct host key in /root/.ssh/known_hosts to get rid of this message.
Offending RSA key in /root/.ssh/known_hosts:3
  remove with:
  ssh-keygen -f "/root/.ssh/known_hosts" -R 192.168.102.159
RSA host key for 192.168.102.159 has changed and you have requested strict checking.
Host key verification failed.
```
> 解法: ssh-keygen -f "/root/.ssh/known_hosts" -R 192.168.102.159

確認連線成功並`exit`後，先創建一個資料夾`cisco`，並且創建兩個檔案名叫 ‘hosts’, ‘config_cisco.yml’

1. 'hosts'

```
[cisco]
192.168.102.159 ansible_connection=local
```
> **設定要管理的機器，這邊我們使用的是"Router"**


2. 'config_cisco.yml'

```
- hosts: 192.168.102.159 # R1 的 ip
  gather_facts: yes
  vars:
    - cisco_host_ip: 192.168.102.159
      ssh_username: cisco
      ssh_password: cisco
      enable_open: yes
      enable_password: cisco
  roles:
     - config_cisco
```
> **設定該機器的相關變數**


設定完之後，我們接著要根據 cicso_playbook 的格式進行設定

![](https://i.imgur.com/ojagQwO.png)

因此我們在當前目錄下再創建資料夾`config_cisco`，進入後再創建`tasks`去建立 ansible-playbook 的樹狀圖

再當前目錄下建立檔案 ‘main.yml’

3. 'main.yml'

```
- name: cisco_description_ssh_certification
  set_fact:
    cisco_verification:
      host: "{{cisco_host_ip}}"
      username: "{{ssh_username}}"
      password: "{{ssh_password}}"
      authorize: "{{enable_open}}"
      auth_pass: "{{enable_password}}"

- name: config e0/0
  ios_config:
      provider: "{{cisco_verification}}"
      parents:  interface Ethernet 0/0     
      lines:
        - ip addr 192.168.1.254 255.255.255.0
        - no shut

- name: config e0/1
  ios_config:
      provider: "{{cisco_verification}}"
      parents:  interface Ethernet 0/1     
      lines:
        - ip addr 192.168.2.254 255.255.255.0
        - no shut

- name: show ip interface
  ios_command:
      provider: "{{cisco_verification}}"
      commands: show ip interface brief
  register: show_ip_log

- name: show_ip_log
  debug:
    var: show_ip_log.stdout_lines
  with_items: show_ip_log.results
```

> **根據 Router 下的 Hosts 去管理多台主機的配置＆設定**

儲存後，回到`cisco`的路徑下，使用`tree`查看該樹狀圖

```
root@vm2:/home/user/cisco/config_cisco# cd ..
root@vm2:/home/user/cisco# tree
.
├── config_cisco
│   └── tasks
│       └── main.yml
├── config_cisco.yml
└── hosts

2 directories, 3 files
```
> **若未安裝 `tree`，可使用`apt install tree`**

使用`ansible-playbook config_cisco.yml -i hosts`進行配置
```
PLAY [192.168.102.159] *********************************************************

TASK [Gathering Facts] *********************************************************
[DEPRECATION WARNING]: Distribution Ubuntu 16.04 on host 192.168.102.159 should
 use /usr/bin/python3, but is using /usr/bin/python for backward compatibility 
with prior Ansible releases. A future Ansible release will default to using the
 discovered platform python for this host. See https://docs.ansible.com/ansible
/2.9/reference_appendices/interpreter_discovery.html for more information. This
 feature will be removed in version 2.12. Deprecation warnings can be disabled 
by setting deprecation_warnings=False in ansible.cfg.
ok: [192.168.102.159]

TASK [config_cisco : cisco_description_ssh_certification] **********************
ok: [192.168.102.159]

TASK [config_cisco : config e0/0] **********************************************
changed: [192.168.102.159]

TASK [config_cisco : config e0/1] **********************************************
changed: [192.168.102.159]

TASK [config_cisco : show ip interface] ****************************************
ok: [192.168.102.159]

TASK [config_cisco : show_ip_log] **********************************************
ok: [192.168.102.159] => (item=show_ip_log.results) => {
    "ansible_loop_var": "item", 
    "item": "show_ip_log.results", 
    "show_ip_log.stdout_lines": [
        [
            "Interface                  IP-Address      OK? Method Status                Protocol", 
            "Ethernet0/0                192.168.1.254   YES manual up                    up      ", 
            "Ethernet0/1                192.168.2.254   YES manual up                    up      ", 
            "Ethernet0/2                192.168.102.159 YES DHCP   up                    up      ", 
            "Ethernet0/3                unassigned      YES unset  administratively down down"
        ]
    ]
}

PLAY RECAP *********************************************************************
192.168.102.159            : ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

接著可以回到 Router 進行配置檔是否成功？

```
R1(config)#do show ip int brief
Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.1.254   YES manual up                    up
Ethernet0/1                192.168.2.254   YES manual up                    up
Ethernet0/2                192.168.102.159 YES DHCP   up                    up
Ethernet0/3                unassigned      YES unset  administratively down down
```

---

## 在 Mininet 環境下使用 Iperf + gnuplot

若未安裝 mininet 可以用`sudo apt-get install mininet`

在家目錄創建資料夾`mininet`，進入後使用`mn`創建拓樸

```
root@vm2:/home/user/mininet# mn
*** 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>
```

1. net：查看拓樸資訊
```
h1 h1-eth0:s1-eth1
h2 h2-eth0:s1-eth2
s1 lo:  s1-eth1:h1-eth0 s1-eth2:h2-eth0
c0
```
2. xterm s1 h1 h2：在 mininet 開啟 s1, h1, h2 虛擬機

![s1](https://i.imgur.com/TjAIghA.png)

![h1 h2](https://i.imgur.com/SNh2ZU9.png)
> 預設 IP 是 `10.0.0.0/24` 網域

3. pingall：測試該拓樸連線

```
mininet-wifi> pingall
*** Ping: testing ping reachability
h1 -> h2 
h2 -> h1 
*** Results: 0% dropped (2/2 received)
```

4. h1 ping 10.0.0.2 -c 3：在 Mininet 環境下進行封包測試

![](https://i.imgur.com/GAeKtaq.png)
> `-c(count): + [變數]`


---
### Mininet 進行 HTTP 測試


* 對於 Mininet 的環境下，每一個 Host 都可以視為獨立的主機，因此我們可以做一個簡單的測試。

將 h1 設為 HTTPServer，使用該指令`python -m SimpleHTTPServer 80`

![h1](https://i.imgur.com/htyXHIr.png)


而 h2 可以切換成一般使用者進行 firefox 的測試，去查看 HTTPServer。

![](https://i.imgur.com/cUrc3Yl.png)

h1 的網頁伺服器情形
![](https://i.imgur.com/j1WMAYv.png)

5. 我們也可以透過封包傳遞的過程，去執行`link h1 s1 down`來模擬網路連線中斷的情形。

![](https://i.imgur.com/gvEe8jY.png)

使用`link h1 s1 up`可以將 link 重新連線，因此可以看到封包中斷後又重新發送封包到 h2 了。

![](https://i.imgur.com/CFZeOXT.png)

6. 在 Mininet 執行終端機的內容，可以在 mininet 使用`sh + command`，ex:`sh pwd`

![](https://i.imgur.com/cuV0spX.png)


---

### Iperf 進行網路效能檢測


Iperf 一共有兩種傳輸方式：TCP/UDP，在實驗中，我們將 h1 當作 Client，h2 當作 Server。

1. TCP 封包

h2 [server] 執行`iperf -s -i 1`
> `-s`: server
> `-i(interval) 1`: 計算吞吐量，週期是一秒一次

h1 [client] 執行`iperf -c 10.0.0.2 -t 15`
> `-c`: client
> `10.0.0.2`: 指定 server 的 ip，這邊我們使用 h2 的 ip
> -t(time/duration): 根據傳輸時間去發送 TCP 封包，預設值是10秒，這邊我們設定15秒。


![](https://i.imgur.com/xYZsgOE.png)

當 Client 發送後，可以看到 Server 收到了每一秒封包的值，還有0-15秒封包平均值。

![](https://i.imgur.com/bFgqkGA.png)

2. UDP 封包

h2 [server] 執行`iperf -s -i 1 -u`
> `-u`: 指定 UDP 封包


h1 [client] 執行`iperf -c 10.0.0.2 -t 15 -u -b 100M`
> `-u`: 指定 UDP 封包
> `-b(badnwidth) 100M`: 封包傳送速率為100M
> * TCP 與 UDP 不同在於，TCP 是根據你網路的傳輸速率有多快就會給多快。 

![](https://i.imgur.com/bEJeWhl.png)

我們回到 Server，一樣可以看到每筆封包的數值以及平均。

![](https://i.imgur.com/glEUP3n.png)


3. 多台 Server 設定

假設你的環境需要多個 Server 測試封包的流量，你可以使用`-p`去區分每個 Server 所屬的埠號。

因此我們可以在 Mininet 在新增一台 h1, h2，使用`xterm h1 h2`

h2 [server] 執行`iperf -s -t 1 -u -p 5555`
h2 [server] 執行`iperf -s -t 1 -u -p 6666`

![](https://i.imgur.com/xTZHy0T.png)

因此現在2台 Server 有了不同的 port 號後，我們的 Client 就可以對於不同的 Server 去進行連線。

![](https://i.imgur.com/W4XBjn5.png)

---

### 將數值利用 gnuplot 進行繪圖

上一個實驗我們利用了 Iperf 取得了封包的數值，若你想將數值繪成圖，便可以使用這個套件 gnuplot 將數值繪圖起來。

但是在繪圖之前，我們需要將數據記錄起來，因此我們可以使用`>`將數據導向到所指定的檔案內。

h2 [server] 執行`iperf -s -t 1 -u -p 5555 > udp5555`
> udp5555: 所指定的檔案 ‘udp5555’

而 h1 一樣發送 UDP 封包到 Server`iperf -c 10.0.0.2 -t 10 -b 100M -p 5555`

當 h1 結束後，h2 使用`ctrl + C`結束執行，並且查看 ‘udp5555’ 的檔案內是否有 UDP 的資料？ 

![](https://i.imgur.com/QXah3Ay.png)

結果可以看到資料進來了，代表 UDP 封包的數據已經記錄起來了

![](https://i.imgur.com/7oph5pR.png)

**接著我們可以使用3個 linux 的文字處理：grep, tr, awk 去取得數據的時間跟吞吐量。**

我們可以從 h2 得知數據後，再近一步的過濾出想要的結果。

因此 “udp5555” 過濾結果的過程會如下方所示
```
1. cat udp5555 | grep sec 
// 過濾出“時間”、“吞吐量”共通有的特徵
2. cat udp5555 | grep sec | head -n 20 
// 取前20筆資料
3. cat udp5555 | grep sec | head -n 20 | tr "-" " " 
// 用空格取代每一筆時間軸的 “-”
4. cat udp5555 | grep sec | head -n 20 | tr "-" " "  | awk '{print $4, $8}'
// 取出“時間”、“吞吐量”的數值
5. cat udp5555 | grep sec | head -n 20 | tr "-" " "  | awk '{print $4, $8}' > result.txt
// 將結果映射到 “result.txt”
```

取出結果後，我們可以在終端機使用`gnuplot`的指令進入到 ‘gnuplot’ 內

```
root@vm2:/home/user# gnuplot

	G N U P L O T
	Version 5.0 patchlevel 3    last modified 2016-02-21 

	Copyright (C) 1986-1993, 1998, 2004, 2007-2016
	Thomas Williams, Colin Kelley and many others

	gnuplot home:     http://www.gnuplot.info
	faq, bugs, etc:   type "help FAQ"
	immediate help:   type "help"  (plot window: hit 'h')

Terminal type set to 'qt'
gnuplot>
```

接著選擇 result.txt 進行繪圖`plot "result.txt"`

接著依照你的結果去設定 x 軸, y 軸，以我的例子是：

```
set yrange [0:30]
set ytics 0,5,30
replot
set xrange [0:20]
set xtics 0,1,20
replot
plot "result.txt" with linespoints
set xlabel "tine (sec)"
set ylabel "throughput (Mbis/sec)"
replot
set title "udp throughput"
replot
```
> `xrange`,`yrange`: x 軸, y 軸參數的範圍
> `xtics`,`ytics`: x 軸, y 軸每個參數之間的間隔
> `xlabel`,`ylabel`: x 軸, y 軸的 label
> `title`: 圖片的 title
> `replot`: 更新

繪製完圖片後，將格式轉為 gif 檔`set terminal "gif"`

將圖片儲存成 gif 檔`set output "result.gif"`，並且`replot`。

最後你就可以得到一份完整的結果圖了

![](https://i.imgur.com/0F6AnkK.png)

---

## Reference
1. iperf intro : https://m1016c.pixnet.net/blog/post/145780230
2. iperf command : https://linuxhint.com/iperf_command_usage/
3. p4-bmv2 download : https://github.com/p4lang/behavioral-model
4. p4c download : https://github.com/p4lang/p4c