1use core::num::NonZeroU8;
10use core::time::Duration;
11
12use assert_matches::assert_matches;
13use derivative::Derivative;
14use net_types::UnicastAddr;
15use net_types::ip::Ipv6Addr;
16use netstack3_base::{
17 AnyDevice, CoreTimerContext, DeviceIdContext, HandleableTimer, NetworkPartialSerializer,
18 NetworkSerializer, RngContext, StrongDeviceIdentifier as _, TimerBindingsTypes, TimerContext,
19 TimerHandler, WeakDeviceIdentifier,
20};
21use packet::{EitherSerializer, EmptyBuf, InnerPacketBuilder as _};
22use packet_formats::icmp::ndp::options::NdpOptionBuilder;
23use packet_formats::icmp::ndp::{OptionSequenceBuilder, RouterSolicitation};
24use rand::RngExt as _;
25
26use crate::internal::base::IpSendFrameError;
27use crate::internal::device::Ipv6LinkLayerAddr;
28
29pub const MAX_RTR_SOLICITATION_DELAY: Duration = Duration::from_secs(1);
44
45pub const RTR_SOLICITATION_INTERVAL: Duration = Duration::from_secs(4);
50
51#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
53pub struct RsTimerId<D: WeakDeviceIdentifier> {
54 device_id: D,
55}
56
57impl<D: WeakDeviceIdentifier> RsTimerId<D> {
58 pub(super) fn device_id(&self) -> &D {
59 &self.device_id
60 }
61
62 #[cfg(any(test, feature = "testutils"))]
64 pub fn new(device_id: D) -> Self {
65 Self { device_id }
66 }
67}
68
69#[derive(Derivative)]
71#[derivative(Default(bound = ""))]
72pub struct RsState<BT: RsBindingsTypes> {
73 solicitations_sent: u8,
74 timer: Option<BT::Timer>,
75}
76
77pub trait RsContext<BC: RsBindingsTypes>:
79 DeviceIdContext<AnyDevice> + CoreTimerContext<RsTimerId<Self::WeakDeviceId>, BC>
80{
81 type LinkLayerAddr: Ipv6LinkLayerAddr;
83
84 fn with_rs_state_mut_and_max<O, F: FnOnce(&mut RsState<BC>, Option<NonZeroU8>) -> O>(
87 &mut self,
88 device_id: &Self::DeviceId,
89 cb: F,
90 ) -> O;
91
92 fn with_rs_state_mut<O, F: FnOnce(&mut RsState<BC>) -> O>(
95 &mut self,
96 device_id: &Self::DeviceId,
97 cb: F,
98 ) -> O {
99 self.with_rs_state_mut_and_max(device_id, |state, _max| cb(state))
100 }
101
102 fn get_link_layer_addr(&mut self, device_id: &Self::DeviceId) -> Option<Self::LinkLayerAddr>;
105
106 fn send_rs_packet<
111 S: NetworkSerializer<Buffer = EmptyBuf> + NetworkPartialSerializer,
112 F: FnOnce(Option<UnicastAddr<Ipv6Addr>>) -> S,
113 >(
114 &mut self,
115 bindings_ctx: &mut BC,
116 device_id: &Self::DeviceId,
117 message: RouterSolicitation,
118 body: F,
119 ) -> Result<(), IpSendFrameError<S>>;
120}
121
122pub trait RsBindingsTypes: TimerBindingsTypes {}
124impl<BT> RsBindingsTypes for BT where BT: TimerBindingsTypes {}
125
126pub trait RsBindingsContext: RngContext + TimerContext {}
128impl<BC> RsBindingsContext for BC where BC: RngContext + TimerContext {}
129
130pub trait RsHandler<BC: RsBindingsTypes>:
132 DeviceIdContext<AnyDevice> + TimerHandler<BC, RsTimerId<Self::WeakDeviceId>>
133{
134 fn start_router_solicitation(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
136
137 fn stop_router_solicitation(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
141}
142
143impl<BC: RsBindingsContext, CC: RsContext<BC>> RsHandler<BC> for CC {
144 fn start_router_solicitation(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
145 self.with_rs_state_mut_and_max(device_id, |state, max| {
146 let RsState { solicitations_sent, timer } = state;
147 *solicitations_sent = 0;
148
149 assert_matches!(timer, None);
152
153 match max {
154 None => {}
155 Some(_) => {
156 let delay =
161 bindings_ctx.rng().random_range(Duration::ZERO..MAX_RTR_SOLICITATION_DELAY);
162
163 let timer = timer.insert(CC::new_timer(
164 bindings_ctx,
165 RsTimerId { device_id: device_id.downgrade() },
166 ));
167 assert_eq!(bindings_ctx.schedule_timer(delay, timer), None);
168 }
169 }
170 });
171 }
172
173 fn stop_router_solicitation(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
174 self.with_rs_state_mut(device_id, |state| {
175 if let Some(mut timer) = state.timer.take() {
177 let _: Option<BC::Instant> = bindings_ctx.cancel_timer(&mut timer);
178 }
179 });
180 }
181}
182
183impl<BC: RsBindingsContext, CC: RsContext<BC>> HandleableTimer<CC, BC>
184 for RsTimerId<CC::WeakDeviceId>
185{
186 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, timer: BC::UniqueTimerId) {
187 let Self { device_id } = self;
188 if let Some(device_id) = device_id.upgrade() {
189 do_router_solicitation(core_ctx, bindings_ctx, &device_id, timer)
190 }
191 }
192}
193
194fn do_router_solicitation<BC: RsBindingsContext, CC: RsContext<BC>>(
196 core_ctx: &mut CC,
197 bindings_ctx: &mut BC,
198 device_id: &CC::DeviceId,
199 timer_id: BC::UniqueTimerId,
200) {
201 let send_rs = core_ctx.with_rs_state_mut_and_max(
202 device_id,
203 |RsState { solicitations_sent, timer }, max| {
204 let Some(timer) = timer.as_mut() else {
205 return false;
207 };
208 if bindings_ctx.unique_timer_id(timer) != timer_id {
209 return false;
212 }
213
214 let max_solicitations = max.map_or(0, NonZeroU8::get);
215 if *solicitations_sent >= max_solicitations {
216 return false;
217 }
218
219 *solicitations_sent = solicitations_sent.saturating_add(1);
220
221 if *solicitations_sent < max_solicitations {
223 assert_eq!(bindings_ctx.schedule_timer(RTR_SOLICITATION_INTERVAL, timer), None);
224 }
225
226 true
227 },
228 );
229
230 if !send_rs {
231 return;
232 }
233
234 let src_ll = core_ctx.get_link_layer_addr(device_id);
235
236 let _: Result<(), _> =
239 core_ctx.send_rs_packet(bindings_ctx, device_id, RouterSolicitation::default(), |src_ip| {
240 src_ip.map_or(EitherSerializer::A(EmptyBuf), |UnicastAddr { .. }| {
250 EitherSerializer::B(
251 OptionSequenceBuilder::new(
252 src_ll
253 .as_ref()
254 .map(Ipv6LinkLayerAddr::as_bytes)
255 .into_iter()
256 .map(NdpOptionBuilder::SourceLinkLayerAddress),
257 )
258 .into_serializer(),
259 )
260 })
261 });
262}
263
264#[cfg(test)]
265mod tests {
266 use alloc::vec;
267 use alloc::vec::Vec;
268
269 use net_declare::net_ip_v6;
270 use netstack3_base::testutil::{
271 FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeTimerCtxExt as _, FakeWeakDeviceId,
272 };
273 use netstack3_base::{CtxPair, InstantContext as _, SendFrameContext as _};
274 use packet_formats::icmp::ndp::Options;
275 use packet_formats::icmp::ndp::options::NdpOption;
276 use test_case::test_case;
277
278 use super::*;
279
280 struct FakeRsContext {
281 max_router_solicitations: Option<NonZeroU8>,
282 rs_state: RsState<FakeBindingsCtxImpl>,
283 source_address: Option<UnicastAddr<Ipv6Addr>>,
284 link_layer_bytes: Option<Vec<u8>>,
285 }
286
287 #[derive(Debug, PartialEq)]
288 struct RsMessageMeta {
289 message: RouterSolicitation,
290 }
291
292 type FakeCoreCtxImpl = FakeCoreCtx<FakeRsContext, RsMessageMeta, FakeDeviceId>;
293 type FakeBindingsCtxImpl =
294 FakeBindingsCtx<RsTimerId<FakeWeakDeviceId<FakeDeviceId>>, (), (), ()>;
295
296 impl CoreTimerContext<RsTimerId<FakeWeakDeviceId<FakeDeviceId>>, FakeBindingsCtxImpl>
297 for FakeCoreCtxImpl
298 {
299 fn convert_timer(
300 dispatch_id: RsTimerId<FakeWeakDeviceId<FakeDeviceId>>,
301 ) -> <FakeBindingsCtxImpl as TimerBindingsTypes>::DispatchId {
302 dispatch_id
303 }
304 }
305
306 impl Ipv6LinkLayerAddr for Vec<u8> {
307 fn as_bytes(&self) -> &[u8] {
308 &self
309 }
310
311 fn eui64_iid(&self) -> [u8; 8] {
312 unimplemented!()
313 }
314 }
315
316 impl RsContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
317 type LinkLayerAddr = Vec<u8>;
318
319 fn with_rs_state_mut_and_max<
320 O,
321 F: FnOnce(&mut RsState<FakeBindingsCtxImpl>, Option<NonZeroU8>) -> O,
322 >(
323 &mut self,
324 &FakeDeviceId: &FakeDeviceId,
325 cb: F,
326 ) -> O {
327 let FakeRsContext { max_router_solicitations, rs_state, .. } = &mut self.state;
328 cb(rs_state, *max_router_solicitations)
329 }
330
331 fn get_link_layer_addr(&mut self, &FakeDeviceId: &FakeDeviceId) -> Option<Vec<u8>> {
332 let FakeRsContext { link_layer_bytes, .. } = &self.state;
333 link_layer_bytes.clone()
334 }
335
336 fn send_rs_packet<
337 S: NetworkSerializer<Buffer = EmptyBuf>,
338 F: FnOnce(Option<UnicastAddr<Ipv6Addr>>) -> S,
339 >(
340 &mut self,
341 bindings_ctx: &mut FakeBindingsCtxImpl,
342 &FakeDeviceId: &FakeDeviceId,
343 message: RouterSolicitation,
344 body: F,
345 ) -> Result<(), IpSendFrameError<S>> {
346 let FakeRsContext { source_address, .. } = &self.state;
347 self.send_frame(bindings_ctx, RsMessageMeta { message }, body(*source_address))
348 .map_err(|e| e.err_into())
349 }
350 }
351
352 const RS_TIMER_ID: RsTimerId<FakeWeakDeviceId<FakeDeviceId>> =
353 RsTimerId { device_id: FakeWeakDeviceId(FakeDeviceId) };
354
355 #[test]
356 fn stop_router_solicitation() {
357 let CtxPair { mut core_ctx, mut bindings_ctx } =
358 CtxPair::with_core_ctx(FakeCoreCtxImpl::with_state(FakeRsContext {
359 max_router_solicitations: NonZeroU8::new(1),
360 rs_state: Default::default(),
361 source_address: None,
362 link_layer_bytes: None,
363 }));
364 RsHandler::start_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
365
366 let now = bindings_ctx.now();
367 bindings_ctx
368 .timers
369 .assert_timers_installed_range([(RS_TIMER_ID, now..=now + MAX_RTR_SOLICITATION_DELAY)]);
370
371 RsHandler::stop_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
372 bindings_ctx.timers.assert_no_timers_installed();
373
374 assert_eq!(core_ctx.frames(), &[][..]);
375 }
376
377 const SOURCE_ADDRESS: UnicastAddr<Ipv6Addr> =
378 unsafe { UnicastAddr::new_unchecked(net_ip_v6!("fe80::1")) };
379
380 #[test_case(0, None, None, None; "disabled")]
381 #[test_case(1, None, None, None; "once_without_source_address_or_link_layer_option")]
382 #[test_case(
383 1,
384 Some(SOURCE_ADDRESS),
385 None,
386 None; "once_with_source_address_and_without_link_layer_option")]
387 #[test_case(
388 1,
389 None,
390 Some(vec![1, 2, 3, 4, 5, 6]),
391 None; "once_without_source_address_and_with_mac_address_source_link_layer_option")]
392 #[test_case(
393 1,
394 Some(SOURCE_ADDRESS),
395 Some(vec![1, 2, 3, 4, 5, 6]),
396 Some(&[1, 2, 3, 4, 5, 6]); "once_with_source_address_and_mac_address_source_link_layer_option")]
397 #[test_case(
398 1,
399 Some(SOURCE_ADDRESS),
400 Some(vec![1, 2, 3, 4, 5]),
401 Some(&[1, 2, 3, 4, 5, 0]); "once_with_source_address_and_short_address_source_link_layer_option")]
402 #[test_case(
403 1,
404 Some(SOURCE_ADDRESS),
405 Some(vec![1, 2, 3, 4, 5, 6, 7]),
406 Some(&[
407 1, 2, 3, 4, 5, 6, 7,
408 0, 0, 0, 0, 0, 0, 0,
409 ]); "once_with_source_address_and_long_address_source_link_layer_option")]
410 fn perform_router_solicitation(
411 max_router_solicitations: u8,
412 source_address: Option<UnicastAddr<Ipv6Addr>>,
413 link_layer_bytes: Option<Vec<u8>>,
414 expected_sll_bytes: Option<&[u8]>,
415 ) {
416 let CtxPair { mut core_ctx, mut bindings_ctx } =
417 CtxPair::with_core_ctx(FakeCoreCtxImpl::with_state(FakeRsContext {
418 max_router_solicitations: NonZeroU8::new(max_router_solicitations),
419 rs_state: Default::default(),
420 source_address,
421 link_layer_bytes,
422 }));
423 RsHandler::start_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
424
425 assert_eq!(core_ctx.frames(), &[][..]);
426
427 let mut duration = MAX_RTR_SOLICITATION_DELAY;
428 for i in 0..max_router_solicitations {
429 assert_eq!(core_ctx.state.rs_state.solicitations_sent, i);
430 let now = bindings_ctx.now();
431 bindings_ctx
432 .timers
433 .assert_timers_installed_range([(RS_TIMER_ID, now..=now + duration)]);
434
435 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(RS_TIMER_ID));
436 let frames = core_ctx.frames();
437 assert_eq!(frames.len(), usize::from(i + 1), "frames = {:?}", frames);
438 let (RsMessageMeta { message }, frame) =
439 frames.last().expect("should have transmitted a frame");
440 assert_eq!(*message, RouterSolicitation::default());
441 let options = Options::parse(&frame[..]).expect("parse NDP options");
442 let sll_bytes = options.iter().find_map(|o| match o {
443 NdpOption::SourceLinkLayerAddress(a) => Some(a),
444 o => panic!("unexpected NDP option = {:?}", o),
445 });
446
447 assert_eq!(sll_bytes, expected_sll_bytes);
448 duration = RTR_SOLICITATION_INTERVAL;
449 }
450
451 bindings_ctx.timers.assert_no_timers_installed();
452 assert_eq!(core_ctx.state.rs_state.solicitations_sent, max_router_solicitations);
453 let frames = core_ctx.frames();
454 assert_eq!(frames.len(), usize::from(max_router_solicitations), "frames = {:?}", frames);
455 }
456
457 #[test]
458 fn max_router_solicitations_updated_after_start() {
459 let CtxPair { mut core_ctx, mut bindings_ctx } =
460 CtxPair::with_core_ctx(FakeCoreCtxImpl::with_state(FakeRsContext {
461 max_router_solicitations: NonZeroU8::new(3),
462 rs_state: Default::default(),
463 source_address: None,
464 link_layer_bytes: None,
465 }));
466 RsHandler::start_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
467
468 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(RS_TIMER_ID));
470 assert_eq!(core_ctx.frames().len(), 1);
471
472 core_ctx.state.max_router_solicitations = NonZeroU8::new(1);
474
475 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(RS_TIMER_ID));
477 assert_eq!(core_ctx.frames().len(), 1);
478 bindings_ctx.timers.assert_no_timers_installed();
479 }
480
481 #[test]
484 fn previous_cycle_timers_ignored() {
485 let CtxPair { mut core_ctx, mut bindings_ctx } =
486 CtxPair::with_core_ctx(FakeCoreCtxImpl::with_state(FakeRsContext {
487 max_router_solicitations: NonZeroU8::new(1),
488 rs_state: Default::default(),
489 source_address: None,
490 link_layer_bytes: None,
491 }));
492 RsHandler::start_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
493 let timer_id = core_ctx.state.rs_state.timer.as_ref().unwrap().timer_id();
495 RsHandler::stop_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
496 do_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId, timer_id);
498 assert_eq!(core_ctx.frames(), &[][..]);
499 RsHandler::start_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
502 do_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId, timer_id);
503 assert_eq!(core_ctx.frames(), &[][..]);
504 }
505}