'example.com' == 'example.com.' => false... is this intended?

S

Sam Roberts

Hi Tanaka,

I don't understand why DNS::Name#== requires both to be absolute if one
is.

Is this really necessary/useful? It surprises me.

Also, I have a set of comparison operations for Resolv::DNS::Name.

I copied the style (and docs) from Module/hierarchy comparisons, because
I think there is some similarity.

Comments?

If you will accept, I will send patch and changelog.

Thanks,
Sam

Below is implementation, followed by unit test so you can see behaviour.


# DNS names are hierarchical in a similar sense to ruby classes/modules, and the
# comparison operators are defined similarly to those of Module. A name is
# +<+ another if it is a subdomain.
# www.example.com < example.com # -> true
# example.com < example.com # -> false
# example.com <= example.com # -> true
# com < example.com # -> false
# bar.com < example.com # -> nil
#
# Note that #== does not consider two names equal if they differ in whether
# they are #absolute?, but #equal? considers only the label when comparing
# names.
class Name
def inspect
n = to_s
n << '.' if absolute?
return n
end

def equal?(name)
n = Name.create(name)

@labels == n.to_a
end

def related?(name)
n = Name.create(name)

l = length < n.length ? length : n.length

@labels[-l, l] == n.to_a[-l, l]
end

def lt?(name)
n = Name.create(name)
length > n.length && to_a[-n.length, n.length] == n.to_a
end


# Summary:
# name < other => true, false, or nil
#
# Returns true if +name+ is a subdomain of +other+. Returns
# <code>nil</code> if there's no relationship between the two.
def <(name)
n = Name.create(name)

return nil unless self.related?(n)

lt?(n)
end

# Summary:
# name > other => true, false, or nil
#
# Same as +other < name+, see #<.
def >(name)
n = Name.create(name)

n < self
end

# Summary:
# name <= other => true, false, or nil
#
# Returns true if +name+ is a subdomain of +other+ or is the same as
# +other+. Returns <code>nil</code> if there's no relationship between
# the two.
def <=(name)
n = Name.create(name)
self.equal?(n) || self < n
end

# Summary:
# name >= other => true, false, or nil
#
# Returns true if +name+ is an ancestor of +other+, or the two DNS names
# are the same. Returns <code>nil</code> if there's no relationship
# between the two.
def >=(name)
n = Name.create(name)
self.equal?(n) || self > n
end

# Summary:
# name <=> other => -1, 0, +1, nil
#
# Returns -1 if +name+ is a subdomain of +other+, 0 if
# +name+ is the same as +other+, and +1 if +other+ is a subdomain of
# +name+, or nil if +name+ has no relationship with +other+.
def <=>(name)
n = Name.create(name)

return nil unless self.related?(n)

return -1 if self.lt?(n)
return +1 if n.lt?(self)
# must be #equal?
return 0
end

end

require 'test/unit'

Name = Resolv::DNS::Name

class TestDnsName < Test::Unit::TestCase

def test_what_I_think_are_odd_behaviours
# Why can't test against strings?
assert_equal(false, Name.create("example.CoM") == "example.com")
assert_equal(false, Name.create("example.CoM").eql?("example.com"))

# Why does making it absolute mean they aren't equal?
assert_equal(false, Name.create("example.CoM").eql?(Name.create("example.com.")))
assert_equal(false, Name.create("example.CoM") == Name.create("example.com."))
end

def test_CoMparisons

assert_equal(true, Name.create("example.CoM").eql?(Name.create("example.com")))
assert_equal(true, Name.create("example.CoM") == Name.create("example.com"))

assert_equal(true, Name.create("example.CoM").equal?("example.com."))
assert_equal(true, Name.create("example.CoM").equal?("example.com"))

assert_equal(true, Name.create("www.example.CoM") < "example.com")
assert_equal(true, Name.create("www.example.CoM") <= "example.com")
assert_equal(-1, Name.create("www.example.CoM") <=> "example.com")
assert_equal(false, Name.create("www.example.CoM") >= "example.com")
assert_equal(false, Name.create("www.example.CoM") > "example.com")

