Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some utility functions #76

Merged
merged 2 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions luomu-common/src/ipaddr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,37 @@ pub const fn is_valid_forwardable_ip6(ip: Ipv6Addr) -> bool {
is_valid_destination_ip6(ip) && !ip.is_unicast_link_local()
}

/// Returns the computed Solicited-Node Multicast address for `ip`.
///
/// See RFC 4291 section 2.7.1
pub fn solicited_node_multicast_address_for(ip: Ipv6Addr) -> Ipv6Addr {
debug_assert!(!ip.is_multicast(), "ip is multicast address");

// RFC 4291 sect 2.7.1:
// A Solicited-Node multicast address is formed by taking the
// low-order 24 bits of an address (unicast or anycast) and
// appending those bits to the prefix FF02:0:0:0:0:1:FF00::/104

Ipv6Addr::from([
0xff02,
0x0,
0x0,
0x0,
0x0,
0x0001,
0xff00 | (0x00FF & ip.segments()[6]),
ip.segments()[7],
])
}

/// Returns true if `ip` is Solicited-Node multicast address.
///
/// See RFC 4291 section 2.7.1
pub const fn is_solicited_node_multicast_address(ip: Ipv6Addr) -> bool {
matches!(ip.segments(), [0xff02, 0x0, 0x0, 0x0, 0x0, 0x0001, _, _])
&& (ip.segments()[6] & 0xff00) == 0xff00
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -437,4 +468,16 @@ mod tests {
assert!(is_valid_forwardable_ip(ip), "invalid valid ip {ip}");
}
}

#[test]
fn test_solicited_node_multicast_addr() {
let ip = "4047::01:800:200e:8c6c"
.parse()
.expect("invalid IPv6 address");
let expected: Ipv6Addr = "ff02::1:ff0e:8c6c".parse().expect("invalid IPv6 address");
let solicited = solicited_node_multicast_address_for(ip);
assert!(is_solicited_node_multicast_address(solicited));
assert!(!is_solicited_node_multicast_address(ip));
assert_eq!(solicited, expected);
}
}
116 changes: 115 additions & 1 deletion luomu-common/src/macaddr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

use crate::{InvalidAddress, TagError};
Expand All @@ -10,6 +11,15 @@ const MAC_BITS: u64 = 0x0000FFFFFFFFFFFF;
// The VLAN tag portion of u64
const TAG_BITS: u64 = 0x0FFF000000000000;

/// Address bytes for Ethernet multicast MAC for All Nodes IPv6 multicast.
const ALL_NODES_MAC: [u8; 6] = [0x33, 0x33, 0x00, 0x00, 0x00, 0x01];
/// Base address to use when creating mac addresses for IPv6 multicast
/// addresses. See RFC2464 sect 7.
const IPV6_MULTICAST_BASE_MAC: [u8; 6] = [0x33, 0x33, 0x0, 0x0, 0x0, 0x0];
/// Base address to use when creating mac addresses for IPv4 multicast
/// addresses. Ses RFC1112 sect6.4
const IPV4_MULTICAST_BASE_MAC: [u8; 6] = [0x01, 0x00, 0x5E, 0x0, 0x0, 0x0];

/// A Mac address used for example with Ethernet.
///
/// Mac address is handled as big endian value. All `From<T>` implementations
Expand Down Expand Up @@ -105,6 +115,64 @@ impl MacAddr {
_ => None,
}
}

/// Creates corresponding [MacAddr] for multicast address `addr`.
///
/// If `addr` is a multicast address, the MAC address is created for it
/// according to relevant specification. For non-multicast addresses
/// broadcast Mac is returned.
pub fn multicast_mac_for_ip4(addr: Ipv4Addr) -> Self {
if !addr.is_multicast() {
return MacAddr::BROADCAST;
}

// RFC 1112 sect 6.4:
// An IP host group address is mapped to an Ethernet multicast address
// by placing the low-order 23-bits of the IP address into the low-order
// 23 bits of the Ethernet multicast address 01-00-5E-00-00-00 (hex).
let mut mac = IPV4_MULTICAST_BASE_MAC;
let addr_bytes = addr.octets();
mac[3] = 0xef & addr_bytes[1];
mac[4] = addr_bytes[2];
mac[5] = addr_bytes[3];
MacAddr::from(mac)
}

