astro-em-dns 0.0.0 → 0.0.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/VERSION.yml +4 -0
- data/lib/em/dns_cache.rb +286 -278
- data/test/test_basic.rb +2 -2
- metadata +2 -1
data/VERSION.yml
ADDED
data/lib/em/dns_cache.rb
CHANGED
@@ -8,287 +8,295 @@ require 'resolv'
|
|
8
8
|
|
9
9
|
|
10
10
|
module EventMachine
|
11
|
-
module DnsCache
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
+
|
39
|
+
def self.add_nameserver ns
|
40
|
+
@nameservers << ns unless @nameservers.include?(ns)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.add_nameservers_from_file file='/etc/resolv.conf'
|
44
|
+
IO::readlines(file).each do |line|
|
45
|
+
if line =~ /^nameserver (.+)$/
|
46
|
+
$1.split(/\s+/).each { |ns|
|
47
|
+
@nameservers << ns unless ns.empty?
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.verbose v=true
|
54
|
+
@verbose = v
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def self.add_cache_entry cache_type, domain, value, expiration
|
59
|
+
cache = if cache_type == :mx
|
60
|
+
@mx_cache
|
61
|
+
elsif cache_type == :a
|
62
|
+
@a_cache
|
63
|
+
else
|
64
|
+
raise "bad cache type"
|
65
|
+
end
|
66
|
+
|
67
|
+
v = EM::DefaultDeferrable.new
|
68
|
+
v.succeed( value.dup.freeze )
|
69
|
+
cache.add domain, v, expiration
|
70
|
+
end
|
71
|
+
|
72
|
+
# Needs to be DRYed up with resolve_mx.
|
73
|
+
#
|
74
|
+
def self.resolve domain
|
75
|
+
if d = @a_cache.retrieve(domain)
|
76
|
+
d
|
77
|
+
else
|
68
78
|
=begin
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
+
d = @a_cache[domain]
|
80
|
+
if d.first < Time.now
|
81
|
+
STDOUT.puts "Expiring stale cache entry for #{domain}" if @verbose
|
82
|
+
@a_cache.delete domain
|
83
|
+
resolve domain
|
84
|
+
else
|
85
|
+
STDOUT.puts "Fulfilled #{domain} from cache" if @verbose
|
86
|
+
d.last
|
87
|
+
end
|
88
|
+
else
|
79
89
|
=end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
90
|
+
STDOUT.puts "Fulfilling #{domain} from network" if @verbose
|
91
|
+
d = EM::DefaultDeferrable.new
|
92
|
+
d.timeout(5)
|
93
|
+
@a_cache.add domain, d, 300 # Hard-code a 5 minute expiration
|
94
|
+
#@a_cache[domain] = [Time.now+120, d] # Hard code a 120-second expiration.
|
95
|
+
|
96
|
+
lazy_initialize
|
97
|
+
m = Resolv::DNS::Message.new
|
98
|
+
m.rd = 1
|
99
|
+
m.add_question domain, Resolv::DNS::Resource::IN::A
|
100
|
+
m = m.encode
|
101
|
+
@nameservers.each {|ns|
|
102
|
+
@message_ix = (@message_ix + 1) % 60000
|
103
|
+
Request.new d, @message_ix
|
104
|
+
msg = m.dup
|
105
|
+
msg[0,2] = [@message_ix].pack("n")
|
106
|
+
@u.send_datagram msg, ns, 53
|
107
|
+
}
|
108
|
+
|
109
|
+
d.callback {|resp|
|
110
|
+
r = []
|
111
|
+
resp.each_answer {|name,ttl,data|
|
112
|
+
r << data.address.to_s if data.kind_of?(Resolv::DNS::Resource::IN::A)
|
113
|
+
}
|
114
|
+
|
115
|
+
# Freeze the array since we'll be keeping it in cache and passing it
|
116
|
+
# around to multiple users. And alternative would have been to dup it.
|
117
|
+
r.freeze
|
118
|
+
d.succeed r
|
119
|
+
}
|
120
|
+
|
121
|
+
|
122
|
+
d
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Needs to be DRYed up with resolve.
|
128
|
+
#
|
129
|
+
def self.resolve_mx domain
|
130
|
+
if d = @mx_cache.retrieve(domain)
|
131
|
+
d
|
132
|
+
else
|
123
133
|
=begin
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
134
|
+
if @mx_cache.has_key?(domain)
|
135
|
+
d = @mx_cache[domain]
|
136
|
+
if d.first < Time.now
|
137
|
+
STDOUT.puts "Expiring stale cache entry for #{domain}" if @verbose
|
138
|
+
@mx_cache.delete domain
|
139
|
+
resolve_mx domain
|
140
|
+
else
|
141
|
+
STDOUT.puts "Fulfilled #{domain} from cache" if @verbose
|
142
|
+
d.last
|
143
|
+
end
|
144
|
+
else
|
135
145
|
=end
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
end
|
146
|
+
STDOUT.puts "Fulfilling #{domain} from network" if @verbose
|
147
|
+
d = EM::DefaultDeferrable.new
|
148
|
+
d.timeout(5)
|
149
|
+
#@mx_cache[domain] = [Time.now+120, d] # Hard code a 120-second expiration.
|
150
|
+
@mx_cache.add domain, d, 300 # Hard-code a 5 minute expiration
|
151
|
+
|
152
|
+
mx_query = MxQuery.new d
|
153
|
+
|
154
|
+
lazy_initialize
|
155
|
+
m = Resolv::DNS::Message.new
|
156
|
+
m.rd = 1
|
157
|
+
m.add_question domain, Resolv::DNS::Resource::IN::MX
|
158
|
+
m = m.encode
|
159
|
+
@nameservers.each {|ns|
|
160
|
+
@message_ix = (@message_ix + 1) % 60000
|
161
|
+
Request.new mx_query, @message_ix
|
162
|
+
msg = m.dup
|
163
|
+
msg[0,2] = [@message_ix].pack("n")
|
164
|
+
@u.send_datagram msg, ns, 53
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
d
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def self.lazy_initialize
|
174
|
+
# Will throw an exception if EM is not running.
|
175
|
+
# We wire a signaller into the socket handler to tell us when that socket
|
176
|
+
# goes away. (Which can happen, among other things, if the reactor
|
177
|
+
# stops and restarts.)
|
178
|
+
#
|
179
|
+
raise "EventMachine reactor not running" unless EM.reactor_running?
|
180
|
+
|
181
|
+
unless @u
|
182
|
+
us = proc {@u = nil}
|
183
|
+
@u = EM::open_datagram_socket( "0.0.0.0", 0, Socket ) {|c|
|
184
|
+
c.unbind_signaller = us
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
def self.parse_local_mx_records txt
|
192
|
+
domain = nil
|
193
|
+
addrs = []
|
194
|
+
|
195
|
+
add_it = proc {
|
196
|
+
a = addrs.sort {|m,n| m.last <=> n.last}.map {|y| y.first}
|
197
|
+
add_cache_entry :mx, domain, a, -1
|
198
|
+
}
|
199
|
+
|
200
|
+
txt = StringIO.new( txt ) if txt.is_a?(String)
|
201
|
+
txt.each_line {|ln|
|
202
|
+
if ln =~ /\A\s*([\d\w\.\-\_]+)\s+(\d+)\s*\Z/
|
203
|
+
if domain
|
204
|
+
addrs << [$1.dup, $2.dup.to_i]
|
205
|
+
end
|
206
|
+
elsif ln =~ /\A\s*([^\s\:]+)\s*\:\s*\Z/
|
207
|
+
add_it.call if domain
|
208
|
+
domain = $1.dup
|
209
|
+
addrs.clear
|
210
|
+
end
|
211
|
+
}
|
212
|
+
|
213
|
+
add_it.call if domain
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
class MxQuery
|
218
|
+
include EM::Deferrable
|
219
|
+
|
220
|
+
def initialize rslt
|
221
|
+
@result = rslt # Deferrable
|
222
|
+
@n_a_lookups = 0
|
223
|
+
|
224
|
+
self.callback {|resp|
|
225
|
+
addrs = {}
|
226
|
+
resp.each_additional {|name,ttl,data|
|
227
|
+
addrs.has_key?(name) ? (addrs[name] << data.address.to_s) : (addrs[name] = [data.address.to_s])
|
228
|
+
}
|
229
|
+
|
230
|
+
@addresses = resp.answer.
|
231
|
+
sort {|a,b| a[2].preference <=> b[2].preference}.
|
232
|
+
map {|name,ttl,data|
|
233
|
+
ex = data.exchange
|
234
|
+
addrs[ex] or EM::DnsCache.resolve(ex.to_s)
|
235
|
+
}
|
236
|
+
|
237
|
+
@addresses.each_with_index {|a,ix|
|
238
|
+
if a.respond_to?(:set_deferred_status)
|
239
|
+
@n_a_lookups += 1
|
240
|
+
a.callback {|r|
|
241
|
+
@addresses[ix] = r
|
242
|
+
@n_a_lookups -= 1
|
243
|
+
succeed_result if @n_a_lookups == 0
|
244
|
+
}
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
succeed_result if @n_a_lookups == 0
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
def succeed_result
|
253
|
+
# Questionable whether we should uniq if it perturbs the sort order.
|
254
|
+
# Also freeze it so some user can't wipe it out on us.
|
255
|
+
@result.succeed @addresses.flatten.uniq.freeze
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
class Request
|
261
|
+
include EM::Deferrable
|
262
|
+
|
263
|
+
@@outstanding = {}
|
264
|
+
|
265
|
+
def self.post response
|
266
|
+
if r = @@outstanding.delete(response.id)
|
267
|
+
r.succeed response
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def initialize rslt, m_id
|
272
|
+
@result = rslt
|
273
|
+
@msgid = m_id
|
274
|
+
raise "request-queue overflow" if @@outstanding.has_key?(@msgid)
|
275
|
+
@@outstanding[@msgid] = self
|
276
|
+
|
277
|
+
self.timeout(10)
|
278
|
+
self.errback { @@outstanding.delete(@msgid) }
|
279
|
+
self.callback {|resp| @result.succeed resp }
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class Socket < EM::Connection
|
284
|
+
attr_accessor :unbind_signaller
|
285
|
+
|
286
|
+
def receive_data dg
|
287
|
+
m = nil
|
288
|
+
begin
|
289
|
+
m = Resolv::DNS::Message.decode dg
|
290
|
+
rescue
|
291
|
+
end
|
292
|
+
Request.post(m) if m
|
293
|
+
end
|
294
|
+
|
295
|
+
def unbind
|
296
|
+
@unbind_signaller.call if @unbind_signaller
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
293
301
|
end
|
294
302
|
|
data/test/test_basic.rb
CHANGED
@@ -27,7 +27,7 @@ class TestBasic < Test::Unit::TestCase
|
|
27
27
|
)
|
28
28
|
|
29
29
|
def test_a
|
30
|
-
EM::DnsCache.
|
30
|
+
EM::DnsCache.add_nameservers_from_file
|
31
31
|
EM::DnsCache.verbose
|
32
32
|
|
33
33
|
out = nil
|
@@ -46,7 +46,7 @@ class TestBasic < Test::Unit::TestCase
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def test_a_pair
|
49
|
-
EM::DnsCache.
|
49
|
+
EM::DnsCache.add_nameservers_from_file
|
50
50
|
EM::DnsCache.verbose
|
51
51
|
|
52
52
|
out = nil
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: astro-em-dns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aman Gupta
|
@@ -33,6 +33,7 @@ extra_rdoc_files: []
|
|
33
33
|
|
34
34
|
files:
|
35
35
|
- Rakefile
|
36
|
+
- VERSION.yml
|
36
37
|
- lib/em/dns_cache.rb
|
37
38
|
- test/test_basic.rb
|
38
39
|
has_rdoc: true
|