1

I have been having issues trying to get JmDNS working on Mac OS X. The symptom is that I can discover any services on the network except the ones on my own computer. It doesn't matter if they are on localhost or on a virtual machine running on my computer - in either case, they just don't come back from the list call.

I managed to condense what we're doing down to a test which passes on Windows but fails on Mac OS X. Now the problem is that I can't figure out where the problem is.

@Test
public void testAdvertisingOverLoopback() throws Exception
{
    // happens on any address but loopback is the strangest
    InetAddress address = InetAddress.getLoopbackAddress();
    String type = "_test._tcp.local.";
    String name = "test-service";
    int port = 9999;
    Map<String, String> properties = ImmutableMap.of("key", "value");

    // simulate the service starting up. issue also occurs in separate VMs
    try (JmDNS serviceDns = JmDNS.create(address))
    {
        serviceDns.registerService(ServiceInfo.create(type, name, port,
                                   0, 0, properties));

        try (JmDNS clientDns = JmDNS.create(address))
        {
            ServiceInfo[] services = clientDns.list(type);

            // One of the entries should:
            assertThat(services, is(arrayContaining(allOf(

                // Contain an address which matches the one we advertised (culling those which might
                // be registered by other tests which happen to run at the same time.)
                hasProperty("inetAddresses", arrayContaining(sameAddressAs(address))),

                // Match the parameters we specified in the call to list.
                hasProperty("application", equalTo("test")),
                hasProperty("protocol", equalTo("tcp")),
                hasProperty("domain", equalTo("local")),

                // Match the info we advertised.
                hasProperty("port", equalTo(9999)),
                hasCustomProperty("key", "value")
            ))));
        }
    }
}

private static Matcher<InetAddress> sameAddressAs(final InetAddress address)
{
    return new TypeSafeMatcher<InetAddress>()
    {
        @Override
        protected boolean matchesSafely(InetAddress inetAddress)
        {
            return Arrays.equals(address.getAddress(), inetAddress.getAddress());
        }

        @Override
        public void describeTo(Description description)
        {
            description.appendText("same address as ");
            description.appendValue(address.getHostAddress());
        }
    };
}

private static Matcher<ServiceInfo> hasCustomProperty(final String key,
                                                      final String value)
{
    return new TypeSafeMatcher<ServiceInfo>()
    {
        @Override
        protected boolean matchesSafely(ServiceInfo serviceInfo)
        {
            return value.equals(serviceInfo.getPropertyString(key));
        }

        @Override
        public void describeTo(Description description)
        {
            description.appendText("has custom mDNS property ");
            description.appendValue(key);
            description.appendText(" = ");
            description.appendValue(value);
        }
    };
}

In the debugger, I can see that it is not binding the socket to any particular address, just a particular port. But it is then setting it to a specific interface.

What I see in Wireshark is that the packets are coming out from my machine's public IP (the address of en0) even though lo0 is the interface I'm using for the test. I also see both the query and the response packets. The responses are coming back.

But then on the Java side, I see it calling DatagramSocket#receive(DatagramPacket) and never getting a packet.

(I also spent half the day looking for alternatives for JmDNS, but it looks like the other libraries which are claiming to be replacements for it can't actually do multicast yet, which makes them a bit pointless. :( )

What is going on here?

Hakanai
  • 12,010
  • 10
  • 62
  • 132

1 Answers1

1

What is going on is that the built-in zeroconf service in OS X is getting the packets.

JmDNS assumes that it is the only daemon running on the machine listening on that port. Because the code it's using deliberately binds to 0.0.0.0, no exception is thrown about the port being in use (apparently this is a "feature" of the Socket API.)

For Windows, this works fine, because there is never another zeroconf daemon running.

For Mac OS X, it is guaranteed to fail, because the built-in one is always running.

I guess that on Linux, you would get mixed results, depending on which distro you're running and which services you have installed.

The solution to the issue I'm working on is to make a completely different API on top of CFNetServices.

Hakanai
  • 12,010
  • 10
  • 62
  • 132
  • "For Windows, this works fine, because there is never another zeroconf daemon running." Apparently not anymore with Windows 10: https://www.slightfuture.com/technote/windows-mdns-dnssd – Grodriguez Apr 06 '16 at 11:26
  • Reading your answer, I was trying to disable the mDNS daemons according to: http://www.dgkapps.com/blog/osx-tips/osx-tips-turn-off-disable-bonjour-from-the-command-line/ and also playing around with the configurations. Although the deamons seem to be down (`ps aux | grep mDNS`) they still seem to interfere with the jmDNS library - any thoughts? – Wingo Oct 31 '17 at 19:25
  • I went the other way entirely - disabled jmDNS for macOS, and used the system APIs to register my records. – Hakanai Nov 01 '17 at 03:32