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,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