Page MenuHomeFreeBSD

dhclient(8): Add support for IPv6-Only option (RFC 8925)
Needs ReviewPublic

Authored by pouria on Sat, Apr 25, 6:02 PM.
Tags
None
Referenced Files
Unknown Object (File)
Mon, May 18, 12:17 PM
Unknown Object (File)
Sun, May 17, 11:13 AM
Unknown Object (File)
Sat, May 16, 4:39 AM
Unknown Object (File)
Thu, May 14, 3:07 PM
Unknown Object (File)
Thu, May 14, 3:07 PM
Unknown Object (File)
Thu, May 14, 2:40 PM
Unknown Object (File)
Thu, May 14, 2:39 PM
Unknown Object (File)
Thu, May 14, 2:34 PM

Details

Summary

Accept and validate ipv6only option and stop dhcp configuration
process if and only if ipv6 connectivity exists.
Use netlink for ipv6 connectivity check.

Relnotes: yes

I have absolutely no idea about DHCP state machine and its interaction
with dhcp script.
PLEASE REVIEW CAREFULLY.

Test Plan

Set ipv6only option in your dhcp server.
If its value is set to 0xffffffff, dhcp client should not try again.
If it was below max value, it should wait and try again at least
MIN_V6ONLY_WAIT (300) seconds to send another request.
In both cases, it should go_daemon.

% mdo dhclient bridge0
DHCPDISCOVER on bridge0 to 255.255.255.255 port 67 interval 7
DHCPOFFER from 100.64.1.254
IPv6-Only Preferred option received (300 seconds), abort

If option sets after dhclient get bounded, it will renew,
even if the option added later to dhcp server.

DHCPDISCOVER on bridge0 to 255.255.255.255 port 67 interval 3
DHCPOFFER from 100.64.1.254
DHCPREQUEST on bridge0 to 255.255.255.255 port 67
DHCPACK from 100.64.1.254
bound to 100.64.1.125 -- renewal in 300 seconds.
DHCPREQUEST on bridge0 to 100.64.1.254 port 67
DHCPACK from 100.64.1.254
bound to 100.64.1.125 -- renewal in 300 seconds.

So, normally existing clients shouldn't react to option since they simply
send a DHCP REQUEST to renew and we only react to DHCP ACK if we're in INIT-REBOOT state.

% mdo cat /var/db/dhclient.leases.bridge0
lease {
interface "bridge0";
fixed-address 100.64.1.125;
next-server 100.64.1.254;
option subnet-mask 255.255.255.0;
option routers 100.64.1.254;
option domain-name-servers 100.64.1.53,9.9.9.9;
option domain-name "spmzt.net";
option dhcp-lease-time 600;
option dhcp-message-type 5;
option dhcp-server-identifier 100.64.1.254;
option ipv6only 4095;
renew 6 2026/4/25 20:32:56;
rebind 6 2026/4/25 20:36:41;
expire 6 2026/4/25 20:37:56;
}
% mdo dhclient bridge0
DHCPREQUEST on bridge0 to 255.255.255.255 port 67
DHCPACK from 100.64.1.254
bound to 100.64.1.125 -- renewal in 300 seconds.
% mdo pkill dhclient
% mdo dhclient bridge0
DHCPREQUEST on bridge0 to 255.255.255.255 port 67
DHCPACK from 100.64.1.254
IPv6-Only Preferred option received (4095 seconds), abort

If compiled without netlink support, it assumes we don't have connectivity
and ignores the option.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 72751
Build 69634: arc lint + arc unit

Event Timeline

Fix typo in a single log message + rebase main.

  • If the client is in the INIT-REBOOT state and the DHCPv4 server responds with a DHCPACK that includes the IPv6-Only Preferred option, react.
  • Remove extra INET6 macro.

We may want to also update the /etc/rc.d/defaultroute consequently to avoid unnecessary wait time.
But that's another review.

We may want to also update the /etc/rc.d/defaultroute consequently to avoid unnecessary wait time.
But that's another review.

YES PLEASE but also in a way that if there is no IPv4 but 127.1 maybe it doesn't run. People complained about 3 seconds rtsol in the past when I complained about 30 seconds defaultroute. I think further checks in the script and not having DHCP configured anymore made it more bareable if I still have a dual-stack enabled kernel but ... getting it right would be good :)

