Add ICMP sockets

- Add support for ICMP sockets
 - Add tests for ICMP sockets
 - Rename proto-<type> features to socket-<type>
 - Update documentation
v0.7.x
Dan Robertson 2017-10-30 00:19:34 +00:00 committed by whitequark
parent c3e07dad9a
commit adb5014780
12 changed files with 749 additions and 69 deletions

View File

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

View File

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

View File

@ -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::<RawSocket>(raw_handle);
let mut socket = sockets.get::<IcmpSocket>(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;
}
}
}

View File

@ -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<Packet<'frame>>
fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
{
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::<IcmpSocket>(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::<IcmpSocket>(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::<IcmpSocket>(socket_handle);
assert!(socket.can_recv());
assert_eq!(socket.recv(),
Ok((&data[..], IpAddress::Ipv4(Ipv4Address::new(0x7f, 0x00, 0x00, 0x02)))));
}
}
}

View File

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

583
src/socket/icmp.rs Normal file
View File

@ -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<T>(payload: T) -> PacketBuffer<'a>
where T: Into<Managed<'a, [u8]>> {
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<u8>
}
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<u8> {
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<u8>) {
// 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<T: Into<Endpoint>>(&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<F>(&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<u64> {
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());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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