Skip to content

Commit 1e83526

Browse files
committed
Merge pull request #599 from estolfo/FREE-72862-resolvers
Address uses getaddrinfo to determine protocol
2 parents bfd7b87 + 8a3c123 commit 1e83526

File tree

10 files changed

+209
-36
lines changed

10 files changed

+209
-36
lines changed

lib/mongo/address.rb

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ def hash
8989
# @since 2.0.0
9090
def initialize(seed, options = {})
9191
address = seed.downcase
92-
case address
93-
when Unix::MATCH then @resolver = Unix.new(address)
94-
when IPv6::MATCH then @resolver = IPv6.new(address)
95-
else @resolver = IPv4.new(address)
92+
host, port = match(address)
93+
case family(host)
94+
when ::Socket::PF_UNIX then @resolver = Unix.new(host, address)
95+
when ::Socket::AF_INET6 then @resolver = IPv6.new(host, port, address)
96+
else @resolver = IPv4.new(host, port, address)
9697
end
9798
end
9899

@@ -107,5 +108,20 @@ def initialize(seed, options = {})
107108
def inspect
108109
"#<Mongo::Address:0x#{object_id} address=#{resolver.to_s}>"
109110
end
111+
112+
private
113+
114+
def family(host)
115+
fam = (host == 'localhost') ? ::Socket::AF_INET : ::Socket::AF_UNSPEC
116+
::Socket.getaddrinfo(host, nil, fam, ::Socket::SOCK_STREAM).first[4]
117+
end
118+
119+
def match(address)
120+
case address
121+
when Unix::MATCH then Unix.parse(address)
122+
when IPv6::MATCH then IPv6.parse(address)
123+
else IPv4.parse(address)
124+
end
125+
end
110126
end
111127
end

lib/mongo/address/ipv4.rb

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,36 @@ class IPv4
3535
# @since 2.0.0
3636
MATCH = Regexp.new('/\./').freeze
3737

38+
# Parse an IPv4 address into its host and port.
39+
#
40+
# @example Parse the address.
41+
# IPv4.parse("127.0.0.1:28011")
42+
#
43+
# @param [ String ] address The address to parse.
44+
#
45+
# @return [ Array<String, Integer> ] The host and port pair.
46+
#
47+
# @since 2.0.0
48+
def self.parse(address)
49+
parts = address.split(':')
50+
host = parts[0]
51+
port = (parts[1] || 27017).to_i
52+
[ host, port ]
53+
end
54+
3855
# Initialize the IPv4 resolver.
3956
#
4057
# @example Initialize the resolver.
41-
# IPv4.new("127.0.0.1:28011")
58+
# IPv4.new("127.0.0.1", 27017, "127.0.0.1:28011")
4259
#
43-
# @param [ String ] address The address to resolve.
60+
# @param [ String ] host The host.
61+
# @param [ Integer ] port The port.
62+
# @param [ String ] address The seed address.
4463
#
4564
# @since 2.0.0
46-
def initialize(address)
47-
parts = address.split(':')
48-
@host = parts[0]
49-
@port = (parts[1] || 27017).to_i
65+
def initialize(host, port, address)
66+
@host = host
67+
@port = port
5068
@seed = address
5169
end
5270

lib/mongo/address/ipv6.rb

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,36 @@ class IPv6
3535
# @since 2.0.0
3636
MATCH = Regexp.new('::').freeze
3737

38+
# Parse an IPv6 address into its host and port.
39+
#
40+
# @example Parse the address.
41+
# IPv4.parse("[::1]:28011")
42+
#
43+
# @param [ String ] address The address to parse.
44+
#
45+
# @return [ Array<String, Integer> ] The host and port pair.
46+
#
47+
# @since 2.0.0
48+
def self.parse(address)
49+
parts = address.match(/\[(.+)\]:?(.+)?/)
50+
host = parts[1]
51+
port = (parts[2] || 27017).to_i
52+
[ host, port ]
53+
end
54+
3855
# Initialize the IPv6 resolver.
3956
#
4057
# @example Initialize the resolver.
41-
# IPv6.new("[::1]:28011")
58+
# IPv6.new("::1", 28011, "[::1]:28011")
4259
#
43-
# @param [ String ] address The address to resolve.
60+
# @param [ String ] host The host.
61+
# @param [ Integer ] port The port.
62+
# @param [ String ] address The seed address.
4463
#
4564
# @since 2.0.0
46-
def initialize(address)
47-
parts = address.match(/\[(.+)\]:?(.+)?/)
48-
@host = parts[1]
49-
@port = (parts[2] || 27017).to_i
65+
def initialize(host, port, address)
66+
@host = host
67+
@port = port
5068
@seed = address
5169
end
5270

