SRv6项目实践(二):基本的P4框架

这篇具有很好参考价值的文章主要介绍了SRv6项目实践(二):基本的P4框架。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

SRv6项目实践(二):基本的P4框架

1.数据包头的定义

在实现SRv6之前,有很多的工作需要做,首先先阅读一下p4的代码总体框架,数据包的包头格式一共有如下这些,我们需要把他们的协议逐一完善

struct parsed_headers_t {
    cpu_out_header_t cpu_out;
    cpu_in_header_t cpu_in;
    ethernet_t ethernet;
    ipv4_t ipv4;
    ipv6_t ipv6;
    srv6h_t srv6h;
    srv6_list_t[SRV6_MAX_HOPS] srv6_list;
    tcp_t tcp;
    udp_t udp;
    icmp_t icmp;
    icmpv6_t icmpv6;
    ndp_t ndp;
}

展开来说,这些协议的包头格式如下所示,其中一个很重要的数据包头是packet_in和out

header ethernet_t {
    mac_addr_t  dst_addr;
    mac_addr_t  src_addr;
    bit<16>     ether_type;
}

header ipv4_t {
    bit<4>   version;
    bit<4>   ihl;
    bit<6>   dscp;
    bit<2>   ecn;
    bit<16>  total_len;
    bit<16>  identification;
    bit<3>   flags;
    bit<13>  frag_offset;
    bit<8>   ttl;
    bit<8>   protocol;
    bit<16>  hdr_checksum;
    bit<32>  src_addr;
    bit<32>  dst_addr;
}

header ipv6_t {
    bit<4>    version;
    bit<8>    traffic_class;
    bit<20>   flow_label;
    bit<16>   payload_len;
    bit<8>    next_hdr;
    bit<8>    hop_limit;
    bit<128>  src_addr;
    bit<128>  dst_addr;
}

header srv6h_t {
    bit<8>   next_hdr;
    bit<8>   hdr_ext_len;
    bit<8>   routing_type;
    bit<8>   segment_left;
    bit<8>   last_entry;
    bit<8>   flags;
    bit<16>  tag;
}

header srv6_list_t {
    bit<128>  segment_id;
}

header tcp_t {
    bit<16>  src_port;
    bit<16>  dst_port;
    bit<32>  seq_no;
    bit<32>  ack_no;
    bit<4>   data_offset;
    bit<3>   res;
    bit<3>   ecn;
    bit<6>   ctrl;
    bit<16>  window;
    bit<16>  checksum;
    bit<16>  urgent_ptr;
}

header udp_t {
    bit<16> src_port;
    bit<16> dst_port;
    bit<16> len;
    bit<16> checksum;
}

header icmp_t {
    bit<8>   type;
    bit<8>   icmp_code;
    bit<16>  checksum;
    bit<16>  identifier;
    bit<16>  sequence_number;
    bit<64>  timestamp;
}

header icmpv6_t {
    bit<8>   type;
    bit<8>   code;
    bit<16>  checksum;
}

header ndp_t {
    bit<32>      flags;
    ipv6_addr_t  target_ipv6_addr;
    // NDP option.
    bit<8>       type;
    bit<8>       length;
    bit<48>      target_mac_addr;
}

// Packet-in header. Prepended to packets sent to the CPU_PORT and used by the
// P4Runtime server (Stratum) to populate the PacketIn message metadata fields.
// Here we use it to carry the original ingress port where the packet was
// received.
@controller_header("packet_in")
header cpu_in_header_t {
    port_num_t  ingress_port;
    bit<7>      _pad;
}

// Packet-out header. Prepended to packets received from the CPU_PORT. Fields of
// this header are populated by the P4Runtime server based on the P4Runtime
// PacketOut metadata fields. Here we use it to inform the P4 pipeline on which
// port this packet-out should be transmitted.
@controller_header("packet_out")
header cpu_out_header_t {
    port_num_t  egress_port;
    bit<7>      _pad;
}

2.包的解析

