
Configuring IPv6 on Linux with nmcli
The Problem to be Solved:
I wanted to reduce the work involved in updating my
local DNS and DHCPv6 server
when my ISP changes my globally routeable IPv6 address block.
Open source applications strongly prefer IPv6.
A connection with ssh
or sftp
will attempt to connect to the IPv6 address first,
and then after a timeout, roll back to IPv4.
I want to be able to use hostnames that correctly resolve
to IPv6 addresses.
That meant changing the IPv6 generation algorithm
from its default behavior to the predictable EUI-64 method.
That in turn meant that I must figure out how to do that
with the nmcli
command.
I would be doing this on multiple Linux distributions.
While the nmcli
has its own peculiar and
long-winded syntax, I only have to solve this problem once.
The command-line solution works the same across Linux
distributions.
Now, before you cringe at my use of EUI-64 and SLAAC
and start lecturing me about privacy issues:
I only use this solution,
and I only suggest that you might want to use it,
within a trusted organization.
For me, that's my home (and consulting lab).
When I take my laptop to the coffee shop,
it will still generate randomized interface identifiers
on those other WiFi networks.
The Context:
Linux Mint Oracle Linux Raspberry Pi OS OSMC Media CenterMy primary desktop and my laptop both run Linux Mint.
A desktop I use to mirror media storage (pictures, music, videos) runs Oracle Linux, because that also gives me an added test platform for a consulting project that uses Red Hat Enterprise Linux.
I own several Raspberry Pi single-board computers that run Raspberry Pi OS. It's based on Debian Linux, and used to be called Raspbian. One is my DNS and DHCPv6 server, two do ACARS aircraft tracking and some GnuRadio experiments, one is my OSMC-based media server.
Once I figured out how to accomplish what I show you here, I ran the corresponding commands on all those systems, changing the connection names as needed.
My ISP is Xfinity, operating as Comcast.
They allocate me a globally routeable /64 block of
IP addresses.
My router, a Netgear R6220, can handle the IPv6.
Notice that Xfinity/Comcast has assigned my router's
external interface an address in the
2001:558:6002:39::/64 address block,
and told it to use the
2601:249:4300:4f7::/64 address block internally.
The whois
command shows me that both
of the blocks
2001:558::/29
and
2601::/20
are assigned to Comcast.

