Contents

P4-学习资料

P4-learning wiki

入门

将此存储库克隆到您的机器中:

1
git clone https://github.com/nsg-ethz/p4-learning.git

所需软件

  • PI为P4Runtime服务器提供了一个实现框架。只有涉及P4Runtime交换机的拓扑才需要它。
  • behavioral Model(BMv2)包含行为模型的几种变体(例如simple_switch和simple_switch_grpc)的软件实现。
  • P4C是支持P4_14和P4_16的 P4 编程语言的参考编译器。
  • Mininet允许在单个机器(VM、云或本机)上创建一个真实的虚拟网络,运行真实的内核、交换机和应用程序代码。
  • FRRouting是适用于 Linux 和 Unix 平台的免费开源 Internet 路由协议套件。它实现了 BGP、OSPF、RIP、IS-IS、PIM、LDP、BFD、Babel、PBR、OpenFabric 和 VRRP,并支持 EIGRP 和 NHRP。P4-Utils中的路由器节点基于FRRouting。没有路由器的拓扑不需要它。
  • P4-Utils是支持 P4 设备的Mininet的扩展它受到p4lang存储库中原始p4app的强烈启发。

迁移到新版本

新特性

P4-Utils是一个框架,它可以创建带有 P4 设备的虚拟网络

  • 现在完全基于 Python 3。
  • 用于启动网络的遗留 JSON 配置文件已得到简化。
  • 现在也可以使用 Python 脚本和新的NetworkAPI.
  • P4Runtime目前可以使用新的 API。
  • 出于兼容性原因,保留了遗留的Thrift API 和客户端。

如何迁移到新版本?

官网

BMv2 simple swich

介绍

Simple Switch是 P4 开发中使用的实际架构。Simple Switch架构是P4_14规范(P4 语言的第一版)中提出的抽象交换机模型的实现。Simple Switch使用行为模型 (BMv2) 库实现,这是一个允许开发人员实现自己的软件 P4 target的框架。

BMv2 存储库实现了两个不同版本的Simple Switch,它们具有不同的控制平面接口。

|目标target|控制平面| |:=:|:=:| |simple_switch|Thrift| |simple_switch_grpc|P4Runtime,Thrift|

对于控制平面,下表显示了配置Simple Switch的不同方法。

|控制平面|方法| |:=:|:=:| |Thrift|SimpleSwitchThriftAPI,simple_switch_CLI| |P4Runtime|SimpleSwitchP4RuntimeAPI|

v1model架构(我们在每个程序开始时导入的架构)是Simple Switch的架构库目标。它包括所有标准元数据和内部元数据字段、外部函数和交换机架构(或管道)包描述的声明。

现在,P4_16语言也有自己的规范中定义的便携式交换架构(PSA)。截至 2018 年 9 月,PSA 架构的部分实现已经完成,但尚未完成。它将在名为psa_switch的单独可执行程序中实现,与此处描述的simple_switch程序不同。

标准元数据

通过standard_metadata_t类型结构体访问以下字段

  • ingress_port (bit<9>):数据包到达设备的入口端口号。只读。对于重新提交和重新循环的数据包ingress_port 是0。
  • packet_length (bit<32>):来自端口的新数据包或再循环数据包的长度(以字节为单位)。对于克隆或重新提交的数据包,您可能需要将其包含在要保留的字段列表中,否则其值将变为0.
  • egress_spec (bit<9>)可以在ingress入口代码中分配一个值,以控制数据包将转到哪个输出端口。mark_to_drop函数为该字段分配实现特定值DROP_PORT.
  • egress_port (bit<9>):仅在出口处理期间访问,只读。此数据包的目标输出端口。
  • instance_type (bit<32>):入口代码中用于区分数据包是从端口新到达的(NORMAL),重新提交原语操作的结果(RESUBMIT),还是再循环原语操作的结果(RECIRC)。出口处理中用于确定数据包是否作为入口到出口克隆原语操作 ( INGRESS_CLONE)、出口到出口克隆原语操作 ( EGRESS_CLONE)、入口处理期间指定的多播复制 ( REPLICATION)、或者来自入口 ( NORMAL) 的普通单播数据包。
  • parser_error:该值指示解析期间发生的错误:
1
2
3
4
5
6
7
8
error {
    NoError,           /// No error.
    PacketTooShort,    /// Not enough bits in packet for 'extract'.
    NoMatch,           /// 'select' expression has no matches.
    StackOutOfBounds,  /// Reference to invalid element of a header stack.
    HeaderTooShort,    /// Extracting too many bits into a varbit field.
    ParserTimeout      /// Parser execution time limit exceeded.
}

排队元数据

当从入口到出口管道时,由交换机填充队列元数据。因此,只能从出口管道访问,并且它们是只读的。通过standard_metadata_t类型结构体访问以下字段

  • enq_timestamp (bit<32>): 在数据包首次入队时设置的时间戳,以微秒为单位。
  • enq_qdepth (bit<19>): 数据包首次入队时的队列深度。
  • deq_timedelta (bit<32>): 数据包在队列中花费的时间,以微秒为单位。
  • deq_qdepth (bit<19>): 数据包出队时的队列深度。
  • qid (bit<5>):当有多个队列为每个出口端口提供服务时(例如,启用优先级排队时),每个队列都会分配一个固定的唯一 ID,该 ID 将写入该字段。qid当前不是v1modelstandard_metadata_t中类型的一部分。也许应该添加?

内在元数据

特定于体系结构的元数据字段,内部元数据是standard_metadata_t类型结构体的一部分. 这些字段中的大多数,对于位宽没有严格的要求,但我们建议您遵循我们的以下建议。其中一些固有元数据字段可以直接访问(读和/或写),其他的只能通过原语操作访问。

  • ingress_global_timestamp (bit<48>):一个时间戳,以微秒为单位,当数据包出现在入口时设置。每次交换机启动时,时钟都设置为 0。该字段可以直接从入口和出口读取,不应写入。
  • egress_global_timestamp (bit<48>):一个时间戳,以微秒为单位,在数据包出现出口处理时设置。该字段只能从出口管道中读取,不应写入。
  • mcast_grp (bit<16>): 希望多播数据包时,在入口管道中写入此字段。值为 0 表示没有多播。此值必须是通过 BMv2 运行时接口配置的有效多播组之一。
  • egress_rid (bit<16>): 在出口管道只读。唯一标识同一个入口数据包的多播副本。
  • checksum_error (bit<1>): 只读。verify_checksum调用后为 1则错误。调用verify_checksum应该在v1model的VerifyChecksum控件中,它在解析器之后和入口之前执行。
  • priority (bit<3>): 优先级队列中的数据包优先级。可能的优先级介于 0(最低优先级)和 7(最高优先级)之间。

Externs

v1model.p4架构文件描述中定义的外部类型、函数和对象。

  • counter(bit<32> size, CounterType type):计数器数组。
    • void count(in bit<32> index): 将索引为index的计数器增加1或数据包中的字节数。
  • direct_counter(CounterType type):直接计数器,用表引用它。每次匹配表时,匹配项条目位置的计数器都会增加1或者数据包字节数。
    • void count():在给定引用表的匹配操作期间自动调用。
  • meter(bit<32> size, MeterType type):间接meter仪表数组,跟踪数据包或字节速率。
    • void execute_meter(in bit<32> index, out T result):给定index并返回meter的颜色状态。
  • direct_meter(MeterType Type):直接仪表,用表引用。每次匹配表时,匹配项的条目位置的计量器就会增加1或者数据包字节数。
    • void read(out T result):返回最后执行的条目的颜色。
  • register(bit size):数组或寄存器,大小为size,元素位数为T(例如bit<8>)。
    • void read(out T result, in bit<32> index): 读取index元素的内容。将输出(位数一致)存储在变量 result 中。
    • void write(in bit<32> index, in T value)value: 在index元素写入T(位数一致)。
  • void random(out T result, in T lo, in T hi): 在lo和hi之间生成一个随机值并将其存储在result,三个变量位数相同。
  • void digest(in bit<32> receiver, in T data): 摘要小块信息并将它们发送到控制器。用于发送摘要消息的通道取决于交换机架构。在Simple Switch中,摘要是使用nanomsg套接字库实现的。与simple_switch一起使用时,可以将接收方字段始终设置为1。摘要是变量、标头或元数据的一个结构体。
  • void mark_to_drop(inout standard_metadata_t standard_metadata):将standard_metadata.egress_spec设置为一个特殊值指示丢弃数据包。如果程序在离开ingress或egress管道之前更改egress_spec数据包将不会被丢弃。
  • void hash<O, T, D, M>(out O result, in HashAlgorithm algo, in T base, in D data, in M max): 对data执行algo哈希算法并将输出存储在result. 输出值将介于base和max之间。v1model.p4描述了不同的算法。
  • void verify_checksum<T, O>(in bool condition, in T data, in O checksum, HashAlgorithm algo): condition为时对data结构体执行algo哈希算法并将值与checksum进行比较。然后将输出存储在standard_metadata.checksum_error(0 有效,1 无效)中。
  • void update_checksum<T, O>(in bool condition, in T data, inout O checksum, HashAlgorithm algo): condition为真,则使用algo算法对data结构体进行哈希处理并存储在您选择的checksum字段中。比如ipv4.checksum字段。
  • void verify_checksum_with_payload<T, O>(in bool condition, in T data, in O checksum, HashAlgorithm algo): 与verify_checksum相同,输入是data和有效载荷。
  • void resubmit(in T data): 将(未修改的)original数据包重新提交给parser。只能在入口处调用。在入口结束时,original数据包将再次提交给解析器,data中的所有字段将被保留入口结束时的值。对一个数据包执行多个重新提交操作,仅使用最后一次重新提交操作的字段列表,仅重新提交一个数据包。
  • void recirculate(in T data):将修改后的数据包重新循环到ingress。只能在出口处调用。在deparser后重新循环数据包,对数据包所做的所有更改都将保留在重新循环的数据包中。可以使用data参数保留一些元数据字段。
  • void clone(in CloneType type, in bit<32> session):创建数据包克隆,后文详细介绍。
  • void clone3(in CloneType type, in bit<32> session, in T data):将一些元数据字段复制到克隆的数据包。
  • void truncate(in bit<32> length):截断数据包,只保留length指定的字节数。可以在入口或出口处执行,但只会在deparser生效。

