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.
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