对数据包的解析工作主要是:

  1. 端口检查:判定数据包的进端口是否是CPU_PORT,这里CPU_PORT=255,如果数据包的进端口是从这里进来的,那么它是一个从数据平面下达的packet_out类型的数据包(跳转到2),如果是普通数据包,直接解析以太网地址即可(跳转到3)
  2. packet_out:解析packet_out数据包,观察这个数据包的cpu_out的字段,发现它的字段中包括了一个egress_port,这里使用它来定义接下去packet_out的包要如何转发(跳转到3)
  3. ethernet:正常的以太网地址解析,根据其以太网地址解析IPv4(跳转到4)或者IPv6(跳转到5)的数据包
  4. ipv4:解析ipv4数据包头,并用ip_proto来标记数据包的ip协议的上层协议类型,区分它是TCP(跳转到8)还是UDP(跳转到9)还是ICMP(跳转到10)
  5. ipv6:解析ipv6数据包头,并用ip_proto来标记数据包的ipv6协议的下一个协议类型,区分它是TCP(跳转到8)还是UDP(跳转到9)还是ICMPv6(跳转到11),当然,可能上层协议是自己的拓展头(跳转到6),在这里这个拓展头是SRH,协议号是43
  6. srv6:解析出来它的srv6的拓展头,紧接着去解析它的SID序列(跳转到7)

  7. srv6_list:核心思想是,根据自己的SL一步一步拆开,然后解析,直到最后一个为之,srv6的解析才算结束。

    • bool next_segment = 
      (bit<32>)hdr.srv6h.segment_left - 1 == 
      (bit<32>)hdr.srv6_list.lastIndex;
      首先判断SL的值是否是SID序列中当前对应的索引,如果是了话,说明这个SID是接下来我要去的地方,就把它替换到自己的包头的IPv6目的地址上mark_current_srv6
    • 如果上述判断没有成功,就继续逐个检查下去:check_last_srv6。检查的过程中,如果发现这是最后一个了,那就去解析srv6以外的东西了,也就是上层协议

      bool last_segment = 
      (bit<32>)hdr.srv6h.last_entry == 
      (bit<32>)hdr.srv6_list.lastIndex;
    • 解析srv6以外的东西:根据hdr.srv6h.next_hdr,判断下一个协议是TCP(跳转到8)还是UDP(跳转到9)还是ICMP(跳转到10)。小注释:hdr.srv6_list.next用完以后,srv6_list和lastIndex都会自动加一
  8. TCP:解析tcp协议,把交换机内的local_metadata的传输层源和目的端口都赋值

  9. UDP:解析udp协议,把交换机内的local_metadata的传输层源和目的端口都赋值

  10. ICMP:解析icmp协议,把交换机内local_metadata的icmp类型赋值

  11. ICMPv6:解析icmpv6协议,把交换机内local_metadata的icmp类型赋值,这里icmpv6有3种类型,分别是ICMP6_TYPE_NS和ICMP6_TYPE_NA和其他,他们都被视为NDP解析(跳转到12),无视其他类型

  12. NDP:解析NDP的首部,解析结束

parser ParserImpl (packet_in packet,
                   out parsed_headers_t hdr,
                   inout local_metadata_t local_metadata,
                   inout standard_metadata_t standard_metadata)
{
    state start {
        transition select(standard_metadata.ingress_port) {
            CPU_PORT: parse_packet_out;
            default: parse_ethernet;
        }
    }

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

    state parse_ethernet {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.ether_type){
            ETHERTYPE_IPV4: parse_ipv4;
            ETHERTYPE_IPV6: parse_ipv6;
            default: accept;
        }
    }

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        local_metadata.ip_proto = hdr.ipv4.protocol;
        transition select(hdr.ipv4.protocol) {
            IP_PROTO_TCP: parse_tcp;
            IP_PROTO_UDP: parse_udp;
            IP_PROTO_ICMP: parse_icmp;
            default: accept;
        }
    }

    state parse_ipv6 {
        packet.extract(hdr.ipv6);
        local_metadata.ip_proto = hdr.ipv6.next_hdr;
        transition select(hdr.ipv6.next_hdr) {
            IP_PROTO_TCP: parse_tcp;
            IP_PROTO_UDP: parse_udp;
            IP_PROTO_ICMPV6: parse_icmpv6;
            IP_PROTO_SRV6: parse_srv6;
            default: accept;
        }
    }

    state parse_tcp {
        packet.extract(hdr.tcp);
        local_metadata.l4_src_port = hdr.tcp.src_port;
        local_metadata.l4_dst_port = hdr.tcp.dst_port;
        transition accept;
    }

    state parse_udp {
        packet.extract(hdr.udp);
        local_metadata.l4_src_port = hdr.udp.src_port;
        local_metadata.l4_dst_port = hdr.udp.dst_port;
        transition accept;
    }

    state parse_icmp {
        packet.extract(hdr.icmp);
        local_metadata.icmp_type = hdr.icmp.type;
        transition accept;
    }

    state parse_icmpv6 {
        packet.extract(hdr.icmpv6);
        local_metadata.icmp_type = hdr.icmpv6.type;
        transition select(hdr.icmpv6.type) {
            ICMP6_TYPE_NS: parse_ndp;
            ICMP6_TYPE_NA: parse_ndp;
            default: accept;
        }
    }

    state parse_ndp {
        packet.extract(hdr.ndp);
        transition accept;
    }

    state parse_srv6 {
        packet.extract(hdr.srv6h);
        transition parse_srv6_list;
    }

    state parse_srv6_list {
        packet.extract(hdr.srv6_list.next);
        bool next_segment = (bit<32>)hdr.srv6h.segment_left - 1 == (bit<32>)hdr.srv6_list.lastIndex;
        transition select(next_segment) {
            true: mark_current_srv6;
            default: check_last_srv6;
        }
    }

    state mark_current_srv6 {
        local_metadata.next_srv6_sid = hdr.srv6_list.last.segment_id;
        transition check_last_srv6;
    }

    state check_last_srv6 {
        // working with bit<8> and int<32> which cannot be cast directly; using
        // bit<32> as common intermediate type for comparision
        bool last_segment = (bit<32>)hdr.srv6h.last_entry == (bit<32>)hdr.srv6_list.lastIndex;
        transition select(last_segment) {
           true: parse_srv6_next_hdr;
           false: parse_srv6_list;
        }
    }

    state parse_srv6_next_hdr {
        transition select(hdr.srv6h.next_hdr) {
            IP_PROTO_TCP: parse_tcp;
            IP_PROTO_UDP: parse_udp;
            IP_PROTO_ICMPV6: parse_icmpv6;
            default: accept;
        }
    }
}

