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