rubydns 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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]}"