bluemonk-net-dns 0.5.0

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,195 @@
1
+ #---
2
+ # $Id: Question.rb,v 1.8 2006/07/28 19:00:03 bluemonk Exp $
3
+ #+++
4
+
5
+ require 'net/dns/dns'
6
+ require 'net/dns/names/names'
7
+ require 'net/dns/rr/types'
8
+ require 'net/dns/rr/classes'
9
+
10
+ module Net # :nodoc:
11
+ module DNS
12
+
13
+ #
14
+ # =Name
15
+ #
16
+ # Net::DNS::Question - DNS packet question class
17
+ #
18
+ # =Synopsis
19
+ #
20
+ # require 'net/dns/question'
21
+ #
22
+ # =Description
23
+ #
24
+ # This class represent the Question portion of a DNS packet. The number
25
+ # of question entries is stored in the +qdCount+ variable of an Header
26
+ # object.
27
+ #
28
+ # A new object can be created passing the name of the query and the type
29
+ # of answer desired, plus an optional argument containing the class:
30
+ #
31
+ # question = Net::DNS::Question.new("google.com.", Net::DNS::A)
32
+ # #=> "google.com. A IN"
33
+ #
34
+ # Alternatevly, a new object is created when processing a binary
35
+ # packet, as when an answer is received.
36
+ # To obtain the binary data from a question object you can use
37
+ # the method Question#data:
38
+ #
39
+ # question.data
40
+ # #=> "\006google\003com\000\000\001\000\001"
41
+ #
42
+ # A lot of methods were written to keep a compatibility layer with
43
+ # the Perl version of the library, as long as methods name which are
44
+ # more or less the same.
45
+ #
46
+ # =Error classes
47
+ #
48
+ # Some error classes has been defined for the Net::DNS::Header class,
49
+ # which are listed here to keep a light and browsable main documentation.
50
+ # We have:
51
+ #
52
+ # * QuestionArgumentError: generic argument error
53
+ # * QuestionNameError: an error in the +name+ part of a Question entry
54
+ #
55
+ # =Copyright
56
+ #
57
+ # Copyright (c) 2006 Marco Ceresa
58
+ #
59
+ # All rights reserved. This program is free software; you may redistribute
60
+ # it and/or modify it under the same terms as Ruby itself.
61
+ #
62
+ class Question
63
+
64
+ include Net::DNS::Names
65
+
66
+ # +name+ part of a Question entry
67
+ attr_reader :qName
68
+ # +type+ part of a Question entry
69
+ attr_reader :qType
70
+ # +class+ part of a Question entry
71
+ attr_reader :qClass
72
+
73
+ # Creates a new Net::DNS::Question object:
74
+ #
75
+ # question = Net::DNS::Question.new("example.com")
76
+ # #=> "example.com A IN"
77
+ # question = Net::DNS::Question.new("example.com", Net::DNS::MX)
78
+ # #=> "example.com MX IN"
79
+ # question = Net::DNS::Question.new("example.com", Net::DNS::TXT, Net::DNS::HS)
80
+ # #=> "example.com TXT HS"
81
+
82
+ # If not specified, +type+ and +cls+ arguments defaults
83
+ # to Net::DNS::A and Net::DNS::IN respectively.
84
+ #
85
+ def initialize(name,type=Net::DNS::A,cls=Net::DNS::IN)
86
+ @qName = check_name name
87
+ @qType = Net::DNS::RR::Types.new type
88
+ @qClass = Net::DNS::RR::Classes.new cls
89
+ end
90
+
91
+ # Return a new Net::DNS::Question object created by
92
+ # parsing binary data, such as an answer from the
93
+ # nameserver.
94
+ #
95
+ # question = Net::DNS::Question.parse(data)
96
+ # puts "Queried for #{question.qName} type #{question.qType.to_s}"
97
+ # #=> Queried for example.com type A
98
+ #
99
+ def self.parse(arg)
100
+ if arg.kind_of? String
101
+ o = allocate
102
+ o.send(:new_from_binary,arg)
103
+ o
104
+ else
105
+ raise QuestionArgumentError, "Wrong argument format, must be a String"
106
+ end
107
+ end
108
+
109
+ # Known inspect method with nice formatting
110
+ def inspect
111
+ if @qName.size > 29 then
112
+ len = @qName.size + 1
113
+ else
114
+ len = 29
115
+ end
116
+ [@qName,@qClass.to_s,@qType.to_s].pack("A#{len} A8 A8")
117
+ end
118
+
119
+ # Outputs binary data from a Question object
120
+ #
121
+ # question.data
122
+ # #=> "\006google\003com\000\000\001\000\001"
123
+ #
124
+ def data
125
+ [pack_name(@qName),@qType.to_i,@qClass.to_i].pack("a*nn")
126
+ end
127
+
128
+ # Return the binary data of the objects, plus an offset
129
+ # and an Hash with references to compressed names. For use in
130
+ # Net::DNS::Packet compressed packet creation.
131
+ #
132
+ def comp_data
133
+ arr = @qName.split(".")
134
+ str = pack_name(@qName)
135
+ string = ""
136
+ names = {}
137
+ offset = Net::DNS::HFIXEDSZ
138
+ arr.size.times do |i|
139
+ x = i+1
140
+ elem = arr[-x]
141
+ len = elem.size
142
+ string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
143
+ names[string] = offset
144
+ offset += len
145
+ end
146
+ offset += 2 * Net::DNS::INT16SZ
147
+ str += "\000"
148
+ [[str,@qType.to_i,@qClass.to_i].pack("a*nn"),offset,names]
149
+ end
150
+
151
+ private
152
+
153
+ def build_qName(str)
154
+ result = ""
155
+ offset = 0
156
+ loop do
157
+ len = str.unpack("@#{offset} C")[0]
158
+ break if len == 0
159
+ offset += 1
160
+ result += str[offset..offset+len-1]
161
+ result += "."
162
+ offset += len
163
+ end
164
+ result
165
+ end
166
+
167
+ def check_name(name)
168
+ name.strip!
169
+ if name =~ /[^\w\.\-_]/
170
+ raise QuestionNameError, "Question name #{name.inspect} not valid"
171
+ else
172
+ name
173
+ end
174
+ rescue
175
+ raise QuestionNameError, "Question name #{name.inspect} not valid"
176
+ end
177
+
178
+ def new_from_binary(data)
179
+ str,type,cls = data.unpack("a#{data.size-4}nn")
180
+ @qName = build_qName(str)
181
+ @qType = Net::DNS::RR::Types.new type
182
+ @qClass = Net::DNS::RR::Classes.new cls
183
+ rescue StandardError => e
184
+ raise QuestionArgumentError, "Invalid data: #{data.inspect}\n{e.backtrace}"
185
+ end
186
+
187
+ end # class Question
188
+
189
+ end # class DNS
190
+ end # module Net
191
+
192
+ class QuestionArgumentError < ArgumentError # :nodoc:
193
+ end
194
+ class QuestionNameError < StandardError # :nodoc:
195
+ end
@@ -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