net-dns 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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