1小时学会P4-16编程基础

本文主要讲述一些P4-16的基本元素,以及相关基础架构,旨在帮助初学者快速上手P4-16。

P4开源项目

P4项目源码可以在github上直接获取(https://github.com/p4lang)。

项目关系

项目关系如下:
image.png

  • 高级语言层:高级抽象的P4语言编写程序
  • 前端编译器:对高级语言进行与目标无关的语义分析并生产中间表示
  • 中间表示层:高级语言中间表示,可转换成多种其他语言
  • 后端编译器:将中间表示转换为目标平台机器码
  • 目标对象层:受控制硬件/软件设备

项目功能

P4项目由很多个单独的模块组成,每个模块就是一个子项目,各子项目功能介绍如下:

  • p4-hlir: 前端编译器。将P4代码转换成高级中间表示。目前,高级中间表示的展示形式与Python对象的层次结构相同。作用是: 使后端编译器不用关心语法分析和目标无关的语义检查

  • p4c-bm:后端编译器。可以将高级语言或高级中间表示转为JSON格式或PD格式的配置文件。

  • behavivoral-model: 又称bmv2,P4软件交换机。使用C++语言编写。该模块主要实现三个目标,最重要的是simple_switch,即P4语言标准中抽象交换机模型。另外两个目标是simple_router, l2_switch

  • p4-build: 需要手动生成的基础设施库,为执行P4程序,编译安装PD库。

  • switch: switch示例,基本完成交换机的绝大部分功能。

  • p4factory: 快速开始,内含6个可快速启动的项目,包括basic_routing, copy_to_cpu, l2_switch, sai_p4, simple_router, switch

  • ntf (Network Test Framework): 网络测试框架。内含用以执行bmv2应用的网络测试用例。该框架中集成了mininet和docker。

  • ptf:Python测试框架,基于unittest框架实现,该框架中的大部分代码从floodlight项目中的OFTest框架移植而来。

  • tutorials:示例教程。包括Basic Forwarding, Basic Tunneling, P4Runtime, Explicit Congestion Notification, Multi-Hop Route Inspection, Source Routing, Calculator, Load Balancing。

  • scapy-vxlan: 扩展Vxlan和ERSPAN-like协议包头处理。

P4语言基础(P4-16)

相关术语

  • PISA(Protocol-Independent Switch Architecture):协议无关交换机架构。
  • P4 Target:特定的硬件实现。
  • P4 Architecture:通过一组P4可编程、外部(externs)与固定组件,提供对P4 Target进行编程的接口。

P4语言与核心库由社区发展而来。外部(extern)库以及P4架构定义由设备上提供。

PISA

image.png

  • Parser:根据程序员声明的数据报头,解析数据包,生产独立的数据包头与中间信息(MetaData);
  • Match-Action Pipeline:根据解析的报头与元信息,匹配流表,对报头进行修改、添加、删除等操作;
  • Deparser:数据包重新被序列化。

P4架构

image.png

除了V1Model,目前还有Portable Switch Architecture和Tofino native等架构,目标是tofino native的架构比v1model有更多的功能,PSA的架构可以被更多的target (FPGA,ASIC,Software)所支持,提供从V1model到其他两个架构的translation。所以可以按照v1model的架构来写程序,之后compiler会帮助把v1model的程序转换成其他的架构的程序。

P4-16语言元素

  • Parser:通过一系列状态(State)转换,提取报头字段,与一些元数据;
  • Control:Tables, Actions, control flow声明;
  • Expressions:基本的操作与运算符;
  • Data Type:BiStrings,header, Struct,array等数据类型。
  • Architecture Description:目标硬件提供的可编程的组件与接口;
  • Extern Libraries:目标硬件支持的特定组件。

image.png

为一个目标硬件编写P4程序

image.png

整个过程由几部分组成,如下:

  • 用户提供: P4程序与控制面;
  • 厂家提供:P4架构模型与目标硬件;
  • 运行时操作:控制流表,外部控制,数据包上报控制面等。

数据类型

基础类型

  • bit<n>: n位的无符号整型(bitstring);
  • bit: 等同于bit<1>
  • int<n>:n(n >=2)位的有符号整型;
  • varbit<n>:最大为n位的变长bitstring。

示例:

bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;

typedef 定义类型别名

设置类型别名,如:

typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header 类型

header类型为有序的程序集合,特点如下:

  • 可以包含bit<n>、int<n>、varbit<n>基础数据类型;
  • 必须字节对齐
  • 可以是validinvalid,并提供几种操作测试或设置有效位:isValid()/setValid()/setInvalid()

示例:

typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
    macAddr_t dstAddr;
    macAddr_t srcAddr;
    bit<16> etherType;
}

