Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: smoltcp-rs/smoltcp
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 68d9c86a5b15
Choose a base ref
...
head repository: smoltcp-rs/smoltcp
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 10d538c02fa3
Choose a head ref
  • 2 commits
  • 9 files changed
  • 1 contributor

Commits on Dec 10, 2016

  1. Copy the full SHA
    34d5e1b View commit details
  2. Copy the full SHA
    10d538c View commit details
Showing with 315 additions and 81 deletions.
  1. +5 −0 Cargo.toml
  2. +39 −0 examples/smoltcpdump.rs
  3. +10 −0 src/interface/mod.rs
  4. +101 −0 src/interface/raw_socket.rs
  5. +2 −50 src/lib.rs
  6. +55 −18 src/{ → wire}/arp.rs
  7. +30 −11 src/{ → wire}/ethernet.rs
  8. +1 −2 src/{ → wire}/ipv4.rs
  9. +72 −0 src/wire/mod.rs
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -5,3 +5,8 @@ authors = ["whitequark <whitequark@whitequark.org>"]

[dependencies]
byteorder = { version = "0.5", default-features = false }
libc = { version = "0.2.18", optional = true }

[features]
std = ["libc"]
default = ["std"]
39 changes: 39 additions & 0 deletions examples/smoltcpdump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
extern crate smoltcp;

use std::{env, io};
use smoltcp::wire::{EthernetFrame, EthernetProtocolType, ArpPacket};
use smoltcp::interface::RawSocket;

fn get<T>(result: Result<T, ()>) -> io::Result<T> {
result.map_err(|()| io::Error::new(io::ErrorKind::InvalidData,
"buffer too small"))
.into()
}

fn print_frame(socket: &mut RawSocket) -> io::Result<()> {
let buffer = try!(socket.capture());

let frame = try!(get(EthernetFrame::new(&buffer[..])));
println!("{}", frame);

match frame.ethertype() {
EthernetProtocolType::Arp => {
let packet = try!(get(ArpPacket::new(frame.payload())));
println!("| {}", packet);
},
_ => ()
}

Ok(())
}

fn main() {
let ifname = env::args().nth(1).unwrap();
let mut socket = RawSocket::new(ifname.as_ref()).unwrap();
loop {
match print_frame(&mut socket) {
Ok(()) => (),
Err(e) => println!("Cannot print frame: {}", e)
}
}
}
10 changes: 10 additions & 0 deletions src/interface/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Access to networking hardware.
//!
//! The `interface` module provides a way to capture and inject packets.
//! It requires the standard library, and currently only works on Linux.
#[cfg(all(unix, feature = "std"))]
mod raw_socket;

#[cfg(all(unix, feature = "std"))]
pub use self::raw_socket::RawSocket;
101 changes: 101 additions & 0 deletions src/interface/raw_socket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
extern crate std;
extern crate libc;

use self::std::{mem, vec, io};

#[repr(C)]
struct ifreq {
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
ifr_data: libc::c_int /* ifr_ifindex or ifr_mtu */
}

const SIOCGIFMTU: libc::c_ulong = 0x8921;
const SIOCGIFINDEX: libc::c_ulong = 0x8933;

const ETH_P_ALL: libc::c_short = 0x0003;

/// A raw socket: a socket that captures the entire packet, up to and including
/// the link layer header.
#[derive(Debug)]
pub struct RawSocket {
sockfd: libc::c_int,
buffer: vec::Vec<u8>
}

