netstack3_ip/device/
config.rs

1// Copyright 2024 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//! IP Device configuration.
6
7use core::num::{NonZeroU8, NonZeroU16};
8
9use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6};
10use netstack3_base::{AnyDevice, DeviceIdContext, DeviceIdentifier};
11
12use crate::internal::device::route_discovery::RouteDiscoveryConfigurationUpdate;
13use crate::internal::device::slaac::SlaacConfigurationUpdate;
14use crate::internal::device::state::{
15    IpDeviceConfiguration, IpDeviceFlags, Ipv4DeviceConfiguration,
16};
17use crate::internal::device::{
18    self, IpDeviceBindingsContext, IpDeviceConfigurationContext, IpDeviceEvent, IpDeviceIpExt,
19    Ipv6DeviceConfigurationContext, WithIpDeviceConfigurationMutInner as _,
20    WithIpv6DeviceConfigurationMutInner as _,
21};
22use crate::internal::gmp::GmpHandler;
23use crate::internal::gmp::igmp::IgmpConfigMode;
24use crate::internal::gmp::mld::MldConfigMode;
25
26/// A trait abstracting configuration between IPv4 and IPv6.
27///
28/// Configuration is different enough between IPv4 and IPv6 that the
29/// implementations are completely disjoint. This trait allows us to implement
30/// these completely separately but still offer a unified configuration update
31/// API.
32pub trait IpDeviceConfigurationHandler<I: IpDeviceIpExt, BC>: DeviceIdContext<AnyDevice> {
33    /// Applies the [`PendingIpDeviceConfigurationUpdate`].
34    fn apply_configuration(
35        &mut self,
36        bindings_ctx: &mut BC,
37        config: PendingIpDeviceConfigurationUpdate<'_, I, Self::DeviceId>,
38    ) -> I::ConfigurationUpdate;
39}
40
41/// An update to IP device configuration.
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, GenericOverIp)]
43#[generic_over_ip()]
44pub struct IpDeviceConfigurationUpdate {
45    /// A change in IP enabled.
46    pub ip_enabled: Option<bool>,
47    /// A change in unicast forwarding enabled.
48    pub unicast_forwarding_enabled: Option<bool>,
49    /// A change in multicast forwarding enabled.
50    pub multicast_forwarding_enabled: Option<bool>,
51    /// A change in Group Messaging Protocol (GMP) enabled.
52    pub gmp_enabled: Option<bool>,
53    /// A change in DAD transmits.
54    pub dad_transmits: Option<Option<NonZeroU16>>,
55}
56
57/// An update to IPv4 device configuration.
58#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
59pub struct Ipv4DeviceConfigurationUpdate {
60    /// A change in the IP device configuration.
61    pub ip_config: IpDeviceConfigurationUpdate,
62    /// A change in the IGMP mode.
63    pub igmp_mode: Option<IgmpConfigMode>,
64}
65
66impl From<IpDeviceConfigurationUpdate> for Ipv4DeviceConfigurationUpdate {
67    fn from(ip_config: IpDeviceConfigurationUpdate) -> Self {
68        Self { ip_config, ..Default::default() }
69    }
70}
71
72impl AsRef<IpDeviceConfigurationUpdate> for Ipv4DeviceConfigurationUpdate {
73    fn as_ref(&self) -> &IpDeviceConfigurationUpdate {
74        &self.ip_config
75    }
76}
77
78/// Errors observed from updating a device's IP configuration.
79#[derive(Debug, Eq, PartialEq, Copy, Clone)]
80pub enum UpdateIpConfigurationError {
81    /// Unicast Forwarding is not supported in the target interface.
82    UnicastForwardingNotSupported,
83    /// Multicast Forwarding is not supported in the target interface.
84    MulticastForwardingNotSupported,
85}
86
87/// A validated and pending IP device configuration update.
88///
89/// This type is a witness for a valid IP configuration for a device ID `D`.
90#[derive(GenericOverIp)]
91#[generic_over_ip(I, Ip)]
92pub struct PendingIpDeviceConfigurationUpdate<'a, I: IpDeviceIpExt, D>(
93    I::ConfigurationUpdate,
94    &'a D,
95);
96
97impl<'a, I: IpDeviceIpExt, D: DeviceIdentifier> PendingIpDeviceConfigurationUpdate<'a, I, D> {
98    /// Creates a new [`PendingIpDeviceConfigurationUpdate`] if `config` is
99    /// valid for `device`.
100    pub(crate) fn new(
101        config: I::ConfigurationUpdate,
102        device_id: &'a D,
103    ) -> Result<Self, UpdateIpConfigurationError> {
104        let IpDeviceConfigurationUpdate {
105            ip_enabled: _,
106            gmp_enabled: _,
107            unicast_forwarding_enabled,
108            multicast_forwarding_enabled,
109            dad_transmits: _,
110        } = config.as_ref();
111
112        if device_id.is_loopback() {
113            if unicast_forwarding_enabled.unwrap_or(false) {
114                return Err(UpdateIpConfigurationError::UnicastForwardingNotSupported);
115            }
116            if multicast_forwarding_enabled.unwrap_or(false) {
117                return Err(UpdateIpConfigurationError::MulticastForwardingNotSupported);
118            }
119        }
120
121        Ok(Self(config, device_id))
122    }
123
124    /// Returns the configuration update.
125    pub fn config_update(&self) -> &I::ConfigurationUpdate {
126        &self.0
127    }
128
129    /// Returns the device ID.
130    pub fn device_id(&self) -> &'a D {
131        self.1
132    }
133}
134
135impl<CC, BC> IpDeviceConfigurationHandler<Ipv4, BC> for CC
136where
137    CC: IpDeviceConfigurationContext<Ipv4, BC>,
138    BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
139{
140    fn apply_configuration(
141        &mut self,
142        bindings_ctx: &mut BC,
143        config: PendingIpDeviceConfigurationUpdate<'_, Ipv4, Self::DeviceId>,
144    ) -> Ipv4DeviceConfigurationUpdate {
145        let PendingIpDeviceConfigurationUpdate(
146            Ipv4DeviceConfigurationUpdate { ip_config, igmp_mode },
147            device_id,
148        ) = config;
149        let device_id: &CC::DeviceId = device_id;
150
151        // NB: Extracted to prevent deep nesting which breaks rustfmt.
152        let handle_config_and_flags =
153            |config: &mut Ipv4DeviceConfiguration, flags: &mut IpDeviceFlags| {
154                let IpDeviceConfigurationUpdate {
155                    ip_enabled,
156                    gmp_enabled,
157                    unicast_forwarding_enabled,
158                    multicast_forwarding_enabled,
159                    dad_transmits,
160                } = ip_config;
161                (
162                    get_prev_next_and_update(&mut flags.ip_enabled, ip_enabled),
163                    get_prev_next_and_update(&mut config.ip_config.gmp_enabled, gmp_enabled),
164                    get_prev_next_and_update(
165                        &mut config.ip_config.unicast_forwarding_enabled,
166                        unicast_forwarding_enabled,
167                    ),
168                    get_prev_next_and_update(
169                        &mut config.ip_config.multicast_forwarding_enabled,
170                        multicast_forwarding_enabled,
171                    ),
172                    get_prev_next_and_update(&mut config.ip_config.dad_transmits, dad_transmits),
173                )
174            };
175
176        self.with_ip_device_configuration_mut(device_id, |mut inner| {
177            let (
178                ip_enabled_updates,
179                gmp_enabled_updates,
180                unicast_forwarding_enabled_updates,
181                multicast_forwarding_enabled_updates,
182                dad_transmits,
183            ) = inner.with_configuration_and_flags_mut(device_id, handle_config_and_flags);
184
185            let (config, mut core_ctx) = inner.ip_device_configuration_and_ctx();
186            let core_ctx = &mut core_ctx;
187
188            let ip_enabled = handle_change_and_get_prev(ip_enabled_updates, |next| {
189                if next {
190                    device::enable_ipv4_device_with_config(
191                        core_ctx,
192                        bindings_ctx,
193                        device_id,
194                        config,
195                    )
196                } else {
197                    device::disable_ipv4_device_with_config(
198                        core_ctx,
199                        bindings_ctx,
200                        device_id,
201                        config,
202                    )
203                }
204                bindings_ctx.on_event(IpDeviceEvent::EnabledChanged {
205                    device: device_id.clone(),
206                    ip_enabled: next,
207                })
208            });
209
210            // NB: change GMP mode before enabling in case those are changed
211            // atomically.
212            let igmp_mode = igmp_mode
213                .map(|igmp_mode| core_ctx.gmp_set_mode(bindings_ctx, device_id, igmp_mode));
214
215            let gmp_enabled = handle_change_and_get_prev(gmp_enabled_updates, |next| {
216                if next {
217                    GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id)
218                } else {
219                    GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id)
220                }
221            });
222            let unicast_forwarding_enabled =
223                dont_handle_change_and_get_prev(unicast_forwarding_enabled_updates);
224            let multicast_forwarding_enabled =
225                dont_handle_change_and_get_prev(multicast_forwarding_enabled_updates);
226            let dad_transmits = dont_handle_change_and_get_prev(dad_transmits);
227            let ip_config = IpDeviceConfigurationUpdate {
228                ip_enabled,
229                gmp_enabled,
230                unicast_forwarding_enabled,
231                multicast_forwarding_enabled,
232                dad_transmits,
233            };
234            Ipv4DeviceConfigurationUpdate { ip_config, igmp_mode }
235        })
236    }
237}
238
239/// An update to IPv6 device configuration.
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
241pub struct Ipv6DeviceConfigurationUpdate {
242    /// A change in maximum router solicitations.
243    pub max_router_solicitations: Option<Option<NonZeroU8>>,
244    /// A change in SLAAC configuration.
245    pub slaac_config: SlaacConfigurationUpdate,
246    /// A change in route discovery configuration.
247    pub route_discovery_config: RouteDiscoveryConfigurationUpdate,
248    /// A change in the IP device configuration.
249    pub ip_config: IpDeviceConfigurationUpdate,
250    /// A change in the MLD mode.
251    pub mld_mode: Option<MldConfigMode>,
252}
253
254impl From<IpDeviceConfigurationUpdate> for Ipv6DeviceConfigurationUpdate {
255    fn from(ip_config: IpDeviceConfigurationUpdate) -> Self {
256        Self { ip_config, ..Default::default() }
257    }
258}
259
260impl AsRef<IpDeviceConfigurationUpdate> for Ipv6DeviceConfigurationUpdate {
261    fn as_ref(&self) -> &IpDeviceConfigurationUpdate {
262        &self.ip_config
263    }
264}
265
266impl<CC, BC> IpDeviceConfigurationHandler<Ipv6, BC> for CC
267where
268    CC: Ipv6DeviceConfigurationContext<BC>,
269    BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
270{
271    fn apply_configuration(
272        &mut self,
273        bindings_ctx: &mut BC,
274        config: PendingIpDeviceConfigurationUpdate<'_, Ipv6, Self::DeviceId>,
275    ) -> Ipv6DeviceConfigurationUpdate {
276        let PendingIpDeviceConfigurationUpdate(
277            Ipv6DeviceConfigurationUpdate {
278                max_router_solicitations,
279                slaac_config,
280                route_discovery_config,
281                ip_config,
282                mld_mode,
283            },
284            device_id,
285        ) = config;
286        self.with_ipv6_device_configuration_mut(device_id, |mut inner| {
287            let (
288                max_router_solicitations_updates,
289                slaac_config_updates,
290                route_discovery_config_updates,
291                ip_enabled_updates,
292                gmp_enabled_updates,
293                unicast_forwarding_enabled_updates,
294                multicast_forwarding_enabled_updates,
295                dad_transmits,
296            ) = inner.with_configuration_and_flags_mut(device_id, |config, flags| {
297                let IpDeviceConfigurationUpdate {
298                    ip_enabled,
299                    gmp_enabled,
300                    unicast_forwarding_enabled,
301                    multicast_forwarding_enabled,
302                    dad_transmits,
303                } = ip_config;
304                (
305                    get_prev_next_and_update(
306                        &mut config.max_router_solicitations,
307                        max_router_solicitations,
308                    ),
309                    config.slaac_config.update(slaac_config),
310                    config.route_discovery_config.update(route_discovery_config),
311                    get_prev_next_and_update(&mut flags.ip_enabled, ip_enabled),
312                    get_prev_next_and_update(&mut config.ip_config.gmp_enabled, gmp_enabled),
313                    get_prev_next_and_update(
314                        &mut config.ip_config.unicast_forwarding_enabled,
315                        unicast_forwarding_enabled,
316                    ),
317                    get_prev_next_and_update(
318                        &mut config.ip_config.multicast_forwarding_enabled,
319                        multicast_forwarding_enabled,
320                    ),
321                    get_prev_next_and_update(&mut config.ip_config.dad_transmits, dad_transmits),
322                )
323            });
324
325            let (config, mut core_ctx) = inner.ipv6_device_configuration_and_ctx();
326            let core_ctx = &mut core_ctx;
327
328            let max_router_solicitations =
329                dont_handle_change_and_get_prev(max_router_solicitations_updates);
330
331            // NB: change GMP mode before enabling in case those are changed
332            // atomically.
333            let mld_mode =
334                mld_mode.map(|mld_mode| core_ctx.gmp_set_mode(bindings_ctx, device_id, mld_mode));
335
336            let ip_config = IpDeviceConfigurationUpdate {
337                ip_enabled: handle_change_and_get_prev(ip_enabled_updates, |next| {
338                    if next {
339                        device::enable_ipv6_device_with_config(
340                            core_ctx,
341                            bindings_ctx,
342                            device_id,
343                            config,
344                        )
345                    } else {
346                        device::disable_ipv6_device_with_config(
347                            core_ctx,
348                            bindings_ctx,
349                            device_id,
350                            config,
351                        )
352                    }
353
354                    bindings_ctx.on_event(IpDeviceEvent::EnabledChanged {
355                        device: device_id.clone(),
356                        ip_enabled: next,
357                    })
358                }),
359                gmp_enabled: handle_change_and_get_prev(gmp_enabled_updates, |next| {
360                    if next {
361                        GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id)
362                    } else {
363                        GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id)
364                    }
365                }),
366                unicast_forwarding_enabled: handle_change_and_get_prev(
367                    unicast_forwarding_enabled_updates,
368                    |next| {
369                        if next {
370                            device::join_ip_multicast_with_config(
371                                core_ctx,
372                                bindings_ctx,
373                                device_id,
374                                Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
375                                config,
376                            );
377                        } else {
378                            device::leave_ip_multicast_with_config(
379                                core_ctx,
380                                bindings_ctx,
381                                device_id,
382                                Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
383                                config,
384                            );
385                        }
386                    },
387                ),
388                multicast_forwarding_enabled: dont_handle_change_and_get_prev(
389                    multicast_forwarding_enabled_updates,
390                ),
391                dad_transmits: dont_handle_change_and_get_prev(dad_transmits),
392            };
393            Ipv6DeviceConfigurationUpdate {
394                max_router_solicitations,
395                slaac_config: slaac_config_updates,
396                route_discovery_config: route_discovery_config_updates,
397                ip_config,
398                mld_mode,
399            }
400        })
401    }
402}
403
404struct Delta<T> {
405    prev: T,
406    next: T,
407}
408
409fn get_prev_next_and_update<T: Copy>(field: &mut T, next: Option<T>) -> Option<Delta<T>> {
410    next.map(|next| Delta { prev: core::mem::replace(field, next), next })
411}
412
413fn handle_change_and_get_prev<T: PartialEq>(
414    delta: Option<Delta<T>>,
415    f: impl FnOnce(T),
416) -> Option<T> {
417    delta.map(|Delta { prev, next }| {
418        if prev != next {
419            f(next)
420        }
421        prev
422    })
423}
424
425fn dont_handle_change_and_get_prev<T: PartialEq>(delta: Option<Delta<T>>) -> Option<T> {
426    handle_change_and_get_prev(delta, |_: T| {})
427}
428
429/// The device configurations and flags.
430#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
431pub struct IpDeviceConfigurationAndFlags<I: IpDeviceIpExt> {
432    /// The device configuration.
433    pub config: I::Configuration,
434    /// The device flags.
435    pub flags: IpDeviceFlags,
436    /// The GMP mode.
437    pub gmp_mode: I::GmpProtoConfigMode,
438}
439
440impl<I: IpDeviceIpExt> AsRef<IpDeviceConfiguration> for IpDeviceConfigurationAndFlags<I> {
441    fn as_ref(&self) -> &IpDeviceConfiguration {
442        self.config.as_ref()
443    }
444}
445
446impl<I: IpDeviceIpExt> AsMut<IpDeviceConfiguration> for IpDeviceConfigurationAndFlags<I> {
447    fn as_mut(&mut self) -> &mut IpDeviceConfiguration {
448        self.config.as_mut()
449    }
450}
451
452impl<I: IpDeviceIpExt> AsRef<IpDeviceFlags> for IpDeviceConfigurationAndFlags<I> {
453    fn as_ref(&self) -> &IpDeviceFlags {
454        &self.flags
455    }
456}