P4 语言的核心思想是将数据包处理逻辑抽象为以下几个主要部分:

数据包解析(Parsing):定义如何从数据包中提取字段。
表匹配(Table Matching):定义如何根据数据包字段进行匹配和查找。
数据包处理(Processing):定义如何对匹配结果进行处理和修改。
数据包转发(Forwarding):定义如何将处理后的数据包转发到适当的端口。

语法案例

// 头部定义
header ethernet_t {
    bit<48> dstAddr;
    bit<48> 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;
    bit<32> srcAddr;
    bit<32> dstAddr;
}

// 头部实例
struct headers_t {
    ethernet_t ethernet;
    ipv4_t ipv4;
}

// 解析器
parser MyParser(packet_in packet,
                out headers_t hdr,
                inout metadata_t meta,
                inout standard_metadata_t standard_metadata) {
    state start {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            0x0800: parse_ipv4;
            default: accept;
        }
    }

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

// 动作
action ipv4_forward(bit<48> dstAddr, bit<9> port) {
    hdr.ethernet.dstAddr = dstAddr;
    standard_metadata.egress_spec = port;
}

action drop() {
    mark_to_drop();
}

// 表
table ipv4_lpm {
    key = {
        hdr.ipv4.dstAddr: lpm;
    }
    actions = {
        ipv4_forward;
        drop;
    }
    size = 1024;
    default_action = drop();
}

// 控制逻辑
control MyIngress(inout headers_t hdr,
                  inout metadata_t meta,
                  inout standard_metadata_t standard_metadata) {
    apply {
        if (hdr.ipv4.isValid()) {
            ipv4_lpm.apply();
        }
    }
}

// 出口处理
control MyDeparser(packet_out packet,
                   in headers_t hdr) {
    apply {
        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
    }
}

// 顶层管道
pipeline MyPipeline {
    parser MyParser;
    control MyIngress;
    deparser MyDeparser;
}

编写一个简单的 P4 程序,定义以太网头部和 L2 转发表。

P4 程序(simple_switch.p4):

// 头部定义
header ethernet_t {
    bit<48> dstAddr;
    bit<48> srcAddr;
    bit<16> etherType;
}

// 头部实例
struct headers_t {
    ethernet_t ethernet;
}

// 解析器
parser MyParser(packet_in packet,
                out headers_t hdr,
                inout metadata_t meta,
                inout standard_metadata_t standard_metadata) {
    state start {
        packet.extract(hdr.ethernet);
        transition accept;
    }
}

// 动作
action forward(bit<9> port) {
    standard_metadata.egress_spec = port;
}

action drop() {
    mark_to_drop();
}

// 表
table l2_forward {
    key = {
        hdr.ethernet.dstAddr: exact;
    }
    actions = {
        forward;
        drop;
    }
    size = 1024;
    default_action = drop();
}

// 控制逻辑
control MyIngress(inout headers_t hdr,
                  inout metadata_t meta,
                  inout standard_metadata_t standard_metadata) {
    apply {
        l2_forward.apply();
    }
}

// 出口处理
control MyDeparser(packet_out packet,
                   in headers_t hdr) {
    apply {
        packet.emit(hdr.ethernet);
    }
}

// 顶层管道
pipeline MyPipeline {
    parser MyParser;
    control MyIngress;
    deparser MyDeparser;
}

编写一个简单的 Rust 客户端,使用 P4Runtime 设置转发表项。
依赖:

[dependencies]
tonic = "0.5"
prost = "0.7"

接下来,编写一个简单的 Rust 客户端,连接到 P4Runtime 服务器并发送请求:

use tonic::{transport::Channel, Request};
use p4runtime::p4runtime_client::P4RuntimeClient;
use p4runtime::{Entity, MasterArbitrationUpdate, Uint128, WriteRequest, Update, TableEntry, FieldMatch, Action, ActionParam};

pub mod p4runtime {
    tonic::include_proto!("p4.v1");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建 gRPC 连接
    let channel = Channel::from_static("http://127.0.0.1:50051")
        .connect()
        .await?;

    // 创建 P4Runtime 客户端
    let mut client = P4RuntimeClient::new(channel);

    // 创建 MasterArbitrationUpdate 请求
    let arbitration_request = MasterArbitrationUpdate {
        device_id: 1,
        election_id: Some(Uint128 { high: 0, low: 1 }),
    };

    // 发送 MasterArbitrationUpdate 请求
    let arbitration_response = client
        .master_arbitration_update(Request::new(arbitration_request))
        .await?;

    println!("Arbitration Response: {:?}", arbitration_response);

    // 定义转发表项
    let table_entry = TableEntry {
        table_id: 1, // l2_forward 表的 ID
        match: vec![FieldMatch {
            field_id: 1, // hdr.ethernet.dstAddr 字段的 ID
            field_match_type: Some(p4runtime::field_match::FieldMatchType::Exact(p4runtime::field_match::Exact {
                value: vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55], // 目标 MAC 地址
            })),
        }],
        action: Some(p4runtime::TableAction {
            action: Some(p4runtime::table_action::Action::Action(Action {
                action_id: 1, // forward 动作的 ID
                params: vec![ActionParam {
                    param_id: 1, // port 参数的 ID
                    value: vec![0x00, 0x01], // 输出端口 1
                }],
            })),
        }),
        ..Default::default()
    };

    // 创建 WriteRequest
    let write_request = WriteRequest {
        device_id: 1,
        election_id: Some(Uint128 { high: 0, low: 1 }),
        updates: vec![Update {
            r#type: Update_Type::Insert as i32,
            entity: Some(Entity {
                entity: Some(p4runtime::entity::Entity::TableEntry(table_entry)),
            }),
        }],
        ..Default::default()
    };

    // 发送 WriteRequest
    let write_response = client.write(Request::new(write_request)).await?;

    println!("Write Response: {:?}", write_response);

    Ok(())
}

编译 P4 程序:使用 P4 编译器(如 P4C)编译 simple_switch.p4 程序,生成目标平台的代码。

p4c --target bmv2 --arch v1model --std p4-16 simple_switch.p4

启动 P4 运行时环境:启动一个支持 P4Runtime 的数据平面模拟器(如 BMv2)。

simple_switch_grpc --device-id 1 --grpc-server-addr 127.0.0.1:50051 simple_switch.json

putao
8 声望1 粉丝

推动世界向前发展,改善民生。


引用和评论

0 条评论