看看有趣的P4 BMv2 Ⅰ:simple_switch.md

BMv2 Simple Switch target

说明:翻译自The BMv2 Simple Switch target,学习自用

bmv2框架允许开发人员将自己的P4可编程体系结构实现为软件交换机。对于大多数用户而言,simple_switch体系结构是事实上的体系结构,因为它大致等效于P4_14规范中描述的“抽象交换机模型”。

P4_16语言经过设计,可以有多种体系结构,例如,用于交换设备的一种或多种体系结构,用于NIC设备的一种或多种体系结构等。v1model体系结构包含在p4c编译器中,并且旨在与P4_14开关体系结构完全相同,可以直接从P4_14程序自动转换为使用v1model体系结构的P4_16程序。下面提到的P4_14和v1model之间有一些细微的差异,主要是在一些元数据字段的名称中。

现在,P4_16语言还具有在其自己的规范中定义的便携式交换机体系结构(PSA)。截至2019年10月,PSA体系结构的部分实现已完成,但尚未完成。它将在一个单独的名为的可执行程序中实现psa_switch,与simple_switch此处描述的程序不同。

本文档旨在为P4程序员提供有关simple_switch体系结构的重要信息。

标准元数据

对于使用v1model体系结构并包含文件的P4_16程序v1model.p4,下面的所有字段都是类型为的struct的一部分standard_metadata_t

对于P4_14程序,下面的所有字段都是名为的标题的一部分,该标题standard_metadata已为您预定义。

以下缩写用于标记下面的字段名称,以指示它们出现的位置:

  • sm14 — 该字段在P4_14语言规范的v1.0.4中的标题为“Standard Intrinsic Metadata”的第6节中定义。
  • v1m — 该字段p4include/v1model.p4在p4c存储库的包含文件中定义,旨在包含在为v1model体系结构编译的P4_16程序中。

这些是字段:

  • ingress_port(sm14,v1m)— 对于新数据包,数据包到达设备的入口端口号。只读。
  • packet_length(sm14,v1m)— 对于来自端口的新数据包或再循环数据包,以字节为单位的数据包长度。对于克隆或重新提交的数据包,您可能需要将此字段包含在要保留的字段列表中,否则其值将变为0。
  • egress_spec(sm14,v1m)— 可以在入口代码中分配一个值,以控制数据包将转到哪个输出端口。P4_14原语drop和v1model原语动作mark_to_drop具有为该字段分配实现特定值DROP_PORT的副作用(默认情况下,simple_switch为十进制下的511,但可以通过--drop-port目标特定的命令行选项进行更改),例如egress_spec在入口处理结束时具有该值,该数据包将被丢弃,不存储在数据包缓冲区中,也不发送到出口处理。请参阅“after-ingress pseudocode”,以了解入口结束时此相对于其他可能的数据包操作的相对优先级。如果您的P4程序将DROP_PORT的值分配给egress_spec,即使您从不调用mark_to_drop(P4_16)或drop(P4_14),它仍然会按照“after-ingress pseudocode”运行。
  • egress_port(sm14,v1m)— 仅打算在出口处理期间访问,只读。该数据包的目的地是输出端口。
  • egress_instance(sm14)— egress_rid在simple_switch中重命名。见下文的egress_rid
  • instance_type(sm14,v1m)— 包含可以由您的P4代码读取的值。在入口代码中,该值可用于区分数据包是从端口(NORMAL)重新到达的,还是重新提交原始操作(RESUBMIT)的结果,还是重新循环原始操作(RECIRC)的结果。在出口处理中,可用于确定数据包是否是由入口到出口克隆原始动作(INGRESS_CLONE),出口到出口克隆原始动作(EGRESS_CLONE),在入口处理期间指定的多播复制(REPLICATION)的结果而产生的,或没有这些,因此来自入口(NORMAL)的普通单播数据包。在为您预定义类似常量之前,您可以将此常量列表复制到代码中。
  • parser_status(sm14)或parser_error(v1m)— parser_status是P4_14语言规范中的名称。它已parser_error在v1model中重命名为。值0(sm14)或error.NoError(P4_16 + v1model)表示没有错误。否则,该值指示解析期间发生了什么错误。
  • parser_error_location (sm14)— 在v1model.p4中不存在,并且在simple_switch中未实现。
  • checksum_error(v1m)— 只读。如果对verify_checksum原始操作的调用发现校验和错误,则为1;否则为0。调用verify_checksumVerifyChecksum在v1model的控件中,该控件在解析器之后和入口之前执行。

内部元数据

