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_t
s. Accordingly, individual headers do not havepayload
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 thezerocopy
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§
Attribute Macros§
- choice
- Attribute macro for a selection over one or more individual headers, conditional on an input hint.