在对OpenFlow协议有了一定了解以后,开始尝试如何通过Ryu控制器实现网络中的通信。根据协议,我们知道,当数据信息首次传输到交换机时,由于交换机不存在该数据信息所对应的流表,因此,会触发PacketIn消息,即交换机会将数据信息打包后,通过相应的交换机-控制器的专用通道将数据信息描述之后,传输给控制器,控制器在对数据包进行解析之后,根据相应的逻辑(基于底层网络协议),给交换机添加相应的流表,在这之后,数据包会根据新添加的流表传输给下一个交换机或者目的地址。

下面给出相应的交互流程:

从图中可以看到,左边的PC,假设为h1,右边为h2。首先,下方的控制器与交换机进行了hello以及switch_features两个事件消息的交换,这是交换机与控制器在初始阶段就要做的,与当前是否有数据通过交换机不相关,通过这两个消息事件的处理,控制器能知道交换机的特征信息,这是OpenFlow协议内的部分内容,基础的交互顺序在我之前写的OpenFlow协议中有提到过。然后呢,当数据从h1发往h2的时候,在通信的初始阶段,由于交换机中并没有添加相应的流表以及对应的主机h2的地址,因此,交换机这个时候并不知道该数据要发往哪里,这个时候,就会触发Packet_in消息,这个消息只在相应的数据包到达某台交换机时触发的。然后控制器通过过数据的解析,得出该数据包要发往的方向,给交换机添加相应的流表,出发add_flow事件,这个时候,数据包就可以通过添加的流表,走向h2了。

在知道具体的通信原理之后,就可以撰写代码实现,无环路下的主机之间的通信了,这里,最具有代表性的就是ryu自带的app中的simple_switch_13.py文件了,以下是该文件的代码:

 from ryu.base import app_manager   #继承ryu.base.app_manager
