Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Socket.gethostbyname respond with "<any>" and "<broadcast>" hosts #2111

Closed
wants to merge 4 commits into from

3 participants

@sdaubert

Make specs "Socket#gethostbyname returns broadcast address info for ''" and "Socket#gethostbyname returns broadcast address info for ''" pass
See commit messages for details.

sdaubert added some commits
@sdaubert sdaubert Spec "Socket#gethostbyname returns broadcast address info for '<any>'…
…" is OK.

Socket.getaddrinfo is modified to act the same way when +host+ is "" or is
"<any>".
Socket.gethostbyname is modified before returning : addresses array is modified
to return packed-string addresses instead of IPv4 dotted addresses.
a13ece8
@sdaubert sdaubert Make spec "Socket#gethostbyname returns broadcast address info for '<…
…broadcast>'" pass.

Socket::Foreign.getaddrinfo is modified to use "<broadcast>" as "255.255.255.255" (first
+if+ statement).
25fc012
@sdaubert sdaubert Remove fail tag on library/socket/socket/gethostbyname. 20540b1
@dbussink
Owner

Few comments, first of all, why was this only added for 1.9 mode? This seems like a feature that should be available also in 1.8, since there are also tags in 1.8 mode for this.

@dbussink dbussink commented on the diff
lib/19/socket.rb
@@ -688,6 +690,16 @@ def self.gethostbyname(hostname)
addresses << a[3] if a[4] == family
end
+ addresses.map! do |addr|
@dbussink Owner

Could you explain what this code does? Personally I don't find it very readable, maybe there is a way we can make this cleaner / more obvious as to what it does?

This code transforms a dotted IPv4 address into a packed address, as returned by MRI.
First, if addr seems to be an IPv4 address, each address byte is packed into string s. Else addr is returned unchanged.

Now, for packing, i think it should use pack:

$~[1..4].map(:to_i).pack("C*")

instead of

$~[1..4].each { |v| s << v.to_i.chr }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@sdaubert

I plan to add it for 1.8 mode after it will be accepted for 1.9. I am not confident about some modifications, like line 693.

@dbussink
Owner

Ah ok, we would prefer to have them in a single pull request then, so the changes can be made together. I suspect the changes will be exactly the same anyway, but we can work on reviewing this change first then if you are more comfortable with that.

I don't really have much time today anymore though, you know, something with 2013 ;). Have a good new year, and I'll take a further look later this week :).

@sdaubert

I agree you about changes on 1.8 version, but I would prefer reviewing this change first.
Thanks.

@sdaubert

As MRI also returns a packed string for IPv6 address, i propose to modify code as:

    addresses.map! do |addr|
      if addr =~ /\./
        # IPv4 address
        addr.split('.').map { |v| v.to_i }.pack('C*')
      elsif addr =~ /:/
        # IPv6 address
        left, right = addr.split('::')
        l = left.split(':')
        r = right.split(':')
        rest = 8 - l.size - r.size
        ary = l + ['0'] * rest + r
        ary.map { |v| v.to_i }.pack('n8')
      else
        # other (?): return addr as is
        addr
      end
@hron84

@sdaubert I worry about your solution. IPv6 address can contain double colon, but it is not a requirement. If IPv6 address is written in fully-expanded form, there is no double colon.

Ohh, and simple shortening:

addr.split('.').map(&:to_i).pack('C*')
@sdaubert

@hron84 you are right. To make this code work, we have to add

right ||= ''

after

left, right = addr.split('::')

I also discovered a bug by testing fully expanded form. We must use

ary.map { |v| v.to_i(16) }.pack('n8')

instead of

ary.map { |v| v.to_i }.pack('n8')
@sdaubert

I added commit 90534f2 for supporting IPv6 addresses (and also dotted format for IPv4-mapped and IPv4-compatible IPv6 addresses).

@dbussink
Owner

Ok, there are two concerns here. The one with handling and and the fact that Socket.gethostbyname doesn't return the addresses encoded in the same way as MRI. We should not be mixing these two issues here probably.

For the second issue, this is not the way we should approach fixing it. Doing this with manual regexp parsing is error prone and we should be using SockAddr_In and probably add the SockAddr_In6 struct for that. Those are the canonical structures that handle parsing like this, we shouldn't be copying that logic in other places.

