Implement raw socket interface and tcpdump.

v0.7.x
whitequark 2016-12-10 16:25:43 +00:00
parent 32ff49a795
commit b1149e746c
9 changed files with 226 additions and 17 deletions

View File

@ -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
examples/smoltcpdump.rs Normal file
View File

@ -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
src/interface/mod.rs Normal file
View File

@ -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
src/interface/raw_socket.rs Normal file
View File

@ -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); }
}
}

View File

@ -8,3 +8,4 @@ extern crate std;
extern crate byteorder;
pub mod wire;
pub mod interface;

View File

@ -1,3 +1,4 @@
use core::fmt;
use byteorder::{ByteOrder, NetworkEndian};
pub use super::EthernetProtocolType as ProtocolType;
@ -209,6 +210,24 @@ 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.
@ -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::*;

View File

@ -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<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::*;

View File

@ -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])
}
}

View File

@ -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;