blob: 80e8cba21fe67aa4a375e36d38297db015da92ca [file] [log] [blame]
// Copyright 2019 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.
//! IPv4 and IPv6 sockets.
use core::cmp::Ordering;
use core::marker::PhantomData;
use core::num::NonZeroU8;
use net_types::ip::{Ip, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
use net_types::SpecifiedAddr;
use packet::{BufferMut, Serializer};
use crate::device::{AddressEntry, DeviceId};
use crate::error::NoRouteError;
use crate::ip::forwarding::ForwardingTable;
use crate::ip::{IpExt, IpProto};
use crate::socket::Socket;
use crate::wire::{ipv4::Ipv4PacketBuilder, ipv6::Ipv6PacketBuilder};
use crate::{BufferDispatcher, Context, EventDispatcher};
/// A socket identifying a connection between a local and remote IP host.
pub(crate) trait IpSocket<I: Ip>: Socket<UpdateError = NoRouteError> {
/// Get the local IP address.
fn local_ip(&self) -> &SpecifiedAddr<I::Addr>;
/// Get the remote IP address.
fn remote_ip(&self) -> &SpecifiedAddr<I::Addr>;
}
/// An execution context defining a type of IP socket.
pub(crate) trait IpSocketContext<I: Ip> {
// TODO(joshlf): Remove `Clone` bound once we're no longer cloning sockets.
type IpSocket: IpSocket<I> + Clone;
/// A builder carrying optional parameters passed to [`new_ip_socket`].
///
/// [`new_ip_socket`]: crate::ip::socket::IpSocketContext::new_ip_socket
type Builder: Default;
/// Constructs a new [`Self::IpSocket`].
///
/// `new_ip_socket` constructs a new `Self::IpSocket` to the given remote IP
/// address from the given local IP address with the given IP protocol. If
/// no local IP address is given, one will be chosen automatically.
///
/// `new_ip_socket` returns an error if no route to the remote was found in
/// the forwarding table, if the given local IP address is not valid for the
/// found route, or if the remote is a loopback address (which is not
/// currently supported, but will be in the future). Currently, this is true
/// regardless of the value of `unroutable_behavior`. Eventually, this will
/// be changed.
///
/// The builder may be used to override certain default parameters. Passing
/// `None` for the `builder` parameter is equivalent to passing
/// `Some(Default::default())`.
fn new_ip_socket(
&mut self,
local_ip: Option<SpecifiedAddr<I::Addr>>,
remote_ip: SpecifiedAddr<I::Addr>,
proto: IpProto,
unroutable_behavior: UnroutableBehavior,
builder: Option<Self::Builder>,
) -> Result<Self::IpSocket, NoRouteError>;
}
// TODO(joshlf): Use FrameContext instead?
/// An error in sending a packet on an IP socket.
#[derive(Debug, Eq, PartialEq)]
pub enum SendError {
/// An MTU was exceeded.
///
/// This could be caused by an MTU at any layer of the stack, including both
/// device MTUs and packet format body size limits.
Mtu,
/// The socket is currently unroutable.
Unroutable,
}
pub(crate) trait BufferIpSocketContext<I: Ip, B: BufferMut>: IpSocketContext<I> {
/// Send an IP packet on a socket.
///
/// The generated packet has its metadata initialized from `socket`,
/// including the source and destination addresses, the Time To Live/Hop
/// Limit, and the Protocol/Next Header. The outbound device is also chosen
/// based on information stored in the socket.
///
/// If the socket is currently unroutable, an error is returned.
fn send_ip_packet<S: Serializer<Buffer = B>>(
&mut self,
socket: &Self::IpSocket,
body: S,
) -> Result<(), (S, SendError)>;
}
/// What should a socket do when it becomes unroutable?
///
/// `UnroutableBehavior` describes how a socket is configured to behave if it
/// becomes unroutable. In particular, this affects the behavior of
/// [`Socket::apply_update`].
#[derive(Copy, Clone, Eq, PartialEq)]
#[allow(unused)]
pub(crate) enum UnroutableBehavior {
/// The socket should stay open.
///
/// When a call to [`Socket::apply_update`] results in the socket becoming
/// unroutable, the socket will remain open, and `apply_update` will return
/// `Ok`.
///
/// So long as the socket is unroutable, attempting to send packets will
/// fail on a per-packet basis. If the socket later becomes routable again,
/// these operations will succeed again.
StayOpen,
/// The socket should close.
///
/// When a call to [`Socket::apply_update`] results in the socket becoming
/// unroutable, the socket will be closed - `apply_update` will return
/// `Err`.
Close,
}
/// A builder for IPv4 sockets.
///
/// [`IpSocketContext::new_ip_socket`] accepts optional configuration in the
/// form of a `SocketBuilder`. All configurations have default values that are
/// used if a custom value is not provided.
#[derive(Default)]
pub(crate) struct Ipv4SocketBuilder {
// NOTE(joshlf): These fields are `Option`s rather than being set to a
// default value in `Default::default` because global defaults may be set
// per-stack at runtime, meaning that default values cannot be known at
// compile time.
ttl: Option<NonZeroU8>,
}
impl Ipv4SocketBuilder {
/// Set the Time to Live (TTL) field that will be set on outbound IPv4
/// packets.
///
/// The TTL must be non-zero. Per [RFC 1122 Section 3.2.1.7] and [RFC 1812
/// Section 4.2.2.9], hosts and routers (respectively) must not originate
/// IPv4 packets with a TTL of zero.
///
/// [RFC 1122 Section 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
/// [RFC 1812 Section 4.2.2.9]: https://tools.ietf.org/html/rfc1812#section-4.2.2.9
#[allow(dead_code)] // TODO(joshlf): Remove once this is used
pub(crate) fn ttl(&mut self, ttl: NonZeroU8) -> &mut Ipv4SocketBuilder {
self.ttl = Some(ttl);
self
}
}
/// A builder for IPv6 sockets.
///
/// [`IpSocketContext::new_ip_socket`] accepts optional configuration in the
/// form of a `SocketBuilder`. All configurations have default values that are
/// used if a custom value is not provided.
#[derive(Default)]
pub(crate) struct Ipv6SocketBuilder {
// NOTE(joshlf): These fields are `Option`s rather than being set to a
// default value in `Default::default` because global defaults may be set
// per-stack at runtime, meaning that default values cannot be known at
// compile time.
hop_limit: Option<u8>,
}
impl Ipv6SocketBuilder {
/// Set the Hop Limit field that will be set on outbound IPv6 packets.
#[allow(dead_code)] // TODO(joshlf): Remove once this is used
pub(crate) fn hop_limit(&mut self, hop_limit: u8) -> &mut Ipv6SocketBuilder {
self.hop_limit = Some(hop_limit);
self
}
}
/// The production implementation of the [`IpSocket`] trait.
#[derive(Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub(crate) struct IpSock<I: IpExt, D> {
// TODO(joshlf): This struct is larger than it needs to be. `remote_ip`,
// `local_ip`, and `proto` are all stored in `cached`'s `builder` when it's
// `Routable`. Instead, we could move these into the `Unroutable` variant of
// `CachedInfo`.
//
// These are part of the socket definition and never change.
//
remote_ip: SpecifiedAddr<I::Addr>,
// Guaranteed to be unicast in its subnet since it's always equal to an
// address assigned to the local device. We can't use the `UnicastAddr`
// witness type since `Ipv4Addr` doesn't implement `UnicastAddress`.
//
// TODO(joshlf): Support unnumbered interfaces. Once we do that, a few
// issues arise: A) Does the unicast restriction still apply, and is that
// even well-defined for IPv4 in the absence of a subnet? B) Presumably we
// have to always bind to a particular interface?
local_ip: SpecifiedAddr<I::Addr>,
proto: IpProto,
unroutable_behavior: UnroutableBehavior,
//
// This is merely cached and can change.
//
// This is `Routable` if the socket is currently routable and `Unroutable`
// otherwise. If `unroutable_behavior` is `Close`, then this is guaranteed
// to be `Routable`.
cached: CachedInfo<I, D>,
}
/// Information which is cached inside an [`IpSock`].
#[derive(Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[allow(unused)]
enum CachedInfo<I: IpExt, D> {
Routable { builder: I::PacketBuilder, device: D, next_hop: SpecifiedAddr<I::Addr> },
Unroutable,
}
/// An update to the information cached in an [`IpSock`].
///
/// Whenever IP-layer information changes that might affect `IpSock`s, an
/// `IpSockUpdate` is emitted, and clients are responsible for applying this
/// update to all `IpSock`s that they are responsible for.
pub(crate) struct IpSockUpdate<I: IpExt> {
// Currently, `IpSockUpdate`s only represent a single type of update: that
// the forwarding table or assignment of IP addresses to devices have
// changed in some way. Thus, this is a ZST.
_marker: PhantomData<I>,
}
impl<I: IpExt> IpSockUpdate<I> {
/// Constructs a new `IpSocketUpdate`.
pub(crate) fn new() -> IpSockUpdate<I> {
IpSockUpdate { _marker: PhantomData }
}
}
impl<I: IpExt, D> Socket for IpSock<I, D> {
type Update = IpSockUpdate<I>;
type UpdateMeta = ForwardingTable<I, D>;
type UpdateError = NoRouteError;
fn apply_update(
&mut self,
_update: &IpSockUpdate<I>,
_meta: &ForwardingTable<I, D>,
) -> Result<(), NoRouteError> {
// NOTE(joshlf): Currently, with this unimplemented, we will panic if we
// ever try to update the forwarding table or the assignment of IP
// addresses to devices while sockets are installed. However, updates
// that happen while no sockets are installed will still succeed. This
// should allow us to continue testing Netstack3 until we implement this
// method.
unimplemented!()
}
}
impl<I: IpExt, D> IpSocket<I> for IpSock<I, D> {
fn local_ip(&self) -> &SpecifiedAddr<I::Addr> {
&self.local_ip
}
fn remote_ip(&self) -> &SpecifiedAddr<I::Addr> {
&self.remote_ip
}
}
/// Apply an update to all IPv4 sockets.
///
/// `apply_ipv4_socket_update` applies the given socket update to all IPv4
/// sockets in existence. It does this by delegating to every module that is
/// responsible for storing IPv4 sockets.
pub(crate) fn apply_ipv4_socket_update<D: EventDispatcher>(
ctx: &mut Context<D>,
update: IpSockUpdate<Ipv4>,
) {
crate::ip::icmp::apply_ipv4_socket_update(ctx, update);
}
/// Apply an update to all IPv6 sockets.
///
/// `apply_ipv6_socket_update` applies the given socket update to all IPv6
/// sockets in existence. It does this by delegating to every module that is
/// responsible for storing IPv6 sockets.
pub(crate) fn apply_ipv6_socket_update<D: EventDispatcher>(
ctx: &mut Context<D>,
update: IpSockUpdate<Ipv6>,
) {
crate::ip::icmp::apply_ipv6_socket_update(ctx, update);
}
// TODO(joshlf): Once we support configuring transport-layer protocols using
// type parameters, use that to ensure that `proto` is the right protocol for
// the caller. We will still need to have a separate enforcement mechanism for
// raw IP sockets once we support those.
impl<D: EventDispatcher> IpSocketContext<Ipv4> for Context<D> {
type IpSocket = IpSock<Ipv4, DeviceId>;
type Builder = Ipv4SocketBuilder;
fn new_ip_socket(
&mut self,
local_ip: Option<SpecifiedAddr<Ipv4Addr>>,
remote_ip: SpecifiedAddr<Ipv4Addr>,
proto: IpProto,
unroutable_behavior: UnroutableBehavior,
builder: Option<Ipv4SocketBuilder>,
) -> Result<IpSock<Ipv4, DeviceId>, NoRouteError> {
let builder = builder.unwrap_or_default();
if Ipv4::LOOPBACK_SUBNET.contains(&remote_ip) {
return Err(NoRouteError);
}
super::lookup_route(self, remote_ip).ok_or(NoRouteError).and_then(|dst| {
let local_ip = if let Some(local_ip) = local_ip {
if crate::device::get_assigned_ip_addr_subnets::<_, Ipv4Addr>(self, dst.device)
.any(|addr_sub| local_ip == addr_sub.addr())
{
local_ip
} else {
return Err(NoRouteError);
}
} else {
// TODO(joshlf): Are we sure that a device route can never be
// set for a device without an IP address? At the least, this is
// not currently enforced anywhere, and is a DoS vector.
crate::device::get_ip_addr_subnet(self, dst.device)
.expect("IP device route set for device without IP address")
.addr()
};
Ok(IpSock {
// `get_ip_addr_subnet` and `get_assigned_ip_addr_subnets` both return
// `AddrSubnet`s, which guarantee that their addresses are
// unicast address in their subnets. That satisfies the
// invariant on this field.
local_ip,
remote_ip,
proto,
unroutable_behavior,
cached: CachedInfo::Routable {
builder: Ipv4PacketBuilder::new(
local_ip,
remote_ip,
builder.ttl.unwrap_or(super::DEFAULT_TTL).get(),
proto,
),
device: dst.device,
next_hop: dst.next_hop,
},
})
})
}
}
impl<D: EventDispatcher> IpSocketContext<Ipv6> for Context<D> {
type IpSocket = IpSock<Ipv6, DeviceId>;
type Builder = Ipv6SocketBuilder;
fn new_ip_socket(
&mut self,
local_ip: Option<SpecifiedAddr<Ipv6Addr>>,
remote_ip: SpecifiedAddr<Ipv6Addr>,
proto: IpProto,
unroutable_behavior: UnroutableBehavior,
builder: Option<Ipv6SocketBuilder>,
) -> Result<IpSock<Ipv6, DeviceId>, NoRouteError> {
let builder = builder.unwrap_or_default();
if Ipv6::LOOPBACK_SUBNET.contains(&remote_ip) {
return Err(NoRouteError);
}
super::lookup_route(self, remote_ip).ok_or(NoRouteError).and_then(|dst| {
let (local_ip, device) = if let Some(local_ip) = local_ip {
// TODO(joshlf):
// - Allow the specified local IP to be the local IP of a
// different device so long as we're operating in the weak
// host model.
// - What about when the socket is bound to a device? How does
// that affect things?
if crate::device::get_ip_addr_state(self, dst.device, &local_ip)
.map(|state| !state.is_tentative())
.unwrap_or(false)
{
(local_ip, dst.device)
} else {
return Err(NoRouteError);
}
} else {
// TODO(joshlf):
// - If device binding is used, then we should only consider the
// addresses of the device being bound to.
// - If we are operating in the strong host model, then perhaps
// we should restrict ourselves to addresses associated with
// the device found by looking up the remote in the forwarding
// table? This I'm less sure of.
select_ipv6_source_address(
remote_ip,
dst.device,
crate::device::list_devices(self)
.map(|device_id| {
crate::device::get_ip_addr_subnets(self, device_id)
.map(move |a| (a, device_id))
})
.flatten(),
)
.ok_or(NoRouteError)?
};
Ok(IpSock {
// `get_ip_addr_subnet` and `get_ip_addr_subnets` both return
// `AddrSubnet`s, which guarantee that their addresses are
// unicast address in their subnets. That satisfies the
// invariant on this field.
local_ip,
remote_ip,
proto,
unroutable_behavior,
cached: CachedInfo::Routable {
builder: Ipv6PacketBuilder::new(
local_ip,
remote_ip,
builder.hop_limit.unwrap_or(super::DEFAULT_TTL.get()),
proto,
),
device,
next_hop: dst.next_hop,
},
})
})
}
}
/// Select the source address for an IPv6 socket using the algorithm defined in
/// [RFC 6724 Section 5].
///
/// This algorithm is only applicable when the user has not explicitly specified
/// a source address.
///
/// `remote_ip` is the remote IP address of the socket, `outbound_device` is the
/// device over which outbound traffic to `remote_ip` is sent (according to the
/// forwarding table), and `addresses` is an iterator of all addresses on all
/// devices. The algorithm works by iterating over `addresses` and selecting the
/// address which is most preferred according to a set of selection criteria.
fn select_ipv6_source_address<
'a,
Instant: 'a,
I: Iterator<Item = (&'a AddressEntry<Ipv6Addr, Instant>, DeviceId)>,
>(
remote_ip: SpecifiedAddr<Ipv6Addr>,
outbound_device: DeviceId,
addresses: I,
) -> Option<(SpecifiedAddr<Ipv6Addr>, DeviceId)> {
// Source address selection as defined in RFC 6724 Section 5.
//
// The algorithm operates by defining a partial ordering on available source
// addresses, and choosing one of the best address as defined by that
// ordering (given multiple best addresses, the choice from among those is
// implementation-defined). The partial order is defined in terms of a
// sequence of rules. If a given rule defines an order between two
// addresses, then that is their order. Otherwise, the next rule must be
// consulted, and so on until all of the rules are exhausted.
addresses
// Tentative addresses are not considered available to the source
// selection algorithm.
.filter(|(a, _)| !a.state().is_tentative())
.max_by(|(a, a_device), (b, b_device)| {
select_ipv6_source_address_cmp(remote_ip, outbound_device, a, a_device, b, b_device)
})
.map(|(addr, device)| (addr.addr_sub().addr(), device))
}
/// Comparison operator used by `select_ipv6_source_address_cmp`.
fn select_ipv6_source_address_cmp<Instant>(
remote_ip: SpecifiedAddr<Ipv6Addr>,
outbound_device: DeviceId,
a: &AddressEntry<Ipv6Addr, Instant>,
a_device: &DeviceId,
b: &AddressEntry<Ipv6Addr, Instant>,
b_device: &DeviceId,
) -> Ordering {
// TODO(fxb/46822): Implement rules 2, 4, 5.5, 6, 7, and 8.
let a_state = a.state();
let a = a.addr_sub().addr();
let b_state = b.state();
let b = b.addr_sub().addr();
// Assertions required in order for this implementation to be valid.
// Required by the implementation of Rule 1.
debug_assert!(!(a == remote_ip && b == remote_ip));
// Required by the implementation of Rule 3.
debug_assert!(!a_state.is_tentative());
debug_assert!(!b_state.is_tentative());
if (a == remote_ip) != (b == remote_ip) {
// Rule 1: Prefer same address.
//
// Note that both `a` and `b` cannot be equal to `remote_ip` since that
// would imply that we had added the same address twice to the same
// device.
//
// If `(a == remote_ip) != (b == remote_ip)`, then exactly one of them
// is equal. If this inequality does not hold, then they must both be
// unequal to `remote_ip`. In the first case, we have a tie, and in the
// second case, the rule doesn't apply. In either case, we move onto the
// next rule.
if a == remote_ip {
Ordering::Greater
} else {
Ordering::Less
}
} else if a_state != b_state {
// Rule 3: Avoid deprecated addresses.
//
// Note that, since we've already filtered out tentative addresses, the
// only two possible states are deprecated and assigned. Thus, `a_state
// != b_state` and `a_state.is_deprecated()` together imply that
// `b_state` is assigned. Conversely, `a_state != b_state` and
// `!a_state.is_deprecated()` together imply that `b_state` is
// deprecated.
if a_state.is_deprecated() {
Ordering::Less
} else {
Ordering::Greater
}
} else if (a_device == &outbound_device) != (b_device == &outbound_device) {
// Rule 5: Prefer outgoing interface.
if a_device == &outbound_device {
Ordering::Greater
} else {
Ordering::Less
}
} else {
Ordering::Equal
}
}
impl<B: BufferMut, D: BufferDispatcher<B>> BufferIpSocketContext<Ipv4, B> for Context<D> {
fn send_ip_packet<S: Serializer<Buffer = B>>(
&mut self,
socket: &IpSock<Ipv4, DeviceId>,
body: S,
) -> Result<(), (S, SendError)> {
// TODO(joshlf): Call `trace!` with relevant fields from the socket.
increment_counter!(self, "send_ipv4_packet");
// TODO(joshlf): Handle loopback sockets.
assert!(!Ipv4::LOOPBACK_SUBNET.contains(&socket.remote_ip));
// If the remote IP is non-loopback but the local IP is loopback, that
// implies a bug elsewhere - when we resolve the local IP in
// `new_ipv4_socket`, if the remote IP is not a loopback IP, then we
// should never choose a loopback IP as our local IP.
assert!(!Ipv4::LOOPBACK_SUBNET.contains(&socket.local_ip));
match &socket.cached {
CachedInfo::Routable { builder, device, next_hop } => {
// Tentative addresses are not considered bound to an interface
// in the traditional sense. Therefore, no packet should have a
// source IP set to a tentative address. This should be enforced
// by the `IpSock` being kept up to date.
debug_assert!(!crate::device::is_addr_tentative_on_device(
self,
&socket.local_ip,
*device
));
crate::device::send_ip_frame(self, *device, *next_hop, body.encapsulate(builder))
.map_err(|ser| (ser.into_inner(), SendError::Mtu))
}
CachedInfo::Unroutable => Err((body, SendError::Unroutable)),
}
}
}
impl<B: BufferMut, D: BufferDispatcher<B>> BufferIpSocketContext<Ipv6, B> for Context<D> {
fn send_ip_packet<S: Serializer<Buffer = B>>(
&mut self,
socket: &IpSock<Ipv6, DeviceId>,
body: S,
) -> Result<(), (S, SendError)> {
// TODO(joshlf): Call `trace!` with relevant fields from the socket.
increment_counter!(self, "send_ipv6_packet");
// TODO(joshlf): Handle loopback sockets.
assert!(!Ipv6::LOOPBACK_SUBNET.contains(&socket.remote_ip));
// If the remote IP is non-loopback but the local IP is loopback, that
// implies a bug elsewhere - when we resolve the local IP in
// `new_ipv6_socket`, if the remote IP is not a loopback IP, then we
// should never choose a loopback IP as our local IP.
assert!(!Ipv6::LOOPBACK_SUBNET.contains(&socket.local_ip));
match &socket.cached {
CachedInfo::Routable { builder, device, next_hop } => {
// Tentative addresses are not considered bound to an interface
// in the traditional sense. Therefore, no packet should have a
// source IP set to a tentative address. This should be enforced
// by the `IpSock` being kept up to date.
debug_assert!(!crate::device::is_addr_tentative_on_device(
self,
&socket.local_ip,
*device
));
crate::device::send_ip_frame(self, *device, *next_hop, body.encapsulate(builder))
.map_err(|ser| (ser.into_inner(), SendError::Mtu))
}
CachedInfo::Unroutable => Err((body, SendError::Unroutable)),
}
}
}
/// Test mock implementations of the traits defined in the `socket` module.
#[cfg(test)]
pub(crate) mod testutil {
use net_types::ip::IpAddress;
use super::*;
use crate::context::testutil::DummyContext;
/// A dummy implementation of [`IpSocket`].
#[derive(Clone)]
pub(crate) struct DummyIpSocket<A: IpAddress> {
local_ip: SpecifiedAddr<A>,
remote_ip: SpecifiedAddr<A>,
proto: IpProto,
ttl: u8,
unroutable_behavior: UnroutableBehavior,
// Guaranteed to be `true` if `unroutable_behavior` is `Close`.
routable: bool,
}
/// An update to a [`DummyIpSocket`].
pub(crate) struct DummyIpSocketUpdate<I: Ip> {
// The value to set the socket's `routable` field to.
routable: bool,
_marker: PhantomData<I>,
}
impl<A: IpAddress> Socket for DummyIpSocket<A> {
type Update = DummyIpSocketUpdate<A::Version>;
type UpdateMeta = ();
type UpdateError = NoRouteError;
fn apply_update(
&mut self,
update: &DummyIpSocketUpdate<A::Version>,
_meta: &(),
) -> Result<(), NoRouteError> {
if !update.routable && self.unroutable_behavior == UnroutableBehavior::Close {
return Err(NoRouteError);
}
self.routable = update.routable;
Ok(())
}
}
impl<I: Ip> IpSocket<I> for DummyIpSocket<I::Addr> {
fn local_ip(&self) -> &SpecifiedAddr<I::Addr> {
&self.local_ip
}
fn remote_ip(&self) -> &SpecifiedAddr<I::Addr> {
&self.remote_ip
}
}
/// A dummy implementation of [`IpSocketContext`].
///
/// `IpSocketContext` is implemented for any `DummyContext<S>` where `S`
/// implements `AsRef` and `AsMut` for `DummyIpSocketContext`.
pub(crate) struct DummyIpSocketContext<I: Ip> {
// The default value to use if `new_ip_socket` is called with a
// `local_ip` of `None`.
default_local_ip: SpecifiedAddr<I::Addr>,
// Whether or not calls to `new_ip_socket` should succeed.
routable_at_creation: bool,
}
impl<I: Ip> DummyIpSocketContext<I> {
/// Creates a new `DummyIpSocketContext`.
///
/// `default_local_ip` is the local IP address to use if none is
/// specified in a request to create a new IP socket.
/// `routable_at_creation` controls whether or not calls to
/// `new_ip_socket` should succeed.
pub(crate) fn new(
default_local_ip: SpecifiedAddr<I::Addr>,
routable_at_creation: bool,
) -> DummyIpSocketContext<I> {
DummyIpSocketContext { default_local_ip, routable_at_creation }
}
}
#[derive(Default)]
pub(crate) struct DummyIpSocketBuilder {
ttl: Option<u8>,
}
impl<I: Ip, S: AsRef<DummyIpSocketContext<I>> + AsMut<DummyIpSocketContext<I>>, Id, Meta>
IpSocketContext<I> for DummyContext<S, Id, Meta>
{
type IpSocket = DummyIpSocket<I::Addr>;
type Builder = DummyIpSocketBuilder;
fn new_ip_socket(
&mut self,
local_ip: Option<SpecifiedAddr<I::Addr>>,
remote_ip: SpecifiedAddr<I::Addr>,
proto: IpProto,
unroutable_behavior: UnroutableBehavior,
builder: Option<DummyIpSocketBuilder>,
) -> Result<DummyIpSocket<I::Addr>, NoRouteError> {
let builder = builder.unwrap_or_default();
let ctx = self.get_ref().as_ref();
if !ctx.routable_at_creation {
return Err(NoRouteError);
}
Ok(DummyIpSocket {
local_ip: local_ip.unwrap_or(ctx.default_local_ip),
remote_ip,
proto,
ttl: builder.ttl.unwrap_or(crate::ip::DEFAULT_TTL.get()),
unroutable_behavior,
routable: true,
})
}
}
impl<
I: Ip,
B: BufferMut,
S: AsRef<DummyIpSocketContext<I>> + AsMut<DummyIpSocketContext<I>>,
Id,
Meta,
> BufferIpSocketContext<I, B> for DummyContext<S, Id, Meta>
{
fn send_ip_packet<SS: Serializer<Buffer = B>>(
&mut self,
_socket: &Self::IpSocket,
_body: SS,
) -> Result<(), (SS, SendError)> {
unimplemented!()
}
}
}
#[cfg(test)]
mod tests {
use net_types::ip::AddrSubnet;
use net_types::Witness;
use packet::InnerPacketBuilder;
use super::*;
use crate::device::{AddressConfigurationType, AddressState};
use crate::testutil::*;
#[test]
fn test_new() {
// Test that `new_ip_socket` works with various edge cases.
//
// IPv4
//
let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V4)
.build::<DummyEventDispatcher>();
// A template socket that we can use to more concisely define sockets in
// various test cases.
let template = IpSock {
remote_ip: DUMMY_CONFIG_V4.remote_ip,
local_ip: DUMMY_CONFIG_V4.local_ip,
proto: IpProto::Icmp,
unroutable_behavior: UnroutableBehavior::Close,
cached: CachedInfo::Routable {
builder: Ipv4PacketBuilder::new(
DUMMY_CONFIG_V4.local_ip,
DUMMY_CONFIG_V4.remote_ip,
crate::ip::DEFAULT_TTL.get(),
IpProto::Icmp,
),
device: DeviceId::new_ethernet(0),
next_hop: DUMMY_CONFIG_V4.remote_ip,
},
};
// All optional fields are `None`.
assert!(
IpSocketContext::<Ipv4>::new_ip_socket(
&mut ctx,
None,
DUMMY_CONFIG_V4.remote_ip,
IpProto::Icmp,
UnroutableBehavior::Close,
None,
)
.unwrap()
== template
);
// TTL is specified.
let mut builder = Ipv4SocketBuilder::default();
builder.ttl(NonZeroU8::new(1).unwrap());
assert!(
IpSocketContext::<Ipv4>::new_ip_socket(
&mut ctx,
None,
DUMMY_CONFIG_V4.remote_ip,
IpProto::Icmp,
UnroutableBehavior::Close,
Some(builder),
)
.unwrap()
== {
// The template socket, but with the TTL set to 1.
let mut x = template.clone();
x.cached = CachedInfo::Routable {
builder: Ipv4PacketBuilder::new(
DUMMY_CONFIG_V4.local_ip,
DUMMY_CONFIG_V4.remote_ip,
1,
IpProto::Icmp,
),
device: DeviceId::new_ethernet(0),
next_hop: DUMMY_CONFIG_V4.remote_ip,
};
x
}
);
// Local address is specified, and is a valid local address.
assert!(
IpSocketContext::<Ipv4>::new_ip_socket(
&mut ctx,
Some(DUMMY_CONFIG_V4.local_ip),
DUMMY_CONFIG_V4.remote_ip,
IpProto::Icmp,
UnroutableBehavior::Close,
None,
)
.unwrap()
== template
);
// Local address is specified, and is an invalid local address.
assert!(
IpSocketContext::<Ipv4>::new_ip_socket(
&mut ctx,
Some(DUMMY_CONFIG_V4.remote_ip),
DUMMY_CONFIG_V4.remote_ip,
IpProto::Icmp,
UnroutableBehavior::Close,
None,
) == Err(NoRouteError)
);
// Loopback sockets are not yet supported.
assert!(
IpSocketContext::<Ipv4>::new_ip_socket(
&mut ctx,
None,
Ipv4::LOOPBACK_ADDRESS,
IpProto::Icmp,
UnroutableBehavior::Close,
None,
) == Err(NoRouteError)
);
//
// IPv6
//
let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6)
.build::<DummyEventDispatcher>();
// A template socket that we can use to more concisely define sockets in
// various test cases.
let template = IpSock {
remote_ip: DUMMY_CONFIG_V6.remote_ip,
local_ip: DUMMY_CONFIG_V6.local_ip,
proto: IpProto::Icmpv6,
unroutable_behavior: UnroutableBehavior::Close,
cached: CachedInfo::Routable {
builder: Ipv6PacketBuilder::new(
DUMMY_CONFIG_V6.local_ip,
DUMMY_CONFIG_V6.remote_ip,
crate::ip::DEFAULT_TTL.get(),
IpProto::Icmpv6,
),
device: DeviceId::new_ethernet(0),
next_hop: DUMMY_CONFIG_V6.remote_ip,
},
};
// All optional fields are `None`.
assert!(
IpSocketContext::<Ipv6>::new_ip_socket(
&mut ctx,
None,
DUMMY_CONFIG_V6.remote_ip,
IpProto::Icmpv6,
UnroutableBehavior::Close,
None,
)
.unwrap()
== template
);
// TTL is specified.
let mut builder = Ipv6SocketBuilder::default();
builder.hop_limit(1);
assert!(
IpSocketContext::<Ipv6>::new_ip_socket(
&mut ctx,
None,
DUMMY_CONFIG_V6.remote_ip,
IpProto::Icmpv6,
UnroutableBehavior::Close,
Some(builder),
)
.unwrap()
== {
// The template socket, but with the TTL set to 1.
let mut x = template.clone();
x.cached = CachedInfo::Routable {
builder: Ipv6PacketBuilder::new(
DUMMY_CONFIG_V6.local_ip,
DUMMY_CONFIG_V6.remote_ip,
1,
IpProto::Icmpv6,
),
device: DeviceId::new_ethernet(0),
next_hop: DUMMY_CONFIG_V6.remote_ip,
};
x
}
);
// Local address is specified, and is a valid local address.
assert!(
IpSocketContext::<Ipv6>::new_ip_socket(
&mut ctx,
Some(DUMMY_CONFIG_V6.local_ip),
DUMMY_CONFIG_V6.remote_ip,
IpProto::Icmpv6,
UnroutableBehavior::Close,
None,
)
.unwrap()
== template
);
// Local address is specified, and is an invalid local address.
assert!(
IpSocketContext::<Ipv6>::new_ip_socket(
&mut ctx,
Some(DUMMY_CONFIG_V6.remote_ip),
DUMMY_CONFIG_V6.remote_ip,
IpProto::Icmpv6,
UnroutableBehavior::Close,
None,
) == Err(NoRouteError)
);
// Loopback sockets are not yet supported.
assert!(
IpSocketContext::<Ipv6>::new_ip_socket(
&mut ctx,
None,
Ipv6::LOOPBACK_ADDRESS,
IpProto::Icmpv6,
UnroutableBehavior::Close,
None,
) == Err(NoRouteError)
);
}
#[test]
fn test_send() {
// Test various edge cases of the
// `BufferIpSocketContext::send_ip_packet` method.
//
// IPv4
//
let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V4)
.build::<DummyEventDispatcher>();
// Create a normal, routable socket.
let mut builder = Ipv4SocketBuilder::default();
builder.ttl(NonZeroU8::new(1).unwrap());
let mut sock = IpSocketContext::<Ipv4>::new_ip_socket(
&mut ctx,
None,
DUMMY_CONFIG_V4.remote_ip,
IpProto::Icmp,
UnroutableBehavior::Close,
Some(builder),
)
.unwrap();
// Send a packet on the socket and make sure that the right contents
// are sent.
BufferIpSocketContext::<Ipv4, _>::send_ip_packet(
&mut ctx,
&sock,
(&[0u8][..]).into_serializer(),
)
.unwrap();
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (dev, frame) = &ctx.dispatcher().frames_sent()[0];
assert_eq!(dev, &DeviceId::new_ethernet(0));
let (body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl) =
parse_ip_packet_in_ethernet_frame::<Ipv4>(&frame).unwrap();
assert_eq!(body, [0]);
assert_eq!(src_mac, DUMMY_CONFIG_V4.local_mac);
assert_eq!(dst_mac, DUMMY_CONFIG_V4.remote_mac);
assert_eq!(src_ip, DUMMY_CONFIG_V4.local_ip.get());
assert_eq!(dst_ip, DUMMY_CONFIG_V4.remote_ip.get());
assert_eq!(proto, IpProto::Icmp);
assert_eq!(ttl, 1);
// Try sending a packet which will be larger than the device's MTU,
// and make sure it fails.
let body = vec![0u8; crate::ip::Ipv6::MINIMUM_LINK_MTU as usize];
assert_eq!(
BufferIpSocketContext::<Ipv4, _>::send_ip_packet(
&mut ctx,
&sock,
(&body[..]).into_serializer(),
)
.unwrap_err()
.1,
SendError::Mtu
);
// Make sure that sending on an unroutable socket fails.
sock.cached = CachedInfo::Unroutable;
assert_eq!(
BufferIpSocketContext::<Ipv4, _>::send_ip_packet(
&mut ctx,
&sock,
(&[0u8][..]).into_serializer(),
)
.unwrap_err()
.1,
SendError::Unroutable
);
//
// IPv6
//
let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6)
.build::<DummyEventDispatcher>();
// Create a normal, routable socket.
let mut builder = Ipv6SocketBuilder::default();
builder.hop_limit(1);
let mut sock = IpSocketContext::<Ipv6>::new_ip_socket(
&mut ctx,
None,
DUMMY_CONFIG_V6.remote_ip,
IpProto::Icmpv6,
UnroutableBehavior::Close,
Some(builder),
)
.unwrap();
// Send a packet on the socket and make sure that the right contents
// are sent.
BufferIpSocketContext::<Ipv6, _>::send_ip_packet(
&mut ctx,
&sock,
(&[0u8][..]).into_serializer(),
)
.unwrap();
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (dev, frame) = &ctx.dispatcher().frames_sent()[0];
assert_eq!(dev, &DeviceId::new_ethernet(0));
let (body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl) =
parse_ip_packet_in_ethernet_frame::<Ipv6>(&frame).unwrap();
assert_eq!(body, [0]);
assert_eq!(src_mac, DUMMY_CONFIG_V6.local_mac);
assert_eq!(dst_mac, DUMMY_CONFIG_V6.remote_mac);
assert_eq!(src_ip, DUMMY_CONFIG_V6.local_ip.get());
assert_eq!(dst_ip, DUMMY_CONFIG_V6.remote_ip.get());
assert_eq!(proto, IpProto::Icmpv6);
assert_eq!(ttl, 1);
// Try sending a packet which will be larger than the device's MTU,
// and make sure it fails.
let body = vec![0u8; crate::ip::Ipv6::MINIMUM_LINK_MTU as usize];
assert_eq!(
BufferIpSocketContext::<Ipv6, _>::send_ip_packet(
&mut ctx,
&sock,
(&body[..]).into_serializer(),
)
.unwrap_err()
.1,
SendError::Mtu
);
// Make sure that sending on an unroutable socket fails.
sock.cached = CachedInfo::Unroutable;
assert_eq!(
BufferIpSocketContext::<Ipv6, _>::send_ip_packet(
&mut ctx,
&sock,
(&[0u8][..]).into_serializer(),
)
.unwrap_err()
.1,
SendError::Unroutable
);
}
#[test]
fn test_select_ipv6_source_address() {
use AddressState::*;
//
// Test the comparison operator used by `select_ipv6_source_address` by
// separately testing each comparison condition
//
/// Construct a new `AddressEntry` with reasonable defaults for this
/// test.
fn new_addr_entry(addr: Ipv6Addr, state: AddressState) -> AddressEntry<Ipv6Addr, ()> {
AddressEntry::new(
AddrSubnet::new(addr, 128).unwrap(),
state,
AddressConfigurationType::Manual,
None,
)
}
let remote =
SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 1]))
.unwrap();
let local0 =
SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 2]))
.unwrap();
let local1 =
SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 3]))
.unwrap();
let dev0 = DeviceId::new_ethernet(0);
let dev1 = DeviceId::new_ethernet(1);
let dev2 = DeviceId::new_ethernet(2);
// Rule 1: Prefer same address
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*remote, Assigned),
&dev1,
&new_addr_entry(*local0, Assigned),
&dev2
),
Ordering::Greater
);
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*local0, Assigned),
&dev1,
&new_addr_entry(*remote, Assigned),
&dev2
),
Ordering::Less
);
// Rule 3: Avoid deprecated states
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*local0, Deprecated),
&dev1,
&new_addr_entry(*local1, Assigned),
&dev2
),
Ordering::Less
);
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*local0, Assigned),
&dev1,
&new_addr_entry(*local1, Deprecated),
&dev2
),
Ordering::Greater
);
// Rule 5: Prefer outgoing interface
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*local0, Assigned),
&dev0,
&new_addr_entry(*local1, Assigned),
&dev2
),
Ordering::Greater
);
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*local0, Assigned),
&dev1,
&new_addr_entry(*local1, Assigned),
&dev0
),
Ordering::Less
);
// Otherwise, they're equal
assert_eq!(
select_ipv6_source_address_cmp(
remote,
dev0,
&new_addr_entry(*local0, Assigned),
&dev1,
&new_addr_entry(*local1, Assigned),
&dev2
),
Ordering::Equal
);
}
}