net-dns 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+