mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 11:08:25 +00:00
Show an Ethernet peer address as a MAC whatever its bytes decode as
A transport address printed its bytes as text whenever they were valid UTF-8, and as a colon-separated MAC only when they were not. A MAC is six arbitrary bytes, so some Ethernet peers showed up in `fipsctl show links` and in log lines as garbled text such as "2|ѬZd" instead of 32:7c:d1:ac:5a:64. An address built from a MAC now records that it is one, and always displays as a MAC. The Ethernet receive path, the beacon buffer and configured-peer resolution build their addresses that way. Equality and hashing still compare the bytes only, so lookups and comparisons behave as before, and every other transport's addresses display as they did.
This commit is contained in:
+1
-1
@@ -1328,7 +1328,7 @@ impl Node {
|
||||
NodeError::NoTransportForType(format!("invalid MAC in '{}': {}", addr_str, e))
|
||||
})?;
|
||||
|
||||
Ok((transport_id, TransportAddr::from_bytes(&mac)))
|
||||
Ok((transport_id, TransportAddr::from_mac(mac)))
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
|
||||
@@ -465,7 +465,7 @@ async fn ethernet_receive_loop(
|
||||
}
|
||||
};
|
||||
let bytes = data.len();
|
||||
let addr = TransportAddr::from_bytes(&src_mac);
|
||||
let addr = TransportAddr::from_mac(src_mac);
|
||||
let packet = ReceivedPacket::new(transport_id, addr, data);
|
||||
|
||||
trace!(
|
||||
|
||||
@@ -144,7 +144,7 @@ impl NeighborBuffer {
|
||||
|
||||
/// Build the buffered peer record for one beacon.
|
||||
fn peer(&self, src_mac: [u8; 6], pubkey: XOnlyPublicKey) -> DiscoveredPeer {
|
||||
let addr = TransportAddr::from_bytes(&src_mac);
|
||||
let addr = TransportAddr::from_mac(src_mac);
|
||||
DiscoveredPeer::with_hint(self.transport_id, addr, pubkey)
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,17 @@ mod tests {
|
||||
assert!(peers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_discovered_peer_whose_mac_bytes_are_valid_utf8_still_displays_as_a_mac() {
|
||||
// "2|\u{46c}Zd": six bytes that decode as UTF-8, seen on a veth MAC.
|
||||
let mac = [0x32, 0x7c, 0xd1, 0xac, 0x5a, 0x64];
|
||||
assert!(core::str::from_utf8(&mac).is_ok());
|
||||
let buffer = NeighborBuffer::new(TransportId::new(1));
|
||||
assert!(buffer.add_peer(mac, test_pubkey()));
|
||||
let peers = buffer.take();
|
||||
assert_eq!(peers[0].addr.to_string(), "32:7c:d1:ac:5a:64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neighbor_buffer_dedup() {
|
||||
let buffer = NeighborBuffer::new(TransportId::new(1));
|
||||
|
||||
+23
-2
@@ -1182,12 +1182,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_transport_addr_mac_display() {
|
||||
// Raw 6-byte MACs (as Ethernet stores via from_bytes) display in
|
||||
// standard colon-separated notation, not bare hex.
|
||||
// Raw 6-byte non-UTF-8 values from from_bytes display in standard
|
||||
// colon-separated notation, not bare hex.
|
||||
let mac = TransportAddr::from_bytes(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
|
||||
assert_eq!(format!("{}", mac), "aa:bb:cc:dd:ee:ff");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mac_address_whose_bytes_are_valid_utf8_displays_as_a_mac() {
|
||||
let bytes = [0x32, 0x7c, 0xd1, 0xac, 0x5a, 0x64];
|
||||
assert!(core::str::from_utf8(&bytes).is_ok());
|
||||
let mac = TransportAddr::from_mac(bytes);
|
||||
assert_eq!(mac.to_string(), "32:7c:d1:ac:5a:64");
|
||||
assert_eq!(format!("{:?}", mac), "TransportAddr(32:7c:d1:ac:5a:64)");
|
||||
assert_eq!(mac.as_bytes(), &bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mac_address_equals_and_hashes_as_its_bytes() {
|
||||
use std::hash::BuildHasher;
|
||||
let bytes = [0x32, 0x7c, 0xd1, 0xac, 0x5a, 0x64];
|
||||
let mac = TransportAddr::from_mac(bytes);
|
||||
let raw = TransportAddr::from_bytes(&bytes);
|
||||
assert_eq!(mac, raw);
|
||||
let hasher = std::collections::hash_map::RandomState::new();
|
||||
assert_eq!(hasher.hash_one(&mac), hasher.hash_one(&raw));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transport_addr_non_mac_binary_is_bare_hex() {
|
||||
// Non-6-byte non-UTF-8 payloads stay bare hex (no separators).
|
||||
|
||||
+67
-26
@@ -90,75 +90,116 @@ impl fmt::Display for LinkDirection {
|
||||
///
|
||||
/// Each transport type interprets this differently:
|
||||
/// - UDP/TCP: "host:port" (IP address or DNS hostname)
|
||||
/// - Ethernet: MAC address (6 bytes)
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct TransportAddr(Vec<u8>);
|
||||
/// - Ethernet: MAC address (6 bytes), built with [`TransportAddr::from_mac`]
|
||||
/// so it always displays as a MAC, whatever its bytes decode as
|
||||
#[derive(Clone)]
|
||||
pub struct TransportAddr {
|
||||
bytes: Vec<u8>,
|
||||
/// Set only by [`TransportAddr::from_mac`]: the bytes are a MAC address
|
||||
/// and always display as one. Not part of equality or hashing.
|
||||
mac: bool,
|
||||
}
|
||||
|
||||
impl PartialEq for TransportAddr {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.bytes == other.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TransportAddr {}
|
||||
|
||||
impl core::hash::Hash for TransportAddr {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.bytes.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportAddr {
|
||||
/// Create a transport address from raw bytes.
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
Self(bytes)
|
||||
Self { bytes, mac: false }
|
||||
}
|
||||
|
||||
/// Create a transport address from a byte slice.
|
||||
pub fn from_bytes(bytes: &[u8]) -> Self {
|
||||
Self(bytes.to_vec())
|
||||
Self::new(bytes.to_vec())
|
||||
}
|
||||
|
||||
/// Create an Ethernet transport address from a MAC address.
|
||||
pub fn from_mac(mac: [u8; 6]) -> Self {
|
||||
Self {
|
||||
bytes: mac.to_vec(),
|
||||
mac: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a transport address from a string.
|
||||
pub fn from_string(s: &str) -> Self {
|
||||
Self(s.as_bytes().to_vec())
|
||||
Self::new(s.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Get the raw bytes.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// Try to interpret as a UTF-8 string.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
core::str::from_utf8(&self.0).ok()
|
||||
core::str::from_utf8(&self.bytes).ok()
|
||||
}
|
||||
|
||||
/// Get the length in bytes.
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
self.bytes.len()
|
||||
}
|
||||
|
||||
/// Check if empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
self.bytes.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportAddr {
|
||||
/// Write the bytes as a colon-separated MAC address.
|
||||
fn write_mac(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, byte) in self.bytes.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ":")?;
|
||||
}
|
||||
write!(f, "{:02x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TransportAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.mac {
|
||||
write!(f, "TransportAddr(")?;
|
||||
self.write_mac(f)?;
|
||||
return write!(f, ")");
|
||||
}
|
||||
match self.as_str() {
|
||||
Some(s) => write!(f, "TransportAddr(\"{}\")", s),
|
||||
None => write!(f, "TransportAddr({:?})", self.0),
|
||||
None => write!(f, "TransportAddr({:?})", self.bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TransportAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Best-effort display as string if valid UTF-8. Otherwise render a
|
||||
// 6-byte payload as a colon-separated MAC (standard Unix notation,
|
||||
// matching BLE addrs, `ip link`/`ip neigh`, and packet logs), and
|
||||
// any other non-UTF-8 byte string as bare hex.
|
||||
// An Ethernet address is a MAC whatever its bytes decode as. Any
|
||||
// other address displays as a string if it is valid UTF-8; otherwise
|
||||
// a 6-byte payload is rendered as a colon-separated MAC and any other
|
||||
// byte string as bare hex.
|
||||
if self.mac {
|
||||
return self.write_mac(f);
|
||||
}
|
||||
match self.as_str() {
|
||||
Some(s) => write!(f, "{}", s),
|
||||
None if self.0.len() == 6 => {
|
||||
for (i, byte) in self.0.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ":")?;
|
||||
}
|
||||
write!(f, "{:02x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None if self.bytes.len() == 6 => self.write_mac(f),
|
||||
None => {
|
||||
for byte in &self.0 {
|
||||
for byte in &self.bytes {
|
||||
write!(f, "{:02x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -175,7 +216,7 @@ impl From<&str> for TransportAddr {
|
||||
|
||||
impl From<String> for TransportAddr {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s.into_bytes())
|
||||
Self::new(s.into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user