Skip to content

Commit

Permalink
Make PacketBuilder and Layer::stack_and_encode
Browse files Browse the repository at this point in the history
- Implement `PacketBuilder` to sculpt packets.
- Implement `Layer::stack_and_encode` for Ethernet and IPv4.
- Implement `as_slice` and `as_mut_slice` for MACAddress, IPv4Address and
  IPv6Address.
- Create example `packet_sculpting.rs`.
- Add new `crate::Error` variant SculptingError.

Signed-off-by: Mohammad Aadil Shabier <aadilshabier1@gmail.com>
  • Loading branch information
aadilshabier committed May 2, 2024
1 parent df86e98 commit f8fcb38
Show file tree
Hide file tree
Showing 23 changed files with 291 additions and 5 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ path = "examples/packet_as_json.rs"
name = "pcap"
path = "examples/pcap_example.rs"

[[example]]
name = "packet_sculpting"
path = "examples/packet_sculpting.rs"

[build-dependencies]
syn = { version = "1.0", features = ["full"]}
walkdir = "2"
Expand Down
35 changes: 35 additions & 0 deletions examples/packet_sculpting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::error::Error;

use scalpel::{layers, ENCAP_TYPE_ETH};

fn main() -> Result<(), Box<dyn Error>> {
let _ = scalpel::register_defaults()?;

let eth_layer = Box::new(layers::ethernet::Ethernet::new());
let ipv4_layer = Box::new(layers::ipv4::IPv4::new());
let bytes: Vec<u8> = vec![0x12, 0x34, 0x56, 0x78, 0x90];

let builder = scalpel::builder::PacketBuilder::new()
.stack(eth_layer)?
.stack(ipv4_layer)?
.stack_bytes(bytes);

let (_, result) = builder.build().unwrap();

let res = &result[0];
let res_string = res
.iter()
// .map(|x| "0x".to_string() + &x.to_string() + " ")
.fold(String::new(), |acc, num| {
acc + "0x" + &num.to_string() + " "
})
.trim_end()
.to_string();

println!("res: {:#?}", res_string);

let p = scalpel::Packet::from_bytes(&res, ENCAP_TYPE_ETH);
println!("{}", serde_json::to_string_pretty(&p.unwrap()).unwrap());

Ok(())
}
55 changes: 55 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::{errors::Error, Layer, Packet};

#[derive(Debug, Default)]
pub struct PacketBuilder {
inner: Packet,
}

impl PacketBuilder {
pub fn new() -> Self {
Self::default()
}

pub fn stack(mut self, layer: Box<dyn Layer + Send>) -> Result<Self, Error> {
if self.inner.unprocessed.is_empty() {
self.inner.layers.push(layer);
Ok(self)
} else {
Err(Error::SculptingError(
"Cannot push layer on top of raw byte layer".to_string(),
))
}
}

pub fn stack_bytes(mut self, bytes: Vec<u8>) -> Self {
self.inner.unprocessed = bytes;
self
}

pub fn build(mut self) -> Result<(Packet, Vec<Vec<u8>>), Error> {
let len = self.inner.layers.len();
if len < 1 {
return Err(Error::SculptingError(
"Packet to build does not contain any layers".to_string(),
));
}
let mut results = vec![];

// last layer
let mut bytes =
self.inner.layers[len - 1].stack_and_encode(&self.inner.unprocessed, "raw")?;
results.push(bytes);

for i in (0..len - 1).rev() {
let next_layer = results.last().unwrap();
let info = self.inner.layers[i + 1].name();
bytes = self.inner.layers[i].stack_and_encode(next_layer, info)?;
results.push(bytes);
}

results.reverse();
Ok((self.inner, results))
}

// TODO: Add metadata related functions
}
3 changes: 3 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub enum Error {

/// A layer registration error.
RegisterError(String),

/// Error in sculpting
SculptingError(String),
}

// FIXME: Should work with `no_std`
Expand Down
10 changes: 10 additions & 0 deletions src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ pub trait Layer: Send + Debug + erased_serde::Serialize {
bytes: &[u8],
) -> Result<(Option<Box<dyn Layer + Send>>, usize), Error>;

