net-dns 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,195 @@
1
+ #---
2
+ # $Id: Question.rb,v 1.8 2006/07/28 19:00:03 bluemonk Exp $
3
+ #+++
4
+
5
+ require 'net/dns/dns'
6
+ require 'net/dns/names/names'
7
+ require 'net/dns/rr/types'
8
+ require 'net/dns/rr/classes'
9
+
10
+ module Net # :nodoc:
11
+ module DNS
12
+
13
+ #
14
+ # =Name
15
+ #
16
+ # Net::DNS::Question - DNS packet question class
17
+ #
18
+ # =Synopsis
19
+ #
20
+ # require 'net/dns/question'
21
+ #
22
+ # =Description
23
+ #
24
+ # This class represent the Question portion of a DNS packet. The number
25
+ # of question entries is stored in the +qdCount+ variable of an Header
26
+ # object.
27
+ #
28
+ # A new object can be created passing the name of the query and the type
29
+ # of answer desired, plus an optional argument containing the class:
30
+ #
31
+ # question = Net::DNS::Question.new("google.com.", Net::DNS::A)
32
+ # #=> "google.com. A IN"
33
+ #
34
+ # Alternatevly, a new object is created when processing a binary
35
+ # packet, as when an answer is received.
36
+ # To obtain the binary data from a question object you can use
37
+ # the method Question#data:
38
+ #
39
+ # question.data
40
+ # #=> "\006google\003com\000\000\001\000\001"
41
+ #
42
+ # A lot of methods were written to keep a compatibility layer with
43
+ # the Perl version of the library, as long as methods name which are
44
+ # more or less the same.
45
+ #
46
+ # =Error classes
47
+ #
48
+ # Some error classes has been defined for the Net::DNS::Header class,
49
+ # which are listed here to keep a light and browsable main documentation.
50
+ # We have:
51
+ #
52
+ # * QuestionArgumentError: generic argument error
53
+ # * QuestionNameError: an error in the +name+ part of a Question entry
54
+ #
55
+ # =Copyright
56
+ #
57
+ # Copyright (c) 2006 Marco Ceresa
58
+ #
59
+ # All rights reserved. This program is free software; you may redistribute
60
+ # it and/or modify it under the same terms as Ruby itself.
61
+ #
62
+ class Question
63
+
64
+ include Net::DNS::Names
65
+
66
+ # +name+ part of a Question entry
67
+ attr_reader :qName
68
+ # +type+ part of a Question entry
69
+ attr_reader :qType
70
+ # +class+ part of a Question entry
71
+ attr_reader :qClass
72
+
73
+ # Creates a new Net::DNS::Question object:
74
+ #
75
+ # question = Net::DNS::Question.new("example.com")
76
+ # #=> "example.com A IN"
77
+ # question = Net::DNS::Question.new("example.com", Net::DNS::MX)
78
+ # #=> "example.com MX IN"
79
+ # question = Net::DNS::Question.new("example.com", Net::DNS::TXT, Net::DNS::HS)
80
+ # #=> "example.com TXT HS"
81
+
82
+ # If not specified, +type+ and +cls+ arguments defaults
83
+ # to Net::DNS::A and Net::DNS::IN respectively.
84
+ #
85
+ def initialize(name,type=Net::DNS::A,cls=Net::DNS::IN)
86
+ @qName = check_name name
87
+ @qType = Net::DNS::RR::Types.new type
88
+ @qClass = Net::DNS::RR::Classes.new cls
89
+ end
90
+
91
+ # Return a new Net::DNS::Question object created by
92
+ # parsing binary data, such as an answer from the
93
+ # nameserver.
94
+ #
95
+ # question = Net::DNS::Question.parse(data)
96
+ # puts "Queried for #{question.qName} type #{question.qType.to_s}"
97
+ # #=> Queried for example.com type A
98
+ #
99
+ def self.parse(arg)
100
+ if arg.kind_of? String
101
+ o = allocate
102
+ o.send(:new_from_binary,arg)
103
+ o
104
+ else
105
+ raise QuestionArgumentError, "Wrong argument format, must be a String"
106
+ end
107
+ end
108
+
109
+ # Known inspect method with nice formatting
110
+ def inspect
111
+ if @qName.size > 29 then
112
+ len = @qName.size + 1
113
+ else
114
+ len = 29
115
+ end
116
+ [@qName,@qClass.to_s,@qType.to_s].pack("A#{len} A8 A8")
117
+ end
118
+
119
+ # Outputs binary data from a Question object
120
+ #
121
+ # question.data
122
+ # #=> "\006google\003com\000\000\001\000\001"
123
+ #
124
+ def data
125
+ [pack_name(@qName),@qType.to_i,@qClass.to_i].pack("a*nn")
126
+ end
127
+
128
+ # Return the binary data of the objects, plus an offset
129
+ # and an Hash with references to compressed names. For use in
130
+ # Net::DNS::Packet compressed packet creation.
131
+ #
132
+ def comp_data
133
+ arr = @qName.split(".")
134
+ str = pack_name(@qName)
135
+ string = ""
136
+ names = {}
137
+ offset = Net::DNS::HFIXEDSZ
138
+ arr.size.times do |i|
139
+ x = i+1
140
+ elem = arr[-x]
141
+ len = elem.size
142
+ string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
143
+ names[string] = offset
144
+ offset += len
145
+ end
146
+ offset += 2 * Net::DNS::INT16SZ
147
+ str += "\000"
148
+ [[str,@qType.to_i,@qClass.to_i].pack("a*nn"),offset,names]
149
+ end
150
+
151
+ private
152
+
153
+ def build_qName(str)
154
+ result = ""
155
+ offset = 0
156
+ loop do
157
+ len = str.unpack("@#{offset} C")[0]
158
+ break if len == 0
159
+ offset += 1
160
+ result += str[offset..offset+len-1]
161
+ result += "."
162
+ offset += len
163
+ end
164
+ result
165
+ end
166
+
167
+ def check_name(name)
168
+ name.strip!
169
+ if name =~ /[^\w\.\-_]/
170
+ raise QuestionNameError, "Question name #{name.inspect} not valid"
171
+ else
172
+ name
173
+ end
174
+ rescue
175
+ raise QuestionNameError, "Question name #{name.inspect} not valid"
176
+ end
177
+
178
+ def new_from_binary(data)
179
+ str,type,cls = data.unpack("a#{data.size-4}nn")
180
+ @qName = build_qName(str)
181
+ @qType = Net::DNS::RR::Types.new type
182
+ @qClass = Net::DNS::RR::Classes.new cls
183
+ rescue StandardError => e
184
+ raise QuestionArgumentError, "Invalid data: #{data.inspect}\n{e.backtrace}"
185
+ end
186
+
187
+ end # class Question
188
+
189
+ end # class DNS
190
+ end # module Net
191
+
192
+ class QuestionArgumentError < ArgumentError # :nodoc:
193
+ end
194
+ class QuestionNameError < StandardError # :nodoc:
195
+ end
@@ -0,0 +1,1199 @@
1
+ #
2
+ # $Id: Resolver.rb,v 1.11 2006/07/30 16:55:35 bluemonk Exp $
3
+ #
4
+
5
+
6
+
7
+ require 'socket'
8
+ require 'timeout'
9
+ require 'ipaddr'
10
+ require 'logger'
11
+ require 'net/dns/packet'
12
+ require 'net/dns/resolver/timeouts'
13
+
14
+
15
+ module Net # :nodoc:
16
+ module DNS
17
+
18
+
19
+
20
+ # =Name
21
+ #
22
+ # Net::DNS::Resolver - DNS resolver class
23
+ #
24
+ # =Synopsis
25
+ #
26
+ # require 'net/dns/resolver'
27
+ #
28
+ # =Description
29
+ #
30
+ # The Net::DNS::Resolver class implements a complete DNS resolver written
31
+ # in pure Ruby, without a single C line of code. It has all of the
32
+ # tipical properties of an evoluted resolver, and a bit of OO which
33
+ # comes from having used Ruby.
34
+ #
35
+ # This project started as a porting of the Net::DNS Perl module,
36
+ # written by Martin Fuhr, but turned out (in the last months) to be
37
+ # an almost complete rewriting. Well, maybe some of the features of
38
+ # the Perl version are still missing, but guys, at least this is
39
+ # readable code!
40
+ #
41
+ # FIXME
42
+ #
43
+ # =Environment
44
+ #
45
+ # The Following Environment variables can also be used to configure
46
+ # the resolver:
47
+ #
48
+ # * +RES_NAMESERVERS+: A space-separated list of nameservers to query.
49
+ #
50
+ # # Bourne Shell
51
+ # $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"
52
+ # $ export RES_NAMESERVERS
53
+ #
54
+ # # C Shell
55
+ # % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"
56
+ #
57
+ # * +RES_SEARCHLIST+: A space-separated list of domains to put in the
58
+ # search list.
59
+ #
60
+ # # Bourne Shell
61
+ # $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"
62
+ # $ export RES_SEARCHLIST
63
+ #
64
+ # # C Shell
65
+ # % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"
66
+ #
67
+ # * +LOCALDOMAIN+: The default domain.
68
+ #
69
+ # # Bourne Shell
70
+ # $ LOCALDOMAIN=example.com
71
+ # $ export LOCALDOMAIN
72
+ #
73
+ # # C Shell
74
+ # % setenv LOCALDOMAIN example.com
75
+ #
76
+ # * +RES_OPTIONS+: A space-separated list of resolver options to set.
77
+ # Options that take values are specified as option:value.
78
+ #
79
+ # # Bourne Shell
80
+ # $ RES_OPTIONS="retrans:3 retry:2 debug"
81
+ # $ export RES_OPTIONS
82
+ #
83
+ # # C Shell
84
+ # % setenv RES_OPTIONS "retrans:3 retry:2 debug"
85
+ #
86
+ class Resolver
87
+
88
+ # An hash with the defaults values of almost all the
89
+ # configuration parameters of a resolver object. See
90
+ # the description for each parameter to have an
91
+ # explanation of its usage.
92
+ Defaults = {
93
+ :config_file => "/etc/resolv.conf",
94
+ :log_file => $stdout,
95
+ :port => 53,
96
+ :searchlist => [],
97
+ :nameservers => [IPAddr.new("127.0.0.1")],
98
+ :domain => "",
99
+ :source_port => 0,
100
+ :source_address => IPAddr.new("0.0.0.0"),
101
+ :retry_interval => 5,
102
+ :retry_number => 4,
103
+ :recursive => true,
104
+ :defname => true,
105
+ :dns_search => true,
106
+ :use_tcp => false,
107
+ :ignore_trucated => false,
108
+ :packet_size => 512,
109
+ :tcp_timeout => TcpTimeout.new(120),
110
+ :udp_timeout => UdpTimeout.new(0)}
111
+
112
+
113
+ # Create a new resolver object.
114
+ #
115
+ # Argument +config+ can either be empty or be an hash with
116
+ # some configuration parameters. To know what each parameter
117
+ # do, look at the description of each.
118
+ # Some example:
119
+ #
120
+ # # Use the sistem defaults
121
+ # res = Net::DNS::Resolver.new
122
+ #
123
+ # # Specify a configuration file
124
+ # res = Net::DNS::Resolver.new(:config_file => '/my/dns.conf')
125
+ #
126
+ # # Set some option
127
+ # res = Net::DNS::Resolver.new(:nameservers => "172.16.1.1",
128
+ # :recursive => false,
129
+ # :retry => 10)
130
+ #
131
+ # ===Config file
132
+ #
133
+ # Net::DNS::Resolver uses a config file to read the usual
134
+ # values a resolver needs, such as nameserver list and
135
+ # domain names. On UNIX systems the defaults are read from the
136
+ # following files, in the order indicated:
137
+ #
138
+ # * /etc/resolv.conf
139
+ # * $HOME/.resolv.conf
140
+ # * ./.resolv.conf
141
+ #
142
+ # The following keywords are recognized in resolver configuration files:
143
+ #
144
+ # * domain: the default domain.
145
+ # * search: a space-separated list of domains to put in the search list.
146
+ # * nameserver: a space-separated list of nameservers to query.
147
+ #
148
+ # Files except for /etc/resolv.conf must be owned by the effective userid
149
+ # running the program or they won't be read. In addition, several environment
150
+ # variables can also contain configuration information; see Environment
151
+ # in the main description for Resolver class.
152
+ #
153
+ # On Windows Systems, an attempt is made to determine the system defaults
154
+ # using the registry. This is still a work in progress; systems with many
155
+ # dynamically configured network interfaces may confuse Net::DNS.
156
+ #
157
+ # You can include a configuration file of your own when creating a resolver
158
+ # object:
159
+ #
160
+ # # Use my own configuration file
161
+ # my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');
162
+ #
163
+ # This is supported on both UNIX and Windows. Values pulled from a custom
164
+ # configuration file override the the system's defaults, but can still be
165
+ # overridden by the other arguments to Resolver::new.
166
+ #
167
+ # Explicit arguments to Resolver::new override both the system's defaults
168
+ # and the values of the custom configuration file, if any.
169
+ #
170
+ # ===Parameters
171
+ #
172
+ # The following arguments to Resolver::new are supported:
173
+ #
174
+ # - nameservers: an array reference of nameservers to query.
175
+ # - searchlist: an array reference of domains.
176
+ # - recurse
177
+ # - debug
178
+ # - domain
179
+ # - port
180
+ # - srcaddr
181
+ # - srcport
182
+ # - tcp_timeout
183
+ # - udp_timeout
184
+ # - retrans
185
+ # - retry
186
+ # - usevc
187
+ # - stayopen
188
+ # - igntc
189
+ # - defnames
190
+ # - dnsrch
191
+ # - persistent_tcp
192
+ # - persistent_udp
193
+ # - dnssec
194
+ #
195
+ # For more information on any of these options, please consult the
196
+ # method of the same name.
197
+ #
198
+ # ===Disclaimer
199
+ #
200
+ # Part of the above documentation is taken from the one in the
201
+ # Net::DNS::Resolver Perl module.
202
+ #
203
+ def initialize(config = {})
204
+ raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash
205
+ # config.key_downcase!
206
+ @config = Defaults.merge config
207
+ @raw = false
208
+
209
+ # New logger facility
210
+ @logger = Logger.new(@config[:log_file])
211
+ @logger.level = Logger::WARN
212
+
213
+ #------------------------------------------------------------
214
+ # Resolver configuration will be set in order from:
215
+ # 1) initialize arguments
216
+ # 2) ENV variables
217
+ # 3) config file
218
+ # 4) defaults (and /etc/resolv.conf for config)
219
+ #------------------------------------------------------------
220
+
221
+
222
+
223
+ #------------------------------------------------------------
224
+ # Parsing config file
225
+ #------------------------------------------------------------
226
+ parse_config_file
227
+
228
+ #------------------------------------------------------------
229
+ # Parsing ENV variables
230
+ #------------------------------------------------------------
231
+ parse_environment_variables
232
+
233
+ #------------------------------------------------------------
234
+ # Parsing arguments
235
+ #------------------------------------------------------------
236
+ config.each do |key,val|
237
+ next if key == :log_file or key == :config_file
238
+ begin
239
+ eval "self.#{key.to_s} = val"
240
+ rescue NoMethodError
241
+ raise ResolverArgumentError, "Option #{key} not valid"
242
+ end
243
+ end
244
+ end
245
+
246
+ def Resolver.cname_addr(names,packet)
247
+ addr = []
248
+ oct2 = RegExp.new('(?:2[0-4]\d|25[0-5]|[0-1]?\d\d|\d)')
249
+
250
+ packet.answer.each do |rr|
251
+ next unless begin
252
+ ret = false
253
+ names.each {|x| ret = true if rr.name.include? x}
254
+ ret
255
+ end
256
+ if rr.type == Net::DNS::CNAME
257
+ names.push(rr.cname)
258
+ elsif rr.type == Net::DNS::A
259
+ next unless rr.address =~ /^(#{oct2}\.#{oct2}\.#{oct2}\.#{oct2})$/
260
+ addr.push($1)
261
+ end
262
+ end
263
+ addr
264
+ end
265
+
266
+ def _packetsz
267
+ if @udppacketsize > Net::DNS::PACKETSZ
268
+ @udppacketsize
269
+ else
270
+ Net::DNS::PACKETSZ
271
+ end
272
+ end
273
+
274
+ def send_tcp(packet, packet_data)
275
+ if @nameservers.size == 0
276
+ @errorstring = "no nameservers"
277
+ puts ";; ERROR: send_tcp: no nameservers\n" if $DEBUG
278
+ return nil # ?
279
+ end
280
+
281
+ self._reset_errorstring
282
+
283
+ timeout = @tcp_timeout
284
+
285
+ @nameservers.each do |ns|
286
+ srcport = @srcport
287
+ srcaddr = @srcaddr
288
+ dstport = @port
289
+
290
+ puts ";; send_tcp(#{ns}:#{dstport}) (src port = #{srcport})" if $DEBUG
291
+
292
+ sock = TCPSocket.new(ns,dstport)
293
+
294
+ # TODO !!!
295
+ end
296
+ end
297
+
298
+ end
299
+ end
300
+ end
301
+
302
+
303
+
304
+
305
+
306
+ module Net
307
+ module DNS
308
+ class Resolver
309
+
310
+ # Get the resolver searchlist, returned as an array of entries
311
+ #
312
+ # res.searchlist
313
+ # #=> ["example.com","a.example.com","b.example.com"]
314
+ #
315
+ def searchlist
316
+ @config[:searchlist].inspect
317
+ end
318
+
319
+ # Set the resolver searchlist.
320
+ # +arg+ can be a single string or an array of strings
321
+ #
322
+ # res.searchstring = "example.com"
323
+ # res.searchstring = ["example.com","a.example.com","b.example.com"]
324
+ #
325
+ # Note that you can also append a new name to the searchlist
326
+ #
327
+ # res.searchlist << "c.example.com"
328
+ # res.searchlist
329
+ # #=> ["example.com","a.example.com","b.example.com","c.example.com"]
330
+ #
331
+ # The default is an empty array
332
+ #
333
+ def searchlist=(arg)
334
+ case arg
335
+ when String
336
+ @config[:searchlist] = [arg] if valid? arg
337
+ @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
338
+ when Array
339
+ @config[:searchlist] = arg if arg.all? {|x| valid? x}
340
+ @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
341
+ else
342
+ raise ResolverArgumentError, "Wrong argument format, neither String nor Array"
343
+ end
344
+ end
345
+
346
+ # Get the list of resolver nameservers, in a dotted decimal format
347
+ #
348
+ # res.nameservers
349
+ # #=> ["192.168.0.1","192.168.0.2"]
350
+ #
351
+ def nameservers
352
+ arr = []
353
+ @config[:nameservers].each do |x|
354
+ arr << x.to_s
355
+ end
356
+ arr.inspect
357
+ end
358
+ alias_method :nameserver, :nameservers
359
+
360
+ # Set the list of resolver nameservers
361
+ # +arg+ can be a single ip address or an array of addresses
362
+ #
363
+ # res.nameservers = "192.168.0.1"
364
+ # res.nameservers = ["192.168.0.1","192.168.0.2"]
365
+ #
366
+ # If you want you can specify the addresses as IPAddr instances
367
+ #
368
+ # ip = IPAddr.new("192.168.0.3")
369
+ # res.nameservers << ip
370
+ # #=> ["192.168.0.1","192.168.0.2","192.168.0.3"]
371
+ #
372
+ # The default is 127.0.0.1 (localhost)
373
+ #
374
+ def nameservers=(arg)
375
+ case arg
376
+ when String
377
+ begin
378
+ @config[:nameservers] = [IPAddr.new(arg)]
379
+ @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
380
+ rescue ArgumentError # arg is in the name form, not IP
381
+ nameservers_from_name(arg)
382
+ end
383
+ when IPAddr
384
+ @config[:nameservers] = [arg]
385
+ @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
386
+ when Array
387
+ @config[:nameservers] = []
388
+ arg.each do |x|
389
+ @config[:nameservers] << case x
390
+ when String
391
+ begin
392
+ IPAddr.new(x)
393
+ rescue ArgumentError
394
+ nameservers_from_name(arg)
395
+ return
396
+ end
397
+ when IPAddr
398
+ x
399
+ else
400
+ raise ResolverArgumentError, "Wrong argument format"
401
+ end
402
+ end
403
+ @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
404
+ else
405
+ raise ResolverArgumentError, "Wrong argument format, neither String, Array nor IPAddr"
406
+ end
407
+ end
408
+ alias_method("nameserver=","nameservers=")
409
+
410
+ # Return a string with the default domain
411
+ #
412
+ def domain
413
+ @config[:domain].inspect
414
+ end
415
+
416
+ # Set the domain for the query
417
+ #
418
+ def domain=(name)
419
+ @config[:domain] = name if valid? name
420
+ end
421
+
422
+ # Return the defined size of the packet
423
+ #
424
+ def packet_size
425
+ @config[:packet_size]
426
+ end
427
+
428
+ # Get the port number to which the resolver sends queries.
429
+ #
430
+ # puts "Sending queries to port #{res.port}"
431
+ #
432
+ def port
433
+ @config[:port]
434
+ end
435
+
436
+ # Set the port number to which the resolver sends queries. This can be useful
437
+ # for testing a nameserver running on a non-standard port.
438
+ #
439
+ # res.port = 10053
440
+ #
441
+ # The default is port 53.
442
+ #
443
+ def port=(num)
444
+ if (0..65535).include? num
445
+ @config[:port] = num
446
+ @logger.info "Port number changed to #{num}"
447
+ else
448
+ raise ResolverArgumentError, "Wrong port number #{num}"
449
+ end
450
+ end
451
+
452
+ # Get the value of the source port number
453
+ #
454
+ # puts "Sending queries using port #{res.source_port}"
455
+ #
456
+ def source_port
457
+ @config[:source_port]
458
+ end
459
+ alias srcport source_port
460
+
461
+ # Set the local source port from which the resolver sends its queries.
462
+ #
463
+ # res.source_port = 40000
464
+ #
465
+ # Note that if you want to set a port you need root priviledges, as
466
+ # raw sockets will be used to generate packets. The class will then
467
+ # generate the exception ResolverPermissionError if you're not root.
468
+ #
469
+ # The default is 0, which means that the port will be chosen by the
470
+ # underlaying layers.
471
+ #
472
+ def source_port=(num)
473
+ unless root?
474
+ raise ResolverPermissionError, "Are you root?"
475
+ end
476
+ if (0..65535).include?(num)
477
+ @config[:source_port] = num
478
+ else
479
+ raise ResolverArgumentError, "Wrong port number #{num}"
480
+ end
481
+ end
482
+ alias srcport= source_port=
483
+
484
+ # Get the local address from which the resolver sends queries
485
+ #
486
+ # puts "Sending queries using source address #{res.source_address}"
487
+ #
488
+ def source_address
489
+ @config[:source_address].to_s
490
+ end
491
+ alias srcaddr source_address
492
+
493
+ # Set the local source address from which the resolver sends its
494
+ # queries.
495
+ #
496
+ # res.source_address = "172.16.100.1"
497
+ # res.source_address = IPAddr.new("172.16.100.1")
498
+ #
499
+ # You can specify +arg+ as either a string containing the ip address
500
+ # or an instance of IPAddr class.
501
+ #
502
+ # Normally this can be used to force queries out a specific interface
503
+ # on a multi-homed host. In this case, you should of course need to
504
+ # know the addresses of the interfaces.
505
+ #
506
+ # Another way to use this option is for some kind of spoofing attacks
507
+ # towards weak nameservers, to probe the security of your network.
508
+ # This includes specifing ranged attacks such as DoS and others. For
509
+ # a paper on DNS security, checks http://www.marcoceresa.com/security/
510
+ #
511
+ # Note that if you want to set a non-binded source address you need
512
+ # root priviledges, as raw sockets will be used to generate packets.
513
+ # The class will then generate an exception if you're not root.
514
+ #
515
+ # The default is 0.0.0.0, meaning any local address (chosen on routing
516
+ # needs).
517
+ #
518
+ def source_address=(addr)
519
+ unless addr.respond_to? :to_s
520
+ raise ResolverArgumentError, "Wrong address argument #{addr}"
521
+ end
522
+
523
+ begin
524
+ port = rand(64000)+1024
525
+ @logger.warn "Try to determine state of source address #{addr} with port #{port}"
526
+ a = TCPServer.new(addr.to_s,port)
527
+ rescue SystemCallError => e
528
+ case e.errno
529
+ when 98 # Port already in use!
530
+ @logger.warn "Port already in use"
531
+ retry
532
+ when 99 # Address is not valid: raw socket
533
+ @raw = true
534
+ @logger.warn "Using raw sockets"
535
+ else
536
+ raise SystemCallError, e
537
+ end
538
+ ensure
539
+ a.close
540
+ end
541
+
542
+ case addr
543
+ when String
544
+ @config[:source_address] = IPAddr.new(string)
545
+ @logger.info "Using new source address: #{@config[:source_address]}"
546
+ when IPAddr
547
+ @config[:source_address] = addr
548
+ @logger.info "Using new source address: #{@config[:source_address]}"
549
+ else
550
+ raise ArgumentError, "Unknown dest_address format"
551
+ end
552
+ end
553
+ alias srcaddr= source_address=
554
+
555
+ # Return the retrasmission interval (in seconds) the resolvers has
556
+ # been set on
557
+ #
558
+ def retry_interval
559
+ @config[:retry_interval]
560
+ end
561
+ alias retrans retry_interval
562
+
563
+ # Set the retrasmission interval in seconds. Default 5 seconds
564
+ #
565
+ def retry_interval=(num)
566
+ if num > 0
567
+ @config[:retry_interval] = num
568
+ @logger.info "Retransmission interval changed to #{num} seconds"
569
+ else
570
+ raise ResolverArgumentError, "Interval must be positive"
571
+ end
572
+ end
573
+ alias retrans= retry_interval=
574
+
575
+ # The number of times the resolver will try a query
576
+ #
577
+ # puts "Will try a max of #{res.retry_number} queries"
578
+ #
579
+ def retry_number
580
+ @config[:retry_number]
581
+ end
582
+
583
+ # Set the number of times the resolver will try a query.
584
+ # Default 4 times
585
+ #
586
+ def retry_number=(num)
587
+ if num.kind_of? Integer and num > 0
588
+ @config[:retry_number] = num
589
+ @logger.info "Retrasmissions number changed to #{num}"
590
+ else
591
+ raise ResolverArgumentError, "Retry value must be a positive integer"
592
+ end
593
+ end
594
+ alias_method('retry=', 'retry_number=')
595
+
596
+ # This method will return true if the resolver is configured to
597
+ # perform recursive queries.
598
+ #
599
+ # print "The resolver will perform a "
600
+ # print res.recursive? ? "" : "not "
601
+ # puts "recursive query"
602
+ #
603
+ def recursive?
604
+ @config[:recursive]
605
+ end
606
+ alias_method :recurse, :recursive?
607
+ alias_method :recursive, :recursive?
608
+
609
+ # Sets whether or not the resolver should perform recursive
610
+ # queries. Default is true.
611
+ #
612
+ # res.recursive = false # perform non-recursive query
613
+ #
614
+ def recursive=(bool)
615
+ case bool
616
+ when TrueClass,FalseClass
617
+ @config[:recursive] = bool
618
+ @logger.info("Recursive state changed to #{bool}")
619
+ else
620
+ raise ResolverArgumentError, "Argument must be boolean"
621
+ end
622
+ end
623
+ alias_method :recurse=, :recursive=
624
+
625
+ # Return a string rapresenting the resolver state, suitable
626
+ # for printing on the screen.
627
+ #
628
+ # puts "Resolver state:"
629
+ # puts res.state
630
+ #
631
+ def state
632
+ str = ";; RESOLVER state:\n;; "
633
+ i = 1
634
+ @config.each do |key,val|
635
+ if key == :log_file or key == :config_file
636
+ str << "#{key}: #{val} \t"
637
+ else
638
+ str << "#{key}: #{eval(key.to_s)} \t"
639
+ end
640
+ str << "\n;; " if i % 2 == 0
641
+ i += 1
642
+ end
643
+ str
644
+ end
645
+ alias print state
646
+ alias inspect state
647
+
648
+ # Checks whether the +defname+ flag has been activate.
649
+ def defname?
650
+ @config[:defname]
651
+ end
652
+ alias defname defname?
653
+
654
+ # Set the flag +defname+ in a boolean state. if +defname+ is true,
655
+ # calls to Resolver#query will append the default domain to names
656
+ # that contain no dots.
657
+ # Example:
658
+ #
659
+ # # Domain example.com
660
+ # res.defname = true
661
+ # res.query("machine1")
662
+ # #=> This will perform a query for machine1.example.com
663
+ #
664
+ # Default is true.
665
+ #
666
+ def defname=(bool)
667
+ case bool
668
+ when TrueClass,FalseClass
669
+ @config[:defname] = bool
670
+ @logger.info("Defname state changed to #{bool}")
671
+ else
672
+ raise ResolverArgumentError, "Argument must be boolean"
673
+ end
674
+ end
675
+
676
+ # Get the state of the dns_search flag
677
+ def dns_search
678
+ @config[:dns_search]
679
+ end
680
+ alias_method :dnsrch, :dns_search
681
+
682
+ # Set the flag +dns_search+ in a boolean state. If +dns_search+
683
+ # is true, when using the Resolver#search method will be applied
684
+ # the search list. Default is true.
685
+ #
686
+ def dns_search=(bool)
687
+ case bool
688
+ when TrueClass,FalseClass
689
+ @config[:dns_search] = bool
690
+ @logger.info("DNS search state changed to #{bool}")
691
+ else
692
+ raise ResolverArgumentError, "Argument must be boolean"
693
+ end
694
+ end
695
+ alias_method("dnsrch=","dns_search=")
696
+
697
+ # Get the state of the use_tcp flag.
698
+ #
699
+ def use_tcp?
700
+ @config[:use_tcp]
701
+ end
702
+ alias_method :usevc, :use_tcp?
703
+ alias_method :use_tcp, :use_tcp?
704
+
705
+ # If +use_tcp+ is true, the resolver will perform all queries
706
+ # using TCP virtual circuits instead of UDP datagrams, which
707
+ # is the default for the DNS protocol.
708
+ #
709
+ # res.use_tcp = true
710
+ # res.query "host.example.com"
711
+ # #=> Sending TCP segments...
712
+ #
713
+ # Default is false.
714
+ #
715
+ def use_tcp=(bool)
716
+ case bool
717
+ when TrueClass,FalseClass
718
+ @config[:use_tcp] = bool
719
+ @logger.info("Use tcp flag changed to #{bool}")
720
+ else
721
+ raise ResolverArgumentError, "Argument must be boolean"
722
+ end
723
+ end
724
+ alias usevc= use_tcp=
725
+
726
+ def ignore_trucated?
727
+ @config[:ignore_trucated]
728
+ end
729
+ alias_method :ignore_trucated, :ignore_trucated?
730
+
731
+ def ignore_truncated=(bool)
732
+ case bool
733
+ when TrueClass,FalseClass
734
+ @config[:ignore_truncated] = bool
735
+ @logger.info("Ignore truncated flag changed to #{bool}")
736
+ else
737
+ raise ResolverArgumentError, "Argument must be boolean"
738
+ end
739
+ end
740
+
741
+ # Return an object representing the value of the stored TCP
742
+ # timeout the resolver will use in is queries. This object
743
+ # is an instance of the class +TcpTimeout+, and two methods
744
+ # are available for printing informations: TcpTimeout#to_s
745
+ # and TcpTimeout#pretty_to_s.
746
+ #
747
+ # Here's some example:
748
+ #
749
+ # puts "Timeout of #{res.tcp_timeout} seconds" # implicit to_s
750
+ # #=> Timeout of 150 seconds
751
+ #
752
+ # puts "You set a timeout of " + res.tcp_timeout.pretty_to_s
753
+ # #=> You set a timeout of 2 minutes and 30 seconds
754
+ #
755
+ # If the timeout is infinite, a string "infinite" will
756
+ # be returned.
757
+ #
758
+ def tcp_timeout
759
+ @config[:tcp_timeout].to_s
760
+ end
761
+
762
+ # Set the value of TCP timeout for resolver queries that
763
+ # will be performed using TCP. A value of 0 means that
764
+ # the timeout will be infinite.
765
+ # The value is stored internally as a +TcpTimeout+ object, see
766
+ # the description for Resolver#tcp_timeout
767
+ #
768
+ # Default is 120 seconds
769
+ def tcp_timeout=(secs)
770
+ @config[:tcp_timeout] = TcpTimeout.new(secs)
771
+ @logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds")
772
+ end
773
+
774
+ # Return an object representing the value of the stored UDP
775
+ # timeout the resolver will use in is queries. This object
776
+ # is an instance of the class +UdpTimeout+, and two methods
777
+ # are available for printing informations: UdpTimeout#to_s
778
+ # and UdpTimeout#pretty_to_s.
779
+ #
780
+ # Here's some example:
781
+ #
782
+ # puts "Timeout of #{res.udp_timeout} seconds" # implicit to_s
783
+ # #=> Timeout of 150 seconds
784
+ #
785
+ # puts "You set a timeout of " + res.udp_timeout.pretty_to_s
786
+ # #=> You set a timeout of 2 minutes and 30 seconds
787
+ #
788
+ # If the timeout is zero, a string "not defined" will
789
+ # be returned.
790
+ #
791
+ def udp_timeout
792
+ @config[:udp_timeout].to_s
793
+ end
794
+
795
+ # Set the value of UDP timeout for resolver queries that
796
+ # will be performed using UDP. A value of 0 means that
797
+ # the timeout will not be used, and the resolver will use
798
+ # only +retry_number+ and +retry_interval+ parameters.
799
+ # That is the default.
800
+ #
801
+ # The value is stored internally as a +UdpTimeout+ object, see
802
+ # the description for Resolver#udp_timeout
803
+ #
804
+ def udp_timeout=(secs)
805
+ @config[:udp_timeout] = UdpTimeout.new(secs)
806
+ @logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds")
807
+ end
808
+
809
+ # Set a new log file for the logger facility of the resolver
810
+ # class. Could be a file descriptor too:
811
+ #
812
+ # res.log_file = $stderr
813
+ #
814
+ # Note that a new logging facility will be create, destroing
815
+ # the old one, which will then be impossibile to recover.
816
+ #
817
+ def log_file=(log)
818
+ @logger.close
819
+ @config[:log_file] = log
820
+ @logger = Logger.new(@config[:log_file])
821
+ @logger.level = $DEBUG ? Logger::Debug : Logger::WARN
822
+ end
823
+
824
+ # This one permits to have a personal logger facility to handle
825
+ # resolver messages, instead of new built-in one, which is set up
826
+ # for a +$stdout+ (or +$stderr+) use.
827
+ #
828
+ # If you want your own logging facility you can create a new instance
829
+ # of the +Logger+ class:
830
+ #
831
+ # log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024)
832
+ # log.level = Logger::DEBUG
833
+ # log.progname = "ruby_resolver"
834
+ #
835
+ # and then pass it to the resolver:
836
+ #
837
+ # res.logger = log
838
+ #
839
+ # Note that this will destroy the precedent logger.
840
+ #
841
+ # This is the only mode user will have to control their logger, such
842
+ # as setting the level threshold, because no reader is set for this
843
+ # instance variable.
844
+ #
845
+ def logger=(logger)
846
+ if logger.kind_of? Logger
847
+ @logger.close
848
+ @logger = logger
849
+ else
850
+ raise ResolverArgumentError, "Argument must be an instance of Logger class"
851
+ end
852
+ end
853
+
854
+ # Performs a DNS query for the given name, applying the searchlist if
855
+ # appropriate. The search algorithm is as follows:
856
+ #
857
+ # 1. If the name contains at least one dot, try it as is.
858
+ # 2. If the name doesn't end in a dot then append each item in the search
859
+ # list to the name. This is only done if +dns_search+ is true.
860
+ # 3. If the name doesn't contain any dots, try it as is.
861
+ #
862
+ # The record type and class can be omitted; they default to +A+ and +IN+.
863
+ #
864
+ # packet = res.search('mailhost')
865
+ # packet = res.search('mailhost.example.com')
866
+ # packet = res.search('example.com', Net::DNS::MX)
867
+ # packet = res.search('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
868
+ #
869
+ # If the name is an IP address (Ipv4 or IPv6), in the form of a string
870
+ # or a +IPAddr+ object, then an appropriate PTR query will be performed:
871
+ #
872
+ # ip = IPAddr.new("172.16.100.2")
873
+ # packet = res.search(ip)
874
+ # packet = res.search("192.168.10.254")
875
+ #
876
+ # Returns a Net::DNS::Packet object. If you need to examine the response packet
877
+ # whether it contains any answers or not, use the send() method instead.
878
+ #
879
+ def search(name,type=Net::DNS::A,cls=Net::DNS::IN)
880
+
881
+ # If the name contains at least one dot then try it as is first.
882
+ if name.include? "."
883
+ @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
884
+ ans = query(name,type,cls)
885
+ return ans if ans.header.anCount > 0
886
+ end
887
+
888
+ # If the name doesn't end in a dot then apply the search list.
889
+ if name !~ /\.$/ and @config[:dns_search]
890
+ @config[:searchlist].each do |domain|
891
+ newname = name + "." + domain
892
+ @logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
893
+ ans = query(newname,type,cls)
894
+ return ans if ans.header.anCount > 0
895
+ end
896
+ end
897
+
898
+ # Finally, if the name has no dots then try it as is.
899
+ @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
900
+ query(name+".",type,cls)
901
+
902
+ end
903
+
904
+ # Performs a DNS query for the given name; the search list
905
+ # is not applied. If the name doesn't contain any dots and
906
+ # +defname+ is true then the default domain will be appended.
907
+ #
908
+ # The record type and class can be omitted; they default to +A+
909
+ # and +IN+. If the name looks like an IP address (IPv4 or IPv6),
910
+ # then an appropriate PTR query will be performed.
911
+ #
912
+ # packet = res.query('mailhost')
913
+ # packet = res.query('mailhost.example.com')
914
+ # packet = res.query('example.com', Net::DNS::MX)
915
+ # packet = res.query('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
916
+ #
917
+ # If the name is an IP address (Ipv4 or IPv6), in the form of a string
918
+ # or a +IPAddr+ object, then an appropriate PTR query will be performed:
919
+ #
920
+ # ip = IPAddr.new("172.16.100.2")
921
+ # packet = res.query(ip)
922
+ # packet = res.query("192.168.10.254")
923
+ #
924
+ # Returns a Net::DNS::Packet object. If you need to examine the response
925
+ # packet whether it contains any answers or not, use the Resolver#send
926
+ # method instead.
927
+ #
928
+ def query(name,type=Net::DNS::A,cls=Net::DNS::IN)
929
+
930
+ # If the name doesn't contain any dots then append the default domain.
931
+ if name !~ /\./ and name !~ /:/ and @config[:defnames]
932
+ name += "." + @config[:domain]
933
+ end
934
+
935
+ @logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
936
+
937
+ send(name,type,cls)
938
+
939
+ end
940
+
941
+
942
+ # Performs a DNS query for the given name. Neither the
943
+ # searchlist nor the default domain will be appended.
944
+ #
945
+ # The argument list can be either a Net::DNS::Packet object
946
+ # or a name string plus optional type and class, which if
947
+ # omitted default to +A+ and +IN+.
948
+ #
949
+ # Returns a Net::DNS::Packet object.
950
+ #
951
+ # # Sending a +Packet+ object
952
+ # send_packet = Net::DNS::Packet.new("host.example.com",Net::DNS::NS,Net::DNS::HS)
953
+ # packet = res.send(send_packet)
954
+ #
955
+ # # Performing a query
956
+ # packet = res.send("host.example.com")
957
+ # packet = res.send("host.example.com",Net::DNS::NS)
958
+ # packet = res.send("host.example.com",Net::DNS::NS,Net::DNS::HS)
959
+ #
960
+ # If the name is an IP address (Ipv4 or IPv6), in the form of a string
961
+ # or a IPAddr object, then an appropriate PTR query will be performed:
962
+ #
963
+ # ip = IPAddr.new("172.16.100.2")
964
+ # packet = res.send(ip)
965
+ # packet = res.send("192.168.10.254")
966
+ #
967
+ # Use +packet.header.ancount+ or +packet.answer+ to find out if there
968
+ # were any records in the answer section.
969
+ #
970
+ def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)
971
+ if @config[:nameservers].size == 0
972
+ raise ResolverError, "No nameservers specified!"
973
+ end
974
+
975
+ if argument.kind_of? Net::DNS::Packet
976
+ packet = argument
977
+ else
978
+ packet = make_query_packet(argument,type,cls)
979
+ end
980
+
981
+ # Store packet_data for performance improvements,
982
+ # so methods don't keep on calling Packet#data
983
+ packet_data = packet.data
984
+ packet_size = packet_data.size
985
+
986
+ # Choose whether use TCP, UDP or RAW
987
+ if packet_size > @config[:packet_size] # Must use TCP, either plain or raw
988
+ if @raw # Use raw sockets?
989
+ @logger.info "Sending #{packet_size} bytes using TCP over RAW socket"
990
+ ans = send_raw_tcp(packet,packet_data)
991
+ else
992
+ @logger.info "Sending #{packet_size} bytes using TCP"
993
+ ans = send_tcp(packet,packet_data)
994
+ end
995
+ else # Packet size is inside the boundaries
996
+ if @raw # Use raw sockets?
997
+ @logger.info "Sending #{packet_size} bytes using UDP over RAW socket"
998
+ ans = send_raw_udp(packet,packet_data)
999
+ elsif use_tcp? # User requested TCP
1000
+ @logger.info "Sending #{packet_size} bytes using TCP"
1001
+ ans = send_tcp(packet,packet_data)
1002
+ else # Finally use UDP
1003
+ @logger.info "Sending #{packet_size} bytes using UDP"
1004
+ ans = send_udp(packet,packet_data)
1005
+ end
1006
+ end
1007
+
1008
+ if ans.header.truncated? and not ignore_truncated?
1009
+ @logger.warn "Packet truncated, retrying using TCP"
1010
+ ans = send_tcp(packet,packet_data)
1011
+ end
1012
+
1013
+ ans
1014
+ end
1015
+
1016
+
1017
+ private
1018
+
1019
+ # Parse a configuration file specified as the argument.
1020
+ #
1021
+ def parse_config_file
1022
+ IO.foreach(@config[:config_file]) do |line|
1023
+ line.gsub!(/\s*[;#].*/,"")
1024
+ next unless line =~ /\S/
1025
+ case line
1026
+ when /^\s*domain\s+(\S+)/
1027
+ self.domain = $1
1028
+ when /^\s*search\s+(.*)/
1029
+ self.searchlist = $1.split(" ")
1030
+ when /^\s*nameserver\s+(.*)/
1031
+ self.nameservers = $1.split(" ")
1032
+ end
1033
+ end
1034
+ end
1035
+
1036
+ # Parse environment variables
1037
+ def parse_environment_variables
1038
+ if ENV['RES_NAMESERVERS']
1039
+ self.nameservers = ENV['RES_NAMESERVERS'].split(" ")
1040
+ end
1041
+ if ENV['RES_SEARCHLIST']
1042
+ self.searchlist = ENV['RES_SEARCHLIST'].split(" ")
1043
+ end
1044
+ if ENV['LOCALDOMAIN']
1045
+ self.domain = ENV['LOCALDOMAIN']
1046
+ end
1047
+ if ENV['RES_OPTIONS']
1048
+ ENV['RES_OPTIONS'].split(" ").each do |opt|
1049
+ name,val = opt.split(":")
1050
+ begin
1051
+ eval("self.#{name} = #{val}")
1052
+ rescue NoMethodError
1053
+ raise ResolverArgumentError, "Invalid ENV option #{name}"
1054
+ end
1055
+ end
1056
+ end
1057
+ end
1058
+
1059
+ def nameservers_from_name(arg)
1060
+ arr = []
1061
+ arg.split(" ").each do |name|
1062
+ Resolver.new.search(name).each_address do |ip|
1063
+ arr << ip
1064
+ end
1065
+ end
1066
+ @config[:nameservers] << arr
1067
+ end
1068
+
1069
+ def make_query_packet(string,type,cls)
1070
+ case string
1071
+ when IPAddr
1072
+ name = string.reverse
1073
+ type = Net::DNS::PTR
1074
+ @logger.warn "PTR query required for address #{string}, changing type to PTR"
1075
+ when /\d/ # Contains a number, try to see if it's an IP or IPv6 address
1076
+ begin
1077
+ name = IPAddr.new(string).reverse
1078
+ type = Net::DNS::PTR
1079
+ rescue ArgumentError
1080
+ name = string if valid? string
1081
+ end
1082
+ else
1083
+ name = string if valid? string
1084
+ end
1085
+
1086
+ # Create the packet
1087
+ packet = Net::DNS::Packet.new(name,type,cls)
1088
+
1089
+ if packet.query?
1090
+ packet.header.recursive = @config[:recursive] ? 1 : 0
1091
+ end
1092
+
1093
+ # DNSSEC and TSIG stuff to be inserted here
1094
+
1095
+ packet
1096
+
1097
+ end
1098
+
1099
+ def send_tcp(packet,packet_data)
1100
+ # Generate new TCP socket or use persistent one
1101
+ if @config[:persistent_tcp] and @@tcp_socket
1102
+ socket = @@tcp_socket
1103
+ @logger.info "Using persistent socket #{socket.inspect}"
1104
+ else
1105
+ socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
1106
+ socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s))
1107
+ @@tcp_socket = socket if @config[:persistent_tcp]
1108
+ end
1109
+
1110
+ ans = ""
1111
+ response = ""
1112
+ length = [packet_data.size].pack("n")
1113
+ @config[:nameservers].each do |ns|
1114
+ @config[:tcp_timeout].timeout do
1115
+ sockaddr = Socket.pack_sockaddr_in(@config[:port],ns.to_s)
1116
+ socket.connect(sockaddr)
1117
+ socket.write(length+packet_data)
1118
+ ans = socket.recv(Net::DNS::INT16SZ)
1119
+ len = ans.unpack("n")[0]
1120
+ p len
1121
+ next if len == 0
1122
+ ans = socket.recv(len)
1123
+ next if ans.size == 0
1124
+ p ans.size
1125
+ ans = [ans,["",@config[:port],ns.to_s,ns.to_s]]
1126
+ @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
1127
+ end
1128
+ response = Net::DNS::Packet.parse(ans[0],ans[1])
1129
+ end
1130
+ return response
1131
+ end
1132
+
1133
+
1134
+ def send_udp(packet,packet_data)
1135
+ # Generate new UDP socket or use persistent one
1136
+ if @config[:persistent_udp] and @@udp_socket
1137
+ socket = @@udp_socket
1138
+ @logger.info "Using persistent socket #{socket.inspect}"
1139
+ else
1140
+ socket = UDPSocket.new
1141
+ socket.bind(@config[:source_address].to_s,@config[:source_port])
1142
+ @@udp_socket = socket if @config[:persistent_udp]
1143
+ end
1144
+
1145
+ ans = ""
1146
+ response = ""
1147
+ @config[:nameservers].each do |ns|
1148
+ @config[:udp_timeout].timeout do
1149
+ socket.send(packet_data,0,ns.to_s,@config[:port])
1150
+ ans = socket.recvfrom(@config[:packet_size])
1151
+ @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
1152
+ end
1153
+ response = Net::DNS::Packet.parse(ans[0],ans[1])
1154
+ end
1155
+ return response
1156
+ end
1157
+
1158
+ def valid?(name)
1159
+ if name =~ /[^-\w\.]/
1160
+ raise ResolverArgumentError, "Invalid domain name #{name}"
1161
+ else
1162
+ true
1163
+ end
1164
+ end
1165
+
1166
+ end # class Resolver
1167
+ end # module DNS
1168
+ end # module Net
1169
+
1170
+ class ResolverArgumentError < ArgumentError # :nodoc:
1171
+ end
1172
+
1173
+ class Hash # :nodoc:
1174
+ # Returns an hash with all the
1175
+ # keys turned into downcase
1176
+ #
1177
+ # hsh = {"Test" => 1, "FooBar" => 2}
1178
+ # hsh.key_downcase!
1179
+ # #=> {"test"=>1,"foobar"=>2}
1180
+ #
1181
+ def key_downcase!
1182
+ hsh = Hash.new
1183
+ self.each do |key,val|
1184
+ hsh[key.downcase] = val
1185
+ end
1186
+ self.replace(hsh)
1187
+ end
1188
+ end
1189
+
1190
+
1191
+
1192
+
1193
+
1194
+
1195
+
1196
+
1197
+
1198
+
1199
+