diff options
Diffstat (limited to 'src/iface/interface/tests')
-rw-r--r-- | src/iface/interface/tests/ipv4.rs | 968 | ||||
-rw-r--r-- | src/iface/interface/tests/ipv6.rs | 988 | ||||
-rw-r--r-- | src/iface/interface/tests/mod.rs | 235 | ||||
-rw-r--r-- | src/iface/interface/tests/sixlowpan.rs | 434 |
4 files changed, 2625 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) + )) + ); +} diff --git a/src/iface/interface/tests/ipv6.rs b/src/iface/interface/tests/ipv6.rs new file mode 100644 index 0000000..9c5f099 --- /dev/null +++ b/src/iface/interface/tests/ipv6.rs @@ -0,0 +1,988 @@ +use super::*; + +fn parse_ipv6(data: &[u8]) -> crate::wire::Result<Packet<'_>> { + let ipv6_header = Ipv6Packet::new_checked(data)?; + let ipv6 = Ipv6Repr::parse(&ipv6_header)?; + + match ipv6.next_header { + IpProtocol::HopByHop => todo!(), + IpProtocol::Icmp => todo!(), + IpProtocol::Igmp => todo!(), + IpProtocol::Tcp => todo!(), + IpProtocol::Udp => todo!(), + IpProtocol::Ipv6Route => todo!(), + IpProtocol::Ipv6Frag => todo!(), + IpProtocol::IpSecEsp => todo!(), + IpProtocol::IpSecAh => todo!(), + IpProtocol::Icmpv6 => { + let icmp = Icmpv6Repr::parse( + &ipv6.src_addr.into(), + &ipv6.dst_addr.into(), + &Icmpv6Packet::new_checked(ipv6_header.payload())?, + &Default::default(), + )?; + Ok(Packet::new_ipv6(ipv6, IpPayload::Icmpv6(icmp))) + } + IpProtocol::Ipv6NoNxt => todo!(), + IpProtocol::Ipv6Opts => todo!(), + IpProtocol::Unknown(_) => todo!(), + } +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn multicast_source_address(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, + ]; + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn hop_by_hop_skip_with_icmp(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (skipped) + // - ICMP echo request + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0xf, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 42, + seq_no: 420, + data: b"Lorem Ipsum", + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn hop_by_hop_discard_with_icmp(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (discard) + // - ICMP echo request + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +fn hop_by_hop_discard_param_problem(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (discard + ParamProblem) + // - ICMP echo request + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Icmpv6, + payload_len: 75, + hop_limit: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedOption, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1), + next_header: IpProtocol::HopByHop, + payload_len: 27, + hop_limit: 64, + }, + data: &[ + 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, + 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +fn hop_by_hop_discard_with_multicast(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (discard (0b11) + ParamProblem) + // - ICMP echo request + // + // In this case, even if the destination address is a multicast address, an ICMPv6 ParamProblem + // should be transmitted. + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Icmpv6, + payload_len: 75, + hop_limit: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedOption, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1), + next_header: IpProtocol::HopByHop, + payload_len: 27, + hop_limit: 64, + }, + data: &[ + 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, + 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn imcp_empty_echo_request(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x8, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x84, 0x3c, 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 8, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoRequest { + ident: 0, + seq_no: 0, + data: b"", + }) + )) + ); + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 8, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 0, + seq_no: 0, + data: b"", + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn icmp_echo_request(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, + 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoRequest { + ident: 42, + seq_no: 420, + data: b"Lorem Ipsum", + }) + )) + ); + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 42, + seq_no: 420, + data: b"Lorem Ipsum", + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn icmp_echo_reply_as_input(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x81, 0x0, 0x2d, 0x56, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x6f, 0x72, 0x65, + 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 0, + seq_no: 0, + data: b"Lorem Ipsum", + }) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 48, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedNxtHdr, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 64, + next_header: IpProtocol::Unknown(0x0c), + payload_len: 0, + }, + data: &[], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn unknown_proto(#[case] medium: Medium) { + // Since the destination address is multicast, we should answer with an ICMPv6 message. + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 48, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedNxtHdr, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 64, + next_header: IpProtocol::Unknown(0x0c), + payload_len: 0, + }, + data: &[], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn ndsic_neighbor_advertisement_ethernet(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x9f, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 255, + next_header: IpProtocol::Icmpv6, + payload_len: 32, + }, + IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]), + lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 1])), + })) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); + + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])), + iface.inner.now, + ), + NeighborAnswer::Found(HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[ + 0, 0, 0, 0, 0, 1 + ]))), + ); +} + +#[rstest] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn ndsic_neighbor_advertisement_ethernet_multicast_addr(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0xa0, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 255, + next_header: IpProtocol::Icmpv6, + payload_len: 32, + }, + IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]), + lladdr: Some(RawHardwareAddress::from_bytes(&[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + ])), + })) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); + + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])), + iface.inner.now, + ), + NeighborAnswer::NotFound, + ); +} + +#[rstest] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn ndsic_neighbor_advertisement_ieee802154(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x28, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x96, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]), + dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001]), + hop_limit: 255, + next_header: IpProtocol::Icmpv6, + payload_len: 40, + }, + IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0002]), + lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 0, 0, 1])), + })) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); + + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002])), + iface.inner.now, + ), + NeighborAnswer::Found(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes( + &[0, 0, 0, 0, 0, 0, 0, 1] + ))), + ); +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_handle_valid_ndisc_request(#[case] medium: Medium) { + let (mut iface, mut sockets, _device) = setup(medium); + + let mut eth_bytes = vec![0u8; 86]; + + let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1); + let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2); + let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + + let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit { + target_addr: local_ip_addr, + lladdr: Some(remote_hw_addr.into()), + }); + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: remote_ip_addr, + dst_addr: local_ip_addr.solicited_node(), + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: solicit.buffer_len(), + }); + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00])); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + solicit.emit( + &remote_ip_addr.into(), + &local_ip_addr.solicited_node().into(), + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: local_ip_addr, + lladdr: Some(local_hw_addr.into()), + }); + + let ipv6_expected = Ipv6Repr { + src_addr: local_ip_addr, + dst_addr: remote_ip_addr, + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: icmpv6_expected.buffer_len(), + }; + + // Ensure an Neighbor Solicitation triggers a Neighbor Advertisement + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + Some(EthernetPacket::Ip(Packet::new_ipv6( + ipv6_expected, + IpPayload::Icmpv6(icmpv6_expected) + ))) + ); + + // Ensure the address of the requester was entered in the cache + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv6(local_ip_addr), + &IpAddress::Ipv6(remote_ip_addr), + &mut iface.fragmenter, + ), + Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken)) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn test_solicited_node_addrs(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let mut new_addrs = heapless::Vec::<IpCidr, IFACE_MAX_ADDR_COUNT>::new(); + new_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 1, 2, 0, 2), 64)) + .unwrap(); + new_addrs + .push(IpCidr::new( + IpAddress::v6(0xfe80, 0, 0, 0, 3, 4, 0, 0xffff), + 64, + )) + .unwrap(); + iface.update_ip_addrs(|addrs| { + new_addrs.extend(addrs.to_vec()); + *addrs = new_addrs; + }); + assert!(iface + .inner + .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002))); + assert!(iface + .inner + .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff))); + assert!(!iface + .inner + .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0003))); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-udp", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))] +#[case(Medium::Ieee802154)] +#[cfg(all(feature = "socket-udp", feature = "medium-ieee802154"))] +fn test_icmp_reply_size(#[case] medium: Medium) { + use crate::wire::Icmpv6DstUnreachable; + use crate::wire::IPV6_MIN_MTU as MIN_MTU; + const MAX_PAYLOAD_LEN: usize = 1192; + + let (mut iface, mut sockets, _device) = setup(medium); + + let src_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + let dst_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 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 = Ipv6Repr { + 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 = Icmpv6Repr::DstUnreachable { + reason: Icmpv6DstUnreachable::PortUnreachable, + header: ip_repr, + data: &payload[..MAX_PAYLOAD_LEN], + }; + + let expected_ip_repr = Ipv6Repr { + src_addr: dst_addr, + dst_addr: src_addr, + next_header: IpProtocol::Icmpv6, + 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_ipv6( + expected_ip_repr, + IpPayload::Icmpv6(expected_icmp_repr) + )) + ); +} + +#[cfg(feature = "medium-ip")] +#[test] +fn get_source_address() { + let (mut iface, _, _) = setup(Medium::Ip); + + const OWN_LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const OWN_UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 2); + const OWN_UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 2); + const OWN_GLOBAL_UNICAST_ADDR1: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 1); + + // List of addresses of the interface: + // fe80::1/64 + // fd00::201:1:1:1:2/64 + // fd01::201:1:1:1:2/64 + // 2001:db8:3::1/64 + // ::1/128 + // ::/128 + iface.update_ip_addrs(|addrs| { + addrs.clear(); + + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_LINK_LOCAL_ADDR, 64))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR1, 64))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR2, 64))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_GLOBAL_UNICAST_ADDR1, 64))) + .unwrap(); + + // These should never be used: + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128))) + .unwrap(); + }); + + // List of addresses we test: + // fe80::42 -> fe80::1 + // fd00::201:1:1:1:1 -> fd00::201:1:1:1:2 + // fd01::201:1:1:1:1 -> fd01::201:1:1:1:2 + // fd02::201:1:1:1:1 -> fd00::201:1:1:1:2 (because first added in the list) + // ff02::1 -> fe80::1 (same scope) + // 2001:db8:3::2 -> 2001:db8:3::1 + // 2001:db9:3::2 -> 2001:db8:3::1 + const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42); + const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1); + const GLOBAL_UNICAST_ADDR1: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2); + const GLOBAL_UNICAST_ADDR2: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db9, 0x0003, 0, 0, 0, 0, 2); + + assert_eq!( + iface.inner.get_source_address_ipv6(&LINK_LOCAL_ADDR), + Some(OWN_LINK_LOCAL_ADDR) + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + Some(OWN_UNIQUE_LOCAL_ADDR1) + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + Some(OWN_UNIQUE_LOCAL_ADDR2) + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + Some(OWN_UNIQUE_LOCAL_ADDR1) + ); + assert_eq!( + iface + .inner + .get_source_address_ipv6(&Ipv6Address::LINK_LOCAL_ALL_NODES), + Some(OWN_LINK_LOCAL_ADDR) + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + Some(OWN_GLOBAL_UNICAST_ADDR1) + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + Some(OWN_GLOBAL_UNICAST_ADDR1) + ); + + assert_eq!( + iface.get_source_address_ipv6(&LINK_LOCAL_ADDR), + Some(OWN_LINK_LOCAL_ADDR) + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + Some(OWN_UNIQUE_LOCAL_ADDR1) + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + Some(OWN_UNIQUE_LOCAL_ADDR2) + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + Some(OWN_UNIQUE_LOCAL_ADDR1) + ); + assert_eq!( + iface.get_source_address_ipv6(&Ipv6Address::LINK_LOCAL_ALL_NODES), + Some(OWN_LINK_LOCAL_ADDR) + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + Some(OWN_GLOBAL_UNICAST_ADDR1) + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + Some(OWN_GLOBAL_UNICAST_ADDR1) + ); +} diff --git a/src/iface/interface/tests/mod.rs b/src/iface/interface/tests/mod.rs new file mode 100644 index 0000000..b4b4416 --- /dev/null +++ b/src/iface/interface/tests/mod.rs @@ -0,0 +1,235 @@ +#[cfg(feature = "proto-ipv4")] +mod ipv4; +#[cfg(feature = "proto-ipv6")] +mod ipv6; +#[cfg(feature = "proto-sixlowpan")] +mod sixlowpan; + +#[cfg(feature = "proto-igmp")] +use std::vec::Vec; + +use crate::tests::setup; + +use rstest::*; + +use super::*; + +use crate::iface::Interface; +use crate::phy::ChecksumCapabilities; +#[cfg(feature = "alloc")] +use crate::phy::Loopback; +use crate::time::Instant; + +#[allow(unused)] +fn fill_slice(s: &mut [u8], val: u8) { + for x in s.iter_mut() { + *x = val + } +} + +#[cfg(feature = "proto-igmp")] +fn recv_all(device: &mut crate::tests::TestingDevice, timestamp: Instant) -> Vec<Vec<u8>> { + let mut pkts = Vec::new(); + while let Some((rx, _tx)) = device.receive(timestamp) { + rx.consume(|pkt| { + pkts.push(pkt.to_vec()); + }); + } + pkts +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct MockTxToken; + +impl TxToken for MockTxToken { + fn consume<R, F>(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut junk = [0; 1536]; + f(&mut junk[..len]) + } +} + +#[test] +#[should_panic(expected = "The hardware address does not match the medium of the interface.")] +#[cfg(all(feature = "medium-ip", feature = "medium-ethernet", feature = "alloc"))] +fn test_new_panic() { + let mut device = Loopback::new(Medium::Ethernet); + let config = Config::new(HardwareAddress::Ip); + Interface::new(config, &mut device, Instant::ZERO); +} + +#[rstest] +#[cfg(feature = "default")] +fn test_handle_udp_broadcast( + #[values(Medium::Ip, Medium::Ethernet, Medium::Ieee802154)] medium: Medium, +) { + use crate::wire::IpEndpoint; + + static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f]; + + let (mut iface, mut sockets, _device) = setup(medium); + + let rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]); + let tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]); + + let udp_socket = udp::Socket::new(rx_buffer, tx_buffer); + + let mut udp_bytes = vec![0u8; 13]; + let mut packet = UdpPacket::new_unchecked(&mut udp_bytes); + + let socket_handle = sockets.add(udp_socket); + + #[cfg(feature = "proto-ipv6")] + let src_ip = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] + let src_ip = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02); + + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + + #[cfg(feature = "proto-ipv6")] + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: src_ip, + dst_addr: Ipv6Address::LINK_LOCAL_ALL_NODES, + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 0x40, + }); + #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: src_ip, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 0x40, + }); + + // Bind the socket to port 68 + let socket = sockets.get_mut::<udp::Socket>(socket_handle); + assert_eq!(socket.bind(68), Ok(())); + assert!(!socket.can_recv()); + assert!(socket.can_send()); + + udp_repr.emit( + &mut packet, + &ip_repr.src_addr(), + &ip_repr.dst_addr(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(&UDP_PAYLOAD), + &ChecksumCapabilities::default(), + ); + + // Packet should be handled by bound UDP socket + assert_eq!( + iface.inner.process_udp( + &mut sockets, + PacketMeta::default(), + ip_repr, + udp_repr, + false, + &UDP_PAYLOAD, + packet.into_inner(), + ), + None + ); + + // Make sure the payload to the UDP packet processed by process_udp is + // appended to the bound sockets rx_buffer + let socket = sockets.get_mut::<udp::Socket>(socket_handle); + assert!(socket.can_recv()); + assert_eq!( + socket.recv(), + Ok((&UDP_PAYLOAD[..], IpEndpoint::new(src_ip.into(), 67).into())) + ); +} + +#[test] +#[cfg(all(feature = "medium-ip", feature = "socket-tcp", feature = "proto-ipv6"))] +pub fn tcp_not_accepted() { + let (mut iface, mut sockets, _) = setup(Medium::Ip); + let tcp = TcpRepr { + src_port: 4242, + dst_port: 4243, + control: TcpControl::Syn, + seq_number: TcpSeqNumber(-10001), + ack_number: None, + window_len: 256, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + payload: &[], + }; + + let mut tcp_bytes = vec![0u8; tcp.buffer_len()]; + + tcp.emit( + &mut TcpPacket::new_unchecked(&mut tcp_bytes), + &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(), + &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1).into(), + &ChecksumCapabilities::default(), + ); + + assert_eq!( + iface.inner.process_tcp( + &mut sockets, + IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + next_header: IpProtocol::Tcp, + payload_len: tcp.buffer_len(), + hop_limit: 64, + }), + &tcp_bytes, + ), + Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Tcp, + payload_len: tcp.buffer_len(), + hop_limit: 64, + }, + IpPayload::Tcp(TcpRepr { + src_port: 4243, + dst_port: 4242, + control: TcpControl::Rst, + seq_number: TcpSeqNumber(0), + ack_number: Some(TcpSeqNumber(-10000)), + window_len: 0, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + payload: &[], + }) + )) + ); + // Unspecified destination address. + tcp.emit( + &mut TcpPacket::new_unchecked(&mut tcp_bytes), + &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(), + &Ipv6Address::UNSPECIFIED.into(), + &ChecksumCapabilities::default(), + ); + + assert_eq!( + iface.inner.process_tcp( + &mut sockets, + IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::UNSPECIFIED, + next_header: IpProtocol::Tcp, + payload_len: tcp.buffer_len(), + hop_limit: 64, + }), + &tcp_bytes, + ), + None, + ); +} diff --git a/src/iface/interface/tests/sixlowpan.rs b/src/iface/interface/tests/sixlowpan.rs new file mode 100644 index 0000000..676835e --- /dev/null +++ b/src/iface/interface/tests/sixlowpan.rs @@ -0,0 +1,434 @@ +use super::*; + +#[rstest] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn ieee802154_wrong_pan_id(#[case] medium: Medium) { + let data = [ + 0x41, 0xcc, 0x3b, 0xff, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a, + 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26, + ]; + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ieee802154( + &mut sockets, + PacketMeta::default(), + &data[..], + &mut iface.fragments + ), + response, + ); +} + +#[rstest] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn icmp_echo_request(#[case] medium: Medium) { + let data = [ + 0x41, 0xcc, 0x3b, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a, + 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26, 0x6a, 0x33, 0x0a, 0x62, 0x17, 0x3a, 0x80, 0x00, 0xb0, + 0xe3, 0x00, 0x04, 0x00, 0x01, 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242]), + dst_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x241c, 0x2957, 0x34a6, 0x3a62]), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 4, + seq_no: 1, + data: &[ + 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + ], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242]), + 10, + ))) + .unwrap(); + }); + + assert_eq!( + iface.inner.process_ieee802154( + &mut sockets, + PacketMeta::default(), + &data[..], + &mut iface.fragments + ), + response, + ); +} + +#[test] +#[cfg(feature = "proto-sixlowpan-fragmentation")] +fn test_echo_request_sixlowpan_128_bytes() { + use crate::phy::Checksum; + + let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76), + 10, + ))) + .unwrap(); + }); + // TODO: modify the example, such that we can also test if the checksum is correctly + // computed. + iface.inner.caps.checksum.icmpv6 = Checksum::None; + + assert_eq!(iface.inner.caps.medium, Medium::Ieee802154); + let now = iface.inner.now(); + + iface.inner.neighbor_cache.fill( + Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x2, 0, 0, 0, 0, 0, 0, 0]).into(), + HardwareAddress::Ieee802154(Ieee802154Address::default()), + now, + ); + + let mut ieee802154_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(5), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: Some(Ieee802154Pan(0xbeef)), + dst_addr: Some(Ieee802154Address::Extended([ + 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76, + ])), + src_pan_id: Some(Ieee802154Pan(0xbeef)), + src_addr: Some(Ieee802154Address::Extended([ + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, + ])), + }; + + // NOTE: this data is retrieved from tests with Contiki-NG + + let request_first_part_packet = SixlowpanFragPacket::new_checked(&[ + 0xc0, 0xb0, 0x00, 0x8e, 0x6a, 0x33, 0x05, 0x25, 0x2c, 0x3a, 0x80, 0x00, 0xe0, 0x71, 0x00, + 0x27, 0x00, 0x02, 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + ]) + .unwrap(); + + let request_first_part_iphc_packet = + SixlowpanIphcPacket::new_checked(request_first_part_packet.payload()).unwrap(); + + let request_first_part_iphc_repr = SixlowpanIphcRepr::parse( + &request_first_part_iphc_packet, + ieee802154_repr.src_addr, + ieee802154_repr.dst_addr, + &iface.inner.sixlowpan_address_context, + ) + .unwrap(); + + assert_eq!( + request_first_part_iphc_repr.src_addr, + Ipv6Address([ + 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, + 0x1a, + ]), + ); + assert_eq!( + request_first_part_iphc_repr.dst_addr, + Ipv6Address([ + 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, + 0x76, + ]), + ); + + let request_second_part = [ + 0xe0, 0xb0, 0x00, 0x8e, 0x10, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + ]; + + assert_eq!( + iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + &request_first_part_packet.into_inner()[..], + &mut iface.fragments + ), + None + ); + + ieee802154_repr.sequence_number = Some(6); + + // data that was generated when using `ping -s 128` + let data = &[ + 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + ]; + + let result = iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + &request_second_part, + &mut iface.fragments, + ); + + assert_eq!( + result, + Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address([ + 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92, 0xfc, 0x48, 0xc2, 0xa4, 0x41, + 0xfc, 0x76, + ]), + dst_addr: Ipv6Address([ + 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, + 0xb, 0x1a, + ]), + next_header: IpProtocol::Icmpv6, + payload_len: 136, + hop_limit: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 39, + seq_no: 2, + data, + }) + )) + ); + + iface.inner.neighbor_cache.fill( + IpAddress::Ipv6(Ipv6Address([ + 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a, + ])), + HardwareAddress::Ieee802154(Ieee802154Address::default()), + Instant::now(), + ); + + let tx_token = device.transmit(Instant::now()).unwrap(); + iface.inner.dispatch_ieee802154( + Ieee802154Address::default(), + tx_token, + PacketMeta::default(), + result.unwrap(), + &mut iface.fragmenter, + ); + + assert_eq!( + device.queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb0, 0x5, 0x4e, 0x7a, 0x11, 0x3a, 0x92, 0xfc, 0x48, 0xc2, + 0xa4, 0x41, 0xfc, 0x76, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a, 0x81, 0x0, 0x0, + 0x0, 0x0, 0x27, 0x0, 0x2, 0xa2, 0xc2, 0x2d, 0x63, 0x0, 0x0, 0x0, 0x0, 0xd9, 0x5e, 0xc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, + ] + ); + + iface.poll(Instant::now(), &mut device, &mut sockets); + + assert_eq!( + device.queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb0, 0x5, 0x4e, 0xf, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, + 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + ] + ); +} + +#[test] +#[cfg(feature = "proto-sixlowpan-fragmentation")] +fn test_sixlowpan_udp_with_fragmentation() { + use crate::phy::Checksum; + + let mut ieee802154_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(5), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: Some(Ieee802154Pan(0xbeef)), + dst_addr: Some(Ieee802154Address::Extended([ + 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76, + ])), + src_pan_id: Some(Ieee802154Pan(0xbeef)), + src_addr: Some(Ieee802154Address::Extended([ + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, + ])), + }; + + let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76), + 10, + ))) + .unwrap(); + }); + iface.inner.caps.checksum.udp = Checksum::None; + + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let udp_socket_handle = sockets.add(udp_socket); + + { + let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle); + assert_eq!(socket.bind(6969), Ok(())); + assert!(!socket.can_recv()); + assert!(socket.can_send()); + } + + let udp_first_part = &[ + 0xc0, 0xbc, 0x00, 0x92, 0x6e, 0x33, 0x07, 0xe7, 0xdc, 0xf0, 0xd3, 0xc9, 0x1b, 0x39, 0xbf, + 0xa0, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, + 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, + 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x49, 0x6e, + 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, 0x20, 0x74, 0x6f, 0x72, + 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e, + ]; + + assert_eq!( + iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + udp_first_part, + &mut iface.fragments + ), + None + ); + + ieee802154_repr.sequence_number = Some(6); + + let udp_second_part = &[ + 0xe0, 0xbc, 0x00, 0x92, 0x11, 0x64, 0x69, 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73, + 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76, 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76, + 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c, 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e, + ]; + + assert_eq!( + iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + udp_second_part, + &mut iface.fragments + ), + None + ); + + let socket = sockets.get_mut::<udp::Socket>(udp_socket_handle); + + let udp_data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. \ +In at rhoncus tortor. Cras blandit tellus diam, varius vestibulum nibh commodo nec."; + assert_eq!( + socket.recv(), + Ok(( + &udp_data[..], + IpEndpoint { + addr: IpAddress::Ipv6(Ipv6Address([ + 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, + 0xb, 0x1a, + ])), + port: 54217, + } + .into() + )) + ); + + let tx_token = device.transmit(Instant::now()).unwrap(); + iface.inner.dispatch_ieee802154( + Ieee802154Address::default(), + tx_token, + PacketMeta::default(), + Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::default(), + dst_addr: Ipv6Address::default(), + next_header: IpProtocol::Udp, + payload_len: udp_data.len(), + hop_limit: 64, + }, + IpPayload::Udp( + UdpRepr { + src_port: 1234, + dst_port: 1234, + }, + udp_data, + ), + ), + &mut iface.fragmenter, + ); + + iface.poll(Instant::now(), &mut device, &mut sockets); + + assert_eq!( + device.queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb4, 0x5, 0x4e, 0x7e, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x4, 0xd2, 0x4, 0xd2, 0x0, 0x0, + 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, + 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, + 0x20, 0x49, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, + 0x20, 0x74, + ], + ); + + assert_eq!( + device.queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb4, 0x5, 0x4e, 0xf, 0x6f, 0x72, 0x74, 0x6f, 0x72, 0x2e, + 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e, 0x64, 0x69, 0x74, 0x20, + 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73, 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76, + 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76, 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c, + 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, + 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e, + ] + ); +} |