Page MenuHomeFreeBSD

D56637.id179853.diff
No OneTemporary

D56637.id179853.diff

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

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)

Event Timeline