Crate ingot

Source
Expand description

§Ingot – bare metal packets

Ingot is a framework for writing network packet and header parsers, designed to support hybrid zero-copy and owned packet representations. The library is built on top of zerocopy.

Ingot takes heavy inspiration from libpnet, with some key differences to support OPTE:

  • First-class support for chaining headers and selecting over next-header values to parse packets.
  • Packet views and representations generate common read and write traits (UdpRef, UdpMut). Setting and getting fields from present or pushed headers is consistent and easy.
  • Support for nested parsing of headers – e.g., IPv6 extensions within a parent IPv6 struct.
  • Ingot allows packet parsing over split buffers (so long as each header is contiguous), e.g., in illumos mblk_ts. Accordingly, individual headers do not have payload fields.
  • Variable-width packet segments (options, extensions) can be replaced with their owned representation, even when their parent is a zero-copy view. This makes it easier to alter options in place, if needed.

§Performance

Because ingot is based upon the third-party library zerocopy, compiling your binaries with LTO enabled is crucial for maximising performance. To do so, include the following in your Cargo.toml:

[profile.release]
debug = 2
lto = true

§Current limitations

  • Packet bitfields cannot currently be specified with little-endian integers.
  • Ingot does not yet support no-alloc use.
  • To locally define packets through the Ingot macro, you must import the zerocopy crate into your own project.

§License

This project is available under the terms of the MPL 2.0 license.

§Usage

Packets and headers are defined using the procedural macros Ingot (headers), choice (selecting between headers), and Parse (chains of individual layers).

The documentation for each macro (as well as the packet and header types) defined here double as examples of their use. See also the ingot-examples crate.

Headers can be used directly (as with any other rust struct), or using protocol-specific traits and the Header type when we need to hold mixed owned/borrowed data.

╔═╗
║P║   pub struct Packet<Owned, View> {      pub struct DirectPacket<Owned, View> {
║r║       Repr(Box<Owned>),                     Repr(Owned),
║o║       Raw(View),                            Raw(View),
║v║   }                                     }
║i║                   │                                         │
║d║                   │                                         │
║e║                   └───────────────┬────────IMPL─────────────┴──────┐
║d║                                   │                                │
╚═╝─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─
                                      ▼                                ▼
╔═╗                     │ #[derive(Ingot)]           #[derive(Ingot)]
║U║  #[derive(Ingot)]     pub trait UdpRef {         pub trait UdpMut {
║s║  pub struct Udp {   │     fn src(&self) -> u16;      fn set_src(&mut self, val: u16);
║e║      // ...        ──┐    fn dst(&self) -> u16;      fn set_dst(&mut self, val: u16);
║r║  } ▲          │     ││    // ...                     // ...
╚═╝    │          │      │}                          }
       │      HasView   ││            ▲                                ▲
─ ─ ─ ─│─ ─ ─ ─ ─ ┼ ─ ─ ─│            │                                │
    HasRepr       │      ├────────────┴───────────IMPL─────────────────┘
       │          │      │
       │          │      │                                  ╔═══════════╗
       │          ▼                                         ║ Generated ║
   pub struct ValidUdp<B: ByteSlice> (                      ╚═══════════╝
       Ref<B, UdpPart0>, // Zerocopy, repr(C)
   );

Headers define owned and borrowed versions of their contents, with shared traits to use and modify each individually or through the Header abstraction. Base traits, primitive types, and assorted helpers are defined in ingot_types.

§Working with packets.

Packets/headers can be read and modified whether they are owned or borrowed:

use ingot::ethernet::{Ethernet, Ethertype, EthernetRef, EthernetMut, ValidEthernet};
use ingot::types::{Emit, HeaderParse, Header};
use macaddr::MacAddr6;

let owned_ethernet = Ethernet {
    destination: MacAddr6::broadcast(),
    source: MacAddr6::nil(),
    ethertype: Ethertype::ARP,
};

// ----------------
// Field reads.
// ----------------

let emitted_ethernet = owned_ethernet.emit_vec();
let (reparsed_ethernet, ..) = ValidEthernet::parse(&emitted_ethernet[..]).unwrap();

// via EthernetRef
assert_eq!(reparsed_ethernet.source(), MacAddr6::nil());

// compile error!
// assert_eq!(reparsed_ethernet.set_source(), MacAddr6::nil());

// ----------------
// Field mutation.
// ----------------

let mut emitted_ethernet = emitted_ethernet;
let (mut rereparsed_ethernet, ..) = ValidEthernet::parse(&mut emitted_ethernet[..]).unwrap();
rereparsed_ethernet.set_source(MacAddr6::broadcast());
rereparsed_ethernet.set_destination(MacAddr6::nil());

assert_eq!(rereparsed_ethernet.source(), MacAddr6::broadcast());
assert_eq!(rereparsed_ethernet.destination(), MacAddr6::nil());

// ----------------
// ...and via Header
// ----------------
let eth_pkt = Header::from(rereparsed_ethernet);
assert_eq!(eth_pkt.source(), MacAddr6::broadcast());

Packets can also be written into any buffer easily for any tuple of headers:

use ingot::geneve::*;
use ingot::udp::*;

// Headers can be emitted on their own.
let owned_ethernet = Ethernet {
    destination: MacAddr6::broadcast(),
    source: MacAddr6::nil(),
    ethertype: Ethertype::ARP,
};

let emitted_ethernet = owned_ethernet.emit_vec();
let (reparsed_ethernet, hint, rest) = ValidEthernet::parse(&emitted_ethernet[..]).unwrap();

// Or we can easily emit an arbitrary stack in order
let makeshift_stack = (
    Udp { source: 1234, destination: 5678, length: 77, checksum: 0xffff },
    Geneve {
        flags: GeneveFlags::CRITICAL_OPTS,
        protocol_type: Ethertype::ETHERNET,
        vni: 7777.try_into().unwrap(),
        ..Default::default()
    },
    &[1, 2, 3, 4][..],
    reparsed_ethernet,
);

// ...to a new buffer.
let out = makeshift_stack.emit_vec();

// ...or to an existing one
let mut slot = [0u8; 1500];
let _remainder = makeshift_stack.emit_prefix(&mut slot[..]).unwrap();

Re-exports§

pub use ingot_types as types;

Modules§

ethernet
geneve
icmp
igmp
ip
tcp
udp

Attribute Macros§

choice
Attribute macro for a selection over one or more individual headers, conditional on an input hint.

Derive Macros§

Ingot
Derive macro for defining individual protocol headers.
Parse
Derive macro for parsing complete packets, potentially spanning several byte slices.