Browse Source

Implement ICMPv4 echo replies.

whitequark 8 years ago
parent
commit
4da6b0bf61
4 changed files with 113 additions and 29 deletions
  1. 6 4
      README.md
  2. 79 15
      src/iface/ethernet.rs
  3. 12 7
      src/lib.rs
  4. 16 3
      src/wire/icmpv4.rs

+ 6 - 4
README.md

@@ -18,11 +18,10 @@ features are listed.
 The only supported medium is Ethernet.
 
   * Regular 802.3 frames are supported.
+  * ARP packets (including gratuitous requests and replies) are supported.
   * 802.1Q is **not** supported.
   * Jumbo frames are **not** supported.
-  * CRC calculation is **not** supported.
-  * ARP packets are supported.
-  * ARP probes or announcements are **not** supported.
+  * Frame check sequence calculation is **not** supported.
 
 ### IP layer
 
@@ -31,6 +30,9 @@ The only supported internetworking protocol is IPv4.
   * IPv4 header checksum is supported.
   * IPv4 fragmentation is **not** supported.
   * IPv4 options are **not** supported.
+  * ICMPv4 echo requests and replies are supported.
+  * ICMPv4 destination unreachable message is **not** supported.
+  * ICMPv4 parameter problem message is **not** supported.
 
 ### UDP layer
 
@@ -91,7 +93,7 @@ cargo run --example smoltcpserver -- tap0
 
 It responds to:
 
-  * pings (`ping 192.168.69.1`) (actually not yet).
+  * pings (`ping 192.168.69.1`).
 
 License
 -------

+ 79 - 15
src/iface/ethernet.rs

@@ -2,6 +2,9 @@ use Error;
 use phy::Device;
 use wire::{EthernetAddress, EthernetProtocolType, EthernetFrame};
 use wire::{ArpPacket, ArpRepr, ArpOperation};
+use wire::InternetProtocolType;
+use wire::{Ipv4Packet, Ipv4Repr};
+use wire::{Icmpv4Packet, Icmpv4Repr};
 use super::{ProtocolAddress, ArpCache};
 
 /// An Ethernet network interface.