In D56637#1297542, @bz wrote:

We may want to also update the /etc/rc.d/defaultroute consequently to avoid unnecessary wait time.
But that's another review.

YES PLEASE but also in a way that if there is no IPv4 but 127.1 maybe it doesn't run. People complained about 3 seconds rtsol in the past when I complained about 30 seconds defaultroute. I think further checks in the script and not having DHCP configured anymore made it more bareable if I still have a dual-stack enabled kernel but ... getting it right would be good :)

Unfortunately, I'm not an expert in our /etc/network.subr.
However, I'd like to work on it and see what can I do about it. of course, after this review.

Thank you for adding me to the list of subscribers and thus allowing testing it early.
Today I have tested it in a wired and wireless network, and I can confirm that it works fine. I have not investigated why, but once, after a fresh reboot, the wireless interface got an IPv4 address along with IPv6 - though it never happened to the wired one. I will try to figure out what was going on, perhaps some delays in the wireless network can trigger this.
To make it even smoother, let me propose some enhancements in devd.conf and the defaultroute script - it can improve UX slightly.

--- /etc/rc.d/defaultroute.orig	2026-04-27 00:21:39.995670000 +0200
+++ /etc/rc.d/defaultroute	2026-04-27 08:45:00.451195000 +0200
@@ -36,6 +36,12 @@


 
 	afexists inet || return 0
 
+	# Skip if we support and already have default IPv6 route
+	afexists inet6 && {
+	defif=`get_default_if -inet6`
+	[ -n "${defif}" ] && return
+	}
+	
 	# Return without waiting if we don't have dhcp interfaces or
 	# if none of the dhcp interfaces is plugged in.
 	dhcp_interfaces=`list_net_interfaces dhcp`
--- /etc/devd.conf.orig	2026-04-27 09:14:32.715984000 +0200
+++ /etc/devd.conf	2026-04-27 10:03:57.566192000 +0200
@@ -53,7 +53,7 @@
 	match "system"		"IFNET";
 	match "type"		"LINK_UP";
 	media-type		"ethernet";
-	action "service dhclient quietstart $subsystem";
+	action "service dhclient quietstart $subsystem; service rtsold quietrestart";
 };
 
 #
@@ -72,7 +72,7 @@
 	match "system"		"IFNET";
 	match "type"		"LINK_UP";
 	media-type		"802.11";
-	action "service dhclient quietstart $subsystem";
+	action "service dhclient quietstart $subsystem; service rtsold quietrestart";
 };
 
 # An entry like this might be in a different file, but is included here

Thank you for adding me to the list of subscribers and thus allowing testing it early.
Today I have tested it in a wired and wireless network, and I can confirm that it works fine. I have not investigated why, but once, after a fresh reboot, the wireless interface got an IPv4 address along with IPv6 - though it never happened to the wired one. I will try to figure out what was going on, perhaps some delays in the wireless network can trigger this.

ipv6only option will check if you have a working connection or not.
I guess DORA happens faster than receiving RA in your wifi network.
Is this problem happening all the time or at random?

To make it even smoother, let me propose some enhancements in devd.conf and the defaultroute script - it can improve UX slightly.

--- /etc/rc.d/defaultroute.orig	2026-04-27 00:21:39.995670000 +0200
+++ /etc/rc.d/defaultroute	2026-04-27 08:45:00.451195000 +0200
@@ -36,6 +36,12 @@


 
 	afexists inet || return 0
 
+	# Skip if we support and already have default IPv6 route
+	afexists inet6 && {
+	defif=`get_default_if -inet6`
+	[ -n "${defif}" ] && return
+	}
+	
 	# Return without waiting if we don't have dhcp interfaces or
 	# if none of the dhcp interfaces is plugged in.
 	dhcp_interfaces=`list_net_interfaces dhcp`
--- /etc/devd.conf.orig	2026-04-27 09:14:32.715984000 +0200
+++ /etc/devd.conf	2026-04-27 10:03:57.566192000 +0200
@@ -53,7 +53,7 @@
 	match "system"		"IFNET";
 	match "type"		"LINK_UP";
 	media-type		"ethernet";
