net: ipv6 link local compliance with rfc 4291
PATCH 1/1 net: ipv6 link local compliance with rfc 4291
From: Christophe Fontaine <[email protected]>
As specified in RFC 4291 section 2.5.1, link local addresses must be generated based on a modified EUI-64 interface identifier:
Quoted 3 lines
Modified EUI-64 format interface identifiers are formed by inverting the "u" bit (universal/local bit in IEEE EUI-64 terminology) when forming the interface identifier from IEEE EUI-64 identifiers.
This translates to 'mac->addr_bytes[0] ^= 0x02'.
--- app/test/test_net_ip6.c | 2 +- lib/net/rte_ip6.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
app/test/test_net_ip6.c +1 -1
@@ -160,7 +160,7 @@ test_ipv6_llocal_from_ethernet(void) {const struct rte_ether_addr local_mac = {{0x04 ,0x7b ,0xcb ,0x5c ,0x08 ,0x44 }};const struct rte_ipv6_addr local_ip = -RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x047b ,0xcbff ,0xfe5c ,0x0844 ); +RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x067b ,0xcbff ,0xfe5c ,0x0844 );struct rte_ipv6_addr ip;rte_ipv6_llocal_from_ethernet (&ip, &local_mac);
lib/net/rte_ip6.h +2 -2
@@ -393,7 +393,7 @@ rte_ipv6_mc_scope(const struct rte_ipv6_addr *ip)/* * Generate a link-local IPv6 address from an Ethernet address as specified in -* RFC 2464, section 5. +* RFC 4291, section 2.5.1. * * @param[out] ip * The link-local IPv6 address to generate.
@@ -406,7 +406,7 @@ rte_ipv6_llocal_from_ethernet(struct rte_ipv6_addr *ip, const struct rte_ether_a ip->a[0 ] =0xfe ; ip->a[1 ] =0x80 ;memset (&ip->a[2 ],0 ,6 ); - ip->a[8 ] = mac->addr_bytes[0 ]; + ip->a[8 ] = mac->addr_bytes[0 ] ^0x02 ; ip->a[9 ] = mac->addr_bytes[1 ]; ip->a[10 ] = mac->addr_bytes[2 ]; ip->a[11 ] =0xff ;
Signature · 1 line
2.51.1
app/test/test_net_ip6.c test_ipv6_llocal_from_ethernet(void) · line 164
Quoted 11 lines · On Fri, 14 Nov 2025 11:57:53 +0100 Robin Jarry <[email protected]> wrote:
app/test/test_net_ip6.c +1
-1@@ -160,7 +160,7 @@ test_ipv6_llocal_from_ethernet(void) {const struct rte_ether_addr local_mac = {{0x04 ,0x7b ,0xcb ,0x5c ,0x08 ,0x44 }};const struct rte_ipv6_addr local_ip = -RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x047b ,0xcbff ,0xfe5c ,0x0844 ); +RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x067b ,0xcbff ,0xfe5c ,0x0844 );struct rte_ipv6_addr ip;
Reading the kernel source there is also special handling to include device id into link local address. This is to handle corner case where multiple network cards have same hardware MAC address.
Perhaps DPDK should roll port id into link local address?
Stephen Hemminger, Nov 14, 2025 at 17:05:
Quoted 19 lines
app/test/test_net_ip6.c test_ipv6_llocal_from_ethernet(void) · line 164
Quoted 11 lines · On Fri, 14 Nov 2025 11:57:53 +0100 Robin Jarry <[email protected]> wrote:
app/test/test_net_ip6.c +1
-1@@ -160,7 +160,7 @@ test_ipv6_llocal_from_ethernet(void) {const struct rte_ether_addr local_mac = {{0x04 ,0x7b ,0xcb ,0x5c ,0x08 ,0x44 }};const struct rte_ipv6_addr local_ip = -RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x047b ,0xcbff ,0xfe5c ,0x0844 ); +RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x067b ,0xcbff ,0xfe5c ,0x0844 );struct rte_ipv6_addr ip;Reading the kernel source there is also special handling to include device id into link local address. This is to handle corner case where multiple network cards have same hardware MAC address.
Quoted 1 line
Perhaps DPDK should roll port id into link local address?
We discussed about this but it would break the API since you would need to pass an additional parameter to this function.
And also, I couldn't find anything in the RFC that describes what the kernel does with device IDs. Nothing mentions if the device id should be big or little endian, etc.
I don't mind changing the API though but it seems very opinionated.
Signature · 1 line
Robin
Quoted 1 line
Contest void where prohibited by law.
Quoted 14 lines
From: Stephen Hemminger [mailto:[email protected]]
Sent: Friday, 14 November 2025 17.06app/test/test_net_ip6.c test_ipv6_llocal_from_ethernet(void) · line 161
Quoted 7 lines · On Fri, 14 Nov 2025 11:57:53 +0100 Robin Jarry <[email protected]> wrote:
app/test/test_net_ip6.c +0
-0@@ -160,7 +160,7 @@ test_ipv6_llocal_from_ethernet(void) {const struct rte_ether_addr local_mac = {{0x04 ,0x7b ,0xcb ,0x5c ,
0x08,0x44}};
Quoted 10 lines
app/test/test_net_ip6.c test_ipv6_llocal_from_ethernet(void) · line 164
Quoted 4 lines
app/test/test_net_ip6.c +1
-1const struct rte_ipv6_addr local_ip = -RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x047b ,0xcbff ,0xfe5c ,0x0844 ); +RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x067b ,0xcbff ,0xfe5c ,0x0844 );struct rte_ipv6_addr ip;Reading the kernel source there is also special handling to include device
id into link local address. This is to handle corner case where multiple network
cards have same hardware MAC address.
Multiple physical NICs should never have the same MAC address.
Lots of stuff will break if they do.
E.g. a network switch will forward packets to the NIC that most recently sent packets, so its MAC address was learned in the MAC address table in the switch.
What is the use case justifying this tweak in the kernel?
Quoted 2 lines
Perhaps DPDK should roll port id into link local address?
Morten Brørup, Nov 14, 2025 at 17:14:
Quoted 7 lines
Quoted 6 lines
From: Stephen Hemminger [mailto:[email protected]] Reading the kernel source there is also special handling to include device
id into link local address. This is to handle corner case where multiple network
cards have same hardware MAC address.
Quoted 5 lines
Multiple physical NICs should never have the same MAC address.
Lots of stuff will break if they do.
E.g. a network switch will forward packets to the NIC that most recently sent packets, so its MAC address was learned in the MAC address table in the switch.What is the use case justifying this tweak in the kernel?
Some drivers expose multiple ports sharing the same mac address.
This very is specific to certain hardware vendors. The comment here refers to a change from 2005. I couldn't find the root commit/email.
https://github.com/jeffmahoney/linux-pre-git/commit/44993d3bb812f
Quoted 10 lines
2005/01/17 13:13:52-08:00 hch
[IPV6]: Fix EUI64 generation on S/390.- put a
dev_idfield in structnet_device, so that it uses space that would be wasted by padding otherwise.
- if this fields is non-null letipv6_generate_eui64use the algorithm from the QETH code to generate an EUI that's different for each OS instance. See code comments for details.Signed-off-by David S. Miller <[email protected]>
I don't think DPDK should care about this.
Signature · 1 line
Robin
Quoted 1 line
Do not use while operating a motor vehicle or heavy machinery.
Quoted 12 lines · On Fri, 14 Nov 2025 17:46:42 +0100 "Robin Jarry" <[email protected]> wrote:
Morten Brørup, Nov 14, 2025 at 17:14:
Quoted 11 lines
Quoted 6 lines
From: Stephen Hemminger [mailto:[email protected]] Reading the kernel source there is also special handling to include device
id into link local address. This is to handle corner case where multiple network
cards have same hardware MAC address.Multiple physical NICs should never have the same MAC address.
Lots of stuff will break if they do.
E.g. a network switch will forward packets to the NIC that most recently sent packets, so its MAC address was learned in the MAC address table in the switch.
Quoted 6 lines
Quoted 1 line
What is the use case justifying this tweak in the kernel?
Some drivers expose multiple ports sharing the same mac address.
This very is specific to certain hardware vendors. The comment here refers to a change from 2005. I couldn't find the root commit/email.
Yes, this code is in first git commit 2.6.12 from 2005 it is possible (but not worth the effort) to find it in earlier repos.
Stephen Hemminger, Nov 14, 2025 at 18:03:
Quoted 2 lines
Yes, this code is in first git commit 2.6.12 from 2005 it is possible (but not worth the effort) to find it in earlier repos.
I finally found the original discussion on the netdev mailing list:
https://lore.kernel.org/all/[email protected]/
And the commit that introduced this quirk:
Signature · 1 line
Robin
Quoted 1 line
Post no bills.
Quoted 25 lines · On Fri, 14 Nov 2025 17:14:32 +0100 Morten Brørup <[email protected]>…
Quoted 14 lines
From: Stephen Hemminger [mailto:[email protected]]
Sent: Friday, 14 November 2025 17.06app/test/test_net_ip6.c test_ipv6_llocal_from_ethernet(void) · line 161
Quoted 7 lines · On Fri, 14 Nov 2025 11:57:53 +0100 Robin Jarry <[email protected]> wrote:
app/test/test_net_ip6.c +0
-0@@ -160,7 +160,7 @@ test_ipv6_llocal_from_ethernet(void) {const struct rte_ether_addr local_mac = {{0x04 ,0x7b ,0xcb ,0x5c ,
0x08,0x44}};Quoted 10 lines
app/test/test_net_ip6.c test_ipv6_llocal_from_ethernet(void) · line 164
Quoted 4 lines
app/test/test_net_ip6.c +1
-1const struct rte_ipv6_addr local_ip = -RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x047b ,0xcbff ,0xfe5c ,0x0844 ); +RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x067b ,0xcbff ,0xfe5c ,0x0844 );struct rte_ipv6_addr ip;Reading the kernel source there is also special handling to include device
id into link local address. This is to handle corner case where multiple network
cards have same hardware MAC address.
Quoted 8 lines
Multiple physical NICs should never have the same MAC address.
Lots of stuff will break if they do.
E.g. a network switch will forward packets to the NIC that most recently sent packets, so its MAC address was learned in the MAC address table in the switch.What is the use case justifying this tweak in the kernel?
Quoted 2 lines
Perhaps DPDK should roll port id into link local address?
It is allowed for multiple NIC's to have the same MAC but they can't be on same network namespace.
Code in Linux is working around special case here:
static inline int addrconf_ifid_eui48(u8 *eui, struct net_device *dev)
{
if (dev->addr_len != ETH_ALEN)
return -1;
/*
* The zSeries OSA network cards can be shared among various
* OS instances, but the OSA cards have only one MAC address.
* This leads to duplicate address conflicts in conjunction
* with IPv6 if more than one instance uses the same card.
*
* The driver for these cards can deliver a unique 16-bit
* identifier for each instance sharing the same card. It is
* placed instead of 0xFFFE in the interface identifier. The
* "u" bit of the interface identifier is not inverted in this
* case. Hence the resulting interface identifier has local
* scope according to RFC2373.
*/
addrconf_addr_eui48_base(eui, dev->dev_addr);
if (dev->dev_id) {
eui[3] = (dev->dev_id >> 8) & 0xFF;
eui[4] = dev->dev_id & 0xFF;
} else {
eui[0] ^= 2;
}
return 0;
}lib/net/rte_ip6.h rte_ipv6_llocal_from_ethernet(struct rte_ipv6_addr *ip, const struct rte_ether_a · line 409
Quoted 6 lines · On Fri, 14 Nov 2025 11:57:53 +0100 Robin Jarry <[email protected]> wrote:
lib/net/rte_ip6.h +1
-1@@ -406,7 +406,7 @@ rte_ipv6_llocal_from_ethernet(struct rte_ipv6_addr *ip, const struct rte_ether_a ip->a[0 ] =0xfe ; ip->a[1 ] =0x80 ;memset (&ip->a[2 ],0 ,6 ); - ip->a[8 ] = mac->addr_bytes[0 ]; + ip->a[8 ] = mac->addr_bytes[0 ] ^0x02 ;
Could use RTE_ETHER_LOCAL_ADMIN_ADDR instead of magic constant 2, but seems a little wordy to do that.
PATCH v2 1/1 net: ipv6 link local compliance with rfc 4291
In reply to Robin JarryFrom: Christophe Fontaine <[email protected]>
As specified in RFC 4291 section 2.5.1, link local addresses must be generated based on a modified EUI-64 interface identifier:
Quoted 3 lines
Modified EUI-64 format interface identifiers are formed by inverting the "u" bit (universal/local bit in IEEE EUI-64 terminology) when forming the interface identifier from IEEE EUI-64 identifiers.
This translates to 'mac->addr_bytes[0] ^= 0x02'.
---
Notes:
v2:
- Stephen: replaced magic number 0x02 with RTE_ETHER_LOCAL_ADMIN_ADDR
app/test/test_net_ip6.c | 2 +- lib/net/rte_ip6.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
app/test/test_net_ip6.c +1 -1
@@ -160,7 +160,7 @@ test_ipv6_llocal_from_ethernet(void) {const struct rte_ether_addr local_mac = {{0x04 ,0x7b ,0xcb ,0x5c ,0x08 ,0x44 }};const struct rte_ipv6_addr local_ip = -RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x047b ,0xcbff ,0xfe5c ,0x0844 ); +RTE_IPV6 (0xfe80 ,0 ,0 ,0 ,0x067b ,0xcbff ,0xfe5c ,0x0844 );struct rte_ipv6_addr ip;rte_ipv6_llocal_from_ethernet (&ip, &local_mac);
lib/net/rte_ip6.h +2 -2
@@ -393,7 +393,7 @@ rte_ipv6_mc_scope(const struct rte_ipv6_addr *ip)/* * Generate a link-local IPv6 address from an Ethernet address as specified in -* RFC 2464, section 5. +* RFC 4291, section 2.5.1. * * @param[out] ip * The link-local IPv6 address to generate.
@@ -406,7 +406,7 @@ rte_ipv6_llocal_from_ethernet(struct rte_ipv6_addr *ip, const struct rte_ether_a ip->a[0 ] =0xfe ; ip->a[1 ] =0x80 ;memset (&ip->a[2 ],0 ,6 ); - ip->a[8 ] = mac->addr_bytes[0 ]; + ip->a[8 ] = mac->addr_bytes[0 ] ^RTE_ETHER_LOCAL_ADMIN_ADDR ; ip->a[9 ] = mac->addr_bytes[1 ]; ip->a[10 ] = mac->addr_bytes[2 ]; ip->a[11 ] =0xff ;
Signature · 1 line
2.51.1
18/11/2025 18:45, Robin Jarry:
Quoted 4 lines
From: Christophe Fontaine <[email protected]>
Quoted 3 lines
Modified EUI-64 format interface identifiers are formed by inverting the "u" bit (universal/local bit in IEEE EUI-64 terminology) when forming the interface identifier from IEEE EUI-64 identifiers.
[...]
lib/net/rte_ip6.h rte_ipv6_llocal_from_ethernet(struct rte_ipv6_addr *ip, const struct rte_ether_a · line 409
Quoted 2 lines
lib/net/rte_ip6.h +1
-1- ip->a[8 ] = mac->addr_bytes[0 ]; + ip->a[8 ] = mac->addr_bytes[0 ] ^RTE_ETHER_LOCAL_ADMIN_ADDR ;
In all other places, we use the operator OR, so I'm not sure about this XOR. Also, rte_is_universal_ether_addr() and rte_is_local_admin_ether_addr() are comparing this bit with 0.
Is this bit always 0 by default in MAC addresses?
Thomas Monjalon
Applied19/11/2025 01:07, Thomas Monjalon:
Quoted 5 lines
18/11/2025 18:45, Robin Jarry:
Quoted 4 lines
From: Christophe Fontaine <[email protected]>
Quoted 3 lines
Modified EUI-64 format interface identifiers are formed by inverting the "u" bit (universal/local bit in IEEE EUI-64 terminology) when forming the interface identifier from IEEE EUI-64 identifiers.
Quoted 8 lines
[...]
lib/net/rte_ip6.h rte_ipv6_llocal_from_ethernet(struct rte_ipv6_addr *ip, const struct rte_ether_a · line 409
Quoted 2 lines
lib/net/rte_ip6.h +1
-1- ip->a[8 ] = mac->addr_bytes[0 ]; + ip->a[8 ] = mac->addr_bytes[0 ] ^RTE_ETHER_LOCAL_ADMIN_ADDR ;In all other places, we use the operator OR, so I'm not sure about this XOR. Also,
rte_is_universal_ether_addr()andrte_is_local_admin_ether_addr()are comparing this bit with 0.
Is this bit always 0 by default in MAC addresses?
After checking with Stephen, it appears that XOR is really desired. This bit must be inverted, so 0 is local and 1 is universal.
Adding this comment in the code to avoid confusion in future:
/*
* The "u" bit (universal/local bit in IEEE EUI-64 terminology)
* must be inverted for IPv6 link local address.
* 0 means local scope, 1 means universal scope.
*/
Applied, thanks.
Thomas Monjalon, Nov 19, 2025 at 01:27:
Quoted 11 lines
After checking with Stephen, it appears that XOR is really desired. This bit must be inverted, so 0 is local and 1 is universal.
Adding this comment in the code to avoid confusion in future:
/*
* The "u" bit (universal/local bit in IEEE EUI-64 terminology)
* must be inverted for IPv6 link local address.
* 0 means local scope, 1 means universal scope.
*/Applied, thanks.
Thanks Thomas.
That XOR is described in the RFCs as "flipping the 'u' bit". I.e. the bit should be 1 if the address is "global/universal" and 0 if the address is "local/administratively configured".
Here's the corresponding code in Linux and BSD:
https://github.com/torvalds/linux/blob/v6.17/include/net/addrconf.h#L201
https://github.com/freebsd/freebsd-src/blob/release/14.3.0/sys/netinet6/in6_ifattach.c#L97
Signature · 1 line
Robin
Quoted 1 line
No animals were injured.