| // 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. |
| |
| //! Shared code for implementing datagram sockets. |
| |
| use alloc::{ |
| collections::{HashMap, HashSet}, |
| vec::Vec, |
| }; |
| use core::{ |
| borrow::Borrow, |
| convert::Infallible as Never, |
| fmt::Debug, |
| hash::Hash, |
| marker::PhantomData, |
| num::{NonZeroU16, NonZeroU8}, |
| ops::{Deref, DerefMut}, |
| }; |
| |
| use derivative::Derivative; |
| use either::Either; |
| use net_types::{ |
| ip::{GenericOverIp, Ip, IpAddress, IpVersionMarker, Ipv4, Ipv6}, |
| MulticastAddr, MulticastAddress as _, SpecifiedAddr, ZonedAddr, |
| }; |
| use packet::BufferMut; |
| use packet_formats::ip::IpProtoExt; |
| use thiserror::Error; |
| |
| use crate::{ |
| algorithm::ProtocolFlowId, |
| context::{ReferenceNotifiers, RngContext}, |
| convert::{BidirectionalConverter, OwnedOrRefsBidirectionalConverter}, |
| device::{self, AnyDevice, DeviceIdContext, StrongId as _, WeakId as _}, |
| error::ExistsError, |
| error::{LocalAddressError, NotFoundError, RemoteAddressError, SocketError, ZonedAddressError}, |
| filter::TransportPacketSerializer, |
| inspect::{Inspector, InspectorDeviceExt}, |
| ip::{ |
| socket::{ |
| IpSock, IpSockCreateAndSendError, IpSockCreationError, IpSockSendError, |
| IpSocketHandler, SendOneShotIpPacketError, SendOptions, |
| }, |
| EitherDeviceId, HopLimits, MulticastMembershipHandler, ResolveRouteError, |
| TransportIpContext, |
| }, |
| socket::{ |
| self, |
| address::{ |
| dual_stack_remote_ip, try_unmap, AddrVecIter, ConnAddr, ConnInfoAddr, ConnIpAddr, |
| DualStackConnIpAddr, DualStackListenerIpAddr, DualStackRemoteIp, ListenerAddr, |
| ListenerIpAddr, SocketIpAddr, TryUnmapResult, |
| }, |
| AddrVec, BoundSocketMap, EitherStack, InsertError, MaybeDualStack, |
| NotDualStackCapableError, Shutdown, ShutdownType, SocketMapAddrSpec, |
| SocketMapConflictPolicy, SocketMapStateSpec, StrictlyZonedAddr, |
| }, |
| sync::{MapRcNotifier, RemoveResourceResult, RemoveResourceResultWithContext, RwLock}, |
| }; |
| |
| /// Datagram demultiplexing map. |
| pub(crate) type BoundSockets<I, D, A, S> = BoundSocketMap<I, D, A, S>; |
| |
| /// Top-level struct kept in datagram socket references. |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = ""))] |
| pub struct ReferenceState<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| pub(crate) state: RwLock<SocketState<I, D, S>>, |
| pub(crate) external_data: S::ExternalData<I>, |
| } |
| |
| // Local aliases for brevity. |
| type PrimaryRc<I, D, S> = crate::sync::PrimaryRc<ReferenceState<I, D, S>>; |
| pub(crate) type StrongRc<I, D, S> = crate::sync::StrongRc<ReferenceState<I, D, S>>; |
| pub(crate) type WeakRc<I, D, S> = crate::sync::WeakRc<ReferenceState<I, D, S>>; |
| |
| /// A set containing all datagram sockets for a given implementation. |
| #[derive(Derivative, GenericOverIp)] |
| #[derivative(Default(bound = ""))] |
| #[generic_over_ip(I, Ip)] |
| pub struct DatagramSocketSet<I: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| HashMap<StrongRc<I, D, S>, PrimaryRc<I, D, S>>, |
| ); |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> Debug for DatagramSocketSet<I, D, S> { |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| let Self(rc) = self; |
| f.debug_list().entries(rc.keys().map(StrongRc::debug_id)).finish() |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> Deref for DatagramSocketSet<I, D, S> { |
| type Target = HashMap<StrongRc<I, D, S>, PrimaryRc<I, D, S>>; |
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> DerefMut for DatagramSocketSet<I, D, S> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.0 |
| } |
| } |
| |
| pub trait IpExt: crate::ip::IpExt + DualStackIpExt + crate::ip::icmp::IcmpIpExt {} |
| impl<I: crate::ip::IpExt + DualStackIpExt + crate::ip::icmp::IcmpIpExt> IpExt for I {} |
| |
| #[derive(Derivative, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| #[derivative(Debug(bound = ""))] |
| pub enum SocketState<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| Unbound(UnboundSocketState<I, D, S>), |
| Bound(BoundSocketState<I, D, S>), |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<IpOptions<I, D, S>> |
| for SocketState<I, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<I, D, S> { |
| match self { |
| Self::Unbound(unbound) => unbound.as_ref(), |
| Self::Bound(bound) => bound.as_ref(), |
| } |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> SocketState<I, D, S> { |
| fn to_socket_info(&self) -> SocketInfo<I::Addr, D> { |
| match self { |
| Self::Unbound(_) => SocketInfo::Unbound, |
| Self::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state, sharing: _ } => { |
| let ListenerState { addr, ip_options: _ } = state; |
| SocketInfo::Listener(addr.clone().into()) |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| SocketInfo::Connected(S::conn_info_from_state(&state)) |
| } |
| } |
| } |
| } |
| } |
| |
| /// Record inspect information generic to each datagram protocol. |
| pub(crate) fn record_common_info<N>(&self, inspector: &mut N, socket_id: &S::SocketId<I, D>) |
| where |
| N: Inspector + InspectorDeviceExt<D>, |
| { |
| inspector.record_debug_child(socket_id, |node| { |
| node.record_str("TransportProtocol", S::NAME); |
| node.record_str("NetworkProtocol", I::NAME); |
| |
| let socket_info = self.to_socket_info(); |
| let (local, remote) = match socket_info { |
| SocketInfo::Unbound => (None, None), |
| SocketInfo::Listener(ListenerInfo { local_ip, local_identifier }) => ( |
| Some(( |
| local_ip.map_or_else( |
| || ZonedAddr::Unzoned(I::UNSPECIFIED_ADDRESS), |
| |addr| addr.into_inner_without_witness(), |
| ), |
| local_identifier, |
| )), |
| None, |
| ), |
| SocketInfo::Connected(ConnInfo { |
| local_ip, |
| local_identifier, |
| remote_ip, |
| remote_identifier, |
| }) => ( |
| Some((local_ip.into_inner_without_witness(), local_identifier)), |
| Some((remote_ip.into_inner_without_witness(), remote_identifier)), |
| ), |
| }; |
| node.record_local_socket_addr::<N, _, _, _>(local); |
| node.record_remote_socket_addr::<N, _, _, _>(remote); |
| }); |
| } |
| } |
| |
| /// State associated with a Bound Socket. |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = "D: Debug"))] |
| pub struct BoundSocketState<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| // The type of bound socket (e.g. Listener vs. Connected), and any |
| // type-specific state. |
| pub(crate) socket_type: BoundSocketStateType<I, D, S>, |
| // The original bound address of the socket, as requested by the caller. |
| // `None` if: |
| // * the socket was connected from unbound, or |
| // * listen was called without providing a local port. |
| pub(crate) original_bound_addr: Option<S::ListenerIpAddr<I>>, |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<IpOptions<I, D, S>> |
| for BoundSocketState<I, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<I, D, S> { |
| let BoundSocketState { socket_type, original_bound_addr: _ } = self; |
| match socket_type { |
| BoundSocketStateType::Listener { state, sharing: _ } => state.as_ref(), |
| BoundSocketStateType::Connected { state, sharing: _ } => state.as_ref(), |
| } |
| } |
| } |
| |
| // State for the sub-types of bound socket (e.g. Listener or Connected). |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = "D: Debug"))] |
| pub(crate) enum BoundSocketStateType<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| Listener { state: ListenerState<I, D, S>, sharing: S::SharingState }, |
| Connected { state: S::ConnState<I, D>, sharing: S::SharingState }, |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<IpOptions<I, D, S>> |
| for BoundSocketStateType<I, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<I, D, S> { |
| match self { |
| Self::Listener { state, sharing: _ } => state.as_ref(), |
| Self::Connected { state, sharing: _ } => state.as_ref(), |
| } |
| } |
| } |
| |
| impl<I: Ip, D: device::Id, A: SocketMapAddrSpec, S: DatagramSocketMapSpec<I, D, A>> |
| BoundSockets<I, D, A, S> |
| { |
| pub(crate) fn iter_receivers( |
| &self, |
| (src_ip, src_port): (Option<SocketIpAddr<I::Addr>>, Option<A::RemoteIdentifier>), |
| (dst_ip, dst_port): (SocketIpAddr<I::Addr>, A::LocalIdentifier), |
| device: D, |
| ) -> Option< |
| FoundSockets< |
| AddrEntry<'_, I, D, A, S>, |
| impl Iterator<Item = AddrEntry<'_, I, D, A, S>> + '_, |
| >, |
| > { |
| self.lookup((src_ip, src_port), (dst_ip, dst_port), device) |
| } |
| } |
| |
| pub(crate) enum FoundSockets<A, It> { |
| /// A single recipient was found for the address. |
| Single(A), |
| /// Indicates the looked-up address was multicast, and holds an iterator of |
| /// the found receivers. |
| Multicast(It), |
| } |
| |
| impl<I: Ip, D: device::Id, A: SocketMapAddrSpec, S: DatagramSocketMapSpec<I, D, A>> |
| BoundSocketMap<I, D, A, S> |
| { |
| /// Finds the socket(s) that should receive an incoming packet. |
| /// |
| /// Uses the provided addresses and receiving device to look up sockets that |
| /// should receive a matching incoming packet. Returns `None` if no sockets |
| /// were found, or the results of the lookup. |
| fn lookup( |
| &self, |
| (src_ip, src_port): (Option<SocketIpAddr<I::Addr>>, Option<A::RemoteIdentifier>), |
| (dst_ip, dst_port): (SocketIpAddr<I::Addr>, A::LocalIdentifier), |
| device: D, |
| ) -> Option< |
| FoundSockets< |
| AddrEntry<'_, I, D, A, S>, |
| impl Iterator<Item = AddrEntry<'_, I, D, A, S>> + '_, |
| >, |
| > { |
| let mut matching_entries = AddrVecIter::with_device( |
| match (src_ip, src_port) { |
| (Some(specified_src_ip), Some(src_port)) => { |
| ConnIpAddr { local: (dst_ip, dst_port), remote: (specified_src_ip, src_port) } |
| .into() |
| } |
| _ => ListenerIpAddr { addr: Some(dst_ip), identifier: dst_port }.into(), |
| }, |
| device, |
| ) |
| .filter_map(move |addr: AddrVec<I, D, A>| match addr { |
| AddrVec::Listen(l) => { |
| self.listeners().get_by_addr(&l).map(|state| AddrEntry::Listen(state, l)) |
| } |
| AddrVec::Conn(c) => self.conns().get_by_addr(&c).map(|state| AddrEntry::Conn(state, c)), |
| }); |
| |
| if dst_ip.addr().is_multicast() { |
| Some(FoundSockets::Multicast(matching_entries)) |
| } else { |
| let single_entry: Option<_> = matching_entries.next(); |
| single_entry.map(FoundSockets::Single) |
| } |
| } |
| } |
| |
| pub(crate) enum AddrEntry<'a, I: Ip, D, A: SocketMapAddrSpec, S: SocketMapStateSpec> { |
| Listen(&'a S::ListenerAddrState, ListenerAddr<ListenerIpAddr<I::Addr, A::LocalIdentifier>, D>), |
| Conn( |
| &'a S::ConnAddrState, |
| ConnAddr<ConnIpAddr<I::Addr, A::LocalIdentifier, A::RemoteIdentifier>, D>, |
| ), |
| } |
| |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = ""), Default(bound = ""))] |
| pub struct UnboundSocketState<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| device: Option<D>, |
| sharing: S::SharingState, |
| ip_options: IpOptions<I, D, S>, |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<IpOptions<I, D, S>> |
| for UnboundSocketState<I, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<I, D, S> { |
| &self.ip_options |
| } |
| } |
| |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = ""))] |
| pub(crate) struct ListenerState<I: IpExt, D: device::WeakId, S: DatagramSocketSpec + ?Sized> { |
| pub(crate) ip_options: IpOptions<I, D, S>, |
| pub(crate) addr: ListenerAddr<S::ListenerIpAddr<I>, D>, |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<IpOptions<I, D, S>> |
| for ListenerState<I, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<I, D, S> { |
| &self.ip_options |
| } |
| } |
| |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = "D: Debug"))] |
| pub struct ConnState< |
| WireI: IpExt, |
| SocketI: IpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec + ?Sized, |
| > { |
| pub(crate) socket: IpSock<WireI, D>, |
| pub(crate) ip_options: IpOptions<SocketI, D, S>, |
| pub(crate) shutdown: Shutdown, |
| pub(crate) addr: ConnAddr< |
| ConnIpAddr< |
| WireI::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| D, |
| >, |
| /// Determines whether a call to disconnect this socket should also clear |
| /// the device on the socket address. |
| /// |
| /// This will only be `true` if |
| /// 1) the corresponding address has a bound device |
| /// 2) the local address does not require a zone |
| /// 3) the remote address does require a zone |
| /// 4) the device was not set via [`set_unbound_device`] |
| /// |
| /// In that case, when the socket is disconnected, the device should be |
| /// cleared since it was set as part of a `connect` call, not explicitly. |
| /// |
| /// TODO(https://fxbug.dev/42061727): Implement this by changing socket |
| /// addresses. |
| pub(crate) clear_device_on_disconnect: bool, |
| |
| /// The extra state for the connection. |
| /// |
| /// For UDP it should be [`()`], for ICMP it should be [`NonZeroU16`] to |
| /// remember the remote ID set by connect. |
| pub(crate) extra: S::ConnStateExtra, |
| } |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> |
| AsRef<IpOptions<SocketI, D, S>> for ConnState<WireI, SocketI, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<SocketI, D, S> { |
| &self.ip_options |
| } |
| } |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<Shutdown> |
| for ConnState<WireI, SocketI, D, S> |
| { |
| fn as_ref(&self) -> &Shutdown { |
| &self.shutdown |
| } |
| } |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> |
| AsMut<IpOptions<SocketI, D, S>> for ConnState<WireI, SocketI, D, S> |
| { |
| fn as_mut(&mut self) -> &mut IpOptions<SocketI, D, S> { |
| &mut self.ip_options |
| } |
| } |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsMut<Shutdown> |
| for ConnState<WireI, SocketI, D, S> |
| { |
| fn as_mut(&mut self) -> &mut Shutdown { |
| &mut self.shutdown |
| } |
| } |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> |
| ConnState<WireI, SocketI, D, S> |
| { |
| pub(crate) fn should_receive(&self) -> bool { |
| let Self { |
| shutdown, |
| socket: _, |
| ip_options: _, |
| clear_device_on_disconnect: _, |
| addr: _, |
| extra: _, |
| } = self; |
| let Shutdown { receive, send: _ } = shutdown; |
| !*receive |
| } |
| } |
| |
| /// Connection state belong to either this-stack or the other-stack. |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = ""))] |
| pub enum DualStackConnState< |
| I: IpExt + DualStackIpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec + ?Sized, |
| > { |
| /// The [`ConnState`] for a socked connected with [`I::Version`]. |
| ThisStack(ConnState<I, I, D, S>), |
| /// The [`ConnState`] for a socked connected with [`I::OtherVersion`]. |
| OtherStack(ConnState<I::OtherVersion, I, D, S>), |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<IpOptions<I, D, S>> |
| for DualStackConnState<I, D, S> |
| { |
| fn as_ref(&self) -> &IpOptions<I, D, S> { |
| match self { |
| DualStackConnState::ThisStack(state) => state.as_ref(), |
| DualStackConnState::OtherStack(state) => state.as_ref(), |
| } |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsMut<IpOptions<I, D, S>> |
| for DualStackConnState<I, D, S> |
| { |
| fn as_mut(&mut self) -> &mut IpOptions<I, D, S> { |
| match self { |
| DualStackConnState::ThisStack(state) => state.as_mut(), |
| DualStackConnState::OtherStack(state) => state.as_mut(), |
| } |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<Shutdown> |
| for DualStackConnState<I, D, S> |
| { |
| fn as_ref(&self) -> &Shutdown { |
| match self { |
| DualStackConnState::ThisStack(state) => state.as_ref(), |
| DualStackConnState::OtherStack(state) => state.as_ref(), |
| } |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsMut<Shutdown> |
| for DualStackConnState<I, D, S> |
| { |
| fn as_mut(&mut self) -> &mut Shutdown { |
| match self { |
| DualStackConnState::ThisStack(state) => state.as_mut(), |
| DualStackConnState::OtherStack(state) => state.as_mut(), |
| } |
| } |
| } |
| |
| #[derive(Derivative, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| #[derivative(Clone(bound = ""), Debug, Default(bound = ""))] |
| pub struct IpOptions<I: IpExt, D: device::WeakId, S: DatagramSocketSpec + ?Sized> { |
| multicast_memberships: MulticastMemberships<I::Addr, D>, |
| multicast_interface: Option<D>, |
| hop_limits: SocketHopLimits<I>, |
| other_stack: S::OtherStackIpOptions<I, D>, |
| transparent: bool, |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> AsRef<Self> for IpOptions<I, D, S> { |
| fn as_ref(&self) -> &Self { |
| self |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> IpOptions<I, D, S> { |
| pub(crate) fn other_stack(&self) -> &S::OtherStackIpOptions<I, D> { |
| &self.other_stack |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> SendOptions<I, D> for IpOptions<I, D, S> { |
| fn hop_limit(&self, destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8> { |
| let Self { hop_limits: SocketHopLimits { unicast, multicast, version: _ }, .. } = self; |
| if destination.is_multicast() { |
| *multicast |
| } else { |
| *unicast |
| } |
| } |
| |
| fn multicast_interface(&self) -> Option<&D> { |
| self.multicast_interface.as_ref() |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] |
| pub(crate) struct SocketHopLimits<I: Ip> { |
| pub(crate) unicast: Option<NonZeroU8>, |
| // TODO(https://fxbug.dev/42059735): Make this an Option<u8> to allow sending |
| // multicast packets destined only for the local machine. |
| pub(crate) multicast: Option<NonZeroU8>, |
| // An unused marker type signifying the IP version for which these hop |
| // limits are valid. Including this helps prevent using the wrong hop limits |
| // when operating on dualstack sockets. |
| pub(crate) version: IpVersionMarker<I>, |
| } |
| |
| impl<I: Ip> SocketHopLimits<I> { |
| pub(crate) fn set_unicast(value: Option<NonZeroU8>) -> impl FnOnce(&mut Self) { |
| move |limits| limits.unicast = value |
| } |
| |
| pub(crate) fn set_multicast(value: Option<NonZeroU8>) -> impl FnOnce(&mut Self) { |
| move |limits| limits.multicast = value |
| } |
| |
| fn get_limits_with_defaults(&self, defaults: &HopLimits) -> HopLimits { |
| let Self { unicast, multicast, version: _ } = self; |
| HopLimits { |
| unicast: unicast.unwrap_or(defaults.unicast), |
| multicast: multicast.unwrap_or(defaults.multicast), |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, Derivative)] |
| #[derivative(Default(bound = ""))] |
| pub(crate) struct MulticastMemberships<A, D>(HashSet<(MulticastAddr<A>, D)>); |
| |
| #[cfg_attr(test, derive(Debug, PartialEq))] |
| pub(crate) enum MulticastMembershipChange { |
| Join, |
| Leave, |
| } |
| |
| impl<A: Eq + Hash, D: device::WeakId> MulticastMemberships<A, D> { |
| pub(crate) fn apply_membership_change( |
| &mut self, |
| address: MulticastAddr<A>, |
| device: &D, |
| want_membership: bool, |
| ) -> Option<MulticastMembershipChange> { |
| let device = device.clone(); |
| |
| let Self(map) = self; |
| if want_membership { |
| map.insert((address, device)).then(|| MulticastMembershipChange::Join) |
| } else { |
| map.remove(&(address, device)).then(|| MulticastMembershipChange::Leave) |
| } |
| } |
| } |
| |
| impl<A: Eq + Hash, D: Eq + Hash> IntoIterator for MulticastMemberships<A, D> { |
| type Item = (MulticastAddr<A>, D); |
| type IntoIter = <HashSet<(MulticastAddr<A>, D)> as IntoIterator>::IntoIter; |
| |
| fn into_iter(self) -> Self::IntoIter { |
| let Self(memberships) = self; |
| memberships.into_iter() |
| } |
| } |
| |
| impl<A: IpAddress, D: crate::device::Id, LI, RI: Copy> ConnAddr<ConnIpAddr<A, LI, RI>, D> { |
| pub(crate) fn from_protocol_flow_and_local_port( |
| id: &ProtocolFlowId<SocketIpAddr<A>, RI>, |
| local_port: LI, |
| ) -> Self { |
| Self { |
| ip: ConnIpAddr { |
| local: (*id.local_addr(), local_port), |
| remote: (*id.remote_addr(), *id.remote_port()), |
| }, |
| device: None, |
| } |
| } |
| } |
| |
| fn leave_all_joined_groups<A: IpAddress, BC, CC: MulticastMembershipHandler<A::Version, BC>>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| memberships: MulticastMemberships<A, CC::WeakDeviceId>, |
| ) { |
| for (addr, device) in memberships { |
| let Some(device) = device.upgrade() else { |
| continue; |
| }; |
| core_ctx.leave_multicast_group(bindings_ctx, &device, addr) |
| } |
| } |
| |
| /// Identifies a flow for a datagram socket. |
| #[derive(Hash)] |
| pub struct DatagramFlowId<A: IpAddress, RI> { |
| pub(crate) local_ip: SocketIpAddr<A>, |
| pub(crate) remote_ip: SocketIpAddr<A>, |
| pub(crate) remote_id: RI, |
| } |
| |
| pub(crate) trait DatagramStateContext<I: IpExt, BC, S: DatagramSocketSpec>: |
| DeviceIdContext<AnyDevice> |
| { |
| /// The core context passed to the callback provided to methods. |
| type SocketsStateCtx<'a>: DatagramBoundStateContext<I, BC, S> |
| + DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId>; |
| |
| /// Calls the function with mutable access to the set with all datagram |
| /// sockets. |
| fn with_all_sockets_mut<O, F: FnOnce(&mut DatagramSocketSet<I, Self::WeakDeviceId, S>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the function with immutable access to the set with all datagram |
| /// sockets. |
| fn with_all_sockets<O, F: FnOnce(&DatagramSocketSet<I, Self::WeakDeviceId, S>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the function with an immutable reference to the given socket's |
| /// state. |
| fn with_socket_state< |
| O, |
| F: FnOnce(&mut Self::SocketsStateCtx<'_>, &SocketState<I, Self::WeakDeviceId, S>) -> O, |
| >( |
| &mut self, |
| id: &S::SocketId<I, Self::WeakDeviceId>, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the function with a mutable reference to the given socket's state. |
| fn with_socket_state_mut< |
| O, |
| F: FnOnce(&mut Self::SocketsStateCtx<'_>, &mut SocketState<I, Self::WeakDeviceId, S>) -> O, |
| >( |
| &mut self, |
| id: &S::SocketId<I, Self::WeakDeviceId>, |
| cb: F, |
| ) -> O; |
| |
| /// Call `f` with each socket's state. |
| fn for_each_socket< |
| F: FnMut( |
| &mut Self::SocketsStateCtx<'_>, |
| &S::SocketId<I, Self::WeakDeviceId>, |
| &SocketState<I, Self::WeakDeviceId, S>, |
| ), |
| >( |
| &mut self, |
| cb: F, |
| ); |
| } |
| |
| pub(crate) trait DatagramBoundStateContext<I: IpExt + DualStackIpExt, BC, S: DatagramSocketSpec>: |
| DeviceIdContext<AnyDevice> |
| { |
| /// The core context passed to the callback provided to methods. |
| type IpSocketsCtx<'a>: TransportIpContext<I, BC> |
| + MulticastMembershipHandler<I, BC> |
| + DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId>; |
| |
| /// Context for dual-stack socket state access. |
| /// |
| /// This type type provides access, via an implementation of the |
| /// [`DualStackDatagramBoundStateContext`] trait, to state necessary for |
| /// implementing dual-stack socket operations. While a type must always be |
| /// provided, implementations of [`DatagramBoundStateContext`] for socket |
| /// types that don't support dual-stack operation (like ICMP and raw IP |
| /// sockets, and UDPv4) can use the [`UninstantiableDualStackContext`] type, |
| /// which is uninstantiable. |
| type DualStackContext: DualStackDatagramBoundStateContext< |
| I, |
| BC, |
| S, |
| DeviceId = Self::DeviceId, |
| WeakDeviceId = Self::WeakDeviceId, |
| >; |
| |
| /// Context for single-stack socket access. |
| /// |
| /// This type provides access, via an implementation of the |
| /// [`NonDualStackDatagramBoundStateContext`] trait, to functionality |
| /// necessary to implement sockets that do not support dual-stack operation. |
| type NonDualStackContext: NonDualStackDatagramBoundStateContext< |
| I, |
| BC, |
| S, |
| DeviceId = Self::DeviceId, |
| WeakDeviceId = Self::WeakDeviceId, |
| >; |
| |
| /// Calls the function with an immutable reference to the datagram sockets. |
| fn with_bound_sockets< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &BoundSockets< |
| I, |
| Self::WeakDeviceId, |
| S::AddrSpec, |
| S::SocketMapSpec<I, Self::WeakDeviceId>, |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the function with a mutable reference to the datagram sockets. |
| fn with_bound_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut BoundSockets< |
| I, |
| Self::WeakDeviceId, |
| S::AddrSpec, |
| S::SocketMapSpec<I, Self::WeakDeviceId>, |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Provides access to either the dual-stack or non-dual-stack context. |
| /// |
| /// For socket types that don't support dual-stack operation (like ICMP, |
| /// raw IP sockets, and UDPv4), this method should always return a reference |
| /// to the non-dual-stack context to allow the caller to access |
| /// non-dual-stack state. Otherwise it should provide an instance of the |
| /// `DualStackContext`, which can be used by the caller to access dual-stack |
| /// state. |
| fn dual_stack_context( |
| &mut self, |
| ) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext>; |
| |
| /// Calls the function with only the inner context. |
| fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O; |
| } |
| |
| // Implement `GenericOverIp` for a `MaybeDualStack` whose `DS` and `NDS` also |
| // implement `GenericOverIp`. |
| impl<I: DualStackIpExt, DS: GenericOverIp<I>, NDS: GenericOverIp<I>> GenericOverIp<I> |
| for MaybeDualStack<DS, NDS> |
| { |
| type Type = MaybeDualStack<<DS as GenericOverIp<I>>::Type, <NDS as GenericOverIp<I>>::Type>; |
| } |
| |
| impl<'a, DS, NDS> MaybeDualStack<&'a mut DS, &'a mut NDS> { |
| fn to_converter<I: IpExt, BC, S: DatagramSocketSpec>( |
| self, |
| ) -> MaybeDualStack<DS::Converter, NDS::Converter> |
| where |
| DS: DualStackDatagramBoundStateContext<I, BC, S>, |
| NDS: NonDualStackDatagramBoundStateContext<I, BC, S>, |
| { |
| match self { |
| MaybeDualStack::DualStack(ds) => MaybeDualStack::DualStack(ds.converter()), |
| MaybeDualStack::NotDualStack(nds) => MaybeDualStack::NotDualStack(nds.converter()), |
| } |
| } |
| } |
| |
| /// Provides access to dual-stack socket state. |
| pub(crate) trait DualStackDatagramBoundStateContext<I: IpExt, BC, S: DatagramSocketSpec>: |
| DeviceIdContext<AnyDevice> |
| { |
| /// The core context passed to the callbacks to methods. |
| type IpSocketsCtx<'a>: TransportIpContext<I, BC> |
| + DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId> |
| // Allow creating IP sockets for the other IP version. |
| + TransportIpContext<I::OtherVersion, BC>; |
| |
| /// Returns if the socket state indicates dual-stack operation is enabled. |
| fn dual_stack_enabled(&self, state: &impl AsRef<IpOptions<I, Self::WeakDeviceId, S>>) -> bool; |
| |
| /// Type for [`SendOptions`] for the other stack. |
| type OtherSendOptions: SendOptions<I::OtherVersion, Self::WeakDeviceId>; |
| |
| /// Returns the [`SendOptions`] to use for packets in the other stack. |
| fn to_other_send_options<'a>( |
| &self, |
| state: &'a IpOptions<I, Self::WeakDeviceId, S>, |
| ) -> &'a Self::OtherSendOptions; |
| |
| /// Asserts that the socket state indicates dual-stack operation is enabled. |
| /// |
| /// Provided trait function. |
| fn assert_dual_stack_enabled(&self, state: &impl AsRef<IpOptions<I, Self::WeakDeviceId, S>>) { |
| debug_assert!(self.dual_stack_enabled(state), "socket must be dual-stack enabled") |
| } |
| |
| /// A type for converting between address types. |
| /// |
| /// This allows converting between the possibly-dual-stack |
| /// `S::ListenerIpAddr<I>` and the concrete dual-stack |
| /// [`DualStackListenerIpAddr`]. |
| type Converter: OwnedOrRefsBidirectionalConverter< |
| S::ListenerIpAddr<I>, |
| DualStackListenerIpAddr<I::Addr, <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| > + OwnedOrRefsBidirectionalConverter< |
| S::ConnIpAddr<I>, |
| DualStackConnIpAddr< |
| I::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| > + OwnedOrRefsBidirectionalConverter< |
| S::ConnState<I, Self::WeakDeviceId>, |
| DualStackConnState<I, Self::WeakDeviceId, S>, |
| >; |
| |
| /// Returns an instance of a type that implements [`BidirectionalConverter`] |
| /// for addresses. |
| /// |
| /// The returned object can be used to convert between the |
| /// `S::ListenerIpAddr<I>` and the appropriate [`DualStackListenerIpAddr`]. |
| fn converter(&self) -> Self::Converter; |
| |
| /// Converts a socket ID to a bound socket ID. |
| /// |
| /// Converts a socket ID for IP version `I` into a bound socket ID that can |
| /// be inserted into the demultiplexing map for IP version `I::OtherVersion`. |
| fn to_other_bound_socket_id( |
| &self, |
| id: &S::SocketId<I, Self::WeakDeviceId>, |
| ) -> <S::SocketMapSpec<I::OtherVersion, Self::WeakDeviceId> as DatagramSocketMapSpec< |
| I::OtherVersion, |
| Self::WeakDeviceId, |
| S::AddrSpec, |
| >>::BoundSocketId; |
| |
| /// demultiplexing maps. |
| fn with_both_bound_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut BoundSockets< |
| I, |
| Self::WeakDeviceId, |
| S::AddrSpec, |
| S::SocketMapSpec<I, Self::WeakDeviceId>, |
| >, |
| &mut BoundSockets< |
| I::OtherVersion, |
| Self::WeakDeviceId, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, Self::WeakDeviceId>, |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the provided callback with mutable access to the demultiplexing |
| /// map for the other IP version. |
| fn with_other_bound_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut BoundSockets< |
| I::OtherVersion, |
| Self::WeakDeviceId, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, Self::WeakDeviceId>, |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the provided callback with access to the `IpSocketsCtx`. |
| fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O; |
| } |
| |
| /// Provides access to socket state for a single IP version. |
| pub(crate) trait NonDualStackDatagramBoundStateContext<I: IpExt, BC, S: DatagramSocketSpec>: |
| DeviceIdContext<AnyDevice> |
| { |
| /// A type for converting between address types. |
| /// |
| /// This allows converting between the possibly-dual-stack |
| /// `S::ListenerIpAddr<I>` and the concrete not-dual-stack |
| /// [``ListenerIpAddr`]. |
| type Converter: OwnedOrRefsBidirectionalConverter< |
| S::ListenerIpAddr<I>, |
| ListenerIpAddr<I::Addr, <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| > + OwnedOrRefsBidirectionalConverter< |
| S::ConnIpAddr<I>, |
| ConnIpAddr< |
| I::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| > + OwnedOrRefsBidirectionalConverter< |
| S::ConnState<I, Self::WeakDeviceId>, |
| ConnState<I, I, Self::WeakDeviceId, S>, |
| >; |
| |
| /// Returns an instance of a type that implements [`BidirectionalConverter`] |
| /// for addresses. |
| /// |
| /// The returned object can be used to convert between the |
| /// `S::ListenerIpAddr<I>` and the appropriate `ListenerIpAddr`. |
| fn converter(&self) -> Self::Converter; |
| } |
| |
| pub(crate) trait DatagramStateBindingsContext<I: Ip, S>: |
| RngContext + ReferenceNotifiers |
| { |
| } |
| impl<BC: RngContext + ReferenceNotifiers, I: Ip, S> DatagramStateBindingsContext<I, S> for BC {} |
| |
| /// Types and behavior for datagram socket demultiplexing map. |
| /// |
| /// `I: Ip` describes the type of packets that can be received by sockets in |
| /// the map. |
| pub trait DatagramSocketMapSpec<I: Ip, D: device::Id, A: SocketMapAddrSpec>: |
| SocketMapStateSpec<ListenerId = Self::BoundSocketId, ConnId = Self::BoundSocketId> |
| + SocketMapConflictPolicy< |
| ListenerAddr<ListenerIpAddr<I::Addr, A::LocalIdentifier>, D>, |
| <Self as SocketMapStateSpec>::ListenerSharingState, |
| I, |
| D, |
| A, |
| > + SocketMapConflictPolicy< |
| ConnAddr<ConnIpAddr<I::Addr, A::LocalIdentifier, A::RemoteIdentifier>, D>, |
| <Self as SocketMapStateSpec>::ConnSharingState, |
| I, |
| D, |
| A, |
| > |
| { |
| /// The type of IDs stored in a [`BoundSocketMap`] for which this is the |
| /// specification. |
| /// |
| /// This can be the same as [`DatagramSocketSpec::SocketId`] but doesn't |
| /// have to be. In the case of |
| /// dual-stack sockets, for example, an IPv4 socket will have type |
| /// `DatagramSocketSpec::SocketId<Ipv4>` but the IPv4 demultiplexing map |
| /// might have `BoundSocketId=Either<DatagramSocketSpec::SocketId<Ipv4>, |
| /// DatagramSocketSpec::SocketId<Ipv6>>` to allow looking up IPv6 sockets |
| /// when receiving IPv4 packets. |
| type BoundSocketId: Clone + Debug; |
| } |
| |
| /// Common features of dual-stack sockets that vary by IP version. |
| /// |
| /// This trait exists to provide per-IP-version associated types that are |
| /// useful for implementing dual-stack sockets. The types are intentionally |
| /// asymmetric - `DualStackIpExt::Xxx` has a different shape for the [`Ipv4`] |
| /// and [`Ipv6`] impls. |
| pub trait DualStackIpExt: super::DualStackIpExt { |
| /// The type of socket that can receive an IP packet. |
| /// |
| /// For `Ipv4`, this is [`EitherIpSocket<S>`], and for `Ipv6` it is just |
| /// `S::SocketId<Ipv6>`. |
| /// |
| /// [`EitherIpSocket<S>]`: [EitherIpSocket] |
| type DualStackBoundSocketId<D: device::WeakId, S: DatagramSocketSpec>: Clone + Debug + Eq; |
| |
| /// The IP options type for the other stack that will be held for a socket. |
| /// |
| /// For [`Ipv4`], this is `()`, and for [`Ipv6`] it is `State`. For a |
| /// protocol like UDP or TCP where the IPv6 socket is dual-stack capable, |
| /// the generic state struct can have a field with type |
| /// `I::OtherStackIpOptions<Ipv4InIpv6Options>`. |
| type OtherStackIpOptions<State: Clone + Debug + Default + Send + Sync>: Clone |
| + Debug |
| + Default |
| + Send |
| + Sync; |
| |
| /// A listener address for dual-stack operation. |
| type DualStackListenerIpAddr<LocalIdentifier: Clone + Debug + Send + Sync + Into<NonZeroU16>>: Clone |
| + Debug |
| + Send |
| + Sync |
| + Into<(Option<SpecifiedAddr<Self::Addr>>, NonZeroU16)>; |
| |
| /// A connected address for dual-stack operation. |
| type DualStackConnIpAddr<S: DatagramSocketSpec>: Clone |
| + Debug |
| + Into<ConnInfoAddr<Self::Addr, <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier>>; |
| |
| /// Connection state for a dual-stack socket. |
| type DualStackConnState<D: device::WeakId, S: DatagramSocketSpec>: Debug |
| + AsRef<IpOptions<Self, D, S>> |
| + AsMut<IpOptions<Self, D, S>> |
| + Send |
| + Sync; |
| |
| /// Convert a socket ID into a `Self::DualStackBoundSocketId`. |
| /// |
| /// For coherency reasons this can't be a `From` bound on |
| /// `DualStackBoundSocketId`. If more methods are added, consider moving |
| /// this to its own dedicated trait that bounds `DualStackBoundSocketId`. |
| fn into_dual_stack_bound_socket_id<D: device::WeakId, S: DatagramSocketSpec>( |
| id: S::SocketId<Self, D>, |
| ) -> Self::DualStackBoundSocketId<D, S> |
| where |
| Self: IpExt; |
| |
| /// Retrieves the associated connection address from the connection state. |
| fn conn_addr_from_state<D: device::WeakId, S: DatagramSocketSpec>( |
| state: &Self::DualStackConnState<D, S>, |
| ) -> ConnAddr<Self::DualStackConnIpAddr<S>, D>; |
| } |
| |
| /// An IP Socket ID that is either `Ipv4` or `Ipv6`. |
| #[derive(Derivative)] |
| #[derivative( |
| Clone(bound = ""), |
| Debug(bound = ""), |
| Eq(bound = "S::SocketId<Ipv4, D>: Eq, S::SocketId<Ipv6, D>: Eq"), |
| PartialEq(bound = "S::SocketId<Ipv4, D>: PartialEq, S::SocketId<Ipv6, D>: PartialEq") |
| )] |
| pub enum EitherIpSocket<D: device::WeakId, S: DatagramSocketSpec> { |
| V4(S::SocketId<Ipv4, D>), |
| V6(S::SocketId<Ipv6, D>), |
| } |
| |
| impl DualStackIpExt for Ipv4 { |
| /// Incoming IPv4 packets may be received by either IPv4 or IPv6 sockets. |
| type DualStackBoundSocketId<D: device::WeakId, S: DatagramSocketSpec> = EitherIpSocket<D, S>; |
| type OtherStackIpOptions<State: Clone + Debug + Default + Send + Sync> = (); |
| /// IPv4 sockets can't listen on dual-stack addresses. |
| type DualStackListenerIpAddr<LocalIdentifier: Clone + Debug + Send + Sync + Into<NonZeroU16>> = |
| ListenerIpAddr<Self::Addr, LocalIdentifier>; |
| /// IPv4 sockets cannot connect on dual-stack addresses. |
| type DualStackConnIpAddr<S: DatagramSocketSpec> = ConnIpAddr< |
| Self::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >; |
| /// IPv4 sockets cannot connect on dual-stack addresses. |
| type DualStackConnState<D: device::WeakId, S: DatagramSocketSpec> = ConnState<Self, Self, D, S>; |
| |
| fn into_dual_stack_bound_socket_id<D: device::WeakId, S: DatagramSocketSpec>( |
| id: S::SocketId<Self, D>, |
| ) -> Self::DualStackBoundSocketId<D, S> { |
| EitherIpSocket::V4(id) |
| } |
| |
| fn conn_addr_from_state<D: device::WeakId, S: DatagramSocketSpec>( |
| state: &Self::DualStackConnState<D, S>, |
| ) -> ConnAddr<Self::DualStackConnIpAddr<S>, D> { |
| let ConnState { |
| socket: _, |
| ip_options: _, |
| shutdown: _, |
| addr, |
| clear_device_on_disconnect: _, |
| extra: _, |
| } = state; |
| addr.clone() |
| } |
| } |
| |
| impl DualStackIpExt for Ipv6 { |
| /// Incoming IPv6 packets may only be received by IPv6 sockets. |
| type DualStackBoundSocketId<D: device::WeakId, S: DatagramSocketSpec> = S::SocketId<Self, D>; |
| type OtherStackIpOptions<State: Clone + Debug + Default + Send + Sync> = State; |
| /// IPv6 listeners can listen on dual-stack addresses (if the protocol |
| /// and socket are dual-stack-enabled). |
| type DualStackListenerIpAddr<LocalIdentifier: Clone + Debug + Send + Sync + Into<NonZeroU16>> = |
| DualStackListenerIpAddr<Self::Addr, LocalIdentifier>; |
| /// IPv6 sockets can connect on dual-stack addresses (if the protocol and |
| /// socket are dual-stack-enabled). |
| type DualStackConnIpAddr<S: DatagramSocketSpec> = DualStackConnIpAddr< |
| Self::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >; |
| /// IPv6 sockets can connect on dual-stack addresses (if the protocol and |
| /// socket are dual-stack-enabled). |
| type DualStackConnState<D: device::WeakId, S: DatagramSocketSpec> = |
| DualStackConnState<Self, D, S>; |
| |
| fn into_dual_stack_bound_socket_id<D: device::WeakId, S: DatagramSocketSpec>( |
| id: S::SocketId<Self, D>, |
| ) -> Self::DualStackBoundSocketId<D, S> { |
| id |
| } |
| |
| fn conn_addr_from_state<D: device::WeakId, S: DatagramSocketSpec>( |
| state: &Self::DualStackConnState<D, S>, |
| ) -> ConnAddr<Self::DualStackConnIpAddr<S>, D> { |
| match state { |
| DualStackConnState::ThisStack(state) => { |
| let ConnState { addr, .. } = state; |
| let ConnAddr { ip, device } = addr.clone(); |
| ConnAddr { ip: DualStackConnIpAddr::ThisStack(ip), device } |
| } |
| DualStackConnState::OtherStack(state) => { |
| let ConnState { |
| socket: _, |
| ip_options: _, |
| shutdown: _, |
| addr, |
| clear_device_on_disconnect: _, |
| extra: _, |
| } = state; |
| let ConnAddr { ip, device } = addr.clone(); |
| ConnAddr { ip: DualStackConnIpAddr::OtherStack(ip), device } |
| } |
| } |
| } |
| } |
| |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| /// A wrapper to make [`DualStackIpExt::OtherStackIpOptions`] [`GenericOverIp`]. |
| pub(crate) struct WrapOtherStackIpOptions< |
| 'a, |
| I: DualStackIpExt, |
| S: 'a + Clone + Debug + Default + Send + Sync, |
| >(pub(crate) &'a I::OtherStackIpOptions<S>); |
| |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| /// A wrapper to make [`DualStackIpExt::OtherStackIpOptions`] [`GenericOverIp`]. |
| pub(crate) struct WrapOtherStackIpOptionsMut< |
| 'a, |
| I: DualStackIpExt, |
| S: 'a + Clone + Debug + Default + Send + Sync, |
| >(pub(crate) &'a mut I::OtherStackIpOptions<S>); |
| |
| /// Types and behavior for datagram sockets. |
| /// |
| /// These sockets may or may not support dual-stack operation. |
| pub trait DatagramSocketSpec: Sized { |
| /// Name of this datagram protocol. |
| const NAME: &'static str; |
| |
| /// The socket address spec for the datagram socket type. |
| /// |
| /// This describes the types of identifiers the socket uses, e.g. |
| /// local/remote port for UDP. |
| type AddrSpec: SocketMapAddrSpec; |
| |
| /// Identifier for an individual socket for a given IP version. |
| /// |
| /// Corresponds uniquely to a socket resource. This is the type that will |
| /// be returned by [`create`] and used to identify which socket is being |
| /// acted on by calls like [`listen`], [`connect`], [`remove`], etc. |
| type SocketId<I: IpExt, D: device::WeakId>: Clone |
| + Debug |
| + Eq |
| + Send |
| + Borrow<StrongRc<I, D, Self>> |
| + From<StrongRc<I, D, Self>>; |
| |
| /// IP-level options for sending `I::OtherVersion` IP packets. |
| type OtherStackIpOptions<I: IpExt, D: device::WeakId>: Clone + Debug + Default + Send + Sync; |
| |
| /// The type of a listener IP address. |
| /// |
| /// For dual-stack-capable datagram protocols like UDP, this should use |
| /// [`DualStackIpExt::ListenerIpAddr`], which will be one of |
| /// [`ListenerIpAddr`] or [`DualStackListenerIpAddr`]. |
| /// Non-dual-stack-capable protocols (like ICMP and raw IP sockets) should |
| /// just use [`ListenerIpAddr`]. |
| type ListenerIpAddr<I: IpExt>: Clone |
| + Debug |
| + Into<(Option<SpecifiedAddr<I::Addr>>, NonZeroU16)>; |
| |
| /// The sharing state for a socket. |
| /// |
| /// NB: The underlying [`BoundSocketMap`]` uses separate types for the |
| /// sharing state of connected vs listening sockets. At the moment, datagram |
| /// sockets have no need for differentiated sharing states, so consolidate |
| /// them under one type. |
| type SharingState: Clone + Debug + Default; |
| |
| /// The type of an IP address for a connected socket. |
| /// |
| /// For dual-stack-capable datagram protocols like UDP, this should use |
| /// [`DualStackIpExt::ConnIpAddr`], which will be one of |
| /// [`ConnIpAddr`] or [`DualStackConnIpAddr`]. |
| /// Non-dual-stack-capable protocols (like ICMP and raw IP sockets) should |
| /// just use [`ConnIpAddr`]. |
| type ConnIpAddr<I: IpExt>: Clone |
| + Debug |
| + Into<ConnInfoAddr<I::Addr, <Self::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier>>; |
| |
| /// The type of a state held by a connected socket. |
| /// |
| /// For dual-stack-capable datagram protocols like UDP, this should use |
| /// [`DualStackIpExt::ConnState`], which will be one of [`ConnState`] or |
| /// [`DualStackConnState`]. Non-dual-stack-capable protocols (like ICMP and |
| /// raw IP sockets) should just use [`ConnState`]. |
| type ConnState<I: IpExt, D: device::WeakId>: Debug |
| + AsRef<IpOptions<I, D, Self>> |
| + AsMut<IpOptions<I, D, Self>> |
| + Send |
| + Sync; |
| |
| /// The extra state that a connection state want to remember. |
| /// |
| /// For example: UDP sockets does not have any extra state to remember, so |
| /// it should just be `()`; ICMP sockets need to remember the remote ID the |
| /// socket is 'connected' to, the remote ID is not used when sending nor |
| /// participating in the demuxing decisions. So it will be stored in the |
| /// extra state so that it can be retrieved later, i.e, it should be |
| /// `NonZeroU16` for ICMP sockets. |
| type ConnStateExtra: Debug + Send + Sync; |
| |
| /// The specification for the [`BoundSocketMap`] for a given IP version. |
| /// |
| /// Describes the per-address and per-socket values held in the |
| /// demultiplexing map for a given IP version. |
| type SocketMapSpec<I: IpExt + DualStackIpExt, D: device::WeakId>: DatagramSocketMapSpec< |
| I, |
| D, |
| Self::AddrSpec, |
| ListenerSharingState = Self::SharingState, |
| ConnSharingState = Self::SharingState, |
| >; |
| |
| /// External data kept by datagram sockets. |
| /// |
| /// This is used to store opaque bindings data alongside the core data |
| /// inside the socket references. |
| type ExternalData<I: Ip>: Debug + Send + Sync; |
| |
| /// Returns the IP protocol of this datagram specification. |
| fn ip_proto<I: IpProtoExt>() -> I::Proto; |
| |
| /// Converts [`Self::SocketId`] to [`DatagramSocketMapSpec::BoundSocketId`]. |
| /// |
| /// Constructs a socket identifier to its in-demultiplexing map form. For |
| /// protocols with dual-stack sockets, like UDP, implementations should |
| /// perform a transformation. Otherwise it should be the identity function. |
| fn make_bound_socket_map_id<I: IpExt, D: device::WeakId>( |
| s: &Self::SocketId<I, D>, |
| ) -> <Self::SocketMapSpec<I, D> as DatagramSocketMapSpec<I, D, Self::AddrSpec>>::BoundSocketId; |
| |
| /// The type of serializer returned by [`DatagramSocketSpec::make_packet`] |
| /// for a given IP version and buffer type. |
| type Serializer<I: IpExt, B: BufferMut>: TransportPacketSerializer<Buffer = B>; |
| /// The potential error for serializing a packet. For example, in UDP, this |
| /// should be infallible but for ICMP, there will be an error if the input |
| /// is not an echo request. |
| type SerializeError; |
| fn make_packet<I: IpExt, B: BufferMut>( |
| body: B, |
| addr: &ConnIpAddr< |
| I::Addr, |
| <Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <Self::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| ) -> Result<Self::Serializer<I, B>, Self::SerializeError>; |
| |
| /// Attempts to allocate a local identifier for a listening socket. |
| /// |
| /// Returns the identifier on success, or `None` on failure. |
| fn try_alloc_listen_identifier<I: IpExt, D: device::WeakId>( |
| rng: &mut impl RngContext, |
| is_available: impl Fn( |
| <Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| ) -> Result<(), InUseError>, |
| ) -> Option<<Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>; |
| |
| /// Retrieves the associated connection info from the connection state. |
| fn conn_info_from_state<I: IpExt, D: device::WeakId>( |
| state: &Self::ConnState<I, D>, |
| ) -> ConnInfo<I::Addr, D>; |
| |
| /// Tries to allocate a local identifier. |
| fn try_alloc_local_id<I: IpExt, D: device::WeakId, BC: RngContext>( |
| bound: &BoundSocketMap<I, D, Self::AddrSpec, Self::SocketMapSpec<I, D>>, |
| bindings_ctx: &mut BC, |
| flow: DatagramFlowId<I::Addr, <Self::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier>, |
| ) -> Option<<Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>; |
| } |
| |
| pub struct InUseError; |
| |
| /// Creates a primary ID without inserting it into the all socket map. |
| pub(crate) fn create_primary_id<I: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| external_data: S::ExternalData<I>, |
| ) -> PrimaryRc<I, D, S> { |
| PrimaryRc::new(ReferenceState { |
| state: RwLock::new(SocketState::Unbound(UnboundSocketState::default())), |
| external_data, |
| }) |
| } |
| |
| /// Creates a new datagram socket and inserts it into the list of all open |
| /// datagram sockets for the provided spec `S`. |
| /// |
| /// The caller is responsible for calling [`close`] when it's done with the |
| /// resource. |
| pub(crate) fn create<I: IpExt, S: DatagramSocketSpec, BC, CC: DatagramStateContext<I, BC, S>>( |
| core_ctx: &mut CC, |
| external_data: S::ExternalData<I>, |
| ) -> S::SocketId<I, CC::WeakDeviceId> { |
| let primary = create_primary_id(external_data); |
| let strong = PrimaryRc::clone_strong(&primary); |
| core_ctx.with_all_sockets_mut(move |socket_set| { |
| let strong = PrimaryRc::clone_strong(&primary); |
| assert_matches::assert_matches!(socket_set.insert(strong, primary), None); |
| }); |
| strong.into() |
| } |
| |
| /// Collects all currently opened sockets. |
| pub(crate) fn collect_all_sockets< |
| I: IpExt, |
| S: DatagramSocketSpec, |
| BC, |
| CC: DatagramStateContext<I, BC, S>, |
| >( |
| core_ctx: &mut CC, |
| ) -> Vec<S::SocketId<I, CC::WeakDeviceId>> { |
| core_ctx.with_all_sockets(|socket_set| socket_set.keys().map(|s| s.clone().into()).collect()) |
| } |
| |
| /// Information associated with a datagram listener. |
| #[derive(GenericOverIp)] |
| #[cfg_attr(test, derive(Debug, Eq, PartialEq))] |
| #[generic_over_ip(A, IpAddress)] |
| pub struct ListenerInfo<A: IpAddress, D> { |
| /// The local address associated with a datagram listener, or `None` for any |
| /// address. |
| pub local_ip: Option<StrictlyZonedAddr<A, SpecifiedAddr<A>, D>>, |
| /// The local port associated with a datagram listener. |
| pub local_identifier: NonZeroU16, |
| } |
| |
| impl<A: IpAddress, LA: Into<(Option<SpecifiedAddr<A>>, NonZeroU16)>, D> From<ListenerAddr<LA, D>> |
| for ListenerInfo<A, D> |
| { |
| fn from(ListenerAddr { ip, device }: ListenerAddr<LA, D>) -> Self { |
| let (addr, local_identifier) = ip.into(); |
| Self { |
| local_ip: addr.map(|addr| { |
| StrictlyZonedAddr::new_with_zone(addr, || { |
| // The invariant that a zone is present if needed is upheld by |
| // set_bindtodevice and bind. |
| device.expect("device must be bound for addresses that require zones") |
| }) |
| }), |
| local_identifier, |
| } |
| } |
| } |
| |
| impl<A: IpAddress, D> From<NonZeroU16> for ListenerInfo<A, D> { |
| fn from(local_identifier: NonZeroU16) -> Self { |
| Self { local_ip: None, local_identifier } |
| } |
| } |
| |
| /// Information associated with a datagram connection. |
| #[derive(Debug, GenericOverIp)] |
| #[cfg_attr(test, derive(PartialEq))] |
| #[generic_over_ip(A, IpAddress)] |
| pub struct ConnInfo<A: IpAddress, D> { |
| /// The local address associated with a datagram connection. |
| pub local_ip: StrictlyZonedAddr<A, SpecifiedAddr<A>, D>, |
| /// The local identifier associated with a datagram connection. |
| pub local_identifier: NonZeroU16, |
| /// The remote address associated with a datagram connection. |
| pub remote_ip: StrictlyZonedAddr<A, SpecifiedAddr<A>, D>, |
| /// The remote identifier associated with a datagram connection. |
| pub remote_identifier: u16, |
| } |
| |
| impl<A: IpAddress, D> ConnInfo<A, D> { |
| /// Construct a new `ConnInfo`. |
| pub fn new( |
| local_ip: SpecifiedAddr<A>, |
| local_identifier: NonZeroU16, |
| remote_ip: SpecifiedAddr<A>, |
| remote_identifier: u16, |
| mut get_zone: impl FnMut() -> D, |
| ) -> Self { |
| Self { |
| local_ip: StrictlyZonedAddr::new_with_zone(local_ip, &mut get_zone), |
| local_identifier, |
| remote_ip: StrictlyZonedAddr::new_with_zone(remote_ip, &mut get_zone), |
| remote_identifier, |
| } |
| } |
| } |
| |
| /// Information about the addresses for a socket. |
| #[derive(GenericOverIp)] |
| #[cfg_attr(test, derive(Debug, PartialEq))] |
| #[generic_over_ip(A, IpAddress)] |
| pub enum SocketInfo<A: IpAddress, D> { |
| /// The socket is not bound. |
| Unbound, |
| /// The socket is listening. |
| Listener(ListenerInfo<A, D>), |
| /// The socket is connected. |
| Connected(ConnInfo<A, D>), |
| } |
| |
| pub(crate) fn close< |
| I: IpExt, |
| S: DatagramSocketSpec, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: S::SocketId<I, CC::WeakDeviceId>, |
| ) -> RemoveResourceResultWithContext<S::ExternalData<I>, BC> { |
| // Remove the socket from the list first to prevent double close. |
| let primary = core_ctx.with_all_sockets_mut(|all_sockets| { |
| all_sockets.remove(id.borrow()).expect("socket already closed") |
| }); |
| core_ctx.with_socket_state(&id, |core_ctx, state| { |
| let ip_options = match state { |
| SocketState::Unbound(UnboundSocketState { device: _, sharing: _, ip_options }) => { |
| ip_options.clone() |
| } |
| SocketState::Bound(state) => match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(dual_stack) => { |
| let op = DualStackRemoveOperation::new_from_state(dual_stack, &id, state); |
| dual_stack |
| .with_both_bound_sockets_mut(|_core_ctx, sockets, other_sockets| { |
| op.apply(sockets, other_sockets) |
| }) |
| .into_options() |
| } |
| MaybeDualStack::NotDualStack(not_dual_stack) => { |
| let op = SingleStackRemoveOperation::new_from_state(not_dual_stack, &id, state); |
| core_ctx |
| .with_bound_sockets_mut(|_core_ctx, sockets| op.apply(sockets)) |
| .into_options() |
| } |
| }, |
| }; |
| DatagramBoundStateContext::<I, _, _>::with_transport_context(core_ctx, |core_ctx| { |
| leave_all_joined_groups(core_ctx, bindings_ctx, ip_options.multicast_memberships) |
| }); |
| }); |
| // Drop the (hopefully last) strong ID before unwrapping the primary |
| // reference. |
| core::mem::drop(id); |
| let debug_references = PrimaryRc::debug_references(&primary); |
| match PrimaryRc::unwrap_or_notify_with(primary, || { |
| let (notifier, receiver) = |
| BC::new_reference_notifier::<S::ExternalData<I>, _>(debug_references); |
| let notifier = |
| MapRcNotifier::new(notifier, |ReferenceState { state: _, external_data }| { |
| external_data |
| }); |
| (notifier, receiver) |
| }) { |
| Ok(ReferenceState { state: _, external_data }) => { |
| RemoveResourceResult::Removed(external_data) |
| } |
| Err(receiver) => RemoveResourceResult::Deferred(receiver), |
| } |
| } |
| |
| /// State associated with removing a socket. |
| /// |
| /// Note that this type is generic over two `IpExt` parameters: `WireI` and |
| /// `SocketI`. This allows it to be used for both single-stack remove operations |
| /// (where `WireI` and `SocketI` are the same), as well as dual-stack remove |
| /// operations (where `WireI`, and `SocketI` may be different). |
| enum Remove<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| Listener { |
| // The socket's address, stored as a concrete `ListenerIpAddr`. |
| concrete_addr: ListenerAddr< |
| ListenerIpAddr<WireI::Addr, <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| D, |
| >, |
| ip_options: IpOptions<SocketI, D, S>, |
| sharing: S::SharingState, |
| socket_id: |
| <S::SocketMapSpec<WireI, D> as DatagramSocketMapSpec<WireI, D, S::AddrSpec>>::BoundSocketId, |
| }, |
| Connected { |
| // The socket's address, stored as a concrete `ConnIpAddr`. |
| concrete_addr: ConnAddr< |
| ConnIpAddr< |
| WireI::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| D, |
| >, |
| ip_options: IpOptions<SocketI, D, S>, |
| sharing: S::SharingState, |
| socket_id: |
| <S::SocketMapSpec<WireI, D> as DatagramSocketMapSpec<WireI, D, S::AddrSpec>>::BoundSocketId, |
| }, |
| } |
| |
| /// The yet-to-be-performed removal of a socket. |
| /// |
| /// Like [`Remove`], this type takes two generic `IpExt` parameters so that |
| /// it can be used from single-stack and dual-stack remove operations. |
| struct RemoveOperation<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| Remove<WireI, SocketI, D, S>, |
| ); |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> |
| RemoveOperation<WireI, SocketI, D, S> |
| { |
| /// Apply this remove operation to the given `BoundSocketMap`. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the given socket map does not contain the socket specified by |
| /// this removal operation. |
| fn apply( |
| self, |
| sockets: &mut BoundSocketMap<WireI, D, S::AddrSpec, S::SocketMapSpec<WireI, D>>, |
| ) -> RemoveInfo<WireI, SocketI, D, S> { |
| let RemoveOperation(remove) = self; |
| match &remove { |
| Remove::Listener { concrete_addr, ip_options: _, sharing: _, socket_id } => { |
| let ListenerAddr { ip: ListenerIpAddr { addr, identifier }, device } = |
| concrete_addr; |
| BoundStateHandler::<_, S, _>::remove_listener( |
| sockets, |
| addr, |
| *identifier, |
| device, |
| socket_id, |
| ); |
| } |
| Remove::Connected { concrete_addr, ip_options: _, sharing: _, socket_id } => { |
| sockets |
| .conns_mut() |
| .remove(socket_id, concrete_addr) |
| .expect("UDP connection not found"); |
| } |
| } |
| RemoveInfo(remove) |
| } |
| } |
| |
| // A single stack implementation of `RemoveOperation` (e.g. where `WireI` and |
| // `SocketI` are the same type). |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> RemoveOperation<I, I, D, S> { |
| /// Constructs the remove operation from existing socket state. |
| fn new_from_state<BC, CC: NonDualStackDatagramBoundStateContext<I, BC, S, WeakDeviceId = D>>( |
| core_ctx: &mut CC, |
| socket_id: &S::SocketId<I, D>, |
| state: &BoundSocketState<I, D, S>, |
| ) -> Self { |
| let BoundSocketState { socket_type: state, original_bound_addr: _ } = state; |
| RemoveOperation(match state { |
| BoundSocketStateType::Listener { |
| state: ListenerState { addr: ListenerAddr { ip, device }, ip_options }, |
| sharing, |
| } => Remove::Listener { |
| concrete_addr: ListenerAddr { |
| ip: core_ctx.converter().convert(ip.clone()), |
| device: device.clone(), |
| }, |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| }, |
| BoundSocketStateType::Connected { state, sharing } => { |
| let ConnState { |
| addr, |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown: _, |
| extra: _, |
| } = core_ctx.converter().convert(state); |
| Remove::Connected { |
| concrete_addr: addr.clone(), |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| } |
| } |
| }) |
| } |
| } |
| |
| /// Information for a recently-removed single-stack socket. |
| /// |
| /// Like [`Remove`], this type takes two generic `IpExt` parameters so that |
| /// it can be used from single-stack and dual-stack remove operations. |
| struct RemoveInfo<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| Remove<WireI, SocketI, D, S>, |
| ); |
| |
| impl<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> |
| RemoveInfo<WireI, SocketI, D, S> |
| { |
| fn into_options(self) -> IpOptions<SocketI, D, S> { |
| let RemoveInfo(remove) = self; |
| match remove { |
| Remove::Listener { concrete_addr: _, ip_options, sharing: _, socket_id: _ } => { |
| ip_options |
| } |
| Remove::Connected { concrete_addr: _, ip_options, sharing: _, socket_id: _ } => { |
| ip_options |
| } |
| } |
| } |
| |
| fn into_options_sharing_and_device( |
| self, |
| ) -> (IpOptions<SocketI, D, S>, S::SharingState, Option<D>) { |
| let RemoveInfo(remove) = self; |
| match remove { |
| Remove::Listener { |
| concrete_addr: ListenerAddr { ip: _, device }, |
| ip_options, |
| sharing, |
| socket_id: _, |
| } => (ip_options, sharing, device), |
| Remove::Connected { |
| concrete_addr: ConnAddr { ip: _, device }, |
| ip_options, |
| sharing, |
| socket_id: _, |
| } => (ip_options, sharing, device), |
| } |
| } |
| |
| /// Undo this removal by reinserting the socket into the [`BoundSocketMap`]. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the socket can not be inserted into the given map (i.e. if it |
| /// already exists). This is not expected to happen, provided the |
| /// [`BoundSocketMap`] lock has been held across removal and reinsertion. |
| fn reinsert( |
| self, |
| sockets: &mut BoundSocketMap<WireI, D, S::AddrSpec, S::SocketMapSpec<WireI, D>>, |
| ) { |
| let RemoveInfo(remove) = self; |
| match remove { |
| Remove::Listener { |
| concrete_addr: ListenerAddr { ip: ListenerIpAddr { addr, identifier }, device }, |
| ip_options: _, |
| sharing, |
| socket_id, |
| } => { |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| sockets, addr, identifier, device, sharing, socket_id, |
| ) |
| .expect("reinserting just-removed listener failed"); |
| } |
| Remove::Connected { concrete_addr, ip_options: _, sharing, socket_id } => { |
| let _entry = sockets |
| .conns_mut() |
| .try_insert(concrete_addr, sharing, socket_id) |
| .expect("reinserting just-removed connected failed"); |
| } |
| } |
| } |
| } |
| |
| /// The yet-to-be-performed removal of a single-stack socket. |
| type SingleStackRemoveOperation<I, D, S> = RemoveOperation<I, I, D, S>; |
| |
| /// Information for a recently-removed single-stack socket. |
| type SingleStackRemoveInfo<I, D, S> = RemoveInfo<I, I, D, S>; |
| |
| /// State associated with removing a dual-stack socket. |
| enum DualStackRemove<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| CurrentStack(Remove<I, I, D, S>), |
| OtherStack(Remove<I::OtherVersion, I, D, S>), |
| ListenerBothStacks { |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: Option<D>, |
| ip_options: IpOptions<I, D, S>, |
| sharing: S::SharingState, |
| socket_ids: PairedBoundSocketIds<I, D, S>, |
| }, |
| } |
| |
| /// The yet-to-be-performed removal of a single-stack socket. |
| struct DualStackRemoveOperation<I: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| DualStackRemove<I, D, S>, |
| ); |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> DualStackRemoveOperation<I, D, S> { |
| /// Constructs the removal operation from existing socket state. |
| fn new_from_state<BC, CC: DualStackDatagramBoundStateContext<I, BC, S, WeakDeviceId = D>>( |
| core_ctx: &mut CC, |
| socket_id: &S::SocketId<I, D>, |
| state: &BoundSocketState<I, D, S>, |
| ) -> Self { |
| let BoundSocketState { socket_type: state, original_bound_addr: _ } = state; |
| DualStackRemoveOperation(match state { |
| BoundSocketStateType::Listener { |
| state: ListenerState { addr, ip_options }, |
| sharing, |
| } => { |
| let ListenerAddr { ip, device } = addr.clone(); |
| match (core_ctx.converter().convert(ip), core_ctx.dual_stack_enabled(&ip_options)) { |
| // Dual-stack enabled, bound in both stacks. |
| (DualStackListenerIpAddr::BothStacks(identifier), true) => { |
| DualStackRemove::ListenerBothStacks { |
| identifier: identifier.clone(), |
| device, |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_ids: PairedBoundSocketIds { |
| this: S::make_bound_socket_map_id(socket_id), |
| other: core_ctx.to_other_bound_socket_id(socket_id), |
| }, |
| } |
| } |
| // Bound in this stack, with/without dual-stack enabled. |
| (DualStackListenerIpAddr::ThisStack(addr), true | false) => { |
| DualStackRemove::CurrentStack(Remove::Listener { |
| concrete_addr: ListenerAddr { ip: addr, device }, |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| }) |
| } |
| // Dual-stack enabled, bound only in the other stack. |
| (DualStackListenerIpAddr::OtherStack(addr), true) => { |
| DualStackRemove::OtherStack(Remove::Listener { |
| concrete_addr: ListenerAddr { ip: addr, device }, |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_id: core_ctx.to_other_bound_socket_id(socket_id), |
| }) |
| } |
| (DualStackListenerIpAddr::OtherStack(_), false) |
| | (DualStackListenerIpAddr::BothStacks(_), false) => { |
| unreachable!("dual-stack disabled socket cannot use the other stack") |
| } |
| } |
| } |
| BoundSocketStateType::Connected { state, sharing } => { |
| match core_ctx.converter().convert(state) { |
| DualStackConnState::ThisStack(state) => { |
| let ConnState { |
| addr, |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown: _, |
| extra: _, |
| } = state; |
| DualStackRemove::CurrentStack(Remove::Connected { |
| concrete_addr: addr.clone(), |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| }) |
| } |
| DualStackConnState::OtherStack(state) => { |
| core_ctx.assert_dual_stack_enabled(&state); |
| let ConnState { |
| addr, |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown: _, |
| extra: _, |
| } = state; |
| DualStackRemove::OtherStack(Remove::Connected { |
| concrete_addr: addr.clone(), |
| ip_options: ip_options.clone(), |
| sharing: sharing.clone(), |
| socket_id: core_ctx.to_other_bound_socket_id(socket_id), |
| }) |
| } |
| } |
| } |
| }) |
| } |
| |
| /// Apply this remove operation to the given `BoundSocketMap`s. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the given socket maps does not contain the socket specified by |
| /// this removal operation. |
| fn apply( |
| self, |
| sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| other_sockets: &mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >, |
| ) -> DualStackRemoveInfo<I, D, S> { |
| let DualStackRemoveOperation(dual_stack_remove) = self; |
| match dual_stack_remove { |
| DualStackRemove::CurrentStack(remove) => { |
| let RemoveInfo(remove) = RemoveOperation(remove).apply(sockets); |
| DualStackRemoveInfo(DualStackRemove::CurrentStack(remove)) |
| } |
| DualStackRemove::OtherStack(remove) => { |
| let RemoveInfo(remove) = RemoveOperation(remove).apply(other_sockets); |
| DualStackRemoveInfo(DualStackRemove::OtherStack(remove)) |
| } |
| DualStackRemove::ListenerBothStacks { |
| identifier, |
| device, |
| ip_options, |
| sharing, |
| socket_ids, |
| } => { |
| PairedSocketMapMut::<_, _, S> { bound: sockets, other_bound: other_sockets } |
| .remove_listener(&DualStackUnspecifiedAddr, identifier, &device, &socket_ids); |
| DualStackRemoveInfo(DualStackRemove::ListenerBothStacks { |
| identifier, |
| device, |
| ip_options, |
| sharing, |
| socket_ids, |
| }) |
| } |
| } |
| } |
| } |
| |
| /// Information for a recently-removed single-stack socket. |
| struct DualStackRemoveInfo<I: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| DualStackRemove<I, D, S>, |
| ); |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> DualStackRemoveInfo<I, D, S> { |
| fn into_options(self) -> IpOptions<I, D, S> { |
| let DualStackRemoveInfo(dual_stack_remove) = self; |
| match dual_stack_remove { |
| DualStackRemove::CurrentStack(remove) => RemoveInfo(remove).into_options(), |
| DualStackRemove::OtherStack(remove) => RemoveInfo(remove).into_options(), |
| DualStackRemove::ListenerBothStacks { |
| identifier: _, |
| device: _, |
| ip_options, |
| sharing: _, |
| socket_ids: _, |
| } => ip_options, |
| } |
| } |
| |
| fn into_options_sharing_and_device(self) -> (IpOptions<I, D, S>, S::SharingState, Option<D>) { |
| let DualStackRemoveInfo(dual_stack_remove) = self; |
| match dual_stack_remove { |
| DualStackRemove::CurrentStack(remove) => { |
| RemoveInfo(remove).into_options_sharing_and_device() |
| } |
| DualStackRemove::OtherStack(remove) => { |
| RemoveInfo(remove).into_options_sharing_and_device() |
| } |
| DualStackRemove::ListenerBothStacks { |
| identifier: _, |
| device, |
| ip_options, |
| sharing, |
| socket_ids: _, |
| } => (ip_options, sharing, device), |
| } |
| } |
| |
| /// Undo this removal by reinserting the socket into the [`BoundSocketMap`]. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the socket can not be inserted into the given maps (i.e. if it |
| /// already exists). This is not expected to happen, provided the |
| /// [`BoundSocketMap`] lock has been held across removal and reinsertion. |
| fn reinsert( |
| self, |
| sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| other_sockets: &mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >, |
| ) { |
| let DualStackRemoveInfo(dual_stack_remove) = self; |
| match dual_stack_remove { |
| DualStackRemove::CurrentStack(remove) => { |
| RemoveInfo(remove).reinsert(sockets); |
| } |
| DualStackRemove::OtherStack(remove) => { |
| RemoveInfo(remove).reinsert(other_sockets); |
| } |
| DualStackRemove::ListenerBothStacks { |
| identifier, |
| device, |
| ip_options: _, |
| sharing, |
| socket_ids, |
| } => { |
| let mut socket_maps_pair = |
| PairedSocketMapMut { bound: sockets, other_bound: other_sockets }; |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| &mut socket_maps_pair, |
| DualStackUnspecifiedAddr, |
| identifier, |
| device, |
| sharing, |
| socket_ids, |
| ) |
| .expect("reinserting just-removed listener failed") |
| } |
| } |
| } |
| } |
| |
| pub(crate) fn get_info< |
| I: IpExt, |
| S: DatagramSocketSpec, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> SocketInfo<I::Addr, CC::WeakDeviceId> { |
| core_ctx.with_socket_state(id, |_core_ctx, state| state.to_socket_info()) |
| } |
| |
| pub(crate) fn listen< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| addr: Option<ZonedAddr<SpecifiedAddr<I::Addr>, CC::DeviceId>>, |
| local_id: Option<<S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| ) -> Result<(), Either<ExpectedUnboundError, LocalAddressError>> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| listen_inner::<_, BC, _, S>(core_ctx, bindings_ctx, state, id, addr, local_id) |
| }) |
| } |
| |
| /// Abstraction for operations over one or two demultiplexing maps. |
| trait BoundStateHandler<I: IpExt, S: DatagramSocketSpec, D: device::WeakId> { |
| /// The type of address that can be inserted or removed for listeners. |
| type ListenerAddr: Clone; |
| /// The type of ID that can be inserted or removed. |
| type BoundSocketId; |
| |
| /// Checks whether an entry could be inserted for the specified address and |
| /// identifier. |
| /// |
| /// Returns `true` if a value could be inserted at the specified address and |
| /// local ID, with the provided sharing state; otherwise returns `false`. |
| fn is_listener_entry_available( |
| &self, |
| addr: Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| sharing_state: &S::SharingState, |
| ) -> bool; |
| |
| /// Inserts `id` at a listener address or returns an error. |
| /// |
| /// Inserts the identifier `id` at the listener address for `addr` and |
| /// local `identifier` with device `device` and the given sharing state. If |
| /// the insertion conflicts with an existing socket, a `LocalAddressError` |
| /// is returned. |
| fn try_insert_listener( |
| &mut self, |
| addr: Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: Option<D>, |
| sharing: S::SharingState, |
| id: Self::BoundSocketId, |
| ) -> Result<(), LocalAddressError>; |
| |
| /// Removes `id` at listener address, assuming it exists. |
| /// |
| /// Panics if `id` does not exit. |
| fn remove_listener( |
| &mut self, |
| addr: &Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: &Option<D>, |
| id: &Self::BoundSocketId, |
| ); |
| } |
| |
| /// A sentinel type for the unspecified address in a dual-stack context. |
| /// |
| /// This is kind of like [`Ipv6::UNSPECIFIED_ADDRESS`], but makes it clear that |
| /// the value is being used in a dual-stack context. |
| #[derive(Copy, Clone, Debug)] |
| struct DualStackUnspecifiedAddr; |
| |
| /// Implementation of BoundStateHandler for a single demultiplexing map. |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> BoundStateHandler<I, S, D> |
| for BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>> |
| { |
| type ListenerAddr = Option<SocketIpAddr<I::Addr>>; |
| type BoundSocketId = |
| <S::SocketMapSpec<I, D> as DatagramSocketMapSpec<I, D, S::AddrSpec>>::BoundSocketId; |
| fn is_listener_entry_available( |
| &self, |
| addr: Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| sharing: &S::SharingState, |
| ) -> bool { |
| let check_addr = ListenerAddr { device: None, ip: ListenerIpAddr { identifier, addr } }; |
| match self.listeners().could_insert(&check_addr, sharing) { |
| Ok(()) => true, |
| Err( |
| InsertError::Exists |
| | InsertError::IndirectConflict |
| | InsertError::ShadowAddrExists |
| | InsertError::ShadowerExists, |
| ) => false, |
| } |
| } |
| |
| fn try_insert_listener( |
| &mut self, |
| addr: Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: Option<D>, |
| sharing: S::SharingState, |
| id: Self::BoundSocketId, |
| ) -> Result<(), LocalAddressError> { |
| try_insert_single_listener(self, addr, identifier, device, sharing, id).map(|_entry| ()) |
| } |
| |
| fn remove_listener( |
| &mut self, |
| addr: &Self::ListenerAddr, |
| identifier: <<S as DatagramSocketSpec>::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: &Option<D>, |
| id: &Self::BoundSocketId, |
| ) { |
| remove_single_listener(self, addr, identifier, device, id) |
| } |
| } |
| |
| struct PairedSocketMapMut<'a, I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| bound: &'a mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| other_bound: &'a mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >, |
| } |
| |
| struct PairedBoundSocketIds<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| this: <S::SocketMapSpec<I, D> as DatagramSocketMapSpec<I, D, S::AddrSpec>>::BoundSocketId, |
| other: <S::SocketMapSpec<I::OtherVersion, D> as DatagramSocketMapSpec< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| >>::BoundSocketId, |
| } |
| |
| /// Implementation for a pair of demultiplexing maps for different IP versions. |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> BoundStateHandler<I, S, D> |
| for PairedSocketMapMut<'_, I, D, S> |
| { |
| type ListenerAddr = DualStackUnspecifiedAddr; |
| type BoundSocketId = PairedBoundSocketIds<I, D, S>; |
| |
| fn is_listener_entry_available( |
| &self, |
| DualStackUnspecifiedAddr: Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| sharing: &S::SharingState, |
| ) -> bool { |
| let PairedSocketMapMut { bound, other_bound } = self; |
| BoundStateHandler::<I, S, D>::is_listener_entry_available(*bound, None, identifier, sharing) |
| && BoundStateHandler::<I::OtherVersion, S, D>::is_listener_entry_available( |
| *other_bound, |
| None, |
| identifier, |
| sharing, |
| ) |
| } |
| |
| fn try_insert_listener( |
| &mut self, |
| DualStackUnspecifiedAddr: Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: Option<D>, |
| sharing: S::SharingState, |
| id: Self::BoundSocketId, |
| ) -> Result<(), LocalAddressError> { |
| let PairedSocketMapMut { bound: this, other_bound: other } = self; |
| let PairedBoundSocketIds { this: this_id, other: other_id } = id; |
| try_insert_single_listener(this, None, identifier, device.clone(), sharing.clone(), this_id) |
| .and_then(|first_entry| { |
| match try_insert_single_listener(other, None, identifier, device, sharing, other_id) |
| { |
| Ok(_second_entry) => Ok(()), |
| Err(e) => { |
| first_entry.remove(); |
| Err(e) |
| } |
| } |
| }) |
| } |
| |
| fn remove_listener( |
| &mut self, |
| DualStackUnspecifiedAddr: &Self::ListenerAddr, |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: &Option<D>, |
| id: &PairedBoundSocketIds<I, D, S>, |
| ) { |
| let PairedSocketMapMut { bound: this, other_bound: other } = self; |
| let PairedBoundSocketIds { this: this_id, other: other_id } = id; |
| remove_single_listener(this, &None, identifier, device, this_id); |
| remove_single_listener(other, &None, identifier, device, other_id); |
| } |
| } |
| |
| fn try_insert_single_listener< |
| I: IpExt, |
| D: device::WeakId, |
| A: SocketMapAddrSpec, |
| S: DatagramSocketMapSpec<I, D, A>, |
| >( |
| bound: &mut BoundSocketMap<I, D, A, S>, |
| addr: Option<SocketIpAddr<I::Addr>>, |
| identifier: A::LocalIdentifier, |
| device: Option<D>, |
| sharing: S::ListenerSharingState, |
| id: S::ListenerId, |
| ) -> Result<socket::SocketStateEntry<'_, I, D, A, S, socket::Listener>, LocalAddressError> { |
| bound |
| .listeners_mut() |
| .try_insert(ListenerAddr { ip: ListenerIpAddr { addr, identifier }, device }, sharing, id) |
| .map_err(|e| match e { |
| ( |
| InsertError::ShadowAddrExists |
| | InsertError::Exists |
| | InsertError::IndirectConflict |
| | InsertError::ShadowerExists, |
| sharing, |
| ) => { |
| let _: S::ListenerSharingState = sharing; |
| LocalAddressError::AddressInUse |
| } |
| }) |
| } |
| |
| fn remove_single_listener< |
| I: IpExt, |
| D: device::WeakId, |
| A: SocketMapAddrSpec, |
| S: DatagramSocketMapSpec<I, D, A>, |
| >( |
| bound: &mut BoundSocketMap<I, D, A, S>, |
| addr: &Option<SocketIpAddr<I::Addr>>, |
| identifier: A::LocalIdentifier, |
| device: &Option<D>, |
| id: &S::ListenerId, |
| ) { |
| let addr = |
| ListenerAddr { ip: ListenerIpAddr { addr: *addr, identifier }, device: device.clone() }; |
| bound |
| .listeners_mut() |
| .remove(id, &addr) |
| .unwrap_or_else(|NotFoundError| panic!("socket ID {:?} not found for {:?}", id, addr)) |
| } |
| |
| fn try_pick_identifier< |
| I: IpExt, |
| S: DatagramSocketSpec, |
| D: device::WeakId, |
| BS: BoundStateHandler<I, S, D>, |
| BC: RngContext, |
| >( |
| addr: BS::ListenerAddr, |
| bound: &BS, |
| bindings_ctx: &mut BC, |
| sharing: &S::SharingState, |
| ) -> Option<<S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier> { |
| S::try_alloc_listen_identifier::<I, D>(bindings_ctx, move |identifier| { |
| bound |
| .is_listener_entry_available(addr.clone(), identifier, sharing) |
| .then_some(()) |
| .ok_or(InUseError) |
| }) |
| } |
| |
| fn try_pick_bound_address<I: IpExt, CC: TransportIpContext<I, BC>, BC, LI>( |
| addr: Option<ZonedAddr<SocketIpAddr<I::Addr>, CC::DeviceId>>, |
| device: &Option<CC::WeakDeviceId>, |
| core_ctx: &mut CC, |
| identifier: LI, |
| ) -> Result< |
| (Option<SocketIpAddr<I::Addr>>, Option<EitherDeviceId<CC::DeviceId, CC::WeakDeviceId>>, LI), |
| LocalAddressError, |
| > { |
| let (addr, device, identifier) = match addr { |
| Some(addr) => { |
| // Extract the specified address and the device. The device |
| // is either the one from the address or the one to which |
| // the socket was previously bound. |
| let (addr, device) = crate::transport::resolve_addr_with_device(addr, device.clone())?; |
| |
| // Binding to multicast addresses is allowed regardless. |
| // Other addresses can only be bound to if they are assigned |
| // to the device. |
| if !addr.addr().is_multicast() { |
| let mut assigned_to = TransportIpContext::<I, _>::get_devices_with_assigned_addr( |
| core_ctx, |
| addr.into(), |
| ); |
| if let Some(device) = &device { |
| if !assigned_to.any(|d| device == &EitherDeviceId::Strong(d)) { |
| return Err(LocalAddressError::AddressMismatch); |
| } |
| } else { |
| if !assigned_to.any(|_: CC::DeviceId| true) { |
| return Err(LocalAddressError::CannotBindToAddress); |
| } |
| } |
| } |
| (Some(addr), device, identifier) |
| } |
| None => (None, device.clone().map(EitherDeviceId::Weak), identifier), |
| }; |
| Ok((addr, device, identifier)) |
| } |
| |
| fn listen_inner< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| state: &mut SocketState<I, CC::WeakDeviceId, S>, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| addr: Option<ZonedAddr<SpecifiedAddr<I::Addr>, CC::DeviceId>>, |
| local_id: Option<<S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| ) -> Result<(), Either<ExpectedUnboundError, LocalAddressError>> { |
| /// Possible operations that might be performed, depending on whether the |
| /// socket state spec supports dual-stack operation and what the address |
| /// looks like. |
| #[derive(Debug, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| enum BoundOperation<'a, I: IpExt, DS: DeviceIdContext<AnyDevice>, NDS> { |
| /// Bind to the "any" address on both stacks. |
| DualStackAnyAddr(&'a mut DS), |
| /// Bind to a non-dual-stack address only on the current stack. |
| OnlyCurrentStack( |
| MaybeDualStack<&'a mut DS, &'a mut NDS>, |
| Option<ZonedAddr<SocketIpAddr<I::Addr>, DS::DeviceId>>, |
| ), |
| /// Bind to an address only on the other stack. |
| OnlyOtherStack( |
| &'a mut DS, |
| Option<ZonedAddr<SocketIpAddr<<I::OtherVersion as Ip>::Addr>, DS::DeviceId>>, |
| ), |
| } |
| |
| let UnboundSocketState { device, sharing, ip_options } = match state { |
| SocketState::Unbound(state) => state, |
| SocketState::Bound(_) => return Err(Either::Left(ExpectedUnboundError)), |
| }; |
| |
| let dual_stack = core_ctx.dual_stack_context(); |
| let bound_operation: BoundOperation<'_, I, _, _> = match (dual_stack, addr) { |
| // Dual-stack support and unspecified address. |
| (MaybeDualStack::DualStack(dual_stack), None) => { |
| match dual_stack.dual_stack_enabled(ip_options) { |
| // Socket is dual-stack enabled, bind in both stacks. |
| true => BoundOperation::DualStackAnyAddr(dual_stack), |
| // Dual-stack support but not enabled, so bind unspecified in the |
| // current stack. |
| false => { |
| BoundOperation::OnlyCurrentStack(MaybeDualStack::DualStack(dual_stack), None) |
| } |
| } |
| } |
| // There is dual-stack support and the address is not unspecified so how |
| // to proceed is going to depend on the value of `addr`. |
| (MaybeDualStack::DualStack(dual_stack), Some(addr)) => match try_unmap(addr) { |
| // `addr` can't be represented in the other stack. |
| TryUnmapResult::CannotBeUnmapped(addr) => { |
| BoundOperation::OnlyCurrentStack(MaybeDualStack::DualStack(dual_stack), Some(addr)) |
| } |
| // There's a representation in the other stack, so use that if possible. |
| TryUnmapResult::Mapped(addr) => match dual_stack.dual_stack_enabled(ip_options) { |
| true => BoundOperation::OnlyOtherStack(dual_stack, addr), |
| false => return Err(Either::Right(LocalAddressError::CannotBindToAddress)), |
| }, |
| }, |
| // No dual-stack support, so only bind on the current stack. |
| (MaybeDualStack::NotDualStack(single_stack), None) => { |
| BoundOperation::OnlyCurrentStack(MaybeDualStack::NotDualStack(single_stack), None) |
| } |
| // No dual-stack support, so check the address is allowed in the current |
| // stack. |
| (MaybeDualStack::NotDualStack(single_stack), Some(addr)) => match try_unmap(addr) { |
| // The address is only representable in the current stack. |
| TryUnmapResult::CannotBeUnmapped(addr) => BoundOperation::OnlyCurrentStack( |
| MaybeDualStack::NotDualStack(single_stack), |
| Some(addr), |
| ), |
| // The address has a representation in the other stack but there's |
| // no dual-stack support! |
| TryUnmapResult::Mapped(_addr) => { |
| let _: Option<ZonedAddr<SocketIpAddr<<I::OtherVersion as Ip>::Addr>, _>> = _addr; |
| return Err(Either::Right(LocalAddressError::CannotBindToAddress)); |
| } |
| }, |
| }; |
| |
| fn try_bind_single_stack< |
| I: IpExt, |
| S: DatagramSocketSpec, |
| CC: TransportIpContext<I, BC>, |
| BC: RngContext, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| bound: &mut BoundSocketMap< |
| I, |
| CC::WeakDeviceId, |
| S::AddrSpec, |
| S::SocketMapSpec<I, CC::WeakDeviceId>, |
| >, |
| addr: Option<ZonedAddr<SocketIpAddr<I::Addr>, CC::DeviceId>>, |
| device: &Option<CC::WeakDeviceId>, |
| local_id: Option<<S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| id: <S::SocketMapSpec<I, CC::WeakDeviceId> as SocketMapStateSpec>::ListenerId, |
| sharing: S::SharingState, |
| ) -> Result< |
| ListenerAddr< |
| ListenerIpAddr<I::Addr, <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| CC::WeakDeviceId, |
| >, |
| LocalAddressError, |
| > { |
| let identifier = match local_id { |
| Some(id) => Some(id), |
| None => try_pick_identifier::<I, S, _, _, _>( |
| addr.as_ref().map(ZonedAddr::addr), |
| bound, |
| bindings_ctx, |
| &sharing, |
| ), |
| } |
| .ok_or(LocalAddressError::FailedToAllocateLocalPort)?; |
| let (addr, device, identifier) = |
| try_pick_bound_address::<I, _, _, _>(addr, device, core_ctx, identifier)?; |
| let weak_device = device.map(|d| d.as_weak().into_owned()); |
| |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| bound, |
| addr, |
| identifier, |
| weak_device.clone(), |
| sharing, |
| id, |
| ) |
| .map(|()| ListenerAddr { ip: ListenerIpAddr { addr, identifier }, device: weak_device }) |
| } |
| |
| let bound_addr: ListenerAddr<S::ListenerIpAddr<I>, CC::WeakDeviceId> = match bound_operation { |
| BoundOperation::OnlyCurrentStack(either_dual_stack, addr) => { |
| let converter = either_dual_stack.to_converter(); |
| core_ctx |
| .with_bound_sockets_mut(|core_ctx, bound| { |
| let id = S::make_bound_socket_map_id(id); |
| |
| try_bind_single_stack::<I, S, _, _>( |
| core_ctx, |
| bindings_ctx, |
| bound, |
| addr, |
| device, |
| local_id, |
| id, |
| sharing.clone(), |
| ) |
| }) |
| .map(|ListenerAddr { ip: ListenerIpAddr { addr, identifier }, device }| { |
| let ip = match converter { |
| MaybeDualStack::DualStack(converter) => converter.convert_back( |
| DualStackListenerIpAddr::ThisStack(ListenerIpAddr { addr, identifier }), |
| ), |
| MaybeDualStack::NotDualStack(converter) => { |
| converter.convert_back(ListenerIpAddr { addr, identifier }) |
| } |
| }; |
| ListenerAddr { ip, device } |
| }) |
| } |
| BoundOperation::OnlyOtherStack(core_ctx, addr) => { |
| let id = core_ctx.to_other_bound_socket_id(id); |
| core_ctx |
| .with_other_bound_sockets_mut(|core_ctx, other_bound| { |
| try_bind_single_stack::<_, S, _, _>( |
| core_ctx, |
| bindings_ctx, |
| other_bound, |
| addr, |
| device, |
| local_id, |
| id, |
| sharing.clone(), |
| ) |
| }) |
| .map(|ListenerAddr { ip: ListenerIpAddr { addr, identifier }, device }| { |
| ListenerAddr { |
| ip: core_ctx.converter().convert_back(DualStackListenerIpAddr::OtherStack( |
| ListenerIpAddr { addr, identifier }, |
| )), |
| device, |
| } |
| }) |
| } |
| BoundOperation::DualStackAnyAddr(core_ctx) => { |
| let ids = PairedBoundSocketIds { |
| this: S::make_bound_socket_map_id(id), |
| other: core_ctx.to_other_bound_socket_id(id), |
| }; |
| core_ctx |
| .with_both_bound_sockets_mut(|core_ctx, bound, other_bound| { |
| let mut bound_pair = PairedSocketMapMut { bound, other_bound }; |
| let sharing = sharing.clone(); |
| |
| let identifier = match local_id { |
| Some(id) => Some(id), |
| None => try_pick_identifier::<I, S, _, _, _>( |
| DualStackUnspecifiedAddr, |
| &bound_pair, |
| bindings_ctx, |
| &sharing, |
| ), |
| } |
| .ok_or(LocalAddressError::FailedToAllocateLocalPort)?; |
| let (_addr, device, identifier) = |
| try_pick_bound_address::<I, _, _, _>(None, device, core_ctx, identifier)?; |
| let weak_device = device.map(|d| d.as_weak().into_owned()); |
| |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| &mut bound_pair, |
| DualStackUnspecifiedAddr, |
| identifier, |
| weak_device.clone(), |
| sharing, |
| ids, |
| ) |
| .map(|()| (identifier, weak_device)) |
| }) |
| .map(|(identifier, device)| ListenerAddr { |
| ip: core_ctx |
| .converter() |
| .convert_back(DualStackListenerIpAddr::BothStacks(identifier)), |
| device, |
| }) |
| } |
| } |
| .map_err(Either::Right)?; |
| // Match Linux behavior by only storing the original bound addr when the |
| // local_id was provided by the caller. |
| let original_bound_addr = local_id.map(|_id| { |
| let ListenerAddr { ip, device: _ } = &bound_addr; |
| ip.clone() |
| }); |
| |
| // Replace the unbound state only after we're sure the |
| // insertion has succeeded. |
| *state = SocketState::Bound(BoundSocketState { |
| socket_type: BoundSocketStateType::Listener { |
| state: ListenerState { |
| // TODO(https://fxbug.dev/42082099): Remove this clone(). |
| ip_options: ip_options.clone(), |
| addr: bound_addr, |
| }, |
| sharing: sharing.clone(), |
| }, |
| original_bound_addr, |
| }); |
| Ok(()) |
| } |
| |
| /// An error when attempting to create a datagram socket. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum ConnectError { |
| /// An error was encountered creating an IP socket. |
| #[error("{}", _0)] |
| Ip(#[from] IpSockCreationError), |
| /// No local port was specified, and none could be automatically allocated. |
| #[error("a local port could not be allocated")] |
| CouldNotAllocateLocalPort, |
| /// The specified socket addresses (IP addresses and ports) conflict with an |
| /// existing socket. |
| #[error("the socket's IP address and port conflict with an existing socket")] |
| SockAddrConflict, |
| /// There was a problem with the provided address relating to its zone. |
| #[error("{}", _0)] |
| Zone(#[from] ZonedAddressError), |
| /// The remote address is mapped (i.e. an ipv4-mapped-ipv6 address), but the |
| /// socket is not dual-stack enabled. |
| #[error("IPv4-mapped-IPv6 addresses are not supported by this socket")] |
| RemoteUnexpectedlyMapped, |
| /// The remote address is non-mapped (i.e not an ipv4-mapped-ipv6 address), |
| /// but the socket is dual stack enabled and bound to a mapped address. |
| #[error("non IPv4-mapped-Ipv6 addresses are not supported by this socket")] |
| RemoteUnexpectedlyNonMapped, |
| } |
| |
| /// Parameters required to connect a socket. |
| struct ConnectParameters<WireI: IpExt, SocketI: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| local_ip: Option<SocketIpAddr<WireI::Addr>>, |
| local_port: Option<<S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| remote_ip: ZonedAddr<SocketIpAddr<WireI::Addr>, D::Strong>, |
| remote_port: <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| device: Option<D>, |
| sharing: S::SharingState, |
| ip_options: IpOptions<SocketI, D, S>, |
| socket_id: |
| <S::SocketMapSpec<WireI, D> as DatagramSocketMapSpec<WireI, D, S::AddrSpec>>::BoundSocketId, |
| original_shutdown: Option<Shutdown>, |
| extra: S::ConnStateExtra, |
| } |
| |
| /// Inserts a connected socket into the bound socket map. |
| /// |
| /// It accepts two closures that capture the logic required to remove and |
| /// reinsert the original state from/into the bound_socket_map. The original |
| /// state will only be reinserted if an error is encountered during connect. |
| /// The output of `remove_original` is fed into `reinsert_original`. |
| fn connect_inner< |
| WireI: IpExt, |
| SocketI: IpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec, |
| R, |
| BC: RngContext, |
| CC: IpSocketHandler<WireI, BC, WeakDeviceId = D, DeviceId = D::Strong>, |
| >( |
| connect_params: ConnectParameters<WireI, SocketI, D, S>, |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| sockets: &mut BoundSocketMap<WireI, D, S::AddrSpec, S::SocketMapSpec<WireI, D>>, |
| remove_original: impl FnOnce( |
| &mut BoundSocketMap<WireI, D, S::AddrSpec, S::SocketMapSpec<WireI, D>>, |
| ) -> R, |
| reinsert_original: impl FnOnce( |
| &mut BoundSocketMap<WireI, D, S::AddrSpec, S::SocketMapSpec<WireI, D>>, |
| R, |
| ), |
| ) -> Result<ConnState<WireI, SocketI, D, S>, ConnectError> { |
| let ConnectParameters { |
| local_ip, |
| local_port, |
| remote_ip, |
| remote_port, |
| device, |
| sharing, |
| ip_options, |
| socket_id, |
| original_shutdown, |
| extra, |
| } = connect_params; |
| |
| let (remote_ip, socket_device) = |
| crate::transport::resolve_addr_with_device::<WireI::Addr, _, _, _>( |
| remote_ip, |
| device.clone(), |
| )?; |
| |
| let clear_device_on_disconnect = device.is_none() && socket_device.is_some(); |
| |
| let ip_sock = IpSocketHandler::<WireI, _>::new_ip_socket( |
| core_ctx, |
| bindings_ctx, |
| socket_device.as_ref().map(|d| d.as_ref()), |
| local_ip.map(SocketIpAddr::into), |
| remote_ip, |
| S::ip_proto::<WireI>(), |
| )?; |
| |
| let local_port = match local_port { |
| Some(id) => id.clone(), |
| None => S::try_alloc_local_id( |
| sockets, |
| bindings_ctx, |
| DatagramFlowId { |
| local_ip: *ip_sock.local_ip(), |
| remote_ip: *ip_sock.remote_ip(), |
| remote_id: remote_port.clone(), |
| }, |
| ) |
| .ok_or(ConnectError::CouldNotAllocateLocalPort)?, |
| }; |
| let conn_addr = ConnAddr { |
| ip: ConnIpAddr { |
| local: (*ip_sock.local_ip(), local_port), |
| remote: (*ip_sock.remote_ip(), remote_port), |
| }, |
| device: ip_sock.device().cloned(), |
| }; |
| // Now that all the other checks have been done, actually remove the |
| // original state from the socket map. |
| let reinsert_op = remove_original(sockets); |
| // Try to insert the new connection, restoring the original state on |
| // failure. |
| let bound_addr = match sockets.conns_mut().try_insert(conn_addr, sharing, socket_id) { |
| Ok(bound_entry) => bound_entry.get_addr().clone(), |
| Err(( |
| InsertError::Exists |
| | InsertError::IndirectConflict |
| | InsertError::ShadowerExists |
| | InsertError::ShadowAddrExists, |
| _sharing, |
| )) => { |
| reinsert_original(sockets, reinsert_op); |
| return Err(ConnectError::SockAddrConflict); |
| } |
| }; |
| Ok(ConnState { |
| socket: ip_sock, |
| ip_options, |
| clear_device_on_disconnect, |
| shutdown: original_shutdown.unwrap_or(Shutdown::default()), |
| addr: bound_addr, |
| extra, |
| }) |
| } |
| |
| /// State required to perform single-stack connection of a socket. |
| struct SingleStackConnectOperation<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| params: ConnectParameters<I, I, D, S>, |
| remove_op: Option<SingleStackRemoveOperation<I, D, S>>, |
| sharing: S::SharingState, |
| } |
| |
| impl<I: IpExt, D: device::WeakId, S: DatagramSocketSpec> SingleStackConnectOperation<I, D, S> { |
| /// Constructs the connect operation from existing socket state. |
| fn new_from_state< |
| BC, |
| CC: NonDualStackDatagramBoundStateContext<I, BC, S, WeakDeviceId = D, DeviceId = D::Strong>, |
| >( |
| core_ctx: &mut CC, |
| socket_id: &S::SocketId<I, D>, |
| state: &SocketState<I, D, S>, |
| remote_ip: ZonedAddr<SocketIpAddr<I::Addr>, D::Strong>, |
| remote_port: <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| extra: S::ConnStateExtra, |
| ) -> Self { |
| match state { |
| SocketState::Unbound(UnboundSocketState { device, sharing, ip_options }) => { |
| SingleStackConnectOperation { |
| params: ConnectParameters { |
| local_ip: None, |
| local_port: None, |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }, |
| sharing: sharing.clone(), |
| remove_op: None, |
| } |
| } |
| SocketState::Bound(state) => { |
| let remove_op = |
| SingleStackRemoveOperation::new_from_state(core_ctx, socket_id, state); |
| let BoundSocketState { socket_type, original_bound_addr: _ } = state; |
| match socket_type { |
| BoundSocketStateType::Listener { |
| state: ListenerState { ip_options, addr: ListenerAddr { ip, device } }, |
| sharing, |
| } => { |
| let ListenerIpAddr { addr, identifier } = core_ctx.converter().convert(ip); |
| SingleStackConnectOperation { |
| params: ConnectParameters { |
| local_ip: addr.clone(), |
| local_port: Some(*identifier), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }, |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| } |
| } |
| BoundSocketStateType::Connected { state, sharing } => { |
| let ConnState { |
| socket: _, |
| ip_options, |
| shutdown, |
| addr: |
| ConnAddr { |
| ip: ConnIpAddr { local: (local_ip, local_id), remote: _ }, |
| device, |
| }, |
| clear_device_on_disconnect: _, |
| extra: _, |
| } = core_ctx.converter().convert(state); |
| SingleStackConnectOperation { |
| params: ConnectParameters { |
| local_ip: Some(local_ip.clone()), |
| local_port: Some(*local_id), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: Some(shutdown.clone()), |
| extra, |
| }, |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// Performs this operation and connects the socket. |
| /// |
| /// This is primarily a wrapper around `connect_inner` that establishes the |
| /// remove/reinsert closures for single stack removal. |
| /// |
| /// Returns a tuple containing the state, and sharing state for the new |
| /// connection. |
| fn apply<BC: RngContext, CC: IpSocketHandler<I, BC, WeakDeviceId = D, DeviceId = D::Strong>>( |
| self, |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| socket_map: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| ) -> Result<(ConnState<I, I, D, S>, S::SharingState), ConnectError> { |
| let SingleStackConnectOperation { params, remove_op, sharing } = self; |
| let remove_fn = |
| |sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>| { |
| remove_op.map(|remove_op| remove_op.apply(sockets)) |
| }; |
| let reinsert_fn = |
| |sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| remove_info: Option<SingleStackRemoveInfo<I, D, S>>| { |
| if let Some(remove_info) = remove_info { |
| remove_info.reinsert(sockets) |
| } |
| }; |
| let conn_state = |
| connect_inner(params, core_ctx, bindings_ctx, socket_map, remove_fn, reinsert_fn)?; |
| Ok((conn_state, sharing)) |
| } |
| } |
| |
| /// State required to perform dual-stack connection of a socket. |
| struct DualStackConnectOperation<I: DualStackIpExt, D: device::WeakId, S: DatagramSocketSpec> { |
| params: EitherStack<ConnectParameters<I, I, D, S>, ConnectParameters<I::OtherVersion, I, D, S>>, |
| remove_op: Option<DualStackRemoveOperation<I, D, S>>, |
| sharing: S::SharingState, |
| } |
| |
| impl<I: DualStackIpExt, D: device::WeakId, S: DatagramSocketSpec> |
| DualStackConnectOperation<I, D, S> |
| { |
| /// Constructs the connect operation from existing socket state. |
| fn new_from_state< |
| BC, |
| CC: DualStackDatagramBoundStateContext<I, BC, S, WeakDeviceId = D, DeviceId = D::Strong>, |
| >( |
| core_ctx: &mut CC, |
| socket_id: &S::SocketId<I, D>, |
| state: &SocketState<I, D, S>, |
| remote_ip: DualStackRemoteIp<I, D::Strong>, |
| remote_port: <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| extra: S::ConnStateExtra, |
| ) -> Result<Self, ConnectError> { |
| match state { |
| SocketState::Unbound(UnboundSocketState { device, sharing, ip_options }) => { |
| // Unbound sockets don't have a predisposition of which stack to |
| // connect in. Instead, it's dictated entirely by the remote. |
| let params = match remote_ip { |
| DualStackRemoteIp::ThisStack(remote_ip) => { |
| EitherStack::ThisStack(ConnectParameters { |
| local_ip: None, |
| local_port: None, |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }) |
| } |
| DualStackRemoteIp::OtherStack(remote_ip) => { |
| if !core_ctx.dual_stack_enabled(ip_options) { |
| return Err(ConnectError::RemoteUnexpectedlyMapped); |
| } |
| EitherStack::OtherStack(ConnectParameters { |
| local_ip: None, |
| local_port: None, |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: core_ctx.to_other_bound_socket_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }) |
| } |
| }; |
| Ok(DualStackConnectOperation { params, remove_op: None, sharing: sharing.clone() }) |
| } |
| SocketState::Bound(state) => { |
| let remove_op = |
| DualStackRemoveOperation::new_from_state(core_ctx, socket_id, state); |
| |
| let BoundSocketState { socket_type, original_bound_addr: _ } = state; |
| match socket_type { |
| BoundSocketStateType::Listener { |
| state: ListenerState { ip_options, addr: ListenerAddr { ip, device } }, |
| sharing, |
| } => { |
| match (remote_ip, core_ctx.converter().convert(ip)) { |
| // Disallow connecting to the other stack because the |
| // existing socket state is in this stack. |
| ( |
| DualStackRemoteIp::OtherStack(_), |
| DualStackListenerIpAddr::ThisStack(_), |
| ) => Err(ConnectError::RemoteUnexpectedlyMapped), |
| // Disallow connecting to this stack because the existing |
| // socket state is in the other stack. |
| ( |
| DualStackRemoteIp::ThisStack(_), |
| DualStackListenerIpAddr::OtherStack(_), |
| ) => Err(ConnectError::RemoteUnexpectedlyNonMapped), |
| // Connect in this stack. |
| ( |
| DualStackRemoteIp::ThisStack(remote_ip), |
| DualStackListenerIpAddr::ThisStack(ListenerIpAddr { |
| addr, |
| identifier, |
| }), |
| ) => Ok(DualStackConnectOperation { |
| params: EitherStack::ThisStack(ConnectParameters { |
| local_ip: addr.clone(), |
| local_port: Some(*identifier), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }), |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| }), |
| // Listeners in "both stacks" can connect to either |
| // stack. Connect in this stack as specified by the |
| // remote. |
| ( |
| DualStackRemoteIp::ThisStack(remote_ip), |
| DualStackListenerIpAddr::BothStacks(identifier), |
| ) => Ok(DualStackConnectOperation { |
| params: EitherStack::ThisStack(ConnectParameters { |
| local_ip: None, |
| local_port: Some(*identifier), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }), |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| }), |
| // Connect in the other stack. |
| ( |
| DualStackRemoteIp::OtherStack(remote_ip), |
| DualStackListenerIpAddr::OtherStack(ListenerIpAddr { |
| addr, |
| identifier, |
| }), |
| ) => Ok(DualStackConnectOperation { |
| params: EitherStack::OtherStack(ConnectParameters { |
| local_ip: addr.clone(), |
| local_port: Some(*identifier), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: core_ctx.to_other_bound_socket_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }), |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| }), |
| // Listeners in "both stacks" can connect to either |
| // stack. Connect in the other stack as specified by |
| // the remote. |
| ( |
| DualStackRemoteIp::OtherStack(remote_ip), |
| DualStackListenerIpAddr::BothStacks(identifier), |
| ) => Ok(DualStackConnectOperation { |
| params: EitherStack::OtherStack(ConnectParameters { |
| local_ip: None, |
| local_port: Some(*identifier), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: core_ctx.to_other_bound_socket_id(socket_id), |
| original_shutdown: None, |
| extra, |
| }), |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| }), |
| } |
| } |
| BoundSocketStateType::Connected { state, sharing } => { |
| match (remote_ip, core_ctx.converter().convert(state)) { |
| // Disallow connecting to the other stack because the |
| // existing socket state is in this stack. |
| ( |
| DualStackRemoteIp::OtherStack(_), |
| DualStackConnState::ThisStack(_), |
| ) => Err(ConnectError::RemoteUnexpectedlyMapped), |
| // Disallow connecting to this stack because the existing |
| // socket state is in the other stack. |
| ( |
| DualStackRemoteIp::ThisStack(_), |
| DualStackConnState::OtherStack(_), |
| ) => Err(ConnectError::RemoteUnexpectedlyNonMapped), |
| // Connect in this stack. |
| ( |
| DualStackRemoteIp::ThisStack(remote_ip), |
| DualStackConnState::ThisStack(ConnState { |
| socket: _, |
| ip_options, |
| shutdown, |
| addr: |
| ConnAddr { |
| ip: |
| ConnIpAddr { local: (local_ip, local_id), remote: _ }, |
| device, |
| }, |
| clear_device_on_disconnect: _, |
| extra: _, |
| }), |
| ) => Ok(DualStackConnectOperation { |
| params: EitherStack::ThisStack(ConnectParameters { |
| local_ip: Some(local_ip.clone()), |
| local_port: Some(*local_id), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: S::make_bound_socket_map_id(socket_id), |
| original_shutdown: Some(shutdown.clone()), |
| extra, |
| }), |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| }), |
| // Connect in the other stack. |
| ( |
| DualStackRemoteIp::OtherStack(remote_ip), |
| DualStackConnState::OtherStack(ConnState { |
| socket: _, |
| ip_options, |
| shutdown, |
| addr: |
| ConnAddr { |
| ip: |
| ConnIpAddr { local: (local_ip, local_id), remote: _ }, |
| device, |
| }, |
| clear_device_on_disconnect: _, |
| extra: _, |
| }), |
| ) => Ok(DualStackConnectOperation { |
| params: EitherStack::OtherStack(ConnectParameters { |
| local_ip: Some(local_ip.clone()), |
| local_port: Some(*local_id), |
| remote_ip, |
| remote_port, |
| device: device.clone(), |
| sharing: sharing.clone(), |
| ip_options: ip_options.clone(), |
| socket_id: core_ctx.to_other_bound_socket_id(socket_id), |
| original_shutdown: Some(shutdown.clone()), |
| extra, |
| }), |
| sharing: sharing.clone(), |
| remove_op: Some(remove_op), |
| }), |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// Performs this operation and connects the socket. |
| /// |
| /// This is primarily a wrapper around [`connect_inner`] that establishes the |
| /// remove/reinsert closures for dual stack removal. |
| /// |
| /// Returns a tuple containing the state, and sharing state for the new |
| /// connection. |
| fn apply< |
| BC: RngContext, |
| CC: IpSocketHandler<I, BC, WeakDeviceId = D, DeviceId = D::Strong> |
| + IpSocketHandler<I::OtherVersion, BC, WeakDeviceId = D, DeviceId = D::Strong>, |
| >( |
| self, |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| socket_map: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| other_socket_map: &mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >, |
| ) -> Result<(DualStackConnState<I, D, S>, S::SharingState), ConnectError> { |
| let DualStackConnectOperation { params, remove_op, sharing } = self; |
| let conn_state = match params { |
| EitherStack::ThisStack(params) => { |
| // NB: Because we're connecting in this stack, we receive this |
| // stack's sockets as an argument to `remove_fn` and |
| // `reinsert_fn`. Thus we need to capture + pass through the |
| // other stack's sockets. |
| let remove_fn = |
| |sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>| { |
| remove_op.map(|remove_op| { |
| let remove_info = remove_op.apply(sockets, other_socket_map); |
| (remove_info, other_socket_map) |
| }) |
| }; |
| let reinsert_fn = |
| |sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| remove_info: Option<( |
| DualStackRemoveInfo<I, D, S>, |
| &mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >, |
| )>| { |
| if let Some((remove_info, other_sockets)) = remove_info { |
| remove_info.reinsert(sockets, other_sockets) |
| } |
| }; |
| connect_inner(params, core_ctx, bindings_ctx, socket_map, remove_fn, reinsert_fn) |
| .map(DualStackConnState::ThisStack) |
| } |
| EitherStack::OtherStack(params) => { |
| // NB: Because we're connecting in the other stack, we receive |
| // the other stack's sockets as an argument to `remove_fn` and |
| // `reinsert_fn`. Thus we need to capture + pass through this |
| // stack's sockets. |
| let remove_fn = |other_sockets: &mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >| { |
| remove_op.map(|remove_op| { |
| let remove_info = remove_op.apply(socket_map, other_sockets); |
| (remove_info, socket_map) |
| }) |
| }; |
| let reinsert_fn = |other_sockets: &mut BoundSocketMap< |
| I::OtherVersion, |
| D, |
| S::AddrSpec, |
| S::SocketMapSpec<I::OtherVersion, D>, |
| >, |
| remove_info: Option<( |
| DualStackRemoveInfo<I, D, S>, |
| &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| )>| { |
| if let Some((remove_info, sockets)) = remove_info { |
| remove_info.reinsert(sockets, other_sockets) |
| } |
| }; |
| connect_inner( |
| params, |
| core_ctx, |
| bindings_ctx, |
| other_socket_map, |
| remove_fn, |
| reinsert_fn, |
| ) |
| .map(DualStackConnState::OtherStack) |
| } |
| }?; |
| Ok((conn_state, sharing)) |
| } |
| } |
| |
| pub(crate) fn connect< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| remote_ip: Option<ZonedAddr<SpecifiedAddr<I::Addr>, CC::DeviceId>>, |
| remote_id: <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| extra: S::ConnStateExtra, |
| ) -> Result<(), ConnectError> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let (conn_state, sharing) = match ( |
| core_ctx.dual_stack_context(), |
| dual_stack_remote_ip::<I, _>(remote_ip.clone()), |
| ) { |
| (MaybeDualStack::DualStack(ds), remote_ip) => { |
| let connect_op = DualStackConnectOperation::new_from_state( |
| ds, id, state, remote_ip, remote_id, extra, |
| )?; |
| let converter = ds.converter(); |
| let (conn_state, sharing) = |
| ds.with_both_bound_sockets_mut(|core_ctx, bound, other_bound| { |
| connect_op.apply(core_ctx, bindings_ctx, bound, other_bound) |
| })?; |
| Ok((converter.convert_back(conn_state), sharing)) |
| } |
| (MaybeDualStack::NotDualStack(nds), DualStackRemoteIp::ThisStack(remote_ip)) => { |
| let connect_op = SingleStackConnectOperation::new_from_state( |
| nds, id, state, remote_ip, remote_id, extra, |
| ); |
| let converter = nds.converter(); |
| let (conn_state, sharing) = |
| core_ctx.with_bound_sockets_mut(|core_ctx, bound| { |
| connect_op.apply(core_ctx, bindings_ctx, bound) |
| })?; |
| Ok((converter.convert_back(conn_state), sharing)) |
| } |
| (MaybeDualStack::NotDualStack(_), DualStackRemoteIp::OtherStack(_)) => { |
| Err(ConnectError::RemoteUnexpectedlyMapped) |
| } |
| }?; |
| let original_bound_addr = match state { |
| SocketState::Unbound(_) => None, |
| SocketState::Bound(BoundSocketState { socket_type: _, original_bound_addr }) => { |
| original_bound_addr.clone() |
| } |
| }; |
| *state = SocketState::Bound(BoundSocketState { |
| socket_type: BoundSocketStateType::Connected { state: conn_state, sharing }, |
| original_bound_addr, |
| }); |
| Ok(()) |
| }) |
| } |
| |
| /// A connected socket was expected. |
| #[derive(Copy, Clone, Debug, Default, Eq, GenericOverIp, PartialEq)] |
| #[generic_over_ip()] |
| pub struct ExpectedConnError; |
| |
| /// An unbound socket was expected. |
| #[derive(Copy, Clone, Debug, Default, Eq, GenericOverIp, PartialEq)] |
| #[generic_over_ip()] |
| pub struct ExpectedUnboundError; |
| |
| pub(crate) fn disconnect_connected< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> Result<(), ExpectedConnError> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let inner_state = match state { |
| SocketState::Unbound(_) => return Err(ExpectedConnError), |
| SocketState::Bound(state) => state, |
| }; |
| let BoundSocketState { socket_type, original_bound_addr } = inner_state; |
| let conn_state = match socket_type { |
| BoundSocketStateType::Listener { state: _, sharing: _ } => { |
| return Err(ExpectedConnError) |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => state, |
| }; |
| |
| let clear_device_on_disconnect = match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(dual_stack) => match dual_stack |
| .converter() |
| .convert(conn_state) |
| { |
| DualStackConnState::ThisStack(conn_state) => conn_state.clear_device_on_disconnect, |
| DualStackConnState::OtherStack(conn_state) => conn_state.clear_device_on_disconnect, |
| }, |
| MaybeDualStack::NotDualStack(not_dual_stack) => { |
| not_dual_stack.converter().convert(conn_state).clear_device_on_disconnect |
| } |
| }; |
| |
| *state = match original_bound_addr { |
| None => SocketState::Unbound(disconnect_to_unbound( |
| core_ctx, |
| id, |
| clear_device_on_disconnect, |
| inner_state, |
| )), |
| Some(original_bound_addr) => SocketState::Bound(disconnect_to_listener( |
| core_ctx, |
| id, |
| original_bound_addr.clone(), |
| clear_device_on_disconnect, |
| inner_state, |
| )), |
| }; |
| Ok(()) |
| }) |
| } |
| |
| /// Converts a connected socket to an unbound socket. |
| /// |
| /// Removes the connection's entry from the [`BoundSocketMap`], and returns the |
| /// socket's new state. |
| fn disconnect_to_unbound< |
| I: IpExt, |
| BC, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| clear_device_on_disconnect: bool, |
| socket_state: &BoundSocketState<I, CC::WeakDeviceId, S>, |
| ) -> UnboundSocketState<I, CC::WeakDeviceId, S> { |
| let (ip_options, sharing, mut device) = match core_ctx.dual_stack_context() { |
| MaybeDualStack::NotDualStack(nds) => { |
| let remove_op = SingleStackRemoveOperation::new_from_state(nds, id, socket_state); |
| let info = core_ctx.with_bound_sockets_mut(|_core_ctx, bound| remove_op.apply(bound)); |
| info.into_options_sharing_and_device() |
| } |
| MaybeDualStack::DualStack(ds) => { |
| let remove_op = DualStackRemoveOperation::new_from_state(ds, id, socket_state); |
| let info = ds.with_both_bound_sockets_mut(|_core_ctx, bound, other_bound| { |
| remove_op.apply(bound, other_bound) |
| }); |
| info.into_options_sharing_and_device() |
| } |
| }; |
| if clear_device_on_disconnect { |
| device = None |
| } |
| UnboundSocketState { device, sharing, ip_options } |
| } |
| |
| /// Converts a connected socket to a listener socket. |
| /// |
| /// Removes the connection's entry from the [`BoundSocketMap`] and returns the |
| /// socket's new state. |
| fn disconnect_to_listener< |
| I: IpExt, |
| BC, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| listener_ip: S::ListenerIpAddr<I>, |
| clear_device_on_disconnect: bool, |
| socket_state: &BoundSocketState<I, CC::WeakDeviceId, S>, |
| ) -> BoundSocketState<I, CC::WeakDeviceId, S> { |
| let (ip_options, sharing, device) = match core_ctx.dual_stack_context() { |
| MaybeDualStack::NotDualStack(nds) => { |
| let ListenerIpAddr { addr, identifier } = nds.converter().convert(listener_ip.clone()); |
| let remove_op = SingleStackRemoveOperation::new_from_state(nds, id, socket_state); |
| core_ctx.with_bound_sockets_mut(|_core_ctx, bound| { |
| let (ip_options, sharing, mut device) = |
| remove_op.apply(bound).into_options_sharing_and_device(); |
| if clear_device_on_disconnect { |
| device = None; |
| } |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| bound, |
| addr, |
| identifier, |
| device.clone(), |
| sharing.clone(), |
| S::make_bound_socket_map_id(id), |
| ) |
| .expect("inserting listener for disconnected socket should succeed"); |
| (ip_options, sharing, device) |
| }) |
| } |
| MaybeDualStack::DualStack(ds) => { |
| let remove_op = DualStackRemoveOperation::new_from_state(ds, id, socket_state); |
| let other_id = ds.to_other_bound_socket_id(id); |
| let id = S::make_bound_socket_map_id(id); |
| let converter = ds.converter(); |
| ds.with_both_bound_sockets_mut(|_core_ctx, bound, other_bound| { |
| let (ip_options, sharing, mut device) = |
| remove_op.apply(bound, other_bound).into_options_sharing_and_device(); |
| if clear_device_on_disconnect { |
| device = None; |
| } |
| |
| match converter.convert(listener_ip.clone()) { |
| DualStackListenerIpAddr::ThisStack(ListenerIpAddr { addr, identifier }) => { |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| bound, |
| addr, |
| identifier, |
| device.clone(), |
| sharing.clone(), |
| id, |
| ) |
| } |
| DualStackListenerIpAddr::OtherStack(ListenerIpAddr { addr, identifier }) => { |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| other_bound, |
| addr, |
| identifier, |
| device.clone(), |
| sharing.clone(), |
| other_id, |
| ) |
| } |
| DualStackListenerIpAddr::BothStacks(identifier) => { |
| let ids = PairedBoundSocketIds { this: id, other: other_id }; |
| let mut bound_pair = PairedSocketMapMut { bound, other_bound }; |
| BoundStateHandler::<_, S, _>::try_insert_listener( |
| &mut bound_pair, |
| DualStackUnspecifiedAddr, |
| identifier, |
| device.clone(), |
| sharing.clone(), |
| ids, |
| ) |
| } |
| } |
| .expect("inserting listener for disconnected socket should succeed"); |
| (ip_options, sharing, device) |
| }) |
| } |
| }; |
| BoundSocketState { |
| original_bound_addr: Some(listener_ip.clone()), |
| socket_type: BoundSocketStateType::Listener { |
| state: ListenerState { ip_options, addr: ListenerAddr { ip: listener_ip, device } }, |
| sharing, |
| }, |
| } |
| } |
| |
| pub(crate) fn shutdown_connected< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| which: ShutdownType, |
| ) -> Result<(), ExpectedConnError> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let state = match state { |
| SocketState::Unbound(_) => return Err(ExpectedConnError), |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state: _, sharing: _ } => { |
| return Err(ExpectedConnError) |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => state, |
| } |
| } |
| }; |
| let (shutdown_send, shutdown_receive) = which.to_send_receive(); |
| let Shutdown { send, receive } = match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(ds) => ds.converter().convert(state).as_mut(), |
| MaybeDualStack::NotDualStack(nds) => nds.converter().convert(state).as_mut(), |
| }; |
| *send |= shutdown_send; |
| *receive |= shutdown_receive; |
| Ok(()) |
| }) |
| } |
| |
| pub(crate) fn get_shutdown_connected< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> Option<ShutdownType> { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let state = match state { |
| SocketState::Unbound(_) => return None, |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state: _, sharing: _ } => return None, |
| BoundSocketStateType::Connected { state, sharing: _ } => state, |
| } |
| } |
| }; |
| let Shutdown { send, receive } = match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(ds) => ds.converter().convert(state).as_ref(), |
| MaybeDualStack::NotDualStack(nds) => nds.converter().convert(state).as_ref(), |
| }; |
| ShutdownType::from_send_receive(*send, *receive) |
| }) |
| } |
| |
| /// Error encountered when sending a datagram on a socket. |
| #[derive(Debug, GenericOverIp)] |
| #[generic_over_ip()] |
| pub enum SendError<SE> { |
| /// The socket is not connected, |
| NotConnected, |
| /// The socket is not writeable. |
| NotWriteable, |
| /// There was a problem sending the IP packet. |
| IpSock(IpSockSendError), |
| /// There was a problem when serializing the packet. |
| SerializeError(SE), |
| } |
| |
| pub(crate) fn send_conn< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| B: BufferMut, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| body: B, |
| ) -> Result<(), SendError<S::SerializeError>> { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let state = match state { |
| SocketState::Unbound(_) => return Err(SendError::NotConnected), |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state: _, sharing: _ } => { |
| return Err(SendError::NotConnected) |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => state, |
| } |
| } |
| }; |
| |
| struct SendParams< |
| 'a, |
| I: IpExt, |
| S: DatagramSocketSpec, |
| D: device::WeakId, |
| O: SendOptions<I, D>, |
| > { |
| socket: &'a IpSock<I, D>, |
| ip: &'a ConnIpAddr< |
| I::Addr, |
| <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| options: &'a O, |
| } |
| |
| enum Operation< |
| 'a, |
| I: DualStackIpExt, |
| S: DatagramSocketSpec, |
| D: device::WeakId, |
| BC: DatagramStateBindingsContext<I, S>, |
| DualStackSC: DualStackDatagramBoundStateContext<I, BC, S>, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| O: SendOptions<I, D>, |
| OtherO: SendOptions<I::OtherVersion, D>, |
| > { |
| SendToThisStack((SendParams<'a, I, S, D, O>, &'a mut CC)), |
| SendToOtherStack((SendParams<'a, I::OtherVersion, S, D, OtherO>, &'a mut DualStackSC)), |
| // Allow `Operation` to be generic over `B` and `C` so that they can |
| // be used in trait bounds for `DualStackSC` and `SC`. |
| _Phantom((Never, PhantomData<BC>)), |
| } |
| |
| let (shutdown, operation) = match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(dual_stack) => match dual_stack.converter().convert(state) { |
| DualStackConnState::ThisStack(ConnState { |
| socket, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown, |
| addr: ConnAddr { ip, device: _ }, |
| extra: _, |
| }) => ( |
| shutdown, |
| Operation::SendToThisStack(( |
| SendParams { socket, ip, options: ip_options }, |
| core_ctx, |
| )), |
| ), |
| DualStackConnState::OtherStack(ConnState { |
| socket, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown, |
| addr: ConnAddr { ip, device: _ }, |
| extra: _, |
| }) => ( |
| shutdown, |
| Operation::SendToOtherStack(( |
| SendParams { |
| socket, |
| ip, |
| options: dual_stack.to_other_send_options(ip_options), |
| }, |
| dual_stack, |
| )), |
| ), |
| }, |
| MaybeDualStack::NotDualStack(not_dual_stack) => { |
| let ConnState { |
| socket, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown, |
| addr: ConnAddr { ip, device: _ }, |
| extra: _, |
| } = not_dual_stack.converter().convert(state); |
| ( |
| shutdown, |
| Operation::SendToThisStack(( |
| SendParams { socket, ip, options: ip_options }, |
| core_ctx, |
| )), |
| ) |
| } |
| }; |
| |
| let Shutdown { send: shutdown_send, receive: _ } = shutdown; |
| if *shutdown_send { |
| return Err(SendError::NotWriteable); |
| } |
| |
| match operation { |
| Operation::SendToThisStack((SendParams { socket, ip, options }, core_ctx)) => { |
| let packet = |
| S::make_packet::<I, _>(body, &ip).map_err(SendError::SerializeError)?; |
| core_ctx.with_transport_context(|core_ctx| { |
| core_ctx |
| .send_ip_packet(bindings_ctx, &socket, packet, None, options) |
| .map_err(|(_serializer, send_error)| SendError::IpSock(send_error)) |
| }) |
| } |
| Operation::SendToOtherStack((SendParams { socket, ip, options }, dual_stack)) => { |
| let packet = S::make_packet::<I::OtherVersion, _>(body, &ip) |
| .map_err(SendError::SerializeError)?; |
| dual_stack.with_transport_context::<_, _>(|core_ctx| { |
| core_ctx |
| .send_ip_packet(bindings_ctx, &socket, packet, None, options) |
| .map_err(|(_serializer, send_error)| SendError::IpSock(send_error)) |
| }) |
| } |
| Operation::_Phantom((never, _)) => match never {}, |
| } |
| }) |
| } |
| |
| /// An error encountered while sending a datagram packet to an alternate address. |
| #[derive(Debug)] |
| pub enum SendToError<SE> { |
| /// The socket is not writeable. |
| NotWriteable, |
| /// There was a problem with the remote address relating to its zone. |
| Zone(ZonedAddressError), |
| /// An error was encountered while trying to create a temporary IP socket |
| /// to use for the send operation. |
| CreateAndSend(IpSockCreateAndSendError), |
| /// The remote address is mapped (i.e. an ipv4-mapped-ipv6 address), but the |
| /// socket is not dual-stack enabled. |
| RemoteUnexpectedlyMapped, |
| /// The remote address is non-mapped (i.e not an ipv4-mapped-ipv6 address), |
| /// but the socket is dual stack enabled and bound to a mapped address. |
| RemoteUnexpectedlyNonMapped, |
| /// The provided buffer is not vailid. |
| SerializeError(SE), |
| } |
| |
| pub(crate) fn send_to< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| B: BufferMut, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| remote_ip: Option<ZonedAddr<SpecifiedAddr<I::Addr>, CC::DeviceId>>, |
| remote_identifier: <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| body: B, |
| ) -> Result<(), Either<LocalAddressError, SendToError<S::SerializeError>>> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| match listen_inner(core_ctx, bindings_ctx, state, id, None, None) { |
| Ok(()) | Err(Either::Left(ExpectedUnboundError)) => (), |
| Err(Either::Right(e)) => return Err(Either::Left(e)), |
| }; |
| let state = match state { |
| SocketState::Unbound(_) => panic!("expected bound socket"), |
| SocketState::Bound(BoundSocketState { socket_type: state, original_bound_addr: _ }) => { |
| state |
| } |
| }; |
| |
| enum Operation< |
| 'a, |
| I: DualStackIpExt, |
| S: DatagramSocketSpec, |
| D: device::WeakId, |
| BC: DatagramStateBindingsContext<I, S>, |
| DualStackSC: DualStackDatagramBoundStateContext<I, BC, S>, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| O: SendOptions<I, D>, |
| OtherO: SendOptions<I::OtherVersion, D>, |
| > { |
| SendToThisStack((SendOneshotParameters<'a, I, S, D, O>, &'a mut CC)), |
| |
| SendToOtherStack( |
| (SendOneshotParameters<'a, I::OtherVersion, S, D, OtherO>, &'a mut DualStackSC), |
| ), |
| // Allow `Operation` to be generic over `B` and `C` so that they can |
| // be used in trait bounds for `DualStackSC` and `SC`. |
| _Phantom((Never, PhantomData<BC>)), |
| } |
| |
| let (operation, shutdown) = match ( |
| core_ctx.dual_stack_context(), |
| dual_stack_remote_ip::<I, _>(remote_ip.clone()), |
| ) { |
| (MaybeDualStack::NotDualStack(_), DualStackRemoteIp::OtherStack(_)) => { |
| return Err(Either::Right(SendToError::RemoteUnexpectedlyMapped)) |
| } |
| (MaybeDualStack::NotDualStack(nds), DualStackRemoteIp::ThisStack(remote_ip)) => { |
| match state { |
| BoundSocketStateType::Listener { |
| state: ListenerState { ip_options, addr: ListenerAddr { ip, device } }, |
| sharing: _, |
| } => { |
| let ListenerIpAddr { addr, identifier } = |
| nds.converter().convert(ip.clone()); |
| ( |
| Operation::SendToThisStack(( |
| SendOneshotParameters { |
| local_ip: addr, |
| local_id: identifier, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ip_options, |
| }, |
| core_ctx, |
| )), |
| None, |
| ) |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| let ConnState { |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown, |
| addr: |
| ConnAddr { |
| ip: ConnIpAddr { local: (local_ip, local_id), remote: _ }, |
| device, |
| }, |
| extra: _, |
| } = nds.converter().convert(state); |
| ( |
| Operation::SendToThisStack(( |
| SendOneshotParameters { |
| local_ip: Some(*local_ip), |
| local_id: *local_id, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ip_options, |
| }, |
| core_ctx, |
| )), |
| Some(shutdown), |
| ) |
| } |
| } |
| } |
| (MaybeDualStack::DualStack(ds), remote_ip) => match state { |
| BoundSocketStateType::Listener { |
| state: ListenerState { ip_options, addr: ListenerAddr { ip, device } }, |
| sharing: _, |
| } => match (ds.converter().convert(ip), remote_ip) { |
| (DualStackListenerIpAddr::ThisStack(_), DualStackRemoteIp::OtherStack(_)) => { |
| return Err(Either::Right(SendToError::RemoteUnexpectedlyMapped)) |
| } |
| (DualStackListenerIpAddr::OtherStack(_), DualStackRemoteIp::ThisStack(_)) => { |
| return Err(Either::Right(SendToError::RemoteUnexpectedlyNonMapped)) |
| } |
| ( |
| DualStackListenerIpAddr::ThisStack(ListenerIpAddr { addr, identifier }), |
| DualStackRemoteIp::ThisStack(remote_ip), |
| ) => ( |
| Operation::SendToThisStack(( |
| SendOneshotParameters { |
| local_ip: *addr, |
| local_id: *identifier, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ip_options, |
| }, |
| core_ctx, |
| )), |
| None, |
| ), |
| ( |
| DualStackListenerIpAddr::BothStacks(identifier), |
| DualStackRemoteIp::ThisStack(remote_ip), |
| ) => ( |
| Operation::SendToThisStack(( |
| SendOneshotParameters { |
| local_ip: None, |
| local_id: *identifier, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ip_options, |
| }, |
| core_ctx, |
| )), |
| None, |
| ), |
| ( |
| DualStackListenerIpAddr::OtherStack(ListenerIpAddr { addr, identifier }), |
| DualStackRemoteIp::OtherStack(remote_ip), |
| ) => ( |
| Operation::SendToOtherStack(( |
| SendOneshotParameters { |
| local_ip: *addr, |
| local_id: *identifier, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ds.to_other_send_options(ip_options), |
| }, |
| ds, |
| )), |
| None, |
| ), |
| ( |
| DualStackListenerIpAddr::BothStacks(identifier), |
| DualStackRemoteIp::OtherStack(remote_ip), |
| ) => ( |
| Operation::SendToOtherStack(( |
| SendOneshotParameters { |
| local_ip: None, |
| local_id: *identifier, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ds.to_other_send_options(ip_options), |
| }, |
| ds, |
| )), |
| None, |
| ), |
| }, |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| match (ds.converter().convert(state), remote_ip) { |
| (DualStackConnState::ThisStack(_), DualStackRemoteIp::OtherStack(_)) => { |
| return Err(Either::Right(SendToError::RemoteUnexpectedlyMapped)) |
| } |
| (DualStackConnState::OtherStack(_), DualStackRemoteIp::ThisStack(_)) => { |
| return Err(Either::Right(SendToError::RemoteUnexpectedlyNonMapped)) |
| } |
| ( |
| DualStackConnState::ThisStack(ConnState { |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown, |
| addr: |
| ConnAddr { |
| ip: ConnIpAddr { local: (local_ip, local_id), remote: _ }, |
| device, |
| }, |
| extra: _, |
| }), |
| DualStackRemoteIp::ThisStack(remote_ip), |
| ) => ( |
| Operation::SendToThisStack(( |
| SendOneshotParameters { |
| local_ip: Some(*local_ip), |
| local_id: *local_id, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ip_options, |
| }, |
| core_ctx, |
| )), |
| Some(shutdown), |
| ), |
| ( |
| DualStackConnState::OtherStack(ConnState { |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown, |
| addr: |
| ConnAddr { |
| ip: ConnIpAddr { local: (local_ip, local_id), remote: _ }, |
| device, |
| }, |
| extra: _, |
| }), |
| DualStackRemoteIp::OtherStack(remote_ip), |
| ) => ( |
| Operation::SendToOtherStack(( |
| SendOneshotParameters { |
| local_ip: Some(*local_ip), |
| local_id: *local_id, |
| remote_ip, |
| remote_id: remote_identifier, |
| device, |
| send_options: ds.to_other_send_options(ip_options), |
| }, |
| ds, |
| )), |
| Some(shutdown), |
| ), |
| } |
| } |
| }, |
| }; |
| |
| if let Some(Shutdown { send: shutdown_write, receive: _ }) = shutdown { |
| if *shutdown_write { |
| return Err(Either::Right(SendToError::NotWriteable)); |
| } |
| } |
| |
| match operation { |
| Operation::SendToThisStack((params, core_ctx)) => { |
| DatagramBoundStateContext::with_transport_context(core_ctx, |core_ctx| { |
| send_oneshot::<_, S, _, _, _, _>(core_ctx, bindings_ctx, params, body) |
| }) |
| } |
| Operation::SendToOtherStack((params, core_ctx)) => { |
| DualStackDatagramBoundStateContext::with_transport_context::<_, _>( |
| core_ctx, |
| |core_ctx| { |
| send_oneshot::<_, S, _, _, _, _>(core_ctx, bindings_ctx, params, body) |
| }, |
| ) |
| } |
| Operation::_Phantom((never, _)) => match never {}, |
| } |
| .map_err(Either::Right) |
| }) |
| } |
| |
| struct SendOneshotParameters< |
| 'a, |
| I: IpExt, |
| S: DatagramSocketSpec, |
| D: device::WeakId, |
| O: SendOptions<I, D>, |
| > { |
| local_ip: Option<SocketIpAddr<I::Addr>>, |
| local_id: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| remote_ip: ZonedAddr<SocketIpAddr<I::Addr>, D::Strong>, |
| remote_id: <S::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| device: &'a Option<D>, |
| send_options: &'a O, |
| } |
| |
| fn send_oneshot< |
| I: IpExt, |
| S: DatagramSocketSpec, |
| CC: IpSocketHandler<I, BC>, |
| BC, |
| B: BufferMut, |
| O: SendOptions<I, CC::WeakDeviceId>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| SendOneshotParameters { |
| local_ip, |
| local_id, |
| remote_ip, |
| remote_id, |
| device, |
| send_options, |
| }: SendOneshotParameters<'_, I, S, CC::WeakDeviceId, O>, |
| body: B, |
| ) -> Result<(), SendToError<S::SerializeError>> { |
| let (remote_ip, device) = match crate::transport::resolve_addr_with_device::<I::Addr, _, _, _>( |
| remote_ip, |
| device.clone(), |
| ) { |
| Ok(addr) => addr, |
| Err(e) => return Err(SendToError::Zone(e)), |
| }; |
| |
| core_ctx |
| .send_oneshot_ip_packet_with_fallible_serializer( |
| bindings_ctx, |
| device.as_ref().map(|d| d.as_ref()), |
| local_ip, |
| remote_ip, |
| S::ip_proto::<I>(), |
| send_options, |
| |local_ip| { |
| S::make_packet::<I, _>( |
| body, |
| &ConnIpAddr { local: (local_ip, local_id), remote: (remote_ip, remote_id) }, |
| ) |
| }, |
| None, |
| ) |
| .map_err(|err| match err { |
| SendOneShotIpPacketError::CreateAndSendError { err } => SendToError::CreateAndSend(err), |
| SendOneShotIpPacketError::SerializeError(err) => SendToError::SerializeError(err), |
| }) |
| } |
| |
| /// Mutably holds the original state of a bound socket required to update the |
| /// bound device. |
| enum SetBoundDeviceParameters< |
| 'a, |
| WireI: IpExt, |
| SocketI: IpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec, |
| > { |
| Listener { |
| ip: &'a ListenerIpAddr<WireI::Addr, <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| device: &'a mut Option<D>, |
| }, |
| Connected(&'a mut ConnState<WireI, SocketI, D, S>), |
| } |
| |
| /// Update the device for a bound socket. |
| /// |
| /// The update is applied both to the socket's entry in the given |
| /// [`BoundSocketMap`], and the mutable socket state in the given |
| /// [`SetBoundDeviceParameters`]. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the given `socket_id` is not present in the given `sockets` map. |
| fn set_bound_device_single_stack< |
| 'a, |
| WireI: IpExt, |
| SocketI: IpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec, |
| BC, |
| CC: IpSocketHandler<WireI, BC, WeakDeviceId = D, DeviceId = D::Strong>, |
| >( |
| bindings_ctx: &mut BC, |
| core_ctx: &mut CC, |
| params: SetBoundDeviceParameters<'a, WireI, SocketI, D, S>, |
| sockets: &mut BoundSocketMap<WireI, D, S::AddrSpec, S::SocketMapSpec<WireI, D>>, |
| socket_id: &< |
| S::SocketMapSpec<WireI, D> as DatagramSocketMapSpec<WireI, D, S::AddrSpec> |
| >::BoundSocketId, |
| new_device: Option<&D::Strong>, |
| ) -> Result<(), SocketError> { |
| let (local_ip, remote_ip, old_device) = match ¶ms { |
| SetBoundDeviceParameters::Listener { |
| ip: ListenerIpAddr { addr, identifier: _ }, |
| device, |
| } => (addr.as_ref(), None, device.as_ref()), |
| SetBoundDeviceParameters::Connected(ConnState { |
| socket: _, |
| ip_options: _, |
| addr: |
| ConnAddr { |
| ip: ConnIpAddr { local: (local_ip, _local_id), remote: (remote_ip, _remote_id) }, |
| device, |
| }, |
| shutdown: _, |
| clear_device_on_disconnect: _, |
| extra: _, |
| }) => (Some(local_ip), Some(remote_ip), device.as_ref()), |
| }; |
| // Don't allow changing the device if one of the IP addresses in the |
| // socket address vector requires a zone (scope ID). |
| if !socket::can_device_change( |
| local_ip.map(AsRef::<SpecifiedAddr<WireI::Addr>>::as_ref), |
| remote_ip.map(AsRef::<SpecifiedAddr<WireI::Addr>>::as_ref), |
| old_device, |
| new_device, |
| ) { |
| return Err(SocketError::Local(LocalAddressError::Zone( |
| ZonedAddressError::DeviceZoneMismatch, |
| ))); |
| } |
| |
| match params { |
| SetBoundDeviceParameters::Listener { ip, device } => { |
| let new_device = new_device.map(|d| d.downgrade()); |
| let old_addr = ListenerAddr { ip: ip.clone(), device: device.clone() }; |
| let new_addr = ListenerAddr { ip: ip.clone(), device: new_device.clone() }; |
| let entry = sockets |
| .listeners_mut() |
| .entry(socket_id, &old_addr) |
| .unwrap_or_else(|| panic!("invalid listener ID {:?}", socket_id)); |
| let _entry = entry |
| .try_update_addr(new_addr) |
| .map_err(|(ExistsError {}, _entry)| LocalAddressError::AddressInUse)?; |
| *device = new_device |
| } |
| SetBoundDeviceParameters::Connected(ConnState { |
| socket, |
| ip_options: _, |
| addr, |
| shutdown: _, |
| clear_device_on_disconnect, |
| extra: _, |
| }) => { |
| let ConnAddr { ip, device } = addr; |
| let ConnIpAddr { local: (local_ip, _local_id), remote: (remote_ip, _remote_id) } = ip; |
| let new_socket = core_ctx |
| .new_ip_socket( |
| bindings_ctx, |
| new_device.map(EitherDeviceId::Strong), |
| Some(local_ip.clone()), |
| remote_ip.clone(), |
| socket.proto(), |
| ) |
| .map_err(|_: IpSockCreationError| { |
| SocketError::Remote(RemoteAddressError::NoRoute) |
| })?; |
| let new_device = new_socket.device().cloned(); |
| let old_addr = ConnAddr { ip: ip.clone(), device: device.clone() }; |
| let entry = sockets |
| .conns_mut() |
| .entry(socket_id, &old_addr) |
| .unwrap_or_else(|| panic!("invalid conn id {:?}", socket_id)); |
| let new_addr = ConnAddr { ip: ip.clone(), device: new_device.clone() }; |
| let entry = entry |
| .try_update_addr(new_addr) |
| .map_err(|(ExistsError {}, _entry)| LocalAddressError::AddressInUse)?; |
| *socket = new_socket; |
| // If this operation explicitly sets the device for the socket, it |
| // should no longer be cleared on disconnect. |
| if new_device.is_some() { |
| *clear_device_on_disconnect = false; |
| } |
| *addr = entry.get_addr().clone() |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Update the device for a listener socket in both stacks. |
| /// |
| /// Either the update is applied successfully to both stacks, or (in the case of |
| /// an error) both stacks are left in their original state. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the given socket IDs are not present in the given socket maps. |
| fn set_bound_device_listener_both_stacks<'a, I: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| old_device: &mut Option<D>, |
| local_id: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| PairedSocketMapMut { bound: sockets, other_bound: other_sockets }: PairedSocketMapMut< |
| 'a, |
| I, |
| D, |
| S, |
| >, |
| PairedBoundSocketIds { this: socket_id, other: other_socket_id }: PairedBoundSocketIds<I, D, S>, |
| new_device: Option<D>, |
| ) -> Result<(), SocketError> { |
| fn try_update_entry<I: IpExt, D: device::WeakId, S: DatagramSocketSpec>( |
| old_device: Option<D>, |
| new_device: Option<D>, |
| sockets: &mut BoundSocketMap<I, D, S::AddrSpec, S::SocketMapSpec<I, D>>, |
| socket_id: &< |
| S::SocketMapSpec<I, D> as DatagramSocketMapSpec<I, D, S::AddrSpec> |
| >::BoundSocketId, |
| local_id: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| ) -> Result<(), SocketError> { |
| let old_addr = ListenerAddr { |
| ip: ListenerIpAddr { addr: None, identifier: local_id }, |
| device: old_device, |
| }; |
| let entry = sockets |
| .listeners_mut() |
| .entry(socket_id, &old_addr) |
| .unwrap_or_else(|| panic!("invalid listener ID {:?}", socket_id)); |
| let new_addr = ListenerAddr { |
| ip: ListenerIpAddr { addr: None, identifier: local_id }, |
| device: new_device, |
| }; |
| let _entry = entry |
| .try_update_addr(new_addr) |
| .map_err(|(ExistsError {}, _entry)| LocalAddressError::AddressInUse)?; |
| return Ok(()); |
| } |
| |
| // Try to update the entry in this stack. |
| try_update_entry::<_, _, S>( |
| old_device.clone(), |
| new_device.clone(), |
| sockets, |
| &socket_id, |
| local_id, |
| )?; |
| |
| // Try to update the entry in the other stack. |
| let result = try_update_entry::<_, _, S>( |
| old_device.clone(), |
| new_device.clone(), |
| other_sockets, |
| &other_socket_id, |
| local_id, |
| ); |
| |
| if let Err(e) = result { |
| // This stack was successfully updated, but the other stack failed to |
| // update; rollback this stack to the original device. This shouldn't be |
| // fallible, because both socket maps are locked. |
| try_update_entry::<_, _, S>(new_device, old_device.clone(), sockets, &socket_id, local_id) |
| .expect("failed to rollback listener in this stack to it's original device"); |
| return Err(e); |
| } |
| *old_device = new_device; |
| return Ok(()); |
| } |
| |
| pub(crate) fn set_device< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| new_device: Option<&CC::DeviceId>, |
| ) -> Result<(), SocketError> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| match state { |
| SocketState::Unbound(state) => { |
| let UnboundSocketState { ref mut device, sharing: _, ip_options: _ } = state; |
| *device = new_device.map(|d| d.downgrade()); |
| Ok(()) |
| } |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| // Information about the set-device operation for the given |
| // socket. |
| enum Operation< |
| 'a, |
| I: IpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec, |
| CC, |
| DualStackSC, |
| > { |
| ThisStack { |
| params: SetBoundDeviceParameters<'a, I, I, D, S>, |
| core_ctx: CC, |
| }, |
| OtherStack { |
| params: SetBoundDeviceParameters<'a, I::OtherVersion, I, D, S>, |
| core_ctx: DualStackSC, |
| }, |
| ListenerBothStacks { |
| identifier: <S::AddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| device: &'a mut Option<D>, |
| core_ctx: DualStackSC, |
| }, |
| } |
| |
| // Determine which operation needs to be applied. |
| let op = match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(ds) => match socket_type { |
| BoundSocketStateType::Listener { |
| state: |
| ListenerState { ip_options: _, addr: ListenerAddr { ip, device } }, |
| sharing: _, |
| } => match ds.converter().convert(ip) { |
| DualStackListenerIpAddr::ThisStack(ip) => Operation::ThisStack { |
| params: SetBoundDeviceParameters::Listener { ip, device }, |
| core_ctx, |
| }, |
| DualStackListenerIpAddr::OtherStack(ip) => Operation::OtherStack { |
| params: SetBoundDeviceParameters::Listener { ip, device }, |
| core_ctx: ds, |
| }, |
| DualStackListenerIpAddr::BothStacks(identifier) => { |
| Operation::ListenerBothStacks { |
| identifier: *identifier, |
| device, |
| core_ctx: ds, |
| } |
| } |
| }, |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| match ds.converter().convert(state) { |
| DualStackConnState::ThisStack(state) => Operation::ThisStack { |
| params: SetBoundDeviceParameters::Connected(state), |
| core_ctx, |
| }, |
| DualStackConnState::OtherStack(state) => Operation::OtherStack { |
| params: SetBoundDeviceParameters::Connected(state), |
| core_ctx: ds, |
| }, |
| } |
| } |
| }, |
| MaybeDualStack::NotDualStack(nds) => match socket_type { |
| BoundSocketStateType::Listener { |
| state: |
| ListenerState { ip_options: _, addr: ListenerAddr { ip, device } }, |
| sharing: _, |
| } => Operation::ThisStack { |
| params: SetBoundDeviceParameters::Listener { |
| ip: nds.converter().convert(ip), |
| device, |
| }, |
| core_ctx, |
| }, |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| Operation::ThisStack { |
| params: SetBoundDeviceParameters::Connected( |
| nds.converter().convert(state), |
| ), |
| core_ctx, |
| } |
| } |
| }, |
| }; |
| |
| // Apply the operation |
| match op { |
| Operation::ThisStack { params, core_ctx } => { |
| let socket_id = S::make_bound_socket_map_id(id); |
| DatagramBoundStateContext::<I, _, _>::with_bound_sockets_mut( |
| core_ctx, |
| |core_ctx, bound| { |
| set_bound_device_single_stack( |
| bindings_ctx, |
| core_ctx, |
| params, |
| bound, |
| &socket_id, |
| new_device, |
| ) |
| }, |
| ) |
| } |
| Operation::OtherStack { params, core_ctx } => { |
| let socket_id = core_ctx.to_other_bound_socket_id(id); |
| core_ctx.with_other_bound_sockets_mut(|core_ctx, bound| { |
| set_bound_device_single_stack( |
| bindings_ctx, |
| core_ctx, |
| params, |
| bound, |
| &socket_id, |
| new_device, |
| ) |
| }) |
| } |
| Operation::ListenerBothStacks { identifier, device, core_ctx } => { |
| let socket_id = PairedBoundSocketIds::<_, _, S> { |
| this: S::make_bound_socket_map_id(id), |
| other: core_ctx.to_other_bound_socket_id(id), |
| }; |
| core_ctx.with_both_bound_sockets_mut(|_core_ctx, bound, other_bound| { |
| set_bound_device_listener_both_stacks( |
| device, |
| identifier, |
| PairedSocketMapMut { bound, other_bound }, |
| socket_id, |
| new_device.map(|d| d.downgrade()), |
| ) |
| }) |
| } |
| } |
| } |
| } |
| }) |
| } |
| |
| pub(crate) fn get_bound_device< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> Option<CC::WeakDeviceId> { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let (_, device): (&IpOptions<_, _, _>, _) = get_options_device(core_ctx, state); |
| device.clone() |
| }) |
| } |
| |
| /// Error resulting from attempting to change multicast membership settings for |
| /// a socket. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum SetMulticastMembershipError { |
| /// The provided address does not match the provided device. |
| AddressNotAvailable, |
| /// The device does not exist. |
| DeviceDoesNotExist, |
| /// The provided address does not match any address on the host. |
| NoDeviceWithAddress, |
| /// No device or address was specified and there is no device with a route |
| /// to the multicast address. |
| NoDeviceAvailable, |
| /// Tried to join a group again. |
| GroupAlreadyJoined, |
| /// Tried to leave an unjoined group. |
| GroupNotJoined, |
| /// The socket is bound to a device that doesn't match the one specified. |
| WrongDevice, |
| } |
| |
| /// Selects the interface for the given remote address, optionally with a |
| /// constraint on the source address. |
| fn pick_interface_for_addr< |
| A: IpAddress, |
| S: DatagramSocketSpec, |
| BC: DatagramStateBindingsContext<A::Version, S>, |
| CC: DatagramBoundStateContext<A::Version, BC, S>, |
| >( |
| core_ctx: &mut CC, |
| remote_addr: MulticastAddr<A>, |
| source_addr: Option<SpecifiedAddr<A>>, |
| ) -> Result<CC::DeviceId, SetMulticastMembershipError> |
| where |
| A::Version: IpExt, |
| { |
| core_ctx.with_transport_context(|core_ctx| match source_addr { |
| Some(source_addr) => { |
| let mut devices = TransportIpContext::<A::Version, _>::get_devices_with_assigned_addr( |
| core_ctx, |
| source_addr, |
| ); |
| if let Some(d) = devices.next() { |
| if devices.next() == None { |
| return Ok(d); |
| } |
| } |
| Err(SetMulticastMembershipError::NoDeviceAvailable) |
| } |
| None => { |
| let device = MulticastMembershipHandler::select_device_for_multicast_group( |
| core_ctx, |
| remote_addr, |
| ) |
| .map_err(|e| match e { |
| ResolveRouteError::NoSrcAddr | ResolveRouteError::Unreachable => { |
| SetMulticastMembershipError::NoDeviceAvailable |
| } |
| })?; |
| Ok(device) |
| } |
| }) |
| } |
| |
| /// Selector for the device to affect when changing multicast settings. |
| #[derive(Copy, Clone, Debug, Eq, GenericOverIp, PartialEq)] |
| #[generic_over_ip(A, IpAddress)] |
| pub enum MulticastInterfaceSelector<A: IpAddress, D> { |
| /// Use the device with the assigned address. |
| LocalAddress(SpecifiedAddr<A>), |
| /// Use the device with the specified identifier. |
| Interface(D), |
| } |
| |
| /// Selector for the device to use when changing multicast membership settings. |
| /// |
| /// This is like `Option<MulticastInterfaceSelector` except it specifies the |
| /// semantics of the `None` value as "pick any device". |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, GenericOverIp)] |
| #[generic_over_ip(A, IpAddress)] |
| pub enum MulticastMembershipInterfaceSelector<A: IpAddress, D> { |
| /// Use the specified interface. |
| Specified(MulticastInterfaceSelector<A, D>), |
| /// Pick any device with a route to the multicast target address. |
| AnyInterfaceWithRoute, |
| } |
| |
| impl<A: IpAddress, D> From<MulticastInterfaceSelector<A, D>> |
| for MulticastMembershipInterfaceSelector<A, D> |
| { |
| fn from(selector: MulticastInterfaceSelector<A, D>) -> Self { |
| Self::Specified(selector) |
| } |
| } |
| |
| /// Sets the specified socket's membership status for the given group. |
| /// |
| /// If `id` is unbound, the membership state will take effect when it is bound. |
| /// An error is returned if the membership change request is invalid (e.g. |
| /// leaving a group that was not joined, or joining a group multiple times) or |
| /// if the device to use to join is unspecified or conflicts with the existing |
| /// socket state. |
| pub(crate) fn set_multicast_membership< |
| I: IpExt, |
| BC: DatagramStateBindingsContext<I, S>, |
| CC: DatagramStateContext<I, BC, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| multicast_group: MulticastAddr<I::Addr>, |
| interface: MulticastMembershipInterfaceSelector<I::Addr, CC::DeviceId>, |
| want_membership: bool, |
| ) -> Result<(), SetMulticastMembershipError> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let (_, bound_device): (&IpOptions<_, _, _>, _) = get_options_device(core_ctx, state); |
| |
| let interface = match interface { |
| MulticastMembershipInterfaceSelector::Specified(selector) => match selector { |
| MulticastInterfaceSelector::Interface(device) => { |
| if bound_device.as_ref().is_some_and(|d| d != &device) { |
| return Err(SetMulticastMembershipError::WrongDevice); |
| } else { |
| EitherDeviceId::Strong(device) |
| } |
| } |
| MulticastInterfaceSelector::LocalAddress(addr) => EitherDeviceId::Strong( |
| pick_interface_for_addr(core_ctx, multicast_group, Some(addr))?, |
| ), |
| }, |
| MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute => { |
| if let Some(bound_device) = bound_device.as_ref() { |
| EitherDeviceId::Weak(bound_device.clone()) |
| } else { |
| EitherDeviceId::Strong(pick_interface_for_addr( |
| core_ctx, |
| multicast_group, |
| None, |
| )?) |
| } |
| } |
| }; |
| |
| let ip_options = get_options_mut(core_ctx, state); |
| |
| let Some(strong_interface) = interface.as_strong() else { |
| return Err(SetMulticastMembershipError::DeviceDoesNotExist); |
| }; |
| |
| let change = ip_options |
| .multicast_memberships |
| .apply_membership_change(multicast_group, &interface.as_weak(), want_membership) |
| .ok_or(if want_membership { |
| SetMulticastMembershipError::GroupAlreadyJoined |
| } else { |
| SetMulticastMembershipError::GroupNotJoined |
| })?; |
| |
| DatagramBoundStateContext::<I, _, _>::with_transport_context(core_ctx, |core_ctx| { |
| match change { |
| MulticastMembershipChange::Join => { |
| MulticastMembershipHandler::<I, _>::join_multicast_group( |
| core_ctx, |
| bindings_ctx, |
| &strong_interface, |
| multicast_group, |
| ) |
| } |
| MulticastMembershipChange::Leave => { |
| MulticastMembershipHandler::<I, _>::leave_multicast_group( |
| core_ctx, |
| bindings_ctx, |
| &strong_interface, |
| multicast_group, |
| ) |
| } |
| } |
| }); |
| |
| Ok(()) |
| }) |
| } |
| |
| fn get_options_device_from_conn_state< |
| WireI: IpExt, |
| SocketI: IpExt, |
| D: device::WeakId, |
| S: DatagramSocketSpec, |
| >( |
| ConnState { |
| socket: _, |
| ip_options, |
| clear_device_on_disconnect: _, |
| shutdown: _, |
| addr: ConnAddr { device, ip: _ }, |
| extra: _, |
| }: &ConnState<WireI, SocketI, D, S>, |
| ) -> (&IpOptions<SocketI, D, S>, &Option<D>) { |
| (ip_options, device) |
| } |
| |
| pub(crate) fn get_options_device< |
| 'a, |
| I: IpExt, |
| S: DatagramSocketSpec, |
| BC, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| >( |
| core_ctx: &mut CC, |
| state: &'a SocketState<I, CC::WeakDeviceId, S>, |
| ) -> (&'a IpOptions<I, CC::WeakDeviceId, S>, &'a Option<CC::WeakDeviceId>) { |
| match state { |
| SocketState::Unbound(state) => { |
| let UnboundSocketState { ip_options, device, sharing: _ } = state; |
| (ip_options, device) |
| } |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state, sharing: _ } => { |
| let ListenerState { ip_options, addr: ListenerAddr { device, ip: _ } } = state; |
| (ip_options, device) |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(dual_stack) => { |
| match dual_stack.converter().convert(state) { |
| DualStackConnState::ThisStack(state) => { |
| get_options_device_from_conn_state(state) |
| } |
| DualStackConnState::OtherStack(state) => { |
| dual_stack.assert_dual_stack_enabled(state); |
| get_options_device_from_conn_state(state) |
| } |
| } |
| } |
| MaybeDualStack::NotDualStack(not_dual_stack) => { |
| get_options_device_from_conn_state( |
| not_dual_stack.converter().convert(state), |
| ) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fn get_options_mut< |
| 'a, |
| I: IpExt, |
| S: DatagramSocketSpec, |
| BC, |
| CC: DatagramBoundStateContext<I, BC, S>, |
| >( |
| core_ctx: &mut CC, |
| state: &'a mut SocketState<I, CC::WeakDeviceId, S>, |
| ) -> &'a mut IpOptions<I, CC::WeakDeviceId, S> { |
| match state { |
| SocketState::Unbound(state) => { |
| let UnboundSocketState { ip_options, device: _, sharing: _ } = state; |
| ip_options |
| } |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state, sharing: _ } => { |
| let ListenerState { ip_options, addr: _ } = state; |
| ip_options |
| } |
| BoundSocketStateType::Connected { state, sharing: _ } => { |
| match core_ctx.dual_stack_context() { |
| MaybeDualStack::DualStack(dual_stack) => { |
| match dual_stack.converter().convert(state) { |
| DualStackConnState::ThisStack(state) => state.as_mut(), |
| DualStackConnState::OtherStack(state) => { |
| dual_stack.assert_dual_stack_enabled(state); |
| state.as_mut() |
| } |
| } |
| } |
| MaybeDualStack::NotDualStack(not_dual_stack) => { |
| not_dual_stack.converter().convert(state).as_mut() |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| pub(crate) fn update_ip_hop_limit< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| update: impl FnOnce(&mut SocketHopLimits<I>), |
| ) { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let options = get_options_mut(core_ctx, state); |
| |
| update(&mut options.hop_limits) |
| }) |
| } |
| |
| pub(crate) fn get_ip_hop_limits< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> HopLimits { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let (options, device) = get_options_device(core_ctx, state); |
| let device = device.as_ref().and_then(|d| d.upgrade()); |
| DatagramBoundStateContext::<I, _, _>::with_transport_context(core_ctx, |core_ctx| { |
| options.hop_limits.get_limits_with_defaults( |
| &TransportIpContext::<I, _>::get_default_hop_limits(core_ctx, device.as_ref()), |
| ) |
| }) |
| }) |
| } |
| |
| /// Calls the callback with mutable access to [`S::OtherStackIpOptions<I, D>`]. |
| /// |
| /// If the socket is bound, the callback is not called, and instead an |
| /// `ExpectedUnboundError` is returned. |
| pub(crate) fn with_other_stack_ip_options_mut_if_unbound< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| R, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| cb: impl FnOnce(&mut S::OtherStackIpOptions<I, CC::WeakDeviceId>) -> R, |
| ) -> Result<R, ExpectedUnboundError> { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let is_unbound = match state { |
| SocketState::Unbound(_) => true, |
| SocketState::Bound(_) => false, |
| }; |
| if is_unbound { |
| let options = get_options_mut(core_ctx, state); |
| Ok(cb(&mut options.other_stack)) |
| } else { |
| Err(ExpectedUnboundError) |
| } |
| }) |
| } |
| |
| /// Calls the callback with mutable access to [`S::OtherStackIpOptions<I, D>`]. |
| pub(crate) fn with_other_stack_ip_options_mut< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| R, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| cb: impl FnOnce(&mut S::OtherStackIpOptions<I, CC::WeakDeviceId>) -> R, |
| ) -> R { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| let options = get_options_mut(core_ctx, state); |
| cb(&mut options.other_stack) |
| }) |
| } |
| |
| /// Calls the callback with access to [`S::OtherStackIpOptions<I, D>`]. |
| pub(crate) fn with_other_stack_ip_options< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| R, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| cb: impl FnOnce(&S::OtherStackIpOptions<I, CC::WeakDeviceId>) -> R, |
| ) -> R { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let (options, _device) = get_options_device(core_ctx, state); |
| |
| cb(&options.other_stack) |
| }) |
| } |
| |
| /// Calls the callback with access to [`S::OtherStackIpOptions<I, D>`], and the |
| /// default [`HopLimits`] for `I::OtherVersion`. |
| /// |
| /// If dualstack operations are not supported, the callback is not called, and |
| /// instead `NotDualStackCapableError` is returned. |
| pub(crate) fn with_other_stack_ip_options_and_default_hop_limits< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| R, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &BC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| cb: impl FnOnce(&S::OtherStackIpOptions<I, CC::WeakDeviceId>, HopLimits) -> R, |
| ) -> Result<R, NotDualStackCapableError> { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let (options, device) = get_options_device(core_ctx, state); |
| let device = device.as_ref().and_then(|d| d.upgrade()); |
| match DatagramBoundStateContext::<I, _, _>::dual_stack_context(core_ctx) { |
| MaybeDualStack::NotDualStack(_) => Err(NotDualStackCapableError), |
| MaybeDualStack::DualStack(ds) => { |
| let default_hop_limits = |
| DualStackDatagramBoundStateContext::<I, _, _>::with_transport_context( |
| ds, |
| |sync_ctx| { |
| TransportIpContext::<I, _>::get_default_hop_limits( |
| sync_ctx, |
| device.as_ref(), |
| ) |
| }, |
| ); |
| Ok(cb(&options.other_stack, default_hop_limits)) |
| } |
| } |
| }) |
| } |
| |
| pub(crate) fn update_sharing< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec<SharingState = Sharing>, |
| Sharing: Clone, |
| >( |
| core_ctx: &mut CC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| new_sharing: Sharing, |
| ) -> Result<(), ExpectedUnboundError> { |
| core_ctx.with_socket_state_mut(id, |_core_ctx, state| { |
| let state = match state { |
| SocketState::Bound(_) => return Err(ExpectedUnboundError), |
| SocketState::Unbound(state) => state, |
| }; |
| |
| let UnboundSocketState { device: _, sharing, ip_options: _ } = state; |
| *sharing = new_sharing; |
| Ok(()) |
| }) |
| } |
| |
| pub(crate) fn get_sharing< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec<SharingState = Sharing>, |
| Sharing: Clone, |
| >( |
| core_ctx: &mut CC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> Sharing { |
| core_ctx.with_socket_state(id, |_core_ctx, state| { |
| match state { |
| SocketState::Unbound(state) => { |
| let UnboundSocketState { device: _, sharing, ip_options: _ } = state; |
| sharing |
| } |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Listener { state: _, sharing } => sharing, |
| BoundSocketStateType::Connected { state: _, sharing } => sharing, |
| } |
| } |
| } |
| .clone() |
| }) |
| } |
| |
| pub(crate) fn set_ip_transparent< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| value: bool, |
| ) { |
| core_ctx.with_socket_state_mut(id, |core_ctx, state| { |
| get_options_mut(core_ctx, state).transparent = value; |
| }) |
| } |
| |
| pub(crate) fn get_ip_transparent< |
| I: IpExt, |
| CC: DatagramStateContext<I, BC, S>, |
| BC: DatagramStateBindingsContext<I, S>, |
| S: DatagramSocketSpec, |
| >( |
| core_ctx: &mut CC, |
| id: &S::SocketId<I, CC::WeakDeviceId>, |
| ) -> bool { |
| core_ctx.with_socket_state(id, |core_ctx, state| { |
| let (options, _device) = get_options_device(core_ctx, state); |
| options.transparent |
| }) |
| } |
| |
| #[cfg(test)] |
| pub(crate) mod testutil { |
| use super::*; |
| |
| use alloc::vec; |
| use net_types::{ip::IpAddr, Witness}; |
| |
| use crate::{ |
| context::testutil::FakeCtxWithCoreCtx, device::testutil::FakeStrongDeviceId, |
| ip::socket::testutil::FakeDeviceConfig, testutil::TestIpExt, |
| }; |
| |
| // Helper function to ensure the Fake CoreCtx and BindingsCtx are setup with |
| // [`FakeDeviceConfig`] (one per provided device), with remote/local IPs |
| // that support a connection to the given remote_ip. |
| pub(crate) fn setup_fake_ctx_with_dualstack_conn_addrs< |
| TimerId, |
| Event: Debug, |
| BindingsCtxState: Default, |
| CC, |
| D: FakeStrongDeviceId, |
| >( |
| local_ip: IpAddr, |
| remote_ip: SpecifiedAddr<IpAddr>, |
| devices: impl IntoIterator<Item = D>, |
| core_ctx_builder: impl FnOnce(Vec<FakeDeviceConfig<D, SpecifiedAddr<IpAddr>>>) -> CC, |
| ) -> FakeCtxWithCoreCtx<CC, TimerId, Event, BindingsCtxState> { |
| // A conversion helper to unmap ipv4-mapped-ipv6 addresses. |
| fn unmap_ip(addr: IpAddr) -> IpAddr { |
| match addr { |
| IpAddr::V4(v4) => IpAddr::V4(v4), |
| IpAddr::V6(v6) => match v6.to_ipv4_mapped() { |
| Some(v4) => IpAddr::V4(v4), |
| None => IpAddr::V6(v6), |
| }, |
| } |
| } |
| |
| // Convert the local/remote IPs into `IpAddr` in their non-mapped form. |
| let local_ip = unmap_ip(local_ip); |
| let remote_ip = unmap_ip(remote_ip.get()); |
| // If the given local_ip is unspecified, use the default from |
| // `FAKE_CONFIG`. This ensures we always instantiate the |
| // FakeDeviceConfig below with at least one local_ip, which is |
| // required for connect operations to succeed. |
| let local_ip = SpecifiedAddr::new(local_ip).unwrap_or_else(|| match remote_ip { |
| IpAddr::V4(_) => Ipv4::FAKE_CONFIG.local_ip.into(), |
| IpAddr::V6(_) => Ipv6::FAKE_CONFIG.local_ip.into(), |
| }); |
| // If the given remote_ip is unspecified, we won't be able to |
| // connect; abort the test. |
| let remote_ip = SpecifiedAddr::new(remote_ip).expect("remote-ip should be specified"); |
| FakeCtxWithCoreCtx::with_core_ctx(core_ctx_builder( |
| devices |
| .into_iter() |
| .map(|device| FakeDeviceConfig { |
| device, |
| local_ips: vec![local_ip], |
| remote_ips: vec![remote_ip], |
| }) |
| .collect(), |
| )) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use core::convert::Infallible as Never; |
| |
| use alloc::vec; |
| use assert_matches::assert_matches; |
| use const_unwrap::const_unwrap_option; |
| use derivative::Derivative; |
| use ip_test_macro::ip_test; |
| use net_declare::{net_ip_v4, net_ip_v6}; |
| use net_types::{ |
| ip::{Ipv4Addr, Ipv6Addr}, |
| Witness, |
| }; |
| use packet::{Buf, Serializer as _}; |
| use packet_formats::ip::{Ipv4Proto, Ipv6Proto}; |
| use test_case::test_case; |
| |
| use crate::{ |
| context::testutil::{FakeBindingsCtx, FakeCtxWithCoreCtx, Wrapped, WrappedFakeCoreCtx}, |
| data_structures::socketmap::SocketMap, |
| device::testutil::{ |
| FakeDeviceId, FakeReferencyDeviceId, FakeStrongDeviceId, FakeWeakDeviceId, |
| MultipleDevicesId, |
| }, |
| ip::{ |
| device::state::IpDeviceStateIpExt, |
| socket::testutil::{FakeDeviceConfig, FakeDualStackIpSocketCtx, FakeIpSocketCtx}, |
| testutil::DualStackSendIpPacketMeta, |
| IpLayerIpExt, DEFAULT_HOP_LIMITS, |
| }, |
| socket::{ |
| Bound, IncompatibleError, ListenerAddrInfo, RemoveResult, SocketMapAddrStateSpec, |
| }, |
| testutil::TestIpExt, |
| uninstantiable::UninstantiableWrapper, |
| }; |
| |
| use super::*; |
| |
| trait DatagramIpExt<D: FakeStrongDeviceId>: |
| Ip + IpExt + IpDeviceStateIpExt + TestIpExt + DualStackIpExt + DualStackContextsIpExt<D> |
| { |
| } |
| impl< |
| D: FakeStrongDeviceId, |
| I: Ip |
| + IpExt |
| + IpDeviceStateIpExt |
| + TestIpExt |
| + DualStackIpExt |
| + DualStackContextsIpExt<D>, |
| > DatagramIpExt<D> for I |
| { |
| } |
| |
| #[derive(Debug)] |
| enum FakeAddrSpec {} |
| |
| impl SocketMapAddrSpec for FakeAddrSpec { |
| type LocalIdentifier = NonZeroU16; |
| type RemoteIdentifier = u16; |
| } |
| |
| #[derive(Debug)] |
| enum FakeStateSpec {} |
| |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| struct Tag; |
| |
| #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] |
| |
| enum Sharing { |
| #[default] |
| NoConflicts, |
| // Any attempt to insert a connection with the following remote port |
| // will conflict. |
| ConnectionConflicts { |
| remote_port: u16, |
| }, |
| } |
| |
| #[derive(Clone, Debug, Derivative)] |
| #[derivative(Eq(bound = ""), PartialEq(bound = ""))] |
| struct Id<I: IpExt, D: device::WeakId>(StrongRc<I, D, FakeStateSpec>); |
| |
| /// Utilities for accessing locked internal state in tests. |
| impl<I: IpExt, D: device::WeakId> Id<I, D> { |
| fn get(&self) -> impl Deref<Target = SocketState<I, D, FakeStateSpec>> + '_ { |
| let Self(rc) = self; |
| rc.state.read() |
| } |
| |
| fn get_mut(&self) -> impl DerefMut<Target = SocketState<I, D, FakeStateSpec>> + '_ { |
| let Self(rc) = self; |
| rc.state.write() |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId> From<StrongRc<I, D, FakeStateSpec>> for Id<I, D> { |
| fn from(value: StrongRc<I, D, FakeStateSpec>) -> Self { |
| Self(value) |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId> Borrow<StrongRc<I, D, FakeStateSpec>> for Id<I, D> { |
| fn borrow(&self) -> &StrongRc<I, D, FakeStateSpec> { |
| let Self(rc) = self; |
| rc |
| } |
| } |
| |
| #[derive(Debug)] |
| struct AddrState<T>(T); |
| |
| impl<I: IpExt, D: device::WeakId> SocketMapStateSpec for (FakeStateSpec, I, D) { |
| type AddrVecTag = Tag; |
| type ConnAddrState = AddrState<Self::ConnId>; |
| type ConnId = I::DualStackBoundSocketId<D, FakeStateSpec>; |
| type ConnSharingState = Sharing; |
| type ListenerAddrState = AddrState<Self::ListenerId>; |
| type ListenerId = I::DualStackBoundSocketId<D, FakeStateSpec>; |
| type ListenerSharingState = Sharing; |
| fn listener_tag(_: ListenerAddrInfo, _state: &Self::ListenerAddrState) -> Self::AddrVecTag { |
| Tag |
| } |
| fn connected_tag(_has_device: bool, _state: &Self::ConnAddrState) -> Self::AddrVecTag { |
| Tag |
| } |
| } |
| |
| const FAKE_DATAGRAM_IPV4_PROTOCOL: Ipv4Proto = Ipv4Proto::Other(253); |
| const FAKE_DATAGRAM_IPV6_PROTOCOL: Ipv6Proto = Ipv6Proto::Other(254); |
| |
| #[derive(Clone, Default, Debug)] |
| struct OtherStackSocketState<I: IpExt> { |
| hop_limits: SocketHopLimits<I>, |
| } |
| |
| impl<I: IpExt, D> SendOptions<I, D> for OtherStackSocketState<I> { |
| fn hop_limit(&self, destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8> { |
| let Self { hop_limits, .. } = self; |
| if destination.is_multicast() { |
| hop_limits.multicast |
| } else { |
| hop_limits.unicast |
| } |
| } |
| |
| fn multicast_interface(&self) -> Option<&D> { |
| None |
| } |
| } |
| |
| impl DatagramSocketSpec for FakeStateSpec { |
| const NAME: &'static str = "FAKE"; |
| type AddrSpec = FakeAddrSpec; |
| type SocketId<I: IpExt, D: device::WeakId> = Id<I, D>; |
| type OtherStackIpOptions<I: IpExt, D: device::WeakId> = |
| OtherStackSocketState<I::OtherVersion>; |
| type SocketMapSpec<I: IpExt, D: device::WeakId> = (Self, I, D); |
| type SharingState = Sharing; |
| type ListenerIpAddr<I: IpExt> = |
| I::DualStackListenerIpAddr<<FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier>; |
| type ConnIpAddr<I: IpExt> = I::DualStackConnIpAddr<Self>; |
| type ConnStateExtra = (); |
| type ConnState<I: IpExt, D: device::WeakId> = I::DualStackConnState<D, Self>; |
| type ExternalData<I: Ip> = (); |
| |
| fn ip_proto<I: IpProtoExt>() -> I::Proto { |
| I::map_ip((), |()| FAKE_DATAGRAM_IPV4_PROTOCOL, |()| FAKE_DATAGRAM_IPV6_PROTOCOL) |
| } |
| |
| fn make_bound_socket_map_id<I: IpExt, D: device::WeakId>( |
| s: &Self::SocketId<I, D>, |
| ) -> <Self::SocketMapSpec<I, D> as DatagramSocketMapSpec<I, D, Self::AddrSpec>>::BoundSocketId |
| { |
| I::into_dual_stack_bound_socket_id(s.clone()) |
| } |
| |
| type Serializer<I: IpExt, B: BufferMut> = packet::Nested<B, ()>; |
| type SerializeError = Never; |
| fn make_packet<I: IpExt, B: BufferMut>( |
| body: B, |
| _addr: &ConnIpAddr< |
| I::Addr, |
| <FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <FakeAddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| ) -> Result<Self::Serializer<I, B>, Never> { |
| Ok(body.encapsulate(())) |
| } |
| fn try_alloc_listen_identifier<I: Ip, D: device::WeakId>( |
| _bindings_ctx: &mut impl RngContext, |
| is_available: impl Fn( |
| <FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| ) -> Result<(), InUseError>, |
| ) -> Option<<FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier> { |
| (1..=u16::MAX).map(|i| NonZeroU16::new(i).unwrap()).find(|i| is_available(*i).is_ok()) |
| } |
| |
| fn conn_info_from_state<I: IpExt, D: device::WeakId>( |
| state: &Self::ConnState<I, D>, |
| ) -> ConnInfo<I::Addr, D> { |
| let ConnAddr { ip, device } = I::conn_addr_from_state(state); |
| let ConnInfoAddr { local: (local_ip, local_port), remote: (remote_ip, remote_port) } = |
| ip.into(); |
| ConnInfo::new(local_ip, local_port, remote_ip, remote_port, || { |
| device.clone().expect("device must be bound for addresses that require zones") |
| }) |
| } |
| |
| fn try_alloc_local_id<I: IpExt, D: device::WeakId, BC: RngContext>( |
| bound: &BoundSocketMap<I, D, FakeAddrSpec, (FakeStateSpec, I, D)>, |
| _bindings_ctx: &mut BC, |
| _flow: DatagramFlowId<I::Addr, <FakeAddrSpec as SocketMapAddrSpec>::RemoteIdentifier>, |
| ) -> Option<<FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier> { |
| (1..u16::MAX).find_map(|identifier| { |
| let identifier = NonZeroU16::new(identifier).unwrap(); |
| bound |
| .listeners() |
| .could_insert( |
| &ListenerAddr { |
| device: None, |
| ip: ListenerIpAddr { addr: None, identifier }, |
| }, |
| &Default::default(), |
| ) |
| .is_ok() |
| .then_some(identifier) |
| }) |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId> DatagramSocketMapSpec<I, D, FakeAddrSpec> |
| for (FakeStateSpec, I, D) |
| { |
| type BoundSocketId = I::DualStackBoundSocketId<D, FakeStateSpec>; |
| } |
| |
| impl<I: IpExt, D: device::WeakId> |
| SocketMapConflictPolicy< |
| ConnAddr< |
| ConnIpAddr< |
| I::Addr, |
| <FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <FakeAddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| D, |
| >, |
| Sharing, |
| I, |
| D, |
| FakeAddrSpec, |
| > for (FakeStateSpec, I, D) |
| { |
| fn check_insert_conflicts( |
| sharing: &Sharing, |
| addr: &ConnAddr< |
| ConnIpAddr< |
| I::Addr, |
| <FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier, |
| <FakeAddrSpec as SocketMapAddrSpec>::RemoteIdentifier, |
| >, |
| D, |
| >, |
| _socketmap: &SocketMap<AddrVec<I, D, FakeAddrSpec>, Bound<Self>>, |
| ) -> Result<(), InsertError> { |
| let ConnAddr { ip: ConnIpAddr { local: _, remote: (_remote_ip, port) }, device: _ } = |
| addr; |
| match sharing { |
| Sharing::NoConflicts => Ok(()), |
| Sharing::ConnectionConflicts { remote_port } => { |
| if remote_port == port { |
| Err(InsertError::Exists) |
| } else { |
| Ok(()) |
| } |
| } |
| } |
| } |
| } |
| |
| impl<I: IpExt, D: device::WeakId> |
| SocketMapConflictPolicy< |
| ListenerAddr< |
| ListenerIpAddr<I::Addr, <FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| D, |
| >, |
| Sharing, |
| I, |
| D, |
| FakeAddrSpec, |
| > for (FakeStateSpec, I, D) |
| { |
| fn check_insert_conflicts( |
| sharing: &Sharing, |
| _addr: &ListenerAddr< |
| ListenerIpAddr<I::Addr, <FakeAddrSpec as SocketMapAddrSpec>::LocalIdentifier>, |
| D, |
| >, |
| _socketmap: &SocketMap<AddrVec<I, D, FakeAddrSpec>, Bound<Self>>, |
| ) -> Result<(), InsertError> { |
| match sharing { |
| Sharing::NoConflicts => Ok(()), |
| // Since this implementation is strictly for ListenerAddr, |
| // ignore connection conflicts. |
| Sharing::ConnectionConflicts { remote_port: _ } => Ok(()), |
| } |
| } |
| } |
| |
| impl<T: Eq> SocketMapAddrStateSpec for AddrState<T> { |
| type Id = T; |
| type SharingState = Sharing; |
| type Inserter<'a> = Never where Self: 'a; |
| |
| fn new(_sharing: &Self::SharingState, id: Self::Id) -> Self { |
| AddrState(id) |
| } |
| fn contains_id(&self, id: &Self::Id) -> bool { |
| let Self(inner) = self; |
| inner == id |
| } |
| fn try_get_inserter<'a, 'b>( |
| &'b mut self, |
| _new_sharing_state: &'a Self::SharingState, |
| ) -> Result<Self::Inserter<'b>, IncompatibleError> { |
| Err(IncompatibleError) |
| } |
| fn could_insert( |
| &self, |
| _new_sharing_state: &Self::SharingState, |
| ) -> Result<(), IncompatibleError> { |
| Err(IncompatibleError) |
| } |
| fn remove_by_id(&mut self, _id: Self::Id) -> RemoveResult { |
| RemoveResult::IsLast |
| } |
| } |
| |
| #[derive(Derivative, GenericOverIp)] |
| #[derivative(Default(bound = ""))] |
| #[generic_over_ip()] |
| struct FakeBoundSockets<D: FakeStrongDeviceId> { |
| v4: BoundSockets< |
| Ipv4, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, Ipv4, FakeWeakDeviceId<D>), |
| >, |
| v6: BoundSockets< |
| Ipv6, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, Ipv6, FakeWeakDeviceId<D>), |
| >, |
| } |
| |
| impl<D: FakeStrongDeviceId, I: IpExt> |
| AsRef< |
| BoundSockets< |
| I, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, I, FakeWeakDeviceId<D>), |
| >, |
| > for FakeBoundSockets<D> |
| { |
| fn as_ref( |
| &self, |
| ) -> &BoundSockets< |
| I, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, I, FakeWeakDeviceId<D>), |
| > { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrap<'a, I: IpExt, D: FakeStrongDeviceId>( |
| &'a BoundSockets< |
| I, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, I, FakeWeakDeviceId<D>), |
| >, |
| ); |
| let Wrap(state) = I::map_ip(self, |state| Wrap(&state.v4), |state| Wrap(&state.v6)); |
| state |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId, I: IpExt> |
| AsMut< |
| BoundSockets< |
| I, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, I, FakeWeakDeviceId<D>), |
| >, |
| > for FakeBoundSockets<D> |
| { |
| fn as_mut( |
| &mut self, |
| ) -> &mut BoundSockets< |
| I, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, I, FakeWeakDeviceId<D>), |
| > { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrap<'a, I: IpExt, D: FakeStrongDeviceId>( |
| &'a mut BoundSockets< |
| I, |
| FakeWeakDeviceId<D>, |
| FakeAddrSpec, |
| (FakeStateSpec, I, FakeWeakDeviceId<D>), |
| >, |
| ); |
| let Wrap(state) = |
| I::map_ip(self, |state| Wrap(&mut state.v4), |state| Wrap(&mut state.v6)); |
| state |
| } |
| } |
| |
| type FakeSocketsState<I, D> = DatagramSocketSet<I, FakeWeakDeviceId<D>, FakeStateSpec>; |
| |
| type FakeInnerCoreCtx<D> = crate::context::testutil::FakeCoreCtx< |
| FakeDualStackIpSocketCtx<D>, |
| DualStackSendIpPacketMeta<D>, |
| D, |
| >; |
| |
| type FakeCoreCtx<I, D> = |
| Wrapped<FakeSocketsState<I, D>, Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>>; |
| |
| impl<I: IpExt, D: FakeStrongDeviceId> FakeCoreCtx<I, D> { |
| fn new_with_sockets(state: FakeSocketsState<I, D>, bound: FakeBoundSockets<D>) -> Self { |
| Self { |
| outer: state, |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::default(), |
| bound, |
| ), |
| } |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId, I: DatagramIpExt<D> + IpLayerIpExt> |
| DatagramStateContext<I, FakeBindingsCtx<(), (), (), ()>, FakeStateSpec> |
| for FakeCoreCtx<I, D> |
| { |
| type SocketsStateCtx<'a> = |
| Wrapped<FakeBoundSockets<Self::DeviceId>, FakeInnerCoreCtx<Self::DeviceId>>; |
| |
| fn with_all_sockets_mut< |
| O, |
| F: FnOnce(&mut DatagramSocketSet<I, Self::WeakDeviceId, FakeStateSpec>) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&mut self.outer) |
| } |
| |
| fn with_all_sockets< |
| O, |
| F: FnOnce(&DatagramSocketSet<I, Self::WeakDeviceId, FakeStateSpec>) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&self.outer) |
| } |
| |
| fn with_socket_state< |
| O, |
| F: FnOnce( |
| &mut Self::SocketsStateCtx<'_>, |
| &SocketState<I, Self::WeakDeviceId, FakeStateSpec>, |
| ) -> O, |
| >( |
| &mut self, |
| id: &Id<I, Self::WeakDeviceId>, |
| cb: F, |
| ) -> O { |
| cb(&mut self.inner, &id.get()) |
| } |
| |
| fn with_socket_state_mut< |
| O, |
| F: FnOnce( |
| &mut Self::SocketsStateCtx<'_>, |
| &mut SocketState<I, Self::WeakDeviceId, FakeStateSpec>, |
| ) -> O, |
| >( |
| &mut self, |
| id: &Id<I, Self::WeakDeviceId>, |
| cb: F, |
| ) -> O { |
| cb(&mut self.inner, &mut id.get_mut()) |
| } |
| |
| fn for_each_socket< |
| F: FnMut( |
| &mut Self::SocketsStateCtx<'_>, |
| &Id<I, Self::WeakDeviceId>, |
| &SocketState<I, Self::WeakDeviceId, FakeStateSpec>, |
| ), |
| >( |
| &mut self, |
| mut cb: F, |
| ) { |
| self.outer.keys().for_each(|id| { |
| let id = Id::from(id.clone()); |
| cb(&mut self.inner, &id, &id.get()); |
| }) |
| } |
| } |
| |
| /// A test-only IpExt trait to specialize the `DualStackContext` and |
| /// `NonDualStackContext` associated types on the |
| /// `DatagramBoundStateContext`. |
| /// |
| /// This allows us to implement `DatagramBoundStateContext` for all `I` |
| /// while also assigning its associated types different values for `Ipv4` |
| /// and `Ipv6`. |
| trait DualStackContextsIpExt<D: FakeStrongDeviceId>: Ip + DualStackIpExt { |
| type DualStackContext: DualStackDatagramBoundStateContext< |
| Self, |
| FakeBindingsCtx<(), (), (), ()>, |
| FakeStateSpec, |
| DeviceId = D, |
| WeakDeviceId = FakeWeakDeviceId<D>, |
| >; |
| type NonDualStackContext: NonDualStackDatagramBoundStateContext< |
| Self, |
| FakeBindingsCtx<(), (), (), ()>, |
| FakeStateSpec, |
| DeviceId = D, |
| WeakDeviceId = FakeWeakDeviceId<D>, |
| >; |
| fn dual_stack_context( |
| core_ctx: &mut Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>, |
| ) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext>; |
| } |
| |
| impl<D: FakeStrongDeviceId> DualStackContextsIpExt<D> for Ipv4 { |
| type DualStackContext = |
| UninstantiableWrapper<Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>>; |
| type NonDualStackContext = Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>; |
| fn dual_stack_context( |
| core_ctx: &mut Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>, |
| ) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext> { |
| MaybeDualStack::NotDualStack(core_ctx) |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId> DualStackContextsIpExt<D> for Ipv6 { |
| type DualStackContext = Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>; |
| type NonDualStackContext = |
| UninstantiableWrapper<Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>>; |
| fn dual_stack_context( |
| core_ctx: &mut Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>>, |
| ) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext> { |
| MaybeDualStack::DualStack(core_ctx) |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId, I: Ip + IpExt + IpDeviceStateIpExt + DualStackContextsIpExt<D>> |
| DatagramBoundStateContext<I, FakeBindingsCtx<(), (), (), ()>, FakeStateSpec> |
| for Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>> |
| { |
| type IpSocketsCtx<'a> = FakeInnerCoreCtx<D>; |
| type DualStackContext = I::DualStackContext; |
| type NonDualStackContext = I::NonDualStackContext; |
| |
| fn with_bound_sockets< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &BoundSockets< |
| I, |
| Self::WeakDeviceId, |
| FakeAddrSpec, |
| (FakeStateSpec, I, Self::WeakDeviceId), |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let Self { outer, inner } = self; |
| cb(inner, outer.as_ref()) |
| } |
| fn with_bound_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut BoundSockets< |
| I, |
| Self::WeakDeviceId, |
| FakeAddrSpec, |
| (FakeStateSpec, I, Self::WeakDeviceId), |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let Self { outer, inner } = self; |
| cb(inner, outer.as_mut()) |
| } |
| |
| fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let Self { outer: _, inner } = self; |
| cb(inner) |
| } |
| |
| fn dual_stack_context( |
| &mut self, |
| ) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext> { |
| I::dual_stack_context(self) |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId> |
| NonDualStackDatagramBoundStateContext<Ipv4, FakeBindingsCtx<(), (), (), ()>, FakeStateSpec> |
| for Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>> |
| { |
| type Converter = (); |
| fn converter(&self) -> Self::Converter { |
| () |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId> |
| DualStackDatagramBoundStateContext<Ipv6, FakeBindingsCtx<(), (), (), ()>, FakeStateSpec> |
| for Wrapped<FakeBoundSockets<D>, FakeInnerCoreCtx<D>> |
| { |
| type IpSocketsCtx<'a> = FakeInnerCoreCtx<D>; |
| fn dual_stack_enabled( |
| &self, |
| _state: &impl AsRef<IpOptions<Ipv6, Self::WeakDeviceId, FakeStateSpec>>, |
| ) -> bool { |
| // For now, it's simplest to have dual-stack unconditionally enabled |
| // for datagram tests. However, in the future this could be stateful |
| // and follow an implementation similar to UDP's test fixture. |
| true |
| } |
| |
| type OtherSendOptions = OtherStackSocketState<Ipv4>; |
| |
| fn to_other_send_options<'a>( |
| &self, |
| state: &'a IpOptions<Ipv6, Self::WeakDeviceId, FakeStateSpec>, |
| ) -> &'a Self::OtherSendOptions { |
| let IpOptions { other_stack, .. } = state; |
| other_stack |
| } |
| |
| fn assert_dual_stack_enabled( |
| &self, |
| state: &impl AsRef<IpOptions<Ipv6, Self::WeakDeviceId, FakeStateSpec>>, |
| ) { |
| assert!(self.dual_stack_enabled(state)) |
| } |
| type Converter = (); |
| fn converter(&self) -> Self::Converter { |
| () |
| } |
| |
| fn to_other_bound_socket_id( |
| &self, |
| id: &Id<Ipv6, D::Weak>, |
| ) -> EitherIpSocket<D::Weak, FakeStateSpec> { |
| EitherIpSocket::V6(id.clone()) |
| } |
| |
| fn with_both_bound_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut BoundSockets< |
| Ipv6, |
| Self::WeakDeviceId, |
| FakeAddrSpec, |
| (FakeStateSpec, Ipv6, Self::WeakDeviceId), |
| >, |
| &mut BoundSockets< |
| Ipv4, |
| Self::WeakDeviceId, |
| FakeAddrSpec, |
| (FakeStateSpec, Ipv4, Self::WeakDeviceId), |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let Self { ref mut outer, inner } = self; |
| let FakeBoundSockets { v4, v6 } = outer; |
| cb(inner, v6, v4) |
| } |
| |
| fn with_other_bound_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut BoundSockets< |
| Ipv4, |
| Self::WeakDeviceId, |
| FakeAddrSpec, |
| (FakeStateSpec, Ipv4, Self::WeakDeviceId), |
| >, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let Self { outer, inner } = self; |
| cb(inner, outer.as_mut()) |
| } |
| |
| fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let Self { outer: _, inner } = self; |
| cb(inner) |
| } |
| } |
| |
| #[ip_test] |
| fn set_get_hop_limits<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>() { |
| let mut core_ctx = FakeCoreCtx::<I, FakeDeviceId>::new_with_sockets( |
| Default::default(), |
| Default::default(), |
| ); |
| let mut bindings_ctx = FakeBindingsCtx::default(); |
| |
| let unbound = create(&mut core_ctx, ()); |
| const EXPECTED_HOP_LIMITS: HopLimits = HopLimits { |
| unicast: const_unwrap_option(NonZeroU8::new(45)), |
| multicast: const_unwrap_option(NonZeroU8::new(23)), |
| }; |
| |
| update_ip_hop_limit(&mut core_ctx, &mut bindings_ctx, &unbound, |limits| { |
| *limits = SocketHopLimits { |
| unicast: Some(EXPECTED_HOP_LIMITS.unicast), |
| multicast: Some(EXPECTED_HOP_LIMITS.multicast), |
| version: IpVersionMarker::default(), |
| } |
| }); |
| |
| assert_eq!(get_ip_hop_limits(&mut core_ctx, &bindings_ctx, &unbound), EXPECTED_HOP_LIMITS); |
| } |
| |
| #[ip_test] |
| fn set_get_device_hop_limits<I: Ip + DatagramIpExt<FakeReferencyDeviceId> + IpLayerIpExt>() { |
| let device = FakeReferencyDeviceId::default(); |
| let mut core_ctx = FakeCoreCtx::<I, _> { |
| outer: FakeSocketsState::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new([FakeDeviceConfig::<_, SpecifiedAddr<I::Addr>> { |
| device: device.clone(), |
| local_ips: Default::default(), |
| remote_ips: Default::default(), |
| }]), |
| Default::default(), |
| ), |
| }; |
| let mut bindings_ctx = FakeBindingsCtx::default(); |
| |
| let unbound = create(&mut core_ctx, ()); |
| set_device(&mut core_ctx, &mut bindings_ctx, &unbound, Some(&device)).unwrap(); |
| |
| let HopLimits { mut unicast, multicast } = DEFAULT_HOP_LIMITS; |
| unicast = unicast.checked_add(1).unwrap(); |
| { |
| let ip_socket_ctx = core_ctx.inner.inner.get_mut(); |
| let device_state = ip_socket_ctx.get_device_state_mut::<I>(&device); |
| assert_ne!(device_state.default_hop_limit, unicast); |
| device_state.default_hop_limit = unicast; |
| } |
| assert_eq!( |
| get_ip_hop_limits(&mut core_ctx, &bindings_ctx, &unbound), |
| HopLimits { unicast, multicast } |
| ); |
| |
| // If the device is removed, use default hop limits. |
| device.mark_removed(); |
| assert_eq!(get_ip_hop_limits(&mut core_ctx, &bindings_ctx, &unbound), DEFAULT_HOP_LIMITS); |
| } |
| |
| #[ip_test] |
| fn default_hop_limits<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>() { |
| let mut core_ctx = FakeCoreCtx::<I, FakeDeviceId>::new_with_sockets( |
| Default::default(), |
| Default::default(), |
| ); |
| let mut bindings_ctx = FakeBindingsCtx::default(); |
| |
| let unbound = create(&mut core_ctx, ()); |
| assert_eq!(get_ip_hop_limits(&mut core_ctx, &bindings_ctx, &unbound), DEFAULT_HOP_LIMITS); |
| |
| update_ip_hop_limit(&mut core_ctx, &mut bindings_ctx, &unbound, |limits| { |
| *limits = SocketHopLimits { |
| unicast: Some(const_unwrap_option(NonZeroU8::new(1))), |
| multicast: Some(const_unwrap_option(NonZeroU8::new(1))), |
| version: IpVersionMarker::default(), |
| } |
| }); |
| |
| // The limits no longer match the default. |
| assert_ne!(get_ip_hop_limits(&mut core_ctx, &bindings_ctx, &unbound), DEFAULT_HOP_LIMITS); |
| |
| // Clear the hop limits set on the socket. |
| update_ip_hop_limit(&mut core_ctx, &mut bindings_ctx, &unbound, |limits| { |
| *limits = Default::default() |
| }); |
| |
| // The values should be back at the defaults. |
| assert_eq!(get_ip_hop_limits(&mut core_ctx, &bindings_ctx, &unbound), DEFAULT_HOP_LIMITS); |
| } |
| |
| #[ip_test] |
| fn bind_device_unbound<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>() { |
| let mut core_ctx = |
| FakeCoreCtx::<I, _>::new_with_sockets(Default::default(), Default::default()); |
| let mut bindings_ctx = FakeBindingsCtx::default(); |
| |
| let unbound = create(&mut core_ctx, ()); |
| |
| set_device(&mut core_ctx, &mut bindings_ctx, &unbound, Some(&FakeDeviceId)).unwrap(); |
| assert_eq!( |
| get_bound_device(&mut core_ctx, &bindings_ctx, &unbound), |
| Some(FakeWeakDeviceId(FakeDeviceId)) |
| ); |
| |
| set_device(&mut core_ctx, &mut bindings_ctx, &unbound, None).unwrap(); |
| assert_eq!(get_bound_device(&mut core_ctx, &bindings_ctx, &unbound), None); |
| } |
| |
| #[ip_test] |
| fn send_to_binds_unbound<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>() { |
| let mut core_ctx = FakeCoreCtx::<I, FakeDeviceId> { |
| outer: Default::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new([FakeDeviceConfig { |
| device: FakeDeviceId, |
| local_ips: vec![I::FAKE_CONFIG.local_ip], |
| remote_ips: vec![I::FAKE_CONFIG.remote_ip], |
| }]), |
| Default::default(), |
| ), |
| }; |
| let mut bindings_ctx = FakeBindingsCtx::default(); |
| |
| let socket = create(&mut core_ctx, ()); |
| let body = Buf::new(Vec::new(), ..); |
| |
| send_to( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| Some(ZonedAddr::Unzoned(I::FAKE_CONFIG.remote_ip)), |
| 1234, |
| body, |
| ) |
| .expect("succeeds"); |
| assert_matches!( |
| get_info(&mut core_ctx, &mut bindings_ctx, &socket), |
| SocketInfo::Listener(_) |
| ); |
| } |
| |
| #[ip_test] |
| fn send_to_no_route_still_binds<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>() { |
| let mut core_ctx = FakeCoreCtx::<I, _> { |
| outer: Default::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new([FakeDeviceConfig { |
| device: FakeDeviceId, |
| local_ips: vec![I::FAKE_CONFIG.local_ip], |
| remote_ips: vec![], |
| }]), |
| Default::default(), |
| ), |
| }; |
| let mut bindings_ctx = FakeBindingsCtx::default(); |
| |
| let socket = create(&mut core_ctx, ()); |
| let body = Buf::new(Vec::new(), ..); |
| |
| assert_matches!( |
| send_to( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| Some(ZonedAddr::Unzoned(I::FAKE_CONFIG.remote_ip)), |
| 1234, |
| body, |
| ), |
| Err(Either::Right(SendToError::CreateAndSend(_))) |
| ); |
| assert_matches!( |
| get_info(&mut core_ctx, &mut bindings_ctx, &socket), |
| SocketInfo::Listener(_) |
| ); |
| } |
| |
| #[ip_test] |
| #[test_case(true; "remove device b")] |
| #[test_case(false; "dont remove device b")] |
| fn multicast_membership_changes<I: Ip + DatagramIpExt<FakeReferencyDeviceId> + TestIpExt>( |
| remove_device_b: bool, |
| ) { |
| let device_a = FakeReferencyDeviceId::default(); |
| let device_b = FakeReferencyDeviceId::default(); |
| let mut core_ctx = FakeIpSocketCtx::<I, FakeReferencyDeviceId>::new( |
| [device_a.clone(), device_b.clone()].into_iter().map(|device| FakeDeviceConfig { |
| device, |
| local_ips: Default::default(), |
| remote_ips: Default::default(), |
| }), |
| ); |
| let mut bindings_ctx = FakeBindingsCtx::<(), (), (), ()>::default(); |
| |
| let multicast_addr1 = I::get_multicast_addr(1); |
| let mut memberships = MulticastMemberships::default(); |
| assert_eq!( |
| memberships.apply_membership_change( |
| multicast_addr1, |
| &FakeWeakDeviceId(device_a.clone()), |
| true /* want_membership */ |
| ), |
| Some(MulticastMembershipChange::Join), |
| ); |
| core_ctx.join_multicast_group(&mut bindings_ctx, &device_a, multicast_addr1); |
| |
| let multicast_addr2 = I::get_multicast_addr(2); |
| assert_eq!( |
| memberships.apply_membership_change( |
| multicast_addr2, |
| &FakeWeakDeviceId(device_b.clone()), |
| true /* want_membership */ |
| ), |
| Some(MulticastMembershipChange::Join), |
| ); |
| core_ctx.join_multicast_group(&mut bindings_ctx, &device_b, multicast_addr2); |
| |
| for (device, addr, expected) in [ |
| (&device_a, multicast_addr1, true), |
| (&device_a, multicast_addr2, false), |
| (&device_b, multicast_addr1, false), |
| (&device_b, multicast_addr2, true), |
| ] { |
| assert_eq!( |
| core_ctx.get_device_state(device).is_in_multicast_group(&addr), |
| expected, |
| "device={:?}, addr={}", |
| device, |
| addr, |
| ); |
| } |
| |
| if remove_device_b { |
| device_b.mark_removed(); |
| } |
| |
| leave_all_joined_groups(&mut core_ctx, &mut bindings_ctx, memberships); |
| for (device, addr, expected) in [ |
| (&device_a, multicast_addr1, false), |
| (&device_a, multicast_addr2, false), |
| (&device_b, multicast_addr1, false), |
| // Should not attempt to leave the multicast group on the device if |
| // the device looks like it was removed. Note that although we mark |
| // the device as removed, we do not destroy its state so we can |
| // inspect it here. |
| (&device_b, multicast_addr2, remove_device_b), |
| ] { |
| assert_eq!( |
| core_ctx.get_device_state(device).is_in_multicast_group(&addr), |
| expected, |
| "device={:?}, addr={}", |
| device, |
| addr, |
| ); |
| } |
| } |
| |
| #[ip_test] |
| fn set_get_transparent<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>() { |
| let mut core_ctx = FakeCoreCtx::<I, _> { |
| outer: FakeSocketsState::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new([FakeDeviceConfig::<_, SpecifiedAddr<I::Addr>> { |
| device: FakeDeviceId, |
| local_ips: Default::default(), |
| remote_ips: Default::default(), |
| }]), |
| Default::default(), |
| ), |
| }; |
| |
| let unbound = create(&mut core_ctx, ()); |
| |
| assert!(!get_ip_transparent(&mut core_ctx, &unbound)); |
| |
| set_ip_transparent(&mut core_ctx, &unbound, true); |
| |
| assert!(get_ip_transparent(&mut core_ctx, &unbound)); |
| |
| set_ip_transparent(&mut core_ctx, &unbound, false); |
| |
| assert!(!get_ip_transparent(&mut core_ctx, &unbound)); |
| } |
| |
| #[derive(Eq, PartialEq)] |
| enum OriginalSocketState { |
| Unbound, |
| Listener, |
| Connected, |
| } |
| |
| #[ip_test] |
| #[test_case(OriginalSocketState::Unbound; "reinsert_unbound")] |
| #[test_case(OriginalSocketState::Listener; "reinsert_listener")] |
| #[test_case(OriginalSocketState::Connected; "reinsert_connected")] |
| fn connect_reinserts_on_failure_single_stack< |
| I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt, |
| >( |
| original: OriginalSocketState, |
| ) { |
| connect_reinserts_on_failure_inner::<I>( |
| original, |
| I::FAKE_CONFIG.local_ip.get(), |
| I::FAKE_CONFIG.remote_ip, |
| ); |
| } |
| |
| #[test_case(OriginalSocketState::Listener, net_ip_v6!("::FFFF:192.0.2.1"), |
| net_ip_v4!("192.0.2.2"); "reinsert_listener_other_stack")] |
| #[test_case(OriginalSocketState::Listener, net_ip_v6!("::"), |
| net_ip_v4!("192.0.2.2"); "reinsert_listener_both_stacks")] |
| #[test_case(OriginalSocketState::Connected, net_ip_v6!("::FFFF:192.0.2.1"), |
| net_ip_v4!("192.0.2.2"); "reinsert_connected_other_stack")] |
| fn connect_reinserts_on_failure_dual_stack( |
| original: OriginalSocketState, |
| local_ip: Ipv6Addr, |
| remote_ip: Ipv4Addr, |
| ) { |
| let remote_ip = remote_ip.to_ipv6_mapped(); |
| connect_reinserts_on_failure_inner::<Ipv6>(original, local_ip, remote_ip); |
| } |
| |
| fn connect_reinserts_on_failure_inner<I: Ip + DatagramIpExt<FakeDeviceId> + IpLayerIpExt>( |
| original: OriginalSocketState, |
| local_ip: I::Addr, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| ) { |
| let FakeCtxWithCoreCtx { mut core_ctx, mut bindings_ctx } = |
| testutil::setup_fake_ctx_with_dualstack_conn_addrs( |
| local_ip.to_ip_addr(), |
| remote_ip.into(), |
| [FakeDeviceId {}], |
| |device_configs| FakeCoreCtx::<I, _> { |
| outer: FakeSocketsState::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new(device_configs), |
| Default::default(), |
| ), |
| }, |
| ); |
| |
| let socket = create(&mut core_ctx, ()); |
| const LOCAL_PORT: NonZeroU16 = const_unwrap_option(NonZeroU16::new(10)); |
| const ORIGINAL_REMOTE_PORT: u16 = 1234; |
| const NEW_REMOTE_PORT: u16 = 5678; |
| |
| // Setup the original socket state. |
| match original { |
| OriginalSocketState::Unbound => {} |
| OriginalSocketState::Listener => listen( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| SpecifiedAddr::new(local_ip).map(ZonedAddr::Unzoned), |
| Some(LOCAL_PORT), |
| ) |
| .expect("listen should succeed"), |
| OriginalSocketState::Connected => connect( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| Some(ZonedAddr::Unzoned(remote_ip)), |
| ORIGINAL_REMOTE_PORT, |
| Default::default(), |
| ) |
| .expect("connect should succeed"), |
| } |
| |
| // Update the sharing state to generate conflicts during the call to `connect`. |
| core_ctx.with_socket_state_mut(&socket, |_core_ctx, state| { |
| let sharing = match state { |
| SocketState::Unbound(UnboundSocketState { device: _, sharing, ip_options: _ }) => { |
| sharing |
| } |
| SocketState::Bound(BoundSocketState { socket_type, original_bound_addr: _ }) => { |
| match socket_type { |
| BoundSocketStateType::Connected { state: _, sharing } => sharing, |
| BoundSocketStateType::Listener { state: _, sharing } => sharing, |
| } |
| } |
| }; |
| *sharing = Sharing::ConnectionConflicts { remote_port: NEW_REMOTE_PORT }; |
| }); |
| |
| // Try to connect and observe a conflict error. |
| assert_matches!( |
| connect( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| Some(ZonedAddr::Unzoned(remote_ip)), |
| NEW_REMOTE_PORT, |
| Default::default(), |
| ), |
| Err(ConnectError::SockAddrConflict) |
| ); |
| |
| // Verify the original socket state is intact. |
| let info = get_info(&mut core_ctx, &mut bindings_ctx, &socket); |
| match original { |
| OriginalSocketState::Unbound => assert_matches!(info, SocketInfo::Unbound), |
| OriginalSocketState::Listener => { |
| let local_port = assert_matches!( |
| info, |
| SocketInfo::Listener(ListenerInfo { |
| local_ip: _, |
| local_identifier, |
| }) => local_identifier |
| ); |
| assert_eq!(LOCAL_PORT, local_port); |
| } |
| OriginalSocketState::Connected => { |
| let remote_port = assert_matches!( |
| info, |
| SocketInfo::Connected(ConnInfo { |
| local_ip: _, |
| local_identifier: _, |
| remote_ip: _, |
| remote_identifier, |
| }) => remote_identifier |
| ); |
| assert_eq!(ORIGINAL_REMOTE_PORT, remote_port); |
| } |
| } |
| } |
| |
| #[test_case(net_ip_v6!("::a:b:c:d"), ShutdownType::Send; "this_stack_send")] |
| #[test_case(net_ip_v6!("::a:b:c:d"), ShutdownType::Receive; "this_stack_receive")] |
| #[test_case(net_ip_v6!("::a:b:c:d"), ShutdownType::SendAndReceive; "this_stack_send_and_receive")] |
| #[test_case(net_ip_v6!("::FFFF:192.0.2.1"), ShutdownType::Send; "other_stack_send")] |
| #[test_case(net_ip_v6!("::FFFF:192.0.2.1"), ShutdownType::Receive; "other_stack_receive")] |
| #[test_case(net_ip_v6!("::FFFF:192.0.2.1"), ShutdownType::SendAndReceive; "other_stack_send_and_receive")] |
| fn set_get_shutdown_dualstack(remote_ip: Ipv6Addr, shutdown: ShutdownType) { |
| let remote_ip = SpecifiedAddr::new(remote_ip).expect("remote_ip should be specified"); |
| let FakeCtxWithCoreCtx { mut core_ctx, mut bindings_ctx } = |
| testutil::setup_fake_ctx_with_dualstack_conn_addrs( |
| Ipv6::UNSPECIFIED_ADDRESS.into(), |
| remote_ip.into(), |
| [FakeDeviceId {}], |
| |device_configs| FakeCoreCtx::<Ipv6, _> { |
| outer: FakeSocketsState::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new(device_configs), |
| Default::default(), |
| ), |
| }, |
| ); |
| |
| const REMOTE_PORT: u16 = 1234; |
| let socket = create(&mut core_ctx, ()); |
| connect( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| Some(ZonedAddr::Unzoned(remote_ip)), |
| REMOTE_PORT, |
| Default::default(), |
| ) |
| .expect("connect should succeed"); |
| assert_eq!(get_shutdown_connected(&mut core_ctx, &bindings_ctx, &socket), None); |
| |
| shutdown_connected(&mut core_ctx, &bindings_ctx, &socket, shutdown) |
| .expect("shutdown should succeed"); |
| assert_eq!(get_shutdown_connected(&mut core_ctx, &bindings_ctx, &socket), Some(shutdown)); |
| } |
| |
| #[ip_test] |
| #[test_case(OriginalSocketState::Unbound; "unbound")] |
| #[test_case(OriginalSocketState::Listener; "listener")] |
| #[test_case(OriginalSocketState::Connected; "connected")] |
| fn set_get_device_single_stack<I: Ip + DatagramIpExt<MultipleDevicesId> + IpLayerIpExt>( |
| original: OriginalSocketState, |
| ) { |
| set_get_device_inner::<I>( |
| original, |
| I::FAKE_CONFIG.local_ip.get(), |
| I::FAKE_CONFIG.remote_ip, |
| ); |
| } |
| |
| #[test_case(OriginalSocketState::Listener, net_ip_v6!("::FFFF:192.0.2.1"), |
| net_ip_v4!("192.0.2.2"); "listener_other_stack")] |
| #[test_case(OriginalSocketState::Listener, net_ip_v6!("::"), |
| net_ip_v4!("192.0.2.2"); "listener_both_stacks")] |
| #[test_case(OriginalSocketState::Connected, net_ip_v6!("::FFFF:192.0.2.1"), |
| net_ip_v4!("192.0.2.2"); "connected_other_stack")] |
| fn set_get_device_dual_stack( |
| original: OriginalSocketState, |
| local_ip: Ipv6Addr, |
| remote_ip: Ipv4Addr, |
| ) { |
| let remote_ip = remote_ip.to_ipv6_mapped(); |
| set_get_device_inner::<Ipv6>(original, local_ip, remote_ip); |
| } |
| |
| fn set_get_device_inner<I: Ip + DatagramIpExt<MultipleDevicesId> + IpLayerIpExt>( |
| original: OriginalSocketState, |
| local_ip: I::Addr, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| ) { |
| const DEVICE_ID1: MultipleDevicesId = MultipleDevicesId::A; |
| const DEVICE_ID2: MultipleDevicesId = MultipleDevicesId::B; |
| |
| let FakeCtxWithCoreCtx { mut core_ctx, mut bindings_ctx } = |
| testutil::setup_fake_ctx_with_dualstack_conn_addrs( |
| local_ip.to_ip_addr(), |
| remote_ip.into(), |
| [DEVICE_ID1, DEVICE_ID2], |
| |device_configs| FakeCoreCtx::<I, _> { |
| outer: FakeSocketsState::default(), |
| inner: WrappedFakeCoreCtx::with_inner_and_outer_state( |
| FakeDualStackIpSocketCtx::new(device_configs), |
| Default::default(), |
| ), |
| }, |
| ); |
| |
| const LOCAL_PORT: NonZeroU16 = const_unwrap_option(NonZeroU16::new(10)); |
| const REMOTE_PORT: u16 = 1234; |
| |
| let socket1 = create(&mut core_ctx, ()); |
| let socket2 = create(&mut core_ctx, ()); |
| |
| // Initialize each socket to the `original` state, and verify that their |
| // device can be set. |
| for (socket, device_id) in [(&socket1, DEVICE_ID1), (&socket2, DEVICE_ID2)] { |
| match original { |
| OriginalSocketState::Unbound => {} |
| OriginalSocketState::Listener => listen( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| SpecifiedAddr::new(local_ip).map(ZonedAddr::Unzoned), |
| Some(LOCAL_PORT), |
| ) |
| .expect("listen should succeed"), |
| OriginalSocketState::Connected => connect( |
| &mut core_ctx, |
| &mut bindings_ctx, |
| &socket, |
| Some(ZonedAddr::Unzoned(remote_ip)), |
| REMOTE_PORT, |
| Default::default(), |
| ) |
| .expect("connect should succeed"), |
| } |
| |
| assert_eq!(get_bound_device(&mut core_ctx, &bindings_ctx, socket), None); |
| set_device(&mut core_ctx, &mut bindings_ctx, socket, Some(&device_id)) |
| .expect("set device should succeed"); |
| assert_eq!( |
| get_bound_device(&mut core_ctx, &bindings_ctx, socket), |
| Some(FakeWeakDeviceId(device_id)) |
| ); |
| } |
| |
| // For bound sockets, try to bind socket 2 to device 1, and expect it |
| // it to conflict with socket 1 (They now have identical address keys in |
| // the bound socket map) |
| if original != OriginalSocketState::Unbound { |
| assert_eq!( |
| set_device(&mut core_ctx, &mut bindings_ctx, &socket2, Some(&DEVICE_ID1)), |
| Err(SocketError::Local(LocalAddressError::AddressInUse)) |
| ); |
| // Verify both sockets still have their original device. |
| assert_eq!( |
| get_bound_device(&mut core_ctx, &bindings_ctx, &socket1), |
| Some(FakeWeakDeviceId(DEVICE_ID1)) |
| ); |
| assert_eq!( |
| get_bound_device(&mut core_ctx, &bindings_ctx, &socket2), |
| Some(FakeWeakDeviceId(DEVICE_ID2)) |
| ); |
| } |
| |
| // Verify the device can be unset. |
| // NB: Close socket2 first, otherwise socket 1 will conflict with it. |
| close(&mut core_ctx, &mut bindings_ctx, socket2).into_removed(); |
| set_device(&mut core_ctx, &mut bindings_ctx, &socket1, None) |
| .expect("set device should succeed"); |
| assert_eq!(get_bound_device(&mut core_ctx, &bindings_ctx, &socket1), None,); |
| } |
| } |