Skip to content

Commit 29f5db2

Browse files
committed
IPv6: Add IPv6 autogenerated address functions
1 parent c069d40 commit 29f5db2

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

src/wire/ethernet.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ impl Address {
6565
pub const fn is_local(&self) -> bool {
6666
self.0[0] & 0x02 != 0
6767
}
68+
69+
/// Convert the address to an Extended Unique Identifier (EUI-64)
70+
pub fn as_eui_64(&self) -> Option<[u8; 8]> {
71+
let mut bytes = [0; 8];
72+
bytes[0..3].copy_from_slice(&self.0[0..3]);
73+
bytes[3] = 0xFF;
74+
bytes[4] = 0xFE;
75+
bytes[5..8].copy_from_slice(&self.0[3..6]);
76+
bytes[0] ^= 1 << 1;
77+
Some(bytes)
78+
}
6879
}
6980

7081
impl fmt::Display for Address {

src/wire/ipv6.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use core::fmt;
55

66
use super::{Error, Result};
77
use crate::wire::ip::pretty_print_ip_payload;
8+
use crate::wire::HardwareAddress;
89

910
pub use super::IpProtocol as Protocol;
1011

@@ -83,6 +84,12 @@ pub(crate) trait AddressExt {
8384
/// The function panics if `data` is not sixteen octets long.
8485
fn from_bytes(data: &[u8]) -> Address;
8586

87+
/// Create an IPv6 address based on the provided prefix and hardware identifier.
88+
fn from_link_prefix(
89+
link_prefix: &Cidr,
90+
interface_identifier: HardwareAddress,
91+
) -> Option<Address>;
92+
8693
/// Query whether the IPv6 address is an [unicast address].
8794
///
8895
/// [unicast address]: https://tools.ietf.org/html/rfc4291#section-2.5
@@ -142,6 +149,23 @@ impl AddressExt for Address {
142149
Address::from(bytes)
143150
}
144151

152+
fn from_link_prefix(
153+
link_prefix: &Cidr,
154+
interface_identifier: HardwareAddress,
155+
) -> Option<Address> {
156+
if let Some(eui64) = interface_identifier.as_eui_64() {
157+
if link_prefix.prefix_len() != 64 {
158+
return None;
159+
}
160+
let mut bytes = [0; 16];
161+
bytes[0..8].copy_from_slice(&link_prefix.address().octets()[0..8]);
162+
bytes[8..16].copy_from_slice(&eui64);
163+
Some(Address::from_bytes(&bytes))
164+
} else {
165+
None
166+
}
167+
}
168+
145169
fn x_is_unicast(&self) -> bool {
146170
!(self.is_multicast() || self.is_unspecified())
147171
}
@@ -259,6 +283,15 @@ impl Cidr {
259283
}
260284
}
261285

286+
/// Create an IPv6 CIDR based on the provided prefix and hardware identifier.
287+
pub fn from_link_prefix(
288+
link_prefix: &Cidr,
289+
interface_identifier: HardwareAddress,
290+
) -> Option<Self> {
291+
Address::from_link_prefix(link_prefix, interface_identifier)
292+
.map(|address| Self::new(address, link_prefix.prefix_len()))
293+
}
294+
262295
/// Return the address of this IPv6 CIDR block.
263296
pub const fn address(&self) -> Address {
264297
self.address
@@ -930,6 +963,45 @@ pub(crate) mod test {
930963
assert!(cidr_without_prefix.contains_addr(&Address::LOCALHOST));
931964
}
932965

966+
#[test]
967+
fn test_from_eui_64() {
968+
#[cfg(feature = "medium-ethernet")]
969+
use crate::wire::EthernetAddress;
970+
#[cfg(feature = "medium-ieee802154")]
971+
use crate::wire::Ieee802154Address;
972+
let tests: std::vec::Vec<(HardwareAddress, Address)> = vec![
973+
#[cfg(feature = "medium-ethernet")]
974+
(
975+
HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
976+
0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
977+
])),
978+
Address::new(0x2001, 0xdb8, 0x3, 0x0, 0xa8bb, 0xccff, 0xfedd, 0xeeff),
979+
),
980+
#[cfg(feature = "medium-ieee802154")]
981+
(
982+
HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(&[
983+
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
984+
])),
985+
Address::new(0x2001, 0xdb8, 0x3, 0x0, 0x0211, 0x2233, 0x4455, 0x6677),
986+
),
987+
];
988+
989+
let prefix = Cidr::new(GLOBAL_UNICAST_ADDR.mask(64).into(), 64);
990+
let wrong_prefix = Cidr::new(GLOBAL_UNICAST_ADDR.mask(72).into(), 72);
991+
992+
for (hardware, result) in tests {
993+
let generated = Address::from_link_prefix(&prefix, hardware).unwrap();
994+
assert!(prefix.contains_addr(&generated));
995+
assert_eq!(generated, result);
996+
assert!(Address::from_link_prefix(&wrong_prefix, hardware).is_none());
997+
998+
let generated = Cidr::from_link_prefix(&prefix, hardware).unwrap();
999+
assert!(prefix.contains_subnet(&generated));
1000+
assert_eq!(generated.address(), result);
1001+
assert!(Cidr::from_link_prefix(&wrong_prefix, hardware).is_none());
1002+
}
1003+
}
1004+
9331005
#[test]
9341006
#[should_panic(expected = "length")]
9351007
fn test_from_bytes_too_long() {

src/wire/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,17 @@ impl HardwareAddress {
426426
HardwareAddress::Ieee802154(_) => Medium::Ieee802154,
427427
}
428428
}
429+
430+
pub fn as_eui_64(&self) -> Option<[u8; 8]> {
431+
match self {
432+
#[cfg(feature = "medium-ip")]
433+
HardwareAddress::Ip => None,
434+
#[cfg(feature = "medium-ethernet")]
435+
HardwareAddress::Ethernet(ethernet) => ethernet.as_eui_64(),
436+
#[cfg(feature = "medium-ieee802154")]
437+
HardwareAddress::Ieee802154(ieee802154) => ieee802154.as_eui_64(),
438+
}
439+
}
429440
}
430441

431442
#[cfg(any(

0 commit comments

Comments
 (0)