每种体系结构通常定义自己的固有元数据字段,除了标准元数据字段外,还可以使用这些固有元数据字段来提供更多高级功能。对于simple_switch,我们有两个单独的固有元数据标头。这些标头不是体系结构严格要求的,因为可以编写P4程序并通过simple_switch运行它,而无需定义它们。但是,需要它们的存在才能启用simple_switch的某些功能。对于大多数这些字段,位宽没有严格要求,但是我们建议您遵循以下建议。这些固有元数据字段中的某些可以直接访问(读取和/或写入),而其他某些字段只能通过原始操作进行访问。

intrinsic_metadata header

对于使用v1model体系结构并包含文件的P4_16程序v1model.p4,下面的所有字段都是类型为的struct的一部分standard_metadata_t。无需为这些字段定义自己的结构类型。

对于P4_14程序,我们建议您使用以下代码为为simple_switch体系结构编写的每个P4程序定义和实例化此元数据头:

header_type intrinsic_metadata_t {
    fields {
        ingress_global_timestamp : 48;
        egress_global_timestamp : 48;
        mcast_grp : 16;
        egress_rid : 16;
    }
}
metadata intrinsic_metadata_t intrinsic_metadata;
  • ingress_global_timestamp:时间戳(以微秒为单位),当数据包在入口出现时设置。每次开关启动时,时钟都设置为0。可以直接从任一管道(入口和出口)读取此字段,但不应将其写入。
  • egress_global_timestamp:时间戳(以微秒为单位),当数据包开始进行出口处理时设置。时钟与相同ingress_global_timestamp。该字段只能从出口管道中读取,而不能写入。
  • mcast_grp:多播功能所需。当您希望对数据包进行多播时,需要在入口管道中写入此字段。值为0表示无组播。该值必须是通过bmv2运行时接口配置的有效多播组之一。请参阅“入口后伪代码”,以了解入口结束时此相对于其他可能的数据包操作的相对优先级。
  • egress_rid:多播功能所需。该字段仅在出口管道中有效,并且只能从中读取。它用于唯一标识同一入口数据包的多播副本。

queueing_metadata header

对于使用v1model体系结构并包含文件的P4_16程序v1model.p4,下面的所有字段都是类型为的struct的一部分standard_metadata_t。无需为这些字段定义自己的结构类型。

对于P4_14程序,仅在要访问排队信息时才需要定义此P4标头—提醒一下,数据包在入口和出口管道之间排队。请注意,此标头是“all or nothing”。您要么不定义它,要么定义它的所有字段(其中有4个)。我们建议您使用以下P4_14代码:

header_type queueing_metadata_t {
    fields {
        enq_timestamp : 48;
        enq_qdepth : 16;
        deq_timedelta : 32;
        deq_qdepth : 16;
        qid : 8;
    }
}
metadata queueing_metadata_t queueing_metadata;

当然,所有这些字段只能从出口管道访问,并且它们是只读的。

  • enq_timestamp:时间戳,以毫秒为单位,设置首次将数据包加入队列的时间。
  • enq_qdepth:首次将数据包排入队列时的队列深度,以数据包数(而不是数据包的总大小)为单位。
  • deq_timedelta:数据包在队列中花费的时间(以微秒为单位)。
  • deq_qdepth:数据包出队时的队列深度,以数据包数量(而不是数据包的总大小)为单位。
  • qid:当有多个队列服务每个出口端口时(例如,启用优先级队列时),每个队列都分配有固定的唯一ID,并将其写入此字段。否则,此字段设置为0。TBD:qid当前不是standard_metadata_tv1model中类型的一部分。也许应该添加?

支持的primitive(原始) actions

我们主要支持标准的P4_14基本动作。一个区别是bmv2不支持可选参数,因此始终需要所有参数(resubmit例如,请参见)。在此C++源文件中可以找到基元的完整列表。

伪代码,用于在入口和出口处理结束时发生的情况

After-ingress伪代码—简短版本:

if (a clone primitive action was called) {
    create clone(s) of the packet with details configured for the clone session
}
if (digest to generate) {   // because your code called generate_digest
    send a digest message to the control plane software
}
if (resubmit was called) {
    start ingress processing over again for the original packet
} else if (mcast_grp != 0) {  // because your code assigned a value to mcast_grp
    multicast the packet to the output port(s) configured for group mcast_grp
} else if (egress_spec == DROP_PORT) {  // e.g. because your code called drop/mark_to_drop
    Drop packet.
} else {
    unicast the packet to the port equal to egress_spec
}

After-ingress伪代码—用于确定在ingress处理完成之后数据包发生了什么。更详细的版本:

