You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 11:27:35