123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- use super::*;
- /// 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<Packet<'frame>>),
- }
- // We implement `Default` such that we can use the check! macro.
- impl Default for HopByHopResponse<'_> {
- fn default() -> Self {
- Self::Discard(None)
- }
- }
- impl InterfaceInner {
- /// Return the IPv6 address that is a candidate source address for the given destination
- /// address, based on RFC 6724.
- #[allow(unused)]
- pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Option<Ipv6Address> {
- // See RFC 6724 Section 4: Candidate source address
- fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool {
- // For all multicast and link-local destination addresses, the candidate address MUST
- // only be an address from the same link.
- if dst_addr.is_link_local() && !src_addr.is_link_local() {
- return false;
- }
- if dst_addr.is_multicast()
- && matches!(dst_addr.scope(), Ipv6AddressScope::LinkLocal)
- && src_addr.is_multicast()
- && !matches!(src_addr.scope(), Ipv6AddressScope::LinkLocal)
- {
- return false;
- }
- // Loopback addresses and multicast address can not be in the candidate source address
- // list. Except when the destination multicast address has a link-local scope, then the
- // source address can also be link-local multicast.
- if src_addr.is_loopback() || src_addr.is_multicast() {
- return false;
- }
- true
- }
- // See RFC 6724 Section 2.2: Common Prefix Length
- fn common_prefix_length(dst_addr: &Ipv6Cidr, src_addr: &Ipv6Address) -> usize {
- let addr = dst_addr.address();
- let mut bits = 0;
- for (l, r) in addr.as_bytes().iter().zip(src_addr.as_bytes().iter()) {
- if l == r {
- bits += 8;
- } else {
- bits += (l ^ r).leading_zeros();
- break;
- }
- }
- bits = bits.min(dst_addr.prefix_len() as u32);
- bits as usize
- }
- // Get the first address that is a candidate address.
- let mut candidate = self
- .ip_addrs
- .iter()
- .filter_map(|a| match a {
- #[cfg(feature = "proto-ipv4")]
- IpCidr::Ipv4(_) => None,
- #[cfg(feature = "proto-ipv6")]
- IpCidr::Ipv6(a) => Some(a),
- })
- .find(|a| is_candidate_source_address(dst_addr, &a.address()))
- .unwrap();
- for addr in self.ip_addrs.iter().filter_map(|a| match a {
- #[cfg(feature = "proto-ipv4")]
- IpCidr::Ipv4(_) => None,
- #[cfg(feature = "proto-ipv6")]
- IpCidr::Ipv6(a) => Some(a),
- }) {
- if !is_candidate_source_address(dst_addr, &addr.address()) {
- continue;
- }
- // Rule 1: prefer the address that is the same as the output destination address.
- if candidate.address() != *dst_addr && addr.address() == *dst_addr {
- candidate = addr;
- }
- // Rule 2: prefer appropriate scope.
- if (candidate.address().scope() as u8) < (addr.address().scope() as u8) {
- if (candidate.address().scope() as u8) < (dst_addr.scope() as u8) {
- candidate = addr;
- }
- } else if (addr.address().scope() as u8) > (dst_addr.scope() as u8) {
- candidate = addr;
- }
- // Rule 3: avoid deprecated addresses (TODO)
- // Rule 4: prefer home addresses (TODO)
- // Rule 5: prefer outgoing interfaces (TODO)
- // Rule 5.5: prefer addresses in a prefix advertises by the next-hop (TODO).
- // Rule 6: prefer matching label (TODO)
- // Rule 7: prefer temporary addresses (TODO)
- // Rule 8: use longest matching prefix
- if common_prefix_length(candidate, dst_addr) < common_prefix_length(addr, dst_addr) {
- candidate = addr;
- }
- }
- Some(candidate.address())
- }
- /// Determine if the given `Ipv6Address` is the solicited node
- /// multicast address for a IPv6 addresses assigned to the interface.
- /// See [RFC 4291 § 2.7.1] for more details.
- ///
- /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
- pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
- self.ip_addrs.iter().any(|cidr| {
- match *cidr {
- IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOOPBACK => {
- // Take the lower order 24 bits of the IPv6 address and
- // append those bits to FF02:0:0:0:0:1:FF00::/104.
- addr.as_bytes()[14..] == cidr.address().as_bytes()[14..]
- }
- _ => false,
- }
- })
- }
- /// Get the first IPv6 address if present.
- pub fn ipv6_addr(&self) -> Option<Ipv6Address> {
- self.ip_addrs.iter().find_map(|addr| match *addr {
- IpCidr::Ipv6(cidr) => Some(cidr.address()),
- #[allow(unreachable_patterns)]
- _ => None,
- })
- }
- pub(super) fn process_ipv6<'frame>(
- &mut self,
- sockets: &mut SocketSet,
- meta: PacketMeta,
- ipv6_packet: &Ipv6Packet<&'frame [u8]>,
- ) -> Option<Packet<'frame>> {
- let ipv6_repr = check!(Ipv6Repr::parse(ipv6_packet));
- if !ipv6_repr.src_addr.is_unicast() {
- // Discard packets with non-unicast source addresses.
- net_debug!("non-unicast source address");
- return None;
- }
- 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)
- && !ipv6_repr.dst_addr.is_loopback()
- {
- 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);
- #[cfg(not(feature = "socket-raw"))]
- let handled_by_raw_socket = false;
- self.process_nxt_hdr(
- sockets,
- meta,
- ipv6_repr,
- 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.
- fn process_nxt_hdr<'frame>(
- &mut self,
- sockets: &mut SocketSet,
- meta: PacketMeta,
- ipv6_repr: Ipv6Repr,
- nxt_hdr: IpProtocol,
- handled_by_raw_socket: bool,
- ip_payload: &'frame [u8],
- ) -> Option<Packet<'frame>> {
- match nxt_hdr {
- IpProtocol::Icmpv6 => self.process_icmpv6(sockets, ipv6_repr.into(), ip_payload),
- #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
- IpProtocol::Udp => self.process_udp(
- sockets,
- meta,
- handled_by_raw_socket,
- ipv6_repr.into(),
- ip_payload,
- ),
- #[cfg(feature = "socket-tcp")]
- IpProtocol::Tcp => self.process_tcp(sockets, ipv6_repr.into(), ip_payload),
- #[cfg(feature = "socket-raw")]
- _ if handled_by_raw_socket => None,
- _ => {
- // Send back as much of the original payload as we can.
- let payload_len =
- icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
- let icmp_reply_repr = Icmpv6Repr::ParamProblem {
- reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
- // The offending packet is after the IPv6 header.
- pointer: ipv6_repr.buffer_len() as u32,
- header: ipv6_repr,
- data: &ip_payload[0..payload_len],
- };
- self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
- }
- }
- }
- pub(super) fn process_icmpv6<'frame>(
- &mut self,
- _sockets: &mut SocketSet,
- ip_repr: IpRepr,
- ip_payload: &'frame [u8],
- ) -> Option<Packet<'frame>> {
- let icmp_packet = check!(Icmpv6Packet::new_checked(ip_payload));
- let icmp_repr = check!(Icmpv6Repr::parse(
- &ip_repr.src_addr(),
- &ip_repr.dst_addr(),
- &icmp_packet,
- &self.caps.checksum,
- ));
- #[cfg(feature = "socket-icmp")]
- let mut handled_by_icmp_socket = false;
- #[cfg(feature = "socket-icmp")]
- {
- use crate::socket::icmp::Socket as IcmpSocket;
- for icmp_socket in _sockets
- .items_mut()
- .filter_map(|i| IcmpSocket::downcast_mut(&mut i.socket))
- {
- if icmp_socket.accepts(self, &ip_repr, &icmp_repr.into()) {
- icmp_socket.process(self, &ip_repr, &icmp_repr.into());
- handled_by_icmp_socket = true;
- }
- }
- }
- match icmp_repr {
- // Respond to echo requests.
- Icmpv6Repr::EchoRequest {
- ident,
- seq_no,
- data,
- } => match ip_repr {
- IpRepr::Ipv6(ipv6_repr) => {
- let icmp_reply_repr = Icmpv6Repr::EchoReply {
- ident,
- seq_no,
- data,
- };
- self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
- }
- #[allow(unreachable_patterns)]
- _ => unreachable!(),
- },
- // Ignore any echo replies.
- Icmpv6Repr::EchoReply { .. } => None,
- // Forward any NDISC packets to the ndisc packet handler
- #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
- Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit() == 0xff => match ip_repr {
- IpRepr::Ipv6(ipv6_repr) => match self.caps.medium {
- #[cfg(feature = "medium-ethernet")]
- Medium::Ethernet => self.process_ndisc(ipv6_repr, repr),
- #[cfg(feature = "medium-ieee802154")]
- Medium::Ieee802154 => self.process_ndisc(ipv6_repr, repr),
- #[cfg(feature = "medium-ip")]
- Medium::Ip => None,
- },
- #[allow(unreachable_patterns)]
- _ => unreachable!(),
- },
- // Don't report an error if a packet with unknown type
- // has been handled by an ICMP socket
- #[cfg(feature = "socket-icmp")]
- _ if handled_by_icmp_socket => None,
- // FIXME: do something correct here?
- _ => None,
- }
- }
- #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
- pub(super) fn process_ndisc<'frame>(
- &mut self,
- ip_repr: Ipv6Repr,
- repr: NdiscRepr<'frame>,
- ) -> Option<Packet<'frame>> {
- match repr {
- NdiscRepr::NeighborAdvert {
- lladdr,
- target_addr,
- flags,
- } => {
- let ip_addr = ip_repr.src_addr.into();
- if let Some(lladdr) = lladdr {
- let lladdr = check!(lladdr.parse(self.caps.medium));
- if !lladdr.is_unicast() || !target_addr.is_unicast() {
- return None;
- }
- if flags.contains(NdiscNeighborFlags::OVERRIDE)
- || !self.neighbor_cache.lookup(&ip_addr, self.now).found()
- {
- self.neighbor_cache.fill(ip_addr, lladdr, self.now)
- }
- }
- None
- }
- NdiscRepr::NeighborSolicit {
- target_addr,
- lladdr,
- ..
- } => {
- if let Some(lladdr) = lladdr {
- let lladdr = check!(lladdr.parse(self.caps.medium));
- if !lladdr.is_unicast() || !target_addr.is_unicast() {
- return None;
- }
- self.neighbor_cache
- .fill(ip_repr.src_addr.into(), lladdr, self.now);
- }
- if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
- let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
- flags: NdiscNeighborFlags::SOLICITED,
- target_addr,
- #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
- lladdr: Some(self.hardware_addr.into()),
- });
- let ip_repr = Ipv6Repr {
- src_addr: target_addr,
- dst_addr: ip_repr.src_addr,
- next_header: IpProtocol::Icmpv6,
- hop_limit: 0xff,
- payload_len: advert.buffer_len(),
- };
- Some(Packet::new_ipv6(ip_repr, IpPayload::Icmpv6(advert)))
- } else {
- None
- }
- }
- _ => None,
- }
- }
- pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>(
- &self,
- ipv6_repr: Ipv6Repr,
- icmp_repr: Icmpv6Repr<'icmp>,
- ) -> Option<Packet<'frame>> {
- let src_addr = ipv6_repr.dst_addr;
- let dst_addr = ipv6_repr.src_addr;
- let src_addr = if src_addr.is_unicast() {
- src_addr
- } else if let Some(addr) = self.get_source_address_ipv6(&dst_addr) {
- addr
- } else {
- net_debug!("no suitable source address found");
- return None;
- };
- let ipv6_reply_repr = Ipv6Repr {
- src_addr,
- dst_addr,
- next_header: IpProtocol::Icmpv6,
- payload_len: icmp_repr.buffer_len(),
- hop_limit: 64,
- };
- Some(Packet::new_ipv6(
- ipv6_reply_repr,
- IpPayload::Icmpv6(icmp_repr),
- ))
- }
- }
|