SDN学习之实现环路通信

在对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文件了,以下是该文件的代码:

  1 from ryu.base import app_manager   #继承ryu.base.app_manager
  2 from ryu.controller import ofp_event  #继承ryu.controller.ofp_event
  3 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER  #继承ryu.controller.handler.CONFIG_DISPATCHER, MAIN_DISPATCHER
  4 from ryu.controller.handler import set_ev_cls   #继承ryu.controller.handler.set_ev_cls
  5 from ryu.ofproto import ofproto_v1_3    #继承ryu.ofproto.ofproto_v1_3
  6 from ryu.lib.packet import packet    #继承ryu.lib.packet.packet
  7 from ryu.lib.packet import ethernet   #继承ryu.lib.packet.ethernet
  8 from ryu.lib.packet import ether_types  #继承ryu.lib.packet.ether_types+63652020
  9
 10 class SimpleSwitch13(app_manager.RyuApp):
 11     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]   #用于指定OpenFlow的版本,这里指定OpenFlow的版本为1.3版
 12                                                 #定义版本以后,mac_to_port也已经被指定
 13     #初始化环境变量
 14     def __init__(self, *args, **kwargs):
 15         super(SimpleSwitch13, self).__init__(*args, **kwargs)
 16         self.mac_to_port = {}
 17
 18     #Event Handler是一个拥有事件物件(Event Object)作为参数
 19     #并使用"ryu.controller.handler.set_ev_cls"来修饰decorator函数
 20     #set_ev_cls用于指定事件类别得以接受讯息和交换机状态作为参数
 21     #时间类别命名名称的规则为ryu.controller.ofp_event.EventOFP + <OpenFlow讯息名称>
 22     #如Packet-in讯息的状态下的时间为EventOFPPacketIn
 23
 24     #部分名称的作用
 25     #ryu.controller.handler.HANDSHAKE_DISPATCHER  交换HELLO信息
 26     #ryu.controller.handler.CONFIG_DISPATCHER   接收SwitchFeatures讯息
 27     #ryu.controller.handler.MAIN_DISPATCHER   一般状态
 28     #ryu.controller.handler.DEAD_DISPATCHER  连线中断
 29     @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
 30     def switch_features_handler(self, ev):
 31         datapath = ev.msg.datapath     #此讯息用于存储OpenFlow交换机的ryu.controller.controller.Datapath类别所对应的实体
 32         ofproto = datapath.ofproto
 33         parser = datapath.ofproto_parser
 34
 35         #ev.msg是用来存储对应事件的OpenFlow讯息类别实体。在这个例子中,则是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures
 36         #Datapath类别是用来处理OpenFlow交换机的重要讯息,例如执行与交换机的通信和触发接收讯息的事件
 37
 38         match = parser.OFPMatch()   #为了match所有封包,需要产生一个空的match
 39         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
 40                                           ofproto.OFPCML_NO_BUFFER)]
 41         #为了将封包转送到Controller连接埠,OFPActionOutput类别的实例也会被产生
 42         #指定OFPP_Controller为封包目的地
 43         #设定OFPCML_NO_BUFFER为max_len以便接下来的封包传送
 44         self.add_flow(datapath, 0, match, actions)
 45         #设定Table-miss Flow Entry的优先权为0(最低优先权)
 46         #然后执行add_flow()方法以发送Flow Mod讯息
 47
 48         #交换机本身不仅仅使用Switch features讯息,
 49         #还使用事件处理以取得新增Table-miss Flow Entry的时间点
 50         #Table-miss Flow Entry的优先权为0(最低优先权),而且此Entry可以match所有的封包
 51         #这个Entry的Instruction通常指定为output action
 52
 53     #定义add_flow()函数,用于新增Flow Entry
 54     def add_flow(self, datapath, priority, match, actions, buffer_id=None):
 55         ofproto = datapath.ofproto
 56         parser = datapath.ofproto_parser
 57
 58         inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
 59                                              actions)]
 60         #APPLY_ACTIONS是用来设定那些必须立即执行的action所使用的
 61
 62         if buffer_id:
 63             mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
 64                                     priority=priority, match=match,
 65                                     instructions=inst)
 66         else:
 67             mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
 68                                     match=match, instructions=inst)
 69         #OFPFlowMod类别中参数的预设值
 70         #datapath:openflow交换机以及flow table的操作都是通过datapath类别的实体来进行。
 71         #在一般的情况下,会由事件传递给事件管理的讯息中取得,如Packet-In
 72         #cookie(0): controller所设定存储的资料,在Entry的更新或者删除时所需要使用的资料存放地
 73         #并作为过滤器使用,而且不可以作为封包处理的参数
 74         #cookie_mask():Entry的更新或删除时,若是该值为非零,则作为指定Entry的cookie使用
 75         #table_id(0):使用Flow Entry的Table ID
 76         #idle_timeout:flow entry 的有效期限,以秒为单位
 77         #hard_timeout:flow entry的有效期限,但在超过时间限后不会重新归零计算
 78         #priority:优先权,值越大,优先权限越高
 79         #out_put(0):OFPFC_DELETE 和 OFPFC_DELETE_STRICT 命令用來指定输出位置的参数。
 80         #命令为 OFPFC_ADD、 OFPFC_MODIFY、OFPFC_MODIFY_STRICT 时可以忽略。
 81         #若要制定无效,指定输出为OFPP_ANY
 82         #out_group(0):与上相同,作为一个输出位置,但是转到特定的group,若无效,使用OFPG_ANY
 83
 84
 85         #通过FlowMod讯息将Flow Entry新增到Flow table中
 86         datapath.send_msg(mod)
 87         #使用OFPFlowMod所产生的实体通过datapath,send_msg()来发送讯息至交换机
 88
 89     #Packet-In事件接收处理位置目的地的封包
 90     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
 91     #OFPPacketIn类别的常用属性
 92     #match: ryu.ofproto.ofproto_v1_3_parser.OFPMatch类别的实体,用来存储接收封包的meta讯息
 93     #data:接收封包本身的binary资料
 94     #tatal_len:接收封包的资料长度
 95     #buffer_id:接受封包的内容。
 96     #若存在OpenFlow交换机上时所指定的ID,如果在没有buffer的状态下,则设定ryu.ofproto.ofproto_v1_3.OFP_NO_BUFFER
 97
 98     #更新MAC地址表
 99     def _packet_in_handler(self, ev):
