diff --git a/Cargo.toml b/Cargo.toml index 049eec0..2e4079a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "libs/xen/xenevtchn", "libs/xen/xencall", "libs/xen/xenclient", + "libs/advmac", "libs/loopdev", "hypha", ] @@ -40,6 +41,8 @@ path-clean = "1.0.1" ureq = "2.9.1" url = "2.5.0" cli-tables = "0.2.1" +rand = "0.8.5" +arrayvec = "0.7.4" [workspace.dependencies.uuid] version = "1.6.1" diff --git a/hypha/Cargo.toml b/hypha/Cargo.toml index ad08d24..70b51d4 100644 --- a/hypha/Cargo.toml +++ b/hypha/Cargo.toml @@ -31,6 +31,9 @@ uuid = { workspace = true } workspace = true features = ["process"] +[dependencies.advmac] +path = "../libs/advmac" + [dependencies.loopdev] path = "../libs/loopdev" diff --git a/hypha/src/ctl/mod.rs b/hypha/src/ctl/mod.rs index bcbf89d..c819b0a 100644 --- a/hypha/src/ctl/mod.rs +++ b/hypha/src/ctl/mod.rs @@ -6,6 +6,7 @@ use crate::image::cache::ImageCache; use crate::image::name::ImageName; use crate::image::{ImageCompiler, ImageInfo}; use crate::shared::LaunchInfo; +use advmac::MacAddr6; use anyhow::{anyhow, Result}; use loopdev::LoopControl; use std::io::{Read, Write}; @@ -15,7 +16,7 @@ use std::str::FromStr; use std::{fs, io, thread}; use termion::raw::IntoRawMode; use uuid::Uuid; -use xenclient::{DomainConfig, DomainDisk, XenClient}; +use xenclient::{DomainConfig, DomainDisk, DomainNetworkInterface, XenClient}; use xenstore::client::{XsdClient, XsdInterface}; pub struct Controller { @@ -101,6 +102,7 @@ impl Controller { let cmdline_options = [if debug { "debug" } else { "quiet" }, "elevator=noop"]; let cmdline = cmdline_options.join(" "); + let mac = MacAddr6::random().to_string().replace('-', ":"); let config = DomainConfig { backend_domid: 0, name: &name, @@ -121,7 +123,12 @@ impl Controller { writable: false, }, ], - vifs: vec![], + vifs: vec![DomainNetworkInterface { + mac: &mac, + mtu: 1500, + bridge: "xenbr0", + script: "/etc/xen/scripts/vif-bridge", + }], filesystems: vec![], extra_keys: vec![ ("hypha/uuid".to_string(), uuid.to_string()), diff --git a/initrd/build.sh b/initrd/build.sh index 7e8b553..cb01d6e 100755 --- a/initrd/build.sh +++ b/initrd/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -TARGET="x86_64-unknown-linux-musl" +TARGET="x86_64-unknown-linux-gnu" export RUSTFLAGS="-Ctarget-feature=+crt-static" cd "$(dirname "${0}")/.." diff --git a/libs/advmac/Cargo.toml b/libs/advmac/Cargo.toml new file mode 100644 index 0000000..b0bad97 --- /dev/null +++ b/libs/advmac/Cargo.toml @@ -0,0 +1,16 @@ +# This package is from https://github.com/GamePad64/advmac +# Mycelium maintains an in-tree version because of dependencies +# being out of date. +[package] +name = "advmac" +version.workspace = true +license = "MIT" +edition = "2021" + +[lib] +name = "advmac" + +[dependencies] +serde = { workspace = true } +rand = { workspace = true } +arrayvec = { workspace = true, features = ["serde"] } diff --git a/libs/advmac/src/lib.rs b/libs/advmac/src/lib.rs new file mode 100644 index 0000000..ff8b5d8 --- /dev/null +++ b/libs/advmac/src/lib.rs @@ -0,0 +1,473 @@ +mod parser; + +use arrayvec::ArrayString; +use core::fmt::{self, Debug, Display, Formatter}; +use core::str::FromStr; +use rand::Rng; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum ParseError { + InvalidMac, + InvalidLength { length: usize }, +} + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidMac => write!(f, "invalid MAC address"), + Self::InvalidLength { length } => write!(f, "invalid string length: {}", length), + } + } +} + +impl std::error::Error for ParseError {} + +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum IpError { + NotLinkLocal, + NotMulticast, +} + +impl Display for IpError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::NotLinkLocal => write!(f, "not link-local address"), + Self::NotMulticast => write!(f, "not multicast address"), + } + } +} + +impl std::error::Error for IpError {} + +/// Maximum formatted size. +/// +/// It is useful for creating a stack-allocated buffer `[u8; MAC_MAX_SIZE]` +/// and formatting address into it using [MacAddr6::format_write] or [MacAddr8::format_write]. +pub const MAC_MAX_SIZE: usize = 23; +/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::Canonical]. +pub const MAC_CANONICAL_SIZE6: usize = 17; +/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::Canonical]. +pub const MAC_CANONICAL_SIZE8: usize = 23; +/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::ColonNotation]. +pub const MAC_COLON_NOTATION_SIZE6: usize = 17; +/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::ColonNotation]. +pub const MAC_COLON_NOTATION_SIZE8: usize = 23; +/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::DotNotation]. +pub const MAC_DOT_NOTATION_SIZE6: usize = 14; +/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::DotNotation]. +pub const MAC_DOT_NOTATION_SIZE8: usize = 19; +/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::Hexadecimal]. +pub const MAC_HEXADECIMAL_SIZE6: usize = 12; +/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::Hexadecimal]. +pub const MAC_HEXADECIMAL_SIZE8: usize = 16; +/// Size of formatted MAC using [MacAddr6::format_string] and [MacAddrFormat::Hexadecimal0x]. +pub const MAC_HEXADECIMAL0X_SIZE6: usize = 14; +/// Size of formatted MAC using [MacAddr8::format_string] and [MacAddrFormat::Hexadecimal0x]. +pub const MAC_HEXADECIMAL0X_SIZE8: usize = 18; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum MacAddrFormat { + /// `AA-BB-CC-DD-EE-FF` (17 bytes) or `AA-BB-CC-DD-EE-FF-GG-HH` (23 bytes) + Canonical, + /// `AA:BB:CC:DD:EE:FF` (17 bytes) or `AA:BB:CC:DD:EE:FF:GG:HH` (23 bytes) + ColonNotation, + /// `AABB.CCDD.EEFF` (14 bytes) or `AABB.CCDD.EEFF.GGHH` (19 bytes) + DotNotation, + /// `AABBCCDDEEFF` (12 bytes) or `AABBCCDDEEFFGGHH` (16 bytes) + Hexadecimal, + /// `0xAABBCCDDEEFF` (14 bytes) or `0xAABBCCDDEEFFGGHH` (18 bytes) + Hexadecimal0x, +} + +macro_rules! mac_impl { + ($nm:ident, $sz:literal, $hex_sz:literal) => { + impl $nm { + pub const fn new(eui: [u8; $sz]) -> Self { + Self(eui) + } + + pub fn random() -> Self { + let mut result = Self::default(); + rand::rngs::OsRng.fill(result.as_mut_slice()); + result + } + + pub const fn broadcast() -> Self { + Self([0xFF; $sz]) + } + + pub const fn nil() -> Self { + Self([0; $sz]) + } + + /// Sets *locally administered* flag + pub fn set_local(&mut self, v: bool) { + if v { + self.0[0] |= 0b0000_0010; + } else { + self.0[0] &= !0b0000_0010; + } + } + + /// Returns the state of *locally administered* flag + pub const fn is_local(&self) -> bool { + (self.0[0] & 0b0000_0010) != 0 + } + + /// Sets *multicast* flag + pub fn set_multicast(&mut self, v: bool) { + if v { + self.0[0] |= 0b0000_0001; + } else { + self.0[0] &= !0b0000_0001; + } + } + + /// Returns the state of *multicast* flag + pub const fn is_multicast(&self) -> bool { + (self.0[0] & 0b0000_0001) != 0 + } + + /// Returns [organizationally unique identifier (OUI)](https://en.wikipedia.org/wiki/Organizationally_unique_identifier) of this MAC address + pub const fn oui(&self) -> [u8; 3] { + [self.0[0], self.0[1], self.0[2]] + } + + /// Sets [organizationally unique identifier (OUI)](https://en.wikipedia.org/wiki/Organizationally_unique_identifier) for this MAC address + pub fn set_oui(&mut self, oui: [u8; 3]) { + self.0[..3].copy_from_slice(&oui); + } + + /// Returns internal array representation for this MAC address, consuming it + pub const fn to_array(self) -> [u8; $sz] { + self.0 + } + + /// Returns internal array representation for this MAC address as [u8] slice + pub const fn as_slice(&self) -> &[u8] { + &self.0 + } + + /// Returns internal array representation for this MAC address as mutable [u8] slice + pub fn as_mut_slice(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Returns internal array representation for this MAC address as [core::ffi::c_char] slice. + /// This can be useful in parsing `ifr_hwaddr`, for example. + pub const fn as_c_slice(&self) -> &[core::ffi::c_char] { + unsafe { &*(self.as_slice() as *const _ as *const [core::ffi::c_char]) } + } + + /// Parse MAC address from string and return it as `MacAddr`. + /// This function can be used in const context, so MAC address can be parsed in compile-time. + pub const fn parse_str(s: &str) -> Result { + match parser::MacParser::<$sz, $hex_sz>::parse(s) { + Ok(v) => Ok(Self(v)), + Err(e) => Err(e), + } + } + + /// Write MAC address to `impl core::fmt::Write`, which can be used in `no_std` environments. + /// + /// It can be used like this with [arrayvec::ArrayString] without allocations: + /// ``` + /// use arrayvec::ArrayString; + /// use advmac::{MacAddr6, MacAddrFormat, MAC_CANONICAL_SIZE6}; + /// + /// let mac = MacAddr6::parse_str("AA:BB:CC:DD:EE:FF").unwrap(); + /// + /// let mut buf = ArrayString::::new(); + /// mac.format_write(&mut buf, MacAddrFormat::Canonical).unwrap(); + /// # assert_eq!(buf.as_str(), "AA-BB-CC-DD-EE-FF") + /// ``` + pub fn format_write( + &self, + f: &mut T, + format: MacAddrFormat, + ) -> fmt::Result { + match format { + MacAddrFormat::Canonical => self.write_internal(f, "", "-", "-"), + MacAddrFormat::ColonNotation => self.write_internal(f, "", ":", ":"), + MacAddrFormat::DotNotation => self.write_internal(f, "", "", "."), + MacAddrFormat::Hexadecimal => self.write_internal(f, "", "", ""), + MacAddrFormat::Hexadecimal0x => self.write_internal(f, "0x", "", ""), + } + } + + /// Write MAC address to [String]. This function uses [Self::format_write] internally and + /// produces the same result, but in string form, which can be convenient in non-constrainted + /// environments. + + pub fn format_string(&self, format: MacAddrFormat) -> String { + let mut buf = String::new(); + self.format_write(&mut buf, format).unwrap(); + buf + } + } + + impl Display for $nm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.format_write(f, MacAddrFormat::Canonical) + } + } + + impl Debug for $nm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.format_write(f, MacAddrFormat::Canonical) + } + } + + impl From<[u8; $sz]> for $nm { + fn from(arr: [u8; $sz]) -> Self { + Self(arr) + } + } + + impl TryFrom<&[u8]> for $nm { + type Error = ParseError; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into().map_err(|_| ParseError::InvalidMac)?)) + } + } + + impl TryFrom<&[core::ffi::c_char]> for $nm { + type Error = ParseError; + + fn try_from(value: &[core::ffi::c_char]) -> Result { + Self::try_from(unsafe { &*(value as *const _ as *const [u8]) }) + } + } + + impl TryFrom<&str> for $nm { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + Self::parse_str(value) + } + } + + impl TryFrom for $nm { + type Error = ParseError; + + fn try_from(value: String) -> Result { + Self::parse_str(&value) + } + } + + impl FromStr for $nm { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + Self::parse_str(s) + } + } + + impl Serialize for $nm { + fn serialize(&self, s: S) -> Result { + let mut buf = ArrayString::::new(); + self.format_write(&mut buf, MacAddrFormat::Canonical) + .unwrap(); + s.serialize_str(buf.as_ref()) + } + } + + impl<'de> Deserialize<'de> for $nm { + fn deserialize>(d: D) -> Result { + Self::from_str(ArrayString::::deserialize(d)?.as_ref()) + .map_err(serde::de::Error::custom) + } + } + }; +} + +/// MAC address, represented as EUI-48 +#[repr(transparent)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct MacAddr6([u8; 6]); +/// MAC address, represented as EUI-64 +#[repr(transparent)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct MacAddr8([u8; 8]); + +mac_impl!(MacAddr6, 6, 12); +mac_impl!(MacAddr8, 8, 16); + +impl MacAddr6 { + pub const fn to_modified_eui64(self) -> MacAddr8 { + let b = self.to_array(); + MacAddr8([b[0] ^ 0b00000010, b[1], b[2], 0xFF, 0xFE, b[3], b[4], b[5]]) + } + + pub const fn try_from_modified_eui64(eui64: MacAddr8) -> Result { + let b = eui64.to_array(); + if (b[3] == 0xFF) | (b[4] == 0xFE) { + Ok(Self([b[0] ^ 0b00000010, b[1], b[2], b[5], b[6], b[7]])) + } else { + Err(IpError::NotLinkLocal) + } + } + + pub const fn to_link_local_ipv6(self) -> Ipv6Addr { + let mac64 = self.to_modified_eui64().to_array(); + + Ipv6Addr::new( + 0xFE80, + 0x0000, + 0x0000, + 0x0000, + ((mac64[0] as u16) << 8) + mac64[1] as u16, + ((mac64[2] as u16) << 8) + mac64[3] as u16, + ((mac64[4] as u16) << 8) + mac64[5] as u16, + ((mac64[6] as u16) << 8) + mac64[7] as u16, + ) + } + + pub const fn try_from_link_local_ipv6(ip: Ipv6Addr) -> Result { + let octets = ip.octets(); + if (octets[0] != 0xFE) + | (octets[1] != 0x80) + | (octets[2] != 0x00) + | (octets[3] != 0x00) + | (octets[4] != 0x00) + | (octets[5] != 0x00) + | (octets[6] != 0x00) + | (octets[7] != 0x00) + | (octets[11] != 0xFF) + | (octets[12] != 0xFE) + { + return Err(IpError::NotLinkLocal); + } + + Ok(Self([ + octets[8] ^ 0b00000010, + octets[9], + octets[10], + octets[13], + octets[14], + octets[15], + ])) + } + + pub const fn try_from_multicast_ipv4(ip: Ipv4Addr) -> Result { + if !ip.is_multicast() { + return Err(IpError::NotMulticast); + } + let b = ip.octets(); + Ok(Self::new([0x01, 0x00, 0x5E, b[1] & 0x7F, b[2], b[3]])) + } + + pub const fn try_from_multicast_ipv6(ip: Ipv6Addr) -> Result { + if !ip.is_multicast() { + return Err(IpError::NotMulticast); + } + let b = ip.octets(); + Ok(Self::new([0x33, 0x33, b[12], b[13], b[14], b[15]])) + } + + pub const fn try_from_multicast_ip(ip: IpAddr) -> Result { + match ip { + IpAddr::V4(ip) => Self::try_from_multicast_ipv4(ip), + IpAddr::V6(ip) => Self::try_from_multicast_ipv6(ip), + } + } +} + +impl MacAddr6 { + // String representations + fn write_internal( + &self, + f: &mut T, + pre: &str, + sep: &str, + sep2: &str, + ) -> fmt::Result { + write!( + f, + "{pre}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] + ) + } +} + +impl MacAddr8 { + // String representations + fn write_internal( + &self, + f: &mut T, + pre: &str, + sep: &str, + sep2: &str, + ) -> fmt::Result { + write!( + f, + "{pre}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}{sep2}{:02X}{sep}{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7] + ) + } +} + +/// Convenience macro for creating [MacAddr6] in compile-time. +/// +/// Example: +/// ``` +/// use advmac::{mac6, MacAddr6}; +/// const MAC6: MacAddr6 = mac6!("11:22:33:44:55:66"); +/// # assert_eq!(MAC6.to_array(), [0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); +/// ``` +#[macro_export] +macro_rules! mac6 { + ($s:expr) => { + match $crate::MacAddr6::parse_str($s) { + Ok(mac) => mac, + Err(_) => panic!("Invalid MAC address"), + } + }; +} + +/// Convenience macro for creating [MacAddr8] in compile-time. +/// +/// Example: +/// ``` +/// use advmac::{mac8, MacAddr8}; +/// const MAC8: MacAddr8 = mac8!("11:22:33:44:55:66:77:88"); +/// # assert_eq!(MAC8.to_array(), [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); +/// ``` +#[macro_export] +macro_rules! mac8 { + ($s:expr) => { + match $crate::MacAddr8::parse_str($s) { + Ok(mac) => mac, + Err(_) => panic!("Invalid MAC address"), + } + }; +} + +#[cfg(test)] +mod test { + #[test] + fn test_flags_roundtrip() { + let mut addr = mac6!("50:74:f2:b1:a8:7f"); + assert!(!addr.is_local()); + assert!(!addr.is_multicast()); + + addr.set_multicast(true); + assert!(!addr.is_local()); + assert!(addr.is_multicast()); + + addr.set_local(true); + assert!(addr.is_local()); + assert!(addr.is_multicast()); + + addr.set_multicast(false); + assert!(addr.is_local()); + assert!(!addr.is_multicast()); + + addr.set_local(false); + assert!(!addr.is_local()); + assert!(!addr.is_multicast()); + } +} diff --git a/libs/advmac/src/parser.rs b/libs/advmac/src/parser.rs new file mode 100644 index 0000000..63b2d9c --- /dev/null +++ b/libs/advmac/src/parser.rs @@ -0,0 +1,113 @@ +use crate::ParseError; + +// This whole thing is written this way to be const. +// If you want normal hex handling, just use hex crate +pub struct MacParser; + +impl MacParser { + const CANONICAL_COLON_SIZE: usize = 3 * N - 1; + const DOT_NOTATION_SIZE: usize = (2 * N) + (N / 2 - 1); + const HEXADECIMAL_SIZE: usize = 2 * N; + const HEXADECIMAL0X_SIZE: usize = 2 * N + 2; + + #[inline] + const fn nibble(v: u8) -> Result { + match v { + b'A'..=b'F' => Ok(10 + (v - b'A')), + b'a'..=b'f' => Ok(10 + (v - b'a')), + b'0'..=b'9' => Ok(v - b'0'), + _ => Err(ParseError::InvalidMac), + } + } + + #[inline] + const fn byte(b1: u8, b2: u8) -> Result { + // ? is not available in const + match (Self::nibble(b1), Self::nibble(b2)) { + (Ok(v1), Ok(v2)) => Ok((v1 << 4) + v2), + (Err(e), _) | (_, Err(e)) => Err(e), + } + } + + const fn from_hex(s: &[u8]) -> Result<[u8; N], ParseError> { + if s.len() != Self::HEXADECIMAL_SIZE { + return Err(ParseError::InvalidLength { length: s.len() }); + } + + let mut result = [0u8; N]; + + // for-loops and iterators are unavailable in const + let mut i = 0; + while i < N { + result[i] = match Self::byte(s[2 * i], s[2 * i + 1]) { + Ok(v) => v, + Err(e) => return Err(e), + }; + i += 1; + } + + Ok(result) + } + + const fn check_separator(s: &[u8], sep: u8, group_len: usize) -> bool { + let mut i = group_len; + while i < s.len() { + if s[i] != sep { + return false; + } + i += group_len + 1; + } + true + } + + const fn parse_separated(s: &[u8], sep: u8, group_len: usize) -> Result<[u8; N], ParseError> { + let expected_len = (2 * N) + ((2 * N) / group_len) - 1; + if s.len() != expected_len { + return Err(ParseError::InvalidLength { length: s.len() }); + } + + if !Self::check_separator(s, sep, group_len) { + return Err(ParseError::InvalidMac); + } + + let mut hex_buf = [0u8; N2]; + + let (mut in_i, mut out_i) = (0, 0); + while in_i < s.len() { + if (in_i + 1) % (group_len + 1) != 0 { + hex_buf[out_i] = s[in_i]; + out_i += 1; + } + in_i += 1; + } + + Self::from_hex(&hex_buf) + } + + pub const fn parse(s: &str) -> Result<[u8; N], ParseError> { + let s = s.as_bytes(); + + if s.len() == Self::HEXADECIMAL_SIZE { + Self::from_hex(s) + } else if (s.len() == Self::HEXADECIMAL0X_SIZE) && (s[0] == b'0') && (s[1] == b'x') { + // unsafe is the only way I know to make it const + Self::from_hex(unsafe { + core::slice::from_raw_parts(s.as_ptr().offset(2), s.len() - 2) + }) + } else if s.len() == Self::CANONICAL_COLON_SIZE { + let sep = s[2]; + match sep { + b'-' | b':' => Self::parse_separated(s, sep, 2), + _ => Err(ParseError::InvalidMac), + } + } else if s.len() == Self::DOT_NOTATION_SIZE { + let sep = s[4]; + match sep { + b'.' => Self::parse_separated(s, sep, 4), + _ => Err(ParseError::InvalidMac), + } + } else { + Err(ParseError::InvalidLength { length: s.len() }) + } + } +}