From 969a095a04514726ff01facdf8525c7aa21e3019 Mon Sep 17 00:00:00 2001 From: Massimo Mazzariol Date: Sun, 30 Aug 2026 22:47:49 +0200 Subject: [PATCH 1/2] interface: fix IPv6 hop-limit socket options IPV6_MULTICAST_HOPS and IPV6_UNICAST_HOPS expect an int-sized option value on Linux. interface_init_socket() currently passes a uint8_t value and sizeof(uint8_t), causing setsockopt() to fail with EINVAL. The failure is ignored, leaving IPv6 multicast packets at the default hop limit of 1 and unicast packets at the default hop limit of 64. Use the existing int-sized ittl value instead. This restores the int-sized socket option used before the global socket refactor and sets both IPv6 hop limits to 255 as intended. Tested on OpenWrt 25.12.5: - multicast mDNS hop limit: 1 -> 255 - unicast QU response hop limit: 64 -> 255 Signed-off-by: Massimo Mazzariol --- interface.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface.c b/interface.c index 340878e..81b5173 100644 --- a/interface.c +++ b/interface.c @@ -518,8 +518,8 @@ static int interface_init_socket(enum umdns_socket_type type, bool *mcast) local.sin_port = htons(MCAST_PORT); break; case SOCK_MC_IPV6: - setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ittl, sizeof(ittl)); + setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ittl, sizeof(ittl)); setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &no, sizeof(no)); local6.sin6_port = htons(MCAST_PORT); From e868dac6024ffe5ee4423fe164f5c1431a74740c Mon Sep 17 00:00:00 2001 From: Massimo Mazzariol Date: Sun, 30 Aug 2026 22:50:37 +0200 Subject: [PATCH 2/2] dns: send QU responses from the mDNS socket A QU question received on the multicast socket requests a response directly to the querier. The response is still an mDNS response and must originate from UDP port 5353. Since the global socket refactor in 4035fe42, parse_question() switches QU replies to the global unicast socket. That socket is not bound to port 5353, so the reply can use an ephemeral source port even though the destination address and port are correct. Keep QU replies on the mDNS socket and use the querier address only as the destination. Allow multicast interfaces to honor an explicit destination while continuing to use the socket bound to port 5353. This also makes an existing destination argument in the cache refresh path significant. Preserve its historical behavior explicitly: refreshes on multicast interfaces remain multicast-destination QU queries, while unicast interfaces continue to query the cached peer. Hardware validation on OpenWrt 25.12.5 confirmed: - IPv4 QU replies use UDP 5353 -> 5353 with TTL 255 - IPv6 QU replies use UDP 5353 -> 5353 with hop limit 255 - normal multicast responses remain multicast - multicast cache refresh queries remain multicast with the QU bit Signed-off-by: Massimo Mazzariol --- cache.c | 9 ++++++++- dns.c | 5 +---- interface.c | 8 ++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cache.c b/cache.c index 06c4eb6..c9527e2 100644 --- a/cache.c +++ b/cache.c @@ -128,7 +128,14 @@ cache_gc_timer(struct uloop_timeout *timeout) continue; } r->refresh += 50; - dns_send_question(r->iface, (struct sockaddr *)&r->from, r->record, r->type, 0); + /* + * Preserve refresh routing: multicast interfaces send a QU query + * to the mDNS group, while unicast interfaces query the cached peer. + */ + dns_send_question(r->iface, + interface_multicast(r->iface) ? NULL : + (struct sockaddr *)&r->from, + r->record, r->type, 0); } avl_for_each_element_safe(&services, s, avl, t) { diff --git a/dns.c b/dns.c index 6e7ec15..5159302 100644 --- a/dns.c +++ b/dns.c @@ -589,11 +589,8 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc char *host, *host6; /* TODO: Multicast if more than one quarter of TTL has passed */ - if (is_unicast) { + if (is_unicast) to = from; - if (interface_multicast(iface)) - iface = interface_get(iface->name, iface->type | SOCKTYPE_BIT_UNICAST); - } DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name); diff --git a/interface.c b/interface.c index 81b5173..0017084 100644 --- a/interface.c +++ b/interface.c @@ -78,10 +78,8 @@ interface_send_packet4(struct interface *iface, struct sockaddr_in *to, struct i pkti->ipi_ifindex = iface->ifindex; fd = ufd[iface->type].fd; - if (interface_multicast(iface)) { + if (interface_multicast(iface) && !to) { a.sin_addr.s_addr = inet_addr(MCAST_ADDR); - if (to) - fprintf(stderr, "Ignoring IPv4 address for multicast interface\n"); } else { a.sin_addr.s_addr = to->sin_addr.s_addr; a.sin_port = to->sin_port; @@ -121,10 +119,8 @@ interface_send_packet6(struct interface *iface, struct sockaddr_in6 *to, struct pkti->ipi6_ifindex = iface->ifindex; fd = ufd[iface->type].fd; - if (interface_multicast(iface)) { + if (interface_multicast(iface) && !to) { inet_pton(AF_INET6, MCAST_ADDR6, &a.sin6_addr); - if (to) - fprintf(stderr, "Ignoring IPv6 address for multicast interface\n"); } else { a.sin6_addr = to->sin6_addr; }