Class: Resolve::Hostname

Inherits:
Object
  • Object
show all
Defined in:
lib/resolve/hostname.rb,
lib/resolve/hostname.rb,
lib/resolve/hostname/version.rb

Defined Under Namespace

Classes: CachedValue, NotFoundError

Constant Summary collapse

DEFAULT_EXPIRATION_SECONDS =
60
DEFAULT_RESOLVER_TTL =

for rereading of /etc/resolve.conf

1800
DEFAULT_ENABLE_SYSTEM_RESOLVER =
false
DEFAULT_PRIMARY_ADDRESS_VERSION =
:ipv4
DEFAULT_PERMIT_SECONDARY_ADDRESS_VERSION =
true
ADDRESS_VERSIONS =
[:ipv4, :ipv6]
DEFAULT_RAISE_NOTFOUND =
true
VERSION =
"0.1.0"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Hostname

TODO: DNS RoundRobin with resolv DEFAULT_SUPPORTS_DNS_RR = false



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/resolve/hostname.rb', line 35

def initialize(opts={})
  @primary_ip_version = opts[:version] || DEFAULT_PRIMARY_ADDRESS_VERSION
  unless ADDRESS_VERSIONS.include? @primary_ip_version
    raise ArgumentError, "unknown version of ip address: #{opts[:version]}"
  end

  @ttl = opts[:ttl] || DEFAULT_EXPIRATION_SECONDS
  @resolver_ttl = opts[:resolver_ttl] || DEFAULT_RESOLVER_TTL

  @system_resolver_enabled = opts.fetch(:system_resolver, DEFAULT_ENABLE_SYSTEM_RESOLVER)
  @permit_secondary_address_version = opts.fetch(:permit_other_version, DEFAULT_PERMIT_SECONDARY_ADDRESS_VERSION)
  @raise_notfound = opts.fetch(:raise_notfound, DEFAULT_RAISE_NOTFOUND)

  @cache = {}
  @mutex = Mutex.new

  @resolver = nil
  @resolver_expires = nil

  @invalid_address_error = if IPAddr.const_defined?('InvalidAddressError')
                             IPAddr::InvalidAddressError
                           else
                             ArgumentError
                           end
end

Instance Attribute Details

#cacheObject (readonly)

for testing



16
17
18
# File 'lib/resolve/hostname.rb', line 16

def cache
  @cache
end

#resolver_expiresObject

Returns the value of attribute resolver_expires.



15
16
17
# File 'lib/resolve/hostname.rb', line 15

def resolver_expires
  @resolver_expires
end

#resolver_ttlObject

Returns the value of attribute resolver_ttl.



15
16
17
# File 'lib/resolve/hostname.rb', line 15

def resolver_ttl
  @resolver_ttl
end

#ttlObject

Returns the value of attribute ttl.



15
16
17
# File 'lib/resolve/hostname.rb', line 15

def ttl
  @ttl
end

Instance Method Details

#getaddress(name) ⇒ Object



61
62
63
64
65
66
67
68
# File 'lib/resolve/hostname.rb', line 61

def getaddress(name)
  unless @cache[name]
    @mutex.synchronize do
      @cache[name] ||= CachedValue.new(@ttl)
    end
  end
  @cache[name].get_or_refresh{ resolve(name) }
end

#primary_ip_versionObject



70
71
72
# File 'lib/resolve/hostname.rb', line 70

def primary_ip_version
  @primary_ip_version
end

#primary_version_address?(str) ⇒ Boolean

Returns:

  • (Boolean)


78
79
80
81
82
83
84
# File 'lib/resolve/hostname.rb', line 78

def primary_version_address?(str)
  if @primary_ip_version == :ipv4
    IPAddr.new(str).ipv4?
  else
    IPAddr.new(str).ipv6?
  end
end

#resolv_instanceObject



129
130
131
132
133
134
135
136
# File 'lib/resolve/hostname.rb', line 129

def resolv_instance
  return @resolver if @resolver && @resolver_expires >= Time.now

  @resolver_expires = Time.now + @resolver_ttl
  @resolver = Resolv::DNS.new

  @resolver
end

#resolve(name) ⇒ Object

Raises:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/resolve/hostname.rb', line 86

def resolve(name)
  secondary = nil

  is_address = false
  begin
    IPAddr.new(name)
    is_address = true
  rescue @invalid_address_error
    # ignore
  end
  return name if is_address

  if @system_resolver_enabled
    addr = resolve_builtin(name)
    if addr
      return addr if primary_version_address?(addr)
      secondary = addr
    end
  end

  addr = resolve_resolv(name, primary_ip_version)
  if addr
    return addr if primary_version_address?(addr)
    secondary ||= addr
  end

  if secondary.nil? && @permit_secondary_address_version
    secondary = resolve_resolv(name, secondary_ip_version)
  end

  addr = resolve_magic(name)
  if addr
    return addr if primary_version_address?(addr)
    secondary ||= addr
  end

  return secondary if secondary && @permit_secondary_address_version

  raise NotFoundError, "cannot resolve hostname #{name}" if @raise_notfound

  nil
end

#resolve_builtin(name) ⇒ Object



155
156
157
158
159
160
161
162
# File 'lib/resolve/hostname.rb', line 155

def resolve_builtin(name)
  begin
    IPSocket.getaddress(name)
  rescue SocketError => e
    raise unless e.message.start_with?('getaddrinfo: nodename nor servname provided, or not known')
    nil
  end
end

#resolve_magic(name) ⇒ Object



164
165
166
167
168
169
# File 'lib/resolve/hostname.rb', line 164

def resolve_magic(name)
  if name =~ /^localhost$/i
    return @primary_ip_version == :ipv4 ? '127.0.0.1' : '::1'
  end
  nil
end

#resolve_resolv(name, version) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/resolve/hostname.rb', line 138

def resolve_resolv(name, version)
  t = case version
      when :ipv4
        Resolv::DNS::Resource::IN::A
      when :ipv6
        Resolv::DNS::Resource::IN::AAAA
      else
        raise ArgumentError, "invalid ip address version:#{version}"
      end
  begin
    resolv_instance.getresource(name, t).address.to_s
  rescue Resolv::ResolvError => e
    raise unless e.message.start_with?('DNS result has no information for')
    nil
  end
end

#secondary_ip_versionObject



74
75
76
# File 'lib/resolve/hostname.rb', line 74

def secondary_ip_version
  @primary_ip_version == :ipv4 ? :ipv6 : :ipv4
end