bluemonk-net-dns 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +10 -0
- data/CHANGELOG +7 -0
- data/INSTALL +8 -0
- data/README.rdoc +150 -0
- data/Rakefile +93 -0
- data/THANKS +24 -0
- data/VERSION.yml +4 -0
- data/demo/check_soa.rb +120 -0
- data/demo/threads.rb +22 -0
- data/lib/net/dns/dns.rb +117 -0
- data/lib/net/dns/header.rb +761 -0
- data/lib/net/dns/names/names.rb +109 -0
- data/lib/net/dns/packet.rb +581 -0
- data/lib/net/dns/question.rb +195 -0
- data/lib/net/dns/resolver/socks.rb +154 -0
- data/lib/net/dns/resolver/timeouts.rb +73 -0
- data/lib/net/dns/resolver.rb +1267 -0
- data/lib/net/dns/rr/a.rb +121 -0
- data/lib/net/dns/rr/aaaa.rb +92 -0
- data/lib/net/dns/rr/classes.rb +148 -0
- data/lib/net/dns/rr/cname.rb +69 -0
- data/lib/net/dns/rr/hinfo.rb +74 -0
- data/lib/net/dns/rr/mr.rb +68 -0
- data/lib/net/dns/rr/mx.rb +74 -0
- data/lib/net/dns/rr/ns.rb +70 -0
- data/lib/net/dns/rr/null.rb +61 -0
- data/lib/net/dns/rr/ptr.rb +71 -0
- data/lib/net/dns/rr/soa.rb +85 -0
- data/lib/net/dns/rr/srv.rb +57 -0
- data/lib/net/dns/rr/txt.rb +72 -0
- data/lib/net/dns/rr/types.rb +200 -0
- data/lib/net/dns/rr.rb +406 -0
- data/setup.rb +1360 -0
- data/test/net/dns/resolver/test_timeouts.rb +59 -0
- data/test/net/dns/rr/test_a.rb +72 -0
- data/test/net/dns/rr/test_classes.rb +73 -0
- data/test/net/dns/rr/test_ns.rb +66 -0
- data/test/net/dns/rr/test_types.rb +127 -0
- data/test/net/dns/test_header.rb +170 -0
- data/test/net/dns/test_packet.rb +42 -0
- data/test/net/dns/test_question.rb +54 -0
- data/test/net/dns/test_rr.rb +133 -0
- metadata +105 -0
@@ -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
|