在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控制器运行后,数据转发层面截图