Skip to content

Commit 7b692fc

Browse files
committed
Merge pull request #243 from astratto/connect_timeout
Set a connect_timeout for the creation of a socket
2 parents 4671db5 + f6611e2 commit 7b692fc

File tree

8 files changed

+71
-33
lines changed

8 files changed

+71
-33
lines changed

lib/net/ber.rb

+7-7
Original file line numberDiff line numberDiff line change
@@ -293,24 +293,24 @@ def to_arr
293293

294294
##
295295
# A String object with a BER identifier attached.
296-
#
296+
#
297297
class Net::BER::BerIdentifiedString < String
298298
attr_accessor :ber_identifier
299299

300300
# The binary data provided when parsing the result of the LDAP search
301301
# has the encoding 'ASCII-8BIT' (which is basically 'BINARY', or 'unknown').
302-
#
302+
#
303303
# This is the kind of a backtrace showing how the binary `data` comes to
304304
# BerIdentifiedString.new(data):
305305
#
306306
# @conn.read_ber(syntax)
307307
# -> StringIO.new(self).read_ber(syntax), i.e. included from module
308-
# -> Net::BER::BERParser.read_ber(syntax)
308+
# -> Net::BER::BERParser.read_ber(syntax)
309309
# -> (private)Net::BER::BERParser.parse_ber_object(syntax, id, data)
310-
#
310+
#
311311
# In the `#parse_ber_object` method `data`, according to its OID, is being
312312
# 'casted' to one of the Net::BER:BerIdentifiedXXX classes.
313-
#
313+
#
314314
# As we are using LDAP v3 we can safely assume that the data is encoded
315315
# in UTF-8 and therefore the only thing to be done when instantiating is to
316316
# switch the encoding from 'ASCII-8BIT' to 'UTF-8'.
@@ -322,9 +322,9 @@ class Net::BER::BerIdentifiedString < String
322322
# I have no clue how this encodings function.
323323
def initialize args
324324
super
325-
#
325+
#
326326
# Check the encoding of the newly created String and set the encoding
327-
# to 'UTF-8' (NOTE: we do NOT change the bytes, but only set the
327+
# to 'UTF-8' (NOTE: we do NOT change the bytes, but only set the
328328
# encoding to 'UTF-8').
329329
current_encoding = encoding
330330
if current_encoding == Encoding::BINARY

lib/net/ldap.rb

+17-7
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ class LDAP
7979
#
8080
# p ldap.get_operation_result
8181
#
82+
# === Setting connect timeout
83+
#
84+
# By default, Net::LDAP uses TCP sockets with a connection timeout of 5 seconds.
85+
#
86+
# This value can be tweaked passing the :connect_timeout parameter.
87+
# i.e.
88+
# ldap = Net::LDAP.new ...,
89+
# :connect_timeout => 3
8290
#
8391
# == A Brief Introduction to LDAP
8492
#
@@ -487,22 +495,22 @@ def self.result2string(code) #:nodoc:
487495
# The :start_tls like the :simple_tls encryption method also encrypts all
488496
# communcations with the LDAP server. With the exception that it operates
489497
# over the standard TCP port.
490-
#
498+
#
491499
# In order to verify certificates and enable other TLS options, the
492500
# :tls_options hash can be passed alongside :simple_tls or :start_tls.
493501
# This hash contains any options that can be passed to
494502
# OpenSSL::SSL::SSLContext#set_params(). The most common options passed
495503
# should be OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, or the :ca_file option,
496504
# which contains a path to a Certificate Authority file (PEM-encoded).
497-
#
505+
#
498506
# Example for a default setup without custom settings:
499507
# {
500508
# :method => :simple_tls,
501509
# :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
502510
# }
503-
#
511+
#
504512
# Example for specifying a CA-File and only allowing TLSv1.1 connections:
505-
#
513+
#
506514
# {
507515
# :method => :start_tls,
508516
# :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" }
@@ -524,6 +532,7 @@ def initialize(args = {})
524532
@base = args[:base] || DefaultTreebase
525533
@force_no_page = args[:force_no_page] || DefaultForceNoPage
526534
@encryption = args[:encryption] # may be nil
535+
@connect_timeout = args[:connect_timeout]
527536