if (a clone primitive action was called) {
    // This condition will be true if your code called the `clone` or
    // `clone3` primitive action from a P4_16 program, or the
    // `clone_ingress_pkt_to_egress` primitive action in a P4_14
    // program, during ingress processing.

    Create zero or more clones of the packet.  The cloned packet(s)
    will be enqueued in the packet buffer, destined for the egress
    port(s) configured in the clone session whose numeric id was given
    as the value of the `session` parameter when the last clone
    primitive action was called.

    Each cloned packet will later perform egress processing,
    independently from whatever the original packet does next, and if
    multiple cloned packets are created via the same clone operation,
    independently from each other.

    The contents of the cloned packet(s) will be the same as the
    packet when it most recently began ingress processing, where the
    clone operation was performed, without any modifications that may
    have been made during the execution of that ingress code.  (That
    may not be the packet as originally received by the switch, if the
    packet reached this occurrence of ingress processing via a
    recirculate operation, for example.)

    If it was a clone3 (P4_16) or clone_ingress_pkt_to_egress (P4_14)
    action, also preserve the final ingress values of the metadata
    fields specified in the field list argument, except assign
    instance_type a value of PKT_INSTANCE_TYPE_INGRESS_CLONE.

    Each cloned packet will be processed by your parser code again.
    In many cases this will result in exactly the same headers being
    parsed as when the packet was most recently parsed, but if your
    parser code uses the value of standard_metadata.instance_type to
    affect its behavior, it could be different.

    After this parsing is done, each clone will continue processing at
    the beginning of your egress code.
    // fall through to code below
}
if (digest to generate) {
    // This condition will be true if your code called the
    // generate_digest primitive action during ingress processing.
    Send a digest message to the control plane that contains the
    values of the fields in the specified field list.
    // fall through to code below
}
if (resubmit was called) {
    // This condition will be true if your code called the resubmit
    // primitive action during ingress processing.
    Start ingress over again for this packet, with its original
    unmodified packet contents and metadata values.  Preserve the
    final ingress values of any fields specified in the field list
    given as an argument to the last resubmit() primitive operation
    called, except assign instance_type a value of PKT_INSTANCE_TYPE_RESUBMIT.
} else if (mcast_grp != 0) {
    // This condition will be true if your code made an assignment to
    // standard_metadata.mcast_grp during ingress processing.  There
    // are no special primitive actions built in to simple_switch for
    // you to call to do this -- use a normal P4_16 assignment
    // statement, or P4_14 modify_field() primitive action.
    Make 0 or more copies of the packet based upon the list of
    (egress_port, egress_rid) values configured by the control plane
    for the mcast_grp value.  Enqueue each one in the appropriate
    packet buffer queue.  The instance_type of each will be
    PKT_INSTANCE_TYPE_REPLICATION.
} else if (egress_spec == DROP_PORT) {
    // This condition will be true if your code called the
    // mark_to_drop (P4_16) or drop (P4_14) primitive action during
    // ingress processing.
    Drop packet.
} else {
    Enqueue one copy of the packet destined for egress_port equal to
    egress_spec.
}

After-egress伪代码—简短版本:

if (a clone primitive action was called) {
    create clone(s) of the packet with details configured for the clone session
}
if (egress_spec == DROP_PORT) {  // e.g. because your code called drop/mark_to_drop
    Drop packet.
} else if (recirculate was called) {
    start ingress processing over again for deparsed packet
} else {
    Send the packet to the port in egress_port.
}

After-egress伪代码—用于确定egress处理完成后数据包发生了什么。更详细的版本:

