rubydns 0.8.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ca335a666974f8ec5e2a9c3cfe075e2d156593f0
4
- data.tar.gz: ff3d9313d716dcf7c25cf40272fc97e7a36c508e
3
+ metadata.gz: dbd9b4d8a8eec2e1ae92fb9d8e5b57cf0eb120d4
4
+ data.tar.gz: dc2b13bae0244a6bade2b299f13b7ce1524aafc1
5
5
  SHA512:
6
- metadata.gz: 4603602b5a55406025fb7064a6e4ed002bbeec6216397f458ca1f0105eeecda08d90d66cc360440cbdd8e3d172e38cad114130a988ec9830919ade26e0339506
7
- data.tar.gz: 9f9efda65a0c352060278fb229f4d61b9a35514b13e4fab35ad996bb3a37dfc973589ea1614e928230828b47b648f2418c1aeabef67b4f7b50612f06ef3dfd05
6
+ metadata.gz: 661f8462c7fd1f6769b1674a556d34c3859b8ef8832435d294c9879a49d230a27ec1cb961bfc4814d43e1d81d5ea5b2b701c7f423465ab0b24c9f72df4822f7e
7
+ data.tar.gz: 99aab4506a8903ad037f6bcbfb1d0fb8c64a25c48b9599f14336e4bb4522649de45b24ff8d983cbf1ef598730ee70ea53b923e9f425d630921eb89da6f670113
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ # Pulls down DNS data from old-dns
23
+ # rd-dns-check -s old-dns.mydomain.com -d mydomain.com. -f old-dns.yml
24
+
25
+ # Check data against old-dns
26
+ # rd-dns-check -s old-dns.mydomain.com -d mydomain.com. -c old-dns.yml
27
+
28
+ # Check data against new DNS server
29
+ # rd-dns-check -s 10.0.0.36 -d mydomain.com. -c old-dns.yml
30
+
31
+ require 'yaml'
32
+ require 'optparse'
33
+ require 'set'
34
+
35
+ class DNSRecord
36
+ def initialize(arr)
37
+ @record = arr
38
+ normalize
39
+ end
40
+
41
+ def normalize
42
+ @record[0] = @record[0].downcase
43
+ @record[1] = @record[1].upcase
44
+ @record[2] = @record[2].upcase
45
+ @record[3] = @record[3].downcase
46
+ end
47
+
48
+ def hostname
49
+ @record[0]
50
+ end
51
+
52
+ def klass
53
+ @record[1]
54
+ end
55
+
56
+ def type
57
+ @record[2]
58
+ end
59
+
60
+ def value
61
+ @record[3]
62
+ end
63
+
64
+ def is_address?
65
+ ["A", "AAAA"].include?(type)
66
+ end
67
+
68
+ def is_cname?
69
+ return type == "CNAME"
70
+ end
71
+
72
+ def to_s
73
+ "#{hostname.ljust(50)} #{klass.rjust(4)} #{type.rjust(5)} #{value}"
74
+ end
75
+
76
+ def key
77
+ "#{hostname}:#{klass}:#{type}".downcase
78
+ end
79
+
80
+ def to_a
81
+ @record
82
+ end
83
+
84
+ def == other
85
+ return @record == other.to_a
86
+ end
87
+ end
88
+
89
+ def dig(dns_server, cmd, exclude = ["TXT", "HINFO", "SOA", "NS"])
90
+ records = []
91
+
92
+ IO.popen("dig @#{dns_server} +nottlid +nocmd +noall +answer " + cmd) do |p|
93
+ p.each do |line|
94
+ r = line.chomp.split(/\s/, 4)
95
+
96
+ next if exclude.include?(r[2])
97
+
98
+ records << DNSRecord.new(r)
99
+ end
100
+ end
101
+
102
+ return records
103
+ end
104
+
105
+ def retrieve_records(dns_server, dns_root)
106
+ return dig(dns_server, "#{dns_root} AXFR")
107
+ end
108
+
109
+ def resolve_hostname(dns_server, hostname)
110
+ return dig(dns_server, "#{hostname} A").first
111
+ end
112
+
113
+ def resolve_address(dns_server, address)
114
+ return dig(dns_server, "-x #{address}").first
115
+ end
116
+
117
+ def print_summary(records, errors, okay, &block)
118
+ puts "[ Summary ]".center(72, "=")
119
+ puts "Checked #{records.size} record(s). #{errors} errors."
120
+ if errors == 0
121
+ puts "Everything seemed okay."
122
+ else
123
+ puts "The following records are okay:"
124
+ okay.each do |r|
125
+ if block_given?
126
+ yield r
127
+ else
128
+ puts "".rjust(12) + r.to_s
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ # Resolve hostnames to IP address "A" or "AAAA" records.
135
+ # Works through CNAME records in order to find out the final
136
+ # address if possible. Checks for loops in CNAME records.
137
+ def resolve_addresses(records)
138
+ addresses = {}
139
+ cnames = {}
140
+
141
+ # Extract all hostname -> ip address mappings
142
+ records.each do |r|
143
+ if r.is_address?
144
+ addresses[r.hostname] = r
145
+ elsif r.is_cname?
146
+ cnames[r.hostname] = r
147
+ end
148
+ end
149
+
150
+ cnames.each do |hostname, r|
151
+ q = r
152
+ trail = []
153
+ failed = false
154
+
155
+ # Keep track of CNAME records to avoid loops
156
+ while q.is_cname?
157
+ trail << q
158
+ q = cnames[q.value] || addresses[q.value]
159
+
160
+ # Q could be nil at this point, which means there was no address record
161
+ # Q could be already part of the trail, which means there was a loop
162
+ if q == nil || trail.include?(q)
163
+ failed = true
164
+ break
165
+ end
166
+ end
167
+
168
+ if failed
169
+ q = trail.last
170
+ puts "*** Warning: CNAME record #{hostname} does not point to actual address!"
171
+ trail.each_with_index do |r, idx|
172
+ puts idx.to_s.rjust(10) + ": " + r.to_s
173
+ end
174
+ end
175
+
176
+ addresses[r.hostname] = q
177
+ end
178
+
179
+ return addresses, cnames
180
+ end
181
+
182
+ def check_reverse(records, dns_server)
183
+ errors = 0
184
+ okay = []
185
+
186
+ puts "[ Checking Reverse Lookups ]".center(72, "=")
187
+
188
+ records.each do |r|
189
+ next unless r.is_address?
190
+
191
+ sr = resolve_address(dns_server, r.value)
192
+
193
+ if sr == nil
194
+ puts "*** Could not resolve host"
195
+ puts "".rjust(12) + r.to_s
196
+ errors += 1
197
+ elsif r.hostname != sr.value
198
+ puts "*** Hostname does not match"
199
+ puts "Primary: ".rjust(12) + r.to_s
200
+ puts "Secondary: ".rjust(12) + sr.to_s
201
+ errors += 1
202
+ else
203
+ okay << [r, sr]
204
+ end
205
+ end
206
+
207
+ print_summary(records, errors, okay) do |r|
208
+ puts "Primary:".rjust(12) + r[0].to_s
209
+ puts "Secondary:".rjust(12) + r[1].to_s
210
+ end
211
+ end
212
+
213
+ def ping_records(records)
214
+ addresses, cnames = resolve_addresses(records)
215
+
216
+ errors = 0
217
+ okay = []
218
+
219
+ puts "[ Pinging Records ]".center(72, "=")
220
+
221
+ addresses.each do |hostname, r|
222
+ ping = "ping -c 5 -t 5 -i 1 -o #{r.value} > /dev/null"
223
+
224
+ system(ping)
225
+
226
+ if $?.exitstatus == 0
227
+ okay << r
228
+ else
229
+ puts "*** Could not ping host #{hostname.dump}: #{ping.dump}"
230
+ puts "".rjust(12) + r.to_s
231
+ errors += 1
232
+ end
233
+ end
234
+
235
+ print_summary(records, errors, okay)
236
+ end
237
+
238
+ def query_records(primary, secondary_server)
239
+ addresses, cnames = resolve_addresses(primary)
240
+
241
+ okay = []
242
+ errors = 0
243
+
244
+ primary.each do |r|
245
+ sr = resolve_hostname(secondary_server, r.hostname)
246
+
247
+ if sr == nil
248
+ puts "*** Could not resolve hostname #{r.hostname.dump}"
249
+ puts "Primary: ".rjust(12) + r.to_s
250
+
251
+ rsr = resolve_address(secondary_server, (addresses[r.value] || r).value)
252
+ puts "Address: ".rjust(12) + rsr.to_s if rsr
253
+
254
+ errors += 1
255
+ elsif sr.value != r.value
256
+ ra = addresses[r.value] if r.is_cname?
257
+ sra = addresses[sr.value] if sr.is_cname?
258
+
259
+ if (sra || sr).value != (ra || r).value
260
+ puts "*** IP Address does not match"
261
+ puts "Primary: ".rjust(12) + r.to_s
262
+ puts "Resolved: ".rjust(12) + ra.to_s if ra
263
+ puts "Secondary: ".rjust(12) + sr.to_s
264
+ puts "Resolved: ".rjust(12) + sra.to_s if sra
265
+ errors += 1
266
+ end
267
+ else
268
+ okay << r
269
+ end
270
+ end
271
+
272
+ print_summary(primary, errors, okay)
273
+ end
274
+
275
+ def check_records(primary, secondary)
276
+ s = {}
277
+ okay = []
278
+ errors = 0
279
+
280
+ secondary.each do |r|
281
+ s[r.key] = r
282
+ end
283
+
284
+ puts "[ Checking Records ]".center(72, "=")
285
+
286
+ primary.each do |r|
287
+ sr = s[r.key]
288
+
289
+ if sr == nil
290
+ puts "*** Could not find record"
291
+ puts "Primary: ".rjust(12) + r.to_s
292
+ errors += 1
293
+ elsif sr != r
294
+ puts "*** Records are different"
295
+ puts "Primary: ".rjust(12) + r.to_s
296
+ puts "Secondary: ".rjust(12) + sr.to_s
297
+ errors += 1
298
+ else
299
+ okay << r
300
+ end
301
+ end
302
+
303
+ print_summary(primary, errors, okay)
304
+ end
305
+
306
+ OPTIONS = {
307
+ :DNSServer => nil,
308
+ :DNSRoot => ".",
309
+ }
310
+
311
+ ARGV.options do |o|
312
+ script_name = File.basename($0)
313
+
314
+ o.set_summary_indent(' ')
315
+ o.banner = "Usage: #{script_name} [options]"
316
+ o.define_head "This script is designed to test and check DNS servers."
317
+
318
+ o.on("-s ns.my.domain.", "--server ns.my.domain.", String, "The DNS server to query.") { |host| OPTIONS[:DNSServer] = host }
319
+ o.on("-d my.domain.", "--domain my.domain.", String, "The DNS zone to transfer/test.") { |host| OPTIONS[:DNSRoot] = host }
320
+
321
+ o.on("-f output.yml", "--fetch output.yml", String, "Pull down a list of hosts. Filters TXT and HINFO records. DNS transfers must be enabled.") { |f|
322
+ records = retrieve_records(OPTIONS[:DNSServer], OPTIONS[:DNSRoot])
323
+
324
+ output = (f ? File.open(f, "w") : STDOUT)
325
+
326
+ output.write(YAML::dump(records))
327
+
328
+ puts "#{records.size} record(s) retrieved."
329
+ }
330
+
331
+ o.on("-c input.yml", "--check input.yml", String, "Check that the DNS server returns results as specified by the file.") { |f|
332
+ input = (f ? File.open(f) : STDIN)
333
+
334
+ master_records = YAML::load(input.read)
335
+ secondary_records = retrieve_records(OPTIONS[:DNSServer], OPTIONS[:DNSRoot])
336
+
337
+ check_records(master_records, secondary_records)
338
+ }
339
+
340
+ o.on("-q input.yml", "--query input.yml", String, "Query the remote DNS server with all hostnames in the given file, and checks the IP addresses are consistent.") { |f|
341
+ input = (f ? File.open(f) : STDIN)
342
+
343
+ master_records = YAML::load(input.read)
344
+
345
+ query_records(master_records, OPTIONS[:DNSServer])
346
+ }
347
+
348
+ o.on("-p input.yml", "--ping input.yml", String, "Ping all hosts to check if they are available or not.") { |f|
349
+ input = (f ? File.open(f) : STDIN)
350
+
351
+ master_records = YAML::load(input.read)
352
+
353
+ ping_records(master_records)
354
+ }
355
+
356
+ o.on("-r input.yml", "--reverse input.yml", String, "Check that all address records have appropriate reverse entries.") { |f|
357
+ input = (f ? File.open(f) : STDIN)
358
+
359
+ master_records = YAML::load(input.read)
360
+
361
+ check_reverse(master_records, OPTIONS[:DNSServer])
362
+ }
363
+
364
+ o.separator ""
365
+ o.separator "Help and Copyright information"
366
+
367
+ o.on_tail("--copy", "Display copyright information") {
368
+ puts "#{script_name}. Copyright (c) 2009, 2011 Samuel Williams. Released under the MIT license."
369
+ puts "See http://www.oriontransfer.co.nz/ for more information."
370
+ exit
371
+ }
372
+
373
+ o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
374
+ end.parse!
@@ -21,6 +21,8 @@
21
21
  require_relative 'message'
