blob: beb33aa49325ce5923df507001c930777382441d [file] [log] [blame] [edit]
// 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.
//! Ethernet protocol types.
use core::fmt::{self, Debug, Display, Formatter};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
use crate::ip::{AddrSubnet, IpAddr, IpAddress, Ipv6, Ipv6Addr};
use crate::{
BroadcastAddr, BroadcastAddress, LinkLocalUnicastAddr, MulticastAddr, MulticastAddress,
UnicastAddr, UnicastAddress, Witness,
};
/// A media access control (MAC) address.
///
/// MAC addresses are used to identify devices in the Ethernet protocol.
///
/// MAC addresses can be derived from multicast IP addresses; see the `From`
/// implementation for more details.
///
/// # Layout
///
/// `Mac` has the same layout as `[u8; 6]`, which is the layout that most
/// protocols use to represent a MAC address in their packet formats. This can
/// be useful when parsing a MAC address from a packet. For example:
///
/// ```rust
/// # use net_types::ethernet::Mac;
/// /// The header of an Ethernet frame.
/// ///
/// /// `EthernetHeader` has the same layout as the header of an Ethernet frame.
/// #[repr(C)]
/// struct EthernetHeader {
/// dst: Mac,
/// src: Mac,
/// ethertype: [u8; 2],
/// }
/// ```
#[derive(
Copy, Clone, Eq, PartialEq, Hash, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned,
)]
#[repr(transparent)]
pub struct Mac([u8; Mac::BYTES]);
impl Mac {
/// The number of bytes in a Mac address.
pub const BYTES: usize = 6;
/// 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.
// TODO(https://github.com/rust-lang/rust/issues/73255): Make this
// `BroadcastAddr<Mac>` once the `const_precise_live_drops` feature has
// stabilized, and thus it's possible to write a `const fn` which converts
// from `BroadcastAddr<A>` to `A`.
pub const BROADCAST: Mac = Mac([0xFF; Self::BYTES]);
/// The default [RFC 4291] EUI-64 magic value used by the [`to_eui64`]
/// method.
///
/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
/// [`to_eui64`]: crate::ethernet::Mac::to_eui64
pub const DEFAULT_EUI_MAGIC: [u8; 2] = [0xff, 0xfe];
/// The all-zeroes MAC address.
pub const UNSPECIFIED: Mac = Mac([0x00; Self::BYTES]);
/// Constructs a new MAC address.
#[inline]
pub const fn new(bytes: [u8; Self::BYTES]) -> Mac {
Mac(bytes)
}
/// Gets the bytes of the MAC address.
#[inline]
pub const fn bytes(self) -> [u8; Self::BYTES] {
self.0
}
/// Returns the [RFC 4291] EUI-64 interface identifier for this MAC address
/// with the default EUI magic value.
///
/// `mac.to_eui64()` is equivalent to
/// `mac.to_eui64_with_magic(Mac::DEFAULT_EUI_MAGIC)`.
///
/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
#[inline]
pub fn to_eui64(self) -> [u8; 8] {
self.to_eui64_with_magic(Mac::DEFAULT_EUI_MAGIC)
}
/// Returns the [RFC 4291] EUI-64 interface identifier for this MAC address
/// with a custom EUI magic value.
///
/// `eui_magic` is the two bytes that are inserted between the bytes of the
/// MAC address to form the identifier. Also see the [`to_eui64`] method,
/// which uses the default magic value of 0xFFFE.
///
/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
/// [`to_eui64`]: crate::ethernet::Mac::to_eui64
#[inline]
pub fn to_eui64_with_magic(self, eui_magic: [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);
eui[5..8].copy_from_slice(&self.0[3..6]);
eui[0] ^= 0b0000_0010;
eui
}
/// Returns the link-local unicast IPv6 address and subnet for this MAC
/// address, as per [RFC 4862], with the default EUI magic value.
///
/// `mac.to_ipv6_link_local()` is equivalent to
/// `mac.to_ipv6_link_local_with_magic(Mac::DEFAULT_EUI_MAGIC)`.
///
/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
#[inline]
pub fn to_ipv6_link_local(self) -> AddrSubnet<Ipv6Addr, LinkLocalUnicastAddr<Ipv6Addr>> {
self.to_ipv6_link_local_with_magic(Mac::DEFAULT_EUI_MAGIC)
}
/// Returns the link-local unicast IPv6 address and subnet for this MAC
/// address, as per [RFC 4862].
///
/// `eui_magic` is the two bytes that are inserted between the bytes of the
/// MAC address to form the identifier. Also see the [`to_ipv6_link_local`]
/// method, which uses the default magic value of 0xFFFE.
///
/// The subnet prefix length is 128 -
/// [`Ipv6::UNICAST_INTERFACE_IDENTIFIER_BITS`].
///
/// [RFC 4862]: https://tools.ietf.org/html/rfc4862
/// [`to_ipv6_link_local`]: crate::ethernet::Mac::to_ipv6_link_local
/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
#[inline]
pub fn to_ipv6_link_local_with_magic(
self,
eui_magic: [u8; 2],
) -> AddrSubnet<Ipv6Addr, LinkLocalUnicastAddr<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_with_magic(eui_magic));
// We know the call to `unwrap` will not panic because we know we are
// passing `AddrSubnet::new` a valid link local address as per RFC 4291.
// Specifically, the first 10 bits of the generated address is
// `0b1111111010`. `AddrSubnet::new` also validates the prefix length,
// and we know that 64 is a valid IPv6 subnet prefix length.
//
// TODO(ghanan): Investigate whether this unwrap is optimized out in
// practice as this code will be on the hot path.
AddrSubnet::new(
Ipv6Addr::from(ipv6_addr),
Ipv6Addr::BYTES * 8 - Ipv6::UNICAST_INTERFACE_IDENTIFIER_BITS,
)
.unwrap()
}
}
impl AsRef<[u8]> for Mac {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsMut<[u8]> for Mac {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl UnicastAddress for Mac {
/// Is this a unicast MAC address?
///
/// Returns true if the least significant bit of the first byte of the
/// address is 0.
#[inline]
fn is_unicast(&self) -> bool {
// https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
self.0[0] & 1 == 0
}
}
impl MulticastAddress for Mac {
/// Is this a multicast MAC address?
///
/// Returns true if the least significant bit of the first byte of the
/// address is 1.
#[inline]
fn is_multicast(&self) -> bool {
// https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
self.0[0] & 1 == 1
}
}
impl BroadcastAddress for Mac {
/// Is this the broadcast MAC address?
///
/// Returns true if this is the broadcast MAC address, FF:FF:FF:FF:FF:FF.
/// Note that the broadcast address is also considered a multicast address,
/// so `addr.is_broadcast()` implies `addr.is_multicast()`.
#[inline]
fn is_broadcast(&self) -> bool {
// https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
*self == Mac::BROADCAST
}
}
impl<'a, A: IpAddress> From<&'a MulticastAddr<A>> for Mac {
/// Converts a multicast IP address to a MAC address.
///
/// This method is equivalent to `MulticastAddr::<Mac>::from(addr).get()`.
#[inline]
fn from(addr: &'a MulticastAddr<A>) -> Mac {
MulticastAddr::<Mac>::from(addr).get()
}
}
impl<A: IpAddress> From<MulticastAddr<A>> for Mac {
/// Converts a multicast IP address to a MAC address.
///
/// This method is equivalent to `(&addr).into()`.
#[inline]
fn from(addr: MulticastAddr<A>) -> Mac {
(&addr).into()
}
}
impl<'a, A: IpAddress> From<&'a MulticastAddr<A>> for MulticastAddr<Mac> {
/// Converts a multicast IP address to a multicast MAC address.
///
/// When a multicast IP packet is sent over an Ethernet link, the frame's
/// destination MAC address is a multicast MAC address that is derived from
/// the destination IP address. This function performs that conversion.
///
/// See [RFC 7042 Section 2.1.1] and [Section 2.3.1] for details on how IPv4
/// and IPv6 addresses are mapped, respectively.
///
/// [RFC 7042 Section 2.1.1]: https://tools.ietf.org/html/rfc7042#section-2.1.1
/// [Section 2.3.1]: https://tools.ietf.org/html/rfc7042#section-2.3.1
#[inline]
fn from(addr: &'a MulticastAddr<A>) -> MulticastAddr<Mac> {
// We know the call to `unwrap` will not panic because we are generating
// a multicast MAC as defined in RFC 7042 section 2.1.1 and section
// 2.3.1 for IPv4 and IPv6 addresses, respectively.
MulticastAddr::new(Mac::new(match (*addr).get().into() {
IpAddr::V4(addr) => {
let ip_bytes = addr.ipv4_bytes();
let mut mac_bytes = [0; 6];
mac_bytes[0] = 0x01;
mac_bytes[1] = 0x00;
mac_bytes[2] = 0x5e;
mac_bytes[3] = ip_bytes[1] & 0x7f;
mac_bytes[4] = ip_bytes[2];
mac_bytes[5] = ip_bytes[3];
mac_bytes
}
IpAddr::V6(addr) => {
let ip_bytes = addr.ipv6_bytes();
let mut mac_bytes = [0; 6];
mac_bytes[0] = 0x33;
mac_bytes[1] = 0x33;
mac_bytes[2] = ip_bytes[12];
mac_bytes[3] = ip_bytes[13];
mac_bytes[4] = ip_bytes[14];
mac_bytes[5] = ip_bytes[15];
mac_bytes
}
}))
.unwrap()
}
}
impl<A: IpAddress> From<MulticastAddr<A>> for MulticastAddr<Mac> {
fn from(addr: MulticastAddr<A>) -> MulticastAddr<Mac> {
(&addr).into()
}
}
macro_rules! impl_from_witness {
($witness:ident) => {
impl From<$witness<Mac>> for Mac {
fn from(addr: $witness<Mac>) -> Mac {
addr.get()
}
}
};
}
impl_from_witness!(UnicastAddr);
impl_from_witness!(MulticastAddr);
impl_from_witness!(BroadcastAddr);
impl Display for Mac {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
}
impl Debug for Mac {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ip::Ipv4Addr;
#[test]
fn test_mac_to_eui() {
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_eui64(),
[0x02, 0x1a, 0xaa, 0xff, 0xfe, 0x12, 0x34, 0x56]
);
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_eui64_with_magic([0xfe, 0xfe]),
[0x02, 0x1a, 0xaa, 0xfe, 0xfe, 0x12, 0x34, 0x56]
);
}
#[test]
fn test_to_ipv6_link_local() {
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56]).to_ipv6_link_local(),
AddrSubnet::new(
Ipv6Addr::new([
0xfe80, // IPv6 link-local prefix
0, 0, 0, // Padding zeroes
0x021a, 0xaaff, 0xfe12, 0x3456, // EUI-64
]),
64
)
.unwrap()
);
assert_eq!(
Mac::new([0x00, 0x1a, 0xaa, 0x12, 0x34, 0x56])
.to_ipv6_link_local_with_magic([0xfe, 0xfe]),
AddrSubnet::new(
Ipv6Addr::new([
0xfe80, // IPv6 link-local prefix
0, 0, 0, // Padding zeroes
0x021a, 0xaafe, 0xfe12, 0x3456, // EUI-64
]),
64
)
.unwrap()
);
}
#[test]
fn test_map_multicast_ip_to_ethernet_mac() {
let ipv4 = Ipv4Addr::new([224, 1, 1, 1]);
let mac = Mac::from(&MulticastAddr::new(ipv4).unwrap());
assert_eq!(mac, Mac::new([0x01, 0x00, 0x5e, 0x1, 0x1, 0x1]));
let ipv4 = Ipv4Addr::new([224, 129, 1, 1]);
let mac = Mac::from(&MulticastAddr::new(ipv4).unwrap());
assert_eq!(mac, Mac::new([0x01, 0x00, 0x5e, 0x1, 0x1, 0x1]));
let ipv4 = Ipv4Addr::new([225, 1, 1, 1]);
let mac = Mac::from(&MulticastAddr::new(ipv4).unwrap());
assert_eq!(mac, Mac::new([0x01, 0x00, 0x5e, 0x1, 0x1, 0x1]));
let ipv6 = Ipv6Addr::new([0xff02, 0, 0, 0, 0, 0, 0, 3]);
let mac = Mac::from(&MulticastAddr::new(ipv6).unwrap());
assert_eq!(mac, Mac::new([0x33, 0x33, 0, 0, 0, 3]));
let ipv6 = Ipv6Addr::new([0xff02, 0, 0, 1, 0, 0, 0, 3]);
let mac = Mac::from(&MulticastAddr::new(ipv6).unwrap());
assert_eq!(mac, Mac::new([0x33, 0x33, 0, 0, 0, 3]));
let ipv6 = Ipv6Addr::new([0xff02, 0, 0, 0, 0, 0, 0x100, 3]);
let mac = Mac::from(&MulticastAddr::new(ipv6).unwrap());
assert_eq!(mac, Mac::new([0x33, 0x33, 1, 0, 0, 3]));
}
#[test]
fn mac_display_leading_zeroes() {
assert_eq!(Mac::new([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).to_string(), "00:00:00:00:00:00");
}
}