impl RawSocket {
/// Creates and returns a raw socket, bound to the interface called `name`.
pub fn new(name: &str) -> io::Result<RawSocket> {
unsafe {
let sockfd = libc::socket(libc::AF_PACKET, libc::SOCK_RAW, ETH_P_ALL.to_be() as i32);
if sockfd == -1 {
return Err(io::Error::last_os_error())
}

let mut ifreq = ifreq {
ifr_name: [0; libc::IF_NAMESIZE],
ifr_data: 0
};
for (i, byte) in name.as_bytes().iter().enumerate() {
ifreq.ifr_name[i] = *byte as libc::c_char
}

let res = libc::ioctl(sockfd, SIOCGIFINDEX, &mut ifreq as *mut ifreq);
if res == -1 {
libc::close(sockfd);
return Err(io::Error::last_os_error())
}
let if_index = ifreq.ifr_data;

let res = libc::ioctl(sockfd, SIOCGIFMTU, &mut ifreq as *mut ifreq);
if res == -1 {
libc::close(sockfd);
return Err(io::Error::last_os_error())
}
let if_mtu = ifreq.ifr_data;

let sockaddr = libc::sockaddr_ll {
sll_family: libc::AF_PACKET as u16,
sll_protocol: ETH_P_ALL.to_be() as u16,
sll_ifindex: if_index as i32,
sll_hatype: 1,
sll_pkttype: 0,
sll_halen: 6,
sll_addr: [0; 8]
};
libc::bind(sockfd,
&sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
mem::size_of::<libc::sockaddr_ll>() as u32);
if res == -1 {
libc::close(sockfd);
return Err(io::Error::last_os_error())
}

let mut buffer = vec::Vec::new();
buffer.resize(if_mtu as usize, 0);
Ok(RawSocket {
sockfd: sockfd,
buffer: buffer
})
}
}

/// Captures a packet into the internal buffer, which is sized appropriately
/// for the interface MTU.
pub fn capture(&mut self) -> io::Result<&[u8]> {
unsafe {
let len = libc::recv(self.sockfd, self.buffer.as_mut_ptr() as *mut libc::c_void,
self.buffer.len(), 0);
if len == -1 {
return Err(io::Error::last_os_error())
}

Ok(&self.buffer[..len as usize])
}
}
}

impl Drop for RawSocket {
fn drop(&mut self) {
unsafe { libc::close(self.sockfd); }
}
}
52 changes: 2 additions & 50 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -7,53 +7,5 @@ extern crate std;

extern crate byteorder;

macro_rules! enum_with_unknown {
(#[$( $attr:meta ),*]
pub enum $name:ident($ty:ty) { $( $variant:ident = $value:expr ),+ }) => {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[$( $attr ),*]
pub enum $name {
$( $variant ),*,
Unknown($ty)
}

impl ::core::convert::From<$ty> for $name {
fn from(value: $ty) -> Self {
match value {
$( $value => $name::$variant ),*,
other => $name::Unknown(other)
}
}
}

impl ::core::convert::From<$name> for $ty {
fn from(value: $name) -> Self {
match value {
$( $name::$variant => $value ),*,
$name::Unknown(other) => other
}
}
}
}
}

mod field {
pub type Field = ::core::ops::Range<usize>;
pub type FieldFrom = ::core::ops::RangeFrom<usize>;
}

mod ethernet;
mod arp;
mod ipv4;

pub use ethernet::ProtocolType as EthernetProtocolType;
pub use ethernet::Address as EthernetAddress;
pub use ethernet::Frame as EthernetFrame;

pub use arp::HardwareType as ArpHardwareType;
pub use arp::ProtocolType as ArpProtocolType;
pub use arp::Operation as ArpOperation;
pub use arp::Packet as ArpPacket;
pub use arp::Repr as ArpRepr;

pub use ipv4::Address as Ipv4Address;
pub mod wire;
pub mod interface;
73 changes: 55 additions & 18 deletions src/arp.rs → src/wire/arp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use core::fmt;
use byteorder::{ByteOrder, NetworkEndian};

pub use ::ethernet::ProtocolType as ProtocolType;
pub use super::EthernetProtocolType as ProtocolType;

enum_with_unknown! {
/// ARP network protocol type.
@@ -24,7 +25,7 @@ pub struct Packet<T: AsRef<[u8]>>(T);
mod field {
#![allow(non_snake_case)]

use ::field::*;
use ::wire::field::*;

pub const HTYPE: Field = 0..2;
pub const PTYPE: Field = 2..4;
@@ -209,16 +210,36 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
}
}

impl<T: AsRef<[u8]>> fmt::Display for Packet<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match Repr::parse(self) {
Ok(repr) => write!(f, "{}", repr),
_ => {
try!(write!(f, "ARP htype={:?} ptype={:?} hlen={:?} plen={:?} op={:?}",
self.hardware_type(), self.protocol_type(),
self.hardware_length(), self.protocol_length(),
self.operation()));
try!(write!(f, " sha={:?} spa={:?} tha={:?} tpa={:?}",
self.source_hardware_addr(), self.source_protocol_addr(),
self.target_hardware_addr(), self.target_protocol_addr()));
Ok(())
}
}
}
}

use super::{EthernetAddress, Ipv4Address};

/// A high-level representation of an Address Resolution Protocol packet.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Repr {
/// An Ethernet and IPv4 Address Resolution Protocol packet.
EthernetIpv4 {
operation: Operation,
source_hardware_addr: ::EthernetAddress,
source_protocol_addr: ::Ipv4Address,
target_hardware_addr: ::EthernetAddress,
target_protocol_addr: ::Ipv4Address
source_hardware_addr: EthernetAddress,
source_protocol_addr: Ipv4Address,
target_hardware_addr: EthernetAddress,
target_protocol_addr: Ipv4Address
},
#[doc(hidden)]
__Nonexhaustive
@@ -234,13 +255,13 @@ impl Repr {
Ok(Repr::EthernetIpv4 {
operation: packet.operation(),
source_hardware_addr:
::EthernetAddress::from_bytes(packet.source_hardware_addr()),
EthernetAddress::from_bytes(packet.source_hardware_addr()),
source_protocol_addr:
::Ipv4Address::from_bytes(packet.source_protocol_addr()),
Ipv4Address::from_bytes(packet.source_protocol_addr()),
target_hardware_addr:
::EthernetAddress::from_bytes(packet.target_hardware_addr()),
EthernetAddress::from_bytes(packet.target_hardware_addr()),
target_protocol_addr:
::Ipv4Address::from_bytes(packet.target_protocol_addr())
Ipv4Address::from_bytes(packet.target_protocol_addr())
})
},
_ => Err(())
@@ -252,10 +273,8 @@ impl Repr {
match self {
&Repr::EthernetIpv4 {
operation,
source_hardware_addr,
source_protocol_addr,
target_hardware_addr,
target_protocol_addr
source_hardware_addr, source_protocol_addr,
target_hardware_addr, target_protocol_addr
} => {
packet.set_hardware_type(HardwareType::Ethernet);
packet.set_protocol_type(ProtocolType::Ipv4);
@@ -272,6 +291,24 @@ impl Repr {
}
}

impl fmt::Display for Repr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Repr::EthernetIpv4 {
operation,
source_hardware_addr, source_protocol_addr,
target_hardware_addr, target_protocol_addr
} => {
write!(f, "ARP type=Ethernet+IPv4 src={}/{} dst={}/{} op={:?}",
source_hardware_addr, source_protocol_addr,
target_hardware_addr, target_protocol_addr,
operation)
},
&Repr::__Nonexhaustive => unreachable!()
}
}
}

#[cfg(test)]
mod test {
use super::*;
@@ -321,13 +358,13 @@ mod test {
Repr::EthernetIpv4 {
operation: Operation::Request,
source_hardware_addr:
::EthernetAddress::from_bytes(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]),
EthernetAddress::from_bytes(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]),
source_protocol_addr:
::Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]),
Ipv4Address::from_bytes(&[0x21, 0x22, 0x23, 0x24]),
target_hardware_addr:
::EthernetAddress::from_bytes(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]),
EthernetAddress::from_bytes(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]),
target_protocol_addr:
::Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44])
Ipv4Address::from_bytes(&[0x41, 0x42, 0x43, 0x44])
}
}

41 changes: 30 additions & 11 deletions src/ethernet.rs → src/wire/ethernet.rs
Original file line number Diff line number Diff line change
@@ -3,10 +3,21 @@ use byteorder::{ByteOrder, NetworkEndian};

enum_with_unknown! {
/// Ethernet protocol type.
pub enum ProtocolType(u16) {
pub enum EtherType(u16) {
Ipv4 = 0x0800,
Arp = 0x0806,
Iv6 = 0x86DD
Ipv6 = 0x86DD
}
}

impl fmt::Display for EtherType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&EtherType::Ipv4 => write!(f, "IPv4"),
&EtherType::Ipv6 => write!(f, "IPv6"),
&EtherType::Arp => write!(f, "ARP"),
&EtherType::Unknown(ty) => write!(f, "0x{:04x}", ty)
}
}
}

