Skip to content

DNS-SD support #26

Description

@matthew-a-thomas

Issue

It doesn't appear that DNS-SD is entirely supported due to the fact that DNS label names in DnsClient.NET strictly follow the recommendation in RFC 1035.

Background

DNS Service Discovery (DNS-SD) is defined in RFC 6763. It uses normal DNS queries to find and resolve services. Bonjour and Avahi are Apple and Linux's implementations, respectively.

According to Stuart Cheshire (the main guy on the RFC):

The configuration the clients need is not who to ask, but what to ask

This is because a DNS-SD query is just a regular DNS query: service descriptions are stored in SRV and TXT records.

mDNS is just DNS implemented in a distributed manner on the 224.0.0.251 multicast address over UDP port 5353.

So, DNS-SD queries can be asked of either a normal DNS server or of an mDNS network. My examples focus on mDNS+DNS-SD. So just keep in mind that most things I say would still apply if I were communicating with a "real" DNS server.

Details

I am able to fabricate a query to discover all printers via mDNS, DNS-SD style. It's really neat that this works with DnsClient.NET, by the way. Kudos!

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(
    "_printer._tcp.local.",
    QueryType.ANY);

Because I happen to have an HP printer on my network which implements mDNS+DNS-SD, then in response I get all the PTR, SRV, and TXT records I need to find its friendly name, regular cryptic DNS name, IP addresses, port numbers, and printer config stuff.

But I think I would quickly face truncation as the number of printers on the network grows.

Instead of querying for everything, section 4 of RFC 6763 says to issue a PTR query. The responses will include friendly names that can be individually resolved.

And the PTR query works. For example, I get back a PtrRecord containing PtrDomainName=HP LaserJet MXYZ (ABCDEF)._printer._tcp.local. when I execute this code:

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(
    "_printer._tcp.local.",
    QueryType.ANY);

The PtrDomainName is of the format <Instance>.<Service>.<Domain>. and in this case the <Instance> is HP LaserJet MXYZ (ABCDEF). According to section 4.1.1:

[The instance] is a user-friendly name consisting of arbitrary Net-Unicode text [RFC5198]. It MUST NOT contain ASCII control characters (byte values 0x00-0x1F and 0x7F) [RFC20] but otherwise is allowed to contain any characters, without restriction, including spaces, uppercase, lowercase, punctuation -- including dots -- accented characters, non-Roman text, and anything else that may be represented using Net-Unicode

This is where the trouble starts: instance names include spaces (and other bad stuff) which isn't a strictly traditional DNS label. Therefore I'm unable to issue further SRV and TXT queries for this specific instance.

For example, this code:

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(
    "HP LaserJet MXYZ (ABCDEFG)._printer._tcp.local.",
    QueryType.ANY);

...results in this error:

HP LaserJet MXYZ (ABCDEFG)._printer._tcp.local.' is not a valid hostname.

Proposal

I propose that you relax the label name requirements to be "anything at most 63 octets in length, except ASCII control characters".

After all, as you know names end up encoded as just a bunch of length-prefixed byte arrays. So strictly speaking, any 63-octet label is able to be transmitted.

Unfortunately, this would be a breaking change. So if you're concerned about yanking out the rug from under the feet of existing users relying on DnsString to sanitize DNS names, then perhaps the DnsString class could gain a static IsTraditional method for folks to use instead?

Alternative

I know what you're going to say:

RFC 1035 section 2.3.1 says that labels cannot include spaces, or parentheses, or anything non-A-Z or non-0-9!

And although I disagree (the RFC merely recommends that format), I can understand where you'd be coming from.

So if you feel that way, then I propose that you create a query method having this signature:

public Task<IDnsQueryResponse> QueryAsync(DnsQuestion query, CancellationToken cancellationToken = default (CancellationToken))

(In fact, perhaps this could replace the existing QueryAsync method, since the existing method signature could be an extension method deriving from the above signature. And there would be no breaking changes.)

Then I would be free to shoot myself in the foot with strange queries if I want to issue DNS-SD queries like:

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(new DnsQuestion(
    query: DnsString.FromResponseQueryString("HP LaserJet MXYZ (ABCDEFG)._printer._tcp.local."),
    questionType: QueryType.ANY,
    questionClass: QueryClass.IN
));

(And if DnsString.FromResponseQueryString is too strange in this context, then perhaps you could give us a DnsString.FromUnchecked method or open up the DnsString constructor?)

Conclusion

I believe that either of these options would enable full DNS-SD support.

Bonus 👍

Full DNS-SD support would bring you up to par with onovotny/Zeroconf. I would be able to use your package to do everything that Zeroconf does, and more.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions