blob: 7e8d684d9c16bf61ee119bce7cdec79691ecfe03 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! The Address Resolution Protocol (ARP).
use std::collections::HashMap;
use std::hash::Hash;
use std::time::Duration;
use packet::{BufferMut, InnerSerializer, Serializer};
use crate::device::ethernet::EthernetArpDevice;
use crate::device::DeviceLayerTimerId;
use crate::ip::Ipv4Addr;
use crate::wire::arp::{ArpPacket, ArpPacketBuilder, HType, PType};
use crate::{Context, EventDispatcher, TimerId, TimerIdInner};
use log::debug;
/// The type of an ARP operation.
#[derive(Debug, Eq, PartialEq)]
#[allow(missing_docs)]
#[repr(u16)]
pub enum ArpOp {
Request = ArpOp::REQUEST,
Response = ArpOp::RESPONSE,
}
impl ArpOp {
const REQUEST: u16 = 0x0001;
const RESPONSE: u16 = 0x0002;
/// Construct an `ArpOp` from a `u16`.
///
/// `from_u16` returns the `ArpOp` with the numerical value `u`, or `None`
/// if the value is unrecognized.
pub fn from_u16(u: u16) -> Option<ArpOp> {
match u {
Self::REQUEST => Some(ArpOp::Request),
Self::RESPONSE => Some(ArpOp::Response),
_ => None,
}
}
}
/// An ARP hardware protocol.
#[derive(Debug, PartialEq)]
#[allow(missing_docs)]
#[repr(u16)]
pub enum ArpHardwareType {
Ethernet = ArpHardwareType::ETHERNET,
}
impl ArpHardwareType {
const ETHERNET: u16 = 0x0001;
/// Construct an `ArpHardwareType` from a `u16`.
///
/// `from_u16` returns the `ArpHardwareType` with the numerical value `u`,
/// or `None` if the value is unrecognized.
pub fn from_u16(u: u16) -> Option<ArpHardwareType> {
match u {
Self::ETHERNET => Some(ArpHardwareType::Ethernet),
_ => None,
}
}
}
/// The identifier for timer events in the ARP layer.
///
/// This is used to retry sending ARP requests.
#[derive(Copy, Clone, PartialEq)]
pub struct ArpTimerId<P: PType> {
device_id: u64,
ip_addr: P,
}
/// A device layer protocol which can support ARP.
///
/// An `ArpDevice<P>` is a device layer protocol which can support ARP with the
/// network protocol `P` (e.g., IPv4, IPv6, etc).
pub trait ArpDevice<P: PType + Eq + Hash>: Sized {
/// The hardware address type used by this protocol.
type HardwareAddr: HType;
/// The broadcast address.
const BROADCAST: Self::HardwareAddr;
/// Send an ARP packet in a device layer frame.
///
/// `send_arp_frame` accepts a device ID, a destination hardware address,
/// and a `Serializer`. It computes the routing information, serializes the
/// request in a device layer frame, and sends it.
fn send_arp_frame<D: EventDispatcher, S: Serializer>(
ctx: &mut Context<D>, device_id: u64, dst: Self::HardwareAddr, body: S,
);
/// Get a mutable reference to a device's ARP state.
fn get_arp_state<D: EventDispatcher>(
ctx: &mut Context<D>, device_id: u64,
) -> &mut ArpState<P, Self>;
/// Get the protocol address of this interface.
fn get_protocol_addr<D: EventDispatcher>(ctx: &mut Context<D>, device_id: u64) -> Option<P>;
fn get_hardware_addr<D: EventDispatcher>(
ctx: &mut Context<D>, device_id: u64,
) -> Self::HardwareAddr;
}
/// Handle a ARP timer event
///
/// This currently only supports Ipv4/Ethernet ARP, since we know that that is
/// the only case that the netstack currently handles. In the future, this may
/// be extended to support other hardware or protocol types.
pub fn handle_timeout<D: EventDispatcher>(ctx: &mut Context<D>, id: ArpTimerId<Ipv4Addr>) {
handle_timeout_inner::<D, Ipv4Addr, EthernetArpDevice>(ctx, id);
}
fn handle_timeout_inner<D: EventDispatcher, P: PType + Eq + Hash, AD: ArpDevice<P>>(
ctx: &mut Context<D>, id: ArpTimerId<P>,
) {
send_arp_request::<D, P, AD>(ctx, id.device_id, id.ip_addr);
}
/// Receive an ARP packet from a device.
///
/// The protocol and hardware types (`P` and `D::HardwareAddr` respectively)
/// must be set statically. Unless there is only one valid pair of protocol and
/// hardware types in a given context, it is the caller's responsibility to call
/// `peek_arp_types` in order to determine which types to use in calling this
/// function.
pub fn receive_arp_packet<
D: EventDispatcher,
P: PType + Eq + Hash,
AD: ArpDevice<P>,
B: BufferMut,
>(
ctx: &mut Context<D>, device_id: u64, src_addr: AD::HardwareAddr, dst_addr: AD::HardwareAddr,
mut buffer: B,
) {
// TODO(wesleyac) Add support for gratuitous ARP and probe/announce.
let packet = if let Ok(packet) = buffer.parse::<ArpPacket<_, AD::HardwareAddr, P>>() {
let addressed_to_me =
Some(packet.target_protocol_address()) == AD::get_protocol_addr(ctx, device_id);
let table = &mut AD::get_arp_state(ctx, device_id).table;
// The following logic is equivalent to the "Packet Reception" section of RFC 826.
//
// We statically know that the hardware type and protocol type are correct, so we do not
// need to have additional code to check that. The remainder of the algorithm is:
//
// Merge_flag := false
// If the pair <protocol type, sender protocol address> is
// already in my translation table, update the sender
// hardware address field of the entry with the new
// information in the packet and set Merge_flag to true.
// ?Am I the target protocol address?
// Yes:
// If Merge_flag is false, add the triplet <protocol type,
// sender protocol address, sender hardware address> to
// the translation table.
// ?Is the opcode ares_op$REQUEST? (NOW look at the opcode!!)
// Yes:
// Swap hardware and protocol fields, putting the local
// hardware and protocol addresses in the sender fields.
// Set the ar$op field to ares_op$REPLY
// Send the packet to the (new) target hardware address on
// the same hardware on which the request was received.
//
// This can be summed up as follows:
//
// +----------+---------------+---------------+-----------------------------+
// | opcode | Am I the TPA? | SPA in table? | action |
// +----------+---------------+---------------+-----------------------------+
// | REQUEST | yes | yes | Update table, Send response |
// | REQUEST | yes | no | Update table, Send response |
// | REQUEST | no | yes | Update table |
// | REQUEST | no | no | NOP |
// | RESPONSE | yes | yes | Update table |
// | RESPONSE | yes | no | Update table |
// | RESPONSE | no | yes | Update table |
// | RESPONSE | no | no | NOP |
// +----------+---------------+---------------+-----------------------------+
//
// Given that the semantics of ArpTable is that inserting and updating an entry are the
// same, this can be implemented with two if statements (one to update the table, and one
// to send a response).
if addressed_to_me || table.lookup(packet.sender_protocol_address()).is_some() {
table.insert(
packet.sender_protocol_address(),
packet.sender_hardware_address(),
);
// Since we just got the protocol -> hardware address mapping, we can cancel a timeout
// to resend a request.
ctx.dispatcher
.cancel_timeout(TimerId(TimerIdInner::DeviceLayer(
DeviceLayerTimerId::ArpIpv4(ArpTimerId {
device_id: device_id,
ip_addr: packet.sender_protocol_address().addr(),
}),
)));
}
if addressed_to_me && packet.operation() == ArpOp::Request {
let self_hw_addr = AD::get_hardware_addr(ctx, device_id);
AD::send_arp_frame(
ctx,
device_id,
packet.sender_hardware_address(),
InnerSerializer::new_vec(
ArpPacketBuilder::new(
ArpOp::Response,
self_hw_addr,
packet.target_protocol_address(),
packet.sender_hardware_address(),
packet.sender_protocol_address(),
),
buffer,
),
);
}
} else {
// TODO(joshlf): Do something else here?
return;
};
}
/// Look up the hardware address for a network protocol address.
pub fn lookup<D: EventDispatcher, P: PType + Eq + Hash, AD: ArpDevice<P>>(
ctx: &mut Context<D>, device_id: u64, local_addr: AD::HardwareAddr, lookup_addr: P,
) -> Option<AD::HardwareAddr> {
// TODO(joshlf): Figure out what to do if a frame can't be sent right now
// because it needs to wait for an ARP reply. Where do we put those frames?
// How do we associate them with the right ARP reply? How do we retreive
// them when we get that ARP reply? How do we time out so we don't hold onto
// a stale frame forever?
let result = AD::get_arp_state(ctx, device_id)
.table
.lookup(lookup_addr)
.cloned();
// Send an ARP Request if the address is not in our cache
if result.is_none() {
send_arp_request::<D, P, AD>(ctx, device_id, lookup_addr);
}
result
}
fn send_arp_request<D: EventDispatcher, P: PType + Eq + Hash, AD: ArpDevice<P>>(
ctx: &mut Context<D>, device_id: u64, lookup_addr: P,
) {
if let Some(sender_protocol_addr) = AD::get_protocol_addr(ctx, device_id) {
let self_hw_addr = AD::get_hardware_addr(ctx, device_id);
AD::send_arp_frame(
ctx,
device_id,
AD::BROADCAST,
ArpPacketBuilder::new(
ArpOp::Request,
self_hw_addr,
sender_protocol_addr,
// This is meaningless, since RFC 826 does not specify the behaviour.
// However, the broadcast address is sensible, as this is the actual
// address we are sending the packet to.
AD::BROADCAST,
lookup_addr,
),
);
// TODO(wesleyac): Configurable timeout.
// Currently at 20 seconds because that's what FreeBSD does.
// TODO(wesleyac): maxretries
ctx.dispatcher.schedule_timeout(
Duration::from_secs(20),
TimerId(TimerIdInner::DeviceLayer(DeviceLayerTimerId::ArpIpv4(
ArpTimerId {
device_id: device_id,
ip_addr: lookup_addr.addr(),
},
))),
);
AD::get_arp_state(ctx, device_id)
.table
.set_waiting(lookup_addr);
} else {
// RFC 826 does not specify what to do if we don't have a local address, but there is no
// reasonable way to send an ARP request without one (as the receiver will cache our local
// address on receiving the packet. So, if this is the case, we do not send an ARP request.
// TODO(wesleyac): Should we cache these, and send packets once we have an address?
debug!("Not sending ARP request, since we don't know our local protocol address");
}
}
/// The state associated with an instance of the Address Resolution Protocol
/// (ARP).
///
/// Each device will contain an `ArpState` object for each of the network
/// protocols that it supports.
pub struct ArpState<P: PType + Hash + Eq, D: ArpDevice<P>> {
// NOTE(joshlf): Taking an ArpDevice type parameter is technically
// unnecessary here; we could instead just be parametric on a hardware type
// and a network protocol type. However, doing it this way ensure that
// device layer code doesn't accidentally invoke receive_arp_packet with
// different ArpDevice implementations in different places (this would fail
// to compile because the get_arp_state method on ArpDevice returns an
// ArpState<_, Self>, which requires that the ArpDevice implementation
// matches the type of the ArpState stored in that device's state).
table: ArpTable<D::HardwareAddr, P>,
}
impl<P: PType + Hash + Eq, D: ArpDevice<P>> Default for ArpState<P, D> {
fn default() -> Self {
ArpState {
table: ArpTable::default(),
}
}
}
struct ArpTable<H, P: Hash + Eq> {
table: HashMap<P, ArpValue<H>>,
}
#[derive(Debug, Eq, PartialEq)] // for testing
enum ArpValue<H> {
Known(H),
Waiting,
}
impl<H, P: Hash + Eq> ArpTable<H, P> {
fn insert(&mut self, net: P, hw: H) {
self.table.insert(net, ArpValue::Known(hw));
}
fn set_waiting(&mut self, net: P) {
self.table.insert(net, ArpValue::Waiting);
}
fn lookup(&self, addr: P) -> Option<&H> {
match self.table.get(&addr) {
Some(ArpValue::Known(x)) => Some(x),
_ => None,
}
}
}
impl<H, P: Hash + Eq> Default for ArpTable<H, P> {
fn default() -> Self {
ArpTable {
table: HashMap::default(),
}
}
}
#[cfg(test)]
mod tests {
use packet::ParseBuffer;
use super::*;
use crate::device::ethernet::{set_ip_addr, EtherType, Mac};
use crate::ip::{Ipv4Addr, Subnet};
use crate::testutil;
use crate::testutil::DummyEventDispatcher;
use crate::wire::arp::{peek_arp_types, ArpPacketBuilder};
use crate::wire::ethernet::EthernetFrame;
use crate::StackState;
const TEST_LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
const TEST_REMOTE_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
const TEST_LOCAL_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
const TEST_REMOTE_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
#[test]
fn test_send_arp_request_on_cache_miss() {
let mut state = StackState::default();
let dev_id = state.device.add_ethernet_device(TEST_LOCAL_MAC);
let dispatcher = DummyEventDispatcher::default();
let mut ctx: Context<DummyEventDispatcher> = Context::new(state, dispatcher);
set_ip_addr(
&mut ctx,
dev_id.id,
TEST_LOCAL_IPV4,
Subnet::new(TEST_LOCAL_IPV4, 24),
);
lookup::<DummyEventDispatcher, Ipv4Addr, EthernetArpDevice>(
&mut ctx,
1,
TEST_LOCAL_MAC,
TEST_REMOTE_IPV4,
);
// Check that we send the original packet, then resend a few times if
// we don't receive a response.
for packet_num in 0..3 {
assert_eq!(ctx.dispatcher.frames_sent().len(), packet_num + 1);
let mut buf = &ctx.dispatcher.frames_sent()[packet_num].1[..];
let frame = buf.parse::<EthernetFrame<_>>().unwrap();
assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
assert_eq!(frame.src_mac(), TEST_LOCAL_MAC);
assert_eq!(EthernetArpDevice::BROADCAST, frame.dst_mac());
let (hw, proto) = peek_arp_types(frame.body()).unwrap();
assert_eq!(hw, ArpHardwareType::Ethernet);
assert_eq!(proto, EtherType::Ipv4);
let arp = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
assert_eq!(arp.operation(), ArpOp::Request);
assert_eq!(arp.sender_hardware_address(), TEST_LOCAL_MAC);
assert_eq!(arp.target_hardware_address(), EthernetArpDevice::BROADCAST);
assert_eq!(arp.sender_protocol_address(), TEST_LOCAL_IPV4);
assert_eq!(arp.target_protocol_address(), TEST_REMOTE_IPV4);
testutil::trigger_next_timer(&mut ctx);
}
// TODO(wesleyac): Check that once we receive a response, we no longer resend packets
}
#[test]
fn test_handle_arp_request() {
let mut state = StackState::default();
let dev_id = state.device.add_ethernet_device(TEST_LOCAL_MAC);
let dispatcher = DummyEventDispatcher::default();
let mut ctx: Context<DummyEventDispatcher> = Context::new(state, dispatcher);
set_ip_addr(
&mut ctx,
dev_id.id,
TEST_LOCAL_IPV4,
Subnet::new(TEST_LOCAL_IPV4, 24),
);
let mut buf = ArpPacketBuilder::new(
ArpOp::Request,
TEST_REMOTE_MAC,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
TEST_LOCAL_IPV4,
)
.serialize_outer();
let (hw, proto) = peek_arp_types(buf.as_ref()).unwrap();
assert_eq!(hw, ArpHardwareType::Ethernet);
assert_eq!(proto, EtherType::Ipv4);
receive_arp_packet::<DummyEventDispatcher, Ipv4Addr, EthernetArpDevice, _>(
&mut ctx,
1,
TEST_REMOTE_MAC,
TEST_LOCAL_MAC,
buf,
);
assert_eq!(
lookup::<DummyEventDispatcher, Ipv4Addr, EthernetArpDevice>(
&mut ctx,
1,
TEST_LOCAL_MAC,
TEST_REMOTE_IPV4
)
.unwrap(),
TEST_REMOTE_MAC
);
assert_eq!(ctx.dispatcher.frames_sent().len(), 1);
let mut buf = &ctx.dispatcher.frames_sent()[0].1[..];
let frame = buf.parse::<EthernetFrame<_>>().unwrap();
assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
assert_eq!(frame.src_mac(), TEST_LOCAL_MAC);
assert_eq!(frame.dst_mac(), TEST_REMOTE_MAC);
let (hw, proto) = peek_arp_types(frame.body()).unwrap();
assert_eq!(hw, ArpHardwareType::Ethernet);
assert_eq!(proto, EtherType::Ipv4);
let arp = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
assert_eq!(arp.operation(), ArpOp::Response);
assert_eq!(arp.sender_hardware_address(), TEST_LOCAL_MAC);
assert_eq!(arp.target_hardware_address(), TEST_REMOTE_MAC);
assert_eq!(arp.sender_protocol_address(), TEST_LOCAL_IPV4);
assert_eq!(arp.target_protocol_address(), TEST_REMOTE_IPV4);
}
#[test]
fn test_arp_table() {
let mut t: ArpTable<Mac, Ipv4Addr> = ArpTable::default();
assert_eq!(t.lookup(Ipv4Addr::new([10, 0, 0, 1])), None);
t.insert(Ipv4Addr::new([10, 0, 0, 1]), Mac::new([1, 2, 3, 4, 5, 6]));
assert_eq!(
*t.lookup(Ipv4Addr::new([10, 0, 0, 1])).unwrap(),
Mac::new([1, 2, 3, 4, 5, 6])
);
assert_eq!(t.lookup(Ipv4Addr::new([10, 0, 0, 2])), None);
}
}