From adb501478029ea2ddff009501586a9d2351da4b9 Mon Sep 17 00:00:00 2001 From: Dan Robertson Date: Mon, 30 Oct 2017 00:19:34 +0000 Subject: [PATCH] Add ICMP sockets - Add support for ICMP sockets - Add tests for ICMP sockets - Rename proto- features to socket- - Update documentation --- .travis.yml | 8 +- Cargo.toml | 4 +- examples/ping.rs | 68 ++--- src/iface/ethernet.rs | 115 +++++++-- src/lib.rs | 6 +- src/socket/icmp.rs | 583 ++++++++++++++++++++++++++++++++++++++++++ src/socket/mod.rs | 15 +- src/socket/raw.rs | 4 +- src/socket/ref_.rs | 4 + src/socket/set.rs | 3 + src/socket/tcp.rs | 4 +- src/socket/udp.rs | 4 +- 12 files changed, 749 insertions(+), 69 deletions(-) create mode 100644 src/socket/icmp.rs diff --git a/.travis.yml b/.travis.yml index 26ec5da..c645a04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,13 @@ matrix: - rust: nightly env: FEATURES='socket-tcp' MODE='build' - rust: nightly - env: FEATURES='socket-raw socket-udp socket-tcp' MODE='build' + env: FEATURES='socket-icmp' MODE='build' - rust: nightly - env: FEATURES='socket-raw socket-udp socket-tcp std' MODE='build' + env: FEATURES='socket-raw socket-udp socket-tcp socket-icmp' MODE='build' - rust: nightly - env: FEATURES='socket-raw socket-udp socket-tcp alloc' MODE='build' + env: FEATURES='socket-raw socket-udp socket-tcp socket-icmp std' MODE='build' + - rust: nightly + env: FEATURES='socket-raw socket-udp socket-tcp socket-icmp alloc' MODE='build' script: - cargo "$MODE" --no-default-features --features "$FEATURES" notifications: diff --git a/Cargo.toml b/Cargo.toml index ee6c4f4..9d9539e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,9 +31,11 @@ verbose = [] "socket-raw" = [] "socket-udp" = [] "socket-tcp" = [] +"socket-icmp" = [] default = ["std", "log", "phy-raw_socket", "phy-tap_interface", - "socket-raw", "socket-udp", "socket-tcp"] + "socket-raw", "socket-udp", "socket-tcp", + "socket-icmp"] [[example]] name = "tcpdump" diff --git a/examples/ping.rs b/examples/ping.rs index ed40a71..f19d341 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -12,11 +12,10 @@ use std::time::Instant; use std::os::unix::io::AsRawFd; use smoltcp::phy::Device; use smoltcp::phy::wait as phy_wait; -use smoltcp::wire::{EthernetAddress, IpVersion, IpProtocol, IpAddress, IpCidr, - Ipv4Address, Ipv4Packet, Ipv4Repr, - Icmpv4Repr, Icmpv4Packet}; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, + Ipv4Address, Icmpv4Repr, Icmpv4Packet}; use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface}; -use smoltcp::socket::{SocketSet, RawSocket, RawSocketBuffer, RawPacketBuffer}; +use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketBuffer, IcmpEndpoint}; use std::collections::HashMap; use byteorder::{ByteOrder, NetworkEndian}; @@ -51,10 +50,9 @@ fn main() { let remote_addr = address; let local_addr = Ipv4Address::new(192, 168, 69, 1); - let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]); - let raw_tx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]); - let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Icmp, - raw_rx_buffer, raw_tx_buffer); + let icmp_rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 256])]); + let icmp_tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 256])]); + let icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer); let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]); let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24); @@ -64,17 +62,22 @@ fn main() { ethernet_addr, [ip_addr], Some(default_v4_gw)); let mut sockets = SocketSet::new(vec![]); - let raw_handle = sockets.add(raw_socket); + let icmp_handle = sockets.add(icmp_socket); let mut send_at = 0; let mut seq_no = 0; let mut received = 0; let mut echo_payload = [0xffu8; 40]; let mut waiting_queue = HashMap::new(); + let ident = 0x22b; + let endpoint = IpAddress::Ipv4(remote_addr); loop { { - let mut socket = sockets.get::(raw_handle); + let mut socket = sockets.get::(icmp_handle); + if !socket.is_open() { + socket.bind(IcmpEndpoint::Ident(ident)).unwrap() + } let timestamp = Instant::now().duration_since(startup_time); let timestamp_us = (timestamp.as_secs() * 1000000) + @@ -84,26 +87,16 @@ fn main() { send_at <= utils::millis_since(startup_time) { NetworkEndian::write_u64(&mut echo_payload, timestamp_us); let icmp_repr = Icmpv4Repr::EchoRequest { - ident: 1, + ident: ident, seq_no, data: &echo_payload, }; - let ipv4_repr = Ipv4Repr { - /*src_addr: Ipv4Address::UNSPECIFIED,*/ - src_addr: Ipv4Address::new(0, 0, 0, 0), - dst_addr: remote_addr, - protocol: IpProtocol::Icmp, - payload_len: icmp_repr.buffer_len(), - ttl: 64 - }; - let raw_payload = socket - .send(ipv4_repr.buffer_len() + icmp_repr.buffer_len()) + let icmp_payload = socket + .send(icmp_repr.buffer_len(), endpoint) .unwrap(); - let mut ipv4_packet = Ipv4Packet::new(raw_payload); - ipv4_repr.emit(&mut ipv4_packet, &device_caps.checksum); - let mut icmp_packet = Icmpv4Packet::new(ipv4_packet.payload_mut()); + let mut icmp_packet = Icmpv4Packet::new(icmp_payload); icmp_repr.emit(&mut icmp_packet, &device_caps.checksum); waiting_queue.insert(seq_no, timestamp); @@ -112,23 +105,18 @@ fn main() { } if socket.can_recv() { - let payload = socket.recv().unwrap(); - let ipv4_packet = Ipv4Packet::new(payload); - let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &device_caps.checksum).unwrap(); + let (payload, _) = socket.recv().unwrap(); + let icmp_packet = Icmpv4Packet::new(&payload); + let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap(); - if ipv4_repr.src_addr == remote_addr && ipv4_repr.dst_addr == local_addr { - let icmp_packet = Icmpv4Packet::new(ipv4_packet.payload()); - let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum); - - if let Ok(Icmpv4Repr::EchoReply { seq_no, data, .. }) = icmp_repr { - if let Some(_) = waiting_queue.get(&seq_no) { - let packet_timestamp_us = NetworkEndian::read_u64(data); - println!("{} bytes from {}: icmp_seq={}, time={:.3}ms", - data.len(), remote_addr, seq_no, - (timestamp_us - packet_timestamp_us) as f64 / 1000.0); - waiting_queue.remove(&seq_no); - received += 1; - } + if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr { + if let Some(_) = waiting_queue.get(&seq_no) { + let packet_timestamp_us = NetworkEndian::read_u64(data); + println!("{} bytes from {}: icmp_seq={}, time={:.3}ms", + data.len(), remote_addr, seq_no, + (timestamp_us - packet_timestamp_us) as f64 / 1000.0); + waiting_queue.remove(&seq_no); + received += 1; } } } diff --git a/src/iface/ethernet.rs b/src/iface/ethernet.rs index 67c0f4f..27e92a6 100644 --- a/src/iface/ethernet.rs +++ b/src/iface/ethernet.rs @@ -24,6 +24,8 @@ use socket::RawSocket; use socket::UdpSocket; #[cfg(feature = "socket-tcp")] use socket::TcpSocket; +#[cfg(feature = "socket-icmp")] +use socket::IcmpSocket; use super::ArpCache; /// An Ethernet network interface. @@ -55,7 +57,7 @@ struct InterfaceInner<'b, 'c> { enum Packet<'a> { None, Arp(ArpRepr), - Icmpv4(Ipv4Repr, Icmpv4Repr<'a>), + Icmpv4((Ipv4Repr, Icmpv4Repr<'a>)), #[cfg(feature = "socket-raw")] Raw((IpRepr, &'a [u8])), #[cfg(feature = "socket-udp")] @@ -235,6 +237,16 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT> device_result = inner.dispatch(tx_token, timestamp, Packet::Tcp(response)); device_result }), + #[cfg(feature = "socket-icmp")] + Socket::Icmp(ref mut socket) => + socket.dispatch(&caps, |response| { + let tx_token = device.transmit().ok_or(Error::Exhausted)?; + match response { + (IpRepr::Ipv4(repr), icmp_repr) => + inner.dispatch(tx_token, timestamp, Packet::Icmpv4((repr, icmp_repr))), + _ => Err(Error::Unaddressable), + } + }), Socket::__Nonexhaustive(_) => unreachable!() }; match (device_result, socket_result) { @@ -384,7 +396,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { match ipv4_repr.protocol { IpProtocol::Icmp => - self.process_icmpv4(ipv4_repr, ip_payload), + self.process_icmpv4(sockets, ip_repr, ip_payload), #[cfg(feature = "socket-udp")] IpProtocol::Udp => @@ -412,13 +424,25 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { } } - fn process_icmpv4<'frame>(&self, ipv4_repr: Ipv4Repr, ip_payload: &'frame [u8]) -> - Result> + fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr, + ip_payload: &'frame [u8]) -> Result> { let icmp_packet = Icmpv4Packet::new_checked(ip_payload)?; let checksum_caps = self.device_capabilities.checksum.clone(); let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &checksum_caps)?; + #[cfg(feature = "socket-icmp")] + for mut icmp_socket in _sockets.iter_mut().filter_map(IcmpSocket::downcast) { + if !icmp_socket.accepts(&ip_repr, &icmp_repr, &checksum_caps) { continue } + + match icmp_socket.process(&ip_repr, ip_payload) { + // The packet is valid and handled by socket. + Ok(()) => return Ok(Packet::None), + // The packet is malformed, or the socket buffer is full. + Err(e) => return Err(e) + } + } + match icmp_repr { // Respond to echo requests. Icmpv4Repr::EchoRequest { ident, seq_no, data } => { @@ -427,7 +451,10 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { seq_no: seq_no, data: data }; - Ok(self.icmpv4_reply(ipv4_repr, icmp_reply_repr)) + match ip_repr { + IpRepr::Ipv4(ipv4_repr) => Ok(self.icmpv4_reply(ipv4_repr, icmp_reply_repr)), + _ => Err(Error::Unrecognized), + } } // Ignore any echo replies. @@ -450,7 +477,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { payload_len: icmp_repr.buffer_len(), ttl: 64 }; - Packet::Icmpv4(ipv4_reply_repr, icmp_repr) + Packet::Icmpv4((ipv4_reply_repr, icmp_repr)) } else { // Do not send any ICMP replies to a broadcast destination address. Packet::None @@ -549,7 +576,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { arp_repr.emit(&mut packet); }) }, - Packet::Icmpv4(ipv4_repr, icmpv4_repr) => { + Packet::Icmpv4((ipv4_repr, icmpv4_repr)) => { self.dispatch_ip(tx_token, timestamp, IpRepr::Ipv4(ipv4_repr), |_ip_repr, payload| { icmpv4_repr.emit(&mut Icmpv4Packet::new(payload), &checksum_caps); }) @@ -702,17 +729,19 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { #[cfg(test)] mod test { use std::boxed::Box; - use super::Packet; + use {Result, Error}; + + use iface::{ArpCache, SliceArpCache, EthernetInterface}; use phy::{self, Loopback, ChecksumCapabilities}; + use socket::SocketSet; use wire::{ArpOperation, ArpPacket, ArpRepr}; use wire::{EthernetAddress, EthernetFrame, EthernetProtocol}; use wire::{IpAddress, IpCidr, IpProtocol, IpRepr}; use wire::{Ipv4Address, Ipv4Repr}; use wire::{Icmpv4Repr, Icmpv4DstUnreachable}; use wire::{UdpPacket, UdpRepr}; - use iface::{ArpCache, SliceArpCache, EthernetInterface}; - use socket::SocketSet; - use {Result, Error}; + + use super::Packet; fn create_loopback<'a, 'b>() -> (EthernetInterface<'static, 'b, Loopback>, SocketSet<'static, 'a, 'b>) { @@ -812,7 +841,7 @@ mod test { data: &NO_BYTES }; - let expected_repr = Packet::Icmpv4( + let expected_repr = Packet::Icmpv4(( Ipv4Repr { src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]), dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]), @@ -821,7 +850,7 @@ mod test { ttl: 64 }, icmp_repr - ); + )); // Ensure that the unknown protocol triggers an error response. // And we correctly handle no payload. @@ -877,7 +906,7 @@ mod test { }, data: &data }; - let expected_repr = Packet::Icmpv4( + let expected_repr = Packet::Icmpv4(( Ipv4Repr { src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]), dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]), @@ -886,7 +915,7 @@ mod test { ttl: 64 }, icmp_repr - ); + )); // Ensure that the unknown protocol triggers an error response. // And we correctly handle no payload. @@ -994,4 +1023,60 @@ mod test { &IpAddress::Ipv4(remote_ip_addr)), Ok((remote_hw_addr, MockTxToken))); } + + #[test] + #[cfg(feature = "socket-icmp")] + fn test_icmpv4_socket() { + use socket::{IcmpPacketBuffer, IcmpSocket, IcmpSocketBuffer, IcmpEndpoint}; + use wire::Icmpv4Packet; + + let (iface, mut socket_set) = create_loopback(); + + let rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 24])]); + let tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 24])]); + + let icmpv4_socket = IcmpSocket::new(rx_buffer, tx_buffer); + + let socket_handle = socket_set.add(icmpv4_socket); + + let ident = 0x1234; + { + let mut socket = socket_set.get::(socket_handle); + // Bind to the ID 0x1234 + assert_eq!(socket.bind(IcmpEndpoint::Ident(ident)), Ok(())); + } + + // Ensure the ident we bound to and the ident of the packet are the same. + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new(&mut bytes); + let echo_repr = Icmpv4Repr::EchoReply { + ident: ident, + seq_no: 0x5432, + data: &[0xff; 16], + }; + echo_repr.emit(&mut packet, &ChecksumCapabilities::default()); + let data = &packet.into_inner()[..]; + + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + protocol: IpProtocol::Icmp, + payload_len: 24, + ttl: 64 + }); + + // Open a socket and ensure the packet is handled due to the listening + // socket. + { + assert!(!socket_set.get::(socket_handle).can_recv()); + } + assert_eq!(iface.inner.process_icmpv4(&mut socket_set, ip_repr, data), + Ok(Packet::None)); + { + let mut socket = socket_set.get::(socket_handle); + assert!(socket.can_recv()); + assert_eq!(socket.recv(), + Ok((&data[..], IpAddress::Ipv4(Ipv4Address::new(0x7f, 0x00, 0x00, 0x02))))); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 80801de..89fe6e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,9 @@ //! //! # The socket layer //! The socket layer APIs are provided in the module [socket](socket/index.html); currently, -//! TCP and UDP sockets are provided. The socket API provides the usual primitives, but -//! necessarily differs in many from the [Berkeley socket API][berk], as the latter was not -//! designed to be used without heap allocation. +//! TCP, UDP, ICMP, and Raw sockets are provided. The socket API provides the usual primitives, +//! but necessarily differs in many from the [Berkeley socket API][berk], as the latter was +//! not designed to be used without heap allocation. //! [berk]: https://en.wikipedia.org/wiki/Berkeley_sockets //! //! The socket layer provides the buffering, packet construction and validation, and (for diff --git a/src/socket/icmp.rs b/src/socket/icmp.rs new file mode 100644 index 0000000..50c9eb0 --- /dev/null +++ b/src/socket/icmp.rs @@ -0,0 +1,583 @@ +use core::cmp; +use managed::Managed; + +use {Error, Result}; +use phy::{ChecksumCapabilities, DeviceCapabilities}; +use socket::{Socket, SocketHandle}; +use storage::{Resettable, RingBuffer}; +use wire::{IpAddress, IpEndpoint, IpProtocol, IpRepr}; +use wire::{Ipv4Address, Ipv4Repr}; +use wire::{Icmpv4Packet, Icmpv4Repr}; +use wire::{UdpPacket, UdpRepr}; + +/// Type of endpoint to bind the ICMP socket to. See [IcmpSocket::bind] for +/// more details. +/// +/// [IcmpSocket::bind]: struct.IcmpSocket.html#method.bind +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Endpoint { + Unspecified, + Ident(u16), + Udp(IpEndpoint) +} + +impl Endpoint { + pub fn is_specified(&self) -> bool { + match *self { + Endpoint::Ident(_) => true, + Endpoint::Udp(endpoint) => endpoint.port != 0, + Endpoint::Unspecified => false + } + } +} + +impl Default for Endpoint { + fn default() -> Endpoint { Endpoint::Unspecified } +} + +/// A buffered ICMPv4 packet. +#[derive(Debug)] +pub struct PacketBuffer<'a> { + endpoint: IpAddress, + size: usize, + payload: Managed<'a, [u8]> +} + +impl<'a> PacketBuffer<'a> { + /// Create a buffered packet. + pub fn new(payload: T) -> PacketBuffer<'a> + where T: Into> { + PacketBuffer { + endpoint: IpAddress::default(), + size: 0, + payload: payload.into() + } + } + + fn as_ref<'b>(&'b self) -> &'b [u8] { + &self.payload[..self.size] + } + + fn as_mut<'b>(&'b mut self) -> &'b mut [u8] { + &mut self.payload[..self.size] + } + + fn resize<'b>(&'b mut self, size: usize) -> Result<&'b mut Self> { + if self.payload.len() >= size { + self.size = size; + Ok(self) + } else { + Err(Error::Truncated) + } + } +} + +impl<'a> Resettable for PacketBuffer<'a> { + fn reset(&mut self) { + self.size = 0; + } +} + +/// An ICMPv4 packet ring buffer. +pub type SocketBuffer<'a, 'b: 'a> = RingBuffer<'a, PacketBuffer<'b>>; + +/// An ICMPv4 socket +/// +/// An ICMPv4 socket is bound to a specific [IcmpEndpoint] which may +/// be a sepecific UDP port to listen for ICMP error messages related +/// to the port or a specific ICMP identifier value. See [bind] for +/// more details. +/// +/// [IcmpEndpoint]: enum.IcmpEndpoint.html +/// [bind]: #method.bind +#[derive(Debug)] +pub struct IcmpSocket<'a, 'b: 'a> { + handle: SocketHandle, + rx_buffer: SocketBuffer<'a, 'b>, + tx_buffer: SocketBuffer<'a, 'b>, + /// The endpoint this socket is communicating with + endpoint: Endpoint, + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + ttl: Option +} + +impl<'a, 'b> IcmpSocket<'a, 'b> { + /// Create an ICMPv4 socket with the given buffers. + pub fn new(rx_buffer: SocketBuffer<'a, 'b>, tx_buffer: SocketBuffer<'a, 'b>) -> Socket<'a, 'b> { + Socket::Icmp(IcmpSocket { + handle: SocketHandle::EMPTY, + rx_buffer: rx_buffer, + tx_buffer: tx_buffer, + endpoint: Endpoint::default(), + ttl: None + }) + } + + /// Return the socket handle. + #[inline] + pub fn handle(&self) -> SocketHandle { + self.handle + } + + /// Set the socket handle. + pub(in super) fn set_handle(&mut self, handle: SocketHandle) { + self.handle = handle; + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_ttl](#method.set_ttl) method + pub fn ttl(&self) -> Option { + self.ttl + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set TTL value uses the default [IANA recommended] + /// value (64). + /// + /// # Panics + /// + /// This function panics if a TTL value of 0 is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_ttl(&mut self, ttl: Option) { + // A host MUST NOT send a datagram with a Time-to-Live (TTL) value of 0 + if let Some(0) = ttl { + panic!("the time-to-live value of a packet must not be zero") + } + + self.ttl = ttl + } + + /// Bind the socket to the given endpoint. + /// + /// This function returns `Err(Error::Illegal)` if the socket was open + /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)` + /// if `endpoint` is unspecified (see [is_specified]). + /// + /// # Examples + /// + /// ## Bind to ICMP Error messages associated with a specific UDP port: + /// + /// To [recv] ICMP error messages that are associated with a specific local + /// UDP port, the socket may be bound to a given port using [IcmpEndpoint::Udp]. + /// This may be useful for applications using UDP attempting to detect and/or + /// diagnose connection problems. + /// + /// ``` + /// # use smoltcp::socket::{IcmpPacketBuffer, IcmpSocketBuffer}; + /// # let rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 20])]); + /// # let tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 20])]); + /// use smoltcp::wire::IpEndpoint; + /// use smoltcp::socket::{Socket, IcmpSocket, IcmpEndpoint}; + /// let mut icmp_socket = match IcmpSocket::new(rx_buffer, tx_buffer) { + /// Socket::Icmp(socket) => socket, + /// _ => unreachable!() + /// }; + /// // Bind to ICMP error responses for UDP packets sent from port 53. + /// let endpoint = IpEndpoint::from(53); + /// icmp_socket.bind(IcmpEndpoint::Udp(endpoint)).unwrap(); + /// ``` + /// + /// ## Bind to a specific ICMP identifier: + /// + /// To [send] and [recv] ICMP packets that are not associated with a specific UDP + /// port, the socket may be bound to a specific ICMP identifier using + /// [IcmpEndpoint::Ident]. This is useful for sending and receiving Echo Request/Reply + /// messages. + /// + /// ``` + /// # use smoltcp::socket::{IcmpPacketBuffer, IcmpSocketBuffer}; + /// # let rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 20])]); + /// # let tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 20])]); + /// use smoltcp::socket::{Socket, IcmpSocket, IcmpEndpoint}; + /// let mut icmp_socket = match IcmpSocket::new(rx_buffer, tx_buffer) { + /// Socket::Icmp(socket) => socket, + /// _ => unreachable!() + /// }; + /// // Bind to ICMP messages with the identifier 0x1234 + /// icmp_socket.bind(IcmpEndpoint::Ident(0x1234)).unwrap(); + /// ``` + /// + /// [is_specified]: enum.IcmpEndpoint.html#method.is_specified + /// [IcmpEndpoint::Ident]: enum.IcmpEndpoint#variant.Ident + /// [IcmpEndpoint::Udp]: enum.IcmpEndpoint#variant.Udp + /// [send]: #method.send + /// [recv]: #method.recv + pub fn bind>(&mut self, endpoint: T) -> Result<()> { + let endpoint = endpoint.into(); + if !endpoint.is_specified() { + return Err(Error::Unaddressable); + } + + if self.is_open() { return Err(Error::Illegal) } + + self.endpoint = endpoint; + Ok(()) + } + + /// Check whether the transmit buffer is full. + #[inline] + pub fn can_send(&self) -> bool { + !self.tx_buffer.is_full() + } + + /// Check whether the receive buffer is not empty. + #[inline] + pub fn can_recv(&self) -> bool { + !self.rx_buffer.is_empty() + } + + /// Check whether the socket is open. + #[inline] + pub fn is_open(&self) -> bool { + self.endpoint != Endpoint::Unspecified + } + + /// Enqueue a packet to be sent to a given remote address, and return a pointer + /// to its payload. + /// + /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full, + /// `Err(Error::Truncated)` if the requested size is larger than the packet buffer + /// size, and `Err(Error::Unaddressable)` if the or remote address, is unspecified. + pub fn send(&mut self, size: usize, endpoint: IpAddress) -> Result<&mut [u8]> { + if endpoint.is_unspecified() { + return Err(Error::Unaddressable) + } + + let packet_buf = self.tx_buffer.enqueue_one_with(|buf| buf.resize(size))?; + packet_buf.endpoint = endpoint; + net_trace!("{}:{}: buffer to send {} octets", + self.handle, packet_buf.endpoint, size); + Ok(&mut packet_buf.as_mut()[..size]) + } + + /// Enqueue a packet to be sent to a given remote address, and fill it from a slice. + /// + /// See also [send](#method.send). + pub fn send_slice(&mut self, data: &[u8], endpoint: IpAddress) -> Result<()> { + let packet_buf = self.send(data.len(), endpoint)?; + packet_buf.copy_from_slice(data); + Ok(()) + } + + /// Dequeue a packet received from a remote endpoint, and return the `IpAddress` as well + /// as a pointer to the payload. + /// + /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty. + pub fn recv(&mut self) -> Result<(&[u8], IpAddress)> { + let packet_buf = self.rx_buffer.dequeue_one()?; + net_trace!("{}:{}: receive {} buffered octets", + self.handle, packet_buf.endpoint, packet_buf.size); + Ok((&packet_buf.as_ref(), packet_buf.endpoint)) + } + + /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice, + /// and return the amount of octets copied as well as the `IpAddress` + /// + /// See also [recv](#method.recv). + pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<(usize, IpAddress)> { + let (buffer, endpoint) = self.recv()?; + let length = cmp::min(data.len(), buffer.len()); + data[..length].copy_from_slice(&buffer[..length]); + Ok((length, endpoint)) + } + + /// Filter determining which packets received by the interface are appended to + /// the given sockets received buffer. + pub(crate) fn accepts(&self, ip_repr: &IpRepr, icmp_repr: &Icmpv4Repr, + cksum: &ChecksumCapabilities) -> bool { + match self.endpoint { + // If we are bound to ICMP errors associated to a UDP port, only + // accept Destination Unreachable messages with the data containing + // a UDP packet send from the local port we are bound to. + Endpoint::Udp(endpoint) => + if !endpoint.addr.is_unspecified() && endpoint.addr != ip_repr.dst_addr() { + false + } else { + match icmp_repr { + &Icmpv4Repr::DstUnreachable { data, .. } => { + let packet = UdpPacket::new(data); + match UdpRepr::parse(&packet, &ip_repr.src_addr(), + &ip_repr.dst_addr(), cksum) { + Ok(repr) => endpoint.port == repr.src_port, + Err(_) => false, + } + } + _ => false, + } + } + // If we are bound to a specific ICMP identifier value, only accept an + // Echo Request/Reply with the identifier field matching the endpoint + // port. + Endpoint::Ident(id) => match icmp_repr { + &Icmpv4Repr::EchoRequest { ident, .. } | &Icmpv4Repr::EchoReply { ident, .. } => + ident == id, + _ => false, + } + _ => false, + } + } + + pub(crate) fn process(&mut self, ip_repr: &IpRepr, ip_payload: &[u8]) -> Result<()> { + let packet_buf = self.rx_buffer.enqueue_one_with(|buf| buf.resize(ip_payload.len()))?; + packet_buf.as_mut().copy_from_slice(ip_payload); + packet_buf.endpoint = ip_repr.src_addr(); + net_trace!("{}:{}: receiving {} octets", + self.handle, packet_buf.endpoint, packet_buf.size); + Ok(()) + } + + pub(crate) fn dispatch(&mut self, caps: &DeviceCapabilities, + emit: F) -> Result<()> + where F: FnOnce((IpRepr, Icmpv4Repr)) -> Result<()> { + let handle = self.handle; + let ttl = self.ttl.unwrap_or(64); + let checksum = &caps.checksum; + self.tx_buffer.dequeue_one_with(|packet_buf| { + net_trace!("{}:{}: sending {} octets", + handle, packet_buf.endpoint, packet_buf.size); + match packet_buf.endpoint { + IpAddress::Ipv4(ipv4_addr) => { + let packet = Icmpv4Packet::new(packet_buf.as_ref()); + let repr = Icmpv4Repr::parse(&packet, checksum)?; + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::default(), + dst_addr: ipv4_addr, + protocol: IpProtocol::Icmp, + payload_len: repr.buffer_len(), + ttl: ttl, + }); + emit((ip_repr, repr)) + }, + _ => Err(Error::Unaddressable) + } + }) + } + + pub(crate) fn poll_at(&self) -> Option { + if self.tx_buffer.is_empty() { + None + } else { + Some(0) + } + } +} + +#[cfg(test)] +mod test { + use phy::DeviceCapabilities; + use wire::{IpAddress, Icmpv4DstUnreachable}; + use super::*; + + fn buffer(packets: usize) -> SocketBuffer<'static, 'static> { + let mut storage = vec![]; + for _ in 0..packets { + storage.push(PacketBuffer::new(vec![0; 24])) + } + SocketBuffer::new(storage) + } + + fn socket(rx_buffer: SocketBuffer<'static, 'static>, + tx_buffer: SocketBuffer<'static, 'static>) -> IcmpSocket<'static, 'static> { + match IcmpSocket::new(rx_buffer, tx_buffer) { + Socket::Icmp(socket) => socket, + _ => unreachable!() + } + } + + const REMOTE_IPV4: Ipv4Address = Ipv4Address([0x7f, 0x00, 0x00, 0x02]); + const LOCAL_IPV4: Ipv4Address = Ipv4Address([0x7f, 0x00, 0x00, 0x01]); + const REMOTE_IP: IpAddress = IpAddress::Ipv4(REMOTE_IPV4); + const LOCAL_IP: IpAddress = IpAddress::Ipv4(LOCAL_IPV4); + const LOCAL_PORT: u16 = 53; + const LOCAL_END: IpEndpoint = IpEndpoint { addr: LOCAL_IP, port: LOCAL_PORT }; + + static ECHO_REPR: Icmpv4Repr = Icmpv4Repr::EchoRequest { + ident: 0x1234, + seq_no: 0x5678, + data: &[0xff; 16] + }; + + static UDP_REPR: UdpRepr = UdpRepr { + src_port: 53, + dst_port: 9090, + payload: &[0xff; 10] + }; + + static LOCAL_IP_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::UNSPECIFIED, + dst_addr: REMOTE_IPV4, + protocol: IpProtocol::Icmp, + payload_len: 24, + ttl: 0x40 + }); + + static REMOTE_IP_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { + src_addr: REMOTE_IPV4, + dst_addr: LOCAL_IPV4, + protocol: IpProtocol::Icmp, + payload_len: 24, + ttl: 0x40 + }); + + #[test] + fn test_send_unaddressable() { + let mut socket = socket(buffer(0), buffer(1)); + assert_eq!(socket.send_slice(b"abcdef", IpAddress::default()), + Err(Error::Unaddressable)); + assert_eq!(socket.send_slice(b"abcdef", REMOTE_IP), Ok(())); + } + + #[test] + fn test_send_dispatch() { + let mut socket = socket(buffer(0), buffer(1)); + let caps = DeviceCapabilities::default(); + + assert_eq!(socket.dispatch(&caps, |_| unreachable!()), + Err(Error::Exhausted)); + + // This buffer is too long + assert_eq!(socket.send_slice(&[0xff; 25], REMOTE_IP), Err(Error::Truncated)); + assert!(socket.can_send()); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new(&mut bytes); + ECHO_REPR.emit(&mut packet, &caps.checksum); + + assert_eq!(socket.send_slice(&packet.into_inner()[..], REMOTE_IP), Ok(())); + assert_eq!(socket.send_slice(b"123456", REMOTE_IP), Err(Error::Exhausted)); + assert!(!socket.can_send()); + + assert_eq!(socket.dispatch(&caps, |(ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IP_REPR); + assert_eq!(icmp_repr, ECHO_REPR); + Err(Error::Unaddressable) + }), Err(Error::Unaddressable)); + // buffer is not taken off of the tx queue due to the error + assert!(!socket.can_send()); + + assert_eq!(socket.dispatch(&caps, |(ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IP_REPR); + assert_eq!(icmp_repr, ECHO_REPR); + Ok(()) + }), Ok(())); + // buffer is taken off of the queue this time + assert!(socket.can_send()); + } + + #[test] + fn test_set_ttl() { + let mut s = socket(buffer(0), buffer(1)); + let caps = DeviceCapabilities::default(); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new(&mut bytes); + ECHO_REPR.emit(&mut packet, &caps.checksum); + + s.set_ttl(Some(0x2a)); + + assert_eq!(s.send_slice(&packet.into_inner()[..], REMOTE_IP), Ok(())); + assert_eq!(s.dispatch(&caps, |(ip_repr, _)| { + assert_eq!(ip_repr, IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::UNSPECIFIED, + dst_addr: REMOTE_IPV4, + protocol: IpProtocol::Icmp, + payload_len: ECHO_REPR.buffer_len(), + ttl: 0x2a, + })); + Ok(()) + }), Ok(())); + } + + #[test] + fn test_recv_process() { + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + assert!(!socket.can_recv()); + assert_eq!(socket.recv(), Err(Error::Exhausted)); + + let caps = DeviceCapabilities::default(); + + let mut bytes = [0xff; 20]; + let mut packet = Icmpv4Packet::new(&mut bytes); + ECHO_REPR.emit(&mut packet, &caps.checksum); + let data = &packet.into_inner()[..]; + + assert!(socket.accepts(&REMOTE_IP_REPR, &ECHO_REPR, &caps.checksum)); + assert_eq!(socket.process(&REMOTE_IP_REPR, &data[..]), + Ok(())); + assert!(socket.can_recv()); + + assert!(socket.accepts(&REMOTE_IP_REPR, &ECHO_REPR, &caps.checksum)); + assert_eq!(socket.process(&REMOTE_IP_REPR, &data[..]), + Err(Error::Exhausted)); + assert_eq!(socket.recv(), Ok((&data[..], REMOTE_IP))); + assert!(!socket.can_recv()); + } + + #[test] + fn test_accept_bad_id() { + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + let caps = DeviceCapabilities::default(); + let mut bytes = [0xff; 20]; + let mut packet = Icmpv4Packet::new(&mut bytes); + let icmp_repr = Icmpv4Repr::EchoRequest { + ident: 0x4321, + seq_no: 0x5678, + data: &[0xff; 16] + }; + icmp_repr.emit(&mut packet, &caps.checksum); + + // Ensure that a packet with an identifier that isn't the bound + // ID is not accepted + assert!(!socket.accepts(&REMOTE_IP_REPR, &icmp_repr, &caps.checksum)); + } + + #[test] + fn test_accepts_udp() { + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END)), Ok(())); + + let caps = DeviceCapabilities::default(); + + let mut bytes = [0xff; 18]; + let mut packet = UdpPacket::new(&mut bytes); + UDP_REPR.emit(&mut packet, &REMOTE_IP, &LOCAL_IP, &caps.checksum); + + let data = &packet.into_inner()[..]; + + let icmp_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::PortUnreachable, + header: Ipv4Repr { + src_addr: LOCAL_IPV4, + dst_addr: REMOTE_IPV4, + protocol: IpProtocol::Icmp, + payload_len: 12, + ttl: 0x40 + }, + data: data + }; + let ip_repr = IpRepr::Unspecified { + src_addr: REMOTE_IP, + dst_addr: LOCAL_IP, + protocol: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + ttl: 0x40 + }; + + assert!(!socket.can_recv()); + + // Ensure we can accept ICMP error response to the bound + // UDP port + assert!(socket.accepts(&ip_repr, &icmp_repr, &caps.checksum)); + assert_eq!(socket.process(&ip_repr, &data[..]), + Ok(())); + assert!(socket.can_recv()); + } +} diff --git a/src/socket/mod.rs b/src/socket/mod.rs index 876a148..0c32d10 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -11,10 +11,11 @@ //! size for a buffer, allocate it, and let the networking stack use it. use core::marker::PhantomData; -use wire::IpRepr; #[cfg(feature = "socket-raw")] mod raw; +#[cfg(feature = "socket-icmp")] +mod icmp; #[cfg(feature = "socket-udp")] mod udp; #[cfg(feature = "socket-tcp")] @@ -27,6 +28,12 @@ pub use self::raw::{PacketBuffer as RawPacketBuffer, SocketBuffer as RawSocketBuffer, RawSocket}; +#[cfg(feature = "socket-icmp")] +pub use self::icmp::{PacketBuffer as IcmpPacketBuffer, + SocketBuffer as IcmpSocketBuffer, + Endpoint as IcmpEndpoint, + IcmpSocket}; + #[cfg(feature = "socket-udp")] pub use self::udp::{PacketBuffer as UdpPacketBuffer, SocketBuffer as UdpSocketBuffer, @@ -57,6 +64,8 @@ pub(crate) use self::ref_::Session as SocketSession; pub enum Socket<'a, 'b: 'a> { #[cfg(feature = "socket-raw")] Raw(RawSocket<'a, 'b>), + #[cfg(feature = "socket-icmp")] + Icmp(IcmpSocket<'a, 'b>), #[cfg(feature = "socket-udp")] Udp(UdpSocket<'a, 'b>), #[cfg(feature = "socket-tcp")] @@ -70,6 +79,8 @@ macro_rules! dispatch_socket { match $self_ { #[cfg(feature = "socket-raw")] &$( $mut_ )* Socket::Raw(ref $( $mut_ )* $socket) => $code, + #[cfg(feature = "socket-icmp")] + &$( $mut_ )* Socket::Icmp(ref $( $mut_ )* $socket) => $code, #[cfg(feature = "socket-udp")] &$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code, #[cfg(feature = "socket-tcp")] @@ -122,6 +133,8 @@ macro_rules! from_socket { #[cfg(feature = "socket-raw")] from_socket!(RawSocket<'a, 'b>, Raw); +#[cfg(feature = "socket-icmp")] +from_socket!(IcmpSocket<'a, 'b>, Icmp); #[cfg(feature = "socket-udp")] from_socket!(UdpSocket<'a, 'b>, Udp); #[cfg(feature = "socket-tcp")] diff --git a/src/socket/raw.rs b/src/socket/raw.rs index 68dacbc..2d20144 100644 --- a/src/socket/raw.rs +++ b/src/socket/raw.rs @@ -3,8 +3,8 @@ use managed::Managed; use {Error, Result}; use phy::ChecksumCapabilities; -use wire::{IpVersion, IpProtocol, Ipv4Repr, Ipv4Packet}; -use socket::{IpRepr, Socket, SocketHandle}; +use wire::{IpVersion, IpRepr, IpProtocol, Ipv4Repr, Ipv4Packet}; +use socket::{Socket, SocketHandle}; use storage::{Resettable, RingBuffer}; /// A buffered raw IP packet. diff --git a/src/socket/ref_.rs b/src/socket/ref_.rs index c3fdc67..c91860b 100644 --- a/src/socket/ref_.rs +++ b/src/socket/ref_.rs @@ -2,6 +2,8 @@ use core::ops::{Deref, DerefMut}; #[cfg(feature = "socket-raw")] use socket::RawSocket; +#[cfg(feature = "socket-icmp")] +use socket::IcmpSocket; #[cfg(feature = "socket-udp")] use socket::UdpSocket; #[cfg(feature = "socket-tcp")] @@ -19,6 +21,8 @@ pub trait Session { #[cfg(feature = "socket-raw")] impl<'a, 'b> Session for RawSocket<'a, 'b> {} +#[cfg(feature = "socket-icmp")] +impl<'a, 'b> Session for IcmpSocket<'a, 'b> {} #[cfg(feature = "socket-udp")] impl<'a, 'b> Session for UdpSocket<'a, 'b> {} #[cfg(feature = "socket-tcp")] diff --git a/src/socket/set.rs b/src/socket/set.rs index 700546f..1813570 100644 --- a/src/socket/set.rs +++ b/src/socket/set.rs @@ -144,6 +144,9 @@ impl<'a, 'b: 'a, 'c: 'a + 'b> Set<'a, 'b, 'c> { #[cfg(feature = "socket-raw")] &mut Socket::Raw(_) => may_remove = true, + #[cfg(feature = "socket-icmp")] + &mut Socket::Icmp(_) => + may_remove = true, #[cfg(feature = "socket-udp")] &mut Socket::Udp(_) => may_remove = true, diff --git a/src/socket/tcp.rs b/src/socket/tcp.rs index 9cda6e3..6f1d63b 100644 --- a/src/socket/tcp.rs +++ b/src/socket/tcp.rs @@ -6,8 +6,8 @@ use core::{cmp, fmt}; use {Error, Result}; use phy::DeviceCapabilities; -use wire::{IpProtocol, IpAddress, IpEndpoint, TcpSeqNumber, TcpRepr, TcpControl}; -use socket::{Socket, SocketHandle, IpRepr}; +use wire::{IpProtocol, IpRepr, IpAddress, IpEndpoint, TcpSeqNumber, TcpRepr, TcpControl}; +use socket::{Socket, SocketHandle}; use storage::{Assembler, RingBuffer}; pub type SocketBuffer<'a> = RingBuffer<'a, u8>; diff --git a/src/socket/udp.rs b/src/socket/udp.rs index 7fe7185..d01af0d 100644 --- a/src/socket/udp.rs +++ b/src/socket/udp.rs @@ -2,8 +2,8 @@ use core::cmp::min; use managed::Managed; use {Error, Result}; -use wire::{IpProtocol, IpEndpoint, UdpRepr}; -use socket::{Socket, SocketHandle, IpRepr}; +use wire::{IpProtocol, IpRepr, IpEndpoint, UdpRepr}; +use socket::{Socket, SocketHandle}; use storage::{Resettable, RingBuffer}; /// A buffered UDP packet.