diff --git a/Cargo.toml b/Cargo.toml index 7bd7ab7..a1f7da1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,8 @@ authors = ["whitequark "] [dependencies] byteorder = { version = "0.5", default-features = false } +libc = { version = "0.2.18", optional = true } + +[features] +std = ["libc"] +default = ["std"] diff --git a/examples/smoltcpdump.rs b/examples/smoltcpdump.rs new file mode 100644 index 0000000..6a9035a --- /dev/null +++ b/examples/smoltcpdump.rs @@ -0,0 +1,39 @@ +extern crate smoltcp; + +use std::{env, io}; +use smoltcp::wire::{EthernetFrame, EthernetProtocolType, ArpPacket}; +use smoltcp::interface::RawSocket; + +fn get(result: Result) -> io::Result { + 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) + } + } +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs new file mode 100644 index 0000000..2f78667 --- /dev/null +++ b/src/interface/mod.rs @@ -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; diff --git a/src/interface/raw_socket.rs b/src/interface/raw_socket.rs new file mode 100644 index 0000000..9467dfc --- /dev/null +++ b/src/interface/raw_socket.rs @@ -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 +} + +impl RawSocket { + /// Creates and returns a raw socket, bound to the interface called `name`. + pub fn new(name: &str) -> io::Result { + 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::() 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); } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2f4fa5e..6f7a689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,4 @@ extern crate std; extern crate byteorder; pub mod wire; +pub mod interface; diff --git a/src/wire/arp.rs b/src/wire/arp.rs index fea1569..b0984d2 100644 --- a/src/wire/arp.rs +++ b/src/wire/arp.rs @@ -1,3 +1,4 @@ +use core::fmt; use byteorder::{ByteOrder, NetworkEndian}; pub use super::EthernetProtocolType as ProtocolType; @@ -209,6 +210,24 @@ impl + AsMut<[u8]>> Packet { } } +impl> fmt::Display for Packet { + 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. @@ -254,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); @@ -274,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::*; diff --git a/src/wire/ethernet.rs b/src/wire/ethernet.rs index 8b2831e..f04c9c4 100644 --- a/src/wire/ethernet.rs +++ b/src/wire/ethernet.rs @@ -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) + } } } @@ -48,7 +59,7 @@ mod 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> Frame { /// is too small or too large to contain one. pub fn new(storage: T) -> Result, ()> { 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> Frame { 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 + AsMut<[u8]>> Frame { 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 + AsMut<[u8]>> Frame { } } +impl> fmt::Display for Frame { + 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::*; diff --git a/src/wire/ipv4.rs b/src/wire/ipv4.rs index c39ffb0..992662c 100644 --- a/src/wire/ipv4.rs +++ b/src/wire/ipv4.rs @@ -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]) } } diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 0417bd9..9e73c7f 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -59,7 +59,7 @@ mod ethernet; mod arp; mod ipv4; -pub use self::ethernet::ProtocolType as EthernetProtocolType; +pub use self::ethernet::EtherType as EthernetProtocolType; pub use self::ethernet::Address as EthernetAddress; pub use self::ethernet::Frame as EthernetFrame;