net-dns2 0.8.1

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