pNet-DNS 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +68 -0
- data/lib/Net/DNS.rb +879 -0
- data/lib/Net/DNS/Header.rb +303 -0
- data/lib/Net/DNS/Nameserver.rb +601 -0
- data/lib/Net/DNS/Packet.rb +851 -0
- data/lib/Net/DNS/Question.rb +117 -0
- data/lib/Net/DNS/RR.rb +630 -0
- data/lib/Net/DNS/RR/A.rb +103 -0
- data/lib/Net/DNS/RR/AAAA.rb +147 -0
- data/lib/Net/DNS/RR/AFSDB.rb +114 -0
- data/lib/Net/DNS/RR/CERT.rb +191 -0
- data/lib/Net/DNS/RR/CNAME.rb +89 -0
- data/lib/Net/DNS/RR/DNAME.rb +84 -0
- data/lib/Net/DNS/RR/EID.rb +70 -0
- data/lib/Net/DNS/RR/HINFO.rb +108 -0
- data/lib/Net/DNS/RR/ISDN.rb +118 -0
- data/lib/Net/DNS/RR/LOC.rb +341 -0
- data/lib/Net/DNS/RR/MB.rb +92 -0
- data/lib/Net/DNS/RR/MG.rb +96 -0
- data/lib/Net/DNS/RR/MINFO.rb +109 -0
- data/lib/Net/DNS/RR/MR.rb +92 -0
- data/lib/Net/DNS/RR/MX.rb +124 -0
- data/lib/Net/DNS/RR/NAPTR.rb +182 -0
- data/lib/Net/DNS/RR/NIMLOC.rb +70 -0
- data/lib/Net/DNS/RR/NS.rb +100 -0
- data/lib/Net/DNS/RR/NSAP.rb +273 -0
- data/lib/Net/DNS/RR/NULL.rb +68 -0
- data/lib/Net/DNS/RR/OPT.rb +251 -0
- data/lib/Net/DNS/RR/PTR.rb +93 -0
- data/lib/Net/DNS/RR/PX.rb +131 -0
- data/lib/Net/DNS/RR/RP.rb +108 -0
- data/lib/Net/DNS/RR/RT.rb +115 -0
- data/lib/Net/DNS/RR/SOA.rb +195 -0
- data/lib/Net/DNS/RR/SPF.rb +46 -0
- data/lib/Net/DNS/RR/SRV.rb +153 -0
- data/lib/Net/DNS/RR/SSHFP.rb +190 -0
- data/lib/Net/DNS/RR/TKEY.rb +219 -0
- data/lib/Net/DNS/RR/TSIG.rb +358 -0
- data/lib/Net/DNS/RR/TXT.rb +162 -0
- data/lib/Net/DNS/RR/UNKNOWN.rb +76 -0
- data/lib/Net/DNS/RR/X25.rb +90 -0
- data/lib/Net/DNS/Resolver.rb +2090 -0
- data/lib/Net/DNS/Resolver/Recurse.rb +478 -0
- data/lib/Net/DNS/Update.rb +189 -0
- data/test/custom.txt +4 -0
- data/test/resolv.conf +4 -0
- data/test/tc_escapedchars.rb +498 -0
- data/test/tc_header.rb +91 -0
- data/test/tc_inet6.rb +169 -0
- data/test/tc_misc.rb +137 -0
- data/test/tc_online.rb +236 -0
- data/test/tc_packet.rb +174 -0
- data/test/tc_packet_unique_push.rb +126 -0
- data/test/tc_question.rb +49 -0
- data/test/tc_recurse.rb +69 -0
- data/test/tc_res_env.rb +59 -0
- data/test/tc_res_file.rb +55 -0
- data/test/tc_res_opt.rb +135 -0
- data/test/tc_resolver.rb +102 -0
- data/test/tc_rr-opt.rb +40 -0
- data/test/tc_rr-rrsort.rb +116 -0
- data/test/tc_rr-txt.rb +138 -0
- data/test/tc_rr-unknown.rb +95 -0
- data/test/tc_rr.rb +246 -0
- data/test/tc_tcp.rb +34 -0
- data/test/tc_tkey.rb +115 -0
- data/test/tc_update.rb +226 -0
- data/test/ts_netdns.rb +17 -0
- data/test/ts_offline.rb +32 -0
- data/test/ts_online.rb +33 -0
- metadata +119 -0
@@ -0,0 +1,303 @@
|
|
1
|
+
# The contents of this file are subject to the Mozilla
|
2
|
+
# Public Licence Version 1.1 (the "Licence"); you may
|
3
|
+
# not use this file except in compliance with the
|
4
|
+
# Licence. You may obtain a copy of the Licence at
|
5
|
+
# http://www.mozilla.org/MPL
|
6
|
+
# Software distributed under the Licence is distributed
|
7
|
+
# on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
|
8
|
+
# either express or implied. See the Licence of the
|
9
|
+
# specific language governing rights and limitations
|
10
|
+
# under the Licence.
|
11
|
+
# The Original Code is pNet::DNS.
|
12
|
+
# The Initial Developer of the Original Code is
|
13
|
+
# Nominet UK (www.nominet.org.uk). Portions created by
|
14
|
+
# Nominet UK are Copyright (c) Nominet UK 2006.
|
15
|
+
# All rights reserved.
|
16
|
+
module Net
|
17
|
+
module DNS
|
18
|
+
#= NAME
|
19
|
+
#
|
20
|
+
#Net::DNS::Header - DNS packet header class
|
21
|
+
#
|
22
|
+
#= DESCRIPTION
|
23
|
+
#
|
24
|
+
#A Net::DNS::Header object represents the header portion of a DNS
|
25
|
+
#packet.
|
26
|
+
#
|
27
|
+
#= COPYRIGHT
|
28
|
+
#
|
29
|
+
#Copyright (c) 1997-2002 Michael Fuhr.
|
30
|
+
#
|
31
|
+
#Portions Copyright (c) 2002-2004 Chris Reinhardt.
|
32
|
+
#
|
33
|
+
#Portions Copyright (c) 2006 AlexD (Nominet UK)
|
34
|
+
#
|
35
|
+
#All rights reserved. This program is free software; you may redistribute
|
36
|
+
#it and/or modify it under the same terms as Perl itself.
|
37
|
+
#
|
38
|
+
#= SEE ALSO
|
39
|
+
#
|
40
|
+
#Net::DNS, Net::DNS::Resolver, Net::DNS::Packet,
|
41
|
+
#Net::DNS::Update, Net::DNS::Question, Net::DNS::RR,
|
42
|
+
#RFC 1035 Section 4.1.1
|
43
|
+
class Header
|
44
|
+
|
45
|
+
#The query identification number.
|
46
|
+
attr_accessor :id
|
47
|
+
|
48
|
+
#Gets or sets the query response flag.
|
49
|
+
attr_accessor :qr
|
50
|
+
|
51
|
+
# print "query opcode = ", header.opcode, "\n"
|
52
|
+
# header.opcode("UPDATE")
|
53
|
+
#
|
54
|
+
#Gets or sets the query opcode (the purpose of the query).
|
55
|
+
attr_accessor :opcode
|
56
|
+
|
57
|
+
# print "answer is ", (header.aa!=0) ? "" : "non-", "authoritative\n"
|
58
|
+
# header.aa=(0)
|
59
|
+
#
|
60
|
+
#Gets or sets the authoritative answer flag.
|
61
|
+
attr_accessor :aa
|
62
|
+
|
63
|
+
# print "packet is ", header.tc!=0 ? "" : "not ", "truncated\n"
|
64
|
+
# header.tc=(0)
|
65
|
+
#
|
66
|
+
#Gets or sets the truncated packet flag.
|
67
|
+
attr_accessor :tc
|
68
|
+
|
69
|
+
# print "recursion was ", header.rd!=0 ? "" : "not ", "desired\n"
|
70
|
+
# header.rd=(0)
|
71
|
+
#
|
72
|
+
#Gets or sets the recursion desired flag.
|
73
|
+
attr_accessor :rd
|
74
|
+
|
75
|
+
# print "checking was ", header.cd!=0 ? "not" : "", "desired\n"
|
76
|
+
# header.cd=(0)
|
77
|
+
#
|
78
|
+
#Gets or sets the checking disabled flag.
|
79
|
+
attr_accessor :cd
|
80
|
+
|
81
|
+
# print "recursion is ", header.ra!=0 ? "" : "not ", "available\n"
|
82
|
+
# header.ra=(0)
|
83
|
+
#
|
84
|
+
#Gets or sets the recursion available flag.
|
85
|
+
attr_accessor :ra
|
86
|
+
|
87
|
+
# print "The result has ", header.ad!=0 ? "" : "not", "been verified\n"
|
88
|
+
#
|
89
|
+
#Relevant in DNSSEC context.
|
90
|
+
#
|
91
|
+
#(The AD bit is only set on answers where signatures have been
|
92
|
+
#cryptographically verified or the server is authoritative for the data
|
93
|
+
#and is allowed to set the bit by policy.)
|
94
|
+
attr_accessor :ad
|
95
|
+
|
96
|
+
# print "query response code = ", header.rcode, "\n"
|
97
|
+
# header.rcode=("SERVFAIL")
|
98
|
+
#
|
99
|
+
#Gets or sets the query response code (the status of the query).
|
100
|
+
attr_accessor :rcode
|
101
|
+
|
102
|
+
# print "# of question records: ", header.qdcount, "\n"
|
103
|
+
# header.qdcount=(2)
|
104
|
+
#
|
105
|
+
#Gets or sets the number of records in the question section of the packet.
|
106
|
+
#In dynamic update packets, this field is known as zocount and refers
|
107
|
+
#to the number of RRs in the zone section.
|
108
|
+
attr_accessor :qdcount
|
109
|
+
|
110
|
+
# print "# of answer records: ", header.ancount, "\n"
|
111
|
+
# header.ancount=(5)
|
112
|
+
#
|
113
|
+
#Gets or sets the number of records in the answer section of the packet.
|
114
|
+
#In dynamic update packets, this field is known as prcount and refers
|
115
|
+
#to the number of RRs in the prerequisite section.
|
116
|
+
attr_accessor :ancount
|
117
|
+
|
118
|
+
# print "# of authority records: ", header.nscount, "\n"
|
119
|
+
# header.nscount=(2)
|
120
|
+
#
|
121
|
+
#Gets or sets the number of records in the authority section of the packet.
|
122
|
+
#In dynamic update packets, this field is known as upcount and refers
|
123
|
+
#to the number of RRs in the update section.
|
124
|
+
attr_accessor :nscount
|
125
|
+
|
126
|
+
# print "# of additional records: ", header.arcount, "\n"
|
127
|
+
# header.arcount=(3)
|
128
|
+
#
|
129
|
+
#Gets or sets the number of records in the additional section of the packet.
|
130
|
+
#In dynamic update packets, this field is known as adcount.
|
131
|
+
attr_accessor :arcount
|
132
|
+
|
133
|
+
alias zocount qdcount
|
134
|
+
alias zocount= qdcount=
|
135
|
+
|
136
|
+
alias prcount ancount
|
137
|
+
alias prcount= ancount=
|
138
|
+
|
139
|
+
alias upcount nscount
|
140
|
+
alias upcount= nscount=
|
141
|
+
|
142
|
+
alias adcount arcount
|
143
|
+
alias adcount= arcount=
|
144
|
+
|
145
|
+
MAX_ID = 65535
|
146
|
+
@@next_id = rand(MAX_ID)
|
147
|
+
|
148
|
+
# Get the next Header ID
|
149
|
+
def nextid()
|
150
|
+
@@next_id += 1
|
151
|
+
if (@@next_id > MAX_ID)
|
152
|
+
@@next_id = 0
|
153
|
+
end
|
154
|
+
return @@next_id
|
155
|
+
end
|
156
|
+
|
157
|
+
# header = Net::DNS::Header.new
|
158
|
+
# header = Net::DNS::Header.new(data)
|
159
|
+
#
|
160
|
+
#Without an argument, new creates a header object appropriate
|
161
|
+
#for making a DNS query.
|
162
|
+
#
|
163
|
+
#If new is passed a reference to a scalar containing DNS packet
|
164
|
+
#data, it creates a header object from that data.
|
165
|
+
#
|
166
|
+
#Returns *nil* if unable to create a header object (e.g., if
|
167
|
+
#the data is incomplete).
|
168
|
+
def initialize(*args)
|
169
|
+
|
170
|
+
@qr = 0
|
171
|
+
@opcode = 0
|
172
|
+
@aa = 0
|
173
|
+
@tc = 0
|
174
|
+
@rd = 1
|
175
|
+
@ra = 0
|
176
|
+
@ad = 0
|
177
|
+
@cd = 0
|
178
|
+
@rcode = 0
|
179
|
+
@qdcount = 0
|
180
|
+
@ancount = 0
|
181
|
+
@nscount = 0
|
182
|
+
@arcount = 0
|
183
|
+
|
184
|
+
if (args != nil && args.length > 0)
|
185
|
+
data = args[0];
|
186
|
+
|
187
|
+
if (data)
|
188
|
+
|
189
|
+
if (data.length < Net::DNS::HFIXEDSZ )
|
190
|
+
return nil;
|
191
|
+
end
|
192
|
+
|
193
|
+
a = data.unpack("n C2 n4");
|
194
|
+
@id = a[0]
|
195
|
+
@qr = (a[1] >> 7) & 0x1
|
196
|
+
@opcode = (a[1] >> 3) & 0xf
|
197
|
+
@aa = (a[1] >> 2) & 0x1
|
198
|
+
@tc = (a[1] >> 1) & 0x1
|
199
|
+
@rd = a[1] & 0x1
|
200
|
+
@ra = (a[2] >> 7) & 0x1
|
201
|
+
@ad = (a[2] >> 5) & 0x1
|
202
|
+
@cd = (a[2] >> 4) & 0x1
|
203
|
+
@rcode = a[2] & 0xf
|
204
|
+
@qdcount = a[3]
|
205
|
+
@ancount = a[4]
|
206
|
+
@nscount = a[5]
|
207
|
+
@arcount = a[6]
|
208
|
+
else
|
209
|
+
@id = nextid()
|
210
|
+
end
|
211
|
+
else
|
212
|
+
@id = nextid()
|
213
|
+
end
|
214
|
+
|
215
|
+
hasKey = Net::DNS::Opcodesbyval.has_key?@opcode
|
216
|
+
temp = Net::DNS::Opcodesbyval[@opcode]
|
217
|
+
temp2 = Net::DNS::Opcodesbyval
|
218
|
+
if (Net::DNS::Opcodesbyval[@opcode] != nil)
|
219
|
+
@opcode = Net::DNS::Opcodesbyval[@opcode]
|
220
|
+
end
|
221
|
+
if (Net::DNS::Rcodesbyval[@rcode]!=nil)
|
222
|
+
@rcode = Net::DNS::Rcodesbyval[@rcode]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
#Returns a string representation of the header object.
|
227
|
+
#
|
228
|
+
# print header.inspect
|
229
|
+
def inspect
|
230
|
+
retval = ";; id = #{@id}\n";
|
231
|
+
|
232
|
+
if (@opcode == "UPDATE")
|
233
|
+
retval += ";; qr = #{@qr} " +\
|
234
|
+
"opcode = #{@opcode} "+\
|
235
|
+
"rcode = #{@rcode}\n";
|
236
|
+
|
237
|
+
retval += ";; zocount = #{@qdcount} "+\
|
238
|
+
"prcount = #{@ancount} " +\
|
239
|
+
"upcount = #{@nscount} " +\
|
240
|
+
"adcount = #{@arcount}\n";
|
241
|
+
else
|
242
|
+
retval += ";; qr = #{@qr} " +\
|
243
|
+
"opcode = #{@opcode} " +\
|
244
|
+
"aa = #{@aa} " +\
|
245
|
+
"tc = #{@tc} " +\
|
246
|
+
"rd = #{@rd}\n";
|
247
|
+
|
248
|
+
retval += ";; ra = #{@ra} " +\
|
249
|
+
"ad = #{@ad} " +\
|
250
|
+
"cd = #{@cd} " +\
|
251
|
+
"rcode = #{@rcode}\n";
|
252
|
+
|
253
|
+
retval += ";; qdcount = #{@qdcount} " +\
|
254
|
+
"ancount = #{@ancount} " +\
|
255
|
+
"nscount = #{@nscount} " +\
|
256
|
+
"arcount = #{@arcount}\n";
|
257
|
+
end
|
258
|
+
|
259
|
+
return retval;
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
#Returns the header data in binary format, appropriate for use in a
|
264
|
+
#DNS query packet.
|
265
|
+
#
|
266
|
+
# hdata = header.data
|
267
|
+
def data
|
268
|
+
opcode = Net::DNS::Opcodesbyname[@opcode];
|
269
|
+
rcode = Net::DNS::Rcodesbyname[@rcode];
|
270
|
+
|
271
|
+
byte2 = (@qr << 7) | (opcode << 3) | (@aa << 2) | (@tc << 1) | @rd;
|
272
|
+
|
273
|
+
byte3 = (@ra << 7) | (@ad << 5) | (@cd << 4) | rcode;
|
274
|
+
|
275
|
+
return [@id, byte2, byte3, @qdcount, @ancount, @nscount, @arcount].pack("n C2 n4");
|
276
|
+
end
|
277
|
+
|
278
|
+
def ==(other)
|
279
|
+
if (other != nil) && (other.is_a?(Header))
|
280
|
+
return false if @qr!=other.qr;
|
281
|
+
return false if @opcode!=other.opcode;
|
282
|
+
return false if @aa!=other.aa;
|
283
|
+
return false if @tc!=other.tc;
|
284
|
+
return false if @rd!=other.rd;
|
285
|
+
return false if @ra!=other.ra;
|
286
|
+
return false if @ad!=other.ad;
|
287
|
+
return false if @cd!=other.cd;
|
288
|
+
return false if @rcode!=other.rcode;
|
289
|
+
return false if @ancount!=other.ancount;
|
290
|
+
return false if @nscount!=other.nscount;
|
291
|
+
return false if @qdcount!=other.qdcount;
|
292
|
+
return false if @id!=other.id;
|
293
|
+
return false if @arcount!=other.arcount;
|
294
|
+
|
295
|
+
return true;
|
296
|
+
else
|
297
|
+
return false
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,601 @@
|
|
1
|
+
# The contents of this file are subject to the Mozilla
|
2
|
+
# Public Licence Version 1.1 (the "Licence"); you may
|
3
|
+
# not use this file except in compliance with the
|
4
|
+
# Licence. You may obtain a copy of the Licence at
|
5
|
+
# http://www.mozilla.org/MPL
|
6
|
+
# Software distributed under the Licence is distributed
|
7
|
+
# on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
|
8
|
+
# either express or implied. See the Licence of the
|
9
|
+
# specific language governing rights and limitations
|
10
|
+
# under the Licence.
|
11
|
+
# The Original Code is pNet::DNS.
|
12
|
+
# The Initial Developer of the Original Code is
|
13
|
+
# Nominet UK (www.nominet.org.uk). Portions created by
|
14
|
+
# Nominet UK are Copyright (c) Nominet UK 2006.
|
15
|
+
# All rights reserved.
|
16
|
+
require 'socket'
|
17
|
+
require 'ipaddr'
|
18
|
+
require 'Net/DNS'
|
19
|
+
|
20
|
+
module Net
|
21
|
+
module DNS
|
22
|
+
class Nameserver
|
23
|
+
|
24
|
+
STATE_ACCEPTED = 1
|
25
|
+
STATE_GOT_LENGTH = 2
|
26
|
+
STATE_SENDING = 3
|
27
|
+
|
28
|
+
DEFAULT_PORT=53
|
29
|
+
DEFAULT_ADDR = Socket::INADDR_ANY
|
30
|
+
|
31
|
+
|
32
|
+
# def initialize(localaddr=DEFAULT_ADDR, localport=DEFAULT_PORT, verbose=false, &replyhandler)
|
33
|
+
def initialize(opts={})
|
34
|
+
# LocalAddr IP address on which to listen. Defaults to INADDR_ANY.
|
35
|
+
# LocalPort Port on which to listen. Defaults to 53.
|
36
|
+
# ReplyHandler Reference to reply-handling
|
37
|
+
# subroutine Required.
|
38
|
+
# Verbose Print info about received
|
39
|
+
# queries. Defaults to 0 (off).
|
40
|
+
opts = {:localaddr => DEFAULT_ADDR, :localport => DEFAULT_PORT, :verbose => false}.merge(opts)
|
41
|
+
if (!opts[:replyhandler] || opts[:replyhandler]==nil)
|
42
|
+
raise RuntimeError, "No reply handler!"
|
43
|
+
end
|
44
|
+
@replyhandler = opts[:replyhandler]
|
45
|
+
@verbose = opts[:verbose]
|
46
|
+
@sockets = []
|
47
|
+
@_tcp = Hash.new
|
48
|
+
|
49
|
+
port=opts[:localport]
|
50
|
+
|
51
|
+
# make sure we have an array.
|
52
|
+
# localaddr= [DEFAULT_ADDR] unless opts[:localaddr]!=nil
|
53
|
+
localaddr= [ opts[:localaddr] ] unless (opts[:localaddr].is_a?(Array))
|
54
|
+
|
55
|
+
localaddresses = localaddr
|
56
|
+
|
57
|
+
print "Nameserver on #{localaddresses.inspect}:#{port}\n" if @verbose
|
58
|
+
|
59
|
+
# while we are here, print incomplete lines as they come along.
|
60
|
+
# local $| = 1 if @verbose
|
61
|
+
|
62
|
+
localaddresses.each do |localaddress|
|
63
|
+
|
64
|
+
addr = localaddress
|
65
|
+
|
66
|
+
# If not, it will do DNS lookups trying to resolve it as a hostname
|
67
|
+
# We could also just set it to undef?
|
68
|
+
|
69
|
+
# addr = IPAddr.ntop(addr) unless IPAddr.ipv4?(addr) || IPAddr.ipv6?(addr)
|
70
|
+
|
71
|
+
# Pretty IP-addresses, if they are otherwise binary.
|
72
|
+
# addrname = addr
|
73
|
+
# addrname = IPAddr.ntop(addrname) unless addrname =~ /^[\w\.:\-]+$/
|
74
|
+
addrname = IPAddr.new(addr, Socket::AF_INET)
|
75
|
+
print "Setting up listening sockets for #{addrname}...\n" if @verbose
|
76
|
+
|
77
|
+
print "Creating TCP socket for #{addrname} - " if @verbose
|
78
|
+
|
79
|
+
#--------------------------------------------------------------------------
|
80
|
+
# Create the TCP socket.
|
81
|
+
#--------------------------------------------------------------------------
|
82
|
+
|
83
|
+
# sock_tcp = inet_new(
|
84
|
+
# LocalAddr => $addr,
|
85
|
+
# LocalPort => $port,
|
86
|
+
# Listen => 64,
|
87
|
+
# Proto => "tcp",
|
88
|
+
# Reuse => 1,
|
89
|
+
# );
|
90
|
+
# sock_tcp = TCPSocket.new(addr, port)
|
91
|
+
@tcpserver = TCPServer.new(addr, port)
|
92
|
+
if (!@tcpserver)
|
93
|
+
raise RuntimError, "Couldn't create TCP socket: #{$!}"
|
94
|
+
return
|
95
|
+
end
|
96
|
+
@tcpserver.listen(64)
|
97
|
+
@sockets.push(@tcpserver)
|
98
|
+
print "done.\n" if @verbose
|
99
|
+
|
100
|
+
#--------------------------------------------------------------------------
|
101
|
+
# Create the UDP Socket.
|
102
|
+
#--------------------------------------------------------------------------
|
103
|
+
|
104
|
+
print "Creating UDP socket for #{addrname} - " if @verbose
|
105
|
+
|
106
|
+
sock_udp = UDPSocket.new()
|
107
|
+
sock_udp.bind(addr, port)
|
108
|
+
|
109
|
+
if (!sock_udp)
|
110
|
+
raise RuntimeError, "Couldn't create UDP socket: #{$!}"
|
111
|
+
return
|
112
|
+
end
|
113
|
+
@sockets.push(sock_udp)
|
114
|
+
print "done.\n" if @verbose
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
#------------------------------------------------------------------------------
|
120
|
+
# make_reply - Make a reply packet.
|
121
|
+
#------------------------------------------------------------------------------
|
122
|
+
|
123
|
+
def make_reply(query, peerhost)
|
124
|
+
reply=""
|
125
|
+
headermask=""
|
126
|
+
|
127
|
+
if (not query)
|
128
|
+
print "ERROR: invalid packet\n" if @verbose
|
129
|
+
reply = Net::DNS::Packet.new_from_values("", "ANY", "ANY")
|
130
|
+
reply.header.rcode=("FORMERR")
|
131
|
+
|
132
|
+
return reply
|
133
|
+
end
|
134
|
+
|
135
|
+
if (query.header.qr==1)
|
136
|
+
print "ERROR: invalid packet (qr was set, dropping)\n" if @verbose
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
qr = (query.question)[0]
|
142
|
+
|
143
|
+
qname = qr ? qr.qname : ""
|
144
|
+
qclass = qr ? qr.qclass : "ANY"
|
145
|
+
qtype = qr ? qr.qtype : "ANY"
|
146
|
+
|
147
|
+
reply = Net::DNS::Packet.new_from_values(qname, qtype, qclass)
|
148
|
+
|
149
|
+
if (query.header.opcode == "QUERY")
|
150
|
+
if (query.header.qdcount == 1)
|
151
|
+
print "query ", query.header.id,
|
152
|
+
": (#{qname}, #{qclass}, #{qtype}) - " if @verbose
|
153
|
+
|
154
|
+
rcode, ans, auth, add, headermask = @replyhandler.call(qname, qclass, qtype, peerhost, query)
|
155
|
+
|
156
|
+
print "#{rcode}\n" if @verbose
|
157
|
+
|
158
|
+
reply.header.rcode=(rcode)
|
159
|
+
|
160
|
+
reply.push("answer", ans) if ans
|
161
|
+
reply.push("authority", auth) if auth
|
162
|
+
reply.push("additional", add) if add
|
163
|
+
else
|
164
|
+
print "ERROR: qdcount ", query.header.qdcount, "unsupported\n" if @verbose
|
165
|
+
reply.header.rcode=("FORMERR")
|
166
|
+
end
|
167
|
+
else
|
168
|
+
print "ERROR: opcode ", query.header.opcode, " unsupported\n" if @verbose
|
169
|
+
reply.header.rcode=("FORMERR")
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
if (!headermask)
|
175
|
+
reply.header.ra=(1)
|
176
|
+
reply.header.ad=(0)
|
177
|
+
else
|
178
|
+
reply.header.aa=(1) if headermask['aa']
|
179
|
+
reply.header.ra=(1) if headermask['ra']
|
180
|
+
reply.header.ad=(1) if headermask['ad']
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
reply.header.qr=(1)
|
185
|
+
reply.header.cd=(query.header.cd)
|
186
|
+
reply.header.rd=(query.header.rd)
|
187
|
+
reply.header.id=(query.header.id)
|
188
|
+
|
189
|
+
|
190
|
+
print reply.header.inspect if @verbose && headermask
|
191
|
+
|
192
|
+
return reply
|
193
|
+
end
|
194
|
+
|
195
|
+
#------------------------------------------------------------------------------
|
196
|
+
# readfromtcp - read from a TCP client
|
197
|
+
#------------------------------------------------------------------------------
|
198
|
+
|
199
|
+
def readfromtcp(sock)
|
200
|
+
return -1 unless @_tcp[sock]
|
201
|
+
peer = @_tcp[sock]["peer"]
|
202
|
+
# charsread = sock.sysread(@_tcp[sock]["inbuffer"], 16384)
|
203
|
+
@_tcp[sock]["inbuffer"] = sock.recv_nonblock(16384)
|
204
|
+
charsread = @_tcp[sock]["inbuffer"].length
|
205
|
+
@_tcp[sock]["timeout"] = Time.now()+120; # Reset idle timer
|
206
|
+
print "Received #{charsread} octets from #{peer}\n" if @verbose
|
207
|
+
if (charsread == 0) # 0 octets means socket has closed
|
208
|
+
print "Connection to #{peer} closed or lost.\n" if @verbose
|
209
|
+
@sockets.delete(sock)
|
210
|
+
sock.close()
|
211
|
+
@_tcp.delete(sock)
|
212
|
+
return charsread
|
213
|
+
end
|
214
|
+
return charsread
|
215
|
+
end
|
216
|
+
|
217
|
+
#------------------------------------------------------------------------------
|
218
|
+
# tcp_connection - Handle a TCP connection.
|
219
|
+
#------------------------------------------------------------------------------
|
220
|
+
|
221
|
+
def tcp_connection(sock)
|
222
|
+
# if (not @_tcp[sock])
|
223
|
+
if ((sock == @tcpserver)) # || (not @_tcp[sock]))
|
224
|
+
# We go here if we are called with a listener socket.
|
225
|
+
client = sock.accept_nonblock
|
226
|
+
if (!client)
|
227
|
+
print "TCP connection closed by peer before we could accept it.\n" if @verbose
|
228
|
+
return 0
|
229
|
+
end
|
230
|
+
peerport= client.addr[1]
|
231
|
+
peerhost = client.addr[3]
|
232
|
+
|
233
|
+
print "TCP connection from #{peerhost}:#{peerport}\n" if @verbose
|
234
|
+
# client.blocking=(0)
|
235
|
+
@_tcp[client]=Hash.new
|
236
|
+
@_tcp[client]["peer"] = "tcp:"+peerhost.inspect+":"+peerport.inspect
|
237
|
+
@_tcp[client]["state"] = STATE_ACCEPTED
|
238
|
+
@_tcp[client]["socket"] = client
|
239
|
+
@_tcp[client]["timeout"] = Time.now()+120
|
240
|
+
@_tcp[client]["outbuffer"] = ""
|
241
|
+
@sockets.push(client)
|
242
|
+
# After we accepted we will look at the socket again
|
243
|
+
# to see if there is any data there. ---Olaf
|
244
|
+
loop_once(0)
|
245
|
+
elsif @_tcp[sock]
|
246
|
+
# We go here if we are called with a client socket
|
247
|
+
peer = @_tcp[sock]["peer"]
|
248
|
+
|
249
|
+
if (@_tcp[sock]["state"] == STATE_ACCEPTED)
|
250
|
+
if (not @_tcp[sock]["inbuffer"].sub!(/^(..)/, ""))
|
251
|
+
return; # Still not 2 octets ready
|
252
|
+
end
|
253
|
+
msglen = $1.unpack("n")[0]
|
254
|
+
print "Removed 2 octets from the input buffer from #{peer}.\n" +
|
255
|
+
"#{peer} said his query contains #{msglen} octets.\n" if @verbose
|
256
|
+
@_tcp[sock]["state"] = STATE_GOT_LENGTH
|
257
|
+
@_tcp[sock]["querylength"] = msglen
|
258
|
+
end
|
259
|
+
# Not elsif, because we might already have all the data
|
260
|
+
if (@_tcp[sock]["state"] == STATE_GOT_LENGTH)
|
261
|
+
# return if not all data has been received yet.
|
262
|
+
return if @_tcp[sock]["querylength"] > @_tcp[sock]["inbuffer"].length
|
263
|
+
|
264
|
+
qbuf = @_tcp[sock]["inbuffer"][0, @_tcp[sock]["querylength"]]
|
265
|
+
# substr($self->{"_tcp"}{$sock}{"inbuffer"}, 0, $self->{"_tcp"}{$sock}{"querylength"}) = "";
|
266
|
+
@_tcp[sock]["inbuffer"][0, @_tcp[sock]["querylength"]]=""
|
267
|
+
query = Net::DNS::Packet.new_from_binary(qbuf)
|
268
|
+
reply = make_reply(query, sock.addr[3])
|
269
|
+
if (!reply)
|
270
|
+
print "I couldn't create a reply for #{peer}. Closing socket.\n" if @verbose
|
271
|
+
# @select.remove(sock)
|
272
|
+
@sockets.delete(sock)
|
273
|
+
sock.close()
|
274
|
+
@_tcp.delete(sock)
|
275
|
+
return
|
276
|
+
end
|
277
|
+
reply_data = reply.data
|
278
|
+
len = reply_data.length
|
279
|
+
@_tcp[sock]["outbuffer"] = [len].pack("n") + reply_data
|
280
|
+
print "Queued #{@_tcp[sock]['outbuffer'].length} octets to #{peer}.\n" if @verbose
|
281
|
+
# We are done.
|
282
|
+
@_tcp[sock]["state"] = STATE_SENDING
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
#------------------------------------------------------------------------------
|
288
|
+
# udp_connection - Handle a UDP connection.
|
289
|
+
#------------------------------------------------------------------------------
|
290
|
+
|
291
|
+
def udp_connection(sock)
|
292
|
+
buf, sender = sock.recvfrom(Net::DNS::PACKETSZ)
|
293
|
+
peerhost = sender[2]
|
294
|
+
peerport= sender[1]
|
295
|
+
|
296
|
+
print "UDP connection from #{peerhost}:#{peerport}\n" if @verbose
|
297
|
+
|
298
|
+
query = Net::DNS::Packet.new_from_binary(buf)
|
299
|
+
|
300
|
+
reply = make_reply(query, peerhost) || return
|
301
|
+
reply_data = reply.data
|
302
|
+
|
303
|
+
# local $| = 1 if @verbose
|
304
|
+
print "Writing response - " if @verbose
|
305
|
+
# die() ?!?? I think we need something better. --robert
|
306
|
+
sock.send(reply_data, 0, peerhost, peerport) or raise RuntimError, "send: #{$!}"
|
307
|
+
print "done\n" if @verbose
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
def get_open_tcp
|
312
|
+
return @_tcp.keys
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
#------------------------------------------------------------------------------
|
317
|
+
# loop_once - Just check "once" on sockets already set up
|
318
|
+
#------------------------------------------------------------------------------
|
319
|
+
|
320
|
+
# This function might not actually return immediately. If an AXFR request is
|
321
|
+
# coming in which will generate a huge reply, we will not relinquish control
|
322
|
+
# until our outbuffers are empty.
|
323
|
+
|
324
|
+
#
|
325
|
+
# NB this method may be subject to change and is therefore left 'undocumented'
|
326
|
+
#
|
327
|
+
|
328
|
+
def loop_once(timeout=0)
|
329
|
+
# print ";loop_once called with #{timeout} \n" if @verbose
|
330
|
+
@_tcp.keys.each do |sock|
|
331
|
+
timeout = 0.1 if @_tcp[sock]["outbuffer"]!=""
|
332
|
+
end
|
333
|
+
# ready = @select.can_read(timeout)
|
334
|
+
ret = IO::select(@sockets, nil, nil, timeout)
|
335
|
+
if (ret!=nil)
|
336
|
+
ready = ret[0]
|
337
|
+
print "ready : " + ready.inspect + "\n"
|
338
|
+
|
339
|
+
ready.each do |sock|
|
340
|
+
if (!(sock.is_a?UDPSocket))
|
341
|
+
|
342
|
+
self.readfromtcp(sock) &&
|
343
|
+
self.tcp_connection(sock)
|
344
|
+
else
|
345
|
+
self.udp_connection(sock)
|
346
|
+
# else
|
347
|
+
# print "ERROR: connection with unsupported protocol #{proto}\n" if @verbose
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
now = Time.now()
|
353
|
+
# Lets check if any of our TCP clients has pending actions.
|
354
|
+
# (outbuffer, timeout)
|
355
|
+
# @_tcp.keys.each do |s|
|
356
|
+
@_tcp.keys.each do |s|
|
357
|
+
sock = @_tcp[s]["socket"]
|
358
|
+
if (@_tcp[s]["outbuffer"].length>0)
|
359
|
+
# If we have buffered output, then send as much as the OS will accept
|
360
|
+
# and wait with the rest
|
361
|
+
len = @_tcp[s]["outbuffer"].length
|
362
|
+
# charssent = sock.syswrite(@_tcp[s]["outbuffer"]
|
363
|
+
charssent = sock.write_nonblock(@_tcp[s]["outbuffer"])
|
364
|
+
print "Sent #{charssent} of #{len} octets to " + @_tcp[s]["peer"] + ".\n" if @verbose
|
365
|
+
# substr($self->{"_tcp"}{$s}{"outbuffer"}, 0, charssent) = ""
|
366
|
+
@_tcp[s]["outbuffer"] [0, charssent] = ""
|
367
|
+
if (@_tcp[s]["outbuffer"].length == 0)
|
368
|
+
@_tcp[s]["outbuffer"] = ""
|
369
|
+
@_tcp[s]["state"] = STATE_ACCEPTED
|
370
|
+
if (@_tcp[s]["inbuffer"].length >= 2)
|
371
|
+
# See if the client has send us enough data to process the
|
372
|
+
# next query.
|
373
|
+
# We do this here, because we only want to process (and buffer!!)
|
374
|
+
# a single query at a time, per client. If we allowed a STATE_SENDING
|
375
|
+
# client to have new requests processed. We could be easilier
|
376
|
+
# victims of DoS (client sending lots of queries and never reading
|
377
|
+
# from it's socket).
|
378
|
+
# Note that this does not disable serialisation on part of the
|
379
|
+
# client. The split second it should take for us to lookip the
|
380
|
+
# next query, is likely faster than the time it takes to
|
381
|
+
# send the response... well, unless it's a lot of tiny queries,
|
382
|
+
# in which case we will be generating an entire TCP packet per
|
383
|
+
# reply. --robert
|
384
|
+
tcp_connection(@_tcp["socket"])
|
385
|
+
end
|
386
|
+
end
|
387
|
+
@_tcp[s]["timeout"] = Time.now()+120
|
388
|
+
else
|
389
|
+
# Get rid of idle clients.
|
390
|
+
timeout = @_tcp[s]["timeout"]
|
391
|
+
if (timeout - now < 0)
|
392
|
+
print @_tcp[s]["peer"]," has been idle for too long and will be disconnected.\n" if @verbose
|
393
|
+
# @select.remove(sock)
|
394
|
+
@sockets.delete(sock)
|
395
|
+
sock.close()
|
396
|
+
@_tcp.delete(s)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
#------------------------------------------------------------------------------
|
403
|
+
# main_loop - Main nameserver loop.
|
404
|
+
#------------------------------------------------------------------------------
|
405
|
+
|
406
|
+
def main_loop
|
407
|
+
while (true) do
|
408
|
+
print "Waiting for connections...\n" if @verbose
|
409
|
+
# You really need an argument otherwise you'll be burning
|
410
|
+
# CPU.
|
411
|
+
loop_once(10)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
#= NAME
|
420
|
+
#
|
421
|
+
#Net::DNS::Nameserver - DNS server class
|
422
|
+
#
|
423
|
+
#= SYNOPSIS
|
424
|
+
#
|
425
|
+
#require 'Net\DNS'
|
426
|
+
#
|
427
|
+
#= DESCRIPTION
|
428
|
+
#
|
429
|
+
#Instances of the Net::DNS::Nameserver class represent DNS server
|
430
|
+
#objects. See EXAMPLE for an example.
|
431
|
+
#
|
432
|
+
#= METHODS
|
433
|
+
#
|
434
|
+
#== new
|
435
|
+
#
|
436
|
+
# ns = Net::DNS::Nameserver.new({
|
437
|
+
# :localaddr => "10.1.2.3",
|
438
|
+
# :localport => 5353,
|
439
|
+
# :replyhandler => reply_handler_proc,
|
440
|
+
# :verbose => 1})
|
441
|
+
#
|
442
|
+
#
|
443
|
+
#
|
444
|
+
# ns = Net::DNS::Nameserver.new(
|
445
|
+
# :localaddr => ['::1' , '127.0.0.1' ],
|
446
|
+
# :localport => 5353,
|
447
|
+
# :replyhandler => reply_handler_proc,
|
448
|
+
# :verbose => 1
|
449
|
+
# })
|
450
|
+
#
|
451
|
+
#Creates a nameserver object. Attributes are:
|
452
|
+
#
|
453
|
+
# localaddr IP address on which to listen. Defaults to INADDR_ANY.
|
454
|
+
# localport Port on which to listen. Defaults to 53.
|
455
|
+
# replyhandler Reference to reply-handling
|
456
|
+
# subroutine Required.
|
457
|
+
# verbose Print info about received
|
458
|
+
# queries. Defaults to 0 (off).
|
459
|
+
#
|
460
|
+
#
|
461
|
+
#The LocalAddr attribute may alternatively be specified as a list of IP
|
462
|
+
#addresses to listen to.
|
463
|
+
#
|
464
|
+
#
|
465
|
+
#The ReplyHandler proc is passed the query name, query class,
|
466
|
+
#query type and optionally an argument containing header bit settings
|
467
|
+
#(see below). It must return the response code and references to the
|
468
|
+
#answer, authority, and additional sections of the response. Common
|
469
|
+
#response codes are:
|
470
|
+
#
|
471
|
+
# NOERROR No error
|
472
|
+
# FORMERR Format error
|
473
|
+
# SERVFAIL Server failure
|
474
|
+
# NXDOMAIN Non-existent domain (name doesn't exist)
|
475
|
+
# NOTIMP Not implemented
|
476
|
+
# REFUSED Query refused
|
477
|
+
#
|
478
|
+
#For advanced usage there is an optional argument containing an
|
479
|
+
#hashref with the settings for the aa, ra, and ad
|
480
|
+
#header bits. The argument is of the form
|
481
|
+
# { :ad => 1, :aa => 0, :ra => 1 }
|
482
|
+
#
|
483
|
+
#
|
484
|
+
#See RFC 1035 and the IANA dns-parameters file for more information:
|
485
|
+
#
|
486
|
+
# ftp://ftp.rfc-editor.org/in-notes/rfc1035.txt
|
487
|
+
# http://www.isi.edu/in-notes/iana/assignments/dns-parameters
|
488
|
+
#
|
489
|
+
#The nameserver will listen for both UDP and TCP connections. On
|
490
|
+
#Unix-like systems, the program will probably have to run as root
|
491
|
+
#to listen on the default port, 53. A non-privileged user should
|
492
|
+
#be able to listen on ports 1024 and higher.
|
493
|
+
#
|
494
|
+
#Returns a Net::DNS::Nameserver object, or undef if the object
|
495
|
+
#couldn't be created.
|
496
|
+
#
|
497
|
+
#See EXAMPLE for an example.
|
498
|
+
#
|
499
|
+
#== main_loop
|
500
|
+
#
|
501
|
+
# ns.main_loop
|
502
|
+
#
|
503
|
+
#Start accepting queries. Calling main_loop never returns.
|
504
|
+
#
|
505
|
+
#== loop_once
|
506
|
+
#
|
507
|
+
# ns.loop_once( [TIMEOUT_IN_SECONDS] )
|
508
|
+
#
|
509
|
+
#Start accepting queries, but returns. If called without a parameter,
|
510
|
+
#the call will not return until a request has been received (and
|
511
|
+
#replied to). If called with a number, that number specifies how many
|
512
|
+
#seconds (even fractional) to maximum wait before returning. If called
|
513
|
+
#with 0 it will return immediately unless there's something to do.
|
514
|
+
#
|
515
|
+
#Handling a request and replying obviously depends on the speed of
|
516
|
+
#ReplyHandler. Assuming ReplyHandler is super fast, loop_once should spend
|
517
|
+
#just a fraction of a second, if called with a timeout value of 0 seconds.
|
518
|
+
#One exception is when an AXFR has requested a huge amount of data that
|
519
|
+
#the OS is not ready to receive in full. In that case, it will keep
|
520
|
+
#running through a loop (while servicing new requests) until the reply
|
521
|
+
#has been sent.
|
522
|
+
#
|
523
|
+
#In case loop_once accepted a TCP connection it will immediatly check
|
524
|
+
#if there is data to be read from the socket. If not it will return and
|
525
|
+
#you will have to call loop_once() again to check if there is any data
|
526
|
+
#waiting on the socket to be processed. In most cases you will have to
|
527
|
+
#count on calling "loop_once" twice.
|
528
|
+
#
|
529
|
+
#A code fragment like:
|
530
|
+
# ns.loop_once(10)
|
531
|
+
# while( ns.get_open_tcp.length > 0 ) do
|
532
|
+
# ns.loop_once(0)
|
533
|
+
# end
|
534
|
+
#
|
535
|
+
#Would wait for 10 seconds for the initial connection and would then
|
536
|
+
#process all TCP sockets until none is left.
|
537
|
+
#
|
538
|
+
#== get_open_tcp
|
539
|
+
#
|
540
|
+
# Returns IO::Socket objects, these could
|
541
|
+
#be useful for troubleshooting but be careful using them.
|
542
|
+
#
|
543
|
+
#= EXAMPLE
|
544
|
+
#
|
545
|
+
#The following example will listen on port 5353 and respond to all queries
|
546
|
+
#for A records with the IP address 10.1.2.3. All other queries will be
|
547
|
+
#answered with NXDOMAIN. Authority and additional sections are left empty.
|
548
|
+
#The peerhost variable catches the IP address of the peer host, so that
|
549
|
+
#additional filtering on its basis may be applied.
|
550
|
+
#
|
551
|
+
# require 'Net\DNS'
|
552
|
+
#
|
553
|
+
# def reply_handler(qname, qclass, qtype, peerhost)
|
554
|
+
# rcode="NOERROR"
|
555
|
+
# ans = []
|
556
|
+
# auth= []
|
557
|
+
# add = []
|
558
|
+
#
|
559
|
+
# if (qtype == "A" && qname == "foo.example.com" )
|
560
|
+
# ttl, rdata = 3600, "10.1.2.3"
|
561
|
+
# push ans, Net::DNS::RR.new("#{qname} #{ttl} #{qclass} #{qtype} #{rdata}")
|
562
|
+
# rcode = "NOERROR"
|
563
|
+
# elsif( qname eq "foo.example.com" )
|
564
|
+
# rcode = "NOERROR"
|
565
|
+
#
|
566
|
+
# else
|
567
|
+
# rcode = "NXDOMAIN"
|
568
|
+
# end
|
569
|
+
#
|
570
|
+
# # mark the answer as authoritive (by setting the 'aa' flag
|
571
|
+
# return (rcode, ans, auth, add, { :aa => 1 })
|
572
|
+
# end
|
573
|
+
#
|
574
|
+
# ns = Net::DNS::Nameserver.new({
|
575
|
+
# :localport => 5353,
|
576
|
+
# :replyhandler => proc {|qname, qclass, qtype, peerhost|, reply_handler(qname, qclass, qtype, peerhost)},
|
577
|
+
# :verbose => 1
|
578
|
+
# }) || die "couldn't create nameserver object\n"
|
579
|
+
#
|
580
|
+
# ns.main_loop
|
581
|
+
#
|
582
|
+
#= COPYRIGHT
|
583
|
+
#
|
584
|
+
#Copyright (c) 1997-2002 Michael Fuhr.
|
585
|
+
#
|
586
|
+
#Portions Copyright (c) 2002-2004 Chris Reinhardt.
|
587
|
+
#
|
588
|
+
#Portions Copyright (c) 2005 O.M, Kolkman, RIPE NCC.
|
589
|
+
#
|
590
|
+
#Portions Copyright (c) 2005 Robert Martin-Legene.
|
591
|
+
#
|
592
|
+
#Ruby version Copyright (C) 2006 AlexD (Nominet UK)
|
593
|
+
#
|
594
|
+
#All rights reserved. This program is free software; you may redistribute
|
595
|
+
#it and/or modify it under the same terms as Perl itself.
|
596
|
+
#
|
597
|
+
#= SEE ALSO
|
598
|
+
#
|
599
|
+
#Net::DNS, Net::DNS::Resolver, Net::DNS::Packet,
|
600
|
+
#Net::DNS::Update, Net::DNS::Header, Net::DNS::Question,
|
601
|
+
#Net::DNS::RR, RFC 1035
|