Skip to content

Commit 01e7775

Browse files
committed
interface: Hook up SLAAC to interface
1 parent 1264453 commit 01e7775

File tree

5 files changed

+472
-3
lines changed

5 files changed

+472
-3
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ The ICMPv6 protocol is supported, and ICMP sockets are available.
103103
#### NDISC
104104

105105
* Neighbor Advertisement messages are generated in response to Neighbor Solicitations.
106-
* Router Advertisement messages are **not** generated or read.
107-
* Router Solicitation messages are **not** generated or read.
106+
* Router Advertisement messages are read, but **not** generated.
107+
* Router Solicitation messages are generated, but **not** read.
108108
* Redirected Header messages are **not** generated or read.
109109

110110
### UDP layer

src/iface/interface/ipv6.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use super::*;
22

3+
use crate::iface::Route;
4+
35
/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMP
46
/// parameter problem message needs to be transmitted to the source of the address. In other cases,
57
/// the processing of the IP packet can continue.
@@ -502,6 +504,30 @@ impl InterfaceInner {
502504
None
503505
}
504506
}
507+
NdiscRepr::RouterAdvert {
508+
hop_limit: _,
509+
flags: _,
510+
router_lifetime,
511+
reachable_time: _,
512+
retrans_time: _,
513+
lladdr: _,
514+
mtu: _,
515+
prefix_info,
516+
} if self.slaac_enabled => {
517+
if ip_repr.src_addr.is_link_local()
518+
&& (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES
519+
|| ip_repr.dst_addr.is_link_local())
520+
&& ip_repr.hop_limit == 255
521+
{
522+
self.slaac.process_advertisement(
523+
&ip_repr.src_addr,
524+
router_lifetime,
525+
prefix_info,
526+
self.now,
527+
)
528+
}
529+
None
530+
}
505531
_ => None,
506532
}
507533
}
@@ -581,3 +607,132 @@ impl InterfaceInner {
581607
))
582608
}
583609
}
610+
611+
impl Interface {
612+
/// Synchronize the slaac address and router state with the interface state.
613+
#[cfg(all(
614+
feature = "proto-ipv6",
615+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
616+
))]
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+
701+
self.inner.slaac.update_slaac_state(timestamp);
702+
}
703+
704+
/// Emit a router solicitation when required by the interface's slaac state machine.
705+
#[cfg(all(
706+
feature = "proto-ipv6",
707+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
708+
))]
709+
pub(super) fn ndisc_rs_egress(&mut self, device: &mut (impl Device + ?Sized)) {
710+
if !self.inner.slaac.rs_required(self.inner.now) {
711+
return;
712+
}
713+
let rs_repr = Icmpv6Repr::Ndisc(NdiscRepr::RouterSolicit {
714+
lladdr: Some(self.hardware_addr().into()),
715+
});
716+
let ipv6_repr = Ipv6Repr {
717+
src_addr: self.inner.link_local_ipv6_address().unwrap(),
718+
dst_addr: IPV6_LINK_LOCAL_ALL_ROUTERS,
719+
next_header: IpProtocol::Icmpv6,
720+
payload_len: rs_repr.buffer_len(),
721+
hop_limit: 255,
722+
};
723+
let packet = Packet::new_ipv6(ipv6_repr, IpPayload::Icmpv6(rs_repr));
724+
let Some(tx_token) = device.transmit(self.inner.now) else {
725+
return;
726+
};
727+
// NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
728+
self.inner
729+
.dispatch_ip(
730+
tx_token,
731+
PacketMeta::default(),
732+
packet,
733+
&mut self.fragmenter,
734+
)
735+
.unwrap();
736+
self.inner.slaac.rs_sent(self.inner.now);
737+
}
738+
}