/// Main 'encoder' function.
///
/// The return value is a `Vec<u8>` on success. This indicates the encoded packet of the
/// layer. Typically return a [SculptingError][`crate::errors::Error::SculptingError`],
/// but may as well return other values. This updates the fields of the packet using the
/// encoded bytes of the next layer(`next_layer`), and `info`, to provide additional details
/// about the packet. This can be the result of [Layer::name()][`Self::name()`]
/// of the next packet.
fn stack_and_encode(&mut self, next_layer: &[u8], info: &str) -> Result<Vec<u8>, Error>;

/// Name for the given layer.
fn name(&self) -> &'static str;

Expand Down
4 changes: 4 additions & 0 deletions src/layers/arp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ impl Layer for ARP {
Ok((None, ARP_HEADER_LENGTH))
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}

fn name(&self) -> &'static str {
"ARP"
}
Expand Down
4 changes: 4 additions & 0 deletions src/layers/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ impl Layer for DNS {
Ok((None, decoded))
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}

fn name(&self) -> &'static str {
"DNS"
}
Expand Down
37 changes: 35 additions & 2 deletions src/layers/ethernet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::convert::TryInto;

// FIXME: Should work with no_std
use std::collections::HashMap;
use std::sync::{RwLock,OnceLock};
use std::sync::{OnceLock, RwLock};

use serde::Serialize;

Expand All @@ -19,7 +19,8 @@ pub fn get_ethertypes_map() -> &'static RwLock<HashMap<EtherType, LayerCreatorFn
///
/// The creator function simply creates a `default` L3 struct that implements the dissector
/// for the Layer.
pub(crate) static ETHERTYPES_MAP: OnceLock<RwLock<HashMap<EtherType, LayerCreatorFn>>> = OnceLock::new();
pub(crate) static ETHERTYPES_MAP: OnceLock<RwLock<HashMap<EtherType, LayerCreatorFn>>> =
OnceLock::new();
ETHERTYPES_MAP.get_or_init(|| RwLock::new(HashMap::new()))
}

Expand Down Expand Up @@ -55,6 +56,13 @@ pub struct Ethernet {
}

impl Ethernet {
pub fn new() -> Self {
Self {
ethertype: 0xFFFF,
..Default::default()
}
}

pub(crate) fn creator() -> Box<dyn Layer + Send> {
Box::<Ethernet>::default()
}
Expand Down Expand Up @@ -84,6 +92,31 @@ impl Layer for Ethernet {
}
}

fn stack_and_encode(&mut self, next_layer: &[u8], info: &str) -> Result<Vec<u8>, Error> {
let mut result = vec![];

result.extend(self.dst_mac.as_slice());
result.extend(self.src_mac.as_slice());

let ethertype: u16 = match info {
"ARP" => crate::types::ETHERTYPE_ARP,
"IPv4" => crate::types::ETHERTYPE_IP,
"IPv6" => crate::types::ETHERTYPE_IP6,
// FIXME: can also be `ETHERTYPE_MPLS_MULTICAST`
"MPLS" => crate::types::ETHERTYPE_MPLS_UNICAST,
"raw" => 0xffff,
// NOTE: should return Err instead
_ => unimplemented!(),
};

result.extend(ethertype.to_be_bytes());
result.extend(next_layer);

// TODO: pad to 64 bytes(minimum packet size)

Ok(result)
}

