Add ICMP sockets
- Add support for ICMP sockets - Add tests for ICMP sockets - Rename proto-<type> features to socket-<type> - Update documentation
This commit is contained in:
parent
c3e07dad9a
commit
adb5014780
|
@ -20,11 +20,13 @@ matrix:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: FEATURES='socket-tcp' MODE='build'
|
env: FEATURES='socket-tcp' MODE='build'
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
env: FEATURES='socket-raw socket-udp socket-tcp' MODE='build'
|
env: FEATURES='socket-icmp' MODE='build'
|
||||||
- rust: nightly
|
- 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
|
- 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:
|
script:
|
||||||
- cargo "$MODE" --no-default-features --features "$FEATURES"
|
- cargo "$MODE" --no-default-features --features "$FEATURES"
|
||||||
notifications:
|
notifications:
|
||||||
|
|
|
@ -31,9 +31,11 @@ verbose = []
|
||||||
"socket-raw" = []
|
"socket-raw" = []
|
||||||
"socket-udp" = []
|
"socket-udp" = []
|
||||||
"socket-tcp" = []
|
"socket-tcp" = []
|
||||||
|
"socket-icmp" = []
|
||||||
default = ["std", "log",
|
default = ["std", "log",
|
||||||
"phy-raw_socket", "phy-tap_interface",
|
"phy-raw_socket", "phy-tap_interface",
|
||||||
"socket-raw", "socket-udp", "socket-tcp"]
|
"socket-raw", "socket-udp", "socket-tcp",
|
||||||
|
"socket-icmp"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "tcpdump"
|
name = "tcpdump"
|
||||||
|
|
|
@ -12,11 +12,10 @@ use std::time::Instant;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use smoltcp::phy::Device;
|
use smoltcp::phy::Device;
|
||||||
use smoltcp::phy::wait as phy_wait;
|
use smoltcp::phy::wait as phy_wait;
|
||||||
use smoltcp::wire::{EthernetAddress, IpVersion, IpProtocol, IpAddress, IpCidr,
|
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr,
|
||||||
Ipv4Address, Ipv4Packet, Ipv4Repr,
|
Ipv4Address, Icmpv4Repr, Icmpv4Packet};
|
||||||
Icmpv4Repr, Icmpv4Packet};
|
|
||||||
use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface};
|
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 std::collections::HashMap;
|
||||||
use byteorder::{ByteOrder, NetworkEndian};
|
use byteorder::{ByteOrder, NetworkEndian};
|
||||||
|
|
||||||
|
@ -51,10 +50,9 @@ fn main() {
|
||||||
let remote_addr = address;
|
let remote_addr = address;
|
||||||
let local_addr = Ipv4Address::new(192, 168, 69, 1);
|
let local_addr = Ipv4Address::new(192, 168, 69, 1);
|
||||||
|
|
||||||
let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]);
|
let icmp_rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 256])]);
|
||||||
let raw_tx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]);
|
let icmp_tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketBuffer::new(vec![0; 256])]);
|
||||||
let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Icmp,
|
let icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer);
|
||||||
raw_rx_buffer, raw_tx_buffer);
|
|
||||||
|
|
||||||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
|
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
|
||||||
let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24);
|
let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24);
|
||||||
|
@ -64,17 +62,22 @@ fn main() {
|
||||||
ethernet_addr, [ip_addr], Some(default_v4_gw));
|
ethernet_addr, [ip_addr], Some(default_v4_gw));
|
||||||
|
|
||||||
let mut sockets = SocketSet::new(vec![]);
|
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 send_at = 0;
|
||||||
let mut seq_no = 0;
|
let mut seq_no = 0;
|
||||||
let mut received = 0;
|
let mut received = 0;
|
||||||
let mut echo_payload = [0xffu8; 40];
|
let mut echo_payload = [0xffu8; 40];
|
||||||
let mut waiting_queue = HashMap::new();
|
let mut waiting_queue = HashMap::new();
|
||||||
|
let ident = 0x22b;
|
||||||
|
let endpoint = IpAddress::Ipv4(remote_addr);
|
||||||
|
|
||||||
loop {
|
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 = Instant::now().duration_since(startup_time);
|
||||||
let timestamp_us = (timestamp.as_secs() * 1000000) +
|
let timestamp_us = (timestamp.as_secs() * 1000000) +
|
||||||
|
@ -84,26 +87,16 @@ fn main() {
|
||||||
send_at <= utils::millis_since(startup_time) {
|
send_at <= utils::millis_since(startup_time) {
|
||||||
NetworkEndian::write_u64(&mut echo_payload, timestamp_us);
|
NetworkEndian::write_u64(&mut echo_payload, timestamp_us);
|
||||||
let icmp_repr = Icmpv4Repr::EchoRequest {
|
let icmp_repr = Icmpv4Repr::EchoRequest {
|
||||||
ident: 1,
|
ident: ident,
|
||||||
seq_no,
|
seq_no,
|
||||||
data: &echo_payload,
|
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
|
let icmp_payload = socket
|
||||||
.send(ipv4_repr.buffer_len() + icmp_repr.buffer_len())
|
.send(icmp_repr.buffer_len(), endpoint)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut ipv4_packet = Ipv4Packet::new(raw_payload);
|
let mut icmp_packet = Icmpv4Packet::new(icmp_payload);
|
||||||
ipv4_repr.emit(&mut ipv4_packet, &device_caps.checksum);
|
|
||||||
let mut icmp_packet = Icmpv4Packet::new(ipv4_packet.payload_mut());
|
|
||||||
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
|
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
|
||||||
|
|
||||||
waiting_queue.insert(seq_no, timestamp);
|
waiting_queue.insert(seq_no, timestamp);
|
||||||
|
@ -112,23 +105,18 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if socket.can_recv() {
|
if socket.can_recv() {
|
||||||
let payload = socket.recv().unwrap();
|
let (payload, _) = socket.recv().unwrap();
|
||||||
let ipv4_packet = Ipv4Packet::new(payload);
|
let icmp_packet = Icmpv4Packet::new(&payload);
|
||||||
let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &device_caps.checksum).unwrap();
|
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();
|
||||||
|
|
||||||
if ipv4_repr.src_addr == remote_addr && ipv4_repr.dst_addr == local_addr {
|
if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr {
|
||||||
let icmp_packet = Icmpv4Packet::new(ipv4_packet.payload());
|
if let Some(_) = waiting_queue.get(&seq_no) {
|
||||||
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum);
|
let packet_timestamp_us = NetworkEndian::read_u64(data);
|
||||||
|
println!("{} bytes from {}: icmp_seq={}, time={:.3}ms",
|
||||||
if let Ok(Icmpv4Repr::EchoReply { seq_no, data, .. }) = icmp_repr {
|
data.len(), remote_addr, seq_no,
|
||||||
if let Some(_) = waiting_queue.get(&seq_no) {
|
(timestamp_us - packet_timestamp_us) as f64 / 1000.0);
|
||||||
let packet_timestamp_us = NetworkEndian::read_u64(data);
|
waiting_queue.remove(&seq_no);
|
||||||
println!("{} bytes from {}: icmp_seq={}, time={:.3}ms",
|
received += 1;
|
||||||
data.len(), remote_addr, seq_no,
|
|
||||||
(timestamp_us - packet_timestamp_us) as f64 / 1000.0);
|
|
||||||
waiting_queue.remove(&seq_no);
|
|
||||||
received += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ use socket::RawSocket;
|
||||||
use socket::UdpSocket;
|
use socket::UdpSocket;
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
use socket::TcpSocket;
|
use socket::TcpSocket;
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
use socket::IcmpSocket;
|
||||||
use super::ArpCache;
|
use super::ArpCache;
|
||||||
|
|
||||||
/// An Ethernet network interface.
|
/// An Ethernet network interface.
|
||||||
|
@ -55,7 +57,7 @@ struct InterfaceInner<'b, 'c> {
|
||||||
enum Packet<'a> {
|
enum Packet<'a> {
|
||||||
None,
|
None,
|
||||||
Arp(ArpRepr),
|
Arp(ArpRepr),
|
||||||
Icmpv4(Ipv4Repr, Icmpv4Repr<'a>),
|
Icmpv4((Ipv4Repr, Icmpv4Repr<'a>)),
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
Raw((IpRepr, &'a [u8])),
|
Raw((IpRepr, &'a [u8])),
|
||||||
#[cfg(feature = "socket-udp")]
|
#[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 = inner.dispatch(tx_token, timestamp, Packet::Tcp(response));
|
||||||
device_result
|
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!()
|
Socket::__Nonexhaustive(_) => unreachable!()
|
||||||
};
|
};
|
||||||
match (device_result, socket_result) {
|
match (device_result, socket_result) {
|
||||||
|
@ -384,7 +396,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
||||||
|
|
||||||
match ipv4_repr.protocol {
|
match ipv4_repr.protocol {
|
||||||
IpProtocol::Icmp =>
|
IpProtocol::Icmp =>
|
||||||
self.process_icmpv4(ipv4_repr, ip_payload),
|
self.process_icmpv4(sockets, ip_repr, ip_payload),
|
||||||
|
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
IpProtocol::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]) ->
|
fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
|
||||||
Result<Packet<'frame>>
|
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
|
||||||
{
|
{
|
||||||
let icmp_packet = Icmpv4Packet::new_checked(ip_payload)?;
|
let icmp_packet = Icmpv4Packet::new_checked(ip_payload)?;
|
||||||
let checksum_caps = self.device_capabilities.checksum.clone();
|
let checksum_caps = self.device_capabilities.checksum.clone();
|
||||||
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &checksum_caps)?;
|
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 {
|
match icmp_repr {
|
||||||
// Respond to echo requests.
|
// Respond to echo requests.
|
||||||
Icmpv4Repr::EchoRequest { ident, seq_no, data } => {
|
Icmpv4Repr::EchoRequest { ident, seq_no, data } => {
|
||||||
|
@ -427,7 +451,10 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
||||||
seq_no: seq_no,
|
seq_no: seq_no,
|
||||||
data: data
|
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.
|
// Ignore any echo replies.
|
||||||
|
@ -450,7 +477,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
||||||
payload_len: icmp_repr.buffer_len(),
|
payload_len: icmp_repr.buffer_len(),
|
||||||
ttl: 64
|
ttl: 64
|
||||||
};
|
};
|
||||||
Packet::Icmpv4(ipv4_reply_repr, icmp_repr)
|
Packet::Icmpv4((ipv4_reply_repr, icmp_repr))
|
||||||
} else {
|
} else {
|
||||||
// Do not send any ICMP replies to a broadcast destination address.
|
// Do not send any ICMP replies to a broadcast destination address.
|
||||||
Packet::None
|
Packet::None
|
||||||
|
@ -549,7 +576,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
||||||
arp_repr.emit(&mut packet);
|
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| {
|
self.dispatch_ip(tx_token, timestamp, IpRepr::Ipv4(ipv4_repr), |_ip_repr, payload| {
|
||||||
icmpv4_repr.emit(&mut Icmpv4Packet::new(payload), &checksum_caps);
|
icmpv4_repr.emit(&mut Icmpv4Packet::new(payload), &checksum_caps);
|
||||||
})
|
})
|
||||||
|
@ -702,17 +729,19 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use super::Packet;
|
use {Result, Error};
|
||||||
|
|
||||||
|
use iface::{ArpCache, SliceArpCache, EthernetInterface};
|
||||||
use phy::{self, Loopback, ChecksumCapabilities};
|
use phy::{self, Loopback, ChecksumCapabilities};
|
||||||
|
use socket::SocketSet;
|
||||||
use wire::{ArpOperation, ArpPacket, ArpRepr};
|
use wire::{ArpOperation, ArpPacket, ArpRepr};
|
||||||
use wire::{EthernetAddress, EthernetFrame, EthernetProtocol};
|
use wire::{EthernetAddress, EthernetFrame, EthernetProtocol};
|
||||||
use wire::{IpAddress, IpCidr, IpProtocol, IpRepr};
|
use wire::{IpAddress, IpCidr, IpProtocol, IpRepr};
|
||||||
use wire::{Ipv4Address, Ipv4Repr};
|
use wire::{Ipv4Address, Ipv4Repr};
|
||||||
use wire::{Icmpv4Repr, Icmpv4DstUnreachable};
|
use wire::{Icmpv4Repr, Icmpv4DstUnreachable};
|
||||||
use wire::{UdpPacket, UdpRepr};
|
use wire::{UdpPacket, UdpRepr};
|
||||||
use iface::{ArpCache, SliceArpCache, EthernetInterface};
|
|
||||||
use socket::SocketSet;
|
use super::Packet;
|
||||||
use {Result, Error};
|
|
||||||
|
|
||||||
fn create_loopback<'a, 'b>() ->
|
fn create_loopback<'a, 'b>() ->
|
||||||
(EthernetInterface<'static, 'b, Loopback>, SocketSet<'static, 'a, 'b>) {
|
(EthernetInterface<'static, 'b, Loopback>, SocketSet<'static, 'a, 'b>) {
|
||||||
|
@ -812,7 +841,7 @@ mod test {
|
||||||
data: &NO_BYTES
|
data: &NO_BYTES
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_repr = Packet::Icmpv4(
|
let expected_repr = Packet::Icmpv4((
|
||||||
Ipv4Repr {
|
Ipv4Repr {
|
||||||
src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
|
src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
|
||||||
dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
|
dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
|
||||||
|
@ -821,7 +850,7 @@ mod test {
|
||||||
ttl: 64
|
ttl: 64
|
||||||
},
|
},
|
||||||
icmp_repr
|
icmp_repr
|
||||||
);
|
));
|
||||||
|
|
||||||
// Ensure that the unknown protocol triggers an error response.
|
// Ensure that the unknown protocol triggers an error response.
|
||||||
// And we correctly handle no payload.
|
// And we correctly handle no payload.
|
||||||
|
@ -877,7 +906,7 @@ mod test {
|
||||||
},
|
},
|
||||||
data: &data
|
data: &data
|
||||||
};
|
};
|
||||||
let expected_repr = Packet::Icmpv4(
|
let expected_repr = Packet::Icmpv4((
|
||||||
Ipv4Repr {
|
Ipv4Repr {
|
||||||
src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
|
src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
|
||||||
dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
|
dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
|
||||||
|
@ -886,7 +915,7 @@ mod test {
|
||||||
ttl: 64
|
ttl: 64
|
||||||
},
|
},
|
||||||
icmp_repr
|
icmp_repr
|
||||||
);
|
));
|
||||||
|
|
||||||
// Ensure that the unknown protocol triggers an error response.
|
// Ensure that the unknown protocol triggers an error response.
|
||||||
// And we correctly handle no payload.
|
// And we correctly handle no payload.
|
||||||
|
@ -994,4 +1023,60 @@ mod test {
|
||||||
&IpAddress::Ipv4(remote_ip_addr)),
|
&IpAddress::Ipv4(remote_ip_addr)),
|
||||||
Ok((remote_hw_addr, MockTxToken)));
|
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)))));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
//!
|
//!
|
||||||
//! # The socket layer
|
//! # The socket layer
|
||||||
//! The socket layer APIs are provided in the module [socket](socket/index.html); currently,
|
//! 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
|
//! TCP, UDP, ICMP, and Raw sockets are provided. The socket API provides the usual primitives,
|
||||||
//! necessarily differs in many from the [Berkeley socket API][berk], as the latter was not
|
//! but necessarily differs in many from the [Berkeley socket API][berk], as the latter was
|
||||||
//! designed to be used without heap allocation.
|
//! not designed to be used without heap allocation.
|
||||||
//! [berk]: https://en.wikipedia.org/wiki/Berkeley_sockets
|
//! [berk]: https://en.wikipedia.org/wiki/Berkeley_sockets
|
||||||
//!
|
//!
|
||||||
//! The socket layer provides the buffering, packet construction and validation, and (for
|
//! The socket layer provides the buffering, packet construction and validation, and (for
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,10 +11,11 @@
|
||||||
//! size for a buffer, allocate it, and let the networking stack use it.
|
//! size for a buffer, allocate it, and let the networking stack use it.
|
||||||
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use wire::IpRepr;
|
|
||||||
|
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
mod raw;
|
mod raw;
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
mod icmp;
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
mod udp;
|
mod udp;
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
|
@ -27,6 +28,12 @@ pub use self::raw::{PacketBuffer as RawPacketBuffer,
|
||||||
SocketBuffer as RawSocketBuffer,
|
SocketBuffer as RawSocketBuffer,
|
||||||
RawSocket};
|
RawSocket};
|
||||||
|
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
pub use self::icmp::{PacketBuffer as IcmpPacketBuffer,
|
||||||
|
SocketBuffer as IcmpSocketBuffer,
|
||||||
|
Endpoint as IcmpEndpoint,
|
||||||
|
IcmpSocket};
|
||||||
|
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
pub use self::udp::{PacketBuffer as UdpPacketBuffer,
|
pub use self::udp::{PacketBuffer as UdpPacketBuffer,
|
||||||
SocketBuffer as UdpSocketBuffer,
|
SocketBuffer as UdpSocketBuffer,
|
||||||
|
@ -57,6 +64,8 @@ pub(crate) use self::ref_::Session as SocketSession;
|
||||||
pub enum Socket<'a, 'b: 'a> {
|
pub enum Socket<'a, 'b: 'a> {
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
Raw(RawSocket<'a, 'b>),
|
Raw(RawSocket<'a, 'b>),
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
Icmp(IcmpSocket<'a, 'b>),
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
Udp(UdpSocket<'a, 'b>),
|
Udp(UdpSocket<'a, 'b>),
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
|
@ -70,6 +79,8 @@ macro_rules! dispatch_socket {
|
||||||
match $self_ {
|
match $self_ {
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
&$( $mut_ )* Socket::Raw(ref $( $mut_ )* $socket) => $code,
|
&$( $mut_ )* Socket::Raw(ref $( $mut_ )* $socket) => $code,
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
&$( $mut_ )* Socket::Icmp(ref $( $mut_ )* $socket) => $code,
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
&$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code,
|
&$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code,
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
|
@ -122,6 +133,8 @@ macro_rules! from_socket {
|
||||||
|
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
from_socket!(RawSocket<'a, 'b>, Raw);
|
from_socket!(RawSocket<'a, 'b>, Raw);
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
from_socket!(IcmpSocket<'a, 'b>, Icmp);
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
from_socket!(UdpSocket<'a, 'b>, Udp);
|
from_socket!(UdpSocket<'a, 'b>, Udp);
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
|
|
|
@ -3,8 +3,8 @@ use managed::Managed;
|
||||||
|
|
||||||
use {Error, Result};
|
use {Error, Result};
|
||||||
use phy::ChecksumCapabilities;
|
use phy::ChecksumCapabilities;
|
||||||
use wire::{IpVersion, IpProtocol, Ipv4Repr, Ipv4Packet};
|
use wire::{IpVersion, IpRepr, IpProtocol, Ipv4Repr, Ipv4Packet};
|
||||||
use socket::{IpRepr, Socket, SocketHandle};
|
use socket::{Socket, SocketHandle};
|
||||||
use storage::{Resettable, RingBuffer};
|
use storage::{Resettable, RingBuffer};
|
||||||
|
|
||||||
/// A buffered raw IP packet.
|
/// A buffered raw IP packet.
|
||||||
|
|
|
@ -2,6 +2,8 @@ use core::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
use socket::RawSocket;
|
use socket::RawSocket;
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
use socket::IcmpSocket;
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
use socket::UdpSocket;
|
use socket::UdpSocket;
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
|
@ -19,6 +21,8 @@ pub trait Session {
|
||||||
|
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
impl<'a, 'b> Session for RawSocket<'a, 'b> {}
|
impl<'a, 'b> Session for RawSocket<'a, 'b> {}
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
impl<'a, 'b> Session for IcmpSocket<'a, 'b> {}
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
impl<'a, 'b> Session for UdpSocket<'a, 'b> {}
|
impl<'a, 'b> Session for UdpSocket<'a, 'b> {}
|
||||||
#[cfg(feature = "socket-tcp")]
|
#[cfg(feature = "socket-tcp")]
|
||||||
|
|
|
@ -144,6 +144,9 @@ impl<'a, 'b: 'a, 'c: 'a + 'b> Set<'a, 'b, 'c> {
|
||||||
#[cfg(feature = "socket-raw")]
|
#[cfg(feature = "socket-raw")]
|
||||||
&mut Socket::Raw(_) =>
|
&mut Socket::Raw(_) =>
|
||||||
may_remove = true,
|
may_remove = true,
|
||||||
|
#[cfg(feature = "socket-icmp")]
|
||||||
|
&mut Socket::Icmp(_) =>
|
||||||
|
may_remove = true,
|
||||||
#[cfg(feature = "socket-udp")]
|
#[cfg(feature = "socket-udp")]
|
||||||
&mut Socket::Udp(_) =>
|
&mut Socket::Udp(_) =>
|
||||||
may_remove = true,
|
may_remove = true,
|
||||||
|
|
|
@ -6,8 +6,8 @@ use core::{cmp, fmt};
|
||||||
|
|
||||||
use {Error, Result};
|
use {Error, Result};
|
||||||
use phy::DeviceCapabilities;
|
use phy::DeviceCapabilities;
|
||||||
use wire::{IpProtocol, IpAddress, IpEndpoint, TcpSeqNumber, TcpRepr, TcpControl};
|
use wire::{IpProtocol, IpRepr, IpAddress, IpEndpoint, TcpSeqNumber, TcpRepr, TcpControl};
|
||||||
use socket::{Socket, SocketHandle, IpRepr};
|
use socket::{Socket, SocketHandle};
|
||||||
use storage::{Assembler, RingBuffer};
|
use storage::{Assembler, RingBuffer};
|
||||||
|
|
||||||
pub type SocketBuffer<'a> = RingBuffer<'a, u8>;
|
pub type SocketBuffer<'a> = RingBuffer<'a, u8>;
|
||||||
|
|
|
@ -2,8 +2,8 @@ use core::cmp::min;
|
||||||
use managed::Managed;
|
use managed::Managed;
|
||||||
|
|
||||||
use {Error, Result};
|
use {Error, Result};
|
||||||
use wire::{IpProtocol, IpEndpoint, UdpRepr};
|
use wire::{IpProtocol, IpRepr, IpEndpoint, UdpRepr};
|
||||||
use socket::{Socket, SocketHandle, IpRepr};
|
use socket::{Socket, SocketHandle};
|
||||||
use storage::{Resettable, RingBuffer};
|
use storage::{Resettable, RingBuffer};
|
||||||
|
|
||||||
/// A buffered UDP packet.
|
/// A buffered UDP packet.
|
||||||
|
|
Loading…
Reference in New Issue