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,154 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+
4
+ class RawSocket # :nodoc:
5
+
6
+ @@id_arr = []
7
+
8
+ def initialize(src_addr,dest_addr)
9
+
10
+ # Define socket
11
+ begin
12
+ @socket = Socket.new PF_INET, SOCK_RAW, IPPROTO_RAW
13
+ rescue SystemCallError => e
14
+ raise SystemCallError, "You must be root to use raw sockets! #{e}"
15
+ end
16
+
17
+ @socket.setsockopt IPPROTO_IP, IP_HDRINCL, 1
18
+
19
+ # Checks addresses
20
+ @src_addr = check_addr src_addr
21
+ @dest_addr = check_addr dest_addr
22
+
23
+ # Source and destination port are zero
24
+ @src_port = 0
25
+ @dest_port = 0
26
+
27
+ # Set correct protocol version in the header
28
+ @version = @dest_addr.ipv4? ? "0100" : "0110"
29
+
30
+ # Total lenght: must be overridden by subclasses
31
+ @tot_lenght = 20
32
+
33
+ # Protocol: must be overridden by subclasses
34
+ @protocol = 1 # ICMP by default
35
+
36
+ # Generate a new id
37
+ # @id = genID
38
+ @id = 1234
39
+
40
+ # Generate peer sockaddr
41
+ @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
42
+ end
43
+
44
+ def send(payload = '')
45
+ packet = make_ip_header([[ @version+'0101', 'B8' ], # version, hlen
46
+ [ 0, 'C' ], # tos
47
+ [ @tot_lenght + payload.size, 'n' ], # total len
48
+ [ @id, 'n' ], # id
49
+ [ 0, 'n' ], # flags, offset
50
+ [ 64, 'C' ], # ttl
51
+ [ @protocol, 'C' ], # protocol
52
+ [ 0, 'n' ], # checksum
53
+ [ @src_addr.to_i, 'N' ], # source
54
+ [ @dest_addr.to_i, 'N' ], # destination
55
+ ])
56
+ packet << make_transport_header(payload.size)
57
+ packet << [payload].pack("a*")
58
+ @socket.send(packet,0,@to)
59
+ end
60
+
61
+ private
62
+
63
+ def check_addr addr
64
+ case addr
65
+ when String
66
+ IPAddr.new(addr)
67
+ when IPAddr
68
+ addr
69
+ else
70
+ raise ArgumentError, "Wrong address format: #{addr}"
71
+ end
72
+ end
73
+
74
+ def check_port port
75
+ if (1..65535).include? port and port.kind_of? Integer
76
+ port
77
+ else
78
+ raise ArgumentError, "Port #{port} not valid"
79
+ end
80
+ end
81
+
82
+ def genID
83
+ while (@@id_arr.include?(q = rand(65535)))
84
+ end
85
+ @@id_arr.push(q)
86
+ q
87
+ end
88
+
89
+ def ipchecksum(data)
90
+ checksum = data.unpack("n*").inject(0) { |s, x| s + x }
91
+ ((checksum >> 16) + (checksum & 0xffff)) ^ 0xffff
92
+ end
93
+
94
+ def make_ip_header(parts)
95
+ template = ''
96
+ data = []
97
+ parts.each do |part|
98
+ data += part[0..-2]
99
+ template << part[-1]
100
+ end
101
+ data_str = data.pack(template)
102
+ checksum = ipchecksum(data_str)
103
+ data[-3] = checksum
104
+ data.pack(template)
105
+ end
106
+
107
+ def make_transport_header
108
+ ""
109
+ end
110
+
111
+ end
112
+
113
+ class UdpRawSocket < RawSocket # :nodoc:
114
+
115
+ def initialize(src_addr,src_port,dest_addr,dest_port)
116
+
117
+ super(src_addr,dest_addr)
118
+
119
+ # Check ports
120
+ @src_port = check_port src_port
121
+ @dest_port = check_port dest_port
122
+
123
+ # Total lenght: must be overridden by subclasses
124
+ @tot_lenght = 20 + 8 # 8 bytes => UDP Header
125
+
126
+ # Protocol: must be overridden by subclasses
127
+ @protocol = 17 # UDP protocol
128
+
129
+ @to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
130
+ end
131
+
132
+ private
133
+
134
+ def make_udp_header(parts)
135
+ template = ''
136
+ data = []
137
+ parts.each do |part|
138
+ data += part[0..-2]
139
+ template << part[-1]
140
+ end
141
+ data.pack(template)
142
+ end
143
+
144
+ def make_transport_header(pay_size)
145
+ make_udp_header([
146
+ [ @src_port, 'n'], # source port
147
+ [ @dest_port, 'n' ], # destination port
148
+ [ 8 + pay_size, 'n' ], # len
149
+ [ 0, 'n' ] # checksum (mandatory)
150
+ ])
151
+ end
152
+
153
+ end
154
+
@@ -0,0 +1,75 @@
1
+ require 'timeout'
2
+
3
+ module Net # :nodoc:
4
+ module DNS
5
+ class Resolver
6
+
7
+ class DnsTimeout
8
+
9
+ attr_reader :seconds
10
+
11
+
12
+ def initialize(seconds)
13
+ if seconds.is_a?(Numeric) && seconds >= 0
14
+ @seconds = seconds
15
+ else
16
+ raise ArgumentError, "Invalid value for tcp timeout"
17
+ end
18
+ end
19
+
20
+ # Returns a string representation of the timeout corresponding
21
+ # to the number of <tt>@seconds</tt>.
22
+ def to_s
23
+ @seconds == 0 ? @output.to_s : @seconds.to_s
24
+ end
25
+
26
+ def pretty_to_s
27
+ transform(@seconds)
28
+ end
29
+
30
+ # Executes the method's block. If the block execution terminates before +sec+
31
+ # seconds has passed, it returns true. If not, it terminates the execution
32
+ # and raises Timeout::Error.
33
+ # If @seconds is 0 or nil, no timeout is set.
34
+ def timeout(&block)
35
+ raise LocalJumpError, "no block given" unless block_given?
36
+ Timeout.timeout(@seconds, &block)
37
+ end
38
+
39
+
40
+ private
41
+
42
+ def transform(secs)
43
+ case secs
44
+ when 0
45
+ to_s
46
+ when 1..59
47
+ "#{secs} seconds"
48
+ when 60..3559
49
+ "#{secs / 60} minutes and #{secs % 60} seconds"
50
+ else
51
+ hours = secs / 3600
52
+ secs -= (hours * 3600)
53
+ "#{hours} hours, #{secs / 60} minutes and #{secs % 60} seconds"
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ class TcpTimeout < DnsTimeout
60
+ def initialize(seconds)
61
+ @output = "infinite"
62
+ super
63
+ end
64
+ end
65
+
66
+ class UdpTimeout < DnsTimeout
67
+ def initialize(seconds)
68
+ @output = "not defined"
69
+ super
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,366 @@
1
+ require 'ipaddr'
2
+ require 'net/dns/names'
3
+ require 'net/dns/rr/types'
4
+ require 'net/dns/rr/classes'
5
+
6
+ %w(a aaaa cname hinfo mr mx ns ptr soa srv txt).each do |file|
7
+ require "net/dns/rr/#{file}"
8
+ end
9
+
10
+ module Net
11
+ module DNS
12
+
13
+ #
14
+ # = Net::DNS::RR - DNS Resource Record class
15
+ #
16
+ # The Net::DNS::RR is the base class for DNS Resource
17
+ # Record (RR) objects. A RR is a pack of data that represents
18
+ # resources for a DNS zone. The form in which this data is
19
+ # shows can be drawed as follow:
20
+ #
21
+ # "name ttl class type data"
22
+ #
23
+ # The +name+ is the name of the resource, like an canonical
24
+ # name for an +A+ record (internet ip address). The +ttl+ is the
25
+ # time to live, expressed in seconds. +type+ and +class+ are
26
+ # respectively the type of resource (+A+ for ip addresses, +NS+
27
+ # for nameservers, and so on) and the class, which is almost
28
+ # always +IN+, the Internet class. At the end, +data+ is the
29
+ # value associated to the name for that particular type of
30
+ # resource record. An example:
31
+ #
32
+ # # A record for IP address
33
+ # "www.example.com 86400 IN A 172.16.100.1"
34
+ #
35
+ # # NS record for name server
36
+ # "www.example.com 86400 IN NS ns.example.com"
37
+ #
38
+ # A new RR object can be created in 2 ways: passing a string
39
+ # such the ones above, or specifying each field as the pair
40
+ # of an hash. See the Net::DNS::RR.new method for details.
41
+ #
42
+ class RR
43
+ include Names
44
+
45
+
46
+ # Base error class.
47
+ class Error < StandardError
48
+ end
49
+
50
+ # Error in parsing binary data, maybe from a malformed packet.
51
+ class DataError < Error
52
+ end
53
+
54
+
55
+ # Regexp matching an RR string
56
+ RR_REGEXP = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s+(" +
57
+ Net::DNS::RR::Classes.regexp +
58
+ "|CLASS\\d+)?\\s*(" +
59
+ Net::DNS::RR::Types.regexp +
60
+ "|TYPE\\d+)?\\s*(.*)$", Regexp::IGNORECASE)
61
+
62
+ # Dimension of the sum of class, type, TTL and rdlength fields in a
63
+ # RR portion of the packet, in bytes
64
+ RRFIXEDSZ = 10
65
+
66
+
67
+ # Create a new instance of Net::DNS::RR class, or an instance of
68
+ # any of the subclass of the appropriate type.
69
+ #
70
+ # Argument can be a string or an hash. With a sting, we can pass
71
+ # a RR resource record in the canonical format:
72
+ #
73
+ # a = Net::DNS::RR.new("foo.example.com. 86400 A 10.1.2.3")
74
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
75
+ # cname = Net::DNS::RR.new("www.example.com 300 IN CNAME www1.example.com")
76
+ # txt = Net::DNS::RR.new('baz.example.com 3600 HS TXT "text record"')
77
+ #
78
+ # Incidentally, +a+, +mx+, +cname+ and +txt+ objects will be instances of
79
+ # respectively Net::DNS::RR::A, Net::DNS::RR::MX, Net::DNS::RR::CNAME and
80
+ # Net::DNS::RR::TXT classes.
81
+ #
82
+ # The name and RR data are required; all other informations are optional.
83
+ # If omitted, the +TTL+ defaults to 10800, +type+ default to +A+ and the RR class
84
+ # defaults to +IN+. Omitting the optional fields is useful for creating the
85
+ # empty RDATA sections required for certain dynamic update operations.
86
+ # All names must be fully qualified. The trailing dot (.) is optional.
87
+ #
88
+ # The preferred method is however passing an hash with keys and values:
89
+ #
90
+ # rr = Net::DNS::RR.new(
91
+ # :name => "foo.example.com",
92
+ # :ttl => 86400,
93
+ # :cls => "IN",
94
+ # :type => "A",
95
+ # :address => "10.1.2.3"
96
+ # )
97
+ #
98
+ # rr = Net::DNS::RR.new(
99
+ # :name => "foo.example.com",
100
+ # :rdata => "10.1.2.3"
101
+ # )
102
+ #
103
+ # Name and data are required; all the others fields are optionals like
104
+ # we've seen before. The data field can be specified either with the
105
+ # right name of the resource (+:address+ in the example above) or with
106
+ # the generic key +:rdata+. Consult documentation to find the exact name
107
+ # for the resource in each subclass.
108
+ #
109
+ def initialize(arg)
110
+ instance = case arg
111
+ when String
112
+ new_from_string(arg)
113
+ when Hash
114
+ new_from_hash(arg)
115
+ else
116
+ raise ArgumentError, "Invalid argument, must be a RR string or an hash of values"
117
+ end
118
+
119
+ if @type.to_s == "ANY"
120
+ @cls = Net::DNS::RR::Classes.new("IN")
121
+ end
122
+
123
+ build_pack
124
+ set_type
125
+
126
+ instance
127
+ end
128
+
129
+ # Return a new RR object of the correct type (like Net::DNS::RR::A
130
+ # if the type is A) from a binary string, usually obtained from
131
+ # network stream.
132
+ #
133
+ # This method is used when parsing a binary packet by the Packet
134
+ # class.
135
+ #
136
+ def RR.parse(data)
137
+ o = allocate
138
+ obj, offset = o.send(:new_from_binary, data, 0)
139
+ obj
140
+ end
141
+
142
+ # Same as RR.parse, but takes an entire packet binary data to
143
+ # perform name expansion. Default when analizing a packet
144
+ # just received from a network stream.
145
+ #
146
+ # Return an instance of appropriate class and the offset
147
+ # pointing at the end of the data parsed.
148
+ #
149
+ def RR.parse_packet(data, offset)
150
+ o = allocate
151
+ o.send(:new_from_binary, data, offset)
152
+ end
153
+
154
+ def name
155
+ @name
156
+ end
157
+
158
+ def ttl
159
+ @ttl
160
+ end
161
+
162
+ # Type accessor
163
+ def type
164
+ @type.to_s
165
+ end
166
+
167
+ # Class accessor
168
+ def cls
169
+ @cls.to_s
170
+ end
171
+
172
+
173
+ def value
174
+ get_inspect
175
+ end
176
+
177
+ # Data belonging to that appropriate class,
178
+ # not to be used (use real accessors instead)
179
+ def rdata
180
+ @rdata
181
+ end
182
+
183
+ # Return the RR object in binary data format, suitable
184
+ # for using in network streams.
185
+ #
186
+ # raw_data = rr.data
187
+ # puts "RR is #{raw_data.size} bytes long"
188
+ #
189
+ def data
190
+ str = pack_name(@name)
191
+ str + [@type.to_i, @cls.to_i, ttl, @rdlength].pack("n2 N n") + get_data
192
+ end
193
+
194
+ # Return the RR object in binary data format, suitable
195
+ # for using in network streams, with names compressed.
196
+ # Must pass as arguments the offset inside the packet
197
+ # and an hash of compressed names.
198
+ #
199
+ # This method is to be used in other classes and is
200
+ # not intended for user space programs.
201
+ #
202
+ # TO FIX in one of the future releases
203
+ #
204
+ def comp_data(offset,compnames)
205
+ str, offset, names = dn_comp(@name, offset, compnames)
206
+ str += [@type.to_i, @cls.to_i, ttl, @rdlength].pack("n2 N n")
207
+ offset += Net::DNS::RRFIXEDSZ
208
+ [str, offset, names]
209
+ end
210
+
211
+
212
+ # Returns a human readable representation of this record.
213
+ # The value is always a String.
214
+ #
215
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
216
+ # #=> example.com. 7200 IN MX 10 mailhost.example.com.
217
+ #
218
+ def inspect
219
+ to_s
220
+ end
221
+
222
+ # Returns a String representation of this record.
223
+ #
224
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
225
+ # mx.to_s
226
+ # #=> "example.com. 7200 IN MX 10 mailhost.example.com."
227
+ #
228
+ def to_s
229
+ items = to_a.map { |e| e.to_s }
230
+ if @name.size < 24
231
+ items.pack("A24 A8 A8 A8 A*")
232
+ else
233
+ items.join(" ")
234
+ end.to_s
235
+ end
236
+
237
+ # Returns an Array with all the attributes for this record.
238
+ #
239
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
240
+ # mx.to_a
241
+ # #=> ["example.com.", 7200, "IN", "MX", "10 mailhost.example.com."]
242
+ #
243
+ def to_a
244
+ [name, ttl, cls.to_s, type.to_s, value]
245
+ end
246
+
247
+
248
+ private
249
+
250
+ def new_from_string(rrstring)
251
+ unless rrstring =~ RR_REGEXP
252
+ raise ArgumentError,
253
+ "Format error for RR string (maybe CLASS and TYPE not valid?)"
254
+ end
255
+
256
+ # Name of RR - mandatory
257
+ begin
258
+ @name = $1.downcase
259
+ rescue NoMethodError
260
+ raise ArgumentError, "Missing name field in RR string #{rrstring}"
261
+ end
262
+
263
+ # Time to live for RR, default 3 hours
264
+ @ttl = $2 ? $2.to_i : 10800
265
+
266
+ # RR class, default to IN
267
+ @cls = Net::DNS::RR::Classes.new $3
268
+
269
+ # RR type, default to A
270
+ @type = Net::DNS::RR::Types.new $4
271
+
272
+ # All the rest is data
273
+ @rdata = $5 ? $5.strip : ""
274
+
275
+ if self.class == Net::DNS::RR
276
+ Net::DNS::RR.const_get(@type.to_s).new(rrstring)
277
+ else
278
+ subclass_new_from_string(@rdata)
279
+ self.class
280
+ end
281
+ end
282
+
283
+ def new_from_hash(args)
284
+ # Name field is mandatory
285
+ unless args.has_key? :name
286
+ raise ArgumentError, ":name field is mandatory"
287
+ end
288
+
289
+ @name = args[:name].downcase
290
+ @ttl = args[:ttl] ? args[:ttl].to_i : 10800 # Default 3 hours
291
+ @type = Net::DNS::RR::Types.new args[:type]
292
+ @cls = Net::DNS::RR::Classes.new args[:cls]
293
+
294
+ @rdata = args[:rdata] ? args[:rdata].strip : ""
295
+ @rdlength = args[:rdlength] || @rdata.size
296
+
297
+ if self.class == Net::DNS::RR
298
+ Net::DNS::RR.const_get(@type.to_s).new(args)
299
+ else
300
+ hash = args - [:name, :ttl, :type, :cls]
301
+ if hash.has_key? :rdata
302
+ subclass_new_from_string(hash[:rdata])
303
+ else
304
+ subclass_new_from_hash(hash)
305
+ end
306
+ self.class
307
+ end
308
+ end
309
+
310
+ def new_from_binary(data,offset)
311
+ if self.class == Net::DNS::RR
312
+ temp = dn_expand(data,offset)[1]
313
+ type = Net::DNS::RR::Types.new data.unpack("@#{temp} n")[0]
314
+ (eval "Net::DNS::RR::#{type}").parse_packet(data,offset)
315
+ else
316
+ @name,offset = dn_expand(data,offset)
317
+ rrtype,cls,@ttl,@rdlength = data.unpack("@#{offset} n2 N n")
318
+ @type = Net::DNS::RR::Types.new rrtype
319
+ @cls = Net::DNS::RR::Classes.new cls
320
+ offset += RRFIXEDSZ
321
+ offset = subclass_new_from_binary(data,offset)
322
+ build_pack
323
+ set_type
324
+ [self, offset]
325
+ end
326
+ end
327
+
328
+ # Methods to be overridden by subclasses
329
+ def subclass_new_from_array(arr)
330
+ end
331
+ def subclass_new_from_string(str)
332
+ end
333
+ def subclass_new_from_hash(hash)
334
+ end
335
+ def subclass_new_from_binary(data, offset)
336
+ end
337
+ def build_pack
338
+ end
339
+ def get_inspect
340
+ @rdata
341
+ end
342
+ def get_data
343
+ @rdata
344
+ end
345
+
346
+ def set_type
347
+ # TODO: Here we should probably
348
+ # raise NotImplementedError
349
+ # if we want the method to be implemented in any subclass.
350
+ end
351
+
352
+
353
+ def self.new(*args)
354
+ o = allocate
355
+ obj = o.send(:initialize,*args)
356
+ if self == Net::DNS::RR
357
+ obj
358
+ else
359
+ o
360
+ end
361
+ end
362
+
363
+ end
364
+
365
+ end
366
+ end