if (a clone primitive action was called) {
    // This condition will be true if your code called the `clone` or
    // `clone3` primitive action from a P4_16 program, or the
    // `clone_egress_pkt_to_egress` primitive action in a P4_14
    // program, during egress processing.

    Create zero or more clones of the packet.  The cloned packet(s)
    will be enqueued in the packet buffer, destined for the egress
    port(s) configured in the clone session whose numeric id was given
    as the value of the `session` parameter when the last clone
    primitive action was called.

    Each cloned packet will later perform egress processing,
    independently from whatever the original packet does next, and if
    multiple cloned packets are created via the same clone operation,
    independently from each other.

    The contents of the cloned packet(s) will be as they are at the
    end of egress processing, including any changes made to the values
    of fields in headers, and whether headers were made valid or
    invalid.  Your deparser code will _not_ be executed for
    egress-to-egress cloned packets, nor will your parser code be
    executed for them.

    If it was a clone3 (P4_16) or clone_egress_pkt_to_egress (P4_14)
    action, also preserve the final egress values of the metadata
    fields specified in the field list argument, except assign
    instance_type a value of PKT_INSTANCE_TYPE_EGRESS_CLONE.  Each
    cloned packet will have the same standard_metadata fields
    overwritten that all packets that begin egress processing do,
    e.g. egress_port, egress_spec, egress_global_timestamp, etc.

    Each cloned packet will continue processing at the beginning of
    your egress code.
    // fall through to code below
}
if (egress_spec == DROP_PORT) {
    // This condition will be true if your code called the
    // mark_to_drop (P4_16) or drop (P4_14) primitive action during
    // egress processing.
    Drop packet.
} else if (recirculate was called) {
    // This condition will be true if your code called the recirculate
    // primitive action during egress processing.
    Start ingress over again, for the packet as constructed by the
    deparser, with any modifications made to the packet during both
    ingress and egress processing.  Preserve the final egress values
    of any fields specified in the field list given as an argument to
    the last recirculate primitive action called, except assign
    instance_type a value of PKT_INSTANCE_TYPE_RECIRC.
} else {
    Send the packet to the port in egress_port.  Since egress_port is
    read only during egress processing, note that its value must have
    been determined during ingress processing for normal packets.  One
    exception is that a clone primitive action executed during egress
    processing will have its egress_port determined from the port that
    the control plane configured for that clone session).
}

支持表匹配种类

simple_switch支持具有以下任何match_kind值的表键字段:

  • exact — 来自P4_16语言规范
  • lpm — 来自P4_16语言规范
  • ternary — 来自P4_16语言规范
  • optional — 在v1model.p4中定义
  • range — 在v1model.p4中定义
  • selector — 在v1model.p4中定义

selector仅支持具有操作选择器实现的表。

如果一个表具有多个lpm键字段,则BMv2的p4c后端将拒绝该表。如下所述,这可能会稍作概括,但是从2019年1月的p4c版本开始,该限制已经到位。

Range(范围)表

如果表具有至少一个range字段,则range在BMv2中将其内部实现为表。因为单个搜索键可以匹配多个条目,所以在安装时,控制平面软件必须为每个条目分配数字优先级。如果已安装的多个表条目与同一搜索键匹配,则其中一个数字优先级最高的条目将“win”,并执行其操作。

请注意,如果使用P4Runtime API指定数字优先级,则获胜者是具有最大数字优先级值的获胜者。如果使用其他API,请查看控制平面API的文档,因为有些人可能选择使用最小数字优先级值胜过较大数字优先级值的约定。

一个range表可能有一个lpm字段。如果是这样,则前缀长度用于确定搜索关键字是否与条目匹配,但是前缀长度不能确定多个匹配表条目之间的相对优先级。只有控制平面软件提供的数字优先级才能确定。因此,一个range表支持多个lpm关键字段是合理的,但是从2020年1月开始,不支持此功能。

如果范围表具有通过const entries表属性定义的条目,则根据它们在P4程序中出现的顺序,条目的相对优先级从高优先级到低优先级。

Ternary(三元)表

如果一个表没有range字段,但至少有一个ternaryoptional字段,则ternary在BMv2中将其内部实现为表。对于range表,单个搜索键可以与多个表条目匹配,因此每个条目必须具有由控制平面软件分配的数字优先级。关于表格的lpm上述字段的注释range也适用于ternary表格,以及关于通过指定的条目的注释const entries

Longest prefix match(最长前缀匹配)表

如果一个表没有range,,ternary也没有optional字段,但至少有一个lpm字段,则必须恰好有一个lpm字段。可以有0个或多个exact字段。尽管可以有多个已安装的表条目与单个搜索键匹配,但是由于这些限制,该lpm字段的每个可能前缀长度最多可以有一个匹配表条目(因为不允许同时安装两个表条目)相同的搜索键)。前缀长度最长的匹配条目始终是获胜者。在为此类表安装条目时,控制平面无法指定优先级-它始终由前缀长度确定。

如果最长前缀匹配表具有通过const entries表属性定义的条目,则条目的相对优先级由前缀长度(而不是它们在P4程序中出现的顺序)确定。

Exact match(完全匹配)表

如果表仅包含exact字段,则exact在BMv2中将其内部实现为表。对于任何搜索关键字,最多只能有一个匹配的表条目,因为不允许重复的搜索关键字。因此,不需要数字优先级来确定“win”匹配表项。BMv2(以及许多其他P4实现)使用哈希表实现该表功能的匹配部分。

如果精确匹配表具有通过const entries表属性定义的条目,则任何搜索关键字最多可以有一个匹配条目,因此,在P4程序中出现的相对顺序对于确定哪个条目将获胜并不重要。

使用以下命令为表条目指定匹配条件const entries