assert_equal(false, Name.create("example.CoM") < "example.com")
assert_equal(true, Name.create("example.CoM") <= "example.com")
assert_equal(0, Name.create("example.CoM") <=> "example.com")
assert_equal(true, Name.create("example.CoM") >= "example.com")
assert_equal(false, Name.create("example.CoM") > "example.com")

assert_equal(false, Name.create("CoM") < "example.com")
assert_equal(false, Name.create("CoM") <= "example.com")
assert_equal(+1, Name.create("CoM") <=> "example.com")
assert_equal(true, Name.create("CoM") >= "example.com")
assert_equal(true, Name.create("CoM") > "example.com")

assert_equal(nil, Name.create("bar.CoM") < "example.com")
assert_equal(nil, Name.create("bar.CoM") <= "example.com")
assert_equal(nil, Name.create("bar.CoM") <=> "example.com")
assert_equal(nil, Name.create("bar.CoM") >= "example.com")
assert_equal(nil, Name.create("bar.CoM") > "example.com")

assert_equal(nil, Name.create("net.") < "com")
assert_equal(nil, Name.create("net.") <= "com")
assert_equal(nil, Name.create("net.") <=> "com")
assert_equal(nil, Name.create("net.") >= "com")
assert_equal(nil, Name.create("net.") > "com")

end
end
 
T

Tanaka Akira

Sam Roberts said:
I don't understand why DNS::Name#== requires both to be absolute if one
is.

Is this really necessary/useful? It surprises me.

I think it's right behavior.
Also, I have a set of comparison operations for Resolv::DNS::Name.

I copied the style (and docs) from Module/hierarchy comparisons, because
I think there is some similarity.

Comments?

I'm not sure that they are used frequently enough to occupy comparison
operators.
 
S

Sam Roberts

Quoteing (e-mail address removed), on Fri, Feb 04, 2005 at 01:25:20PM +0900:
I think it's right behavior.

Why? When are they not the same?

They are equivalent in the DNS, Resolv::DNS#getaddress() would return
the same A record for both.
I'm not sure that they are used frequently enough to occupy comparison
operators.

Name doesn't have any comparison operators, so they occupy empty space.

Do you have another idea on what Name < Name could mean?

I found it necessary to find whether a Name was a sub-domain of another.
How would you recommend to do this, if there is no method?

lhs.length > rhs.length && lhs.to_a[-rhs.length, rhs.length] == rhs.to_a

is the best I came up with, and its not the kind of thing I would want
to type regularly.

Cheers,
Sam
 
T

Tanaka Akira

Sam Roberts said:
Why? When are they not the same?

For example, if you have a machine named "museum", it is confusing with
"museum." domain.
They are equivalent in the DNS, Resolv::DNS#getaddress() would return
the same A record for both.

There are several top-domains which have A record: ac, museum, etc.
If your local domain have a machine named "ac", it is important to
distinguish a relative "ac" domain and the absolute "ac" domain.
Name doesn't have any comparison operators, so they occupy empty space.

Do you have another idea on what Name < Name could mean?

For example, lexical order.
I found it necessary to find whether a Name was a sub-domain of another.
How would you recommend to do this, if there is no method?

lhs.length > rhs.length && lhs.to_a[-rhs.length, rhs.length] == rhs.to_a

is the best I came up with, and its not the kind of thing I would want
to type regularly.

Adding some method (not operator) is acceptable if it has a good name.
 
N

Navindra Umanee

Sam Roberts said:
Why? When are they not the same?

They are equivalent in the DNS, Resolv::DNS#getaddress() would return
the same A record for both.

"example.com" is a relative domain name. Depending on your
resolver/nameserver's configuration, I believe it could resolve to
"example.com.DEFAULTDOMAIN." which wouldn't be the same as
"example.com."

Cheers,
Navin.
 
S

Sam Roberts

Quoteing (e-mail address removed), on Fri, Feb 04, 2005 at 05:02:45PM +0900:
For example, if you have a machine named "museum", it is confusing with
"museum." domain.


There are several top-domains which have A record: ac, museum, etc.
If your local domain have a machine named "ac", it is important to
distinguish a relative "ac" domain and the absolute "ac" domain.

