Skip to content

Commit 5dc45d8

Browse files
committed
Add time protection to DNS resolution, when applicable
1 parent 484602b commit 5dc45d8

File tree

1 file changed

+33
-2
lines changed

1 file changed

+33
-2
lines changed

lib/dalli/socket.rb

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'resolv'
4+
35
module Dalli
46
module Socket
57
module InstanceMethods
@@ -64,8 +66,7 @@ class TCP < ::Socket
6466
attr_accessor :options, :server
6567

6668
def self.open(host, port, server, options = {})
67-
addr_info = ::Socket.getaddrinfo(host, nil, ::Socket::AF_UNSPEC, ::Socket::SOCK_STREAM)
68-
ai = addr_info.first
69+
ai = resolve_address(host, options[:socket_timeout])
6970
sock = new(ai[4], ::Socket::SOCK_STREAM, 0)
7071

7172
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
@@ -93,6 +94,36 @@ def self.open(host, port, server, options = {})
9394
sock&.close rescue nil
9495
raise
9596
end
97+
98+
# Resolve a hostname to structured address info with timeout protection.
99+
# getaddrinfo(3) is a blocking C library call that can block indefinitely
100+
# on unresponsive DNS. For IP addresses (the common case with memcached),
101+
# getaddrinfo returns immediately without DNS and is safe to call directly.
102+
# For hostnames, we use Ruby's Resolv library which is pure Ruby and
103+
# supports timeouts, then pass the resolved IP to getaddrinfo for the
104+
# structured address info the caller expects.
105+
def self.resolve_address(host, timeout)
106+
if ip_address?(host)
107+
return ::Socket.getaddrinfo(host, nil, ::Socket::AF_UNSPEC, ::Socket::SOCK_STREAM).first
108+
end
109+
110+
dns = Resolv::DNS.new
111+
dns.timeouts = timeout
112+
resolver = Resolv.new([Resolv::Hosts.new, dns])
113+
resolved_ip = resolver.getaddress(host).to_s
114+
::Socket.getaddrinfo(resolved_ip, nil, ::Socket::AF_UNSPEC, ::Socket::SOCK_STREAM).first
115+
rescue Resolv::ResolvError => e
116+
raise SocketError, "getaddrinfo: Name or service not known - #{host} (#{e.message})"
117+
ensure
118+
dns&.close
119+
end
120+
private_class_method :resolve_address
121+
122+
# Returns true if host is an IP address (v4 or v6) rather than a hostname.
123+
def self.ip_address?(host)
124+
host.match?(/\A\d{1,3}(\.\d{1,3}){3}\z/) || host.include?(':')
125+
end
126+
private_class_method :ip_address?
96127
end
97128

98129
class UNIX < ::Socket

0 commit comments

Comments
 (0)