use core::net::{IpAddr, Ipv4Addr, SocketAddr};
use smoltcp::{
iface::{Config, Interface, SocketSet},
phy::{Device, DeviceCapabilities, Loopback, Medium, RxToken, TxToken},
socket::udp,
time::Instant as RawInstant,
wire::{HardwareAddress, IpAddress, IpCidr, IpEndpoint, Ipv4Packet},
};
use super::DualStack;
use crate::udpio::{SendError, UdpIo};
#[derive(Default)]
struct CapturingDevice {
sent: alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
struct CapTxToken<'a> {
sent: &'a mut alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
impl TxToken for CapTxToken<'_> {
fn consume<R, F: FnOnce(&mut [u8]) -> R>(self, len: usize, f: F) -> R {
let mut buf = alloc::vec![0u8; len];
let r = f(&mut buf);
self.sent.push(buf);
r
}
}
struct CapRxToken;
impl RxToken for CapRxToken {
fn consume<R, F: FnOnce(&[u8]) -> R>(self, _f: F) -> R {
unreachable!("the capturing device never receives")
}
}
impl Device for CapturingDevice {
type RxToken<'a> = CapRxToken;
type TxToken<'a> = CapTxToken<'a>;
fn receive(&mut self, _t: RawInstant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
None
}
fn transmit(&mut self, _t: RawInstant) -> Option<Self::TxToken<'_>> {
Some(CapTxToken {
sent: &mut self.sent,
})
}
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.medium = Medium::Ip;
caps.max_transmission_unit = 1500;
caps
}
}
fn udp_socket<'a>(
rx_meta: &'a mut [udp::PacketMetadata],
rx_buf: &'a mut [u8],
tx_meta: &'a mut [udp::PacketMetadata],
tx_buf: &'a mut [u8],
) -> udp::Socket<'a> {
udp::Socket::new(
udp::PacketBuffer::new(rx_meta, rx_buf),
udp::PacketBuffer::new(tx_meta, tx_buf),
)
}
#[test]
fn dual_stack_routes_by_family_and_reports_absent() {
let mut rx_meta = [udp::PacketMetadata::EMPTY; 1];
let mut rx_buf = [0u8; 256];
let mut tx_meta = [udp::PacketMetadata::EMPTY; 1];
let mut tx_buf = [0u8; 256];
let socket = udp_socket(&mut rx_meta, &mut rx_buf, &mut tx_meta, &mut tx_buf);
let mut storage: [_; 1] = Default::default();
let mut sockets = SocketSet::new(&mut storage[..]);
let h4 = sockets.add(socket);
sockets.get_mut::<udp::Socket<'_>>(h4).bind(5353).unwrap();
let mut io = DualStack::new(&mut sockets, Some(h4), None);
let v6 = SocketAddr::new(
IpAddr::V6(core::net::Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0xfb)),
5353,
);
assert_eq!(io.try_send(&[0u8; 8], v6), Err(SendError::Unsupported));
let v4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 251)), 5353);
assert_ne!(io.try_send(&[0u8; 8], v4), Err(SendError::Unsupported));
}
#[test]
fn oversized_datagram_maps_to_too_large_not_busy() {
let mut rx_meta = [udp::PacketMetadata::EMPTY; 1];
let mut rx_buf = [0u8; 64];
let mut tx_meta = [udp::PacketMetadata::EMPTY; 1];
let mut tx_buf = [0u8; 64]; let socket = udp_socket(&mut rx_meta, &mut rx_buf, &mut tx_meta, &mut tx_buf);
let mut storage: [_; 1] = Default::default();
let mut sockets = SocketSet::new(&mut storage[..]);
let h4 = sockets.add(socket);
sockets.get_mut::<udp::Socket<'_>>(h4).bind(5353).unwrap();
assert!(
sockets
.get_mut::<udp::Socket<'_>>(h4)
.payload_send_capacity()
< 128
);
let mut io = DualStack::new(&mut sockets, Some(h4), None);
let v4 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 251)), 5353);
assert_eq!(
io.try_send(&[0u8; 128], v4),
Err(SendError::TooLarge),
"a datagram exceeding the TX payload capacity must map to TooLarge, not Busy"
);
assert_ne!(io.try_send(&[0u8; 8], v4), Err(SendError::TooLarge));
}
#[test]
fn loopback_udpio_roundtrip() {
const PORT: u16 = 5353;
let own_v4 = Ipv4Addr::new(127, 0, 0, 1);
let dst = SocketAddr::new(IpAddr::V4(own_v4), PORT);
let mut device = Loopback::new(Medium::Ip);
let config = Config::new(HardwareAddress::Ip);
let mut iface = Interface::new(config, &mut device, RawInstant::ZERO);
iface.update_ip_addrs(|addrs| {
addrs
.push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
.unwrap();
});
let mut rx_meta = [udp::PacketMetadata::EMPTY; 4];
let mut rx_buf = [0u8; 1500];
let mut tx_meta = [udp::PacketMetadata::EMPTY; 4];
let mut tx_buf = [0u8; 1500];
let socket = udp::Socket::new(
udp::PacketBuffer::new(&mut rx_meta[..], &mut rx_buf[..]),
udp::PacketBuffer::new(&mut tx_meta[..], &mut tx_buf[..]),
);
let mut sock_storage: [_; 2] = Default::default();
let mut sockets = SocketSet::new(&mut sock_storage[..]);
let handle = sockets.add(socket);
sockets
.get_mut::<udp::Socket<'_>>(handle)
.bind(PORT)
.unwrap();
let payload = b"hick-mdns-loopback";
DualStack::new(&mut sockets, Some(handle), None)
.try_send(payload, dst)
.expect("try_send should queue the datagram");
for _ in 0..8 {
iface.poll(RawInstant::ZERO, &mut device, &mut sockets);
}
let mut buf = [0u8; 1500];
let meta = DualStack::new(&mut sockets, Some(handle), None)
.try_recv(&mut buf)
.expect("the looped-back datagram should be received");
assert_eq!(meta.len, payload.len());
assert_eq!(&buf[..meta.len], payload);
assert_eq!(meta.src, dst, "source is our own bound address:port");
assert_eq!(
meta.local,
Some(IpAddr::V4(own_v4)),
"local/destination address the datagram arrived on"
);
assert_eq!(
meta.hop_limit, None,
"smoltcp udp::Socket doesn't surface RX TTL"
);
}
#[test]
fn egress_packets_carry_hop_limit_255() {
let mut device = CapturingDevice::default();
let config = Config::new(HardwareAddress::Ip);
let mut iface = Interface::new(config, &mut device, RawInstant::ZERO);
iface.update_ip_addrs(|addrs| {
addrs
.push(IpCidr::new(IpAddress::v4(192, 168, 1, 10), 24))
.unwrap();
});
let mut rx_meta = [udp::PacketMetadata::EMPTY; 1];
let mut rx_buf = [0u8; 256];
let mut tx_meta = [udp::PacketMetadata::EMPTY; 1];
let mut tx_buf = [0u8; 256];
let socket = udp_socket(&mut rx_meta, &mut rx_buf, &mut tx_meta, &mut tx_buf);
let mut storage: [_; 1] = Default::default();
let mut sockets = SocketSet::new(&mut storage[..]);
let h4 = sockets.add(socket);
sockets.get_mut::<udp::Socket<'_>>(h4).bind(5353).unwrap();
let dst = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 251)), 5353);
DualStack::new(&mut sockets, Some(h4), None)
.try_send(b"hick-mdns", dst)
.expect("try_send should queue the datagram");
for _ in 0..4 {
iface.poll(RawInstant::ZERO, &mut device, &mut sockets);
}
assert!(
!device.sent.is_empty(),
"the datagram must have egressed to the device"
);
let frame = &device.sent[0];
let ip = Ipv4Packet::new_checked(&frame[..]).expect("a valid IPv4 packet egressed");
assert_eq!(
ip.hop_limit(),
255,
"RFC 6762 §11: every outgoing mDNS packet must leave with IP TTL 255, not the \
smoltcp default 64"
);
}
#[test]
fn oversized_received_datagram_yields_a_drop_marker_not_a_loop() {
const PORT: u16 = 5353;
let own = Ipv4Addr::new(127, 0, 0, 1);
let dst = SocketAddr::new(IpAddr::V4(own), PORT);
let mut device = Loopback::new(Medium::Ip);
let config = Config::new(HardwareAddress::Ip);
let mut iface = Interface::new(config, &mut device, RawInstant::ZERO);
iface.update_ip_addrs(|addrs| {
addrs
.push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
.unwrap();
});
let mut rx_meta = [udp::PacketMetadata::EMPTY; 4];
let mut rx_buf = [0u8; 1500];
let mut tx_meta = [udp::PacketMetadata::EMPTY; 4];
let mut tx_buf = [0u8; 1500];
let socket = udp::Socket::new(
udp::PacketBuffer::new(&mut rx_meta[..], &mut rx_buf[..]),
udp::PacketBuffer::new(&mut tx_meta[..], &mut tx_buf[..]),
);
let mut sock_storage: [_; 2] = Default::default();
let mut sockets = SocketSet::new(&mut sock_storage[..]);
let handle = sockets.add(socket);
sockets
.get_mut::<udp::Socket<'_>>(handle)
.bind(PORT)
.unwrap();
DualStack::new(&mut sockets, Some(handle), None)
.try_send(&[0xABu8; 100], dst)
.expect("try_send should queue the datagram");
for _ in 0..8 {
iface.poll(RawInstant::ZERO, &mut device, &mut sockets);
}
let mut small = [0u8; 50];
let meta = DualStack::new(&mut sockets, Some(handle), None)
.try_recv(&mut small)
.expect("a drop marker (Some), not None");
assert_eq!(
meta.len, 0,
"an oversized datagram must surface as a zero-length drop marker"
);
assert!(
DualStack::new(&mut sockets, Some(handle), None)
.try_recv(&mut small)
.is_none(),
"the dropped datagram must have been consumed"
);
}
#[test]
fn try_recv_round_robins_handles_so_one_backlog_cannot_starve_the_other() {
let addr_a = Ipv4Addr::new(127, 0, 0, 1);
let addr_b = Ipv4Addr::new(127, 0, 0, 2);
let mut device = Loopback::new(Medium::Ip);
let config = Config::new(HardwareAddress::Ip);
let mut iface = Interface::new(config, &mut device, RawInstant::ZERO);
iface.update_ip_addrs(|addrs| {
addrs
.push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
.unwrap();
addrs
.push(IpCidr::new(IpAddress::v4(127, 0, 0, 2), 8))
.unwrap();
});
let mut ra = [udp::PacketMetadata::EMPTY; 8];
let mut rab = [0u8; 2048];
let mut ta = [udp::PacketMetadata::EMPTY; 8];
let mut tab = [0u8; 2048];
let mut rb = [udp::PacketMetadata::EMPTY; 8];
let mut rbb = [0u8; 2048];
let mut tb = [udp::PacketMetadata::EMPTY; 8];
let mut tbb = [0u8; 2048];
let sa = udp::Socket::new(
udp::PacketBuffer::new(&mut ra[..], &mut rab[..]),
udp::PacketBuffer::new(&mut ta[..], &mut tab[..]),
);
let sb = udp::Socket::new(
udp::PacketBuffer::new(&mut rb[..], &mut rbb[..]),
udp::PacketBuffer::new(&mut tb[..], &mut tbb[..]),
);
let mut storage: [_; 4] = Default::default();
let mut sockets = SocketSet::new(&mut storage[..]);
let ha = sockets.add(sa);
let hb = sockets.add(sb);
sockets
.get_mut::<udp::Socket<'_>>(ha)
.bind(IpEndpoint::new(IpAddress::v4(127, 0, 0, 1), 5353))
.unwrap();
sockets
.get_mut::<udp::Socket<'_>>(hb)
.bind(IpEndpoint::new(IpAddress::v4(127, 0, 0, 2), 5353))
.unwrap();
for _ in 0..4 {
sockets
.get_mut::<udp::Socket<'_>>(ha)
.send_slice(b"a", IpEndpoint::new(IpAddress::v4(127, 0, 0, 1), 5353))
.unwrap();
}
for _ in 0..2 {
sockets
.get_mut::<udp::Socket<'_>>(hb)
.send_slice(b"b", IpEndpoint::new(IpAddress::v4(127, 0, 0, 2), 5353))
.unwrap();
}
for _ in 0..16 {
iface.poll(RawInstant::ZERO, &mut device, &mut sockets);
}
let mut io = DualStack::new(&mut sockets, Some(ha), Some(hb));
let mut buf = [0u8; 64];
let mut from_a = alloc::vec::Vec::new();
while let Some(meta) = io.try_recv(&mut buf) {
if meta.len > 0 {
from_a.push(meta.src.ip() == IpAddr::V4(addr_a));
}
}
let _ = addr_b;
assert_eq!(
from_a.iter().filter(|&&a| a).count(),
4,
"all 4 datagrams on handle A must arrive; order = {from_a:?}"
);
assert_eq!(
from_a.iter().filter(|&&a| !a).count(),
2,
"both datagrams on handle B must arrive; order = {from_a:?}"
);
let first_b = from_a
.iter()
.position(|&a| !a)
.expect("a handle-B datagram");
assert!(
first_b < 4,
"handle B must interleave, not be starved behind A's backlog; order = {from_a:?}"
);
}