Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion codegen/rust/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,43 @@ impl<'a> HeaderGenerator<'a> {
checksum_statements.push(quote! {
csum = p4rs::bitmath::add_le(csum.clone(), self.#name.csum())
});
let dump_call = if size == 32

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way we want to do this is either through a) attributes or b) typefs where the display format is implied by some set of types known to the compiler and could be included with x4c. I think both of these would require some additional compiler support, and it seems like (a) would be a better path than (b).

&& (name_s == "src"
|| name_s == "dst"
|| name_s.ends_with("_ip")
|| name_s.ends_with("_addr"))
{
quote! { p4rs::dump_ip4(&self.#name) }
} else if size == 128 && (name_s == "src" || name_s == "dst") {
quote! { p4rs::dump_ip6(&self.#name) }
} else if size == 48
&& (name_s == "src"
|| name_s == "dst"
|| name_s.ends_with("_mac"))
{
quote! { p4rs::dump_mac(&self.#name) }
} else if size == 16
&& (name_s.ends_with("ether_type")
|| name_s == "proto_type"
|| name_s == "protocol")
{
quote! { p4rs::dump_ether_type(&self.#name) }
} else if size == 8
&& (name_s == "protocol" || name_s == "next_hdr")
{
quote! { p4rs::dump_ip_proto(&self.#name) }
} else if size == 8 && name_s == "flags" {
quote! { p4rs::dump_tcp_flags(&self.#name) }
} else if size == 16 && name_s == "opcode" {
quote! { p4rs::dump_arp_op(&self.#name) }
} else if size == 8 && name_s == "type" {
quote! { p4rs::dump_icmp_type(&self.#name) }
} else {
quote! { p4rs::dump_bv(&self.#name) }
};
dump_statements.push(quote! {
#name_s.cyan(),
p4rs::dump_bv(&self.#name)
#dump_call
});

offset += size;
Expand Down
264 changes: 263 additions & 1 deletion lang/p4rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
#![allow(non_camel_case_types)]

use std::fmt;
use std::net::IpAddr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

pub use error::TryFromSliceError;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -276,6 +276,172 @@ pub fn dump_bv(x: &BitVec<u8, Msb0>) -> String {
}
}

pub fn dump_ip4(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 32 {
return dump_bv(x);
}
let v: u32 = x.load_le();
Ipv4Addr::from(v).to_string()
}

pub fn dump_ip6(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 128 {
return dump_bv(x);
}
let v: u128 = x.load_le();
Ipv6Addr::from(v).to_string()
}

pub fn dump_mac(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 48 {
return dump_bv(x);
}
let v: u128 = x.load_le();
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
(v >> 40) & 0xff,
(v >> 32) & 0xff,
(v >> 24) & 0xff,
(v >> 16) & 0xff,
(v >> 8) & 0xff,
v & 0xff,
)
}

pub fn dump_ether_type(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 16 {
return dump_bv(x);
}
let v: u16 = x.load_le();
let name = match v {
0x0800 => Some("IPv4"),
0x0806 => Some("ARP"),
0x0901 => Some("Sidecar"),
0x8100 => Some("VLAN"),
0x86dd => Some("IPv6"),
0x8847 => Some("MPLS unicast"),
0x8848 => Some("MPLS multicast"),
0x88a8 => Some("QinQ"),
_ => None,
};
match name {
Some(n) => format!("0x{:04x} ({})", v, n),
None => format!("0x{:04x}", v),
}
}

pub fn dump_ip_proto(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 8 {
return dump_bv(x);
}
let v: u8 = x.load_le();
let name = match v {
1 => Some("ICMPv4"),
6 => Some("TCP"),
17 => Some("UDP"),
41 => Some("IPv6-in-IPv4"),
43 => Some("IPv6-Route"),
44 => Some("IPv6-Frag"),
58 => Some("ICMPv6"),
59 => Some("IPv6-NoNxt"),
60 => Some("IPv6-Opts"),
132 => Some("SCTP"),
_ => None,
};
match name {
Some(n) => format!("0x{:02x} ({})", v, n),
None => format!("0x{:02x}", v),
}
}

pub fn dump_tcp_flags(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 8 {
return dump_bv(x);
}
let v: u8 = x.load_le();
if v == 0 {
return "none".into();
}
let mut flags = Vec::new();
if v & 0x01 != 0 {
flags.push("FIN");
}
if v & 0x02 != 0 {
flags.push("SYN");
}
if v & 0x04 != 0 {
flags.push("RST");
}
if v & 0x08 != 0 {
flags.push("PSH");
}
if v & 0x10 != 0 {
flags.push("ACK");
}
if v & 0x20 != 0 {
flags.push("URG");
}
if v & 0x40 != 0 {
flags.push("ECE");
}
if v & 0x80 != 0 {
flags.push("CWR");
}
flags.join("|")
}

pub fn dump_arp_op(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 16 {
return dump_bv(x);
}
let v: u16 = x.load_le();
let name = match v {
1 => Some("Request"),
2 => Some("Reply"),
3 => Some("RARP Request"),
4 => Some("RARP Reply"),
_ => None,
};
match name {
Some(n) => format!("0x{:04x} ({})", v, n),
None => format!("0x{:04x}", v),
}
}

pub fn dump_icmp_type(x: &BitVec<u8, Msb0>) -> String {
if x.len() != 8 {
return dump_bv(x);
}
let v: u8 = x.load_le();
// Types 0-127 use ICMPv4 semantics; types 128-255 are exclusively ICMPv6.
let name = match v {
// ICMPv4
0 => Some("Echo Reply"),
3 => Some("Dest Unreachable"),
5 => Some("Redirect"),
8 => Some("Echo Request"),
11 => Some("Time Exceeded"),
12 => Some("Parameter Problem"),
// ICMPv6 (128+ are unambiguously ICMPv6)
128 => Some("Echo Request"),
129 => Some("Echo Reply"),
130 => Some("MLD Query"),
131 => Some("MLD Report"),
132 => Some("MLD Done"),
133 => Some("RS"),
134 => Some("RA"),
135 => Some("NS"),
136 => Some("NA"),
137 => Some("Redirect"),
143 => Some("MLDv2 Report"),
_ => None,
};
match name {
Some(n) => format!("0x{:02x} ({})", v, n),
None => format!("0x{:02x}", v),
}
}

pub fn extract_exact_key(
keyset_data: &[u8],
offset: usize,
Expand Down Expand Up @@ -378,3 +544,99 @@ pub fn extract_bit_action_parameter(
b.resize(size, false);
b
}

#[cfg(test)]
mod tests {
use super::*;

// Construct a bitvec as it would appear after header::set() — bytes are
// reversed for multi-byte fields (>8 bits), unchanged for single-byte fields.
fn net_bv(bytes: &[u8]) -> BitVec<u8, Msb0> {
let mut v = bytes.to_vec();
if v.len() > 1 {
v.reverse();
}
BitVec::<u8, Msb0>::from_vec(v)
}

#[test]
fn test_dump_ip4() {
assert_eq!(dump_ip4(&net_bv(&[192, 168, 1, 1])), "192.168.1.1");
assert_eq!(dump_ip4(&net_bv(&[10, 0, 0, 1])), "10.0.0.1");
assert_eq!(dump_ip4(&net_bv(&[0, 0, 0, 0])), "0.0.0.0");
assert_eq!(dump_ip4(&net_bv(&[255, 255, 255, 255])), "255.255.255.255");
}

#[test]
fn test_dump_ip6() {
let mut fd00_1 = [0u8; 16];
fd00_1[0] = 0xfd;
fd00_1[15] = 0x01;
assert_eq!(dump_ip6(&net_bv(&fd00_1)), "fd00::1");

let mut loopback = [0u8; 16];
loopback[15] = 1;
assert_eq!(dump_ip6(&net_bv(&loopback)), "::1");
}

#[test]
fn test_dump_mac() {
assert_eq!(
dump_mac(&net_bv(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff])),
"aa:bb:cc:dd:ee:ff"
);
assert_eq!(
dump_mac(&net_bv(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00])),
"00:00:00:00:00:00"
);
}

#[test]
fn test_dump_ether_type() {
assert_eq!(dump_ether_type(&net_bv(&[0x08, 0x00])), "0x0800 (IPv4)");
assert_eq!(dump_ether_type(&net_bv(&[0x08, 0x06])), "0x0806 (ARP)");
assert_eq!(dump_ether_type(&net_bv(&[0x86, 0xdd])), "0x86dd (IPv6)");
assert_eq!(dump_ether_type(&net_bv(&[0x09, 0x01])), "0x0901 (Sidecar)");
assert_eq!(dump_ether_type(&net_bv(&[0x12, 0x34])), "0x1234");
}

#[test]
fn test_dump_ip_proto() {
assert_eq!(dump_ip_proto(&net_bv(&[1])), "0x01 (ICMPv4)");
assert_eq!(dump_ip_proto(&net_bv(&[6])), "0x06 (TCP)");
assert_eq!(dump_ip_proto(&net_bv(&[17])), "0x11 (UDP)");
assert_eq!(dump_ip_proto(&net_bv(&[58])), "0x3a (ICMPv6)");
assert_eq!(dump_ip_proto(&net_bv(&[99])), "0x63");
}

#[test]
fn test_dump_tcp_flags() {
assert_eq!(dump_tcp_flags(&net_bv(&[0x00])), "none");
assert_eq!(dump_tcp_flags(&net_bv(&[0x02])), "SYN");
assert_eq!(dump_tcp_flags(&net_bv(&[0x12])), "SYN|ACK");
assert_eq!(dump_tcp_flags(&net_bv(&[0x01])), "FIN");
assert_eq!(dump_tcp_flags(&net_bv(&[0x04])), "RST");
assert_eq!(
dump_tcp_flags(&net_bv(&[0xff])),
"FIN|SYN|RST|PSH|ACK|URG|ECE|CWR"
);
}

#[test]
fn test_dump_arp_op() {
assert_eq!(dump_arp_op(&net_bv(&[0x00, 0x01])), "0x0001 (Request)");
assert_eq!(dump_arp_op(&net_bv(&[0x00, 0x02])), "0x0002 (Reply)");
assert_eq!(dump_arp_op(&net_bv(&[0x00, 0x05])), "0x0005");
}

#[test]
fn test_dump_icmp_type() {
assert_eq!(dump_icmp_type(&net_bv(&[0])), "0x00 (Echo Reply)");
assert_eq!(dump_icmp_type(&net_bv(&[8])), "0x08 (Echo Request)");
assert_eq!(dump_icmp_type(&net_bv(&[133])), "0x85 (RS)");
assert_eq!(dump_icmp_type(&net_bv(&[134])), "0x86 (RA)");
assert_eq!(dump_icmp_type(&net_bv(&[135])), "0x87 (NS)");
assert_eq!(dump_icmp_type(&net_bv(&[136])), "0x88 (NA)");
assert_eq!(dump_icmp_type(&net_bv(&[50])), "0x32");
}
}