net-dns2 0.8.1

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