You only sometimes distinguish between the two, sometimes you convert
them to each other:
=> true
# But, wouldn't this depend on the location? On my net, museum would
# be museum.local.=> true
# Isn't it important to distinguish?

n0=Resolv::DNS.new.getname("193.108.154.9")

n1=Resolv.getname("193.108.154.9")
=> "a193-108-154-9.deploy.akamaitechnologies.com"
# note it isn't absolute, though the DNS response was

n0 == n1
=> in `==': undefined method `absolute?' for "com":String (NoMethodError)
# I have to change my String to a Name to compare it? Irritating.

n1 = Resolv::DNS::Name.create(n0)
n0 == n1
=> false
# DNS returns absolute names
n2 = Resolv::DNS::Name.create(n0.to_s)
n0 == n2
=> false
n0.to_s == n2.to_s
=> true

Maybe you haven't seen how unuseful the behaviour of == is because you
use it inside resolv.rb, and all names you see are absolute?

Outside of resolv.rb (including input/output values of the common Resolv
class methods) names are non-absolute strings, and the DNS Name objects
can't be compared to them.
Name doesn't have any comparison operators, so they occupy empty space.

Do you have another idea on what Name < Name could mean?

For example, lexical order.
I found it necessary to find whether a Name was a sub-domain of another.
How would you recommend to do this, if there is no method?

lhs.length > rhs.length && lhs.to_a[-rhs.length, rhs.length] == rhs.to_a

is the best I came up with, and its not the kind of thing I would want
to type regularly.

Adding some method (not operator) is acceptable if it has a good name.

The code I posted included the method #lt?, you could include it as is,
or change its name. For consistency with #eql?, you may want to make it
NOT convert its argument from String to Name, or you may want to change
both to allow comparison to String.

def lt?(name)
n = Name.create(name) # maybe remove?
length > n.length && to_a[-n.length, n.length] == n.to_a
end

def ==(other)
other = Name.create(other) # maybe add?
return @labels == other.to_a && @absolute == other.absolute?
end
alias eql? ==


Cheers,
Sam
 
S

Sam Roberts

The attached patch fixes this.

Note well that it does it in such a way that it is backwards compatible, but
also allows callers to see the strings as an array. I need this.

RFC1035 says "semantics of the text depends on the domain where it is found",
and the DNS-SD draft says mDNS TXT responses contain key/value pairs, where
each key/value pair is a <character-string> from the TXT rdata.

RFC1035:

3.3.14. TXT RDATA format

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
/ TXT-DATA /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

where:

TXT-DATA One or more <character-string>s.

TXT RRs are used to hold descriptive text. The semantics of the text
depends on the domain where it is found.

Reproduce this with packet collected from wild:

require 'resolv.rb'
require 'pp'

data = "\000\000\000\000\000\003\000\003\000\000\000\000\025Sam Roberts\342\200\231s Music\005_daap\004_tcp\005local\000\000!\000\001\300\f\000\020\000\001\300\"\000\f\000\001\300\f\000!\000\001\000\000\000;\000\021\000\000\000\000\016i\010ensemble\300-\300\f\000\020\000\001\000\000\000;\000\224\ttxtvers=1\016Version=196608\023iTSh Version=131073\027Machine ID=9C5AC2725708\034Database ID=681233690CC1C418\"Machine Name=Sam Roberts\342\200\231s Music\016Password=false\300\"\000\f\000\001\000\000\034\037\000\002\300\f"

msg = Resolv::DNS::Message.decode(data)

pp msg

txtanswer = msg.answer[1]
txtrr = txtanswer[2]

pp txtrr.data
pp txtrr.datas


Changelog:

resolv.rb dies on TXT records with multiple strings

Patch:


Index: resolv.rb
===================================================================
RCS file: /src/ruby/lib/resolv.rb,v
retrieving revision 1.17.2.8
diff -u -r1.17.2.8 resolv.rb
--- resolv.rb 29 Jan 2005 05:22:35 -0000 1.17.2.8
+++ resolv.rb 5 Feb 2005 03:11:52 -0000
@@ -1266,6 +1266,14 @@
return d
end