小插曲:在继续讲之前,可能有很多人对这些协议都有个大概的了解,但是对ICMPv6可能比较陌生,所以,如果知道ICMPv6协议的朋友,请直接看后面。

以下是IPV6深入-NDP邻居发现协议 - 知乎 (zhihu.com)的一些截取

在IPv4中,当主机需要和目标主机通信时,必须先通过ARP协议获得目的主机的链路层地址。在IPv6中,同样需要从IP地址解析到链路层地址的功能。邻居发现协议实现了这个功能。

与IPv4的ARP相比,IPv6地址解析技术工作在OSI参考模型的网络层,与链路层协议无关。这一特点的益处如下:

(1)在第三层实现地址解析可以利用三层标准的安全认证机制来防止ARP攻击和ARP欺骗。

(2)IPv6的地址解析利用三层组播寻址限制了报文的传播范围,可节省网络带宽。

IPV6地址解析的具体过程如下:

(1)节点A向节点B发送NS报文,源地址为A的IPV6单播地址(可以是唯一本地地址也可以是链路本地地址),目的地址是B的被请求节点组播地址。源mac地址是节点A mac地址,目的mac地址是节点B的被请求节点组播mac地址。

(2)节点B收到节点A的NS报文后即知道节点A的IPV6单播地址、mac地址及被请求节点组播地址等,此时,节点B会回复NA报文,NA报文中源地址为节点B的IPV6单播地址、 MAC地址,目的地址为节点A的IPV6单播地址、mac地址

(3)节点A收到节点B的NA报文后,即知晓了节点B的IPV6单播地址、mac地址

NUD(Neighbor Unreachable Detection,邻居不可达检测)是节点确定邻居可达性的过程。邻居不可达检测机制通过邻居可达性状态机来描述邻居的可达性。

邻居可达性状态机保存在邻居缓存表中,共有如下6种状态:

(1)INCOMPLETE(未完成状态):表示正在解析地址,但邻居链路层地址尚未确定。

(2)REACHABLE(可达状态):表示地址解析成功,该邻居可达。

(3)STALE(失效状态):表示可达时间耗尽,未确定邻居是否可达。

(4)DELAY(延迟状态):表示未确定邻居是否可达。DELAY状态不是一个稳定的状态,而是一个延时等待状态。

(5)PROBE(探测状态):节点会向处于PROBE状态的邻居持续发送NS报文。

(6)EMPTY(空闲状态):表示节点上没有相关邻接点的邻居缓存表项。

之后的东西大家可以上对应的网站查询,本项目只关注NA和NS

3.进端口控制流

首先,在进端口控制流中,先写几个基本的表(在SRv6项目实践系列中还会不断添加),这里的注解用于在控制面能够得到匹配到该表的计数器,这个是一个L2的根据具体以太网目的地址转发的表,很简单吧,它是一个用户单播的表。