-	action "service dhclient quietstart $subsystem";
+	action "service dhclient quietstart $subsystem; service rtsold quietrestart";
 };
 
 #
@@ -72,7 +72,7 @@
 	match "system"		"IFNET";
 	match "type"		"LINK_UP";
 	media-type		"802.11";
-	action "service dhclient quietstart $subsystem";
+	action "service dhclient quietstart $subsystem; service rtsold quietrestart";
 };
 
 # An entry like this might be in a different file, but is included here

Unfortunately, skipping configuration of defaultroute and automatically starting rtsold beside dhcp are depends on administrator.
We're not allowed to do this automatically for user.

Thank you for adding me to the list of subscribers and thus allowing testing it early.
Today I have tested it in a wired and wireless network, and I can confirm that it works fine. I have not investigated why, but once, after a fresh reboot, the wireless interface got an IPv4 address along with IPv6 - though it never happened to the wired one. I will try to figure out what was going on, perhaps some delays in the wireless network can trigger this.

ipv6only option will check if you have a working connection or not.
I guess DORA happens faster than receiving RA in your wifi network.
Is this problem happening all the time or at random?

I only noticed this once on a wireless network, so considering the number of experiments performed, the probability of this occurring was less than 10%.

To make it even smoother, let me propose some enhancements in devd.conf and the defaultroute script - it can improve UX slightly.

--- /etc/rc.d/defaultroute.orig	2026-04-27 00:21:39.995670000 +0200
+++ /etc/rc.d/defaultroute	2026-04-27 08:45:00.451195000 +0200
@@ -36,6 +36,12 @@


 
 	afexists inet || return 0
 
+	# Skip if we support and already have default IPv6 route
+	afexists inet6 && {
+	defif=`get_default_if -inet6`
+	[ -n "${defif}" ] && return
+	}
+	
 	# Return without waiting if we don't have dhcp interfaces or
 	# if none of the dhcp interfaces is plugged in.
 	dhcp_interfaces=`list_net_interfaces dhcp`
--- /etc/devd.conf.orig	2026-04-27 09:14:32.715984000 +0200
+++ /etc/devd.conf	2026-04-27 10:03:57.566192000 +0200
@@ -53,7 +53,7 @@
 	match "system"		"IFNET";
 	match "type"		"LINK_UP";
 	media-type		"ethernet";
-	action "service dhclient quietstart $subsystem";
+	action "service dhclient quietstart $subsystem; service rtsold quietrestart";
 };
 
 #
@@ -72,7 +72,7 @@
 	match "system"		"IFNET";
 	match "type"		"LINK_UP";
 	media-type		"802.11";
-	action "service dhclient quietstart $subsystem";
+	action "service dhclient quietstart $subsystem; service rtsold quietrestart";
 };
 
 # An entry like this might be in a different file, but is included here

Unfortunately, skipping configuration of defaultroute and automatically starting rtsold beside dhcp are depends on administrator.
We're not allowed to do this automatically for user.

I hope that most people testing this solution are not only FreeBSD users but also administrators, and will be able to improve their IPv6-only testing experience this way. You have to admit, waiting for the next periodic RA after plugging in an Ethernet cable can be stressful, as can waiting for the defaultroute script to timeout while booting a laptop. I also believe servers can start once the system has IPv6 network access, so I'll add these fixes to my local repository until the FreeBSD developers resolve the issues.

None of these issues is directly related to this review, and these shortcomings have been present in FreeBSD for a long time, as initially IPv6 connections were marginal, experimental, and didn't provide the real network access services needed. Today, in my opinion, things are different.

So definitely, I haven’t identified any real drawbacks in the proposed solution. Finally, I would like to express my sincere gratitude - I truly appreciate the opportunity to test this upcoming feature.

Manpage changes LGTM! Thanks for tagging me!

sbin/dhclient/dhcp-options.5
375

we may want to alphasort this list in a later commit.

sbin/dhclient/inet6.c
2

Please remove this hyphen. It was for a long abandoned WIP parser that was deemed infeasible. I think I have removed it from all of our style guides, please let me know if this is not the case.