下表显示了对于match_kindP4_16表键字段的每个值,允许使用哪种语法为const entries列表中的表条目指定一组匹配字段值。

在所有允许的情况下,一个限制是lohival,和mask必须是该领域的标准规定可能值,即不是外面该字段的值的范围。它们都可以是包含编译时间常数值的算术表达式。

range ternary optional lpm exact
lo .. hi 是(注1) 没有 没有 没有 没有
val &&& mask 没有 是(注2) 没有 是(注3) 没有
val 是(注4) 是(注5) 是(注5)
_ or default 是(注6) 是(注6) 是(注6) 是(注6) 没有

注1:限制:lo <= hi。运行时搜索关键字值k与匹配lo <= k <= hi

注2:限制:val == (val & mask)。的位位置mask等于1是精确匹配位的位置,且位位置,其中mask为0的“外卡”或不关心位的位置。运行时搜索关键字值k与匹配(k & mask) == (val & mask)

注3:限制:val == (val & mask)并且mask是一个“prefix mask(前缀掩码)”,即它具有所有位置是连续的1 bit,并且在该字段的最高有效bit位置。警告:如果在P4_16程序尝试指定的前缀作为val/prefix_length(用于指定的前缀,诸如simple_switch_CLI等使用命令行界面的语法),则它实际上是将val除以prefix_length的算术表达式,因此val的情况将视为完全匹配。编译器不会发出警告,因为它是完全合法的除法运算语法。

注4:等同于范围val .. val,因此其行为与上的完全匹配val

注5:在该字段的所有位位置中,等于val &&& maskwhere mask为1,因此它的行为与上的完全匹配val

注6:匹配该字段的任何允许值。等价于min_posible_field_value .. max_possible_field_valuerange字段,或0 &&& 0ternarylpm字段。

以下是P4_16程序的一部分,该程序演示了match_kind用于指定匹配值集的大多数允许的组合和语法。

header h1_t {
    bit<8> f1;
    bit<8> f2;
}

struct headers_t {
    h1_t h1;
}

// ... later ...

control ingress(inout headers_t hdr,
                inout metadata_t m,
                inout standard_metadata_t stdmeta)
{
    action a(bit<9> x) { stdmeta.egress_spec = x; }
    table t1 {
        key = { hdr.h1.f1 : range; }
        actions = { a; }
        const entries = {
             1 ..  8 : a(1);
             6 .. 12 : a(2);  // ranges are allowed to overlap between entries
            15 .. 15 : a(3);
            17       : a(4);  // equivalent to 17 .. 17
            // It is not required to have a "match anything" rule in a table,
            // but it is allowed (except for exact match fields), and several of
            // these examples have one.
            _        : a(5);
        }
    }
    table t2 {
        key = { hdr.h1.f1 : ternary; }
        actions = { a; }
        // There is no requirement to specify ternary match criteria using
        // hexadecimal values.  I personally prefer it to make the mask bit
        // positions more obvious.
        const entries = {
            0x04 &&& 0xfc : a(1);
            0x40 &&& 0x72 : a(2);
            0x50 &&& 0xff : a(3);
            0xfe          : a(4);  // equivalent to 0xfe &&& 0xff
            _             : a(5);
        }
    }
    table t3 {
        key = {
            hdr.h1.f1 : optional;
            hdr.h1.f2 : optional;
        }
        actions = { a; }
        const entries = {
            // Note that when there are two or more fields in the key of a
            // table, const entries key select expressions must be surrounded by
            // parentheses.
            (47, 72) : a(1);
            ( _, 72) : a(2);
            ( _, 75) : a(3);
            (49,  _) : a(4);
            _        : a(5);
        }
    }
    table t4 {
        key = { hdr.h1.f1 : lpm; }
        actions = { a; }
        const entries = {
            0x04 &&& 0xfc : a(1);
            0x40 &&& 0xf8 : a(2);
            0x04 &&& 0xff : a(3);
            0xf9          : a(4);  // equivalent to 0xf9 &&& 0xff
            _             : a(5);
        }
    }
    table t5 {
        key = { hdr.h1.f1 : exact; }
        actions = { a; }
        const entries = {
            0x04 : a(1);
            0x40 : a(2);
            0x05 : a(3);
            0xf9 : a(4);
        }
    }
    // ... more code here ...
}

Recirculate、resubmit和clone操作的限制(Restrictions)

Recirculate、resubmit和clone是不保留元数据的操作,即,它们具有应保留其值的字段的空列表,使用p4c和simple_switch(使用P4_14作为源语言,或使用P4_16和v1model体系结构)时可以按预期工作。