fn name(&self) -> &'static str {
"Ethernet"
}
Expand Down
4 changes: 4 additions & 0 deletions src/layers/icmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ impl Layer for ICMP {
fn short_name(&self) -> &'static str {
"icmp"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions src/layers/icmpv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@ impl Layer for ICMPv6 {
fn short_name(&self) -> &'static str {
"icmpv6"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}

#[cfg(test)]
Expand Down
54 changes: 53 additions & 1 deletion src/layers/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub const IPV4_OPTION_RR: u8 = 7;
pub const IPV4_OPTION_MTUP: u8 = 11;
pub const IPV4_OPTION_MTUR: u8 = 12;

pub const IPPROTO_RAW: u8 = 0xFF;

fn get_protocol_map() -> &'static RwLock<HashMap<u8, LayerCreatorFn>> {
static PROTOCOLS_MAP: OnceLock<RwLock<HashMap<u8, LayerCreatorFn>>> = OnceLock::new();
PROTOCOLS_MAP.get_or_init(|| RwLock::new(HashMap::new()))
Expand Down Expand Up @@ -101,7 +103,17 @@ pub struct IPv4 {
}

impl IPv4 {
pub fn creator() -> Box<dyn Layer + Send> {
pub fn new() -> Self {
Self {
version: 4,
hdr_len: (IPV4_BASE_HEADER_LENGTH / 4) as u8,
proto: IPPROTO_RAW,
len: IPV4_BASE_HEADER_LENGTH as u16,
..Default::default()
}
}

pub(crate) fn creator() -> Box<dyn Layer + Send> {
Box::<IPv4>::default()
}
}
Expand Down Expand Up @@ -242,6 +254,10 @@ impl IPv4 {

Ok(((len as u8, data), i))
}

fn calculate_checksum(_bytes: &[u8]) -> u16 {
0
}
}

impl Layer for IPv4 {
Expand Down Expand Up @@ -305,6 +321,42 @@ impl Layer for IPv4 {
fn short_name(&self) -> &'static str {
"ip"
}

fn stack_and_encode(&mut self, next_layer: &[u8], info: &str) -> Result<Vec<u8>, Error> {
self.len = self.hdr_len as u16 + next_layer.len() as u16;
self.proto = match info {
"raw" => IPPROTO_RAW,
_ => self.proto,
};

let mut result = vec![];

let byte = (self.version << 4) | self.hdr_len;
result.push(byte);
result.push(self.tos);
result.extend(self.len.to_be_bytes());
result.extend(self.id.to_be_bytes());

let word = (self.flags as u16) << 13 | self.frag_offset;
result.extend(word.to_be_bytes());
result.push(self.ttl);
result.push(self.proto);

let checksum_start = result.len();
result.extend(self.checksum.to_be_bytes());
result.extend(self.src_addr.as_slice());
result.extend(self.dst_addr.as_slice());

if !self.options.is_empty() {
todo!();
}

result.extend(next_layer);

let checksum = IPv4::calculate_checksum(&result);
result[checksum_start..checksum_start + 2].copy_from_slice(&checksum.to_be_bytes());
Ok(result)
}
}

#[cfg(test)]
Expand Down
5 changes: 4 additions & 1 deletion src/layers/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::Layer;
/// Basic Length of the IPv6 Header
pub const IPV6_BASE_HEADER_LENGTH: usize = 40_usize;


fn get_next_headers_map() -> &'static RwLock<HashMap<u8, LayerCreatorFn>> {
static NEXT_HEADERS_MAP: OnceLock<RwLock<HashMap<u8, LayerCreatorFn>>> = OnceLock::new();
NEXT_HEADERS_MAP.get_or_init(|| RwLock::new(HashMap::new()))
Expand Down Expand Up @@ -108,4 +107,8 @@ impl Layer for IPv6 {
fn short_name(&self) -> &'static str {
"ip6"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}
4 changes: 4 additions & 0 deletions src/layers/linux_sll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,8 @@ impl Layer for LinuxSll {
fn short_name(&self) -> &'static str {
"linux_sll"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}
4 changes: 4 additions & 0 deletions src/layers/linux_sll2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ impl Layer for LinuxSll2 {
}
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}

fn name(&self) -> &'static str {
"Linux SLL Version 2"
}
Expand Down
4 changes: 4 additions & 0 deletions src/layers/m3ua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,8 @@ impl Layer for M3UA {
fn short_name(&self) -> &'static str {
"m3ua"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}
4 changes: 4 additions & 0 deletions src/layers/mpls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ impl Layer for MPLS {
fn short_name(&self) -> &'static str {
"mpls"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions src/layers/sctp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ impl Layer for SCTP {
fn short_name(&self) -> &'static str {
"sctp"
}

fn stack_and_encode(&mut self, _next_layer: &[u8], _info: &str) -> Result<Vec<u8>, Error> {
todo!()
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit f8fcb38

Please sign in to comment.