在SDN三層結構中,我們通過OpenFlow 協議可以控制數據轉發設備的相關行為(包括收集設備的信息),那么控制器上的數據能否通過應用層的程序進行管理調用呢?
SDN(軟件定義網絡)的北向開發是指通過編寫應用程序或API來與SDN控制器進行交互,以實現網絡管理、配置和控制性。實際上就是解決:ryu控制器如何實現與應用層(如web、app等)的通信,利用SDN的北向接口的功能就是和其他軟件實體之間的通信。
本文將展示控制器與應用層如何通信,所以為了便于理解,本次以上博文中編寫的控制器為例子(packet_statics.py)結合mininet模塊創建的網絡拓撲(3_2_topo.py),然后在另外一個應用程序中開啟數據收集(另外一個應用進程app_server.py),獲取在SDN環境中每次通信的dpid和port,并傳輸給應用層(app_server.p),開發環境結構如下。
圖1 框架
數據轉發層面和控制層面都是在Ubuntu虛擬機下面開發,在ryu環境中運行編寫好的程序,在宿主機(本文也放在ubuntu)中運行編寫好的server端程序。
(1)server程序編寫
server端主要用于接收控制器的連接,并接收由控制器發送過來的信息(如dpid和port信息),也可以收集控制器其他的數據,原理一樣。
import socket
import redef main():server1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCPhost = '127.0.0.1' # ubuntu地址,開啟服務功能,用來給ryu主動連接提交數據port = 12345 # 開放端口,自主定義一般大于1024server1.bind((host, port))server1.listen(5)while True:conn, addr = server1.accept()print("----------------------------")print("Success connect from ", addr)try:count = 0while True:data = conn.recv(1024)data = re.split(r'[, :]', data.decode('utf-8')) # 對收到的信息進行解析,包括dpid和portcount += 1print("from {0}:dpid={1}, in_port={2}".format(addr, data[0], data[1]))conn.close()except Exception as error: # 當控制器和應用層斷開連接后,輸出統計信息print('共接收{}條信息。'.format(count - 1))print(error)exit()if __name__ == '__main__':main()
(2)ryu控制器程序
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet, ethernet, ipv6, icmp, icmpv6
import socket
class L2Switch(app_manager.RyuApp):
def __init__(self, *args, **kwargs):super(L2Switch, self).__init__(*args, **kwargs)self.mac_port_table = {}self.protocol_stats = {}# 開啟client,并連接serverself.client1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)dst_host = '127.0.0.1'dst_port = 12345# 防止server端連接不上影響hub的使用try:self.client1.connect((dst_host, dst_port))except Exception as error:print('Connect error:', error)@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)def switch_features_handler(self, ev):datapath = ev.msg.datapathofproto = datapath.ofprotoparser = datapath.ofproto_parsermatch = parser.OFPMatch()actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]self.add_flow(datapath, 0, match, actions)def add_flow(self, datapath, priority, match, actions):ofproto = datapath.ofprotoparser = datapath.ofproto_parserinst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]mod = parser.OFPFlowMod(datapath=datapath, priority=priority, match=match, instructions=inst)datapath.send_msg(mod)@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)def packet_in_handler(self, ev):msg = ev.msgdp = msg.datapathofp = dp.ofprotoofp_parser = dp.ofproto_parserin_port = msg.match['in_port']dpid = dp.idprint(dpid)pkt = packet.Packet(msg.data)icmp_pkt = pkt.get_protocol(icmp.icmp)icmp6_pkt = pkt.get_protocol(icmpv6.icmpv6)if not icmp_pkt and not icmp6_pkt:self.mac_port_table.setdefault(dpid, {})pkt = packet.Packet(msg.data)eth_pkt = pkt.get_protocols(ethernet.ethernet)[0]dst = eth_pkt.dstsrc = eth_pkt.srcself.mac_port_table[dpid][src] = in_portif dst in self.mac_port_table[dpid]:out_port = self.mac_port_table[dpid][dst]else:out_port = ofp.OFPP_FLOODactions = [ofp_parser.OFPActionOutput(out_port)]if out_port != ofp.OFPP_FLOOD:match = ofp_parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)if msg.buffer_id != ofp.OFP_NO_BUFFER:self.add_flow(dp, 1, match, actions, msg.buffer_id)returnelse:self.add_flow(dp, 1, match, actions)data = Noneif msg.buffer_id == ofp.OFP_NO_BUFFER:data = msg.dataout = ofp_parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id,in_port=in_port, actions=actions, data=data)dp.send_msg(out)# send to serverinfo = str(dpid) + ',' + str(in_port)self.client1.send(info.encode()) #把信息傳送到應用程序端
(3) 轉發層程序
轉發層層序主要是負責網絡拓撲環境搭建
from mininet.net import Mininet
from mininet.node import OVSSwitch, Host
from mininet.cli import CLI
from mininet.link import Link
from mininet.node import RemoteController
#import networkx as nx
#import matplotlib.pyplot as pltclass NoIPv6Host(Host):def config(self, **kwargs):super(NoIPv6Host, self).config(**kwargs)self.cmd('sysctl -w net.ipv6.conf.all.disable_ipv6=1')self.cmd('sysctl -w net.ipv6.conf.default.disable_ipv6=1')def create_network():net = Mininet(host=NoIPv6Host)# 創建單個OVS交換機switch1 = net.addSwitch('s1', cls=OVSSwitch,protocols='OpenFlow13')switch2 = net.addSwitch('s2', cls=OVSSwitch,protocols='OpenFlow13')switch3 = net.addSwitch('s3', cls=OVSSwitch,protocols='OpenFlow13')switch4 = net.addSwitch('s4', cls=OVSSwitch,protocols='OpenFlow13')switch5 = net.addSwitch('s5', cls=OVSSwitch,protocols='OpenFlow13')# 創建2個主機host1 = net.addHost('h1', cls=Host, ip='192.168.0.1/24', defaultRoute='via 192.168.0.254')host2 = net.addHost('h2', cls=Host, ip='192.168.0.2/24', defaultRoute='via 192.168.0.254')host3 = net.addHost('h3', cls=Host, ip='192.168.0.3/24', defaultRoute='via 192.168.0.254')host4 = net.addHost('h4', cls=Host, ip='192.168.0.4/24', defaultRoute='via 192.168.0.254')# 連接主機到交換機net.addLink(host1, switch1)net.addLink(host2, switch5)net.addLink(host3, switch5)net.addLink(host4, switch5)
#交換機連接交換機net.addLink(switch1, switch2)net.addLink(switch2, switch5)net.addLink(switch1, switch3)net.addLink(switch3, switch5)net.addLink(switch1, switch4)net.addLink(switch4, switch5)# 指定控制器的IP地址和端口#可以先使用ss -tlnp | grep ryu-manager查看ryu運行后的監聽端口controller_ip = '127.0.0.1'controller_port = 6633# 創建Mininet網絡,并指定控制器和OpenFlow協議版本net.addController('controller', controller=RemoteController, ip=controller_ip, port=controller_port,protocols='OpenFlow13')# 啟動網絡net.start()# 打開命令行界面CLI(net)# 關閉網絡net.stop()if __name__ == '__main__':create_network()
(4)實驗測試
按照以下順序:
1) 運行app_server.py 應用程序監聽
2)運行RYU控制器,觀察與app_server.py 應用連接
3)運行轉發層網絡
可以觀察到數據之間的交互
圖2 app_server 數據接收
圖3 packet_statics控制器運行后,數據轉發層面截圖