socketry/rubydns

call addresses_for for a record(cname message returned) throw NoMethodError exception

akawhy opened this issue · 8 comments

hello, when I use addressed for a record, like www.baidu.com which is a cname record, will throw such an exception:

in `block (2 levels) in addresses_for': undefined method `address' for #<Resolv::DNS::Resource::IN::CNAME:0x000000019e4fc0> (NoMethodError)
        from rubydns-0.8.4/lib/rubydns/resolver.rb:70:in `collect'
        from rubydns-0.8.4/lib/rubydns/resolver.rb:70:in `block in addresses_for'
        from rubydns-0.8.4/lib/rubydns/resolver.rb:85:in `block in fetch'
        from eventmachine-1.0.3/lib/em/deferrable.rb:151:in `call'
        from eventmachine-1.0.3/lib/em/deferrable.rb:151:in `set_deferred_status'
        from eventmachine-1.0.3/lib/em/deferrable.rb:191:in `succeed'
        from rubydns-0.8.4/lib/rubydns/resolver.rb:142:in `process_response!'
        from rubydns-0.8.4/lib/rubydns/resolver.rb:219:in `receive_data'
        from eventmachine-1.0.3/lib/eventmachine.rb:187:in `run_machine'
        from eventmachine-1.0.3/lib/eventmachine.rb:187:in `run'

is there a way to get the target ip ?

Thanks

Thanks for reporting a bug. I will be fixing this in the 0.9 branch which is due to be released soon.

A work around is to use the more complex Resolver#query function and do your own CNAME resolution. Apologies for this complication.

@petergoldstein I've been considering solutions to this problem.

I've got something working but I'm concerned about the guts of Resolv::DNS::Message::Message(Encoder|Decoder).

I'm not completely sure but it appears from my cursory testing that it is impossible to distinguish "test.domain." and "test.domain" as they go across the wire.

The problem with this is that currently RubyDNS doesn't make any assumptions about default search domains. If you send "test.domain" across the wire, it (currently) gets decoded to "test.domain." which is, at least IMHO, incorrect.

I'm trying to find out from the RFC if it's actually possible (and normative/desirable) to encode relative or absolute domain names, or if ALL messages (looks likely) MUST have absolute names by default. The typical way to encode a domain is like so:

[3] www [6] foobar [3] com [0]

But if we assume to encode absolute or relative names, it would look like:

[3] www [6] foobar [3] com [?] [0]

The reason why this is important is because the current resolver implementation makes no distinction between relative and absolute domains, but when, for example, requesting "www.foobar.com", the results come back for "www.foobar.com." and for this bug report, we need to be able to say that those are the same things, but at least in certain cases in RubyDNS, currently, that is not the case.

Okay, so looking at other implementations (e.g. Python, https://github.com/rthalley/dnspython/blob/43c14fd73b3b94211ff8bfad8f894b48cce4e577/dns/name.py#L363-L385) it looks like all domains that are to be encoded must always be absolute.

This means that, for example, technically, match("foobar") would never match anything, it would technically need to be "foobar.". We need to convert all strings into FQDNs for this functionality to work correctly.

From https://www.ietf.org/rfc/rfc1035.txt

s make up a large share of the data in the master file.
The labels in the domain name are expressed as character strings and
separated by dots. Quoting conventions allow arbitrary characters to be
stored in domain names. Domain names that end in a dot are called
absolute, and are taken as complete. Domain names which do not end in a
dot are called relative; the actual domain name is the concatenation of
the relative part with an origin specified in a $ORIGIN, $INCLUDE, or as
an argument to the master file loading routine. A relative name is an
error when no origin is available.

Not quite as definitive as I was looking for, but perhaps RubyDNS server should have an origin which defaults to "." and is used to map to FQDN. Or, perhaps a better approach is to force users to be explicit about their choice?

@ioquatix I need to give this a more detailed look, but if I'm understanding the issue correctly then here are my initial thoughts

Probably the best way to ascertain how things should behave on the wire is to look at the queries produced by commonly used tools (e.g. dig) in the most common case where the user doesn't provide a trailing '.' (e.g. dig www.cnn.com) vs. the case when they do (e.g. dig www.cnn.com.).

Does dig treat these two cases the same? Does it automatically add the trailing '.' to the encoded query when the user doesn't provide one? Is it meaningful in any way for a relative domain to be encoded in a query (I don't think so), or is this just a convention that is meaningful in a zone file?

@petergoldstein I'm going to go with the "origin" postfix and strip it off so that RubyDNS code won't need to be changed but the mechanism for doing so is well defined (e.g. the default origin would be '.' so that queries like match('test.domain') would just work).

I'd really appreciate having your detailed feedback on this issue because it affects the entire resolver.

My initial goal for RubyDNS was NOT to impose any additional constraints on how records are resolved. Adding origin is one such constraint but it is necessary because it DOES appear that DNS messages must always have absolute domains, so we need to be able to serialise from non-absolute names -> absolute for message -> non-absolute for matching.

I believe, and will check, that dig www.cnn.com and dig www.cnn.com. are slightly different. If your local network is set up with a search domain, in the first instance, it may try to search that using the search domain as a postfix, e.g. if your search domain was "local.", it might actually, technically try "www.cnn.com.local." - but the second case, because you are specifying an absolute name, no such postfix would be applied or checked [in any typical case].

This should now be working correctly. Please try out and let me know.