troglobit / mdnsd

Jeremie Miller's original mdnsd
BSD 3-Clause "New" or "Revised" License
56 stars 35 forks source link

Use config files to define DNS-SD services #6

Closed thom-nic closed 6 years ago

thom-nic commented 6 years ago

mdnsd currently assumes you want to advertise a single HTTP service however mDNS/DNS-SD allows one to advertise multiple services (e.g. http, tftp, ssh.) Since SO_REUSEADDR is being used I think one could run multiple mdnsd processes, each specifying a different service.

So at minimum, as you mentioned in #4 this could get facelifted to use getopt, and take the service type as an optional flag.

However it should, I think, also be possible to respond with multiple DNS records as well e.g. when looking up foo.local you could return multiple A or AAAA records. I'm not sure how practical that becomes w/r/t specifying everything as options. Furthermore I'm trying to think through "what happens if/when the network changes?" e.g. if mdnsd is started before ethernet is plugged in, wifi goes up or down, DHCP lease acquired or lost. I would have to kill and restart mdnsd when any of those changes occur.

I am wondering (and I understand this would be a bit of work!) if it would make sense for mdnsd to take a config file (bind9 format?) and respond to queries for any names in that file. In that case, if network configuration changes, the file could be re-written/ read by mdnsd (use SIGUSR1 to re-read?) and potentially avoid restarting the daemon.

troglobit commented 6 years ago

Yup, having a config file would be preferable to command line options, definitely! I don't think running multiple daemons, one for each service, would be a good idea. Maybe if it's a multi-homed server (or switch/router like we do at work), but then you'd likely want source routing or network namespaces.

I've considered adding support for libConfuse style files, which I use in other projects, or even some really simple homegrown UNIX-style format. Most traditional UNIX applications use SIGHUP to reload their config file, so that's what I'd go for here as well.

I could help out with the .conf file support if you want? I recently added basic support to another project of mine, just to get us off the ground, https://github.com/troglobit/mini-snmpd/commit/0549c55

thom-nic commented 6 years ago

Cool! I am wondering if key/value config file would work in this case though. Each line would want to look like

[name]      IN    [RECORD_TYPE]      [VALUE]       [FLAGS]

It's actually a shame busybox dnsd doesn't support mdns, might almost be easier to add multicast support to dnsd 😂

troglobit commented 6 years ago

