Skip to content

Commit 360a57c

Browse files
stephengroatstephengroat-dd
authored andcommitted
ndp: fix macOS IPv6 compatibility by using link-local source addresses
macOS ignores NDP packets that don't originate from link-local addresses, causing IPv6 connectivity issues with odhcpd. This change ensures NDP packets (Neighbor Advertisements and ICMP Echo Requests) are sent using link-local source addresses for RFC 4861 compliance. Changes: - Add odhcpd_send_with_src() to allow explicit source address control - Add odhcpd_get_interface_linklocal_addr() to find link-local addresses - Update send_na() and ping6() to prefer link-local source addresses - Maintain backward compatibility with fallback behavior Fixes: openwrt/openwrt#7561
1 parent 4308384 commit 360a57c

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

src/ndp.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,21 @@ static void ping6(struct in6_addr *addr,
286286
struct icmp6_hdr echo = { .icmp6_type = ICMP6_ECHO_REQUEST };
287287
struct iovec iov = { .iov_base = &echo, .iov_len = sizeof(echo) };
288288
char ipbuf[INET6_ADDRSTRLEN];
289+
struct in6_addr src_addr;
289290

290291
inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
291292
syslog(LOG_DEBUG, "Pinging for %s on %s", ipbuf, iface->name);
292293

293294
netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, true);
294-
odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
295+
296+
/* Use link-local address as source for consistency and macOS compatibility */
297+
if (odhcpd_get_interface_linklocal_addr(iface, &src_addr) == 0) {
298+
odhcpd_send_with_src(iface->ndp_ping_fd, &dest, &iov, 1, iface, &src_addr);
299+
} else {
300+
/* Fallback to default behavior if no link-local address is available */
301+
odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
302+
}
303+
295304
netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, false);
296305
}
297306

@@ -306,6 +315,7 @@ static void send_na(struct in6_addr *to_addr,
306315
struct nd_opt_hdr *opt = (struct nd_opt_hdr*) &pbuf[sizeof(struct nd_neighbor_advert)];
307316
struct iovec iov = { .iov_base = &pbuf, .iov_len = sizeof(pbuf) };
308317
char ipbuf[INET6_ADDRSTRLEN];
318+
struct in6_addr src_addr;
309319

310320
memset(pbuf, 0, sizeof(pbuf));
311321
adv->nd_na_hdr = (struct icmp6_hdr) {
@@ -319,7 +329,13 @@ static void send_na(struct in6_addr *to_addr,
319329
inet_ntop(AF_INET6, to_addr, ipbuf, sizeof(ipbuf));
320330
syslog(LOG_DEBUG, "Answering NS to %s on %s", ipbuf, iface->ifname);
321331

322-
odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
332+
/* Use link-local address as source for macOS compatibility (RFC 4861 compliance) */
333+
if (odhcpd_get_interface_linklocal_addr(iface, &src_addr) == 0) {
334+
odhcpd_send_with_src(iface->ndp_ping_fd, &dest, &iov, 1, iface, &src_addr);
335+
} else {
336+
/* Fallback to default behavior if no link-local address is available */
337+
odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
338+
}
323339
}
324340

325341
/* Handle solicitations */

src/odhcpd.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,14 @@ int odhcpd_get_flags(const struct interface *iface)
189189
ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
190190
struct iovec *iov, size_t iov_len,
191191
const struct interface *iface)
192+
{
193+
return odhcpd_send_with_src(socket, dest, iov, iov_len, iface, NULL);
194+
}
195+
196+
/* Forwards a packet on a specific interface with optional source address */
197+
ssize_t odhcpd_send_with_src(int socket, struct sockaddr_in6 *dest,
198+
struct iovec *iov, size_t iov_len,
199+
const struct interface *iface, const struct in6_addr *src_addr)
192200
{
193201
/* Construct headers */
194202
uint8_t cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))] = {0};
@@ -210,6 +218,10 @@ ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
210218
struct in6_pktinfo *pktinfo = (struct in6_pktinfo*)CMSG_DATA(chdr);
211219
pktinfo->ipi6_ifindex = iface->ifindex;
212220

221+
/* Set source address if provided */
222+
if (src_addr)
223+
pktinfo->ipi6_addr = *src_addr;
224+
213225
/* Also set scope ID if link-local */
214226
if (IN6_IS_ADDR_LINKLOCAL(&dest->sin6_addr)
215227
|| IN6_IS_ADDR_MC_LINKLOCAL(&dest->sin6_addr))
@@ -254,6 +266,21 @@ static int odhcpd_get_linklocal_interface_address(int ifindex, struct in6_addr *
254266
return ret;
255267
}
256268

269+
/* Get link-local address for NDP operations - prefer this over ULA for macOS compatibility */
270+
int odhcpd_get_interface_linklocal_addr(const struct interface *iface, struct in6_addr *addr)
271+
{
272+
/* First try to get link-local address from interface addresses */
273+
for (size_t i = 0; i < iface->addr6_len; ++i) {
274+
if (IN6_IS_ADDR_LINKLOCAL(&iface->addr6[i].addr.in6)) {
275+
*addr = iface->addr6[i].addr.in6;
276+
return 0;
277+
}
278+
}
279+
280+
/* Fallback to the existing method */
281+
return odhcpd_get_linklocal_interface_address(iface->ifindex, addr);
282+
}
283+
257284
/*
258285
* DNS address selection criteria order :
259286
* - use IPv6 address with valid lifetime if none is yet selected

src/odhcpd.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,13 @@ void odhcpd_process(struct odhcpd_event *event);
437437
ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
438438
struct iovec *iov, size_t iov_len,
439439
const struct interface *iface);
440+
ssize_t odhcpd_send_with_src(int socket, struct sockaddr_in6 *dest,
441+
struct iovec *iov, size_t iov_len,
442+
const struct interface *iface, const struct in6_addr *src_addr);
440443
int odhcpd_get_interface_dns_addr(const struct interface *iface,
441444
struct in6_addr *addr);
445+
int odhcpd_get_interface_linklocal_addr(const struct interface *iface,
446+
struct in6_addr *addr);
442447
int odhcpd_get_interface_config(const char *ifname, const char *what);
443448
int odhcpd_get_mac(const struct interface *iface, uint8_t mac[6]);
444449
int odhcpd_get_flags(const struct interface *iface);

0 commit comments

Comments
 (0)