Trace neighbor discovery status on a per-socket basis.

This avoids delaying the first packets for new neighbors by
(at least) the ARP cache silence time, or potentially even
indefinitely.
v0.7.x
whitequark 2017-12-18 10:57:26 +00:00
parent 16b59eefad
commit 8dd9bdeaad
4 changed files with 153 additions and 51 deletions

View File

@ -67,6 +67,22 @@ enum Packet<'a> {
Tcp((IpRepr, TcpRepr<'a>))
}
impl<'a> Packet<'a> {
fn neighbor_addr(&self) -> Option<IpAddress> {
match self {
&Packet::None |
&Packet::Arp(_) =>
None,
&Packet::Icmpv4((ref ipv4_repr, _)) =>
Some(ipv4_repr.dst_addr.into()),
&Packet::Raw((ref ip_repr, _)) |
&Packet::Udp((ref ip_repr, _)) |
&Packet::Tcp((ref ip_repr, _)) =>
Some(ip_repr.dst_addr())
}
}
}
impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
where DeviceT: for<'d> Device<'d> {
/// Create a network interface using the provided network device.
@ -167,7 +183,11 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
if self.socket_ingress(sockets, timestamp)? {
Ok(Some(0))
} else {
Ok(sockets.iter().filter_map(|socket| socket.poll_at()).min())
Ok(sockets.iter().filter_map(|socket| {
let socket_poll_at = socket.poll_at();
socket.meta().poll_at(socket_poll_at, |ip_addr|
self.inner.has_neighbor(&ip_addr, timestamp))
}).min())
}
}
@ -203,12 +223,12 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
caps.max_transmission_unit -= EthernetFrame::<&[u8]>::header_len();
for mut socket in sockets.iter_mut() {
if let Some(hushed_until) = socket.meta().hushed_until {
if hushed_until > timestamp {
continue
}
if !socket.meta_mut().egress_permitted(|ip_addr|
self.inner.has_neighbor(&ip_addr, timestamp)) {
continue
}
let mut neighbor_addr = None;
let mut device_result = Ok(());
let &mut Self { ref mut device, ref mut inner } = self;
let socket_result =
@ -216,9 +236,10 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
#[cfg(feature = "socket-raw")]
Socket::Raw(ref mut socket) =>
socket.dispatch(|response| {
let response = Packet::Raw(response);
neighbor_addr = response.neighbor_addr();
let tx_token = device.transmit().ok_or(Error::Exhausted)?;
device_result = inner.dispatch(tx_token, timestamp,
Packet::Raw(response));
device_result = inner.dispatch(tx_token, timestamp, response);
device_result
}, &caps.checksum),
#[cfg(feature = "socket-icmp")]
@ -226,9 +247,11 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
socket.dispatch(&caps, |response| {
let tx_token = device.transmit().ok_or(Error::Exhausted)?;
device_result = match response {
(IpRepr::Ipv4(ipv4_repr), icmpv4_repr) =>
inner.dispatch(tx_token, timestamp,
Packet::Icmpv4((ipv4_repr, icmpv4_repr))),
(IpRepr::Ipv4(ipv4_repr), icmpv4_repr) => {
let response = Packet::Icmpv4((ipv4_repr, icmpv4_repr));
neighbor_addr = response.neighbor_addr();
inner.dispatch(tx_token, timestamp, response)
}
_ => Err(Error::Unaddressable),
};
device_result
@ -236,17 +259,19 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
#[cfg(feature = "socket-udp")]
Socket::Udp(ref mut socket) =>
socket.dispatch(|response| {
let response = Packet::Udp(response);
neighbor_addr = response.neighbor_addr();
let tx_token = device.transmit().ok_or(Error::Exhausted)?;
device_result = inner.dispatch(tx_token, timestamp,
Packet::Udp(response));
device_result = inner.dispatch(tx_token, timestamp, response);
device_result
}),
#[cfg(feature = "socket-tcp")]
Socket::Tcp(ref mut socket) =>
socket.dispatch(timestamp, &caps, |response| {
let response = Packet::Tcp(response);
neighbor_addr = response.neighbor_addr();
let tx_token = device.transmit().ok_or(Error::Exhausted)?;
device_result = inner.dispatch(tx_token, timestamp,
Packet::Tcp(response));
device_result = inner.dispatch(tx_token, timestamp, response);
device_result
}),
Socket::__Nonexhaustive(_) => unreachable!()
@ -258,12 +283,9 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
// `NeighborCache` already takes care of rate limiting the neighbor discovery
// requests from the socket. However, without an additional rate limiting
// mechanism, we would spin on every socket that has yet to discover its
// peer/neighboor./
if let None = socket.meta_mut().hushed_until {
net_trace!("{}: hushed", socket.meta().handle);
}
socket.meta_mut().hushed_until =
Some(timestamp + NeighborCache::SILENT_TIME);
// neighboor.
socket.meta_mut().neighbor_missing(timestamp,
neighbor_addr.expect("non-IP response packet"));
break
}
(Err(err), _) | (_, Err(err)) => {
@ -271,13 +293,7 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
socket.meta().handle, err);
return Err(err)
}
(Ok(()), Ok(())) => {
// We definitely have a neighbor cache entry now, so this socket does not
// need to be hushed anymore.
if let Some(_) = socket.meta_mut().hushed_until.take() {
net_trace!("{}: unhushed", socket.meta().handle);
}
}
(Ok(()), Ok(())) => ()
}
}
Ok(())
@ -679,7 +695,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
fn route(&self, addr: &IpAddress) -> Result<IpAddress> {
self.ip_addrs
.iter()
.find(|cidr| cidr.contains_addr(&addr))
.find(|cidr| cidr.contains_addr(addr))
.map(|_cidr| Ok(addr.clone())) // route directly
.unwrap_or_else(|| {
match (addr, self.ipv4_gateway) {
@ -692,6 +708,17 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
})
}
fn has_neighbor<'a>(&self, addr: &'a IpAddress, timestamp: u64) -> bool {
match self.route(addr) {
Ok(routed_addr) => {
self.neighbor_cache
.lookup_pure(&routed_addr, timestamp)
.is_some()
}
Err(_) => false
}
}
fn lookup_hardware_addr<Tx>(&mut self, tx_token: Tx, timestamp: u64,
src_addr: &IpAddress, dst_addr: &IpAddress) ->
Result<(EthernetAddress, Tx)>
@ -702,7 +729,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
match self.neighbor_cache.lookup(&dst_addr, timestamp) {
NeighborAnswer::Found(hardware_addr) =>
return Ok((hardware_addr, tx_token)),
NeighborAnswer::Hushed =>
NeighborAnswer::RateLimited =>
return Err(Error::Unaddressable),
NeighborAnswer::NotFound => (),
}

View File

@ -24,7 +24,7 @@ pub(crate) enum Answer {
NotFound,
/// The neighbor address is not in the cache, or has expired,
/// and a lookup has been made recently.
Hushed
RateLimited
}
/// A neighbor cache backed by a map.
@ -47,7 +47,7 @@ pub(crate) enum Answer {
#[derive(Debug)]
pub struct Cache<'a> {
storage: ManagedMap<'a, IpAddress, Neighbor>,
hushed_until: u64,
silent_until: u64,
}
impl<'a> Cache<'a> {
@ -66,7 +66,7 @@ impl<'a> Cache<'a> {
let mut storage = storage.into();
storage.clear();
Cache { storage, hushed_until: 0 }
Cache { storage, silent_until: 0 }
}
pub(crate) fn fill(&mut self, protocol_addr: IpAddress, hardware_addr: EthernetAddress,
@ -145,10 +145,10 @@ impl<'a> Cache<'a> {
match self.lookup_pure(protocol_addr, timestamp) {
Some(hardware_addr) =>
Answer::Found(hardware_addr),
None if timestamp < self.hushed_until =>
Answer::Hushed,
None if timestamp < self.silent_until =>
Answer::RateLimited,
None => {
self.hushed_until = timestamp + Self::SILENT_TIME;
self.silent_until = timestamp + Self::SILENT_TIME;
Answer::NotFound
}
}
@ -230,7 +230,7 @@ mod test {
let mut cache = Cache::new(&mut cache_storage[..]);
assert_eq!(cache.lookup(&PADDR_A, 0), Answer::NotFound);
assert_eq!(cache.lookup(&PADDR_A, 100), Answer::Hushed);
assert_eq!(cache.lookup(&PADDR_A, 100), Answer::RateLimited);
assert_eq!(cache.lookup(&PADDR_A, 2000), Answer::NotFound);
}
}

86
src/socket/meta.rs Normal file
View File

@ -0,0 +1,86 @@
use wire::IpAddress;
use super::SocketHandle;
/// Neighbor dependency.
///
/// This enum tracks whether the socket should be polled based on the neighbor it is
/// going to send packets to.
#[derive(Debug)]
enum NeighborState {
/// Socket can be polled immediately.
Active,
/// Socket should not be polled until either `silent_until` passes or `neighbor` appears
/// in the neighbor cache.
Waiting {
neighbor: IpAddress,
silent_until: u64,
}
}
impl Default for NeighborState {
fn default() -> Self {
NeighborState::Active
}
}
/// Network socket metadata.
///
/// This includes things that only external (to the socket, that is) code
/// is interested in, but which are more conveniently stored inside the socket itself.
#[derive(Debug, Default)]
pub struct Meta {
/// Handle of this socket within its enclosing `SocketSet`.
/// Mainly useful for debug output.
pub(crate) handle: SocketHandle,
/// See [NeighborState](struct.NeighborState.html).
neighbor_state: NeighborState,
}
impl Meta {
/// Minimum delay between neighbor discovery requests for this particular socket,
/// in milliseconds.
///
/// See also `iface::NeighborCache::SILENT_TIME`.
pub(crate) const DISCOVERY_SILENT_TIME: u64 = 3_000;
pub(crate) fn poll_at<F>(&self, socket_poll_at: Option<u64>, has_neighbor: F) -> Option<u64>
where F: Fn(IpAddress) -> bool
{
match self.neighbor_state {
NeighborState::Active =>
socket_poll_at,
NeighborState::Waiting { neighbor, .. }
if has_neighbor(neighbor) =>
socket_poll_at,
NeighborState::Waiting { silent_until, .. } =>
Some(silent_until)
}
}
pub(crate) fn egress_permitted<F>(&mut self, has_neighbor: F) -> bool
where F: Fn(IpAddress) -> bool
{
match self.neighbor_state {
NeighborState::Active =>
true,
NeighborState::Waiting { neighbor, .. } => {
if has_neighbor(neighbor) {
net_trace!("{}: neighbor {} discovered, unsilencing",
self.handle, neighbor);
self.neighbor_state = NeighborState::Active;
true
} else {
false
}
}
}
}
pub(crate) fn neighbor_missing(&mut self, timestamp: u64, neighbor: IpAddress) {
net_trace!("{}: neighbor {} missing, silencing until t+{}ms",
self.handle, neighbor, Self::DISCOVERY_SILENT_TIME);
self.neighbor_state = NeighborState::Waiting {
neighbor, silent_until: timestamp + Self::DISCOVERY_SILENT_TIME
};
}
}

View File

@ -12,6 +12,7 @@
use core::marker::PhantomData;
mod meta;
#[cfg(feature = "socket-raw")]
mod raw;
#[cfg(feature = "socket-icmp")]
@ -23,6 +24,8 @@ mod tcp;
mod set;
mod ref_;
pub(crate) use self::meta::Meta as SocketMeta;
#[cfg(feature = "socket-raw")]
pub use self::raw::{PacketBuffer as RawPacketBuffer,
SocketBuffer as RawSocketBuffer,
@ -74,19 +77,6 @@ pub enum Socket<'a, 'b: 'a> {
__Nonexhaustive(PhantomData<(&'a (), &'b ())>)
}
/// Network socket metadata.
///
/// This includes things that only external (to the socket, that is) code
/// is interested in, but which are more conveniently stored inside the socket itself.
#[derive(Debug, Default)]
pub(crate) struct SocketMeta {
/// Handle of this socket within its enclosing `SocketSet`.
/// Mainly useful for debug output.
pub(crate) handle: SocketHandle,
/// A lower limit on the timestamp returned from the socket's `poll_at()` method.
pub(crate) hushed_until: Option<u64>,
}
macro_rules! dispatch_socket {
($self_:expr, |$socket:ident [$( $mut_:tt )*]| $code:expr) => ({
match $self_ {
@ -119,8 +109,7 @@ impl<'a, 'b> Socket<'a, 'b> {
}
pub(crate) fn poll_at(&self) -> Option<u64> {
let poll_at = dispatch_socket!(self, |socket []| socket.poll_at());
self.meta().hushed_until.or(poll_at)
dispatch_socket!(self, |socket []| socket.poll_at())
}
}