528537
if pr = @auth[:password] and pr.respond_to?(:call)
529538
@auth[:password] = pr.call
@@ -587,7 +596,7 @@ def authenticate(username, password)
587596
# additional capabilities are added, more configuration values will be
588597
# added here.
589598
#
590-
# This method is deprecated.
599+
# This method is deprecated.
591600
#
592601
def encryption(args)
593602
warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new"
@@ -1247,8 +1256,9 @@ def new_connection
12471256
:port => @port,
12481257
:hosts => @hosts,
12491258
:encryption => @encryption,
1250-
:instrumentation_service => @instrumentation_service
1251-
rescue Errno::ECONNREFUSED, Net::LDAP::ConnectionRefusedError => e
1259+
:instrumentation_service => @instrumentation_service,
1260+
:connect_timeout => @connect_timeout
1261+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::LDAP::ConnectionRefusedError => e
12521262
@result = {
12531263
:resultCode => 52,
12541264
:errorMessage => ResultStrings[ResultCodeUnavailable]

lib/net/ldap/connection.rb

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
class Net::LDAP::Connection #:nodoc:
44
include Net::LDAP::Instrumentation
55

6+
# Seconds before failing for socket connect timeout
7+
DefaultConnectTimeout = 5
8+
69
LdapVersion = 3
710
MaxSaslChallenges = 10
811

@@ -31,10 +34,14 @@ def open_connection(server)
3134
hosts = server[:hosts]
3235
encryption = server[:encryption]
3336

37+
socket_opts = {
38+
connect_timeout: server[:connect_timeout] || DefaultConnectTimeout
39+
}
40+
3441
errors = []
3542
hosts.each do |host, port|
3643
begin
37-
prepare_socket(server.merge(socket: TCPSocket.new(host, port)))
44+
prepare_socket(server.merge(socket: Socket.tcp(host, port, socket_opts)))
3845
return
3946
rescue Net::LDAP::Error, SocketError, SystemCallError,
4047
OpenSSL::SSL::SSLError => e

script/install-openldap

+3
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,7 @@ chgrp ssl-cert /etc/ssl/private/ldap01_slapd_key.pem
109109
chmod g+r /etc/ssl/private/ldap01_slapd_key.pem
110110
chmod o-r /etc/ssl/private/ldap01_slapd_key.pem
111111

112+
# Drop packets on a secondary port used to specific timeout tests
113+
iptables -A OUTPUT -p tcp -j DROP --dport 8389
114+
112115
service slapd restart

test/ber/test_ber.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ def test_binary_data
130130
def test_ascii_data_in_utf8
131131
data = "some text".force_encoding("UTF-8")
132132
bis = Net::BER::BerIdentifiedString.new(data)
133-
133+
134134
assert bis.valid_encoding?, "should be a valid encoding"
135135
assert_equal "UTF-8", bis.encoding.name
136136
end
137-
137+
138138
def test_umlaut_data_in_utf8
139139
data = "Müller".force_encoding("UTF-8")
140140
bis = Net::BER::BerIdentifiedString.new(data)

test/integration/test_bind.rb

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ def test_bind_success
55
assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect
66
end
77

8+
def test_bind_timeout
9+
@ldap.port = 8389
10+
error = assert_raise Net::LDAP::Error do
11+
@ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1")
12+
end
13+
assert_equal('Connection timed out - user specified timeout', error.message)
14+
end
15+
816
def test_bind_anonymous_fail
917
refute @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: ""), @ldap.get_operation_result.inspect
1018

test/test_auth_adapter.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
class TestAuthAdapter < Test::Unit::TestCase
44
def test_undefined_auth_adapter
5-
flexmock(TCPSocket).should_receive(:new).ordered.with('ldap.example.com', 379).once.and_return(nil)
5+
flexmock(Socket).should_receive(:tcp).ordered.with('ldap.example.com', 379, { connect_timeout: 5 }).once.and_return(nil)
6+
67
conn = Net::LDAP::Connection.new(host: 'ldap.example.com', port: 379)
78
assert_raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (foo)" do
89
conn.bind(method: :foo)

test/test_ldap_connection.rb

+24-15
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ def test_list_of_hosts_with_first_host_successful
1515
['test2.mocked.com', 636],
1616
['test3.mocked.com', 636],
1717
]
18-
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_return(nil)
19-
flexmock(TCPSocket).should_receive(:new).ordered.never
18+
flexmock(Socket).should_receive(:tcp).ordered.with(*hosts[0], { connect_timeout: 5 }).once.and_return(nil)
19+
flexmock(Socket).should_receive(:tcp).ordered.never
2020
Net::LDAP::Connection.new(:hosts => hosts)
2121
end
2222

