pNet-DNS 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/README +68 -0
  2. data/lib/Net/DNS.rb +879 -0
  3. data/lib/Net/DNS/Header.rb +303 -0
  4. data/lib/Net/DNS/Nameserver.rb +601 -0
  5. data/lib/Net/DNS/Packet.rb +851 -0
  6. data/lib/Net/DNS/Question.rb +117 -0
  7. data/lib/Net/DNS/RR.rb +630 -0
  8. data/lib/Net/DNS/RR/A.rb +103 -0
  9. data/lib/Net/DNS/RR/AAAA.rb +147 -0
  10. data/lib/Net/DNS/RR/AFSDB.rb +114 -0
  11. data/lib/Net/DNS/RR/CERT.rb +191 -0
  12. data/lib/Net/DNS/RR/CNAME.rb +89 -0
  13. data/lib/Net/DNS/RR/DNAME.rb +84 -0
  14. data/lib/Net/DNS/RR/EID.rb +70 -0
  15. data/lib/Net/DNS/RR/HINFO.rb +108 -0
  16. data/lib/Net/DNS/RR/ISDN.rb +118 -0
  17. data/lib/Net/DNS/RR/LOC.rb +341 -0
  18. data/lib/Net/DNS/RR/MB.rb +92 -0
  19. data/lib/Net/DNS/RR/MG.rb +96 -0
  20. data/lib/Net/DNS/RR/MINFO.rb +109 -0
  21. data/lib/Net/DNS/RR/MR.rb +92 -0
  22. data/lib/Net/DNS/RR/MX.rb +124 -0
  23. data/lib/Net/DNS/RR/NAPTR.rb +182 -0
  24. data/lib/Net/DNS/RR/NIMLOC.rb +70 -0
  25. data/lib/Net/DNS/RR/NS.rb +100 -0
  26. data/lib/Net/DNS/RR/NSAP.rb +273 -0
  27. data/lib/Net/DNS/RR/NULL.rb +68 -0
  28. data/lib/Net/DNS/RR/OPT.rb +251 -0
  29. data/lib/Net/DNS/RR/PTR.rb +93 -0
  30. data/lib/Net/DNS/RR/PX.rb +131 -0
  31. data/lib/Net/DNS/RR/RP.rb +108 -0
  32. data/lib/Net/DNS/RR/RT.rb +115 -0
  33. data/lib/Net/DNS/RR/SOA.rb +195 -0
  34. data/lib/Net/DNS/RR/SPF.rb +46 -0
  35. data/lib/Net/DNS/RR/SRV.rb +153 -0
  36. data/lib/Net/DNS/RR/SSHFP.rb +190 -0
  37. data/lib/Net/DNS/RR/TKEY.rb +219 -0
  38. data/lib/Net/DNS/RR/TSIG.rb +358 -0
  39. data/lib/Net/DNS/RR/TXT.rb +162 -0
  40. data/lib/Net/DNS/RR/UNKNOWN.rb +76 -0
  41. data/lib/Net/DNS/RR/X25.rb +90 -0
  42. data/lib/Net/DNS/Resolver.rb +2090 -0
  43. data/lib/Net/DNS/Resolver/Recurse.rb +478 -0
  44. data/lib/Net/DNS/Update.rb +189 -0
  45. data/test/custom.txt +4 -0
  46. data/test/resolv.conf +4 -0
  47. data/test/tc_escapedchars.rb +498 -0
  48. data/test/tc_header.rb +91 -0
  49. data/test/tc_inet6.rb +169 -0
  50. data/test/tc_misc.rb +137 -0
  51. data/test/tc_online.rb +236 -0
  52. data/test/tc_packet.rb +174 -0
  53. data/test/tc_packet_unique_push.rb +126 -0
  54. data/test/tc_question.rb +49 -0
  55. data/test/tc_recurse.rb +69 -0
  56. data/test/tc_res_env.rb +59 -0
  57. data/test/tc_res_file.rb +55 -0
  58. data/test/tc_res_opt.rb +135 -0
  59. data/test/tc_resolver.rb +102 -0
  60. data/test/tc_rr-opt.rb +40 -0
  61. data/test/tc_rr-rrsort.rb +116 -0
  62. data/test/tc_rr-txt.rb +138 -0
  63. data/test/tc_rr-unknown.rb +95 -0
  64. data/test/tc_rr.rb +246 -0
  65. data/test/tc_tcp.rb +34 -0
  66. data/test/tc_tkey.rb +115 -0
  67. data/test/tc_update.rb +226 -0
  68. data/test/ts_netdns.rb +17 -0
  69. data/test/ts_offline.rb +32 -0
  70. data/test/ts_online.rb +33 -0
  71. 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