action set_egress_port(port_num_t port_num) {
        standard_metadata.egress_spec = port_num;
    }

    table l2_exact_table {
        key = {
            hdr.ethernet.dst_addr: exact;
        }
        actions = {
            set_egress_port;
            @defaultonly drop;
        }
        const default_action = drop;
        // The @name annotation is used here to provide a name to this table
        // counter, as it will be needed by the compiler to generate the
        // corresponding P4Info entity.
        @name("l2_exact_table_counter")
        counters = direct_counter(CounterType.packets_and_bytes);
    }

 这是一个用于组播的表,它被运用于组播中,比如NS的消息,在以太网中,NS组播的L2地址是以33:33为掩码的,在set_multicast_group中,设定一下它的组播的id就好啦,至于id对应的组播哪些地址,还不用说。

action set_multicast_group(mcast_group_id_t gid) {
        // gid will be used by the Packet Replication Engine (PRE) in the
        // Traffic Manager--located right after the ingress pipeline, to
        // replicate a packet to multiple egress ports, specified by the control
        // plane by means of P4Runtime MulticastGroupEntry messages.
        standard_metadata.mcast_grp = gid;
        local_metadata.is_multicast = true;
    }

    table l2_ternary_table {
        key = {
            hdr.ethernet.dst_addr: ternary;
        }
        actions = {
            set_multicast_group;
            @defaultonly drop;
        }
        const default_action = drop;
        @name("l2_ternary_table_counter")
        counters = direct_counter(CounterType.packets_and_bytes);
    }

这里,有一个及其重要的东西叫 ACLACL(访问控制列表)基础篇-超有趣学网络 - 知乎 (zhihu.com)

ACL,是Access Control List的简写,中文名称叫做“访问控制列表”。它是由一系列条件规则(即描述报文匹配条件的判断语句)组成, 这些条件规则可以是报文的源地址、目的地址、端口号等,是一种应用在网络设备各种软硬接口上的的指令列表。

访问控制列表的使用场景:

根据ACL中的匹配条件对进站和出站的报文进行过滤处理。打个比方,ACL其实是一种报文过滤器,ACL规则就是过滤器的滤芯。安装什么样的滤芯(即根据报文特征配置相应的ACL规则),ACL就能过滤出什么样的报文。

 ACL的主要功能:根据数据包的信息,决定把他们丢掉或者发送到cpu中,或者复制一份发到cpu,然后转发。在这里CPU就是p4runtime

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 });
    }

    table acl_table {
        key = {
            standard_metadata.ingress_port: ternary;
            hdr.ethernet.dst_addr:          ternary;
            hdr.ethernet.src_addr:          ternary;
            hdr.ethernet.ether_type:        ternary;
            local_metadata.ip_proto:        ternary;
            local_metadata.icmp_type:       ternary;
            local_metadata.l4_src_port:     ternary;
            local_metadata.l4_dst_port:     ternary;
        }
        actions = {
            send_to_cpu;
            clone_to_cpu;
            drop;
        }
        @name("acl_table_counter")
        counters = direct_counter(CounterType.packets_and_bytes);
    }

最后,apply这几个表,可以看到首先匹配单播,然后匹配多播,最后都要运用ACL实现访问控制

 apply {


        bool do_l3_l2 = true;

        if (do_l3_l2) {

            // L2 bridging logic. Apply the exact table first...
            if (!l2_exact_table.apply().hit) {
                // ...if an entry is NOT found, apply the ternary one in case
                // this is a multicast/broadcast NDP NS packet.
                l2_ternary_table.apply();
            }
        }
acl_table.apply();
}

4.出端口控制流

出端口的实现,相对简单,本地的组播不可能会组播回入端口。

control EgressPipeImpl (inout parsed_headers_t hdr,
                        inout local_metadata_t local_metadata,
                        inout standard_metadata_t standard_metadata) {
    apply {


        // If this is a multicast packet (flag set by l2_ternary_table), make
        // sure we are not replicating the packet on the same port where it was
        // received. This is useful to avoid broadcasting NDP requests on the
        // ingress port.
        if (local_metadata.is_multicast == true &&
              standard_metadata.ingress_port == standard_metadata.egress_port) {
            mark_to_drop(standard_metadata);
        }
    }
}

最后的校验和和封包就不用看了,从这个基本框架上来看,目前,还没能实现与控制面的交互,接下来的SRv6项目实践(三),我们将手动使用P4runtime控制数据平面。文章来源地址https://www.toymoban.com/news/detail-417998.html

