Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F161558109
D56637.id179853.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
D56637.id179853.diff
View Options
diff --git a/sbin/dhclient/Makefile b/sbin/dhclient/Makefile
--- a/sbin/dhclient/Makefile
+++ b/sbin/dhclient/Makefile
@@ -36,7 +36,7 @@
PACKAGE=dhclient
SRCS= dhclient.c clparse.c alloc.c dispatch.c hash.c bpf.c options.c \
tree.c conflex.c errwarn.c inet.c packet.c convert.c tables.c \
- parse.c privsep.c
+ parse.c privsep.c inet6.c
PROG= dhclient
SCRIPTS=dhclient-script
@@ -44,6 +44,14 @@
dhclient-script.8
LIBADD= util
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+
+.if ${MK_NETLINK_SUPPORT} == "no"
+CFLAGS+=-DWITHOUT_NETLINK
+.endif
+
.if ${MK_DYNAMICROOT} == "no"
.warning ${PROG} built without libcasper support
.elif ${MK_CASPER} != "no" && !defined(RESCUE)
diff --git a/sbin/dhclient/clparse.c b/sbin/dhclient/clparse.c
--- a/sbin/dhclient/clparse.c
+++ b/sbin/dhclient/clparse.c
@@ -104,6 +104,15 @@
[top_level_config.requested_option_count++] = DHO_DOMAIN_SEARCH;
top_level_config.requested_options
[top_level_config.requested_option_count++] = DHO_INTERFACE_MTU;
+#ifdef INET6
+ /*
+ * RFC 8925 sec 3.2: The DHCPv4 client on an IPv4-requiring host MUST
+ * NOT include the IPv6-Only Preferred option code in the Parameter
+ * Request List.
+ */
+ top_level_config.requested_options
+ [top_level_config.requested_option_count++] = DHO_IPV6_ONLY;
+#endif
if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) {
do {
diff --git a/sbin/dhclient/dhclient.c b/sbin/dhclient/dhclient.c
--- a/sbin/dhclient/dhclient.c
+++ b/sbin/dhclient/dhclient.c
@@ -128,6 +128,10 @@
static int unknown_ok = 1;
static int routefd;
+#ifndef WITHOUT_NETLINK
+struct snl_state *nl_ss_route = NULL;
+#endif
+
struct interface_info *ifi;
int findproto(char *, int);
@@ -534,6 +538,16 @@
if (caph_rights_limit(routefd, &rights) < 0)
error("can't limit route socket: %m");
+#ifndef WITHOUT_NETLINK
+ struct snl_state ss;
+ if (!snl_init(&ss, NETLINK_ROUTE))
+ error("can't open netlink socket");
+ cap_rights_init(&rights, CAP_EVENT, CAP_READ, CAP_WRITE);
+ if (caph_rights_limit(ss.fd, &rights) < 0)
+ error("can't limit netlink route socket: %m");
+ nl_ss_route = &ss;
+#endif
+
endpwent();
setproctitle("%s", ifi->name);
@@ -787,6 +801,36 @@
note("DHCPACK from %s", piaddr(packet->client_addr));
+ /*
+ * RFC 8925, sec 3.2: If the DHCPv4 server responds with a DHCPACK that
+ * includes the IPv6-Only Preferred option, the client's behavior
+ * depends on the client's state. If the client is in the INIT-REBOOT
+ * state, it SHOULD stop the DHCPv4 configuration process
+ */
+ if (packet->options[DHO_IPV6_ONLY].data != NULL &&
+ ip->client->state == S_REBOOTING) {
+ if (check_ipv6_connectivity(ip->index)) {
+ struct timespec stop_time;
+ uint32_t v6wait = getULong(packet->options[DHO_IPV6_ONLY].data);
+
+ if (v6wait < UINT32_MAX) {
+ struct timespec v6wait_left = {
+ .tv_sec = (time_t)v6wait,
+ .tv_nsec = 0
+ };
+ timespecadd(&time_now, &v6wait_left, &stop_time);
+ add_timeout_timespec(stop_time, state_reboot, ip);
+ } else
+ ip->client->state = S_INIT;
+ cancel_timeout(send_request, ip);
+ note("IPv6-Only Preferred option received (%u seconds), abort", v6wait);
+ go_daemon();
+ return;
+ } else
+ note("IPv6-Only Preferred option received,"
+ "but we can't verify IPv6 connectivity, ignore");
+ }
+
lease = packet_to_lease(packet);
if (!lease) {
note("packet_to_lease failed.");
@@ -1083,6 +1127,34 @@
/* Record the medium under which this lease was offered. */
lease->medium = ip->client->medium;
+ /*
+ * RFC 8925, sec 3.2: If the client includes the IPv6-Only Preferred option
+ * code in the Parameter Request List and the DHCPOFFER message from
+ * the server contains a valid IPv6-Only Preferred option, the client
+ * SHOULD NOT request the IPv4 address provided in the DHCPOFFER.
+ */
+ if (packet->options[DHO_IPV6_ONLY].data != NULL) {
+ if (check_ipv6_connectivity(ip->index)) {
+ uint32_t v6wait = getULong(packet->options[DHO_IPV6_ONLY].data);
+
+ if (v6wait < UINT32_MAX) {
+ struct timespec v6wait_left = {
+ .tv_sec = (time_t)v6wait,
+ .tv_nsec = 0
+ };
+ timespecadd(&time_now, &v6wait_left, &stop_time);
+ add_timeout_timespec(stop_time, state_reboot, ip);
+ } else
+ ip->client->state = S_INIT;
+ cancel_timeout(send_discover, ip);
+ note("IPv6-Only Preferred option received (%u seconds), abort", v6wait);
+ go_daemon();
+ return;
+ } else
+ note("IPv6-Only Preferred option received,"
+ "but we can't verify IPv6 connectivity, ignore");
+ }
+
/* Send out an ARP Request for the offered IP address. */
script_init("ARPSEND", lease->medium);
script_write_params("check_", lease);
@@ -2682,6 +2754,7 @@
case DHO_SIP_SERVERS:
case DHO_V_I_VENDOR_CLASS:
case DHO_V_I_VENDOR_OPTS:
+ case DHO_IPV6_ONLY:
case DHO_END:
return (1);
case DHO_CLASSLESS_ROUTES:
diff --git a/sbin/dhclient/dhcp-options.5 b/sbin/dhclient/dhcp-options.5
--- a/sbin/dhclient/dhcp-options.5
+++ b/sbin/dhclient/dhcp-options.5
@@ -372,6 +372,12 @@
To specify the default route, use the
.Ic routers
option.
+.It Ic option ipv6only Ar uint32 ;
+This option specifies the number of seconds for which the client should disable
+DHCPv4 configuration provided by the DHCP server.
+The IPv6-Only Preferred option will check if the host is IPv6-Only capable or
+not.
+The time is specified as a 32-bit unsigned integer.
.El
.Ss Link Layer Parameters per Interface
.Bl -tag -width indent
@@ -596,7 +602,7 @@
.Xr dhclient 8 ,
.Xr dhcpd 8
.Rs
-.%R "RFC 2131, RFC 2132, RFC 3769"
+.%R "RFC 2131, RFC 2132, RFC 3769, RFC 8925"
.Re
.Sh AUTHORS
.An -nosplit
diff --git a/sbin/dhclient/dhcp.h b/sbin/dhclient/dhcp.h
--- a/sbin/dhclient/dhcp.h
+++ b/sbin/dhclient/dhcp.h
@@ -92,6 +92,8 @@
extensions field). */
#define DHCP_OPTIONS_COOKIE "\143\202\123\143"
+#define MIN_V6ONLY_WAIT 300 /* RFC 8925 */
+
/* DHCP Option codes: */
#define DHO_PAD 0
@@ -170,6 +172,7 @@
#define DHO_STREETTALK_SERVER 75
#define DHO_STREETTALK_DA_SERVER 76
#define DHO_DHCP_USER_CLASS_ID 77
+#define DHO_IPV6_ONLY 108
#define DHO_URL 114
#define DHO_DOMAIN_SEARCH 119
#define DHO_SIP_SERVERS 120
diff --git a/sbin/dhclient/dhcpd.h b/sbin/dhclient/dhcpd.h
--- a/sbin/dhclient/dhcpd.h
+++ b/sbin/dhclient/dhcpd.h
@@ -54,6 +54,10 @@
#include <net/if_dl.h>
#include <net/route.h>
+#ifndef WITHOUT_NETLINK
+#include <netlink/netlink_snl.h>
+#endif
+
#include <netinet/in.h>
#include <arpa/inet.h>
@@ -357,6 +361,9 @@
int addr_eq(struct iaddr, struct iaddr);
char *piaddr(struct iaddr);
+/* inet6.c */
+bool check_ipv6_connectivity(uint16_t ifindex);
+
/* dhclient.c */
extern cap_channel_t *capsyslog;
extern const char *path_dhclient_conf;
@@ -372,6 +379,10 @@
extern struct interface_info *ifi;
+#ifndef WITHOUT_NETLINK
+extern struct snl_state *nl_ss_route;
+#endif
+
void dhcpoffer(struct packet *);
void dhcpack(struct packet *);
void dhcpnak(struct packet *);
diff --git a/sbin/dhclient/inet6.c b/sbin/dhclient/inet6.c
new file mode 100644
--- /dev/null
+++ b/sbin/dhclient/inet6.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2026 Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#ifndef WITHOUT_NETLINK
+#include <netlink/netlink.h>
+#include <netlink/netlink_route.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_route.h>
+#include <netlink/netlink_snl_route_compat.h>
+#include <netlink/netlink_snl_route_parsers.h>
+#endif
+
+#include "dhcpd.h"
+
+#ifndef WITHOUT_NETLINK
+/*
+ * Check if the interface has a routable IPv6 address, if true
+ * assume it has IPv6 connectivity.
+ * Returns true if a unicast IPv6 address with non-link-local scope
+ * is found, false otherwise.
+ */
+static bool
+check_ipv6_address(struct snl_state *ss, uint16_t ifindex)
+{
+ struct snl_writer nw;
+ struct snl_errmsg_data e = {};
+ struct snl_parsed_addr addr = {};
+ struct nlmsghdr *hdr, *rx_hdr;
+ struct ifaddrmsg *ifahdr;
+
+ snl_init_writer(ss, &nw);
+ hdr = snl_create_msg_request(&nw, RTM_GETADDR);
+ hdr->nlmsg_flags |= NLM_F_DUMP;
+ ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg);
+ ifahdr->ifa_family = AF_INET6;
+ ifahdr->ifa_index = ifindex;
+
+ if ((hdr = snl_finalize_msg(&nw)) == NULL || !snl_send_message(ss, hdr))
+ return (false);
+
+ while ((rx_hdr = snl_read_reply_multi(ss, hdr->nlmsg_seq, &e)) != NULL) {
+ struct sockaddr_in6 *sin6;
+
+ if (!snl_parse_nlmsg(ss, rx_hdr, &snl_rtm_addr_parser, &addr))
+ continue;
+
+ if (addr.ifa_address != NULL) {
+ sin6 = (struct sockaddr_in6 *)addr.ifa_address;
+ if (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/*
+ * If a default route is found, assume IPv6 connectivity.
+ * Returns true if found, false otherwise.
+ */
+static bool
+check_ipv6_defaultroute(struct snl_state *ss)
+{
+ struct snl_writer nw;
+ struct snl_parsed_route r = {};
+ struct in6_addr in6 = IN6ADDR_ANY_INIT;
+ struct nlmsghdr *hdr, *rx_hdr;
+ struct rtmsg *rtmsg;
+
+ snl_init_writer(ss, &nw);
+ hdr = snl_create_msg_request(&nw, RTM_GETROUTE);
+ rtmsg = snl_reserve_msg_object(&nw, struct rtmsg);
+ rtmsg->rtm_family = AF_INET6;
+ rtmsg->rtm_dst_len = 0;
+ rtmsg->rtm_type = RTN_UNICAST;
+ rtmsg->rtm_flags = RTM_F_PREFIX;
+ snl_add_msg_attr_ip6(&nw, RTA_DST, &in6);
+
+ if ((hdr = snl_finalize_msg(&nw)) == NULL || !snl_send_message(ss, hdr))
+ return (false);
+
+ rx_hdr = snl_read_reply(ss, hdr->nlmsg_seq);
+ if (rx_hdr == NULL || rx_hdr->nlmsg_type != NL_RTM_NEWROUTE)
+ return (false);
+
+ if (!snl_parse_nlmsg(ss, rx_hdr, &snl_rtm_route_parser, &r))
+ return (false);
+
+ if (r.rta_gw == NULL)
+ return (false);
+
+ return (true);
+}
+#endif
+
+bool
+check_ipv6_connectivity(uint16_t ifindex __unused)
+{
+#ifndef WITHOUT_NETLINK
+ if (nl_ss_route == NULL)
+ return (false);
+
+ /* Return true if the interface has a routable ipv6 address */
+ if (check_ipv6_address(nl_ss_route, ifindex))
+ return (true);
+
+ /* A default route using a link-local address may exist. */
+ if (check_ipv6_defaultroute(nl_ss_route))
+ return (true);
+#endif
+ return (false);
+}
diff --git a/sbin/dhclient/options.c b/sbin/dhclient/options.c
--- a/sbin/dhclient/options.c
+++ b/sbin/dhclient/options.c
@@ -59,6 +59,7 @@
int find_search_domain_name_len(struct option_data *option, size_t *offset);
void expand_search_domain_name(struct option_data *option, size_t *offset,
unsigned char **domain_search);
+void ipv6_only_option(struct packet *packet);
/*
@@ -99,10 +100,12 @@
sizeof(packet->raw->sname));
}
- /* Expand DHCP Domain Search option. */
+ /* Process options */
if (packet->options_valid) {
expand_domain_search(packet);
+ ipv6_only_option(packet);
}
+
}
/*
@@ -372,6 +375,69 @@
}
}
+/*
+ * process ipv6_only option.
+ * See: RFC 8925
+ */
+void
+ipv6_only_option(struct packet *packet)
+{
+ struct option_data *option;
+ struct client_config *config;
+ uint32_t val;
+ bool requested;
+
+ if (packet->options[DHO_IPV6_ONLY].data == NULL)
+ return;
+
+ option = &packet->options[DHO_IPV6_ONLY];
+ config = packet->interface->client->config;
+
+ /*
+ * RFC 8925, sec 3.1: The client MUST ignore the IPv6-Only Preferred
+ * option if the length field value is not 4.
+ */
+ if (option->len != 4) {
+ warning("IPv6-Only preferred option length is invalid");
+ goto bad;
+ }
+ val = getULong(option->data);
+
+ /*
+ * RFC 8925, sec 3.2: If the client did not include the IPv6-Only Preferred
+ * option code in the Parameter Request List in the DHCPDISCOVER or
+ * DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option
+ * in any messages received from the server.
+ */
+ requested = false;
+ for (int i = 0; i < config->requested_option_count; i++) {
+ if (config->requested_options[i] == DHO_IPV6_ONLY) {
+ requested = true;
+ break;
+ }
+ }
+ if (!requested) {
+ note("Unwanted IPv6-Only Preferred option received, ignore it");
+ goto bad;
+ }
+
+ /*
+ * If the IPv6-Only Preferred option returned by the server contains
+ * a value greater than or equal to MIN_V6ONLY_WAIT, the client SHOULD
+ * set the V6ONLY_WAIT timer to that value.
+ * Otherwise, the client SHOULD set the V6ONLY_WAIT timer
+ * to MIN_V6ONLY_WAIT.
+ */
+ if (val < MIN_V6ONLY_WAIT)
+ putULong(option->data, MIN_V6ONLY_WAIT);
+
+ return;
+bad:
+ free(option->data);
+ option->len = 0;
+ option->data = NULL;
+}
+
/*
* cons options into a big buffer, and then split them out into the
* three separate buffers if needed. This allows us to cons up a set of
diff --git a/sbin/dhclient/tables.c b/sbin/dhclient/tables.c
--- a/sbin/dhclient/tables.c
+++ b/sbin/dhclient/tables.c
@@ -173,7 +173,7 @@
{ "option-105", "X", &dhcp_universe, 105 },
{ "option-106", "X", &dhcp_universe, 106 },
{ "option-107", "X", &dhcp_universe, 107 },
- { "option-108", "X", &dhcp_universe, 108 },
+ { "ipv6only", "L", &dhcp_universe, 108 },
{ "option-109", "X", &dhcp_universe, 109 },
{ "option-110", "X", &dhcp_universe, 110 },
{ "option-111", "X", &dhcp_universe, 111 },
@@ -403,11 +403,12 @@
DHO_DHCP_USER_CLASS_ID,
DHO_DOMAIN_SEARCH,
DHO_URL,
+ DHO_IPV6_ONLY,
/* Presently-undefined options... */
62, 63, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
- 106, 107, 108, 109, 110, 111, 112, 113, 115, 116, 117,
+ 106, 107, 109, 110, 111, 112, 113, 115, 116, 117,
118, 120, 122, 123, 124, 125, 126, 127, 128, 129, 130,
131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jul 5, 8:49 PM (7 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34723370
Default Alt Text
D56637.id179853.diff (13 KB)
Attached To
Mode
D56637: dhclient(8): Add support for IPv6-Only option (RFC 8925)
Attached
Detach File
Event Timeline
Log In to Comment