P4lecture总结
basic
运用
make run
编译basic.p4
在mininet中启动pod-topo,
尝试在拓扑中的主机之间执行 ping 操作:
mininet> h1 ping h2 mininet> pingall
stop:
mak stop
删除文件和日志
make clean
有关控制平面的说明
P4 程序定义数据包处理管道,但每个表中的规则由控制平面插入。当规则与数据包匹配时,将使用控制平面提供的参数作为规则的一部分调用其操作。
重要
我们使用 P4Runtime 来安装控制平面规则。文件的内容是指表、键和操作的特定名称,如编译器生成的 P4Info 文件中所定义(执行后查找该文件)。P4 程序中添加或重命名表、键或操作的任何更改都需要反映在这些文件中
代办
一个操作(称为):ipv4_forward
· 设置下一跃点的出口端口。
· 使用下一跃点的地址更新以太网目标地址。
· 使用交换机的地址更新以太网源地址。
· 递减 TTL。
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = drop();
}
apply {
if (hdr.ipv4.isValid()) {
basic_tunnel
任务:定义新的表头类型来封装IP数据包并修改交换机代码,以便它使用新的隧道标头来决定目标端口
新的标头类型将包含一个协议 ID,该协议 ID 指示要封装的数据包的类型,以及用于路由的目标 ID。
工作:
1)添加了一个新的标头类型,称为包含两个 16 位字段:myTunnel_tproto_iddst_id
header myTunnel_t {
bit<16> proto_id;
bit<16> dst_id;
}
2)标头已添加到结构myTunnel_theaders中
struct headers {
ethernet_t ethernet;
myTunnel_t myTunnel;
ipv4_t ipv4;
}
3)待办事项:更新解析器以根据以太网标头中的字段提取标头。
(myTunnel ipv4etherType 0x1212 ipv4 myTunnel proto_id TYPE_IPV4)
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_MYTUNNEL: parse_myTunnel;
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_myTunnel {
packet.extract(hdr.myTunnel);
transition select(hdr.myTunnel.proto_id) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
4)定义一个调用的新操作,该操作仅设置出口端口(即 字段)到控制平面提供的端口号。
5)定义一个名为"在标头的字段上显示完全匹配"的新表。如果表中存在匹配项,则此表应调用该操作,否则应调用该操作
6)如果标头有效,请更新控制块中的语句以应用新定义的表
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = drop();
}
action myTunnel_forward(egressSpec_t port) {
standard_metadata.egress_spec = port;
}
table myTunnel_exact {
key = {
hdr.myTunnel.dst_id: exact;
}
actions = {
myTunnel_forward;
drop;
}
size = 1024;
default_action = drop();
}
apply {
if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) {
// Process only non-tunneled IPv4 packets
ipv4_lpm.apply();
}
if (hdr.myTunnel.isValid()) {
// process tunneled packets
myTunnel_exact.apply();
}
}
}
7)更新 deparser 以发出 ,然后发出标头。如果参数有效,则只会发出标头。标头的隐式有效性位由解析器在提取时设置。因此,无需在此处检查标头有效性。
8)为新定义的表添加静态规则,以便交换机能够针对 的每个可能值正确转发
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.myTunnel);
packet.emit(hdr.ipv4);
}
}
calc
任务:用P4编写的自定义协议表头实现基本计算器,标头将包含要执行的操作和两个操作数,当交换机收到计算器数据包标头时,它将对操作数执行操作,并将结果返回给发送方。
测试计算器:
mininet> h1 python calc.py
>
驱动程序将提供新的提示,您可以在其中键入基本表达式。测试工具将分析表达式,并使用相应的运算符和操作数准备数据包。然后,它将向交换机发送数据包以进行评估。当开关返回计算结果时,测试程序将打印结果。
to do:
要实现计算器,您需要定义自定义计算器标头,并实现开关逻辑以解析标头,执行请求的操作,在标头中写入结果,然后将数据包返回给发送方
使用以下格式