不幸的是,这些尝试保留元数据的操作中至少有一些不能正常工作 — 它们仍然导致数据包被recirculate、resubmit或clone,但它们并未保留所需的元数据字段值。

这是一个已知问题,p4c开发人员和P4语言设计工作组对此进行了详尽的讨论,做出的决定是:

  • 从长远来看,与v1model所使用的P4_16便携式交换机架构采用不同的机制来指定要保留的元数据,并且应该可以正常工作。截至2019年10月,PSA的实现尚未完成,因此这对今天编写工作代码没有帮助。
  • 如果有人希望自愿对p4c和/或simple_switch进行更改以改善这种情况,请与P4语言设计工作组联系并进行协调。
    • 帮助的首选形式是完成便携式交换机体系结构的实现。
    • 另一种可能性是增强v1model架构的实现。这里描述了几种这样做的方法。

请注意,此问题不仅影响使用v1model体系结构的P4_16程序,而且还影响使用p4c编译器编译的P4_14程序,因为在内部p4c编译器将导致问题发生的部分之前将P4_14转换为P4_16代码。

根本问题是P4_14具有field_list构造,这是对其他字段的named reference一种受限类型。当这些字段列表在P4_14中用于保留元数据的重新分发,重新提交和克隆操作时,它们不仅指示目标读取这些字段值,而且以后还会将它们写入(对于循环,重新提交或克隆后的数据包)。使用P4_16的语法字段列表{field_1, field_2, ...}仅限于在执行语句或表达式时访问字段的当前值,但不表示任何类型的引用,并且P4_16语言规范不能导致这些字段的值发生更改在程序的另一部分。

有关区分何时正确保存元数据的提示

以下是任何试图通过钩子或骗子使元数据的保留与当前p4c实现一起工作而没有上述未来改进的人的一些详细信息。

没有已知的简单规则来确定这些操作的哪些调用将正确地保留元数据,而哪些不能。如果至少需要一些指示,请检查作为编译器输出生成的BMv2 JSON文件。

Python程序bmv2-json-check.py尝试确定是否有任何用于保留元数据字段以进行recirculate、resubmit和clone操作的字段列表看起来像是编译器生成的临时变量的名称。警告:它的方法是相当基本的,因此不能保证如果脚本说没有问题,元数据保存将正确运行,或者相反,如果脚本说发现可疑的东西,元数据保存将不能正确运行。它可以自动执行以下内容。
警告:它的方法是相当基本的,因此不能保证脚本没问题的情况下正常进行保存元数据的工作,或者相反,如果在脚本发现可疑的东西,元数据保存将无法正确工作。它可以自动执行以下内容。

BMv2 JSON文件中的JSON数据表示每个重新分发,重新提交和克隆操作。搜索以下with双引号的字符串:

  • "recirculate" — 唯一的参数是field_list id
  • "resubmit" — 唯一的参数是field_list id
  • "clone_ingress_pkt_to_egress" — 第二个参数是field_list ID
  • "clone_egress_pkt_to_egress" — 第二个参数是field_list ID

您可以使用key在BMv2 JSON文件的部分中的每个字段列表中找到这些字段"field_lists"。无论字段名称在哪里,simple_switch都将使用这些名称保留字段的值。主要问题是这些字段是否与编译器用来表示要保留的字段具有相同存储位置。

在某些情况下,如果BMv2 JSON文件中的字段名称看起来与P4源代码中的字段名称相同或相似,通常就是这种情况。

在某些情况下,它们表示不同的存储位置,例如,编译器生成的临时变量,用于保存要保留的字段的副本。截至2019年10月p4c为止,以tmp.字段开头的字段暗示了这一点,但这是一个可能会更改的p4c实现细节。

P4_16 plus v1model架构说明

本部分是针对P4_16的v1model架构的不完整描述。本文档的许多早期部分也对P4_16 v1model体系结构进行了描述,但在某些情况下,它们还用于记录的P4_14行为simple_switch。本部分仅适用于特定于P4_16以及v1model架构的内容。

在某些情况下,此处记录的详细信息是关于如何在BMv2 simple_switch中实现v1model体系结构的。此类详细信息以“ BMv2 v1model implementation”开头。v1model架构的其他实现可能会做出不同的实现选择或限制。

H类型限制

v1model.p4中,有一个包V1Switch定义的名为的H类型的参数,摘录如下:

package V1Switch(Parser p,
                       VerifyChecksum vr,
                       Ingress ig,
                       Egress eg,
                       ComputeChecksum ck,
                       Deparser dep
                       );

H类型被限制为仅包含以下类型之一的成员字段的结构:

  • header
  • header_union
  • header stack