src/iface/interface/mod.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@ use super::fragmentation::{Fragmenter, FragmentsBuffer};
3838
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
3939
use super::neighbor::{Answer as NeighborAnswer, Cache as NeighborCache};
4040
use super::socket_set::SocketSet;
41-
use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT};
41+
use crate::config::{
42+
IFACE_MAX_ADDR_COUNT, IFACE_MAX_PREFIX_COUNT, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT,
43+
};
4244
use crate::iface::Routes;
45+
#[cfg(all(
46+
feature = "proto-ipv6",
47+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
48+
))]
49+
use crate::iface::Slaac;
4350
use crate::phy::PacketMeta;
4451
use crate::phy::{ChecksumCapabilities, Device, DeviceCapabilities, Medium, RxToken, TxToken};
4552
use crate::rand::Rand;
@@ -142,6 +149,16 @@ pub struct InterfaceInner {
142149
tag: u16,
143150
ip_addrs: Vec<IpCidr, IFACE_MAX_ADDR_COUNT>,
144151
any_ip: bool,
152+
#[cfg(all(
153+
feature = "proto-ipv6",
154+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
155+
))]
156+
slaac_enabled: bool,
157+
#[cfg(all(
158+
feature = "proto-ipv6",
159+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
160+
))]
161+
slaac: Slaac,
145162
routes: Routes,
146163
#[cfg(feature = "multicast")]
147164
multicast: multicast::State,
@@ -169,6 +186,10 @@ pub struct Config {
169186
/// **NOTE**: we use the same PAN ID for destination and source.
170187
#[cfg(feature = "medium-ieee802154")]
171188
pub pan_id: Option<Ieee802154Pan>,
189+
190+
/// Enable stateless address autoconfiguration on the interface.
191+
#[cfg(feature = "proto-ipv6")]
192+
pub slaac: bool,
172193
}
173194

174195
impl Config {
@@ -178,6 +199,8 @@ impl Config {
178199
hardware_addr,
179200
#[cfg(feature = "medium-ieee802154")]
180201
pan_id: None,
202+
#[cfg(feature = "proto-ipv6")]
203+
slaac: false,
181204
}
182205
}
183206
}
@@ -262,6 +285,16 @@ impl Interface {
262285
ipv4_id,
263286
#[cfg(feature = "proto-sixlowpan")]
264287
sixlowpan_address_context: Vec::new(),
288+
#[cfg(all(
289+
feature = "proto-ipv6",
290+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
291+
))]
292+
slaac_enabled: config.slaac,
293+
#[cfg(all(
294+
feature = "proto-ipv6",
295+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
296+
))]
297+
slaac: Slaac::new(),
265298
rand,
266299
},
267300
}
@@ -491,6 +524,14 @@ impl Interface {
491524
}
492525
}
493526

527+
#[cfg(all(
528+
feature = "proto-ipv6",
529+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
530+
))]
531+
if self.inner.slaac_enabled {
532+
self.ndisc_rs_egress(device);
533+
}
534+
494535
#[cfg(feature = "multicast")]
495536
self.multicast_egress(device);
496537

@@ -526,6 +567,14 @@ impl Interface {
526567

527568
#[cfg(feature = "_proto-fragmentation")]
528569
self.fragments.assembler.remove_expired(timestamp);
570+
571+
#[cfg(all(
572+
feature = "proto-ipv6",
573+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
574+
))]
575+
if self.inner.slaac.sync_required(timestamp) {
576+
self.sync_slaac_state(timestamp)
577+
}
529578
}
530579

531580
/// Return a _soft deadline_ for calling [poll] the next time.
@@ -546,6 +595,15 @@ impl Interface {
546595

547596
let inner = &mut self.inner;
548597

598+
let other_polls = [
599+
#[cfg(all(
600+
feature = "proto-ipv6",
601+
any(feature = "medium-ethernet", feature = "medium-ieee802154")
602+
))]
603+
inner.slaac.poll_at(timestamp),
604+
None,
605+
];
606+
549607
sockets
550608
.items()
551609
.filter_map(move |item| {
@@ -559,6 +617,7 @@ impl Interface {
559617
PollAt::Now => Some(Instant::from_millis(0)),
560618
}
561619
})
620+
.chain(other_polls.into_iter().flatten())
562621
.min()
563622
}
564623

0 commit comments

Comments
 (0)