P 是 ASCII 字母"P"(0x50)
4 是 ASCII 字母"4"(0x34)
版本当前为 0.1 (0x01)
操作是要执行的操作:
'+' (0x2b) 结果 = 操作数 A + 操作数 B
'-' (0x2d) 结果 = 操作数 A - 操作数 B
'&' (0x26) Result = OperandA & OperandB
'|'(0x7c) 结果 = 操作数A |操作数 B
'^' (0x5e) Result = OperandA ^ OperandB
假设计算器标头通过以太网传输,并且我们将使用以太网类型0x1234来指示标头的存在
const bit<16> P4CALC_ETYPE = 0x1234;
const bit<8> P4CALC_P = 0x50; // 'P'
const bit<8> P4CALC_4 = 0x34; // '4'
const bit<8> P4CALC_VER = 0x01; // v0.1
const bit<8> P4CALC_PLUS = 0x2b; // '+'
const bit<8> P4CALC_MINUS = 0x2d; // '-'
const bit<8> P4CALC_AND = 0x26; // '&'
const bit<8> P4CALC_OR = 0x7c; // '|'
const bit<8> P4CALC_CARET = 0x5e; // '^'
header p4calc_t {
bit<8> p;
bit<8> four;
bit<8> ver;
bit<8> op;
bit<32> operand_a;
bit<32> operand_b;
bit<32> res;
}
/*
* All headers, used in the program needs to be assembed into a single struct.
* We only need to declare the type, but there is no need to instantiate it,
* because it is done "by the architecture", i.e. outside of P4 functions
*/
struct headers {
ethernet_t ethernet;
p4calc_t p4calc;
}
/*
* All metadata, globally used in the program, also needs to be assembed
* into a single struct. As in the case of the headers, we only need to
* declare the type, but there is no need to instantiate it,
* because it is done "by the architecture", i.e. outside of P4 functions
*/
struct metadata {
/* In our case it is empty */
}
Parser
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
P4CALC_ETYPE : check_p4calc;
default : accept;
}
}
state check_p4calc {
transition select(packet.lookahead<p4calc_t>().p,
packet.lookahead<p4calc_t>().four,
packet.lookahead<p4calc_t>().ver) {
(P4CALC_P, P4CALC_4, P4CALC_VER) : parse_p4calc;
default : accept;
}
}
state parse_p4calc {
packet.extract(hdr.p4calc);
transition accept;
}
}
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action send_back(bit<32> result) {
bit<48> tmp;
/* Put the result back in */
hdr.p4calc.res = result;
/* Swap the MAC addresses */
tmp = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = hdr.ethernet.srcAddr;
hdr.ethernet.srcAddr = tmp;
/* Send the packet back to the port it came from */
standard_metadata.egress_spec = standard_metadata.ingress_port;
}
action operation_add() {
send_back(hdr.p4calc.operand_a + hdr.p4calc.operand_b);
}
action operation_sub() {
send_back(hdr.p4calc.operand_a - hdr.p4calc.operand_b);
}
action operation_and() {
send_back(hdr.p4calc.operand_a & hdr.p4calc.operand_b);
}
action operation_or() {
send_back(hdr.p4calc.operand_a | hdr.p4calc.operand_b);
}
action operation_xor() {
send_back(hdr.p4calc.operand_a ^ hdr.p4calc.operand_b);
}
action operation_drop() {
mark_to_drop(standard_metadata);
}
table calculate {
key = {
hdr.p4calc.op : exact;
}
actions = {
operation_add;
operation_sub;
operation_and;
operation_or;
operation_xor;
operation_drop;
}
const default_action = operation_drop();
const entries = {
P4CALC_PLUS : operation_add();
P4CALC_MINUS: operation_sub();
P4CALC_AND : operation_and();
P4CALC_OR : operation_or();
P4CALC_CARET: operation_xor();
}
}
apply {
if (hdr.p4calc.isValid()) {
calculate.apply();
} else {
operation_drop();
}
}
}
ecn
目标:通过显式拥塞通知(ECN)的实现来扩展基本的L3转发
ECN允许在不丢弃数据包的情况下端到端地通知网络拥塞。如果最终主机支持 ECN,则会在字段中放置值 1 或 2。对于此类数据包,如果队列大小大于阈值,则每个交换机可能会将值更改为 3。接收方将值复制到发送方,发送方可以降低速率。
mininet> xterm h1 h11 h2 h22
In 's XTerm, start the server that captures packets: h2
./receive.py
start the iperf UDP server:h2
iperf -s -u
在 的 XTerm 中,启动 iperf 客户端发送 15 秒h11
iperf -c 10.0.2.22 -t 15 -u
将TOS字段拆分为DiffServ和ECN字段来更改ipv4_t标头,同时需要更改校验和块
在出口控制块中,我们必须将队列长度与ECN_THRESHOLD进行比较。如果队列长度大于阈值,则将设置 ECN 标志。请注意,仅当最终主机通过将原始 ECN 设置为 1 或 2 来声明支持 ECN 时,才应使用此逻辑。
const bit<8> TCP_PROTOCOL = 0x06;
const bit<16> TYPE_IPV4 = 0x800;
const bit<19> ECN_THRESHOLD = 10;
bit<6> diffserv;
bit<2> ecn;
主要是在egress中增加一个action以设置hdr.ipv4.ecn的值
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action mark_ecn() {
hdr.ipv4.ecn = 3;
}
apply {
if (hdr.ipv4.ecn == 1 || hdr.ipv4.ecn == 2){
if (standard_metadata.enq_qdepth >= ECN_THRESHOLD){
mark_ecn();
}
}
}
}
firewall
这部分内容还是不太理解
目标:实现简单状态的防火墙程序
交换机 s1 将配置一个实现简单状态防火墙的 P4 程序
s1 上的防火墙应具有以下功能:
主机 h1 和 h2 位于内部网络上,并且始终可以相互连接。
主机 h1 和 h2 可以自由连接到外部网络上的 h3 和 h4。
主机 h3 和 h4 只能在从 h1 或 h2 建立连接后才应答连接,但无法启动与内部网络上的主机的新连接。
注意:此有状态防火墙使用简单的布隆过滤器在数据平面中 100% 实现。因此,存在一些哈希冲突的可能性,这些冲突会让不需要的流通过。
拓扑结构:

交换机 s1 将配置一个实现简单状态防火墙的 P4 程序
s1 上的防火墙应具有以下功能:
1)主机 h1 和 h2 位于内部网络上,并且始终可以相互连接。
2)主机 h1 和 h2 可以自由连接到外部网络上的 h3 和 h4。
3)主机 h3 和 h4 只能在从 h1 或 h2 建立连接后才应答连接,但无法启动与内部网络上的主机的新连接
注意:此状态防火墙使用简单的布隆过滤器在数据平面中 100% 实现。因此,存在一些哈希冲突的可能性,这些冲突会让不需要的流通过。
从外部主机到内部网络内部主机的 TCP 流应该不起作用
mininet> iperf h3 h1
我们将使用具有两个哈希函数的绽放过滤器来检查进入内部网络的数据包是否是已建立的TCP连接的一部分。我们将为绽放过滤器使用两个不同的寄存器数组,每个数组都由哈希函数更新。使用不同的寄存器阵列使我们的设计适合高速P4目标,这些目标通常只允许每个数据包访问一个寄存器阵列。
任务:
完整版将包含以下组件:firewall.p4
1)以太网 ()、IPv4 () 和 TCP () 的标头类型定义。ethernet_tipv4_ttcp_t
header tcp_t{
bit<16> srcPort;
bit<16> dstPort;
bit<32> seqNo;
bit<32> ackNo;
bit<4> dataOffset;
bit<4> res;
bit<1> cwr;
bit<1> ece;
bit<1> urg;
bit<1> ack;
bit<1> psh;
bit<1> rst;
bit<1> syn;
bit<1> fin;
bit<16> window;
bit<16> checksum;
bit<16> urgentPtr;
}
2)用于填充和字段的以太网、IPv4 和 TCP 的解析器。ethernet_tipv4_ttcp_t
3)使用 丢弃数据包的操作。mark_to_drop()
4)一个操作(称为 ),用于使用哈希算法和 计算 bloom 筛选器的两个哈希值。哈希将在由 IPv4 源地址和目标地址、源和目标端口号以及 IPv4 协议类型组成的数据包 5 元组上计算。compute_hashescrc16crc32
5)将执行基本 IPv4 转发的操作 () 和表 ()(从 中采用)。ipv4_forwardipv4_lpmbasic.p4
6)一个操作(称为 ),它将根据操作的参数简单地设置一位方向变量。set_direction
7)一个表(称为 ),它将读取数据包的入口和出口端口(在 IPv4 转发之后)并调用 。如果数据包传入内部网络,则方向将设置为 。。否则,方向将设置为 。为此,该文件包含表的相应控制平面条目。check_portsset_direction10pod-topo/s1-runtime.jsoncheck_ports
8)一个控件,它将:
如果数据包具有有效的 IPv4 标头,请首先应用该表。ipv4_lpm
然后,如果 TCP 标头有效,请应用该表来确定方向。check_ports
应用该操作来计算两个哈希值,这两个哈希值是 bloom 筛选器 ( 和 ) 的两个寄存器数组中的位位置。当方向为(即数据包传入内部网络)时,将通过交换源和目标IPv4地址以及源和目标端口来调用。这是为了在最初从内部网络建立 TCP 连接时检查绽放筛选器的设置位。compute_hashesreg_pos_onereg_pos_two1compute_hashes
待办事项:如果 TCP 数据包从内部网络传出并且是 SYN 数据包,请在计算位位置 ( 和 ) 处设置布隆过滤器数组。否则,如果 TCP 数据包正在进入内部网络,请在计算位位置读取两个绽放过滤器数组,如果未设置任何一个,则丢弃数据包。reg_pos_onereg_pos_two
一种以正确顺序发出以太网、IPv4 和 TCP 标头的分离器。
随分析器、控件和分离器提供的实例化。package
通常,包还需要校验和验证和重新计算控件的实例。这些对于本教程不是必需的,并且将替换为空控件的实例化。
#define BLOOM_FILTER_ENTRIES 4096
#define BLOOM_FILTER_BIT_WIDTH 1
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition select(hdr.ipv4.protocol){
TYPE_TCP: tcp;
default: accept;
}
}
state tcp {
packet.extract(hdr.tcp);
transition accept;
}
}
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
register<bit<BLOOM_FILTER_BIT_WIDTH>>(BLOOM_FILTER_ENTRIES) bloom_filter_1;
register<bit<BLOOM_FILTER_BIT_WIDTH>>(BLOOM_FILTER_ENTRIES) bloom_filter_2;
bit<32> reg_pos_one; bit<32> reg_pos_two;
bit<1> reg_val_one; bit<1> reg_val_two;
bit<1> direction;
action drop() {
mark_to_drop(standard_metadata);
}
action compute_hashes(ip4Addr_t ipAddr1, ip4Addr_t ipAddr2, bit<16> port1, bit<16> port2){
//Get register position
hash(reg_pos_one, HashAlgorithm.crc16, (bit<32>)0, {ipAddr1,
ipAddr2,
port1,
port2,
hdr.ipv4.protocol},
(bit<32>)BLOOM_FILTER_ENTRIES);
hash(reg_pos_two, HashAlgorithm.crc32, (bit<32>)0, {ipAddr1,
ipAddr2,
port1,
port2,
hdr.ipv4.protocol},
(bit<32>)BLOOM_FILTER_ENTRIES);
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = drop();
}
action set_direction(bit<1> dir) {
direction = dir;
}
table check_ports {
key = {
standard_metadata.ingress_port: exact;
standard_metadata.egress_spec: exact;
}
actions = {
set_direction;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
if (hdr.ipv4.isValid()){
ipv4_lpm.apply();
if (hdr.tcp.isValid()){
direction = 0; // default
if (check_ports.apply().hit) {
// test and set the bloom filter
if (direction == 0) {
compute_hashes(hdr.ipv4.srcAddr, hdr.ipv4.dstAddr, hdr.tcp.srcPort, hdr.tcp.dstPort);
}
else {
compute_hashes(hdr.ipv4.dstAddr, hdr.ipv4.srcAddr, hdr.tcp.dstPort, hdr.tcp.srcPort);
}
// Packet comes from internal network
if (direction == 0){
// If there is a syn we update the bloom filter and add the entry
if (hdr.tcp.syn == 1){
bloom_filter_1.write(reg_pos_one, 1);
bloom_filter_2.write(reg_pos_two, 1);
}
}
// Packet comes from outside
else if (direction == 1){
// Read bloom filter cells to check if there are 1's
bloom_filter_1.read(reg_val_one, reg_pos_one);
bloom_filter_2.read(reg_val_two, reg_pos_two);
// only allow flow to pass if both entries are set
if (reg_val_one != 1 || reg_val_two != 1){
drop();
}
}
}
}
}
}
}
link_monitor
目标:编写一个 P4 程序,使主机能够监视网络中所有链路的利用率,将修改基本的P4程序以处理源路由探测数据包,以便它能够在每个跃点获取出口链路利用率并将其传到主机以进行监视
我们探测数据包包含以下三种标头类型
// Top-level probe header, indicates how many hops this probe
// packet has traversed so far.
header probe_t {
bit<8> hop_cnt;
}
// The data added to the probe by each switch at each hop.
header probe_data_t {
bit<1> bos;
bit<7> swid;
bit<8> port;
bit<32> byte_cnt;
time_t last_time;
time_t cur_time;
}
// Indicates the egress port the switch should send this probe
// packet out of. There is one of these headers for each hop.
//指示交换机应发送此探测数据包的出口端口。
header probe_fwd_t {
bit<8> egress_spec;
}
为了监控利用率,我们的交换机将维护两个寄存器阵列
byte_cnt_reg:计算自上次探测数据包从端口传出以来从每个端口传输出的字节数
last_time_reg: 存储上次从每个端口传输探测数据包的时间
对于解析器部分:如果这是第一个跃点,则数据包中将没有任何跃点,因此我们跳过该状态并直接转换该状态。在状态下,我们使用该字段来确定要用于执行转发的标头字段,并将该端口值保存到元数据字段中,该字段随后用于执行转发
ingress部分:
入口控制块看起来与练习非常相似。唯一的区别是,该块包含另一个条件,用于使用解析器提取的字段转发探测数据包。它还会递增字段。
egress部分:
它使用寄存器来计算自上次探测数据包通过该端口以来通过每个端口的字节数。byte_cnt_reg
将一个新的标头添加到数据包中,并填写(堆栈底部)字段以及(交换机ID)字段probe_databosswid
任务:填写其余探测数据包字段,以确保可以正确测量链路利用率
您现在应该看到一个 Mininet 命令提示符。打开两个终端:h1
编完后:
mininet> xterm h1 h1
在其中一个 xterms 中,运行脚本以开始每秒发送探测数据包。这些探测数据包中的每一个都采用链路监视器 topo.png 中指示的路径。send.py
./send.py
在另一个终端中,运行脚本以开始接收和分析探测数据包。这使我们能够监控网络内的链路利用率。receive.py
./receive.py
报告的链路利用率和交换机端口号将始终为 0,因为尚未填写探测字段。
在 h1 和 h4 之间运行 iperf 流:
mininet> iperf h1 h4
const bit<16> TYPE_IPV4 = 0x800;
const bit<16> TYPE_PROBE = 0x812;
#define MAX_HOPS 10
#define MAX_PORTS 8
新增header部分
// Top-level probe header, indicates how many hops this probe
// packet has traversed so far.
header probe_t {
bit<8> hop_cnt;
}
// The data added to the probe by each switch at each hop.
header probe_data_t {
bit<1> bos;
bit<7> swid;
bit<8> port;
bit<32> byte_cnt;
time_t last_time;
time_t cur_time;
}
// Indicates the egress port the switch should send this probe
// packet out of. There is one of these headers for each hop.
header probe_fwd_t {
bit<8> egress_spec;
}
struct parser_metadata_t {
bit<8> remaining;
}
struct metadata {
bit<8> egress_spec;
parser_metadata_t parser_metadata;
}
struct headers {
ethernet_t ethernet;
ipv4_t ipv4;
probe_t probe;
probe_data_t[MAX_HOPS] probe_data;
probe_fwd_t[MAX_HOPS] probe_fwd;
}
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
TYPE_PROBE: parse_probe;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
state parse_probe {
packet.extract(hdr.probe);
meta.parser_metadata.remaining = hdr.probe.hop_cnt + 1;
transition select(hdr.probe.hop_cnt) {
0: parse_probe_fwd;
default: parse_probe_data;
}
}
state parse_probe_data {
packet.extract(hdr.probe_data.next);
transition select(hdr.probe_data.last.bos) {
1: parse_probe_fwd;
default: parse_probe_data;
}
}
state parse_probe_fwd {
packet.extract(hdr.probe_fwd.next);
meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1;
// extract the forwarding data
meta.egress_spec = hdr.probe_fwd.last.egress_spec;
transition select(meta.parser_metadata.remaining) {
0: accept;
default: parse_probe_fwd;
}
}
}
apply {
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
else if (hdr.probe.isValid()) {
standard_metadata.egress_spec = (bit<9>)meta.egress_spec;
hdr.probe.hop_cnt = hdr.probe.hop_cnt + 1;
}
}
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
// count the number of bytes seen since the last probe
register<bit<32>>(MAX_PORTS) byte_cnt_reg;
// remember the time of the last probe
register<time_t>(MAX_PORTS) last_time_reg;
action set_swid(bit<7> swid) {
hdr.probe_data[0].swid = swid;
}
table swid {
actions = {
set_swid;
NoAction;
}
default_action = NoAction();
}
apply {
bit<32> byte_cnt;
bit<32> new_byte_cnt;
time_t last_time;
time_t cur_time = standard_metadata.egress_global_timestamp;
// increment byte cnt for this packet's port
byte_cnt_reg.read(byte_cnt, (bit<32>)standard_metadata.egress_port);
byte_cnt = byte_cnt + standard_metadata.packet_length;
// reset the byte count when a probe packet passes through
new_byte_cnt = (hdr.probe.isValid()) ? 0 : byte_cnt;
byte_cnt_reg.write((bit<32>)standard_metadata.egress_port, new_byte_cnt);
if (hdr.probe.isValid()) {
// fill out probe fields
hdr.probe_data.push_front(1);
hdr.probe_data[0].setValid();
if (hdr.probe.hop_cnt == 1) {
hdr.probe_data[0].bos = 1;
}
else {
hdr.probe_data[0].bos = 0;
}
// set switch ID field
swid.apply();
hdr.probe_data[0].port = (bit<8>)standard_metadata.egress_port;
hdr.probe_data[0].byte_cnt = byte_cnt;
// read / update the last_time_reg
last_time_reg.read(last_time, (bit<32>)standard_metadata.egress_port);
last_time_reg.write((bit<32>)standard_metadata.egress_port, cur_time);
hdr.probe_data[0].last_time = last_time;
hdr.probe_data[0].cur_time = cur_time;
}
}
}
load_balance
目标:实现一种基于简单版本的等价多路径转发的负载平衡形式。您将实现的交换机将使用两个表将数据包随机转发到两个目标主机之一。第一个表将使用哈希函数(应用于由源和目标 IP 地址、IP 协议以及源和目标 TCP 端口组成的 5 元组)来选择两个主机之一。第二个表将使用计算的哈希值将数据包转发到所选主机。
header tcp_t {
bit<16> srcPort;
bit<16> dstPort;
bit<32> seqNo;
bit<32> ackNo;
bit<4> dataOffset;
bit<3> res;
bit<3> ecn;
bit<6> ctrl;
bit<16> window;
bit<16> checksum;
bit<16> urgentPtr;
}
struct metadata {
bit<14> ecmp_select;
}
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
0x800: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition select(hdr.ipv4.protocol) {
6: parse_tcp;
default: accept;
}
}
state parse_tcp {
packet.extract(hdr.tcp);
transition accept;
}
}
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
action set_ecmp_select(bit<16> ecmp_base, bit<32> ecmp_count) {
hash(meta.ecmp_select,
HashAlgorithm.crc16,
ecmp_base,
{ hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr,
hdr.ipv4.protocol,
hdr.tcp.srcPort,
hdr.tcp.dstPort },
ecmp_count);
}
action set_nhop(bit<48> nhop_dmac, bit<32> nhop_ipv4, bit<9> port) {
hdr.ethernet.dstAddr = nhop_dmac;
hdr.ipv4.dstAddr = nhop_ipv4;
standard_metadata.egress_spec = port;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ecmp_group {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
drop;
set_ecmp_select;
}
size = 1024;
}
table ecmp_nhop {
key = {
meta.ecmp_select: exact;
}
actions = {
drop;
set_nhop;
}
size = 2;
}
apply {
if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 0) {
ecmp_group.apply();
ecmp_nhop.apply();
}
}
}
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action rewrite_mac(bit<48> smac) {
hdr.ethernet.srcAddr = smac;
}
action drop() {
mark_to_drop(standard_metadata);
}
table send_frame {
key = {
standard_metadata.egress_port: exact;
}
actions = {
rewrite_mac;
drop;
}
size = 256;
}
apply {
send_frame.apply();
}
}
{
"target": "bmv2",
"p4info": "build/load_balance.p4.p4info.txt",
"bmv2_json": "build/load_balance.json",
"table_entries": [
{
"table": "MyIngress.ecmp_group",
"default_action": true,
"action_name": "MyIngress.drop",
"action_params": { }
},
{
"table": "MyIngress.ecmp_group",
"match": {
"hdr.ipv4.dstAddr": ["10.0.0.1", 32]
},
"action_name": "MyIngress.set_ecmp_select",
"action_params": {
"ecmp_base": 0,
"ecmp_count": 2
}
},
{
"table": "MyIngress.ecmp_nhop",
"match": {
"meta.ecmp_select": 0
},
"action_name": "MyIngress.set_nhop",
"action_params": {
"nhop_dmac": "00:00:00:00:01:02",
"nhop_ipv4": "10.0.2.2",
"port" : 2
}
},
{
"table": "MyIngress.ecmp_nhop",
"match": {
"meta.ecmp_select": 1
},
"action_name": "MyIngress.set_nhop",
"action_params": {
"nhop_dmac": "00:00:00:00:01:03",
"nhop_ipv4": "10.0.3.3",
"port" : 3
}
},
{
"table": "MyEgress.send_frame",
"match": {
"standard_metadata.egress_port": 2
},
"action_name": "MyEgress.rewrite_mac",
"action_params": {
"smac": "00:00:00:01:02:00"
}
},
{
"table": "MyEgress.send_frame",
"match": {
"standard_metadata.egress_port": 3
},
"action_name": "MyEgress.rewrite_mac",
"action_params": {
"smac": "00:00:00:01:03:00"
}
}
]
}
mri
目的:使用缩小版的带内网络遥测 (INT) 扩展基本的 L3 转发——多跳路由检查
MRI允许用户跟踪每个数据包通过的队列的路径和长度,因而需要编写一个 P4 程序,该程序将 ID 和队列长度附加到每个数据包的标头堆栈。在目标位置,交换机 ID 序列对应于路径,每个 ID 后跟交换机端口的队列长度。
multicast
目的:
编写一个 P4 程序,用于将数据包多播到一组端口。
收到以太网数据包后,交换机会根据目标 MAC 地址查找输出端口。如果是未命中,交换机将在属于多播组的端口上广播数据包(如果入口端口出现在组中,则该数据包将被丢弃在出口管道中)。
交换机将具有一个表,控制平面将使用静态规则填充该表。每个规则都会将以太网 MAC 地址映射到输出端口
p4runtime
目的:将使用 P4Runtime 将流条目发送到交换机
qos
目的:
目标是使用差异化服务通过服务质量 (QOS) 的实现来扩展基本的 L3 转发。
Diffserv简单且可扩展。它对网络流量进行分类和管理,并在现代 IP 网络上提供 QOS。
打开终端h1,h2
mininet> xterm h1 h2
在xterm中,启动捕获数据包的服务器:h2
./receive.py
在 的 XTerm 中,每秒发送一个数据包到使用 send.py 说 30 秒。要发送 UDP:h1h2
./send.py --p=UDP --des=10.0.2.2 --m="P4 is cool" --dur=30
要发送 TCP:请执行以下操作:
./send.py --p=TCP --des=10.0.2.2 --m="P4 is cool" --dur=30
任务:
首先,我们必须通过将TOS字段拆分为DiffServ和ECN字段来更改ipv4_t标头。请记住相应地更新校验和块。然后,在出口控制块中,我们必须将 IP 标头中的协议与 IP 协议进行比较。根据流量类别和优先级,将设置标志。diffserv
完整版将包含以下组件:qos.p4
以太网 () 和 IPv4 () 的标头类型定义。ethernet_tipv4_t
用于以太网、IPv4、的解析器
使用 丢弃数据包的操作。mark_to_drop()
一个操作(称为 ),它将:ipv4_forward
设置下一跃点的出口端口。
使用下一跃点的地址更新以太网目标地址。
使用交换机的地址更新以太网源地址。
减少TTL。
一个入口控制块,用于检查协议并设置 ipv4.diffserv。
一个 deparser,用于选择将标头插入传出数据包的顺序。
随解析器、控件、校验和验证以及重新计算和分离器一起提供的实例化。package
ip protocol
/* IP protocols */ const bit<8> IP_PROTOCOLS_ICMP = 1; const bit<8> IP_PROTOCOLS_IGMP = 2; const bit<8> IP_PROTOCOLS_IPV4 = 4; const bit<8> IP_PROTOCOLS_TCP = 6; const bit<8> IP_PROTOCOLS_UDP = 17; const bit<8> IP_PROTOCOLS_IPV6 = 41; const bit<8> IP_PROTOCOLS_GRE = 47; const bit<8> IP_PROTOCOLS_IPSEC_ESP = 50; const bit<8> IP_PROTOCOLS_IPSEC_AH = 51; const bit<8> IP_PROTOCOLS_ICMPV6 = 58; const bit<8> IP_PROTOCOLS_EIGRP = 88; const bit<8> IP_PROTOCOLS_OSPF = 89; const bit<8> IP_PROTOCOLS_PIM = 103; const bit<8> IP_PROTOCOLS_VRRP = 112;
``
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
/* Default Forwarding */
action default_forwarding() {
hdr.ipv4.diffserv = 0;
}
/* Expedited Forwarding */
action expedited_forwarding() {
hdr.ipv4.diffserv = 46;
}
/* Voice Admit */
action voice_admit() {
hdr.ipv4.diffserv = 44;
}
/* Assured Forwarding */
/* Class 1 Low drop probability */
action af_11() {
hdr.ipv4.diffserv = 10;
}
/* Class 1 Med drop probability */
action af_12() {
hdr.ipv4.diffserv = 12;
}
/* Class 1 High drop probability */
action af_13() {
hdr.ipv4.diffserv = 14;
}
/* Class 2 Low drop probability */
action af_21() {
hdr.ipv4.diffserv = 18;
}
/* Class 2 Med drop probability */
action af_22() {
hdr.ipv4.diffserv = 20;
}
/* Class 2 High drop probability */
action af_23() {
hdr.ipv4.diffserv = 22;
}
/* Class 3 Low drop probability */
action af_31() {
hdr.ipv4.diffserv = 26;
}
/* Class 3 Med drop probability */
action af_32() {
hdr.ipv4.diffserv = 28;
}
/* Class 3 High drop probability */
action af_33() {
hdr.ipv4.diffserv = 30;
}
/* Class 4 Low drop probability */
action af_41() {
hdr.ipv4.diffserv = 34;
}
/* Class 4 Med drop probability */
action af_42() {
hdr.ipv4.diffserv = 36;
}
/* Class 4 High drop probability */
action af_43() {
hdr.ipv4.diffserv = 38;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
apply {
if (hdr.ipv4.isValid()) {
if (hdr.ipv4.protocol == IP_PROTOCOLS_UDP) {
expedited_forwarding();
}
else if (hdr.ipv4.protocol == IP_PROTOCOLS_TCP) {
voice_admit();
}
ipv4_lpm.apply();
}
}
}
``
由hdr.ipv4.diffserv控制
source_routing
目标:
实现源路由。通过源路由,源主机引导网络中的每个交换机将数据包发送到特定端口。主机在数据包中放置一个输出端口堆栈。我们只需将堆栈放在以太网标头之后,然后选择一个特殊的 etherType 来指示这一点。每个交换机从堆栈中弹出一个项目,并根据指定的端口号转发数据包。
交换机必须解析源路由堆栈。每个项目都有一个 bos(堆栈底部)位和一个端口号。bos 位仅为 1,仅用于堆栈的最后一个条目。然后在入口处,它应该从堆栈中弹出一个条目并相应地设置出口端口。最后一个跃点也可能将 etherType 恢复为 。

浙公网安备 33010602011771号