100         # If you hit this you might want to increase
101         # the "miss_send_length" of your switch
102         if ev.msg.msg_len < ev.msg.total_len:
103             self.logger.debug("packet truncated: only %s of %s bytes",
104                               ev.msg.msg_len, ev.msg.total_len)
105         msg = ev.msg
106         datapath = msg.datapath
107         ofproto = datapath.ofproto
108         parser = datapath.ofproto_parser
109         in_port = msg.match[‘in_port‘]
110         #从OFPPacketIn类别的match得到接收埠(in_port)的讯息。
111         #目的MAC地址和来源MAC地址分别使用Ryu的封包函数库,从接收到封包的Ethernet header取得
112
113         pkt = packet.Packet(msg.data)
114         eth = pkt.get_protocols(ethernet.ethernet)[0]
115
116         if eth.ethertype == ether_types.ETH_TYPE_LLDP:
117             return
118         dst = eth.dst
119         src = eth.src
120
121         dpid = datapath.id
122         #是同datapath.id来确认MAC地址表和每个交换机之间的识别来应对连接到多个OpenFlow交换机
123         self.mac_to_port.setdefault(dpid, {})
124
125         self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
126
127         # learn a mac address to avoid FLOOD next time.
128         self.mac_to_port[dpid][src] = in_port
129         #借此得知目的MAC地址表和来源MAC地址,更新MAC地址表
130
131
132         if dst in self.mac_to_port[dpid]:
133             out_port = self.mac_to_port[dpid][dst]
134         else:
135             out_port = ofproto.OFPP_FLOOD
136
137         actions = [parser.OFPActionOutput(ofproto.OFPP_IN_PORT)]
138         #判断转送封包的连接埠
139         #若目的MAC地址存在于MAC地址表,则判断该连接埠的号码作为输出
140         #反之若不存在MAC地址表
141         #则ActionOutput类别的尸体并生成flooding(OFPP_FLOOD)给目的连接埠使用
142
143         #转送封包
144         #在MAC位置表中找寻目的MAC地址,若有则发送Packet-in讯息,并转送封包
145         # install a flow to avoid packet_in next time
146         if out_port != ofproto.OFPP_FLOOD:
147             match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
148             # verify if we have a valid buffer_id, if yes avoid to send both
149             # flow_mod & packet_out
150             if msg.buffer_id != ofproto.OFP_NO_BUFFER:
151                 self.add_flow(datapath, 1, match, actions, msg.buffer_id)
152                 return
153             else:
154                 self.add_flow(datapath, 1, match, actions)
155         data = None
156         if msg.buffer_id == ofproto.OFP_NO_BUFFER:
157             data = msg.data
158
159         out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
160                                   in_port=in_port, actions=actions, data=data)
161         datapath.send_msg(out)
162
163         #buffer_id:指定openflow交换机上封包对应的缓冲区,若不需要,则指定为OFP_NO_BUFFER
164         #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字典中是否存在过相应的交换机信息,若存在,则判断其进入端口是否相同,若不相同,则发生环路风暴,对该数据包进行丢包操作,这样就能做到环路中的正常通信。附上相应的代码:

  1 from ryu.base import app_manager
  2 from ryu.controller import ofp_event
  3 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
  4 from ryu.controller.handler import set_ev_cls
  5 from ryu.ofproto import ofproto_v1_3
  6 from ryu.lib.packet import packet
  7 from ryu.lib.packet import ethernet
  8 from ryu.lib.packet import tcp
  9 from ryu.lib.packet import ether_types
 10 from ryu.lib.packet import arp
 11
 12 class ARP_PROXY_13(app_manager.RyuApp):
 13     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
 14
 15     def __init__(self, *args, **kwargs):
 16         super(ARP_PROXY_13, self).__init__(*args, **kwargs)
 17         self.mac_to_port = {}
 18
 19     @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
 20     def switch_features_handler(self, ev):
 21         datapath = ev.msg.datapath
 22         ofproto = datapath.ofproto
 23         parser = datapath.ofproto_parser
 24
 25         match = parser.OFPMatch()
 26         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
 27                                           ofproto.OFPCML_NO_BUFFER)]
 28         self.add_flow(datapath, 0, match, actions)
 29
 30     def add_flow(self, datapath, priority, match, actions, buffer_id=None):
 31         ofproto = datapath.ofproto
 32         parser = datapath.ofproto_parser
 33
 34         inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
 35                                              actions)]
 36
 37         if buffer_id:
 38           mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
 39                              priority=priority, match=match,
 40                              instructions=inst)
 41         else:
 42             mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
 43                              match=match, instructions=inst)
 44         datapath.send_msg(mod)
 45
 46 #mac learning
 47     def mac_learning(self, datapath, src, in_port):
 48         self.mac_to_port.setdefault((datapath,datapath.id), {})
 49         # learn a mac address to avoid FLOOD next time.
 50         if src in self.mac_to_port[(datapath,datapath.id)]:
 51             if in_port != self.mac_to_port[(datapath,datapath.id)][src]:
 52                 return False
 53         else:
 54             self.mac_to_port[(datapath,datapath.id)][src] = in_port
 55             return True
 56
 57     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
 58     def _packet_in_handler(self, ev):
 59         msg = ev.msg
 60         datapath = msg.datapath
 61         ofproto = datapath.ofproto
 62         parser = datapath.ofproto_parser
 63         in_port = msg.match[‘in_port‘]
 64
 65         pkt = packet.Packet(msg.data)
 66
 67         eth = pkt.get_protocols(ethernet.ethernet)[0]
 68
 69         if eth.ethertype == ether_types.ETH_TYPE_LLDP:
 70             match = parser.OFPMatch(eth_type=eth.ethertype)
 71             actions = []
 72             self.add_flow(datapath, 10, match, actions)
 73             return
 74
 75         if eth.ethertype == ether_types.ETH_TYPE_IPV6:
 76             match = parser.OFPMatch(eth_type=eth.ethertype)
 77             actions = []
 78             self.add_flow(datapath, 10, match, actions)
 79             return
 80
 81         dst = eth.dst
 82         src = eth.src
 83         dpid = datapath.id
 84
 85
 86         self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
 87         self.mac_learning(datapath, src, in_port)
 88
 89         if dst in self.mac_to_port[(datapath,datapath.id)]:
 90             out_port = self.mac_to_port[(datapath,datapath.id)][dst]
 91         else:
 92             if self.mac_learning(datapath, src, in_port) is False:
 93                 out_port = ofproto.OFPPC_NO_RECV
 94             else:
 95                 out_port = ofproto.OFPP_FLOOD
 96
 97         actions = [parser.OFPActionOutput(out_port)]
 98
 99         if out_port != ofproto.OFPP_FLOOD:
