1use super::*;
2
3use crate::iface::Route;
4
5#[allow(clippy::large_enum_variant)]
9enum HopByHopResponse<'frame> {
10 Continue((IpProtocol, &'frame [u8])),
12 Discard(Option<Packet<'frame>>),
14}
15
16impl Default for HopByHopResponse<'_> {
18 fn default() -> Self {
19 Self::Discard(None)
20 }
21}
22
23impl InterfaceInner {
24 #[allow(unused)]
30 pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address {
31 assert!(!dst_addr.is_unspecified());
32
33 fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool {
35 if dst_addr.is_link_local() && !src_addr.is_link_local() {
38 return false;
39 }
40
41 if dst_addr.is_multicast()
42 && matches!(dst_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal)
43 && src_addr.is_multicast()
44 && !matches!(src_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal)
45 {
46 return false;
47 }
48
49 if src_addr.is_unspecified() || src_addr.is_multicast() {
53 return false;
54 }
55
56 true
57 }
58
59 fn common_prefix_length(dst_addr: &Ipv6Cidr, src_addr: &Ipv6Address) -> usize {
61 let addr = dst_addr.address();
62 let mut bits = 0;
63 for (l, r) in addr.octets().iter().zip(src_addr.octets().iter()) {
64 if l == r {
65 bits += 8;
66 } else {
67 bits += (l ^ r).leading_zeros();
68 break;
69 }
70 }
71
72 bits = bits.min(dst_addr.prefix_len() as u32);
73
74 bits as usize
75 }
76
77 if dst_addr.is_loopback()
80 || self
81 .ip_addrs
82 .iter()
83 .filter(|a| matches!(a, IpCidr::Ipv6(_)))
84 .count()
85 == 0
86 {
87 return Ipv6Address::LOCALHOST;
88 }
89
90 let mut candidate = self
91 .ip_addrs
92 .iter()
93 .find_map(|a| match a {
94 #[cfg(feature = "proto-ipv4")]
95 IpCidr::Ipv4(_) => None,
96 IpCidr::Ipv6(a) => Some(a),
97 })
98 .unwrap(); for addr in self.ip_addrs.iter().filter_map(|a| match a {
101 #[cfg(feature = "proto-ipv4")]
102 IpCidr::Ipv4(_) => None,
103 #[cfg(feature = "proto-ipv6")]
104 IpCidr::Ipv6(a) => Some(a),
105 }) {
106 if !is_candidate_source_address(dst_addr, &addr.address()) {
107 continue;
108 }
109
110 if candidate.address() != *dst_addr && addr.address() == *dst_addr {
112 candidate = addr;
113 }
114
115 if (candidate.address().x_multicast_scope() as u8)
117 < (addr.address().x_multicast_scope() as u8)
118 {
119 if (candidate.address().x_multicast_scope() as u8)
120 < (dst_addr.x_multicast_scope() as u8)
121 {
122 candidate = addr;
123 }
124 } else if (addr.address().x_multicast_scope() as u8)
125 > (dst_addr.x_multicast_scope() as u8)
126 {
127 candidate = addr;
128 }
129
130 if common_prefix_length(candidate, dst_addr) < common_prefix_length(addr, dst_addr) {
138 candidate = addr;
139 }
140 }
141
142 candidate.address()
143 }
144
145 pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
151 self.ip_addrs.iter().any(|cidr| {
152 match *cidr {
153 IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOCALHOST => {
154 addr.octets()[14..] == cidr.address().octets()[14..]
157 }
158 _ => false,
159 }
160 })
161 }
162
163 pub fn ipv6_addr(&self) -> Option<Ipv6Address> {
165 self.ip_addrs.iter().find_map(|addr| match *addr {
166 IpCidr::Ipv6(cidr) => Some(cidr.address()),
167 #[allow(unreachable_patterns)]
168 _ => None,
169 })
170 }
171
172 fn link_local_ipv6_address(&self) -> Option<Ipv6Address> {
174 self.ip_addrs.iter().find_map(|addr| match *addr {
175 #[cfg(feature = "proto-ipv4")]
176 IpCidr::Ipv4(_) => None,
177 #[cfg(feature = "proto-ipv6")]
178 IpCidr::Ipv6(cidr) => {
179 let addr = cidr.address();
180 if addr.is_link_local() {
181 Some(addr)
182 } else {
183 None
184 }
185 }
186 })
187 }
188
189 pub(super) fn process_ipv6<'frame>(
190 &mut self,
191 sockets: &mut SocketSet,
192 meta: PacketMeta,
193 source_hardware_addr: HardwareAddress,
194 ipv6_packet: &Ipv6Packet<&'frame [u8]>,
195 ) -> Option<Packet<'frame>> {
196 let ipv6_repr = check!(Ipv6Repr::parse(ipv6_packet));
197
198 if !ipv6_repr.src_addr.x_is_unicast() {
199 net_debug!("non-unicast source address");
201 return None;
202 }
203
204 let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop {
205 match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) {
206 HopByHopResponse::Discard(e) => return e,
207 HopByHopResponse::Continue(next) => next,
208 }
209 } else {
210 (ipv6_repr.next_header, ipv6_packet.payload())
211 };
212
213 if !self.has_ip_addr(ipv6_repr.dst_addr)
214 && !self.has_multicast_group(ipv6_repr.dst_addr)
215 && !ipv6_repr.dst_addr.is_loopback()
216 {
217 if !ipv6_repr.dst_addr.x_is_unicast() {
218 net_trace!(
219 "Rejecting IPv6 packet; {} is not a unicast address",
220 ipv6_repr.dst_addr
221 );
222 return None;
223 }
224
225 if self
226 .routes
227 .lookup(&IpAddress::Ipv6(ipv6_repr.dst_addr), self.now)
228 .is_none_or(|router_addr| !self.has_ip_addr(router_addr))
229 {
230 net_trace!("Rejecting IPv6 packet; no matching routes");
231
232 return None;
233 }
234
235 net_trace!("Rejecting IPv6 packet; no assigned address");
236 return None;
237 }
238
239 #[cfg(feature = "socket-raw")]
240 let handled_by_raw_socket = self.raw_socket_filter(sockets, &ipv6_repr.into(), ip_payload);
241 #[cfg(not(feature = "socket-raw"))]
242 let handled_by_raw_socket = false;
243
244 #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
245 if ipv6_repr.dst_addr.x_is_unicast() {
246 self.neighbor_cache.reset_expiry_if_existing(
247 IpAddress::Ipv6(ipv6_repr.src_addr),
248 source_hardware_addr,
249 self.now,
250 );
251 }
252
253 self.process_nxt_hdr(
254 sockets,
255 meta,
256 ipv6_repr,
257 next_header,
258 handled_by_raw_socket,
259 ip_payload,
260 )
261 }
262
263 fn process_hopbyhop<'frame>(
264 &mut self,
265 ipv6_repr: Ipv6Repr,
266 ip_payload: &'frame [u8],
267 ) -> HopByHopResponse<'frame> {
268 let param_problem = || {
269 let payload_len =
270 icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
271 self.icmpv6_reply(
272 ipv6_repr,
273 Icmpv6Repr::ParamProblem {
274 reason: Icmpv6ParamProblem::UnrecognizedOption,
275 pointer: ipv6_repr.buffer_len() as u32,
276 header: ipv6_repr,
277 data: &ip_payload[0..payload_len],
278 },
279 )
280 };
281
282 let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload));
283 let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr));
284 let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data));
285 let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr));
286
287 for opt_repr in &hbh_repr.options {
288 match opt_repr {
289 Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) | Ipv6OptionRepr::RouterAlert(_) => {
290 }
291 #[cfg(feature = "proto-rpl")]
292 Ipv6OptionRepr::Rpl(_) => {}
293
294 Ipv6OptionRepr::Unknown { type_, .. } => {
295 match Ipv6OptionFailureType::from(*type_) {
296 Ipv6OptionFailureType::Skip => (),
297 Ipv6OptionFailureType::Discard => {
298 return HopByHopResponse::Discard(None);
299 }
300 Ipv6OptionFailureType::DiscardSendAll => {
301 return HopByHopResponse::Discard(param_problem());
302 }
303 Ipv6OptionFailureType::DiscardSendUnicast => {
304 if !ipv6_repr.dst_addr.is_multicast() {
305 return HopByHopResponse::Discard(param_problem());
306 } else {
307 return HopByHopResponse::Discard(None);
308 }
309 }
310 }
311 }
312 }
313 }
314
315 HopByHopResponse::Continue((
316 ext_repr.next_header,
317 &ip_payload[ext_repr.header_len() + ext_repr.data.len()..],
318 ))
319 }
320
321 fn process_nxt_hdr<'frame>(
324 &mut self,
325 sockets: &mut SocketSet,
326 meta: PacketMeta,
327 ipv6_repr: Ipv6Repr,
328 nxt_hdr: IpProtocol,
329 handled_by_raw_socket: bool,
330 ip_payload: &'frame [u8],
331 ) -> Option<Packet<'frame>> {
332 match nxt_hdr {
333 IpProtocol::Icmpv6 => self.process_icmpv6(sockets, ipv6_repr, ip_payload),
334
335 #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
336 IpProtocol::Udp => self.process_udp(
337 sockets,
338 meta,
339 handled_by_raw_socket,
340 ipv6_repr.into(),
341 ip_payload,
342 ),
343
344 #[cfg(feature = "socket-tcp")]
345 IpProtocol::Tcp => {
346 self.process_tcp(sockets, handled_by_raw_socket, ipv6_repr.into(), ip_payload)
347 }
348
349 #[cfg(feature = "socket-raw")]
350 _ if handled_by_raw_socket => None,
351
352 _ => {
353 let payload_len =
355 icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
356 let icmp_reply_repr = Icmpv6Repr::ParamProblem {
357 reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
358 pointer: ipv6_repr.buffer_len() as u32,
360 header: ipv6_repr,
361 data: &ip_payload[0..payload_len],
362 };
363 self.icmpv6_reply(ipv6_repr, icmp_reply_repr)
364 }
365 }
366 }
367
368 pub(super) fn process_icmpv6<'frame>(
369 &mut self,
370 _sockets: &mut SocketSet,
371 ip_repr: Ipv6Repr,
372 ip_payload: &'frame [u8],
373 ) -> Option<Packet<'frame>> {
374 let icmp_packet = check!(Icmpv6Packet::new_checked(ip_payload));
375 let icmp_repr = check!(Icmpv6Repr::parse(
376 &ip_repr.src_addr,
377 &ip_repr.dst_addr,
378 &icmp_packet,
379 &self.caps.checksum,
380 ));
381
382 #[cfg(feature = "socket-icmp")]
383 let mut handled_by_icmp_socket = false;
384
385 #[cfg(feature = "socket-icmp")]
386 {
387 use crate::socket::icmp::Socket as IcmpSocket;
388 for icmp_socket in _sockets
389 .items_mut()
390 .filter_map(|i| IcmpSocket::downcast_mut(&mut i.socket))
391 {
392 if icmp_socket.accepts_v6(self, &ip_repr, &icmp_repr) {
393 icmp_socket.process_v6(self, &ip_repr, &icmp_repr);
394 handled_by_icmp_socket = true;
395 }
396 }
397 }
398
399 match icmp_repr {
400 #[cfg(feature = "auto-icmp-echo-reply")]
402 Icmpv6Repr::EchoRequest {
403 ident,
404 seq_no,
405 data,
406 } => {
407 let icmp_reply_repr = Icmpv6Repr::EchoReply {
408 ident,
409 seq_no,
410 data,
411 };
412 self.icmpv6_reply(ip_repr, icmp_reply_repr)
413 }
414
415 Icmpv6Repr::EchoReply { .. } => None,
417
418 #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
420 Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit == 0xff => match self.caps.medium {
421 #[cfg(feature = "medium-ethernet")]
422 Medium::Ethernet => self.process_ndisc(ip_repr, repr),
423 #[cfg(feature = "medium-ieee802154")]
424 Medium::Ieee802154 => self.process_ndisc(ip_repr, repr),
425 #[cfg(feature = "medium-ip")]
426 Medium::Ip => None,
427 },
428 #[cfg(feature = "multicast")]
429 Icmpv6Repr::Mld(repr) => match repr {
430 MldRepr::Query { .. }
432 if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() =>
433 {
434 self.process_mldv2(ip_repr, repr)
435 }
436 _ => None,
437 },
438
439 #[cfg(feature = "socket-icmp")]
442 _ if handled_by_icmp_socket => None,
443
444 _ => None,
447 }
448 }
449
450 #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
451 pub(super) fn process_ndisc<'frame>(
452 &mut self,
453 ip_repr: Ipv6Repr,
454 repr: NdiscRepr<'frame>,
455 ) -> Option<Packet<'frame>> {
456 match repr {
457 NdiscRepr::NeighborAdvert {
458 lladdr,
459 target_addr,
460 flags,
461 } => {
462 let ip_addr = ip_repr.src_addr.into();
463 if let Some(lladdr) = lladdr {
464 let lladdr = check!(lladdr.parse(self.caps.medium));
465 if !lladdr.is_unicast() || !target_addr.x_is_unicast() {
466 return None;
467 }
468 if flags.contains(NdiscNeighborFlags::OVERRIDE)
469 || !self.neighbor_cache.lookup(&ip_addr, self.now).found()
470 {
471 self.neighbor_cache.fill(ip_addr, lladdr, self.now)
472 }
473 }
474 None
475 }
476 NdiscRepr::NeighborSolicit {
477 target_addr,
478 lladdr,
479 ..
480 } => {
481 if let Some(lladdr) = lladdr {
482 let lladdr = check!(lladdr.parse(self.caps.medium));
483 if !lladdr.is_unicast() || !target_addr.x_is_unicast() {
484 return None;
485 }
486 self.neighbor_cache
487 .fill(ip_repr.src_addr.into(), lladdr, self.now);
488 }
489
490 if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
491 let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
492 flags: NdiscNeighborFlags::SOLICITED,
493 target_addr,
494 #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
495 lladdr: Some(self.hardware_addr.into()),
496 });
497 let ip_repr = Ipv6Repr {
498 src_addr: target_addr,
499 dst_addr: ip_repr.src_addr,
500 next_header: IpProtocol::Icmpv6,
501 hop_limit: 0xff,
502 payload_len: advert.buffer_len(),
503 };
504 Some(Packet::new_ipv6(ip_repr, IpPayload::Icmpv6(advert)))
505 } else {
506 None
507 }
508 }
509 #[cfg(feature = "proto-ipv6-slaac")]
510 NdiscRepr::RouterAdvert {
511 hop_limit: _,
512 flags: _,
513 router_lifetime,
514 reachable_time: _,
515 retrans_time: _,
516 lladdr: _,
517 mtu: _,
518 prefix_info,
519 } if self.slaac_enabled => {
520 if ip_repr.src_addr.is_link_local()
521 && (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES
522 || ip_repr.dst_addr.is_link_local())
523 && ip_repr.hop_limit == 255
524 {
525 self.slaac.process_advertisement(
526 &ip_repr.src_addr,
527 router_lifetime,
528 prefix_info,
529 self.now,
530 )
531 }
532 None
533 }
534 _ => None,
535 }
536 }
537
538 pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>(
539 &self,
540 ipv6_repr: Ipv6Repr,
541 icmp_repr: Icmpv6Repr<'icmp>,
542 ) -> Option<Packet<'frame>> {
543 let src_addr = ipv6_repr.dst_addr;
544 let dst_addr = ipv6_repr.src_addr;
545
546 let src_addr = if src_addr.x_is_unicast() {
547 src_addr
548 } else {
549 self.get_source_address_ipv6(&dst_addr)
550 };
551
552 let ipv6_reply_repr = Ipv6Repr {
553 src_addr,
554 dst_addr,
555 next_header: IpProtocol::Icmpv6,
556 payload_len: icmp_repr.buffer_len(),
557 hop_limit: 64,
558 };
559 Some(Packet::new_ipv6(
560 ipv6_reply_repr,
561 IpPayload::Icmpv6(icmp_repr),
562 ))
563 }
564
565 pub(super) fn mldv2_report_packet<'any>(
566 &self,
567 records: &'any [MldAddressRecordRepr<'any>],
568 ) -> Option<Packet<'any>> {
569 let src_addr = self
573 .link_local_ipv6_address()
574 .unwrap_or(Ipv6Address::UNSPECIFIED);
575
576 let dst_addr = IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS;
579
580 let dummy_ext_hdr = Ipv6ExtHeaderRepr {
583 next_header: IpProtocol::Unknown(0),
584 length: 0,
585 data: &[],
586 };
587
588 let mut hbh_repr = Ipv6HopByHopRepr::mldv2_router_alert();
589 hbh_repr.push_padn_option(0);
590
591 let mld_repr = MldRepr::ReportRecordReprs(records);
592 let records_len = records
593 .iter()
594 .map(MldAddressRecordRepr::buffer_len)
595 .sum::<usize>();
596
597 Some(Packet::new_ipv6(
599 Ipv6Repr {
600 src_addr,
601 dst_addr,
602 next_header: IpProtocol::HopByHop,
603 payload_len: dummy_ext_hdr.header_len()
604 + hbh_repr.buffer_len()
605 + mld_repr.buffer_len()
606 + records_len,
607 hop_limit: 1,
608 },
609 IpPayload::HopByHopIcmpv6(hbh_repr, Icmpv6Repr::Mld(mld_repr)),
610 ))
611 }
612}
613
614impl Interface {
615 #[cfg(feature = "proto-ipv6-slaac")]
617 pub(super) fn sync_slaac_state(&mut self, timestamp: Instant) {
618 let required_addresses: Vec<_, IFACE_MAX_PREFIX_COUNT> = self
619 .inner
620 .slaac
621 .prefix()
622 .iter()
623 .filter_map(|(prefix, prefixinfo)| {
624 if prefixinfo.is_valid(timestamp) {
625 Ipv6Cidr::from_link_prefix(prefix, self.inner.hardware_addr())
626 } else {
627 None
628 }
629 })
630 .collect();
631 let removed_addresses: Vec<_, IFACE_MAX_PREFIX_COUNT> = self
632 .inner
633 .slaac
634 .prefix()
635 .iter()
636 .filter_map(|(prefix, prefixinfo)| {
637 if !prefixinfo.is_valid(timestamp) {
638 Ipv6Cidr::from_link_prefix(prefix, self.inner.hardware_addr())
639 } else {
640 None
641 }
642 })
643 .collect();
644
645 self.update_ip_addrs(|addresses| {
646 for address in required_addresses {
647 if !addresses.contains(&IpCidr::Ipv6(address)) {
648 let _ = addresses.push(IpCidr::Ipv6(address));
649 }
650 }
651 addresses.retain(|address| {
652 if let IpCidr::Ipv6(address) = address {
653 !removed_addresses.contains(address)
654 } else {
655 true
656 }
657 });
658 });
659
660 {
661 let required_routes = self
662 .inner
663 .slaac
664 .routes()
665 .into_iter()
666 .filter(|required| required.is_valid(timestamp));
667
668 let removed_routes = self
669 .inner
670 .slaac
671 .routes()
672 .into_iter()
673 .filter(|r| !r.is_valid(timestamp));
674
675 self.inner.routes.update(|routes| {
676 routes.retain(|r| match (&r.cidr, &r.via_router) {
677 (IpCidr::Ipv6(cidr), IpAddress::Ipv6(via_router)) => !removed_routes
678 .clone()
679 .any(|f| f.same_route(cidr, via_router)),
680 _ => true,
681 });
682
683 for route in required_routes {
684 if routes.iter().all(|r| match (&r.cidr, &r.via_router) {
685 (IpCidr::Ipv6(cidr), IpAddress::Ipv6(via_router)) => {
686 !route.same_route(cidr, via_router)
687 }
688 _ => false,
689 }) {
690 let _ = routes.push(Route {
691 cidr: route.cidr.into(),
692 via_router: route.via_router.into(),
693 preferred_until: None,
694 expires_at: None,
695 });
696 }
697 }
698 });
699 }
700 self.inner.slaac_updated = timestamp;
701 self.inner.slaac.update_slaac_state(timestamp);
702 }
703
704 #[cfg(feature = "proto-ipv6-slaac")]
706 pub fn slaac_updated_at(&self) -> Instant {
707 self.inner.slaac_updated
708 }
709
710 #[cfg(feature = "proto-ipv6-slaac")]
712 pub(super) fn ndisc_rs_egress(&mut self, device: &mut (impl Device + ?Sized)) {
713 if !self.inner.slaac.rs_required(self.inner.now) {
714 return;
715 }
716 let rs_repr = Icmpv6Repr::Ndisc(NdiscRepr::RouterSolicit {
717 lladdr: Some(self.hardware_addr().into()),
718 });
719 let ipv6_repr = Ipv6Repr {
720 src_addr: self.inner.link_local_ipv6_address().unwrap(),
721 dst_addr: IPV6_LINK_LOCAL_ALL_ROUTERS,
722 next_header: IpProtocol::Icmpv6,
723 payload_len: rs_repr.buffer_len(),
724 hop_limit: 255,
725 };
726 let packet = Packet::new_ipv6(ipv6_repr, IpPayload::Icmpv6(rs_repr));
727 let Some(tx_token) = device.transmit(self.inner.now) else {
728 return;
729 };
730 self.inner
732 .dispatch_ip(
733 tx_token,
734 PacketMeta::default(),
735 packet,
736 &mut self.fragmenter,
737 )
738 .unwrap();
739 self.inner.slaac.rs_sent(self.inner.now);
740 }
741}