图1 SDN框架
通过利用Ryu框架的丰富功能以及RYU应用程序开发中的事件驱动(装饰器)的编程模型,我们能够开发符合需求的SDN控制器应用程序。通过开发的RYU控制器可以实现各种网络策略控制和管理功能。在SDN软件定义网络编程中,RYU应用程序开发基于图1中的OpenFlow协议实现对特定拓扑中转发层的策略制定和控制,并根据特定的策略生成相应的流表下发到特定拓扑中的ovs交换机中。
一,传统网络
图2
在传统网络中每个交换机都是各自独立管理,控制和维护自己的转发策略,每个交换机上都有对应的MAC地址表,在mac记录表中登记了当前交换机连接的各个主机mac地址和对应的交换机连接端口。
二, SDN软件定义网络
与传统网络不同的是,在SDN网络中,每个ovs交换机不进行mac地址学习,所以ovs交换机也不存储MAC表。地址学习操作由控制器实现,控制器根据学习到的mac地址表进行制定策略,并将制定的结果以流表形式下发给交换机。OVS交换机按照流表进行数据的转发。
1 RYU应用程序原理
根据上文的描述,在SDN网络中OVS的流表由控制器下发,我们可以根据需求通过控制器的编程开发制定不同的网络流路径和行为。下面我们将开发第一个控制器,实现:
(1) 给特定的网络ovs交换机下发默认的流表,当ovs交换机中没有可匹配的项目时,把当前数据包使用packet_in 的方式发送到控制器
(2)控制器收集ovs交换机发送的packet_in数据包信息,建立mac地址转发表,同时根据mac表学习情况生成对应的action通过packet_out下发到交换机的流表中
(3)使用ryu-manager启动对应的应用程序进行测试
(4) 准备好对应的拓扑程序
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 plt
def create_network():
net = Mininet()
# 创建单个OVS交换机
switch1 = net.addSwitch('s1', cls=OVSSwitch,protocols='OpenFlow13')
switch2 = net.addSwitch('s2', 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, switch1)
net.addLink(host3, switch2)
net.addLink(host4, switch2)
#交换机连接交换机
net.addLink(switch1, switch2)
# 指定控制器的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()
2 ryu应用程序实现
(1)初始程序
from ryu.base import app_manager
class ryu1(app_manager.RyuApp): #使用app_manager创建一个控制器类
def __init__(self, *args, **kwargs):
super(ryu1, self).__init__(*args, **kwargs) # 类初始化
把以上代码保存到ryu安装路径下的app文件夹下为:l2.py然后就可以使用ryu-manager运行,运行效果如图3所示
根据运行我们可以发现,当前实现的ryu只是一个空程序,还没有任何逻辑处理功能,因此我们如果使用某个拓扑(3_2_topo.py)是无法对接成功的。
(2)程序功能强化
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
class ryu1(app_manager.RyuApp):
def __init__(self, *args, **kwargs):
super(ryu1, self).__init__(*args, **kwargs)
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, MAIN_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser=datapath.ofproto_parser
match=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_parser = datapath.ofproto_parser
parser = datapath.ofproto_parser
ins=[parser.OFPActionOutput(ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod=ofproto_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,actions=actions,instructions=ins)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
def packet_in_hander(self,ev):
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
ofp_parser = dp.ofproto_parser
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
data = None
if msg.buffer_id == ofp.OFP_NO_BUFFER:
data = msg.data
out = ofp_parser.OFPPacketOut(
datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
actions=actions, data=data)
dp.send_msg(out)
测试发现,拓扑中的ovs与控制器已经开始数据交互(但是只有request和reply包),packet_in包还没有,因此无法触发packet_in_hander(self,ev)函数
从测试结果我们可以发现,在ovs与控制器之间采用OpenFlow握手消息进行交互情况:
(1)Hello消息:在握手的起始阶段,交换机和控制器互相发送Hello消息以建立连接。
消息用于指示各方支持OpenFlow协议,并在消息中声明所使用的OpenFlow版本
(2)Features Request和Features Reply消息:在完成Hello消息的交换后,控制器会发送一个Features Request消息给交换机,请求交换机发送其特性和能力信息。交换机收到Features Request消息后,会回复一个Features Reply消息,其中包含了交换机的特性和能力信息。
通过交换这些握手消息,交换机和控制器可以互相确认对方的存在、协商OpenFlow版本,并了解彼此的特性和能力,以便在后续的通信中进行适当的协议和功能配置。握手过程的成功完成为后续的控制器-交换机通信奠定了基础。
值得注意的是:
OFPSwitchFeatures消息是在交换机与控制器之间完成hello握手过程后发送的第一个消息。控制器可以通过监听EventOFPSwitchFeatures事件来处理接收到的OFPSwitchFeatures消息,尤其是下发默认流表,使得ovs在还没有流表时能够把数据发送到控制器中,以便控制器收集、学习mac地址,并为流表下发做准备。
对应的代码:
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser=datapath.ofproto_parser
match=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.ofproto
parser = datapath.ofproto_parser
inst=[parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
mod=parser.OFPFlowMod(datapath=datapath, priority=priority,match=match, instructions=inst)
datapath.send_msg(mod)
此时。已经可以成功在ovs中添加默认流表(但是流表的内容只是指导ovs把流表中没有匹配的流发送到控制器(产生packet_in事件),还需要进一步晚上packet_in事件的处理逻辑,使得当packet-in事件处理函数中能够产生packet_out 事件(控制器到交换机的数据,优先级为1的流表)
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
def packet_in_hander(self,ev):
msg = ev.msg
dp = msg.datapath
ofp = dp.ofproto
ofp_parser = dp.ofproto_parser
in_port = msg.match['in_port']
dpid=dp.id
self.mac_port_table.setdefault(dpid,{})
pkt=packet.Packet(msg.data)
eth_pkt=pkt.get_protocols(ethernet.ethernet)[0]
dst=eth_pkt.dst
print(dst)
src=eth_pkt.src
self.mac_port_table[dpid][src]=in_port
if dst in self.mac_port_table[dpid]:
out_port=self.mac_port_table[dpid][dst]
else:
out_port=ofp.OFPP_FLOOD
actions=[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)
# verify if we have a valid buffer_id, if yes avoid to send both
# flow_mod & packet_out
if msg.buffer_id != ofp.OFP_NO_BUFFER:
self.add_flow(dp, 1, match, actions, msg.buffer_id)
print("1")
return
else:
self.add_flow(dp, 1, match, actions)
print("2")
data = None
if msg.buffer_id == ofp.OFP_NO_BUFFER:
data = msg.data
out = ofp_parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
dp.send_msg(out)
测试,各个ovs中的主机之间可以互通,且能抓取到完整数据交互,如图