@@ -26,9 +26,9 @@ def test_list_of_hosts_with_first_host_failure
2626
['test2.mocked.com', 636],
2727
['test3.mocked.com', 636],
2828
]
29-
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
30-
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_return(nil)
31-
flexmock(TCPSocket).should_receive(:new).ordered.never
29+
flexmock(Socket).should_receive(:tcp).ordered.with(*hosts[0], { connect_timeout: 5 }).once.and_raise(SocketError)
30+
flexmock(Socket).should_receive(:tcp).ordered.with(*hosts[1], { connect_timeout: 5 }).once.and_return(nil)
31+
flexmock(Socket).should_receive(:tcp).ordered.never
3232
Net::LDAP::Connection.new(:hosts => hosts)
3333
end
3434

@@ -38,17 +38,17 @@ def test_list_of_hosts_with_all_hosts_failure
3838
['test2.mocked.com', 636],
3939
['test3.mocked.com', 636],
4040
]
41-
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
42-
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_raise(SocketError)
43-
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[2]).once.and_raise(SocketError)
44-
flexmock(TCPSocket).should_receive(:new).ordered.never
41+
flexmock(Socket).should_receive(:tcp).ordered.with(*hosts[0], { connect_timeout: 5 }).once.and_raise(SocketError)
42+
flexmock(Socket).should_receive(:tcp).ordered.with(*hosts[1], { connect_timeout: 5 }).once.and_raise(SocketError)
43+
flexmock(Socket).should_receive(:tcp).ordered.with(*hosts[2], { connect_timeout: 5 }).once.and_raise(SocketError)
44+
flexmock(Socket).should_receive(:tcp).ordered.never
4545
assert_raise Net::LDAP::ConnectionError do
4646
Net::LDAP::Connection.new(:hosts => hosts)
4747
end
4848
end
4949

5050
def test_result_for_connection_failed_is_set
51-
flexmock(TCPSocket).should_receive(:new).and_raise(Errno::ECONNREFUSED)
51+
flexmock(Socket).should_receive(:tcp).and_raise(Errno::ECONNREFUSED)
5252

5353
ldap_client = Net::LDAP.new(host: '127.0.0.1', port: 12345)
5454

@@ -67,14 +67,14 @@ def test_unresponsive_host
6767
end
6868

6969
def test_blocked_port
70-
flexmock(TCPSocket).should_receive(:new).and_raise(SocketError)
70+
flexmock(Socket).should_receive(:tcp).and_raise(SocketError)
7171
assert_raise Net::LDAP::Error do
7272
Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
7373
end
7474
end
7575

7676
def test_connection_refused
77-
flexmock(TCPSocket).should_receive(:new).and_raise(Errno::ECONNREFUSED)
77+
flexmock(Socket).should_receive(:tcp).and_raise(Errno::ECONNREFUSED)
7878
stderr = capture_stderr do
7979
assert_raise Net::LDAP::ConnectionRefusedError do
8080
Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
@@ -83,9 +83,18 @@ def test_connection_refused
8383
assert_equal("Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead.\n", stderr)
8484
end
8585

86+
def test_connection_timedout
87+
flexmock(Socket).should_receive(:tcp).and_raise(Errno::ETIMEDOUT)
88+
stderr = capture_stderr do
89+
assert_raise Net::LDAP::Error do
90+
Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
91+
end
92+
end
93+
end
94+
8695
def test_raises_unknown_exceptions
8796
error = Class.new(StandardError)
88-
flexmock(TCPSocket).should_receive(:new).and_raise(error)
97+
flexmock(Socket).should_receive(:tcp).and_raise(error)
8998
assert_raise error do
9099
Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
91100
end
@@ -328,7 +337,7 @@ class TestLDAPConnectionErrors < Test::Unit::TestCase
328337
def setup
329338
@tcp_socket = flexmock(:connection)
330339
@tcp_socket.should_receive(:write)
331-
flexmock(TCPSocket).should_receive(:new).and_return(@tcp_socket)
340+
flexmock(Socket).should_receive(:tcp).and_return(@tcp_socket)
332341
@connection = Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
333342
end
334343

@@ -357,7 +366,7 @@ class TestLDAPConnectionInstrumentation < Test::Unit::TestCase
357366
def setup
358367
@tcp_socket = flexmock(:connection)
359368
@tcp_socket.should_receive(:write)
360-
flexmock(TCPSocket).should_receive(:new).and_return(@tcp_socket)
369+
flexmock(Socket).should_receive(:tcp).and_return(@tcp_socket)
361370

362371
@service = MockInstrumentationService.new
363372
@connection = Net::LDAP::Connection.new \

0 commit comments

Comments
 (0)