summaryrefslogtreecommitdiff
path: root/src/iface/interface/tests/ipv4.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/iface/interface/tests/ipv4.rs')
-rw-r--r--src/iface/interface/tests/ipv4.rs968
1 files changed, 968 insertions, 0 deletions
diff --git a/src/iface/interface/tests/ipv4.rs b/src/iface/interface/tests/ipv4.rs
new file mode 100644
index 0000000..d685f37
--- /dev/null
+++ b/src/iface/interface/tests/ipv4.rs
@@ -0,0 +1,968 @@
+use super::*;
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_no_icmp_no_unicast(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ // Unknown Ipv4 Protocol
+ //
+ // Because the destination is the broadcast address
+ // this should not trigger and Destination Unreachable
+ // response. See RFC 1122 ยง 3.2.2.
+ let repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ hop_limit: 0x40,
+ });
+
+ let mut bytes = vec![0u8; 54];
+ repr.emit(&mut bytes, &ChecksumCapabilities::default());
+ let frame = Ipv4Packet::new_unchecked(&bytes[..]);
+
+ // Ensure that the unknown protocol frame does not trigger an
+ // ICMP error response when the destination address is a
+ // broadcast address
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_icmp_error_no_payload(#[case] medium: Medium) {
+ static NO_BYTES: [u8; 0] = [];
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ // Unknown Ipv4 Protocol with no payload
+ let repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Unknown(0x0c),
+ payload_len: 0,
+ hop_limit: 0x40,
+ });
+
+ let mut bytes = vec![0u8; 34];
+ repr.emit(&mut bytes, &ChecksumCapabilities::default());
+ let frame = Ipv4Packet::new_unchecked(&bytes[..]);
+
+ // The expected Destination Unreachable response due to the
+ // unknown protocol
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::ProtoUnreachable,
+ header: Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Unknown(12),
+ payload_len: 0,
+ hop_limit: 64,
+ },
+ data: &NO_BYTES,
+ };
+
+ let expected_repr = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv4(icmp_repr),
+ );
+
+ // Ensure that the unknown protocol triggers an error response.
+ // And we correctly handle no payload.
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ Some(expected_repr)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_local_subnet_broadcasts(#[case] medium: Medium) {
+ let (mut iface, _, _device) = setup(medium);
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 23]), 24));
+ });
+ });
+
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(iface.inner.is_broadcast_v4(Ipv4Address([192, 168, 1, 255])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 168, 1, 254])));
+
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 23, 24]), 16));
+ });
+ });
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 23, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 23, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 255, 254])));
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 168, 255, 255])));
+
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address([192, 168, 23, 24]), 8));
+ });
+ });
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 255])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([255, 255, 255, 254])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 23, 1, 255])));
+ assert!(!iface.inner.is_broadcast_v4(Ipv4Address([192, 23, 1, 254])));
+ assert!(!iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 255, 255, 254])));
+ assert!(iface
+ .inner
+ .is_broadcast_v4(Ipv4Address([192, 255, 255, 255])));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "medium-ip", feature = "socket-udp"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "medium-ethernet", feature = "socket-udp"))]
+fn test_icmp_error_port_unreachable(#[case] medium: Medium) {
+ static UDP_PAYLOAD: [u8; 12] = [
+ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x6c, 0x64, 0x21,
+ ];
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut udp_bytes_unicast = vec![0u8; 20];
+ let mut udp_bytes_broadcast = vec![0u8; 20];
+ let mut packet_unicast = UdpPacket::new_unchecked(&mut udp_bytes_unicast);
+ let mut packet_broadcast = UdpPacket::new_unchecked(&mut udp_bytes_broadcast);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ });
+
+ // Emit the representations to a packet
+ udp_repr.emit(
+ &mut packet_unicast,
+ &ip_repr.src_addr(),
+ &ip_repr.dst_addr(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ let data = packet_unicast.into_inner();
+
+ // The expected Destination Unreachable ICMPv4 error response due
+ // to no sockets listening on the destination port.
+ let icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ },
+ data,
+ };
+ let expected_repr = Packet::new_ipv4(
+ Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ next_header: IpProtocol::Icmp,
+ payload_len: icmp_repr.buffer_len(),
+ hop_limit: 64,
+ },
+ IpPayload::Icmpv4(icmp_repr),
+ );
+
+ // Ensure that the unknown protocol triggers an error response.
+ // And we correctly handle no payload.
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ data
+ ),
+ Some(expected_repr)
+ );
+
+ let ip_repr = IpRepr::Ipv4(Ipv4Repr {
+ src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Udp,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ hop_limit: 64,
+ });
+
+ // Emit the representations to a packet
+ udp_repr.emit(
+ &mut packet_broadcast,
+ &ip_repr.src_addr(),
+ &IpAddress::Ipv4(Ipv4Address::BROADCAST),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+
+ // Ensure that the port unreachable error does not trigger an
+ // ICMP error response when the destination address is a
+ // broadcast address and no socket is bound to the port.
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr,
+ udp_repr,
+ false,
+ &UDP_PAYLOAD,
+ packet_broadcast.into_inner(),
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(feature = "medium-ip")]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_ipv4_broadcast(#[case] medium: Medium) {
+ use crate::wire::{Icmpv4Packet, Icmpv4Repr};
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let our_ipv4_addr = iface.ipv4_addr().unwrap();
+ let src_ipv4_addr = Ipv4Address([127, 0, 0, 2]);
+
+ // ICMPv4 echo request
+ let icmpv4_data: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
+ let icmpv4_repr = Icmpv4Repr::EchoRequest {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &icmpv4_data,
+ };
+
+ // Send to IPv4 broadcast address
+ let ipv4_repr = Ipv4Repr {
+ src_addr: src_ipv4_addr,
+ dst_addr: Ipv4Address::BROADCAST,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: icmpv4_repr.buffer_len(),
+ };
+
+ // Emit to ip frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + icmpv4_repr.buffer_len()];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes[..]),
+ &ChecksumCapabilities::default(),
+ );
+ icmpv4_repr.emit(
+ &mut Icmpv4Packet::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ // Expected ICMPv4 echo reply
+ let expected_icmpv4_repr = Icmpv4Repr::EchoReply {
+ ident: 0x1234,
+ seq_no: 0xabcd,
+ data: &icmpv4_data,
+ };
+ let expected_ipv4_repr = Ipv4Repr {
+ src_addr: our_ipv4_addr,
+ dst_addr: src_ipv4_addr,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: expected_icmpv4_repr.buffer_len(),
+ };
+ let expected_packet =
+ Packet::new_ipv4(expected_ipv4_repr, IpPayload::Icmpv4(expected_icmpv4_repr));
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ Some(expected_packet)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_valid_arp_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: local_ip_addr,
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+
+ // Ensure an ARP Request for us triggers an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: local_hw_addr,
+ source_protocol_addr: local_ip_addr,
+ target_hardware_addr: remote_hw_addr,
+ target_protocol_addr: remote_ip_addr
+ }))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(local_ip_addr),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_handle_other_arp_request(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x03]),
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+
+ // Ensure an ARP Request for someone else does not trigger an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ // Ensure the address of the requester was NOT entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(Ipv4Address([0x7f, 0x00, 0x00, 0x01])),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Err(DispatchError::NeighborPending)
+ );
+}
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(feature = "medium-ethernet")]
+fn test_arp_flush_after_update_ip(#[case] medium: Medium) {
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let mut eth_bytes = vec![0u8; 42];
+
+ let local_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ let remote_ip_addr = Ipv4Address([0x7f, 0x00, 0x00, 0x02]);
+ let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+ let repr = ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Request,
+ source_hardware_addr: remote_hw_addr,
+ source_protocol_addr: remote_ip_addr,
+ target_hardware_addr: EthernetAddress::default(),
+ target_protocol_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
+ };
+
+ let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes);
+ frame.set_dst_addr(EthernetAddress::BROADCAST);
+ frame.set_src_addr(remote_hw_addr);
+ frame.set_ethertype(EthernetProtocol::Arp);
+ {
+ let mut packet = ArpPacket::new_unchecked(frame.payload_mut());
+ repr.emit(&mut packet);
+ }
+
+ // Ensure an ARP Request for us triggers an ARP Reply
+ assert_eq!(
+ iface.inner.process_ethernet(
+ &mut sockets,
+ PacketMeta::default(),
+ frame.into_inner(),
+ &mut iface.fragments
+ ),
+ Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
+ operation: ArpOperation::Reply,
+ source_hardware_addr: local_hw_addr,
+ source_protocol_addr: local_ip_addr,
+ target_hardware_addr: remote_hw_addr,
+ target_protocol_addr: remote_ip_addr
+ }))
+ );
+
+ // Ensure the address of the requester was entered in the cache
+ assert_eq!(
+ iface.inner.lookup_hardware_addr(
+ MockTxToken,
+ &IpAddress::Ipv4(local_ip_addr),
+ &IpAddress::Ipv4(remote_ip_addr),
+ &mut iface.fragmenter,
+ ),
+ Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken))
+ );
+
+ // Update IP addrs to trigger ARP cache flush
+ let local_ip_addr_new = Ipv4Address([0x7f, 0x00, 0x00, 0x01]);
+ iface.update_ip_addrs(|addrs| {
+ addrs.iter_mut().next().map(|addr| {
+ *addr = IpCidr::Ipv4(Ipv4Cidr::new(local_ip_addr_new, 24));
+ });
+ });
+
+ // ARP cache flush after address change
+ assert!(!iface.inner.has_neighbor(&IpAddress::Ipv4(remote_ip_addr)));
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-icmp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-icmp", feature = "medium-ethernet"))]
+fn test_icmpv4_socket(#[case] medium: Medium) {
+ use crate::wire::Icmpv4Packet;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]);
+ let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]);
+
+ let icmpv4_socket = icmp::Socket::new(rx_buffer, tx_buffer);
+
+ let socket_handle = sockets.add(icmpv4_socket);
+
+ let ident = 0x1234;
+ let seq_no = 0x5432;
+ let echo_data = &[0xff; 16];
+
+ let socket = sockets.get_mut::<icmp::Socket>(socket_handle);
+ // Bind to the ID 0x1234
+ assert_eq!(socket.bind(icmp::Endpoint::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_unchecked(&mut bytes[..]);
+ let echo_repr = Icmpv4Repr::EchoRequest {
+ ident,
+ seq_no,
+ data: echo_data,
+ };
+ echo_repr.emit(&mut packet, &ChecksumCapabilities::default());
+ let icmp_data = &*packet.into_inner();
+
+ let ipv4_repr = Ipv4Repr {
+ src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02),
+ dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01),
+ next_header: IpProtocol::Icmp,
+ payload_len: 24,
+ hop_limit: 64,
+ };
+ let ip_repr = IpRepr::Ipv4(ipv4_repr);
+
+ // Open a socket and ensure the packet is handled due to the listening
+ // socket.
+ assert!(!sockets.get_mut::<icmp::Socket>(socket_handle).can_recv());
+
+ // Confirm we still get EchoReply from `smoltcp` even with the ICMP socket listening
+ let echo_reply = Icmpv4Repr::EchoReply {
+ ident,
+ seq_no,
+ data: echo_data,
+ };
+ let ipv4_reply = Ipv4Repr {
+ src_addr: ipv4_repr.dst_addr,
+ dst_addr: ipv4_repr.src_addr,
+ ..ipv4_repr
+ };
+ assert_eq!(
+ iface.inner.process_icmpv4(&mut sockets, ip_repr, icmp_data),
+ Some(Packet::new_ipv4(ipv4_reply, IpPayload::Icmpv4(echo_reply)))
+ );
+
+ let socket = sockets.get_mut::<icmp::Socket>(socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ icmp_data,
+ IpAddress::Ipv4(Ipv4Address::new(0x7f, 0x00, 0x00, 0x02))
+ ))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "proto-igmp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "proto-igmp", feature = "medium-ethernet"))]
+fn test_handle_igmp(#[case] medium: Medium) {
+ fn recv_igmp(
+ device: &mut crate::tests::TestingDevice,
+ timestamp: Instant,
+ ) -> Vec<(Ipv4Repr, IgmpRepr)> {
+ let caps = device.capabilities();
+ let checksum_caps = &caps.checksum;
+ recv_all(device, timestamp)
+ .iter()
+ .filter_map(|frame| {
+ let ipv4_packet = match caps.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ let eth_frame = EthernetFrame::new_checked(frame).ok()?;
+ Ipv4Packet::new_checked(eth_frame.payload()).ok()?
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => Ipv4Packet::new_checked(&frame[..]).ok()?,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+ let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, checksum_caps).ok()?;
+ let ip_payload = ipv4_packet.payload();
+ let igmp_packet = IgmpPacket::new_checked(ip_payload).ok()?;
+ let igmp_repr = IgmpRepr::parse(&igmp_packet).ok()?;
+ Some((ipv4_repr, igmp_repr))
+ })
+ .collect::<Vec<_>>()
+ }
+
+ let groups = [
+ Ipv4Address::new(224, 0, 0, 22),
+ Ipv4Address::new(224, 0, 0, 56),
+ ];
+
+ let (mut iface, mut sockets, mut device) = setup(medium);
+
+ // Join multicast groups
+ let timestamp = Instant::ZERO;
+ for group in &groups {
+ iface
+ .join_multicast_group(&mut device, *group, timestamp)
+ .unwrap();
+ }
+
+ let reports = recv_igmp(&mut device, timestamp);
+ assert_eq!(reports.len(), 2);
+ for (i, group_addr) in groups.iter().enumerate() {
+ assert_eq!(reports[i].0.next_header, IpProtocol::Igmp);
+ assert_eq!(reports[i].0.dst_addr, *group_addr);
+ assert_eq!(
+ reports[i].1,
+ IgmpRepr::MembershipReport {
+ group_addr: *group_addr,
+ version: IgmpVersion::Version2,
+ }
+ );
+ }
+
+ // General query
+ let timestamp = Instant::ZERO;
+ const GENERAL_QUERY_BYTES: &[u8] = &[
+ 0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, 0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63,
+ 0x04, 0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, 0x11, 0x64, 0xec, 0x8f, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ ];
+ {
+ // Transmit GENERAL_QUERY_BYTES into loopback
+ let tx_token = device.transmit(timestamp).unwrap();
+ tx_token.consume(GENERAL_QUERY_BYTES.len(), |buffer| {
+ buffer.copy_from_slice(GENERAL_QUERY_BYTES);
+ });
+ }
+ // Trigger processing until all packets received through the
+ // loopback have been processed, including responses to
+ // GENERAL_QUERY_BYTES. Therefore `recv_all()` would return 0
+ // pkts that could be checked.
+ iface.socket_ingress(&mut device, &mut sockets);
+
+ // Leave multicast groups
+ let timestamp = Instant::ZERO;
+ for group in &groups {
+ iface
+ .leave_multicast_group(&mut device, *group, timestamp)
+ .unwrap();
+ }
+
+ let leaves = recv_igmp(&mut device, timestamp);
+ assert_eq!(leaves.len(), 2);
+ for (i, group_addr) in groups.iter().cloned().enumerate() {
+ assert_eq!(leaves[i].0.next_header, IpProtocol::Igmp);
+ assert_eq!(leaves[i].0.dst_addr, Ipv4Address::MULTICAST_ALL_ROUTERS);
+ assert_eq!(leaves[i].1, IgmpRepr::LeaveGroup { group_addr });
+ }
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-raw", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-raw", feature = "medium-ethernet"))]
+fn test_raw_socket_no_reply(#[case] medium: Medium) {
+ use crate::wire::{IpVersion, UdpPacket, UdpRepr};
+
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ let packets = 1;
+ let rx_buffer =
+ raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]);
+ let tx_buffer = raw::PacketBuffer::new(
+ vec![raw::PacketMetadata::EMPTY; packets],
+ vec![0; 48 * packets],
+ );
+ let raw_socket = raw::Socket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer);
+ sockets.add(raw_socket);
+
+ let src_addr = Ipv4Address([127, 0, 0, 2]);
+ let dst_addr = Ipv4Address([127, 0, 0, 1]);
+
+ const PAYLOAD_LEN: usize = 10;
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+ let ipv4_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + PAYLOAD_LEN,
+ };
+
+ // Emit to frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes),
+ &ChecksumCapabilities::default(),
+ );
+ udp_repr.emit(
+ &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &src_addr.into(),
+ &dst_addr.into(),
+ PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-raw", feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(
+ feature = "socket-raw",
+ feature = "socket-udp",
+ feature = "medium-ethernet"
+))]
+fn test_raw_socket_with_udp_socket(#[case] medium: Medium) {
+ use crate::wire::{IpEndpoint, IpVersion, UdpPacket, UdpRepr};
+
+ static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f];
+
+ let (mut iface, mut sockets, _) = setup(medium);
+
+ let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]);
+ let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
+ let udp_socket_handle = sockets.add(udp_socket);
+
+ // Bind the socket to port 68
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert_eq!(socket.bind(68), Ok(()));
+ assert!(!socket.can_recv());
+ assert!(socket.can_send());
+
+ let packets = 1;
+ let raw_rx_buffer =
+ raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]);
+ let raw_tx_buffer = raw::PacketBuffer::new(
+ vec![raw::PacketMetadata::EMPTY; packets],
+ vec![0; 48 * packets],
+ );
+ let raw_socket = raw::Socket::new(
+ IpVersion::Ipv4,
+ IpProtocol::Udp,
+ raw_rx_buffer,
+ raw_tx_buffer,
+ );
+ sockets.add(raw_socket);
+
+ let src_addr = Ipv4Address([127, 0, 0, 2]);
+ let dst_addr = Ipv4Address([127, 0, 0, 1]);
+
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + UDP_PAYLOAD.len()];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+ let ipv4_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
+ };
+
+ // Emit to frame
+ let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + UDP_PAYLOAD.len()];
+ let frame = {
+ ipv4_repr.emit(
+ &mut Ipv4Packet::new_unchecked(&mut bytes),
+ &ChecksumCapabilities::default(),
+ );
+ udp_repr.emit(
+ &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]),
+ &src_addr.into(),
+ &dst_addr.into(),
+ UDP_PAYLOAD.len(),
+ |buf| buf.copy_from_slice(&UDP_PAYLOAD),
+ &ChecksumCapabilities::default(),
+ );
+ Ipv4Packet::new_unchecked(&bytes[..])
+ };
+
+ assert_eq!(
+ iface.inner.process_ipv4(
+ &mut sockets,
+ PacketMeta::default(),
+ &frame,
+ &mut iface.fragments
+ ),
+ None
+ );
+
+ // Make sure the UDP socket can still receive in presence of a Raw socket that handles UDP
+ let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle);
+ assert!(socket.can_recv());
+ assert_eq!(
+ socket.recv(),
+ Ok((
+ &UDP_PAYLOAD[..],
+ IpEndpoint::new(src_addr.into(), 67).into()
+ ))
+ );
+}
+
+#[rstest]
+#[case(Medium::Ip)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ip"))]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))]
+fn test_icmp_reply_size(#[case] medium: Medium) {
+ use crate::wire::IPV4_MIN_MTU as MIN_MTU;
+ const MAX_PAYLOAD_LEN: usize = 528;
+
+ let (mut iface, mut sockets, _device) = setup(medium);
+
+ let src_addr = Ipv4Address([192, 168, 1, 1]);
+ let dst_addr = Ipv4Address([192, 168, 1, 2]);
+
+ // UDP packet that if not tructated will cause a icmp port unreachable reply
+ // to exceed the minimum mtu bytes in length.
+ let udp_repr = UdpRepr {
+ src_port: 67,
+ dst_port: 68,
+ };
+ let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN];
+ let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
+ udp_repr.emit(
+ &mut packet,
+ &src_addr.into(),
+ &dst_addr.into(),
+ MAX_PAYLOAD_LEN,
+ |buf| fill_slice(buf, 0x2a),
+ &ChecksumCapabilities::default(),
+ );
+
+ let ip_repr = Ipv4Repr {
+ src_addr,
+ dst_addr,
+ next_header: IpProtocol::Udp,
+ hop_limit: 64,
+ payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN,
+ };
+ let payload = packet.into_inner();
+
+ let expected_icmp_repr = Icmpv4Repr::DstUnreachable {
+ reason: Icmpv4DstUnreachable::PortUnreachable,
+ header: ip_repr,
+ data: &payload[..MAX_PAYLOAD_LEN],
+ };
+
+ let expected_ip_repr = Ipv4Repr {
+ src_addr: dst_addr,
+ dst_addr: src_addr,
+ next_header: IpProtocol::Icmp,
+ hop_limit: 64,
+ payload_len: expected_icmp_repr.buffer_len(),
+ };
+
+ assert_eq!(
+ expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(),
+ MIN_MTU
+ );
+
+ assert_eq!(
+ iface.inner.process_udp(
+ &mut sockets,
+ PacketMeta::default(),
+ ip_repr.into(),
+ udp_repr,
+ false,
+ &vec![0x2a; MAX_PAYLOAD_LEN],
+ payload,
+ ),
+ Some(Packet::new_ipv4(
+ expected_ip_repr,
+ IpPayload::Icmpv4(expected_icmp_repr)
+ ))
+ );
+}