SRv6实践项目(六):控制面完成链路和主机的发现

在本次实验中,我们需要利用ONOS完成对数据面的控制

1.使能packet的IO功能,验证链路发现

main.p4提供了和P4Runtime的通信的消息的定义格式,分别是PacketInPacketOut,他们都被加上了一个注解,表示这是一个控制器交互的数据包包头格式

@controller_header("packet_in")
header cpu_in_header_t {
    port_num_t ingress_port;
    bit<7> _pad;
}

@controller_header("packet_out")
header cpu_out_header_t {
    port_num_t egress_port;
    bit<7> _pad;
}

这些报头用于携带数据包的初始的交换机入端口,并指定数据包转发的预期出端口。

  • Stratum中的P4Runtime代理从交换机CPU端口接收到数据包时,它希望找到数据包的CPU_in_header_t标头作为帧中的第一个标头,为了解析这个数据包,它去查看P4Info文件的controller_packet_metadata部分,以确定在帧的开头剥离的比特数,并填充PacketIn消息的相应元数据字段,在本例中就是一个入端口。
  • 类似地,当Stratum接收到P4Runtime PacketOut消息时,它使用在PacketOut的元数据字段中找到的值对帧进行序列化,并在将其提供给管道解析器之前将cpu_out_header_t预附加到帧中

因此,在p4代码中,我们应该实现如下几个功能

1.1 解析数据包

这个早在第二部分就讲过了,这里做一个回忆,解析指的是解析来自于控制面的packet_out消息(解析packet_in消息是控制面的工作)

state parse_packet_out {
        packet.extract(hdr.cpu_out);
        transition parse_ethernet;
    }

1.2什么时候反馈控制器

这里设计了两个操作,分别是直接发送给CPU,另一个则是克隆一份发给CPU

CPU_CLONE_SSION_ID:为要克隆到CPU端口的数据包指定镜像会话。与此会话ID相关联的数据包将被克隆到CPU_port,并通过其出口进行传输(由桥接/路由/acl表设置)。要使克隆工作,P4Runtime控制器需要首先插入一个将此会话ID映射到CPU_PORT的CloneSessionEntry。

action send_to_cpu() {
        standard_metadata.egress_spec = CPU_PORT;
    }

action clone_to_cpu() {
        // Cloning is achieved by using a v1model-specific primitive. Here we
        // set the type of clone operation (ingress-to-egress pipeline), the
        // clone session ID (the CPU one), and the metadata fields we want to
        // preserve for the cloned packet replica.
        clone3(CloneType.I2E, CPU_CLONE_SESSION_ID, { standard_metadata.ingress_port });
    }

1.3 收到了Packet OUT消息要怎么做

这里,我们在Ingress控制流的apply中,加入一段代码:

if (hdr.cpu_out.isValid()) {
           
            standard_metadata.egress_spec = hdr.cpu_out.egress_port;
            hdr.cpu_out.setInvalid();
            exit;
        }

这段代码的核心思想就是如果这个包是控制面下来的包,就要把它转发到由控制面指定的位置去

1.4 如何发出一个Packet IN消息

这里,我们在Egress控制流的apply中,加入一段代码:

if (standard_metadata.egress_port == CPU_PORT) {
            
            hdr.cpu_in.setValid();
            hdr.cpu_in.ingress_port = standard_metadata.ingress_port;
            exit;
        }

如果要从CPU_PORT发出去,说明这是一个packetin数据包,把它的进端口封包到它的cpu_in中,这样,等它到控制面的时候,控制面就知道了这个包是来自于交换机的这个端口的,这里设计了一下cpu_in是有效的。

1.5 封装Packetin数据包

如果cpu_invalid的,它就会封包

apply {
        packet.emit(hdr.cpu_in);
        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
        packet.emit(hdr.ipv6);
        packet.emit(hdr.srv6h);
        packet.emit(hdr.srv6_list);
        packet.emit(hdr.tcp);
        packet.emit(hdr.udp);
        packet.emit(hdr.icmp);
        packet.emit(hdr.icmpv6);
        packet.emit(hdr.ndp);
    }

总的来说,来讲讲控制面和数据面交互是怎么做的:

  • 我们先只说LLDP和BDDP:控制器会发送一个PACKET_OUT的数据包到数据面,这个PACKET_OUT数据包指定了它的出端口port A
  • 当交换机leaf1收到这个数据包,把它拆开来一看,发现它的出端口port A要去leaf2,于是它拆了包,然后不做任何操作了,直接发出去到leaf2
  • leaf2从port B收到了这个包,此时这个包的格式已经不是PACKET_OUT了,它是一个LLDP协议包或者BDDP协议包,此时leaf2去它的ACL表里检索,发现匹配到了这个包,然后它执行了对应的操作:把它克隆一份
    • 其中,原件按照它原有的逻辑继续走
    • 复印件到管道的元数据的ingress和原件一样,其他不存在,然后复印件把自己的ingress(port B)信息封装在PACKET_IN包头中,并激活这个数据包的PACKET_IN包头,然后发给控制器
  • 控制器收到了一个PACKET_IN的包,它打开这个包一看得知这个包在数据面的ingress是(port B),然后进一步拆开,发现它是一个LLDP或者BDDP数据包,然后控制面回忆起自己曾经向leaf1发过这个包叫它去port A的,这个包路线是控制器->eaf1 port A->leaf2 port B->控制器,控制器就得到了两个交换机相连的端口
  • 但是我们知道,ACL中并不只关注了LLDP和BDDP,它还关注了ARP以及NDP的NS和NA,也就是说,控制器还能跟踪到IPv4和IPv6的主机的信息

2.测试上述功能

在这里,我们用一个代码测试了它的功能:

PacketOutTest

  • runTest函数:生成了一大堆的数据包,然后去testPacket这些数据包的IO的功能
  • testPacket(self, pkt)函数:把packetout消息的cpu_out的出端口分别发出去

PacketInTest

  • runTest函数:生成了一大堆的数据包,然后去testPacket这些数据包的IO的功能
  • testPacket(self, pkt)函数:insert这个数据包对应的表的表项,然后,看每一个端口,它有一个期望的数据包格式类型,testutils发包工具发出数据包,如果符合期望,就ok了

from ptf.testutils import group

from base_test import *

CPU_CLONE_SESSION_ID = 99


@group("packetio")
class PacketOutTest(P4RuntimeTest):
    """Tests controller packet-out capability by sending PacketOut messages and
    expecting a corresponding packet on the output port set in the PacketOut
    metadata.
    """

    def runTest(self):
        for pkt_type in ["tcp", "udp", "icmp", "arp", "tcpv6", "udpv6",
                         "icmpv6"]:
            print_inline("%s ... " % pkt_type)
            pkt = getattr(testutils, "simple_%s_packet" % pkt_type)()
            self.testPacket(pkt)

    def testPacket(self, pkt):
        for outport in [self.port1, self.port2]:
            # Build PacketOut message.
            # Modify metadata names to match the content of your P4Info file
            # ---- START SOLUTION ----
            packet_out_msg = self.helper.build_packet_out(
                payload=str(pkt),
                metadata={
                    "egress_port": outport,
                    "_pad": 0
                })
            
            # Send message and expect packet on the given data plane port.
            self.send_packet_out(packet_out_msg)

            testutils.verify_packet(self, pkt, outport)

        # Make sure packet was forwarded only on the specified ports
        testutils.verify_no_other_packets(self)


@group("packetio")
class PacketInTest(P4RuntimeTest):
    """Tests controller packet-in capability my matching on the packet EtherType
    and cloning to the CPU port.
    """

    def runTest(self):
        for pkt_type in ["tcp", "udp", "icmp", "arp", "tcpv6", "udpv6",
                         "icmpv6"]:
            print_inline("%s ... " % pkt_type)
            pkt = getattr(testutils, "simple_%s_packet" % pkt_type)()
            self.testPacket(pkt)

    @autocleanup
    def testPacket(self, pkt):

        # Insert clone to CPU session
        self.insert_pre_clone_session(
            session_id=CPU_CLONE_SESSION_ID,
            ports=[self.cpu_port])

        # Insert ACL entry to match on the given eth_type and clone to CPU.
        eth_type = pkt[Ether].type
        
        # Modify names to match content of P4Info file (look for the fully
        # qualified name of the ACL table, EtherType match field, and
        # clone_to_cpu action.
        
        self.insert(self.helper.build_table_entry(
            table_name="IngressPipeImpl.acl_table",
            match_fields={
                # Ternary match.
                "hdr.ethernet.ether_type": (eth_type, 0xffff)
            },
            action_name="IngressPipeImpl.clone_to_cpu",
            priority=DEFAULT_PRIORITY
        ))
        

        for inport in [self.port1, self.port2, self.port3]:
            
            # Expected P4Runtime PacketIn message.
            exp_packet_in_msg = self.helper.build_packet_in(
                payload=str(pkt),
                metadata={
                    "ingress_port": inport,
                    "_pad": 0
                })

            # Send packet to given switch ingress port and expect P4Runtime
            # PacketIn message.
            testutils.send_packet(self, inport, str(pkt))
            self.verify_packet_in(exp_packet_in_msg)

看完了代码,开始测试:

make p4-test TEST=packetio

3.开启ONOS完成链路发现

链路发现和主机发现的功能已经实现在ONOS中了,在控制器中的应用程序的解释器PipelineInterpreter中让他能匹配P4Info,这样,就能完成对数据包IO的真正实现了,记得make netcfg把网络配置推送到ONOS中,你发现了两个主机。

make app-reload,加载一下应用程序,现在,你能实现h1a和h1b互ping的功能了

在这里,我们先做一个小结: 

你会发现一个现象,h1a和h1b能互相ping,但是h2就不行了,这是为什么呢?

h1a在一开始,它要找主机h1b,它发出了对h1b的组播,ipv6组播的mac地址也是个组播,是0x333300000..所以,当组播请求经过leaf1的时候,leaf1根据自己身上的2层组播地址转发,转发的出端口是主机向的端口,也就是3456,所以,理论上h1c,h1b,h2都会收到它的组播,但是实际上

  • 同一个广播域内的h1c,它不知道h1a的地址,h1a虽然要组播他要去的地方,但是h1c收到了发现h1a的3层的目的地址不是它,h1c就不再打开看了,在同一广播域内,leaf1呈现l2功能,是一个交换机
  • 同一个leaf的h2,虽然与h1a和h1b,h1c都是与leaf1相连,但是,h1a为了要找h2,它知道h2不是这个子网的,在这个子网内别说组播了,广播都没用,所以h1a要找到与外网相连的leaf1,它要知道路由器怎么走(router的mac),才能知道h2的MEC,然而我们没有实现第三层,所以它连路由器在哪都不知道,leaf1在h1a的眼中,只不过是一个交换机而已。
  • 不同的leaf的h3和h4,他们根本就收不到信息,因为我们还没有实现3层转发功能

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值