/// Creates corresponding [MacAddr] for multicast address `addr`.
///
/// If `addr` is a multicast address, the MAC address is created for it
/// according to relevant specification. For non-multicast addresses
/// all-nodes multicast address is returned.
pub fn multicast_mac_for_ip6(addr: Ipv6Addr) -> Self {
if !addr.is_multicast() {
return MacAddr::from(ALL_NODES_MAC);
}

// RFC 2464 sect 7:
// An IPv6 packet with a multicast destination address DST, consisting
// of the sixteen octets DST[1] through DST[16], is transmitted to the
// Ethernet multicast address whose first two octets are the value 3333
// hexadecimal and whose last four octets are the last four octets of
// DST.
let mut mac = IPV6_MULTICAST_BASE_MAC;
mac[2] = addr.octets()[12];
mac[3] = addr.octets()[13];
mac[4] = addr.octets()[14];
mac[5] = addr.octets()[15];
MacAddr::from(mac)
}

/// Creates corresponding [MacAddr] for multicast address `addr`.
///
/// If `addr` is a multicast address, the MAC address is created for it
/// according to relevant specification. For non-multicast addresses either
/// broadcast Mac (IPv4) or all-nodes multicast address (IPv6) is returned.
pub fn multicast_mac_for(addr: IpAddr) -> Self {
match addr {
IpAddr::V4(a) => Self::multicast_mac_for_ip4(a),
IpAddr::V6(a) => Self::multicast_mac_for_ip6(a),
}
}
}

impl Ord for MacAddr {
Expand Down Expand Up @@ -221,10 +289,15 @@ impl fmt::Debug for MacAddr {

#[cfg(test)]
mod tests {
use std::convert::{TryFrom, TryInto};
use std::{
convert::{TryFrom, TryInto},
net::{Ipv4Addr, Ipv6Addr},
};

use quickcheck::quickcheck;

use crate::macaddr::ALL_NODES_MAC;

use super::{MacAddr, TagError};

#[test]
Expand Down Expand Up @@ -379,6 +452,47 @@ mod tests {
assert_eq!(addr1.push_tag(0x0FFF + 1), Err(TagError::TooLargeTag));
}

#[test]
fn test_create_multicast_mac_for6() {
let mut addr: Ipv6Addr = "ff02::1".parse().unwrap();
let mut mac = MacAddr::multicast_mac_for_ip6(addr);
assert_eq!(mac, MacAddr::from(&[0x33, 0x033, 0x00, 0x00, 0x00, 0x01]));
assert_eq!(mac, MacAddr::multicast_mac_for(addr.into()));

addr = "ff02::aabb:ccdd:eeff".parse().unwrap();
mac = MacAddr::multicast_mac_for_ip6(addr);
assert_eq!(mac, MacAddr::from(&[0x33, 0x33, 0xcc, 0xdd, 0xee, 0xff]));
assert_eq!(mac, MacAddr::multicast_mac_for(addr.into()));
}

#[test]
fn test_create_multicast_mac_for4() {
let mut addr: Ipv4Addr = "224.0.0.251".parse().unwrap();
let mut mac = MacAddr::multicast_mac_for_ip4(addr);
assert_eq!(mac, MacAddr::from(&[0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb]));
assert_eq!(mac, MacAddr::multicast_mac_for(addr.into()));

addr = "224.255.127.127".parse().unwrap();
mac = MacAddr::multicast_mac_for_ip4(addr);
assert_eq!(mac, MacAddr::from(&[0x01, 0x00, 0x5e, 0xef, 0x7f, 0x7f]));
assert_eq!(mac, MacAddr::multicast_mac_for(addr.into()));
}

#[test]
fn test_create_multicast_mac_for4_unicast() {
let addr = "192.0.2.1".parse().unwrap();
let mac = MacAddr::multicast_mac_for_ip4(addr);
assert_eq!(mac, MacAddr::BROADCAST);
assert_eq!(mac, MacAddr::multicast_mac_for(addr.into()));
}
#[test]
fn test_create_multicast_mac_for6_unicast() {
let addr = "2001:db8::1".parse().unwrap();
let mac = MacAddr::multicast_mac_for_ip6(addr);
assert_eq!(mac, MacAddr::from(ALL_NODES_MAC));
assert_eq!(mac, MacAddr::multicast_mac_for(addr.into()));
}

quickcheck! {
fn prop_macaddr_to_from(xs: (u8, u8, u8, u8, u8, u8)) -> bool {
let b1: [u8; 6] = [xs.0, xs.1, xs.2, xs.3, xs.4, xs.5];
Expand Down
Loading