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 comparisionbool 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控制数据平面。

SRv6项目实践(二):基本的P4框架相关推荐

  1. SRv6项目实践(一):环境与工具介绍

    在一切开始之前,首先介绍一下我们要做什么,做这个要有什么基础,以及实现的环境 1,实验目标与实验基础 我们要在图下图所示的拓扑中,完成在如以下拓扑所示的网络中,配合ONOS实现基本的L2L3转发以及S ...

  2. Python 项目实践二(生成数据)第二篇

    接着上节继续学习,在本节中,我们将使用Python来生成随机漫步数据,再使用matplotlib以引人瞩目的方式将这些数据呈现出来.随机漫步是这样行走得到的路径:每次行走都完全是随机的,没有明确的方向 ...

  3. Python 项目实践二(下载数据)第四篇

    接着上节继续学习,在本节中,你将下载JSON格式的人口数据,并使用json模块来处理它们.Pygal提供了一个适合初学者使用的地图创建工具,你将使用它来对人口数据进行可视化,以探索全球人口的分布情况. ...

  4. NHibernate3.2+Asp.net MVC3+Extjs 4.0.2项目实践(二): NHibernate数据访问层实现

    关于NHibernate的ORM映射可以通过Hbm映射文件来完成,代码生成工具使得这一步骤变得简化:而NHibernate3.2版本集成Mapping-By-Code(代码映射),不同于其他映射方式, ...

  5. P4语言编程快速开始 实践二

    参考:P4语言编程快速开始 上一篇系列博客:P4语言编程快速开始 实践二 Demo 2 本Demo所做的修改及实现的功能: 为simple_router添加一个计数器(counter),该计数器附加( ...

  6. 项目实践系列-点击生成自定义设置的二维码

    目前为止,生活中的我们到处可见一些二维码,使用微信扫一扫即可进入到另一个网络空间,这种方式方便了我们的生活,更让我们可以适应这种方式. 那么今天呢,我就个人项目经历,把点击生成自定义设置的二维码的一个 ...

  7. SSM框架项目实践,leetcode46

    学习目标: SSM框架项目实践,leetcode46 学习内容: 学习使用开发wangEditor文件上传功能,并且开发了新增.更新.删除图书的功能. 通过运送回溯算法的思想解决leetcode46问 ...

  8. 安卓项目实践——仿淘宝界面(二)——底部导航栏技术(Fragment实现)

    安卓项目实践--仿淘宝界面(一)--底部导航栏技术(Fragment实现) 1.实现效果展示 2.技术简述 该导航栏主要使用Fragment技术实现,关于Fragment的介绍大家可以自行百度,导航栏 ...

  9. Python项目实践之二:下载数据(CSV和JSON )

    Python项目实践之二:下载数据(CSV和JSON ) 下载数据的可视化着重点在于从网上下载数据进行分析后,进行可视化处理,网上的数据格式多的难以置信,且大多未经过仔细检查,如果能够对这些数据进行分 ...

最新文章

  1. 精选180+Python开源项目,随你选!做项目何愁没代码
  2. 【硅谷牛仔】DropBox CEO --德鲁休斯敦--找到合适自己的圈子,追逐自己感兴趣的事...
  3. CMS模板应用调研问卷
  4. 几种替代MATLAB的工具,堪称完美!
  5. Java基础提升篇:equals()方法和“==”运算符
  6. .Net 零星小知识
  7. boost::sort模块实现spreadsort 字符串排序示例
  8. app抢购脚本如何编写_如何用1个记事本文件征服全世界?——cmd批处理脚本编写...
  9. oracle 临时表空间满了_精心总结--Oracle查询表空间的每日增长量和历史情况统计脚本...
  10. java 百分比怎么比较_这88道阿里高级岗面试题,刷掉了80%以上的Java程序员
  11. 自考运筹学第三章课后题
  12. iis10 asp 如何连接mdb_网站500内部服务器错误如何解决 - 最蜘蛛池
  13. tcp流式传输_收听互联网广播以及下载和流式传输免费音乐的最佳网站
  14. 为什么每个阿里新人都要上“百阿”?
  15. Google 就业岗分析
  16. 安全风险 microsoft 已阻止宏运行 因为此文件的来源不受信任
  17. MyBatis-Plus——自动填充功能实现
  18. MATLAB:零状态响应(lsim(连续);filter(离散))、冲激响应(impulse或impz)和阶跃响应(step)、卷积(conv)
  19. 读书笔记014:《伤寒论》- 足厥阴肝经
  20. oracle定时器每天下午6点_每天下午5到7点吃它,补肾效果杠杠的!

热门文章

  1. 生日快乐程序_「秒福」小程序:送祝福,2020我们不一样
  2. 2014去哪儿网校园招聘笔试(10.13北京)
  3. 【SAS应用统计分析】软件的基本操及SAS数据集的整理
  4. php正则表达式替换字符,php正则表达式如何替换字符
  5. 是时候来了解android7了:shortcuts(快捷方式)
  6. pjsip学习 ------ 三
  7. 4、Reading Rasa Source Code —— Domain 解析
  8. Ionic3项目实战
  9. requirejs 简介
  10. display:none和visibility:hidden的区别