高级功能示例

创建组播组

为了使用Simple Switch的数据包复制引擎,需要在 P4 程序和使用运行时接口 ( SimpleSwitchThriftAPI, SimpleSwitchP4RuntimeAPI) 或simple_switch_CLI完成几件事.

SimpleSwitchP4RuntimeAPI无法控制simple_switch

首先,您需要创建多播组、多播节点并将它们关联到端口和组。这可以使用P4-Utils提供的API 或 simple_switch_CLI 来完成:

  1. 创建多播组
1
mc_mgrp_create <id>
  1. 创建具有复制 ID ( rid) 的多播节点
1
2
3
mc_node_create <rid> <port_number>
# 创建具有多个端口的节点
mc_node_create 0 1 2 3

每个多播节点对应一个或多个物理port,带有一个rid信息,并返回一个handle_id。默认情况下,创建的第一个节点handle_id返回 0,下一个节点返回 1,依此类推。只需要记住我们添加它们的顺序。rid和handle_id是不一样的。rid 只是标识符,可以设置相同的rid,rid 将附加到使用此mc_node进行多播的每个数据包。可以在出口处通过读取standard_metadata.egress_rid找到该值。

  1. 关联节点多播组
1
mc_node_associate <mcast_grp_id> <node_handle_id>
1
2
3
mc_mgrp_create 1
mc_node_create 0 1 2 3
mc_node_associate 1 0

在入口管道中给standard_metadata.mcast_grp写入要用于多播的多播组 ID 的值。示例中要将数据包发送到端口 1、2 和 3,需要设置standard_metadata.mcast_grp = 1.

克隆数据包

克隆用于创建数据包副本并将它们发送到其他地方。可用于监控、将数据发送到控制平面等。

simple Switch提供了两个可用于克隆数据包的extern函数:

  • clone(in CloneType type, in bit<32> session)
  • clone3(in CloneType type, in bit<32> session, in T data)
  1. 第一个参数是类型,CloneType.I2E将orignal数据包的副本发送到出口管道,CloneType.E2E将出口数据包的副本发送到缓冲区机制。
  2. 第二个参数是镜像 id或session id。指定克隆端口。需要使用控制平面或客户端API配置此映射
1
mirroring_add <session> <output_port>
  1. 使用clone3时,可以复制一些元数据值,以便克隆的数据包能够访问它们。

使用该standard_metadata.instance_type字段区分普通数据包和克隆数据包。

数据包摘要

digest外部函数将一些小信息(摘要)发送到控制器的方法。摘要数据包是和原始数据包一起发送的,因此不需要克隆任何东西。Simple Switch摘要是使用套接字库Nanomsg实现的。digest必须从入口管道调用。

假设我们在 p4 代码中定义了这个元数据结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct digest_data_t {

    bit<8> a;
    bit<8> b;

}

struct metadata {
    /* empty */
    digest_data_t digest_data;
}

然后我们可以在入口管道中调用摘要:

1
digest(1, meta.digest_data); //assume that metadata is called meta in the ingress parameters

digest 的第一个参数总是1.

交换机添加了一些需要解析的控制标头,对于每个摘要数据包,交换机都需要一个确认消息(用于过滤重复项)。

使用严格的优先级队列

Simple Switch允许每个输出端口使用多个队列。但是,为了使用它们,需要做一些小的修改。

  1. 在bmv2/targets/simple_switch/simple_switch.h文件中取消注释#define SSWITCH_PRIORITY_QUEUEING_ON。
  2. 将这两个元数据字段添加到v1model.p4文件中:
1
2
3
//Priority queueing
@alias("queueing_metadata.qid")           bit<5>  qid;
@alias("intrinsic_metadata.priority")     bit<3> priority;
  1. 将修改后的v1model.p4文件复制到/usr/local/share/p4c/p4include/:
1
cp v1model.p4 /usr/local/share/p4c/p4include/
  1. 重新编译 BMv2,以便添加多个队列。

默认情况下,有 8 个严格优先级队列,0 为最高优先级,7 为最低优先级。较高优先级队列中的数据包将始终比较低优先级队列中的数据包先传输。将standard_metadata.priority字段设置为0-7来选择队列。

入口和出口管道

入口管道

在本节中,我们将展示在执行了来自入口控制的所有逻辑之后数据包会发生什么。

  1. 如果clone或clone3被调用,数据包将使用镜像 ID 克隆到您指定的egress_port位置。这会将入口数据包复制到出口管道,而无需所有入口控制修改。如果是clone3,数据包还将保留指定的元数据字段。最后将standard_metadata.instance_type修改为相应的值。
  2. 如果有digest的调用,交换机将向控制器发送带有指定字段的控制平面消息。
  3. 1和2可以并行执行。一些相互排斥的动作,如果一个发生,另一个就不会发生。同时为真交换机仅执行顺序靠前的动作:
  • resubmit
  • 多播
  • 丢弃
  • 单播

出口管道

在本节中,我们将展示在执行了出口控制的所有逻辑后数据包会发生什么。

  1. 如果在出口管道中调用clone或clone3被调用,数据包将使用镜像 ID 克隆到您指定的egress_port位置(有关更多信息,请参阅克隆部分)。这会将出口数据包的副本和被clone3指定的元数据发送到出口控制块。
  2. 一些相互排斥的动作,如果一个发生,另一个就不会发生。同时为真交换机仅执行顺序靠前的动作:
  • 丢弃
  • Recirculate
  • Send Packet Out

控制平面

控制交换机

|目标target|控制平面| |:=:|:=:| |simple_switch|Thrift| |simple_switch_grpc|P4Runtime,Thrift|

|控制平面|方法| |:=:|:=:| |Thrift|SimpleSwitchThriftAPI,simple_switch_CLI| |P4Runtime|SimpleSwitchP4RuntimeAPI|

simple switch CLI

运行客户端

simple_switch_CLI --thrift-port <port>

客户端连接到在每个交换机进程中运行的Thrift服务器。9090是默认端口号。一个客户端实例只能连接到一台交换机设备。

填表

  • table_set_default <table_name> <action_name> <action_parameters>用于设置表的默认动作(即没有找到匹配时执行的动作)。
  • table_add <table_name> <action_name> <match_fields> => <action_parameters>用于设置与表中特定匹配项相关的操作。

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
action drop(){
    // drops packet
    mark_to_drop(standard_metadata);
}

action action_name(bit<8> action_parameter){
    ...
}

table table_name {
    key = {
        standard_metadata.ingress_port: exact;
    }
    actions = {
        drop;
        action_name;
    }
}
1
2
table_set_default table_name drop
table_add table_name action_name 1 => 5

将客户端输入写入文件

直接在文本文件中写入命令,然后提供给客户端:

1
simple_switch_CLI --thrift-port <port> < command_file.txt

使用P4-Utils配置文件

在p4app.json中为每个交换机设置一个配置文件。

1
2
3
4
5
    "switches": {
      "s1": {
        "cli_input": "<path_to_cli_commands_file>"
      }
    }

您可以p4app.json在官方P4-Utils文档中找到有关的所有文档。

Thrift API

使用python与交换机内置的Thrift服务器通信以执行命令。在此处阅读更多有关信息。

P4Runtime API

P4Runtime是一种控制平面规范,直接与交换机通信。这样控制平面程序也与目标无关。

