em-dns 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +21 -0
- data/VERSION.yml +4 -0
- data/examples/lookup_many.rb +55 -0
- data/lib/em/dns_cache.rb +347 -0
- data/lib/em/dns_resolver.rb +156 -0
- data/test/test_basic.rb +222 -0
- metadata +71 -0
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
rescue LoadError
|
4
|
+
raise "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
5
|
+
end
|
6
|
+
|
7
|
+
Jeweler::Tasks.new do |s|
|
8
|
+
s.name = "em-dns"
|
9
|
+
s.summary = "Resolve domain names from EventMachine natively"
|
10
|
+
s.email = "astro@spaceboyz.net"
|
11
|
+
s.homepage = "http://github.com/astro/em-dns"
|
12
|
+
s.description = "DNS::Resolv made ready for EventMachine"
|
13
|
+
s.authors = ["Aman Gupta", "Stephan Maka"]
|
14
|
+
s.files = FileList["[A-Z]*", "{lib,test,examples}/**/*"]
|
15
|
+
s.add_dependency 'eventmachine'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'rake/testtask'
|
19
|
+
Rake::TestTask.new do |t|
|
20
|
+
t.test_files = FileList["test/test*.rb"]
|
21
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'eventmachine'
|
5
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
6
|
+
require 'em/dns_resolver'
|
7
|
+
|
8
|
+
if ARGV.size == 0
|
9
|
+
puts "Usage: #{$0} <url-files>"
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
hosts = []
|
15
|
+
ARGV.each do |fn|
|
16
|
+
IO::readlines(fn).each do |line|
|
17
|
+
line.chomp!
|
18
|
+
line.split(/\s+/).each do |url|
|
19
|
+
uri = URI::parse(url)
|
20
|
+
hosts << uri.host
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
hosts.uniq!
|
25
|
+
puts "Will resolve #{hosts.size} hosts"
|
26
|
+
|
27
|
+
BATCH_SIZE = 10
|
28
|
+
successes = 0
|
29
|
+
failures = 0
|
30
|
+
t1 = Time.now
|
31
|
+
EM.run {
|
32
|
+
pending = 0
|
33
|
+
EM.add_periodic_timer(0.1) do
|
34
|
+
batch, hosts = hosts[0..(BATCH_SIZE-1)], (hosts[BATCH_SIZE..-1] || [])
|
35
|
+
batch.each do |host|
|
36
|
+
df = EM::DnsResolver.resolve(host)
|
37
|
+
df.callback { |a|
|
38
|
+
p host => a
|
39
|
+
successes += 1
|
40
|
+
pending -= 1
|
41
|
+
EM.stop if pending < 1 && hosts.empty?
|
42
|
+
}
|
43
|
+
df.errback { |*a|
|
44
|
+
puts "Cannot resolve #{host}: #{a.inspect}"
|
45
|
+
failures += 1
|
46
|
+
pending -= 1
|
47
|
+
EM.stop if pending < 1 && hosts.empty?
|
48
|
+
}
|
49
|
+
pending += 1
|
50
|
+
end
|
51
|
+
puts "#{pending} pending"
|
52
|
+
end
|
53
|
+
}
|
54
|
+
t2 = Time.now
|
55
|
+
puts "#{successes} successful, #{failures} failures in #{t2 - t1} s"
|
data/lib/em/dns_cache.rb
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
# $Id: dns_cache.rb 5040 2007-10-05 17:31:04Z francis $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'eventmachine'
|
7
|
+
require 'resolv'
|
8
|
+
|
9
|
+
|
10
|
+
module EventMachine
|
11
|
+
module DnsCache
|
12
|
+
|
13
|
+
class Cache
|
14
|
+
def initialize
|
15
|
+
@hash = {}
|
16
|
+
end
|
17
|
+
def add domain, value, expiration
|
18
|
+
ex = ((expiration < 0) ? :none : (Time.now + expiration))
|
19
|
+
@hash[domain] = [ex, value]
|
20
|
+
end
|
21
|
+
def retrieve domain
|
22
|
+
if @hash.has_key?(domain)
|
23
|
+
d = @hash[domain]
|
24
|
+
if d.first != :none and d.first < Time.now
|
25
|
+
@hash.delete(domain)
|
26
|
+
nil
|
27
|
+
else
|
28
|
+
d.last
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@a_cache = Cache.new
|
35
|
+
@mx_cache = Cache.new
|
36
|
+
@nameservers = []
|
37
|
+
@message_ix = 0
|
38
|
+
MAX_WAITING = 100
|
39
|
+
@waiting = 0
|
40
|
+
@pending = []
|
41
|
+
|
42
|
+
def self.add_nameserver ns
|
43
|
+
@nameservers << ns unless @nameservers.include?(ns)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.add_nameservers_from_file file='/etc/resolv.conf'
|
47
|
+
IO::readlines(file).each do |line|
|
48
|
+
if line =~ /^nameserver (.+)$/
|
49
|
+
$1.split(/\s+/).each { |ns|
|
50
|
+
@nameservers << ns unless ns.empty?
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.verbose v=true
|
57
|
+
@verbose = v
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def self.add_cache_entry cache_type, domain, value, expiration
|
62
|
+
cache = if cache_type == :mx
|
63
|
+
@mx_cache
|
64
|
+
elsif cache_type == :a
|
65
|
+
@a_cache
|
66
|
+
else
|
67
|
+
raise "bad cache type"
|
68
|
+
end
|
69
|
+
|
70
|
+
v = EM::DefaultDeferrable.new
|
71
|
+
v.succeed( value.dup.freeze )
|
72
|
+
cache.add domain, v, expiration
|
73
|
+
end
|
74
|
+
|
75
|
+
# Needs to be DRYed up with resolve_mx.
|
76
|
+
#
|
77
|
+
def self.resolve domain
|
78
|
+
if d = @a_cache.retrieve(domain)
|
79
|
+
puts "Cache hit for #{domain}" if @verbose
|
80
|
+
look_pending
|
81
|
+
d
|
82
|
+
else
|
83
|
+
=begin
|
84
|
+
d = @a_cache[domain]
|
85
|
+
if d.first < Time.now
|
86
|
+
STDOUT.puts "Expiring stale cache entry for #{domain}" if @verbose
|
87
|
+
@a_cache.delete domain
|
88
|
+
resolve domain
|
89
|
+
else
|
90
|
+
STDOUT.puts "Fulfilled #{domain} from cache" if @verbose
|
91
|
+
d.last
|
92
|
+
end
|
93
|
+
else
|
94
|
+
=end
|
95
|
+
if @waiting >= MAX_WAITING
|
96
|
+
puts "Postponing #{domain} because already waiting for #{@waiting} queries" if @verbose
|
97
|
+
d = EM::DefaultDeferrable.new
|
98
|
+
@pending << lambda {
|
99
|
+
d_inner = resolve domain
|
100
|
+
d_inner.callback &d.method(:succeed)
|
101
|
+
d_inner.errback &d.method(:fail)
|
102
|
+
}
|
103
|
+
puts "#{@pending.size} pending requests now" if @verbose
|
104
|
+
d
|
105
|
+
else
|
106
|
+
d = resolve_do domain
|
107
|
+
@waiting += 1
|
108
|
+
STDOUT.puts "Now waiting for #{@waiting}" if @verbose
|
109
|
+
on_one_done = lambda {
|
110
|
+
@waiting -= 1
|
111
|
+
look_pending
|
112
|
+
}
|
113
|
+
d.callback &on_one_done
|
114
|
+
d.errback &on_one_done
|
115
|
+
d
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.look_pending
|
121
|
+
EM.next_tick {
|
122
|
+
while @waiting < MAX_WAITING && !@pending.empty?
|
123
|
+
pending1 = @pending.shift
|
124
|
+
pending1.call
|
125
|
+
puts "#{@pending.size} pending requests now" if @verbose
|
126
|
+
end
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.resolve_do domain
|
131
|
+
STDOUT.puts "Fulfilling #{domain} from network" if @verbose
|
132
|
+
d = EM::DefaultDeferrable.new
|
133
|
+
#d.timeout(5)
|
134
|
+
d.callback { d.cancel_timeout }
|
135
|
+
d.errback { d.cancel_timeout }
|
136
|
+
@a_cache.add domain, d, 300 # Hard-code a 5 minute expiration
|
137
|
+
#@a_cache[domain] = [Time.now+120, d] # Hard code a 120-second expiration.
|
138
|
+
|
139
|
+
lazy_initialize
|
140
|
+
m = Resolv::DNS::Message.new
|
141
|
+
m.rd = 1
|
142
|
+
m.add_question domain, Resolv::DNS::Resource::IN::A
|
143
|
+
m = m.encode
|
144
|
+
d_inner = EM::DefaultDeferrable.new
|
145
|
+
@nameservers.each {|ns|
|
146
|
+
@message_ix = (@message_ix + 1) % 60000
|
147
|
+
Request.new d_inner, @message_ix
|
148
|
+
msg = m.dup
|
149
|
+
msg[0,2] = [@message_ix].pack("n")
|
150
|
+
@u.send_datagram msg, ns, 53
|
151
|
+
}
|
152
|
+
|
153
|
+
d_inner.callback {|resp|
|
154
|
+
r = []
|
155
|
+
resp.each_answer {|name,ttl,data|
|
156
|
+
r << data.address.to_s if data.kind_of?(Resolv::DNS::Resource::IN::A)
|
157
|
+
}
|
158
|
+
|
159
|
+
# Freeze the array since we'll be keeping it in cache and passing it
|
160
|
+
# around to multiple users. And alternative would have been to dup it.
|
161
|
+
r.freeze
|
162
|
+
puts "Succeeding with #{r.inspect}"
|
163
|
+
d.succeed r
|
164
|
+
}
|
165
|
+
d_inner.errback &d.method(:fail)
|
166
|
+
|
167
|
+
|
168
|
+
d
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# Needs to be DRYed up with resolve.
|
173
|
+
#
|
174
|
+
def self.resolve_mx domain
|
175
|
+
if d = @mx_cache.retrieve(domain)
|
176
|
+
d
|
177
|
+
else
|
178
|
+
=begin
|
179
|
+
if @mx_cache.has_key?(domain)
|
180
|
+
d = @mx_cache[domain]
|
181
|
+
if d.first < Time.now
|
182
|
+
STDOUT.puts "Expiring stale cache entry for #{domain}" if @verbose
|
183
|
+
@mx_cache.delete domain
|
184
|
+
resolve_mx domain
|
185
|
+
else
|
186
|
+
STDOUT.puts "Fulfilled #{domain} from cache" if @verbose
|
187
|
+
d.last
|
188
|
+
end
|
189
|
+
else
|
190
|
+
=end
|
191
|
+
STDOUT.puts "Fulfilling #{domain} from network" if @verbose
|
192
|
+
d = EM::DefaultDeferrable.new
|
193
|
+
d.timeout(5)
|
194
|
+
#@mx_cache[domain] = [Time.now+120, d] # Hard code a 120-second expiration.
|
195
|
+
@mx_cache.add domain, d, 300 # Hard-code a 5 minute expiration
|
196
|
+
|
197
|
+
mx_query = MxQuery.new d
|
198
|
+
|
199
|
+
lazy_initialize
|
200
|
+
m = Resolv::DNS::Message.new
|
201
|
+
m.rd = 1
|
202
|
+
m.add_question domain, Resolv::DNS::Resource::IN::MX
|
203
|
+
m = m.encode
|
204
|
+
@nameservers.each {|ns|
|
205
|
+
@message_ix = (@message_ix + 1) % 60000
|
206
|
+
Request.new mx_query, @message_ix
|
207
|
+
msg = m.dup
|
208
|
+
msg[0,2] = [@message_ix].pack("n")
|
209
|
+
@u.send_datagram msg, ns, 53
|
210
|
+
}
|
211
|
+
|
212
|
+
|
213
|
+
d
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
def self.lazy_initialize
|
219
|
+
# Will throw an exception if EM is not running.
|
220
|
+
# We wire a signaller into the socket handler to tell us when that socket
|
221
|
+
# goes away. (Which can happen, among other things, if the reactor
|
222
|
+
# stops and restarts.)
|
223
|
+
#
|
224
|
+
raise "EventMachine reactor not running" unless EM.reactor_running?
|
225
|
+
|
226
|
+
unless @u
|
227
|
+
us = proc {@u = nil}
|
228
|
+
@u = EM::open_datagram_socket( "0.0.0.0", 0, Socket ) {|c|
|
229
|
+
c.unbind_signaller = us
|
230
|
+
}
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
def self.parse_local_mx_records txt
|
237
|
+
domain = nil
|
238
|
+
addrs = []
|
239
|
+
|
240
|
+
add_it = proc {
|
241
|
+
a = addrs.sort {|m,n| m.last <=> n.last}.map {|y| y.first}
|
242
|
+
add_cache_entry :mx, domain, a, -1
|
243
|
+
}
|
244
|
+
|
245
|
+
txt = StringIO.new( txt ) if txt.is_a?(String)
|
246
|
+
txt.each_line {|ln|
|
247
|
+
if ln =~ /\A\s*([\d\w\.\-\_]+)\s+(\d+)\s*\Z/
|
248
|
+
if domain
|
249
|
+
addrs << [$1.dup, $2.dup.to_i]
|
250
|
+
end
|
251
|
+
elsif ln =~ /\A\s*([^\s\:]+)\s*\:\s*\Z/
|
252
|
+
add_it.call if domain
|
253
|
+
domain = $1.dup
|
254
|
+
addrs.clear
|
255
|
+
end
|
256
|
+
}
|
257
|
+
|
258
|
+
add_it.call if domain
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
class MxQuery
|
263
|
+
include EM::Deferrable
|
264
|
+
|
265
|
+
def initialize rslt
|
266
|
+
@result = rslt # Deferrable
|
267
|
+
@n_a_lookups = 0
|
268
|
+
|
269
|
+
self.callback {|resp|
|
270
|
+
addrs = {}
|
271
|
+
resp.each_additional {|name,ttl,data|
|
272
|
+
addrs.has_key?(name) ? (addrs[name] << data.address.to_s) : (addrs[name] = [data.address.to_s])
|
273
|
+
}
|
274
|
+
|
275
|
+
@addresses = resp.answer.
|
276
|
+
sort {|a,b| a[2].preference <=> b[2].preference}.
|
277
|
+
map {|name,ttl,data|
|
278
|
+
ex = data.exchange
|
279
|
+
addrs[ex] or EM::DnsCache.resolve(ex.to_s)
|
280
|
+
}
|
281
|
+
|
282
|
+
@addresses.each_with_index {|a,ix|
|
283
|
+
if a.respond_to?(:set_deferred_status)
|
284
|
+
@n_a_lookups += 1
|
285
|
+
a.callback {|r|
|
286
|
+
@addresses[ix] = r
|
287
|
+
@n_a_lookups -= 1
|
288
|
+
succeed_result if @n_a_lookups == 0
|
289
|
+
}
|
290
|
+
end
|
291
|
+
}
|
292
|
+
|
293
|
+
succeed_result if @n_a_lookups == 0
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
def succeed_result
|
298
|
+
# Questionable whether we should uniq if it perturbs the sort order.
|
299
|
+
# Also freeze it so some user can't wipe it out on us.
|
300
|
+
@result.succeed @addresses.flatten.uniq.freeze
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
class Request
|
306
|
+
include EM::Deferrable
|
307
|
+
|
308
|
+
@@outstanding = {}
|
309
|
+
|
310
|
+
def self.post response
|
311
|
+
if r = @@outstanding.delete(response.id)
|
312
|
+
r.succeed response
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def initialize rslt, m_id
|
317
|
+
@result = rslt
|
318
|
+
@msgid = m_id
|
319
|
+
raise "request-queue overflow" if @@outstanding.has_key?(@msgid)
|
320
|
+
@@outstanding[@msgid] = self
|
321
|
+
|
322
|
+
self.timeout(10)
|
323
|
+
self.errback { self.cancel_timeout; @@outstanding.delete(@msgid); @result.fail }
|
324
|
+
self.callback {|resp| self.cancel_timeout; @result.succeed resp }
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class Socket < EM::Connection
|
329
|
+
attr_accessor :unbind_signaller
|
330
|
+
|
331
|
+
def receive_data dg
|
332
|
+
m = nil
|
333
|
+
begin
|
334
|
+
m = Resolv::DNS::Message.decode dg
|
335
|
+
rescue
|
336
|
+
end
|
337
|
+
Request.post(m) if m
|
338
|
+
end
|
339
|
+
|
340
|
+
def unbind
|
341
|
+
@unbind_signaller.call if @unbind_signaller
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
module DnsResolver
|
6
|
+
##
|
7
|
+
# Global interface
|
8
|
+
##
|
9
|
+
|
10
|
+
def self.resolve(hostname)
|
11
|
+
Request.new(socket, hostname)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.socket
|
15
|
+
unless defined?(@socket)
|
16
|
+
@socket = DnsSocket.open
|
17
|
+
end
|
18
|
+
@socket
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.nameserver=(ns)
|
22
|
+
@nameserver = ns
|
23
|
+
end
|
24
|
+
def self.nameserver
|
25
|
+
unless defined?(@nameserver)
|
26
|
+
IO::readlines('/etc/resolv.conf').each do |line|
|
27
|
+
if line =~ /^nameserver (.+)$/
|
28
|
+
@nameserver = $1.split(/\s+/).first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@nameserver
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Socket stuff
|
37
|
+
##
|
38
|
+
|
39
|
+
class RequestIdAlreadyUsed < RuntimeError
|
40
|
+
end
|
41
|
+
|
42
|
+
class DnsSocket < EM::Connection
|
43
|
+
def self.open
|
44
|
+
EM::open_datagram_socket('0.0.0.0', 0, self)
|
45
|
+
end
|
46
|
+
def post_init
|
47
|
+
@requests = {}
|
48
|
+
EM.add_periodic_timer(0.1, &method(:tick))
|
49
|
+
end
|
50
|
+
# Periodically called each second to fire request retries
|
51
|
+
def tick
|
52
|
+
@requests.each do |id,req|
|
53
|
+
req.tick
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def register_request(id, req)
|
57
|
+
if @requests.has_key?(id)
|
58
|
+
raise RequestIdAlreadyUsed
|
59
|
+
else
|
60
|
+
@requests[id] = req
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def send_packet(pkt)
|
64
|
+
send_datagram(pkt, nameserver, 53)
|
65
|
+
end
|
66
|
+
def nameserver=(ns)
|
67
|
+
@nameserver = ns
|
68
|
+
end
|
69
|
+
def nameserver
|
70
|
+
@nameserver ||= DnsResolver.nameserver
|
71
|
+
end
|
72
|
+
# Decodes the packet, looks for the request and passes the
|
73
|
+
# response over to the requester
|
74
|
+
def receive_data(data)
|
75
|
+
msg = nil
|
76
|
+
begin
|
77
|
+
msg = Resolv::DNS::Message.decode data
|
78
|
+
rescue
|
79
|
+
else
|
80
|
+
req = @requests[msg.id]
|
81
|
+
if req
|
82
|
+
@requests.delete(msg.id)
|
83
|
+
req.receive_answer(msg)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Request
|
91
|
+
##
|
92
|
+
|
93
|
+
class Request
|
94
|
+
include Deferrable
|
95
|
+
attr_accessor :retry_interval
|
96
|
+
attr_accessor :max_tries
|
97
|
+
def initialize(socket, hostname)
|
98
|
+
@socket = socket
|
99
|
+
@hostname = hostname
|
100
|
+
@tries = 0
|
101
|
+
@last_send = Time.at(0)
|
102
|
+
@retry_interval = 3
|
103
|
+
@max_tries = 5
|
104
|
+
EM.next_tick { tick }
|
105
|
+
end
|
106
|
+
def tick
|
107
|
+
# Break early if nothing to do
|
108
|
+
return if @last_send + @retry_interval > Time.now
|
109
|
+
|
110
|
+
if @tries < @max_tries
|
111
|
+
send
|
112
|
+
else
|
113
|
+
fail 'retries exceeded'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
# Called by DnsSocket#receive_data
|
117
|
+
def receive_answer(msg)
|
118
|
+
addrs = []
|
119
|
+
msg.each_answer do |name,ttl,data|
|
120
|
+
if data.kind_of?(Resolv::DNS::Resource::IN::A) ||
|
121
|
+
data.kind_of?(Resolv::DNS::Resource::IN::AAAA)
|
122
|
+
addrs << data.address.to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
if addrs.empty?
|
126
|
+
fail "rcode=#{msg.rcode}"
|
127
|
+
else
|
128
|
+
succeed addrs
|
129
|
+
end
|
130
|
+
end
|
131
|
+
private
|
132
|
+
def send
|
133
|
+
@socket.send_packet(packet.encode)
|
134
|
+
@tries += 1
|
135
|
+
@last_send = Time.now
|
136
|
+
end
|
137
|
+
def id
|
138
|
+
begin
|
139
|
+
@id = rand(65535)
|
140
|
+
@socket.register_request(@id, self)
|
141
|
+
rescue RequestIdAlreadyUsed
|
142
|
+
retry
|
143
|
+
end unless defined?(@id)
|
144
|
+
|
145
|
+
@id
|
146
|
+
end
|
147
|
+
def packet
|
148
|
+
msg = Resolv::DNS::Message.new
|
149
|
+
msg.id = id
|
150
|
+
msg.rd = 1
|
151
|
+
msg.add_question @hostname, Resolv::DNS::Resource::IN::A
|
152
|
+
msg
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/test/test_basic.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
# $Id: test_basic.rb 5040 2007-10-05 17:31:04Z francis $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'test/unit'
|
6
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
7
|
+
require 'em/dns_cache'
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
#--------------------------------------
|
13
|
+
|
14
|
+
|
15
|
+
class TestBasic < Test::Unit::TestCase
|
16
|
+
|
17
|
+
TestNameserver = "151.202.0.85"
|
18
|
+
TestNameserver2 = "151.202.0.86"
|
19
|
+
|
20
|
+
LocalMxRecords = %Q(
|
21
|
+
boondoggle.zzz:
|
22
|
+
65.66.67.68 10
|
23
|
+
55.56.57.58 5
|
24
|
+
esmtp.someone.zzz 4
|
25
|
+
|
26
|
+
boondoggle.yyy:
|
27
|
+
)
|
28
|
+
|
29
|
+
def test_a
|
30
|
+
EM::DnsCache.add_nameservers_from_file
|
31
|
+
EM::DnsCache.verbose
|
32
|
+
|
33
|
+
out = nil
|
34
|
+
|
35
|
+
EM.run {
|
36
|
+
d = EM::DnsCache.resolve "bayshorenetworks.com"
|
37
|
+
d.errback {EM.stop}
|
38
|
+
d.callback {|r|
|
39
|
+
d = EM::DnsCache.resolve "bayshorenetworks.com"
|
40
|
+
d.errback { EM.stop }
|
41
|
+
d.callback {|r| puts r; out = r; EM.stop }
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
assert out
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_a_pair
|
49
|
+
EM::DnsCache.add_nameservers_from_file
|
50
|
+
EM::DnsCache.verbose
|
51
|
+
|
52
|
+
out = nil
|
53
|
+
|
54
|
+
EM.run {
|
55
|
+
d = EM::DnsCache.resolve "maila.microsoft.com"
|
56
|
+
d.errback {EM.stop}
|
57
|
+
d.callback {|r|
|
58
|
+
out = r
|
59
|
+
EM.stop
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
assert_equal( Array, out.class )
|
64
|
+
assert_equal( 2, out.length )
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# This test causes each request to hit the network because they're all scheduled
|
69
|
+
# before the first one can come back and load the cache. Although a nice mis-feature for
|
70
|
+
# stress testing, it would be nice to fix it someday, perhaps by not kicking off a
|
71
|
+
# request for a particular domain if one is already pending.
|
72
|
+
# Without epoll, this test gets really slow and usually doesn't complete.
|
73
|
+
def test_lots_of_a
|
74
|
+
EM.epoll
|
75
|
+
EM::DnsCache.add_nameserver TestNameserver
|
76
|
+
EM::DnsCache.add_nameserver TestNameserver2
|
77
|
+
EM::DnsCache.verbose
|
78
|
+
|
79
|
+
n = 250
|
80
|
+
e = 0
|
81
|
+
s = 0
|
82
|
+
EM.run {
|
83
|
+
n.times {
|
84
|
+
d = EM::DnsCache.resolve "ibm.com"
|
85
|
+
d.errback {e+=1; n -= 1; EM.stop if n == 0}
|
86
|
+
d.callback {s+=1; n -= 1; EM.stop if n == 0}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
assert_equal( 0, n)
|
90
|
+
assert_equal( 250, s)
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
def test_mx
|
97
|
+
EM::DnsCache.add_nameserver TestNameserver
|
98
|
+
EM::DnsCache.verbose
|
99
|
+
|
100
|
+
out = nil
|
101
|
+
|
102
|
+
EM.run {
|
103
|
+
d = EM::DnsCache.resolve_mx "steamheat.net"
|
104
|
+
d.errback {EM.stop}
|
105
|
+
d.callback {|r|
|
106
|
+
p r
|
107
|
+
d = EM::DnsCache.resolve_mx "steamheat.net"
|
108
|
+
d.errback {EM.stop}
|
109
|
+
d.callback {|r|
|
110
|
+
out = r
|
111
|
+
p r
|
112
|
+
EM.stop
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
assert out
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# The arrays of addresses we get back from the DnsCache are FROZEN.
|
122
|
+
# That's because the same objects get passed around to any caller that
|
123
|
+
# asks for them. If you need to modify the array, dup it.
|
124
|
+
#
|
125
|
+
def test_freeze
|
126
|
+
EM::DnsCache.add_nameserver TestNameserver
|
127
|
+
EM::DnsCache.verbose
|
128
|
+
|
129
|
+
out = nil
|
130
|
+
|
131
|
+
EM.run {
|
132
|
+
d = EM::DnsCache.resolve_mx "steamheat.net"
|
133
|
+
d.errback {EM.stop}
|
134
|
+
d.callback {|r|
|
135
|
+
out = r
|
136
|
+
EM.stop
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
assert out
|
141
|
+
assert( out.length > 0)
|
142
|
+
assert_raise( TypeError ) {
|
143
|
+
out.clear
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
def test_local_defs
|
149
|
+
EM::DnsCache.add_nameserver TestNameserver
|
150
|
+
EM::DnsCache.verbose
|
151
|
+
|
152
|
+
EM::DnsCache.add_cache_entry( :mx, "example.zzz", ["1.2.3.4"], -1 )
|
153
|
+
out = nil
|
154
|
+
EM.run {
|
155
|
+
d = EM::DnsCache.resolve_mx "example.zzz"
|
156
|
+
d.errback {EM.stop}
|
157
|
+
d.callback {|r|
|
158
|
+
out = r
|
159
|
+
EM.stop
|
160
|
+
}
|
161
|
+
}
|
162
|
+
assert_equal( ["1.2.3.4"], out )
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_multiple_local_defs
|
166
|
+
EM::DnsCache.verbose
|
167
|
+
|
168
|
+
EM::DnsCache.add_cache_entry( :mx, "example.zzz", ["1.2.3.4", "5.6.7.8"], -1 )
|
169
|
+
out = nil
|
170
|
+
EM.run {
|
171
|
+
d = EM::DnsCache.resolve_mx "example.zzz"
|
172
|
+
d.errback {EM.stop}
|
173
|
+
d.callback {|r|
|
174
|
+
out = r
|
175
|
+
EM.stop
|
176
|
+
}
|
177
|
+
}
|
178
|
+
assert_equal( ["1.2.3.4","5.6.7.8"], out )
|
179
|
+
end
|
180
|
+
|
181
|
+
# Adding cache entries where they already exist causes them to be REPLACED.
|
182
|
+
#
|
183
|
+
def test_replace
|
184
|
+
EM::DnsCache.verbose
|
185
|
+
|
186
|
+
EM::DnsCache.add_cache_entry( :mx, "example.zzz", ["1.2.3.4", "5.6.7.8"], -1 )
|
187
|
+
EM::DnsCache.add_cache_entry( :mx, "example.zzz", ["10.11.12.13"], -1 )
|
188
|
+
out = nil
|
189
|
+
EM.run {
|
190
|
+
d = EM::DnsCache.resolve_mx "example.zzz"
|
191
|
+
d.errback {EM.stop}
|
192
|
+
d.callback {|r|
|
193
|
+
out = r
|
194
|
+
EM.stop
|
195
|
+
}
|
196
|
+
}
|
197
|
+
assert_equal( ["10.11.12.13"], out )
|
198
|
+
end
|
199
|
+
|
200
|
+
# We have a facility for storing locally-defined MX records.
|
201
|
+
# The DNS cache has a way to parse and process these values.
|
202
|
+
#
|
203
|
+
def test_external_mx_defs
|
204
|
+
EM::DnsCache.verbose
|
205
|
+
|
206
|
+
EM::DnsCache.parse_local_mx_records LocalMxRecords
|
207
|
+
|
208
|
+
out = nil
|
209
|
+
EM.run {
|
210
|
+
d = EM::DnsCache.resolve_mx "boondoggle.zzz"
|
211
|
+
d.errback {EM.stop}
|
212
|
+
d.callback {|r|
|
213
|
+
out = r
|
214
|
+
EM.stop
|
215
|
+
}
|
216
|
+
}
|
217
|
+
assert_equal( ["esmtp.someone.zzz", "55.56.57.58", "65.66.67.68"], out )
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-dns
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aman Gupta
|
8
|
+
- Stephan Maka
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-11-07 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: eventmachine
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
description: DNS::Resolv made ready for EventMachine
|
27
|
+
email: astro@spaceboyz.net
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- Rakefile
|
36
|
+
- VERSION.yml
|
37
|
+
- examples/lookup_many.rb
|
38
|
+
- lib/em/dns_cache.rb
|
39
|
+
- lib/em/dns_resolver.rb
|
40
|
+
- test/test_basic.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/astro/em-dns
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.5
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Resolve domain names from EventMachine natively
|
69
|
+
test_files:
|
70
|
+
- test/test_basic.rb
|
71
|
+
- examples/lookup_many.rb
|