| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! State for an IP device. |
| |
| use alloc::vec::Vec; |
| use core::{fmt::Debug, num::NonZeroU8, time::Duration}; |
| |
| use net_types::{ |
| ip::{AddrSubnet, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}, |
| SpecifiedAddr, UnicastAddr, Witness, |
| }; |
| use nonzero_ext::nonzero; |
| use packet_formats::utils::NonZeroDuration; |
| |
| use crate::{ |
| ip::{ |
| device::{route_discovery::Ipv6RouteDiscoveryState, slaac::SlaacConfiguration}, |
| gmp::{igmp::IgmpGroupState, mld::MldGroupState, MulticastGroupSet}, |
| }, |
| Instant, |
| }; |
| |
| /// The default value for *RetransTimer* as defined in [RFC 4861 section 10]. |
| /// |
| /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10 |
| const RETRANS_TIMER_DEFAULT: NonZeroDuration = NonZeroDuration::from_nonzero_secs(nonzero!(1u64)); |
| |
| pub(crate) enum DelIpv6AddrReason { |
| ManualAction, |
| DadFailed, |
| } |
| |
| /// An `Ip` extension trait adding IP device state properties. |
| pub(crate) trait IpDeviceStateIpExt<Instant>: Ip { |
| /// The information stored about an IP address assigned to an interface. |
| type AssignedAddress: AssignedAddress<Self::Addr> + Debug; |
| |
| /// The state kept by the Group Messaging Protocol (GMP) used to announce |
| /// membership in an IP multicast group for this version of IP. |
| /// |
| /// Note that a GMP is only used when membership must be explicitly |
| /// announced. For example, a GMP is not used in the context of a loopback |
| /// device (because there are no remote hosts) or in the context of an IPsec |
| /// device (because multicast is not supported). |
| type GmpState; |
| |
| /// The default TTL (IPv4) or hop limit (IPv6) to configure for new IP |
| /// devices. |
| const DEFAULT_HOP_LIMIT: NonZeroU8; |
| } |
| |
| impl<I: Instant> IpDeviceStateIpExt<I> for Ipv4 { |
| type AssignedAddress = AddrSubnet<Ipv4Addr>; |
| type GmpState = IgmpGroupState<I>; |
| const DEFAULT_HOP_LIMIT: NonZeroU8 = nonzero!(64u8); |
| } |
| |
| impl<I: Instant> IpDeviceStateIpExt<I> for Ipv6 { |
| type AssignedAddress = Ipv6AddressEntry<I>; |
| type GmpState = MldGroupState<I>; |
| const DEFAULT_HOP_LIMIT: NonZeroU8 = crate::device::ndp::HOP_LIMIT_DEFAULT; |
| } |
| |
| /// The state associated with an IP address assigned to an IP device. |
| pub trait AssignedAddress<A: IpAddress> { |
| /// Gets the address. |
| fn addr(&self) -> SpecifiedAddr<A>; |
| } |
| |
| impl AssignedAddress<Ipv4Addr> for AddrSubnet<Ipv4Addr> { |
| fn addr(&self) -> SpecifiedAddr<Ipv4Addr> { |
| self.addr() |
| } |
| } |
| |
| impl<I: Instant> AssignedAddress<Ipv6Addr> for Ipv6AddressEntry<I> { |
| fn addr(&self) -> SpecifiedAddr<Ipv6Addr> { |
| self.addr_sub().addr().into_specified() |
| } |
| } |
| |
| /// The state common to all IP devices. |
| #[cfg_attr(test, derive(Debug))] |
| pub(crate) struct IpDeviceState<Instant, I: IpDeviceStateIpExt<Instant>> { |
| /// IP addresses assigned to this device. |
| /// |
| /// IPv6 addresses may be tentative (performing NDP's Duplicate Address |
| /// Detection). |
| /// |
| /// Does not contain any duplicates. |
| addrs: Vec<I::AssignedAddress>, |
| |
| /// Multicast groups this device has joined. |
| pub multicast_groups: MulticastGroupSet<I::Addr, I::GmpState>, |
| |
| /// The default TTL (IPv4) or hop limit (IPv6) for outbound packets sent |
| /// over this device. |
| pub default_hop_limit: NonZeroU8, |
| |
| // TODO(https://fxbug.dev/85682): Rename this flag to something like |
| // `forwarding_enabled`, and make it control only forwarding. Make |
| // participation in Router NDP a separate flag owned by the `ndp` module. |
| // |
| // TODO(joshlf): The `routing_enabled` field probably doesn't make sense for |
| // loopback devices. |
| /// A flag indicating whether routing of IP packets not destined for this |
| /// device is enabled. |
| /// |
| /// This flag controls whether or not packets can be routed from this |
| /// device. That is, when a packet arrives at a device it is not destined |
| /// for, the packet can only be routed if the device it arrived at has |
| /// routing enabled and there exists another device that has a path to the |
| /// packet's destination, regardless of the other device's routing ability. |
| /// |
| /// Default: `false`. |
| pub routing_enabled: bool, |
| } |
| |
| impl<Instant, I: IpDeviceStateIpExt<Instant>> Default for IpDeviceState<Instant, I> { |
| fn default() -> IpDeviceState<Instant, I> { |
| IpDeviceState { |
| addrs: Vec::default(), |
| multicast_groups: MulticastGroupSet::default(), |
| default_hop_limit: I::DEFAULT_HOP_LIMIT, |
| routing_enabled: false, |
| } |
| } |
| } |
| |
| // TODO(https://fxbug.dev/84871): Once we figure out what invariants we want to |
| // hold regarding the set of IP addresses assigned to a device, ensure that all |
| // of the methods on `IpDeviceState` uphold those invariants. |
| impl<Instant, I: IpDeviceStateIpExt<Instant>> IpDeviceState<Instant, I> { |
| /// Iterates over the addresses assigned to this device. |
| pub(crate) fn iter_addrs( |
| &self, |
| ) -> impl ExactSizeIterator<Item = &I::AssignedAddress> + ExactSizeIterator + Clone { |
| self.addrs.iter() |
| } |
| |
| /// Iterates mutably over the addresses assigned to this device. |
| pub(crate) fn iter_addrs_mut( |
| &mut self, |
| ) -> impl ExactSizeIterator<Item = &mut I::AssignedAddress> + ExactSizeIterator { |
| self.addrs.iter_mut() |
| } |
| |
| /// Finds the entry for `addr` if any. |
| pub(crate) fn find_addr(&self, addr: &I::Addr) -> Option<&I::AssignedAddress> { |
| self.addrs.iter().find(|entry| &entry.addr().get() == addr) |
| } |
| |
| /// Finds the mutable entry for `addr` if any. |
| pub(crate) fn find_addr_mut(&mut self, addr: &I::Addr) -> Option<&mut I::AssignedAddress> { |
| self.addrs.iter_mut().find(|entry| &entry.addr().get() == addr) |
| } |
| |
| /// Adds an IP address to this interface. |
| pub(crate) fn add_addr( |
| &mut self, |
| addr: I::AssignedAddress, |
| ) -> Result<(), crate::error::ExistsError> { |
| if self.iter_addrs().any(|a| a.addr() == addr.addr()) { |
| return Err(crate::error::ExistsError); |
| } |
| |
| Ok(self.addrs.push(addr)) |
| } |
| |
| /// Removes the address. |
| pub(crate) fn remove_addr( |
| &mut self, |
| addr: &I::Addr, |
| ) -> Result<I::AssignedAddress, crate::error::NotFoundError> { |
| let (index, _entry): (_, &I::AssignedAddress) = self |
| .addrs |
| .iter() |
| .enumerate() |
| .find(|(_, entry)| &entry.addr().get() == addr) |
| .ok_or(crate::error::NotFoundError)?; |
| Ok(self.addrs.remove(index)) |
| } |
| } |
| |
| impl<Instant: crate::Instant> IpDeviceState<Instant, Ipv6> { |
| /// Iterates over the IPv6 addresses assigned to this device, but only those |
| /// which are in the "assigned" state. |
| pub(crate) fn iter_assigned_ipv6_addrs( |
| &self, |
| ) -> impl Iterator<Item = &Ipv6AddressEntry<Instant>> + Clone { |
| self.addrs.iter().filter(|entry| entry.state.is_assigned()) |
| } |
| } |
| |
| /// The state common to all IPv4 devices. |
| pub(crate) struct Ipv4DeviceState<I: Instant> { |
| pub(crate) ip_state: IpDeviceState<I, Ipv4>, |
| pub(super) config: Ipv4DeviceConfiguration, |
| } |
| |
| impl<I: Instant> Default for Ipv4DeviceState<I> { |
| fn default() -> Ipv4DeviceState<I> { |
| Ipv4DeviceState { ip_state: Default::default(), config: Default::default() } |
| } |
| } |
| |
| impl<I: Instant> AsRef<IpDeviceState<I, Ipv4>> for Ipv4DeviceState<I> { |
| fn as_ref(&self) -> &IpDeviceState<I, Ipv4> { |
| &self.ip_state |
| } |
| } |
| |
| impl<I: Instant> AsRef<IpDeviceConfiguration> for Ipv4DeviceState<I> { |
| fn as_ref(&self) -> &IpDeviceConfiguration { |
| &self.config.ip_config |
| } |
| } |
| |
| /// Configurations common to all IP devices. |
| #[derive(Copy, Clone, Debug, Default)] |
| pub struct IpDeviceConfiguration { |
| /// Is IP enabled for this device. |
| pub ip_enabled: bool, |
| |
| /// Is a Group Messaging Protocol (GMP) enabled for this device? |
| /// |
| /// If `gmp_enabled` is false, multicast groups will still be added to |
| /// `multicast_groups`, but we will not inform the network of our membership |
| /// in those groups using a GMP. |
| /// |
| /// Default: `false`. |
| pub gmp_enabled: bool, |
| } |
| |
| /// Configuration common to all IPv4 devices. |
| #[derive(Copy, Clone, Debug, Default)] |
| pub struct Ipv4DeviceConfiguration { |
| /// The configuration common to all IP devices. |
| pub ip_config: IpDeviceConfiguration, |
| } |
| |
| /// Configuration common to all IPv6 devices. |
| #[derive(Copy, Clone, Debug, Default)] |
| pub struct Ipv6DeviceConfiguration { |
| /// The value for NDP's DupAddrDetectTransmits parameter as defined by |
| /// [RFC 4862 section 5.1]. |
| /// |
| /// A value of `None` means DAD will not be performed on the interface. |
| /// |
| /// [RFC 4862 section 5.1]: https://datatracker.ietf.org/doc/html/rfc4862#section-5.1 |
| pub dad_transmits: Option<NonZeroU8>, |
| |
| /// Value for NDP's `MAX_RTR_SOLICITATIONS` parameter to configure how many |
| /// router solicitation messages to send when solicing routers. |
| /// |
| /// A value of `None` means router solicitation will not be performed. |
| /// |
| /// See [RFC 4861 section 6.3.7] for details. |
| /// |
| /// [RFC 4861 section 6.3.7]: https://datatracker.ietf.org/doc/html/rfc4861#section-6.3.7 |
| pub max_router_solicitations: Option<NonZeroU8>, |
| |
| /// The configuration for SLAAC. |
| pub slaac_config: SlaacConfiguration, |
| |
| /// The configuration common to all IP devices. |
| pub ip_config: IpDeviceConfiguration, |
| } |
| |
| /// The state common to all IPv6 devices. |
| pub(crate) struct Ipv6DeviceState<I: Instant> { |
| /// The time between retransmissions of Neighbor Solicitation messages to a |
| /// neighbor when resolving the address or when probing the reachability of |
| /// a neighbor. |
| /// |
| /// Default: [`RETRANS_TIMER_DEFAULT`]. |
| /// |
| /// See RetransTimer in [RFC 4861 section 6.3.2] for more details. |
| /// |
| /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2 |
| pub(crate) retrans_timer: NonZeroDuration, |
| pub(super) route_discovery: Ipv6RouteDiscoveryState, |
| pub(super) router_soliciations_remaining: Option<NonZeroU8>, |
| pub(crate) ip_state: IpDeviceState<I, Ipv6>, |
| pub(crate) config: Ipv6DeviceConfiguration, |
| } |
| |
| impl<I: Instant> Default for Ipv6DeviceState<I> { |
| fn default() -> Ipv6DeviceState<I> { |
| Ipv6DeviceState { |
| retrans_timer: RETRANS_TIMER_DEFAULT, |
| route_discovery: Default::default(), |
| router_soliciations_remaining: None, |
| ip_state: Default::default(), |
| config: Default::default(), |
| } |
| } |
| } |
| |
| impl<I: Instant> AsRef<IpDeviceState<I, Ipv6>> for Ipv6DeviceState<I> { |
| fn as_ref(&self) -> &IpDeviceState<I, Ipv6> { |
| &self.ip_state |
| } |
| } |
| |
| impl<I: Instant> AsRef<IpDeviceConfiguration> for Ipv6DeviceState<I> { |
| fn as_ref(&self) -> &IpDeviceConfiguration { |
| &self.config.ip_config |
| } |
| } |
| |
| /// IPv4 and IPv6 state combined. |
| pub(crate) struct DualStackIpDeviceState<I: Instant> { |
| /// IPv4 state. |
| pub ipv4: Ipv4DeviceState<I>, |
| |
| /// IPv6 state. |
| pub ipv6: Ipv6DeviceState<I>, |
| } |
| |
| impl<I: Instant> Default for DualStackIpDeviceState<I> { |
| fn default() -> DualStackIpDeviceState<I> { |
| DualStackIpDeviceState { |
| ipv4: Ipv4DeviceState::default(), |
| ipv6: Ipv6DeviceState::default(), |
| } |
| } |
| } |
| |
| /// The various states an IP address can be on an interface. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub(crate) enum AddressState { |
| /// The address is assigned to an interface and can be considered bound to |
| /// it (all packets destined to the address will be accepted). |
| Assigned, |
| |
| /// The address is considered unassigned to an interface for normal |
| /// operations, but has the intention of being assigned in the future (e.g. |
| /// once NDP's Duplicate Address Detection is completed). |
| /// |
| /// When `dad_transmits_remaining` is `None`, then no more DAD messages need |
| /// to be sent and DAD may be resolved. |
| Tentative { dad_transmits_remaining: Option<NonZeroU8> }, |
| } |
| |
| impl AddressState { |
| /// Is this address assigned? |
| pub(crate) fn is_assigned(self) -> bool { |
| self == AddressState::Assigned |
| } |
| |
| /// Is this address tentative? |
| pub(crate) fn is_tentative(self) -> bool { |
| match self { |
| AddressState::Assigned => false, |
| AddressState::Tentative { dad_transmits_remaining: _ } => true, |
| } |
| } |
| } |
| |
| /// Configuration for a temporary IPv6 address assigned via SLAAC. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub(crate) struct TemporarySlaacConfig<Instant> { |
| /// The time at which the address is no longer valid. |
| pub(crate) valid_until: Instant, |
| /// The per-address DESYNC_FACTOR specified in RFC 8981 Section 3.4. |
| pub(crate) desync_factor: Duration, |
| /// The time at which the address was created. |
| pub(crate) creation_time: Instant, |
| /// The DAD_Counter parameter specified by RFC 8981 Section 3.3.2.1. This is |
| /// used to track the number of retries that occurred prior to picking this |
| /// address. |
| pub(crate) dad_counter: u8, |
| } |
| |
| /// A lifetime that may be forever/infinite. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| pub(crate) enum Lifetime<I> { |
| Finite(I), |
| Infinite, |
| } |
| |
| /// Configuration for an IPv6 address assigned via SLAAC. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub(crate) enum SlaacConfig<Instant> { |
| /// The address is static. |
| Static { |
| /// The lifetime of the address. |
| valid_until: Lifetime<Instant>, |
| }, |
| /// The address is a temporary address, as specified by [RFC 8981]. |
| /// |
| /// [RFC 8981]: https://tools.ietf.org/html/rfc8981 |
| Temporary(TemporarySlaacConfig<Instant>), |
| } |
| |
| /// The configuration for an IPv6 address. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub(crate) enum AddrConfig<Instant> { |
| /// Configured by stateless address autoconfiguration. |
| Slaac(SlaacConfig<Instant>), |
| |
| /// Manually configured. |
| Manual, |
| } |
| |
| impl<Instant> AddrConfig<Instant> { |
| /// The configuration for a link-local address configured via SLAAC. |
| /// |
| /// Per [RFC 4862 Section 5.3]: "A link-local address has an infinite preferred and valid |
| /// lifetime; it is never timed out." |
| /// |
| /// [RFC 4862 Section 5.3]: https://tools.ietf.org/html/rfc4862#section-5.3 |
| pub(crate) const SLAAC_LINK_LOCAL: Self = |
| Self::Slaac(SlaacConfig::Static { valid_until: Lifetime::Infinite }); |
| } |
| |
| /// Data associated with an IPv6 address on an interface. |
| // TODO(https://fxbug.dev/91753): Should this be generalized for loopback? |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub(crate) struct Ipv6AddressEntry<Instant> { |
| pub(crate) addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| pub(crate) state: AddressState, |
| pub(crate) config: AddrConfig<Instant>, |
| pub(crate) deprecated: bool, |
| } |
| |
| impl<Instant> Ipv6AddressEntry<Instant> { |
| pub(crate) fn new( |
| addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| state: AddressState, |
| config: AddrConfig<Instant>, |
| ) -> Self { |
| Self { addr_sub, state, config, deprecated: false } |
| } |
| |
| pub(crate) fn addr_sub(&self) -> &AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>> { |
| &self.addr_sub |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use crate::{context::testutil::DummyInstant, error::ExistsError}; |
| |
| #[test] |
| fn test_add_addr_ipv4() { |
| const ADDRESS: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]); |
| const PREFIX_LEN: u8 = 8; |
| |
| let mut ipv4 = IpDeviceState::<DummyInstant, Ipv4>::default(); |
| |
| assert_eq!(ipv4.add_addr(AddrSubnet::new(ADDRESS, PREFIX_LEN).unwrap()), Ok(())); |
| // Adding the same address with different prefix should fail. |
| assert_eq!( |
| ipv4.add_addr(AddrSubnet::new(ADDRESS, PREFIX_LEN + 1).unwrap()), |
| Err(ExistsError) |
| ); |
| } |
| |
| #[test] |
| fn test_add_addr_ipv6() { |
| const ADDRESS: Ipv6Addr = |
| Ipv6Addr::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6]); |
| const PREFIX_LEN: u8 = 8; |
| |
| let mut ipv6 = IpDeviceState::<DummyInstant, Ipv6>::default(); |
| |
| assert_eq!( |
| ipv6.add_addr(Ipv6AddressEntry::new( |
| AddrSubnet::new(ADDRESS, PREFIX_LEN).unwrap(), |
| AddressState::Tentative { dad_transmits_remaining: None }, |
| AddrConfig::Slaac(SlaacConfig::Static { valid_until: Lifetime::Infinite }), |
| )), |
| Ok(()) |
| ); |
| // Adding the same address with different prefix and configuration |
| // should fail. |
| assert_eq!( |
| ipv6.add_addr(Ipv6AddressEntry::new( |
| AddrSubnet::new(ADDRESS, PREFIX_LEN + 1).unwrap(), |
| AddressState::Assigned, |
| AddrConfig::Manual, |
| )), |
| Err(ExistsError) |
| ); |
| } |
| } |