2009-03-24

Registering a service with DNS-SD from Java

DNS-based service discovery (DNS-SD, but better known as Rendezvous, Bonjour, or ZeroConf) is a great way for services to announce themselves, and for potential clients to see what services are available. Mac OS has mDNSResponder, Linux has Avahi, and Windows – as far as I know – has its own competing alternatives.

I've played with this stuff a few times in the past, but never really needed it. Recently though, I wrote a small web service that will mainly be used from Mac web browsers on the same network. What's the easiest way of finding the server? Probably the fact that registered _http._tcp services automatically appear in both Camino and Safari's bookmarks menus. Unfortunately, Firefox does not support anything like this, even on Linux. Nor, as far as I'm aware, does Chrome. (Epiphany does, though, if you're looking for a Linux browser with DNS-SD support.)

So. What's the easiest way to get a Java HTTP service registered?

avahi-publish(1)

Since my server is running on Linux, and I have the avahi-utils package installed, avahi-publish(1) is a convenient choice. Given my ProcessUtilities class, it's just one extra line to spawn a child that publishes news of my service. And, conveniently, that child dies when I die, so I don't need to do anything special about deregistering. Normal process semantics take care of things well enough.

Here's the single line it took to get my service (called "mp3d") registered:

ProcessUtilities.spawn(null,
    "/usr/bin/avahi-publish", "-f",
        "-s", "mp3d", "_http._tcp", Integer.toString(portNumber));

Pros: trivial to use, from any language.

Cons: Linux-only.

Avahi bindings

I did look at Avahi's bindings, but they don't include Java. And since that wouldn't actually solve the "con" of avahi-publish(1), it's not really obvious that I'd gain anything by going down such a route even if it were open to me.

Pros: none.

Cons: neither useful nor written yet.

mDNSResponder

Apple published mDNSResponder as open source under the APL 2. It supports Mac OS (even the prehistoric pre-Unix variant that it's believed the cavemen used), Windows, and "POSIX platforms". This does come with Java bindings, but they're just that: bindings to the native code using JNI.

This would provide portability, but by this time I'd realized that ideally, I wanted a pure Java solution.

Pros: somewhat portable.

Cons: awkward JNI dependency, recompilation requirements.

waiter

The waiter project is very proud about being written using Test Driven Development and what kind of mocks it uses, but not terribly interested in telling you how to use it. Or what state of development it's at. The code looked a bit odd, too. More like a Java-hater's parody of Java than anything.

Pros: none.

Cons: no documentation or sample code, weird attitude, not obviously alive.

mahalo-mdns

The mahalo-mnds mahalo-mdns project was a fork of JmDNS. A fork on which apparently nothing of substance happened.

Pros: none.

Cons: dead.

JiveDNS

The JiveDNS project was a fork of JmDNS. A fork that apparently didn't get as far as actually committing any code.

Pros: none.

Cons: dead.

JmDNS

So what about the project everyone else seems to fork but then not do anything with? This appears to be the best of the bunch, though it's not without problems.

I tried version 2.1, and the API looked reasonable. More Java-like than the others, certainly. The documentation seems to think the API uses constructors, but the code itself has changed. Luckily, the example programs it ships with have been kept up to date.

I noticed that the project's site has bug reports about deadlock and dodgy use of JVM shutdown hooks going back years. Given that the second of their example programs did indeed deadlock in a shutdown hook, leaving me having to kill -9 the JVM after suspending it, I'm ready to believe those reports.

Anyway, here's the single line I needed after dropping the jmDNS JAR file into my project's lib/jars/ directory:

JmDNS.create().registerService(ServiceInfo.create("_http._tcp.local.", "mp3d",
                                                  portNumber, ""));

And sure enough, if you add something like that to your program, you'll find your program too will deadlock if interrupted. Suboptimal.

Under other circumstances, I'd suggest jmDNS was ripe for a forking, but that doesn't seem to have worked in the past!

Pros: portable.

Cons: deadlock-prone, under-maintained.

Conclusion

So what am I going to use? avahi-publish(1). I was only expecting to run on Linux anyway, so I'll worry about portability when I come to need it.

But... it turns out that I can't use DNS-SD for what I originally wanted it for. My plan was to switch from a hard-coded port number for my HTTP server (8888, just like we used to use in the good old days) to just accepting whatever ephemeral port I got. I thought that if I announce my service using DNS-SD it wouldn't matter what port I was on.

To a certain extent that's true. The missus doesn't need to know the port number if she uses the Bonjour bookmark to get there. But if she wants to get there via the history and the server's restarted, she's screwed. Likewise if the server restarts while she has a browser window open, when she comes back to that window, it's "broken". So it turns out I can't really get away with accepting an ephemeral port after all. This seems like a real pity. Maybe one day we'll be able to use DNS-SD service names as virtual port names, as in http://lithium.local.:mp3d./ say. But not today.

So it's back to the four fat ladies of my hard-coded port number, but I'll leave the DNS-SD registration in anyway. I'm used to it now, and it'll be a comfort in my old age, when my memory's completely gone.