Skip to content
This repository

Socket.gethostbyname #2125

Closed
wants to merge 6 commits into from

2 participants

Sylvain Daubert Dirkjan Bussink
Sylvain Daubert

Update Socket.gethostbyname.

First, spec is updated to add tests on address list to verify that these addresses are in packed format.
In a second commit Socket.gethostbyname is updated for all ruby versions to pass spec.

sdaubert added some commits
Sylvain Daubert sdaubert Update spec library/socket/socket/gethostbyname.
Add two new specs to verify that addresses in address list returned
by Socket.gethosbyname are in 'pack' format (one spec for IPv4
addresses an the other for IPv6 ones).
45a1371
Sylvain Daubert sdaubert Update Socket.gethostbyname to respect two new specs.
Socket.gethostbyname is updated for all ruby versions.

Socket.sockaddr_in is used to transform addresses got from
Socket.getaddrinfo to packed strings. For an IPv4 address,
address is from 5th byte and is 4 bytes long. For an IPv6
address, address is from 8th byte and is 16 bytes long.
9bda567
lib/18/socket.rb
@@ -610,7 +610,17 @@ def self.gethostbyname(hostname)
610 610 alternatives = []
611 611 addrinfos.each do |a|
612 612 alternatives << a[2] unless a[2] == hostname
613   - addresses << a[3] if a[4] == family
  613 + # transform addresses to packed strings
  614 + if a[4] == family
  615 + sockaddr = Socket.sockaddr_in(1, a[3])
  616 + if sockaddr.length == 16
2
Dirkjan Bussink Owner
dbussink added a note

We shouldn't just check the length and thus assume a certain layout. It's better to switch on the family, whether it's AF_INET or AF_INET6

Sylvain Daubert
sdaubert added a note

Good point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/18/socket.rb
@@ -610,7 +610,17 @@ def self.gethostbyname(hostname)
610 610 alternatives = []
611 611 addrinfos.each do |a|
612 612 alternatives << a[2] unless a[2] == hostname
613   - addresses << a[3] if a[4] == family
  613 + # transform addresses to packed strings
  614 + if a[4] == family
  615 + sockaddr = Socket.sockaddr_in(1, a[3])
  616 + if sockaddr.length == 16
  617 + # IPv4 address
  618 + addresses << sockaddr[4, 4]
4
Dirkjan Bussink Owner
dbussink added a note

We should also find another way of know which bytes are what. We can't be sure about the exact byte layouts of these so we shouldn't hard code these offsets here.

Sylvain Daubert
sdaubert added a note

But these offsets are not magic : they come from C struct definitions:

/* man 7 ip */
           struct sockaddr_in {
               sa_family_t    sin_family;   /* AF_INET */
               in_port_t      sin_port;      /* port number */
               struct in_addr sin_addr;   /* internet address */
           };

           struct in_addr {
               uint32_t       s_addr;
           };

and

/* man 7 ipv6 */
           struct sockaddr_in6 {
               sa_family_t     sin6_family;   /* AF_INET6 */
               in_port_t       sin6_port      /* port number */;
               uint32_t        sin6_flowinfo; /* flow information */
               struct in6_addr sin6_addr;     /* IPv6 adress */
               uint32_t        sin6_scope_id; /* Scope ID */
           };

           struct in6_addr {
               unsigned char   s6_addr[16];
           };
Dirkjan Bussink Owner
dbussink added a note

That's exactly my point :). We should get these offsets from struct definitions for these types and not hard code these offsets. They might be different due to platform alignment, type sizes etc. that's why we should depend on what information the platform supplies for these types.

Sylvain Daubert
sdaubert added a note

OK. So we can use Socket::SockAddr_In. SockAddr_In#offset_of gives offset of sockaddr_in* fields. But i don't known how to get their size.

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

I have studied Rubinius::FFI, and I think I understand the problem :

  • struct sockaddr_in6 must be defined in rakelib/platform.rake.
  • FFI.config must be used to obtain offsets and sizes for sockaddr_in* fields.

I will try to do that this week.

Dirkjan Bussink
Owner

Yeah, that sounds like the direction I was also thinking about :). If you have any questions, don't hesitate to ask :). You can also try hopping into #rubinius on IRC to see if people are around there.

Sylvain Daubert sdaubert Pass spec gethostbyname cleanly.
* rakelib/platform.rake:
  Add new structure `sockaddr_in6`
* lib/**/socket.rb :
  * Modify test to determine IP version. Use family
    instead of sockaddr string size.
  * Use platform configuration to determine offset and
    size of IP address in sockaddr string. `sockaddr_in`
    and `sockaddr_in6` platform structures are used for
    this purpose.
77a888c
Sylvain Daubert

Yeah, it works !
Last commit uses platform layout to get packed address from sockaddr string.

