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::{NonZeroU16, NonZeroU8};
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::igmp::IgmpConfigMode;
22use crate::internal::gmp::mld::MldConfigMode;
23use crate::internal::gmp::GmpHandler;
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
124impl<CC, BC> IpDeviceConfigurationHandler<Ipv4, BC> for CC
125where
126    CC: IpDeviceConfigurationContext<Ipv4, BC>,
127    BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
128{
129    fn apply_configuration(
130        &mut self,
131        bindings_ctx: &mut BC,
132        config: PendingIpDeviceConfigurationUpdate<'_, Ipv4, Self::DeviceId>,
133    ) -> Ipv4DeviceConfigurationUpdate {
134        let PendingIpDeviceConfigurationUpdate(
135            Ipv4DeviceConfigurationUpdate { ip_config, igmp_mode },
136            device_id,
137        ) = config;
138        let device_id: &CC::DeviceId = device_id;
139
140        // NB: Extracted to prevent deep nesting which breaks rustfmt.
141        let handle_config_and_flags =
142            |config: &mut Ipv4DeviceConfiguration, flags: &mut IpDeviceFlags| {
143                let IpDeviceConfigurationUpdate {
144                    ip_enabled,
145                    gmp_enabled,
146                    unicast_forwarding_enabled,
147                    multicast_forwarding_enabled,
148                    dad_transmits,
149                } = ip_config;
150                (
151                    get_prev_next_and_update(&mut flags.ip_enabled, ip_enabled),
152                    get_prev_next_and_update(&mut config.ip_config.gmp_enabled, gmp_enabled),
153                    get_prev_next_and_update(
154                        &mut config.ip_config.unicast_forwarding_enabled,
155                        unicast_forwarding_enabled,
156                    ),
157                    get_prev_next_and_update(
158                        &mut config.ip_config.multicast_forwarding_enabled,
159                        multicast_forwarding_enabled,
160                    ),
161                    get_prev_next_and_update(&mut config.ip_config.dad_transmits, dad_transmits),
162                )
163            };
164
165        self.with_ip_device_configuration_mut(device_id, |mut inner| {
166            let (
167                ip_enabled_updates,
168                gmp_enabled_updates,
169                unicast_forwarding_enabled_updates,
170                multicast_forwarding_enabled_updates,
171                dad_transmits,
172            ) = inner.with_configuration_and_flags_mut(device_id, handle_config_and_flags);
173
174            let (config, mut core_ctx) = inner.ip_device_configuration_and_ctx();
175            let core_ctx = &mut core_ctx;
176
177            let ip_enabled = handle_change_and_get_prev(ip_enabled_updates, |next| {
178                if next {
179                    device::enable_ipv4_device_with_config(
180                        core_ctx,
181                        bindings_ctx,
182                        device_id,
183                        config,
184                    )
185                } else {
186                    device::disable_ipv4_device_with_config(
187                        core_ctx,
188                        bindings_ctx,
189                        device_id,
190                        config,
191                    )
192                }
193                bindings_ctx.on_event(IpDeviceEvent::EnabledChanged {
194                    device: device_id.clone(),
195                    ip_enabled: next,
196                })
197            });
198
199            // NB: change GMP mode before enabling in case those are changed
200            // atomically.
201            let igmp_mode = igmp_mode
202                .map(|igmp_mode| core_ctx.gmp_set_mode(bindings_ctx, device_id, igmp_mode));
203
204            let gmp_enabled = handle_change_and_get_prev(gmp_enabled_updates, |next| {
205                if next {
206                    GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id)
207                } else {
208                    GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id)
209                }
210            });
211            let unicast_forwarding_enabled =
212                dont_handle_change_and_get_prev(unicast_forwarding_enabled_updates);
213            let multicast_forwarding_enabled =
214                dont_handle_change_and_get_prev(multicast_forwarding_enabled_updates);
215            let dad_transmits = dont_handle_change_and_get_prev(dad_transmits);
216            let ip_config = IpDeviceConfigurationUpdate {
217                ip_enabled,
218                gmp_enabled,
219                unicast_forwarding_enabled,
220                multicast_forwarding_enabled,
221                dad_transmits,
222            };
223            Ipv4DeviceConfigurationUpdate { ip_config, igmp_mode }
224        })
225    }
226}
227
228/// An update to IPv6 device configuration.
229#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
230pub struct Ipv6DeviceConfigurationUpdate {
231    /// A change in maximum router solicitations.
232    pub max_router_solicitations: Option<Option<NonZeroU8>>,
233    /// A change in SLAAC configuration.
234    pub slaac_config: SlaacConfigurationUpdate,
235    /// A change in the IP device configuration.
236    pub ip_config: IpDeviceConfigurationUpdate,
237    /// A change in the MLD mode.
238    pub mld_mode: Option<MldConfigMode>,
239}
240
241impl From<IpDeviceConfigurationUpdate> for Ipv6DeviceConfigurationUpdate {
242    fn from(ip_config: IpDeviceConfigurationUpdate) -> Self {
243        Self { ip_config, ..Default::default() }
244    }
245}
246
247impl AsRef<IpDeviceConfigurationUpdate> for Ipv6DeviceConfigurationUpdate {
248    fn as_ref(&self) -> &IpDeviceConfigurationUpdate {
249        &self.ip_config
250    }
251}
252
253impl<CC, BC> IpDeviceConfigurationHandler<Ipv6, BC> for CC
254where
255    CC: Ipv6DeviceConfigurationContext<BC>,
256    BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
257{
258    fn apply_configuration(
259        &mut self,
260        bindings_ctx: &mut BC,
261        config: PendingIpDeviceConfigurationUpdate<'_, Ipv6, Self::DeviceId>,
262    ) -> Ipv6DeviceConfigurationUpdate {
263        let PendingIpDeviceConfigurationUpdate(
264            Ipv6DeviceConfigurationUpdate {
265                max_router_solicitations,
266                slaac_config,
267                ip_config,
268                mld_mode,
269            },
270            device_id,
271        ) = config;
272        self.with_ipv6_device_configuration_mut(device_id, |mut inner| {
273            let (
274                max_router_solicitations_updates,
275                slaac_config_updates,
276                ip_enabled_updates,
277                gmp_enabled_updates,
278                unicast_forwarding_enabled_updates,
279                multicast_forwarding_enabled_updates,
280                dad_transmits,
281            ) = inner.with_configuration_and_flags_mut(device_id, |config, flags| {
282                let IpDeviceConfigurationUpdate {
283                    ip_enabled,
284                    gmp_enabled,
285                    unicast_forwarding_enabled,
286                    multicast_forwarding_enabled,
287                    dad_transmits,
288                } = ip_config;
289                (
290                    get_prev_next_and_update(
291                        &mut config.max_router_solicitations,
292                        max_router_solicitations,
293                    ),
294                    config.slaac_config.update(slaac_config),
295                    get_prev_next_and_update(&mut flags.ip_enabled, ip_enabled),
296                    get_prev_next_and_update(&mut config.ip_config.gmp_enabled, gmp_enabled),
297                    get_prev_next_and_update(
298                        &mut config.ip_config.unicast_forwarding_enabled,
299                        unicast_forwarding_enabled,
300                    ),
301                    get_prev_next_and_update(
302                        &mut config.ip_config.multicast_forwarding_enabled,
303                        multicast_forwarding_enabled,
304                    ),
305                    get_prev_next_and_update(&mut config.ip_config.dad_transmits, dad_transmits),
306                )
307            });
308
309            let (config, mut core_ctx) = inner.ipv6_device_configuration_and_ctx();
310            let core_ctx = &mut core_ctx;
311
312            let max_router_solicitations =
313                dont_handle_change_and_get_prev(max_router_solicitations_updates);
314
315            // NB: change GMP mode before enabling in case those are changed
316            // atomically.
317            let mld_mode =
318                mld_mode.map(|mld_mode| core_ctx.gmp_set_mode(bindings_ctx, device_id, mld_mode));
319
320            let ip_config = IpDeviceConfigurationUpdate {
321                ip_enabled: handle_change_and_get_prev(ip_enabled_updates, |next| {
322                    if next {
323                        device::enable_ipv6_device_with_config(
324                            core_ctx,
325                            bindings_ctx,
326                            device_id,
327                            config,
328                        )
329                    } else {
330                        device::disable_ipv6_device_with_config(
331                            core_ctx,
332                            bindings_ctx,
333                            device_id,
334                            config,
335                        )
336                    }
337
338                    bindings_ctx.on_event(IpDeviceEvent::EnabledChanged {
339                        device: device_id.clone(),
340                        ip_enabled: next,
341                    })
342                }),
343                gmp_enabled: handle_change_and_get_prev(gmp_enabled_updates, |next| {
344                    if next {
345                        GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id)
346                    } else {
347                        GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id)
348                    }
349                }),
350                unicast_forwarding_enabled: handle_change_and_get_prev(
351                    unicast_forwarding_enabled_updates,
352                    |next| {
353                        if next {
354                            device::join_ip_multicast_with_config(
355                                core_ctx,
356                                bindings_ctx,
357                                device_id,
358                                Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
359                                config,
360                            );
361                        } else {
362                            device::leave_ip_multicast_with_config(
363                                core_ctx,
364                                bindings_ctx,
365                                device_id,
366                                Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
367                                config,
368                            );
369                        }
370                    },
371                ),
372                multicast_forwarding_enabled: dont_handle_change_and_get_prev(
373                    multicast_forwarding_enabled_updates,
374                ),
375                dad_transmits: dont_handle_change_and_get_prev(dad_transmits),
376            };
377            Ipv6DeviceConfigurationUpdate {
378                max_router_solicitations,
379                slaac_config: slaac_config_updates,
380                ip_config,
381                mld_mode,
382            }
383        })
384    }
385}
386
387struct Delta<T> {
388    prev: T,
389    next: T,
390}
391
392fn get_prev_next_and_update<T: Copy>(field: &mut T, next: Option<T>) -> Option<Delta<T>> {
393    next.map(|next| Delta { prev: core::mem::replace(field, next), next })
394}
395
396fn handle_change_and_get_prev<T: PartialEq>(
397    delta: Option<Delta<T>>,
398    f: impl FnOnce(T),
399) -> Option<T> {
400    delta.map(|Delta { prev, next }| {
401        if prev != next {
402            f(next)
403        }
404        prev
405    })
406}
407
408fn dont_handle_change_and_get_prev<T: PartialEq>(delta: Option<Delta<T>>) -> Option<T> {
409    handle_change_and_get_prev(delta, |_: T| {})
410}
411
412/// The device configurations and flags.
413#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
414pub struct IpDeviceConfigurationAndFlags<I: IpDeviceIpExt> {
415    /// The device configuration.
416    pub config: I::Configuration,
417    /// The device flags.
418    pub flags: IpDeviceFlags,
419    /// The GMP mode.
420    pub gmp_mode: I::GmpProtoConfigMode,
421}
422
423impl<I: IpDeviceIpExt> AsRef<IpDeviceConfiguration> for IpDeviceConfigurationAndFlags<I> {
424    fn as_ref(&self) -> &IpDeviceConfiguration {
425        self.config.as_ref()
426    }
427}
428
429impl<I: IpDeviceIpExt> AsMut<IpDeviceConfiguration> for IpDeviceConfigurationAndFlags<I> {
430    fn as_mut(&mut self) -> &mut IpDeviceConfiguration {
431        self.config.as_mut()
432    }
433}
434
435impl<I: IpDeviceIpExt> AsRef<IpDeviceFlags> for IpDeviceConfigurationAndFlags<I> {
436    fn as_ref(&self) -> &IpDeviceFlags {
437        &self.flags
438    }
439}