P4语言基础组件

P4语言的基础语言组件

本文主要引自

http://www.sdnlab.com/17882.html

一、基础语言组件

首部(Headers)

首部类型是由成员字段组成的列表,每个字段都有对应的名称和长度,每一种首部类型都有对应的首部实例来存储具体数据,有点类似C语言的结构体。

首部分为两种包头、元数据

包头

包头可以用来描述数据包的结构,以IPV4为例子

用P4语言可以这样表示:

//包头类型声明
header_type ipv4_t{
  fields{
    version:4;
    ihl:4;
    diffserv:8;
    totalLen:16;
    identification:16;
    flags:3;
    fragOffset:13;
    ttl:8;
    protocol:4;
    hdrChecksum:16;
    srcAddr:32;
    dstADDr:32;
    options:*;//字段长度不确定
  }
  length:(ihl<<2); //根据ihl计算值
  max_length:60;
}
//包头实例化
header_ipv4_t ipv4;

“包头“与”报头“区别

包头指的是P4语言中的术语;

报头值得是数据包的报文头部。

元数据

元数据用来携带数据和配置信息,元数据的声明与包头类似,但实例化不同,包头和元数据在字段值的约束上存在一定的区别。

元数据分为两种

  • 一种是用来携带P4程序运行过程中产生的数据的用户自定义元数据(User-Defined Metadata),如首部字段的运算结果等。
  • 一种是固有元数据(Intrinsic Metadata),用于携带配置交换机自身的配置信息,如数据包进入交换机的端口号。
//生命入端口元数据类型
header_type ingress_metadata_t{
  fields{
    /* 输入 ×/
    ingress_port:9;//在解析前有效
    packet_length:16;
    instance_type:2;//普通、克隆、再循环等
    ingress_global_tstamp:48;
    parser_status:8;//解析错误
    /*入口流水线输出*/
    egress_spec:16;
    queue_id:9;
  }
}
//实例化元数据
metadata ingress_metadata_t ingress_metadata;

8种固有元数据

字段 描述
ingress_port 数据包的入端口,解析之前设置。只读
packet_length 数据包的字节数,当交换机在快速转发模式下,g该元数据不能在动作中匹配或引用。只读
egress_spec 在入端口流水线的匹配-动作过程之后设置,指定数据包的出端口,可以是物理端口、逻辑端口或者多播组
egress_port 指定数据包的物理出端口,区别于egress_spec,只能用于物理端口。只读
egress_instance 用于区分复制后数据包实例的标识符。只读
instance_type 数据包实例类型:正常(Normal)、入端口复制(ingress clone)、出端口复制(egress clone)、再循环(recirculated)
parser_status 解析器解析结果,0表示无错误,其实数字代表了对应的错误类型
parser_error_location 指向P4程序错误发生处

在P4语言中定义首部类型有几点需要注意

  1. 包头类型的长度需要与字节对齐,即长度必须是8bit的整数倍
  2. 包头中字段长度可以是可变值(该特性在P4语言规范中规定,但当前编译器版本并未实现,后续版本会支持)也可以是首部中其他字段值计算后的值。而元数据中的字段长度只能是定值。
  3. 只有包头能实例化成数据,元数据不行。
  4. 实例化时,首部中已定义名称的字段的值会被初始化程序中的指定值,如果首部中只定义了字段名称而未指定值,字段的值将会被初始化成0.

解析器

一个P4程序定义了许多的首部和首部实例,但并不是所有的首部实例都会对数据包进行操作。

解析器工作时会生成 描述数据包进行哪些匹配 +动作操作的中间表示(intermediate Representation),在P4中称之为解析后表示(Parsed Representation),这些解析后表示规定了对数据包生效的实例,是一组对数据包生效的实例的集合。

(一个理解,生成两个部分,一个用于以后match 一个用于以后的Action 只是Action部分生成的是中间表示,感觉有点类似于运行时API,方便上层的应用,让上层屏蔽底层细节。)

P4语言中解析器采用有限状态机的设计思路,每个解析器方法视为一种状态。当解析器工作时,会将当前处理的数据包头字节的偏移量记录在首部实例中,并在状态迁移(调用另一个解析器)时指向包头中的下一个待处理的有效字节。(有点类似传统wangluo解包的过程)

以以太网帧的解析器为例子,用数据包类型代对应解析器,将每个解析器作为一种状态,用箭头表示状态迁移,则可以构建出如下图的 以太网帧的解析器的状态迁移图。

以太网帧和IPV4数据包解析器定义示例

//解析开始
parser start{
  return parse_ethernet;
}
//以太网解析逻辑
parser parse_ethernet{
  extract(ethernet)
  return switch(latest.ethertype){
    //latest代表最近提取的首部实例,这里指ethernet的实例
    case 0x8100:parse_vlan;
    case 0x0800:parse_ipv4;
    //other case;
  }
}
//ipv4包头解析逻辑
parser parse_ipv4{
  extract(ipv4);
  return select(latest.protocol){
    IP_PROTOCOLS_IPHL_ICMP:parse_icmp;
    IP_PROTOCOLS_IPHL_TCP:parse_tcp;
    IP_PROTOCOLS_IPHL_UPD:parse_udp;
    IP_PROTOCOLS_IPHL_GRE:parse_gre;
    IP_PROTOCOLS_IPHL_IPV4:parse_ipv4_in_ip;
    IP_PROTOCOLS_IPHL_IPV6:parse_ipv6_in_ip;
    //其他情况
    default:ingress;
  }
}

个人感觉这个编译过不了,因为不了解swtich 以及 select的用法,switch是C语言的基础

