| // 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::HashSet; |
| use core::num::{NonZeroU16, NonZeroUsize}; |
| use core::ops::RangeInclusive; |
| |
| use log::trace; |
| use net_types::ip::{Ip, IpAddress, IpVersionMarker}; |
| use net_types::{SpecifiedAddr, Witness}; |
| use packet::{BufferMut, ParsablePacket, ParseBuffer, Serializer}; |
| use packet_formats::ip::IpProto; |
| use packet_formats::udp::{UdpPacket, UdpPacketBuilder, UdpPacketRaw, UdpParseArgs}; |
| use specialize_ip_macro::specialize_ip; |
| use thiserror::Error; |
| |
| use crate::algorithm::{PortAlloc, PortAllocImpl, ProtocolFlowId}; |
| use crate::context::{CounterContext, DualStateContext, RngStateContext, RngStateContextExt}; |
| use crate::data_structures::IdMapCollectionKey; |
| use crate::error::{LocalAddressError, NetstackError, RemoteAddressError, SocketError}; |
| use crate::ip::{ |
| icmp::IcmpIpExt, BufferIpTransportContext, BufferTransportIpContext, IpPacketFromArgs, |
| IpTransportContext, TransportIpContext, TransportReceiveError, |
| }; |
| use crate::transport::{ConnAddrMap, ListenerAddrMap}; |
| use crate::{BufferDispatcher, Context, EventDispatcher}; |
| |
| /// A builder for UDP layer state. |
| #[derive(Clone)] |
| pub 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 |
| pub 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: Ip>(self) -> UdpState<I> { |
| UdpState { |
| conn_state: UdpConnectionState::default(), |
| lazy_port_alloc: None, |
| send_port_unreachable: self.send_port_unreachable, |
| } |
| } |
| } |
| |
| /// The state associated with the UDP protocol. |
| pub struct UdpState<I: Ip> { |
| conn_state: UdpConnectionState<I>, |
| /// port_aloc is lazy-initialized when it's used |
| lazy_port_alloc: Option<PortAlloc<UdpConnectionState<I>>>, |
| send_port_unreachable: bool, |
| } |
| |
| impl<I: Ip> Default for UdpState<I> { |
| fn default() -> UdpState<I> { |
| UdpStateBuilder::default().build() |
| } |
| } |
| |
| /// Holder structure that keeps all the connection maps for UDP connections. |
| /// |
| /// `UdpConnectionState` provides a [`PortAllocImpl`] implementation to |
| /// allocate unused local ports. |
| #[derive(Default)] |
| struct UdpConnectionState<I: Ip> { |
| conns: ConnAddrMap<Conn<I::Addr>>, |
| listeners: ListenerAddrMap<Listener<I::Addr>>, |
| wildcard_listeners: ListenerAddrMap<NonZeroU16>, |
| } |
| |
| enum LookupResult<I: Ip> { |
| Conn(UdpConnId<I>, Conn<I::Addr>), |
| Listener(UdpListenerId<I>, Listener<I::Addr>), |
| WildcardListener(UdpListenerId<I>, NonZeroU16), |
| } |
| |
| impl<I: Ip> UdpConnectionState<I> { |
| fn lookup( |
| &self, |
| local_ip: SpecifiedAddr<I::Addr>, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| local_port: NonZeroU16, |
| remote_port: NonZeroU16, |
| ) -> Option<LookupResult<I>> { |
| let conn = Conn { local_ip, remote_ip, local_port, remote_port }; |
| self.conns |
| .get_id_by_addr(&conn) |
| .map(move |id| LookupResult::Conn(UdpConnId::new(id), conn)) |
| .or_else(|| { |
| let listener = Listener { addr: local_ip, port: local_port }; |
| self.listeners.get_by_addr(&listener).map(move |id| { |
| LookupResult::Listener(UdpListenerId::new_specified(id), listener) |
| }) |
| }) |
| .or_else(|| { |
| self.wildcard_listeners.get_by_addr(&local_port).map(move |id| { |
| LookupResult::WildcardListener(UdpListenerId::new_wildcard(id), local_port) |
| }) |
| }) |
| } |
| |
| /// Collects the currently used local ports into a [`HashSet`]. |
| /// |
| /// If `addrs` is empty, `collect_used_local_ports` returns all the local |
| /// ports currently in use, otherwise it returns all the local ports in use |
| /// for the addresses in `addrs`. |
| fn collect_used_local_ports<'a>( |
| &self, |
| addrs: impl ExactSizeIterator<Item = &'a SpecifiedAddr<I::Addr>> + Clone, |
| ) -> HashSet<NonZeroU16> { |
| let mut ports = HashSet::new(); |
| ports.extend(self.wildcard_listeners.addr_to_listener.keys()); |
| if addrs.len() == 0 { |
| // for wildcard addresses, collect ALL local ports |
| ports.extend(self.listeners.addr_to_listener.keys().map(|l| l.port)); |
| ports.extend(self.conns.addr_to_id.keys().map(|c| c.local_port)); |
| } else { |
| // if `addrs` is not empty, just collect the ones that use the same |
| // local addresses. |
| ports.extend(self.listeners.addr_to_listener.keys().filter_map(|l| { |
| if addrs.clone().any(|a| a == &l.addr) { |
| Some(l.port) |
| } else { |
| None |
| } |
| })); |
| ports.extend(self.conns.addr_to_id.keys().filter_map(|c| { |
| if addrs.clone().any(|a| a == &c.local_ip) { |
| Some(c.local_port) |
| } else { |
| None |
| } |
| })); |
| } |
| ports |
| } |
| |
| /// Checks whether the provided port is available to be used for a listener. |
| /// |
| /// If `addr` is `None`, `is_listen_port_available` will only return `true` |
| /// if *no* connections or listeners bound to any addresses are using the |
| /// provided `port`. |
| fn is_listen_port_available( |
| &self, |
| addr: Option<SpecifiedAddr<I::Addr>>, |
| port: NonZeroU16, |
| ) -> bool { |
| self.wildcard_listeners.get_by_addr(&port).is_none() |
| && addr |
| .map(|addr| { |
| self.listeners.get_by_addr(&Listener { addr, port }).is_none() |
| && !self |
| .conns |
| .addr_to_id |
| .keys() |
| .any(|c| c.local_ip == addr && c.local_port == port) |
| }) |
| .unwrap_or_else(|| { |
| !(self.listeners.addr_to_listener.keys().any(|l| l.port == port) |
| || self.conns.addr_to_id.keys().any(|c| c.local_port == port)) |
| }) |
| } |
| } |
| |
| /// Helper function to allocate a local port. |
| /// |
| /// Attempts to allocate a new unused local port with the given flow identifier |
| /// `id`. |
| fn try_alloc_local_port<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &mut C, |
| id: &ProtocolFlowId<I::Addr>, |
| ) -> Option<NonZeroU16> { |
| let (state, rng) = ctx.get_state_rng(); |
| // lazily init port_alloc if it hasn't been inited yet. |
| let port_alloc = state.lazy_port_alloc.get_or_insert_with(|| PortAlloc::new(rng)); |
| port_alloc.try_alloc(&id, &state.conn_state).and_then(NonZeroU16::new) |
| } |
| |
| /// 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: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &mut C, |
| used_ports: &HashSet<NonZeroU16>, |
| ) -> Option<NonZeroU16> { |
| let mut port = UdpConnectionState::<I>::rand_ephemeral(ctx.rng()); |
| for _ in UdpConnectionState::<I>::EPHEMERAL_RANGE { |
| // we can unwrap here because we know that the EPHEMERAL_RANGE doesn't |
| // include 0. |
| let tryport = NonZeroU16::new(port.get()).unwrap(); |
| if !used_ports.contains(&tryport) { |
| return Some(tryport); |
| } |
| port.next(); |
| } |
| None |
| } |
| |
| impl<I: Ip> PortAllocImpl for UdpConnectionState<I> { |
| const TABLE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(20) }; |
| const EPHEMERAL_RANGE: RangeInclusive<u16> = 49152..=65535; |
| type Id = ProtocolFlowId<I::Addr>; |
| |
| 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(); |
| // check if we have any listeners: |
| // return true if we have no listeners or active connections using the |
| // selected local port: |
| self.listeners.get_by_addr(&Listener { addr: *id.local_addr(), port: port }).is_none() |
| && self.wildcard_listeners.get_by_addr(&port).is_none() |
| && self |
| .conns |
| .get_id_by_addr(&Conn::from_protocol_flow_and_local_port(id, port)) |
| .is_none() |
| } |
| } |
| |
| #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
| struct Conn<A: IpAddress> { |
| local_ip: SpecifiedAddr<A>, |
| local_port: NonZeroU16, |
| remote_ip: SpecifiedAddr<A>, |
| remote_port: NonZeroU16, |
| } |
| |
| impl<'a, A: IpAddress> From<&'a Conn<A>> for Conn<A> { |
| fn from(c: &'a Conn<A>) -> Self { |
| c.clone() |
| } |
| } |
| |
| impl<A: IpAddress> Conn<A> { |
| fn from_protocol_flow_and_local_port(id: &ProtocolFlowId<A>, local_port: NonZeroU16) -> Self { |
| Self { |
| local_ip: *id.local_addr(), |
| local_port, |
| remote_ip: *id.remote_addr(), |
| remote_port: id.remote_port(), |
| } |
| } |
| } |
| |
| /// Information associated with a UDP connection |
| pub struct UdpConnInfo<A: IpAddress> { |
| /// The local address associated with a UDP connection. |
| pub local_ip: SpecifiedAddr<A>, |
| /// The local port associated with a UDP connection. |
| pub local_port: NonZeroU16, |
| /// The remote address associated with a UDP connection. |
| pub remote_ip: SpecifiedAddr<A>, |
| /// The remote port associated with a UDP connection. |
| pub remote_port: NonZeroU16, |
| } |
| |
| impl<A: IpAddress> From<Conn<A>> for UdpConnInfo<A> { |
| fn from(c: Conn<A>) -> Self { |
| let Conn { local_ip, local_port, remote_ip, remote_port } = c; |
| Self { local_ip, local_port, remote_ip, remote_port } |
| } |
| } |
| |
| #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
| struct Listener<A: IpAddress> { |
| addr: SpecifiedAddr<A>, |
| port: NonZeroU16, |
| } |
| |
| /// Information associated with a UDP listener |
| pub struct UdpListenerInfo<A: IpAddress> { |
| /// The local address associated with a UDP listener, or `None` for any |
| /// address. |
| pub local_ip: Option<SpecifiedAddr<A>>, |
| /// The local port associated with a UDP listener. |
| pub local_port: NonZeroU16, |
| } |
| |
| impl<A: IpAddress> From<Listener<A>> for UdpListenerInfo<A> { |
| fn from(l: Listener<A>) -> Self { |
| Self { local_ip: Some(l.addr), local_port: l.port } |
| } |
| } |
| |
| impl<A: IpAddress> From<NonZeroU16> for UdpListenerInfo<A> { |
| fn from(local_port: NonZeroU16) -> Self { |
| Self { local_ip: None, local_port } |
| } |
| } |
| |
| /// The ID identifying a UDP connection. |
| /// |
| /// When a new UDP connection is added, it is given a unique `UdpConnId`. These |
| /// are opaque `usize`s which are intentionally allocated as densely as possible |
| /// around 0, making it possible to store any associated data in a `Vec` indexed |
| /// by the ID. `UdpConnId` implements `Into<usize>`. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| pub struct UdpConnId<I: Ip>(usize, IpVersionMarker<I>); |
| |
| impl<I: Ip> UdpConnId<I> { |
| fn new(id: usize) -> UdpConnId<I> { |
| UdpConnId(id, IpVersionMarker::default()) |
| } |
| } |
| |
| impl<I: Ip> From<UdpConnId<I>> for usize { |
| fn from(id: UdpConnId<I>) -> usize { |
| id.0 |
| } |
| } |
| |
| impl<I: Ip> IdMapCollectionKey for UdpConnId<I> { |
| const VARIANT_COUNT: usize = 1; |
| |
| fn get_variant(&self) -> usize { |
| 0 |
| } |
| |
| fn get_id(&self) -> usize { |
| self.0 |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| enum ListenerType { |
| Specified, |
| Wildcard, |
| } |
| |
| /// The ID identifying a UDP listener. |
| /// |
| /// When a new UDP listener is added, it is given a unique `UdpListenerId`. |
| /// These are opaque `usize`s which are intentionally allocated as densely as |
| /// possible around 0, making it possible to store any associated data in a |
| /// `Vec` indexed by the ID. The `listener_type` field is used to look at the |
| /// correct backing `Vec`: `listeners` or `wildcard_listeners`. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| pub struct UdpListenerId<I: Ip> { |
| id: usize, |
| listener_type: ListenerType, |
| _marker: IpVersionMarker<I>, |
| } |
| |
| impl<I: Ip> UdpListenerId<I> { |
| fn new_specified(id: usize) -> Self { |
| UdpListenerId { |
| id, |
| listener_type: ListenerType::Specified, |
| _marker: IpVersionMarker::default(), |
| } |
| } |
| |
| fn new_wildcard(id: usize) -> Self { |
| UdpListenerId { |
| id, |
| listener_type: ListenerType::Wildcard, |
| _marker: IpVersionMarker::default(), |
| } |
| } |
| } |
| |
| impl<I: Ip> IdMapCollectionKey for UdpListenerId<I> { |
| const VARIANT_COUNT: usize = 2; |
| fn get_variant(&self) -> usize { |
| match self.listener_type { |
| ListenerType::Specified => 0, |
| ListenerType::Wildcard => 1, |
| } |
| } |
| fn get_id(&self) -> usize { |
| self.id |
| } |
| } |
| |
| /// An execution context for the UDP protocol. |
| pub trait UdpContext<I: IcmpIpExt>: |
| TransportIpContext<I> + RngStateContext<UdpState<I>> + CounterContext |
| { |
| /// Receive an ICMP error message related to a previously-sent UDP packet. |
| /// |
| /// `err` is the specific error identified by the incoming ICMP error |
| /// message. |
| /// |
| /// Concretely, this method is called when an ICMP error message is received |
| /// which contains an original packet which - based on its source and |
| /// destination IPs and ports - most likely originated from the given |
| /// socket. Note that the offending packet is not guaranteed to have |
| /// originated from the given socket. For example, it may have originated |
| /// from a previous socket with the same addresses, it may be the result of |
| /// packet corruption on the network, it may have been injected by a |
| /// malicious party, etc. |
| fn receive_icmp_error( |
| &mut self, |
| _id: Result<UdpConnId<I>, UdpListenerId<I>>, |
| _err: I::ErrorCode, |
| ) { |
| log_unimplemented!((), "UdpContext::receive_icmp_error: not implemented"); |
| } |
| } |
| |
| /// An execution context for the UDP protocol when a buffer is provided. |
| /// |
| /// `BufferUdpContext` is like [`UdpContext`], except that it also requires that |
| /// the context be capable of receiving frames in buffers of type `B`. This is |
| /// used when a buffer of type `B` is provided to UDP (in particular, in |
| /// [`send_udp_conn`] and [`send_udp_listener`]), and allows any generated |
| /// link-layer frames to reuse that buffer rather than needing to always |
| /// allocate a new one. |
| pub trait BufferUdpContext<I: IcmpIpExt, B: BufferMut>: |
| UdpContext<I> + BufferTransportIpContext<I, B> |
| { |
| /// Receive a UDP packet for a connection. |
| fn receive_udp_from_conn( |
| &mut self, |
| _conn: UdpConnId<I>, |
| _src_ip: I::Addr, |
| _src_port: NonZeroU16, |
| _body: &[u8], |
| ) { |
| log_unimplemented!((), "BufferUdpContext::receive_udp_from_conn: not implemented"); |
| } |
| |
| /// Receive a UDP packet for a listener. |
| fn receive_udp_from_listen( |
| &mut self, |
| _listener: UdpListenerId<I>, |
| _src_ip: I::Addr, |
| _dst_ip: I::Addr, |
| _src_port: Option<NonZeroU16>, |
| _body: &[u8], |
| ) { |
| log_unimplemented!((), "BufferUdpContext::receive_udp_from_listen: not implemented"); |
| } |
| } |
| |
| impl<I: Ip, D: EventDispatcher> DualStateContext<UdpState<I>, D::Rng> for Context<D> { |
| fn get_states_with(&self, _id0: (), _id1: ()) -> (&UdpState<I>, &D::Rng) { |
| #[specialize_ip] |
| fn get<I: Ip, D: EventDispatcher>(ctx: &Context<D>) -> (&UdpState<I>, &D::Rng) { |
| #[ipv4] |
| return (&ctx.state().transport.udpv4, ctx.dispatcher().rng()); |
| #[ipv6] |
| return (&ctx.state().transport.udpv6, ctx.dispatcher().rng()); |
| } |
| |
| get(self) |
| } |
| |
| fn get_states_mut_with(&mut self, _id0: (), _id1: ()) -> (&mut UdpState<I>, &mut D::Rng) { |
| #[specialize_ip] |
| fn get<I: Ip, D: EventDispatcher>(ctx: &mut Context<D>) -> (&mut UdpState<I>, &mut D::Rng) { |
| let (state, dispatcher) = ctx.state_and_dispatcher(); |
| #[ipv4] |
| return (&mut state.transport.udpv4, dispatcher.rng_mut()); |
| #[ipv6] |
| return (&mut state.transport.udpv6, dispatcher.rng_mut()); |
| } |
| |
| get(self) |
| } |
| } |
| |
| impl<I: IcmpIpExt, D: EventDispatcher + UdpEventDispatcher<I>> UdpContext<I> for Context<D> { |
| fn receive_icmp_error( |
| &mut self, |
| id: Result<UdpConnId<I>, UdpListenerId<I>>, |
| err: I::ErrorCode, |
| ) { |
| UdpEventDispatcher::receive_icmp_error(self.dispatcher_mut(), id, err); |
| } |
| } |
| |
| impl<I: IcmpIpExt, B: BufferMut, D: BufferDispatcher<B> + UdpEventDispatcher<I>> |
| BufferUdpContext<I, B> for Context<D> |
| { |
| fn receive_udp_from_conn( |
| &mut self, |
| conn: UdpConnId<I>, |
| src_ip: I::Addr, |
| src_port: NonZeroU16, |
| body: &[u8], |
| ) { |
| self.dispatcher_mut().receive_udp_from_conn(conn, src_ip, src_port, body); |
| } |
| |
| fn receive_udp_from_listen( |
| &mut self, |
| listener: UdpListenerId<I>, |
| src_ip: I::Addr, |
| dst_ip: I::Addr, |
| src_port: Option<NonZeroU16>, |
| body: &[u8], |
| ) { |
| self.dispatcher_mut().receive_udp_from_listen(listener, src_ip, dst_ip, src_port, body); |
| } |
| } |
| |
| /// An event dispatcher for the UDP layer. |
| /// |
| /// See the `EventDispatcher` trait in the crate root for more details. |
| pub trait UdpEventDispatcher<I: IcmpIpExt> { |
| /// Receive a UDP packet for a connection. |
| fn receive_udp_from_conn( |
| &mut self, |
| _conn: UdpConnId<I>, |
| _src_ip: I::Addr, |
| _src_port: NonZeroU16, |
| _body: &[u8], |
| ) { |
| log_unimplemented!((), "UdpEventDispatcher::receive_udp_from_conn: not implemented"); |
| } |
| |
| /// Receive a UDP packet for a listener. |
| fn receive_udp_from_listen( |
| &mut self, |
| _listener: UdpListenerId<I>, |
| _src_ip: I::Addr, |
| _dst_ip: I::Addr, |
| _src_port: Option<NonZeroU16>, |
| _body: &[u8], |
| ) { |
| log_unimplemented!((), "UdpEventDispatcher::receive_udp_from_listen: not implemented"); |
| } |
| |
| /// Receive an ICMP error message related to a previously-sent UDP packet. |
| /// |
| /// `err` is the specific error identified by the incoming ICMP error |
| /// message. |
| /// |
| /// Concretely, this method is called when an ICMP error message is received |
| /// which contains an original packet which - based on its source and |
| /// destination IPs and ports - most likely originated from the given |
| /// socket. Note that the offending packet is not guaranteed to have |
| /// originated from the given socket. For example, it may have originated |
| /// from a previous socket with the same addresses, it may be the result of |
| /// packet corruption on the network, it may have been injected by a |
| /// malicious party, etc. |
| fn receive_icmp_error( |
| &mut self, |
| _id: Result<UdpConnId<I>, UdpListenerId<I>>, |
| _err: I::ErrorCode, |
| ) { |
| log_unimplemented!((), "UdpEventDispatcher::receive_icmp_error: not implemented"); |
| } |
| } |
| |
| /// An implementation of [`IpTransportContext`] for UDP. |
| pub(crate) enum UdpIpTransportContext {} |
| |
| impl<I: IcmpIpExt, C: UdpContext<I>> IpTransportContext<I, C> for UdpIpTransportContext { |
| fn receive_icmp_error( |
| ctx: &mut C, |
| src_ip: Option<SpecifiedAddr<I::Addr>>, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| mut udp_packet: &[u8], |
| err: I::ErrorCode, |
| ) { |
| ctx.increment_counter("UdpIpTransportContext::receive_icmp_error"); |
| trace!("UdpIpTransportContext::receive_icmp_error({:?})", err); |
| |
| // TODO(joshlf): Do something with this `try_unit!` calls other than |
| // just silently returning? |
| let udp_packet = |
| try_unit!(udp_packet.parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<I>::default())); |
| if let (Some(src_ip), Some(src_port), Some(dst_port)) = |
| (src_ip, udp_packet.src_port(), udp_packet.dst_port()) |
| { |
| if let Some(socket) = |
| ctx.get_first_state().conn_state.lookup(src_ip, dst_ip, src_port, dst_port) |
| { |
| let id = match socket { |
| LookupResult::Conn(id, _) => Ok(id), |
| LookupResult::Listener(id, _) | LookupResult::WildcardListener(id, _) => { |
| Err(id) |
| } |
| }; |
| ctx.receive_icmp_error(id, err); |
| } else { |
| trace!("UdpIpTransportContext::receive_icmp_error: Got ICMP error message for nonexistent UDP socket; either the socket responsible has since been removed, or the error message was sent in error or corrupted"); |
| } |
| } else { |
| trace!("UdpIpTransportContext::receive_icmp_error: Got ICMP error message for IP packet with an invalid source or destination IP or port"); |
| } |
| } |
| } |
| |
| impl<I: IcmpIpExt, B: BufferMut, C: BufferUdpContext<I, B>> BufferIpTransportContext<I, B, C> |
| for UdpIpTransportContext |
| { |
| fn receive_ip_packet( |
| ctx: &mut C, |
| _device: Option<C::DeviceId>, |
| src_ip: I::Addr, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| mut buffer: B, |
| ) -> Result<(), (B, TransportReceiveError)> { |
| trace!("received UDP packet: {:x?}", buffer.as_mut()); |
| let packet = if let Ok(packet) = |
| buffer.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src_ip, dst_ip.get())) |
| { |
| packet |
| } else { |
| // TODO(joshlf): Do something with ICMP here? |
| return Ok(()); |
| }; |
| |
| let state = ctx.get_first_state(); |
| |
| if let Some(socket) = SpecifiedAddr::new(src_ip) |
| .and_then(|src_ip| packet.src_port().map(|src_port| (src_ip, src_port))) |
| .and_then(|(src_ip, src_port)| { |
| state.conn_state.lookup(dst_ip, src_ip, packet.dst_port(), src_port) |
| }) |
| { |
| match socket { |
| LookupResult::Conn(id, conn) => ctx.receive_udp_from_conn( |
| id, |
| conn.remote_ip.get(), |
| conn.remote_port, |
| packet.body(), |
| ), |
| LookupResult::Listener(id, _) | LookupResult::WildcardListener(id, _) => ctx |
| .receive_udp_from_listen( |
| id, |
| src_ip, |
| dst_ip.get(), |
| packet.src_port(), |
| packet.body(), |
| ), |
| } |
| Ok(()) |
| } else if state.send_port_unreachable { |
| // Unfortunately, type inference isn't smart enough for us to just |
| // do packet.parse_metadata(). |
| let meta = |
| ParsablePacket::<_, packet_formats::udp::UdpParseArgs<I::Addr>>::parse_metadata( |
| &packet, |
| ); |
| core::mem::drop(packet); |
| buffer.undo_parse(meta); |
| Err((buffer, TransportReceiveError::new_port_unreachable())) |
| } else { |
| Ok(()) |
| } |
| } |
| } |
| |
| /// Sends a single UDP frame without creating a connection or listener. |
| /// |
| /// `send_udp` is equivalent to creating a UDP connection with [`connect_udp`] |
| /// with the same arguments provided to `send_udp`, sending `body` over the |
| /// created connection and, finally, destroying the connection. |
| /// |
| /// `send_udp` fails if the selected 4-tuple conflicts with any existing socket. |
| // TODO(brunodalbo) we may need more arguments here to express REUSEADDR and |
| // BIND_TO_DEVICE options |
| pub fn send_udp<I: IcmpIpExt, B: BufferMut, C: BufferUdpContext<I, B>>( |
| ctx: &mut C, |
| local_ip: Option<SpecifiedAddr<I::Addr>>, |
| local_port: Option<NonZeroU16>, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| remote_port: NonZeroU16, |
| body: B, |
| ) -> crate::error::Result<()> { |
| // TODO(brunodalbo) this can be faster if we just perform the checks but |
| // don't actually create a UDP connection. |
| let tmp_conn = connect_udp(ctx, local_ip, local_port, remote_ip, remote_port) |
| .map_err(|e| NetstackError::Connect(e))?; |
| |
| // Not using `?` here since we need to `remove_udp_conn` even in the case of failure. |
| let ret = send_udp_conn(ctx, tmp_conn, body).map_err(NetstackError::SendUdp); |
| remove_udp_conn(ctx, tmp_conn); |
| |
| ret |
| } |
| |
| /// Send a UDP packet on an existing connection. |
| pub fn send_udp_conn<I: IcmpIpExt, B: BufferMut, C: BufferUdpContext<I, B>>( |
| ctx: &mut C, |
| conn: UdpConnId<I>, |
| body: B, |
| ) -> Result<(), SendError> { |
| let state = ctx.get_first_state(); |
| let Conn { local_ip, local_port, remote_ip, remote_port } = *state |
| .conn_state |
| .conns |
| .get_conn_by_id(conn.0) |
| .expect("transport::udp::send_udp_conn: no such conn"); |
| |
| ctx.send_frame( |
| IpPacketFromArgs::new(local_ip, remote_ip, IpProto::Udp), |
| body.encapsulate(UdpPacketBuilder::new( |
| local_ip.into_addr(), |
| remote_ip.into_addr(), |
| Some(local_port), |
| remote_port, |
| )), |
| ) |
| .map_err(Into::into) |
| } |
| |
| /// Send a UDP packet on an existing listener. |
| /// |
| /// `send_udp_listener` sends a UDP packet on an existing listener. The caller |
| /// must specify the local address in order to disambiguate in case the listener |
| /// is bound to multiple local addresses. If the listener is not bound to the |
| /// local address provided, `send_udp_listener` will fail. |
| /// |
| /// # Panics |
| /// |
| /// `send_udp_listener` panics if `listener` is not associated with a listener |
| /// for this IP version. |
| pub fn send_udp_listener<I: IcmpIpExt, B: BufferMut, C: BufferUdpContext<I, B>>( |
| ctx: &mut C, |
| listener: UdpListenerId<I>, |
| local_ip: Option<SpecifiedAddr<I::Addr>>, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| remote_port: NonZeroU16, |
| body: B, |
| ) -> Result<(), SendError> { |
| let local_ip = match local_ip { |
| Some(a) => a, |
| // TODO(brunodalbo) this may cause problems when we don't match the |
| // bound listener addresses, we should revisit whether that check is |
| // actually necessary. |
| // Also, if the local address is a multicast address this function |
| // should probably fail and `send_udp` must be used instead |
| None => ctx |
| .local_address_for_remote(remote_ip) |
| .ok_or(SendError::Remote(RemoteAddressError::NoRoute))?, |
| }; |
| if !ctx.is_local_addr(local_ip.get()) { |
| return Err(SendError::Local(LocalAddressError::CannotBindToAddress)); |
| } |
| |
| let state = ctx.get_first_state(); |
| |
| let local_port = match listener.listener_type { |
| ListenerType::Specified => { |
| let addrs = state |
| .conn_state |
| .listeners |
| .get_by_listener(listener.id) |
| .expect("specified listener not found"); |
| // We found the listener. Make sure at least one of the addresses |
| // associated with it is the local_ip the caller passed. |
| addrs |
| .iter() |
| .find_map(|addr| if addr.addr == local_ip { Some(addr.port) } else { None }) |
| .ok_or(SendError::Local(LocalAddressError::AddressMismatch))? |
| } |
| ListenerType::Wildcard => { |
| let ports = state |
| .conn_state |
| .wildcard_listeners |
| .get_by_listener(listener.id) |
| .expect("wildcard listener not found"); |
| ports[0] |
| } |
| }; |
| |
| ctx.send_frame( |
| IpPacketFromArgs::new(local_ip, remote_ip, IpProto::Udp), |
| body.encapsulate(UdpPacketBuilder::new( |
| local_ip.into_addr(), |
| remote_ip.into_addr(), |
| Some(local_port), |
| remote_port, |
| )), |
| )?; |
| |
| Ok(()) |
| } |
| |
| /// Create a UDP connection. |
| /// |
| /// `connect_udp` binds `conn` as a connection to the remote address and port. |
| /// It is also bound to the local address and port, meaning that packets sent on |
| /// this connection will always come from that address and port. If `local_ip` |
| /// is `None`, then the local address will be chosen based on the route to the |
| /// remote address. If `local_port` is `None`, then one will be chosen from the |
| /// available local ports. |
| /// |
| /// If both `local_ip` and `local_port` are specified, but conflict with an |
| /// existing connection or listener, `connect_udp` will fail. 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), `connect_udp` will fail. If there is no route to `remote_ip`, |
| /// `connect_udp` will fail. |
| pub fn connect_udp<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &mut C, |
| local_ip: Option<SpecifiedAddr<I::Addr>>, |
| local_port: Option<NonZeroU16>, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| remote_port: NonZeroU16, |
| ) -> Result<UdpConnId<I>, SocketError> { |
| let default_local = ctx |
| .local_address_for_remote(remote_ip) |
| .ok_or(SocketError::Remote(RemoteAddressError::NoRoute))?; |
| |
| let local_ip = local_ip.unwrap_or(default_local); |
| |
| if !ctx.is_local_addr(local_ip.get()) { |
| return Err(SocketError::Local(LocalAddressError::CannotBindToAddress)); |
| } |
| let local_port = if let Some(local_port) = local_port { |
| local_port |
| } else { |
| try_alloc_local_port(ctx, &ProtocolFlowId::new(local_ip, remote_ip, remote_port)) |
| .ok_or(SocketError::Local(LocalAddressError::FailedToAllocateLocalPort))? |
| }; |
| |
| let c = Conn { local_ip, local_port, remote_ip, remote_port }; |
| let listener = Listener { addr: local_ip, port: local_port }; |
| let state = ctx.get_first_state_mut(); |
| if state.conn_state.conns.get_id_by_addr(&c).is_some() |
| || state.conn_state.listeners.get_by_addr(&listener).is_some() |
| { |
| return Err(SocketError::Local(LocalAddressError::AddressInUse)); |
| } |
| Ok(UdpConnId::new(state.conn_state.conns.insert(c.clone(), c))) |
| } |
| |
| /// Removes a previously registered UDP connection. |
| /// |
| /// `remove_udp_conn` removes a previously registered UDP connection indexed by |
| /// the [`UpConnId`] `id`. It returns the [`UdpConnInfo`] information that was |
| /// associated with that UDP connection. |
| /// |
| /// # Panics |
| /// |
| /// `remove_udp_conn` panics if `id` is not a valid `UdpConnId`. |
| pub fn remove_udp_conn<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &mut C, |
| id: UdpConnId<I>, |
| ) -> UdpConnInfo<I::Addr> { |
| let state = ctx.get_first_state_mut(); |
| state.conn_state.conns.remove_by_id(id.into()).expect("UDP connection not found").into() |
| } |
| |
| /// Gets the [`UdpConnInfo`] associated with the UDP connection referenced by [`id`]. |
| /// |
| /// # Panics |
| /// |
| /// `get_udp_conn_info` panics if `id` is not a valid `UdpConnId`. |
| pub fn get_udp_conn_info<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &C, |
| id: UdpConnId<I>, |
| ) -> UdpConnInfo<I::Addr> { |
| ctx.get_first_state() |
| .conn_state |
| .conns |
| .get_conn_by_id(id.into()) |
| .expect("UDP connection not found") |
| .clone() |
| .into() |
| } |
| |
| /// Listen on for incoming UDP packets. |
| /// |
| /// `listen_udp` registers `listener` 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 `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. |
| /// |
| /// # Panics |
| /// |
| /// `listen_udp` panics if `listener` is already in use. |
| pub fn listen_udp<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &mut C, |
| addr: Option<SpecifiedAddr<I::Addr>>, |
| port: Option<NonZeroU16>, |
| ) -> Result<UdpListenerId<I>, SocketError> { |
| let port = if let Some(port) = port { |
| if !ctx.get_first_state().conn_state.is_listen_port_available(addr, port) { |
| return Err(SocketError::Local(LocalAddressError::AddressInUse)); |
| } |
| port |
| } else { |
| let used_ports = ctx |
| .get_first_state_mut() |
| .conn_state |
| .collect_used_local_ports(addr.as_ref().into_iter()); |
| try_alloc_listen_port(ctx, &used_ports) |
| .ok_or(SocketError::Local(LocalAddressError::FailedToAllocateLocalPort))? |
| }; |
| match addr { |
| None => { |
| let state = ctx.get_first_state_mut(); |
| Ok(UdpListenerId::new_wildcard( |
| state.conn_state.wildcard_listeners.insert(alloc::vec![port]), |
| )) |
| } |
| Some(addr) => { |
| if !ctx.is_local_addr(addr.get()) { |
| return Err(SocketError::Local(LocalAddressError::CannotBindToAddress)); |
| } |
| let state = ctx.get_first_state_mut(); |
| Ok(UdpListenerId::new_specified( |
| state.conn_state.listeners.insert(alloc::vec![Listener { addr, port }]), |
| )) |
| } |
| } |
| } |
| |
| /// Removes a previously registered UDP listener. |
| /// |
| /// `remove_udp_listener` removes a previously registered UDP listener indexed |
| /// by the [`UdpListenerId`] `id`. It returns the [`UdpListenerInfo`] |
| /// information that was associated with that UDP listener. |
| /// |
| /// # Panics |
| /// |
| /// `remove_listener` panics if `id` is not a valid `UdpListenerId`. |
| pub fn remove_udp_listener<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &mut C, |
| id: UdpListenerId<I>, |
| ) -> UdpListenerInfo<I::Addr> { |
| let state = ctx.get_first_state_mut(); |
| match id.listener_type { |
| ListenerType::Specified => state |
| .conn_state |
| .listeners |
| .remove_by_listener(id.id) |
| .expect("Invalid UDP listener ID") |
| // NOTE(brunodalbo) ListenerAddrMap keeps vecs internally, but we |
| // always only add a single address, so unwrap the first one |
| .first() |
| .expect("Unexpected empty UDP listener") |
| .clone() |
| .into(), |
| ListenerType::Wildcard => state |
| .conn_state |
| .wildcard_listeners |
| .remove_by_listener(id.id) |
| .expect("Invalid UDP listener ID") |
| // NOTE(brunodalbo) ListenerAddrMap keeps vecs internally, but we |
| // always only add a single address, so unwrap the first one |
| .first() |
| .expect("Unexpected empty UDP listener") |
| .clone() |
| .into(), |
| } |
| } |
| |
| /// Gets the [`UdpListenerInfo`] associated with the UDP listener referenced by |
| /// [`id`]. |
| /// |
| /// # Panics |
| /// |
| /// `get_udp_conn_info` panics if `id` is not a valid `UdpListenerId`. |
| pub fn get_udp_listener_info<I: IcmpIpExt, C: UdpContext<I>>( |
| ctx: &C, |
| id: UdpListenerId<I>, |
| ) -> UdpListenerInfo<I::Addr> { |
| let state = ctx.get_first_state(); |
| match id.listener_type { |
| ListenerType::Specified => state |
| .conn_state |
| .listeners |
| .get_by_listener(id.id) |
| .expect("UDP listener not found") |
| // NOTE(brunodalbo) ListenerAddrMap keeps vecs internally, but we |
| // always only add a single address, so unwrap the first one |
| .first() |
| .map(|l| l.clone().into()) |
| .expect("Unexpected empty UDP listener"), |
| ListenerType::Wildcard => state |
| .conn_state |
| .wildcard_listeners |
| .get_by_listener(id.id) |
| .expect("UDP listener not found") |
| // NOTE(brunodalbo) ListenerAddrMap keeps vecs internally, but we |
| // always only add a single address, so unwrap the first one |
| .first() |
| .map(|l| l.clone().into()) |
| .expect("Unexpected empty UDP listener"), |
| } |
| } |
| |
| /// Error type for send errors. |
| #[derive(Error, Debug, PartialEq)] |
| pub enum SendError { |
| // TODO(maufflick): Flesh this type out when the underlying error information becomes |
| // available (and probably remove this "unknown" error). |
| /// Failed to send for an unknown reason. |
| #[error("send failed")] |
| Unknown, |
| |
| #[error("{}", _0)] |
| /// Errors related to the local address. |
| Local(LocalAddressError), |
| |
| #[error("{}", _0)] |
| /// Errors related to the remote address. |
| Remote(RemoteAddressError), |
| } |
| |
| // This conversion from a non-error type into an error isn't ideal. |
| // TODO(maufflick): This will be unnecessary/require changes when send_frame returns a proper error. |
| impl<S: Serializer> From<S> for SendError { |
| fn from(_s: S) -> SendError { |
| // TODO(maufflick): Include useful information about the underlying error once propagated. |
| SendError::Unknown |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use packet::{Buf, InnerPacketBuilder, Serializer}; |
| use packet_formats::icmp::{Icmpv4DestUnreachableCode, Icmpv6DestUnreachableCode}; |
| use packet_formats::ip::{IpExt, IpPacketBuilder}; |
| use packet_formats::ipv4::{Ipv4Header, Ipv4PacketRaw}; |
| use packet_formats::ipv6::{Ipv6Header, Ipv6PacketRaw}; |
| use rand_xorshift::XorShiftRng; |
| use specialize_ip_macro::ip_test; |
| |
| use super::*; |
| use crate::ip::{ |
| icmp::{Icmpv4ErrorCode, Icmpv6ErrorCode}, |
| DummyDeviceId, IpDeviceIdContext, |
| }; |
| use crate::testutil::{set_logger_for_test, FakeCryptoRng, TestIpExt}; |
| |
| /// The listener data sent through a [`DummyUdpContext`]. |
| struct ListenData<I: Ip> { |
| listener: UdpListenerId<I>, |
| src_ip: I::Addr, |
| dst_ip: I::Addr, |
| src_port: Option<NonZeroU16>, |
| body: Vec<u8>, |
| } |
| |
| /// The UDP connection data sent through a [`DummyUdpContext`]. |
| struct ConnData<I: Ip> { |
| conn: UdpConnId<I>, |
| body: Vec<u8>, |
| } |
| |
| /// An ICMP error delivered to a [`DummyUdpContext`]. |
| #[derive(Debug, Eq, PartialEq)] |
| struct IcmpError<I: IcmpIpExt> { |
| id: Result<UdpConnId<I>, UdpListenerId<I>>, |
| err: I::ErrorCode, |
| } |
| |
| struct DummyUdpContext<I: IcmpIpExt> { |
| state: UdpState<I>, |
| listen_data: Vec<ListenData<I>>, |
| conn_data: Vec<ConnData<I>>, |
| icmp_errors: Vec<IcmpError<I>>, |
| extra_local_addrs: Vec<I::Addr>, |
| treat_address_unroutable: Option<Box<dyn Fn(&<I as Ip>::Addr) -> bool>>, |
| } |
| |
| impl<I: IcmpIpExt> Default for DummyUdpContext<I> { |
| fn default() -> Self { |
| DummyUdpContext { |
| state: Default::default(), |
| listen_data: Default::default(), |
| conn_data: Default::default(), |
| icmp_errors: Default::default(), |
| extra_local_addrs: Vec::new(), |
| treat_address_unroutable: None, |
| } |
| } |
| } |
| |
| type DummyContext<I> = crate::context::testutil::DummyContext< |
| DummyUdpContext<I>, |
| (), |
| IpPacketFromArgs<<I as Ip>::Addr>, |
| >; |
| |
| impl<I: IcmpIpExt> IpDeviceIdContext for DummyContext<I> { |
| type DeviceId = DummyDeviceId; |
| } |
| |
| impl<I: TestIpExt + IcmpIpExt> TransportIpContext<I> for DummyContext<I> { |
| fn is_local_addr(&self, addr: <I as Ip>::Addr) -> bool { |
| local_ip::<I>().into_addr() == addr || self.get_ref().extra_local_addrs.contains(&addr) |
| } |
| |
| fn local_address_for_remote( |
| &self, |
| remote: SpecifiedAddr<<I as Ip>::Addr>, |
| ) -> Option<SpecifiedAddr<<I as Ip>::Addr>> { |
| if let Some(treat_address_unroutable) = &self.get_ref().treat_address_unroutable { |
| if treat_address_unroutable(&remote) { |
| return None; |
| } |
| } |
| Some(local_ip::<I>()) |
| } |
| } |
| |
| impl<I: IcmpIpExt> DualStateContext<UdpState<I>, FakeCryptoRng<XorShiftRng>> for DummyContext<I> { |
| fn get_states_with( |
| &self, |
| _id0: (), |
| _id1: (), |
| ) -> (&UdpState<I>, &FakeCryptoRng<XorShiftRng>) { |
| let (state, rng): (&DummyUdpContext<I>, _) = self.get_states(); |
| (&state.state, rng) |
| } |
| |
| fn get_states_mut_with( |
| &mut self, |
| _id0: (), |
| _id1: (), |
| ) -> (&mut UdpState<I>, &mut FakeCryptoRng<XorShiftRng>) { |
| let (state, rng): (&mut DummyUdpContext<I>, _) = self.get_states_mut(); |
| (&mut state.state, rng) |
| } |
| } |
| |
| impl<I: TestIpExt + IcmpIpExt> UdpContext<I> for DummyContext<I> { |
| fn receive_icmp_error( |
| &mut self, |
| id: Result<UdpConnId<I>, UdpListenerId<I>>, |
| err: I::ErrorCode, |
| ) { |
| self.get_mut().icmp_errors.push(IcmpError { id, err }) |
| } |
| } |
| impl<I: TestIpExt + IcmpIpExt, B: BufferMut> BufferUdpContext<I, B> for DummyContext<I> { |
| fn receive_udp_from_conn( |
| &mut self, |
| conn: UdpConnId<I>, |
| _src_ip: <I as Ip>::Addr, |
| _src_port: NonZeroU16, |
| body: &[u8], |
| ) { |
| self.get_mut().conn_data.push(ConnData { conn, body: body.to_owned() }) |
| } |
| |
| fn receive_udp_from_listen( |
| &mut self, |
| listener: UdpListenerId<I>, |
| src_ip: <I as Ip>::Addr, |
| dst_ip: <I as Ip>::Addr, |
| src_port: Option<NonZeroU16>, |
| body: &[u8], |
| ) { |
| self.get_mut().listen_data.push(ListenData { |
| listener, |
| src_ip, |
| dst_ip, |
| src_port, |
| body: body.to_owned(), |
| }) |
| } |
| } |
| |
| 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) |
| } |
| |
| /// Helper function to inject an UDP packet with the provided parameters. |
| fn receive_udp_packet<I: TestIpExt + IcmpIpExt>( |
| ctx: &mut DummyContext<I>, |
| src_ip: I::Addr, |
| dst_ip: I::Addr, |
| src_port: NonZeroU16, |
| dst_port: NonZeroU16, |
| body: &[u8], |
| ) { |
| let builder = UdpPacketBuilder::new(src_ip, dst_ip, Some(src_port), dst_port); |
| let buffer = |
| Buf::new(body.to_owned(), ..).encapsulate(builder).serialize_vec_outer().unwrap(); |
| UdpIpTransportContext::receive_ip_packet( |
| ctx, |
| Some(DummyDeviceId), |
| src_ip, |
| SpecifiedAddr::new(dst_ip).unwrap(), |
| buffer, |
| ) |
| .expect("Receive IP packet succeeds"); |
| } |
| |
| /// 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 + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| // Create a listener on local port 100, bound to the local IP: |
| let listener = listen_udp::<I, _>(&mut ctx, Some(local_ip), NonZeroU16::new(100)) |
| .expect("listen_udp failed"); |
| assert_eq!(listener.listener_type, ListenerType::Specified); |
| |
| // Inject a packet and check that the context receives it: |
| let body = [1, 2, 3, 4, 5]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip.into_addr(), |
| local_ip.into_addr(), |
| NonZeroU16::new(200).unwrap(), |
| NonZeroU16::new(100).unwrap(), |
| &body[..], |
| ); |
| |
| let listen_data = &ctx.get_ref().listen_data; |
| assert_eq!(listen_data.len(), 1); |
| let pkt = &listen_data[0]; |
| assert_eq!(pkt.listener, listener); |
| assert_eq!(pkt.src_ip, remote_ip.into_addr()); |
| assert_eq!(pkt.dst_ip, local_ip.into_addr()); |
| assert_eq!(pkt.src_port.unwrap().get(), 200); |
| assert_eq!(pkt.body, &body[..]); |
| |
| // Send a packet providing a local ip: |
| send_udp_listener( |
| &mut ctx, |
| listener, |
| Some(local_ip), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| Buf::new(body.to_owned(), ..), |
| ) |
| .expect("send_udp_listener failed"); |
| // And send a packet that doesn't: |
| send_udp_listener( |
| &mut ctx, |
| listener, |
| None, |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| Buf::new(body.to_owned(), ..), |
| ) |
| .expect("send_udp_listener failed"); |
| let frames = ctx.frames(); |
| assert_eq!(frames.len(), 2); |
| let check_frame = |(meta, frame_body): &(IpPacketFromArgs<I::Addr>, Vec<u8>)| { |
| assert_eq!(meta.src_ip, local_ip); |
| assert_eq!(meta.dst_ip, remote_ip); |
| assert_eq!(meta.proto, IpProto::Udp); |
| let mut buf = &frame_body[..]; |
| let packet = UdpPacket::parse( |
| &mut buf, |
| UdpParseArgs::new(meta.src_ip.into_addr(), meta.dst_ip.into_addr()), |
| ) |
| .expect("Parsed sent UDP packet"); |
| assert_eq!(packet.src_port().unwrap().get(), 100); |
| assert_eq!(packet.dst_port().get(), 200); |
| assert_eq!(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 + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| |
| let body = [1, 2, 3, 4, 5]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip.into_addr(), |
| local_ip.into_addr(), |
| NonZeroU16::new(200).unwrap(), |
| NonZeroU16::new(100).unwrap(), |
| &body[..], |
| ); |
| assert_eq!(ctx.get_ref().listen_data.len(), 0); |
| assert_eq!(ctx.get_ref().conn_data.len(), 0); |
| } |
| |
| /// 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 + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| // create a UDP connection with a specified local port and local ip: |
| let conn = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(NonZeroU16::new(100).unwrap()), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| |
| // Inject a UDP packet and see if we receive it on the context: |
| let body = [1, 2, 3, 4, 5]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip.into_addr(), |
| local_ip.into_addr(), |
| NonZeroU16::new(200).unwrap(), |
| NonZeroU16::new(100).unwrap(), |
| &body[..], |
| ); |
| |
| let conn_data = &ctx.get_ref().conn_data; |
| assert_eq!(conn_data.len(), 1); |
| let pkt = &conn_data[0]; |
| assert_eq!(pkt.conn, conn); |
| assert_eq!(pkt.body, &body[..]); |
| |
| // Now try to send something over this new connection: |
| send_udp_conn(&mut ctx, conn, Buf::new(body.to_owned(), ..)) |
| .expect("send_udp_conn returned an error"); |
| |
| let frames = ctx.frames(); |
| assert_eq!(frames.len(), 1); |
| |
| // check first frame: |
| let (meta, frame_body) = &frames[0]; |
| assert_eq!(meta.src_ip, local_ip); |
| assert_eq!(meta.dst_ip, remote_ip); |
| assert_eq!(meta.proto, IpProto::Udp); |
| let mut buf = &frame_body[..]; |
| let packet = UdpPacket::parse( |
| &mut buf, |
| UdpParseArgs::new(meta.src_ip.into_addr(), meta.dst_ip.into_addr()), |
| ) |
| .expect("Parsed sent UDP packet"); |
| assert_eq!(packet.src_port().unwrap().get(), 100); |
| assert_eq!(packet.dst_port().get(), 200); |
| assert_eq!(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 + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| // set dummy context callback to treat all addresses as unroutable. |
| ctx.get_mut().treat_address_unroutable = Some(Box::new(|_address| true)); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| // create a UDP connection with a specified local port and local ip: |
| let conn_err = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(NonZeroU16::new(100).unwrap()), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .unwrap_err(); |
| |
| assert_eq!(conn_err, SocketError::Remote(RemoteAddressError::NoRoute)); |
| } |
| |
| /// Tests that UDP connections fail with an appropriate error when local address is non-local. |
| #[ip_test] |
| fn test_udp_conn_cannot_bind<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| |
| // use remote address to trigger SocketError::CannotBindToAddress. |
| let local_ip = remote_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| // create a UDP connection with a specified local port and local ip: |
| let conn_err = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(NonZeroU16::new(100).unwrap()), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .unwrap_err(); |
| |
| assert_eq!(conn_err, SocketError::Local(LocalAddressError::CannotBindToAddress)); |
| } |
| |
| /// Tests that UDP connections fail with an appropriate error when local ports are exhausted. |
| #[ip_test] |
| fn test_udp_conn_exhausted<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| |
| let local_ip = local_ip::<I>(); |
| // exhaust local ports to trigger FailedToAllocateLocalPort error. |
| for port_num in UdpConnectionState::<I>::EPHEMERAL_RANGE { |
| DualStateContext::<UdpState<I>, _>::get_first_state_mut(&mut ctx) |
| .conn_state |
| .listeners |
| .insert(vec![Listener { |
| addr: local_ip, |
| port: NonZeroU16::new(port_num).unwrap(), |
| }]); |
| } |
| |
| let remote_ip = remote_ip::<I>(); |
| let conn_err = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| None, |
| remote_ip, |
| NonZeroU16::new(100).unwrap(), |
| ) |
| .unwrap_err(); |
| |
| assert_eq!(conn_err, SocketError::Local(LocalAddressError::FailedToAllocateLocalPort)); |
| } |
| |
| /// Tests that UDP connections fail with an appropriate error when the connection is in use. |
| #[ip_test] |
| fn test_udp_conn_in_use<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| |
| // use remote address to trigger SocketError::CannotBindToAddress. |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| |
| let local_port = NonZeroU16::new(100).unwrap(); |
| |
| // Tie up the connection so the second call to `connect_udp` fails. |
| let _ = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(local_port), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .expect("Initial call to connect_udp was expected to succeed"); |
| |
| // create a UDP connection with a specified local port and local ip: |
| let conn_err = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(local_port), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .unwrap_err(); |
| |
| assert_eq!(conn_err, SocketError::Local(LocalAddressError::AddressInUse)); |
| } |
| |
| #[ip_test] |
| fn test_send_udp<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| |
| // UDP connection count should be zero before and after `send_udp` call. |
| assert_eq!( |
| DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .conns |
| .addr_to_id |
| .keys() |
| .len(), |
| 0 |
| ); |
| |
| let body = [1, 2, 3, 4, 5]; |
| // Try to send something with send_udp |
| send_udp( |
| &mut ctx, |
| Some(local_ip), |
| NonZeroU16::new(100), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| Buf::new(body.to_vec(), ..), |
| ) |
| .expect("send_udp failed"); |
| |
| // UDP connection count should be zero before and after `send_udp` call. |
| assert_eq!( |
| DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .conns |
| .addr_to_id |
| .keys() |
| .len(), |
| 0 |
| ); |
| let frames = ctx.frames(); |
| assert_eq!(frames.len(), 1); |
| |
| // check first frame: |
| let (meta, frame_body) = &frames[0]; |
| assert_eq!(meta.src_ip, local_ip); |
| assert_eq!(meta.dst_ip, remote_ip); |
| assert_eq!(meta.proto, IpProto::Udp); |
| let mut buf = &frame_body[..]; |
| let packet = UdpPacket::parse( |
| &mut buf, |
| UdpParseArgs::new(meta.src_ip.into_addr(), meta.dst_ip.into_addr()), |
| ) |
| .expect("Parsed sent UDP packet"); |
| assert_eq!(packet.src_port().unwrap().get(), 100); |
| assert_eq!(packet.dst_port().get(), 200); |
| assert_eq!(packet.body(), &body[..]); |
| } |
| |
| /// Tests that `send_udp` propogates errors. |
| #[ip_test] |
| fn test_send_udp_errors<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| |
| let mut ctx = DummyContext::<I>::default(); |
| |
| // use invalid local ip to force a CannotBindToAddress error. |
| let local_ip = remote_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| |
| let body = [1, 2, 3, 4, 5]; |
| // Try to send something with send_udp |
| let send_error = send_udp( |
| &mut ctx, |
| Some(local_ip), |
| NonZeroU16::new(100), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| Buf::new(body.to_vec(), ..), |
| ) |
| .expect_err("send_udp unexpectedly succeeded"); |
| |
| assert_eq!( |
| send_error, |
| NetstackError::Connect(SocketError::Local(LocalAddressError::CannotBindToAddress)) |
| ); |
| } |
| |
| /// Tests that `send_udp` cleans up after errors. |
| #[ip_test] |
| fn test_send_udp_errors_cleanup<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| |
| let mut ctx = DummyContext::<I>::default(); |
| |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| |
| // UDP connection count should be zero before and after `send_udp` call. |
| assert_eq!( |
| DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .conns |
| .addr_to_id |
| .keys() |
| .len(), |
| 0 |
| ); |
| |
| // Instruct the dummy frame context to throw errors. |
| let frames: &mut crate::context::testutil::DummyFrameContext<IpPacketFromArgs<I::Addr>> = |
| ctx.as_mut(); |
| frames.set_should_error_for_frame(|_frame_meta| true); |
| |
| let body = [1, 2, 3, 4, 5]; |
| // Try to send something with send_udp |
| let send_error = send_udp( |
| &mut ctx, |
| Some(local_ip), |
| NonZeroU16::new(100), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| Buf::new(body.to_vec(), ..), |
| ) |
| .expect_err("send_udp unexpectedly succeeded"); |
| |
| assert_eq!(send_error, NetstackError::SendUdp(SendError::Unknown)); |
| |
| // UDP connection count should be zero before and after `send_udp` call (even in the case |
| // of errors). |
| assert_eq!( |
| DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .conns |
| .addr_to_id |
| .keys() |
| .len(), |
| 0 |
| ); |
| } |
| |
| /// 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 + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| // create a UDP connection with a specified local port and local ip: |
| let conn = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(NonZeroU16::new(100).unwrap()), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| |
| // Instruct the dummy frame context to throw errors. |
| let frames: &mut crate::context::testutil::DummyFrameContext<IpPacketFromArgs<I::Addr>> = |
| ctx.as_mut(); |
| frames.set_should_error_for_frame(|_frame_meta| true); |
| |
| // Now try to send something over this new connection: |
| let send_err = send_udp_conn(&mut ctx, conn, Buf::new(Vec::new(), ..)).unwrap_err(); |
| assert_eq!(send_err, SendError::Unknown); |
| } |
| |
| /// Tests that if we have multiple listeners and connections, demuxing the |
| /// flows is performed correctly. |
| #[ip_test] |
| fn test_udp_demux<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| 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 remote_port_a = NonZeroU16::new(200).unwrap(); |
| // Create some UDP connections and listeners: |
| let conn1 = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(local_port_d), |
| remote_ip_a, |
| remote_port_a, |
| ) |
| .expect("connect_udp failed"); |
| // conn2 has just a remote addr different than conn1 |
| let conn2 = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| Some(local_port_d), |
| remote_ip_b, |
| remote_port_a, |
| ) |
| .expect("connect_udp failed"); |
| let list1 = listen_udp::<I, _>(&mut ctx, Some(local_ip), Some(local_port_a)) |
| .expect("listen_udp failed"); |
| let list2 = listen_udp::<I, _>(&mut ctx, Some(local_ip), Some(local_port_b)) |
| .expect("listen_udp failed"); |
| let wildcard_list = |
| listen_udp::<I, _>(&mut ctx, None, Some(local_port_c)).expect("listen_udp failed"); |
| |
| // now inject UDP packets that each of the created connections should |
| // receive: |
| let body_conn1 = [1, 1, 1, 1]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_a.into_addr(), |
| local_ip.into_addr(), |
| remote_port_a, |
| local_port_d, |
| &body_conn1[..], |
| ); |
| let body_conn2 = [2, 2, 2, 2]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_b.into_addr(), |
| local_ip.into_addr(), |
| remote_port_a, |
| local_port_d, |
| &body_conn2[..], |
| ); |
| let body_list1 = [3, 3, 3, 3]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_a.into_addr(), |
| local_ip.into_addr(), |
| remote_port_a, |
| local_port_a, |
| &body_list1[..], |
| ); |
| let body_list2 = [4, 4, 4, 4]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_a.into_addr(), |
| local_ip.into_addr(), |
| remote_port_a, |
| local_port_b, |
| &body_list2[..], |
| ); |
| let body_wildcard_list = [5, 5, 5, 5]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_a.into_addr(), |
| local_ip.into_addr(), |
| remote_port_a, |
| local_port_c, |
| &body_wildcard_list[..], |
| ); |
| // check that we got everything in order: |
| let conn_packets = &ctx.get_ref().conn_data; |
| assert_eq!(conn_packets.len(), 2); |
| let pkt = &conn_packets[0]; |
| assert_eq!(pkt.conn, conn1); |
| assert_eq!(pkt.body, &body_conn1[..]); |
| let pkt = &conn_packets[1]; |
| assert_eq!(pkt.conn, conn2); |
| assert_eq!(pkt.body, &body_conn2[..]); |
| |
| let list_packets = &ctx.get_ref().listen_data; |
| assert_eq!(list_packets.len(), 3); |
| let pkt = &list_packets[0]; |
| assert_eq!(pkt.listener, list1); |
| assert_eq!(pkt.src_ip, remote_ip_a.into_addr()); |
| assert_eq!(pkt.dst_ip, local_ip.into_addr()); |
| assert_eq!(pkt.src_port.unwrap(), remote_port_a); |
| assert_eq!(pkt.body, &body_list1[..]); |
| |
| let pkt = &list_packets[1]; |
| assert_eq!(pkt.listener, list2); |
| assert_eq!(pkt.src_ip, remote_ip_a.into_addr()); |
| assert_eq!(pkt.dst_ip, local_ip.into_addr()); |
| assert_eq!(pkt.src_port.unwrap(), remote_port_a); |
| assert_eq!(pkt.body, &body_list2[..]); |
| |
| let pkt = &list_packets[2]; |
| assert_eq!(pkt.listener, wildcard_list); |
| assert_eq!(pkt.src_ip, remote_ip_a.into_addr()); |
| assert_eq!(pkt.dst_ip, local_ip.into_addr()); |
| assert_eq!(pkt.src_port.unwrap(), remote_port_a); |
| assert_eq!(pkt.body, &body_wildcard_list[..]); |
| } |
| |
| /// Tests UDP wildcard listeners for different IP versions. |
| #[ip_test] |
| fn test_wildcard_listeners<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| 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_port = NonZeroU16::new(100).unwrap(); |
| let remote_port = NonZeroU16::new(200).unwrap(); |
| let listener = |
| listen_udp::<I, _>(&mut ctx, None, Some(listener_port)).expect("listen_udp failed"); |
| assert_eq!(listener.listener_type, ListenerType::Wildcard); |
| |
| let body = [1, 2, 3, 4, 5]; |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_a.into_addr(), |
| local_ip_a.into_addr(), |
| remote_port, |
| listener_port, |
| &body[..], |
| ); |
| // receive into a different local ip: |
| receive_udp_packet( |
| &mut ctx, |
| remote_ip_b.into_addr(), |
| local_ip_b.into_addr(), |
| remote_port, |
| listener_port, |
| &body[..], |
| ); |
| |
| // check that we received both packets for the listener: |
| let listen_packets = &ctx.get_ref().listen_data; |
| assert_eq!(listen_packets.len(), 2); |
| let pkt = &listen_packets[0]; |
| assert_eq!(pkt.listener, listener); |
| assert_eq!(pkt.src_ip, remote_ip_a.into_addr()); |
| assert_eq!(pkt.dst_ip, local_ip_a.into_addr()); |
| assert_eq!(pkt.src_port.unwrap(), remote_port); |
| assert_eq!(pkt.body, &body[..]); |
| let pkt = &listen_packets[1]; |
| assert_eq!(pkt.listener, listener); |
| assert_eq!(pkt.src_ip, remote_ip_b.into_addr()); |
| assert_eq!(pkt.dst_ip, local_ip_b.into_addr()); |
| assert_eq!(pkt.src_port.unwrap(), remote_port); |
| assert_eq!(pkt.body, &body[..]); |
| } |
| |
| /// Tests establishing a UDP connection without providing a local IP |
| #[ip_test] |
| fn test_conn_unspecified_local_ip<I: Ip + TestIpExt + IcmpIpExt>() { |
| set_logger_for_test(); |
| let mut ctx = DummyContext::<I>::default(); |
| let local_port = NonZeroU16::new(100).unwrap(); |
| let remote_port = NonZeroU16::new(200).unwrap(); |
| let conn = |
| connect_udp::<I, _>(&mut ctx, None, Some(local_port), remote_ip::<I>(), remote_port) |
| .expect("connect_udp failed"); |
| let connid = ctx.get_ref().state.conn_state.conns.get_conn_by_id(conn.into()).unwrap(); |
| |
| assert_eq!(connid.local_ip, local_ip::<I>()); |
| assert_eq!(connid.local_port, local_port); |
| assert_eq!(connid.remote_ip, remote_ip::<I>()); |
| assert_eq!(connid.remote_port, remote_port); |
| } |
| |
| /// Tests local port allocation for [`connect_udp`]. |
| /// |
| /// Tests that calling [`connect_udp`] causes a valid local port to be |
| /// allocated when no local port is passed. |
| #[ip_test] |
| fn test_udp_local_port_alloc<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| 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 conn_a = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| None, |
| ip_a, |
| NonZeroU16::new(1010).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| let conn_b = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| None, |
| ip_b, |
| NonZeroU16::new(1010).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| let conn_c = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| None, |
| ip_a, |
| NonZeroU16::new(2020).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| let conn_d = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| None, |
| ip_a, |
| NonZeroU16::new(1010).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| let conns = &ctx.get_ref().state.conn_state.conns; |
| let valid_range = &UdpConnectionState::<I>::EPHEMERAL_RANGE; |
| let port_a = conns.get_conn_by_id(conn_a.into()).unwrap().local_port.get(); |
| assert!(valid_range.contains(&port_a)); |
| let port_b = conns.get_conn_by_id(conn_b.into()).unwrap().local_port.get(); |
| assert!(valid_range.contains(&port_b)); |
| assert_ne!(port_a, port_b); |
| let port_c = conns.get_conn_by_id(conn_c.into()).unwrap().local_port.get(); |
| assert!(valid_range.contains(&port_c)); |
| assert_ne!(port_a, port_c); |
| let port_d = conns.get_conn_by_id(conn_d.into()).unwrap().local_port.get(); |
| assert!(valid_range.contains(&port_d)); |
| assert_ne!(port_a, port_d); |
| } |
| |
| /// Tests [`UdpConnectionState::collect_used_local_ports`] |
| #[ip_test] |
| fn test_udp_collect_local_ports<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let local_ip_2 = I::get_other_ip_address(10); |
| let remote_ip = remote_ip::<I>(); |
| ctx.get_mut().extra_local_addrs.push(local_ip_2.get()); |
| |
| let pa = NonZeroU16::new(10).unwrap(); |
| let pb = NonZeroU16::new(11).unwrap(); |
| let pc = NonZeroU16::new(12).unwrap(); |
| let pd = NonZeroU16::new(13).unwrap(); |
| let pe = NonZeroU16::new(14).unwrap(); |
| let pf = NonZeroU16::new(15).unwrap(); |
| let remote_port = NonZeroU16::new(100).unwrap(); |
| |
| // create some listeners and connections: |
| // wildcard listeners: |
| listen_udp::<I, _>(&mut ctx, None, Some(pa)).expect("listen_udp failed"); |
| listen_udp::<I, _>(&mut ctx, None, Some(pb)).expect("listen_udp failed"); |
| // specified address listeners: |
| listen_udp::<I, _>(&mut ctx, Some(local_ip), Some(pc)).expect("listen_udp failed"); |
| listen_udp::<I, _>(&mut ctx, Some(local_ip_2), Some(pd)).expect("listen_udp failed"); |
| // connections: |
| connect_udp::<I, _>(&mut ctx, Some(local_ip), Some(pe), remote_ip, remote_port) |
| .expect("connect_udp failed"); |
| connect_udp::<I, _>(&mut ctx, Some(local_ip_2), Some(pf), remote_ip, remote_port) |
| .expect("connect_udp failed"); |
| |
| let conn_state = &DualStateContext::<UdpState<I>, _>::get_first_state(&ctx).conn_state; |
| |
| // collect all used local ports: |
| assert_eq!( |
| conn_state.collect_used_local_ports(None.into_iter()), |
| [pa, pb, pc, pd, pe, pf].iter().copied().collect() |
| ); |
| // collect all local ports for local_ip: |
| assert_eq!( |
| conn_state.collect_used_local_ports(Some(local_ip).iter()), |
| [pa, pb, pc, pe].iter().copied().collect() |
| ); |
| // collect all local ports for local_ip_2: |
| assert_eq!( |
| conn_state.collect_used_local_ports(Some(local_ip_2).iter()), |
| [pa, pb, pd, pf].iter().copied().collect() |
| ); |
| // collect all local ports for local_ip and local_ip_2: |
| assert_eq!( |
| conn_state.collect_used_local_ports(vec![local_ip, local_ip_2].iter()), |
| [pa, pb, pc, pd, pe, pf].iter().copied().collect() |
| ); |
| } |
| |
| /// 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 + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| |
| let wildcard_list = listen_udp::<I, _>(&mut ctx, None, None).expect("listen_udp failed"); |
| let specified_list = |
| listen_udp::<I, _>(&mut ctx, Some(local_ip), None).expect("listen_udp failed"); |
| |
| let conn_state = &DualStateContext::<UdpState<I>, _>::get_first_state(&ctx).conn_state; |
| let wildcard_port = conn_state |
| .wildcard_listeners |
| .get_by_listener(wildcard_list.id) |
| .unwrap() |
| .first() |
| .unwrap() |
| .clone(); |
| let specified_port = |
| conn_state.listeners.get_by_listener(specified_list.id).unwrap().first().unwrap().port; |
| assert!(UdpConnectionState::<I>::EPHEMERAL_RANGE.contains(&wildcard_port.get())); |
| assert!(UdpConnectionState::<I>::EPHEMERAL_RANGE.contains(&specified_port.get())); |
| assert_ne!(wildcard_port, specified_port); |
| } |
| |
| /// Tests [`remove_udp_conn`] |
| #[ip_test] |
| fn test_remove_udp_conn<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| let local_port = NonZeroU16::new(100).unwrap(); |
| let remote_port = NonZeroU16::new(200).unwrap(); |
| let conn = |
| connect_udp::<I, _>(&mut ctx, Some(local_ip), Some(local_port), remote_ip, remote_port) |
| .expect("connect_udp failed"); |
| let info = remove_udp_conn(&mut ctx, conn); |
| // assert that the info gotten back matches what was expected: |
| assert_eq!(info.local_ip, local_ip); |
| assert_eq!(info.local_port, local_port); |
| assert_eq!(info.remote_ip, remote_ip); |
| assert_eq!(info.remote_port, remote_port); |
| |
| // assert that that connection id was removed from the connections |
| // state: |
| assert!(DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .conns |
| .get_conn_by_id(conn.0) |
| .is_none()); |
| } |
| |
| /// Tests [`remove_udp_listener`] |
| #[ip_test] |
| fn test_remove_udp_listener<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let local_port = NonZeroU16::new(100).unwrap(); |
| |
| // test removing a specified listener: |
| let list = listen_udp::<I, _>(&mut ctx, Some(local_ip), Some(local_port)) |
| .expect("listen_udp failed"); |
| let info = remove_udp_listener(&mut ctx, list); |
| assert_eq!(info.local_ip.unwrap(), local_ip); |
| assert_eq!(info.local_port, local_port); |
| assert!(DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .listeners |
| .get_by_listener(list.id) |
| .is_none()); |
| |
| // test removing a wildcard listener: |
| let list = listen_udp::<I, _>(&mut ctx, None, Some(local_port)).expect("listen_udp failed"); |
| let info = remove_udp_listener(&mut ctx, list); |
| assert!(info.local_ip.is_none()); |
| assert_eq!(info.local_port, local_port); |
| assert!(DualStateContext::<UdpState<I>, _>::get_first_state(&ctx) |
| .conn_state |
| .wildcard_listeners |
| .get_by_listener(list.id) |
| .is_none()); |
| } |
| |
| #[ip_test] |
| fn test_get_conn_info<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| let remote_ip = remote_ip::<I>(); |
| // create a UDP connection with a specified local port and local ip: |
| let conn = connect_udp::<I, _>( |
| &mut ctx, |
| Some(local_ip), |
| NonZeroU16::new(100), |
| remote_ip, |
| NonZeroU16::new(200).unwrap(), |
| ) |
| .expect("connect_udp failed"); |
| let info = get_udp_conn_info(&ctx, conn); |
| assert_eq!(info.local_ip, local_ip); |
| assert_eq!(info.local_port.get(), 100); |
| assert_eq!(info.remote_ip, remote_ip); |
| assert_eq!(info.remote_port.get(), 200); |
| } |
| |
| #[ip_test] |
| fn test_get_listener_info<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let local_ip = local_ip::<I>(); |
| |
| // check getting info on specified listener: |
| let list = listen_udp::<I, _>(&mut ctx, Some(local_ip), NonZeroU16::new(100)) |
| .expect("listen_udp failed"); |
| let info = get_udp_listener_info(&ctx, list); |
| assert_eq!(info.local_ip.unwrap(), local_ip); |
| assert_eq!(info.local_port.get(), 100); |
| |
| // check getting info on wildcard listener: |
| let list = |
| listen_udp::<I, _>(&mut ctx, None, NonZeroU16::new(200)).expect("listen_udp failed"); |
| let info = get_udp_listener_info(&ctx, list); |
| assert!(info.local_ip.is_none()); |
| assert_eq!(info.local_port.get(), 200); |
| } |
| |
| #[ip_test] |
| fn test_listen_udp_forwards_errors<I: Ip + TestIpExt + IcmpIpExt>() { |
| let mut ctx = DummyContext::<I>::default(); |
| let remote_ip = remote_ip::<I>(); |
| |
| // check listening to a non-local ip fails. |
| let listen_err = listen_udp::<I, _>(&mut ctx, Some(remote_ip), NonZeroU16::new(100)) |
| .expect_err("listen_udp unexpectedly succeeded"); |
| assert_eq!(listen_err, SocketError::Local(LocalAddressError::CannotBindToAddress)); |
| |
| let _ = |
| listen_udp::<I, _>(&mut ctx, None, NonZeroU16::new(200)).expect("listen_udp failed"); |
| let listen_err = listen_udp::<I, _>(&mut ctx, None, NonZeroU16::new(200)) |
| .expect_err("listen_udp unexpectedly succeeded"); |
| assert_eq!(listen_err, SocketError::Local(LocalAddressError::AddressInUse)); |
| } |
| |
| /// Tests that incoming ICMP errors are properly delivered to a connection, |
| /// a listener, and a wildcard listener. |
| #[test] |
| fn test_icmp_error() { |
| // Create a context with: |
| // - A wildcard listener on port 1 |
| // - A listener on the local IP and port 2 |
| // - A connection from the local IP to the remote IP on local port 2 and remote port 3 |
| fn initialize_context<I: TestIpExt + IcmpIpExt>() -> DummyContext<I> { |
| let mut ctx = DummyContext::default(); |
| assert_eq!( |
| listen_udp(&mut ctx, None, Some(NonZeroU16::new(1).unwrap())).unwrap(), |
| UdpListenerId::new_wildcard(0) |
| ); |
| assert_eq!( |
| listen_udp(&mut ctx, Some(local_ip::<I>()), Some(NonZeroU16::new(2).unwrap())) |
| .unwrap(), |
| UdpListenerId::new_specified(0) |
| ); |
| assert_eq!( |
| connect_udp( |
| &mut ctx, |
| Some(local_ip::<I>()), |
| Some(NonZeroU16::new(3).unwrap()), |
| remote_ip::<I>(), |
| NonZeroU16::new(4).unwrap(), |
| ) |
| .unwrap(), |
| UdpConnId::new(0) |
| ); |
| ctx |
| } |
| |
| // Serialize a UDP-in-IP packet with the given values, and then receive |
| // an ICMP error message with that packet as the original packet. |
| fn receive_icmp_error< |
| I: IpExt + IcmpIpExt, |
| F: Fn(&mut DummyContext<I>, &[u8], I::ErrorCode), |
| >( |
| ctx: &mut DummyContext<I>, |
| src_ip: I::Addr, |
| dst_ip: I::Addr, |
| src_port: u16, |
| dst_port: u16, |
| err: I::ErrorCode, |
| f: F, |
| ) where |
| I::PacketBuilder: core::fmt::Debug, |
| { |
| let packet = (&[0u8][..]) |
| .into_serializer() |
| .encapsulate(UdpPacketBuilder::new( |
| src_ip, |
| dst_ip, |
| NonZeroU16::new(src_port), |
| NonZeroU16::new(dst_port).unwrap(), |
| )) |
| .encapsulate(I::PacketBuilder::new(src_ip, dst_ip, 64, IpProto::Udp)) |
| .serialize_vec_outer() |
| .unwrap(); |
| f(ctx, packet.as_ref(), err); |
| } |
| |
| fn test< |
| I: IpExt + TestIpExt + IcmpIpExt + PartialEq, |
| F: Copy + Fn(&mut DummyContext<I>, &[u8], I::ErrorCode), |
| >( |
| err: I::ErrorCode, |
| f: F, |
| other_remote_ip: I::Addr, |
| ) where |
| I::PacketBuilder: core::fmt::Debug, |
| I::ErrorCode: Copy + core::fmt::Debug + PartialEq, |
| { |
| let mut ctx = initialize_context::<I>(); |
| |
| let src_ip = local_ip::<I>(); |
| let dst_ip = remote_ip::<I>(); |
| |
| // Test that we receive an error for the connection. |
| receive_icmp_error(&mut ctx, src_ip.get(), dst_ip.get(), 3, 4, err, f); |
| assert_eq!( |
| ctx.get_ref().icmp_errors.as_slice(), |
| [IcmpError { id: Ok(UdpConnId::new(0)), err }] |
| ); |
| |
| // Test that we receive an error for the listener. |
| receive_icmp_error(&mut ctx, src_ip.get(), dst_ip.get(), 2, 4, err, f); |
| assert_eq!( |
| &ctx.get_ref().icmp_errors.as_slice()[1..], |
| [IcmpError { id: Err(UdpListenerId::new_specified(0)), err }] |
| ); |
| |
| // Test that we receive an error for the wildcard listener. |
| receive_icmp_error(&mut ctx, src_ip.get(), dst_ip.get(), 1, 4, err, f); |
| assert_eq!( |
| &ctx.get_ref().icmp_errors.as_slice()[2..], |
| [IcmpError { id: Err(UdpListenerId::new_wildcard(0)), err }] |
| ); |
| |
| // Test that we receive an error for the wildcard listener even if |
| // the original packet was sent to a different remote IP/port. |
| receive_icmp_error(&mut ctx, src_ip.get(), other_remote_ip, 1, 5, err, f); |
| assert_eq!( |
| &ctx.get_ref().icmp_errors.as_slice()[3..], |
| [IcmpError { id: Err(UdpListenerId::new_wildcard(0)), err }] |
| ); |
| |
| // Test that an error that doesn't correspond to any connection or |
| // listener isn't received. |
| receive_icmp_error(&mut ctx, src_ip.get(), dst_ip.get(), 3, 5, err, f); |
| assert_eq!(ctx.get_ref().icmp_errors.len(), 4); |
| } |
| |
| test( |
| Icmpv4ErrorCode::DestUnreachable(Icmpv4DestUnreachableCode::DestNetworkUnreachable), |
| |ctx: &mut DummyContext<Ipv4>, mut packet, error_code| { |
| let packet = packet.parse::<Ipv4PacketRaw<_>>().unwrap(); |
| let src_ip = SpecifiedAddr::new(packet.src_ip()); |
| let dst_ip = SpecifiedAddr::new(packet.dst_ip()).unwrap(); |
| let body = packet.body().into_inner(); |
| <UdpIpTransportContext as IpTransportContext<Ipv4, _>>::receive_icmp_error( |
| ctx, src_ip, dst_ip, body, error_code, |
| ) |
| }, |
| Ipv4Addr::new([1, 2, 3, 4]), |
| ); |
| |
| test( |
| Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute), |
| |ctx: &mut DummyContext<Ipv6>, mut packet, error_code| { |
| let packet = packet.parse::<Ipv6PacketRaw<_>>().unwrap(); |
| let src_ip = SpecifiedAddr::new(packet.src_ip()); |
| let dst_ip = SpecifiedAddr::new(packet.dst_ip()).unwrap(); |
| let body = packet.body().unwrap().into_inner(); |
| <UdpIpTransportContext as IpTransportContext<Ipv6, _>>::receive_icmp_error( |
| ctx, src_ip, dst_ip, body, error_code, |
| ) |
| }, |
| Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8]), |
| ); |
| } |
| } |