Skip to main content

netstack3_ip/device/
router_solicitation.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! IPv6 Router Solicitation as defined by [RFC 4861 section 6.3.7].
6//!
7//! [RFC 4861 section 6.3.7]: https://datatracker.ietf.org/doc/html/rfc4861#section-6.3.7
8
9use 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
29/// Amount of time to wait after sending `MAX_RTR_SOLICITATIONS` Router
30/// Solicitation messages before determining that there are no routers on the
31/// link for the purpose of IPv6 Stateless Address Autoconfiguration if no
32/// Router Advertisement messages have been received as defined in [RFC 4861
33/// section 10].
34///
35/// This parameter is also used when a host sends its initial Router
36/// Solicitation message, as per [RFC 4861 section 6.3.7]. Before a node sends
37/// an initial solicitation, it SHOULD delay the transmission for a random
38/// amount of time between 0 and `MAX_RTR_SOLICITATION_DELAY`. This serves to
39/// alleviate congestion when many hosts start up on a link at the same time.
40///
41/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
42/// [RFC 4861 section 6.3.7]: https://tools.ietf.org/html/rfc4861#section-6.3.7
43pub const MAX_RTR_SOLICITATION_DELAY: Duration = Duration::from_secs(1);
44
45/// Minimum duration between router solicitation messages as defined in [RFC
46/// 4861 section 10].
47///
48/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
49pub const RTR_SOLICITATION_INTERVAL: Duration = Duration::from_secs(4);
50
51/// A router solicitation timer.
52#[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    /// Create a new [`RsTimerId`] for `device_id`.
63    #[cfg(any(test, feature = "testutils"))]
64    pub fn new(device_id: D) -> Self {
65        Self { device_id }
66    }
67}
68
69/// A device's router solicitation state.
70#[derive(Derivative)]
71#[derivative(Default(bound = ""))]
72pub struct RsState<BT: RsBindingsTypes> {
73    solicitations_sent: u8,
74    timer: Option<BT::Timer>,
75}
76
77/// The execution context for router solicitation.
78pub trait RsContext<BC: RsBindingsTypes>:
79    DeviceIdContext<AnyDevice> + CoreTimerContext<RsTimerId<Self::WeakDeviceId>, BC>
80{
81    /// A link-layer address.
82    type LinkLayerAddr: Ipv6LinkLayerAddr;
83
84    /// Calls the callback with a mutable reference to the router solicitation
85    /// state and the maximum number of router solications to send.
86    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    /// Calls the callback with a mutable reference to the router solicitation
93    /// state.
94    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    /// Gets the device's link-layer address, if the device supports link-layer
103    /// addressing.
104    fn get_link_layer_addr(&mut self, device_id: &Self::DeviceId) -> Option<Self::LinkLayerAddr>;
105
106    /// Sends an NDP Router Solicitation to the local-link.
107    ///
108    /// The callback is called with a source address suitable for an outgoing
109    /// router solicitation message and returns the message body.
110    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
122/// The bindings types for router solicitation.
123pub trait RsBindingsTypes: TimerBindingsTypes {}
124impl<BT> RsBindingsTypes for BT where BT: TimerBindingsTypes {}
125
126/// The bindings execution context for router solicitation.
127pub trait RsBindingsContext: RngContext + TimerContext {}
128impl<BC> RsBindingsContext for BC where BC: RngContext + TimerContext {}
129
130/// An implementation of Router Solicitation.
131pub trait RsHandler<BC: RsBindingsTypes>:
132    DeviceIdContext<AnyDevice> + TimerHandler<BC, RsTimerId<Self::WeakDeviceId>>
133{
134    /// Starts router solicitation.
135    fn start_router_solicitation(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
136
137    /// Stops router solicitation.
138    ///
139    /// Does nothing if router solicitaiton is not being performed
140    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            // The caller *must call* `stop_router_solicitation` before starting
150            // a new one.
151            assert_matches!(timer, None);
152
153            match max {
154                None => {}
155                Some(_) => {
156                    // As per RFC 4861 section 6.3.7, delay the first transmission for a
157                    // random amount of time between 0 and `MAX_RTR_SOLICITATION_DELAY` to
158                    // alleviate congestion when many hosts start up on a link at the same
159                    // time.
160                    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            // Get rid of the installed timer if we're stopping solicitations.
176            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
194/// Solicit routers once and schedule next message.
195fn 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                // If we don't have a timer then RS has been stopped.
206                return false;
207            };
208            if bindings_ctx.unique_timer_id(timer) != timer_id {
209                // This is an errant request from a previous round of RS enablement,
210                // just ignore it and don't send a solicitation.
211                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            // Schedule the next timer if more solicitations are needed.
222            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    // TODO(https://fxbug.dev/42165912): Either panic or guarantee that this error
237    // can't happen statically.
238    let _: Result<(), _> =
239        core_ctx.send_rs_packet(bindings_ctx, device_id, RouterSolicitation::default(), |src_ip| {
240            // As per RFC 4861 section 4.1,
241            //
242            //   Valid Options:
243            //
244            //      Source link-layer address The link-layer address of the
245            //                     sender, if known. MUST NOT be included if the
246            //                     Source Address is the unspecified address.
247            //                     Otherwise, it SHOULD be included on link
248            //                     layers that have addresses.
249            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        // Trigger first timer: 1 RS sent.
469        assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(RS_TIMER_ID));
470        assert_eq!(core_ctx.frames().len(), 1);
471
472        // Reduce max_router_solicitations to 1 while solicitation is ongoing.
473        core_ctx.state.max_router_solicitations = NonZeroU8::new(1);
474
475        // Trigger second timer: 0 RS sent because of the new limit.
476        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    // Regression guard for when router solicitations would consider timers for
482    // the previously enabled start request if they were raced against dispatch.
483    #[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        // Get the timer ID for the first instantiation.
494        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        // Attempt to run router solicitation after stopping, nothing is sent out.
497        do_router_solicitation(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId, timer_id);
498        assert_eq!(core_ctx.frames(), &[][..]);
499        // Start again and if the same timer_id fires it should not cause an RS
500        // to be sent out.
501        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}