22
22
  require_relative 'binary_string'
23
23
 
24
+ require 'securerandom'
25
+
24
26
  module RubyDNS
25
27
  class InvalidProtocolError < StandardError
26
28
  end
@@ -34,15 +36,14 @@ module RubyDNS
34
36
  # In the case of multiple servers, they will be checked in sequence.
35
37
  def initialize(servers, options = {})
36
38
  @servers = servers
37
- @sequence = 0
38
39
 
39
40
  @options = options
40
41
  end
41
42
 
42
43
  # Provides the next sequence identification number which is used to keep track of DNS messages.
43
44
  def next_id!
44
- # Sequence IDs are 16-bit integers.
45
- return (@sequence += 1) % (2**16)
45
+ # Using sequential numbers for the query ID is generally a bad thing because over UDP they can be spoofed. 16-bits isn't hard to guess either, but over UDP we also use a random port, so this makes effectively 32-bits of entropy to guess per request.
46
+ SecureRandom.random_number(2**16)
46
47
  end
47
48
 
48
49
  # Look up a named resource of the given resource_class.
@@ -105,7 +106,7 @@ module RubyDNS
105
106
  end
106
107
 
107
108
  # Measured in seconds:
108
- @timeout = options[:timeout] || 5
109
+ @timeout = options[:timeout] || 1
109
110
 