header ipv4_t {
    bit<4> version;
    bit<4> ihl;
    bit<8> diffserv;
    bit<16> totalLen;
    bit<16> identification;
    bit<3> flags;
    bit<13> fragOffset;
    bit<8> ttl;
    bit<8> protocol;
    bit<16> hdrChecksum;
    ip4Addr_t srcAddr;
    ip4Addr_t dstAddr;
}

struct 类型

struct类型为无序数据类型集合,且不需字节对齐。可用于元数据的组织。

示例:

/* Architecture */
struct standard_metadata_t {
    bit<9> ingress_port;
    bit<9> egress_spec;
    bit<9> egress_port;
    bit<32> clone_spec;
    bit<32> instance_type;
    bit<1> drop;
    bit<16> recirculate_port;
    bit<32> packet_length;
    ...
}
/* User program */
struct metadata {
...
}

parser

类似于C语言的function声明关键字,可以有循环。通过一系列状态(state)执行与转换,提取报头字段与元数据。

示例如下:

/* From core.p4 */
extern packet_in {
    void extract<T>(out T hdr);
    void extract<T>(out T variableSizeHeader,
    in bit<32> variableFieldSizeInBits);
    T lookahead<T>();
    void advance(in bit<32> sizeInBits);
    bit<32> length();
}

/* Architecture */
struct standard_metadata_t {
    bit<9> ingress_port;
    bit<9> egress_spec;
    bit<9> egress_port;
    bit<32> clone_spec;
    bit<32> instance_type;
    bit<1> drop;
    bit<16> recirculate_port;
    bit<32> packet_length;
    ...
}

/* User program */
struct metadata_t {
    ...
}

struct headers_t {
    ethernet_t ethernet;
    ipv4_t ipv4;
}

parser MyParser(packet_in packet,
    out headers hdr,
    inout metadata meta,
    inout standard_metadata_t std_meta) {

    state start {
        packet.extract(hdr.ethernet);
        transition accept;
    }
}

parser的输入与输出

parser MyParser(packet_in packet,
    out headers hdr,
    inout metadata meta,
    inout standard_metadata_t std_meta) {...}

parser的输入与输出模型如下:
image.png

  • parser输入:
    • packet_in类型数据包,是由目标设备提供的extern类型数据。
    • metadata: 用户自定义元数据;
    • standard_metadata_t:**设备产生提供的元数据。
  • parser输出:
    • header:用户定义的数据报头;
    • metadata: 用户自定义元数据;
    • standard_metadata_t:设备产生提供的元数据。

注意extern packet_in提供几个接口,说明如下:

  • void extract<T>(out T hdr): 从数据报文指针开始位置,抽取T类型数据大小的报头,存储在hdr,并将数据报文指针前移。可能触发PacketTooShort or StackOutOfBounds错误,T类型的数据的大小必须为固定值**。
  • void extract<T>(out T variableSizeHeader, in bit<32> variableFieldSizeInBits):读取数据报头到可变大小的报头变量variableSizeHeader中,variableSizeHeader必须包含一个及以上varbit字段。数据报指针前移
  • T lookahead<T>():读取T类型数据大小的报头,但不前移数据报指针
  • void advance(in bit<32> sizeInBits):前移数据报头指针。
  • bit<32> length():返回数据报的字节数。

parser的状态与转换

状态

parser有三种预设状态state,为:

  • start:进入parser功能块后的第一个状态,自动进入;
  • accept:若进入accept状态,则表示数据报进入后续control流程;
  • reject:数据包被抛弃。

