diff --git a/src/iface/interface/ipv6.rs b/src/iface/interface/ipv6.rs index 3c6d15f2a..97bd6aa7e 100644 --- a/src/iface/interface/ipv6.rs +++ b/src/iface/interface/ipv6.rs @@ -11,6 +11,24 @@ use crate::socket::AnySocket; use crate::phy::PacketMeta; use crate::wire::*; +/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMMP +/// parameter problem message needs to be transmitted to the source of the address. In other cases, +/// the processing of the IP packet can continue. +#[allow(clippy::large_enum_variant)] +enum HopByHopResponse<'frame> { + /// Continue processing the IPv6 packet. + Continue((IpProtocol, &'frame [u8])), + /// Discard the packet and maybe send back an ICMPv6 packet. + Discard(Option>), +} + +// We implement `Default` such that we can use the check! macro. +impl Default for HopByHopResponse<'_> { + fn default() -> Self { + Self::Discard(None) + } +} + impl InterfaceInner { pub(super) fn process_ipv6<'frame>( &mut self, @@ -26,7 +44,19 @@ impl InterfaceInner { return None; } - let ip_payload = ipv6_packet.payload(); + let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop { + match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) { + HopByHopResponse::Discard(e) => return e, + HopByHopResponse::Continue(next) => next, + } + } else { + (ipv6_repr.next_header, ipv6_packet.payload()) + }; + + if !self.has_ip_addr(ipv6_repr.dst_addr) && !self.has_multicast_group(ipv6_repr.dst_addr) { + net_trace!("packet IP address not for this interface"); + return None; + } #[cfg(feature = "socket-raw")] let handled_by_raw_socket = self.raw_socket_filter(sockets, &ipv6_repr.into(), ip_payload); @@ -37,15 +67,71 @@ impl InterfaceInner { sockets, meta, ipv6_repr, - ipv6_repr.next_header, + next_header, handled_by_raw_socket, ip_payload, ) } + fn process_hopbyhop<'frame>( + &mut self, + ipv6_repr: Ipv6Repr, + ip_payload: &'frame [u8], + ) -> HopByHopResponse<'frame> { + let param_problem = || { + let payload_len = + icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len()); + self.icmpv6_reply( + ipv6_repr, + Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedOption, + pointer: ipv6_repr.buffer_len() as u32, + header: ipv6_repr, + data: &ip_payload[0..payload_len], + }, + ) + }; + + let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload)); + let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr)); + let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data)); + let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr)); + + for opt_repr in &hbh_repr.options { + match opt_repr { + Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) => (), + #[cfg(feature = "proto-rpl")] + Ipv6OptionRepr::Rpl(_) => {} + + Ipv6OptionRepr::Unknown { type_, .. } => { + match Ipv6OptionFailureType::from(*type_) { + Ipv6OptionFailureType::Skip => (), + Ipv6OptionFailureType::Discard => { + return HopByHopResponse::Discard(None); + } + Ipv6OptionFailureType::DiscardSendAll => { + return HopByHopResponse::Discard(param_problem()); + } + Ipv6OptionFailureType::DiscardSendUnicast + if !ipv6_repr.dst_addr.is_multicast() => + { + return HopByHopResponse::Discard(param_problem()); + } + _ => unreachable!(), + } + } + } + } + + HopByHopResponse::Continue(( + ext_repr.next_header, + &ip_payload[ext_repr.header_len() + ext_repr.data.len()..], + )) + } + /// Given the next header value forward the payload onto the correct process /// function. - pub(super) fn process_nxt_hdr<'frame>( + fn process_nxt_hdr<'frame>( &mut self, sockets: &mut SocketSet, meta: PacketMeta, @@ -81,10 +167,6 @@ impl InterfaceInner { #[cfg(feature = "socket-tcp")] IpProtocol::Tcp => self.process_tcp(sockets, ipv6_repr.into(), ip_payload), - IpProtocol::HopByHop => { - self.process_hopbyhop(sockets, meta, ipv6_repr, handled_by_raw_socket, ip_payload) - } - #[cfg(feature = "socket-raw")] _ if handled_by_raw_socket => None, @@ -235,70 +317,27 @@ impl InterfaceInner { } } - pub(super) fn process_hopbyhop<'frame>( - &mut self, - sockets: &mut SocketSet, - meta: PacketMeta, - ipv6_repr: Ipv6Repr, - handled_by_raw_socket: bool, - ip_payload: &'frame [u8], - ) -> Option> { - let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload)); - let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr)); - let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data)); - let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr)); - - for opt_repr in &hbh_repr.options { - match opt_repr { - Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) => (), - #[cfg(feature = "proto-rpl")] - Ipv6OptionRepr::Rpl(_) => {} - - Ipv6OptionRepr::Unknown { type_, .. } => { - match Ipv6OptionFailureType::from(*type_) { - Ipv6OptionFailureType::Skip => (), - Ipv6OptionFailureType::Discard => { - return None; - } - _ => { - // FIXME(dlrobertson): Send an ICMPv6 parameter problem message - // here. - return None; - } - } - } - } - } - self.process_nxt_hdr( - sockets, - meta, - ipv6_repr, - ext_repr.next_header, - handled_by_raw_socket, - &ip_payload[ext_repr.header_len() + ext_repr.data.len()..], - ) - } - pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>( &self, ipv6_repr: Ipv6Repr, icmp_repr: Icmpv6Repr<'icmp>, ) -> Option> { - if ipv6_repr.dst_addr.is_unicast() { - let ipv6_reply_repr = Ipv6Repr { - src_addr: ipv6_repr.dst_addr, - dst_addr: ipv6_repr.src_addr, - next_header: IpProtocol::Icmpv6, - payload_len: icmp_repr.buffer_len(), - hop_limit: 64, - }; - Some(IpPacket::new_ipv6( - ipv6_reply_repr, - IpPayload::Icmpv6(icmp_repr), - )) + let src_addr = if ipv6_repr.dst_addr.is_unicast() { + ipv6_repr.dst_addr } else { - // Do not send any ICMP replies to a broadcast destination address. - None - } + self.ipv6_addr().unwrap() + }; + + let ipv6_reply_repr = Ipv6Repr { + src_addr, + dst_addr: ipv6_repr.src_addr, + next_header: IpProtocol::Icmpv6, + payload_len: icmp_repr.buffer_len(), + hop_limit: 64, + }; + Some(IpPacket::new_ipv6( + ipv6_reply_repr, + IpPayload::Icmpv6(icmp_repr), + )) } } diff --git a/src/iface/interface/tests/ipv6.rs b/src/iface/interface/tests/ipv6.rs index 5ce233f77..90e45ce5c 100644 --- a/src/iface/interface/tests/ipv6.rs +++ b/src/iface/interface/tests/ipv6.rs @@ -140,6 +140,129 @@ fn hop_by_hop_discard_with_icmp(#[case] medium: Medium) { ); } +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +fn hop_by_hop_discard_param_problem(#[case] medium: Medium) { + use crate::iface::ip_packet::Ipv6Packet; + + // 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(IpPacket::Ipv6(Ipv6Packet { + header: 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, + }, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload: 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(), + &Ipv6PacketWire::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +fn hop_by_hop_discard_with_multicast(#[case] medium: Medium) { + use crate::iface::ip_packet::Ipv6Packet; + + // 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(IpPacket::Ipv6(Ipv6Packet { + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 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, + }, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload: 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(), + &Ipv6PacketWire::new_checked(&data[..]).unwrap() + ), + response + ); +} + #[rstest] #[case::ip(Medium::Ip)] #[cfg(feature = "medium-ip")] @@ -314,14 +437,33 @@ fn icmp_echo_reply_as_input(#[case] medium: Medium) { #[case::ieee802154(Medium::Ieee802154)] #[cfg(feature = "medium-ieee802154")] fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) { - // Since the destination address is multicast, we should not 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, 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, ]; - let response = None; + let response = Some(IpPacket::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::from_parts(&[0xfe80, 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); @@ -343,7 +485,7 @@ fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) { #[case::ieee802154(Medium::Ieee802154)] #[cfg(feature = "medium-ieee802154")] fn unknown_proto(#[case] medium: Medium) { - // Since the destination address is multicast, we should not answer with an ICMPv6 message. + // 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,