@@ -44,11 +55,11 @@ impl fmt::Display for Address {
pub struct Frame<T: AsRef<[u8]>>(T);

mod field {
use ::field::*;
use ::wire::field::*;

pub const SOURCE: Field = 0..6;
pub const DESTINATION: Field = 6..12;
pub const LENGTH: Field = 12..14;
pub const ETHERTYPE: Field = 12..14;
pub const PAYLOAD: FieldFrom = 14..;
}

@@ -57,7 +68,7 @@ impl<T: AsRef<[u8]>> Frame<T> {
/// is too small or too large to contain one.
pub fn new(storage: T) -> Result<Frame<T>, ()> {
let len = storage.as_ref().len();
if !(64..1518).contains(len) {
if !(14..1518).contains(len) {
Err(()) // TODO: error type?
} else {
Ok(Frame(storage))
@@ -83,11 +94,12 @@ impl<T: AsRef<[u8]>> Frame<T> {
Address::from_bytes(&bytes[field::DESTINATION])
}

/// Return the length field, without checking for 802.1Q.
/// Return the EtherType field, without checking for 802.1Q.
#[inline(always)]
pub fn length(&self) -> u16 {
pub fn ethertype(&self) -> EtherType {
let bytes = self.0.as_ref();
NetworkEndian::read_u16(&bytes[field::LENGTH])
let raw = NetworkEndian::read_u16(&bytes[field::ETHERTYPE]);
EtherType::from(raw)
}

/// Return a pointer to the payload, without checking for 802.1Q.
@@ -113,11 +125,11 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Frame<T> {
bytes[field::DESTINATION].copy_from_slice(value.as_bytes())
}

/// Set the length field.
/// Set the EtherType field.
#[inline(always)]
pub fn set_length(&mut self, value: u16) {
pub fn set_ethertype(&mut self, value: EtherType) {
let bytes = self.0.as_mut();
NetworkEndian::write_u16(&mut bytes[field::LENGTH], value)
NetworkEndian::write_u16(&mut bytes[field::ETHERTYPE], value.into())
}

/// Return a mutable pointer to the payload.
@@ -128,6 +140,13 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Frame<T> {
}
}

impl<T: AsRef<[u8]>> fmt::Display for Frame<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "EthernetII src={} dst={} type={}",
self.source(), self.destination(), self.ethertype())
}
}

#[cfg(test)]
mod test {
use super::*;
3 changes: 1 addition & 2 deletions src/ipv4.rs → src/wire/ipv4.rs
Original file line number Diff line number Diff line change
@@ -24,7 +24,6 @@ impl Address {
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.0;
write!(f, "{:02x}.{:02x}.{:02x}.{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3])
write!(f, "{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
}
}
72 changes: 72 additions & 0 deletions src/wire/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Low-level packet access and construction.
//!
//! The `wire` module deals with the packet *representation*. It provides two levels
//! of functionality.
//!
//! * First, it provides functions to extract fields from sequences of octets,
//! and to insert fields into sequences of octets. This happens through the `Frame`
//! and `Packet` families of structures, e.g. [EthernetPacket](struct.EthernetPacket.html).
//!
//! * Second, in cases where the space of valid field values is much smaller than the space
//! of possible field values, it provides a compact, high-level representation
//! of packet data that can be parsed from and emitted into a sequence of octets.
//! This happens through the `Repr` family of enums, e.g. [ArpRepr](enum.ArpRepr.html).
//!
//! The functions in the `wire` module are designed for robustness and use together with
//! `-Cpanic=abort`. The accessor and parsing functions never panic. The setter and emission
//! functions only panic if the underlying buffer is too small.
//!
//! The data structures in the `wire` module do not perform validation of received data;
//! that is the job of an upper layer. This includes the `Repr` family, which only validate
//! as much as is necessary to build the representation.
macro_rules! enum_with_unknown {
(#[$( $attr:meta ),*]
pub enum $name:ident($ty:ty) { $( $variant:ident = $value:expr ),+ }) => {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[$( $attr ),*]
pub enum $name {
$( $variant ),*,
Unknown($ty)
}

impl ::core::convert::From<$ty> for $name {
fn from(value: $ty) -> Self {
match value {
$( $value => $name::$variant ),*,
other => $name::Unknown(other)
}
}
}

impl ::core::convert::From<$name> for $ty {
fn from(value: $name) -> Self {
match value {
$( $name::$variant => $value ),*,
$name::Unknown(other) => other
}
}
}
}
}

mod field {
pub type Field = ::core::ops::Range<usize>;
pub type FieldFrom = ::core::ops::RangeFrom<usize>;
}

mod ethernet;
mod arp;
mod ipv4;

pub use self::ethernet::EtherType as EthernetProtocolType;
pub use self::ethernet::Address as EthernetAddress;
pub use self::ethernet::Frame as EthernetFrame;

pub use self::arp::HardwareType as ArpHardwareType;
pub use self::arp::ProtocolType as ArpProtocolType;
pub use self::arp::Operation as ArpOperation;
pub use self::arp::Packet as ArpPacket;
pub use self::arp::Repr as ArpRepr;

pub use self::ipv4::Address as Ipv4Address;