p4rs/
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<dyn Pipeline>
17//! }
18//!
19//! impl Handler {
20//!     /// Create a new pipeline handler.
21//!     fn new(pipe: Box<dyn Pipeline>) -> 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
63use std::fmt;
64use std::net::IpAddr;
65
66pub use error::TryFromSliceError;
67use serde::{Deserialize, Serialize};
68
69use bitvec::prelude::*;
70
71pub mod error;
72//pub mod hicuts;
73//pub mod rice;
74pub mod bitmath;
75pub mod checksum;
76pub mod externs;
77pub mod table;
78
79#[usdt::provider]
80mod p4rs_provider {
81    fn match_miss(_: &str) {}
82}
83
84#[derive(Debug)]
85pub struct Bit<'a, const N: usize>(pub &'a [u8]);
86
87impl<'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<Self, TryFromSliceError> {
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
100impl<const N: usize> 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
110impl<'a> From<Bit<'a, 16>> 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
117impl std::hash::Hash for Bit<'_, 8> {
118    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
119        self.0[0].hash(state);
120    }
121}
122
123impl std::cmp::PartialEq for Bit<'_, 8> {
124    fn eq(&self, other: &Self) -> bool {
125        self.0[0] == other.0[0]
126    }
127}
128
129impl 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)]
135pub 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)]
146pub struct packet_out<'a> {
147    pub header_data: Vec<u8>,
148    pub payload_data: &'a [u8],
149}
150
151#[derive(Debug, Serialize, Deserialize)]
152pub struct TableEntry {
153    pub action_id: String,
154    pub keyset_data: Vec<u8>,
155    pub parameter_data: Vec<u8>,
156}
157
158pub 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<Vec<TableEntry>>;
184
185    /// Get a list of table ids
186    fn get_table_ids(&self) -> Vec<&str>;
187}
188
189/// A fixed length header trait.
190pub 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<u8, Msb0>;
198}
199
200impl<'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<H: Header>(&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<H: Header>(&mut self) -> Result<H, TryFromSliceError> {
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
241pub fn bitvec_to_biguint(bv: &BitVec<u8, Msb0>) -> 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
249pub fn bitvec_to_ip6addr(bv: &BitVec<u8, Msb0>) -> 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))]
256pub struct AlignedU128(pub u128);
257
258pub fn int_to_bitvec(x: i128) -> BitVec<u8, Msb0> {
259    //let mut bv = BitVec::<u8, Msb0>::new();
260    let mut bv = bitvec![mut u8, Msb0; 0; 128];
261    bv.store(x);
262    bv
263}
264
265pub fn bitvec_to_bitvec16(mut x: BitVec<u8, Msb0>) -> BitVec<u8, Msb0> {
266    x.resize(16, false);
267    x
268}
269
270pub fn dump_bv(x: &BitVec<u8, Msb0>) -> String {
271    if x.is_empty() {
272        "∅".into()
273    } else {
274        let v: u128 = x.load_le();
275        format!("{:x}", v)
276    }
277}
278
279pub 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
290pub 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.
316pub 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
334pub 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
360pub fn extract_bool_action_parameter(
361    parameter_data: &[u8],
362    offset: usize,
363) -> bool {
364    parameter_data[offset] == 1
365}
366
367pub fn extract_bit_action_parameter(
368    parameter_data: &[u8],
369    offset: usize,
370    size: usize,
371) -> BitVec<u8, Msb0> {
372    let mut byte_size = size >> 3;
373    if size % 8 != 0 {
374        byte_size += 1;
375    }
376    let mut b: BitVec<u8, Msb0> =
377        BitVec::from_slice(&parameter_data[offset..offset + byte_size]);
378    b.resize(size, false);
379    b
380}