Parser代码的限制

P4_16语言规范版本1.1不支持解析器内部的if语句。因为p4c编译器在调用位置处内联调用函数,所以此限制扩展到从解析器调用的函数的函数主体。有两种可能的解决方法:

  • 如果通过等会在ingress control processing来执行条件逻辑同样有效,则可以在ingress control processing中使用if语句。
  • 在某些情况下,您可以使用transition select语句跳转到不同的解析器state,以实现所需的条件执行效果,每个解析器状态可以具有自己的不同代码。

v1model体系结构中,到达reject解析器状态(例如由于verify呼叫失败)的数据包不会自动丢弃。这样的数据包将以标准元数据字段Ingress的值parser_error等效于发生错误的情况开始处理。如果需要,您的P4代码可以定向丢弃此类数据包,但是您也可以选择编写对此类数据包执行其他操作的代码,例如,将它们的副本发送到控制CPU进行进一步分析或错误记录。

p4c plus simple_switch不支持在源代码中显式转换到reject状态。它只能通过调用verify失败来转到。

VerifyChecksum控件中对代码的限制

VerifyChecksum将在Parser完成之后和Ingress开始之前执行。p4c plus simple_switch仅支持此类控件内部对verify_checksumverify_checksum_with_payload外部函数​​的一系列调用。有关其定义,请参见v1model.p4 include文件。这些函数的第一个参数是布尔值,它可以是用于使校验和计算以该表达式为真为条件的任意布尔条件。

v1model架构中,不会自动丢弃具有错误校验和的数据包。这样的数据包将以checksum_error标准元数据字段的值等于1开始Ingress处理。如果您的程序有多个对verify_checksum和/或verify_checksum_with_payload的调用,则无法确定到底是哪里调用了使得校验和不正确。如果您愿意,您的P4代码可以指示丢弃此类数据包,但这不会自动完成。

IngressEgress控件中的代码限制

simple_switch不支持同一表在Ingress控件执行一次以上或在Egress控件执行一次以上。

在某些情况下,您可以通过使用多个具有相似定义的表来解决此限制,并且这个表每次在Ingress(或Egress)要执行一次apply时都会被相似地定义一次。如果希望两个这样的表包含相同的表条目和动作集,则必须编写控制平面以使其内容相同,例如,每次向T2添加条目时,也始终给T1添加一个条目。

这种限制没准是simple_switch可以解决的,但是要注意P4的某些高速ASIC实现也可能会施加相同的限制,因为该限制可能是由硬件设计施加的。

Action中对代码的限制

这些限制实际上是p4c编译器的限制,而不是simple_switch的限制。任何有兴趣增强p4c以消除这些限制的人都来一起康康以下问题。

P4_16语言规范v1.1.0允许在动作声明中使用if语句。p4c在为目标BMv2 simple_switch进行编译时,支持某些类型的if语句,尤其是可以使用三目运算符condition ? true_expr : false_expr转换为赋值的语句。如下的情况是支持的:

    action foo() {
        meta.b = meta.b + 5;
        if (hdr.ethernet.etherType == 7) {
            hdr.ethernet.dstAddr = 1;
        } else {
            hdr.ethernet.dstAddr = 2;
        }
    }

但截至2019年7月1日下面这种写法是不支持的,:

    action foo() {
        meta.b = meta.b + 5;
        if (hdr.ethernet.etherType == 7) {
            hdr.ethernet.dstAddr = 1;
        } else {
            mark_to_drop(standard_metadata);
        }
    }

给定P4_16语言规范中的以下文本,很可能还有其他P4实现对动作中if语句提供了有限的支持或不提供支持:

No `switch` statements are allowed within an action --- the grammar permits
them, but a semantic check should reject them.  Some targets may impose
additional restrictions on action bodies --- e.g., only allowing
straight-line code, with no conditional statements or expressions.

因此,非要在action中使用if语句的P4程序可能比避免这样做的程序具有更低的可移植性。

如上所述,将在未来增强p4c功能使action内的各种if语句得到支持。

  • p4c issue #644
  • behavioral-model issue #379

ComputeChecksum控件中对代码的限制

ComputeChecksum控件在Egress控件完成之后和Deparser控件开始之前立即执行。p4c plus simple_switch仅支持此类控件内部对update_checksumupdate_checksum_with_payload外部函数​​的一系列调用。这些函数的第一个参数是布尔值,它可以是一个任意布尔条件,用于使校验和更新操作以该表达式为true为条件。

Deparser控件中对代码的限制

Deparser控件被限制为仅包含对packet_out对象的发出方法的一系列调用。

