From 3a92ceafb676a60516045ea44bf370876b21e694 Mon Sep 17 00:00:00 2001 From: koenvelle Date: Wed, 18 Mar 2026 12:03:29 +0100 Subject: [PATCH] fix: pass interface to multicast receive socket on Windows multi-NIC hosts On Windows with multiple network interfaces, newClient creates the multicast receive socket with net.ListenMulticastUDP(udp4, nil, ipv4Addr). The nil interface argument causes IP_ADD_MEMBERSHIP to be issued with INADDR_ANY, which lets Windows choose the multicast group membership interface based on the lowest route metric. On a multi-NIC machine this is often not the interface passed via params.Interface, so the receive socket never sees incoming packets even though queries are sent on the correct interface via setInterface/IP_MULTICAST_IF. The fix passes iface (from params.Interface) through newClient to both ListenMulticastUDP calls so that IP_ADD_MEMBERSHIP is issued for the correct interface from the start. When params.Interface is nil, iface is nil and behaviour is identical to before. Verified on Windows 11 with Ethernet + vEthernet (WSL Hyper-V) adapters: before the fix ReadFromUDP was never called during a 15-second query; after the fix the target board is discovered within 15 ms. Fixes #80 --- client.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index c9806fb..a192c48 100644 --- a/client.go +++ b/client.go @@ -83,7 +83,11 @@ func QueryContext(ctx context.Context, params *QueryParam) error { params.Logger = log.Default() } // Create a new client - client, err := newClient(!params.DisableIPv4, !params.DisableIPv6, params.Logger) + // Pass params.Interface so the multicast receive socket joins the correct + // interface. On Windows with multiple NICs, using nil here causes + // IP_ADD_MEMBERSHIP with INADDR_ANY, which joins the wrong interface and + // results in zero packets received (see issue #80). + client, err := newClient(!params.DisableIPv4, !params.DisableIPv6, params.Logger, params.Interface) if err != nil { return err } @@ -143,8 +147,11 @@ type client struct { } // NewClient creates a new mdns Client that can be used to query -// for records -func newClient(v4 bool, v6 bool, logger *log.Logger) (*client, error) { +// for records. +// iface specifies the network interface to join the multicast group on. +// A nil iface lets the OS choose (system default), which is correct for +// single-NIC machines but unreliable on Windows with multiple NICs. +func newClient(v4 bool, v6 bool, logger *log.Logger, iface *net.Interface) (*client, error) { if !v4 && !v6 { return nil, fmt.Errorf("Must enable at least one of IPv4 and IPv6 querying") } @@ -174,15 +181,18 @@ func newClient(v4 bool, v6 bool, logger *log.Logger) (*client, error) { return nil, fmt.Errorf("failed to bind to any unicast udp port") } - // Establish multicast connections + // Establish multicast connections. + // iface is passed explicitly so that IP_ADD_MEMBERSHIP is issued for the + // correct interface on multi-NIC Windows hosts. When iface is nil the + // behaviour is identical to before (OS picks based on routing table). if v4 { - mconn4, err = net.ListenMulticastUDP("udp4", nil, ipv4Addr) + mconn4, err = net.ListenMulticastUDP("udp4", iface, ipv4Addr) if err != nil { logger.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) } } if v6 { - mconn6, err = net.ListenMulticastUDP("udp6", nil, ipv6Addr) + mconn6, err = net.ListenMulticastUDP("udp6", iface, ipv6Addr) if err != nil { logger.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) }