lib/18/socket.rb
@@ -610,7 +610,23 @@ def self.gethostbyname(hostname)
610 610 alternatives = []
611 611 addrinfos.each do |a|
612 612 alternatives << a[2] unless a[2] == hostname
613   - addresses << a[3] if a[4] == family
  613 + # transform addresses to packed strings
  614 + if a[4] == family
  615 + sockaddr = Socket.sockaddr_in(1, a[3])
  616 + if family == AF_INET
  617 + # IPv4 address
  618 + offset = FFI.config("sockaddr_in.sin_addr.offset")
  619 + size = FFI.config("sockaddr_in.sin_addr.size")
  620 + addresses << sockaddr[offset, size]
  621 + elsif family == AF_INET6
  622 + # Ipv6 address
  623 + offset = FFI.config("sockaddr_in6.sin6_addr.offset")
  624 + size = FFI.config("sockaddr_in6.sin6_addr.size")
  625 + addresses << sockaddr[8,16]
3
Dirkjan Bussink Owner
dbussink added a note

Looks like you're not using the offsets here, but have the hardcoded values left over :)

Dirkjan Bussink Owner
dbussink added a note

BTW, since these should be interpreter as binary strings, it's probably better to use byteslice instead of String#[]

Sylvain Daubert
sdaubert added a note

Oops ! I missed one.
And i'll add #byteslice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sylvain Daubert sdaubert Socket.gethostbyname
* Remove hard-coded offset and size for IPv6.
* Replace String#[] by String#byteslice to get address
  from sockaddr string.
e10cc24
Dirkjan Bussink
Owner

So, one final question, could you perhaps squash the implementation commits into a single commit? So the pull request is 2 commits, one for the specs, one for the implementation?

Sylvain Daubert

I don't know how doing that... I am a git newbie.

sdaubert added some commits
Sylvain Daubert sdaubert Update Socket.gethostbyname to respect two new specs.
Socket.gethostbyname is updated for all ruby versions.

* rakelib/platform.rake:
  Add new structure `sockaddr_in6`

* lib/**/socket.rb :
  Socket.sockaddr_in is used to transform addresses got
  from Socket.getaddrinfo to packed strings. Platform
  configuration is then used to determine offset and size
  of IP address in sockaddr string. `sockaddr_in` and
  `sockaddr_in6` platform structures are used for this
  purpose.
e956c23
Sylvain Daubert sdaubert Merge branch 'socket.gethostbyname' of github.com:sdaubert/rubinius i…
…nto socket.gethostbyname
f5e460f
Sylvain Daubert

I tried something with git rebase, but i'm not sure that's good.
May i do that in another branch ?

Dirkjan Bussink
Owner

git rebase should work yeah, if you do an interactive rebase you can squash commits together. If you have it right locally, you can force push it to overwrite the changes in the pull request.

Otherwise I can merge it locally on my machine and squash it there.

Sylvain Daubert

It seems I made a mess here. Please merge it locally on your machine.

Dirkjan Bussink
Owner

Merged this manually

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

Showing 6 unique commits by 1 author.

Jan 14, 2013
Sylvain Daubert sdaubert Update spec library/socket/socket/gethostbyname.
Add two new specs to verify that addresses in address list returned
by Socket.gethosbyname are in 'pack' format (one spec for IPv4
addresses an the other for IPv6 ones).
45a1371
Sylvain Daubert sdaubert Update Socket.gethostbyname to respect two new specs.
Socket.gethostbyname is updated for all ruby versions.

Socket.sockaddr_in is used to transform addresses got from
Socket.getaddrinfo to packed strings. For an IPv4 address,
address is from 5th byte and is 4 bytes long. For an IPv6
address, address is from 8th byte and is 16 bytes long.
9bda567
Jan 18, 2013
Sylvain Daubert sdaubert Pass spec gethostbyname cleanly.
* rakelib/platform.rake:
  Add new structure `sockaddr_in6`
* lib/**/socket.rb :
  * Modify test to determine IP version. Use family
    instead of sockaddr string size.
  * Use platform configuration to determine offset and
    size of IP address in sockaddr string. `sockaddr_in`
    and `sockaddr_in6` platform structures are used for
    this purpose.
77a888c
Sylvain Daubert sdaubert Socket.gethostbyname
* Remove hard-coded offset and size for IPv6.
* Replace String#[] by String#byteslice to get address
  from sockaddr string.
e10cc24
Sylvain Daubert sdaubert Update Socket.gethostbyname to respect two new specs.
Socket.gethostbyname is updated for all ruby versions.

* rakelib/platform.rake:
  Add new structure `sockaddr_in6`

* lib/**/socket.rb :
  Socket.sockaddr_in is used to transform addresses got
  from Socket.getaddrinfo to packed strings. Platform
  configuration is then used to determine offset and size
  of IP address in sockaddr string. `sockaddr_in` and
  `sockaddr_in6` platform structures are used for this
  purpose.