到了这里,关于SRv6项目实践(二):基本的P4框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringCloudGateway框架下的http请求头部自定义实践

    在微服务框架中,SpringCloud-Gateway的方式几乎是首选。也得益于Reactor的实现,使得SpringCloud-Gateway的性能在所有的网关平台中脱颖而出。在一次项目实践中,需要自定义请求的头部信息,故来了一趟学习之旅。 一个请求在网关中的执行顺序是经过一系列的过滤器链进行链式调用

    2024年02月09日
    浏览(40)
  • 【Unity3D框架】Unity Package Manager自定义包管理实践

            在公司开发的前两个项目,虽然搭建了基础的框架,有一些目录划分,但是当项目复杂度增长到一定程度,以及后续新开了一些新的项目之后,对于基础框架的管理就遇到了一些挑战,主要体现在以下几个方面:         1、多项目之间拷贝了类似的基础框架,但是

    2024年02月03日
    浏览(72)
  • Dell R730 2U服务器实践3:安装英伟达上代专业AI训练Nvidia P4计算卡

    Dell R730是一款非常流行的服务器,2U的机箱可以放入两张显卡,这次先用一张英伟达上代专业级AI训练卡:P4卡做实验,本文记录安装过程。 打开机箱 将P4显卡插在4号槽位 关闭机箱 安装驱动 对于新手来说,步步为坑,有很多小细节需要注意。 首先了解到,R730号称可以装两块

    2024年04月16日
    浏览(81)
  • 【FPGA基础入门实践】Verilog 基本项目操作逐步演示

    0x00 回顾:AND/OR/NOT 逻辑的特性 AND: 与门可以具有两个或更多的输入,并返回一个输出。当所有输入值都为 1 时,输出值为 1。如果输入值中有任何一个为 0,则输出值为 0。 OR: 或门可以具有两个或更多的输入,并返回一个输出。如果输入值中至少有一个为 1,则输出值为

    2024年02月12日
    浏览(39)
  • IDEA项目实践——Spring框架简介,以及IOC注解

    IDEA创建项目的操作步骤以及在虚拟机里面创建Scala的项目简单介绍 IDEA项目实践——创建Java项目以及创建Maven项目案例、使用数据库连接池创建项目简介 IDEWA项目实践——mybatis的一些基本原理以及案例 IDEA项目实践——动态SQL、关系映射、注解开发 文章目录 第1章 Spring 概述

    2024年02月14日
    浏览(48)
  • 各种数据包头的详解(tcp,udp,ipv4,ipv6)

    Osi七层参考模型: 应用层 表示层 会话层 --------前三层控制层面 --------后四层数据层面 传输层: 区分不同的流量,定义传输方式(tcp/udp),定义端口号(区分不同的服务) 网络层: 寻址,编址(ipv4地址) 数据链路层: 定义局域网的封装, Ethernet 2 IEEE802.3 封装 物理层 Tcp/ip参

    2024年02月08日
    浏览(47)
  • React框架创建项目详细流程-项目的基本配置-项目的代码规范

    项目规范 项目规范: 在项目中都会有一些开发规范和代码风格, 下面介绍一下我采用的规范与风格 文件夹、文件名称统一小写、多个单词以连接符(-)连接, 组件采用大驼峰; JavaScript变量名称采用小驼峰标识,常量全部使用大写字母; CSS采用普通CSS和styled-component结合来编写(全局

    2024年01月16日
    浏览(75)
  • 【期末不挂科-单片机考前速过系列P4】(第四章:32题搞定基本指令例题)经典例题盘点(带图解析)

    前言 大家好吖,欢迎来到 YY 滴单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 主要内容含: 欢迎订阅 YY 滴C++专栏!更多干货持续更新!以下是传送门! YY的《C++》专栏 YY的《C++11》专栏 YY的《Linux》专栏 YY的《数据结构》专栏 YY的《C语言基础》专栏 YY的《

    2024年02月02日
    浏览(55)
  • 从定义到实际应用,详解项目管理的基本概念与核心内容

    项目管理是项目的管理者,在有限的资源约束下,运用系统的观点、方法和理论,对项目涉及的全部工作进行有效地管理。项目管理的内容包括项目范围管理,是为了实现项目的目标,对项目的工作内容进行控制的管理过程。它包括范围的界定,范围的规划,范围的调整等。

    2024年02月08日
    浏览(61)
  • WebView交互架构项目实战(四):WebView与Native的通信框架手写实践

    其中wholeJS就是JS文件转化而来的字符串,然后调用 webView.loadUrl(\\\"javascript: \\\" + wholeJS); 1 就可以看到和之前一样的效果。 方式2: 通过evaluateJavascript方法 相较于loadUrl,evaluateJavascript的优势在于异步加载,还可以将执行JS代码的结果带回来,我们以下面一个小例子来说明。 我们在

    2024年04月29日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包