This is not really a trivial change and probably requires much more work than this. What I suggest for this pull request, is changing the spec so it doesn't depend on matching the whole result of Socket.gethostbyname, but just on the first entry of the array that gethostbyname returns and specify that has to be "0.0.0.0" for and "255.255.255.255" for .

The formatting of the address as the last entry should be specced separate, so we can also tackle that as a separate issue.

@sdaubert

So, I make a new pull request for handling 'any' and 'broadcast' and for modifying specs to test only the first item of returned array. This pull request will handle all ruby versions.
I will try to fill another pull request later about handling last item in returned array, with specs.
Is that right ?

@dbussink
Owner

Right, we should handle this first case first. The second part will be a lot more complicated I guess though, I've tried to see what needed to be done, and it's quite a significant effort and probably touches a bunch of more places, like FFI struct generation etc.

@sdaubert

For the first case, see new pull request #2115.

@sdaubert

For the second issue, I propose a new solution in #2125. If #2125 is accepted, then we could close this pull request, i think.

@dbussink
Owner

I'm closing this since it has been handled in two separate pull requests.

@dbussink dbussink closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 30, 2012
  1. @sdaubert

    Spec "Socket#gethostbyname returns broadcast address info for '<any>'…

    sdaubert authored
    …" is OK.
    
    Socket.getaddrinfo is modified to act the same way when +host+ is "" or is
    "<any>".
    Socket.gethostbyname is modified before returning : addresses array is modified
    to return packed-string addresses instead of IPv4 dotted addresses.
Commits on Dec 31, 2012
  1. @sdaubert

    Make spec "Socket#gethostbyname returns broadcast address info for '<…

    sdaubert authored
    …broadcast>'" pass.
    
    Socket::Foreign.getaddrinfo is modified to use "<broadcast>" as "255.255.255.255" (first
    +if+ statement).
  2. @sdaubert
Commits on Jan 3, 2013
  1. @sdaubert

    Modify end of Socket.gethostbyname to also convert IPv6 addresses into

    sdaubert authored
    packed strings. This code is taken from IPAddr#hton.
This page is out of date. Refresh to see the latest.
View
30 lib/19/socket.rb
@@ -357,8 +357,10 @@ def self.getaddrinfo(host, service = nil, family = 0, socktype = 0, protocol =
hints[:ai_protocol] = protocol
hints[:ai_flags] = flags
- if host && host.empty?
+ if host && (host.empty? || host == '<any>')
host = "0.0.0.0"
+ elsif host == '<broadcast>'
+ host = '255.255.255.255'
end
res_p = FFI::MemoryPointer.new :pointer
@@ -688,6 +690,32 @@ def self.gethostbyname(hostname)
addresses << a[3] if a[4] == family
end
+ addresses.map! do |addr|
@dbussink Owner

Could you explain what this code does? Personally I don't find it very readable, maybe there is a way we can make this cleaner / more obvious as to what it does?

This code transforms a dotted IPv4 address into a packed address, as returned by MRI.
First, if addr seems to be an IPv4 address, each address byte is packed into string s. Else addr is returned unchanged.

Now, for packing, i think it should use pack:

$~[1..4].map(:to_i).pack("C*")

instead of

$~[1..4].each { |v| s << v.to_i.chr }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ case addr
+ when /^\d+\.\d+\.\d+\.\d+$/
+ # IPv4 address
+ addr.split('.').map(&:to_i).pack('C*')
+ when /^::(ffff:)?(\d+\.\d+\.\d+\.\d+)$/i
+ # IPv4-mapped and IPv4-compatible IPv6 address
+ constant_bytes = "\x00" * 10
+ if $1
+ constant_bytes += "\xff" * 2
+ else
+ constant_bytes += "\x00" * 2
+ end
+ constant_bytes + $2.split('.').map(&:to_i).pack('C*')
+ else
+ # IPv6 address
+ left, right = addr.split('::')
+ right ||= ''
+ l = left.split(':')
+ r = right.split(':')
+ rest = 8 - l.size - r.size
+ ary = l + ['0'] * rest + r
+ ary.map { |v| v.to_i(16) }.pack('n8')
+ end
+ end
+
[hostname, alternatives.uniq, family] + addresses.uniq
end
View
2  spec/tags/19/ruby/library/socket/socket/gethostbyname_tags.txt
@@ -1,2 +0,0 @@
-fails:Socket#gethostbyname returns broadcast address info for '<broadcast>'
-fails:Socket#gethostbyname returns broadcast address info for '<any>'
Something went wrong with that request. Please try again.