e956c23
Sylvain Daubert sdaubert Merge branch 'socket.gethostbyname' of github.com:sdaubert/rubinius i…
…nto socket.gethostbyname
f5e460f
This page is out of date. Refresh to see the latest.
18 lib/18/socket.rb
@@ -610,7 +610,23 @@ def self.gethostbyname(hostname)
610 610 alternatives = []
611 611 addrinfos.each do |a|
612 612 alternatives << a[2] unless a[2] == hostname
613   - addresses << a[3] if a[4] == family
  613 + # transform addresses to packed strings
  614 + if a[4] == family
  615 + sockaddr = Socket.sockaddr_in(1, a[3])
  616 + if family == AF_INET
  617 + # IPv4 address
  618 + offset = FFI.config("sockaddr_in.sin_addr.offset")
  619 + size = FFI.config("sockaddr_in.sin_addr.size")
  620 + addresses << sockaddr.byteslice(offset, size)
  621 + elsif family == AF_INET6
  622 + # Ipv6 address
  623 + offset = FFI.config("sockaddr_in6.sin6_addr.offset")
  624 + size = FFI.config("sockaddr_in6.sin6_addr.size")
  625 + addresses << sockaddr.byteslice(offset, size)
  626 + else
  627 + addresses << a[3]
  628 + end
  629 + end
614 630 end
615 631
616 632 [hostname, alternatives.uniq, family] + addresses.uniq
18 lib/19/socket.rb
@@ -687,7 +687,23 @@ def self.gethostbyname(hostname)
687 687 alternatives = []
688 688 addrinfos.each do |a|
689 689 alternatives << a[2] unless a[2] == hostname
690   - addresses << a[3] if a[4] == family
  690 + # transform addresses to packed strings
  691 + if a[4] == family
  692 + sockaddr = Socket.sockaddr_in(1, a[3])
  693 + if family == AF_INET
  694 + # IPv4 address
  695 + offset = FFI.config("sockaddr_in.sin_addr.offset")
  696 + size = FFI.config("sockaddr_in.sin_addr.size")
  697 + addresses << sockaddr.byteslice(offset, size)
  698 + elsif family == AF_INET6
  699 + # Ipv6 address
  700 + offset = FFI.config("sockaddr_in6.sin6_addr.offset")
  701 + size = FFI.config("sockaddr_in6.sin6_addr.size")
  702 + addresses << sockaddr.byteslice(offset, size)
  703 + else
  704 + addresses << a[3]
  705 + end
  706 + end
691 707 end
692 708
693 709 [hostname, alternatives.uniq, family] + addresses.uniq
18 lib/20/socket.rb
@@ -687,7 +687,23 @@ def self.gethostbyname(hostname)
687 687 alternatives = []
688 688 addrinfos.each do |a|
689 689 alternatives << a[2] unless a[2] == hostname
690   - addresses << a[3] if a[4] == family
  690 + # transform addresses to packed strings
  691 + if a[4] == family
  692 + sockaddr = Socket.sockaddr_in(1, a[3])
  693 + if family == AF_INET
  694 + # IPv4 address
  695 + offset = FFI.config("sockaddr_in.sin_addr.offset")
  696 + size = FFI.config("sockaddr_in.sin_addr.size")
  697 + addresses << sockaddr.byteslice(offset, size)
  698 + elsif family == AF_INET6
  699 + # Ipv6 address
  700 + offset = FFI.config("sockaddr_in6.sin6_addr.offset")
  701 + size = FFI.config("sockaddr_in6.sin6_addr.size")
  702 + addresses << sockaddr.byteslice(offset, size)
  703 + else
  704 + addresses << a[3]
  705 + end
  706 + end
691 707 end
692 708
693 709 [hostname, alternatives.uniq, family] + addresses.uniq
17 rakelib/platform.rake
@@ -58,6 +58,23 @@ file 'runtime/platform.conf' => deps do |task|
58 58 s.field :sin_zero, :char_array
59 59 end.write_config(f)
60 60
  61 + Rubinius::FFI::Generators::Structures.new 'sockaddr_in6' do |s|
  62 + if BUILD_CONFIG[:windows]
  63 + s.include "ws2tcpip.h"
  64 + else
  65 + s.include "netinet/in.h"
  66 + s.include "sys/socket.h"
  67 + end
  68 + s.include "fcntl.h"
  69 + s.include "sys/stat.h"
  70 + s.name 'struct sockaddr_in6'
  71 + s.field :sin6_family, :sa_family_t
  72 + s.field :sin6_port, :ushort
  73 + s.field :sin6_flowinfo
  74 + s.field :sin6_addr, :char_array
  75 + s.field :sin6_scope_id
  76 + end.write_config(f)
  77 +
61 78 unless BUILD_CONFIG[:windows]
62 79 sockaddr_un = Rubinius::FFI::Generators::Structures.new 'sockaddr_un' do |s|
63 80 s.include "sys/un.h"
10 spec/ruby/library/socket/socket/gethostbyname_spec.rb
@@ -13,4 +13,14 @@
13 13 addr = Socket.gethostbyname('<any>').first;
14 14 addr.should == "0.0.0.0"
15 15 end
  16 +
  17 + it "returns address list in pack format (IPv4)" do
  18 + laddr = Socket.gethostbyname('127.0.0.1')[3..-1];
  19 + laddr.should == ["\x7f\x00\x00\x01"]
  20 + end
  21 +
  22 + it "returns address list in pack format (IPv6)" do
  23 + laddr = Socket.gethostbyname('::1')[3..-1]
  24 + laddr.should == ["\x00" * 15 + "\x01"]
  25 + end
16 26 end

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.