110
111
  @logger = options[:logger]
111
112
  end
@@ -192,7 +193,7 @@ module RubyDNS
192
193
 
193
194
  module UDPRequestHandler
194
195
  def self.open(host, port, request)
195
- # Open a datagram socket... EventMachine doesn't support connected datagram sockets, so we have to cheat a bit:
196
+ # Open a datagram socket... a random socket chosen by the OS by specifying 0 for the port:
196
197
  EventMachine::open_datagram_socket('', 0, self, request, host, port)
197
198
  end
198
199
 
@@ -208,6 +209,9 @@ module RubyDNS
208
209
  end
209
210
 
210
211
  def receive_data(data)
212
+ # local_port, local_ip = Socket.unpack_sockaddr_in(get_sockname)
213
+ # puts "Socket name: #{local_ip}:#{local_port}"
214
+
211
215
  # Receiving response from remote DNS server...
212
216
  message = RubyDNS::decode_message(data)
213
217
 
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module RubyDNS
22
- VERSION = "0.8.0"
22
+ VERSION = "0.8.1"
23
23
  end
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+
25
+ require 'rubydns'
26
+ require 'rubydns/system'
27
+
28
+ require 'benchmark'
29
+
30
+ class ResolverPerformanceTest < MiniTest::Test
31
+ # The larger this number, the better RubyDNS::Resolver will look as it is highly asynchronous.
32
+ DOMAINS = %W{
33
+ Facebook.com
34
+ Twitter.com
35
+ Google.com
36
+ Youtube.com
37
+ Wordpress.org
38
+ Adobe.com
39
+ Blogspot.com
40
+ Wikipedia.org
41
+ Linkedin.com
42
+ Wordpress.com
43
+ Yahoo.com
44
+ Amazon.com
45
+ Flickr.com
46
+ Pinterest.com
47
+ Tumblr.com
48
+ W3.org
49
+ Apple.com
50
+ Myspace.com
51
+ Vimeo.com
52
+ Microsoft.com
53
+ Youtu.be
54
+ Qq.com
55
+ Digg.com
56
+ Baidu.com
57
+ Stumbleupon.com
58
+ Addthis.com
59
+ Statcounter.com
60
+ Feedburner.com
61
+ TradeMe.co.nz
62
+ Delicious.com
63
+ Nytimes.com
64
+ Reddit.com
65
+ Weebly.com
66
+ Bbc.co.uk
67
+ Blogger.com
68
+ Msn.com
69
+ Macromedia.com
70
+ Goo.gl
71
+ Instagram.com
72
+ Gov.uk
73
+ Icio.us
74
+ Yandex.ru
75
+ Cnn.com
76
+ Webs.com
77
+ Google.de
78
+ T.co
79
+ Livejournal.com
80
+ Imdb.com
81
+ Mail.ru
82
+ Jimdo.com
83
+ }
84
+
85
+ def test_resolvers
86
+ rubydns_resolved = {}
87
+ resolv_resolved = {}
88
+
89
+ Benchmark.bm(20) do |x|
90
+ x.report("RubyDNS::Resolver") do
91
+ resolver = RubyDNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
92
+
93
+ # Number of requests remaining since this is an asynchronous event loop:
94
+ pending = DOMAINS.size
95
+
96
+ EventMachine::run do
97
+ DOMAINS.each do |domain|
98
+ resolver.addresses_for(domain) do |addresses|
99
+ rubydns_resolved[domain] = addresses
100
+
101
+ EventMachine::stop if (pending -= 1) == 0
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ x.report("Resolv::DNS") do
108
+ resolver = Resolv::DNS.new(:nameserver => "8.8.8.8")
109
+
110
+ DOMAINS.each do |domain|
111
+ resolv_resolved[domain] = resolver.getaddresses(domain)
112
+ end
113
+ end
114
+ end
115
+
116
+ DOMAINS.each do |domain|
117
+ # We don't really care if the responses aren't identical - they should be most of the time but due to the way DNS works this isn't always the case:
118
+ refute_empty resolv_resolved[domain]
119
+ refute_empty rubydns_resolved[domain]
120
+ end
121
+ end
122
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubydns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-13 00:00:00.000000000 Z
11
+ date: 2014-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -76,8 +76,7 @@ description: "\t\tRubyDNS is a high-performance DNS server which can be easily i
76
76
  email:
77
77
  - samuel.williams@oriontransfer.co.nz
78
78
  executables:
79
- - rd-dns-check
80
- - rd-resolve-test
79
+ - rubydns-check
81
80
  extensions: []
82
81
  extra_rdoc_files: []
83
82
  files:
@@ -87,8 +86,7 @@ files:
87
86
  - Gemfile
88
87
  - README.md
89
88
  - Rakefile
90
- - bin/rd-dns-check
91
- - bin/rd-resolve-test
89
+ - bin/rubydns-check
92
90
  - lib/rubydns.rb
93
91
  - lib/rubydns/binary_string.rb
94
92
  - lib/rubydns/chunked.rb
@@ -116,6 +114,7 @@ files:
116
114
  - test/test_message.rb
117
115
  - test/test_passthrough.rb
118
116
  - test/test_resolver.rb
117
+ - test/test_resolver_performance.rb
119
118
  - test/test_rules.rb
120
119
  - test/test_slow_server.rb
121
120
  - test/test_system.rb
@@ -158,6 +157,7 @@ test_files:
158
157
  - test/test_message.rb
159
158
  - test/test_passthrough.rb
160
159
  - test/test_resolver.rb
160
+ - test/test_resolver_performance.rb
161
161
  - test/test_rules.rb
162
162
  - test/test_slow_server.rb
