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