其他states,用户可以自行定义。每个state执行0次或以上,然后转换为其他state。(允许循环

转换

  1. 使用transition关键字使parser在状态之间转换。

示例:

parser MyParser(...) {
    state start {
        packet.extract(hdr.ethernet);
        transition accept;
    }
}
  1. 使用transition select(data){...}根据data的值转换到不同状态。
    select声明类似C语言中的switch...case,但是其没有fall-through行为,不用break进行中断后续选择。
state start {
    transition parse_ethernet;
}

state parse_ethernet {
    packet.extract(hdr.ethernet);
    transition select(hrd.ethernet.etherType) {
        0x800: parse_ipv4;
        default: accept;
    }
}

state parse_ipv4 {
    packet.extract(hdr.ipv4);
    transition accept;
}

value_set

只能用在parser 语句块中。

emit

pkt.emit 不支持条件emit,mirror.emit支持单bit的条件emit

header stacks

头堆栈有两个属性 nextlast,其可以在解析器中解析时使用。

举例如下,使用如下mpls定义表示10个MPLS头:

header Mpls_h {
    bit<20> label;
    bit<3> tc;
    bit bos;
    bit<8> ttl;
}

Mpls_h[10] mpls;

mpls.next 表示 mpls 堆栈中的一个元素的值。初始时,mpls.next 指向堆栈的第一个元素,当成功调用extract方法后,mpls.next将自动向前偏移,指向下一个元素。
mpls.last指向 next 前面的那个元素(如果元素存在),即最近 extract 出来的那个元素。

注意:通过 next 或 last 属性访问头堆栈中不存在的元素将引发 transition reject 状态转换,并设置错误到 error.StackOutOfBounds

struct Pkthdr {
    Ethernet_h ethernet;
    Mpls_h[3] mpls;
    // other headers omitted
}

parser P(packet_in b, out Pkthdr p) {
    state start {
        b.extract(p.ethernet);
        transition select(p.ethernet.etherType) {
            0x8847: parse_mpls;
            0x0800: parse_ipv4;
        }
    }

    state parse_mpls {
        b.extract(p.mpls.next);
        transition select(p.mpls.last.bos) {
            0: parse_mpls; // This creates a loop
            1: parse_ipv4;
        }
    }

    // other states omitted
}

control

对于固定硬件,SwitchIngress 控制流程的参数固定,如下:

control SwitchIngress(
        inout header_t hdr,
        inout metadata_t ig_md,
        in ingress_intrinsic_metadata_t ig_intr_md,
        in ingress_intrinsic_metadata_from_parser_t ig_prsr_md,
        inout ingress_intrinsic_metadata_for_deparser_t ig_dprsr_md,
        inout ingress_intrinsic_metadata_for_tm_t ig_tm_md) {
        ...
}
  • 类似于C语言的function声明关键字,不能有循环
  • if 语句中表达式<, >两侧只能有一个变量,==两侧可以都为变量,但判断条件产生的PHV不能超过 44 bits,否则报如下错误
if (eg_md.lkp.outer_dst_mac == eg_md.lkp.outer_src_mac) {
/home/lihaifeng/test/casteni-gw/p4src/casteni_gw.p4(314): error: : condition too complex, limit of 4 bytes + 12  bits of PHV input exceeded

/home/lihaifeng/test/casteni-gw/p4src/casteni_gw.p4(314): [--Werror=legacy] error: condition too complex, one operand of > must be constant
  • 在control中,可以申明变量、创建tables,以及实例化externs等。
  • 在control中,通过apply声明功能;
  • 代表可表达为DAG的所有类型的处理,包括:
    • Match-Action Pipelines;
    • Deparsers;
    • 其他的数据报处理,如更新checksums。
  • 由用户与特定架构指定的接口,如headers和metadata。

示例:

control MyIngress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t std_meta) {
    /* Declarations region */
    bit<48> tmp;
    apply {
        /* Control Flow */
        tmp = hdr.ethernet.dstAddr;
        hdr.ethernet.dstAddr = hdr.ethernet.srcAddr;
        hdr.ethernet.srcAddr = tmp;
        std_meta.egress_spec = std_meta.ingress_port;
    }
}

上述程%

Was this helpful?

3 / 0