| 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) |
| )) |
| ); |
| } |