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.
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