Land #2855, Rex::Socket refactor and specs
commit
b43a221959
|
@ -155,89 +155,52 @@ module Socket
|
|||
end
|
||||
end
|
||||
|
||||
# Get the first address returned by a DNS lookup for +hostname+.
|
||||
#
|
||||
# Wrapper for Resolv.getaddress that takes special care to see if the
|
||||
# supplied address is already a dotted quad, for instance. This is
|
||||
# necessary to prevent calls to gethostbyaddr (which occurs on windows).
|
||||
# These calls can be quite slow. This also fixes an issue with the
|
||||
# Resolv.getaddress() call being non-functional on Ruby 1.9.1 (Win32).
|
||||
# @see .getaddresses
|
||||
#
|
||||
def self.getaddress(addr, accept_ipv6 = true)
|
||||
begin
|
||||
if addr =~ MATCH_IPV4 or (accept_ipv6 and addr =~ MATCH_IPV6)
|
||||
return addr
|
||||
end
|
||||
|
||||
res = ::Socket.gethostbyname(addr)
|
||||
return nil if not res
|
||||
|
||||
# Shift the first three elements out
|
||||
rname = res.shift
|
||||
ralias = res.shift
|
||||
rtype = res.shift
|
||||
|
||||
# Rubinius has a bug where gethostbyname returns dotted quads instead of
|
||||
# NBO, but that's what we want anyway, so just short-circuit here.
|
||||
if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6
|
||||
res.each { |r|
|
||||
# if the caller doesn't mind ipv6, just return whatever we have
|
||||
return r if accept_ipv6
|
||||
# otherwise, take the first v4 address
|
||||
return r if r =~ MATCH_IPV4
|
||||
}
|
||||
# didn't find one
|
||||
return nil
|
||||
end
|
||||
|
||||
# Reject IPv6 addresses if we don't accept them
|
||||
if not accept_ipv6
|
||||
res.reject!{|nbo| nbo.length != 4}
|
||||
end
|
||||
|
||||
# Make sure we have at least one name
|
||||
return nil if res.length == 0
|
||||
|
||||
# Return the first address of the result
|
||||
self.addr_ntoa( res[0] )
|
||||
rescue ::ArgumentError # Win32 bug
|
||||
nil
|
||||
end
|
||||
# @param (see .getaddresses)
|
||||
# @return [String] ASCII IP address
|
||||
def self.getaddress(hostname, accept_ipv6 = true)
|
||||
getaddresses(hostname, accept_ipv6).first
|
||||
end
|
||||
|
||||
#
|
||||
# Wrapper for Resolv.getaddress that takes special care to see if the
|
||||
# supplied address is already a dotted quad, for instance. This is
|
||||
# necessary to prevent calls to gethostbyaddr (which occurs on windows).
|
||||
# These calls can be quite slow. This also fixes an issue with the
|
||||
# Resolv.getaddress() call being non-functional on Ruby 1.9.1 (Win32).
|
||||
# Wrapper for +::Socket.gethostbyname+ that takes special care to see if the
|
||||
# supplied address is already an ASCII IP address. This is necessary to
|
||||
# prevent blocking while waiting on a DNS reverse lookup when we already
|
||||
# have what we need.
|
||||
#
|
||||
def self.getaddresses(addr, accept_ipv6 = true)
|
||||
begin
|
||||
if addr =~ MATCH_IPV4 or (accept_ipv6 and addr =~ MATCH_IPV6)
|
||||
return [addr]
|
||||
end
|
||||
|
||||
res = ::Socket.gethostbyname(addr)
|
||||
return [] if not res
|
||||
|
||||
# Shift the first three elements out
|
||||
rname = res.shift
|
||||
ralias = res.shift
|
||||
rtype = res.shift
|
||||
|
||||
# Reject IPv6 addresses if we don't accept them
|
||||
if not accept_ipv6
|
||||
res.reject!{|nbo| nbo.length != 4}
|
||||
end
|
||||
|
||||
# Make sure we have at least one name
|
||||
return [] if res.length == 0
|
||||
|
||||
# Return an array of all addresses
|
||||
res.map{ |addr| self.addr_ntoa(addr) }
|
||||
rescue ::ArgumentError # Win32 bug
|
||||
[]
|
||||
# @param hostname [String] A hostname or ASCII IP address
|
||||
# @return [Array<String>]
|
||||
def self.getaddresses(hostname, accept_ipv6 = true)
|
||||
if hostname =~ MATCH_IPV4 or (accept_ipv6 and hostname =~ MATCH_IPV6)
|
||||
return [hostname]
|
||||
end
|
||||
|
||||
res = ::Socket.gethostbyname(hostname)
|
||||
return [] if not res
|
||||
|
||||
# Shift the first three elements out, leaving just the list of
|
||||
# addresses
|
||||
res.shift # name
|
||||
res.shift # alias hostnames
|
||||
res.shift # address_family
|
||||
|
||||
# Rubinius has a bug where gethostbyname returns dotted quads instead of
|
||||
# NBO, but that's what we want anyway, so just short-circuit here.
|
||||
if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6
|
||||
unless accept_ipv6
|
||||
res.reject!{ |ascii| ascii =~ MATCH_IPV6 }
|
||||
end
|
||||
else
|
||||
unless accept_ipv6
|
||||
res.reject!{ |nbo| nbo.length != 4 }
|
||||
end
|
||||
res.map!{ |nbo| self.addr_ntoa(nbo) }
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -252,7 +215,9 @@ module Socket
|
|||
end
|
||||
|
||||
if is_ipv6?(host)
|
||||
host, scope_id = host.split('%', 2)
|
||||
# pop off the scopeid since gethostbyname isn't smart enough to
|
||||
# deal with it.
|
||||
host, _ = host.split('%', 2)
|
||||
end
|
||||
|
||||
::Socket.gethostbyname(host)
|
||||
|
@ -445,7 +410,7 @@ module Socket
|
|||
# @param addr [Numeric] The address as a number
|
||||
# @param v6 [Boolean] Whether +addr+ is IPv6
|
||||
def self.addr_iton(addr, v6=false)
|
||||
if(addr < 0x100000000 and not v6)
|
||||
if(addr < 0x100000000 && !v6)
|
||||
return [addr].pack('N')
|
||||
else
|
||||
w = []
|
||||
|
@ -628,7 +593,7 @@ module Socket
|
|||
#
|
||||
def self.ipv6_link_address(intf)
|
||||
r = source_address("FF02::1%#{intf}")
|
||||
return if not (r and r =~ /^fe80/i)
|
||||
return nil if r.nil? || r !~ /^fe80/i
|
||||
r
|
||||
end
|
||||
|
||||
|
@ -679,7 +644,7 @@ module Socket
|
|||
lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname )
|
||||
end
|
||||
}
|
||||
lsock, saddr = server.accept
|
||||
lsock, _ = server.accept
|
||||
server.close
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'rex/socket/range_walker'
|
||||
|
||||
describe Rex::Socket do
|
||||
|
||||
describe '.addr_itoa' do
|
||||
|
||||
context 'with explicit v6' do
|
||||
it "should convert a number to a human-readable IPv6 address" do
|
||||
described_class.addr_itoa(1, true).should == "::1"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with explicit v4' do
|
||||
it "should convert a number to a human-readable IPv4 address" do
|
||||
described_class.addr_itoa(1, false).should == "0.0.0.1"
|
||||
end
|
||||
end
|
||||
|
||||
context 'without explicit version' do
|
||||
it "should convert a number within the range of possible v4 addresses to a human-readable IPv4 address" do
|
||||
described_class.addr_itoa(0).should == "0.0.0.0"
|
||||
described_class.addr_itoa(1).should == "0.0.0.1"
|
||||
described_class.addr_itoa(0xffff_ffff).should == "255.255.255.255"
|
||||
end
|
||||
it "should convert a number larger than possible v4 addresses to a human-readable IPv6 address" do
|
||||
described_class.addr_itoa(0xfe80_0000_0000_0000_0000_0000_0000_0001).should == "fe80::1"
|
||||
described_class.addr_itoa(0x1_0000_0001).should == "::1:0:1"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.addr_aton' do
|
||||
subject(:nbo) do
|
||||
described_class.addr_aton(try)
|
||||
end
|
||||
|
||||
context 'with ipv6' do
|
||||
let(:try) { "fe80::1" }
|
||||
it { should be_a(String) }
|
||||
it { should have(16).bytes }
|
||||
it "should be in the right order" do
|
||||
nbo.should == "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ipv4' do
|
||||
let(:try) { "127.0.0.1" }
|
||||
it { should be_a(String) }
|
||||
it { should have(4).bytes }
|
||||
it "should be in the right order" do
|
||||
nbo.should == "\x7f\x00\x00\x01"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a hostname' do
|
||||
let(:try) { "localhost" }
|
||||
it "should resolve" do
|
||||
nbo.should be_a(String)
|
||||
nbo.encoding.should == Encoding.find('binary')
|
||||
[ 4, 16 ].should include(nbo.length)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.compress_address' do
|
||||
|
||||
subject(:compressed) do
|
||||
described_class.compress_address(try)
|
||||
end
|
||||
|
||||
context 'with lots of single 0s' do
|
||||
let(:try) { "fe80:0:0:0:0:0:0:1" }
|
||||
it { should == "fe80::1" }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.getaddress' do
|
||||
|
||||
subject { described_class.getaddress('whatever') }
|
||||
|
||||
before(:each) do
|
||||
Socket.stub(:gethostbyname).and_return(['name', ['aliases'], response_afamily, *response_addresses])
|
||||
end
|
||||
|
||||
context 'when ::Socket.gethostbyname returns IPv4 responses' do
|
||||
let(:response_afamily) { Socket::AF_INET }
|
||||
let(:response_addresses) { ["\x01\x01\x01\x01", "\x02\x02\x02\x02"] }
|
||||
|
||||
it { should be_a(String) }
|
||||
it "should return the first ASCII address" do
|
||||
subject.should == "1.1.1.1"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ::Socket.gethostbyname returns IPv6 responses' do
|
||||
let(:response_afamily) { Socket::AF_INET6 }
|
||||
let(:response_addresses) { ["\xfe\x80"+("\x00"*13)+"\x01", "\xfe\x80"+("\x00"*13)+"\x02"] }
|
||||
|
||||
it { should be_a(String) }
|
||||
it "should return the first ASCII address" do
|
||||
subject.should == "fe80::1"
|
||||
end
|
||||
end
|
||||
|
||||
context "with rubinius' bug returning ASCII addresses" do
|
||||
let(:response_afamily) { Socket::AF_INET }
|
||||
let(:response_addresses) { ["1.1.1.1", "2.2.2.2"] }
|
||||
|
||||
it { should be_a(String) }
|
||||
it "should return the first ASCII address" do
|
||||
subject.should == "1.1.1.1"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe '.getaddresses' do
|
||||
|
||||
subject { described_class.getaddresses('whatever') }
|
||||
|
||||
before(:each) do
|
||||
Socket.stub(:gethostbyname).and_return(['name', ['aliases'], response_afamily, *response_addresses])
|
||||
end
|
||||
|
||||
context 'when ::Socket.gethostbyname returns IPv4 responses' do
|
||||
let(:response_afamily) { Socket::AF_INET }
|
||||
let(:response_addresses) { ["\x01\x01\x01\x01", "\x02\x02\x02\x02"] }
|
||||
|
||||
it { should be_a(Array) }
|
||||
it { should have(2).addresses }
|
||||
it "should return the ASCII addresses" do
|
||||
subject.should include("1.1.1.1")
|
||||
subject.should include("2.2.2.2")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ::Socket.gethostbyname returns IPv6 responses' do
|
||||
let(:response_afamily) { Socket::AF_INET6 }
|
||||
let(:response_addresses) { ["\xfe\x80"+("\x00"*13)+"\x01", "\xfe\x80"+("\x00"*13)+"\x02"] }
|
||||
|
||||
it { should be_a(Array) }
|
||||
it { should have(2).addresses }
|
||||
it "should return the ASCII addresses" do
|
||||
subject.should include("fe80::1")
|
||||
subject.should include("fe80::2")
|
||||
end
|
||||
end
|
||||
|
||||
context "with rubinius' bug returning ASCII addresses" do
|
||||
let(:response_afamily) { Socket::AF_INET }
|
||||
let(:response_addresses) { ["1.1.1.1", "2.2.2.2"] }
|
||||
|
||||
it { should be_a(Array) }
|
||||
it { should have(2).addresses }
|
||||
it "should return the ASCII addresses" do
|
||||
subject.should include("1.1.1.1")
|
||||
subject.should include("2.2.2.2")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue