pNet-DNS 0.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.
- 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
|