实验7:基于REST API的SDN北向应用实践
一、实验目的
- 能够编写程序调用OpenDaylight REST API实现特定网络功能;
- 能够编写程序调用Ryu REST API实现特定网络功能。
二、实验环境
- 下载虚拟机软件Oracle VisualBox或VMware;
- 在虚拟机中安装Ubuntu 20.04 Desktop amd64,并完整安装Mininet、OpenDaylight(Carbon版本)、Postman和Ryu;
三、实验要求
(一)基本要求
-
OpenDaylight
(1) 利用Mininet平台搭建下图所示网络拓扑,并连接OpenDaylight;
-
运行odl,并使用
sudo mn --topo=single,3 --mac --controller=remote,ip=127.0.0.1,port=6633 --switch ovsk,protocols=OpenFlow13
构建拓扑。 -
调用OpenDaylight的北向接口获取拓扑信息
import requests as rq from requests.auth import HTTPBasicAuth if __name__ == '__main__': url = 'http://127.0.0.1:8181/restconf/operational/opendaylight-inventory:nodes' headers = {'Content-Type': 'application/json'} response = rq.get(url, headers=headers, auth=HTTPBasicAuth('admin', 'admin')) print(response.content)
可以获取到当前拓扑的node信息
根据拓扑信息进行下面操作
(2) 编写Python程序,调用OpenDaylight的北向接口下发指令删除s1上的流表数据。import requests as rq from requests.auth import HTTPBasicAuth if __name__ == '__main__': url = 'http://127.0.0.1:8181/restconf/operational/opendaylight-inventory:nodes/node/openflow:1/' headers = {'Content-Type': 'application/json'} response = rq.delete(url=url, headers=headers, auth=HTTPBasicAuth('admin', 'admin')) print(response.content)
(3) 编写Python程序,调用OpenDaylight的北向接口下发硬超时流表,实现拓扑内主机h1和h3网络中断20s。
import requests as rq from requests.auth import HTTPBasicAuth if __name__ == '__main__': url = 'http://127.0.0.1:8181/restconf/config/opendaylight-inventory:nodes/node/openflow:1/flow-node-inventory:table/0/flow/1' headers = {'Content-Type': 'application/json'} json = open('flowTable.json').read() response = rq.get(url, data=json, headers=headers, auth=HTTPBasicAuth('admin', 'admin')) print(response.content)
- 其中flowTable.json如下
{ "flow": [ { "id": "1", "match": { "in-port": "1", "ethernet-match": { "ethernet-type": { "type": "0x0800" } }, "ipv4-destination": "10.0.0.3/32" }, "instructions": { "instruction": [ { "order": "0", "apply-actions": { "action": [ { "order": "0", "drop-action": {} } ] } } ] }, "flow-name": "flow1", "priority": "65535", "hard-timeout": "20", "cookie": "2", "table_id": "0" } ] }
- h1 ping h3,并运行py文件
可以看到中间网络中断了20s
(4) 编写Python程序,调用OpenDaylight的北向接口获取s1上活动的流表数。
import requests as rq from requests.auth import HTTPBasicAuth if __name__ == '__main__': url = 'http://127.0.0.1:8181/restconf/operational/opendaylight-inventory:nodes/node/openflow:1/' \ 'flow-node-inventory:table/0/opendaylight-flow-table-statistics:flow-table-statistics' headers = {'Content-Type': 'application/json'} response = rq.get(url=url, headers=headers, auth=HTTPBasicAuth('admin', 'admin')) print(response.content)
-
-
Ryu
(1) 编写Python程序,调用Ryu的北向接口,实现上述OpenDaylight实验拓扑上相同的硬超时流表下发。import requests as rq from requests.auth import HTTPBasicAuth if __name__ == '__main__': url = 'http://127.0.0.1:8080/stats/flowentry/add' headers = {'Content-Type': 'application/json'} json = open('flowTable1.json').read() response = rq.post(url, data=json, headers=headers) print(response.content)
其中flowTable1.json如下
{ "dpid": 1, "cookie": 1, "cookie_mask": 1, "table_id": 0, "hard_timeout": 20, "priority": 65535, "flags": 1, "match":{ "in_port":1 }, "actions":[] }
打开ryu:
ryu-manager ryu.app.simple_switch_13 ryu.app.ofctl_rest
运行py文件,结果如下:
(2) 利用Mininet平台搭建下图所示网络拓扑,要求支持OpenFlow 1.3协议,主机名、交换机名以及端口对应正确。拓扑生成后需连接Ryu,且Ryu应能够提供REST API服务。
from mininet.topo import Topo class ryu_topo(Topo): def __init__(self): Topo.__init__(self) self.addSwitch("s1") self.addSwitch("s2") self.addHost("h1") self.addHost("h2") self.addHost("h3") self.addHost("h4") self.addLink("s1", "h1") self.addLink("s1", "h2") self.addLink("s2", "h3") self.addLink("s2", "h4") self.addLink("s1", "s2") topos = {'ryu_topo': (lambda: ryu_topo())}
(3) 整理一个Shell脚本,参考Ryu REST API的文档,利用curl命令,实现和实验2相同的VLAN。
VLAN_ID Hosts 0 h1 h3 1 h2 h4 - 删除流表
curl -X DELETE http://localhost:8080/stats/flowentry/clear/1 curl -X DELETE http://localhost:8080/stats/flowentry/clear/2
- shell脚本
# 将主机1,2发送来的包打上vlan标记 curl -X POST -d '{ "dpid": 1, "priority": 1, "match":{ "in_port": 1 }, "actions":[ { "type": "PUSH_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "SET_FIELD", "field": "vlan_vid", # Set VLAN ID "value": 4096 # Describe sum of vlan_id(e.g. 6) | OFPVID_PRESENT(0x1000=4096) }, { "type": "OUTPUT", "port": 3 } ] }' http://localhost:8080/stats/flowentry/add curl -X POST -d '{ "dpid": 1, "priority": 1, "match":{ "in_port": 2 }, "actions":[ { "type": "PUSH_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "SET_FIELD", "field": "vlan_vid", # Set VLAN ID "value": 4097 # Describe sum of vlan_id(e.g. 6) | OFPVID_PRESENT(0x1000=4096) }, { "type": "OUTPUT", "port": 3 } ] }' http://localhost:8080/stats/flowentry/add # 将主机3,4发送来的包取出vlan标记 curl -X POST -d '{ "dpid": 1, "priority": 1, "match":{ "vlan_vid": 0 }, "actions":[ { "type": "POP_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "OUTPUT", "port": 1 } ] }' http://localhost:8080/stats/flowentry/add curl -X POST -d '{ "dpid": 1, "priority": 1, "match":{ "vlan_vid": 1 }, "actions":[ { "type": "POP_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "OUTPUT", "port": 2 } ] }' http://localhost:8080/stats/flowentry/add # 将主机3,4发送来的包打上vlan标记 curl -X POST -d '{ "dpid": 2, "priority": 1, "match":{ "in_port": 1 }, "actions":[ { "type": "PUSH_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "SET_FIELD", "field": "vlan_vid", # Set VLAN ID "value": 4096 # Describe sum of vlan_id(e.g. 6) | OFPVID_PRESENT(0x1000=4096) }, { "type": "OUTPUT", "port": 3 } ] }' http://localhost:8080/stats/flowentry/add curl -X POST -d '{ "dpid": 2, "priority": 1, "match":{ "in_port": 2 }, "actions":[ { "type": "PUSH_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "SET_FIELD", "field": "vlan_vid", # Set VLAN ID "value": 4097 # Describe sum of vlan_id(e.g. 6) | OFPVID_PRESENT(0x1000=4096) }, { "type": "OUTPUT", "port": 3 } ] }' http://localhost:8080/stats/flowentry/add curl -X POST -d '{ "dpid": 2, "priority": 1, "match":{ "vlan_vid": 0 }, "actions":[ { "type": "POP_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "OUTPUT", "port": 1 } ] }' http://localhost:8080/stats/flowentry/add curl -X POST -d '{ "dpid": 2, "priority": 1, "match":{ "vlan_vid": 1 }, "actions":[ { "type": "POP_VLAN", # Push a new VLAN tag if a input frame is non-VLAN-tagged "ethertype": 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame }, { "type": "OUTPUT", "port": 2 } ] }' http://localhost:8080/stats/flowentry/add
在(2)拓扑上运行shell脚本,并pingall测试连通性
(二)进阶要求
编程实现基本要求第2部分Ryu(3)中的VLAN划分。
- py代码如下
import json
import requests as rq
if __name__ == '__main__':
url = 'http://127.0.0.1:8080/stats/flowentry/add'
headers = {'Content-Type': 'application/json'}
f = open('flowTables.json').read()
flows = json.loads(f)['flows']
[rq.post(url, data=json.dumps(flows[i]), headers=headers) for i in range(0, 8)]
- 其中flowTables.json如下
{
"flows": [{
"dpid": 1,
"priority": 1,
"match": {
"in_port": 1
},
"actions": [{
"type": "PUSH_VLAN",
"ethertype": 33024
},
{
"type": "SET_FIELD",
"field": "vlan_vid",
"value": 4096
},
{
"type": "OUTPUT",
"port": 3
}
]
},
{
"dpid": 1,
"priority": 1,
"match": {
"in_port": 2
},
"actions": [{
"type": "PUSH_VLAN",
"ethertype": 33024
},
{
"type": "SET_FIELD",
"field": "vlan_vid",
"value": 4097
},
{
"type": "OUTPUT",
"port": 3
}
]
},
{
"dpid": 1,
"priority": 1,
"match": {
"vlan_vid": 0
},
"actions": [{
"type": "POP_VLAN",
"ethertype": 33024
},
{
"type": "OUTPUT",
"port": 1
}
]
},
{
"dpid": 1,
"priority": 1,
"match": {
"vlan_vid": 1
},
"actions": [{
"type": "POP_VLAN",
"ethertype": 33024
},
{
"type": "OUTPUT",
"port": 2
}
]
},
{
"dpid": 2,
"priority": 1,
"match": {
"in_port": 1
},
"actions": [{
"type": "PUSH_VLAN",
"ethertype": 33024
},
{
"type": "SET_FIELD",
"field": "vlan_vid",
"value": 4096
},
{
"type": "OUTPUT",
"port": 3
}
]
}, {
"dpid": 2,
"priority": 1,
"match": {
"in_port": 2
},
"actions": [{
"type": "PUSH_VLAN",
"ethertype": 33024
},
{
"type": "SET_FIELD",
"field": "vlan_vid",
"value": 4097
},
{
"type": "OUTPUT",
"port": 3
}
]
},
{
"dpid": 2,
"priority": 1,
"match": {
"vlan_vid": 0
},
"actions": [{
"type": "POP_VLAN",
"ethertype": 33024
},
{
"type": "OUTPUT",
"port": 1
}
]
},
{
"dpid": 2,
"priority": 1,
"match": {
"vlan_vid": 1
},
"actions": [{
"type": "POP_VLAN",
"ethertype": 33024
},
{
"type": "OUTPUT",
"port": 2
}
]
}
]
}
- 结果如下
(三)心得
实验难度:
- 本次实验是对前两次的进阶,进一步考验通过编程来控制控制器的使用,总体难度较大,需要查阅API文档较多。
实验过程遇到的困难及解决办法:
- 在运行代码来下发流表时,容易遇到以下情况:
检查了几遍后发现是对应的请求方法写错了,更改即可
心得:
- 通过查阅官方文档,进一步学习到了ODL REST API和Ryu REST API的使用,同时也学习到了shell脚本与网络请求的结合使用。
- 这一次实验是对Ryu和ODL的使用掌握的进一步考察,让我对用ODL和Ryu北向接口的使用更加熟悉,也为后续大作业打下良好的基础。