Ryu控制器SDN应用中MPLS Push动作未执行问题求助
Ryu控制器MPLS部署问题排查与修复
问题概述
基于Ryu控制器开发SDN应用,拓扑包含4台交换机+2台主机(连接关系:s1-s2、s2-s3、s3-s4、s1-s3),计划先实现MPLS再做流量工程。当前用NetworkX获取最短路径,但调用push_mpls函数时内部动作不生效,只有_packet_in_handler末尾硬编码的actions = [parser.OFPActionOutput(out_port)]能让主机ping通,改用push_mpls内的动作则失效。
核心问题分析
- push_mpls函数未实际生效:函数内仅定义了actions但既没有调用
add_flow将流表下发到交换机,也没有返回actions给调用方用于PacketOut,相当于只打了日志却没做实际操作。 - swap_mpls函数存在多个错误:
- 未导入
mpls模块,导致mpls.mpls调用报错 - 变量
dst、ethtype未定义,构造Match时会抛出异常 dpid == 2是字符串与数字比较(dpid是格式化后的字符串,比如"0000000000000002"),永远不成立- 动作列表缺少
OFPActionOutput(out_port),即使下发流表也无法转发 - 同样未调用
add_flow下发流表,也未返回动作
- 未导入
- get_path函数错误重置拓扑:每次调用都执行
self.net=nx.DiGraph(),导致之前学习到的主机节点和拓扑链接全部丢失,路径计算会出错。 - _packet_in_handler逻辑缺陷:不管push/swap的动作,始终用默认的output动作发送PacketOut,完全忽略了MPLS相关操作。
修复方案
1. 修正push_mpls函数
让函数返回构造好的MPLS动作,同时添加流表下发逻辑:
def push_mpls(self, ev, out_port): 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] dst = eth.dst src = eth.src ethtype = eth.ethertype # 构造匹配规则:入端口+目的MAC+以太网类型(IPv4) match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_type=ethtype) self.label += 1 self.logger.info("Flow actions: push MPLS=%s, out_port=%s, dst=%s ", self.label, out_port, dst) # 构造MPLS动作:push标签+设置标签值+转发到出端口 actions = [ parser.OFPActionPushMpls(ethertype=34887), # 34887是MPLS_UNICAST的以太网类型 parser.OFPActionSetField(mpls_label=self.label), parser.OFPActionOutput(out_port) ] # 下发流表 self.add_flow(datapath, 10, match, actions, msg.buffer_id) return actions
2. 修复swap_mpls函数
补充依赖导入,修正变量与类型问题,完善动作与流表下发:
# 先在顶部导入mpls模块 from ryu.lib.packet import mpls def swap_mpls(self, ev, out_port): msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser in_port = msg.match['in_port'] dpid = datapath.id # 直接用数字类型的dpid,避免字符串比较问题 pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] mpls_proto = pkt.get_protocol(mpls.mpls) dst = eth.dst ethtype = eth.ethertype if not mpls_proto: self.logger.error("No MPLS header found in packet") return [parser.OFPActionOutput(out_port)] # 构造匹配规则:入端口+目的MAC+MPLS标签 match = parser.OFPMatch( in_port=in_port, eth_dst=dst, eth_type=34887, mpls_label=mpls_proto.label ) actions = [] if dpid in [2, 3]: # 中间交换机执行标签交换 self.label += 1 self.logger.info("Flow actions: swap MPLS from %s to %s, out_port=%s", mpls_proto.label, self.label, out_port) actions = [ parser.OFPActionPopMpls(), parser.OFPActionPushMpls(ethertype=34887), parser.OFPActionSetField(mpls_label=self.label), parser.OFPActionOutput(out_port) ] else: # 边缘交换机弹出MPLS标签 self.logger.info("Flow actions: pop MPLS=%s, out_port=%s", mpls_proto.label, out_port) actions = [ parser.OFPActionPopMpls(), parser.OFPActionOutput(out_port) ] # 下发流表 self.add_flow(datapath, 10, match, actions, msg.buffer_id) return actions
3. 修复get_path函数的拓扑重置问题
删除每次调用时的self.net=nx.DiGraph(),改为仅在拓扑初始化时构建,同时修正主机节点的添加逻辑:
def get_path(self, ev): msg = ev.msg datapath = msg.datapath pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] dst = eth.dst src = eth.src dpid = datapath.id # 用数字dpid,避免格式化问题 # 学习主机与交换机的连接 if src not in self.net: self.net.add_node(src) self.net.add_edge(src, dpid) self.net.add_edge(dpid, src, port=msg.match['in_port']) if dst in self.net: try: path = nx.shortest_path(self.net, src, dst) next_hop = path[path.index(dpid)+1] out_port = self.net[dpid][next_hop]['port'] self.logger.info("Path: %s, out_port: %s", path, out_port) return out_port, path except (nx.NetworkXNoPath, IndexError): self.logger.error("No path found from %s to %s", src, dst) return None, None return None, None
4. 调整_packet_in_handler逻辑
根据push/swap的返回动作构造PacketOut,不再使用硬编码的output动作:
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): 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'] 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 ethtype = eth.ethertype out_port, path = self.get_path(ev) if out_port is None: out_port = ofproto.OFPP_FLOOD actions = [parser.OFPActionOutput(out_port)] else: # 根据以太网类型选择MPLS操作 if ethtype == ether_types.ETH_TYPE_IP: # 2048对应IPv4 actions = self.push_mpls(ev, out_port) elif ethtype == 34887: # MPLS_UNICAST actions = self.swap_mpls(ev, out_port) else: actions = [parser.OFPActionOutput(out_port)] # 发送PacketOut处理当前报文 out = parser.OFPPacketOut( datapath=datapath, buffer_id=msg.buffer_id, in_port=in_port, actions=actions, data=msg.data if msg.buffer_id == ofproto.OFP_NO_BUFFER else None ) datapath.send_msg(out)
5. 修正get_topology_data函数的节点添加逻辑
确保交换机节点以数字类型加入拓扑:
@set_ev_cls(event.EventSwitchEnter) def get_topology_data(self, ev): switch_list = get_switch(self.topology_api_app, None) switches = [switch.dp.id for switch in switch_list] self.net.add_nodes_from(switches) links_list = get_link(self.topology_api_app, None) links = [(link.src.dpid, link.dst.dpid, {'port': link.src.port_no}) for link in links_list] self.net.add_edges_from(links) self.links = links self.switches = switches for src_dpid, dst_dpid, link_info in links: if src_dpid not in self.switch_ports: self.switch_ports[src_dpid] = {'in_port': [], 'out_port': []} if dst_dpid not in self.switch_ports: self.switch_ports[dst_dpid] = {'in_port': [], 'out_port': []} self.switch_ports[src_dpid]['out_port'].append(link_info['port']) # 添加反向链路的入端口 reverse_link = next((l for l in links_list if l.src.dpid == dst_dpid and l.dst.dpid == src_dpid), None) if reverse_link: self.switch_ports[dst_dpid]['in_port'].append(reverse_link.src.port_no) self.logger.info("Topology updated: switches=%s, links=%s", switches, links) return links, switches
验证步骤
- 启动Ryu控制器运行修正后的代码
- 启动SDN拓扑,确保交换机连接到控制器
- 用
ovs-ofctl dump-flows <switch-name>查看各交换机的流表,确认MPLS相关流表已下发 - 在主机间执行ping测试,验证连通性
- 查看控制器日志,确认MPLS push/swap/pop动作已正确执行
内容的提问来源于stack exchange,提问作者nessrine khiat
相关产品推荐
相关产品推荐