Also, check style.9 or the https://docs.freebsd.org/en/articles/license-guide/ . Now that SPDX license identifiers are an ISO standard, the project's preferred license is to omit the body of the license text, that way we don't have to keep defining it in every singe file.

This revision is now accepted and ready to land.Sun, May 3, 6:47 PM

Are there some DHCP servers in the wild which set this option? Is it important to support it? It's not immediately obvious to me why this handling is worth the extra complexity.

sbin/dhclient/clparse.c
109

How does this comment relate to the code? That is, what does "IPv4-requiring" really mean?

sbin/dhclient/dhclient.c
548

I don't love that we create a global pointer which refers to a stack variable. Can we make ss global too?

sbin/dhclient/inet6.c
49

Some brief comment here explaining what exactly we're "checking" would be helpful.

75
83

Same here? What is this checking? I cannot easily tell by reading the function.

126

Indentation looks wrong.

sbin/dhclient/options.c
106

I don't think these comments are useful.

pouria marked 6 inline comments as done.
pouria edited the test plan for this revision. (Show Details)

Address @ziaee and @markj comments.

This revision now requires review to proceed.Mon, May 4, 9:59 AM

Are there some DHCP servers in the wild which set this option? Is it important to support it? It's not immediately obvious to me why this handling is worth the extra complexity.

Yes, there are a lot, all the modern OSes supports it, windows, linux, android, ios...
And it definitely worth it.
I've tried my best to implement this feature as simple as possible.

sbin/dhclient/clparse.c
109

How does this comment relate to the code? That is, what does "IPv4-requiring" really mean?

Yes, by definition:

IPv4-requiring host: A host that is not IPv6-only capable and cannot
operate in an IPv6-only network providing NAT64 service.

I kept it simple here.
We could verify kernel support for INET6 with kern.features.inet6, but since dhclient is included in our base, we can reuse the INET6 macro to avoid extra complexity.
Of course, the more accurate method would be checking via sysctl.

The reason for this comment is to explain why this is wrapped inside the INET6 macro.

sbin/dhclient/dhclient.c
548

I don't love that we create a global pointer which refers to a stack variable. Can we make ss global too?

I don't like it either.
Most of my time on this revision was spent on this part because I had to follow the existing style for cap_rights_init, and unfortunately, I'm not an expert on cap_rights.
We could make it global, but I feel it would probably become uglier than it already is.

I'd appreciate any suggestions you might have. I can't think of a cleaner approach.

sbin/dhclient/inet6.c
75

I saw this style of checking negative conditions on some of the Gleb's code.
I thought it improve readability :)
Done.

A couple of hours ago, I watched an excellent talk by Ondřej Caletka during the IPv6 Working Group session at RIPE 92 in Edinburgh [1]. I am linking it below especially for those who are still not convinced whether this feature should be implemented and how soon.

First of all, I would like to report that the patch works flawlessly for me. With the patch from review D56797, I do not observe any noticeable delay during boot. I have also been testing it on my laptop connected to both wired and wireless networks at my workplace, where the “IPv6-Only Preferred” option is deployed. So far, it behaves correctly — after receiving the first DHCPOFFER containing this option, it stops IPv4 lease negotiation as expected.

After watching the RIPE presentation linked below, I have a few remarks. I am not in a position to demand changes since I am not a reviewer, but perhaps these points are worth considering:

  1. Perhaps dhclient should support an explicit opt-in flag for this behavior initially. For example, the implemented feature could become active only when using something like dhclient_flags="-6" (or another dedicated option). In the future, this behavior may become the default, but FreeBSD users tend to be rather conservative. Because of that, it might also be worth keeping a way to ignore this DHCP option and continue normal IPv4 DHCP lease negotiation if desired.
  2. Making this feature non-default initially — similarly to Linux NetworkManager — could help with earlier MFC and potentially allow it to land in FreeBSD 15.2 sooner. This would be a nice step toward modernising the FreeBSD networking stack. Once FreeBSD gains CLAT support, enabling this behavior by default would make even more sense.

[1] https://ripe92.ripe.net/programme/meeting-plan/sessions/94/PBHCLK/