blob: f61816e6bfb68d1ba1939bfa31f95267ddb21a90 [file] [log] [blame]
// Copyright 2018 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.
//! The User Datagram Protocol (UDP).
use alloc::{collections::hash_map::DefaultHasher, vec::Vec};
use core::{
borrow::Borrow,
convert::Infallible as Never,
fmt::Debug,
hash::{Hash, Hasher},
marker::PhantomData,
num::{NonZeroU16, NonZeroU8, NonZeroUsize},
ops::RangeInclusive,
};
use lock_order::{lock::UnlockedAccess, wrap::prelude::*};
use derivative::Derivative;
use either::Either;
use net_types::{
ip::{
GenericOverIp, Ip, IpAddress, IpInvariant, IpMarked, IpVersion, IpVersionMarker, Ipv4, Ipv6,
},
MulticastAddr, SpecifiedAddr, Witness, ZonedAddr,
};
use packet::{BufferMut, Nested, ParsablePacket, Serializer};
use packet_formats::{
ip::{IpProto, IpProtoExt},
udp::{UdpPacket, UdpPacketBuilder, UdpParseArgs},
};
use thiserror::Error;
use tracing::{debug, trace};
use crate::{
algorithm::{self, PortAllocImpl, ProtocolFlowId},
context::{
ContextPair, CounterContext, InstantContext, NonTestCtxMarker, ReferenceNotifiers,
RngContext, TracingContext,
},
convert::BidirectionalConverter,
counters::Counter,
data_structures::socketmap::{IterShadows as _, SocketMap, Tagged},
device::{self, AnyDevice, DeviceIdContext},
error::{LocalAddressError, SocketError, ZonedAddressError},
inspect::{Inspector, InspectorDeviceExt},
ip::{
socket::{IpSockCreateAndSendError, IpSockCreationError, IpSockSendError},
HopLimits, IpTransportContext, MulticastMembershipHandler, TransportIpContext,
TransportReceiveError,
},
socket::{
address::{
AddrIsMappedError, ConnAddr, ConnInfoAddr, ConnIpAddr, ListenerAddr, ListenerIpAddr,
SocketIpAddr,
},
datagram::{
self, AddrEntry, BoundSocketState as DatagramBoundSocketState,
BoundSocketStateType as DatagramBoundSocketStateType,
BoundSockets as DatagramBoundSockets, ConnectError, DatagramBoundStateContext,
DatagramFlowId, DatagramSocketMapSpec, DatagramSocketSet, DatagramSocketSpec,
DatagramStateContext, DualStackConnState, DualStackDatagramBoundStateContext,
DualStackIpExt, EitherIpSocket, ExpectedConnError, ExpectedUnboundError, FoundSockets,
InUseError, IpExt, IpOptions, MulticastMembershipInterfaceSelector,
NonDualStackDatagramBoundStateContext, SendError as DatagramSendError,
SetMulticastMembershipError, SocketHopLimits, SocketInfo,
SocketState as DatagramSocketState, WrapOtherStackIpOptions,
WrapOtherStackIpOptionsMut,
},
AddrVec, Bound, IncompatibleError, InsertError, ListenerAddrInfo, MaybeDualStack,
NotDualStackCapableError, RemoveResult, SetDualStackEnabledError, ShutdownType,
SocketAddrType, SocketMapAddrSpec, SocketMapAddrStateSpec, SocketMapConflictPolicy,
SocketMapStateSpec,
},
sync::{RemoveResourceResultWithContext, RwLock, StrongRc},
trace_duration, transport, CoreCtx, StackState,
};
/// A builder for UDP layer state.
#[derive(Clone)]
pub(crate) struct UdpStateBuilder {
send_port_unreachable: bool,
}
impl Default for UdpStateBuilder {
fn default() -> UdpStateBuilder {
UdpStateBuilder { send_port_unreachable: false }
}
}
impl UdpStateBuilder {
/// Enable or disable sending ICMP Port Unreachable messages in response to
/// inbound UDP packets for which a corresponding local socket does not
/// exist (default: disabled).
///
/// Responding with an ICMP Port Unreachable error is a vector for reflected
/// Denial-of-Service (DoS) attacks. The attacker can send a UDP packet to a
/// closed port with the source address set to the address of the victim,
/// and ICMP response will be sent there.
///
/// According to [RFC 1122 Section 4.1.3.1], "\[i\]f a datagram arrives
/// addressed to a UDP port for which there is no pending LISTEN call, UDP
/// SHOULD send an ICMP Port Unreachable message." Since an ICMP response is
/// not mandatory, and due to the security risks described, responses are
/// disabled by default.
///
/// [RFC 1122 Section 4.1.3.1]: https://tools.ietf.org/html/rfc1122#section-4.1.3.1
#[cfg(test)]
pub(crate) fn send_port_unreachable(&mut self, send_port_unreachable: bool) -> &mut Self {
self.send_port_unreachable = send_port_unreachable;
self
}
pub(crate) fn build<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
self,
) -> UdpState<I, D, BT> {
UdpState {
sockets: Default::default(),
send_port_unreachable: self.send_port_unreachable,
counters: Default::default(),
}
}
}
/// Convenience alias to make names shorter.
pub(crate) type UdpBoundSocketMap<I, D, BT> =
DatagramBoundSockets<I, D, UdpAddrSpec, (Udp<BT>, I, D)>;
impl<I: IpExt, NewIp: IpExt, D: device::WeakId, BT: UdpBindingsTypes> GenericOverIp<NewIp>
for UdpBoundSocketMap<I, D, BT>
{
type Type = UdpBoundSocketMap<NewIp, D, BT>;
}
#[derive(Derivative, GenericOverIp)]
#[generic_over_ip(I, Ip)]
#[derivative(Default(bound = ""))]
pub struct BoundSockets<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> {
bound_sockets: UdpBoundSocketMap<I, D, BT>,
}
/// A collection of UDP sockets.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub(crate) struct Sockets<I: Ip + IpExt, D: device::WeakId, BT: UdpBindingsTypes> {
pub(crate) bound: RwLock<BoundSockets<I, D, BT>>,
// Destroy all_sockets last so the strong references in the demux are
// dropped before the primary references in the set.
pub(crate) all_sockets: RwLock<UdpSocketSet<I, D, BT>>,
}
/// The state associated with the UDP protocol.
///
/// `D` is the device ID type.
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
pub(crate) struct UdpState<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> {
pub(crate) sockets: Sockets<I, D, BT>,
pub(crate) send_port_unreachable: bool,
pub(crate) counters: UdpCounters<I>,
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> Default for UdpState<I, D, BT> {
fn default() -> UdpState<I, D, BT> {
UdpStateBuilder::default().build()
}
}
/// Counters for the UDP layer.
pub type UdpCounters<I> = IpMarked<I, UdpCountersInner>;
/// Counters for the UDP layer.
#[derive(Default)]
pub struct UdpCountersInner {
/// Count of ICMP error messages received.
pub rx_icmp_error: Counter,
/// Count of UDP datagrams received from the IP layer, including error
/// cases.
pub rx: Counter,
/// Count of incoming UDP datagrams dropped because it contained a mapped IP
/// address in the header.
pub rx_mapped_addr: Counter,
/// Count of incoming UDP datagrams dropped because of an unknown
/// destination port.
pub rx_unknown_dest_port: Counter,
/// Count of incoming UDP datagrams dropped because their UDP header was in
/// a malformed state.
pub rx_malformed: Counter,
/// Count of outgoing UDP datagrams sent from the socket layer, including
/// error cases.
pub tx: Counter,
/// Count of outgoing UDP datagrams which failed to be sent out of the
/// transport layer.
pub tx_error: Counter,
}
impl<BC: crate::BindingsContext, I: Ip> UnlockedAccess<crate::lock_ordering::UdpCounters<I>>
for StackState<BC>
{
type Data = UdpCounters<I>;
type Guard<'l> = &'l UdpCounters<I> where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
self.udp_counters()
}
}
impl<BC: crate::BindingsContext, I: Ip, L> CounterContext<UdpCounters<I>> for CoreCtx<'_, BC, L> {
fn with_counters<O, F: FnOnce(&UdpCounters<I>) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::UdpCounters<I>>())
}
}
/// Uninstantiatable type for implementing [`DatagramSocketSpec`].
pub struct Udp<BT>(PhantomData<BT>, Never);
/// Produces an iterator over eligible receiving socket addresses.
#[cfg(test)]
fn iter_receiving_addrs<I: Ip + IpExt, D: device::WeakId>(
addr: ConnIpAddr<I::Addr, NonZeroU16, UdpRemotePort>,
device: D,
) -> impl Iterator<Item = AddrVec<I, D, UdpAddrSpec>> {
crate::socket::address::AddrVecIter::with_device(addr.into(), device)
}
fn check_posix_sharing<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
new_sharing: Sharing,
dest: AddrVec<I, D, UdpAddrSpec>,
socketmap: &SocketMap<AddrVec<I, D, UdpAddrSpec>, Bound<(Udp<BT>, I, D)>>,
) -> Result<(), InsertError> {
// Having a value present at a shadowed address is disqualifying, unless
// both the new and existing sockets allow port sharing.
if dest.iter_shadows().any(|a| {
socketmap.get(&a).is_some_and(|bound| {
!bound.tag(&a).to_sharing_options().is_shareable_with_new_state(new_sharing)
})
}) {
return Err(InsertError::ShadowAddrExists);
}
// Likewise, the presence of a value that shadows the target address is
// disqualifying unless both allow port sharing.
match &dest {
AddrVec::Conn(ConnAddr { ip: _, device: None }) | AddrVec::Listen(_) => {
if socketmap.descendant_counts(&dest).any(|(tag, _): &(_, NonZeroUsize)| {
!tag.to_sharing_options().is_shareable_with_new_state(new_sharing)
}) {
return Err(InsertError::ShadowerExists);
}
}
AddrVec::Conn(ConnAddr { ip: _, device: Some(_) }) => {
// No need to check shadows here because there are no addresses
// that shadow a ConnAddr with a device.
debug_assert_eq!(socketmap.descendant_counts(&dest).len(), 0)
}
}
// There are a few combinations of addresses that can conflict with
// each other even though there is not a direct shadowing relationship:
// - listener address with device and connected address without.
// - "any IP" listener with device and specific IP listener without.
// - "any IP" listener with device and connected address without.
//
// The complication is that since these pairs of addresses don't have a
// direct shadowing relationship, it's not possible to query for one
// from the other in the socketmap without a linear scan. Instead. we
// rely on the fact that the tag values in the socket map have different
// values for entries with and without device IDs specified.
fn conflict_exists<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
new_sharing: Sharing,
socketmap: &SocketMap<AddrVec<I, D, UdpAddrSpec>, Bound<(Udp<BT>, I, D)>>,
addr: impl Into<AddrVec<I, D, UdpAddrSpec>>,
mut is_conflicting: impl FnMut(&AddrVecTag) -> bool,
) -> bool {
socketmap.descendant_counts(&addr.into()).any(|(tag, _): &(_, NonZeroUsize)| {
is_conflicting(tag)
&& !tag.to_sharing_options().is_shareable_with_new_state(new_sharing)
})
}
let found_indirect_conflict = match dest {
AddrVec::Listen(ListenerAddr {
ip: ListenerIpAddr { addr: None, identifier },
device: Some(_device),
}) => {
// An address with a device will shadow an any-IP listener
// `dest` with a device so we only need to check for addresses
// without a device. Likewise, an any-IP listener will directly
// shadow `dest`, so an indirect conflict can only come from a
// specific listener or connected socket (without a device).
conflict_exists(
new_sharing,
socketmap,
ListenerAddr { ip: ListenerIpAddr { addr: None, identifier }, device: None },
|AddrVecTag { has_device, addr_type, sharing: _ }| {
!*has_device
&& match addr_type {
SocketAddrType::SpecificListener | SocketAddrType::Connected => true,
SocketAddrType::AnyListener => false,
}
},
)
}
AddrVec::Listen(ListenerAddr {
ip: ListenerIpAddr { addr: Some(ip), identifier },
device: Some(_device),
}) => {
// A specific-IP listener `dest` with a device will be shadowed
// by a connected socket with a device and will shadow
// specific-IP addresses without a device and any-IP listeners
// with and without devices. That means an indirect conflict can
// only come from a connected socket without a device.
conflict_exists(
new_sharing,
socketmap,
ListenerAddr { ip: ListenerIpAddr { addr: Some(ip), identifier }, device: None },
|AddrVecTag { has_device, addr_type, sharing: _ }| {
!*has_device
&& match addr_type {
SocketAddrType::Connected => true,
SocketAddrType::AnyListener | SocketAddrType::SpecificListener => false,
}
},
)
}
AddrVec::Listen(ListenerAddr {
ip: ListenerIpAddr { addr: Some(_), identifier },
device: None,
}) => {
// A specific-IP listener `dest` without a device will be
// shadowed by a specific-IP listener with a device and by any
// connected socket (with or without a device). It will also
// shadow an any-IP listener without a device, which means an
// indirect conflict can only come from an any-IP listener with
// a device.
conflict_exists(
new_sharing,
socketmap,
ListenerAddr { ip: ListenerIpAddr { addr: None, identifier }, device: None },
|AddrVecTag { has_device, addr_type, sharing: _ }| {
*has_device
&& match addr_type {
SocketAddrType::AnyListener => true,
SocketAddrType::SpecificListener | SocketAddrType::Connected => false,
}
},
)
}
AddrVec::Conn(ConnAddr {
ip: ConnIpAddr { local: (local_ip, local_identifier), remote: _ },
device: None,
}) => {
// A connected socket `dest` without a device shadows listeners
// without devices, and is shadowed by a connected socket with
// a device. It can indirectly conflict with listening sockets
// with devices.
// Check for specific-IP listeners with devices, which would
// indirectly conflict.
conflict_exists(
new_sharing,
socketmap,
ListenerAddr {
ip: ListenerIpAddr {
addr: Some(local_ip),
identifier: local_identifier.clone(),
},
device: None,
},
|AddrVecTag { has_device, addr_type, sharing: _ }| {
*has_device
&& match addr_type {
SocketAddrType::SpecificListener => true,
SocketAddrType::AnyListener | SocketAddrType::Connected => false,
}
},
) ||
// Check for any-IP listeners with devices since they conflict.
// Note that this check cannot be combined with the one above
// since they examine tag counts for different addresses. While
// the counts of tags matched above *will* also be propagated to
// the any-IP listener entry, they would be indistinguishable
// from non-conflicting counts. For a connected address with
// `Some(local_ip)`, the descendant counts at the listener
// address with `addr = None` would include any
// `SpecificListener` tags for both addresses with
// `Some(local_ip)` and `Some(other_local_ip)`. The former
// indirectly conflicts with `dest` but the latter does not,
// hence this second distinct check.
conflict_exists(
new_sharing,
socketmap,
ListenerAddr {
ip: ListenerIpAddr { addr: None, identifier: local_identifier },
device: None,
},
|AddrVecTag { has_device, addr_type, sharing: _ }| {
*has_device
&& match addr_type {
SocketAddrType::AnyListener => true,
SocketAddrType::SpecificListener | SocketAddrType::Connected => false,
}
},
)
}
AddrVec::Listen(ListenerAddr {
ip: ListenerIpAddr { addr: None, identifier: _ },
device: _,
}) => false,
AddrVec::Conn(ConnAddr { ip: _, device: Some(_device) }) => false,
};
if found_indirect_conflict {
Err(InsertError::IndirectConflict)
} else {
Ok(())
}
}
/// The remote port for a UDP socket.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum UdpRemotePort {
/// The remote port is set to the following value.
Set(NonZeroU16),
/// The remote port is unset (i.e. "0") value. An unset remote port is
/// treated specially in a few places:
///
/// 1) Attempting to send to an unset remote port results in a
/// [`UdpSerializeError::RemotePortUnset`] error. Note that this behavior
/// diverges from Linux, which does allow sending to a remote_port of 0
/// (supported by `send` but not `send_to`). The rationale for this
/// divergence originates from RFC 8085 Section 5.1:
///
/// A UDP sender SHOULD NOT use a source port value of zero. A source
/// port number that cannot be easily determined from the address or
/// payload type provides protection at the receiver from data injection
/// attacks by off-path devices. A UDP receiver SHOULD NOT bind to port
/// zero.
///
/// Applications SHOULD implement receiver port and address checks at the
/// application layer or explicitly request that the operating system
/// filter the received packets to prevent receiving packets with an
/// arbitrary port. This measure is designed to provide additional
/// protection from data injection attacks from an off-path source (where
/// the port values may not be known).
///
/// Combined, these two stanzas recommend hosts discard incoming traffic
/// destined to remote port 0 for security reasons. Thus we choose to not
/// allow hosts to send such packets under the assumption that it will be
/// dropped by the receiving end.
///
/// 2) A socket connected to a remote host on port 0 will not receive any
/// packets from the remote host. This is because the
/// [`BoundSocketMap::lookup`] implementation only delivers packets that
/// specify a remote port to connected sockets with an exact match. Further,
/// packets that don't specify a remote port are only delivered to listener
/// sockets. This diverges from Linux (which treats a remote_port of 0) as
/// wild card. If and when a concrete need for such behavior is identified,
/// the [`BoundSocketMap`] lookup behavior can be adjusted accordingly.
Unset,
}
impl From<NonZeroU16> for UdpRemotePort {
fn from(p: NonZeroU16) -> Self {
Self::Set(p)
}
}
impl From<u16> for UdpRemotePort {
fn from(p: u16) -> Self {
NonZeroU16::new(p).map(UdpRemotePort::from).unwrap_or(UdpRemotePort::Unset)
}
}
impl From<UdpRemotePort> for u16 {
fn from(p: UdpRemotePort) -> Self {
match p {
UdpRemotePort::Unset => 0,
UdpRemotePort::Set(p) => p.into(),
}
}
}
/// Uninstantiatable type for implementing [`SocketMapAddrSpec`].
pub enum UdpAddrSpec {}
impl SocketMapAddrSpec for UdpAddrSpec {
type RemoteIdentifier = UdpRemotePort;
type LocalIdentifier = NonZeroU16;
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> SocketMapStateSpec for (Udp<BT>, I, D) {
type ListenerId = I::DualStackBoundSocketId<D, Udp<BT>>;
type ConnId = I::DualStackBoundSocketId<D, Udp<BT>>;
type AddrVecTag = AddrVecTag;
type ListenerSharingState = Sharing;
type ConnSharingState = Sharing;
type ListenerAddrState = AddrState<Self::ListenerId>;
type ConnAddrState = AddrState<Self::ConnId>;
fn listener_tag(
ListenerAddrInfo { has_device, specified_addr }: ListenerAddrInfo,
state: &Self::ListenerAddrState,
) -> Self::AddrVecTag {
AddrVecTag {
has_device,
addr_type: specified_addr
.then_some(SocketAddrType::SpecificListener)
.unwrap_or(SocketAddrType::AnyListener),
sharing: state.to_sharing_options(),
}
}
fn connected_tag(has_device: bool, state: &Self::ConnAddrState) -> Self::AddrVecTag {
AddrVecTag {
has_device,
addr_type: SocketAddrType::Connected,
sharing: state.to_sharing_options(),
}
}
}
impl<AA, I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>
SocketMapConflictPolicy<AA, Sharing, I, D, UdpAddrSpec> for (Udp<BT>, I, D)
where
AA: Into<AddrVec<I, D, UdpAddrSpec>> + Clone,
{
fn check_insert_conflicts(
new_sharing_state: &Sharing,
addr: &AA,
socketmap: &SocketMap<AddrVec<I, D, UdpAddrSpec>, Bound<Self>>,
) -> Result<(), InsertError> {
check_posix_sharing(*new_sharing_state, addr.clone().into(), socketmap)
}
}
/// State held for IPv6 sockets related to dual-stack operation.
#[derive(Clone, Debug, Derivative)]
#[derivative(Default)]
pub struct DualStackSocketState {
/// Whether dualstack operations are enabled on this socket.
/// Match Linux's behavior by enabling dualstack operations by default.
#[derivative(Default(value = "true"))]
dual_stack_enabled: bool,
/// The IPv4 hop limits (e.g. TTL) to be used when sending packets in the
/// IPv4 stack.
hop_limits: SocketHopLimits<Ipv4>,
}
/// Serialization errors for Udp Packets.
pub enum UdpSerializeError {
/// Disallow sending packets with a remote port of 0. See
/// [`UdpRemotePort::Unset`] for the rationale.
RemotePortUnset,
}
impl<BT: UdpBindingsTypes> DatagramSocketSpec for Udp<BT> {
const NAME: &'static str = "UDP";
type AddrSpec = UdpAddrSpec;
type SocketId<I: IpExt, D: device::WeakId> = UdpSocketId<I, D, BT>;
type OtherStackIpOptions<I: IpExt> = I::OtherStackIpOptions<DualStackSocketState>;
type ListenerIpAddr<I: IpExt> = I::DualStackListenerIpAddr<NonZeroU16>;
type ConnIpAddr<I: IpExt> = I::DualStackConnIpAddr<Self>;
type ConnStateExtra = ();
type ConnState<I: IpExt, D: Debug + Eq + Hash + Send + Sync> = I::DualStackConnState<D, Self>;
type SocketMapSpec<I: IpExt, D: device::WeakId> = (Self, I, D);
type SharingState = Sharing;
type Serializer<I: IpExt, B: BufferMut> = Nested<B, UdpPacketBuilder<I::Addr>>;
type SerializeError = UdpSerializeError;
type ExternalData<I: Ip> = BT::ExternalData<I>;
fn ip_proto<I: IpProtoExt>() -> I::Proto {
IpProto::Udp.into()
}
fn make_bound_socket_map_id<I: IpExt, D: device::WeakId>(
s: &Self::SocketId<I, D>,
) -> I::DualStackBoundSocketId<D, Udp<BT>> {
I::into_dual_stack_bound_socket_id(s.clone())
}
fn make_packet<I: IpExt, B: BufferMut>(
body: B,
addr: &ConnIpAddr<I::Addr, NonZeroU16, UdpRemotePort>,
) -> Result<Self::Serializer<I, B>, UdpSerializeError> {
let ConnIpAddr { local: (local_ip, local_port), remote: (remote_ip, remote_port) } = addr;
let remote_port = match remote_port {
UdpRemotePort::Unset => return Err(UdpSerializeError::RemotePortUnset),
UdpRemotePort::Set(remote_port) => *remote_port,
};
Ok(body.encapsulate(UdpPacketBuilder::new(
local_ip.addr(),
remote_ip.addr(),
Some(*local_port),
remote_port,
)))
}
fn try_alloc_listen_identifier<I: IpExt, D: device::WeakId>(
rng: &mut impl crate::RngContext,
is_available: impl Fn(NonZeroU16) -> Result<(), InUseError>,
) -> Option<NonZeroU16> {
try_alloc_listen_port::<I, D, BT>(rng, is_available)
}
fn conn_info_from_state<I: IpExt, D: Clone + Debug + Eq + Hash + Send + Sync>(
state: &Self::ConnState<I, D>,
) -> datagram::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();
datagram::ConnInfo::new(local_ip, local_port, remote_ip, remote_port.into(), || {
// The invariant that a zone is present if needed is upheld by connect.
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: &UdpBoundSocketMap<I, D, BT>,
bindings_ctx: &mut BC,
flow: datagram::DatagramFlowId<I::Addr, UdpRemotePort>,
) -> Option<NonZeroU16> {
let DatagramFlowId { local_ip, remote_ip, remote_id } = flow;
let id = ProtocolFlowId::new(local_ip, remote_ip, remote_id);
let mut rng = bindings_ctx.rng();
algorithm::simple_randomized_port_alloc(&mut rng, &id, bound, &())
.map(|p| NonZeroU16::new(p).expect("ephemeral ports should be non-zero"))
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> DatagramSocketMapSpec<I, D, UdpAddrSpec>
for (Udp<BT>, I, D)
{
type BoundSocketId = I::DualStackBoundSocketId<D, Udp<BT>>;
}
enum LookupResult<'a, I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> {
Conn(
&'a I::DualStackBoundSocketId<D, Udp<BT>>,
ConnAddr<ConnIpAddr<I::Addr, NonZeroU16, UdpRemotePort>, D>,
),
Listener(
&'a I::DualStackBoundSocketId<D, Udp<BT>>,
ListenerAddr<ListenerIpAddr<I::Addr, NonZeroU16>, D>,
),
}
#[derive(Hash, Copy, Clone)]
struct SocketSelectorParams<I: Ip, A: AsRef<I::Addr>> {
src_ip: I::Addr,
dst_ip: A,
src_port: u16,
dst_port: u16,
_ip: IpVersionMarker<I>,
}
#[derive(Debug, Eq, PartialEq)]
pub enum AddrState<T> {
Exclusive(T),
ReusePort(Vec<T>),
}
impl<'a, A: IpAddress, LI> From<&'a ListenerIpAddr<A, LI>> for SocketAddrType {
fn from(ListenerIpAddr { addr, identifier: _ }: &'a ListenerIpAddr<A, LI>) -> Self {
match addr {
Some(_) => SocketAddrType::SpecificListener,
None => SocketAddrType::AnyListener,
}
}
}
impl<'a, A: IpAddress, LI, RI> From<&'a ConnIpAddr<A, LI, RI>> for SocketAddrType {
fn from(_: &'a ConnIpAddr<A, LI, RI>) -> Self {
SocketAddrType::Connected
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Sharing {
Exclusive,
ReusePort,
}
impl Default for Sharing {
fn default() -> Self {
Self::Exclusive
}
}
impl Sharing {
pub(crate) fn is_shareable_with_new_state(&self, new_state: Sharing) -> bool {
match (self, new_state) {
(Sharing::Exclusive, Sharing::Exclusive) => false,
(Sharing::Exclusive, Sharing::ReusePort) => false,
(Sharing::ReusePort, Sharing::Exclusive) => false,
(Sharing::ReusePort, Sharing::ReusePort) => true,
}
}
pub(crate) fn is_reuse_port(&self) -> bool {
match self {
Sharing::Exclusive => false,
Sharing::ReusePort => true,
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct AddrVecTag {
pub(crate) has_device: bool,
pub(crate) addr_type: SocketAddrType,
pub(crate) sharing: Sharing,
}
pub(crate) trait ToSharingOptions {
fn to_sharing_options(&self) -> Sharing;
}
impl ToSharingOptions for AddrVecTag {
fn to_sharing_options(&self) -> Sharing {
let AddrVecTag { has_device: _, addr_type: _, sharing } = self;
*sharing
}
}
impl<T> ToSharingOptions for AddrState<T> {
fn to_sharing_options(&self) -> Sharing {
match self {
AddrState::Exclusive(_) => Sharing::Exclusive,
AddrState::ReusePort(_) => Sharing::ReusePort,
}
}
}
impl<T> ToSharingOptions for (T, Sharing) {
fn to_sharing_options(&self) -> Sharing {
let (_state, sharing) = self;
*sharing
}
}
impl<I: Debug + Eq> SocketMapAddrStateSpec for AddrState<I> {
type Id = I;
type SharingState = Sharing;
type Inserter<'a> = &'a mut Vec<I> where I: 'a;
fn new(new_sharing_state: &Sharing, id: I) -> Self {
match new_sharing_state {
Sharing::Exclusive => Self::Exclusive(id),
Sharing::ReusePort => Self::ReusePort(Vec::from([id])),
}
}
fn contains_id(&self, id: &Self::Id) -> bool {
match self {
Self::Exclusive(x) => id == x,
Self::ReusePort(ids) => ids.contains(id),
}
}
fn try_get_inserter<'a, 'b>(
&'b mut self,
new_sharing_state: &'a Sharing,
) -> Result<&'b mut Vec<I>, IncompatibleError> {
match (self, new_sharing_state) {
(AddrState::Exclusive(_), _) | (AddrState::ReusePort(_), Sharing::Exclusive) => {
Err(IncompatibleError)
}
(AddrState::ReusePort(ids), Sharing::ReusePort) => Ok(ids),
}
}
fn could_insert(
&self,
new_sharing_state: &Self::SharingState,
) -> Result<(), IncompatibleError> {
match (self, new_sharing_state) {
(AddrState::Exclusive(_), _) | (_, Sharing::Exclusive) => Err(IncompatibleError),
(AddrState::ReusePort(_), Sharing::ReusePort) => Ok(()),
}
}
fn remove_by_id(&mut self, id: I) -> RemoveResult {
match self {
AddrState::Exclusive(_) => RemoveResult::IsLast,
AddrState::ReusePort(ids) => {
let index = ids.iter().position(|i| i == &id).expect("couldn't find ID to remove");
assert_eq!(ids.swap_remove(index), id);
if ids.is_empty() {
RemoveResult::IsLast
} else {
RemoveResult::Success
}
}
}
}
}
impl<T> AddrState<T> {
fn select_receiver<I: Ip, A: AsRef<I::Addr> + Hash>(
&self,
selector: SocketSelectorParams<I, A>,
) -> &T {
match self {
AddrState::Exclusive(id) => id,
AddrState::ReusePort(ids) => {
let mut hasher = DefaultHasher::new();
selector.hash(&mut hasher);
let index: usize = hasher.finish() as usize % ids.len();
&ids[index]
}
}
}
fn collect_all_ids(&self) -> impl Iterator<Item = &'_ T> {
match self {
AddrState::Exclusive(id) => Either::Left(core::iter::once(id)),
AddrState::ReusePort(ids) => Either::Right(ids.iter()),
}
}
}
impl<'a, I: Ip + IpExt, D: device::WeakId + 'a, BT: UdpBindingsTypes>
AddrEntry<'a, I, D, UdpAddrSpec, (Udp<BT>, I, D)>
{
/// Returns an iterator that yields a `LookupResult` for each contained ID.
fn collect_all_ids(self) -> impl Iterator<Item = LookupResult<'a, I, D, BT>> + 'a {
match self {
Self::Listen(state, l) => Either::Left(
state.collect_all_ids().map(move |id| LookupResult::Listener(id, l.clone())),
),
Self::Conn(state, c) => Either::Right(
state.collect_all_ids().map(move |id| LookupResult::Conn(id, c.clone())),
),
}
}
/// Returns a `LookupResult` for the contained ID that matches the selector.
fn select_receiver<A: AsRef<I::Addr> + Hash>(
self,
selector: SocketSelectorParams<I, A>,
) -> LookupResult<'a, I, D, BT> {
match self {
Self::Listen(state, l) => LookupResult::Listener(state.select_receiver(selector), l),
Self::Conn(state, c) => LookupResult::Conn(state.select_receiver(selector), c),
}
}
}
/// 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. The returned iterator may
/// yield 0, 1, or multiple sockets.
fn lookup<'s, I: Ip + IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
bound: &'s DatagramBoundSockets<I, D, UdpAddrSpec, (Udp<BT>, I, D)>,
(src_ip, src_port): (Option<SocketIpAddr<I::Addr>>, Option<NonZeroU16>),
(dst_ip, dst_port): (SocketIpAddr<I::Addr>, NonZeroU16),
device: D,
) -> impl Iterator<Item = LookupResult<'s, I, D, BT>> + 's {
let matching_entries = bound.iter_receivers(
(src_ip, src_port.map(UdpRemotePort::from)),
(dst_ip, dst_port),
device,
);
match matching_entries {
None => Either::Left(None),
Some(FoundSockets::Single(entry)) => {
let selector = SocketSelectorParams::<_, SpecifiedAddr<I::Addr>> {
src_ip: src_ip.map_or(I::UNSPECIFIED_ADDRESS, SocketIpAddr::addr),
dst_ip: dst_ip.into(),
src_port: src_port.map_or(0, NonZeroU16::get),
dst_port: dst_port.get(),
_ip: IpVersionMarker::default(),
};
Either::Left(Some(entry.select_receiver(selector)))
}
Some(FoundSockets::Multicast(entries)) => {
Either::Right(entries.into_iter().flat_map(AddrEntry::collect_all_ids))
}
}
.into_iter()
}
/// Helper function to allocate a listen port.
///
/// Finds a random ephemeral port that is not in the provided `used_ports` set.
fn try_alloc_listen_port<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
bindings_ctx: &mut impl RngContext,
is_available: impl Fn(NonZeroU16) -> Result<(), InUseError>,
) -> Option<NonZeroU16> {
let mut port = UdpBoundSocketMap::<I, D, BT>::rand_ephemeral(&mut bindings_ctx.rng());
for _ in UdpBoundSocketMap::<I, D, BT>::EPHEMERAL_RANGE {
// We can unwrap here because we know that the EPHEMERAL_RANGE doesn't
// include 0.
let tryport = NonZeroU16::new(port.get()).unwrap();
match is_available(tryport) {
Ok(()) => return Some(tryport),
Err(InUseError {}) => port.next(),
}
}
None
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> PortAllocImpl
for UdpBoundSocketMap<I, D, BT>
{
const EPHEMERAL_RANGE: RangeInclusive<u16> = 49152..=65535;
type Id = ProtocolFlowId<SocketIpAddr<I::Addr>, UdpRemotePort>;
type PortAvailableArg = ();
fn is_port_available(&self, id: &Self::Id, port: u16, (): &()) -> bool {
// We can safely unwrap here, because the ports received in
// `is_port_available` are guaranteed to be in `EPHEMERAL_RANGE`.
let port = NonZeroU16::new(port).unwrap();
let conn = ConnAddr::from_protocol_flow_and_local_port(id, port);
// A port is free if there are no sockets currently using it, and if
// there are no sockets that are shadowing it.
AddrVec::from(conn).iter_shadows().all(|a| match &a {
AddrVec::Listen(l) => self.listeners().get_by_addr(&l).is_none(),
AddrVec::Conn(c) => self.conns().get_by_addr(&c).is_none(),
} && self.get_shadower_counts(&a) == 0)
}
}
/// A UDP socket.
#[derive(GenericOverIp, Derivative)]
#[derivative(Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""))]
#[generic_over_ip(I, Ip)]
pub struct UdpSocketId<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
datagram::StrongRc<I, D, Udp<BT>>,
);
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> Clone for UdpSocketId<I, D, BT> {
#[cfg_attr(feature = "instrumented", track_caller)]
fn clone(&self) -> Self {
let Self(rc) = self;
Self(StrongRc::clone(rc))
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> From<datagram::StrongRc<I, D, Udp<BT>>>
for UdpSocketId<I, D, BT>
{
fn from(value: datagram::StrongRc<I, D, Udp<BT>>) -> Self {
Self(value)
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> Borrow<datagram::StrongRc<I, D, Udp<BT>>>
for UdpSocketId<I, D, BT>
{
fn borrow(&self) -> &datagram::StrongRc<I, D, Udp<BT>> {
let Self(rc) = self;
rc
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> PartialEq<WeakUdpSocketId<I, D, BT>>
for UdpSocketId<I, D, BT>
{
fn eq(&self, other: &WeakUdpSocketId<I, D, BT>) -> bool {
let Self(rc) = self;
let WeakUdpSocketId(weak) = other;
StrongRc::weak_ptr_eq(rc, weak)
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> Debug for UdpSocketId<I, D, BT> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Self(rc) = self;
f.debug_tuple("UdpSocketId").field(&StrongRc::ptr_debug(rc)).finish()
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> UdpSocketId<I, D, BT> {
/// Returns the inner state for this socket, to be used in conjunction with
/// lock ordering mechanisms.
pub(crate) fn state_for_locking(&self) -> &RwLock<UdpSocketState<I, D, BT>> {
let Self(rc) = self;
&rc.state
}
/// Returns a means to debug outstanding references to this socket.
pub fn debug_references(&self) -> impl Debug {
let Self(rc) = self;
StrongRc::debug_references(rc)
}
/// Downgrades this ID to a weak reference.
pub fn downgrade(&self) -> WeakUdpSocketId<I, D, BT> {
let Self(rc) = self;
WeakUdpSocketId(StrongRc::downgrade(rc))
}
/// Returns external data associated with this socket.
pub fn external_data(&self) -> &BT::ExternalData<I> {
let Self(rc) = self;
&rc.external_data
}
}
/// A weak reference to a UDP socket.
#[derive(GenericOverIp, Derivative)]
#[derivative(Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""), Clone(bound = ""))]
#[generic_over_ip(I, Ip)]
pub struct WeakUdpSocketId<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
datagram::WeakRc<I, D, Udp<BT>>,
);
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> PartialEq<UdpSocketId<I, D, BT>>
for WeakUdpSocketId<I, D, BT>
{
fn eq(&self, other: &UdpSocketId<I, D, BT>) -> bool {
PartialEq::eq(other, self)
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> Debug for WeakUdpSocketId<I, D, BT> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Self(rc) = self;
f.debug_tuple("WeakUdpSocketId").field(&rc.ptr_debug()).finish()
}
}
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> WeakUdpSocketId<I, D, BT> {
#[cfg_attr(feature = "instrumented", track_caller)]
pub fn upgrade(&self) -> Option<UdpSocketId<I, D, BT>> {
let Self(rc) = self;
rc.upgrade().map(UdpSocketId)
}
}
pub(crate) type UdpSocketSet<I, D, BT> = DatagramSocketSet<I, D, Udp<BT>>;
pub(crate) type UdpSocketState<I, D, BT> = DatagramSocketState<I, D, Udp<BT>>;
/// The bindings context handling received UDP frames.
pub trait UdpReceiveBindingsContext<I: IpExt, D: device::StrongId>: UdpBindingsTypes {
/// Receives a UDP packet on a socket.
fn receive_udp<B: BufferMut>(
&mut self,
id: &UdpSocketId<I, D::Weak, Self>,
device_id: &D,
dst_addr: (I::Addr, NonZeroU16),
src_addr: (I::Addr, Option<NonZeroU16>),
body: &B,
);
}
/// The bindings context providing external types to UDP sockets.
///
/// # Discussion
///
/// We'd like this trait to take an `I` type parameter instead of using GAT to
/// get the IP version, however we end up with problems due to the shape of
/// [`DatagramSocketSpec`] and the underlying support for dual stack sockets.
///
/// This is completely fine for all known implementations, except for a rough
/// edge in fake tests bindings contexts that are already parameterized on I
/// themselves. This is still better than relying on `Box<dyn Any>` to keep the
/// external data in our references so we take the rough edge.
pub trait UdpBindingsTypes: Sized {
/// Opaque bindings data held by core for a given IP version.
type ExternalData<I: Ip>: Debug + Send + Sync;
}
/// The bindings context for UDP.
pub trait UdpBindingsContext<I: IpExt, D: device::StrongId>:
InstantContext
+ RngContext
+ TracingContext
+ UdpReceiveBindingsContext<I, D>
+ ReferenceNotifiers
+ UdpBindingsTypes
{
}
impl<
I: IpExt,
BC: InstantContext
+ RngContext
+ TracingContext
+ UdpReceiveBindingsContext<I, D>
+ ReferenceNotifiers
+ UdpBindingsTypes,
D: device::StrongId,
> UdpBindingsContext<I, D> for BC
{
}
/// An execution context for the UDP protocol which also provides access to state.
pub trait BoundStateContext<I: IpExt, BC: UdpBindingsContext<I, Self::DeviceId>>:
DeviceIdContext<AnyDevice> + UdpStateContext
{
/// 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>;
type DualStackContext: DualStackDatagramBoundStateContext<
I,
BC,
Udp<BC>,
DeviceId = Self::DeviceId,
WeakDeviceId = Self::WeakDeviceId,
>;
type NonDualStackContext: NonDualStackDatagramBoundStateContext<
I,
BC,
Udp<BC>,
DeviceId = Self::DeviceId,
WeakDeviceId = Self::WeakDeviceId,
>;
/// Calls the function with an immutable reference to UDP sockets.
fn with_bound_sockets<
O,
F: FnOnce(&mut Self::IpSocketsCtx<'_>, &BoundSockets<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
cb: F,
) -> O;
/// Calls the function with a mutable reference to UDP sockets.
fn with_bound_sockets_mut<
O,
F: FnOnce(&mut Self::IpSocketsCtx<'_>, &mut BoundSockets<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
cb: F,
) -> O;
/// Returns a context for dual- or non-dual-stack operation.
fn dual_stack_context(
&mut self,
) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext>;
/// Calls the function without access to the UDP bound socket state.
fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>(
&mut self,
cb: F,
) -> O;
}
pub trait StateContext<I: IpExt, BC: UdpBindingsContext<I, Self::DeviceId>>:
DeviceIdContext<AnyDevice>
{
/// The core context passed to the callback.
type SocketStateCtx<'a>: BoundStateContext<I, BC>
+ DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId>
+ UdpStateContext;
/// Calls the function with mutable access to the set with all UDP
/// sockets.
fn with_all_sockets_mut<O, F: FnOnce(&mut UdpSocketSet<I, Self::WeakDeviceId, BC>) -> O>(
&mut self,
cb: F,
) -> O;
/// Calls the function with immutable access to the set with all UDP
/// sockets.
fn with_all_sockets<O, F: FnOnce(&UdpSocketSet<I, Self::WeakDeviceId, BC>) -> O>(
&mut self,
cb: F,
) -> O;
/// Calls the function without access to UDP socket state.
fn with_bound_state_context<O, F: FnOnce(&mut Self::SocketStateCtx<'_>) -> 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::SocketStateCtx<'_>, &UdpSocketState<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
id: &UdpSocketId<I, Self::WeakDeviceId, BC>,
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::SocketStateCtx<'_>, &mut UdpSocketState<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
id: &UdpSocketId<I, Self::WeakDeviceId, BC>,
cb: F,
) -> O;
/// Returns true if UDP may send a port unreachable ICMP error message.
fn should_send_port_unreachable(&mut self) -> bool;
/// Call `f` with each socket's state.
fn for_each_socket<
F: FnMut(
&mut Self::SocketStateCtx<'_>,
&UdpSocketId<I, Self::WeakDeviceId, BC>,
&UdpSocketState<I, Self::WeakDeviceId, BC>,
),
>(
&mut self,
cb: F,
);
}
/// Empty trait to work around coherence issues.
///
/// This serves only to convince the coherence checker that a particular blanket
/// trait implementation could only possibly conflict with other blanket impls
/// in this crate. It can be safely implemented for any type.
/// TODO(https://github.com/rust-lang/rust/issues/97811): Remove this once the
/// coherence checker doesn't require it.
pub trait UdpStateContext {}
/// An execution context for UDP dual-stack operations.
pub(crate) trait DualStackBoundStateContext<I: IpExt, BC: UdpBindingsContext<I, Self::DeviceId>>:
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>;
/// Calls the provided callback with mutable access to both the
/// demultiplexing maps.
fn with_both_bound_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut BoundSockets<I, Self::WeakDeviceId, BC>,
&mut BoundSockets<I::OtherVersion, Self::WeakDeviceId, BC>,
) -> 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, BC>,
) -> 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;
}
/// An execution context for UDP non-dual-stack operations.
pub(crate) trait NonDualStackBoundStateContext<I: IpExt, BC: UdpBindingsContext<I, Self::DeviceId>>:
DeviceIdContext<AnyDevice>
{
}
/// An implementation of [`IpTransportContext`] for UDP.
pub(crate) enum UdpIpTransportContext {}
fn receive_ip_packet<
I: IpExt,
B: BufferMut,
BC: UdpBindingsContext<I, CC::DeviceId> + UdpBindingsContext<I::OtherVersion, CC::DeviceId>,
CC: StateContext<I, BC> + StateContext<I::OtherVersion, BC> + CounterContext<UdpCounters<I>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
src_ip: I::RecvSrcAddr,
dst_ip: SpecifiedAddr<I::Addr>,
mut buffer: B,
) -> Result<(), (B, TransportReceiveError)> {
trace_duration!(bindings_ctx, c"udp::receive_ip_packet");
core_ctx.increment(|counters| &counters.rx);
trace!("received UDP packet: {:x?}", buffer.as_mut());
let src_ip: I::Addr = src_ip.into();
let packet = if let Ok(packet) =
buffer.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src_ip, dst_ip.get()))
{
packet
} else {
// There isn't much we can do if the UDP packet is
// malformed.
core_ctx.increment(|counters| &counters.rx_malformed);
return Ok(());
};
let src_ip = if let Some(src_ip) = SpecifiedAddr::new(src_ip) {
match src_ip.try_into() {
Ok(addr) => Some(addr),
Err(AddrIsMappedError {}) => {
core_ctx.increment(|counters| &counters.rx_mapped_addr);
trace!("udp::receive_ip_packet: mapped source address");
return Ok(());
}
}
} else {
None
};
let dst_ip = match dst_ip.try_into() {
Ok(addr) => addr,
Err(AddrIsMappedError {}) => {
core_ctx.increment(|counters| &counters.rx_mapped_addr);
trace!("udp::receive_ip_packet: mapped destination address");
return Ok(());
}
};
let src_port = packet.src_port();
// Unfortunately, type inference isn't smart enough for us to just do
// packet.parse_metadata().
let meta = ParsablePacket::<_, UdpParseArgs<I::Addr>>::parse_metadata(&packet);
/// The maximum number of socket IDs that are expected to receive a given
/// packet. While it's possible for this number to be exceeded, it's
/// unlikely.
const MAX_EXPECTED_IDS: usize = 16;
/// Collection of sockets that will receive a packet.
///
/// Making this a [`smallvec::SmallVec`] lets us keep all the retrieved ids
/// on the stack most of the time. If there are more than
/// [`MAX_EXPECTED_IDS`], this will spill and allocate on the heap.
type Recipients<Id> = smallvec::SmallVec<[Id; MAX_EXPECTED_IDS]>;
let dst_port = packet.dst_port();
let recipients = StateContext::<I, _>::with_bound_state_context(core_ctx, |core_ctx| {
let device_weak = core_ctx.downgrade_device_id(device);
DatagramBoundStateContext::with_bound_sockets(core_ctx, |_core_ctx, bound_sockets| {
lookup(bound_sockets, (src_ip, src_port), (dst_ip, dst_port), device_weak)
.map(|result| match result {
LookupResult::Conn(id, _) | LookupResult::Listener(id, _) => id.clone(),
})
// Collect into an array on the stack.
.collect::<Recipients<_>>()
})
});
let was_delivered = recipients.into_iter().fold(false, |was_delivered, lookup_result| {
let delivered = try_dual_stack_deliver::<I, B, BC, CC>(
core_ctx,
bindings_ctx,
lookup_result,
device,
(dst_ip.addr(), dst_port),
(src_ip.map_or(I::UNSPECIFIED_ADDRESS, SocketIpAddr::addr), src_port),
&buffer,
);
was_delivered | delivered
});
if !was_delivered && StateContext::<I, _>::should_send_port_unreachable(core_ctx) {
buffer.undo_parse(meta);
core_ctx.increment(|counters| &counters.rx_unknown_dest_port);
Err((buffer, TransportReceiveError::new_port_unreachable()))
} else {
Ok(())
}
}
/// Tries to deliver the given UDP packet to the given UDP socket.
fn try_deliver<
I: IpExt,
CC: StateContext<I, BC>,
BC: UdpBindingsContext<I, CC::DeviceId>,
B: BufferMut,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
id: &UdpSocketId<I, CC::WeakDeviceId, BC>,
device: &CC::DeviceId,
(dst_ip, dst_port): (I::Addr, NonZeroU16),
(src_ip, src_port): (I::Addr, Option<NonZeroU16>),
buffer: &B,
) -> bool {
core_ctx.with_socket_state(&id, |core_ctx, state| {
let should_deliver = match state {
DatagramSocketState::Bound(DatagramBoundSocketState {
socket_type,
original_bound_addr: _,
}) => match socket_type {
DatagramBoundSocketStateType::Connected { state, sharing: _ } => {
match transport::udp::BoundStateContext::dual_stack_context(core_ctx) {
MaybeDualStack::DualStack(dual_stack) => {
match dual_stack.converter().convert(state) {
DualStackConnState::ThisStack(state) => state.should_receive(),
DualStackConnState::OtherStack(state) => state.should_receive(),
}
}
MaybeDualStack::NotDualStack(not_dual_stack) => {
not_dual_stack.converter().convert(state).should_receive()
}
}
}
DatagramBoundSocketStateType::Listener { state: _, sharing: _ } => true,
},
DatagramSocketState::Unbound(_) => true,
};
if should_deliver {
bindings_ctx.receive_udp(id, device, (dst_ip, dst_port), (src_ip, src_port), buffer);
}
should_deliver
})
}
/// A wrapper for [`try_deliver`] that supports dual stack delivery.
fn try_dual_stack_deliver<
I: IpExt,
B: BufferMut,
BC: UdpBindingsContext<I, CC::DeviceId> + UdpBindingsContext<I::OtherVersion, CC::DeviceId>,
CC: StateContext<I, BC> + StateContext<I::OtherVersion, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
socket: I::DualStackBoundSocketId<CC::WeakDeviceId, Udp<BC>>,
device: &CC::DeviceId,
(dst_ip, dst_port): (I::Addr, NonZeroU16),
(src_ip, src_port): (I::Addr, Option<NonZeroU16>),
buffer: &B,
) -> bool {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Inputs<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> {
src_ip: I::Addr,
dst_ip: I::Addr,
socket: I::DualStackBoundSocketId<D, Udp<BT>>,
}
struct Outputs<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> {
src_ip: I::Addr,
dst_ip: I::Addr,
socket: UdpSocketId<I, D, BT>,
}
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
enum DualStackOutputs<I: DualStackIpExt, D: device::WeakId, BT: UdpBindingsTypes> {
CurrentStack(Outputs<I, D, BT>),
OtherStack(Outputs<I::OtherVersion, D, BT>),
}
let dual_stack_outputs = I::map_ip(
Inputs { src_ip, dst_ip, socket },
|Inputs { src_ip, dst_ip, socket }| match socket {
EitherIpSocket::V4(socket) => {
DualStackOutputs::CurrentStack(Outputs { src_ip, dst_ip, socket })
}
EitherIpSocket::V6(socket) => DualStackOutputs::OtherStack(Outputs {
src_ip: src_ip.to_ipv6_mapped().get(),
dst_ip: dst_ip.to_ipv6_mapped().get(),
socket,
}),
},
|Inputs { src_ip, dst_ip, socket }| {
DualStackOutputs::CurrentStack(Outputs { src_ip, dst_ip, socket })
},
);
match dual_stack_outputs {
DualStackOutputs::CurrentStack(Outputs { src_ip, dst_ip, socket }) => try_deliver(
core_ctx,
bindings_ctx,
&socket,
device,
(dst_ip, dst_port),
(src_ip, src_port),
buffer,
),
DualStackOutputs::OtherStack(Outputs { src_ip, dst_ip, socket }) => try_deliver(
core_ctx,
bindings_ctx,
&socket,
device,
(dst_ip, dst_port),
(src_ip, src_port),
buffer,
),
}
}
impl<
I: IpExt,
BC: UdpBindingsContext<I, CC::DeviceId> + UdpBindingsContext<I::OtherVersion, CC::DeviceId>,
CC: StateContext<I, BC>
+ StateContext<I::OtherVersion, BC>
+ NonTestCtxMarker
+ CounterContext<UdpCounters<I>>,
> IpTransportContext<I, BC, CC> for UdpIpTransportContext
{
fn receive_icmp_error(
core_ctx: &mut CC,
_bindings_ctx: &mut BC,
_device: &CC::DeviceId,
original_src_ip: Option<SpecifiedAddr<I::Addr>>,
original_dst_ip: SpecifiedAddr<I::Addr>,
_original_udp_packet: &[u8],
err: I::ErrorCode,
) {
core_ctx.increment(|counters| &counters.rx_icmp_error);
// NB: At the moment bindings has no need to consume ICMP errors, so we
// swallow them here.
// TODO(https://fxbug.dev/322214321): Actually implement SO_ERROR.
debug!(
"UDP received ICMP error {:?} from {:?} to {:?}",
err, original_dst_ip, original_src_ip
);
}
fn receive_ip_packet<B: BufferMut>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
src_ip: I::RecvSrcAddr,
dst_ip: SpecifiedAddr<I::Addr>,
buffer: B,
) -> Result<(), (B, TransportReceiveError)> {
receive_ip_packet::<I, _, _, _>(core_ctx, bindings_ctx, device, src_ip, dst_ip, buffer)
}
}
/// An error encountered while sending a UDP packet to an alternate address.
#[derive(Error, Copy, Clone, Debug, Eq, PartialEq)]
pub enum SendToError {
/// The socket is not writeable.
#[error("not writeable")]
NotWriteable,
/// An error was encountered while trying to create a temporary IP socket
/// to use for the send operation.
#[error("could not create a temporary connection socket: {}", _0)]
CreateSock(IpSockCreationError),
/// An error was encountered while trying to send via the temporary IP
/// socket.
#[error("could not send via temporary socket: {}", _0)]
Send(IpSockSendError),
/// There was a problem with the remote address relating to its zone.
#[error("zone error: {}", _0)]
Zone(ZonedAddressError),
/// Disallow sending packets with a remote port of 0. See
/// [`UdpRemotePort::Unset`] for the rationale.
#[error("the remote port was unset")]
RemotePortUnset,
/// The remote address is mapped (i.e. an ipv4-mapped-ipv6 address), but the
/// socket is not dual-stack enabled.
#[error("the remote ip was unexpectedly an ipv4-mapped-ipv6 address")]
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("the remote ip was unexpectedly not an ipv4-mapped-ipv6 address")]
RemoteUnexpectedlyNonMapped,
}
/// The UDP socket API.
pub struct UdpApi<I: Ip, C>(C, IpVersionMarker<I>);
impl<I: Ip, C> UdpApi<I, C> {
pub(crate) fn new(ctx: C) -> Self {
Self(ctx, IpVersionMarker::new())
}
}
/// A local alias for [`UdpSocketId`] for use in [`UdpApi`].
///
/// TODO(https://github.com/rust-lang/rust/issues/8995): Make this an inherent
/// associated type.
type UdpApiSocketId<I, C> = UdpSocketId<
I,
<<C as ContextPair>::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId,
<C as ContextPair>::BindingsContext,
>;
impl<I, C> UdpApi<I, C>
where
I: IpExt,
C: ContextPair,
C::CoreContext: StateContext<I, C::BindingsContext> + CounterContext<UdpCounters<I>>,
C::BindingsContext:
UdpBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
{
fn core_ctx(&mut self) -> &mut C::CoreContext {
let Self(pair, IpVersionMarker { .. }) = self;
pair.core_ctx()
}
fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
let Self(pair, IpVersionMarker { .. }) = self;
pair.contexts()
}
/// Creates a new unbound UDP socket with default external data.
pub fn create(&mut self) -> UdpApiSocketId<I, C>
where
<C::BindingsContext as UdpBindingsTypes>::ExternalData<I>: Default,
{
self.create_with(Default::default())
}
/// Creates a new unbound UDP socket with provided external data.
pub fn create_with(
&mut self,
external_data: <C::BindingsContext as UdpBindingsTypes>::ExternalData<I>,
) -> UdpApiSocketId<I, C> {
datagram::create(self.core_ctx(), external_data)
}
/// Connect a UDP socket
///
/// `connect` binds `id` as a connection to the remote address and port. It
/// is also bound to a local address and port, meaning that packets sent on
/// this connection will always come from that address and port. The local
/// address will be chosen based on the route to the remote address, and the
/// local port will be chosen from the available ones.
///
/// # Errors
///
/// `connect` will fail in the following cases:
/// - If both `local_ip` and `local_port` are specified but conflict with an
/// existing connection or listener
/// - If one or both are left unspecified but there is still no way to
/// satisfy the request (e.g., `local_ip` is specified but there are no
/// available local ports for that address)
/// - If there is no route to `remote_ip`
/// - If `id` belongs to an already-connected socket
///
/// # Panics
///
/// `connect` panics if `id` is not a valid [`SocketId`].
pub fn connect(
&mut self,
id: &UdpApiSocketId<I, C>,
remote_ip: Option<
ZonedAddr<
SpecifiedAddr<I::Addr>,
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
>,
>,
remote_port: UdpRemotePort,
) -> Result<(), ConnectError> {
let (core_ctx, bindings_ctx) = self.contexts();
debug!("connect on {id:?} to {remote_ip:?}:{remote_port:?}");
datagram::connect(core_ctx, bindings_ctx, id, remote_ip, remote_port, ())
}
/// Sets the bound device for a socket.
///
/// Sets the device to be used for sending and receiving packets for a socket.
/// If the socket is not currently bound to a local address and port, the device
/// will be used when binding.
///
/// # Panics
///
/// Panics if `id` is not a valid [`UdpSocketId`].
pub fn set_device(
&mut self,
id: &UdpApiSocketId<I, C>,
device_id: Option<&<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
) -> Result<(), SocketError> {
let (core_ctx, bindings_ctx) = self.contexts();
debug!("set device on {id:?} to {device_id:?}");
datagram::set_device(core_ctx, bindings_ctx, id, device_id)
}
/// Gets the device the specified socket is bound to.
///
/// # Panics
///
/// Panics if `id` is not a valid socket ID.
pub fn get_bound_device(
&mut self,
id: &UdpApiSocketId<I, C>,
) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId> {
let (core_ctx, bindings_ctx) = self.contexts();
datagram::get_bound_device(core_ctx, bindings_ctx, id)
}
/// Enable or disable dual stack operations on the given socket.
///
/// This is notionally the inverse of the `IPV6_V6ONLY` socket option.
///
/// # Errors
///
/// Returns an error if the socket does not support the `IPV6_V6ONLY` socket
/// option (e.g. an IPv4 socket).
///
/// # Panics
///
/// Panics if `id` is not a valid `UdpSocketId`.
pub fn set_dual_stack_enabled(
&mut self,
id: &UdpApiSocketId<I, C>,
enabled: bool,
) -> Result<(), SetDualStackEnabledError> {
let (core_ctx, bindings_ctx) = self.contexts();
datagram::with_other_stack_ip_options_mut_if_unbound(
core_ctx,
bindings_ctx,
id,
|other_stack| {
I::map_ip(
(enabled, WrapOtherStackIpOptionsMut(other_stack)),
|(_enabled, _v4)| Err(SetDualStackEnabledError::NotCapable),
|(enabled, WrapOtherStackIpOptionsMut(other_stack))| {
let DualStackSocketState { dual_stack_enabled, hop_limits: _ } =
other_stack;
*dual_stack_enabled = enabled;
Ok(())
},
)
},
)
.map_err(|ExpectedUnboundError| {
// NB: Match Linux and prefer to return `NotCapable` errors over
// `SocketIsBound` errors, for IPv4 sockets.
match I::VERSION {
IpVersion::V4 => SetDualStackEnabledError::NotCapable,
IpVersion::V6 => SetDualStackEnabledError::SocketIsBound,
}
})?
}
/// Get the enabled state of dual stack operations on the given socket.
///
/// This is notionally the inverse of the `IPV6_V6ONLY` socket option.
///
/// # Errors
///
/// Returns an error if the socket does not support the `IPV6_V6ONLY` socket
/// option (e.g. an IPv4 socket).
///
/// # Panics
///
/// Panics if `id` is not a valid `UdpSocketId`.
pub fn get_dual_stack_enabled(
&mut self,
id: &UdpApiSocketId<I, C>,
) -> Result<bool, NotDualStackCapableError> {
let (core_ctx, bindings_ctx) = self.contexts();
datagram::with_other_stack_ip_options(core_ctx, bindings_ctx, id, |other_stack| {
I::map_ip(
WrapOtherStackIpOptions(other_stack),
|_v4| Err(NotDualStackCapableError),
|WrapOtherStackIpOptions(other_stack)| {
let DualStackSocketState { dual_stack_enabled, hop_limits: _ } = other_stack;
Ok(*dual_stack_enabled)
},
)
})
}
/// Sets the POSIX `SO_REUSEPORT` option for the specified socket.
///
/// # Errors
///
/// Returns an error if the socket is already bound.
///
/// # Panics
///
/// panics if `id` is not a valid `UdpSocketId`.
pub fn set_posix_reuse_port(
&mut self,
id: &UdpApiSocketId<I, C>,
reuse_port: bool,
) -> Result<(), ExpectedUnboundError> {
datagram::update_sharing(
self.core_ctx(),
id,
if reuse_port { Sharing::ReusePort } else { Sharing::Exclusive },
)
}
/// Gets the POSIX `SO_REUSEPORT` option for the specified socket.
///
/// # Panics
///
/// Panics if `id` is not a valid `UdpSocketId`.
pub fn get_posix_reuse_port(&mut self, id: &UdpApiSocketId<I, C>) -> bool {
datagram::get_sharing(self.core_ctx(), id).is_reuse_port()
}
/// 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 fn set_multicast_membership(
&mut self,
id: &UdpApiSocketId<I, C>,
multicast_group: MulticastAddr<I::Addr>,
interface: MulticastMembershipInterfaceSelector<
I::Addr,
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
>,
want_membership: bool,
) -> Result<(), SetMulticastMembershipError> {
let (core_ctx, bindings_ctx) = self.contexts();
datagram::set_multicast_membership(
core_ctx,
bindings_ctx,
id,
multicast_group,
interface,
want_membership,
)
.map_err(Into::into)
}
/// Sets the hop limit for packets sent by the socket to a unicast
/// destination.
///
/// Sets the IPv4 TTL when `ip_version` is [`IpVersion::V4`], and the IPv6
/// hop limits when `ip_version` is [`IpVersion::V6`].
///
/// Returns [`NotDualStackCapableError`] if called on an IPv4 Socket with an
/// `ip_version` of [`IpVersion::V6`].
pub fn set_unicast_hop_limit(
&mut self,
id: &UdpApiSocketId<I, C>,
unicast_hop_limit: Option<NonZeroU8>,
ip_version: IpVersion,
) -> Result<(), NotDualStackCapableError> {
let (core_ctx, bindings_ctx) = self.contexts();
if ip_version == I::VERSION {
return Ok(crate::socket::datagram::update_ip_hop_limit(
core_ctx,
bindings_ctx,
id,
SocketHopLimits::set_unicast(unicast_hop_limit),
));
}
datagram::with_other_stack_ip_options_mut(core_ctx, bindings_ctx, id, |other_stack| {
I::map_ip(
(IpInvariant(unicast_hop_limit), WrapOtherStackIpOptionsMut(other_stack)),
|(IpInvariant(_unicast_hop_limit), _v4)| Err(NotDualStackCapableError),
|(IpInvariant(unicast_hop_limit), WrapOtherStackIpOptionsMut(other_stack))| {
let DualStackSocketState {
dual_stack_enabled: _,
hop_limits: SocketHopLimits { unicast, multicast: _, version: _ },
} = other_stack;
*unicast = unicast_hop_limit;
Ok(())
},
)
})
}
/// Sets the hop limit for packets sent by the socket to a multicast
/// destination.
///
/// Sets the IPv4 TTL when `ip_version` is [`IpVersion::V4`], and the IPv6
/// hop limits when `ip_version` is [`IpVersion::V6`].
///
/// Returns [`NotDualStackCapableError`] if called on an IPv4 Socket with an
/// `ip_version` of [`IpVersion::V6`].
pub fn set_multicast_hop_limit(
&mut self,
id: &UdpApiSocketId<I, C>,
multicast_hop_limit: Option<NonZeroU8>,
ip_version: IpVersion,
) -> Result<(), NotDualStackCapableError> {
let (core_ctx, bindings_ctx) = self.contexts();
if ip_version == I::VERSION {
return Ok(crate::socket::datagram::update_ip_hop_limit(
core_ctx,
bindings_ctx,
id,
SocketHopLimits::set_multicast(multicast_hop_limit),
));
}
datagram::with_other_stack_ip_options_mut(core_ctx, bindings_ctx, id, |other_stack| {
I::map_ip(
(IpInvariant(multicast_hop_limit), WrapOtherStackIpOptionsMut(other_stack)),
|(IpInvariant(_multicast_hop_limit), _v4)| Err(NotDualStackCapableError),
|(IpInvariant(multicast_hop_limit), WrapOtherStackIpOptionsMut(other_stack))| {
let DualStackSocketState {
dual_stack_enabled: _,
hop_limits: SocketHopLimits { unicast: _, multicast, version: _ },
} = other_stack;
*multicast = multicast_hop_limit;
Ok(())
},
)
})
}
/// Gets the hop limit for packets sent by the socket to a unicast
/// destination.
///
/// Gets the IPv4 TTL when `ip_version` is [`IpVersion::V4`], and the IPv6
/// hop limits when `ip_version` is [`IpVersion::V6`].
///
/// Returns [`NotDualStackCapableError`] if called on an IPv4 Socket with an
/// `ip_version` of [`IpVersion::V6`].
pub fn get_unicast_hop_limit(
&mut self,
id: &UdpApiSocketId<I, C>,
ip_version: IpVersion,
) -> Result<NonZeroU8, NotDualStackCapableError> {
let (core_ctx, bindings_ctx) = self.contexts();
if ip_version == I::VERSION {
return Ok(
crate::socket::datagram::get_ip_hop_limits(core_ctx, bindings_ctx, id).unicast
);
}
datagram::with_other_stack_ip_options_and_default_hop_limits(
core_ctx,
bindings_ctx,
id,
|other_stack, default_hop_limits| {
I::map_ip::<_, Result<IpInvariant<NonZeroU8>, _>>(
(WrapOtherStackIpOptions(other_stack), IpInvariant(default_hop_limits)),
|_v4| Err(NotDualStackCapableError),
|(
WrapOtherStackIpOptions(other_stack),
IpInvariant(HopLimits { unicast: default_unicast, multicast: _ }),
)| {
let DualStackSocketState {
dual_stack_enabled: _,
hop_limits: SocketHopLimits { unicast, multicast: _, version: _ },
} = other_stack;
Ok(IpInvariant(unicast.unwrap_or(default_unicast)))
},
)
},
)?
.map(|IpInvariant(unicast)| unicast)
}
/// Gets the hop limit for packets sent by the socket to a multicast
/// destination.
///
/// Gets the IPv4 TTL when `ip_version` is [`IpVersion::V4`], and the IPv6
/// hop limits when `ip_version` is [`IpVersion::V6`].
///
/// Returns [`NotDualStackCapableError`] if called on an IPv4 Socket with an
/// `ip_version` of [`IpVersion::V6`].
pub fn get_multicast_hop_limit(
&mut self,
id: &UdpApiSocketId<I, C>,
ip_version: IpVersion,
) -> Result<NonZeroU8, NotDualStackCapableError> {
let (core_ctx, bindings_ctx) = self.contexts();
if ip_version == I::VERSION {
return Ok(
crate::socket::datagram::get_ip_hop_limits(core_ctx, bindings_ctx, id).multicast
);
}
datagram::with_other_stack_ip_options_and_default_hop_limits(
core_ctx,
bindings_ctx,
id,
|other_stack, default_hop_limits| {
I::map_ip::<_, Result<IpInvariant<NonZeroU8>, _>>(
(WrapOtherStackIpOptions(other_stack), IpInvariant(default_hop_limits)),
|_v4| Err(NotDualStackCapableError),
|(
WrapOtherStackIpOptions(other_stack),
IpInvariant(HopLimits { unicast: _, multicast: default_multicast }),
)| {
let DualStackSocketState {
dual_stack_enabled: _,
hop_limits: SocketHopLimits { unicast: _, multicast, version: _ },
} = other_stack;
Ok(IpInvariant(multicast.unwrap_or(default_multicast)))
},
)
},
)?
.map(|IpInvariant(multicast)| multicast)
}
/// Gets the transparent option.
pub fn get_transparent(&mut self, id: &UdpApiSocketId<I, C>) -> bool {
crate::socket::datagram::get_ip_transparent(self.core_ctx(), id)
}
/// Sets the transparent option.
pub fn set_transparent(&mut self, id: &UdpApiSocketId<I, C>, value: bool) {
crate::socket::datagram::set_ip_transparent(self.core_ctx(), id, value)
}
/// Disconnects a connected UDP socket.
///
/// `disconnect` removes an existing connected socket and replaces it with a
/// listening socket bound to the same local address and port.
///
/// # Errors
///
/// Returns an error if the socket is not connected.
///
/// # Panics
///
/// Panics if `id` is not a valid `UdpSocketId`.
pub fn disconnect(&mut self, id: &UdpApiSocketId<I, C>) -> Result<(), ExpectedConnError> {
let (core_ctx, bindings_ctx) = self.contexts();
debug!("disconnect {id:?}");
datagram::disconnect_connected(core_ctx, bindings_ctx, id)
}
/// Shuts down a socket for reading and/or writing.
///
/// # Errors
///
/// Returns an error if the socket is not connected.
///
/// # Panics
///
/// Panics if `id` is not a valid `UdpSocketId`.
pub fn shutdown(
&mut self,
id: &UdpApiSocketId<I, C>,
which: ShutdownType,
) -> Result<(), ExpectedConnError> {
let (core_ctx, bindings_ctx) = self.contexts();
debug!("shutdown {id:?} {which:?}");
datagram::shutdown_connected(core_ctx, bindings_ctx, id, which)
}
/// Get the shutdown state for a socket.
///
/// If the socket is not connected, or if `shutdown` was not called on it,
/// returns `None`.
///
/// # Panics
///
/// Panics if `id` is not a valid `UdpSocketId`.
pub fn get_shutdown(&mut self, id: &UdpApiSocketId<I, C>) -> Option<ShutdownType> {
let (core_ctx, bindings_ctx) = self.contexts();
datagram::get_shutdown_connected(core_ctx, bindings_ctx, id)
}
/// Removes a socket that was previously created.
///
/// # Panics
///
/// Panics if `id` is not a valid [`UdpSocketId`].
pub fn close(
&mut self,
id: UdpApiSocketId<I, C>,
) -> RemoveResourceResultWithContext<
<C::BindingsContext as UdpBindingsTypes>::ExternalData<I>,
C::BindingsContext,
> {
let (core_ctx, bindings_ctx) = self.contexts();
debug!("close {id:?}");
datagram::close(core_ctx, bindings_ctx, id)
}
/// Gets the [`SocketInfo`] associated with the UDP socket referenced by
/// `id`.
///
/// # Panics
///
/// `get_udp_info` panics if `id` is not a valid `UdpSocketId`.
pub fn get_info(
&mut self,
id: &UdpApiSocketId<I, C>,
) -> SocketInfo<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId> {
let (core_ctx, bindings_ctx) = self.contexts();
datagram::get_info(core_ctx, bindings_ctx, id)
}
/// Use an existing socket to listen for incoming UDP packets.
///
/// `listen_udp` converts `id` into a listening socket and registers the new
/// socket as a listener for incoming UDP packets on the given `port`. If
/// `addr` is `None`, the listener is a "wildcard listener", and is bound to
/// all local addresses. See the [`crate::transport`] module documentation
/// for more details.
///
/// If `addr` is `Some``, and `addr` is already bound on the given port
/// (either by a listener or a connection), `listen_udp` will fail. If
/// `addr` is `None`, and a wildcard listener is already bound to the given
/// port, `listen_udp` will fail.
///
/// # Errors
///
/// Returns an error if the socket is not currently unbound.
///
/// # Panics
///
/// `listen_udp` panics if `id` is not a valid [`UdpSocketId`].
pub fn listen(
&mut self,
id: &UdpApiSocketId<I, C>,
addr: Option<
ZonedAddr<
SpecifiedAddr<I::Addr>,
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
>,
>,
port: Option<NonZeroU16>,
) -> Result<(), Either<ExpectedUnboundError, LocalAddressError>> {
let (core_ctx, bindings_ctx) = self.contexts();
debug!("listen on {id:?} on {addr:?}:{port:?}");
datagram::listen(core_ctx, bindings_ctx, id, addr, port)
}
/// Sends a UDP packet on an existing socket.
///
/// # Errors
///
/// Returns an error if the socket is not connected or the packet cannot be
/// sent. On error, the original `body` is returned unmodified so that it
/// can be reused by the caller.
///
/// # Panics
///
/// Panics if `id` is not a valid UDP socket identifier.
pub fn send<B: BufferMut>(
&mut self,
id: &UdpApiSocketId<I, C>,
body: B,
) -> Result<(), Either<SendError, ExpectedConnError>> {
let (core_ctx, bindings_ctx) = self.contexts();
core_ctx.increment(|counters| &counters.tx);
datagram::send_conn(core_ctx, bindings_ctx, id, body).map_err(|err| {
core_ctx.increment(|counters| &counters.tx_error);
match err {
DatagramSendError::NotConnected => Either::Right(ExpectedConnError),
DatagramSendError::NotWriteable => Either::Left(SendError::NotWriteable),
DatagramSendError::IpSock(err) => Either::Left(SendError::IpSock(err)),
DatagramSendError::SerializeError(err) => match err {
UdpSerializeError::RemotePortUnset => Either::Left(SendError::RemotePortUnset),
},
}
})
}
/// Sends a UDP packet to the provided destination address.
///
/// If this is called with an unbound socket, the socket will be implicitly
/// bound. If that succeeds, the ID for the new socket is returned.
///
/// # Errors
///
/// Returns an error if the socket is unbound and connecting fails, or if the
/// packet could not be sent. If the socket is unbound and connecting succeeds
/// but sending fails, the socket remains connected.
///
/// # Panics
///
/// Panics if `id` is not a valid UDP socket identifier.
pub fn send_to<B: BufferMut>(
&mut self,
id: &UdpApiSocketId<I, C>,
remote_ip: Option<
ZonedAddr<
SpecifiedAddr<I::Addr>,
<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
>,
>,
remote_port: UdpRemotePort,
body: B,
) -> Result<(), Either<LocalAddressError, SendToError>> {
// Match Linux's behavior and verify the remote port is set.
match remote_port {
UdpRemotePort::Unset => return Err(Either::Right(SendToError::RemotePortUnset)),
UdpRemotePort::Set(_) => {}
}
let (core_ctx, bindings_ctx) = self.contexts();
core_ctx.increment(|counters| &counters.tx);
datagram::send_to(core_ctx, bindings_ctx, id, remote_ip, remote_port, body).map_err(|e| {
core_ctx.increment(|counters| &counters.tx_error);
match e {
Either::Left(e) => Either::Left(e),
Either::Right(e) => {
let err = match e {
datagram::SendToError::SerializeError(err) => match err {
UdpSerializeError::RemotePortUnset => SendToError::RemotePortUnset,
},
datagram::SendToError::NotWriteable => SendToError::NotWriteable,
datagram::SendToError::Zone(e) => SendToError::Zone(e),
datagram::SendToError::CreateAndSend(e) => match e {
IpSockCreateAndSendError::Send(e) => SendToError::Send(e),
IpSockCreateAndSendError::Create(e) => SendToError::CreateSock(e),
},
datagram::SendToError::RemoteUnexpectedlyMapped => {
SendToError::RemoteUnexpectedlyMapped
}
datagram::SendToError::RemoteUnexpectedlyNonMapped => {
SendToError::RemoteUnexpectedlyNonMapped
}
};
Either::Right(err)
}
}
})
}
/// Collects all currently opened sockets, returning a cloned reference for
/// each one.
pub fn collect_all_sockets(&mut self) -> Vec<UdpApiSocketId<I, C>> {
datagram::collect_all_sockets(self.core_ctx())
}
/// Provides inspect data for UDP sockets.
pub fn inspect<N>(&mut self, inspector: &mut N)
where
N: Inspector
+ InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId>,
{
DatagramStateContext::for_each_socket(self.core_ctx(), |_ctx, socket_id, socket_state| {
socket_state.record_common_info(inspector, socket_id);
});
}
}
/// Error when sending a packet on a socket.
#[derive(Copy, Clone, Debug, Eq, PartialEq, GenericOverIp)]
#[generic_over_ip()]
pub enum SendError {
/// The socket is not writeable.
NotWriteable,
/// The packet couldn't be sent.
IpSock(IpSockSendError),
/// Disallow sending packets with a remote port of 0. See
/// [`UdpRemotePort::Unset`] for the rationale.
RemotePortUnset,
}
impl<I: IpExt, BC: UdpBindingsContext<I, Self::DeviceId>, CC: StateContext<I, BC>>
DatagramStateContext<I, BC, Udp<BC>> for CC
{
type SocketsStateCtx<'a> = CC::SocketStateCtx<'a>;
fn with_all_sockets_mut<O, F: FnOnce(&mut UdpSocketSet<I, Self::WeakDeviceId, BC>) -> O>(
&mut self,
cb: F,
) -> O {
StateContext::with_all_sockets_mut(self, cb)
}
fn with_all_sockets<O, F: FnOnce(&UdpSocketSet<I, Self::WeakDeviceId, BC>) -> O>(
&mut self,
cb: F,
) -> O {
StateContext::with_all_sockets(self, cb)
}
fn with_socket_state<
O,
F: FnOnce(&mut Self::SocketsStateCtx<'_>, &UdpSocketState<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
id: &UdpSocketId<I, Self::WeakDeviceId, BC>,
cb: F,
) -> O {
StateContext::with_socket_state(self, id, cb)
}
fn with_socket_state_mut<
O,
F: FnOnce(&mut Self::SocketsStateCtx<'_>, &mut UdpSocketState<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
id: &UdpSocketId<I, Self::WeakDeviceId, BC>,
cb: F,
) -> O {
StateContext::with_socket_state_mut(self, id, cb)
}
fn for_each_socket<
F: FnMut(
&mut Self::SocketsStateCtx<'_>,
&UdpSocketId<I, Self::WeakDeviceId, BC>,
&UdpSocketState<I, Self::WeakDeviceId, BC>,
),
>(
&mut self,
cb: F,
) {
StateContext::for_each_socket(self, cb)
}
}
impl<
I: IpExt,
BC: UdpBindingsContext<I, Self::DeviceId>,
CC: BoundStateContext<I, BC> + UdpStateContext,
> DatagramBoundStateContext<I, BC, Udp<BC>> for CC
{
type IpSocketsCtx<'a> = CC::IpSocketsCtx<'a>;
fn with_bound_sockets<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&DatagramBoundSockets<
I,
Self::WeakDeviceId,
UdpAddrSpec,
(Udp<BC>, I, CC::WeakDeviceId),
>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
self.with_bound_sockets(|core_ctx, BoundSockets { bound_sockets }| {
cb(core_ctx, bound_sockets)
})
}
fn with_bound_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut DatagramBoundSockets<
I,
Self::WeakDeviceId,
UdpAddrSpec,
(Udp<BC>, I, CC::WeakDeviceId),
>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
self.with_bound_sockets_mut(|core_ctx, BoundSockets { bound_sockets }| {
cb(core_ctx, bound_sockets)
})
}
type DualStackContext = CC::DualStackContext;
type NonDualStackContext = CC::NonDualStackContext;
fn dual_stack_context(
&mut self,
) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext> {
BoundStateContext::dual_stack_context(self)
}
fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>(
&mut self,
cb: F,
) -> O {
self.with_transport_context(cb)
}
}
impl<
BC: UdpBindingsContext<Ipv6, CC::DeviceId> + UdpBindingsContext<Ipv4, CC::DeviceId>,
CC: DualStackBoundStateContext<Ipv6, BC> + UdpStateContext,
> DualStackDatagramBoundStateContext<Ipv6, BC, Udp<BC>> for CC
{
type IpSocketsCtx<'a> = CC::IpSocketsCtx<'a>;
fn dual_stack_enabled(
&self,
state: &impl AsRef<IpOptions<Ipv6, Self::WeakDeviceId, Udp<BC>>>,
) -> bool {
let DualStackSocketState { dual_stack_enabled, hop_limits: _ } =
state.as_ref().other_stack();
*dual_stack_enabled
}
fn to_other_send_options<'a>(
&self,
state: &'a IpOptions<Ipv6, Self::WeakDeviceId, Udp<BC>>,
) -> &'a SocketHopLimits<Ipv4> {
let DualStackSocketState { dual_stack_enabled: _, hop_limits } = state.other_stack();
hop_limits
}
type Converter = ();
fn converter(&self) -> Self::Converter {
()
}
fn to_other_bound_socket_id(
&self,
id: &UdpSocketId<Ipv6, CC::WeakDeviceId, BC>,
) -> EitherIpSocket<CC::WeakDeviceId, Udp<BC>> {
EitherIpSocket::V6(id.clone())
}
fn with_both_bound_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut UdpBoundSocketMap<Ipv6, Self::WeakDeviceId, BC>,
&mut UdpBoundSocketMap<Ipv4, Self::WeakDeviceId, BC>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
self.with_both_bound_sockets_mut(
|core_ctx,
BoundSockets { bound_sockets: bound_first },
BoundSockets { bound_sockets: bound_second }| {
cb(core_ctx, bound_first, bound_second)
},
)
}
fn with_other_bound_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut UdpBoundSocketMap<Ipv4, Self::WeakDeviceId, BC>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
self.with_other_bound_sockets_mut(|core_ctx, BoundSockets { bound_sockets }| {
cb(core_ctx, bound_sockets)
})
}
fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>(
&mut self,
cb: F,
) -> O {
self.with_transport_context(|core_ctx| cb(core_ctx))
}
}
impl<
BC: UdpBindingsContext<Ipv4, CC::DeviceId>,
CC: BoundStateContext<Ipv4, BC> + NonDualStackBoundStateContext<Ipv4, BC> + UdpStateContext,
> NonDualStackDatagramBoundStateContext<Ipv4, BC, Udp<BC>> for CC
{
type Converter = ();
fn converter(&self) -> Self::Converter {
()
}
}
#[cfg(test)]
mod tests {
use alloc::{
borrow::ToOwned,
collections::{HashMap, HashSet},
vec,
};
use const_unwrap::const_unwrap_option;
use core::{
borrow::BorrowMut,
convert::TryInto as _,
ops::{Deref, DerefMut},
};
use assert_matches::assert_matches;
use ip_test_macro::ip_test;
use itertools::Itertools as _;
use net_declare::net_ip_v4 as ip_v4;
use net_declare::net_ip_v6;
use net_types::{
ip::{IpAddr, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6SourceAddr},
AddrAndZone, LinkLocalAddr, MulticastAddr, Scope as _, ScopeableAddress as _, ZonedAddr,
};
use packet::Buf;
use test_case::test_case;
use super::*;
use crate::{
context::testutil::{
FakeBindingsCtx, FakeCoreCtx, FakeCtxWithCoreCtx, FakeFrameCtx, Wrapped,
WrappedFakeCoreCtx,
},
device::{
loopback::{LoopbackCreationProperties, LoopbackDevice},
testutil::{FakeDeviceId, FakeStrongDeviceId, FakeWeakDeviceId, MultipleDevicesId},
DeviceId,
},
error::RemoteAddressError,
ip::{
device::state::IpDeviceStateIpExt,
socket::testutil::{FakeDeviceConfig, FakeDualStackIpSocketCtx, FakeFilterDeviceId},
testutil::{DualStackSendIpPacketMeta, FakeIpDeviceIdCtx},
ResolveRouteError, SendIpPacketMeta,
},
socket::{self, datagram::MulticastInterfaceSelector, StrictlyZonedAddr},
testutil::{set_logger_for_test, TestIpExt as _},
uninstantiable::UninstantiableWrapper,
};
/// A packet received on a socket.
#[derive(Debug, Derivative, PartialEq)]
#[derivative(Default(bound = ""))]
struct SocketReceived<I: Ip> {
packets: Vec<ReceivedPacket<I>>,
}
#[derive(Debug, PartialEq)]
struct ReceivedPacket<I: Ip> {
addr: ReceivedPacketAddrs<I>,
body: Vec<u8>,
}
#[derive(Debug, PartialEq)]
struct ReceivedPacketAddrs<I: Ip> {
src_ip: I::Addr,
dst_ip: I::Addr,
src_port: Option<NonZeroU16>,
}
impl FakeUdpCoreCtx<FakeDeviceId> {
fn new_fake_device<I: TestIpExt>() -> Self {
Self::with_local_remote_ip_addrs(vec![local_ip::<I>()], vec![remote_ip::<I>()])
}
}
impl<Outer: Default> Wrapped<Outer, FakeUdpInnerCoreCtx<FakeDeviceId>> {
fn with_local_remote_ip_addrs<A: Into<SpecifiedAddr<IpAddr>>>(
local_ips: Vec<A>,
remote_ips: Vec<A>,
) -> Self {
Self::with_state(FakeDualStackIpSocketCtx::new([FakeDeviceConfig {
device: FakeDeviceId,
local_ips,
remote_ips,
}]))
}
}
impl<Outer: Default, D: FakeStrongDeviceId> Wrapped<Outer, FakeUdpInnerCoreCtx<D>> {
fn with_state(state: FakeDualStackIpSocketCtx<D, FakeUdpBindingsCtx<D>>) -> Self {
Wrapped {
outer: Outer::default(),
inner: WrappedFakeCoreCtx::with_inner_and_outer_state(state, Default::default()),
}
}
}
/// `FakeCtx` specialized for UDP.
type FakeUdpCtx<D> = FakeCtxWithCoreCtx<FakeUdpCoreCtx<D>, (), (), FakeBindingsCtxState<D>>;
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
struct FakeBoundSockets<D: device::StrongId> {
v4: BoundSockets<Ipv4, D::Weak, FakeUdpBindingsCtx<D>>,
v6: BoundSockets<Ipv6, D::Weak, FakeUdpBindingsCtx<D>>,
}
impl<D: device::StrongId, I: IpExt> AsRef<BoundSockets<I, D::Weak, FakeUdpBindingsCtx<D>>>
for FakeBoundSockets<D>
{
fn as_ref(&self) -> &BoundSockets<I, D::Weak, FakeUdpBindingsCtx<D>> {
I::map_ip(
IpInvariant(self),
|IpInvariant(state)| &state.v4,
|IpInvariant(state)| &state.v6,
)
}
}
impl<D: device::StrongId, I: IpExt> AsMut<BoundSockets<I, D::Weak, FakeUdpBindingsCtx<D>>>
for FakeBoundSockets<D>
{
fn as_mut(&mut self) -> &mut BoundSockets<I, D::Weak, FakeUdpBindingsCtx<D>> {
I::map_ip(
IpInvariant(self),
|IpInvariant(state)| &mut state.v4,
|IpInvariant(state)| &mut state.v6,
)
}
}
type FakeUdpInnerCoreCtx<D> = Wrapped<FakeBoundSockets<D>, FakeBufferCoreCtx<D>>;
/// `FakeBindingsCtx` specialized for UDP.
type FakeUdpBindingsCtx<D> = FakeBindingsCtx<(), (), FakeBindingsCtxState<D>, ()>;
/// The FakeCoreCtx held as the inner state of the [`WrappedFakeCoreCtx`] that
/// is [`FakeUdpCoreCtx`].
type FakeBufferCoreCtx<D> = FakeCoreCtx<
FakeDualStackIpSocketCtx<D, FakeUdpBindingsCtx<D>>,
DualStackSendIpPacketMeta<D>,
D,
>;
type UdpFakeDeviceCtx = FakeUdpCtx<FakeDeviceId>;
type UdpFakeDeviceCoreCtx = FakeUdpCoreCtx<FakeDeviceId>;
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
struct FakeBindingsCtxState<D: device::StrongId> {
received_v4:
HashMap<WeakUdpSocketId<Ipv4, D::Weak, FakeUdpBindingsCtx<D>>, SocketReceived<Ipv4>>,
received_v6:
HashMap<WeakUdpSocketId<Ipv6, D::Weak, FakeUdpBindingsCtx<D>>, SocketReceived<Ipv6>>,
}
impl<D: device::StrongId> FakeBindingsCtxState<D> {
fn received<I: TestIpExt>(
&self,
) -> &HashMap<WeakUdpSocketId<I, D::Weak, FakeUdpBindingsCtx<D>>, SocketReceived<I>>
{
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Wrap<'a, I: TestIpExt, D: device::WeakId, BT: UdpBindingsTypes>(
&'a HashMap<WeakUdpSocketId<I, D, BT>, SocketReceived<I>>,
);
let Wrap(map) = I::map_ip(
IpInvariant(self),
|IpInvariant(state)| Wrap(&state.received_v4),
|IpInvariant(state)| Wrap(&state.received_v6),
);
map
}
fn received_mut<I: IpExt>(
&mut self,
) -> &mut HashMap<WeakUdpSocketId<I, D::Weak, FakeUdpBindingsCtx<D>>, SocketReceived<I>>
{
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Wrap<'a, I: IpExt, D: device::WeakId, BT: UdpBindingsTypes>(
&'a mut HashMap<WeakUdpSocketId<I, D, BT>, SocketReceived<I>>,
);
let Wrap(map) = I::map_ip(
IpInvariant(self),
|IpInvariant(state)| Wrap(&mut state.received_v4),
|IpInvariant(state)| Wrap(&mut state.received_v6),
);
map
}
fn socket_data<I: TestIpExt>(
&self,
) -> HashMap<WeakUdpSocketId<I, D::Weak, FakeUdpBindingsCtx<D>>, Vec<&'_ [u8]>> {
self.received::<I>()
.iter()
.map(|(id, SocketReceived { packets })| {
(
id.clone(),
packets.iter().map(|ReceivedPacket { addr: _, body }| &body[..]).collect(),
)
})
.collect()
}
}
impl<I: IpExt, D: device::StrongId> UdpReceiveBindingsContext<I, D> for FakeUdpBindingsCtx<D> {
fn receive_udp<B: BufferMut>(
&mut self,
id: &UdpSocketId<I, D::Weak, Self>,
_device: &D,
(dst_ip, _dst_port): (I::Addr, NonZeroU16),
(src_ip, src_port): (I::Addr, Option<NonZeroU16>),
body: &B,
) {
self.state_mut().received_mut::<I>().entry(id.downgrade()).or_default().packets.push(
ReceivedPacket {
addr: ReceivedPacketAddrs { src_ip, dst_ip, src_port },
body: body.as_ref().to_owned(),
},
)
}
}
impl<D: device::StrongId> UdpBindingsTypes for FakeUdpBindingsCtx<D> {
type ExternalData<I: Ip> = ();
}
/// Utilities for accessing locked internal state in tests.
impl<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes> UdpSocketId<I, D, BT> {
fn get(&self) -> impl Deref<Target = UdpSocketState<I, D, BT>> + '_ {
self.state_for_locking().read()
}
fn get_mut(&self) -> impl DerefMut<Target = UdpSocketState<I, D, BT>> + '_ {
self.state_for_locking().write()
}
}
impl<
I: TestIpExt,
D: FakeFilterDeviceId<()>,
Outer: Borrow<UdpSocketSet<I, FakeWeakDeviceId<D>, FakeUdpBindingsCtx<D>>>
+ BorrowMut<UdpSocketSet<I, FakeWeakDeviceId<D>, FakeUdpBindingsCtx<D>>>,
> StateContext<I, FakeUdpBindingsCtx<D>> for Wrapped<Outer, FakeUdpInnerCoreCtx<D>>
{
type SocketStateCtx<'a> = FakeUdpInnerCoreCtx<D>;
fn with_all_sockets_mut<
O,
F: FnOnce(&mut UdpSocketSet<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>) -> O,
>(
&mut self,
cb: F,
) -> O {
cb(self.outer.borrow_mut())
}
fn with_all_sockets<
O,
F: FnOnce(&UdpSocketSet<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>) -> O,
>(
&mut self,
cb: F,
) -> O {
cb(self.outer.borrow())
}
fn with_socket_state<
O,
F: FnOnce(
&mut Self::SocketStateCtx<'_>,
&UdpSocketState<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
) -> O,
>(
&mut self,
id: &UdpSocketId<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
cb: F,
) -> O {
cb(&mut self.inner, &id.get())
}
fn with_socket_state_mut<
O,
F: FnOnce(
&mut Self::SocketStateCtx<'_>,
&mut UdpSocketState<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
) -> O,
>(
&mut self,
id: &UdpSocketId<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
cb: F,
) -> O {
cb(&mut self.inner, &mut id.get_mut())
}
fn with_bound_state_context<O, F: FnOnce(&mut Self::SocketStateCtx<'_>) -> O>(
&mut self,
cb: F,
) -> O {
let Self { outer: _, inner } = self;
cb(inner)
}
fn should_send_port_unreachable(&mut self) -> bool {
false
}
fn for_each_socket<
F: FnMut(
&mut Self::SocketStateCtx<'_>,
&UdpSocketId<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
&UdpSocketState<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
),
>(
&mut self,
mut cb: F,
) {
self.outer.borrow().keys().for_each(|id| {
let id = UdpSocketId::from(id.clone());
cb(&mut self.inner, &id, &id.get());
})
}
}
impl<I: TestIpExt, D: FakeFilterDeviceId<()>> BoundStateContext<I, FakeUdpBindingsCtx<D>>
for FakeUdpInnerCoreCtx<D>
{
type IpSocketsCtx<'a> = FakeBufferCoreCtx<D>;
type DualStackContext = I::UdpDualStackBoundStateContext<D>;
type NonDualStackContext = I::UdpNonDualStackBoundStateContext<D>;
fn with_bound_sockets<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&BoundSockets<I, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
) -> 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, FakeUdpBindingsCtx<D>>,
) -> 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> {
struct Wrap<'a, I: Ip + TestIpExt, D: FakeFilterDeviceId<()> + 'static>(
MaybeDualStack<
&'a mut I::UdpDualStackBoundStateContext<D>,
&'a mut I::UdpNonDualStackBoundStateContext<D>,
>,
);
// TODO(https://fxbug.dev/42082123): Replace this with a derived impl.
impl<'a, I: TestIpExt, NewIp: TestIpExt, D: FakeFilterDeviceId<()> + 'static>
GenericOverIp<NewIp> for Wrap<'a, I, D>
{
type Type = Wrap<'a, NewIp, D>;
}
let Wrap(context) = I::map_ip(
IpInvariant(self),
|IpInvariant(this)| Wrap(MaybeDualStack::NotDualStack(this)),
|IpInvariant(this)| Wrap(MaybeDualStack::DualStack(this)),
);
context
}
}
impl<D: FakeStrongDeviceId + 'static> UdpStateContext for FakeUdpInnerCoreCtx<D> {}
impl<D: FakeStrongDeviceId> NonDualStackBoundStateContext<Ipv4, FakeUdpBindingsCtx<D>>
for FakeUdpInnerCoreCtx<D>
{
}
impl<D: FakeFilterDeviceId<()>> DualStackBoundStateContext<Ipv6, FakeUdpBindingsCtx<D>>
for FakeUdpInnerCoreCtx<D>
{
type IpSocketsCtx<'a> = FakeBufferCoreCtx<D>;
fn with_both_bound_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut BoundSockets<Ipv6, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
&mut BoundSockets<Ipv4, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
let Self { inner, outer: FakeBoundSockets { v4, v6 } } = self;
cb(inner, v6, v4)
}
fn with_other_bound_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut BoundSockets<Ipv4, Self::WeakDeviceId, FakeUdpBindingsCtx<D>>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
DualStackBoundStateContext::with_both_bound_sockets_mut(
self,
|core_ctx, _bound, other_bound| cb(core_ctx, other_bound),
)
}
fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>(
&mut self,
cb: F,
) -> O {
let Self { inner, outer: _ } = self;
cb(inner)
}
}
/// Ip packet delivery for the [`FakeUdpCoreCtx`].
impl<I: IpExt + IpDeviceStateIpExt + TestIpExt, D: FakeFilterDeviceId<()>>
IpTransportContext<I, FakeUdpBindingsCtx<D>, FakeUdpCoreCtx<D>> for UdpIpTransportContext
{
fn receive_icmp_error(
_core_ctx: &mut FakeUdpCoreCtx<D>,
_bindings_ctx: &mut FakeUdpBindingsCtx<D>,
_device: &D,
_original_src_ip: Option<SpecifiedAddr<I::Addr>>,
_original_dst_ip: SpecifiedAddr<I::Addr>,
_original_udp_packet: &[u8],
_err: I::ErrorCode,
) {
unimplemented!()
}
fn receive_ip_packet<B: BufferMut>(
core_ctx: &mut FakeUdpCoreCtx<D>,
bindings_ctx: &mut FakeUdpBindingsCtx<D>,
device: &D,
src_ip: I::RecvSrcAddr,
dst_ip: SpecifiedAddr<I::Addr>,
buffer: B,
) -> Result<(), (B, TransportReceiveError)> {
// NB: The compiler can't deduce that `I::OtherVersion`` implements
// `TestIpExt`, so use `map_ip` to transform the associated types
// into concrete types (`Ipv4` & `Ipv6`).
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct SrcWrapper<I: IpExt>(I::RecvSrcAddr);
let IpInvariant(result) = net_types::map_ip_twice!(
I,
(IpInvariant((core_ctx, bindings_ctx, device, buffer)), SrcWrapper(src_ip), dst_ip),
|(
IpInvariant((core_ctx, bindings_ctx, device, buffer)),
SrcWrapper(src_ip),
dst_ip,
)| {
IpInvariant(receive_ip_packet::<I, _, _, _>(
core_ctx,
bindings_ctx,
device,
src_ip,
dst_ip,
buffer,
))
}
);
result
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
struct DualStackSocketsState<D: device::StrongId> {
v4: UdpSocketSet<Ipv4, D::Weak, FakeUdpBindingsCtx<D>>,
v6: UdpSocketSet<Ipv6, D::Weak, FakeUdpBindingsCtx<D>>,
udpv4_counters: UdpCounters<Ipv4>,
udpv6_counters: UdpCounters<Ipv6>,
}
impl<I: IpExt, D: device::StrongId> Borrow<UdpSocketSet<I, D::Weak, FakeUdpBindingsCtx<D>>>
for DualStackSocketsState<D>
{
fn borrow(&self) -> &UdpSocketSet<I, D::Weak, FakeUdpBindingsCtx<D>> {
I::map_ip(IpInvariant(self), |IpInvariant(dual)| &dual.v4, |IpInvariant(dual)| &dual.v6)
}
}
impl<I: IpExt, D: device::StrongId> BorrowMut<UdpSocketSet<I, D::Weak, FakeUdpBindingsCtx<D>>>
for DualStackSocketsState<D>
{
fn borrow_mut(&mut self) -> &mut UdpSocketSet<I, D::Weak, FakeUdpBindingsCtx<D>> {
I::map_ip(
IpInvariant(self),
|IpInvariant(dual)| &mut dual.v4,
|IpInvariant(dual)| &mut dual.v6,
)
}
}
impl<D: device::StrongId> DualStackSocketsState<D> {
fn udp_counters<I: Ip>(&self) -> &UdpCounters<I> {
I::map_ip(
IpInvariant(self),
|IpInvariant(dual)| &dual.udpv4_counters,
|IpInvariant(dual)| &dual.udpv6_counters,
)
}
}
type FakeUdpCoreCtx<D> = Wrapped<DualStackSocketsState<D>, FakeUdpInnerCoreCtx<D>>;
impl<I: Ip, D: FakeStrongDeviceId> CounterContext<UdpCounters<I>> for FakeUdpCoreCtx<D> {
fn with_counters<O, F: FnOnce(&UdpCounters<I>) -> O>(&self, cb: F) -> O {
cb(&self.outer.udp_counters())
}
}
fn local_ip<I: TestIpExt>() -> SpecifiedAddr<I::Addr> {
I::get_other_ip_address(1)
}
fn remote_ip<I: TestIpExt>() -> SpecifiedAddr<I::Addr> {
I::get_other_ip_address(2)
}
trait TestIpExt: crate::testutil::TestIpExt + IpExt + IpDeviceStateIpExt {
type UdpDualStackBoundStateContext<D: FakeFilterDeviceId<()> + 'static>:
DualStackDatagramBoundStateContext<Self, FakeUdpBindingsCtx<D>, Udp<FakeUdpBindingsCtx<D>>, DeviceId=D, WeakDeviceId=D::Weak>;
type UdpNonDualStackBoundStateContext<D: FakeFilterDeviceId<()> + 'static>:
NonDualStackDatagramBoundStateContext<Self, FakeUdpBindingsCtx<D>, Udp<FakeUdpBindingsCtx<D>>, DeviceId=D, WeakDeviceId=D::Weak>;
fn try_into_recv_src_addr(addr: Self::Addr) -> Option<Self::RecvSrcAddr>;
}
impl TestIpExt for Ipv4 {
type UdpDualStackBoundStateContext<D: FakeFilterDeviceId<()> + 'static> =
UninstantiableWrapper<FakeUdpInnerCoreCtx<D>>;
type UdpNonDualStackBoundStateContext<D: FakeFilterDeviceId<()> + 'static> =
FakeUdpInnerCoreCtx<D>;
fn try_into_recv_src_addr(addr: Ipv4Addr) -> Option<Ipv4Addr> {
Some(addr)
}
}
impl TestIpExt for Ipv6 {
type UdpDualStackBoundStateContext<D: FakeFilterDeviceId<()> + 'static> =
FakeUdpInnerCoreCtx<D>;
type UdpNonDualStackBoundStateContext<D: FakeFilterDeviceId<()> + 'static> =
UninstantiableWrapper<FakeUdpInnerCoreCtx<D>>;
fn try_into_recv_src_addr(addr: Ipv6Addr) -> Option<Ipv6SourceAddr> {
Ipv6SourceAddr::new(addr)
}
}
/// Helper function to inject an UDP packet with the provided parameters.
fn receive_udp_packet<
A: IpAddress,
D: FakeStrongDeviceId,
CC: DeviceIdContext<AnyDevice, DeviceId = D>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut FakeUdpBindingsCtx<D>,
device: D,
src_ip: A,
dst_ip: A,
src_port: impl Into<u16>,
dst_port: NonZeroU16,
body: &[u8],
) where
A::Version: TestIpExt,
UdpIpTransportContext: IpTransportContext<A::Version, FakeUdpBindingsCtx<D>, CC>,
{
let builder =
UdpPacketBuilder::new(src_ip, dst_ip, NonZeroU16::new(src_port.into()), dst_port);
let buffer = Buf::new(body.to_owned(), ..)
.encapsulate(builder)
.serialize_vec_outer()
.unwrap()
.into_inner();
<UdpIpTransportContext as IpTransportContext<A::Version, _, _>>::receive_ip_packet(
core_ctx,
bindings_ctx,
&device,
<A::Version as TestIpExt>::try_into_recv_src_addr(src_ip).unwrap(),
SpecifiedAddr::new(dst_ip).unwrap(),
buffer,
)
.expect("Receive IP packet succeeds");
}
const LOCAL_PORT: NonZeroU16 = const_unwrap_option(NonZeroU16::new(100));
const OTHER_LOCAL_PORT: NonZeroU16 = const_unwrap_option(LOCAL_PORT.checked_add(1));
const REMOTE_PORT: NonZeroU16 = const_unwrap_option(NonZeroU16::new(200));
const OTHER_REMOTE_PORT: NonZeroU16 = const_unwrap_option(REMOTE_PORT.checked_add(1));
fn conn_addr<I>(
device: Option<FakeWeakDeviceId<FakeDeviceId>>,
) -> AddrVec<I, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec>
where
I: Ip + TestIpExt,
{
let local_ip = SocketIpAddr::try_from(local_ip::<I>()).unwrap();
let remote_ip = SocketIpAddr::try_from(remote_ip::<I>()).unwrap();
ConnAddr {
ip: ConnIpAddr {
local: (local_ip, LOCAL_PORT),
remote: (remote_ip, REMOTE_PORT.into()),
},
device,
}
.into()
}
fn local_listener<I>(
device: Option<FakeWeakDeviceId<FakeDeviceId>>,
) -> AddrVec<I, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec>
where
I: Ip + TestIpExt,
{
let local_ip = SocketIpAddr::try_from(local_ip::<I>()).unwrap();
ListenerAddr { ip: ListenerIpAddr { identifier: LOCAL_PORT, addr: Some(local_ip) }, device }
.into()
}
fn wildcard_listener<I>(
device: Option<FakeWeakDeviceId<FakeDeviceId>>,
) -> AddrVec<I, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec>
where
I: Ip + TestIpExt,
{
ListenerAddr { ip: ListenerIpAddr { identifier: LOCAL_PORT, addr: None }, device }.into()
}
#[ip_test]
#[test_case(conn_addr(Some(FakeWeakDeviceId(FakeDeviceId))), [
conn_addr(None), local_listener(Some(FakeWeakDeviceId(FakeDeviceId))), local_listener(None),
wildcard_listener(Some(FakeWeakDeviceId(FakeDeviceId))), wildcard_listener(None)
]; "conn with device")]
#[test_case(local_listener(Some(FakeWeakDeviceId(FakeDeviceId))),
[local_listener(None), wildcard_listener(Some(FakeWeakDeviceId(FakeDeviceId))), wildcard_listener(None)];
"local listener with device")]
#[test_case(wildcard_listener(Some(FakeWeakDeviceId(FakeDeviceId))), [wildcard_listener(None)];
"wildcard listener with device")]
#[test_case(conn_addr(None), [local_listener(None), wildcard_listener(None)]; "conn no device")]
#[test_case(local_listener(None), [wildcard_listener(None)]; "local listener no device")]
#[test_case(wildcard_listener(None), []; "wildcard listener no device")]
fn test_udp_addr_vec_iter_shadows_conn<I: Ip + IpExt, D: device::WeakId, const N: usize>(
addr: AddrVec<I, D, UdpAddrSpec>,
expected_shadows: [AddrVec<I, D, UdpAddrSpec>; N],
) {
assert_eq!(addr.iter_shadows().collect::<HashSet<_>>(), HashSet::from(expected_shadows));
}
#[ip_test]
fn test_iter_receiving_addrs<I: Ip + TestIpExt>() {
let addr = ConnIpAddr {
local: (SocketIpAddr::try_from(local_ip::<I>()).unwrap(), LOCAL_PORT),
remote: (SocketIpAddr::try_from(remote_ip::<I>()).unwrap(), REMOTE_PORT.into()),
};
assert_eq!(
iter_receiving_addrs::<I, _>(addr, FakeWeakDeviceId(FakeDeviceId)).collect::<Vec<_>>(),
vec![
// A socket connected on exactly the receiving vector has precedence.
conn_addr(Some(FakeWeakDeviceId(FakeDeviceId))),
// Connected takes precedence over listening with device match.
conn_addr(None),
local_listener(Some(FakeWeakDeviceId(FakeDeviceId))),
// Specific IP takes precedence over device match.
local_listener(None),
wildcard_listener(Some(FakeWeakDeviceId(FakeDeviceId))),
// Fallback to least specific
wildcard_listener(None)
]
);
}
/// Tests UDP listeners over different IP versions.
///
/// Tests that a listener can be created, that the context receives packet
/// notifications for that listener, and that we can send data using that
/// listener.
#[ip_test]
fn test_listen_udp<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let socket = api.create();
// Create a listener on the local port, bound to the local IP:
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen_udp failed");
// Inject a packet and check that the context receives it:
let body = [1, 2, 3, 4, 5];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip.get(),
local_ip.get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
assert_eq!(
bindings_ctx.state().received::<I>(),
&HashMap::from([(
socket.downgrade(),
SocketReceived {
packets: vec![ReceivedPacket {
body: body.into(),
addr: ReceivedPacketAddrs {
src_ip: remote_ip.get(),
dst_ip: local_ip.get(),
src_port: Some(REMOTE_PORT),
},
}]
}
)])
);
// Send a packet providing a local ip:
api.send_to(
&socket,
Some(ZonedAddr::Unzoned(remote_ip)),
REMOTE_PORT.into(),
Buf::new(body.to_vec(), ..),
)
.expect("send_to suceeded");
// And send a packet that doesn't:
api.send_to(
&socket,
Some(ZonedAddr::Unzoned(remote_ip)),
REMOTE_PORT.into(),
Buf::new(body.to_vec(), ..),
)
.expect("send_to succeeded");
let frames = api.core_ctx().inner.inner.frames();
assert_eq!(frames.len(), 2);
let check_frame =
|(meta, frame_body): &(DualStackSendIpPacketMeta<FakeDeviceId>, Vec<u8>)| {
let SendIpPacketMeta {
device: _,
src_ip,
dst_ip,
broadcast: _,
next_hop,
proto,
ttl: _,
mtu: _,
} = meta.try_as::<I>().unwrap();
assert_eq!(next_hop, &remote_ip);
assert_eq!(src_ip, &local_ip);
assert_eq!(dst_ip, &remote_ip);
assert_eq!(proto, &IpProto::Udp.into());
let mut buf = &frame_body[..];
let udp_packet =
UdpPacket::parse(&mut buf, UdpParseArgs::new(src_ip.get(), dst_ip.get()))
.expect("Parsed sent UDP packet");
assert_eq!(udp_packet.src_port().unwrap(), LOCAL_PORT);
assert_eq!(udp_packet.dst_port(), REMOTE_PORT);
assert_eq!(udp_packet.body(), &body[..]);
};
check_frame(&frames[0]);
check_frame(&frames[1]);
}
/// Tests that UDP packets without a connection are dropped.
///
/// Tests that receiving a UDP packet on a port over which there isn't a
/// listener causes the packet to be dropped correctly.
#[ip_test]
fn test_udp_drop<I: Ip + TestIpExt>() {
set_logger_for_test();
let UdpFakeDeviceCtx { mut core_ctx, mut bindings_ctx } =
UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let body = [1, 2, 3, 4, 5];
receive_udp_packet(
&mut core_ctx,
&mut bindings_ctx,
FakeDeviceId,
remote_ip.get(),
local_ip.get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
assert_eq!(&bindings_ctx.state().socket_data::<I>(), &HashMap::new());
}
/// Tests that UDP connections can be created and data can be transmitted
/// over it.
///
/// Only tests with specified local port and address bounds.
#[ip_test]
fn test_udp_conn_basic<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let socket = api.create();
// Create a UDP connection with a specified local port and local IP.
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen_udp failed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect failed");
// Inject a UDP packet and see if we receive it on the context.
let body = [1, 2, 3, 4, 5];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip.get(),
local_ip.get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([(socket.downgrade(), vec![&body[..]])])
);
// Now try to send something over this new connection.
api.send(&socket, Buf::new(body.to_vec(), ..)).expect("send_udp_conn returned an error");
let (meta, frame_body) =
assert_matches!(api.core_ctx().inner.inner.frames(), [frame] => frame);
// Check first frame.
let SendIpPacketMeta {
device: _,
src_ip,
dst_ip,
broadcast: _,
next_hop,
proto,
ttl: _,
mtu: _,
} = meta.try_as::<I>().unwrap();
assert_eq!(next_hop, &remote_ip);
assert_eq!(src_ip, &local_ip);
assert_eq!(dst_ip, &remote_ip);
assert_eq!(proto, &IpProto::Udp.into());
let mut buf = &frame_body[..];
let udp_packet = UdpPacket::parse(&mut buf, UdpParseArgs::new(src_ip.get(), dst_ip.get()))
.expect("Parsed sent UDP packet");
assert_eq!(udp_packet.src_port().unwrap(), LOCAL_PORT);
assert_eq!(udp_packet.dst_port(), REMOTE_PORT);
assert_eq!(udp_packet.body(), &body[..]);
}
/// Tests that UDP connections fail with an appropriate error for
/// non-routable remote addresses.
#[ip_test]
fn test_udp_conn_unroutable<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
// Set fake context callback to treat all addresses as unroutable.
let remote_ip = I::get_other_ip_address(127);
// Create a UDP connection with a specified local port and local IP.
let unbound = api.create();
let conn_err = api
.connect(&unbound, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.unwrap_err();
assert_eq!(conn_err, ConnectError::Ip(ResolveRouteError::Unreachable.into()));
}
/// Tests that UDP listener creation fails with an appropriate error when
/// local address is non-local.
#[ip_test]
fn test_udp_conn_cannot_bind<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
// Use remote address to trigger IpSockCreationError::LocalAddrNotAssigned.
let remote_ip = remote_ip::<I>();
// Create a UDP listener with a specified local port and local ip:
let unbound = api.create();
let result = api.listen(&unbound, Some(ZonedAddr::Unzoned(remote_ip)), Some(LOCAL_PORT));
assert_eq!(result, Err(Either::Right(LocalAddressError::CannotBindToAddress)));
}
#[test]
fn test_udp_conn_picks_link_local_source_address() {
set_logger_for_test();
// When the remote address has global scope but the source address
// is link-local, make sure that the socket implicitly has its bound
// device set.
set_logger_for_test();
let local_ip = SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap();
let remote_ip = SpecifiedAddr::new(net_ip_v6!("1:2:3:4::")).unwrap();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(
UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(vec![local_ip], vec![remote_ip]),
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("can connect");
let info = api.get_info(&socket);
let (conn_local_ip, conn_remote_ip) = assert_matches!(
info,
SocketInfo::Connected(datagram::ConnInfo {
local_ip: conn_local_ip,
remote_ip: conn_remote_ip,
local_identifier: _,
remote_identifier: _,
}) => (conn_local_ip, conn_remote_ip)
);
assert_eq!(
conn_local_ip,
StrictlyZonedAddr::new_with_zone(local_ip, || FakeWeakDeviceId(FakeDeviceId)),
);
assert_eq!(conn_remote_ip, StrictlyZonedAddr::new_unzoned_or_panic(remote_ip));
// Double-check that the bound device can't be changed after being set
// implicitly.
assert_eq!(
api.set_device(&socket, None),
Err(SocketError::Local(LocalAddressError::Zone(ZonedAddressError::DeviceZoneMismatch)))
);
}
fn set_device_removed<I: TestIpExt>(core_ctx: &mut UdpFakeDeviceCoreCtx, device_removed: bool) {
let core_ctx: &mut FakeCoreCtx<_, _, _> = core_ctx.as_mut();
let core_ctx: &mut FakeIpDeviceIdCtx<_> = core_ctx.get_mut().as_mut();
core_ctx.set_device_removed(FakeDeviceId, device_removed);
}
#[ip_test]
#[test_case(
true,
Err(IpSockCreationError::Route(ResolveRouteError::Unreachable).into()); "remove device")]
#[test_case(false, Ok(()); "dont remove device")]
fn test_udp_conn_device_removed<I: Ip + TestIpExt>(
remove_device: bool,
expected: Result<(), ConnectError>,
) {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
api.set_device(&unbound, Some(&FakeDeviceId)).unwrap();
if remove_device {
set_device_removed::<I>(api.core_ctx(), remove_device);
}
let remote_ip = remote_ip::<I>();
assert_eq!(
api.connect(&unbound, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into()),
expected,
);
}
/// Tests that UDP connections fail with an appropriate error when local
/// ports are exhausted.
#[ip_test]
fn test_udp_conn_exhausted<I: Ip + TestIpExt>() {
// NB: We don't enable logging for this test because it's very spammy.
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
// Exhaust local ports to trigger FailedToAllocateLocalPort error.
for port_num in FakeBoundSocketMap::<I>::EPHEMERAL_RANGE {
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), NonZeroU16::new(port_num))
.unwrap();
}
let remote_ip = remote_ip::<I>();
let unbound = api.create();
let conn_err = api
.connect(&unbound, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.unwrap_err();
assert_eq!(conn_err, ConnectError::CouldNotAllocateLocalPort);
}
#[ip_test]
fn test_connect_success<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let multicast_addr = I::get_multicast_addr(3);
let socket = api.create();
// Set some properties on the socket that should be preserved.
api.set_posix_reuse_port(&socket, true).expect("is unbound");
api.set_multicast_membership(
&socket,
multicast_addr,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true,
)
.expect("join multicast group should succeed");
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("Initial call to listen_udp was expected to succeed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect should succeed");
// Check that socket options set on the listener are propagated to the
// connected socket.
assert!(api.get_posix_reuse_port(&socket));
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::from([(
(FakeDeviceId, multicast_addr),
const_unwrap_option(NonZeroUsize::new(1))
)])
);
assert_eq!(
api.set_multicast_membership(
&socket,
multicast_addr,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true
),
Err(SetMulticastMembershipError::GroupAlreadyJoined)
);
}
#[ip_test]
fn test_connect_fails<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let remote_ip = I::get_other_ip_address(127);
let multicast_addr = I::get_multicast_addr(3);
let socket = api.create();
// Set some properties on the socket that should be preserved.
api.set_posix_reuse_port(&socket, true).expect("is unbound");
api.set_multicast_membership(
&socket,
multicast_addr,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true,
)
.expect("join multicast group should succeed");
// Create a UDP connection with a specified local port and local IP.
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("Initial call to listen_udp was expected to succeed");
assert_matches!(
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into()),
Err(ConnectError::Ip(IpSockCreationError::Route(ResolveRouteError::Unreachable)))
);
// Check that the listener was unchanged by the failed connection.
assert!(api.get_posix_reuse_port(&socket));
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::from([(
(FakeDeviceId, multicast_addr),
const_unwrap_option(NonZeroUsize::new(1))
)])
);
assert_eq!(
api.set_multicast_membership(
&socket,
multicast_addr,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true
),
Err(SetMulticastMembershipError::GroupAlreadyJoined)
);
}
#[ip_test]
fn test_reconnect_udp_conn_success<I: Ip + TestIpExt>() {
set_logger_for_test();
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let other_remote_ip = I::get_other_ip_address(3);
let mut ctx =
UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(
vec![local_ip],
vec![remote_ip, other_remote_ip],
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen should succeed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect was expected to succeed");
api.connect(&socket, Some(ZonedAddr::Unzoned(other_remote_ip)), OTHER_REMOTE_PORT.into())
.expect("connect should succeed");
assert_eq!(
api.get_info(&socket),
SocketInfo::Connected(datagram::ConnInfo {
local_ip: StrictlyZonedAddr::new_unzoned_or_panic(local_ip),
local_identifier: LOCAL_PORT,
remote_ip: StrictlyZonedAddr::new_unzoned_or_panic(other_remote_ip),
remote_identifier: OTHER_REMOTE_PORT.into(),
})
);
}
#[ip_test]
fn test_reconnect_udp_conn_fails<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let other_remote_ip = I::get_other_ip_address(3);
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen should succeed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect was expected to succeed");
let error = api
.connect(&socket, Some(ZonedAddr::Unzoned(other_remote_ip)), OTHER_REMOTE_PORT.into())
.expect_err("connect should fail");
assert_matches!(
error,
ConnectError::Ip(IpSockCreationError::Route(ResolveRouteError::Unreachable))
);
assert_eq!(
api.get_info(&socket),
SocketInfo::Connected(datagram::ConnInfo {
local_ip: StrictlyZonedAddr::new_unzoned_or_panic(local_ip),
local_identifier: LOCAL_PORT,
remote_ip: StrictlyZonedAddr::new_unzoned_or_panic(remote_ip),
remote_identifier: REMOTE_PORT.into()
})
);
}
#[ip_test]
fn test_send_to<I: Ip + TestIpExt>() {
set_logger_for_test();
let local_ip = local_ip::<I>();
let remote_ip = remote_ip::<I>();
let other_remote_ip = I::get_other_ip_address(3);
let mut ctx =
UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(
vec![local_ip],
vec![remote_ip, other_remote_ip],
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen should succeed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect should succeed");
let body = [1, 2, 3, 4, 5];
// Try to send something with send_to
api.send_to(
&socket,
Some(ZonedAddr::Unzoned(other_remote_ip)),
REMOTE_PORT.into(),
Buf::new(body.to_vec(), ..),
)
.expect("send_to failed");
// The socket should not have been affected.
let info = api.get_info(&socket);
let info = assert_matches!(info, SocketInfo::Connected(info) => info);
assert_eq!(info.local_ip.into_inner(), ZonedAddr::Unzoned(local_ip));
assert_eq!(info.remote_ip.into_inner(), ZonedAddr::Unzoned(remote_ip));
assert_eq!(info.remote_identifier, u16::from(REMOTE_PORT));
// Check first frame.
let (meta, frame_body) =
assert_matches!(api.core_ctx().inner.inner.frames(), [frame] => frame);
let SendIpPacketMeta {
device: _,
src_ip,
dst_ip,
broadcast: _,
next_hop,
proto,
ttl: _,
mtu: _,
} = meta.try_as::<I>().unwrap();
assert_eq!(next_hop, &other_remote_ip);
assert_eq!(src_ip, &local_ip);
assert_eq!(dst_ip, &other_remote_ip);
assert_eq!(proto, &I::Proto::from(IpProto::Udp));
let mut buf = &frame_body[..];
let udp_packet = UdpPacket::parse(&mut buf, UdpParseArgs::new(src_ip.get(), dst_ip.get()))
.expect("Parsed sent UDP packet");
assert_eq!(udp_packet.src_port().unwrap(), LOCAL_PORT);
assert_eq!(udp_packet.dst_port(), REMOTE_PORT);
assert_eq!(udp_packet.body(), &body[..]);
}
/// Tests that UDP send failures are propagated as errors.
///
/// Only tests with specified local port and address bounds.
#[ip_test]
fn test_send_udp_conn_failure<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let remote_ip = remote_ip::<I>();
// Create a UDP connection with a specified local port and local IP.
let socket = api.create();
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect failed");
// Instruct the fake frame context to throw errors.
let frames: &mut FakeFrameCtx<_> = api.core_ctx().as_mut();
frames.set_should_error_for_frame(|_frame_meta| true);
// Now try to send something over this new connection:
let send_err = api.send(&socket, Buf::new(Vec::new(), ..)).unwrap_err();
assert_eq!(send_err, Either::Left(SendError::IpSock(IpSockSendError::Mtu)));
}
#[ip_test]
fn test_send_udp_conn_device_removed<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let remote_ip = remote_ip::<I>();
let socket = api.create();
api.set_device(&socket, Some(&FakeDeviceId)).unwrap();
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect failed");
for (device_removed, expected_res) in [
(false, Ok(())),
(
true,
Err(Either::Left(SendError::IpSock(IpSockSendError::Unroutable(
ResolveRouteError::Unreachable,
)))),
),
] {
set_device_removed::<I>(api.core_ctx(), device_removed);
assert_eq!(api.send(&socket, Buf::new(Vec::new(), ..)), expected_res)
}
}
#[ip_test]
#[test_case(false, ShutdownType::Send; "shutdown send then send")]
#[test_case(false, ShutdownType::SendAndReceive; "shutdown both then send")]
#[test_case(true, ShutdownType::Send; "shutdown send then sendto")]
#[test_case(true, ShutdownType::SendAndReceive; "shutdown both then sendto")]
fn test_send_udp_after_shutdown<I: Ip + TestIpExt>(send_to: bool, shutdown: ShutdownType) {
set_logger_for_test();
#[derive(Debug)]
struct NotWriteableError;
let send = |remote_ip, api: &mut UdpApi<_, _>, id| -> Result<(), NotWriteableError> {
match remote_ip {
Some(remote_ip) => api.send_to(
id,
Some(remote_ip),
REMOTE_PORT.into(),
Buf::new(Vec::new(), ..),
)
.map_err(
|e| assert_matches!(e, Either::Right(SendToError::NotWriteable) => NotWriteableError)
),
None => api.send(
id,
Buf::new(Vec::new(), ..),
)
.map_err(|e| assert_matches!(e, Either::Left(SendError::NotWriteable) => NotWriteableError)),
}
};
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let remote_ip = ZonedAddr::Unzoned(remote_ip::<I>());
let send_to_ip = send_to.then_some(remote_ip);
let socket = api.create();
api.connect(&socket, Some(remote_ip), REMOTE_PORT.into()).expect("connect failed");
send(send_to_ip, &mut api, &socket).expect("can send");
api.shutdown(&socket, shutdown).expect("is connected");
assert_matches!(send(send_to_ip, &mut api, &socket), Err(NotWriteableError));
}
#[ip_test]
#[test_case(ShutdownType::Receive; "receive")]
#[test_case(ShutdownType::SendAndReceive; "both")]
fn test_marked_for_receive_shutdown<I: Ip + TestIpExt>(which: ShutdownType) {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip::<I>())), Some(LOCAL_PORT))
.expect("can bind");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip::<I>())), REMOTE_PORT.into())
.expect("can connect");
// Receive once, then set the shutdown flag, then receive again and
// check that it doesn't get to the socket.
let packet = [1, 1, 1, 1];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip::<I>().get(),
local_ip::<I>().get(),
REMOTE_PORT,
LOCAL_PORT,
&packet[..],
);
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([(socket.downgrade(), vec![&packet[..]])])
);
api.shutdown(&socket, which).expect("is connected");
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip::<I>().get(),
local_ip::<I>().get(),
REMOTE_PORT,
LOCAL_PORT,
&packet[..],
);
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([(socket.downgrade(), vec![&packet[..]])])
);
// Calling shutdown for the send direction doesn't change anything.
api.shutdown(&socket, ShutdownType::Send).expect("is connected");
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip::<I>().get(),
local_ip::<I>().get(),
REMOTE_PORT,
LOCAL_PORT,
&packet[..],
);
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([(socket.downgrade(), vec![&packet[..]])])
);
}
/// Tests that if we have multiple listeners and connections, demuxing the
/// flows is performed correctly.
#[ip_test]
fn test_udp_demux<I: Ip + TestIpExt>() {
set_logger_for_test();
let local_ip = local_ip::<I>();
let remote_ip_a = I::get_other_ip_address(70);
let remote_ip_b = I::get_other_ip_address(72);
let local_port_a = NonZeroU16::new(100).unwrap();
let local_port_b = NonZeroU16::new(101).unwrap();
let local_port_c = NonZeroU16::new(102).unwrap();
let local_port_d = NonZeroU16::new(103).unwrap();
let mut ctx =
UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(
vec![local_ip],
vec![remote_ip_a, remote_ip_b],
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
// Create some UDP connections and listeners:
// conn2 has just a remote addr different than conn1, which requires
// allowing them to share the local port.
let [conn1, conn2] = [remote_ip_a, remote_ip_b].map(|remote_ip| {
let socket = api.create();
api.set_posix_reuse_port(&socket, true).expect("is unbound");
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(local_port_d))
.expect("listen_udp failed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect failed");
socket
});
let list1 = api.create();
api.listen(&list1, Some(ZonedAddr::Unzoned(local_ip)), Some(local_port_a))
.expect("listen_udp failed");
let list2 = api.create();
api.listen(&list2, Some(ZonedAddr::Unzoned(local_ip)), Some(local_port_b))
.expect("listen_udp failed");
let wildcard_list = api.create();
api.listen(&wildcard_list, None, Some(local_port_c)).expect("listen_udp failed");
let mut expectations = HashMap::<WeakUdpSocketId<I, _, _>, SocketReceived<I>>::new();
// Now inject UDP packets that each of the created connections should
// receive.
let body_conn1 = [1, 1, 1, 1];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_a.get(),
local_ip.get(),
REMOTE_PORT,
local_port_d,
&body_conn1[..],
);
expectations.entry(conn1.downgrade()).or_default().packets.push(ReceivedPacket {
body: body_conn1.into(),
addr: ReceivedPacketAddrs {
src_ip: remote_ip_a.get(),
dst_ip: local_ip.get(),
src_port: Some(REMOTE_PORT),
},
});
assert_eq!(bindings_ctx.state().received(), &expectations);
let body_conn2 = [2, 2, 2, 2];
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_b.get(),
local_ip.get(),
REMOTE_PORT,
local_port_d,
&body_conn2[..],
);
expectations.entry(conn2.downgrade()).or_default().packets.push(ReceivedPacket {
addr: ReceivedPacketAddrs {
src_ip: remote_ip_b.get(),
dst_ip: local_ip.get(),
src_port: Some(REMOTE_PORT),
},
body: body_conn2.into(),
});
assert_eq!(bindings_ctx.state().received(), &expectations);
let body_list1 = [3, 3, 3, 3];
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_a.get(),
local_ip.get(),
REMOTE_PORT,
local_port_a,
&body_list1[..],
);
expectations.entry(list1.downgrade()).or_default().packets.push(ReceivedPacket {
addr: ReceivedPacketAddrs {
src_ip: remote_ip_a.get(),
dst_ip: local_ip.get(),
src_port: Some(REMOTE_PORT),
},
body: body_list1.into(),
});
assert_eq!(bindings_ctx.state().received(), &expectations);
let body_list2 = [4, 4, 4, 4];
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_a.get(),
local_ip.get(),
REMOTE_PORT,
local_port_b,
&body_list2[..],
);
expectations.entry(list2.downgrade()).or_default().packets.push(ReceivedPacket {
body: body_list2.into(),
addr: ReceivedPacketAddrs {
src_ip: remote_ip_a.get(),
dst_ip: local_ip.get(),
src_port: Some(REMOTE_PORT),
},
});
assert_eq!(bindings_ctx.state().received(), &expectations);
let body_wildcard_list = [5, 5, 5, 5];
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_a.get(),
local_ip.get(),
REMOTE_PORT,
local_port_c,
&body_wildcard_list[..],
);
expectations.entry(wildcard_list.downgrade()).or_default().packets.push(ReceivedPacket {
addr: ReceivedPacketAddrs {
src_ip: remote_ip_a.get(),
dst_ip: local_ip.get(),
src_port: Some(REMOTE_PORT),
},
body: body_wildcard_list.into(),
});
assert_eq!(bindings_ctx.state().received(), &expectations);
}
/// Tests UDP wildcard listeners for different IP versions.
#[ip_test]
fn test_wildcard_listeners<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip_a = I::get_other_ip_address(1);
let local_ip_b = I::get_other_ip_address(2);
let remote_ip_a = I::get_other_ip_address(70);
let remote_ip_b = I::get_other_ip_address(72);
let listener = api.create();
api.listen(&listener, None, Some(LOCAL_PORT)).expect("listen_udp failed");
let body = [1, 2, 3, 4, 5];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_a.get(),
local_ip_a.get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
// Receive into a different local IP.
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip_b.get(),
local_ip_b.get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
// Check that we received both packets for the listener.
assert_eq!(
bindings_ctx.state().received::<I>(),
&HashMap::from([(
listener.downgrade(),
SocketReceived {
packets: vec![
ReceivedPacket {
addr: ReceivedPacketAddrs {
src_ip: remote_ip_a.get(),
dst_ip: local_ip_a.get(),
src_port: Some(REMOTE_PORT),
},
body: body.into(),
},
ReceivedPacket {
addr: ReceivedPacketAddrs {
src_ip: remote_ip_b.get(),
dst_ip: local_ip_b.get(),
src_port: Some(REMOTE_PORT),
},
body: body.into()
}
]
}
)])
);
}
#[ip_test]
fn test_receive_source_port_zero_on_listener<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let listener = api.create();
api.listen(&listener, None, Some(LOCAL_PORT)).expect("listen_udp failed");
let body = [];
let (src_ip, src_port) = (I::FAKE_CONFIG.remote_ip.get(), 0u16);
let (dst_ip, dst_port) = (I::FAKE_CONFIG.local_ip.get(), LOCAL_PORT);
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
src_ip,
dst_ip,
src_port,
dst_port,
&body[..],
);
// Check that we received both packets for the listener.
assert_eq!(
bindings_ctx.state().received(),
&HashMap::from([(
listener.downgrade(),
SocketReceived {
packets: vec![ReceivedPacket {
body: vec![],
addr: ReceivedPacketAddrs { src_ip, dst_ip, src_port: None },
}],
}
)])
);
}
#[ip_test]
fn test_receive_source_addr_unspecified_on_listener<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let listener = api.create();
api.listen(&listener, None, Some(LOCAL_PORT)).expect("listen_udp failed");
let body = [];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
I::UNSPECIFIED_ADDRESS,
I::FAKE_CONFIG.local_ip.get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
// Check that we received the packet on the listener.
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([(listener.downgrade(), vec![&body[..]])])
);
}
#[ip_test]
#[test_case(const_unwrap_option(NonZeroU16::new(u16::MAX)), Ok(const_unwrap_option(NonZeroU16::new(u16::MAX))); "ephemeral available")]
#[test_case(const_unwrap_option(NonZeroU16::new(100)), Err(LocalAddressError::FailedToAllocateLocalPort);
"no ephemeral available")]
fn test_bind_picked_port_all_others_taken<I: Ip + TestIpExt>(
available_port: NonZeroU16,
expected_result: Result<NonZeroU16, LocalAddressError>,
) {
// NB: We don't enable logging for this test because it's very spammy.
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
for port in 1..=u16::MAX {
let port = NonZeroU16::new(port).unwrap();
if port == available_port {
continue;
}
let unbound = api.create();
api.listen(&unbound, None, Some(port)).expect("uncontested bind");
}
// Now that all but the LOCAL_PORT are occupied, ask the stack to
// select a port.
let socket = api.create();
let result = api
.listen(&socket, None, None)
.map(|()| {
let info = api.get_info(&socket);
assert_matches!(info, SocketInfo::Listener(info) => info.local_identifier)
})
.map_err(Either::unwrap_right);
assert_eq!(result, expected_result);
}
#[ip_test]
fn test_receive_multicast_packet<I: Ip + TestIpExt>() {
set_logger_for_test();
let local_ip = local_ip::<I>();
let remote_ip = I::get_other_ip_address(70);
let multicast_addr = I::get_multicast_addr(0);
let multicast_addr_other = I::get_multicast_addr(1);
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(
UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(vec![local_ip], vec![remote_ip]),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
// Create 3 sockets: one listener for all IPs, two listeners on the same
// local address.
let any_listener = {
let socket = api.create();
api.set_posix_reuse_port(&socket, true).expect("is unbound");
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen_udp failed");
socket
};
let specific_listeners = [(); 2].map(|()| {
let socket = api.create();
api.set_posix_reuse_port(&socket, true).expect("is unbound");
api.listen(
&socket,
Some(ZonedAddr::Unzoned(multicast_addr.into_specified())),
Some(LOCAL_PORT),
)
.expect("listen_udp failed");
socket
});
let (core_ctx, bindings_ctx) = api.contexts();
let mut receive_packet = |body, local_ip: MulticastAddr<I::Addr>| {
let body = [body];
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
remote_ip.get(),
local_ip.get(),
REMOTE_PORT,
LOCAL_PORT,
&body,
)
};
// These packets should be received by all listeners.
receive_packet(1, multicast_addr);
receive_packet(2, multicast_addr);
// This packet should be received only by the all-IPs listener.
receive_packet(3, multicast_addr_other);
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([
(specific_listeners[0].downgrade(), vec![[1].as_slice(), &[2]]),
(specific_listeners[1].downgrade(), vec![&[1], &[2]]),
(any_listener.downgrade(), vec![&[1], &[2], &[3]]),
]),
);
}
type UdpMultipleDevicesCtx = FakeUdpCtx<MultipleDevicesId>;
type UdpMultipleDevicesCoreCtx = FakeUdpCoreCtx<MultipleDevicesId>;
type UdpMultipleDevicesBindingsCtx = FakeUdpBindingsCtx<MultipleDevicesId>;
impl FakeUdpCoreCtx<MultipleDevicesId> {
fn new_multiple_devices<I: TestIpExt>() -> Self {
let remote_ips = vec![I::get_other_remote_ip_address(1)];
Self::with_state(FakeDualStackIpSocketCtx::new(
MultipleDevicesId::all().into_iter().enumerate().map(|(i, device)| {
FakeDeviceConfig {
device,
local_ips: vec![I::get_other_ip_address((i + 1).try_into().unwrap())],
remote_ips: remote_ips.clone(),
}
}),
))
}
}
/// Tests that if sockets are bound to devices, they will only receive
/// packets that are received on those devices.
#[ip_test]
fn test_bound_to_device_receive<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let bound_first_device = api.create();
api.listen(
&bound_first_device,
Some(ZonedAddr::Unzoned(local_ip::<I>())),
Some(LOCAL_PORT),
)
.expect("listen should succeed");
api.connect(
&bound_first_device,
Some(ZonedAddr::Unzoned(I::get_other_remote_ip_address(1))),
REMOTE_PORT.into(),
)
.expect("connect should succeed");
api.set_device(&bound_first_device, Some(&MultipleDevicesId::A))
.expect("bind should succeed");
let bound_second_device = api.create();
api.set_device(&bound_second_device, Some(&MultipleDevicesId::B)).unwrap();
api.listen(&bound_second_device, None, Some(LOCAL_PORT)).expect("listen should succeed");
// Inject a packet received on `MultipleDevicesId::A` from the specified
// remote; this should go to the first socket.
let body = [1, 2, 3, 4, 5];
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
MultipleDevicesId::A,
I::get_other_remote_ip_address(1).get(),
local_ip::<I>().get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
// A second packet received on `MultipleDevicesId::B` will go to the
// second socket.
receive_udp_packet(
core_ctx,
bindings_ctx,
MultipleDevicesId::B,
I::get_other_remote_ip_address(1).get(),
local_ip::<I>().get(),
REMOTE_PORT,
LOCAL_PORT,
&body[..],
);
assert_eq!(
bindings_ctx.state().socket_data(),
HashMap::from([
(bound_first_device.downgrade(), vec![&body[..]]),
(bound_second_device.downgrade(), vec![&body[..]])
])
);
}
/// Tests that if sockets are bound to devices, they will send packets out
/// of those devices.
#[ip_test]
fn test_bound_to_device_send<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let bound_on_devices = MultipleDevicesId::all().map(|device| {
let socket = api.create();
api.set_device(&socket, Some(&device)).unwrap();
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen should succeed");
socket
});
// Send a packet from each socket.
let body = [1, 2, 3, 4, 5];
for socket in bound_on_devices {
api.send_to(
&socket,
Some(ZonedAddr::Unzoned(I::get_other_remote_ip_address(1))),
REMOTE_PORT.into(),
Buf::new(body.to_vec(), ..),
)
.expect("send should succeed");
}
let mut received_devices = api
.core_ctx()
.inner
.inner
.frames()
.iter()
.map(|(meta, _body)| {
let SendIpPacketMeta {
device,
src_ip: _,
dst_ip,
broadcast: _,
next_hop: _,
proto,
ttl: _,
mtu: _,
} = meta.try_as::<I>().unwrap();
assert_eq!(proto, &IpProto::Udp.into());
assert_eq!(dst_ip, &I::get_other_remote_ip_address(1));
*device
})
.collect::<Vec<_>>();
received_devices.sort();
assert_eq!(received_devices, &MultipleDevicesId::all());
}
fn receive_packet_on<I: Ip + TestIpExt>(
core_ctx: &mut UdpMultipleDevicesCoreCtx,
bindings_ctx: &mut UdpMultipleDevicesBindingsCtx,
device: MultipleDevicesId,
) {
const BODY: [u8; 5] = [1, 2, 3, 4, 5];
receive_udp_packet(
core_ctx,
bindings_ctx,
device,
I::get_other_remote_ip_address(1).get(),
local_ip::<I>().get(),
REMOTE_PORT,
LOCAL_PORT,
&BODY[..],
)
}
/// Check that sockets can be bound to and unbound from devices.
#[ip_test]
fn test_bind_unbind_device<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
// Start with `socket` bound to a device.
let socket = api.create();
api.set_device(&socket, Some(&MultipleDevicesId::A)).unwrap();
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen failed");
// Since it is bound, it does not receive a packet from another device.
let (core_ctx, bindings_ctx) = api.contexts();
receive_packet_on::<I>(core_ctx, bindings_ctx, MultipleDevicesId::B);
let received = &bindings_ctx.state().socket_data::<I>();
assert_eq!(received, &HashMap::new());
// When unbound, the socket can receive packets on the other device.
api.set_device(&socket, None).expect("clearing bound device failed");
let (core_ctx, bindings_ctx) = api.contexts();
receive_packet_on::<I>(core_ctx, bindings_ctx, MultipleDevicesId::B);
let received = bindings_ctx.state().received::<I>().iter().collect::<Vec<_>>();
let (rx_socket, socket_received) =
assert_matches!(received[..], [(rx_socket, packets)] => (rx_socket, packets));
assert_eq!(rx_socket, &socket);
assert_matches!(socket_received.packets[..], [_]);
}
/// Check that bind fails as expected when it would cause illegal shadowing.
#[ip_test]
fn test_unbind_device_fails<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let bound_on_devices = MultipleDevicesId::all().map(|device| {
let socket = api.create();
api.set_device(&socket, Some(&device)).unwrap();
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen should succeed");
socket
});
// Clearing the bound device is not allowed for either socket since it
// would then be shadowed by the other socket.
for socket in bound_on_devices {
assert_matches!(
api.set_device(&socket, None),
Err(SocketError::Local(LocalAddressError::AddressInUse))
);
}
}
/// Check that binding a device fails if it would make a connected socket
/// unroutable.
#[ip_test]
fn test_bind_conn_socket_device_fails<I: Ip + TestIpExt>() {
set_logger_for_test();
let device_configs = HashMap::from(
[(MultipleDevicesId::A, 1), (MultipleDevicesId::B, 2)].map(|(device, i)| {
(
device,
FakeDeviceConfig {
device,
local_ips: vec![I::get_other_ip_address(i)],
remote_ips: vec![I::get_other_remote_ip_address(i)],
},
)
}),
);
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(device_configs.iter().map(|(_, v)| v).cloned()),
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.connect(
&socket,
Some(ZonedAddr::Unzoned(device_configs[&MultipleDevicesId::A].remote_ips[0])),
REMOTE_PORT.into(),
)
.expect("connect should succeed");
// `socket` is not explicitly bound to device `A` but its route must
// go through it because of the destination address. Therefore binding
// to device `B` wil not work.
assert_matches!(
api.set_device(&socket, Some(&MultipleDevicesId::B)),
Err(SocketError::Remote(RemoteAddressError::NoRoute))
);
// Binding to device `A` should be fine.
api.set_device(&socket, Some(&MultipleDevicesId::A)).expect("routing picked A already");
}
#[ip_test]
fn test_bound_device_receive_multicast_packet<I: Ip + TestIpExt>() {
set_logger_for_test();
let remote_ip = I::get_other_ip_address(1);
let multicast_addr = I::get_multicast_addr(0);
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
// Create 3 sockets: one listener bound on each device and one not bound
// to a device.
let bound_on_devices = MultipleDevicesId::all().map(|device| {
let listener = api.create();
api.set_device(&listener, Some(&device)).unwrap();
api.set_posix_reuse_port(&listener, true).expect("is unbound");
api.listen(&listener, None, Some(LOCAL_PORT)).expect("listen should succeed");
(device, listener)
});
let listener = api.create();
api.set_posix_reuse_port(&listener, true).expect("is unbound");
api.listen(&listener, None, Some(LOCAL_PORT)).expect("listen should succeed");
fn index_for_device(id: MultipleDevicesId) -> u8 {
match id {
MultipleDevicesId::A => 0,
MultipleDevicesId::B => 1,
MultipleDevicesId::C => 2,
}
}
let (core_ctx, bindings_ctx) = api.contexts();
let mut receive_packet = |remote_ip: SpecifiedAddr<I::Addr>, device: MultipleDevicesId| {
let body = vec![index_for_device(device)];
receive_udp_packet(
core_ctx,
bindings_ctx,
device,
remote_ip.get(),
multicast_addr.get(),
REMOTE_PORT,
LOCAL_PORT,
&body,
)
};
// Receive packets from the remote IP on each device (2 packets total).
// Listeners bound on devices should receive one, and the other listener
// should receive both.
for device in MultipleDevicesId::all() {
receive_packet(remote_ip, device);
}
let per_socket_data = bindings_ctx.state().socket_data();
for (device, listener) in bound_on_devices {
assert_eq!(per_socket_data[&listener.downgrade()], vec![&[index_for_device(device)]]);
}
let expected_listener_data = &MultipleDevicesId::all().map(|d| vec![index_for_device(d)]);
assert_eq!(&per_socket_data[&listener.downgrade()], expected_listener_data);
}
/// Tests establishing a UDP connection without providing a local IP
#[ip_test]
fn test_conn_unspecified_local_ip<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen_udp failed");
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip::<I>())), REMOTE_PORT.into())
.expect("connect failed");
let info = api.get_info(&socket);
assert_eq!(
info,
SocketInfo::Connected(datagram::ConnInfo {
local_ip: StrictlyZonedAddr::new_unzoned_or_panic(local_ip::<I>()),
local_identifier: LOCAL_PORT,
remote_ip: StrictlyZonedAddr::new_unzoned_or_panic(remote_ip::<I>()),
remote_identifier: REMOTE_PORT.into(),
})
);
}
/// Tests local port allocation for [`connect`].
///
/// Tests that calling [`connect`] causes a valid local port to be
/// allocated.
#[ip_test]
fn test_udp_local_port_alloc<I: Ip + TestIpExt>() {
let local_ip = local_ip::<I>();
let ip_a = I::get_other_ip_address(100);
let ip_b = I::get_other_ip_address(200);
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(
UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(vec![local_ip], vec![ip_a, ip_b]),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let conn_a = api.create();
api.connect(&conn_a, Some(ZonedAddr::Unzoned(ip_a)), REMOTE_PORT.into())
.expect("connect failed");
let conn_b = api.create();
api.connect(&conn_b, Some(ZonedAddr::Unzoned(ip_b)), REMOTE_PORT.into())
.expect("connect failed");
let conn_c = api.create();
api.connect(&conn_c, Some(ZonedAddr::Unzoned(ip_a)), OTHER_REMOTE_PORT.into())
.expect("connect failed");
let conn_d = api.create();
api.connect(&conn_d, Some(ZonedAddr::Unzoned(ip_a)), REMOTE_PORT.into())
.expect("connect failed");
let valid_range = &FakeBoundSocketMap::<I>::EPHEMERAL_RANGE;
let mut get_conn_port = |id| {
let info = api.get_info(&id);
let info = assert_matches!(info, SocketInfo::Connected(info) => info);
let datagram::ConnInfo {
local_ip: _,
local_identifier,
remote_ip: _,
remote_identifier: _,
} = info;
local_identifier
};
let port_a = get_conn_port(conn_a).get();
let port_b = get_conn_port(conn_b).get();
let port_c = get_conn_port(conn_c).get();
let port_d = get_conn_port(conn_d).get();
assert!(valid_range.contains(&port_a));
assert!(valid_range.contains(&port_b));
assert!(valid_range.contains(&port_c));
assert!(valid_range.contains(&port_d));
assert_ne!(port_a, port_b);
assert_ne!(port_a, port_c);
assert_ne!(port_a, port_d);
}
/// Tests that if `listen_udp` fails, it can be retried later.
#[ip_test]
fn test_udp_retry_listen_after_removing_conflict<I: Ip + TestIpExt>() {
set_logger_for_test();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let listen_unbound = |api: &mut UdpApi<_, _>, socket: &UdpSocketId<_, _, _>| {
api.listen(socket, Some(ZonedAddr::Unzoned(local_ip::<I>())), Some(LOCAL_PORT))
};
// Tie up the address so the second call to `connect` fails.
let listener = api.create();
listen_unbound(&mut api, &listener)
.expect("Initial call to listen_udp was expected to succeed");
// Trying to connect on the same address should fail.
let unbound = api.create();
assert_eq!(
listen_unbound(&mut api, &unbound),
Err(Either::Right(LocalAddressError::AddressInUse))
);
// Once the first listener is removed, the second socket can be
// connected.
api.close(listener).into_removed();
listen_unbound(&mut api, &unbound).expect("listen should succeed");
}
/// Tests local port allocation for [`listen_udp`].
///
/// Tests that calling [`listen_udp`] causes a valid local port to be
/// allocated when no local port is passed.
#[ip_test]
fn test_udp_listen_port_alloc<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let wildcard_list = api.create();
api.listen(&wildcard_list, None, None).expect("listen_udp failed");
let specified_list = api.create();
api.listen(&specified_list, Some(ZonedAddr::Unzoned(local_ip)), None)
.expect("listen_udp failed");
let mut get_listener_port = |id| {
let info = api.get_info(&id);
let info = assert_matches!(info, SocketInfo::Listener(info) => info);
let datagram::ListenerInfo { local_ip: _, local_identifier } = info;
local_identifier
};
let wildcard_port = get_listener_port(wildcard_list);
let specified_port = get_listener_port(specified_list);
assert!(FakeBoundSocketMap::<I>::EPHEMERAL_RANGE.contains(&wildcard_port.get()));
assert!(FakeBoundSocketMap::<I>::EPHEMERAL_RANGE.contains(&specified_port.get()));
assert_ne!(wildcard_port, specified_port);
}
#[ip_test]
fn test_bind_multiple_reuse_port<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let listeners = [(), ()].map(|()| {
let socket = api.create();
api.set_posix_reuse_port(&socket, true).expect("is unbound");
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen_udp failed");
socket
});
for listener in listeners {
assert_eq!(
api.get_info(&listener),
SocketInfo::Listener(datagram::ListenerInfo {
local_ip: None,
local_identifier: LOCAL_PORT
})
);
}
}
#[ip_test]
fn test_set_unset_reuse_port_unbound<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
api.set_posix_reuse_port(&unbound, true).expect("is unbound");
api.set_posix_reuse_port(&unbound, false).expect("is unbound");
api.listen(&unbound, None, Some(LOCAL_PORT)).expect("listen_udp failed");
// Because there is already a listener bound without `SO_REUSEPORT` set,
// the next bind to the same address should fail.
assert_eq!(
{
let unbound = api.create();
api.listen(&unbound, None, Some(LOCAL_PORT))
},
Err(Either::Right(LocalAddressError::AddressInUse))
);
}
#[ip_test]
#[test_case(bind_as_listener)]
#[test_case(bind_as_connected)]
fn test_set_unset_reuse_port_bound<I: Ip + TestIpExt>(
set_up_socket: impl FnOnce(
&mut UdpMultipleDevicesCtx,
&UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
),
) {
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let socket = UdpApi::<I, _>::new(ctx.as_mut()).create();
set_up_socket(&mut ctx, &socket);
// Per src/connectivity/network/netstack3/docs/POSIX_COMPATIBILITY.md,
// Netstack3 only allows setting SO_REUSEPORT on unbound sockets.
assert_matches!(
UdpApi::<I, _>::new(ctx.as_mut()).set_posix_reuse_port(&socket, false),
Err(ExpectedUnboundError)
)
}
/// Tests [`remove_udp`]
#[ip_test]
fn test_remove_udp_conn<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = ZonedAddr::Unzoned(local_ip::<I>());
let remote_ip = ZonedAddr::Unzoned(remote_ip::<I>());
let socket = api.create();
api.listen(&socket, Some(local_ip), Some(LOCAL_PORT)).unwrap();
api.connect(&socket, Some(remote_ip), REMOTE_PORT.into()).expect("connect failed");
api.close(socket).into_removed();
}
/// Tests [`remove_udp`]
#[ip_test]
fn test_remove_udp_listener<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = ZonedAddr::Unzoned(local_ip::<I>());
// Test removing a specified listener.
let specified = api.create();
api.listen(&specified, Some(local_ip), Some(LOCAL_PORT)).expect("listen_udp failed");
api.close(specified).into_removed();
// Test removing a wildcard listener.
let wildcard = api.create();
api.listen(&wildcard, None, Some(LOCAL_PORT)).expect("listen_udp failed");
api.close(wildcard).into_removed();
}
fn try_join_leave_multicast<I: Ip + TestIpExt>(
mcast_addr: MulticastAddr<I::Addr>,
interface: MulticastMembershipInterfaceSelector<I::Addr, MultipleDevicesId>,
set_up_ctx: impl FnOnce(&mut UdpMultipleDevicesCtx),
set_up_socket: impl FnOnce(
&mut UdpMultipleDevicesCtx,
&UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
),
) -> (
Result<(), SetMulticastMembershipError>,
HashMap<(MultipleDevicesId, MulticastAddr<I::Addr>), NonZeroUsize>,
) {
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
set_up_ctx(&mut ctx);
let socket = UdpApi::<I, _>::new(ctx.as_mut()).create();
set_up_socket(&mut ctx, &socket);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let result = api.set_multicast_membership(&socket, mcast_addr, interface, true);
let memberships_snapshot =
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>();
if let Ok(()) = result {
api.set_multicast_membership(&socket, mcast_addr, interface, false)
.expect("leaving group failed");
}
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::default()
);
(result, memberships_snapshot)
}
fn leave_unbound<I: TestIpExt>(
_ctx: &mut UdpMultipleDevicesCtx,
_unbound: &UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
) {
}
fn bind_as_listener<I: TestIpExt>(
ctx: &mut UdpMultipleDevicesCtx,
unbound: &UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
) {
UdpApi::<I, _>::new(ctx.as_mut())
.listen(unbound, Some(ZonedAddr::Unzoned(local_ip::<I>())), Some(LOCAL_PORT))
.expect("listen should succeed")
}
fn bind_as_connected<I: TestIpExt>(
ctx: &mut UdpMultipleDevicesCtx,
unbound: &UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
) {
UdpApi::<I, _>::new(ctx.as_mut())
.connect(
unbound,
Some(ZonedAddr::Unzoned(I::get_other_remote_ip_address(1))),
REMOTE_PORT.into(),
)
.expect("connect should succeed")
}
fn iface_id<A: IpAddress>(
id: MultipleDevicesId,
) -> MulticastMembershipInterfaceSelector<A, MultipleDevicesId> {
MulticastInterfaceSelector::Interface(id).into()
}
fn iface_addr<A: IpAddress>(
addr: SpecifiedAddr<A>,
) -> MulticastMembershipInterfaceSelector<A, MultipleDevicesId> {
MulticastInterfaceSelector::LocalAddress(addr).into()
}
#[ip_test]
#[test_case(iface_id(MultipleDevicesId::A), leave_unbound::<I>; "device_no_addr_unbound")]
#[test_case(iface_addr(local_ip::<I>()), leave_unbound::<I>; "addr_no_device_unbound")]
#[test_case(MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute, leave_unbound::<I>;
"any_interface_unbound")]
#[test_case(iface_id(MultipleDevicesId::A), bind_as_listener::<I>; "device_no_addr_listener")]
#[test_case(iface_addr(local_ip::<I>()), bind_as_listener::<I>; "addr_no_device_listener")]
#[test_case(MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute, bind_as_listener::<I>;
"any_interface_listener")]
#[test_case(iface_id(MultipleDevicesId::A), bind_as_connected::<I>; "device_no_addr_connected")]
#[test_case(iface_addr(local_ip::<I>()), bind_as_connected::<I>; "addr_no_device_connected")]
#[test_case(MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute, bind_as_connected::<I>;
"any_interface_connected")]
fn test_join_leave_multicast_succeeds<I: Ip + TestIpExt>(
interface: MulticastMembershipInterfaceSelector<I::Addr, MultipleDevicesId>,
set_up_socket: impl FnOnce(
&mut UdpMultipleDevicesCtx,
&UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
),
) {
let mcast_addr = I::get_multicast_addr(3);
let set_up_ctx = |ctx: &mut UdpMultipleDevicesCtx| {
// Ensure there is a route to the multicast address, if the interface
// selector requires it.
match interface {
MulticastMembershipInterfaceSelector::Specified(_) => {}
MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute => {
let (core_ctx, _bindings_ctx) = ctx.contexts();
let Wrapped { inner: Wrapped { inner, outer: _ }, outer: _ } = core_ctx;
inner
.get_mut()
.add_route(MultipleDevicesId::A, mcast_addr.into_specified().into());
}
}
};
let (result, ip_options) =
try_join_leave_multicast(mcast_addr, interface, set_up_ctx, set_up_socket);
assert_eq!(result, Ok(()));
assert_eq!(
ip_options,
HashMap::from([(
(MultipleDevicesId::A, mcast_addr),
const_unwrap_option(NonZeroUsize::new(1))
)])
);
}
#[ip_test]
#[test_case(leave_unbound::<I>; "unbound")]
#[test_case(bind_as_listener::<I>; "listener")]
#[test_case(bind_as_connected::<I>; "connected")]
fn test_join_multicast_fails_without_route<I: Ip + TestIpExt>(
set_up_socket: impl FnOnce(
&mut UdpMultipleDevicesCtx,
&UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
),
) {
let mcast_addr = I::get_multicast_addr(3);
let (result, ip_options) = try_join_leave_multicast(
mcast_addr,
MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute,
|_: &mut UdpMultipleDevicesCtx| { /* Don't install a route to `mcast_addr` */ },
set_up_socket,
);
assert_eq!(result, Err(SetMulticastMembershipError::NoDeviceAvailable));
assert_eq!(ip_options, HashMap::new());
}
#[ip_test]
#[test_case(MultipleDevicesId::A, Some(local_ip::<I>()), leave_unbound, Ok(());
"with_ip_unbound")]
#[test_case(MultipleDevicesId::A, None, leave_unbound, Ok(());
"without_ip_unbound")]
#[test_case(MultipleDevicesId::A, Some(local_ip::<I>()), bind_as_listener, Ok(());
"with_ip_listener")]
#[test_case(MultipleDevicesId::A, Some(local_ip::<I>()), bind_as_connected, Ok(());
"with_ip_connected")]
fn test_join_leave_multicast_interface_inferred_from_bound_device<I: Ip + TestIpExt>(
bound_device: MultipleDevicesId,
interface_addr: Option<SpecifiedAddr<I::Addr>>,
set_up_socket: impl FnOnce(
&mut UdpMultipleDevicesCtx,
&UdpSocketId<
I,
FakeWeakDeviceId<MultipleDevicesId>,
FakeUdpBindingsCtx<MultipleDevicesId>,
>,
),
expected_result: Result<(), SetMulticastMembershipError>,
) {
let mcast_addr = I::get_multicast_addr(3);
let (result, ip_options) = try_join_leave_multicast(
mcast_addr,
interface_addr
.map(MulticastInterfaceSelector::LocalAddress)
.map(Into::into)
.unwrap_or(MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute),
|_: &mut UdpMultipleDevicesCtx| { /* No ctx setup required */ },
|ctx, unbound| {
UdpApi::<I, _>::new(ctx.as_mut())
.set_device(&unbound, Some(&bound_device))
.unwrap();
set_up_socket(ctx, &unbound)
},
);
assert_eq!(result, expected_result);
assert_eq!(
ip_options,
expected_result.map_or(HashMap::default(), |()| HashMap::from([(
(bound_device, mcast_addr),
const_unwrap_option(NonZeroUsize::new(1))
)]))
);
}
#[ip_test]
fn test_multicast_membership_with_removed_device<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
api.set_device(&unbound, Some(&FakeDeviceId)).unwrap();
set_device_removed::<I>(api.core_ctx(), true);
let group = I::get_multicast_addr(4);
assert_eq!(
api.set_multicast_membership(
&unbound,
group,
// Will use the socket's bound device.
MulticastMembershipInterfaceSelector::AnyInterfaceWithRoute,
true,
),
Err(SetMulticastMembershipError::DeviceDoesNotExist),
);
// Should not have updated the device's multicast state.
//
// Note that even though we mock the device being removed above, its
// state still exists in the fake IP socket context so we can inspect
// it here.
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::default(),
);
}
#[ip_test]
fn test_remove_udp_unbound_leaves_multicast_groups<I: Ip + TestIpExt>() {
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
let group = I::get_multicast_addr(4);
api.set_multicast_membership(
&unbound,
group,
MulticastInterfaceSelector::LocalAddress(local_ip::<I>()).into(),
true,
)
.expect("join group failed");
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::from([(
(MultipleDevicesId::A, group),
const_unwrap_option(NonZeroUsize::new(1))
)])
);
api.close(unbound).into_removed();
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::default()
);
}
#[ip_test]
fn test_remove_udp_listener_leaves_multicast_groups<I: Ip + TestIpExt>() {
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let socket = api.create();
let first_group = I::get_multicast_addr(4);
api.set_multicast_membership(
&socket,
first_group,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true,
)
.expect("join group failed");
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen_udp failed");
let second_group = I::get_multicast_addr(5);
api.set_multicast_membership(
&socket,
second_group,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true,
)
.expect("join group failed");
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::from([
((MultipleDevicesId::A, first_group), const_unwrap_option(NonZeroUsize::new(1))),
((MultipleDevicesId::A, second_group), const_unwrap_option(NonZeroUsize::new(1)))
])
);
api.close(socket).into_removed();
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::default()
);
}
#[ip_test]
fn test_remove_udp_connected_leaves_multicast_groups<I: Ip + TestIpExt>() {
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(
UdpMultipleDevicesCoreCtx::new_multiple_devices::<I>(),
);
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let socket = api.create();
let first_group = I::get_multicast_addr(4);
api.set_multicast_membership(
&socket,
first_group,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true,
)
.expect("join group failed");
api.connect(
&socket,
Some(ZonedAddr::Unzoned(I::get_other_remote_ip_address(1))),
REMOTE_PORT.into(),
)
.expect("connect failed");
let second_group = I::get_multicast_addr(5);
api.set_multicast_membership(
&socket,
second_group,
MulticastInterfaceSelector::LocalAddress(local_ip).into(),
true,
)
.expect("join group failed");
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::from([
((MultipleDevicesId::A, first_group), const_unwrap_option(NonZeroUsize::new(1))),
((MultipleDevicesId::A, second_group), const_unwrap_option(NonZeroUsize::new(1)))
])
);
api.close(socket).into_removed();
assert_eq!(
api.core_ctx().inner.inner.get_ref().multicast_memberships::<I>(),
HashMap::default()
);
}
#[ip_test]
#[should_panic(expected = "listen again failed")]
fn test_listen_udp_removes_unbound<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = local_ip::<I>();
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT))
.expect("listen_udp failed");
// Attempting to create a new listener from the same unbound ID should
// panic since the unbound socket ID is now invalid.
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(OTHER_LOCAL_PORT))
.expect("listen again failed");
}
#[ip_test]
fn test_get_conn_info<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = ZonedAddr::Unzoned(local_ip::<I>());
let remote_ip = ZonedAddr::Unzoned(remote_ip::<I>());
// Create a UDP connection with a specified local port and local IP.
let socket = api.create();
api.listen(&socket, Some(local_ip), Some(LOCAL_PORT)).expect("listen_udp failed");
api.connect(&socket, Some(remote_ip), REMOTE_PORT.into()).expect("connect failed");
let info = api.get_info(&socket);
let info = assert_matches!(info, SocketInfo::Connected(info) => info);
assert_eq!(info.local_ip.into_inner(), local_ip.map_zone(FakeWeakDeviceId));
assert_eq!(info.local_identifier, LOCAL_PORT);
assert_eq!(info.remote_ip.into_inner(), remote_ip.map_zone(FakeWeakDeviceId));
assert_eq!(info.remote_identifier, u16::from(REMOTE_PORT));
}
#[ip_test]
fn test_get_listener_info<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let local_ip = ZonedAddr::Unzoned(local_ip::<I>());
// Check getting info on specified listener.
let specified = api.create();
api.listen(&specified, Some(local_ip), Some(LOCAL_PORT)).expect("listen_udp failed");
let info = api.get_info(&specified);
let info = assert_matches!(info, SocketInfo::Listener(info) => info);
assert_eq!(info.local_ip.unwrap().into_inner(), local_ip.map_zone(FakeWeakDeviceId));
assert_eq!(info.local_identifier, LOCAL_PORT);
// Check getting info on wildcard listener.
let wildcard = api.create();
api.listen(&wildcard, None, Some(OTHER_LOCAL_PORT)).expect("listen_udp failed");
let info = api.get_info(&wildcard);
let info = assert_matches!(info, SocketInfo::Listener(info) => info);
assert_eq!(info.local_ip, None);
assert_eq!(info.local_identifier, OTHER_LOCAL_PORT);
}
#[ip_test]
fn test_get_reuse_port<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let first = api.create();
assert_eq!(api.get_posix_reuse_port(&first), false);
api.set_posix_reuse_port(&first, true).expect("is unbound");
assert_eq!(api.get_posix_reuse_port(&first), true);
api.listen(&first, Some(ZonedAddr::Unzoned(local_ip::<I>())), None).expect("listen failed");
assert_eq!(api.get_posix_reuse_port(&first), true);
api.close(first).into_removed();
let second = api.create();
api.set_posix_reuse_port(&second, true).expect("is unbound");
api.connect(&second, Some(ZonedAddr::Unzoned(remote_ip::<I>())), REMOTE_PORT.into())
.expect("connect failed");
assert_eq!(api.get_posix_reuse_port(&second), true);
}
#[ip_test]
fn test_get_bound_device_unbound<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
assert_eq!(api.get_bound_device(&unbound), None);
api.set_device(&unbound, Some(&FakeDeviceId)).unwrap();
assert_eq!(api.get_bound_device(&unbound), Some(FakeWeakDeviceId(FakeDeviceId)));
}
#[ip_test]
fn test_get_bound_device_listener<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.set_device(&socket, Some(&FakeDeviceId)).unwrap();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip::<I>())), Some(LOCAL_PORT))
.expect("failed to listen");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
api.set_device(&socket, None).expect("failed to set device");
assert_eq!(api.get_bound_device(&socket), None);
}
#[ip_test]
fn test_get_bound_device_connected<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.set_device(&socket, Some(&FakeDeviceId)).unwrap();
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip::<I>())), REMOTE_PORT.into())
.expect("failed to connect");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
api.set_device(&socket, None).expect("failed to set device");
assert_eq!(api.get_bound_device(&socket), None);
}
#[ip_test]
fn test_listen_udp_forwards_errors<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let remote_ip = remote_ip::<I>();
// Check listening to a non-local IP fails.
let unbound = api.create();
let listen_err = api
.listen(&unbound, Some(ZonedAddr::Unzoned(remote_ip)), Some(LOCAL_PORT))
.expect_err("listen_udp unexpectedly succeeded");
assert_eq!(listen_err, Either::Right(LocalAddressError::CannotBindToAddress));
let unbound = api.create();
let _ = api.listen(&unbound, None, Some(OTHER_LOCAL_PORT)).expect("listen_udp failed");
let unbound = api.create();
let listen_err = api
.listen(&unbound, None, Some(OTHER_LOCAL_PORT))
.expect_err("listen_udp unexpectedly succeeded");
assert_eq!(listen_err, Either::Right(LocalAddressError::AddressInUse));
}
const IPV6_LINK_LOCAL_ADDR: Ipv6Addr = net_ip_v6!("fe80::1234");
#[test_case(IPV6_LINK_LOCAL_ADDR, IPV6_LINK_LOCAL_ADDR; "unicast")]
#[test_case(IPV6_LINK_LOCAL_ADDR, MulticastAddr::new(net_ip_v6!("ff02::1234")).unwrap().get(); "multicast")]
fn test_listen_udp_ipv6_link_local_requires_zone(
interface_addr: Ipv6Addr,
bind_addr: Ipv6Addr,
) {
type I = Ipv6;
let interface_addr = LinkLocalAddr::new(interface_addr).unwrap().into_specified();
let mut ctx =
UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(
vec![interface_addr],
vec![remote_ip::<I>()],
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let bind_addr = LinkLocalAddr::new(bind_addr).unwrap().into_specified();
assert!(bind_addr.scope().can_have_zone());
let unbound = api.create();
let result = api.listen(&unbound, Some(ZonedAddr::Unzoned(bind_addr)), Some(LOCAL_PORT));
assert_eq!(
result,
Err(Either::Right(LocalAddressError::Zone(ZonedAddressError::RequiredZoneNotProvided)))
);
}
#[test_case(MultipleDevicesId::A, Ok(()); "matching")]
#[test_case(MultipleDevicesId::B, Err(LocalAddressError::Zone(ZonedAddressError::DeviceZoneMismatch)); "not matching")]
fn test_listen_udp_ipv6_link_local_with_bound_device_set(
zone_id: MultipleDevicesId,
expected_result: Result<(), LocalAddressError>,
) {
type I = Ipv6;
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(ll_addr.scope().can_have_zone());
let remote_ips = vec![remote_ip::<I>()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[(MultipleDevicesId::A, ll_addr), (MultipleDevicesId::B, local_ip::<I>())].map(
|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![local_ip],
remote_ips: remote_ips.clone(),
},
),
),
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.set_device(&socket, Some(&MultipleDevicesId::A)).unwrap();
let result = api
.listen(
&socket,
Some(ZonedAddr::Zoned(AddrAndZone::new(ll_addr, zone_id).unwrap())),
Some(LOCAL_PORT),
)
.map_err(Either::unwrap_right);
assert_eq!(result, expected_result);
}
#[test_case(MultipleDevicesId::A, Ok(()); "matching")]
#[test_case(MultipleDevicesId::B, Err(LocalAddressError::AddressMismatch); "not matching")]
fn test_listen_udp_ipv6_link_local_with_zone_requires_addr_assigned_to_device(
zone_id: MultipleDevicesId,
expected_result: Result<(), LocalAddressError>,
) {
type I = Ipv6;
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(ll_addr.scope().can_have_zone());
let remote_ips = vec![remote_ip::<I>()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[(MultipleDevicesId::A, ll_addr), (MultipleDevicesId::B, local_ip::<I>())].map(
|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![local_ip],
remote_ips: remote_ips.clone(),
},
),
),
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
let result = api
.listen(
&socket,
Some(ZonedAddr::Zoned(AddrAndZone::new(ll_addr, zone_id).unwrap())),
Some(LOCAL_PORT),
)
.map_err(Either::unwrap_right);
assert_eq!(result, expected_result);
}
#[test_case(None, Err(LocalAddressError::Zone(ZonedAddressError::DeviceZoneMismatch)); "clear device")]
#[test_case(Some(MultipleDevicesId::A), Ok(()); "set same device")]
#[test_case(Some(MultipleDevicesId::B),
Err(LocalAddressError::Zone(ZonedAddressError::DeviceZoneMismatch)); "change device")]
fn test_listen_udp_ipv6_listen_link_local_update_bound_device(
new_device: Option<MultipleDevicesId>,
expected_result: Result<(), LocalAddressError>,
) {
type I = Ipv6;
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(ll_addr.scope().can_have_zone());
let remote_ips = vec![remote_ip::<I>()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[(MultipleDevicesId::A, ll_addr), (MultipleDevicesId::B, local_ip::<I>())].map(
|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![local_ip],
remote_ips: remote_ips.clone(),
},
),
),
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(
&socket,
Some(ZonedAddr::Zoned(AddrAndZone::new(ll_addr, MultipleDevicesId::A).unwrap())),
Some(LOCAL_PORT),
)
.expect("listen failed");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(MultipleDevicesId::A)));
assert_eq!(
api.set_device(&socket, new_device.as_ref()),
expected_result.map_err(SocketError::Local),
);
}
#[test_case(None; "bind all IPs")]
#[test_case(Some(ZonedAddr::Unzoned(local_ip::<Ipv6>())); "bind unzoned")]
#[test_case(Some(ZonedAddr::Zoned(AddrAndZone::new(SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap(),
MultipleDevicesId::A).unwrap())); "bind with same zone")]
fn test_udp_ipv6_connect_with_unzoned(
bound_addr: Option<ZonedAddr<SpecifiedAddr<Ipv6Addr>, MultipleDevicesId>>,
) {
let remote_ips = vec![remote_ip::<Ipv6>()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new([
FakeDeviceConfig {
device: MultipleDevicesId::A,
local_ips: vec![
local_ip::<Ipv6>(),
SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap(),
],
remote_ips: remote_ips.clone(),
},
FakeDeviceConfig {
device: MultipleDevicesId::B,
local_ips: vec![SpecifiedAddr::new(net_ip_v6!("fe80::2")).unwrap()],
remote_ips: remote_ips,
},
]),
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(&socket, bound_addr, Some(LOCAL_PORT)).unwrap();
assert_matches!(
api.connect(
&socket,
Some(ZonedAddr::Unzoned(remote_ip::<Ipv6>())),
REMOTE_PORT.into(),
),
Ok(())
);
}
#[test]
fn test_udp_ipv6_connect_zoned_get_info() {
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(socket::must_have_zone(&ll_addr));
let remote_ips = vec![remote_ip::<Ipv6>()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[(MultipleDevicesId::A, ll_addr), (MultipleDevicesId::B, local_ip::<Ipv6>())].map(
|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![local_ip],
remote_ips: remote_ips.clone(),
},
),
),
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.set_device(&socket, Some(&MultipleDevicesId::A)).unwrap();
let zoned_local_addr =
ZonedAddr::Zoned(AddrAndZone::new(ll_addr, MultipleDevicesId::A).unwrap());
api.listen(&socket, Some(zoned_local_addr), Some(LOCAL_PORT)).unwrap();
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip::<Ipv6>())), REMOTE_PORT.into())
.expect("connect should succeed");
assert_eq!(
api.get_info(&socket),
SocketInfo::Connected(datagram::ConnInfo {
local_ip: StrictlyZonedAddr::new_with_zone(ll_addr, || FakeWeakDeviceId(
MultipleDevicesId::A
)),
local_identifier: LOCAL_PORT,
remote_ip: StrictlyZonedAddr::new_unzoned_or_panic(remote_ip::<Ipv6>()),
remote_identifier: REMOTE_PORT.into(),
})
);
}
#[test_case(ZonedAddr::Zoned(AddrAndZone::new(SpecifiedAddr::new(net_ip_v6!("fe80::2")).unwrap(),
MultipleDevicesId::B).unwrap()),
Err(ConnectError::Zone(ZonedAddressError::DeviceZoneMismatch));
"connect to different zone")]
#[test_case(ZonedAddr::Unzoned(SpecifiedAddr::new(net_ip_v6!("fe80::3")).unwrap()),
Ok(FakeWeakDeviceId(MultipleDevicesId::A)); "connect implicit zone")]
fn test_udp_ipv6_bind_zoned(
remote_addr: ZonedAddr<SpecifiedAddr<Ipv6Addr>, MultipleDevicesId>,
expected: Result<FakeWeakDeviceId<MultipleDevicesId>, ConnectError>,
) {
let remote_ips = vec![SpecifiedAddr::new(net_ip_v6!("fe80::3")).unwrap()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new([
FakeDeviceConfig {
device: MultipleDevicesId::A,
local_ips: vec![SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap()],
remote_ips: remote_ips.clone(),
},
FakeDeviceConfig {
device: MultipleDevicesId::B,
local_ips: vec![SpecifiedAddr::new(net_ip_v6!("fe80::2")).unwrap()],
remote_ips: remote_ips,
},
]),
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(
&socket,
Some(ZonedAddr::Zoned(
AddrAndZone::new(
SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap(),
MultipleDevicesId::A,
)
.unwrap(),
)),
Some(LOCAL_PORT),
)
.unwrap();
let result = api
.connect(&socket, Some(remote_addr), REMOTE_PORT.into())
.map(|()| api.get_bound_device(&socket).unwrap());
assert_eq!(result, expected);
}
#[ip_test]
fn test_listen_udp_loopback_no_zone_is_required<I: Ip + TestIpExt>() {
let loopback_addr = I::LOOPBACK_ADDRESS;
let remote_ips = vec![remote_ip::<I>()];
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[(MultipleDevicesId::A, loopback_addr), (MultipleDevicesId::B, local_ip::<I>())]
.map(|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![local_ip],
remote_ips: remote_ips.clone(),
}),
),
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
api.set_device(&unbound, Some(&MultipleDevicesId::A)).unwrap();
let result =
api.listen(&unbound, Some(ZonedAddr::Unzoned(loopback_addr)), Some(LOCAL_PORT));
assert_matches!(result, Ok(_));
}
#[test_case(None, true, Ok(()); "connected success")]
#[test_case(None, false, Ok(()); "listening success")]
#[test_case(Some(MultipleDevicesId::A), true, Ok(()); "conn bind same device")]
#[test_case(Some(MultipleDevicesId::A), false, Ok(()); "listen bind same device")]
#[test_case(
Some(MultipleDevicesId::B),
true,
Err(SendToError::Zone(ZonedAddressError::DeviceZoneMismatch));
"conn bind different device")]
#[test_case(
Some(MultipleDevicesId::B),
false,
Err(SendToError::Zone(ZonedAddressError::DeviceZoneMismatch));
"listen bind different device")]
fn test_udp_ipv6_send_to_zoned(
bind_device: Option<MultipleDevicesId>,
connect: bool,
expected: Result<(), SendToError>,
) {
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(socket::must_have_zone(&ll_addr));
let conn_remote_ip = Ipv6::get_other_remote_ip_address(1);
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[
(MultipleDevicesId::A, Ipv6::get_other_ip_address(1)),
(MultipleDevicesId::B, Ipv6::get_other_ip_address(2)),
]
.map(|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![local_ip],
remote_ips: vec![ll_addr, conn_remote_ip],
}),
),
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
if let Some(device) = bind_device {
api.set_device(&socket, Some(&device)).unwrap();
}
let send_to_remote_addr =
ZonedAddr::Zoned(AddrAndZone::new(ll_addr, MultipleDevicesId::A).unwrap());
let result = if connect {
api.connect(&socket, Some(ZonedAddr::Unzoned(conn_remote_ip)), REMOTE_PORT.into())
.expect("connect should succeed");
api.send_to(
&socket,
Some(send_to_remote_addr),
REMOTE_PORT.into(),
Buf::new(Vec::new(), ..),
)
} else {
api.listen(&socket, None, Some(LOCAL_PORT)).expect("listen should succeed");
api.send_to(
&socket,
Some(send_to_remote_addr),
REMOTE_PORT.into(),
Buf::new(Vec::new(), ..),
)
};
assert_eq!(result.map_err(|err| assert_matches!(err, Either::Right(e) => e)), expected);
}
#[test_case(true; "connected")]
#[test_case(false; "listening")]
fn test_udp_ipv6_bound_zoned_send_to_zoned(connect: bool) {
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::5678")).unwrap().into_specified();
let device_a_local_ip = net_ip_v6!("fe80::1111");
let conn_remote_ip = Ipv6::get_other_remote_ip_address(1);
let mut ctx = UdpMultipleDevicesCtx::with_core_ctx(UdpMultipleDevicesCoreCtx::with_state(
FakeDualStackIpSocketCtx::new(
[
(MultipleDevicesId::A, device_a_local_ip),
(MultipleDevicesId::B, net_ip_v6!("fe80::2222")),
]
.map(|(device, local_ip)| FakeDeviceConfig {
device,
local_ips: vec![LinkLocalAddr::new(local_ip).unwrap().into_specified()],
remote_ips: vec![ll_addr, conn_remote_ip],
}),
),
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(
&socket,
Some(ZonedAddr::Zoned(
AddrAndZone::new(
SpecifiedAddr::new(device_a_local_ip).unwrap(),
MultipleDevicesId::A,
)
.unwrap(),
)),
Some(LOCAL_PORT),
)
.expect("listen should succeed");
// Use a remote address on device B, while the socket is listening on
// device A. This should cause a failure when sending.
let send_to_remote_addr =
ZonedAddr::Zoned(AddrAndZone::new(ll_addr, MultipleDevicesId::B).unwrap());
let result = if connect {
api.connect(&socket, Some(ZonedAddr::Unzoned(conn_remote_ip)), REMOTE_PORT.into())
.expect("connect should succeed");
api.send_to(
&socket,
Some(send_to_remote_addr),
REMOTE_PORT.into(),
Buf::new(Vec::new(), ..),
)
} else {
api.send_to(
&socket,
Some(send_to_remote_addr),
REMOTE_PORT.into(),
Buf::new(Vec::new(), ..),
)
};
assert_matches!(
result,
Err(Either::Right(SendToError::Zone(ZonedAddressError::DeviceZoneMismatch)))
);
}
#[test_case(None; "removes implicit")]
#[test_case(Some(FakeDeviceId); "preserves implicit")]
fn test_connect_disconnect_affects_bound_device(bind_device: Option<FakeDeviceId>) {
// If a socket is bound to an unzoned address, whether or not it has a
// bound device should be restored after `connect` then `disconnect`.
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(socket::must_have_zone(&ll_addr));
let local_ip = local_ip::<Ipv6>();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(
UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(vec![local_ip], vec![ll_addr]),
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.set_device(&socket, bind_device.as_ref()).unwrap();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT)).unwrap();
api.connect(
&socket,
Some(ZonedAddr::Zoned(AddrAndZone::new(ll_addr, FakeDeviceId).unwrap())),
REMOTE_PORT.into(),
)
.expect("connect should succeed");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
api.disconnect(&socket).expect("was connected");
assert_eq!(api.get_bound_device(&socket), bind_device.map(FakeWeakDeviceId));
}
#[test]
fn test_bind_zoned_addr_connect_disconnect() {
// If a socket is bound to a zoned address, the address's device should
// be retained after `connect` then `disconnect`.
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(socket::must_have_zone(&ll_addr));
let remote_ip = remote_ip::<Ipv6>();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(
UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(vec![ll_addr], vec![remote_ip]),
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(
&socket,
Some(ZonedAddr::Zoned(AddrAndZone::new(ll_addr, FakeDeviceId).unwrap())),
Some(LOCAL_PORT),
)
.unwrap();
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into())
.expect("connect should succeed");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
api.disconnect(&socket).expect("was connected");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
}
#[test]
fn test_bind_device_after_connect_persists_after_disconnect() {
// If a socket is bound to an unzoned address, connected to a zoned address, and then has
// its device set, the device should be *retained* after `disconnect`.
let ll_addr = LinkLocalAddr::new(net_ip_v6!("fe80::1234")).unwrap().into_specified();
assert!(socket::must_have_zone(&ll_addr));
let local_ip = local_ip::<Ipv6>();
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(
UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(vec![local_ip], vec![ll_addr]),
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.listen(&socket, Some(ZonedAddr::Unzoned(local_ip)), Some(LOCAL_PORT)).unwrap();
api.connect(
&socket,
Some(ZonedAddr::Zoned(AddrAndZone::new(ll_addr, FakeDeviceId).unwrap())),
REMOTE_PORT.into(),
)
.expect("connect should succeed");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
// This is a no-op functionally since the socket is already bound to the
// device but it implies that we shouldn't unbind the device on
// disconnect.
api.set_device(&socket, Some(&FakeDeviceId)).expect("binding same device should succeed");
api.disconnect(&socket).expect("was connected");
assert_eq!(api.get_bound_device(&socket), Some(FakeWeakDeviceId(FakeDeviceId)));
}
#[ip_test]
fn test_remove_udp_unbound<I: Ip + TestIpExt>() {
let mut ctx = UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::new_fake_device::<I>());
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let unbound = api.create();
api.close(unbound).into_removed();
}
#[ip_test]
fn test_hop_limits_used_for_sending_packets<I: Ip + TestIpExt>() {
let some_multicast_addr: MulticastAddr<I::Addr> = I::map_ip(
(),
|()| Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS,
|()| MulticastAddr::new(net_ip_v6!("ff0e::1")).unwrap(),
);
let mut ctx =
UdpFakeDeviceCtx::with_core_ctx(UdpFakeDeviceCoreCtx::with_local_remote_ip_addrs(
vec![local_ip::<I>()],
vec![remote_ip::<I>(), some_multicast_addr.into_specified()],
));
let mut api = UdpApi::<I, _>::new(ctx.as_mut());
let listener = api.create();
const UNICAST_HOPS: NonZeroU8 = const_unwrap_option(NonZeroU8::new(23));
const MULTICAST_HOPS: NonZeroU8 = const_unwrap_option(NonZeroU8::new(98));
api.set_unicast_hop_limit(&listener, Some(UNICAST_HOPS), I::VERSION).unwrap();
api.set_multicast_hop_limit(&listener, Some(MULTICAST_HOPS), I::VERSION).unwrap();
api.listen(&listener, None, None).expect("listen failed");
let mut send_and_get_ttl = |remote_ip| {
api.send_to(
&listener,
Some(ZonedAddr::Unzoned(remote_ip)),
REMOTE_PORT.into(),
Buf::new(vec![], ..),
)
.expect("send failed");
let (meta, _body) = api.core_ctx().inner.inner.frames().last().unwrap();
let SendIpPacketMeta {
device: _,
src_ip: _,
dst_ip,
broadcast: _,
next_hop: _,
proto: _,
ttl,
mtu: _,
} = meta.try_as::<I>().unwrap();
assert_eq!(*dst_ip, remote_ip);
*ttl
};
assert_eq!(send_and_get_ttl(some_multicast_addr.into_specified()), Some(MULTICAST_HOPS));
assert_eq!(send_and_get_ttl(remote_ip::<I>()), Some(UNICAST_HOPS));
}
const DUAL_STACK_ANY_ADDR: Ipv6Addr = net_ip_v6!("::");
const DUAL_STACK_V4_ANY_ADDR: Ipv6Addr = net_ip_v6!("::FFFF:0.0.0.0");
#[derive(Copy, Clone, Debug)]
enum DualStackBindAddr {
Any,
V4Any,
V4Specific,
}
impl DualStackBindAddr {
const fn v6_addr(&self) -> Option<Ipv6Addr> {
match self {
Self::Any => Some(DUAL_STACK_ANY_ADDR),
Self::V4Any => Some(DUAL_STACK_V4_ANY_ADDR),
Self::V4Specific => None,
}
}
}
const V4_LOCAL_IP: Ipv4Addr = ip_v4!("192.168.1.10");
const V4_LOCAL_IP_MAPPED: Ipv6Addr = net_ip_v6!("::ffff:192.168.1.10");
const V6_LOCAL_IP: Ipv6Addr = net_ip_v6!("2201::1");
const V6_REMOTE_IP: SpecifiedAddr<Ipv6Addr> =
unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("2001:db8::1")) };
const V4_REMOTE_IP_MAPPED: SpecifiedAddr<Ipv6Addr> =
unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("::FFFF:192.0.2.1")) };
fn get_dual_stack_context<
'a,
BC: UdpBindingsTypes + 'a,
CC: DatagramBoundStateContext<Ipv6, BC, Udp<BC>>,
>(
core_ctx: &'a mut CC,
) -> &'a mut CC::DualStackContext {
match core_ctx.dual_stack_context() {
MaybeDualStack::NotDualStack(_) => unreachable!("UDP is a dual stack enabled protocol"),
MaybeDualStack::DualStack(ds) => ds,
}
}
#[test_case(DualStackBindAddr::Any; "dual-stack")]
#[test_case(DualStackBindAddr::V4Any; "v4 any")]
#[test_case(DualStackBindAddr::V4Specific; "v4 specific")]
fn dual_stack_delivery(bind_addr: DualStackBindAddr) {
const REMOTE_IP: Ipv4Addr = ip_v4!("8.8.8.8");
const REMOTE_IP_MAPPED: Ipv6Addr = net_ip_v6!("::ffff:8.8.8.8");
let bind_addr = bind_addr.v6_addr().unwrap_or(V4_LOCAL_IP_MAPPED);
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![SpecifiedAddr::new(V4_LOCAL_IP).unwrap()],
vec![SpecifiedAddr::new(REMOTE_IP).unwrap()],
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let listener = api.create();
api.listen(
&listener,
SpecifiedAddr::new(bind_addr).map(|a| ZonedAddr::Unzoned(a)),
Some(LOCAL_PORT),
)
.expect("can bind");
const BODY: &[u8] = b"abcde";
let (core_ctx, bindings_ctx) = api.contexts();
receive_udp_packet(
core_ctx,
bindings_ctx,
FakeDeviceId,
REMOTE_IP,
V4_LOCAL_IP,
REMOTE_PORT,
LOCAL_PORT,
BODY,
);
assert_eq!(
bindings_ctx.state().received::<Ipv6>(),
&HashMap::from([(
listener.downgrade(),
SocketReceived {
packets: vec![ReceivedPacket {
body: BODY.into(),
addr: ReceivedPacketAddrs {
dst_ip: V4_LOCAL_IP_MAPPED,
src_ip: REMOTE_IP_MAPPED,
src_port: Some(REMOTE_PORT),
}
}],
}
)])
);
}
#[test_case(DualStackBindAddr::Any, true; "dual-stack any bind v4 first")]
#[test_case(DualStackBindAddr::V4Any, true; "v4 any bind v4 first")]
#[test_case(DualStackBindAddr::V4Specific, true; "v4 specific bind v4 first")]
#[test_case(DualStackBindAddr::Any, false; "dual-stack any bind v4 second")]
#[test_case(DualStackBindAddr::V4Any, false; "v4 any bind v4 second")]
#[test_case(DualStackBindAddr::V4Specific, false; "v4 specific bind v4 second")]
fn dual_stack_bind_conflict(bind_addr: DualStackBindAddr, bind_v4_first: bool) {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![SpecifiedAddr::new(V4_LOCAL_IP).unwrap()],
vec![],
));
let v4_listener = UdpApi::<Ipv4, _>::new(ctx.as_mut()).create();
let v6_listener = UdpApi::<Ipv6, _>::new(ctx.as_mut()).create();
let bind_v4 = |mut api: UdpApi<Ipv4, _>| {
api.listen(
&v4_listener,
SpecifiedAddr::new(V4_LOCAL_IP).map(|a| ZonedAddr::Unzoned(a)),
Some(LOCAL_PORT),
)
};
let bind_v6 = |mut api: UdpApi<Ipv6, _>| {
api.listen(
&v6_listener,
SpecifiedAddr::new(bind_addr.v6_addr().unwrap_or(V4_LOCAL_IP_MAPPED))
.map(ZonedAddr::Unzoned),
Some(LOCAL_PORT),
)
};
let second_bind_error = if bind_v4_first {
bind_v4(UdpApi::<Ipv4, _>::new(ctx.as_mut())).expect("no conflict");
bind_v6(UdpApi::<Ipv6, _>::new(ctx.as_mut())).expect_err("should conflict")
} else {
bind_v6(UdpApi::<Ipv6, _>::new(ctx.as_mut())).expect("no conflict");
bind_v4(UdpApi::<Ipv4, _>::new(ctx.as_mut())).expect_err("should conflict")
};
assert_eq!(second_bind_error, Either::Right(LocalAddressError::AddressInUse));
}
// Verifies that port availability in both the IPv4 and IPv6 bound socket
// maps is considered when allocating a local port for a dual-stack UDP
// socket listening in both stacks.
#[test_case(IpVersion::V4; "v4_is_constrained")]
#[test_case(IpVersion::V6; "v6_is_constrained")]
fn dual_stack_local_port_alloc(ip_version_with_constrained_ports: IpVersion) {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![
SpecifiedAddr::new(V4_LOCAL_IP.to_ip_addr()).unwrap(),
SpecifiedAddr::new(V6_LOCAL_IP.to_ip_addr()).unwrap(),
],
vec![],
));
// Specifically selected to be in the `EPHEMERAL_RANGE`.
const AVAILABLE_PORT: NonZeroU16 = const_unwrap_option(NonZeroU16::new(54321));
// Densely pack the port space for one IP Version.
for port in 1..=u16::MAX {
let port = NonZeroU16::new(port).unwrap();
if port == AVAILABLE_PORT {
continue;
}
match ip_version_with_constrained_ports {
IpVersion::V4 => {
let mut api = UdpApi::<Ipv4, _>::new(ctx.as_mut());
let listener = api.create();
api.listen(
&listener,
SpecifiedAddr::new(V4_LOCAL_IP).map(|a| ZonedAddr::Unzoned(a)),
Some(port),
)
.expect("listen v4 should succeed")
}
IpVersion::V6 => {
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let listener = api.create();
api.listen(
&listener,
SpecifiedAddr::new(V6_LOCAL_IP).map(|a| ZonedAddr::Unzoned(a)),
Some(port),
)
.expect("listen v6 should succeed")
}
}
}
// Create a listener on the dualstack any address, expecting it to be
// allocated `AVAILABLE_PORT`.
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let listener = api.create();
api.listen(&listener, None, None).expect("dualstack listen should succeed");
let port = assert_matches!(api.get_info(&listener), SocketInfo::Listener(info) => info.local_identifier);
assert_eq!(port, AVAILABLE_PORT);
}
#[test_case(DualStackBindAddr::V4Any; "v4 any")]
#[test_case(DualStackBindAddr::V4Specific; "v4 specific")]
fn dual_stack_enable(bind_addr: DualStackBindAddr) {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![SpecifiedAddr::new(V4_LOCAL_IP).unwrap()],
vec![],
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let bind_addr = bind_addr.v6_addr().unwrap_or(V4_LOCAL_IP_MAPPED);
let listener = api.create();
assert_eq!(api.get_dual_stack_enabled(&listener), Ok(true));
api.set_dual_stack_enabled(&listener, false).expect("can set dual-stack enabled");
// With dual-stack behavior disabled, the IPv6 socket can't bind to
// an IPv4-mapped IPv6 address.
assert_eq!(
api.listen(
&listener,
SpecifiedAddr::new(bind_addr).map(|a| ZonedAddr::Unzoned(a)),
Some(LOCAL_PORT),
),
Err(Either::Right(LocalAddressError::CannotBindToAddress))
);
api.set_dual_stack_enabled(&listener, true).expect("can set dual-stack enabled");
// Try again now that dual-stack sockets are enabled.
assert_eq!(
api.listen(
&listener,
SpecifiedAddr::new(bind_addr).map(|a| ZonedAddr::Unzoned(a)),
Some(LOCAL_PORT),
),
Ok(())
);
}
#[test]
fn dual_stack_bind_unassigned_v4_address() {
const NOT_ASSIGNED_MAPPED: Ipv6Addr = net_ip_v6!("::ffff:8.8.8.8");
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![SpecifiedAddr::new(V4_LOCAL_IP).unwrap()],
vec![],
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let listener = api.create();
assert_eq!(
api.listen(
&listener,
SpecifiedAddr::new(NOT_ASSIGNED_MAPPED).map(|a| ZonedAddr::Unzoned(a)),
Some(LOCAL_PORT),
),
Err(Either::Right(LocalAddressError::CannotBindToAddress))
);
}
// Calling `connect` on an already bound socket will cause the existing
// `listener` entry in the bound state map to be upgraded to a `connected`
// entry. Dual-stack listeners may exist in both the IPv4 and IPv6 bound
// state maps, so make sure both entries are properly removed.
#[test]
fn dual_stack_connect_cleans_up_existing_listener() {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![Ipv6::FAKE_CONFIG.local_ip],
vec![Ipv6::FAKE_CONFIG.remote_ip],
));
const DUAL_STACK_ANY_ADDR: Option<ZonedAddr<SpecifiedAddr<Ipv6Addr>, FakeDeviceId>> = None;
fn assert_listeners(core_ctx: &mut FakeUdpCoreCtx<FakeDeviceId>, expect_present: bool) {
const V4_LISTENER_ADDR: ListenerAddr<
ListenerIpAddr<Ipv4Addr, NonZeroU16>,
FakeWeakDeviceId<FakeDeviceId>,
> = ListenerAddr {
ip: ListenerIpAddr { addr: None, identifier: LOCAL_PORT },
device: None,
};
const V6_LISTENER_ADDR: ListenerAddr<
ListenerIpAddr<Ipv6Addr, NonZeroU16>,
FakeWeakDeviceId<FakeDeviceId>,
> = ListenerAddr {
ip: ListenerIpAddr { addr: None, identifier: LOCAL_PORT },
device: None,
};
transport::udp::DualStackBoundStateContext::with_both_bound_sockets_mut(
get_dual_stack_context(&mut core_ctx.inner),
|_core_ctx, v6_sockets, v4_sockets| {
let v4 = v4_sockets.bound_sockets.listeners().get_by_addr(&V4_LISTENER_ADDR);
let v6 = v6_sockets.bound_sockets.listeners().get_by_addr(&V6_LISTENER_ADDR);
if expect_present {
assert_matches!(v4, Some(_));
assert_matches!(v6, Some(_));
} else {
assert_matches!(v4, None);
assert_matches!(v6, None);
}
},
);
}
// Create a socket and listen on the IPv6 any address. Verify we have
// listener state for both IPv4 and IPv6.
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
assert_eq!(api.listen(&socket, DUAL_STACK_ANY_ADDR, Some(LOCAL_PORT)), Ok(()));
assert_listeners(api.core_ctx(), true);
// Connect the socket to a remote V6 address and verify that both
// the IPv4 and IPv6 listener state has been removed.
assert_eq!(
api.connect(
&socket,
Some(ZonedAddr::Unzoned(Ipv6::FAKE_CONFIG.remote_ip)),
REMOTE_PORT.into(),
),
Ok(())
);
assert_matches!(api.get_info(&socket), SocketInfo::Connected(_));
assert_listeners(api.core_ctx(), false);
}
#[test_case(net_ip_v6!("::"), true; "dual stack any")]
#[test_case(net_ip_v6!("::"), false; "v6 any")]
#[test_case(net_ip_v6!("::ffff:0.0.0.0"), true; "v4 unspecified")]
#[test_case(V4_LOCAL_IP_MAPPED, true; "v4 specified")]
#[test_case(V6_LOCAL_IP, true; "v6 specified dual stack enabled")]
#[test_case(V6_LOCAL_IP, false; "v6 specified dual stack disabled")]
fn dual_stack_get_info(bind_addr: Ipv6Addr, enable_dual_stack: bool) {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs::<
SpecifiedAddr<IpAddr>,
>(
vec![
SpecifiedAddr::new(V4_LOCAL_IP).unwrap().into(),
SpecifiedAddr::new(V6_LOCAL_IP).unwrap().into(),
],
vec![],
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let listener = api.create();
api.set_dual_stack_enabled(&listener, enable_dual_stack)
.expect("can set dual-stack enabled");
let bind_addr = SpecifiedAddr::new(bind_addr);
assert_eq!(
api.listen(&listener, bind_addr.map(|a| ZonedAddr::Unzoned(a)), Some(LOCAL_PORT),),
Ok(())
);
assert_eq!(
api.get_info(&listener),
SocketInfo::Listener(datagram::ListenerInfo {
local_ip: bind_addr.map(StrictlyZonedAddr::new_unzoned_or_panic),
local_identifier: LOCAL_PORT,
})
);
}
#[test_case(net_ip_v6!("::"), true; "dual stack any")]
#[test_case(net_ip_v6!("::"), false; "v6 any")]
#[test_case(net_ip_v6!("::ffff:0.0.0.0"), true; "v4 unspecified")]
#[test_case(V4_LOCAL_IP_MAPPED, true; "v4 specified")]
#[test_case(V6_LOCAL_IP, true; "v6 specified dual stack enabled")]
#[test_case(V6_LOCAL_IP, false; "v6 specified dual stack disabled")]
fn dual_stack_remove_listener(bind_addr: Ipv6Addr, enable_dual_stack: bool) {
// Ensure that when a socket is removed, it doesn't leave behind state
// in the demultiplexing maps. Do this by binding a new socket at the
// same address and asserting success.
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs::<
SpecifiedAddr<IpAddr>,
>(
vec![
SpecifiedAddr::new(V4_LOCAL_IP).unwrap().into(),
SpecifiedAddr::new(V6_LOCAL_IP).unwrap().into(),
],
vec![],
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let mut bind_listener = || {
let listener = api.create();
api.set_dual_stack_enabled(&listener, enable_dual_stack)
.expect("can set dual-stack enabled");
let bind_addr = SpecifiedAddr::new(bind_addr);
assert_eq!(
api.listen(&listener, bind_addr.map(|a| ZonedAddr::Unzoned(a)), Some(LOCAL_PORT)),
Ok(())
);
api.close(listener).into_removed();
};
// The first time should succeed because the state is empty.
bind_listener();
// The second time should succeed because the first removal didn't
// leave any state behind.
bind_listener();
}
#[test_case(V6_REMOTE_IP, true; "This stack with dualstack enabled")]
#[test_case(V6_REMOTE_IP, false; "This stack with dualstack disabled")]
#[test_case(V4_REMOTE_IP_MAPPED, true; "other stack with dualstack enabled")]
fn dualstack_remove_connected(remote_ip: SpecifiedAddr<Ipv6Addr>, enable_dual_stack: bool) {
// Ensure that when a socket is removed, it doesn't leave behind state
// in the demultiplexing maps. Do this by binding a new socket at the
// same address and asserting success.
let mut ctx = datagram::testutil::setup_fake_ctx_with_dualstack_conn_addrs(
Ipv6::UNSPECIFIED_ADDRESS.to_ip_addr(),
remote_ip.into(),
[FakeDeviceId {}],
|device_configs| {
FakeUdpCoreCtx::with_state(FakeDualStackIpSocketCtx::new(device_configs))
},
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let mut bind_connected = || {
let socket = api.create();
api.set_dual_stack_enabled(&socket, enable_dual_stack)
.expect("can set dual-stack enabled");
assert_eq!(
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into(),),
Ok(())
);
api.close(socket).into_removed();
};
// The first time should succeed because the state is empty.
bind_connected();
// The second time should succeed because the first removal didn't
// leave any state behind.
bind_connected();
}
#[test_case(false, V6_REMOTE_IP, Ok(());
"connect to this stack with dualstack disabled")]
#[test_case(true, V6_REMOTE_IP, Ok(());
"connect to this stack with dualstack enabled")]
#[test_case(false, V4_REMOTE_IP_MAPPED, Err(ConnectError::RemoteUnexpectedlyMapped);
"connect to other stack with dualstack disabled")]
#[test_case(true, V4_REMOTE_IP_MAPPED, Ok(());
"connect to other stack with dualstack enabled")]
fn dualstack_connect_unbound(
enable_dual_stack: bool,
remote_ip: SpecifiedAddr<Ipv6Addr>,
expected_outcome: Result<(), ConnectError>,
) {
let mut ctx = datagram::testutil::setup_fake_ctx_with_dualstack_conn_addrs(
Ipv6::UNSPECIFIED_ADDRESS.to_ip_addr(),
remote_ip.into(),
[FakeDeviceId {}],
|device_configs| {
FakeUdpCoreCtx::with_state(FakeDualStackIpSocketCtx::new(device_configs))
},
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
api.set_dual_stack_enabled(&socket, enable_dual_stack).expect("can set dual-stack enabled");
assert_eq!(
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into()),
expected_outcome
);
if expected_outcome.is_ok() {
assert_matches!(
api.get_info(&socket),
SocketInfo::Connected(datagram::ConnInfo{
local_ip: _,
local_identifier: _,
remote_ip: found_remote_ip,
remote_identifier: found_remote_port,
}) if found_remote_ip.addr() == remote_ip &&
found_remote_port == u16::from(REMOTE_PORT)
);
// Disconnect the socket, returning it to the original state.
assert_eq!(api.disconnect(&socket), Ok(()));
}
// Verify the original state is preserved.
assert_eq!(api.get_info(&socket), SocketInfo::Unbound);
}
#[test_case(V6_LOCAL_IP, V6_REMOTE_IP, Ok(());
"listener in this stack connected in this stack")]
#[test_case(V6_LOCAL_IP, V4_REMOTE_IP_MAPPED, Err(ConnectError::RemoteUnexpectedlyMapped);
"listener in this stack connected in other stack")]
#[test_case(Ipv6::UNSPECIFIED_ADDRESS, V6_REMOTE_IP, Ok(());
"listener in both stacks connected in this stack")]
#[test_case(Ipv6::UNSPECIFIED_ADDRESS, V4_REMOTE_IP_MAPPED, Ok(());
"listener in both stacks connected in other stack")]
#[test_case(V4_LOCAL_IP_MAPPED, V6_REMOTE_IP,
Err(ConnectError::RemoteUnexpectedlyNonMapped);
"listener in other stack connected in this stack")]
#[test_case(V4_LOCAL_IP_MAPPED, V4_REMOTE_IP_MAPPED, Ok(());
"listener in other stack connected in other stack")]
fn dualstack_connect_listener(
local_ip: Ipv6Addr,
remote_ip: SpecifiedAddr<Ipv6Addr>,
expected_outcome: Result<(), ConnectError>,
) {
let mut ctx = datagram::testutil::setup_fake_ctx_with_dualstack_conn_addrs(
local_ip.to_ip_addr(),
remote_ip.into(),
[FakeDeviceId {}],
|device_configs| {
FakeUdpCoreCtx::with_state(FakeDualStackIpSocketCtx::new(device_configs))
},
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
assert_eq!(
api.listen(
&socket,
SpecifiedAddr::new(local_ip).map(|local_ip| ZonedAddr::Unzoned(local_ip)),
Some(LOCAL_PORT),
),
Ok(())
);
assert_eq!(
api.connect(&socket, Some(ZonedAddr::Unzoned(remote_ip)), REMOTE_PORT.into()),
expected_outcome
);
if expected_outcome.is_ok() {
assert_matches!(
api.get_info(&socket),
SocketInfo::Connected(datagram::ConnInfo{
local_ip: _,
local_identifier: _,
remote_ip: found_remote_ip,
remote_identifier: found_remote_port,
}) if found_remote_ip.addr() == remote_ip &&
found_remote_port == u16::from(REMOTE_PORT)
);
// Disconnect the socket, returning it to the original state.
assert_eq!(api.disconnect(&socket), Ok(()));
}
// Verify the original state is preserved.
assert_matches!(
api.get_info(&socket),
SocketInfo::Listener(datagram::ListenerInfo {
local_ip: found_local_ip,
local_identifier: found_local_port,
}) if found_local_port == LOCAL_PORT &&
local_ip == found_local_ip.map(
|a| a.addr().get()
).unwrap_or(Ipv6::UNSPECIFIED_ADDRESS)
);
}
#[test_case(V6_REMOTE_IP, V6_REMOTE_IP, Ok(());
"connected in this stack reconnected in this stack")]
#[test_case(V6_REMOTE_IP, V4_REMOTE_IP_MAPPED, Err(ConnectError::RemoteUnexpectedlyMapped);
"connected in this stack reconnected in other stack")]
#[test_case(V4_REMOTE_IP_MAPPED, V6_REMOTE_IP,
Err(ConnectError::RemoteUnexpectedlyNonMapped);
"connected in other stack reconnected in this stack")]
#[test_case(V4_REMOTE_IP_MAPPED, V4_REMOTE_IP_MAPPED, Ok(());
"connected in other stack reconnected in other stack")]
fn dualstack_connect_connected(
original_remote_ip: SpecifiedAddr<Ipv6Addr>,
new_remote_ip: SpecifiedAddr<Ipv6Addr>,
expected_outcome: Result<(), ConnectError>,
) {
let mut ctx = datagram::testutil::setup_fake_ctx_with_dualstack_conn_addrs(
Ipv6::UNSPECIFIED_ADDRESS.to_ip_addr(),
original_remote_ip.into(),
[FakeDeviceId {}],
|device_configs| {
FakeUdpCoreCtx::with_state(FakeDualStackIpSocketCtx::new(device_configs))
},
);
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = api.create();
assert_eq!(
api.connect(&socket, Some(ZonedAddr::Unzoned(original_remote_ip)), REMOTE_PORT.into(),),
Ok(())
);
assert_eq!(
api.connect(
&socket,
Some(ZonedAddr::Unzoned(new_remote_ip)),
OTHER_REMOTE_PORT.into(),
),
expected_outcome
);
let (expected_remote_ip, expected_remote_port) = if expected_outcome.is_ok() {
(new_remote_ip, OTHER_REMOTE_PORT)
} else {
// Verify the original state is preserved.
(original_remote_ip, REMOTE_PORT)
};
assert_matches!(
api.get_info(&socket),
SocketInfo::Connected(datagram::ConnInfo{
local_ip: _,
local_identifier: _,
remote_ip: found_remote_ip,
remote_identifier: found_remote_port,
}) if found_remote_ip.addr() == expected_remote_ip &&
found_remote_port == u16::from(expected_remote_port)
);
// Disconnect the socket and verify it returns to unbound state.
assert_eq!(api.disconnect(&socket), Ok(()));
assert_eq!(api.get_info(&socket), SocketInfo::Unbound);
}
type FakeBoundSocketMap<I> =
UdpBoundSocketMap<I, FakeWeakDeviceId<FakeDeviceId>, FakeUdpBindingsCtx<FakeDeviceId>>;
fn listen<I: Ip + IpExt>(
ip: I::Addr,
port: u16,
) -> AddrVec<I, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec> {
let addr = SpecifiedAddr::new(ip).map(|a| SocketIpAddr::try_from(a).unwrap());
let port = NonZeroU16::new(port).expect("port must be nonzero");
AddrVec::Listen(ListenerAddr {
ip: ListenerIpAddr { addr, identifier: port },
device: None,
})
}
fn listen_device<I: Ip + IpExt>(
ip: I::Addr,
port: u16,
device: FakeWeakDeviceId<FakeDeviceId>,
) -> AddrVec<I, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec> {
let addr = SpecifiedAddr::new(ip).map(|a| SocketIpAddr::try_from(a).unwrap());
let port = NonZeroU16::new(port).expect("port must be nonzero");
AddrVec::Listen(ListenerAddr {
ip: ListenerIpAddr { addr, identifier: port },
device: Some(device),
})
}
fn conn<I: Ip + IpExt>(
local_ip: I::Addr,
local_port: u16,
remote_ip: I::Addr,
remote_port: u16,
) -> AddrVec<I, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec> {
let local_ip = SocketIpAddr::new(local_ip).expect("addr must be specified & non-mapped");
let local_port = NonZeroU16::new(local_port).expect("port must be nonzero");
let remote_ip = SocketIpAddr::new(remote_ip).expect("addr must be specified & non-mapped");
let remote_port = NonZeroU16::new(remote_port).expect("port must be nonzero").into();
AddrVec::Conn(ConnAddr {
ip: ConnIpAddr { local: (local_ip, local_port), remote: (remote_ip, remote_port) },
device: None,
})
}
#[test_case([
(listen(ip_v4!("0.0.0.0"), 1), Sharing::Exclusive),
(listen(ip_v4!("0.0.0.0"), 2), Sharing::Exclusive)],
Ok(()); "listen_any_ip_different_port")]
#[test_case([
(listen(ip_v4!("0.0.0.0"), 1), Sharing::Exclusive),
(listen(ip_v4!("0.0.0.0"), 1), Sharing::Exclusive)],
Err(InsertError::Exists); "any_ip_same_port")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive),
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive)],
Err(InsertError::Exists); "listen_same_specific_ip")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort),
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort)],
Ok(()); "listen_same_specific_ip_reuse_port")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive),
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort)],
Err(InsertError::Exists); "listen_same_specific_ip_exclusive_reuse_port")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort),
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive)],
Err(InsertError::Exists); "listen_same_specific_ip_reuse_port_exclusive")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::ReusePort)],
Ok(()); "conn_shadows_listener_reuse_port")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::Exclusive)],
Err(InsertError::ShadowAddrExists); "conn_shadows_listener_exclusive")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::ReusePort)],
Err(InsertError::ShadowAddrExists); "conn_shadows_listener_exclusive_reuse_port")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::Exclusive)],
Err(InsertError::ShadowAddrExists); "conn_shadows_listener_reuse_port_exclusive")]
#[test_case([
(listen_device(ip_v4!("1.1.1.1"), 1, FakeWeakDeviceId(FakeDeviceId)), Sharing::Exclusive),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::Exclusive)],
Err(InsertError::IndirectConflict); "conn_indirect_conflict_specific_listener")]
#[test_case([
(listen_device(ip_v4!("0.0.0.0"), 1, FakeWeakDeviceId(FakeDeviceId)), Sharing::Exclusive),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::Exclusive)],
Err(InsertError::IndirectConflict); "conn_indirect_conflict_any_listener")]
#[test_case([
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::Exclusive),
(listen_device(ip_v4!("1.1.1.1"), 1, FakeWeakDeviceId(FakeDeviceId)), Sharing::Exclusive)],
Err(InsertError::IndirectConflict); "specific_listener_indirect_conflict_conn")]
#[test_case([
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 2), Sharing::Exclusive),
(listen_device(ip_v4!("0.0.0.0"), 1, FakeWeakDeviceId(FakeDeviceId)), Sharing::Exclusive)],
Err(InsertError::IndirectConflict); "any_listener_indirect_conflict_conn")]
fn bind_sequence<
C: IntoIterator<Item = (AddrVec<Ipv4, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec>, Sharing)>,
>(
spec: C,
expected: Result<(), InsertError>,
) {
let mut primary_ids = Vec::new();
let mut create_socket = || {
let primary = datagram::create_primary_id(());
let id = UdpSocketId(crate::sync::PrimaryRc::clone_strong(&primary));
primary_ids.push(primary);
id
};
let mut map = FakeBoundSocketMap::<Ipv4>::default();
let mut spec = spec.into_iter().peekable();
let mut try_insert = |(addr, options)| match addr {
AddrVec::Conn(c) => map
.conns_mut()
.try_insert(c, options, EitherIpSocket::V4(create_socket()))
.map(|_| ())
.map_err(|(e, _)| e),
AddrVec::Listen(l) => map
.listeners_mut()
.try_insert(l, options, EitherIpSocket::V4(create_socket()))
.map(|_| ())
.map_err(|(e, _)| e),
};
let last = loop {
let one_spec = spec.next().expect("empty list of test cases");
if spec.peek().is_none() {
break one_spec;
} else {
try_insert(one_spec).expect("intermediate bind failed")
}
};
let result = try_insert(last);
assert_eq!(result, expected);
}
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::Exclusive),
(listen(ip_v4!("2.2.2.2"), 2), Sharing::Exclusive),
]; "distinct")]
#[test_case([
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort),
(listen(ip_v4!("1.1.1.1"), 1), Sharing::ReusePort),
]; "listen_reuse_port")]
#[test_case([
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 3), Sharing::ReusePort),
(conn(ip_v4!("1.1.1.1"), 1, ip_v4!("2.2.2.2"), 3), Sharing::ReusePort),
]; "conn_reuse_port")]
fn remove_sequence<I>(spec: I)
where
I: IntoIterator<
Item = (AddrVec<Ipv4, FakeWeakDeviceId<FakeDeviceId>, UdpAddrSpec>, Sharing),
>,
I::IntoIter: ExactSizeIterator,
{
enum Socket<I: IpExt, D: device::WeakId, BT: UdpBindingsTypes, LI, RI> {
Listener(UdpSocketId<I, D, BT>, ListenerAddr<ListenerIpAddr<I::Addr, LI>, D>),
Conn(UdpSocketId<I, D, BT>, ConnAddr<ConnIpAddr<I::Addr, LI, RI>, D>),
}
let spec = spec.into_iter();
let spec_len = spec.len();
let mut primary_ids = Vec::new();
let mut create_socket = || {
let primary = datagram::create_primary_id(());
let id = UdpSocketId(crate::sync::PrimaryRc::clone_strong(&primary));
primary_ids.push(primary);
id
};
for spec in spec.permutations(spec_len) {
let mut map = FakeBoundSocketMap::<Ipv4>::default();
let sockets = spec
.into_iter()
.map(|(addr, options)| match addr {
AddrVec::Conn(c) => map
.conns_mut()
.try_insert(c, options, EitherIpSocket::V4(create_socket()))
.map(|entry| {
Socket::Conn(
assert_matches!(entry.id(), EitherIpSocket::V4(id) => id.clone()),
entry.get_addr().clone(),
)
})
.expect("insert_failed"),
AddrVec::Listen(l) => map
.listeners_mut()
.try_insert(l, options, EitherIpSocket::V4(create_socket()))
.map(|entry| {
Socket::Listener(
assert_matches!(entry.id(), EitherIpSocket::V4(id) => id.clone()),
entry.get_addr().clone(),
)
})
.expect("insert_failed"),
})
.collect::<Vec<_>>();
for socket in sockets {
match socket {
Socket::Listener(l, addr) => {
assert_matches!(
map.listeners_mut().remove(&EitherIpSocket::V4(l), &addr),
Ok(())
);
}
Socket::Conn(c, addr) => {
assert_matches!(
map.conns_mut().remove(&EitherIpSocket::V4(c), &addr),
Ok(())
);
}
}
}
}
}
#[ip_test]
#[test_case(true; "bind to device")]
#[test_case(false; "no bind to device")]
#[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)]
fn loopback_bind_to_device<I: Ip + crate::IpExt + crate::testutil::TestIpExt>(
bind_to_device: bool,
) {
set_logger_for_test();
const HELLO: &'static [u8] = b"Hello";
let (mut ctx, local_device_ids) = I::FAKE_CONFIG.into_builder().build();
let loopback_device_id: DeviceId<crate::testutil::FakeBindingsCtx> = ctx
.core_api()
.device::<LoopbackDevice>()
.add_device_with_default_state(
LoopbackCreationProperties { mtu: net_types::ip::Mtu::new(u16::MAX as u32) },
crate::testutil::DEFAULT_INTERFACE_METRIC,
)
.into();
crate::device::testutil::enable_device(&mut ctx, &loopback_device_id);
let mut api = ctx.core_api().udp::<I>();
let socket = api.create();
api.listen(&socket, None, Some(LOCAL_PORT)).unwrap();
if bind_to_device {
api.set_device(&socket, Some(&local_device_ids[0].clone().into())).unwrap();
}
api.send_to(
&socket,
Some(ZonedAddr::Unzoned(I::FAKE_CONFIG.local_ip)),
LOCAL_PORT.into(),
Buf::new(HELLO.to_vec(), ..),
)
.unwrap();
assert!(crate::testutil::handle_queued_rx_packets(&mut ctx));
// TODO(https://fxbug.dev/42084713): They should both be non-empty. The
// socket map should allow a looped back packet to be delivered despite
// it being bound to a device other than loopback.
if bind_to_device {
assert_matches!(&ctx.bindings_ctx.take_udp_received(&socket)[..], []);
} else {
assert_matches!(
&ctx.bindings_ctx.take_udp_received(&socket)[..],
[packet] => assert_eq!(packet, HELLO)
);
}
}
enum OriginalSocketState {
Unbound,
Listener,
Connected,
}
impl OriginalSocketState {
fn create_socket<I, C>(&self, api: &mut UdpApi<I, C>) -> UdpApiSocketId<I, C>
where
I: TestIpExt,
C: ContextPair,
C::CoreContext: StateContext<I, C::BindingsContext> + CounterContext<UdpCounters<I>>,
C::BindingsContext:
UdpBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
<C::BindingsContext as UdpBindingsTypes>::ExternalData<I>: Default,
{
let socket = api.create();
match self {
OriginalSocketState::Unbound => {}
OriginalSocketState::Listener => {
api.listen(
&socket,
Some(ZonedAddr::Unzoned(I::FAKE_CONFIG.local_ip)),
Some(LOCAL_PORT),
)
.expect("listen should succeed");
}
OriginalSocketState::Connected => {
api.connect(
&socket,
Some(ZonedAddr::Unzoned(I::FAKE_CONFIG.remote_ip)),
UdpRemotePort::Set(REMOTE_PORT),
)
.expect("connect should succeed");
}
}
socket
}
}
#[test_case(OriginalSocketState::Unbound; "unbound")]
#[test_case(OriginalSocketState::Listener; "listener")]
#[test_case(OriginalSocketState::Connected; "connected")]
fn set_get_dual_stack_enabled_v4(original_state: OriginalSocketState) {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![Ipv4::FAKE_CONFIG.local_ip],
vec![Ipv4::FAKE_CONFIG.remote_ip],
));
let mut api = UdpApi::<Ipv4, _>::new(ctx.as_mut());
let socket = original_state.create_socket(&mut api);
for enabled in [true, false] {
assert_eq!(
api.set_dual_stack_enabled(&socket, enabled),
Err(SetDualStackEnabledError::NotCapable)
);
assert_eq!(api.get_dual_stack_enabled(&socket), Err(NotDualStackCapableError));
}
}
#[test_case(OriginalSocketState::Unbound, Ok(()); "unbound")]
#[test_case(OriginalSocketState::Listener, Err(SetDualStackEnabledError::SocketIsBound);
"listener")]
#[test_case(OriginalSocketState::Connected, Err(SetDualStackEnabledError::SocketIsBound);
"connected")]
fn set_get_dual_stack_enabled_v6(
original_state: OriginalSocketState,
expected_result: Result<(), SetDualStackEnabledError>,
) {
let mut ctx =
FakeCtxWithCoreCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
vec![Ipv6::FAKE_CONFIG.local_ip],
vec![Ipv6::FAKE_CONFIG.remote_ip],
));
let mut api = UdpApi::<Ipv6, _>::new(ctx.as_mut());
let socket = original_state.create_socket(&mut api);
// Expect dual stack to be enabled by default.
const ORIGINALLY_ENABLED: bool = true;
assert_eq!(api.get_dual_stack_enabled(&socket), Ok(ORIGINALLY_ENABLED));
for enabled in [false, true] {
assert_eq!(api.set_dual_stack_enabled(&socket, enabled), expected_result);
let expect_enabled = match expected_result {
Ok(_) => enabled,
// If the set was unsuccessful expect the state to be unchanged.
Err(_) => ORIGINALLY_ENABLED,
};
assert_eq!(api.get_dual_stack_enabled(&socket), Ok(expect_enabled));
}
}
}