Netlink and MAC addresses
Netlink address fields IFLA_ADDRESS, IFLA_BROADCAST and IFLA_PERM_ADDRESS
A few days ago, I had to figure out how applications such as iproute2 read the MAC address from kernel interfaces. More
specifically, we observed that the MAC addresses of tunnel interfaces seemed to be shorter or longer than 6 Bytes,
ranging from 4 to 16 Bytes depending on the tunnel type.
It turned out that the kernel actually communicates no such field as the MAC address. Instead, a netlink field called
IFLA_ADDRESS transmits the address of the underlying medium which can be an Ethernet MAC (6 Bytes) in case of Ethernet,
or the 4 Byte IPv4 tunnel source address, the 16 Byte IPv6 tunnel source address, etc.
The kernel
The kernel sets
netlink field IFLA_ADDRESS to the value of dev->addr with length dev->addr_len and netlink field
IFLA_BROADCAST to the value of dev->broadcast with length dev->addr_len. It
sets
netlink field IFLA_PERM_ADDESS to the value of dev->perm_addr with length dev->addr_len.
The addr, broadcast and perm_addr fields will contain different values depending on the interface type. For unicast
Ethernet, addr and perm_addr will hold the interface's MAC address while the broadcast field will hold the Ethernet
broadcast address. On the other hand, for tunnel interfaces, the fields will hold the tunnel endpoint addresses.
Specifically, for Ethernet devices, the broadcast field is set to
ff:ff:ff:ff:ff:ff.
The addr_len is set to 6 Bytes.
I couldn't find the exact location where the perm_addr or addr fields are set (maybe here and here?), however, from strace output, it is clear that for Ethernet devices, the perm_addr equals the addr.
For IPv6 GRE, the addr and broadcast fields are populated with laddr and raddr of the tunnel, at locations:
- https://github.com/torvalds/linux/blob/448b3fe5a0eab5b625a7e15c67c7972169e47ff8/net/ipv6/ip6_tunnel.c#L1468
- https://github.com/torvalds/linux/blob/448b3fe5a0eab5b625a7e15c67c7972169e47ff8/net/ipv6/ip6_gre.c#L1542C2-L1543C72
- https://github.com/torvalds/linux/blob/448b3fe5a0eab5b625a7e15c67c7972169e47ff8/net/ipv6/ip6_gre.c#L1108
The length of the 2 fields is set to the size of in6_addr, meaning to the size of an IPv6 address (16 Bytes if we
exclude overhead).
Similar code is used for IPv6 IP in IP tunnels. Even though I have not verified it, I assume that IPv4 tunnels are handled the same way.
The strange handling of tunnel IFLA_PERM_ADDRESS length
For tunnel interfaces, the perm_addr field actually
contains a randomly generated MAC address,
and thus the
advertised length
of IFLA_PERM_ADDRESS does not match the length of the field's contents.
A permanent address for an IPv6 tunnel holding a random 6 Byte MAC address will be interpreted as a 16 Byte permanent
address on the client side. And for IPv4 tunnels, the address will actually be interpreted as having the length of an
IPv4 address, and the last 2 Bytes are cut off. We can clearly see this from the various outputs of strace.
-
strace output of field
IFLA_PERM_ADDRESSfor an Ethernet interface:1[{nla_len=10, nla_type=IFLA_PERM_ADDRESS}, 52:54:00:37:3b:25] -
strace output of field
IFLA_PERM_ADDRESSfor an IPv4 GRE tunnel:1[{nla_len=8, nla_type=IFLA_PERM_ADDRESS}, c0:a8:7b:0a] -
strace output of field
IFLA_PERM_ADDRESSfor an IPv6 GRE tunnel:1[{nla_len=20, nla_type=IFLA_PERM_ADDRESS}, 8a:c3:53:32:b0:22:00]
This leads to a display bug in the output of ip link for IPv6 tunnels, whereas the permaddr field is not displayed
for IPv4 tunnels.
iproute2
Let's use iproute2 to list an IPv6 GRE link:
1 2 3 | |
In iproute2, interface information is retrieved in function iplink_get.
In the strace output, we can see the netlink request to the kernel:
1 2 3 4 | |
Note how we are requesting information for interface [{nla_len=10, nla_type=IFLA_IFNAME}, "test0"]]] because an interface
name was specified:
1 2 3 4 5 | |
The netlink request is sent and received in rtnl_talk:
1 2 | |
The strace shows a fairly long answer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
Note: You can see fields here such as
IFLA_PERM_ADDRESS,IFLA_ADDRESS,IFLA_BROADCAST.Note: Both
IFLA_ADDRESSandIFLA_BROADCASTare not displayed correctly by strace and in the case of tunnels contain the actual left and right addresses.
The retrieved information is then printed with function
print_linkinfo.
The first line of the ip link output is printed with
print_nl.
The link type (link/%s) is read from the ifi_type field. The source address is read from IFLA_ADDRESS, and if
inside ifi_flags flag IFF_POINTOPOINT is set to true, the peer keyword will be printed, followed by the
IFLA_BROADCAST. From this side of the code, it is clear that the left tunnel address is stored inside IFLA_ADDRESS
and the right tunnel address is stored inside IFLA_BROADCAST.
The strange handling of tunnel IFLA_PERM_ADDRESS length
The permanent address
(permaddr)
is extracted from IFLA_PERM_ADDRESS.
We can observe an interesting detail from the strace, and also from the output of ip link: even though the permaddr
holds a randomly generated, 6 Byte MAC address, the field length in total is 20 - 4 = 16 Bytes. That's the length of
an IPv6 address. As a consequence, ip link also prints the address in IPv6 format, even though it should actually
print until the first 6 Bytes in MAC address notation.
We already explained in the kernel section why we see this. However, for IPv4 tunnels, iproute2 does not print
the permaddr, probably because the field actually holds 6 Bytes of content, but the length field only advertises
4 Bytes + overhead:
1 2 3 | |
To me, these findings indicate a minor bug in iproute2 as it should probably not print the permaddr for IPv6 tunnels.