net-dns 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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,73 @@
1
+ require 'timeout'
2
+
3
+ module SecondsHandle #:nodoc: all
4
+ def transform(secs)
5
+ case secs
6
+ when 0
7
+ to_s
8
+ when 1..59
9
+ "#{secs} seconds"
10
+ when 60..3559
11
+ "#{secs/60} minutes and #{secs%60} seconds"
12
+ else
13
+ hours = secs/3600
14
+ secs -= (hours*3600)
15
+ "#{hours} hours, #{secs/60} minutes and #{secs%60} seconds"
16
+ end
17
+ end
18
+ end
19
+
20
+ class DnsTimeout # :nodoc: all
21
+
22
+ include SecondsHandle
23
+
24
+ def initialize(seconds)
25
+ if seconds.is_a? Numeric and seconds >= 0
26
+ @timeout = seconds
27
+ else
28
+ raise DnsTimeoutArgumentError, "Invalid value for tcp timeout"
29
+ end
30
+ end
31
+
32
+ def to_s
33
+ if @timeout == 0
34
+ @output
35
+ else
36
+ @timeout.to_s
37
+ end
38
+ end
39
+
40
+ def pretty_to_s
41
+ transform(@timeout)
42
+ end
43
+
44
+ def timeout
45
+ unless block_given?
46
+ raise DnsTimeoutArgumentError, "Block required but missing"
47
+ end
48
+ if @timeout == 0
49
+ yield
50
+ else
51
+ return Timeout.timeout(@timeout) do
52
+ yield
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ class TcpTimeout < DnsTimeout # :nodoc: all
59
+ def initialize(seconds)
60
+ @output = "infinite"
61
+ super(seconds)
62
+ end
63
+ end
64
+
65
+ class UdpTimeout < DnsTimeout # :nodoc: all
66
+ def initialize(seconds)
67
+ @output = "not defined"
68
+ super(seconds)
69
+ end
70
+ end
71
+
72
+ class DnsTimeoutArgumentError < ArgumentError # :nodoc: all
73
+ end
data/lib/net/dns/rr.rb ADDED
@@ -0,0 +1,386 @@
1
+ #
2
+ # $Id: RR.rb,v 1.19 2006/07/28 07:33:36 bluemonk Exp $
3
+ #
4
+
5
+ require 'net/dns/names/names'
6
+ require 'net/dns/rr/types'
7
+ require 'net/dns/rr/classes'
8
+
9
+
10
+ files = %w[a ns mx cname txt soa ptr aaaa mr] # hinfo mb mg mr
11
+ files.each do |file|
12
+ require "net/dns/rr/#{file}"
13
+ end
14
+
15
+ module Net # :nodoc:
16
+ module DNS
17
+
18
+ # =Name
19
+ #
20
+ # Net::DNS::RR - DNS Resource Record class
21
+ #
22
+ # =Synopsis
23
+ #
24
+ # require 'net/dns/rr'
25
+ #
26
+ # =Description
27
+ #
28
+ # The Net::DNS::RR is the base class for DNS Resource
29
+ # Record (RR) objects. A RR is a pack of data that represents
30
+ # resources for a DNS zone. The form in which this data is
31
+ # shows can be drawed as follow:
32
+ #
33
+ # "name ttl class type data"
34
+ #
35
+ # The +name+ is the name of the resource, like an canonical
36
+ # name for an +A+ record (internet ip address). The +ttl+ is the
37
+ # time to live, expressed in seconds. +type+ and +class+ are
38
+ # respectively the type of resource (+A+ for ip addresses, +NS+
39
+ # for nameservers, and so on) and the class, which is almost
40
+ # always +IN+, the Internet class. At the end, +data+ is the
41
+ # value associated to the name for that particular type of
42
+ # resource record. An example:
43
+ #
44
+ # # A record for IP address
45
+ # "www.example.com 86400 IN A 172.16.100.1"
46
+ #
47
+ # # NS record for name server
48
+ # "www.example.com 86400 IN NS ns.example.com"
49
+ #
50
+ # A new RR object can be created in 2 ways: passing a string
51
+ # such the ones above, or specifying each field as the pair
52
+ # of an hash. See the Net::DNS::RR.new method for details.
53
+ #
54
+ # =Error classes
55
+ #
56
+ # Some error classes has been defined for the Net::DNS::RR class,
57
+ # which are listed here to keep a light and browsable main documentation.
58
+ # We have:
59
+ #
60
+ # * RRArgumentError: Generic argument error for class Net::DNS::RR
61
+ # * RRDataError: Error in parsing binary data, maybe from a malformed packet
62
+ #
63
+ # =Copyright
64
+ #
65
+ # Copyright (c) 2006 Marco Ceresa
66
+ #
67
+ # All rights reserved. This program is free software; you may redistribute
68
+ # it and/or modify it under the same terms as Ruby itself.
69
+ #
70
+ class RR
71
+ include Net::DNS::Names
72
+
73
+ # Regexp matching an RR string
74
+ RR_REGEXP = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s+(" +
75
+ Net::DNS::RR::Classes.regexp +
76
+ "|CLASS\\d+)?\\s*(" +
77
+ Net::DNS::RR::Types.regexp +
78
+ "|TYPE\\d+)?\\s*(.*)$", Regexp::IGNORECASE)
79
+
80
+ # Dimension of the sum of class, type, TTL and rdlength fields in a
81
+ # RR portion of the packet, in bytes
82
+ RRFIXEDSZ = 10
83
+
84
+ # Name of the RR
85
+ attr_reader :name
86
+ # TTL time (in seconds) of the RR
87
+ attr_reader :ttl
88
+ # Data belonging to that appropriate class,
89
+ # not to be used (use real accessors instead)
90
+ attr_reader :rdata
91
+
92
+ # Create a new instance of Net::DNS::RR class, or an instance of
93
+ # any of the subclass of the appropriate type.
94
+ #
95
+ # Argument can be a string or an hash. With a sting, we can pass
96
+ # a RR resource record in the canonical format:
97
+ #
98
+ # a = Net::DNS::RR.new("foo.example.com. 86400 A 10.1.2.3")
99
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
100
+ # cname = Net::DNS::RR.new("www.example.com 300 IN CNAME www1.example.com")
101
+ # txt = Net::DNS::RR.new('baz.example.com 3600 HS TXT "text record"')
102
+ #
103
+ # Incidentally, +a+, +mx+, +cname+ and +txt+ objects will be instances of
104
+ # respectively Net::DNS::RR::A, Net::DNS::RR::MX, Net::DNS::RR::CNAME and
105
+ # Net::DNS::RR::TXT classes.
106
+ #
107
+ # The name is required; all other informations are optional.
108
+ # If omitted, the +TTL+ defaults to 10800, +type+ default to +A+ and the RR class
109
+ # defaults to +IN+. Omitting the optional fields is useful for creating the
110
+ # empty RDATA sections required for certain dynamic update operations.
111
+ # All names must be fully qualified. The trailing dot (.) is optional.
112
+ #
113
+ # The preferred method is however passing an hash with keys and values:
114
+ #
115
+ # rr = Net::DNS::RR.new(
116
+ # :name => "foo.example.com",
117
+ # :ttl => 86400,
118
+ # :cls => "IN",
119
+ # :type => "A",
120
+ # :address => "10.1.2.3"
121
+ # )
122
+ #
123
+ # rr = Net::DNS::RR.new(
124
+ # :name => "foo.example.com",
125
+ # :type => "A"
126
+ # )
127
+ #
128
+ # Name and data are required; all the others fields are optionals like
129
+ # we've seen before. The data field can be specified either with the
130
+ # right name of the resource (:address in the example above) or with
131
+ # the generic key :rdata. Consult documentation to find the exact name
132
+ # for the resource in each subclass.
133
+ #
134
+ def initialize(arg)
135
+ case arg
136
+ when String
137
+ instance = new_from_string(arg)
138
+ when Hash
139
+ instance = new_from_hash(arg)
140
+ else
141
+ raise RRArgumentError, "Invalid argument, must be a RR string or an hash of values"
142
+ end
143
+
144
+ if @type.to_s == "ANY"
145
+ @cls = Net::DNS::RR::Classes.new("IN")
146
+ end
147
+
148
+ build_pack
149
+ set_type
150
+
151
+ instance
152
+ end
153
+
154
+ # Return a new RR object of the correct type (like Net::DNS::RR::A
155
+ # if the type is A) from a binary string, usually obtained from
156
+ # network stream.
157
+ #
158
+ # This method is used when parsing a binary packet by the Packet
159
+ # class.
160
+ #
161
+ def RR.parse(data)
162
+ o = allocate
163
+ obj,offset = o.send(:new_from_binary, data, 0)
164
+ return obj
165
+ end
166
+
167
+ # Same as RR.parse, but takes an entire packet binary data to
168
+ # perform name expansion. Default when analizing a packet
169
+ # just received from a network stream.
170
+ #
171
+ # Return an instance of appropriate class and the offset
172
+ # pointing at the end of the data parsed.
173
+ #
174
+ def RR.parse_packet(data,offset)
175
+ o = allocate
176
+ o.send(:new_from_binary,data,offset)
177
+ end
178
+
179
+ # Return the RR object in binary data format, suitable
180
+ # for using in network streams, with names compressed.
181
+ # Must pass as arguments the offset inside the packet
182
+ # and an hash of compressed names.
183
+ #
184
+ # This method is to be used in other classes and is
185
+ # not intended for user space programs.
186
+ #
187
+ # TO FIX in one of the future releases
188
+ #
189
+ def comp_data(offset,compnames)
190
+ type,cls = @type.to_i, @cls.to_i
191
+ str,offset,names = dn_comp(@name,offset,compnames)
192
+ str += [type,cls,@ttl,@rdlength].pack("n2 N n")
193
+ offset += Net::DNS::RRFIXEDSZ
194
+ return str,offset,names
195
+ end
196
+
197
+ # Return the RR object in binary data format, suitable
198
+ # for using in network streams.
199
+ #
200
+ # raw_data = rr.data
201
+ # puts "RR is #{raw_data.size} bytes long"
202
+ #
203
+ def data
204
+ type,cls = @type.to_i, @cls.to_i
205
+ str = pack_name(@name)
206
+ return str + [type,cls,@ttl,@rdlength].pack("n2 N n") + get_data
207
+ end
208
+
209
+ # Canonical inspect method
210
+ #
211
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
212
+ # #=> example.com. 7200 IN MX 10 mailhost.example.com.
213
+ #
214
+ def inspect
215
+ # Organize data for pretty format
216
+ if @name.size >= 24
217
+ nlen = @name.size + 1
218
+ tlen = @ttl.to_s.size+2
219
+ else
220
+ nlen = 24
221
+ tlen = 8
222
+ end
223
+ data = get_inspect
224
+ # Returns the preformatted string
225
+ [@name, @ttl.to_s, @cls.to_s, @type.to_s,
226
+ data].pack("A#{nlen} A#{tlen} A#{8-(nlen-24)-(tlen-8)} A8 A*")
227
+ end
228
+
229
+ # Type accessor
230
+ def type
231
+ @type.to_s
232
+ end
233
+
234
+ # Class accessor
235
+ def cls
236
+ @cls.to_s
237
+ end
238
+
239
+ private
240
+
241
+ #---
242
+ # New RR with argument in string form
243
+ #---
244
+ def new_from_string(rrstring)
245
+
246
+ unless rrstring =~ RR_REGEXP
247
+ raise RRArgumentError,
248
+ "Format error for RR string (maybe CLASS and TYPE not valid?)"
249
+ end
250
+
251
+ # Name of RR - mandatory
252
+ begin
253
+ @name = $1.downcase
254
+ rescue NoMethodError
255
+ raise RRArgumentError, "Missing name field in RR string #{rrstring}"
256
+ end
257
+
258
+ # Time to live for RR, default 3 hours
259
+ @ttl = $2 ? $2.to_i : 10800
260
+
261
+ # RR class, default to IN
262
+ @cls = Net::DNS::RR::Classes.new $3
263
+
264
+ # RR type, default to A
265
+ @type = Net::DNS::RR::Types.new $4
266
+
267
+ # All the rest is data
268
+ @rdata = $5 ? $5.strip : ""
269
+
270
+ if self.class == Net::DNS::RR
271
+ (eval "Net::DNS::RR::#@type").new(rrstring)
272
+ else
273
+ subclass_new_from_string(@rdata)
274
+ self.class
275
+ end
276
+ end
277
+
278
+ def new_from_hash(args)
279
+
280
+ # Name field is mandatory
281
+ unless args.has_key? :name
282
+ raise RRArgumentError, "RR argument error: need at least RR name"
283
+ end
284
+
285
+ @name = args[:name].downcase
286
+ @ttl = args[:ttl] ? args[:ttl].to_i : 10800 # Default 3 hours
287
+ @type = Net::DNS::RR::Types.new args[:type]
288
+ @cls = Net::DNS::RR::Classes.new args[:cls]
289
+
290
+ @rdata = args[:rdata] ? args[:rdata].strip : ""
291
+ @rdlength = args[:rdlength] || @rdata.size
292
+
293
+ if self.class == Net::DNS::RR
294
+ (eval "Net::DNS::RR::#@type").new(args)
295
+ else
296
+ hash = args - [:name,:ttl,:type,:cls]
297
+ if hash.has_key? :rdata
298
+ subclass_new_from_string(hash[:rdata])
299
+ else
300
+ subclass_new_from_hash(hash)
301
+ end
302
+ self.class
303
+ end
304
+ end # new_from_hash
305
+
306
+ def new_from_binary(data,offset)
307
+ if self.class == Net::DNS::RR
308
+ temp = dn_expand(data,offset)[1]
309
+ type = Net::DNS::RR::Types.new data.unpack("@#{temp} n")[0]
310
+ (eval "Net::DNS::RR::#{type}").parse_packet(data,offset)
311
+ else
312
+ @name,offset = dn_expand(data,offset)
313
+ rrtype,cls,@ttl,@rdlength = data.unpack("@#{offset} n2 N n")
314
+ @type = Net::DNS::RR::Types.new rrtype
315
+ @cls = Net::DNS::RR::Classes.new cls
316
+ offset += RRFIXEDSZ
317
+ offset = subclass_new_from_binary(data,offset)
318
+ build_pack
319
+ set_type
320
+ return [self,offset]
321
+ end
322
+ # rescue StandardError => err
323
+ # raise RRDataError, "Caught exception, maybe packet malformed: #{err}"
324
+ end
325
+
326
+ # Methods to be overridden by subclasses
327
+ def subclass_new_from_array(arr)
328
+ end
329
+ def subclass_new_from_string(str)
330
+ end
331
+ def subclass_new_from_hash(hash)
332
+ end
333
+ def subclass_new_from_binary(data,offset)
334
+ end
335
+ def build_pack
336
+ end
337
+ def set_type
338
+ end
339
+ def get_inspect
340
+ @rdata
341
+ end
342
+ def get_data
343
+ @rdata
344
+ end
345
+
346
+ # NEW new method :)
347
+ def self.new(*args)
348
+ o = allocate
349
+ obj = o.send(:initialize,*args)
350
+ if self == Net::DNS::RR
351
+ return obj
352
+ else
353
+ return o
354
+ end
355
+ end
356
+
357
+ end # class RR
358
+
359
+ end # module DNS
360
+ end # module Net
361
+
362
+ class RRArgumentError < ArgumentError # :nodoc:
363
+ end
364
+ class RRDataError < StandardError # :nodoc:
365
+ end
366
+
367
+ class Hash # :nodoc:
368
+
369
+ # Performs a sort of group difference
370
+ # operation on hashes or arrays
371
+ #
372
+ # a = {:a=>1,:b=>2,:c=>3}
373
+ # b = {:a=>1,:b=>2}
374
+ # c = [:a,:c]
375
+ # a-b #=> {:c=>3}
376
+ # a-c #=> {:b=>2}
377
+ #
378
+ def -(oth)
379
+ case oth
380
+ when Hash
381
+ delete_if {|k,v| oth.has_key? k}
382
+ when Array
383
+ delete_if {|k,v| oth.include? k}
384
+ end
385
+ end
386
+ end