一个解析方法/状态,可以以下面四种方式结束:

  1. return 一个流控制程序名
  2. return 一个解析器名
  3. 发生显式错误
  4. 发生隐式错误

P4语言中流控制程序和解析器的命名空间是共用的,所以在定义解析器和流控制程序的时候需要注意不能重名。变量前面最好要 像这种形式 parse_XXX.

动作

P4语言动作也是分为两种,基本动作(Primitive Actions)和复合动作(Compound Actions).基本动作包括:数据包处理运算符(如添加、删除、修改包头)、基本的算数运算符、哈希运算符和统计跟踪运算符(如计量、测量)。复合动作由基本动作组合而成,由用户自行定义。表8中展示了P4中定义的基本动作。

动作 描述
no_op 占位符动作,不做任何操作
drop 在入口流水线中将数据包丢弃
modify_field 修改解析后表示中的包头字段值
modify_field_with_hash_based_index 使用字段列表索引计算一个值并使用该值生成偏移量
add_header 为数据包的解析后表示添加包头
remove_header 为数据包的解析后表示删除包头
copy_header 复制首部实例
push 将所有首部实例亚茹一个数据,并在顶部添加一个新首部
pop 在实例数据顶部的元素淡出,后续元素向顶部移位
count 更新计数器
meter 执行计量操作
generate_digest 生成一个保温摘要并发送到接收机
truncate 在出口处截断数据包
resubmit 将原始数据包和元数据重新发送给解析器
Recirculate 在数据包完成出口修改操作后重新发送
clone_ingress_pkt_to_ingress 复制原始数据包并发送到解析器
clone_egress_pkt_to_ingress 复制出口数据包并发送到解析器
clone_ingress_pkt_to_egress 复制原始数据包并发送到缓存区
clone_egress_pkt_to_egress 复制出口数据包并发送到缓存区

这些动作高度抽象且协议无关,以实现P4语言处理数据的协议无关性。同时,复杂的操作以及流程可以通过组合的不同基本操作完成,从而保障完善了P4语言对各种协议的支持以及扩展性。

下面给出复合动作定义的示例

//定义IPV4路由动作
action route_ipv4(dst_port,dst_mac,src_mac,vid){
  //复合动作由多个基本动作复合
  modify_field(standard_metadata.egress_spec,dst_port); //修改元数据中的
  //出端口
  modify_field(ethernet.dst_addr,dst_mac);//修改目的mac
  modify_field(vlan_tag.vid,vid);//修改vlan的vid
  modify_field(ipv4.ttl,-1); //使ttl-1
}

匹配-动作表

p4语言中的匹配-动作表定义了匹配字段、动作及一些相关属性(如表容量),当匹配-动作表中定义的字段与数据包匹配成功时,则执行对应的动作;若不匹配则标记为"失配"(miss),并执行默认操作。看个定义

#define IPV4_LPM_TABLE_SIZE 65536
table ipv4_fib_lpm{
  reads{
    metadata.vrf:exact;
    ipv4:dstAddr:lpm;
  }
  actions{
    on_miss;
    fib_hit_nexthop;
  }
  size:IPV4_LPM_TABLE_SIZEL;
}
action on_miss(){
  //noop
}
action fib_hit_nexthop(){
  modify_field(metadata.fib_hit,TRUE);
  
  modify_field(metadata.fib_nexthop,nexthop_index);
  modify_field(ipv4.ttl,-1);
}
//一个类似与路由表匹配,匹配对应执行对应的操作

P4语言的匹配-动作表支持多种匹配类型,如精确匹配、最长前缀匹配、范围匹配等。如表9所示,展示了动作-匹配表支持的匹配类型。

匹配类型 描述
excat 精确匹配
ternary 三重匹配,动作-匹配表的每一个表项都有一个掩码,将掩码和字段值进行逻辑与运算,在执行匹配,为了避免导致多条表项匹配成功,每条表项都需要设定一个优先级
lpm 这是三重匹配的一种特殊情况,当多个表项匹配成功时,选择掩码最长的最高优先级的进行匹配
index 字段值作为表项索引
range 表项中确定一个范围,字段值在此范围内都能成功匹配
valid 仅用于包头字段匹配,表项值只能为true/false

流控制程序

p4语言中匹配-动作表中规定需要匹配的字段和需要执行的操作,流控制程序则用来规定匹配-动作表的执行顺序。

以P4语言定义二层转发流程为例子,数据包首先进行L2转发表(l2_fwd)匹配,然后根据数据包的以太网目的地址是否匹配路由器自身的mac地址(通过查找所属的router_mac表)决定是否要经过l3路由表(ipv4_fib_lpm和ipv6_fib_lpm),在根据包头类型(IPV4或IPV6),数据包匹配不同的L3路由表,最后通过访问控制列表来控制数据包是否通过。

control ingress{
  //二层转发
  apply(l2_fwd);
  //三层路由
  apply(router_mac){
    hit{
      if(valid(ipv4)){
        apply(ipv4_fib_lpm);
      }else{
        if(valid(ipv6)){
          apply(ipv6_fib_lpm);
        }
      }
    }
    //ACL
    apply(acl);//控制列表
  }
}

至此了解所有的P4语言的基础语言组件,并了解他们是用来干什么的。

首先定义一个首部,在来用解析器去读对应的包,然后产生解析后表示,这些解析后表示在流控制程序的控制下按一定的顺序方法去匹配 “匹配动作表”,然后执行相应的动作。

posted @ 2016-10-03 17:23  考拉小无  阅读(1804)  评论(0)    收藏  举报