避免ComputeChecksumDeparser控件中限制的最直接方法是在Egress控件的末尾编写您想要的通用代码。

BMv2 register实施说明

p4c plus simple_switch都支持寄存器数组,其元素为bit int 类型的任意值,但不包含其他类型,例如在某些程序中使用structs(结构体)会很方便,但是就不支持这样的功能。

作为解决此限制的一种方法的示例,假设您想要一个结构,该结构具有三个字段x,y和z,类型为bit <8>bit <3>bit <6>。您可以通过使用类型为bit <17>(3个字段的总宽度)的元素制作一个寄存器数组来模拟此情况,并在从寄存器数组中读取后使用P4_16位切片操作将17位值中的3个字段分开;用P4_16 bit向量串联操作,将3个字段组合在一起,形成17位结果,然后将17位值写入寄存器数组。

    register< bit<17> >(512) my_register_array;
    bit<9> index;

    // ... other code here ...

    // This example action is written assuming that some code executed
    // earlier (not shown here) has assigned a correct desired value
    // to the variable 'index'.

    action update_fields() {
        bit<17> tmp;
        bit<8> x;
        bit<3> y;
        bit<6> z;

        my_register_array.read(tmp, (bit<32>) index);
        // Use bit slicing to extract out the logically separate parts
        // of the 17-bit register entry value.
        x = tmp[16:9];
        y = tmp[8:6];
        z = tmp[5:0];

        // Whatever modifications you wish to make to x, y, and z go
        // here.  This is just an example code snippet, not known to
        // do anything useful for packet processing.
        if (y == 0) {
            x = x + 1;
            y = 7;
            z = z ^ hdr.ethernet.etherType[3:0];
        } else {
            // leave x unchanged
            y = y - 1;
            z = z << 1;
        }

        // Concatenate the updated values of x, y, and z back into a
        // 17-bit value for writing back to the register.
        tmp = x ++ y ++ z;
        my_register_array.write((bit<32>) index, tmp);
    }

尽管p4c plus simple_switch支持所需的宽度W以用于数据包处理的数组元素,但Thrift API(由simple_switch_CLI使用,可能还包括某些交换机控制器软件)仅支持控制平面对数组元素的读写操作,最多64个位宽(请参阅文件standard.thrift中的BmRegisterValue类型,截至2019年10月为64位整数)。 P4Runtime API没有此限制,但是对于simple_switch,还没有完成寄存器读写操作的P4Runtime实现:p4lang/PI#376

BMv2 v1model实现支持并行执行。它使用对动作中访问的所有寄存器对象的锁定来确保动作中所有步骤的执行是原子的,相对于执行同一动作的其他数据包或访问某些相同寄存器对象的任何动作而言。您无需在P4_16程序中使用@atomic注释即可为您保证这种原子性。

截至2019年10月的BMv2 v1model会忽略P4_16程序中的@atomic注释。因此,即使使用此类注释,也不会使BMv2将大于一个action调用的任何代码块都视为atomic事务。

BMv2 random实施说明

random函数的BMv2 v1model实现支持lohi参数作为run-time(运行时的)变量,即它们不必是编译时间常数。

同样,它们不必限于(hi - lo + 1)的2次幂的约束。

类型Tbit 被限制为W <= 64

BMv2 hash实施说明

hash函数的BMv2 v1model实现支持basemax参数作为运行时的变量,即不必是编译时间常数。

同样,max不必限于它是2的幂的约束。

调用从数据H计算得出的哈希值。写入max参数的名为result的值:如果max> = 1,则(base + (H % max));否则为base

类型OresultTbaseMmax被限制为W <= 64bit

BMv2 direct_counter实施说明

如果表t通过在其定义中具有表属性counters = c而与direct_counter对象c关联,则BMv2 v1model实现的行为就好像该表的每个操作都恰好包含一次对c.count()的调用一样,无论是否是没有、一次或多次。

BMv2 direct_meter实施说明

如果表t通过在其定义中具有表属性meter = m而与direct_meter对象m关联,则该表的至少一个动作必须调用m.read(result_field);,对于某些字段result_field具有键入bit 其中W> = 2

完成此操作后,BMv2 v1model实现的行为就好像 表的所有操作t都在其中进行了此类调用,即使它们没有。

如果您对表t有两个动作,其中一个动作为m.read(result_field1),另一个动作为m.read(result_field2),则p4c编译器将给出错误消息,并且如果这两个都通话处于同一动作。m的所有read()方法调用都必须在写入结果的地方具有相同的result参数。

你可能感兴趣的:(看看有趣的P4 BMv2 Ⅰ:simple_switch.md)