├── book ├── .gitignore ├── text │ ├── src │ │ ├── chapter_1.md │ │ ├── 03-guidelines.md │ │ ├── 02-by-example.md │ │ ├── 01-basics.md │ │ ├── SUMMARY.md │ │ ├── intro.md │ │ ├── 03-01-endianness.md │ │ ├── 01-01-installation.md │ │ └── 01-03-compile_and_run.md │ └── book.toml └── code │ ├── build.rs │ ├── src │ └── bin │ │ ├── softnpu.p4 │ │ ├── core.p4 │ │ ├── hello-world.rs │ │ ├── hello-world.p4 │ │ ├── vlan-switch.rs │ │ ├── headers.p4 │ │ └── vlan-switch.p4 │ └── Cargo.toml ├── .rustfmt.toml ├── .gitignore ├── p4 ├── examples │ ├── include.p4 │ ├── bad │ │ ├── parser │ │ │ ├── empty-include.p4 │ │ │ ├── naked-include.p4 │ │ │ ├── non-integer-width-param.p4 │ │ │ └── badness-included.p4 │ │ ├── lexer │ │ │ └── constants.p4 │ │ └── checker │ │ │ ├── parser-no-start-state.p4 │ │ │ ├── struct-undefined-member-type-ref.p4 │ │ │ ├── undefined_type_ref_parser_arg.p4 │ │ │ └── parser-undefined-state-stmt-lval.p4 │ ├── pound-define-func.p4 │ ├── constants.p4 │ ├── codegen │ │ ├── README.md │ │ ├── ipv6_header.p4 │ │ ├── softnpu.p4 │ │ ├── parser-structs-and-headers.p4 │ │ ├── core.p4 │ │ ├── header-stack.p4 │ │ ├── list.p4 │ │ ├── double_instantiation.p4 │ │ └── router.p4 │ ├── structs.p4 │ ├── headers.p4 │ ├── preproc.p4 │ ├── softnpu.p4 │ ├── parser.p4 │ ├── control.p4 │ └── bump-in-the-wire.p4 ├── Cargo.toml └── src │ ├── lib.rs │ ├── util.rs │ ├── error.rs │ └── preprocessor.rs ├── x4c_error_codes ├── src │ ├── lib.rs │ └── error_codes │ │ └── E0001.md └── Cargo.toml ├── rust-toolchain.toml ├── dtrace ├── accepted.d ├── dropped.d ├── dtrace-list-probes.sh ├── softnpu-inout.d ├── softnpu-monitor.d └── softnpu-stats.d ├── lang ├── p4rs │ ├── README.md │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ ├── externs.rs │ │ ├── checksum.rs │ │ ├── bitmath.rs │ │ └── lib.rs ├── prog │ ├── README.md │ └── sidecar-lite │ │ ├── build.rs │ │ ├── src │ │ └── lib.rs │ │ └── Cargo.toml ├── p4-macro-test │ ├── Cargo.toml │ └── src │ │ ├── ether.p4 │ │ └── main.rs └── p4-macro │ ├── Cargo.toml │ └── src │ └── lib.rs ├── .cargo └── config.toml ├── .github ├── buildomat │ ├── config.toml │ └── jobs │ │ └── build-and-test.sh ├── renovate.json └── workflows │ └── rust.yml ├── x4c ├── Cargo.toml └── src │ ├── bin │ └── x4c.rs │ └── lib.rs ├── codegen └── rust │ ├── Cargo.toml │ └── src │ ├── parser.rs │ ├── p4struct.rs │ ├── header.rs │ └── expression.rs ├── test ├── src │ ├── controller_multiple_instantiation.rs │ ├── data.rs │ ├── table_in_egress_and_ingress.rs │ ├── p4 │ │ ├── core.p4 │ │ ├── softnpu.p4 │ │ ├── vlan_header.p4 │ │ ├── arithmetic.p4 │ │ ├── table_in_egress_and_ingress.p4 │ │ ├── controller_multiple_instantiation.p4 │ │ ├── hub.p4 │ │ ├── range.p4 │ │ ├── headers.p4 │ │ ├── dynamic_router_noaddr.p4 │ │ ├── dynamic_router.p4 │ │ ├── dynamic_router_noaddr_nbr.p4 │ │ ├── router.p4 │ │ └── decap.p4 │ ├── lib.rs │ ├── arithmetic.rs │ ├── vlan.rs │ ├── packet.rs │ ├── ipv6.rs │ ├── dload.rs │ ├── basic_router.rs │ ├── disag_router.rs │ ├── hub.rs │ ├── headers.rs │ ├── range.rs │ ├── decap.rs │ ├── dynamic_router.rs │ └── mac_rewrite.rs ├── Cargo.toml ├── build.rs └── README.md ├── Cargo.toml └── README.md /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /book/text/src/chapter_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.sw* 3 | out.rs 4 | tags 5 | -------------------------------------------------------------------------------- /p4/examples/include.p4: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /x4c_error_codes/src/lib.rs: -------------------------------------------------------------------------------- 1 | //mod error_codes; 2 | -------------------------------------------------------------------------------- /p4/examples/bad/parser/empty-include.p4: -------------------------------------------------------------------------------- 1 | #include <> 2 | -------------------------------------------------------------------------------- /p4/examples/bad/parser/naked-include.p4: -------------------------------------------------------------------------------- 1 | #include core.p4 2 | -------------------------------------------------------------------------------- /p4/examples/bad/lexer/constants.p4: -------------------------------------------------------------------------------- 1 | const bit<32> ENTERPRISE = 0z1701D; 2 | -------------------------------------------------------------------------------- /p4/examples/pound-define-func.p4: -------------------------------------------------------------------------------- 1 | #define subspace(channel) (channel == secure) 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.86.0" 3 | profile = "default" 4 | -------------------------------------------------------------------------------- /dtrace/accepted.d: -------------------------------------------------------------------------------- 1 | control_accepted* { 2 | printf("\n%s\n", copyinstr(arg0)); 3 | } 4 | -------------------------------------------------------------------------------- /dtrace/dropped.d: -------------------------------------------------------------------------------- 1 | ::ingress_dropped { 2 | printf("\n%s\n", copyinstr(arg0)); 3 | } 4 | -------------------------------------------------------------------------------- /p4/examples/bad/parser/non-integer-width-param.p4: -------------------------------------------------------------------------------- 1 | const bit ENTERPRISE = 0x1701D; 2 | -------------------------------------------------------------------------------- /p4/examples/constants.p4: -------------------------------------------------------------------------------- 1 | const bit<32> ENTERPRISE = 0x1701D; 2 | const int OUTPOST = 47; 3 | -------------------------------------------------------------------------------- /dtrace/dtrace-list-probes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pfexec dtrace -l -n softnpu_provider*::: 4 | -------------------------------------------------------------------------------- /x4c_error_codes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x4c_error_codes" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /book/text/src/03-guidelines.md: -------------------------------------------------------------------------------- 1 | # Guidelines 2 | 3 | Ths chapter provides guidelines on various aspects of the `x4c` compiler. 4 | -------------------------------------------------------------------------------- /p4/examples/codegen/README.md: -------------------------------------------------------------------------------- 1 | # Code Generation Examples 2 | 3 | The P4 examples in this directory are for testing compiler code generation. 4 | -------------------------------------------------------------------------------- /p4/examples/bad/parser/badness-included.p4: -------------------------------------------------------------------------------- 1 | #include <../checker/undefined_type_ref_parser_arg.p4> 2 | #include <../checker/parser-no-start-state.p4> 3 | -------------------------------------------------------------------------------- /dtrace/softnpu-inout.d: -------------------------------------------------------------------------------- 1 | ::parser_accepted { 2 | printf("%s", copyinstr(arg0)); 3 | } 4 | 5 | ::ingress_accepted { 6 | printf("%s", copyinstr(arg0)); 7 | } 8 | -------------------------------------------------------------------------------- /p4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p4" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | colored.workspace = true 8 | regex.workspace = true 9 | -------------------------------------------------------------------------------- /lang/p4rs/README.md: -------------------------------------------------------------------------------- 1 | # Rust Language Support for P4 2 | 3 | This is a library that contains Rust types for common P4 elements. Rust code 4 | generated by x4c requires this library. 5 | -------------------------------------------------------------------------------- /book/text/src/02-by-example.md: -------------------------------------------------------------------------------- 1 | # By Example 2 | 3 | This chapter presents the use of the P4 language and `x4c` through a series of 4 | examples. This is a living set that will grow over time. 5 | -------------------------------------------------------------------------------- /lang/prog/README.md: -------------------------------------------------------------------------------- 1 | # SoftNPU Programs 2 | 3 | This directory contains a collection of crates that produce precompiled P4 4 | programs as shared libraries that are dynamically loadable by SoftNPU. 5 | -------------------------------------------------------------------------------- /lang/prog/sidecar-lite/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src = ["../../../test/src/p4/sidecar-lite.p4"]; 3 | for x in src { 4 | println!("cargo:rerun-if-changed={}", x); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [net] 2 | git-fetch-with-cli = true 3 | 4 | # https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993 5 | [env] 6 | CARGO_WORKSPACE_DIR = { value = "", relative = true } 7 | -------------------------------------------------------------------------------- /lang/prog/sidecar-lite/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | #![allow(clippy::too_many_arguments)] 4 | 5 | p4_macro::use_p4!(p4 = "test/src/p4/sidecar-lite.p4", pipeline_name = "main"); 6 | -------------------------------------------------------------------------------- /book/text/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Ryan Goodfellow"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The x4c Book" 7 | 8 | [output.html.playground] 9 | runnable = false 10 | -------------------------------------------------------------------------------- /p4/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | pub mod ast; 4 | pub mod check; 5 | pub mod error; 6 | pub mod hlir; 7 | pub mod lexer; 8 | pub mod parser; 9 | pub mod preprocessor; 10 | pub mod util; 11 | -------------------------------------------------------------------------------- /p4/examples/structs.p4: -------------------------------------------------------------------------------- 1 | header Tcp_h { /* fields omitted */ } 2 | header Udp_h { /* fields omitted */ } 3 | struct Parsed_headers { 4 | Ethernet_h ethernet; 5 | Ip_h ip; 6 | Tcp_h tcp; 7 | Udp_h udp; 8 | } 9 | -------------------------------------------------------------------------------- /.github/buildomat/config.toml: -------------------------------------------------------------------------------- 1 | # 2 | # This file, with this flag, must be present in the default branch in order for 3 | # the buildomat integration to create check suites. 4 | # 5 | enable = true 6 | allow_users = [ 7 | "oxide-renovate[bot]", 8 | ] 9 | -------------------------------------------------------------------------------- /p4/examples/codegen/ipv6_header.p4: -------------------------------------------------------------------------------- 1 | header ipv6_t { 2 | bit<4> version; 3 | bit<8> traffic_class; 4 | bit<20> flow_label; 5 | bit<16> payload_len; 6 | bit<8> next_hdr; 7 | bit<8> hop_limit; 8 | bit<128> src; 9 | bit<128> dst; 10 | } 11 | -------------------------------------------------------------------------------- /lang/p4-macro-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p4-macro-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | p4-macro.workspace = true 8 | p4rs.workspace = true 9 | bitvec.workspace = true 10 | colored.workspace = true 11 | usdt.workspace = true 12 | -------------------------------------------------------------------------------- /x4c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x4c" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | colored.workspace = true 8 | clap.workspace = true 9 | anyhow.workspace = true 10 | regex.workspace = true 11 | p4.workspace = true 12 | p4-rust.workspace = true 13 | -------------------------------------------------------------------------------- /lang/p4rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p4rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | num.workspace = true 8 | bitvec.workspace = true 9 | usdt.workspace = true 10 | serde.workspace = true 11 | 12 | [dev-dependencies] 13 | pnet.workspace = true 14 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>oxidecomputer/renovate-config", 5 | "local>oxidecomputer/renovate-config//rust/autocreate", 6 | "local>oxidecomputer/renovate-config//actions/pin" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /p4/examples/headers.p4: -------------------------------------------------------------------------------- 1 | typedef bit<128> ipv6_addr_t; 2 | 3 | header ipv6_h { 4 | bit<4> version; 5 | bit<8> traffic_class; 6 | bit<20> flow_label; 7 | bit<16> payload_len; 8 | bit<8> next_hdr; 9 | bit<8> hop_limit; 10 | ipv6_addr_t src_addr; 11 | ipv6_addr_t dst_addr; 12 | } 13 | -------------------------------------------------------------------------------- /codegen/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p4-rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | p4.workspace = true 8 | quote.workspace = true 9 | proc-macro2.workspace = true 10 | syn.workspace = true 11 | prettyplease.workspace = true 12 | tempfile.workspace = true 13 | -------------------------------------------------------------------------------- /test/src/controller_multiple_instantiation.rs: -------------------------------------------------------------------------------- 1 | p4_macro::use_p4!( 2 | p4 = "test/src/p4/controller_multiple_instantiation.p4", 3 | pipeline_name = "cmi", 4 | ); 5 | 6 | #[test] 7 | fn controller_multiple_instantiation() -> Result<(), anyhow::Error> { 8 | println!("it compiles!"); 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /p4/examples/preproc.p4: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ENTERPRISE 1701 4 | 5 | #define STARSHIP { \ 6 | bit<8> e; \ 7 | bit<16> t; \ 8 | bit<8> l; \ 9 | bit<8> r; \ 10 | bit<1> v; \ 11 | } 12 | 13 | const bit<47> enterprise = ENTERPRISE; 14 | 15 | header starship STARSHIP 16 | -------------------------------------------------------------------------------- /book/code/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src = [ 3 | "src/bin/hello-world.p4", 4 | "src/bin/vlan-switch.p4", 5 | "src/bin/headers.p4", 6 | "src/bin/softnpu.p4", 7 | "src/bin/core.p4", 8 | ]; 9 | for x in src { 10 | println!("cargo:rerun-if-changed={}", x); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lang/p4-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p4-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | proc-macro2.workspace = true 8 | syn.workspace = true 9 | p4.workspace = true 10 | p4-rust.workspace = true 11 | serde_tokenstream.workspace = true 12 | serde.workspace = true 13 | 14 | [lib] 15 | proc-macro = true 16 | -------------------------------------------------------------------------------- /p4/examples/codegen/softnpu.p4: -------------------------------------------------------------------------------- 1 | struct ingress_metadata_t { 2 | bit<16> port; 3 | bool nat; 4 | bit<16> nat_id; 5 | bool drop; 6 | } 7 | 8 | struct egress_metadata_t { 9 | bit<16> port; 10 | bit<128> nexthop; 11 | bool drop; 12 | bool broadcast; 13 | } 14 | 15 | extern Checksum { 16 | bit<16> run(in T data); 17 | } 18 | -------------------------------------------------------------------------------- /test/src/data.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! muffins { 3 | () => { 4 | ( 5 | b"do you know the muffin man?", 6 | b"the muffin man?", 7 | b"the muffin man!", 8 | b"why yes", 9 | b"i know the muffin man", 10 | b"the muffin man is me!!!", 11 | ) 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /test/src/table_in_egress_and_ingress.rs: -------------------------------------------------------------------------------- 1 | p4_macro::use_p4!( 2 | p4 = "test/src/p4/table_in_egress_and_ingress.p4", 3 | pipeline_name = "table_in_ingresss_and_egress", 4 | ); 5 | 6 | // This test is just to make sure the above code compiles 7 | 8 | #[test] 9 | fn table_in_egress_and_ingress() { 10 | println!("table in ingress and egress compiles"); 11 | } 12 | -------------------------------------------------------------------------------- /lang/p4-macro-test/src/ether.p4: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | parser parsadillo(packet_in pkt, out headers_t headers){ 4 | state start { transition accept; } 5 | } 6 | 7 | struct headers_t { 8 | ethernet_t ethernet; 9 | } 10 | 11 | header ethernet_t { 12 | bit<48> dst_addr; 13 | bit<48> src_addr; 14 | bit<16> ether_type; 15 | } 16 | -------------------------------------------------------------------------------- /p4/examples/bad/checker/parser-no-start-state.p4: -------------------------------------------------------------------------------- 1 | // This is a bad parser example. All parsers must include a "start" state which 2 | // this one does not have. It should through a semantic checker error. 3 | 4 | parser bad_parser( 5 | packet_in packet, 6 | out headers_t hdr, 7 | ) { 8 | state parse_something { 9 | transition accept; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lang/prog/sidecar-lite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sidecar-lite" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "sidecar_lite" 8 | crate-type = ["dylib"] 9 | 10 | [dependencies] 11 | p4rs.workspace = true 12 | p4-macro.workspace = true 13 | num.workspace = true 14 | bitvec.workspace = true 15 | colored.workspace = true 16 | usdt.workspace = true 17 | -------------------------------------------------------------------------------- /book/text/src/01-basics.md: -------------------------------------------------------------------------------- 1 | # The Basics 2 | 3 | This chapter will take you from zero to a simple hello-world program in P4. 4 | This will include. 5 | 6 | - Getting a Rust toolchain setup and installing the `x4c` compiler. 7 | - Compiling a P4 hello world program into a Rust program. 8 | - Writing a bit of Rust code to push packets through the our compiled P4 9 | pipelines. 10 | -------------------------------------------------------------------------------- /book/code/src/bin/softnpu.p4: -------------------------------------------------------------------------------- 1 | struct ingress_metadata_t { 2 | bit<16> port; 3 | bool nat; 4 | bit<16> nat_id; 5 | bool drop; 6 | } 7 | 8 | struct egress_metadata_t { 9 | bit<16> port; 10 | bit<128> nexthop_v6; 11 | bit<32> nexthop_v4; 12 | bool drop; 13 | bool broadcast; 14 | } 15 | 16 | extern Checksum { 17 | bit<16> run(in T data); 18 | } 19 | -------------------------------------------------------------------------------- /p4/examples/codegen/parser-structs-and-headers.p4: -------------------------------------------------------------------------------- 1 | parser parsadillo( 2 | packet_in pkt, 3 | out headers_t headers, 4 | ){ 5 | state start { 6 | transition accept; 7 | } 8 | } 9 | 10 | struct headers_t { 11 | ethernet_t ethernet; 12 | } 13 | 14 | header ethernet_t { 15 | bit<48> dst_addr; 16 | bit<48> src_addr; 17 | bit<16> ether_type; 18 | } 19 | -------------------------------------------------------------------------------- /p4/examples/bad/checker/struct-undefined-member-type-ref.p4: -------------------------------------------------------------------------------- 1 | parser parsadillo( 2 | packet_in pkt, 3 | out headers_t headers, 4 | ){ 5 | state start { 6 | transition accept; 7 | } 8 | } 9 | 10 | struct headers_t { 11 | muffin_t ethernet; 12 | } 13 | 14 | header ethernet_t { 15 | bit<48> dst_addr; 16 | bit<48> src_addr; 17 | bit<16> ether_type; 18 | } 19 | -------------------------------------------------------------------------------- /p4/examples/codegen/core.p4: -------------------------------------------------------------------------------- 1 | extern packet_in { 2 | void extract(out T headerLvalue); 3 | void extract(out T variableSizeHeader, in bit<32> varFieldSizeBits); 4 | T lookahead(); 5 | bit<32> length(); // This method may be unavailable in some architectures 6 | void advance(bit<32> bits); 7 | } 8 | 9 | extern packet_out { 10 | void emit(in T hdr); 11 | } 12 | -------------------------------------------------------------------------------- /p4/examples/bad/checker/undefined_type_ref_parser_arg.p4: -------------------------------------------------------------------------------- 1 | parser bad_parser( 2 | packet_in pkt, 3 | out muffins_t the_muffins, 4 | ) { 5 | state start { 6 | transition accept; 7 | } 8 | } 9 | 10 | struct headers_t { 11 | ethernet_t ethernet; 12 | } 13 | 14 | header ethernet_t { 15 | EthernetAddress dst_addr; 16 | EthernetAddress src_addr; 17 | bit<16> ether_type; 18 | } 19 | -------------------------------------------------------------------------------- /book/code/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x4c-book" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | num.workspace = true 8 | tests.workspace = true 9 | p4-macro.workspace = true 10 | p4rs.workspace = true 11 | xfr.workspace = true 12 | bitvec.workspace = true 13 | pnet.workspace = true 14 | colored.workspace = true 15 | usdt.workspace = true 16 | rand.workspace = true 17 | anyhow.workspace = true 18 | -------------------------------------------------------------------------------- /test/src/p4/core.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * This is core.p4 3 | */ 4 | extern packet_in { 5 | void extract(out T headerLvalue); 6 | void extract(out T variableSizeHeader, in bit<32> varFieldSizeBits); 7 | T lookahead(); 8 | bit<32> length(); // This method may be unavailable in some architectures 9 | void advance(bit<32> bits); 10 | } 11 | 12 | extern packet_out { 13 | void emit(in T hdr); 14 | } 15 | -------------------------------------------------------------------------------- /book/code/src/bin/core.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * This is core.p4 3 | */ 4 | extern packet_in { 5 | void extract(out T headerLvalue); 6 | void extract(out T variableSizeHeader, in bit<32> varFieldSizeBits); 7 | T lookahead(); 8 | bit<32> length(); // This method may be unavailable in some architectures 9 | void advance(bit<32> bits); 10 | } 11 | 12 | extern packet_out { 13 | void emit(in T hdr); 14 | } 15 | -------------------------------------------------------------------------------- /book/text/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [The x4c Book](./intro.md) 4 | - [The Basics](./01-basics.md) 5 | - [Installation](./01-01-installation.md) 6 | - [Hello World](./01-02-hello_world.md) 7 | - [Compile and Run](./01-03-compile_and_run.md) 8 | - [By Example](./02-by-example.md) 9 | - [VLAN Switch](./02-01-vlan-switch.md) 10 | - [Guidelines](./03-guidelines.md) 11 | - [Endianness](./03-01-endianness.md) 12 | -------------------------------------------------------------------------------- /lang/p4rs/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use std::error::Error; 4 | use std::fmt; 5 | 6 | #[derive(Debug)] 7 | pub struct TryFromSliceError(pub usize); 8 | 9 | impl fmt::Display for TryFromSliceError { 10 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 11 | write!(f, "slice not big enough for {} bits", self.0) 12 | } 13 | } 14 | 15 | impl Error for TryFromSliceError {} 16 | -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | num.workspace = true 8 | p4-macro.workspace = true 9 | p4rs.workspace = true 10 | xfr.workspace = true 11 | bitvec.workspace = true 12 | pnet.workspace = true 13 | colored.workspace = true 14 | usdt.workspace = true 15 | rand.workspace = true 16 | anyhow.workspace = true 17 | 18 | [dev-dependencies] 19 | libloading.workspace = true 20 | -------------------------------------------------------------------------------- /test/src/p4/softnpu.p4: -------------------------------------------------------------------------------- 1 | struct ingress_metadata_t { 2 | bit<16> port; 3 | bool nat; // XXX this should be a program specific thing 4 | bit<16> nat_id; // XXX this should be a program specific thing 5 | bool drop; 6 | } 7 | 8 | struct egress_metadata_t { 9 | bit<16> port; 10 | bit<128> nexthop_v6; 11 | bit<32> nexthop_v4; 12 | bool drop; 13 | bool broadcast; 14 | } 15 | 16 | extern Checksum { 17 | bit<16> run(in T data); 18 | } 19 | -------------------------------------------------------------------------------- /x4c_error_codes/src/error_codes/E0001.md: -------------------------------------------------------------------------------- 1 | This error indicates incompatable match types have been used in a table keyset. 2 | The following combinations of match types may be used in a table. 3 | 4 | 1. Any number of `ternary`, `exact` or `range` match types may appear in a keyset. 5 | 2. Zero or one `lpm` match types may appear in a keyset. 6 | 3. A combination of 1 and 2. 7 | 8 | More than one `lpm` matches are not currently allowed in a keyset. If there is a 9 | use case for this, please file an issue in the p4 repository. 10 | -------------------------------------------------------------------------------- /test/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src = [ 3 | "../p4/examples/codegen/router.p4", 4 | "src/p4/hub.p4", 5 | "src/p4/dynamic_router.p4", 6 | "src/p4/dynamic_router_noaddr.p4", 7 | "src/p4/dynamic_router_noaddr_nbr.p4", 8 | "src/p4/router.p4", 9 | "src/p4/sidecar-lite.p4", 10 | "src/p4/decap.p4", 11 | "src/p4/router.p4", 12 | "src/p4/arithmetic.p4", 13 | ]; 14 | for x in src { 15 | println!("cargo:rerun-if-changed={}", x); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /book/text/src/intro.md: -------------------------------------------------------------------------------- 1 | # The x4c Book 2 | 3 | This book provides an introduction to the P4 language using the `x4c` compiler. 4 | The presentation is by example. Each concept is introduced through example P4 5 | code and programs - then demonstrated using simple harnesses that pass packets 6 | through compiled P4 pipelines. 7 | 8 | A basic knowledge of programming and networking is assumed. The `x4c` Rust 9 | compilation target will be used in this book, so a working knowledge of 10 | [Rust](https://www.rust-lang.org/) is also good to have. 11 | -------------------------------------------------------------------------------- /p4/examples/codegen/header-stack.p4: -------------------------------------------------------------------------------- 1 | header ipv6_h { 2 | bit<4> version; 3 | bit<8> traffic_class; 4 | bit<20> flow_label; 5 | bit<16> payload_len; 6 | bit<8> next_hdr; 7 | bit<8> hop_limit; 8 | bit<128> src; 9 | bit<128> dst; 10 | } 11 | 12 | header sidecar_h { 13 | bit<8> sc_code; 14 | bit<8> sc_ingress; 15 | bit<8> sc_egress; 16 | bit<16> sc_ether_type; 17 | bit<128> sc_payload; 18 | } 19 | 20 | header ethernet_h { 21 | bit<48> dst; 22 | bit<48> src; 23 | bit<16> ether_type; 24 | } 25 | -------------------------------------------------------------------------------- /test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::too_many_arguments)] 2 | 3 | #[cfg(test)] 4 | mod arithmetic; 5 | #[cfg(test)] 6 | mod basic_router; 7 | #[cfg(test)] 8 | mod controller_multiple_instantiation; 9 | #[cfg(test)] 10 | mod decap; 11 | #[cfg(test)] 12 | mod disag_router; 13 | #[cfg(test)] 14 | mod dload; 15 | #[cfg(test)] 16 | mod dynamic_router; 17 | #[cfg(test)] 18 | mod headers; 19 | #[cfg(test)] 20 | mod hub; 21 | #[cfg(test)] 22 | mod ipv6; 23 | #[cfg(test)] 24 | mod mac_rewrite; 25 | #[cfg(test)] 26 | mod range; 27 | #[cfg(test)] 28 | mod table_in_egress_and_ingress; 29 | #[cfg(test)] 30 | mod vlan; 31 | 32 | pub mod data; 33 | pub mod packet; 34 | pub mod softnpu; 35 | -------------------------------------------------------------------------------- /book/text/src/03-01-endianness.md: -------------------------------------------------------------------------------- 1 | # Endianness 2 | 3 | The basic rules for endianness follow. Generally speaking numeric fields are in 4 | big endian when they come in off the wire, little endian while in the program, 5 | and transformed back to big endian on the way back out onto the wire. We refer 6 | to this as confused endian. 7 | 8 | 1. All numeric packet field data is big endian when enters and leaves a p4 9 | program. 10 | 2. All numeric data, including packet fields is little endian inside a p4 11 | program. 12 | 3. Table keys with the `exact` and `range` type defined over bit types are in 13 | little endian. 14 | 4. Table keys with the `lpm` type are in the byte order they appear on the wire. 15 | -------------------------------------------------------------------------------- /lang/p4-macro-test/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | p4_macro::use_p4!("lang/p4-macro-test/src/ether.p4"); 4 | 5 | fn main() { 6 | let buf = [ 7 | 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac 8 | 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, // src mac 9 | 0x86, 0xdd, // ipv6 ethertype 10 | ]; 11 | 12 | let mut eth = ethernet_t::new(); 13 | eth.set(&buf).unwrap(); 14 | 15 | println!("dst: {:x?}", eth.dst_addr.as_raw_slice()); 16 | println!("src: {:x?}", eth.src_addr.as_raw_slice()); 17 | let ethertype = 18 | u16::from_be_bytes(eth.ether_type.as_raw_slice().try_into().unwrap()); 19 | println!("ethertype: {:x?}", ethertype); 20 | } 21 | -------------------------------------------------------------------------------- /lang/p4rs/src/externs.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use bitvec::prelude::*; 4 | 5 | pub struct Checksum {} 6 | 7 | impl Checksum { 8 | pub fn new() -> Self { 9 | Self {} 10 | } 11 | 12 | pub fn run( 13 | &self, 14 | elements: &[&dyn crate::checksum::Checksum], 15 | ) -> BitVec { 16 | let mut csum: u16 = 0; 17 | for e in elements { 18 | let c: u16 = e.csum().load(); 19 | csum += c; 20 | } 21 | let mut result = bitvec![u8, Msb0; 0u8, 16]; 22 | result.store(csum); 23 | result 24 | } 25 | } 26 | 27 | impl Default for Checksum { 28 | fn default() -> Self { 29 | Self::new() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dtrace/softnpu-monitor.d: -------------------------------------------------------------------------------- 1 | ::parser_transition{ 2 | printf("%s", copyinstr(arg0)); 3 | } 4 | 5 | ::parser_dropped { 6 | printf("parser dropped\n"); 7 | } 8 | 9 | ::parser_accepted { 10 | printf("%s", copyinstr(arg0)); 11 | } 12 | 13 | ::control_apply{ 14 | printf("%s", copyinstr(arg0)); 15 | } 16 | 17 | ::ingress_dropped { 18 | printf("ingress dropped\n"); 19 | } 20 | 21 | ::ingress_accepted { 22 | printf("%s", copyinstr(arg0)); 23 | } 24 | 25 | ::control_table_hit { 26 | printf("%s", copyinstr(arg0)); 27 | } 28 | 29 | ::control_table_miss { 30 | printf("%s", copyinstr(arg0)); 31 | } 32 | 33 | ::match_miss { 34 | printf("%s", copyinstr(arg0)); 35 | } 36 | 37 | ::action { 38 | printf("%s", copyinstr(arg0)); 39 | } 40 | -------------------------------------------------------------------------------- /test/src/arithmetic.rs: -------------------------------------------------------------------------------- 1 | use pnet::packet::ipv4::Ipv4Packet; 2 | 3 | use crate::softnpu::{Interface4, SoftNpu}; 4 | 5 | p4_macro::use_p4!( 6 | p4 = "test/src/p4/arithmetic.p4", 7 | pipeline_name = "arithmetic", 8 | ); 9 | 10 | #[test] 11 | fn arithmetic() -> Result<(), anyhow::Error> { 12 | let pipeline = main_pipeline::new(2); 13 | 14 | let mut npu = SoftNpu::new(2, pipeline, false); 15 | let phy0 = npu.phy(0); 16 | let phy1 = npu.phy(1); 17 | 18 | let if0 = Interface4::new(phy0.clone(), "1.0.0.1".parse().unwrap()); 19 | let msg = b"muffins!"; 20 | 21 | npu.run(); 22 | if0.send(phy1.mac, "2.0.0.1".parse().unwrap(), msg)?; 23 | 24 | let frames = phy1.recv(); 25 | let frame = &frames[0]; 26 | let ip = Ipv4Packet::new(&frame.payload).unwrap(); 27 | assert_eq!(ip.get_identification(), 15); 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /test/src/vlan.rs: -------------------------------------------------------------------------------- 1 | p4_macro::use_p4!("test/src/p4/vlan_header.p4"); 2 | 3 | #[test] 4 | fn test_vlan_parse() -> anyhow::Result<()> { 5 | let mut data = [0u8; 4]; 6 | data[0] = 0x0; 7 | data[1] = 0x47; 8 | let mut pkt = vlan_h::new(); 9 | pkt.set(&data).unwrap(); 10 | let vid: u16 = pkt.vid.to_owned().load_le(); 11 | assert_eq!(vid, 0x47); 12 | let bv = pkt.to_bitvec(); 13 | let readback = bv.into_vec(); 14 | assert_eq!(data.to_vec(), readback); 15 | 16 | let mut data = [0u8; 4]; 17 | data[0] = 0x77; 18 | data[1] = 0x47; 19 | let mut pkt = vlan_h::new(); 20 | pkt.set(&data).unwrap(); 21 | let vid: u16 = pkt.vid.to_owned().load_le(); 22 | assert_eq!(vid, 0x747); 23 | let bv = pkt.to_bitvec(); 24 | let readback = bv.into_vec(); 25 | assert_eq!(data.to_vec(), readback); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | This directory contains 4 | - Simple tests that push packets through compiled P4 pipelines and validate 5 | thaat the results are what we expect. 6 | - A softnpu testing harness for writing automated tests. This harness may be 7 | imported by other crates. 8 | 9 | 10 | ## Testing 11 | 12 | Many of the tests rely on waiting for packets to arrive on a particular 13 | port. If the test is not working, this may never happen. Moreover, you will not 14 | see printed output guiding to to what may be wrong as pipeline observability is 15 | built on DTrace. You can however, use DTrace to get a sense for what is going on 16 | with a test. For example 17 | 18 | ``` 19 | pfexec dtrace -x strsize=4k -Z -s $P4_REPO/p4/dtrace/softnpu-monitor.d -c 'cargo test' 20 | ``` 21 | 22 | Where `$P4_REPO` is an environment variable pointing the top level directory of 23 | the `p4` repo. 24 | -------------------------------------------------------------------------------- /p4/examples/bad/checker/parser-undefined-state-stmt-lval.p4: -------------------------------------------------------------------------------- 1 | // XXX import from core.p4 2 | extern packet_in { 3 | void extract(out T headerLvalue); 4 | void extract(out T variableSizeHeader, in bit<32> varFieldSizeBits); 5 | T lookahead(); 6 | bit<32> length(); // This method may be unavailable in some architectures 7 | void advance(bit<32> bits); 8 | } 9 | 10 | struct headers_t { 11 | ethernet_t ethernet; 12 | } 13 | 14 | header ethernet_t { 15 | bit<48> dst_addr; 16 | bit<48> src_addr; 17 | bit<16> ether_type; 18 | } 19 | 20 | parser test( 21 | packet_in pkt, 22 | out headers_t headers, 23 | ) { 24 | state start { 25 | pkt.extract(headers.ethernet); 26 | pkt.extractX(headers.ethernet); 27 | pktX.extract(headers.ethernet); 28 | pkt.extract(headersX.ethernet); 29 | pkt.extract(headers.ethernetX); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dtrace/softnpu-stats.d: -------------------------------------------------------------------------------- 1 | ::parser_transition{ 2 | @stats["parser transition", copyinstr(arg0)] = count(); 3 | } 4 | 5 | ::parser_dropped { 6 | @stats["parser", "drop"] = count(); 7 | } 8 | 9 | ::parser_accepted { 10 | @stats["parser", "accept"] = count(); 11 | } 12 | 13 | ::control_apply{ 14 | @stats["control apply", copyinstr(arg0)]= count(); 15 | } 16 | 17 | ::ingress_dropped { 18 | @stats["control", "drop"] = count(); 19 | } 20 | 21 | ::ingress_accepted { 22 | @stats["control", "accept"] = count(); 23 | } 24 | 25 | ::control_table_hit { 26 | @stats["table hit", copyinstr(arg0)] = count(); 27 | } 28 | 29 | ::control_table_miss { 30 | @stats["table miss", copyinstr(arg0)] = count(); 31 | } 32 | 33 | tick-5sec 34 | { 35 | printa(@stats); 36 | printf("==========================================================================================================================\n"); 37 | } 38 | -------------------------------------------------------------------------------- /p4/examples/softnpu.p4: -------------------------------------------------------------------------------- 1 | //FIXME there are a number of compilation issues with this file 2 | typedef bit<4> PortId; 3 | 4 | const PortId REAL_PORT_COUNT = 4w4; 5 | const PortId CPU_INGRESS_PORT = 0xA; 6 | const PortId CPU_EGRESS_PORT = 0xB; 7 | const PortId DROP_PORT = 0xC; 8 | 9 | struct ingress_metadata_t { 10 | PortId port; 11 | } 12 | 13 | struct egress_metadata_t { 14 | PortId port; 15 | } 16 | 17 | parser NpuParser( 18 | packet_in pkt, 19 | out H parsed_headers 20 | ); 21 | 22 | control NpuIngress( 23 | inout H hdr, 24 | inout ingress_metadata_t ingress_meta, 25 | inout egress_metadata_t egress_meta, 26 | ); 27 | 28 | control NpuEgress( 29 | inout H hdr, 30 | inout ingress_metadata_t ingress_meta, 31 | inout egress_metadata_t egress_meta, 32 | ); 33 | 34 | package SoftNPU( 35 | NpuParser p, 36 | NpuIngress ingress, 37 | NpuEgress ingress, 38 | ); 39 | -------------------------------------------------------------------------------- /p4/examples/codegen/list.p4: -------------------------------------------------------------------------------- 1 | extern Checksum { 2 | bit<16> run(in T data); 3 | } 4 | 5 | struct headers_t { 6 | ethernet_h eth; 7 | ipv6_h ipv6; 8 | udp_h udp; 9 | } 10 | 11 | header ethernet_h { 12 | bit<48> dst; 13 | bit<48> src; 14 | bit<16> ether_type; 15 | } 16 | 17 | header ipv6_h { 18 | bit<4> version; 19 | bit<8> traffic_class; 20 | bit<20> flow_label; 21 | bit<16> payload_len; 22 | bit<8> next_hdr; 23 | bit<8> hop_limit; 24 | bit<128> src; 25 | bit<128> dst; 26 | } 27 | 28 | header udp_h { 29 | bit<16> src_port; 30 | bit<16> dst_port; 31 | bit<16> len; 32 | bit<16> checksum; 33 | } 34 | 35 | control ingress(inout headers_t hdr) { 36 | 37 | Checksum() csum; 38 | 39 | apply { 40 | csum.run({ 41 | hdr.eth.dst, 42 | hdr.ipv6.src, 43 | hdr.udp, 44 | }); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /test/src/p4/vlan_header.p4: -------------------------------------------------------------------------------- 1 | header ethernet_h { 2 | bit<48> dst; 3 | bit<48> src; 4 | bit<16> ether_type; 5 | } 6 | 7 | header vlan_h { 8 | bit<3> pcp; 9 | bit<1> dei; 10 | bit<12> vid; 11 | bit<16> ether_type; 12 | } 13 | 14 | header sidecar_h { 15 | bit<8> sc_code; 16 | bit<8> sc_pad; 17 | bit<16> sc_ingress; 18 | bit<16> sc_egress; 19 | bit<16> sc_ether_type; 20 | bit<128> sc_payload; 21 | } 22 | 23 | header ipv4_h { 24 | bit<4> version; 25 | bit<4> ihl; 26 | bit<8> diffserv; 27 | bit<16> total_len; 28 | bit<16> identification; 29 | bit<3> flags; 30 | bit<13> frag_offset; 31 | bit<8> ttl; 32 | bit<8> protocol; 33 | bit<16> hdr_checksum; 34 | bit<32> src; 35 | bit<32> dst; 36 | } 37 | 38 | struct headers_t { 39 | ethernet_h ethernet; 40 | vlan_h vlan; 41 | sidecar_h sidecar; 42 | ipv4_h ipv4; 43 | } 44 | -------------------------------------------------------------------------------- /test/src/packet.rs: -------------------------------------------------------------------------------- 1 | use pnet::packet::ipv4::MutableIpv4Packet; 2 | use pnet::packet::ipv6::MutableIpv6Packet; 3 | use std::net::{Ipv4Addr, Ipv6Addr}; 4 | 5 | pub fn v6<'a>( 6 | src: Ipv6Addr, 7 | dst: Ipv6Addr, 8 | payload: &[u8], 9 | data: &'a mut [u8], 10 | ) -> MutableIpv6Packet<'a> { 11 | data.fill(0); 12 | 13 | let mut pkt = MutableIpv6Packet::new(data).unwrap(); 14 | pkt.set_source(src); 15 | pkt.set_destination(dst); 16 | pkt.set_payload_length(payload.len() as u16); 17 | pkt.set_payload(payload); 18 | pkt 19 | } 20 | 21 | pub fn v4<'a>( 22 | src: Ipv4Addr, 23 | dst: Ipv4Addr, 24 | payload: &[u8], 25 | data: &'a mut [u8], 26 | ) -> MutableIpv4Packet<'a> { 27 | data.fill(0); 28 | 29 | let mut pkt = MutableIpv4Packet::new(data).unwrap(); 30 | pkt.set_source(src); 31 | pkt.set_destination(dst); 32 | pkt.set_total_length(20 + payload.len() as u16); 33 | pkt.set_payload(payload); 34 | pkt 35 | } 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "p4", 6 | "x4c", 7 | "x4c_error_codes", 8 | "codegen/rust", 9 | "lang/p4rs", 10 | "lang/p4-macro", 11 | "lang/p4-macro-test", 12 | "lang/prog/sidecar-lite", 13 | "test", 14 | "book/code", 15 | ] 16 | 17 | [workspace.dependencies] 18 | p4-macro = { path = "lang/p4-macro" } 19 | p4-rust = { path = "codegen/rust" } 20 | p4rs = { path = "lang/p4rs" } 21 | tests = { path = "test" } 22 | 23 | anyhow = "1" 24 | bitvec = "1.0" 25 | clap = { version = "4", features = ["color", "derive"] } 26 | colored = "3" 27 | libloading = { version = "0.8" } 28 | num = { version = "0.4", features = ["serde"] } 29 | p4 = { path = "p4" } 30 | pnet = "0.35" 31 | prettyplease = "0.2" 32 | proc-macro2 = "1.0" 33 | quote = "1.0" 34 | rand = "0.9.2" 35 | regex = "1" 36 | serde = "1.0" 37 | serde_tokenstream = "0.2" 38 | syn = "2.0" 39 | tempfile = "3.3" 40 | usdt = "0.5.0" 41 | xfr = { git = "https://github.com/oxidecomputer/xfr" } 42 | -------------------------------------------------------------------------------- /book/code/src/bin/hello-world.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_update)] 2 | use tests::expect_frames; 3 | use tests::softnpu::{RxFrame, SoftNpu, TxFrame}; 4 | 5 | const NUM_PORTS: u16 = 3; 6 | 7 | p4_macro::use_p4!( 8 | p4 = "book/code/src/bin/hello-world.p4", 9 | pipeline_name = "hello" 10 | ); 11 | 12 | fn main() -> Result<(), anyhow::Error> { 13 | let mut npu = 14 | SoftNpu::new(NUM_PORTS.into(), main_pipeline::new(NUM_PORTS), false); 15 | let phy1 = npu.phy(0); 16 | let phy2 = npu.phy(1); 17 | let phy3 = npu.phy(2); 18 | 19 | npu.run(); 20 | 21 | // Expect this packet to be dropped 22 | phy3.send(&[TxFrame::new(phy3.mac, 0, b"to the bit bucket with you!")])?; 23 | 24 | phy1.send(&[TxFrame::new(phy2.mac, 0, b"hello")])?; 25 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0, b"hello")]); 26 | 27 | phy2.send(&[TxFrame::new(phy1.mac, 0, b"world")])?; 28 | expect_frames!(phy1, &[RxFrame::new(phy2.mac, 0, b"world")]); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /test/src/p4/arithmetic.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | ipv4_h ipv4; 14 | } 15 | 16 | parser parse( 17 | packet_in pkt, 18 | out headers_t hdr, 19 | inout ingress_metadata_t ingress, 20 | ){ 21 | state start { 22 | pkt.extract(hdr.ethernet); 23 | transition ipv4; 24 | } 25 | 26 | state ipv4 { 27 | pkt.extract(hdr.ipv4); 28 | hdr.ipv4.identification = hdr.ipv4.identification + 16w20; 29 | transition accept; 30 | } 31 | 32 | } 33 | 34 | control ingress( 35 | inout headers_t hdr, 36 | inout ingress_metadata_t ingress, 37 | inout egress_metadata_t egress, 38 | ) { 39 | apply { 40 | hdr.ipv4.identification = hdr.ipv4.identification - 16w5; 41 | egress.port = 16w1; 42 | } 43 | } 44 | 45 | control egress( 46 | inout headers_t hdr, 47 | inout ingress_metadata_t ingress, 48 | inout egress_metadata_t egress, 49 | ) { 50 | } 51 | -------------------------------------------------------------------------------- /p4/examples/codegen/double_instantiation.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | SoftNPU( 5 | parse(), 6 | ingress() 7 | ) main; 8 | 9 | struct headers_t { } 10 | 11 | parser parse( 12 | packet_in pkt, 13 | out headers_t headers, 14 | inout ingress_metadata_t ingress, 15 | ){ 16 | state start { transition accept; } 17 | } 18 | 19 | control resolver( 20 | in bit<32> x, 21 | out bool resolved, 22 | ) { 23 | action resolve() { 24 | resolved = true; 25 | } 26 | table arp { 27 | key = { x: exact; } 28 | actions = { resolve; } 29 | default_action = NoAction; 30 | } 31 | apply { arp.apply(); } 32 | } 33 | 34 | control foo() { 35 | resolver() resolver; 36 | apply { 37 | resolver.apply(23w47); 38 | } 39 | } 40 | 41 | control bar() { 42 | resolver() resolver; 43 | apply { resolver.apply(23w1701); } 44 | } 45 | 46 | control ingress( 47 | inout headers_t hdr, 48 | inout ingress_metadata_t ingress, 49 | inout egress_metadata_t egress, 50 | ) { 51 | foo() foo; 52 | bar() bar; 53 | 54 | apply { 55 | bar.apply(); 56 | foo.apply(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 11 | 12 | - name: generate documentation 13 | run: cargo doc --no-deps 14 | 15 | - name: generate book 16 | run: | 17 | cargo install mdbook 18 | cd book/text 19 | mdbook build 20 | 21 | - name: prep deployment branch 22 | if: github.ref == 'refs/heads/main' 23 | run: | 24 | mkdir -p dist 25 | cp -r target/doc/* dist/ 26 | cp -r book/text/book dist/ 27 | cd dist 28 | git init 29 | git add -A 30 | git config --local user.email "action@github.com" 31 | git config --local user.name "GitHub Action" 32 | git commit -m 'deploy' 33 | 34 | - name: push deployment branch 35 | uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa # v1.0.0 36 | if: github.ref == 'refs/heads/main' 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | branch: gh-pages 40 | force: true 41 | directory: ./dist 42 | -------------------------------------------------------------------------------- /test/src/p4/table_in_egress_and_ingress.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h eth; 13 | } 14 | 15 | parser parse( 16 | packet_in pkt, 17 | out headers_t headers, 18 | inout ingress_metadata_t ingress, 19 | ){ 20 | state start { 21 | transition accept; 22 | } 23 | } 24 | 25 | control foo( 26 | inout headers_t hdr, 27 | inout ingress_metadata_t ingress, 28 | inout egress_metadata_t egress, 29 | ) { 30 | 31 | action drop() { } 32 | action forward(bit<16> port) { egress.port = port; } 33 | table tbl { 34 | key = { ingress.port: exact; } 35 | actions = { drop; forward; } 36 | default_action = drop; 37 | } 38 | 39 | } 40 | 41 | control ingress( 42 | inout headers_t hdr, 43 | inout ingress_metadata_t ingress, 44 | inout egress_metadata_t egress, 45 | ) { 46 | foo() foo; 47 | apply { 48 | foo.apply(hdr, ingress, egress); 49 | } 50 | } 51 | 52 | control egress( 53 | inout headers_t hdr, 54 | inout ingress_metadata_t ingress, 55 | inout egress_metadata_t egress, 56 | ) { 57 | foo() foo; 58 | apply { 59 | foo.apply(hdr, ingress, egress); 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /p4/examples/parser.p4: -------------------------------------------------------------------------------- 1 | // IPv4 header without options 2 | header IPv4_no_options_h { 3 | bit<4> version; 4 | bit<4> ihl; 5 | bit<8> diffserv; 6 | bit<16> totalLen; 7 | bit<16> identification; 8 | bit<3> flags; 9 | bit<13> fragOffset; 10 | bit<8> ttl; 11 | bit<8> protocol; 12 | bit<16> hdrChecksum; 13 | bit<32> srcAddr; 14 | bit<32> dstAddr; 15 | } 16 | header IPv4_options_h { 17 | varbit<320> options; 18 | } 19 | 20 | struct Parsed_headers { 21 | // Some fields omitted 22 | IPv4_no_options_h ipv4; 23 | IPv4_options_h ipv4options; 24 | } 25 | 26 | // TODO parse error decls 27 | //error { InvalidIPv4Header } 28 | 29 | parser Top(packet_in b, out Parsed_headers headers) { 30 | // Some states omitted 31 | 32 | state parse_ipv4 { 33 | b.extract(headers.ipv4); 34 | verify(headers.ipv4.ihl >= 5, error.InvalidIPv4Header); 35 | transition select (headers.ipv4.ihl) { 36 | 5: dispatch_on_protocol; 37 | _: parse_ipv4_options; 38 | } 39 | } 40 | 41 | state parse_ipv4_options { 42 | // use information in the ipv4 header to compute the number 43 | // of bits to extract 44 | b.extract(headers.ipv4options); //, 45 | // TODO (bit<32>)(((bit<16>)headers.ipv4.ihl - 5) * 32)); 46 | transition dispatch_on_protocol; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/buildomat/jobs/build-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #: 3 | #: name = "build-and-test" 4 | #: variety = "basic" 5 | #: target = "helios-2.0" 6 | #: rust_toolchain = "stable" 7 | #: output_rules = [ 8 | #: "/work/debug/*", 9 | #: "/work/release/*", 10 | #: ] 11 | #: 12 | 13 | set -o errexit 14 | set -o pipefail 15 | set -o xtrace 16 | 17 | cargo --version 18 | rustc --version 19 | 20 | banner "check" 21 | cargo fmt -- --check 22 | cargo check 23 | cargo clippy --all-targets -- --deny warnings 24 | 25 | banner "build" 26 | ptime -m cargo build 27 | ptime -m cargo build --release 28 | 29 | for x in debug release 30 | do 31 | mkdir -p /work/$x 32 | cp target/$x/x4c /work/$x/ 33 | cp target/$x/libsidecar_lite.so /work/$x/ 34 | done 35 | 36 | banner "test" 37 | 38 | cargo test 39 | 40 | pushd test 41 | 42 | banner "mac rewr" 43 | RUST_BACKTRACE=1 cargo test mac_rewrite -- --nocapture 44 | 45 | banner "dyn load" 46 | RUST_BACKTRACE=1 cargo test dload -- --nocapture 47 | 48 | banner "disag" 49 | RUST_BACKTRACE=1 cargo test disag_router -- --nocapture 50 | 51 | banner "dyn rtr" 52 | RUST_BACKTRACE=1 cargo test dynamic_router -- --nocapture 53 | 54 | banner "hub" 55 | RUST_BACKTRACE=1 cargo test hub -- --nocapture 56 | 57 | banner "router" 58 | RUST_BACKTRACE=1 cargo test basic_router -- --nocapture 59 | 60 | banner "headers" 61 | RUST_BACKTRACE=1 cargo test headers -- --nocapture 62 | -------------------------------------------------------------------------------- /x4c/src/bin/x4c.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use anyhow::Result; 4 | use clap::Parser; 5 | use p4::ast::AST; 6 | use std::sync::Arc; 7 | 8 | fn main() { 9 | if let Err(e) = run() { 10 | println!("{}", e); 11 | std::process::exit(1); 12 | } 13 | } 14 | 15 | fn run() -> Result<()> { 16 | let opts = x4c::Opts::parse(); 17 | let filename = Arc::new(opts.filename.clone()); 18 | let mut ast = AST::default(); 19 | x4c::process_file(filename, &mut ast, &opts)?; 20 | 21 | if opts.check { 22 | return Ok(()); 23 | } 24 | 25 | match opts.target { 26 | x4c::Target::Rust => { 27 | // NOTE: it's important to sanitize *before* generating hlir as the 28 | // sanitization process can change lvalue names. 29 | p4_rust::sanitize(&mut ast); 30 | let (hlir, _) = p4::check::all(&ast); 31 | p4_rust::emit( 32 | &ast, 33 | &hlir, 34 | &opts.out, 35 | p4_rust::Settings { 36 | pipeline_name: "main".to_owned(), 37 | }, 38 | )?; 39 | } 40 | x4c::Target::RedHawk => { 41 | todo!("RedHawk code generator"); 42 | } 43 | x4c::Target::Docs => { 44 | todo!("Docs code generator"); 45 | } 46 | } 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /p4/examples/control.p4: -------------------------------------------------------------------------------- 1 | header hdr { 2 | bit<8> e; 3 | bit<16> t; 4 | bit<8> l; 5 | bit<8> r; 6 | bit<1> v; 7 | } 8 | 9 | struct Header_t { 10 | hdr h; 11 | } 12 | 13 | struct Meta_t {} 14 | 15 | struct standard_metadata_t { 16 | bit<16> egress_spec; 17 | } 18 | 19 | control ingress( 20 | inout Header_t h, 21 | inout Meta_t m, 22 | inout standard_metadata_t standard_meta 23 | ) { 24 | action index(bit<16> mask) { 25 | bit<16> csum = 0; 26 | bit<16> offset = 0; 27 | offset = csum & mask; 28 | } 29 | action a() { standard_meta.egress_spec = 16w0; } 30 | action a_with_control_params(bit<16> x) { standard_meta.egress_spec = x; } 31 | 32 | table t_exact_ternary { 33 | 34 | key = { 35 | h.h.e : exact; 36 | h.h.t : ternary; 37 | } 38 | 39 | actions = { 40 | a; 41 | a_with_control_params; 42 | } 43 | 44 | default_action = a; 45 | 46 | const entries = { 47 | (0x01, 0x1111 &&& 0xF ) : a_with_control_params(1); 48 | (0x02, 0x1181 ) : a_with_control_params(2); 49 | (0x03, 0x1111 &&& 0xF000) : a_with_control_params(3); 50 | (0x04, 0x1211 &&& 0x02F0) : a_with_control_params(4); 51 | (0x04, 0x1311 &&& 0x02F0) : a_with_control_params(5); 52 | //TODO (0x06, _ ) : a_with_control_params(6); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /test/src/p4/controller_multiple_instantiation.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | SoftNPU( 5 | parse(), 6 | ingress(), 7 | egress() 8 | ) main; 9 | 10 | struct headers_t { } 11 | 12 | parser parse( 13 | packet_in pkt, 14 | out headers_t headers, 15 | inout ingress_metadata_t ingress, 16 | ){ 17 | state start { transition accept; } 18 | } 19 | 20 | control resolver( 21 | in bit<32> x, 22 | out bool resolved, 23 | ) { 24 | action resolve() { 25 | resolved = true; 26 | } 27 | table arp { 28 | key = { x: exact; } 29 | actions = { resolve; } 30 | default_action = NoAction; 31 | } 32 | apply { arp.apply(); } 33 | } 34 | 35 | control foo() { 36 | resolver() resolver; 37 | apply { 38 | bool resolved; 39 | resolver.apply(32w47, resolved); 40 | } 41 | } 42 | 43 | control bar() { 44 | resolver() resolver; 45 | resolver() taco; 46 | apply { 47 | bool resolved; 48 | resolver.apply(32w1701, resolved); 49 | } 50 | } 51 | 52 | control ingress( 53 | inout headers_t hdr, 54 | inout ingress_metadata_t ingress, 55 | inout egress_metadata_t egress, 56 | ) { 57 | foo() taco; 58 | bar() pizza; 59 | 60 | apply { 61 | taco.apply(); 62 | pizza.apply(); 63 | } 64 | } 65 | 66 | control egress( 67 | inout headers_t hdr, 68 | inout ingress_metadata_t ingress, 69 | inout egress_metadata_t egress, 70 | ) { 71 | 72 | } 73 | -------------------------------------------------------------------------------- /test/src/p4/hub.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | SoftNPU( 5 | parse(), 6 | ingress(), 7 | egress() 8 | ) main; 9 | 10 | struct headers_t { 11 | ethernet_t ethernet; 12 | } 13 | 14 | header ethernet_t { 15 | bit<48> dst_addr; 16 | bit<48> src_addr; 17 | bit<16> ether_type; 18 | } 19 | 20 | parser parse( 21 | packet_in pkt, 22 | out headers_t headers, 23 | inout ingress_metadata_t ingress, 24 | ){ 25 | state start { 26 | pkt.extract(headers.ethernet); 27 | transition finish; 28 | } 29 | 30 | state finish { 31 | transition accept; 32 | } 33 | } 34 | 35 | control ingress( 36 | inout headers_t hdr, 37 | inout ingress_metadata_t ingress, 38 | inout egress_metadata_t egress, 39 | ) { 40 | 41 | action drop() { } 42 | 43 | action forward(bit<16> port) { 44 | egress.port = port; 45 | egress.broadcast = true; 46 | } 47 | 48 | table tbl { 49 | key = { 50 | ingress.port: exact; 51 | } 52 | actions = { 53 | drop; 54 | forward; 55 | } 56 | default_action = drop; 57 | const entries = { 58 | 16w0 : forward(16w1); 59 | 16w1 : forward(16w0); 60 | } 61 | } 62 | 63 | apply { 64 | tbl.apply(); 65 | } 66 | 67 | } 68 | 69 | control egress( 70 | inout headers_t hdr, 71 | inout ingress_metadata_t ingress, 72 | inout egress_metadata_t egress, 73 | ) { 74 | 75 | } 76 | -------------------------------------------------------------------------------- /book/text/src/01-01-installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Rust 4 | 5 | The first thing we'll need to do is install Rust. We'll be using a tool called 6 | [rustup](https://rustup.rs/). On Unix/Linux like platforms, simply run the 7 | following from your terminal. For other platforms see the rustup docs. 8 | 9 | ```bash 10 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 11 | ``` 12 | 13 | It may be necessary to restart your shell session after installing Rust. 14 | 15 | ## `x4c` 16 | 17 | Now we will install the `x4c` compiler using the rust `cargo` tool. 18 | 19 | ```bash 20 | cargo install --git https://github.com/oxidecomputer/p4 x4c 21 | ``` 22 | 23 | You should now be able to run `x4c`. 24 | 25 | ``` 26 | x4c --help 27 | x4c 0.1 28 | 29 | USAGE: 30 | x4c [OPTIONS] [TARGET] 31 | 32 | ARGS: 33 | File to compile 34 | What target to generate code for [default: rust] [possible values: rust, 35 | red-hawk, docs] 36 | 37 | OPTIONS: 38 | --check Just check code, do not compile 39 | -h, --help Print help information 40 | -o, --out Filename to write generated code to [default: out.rs] 41 | --show-ast Show parsed abstract syntax tree 42 | --show-hlir Show high-level intermediate representation info 43 | --show-pre Show parsed preprocessor info 44 | --show-tokens Show parsed lexical tokens 45 | -V, --version Print version information 46 | ``` 47 | 48 | That's it! We're now ready to dive into P4 code. 49 | -------------------------------------------------------------------------------- /test/src/p4/range.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | ipv4_h ipv4; 14 | ipv6_h ipv6; 15 | } 16 | 17 | parser parse( 18 | packet_in pkt, 19 | out headers_t hdr, 20 | inout ingress_metadata_t ingress, 21 | ){ 22 | state start { 23 | pkt.extract(hdr.ethernet); 24 | if (hdr.ethernet.ether_type == 16w0x0800) { 25 | transition ipv4; 26 | } 27 | if (hdr.ethernet.ether_type == 16w0x86dd) { 28 | transition ipv6; 29 | } 30 | transition reject; 31 | } 32 | 33 | state ipv4 { 34 | pkt.extract(hdr.ipv4); 35 | transition accept; 36 | } 37 | 38 | state ipv6 { 39 | pkt.extract(hdr.ipv6); 40 | transition accept; 41 | } 42 | } 43 | 44 | control ingress( 45 | inout headers_t hdr, 46 | inout ingress_metadata_t ingress, 47 | inout egress_metadata_t egress, 48 | ) { 49 | action forward(bit<16> port) { 50 | egress.port = port; 51 | } 52 | 53 | table power_ranger { 54 | key = { 55 | hdr.ipv4.dst: range; 56 | } 57 | actions = { 58 | forward; 59 | } 60 | default_action = NoAction; 61 | } 62 | 63 | apply { 64 | if(hdr.ipv4.isValid()) { 65 | power_ranger.apply(); 66 | } 67 | } 68 | } 69 | 70 | control egress( 71 | inout headers_t hdr, 72 | inout ingress_metadata_t ingress, 73 | inout egress_metadata_t egress, 74 | ) { 75 | } 76 | -------------------------------------------------------------------------------- /book/code/src/bin/hello-world.p4: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct headers_t { 4 | ethernet_h ethernet; 5 | } 6 | 7 | struct ingress_metadata_t { 8 | bit<16> port; 9 | bool drop; 10 | } 11 | 12 | struct egress_metadata_t { 13 | bit<16> port; 14 | bool drop; 15 | bool broadcast; 16 | } 17 | 18 | header ethernet_h { 19 | bit<48> dst; 20 | bit<48> src; 21 | bit<16> ether_type; 22 | } 23 | 24 | parser parse ( 25 | packet_in pkt, 26 | out headers_t headers, 27 | inout ingress_metadata_t ingress, 28 | ){ 29 | state start { 30 | pkt.extract(headers.ethernet); 31 | transition finish; 32 | } 33 | state finish { 34 | transition accept; 35 | } 36 | } 37 | 38 | control ingress( 39 | inout headers_t hdr, 40 | inout ingress_metadata_t ingress, 41 | inout egress_metadata_t egress, 42 | ) { 43 | 44 | action drop() { 45 | ingress.drop = true; 46 | } 47 | 48 | action forward(bit<16> port) { 49 | egress.port = port; 50 | } 51 | 52 | table tbl { 53 | key = { 54 | ingress.port: exact; 55 | } 56 | actions = { 57 | drop; 58 | forward; 59 | } 60 | default_action = drop; 61 | const entries = { 62 | 16w0 : forward(16w1); 63 | 16w1 : forward(16w0); 64 | } 65 | } 66 | 67 | apply { 68 | tbl.apply(); 69 | } 70 | 71 | } 72 | 73 | control egress( 74 | inout headers_t hdr, 75 | inout ingress_metadata_t ingress, 76 | inout egress_metadata_t egress, 77 | ) { 78 | 79 | } 80 | 81 | SoftNPU( 82 | parse(), 83 | ingress(), 84 | egress() 85 | ) main; 86 | -------------------------------------------------------------------------------- /test/src/ipv6.rs: -------------------------------------------------------------------------------- 1 | p4_macro::use_p4!("test/src/p4/sidecar-lite.p4"); 2 | 3 | #[test] 4 | fn test_ipv6_parse() -> anyhow::Result<()> { 5 | let mut data = [0u8; 40]; 6 | // version = 6 7 | // traffic class = 127 (0x7f) 8 | // flow label = 699050 (0xaaaaa) 9 | data[0] = 0b0110_1111; 10 | data[1] = 0b0111_1010; 11 | //data[1] = 0b1010_1111; 12 | data[2] = 0b1010_1010; 13 | data[3] = 0b1010_1010; 14 | // payload len 18247 (0x4747) 15 | data[4] = 47; 16 | data[5] = 47; 17 | // next_hdr 18 | data[6] = 99; 19 | // hop_limit 20 | data[7] = 10; 21 | // src fd00::1 22 | data[8] = 0xfd; 23 | data[9] = 0x00; 24 | data[10] = 0x00; 25 | data[11] = 0x00; 26 | data[12] = 0x00; 27 | data[13] = 0x00; 28 | data[14] = 0x00; 29 | data[15] = 0x00; 30 | data[16] = 0x00; 31 | data[17] = 0x00; 32 | data[18] = 0x00; 33 | data[19] = 0x00; 34 | data[20] = 0x00; 35 | data[21] = 0x00; 36 | data[22] = 0x00; 37 | data[23] = 0x01; 38 | // dst fd00::2 39 | data[24] = 0xfd; 40 | data[25] = 0x00; 41 | data[26] = 0x00; 42 | data[27] = 0x00; 43 | data[28] = 0x00; 44 | data[29] = 0x00; 45 | data[30] = 0x00; 46 | data[31] = 0x00; 47 | data[32] = 0x00; 48 | data[33] = 0x00; 49 | data[34] = 0x00; 50 | data[35] = 0x00; 51 | data[36] = 0x00; 52 | data[37] = 0x00; 53 | data[38] = 0x00; 54 | data[39] = 0x02; 55 | 56 | let mut v6 = ipv6_h::new(); 57 | v6.set(&data).unwrap(); 58 | 59 | let ver: u8 = v6.version.to_owned().load_le(); 60 | assert_eq!(ver, 6); 61 | 62 | let tc: u8 = v6.traffic_class.to_owned().load_le(); 63 | assert_eq!(tc, 127); 64 | 65 | let bv = v6.to_bitvec(); 66 | let readback = bv.into_vec(); 67 | assert_eq!(data.to_vec(), readback); 68 | 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /p4/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use crate::ast::{Lvalue, NameInfo, Type, AST}; 4 | use std::collections::HashMap; 5 | 6 | pub fn resolve_lvalue( 7 | lval: &Lvalue, 8 | ast: &AST, 9 | names: &HashMap, 10 | ) -> Result { 11 | let root = match names.get(lval.root()) { 12 | Some(name_info) => name_info, 13 | None => return Err(format!("{} not found", lval.root())), 14 | }; 15 | let result = match &root.ty { 16 | Type::Bool => root.clone(), 17 | Type::Error => root.clone(), 18 | Type::Bit(_) => root.clone(), 19 | Type::Varbit(_) => root.clone(), 20 | Type::Int(_) => root.clone(), 21 | Type::String => root.clone(), 22 | Type::ExternFunction => root.clone(), 23 | Type::HeaderMethod => root.clone(), 24 | Type::Table => root.clone(), 25 | Type::Void => root.clone(), 26 | Type::List(_) => root.clone(), 27 | Type::State => root.clone(), 28 | Type::Action => root.clone(), 29 | Type::UserDefined(name) => { 30 | if lval.degree() == 1 { 31 | root.clone() 32 | } else if let Some(parent) = ast.get_struct(name) { 33 | resolve_lvalue(&lval.pop_left(), ast, &parent.names())? 34 | } else if let Some(parent) = ast.get_header(name) { 35 | resolve_lvalue(&lval.pop_left(), ast, &parent.names())? 36 | } else if let Some(parent) = ast.get_extern(name) { 37 | resolve_lvalue(&lval.pop_left(), ast, &parent.names())? 38 | } else { 39 | return Err(format!( 40 | "User defined name '{}' does not exist", 41 | name 42 | )); 43 | } 44 | } 45 | }; 46 | Ok(result) 47 | } 48 | -------------------------------------------------------------------------------- /test/src/dload.rs: -------------------------------------------------------------------------------- 1 | p4_macro::use_p4!( 2 | p4 = "test/src/p4/dynamic_router_noaddr_nbr.p4", 3 | pipeline_name = "dload", 4 | ); 5 | 6 | #[test] 7 | fn pipeline_create() -> Result<(), anyhow::Error> { 8 | let p = unsafe { &mut *_dload_pipeline_create(2) }; 9 | 10 | let port: u16 = 47; 11 | let data = [0u8; 500]; 12 | let mut pkt: packet_in = packet_in { 13 | data: &data, 14 | index: 0, 15 | }; 16 | 17 | // the goal is simply not to explode 18 | let result = p.process_packet(port, &mut pkt); 19 | assert!(result.is_empty()); 20 | 21 | Ok(()) 22 | } 23 | 24 | use p4rs::packet_in; 25 | 26 | #[test] 27 | fn dynamic_load() -> Result<(), anyhow::Error> { 28 | // see .cargo/config.toml 29 | let ws = std::env::var("CARGO_WORKSPACE_DIR").unwrap(); 30 | #[cfg(target_os = "macos")] 31 | let path = format!("{}/target/debug/libsidecar_lite.dylib", ws); 32 | #[cfg(not(target_os = "macos"))] 33 | let path = format!("{}/target/debug/libsidecar_lite.so", ws); 34 | 35 | let lib = match unsafe { libloading::Library::new(path) } { 36 | Ok(l) => l, 37 | Err(e) => { 38 | panic!("failed to load p4 program: {}", e); 39 | } 40 | }; 41 | //TODO this is problematic, as we'll not know what these types are under 42 | //normal dynamic loading circumstances. 43 | let func: libloading::Symbol< 44 | unsafe extern "C" fn(u16) -> *mut dyn p4rs::Pipeline, 45 | > = match unsafe { lib.get(b"_main_pipeline_create") } { 46 | Ok(f) => f, 47 | Err(e) => { 48 | panic!("failed to load _main_pipeline_create func: {}", e); 49 | } 50 | }; 51 | 52 | let mut p = unsafe { Box::from_raw(func(2)) }; 53 | 54 | let port: u16 = 47; 55 | let data = [0u8; 500]; 56 | let mut pkt: packet_in = packet_in { 57 | data: &data, 58 | index: 0, 59 | }; 60 | 61 | // the goal is simply not to explode 62 | let result = p.process_packet(port, &mut pkt); 63 | assert!(result.is_empty()); 64 | 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /test/src/basic_router.rs: -------------------------------------------------------------------------------- 1 | use crate::softnpu::{Interface6, RxFrame, SoftNpu}; 2 | use crate::{expect_frames, muffins}; 3 | 4 | p4_macro::use_p4!( 5 | p4 = "p4/examples/codegen/router.p4", 6 | pipeline_name = "basic_router", 7 | ); 8 | 9 | /// 10 | /// ~~~~~~~~~~ 11 | /// ~ ~ 12 | /// ~ p4 ~ 13 | /// ~ ~ 14 | /// ~~~~~~~~~~ 15 | /// | 16 | /// | 17 | /// *=======* *==========* *=======* 18 | /// | | --- ( rx ) --> | | <-- ( rx ) --- | | 19 | /// | phy 1 | | pipeline | | phy 2 | 20 | /// | | <-- ( tx ) --- | | --- ( tx ) --> | | 21 | /// *=======* *==========* *=======* 22 | /// 23 | /// 24 | 25 | #[test] 26 | fn basic_router2() -> Result<(), anyhow::Error> { 27 | let mut npu = SoftNpu::new(2, main_pipeline::new(2), false); 28 | let phy1 = npu.phy(0); 29 | let phy2 = npu.phy(1); 30 | 31 | let if1 = Interface6::new(phy1.clone(), "fd00:1000::1".parse().unwrap()); 32 | let if2 = Interface6::new(phy2.clone(), "fd00:2000::1".parse().unwrap()); 33 | 34 | npu.run(); 35 | 36 | let et = 0x86dd; 37 | let msg = muffins!(); 38 | 39 | if1.send(phy2.mac, if2.addr, msg.0)?; 40 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, et, msg.0)]); 41 | 42 | if2.send(phy1.mac, if1.addr, msg.1)?; 43 | expect_frames!(phy1, &[RxFrame::new(phy2.mac, et, msg.1)]); 44 | 45 | if1.send(phy2.mac, if2.addr, msg.2)?; 46 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, et, msg.2)]); 47 | 48 | if2.send(phy1.mac, if1.addr, msg.3)?; 49 | if2.send(phy1.mac, if1.addr, msg.4)?; 50 | if2.send(phy1.mac, if1.addr, msg.5)?; 51 | expect_frames!( 52 | phy1, 53 | &[ 54 | RxFrame::new(phy2.mac, et, msg.3), 55 | RxFrame::new(phy2.mac, et, msg.4), 56 | RxFrame::new(phy2.mac, et, msg.5), 57 | ] 58 | ); 59 | 60 | assert_eq!(phy1.tx_count(), 2usize); 61 | assert_eq!(phy1.rx_count(), 4usize); 62 | 63 | assert_eq!(phy2.tx_count(), 4usize); 64 | assert_eq!(phy2.rx_count(), 2usize); 65 | 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /test/src/p4/headers.p4: -------------------------------------------------------------------------------- 1 | header sidecar_h { 2 | bit<8> sc_code; 3 | bit<16> sc_ingress; 4 | bit<16> sc_egress; 5 | bit<16> sc_ether_type; 6 | bit<128> sc_payload; 7 | } 8 | 9 | header ethernet_h { 10 | bit<48> dst; 11 | bit<48> src; 12 | bit<16> ether_type; 13 | } 14 | 15 | header vlan_h { 16 | bit<3> pcp; 17 | bit<1> dei; 18 | bit<12> vid; 19 | } 20 | 21 | header ipv6_h { 22 | bit<4> version; 23 | bit<8> traffic_class; 24 | bit<20> flow_label; 25 | bit<16> payload_len; 26 | bit<8> next_hdr; 27 | bit<8> hop_limit; 28 | bit<128> src; 29 | bit<128> dst; 30 | } 31 | 32 | header ipv4_h { 33 | bit<4> version; 34 | bit<4> ihl; 35 | bit<8> diffserv; 36 | bit<16> total_len; 37 | bit<16> identification; 38 | bit<3> flags; 39 | bit<13> frag_offset; 40 | bit<8> ttl; 41 | bit<8> protocol; 42 | bit<16> hdr_checksum; 43 | bit<32> src; 44 | bit<32> dst; 45 | } 46 | 47 | header udp_h { 48 | bit<16> src_port; 49 | bit<16> dst_port; 50 | bit<16> len; 51 | bit<16> checksum; 52 | } 53 | 54 | header tcp_h { 55 | bit<16> src_port; 56 | bit<16> dst_port; 57 | bit<32> seq_no; 58 | bit<32> ack_no; 59 | bit<4> data_offset; 60 | bit<4> res; 61 | bit<8> flags; 62 | bit<16> window; 63 | bit<16> checksum; 64 | bit<16> urgent_ptr; 65 | } 66 | 67 | header icmp_h { 68 | bit<8> type; 69 | bit<8> code; 70 | bit<16> hdr_checksum; 71 | bit<32> data; 72 | } 73 | 74 | header geneve_h { 75 | bit<2> version; 76 | bit<6> opt_len; 77 | bit<1> ctrl; 78 | bit<1> crit; 79 | bit<6> reserved; 80 | bit<16> protocol; 81 | bit<24> vni; 82 | bit<8> reserved2; 83 | } 84 | 85 | header arp_h { 86 | bit<16> hw_type; 87 | bit<16> proto_type; 88 | bit<8> hw_addr_len; 89 | bit<8> proto_addr_len; 90 | bit<16> opcode; 91 | 92 | // In theory, the remaining fields should be 93 | // based on the the two x_len fields. 94 | bit<48> sender_mac; 95 | bit<32> sender_ip; 96 | bit<48> target_mac; 97 | bit<32> target_ip; 98 | } 99 | 100 | header ddm_h { 101 | bit<8> next_header; 102 | bit<8> header_length; 103 | bit<8> version; 104 | bit<1> ack; 105 | bit<7> reserved; 106 | } 107 | 108 | header ddm_element_t { 109 | bit<8> id; 110 | bit<24> timestamp; 111 | } 112 | -------------------------------------------------------------------------------- /p4/examples/bump-in-the-wire.p4: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | SoftNPU( 4 | bump_parser(), 5 | bump_ingress(), 6 | bump_egress(), 7 | bump_deparser() 8 | ) main; 9 | 10 | parser bump_parser( 11 | packet_in packet, 12 | out headers_t hdr, 13 | ) { 14 | 15 | // 16 | // Parse the ethernet header and transition to ipv6 if that is the 17 | // ethertype 18 | // 19 | 20 | state start { 21 | packet.extract(hdr.ethernet); 22 | transition select(hdr.ethernet.ether_type) { 23 | 0x86dd: parse_ipv6; 24 | default: reject; 25 | } 26 | } 27 | 28 | // 29 | // Parse the ipv6 header 30 | // 31 | 32 | state parse_ipv6 { 33 | packet.extract(hdr.ipv6); 34 | verify(hdr.ipv6.version == 4w6, error.Ipv6IncorrectVersion); 35 | transition select(hdr.ipv6.next_header) { 36 | default: accept; 37 | } 38 | } 39 | 40 | } 41 | 42 | control bump_ingress( 43 | inout headers_t hdr, 44 | inout egress_metadata_t ingress_meta, 45 | inout egress_metadata_t egress_meta, 46 | ) { 47 | 48 | action bump_action(bit<8> port) { 49 | hdr.ipv6.hop_limit = hdr.ipv6.hop_limit - 1; 50 | egresss_meta.port = port; 51 | } 52 | 53 | table router { 54 | key = { 55 | hdr.ipv6.dst_addr: lpm; 56 | } 57 | actions = { 58 | bump_action; 59 | } 60 | size = 32; 61 | } 62 | 63 | apply { 64 | router.apply(); 65 | } 66 | 67 | } 68 | 69 | control bump_egress( 70 | inout headers_t hdr, 71 | in egress_metadata_t ingress_meta, 72 | inout egress_metadata_t egress_meta, 73 | ) { 74 | 75 | apply { } 76 | 77 | } 78 | 79 | control bump_deparser( 80 | packet_out packet, 81 | in headers_t hdr, 82 | ) { 83 | 84 | apply { 85 | packet.emit(hdr.ethernet); 86 | packet.emit(hdr.ipv6); 87 | } 88 | 89 | } 90 | 91 | header ethernet_t { 92 | EthernetAddress dst_addr; 93 | EthernetAddress src_addr; 94 | bit<16> ether_type; 95 | } 96 | 97 | header ipv6_t { 98 | bit<4> version; 99 | bit<6> ds_field; 100 | bit<2> ecn; 101 | bit<20> flow_label; 102 | bit<16> len; 103 | bit<8> next_header; 104 | bit<8> hop_limit; 105 | IPv6Address src_addr; 106 | IPv6Address dst_addr; 107 | } 108 | 109 | struct headers_t { 110 | ethernet_t ethernet; 111 | ipv6_t ipv6; 112 | } 113 | -------------------------------------------------------------------------------- /book/code/src/bin/vlan-switch.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_update)] 2 | use tests::expect_frames; 3 | use tests::softnpu::{RxFrame, SoftNpu, TxFrame}; 4 | 5 | const NUM_PORTS: u16 = 2; 6 | 7 | p4_macro::use_p4!( 8 | p4 = "book/code/src/bin/vlan-switch.p4", 9 | pipeline_name = "vlan_switch" 10 | ); 11 | 12 | fn main() -> Result<(), anyhow::Error> { 13 | let mut pipeline = main_pipeline::new(NUM_PORTS); 14 | 15 | let m1 = [0x33, 0x33, 0x33, 0x33, 0x33, 0x33]; 16 | let m2 = [0x44, 0x44, 0x44, 0x44, 0x44, 0x44]; 17 | let m3 = [0x55, 0x55, 0x55, 0x55, 0x55, 0x55]; 18 | 19 | init_tables(&mut pipeline, m1, m2); 20 | run_test(pipeline, m2, m3) 21 | } 22 | 23 | fn init_tables(pipeline: &mut main_pipeline, m1: [u8; 6], m2: [u8; 6]) { 24 | // add static forwarding entries 25 | pipeline.add_ingress_fwd_fib_entry("forward", &m1, &0u16.to_le_bytes(), 0); 26 | pipeline.add_ingress_fwd_fib_entry("forward", &m2, &1u16.to_le_bytes(), 0); 27 | 28 | // port 0 vlan 47 29 | pipeline.add_ingress_vlan_port_vlan_entry( 30 | "filter", 31 | 0u16.to_le_bytes().as_ref(), 32 | 47u16.to_le_bytes().as_ref(), 33 | 0, 34 | ); 35 | 36 | // sanity check the table 37 | let x = pipeline.get_ingress_vlan_port_vlan_entries(); 38 | println!("{:#?}", x); 39 | 40 | // port 1 vlan 47 41 | pipeline.add_ingress_vlan_port_vlan_entry( 42 | "filter", 43 | 1u16.to_le_bytes().as_ref(), 44 | 47u16.to_le_bytes().as_ref(), 45 | 0, 46 | ); 47 | } 48 | 49 | fn run_test( 50 | pipeline: main_pipeline, 51 | m2: [u8; 6], 52 | m3: [u8; 6], 53 | ) -> Result<(), anyhow::Error> { 54 | // create and run the softnpu instance 55 | let mut npu = SoftNpu::new(NUM_PORTS.into(), pipeline, false); 56 | let phy1 = npu.phy(0); 57 | let phy2 = npu.phy(1); 58 | npu.run(); 59 | 60 | // send a packet we expect to make it through 61 | phy1.send(&[TxFrame::newv(m2, 0x8100, b"blueberry", 47)])?; 62 | expect_frames!(phy2, &[RxFrame::newv(phy1.mac, 0x8100, b"blueberry", 47)]); 63 | 64 | // send 3 packets, we expect the first 2 to get filtered by vlan rules 65 | phy1.send(&[TxFrame::newv(m2, 0x8100, b"poppyseed", 74)])?; // 74 != 47 66 | phy1.send(&[TxFrame::new(m2, 0, b"banana")])?; // no tag 67 | phy1.send(&[TxFrame::newv(m2, 0x8100, b"muffin", 47)])?; 68 | phy1.send(&[TxFrame::newv(m3, 0x8100, b"nut", 47)])?; // no forwarding entry 69 | expect_frames!(phy2, &[RxFrame::newv(phy1.mac, 0x8100, b"muffin", 47)]); 70 | 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /book/code/src/bin/headers.p4: -------------------------------------------------------------------------------- 1 | header sidecar_h { 2 | bit<8> sc_code; 3 | bit<16> sc_ingress; 4 | bit<16> sc_egress; 5 | bit<16> sc_ether_type; 6 | bit<128> sc_payload; 7 | } 8 | 9 | header ethernet_h { 10 | bit<48> dst; 11 | bit<48> src; 12 | bit<16> ether_type; 13 | } 14 | 15 | header vlan_h { 16 | bit<3> pcp; 17 | bit<1> dei; 18 | bit<12> vid; 19 | bit<16> ether_type; 20 | } 21 | 22 | header ipv6_h { 23 | bit<4> version; 24 | bit<8> traffic_class; 25 | bit<20> flow_label; 26 | bit<16> payload_len; 27 | bit<8> next_hdr; 28 | bit<8> hop_limit; 29 | bit<128> src; 30 | bit<128> dst; 31 | } 32 | 33 | header ipv4_h { 34 | bit<4> version; 35 | bit<4> ihl; 36 | bit<8> diffserv; 37 | bit<16> total_len; 38 | bit<16> identification; 39 | bit<3> flags; 40 | bit<13> frag_offset; 41 | bit<8> ttl; 42 | bit<8> protocol; 43 | bit<16> hdr_checksum; 44 | bit<32> src; 45 | bit<32> dst; 46 | } 47 | 48 | header udp_h { 49 | bit<16> src_port; 50 | bit<16> dst_port; 51 | bit<16> len; 52 | bit<16> checksum; 53 | } 54 | 55 | header tcp_h { 56 | bit<16> src_port; 57 | bit<16> dst_port; 58 | bit<32> seq_no; 59 | bit<32> ack_no; 60 | bit<4> data_offset; 61 | bit<4> res; 62 | bit<8> flags; 63 | bit<16> window; 64 | bit<16> checksum; 65 | bit<16> urgent_ptr; 66 | } 67 | 68 | header icmp_h { 69 | bit<8> type; 70 | bit<8> code; 71 | bit<16> hdr_checksum; 72 | bit<32> data; 73 | } 74 | 75 | header geneve_h { 76 | bit<2> version; 77 | bit<6> opt_len; 78 | bit<1> ctrl; 79 | bit<1> crit; 80 | bit<6> reserved; 81 | bit<16> protocol; 82 | bit<24> vni; 83 | bit<8> reserved2; 84 | } 85 | 86 | header arp_h { 87 | bit<16> hw_type; 88 | bit<16> proto_type; 89 | bit<8> hw_addr_len; 90 | bit<8> proto_addr_len; 91 | bit<16> opcode; 92 | 93 | // In theory, the remaining fields should be 94 | // based on the the two x_len fields. 95 | bit<48> sender_mac; 96 | bit<32> sender_ip; 97 | bit<48> target_mac; 98 | bit<32> target_ip; 99 | } 100 | 101 | header ddm_h { 102 | bit<8> next_header; 103 | bit<8> header_length; 104 | bit<8> version; 105 | bit<1> ack; 106 | bit<7> reserved; 107 | } 108 | 109 | header ddm_element_t { 110 | bit<8> id; 111 | bit<24> timestamp; 112 | } 113 | -------------------------------------------------------------------------------- /codegen/rust/src/parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use crate::{ 4 | rust_type, 5 | statement::{StatementContext, StatementGenerator}, 6 | Context, 7 | }; 8 | use p4::ast::{Direction, Parser, State, AST}; 9 | use p4::hlir::Hlir; 10 | use proc_macro2::TokenStream; 11 | use quote::{format_ident, quote}; 12 | 13 | pub(crate) struct ParserGenerator<'a> { 14 | ast: &'a AST, 15 | ctx: &'a mut Context, 16 | hlir: &'a Hlir, 17 | } 18 | 19 | impl<'a> ParserGenerator<'a> { 20 | pub(crate) fn new( 21 | ast: &'a AST, 22 | hlir: &'a Hlir, 23 | ctx: &'a mut Context, 24 | ) -> Self { 25 | Self { ast, hlir, ctx } 26 | } 27 | 28 | pub(crate) fn generate(&mut self) { 29 | for parser in &self.ast.parsers { 30 | for state in &parser.states { 31 | self.generate_state_function(parser, state); 32 | } 33 | } 34 | } 35 | 36 | pub(crate) fn generate_state_function( 37 | &mut self, 38 | parser: &Parser, 39 | state: &State, 40 | ) -> (TokenStream, TokenStream) { 41 | let function_name = format_ident!("{}_{}", parser.name, state.name); 42 | 43 | let mut args = Vec::new(); 44 | for arg in &parser.parameters { 45 | let name = format_ident!("{}", arg.name); 46 | let typename = rust_type(&arg.ty); 47 | match arg.direction { 48 | Direction::Out | Direction::InOut => { 49 | args.push(quote! { #name: &mut #typename }); 50 | } 51 | _ => args.push(quote! { #name: &mut #typename }), 52 | }; 53 | } 54 | 55 | let body = self.generate_state_function_body(parser, state); 56 | 57 | let signature = quote! { 58 | (#(#args),*) -> bool 59 | }; 60 | 61 | let function = quote! { 62 | pub fn #function_name #signature { 63 | #body 64 | } 65 | }; 66 | 67 | self.ctx 68 | .functions 69 | .insert(function_name.to_string(), function); 70 | 71 | (signature, body) 72 | } 73 | 74 | fn generate_state_function_body( 75 | &mut self, 76 | parser: &Parser, 77 | state: &State, 78 | ) -> TokenStream { 79 | let sg = StatementGenerator::new( 80 | self.ast, 81 | self.hlir, 82 | StatementContext::Parser(parser), 83 | ); 84 | let mut names = parser.names(); 85 | sg.generate_block(&state.statements, &mut names) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /book/code/src/bin/vlan-switch.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct headers_t { 6 | ethernet_h eth; 7 | vlan_h vlan; 8 | } 9 | 10 | parser parse ( 11 | packet_in pkt, 12 | out headers_t h, 13 | inout ingress_metadata_t ingress, 14 | ) { 15 | state start { 16 | pkt.extract(h.eth); 17 | if (h.eth.ether_type == 16w0x8100) { transition vlan; } 18 | transition accept; 19 | } 20 | state vlan { 21 | pkt.extract(h.vlan); 22 | transition accept; 23 | } 24 | } 25 | 26 | control vlan( 27 | in bit<16> port, 28 | in bit<12> vid, 29 | out bool match, 30 | ) { 31 | action no_vid_for_port() { 32 | match = true; 33 | } 34 | 35 | action filter(bit<12> port_vid) { 36 | if (port_vid == vid) { match = true; } 37 | } 38 | 39 | table port_vlan { 40 | key = { port: exact; } 41 | actions = { no_vid_for_port; filter; } 42 | default_action = no_vid_for_port; 43 | } 44 | 45 | apply { port_vlan.apply(); } 46 | } 47 | 48 | control forward( 49 | inout headers_t hdr, 50 | inout ingress_metadata_t ingress, 51 | inout egress_metadata_t egress, 52 | ) { 53 | action drop() { ingress.drop = true; } 54 | action forward(bit<16> port) { egress.port = port; } 55 | 56 | table fib { 57 | key = { hdr.eth.dst: exact; } 58 | actions = { drop; forward; } 59 | default_action = drop; 60 | } 61 | 62 | apply { fib.apply(); } 63 | } 64 | 65 | 66 | control ingress( 67 | inout headers_t hdr, 68 | inout ingress_metadata_t ingress, 69 | inout egress_metadata_t egress, 70 | ) { 71 | vlan() vlan; 72 | forward() fwd; 73 | 74 | apply { 75 | bit<12> vid = 12w0; 76 | if (hdr.vlan.isValid()) { 77 | vid = hdr.vlan.vid; 78 | } 79 | 80 | // check vlan on ingress 81 | bool vlan_ok = false; 82 | vlan.apply(ingress.port, vid, vlan_ok); 83 | if (vlan_ok == false) { 84 | ingress.drop = true; 85 | return; 86 | } 87 | 88 | // apply switch forwarding logic 89 | fwd.apply(hdr, ingress, egress); 90 | 91 | // check vlan on egress 92 | vlan.apply(egress.port, vid, vlan_ok); 93 | if (vlan_ok == false) { 94 | ingress.drop = true; 95 | return; 96 | } 97 | } 98 | } 99 | 100 | control egress( 101 | inout headers_t hdr, 102 | inout ingress_metadata_t ingress, 103 | inout egress_metadata_t egress, 104 | ) { 105 | 106 | } 107 | 108 | SoftNPU( 109 | parse(), 110 | ingress(), 111 | egress() 112 | ) main; 113 | -------------------------------------------------------------------------------- /p4/examples/codegen/router.p4: -------------------------------------------------------------------------------- 1 | // XXX import from core.p4 2 | extern packet_in { 3 | void extract(out T headerLvalue); 4 | void extract(out T variableSizeHeader, in bit<32> varFieldSizeBits); 5 | T lookahead(); 6 | bit<32> length(); // This method may be unavailable in some architectures 7 | void advance(bit<32> bits); 8 | } 9 | 10 | // XXX import from core.p4 11 | extern packet_out { 12 | void emit(in T hdr); 13 | } 14 | 15 | // XXX import from softnpu.p4 16 | struct ingress_metadata_t { 17 | bit<16> port; 18 | bool nat; 19 | bit<16> nat_id; 20 | bool drop; 21 | } 22 | 23 | struct egress_metadata_t { 24 | bit<16> port; 25 | bit<128> nexthop_v6; 26 | bit<32> nexthop_v4; 27 | bool drop; 28 | bool broadcast; 29 | } 30 | 31 | SoftNPU( 32 | parse(), 33 | ingress(), 34 | egress() 35 | ) main; 36 | 37 | struct headers_t { 38 | ethernet_t ethernet; 39 | ipv6_t ipv6; 40 | } 41 | 42 | header ethernet_t { 43 | bit<48> dst; 44 | bit<48> src; 45 | bit<16> ether_type; 46 | } 47 | 48 | header ipv6_t { 49 | bit<4> version; 50 | bit<8> traffic_class; 51 | bit<20> flow_label; 52 | bit<16> payload_len; 53 | bit<8> next_hdr; 54 | bit<8> hop_limit; 55 | bit<128> src; 56 | bit<128> dst; 57 | } 58 | 59 | parser parse( 60 | packet_in pkt, 61 | out headers_t headers, 62 | inout ingress_metadata_t ingress, 63 | ){ 64 | state start { 65 | pkt.extract(headers.ethernet); 66 | pkt.extract(headers.ipv6); 67 | transition accept; 68 | } 69 | } 70 | 71 | control ingress( 72 | inout headers_t hdr, 73 | inout ingress_metadata_t ingress, 74 | inout egress_metadata_t egress, 75 | ) { 76 | 77 | action drop() { } 78 | 79 | action forward(bit<16> port) { 80 | egress.port = port; 81 | } 82 | 83 | table router { 84 | key = { 85 | hdr.ipv6.dst: lpm; 86 | } 87 | actions = { 88 | drop; 89 | forward; 90 | } 91 | default_action = drop; 92 | const entries = { 93 | 94 | // fd00:1000::/24 95 | 128w0xfd001000000000000000000000000000 &&& 96 | 128w0xffffff00000000000000000000000000 : 97 | forward(16w0); 98 | 99 | // fd00:2000::/24 100 | 128w0xfd002000000000000000000000000000 &&& 101 | 128w0xffffff00000000000000000000000000 : 102 | forward(16w1); 103 | 104 | } 105 | } 106 | 107 | apply { 108 | router.apply(); 109 | } 110 | 111 | } 112 | 113 | control egress( 114 | inout headers_t hdr, 115 | inout ingress_metadata_t ingress, 116 | inout egress_metadata_t egress, 117 | ) { 118 | 119 | } 120 | -------------------------------------------------------------------------------- /test/src/disag_router.rs: -------------------------------------------------------------------------------- 1 | use crate::softnpu::{Interface6, RxFrame, SoftNpu}; 2 | use crate::{expect_frames, muffins}; 3 | use std::net::Ipv6Addr; 4 | 5 | p4_macro::use_p4!(p4 = "test/src/p4/router.p4", pipeline_name = "disag",); 6 | 7 | /// 8 | /// ~~~~~~~~~~ 9 | /// ~ ~ 10 | /// ~ p4 ~ * *=======* 11 | /// ~ ~ | | | 12 | /// ~~~~~~~~~~ |---| phy 1 | 13 | /// | | | | 14 | /// | | *=======* 15 | /// *==========* | *=======* 16 | /// | | <-- ( rx ) --- | | | 17 | /// | pipeline | |---| phy 2 | 18 | /// | | --- ( tx ) --> | | | 19 | /// *==========* | *=======* 20 | /// tx | | | *=======* 21 | /// | | | | | 22 | /// | | rx |---| phy 3 | 23 | /// *========* | | | 24 | /// | | * *=======* 25 | /// | | 26 | /// | CPU | 27 | /// *========* 28 | /// 29 | 30 | #[test] 31 | fn disag_router() -> Result<(), anyhow::Error> { 32 | let mut npu = SoftNpu::new(4, main_pipeline::new(4), true); 33 | let cpu = npu.phy(0); 34 | let phy1 = npu.phy(1); 35 | let phy2 = npu.phy(2); 36 | let phy3 = npu.phy(3); 37 | 38 | let if1 = Interface6::new(phy1.clone(), "fd00:1000::1".parse().unwrap()); 39 | let if2 = Interface6::new(phy2.clone(), "fd00:2000::1".parse().unwrap()); 40 | let mut if3 = Interface6::new( 41 | cpu.clone(), 42 | "fe80::aae1:deff:fe01:701c".parse().unwrap(), 43 | ); 44 | if3.sc_egress = 1; 45 | let mut if4 = Interface6::new( 46 | cpu.clone(), 47 | "fe80::aae1:deff:fe01:701d".parse().unwrap(), 48 | ); 49 | if4.sc_egress = 3; 50 | let mc1: Ipv6Addr = "ff02::1:ff01:701c".parse().unwrap(); 51 | 52 | npu.run(); 53 | 54 | let msg = muffins!(); 55 | 56 | if1.send(phy2.mac, if2.addr, msg.0)?; 57 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0x86dd, msg.0)]); 58 | 59 | // multicast should go to the CPU port 60 | if2.send(phy1.mac, mc1, msg.1)?; 61 | expect_frames!(cpu, &[RxFrame::new(phy2.mac, 0x0901, msg.1)]); 62 | 63 | // link-local should go to the CPU port 64 | if1.send(phy2.mac, if3.addr, msg.2)?; 65 | expect_frames!(cpu, &[RxFrame::new(phy1.mac, 0x0901, msg.2)]); 66 | 67 | // link-local should go to the CPU port 68 | if2.send(phy1.mac, if4.addr, msg.3)?; 69 | expect_frames!(cpu, &[RxFrame::new(phy2.mac, 0x0901, msg.3)]); 70 | 71 | // from the CPU port to phy1 72 | if3.send(phy1.mac, if1.addr, msg.4)?; 73 | expect_frames!(phy1, &[RxFrame::new(cpu.mac, 0x86dd, msg.4)]); 74 | 75 | // from the CPU port to phy1 76 | if4.send(phy2.mac, if2.addr, msg.5)?; 77 | expect_frames!(phy3, &[RxFrame::new(cpu.mac, 0x86dd, msg.5)]); 78 | 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /test/src/hub.rs: -------------------------------------------------------------------------------- 1 | use crate::softnpu::{RxFrame, SoftNpu, TxFrame}; 2 | use crate::{expect_frames, muffins}; 3 | 4 | p4_macro::use_p4!(p4 = "test/src/p4/hub.p4", pipeline_name = "hub2"); 5 | 6 | /// 7 | /// ~~~~~~~~~~ 8 | /// ~ ~ 9 | /// ~ p4 ~ 10 | /// ~ ~ 11 | /// ~~~~~~~~~~ 12 | /// | 13 | /// | 14 | /// *=======* *==========* *=======* 15 | /// | | --- ( rx ) --> | | <-- ( rx ) --- | | 16 | /// | phy 1 | | pipeline | | phy 3 | 17 | /// | | <-- ( tx ) --- | | --- ( tx ) --> | | 18 | /// *=======* *==========* *=======* 19 | /// tx | | 20 | /// | | 21 | /// | | rx 22 | /// *========* 23 | /// | | 24 | /// | | 25 | /// | phy2 | 26 | /// *========* 27 | /// 28 | #[test] 29 | fn hub2() -> Result<(), anyhow::Error> { 30 | let mut npu = SoftNpu::new(3, main_pipeline::new(3), false); 31 | let phy1 = npu.phy(0); 32 | let phy2 = npu.phy(1); 33 | let phy3 = npu.phy(2); 34 | 35 | npu.run(); 36 | 37 | let et = 0; 38 | let msg = muffins!(); 39 | 40 | phy1.send(&[TxFrame::new(phy2.mac, et, msg.0)])?; 41 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, et, msg.0)]); 42 | expect_frames!(phy3, &[RxFrame::new(phy1.mac, et, msg.0)]); 43 | 44 | phy2.send(&[TxFrame::new(phy1.mac, et, msg.1)])?; 45 | expect_frames!(phy1, &[RxFrame::new(phy2.mac, et, msg.1)]); 46 | expect_frames!(phy3, &[RxFrame::new(phy2.mac, et, msg.1)]); 47 | 48 | phy1.send(&[TxFrame::new(phy2.mac, et, msg.2)])?; 49 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, et, msg.2)]); 50 | expect_frames!(phy3, &[RxFrame::new(phy1.mac, et, msg.2)]); 51 | 52 | phy2.send(&[TxFrame::new(phy1.mac, et, msg.3)])?; 53 | phy2.send(&[TxFrame::new(phy1.mac, et, msg.4)])?; 54 | phy2.send(&[TxFrame::new(phy1.mac, et, msg.5)])?; 55 | expect_frames!( 56 | phy1, 57 | &[ 58 | RxFrame::new(phy2.mac, et, msg.3), 59 | RxFrame::new(phy2.mac, et, msg.4), 60 | RxFrame::new(phy2.mac, et, msg.5), 61 | ] 62 | ); 63 | expect_frames!( 64 | phy3, 65 | &[ 66 | RxFrame::new(phy2.mac, et, msg.3), 67 | RxFrame::new(phy2.mac, et, msg.4), 68 | RxFrame::new(phy2.mac, et, msg.5), 69 | ] 70 | ); 71 | 72 | assert_eq!(phy1.tx_count(), 2usize); 73 | assert_eq!(phy1.rx_count(), 4usize); 74 | 75 | assert_eq!(phy2.tx_count(), 4usize); 76 | assert_eq!(phy2.rx_count(), 2usize); 77 | 78 | assert_eq!(phy3.tx_count(), 0usize); 79 | assert_eq!(phy3.rx_count(), 6usize); 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /test/src/headers.rs: -------------------------------------------------------------------------------- 1 | //use num::bigint::BigUint; 2 | p4_macro::use_p4!("p4/examples/codegen/ipv6_header.p4"); 3 | 4 | #[test] 5 | fn ipv6_header_read_write() { 6 | 7 | /* TODO - bitvec interface 8 | 9 | // 1 0 10 | // .... .... 11 | // |10100111|11110110| 12 | // | 13 | // ........ 14 | // |00001010|01111111| >> 4 15 | // 16 | // |a7|f6| 17 | // 1 0 18 | // ........ 19 | // |0a|7f| >> 4 20 | // 1 0 21 | // 22 | // 1 2 3 23 | // |0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| 24 | // |---------------------------------------------------------------| 25 | // | 0 | 1 | 2 | 3 | 26 | // |---------------------------------------------------------------| 27 | // | ver | traf cls | flow lbl | 28 | // |---------------------------------------------------------------| 29 | // |0 1 1 0 1 1 1 1 1 1 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| 30 | // 31 | // 0 1 32 | // |f6|a7| 33 | // 34 | // 8 0 35 | // 0b11111111 36 | // 37 | // 38 | 39 | let mut data = [0u8; 40]; 40 | // version = 6 41 | // traffic class = 127 (0x7f) 42 | // flow label = 699050 (0xaaaaa) 43 | //data[0] = 0b0110_0111; 44 | data[0] = 0b1111_0110; 45 | //data[1] = 0b1111_1010; 46 | data[1] = 0b1010_0111; 47 | data[2] = 0b1010_1010; 48 | data[3] = 0b1010_1010; 49 | // payload len 18247 (0x4747) 50 | data[4] = 47; 51 | data[5] = 47; 52 | // next_hdr 53 | data[6] = 99; 54 | // hop_limit 55 | data[7] = 10; 56 | // src fd00::1 57 | data[8] = 0xfd; 58 | data[9] = 0x00; 59 | data[10] = 0x00; 60 | data[11] = 0x00; 61 | data[12] = 0x00; 62 | data[13] = 0x00; 63 | data[14] = 0x00; 64 | data[15] = 0x00; 65 | data[16] = 0x00; 66 | data[17] = 0x00; 67 | data[18] = 0x00; 68 | data[19] = 0x00; 69 | data[20] = 0x00; 70 | data[21] = 0x00; 71 | data[22] = 0x00; 72 | data[23] = 0x01; 73 | // dst fd00::2 74 | data[24] = 0xfd; 75 | data[25] = 0x00; 76 | data[26] = 0x00; 77 | data[27] = 0x00; 78 | data[28] = 0x00; 79 | data[29] = 0x00; 80 | data[30] = 0x00; 81 | data[31] = 0x00; 82 | data[32] = 0x00; 83 | data[33] = 0x00; 84 | data[34] = 0x00; 85 | data[35] = 0x00; 86 | data[36] = 0x00; 87 | data[37] = 0x00; 88 | data[38] = 0x00; 89 | data[39] = 0x02; 90 | 91 | let mut v6 = ipv6_t::new(); 92 | v6.set(&mut data).unwrap(); 93 | 94 | let version: BigUint = v6.version.unwrap().to_owned().into(); 95 | println!("version = {}", version); 96 | assert_eq!(version, BigUint::from(6u8)); 97 | 98 | let traffic_class: BigUint = v6.traffic_class.unwrap().to_owned().into(); 99 | println!("traffic class = {}", traffic_class); 100 | assert_eq!(traffic_class, BigUint::from(127u8)); 101 | */ 102 | } 103 | -------------------------------------------------------------------------------- /test/src/range.rs: -------------------------------------------------------------------------------- 1 | use crate::expect_frames; 2 | use crate::softnpu::{Interface4, RxFrame, SoftNpu}; 3 | use std::net::Ipv4Addr; 4 | 5 | p4_macro::use_p4!(p4 = "test/src/p4/range.p4", pipeline_name = "range",); 6 | 7 | fn v4_range_key(addr: Ipv4Addr) -> [u8; 4] { 8 | let k: u32 = addr.into(); 9 | k.to_le_bytes() 10 | } 11 | 12 | #[test] 13 | fn range() -> Result<(), anyhow::Error> { 14 | let mut pipeline = main_pipeline::new(4); 15 | 16 | // 17 | // add table entries 18 | // 19 | 20 | let begin = v4_range_key("2.0.0.0".parse().unwrap()); 21 | let end = v4_range_key("4.0.0.0".parse().unwrap()); 22 | let mut buf = begin.to_vec(); 23 | buf.extend_from_slice(&end); 24 | 25 | pipeline.add_ingress_power_ranger_entry( 26 | "forward", 27 | &buf, 28 | &0u16.to_le_bytes(), 29 | 0, 30 | ); 31 | 32 | let begin = v4_range_key("6.0.0.0".parse().unwrap()); 33 | let end = v4_range_key("8.0.0.0".parse().unwrap()); 34 | let mut buf = begin.to_vec(); 35 | buf.extend_from_slice(&end); 36 | 37 | pipeline.add_ingress_power_ranger_entry( 38 | "forward", 39 | &buf, 40 | &1u16.to_le_bytes(), 41 | 0, 42 | ); 43 | 44 | let begin = v4_range_key("10.0.0.0".parse().unwrap()); 45 | let end = v4_range_key("12.0.0.0".parse().unwrap()); 46 | let mut buf = begin.to_vec(); 47 | buf.extend_from_slice(&end); 48 | 49 | pipeline.add_ingress_power_ranger_entry( 50 | "forward", 51 | &buf, 52 | &2u16.to_le_bytes(), 53 | 0, 54 | ); 55 | 56 | let begin = v4_range_key("14.0.0.0".parse().unwrap()); 57 | let end = v4_range_key("16.0.0.0".parse().unwrap()); 58 | let mut buf = begin.to_vec(); 59 | buf.extend_from_slice(&end); 60 | 61 | pipeline.add_ingress_power_ranger_entry( 62 | "forward", 63 | &buf, 64 | &3u16.to_le_bytes(), 65 | 0, 66 | ); 67 | 68 | // 69 | // run program 70 | // 71 | 72 | let mut npu = SoftNpu::new(4, pipeline, false); 73 | let phy0 = npu.phy(0); 74 | let phy1 = npu.phy(1); 75 | let phy2 = npu.phy(2); 76 | let phy3 = npu.phy(3); 77 | 78 | let if0 = Interface4::new(phy0.clone(), "1.0.0.1".parse().unwrap()); 79 | let if1 = Interface4::new(phy1.clone(), "1.0.0.2".parse().unwrap()); 80 | let if2 = Interface4::new(phy2.clone(), "1.0.0.3".parse().unwrap()); 81 | let if3 = Interface4::new(phy3.clone(), "1.0.0.4".parse().unwrap()); 82 | let msg = b"muffins!"; 83 | 84 | npu.run(); 85 | 86 | if1.send(phy1.mac, "11.0.0.0".parse().unwrap(), msg)?; 87 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0x0800, msg)]); 88 | 89 | if0.send(phy0.mac, "14.0.0.1".parse().unwrap(), msg)?; 90 | expect_frames!(phy3, &[RxFrame::new(phy0.mac, 0x0800, msg)]); 91 | 92 | if3.send(phy3.mac, "7.7.7.7".parse().unwrap(), msg)?; 93 | expect_frames!(phy1, &[RxFrame::new(phy3.mac, 0x0800, msg)]); 94 | 95 | if2.send(phy2.mac, "3.4.7.7".parse().unwrap(), msg)?; 96 | expect_frames!(phy0, &[RxFrame::new(phy2.mac, 0x0800, msg)]); 97 | 98 | Ok(()) 99 | } 100 | -------------------------------------------------------------------------------- /x4c/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use anyhow::{anyhow, Result}; 4 | use clap::Parser; 5 | use p4::check::Diagnostics; 6 | use p4::{ 7 | ast::AST, check, error, error::SemanticError, lexer, parser, preprocessor, 8 | }; 9 | use std::fs; 10 | use std::path::Path; 11 | use std::sync::Arc; 12 | 13 | #[derive(Parser)] 14 | #[clap(version = "0.1")] 15 | pub struct Opts { 16 | /// Show parsed lexical tokens. 17 | #[clap(long)] 18 | pub show_tokens: bool, 19 | 20 | /// Show parsed abstract syntax tree. 21 | #[clap(long)] 22 | pub show_ast: bool, 23 | 24 | /// Show parsed preprocessor info. 25 | #[clap(long)] 26 | pub show_pre: bool, 27 | 28 | /// Show high-level intermediate representation info. 29 | #[clap(long)] 30 | pub show_hlir: bool, 31 | 32 | /// File to compile. 33 | pub filename: String, 34 | 35 | /// What target to generate code for. 36 | #[clap(value_enum, default_value_t = Target::Rust)] 37 | pub target: Target, 38 | 39 | /// Just check code, do not compile. 40 | #[clap(long)] 41 | pub check: bool, 42 | 43 | /// Filename to write generated code to. 44 | #[clap(short, long, default_value = "out.rs")] 45 | pub out: String, 46 | } 47 | 48 | #[derive(clap::ValueEnum, Clone)] 49 | pub enum Target { 50 | Rust, 51 | RedHawk, 52 | Docs, 53 | } 54 | 55 | pub fn process_file( 56 | filename: Arc, 57 | ast: &mut AST, 58 | opts: &Opts, 59 | ) -> Result<()> { 60 | let contents = fs::read_to_string(&*filename) 61 | .map_err(|e| anyhow!("read input: {}: {}", &*filename, e))?; 62 | 63 | let ppr = preprocessor::run(&contents, filename.clone())?; 64 | if opts.show_pre { 65 | println!("{:#?}", ppr.elements); 66 | } 67 | 68 | for included in &ppr.elements.includes { 69 | let path = Path::new(included); 70 | if !path.is_absolute() { 71 | let parent = Path::new(&*filename).parent().unwrap(); 72 | let joined = parent.join(included); 73 | process_file( 74 | Arc::new(joined.to_str().unwrap().to_string()), 75 | ast, 76 | opts, 77 | )? 78 | } else { 79 | process_file(Arc::new(included.clone()), ast, opts)? 80 | } 81 | } 82 | 83 | let lines: Vec<&str> = ppr.lines.iter().map(|x| x.as_str()).collect(); 84 | 85 | let mut lxr = lexer::Lexer::new(lines.clone(), filename); 86 | lxr.show_tokens = opts.show_tokens; 87 | 88 | let mut psr = parser::Parser::new(lxr); 89 | psr.run(ast)?; 90 | if opts.show_ast { 91 | println!("{:#?}", ast); 92 | } 93 | 94 | let (hlir, diags) = check::all(ast); 95 | check(&lines, &diags)?; 96 | 97 | if opts.show_hlir { 98 | println!("{:#?}", hlir); 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | fn check(lines: &[&str], diagnostics: &Diagnostics) -> Result<()> { 105 | let errors = diagnostics.errors(); 106 | if !errors.is_empty() { 107 | let mut err = Vec::new(); 108 | for e in errors { 109 | err.push(SemanticError { 110 | at: e.token.clone(), 111 | message: e.message.clone(), 112 | source: lines[e.token.line].into(), 113 | }); 114 | } 115 | Err(error::Error::Semantic(err))?; 116 | } 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /test/src/decap.rs: -------------------------------------------------------------------------------- 1 | use crate::softnpu::{SoftNpu, TxFrame}; 2 | use pnet::packet::ethernet::EtherType; 3 | use pnet::packet::ethernet::MutableEthernetPacket; 4 | use pnet::packet::ip::IpNextHeaderProtocol; 5 | use pnet::packet::ipv4::{Ipv4Packet, MutableIpv4Packet}; 6 | use pnet::packet::ipv6::MutableIpv6Packet; 7 | use pnet::packet::udp::{MutableUdpPacket, UdpPacket}; 8 | use pnet::packet::Packet; 9 | use pnet::util::MacAddr; 10 | use std::net::{Ipv4Addr, Ipv6Addr}; 11 | 12 | p4_macro::use_p4!(p4 = "test/src/p4/decap.p4", pipeline_name = "decap",); 13 | 14 | #[test] 15 | fn geneve_decap() -> Result<(), anyhow::Error> { 16 | let pipeline = main_pipeline::new(2); 17 | let mut npu = SoftNpu::new(2, pipeline, false); 18 | let phy0 = npu.phy(0); 19 | let phy1 = npu.phy(1); 20 | 21 | npu.run(); 22 | 23 | let src: Ipv6Addr = "fd00::2".parse().unwrap(); 24 | let dst: Ipv6Addr = "fd00::1".parse().unwrap(); 25 | let inner_src: Ipv4Addr = "10.0.0.1".parse().unwrap(); 26 | let inner_dst: Ipv4Addr = "8.8.8.8".parse().unwrap(); 27 | 28 | /* 29 | * Create a header stack 30 | * eth 31 | * ipv6 32 | * udp 33 | * geneve 34 | * inner_eth 35 | * inner_ipv4 36 | * inner_udp 37 | */ 38 | 39 | // start from bottom up 40 | let payload = b"muffins"; 41 | let mut n = 8 + payload.len(); 42 | let mut inner_udp_data: Vec = vec![0; n]; 43 | 44 | let mut inner_udp = MutableUdpPacket::new(&mut inner_udp_data).unwrap(); 45 | inner_udp.set_source(47); 46 | inner_udp.set_destination(74); 47 | inner_udp.set_payload(payload); 48 | inner_udp.set_checksum(99); 49 | 50 | n += 20; 51 | let mut inner_ip_data: Vec = vec![0; n]; 52 | 53 | let mut inner_ip = MutableIpv4Packet::new(&mut inner_ip_data).unwrap(); 54 | inner_ip.set_version(4); 55 | inner_ip.set_source(inner_src); 56 | inner_ip.set_header_length(5); 57 | inner_ip.set_destination(inner_dst); 58 | inner_ip.set_next_level_protocol(IpNextHeaderProtocol::new(17)); 59 | inner_ip.set_total_length(20 + inner_udp_data.len() as u16); 60 | inner_ip.set_payload(&inner_udp_data); 61 | 62 | n += 14; 63 | let mut eth_data: Vec = vec![0; n]; 64 | let mut eth = MutableEthernetPacket::new(&mut eth_data).unwrap(); 65 | eth.set_destination(MacAddr::new(0x11, 0x11, 0x11, 0x22, 0x22, 0x22)); 66 | eth.set_source(MacAddr::new(0x33, 0x33, 0x33, 0x44, 0x44, 0x44)); 67 | eth.set_ethertype(EtherType(0x0800)); 68 | eth.set_payload(&inner_ip_data); 69 | 70 | n += 8; 71 | let mut geneve_data: Vec = 72 | vec![0x00, 0x00, 0x65, 0x58, 0x11, 0x11, 0x11, 0x00]; 73 | geneve_data.extend_from_slice(ð_data); 74 | 75 | n += 8; 76 | let mut udp_data: Vec = vec![0; n]; 77 | let mut udp = MutableUdpPacket::new(&mut udp_data).unwrap(); 78 | udp.set_source(100); 79 | udp.set_destination(6081); 80 | udp.set_checksum(0x1701); 81 | udp.set_payload(&geneve_data); 82 | 83 | n += 40; 84 | let mut ip_data: Vec = vec![0; n]; 85 | let mut ip = MutableIpv6Packet::new(&mut ip_data).unwrap(); 86 | ip.set_version(6); 87 | ip.set_source(src); 88 | ip.set_destination(dst); 89 | ip.set_payload_length(udp_data.len() as u16); 90 | ip.set_payload(&udp_data); 91 | ip.set_next_header(IpNextHeaderProtocol::new(17)); 92 | 93 | // outer eth is tacked on by phy::send 94 | 95 | phy0.send(&[TxFrame::new(phy1.mac, 0x86dd, &ip_data)])?; 96 | 97 | let fs = phy1.recv(); 98 | let f = &fs[0]; 99 | 100 | let decapped_ip = Ipv4Packet::new(&f.payload).unwrap(); 101 | let decapped_udp = UdpPacket::new(decapped_ip.payload()).unwrap(); 102 | 103 | println!("Decapped IP: {:#?}", decapped_ip); 104 | println!("Decapped UDP: {:#?}", decapped_udp); 105 | 106 | assert_eq!( 107 | Ipv4Packet::new(&inner_ip_data.clone()).unwrap(), 108 | decapped_ip 109 | ); 110 | assert_eq!( 111 | UdpPacket::new(&inner_udp_data.clone()).unwrap(), 112 | decapped_udp 113 | ); 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /test/src/dynamic_router.rs: -------------------------------------------------------------------------------- 1 | use crate::softnpu::{Interface6, RxFrame, SoftNpu}; 2 | use crate::{expect_frames, muffins}; 3 | use std::net::Ipv6Addr; 4 | 5 | p4_macro::use_p4!( 6 | p4 = "test/src/p4/dynamic_router.p4", 7 | pipeline_name = "dynamic", 8 | ); 9 | 10 | /// This test is the same as the disag router test, except table entries are 11 | /// added dynamically instead of statically defined in the P4 program. 12 | /// 13 | /// ~~~~~~~~~~ 14 | /// ~ ~ 15 | /// ~ p4 ~ * *=======* 16 | /// ~ ~ | | | 17 | /// ~~~~~~~~~~ |---| phy 1 | 18 | /// | | | | 19 | /// | | *=======* 20 | /// *==========* | *=======* 21 | /// | | <-- ( rx ) --- | | | 22 | /// | pipeline | |---| phy 2 | 23 | /// | | --- ( tx ) --> | | | 24 | /// *==========* | *=======* 25 | /// tx | | | *=======* 26 | /// | | | | | 27 | /// | | rx |---| phy 3 | 28 | /// *========* | | | 29 | /// | | * *=======* 30 | /// | | 31 | /// | CPU | 32 | /// *========* 33 | /// 34 | 35 | #[test] 36 | fn dynamic_router2() -> Result<(), anyhow::Error> { 37 | let mut pipeline = main_pipeline::new(4); 38 | 39 | // 40 | // add table entries 41 | // 42 | 43 | let prefix: Ipv6Addr = "fd00:1000::".parse().unwrap(); 44 | let mut buf = prefix.octets().to_vec(); 45 | buf.push(24); // prefix length 46 | 47 | pipeline.add_ingress_router_router_entry( 48 | "forward", 49 | &buf, 50 | &1u16.to_le_bytes(), 51 | 0, 52 | ); 53 | 54 | let prefix: Ipv6Addr = "fd00:2000::".parse().unwrap(); 55 | let mut buf = prefix.octets().to_vec(); 56 | buf.push(24); // prefix length 57 | 58 | pipeline.add_ingress_router_router_entry( 59 | "forward", 60 | &buf, 61 | &2u16.to_le_bytes(), 62 | 0, 63 | ); 64 | 65 | let prefix: Ipv6Addr = "fd00:3000::".parse().unwrap(); 66 | let mut buf = prefix.octets().to_vec(); 67 | buf.push(24); // prefix length 68 | 69 | pipeline.add_ingress_router_router_entry( 70 | "forward", 71 | &buf, 72 | &3u16.to_le_bytes(), 73 | 0, 74 | ); 75 | 76 | // 77 | // run program 78 | // 79 | 80 | let mut npu = SoftNpu::new(4, pipeline, true); 81 | let cpu = npu.phy(0); 82 | let phy1 = npu.phy(1); 83 | let phy2 = npu.phy(2); 84 | let phy3 = npu.phy(3); 85 | 86 | let if1 = Interface6::new(phy1.clone(), "fd00:1000::1".parse().unwrap()); 87 | let if2 = Interface6::new(phy2.clone(), "fd00:2000::1".parse().unwrap()); 88 | let mut if3 = Interface6::new( 89 | cpu.clone(), 90 | "fe80::aae1:deff:fe01:701c".parse().unwrap(), 91 | ); 92 | if3.sc_egress = 1; 93 | let mut if4 = Interface6::new( 94 | cpu.clone(), 95 | "fe80::aae1:deff:fe01:701d".parse().unwrap(), 96 | ); 97 | if4.sc_egress = 3; 98 | let mc1: Ipv6Addr = "ff02::1:ff01:701c".parse().unwrap(); 99 | 100 | npu.run(); 101 | 102 | let msg = muffins!(); 103 | 104 | if1.send(phy2.mac, if2.addr, msg.0)?; 105 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0x86dd, msg.0)]); 106 | 107 | // multicast should go to the CPU port 108 | if2.send(phy1.mac, mc1, msg.1)?; 109 | expect_frames!(cpu, &[RxFrame::new(phy2.mac, 0x0901, msg.1)]); 110 | 111 | // link-local should go to the CPU port 112 | if1.send(phy2.mac, if3.addr, msg.2)?; 113 | expect_frames!(cpu, &[RxFrame::new(phy1.mac, 0x0901, msg.2)]); 114 | 115 | // link-local should go to the CPU port 116 | if2.send(phy1.mac, if4.addr, msg.3)?; 117 | expect_frames!(cpu, &[RxFrame::new(phy2.mac, 0x0901, msg.3)]); 118 | 119 | // from the CPU port to phy1 120 | if3.send(phy1.mac, if1.addr, msg.4)?; 121 | expect_frames!(phy1, &[RxFrame::new(cpu.mac, 0x86dd, msg.4)]); 122 | 123 | // from the CPU port to phy1 124 | if4.send(phy2.mac, if2.addr, msg.5)?; 125 | expect_frames!(phy3, &[RxFrame::new(cpu.mac, 0x86dd, msg.5)]); 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /lang/p4rs/src/checksum.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use bitvec::prelude::*; 4 | 5 | #[derive(Default)] 6 | pub struct Csum(u16); 7 | 8 | impl Csum { 9 | pub fn add(&mut self, a: u8, b: u8) { 10 | let x = u16::from_be_bytes([a, b]); 11 | let (mut result, overflow) = self.0.overflowing_add(x); 12 | if overflow { 13 | result += 1; 14 | } 15 | self.0 = result; 16 | } 17 | pub fn add128(&mut self, data: [u8; 16]) { 18 | self.add(data[0], data[1]); 19 | self.add(data[2], data[3]); 20 | self.add(data[4], data[5]); 21 | self.add(data[6], data[7]); 22 | self.add(data[8], data[9]); 23 | self.add(data[10], data[11]); 24 | self.add(data[12], data[13]); 25 | self.add(data[14], data[15]); 26 | } 27 | pub fn add32(&mut self, data: [u8; 4]) { 28 | self.add(data[0], data[1]); 29 | self.add(data[2], data[3]); 30 | } 31 | pub fn add16(&mut self, data: [u8; 2]) { 32 | self.add(data[0], data[1]); 33 | } 34 | pub fn result(&self) -> u16 { 35 | !self.0 36 | } 37 | } 38 | 39 | pub fn udp6_checksum(data: &[u8]) -> u16 { 40 | let src = &data[8..24]; 41 | let dst = &data[24..40]; 42 | let udp_len = &data[4..6]; 43 | let next_header = &data[6]; 44 | let src_port = &data[40..42]; 45 | let dst_port = &data[42..44]; 46 | let payload_len = &data[44..46]; 47 | let payload = &data[48..]; 48 | 49 | let mut csum = Csum(0); 50 | 51 | for i in (0..src.len()).step_by(2) { 52 | csum.add(src[i], src[i + 1]); 53 | } 54 | for i in (0..dst.len()).step_by(2) { 55 | csum.add(dst[i], dst[i + 1]); 56 | } 57 | csum.add(udp_len[0], udp_len[1]); 58 | //TODO assuming no jumbo 59 | csum.add(0, *next_header); 60 | csum.add(src_port[0], src_port[1]); 61 | csum.add(dst_port[0], dst_port[1]); 62 | csum.add(payload_len[0], payload_len[1]); 63 | 64 | let len = payload.len(); 65 | let (odd, len) = if len % 2 == 0 { 66 | (false, len) 67 | } else { 68 | (true, len - 1) 69 | }; 70 | for i in (0..len).step_by(2) { 71 | csum.add(payload[i], payload[i + 1]); 72 | } 73 | if odd { 74 | csum.add(payload[len], 0); 75 | } 76 | 77 | csum.result() 78 | } 79 | 80 | pub trait Checksum { 81 | fn csum(&self) -> BitVec; 82 | } 83 | 84 | fn bvec_csum(bv: &BitVec) -> BitVec { 85 | let x: u128 = bv.load(); 86 | let buf = x.to_be_bytes(); 87 | let mut c: u16 = 0; 88 | for i in (0..16).step_by(2) { 89 | c += u16::from_be_bytes([buf[i], buf[i + 1]]) 90 | } 91 | let c = !c; 92 | let mut result = bitvec![u8, Msb0; 0u8, 16]; 93 | result.store(c); 94 | result 95 | } 96 | 97 | impl Checksum for BitVec { 98 | fn csum(&self) -> BitVec { 99 | bvec_csum(self) 100 | } 101 | } 102 | 103 | impl Checksum for &BitVec { 104 | fn csum(&self) -> BitVec { 105 | bvec_csum(self) 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use super::*; 112 | use pnet::packet::udp; 113 | use std::f32::consts::PI; 114 | use std::net::Ipv6Addr; 115 | 116 | #[test] 117 | fn udp_checksum() { 118 | let mut packet = [0u8; 200]; 119 | 120 | // 121 | // ipv6 122 | // 123 | 124 | packet[0] = 6; // version = 6 125 | packet[5] = 160; // 160 byte payload (200 - payload=40) 126 | packet[6] = 17; // next header = udp 127 | packet[7] = 255; // hop limit = 255 128 | 129 | // src = fd00::1 130 | packet[8] = 0xfd; 131 | packet[23] = 0x01; 132 | 133 | // dst = fd00::2 134 | packet[24] = 0xfd; 135 | packet[39] = 0x02; 136 | 137 | // 138 | // udp 139 | // 140 | 141 | packet[41] = 47; // source port = 47 142 | packet[43] = 74; // dstination port = 74 143 | packet[45] = 160; // udp header + payload = 160 bytes 144 | for (i, data_point) in packet.iter_mut().enumerate().skip(46) { 145 | *data_point = ((i as f32) * (PI / 32.0) * 10.0) as u8; 146 | } 147 | 148 | let x = udp6_checksum(&packet); 149 | 150 | let p = udp::UdpPacket::new(&packet[40..]).unwrap(); 151 | let src: Ipv6Addr = "fd00::1".parse().unwrap(); 152 | let dst: Ipv6Addr = "fd00::2".parse().unwrap(); 153 | let y = udp::ipv6_checksum(&p, &src, &dst); 154 | 155 | assert_eq!(x, y); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /test/src/p4/dynamic_router_noaddr.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | sidecar_h sidecar; 14 | ipv6_h ipv6; 15 | } 16 | 17 | parser parse( 18 | packet_in pkt, 19 | out headers_t headers, 20 | inout ingress_metadata_t ingress, 21 | ){ 22 | state start { 23 | pkt.extract(headers.ethernet); 24 | if (headers.ethernet.ether_type == 16w0x86dd) { 25 | transition ipv6; 26 | } 27 | if (headers.ethernet.ether_type == 16w0x0901) { 28 | transition sidecar; 29 | } 30 | transition reject; 31 | } 32 | 33 | state sidecar { 34 | pkt.extract(headers.sidecar); 35 | if (headers.sidecar.sc_ether_type == 16w0x86dd) { 36 | transition ipv6; 37 | } 38 | transition reject; 39 | } 40 | 41 | state ipv6 { 42 | pkt.extract(headers.ipv6); 43 | transition accept; 44 | } 45 | 46 | } 47 | 48 | control local( 49 | inout headers_t hdr, 50 | out bool is_local, 51 | ) { 52 | 53 | action nonlocal() { 54 | is_local = false; 55 | } 56 | 57 | action local() { 58 | is_local = true; 59 | } 60 | 61 | table local { 62 | key = { 63 | hdr.ipv6.dst: exact; 64 | } 65 | actions = { 66 | local; 67 | nonlocal; 68 | } 69 | default_action = nonlocal; 70 | } 71 | 72 | apply { 73 | local.apply(); 74 | 75 | bit<16> ll = 16w0xff02; 76 | 77 | if(hdr.ipv6.dst[127:112] == ll) { 78 | is_local = true; 79 | } 80 | } 81 | 82 | } 83 | 84 | control router( 85 | inout headers_t hdr, 86 | inout ingress_metadata_t ingress, 87 | inout egress_metadata_t egress, 88 | ) { 89 | 90 | action drop() { } 91 | 92 | action forward(bit<16> port) { 93 | egress.port = port; 94 | } 95 | 96 | table router { 97 | key = { 98 | hdr.ipv6.dst: lpm; 99 | } 100 | actions = { 101 | drop; 102 | forward; 103 | } 104 | default_action = drop; 105 | } 106 | 107 | apply { 108 | router.apply(); 109 | } 110 | 111 | } 112 | 113 | control ingress( 114 | inout headers_t hdr, 115 | inout ingress_metadata_t ingress, 116 | inout egress_metadata_t egress, 117 | ) { 118 | local() local; 119 | router() router; 120 | 121 | apply { 122 | 123 | // 124 | // Check if this is a packet coming from the scrimlet. 125 | // 126 | 127 | if (hdr.sidecar.isValid()) { 128 | 129 | // Direct packets to the sidecar port corresponding to the scrimlet 130 | // port they came from. 131 | egress.port = hdr.sidecar.sc_ingress; 132 | 133 | // Decap the sidecar header. 134 | hdr.sidecar.setInvalid(); 135 | hdr.ethernet.ether_type = 16w0x86dd; 136 | 137 | // No more processing is required for sidecar packets, they simple 138 | // go out the sidecar port corresponding to the source scrimlet 139 | // port. No sort of hairpin back to the scrimlet is supported. 140 | // Similarly sending packets from one scrimlet port out a different 141 | // sidecar port is also not supported. 142 | return; 143 | } 144 | 145 | // 146 | // If the packet has a local destination, create the sidecar header and 147 | // send it to the scrimlet. 148 | // 149 | 150 | bool local_dst = false; 151 | local.apply(hdr, local_dst); 152 | 153 | if (local_dst) { 154 | hdr.sidecar.setValid(); 155 | hdr.ethernet.ether_type = 16w0x0901; 156 | 157 | //SC_FORWARD_TO_USERSPACE 158 | hdr.sidecar.sc_code = 8w0x01; 159 | hdr.sidecar.sc_ingress = ingress.port; 160 | hdr.sidecar.sc_egress = ingress.port; 161 | hdr.sidecar.sc_ether_type = 16w0x86dd; 162 | hdr.sidecar.sc_payload = 128w0x1701d; 163 | 164 | // scrimlet port 165 | egress.port = 0; 166 | } 167 | 168 | // 169 | // Otherwise route the packet using the L3 routing table. 170 | // 171 | 172 | else { 173 | // if the packet came from the scrimlet invalidate the header 174 | // sidecar header so. 175 | if (ingress.port == 8w1) { 176 | hdr.sidecar.setInvalid(); 177 | } 178 | router.apply(hdr, ingress, egress); 179 | } 180 | } 181 | } 182 | 183 | control egress( 184 | inout headers_t hdr, 185 | inout ingress_metadata_t ingress, 186 | inout egress_metadata_t egress, 187 | ) { 188 | 189 | } 190 | -------------------------------------------------------------------------------- /codegen/rust/src/p4struct.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use crate::Context; 4 | use p4::ast::{Struct, Type, AST}; 5 | use quote::{format_ident, quote}; 6 | 7 | pub(crate) struct StructGenerator<'a> { 8 | ast: &'a AST, 9 | ctx: &'a mut Context, 10 | } 11 | 12 | impl<'a> StructGenerator<'a> { 13 | pub(crate) fn new(ast: &'a AST, ctx: &'a mut Context) -> Self { 14 | Self { ast, ctx } 15 | } 16 | 17 | pub(crate) fn generate(&mut self) { 18 | for s in &self.ast.structs { 19 | self.generate_struct(s); 20 | } 21 | } 22 | 23 | fn generate_struct(&mut self, s: &Struct) { 24 | let mut members = Vec::new(); 25 | let mut valid_member_size = Vec::new(); 26 | let mut to_bitvec_stmts = Vec::new(); 27 | let mut dump_statements = Vec::new(); 28 | let fmt = "{}: {}\n".repeat(s.members.len()); 29 | let fmt = fmt.trim(); 30 | 31 | for member in &s.members { 32 | let name = format_ident!("{}", member.name); 33 | let name_s = &member.name; 34 | match &member.ty { 35 | Type::UserDefined(ref typename) => { 36 | if self.ast.get_header(typename).is_some() { 37 | let ty = format_ident!("{}", typename); 38 | 39 | // member generation 40 | members.push(quote! { pub #name: #ty }); 41 | 42 | // valid header size statements 43 | valid_member_size.push(quote! { 44 | if self.#name.valid { 45 | x += #ty::size(); 46 | } 47 | }); 48 | 49 | // to bitvec statements 50 | to_bitvec_stmts.push(quote!{ 51 | if self.#name.valid { 52 | x[off..off+#ty::size()] |= self.#name.to_bitvec(); 53 | off += #ty::size(); 54 | } 55 | }); 56 | 57 | dump_statements.push(quote! { 58 | #name_s.blue(), 59 | self.#name.dump() 60 | }); 61 | } else { 62 | panic!( 63 | "Struct member {:#?} undefined in {:#?}", 64 | member, s 65 | ); 66 | } 67 | } 68 | Type::Bit(size) => { 69 | members.push(quote! { pub #name: BitVec:: }); 70 | dump_statements.push(quote! { 71 | #name_s.blue(), 72 | p4rs::dump_bv(&self.#name) 73 | }); 74 | valid_member_size.push(quote! { 75 | x += #size; 76 | }); 77 | to_bitvec_stmts.push(quote! { 78 | x[off..off+#size] |= self.#name.to_bitvec(); 79 | off += #size; 80 | }); 81 | } 82 | Type::Bool => { 83 | members.push(quote! { pub #name: bool }); 84 | dump_statements.push(quote! { 85 | #name_s.blue(), 86 | self.#name 87 | }); 88 | } 89 | x => { 90 | todo!("struct member {}", x) 91 | } 92 | } 93 | } 94 | 95 | let dump = quote! { 96 | format!(#fmt, #(#dump_statements),*) 97 | }; 98 | 99 | let name = format_ident!("{}", s.name); 100 | 101 | let mut structure = quote! { 102 | #[derive(Debug, Default, Clone)] 103 | pub struct #name { 104 | #(#members),* 105 | } 106 | }; 107 | if !valid_member_size.is_empty() { 108 | structure.extend(quote! { 109 | impl #name { 110 | fn valid_header_size(&self) -> usize { 111 | let mut x: usize = 0; 112 | #(#valid_member_size)* 113 | x 114 | } 115 | 116 | fn to_bitvec(&self) -> BitVec { 117 | let mut x = 118 | bitvec![u8, Msb0; 0; self.valid_header_size()]; 119 | let mut off = 0; 120 | #(#to_bitvec_stmts)* 121 | x 122 | } 123 | 124 | fn dump(&self) -> String { 125 | #dump 126 | } 127 | } 128 | }) 129 | } else { 130 | structure.extend(quote! { 131 | impl #name { 132 | fn valid_header_size(&self) -> usize { 0 } 133 | 134 | fn to_bitvec(&self) -> BitVec { 135 | bitvec![u8, Msb0; 0; 0] 136 | } 137 | 138 | fn dump(&self) -> String { 139 | std::string::String::new() 140 | } 141 | } 142 | }) 143 | } 144 | 145 | self.ctx.structs.insert(s.name.clone(), structure); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/src/p4/dynamic_router.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | sidecar_h sidecar; 14 | ipv6_h ipv6; 15 | } 16 | 17 | parser parse( 18 | packet_in pkt, 19 | out headers_t headers, 20 | inout ingress_metadata_t ingress, 21 | ){ 22 | state start { 23 | pkt.extract(headers.ethernet); 24 | if (headers.ethernet.ether_type == 16w0x86dd) { 25 | transition ipv6; 26 | } 27 | if (headers.ethernet.ether_type == 16w0x0901) { 28 | transition sidecar; 29 | } 30 | transition reject; 31 | } 32 | 33 | state sidecar { 34 | pkt.extract(headers.sidecar); 35 | if (headers.sidecar.sc_ether_type == 16w0x86dd) { 36 | transition ipv6; 37 | } 38 | transition reject; 39 | } 40 | 41 | state ipv6 { 42 | pkt.extract(headers.ipv6); 43 | transition accept; 44 | } 45 | 46 | } 47 | 48 | control local( 49 | inout headers_t hdr, 50 | out bool is_local, 51 | ) { 52 | 53 | action nonlocal() { 54 | is_local = false; 55 | } 56 | 57 | action local() { 58 | is_local = true; 59 | } 60 | 61 | table tbl { 62 | key = { 63 | hdr.ipv6.dst: exact; 64 | } 65 | actions = { 66 | local; 67 | nonlocal; 68 | } 69 | default_action = nonlocal; 70 | const entries = { 71 | //fe80::aae1:deff:fe01:701c 72 | 128w0xfe80000000000000aae1defffe01701c: local(); 73 | //128w0x1c7001feffdee1aa00000000000080fe: local(); 74 | 75 | //fe80::aae1:deff:fe01:701d 76 | 128w0xfe80000000000000aae1defffe01701d : local(); 77 | //128w0x1d7001feffdee1aa00000000000080fe : local(); 78 | 79 | //fe80::aae1:deff:fe01:701e 80 | 128w0xfe80000000000000aae1defffe01701e : local(); 81 | //128w0x1e7001feffdee1aa00000000000080fe : local(); 82 | } 83 | } 84 | 85 | apply { 86 | tbl.apply(); 87 | 88 | bit<16> ll = 16w0xff02; 89 | 90 | if(hdr.ipv6.dst[127:112] == ll) { 91 | is_local = true; 92 | } 93 | } 94 | 95 | } 96 | 97 | control router( 98 | inout headers_t hdr, 99 | inout ingress_metadata_t ingress, 100 | inout egress_metadata_t egress, 101 | ) { 102 | 103 | action drop() { } 104 | 105 | action forward(bit<16> port) { 106 | egress.port = port; 107 | } 108 | 109 | table router { 110 | key = { 111 | hdr.ipv6.dst: lpm; 112 | } 113 | actions = { 114 | drop; 115 | forward; 116 | } 117 | default_action = drop; 118 | } 119 | 120 | apply { 121 | router.apply(); 122 | } 123 | 124 | } 125 | 126 | control ingress( 127 | inout headers_t hdr, 128 | inout ingress_metadata_t ingress, 129 | inout egress_metadata_t egress, 130 | ) { 131 | local() local; 132 | router() router; 133 | 134 | apply { 135 | 136 | // 137 | // Check if this is a packet coming from the scrimlet. 138 | // 139 | 140 | if (hdr.sidecar.isValid()) { 141 | 142 | // Direct packets to the sidecar port corresponding to the scrimlet 143 | // port they came from. 144 | egress.port = hdr.sidecar.sc_ingress; 145 | 146 | // Decap the sidecar header. 147 | hdr.sidecar.setInvalid(); 148 | hdr.ethernet.ether_type = 16w0x86dd; 149 | 150 | // No more processing is required for sidecar packets, they simple 151 | // go out the sidecar port corresponding to the source scrimlet 152 | // port. No sort of hairpin back to the scrimlet is supported. 153 | // Similarly sending packets from one scrimlet port out a different 154 | // sidecar port is also not supported. 155 | return; 156 | } 157 | 158 | // 159 | // If the packet has a local destination, create the sidecar header and 160 | // send it to the scrimlet. 161 | // 162 | 163 | bool local_dst = false; 164 | local.apply(hdr, local_dst); 165 | 166 | if (local_dst) { 167 | hdr.sidecar.setValid(); 168 | hdr.ethernet.ether_type = 16w0x0901; 169 | 170 | //SC_FORWARD_TO_USERSPACE 171 | hdr.sidecar.sc_code = 8w0x01; 172 | hdr.sidecar.sc_ingress = ingress.port; 173 | hdr.sidecar.sc_egress = ingress.port; 174 | hdr.sidecar.sc_ether_type = 16w0x86dd; 175 | hdr.sidecar.sc_payload = 128w0x1701d; 176 | 177 | // scrimlet port 178 | egress.port = 16w0; 179 | } 180 | 181 | // 182 | // Otherwise route the packet using the L3 routing table. 183 | // 184 | 185 | else { 186 | // if the packet came from the scrimlet invalidate the header 187 | // sidecar header so. 188 | if (ingress.port == 16w1) { 189 | hdr.sidecar.setInvalid(); 190 | } 191 | router.apply(hdr, ingress, egress); 192 | } 193 | } 194 | } 195 | 196 | control egress( 197 | inout headers_t hdr, 198 | inout ingress_metadata_t ingress, 199 | inout egress_metadata_t egress, 200 | ) { 201 | 202 | } 203 | -------------------------------------------------------------------------------- /lang/p4-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | //! The [`use_p4!`] macro allows for P4 programs to be directly integrated into 4 | //! Rust programs. 5 | //! 6 | //! ```ignore 7 | //! p4_macro::use_p4!("path/to/p4/program.p4"); 8 | //! ``` 9 | //! 10 | //! This will generate a `main_pipeline` struct that implements the 11 | //! [Pipeline](../p4rs/trait.Pipeline.html) trait. The [`use_p4!`] macro expands 12 | //! directly in to `x4c` compiled code. This includes all data structures, 13 | //! parsers and control blocks. 14 | //! 15 | //! To customize the name of the generated pipeline use the `pipeline_name` 16 | //! parameter. 17 | //! 18 | //! ```ignore 19 | //! p4_macro::use_p4!(p4 = "path/to/p4/program.p4", pipeline_name = "muffin"); 20 | //! ``` 21 | //! This will result in a `muffin_pipeline` struct being being generated. 22 | //! 23 | //! For documentation on using [Pipeline](../p4rs/trait.Pipeline.html) trait, see the 24 | //! [p4rs](../p4rs/index.html) docs. 25 | 26 | use std::fs; 27 | use std::path::Path; 28 | use std::sync::Arc; 29 | 30 | use p4::check::Diagnostics; 31 | use p4::{ 32 | ast::AST, check, error, error::SemanticError, lexer, parser, preprocessor, 33 | }; 34 | use proc_macro::TokenStream; 35 | use serde::Deserialize; 36 | use serde_tokenstream::ParseWrapper; 37 | use syn::{parse, LitStr}; 38 | 39 | #[derive(Deserialize)] 40 | struct MacroSettings { 41 | p4: ParseWrapper, 42 | pipeline_name: ParseWrapper, 43 | } 44 | 45 | struct GenerationSettings { 46 | pipeline_name: String, 47 | } 48 | 49 | impl Default for GenerationSettings { 50 | fn default() -> Self { 51 | Self { 52 | pipeline_name: "main".to_owned(), 53 | } 54 | } 55 | } 56 | 57 | /// The `use_p4!` macro uses the `x4c` compiler to generate Rust code from a P4 58 | /// program. The macro itself expands into the generated code. The macro can be 59 | /// called with only the path to the P4 program as an argument or, it can be 60 | /// called with the path to the P4 program plus the name to use for the 61 | /// generated pipeline object. 62 | /// 63 | /// For usage examples, see the [p4-macro](index.html) module documentation. 64 | #[proc_macro] 65 | pub fn use_p4(item: TokenStream) -> TokenStream { 66 | match do_use_p4(item) { 67 | Err(err) => err.to_compile_error().into(), 68 | Ok(out) => out, 69 | } 70 | } 71 | 72 | fn do_use_p4(item: TokenStream) -> Result { 73 | let (filename, settings) = 74 | if let Ok(filename) = parse::(item.clone()) { 75 | (filename.value(), GenerationSettings::default()) 76 | } else { 77 | let MacroSettings { p4, pipeline_name } = 78 | serde_tokenstream::from_tokenstream(&item.into())?; 79 | ( 80 | p4.into_inner().value(), 81 | GenerationSettings { 82 | pipeline_name: pipeline_name.into_inner().value(), 83 | }, 84 | ) 85 | }; 86 | 87 | generate_rs(filename, settings) 88 | } 89 | 90 | fn generate_rs( 91 | filename: String, 92 | settings: GenerationSettings, 93 | ) -> Result { 94 | //TODO gracefull error handling 95 | 96 | let mut ast = AST::default(); 97 | process_file(Arc::new(filename), &mut ast, &settings)?; 98 | 99 | let (hlir, _) = check::all(&ast); 100 | 101 | let tokens: TokenStream = p4_rust::emit_tokens( 102 | &ast, 103 | &hlir, 104 | p4_rust::Settings { 105 | pipeline_name: settings.pipeline_name.clone(), 106 | }, 107 | ) 108 | .into(); 109 | 110 | Ok(tokens) 111 | } 112 | 113 | fn process_file( 114 | filename: Arc, 115 | ast: &mut AST, 116 | _settings: &GenerationSettings, 117 | ) -> Result<(), syn::Error> { 118 | let contents = match fs::read_to_string(&*filename) { 119 | Ok(c) => c, 120 | Err(e) => panic!("failed to read file {}: {}", filename, e), 121 | }; 122 | let ppr = preprocessor::run(&contents, filename.clone()).unwrap(); 123 | for included in &ppr.elements.includes { 124 | let path = Path::new(included); 125 | if !path.is_absolute() { 126 | let parent = Path::new(&*filename).parent().unwrap(); 127 | let joined = parent.join(included); 128 | process_file( 129 | Arc::new(joined.to_str().unwrap().to_string()), 130 | ast, 131 | _settings, 132 | )? 133 | } else { 134 | process_file(Arc::new(included.clone()), ast, _settings)?; 135 | } 136 | } 137 | 138 | let (_, diags) = check::all(ast); 139 | let lines: Vec<&str> = ppr.lines.iter().map(|x| x.as_str()).collect(); 140 | check(&lines, &diags); 141 | let lxr = lexer::Lexer::new(lines.clone(), filename); 142 | let mut psr = parser::Parser::new(lxr); 143 | psr.run(ast).unwrap(); 144 | p4_rust::sanitize(ast); 145 | Ok(()) 146 | } 147 | 148 | // TODO copy pasta from x4c 149 | fn check(lines: &[&str], diagnostics: &Diagnostics) { 150 | let errors = diagnostics.errors(); 151 | if !errors.is_empty() { 152 | let mut err = Vec::new(); 153 | for e in errors { 154 | err.push(SemanticError { 155 | at: e.token.clone(), 156 | message: e.message.clone(), 157 | source: lines[e.token.line].into(), 158 | }); 159 | } 160 | panic!("{}", error::Error::Semantic(err)); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test/src/mac_rewrite.rs: -------------------------------------------------------------------------------- 1 | use crate::softnpu::{Interface6, RxFrame, SoftNpu}; 2 | use crate::{expect_frames, muffins}; 3 | use std::net::Ipv6Addr; 4 | 5 | p4_macro::use_p4!( 6 | p4 = "test/src/p4/dynamic_router_noaddr_nbr.p4", 7 | pipeline_name = "mac_rewrite", 8 | ); 9 | 10 | // ~~~~~~~~~~ 11 | // ~ ~ 12 | // ~ p4 ~ * *=======* 13 | // ~ ~ | | | 14 | // ~~~~~~~~~~ |---| phy 1 | 15 | // | | | | 16 | // | | *=======* 17 | // *==========* | *=======* 18 | // | | <-- ( rx ) --- | | | 19 | // | pipeline | |---| phy 2 | 20 | // | | --- ( tx ) --> | | | 21 | // *==========* | *=======* 22 | // | | | *=======* 23 | // | | | | | 24 | // *========* |---| phy 3 | 25 | // | | | | | 26 | // | sc | * *=======* 27 | // | (phy0) | 28 | // *========* 29 | 30 | fn v6_arg(addr: Ipv6Addr) -> [u8; 16] { 31 | let k: u128 = addr.into(); 32 | k.to_le_bytes() 33 | } 34 | 35 | #[test] 36 | fn mac_rewrite2() -> Result<(), anyhow::Error> { 37 | let mut pipeline = main_pipeline::new(4); 38 | 39 | // 40 | // add table entries 41 | // 42 | let addr_c: Ipv6Addr = "fe80::aae1:deff:fe01:701c".parse().unwrap(); 43 | let addr_d: Ipv6Addr = "fe80::aae1:deff:fe01:701d".parse().unwrap(); 44 | let addr_e: Ipv6Addr = "fe80::aae1:deff:fe01:701e".parse().unwrap(); 45 | 46 | pipeline.add_ingress_local_local_entry( 47 | "set_local", 48 | &v6_arg(addr_c), 49 | &Vec::new(), 50 | 0, 51 | ); 52 | pipeline.add_ingress_local_local_entry( 53 | "set_local", 54 | &v6_arg(addr_d), 55 | &Vec::new(), 56 | 0, 57 | ); 58 | pipeline.add_ingress_local_local_entry( 59 | "set_local", 60 | &v6_arg(addr_e), 61 | &Vec::new(), 62 | 0, 63 | ); 64 | 65 | // resolver table entries 66 | 67 | pipeline.add_ingress_router_resolver_resolver_entry( 68 | "rewrite_dst", 69 | &v6_arg(addr_c), 70 | &[0x44, 0x44, 0x44, 0x44, 0x44, 0x44], 71 | 0, 72 | ); 73 | 74 | pipeline.add_ingress_router_resolver_resolver_entry( 75 | "rewrite_dst", 76 | &v6_arg(addr_d), 77 | &[0x33, 0x33, 0x33, 0x33, 0x33, 0x33], 78 | 0, 79 | ); 80 | 81 | pipeline.add_ingress_router_resolver_resolver_entry( 82 | "rewrite_dst", 83 | &v6_arg(addr_e), 84 | &[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], 85 | 0, 86 | ); 87 | 88 | // routing table entries 89 | 90 | let prefix: Ipv6Addr = "fd00:1000::".parse().unwrap(); 91 | let mut key = prefix.octets().to_vec(); 92 | key.push(24); // prefix length 93 | let mut args = 1u16.to_le_bytes().to_vec(); 94 | args.extend_from_slice(&v6_arg(addr_c)); 95 | pipeline.add_ingress_router_router_entry("forward", &key, &args, 0); 96 | 97 | let prefix: Ipv6Addr = "fd00:2000::".parse().unwrap(); 98 | let mut key = prefix.octets().to_vec(); 99 | key.push(24); // prefix length 100 | let mut args = 2u16.to_le_bytes().to_vec(); 101 | args.extend_from_slice(&v6_arg(addr_d)); 102 | pipeline.add_ingress_router_router_entry("forward", &key, &args, 0); 103 | 104 | let prefix: Ipv6Addr = "fd00:3000::".parse().unwrap(); 105 | let mut key = prefix.octets().to_vec(); 106 | key.push(24); // prefix length 107 | let mut args = 3u16.to_le_bytes().to_vec(); 108 | args.extend_from_slice(&v6_arg(addr_e)); 109 | pipeline.add_ingress_router_router_entry("forward", &key, &args, 0); 110 | 111 | // 112 | // run program 113 | // 114 | 115 | let mut npu = SoftNpu::new(4, pipeline, true); 116 | let cpu = npu.phy(0); 117 | let phy1 = npu.phy(1); 118 | let phy2 = npu.phy(2); 119 | let phy3 = npu.phy(3); 120 | 121 | let if1 = Interface6::new(phy1.clone(), "fd00:1000::1".parse().unwrap()); 122 | let if2 = Interface6::new(phy2.clone(), "fd00:2000::1".parse().unwrap()); 123 | let mut if3 = Interface6::new( 124 | cpu.clone(), 125 | "fe80::aae1:deff:fe01:701c".parse().unwrap(), 126 | ); 127 | if3.sc_egress = 1; 128 | let mut if4 = Interface6::new( 129 | cpu.clone(), 130 | "fe80::aae1:deff:fe01:701d".parse().unwrap(), 131 | ); 132 | if4.sc_egress = 3; 133 | let mc1: Ipv6Addr = "ff02::1:ff01:701c".parse().unwrap(); 134 | 135 | npu.run(); 136 | 137 | let msg = muffins!(); 138 | 139 | if1.send(phy2.mac, if2.addr, msg.0)?; 140 | let m = [0x33, 0x33, 0x33, 0x33, 0x33, 0x33]; 141 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0x86dd, msg.0)], m); 142 | 143 | // multicast should go to the CPU port 144 | if2.send(phy1.mac, mc1, msg.1)?; 145 | expect_frames!(cpu, &[RxFrame::new(phy2.mac, 0x0901, msg.1)]); 146 | 147 | // link-local should go to the CPU port 148 | if1.send(phy2.mac, if3.addr, msg.2)?; 149 | expect_frames!(cpu, &[RxFrame::new(phy1.mac, 0x0901, msg.2)]); 150 | 151 | // link-local should go to the CPU port 152 | if2.send(phy1.mac, if4.addr, msg.3)?; 153 | expect_frames!(cpu, &[RxFrame::new(phy2.mac, 0x0901, msg.3)]); 154 | 155 | // from the CPU port to phy1 156 | if3.send(phy1.mac, if1.addr, msg.4)?; 157 | expect_frames!(phy1, &[RxFrame::new(cpu.mac, 0x86dd, msg.4)]); 158 | 159 | // from the CPU port to phy1 160 | if4.send(phy2.mac, if2.addr, msg.5)?; 161 | expect_frames!(phy3, &[RxFrame::new(cpu.mac, 0x86dd, msg.5)]); 162 | 163 | Ok(()) 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # P4 2 | 3 | This repository contains a work in progress P4 compiler `x4c`. The compiler does 4 | not currently handle the entire P4 language, but is expected to evolve 5 | organically based on the concrete needs of users toward that end. 6 | 7 | `x4c` is written in pure Rust and currently compiles P4 programs into Rust 8 | programs. `x4c` generated Rust code implements a `Pipeline` trait that allows 9 | generic harnesses to be written. 10 | 11 | ## Getting started 12 | 13 | This readme contains a brief overview of getting started. More comprehensive 14 | documentation is being developed in the 15 | [x4c book](https://oxidecomputer.github.io/p4/book). 16 | 17 | There are two main forms of using the compiler. 18 | 19 | 1. Generating Rust code from your P4 code using the `x4c` CLI interface. 20 | 2. Importing P4 code directly into your rust code with the `use_p4!` macro. 21 | 22 | ### Compile with `x4c` 23 | 24 | To build the `x4c` compiler simply run the following. 25 | 26 | ``` 27 | cargo build --bin x4c 28 | ``` 29 | 30 | There are no non-rust dependencies. Typically, compiling P4 code is as simple as 31 | `x4c `. This will generate an `out.rs` file. For more advanced 32 | `x4c` usage see `x4c --help`. Generated rust programs do have a few cargo 33 | dependencies, see [this Cargo.toml](lang/prog/sidecar-lite/Cargo.toml) to see 34 | what the current requirements are. 35 | 36 | To get started with Rust code `x4c` generates, see the 37 | [p4rs module documentation](https://oxidecomputer.github.io/p4/p4rs/index.html). 38 | 39 | ### Using P4 directly in Rust. 40 | 41 | To get started with the Rust macro interface, see the 42 | [p4_macro module documentation](http://oxidecomputer.github.io/p4/p4_macro/index.html). 43 | 44 | An example of using this approach to generate a shared library is in 45 | [lang/prog/sidecar-lite](lang/prog/sidecar-lite). 46 | This code can be statically included in other programs. Automatically generated 47 | documentation for the compiled code can be found 48 | [here](https://oxidecomputer.github.io/p4/sidecar_lite/index.html). 49 | Because this crate is compiled as a shared libary, a `Pipeline` can also be 50 | dynamically loaded using the `_main_pipeline_create` function symbol which 51 | returns a `*mut dyn Pipeline` object. 52 | 53 | ## Goals 54 | 55 | - Execute P4 pipeline logic anywhere Rust can execute. 56 | - Capable of handling real-world traffic. 57 | - Provide runtime insight into program execution through dynamic tracing. 58 | - Emulate real P4 ASICs with enough fidelity to understand pipeline behaviors in 59 | a broader networked-system context. 60 | - Provide a foundation for prototyping P4-programmable devices as virtual hardware. 61 | 62 | ## Non-Goals 63 | 64 | - Compilation to Rust code for production purposes. 65 | 66 | If we decide to go in the production code generation direction, it will be 67 | targeting machine code, not another high-level language. This will allow the 68 | generated code to be optimized in terms of the P4 abstract machine model. 69 | 70 | - P4 Runtime Specification support. 71 | 72 | The goal here is to compile P4 programs as pipeline objects with simple 73 | low-level interfaces. These low-level interfaces may be wrapped with 74 | higher-level runtimes as desired by the user, but such runtimes are outside the 75 | scope of this project. 76 | 77 | - I/O handling. 78 | 79 | How packets get from the network to pipelines, and from pipelines to the network 80 | is up to harness code consuming compiled pipelines. 81 | 82 | ## Stretch Goals 83 | 84 | - x86 code generation. 85 | - RISC-V code generation. 86 | 87 | ## Design 88 | 89 | There are several major components to this repository each described below. 90 | 91 | ### Compiler Front End 92 | 93 | This includes the following. 94 | 95 | - preprocessor 96 | - lexer 97 | - parser 98 | - high-level intermediate representation (hlir) 99 | - abstract syntax tree (ast) 100 | 101 | This code lives in the [p4](p4) directory. The lexers and parsers are hand 102 | written with the intent of providing maximum flexibility to provide the best 103 | possible front end user experience. 104 | 105 | ### Code Generation 106 | 107 | Code generators take in an ast and hlir and generate runnable code. Currently 108 | there is only one code generator that produces Rust code in 109 | [codegen/rust/src](codegen/rust/src). 110 | 111 | The rust code generator is broken down into sub-generators that focus on 112 | particular P4 language elements such as control blocks, parsers, headers etc. 113 | The Rust code generation mechanisms heavily leverage the 114 | [quote](https://github.com/dtolnay/quote) crate. 115 | 116 | Generated code is not intended to be completely standalone. There is a support 117 | library [p4rs](lang/p4rs) that is used by all generated programs. This library 118 | contains common types and functions used by generated code. 119 | 120 | ### Command Line Compiler 121 | 122 | The [`x4c`](x4c) program provides a command line interface for compiling P4 123 | programs. Currently an `out.rs` file is generated on a successful compilation 124 | run. In the future when more targets are supported, output will be target 125 | specific. 126 | 127 | ## Contributing 128 | 129 | Contributions are welcome. This is still early days and there is lots of ground 130 | to cover in P4 spec coverage, type checking, static analysis and more. 131 | 132 | Incremental advances and bug fixes are welcome via issues or pull requests. 133 | Please make sure new code passes existing [tests](test) before submitting a PR 134 | for review. If you are adding new functionality, please add to an existing test 135 | or create a new test that exercises that functionality. All PRs must pass CI 136 | before being accepted. 137 | 138 | For large contributions such as design changes or new compiler targets, please 139 | reach out to discuss. 140 | -------------------------------------------------------------------------------- /test/src/p4/dynamic_router_noaddr_nbr.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | sidecar_h sidecar; 14 | ipv6_h ipv6; 15 | } 16 | 17 | parser parse( 18 | packet_in pkt, 19 | out headers_t headers, 20 | inout ingress_metadata_t ingress, 21 | ){ 22 | state start { 23 | pkt.extract(headers.ethernet); 24 | if (headers.ethernet.ether_type == 16w0x86dd) { 25 | transition ipv6; 26 | } 27 | if (headers.ethernet.ether_type == 16w0x0901) { 28 | transition sidecar; 29 | } 30 | transition reject; 31 | } 32 | 33 | state sidecar { 34 | pkt.extract(headers.sidecar); 35 | if (headers.sidecar.sc_ether_type == 16w0x86dd) { 36 | transition ipv6; 37 | } 38 | transition reject; 39 | } 40 | 41 | state ipv6 { 42 | pkt.extract(headers.ipv6); 43 | transition accept; 44 | } 45 | 46 | } 47 | 48 | control local( 49 | inout headers_t hdr, 50 | out bool is_local, 51 | ) { 52 | 53 | action nonlocal() { 54 | is_local = false; 55 | } 56 | 57 | action set_local() { 58 | is_local = true; 59 | } 60 | 61 | table local { 62 | key = { 63 | hdr.ipv6.dst: exact; 64 | } 65 | actions = { 66 | set_local; 67 | nonlocal; 68 | } 69 | default_action = nonlocal; 70 | } 71 | 72 | apply { 73 | local.apply(); 74 | 75 | bit<16> ll = 16w0xff02; 76 | 77 | if(hdr.ipv6.dst[127:112] == ll) { 78 | is_local = true; 79 | } 80 | } 81 | 82 | } 83 | 84 | control resolver( 85 | inout headers_t hdr, 86 | inout egress_metadata_t egress, 87 | ) { 88 | action rewrite_dst(bit<48> dst) { 89 | //TODO the following creates a code generation error that should get 90 | //caught at compile time 91 | // 92 | // hdr.ethernet = dst; 93 | // 94 | hdr.ethernet.dst = dst; 95 | } 96 | 97 | action drop() { 98 | egress.drop = true; 99 | } 100 | 101 | table resolver { 102 | key = { 103 | egress.nexthop_v6: exact; 104 | } 105 | actions = { rewrite_dst; drop; } 106 | default_action = drop; 107 | } 108 | 109 | apply { 110 | resolver.apply(); 111 | } 112 | 113 | } 114 | 115 | 116 | control router( 117 | inout headers_t hdr, 118 | inout ingress_metadata_t ingress, 119 | inout egress_metadata_t egress, 120 | ) { 121 | 122 | resolver() resolver; 123 | 124 | action drop() { } 125 | 126 | action forward(bit<16> port, bit<128> nexthop) { 127 | egress.port = port; 128 | egress.nexthop_v6 = nexthop; 129 | } 130 | 131 | table router { 132 | key = { 133 | hdr.ipv6.dst: lpm; 134 | } 135 | actions = { 136 | drop; 137 | forward; 138 | } 139 | default_action = drop; 140 | } 141 | 142 | apply { 143 | router.apply(); 144 | if (egress.port != 16w0) { 145 | resolver.apply(hdr, egress); 146 | } 147 | } 148 | 149 | } 150 | 151 | control ingress( 152 | inout headers_t hdr, 153 | inout ingress_metadata_t ingress, 154 | inout egress_metadata_t egress, 155 | ) { 156 | local() local; 157 | router() router; 158 | 159 | apply { 160 | 161 | // 162 | // Check if this is a packet coming from the scrimlet. 163 | // 164 | 165 | if (hdr.sidecar.isValid()) { 166 | 167 | // Direct packets to the sidecar port corresponding to the scrimlet 168 | // port they came from. 169 | egress.port = hdr.sidecar.sc_ingress; 170 | 171 | // Decap the sidecar header. 172 | hdr.sidecar.setInvalid(); 173 | hdr.ethernet.ether_type = 16w0x86dd; 174 | 175 | // No more processing is required for sidecar packets, they simple 176 | // go out the sidecar port corresponding to the source scrimlet 177 | // port. No sort of hairpin back to the scrimlet is supported. 178 | // Similarly sending packets from one scrimlet port out a different 179 | // sidecar port is also not supported. 180 | return; 181 | } 182 | 183 | // 184 | // If the packet has a local destination, create the sidecar header and 185 | // send it to the scrimlet. 186 | // 187 | 188 | bool local_dst = false; 189 | local.apply(hdr, local_dst); 190 | 191 | if (local_dst) { 192 | hdr.sidecar.setValid(); 193 | hdr.ethernet.ether_type = 16w0x0901; 194 | 195 | //SC_FORWARD_TO_USERSPACE 196 | hdr.sidecar.sc_code = 8w0x01; 197 | hdr.sidecar.sc_ingress = ingress.port; 198 | hdr.sidecar.sc_egress = ingress.port; 199 | hdr.sidecar.sc_ether_type = 16w0x86dd; 200 | hdr.sidecar.sc_payload = 128w0x1701d; 201 | 202 | // scrimlet port 203 | egress.port = 16w0; 204 | } 205 | 206 | // 207 | // Otherwise route the packet using the L3 routing table. 208 | // 209 | 210 | else { 211 | // if the packet came from the scrimlet invalidate the header 212 | // sidecar header so. 213 | if (ingress.port == 16w1) { 214 | hdr.sidecar.setInvalid(); 215 | } 216 | router.apply(hdr, ingress, egress); 217 | } 218 | } 219 | } 220 | 221 | control egress( 222 | inout headers_t hdr, 223 | inout ingress_metadata_t ingress, 224 | inout egress_metadata_t egress, 225 | ) { 226 | 227 | } 228 | -------------------------------------------------------------------------------- /test/src/p4/router.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress(), 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | sidecar_h sidecar; 14 | ipv6_h ipv6; 15 | } 16 | 17 | parser parse( 18 | packet_in pkt, 19 | out headers_t headers, 20 | inout ingress_metadata_t ingress, 21 | ){ 22 | state start { 23 | pkt.extract(headers.ethernet); 24 | if (headers.ethernet.ether_type == 16w0x86dd) { 25 | transition ipv6; 26 | } 27 | if (headers.ethernet.ether_type == 16w0x0901) { 28 | transition sidecar; 29 | } 30 | transition reject; 31 | } 32 | 33 | state sidecar { 34 | pkt.extract(headers.sidecar); 35 | if (headers.sidecar.sc_ether_type == 16w0x86dd) { 36 | transition ipv6; 37 | } 38 | transition reject; 39 | } 40 | 41 | state ipv6 { 42 | pkt.extract(headers.ipv6); 43 | transition accept; 44 | } 45 | 46 | } 47 | 48 | control local( 49 | inout headers_t hdr, 50 | out bool is_local, 51 | ) { 52 | 53 | action nonlocal() { 54 | is_local = false; 55 | } 56 | 57 | action local() { 58 | is_local = true; 59 | } 60 | 61 | table tbl { 62 | key = { 63 | hdr.ipv6.dst: exact; 64 | } 65 | actions = { 66 | local; 67 | nonlocal; 68 | } 69 | default_action = nonlocal; 70 | const entries = { 71 | //fe80::aae1:deff:fe01:701c 72 | 128w0xfe80000000000000aae1defffe01701c: local(); 73 | 74 | //fe80::aae1:deff:fe01:701d 75 | 128w0xfe80000000000000aae1defffe01701d : local(); 76 | 77 | //fe80::aae1:deff:fe01:701e 78 | 128w0xfe80000000000000aae1defffe01701e : local(); 79 | } 80 | } 81 | 82 | apply { 83 | tbl.apply(); 84 | 85 | bit<16> ll = 16w0xff02; 86 | 87 | if(hdr.ipv6.dst[127:112] == ll) { 88 | is_local = true; 89 | } 90 | } 91 | 92 | } 93 | 94 | control router( 95 | inout headers_t hdr, 96 | inout ingress_metadata_t ingress, 97 | inout egress_metadata_t egress, 98 | ) { 99 | 100 | action drop() { } 101 | 102 | action forward(bit<16> port) { 103 | egress.port = port; 104 | } 105 | 106 | table router { 107 | key = { 108 | hdr.ipv6.dst: lpm; 109 | } 110 | actions = { 111 | drop; 112 | forward; 113 | } 114 | default_action = drop; 115 | const entries = { 116 | 117 | // fd00:1000::/24 118 | 128w0xfd001000000000000000000000000000 &&& 119 | 128w0xffffff00000000000000000000000000 : 120 | forward(16w1); 121 | 122 | // fd00:2000::/24 123 | 128w0xfd002000000000000000000000000000 &&& 124 | 128w0xffffff00000000000000000000000000 : 125 | forward(16w2); 126 | 127 | // fd00:3000::/24 128 | 128w0xfd003000000000000000000000000000 &&& 129 | 128w0xffffff00000000000000000000000000 : 130 | forward(16w3); 131 | 132 | } 133 | } 134 | 135 | apply { 136 | router.apply(); 137 | } 138 | 139 | } 140 | 141 | control ingress( 142 | inout headers_t hdr, 143 | inout ingress_metadata_t ingress, 144 | inout egress_metadata_t egress, 145 | ) { 146 | local() local; 147 | router() router; 148 | 149 | apply { 150 | 151 | // 152 | // Check if this is a packet coming from the scrimlet. 153 | // 154 | 155 | if (hdr.sidecar.isValid()) { 156 | 157 | // Direct packets to the sidecar port corresponding to the scrimlet 158 | // port they came from. 159 | egress.port = hdr.sidecar.sc_ingress; 160 | 161 | // Decap the sidecar header. 162 | hdr.sidecar.setInvalid(); 163 | hdr.ethernet.ether_type = 16w0x86dd; 164 | 165 | // No more processing is required for sidecar packets, they simple 166 | // go out the sidecar port corresponding to the source scrimlet 167 | // port. No sort of hairpin back to the scrimlet is supported. 168 | // Similarly sending packets from one scrimlet port out a different 169 | // sidecar port is also not supported. 170 | return; 171 | } 172 | 173 | // 174 | // If the packet has a local destination, create the sidecar header and 175 | // send it to the scrimlet. 176 | // 177 | 178 | bool local_dst = false; 179 | local.apply(hdr, local_dst); 180 | 181 | if (local_dst) { 182 | hdr.sidecar.setValid(); 183 | hdr.ethernet.ether_type = 16w0x0901; 184 | 185 | //SC_FORWARD_TO_USERSPACE 186 | hdr.sidecar.sc_code = 8w0x01; 187 | hdr.sidecar.sc_ingress = ingress.port; 188 | hdr.sidecar.sc_egress = ingress.port; 189 | hdr.sidecar.sc_ether_type = 16w0x86dd; 190 | hdr.sidecar.sc_payload = 128w0x1701d; 191 | 192 | // scrimlet port 193 | egress.port = 16w0; 194 | } 195 | 196 | // 197 | // Otherwise route the packet using the L3 routing table. 198 | // 199 | 200 | else { 201 | // if the packet came from the scrimlet invalidate the header 202 | // sidecar header so. 203 | if (ingress.port == 16w1) { 204 | hdr.sidecar.setInvalid(); 205 | } 206 | router.apply(hdr, ingress, egress); 207 | } 208 | } 209 | } 210 | 211 | control egress( 212 | inout headers_t hdr, 213 | inout ingress_metadata_t ingress, 214 | inout egress_metadata_t egress, 215 | ) { 216 | 217 | } 218 | -------------------------------------------------------------------------------- /p4/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use crate::lexer::{Kind, Lexer, Token}; 4 | use colored::Colorize; 5 | use std::fmt; 6 | use std::sync::Arc; 7 | 8 | #[derive(Debug)] 9 | pub struct SemanticError { 10 | /// Token where the error was encountered 11 | pub at: Token, 12 | 13 | /// Message associated with this error. 14 | pub message: String, 15 | 16 | /// The source line the token error occured on. 17 | pub source: String, 18 | } 19 | 20 | impl fmt::Display for SemanticError { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | fmt_common(&self.at, &self.message, &self.source, f) 23 | } 24 | } 25 | 26 | impl std::error::Error for SemanticError {} 27 | 28 | #[derive(Debug)] 29 | pub struct ParserError { 30 | /// Token where the error was encountered 31 | pub at: Token, 32 | 33 | /// Message associated with this error. 34 | pub message: String, 35 | 36 | /// The source line the token error occured on. 37 | pub source: String, 38 | } 39 | 40 | impl fmt::Display for ParserError { 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 | fmt_common(&self.at, &self.message, &self.source, f) 43 | } 44 | } 45 | 46 | impl std::error::Error for ParserError {} 47 | 48 | #[derive(Debug)] 49 | pub struct TokenError { 50 | /// Line where the token error was encountered. 51 | pub line: usize, 52 | 53 | /// Column where the token error was encountered. 54 | pub col: usize, 55 | 56 | /// Length of the erronious token. 57 | pub len: usize, 58 | 59 | /// The source line the token error occured on. 60 | pub source: String, 61 | 62 | /// The soruce file where the token error was encountered. 63 | pub file: Arc, 64 | } 65 | 66 | impl fmt::Display for TokenError { 67 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 68 | let at = Token { 69 | kind: Kind::Eof, 70 | line: self.line, 71 | col: self.col, 72 | file: Arc::new(self.source.clone()), 73 | }; 74 | fmt_common(&at, "unrecognized token", &self.source, f) 75 | } 76 | } 77 | 78 | impl std::error::Error for TokenError {} 79 | 80 | #[derive(Debug)] 81 | pub enum Error { 82 | Lexer(TokenError), 83 | Parser(ParserError), 84 | Semantic(Vec), 85 | } 86 | 87 | impl fmt::Display for Error { 88 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 89 | match &self { 90 | Self::Lexer(e) => e.fmt(f), 91 | Self::Parser(e) => e.fmt(f), 92 | Self::Semantic(errors) => { 93 | for e in &errors[..errors.len() - 1] { 94 | e.fmt(f)?; 95 | writeln!(f)?; 96 | } 97 | errors[errors.len() - 1].fmt(f)?; 98 | Ok(()) 99 | } 100 | } 101 | } 102 | } 103 | 104 | impl std::error::Error for Error {} 105 | 106 | impl From for Error { 107 | fn from(e: TokenError) -> Self { 108 | Self::Lexer(e) 109 | } 110 | } 111 | 112 | impl From for Error { 113 | fn from(e: ParserError) -> Self { 114 | Self::Parser(e) 115 | } 116 | } 117 | 118 | impl From> for Error { 119 | fn from(e: Vec) -> Self { 120 | Self::Semantic(e) 121 | } 122 | } 123 | 124 | #[derive(Debug)] 125 | pub struct PreprocessorError { 126 | /// Token where the error was encountered 127 | pub line: usize, 128 | 129 | /// Message associated with this error. 130 | pub message: String, 131 | 132 | /// The source line the token error occured on. 133 | pub source: String, 134 | 135 | /// The soruce file where the token error was encountered. 136 | pub file: Arc, 137 | } 138 | 139 | impl fmt::Display for PreprocessorError { 140 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 141 | let loc = format!("[{}]", self.line + 1).as_str().bright_red(); 142 | writeln!( 143 | f, 144 | "{}\n{} {}\n", 145 | self.message.bright_white(), 146 | loc, 147 | *self.file, 148 | )?; 149 | writeln!(f, " {}", self.source) 150 | } 151 | } 152 | 153 | impl std::error::Error for PreprocessorError {} 154 | 155 | fn carat_line(line: &str, at: &Token) -> String { 156 | // The presence of tabs makes presenting error indicators purely based 157 | // on column position impossible, so here we iterrate over the existing 158 | // string and mask out the non whitespace text inserting the error 159 | // indicators and preserving any tab/space mixture. 160 | let mut carat_line = String::new(); 161 | for x in line[..at.col].chars() { 162 | if x.is_whitespace() { 163 | carat_line.push(x); 164 | } else { 165 | carat_line.push(' '); 166 | } 167 | } 168 | for x in line[at.col..].chars() { 169 | if x.is_whitespace() || (Lexer::is_separator(x) && x != '.') { 170 | break; 171 | } else { 172 | carat_line.push('^'); 173 | } 174 | } 175 | carat_line 176 | } 177 | 178 | fn fmt_common( 179 | at: &Token, 180 | message: &str, 181 | source: &str, 182 | f: &mut fmt::Formatter<'_>, 183 | ) -> fmt::Result { 184 | let loc = format!("[{}:{}]", at.line + 1, at.col + 1) 185 | .as_str() 186 | .bright_red(); 187 | let parts: Vec<&str> = message.split_inclusive('\n').collect(); 188 | let msg = parts[0]; 189 | let extra = if parts.len() > 1 { 190 | parts[1..].join("") 191 | } else { 192 | "".to_string() 193 | }; 194 | writeln!( 195 | f, 196 | "{}: {}{}\n{} {}\n", 197 | "error".bright_red(), 198 | msg.bright_white().bold(), 199 | extra, 200 | loc, 201 | *at.file, 202 | )?; 203 | writeln!(f, " {}", source)?; 204 | 205 | let carat_line = carat_line(source, at); 206 | write!(f, " {}", carat_line.bright_red()) 207 | } 208 | -------------------------------------------------------------------------------- /p4/src/preprocessor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use crate::error::PreprocessorError; 4 | use std::fmt::Write; 5 | use std::sync::Arc; 6 | 7 | #[derive(Clone, Debug)] 8 | struct Macro { 9 | pub name: String, 10 | pub body: String, 11 | } 12 | 13 | #[derive(Debug, Default)] 14 | pub struct PreprocessorResult { 15 | pub elements: PreprocessorElements, 16 | pub lines: Vec, 17 | } 18 | 19 | #[derive(Debug, Default)] 20 | pub struct PreprocessorElements { 21 | pub includes: Vec, 22 | } 23 | 24 | pub fn run( 25 | source: &str, 26 | filename: Arc, 27 | ) -> Result { 28 | let mut result = PreprocessorResult::default(); 29 | let mut macros_to_process = Vec::new(); 30 | let mut current_macro: Option = None; 31 | 32 | // 33 | // first break the source up into lines 34 | // 35 | 36 | let lines: Vec<&str> = source.lines().collect(); 37 | let mut new_lines: Vec<&str> = Vec::new(); 38 | 39 | // 40 | // process each line of the input 41 | // 42 | 43 | for (i, line) in lines.iter().enumerate() { 44 | // 45 | // see if we're in a macro 46 | // 47 | 48 | match current_macro { 49 | None => {} 50 | Some(ref mut m) => { 51 | if !line.ends_with('\\') { 52 | write!(m.body, "\n{}", line).unwrap(); 53 | macros_to_process.push(m.clone()); 54 | current_macro = None; 55 | } else { 56 | write!(m.body, "\n{}", &line[..line.len() - 1]).unwrap(); 57 | } 58 | continue; 59 | } 60 | } 61 | 62 | // 63 | // collect includes 64 | // 65 | 66 | if line.starts_with("#include") { 67 | process_include(i, line, &mut result, &filename)?; 68 | new_lines.push(""); 69 | continue; 70 | } 71 | 72 | // 73 | // collect macros 74 | // 75 | 76 | if line.starts_with("#define") { 77 | let (name, value) = process_macro_begin(i, line, &filename)?; 78 | let m = Macro { name, body: value }; 79 | if !line.ends_with('\\') { 80 | macros_to_process.push(m.clone()); 81 | current_macro = None; 82 | } else { 83 | current_macro = Some(m); 84 | } 85 | new_lines.push(""); 86 | continue; 87 | } 88 | 89 | // 90 | // if we are here, this is not a line to be pre-processed 91 | // 92 | 93 | new_lines.push(line) 94 | } 95 | 96 | //println!("macros to process\n{:#?}", macros_to_process); 97 | 98 | // 99 | // process macros 100 | // 101 | for line in &new_lines { 102 | let mut l = line.to_string(); 103 | for m in ¯os_to_process { 104 | l = l.replace(&m.name, &m.body); 105 | } 106 | result.lines.push(l); 107 | } 108 | 109 | Ok(result) 110 | } 111 | 112 | fn process_include( 113 | i: usize, 114 | line: &str, 115 | result: &mut PreprocessorResult, 116 | filename: &Arc, 117 | ) -> Result<(), PreprocessorError> { 118 | let (begin, end) = if let Some(begin) = line.find('<') { 119 | match line[begin..].find('>') { 120 | Some(end) => (begin + 1, begin + end), 121 | None => { 122 | return Err(PreprocessorError { 123 | line: i, 124 | message: "Unterminated '<'".into(), 125 | source: line.to_string(), 126 | file: filename.clone(), 127 | }) 128 | } 129 | } 130 | } else if let Some(begin) = line.find('"') { 131 | // The file name is quoted by same character " 132 | // So, we need to find the next " after the first " 133 | let begin = begin + 1; 134 | match line[begin..].find('"') { 135 | Some(end) => (begin, begin + end), 136 | None => { 137 | return Err(PreprocessorError { 138 | line: i, 139 | message: "Unterminated '\"'".into(), 140 | source: line.to_string(), 141 | file: filename.clone(), 142 | }) 143 | } 144 | } 145 | } else { 146 | return Err(PreprocessorError { 147 | line: i, 148 | message: "Invalid #include".into(), 149 | source: line.to_string(), 150 | file: filename.clone(), 151 | }); 152 | }; 153 | 154 | if end < line.len() { 155 | for c in line[end + 1..].chars() { 156 | if !c.is_whitespace() { 157 | return Err(PreprocessorError { 158 | line: i, 159 | message: format!( 160 | "Unexpected character after #include '{}'", 161 | c, 162 | ), 163 | source: line.to_string(), 164 | file: filename.clone(), 165 | }); 166 | } 167 | } 168 | } 169 | result.elements.includes.push(line[begin..end].into()); 170 | 171 | Ok(()) 172 | } 173 | 174 | fn process_macro_begin( 175 | i: usize, 176 | line: &str, 177 | filename: &Arc, 178 | ) -> Result<(String, String), PreprocessorError> { 179 | let mut parts = line.split_whitespace(); 180 | // discard #define 181 | parts.next(); 182 | 183 | let name = match parts.next() { 184 | Some(n) => n.into(), 185 | None => { 186 | return Err(PreprocessorError { 187 | line: i, 188 | message: "Macros must have a name".into(), 189 | source: line.to_string(), 190 | file: filename.clone(), 191 | }) 192 | } 193 | }; 194 | 195 | let value = match parts.next() { 196 | Some(v) => v.into(), 197 | None => "".into(), 198 | }; 199 | 200 | Ok((name, value)) 201 | } 202 | -------------------------------------------------------------------------------- /codegen/rust/src/header.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use crate::{rust_type, type_size, Context}; 4 | use p4::ast::{Header, AST}; 5 | use quote::{format_ident, quote}; 6 | 7 | pub(crate) struct HeaderGenerator<'a> { 8 | ast: &'a AST, 9 | ctx: &'a mut Context, 10 | } 11 | 12 | impl<'a> HeaderGenerator<'a> { 13 | pub(crate) fn new(ast: &'a AST, ctx: &'a mut Context) -> Self { 14 | Self { ast, ctx } 15 | } 16 | 17 | pub(crate) fn generate(&mut self) { 18 | for h in &self.ast.headers { 19 | self.generate_header(h); 20 | } 21 | } 22 | 23 | fn generate_header(&mut self, h: &Header) { 24 | let name = format_ident!("{}", h.name); 25 | 26 | // 27 | // genrate a rust struct for the header 28 | // 29 | 30 | // generate struct members 31 | let mut members = Vec::new(); 32 | for member in &h.members { 33 | let name = format_ident!("{}", member.name); 34 | let ty = rust_type(&member.ty); 35 | members.push(quote! { pub #name: #ty }); 36 | } 37 | 38 | let mut generated = quote! { 39 | #[derive(Debug, Default, Clone)] 40 | pub struct #name { 41 | pub valid: bool, 42 | #(#members),* 43 | } 44 | }; 45 | 46 | // 47 | // generate a constructor that maps the header onto a byte slice 48 | // 49 | 50 | // generate member assignments 51 | let mut member_values = Vec::new(); 52 | let mut set_statements = Vec::new(); 53 | let mut to_bitvec_statements = Vec::new(); 54 | let mut checksum_statements = Vec::new(); 55 | let mut dump_statements = Vec::new(); 56 | let fmt = "{} ".repeat(h.members.len() * 2); 57 | let fmt = fmt.trim(); 58 | let mut offset = 0; 59 | for member in &h.members { 60 | let name = format_ident!("{}", member.name); 61 | let name_s = &member.name; 62 | let size = type_size(&member.ty, self.ast); 63 | member_values.push(quote! { 64 | #name: BitVec::::default() 65 | }); 66 | let end = offset + size; 67 | set_statements.push(quote! { 68 | self.#name = { 69 | let mut b = buf.view_bits::()[#offset..#end].to_owned(); 70 | // NOTE this barfing and then unbarfing a vec is to handle 71 | // the p4 confused-endian data model. 72 | if #end-#offset > 8 { 73 | let mut v = b.into_vec(); 74 | v.reverse(); 75 | if ((#end-#offset) % 8) != 0 { 76 | if let Some(x) = v.iter_mut().last() { 77 | *x <<= (#offset % 8); 78 | } 79 | } 80 | let mut b = BitVec::::from_vec(v); 81 | b.resize(#end-#offset, false); 82 | b 83 | } else { 84 | b 85 | } 86 | } 87 | }); 88 | to_bitvec_statements.push(quote! { 89 | // NOTE this barfing and then unbarfing a vec is to handle 90 | // the p4 confused-endian data model. 91 | if #end-#offset > 8 { 92 | let mut v = self.#name.clone().into_vec(); 93 | if ((#end-#offset) % 8) != 0 { 94 | if let Some(x) = v.iter_mut().last() { 95 | *x >>= ((#end - #offset) % 8); 96 | } 97 | } 98 | v.reverse(); 99 | let n = (#end-#offset); 100 | let m = n%8; 101 | let mut b = BitVec::::from_vec(v); 102 | // this field may be null so check that we at least have 103 | // enough space to offset 104 | if b.len() > m { 105 | x[#offset..#end] |= &b[m..]; 106 | } 107 | } else { 108 | x[#offset..#end] |= self.#name.to_owned(); 109 | } 110 | 111 | }); 112 | checksum_statements.push(quote! { 113 | csum = p4rs::bitmath::add_le(csum.clone(), self.#name.csum()) 114 | }); 115 | dump_statements.push(quote! { 116 | #name_s.cyan(), 117 | p4rs::dump_bv(&self.#name) 118 | }); 119 | 120 | offset += size; 121 | } 122 | let dump = quote! { 123 | format!(#fmt, #(#dump_statements),*) 124 | }; 125 | 126 | //TODO perhaps we should just keep the whole header as one bitvec so we 127 | //don't need to construct a consolidated bitvec like to_bitvec does? 128 | generated.extend(quote! { 129 | impl Header for #name { 130 | fn new() -> Self { 131 | Self { 132 | valid: false, 133 | #(#member_values),* 134 | } 135 | } 136 | fn set( 137 | &mut self, 138 | buf: &[u8] 139 | ) -> Result<(), TryFromSliceError> { 140 | #(#set_statements);*; 141 | Ok(()) 142 | } 143 | fn size() -> usize { 144 | #offset 145 | } 146 | fn set_valid(&mut self) { 147 | self.valid = true; 148 | } 149 | fn set_invalid(&mut self) { 150 | self.valid = false; 151 | } 152 | fn is_valid(&self) -> bool { 153 | self.valid 154 | } 155 | fn to_bitvec(&self) -> BitVec { 156 | let mut x = bitvec![u8, Msb0; 0u8; Self::size()]; 157 | #(#to_bitvec_statements);*; 158 | x 159 | } 160 | } 161 | 162 | impl Checksum for #name { 163 | fn csum(&self) -> BitVec:: { 164 | let mut csum = BitVec::new(); 165 | #(#checksum_statements);*; 166 | csum 167 | } 168 | } 169 | 170 | impl #name { 171 | fn setValid(&mut self) { 172 | self.valid = true; 173 | } 174 | fn setInvalid(&mut self) { 175 | self.valid = false; 176 | } 177 | fn isValid(&self) -> bool { 178 | self.valid 179 | } 180 | fn dump(&self) -> String { 181 | if self.isValid() { 182 | #dump 183 | } else { 184 | "∅".to_owned() 185 | } 186 | } 187 | } 188 | }); 189 | 190 | self.ctx.structs.insert(h.name.clone(), generated); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lang/p4rs/src/bitmath.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use bitvec::prelude::*; 4 | 5 | pub fn add_be(a: BitVec, b: BitVec) -> BitVec { 6 | let len = usize::max(a.len(), b.len()); 7 | 8 | // P4 spec says width limits are architecture defined, i here by define 9 | // softnpu to have an architectural bit-type width limit of 128. 10 | let x: u128 = a.load_be(); 11 | let y: u128 = b.load_be(); 12 | let z = x.wrapping_add(y); 13 | let mut c = BitVec::new(); 14 | c.resize(len, false); 15 | c.store_be(z); 16 | c 17 | } 18 | 19 | pub fn add_le(a: BitVec, b: BitVec) -> BitVec { 20 | let len = usize::max(a.len(), b.len()); 21 | 22 | // P4 spec says width limits are architecture defined, i here by define 23 | // softnpu to have an architectural bit-type width limit of 128. 24 | let x: u128 = a.load_le(); 25 | let y: u128 = b.load_le(); 26 | let z = x.wrapping_add(y); 27 | let mut c = BitVec::new(); 28 | c.resize(len, false); 29 | c.store_le(z); 30 | c 31 | } 32 | 33 | pub fn sub_be(a: BitVec, b: BitVec) -> BitVec { 34 | let len = usize::max(a.len(), b.len()); 35 | let x: u128 = a.load_be(); 36 | let y: u128 = b.load_be(); 37 | let z = x.wrapping_sub(y); 38 | let mut c = BitVec::new(); 39 | c.resize(len, false); 40 | c.store_be(z); 41 | c 42 | } 43 | 44 | pub fn sub_le(a: BitVec, b: BitVec) -> BitVec { 45 | let len = usize::max(a.len(), b.len()); 46 | let x: u128 = a.load_le(); 47 | let y: u128 = b.load_le(); 48 | let z = x.wrapping_sub(y); 49 | let mut c = BitVec::new(); 50 | c.resize(len, false); 51 | c.store_le(z); 52 | c 53 | } 54 | 55 | // leaving here in case we have a need for a true arbitrary-width adder. 56 | #[allow(dead_code)] 57 | pub fn add_generic( 58 | a: BitVec, 59 | b: BitVec, 60 | ) -> BitVec { 61 | if a.len() != b.len() { 62 | panic!("bitvec add size mismatch"); 63 | } 64 | let mut c = BitVec::new(); 65 | c.resize(a.len(), false); 66 | 67 | for i in (1..a.len()).rev() { 68 | let y = c[i]; 69 | let x = a[i] ^ b[i]; 70 | if !(a[i] | b[i]) { 71 | continue; 72 | } 73 | c.set(i, x ^ y); 74 | let mut z = (a[i] && b[i]) | y; 75 | for j in (1..i).rev() { 76 | if !z { 77 | break; 78 | } 79 | z = c[j]; 80 | c.set(j, true); 81 | } 82 | } 83 | 84 | c 85 | } 86 | 87 | pub fn mod_be(a: BitVec, b: BitVec) -> BitVec { 88 | let len = usize::max(a.len(), b.len()); 89 | 90 | // P4 spec says width limits are architecture defined, i here by define 91 | // softnpu to have an architectural bit-type width limit of 128. 92 | let x: u128 = a.load_be(); 93 | let y: u128 = b.load_be(); 94 | let z = x % y; 95 | let mut c = BitVec::new(); 96 | c.resize(len, false); 97 | c.store_be(z); 98 | c 99 | } 100 | 101 | pub fn mod_le(a: BitVec, b: BitVec) -> BitVec { 102 | let len = usize::max(a.len(), b.len()); 103 | 104 | // P4 spec says width limits are architecture defined, i here by define 105 | // softnpu to have an architectural bit-type width limit of 128. 106 | let x: u128 = a.load_le(); 107 | let y: u128 = b.load_le(); 108 | let z = x % y; 109 | let mut c = BitVec::new(); 110 | c.resize(len, false); 111 | c.store_le(z); 112 | c 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::*; 118 | 119 | #[test] 120 | fn bitmath_add_be() { 121 | let mut a = bitvec![mut u8, Msb0; 0; 16]; 122 | a.store_be(47); 123 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 124 | b.store_be(74); 125 | 126 | println!("{:?}", a); 127 | println!("{:?}", b); 128 | let c = add_be(a, b); 129 | println!("{:?}", c); 130 | 131 | let cc: u128 = c.load_be(); 132 | assert_eq!(cc, 47u128 + 74u128); 133 | } 134 | 135 | #[test] 136 | fn bitmath_add_le() { 137 | let mut a = bitvec![mut u8, Msb0; 0; 16]; 138 | a.store_le(47); 139 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 140 | b.store_le(74); 141 | 142 | println!("{:?}", a); 143 | println!("{:?}", b); 144 | let c = add_le(a, b); 145 | println!("{:?}", c); 146 | 147 | let cc: u128 = c.load_le(); 148 | assert_eq!(cc, 47u128 + 74u128); 149 | } 150 | 151 | #[test] 152 | fn bitmath_sub_be() { 153 | use super::*; 154 | let mut a = bitvec![mut u8, Msb0; 0; 16]; 155 | a.store_be(74); 156 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 157 | b.store_be(47); 158 | 159 | println!("{:?}", a); 160 | println!("{:?}", b); 161 | let c = sub_be(a, b); 162 | println!("{:?}", c); 163 | 164 | let cc: u128 = c.load_be(); 165 | assert_eq!(cc, 74u128 - 47u128); 166 | } 167 | 168 | #[test] 169 | fn bitmath_sub_le() { 170 | use super::*; 171 | let mut a = bitvec![mut u8, Msb0; 0; 16]; 172 | a.store_le(74); 173 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 174 | b.store_le(47); 175 | 176 | println!("{:?}", a); 177 | println!("{:?}", b); 178 | let c = sub_le(a, b); 179 | println!("{:?}", c); 180 | 181 | let cc: u128 = c.load_le(); 182 | assert_eq!(cc, 74u128 - 47u128); 183 | } 184 | 185 | #[test] 186 | fn bitmath_add_mixed_size() { 187 | use super::*; 188 | let mut a = bitvec![mut u8, Msb0; 0; 8]; 189 | a.store_be(0xAB); 190 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 191 | b.store_be(0xCDE); 192 | 193 | println!("{:?}", a); 194 | println!("{:?}", b); 195 | let c = add_be(a, b); 196 | println!("{:?}", c); 197 | 198 | let cc: u128 = c.load_be(); 199 | assert_eq!(cc, 0xABu128 + 0xCDEu128); 200 | } 201 | 202 | #[test] 203 | fn bitmath_add_cascade() { 204 | use super::*; 205 | let mut a = bitvec![mut u8, Msb0; 0; 16]; 206 | a.store_be(47); 207 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 208 | b.store_be(74); 209 | let mut c = bitvec![mut u8, Msb0; 0; 16]; 210 | c.store_be(123); 211 | let mut d = bitvec![mut u8, Msb0; 0; 16]; 212 | d.store_be(9876); 213 | 214 | let e = add_be(a, add_be(b, add_be(c, d))); 215 | 216 | let ee: u128 = e.load_be(); 217 | assert_eq!(ee, 47u128 + 74u128 + 123u128 + 9876u128); 218 | } 219 | 220 | #[test] 221 | fn bitmath_add_nest() { 222 | use super::*; 223 | let mut orig_l3_len = bitvec![mut u8, Msb0; 0; 16usize]; 224 | orig_l3_len.store_le(0xe9u128); 225 | let x = add_le( 226 | { 227 | let mut x = bitvec![mut u8, Msb0; 0; 16usize]; 228 | x.store_le(14u128); 229 | x 230 | }, 231 | add_le( 232 | orig_l3_len.clone(), 233 | add_le( 234 | { 235 | let mut x = bitvec![mut u8, Msb0; 0; 16usize]; 236 | x.store_le(8u128); 237 | x 238 | }, 239 | { 240 | let mut x = bitvec![mut u8, Msb0; 0; 16usize]; 241 | x.store_le(8u128); 242 | x 243 | }, 244 | ), 245 | ), 246 | ); 247 | 248 | let y: u128 = x.load_le(); 249 | assert_eq!(y, 0xe9 + 14 + 8 + 8); 250 | } 251 | 252 | #[test] 253 | fn bitmath_mod() { 254 | use super::*; 255 | let mut a = bitvec![mut u8, Msb0; 0; 16]; 256 | a.store_be(47); 257 | let mut b = bitvec![mut u8, Msb0; 0; 16]; 258 | b.store_be(7); 259 | 260 | println!("{:?}", a); 261 | println!("{:?}", b); 262 | let c = mod_be(a, b); 263 | println!("{:?}", c); 264 | 265 | let cc: u128 = c.load_be(); 266 | assert_eq!(cc, 47u128 % 7u128); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /test/src/p4/decap.p4: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftNPU( 6 | parse(), 7 | ingress(), 8 | egress() 9 | ) main; 10 | 11 | struct headers_t { 12 | ethernet_h ethernet; 13 | sidecar_h sidecar; 14 | arp_h arp; 15 | ipv4_h ipv4; 16 | ipv6_h ipv6; 17 | 18 | ddm_h ddm; 19 | // The ddm original p4 code used a header stack, but Intel says this is not 20 | // efficient on Tofino, and x4c does not currently support header stacks. So 21 | // the following is an unrolled version. This is not easy on the eyes. 22 | ddm_element_t ddm0; 23 | ddm_element_t ddm1; 24 | ddm_element_t ddm2; 25 | ddm_element_t ddm3; 26 | ddm_element_t ddm4; 27 | ddm_element_t ddm5; 28 | ddm_element_t ddm6; 29 | ddm_element_t ddm7; 30 | ddm_element_t ddm8; 31 | ddm_element_t ddm9; 32 | ddm_element_t ddm10; 33 | ddm_element_t ddm11; 34 | ddm_element_t ddm12; 35 | ddm_element_t ddm13; 36 | ddm_element_t ddm14; 37 | ddm_element_t ddm15; 38 | 39 | icmp_h icmp; 40 | tcp_h tcp; 41 | udp_h udp; 42 | 43 | geneve_h geneve; 44 | ethernet_h inner_eth; 45 | ipv4_h inner_ipv4; 46 | ipv6_h inner_ipv6; 47 | tcp_h inner_tcp; 48 | udp_h inner_udp; 49 | } 50 | 51 | parser parse( 52 | packet_in pkt, 53 | out headers_t hdr, 54 | inout ingress_metadata_t ingress, 55 | ){ 56 | state start { 57 | pkt.extract(hdr.ethernet); 58 | if (hdr.ethernet.ether_type == 16w0x0800) { 59 | transition ipv4; 60 | } 61 | if (hdr.ethernet.ether_type == 16w0x86dd) { 62 | transition ipv6; 63 | } 64 | if (hdr.ethernet.ether_type == 16w0x0901) { 65 | transition sidecar; 66 | } 67 | if (hdr.ethernet.ether_type == 16w0x0806) { 68 | transition arp; 69 | } 70 | transition reject; 71 | } 72 | 73 | state sidecar { 74 | pkt.extract(hdr.sidecar); 75 | if (hdr.sidecar.sc_ether_type == 16w0x86dd) { 76 | transition ipv6; 77 | } 78 | if (hdr.sidecar.sc_ether_type == 16w0x0800) { 79 | transition ipv4; 80 | } 81 | transition reject; 82 | } 83 | 84 | state arp { 85 | pkt.extract(hdr.arp); 86 | transition accept; 87 | } 88 | 89 | state ipv6 { 90 | pkt.extract(hdr.ipv6); 91 | if (hdr.ipv6.next_hdr == 8w0xdd) { 92 | transition ddm; 93 | } 94 | if (hdr.ipv6.next_hdr == 8w58) { 95 | transition icmp; 96 | } 97 | if (hdr.ipv6.next_hdr == 8w17) { 98 | transition udp; 99 | } 100 | if (hdr.ipv6.next_hdr == 8w6) { 101 | transition tcp; 102 | } 103 | transition accept; 104 | } 105 | 106 | state ddm { 107 | pkt.extract(hdr.ddm); 108 | if (hdr.ddm.header_length >= 8w7) { pkt.extract(hdr.ddm0); } 109 | if (hdr.ddm.header_length >= 8w11) { pkt.extract(hdr.ddm1); } 110 | if (hdr.ddm.header_length >= 8w15) { pkt.extract(hdr.ddm2); } 111 | if (hdr.ddm.header_length >= 8w19) { pkt.extract(hdr.ddm3); } 112 | if (hdr.ddm.header_length >= 8w23) { pkt.extract(hdr.ddm4); } 113 | if (hdr.ddm.header_length >= 8w27) { pkt.extract(hdr.ddm5); } 114 | if (hdr.ddm.header_length >= 8w31) { pkt.extract(hdr.ddm6); } 115 | if (hdr.ddm.header_length >= 8w35) { pkt.extract(hdr.ddm7); } 116 | if (hdr.ddm.header_length >= 8w39) { pkt.extract(hdr.ddm8); } 117 | if (hdr.ddm.header_length >= 8w43) { pkt.extract(hdr.ddm9); } 118 | if (hdr.ddm.header_length >= 8w47) { pkt.extract(hdr.ddm10); } 119 | if (hdr.ddm.header_length >= 8w51) { pkt.extract(hdr.ddm11); } 120 | if (hdr.ddm.header_length >= 8w55) { pkt.extract(hdr.ddm12); } 121 | if (hdr.ddm.header_length >= 8w59) { pkt.extract(hdr.ddm13); } 122 | if (hdr.ddm.header_length >= 8w63) { pkt.extract(hdr.ddm14); } 123 | if (hdr.ddm.header_length >= 8w67) { pkt.extract(hdr.ddm15); } 124 | transition accept; 125 | } 126 | 127 | state icmp { 128 | pkt.extract(hdr.icmp); 129 | ingress.nat_id = hdr.icmp.data[15:0]; 130 | transition accept; 131 | } 132 | 133 | state ipv4 { 134 | pkt.extract(hdr.ipv4); 135 | if (hdr.ipv4.protocol == 8w17) { 136 | transition udp; 137 | } 138 | if (hdr.ipv4.protocol == 8w6) { 139 | transition tcp; 140 | } 141 | transition accept; 142 | } 143 | 144 | state udp { 145 | pkt.extract(hdr.udp); 146 | ingress.nat_id = hdr.udp.dst_port; 147 | if (hdr.udp.dst_port == 16w6081) { 148 | transition geneve; 149 | } 150 | transition accept; 151 | } 152 | 153 | state tcp { 154 | pkt.extract(hdr.tcp); 155 | ingress.nat_id = hdr.tcp.dst_port; 156 | transition accept; 157 | } 158 | 159 | state geneve { 160 | pkt.extract(hdr.geneve); 161 | transition inner_eth; 162 | } 163 | 164 | state inner_eth { 165 | pkt.extract(hdr.inner_eth); 166 | if (hdr.inner_eth.ether_type == 16w0x0800) { 167 | transition inner_ipv4; 168 | } 169 | if (hdr.inner_eth.ether_type == 16w0x86dd) { 170 | transition inner_ipv6; 171 | } 172 | transition reject; 173 | } 174 | 175 | state inner_ipv4 { 176 | pkt.extract(hdr.inner_ipv4); 177 | if (hdr.inner_ipv4.protocol == 8w17) { 178 | transition inner_udp; 179 | } 180 | if (hdr.inner_ipv4.protocol == 8w6) { 181 | transition inner_tcp; 182 | } 183 | transition accept; 184 | } 185 | 186 | state inner_ipv6 { 187 | pkt.extract(hdr.inner_ipv6); 188 | if (hdr.inner_ipv6.next_hdr == 8w17) { 189 | transition inner_udp; 190 | } 191 | if (hdr.inner_ipv6.next_hdr == 8w6) { 192 | transition inner_tcp; 193 | } 194 | transition accept; 195 | } 196 | 197 | state inner_udp { 198 | pkt.extract(hdr.inner_udp); 199 | transition accept; 200 | } 201 | 202 | state inner_tcp { 203 | pkt.extract(hdr.inner_tcp); 204 | transition accept; 205 | } 206 | 207 | } 208 | 209 | control ingress( 210 | inout headers_t hdr, 211 | inout ingress_metadata_t ingress, 212 | inout egress_metadata_t egress, 213 | ) { 214 | 215 | apply { 216 | if (hdr.ethernet.isValid()) { 217 | //egress.port = 16w1; 218 | } 219 | if (hdr.ipv6.isValid()) { 220 | //egress.port = 16w1; 221 | } 222 | if (hdr.udp.isValid()) { 223 | //egress.port = 16w1; 224 | } 225 | if (hdr.geneve.isValid()) { 226 | // strip the geneve header and try to route 227 | hdr.geneve.setInvalid(); 228 | hdr.ethernet = hdr.inner_eth; 229 | hdr.inner_eth.setInvalid(); 230 | if (hdr.inner_ipv4.isValid()) { 231 | hdr.ipv4 = hdr.inner_ipv4; 232 | //hdr.ipv4.version = 4w4; 233 | //hdr.ipv4.ihl = 4w5; 234 | hdr.ipv4.setValid(); 235 | hdr.ipv6.setInvalid(); 236 | hdr.inner_ipv4.setInvalid(); 237 | } 238 | if (hdr.inner_ipv6.isValid()) { 239 | hdr.ipv6 = hdr.inner_ipv6; 240 | hdr.ipv6.setValid(); 241 | hdr.ipv4.setInvalid(); 242 | hdr.inner_ipv6.setInvalid(); 243 | } 244 | if (hdr.inner_tcp.isValid()) { 245 | hdr.tcp = hdr.inner_tcp; 246 | hdr.udp.setInvalid(); 247 | hdr.tcp.setValid(); 248 | hdr.inner_tcp.setInvalid(); 249 | } 250 | if (hdr.inner_udp.isValid()) { 251 | hdr.udp = hdr.inner_udp; 252 | hdr.udp.setValid(); 253 | hdr.inner_udp.setInvalid(); 254 | } 255 | egress.port = 16w1; 256 | } 257 | } 258 | 259 | } 260 | 261 | control egress( 262 | inout headers_t hdr, 263 | inout ingress_metadata_t ingress, 264 | inout egress_metadata_t egress, 265 | ) { 266 | 267 | } 268 | -------------------------------------------------------------------------------- /book/text/src/01-03-compile_and_run.md: -------------------------------------------------------------------------------- 1 | # Compile and Run 2 | 3 | In the previous section we put together a hello world P4 program. In this 4 | section we run that program over a software ASIC called SoftNpu. One of the 5 | capabilities of the `x4c` compiler is using P4 code directly from Rust code 6 | and we'll be doing that in this example. 7 | 8 | Below is a Rust program that imports the P4 code developed in the last section, 9 | loads it onto a SoftNpu ASIC instance, and sends some packets through it. We'll 10 | be looking at this program piece-by-piece in the remainder of this section. 11 | 12 | All of the programs in this book are available as buildable programs in the 13 | [oxidecomputer/p4](https://github.com/oxidecomputer/p4) repository in the 14 | `book/code` directory. 15 | 16 | ```rust 17 | use tests::softnpu::{RxFrame, SoftNpu, TxFrame}; 18 | use tests::{expect_frames}; 19 | 20 | const NUM_PORTS: u16 = 3; 21 | 22 | p4_macro::use_p4!(p4 = "book/code/src/bin/hello-world.p4", pipeline_name = "hello"); 23 | 24 | fn main() -> Result<(), anyhow::Error> { 25 | let pipeline = main_pipeline::new(NUM_PORTS); 26 | let mut npu = SoftNpu::new(NUM_PORTS, pipeline, false); 27 | let phy1 = npu.phy(0); 28 | let phy2 = npu.phy(1); 29 | let phy3 = npu.phy(2); 30 | 31 | npu.run(); 32 | 33 | // Expect this packet to be dropped 34 | phy3.send(&[TxFrame::new(phy3.mac, 0, b"to the bit bucket with you!")])?; 35 | 36 | phy1.send(&[TxFrame::new(phy2.mac, 0, b"hello")])?; 37 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0, b"hello")]); 38 | 39 | phy2.send(&[TxFrame::new(phy1.mac, 0, b"world")])?; 40 | expect_frames!(phy1, &[RxFrame::new(phy2.mac, 0, b"world")]); 41 | 42 | Ok(()) 43 | } 44 | ``` 45 | 46 | The program starts with a few Rust imports. 47 | 48 | ```rust 49 | use tests::softnpu::{RxFrame, SoftNpu, TxFrame}; 50 | use tests::{expect_frames}; 51 | ``` 52 | 53 | This first line is the SoftNpu implementation that lives in the `test` crate of 54 | the `oxidecomputer/p4` repository. The second is a helper macro that allows us 55 | to make assertions about frames coming from a SoftNpu "physical" port (referred 56 | to as a phy). 57 | 58 | The next line is using the `x4c` compiler to translate P4 code into Rust code 59 | and dumping that Rust code into our program. The macro literally expands into 60 | the Rust code emitted by the compiler for the specified P4 source file. 61 | 62 | ```rust 63 | p4_macro::use_p4!(p4 = "book/code/src/bin/hello-world.p4", pipeline_name = "hello"); 64 | ``` 65 | 66 | The main artifact this produces is a Rust `struct` called `main_pipeline` which is used 67 | in the code that comes next. 68 | 69 | ```rust 70 | let pipeline = main_pipeline::new(NUM_PORTS); 71 | let mut npu = SoftNpu::new(NUM_PORTS, pipeline, false); 72 | let phy1 = npu.phy(0); 73 | let phy2 = npu.phy(1); 74 | let phy3 = npu.phy(2); 75 | ``` 76 | 77 | This code is instantiating a pipeline object that encapsulates the logic of our 78 | P4 program. Then a SoftNpu ASIC is constructed with three ports and our pipeline 79 | program. SoftNpu objects provide a `phy` method that takes a port index to get a 80 | reference to a port that is attached to the ASIC. These port objects are used to 81 | send and receive packets through the ASIC, which uses our compiled P4 code to 82 | process those packets. 83 | 84 | Next we run our program on the SoftNpu ASIC. 85 | 86 | ```rust 87 | npu.run(); 88 | ``` 89 | 90 | However, this does not actually do anything until we pass some packets through 91 | it, so lets do that. 92 | 93 | ```rust 94 | // Expect this packet to be dropped 95 | phy3.send(&[TxFrame::new(phy3.mac, 0, b"to the bit bucket with you!")])?; 96 | ``` 97 | 98 | This code transmit an Ethernet frame through the third port of the 99 | ASIC with a payload value of "to the bit bucket with you!". The 100 | `phy3.mac` parameter of the `TxFrame` sets the destination MAC address 101 | and the `0` for the second parameter is the ethertype used in the 102 | outgoing Ethernet frame. 103 | 104 | Based on the logic in our P4 program, we would expect this packet to 105 | be dropped by the switch, i.e. it will not be sent out of any port at 106 | all. This is because the table lookup on the ingress port value of 2 107 | would get a miss, and the table would execute the default action 108 | `drop`. Thus we do not call `expect_frames!` here, as we do for the 109 | test packets below. 110 | 111 | ```rust 112 | phy1.send(&[TxFrame::new(phy2.mac, 0, b"hello")])?; 113 | ``` 114 | 115 | This code transmits an Ethernet frame through the first port of the ASIC with a 116 | payload value of `"hello"`. 117 | 118 | Based on the logic in our P4 program, we would expect this packet to come out 119 | the second port. Let's test that. 120 | 121 | ```rust 122 | expect_frames!(phy2, &[RxFrame::new(phy1.mac, 0, b"hello")]); 123 | ``` 124 | 125 | This code reads a packet from the second ASIC port `phy2` (blocking until there 126 | is a packet available) and asserts the following. 127 | 128 | - The Ethernet payload is the byte string `"hello"`. 129 | - The source MAC address is that of `phy1`. 130 | - The ethertype is `0`. 131 | 132 | To complete the hello world program, we do the same thing in the opposite 133 | direction. Sending the byte string `"world"` as an Ethernet payload into port 2 134 | and assert that it comes out port 1. 135 | 136 | ```rust 137 | phy2.send(&[TxFrame::new(phy1.mac, 0, b"world")])?; 138 | expect_frames!(phy1, &[RxFrame::new(phy2.mac, 0, b"world")]); 139 | ``` 140 | 141 | The `expect_frames` macro will also print payloads and the port they came from. 142 | 143 | When we run this program we see the following. 144 | 145 | ```bash 146 | $ cargo run --bin hello-world 147 | Compiling x4c-book v0.1.0 (/home/ry/src/p4/book/code) 148 | Finished dev [unoptimized + debuginfo] target(s) in 2.05s 149 | Running `target/debug/hello-world` 150 | [phy2] hello 151 | [phy1] world 152 | ``` 153 | 154 | ## SoftNpu and Target `x4c` Use Cases. 155 | 156 | The example above shows using `x4c` compiled code is a setting that is only 157 | really useful for testing the logic of compiled pipelines and demonstrating how 158 | P4 and `x4c` compiled pipelines work. This begs the question of what the target 159 | use cases for `x4c` actually are. It also raises question, why build `x4c` in the 160 | first place? Why not use the established reference compiler `p4c` and its 161 | associated reference behavioral model `bmv2`? 162 | 163 | _A key difference between `x4c` and the `p4c` ecosystem is how compilation 164 | and execution concerns are separated. `x4c` generates free-standing pipelines 165 | that can be used by other code, `p4c` generates JSON that is interpreted and run 166 | by `bmv2`_. 167 | 168 | The example above shows how the generation of free-standing runnable pipelines 169 | can be used to test the logic of P4 programs in a lightweight way. We went from 170 | P4 program source to actual packet processing using nothing but the Rust 171 | compiler and package manager. The program is executable in an operating system 172 | independent way and is a great way to get CI going for P4 programs. 173 | 174 | The free-standing pipeline approach is not limited to self-contained use cases 175 | with packets that are generated and consumed in-program. `x4c` generated code 176 | conforms to a well defined 177 | [`Pipeline`](https://oxidecomputer.github.io/p4/p4rs/index.html) 178 | interface that can be used to run pipelines anywhere `rustc` compiled code can 179 | run. Pipelines are even dynamically loadable through `dlopen` and the like. 180 | 181 | The `x4c` authors have used `x4c` generated pipelines to create virtual ASICs 182 | inside hypervisors that transit real traffic between virtual machines, as well 183 | as P4 programs running inside zones/containers that implement NAT and tunnel 184 | encap/decap capabilities. The mechanics of I/O are deliberately outside the 185 | scope of `x4c` generated code. Whether you want to use DLPI, XDP, libpcap, 186 | PF\_RING, DPDK, etc., is up to you and the harness code you write around your 187 | pipelines! 188 | 189 | The win with `x4c` is flexibility. You can compile a free-standing P4 pipeline 190 | and use that pipeline wherever you see fit. The near-term use for `x4c` focuses 191 | on development and evaluation environments. If you are building a system around 192 | P4 programmable components, but it's not realistic to buy all the 193 | switches/routers/ASICs at the scale you need for testing/development, `x4c` is an 194 | option. `x4c` is also a good option for running packets through your pipelines 195 | in a lightweight way in CI. 196 | -------------------------------------------------------------------------------- /codegen/rust/src/expression.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | use p4::ast::{BinOp, DeclarationInfo, Expression, ExpressionKind, Lvalue}; 4 | use p4::hlir::Hlir; 5 | use proc_macro2::TokenStream; 6 | use quote::{format_ident, quote}; 7 | 8 | pub(crate) struct ExpressionGenerator<'a> { 9 | hlir: &'a Hlir, 10 | } 11 | 12 | impl<'a> ExpressionGenerator<'a> { 13 | pub fn new(hlir: &'a Hlir) -> Self { 14 | Self { hlir } 15 | } 16 | 17 | pub(crate) fn generate_expression(&self, xpr: &Expression) -> TokenStream { 18 | match &xpr.kind { 19 | ExpressionKind::BoolLit(v) => { 20 | quote! { #v } 21 | } 22 | ExpressionKind::IntegerLit(v) => { 23 | quote! { #v } 24 | } 25 | ExpressionKind::BitLit(width, v) => { 26 | self.generate_bit_literal(*width, *v) 27 | } 28 | ExpressionKind::SignedLit(_width, _v) => { 29 | todo!("generate expression signed lit"); 30 | } 31 | ExpressionKind::Lvalue(v) => self.generate_lvalue(v), 32 | ExpressionKind::Binary(lhs, op, rhs) => { 33 | let lhs_tks = self.generate_expression(lhs.as_ref()); 34 | let op_tks = self.generate_binop(*op); 35 | let rhs_tks = self.generate_expression(rhs.as_ref()); 36 | let mut ts = TokenStream::new(); 37 | match op { 38 | BinOp::Add => { 39 | ts.extend(quote!{ 40 | p4rs::bitmath::add_le(#lhs_tks.clone(), #rhs_tks.clone()) 41 | }); 42 | } 43 | BinOp::Subtract => { 44 | ts.extend(quote!{ 45 | p4rs::bitmath::sub_le(#lhs_tks.clone(), #rhs_tks.clone()) 46 | }) 47 | } 48 | BinOp::Mod => { 49 | ts.extend(quote!{ 50 | p4rs::bitmath::mod_le(#lhs_tks.clone(), #rhs_tks.clone()) 51 | }); 52 | } 53 | BinOp::Eq | BinOp::NotEq => { 54 | let lhs_tks_ = match &lhs.as_ref().kind { 55 | ExpressionKind::Lvalue(lval) => { 56 | let name_info = self 57 | .hlir 58 | .lvalue_decls 59 | .get(lval) 60 | .unwrap_or_else(|| { 61 | panic!( 62 | "declaration info for {:#?}", 63 | lval 64 | ) 65 | }); 66 | match name_info.decl { 67 | DeclarationInfo::ActionParameter(_) => { 68 | quote! { 69 | &#lhs_tks 70 | } 71 | } 72 | _ => lhs_tks, 73 | } 74 | } 75 | _ => lhs_tks, 76 | }; 77 | let rhs_tks_ = match &rhs.as_ref().kind { 78 | ExpressionKind::Lvalue(lval) => { 79 | let name_info = self 80 | .hlir 81 | .lvalue_decls 82 | .get(lval) 83 | .unwrap_or_else(|| { 84 | panic!( 85 | "declaration info for {:#?}", 86 | lval 87 | ) 88 | }); 89 | match name_info.decl { 90 | DeclarationInfo::ActionParameter(_) => { 91 | quote! { 92 | &#rhs_tks 93 | } 94 | } 95 | _ => rhs_tks, 96 | } 97 | } 98 | _ => rhs_tks, 99 | }; 100 | ts.extend(lhs_tks_); 101 | ts.extend(op_tks); 102 | ts.extend(rhs_tks_); 103 | } 104 | _ => { 105 | ts.extend(lhs_tks); 106 | ts.extend(op_tks); 107 | ts.extend(rhs_tks); 108 | } 109 | } 110 | ts 111 | } 112 | ExpressionKind::Index(lval, xpr) => { 113 | let mut ts = self.generate_lvalue(lval); 114 | ts.extend(self.generate_expression(xpr.as_ref())); 115 | ts 116 | } 117 | ExpressionKind::Slice(begin, end) => { 118 | let l = match &begin.kind { 119 | ExpressionKind::IntegerLit(v) => *v as usize, 120 | _ => panic!("slice ranges can only be integer literals"), 121 | }; 122 | let l = l + 1; 123 | let r = match &end.kind { 124 | ExpressionKind::IntegerLit(v) => *v as usize, 125 | _ => panic!("slice ranges can only be integer literals"), 126 | }; 127 | quote! { 128 | [#r..#l] 129 | } 130 | } 131 | ExpressionKind::Call(call) => { 132 | let lv: Vec = call 133 | .lval 134 | .name 135 | .split('.') 136 | .map(|x| format_ident!("{}", x)) 137 | .map(|x| quote! { #x }) 138 | .collect(); 139 | 140 | let lvalue = quote! { #(#lv).* }; 141 | let mut args = Vec::new(); 142 | for arg in &call.args { 143 | args.push(self.generate_expression(arg)); 144 | } 145 | quote! { 146 | #lvalue(#(#args),*) 147 | } 148 | } 149 | ExpressionKind::List(elements) => { 150 | let mut parts = Vec::new(); 151 | for e in elements { 152 | parts.push(self.generate_expression(e)); 153 | } 154 | quote! { 155 | &[ #(&#parts),* ] 156 | } 157 | } 158 | } 159 | } 160 | 161 | pub(crate) fn generate_bit_literal( 162 | &self, 163 | width: u16, 164 | value: u128, 165 | ) -> TokenStream { 166 | assert!(width <= 128); 167 | 168 | let width = width as usize; 169 | 170 | quote! { 171 | { 172 | let mut x = bitvec![mut u8, Msb0; 0; #width]; 173 | x.store_le(#value); 174 | x 175 | } 176 | } 177 | } 178 | 179 | pub(crate) fn generate_binop(&self, op: BinOp) -> TokenStream { 180 | match op { 181 | BinOp::Add => quote! { + }, 182 | BinOp::Subtract => quote! { - }, 183 | BinOp::Mod => quote! { % }, 184 | BinOp::Geq => quote! { >= }, 185 | BinOp::Gt => quote! { > }, 186 | BinOp::Leq => quote! { <= }, 187 | BinOp::Lt => quote! { < }, 188 | BinOp::Eq => quote! { == }, 189 | BinOp::NotEq => quote! { != }, 190 | BinOp::Mask => quote! { & }, 191 | BinOp::BitAnd => quote! { & }, 192 | BinOp::BitOr => quote! { | }, 193 | BinOp::Xor => quote! { ^ }, 194 | } 195 | } 196 | 197 | pub(crate) fn generate_lvalue(&self, lval: &Lvalue) -> TokenStream { 198 | let lv: Vec = lval 199 | .name 200 | .split('.') 201 | .map(|x| format_ident!("{}", x)) 202 | .map(|x| quote! { #x }) 203 | .collect(); 204 | 205 | let lvalue = quote! { #(#lv).* }; 206 | 207 | let name_info = self 208 | .hlir 209 | .lvalue_decls 210 | .get(lval) 211 | .unwrap_or_else(|| panic!("declaration info for {:#?}", lval)); 212 | 213 | match name_info.decl { 214 | DeclarationInfo::HeaderMember => quote! { 215 | #lvalue 216 | }, 217 | /* 218 | DeclarationInfo::ActionParameter(_) => quote! { 219 | &#lvalue 220 | }, 221 | */ 222 | _ => lvalue, 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /lang/p4rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | 3 | //! This is the runtime support create for `x4c` generated programs. 4 | //! 5 | //! The main abstraction in this crate is the [`Pipeline`] trait. Rust code that 6 | //! is generated by `x4c` implements this trait. A `main_pipeline` struct is 7 | //! exported by the generated code that implements [`Pipeline`]. Users can wrap 8 | //! the `main_pipeline` object in harness code to provide higher level 9 | //! interfaces for table manipulation and packet i/o. 10 | //! 11 | //! ```rust 12 | //! use p4rs::{ packet_in, packet_out, Pipeline }; 13 | //! use std::net::Ipv6Addr; 14 | //! 15 | //! struct Handler { 16 | //! pipe: Box 17 | //! } 18 | //! 19 | //! impl Handler { 20 | //! /// Create a new pipeline handler. 21 | //! fn new(pipe: Box) -> Self { 22 | //! Self{ pipe } 23 | //! } 24 | //! 25 | //! /// Handle a packet from the specified port. If the pipeline produces 26 | //! /// an output result, send the processed packet to the output port 27 | //! /// returned by the pipeline. 28 | //! fn handle_packet(&mut self, port: u16, pkt: &[u8]) { 29 | //! 30 | //! let mut input = packet_in::new(pkt); 31 | //! 32 | //! let output = self.pipe.process_packet(port, &mut input); 33 | //! for (out_pkt, out_port) in &output { 34 | //! let mut out = out_pkt.header_data.clone(); 35 | //! out.extend_from_slice(out_pkt.payload_data); 36 | //! self.send_packet(*out_port, &out); 37 | //! } 38 | //! 39 | //! } 40 | //! 41 | //! /// Add a routing table entry. Packets for the provided destination will 42 | //! /// be sent out the specified port. 43 | //! fn add_router_entry(&mut self, dest: Ipv6Addr, port: u16) { 44 | //! self.pipe.add_table_entry( 45 | //! "ingress.router.ipv6_routes", // qualified name of the table 46 | //! "forward_out_port", // action to invoke on a hit 47 | //! &dest.octets(), 48 | //! &port.to_le_bytes(), 49 | //! 0, 50 | //! ); 51 | //! } 52 | //! 53 | //! /// Send a packet out the specified port. 54 | //! fn send_packet(&self, port: u16, pkt: &[u8]) { 55 | //! // send the packet ... 56 | //! } 57 | //! } 58 | //! ``` 59 | //! 60 | #![allow(incomplete_features)] 61 | #![allow(non_camel_case_types)] 62 | 63 | use std::fmt; 64 | use std::net::IpAddr; 65 | 66 | pub use error::TryFromSliceError; 67 | use serde::{Deserialize, Serialize}; 68 | 69 | use bitvec::prelude::*; 70 | 71 | pub mod error; 72 | //pub mod hicuts; 73 | //pub mod rice; 74 | pub mod bitmath; 75 | pub mod checksum; 76 | pub mod externs; 77 | pub mod table; 78 | 79 | #[usdt::provider] 80 | mod p4rs_provider { 81 | fn match_miss(_: &str) {} 82 | } 83 | 84 | #[derive(Debug)] 85 | pub struct Bit<'a, const N: usize>(pub &'a [u8]); 86 | 87 | impl<'a, const N: usize> Bit<'a, N> { 88 | //TODO measure the weight of returning TryFromSlice error versus just 89 | //dropping and incrementing a counter. Relying on dtrace for more detailed 90 | //debugging. 91 | pub fn new(data: &'a [u8]) -> Result { 92 | let required_bytes = if N & 7 > 0 { (N >> 3) + 1 } else { N >> 3 }; 93 | if data.len() < required_bytes { 94 | return Err(TryFromSliceError(N)); 95 | } 96 | Ok(Self(&data[..required_bytes])) 97 | } 98 | } 99 | 100 | impl fmt::LowerHex for Bit<'_, N> { 101 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 102 | for x in self.0 { 103 | fmt::LowerHex::fmt(&x, f)?; 104 | } 105 | Ok(()) 106 | } 107 | } 108 | 109 | // TODO more of these for other sizes 110 | impl<'a> From> for u16 { 111 | fn from(b: Bit<'a, 16>) -> u16 { 112 | u16::from_be_bytes([b.0[0], b.0[1]]) 113 | } 114 | } 115 | 116 | // TODO more of these for other sizes 117 | impl std::hash::Hash for Bit<'_, 8> { 118 | fn hash(&self, state: &mut H) { 119 | self.0[0].hash(state); 120 | } 121 | } 122 | 123 | impl std::cmp::PartialEq for Bit<'_, 8> { 124 | fn eq(&self, other: &Self) -> bool { 125 | self.0[0] == other.0[0] 126 | } 127 | } 128 | 129 | impl std::cmp::Eq for Bit<'_, 8> {} 130 | 131 | /// Every packet that goes through a P4 pipeline is represented as a `packet_in` 132 | /// instance. `packet_in` objects wrap an underlying mutable data reference that 133 | /// is ultimately rooted in a memory mapped region containing a ring of packets. 134 | #[derive(Debug)] 135 | pub struct packet_in<'a> { 136 | /// The underlying data. Owned by an external, memory-mapped packet ring. 137 | pub data: &'a [u8], 138 | 139 | /// Extraction index. Everything before `index` has been extracted already. 140 | /// Only data after `index` is eligble for extraction. Extraction is always 141 | /// for contiguous segments of the underlying packet ring data. 142 | pub index: usize, 143 | } 144 | 145 | #[derive(Debug)] 146 | pub struct packet_out<'a> { 147 | pub header_data: Vec, 148 | pub payload_data: &'a [u8], 149 | } 150 | 151 | #[derive(Debug, Serialize, Deserialize)] 152 | pub struct TableEntry { 153 | pub action_id: String, 154 | pub keyset_data: Vec, 155 | pub parameter_data: Vec, 156 | } 157 | 158 | pub trait Pipeline: Send { 159 | /// Process an input packet and produce a set of output packets. Normally 160 | /// there will be a single output packet. However, if the pipeline sets 161 | /// `egress_metadata_t.broadcast` there may be multiple output packets. 162 | fn process_packet<'a>( 163 | &mut self, 164 | port: u16, 165 | pkt: &mut packet_in<'a>, 166 | ) -> Vec<(packet_out<'a>, u16)>; 167 | 168 | //TODO use struct TableEntry? 169 | /// Add an entry to a table identified by table_id. 170 | fn add_table_entry( 171 | &mut self, 172 | table_id: &str, 173 | action_id: &str, 174 | keyset_data: &[u8], 175 | parameter_data: &[u8], 176 | priority: u32, 177 | ); 178 | 179 | /// Remove an entry from a table identified by table_id. 180 | fn remove_table_entry(&mut self, table_id: &str, keyset_data: &[u8]); 181 | 182 | /// Get all the entries in a table. 183 | fn get_table_entries(&self, table_id: &str) -> Option>; 184 | 185 | /// Get a list of table ids 186 | fn get_table_ids(&self) -> Vec<&str>; 187 | } 188 | 189 | /// A fixed length header trait. 190 | pub trait Header { 191 | fn new() -> Self; 192 | fn size() -> usize; 193 | fn set(&mut self, buf: &[u8]) -> Result<(), TryFromSliceError>; 194 | fn set_valid(&mut self); 195 | fn set_invalid(&mut self); 196 | fn is_valid(&self) -> bool; 197 | fn to_bitvec(&self) -> BitVec; 198 | } 199 | 200 | impl<'a> packet_in<'a> { 201 | pub fn new(data: &'a [u8]) -> Self { 202 | Self { data, index: 0 } 203 | } 204 | 205 | // TODO: this function signature is a bit unforunate in the sense that the 206 | // p4 compiler generates call sites based on a p4 `packet_in` extern 207 | // definition. But based on that definition, there is no way for the 208 | // compiler to know that this function returns a result that needs to be 209 | // interrogated. In fact, the signature for packet_in::extract from the p4 210 | // standard library requires the return type to be `void`, so this signature 211 | // cannot return a result without the compiler having special knowledge of 212 | // functions that happen to be called "extract". 213 | pub fn extract(&mut self, h: &mut H) { 214 | //TODO what if a header does not end on a byte boundary? 215 | let n = H::size(); 216 | let start = if self.index > 0 { self.index >> 3 } else { 0 }; 217 | match h.set(&self.data[start..start + (n >> 3)]) { 218 | Ok(_) => {} 219 | Err(e) => { 220 | //TODO better than this 221 | println!("packet extraction failed: {}", e); 222 | } 223 | } 224 | self.index += n; 225 | h.set_valid(); 226 | } 227 | 228 | // This is the same as extract except we return a new header instead of 229 | // modifying an existing one. 230 | pub fn extract_new(&mut self) -> Result { 231 | let n = H::size(); 232 | let start = if self.index > 0 { self.index >> 3 } else { 0 }; 233 | self.index += n; 234 | let mut x = H::new(); 235 | x.set(&self.data[start..start + (n >> 3)])?; 236 | Ok(x) 237 | } 238 | } 239 | 240 | //XXX: remove once classifier defined in terms of bitvecs 241 | pub fn bitvec_to_biguint(bv: &BitVec) -> table::BigUintKey { 242 | let s = bv.as_raw_slice(); 243 | table::BigUintKey { 244 | value: num::BigUint::from_bytes_le(s), 245 | width: s.len(), 246 | } 247 | } 248 | 249 | pub fn bitvec_to_ip6addr(bv: &BitVec) -> std::net::IpAddr { 250 | let mut arr: [u8; 16] = bv.as_raw_slice().try_into().unwrap(); 251 | arr.reverse(); 252 | std::net::IpAddr::V6(std::net::Ipv6Addr::from(arr)) 253 | } 254 | 255 | #[repr(C, align(16))] 256 | pub struct AlignedU128(pub u128); 257 | 258 | pub fn int_to_bitvec(x: i128) -> BitVec { 259 | //let mut bv = BitVec::::new(); 260 | let mut bv = bitvec![mut u8, Msb0; 0; 128]; 261 | bv.store(x); 262 | bv 263 | } 264 | 265 | pub fn bitvec_to_bitvec16(mut x: BitVec) -> BitVec { 266 | x.resize(16, false); 267 | x 268 | } 269 | 270 | pub fn dump_bv(x: &BitVec) -> String { 271 | if x.is_empty() { 272 | "∅".into() 273 | } else { 274 | let v: u128 = x.load_le(); 275 | format!("{:x}", v) 276 | } 277 | } 278 | 279 | pub fn extract_exact_key( 280 | keyset_data: &[u8], 281 | offset: usize, 282 | len: usize, 283 | ) -> table::Key { 284 | table::Key::Exact(table::BigUintKey { 285 | value: num::BigUint::from_bytes_le(&keyset_data[offset..offset + len]), 286 | width: len, 287 | }) 288 | } 289 | 290 | pub fn extract_range_key( 291 | keyset_data: &[u8], 292 | offset: usize, 293 | len: usize, 294 | ) -> table::Key { 295 | table::Key::Range( 296 | table::BigUintKey { 297 | value: num::BigUint::from_bytes_le( 298 | &keyset_data[offset..offset + len], 299 | ), 300 | width: len, 301 | }, 302 | table::BigUintKey { 303 | value: num::BigUint::from_bytes_le( 304 | &keyset_data[offset + len..offset + len + len], 305 | ), 306 | width: len, 307 | }, 308 | ) 309 | } 310 | 311 | /// Extract a ternary key from the provided keyset data. Ternary keys come in 312 | /// two parts. The first part is a leading bit that indicates whether we care 313 | /// about the value. If that leading bit is non-zero, the trailing bits of the 314 | /// key are interpreted as a binary value. If the leading bit is zero, the 315 | /// trailing bits are ignored and a Ternary::DontCare key is returned. 316 | pub fn extract_ternary_key( 317 | keyset_data: &[u8], 318 | offset: usize, 319 | len: usize, 320 | ) -> table::Key { 321 | let care = keyset_data[offset]; 322 | if care != 0 { 323 | table::Key::Ternary(table::Ternary::Value(table::BigUintKey { 324 | value: num::BigUint::from_bytes_le( 325 | &keyset_data[offset + 1..offset + 1 + len], 326 | ), 327 | width: len, 328 | })) 329 | } else { 330 | table::Key::Ternary(table::Ternary::DontCare) 331 | } 332 | } 333 | 334 | pub fn extract_lpm_key( 335 | keyset_data: &[u8], 336 | offset: usize, 337 | _len: usize, 338 | ) -> table::Key { 339 | let (addr, len) = match keyset_data.len() { 340 | // IPv4 341 | 5 => { 342 | let data: [u8; 4] = 343 | keyset_data[offset..offset + 4].try_into().unwrap(); 344 | (IpAddr::from(data), keyset_data[offset + 4]) 345 | } 346 | // IPv6 347 | 17 => { 348 | let data: [u8; 16] = 349 | keyset_data[offset..offset + 16].try_into().unwrap(); 350 | (IpAddr::from(data), keyset_data[offset + 16]) 351 | } 352 | x => { 353 | panic!("lpm: key must be len 5 (ipv4) or 17 (ipv6) found {}", x); 354 | } 355 | }; 356 | 357 | table::Key::Lpm(table::Prefix { addr, len }) 358 | } 359 | 360 | pub fn extract_bool_action_parameter( 361 | parameter_data: &[u8], 362 | offset: usize, 363 | ) -> bool { 364 | parameter_data[offset] == 1 365 | } 366 | 367 | pub fn extract_bit_action_parameter( 368 | parameter_data: &[u8], 369 | offset: usize, 370 | size: usize, 371 | ) -> BitVec { 372 | let mut byte_size = size >> 3; 373 | if size % 8 != 0 { 374 | byte_size += 1; 375 | } 376 | let mut b: BitVec = 377 | BitVec::from_slice(¶meter_data[offset..offset + byte_size]); 378 | b.resize(size, false); 379 | b 380 | } 381 | --------------------------------------------------------------------------------