100             match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
101             if msg.buffer_id != ofproto.OFP_NO_BUFFER:
102                 self.add_flow(datapath, 10, match, actions, msg.buffer_id)
103                 return
104             else:
105                 self.add_flow(datapath, 10, match, actions)
106
107         data = None
108         if msg.buffer_id == ofproto.OFP_NO_BUFFER:
109             data = msg.data
110         out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
111                                   in_port=in_port, actions=actions, data=data)
112         datapath.send_msg(out)

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

时间: 2024-08-11 02:47:32

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

[Android学习系列14]聊天通信的实现

说不定以后用得上 基于XMPP http://blog.csdn.net/lnb333666/article/details/7471292 http://www.oschina.net/code/snippet_150934_11012 [Android学习系列14]聊天通信的实现,码迷,mamicode.com

ZigBee学习四 无线+UART通信

ZigBee学习四 无线+UART通信 1) 协调器编程 修改coordinator.c文件 byte GenericApp_TransID; // This is the unique message ID (counter) afAddrType_t GenericApp_DstAddr; //unsigned char uartbuf[128];/********************************************************************** L

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

ucos实时操作系统的任务间通信有好多种,本人主要学习了sem, mutex, queue, messagebox这四种.系统内核代码中,这几种任务间通信机制的实现机制相似,接下来记录一下本人对核心代码的学习心得,供以后回来看看,不过比较遗憾的是没有仔细学习扩展代码的功能实现部分.ucos操作系统的内核代码实现相对简单,但是对理解其他操作系统内核相同功能有帮助. ucos的任务间通信机制主要是基于event实现的,其实理解这个event不用翻译成中文事件,就叫event感觉还更容易接收.下面是操

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

