blob: 6c1a396eddcbaa372f9ae31a77e902f9006d29a4 [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 Ethernet protocol.
use std::fmt::{self, Debug, Display, Formatter};
use log::debug;
use packet::{Buf, MtuError, ParseBuffer, Serializer};
use specialize_ip_macro::specialize_ip_address;
use zerocopy::{AsBytes, FromBytes, Unaligned};
use crate::device::arp::{ArpDevice, ArpHardwareType, ArpState};
use crate::device::{DeviceId, FrameDestination};
use crate::ip::{AddrSubnet, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
use crate::wire::arp::peek_arp_types;
use crate::wire::ethernet::{EthernetFrame, EthernetFrameBuilder};
use crate::{Context, EventDispatcher};
/// A media access control (MAC) address.
#[derive(Copy, Clone, Eq, PartialEq, Debug, FromBytes, AsBytes, Unaligned)]
#[repr(transparent)]
pub struct Mac([u8; 6]);
impl Mac {
/// The broadcast MAC address.
///
/// The broadcast MAC address, FF:FF:FF:FF:FF:FF, indicates that a frame should
/// be received by all receivers regardless of their local MAC address.
pub(crate) const BROADCAST: Mac = Mac([0xFF; 6]);
const EUI_MAGIC: [u8; 2] = [0xff, 0xfe];
/// Construct a new MAC address.
pub const fn new(bytes: [u8; 6]) -> Mac {
Mac(bytes)
}
/// Get the bytes of the MAC address.
pub(crate) fn bytes(self) -> [u8; 6] {
self.0
}
/// Return the RFC4291 EUI-64 interface identifier for this MAC address.
///
/// `eui_magic` is the two bytes that are inserted between the MAC address
/// to form the identifier. If None, the standard 0xfffe will be used.
///
// TODO: remove `eui_magic` arg if/once it is unused.
pub(crate) fn to_eui64(self, eui_magic: Option<[u8; 2]>) -> [u8; 8] {
let mut eui = [0; 8];
eui[0..3].copy_from_slice(&self.0[0..3]);
eui[3..5].copy_from_slice(&eui_magic.unwrap_or(Self::EUI_MAGIC));
eui[5..8].copy_from_slice(&self.0[3..6]);
eui[0] ^= 0b0000_0010;
eui
}
/// Return the link-local IPv6 address for this MAC address, as per RFC 4862.
///
/// `eui_magic` is the two bytes that are inserted between the MAC address
/// to form the identifier. If None, the standard 0xfffe will be used.
///
/// TODO: remove `eui_magic` arg if/once it is unused.
pub(crate) fn to_slaac_ipv6(self, eui_magic: Option<[u8; 2]>) -> Ipv6Addr {
let mut ipv6_addr = [0; 16];
ipv6_addr[0..2].copy_from_slice(&[0xfe, 0x80]);
ipv6_addr[8..16].copy_from_slice(&self.to_eui64(eui_magic));
Ipv6Addr::new(ipv6_addr)
}
/// Is this a unicast MAC address?
///
/// Returns true if the least significant bit of the first byte of the
/// address is 0.
pub(crate) fn is_unicast(self) -> bool {
// https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
self.0[0] & 1 == 0
}
/// Is this a multicast MAC address?
///
/// Returns true if the least significant bit of the first byte of the
/// address is 1.
pub(crate) fn is_multicast(self) -> bool {
// https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
self.0[0] & 1 == 1
}
/// Is this the broadcast MAC address?
///
/// Returns true if this is the broadcast MAC address, FF:FF:FF:FF:FF:FF.
pub(crate) fn is_broadcast(self) -> bool {
// https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
self == Mac::BROADCAST
}
}
/// An EtherType number.
#[allow(missing_docs)]
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
pub(crate) enum EtherType {
Ipv4,
Arp,
Ipv6,
Other(u16),
}
impl EtherType {
const IPV4: u16 = 0x0800;
const ARP: u16 = 0x0806;
const IPV6: u16 = 0x86DD;
}
impl From<u16> for EtherType {
fn from(u: u16) -> EtherType {
match u {
Self::IPV4 => EtherType::Ipv4,
Self::ARP => EtherType::Arp,
Self::IPV6 => EtherType::Ipv6,
u => EtherType::Other(u),
}
}
}
impl Into<u16> for EtherType {
fn into(self) -> u16 {
match self {
EtherType::Ipv4 => Self::IPV4,
EtherType::Arp => Self::ARP,
EtherType::Ipv6 => Self::IPV6,
EtherType::Other(u) => u,
}
}
}
impl Display for EtherType {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(
f,
"{}",
match self {
EtherType::Ipv4 => "IPv4",
EtherType::Arp => "ARP",
EtherType::Ipv6 => "IPv6",
EtherType::Other(u) => return write!(f, "EtherType {}", u),
}
)
}
}
impl Debug for EtherType {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
Display::fmt(self, f)
}
}
/// The state associated with an Ethernet device.
pub(crate) struct EthernetDeviceState {
mac: Mac,
mtu: u32,
ipv4_addr_sub: Option<AddrSubnet<Ipv4Addr>>,
ipv6_addr_sub: Option<AddrSubnet<Ipv6Addr>>,
ipv4_arp: ArpState<Ipv4Addr, EthernetArpDevice>,
}
impl EthernetDeviceState {
/// Construct a new `EthernetDeviceState`.
///
/// `new` constructs a new `EthernetDeviceState` with the given MAC address
/// and MTU. The MTU will be taken as a limit on the size of Ethernet
/// payloads - the Ethernet header is not counted towards the MTU.
pub(crate) fn new(mac: Mac, mtu: u32) -> EthernetDeviceState {
// TODO(joshlf): Ensure that the configured MTU meets the minimum IPv6
// MTU requirement. A few questions:
// - How do we wire error information back up the call stack? Should
// this just return a Result or something?
EthernetDeviceState {
mac,
mtu,
ipv4_addr_sub: None,
ipv6_addr_sub: None,
ipv4_arp: ArpState::default(),
}
}
}
/// An extension trait adding IP-related functionality to `Ipv4` and `Ipv6`.
pub(crate) trait EthernetIpExt: Ip {
const ETHER_TYPE: EtherType;
}
impl<I: Ip> EthernetIpExt for I {
default const ETHER_TYPE: EtherType = EtherType::Ipv4;
}
impl EthernetIpExt for Ipv4 {
const ETHER_TYPE: EtherType = EtherType::Ipv4;
}
impl EthernetIpExt for Ipv6 {
const ETHER_TYPE: EtherType = EtherType::Ipv6;
}
/// Send an IP packet in an Ethernet frame.
///
/// `send_ip_frame` accepts a device ID, a local IP address, and a
/// `SerializationRequest`. It computes the routing information and serializes
/// the request in a new Ethernet frame and sends it.
#[specialize_ip_address]
pub(crate) fn send_ip_frame<D: EventDispatcher, A: IpAddress, S: Serializer>(
ctx: &mut Context<D>,
device_id: u64,
local_addr: A,
body: S,
) -> Result<(), (MtuError<S::InnerError>, S)> {
let state = get_device_state(ctx, device_id);
let (local_mac, mtu) = (state.mac, state.mtu);
#[ipv4addr]
let dst_mac = {
if let Some(dst_mac) = crate::device::arp::lookup::<_, _, EthernetArpDevice>(
ctx, device_id, local_mac, local_addr,
) {
Some(dst_mac)
} else {
log_unimplemented!(
None,
"device::ethernet::send_ip_frame: unimplemented on arp cache miss"
)
}
};
#[ipv6addr]
let dst_mac = log_unimplemented!(None, "device::ethernet::send_ip_frame: IPv6 unimplemented");
if let Some(dst_mac) = dst_mac {
let buffer = body
.with_mtu(mtu as usize)
.encapsulate(EthernetFrameBuilder::new(local_mac, dst_mac, A::Version::ETHER_TYPE))
.serialize_outer()
.map_err(|(err, ser)| (err, ser.into_serializer().into_serializer()))?;
ctx.dispatcher().send_frame(DeviceId::new_ethernet(device_id), buffer.as_ref());
}
Ok(())
}
/// Receive an Ethernet frame from the network.
pub(crate) fn receive_frame<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: u64,
bytes: &mut [u8],
) {
let mut buffer = Buf::new(bytes, ..);
let frame = if let Ok(frame) = buffer.parse::<EthernetFrame<_>>() {
frame
} else {
// TODO(joshlf): Do something else?
return;
};
let (src, dst) = (frame.src_mac(), frame.dst_mac());
let device = DeviceId::new_ethernet(device_id);
let frame_dst = if dst == get_device_state(ctx, device_id).mac {
FrameDestination::Unicast
} else if dst.is_broadcast() {
FrameDestination::Broadcast
} else {
return;
};
match frame.ethertype() {
Some(EtherType::Arp) => {
let types = if let Ok(types) = peek_arp_types(buffer.as_ref()) {
types
} else {
// TODO(joshlf): Do something else here?
return;
};
match types {
(ArpHardwareType::Ethernet, EtherType::Ipv4) => {
crate::device::arp::receive_arp_packet::<D, Ipv4Addr, EthernetArpDevice, _>(
ctx, device_id, src, dst, buffer,
)
}
types => debug!("got ARP packet for unsupported types: {:?}", types),
}
}
Some(EtherType::Ipv4) => {
crate::ip::receive_ip_packet::<D, _, Ipv4>(ctx, device, frame_dst, buffer)
}
Some(EtherType::Ipv6) => {
crate::ip::receive_ip_packet::<D, _, Ipv6>(ctx, device, frame_dst, buffer)
}
Some(EtherType::Other(_)) | None => {} // TODO(joshlf)
}
}
/// Get the IP address and subnet associated with this device.
#[specialize_ip_address]
pub(crate) fn get_ip_addr_subnet<D: EventDispatcher, A: IpAddress>(
ctx: &mut Context<D>,
device_id: u64,
) -> Option<AddrSubnet<A>> {
#[ipv4addr]
return get_device_state(ctx, device_id).ipv4_addr_sub;
#[ipv6addr]
return get_device_state(ctx, device_id).ipv6_addr_sub;
}
/// Set the IP address and subnet associated with this device.
#[specialize_ip_address]
pub(crate) fn set_ip_addr_subnet<D: EventDispatcher, A: IpAddress>(
ctx: &mut Context<D>,
device_id: u64,
addr_sub: AddrSubnet<A>,
) {
#[ipv4addr]
get_device_state(ctx, device_id).ipv4_addr_sub = Some(addr_sub);
#[ipv6addr]
get_device_state(ctx, device_id).ipv6_addr_sub = Some(addr_sub);
}
/// Get the MTU associated with this device.
pub(crate) fn get_mtu<D: EventDispatcher>(ctx: &mut Context<D>, device_id: u64) -> u32 {
get_device_state(ctx, device_id).mtu
}
/// Insert an entry into this device's ARP table.
pub(crate) fn insert_arp_table_entry<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: u64,
addr: Ipv4Addr,
mac: Mac,
) {
crate::device::arp::insert::<D, Ipv4Addr, EthernetArpDevice>(ctx, device_id, addr, mac);
}
fn get_device_state<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: u64,
) -> &mut EthernetDeviceState {
// TODO(joshlf): Sometimes we want lookups to be infallible (if we know that
// the device exists), but sometimes we want to report an error to the user.
// Right now, this is a DoS vector.
ctx.state_mut()
.device
.ethernet
.get_mut(&device_id)
.unwrap_or_else(|| panic!("no such Ethernet device: {}", device_id))
}
// Dummy type used to implement ArpDevice.
pub(crate) struct EthernetArpDevice;
impl ArpDevice<Ipv4Addr> for EthernetArpDevice {
type HardwareAddr = Mac;
const BROADCAST: Mac = Mac::BROADCAST;
fn send_arp_frame<D: EventDispatcher, S: Serializer>(
ctx: &mut Context<D>,
device_id: u64,
dst: Self::HardwareAddr,
body: S,
) -> Result<(), MtuError<S::InnerError>> {
let src = get_device_state(ctx, device_id).mac;
let buffer = body
.encapsulate(EthernetFrameBuilder::new(src, dst, EtherType::Arp))
.serialize_outer()
.map_err(|(err, _)| err)?;
ctx.dispatcher().send_frame(DeviceId::new_ethernet(device_id), buffer.as_ref());
Ok(())
}
fn get_arp_state<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: u64,
) -> &mut ArpState<Ipv4Addr, Self> {
&mut get_device_state(ctx, device_id).ipv4_arp
}
fn get_protocol_addr<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: u64,
) -> Option<Ipv4Addr> {
get_device_state(ctx, device_id).ipv4_addr_sub.map(AddrSubnet::into_addr)
}
fn get_hardware_addr<D: EventDispatcher>(ctx: &mut Context<D>, device_id: u64) -> Mac {
get_device_state(ctx, device_id).mac
}
}
#[cfg(test)]
mod tests {
use packet::{Buf, BufferSerializer};
use super::*;
use crate::testutil::{DummyEventDispatcherBuilder, DUMMY_CONFIG};
#[test]
fn test_mac_to_eui() {
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_eui64(None),
[0x02, 0x1a, 0xaa, 0xff, 0xfe, 0x12, 0x34, 0x56]
);
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_eui64(Some([0xfe, 0xfe])),
[0x02, 0x1a, 0xaa, 0xfe, 0xfe, 0x12, 0x34, 0x56]
);
}
#[test]
fn test_slaac() {
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_slaac_ipv6(None),
Ipv6Addr::new([
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x02, 0x1a, 0xaa, 0xff, 0xfe, 0x12, 0x34, 0x56
])
);
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_slaac_ipv6(Some([0xfe, 0xfe])),
Ipv6Addr::new([
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x02, 0x1a, 0xaa, 0xfe, 0xfe, 0x12, 0x34, 0x56
])
);
}
#[test]
fn test_mtu() {
// Test that we send an Ethernet frame whose size is less than the MTU,
// and that we don't send an Ethernet frame whose size is greater than
// the MTU.
fn test(size: usize, expect_frames_sent: usize) {
let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG).build();
send_ip_frame(
&mut ctx,
1,
DUMMY_CONFIG.remote_ip,
BufferSerializer::new_vec(Buf::new(&mut vec![0; size], ..)),
);
assert_eq!(ctx.dispatcher().frames_sent().len(), expect_frames_sent);
}
// The Ethernet device MTU currently defaults to IPV6_MIN_MTU.
test(crate::ip::IPV6_MIN_MTU as usize, 1);
test(crate::ip::IPV6_MIN_MTU as usize + 1, 0);
}
}