from ryu.controller import ofp_event #继承ryu.controller.ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER #继承ryu.controller.handler.CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls #继承ryu.controller.handler.set_ev_cls
from ryu.ofproto import ofproto_v1_3 #继承ryu.ofproto.ofproto_v1_3
from ryu.lib.packet import packet #继承ryu.lib.packet.packet
from ryu.lib.packet import ethernet #继承ryu.lib.packet.ethernet
from ryu.lib.packet import ether_types #继承ryu.lib.packet.ether_types+63652020 class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] #用于指定OpenFlow的版本,这里指定OpenFlow的版本为1.3版
#定义版本以后,mac_to_port也已经被指定
#初始化环境变量
def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {} #Event Handler是一个拥有事件物件(Event Object)作为参数
#并使用"ryu.controller.handler.set_ev_cls"来修饰decorator函数
#set_ev_cls用于指定事件类别得以接受讯息和交换机状态作为参数
#时间类别命名名称的规则为ryu.controller.ofp_event.EventOFP + <OpenFlow讯息名称>
#如Packet-in讯息的状态下的时间为EventOFPPacketIn #部分名称的作用
#ryu.controller.handler.HANDSHAKE_DISPATCHER 交换HELLO信息
#ryu.controller.handler.CONFIG_DISPATCHER 接收SwitchFeatures讯息
#ryu.controller.handler.MAIN_DISPATCHER 一般状态
#ryu.controller.handler.DEAD_DISPATCHER 连线中断
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath #此讯息用于存储OpenFlow交换机的ryu.controller.controller.Datapath类别所对应的实体
ofproto = datapath.ofproto
parser = datapath.ofproto_parser #ev.msg是用来存储对应事件的OpenFlow讯息类别实体。在这个例子中,则是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures
#Datapath类别是用来处理OpenFlow交换机的重要讯息,例如执行与交换机的通信和触发接收讯息的事件 match = parser.OFPMatch() #为了match所有封包,需要产生一个空的match
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
#为了将封包转送到Controller连接埠,OFPActionOutput类别的实例也会被产生
#指定OFPP_Controller为封包目的地
#设定OFPCML_NO_BUFFER为max_len以便接下来的封包传送
self.add_flow(datapath, 0, match, actions)
#设定Table-miss Flow Entry的优先权为0(最低优先权)
#然后执行add_flow()方法以发送Flow Mod讯息 #交换机本身不仅仅使用Switch features讯息,
#还使用事件处理以取得新增Table-miss Flow Entry的时间点
#Table-miss Flow Entry的优先权为0(最低优先权),而且此Entry可以match所有的封包
#这个Entry的Instruction通常指定为output action #定义add_flow()函数,用于新增Flow Entry
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
#APPLY_ACTIONS是用来设定那些必须立即执行的action所使用的 if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
#OFPFlowMod类别中参数的预设值
#datapath:openflow交换机以及flow table的操作都是通过datapath类别的实体来进行。
#在一般的情况下,会由事件传递给事件管理的讯息中取得,如Packet-In
#cookie(0): controller所设定存储的资料,在Entry的更新或者删除时所需要使用的资料存放地
#并作为过滤器使用,而且不可以作为封包处理的参数
#cookie_mask():Entry的更新或删除时,若是该值为非零,则作为指定Entry的cookie使用
#table_id(0):使用Flow Entry的Table ID
#idle_timeout:flow entry 的有效期限,以秒为单位
#hard_timeout:flow entry的有效期限,但在超过时间限后不会重新归零计算
#priority:优先权,值越大,优先权限越高
#out_put(0):OFPFC_DELETE 和 OFPFC_DELETE_STRICT 命令用來指定输出位置的参数。
#命令为 OFPFC_ADD、 OFPFC_MODIFY、OFPFC_MODIFY_STRICT 时可以忽略。
#若要制定无效,指定输出为OFPP_ANY
#out_group(0):与上相同,作为一个输出位置,但是转到特定的group,若无效,使用OFPG_ANY #通过FlowMod讯息将Flow Entry新增到Flow table中
datapath.send_msg(mod)
#使用OFPFlowMod所产生的实体通过datapath,send_msg()来发送讯息至交换机 #Packet-In事件接收处理位置目的地的封包
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
#OFPPacketIn类别的常用属性
#match: ryu.ofproto.ofproto_v1_3_parser.OFPMatch类别的实体,用来存储接收封包的meta讯息
#data:接收封包本身的binary资料
#tatal_len:接收封包的资料长度
#buffer_id:接受封包的内容。
#若存在OpenFlow交换机上时所指定的ID,如果在没有buffer的状态下,则设定ryu.ofproto.ofproto_v1_3.OFP_NO_BUFFER #更新MAC地址表
def _packet_in_handler(self, ev):
# If you hit this you might want to increase
# the "miss_send_length" of your switch
if ev.msg.msg_len < ev.msg.total_len:
self.logger.debug("packet truncated: only %s of %s bytes",
ev.msg.msg_len, ev.msg.total_len)
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
#从OFPPacketIn类别的match得到接收埠(in_port)的讯息。
#目的MAC地址和来源MAC地址分别使用Ryu的封包函数库,从接收到封包的Ethernet header取得 pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0] if eth.ethertype == ether_types.ETH_TYPE_LLDP:
return
dst = eth.dst
src = eth.src dpid = datapath.id
#是同datapath.id来确认MAC地址表和每个交换机之间的识别来应对连接到多个OpenFlow交换机
self.mac_to_port.setdefault(dpid, {}) self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port) # learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port
#借此得知目的MAC地址表和来源MAC地址,更新MAC地址表 if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD actions = [parser.OFPActionOutput(ofproto.OFPP_IN_PORT)]
#判断转送封包的连接埠
#若目的MAC地址存在于MAC地址表,则判断该连接埠的号码作为输出
#反之若不存在MAC地址表
#则ActionOutput类别的尸体并生成flooding(OFPP_FLOOD)给目的连接埠使用 #转送封包
#在MAC位置表中找寻目的MAC地址,若有则发送Packet-in讯息,并转送封包
# install a flow to avoid packet_in next time
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
# verify if we have a valid buffer_id, if yes avoid to send both
# flow_mod & packet_out
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 1, match, actions)
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out) #buffer_id:指定openflow交换机上封包对应的缓冲区,若不需要,则指定为OFP_NO_BUFFER
#in_port:制定接收到的连接埠号,如果不想使用,就制定为OFPP_CONTROLLER