ucos另一种任务间通信的机制是消息(mbox),个人感觉是它是queue中只有一个信息的特殊情况,从代码中可以很清楚的看到,因为之前有关于queue的学习笔记,所以一并讲一下mbox.为什么有了queue机制还要用mbox呢,只要设置queue的msg只有一个不就行了?其实很简单,就是为了节约资源,因为使用queue的话需要专门描述queue的机构体os_q,同时需要分配一段内存用来存放msg,而如果直接使用mbox机制的话,就好多了,节约..... 首先从mbox的创建开始,mbox创建的函

Android学习——Fragment与Activity通信(二)

接下来就要到Fragment向Activity传输数据了.主要的思路,就是在Fragment中创建一个回调接口,利用该回调接口实现Fragment向Activity传输数据的功能. 回调函数(接口) 在学习利用回调接口实现Fragment向Activity传输数据之前,首先要对回调函数有所了解,下面引用知乎用户futeng的回答,侵删:https://www.zhihu.com/question/19801131/answer/26586203. 简单来说,回调函数就是当在一个类A中去调用类B的

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

2017年8月9日学习内容存放 1 #socket通信介绍 2 3 ''' 4 OSI七层 5 6 应用 7 表示 8 会话 9 传输 10 网络 ip 11 数据链路 mac 12 物理层 网线 13 14 15 16 http 17 smtp 18 dns 19 ftp 20 ssh 21 snmp 22 icmp ping 23 dhcp 24 25 26 27 发 send 28 收 receive 29 30 31 32 33 TCP/IP 三次握手,四次断开 34 35 UDP 36

ucos实时操作系统学习笔记——任务间通信(互斥锁)

想讲一下ucos任务间通信中的mutex,感觉其设计挺巧妙,同sem一样使用的是event机制实现的,代码不每一行都分析,因为讲的没邵贝贝老师清楚,主要讲一下mutex的内核是如何实现的.可以理解互斥锁是设置信号量值为1时候的特殊情况,与之不同的地方是互斥锁为了避免优先级反转采用了优先级继承机制,本文主要讲一下互斥锁的创建,pend和post,对应的函数是OSMutexCreate,OSMutexPend,OSMutexPost,当然讲函数也不会所有的扩展功能都讲,只是讲一下主干部分,下面贴出来

奶爸业余单片机学习之:UART串口通信学习笔记(二)

/************************************** 串口通信实验* 晶振11.0592MHz* 波特率9600bps* 中断方式实现:单片机接收电脑数据,加1后发送回电脑***************************************/ #include<reg52.h> unsigned char dat; /**********串口通信配置*******************/void UART_CONFIG(unsigned long baud

Linux学习之路-http通信原理

http通信原理 应用通讯的基本模型分析 基本通讯流程: 客户端http应用使用本机IP+随机注册生成的TCP端口,形成套接字socket,调用系统socket api 再经过网络层.数据链路层.物理层层层封装,把数据送达请求的服务器,经过层层解封,送达对应的http服务监听的套接字socket监听的相应的端口上. 期间涉及到的N种技术: (1)TCP和UDP协议 TCP特点: a.面向连接:收发数居前必须和对方建立可靠连接,一个连接必须经过3次握手 简单过程: 主机A向主机B发出连接请求数据包