+ def get_strings
+ strings = []
+ until @index == @limit
+ strings << get_string
+ end
+ strings
+ end
+
def get_name
return Name.new(self.get_labels)
end
@@ -1511,14 +1519,21 @@
def initialize(data)
@data = data
end
- attr_reader :data
+
+ def data
+ @data.join
+ end
+
+ def datas
+ @data
+ end

def encode_rdata(msg)
msg.put_string(@data)
end

def self.decode_rdata(msg)
- data = msg.get_string
+ data = msg.get_strings
return self.new(data)
end
end
 
T

Tanaka Akira

Sam Roberts said:
Maybe you haven't seen how unuseful the behaviour of == is because you
use it inside resolv.rb, and all names you see are absolute?

Outside of resolv.rb (including input/output values of the common Resolv
class methods) names are non-absolute strings, and the DNS Name objects
can't be compared to them.

It is caused by the absolute/relative difference is not cared outside
of a resolver. The unusefulness comes from you need supply omitted
absoluteness information for conversion from string to name, and
Resolv::DNS::Name#to_s omit the absoluteness information.

I believe Resolv::DNS::Name's absoluteness sensitive behavior prevents
a kind of bugs. So I don't want to introduce absoluteness insensitive
behavour such as your comparison.

Adding some conversion methods may be a solution. I don't have
concrete idea, though.
The code I posted included the method #lt?, you could include it as is,
or change its name. For consistency with #eql?, you may want to make it
NOT convert its argument from String to Name, or you may want to change
both to allow comparison to String.

I don't think lt? is a good name.
Because it may mean comparison in lexical order and other orders.
 
S

Sam Roberts

Quoteing (e-mail address removed), on Sat, Feb 05, 2005 at 01:27:35PM +0900:
I don't think lt? is a good name.
Because it may mean comparison in lexical order and other orders.

Perhaps #subdomain?

sam
 
T

Tanaka Akira

Sam Roberts said:
The attached patch fixes this.

Note well that it does it in such a way that it is backwards compatible, but
also allows callers to see the strings as an array. I need this.

RFC1035 says "semantics of the text depends on the domain where it is found",
and the DNS-SD draft says mDNS TXT responses contain key/value pairs, where
each key/value pair is a <character-string> from the TXT rdata.

It is fixed based on your patch. Thank you.

Note that I added TXT#strings to retrieve all strings, instead of
TXT#datas.
 
T

Tanaka Akira

Sam Roberts said:
Perhaps #subdomain?

The term "subdomain" is good.

But I feel two possibile meanings with A.subdomain?(B) :

* A is a subdomain of B
* B is a subdomain of A

Is it clear for native speakers?
 
S

Sam Roberts

Quoteing (e-mail address removed), on Sun, Feb 06, 2005 at 04:55:36AM +0900:
The term "subdomain" is good.

But I feel two possibile meanings with A.subdomain?(B) :

* A is a subdomain of B
* B is a subdomain of A

Is it clear for native speakers?

Native speakers are as confused by their language as everybody else.

#subdomainof?

would make it completely clear.

Sam
 
S

Sam Roberts

Quoteing (e-mail address removed), on Sun, Feb 06, 2005 at 03:36:04AM +0900:
It is fixed based on your patch. Thank you.

Note that I added TXT#strings to retrieve all strings, instead of
TXT#datas.

I am happy with that.

Thanks,
Sam
 
A

Austin Ziegler

The term "subdomain" is good.

But I feel two possibile meanings with A.subdomain?(B) :

* A is a subdomain of B
* B is a subdomain of A

Is it clear for native speakers?

Might I suggest #include? instead? Thus, (using <> strings to
represent instances of the class under discussion):

<.ca>.include?(<halostatue.ca>) # => true
<halostatue.ca>.include?(<www.halostatue.ca>) # => true

But, I don't use this functionality at all, so you can safely ignore
my suggestion here.

-austin
 
T

Tanaka Akira

Sam Roberts said:
#subdomainof?

would make it completely clear.

subdomain_of? and inspect is implemented.

I inserted a underscore to consistent with kind_of?.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top