@@ -71,19 +74,20 @@ impl<'a, DeviceT: Device, ArpCacheT: ArpCache> Interface<'a, DeviceT, ArpCacheT>
 
     /// Receive and process a packet, if available.
     pub fn poll(&mut self) -> Result<(), Error> {
-        enum Response {
+        enum Response<'a> {
             Nop,
-            Arp(ArpRepr)
+            Arp(ArpRepr),
+            Icmpv4(Ipv4Repr, Icmpv4Repr<'a>)
         }
         let mut response = Response::Nop;
 
         let rx_buffer = try!(self.device.receive());
-        let frame = try!(EthernetFrame::new(rx_buffer));
-        match frame.ethertype() {
+        let eth_frame = try!(EthernetFrame::new(rx_buffer));
+        match eth_frame.ethertype() {
+            // Snoop all ARP traffic, and respond to ARP packets directed at us.
             EthernetProtocolType::Arp => {
-                let packet = try!(ArpPacket::new(frame.payload()));
-                let repr = try!(ArpRepr::parse(&packet));
-                match repr {
+                let arp_packet = try!(ArpPacket::new(eth_frame.payload()));
+                match try!(ArpRepr::parse(&arp_packet)) {
                     // Respond to ARP requests aimed at us, and fill the ARP cache
                     // from all ARP requests, including gratuitous.
                     ArpRepr::EthernetIpv4 {
@@ -115,17 +119,61 @@ impl<'a, DeviceT: Device, ArpCacheT: ArpCache> Interface<'a, DeviceT, ArpCacheT>
                     _ => return Err(Error::Unrecognized)
                 }
             },
+
+            // Respond to IP packets directed at us.
+            EthernetProtocolType::Ipv4 => {
+                let ip_packet = try!(Ipv4Packet::new(eth_frame.payload()));
+                match try!(Ipv4Repr::parse(&ip_packet)) {
+                    // Ignore IP packets not directed at us.
+                    Ipv4Repr { dst_addr, .. } if !self.has_protocol_addr(dst_addr) => (),
+
+                    // Respond to ICMP packets.
+                    Ipv4Repr { protocol: InternetProtocolType::Icmp, src_addr, dst_addr } => {
+                        let icmp_packet = try!(Icmpv4Packet::new(ip_packet.payload()));
+                        let icmp_repr = try!(Icmpv4Repr::parse(&icmp_packet));
+                        match icmp_repr {
+                            // Respond to echo requests.
+                            Icmpv4Repr::EchoRequest {
+                                ident, seq_no, data
+                            } => {
+                                let ip_reply_repr = Ipv4Repr {
+                                    src_addr: dst_addr,
+                                    dst_addr: src_addr,
+                                    protocol: InternetProtocolType::Icmp
+                                };
+                                let icmp_reply_repr = Icmpv4Repr::EchoReply {
+                                    ident:  ident,
+                                    seq_no: seq_no,
+                                    data:   &[]
+                                };
+                                response = Response::Icmpv4(ip_reply_repr, icmp_reply_repr)
+                            }
+
+                            // Ignore any echo replies.
+                            Icmpv4Repr::EchoReply { .. } => (),
+
+                            // FIXME: do something correct here?
+                            _ => return Err(Error::Unrecognized)
+                        }
+                    },
+
+                    // FIXME: respond with ICMP unknown protocol here?
+                    _ => return Err(Error::Unrecognized)
+                }
+            }
+
+            // Drop all other traffic.
             _ => return Err(Error::Unrecognized)
         }
+        if let Response::Nop = response { return Ok(()) }
 
-        match response {
-            Response::Nop => Ok(()),
+        let tx_size = self.device.mtu();
+        let tx_buffer = try!(self.device.transmit(tx_size));
+        let mut frame = try!(EthernetFrame::new(tx_buffer));
+        frame.set_src_addr(self.hardware_addr);
 
+        match response {
             Response::Arp(repr) => {
-                let tx_size = self.device.mtu();
-                let tx_buffer = try!(self.device.transmit(tx_size));
-                let mut frame = try!(EthernetFrame::new(tx_buffer));
-                frame.set_src_addr(self.hardware_addr);
                 frame.set_dst_addr(match repr {
                     ArpRepr::EthernetIpv4 { target_hardware_addr, .. } => target_hardware_addr,
                     _ => unreachable!()
@@ -133,10 +181,26 @@ impl<'a, DeviceT: Device, ArpCacheT: ArpCache> Interface<'a, DeviceT, ArpCacheT>
                 frame.set_ethertype(EthernetProtocolType::Arp);
 
                 let mut packet = try!(ArpPacket::new(frame.payload_mut()));
-                repr.emit(&mut packet);
+                repr.emit(&mut packet)
+            },
+
+            Response::Icmpv4(ip_repr, icmp_repr) => {
+                match self.arp_cache.lookup(ip_repr.dst_addr.into()) {
+                    None => return Err(Error::Unaddressable),
+                    Some(hardware_addr) => frame.set_dst_addr(hardware_addr)
+                }
+                frame.set_ethertype(EthernetProtocolType::Ipv4);
+
+                let mut ip_packet = try!(Ipv4Packet::new(frame.payload_mut()));
+                ip_repr.emit(&mut ip_packet, icmp_repr.len());
 
-                Ok(())
+                let mut icmp_packet = try!(Icmpv4Packet::new(ip_packet.payload_mut()));
+                icmp_repr.emit(&mut icmp_packet);
             }
+
+            Response::Nop => unreachable!()
         }
+
+        Ok(())
     }
 }

+ 12 - 7
src/lib.rs

@@ -18,8 +18,8 @@ pub mod iface;
 /// The error type for the networking stack.
 #[derive(Debug)]
 pub enum Error {
-    /// A packet could not be parsed or emitted because a field was out of bounds
-    /// for the underlying buffer.
+    /// An incoming packet could not be parsed, or an outgoing packet could not be emitted
+    /// because a field was out of bounds for the underlying buffer.
     Truncated,
     /// An incoming packet could not be recognized and was dropped.
     /// E.g. a packet with an unknown EtherType.
@@ -32,6 +32,10 @@ pub enum Error {
     Checksum,
     /// An incoming packet has been fragmented and was dropped.
     Fragmented,
+    /// An outgoing packet could not be sent because a protocol address could not be mapped
+    /// to hardware address. E.g. an IPv4 packet did not have an Ethernet address
+    /// corresponding to its IPv4 destination address.
+    Unaddressable,
 
     #[doc(hidden)]
     __Nonexhaustive
@@ -40,11 +44,12 @@ pub enum Error {
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            &Error::Truncated    => write!(f, "truncated packet"),
-            &Error::Unrecognized => write!(f, "unrecognized packet"),
-            &Error::Malformed    => write!(f, "malformed packet"),
-            &Error::Checksum     => write!(f, "checksum error"),
-            &Error::Fragmented   => write!(f, "fragmented packet"),
+            &Error::Truncated     => write!(f, "truncated packet"),
+            &Error::Unrecognized  => write!(f, "unrecognized packet"),
+            &Error::Malformed     => write!(f, "malformed packet"),
+            &Error::Checksum      => write!(f, "checksum error"),
+            &Error::Fragmented    => write!(f, "fragmented packet"),
+            &Error::Unaddressable => write!(f, "unaddressable destination"),
             &Error::__Nonexhaustive => unreachable!()
         }
     }

+ 16 - 3
src/wire/icmpv4.rs

@@ -1,4 +1,4 @@
-use core::fmt;
+use core::{cmp, fmt};
 use byteorder::{ByteOrder, NetworkEndian};
 
 use Error;
@@ -332,6 +332,17 @@ impl<'a> Repr<'a> {
         }
     }
 
+    /// Return the length of a packet that will be emitted from this high-level representation.
+    pub fn len(&self) -> usize {
+        match self {
+            &Repr::EchoRequest { data, .. } |
+            &Repr::EchoReply { data, .. } => {
+                field::ECHO_SEQNO.end + data.len()
+            },
+            &Repr::__Nonexhaustive => unreachable!()
+        }
+    }
+
     /// Emit a high-level representation into an Internet Protocol version 4 packet.
     pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
         packet.set_msg_code(0);
@@ -340,13 +351,15 @@ impl<'a> Repr<'a> {
                 packet.set_msg_type(Type::EchoRequest);
                 packet.set_echo_ident(ident);
                 packet.set_echo_seq_no(seq_no);
-                packet.data_mut().copy_from_slice(data)
+                let data_len = cmp::min(packet.data_mut().len(), data.len());
+                packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
             },
             &Repr::EchoReply { ident, seq_no, data } => {
                 packet.set_msg_type(Type::EchoReply);
                 packet.set_echo_ident(ident);
                 packet.set_echo_seq_no(seq_no);
-                packet.data_mut().copy_from_slice(data)
+                let data_len = cmp::min(packet.data_mut().len(), data.len());
+                packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
             },
             &Repr::__Nonexhaustive => unreachable!()
         }