P4-Utils引入了P4Runtime API。包名为SimpleSwitchP4RuntimeAPI并,提供与SimpleSwitchThriftAPI相似的方法。在此处阅读有关此 API的更多信息。

调试和故障排除

监控流量

在调试 P4 程序时,Sniffing traffic嗅探流量是一个非常强大的工具。仅通过观察流量就可以轻松实现验证流量是否穿过特定路径,或者标头字段是否符合预期等基本操作。为此,可以使用多种工具。

.pcap文件

simple_switch的--pcap=<output_dir>选项将通过其接口的所有流量保存在一个.pcap文件中:

1
sudo simple_switch -i 0@<iface0> -i 1@<iface1> --pcap=<output_dir> <path to JSON file>

数据包日志记录文件命名规则:<sw_name>-<intf_num>_<in|out>.pcap

在p4app.json配置文件或网络配置脚本中启用.pcap文件,交换机将使用该–pcap选项启动并用./pcap作输出目录。

Wireshark和TShark

另一种选择是在流量流动时观察流量。可以使用TShark及其 GUI 版本Wireshark等工具

1
sudo tshark -i <interface_name>

Tcpdump

同样,可以使用tcpdump 捕获流量,显示链路层信息并且不解析地址:

1
sudo tcpdump -l -enn -i <interface_name>

Logging记录

要启用日志记录,请确保在编译bmv2之前配置启用--with-nanomsg标志。

控制台记录

要在启动交换机时启用控制台日志记录,请使用--log-console命令行选项。例如:

1
sudo simple_switch -i 0@<iface0> -i 1@<iface1> --log-console <path to JSON file>

将打印终端中的所有消息

还可以将其重定向到日志文件

1
sudo simple_switch -i 0@<iface0> -i 1@<iface1> --log-console <path to JSON file> >/path_to_file/sw.log

在p4app.json配置文件中启用日志记录,交换机会自动将所有控制台日志记录写入./log目录下的一个文件中,后缀为<sw_name>.log

客户端记录

.p4app启用了日志记录,并且在启动拓扑时使用了simple_switch_CLI,则cli输出也将记录在日志文件夹中的<sw_name>_cli_output.log文件

事件记录

使用--nanolog命令行选项在启动交换机时启用事件日志记录。例如,要使用ipc地址ipc:///tmp/bm-log.ipc:

1
sudo ./simple_switch -i 0@<iface0> -i 1@<iface1> --nanolog ipc:///tmp/bm-log.ipc <path to JSON file>

在交换机运行时使用tools/nanomsg_client.py如下:

1
sudo ./nanomsg_client.py [--thrift-port <port>]

该脚本将为每个数据包显示重要事件(表命中/未命中、解析器转换)。

使用P4-Utils创建拓扑时,每个交换机都会自动分配一个Thrift端口。查看p4run显示的print消息找到交换机和端口之间的映射:

1
s1 -> Thrift port: 9090

调试器

要启用调试器,编译bmv2前配置--enable-debugger. 启动交换机时,使用命令行标志--debugger

在交换机运行时使用tools/p4dbg.py将调试器附加到交换机:

1
sudo ./p4dbg.py [--thrift-port <port>]

您可以在BMv2 文档中找到 P4 调试器用户指南。

将信息附加到数据包

可以通过仅根据执行代码的哪一部分修改标头字段来了解代码的作用,并在数据包离开交换机时检查该值。当然,可以做一些更复杂的事情,使用多个字段,读取寄存器的值并将其保存在标头中,等等。

使用 P4 表检查标题/元数据值

使用 P4 表并exact匹配您要跟踪的所有字段。每次执行表时,如果启用了 BMv2 调试,交换机会将用于匹配表条目的每个字段的值写入交换机日志文件中。有关详细信息,请参阅示例

如果以上没有解决你的问题

  1. 首先,检查P4-16 规范对此的说明。
  2. 检查p4-org邮件列表。可能您不是第一个遇到此问题的人,并且已经有人在邮件列表中询问您需要什么。谷歌搜索:site:http://lists.p4.org/
  3. 检查BMv2和P4C存储库的GitHub issue部分。
  4. 如果你在任何地方都找不到解决方案,你可以自己写邮件,或者在 GitHub 中打开一个问题。

Scapy

正如官网所写,Scapy是一个强大的交互式数据包操作程序。它能够伪造或解码大量协议的数据包、在线发送它们、捕获它们、匹配请求和回复等等。它可以轻松处理大多数经典任务,如扫描、跟踪路由、探测、单元测试、攻击或网络发现。

链接

 |