lib/mongo/address/unix.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,31 @@ class Unix
3434
# @since 2.0.0
3535
MATCH = Regexp.new('\.sock').freeze
3636

37+
# Parse a socket path.
38+
#
39+
# @example Parse the address.
40+
# Unix.parse("/path/to/socket.sock")
41+
#
42+
# @param [ String ] address The address to parse.
43+
#
44+
# @return [ Array<String> ] A list with the host (socket path).
45+
#
46+
# @since 2.0.0
47+
def self.parse(address)
48+
[ address ]
49+
end
50+
3751
# Initialize the socket resolver.
3852
#
3953
# @example Initialize the resolver.
40-
# Sock.new("/path/to/socket.sock")
54+
# Unix.new("/path/to/socket.sock", "/path/to/socket.sock")
4155
#
42-
# @param [ String ] address The socket path.
56+
# @param [ String ] host The host.
57+
# @param [ String ] address The seed address.
4358
#
4459
# @since 2.0.0
45-
def initialize(address)
46-
@host = address
60+
def initialize(host, address)
61+
@host = host
4762
@seed = address
4863
end
4964

spec/mongo/address/ipv4_spec.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,33 @@
22

33
describe Mongo::Address::IPv4 do
44

5+
let(:resolver) do
6+
described_class.new(*described_class.parse(address), address)
7+
end
8+
9+
describe 'self.parse' do
10+
11+
context 'when a port is provided' do
12+
13+
it 'returns the host and port' do
14+
expect(described_class.parse('127.0.0.1:27017')).to eq(['127.0.0.1', 27017])
15+
end
16+
end
17+
18+
context 'when no port is provided' do
19+
20+
it 'returns the host and port' do
21+
expect(described_class.parse('127.0.0.1')).to eq(['127.0.0.1', 27017])
22+
end
23+
end
24+
end
25+
526
describe '#initialize' do
627

728
context 'when a port is provided' do
829

9-
let(:resolver) do
10-
described_class.new('127.0.0.1:27017')
30+
let(:address) do
31+
'127.0.0.1:27017'
1132
end
1233

1334
it 'sets the port' do
@@ -21,8 +42,8 @@
2142

2243
context 'when no port is provided' do
2344

24-
let(:resolver) do
25-
described_class.new('127.0.0.1')
45+
let(:address) do
46+
'127.0.0.1'
2647
end
2748

2849
it 'sets the port to 27017' do
@@ -37,8 +58,8 @@
3758

3859
describe '#socket' do
3960

40-
let(:resolver) do
41-
described_class.new('127.0.0.1')
61+
let(:address) do
62+
'127.0.0.1'
4263
end
4364

4465
context 'when ssl options are provided' do

spec/mongo/address/ipv6_spec.rb

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,38 @@
22

33
describe Mongo::Address::IPv6 do
44

5+
before do
6+
allow(::Socket).to receive(:getaddrinfo).at_most(2).times.
7+
and_return([[nil,nil,nil,nil,::Socket::AF_INET6]])
8+
end
9+
10+
let(:resolver) do
11+
described_class.new(*described_class.parse(address), address)
12+
end
13+
14+
describe 'self.parse' do
15+
16+
context 'when a port is provided' do
17+
18+
it 'returns the host and port' do
19+
expect(described_class.parse('[::1]:27017')).to eq(['::1', 27017])
20+
end
21+
end
22+
23+
context 'when no port is provided' do
24+
25+
it 'returns the host and port' do
26+
expect(described_class.parse('[::1]')).to eq(['::1', 27017])
27+
end
28+
end
29+
end
30+
531
describe '#initialize' do
632

733
context 'when a port is provided' do
834

9-
let(:resolver) do
10-
described_class.new('[::1]:27017')
35+
let(:address) do
36+
'[::1]:27017'
1137
end
1238

