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

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

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 groupfrom base_test import *CPU_CLONE_SESSION_ID = 99@group("packetio")
class PacketOutTest(P4RuntimeTest):"""Tests controller packet-out capability by sending PacketOut messages andexpecting a corresponding packet on the output port set in the PacketOutmetadata."""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 portstestutils.verify_no_other_packets(self)@group("packetio")
class PacketInTest(P4RuntimeTest):"""Tests controller packet-in capability my matching on the packet EtherTypeand 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)@autocleanupdef testPacket(self, pkt):# Insert clone to CPU sessionself.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层转发功能