astro-em-dns 0.0.0 → 0.0.1

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