1339
it 'sets the port' do
@@ -21,8 +47,8 @@
2147

2248
context 'when no port is provided' do
2349

24-
let(:resolver) do
25-
described_class.new('[::1]')
50+
let(:address) do
51+
'[::1]'
2652
end
2753

2854
it 'sets the port to 27017' do
@@ -37,8 +63,8 @@
3763

3864
describe '#socket' do
3965

40-
let(:resolver) do
41-
described_class.new('[::1]')
66+
let(:address) do
67+
'[::1]'
4268
end
4369

4470
context 'when ssl options are provided' do

spec/mongo/address/unix_spec.rb

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,26 @@
22

33
describe Mongo::Address::Unix do
44

5+
before do
6+
allow(::Socket).to receive(:getaddrinfo).at_most(2).times.
7+
and_return([[nil,nil,nil,nil,::Socket::PF_UNIX]])
8+
end
9+
10+
let(:resolver) do
11+
described_class.new(*described_class.parse(address), address)
12+
end
13+
14+
describe 'self.parse' do
15+
16+
it 'returns the host and no port' do
17+
expect(described_class.parse('/path/to/socket.sock')).to eq(['/path/to/socket.sock'])
18+
end
19+
end
20+
521
describe '#initialize' do
622

7-
let(:resolver) do
8-
described_class.new('/path/to/socket.sock')
23+
let(:address) do
24+
'/path/to/socket.sock'
925
end
1026

1127
it 'sets the host' do
@@ -15,8 +31,8 @@
1531

1632
describe '#socket' do
1733

18-
let(:resolver) do
19-
described_class.new('/path/to/socket.sock')
34+
let(:address) do
35+
'/path/to/socket.sock'
2036
end
2137

2238
let(:socket) do

spec/mongo/address_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@
6262

6363
context 'when the addresses are identical unix sockets' do
6464

65+
before do
66+
allow(::Socket).to receive(:getaddrinfo).twice.
67+
and_return([[nil,nil,nil,nil,::Socket::PF_UNIX]])
68+
end
69+
6570
let(:address) do
6671
described_class.new('/path/to/socket.sock')
6772
end
@@ -124,6 +129,11 @@
124129

125130
context 'when providing an ipv6 host' do
126131

132+
before do
133+
allow(::Socket).to receive(:getaddrinfo).once.
134+
and_return([[nil,nil,nil,nil,::Socket::AF_INET6]])
135+
end
136+
127137
context 'when a port is provided' do
128138

129139
let(:address) do
@@ -190,6 +200,11 @@
190200

191201
context 'when providing a socket path' do
192202

203+
before do
204+
allow(::Socket).to receive(:getaddrinfo).once.
205+
and_return([[nil,nil,nil,nil,::Socket::PF_UNIX]])
206+
end
207+
193208
let(:address) do
194209
described_class.new('/path/to/socket.sock')
195210
end

spec/mongo/server_discovery_and_monitoring_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@
1111

1212
before(:all) do
1313

14+
# We monkey-patch the address, so that looking up the spec's hostname does
15+
# not throw an error.
16+
#
17+
# @since 2.0.0
18+
class Mongo::Address
19+
private
20+
21+
def family(host)
22+
fam = host == 'localhost' ? ::Socket::AF_INET : ::Socket::AF_UNSPEC
23+
::Socket.getaddrinfo(host, nil, fam, ::Socket::SOCK_STREAM).first[4]
24+
rescue SocketError
25+
end
26+
end
27+
1428
# We monkey-patch the server here, so the monitors do not run and no
1529
# real TCP connection is attempted. Thus we can control the server
1630
# descriptions per-phase.
@@ -60,6 +74,15 @@ def disconnect!
6074
@monitor.stop! and true
6175
end
6276
end
77+
78+
class Mongo::Address
79+
private
80+
81+
def family(host)
82+
fam = host == 'localhost' ? ::Socket::AF_INET : ::Socket::AF_UNSPEC
83+
::Socket.getaddrinfo(host, nil, fam, ::Socket::SOCK_STREAM).first[4]
84+
end
85+
end
6386
end
6487

6588
spec.phases.each_with_index do |phase, index|

0 commit comments

Comments
 (0)