Forráskód Böngészése

Implement raw socket interface and tcpdump.

whitequark 8 éve
szülő
commit
10d538c02f
9 módosított fájl, 226 hozzáadás és 17 törlés
  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. 1 0
      src/lib.rs
  6. 39 4
      src/wire/arp.rs
  7. 29 10
      src/wire/ethernet.rs
  8. 1 2
      src/wire/ipv4.rs
  9. 1 1
      src/wire/mod.rs

+ 5 - 0
Cargo.toml

@@ -5,3 +5,8 @@ authors = ["whitequark <[email protected]>"]
 
 [dependencies]
 byteorder = { version = "0.5", default-features = false }
+libc = { version = "0.2.18", optional = true }
+
+[features]
+std = ["libc"]
+default = ["std"]

+ 39 - 0
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<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 - 0
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;

+ 101 - 0
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<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); }
+    }
+}

+ 1 - 0
src/lib.rs

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

+ 39 - 4
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<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::*;

+ 29 - 10
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<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::*;

+ 1 - 2
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])
     }
 }

+ 1 - 1
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;