socketry/rubydns

IPv6 Support

Closed this issue · 5 comments

Hi,

I see that you have "excluded" IPv6 support. Which is really bad.

When I've used :: as bind host in your example,

INTERFACES = [
    [:udp, "::", 5300],
    [:tcp, "::", 5300]
]

it gave me this error :

  Errno::EAFNOSUPPORT: Address family not supported by protocol - bind(2) for "::" port 5300

Quick look at rubydns-0.9.1/lib/rubydns/handler.rb

shows @126

 def initialize(server, host, port)
      socket = UDPSocket.new

      socket.bind(host, port)

      super(server, socket)
    end

now changing line 127 to :

      socket = UDPSocket.new Socket::AF_INET6

Gets it running like a charm :

ruby example.rb          
I, [2014-12-13T03:05:39.921812 #16965]  INFO -- : Starting RubyDNS server (v0.9.1)...
I, [2014-12-13T03:05:39.921953 #16965]  INFO -- : <> Listening on udp::::5300
I, [2014-12-13T03:05:39.923565 #16965]  INFO -- : <> Listening on tcp::::5300

with netstat confirming opening right sockets:

udp6       0      0 :::5300                 :::*                                16965/ruby      
tcp6       0      0 :::5300                 :::*                    LISTEN      16965/ruby      

and dig confirming it works as expected :

 dig @localhost -p 5300 test.mydomain.org

; <<>> DiG 9.9.5-3ubuntu0.1-Ubuntu <<>> @localhost -p 5300 test.mydomain.org
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37126
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;test.mydomain.org.     IN  A

;; ANSWER SECTION:
test.mydomain.org.  86400   IN  A   10.0.0.80

;; Query time: 2 msec
;; SERVER: ::1#5300(::1)

with that little change you can have it correctly listening on IPv6 sockets, they are brilliant, because on most systems they also enable IPv4 listening :

same example script running, just query over legacy IPv4

dig -4 @localhost -p 5300 test.mydomain.org

; <<>> DiG 9.9.5-3ubuntu0.1-Ubuntu <<>> -4 @localhost -p 5300 test.mydomain.org
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55503
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;test.mydomain.org.     IN  A

;; ANSWER SECTION:
test.mydomain.org.  86400   IN  A   10.0.0.80

;; Query time: 1 msec
;; SERVER: 127.0.0.1#5300(127.0.0.1)

So the only thing missing would be distinguishing between UDP sockets between IPv6 and IPv4 host, which is darn simple with a regex or IPAddr or even, pass it as an attribute so we can decide.

Thanks for the awesome bug report.

I'm surprised you need to specify the constant Socket::AF_INET6, I thought with the correct address (i.e. ::) it would work as expected.

Granted, this has clearly never been tested, so thanks for the thorough review of the current situation.

What solution do you think makes the most sense?

Whatever solution we choose, clearly we should write some specs for it to ensure IPv6 support is well maintained.

If we forgo using "DNS" hostnames it's as simple as 'ipaddr' ( included in stdlib ) can tell you what to use

example (host is a variable containing IPv4/IPv6 address) :

require 'ipaddr' 
host_address = IPAddr.new host
family = host_address.ipv6? ? Socket::AF_INET6 : Socket::AF_INET 

socket = UDPSocket.new family

Okay, so we could make a wrapper, e.g.

def network_address(address)
    if IPAddr === address
       return address
    else
        return IPAddr.new(address)
    end
end

Then in the code for the socket use the code you proposed?

Yup, looks good :)

Okay just release 0.9.2 which fixes this issue.