Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DNS-SD support #26

Open
matthew-a-thomas opened this issue Jun 18, 2018 · 4 comments
Open

DNS-SD support #26

matthew-a-thomas opened this issue Jun 18, 2018 · 4 comments
Milestone

Comments

@matthew-a-thomas
Copy link

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.

@matthew-a-thomas
Copy link
Author

Upon further research, for mDNS+DNS-SD to work it looks like more needs to change than just label name restrictions. Unfortunately the LookupClient immediately returns after it receives the first response. This isn't good for mDNS where multiple hosts can reply with different DNS responses.

So in summary, changing label name restrictions would only help DNS-SD on a network with a standard unicasted DNS server. On an mDNS network it's still no good.

So feel free to toss out this issue if you don't feel like taking on full mDNS+DNS-SD support.

@MichaCo
Copy link
Owner

MichaCo commented Jun 18, 2018

Hi @matthew-a-thomas
I'm actually using this library primarily for service discovery, but in a completely different scenario.
I'm using Consul's DNS endpoint for service discovery in a micro service project @ work.

Your use case sounds totally reasonable. I never played with mDNS though and don't have much experience with what the RFC actually says/requires.

There seem to be some changes needed to support that.
I'm not 100% sure how the multi-casting is supposed to work from a consumer (DNS Client) point of view.
I'd have to spend some time to figure all that out and also setup some testing environment.

Lifting the current restricting in DnsString to support non domain name characters and such, shouldn't be that difficult.
Not sure if that should always be the default behavior though.

I'll try to get some time and dig a bit more into nDNS

@matthew-a-thomas
Copy link
Author

Thank you for being willing to research this stuff.

@MichaCo MichaCo added this to the Future milestone Aug 20, 2018
@MichaCo
Copy link
Owner

MichaCo commented Mar 18, 2024

Hey, @matthew-a-thomas
Didn't reply to this issue in a long time but just wanted to note that the "workaround" by using DnsQuery is possible for quite some time, the example you posted does not throw any validation error and should work:

var lookup = new LookupClient(
    new LookupClientOptions(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
));

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

No branches or pull requests

2 participants