dnsruby 1.37 → 1.38

Sign up to get free protection for your applications and to get access to all the features.
data/html/index.html DELETED
@@ -1,24 +0,0 @@
1
- <?xml version="1.0" encoding="iso-8859-1"?>
2
- <!DOCTYPE html
3
- PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
-
6
- <!--
7
-
8
- RDoc Documentation
9
-
10
- -->
11
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
- <head>
13
- <title>RDoc Documentation</title>
14
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
- </head>
16
- <frameset rows="20%, 80%">
17
- <frameset cols="25%,35%,45%">
18
- <frame src="fr_file_index.html" title="Files" name="Files" />
19
- <frame src="fr_class_index.html" name="Classes" />
20
- <frame src="fr_method_index.html" name="Methods" />
21
- </frameset>
22
- <frame src="classes/Dnsruby.html" name="docwin" />
23
- </frameset>
24
- </html>
@@ -1,6 +0,0 @@
1
- <%#
2
- # To change this template, choose Tools | Templates
3
- # and open the template in the editor.
4
- %>
5
-
6
- <%= "delete_me" %>
@@ -1,602 +0,0 @@
1
- #--
2
- #Copyright 2007 Nominet UK
3
- #
4
- #Licensed under the Apache License, Version 2.0 (the "License");
5
- #you may not use this file except in compliance with the License.
6
- #You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- #Unless required by applicable law or agreed to in writing, software
11
- #distributed under the License is distributed on an "AS IS" BASIS,
12
- #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- #See the License for the specific language governing permissions and
14
- #limitations under the License.
15
- #++
16
- require 'socket'
17
- #require 'thread'
18
- begin
19
- require 'fastthread'
20
- rescue LoadError
21
- require 'thread'
22
- end
23
- require 'singleton'
24
- require 'Dnsruby/validator_thread.rb'
25
- module Dnsruby
26
- Thread::abort_on_exception = true
27
- class SelectThread #:nodoc: all
28
- class SelectWakeup < RuntimeError; end
29
- include Singleton
30
- # This singleton class runs a continuous select loop which
31
- # listens for responses on all of the in-use sockets.
32
- # When a new query is sent, the thread is woken up, and
33
- # the socket is added to the select loop (and the new timeout
34
- # calculated).
35
- # Note that a combination of the socket and the packet ID is
36
- # sufficient to uniquely identify the query to the select thread.
37
- #
38
- # But how do we find the response queue for a particular query?
39
- # Hash of client_id->[query, client_queue, socket]
40
- # and socket->[client_id]
41
- #
42
- # @todo@ should we implement some of cancel function?
43
-
44
- def initialize
45
- @@mutex = Mutex.new
46
- @@mutex.synchronize {
47
- @@in_select=false
48
- # @@notifier,@@notified=IO.pipe
49
- @@sockets = [] # @@notified]
50
- @@timeouts = Hash.new
51
- # @@mutex.synchronize do
52
- @@query_hash = Hash.new
53
- @@socket_hash = Hash.new
54
- @@observers = Hash.new
55
- @@tick_observers = []
56
- @@queued_exceptions=[]
57
- @@queued_responses=[]
58
- @@queued_validation_responses=[]
59
- @@wakeup_sockets = Socket::socketpair(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
60
- # end
61
- # Now start the select thread
62
- @@select_thread = Thread.new {
63
- do_select
64
- }
65
- # # Start the validator thread
66
- # @@validator = ValidatorThread.instance
67
- }
68
- end
69
-
70
- class QuerySettings
71
- attr_accessor :query_bytes, :query, :ignore_truncation, :client_queue,
72
- :client_query_id, :socket, :dest_server, :dest_port, :endtime, :udp_packet_size,
73
- :single_resolver
74
- # new(query_bytes, query, ignore_truncation, client_queue, client_query_id,
75
- # socket, dest_server, dest_port, endtime, , udp_packet_size, single_resolver)
76
- def initialize(*args)
77
- @query_bytes = args[0]
78
- @query = args[1]
79
- @ignore_truncation=args[2]
80
- @client_queue = args[3]
81
- @client_query_id = args[4]
82
- @socket = args[5]
83
- @dest_server = args[6]
84
- @dest_port=args[7]
85
- @endtime = args[8]
86
- @udp_packet_size = args[9]
87
- @single_resolver = args[10]
88
- end
89
- end
90
-
91
- def add_to_select(query_settings)
92
- # Add the query to sockets, and then wake the select thread up
93
- @@mutex.synchronize {
94
- check_select_thread_synchronized
95
- # @TODO@ This assumes that all client_query_ids are unique!
96
- # Would be a good idea at least to check this...
97
- @@query_hash[query_settings.client_query_id]=query_settings
98
- @@socket_hash[query_settings.socket]=[query_settings.client_query_id] # @todo@ If we use persistent sockets then we need to update this array
99
- @@timeouts[query_settings.client_query_id]=query_settings.endtime
100
- @@sockets.push(query_settings.socket)
101
- }
102
- @@wakeup_sockets[0].send("wakeup!", 0)
103
- end
104
-
105
- def check_select_thread_synchronized
106
- if (!@@select_thread.alive?)
107
- Dnsruby.log.debug{"Restarting select thread"}
108
- @@select_thread = Thread.new {
109
- do_select
110
- }
111
- end
112
- end
113
-
114
- def select_thread_alive?
115
- ret=true
116
- @@mutex.synchronize{
117
- ret = @@select_thread.alive?
118
- }
119
- return ret
120
- end
121
-
122
- def do_select
123
- unused_loop_count = 0
124
- last_tick_time = Time.now - 10
125
- while true do
126
- if (last_tick_time < (Time.now - 0.5))
127
- send_tick_to_observers # ONLY NEED TO SEND THIS TWICE A SECOND - NOT EVERY SELECT!!!
128
- last_tick_time = Time.now
129
- end
130
- send_queued_exceptions
131
- send_queued_responses
132
- send_queued_validation_responses
133
- timeout = tick_time = 0.1 # We provide a timer service to various Dnsruby classes
134
- sockets=[]
135
- timeouts=[]
136
- has_observer = false
137
- @@mutex.synchronize {
138
- sockets = @@sockets
139
- timeouts = @@timeouts.values
140
- has_observer = !@@observers.empty?
141
- }
142
- sockets << @@wakeup_sockets[1]
143
- if (timeouts.length > 0)
144
- timeouts.sort!
145
- timeout = timeouts[0] - Time.now
146
- if (timeout <= 0)
147
- process_timeouts
148
- timeout = 0
149
- next
150
- end
151
- end
152
- ready=nil
153
- if (has_observer && (timeout > tick_time))
154
- timeout = tick_time
155
- end
156
- # next if (timeout < 0)
157
- begin
158
- ready, write, errors = IO.select(sockets, nil, nil, timeout)
159
- rescue SelectWakeup
160
- # If SelectWakeup, then just restart this loop - the select call will be made with the new data
161
- next
162
- end
163
- if ready && ready.include?(@@wakeup_sockets[1])
164
- ready.delete(@@wakeup_sockets[1])
165
- wakeup_msg = @@wakeup_sockets[1].recv(20)
166
- end
167
- if (ready == nil)
168
- # proces the timeouts
169
- process_timeouts
170
- unused_loop_count+=1
171
- else
172
- process_ready(ready)
173
- unused_loop_count=0
174
- # process_error(errors)
175
- end
176
- @@mutex.synchronize{
177
- if (unused_loop_count > 10 && @@query_hash.empty? && @@observers.empty?)
178
- Dnsruby.log.debug{"Stopping select loop"}
179
- return
180
- end
181
- }
182
- # }
183
- end
184
- end
185
-
186
- def process_error(errors)
187
- Dnsruby.log.debug{"Error! #{errors.inspect}"}
188
- # @todo@ Process errors [can we do this in single socket environment?]
189
- end
190
-
191
- # @@query_hash[query_settings.client_query_id]=query_settings
192
- # @@socket_hash[query_settings.socket]=[query_settings.client_query_id] # @todo@ If we use persistent sockets then we need to update this array
193
- def process_ready(ready)
194
- ready.each do |socket|
195
- query_settings = nil
196
- @@mutex.synchronize{
197
- # Can do this if we have a query per socket, but not otherwise...
198
- c_q_id = @@socket_hash[socket][0] # @todo@ If we use persistent sockets then this won't work
199
- query_settings = @@query_hash[c_q_id]
200
- }
201
- next if !query_settings
202
- udp_packet_size = query_settings.udp_packet_size
203
- msg, bytes = get_incoming_data(socket, udp_packet_size)
204
- if (msg!=nil)
205
- # Check that the IP we received from was the IP we sent to!
206
- answerip = msg.answerip.downcase
207
- answerfrom = msg.answerfrom.downcase
208
- dest_server = query_settings.dest_server
209
- if (dest_server && (dest_server != '0.0.0.0') &&
210
- (answerip != query_settings.dest_server.downcase) &&
211
- (answerfrom != query_settings.dest_server.downcase))
212
- Dnsruby.log.warn("Unsolicited response received from #{answerip} instead of #{query_settings.dest_server}")
213
- else
214
- send_response_to_client(msg, bytes, socket)
215
- end
216
- end
217
- ready.delete(socket)
218
- end
219
- end
220
-
221
- def send_response_to_client(msg, bytes, socket)
222
- # Figure out which client_ids we were expecting on this socket, then see if any header ids match up
223
- # @TODO@ Can get rid of this, as we only have one query per socket.
224
- client_ids=[]
225
- @@mutex.synchronize{
226
- client_ids = @@socket_hash[socket]
227
- }
228
- # get the queries associated with them
229
- client_ids.each do |id|
230
- query_header_id=nil
231
- @@mutex.synchronize{
232
- query_header_id = @@query_hash[id].query.header.id
233
- }
234
- if (query_header_id == msg.header.id)
235
- # process the response
236
- client_queue = nil
237
- res = nil
238
- query=nil
239
- @@mutex.synchronize{
240
- client_queue = @@query_hash[id].client_queue
241
- res = @@query_hash[id].single_resolver
242
- query = @@query_hash[id].query
243
- }
244
- tcp = (socket.class == TCPSocket)
245
- # At this point, we should check if the response is OK
246
- if (ret = res.check_response(msg, bytes, query, client_queue, id, tcp))
247
- remove_id(id)
248
- exception = msg.get_exception
249
- if (ret.instance_of?TsigError)
250
- exception = ret
251
- end
252
- Dnsruby.log.debug{"Pushing response to client queue"}
253
- push_to_client(id, client_queue, msg, exception, query, res)
254
- # client_queue.push([id, msg, exception])
255
- # notify_queue_observers(client_queue, id)
256
- else
257
- # Sending query again - don't return response
258
- end
259
- return
260
- end
261
- end
262
- # If not, then we have an error
263
- Dnsruby.log.error{"Stray packet - " + msg.inspect + "\n from " + socket.inspect}
264
- print("Stray packet - " + msg.question()[0].qname.to_s + " from " + msg.answerip.to_s + ", #{client_ids.length} client_ids\n")
265
- end
266
-
267
- def remove_id(id)
268
- socket=nil
269
- @@mutex.synchronize{
270
- socket = @@query_hash[id].socket
271
- @@timeouts.delete(id)
272
- @@query_hash.delete(id)
273
- @@socket_hash.delete(socket)
274
- @@sockets.delete(socket) # @TODO@ Not if persistent!
275
- }
276
- Dnsruby.log.debug{"Closing socket #{socket}"}
277
- socket.close # @TODO@ Not if persistent!
278
- end
279
-
280
- def process_timeouts
281
- time_now = Time.now
282
- timeouts={}
283
- @@mutex.synchronize {
284
- timeouts = @@timeouts
285
- }
286
- timeouts.each do |client_id, timeout|
287
- if (timeout < time_now)
288
- send_exception_to_client(ResolvTimeout.new("Query timed out"), nil, client_id)
289
- end
290
- end
291
- end
292
-
293
- def tcp_read(socket, len)
294
- buf=""
295
- while (buf.length < len) do
296
- input = socket.recv(len-buf.length)
297
- if (input=="")
298
- TheLog.info("Bad response from server - no bytes read - ignoring")
299
- return false
300
- end
301
- buf += input
302
- end
303
- return buf
304
- end
305
-
306
- def get_incoming_data(socket, packet_size)
307
- answerfrom,answerip,answerport,answersize=nil
308
- ans,buf = nil
309
- begin
310
- if (socket.class == TCPSocket)
311
- # @todo@ Ruby Bug #9061 stops this working right
312
- # We'd like to do a socket.recvfrom, but that raises an Exception
313
- # on Windows for TCPSocket for Ruby 1.8.5 (and 1.8.6).
314
- # So, we need to do something different for TCP than UDP. *sigh*
315
- # @TODO@ This workaround will only work if there is exactly one socket per query
316
- # - *not* ideal TCP use!
317
- @@mutex.synchronize{
318
- client_id = @@socket_hash[socket][0]
319
- answerfrom = @@query_hash[client_id].dest_server
320
- answerip = answerfrom
321
- answerport = @@query_hash[client_id].dest_port
322
- }
323
- buf = tcp_read(socket, 2)
324
- if (!buf)
325
- handle_recvfrom_failure(socket, "")
326
- return
327
- end
328
- answersize = buf.unpack('n')[0]
329
- buf = tcp_read(socket,answersize)
330
- if (!buf)
331
- handle_recvfrom_failure(socket, "")
332
- return
333
- end
334
- else
335
- if (ret = socket.recvfrom(packet_size))
336
- buf = ret[0]
337
- answerport=ret[1][1]
338
- answerfrom=ret[1][2]
339
- answerip=ret[1][3]
340
- answersize=(buf.length)
341
- else
342
- # recvfrom failed - why?
343
- Dnsruby.log.error{"Error - recvfrom failed from #{socket}"}
344
- handle_recvfrom_failure(socket, "")
345
- return
346
- end
347
- end
348
- rescue Exception => e
349
- Dnsruby.log.error{"Error - recvfrom failed from #{socket}, exception : #{e}"}
350
- handle_recvfrom_failure(socket, e)
351
- return
352
- end
353
- Dnsruby.log.debug{";; answer from #{answerfrom} : #{answersize} bytes\n"}
354
-
355
- begin
356
- ans = Message.decode(buf)
357
- rescue Exception => e
358
- # print "DECODE ERROR\n"
359
- Dnsruby.log.error{"Decode error! #{e.class}, #{e}\nfor msg (length=#{buf.length}) : #{buf}"}
360
- # @TODO@ Should know this from the socket!
361
- client_id=get_client_id_from_answerfrom(socket, answerip, answerport)
362
- if (client_id != nil)
363
- send_exception_to_client(e, socket, client_id)
364
- else
365
- Dnsruby.log.error{"Decode error from #{answerfrom} but can't determine packet id"}
366
- end
367
- return
368
- end
369
-
370
- if (ans!= nil)
371
- Dnsruby.log.debug{"#{ans}"}
372
- ans.answerfrom=(answerfrom)
373
- ans.answersize=(answersize)
374
- ans.answerip =(answerip)
375
- end
376
- return ans, buf
377
- end
378
-
379
- def handle_recvfrom_failure(socket, exception)
380
- # No way to notify the client about this error, unless there was only one connection on the socket
381
- # Not a problem, as there only will ever be one connection on the socket (Kaminsky attack mitigation)
382
- ids_for_socket = []
383
- @@mutex.synchronize{
384
- ids_for_socket = @@socket_hash[socket]
385
- }
386
- if (ids_for_socket.length == 1)
387
- answerfrom=nil
388
- @@mutex.synchronize{
389
- query_settings = @@query_hash[ids_for_socket[0]]
390
- answerfrom=query_settings.dest_server
391
- }
392
- send_exception_to_client(OtherResolvError.new("recvfrom failed from #{answerfrom}; #{exception}"), socket, ids_for_socket[0])
393
- else
394
- Dnsruby.log.fatal{"Recvfrom failed from #{socket}, no way to tell query id"}
395
- end
396
- end
397
-
398
- def get_client_id_from_answerfrom(socket, answerip, answerport)
399
- # @TODO@ Can get rid of this, as there is only one query per socket
400
- client_id=nil
401
- # Figure out client id from answerfrom
402
- @@mutex.synchronize{
403
- ids = @@socket_hash[socket]
404
- ids.each do |id|
405
- # Does this id speak to this dest_server?
406
- query_settings = @@query_hash[id]
407
- if (answerip == query_settings.dest_server && answerport == query_settings.dest_port)
408
- # We have a match
409
- # - @TODO@ as long as we're not speaking to the same server on two ports!
410
- client_id = id
411
- break
412
- end
413
- end
414
- }
415
- return client_id
416
- end
417
-
418
- def send_exception_to_client(err, socket, client_id, msg=nil)
419
- # find the client response queue
420
- client_queue = nil
421
- @@mutex.synchronize {
422
- client_queue = @@query_hash[client_id].client_queue
423
- }
424
- remove_id(client_id)
425
- # push_to_client(client_id, client_queue, msg, err)
426
- client_queue.push([client_id, Resolver::EventType::ERROR, msg, err])
427
- notify_queue_observers(client_queue, client_id)
428
- end
429
-
430
- def push_exception_to_select(client_id, client_queue, err, msg)
431
- @@mutex.synchronize{
432
- @@queued_exceptions.push([client_id, client_queue, err, msg])
433
- }
434
- # Make sure select loop is running!
435
- if (@@select_thread && @@select_thread.alive?)
436
- else
437
- @@select_thread = Thread.new {
438
- do_select
439
- }
440
- end
441
- end
442
-
443
- def push_response_to_select(client_id, client_queue, msg, query, res)
444
- # This needs to queue the response TO THE SELECT THREAD, which then needs
445
- # to send it out from its normal loop.
446
- Dnsruby.log.debug{"Pushing response to client queue direct from resolver or validator"}
447
- @@mutex.synchronize{
448
- @@queued_responses.push([client_id, client_queue, msg, nil, query, res])
449
- }
450
- # Make sure select loop is running!
451
- if (@@select_thread && @@select_thread.alive?)
452
- else
453
- @@select_thread = Thread.new {
454
- do_select
455
- }
456
- end
457
- end
458
-
459
- def push_validation_response_to_select(client_id, client_queue, msg, err, query, res)
460
- # This needs to queue the response TO THE SELECT THREAD, which then needs
461
- # to send it out from its normal loop.
462
- Dnsruby.log.debug{"Pushing response to client queue direct from resolver or validator"}
463
- @@mutex.synchronize{
464
- @@queued_validation_responses.push([client_id, client_queue, msg, err, query, res])
465
- }
466
- # Make sure select loop is running!
467
- if (@@select_thread && @@select_thread.alive?)
468
- else
469
- @@select_thread = Thread.new {
470
- do_select
471
- }
472
- end
473
- end
474
-
475
- def send_queued_exceptions
476
- exceptions = []
477
- @@mutex.synchronize{
478
- exceptions = @@queued_exceptions
479
- @@queued_exceptions = []
480
- }
481
-
482
- exceptions.each do |item|
483
- client_id, client_queue, err, msg = item
484
- # push_to_client(client_id, client_queue, msg, err)
485
- client_queue.push([client_id, Resolver::EventType::ERROR, msg, err])
486
- notify_queue_observers(client_queue, client_id)
487
- end
488
- end
489
-
490
- def send_queued_responses
491
- responses = []
492
- @@mutex.synchronize{
493
- responses = @@queued_responses
494
- @@queued_responses = []
495
- }
496
-
497
- responses.each do |item|
498
- client_id, client_queue, msg, err, query, res = item
499
- # push_to_client(client_id, client_queue, msg, err)
500
- client_queue.push([client_id, Resolver::EventType::RECEIVED, msg, err])
501
- notify_queue_observers(client_queue, client_id)
502
- # Do we need to validate this? The response has come from the cache -
503
- # validate it only if it has not been validated already
504
- # So, if we need to validate it, send it to the validation thread
505
- # Otherwise, send VALIDATED to the requester.
506
- if (((msg.security_level == Message::SecurityLevel::UNCHECKED) ||
507
- (msg.security_level == Message::SecurityLevel::INDETERMINATE)) &&
508
- (ValidatorThread.requires_validation?(query, msg, err, res)))
509
- validator = ValidatorThread.new(client_id, client_queue, msg, err, query ,self, res)
510
- validator.run
511
- else
512
- PacketSender.cache(query, msg) # The validator won't cache it, so we'd better do it now
513
- client_queue.push([client_id, Resolver::EventType::VALIDATED, msg, err])
514
- notify_queue_observers(client_queue, client_id)
515
- end
516
- end
517
- end
518
-
519
- def send_queued_validation_responses
520
- responses = []
521
- @@mutex.synchronize{
522
- responses = @@queued_validation_responses
523
- @@queued_validation_responses = []
524
- }
525
-
526
- responses.each do |item|
527
- client_id, client_queue, msg, err, query, res = item
528
- # push_to_client(client_id, client_queue, msg, err)
529
- client_queue.push([client_id, Resolver::EventType::VALIDATED, msg, err])
530
- notify_queue_observers(client_queue, client_id)
531
- end
532
- end
533
-
534
- def push_to_client(client_id, client_queue, msg, err, query, res)
535
- # @TODO@ Really need to let the client know that we have received a valid response!
536
- # Can do that by calling notify_observers here, but with an identifier which
537
- # defines the response to be a "Response received - validating. Please stop sending"
538
- # type of response.
539
- client_queue.push([client_id, Resolver::EventType::RECEIVED, msg, err])
540
- notify_queue_observers(client_queue, client_id)
541
-
542
- if (!err || (err.instance_of?(NXDomain)))
543
- #
544
- # This method now needs to push the response to the validator,
545
- # which will then take responsibility for delivering it to the client.
546
- # The validator will need access to the queue observers -
547
- validator = ValidatorThread.new(client_id, client_queue, msg, err, query ,self, res)
548
- validator.run
549
- # @@validator.add_to_queue([client_id, client_queue, msg, err, query, self, res])
550
- end
551
- end
552
-
553
- def add_observer(client_queue, observer)
554
- @@mutex.synchronize {
555
- @@observers[client_queue]=observer
556
- check_select_thread_synchronized # Is this really necessary? The client should start the thread by sending a query, really...
557
- if (!@@tick_observers.include?observer)
558
- @@tick_observers.push(observer)
559
- end
560
- }
561
- end
562
-
563
- def remove_observer(client_queue, observer)
564
- @@mutex.synchronize {
565
- if (@@observers[client_queue]==observer)
566
- # @@observers.delete(observer)
567
- @@observers.delete(client_queue)
568
- else
569
- if (@@observers[client_queue] == nil)
570
- end
571
- Dnsruby.log.error{"remove_observer called with wrong observer for queue"}
572
- raise ArgumentError.new("remove_observer called with wrong observer for queue")
573
- end
574
- if (!@@observers.values.include?observer)
575
- @@tick_observers.delete(observer)
576
- end
577
- }
578
- end
579
-
580
- def notify_queue_observers(client_queue, client_query_id)
581
- # If any observers are known for this query queue then notify them
582
- observer=nil
583
- @@mutex.synchronize {
584
- observer = @@observers[client_queue]
585
- }
586
- if (observer)
587
- observer.handle_queue_event(client_queue, client_query_id)
588
- end
589
- end
590
-
591
- def send_tick_to_observers
592
- # If any observers are known then send them a tick
593
- tick_observers=nil
594
- @@mutex.synchronize {
595
- tick_observers = @@tick_observers
596
- }
597
- tick_observers.each do |observer|
598
- observer.tick
599
- end
600
- end
601
- end
602
- end