1*37fd3fcaSPouria Mousavizadeh Tehrani /*
2*37fd3fcaSPouria Mousavizadeh Tehrani * Copyright (c) 2026 Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
3*37fd3fcaSPouria Mousavizadeh Tehrani *
4*37fd3fcaSPouria Mousavizadeh Tehrani * SPDX-License-Identifier: BSD-2-Clause
5*37fd3fcaSPouria Mousavizadeh Tehrani */
6*37fd3fcaSPouria Mousavizadeh Tehrani
7*37fd3fcaSPouria Mousavizadeh Tehrani #include <sys/param.h>
8*37fd3fcaSPouria Mousavizadeh Tehrani #include <sys/ioctl.h>
9*37fd3fcaSPouria Mousavizadeh Tehrani #include <sys/socket.h>
10*37fd3fcaSPouria Mousavizadeh Tehrani
11*37fd3fcaSPouria Mousavizadeh Tehrani #ifndef WITHOUT_NETLINK
12*37fd3fcaSPouria Mousavizadeh Tehrani #include <netlink/netlink.h>
13*37fd3fcaSPouria Mousavizadeh Tehrani #include <netlink/netlink_route.h>
14*37fd3fcaSPouria Mousavizadeh Tehrani #include <netlink/netlink_snl.h>
15*37fd3fcaSPouria Mousavizadeh Tehrani #include <netlink/netlink_snl_route.h>
16*37fd3fcaSPouria Mousavizadeh Tehrani #include <netlink/netlink_snl_route_compat.h>
17*37fd3fcaSPouria Mousavizadeh Tehrani #include <netlink/netlink_snl_route_parsers.h>
18*37fd3fcaSPouria Mousavizadeh Tehrani #endif
19*37fd3fcaSPouria Mousavizadeh Tehrani
20*37fd3fcaSPouria Mousavizadeh Tehrani #include "dhcpd.h"
21*37fd3fcaSPouria Mousavizadeh Tehrani
22*37fd3fcaSPouria Mousavizadeh Tehrani #ifndef WITHOUT_NETLINK
23*37fd3fcaSPouria Mousavizadeh Tehrani /*
24*37fd3fcaSPouria Mousavizadeh Tehrani * Check if the interface has a routable IPv6 address, if true
25*37fd3fcaSPouria Mousavizadeh Tehrani * assume it has IPv6 connectivity.
26*37fd3fcaSPouria Mousavizadeh Tehrani * Returns true if a unicast IPv6 address with non-link-local scope
27*37fd3fcaSPouria Mousavizadeh Tehrani * is found, false otherwise.
28*37fd3fcaSPouria Mousavizadeh Tehrani */
29*37fd3fcaSPouria Mousavizadeh Tehrani static bool
check_ipv6_address(struct snl_state * ss,uint16_t ifindex)30*37fd3fcaSPouria Mousavizadeh Tehrani check_ipv6_address(struct snl_state *ss, uint16_t ifindex)
31*37fd3fcaSPouria Mousavizadeh Tehrani {
32*37fd3fcaSPouria Mousavizadeh Tehrani struct snl_writer nw;
33*37fd3fcaSPouria Mousavizadeh Tehrani struct snl_errmsg_data e = {};
34*37fd3fcaSPouria Mousavizadeh Tehrani struct snl_parsed_addr addr = {};
35*37fd3fcaSPouria Mousavizadeh Tehrani struct nlmsghdr *hdr, *rx_hdr;
36*37fd3fcaSPouria Mousavizadeh Tehrani struct ifaddrmsg *ifahdr;
37*37fd3fcaSPouria Mousavizadeh Tehrani
38*37fd3fcaSPouria Mousavizadeh Tehrani snl_init_writer(ss, &nw);
39*37fd3fcaSPouria Mousavizadeh Tehrani hdr = snl_create_msg_request(&nw, RTM_GETADDR);
40*37fd3fcaSPouria Mousavizadeh Tehrani hdr->nlmsg_flags |= NLM_F_DUMP;
41*37fd3fcaSPouria Mousavizadeh Tehrani ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg);
42*37fd3fcaSPouria Mousavizadeh Tehrani ifahdr->ifa_family = AF_INET6;
43*37fd3fcaSPouria Mousavizadeh Tehrani ifahdr->ifa_index = ifindex;
44*37fd3fcaSPouria Mousavizadeh Tehrani
45*37fd3fcaSPouria Mousavizadeh Tehrani if ((hdr = snl_finalize_msg(&nw)) == NULL || !snl_send_message(ss, hdr))
46*37fd3fcaSPouria Mousavizadeh Tehrani return (false);
47*37fd3fcaSPouria Mousavizadeh Tehrani
48*37fd3fcaSPouria Mousavizadeh Tehrani while ((rx_hdr = snl_read_reply_multi(ss, hdr->nlmsg_seq, &e)) != NULL) {
49*37fd3fcaSPouria Mousavizadeh Tehrani struct sockaddr_in6 *sin6;
50*37fd3fcaSPouria Mousavizadeh Tehrani
51*37fd3fcaSPouria Mousavizadeh Tehrani if (!snl_parse_nlmsg(ss, rx_hdr, &snl_rtm_addr_parser, &addr))
52*37fd3fcaSPouria Mousavizadeh Tehrani continue;
53*37fd3fcaSPouria Mousavizadeh Tehrani
54*37fd3fcaSPouria Mousavizadeh Tehrani if (addr.ifa_address != NULL) {
55*37fd3fcaSPouria Mousavizadeh Tehrani sin6 = (struct sockaddr_in6 *)addr.ifa_address;
56*37fd3fcaSPouria Mousavizadeh Tehrani if (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
57*37fd3fcaSPouria Mousavizadeh Tehrani return (true);
58*37fd3fcaSPouria Mousavizadeh Tehrani }
59*37fd3fcaSPouria Mousavizadeh Tehrani }
60*37fd3fcaSPouria Mousavizadeh Tehrani return (false);
61*37fd3fcaSPouria Mousavizadeh Tehrani }
62*37fd3fcaSPouria Mousavizadeh Tehrani
63*37fd3fcaSPouria Mousavizadeh Tehrani /*
64*37fd3fcaSPouria Mousavizadeh Tehrani * If a default route is found, assume IPv6 connectivity.
65*37fd3fcaSPouria Mousavizadeh Tehrani * Returns true if found, false otherwise.
66*37fd3fcaSPouria Mousavizadeh Tehrani */
67*37fd3fcaSPouria Mousavizadeh Tehrani static bool
check_ipv6_defaultroute(struct snl_state * ss)68*37fd3fcaSPouria Mousavizadeh Tehrani check_ipv6_defaultroute(struct snl_state *ss)
69*37fd3fcaSPouria Mousavizadeh Tehrani {
70*37fd3fcaSPouria Mousavizadeh Tehrani struct snl_writer nw;
71*37fd3fcaSPouria Mousavizadeh Tehrani struct snl_parsed_route r = {};
72*37fd3fcaSPouria Mousavizadeh Tehrani struct in6_addr in6 = IN6ADDR_ANY_INIT;
73*37fd3fcaSPouria Mousavizadeh Tehrani struct nlmsghdr *hdr, *rx_hdr;
74*37fd3fcaSPouria Mousavizadeh Tehrani struct rtmsg *rtmsg;
75*37fd3fcaSPouria Mousavizadeh Tehrani
76*37fd3fcaSPouria Mousavizadeh Tehrani snl_init_writer(ss, &nw);
77*37fd3fcaSPouria Mousavizadeh Tehrani hdr = snl_create_msg_request(&nw, RTM_GETROUTE);
78*37fd3fcaSPouria Mousavizadeh Tehrani rtmsg = snl_reserve_msg_object(&nw, struct rtmsg);
79*37fd3fcaSPouria Mousavizadeh Tehrani rtmsg->rtm_family = AF_INET6;
80*37fd3fcaSPouria Mousavizadeh Tehrani rtmsg->rtm_dst_len = 0;
81*37fd3fcaSPouria Mousavizadeh Tehrani rtmsg->rtm_type = RTN_UNICAST;
82*37fd3fcaSPouria Mousavizadeh Tehrani rtmsg->rtm_flags = RTM_F_PREFIX;
83*37fd3fcaSPouria Mousavizadeh Tehrani snl_add_msg_attr_ip6(&nw, RTA_DST, &in6);
84*37fd3fcaSPouria Mousavizadeh Tehrani
85*37fd3fcaSPouria Mousavizadeh Tehrani if ((hdr = snl_finalize_msg(&nw)) == NULL || !snl_send_message(ss, hdr))
86*37fd3fcaSPouria Mousavizadeh Tehrani return (false);
87*37fd3fcaSPouria Mousavizadeh Tehrani
88*37fd3fcaSPouria Mousavizadeh Tehrani rx_hdr = snl_read_reply(ss, hdr->nlmsg_seq);
89*37fd3fcaSPouria Mousavizadeh Tehrani if (rx_hdr == NULL || rx_hdr->nlmsg_type != NL_RTM_NEWROUTE)
90*37fd3fcaSPouria Mousavizadeh Tehrani return (false);
91*37fd3fcaSPouria Mousavizadeh Tehrani
92*37fd3fcaSPouria Mousavizadeh Tehrani if (!snl_parse_nlmsg(ss, rx_hdr, &snl_rtm_route_parser, &r))
93*37fd3fcaSPouria Mousavizadeh Tehrani return (false);
94*37fd3fcaSPouria Mousavizadeh Tehrani
95*37fd3fcaSPouria Mousavizadeh Tehrani if (r.rta_gw == NULL)
96*37fd3fcaSPouria Mousavizadeh Tehrani return (false);
97*37fd3fcaSPouria Mousavizadeh Tehrani
98*37fd3fcaSPouria Mousavizadeh Tehrani return (true);
99*37fd3fcaSPouria Mousavizadeh Tehrani }
100*37fd3fcaSPouria Mousavizadeh Tehrani #endif
101*37fd3fcaSPouria Mousavizadeh Tehrani
102*37fd3fcaSPouria Mousavizadeh Tehrani bool
check_ipv6_connectivity(uint16_t ifindex __unused)103*37fd3fcaSPouria Mousavizadeh Tehrani check_ipv6_connectivity(uint16_t ifindex __unused)
104*37fd3fcaSPouria Mousavizadeh Tehrani {
105*37fd3fcaSPouria Mousavizadeh Tehrani bool ret = false;
106*37fd3fcaSPouria Mousavizadeh Tehrani
107*37fd3fcaSPouria Mousavizadeh Tehrani #ifndef WITHOUT_NETLINK
108*37fd3fcaSPouria Mousavizadeh Tehrani /* Return true if the interface has a routable ipv6 address */
109*37fd3fcaSPouria Mousavizadeh Tehrani ret = check_ipv6_address(&nl_ss, ifindex);
110*37fd3fcaSPouria Mousavizadeh Tehrani
111*37fd3fcaSPouria Mousavizadeh Tehrani /* A default route using a link-local address may exist. */
112*37fd3fcaSPouria Mousavizadeh Tehrani if (!ret)
113*37fd3fcaSPouria Mousavizadeh Tehrani ret = check_ipv6_defaultroute(&nl_ss);
114*37fd3fcaSPouria Mousavizadeh Tehrani snl_clear_lb(&nl_ss);
115*37fd3fcaSPouria Mousavizadeh Tehrani #endif
116*37fd3fcaSPouria Mousavizadeh Tehrani
117*37fd3fcaSPouria Mousavizadeh Tehrani return (ret);
118*37fd3fcaSPouria Mousavizadeh Tehrani }
119