通过对该代码进行分析,我们可以得到核心的内容,就是,代码中的mac_to_port{}字典,是我们进行路由的依据,它记录了数据包在网络中传输时,经过的交换机的dpid、源目mac地址以及对应的in_port以及out_port的端口号。数据的路由,就是依据它来采取相应的转发action的。

此外,在对sdn网络进行基础的实验的时候,我们会遇到传统网络中最容易遇到的一个问题,网络风暴。这个问题的产生,是由于数据包在网络中的arp广播产生的,对网络的带宽、资源的占有具有很大的损害。

如何解决这个问题,是我们实现环形网络正常通信,所需要面对的基础问题之一。

在此之前,我参考了李呈大神写的arp代理http://www.sdnlab.com/2318.html ,但是,发现这个代码无法正常的在我的环境下运行。不过,他的思路,却给了我很大的启发,结合simple_switch中的代码,我发现,如果单一的实现换路通信,其实只需要在数据包到达每个交换机,进行mac学习的时候,判断当前mac_to_port字典中是否存在过相应的交换机信息,若存在,则判断其进入端口是否相同,若不相同,则发生环路风暴,对该数据包进行丢包操作,这样就能做到环路中的正常通信。附上相应的代码:

 from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import tcp
from ryu.lib.packet import ether_types
from ryu.lib.packet import arp class ARP_PROXY_13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs):
super(ARP_PROXY_13, self).__init__(*args, **kwargs)
self.mac_to_port = {} @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, buffer_id=None):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)] if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod) #mac learning
def mac_learning(self, datapath, src, in_port):
self.mac_to_port.setdefault((datapath,datapath.id), {})
# learn a mac address to avoid FLOOD next time.
if src in self.mac_to_port[(datapath,datapath.id)]:
if in_port != self.mac_to_port[(datapath,datapath.id)][src]:
return False
else:
self.mac_to_port[(datapath,datapath.id)][src] = in_port
return True @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port'] pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] if eth.ethertype == ether_types.ETH_TYPE_LLDP:
match = parser.OFPMatch(eth_type=eth.ethertype)
actions = []
self.add_flow(datapath, 10, match, actions)
return if eth.ethertype == ether_types.ETH_TYPE_IPV6:
match = parser.OFPMatch(eth_type=eth.ethertype)
actions = []
self.add_flow(datapath, 10, match, actions)
return dst = eth.dst
src = eth.src
dpid = datapath.id self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
self.mac_learning(datapath, src, in_port) if dst in self.mac_to_port[(datapath,datapath.id)]:
out_port = self.mac_to_port[(datapath,datapath.id)][dst]
else:
if self.mac_learning(datapath, src, in_port) is False:
out_port = ofproto.OFPPC_NO_RECV
else:
out_port = ofproto.OFPP_FLOOD actions = [parser.OFPActionOutput(out_port)] if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
self.add_flow(datapath, 10, match, actions, msg.buffer_id)
return
else:
self.add_flow(datapath, 10, match, actions) data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
in_port=in_port, actions=actions, data=data)
datapath.send_msg(out)

重点在mac learning这个模块的代码,根据这个代码,就可以实现网络的环路通信。