163
163
  - test/test_system.rb
@@ -1,374 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
-
22
- # Pulls down DNS data from old-dns
23
- # rd-dns-check -s old-dns.mydomain.com -d mydomain.com. -f old-dns.yml
24
-
25
- # Check data against old-dns
26
- # rd-dns-check -s old-dns.mydomain.com -d mydomain.com. -c old-dns.yml
27
-
28
- # Check data against new DNS server
29
- # rd-dns-check -s 10.0.0.36 -d mydomain.com. -c old-dns.yml
30
-
31
- require 'yaml'
32
- require 'optparse'
33
- require 'set'
34
-
35
- class DNSRecord
36
- def initialize(arr)
37
- @record = arr
38
- normalize
39
- end
40
-
41
- def normalize
42
- @record[0] = @record[0].downcase
43
- @record[1] = @record[1].upcase
44
- @record[2] = @record[2].upcase
45
- @record[3] = @record[3].downcase
46
- end
47
-
48
- def hostname
49
- @record[0]
50
- end
51
-
52
- def klass
53
- @record[1]
54
- end
55
-
56
- def type
57
- @record[2]
58
- end
59
-
60
- def value
61
- @record[3]
62
- end
63
-
64
- def is_address?
65
- ["A", "AAAA"].include?(type)
66
- end
67
-
68
- def is_cname?
69
- return type == "CNAME"
70
- end
71
-
72
- def to_s
73
- "#{hostname.ljust(50)} #{klass.rjust(4)} #{type.rjust(5)} #{value}"
74
- end
75
-
76
- def key
77
- "#{hostname}:#{klass}:#{type}".downcase
78
- end
79
-
80
- def to_a
81
- @record
82
- end
83
-
84
- def == other
85
- return @record == other.to_a
86
- end
87
- end
88
-
89
- def dig(dns_server, cmd, exclude = ["TXT", "HINFO", "SOA", "NS"])
90
- records = []
91
-
92
- IO.popen("dig @#{dns_server} +nottlid +nocmd +noall +answer " + cmd) do |p|
93
- p.each do |line|
94
- r = line.chomp.split(/\s/, 4)
95
-
96
- next if exclude.include?(r[2])
97
-
98
- records << DNSRecord.new(r)
99
- end
100
- end
101
-
102
- return records
103
- end
104
-
105
- def retrieve_records(dns_server, dns_root)
106
- return dig(dns_server, "#{dns_root} AXFR")
107
- end
108
-
109
- def resolve_hostname(dns_server, hostname)
110
- return dig(dns_server, "#{hostname} A").first
111
- end
112
-
113
- def resolve_address(dns_server, address)
114
- return dig(dns_server, "-x #{address}").first
115
- end
116
-
117
- def print_summary(records, errors, okay, &block)
118
- puts "[ Summary ]".center(72, "=")
119
- puts "Checked #{records.size} record(s). #{errors} errors."
120
- if errors == 0
121
- puts "Everything seemed okay."
122
- else
123
- puts "The following records are okay:"
124
- okay.each do |r|
125
- if block_given?
126
- yield r
127
- else
128
- puts "".rjust(12) + r.to_s
129
- end
130
- end
131
- end
132
- end
133
-
134
- # Resolve hostnames to IP address "A" or "AAAA" records.
135
- # Works through CNAME records in order to find out the final
136
- # address if possible. Checks for loops in CNAME records.
137
- def resolve_addresses(records)
138
- addresses = {}
139
- cnames = {}
140
-
141
- # Extract all hostname -> ip address mappings
142
- records.each do |r|
143
- if r.is_address?
144
- addresses[r.hostname] = r
145
- elsif r.is_cname?
146
- cnames[r.hostname] = r
147
- end
148
- end
149
-
150
- cnames.each do |hostname, r|
151
- q = r
152
- trail = []
153
- failed = false
154
-
155
- # Keep track of CNAME records to avoid loops
156
- while q.is_cname?
157
- trail << q
158
- q = cnames[q.value] || addresses[q.value]
159
-
160
- # Q could be nil at this point, which means there was no address record
161
- # Q could be already part of the trail, which means there was a loop
162
- if q == nil || trail.include?(q)
163
- failed = true
164
- break
165
- end
166
- end
167
-
168
- if failed
169
- q = trail.last
170
- puts "*** Warning: CNAME record #{hostname} does not point to actual address!"
171
- trail.each_with_index do |r, idx|
172
- puts idx.to_s.rjust(10) + ": " + r.to_s
173
- end
174
- end
175
-
176
- addresses[r.hostname] = q
177
- end
178
-
179
- return addresses, cnames
180
- end
181
-
182
- def check_reverse(records, dns_server)
183
- errors = 0
184
- okay = []
185
-
186
- puts "[ Checking Reverse Lookups ]".center(72, "=")
187
-
188
- records.each do |r|
189
- next unless r.is_address?
190
-
191
- sr = resolve_address(dns_server, r.value)
192
-
193
- if sr == nil
194
- puts "*** Could not resolve host"
195
- puts "".rjust(12) + r.to_s
196
- errors += 1
197
- elsif r.hostname != sr.value
198
- puts "*** Hostname does not match"
199
- puts "Primary: ".rjust(12) + r.to_s
200
- puts "Secondary: ".rjust(12) + sr.to_s
201
- errors += 1
202
- else
203
- okay << [r, sr]
204
- end
205
- end
206
-
207
- print_summary(records, errors, okay) do |r|
208
- puts "Primary:".rjust(12) + r[0].to_s
209
- puts "Secondary:".rjust(12) + r[1].to_s
210
- end
211
- end
212
-
213
- def ping_records(records)
214
- addresses, cnames = resolve_addresses(records)
215
-
216
- errors = 0
217
- okay = []
218
-
219
- puts "[ Pinging Records ]".center(72, "=")
220
-
221
- addresses.each do |hostname, r|
222
- ping = "ping -c 5 -t 5 -i 1 -o #{r.value} > /dev/null"
223
-
224
- system(ping)
225
-
226
- if $?.exitstatus == 0
227
- okay << r
228
- else
229
- puts "*** Could not ping host #{hostname.dump}: #{ping.dump}"
230
- puts "".rjust(12) + r.to_s
231
- errors += 1
232
- end
233
- end
234
-
235
- print_summary(records, errors, okay)
236
- end
237
-
238
- def query_records(primary, secondary_server)
239
- addresses, cnames = resolve_addresses(primary)
240
-
241
- okay = []
242
- errors = 0
243
-
244
- primary.each do |r|
245
- sr = resolve_hostname(secondary_server, r.hostname)
246
-
247
- if sr == nil
248
- puts "*** Could not resolve hostname #{r.hostname.dump}"
249
- puts "Primary: ".rjust(12) + r.to_s
250
-
251
- rsr = resolve_address(secondary_server, (addresses[r.value] || r).value)
252
- puts "Address: ".rjust(12) + rsr.to_s if rsr
253
-
254
- errors += 1
255
- elsif sr.value != r.value
256
- ra = addresses[r.value] if r.is_cname?
257
- sra = addresses[sr.value] if sr.is_cname?
258
-
259
- if (sra || sr).value != (ra || r).value
260
- puts "*** IP Address does not match"
261
- puts "Primary: ".rjust(12) + r.to_s
262
- puts "Resolved: ".rjust(12) + ra.to_s if ra
263
- puts "Secondary: ".rjust(12) + sr.to_s
264
- puts "Resolved: ".rjust(12) + sra.to_s if sra
265
- errors += 1
266
- end
267
- else
268
- okay << r
269
- end
270
- end
271
-
272
- print_summary(primary, errors, okay)
273
- end
274
-
275
- def check_records(primary, secondary)
276
- s = {}
277
- okay = []
278
- errors = 0
279
-
280
- secondary.each do |r|
281
- s[r.key] = r
282
- end
283
-
284
- puts "[ Checking Records ]".center(72, "=")
285
-
286
- primary.each do |r|
287
- sr = s[r.key]
288
-
289
- if sr == nil
290
- puts "*** Could not find record"
291
- puts "Primary: ".rjust(12) + r.to_s
292
- errors += 1
293
- elsif sr != r
294
- puts "*** Records are different"
295
- puts "Primary: ".rjust(12) + r.to_s
296
- puts "Secondary: ".rjust(12) + sr.to_s
297
- errors += 1
298
- else
299
- okay << r
300
- end
301
- end
302
-
303
- print_summary(primary, errors, okay)
304
- end
305
-
306
- OPTIONS = {
307
- :DNSServer => nil,
308
- :DNSRoot => ".",
309
- }
310
-
311
- ARGV.options do |o|
312
- script_name = File.basename($0)
313
-
314
- o.set_summary_indent(' ')
315
- o.banner = "Usage: #{script_name} [options]"
316
- o.define_head "This script is designed to test and check DNS servers."
317
-
318
- o.on("-s ns.my.domain.", "--server ns.my.domain.", String, "The DNS server to query.") { |host| OPTIONS[:DNSServer] = host }
319
- o.on("-d my.domain.", "--domain my.domain.", String, "The DNS zone to transfer/test.") { |host| OPTIONS[:DNSRoot] = host }
320
-
321
- o.on("-f output.yml", "--fetch output.yml", String, "Pull down a list of hosts. Filters TXT and HINFO records. DNS transfers must be enabled.") { |f|
322
- records = retrieve_records(OPTIONS[:DNSServer], OPTIONS[:DNSRoot])
323
-
324
- output = (f ? File.open(f, "w") : STDOUT)
325
-
326
- output.write(YAML::dump(records))
327
-
328
- puts "#{records.size} record(s) retrieved."
329
- }
330
-
331
- o.on("-c input.yml", "--check input.yml", String, "Check that the DNS server returns results as specified by the file.") { |f|
332
- input = (f ? File.open(f) : STDIN)
333
-
334
- master_records = YAML::load(input.read)
335
- secondary_records = retrieve_records(OPTIONS[:DNSServer], OPTIONS[:DNSRoot])
336
-
337
- check_records(master_records, secondary_records)
338
- }
339
-
340
- o.on("-q input.yml", "--query input.yml", String, "Query the remote DNS server with all hostnames in the given file, and checks the IP addresses are consistent.") { |f|
341
- input = (f ? File.open(f) : STDIN)
342
-
343
- master_records = YAML::load(input.read)
344
-
345
- query_records(master_records, OPTIONS[:DNSServer])
346
- }
347
-
348
- o.on("-p input.yml", "--ping input.yml", String, "Ping all hosts to check if they are available or not.") { |f|
349
- input = (f ? File.open(f) : STDIN)
350
-
351
- master_records = YAML::load(input.read)
352
-
353
- ping_records(master_records)
354
- }
355
-
356
- o.on("-r input.yml", "--reverse input.yml", String, "Check that all address records have appropriate reverse entries.") { |f|
357
- input = (f ? File.open(f) : STDIN)
358
-
359
- master_records = YAML::load(input.read)
360
-
361
- check_reverse(master_records, OPTIONS[:DNSServer])
362
- }
363
-
364
- o.separator ""
365
- o.separator "Help and Copyright information"
366
-
367
- o.on_tail("--copy", "Display copyright information") {
368
- puts "#{script_name}. Copyright (c) 2009, 2011 Samuel Williams. Released under the MIT license."
369
- puts "See http://www.oriontransfer.co.nz/ for more information."
370
- exit
371
- }
372
-
373
- o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
374
- end.parse!
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # Copyright, 2009, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
-
22
- require 'rubydns/version'
23
-
24
- require 'resolv'
25
- require 'optparse'
26
-
27
- OPTIONS = {
28
- :Domains => [],
29
- :Nameservers => [],
30
- :Timeout => 0.5,
31
-
32
- :Threads => 10,
33
- :Requests => 20
34
- }
35
-
36
- ARGV.options do |o|
37
- script_name = File.basename($0)
38
-
39
- o.banner = "Usage: #{script_name} [options] nameserver [nameserver]"
40
-
41
- o.on("-d [path]", String, "Specify a file that contains a list of domains") do |path|
42
- OPTIONS[:Domains] += File.readlines(path).collect { |name| name.strip.downcase }
43
- end
44
-
45
- o.on("-t [timeout]", Float, "Queries that take longer than this will be printed") do |timeout|
46
- OPTIONS[:Timeout] = timeout.to_f
47
- end
48
-
49
- o.on("--threads [count]", Integer, "Number of threads to resolve names concurrently") do |count|
50
- OPTIONS[:Threads] = count.to_i
51
- end
52
-
53
- o.on("--requests [count]", Integer, "Number of requests to perform per thread") do |count|
54
- OPTIONS[:Requests] = count.to_i
55
- end
56
-
57
- o.on_tail("--copy", "Display copyright information") do
58
- puts "#{script_name} v#{RubyDNS::VERSION::STRING}. Copyright (c) 2009, 2011 Samuel Williams. Released under the MIT license."
59
- puts "See http://www.oriontransfer.co.nz/ for more information."
60
-
61
- exit
62
- end
63
-
64
- o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
65
- end.parse!
66
-
67
- OPTIONS[:Nameservers] = ARGV
68
-
69
- if OPTIONS[:Nameservers].size > 0
70
- $R = Resolv::DNS.new(:nameserver => ARGV)
71
- else
72
- $R = Resolv::DNS.new
73
- end
74
-
75
- $TG = ThreadGroup.new
76
- $M = Mutex.new
77
- $STATUS = {}
78
- $TOTAL = [0.0, 0]
79
-
80
- if OPTIONS[:Domains].size == 0
81
- OPTIONS[:Domains] += ["www.google.com", "www.amazon.com", "www.apple.com", "www.microsoft.com"]
82
- OPTIONS[:Domains] += ["www.rubygems.org", "www.ruby-lang.org", "www.slashdot.org", "www.lucidsystems.org"]
83
- OPTIONS[:Domains] += ["www.facebook.com", "www.twitter.com", "www.myspace.com", "www.youtube.com"]
84
- OPTIONS[:Domains] += ["www.oriontransfer.co.nz", "www.digg.com"]
85
- end
86
-
87
- def random_domain
88
- d = OPTIONS[:Domains]
89
-
90
- d[rand(d.size - 1)]
91
- end
92
-
93
- puts "Starting test with #{OPTIONS[:Domains].size} domains..."
94
- puts "Using nameservers: " + OPTIONS[:Nameservers].join(", ")
95
- puts "Only long running queries will be printed..."
96
-
97
- def resolve_domain
98
- s = Time.now
99
- result = nil
100
- name = random_domain
101
-
102
- begin
103
- result = [$R.getaddress(name)]
104
- rescue Resolv::ResolvError
105
- $M.synchronize do
106
- puts "Name #{name} failed to resolve!"
107
- $STATUS[name] ||= []
108
- $STATUS[name] << :failure
109
-
110
- if $STATUS[name].include?(:success)
111
- puts "Name #{name} has had previous successes!"
112
- end
113
- end
114
-
115
- return
116
- end
117
-
118
- result.unshift(name)
119
- result.unshift(Time.now - s)
120
-
121
- $M.synchronize do
122
- $TOTAL[0] += result[0]
123
- $TOTAL[1] += 1
124
-
125
- if result[0] > OPTIONS[:Timeout]
126
- puts "\t\t%0.2fs: %s => %s" % result
127
- end
128
-
129
- $STATUS[name] ||= []
130
- $STATUS[name] << :success
131
-
132
- if $STATUS[name].include?(:failure)
133
- puts "Name #{name} has had previous failures!"
134
- end
135
- end
136
- end
137
-
138
- puts "Starting threads..."
139
- Thread.abort_on_exception = true
140
-
141
- OPTIONS[:Threads].times do
142
- th = Thread.new do
143
- OPTIONS[:Requests].times do
144
- resolve_domain
145
- end
146
- end
147
-
148
- $TG.add th
149
- end
150
-
151
- $TG.list.each { |thr| thr.join }
152
-
153
- $STATUS.each do |name, results|
154
- if results.include?(:failure)
155
- puts "Name #{name} failed at least once!"
156
- end
157
- end
158
-
159
- puts
160
- puts "Requests: #{$TOTAL[1]} Average time: #{$TOTAL[0] / $TOTAL[1]}"