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