4

I've registered a record using the Bonjour API. Now I want to know the contents of the record I just published. I created it by specifying a NULL hostname, meaning, "use the daemon's default", but I can't find a simple way to query what that is!

With avahi, it's easy: I call avahi_client_get_host_name() to get the starting value of the machine's hostname.

For both avahi and Bonjour, the value of the SRV record can change during the lifetime of the registration - if the registration was done with a NULL hostname, the record's hostname is updated automatically when necessary. All I want here is a way to get the initial value of the hostname, at the time when I perform the registration.

Note that on my Snow Leopard test machine, the default multicast hostname is not the same as the machine's name from gethostname(2).

Four solutions I can think of:

  1. Grab hostname in my process. It may be in there somewhere. I did a strings(3) search on a memory dump of my process, and found four instances of the multicast hostname in my address space, but that could be coincidence given the name is used for other things. Even if the string I'm after is in my process somewhere, I can't find an API to retrieve it sanely.
  2. Query the hostname from the daemon. There may be some query I can send over the mach port to the daemon that fetches it? I can't find an API again. The relevant chunk of code is in the uDNS.c file in mDNSResponder, and doesn't seem to be exposed via the RPC interface.
  3. I could just lookup the service I registered. This may involve a bit of network traffic though, so unless there's some guarantee that won't happen, I'm loathe to do it.
  4. Re-implement the logic in uDNS.c. It grabs the machine's hostname from a combination of:

    • Dynamic DNS configuration
    • Statically configured multicast hostname
    • Reverse lookup of the primary interface's IPv4 address
    • It specifically doesn't use gethostname(2) or equivalent

    Re-implementing that logic seems infeasible.

At the moment, I'm tending towards doing a lookup to grab the value of the initial SRV registration, but it doesn't seem ideal. What's the correct solution?

Nicholas Wilson
  • 9,435
  • 1
  • 41
  • 80
  • 1
    I don't know if there is a better method, but I don't think that #3 involves network traffic because the local mDNSResponder knows the answer. It looks like the most reliable method to me. – Martin R Aug 16 '13 at 17:02
  • The race condition annoys me - if the daemon detects a conflict it will withdraw the record and the lookup might therefore generate traffic (vanishingly unlikely though). It's bearable though. – Nicholas Wilson Sep 03 '13 at 22:17

4 Answers4

2

I needed to do exactly this. You want to use the ConvertDomainNameToCString macro (included in mDNSEmbeddedAPI.h), and you need access to the core mDNS structure.

Here's how you get the exact Bonjour/Zeroconf hostname that was registered:

char szHostname[512];
extern mDNS m;

ConvertDomainNameToCString(&m.MulticastHostname, szHostname);

I hope this helps you.

  • Yikes! That's not a public header. That's also a suspiciously short symbol name too - where do I find this symbol? I've just linked a test application that uses some mDNS functions, and it's getting the symbols from /usr/lib/libSystem.B.dylib. Unsuprisingly, there aren't any symbols as short as "_m" in a core system library. You're not describing statically linking in the client library that talks to daemon, are you? – Nicholas Wilson Sep 03 '13 at 16:55
  • In my case, I have a straight C language Zeroconf implementation, in which the mDNS service runs as a thread on my embedded device. The instance of the core mDNS structure (which in my case was named simply "m"), was available to my code. I see now that you are probably going through the dns_sd.h API to the daemon on a Mac or PC. In that case, the daemon would have had to register the initial host name with its mDNS object, then dressed it up into a FQDN with "xx.local.". The initial hostname is likely the part of the service name, up to the first period (.). – Jonathan Lawry Sep 03 '13 at 18:29
  • Yes, this question is explicitly about desktop applications using the public Bonjour API (dns_sd.h). The daemon certainly knows what the current hostname is, but that information just doesn't seem to be available to clients without doing a service lookup. The name of the SRV record (string-i-picked._http._tcp) most certainly doesn't contain the hostname ("computer-name.local.") which is the contents of the record. I think duplicating the logic used to create the hostname on all three platforms (Win, Mac, Solaris) is the simplest way to go. – Nicholas Wilson Sep 03 '13 at 22:14
0

For the record, I went with (4), grabbing the machine's configuration to pull together the hostname the daemon is using without having to query it.

static char* getBonjourDefaultHost()
{
  char* rv = 0;
#ifdef __APPLE__
  CFStringRef name = SCDynamicStoreCopyLocalHostName(NULL);
  if (name) {
    int len = CFStringGetLength(name);
    rv = new char[len*4+1];
    CFStringGetCString(name, rv, len*4+1, kCFStringEncodingUTF8);
    CFRelease(name);
  }
  // This fallback is completely incorrect, but why should we care...
  // Mac does something crazy like <sysctl hw.model>-<MAC address>.
  if (!rv)
    rv = GetHostname(); // using gethostname(2)

#elif defined(WIN32)
  CHAR tmp[256+1];
  ULONG namelength = sizeof(tmp);
  DynamicFn<BOOL (WINAPI*)(COMPUTER_NAME_FORMAT,LPSTR,LPDWORD)>
    GetComputerNameExA_("Kernel32", "GetComputerNameExA");
  if (!GetComputerNameExA_.isValid() ||
      !(*GetComputerNameExA_)(ComputerNamePhysicalDnsHostname, tmp, &namelength))
    tmp[0] = 0;
  // Roughly correct; there's some obscure string cleaning mDNSResponder does
  // to tidy up funny international strings.
  rv = tmp[0] ? strdup(tmp) : strdup("My Computer");

#elif defined(__sun)
  // This is exactly correct! What a relief.
  rv = GetHostName();

#else
#error Must add platform mDNS daemon scheme
#endif
  return rv;
}
Nicholas Wilson
  • 9,435
  • 1
  • 41
  • 80
0

From the command line one can obtain the local (Bonjour) hostname using the scutil command:

scutil --get LocalHostName

Programmatically this is obtainable using the kSCPropNetLocalHostName key via the SystemConfiguration Framework.

Pierz
  • 7,064
  • 52
  • 59
  • This is the same as my answer - solution (4), trying to recreate the logic that the mDNS daemon uses. The daemon uses the LocalHostName as its first preference - but of course it doesn't have to be set, in which case various fallbacks kick in... – Nicholas Wilson Oct 11 '17 at 21:52
  • Ah fair enough though I thought showing a command line solution and info on the relevant API/Framework was useful as it wasn't so clear which one to use. – Pierz Oct 12 '17 at 08:33
0

I know this is already answered, but I went looking to see how to do it with the SystemConfiguration framework based on what Pierz said in the second half of his answer.

This is what I got working, figured it might save someone else googling this some time: In Swift:

import SystemConfiguration

    let store = SCDynamicStoreCreate(nil, "ipmenu" as CFString, nil, nil )
    if let hostNames = SCDynamicStoreCopyValue(store, "Setup:/Network/HostNames" as CFString){
        if let hostName:String = (hostNames[kSCPropNetLocalHostName]) as? String {
            print("Host name:\(hostName)")
        }
    }
Jacob White
  • 81
  • 1
  • 4