Aha, that was one of my follow-up questions, thanks! Well maybe we could have an /etc/mdnsd.conf for generic options and have a separate directory for records, one per file maybe, in a /etc/mdnsd.d/*.service or similar?

With a separate directory and a service record per file, it would be quite straight forward to drop a ssh.service record in that directory when, e.g., SSH is enabled/started. We could later on even add inotify support to mdnsd to read all new files dropped there, and stop publishing records of files removed.

What do you think?

troglobit commented 6 years ago

(Btw, watch out I'm pushing a slew of build fixes to master)

thom-nic commented 6 years ago

that sounds awesome!

BTW I noticed the extra printf I left to log responses is (1) noisier than I anticipated and (2) I forgot the '\n'!

thom-nic commented 6 years ago

Might it also be appropriate to look under, say, /var/run/mdnsd.d/ for runtime-configured services? (maybe the service directory is a config option.)

troglobit commented 6 years ago

OK, I can remove the extra printf() you added, if you want?

Yup, /run/mdnsd.d/ support could be useful as well, configurable support (or both by default) sounds great! :+1:

thom-nic commented 6 years ago

OK, I can remove the extra printf() you added, if you want?

yeah it's useless. thanks.

troglobit commented 6 years ago

OK fixing

thom-nic commented 6 years ago

So I did some more reading because I needed a refresher on all the records needed for DNS-SD. These two references gave a good overview (though they assume "normal" unicast DNS, not mDNS.)

Although I was suggesting BIND-style zone files, it would be overkill to require specifying the PTR records in a config file, we can infer that - maybe you already had that in mind. The SRV record could be built if one specifies a port, target, (maybe TTL). The enumeration of multiple values comes in at the point where you want to specify possibly more than one A and AAAA records for the SRV target, and also arbitrary TXT records.

Also the browse domain I think is unnecessary to specify via configuration.

So just to throw a strawman out there... I guess if you're fond of libconfuse it might look something like:


# /etc/mdnsd.d/my-service._http._tcp.conf
name="My Service"                # creates "My Service._http._tcp" entry (or optionally infer from file name?)
type=_http._tcp                  # or infer from file name?
port=80                          # for "My Service._http._tcp" SRV record
target="my-hostname.local"       # for "My Service._http._tcp" SRV record
path="/login"                    # creates standard "My Service._http._tcp" TXT record

txt = {        # additional TXT records for "My Service._http._tcp"
  "Model=\"ABC 1234\"",
  "Version=1.2.3"
}

records = {          # entries for SRV target
  "my-hostname.local A 169.254.22.22",
  "my-hostname.local AAAA fe80::564a:16ff:febb:0"
}
troglobit commented 6 years ago

Awesome, thank you for the pointers, I really need to read up myself on this! :smiley:

Just to be clear, I'm not married to libConfuse, it's just my goto hammer factory. Any homegrown parser is usually just as sufficient.

As for your proposal, I think it looks great. Inferring from the filename is awesome, since this means a simple ls in the directory can be used to get an overview of what services are advertised. We could also infer the port from _http._tcp using getservbyname() (which uses /etc/services to find the correct port + proto). Same goes for target, if omitted from the .conf file we could use gethostname().

However, hard coding IP addresses is something I particularly dislike, and I think we can handle better. The getifaddrs() function and inspecting the routing table should help us decide what IP address(es) to advertise (I can help out here :). I also believe we should advertise AAAA record by default, if the egress interface has an IPv6 address set. We could have an option/options for this, as shown below, but do we really need to disable any of them?

Here's a counter proposal:

# /etc/mdnsd.d/my-service._http._tcp.conf
ipv6   = no                    # or yes, default yes
ipv4   = yes                   # or no, default yes
target = other-name.local    # optional
txt    = {
  "path=/login",
  "Model=\"ABC 1234\"",
  "Version=1.2.3"
}

I.e., a really simple service could be created by just touch /etc/mdnsd.d/my-service._http._tcp.conf (I believe HTTP services default to a path of /)

thom-nic commented 6 years ago

Ok I definitely like the idea of not having to infer valid IP addresses and manually specify those A and AAAA records. In the current implementation I am doing all that bookkeeping by killing and restarting mdnsd via a udhcpc script hook. If the mdnsd daemon can lookup IP addresses assigned to interfaces that's fantastic.

Re: your proposed configuration example:

So if target was omitted, I'd guess the default SRV target and corresponding A/AAAA records would be for $(hostname).local?

Suggest also adding a name (or instance?) param so Instance Names can be pretty, e.g. name="Brother MFC Printer". See [RFC Section 4.1.1](https://tools.ietf.org/html/rfc6763#section-4.1.1) and [Appendix D](https://tools.ietf.org/html/rfc6763#appendix-D). I thinkmy-servicefrom the filename would be the default value ifname` was omitted.

troglobit commented 6 years ago

So if target was omitted, I'd guess the default SRV target and corresponding A/AAAA records would be for $(hostname).local?

Yep.

Ooh nice, a name/instance (or maybe 'description'?) and defaulting to the service name would be great.

thom-nic commented 6 years ago

I think a "Description" could be an additional non-standard TXT record, if one wanted such a thing.

In case this isn't clear: the reason for suggesting a name/instance config value (whichever you call it) is because contrary to hostname convention, DNS-SD encourages a "human readable" instance name for the SRV record like My\ Company\ Foobar\ Thing._http._tcp which might or might not be preferable in the /etc/mdnsd.d/ config file name

troglobit commented 6 years ago

True that. Yeah, I understand :)

The more I think about it though, I'm realizing libConfuse is probably not a great fit for these record files. So I talked to a friend and he told me they'd made something even simpler for mDNSResponder. Here are a few completely made up examples that I'm curious to hear your thoughts on.

# /run/mdnsd/srv/my-service._http._tcp
service  _http._tcp       # Optional, if not using filename
name     My Fine Service
port     80               # Optional here

txt path=/
txt Model="ABC 1234"
txt Version=1.2.3

target some-hostname.local  # Optional, like before we can set up A/AAAA records using system hostname

Imagine a network with multiple devices of the same type. Google creates a UUID for each Chromecast, but you could create a unique target name using the device type and the last three octets in the MAC address (provided the three first are always the same). Here are examples for Chromecast

# /run/mdnsd/srv/Chromecast-231321321321df89aedd3213asd123421d3.local
service _googlecast._tcp   # Not optional in this case
port    8009
name    Living room TV

txt features=0x20f8
txt model=Xbmc,1
txt deviceid=00:13:93:c0:ff:ee

target 231321321321-df89-aedd-3213-asd123421d3.local

Completely useless URL. But with a cname ...

# /run/mdnsd/cname/chromecast
cname chromecast.local
target 231321321321-df89-aedd-3213-asd123421d3.local
shared yes

The shared flag indicates if more than one device is expected to announce the record, if so all devices retract the cname.

For our service above we could create something like this, never retracting the cname if we're first:

# /run/mdnsd/cname/troglobit.local
cname troglobit.local    # Optional, can use filename
target some-hostname.local
shared no
thom-nic commented 6 years ago

That config format looks 100% acceptable to me.

I'm not sure how critical the cname bit is.. I mean it's nice to have a pretty fqdn for a device but the user never sees it (just us technical people.) For instance I have a bunch of Cisco switches on my network that look like:

$ dns-sd -B _http._tcp
... snip ...
16:34:33.188  Add        3   5 local.               _http._tcp.          switcha0bc35
16:34:33.188  Add        3   5 local.               _http._tcp.          switch9d438c
16:34:33.188  Add        3   5 local.               _http._tcp.          switch741322
16:34:33.188  Add        3   5 local.               _http._tcp.          switch12112ac

For my devices, I'm doing like you suggest and use the last 3 bytes of the MAC so their A records look like $productName-c0ff33.local.

Actually I guess you see that target hostname when you either browse to http://foo-c0ff33.local/login or SSH, or whatever but I don't mind the reasonably short and unique A record name. I'm just wondering how much work you're creating for yourself to track if other products are vying for the same CNAME. Is that shared/CNAME stuff part of the DNS-SD spec?

troglobit commented 6 years ago

Cool, let's go with that format then :+1:

The Apple mDNSResponder supports tracking unique cnames, so my friend set up so users could simply connect to http://device.local rather than having to look up the MAC and type http://device-01-02-03.local -- very useful for first time setup after unpacking a new device. (I'd like to replace mDNSResponder with mdnsd, so I'll probably look at cnames if nobody else does :)

troglobit commented 6 years ago

@thom-nic Ah, thanks for updating the issue title! :+1:

Small update, I've merged most of #1 during the weekend and started laying the groundwork for config files today. If the heat in Sweden subsides there will be working code in a couple of days.

thom-nic commented 6 years ago

I saw that, just pulled your changes from master. Looks great so far!

thom-nic commented 6 years ago

Hey these improvements look really great. I'm going to update soon (on vacation next week though!) and give them a try. Haven't updated sooner because I had the old version working and left it as-is.

troglobit commented 6 years ago

Thanks! Yeah it's coming along nicely, but I'm not happy with it yet. Right now I'm trying to figure out why nothing shows up with mdns-scan from (even another) Linux ...

(I'm also on vacation, only way to get some real time off to work on open source ;)

troglobit commented 6 years ago

There, major fixes pushed ... seems to work for me :)

I'm considering moving the remainder of the stuff we talked about to separate issues and close this. But I'll let you take a look at what's on master first, @thom-nic ...

thom-nic commented 6 years ago

Fantastic! I will probably not have a chance to test this until a week from today, at the earliest. Definitely looking forward to it though!

troglobit commented 6 years ago

@thom-nic Awesome, hope you have a great vacation!

thom-nic commented 6 years ago

Config file support looks great! I have another question but I won't tack it on here...

🎉

thom-nic commented 6 years ago

I'd call this fixed as of 3ef9cd3bb0e4cfa41b6d1c6d133d543d910ed7ea. Thanks!

troglobit commented 6 years ago

You're welcome, it was fun working on mdnsd again! =)