图示拓扑,存在环路,导致网络广播风暴,主机不可达:
使用ARP代理,通过环路检测,保证同一交换机的某一个广播数据包只有一个入口,防止成环。
流程如下:
packet_in | | ARP Learning MAC_to_port learning | | | No Multicast?----------------------------------------------->| | | | | |Yes | | | | No | loop? ------->(dpid,eth_src,dst_ip)learning | | | | | | | | | No | No |Yes dst_ip in arp_table?-------->dst in mac_to_port?------>flood | | | | | |Yes |Yes | | | | | drop ARP_REPLY flow_mod | | | | | |<------------------------|<--------------------------|<------------------| | end
代码实现如下:
1 from ryu.base import app_manager 2 from ryu.controller import ofp_event 3 from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, set_ev_cls 4 from ryu.ofproto import ofproto_v1_3 5 from ryu.lib.packet import packet, ethernet, arp, ipv6 6 from ryu.lib import mac 7 8 9 class ARP_PROXY_13(app_manager.RyuApp): 10 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 11 12 def __init__(self, *args, **kwargs): 13 super(ARP_PROXY_13, self).__init__(*args, **kwargs) 14 self.mac_to_port = {} 15 self.arp_table = {} 16 self.sw = {} 17 18 @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) 19 def switch_features_handler(self, ev): 20 datapath = ev.msg.datapath 21 ofproto = datapath.ofproto 22 parser = datapath.ofproto_parser 23 24 # install table-miss flow entry 25 # 26 # we specify NO BUFFER to mac_len of the output action due to 27 # OVS bug. At this moment, if we specify a lesser number, e.g., 28 # 128, OVS will send Packet-In with invalid buffer_id and truncated packet data. 29 # In that case, we cannot output packets correctly. 30 match = parser.OFPMatch() 31 actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, 32 ofproto.OFPCML_NO_BUFFER)] 33 self.add_flow(datapath, 0, match, actions) 34 self.logger.info("switch: %s connected", datapath.id) 35 36 def add_flow(self, datapath, priority, match, actions, buffer_id=None): 37 ofproto = datapath.ofproto 38 parser = datapath.ofproto_parser 39 40 inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] 41 42 if buffer_id: 43 mod = parser.OFPFlowMod(datapath=datapath, priority=priority, 44 idle_timeout=5, hard_timeout=15, 45 match=match, instructions=inst) 46 else: 47 mod = parser.OFPFlowMod(datapath=datapath, priority=priority, 48 match=match, instructions=inst) 49 datapath.send_msg(mod) 50 51 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 52 def _packet_in_handler(self, ev): 53 msg = ev.msg 54 datapath = msg.datapath 55 ofproto = datapath.ofproto 56 parser = datapath.ofproto_parser 57 in_port = msg.match['in_port'] 58 59 pkt = packet.Packet(msg.data) 60 61 eth = pkt.get_protocols(ethernet.ethernet)[0] 62 dst = eth.dst 63 src = eth.src 64 dpid = datapath.id 65 66 self.logger.info("packet in: %s %s %s %s", dpid, src, dst, in_port) 67 68 if pkt.get_protocol(ipv6.ipv6): # drop the ipv6 packets 69 match = parser.OFPMatch(eth_type=eth.ethertype) 70 actions = [] 71 self.add_flow(datapath, 1, match, actions) 72 return None 73 74 arp_pkt = pkt.get_protocol(arp.arp) 75 76 if arp_pkt: 77 self.arp_table[arp_pkt.src_ip] = src #ARP learning 78 self.logger.info(" ARP: %s -> %s", arp_pkt.src_ip, arp_pkt.dst_ip) 79 if self.arp_handler(msg): #answer or drop 80 return None 81 82 self.mac_to_port.setdefault(dpid, {}) 83 self.logger.info("packet in: %s %s %s %s", dpid, src, dst, in_port) 84 85 # learn a mac address to avoid FLOOD next time. 86 if src not in self.mac_to_port[dpid]: 87 self.mac_to_port[dpid][src] = in_port 88 # print self.mac_to_port 89 if dst in self.mac_to_port[dpid]: 90 out_port = self.mac_to_port[dpid][dst] 91 else: 92 print(self.mac_to_port[dpid]) 93 out_port = ofproto.OFPP_FLOOD 94 print("Flood") 95 96 actions = [parser.OFPActionOutput(out_port)] 97 98 # install a flow to avoid packet_in next time. 99 if out_port != ofproto.OFPP_FLOOD: 100 self.logger.info("install flow_mod: %s -> %s", in_port, out_port) 101 102 match = parser.OFPMatch(in_port=in_port, eth_dst=dst) 103 self.add_flow(datapath, 1, match, actions) 104 105 data = None 106 if msg.buffer_id == ofproto.OFP_NO_BUFFER: 107 data = msg.data 108 out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, 109 in_port=in_port, actions=actions, data=data) 110 datapath.send_msg(out) 111 112 def arp_handler(self, msg): 113 datapath = msg.datapath 114 ofproto = datapath.ofproto 115 parser = datapath.ofproto_parser 116 in_port = msg.match['in_port'] 117 118 pkt = packet.Packet(msg.data) 119 eth = pkt.get_protocols(ethernet.ethernet)[0] 120 arp_pkt = pkt.get_protocol(arp.arp) 121 122 if eth: 123 eth_dst = eth.dst 124 eth_src = eth.src 125 126 127 """ 128 解决环路风暴: 129 在回复ARP请求之前,必须解决的是网络环路问题。 130 解决方案是: 131 以(dpid,eth_src,arp_dst_ip)为key, 132 记录第一个数据包的in_port,并将从网络中返回的数据包丢弃, 133 保证同一个交换机中的某一个广播数据包只能有一个入口, 134 从而防止成环。在此应用中,默认网络中发起通信的第一个数据包都是ARP数据包。 135 """ 136 # sw[(datapath.id, eth_src, arp_dst_ip)] = in_port 137 138 # Break the loop for avoiding ARP broadcast storm 139 if eth_dst == mac.BROADCAST_STR: # and arp_pkt 140 arp_dst_ip = arp_pkt.dst_ip 141 arp_src_ip = arp_pkt.src_ip 142 143 if (datapath.id, arp_src_ip, arp_dst_ip) in self.sw: 144 # packet come back at different port. 145 if self.sw[(datapath.id, arp_src_ip, arp_dst_ip)] != in_port: 146 datapath.send_packet_out(in_port=in_port, actions=[]) 147 return True 148 else: 149 self.sw[(datapath.id, arp_src_ip, arp_dst_ip)] = in_port 150 print(self.sw) 151 self.mac_to_port.setdefault(datapath.id, {}) 152 self.mac_to_port[datapath.id][eth_src] = in_port 153 154 """ 155 ARP回复: 156 解决完环路拓扑中存在的广播风暴问题之后,要利用SDN控制器获取网络全局的信息的能力,去代理回复ARP请求, 157 从而减少网络中泛洪的ARP请求数据。通过自学习主机ARP记录,在通过查询记录并回复。 158 """ 159 if arp_pkt: 160 161 opcode = arp_pkt.opcode 162 163 if opcode == arp.ARP_REQUEST: 164 hwtype = arp_pkt.hwtype 165 proto = arp_pkt.proto 166 hlen = arp_pkt.hlen 167 plen = arp_pkt.plen 168 169 arp_src_ip = arp_pkt.src_ip 170 arp_dst_ip = arp_pkt.dst_ip 171 172 if arp_dst_ip in self.arp_table: # arp reply 173 actions = [parser.OFPActionOutput(in_port)] 174 ARP_Reply = packet.Packet() 175 176 ARP_Reply.add_protocol(ethernet.ethernet(ethertype=eth.ethertype, 177 dst=eth.src, 178 src=self.arp_table[arp_dst_ip])) 179 ARP_Reply.add_protocol(arp.arp(opcode=arp.ARP_REPLY, 180 src_mac=self.arp_table[arp_dst_ip], 181 src_ip=arp_dst_ip, 182 dst_mac=eth_src, dst_ip=arp_src_ip)) 183 184 ARP_Reply.serialize() 185 186 out = datapath.ofproto_parser.OFPPacketOut(datapath=datapath, 187 buffer_id=datapath.ofproto.OFP_NO_BUFFER, 188 in_port=datapath.ofproto.OFPP_CONTROLLER, 189 actions=actions, data=ARP_Reply.data) 190 datapath.send_msg(out) 191 print("ARP Reply") 192 return True 193 return False
开启mininet,连接到控制器:
在ping时,可以看到控制器有信息刷新出来,是与交换机交互的消息:
环路消除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南