My Own DNS, and Thus DHCP
I do not want to use my ISP's DNS service, because they do the nonsense of providing bogus answers that would direct my browser to advertisers' sites if I make a typo in a URL. No, if I misspell the hostname in a URL, I want to the browser to tell me that they're no such host and I seem to have made a typing error.
One Raspberry Pi with two IPv4 addresses1 is my DNS and DHCP server for IPv4 and IPv6.
I use the kc9rg.org
domain internally.
It's my ham radio callsign, I have never gotten around
to registering that domain.
My DHCP server tells hosts how to do DNS and routing on IPv4:
root@ns1# more /etc/dhcp/dhcpd.conf [... several lines deleted ...] # option definitions common to all supported networks... option domain-name "kc9rg.org"; option domain-name-servers 192.168.1.3, 192.168.1.4; ddns-update-style none; # Internal network, run the server on 192.168.1.0/24 only. subnet 192.168.1.0 netmask 255.255.255.0 { authoritative; # Default gateway, netmask -- the router is manually configured # to use that IPv4/netmask on its inside interface. option routers 192.168.1.254; option subnet-mask 255.255.255.0; # 30 days by default, 30 days max default-lease-time 2592000; max-lease-time 2592000; range dynamic-bootp 192.168.1.220 192.168.1.249; # Configure the laser printer host hpljp3015 { hardware ethernet 3c:4a:92:c0:aa:21; fixed-address 192.168.1.40; } # Configure the Blu-ray player host bluray { hardware ethernet 00:1c:50:ac:72:1e; fixed-address 192.168.1.42; } [... blocks for more devices deleted ...] }
On IPv6, each host picks up the /64 address block and the default route in the ICMPv6 Router Advertisement packets coming from the router. My DHCPv6 service fills in the IPv6 DNS details:
root@ns1# more /etc/dhcp/dhcpd6.conf [... several lines deleted ...] # Global definitions for name server address(es) and domain search list subnet6 2601:249:4300:4f7::/64 { option dhcp6.name-servers 2601:249:4300:4f7:dea6:32ff:fe36:a94e; option dhcp6.domain-search "kc9rg.org"; default-lease-time 2592000; max-lease-time 2592000; }
IPv6 Addresses
An IPv6 address is 128 bits long. In most situations, the first 64 bits are the network prefix and the last 64 bits are the interface identifier. That's the IPv6 terminology, I'm used to "network ID" and "host ID" for the IPv4 equivalents.
Now, within an organization, feel free to consider the first 48 (or more) bits of the network prefix as the routing prefix and the remaining 16 (or fewer) bits as a subnet ID. However, I would expect an ISP to assign your organization a globally routeable /64 block.
Now, how to assign the interface identifiers, the host IDs?
The obvious choices are:
- Select and manually assign 64-bit interface identifiers on all hosts. That would probably lead you into madness.
- Select and configure your DHCPv6 server to assign selected 64-bit interface identifiers. That would probably be a more complex road into madness.
- Let the operating system generate random interface identifiers. That will prevent easy DNS service. Yes, there are ways to dynamically update DNS, and I could set up a Kerberos server while I'm at it, but I don't want to.
- Encapsulate the 48-bit MAC address within the 64-bit interface identifier. Yes, that could allow someone to detect that the same WiFi MAC address was used at a certain time at one coffee shop, and at a later time at a different location, so that was probably the same portable computer being used by the same person. That is, if my laptop always did that.
So, my solution will be:
- Derive the IPv6 address from the MAC address for systems at home, including my laptop when it's there, and...
- Have my laptop generate a random interface identifier when I'm away from home.
EUI-64 Addresses
Observe what's happening for the wireless interface on my laptop after I have made the needed fix. I want to use the "global dynamic" IPv6 address:
cromwell@laptop:$ ip link show wlp1s0 2: wlp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DORMANT group default qlen 1000 link/ether 7c:88:99:1b:c2:34 brd ff:ff:ff:ff:ff:ff cromwell@laptop:$ ip -6 addr show wlp1s0 2: wlp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 inet6 2601:249:4300:4f7:58ba:4c62:181f:529/64 scope global temporary dynamic valid_lft 345587sec preferred_lft 75302sec inet6 2601:249:4300:4f7:7e88:99ff:fe1b:c234/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 345587sec preferred_lft 345587sec inet6 fe80::7e88:99ff:fe1b:c234/64 scope link noprefixroute valid_lft forever preferred_lft forever
Match the colors to see what has happened
with the two pieces of the MAC address.
The 16-bit 0xfffe
block indicates
that this is an EUI-64 encapsulated
interface identifier.
MAC address: 7c:88:99:1b:c2:34 \---+--/ \---+--/ | | manufacturer ID incremented by manufacturer so that all of its devices have unique 6-octet or 48-bit MAC addresses interface identifier | /--------+--------\ IPv6 address: 2601:249:4300:4f7:7e88:99ff:fe1b:c234 \-------+-------/ \--+--/\-+-/\--+--/ | | | | network prefix, | | | a.k.a. net ID | FF:FE | | | manufacturer ID from device-unique the MAC address with segment of the bit #7 set to 1: MAC address 0x7c = 1111100 0x7e = 1111110
The first 48 bits of my allocated IPv6 block, 2601:249:4300::/48, haven't changed for ages, at least a few years and maybe since Comcast first started supporting IPv6 here. The remaining 16 bits of the network prefix change every few months, once in a while just a few weeks apart. I believe that I'm noticing a correlation with electrical power outages on the scale of at least a neighborhood, at least for some fraction of the changes. So, Comcast is using 2601:249:4300::/48 as a routing prefix to get to the area where I live in West Lafayette, and then with the following 16 subnet bits they could have up to 216 = 65,536 possible customer subnets within that routing prefix. That's plenty for a town with multiple ISPs and 44,595 residents at the last census.
My cable router has dual-band wireless, 2.4 GHz for 802.11b/g/n and 5 GHz for 802.11a/n/ac.
The Raspberry Pi that is my DNS/DHCP server has a 1 Gbps Ethernet interface plugged into a switch, and 802.11 WiFi associated with the router. For the wireless side it reports:
cromwell@ns1$ iwconfig lo no wireless extensions. eth0 no wireless extensions. wlan0 IEEE 802.11 ESSID:"FBI_van4" Mode:Managed Frequency:5.745 GHz Access Point: 38:94:ED:FA:48:8C Bit Rate=24 Mb/s Tx-Power=31 dBm Retry short limit:7 RTS thr:off Fragment thr:off Power Management:on Link Quality=49/70 Signal level=-61 dBm Rx invalid nwid:0 Rx invalid crypt:0 Rx invalid frag:0 Tx excessive retries:0 Invalid misc:0 Missed beacon:0
Let's see what happened after a recent Comcast change to my IPv6 block, from 2601:249:4300:1ec4::/64 to 2601:249:4300:4f7::/64, from subnet 0x1ec4 to subnet 0x04f7:
cromwell@ns1$ ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host noprefixroute valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether dc:a6:32:36:a9:4d brd ff:ff:ff:ff:ff:ff inet 192.168.1.4/24 brd 192.168.1.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet6 2601:249:4300:4f7:9c2:87db:97cc:14e6/64 scope global dynamic noprefixroute valid_lft 345534sec preferred_lft 345534sec inet6 fe80::c8c9:8e5d:7580:eb30/64 scope link noprefixroute valid_lft forever preferred_lft forever 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether dc:a6:32:36:a9:4e brd ff:ff:ff:ff:ff:ff inet 192.168.1.3/24 brd 192.168.1.255 scope global noprefixroute wlan0 valid_lft forever preferred_lft forever inet6 2601:249:4300:4f7:df45:31c8:f6c6:d75b/64 scope global dynamic noprefixroute valid_lft 345534sec preferred_lft 345534sec inet6 fe80::5b28:c9b4:79aa:fede/64 scope link noprefixroute valid_lft forever preferred_lft forever
There are my two IPv4 addresses on the DNS server:
192.168.1.3 and 192.168.1.4.
However, neither of the interface identifiers of the
globally routeable IPv6 addresses:
2601:249:4300:4f7:9c2:87db:97cc:14e6
2601:249:4300:4f7:df45:31c8:f6c6:d75b
bear any resemblance to the MAC addresses
of those interfaces:
dc:a6:32:36:a9:4d
dc:a6:32:36:a9:4e
Nor do they bear any resemblance to what
the IPv6 interface identifiers were
before the recent change.
Using nmcli
Many Linux subsystems are far more complex
than what's needed by the typical user.
And nmcli
and the NetworkManager
daemon it controls and queries
are striking examples of that.
It's not too bad to get a list of defined network connections:
cromwell@ns1$ nmcli connection show NAME UUID TYPE DEVICE Wired connection 1 5e2b664a-6db9-3635-9959-98d3bd981b48 ethernet eth0 FBI_van4 e36f354e-40ea-4733-8683-6345cc55fc05 wifi wlan0 lo 5630b038-69fe-47dc-8503-b2b4c37836ab loopback lo
A full report on just one of them drowns me in detail:
cromwell@ns1$ nmcli connection show FBI_van4 [... over 160 lines of output! ...] cromwell@ns1$ nmcli connection show 'Wired connection 1' [... over 130 lines of output! ...]
However, if I know what I'm looking for, I can ask for just that:
cromwell@ns1$ nmcli --get-values ipv6.addr-gen-mode connection show FBI_van4 default
That's a lot of typing for a one-word answer,
but default
is
the answer that tells me why this isn't yet behaving
as I want it to.
"Ah," you say, "so just how does the default work?"
Unfortunately, the answer to that is "It depends"
and the details are voluminous.
Somewhere over 400 lines into the 1,300-line manual page for
nmcli
it tells you that the settings and property names
and their default behaviors are described
in the 5,140-line manual page for
nm-settings-nmcli
.
But it's worse.
In getting this far, I found lots of commentary about how
default
for the
ipv6.addr-gen-mode
setting is actually ambiguous,
it depends on multiple other settings
described in other multi-thousand-line manual pages.
Fortunately in my case, default
in any of
its variations is never what I was wanting,
so at least I found the thing that was mis-set
and didn't have to go searching for
an understanding of the ambiguous default.
My goal needs eui64
.
Should the designers of the nmcli
command and
the NetworkManager
daemon be beat about the
head and shoulders?
Yes.
It gets even worse. That long-winded command has everything spelled out, making for a slight advantage for understanding what it's doing. But the options and parameters, or at least many of them, can be abbreviated, kind of like Cisco configuration commands. However, the degree to which a particular one can be abbreviated may be context-dependent. These versions all do the same thing:
cromwell@ns1$ nmcli --get-values ipv6.addr-gen-mode connection show FBI_van4 default cromwell@ns1$ nmcli -g ipv6.addr-gen-mode connection show FBI_van4 default cromwell@ns1$ nmcli -g ipv6.addr-gen-mode con sho FBI_van4 default cromwell@ns1$ nmcli -g ipv6.addr-gen-mode c s FBI_van4 default
This, of course, is ridiculous.
Anyway, here's the pair of changes I need to make to change the wired Ethernet and the WiFi connection use EUI-64 to generate the IPv6 interface identifiers, and the pair of checks to verify that they worked:
cromwell@ns1$ sudo bash root@ns1# set -o vi root@ns1# nmcli connection modify FBI_van4 ipv6.addr-gen-mode eui64 root@ns1# nmcli connection modify 'Wired connection 1' ipv6.addr-gen-mode eui64 root@ns1# nmcli --get-values ipv6.addr-gen-mode connection show FBI_van4 eui64 root@ns1# nmcli --get-values ipv6.addr-gen-mode connection show 'Wired connection 1' eui64
Now I can tell the NetworkManager
daemon
to restart.
I'm not going to waste time speculating on what it might
do if I told it to continue running while doing
reload
instead.
root@ns1# systemctl restart NetworkManager
And, let's see if we don't have EUI-64 based IPv6 addresses now:
root@ns1# ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether dc:a6:32:36:a9:4d brd ff:ff:ff:ff:ff:ff 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DORMANT group default qlen 1000 link/ether dc:a6:32:36:a9:4e brd ff:ff:ff:ff:ff:ff root@ns1# ip -6 addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000 inet6 ::1/128 scope host noprefixroute valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000 inet6 2601:249:4300:4f7:dea6:32ff:fe36:a94d/64 scope global dynamic noprefixroute valid_lft 345555sec preferred_lft 345555sec inet6 2601:249:4300:4f7:9c2:87db:97cc:14e6/64 scope global dynamic noprefixroute valid_lft 345497sec preferred_lft 345497sec inet6 fe80::c8c9:8e5d:7580:eb30/64 scope link noprefixroute valid_lft forever preferred_lft forever 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000 inet6 2601:249:4300:4f7:dea6:32ff:fe36:a94e/64 scope global dynamic noprefixroute valid_lft 345555sec preferred_lft 345555sec inet6 fe80::dea6:32ff:fe36:a94e/64 scope link noprefixroute valid_lft forever preferred_lft forever
Yes! Problem solved!
If I ask for a connection list on my laptop, I get an enormous amount of output — the two currently active plus all the connections used in the past preceded by "Auto".
cromwell@laptop:$ nmcli connection show
NAME UUID TYPE DEVICE
FBI_van4 ff1ae12b-d38d-4279-8e50-5f10b6da1a1d wifi wlp1s0
lo 19d7e852-32dd-43c6-9ce1-ebde78f6965e loopback lo
Auto *WIFI-AIRPORT 54d9a0a0-f546-4a9e-a4c3-45d64c66f5e1 wifi --
Auto .ONCF daee3f75-34a5-43db-a490-19adabdf4342 wifi --
Auto 12 T?nar 542d560c-8dfe-477d-9d31-c9f46c89c631 wifi --
Auto @FuriousSpoonChi Free Wi-Fi 8f5dcf64-59af-4d81-9873-dc0558b2a9c7 wifi --
Auto @NewLineTavern Free Wi-Fi 4c5fe568-c2cc-4842-bb4a-cd5402c5ec64 wifi --
Auto ALPHASTUDIOS 2,4G f86a91b8-a27f-4a9a-b60a-0f3200474d92 wifi --
Auto AMIGuest1 546ffd4a-42b4-472e-8f75-28d8da7df281 wifi --
Auto APARTHOTEL PAPAFOTIS 120aba94-d152-460e-934c-b1cea2277313 wifi --
Auto ATL Free Wi-Fi 5458f446-484f-44b3-832b-45dab71bafbf wifi --
Auto Adam snack f10975d1-24d0-40b3-8eab-5349785e3f17 wifi --
Auto Amtrak_WiFi 8374f654-787c-4219-b5ec-758acc7bb62d wifi --
Auto Auberge la palmerie 910fae66-894a-49d3-a2c4-a009222ac1d2 wifi --
[... a few hundred more lines deleted ...]
There are the WiFi connections at every single coffee shop,
bar, airport, train, and budget accommodation
where I have turned on my laptop over the past few years.
All of them except the two in my home will still have
ipv6.addr-gen-mode
set to default
,
which is still doing whatever confusingly documented thing
it was doing before this project.
Now to update the BIND zone files...
Going Deeper
RFC 7217
A Method for Generating Semantically Opaque
Interface Identifiers with IPv6
Stateless Address Autoconfiguration (SLAAC)
RFC 8064
Recommendation on Stable IPv6 Interface Identifiers
1: The U.S. DoD STIG requires two IPv4 addresses for name servers. My consulting project thus avoids false-positive DNS shortcomings while I test the hardening scripts I'm developing.