SDN学习之实现环路通信的更多相关文章

  1. SDN学习之RYU源码安装

    近些天开始接触SDN,即软件定义网络的学习,由于是初学者,想通过写博客来分享自己对sdn学习中所使用的ryu以及mininet仿真软件. Mininet源码安装: 尽管网上对mininet的安装教程很 ...

  2. ZigBee学习四 无线+UART通信

    ZigBee学习四 无线+UART通信 1) 协调器编程 修改coordinator.c文件 byte GenericApp_TransID; // This is the unique messag ...

  3. Vue – 基础学习(2):组件间 通信及参数传递

    Vue – 基础学习(2):组件间 通信及参数传递

  4. 2017年8月9日学习内容存放 #socket通信介绍

    2017年8月9日学习内容存放 #socket通信介绍 ''' OSI七层 应用 表示 会话 传输 网络 ip 数据链路 mac 物理层 网线 http smtp dns ftp ssh snmp i ...

  5. SDN学习之OpenFlow协议分析

    学习SDN相关的学习也已经有快半年了,期间从一无所知到懵懵懂懂,再到现在的有所熟悉,经历了许多,也走了不少弯路,其中,最为忌讳的便是,我在学习过程中,尚未搞明白OpenFlow协议的情况下,便开始对S ...

  6. sdn学习-1(概念:Underlay网络和Overlay网络)

    随着云计算.大数据.移动互联网等新技术的普及,部署大量虚拟机成为一种必然趋势.解决这些虚拟机迁移问题理想的方案是在传统单层网络(Underlay)基础上叠加(Overlay)一层逻辑网络,将网络分成两 ...

  7. SDN学习

    SDN & OpenFlow & Open vSwitch SDN SDN(软件定义网络)是一个概念.是一个思想.一个框架.是一种网络设计理念,它有三个特征 控制平面与转发平面分离 控 ...

  8. ucos实时操作系统学习笔记——任务间通信(消息)

    ucos另一种任务间通信的机制是消息(mbox),个人感觉是它是queue中只有一个信息的特殊情况,从代码中可以很清楚的看到,因为之前有关于queue的学习笔记,所以一并讲一下mbox.为什么有了qu ...

  9. ucos实时操作系统学习笔记——任务间通信(信号量)

    ucos实时操作系统的任务间通信有好多种,本人主要学习了sem, mutex, queue, messagebox这四种.系统内核代码中,这几种任务间通信机制的实现机制相似,接下来记录一下本人对核心代 ...

随机推荐

  1. python去除文本中的HTML标签

    def SplitHtmlTag(file): with open(file,"r") as f,open("result.txt","w+" ...

  2. React服务器渲染最佳实践

    源码地址:https://github.com/skyFi/dva-starter React服务器渲染最佳实践 dva-starter 完美使用 dva react react-router,最好用 ...

  3. Elasticsearch - 快速入门

    Elasticsearch是基于Apache 2.0开源的实时.分布式.分析搜索引擎,相比Lucene,Elasticsearch的上手比较容易,这篇文章主要纪录Elasticsearch的基本概念和 ...

  4. 函数指针|指针函数|C文件操作

    body,table { font-family: 微软雅黑; font-size: 10pt } table { border-collapse: collapse; border: solid g ...

  5. Linux下修改环境终端提示符

    Linux修改环境变量PS1(命令行提示符),可以使用vi编辑/etc/bashrc或/etc/profile文件,在最后加上: export PS1='[\u@\h \W]\$ ' 即可,其中\u显 ...

  6. 统计学习方法:核函数(Kernel function)

    作者:桂. 时间:2017-04-26  12:17:42 链接:http://www.cnblogs.com/xingshansi/p/6767980.html 前言 之前分析的感知机.主成分分析( ...

  7. linq 为什么要用linq linq写法

    LINQ,语言集成查询(Language Integrated Query)是一组用于c#和Visual Basic语言的扩展.它允许编写C#或者Visual Basic代码以查询数据库相同的方式操作 ...

  8. 关于System.Windows.Forms.DateTimePicker的一个Bug

    几天接到客户的反馈,说系统无法查询2017年2月份的账单,原因是没办法选择2017年2月份,没办法选择2月份???,马上开启vs,运行系统,应为市去年的系统,测试数据也是去年的,就查询了2016年2月 ...

  9. hdu1540线段树

    https://vjudge.net/contest/66989#problem/I #include<iostream> #include<cstdio> #include& ...

  10. WebView加载页面的两种方式——网络页面和本地页面

    WebView加载页面的两种方式 一.加载网络页面 加载网络页面,是最简单的一种方式,只需要传入http的URL就可以,实现WebView加载网络页面 代码如下图: 二.加载本地页面 1.加载asse ...