rubydns 0.1.8 → 0.2.0

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.
@@ -0,0 +1,19 @@
1
+
2
+ require 'stringio'
3
+
4
+ class String
5
+ def hexdump
6
+ i = 1
7
+ out = StringIO.new
8
+
9
+ out.puts "Size: #{self.size}"
10
+ while (self.length > 16*(i-1))
11
+ a = self.slice(16*(i-1)..(16*i)-1)
12
+ out.printf("%06x: %4.4x %4.4x %4.4x %4.4x %4.4x %4.4x %4.4x %4.4x ", (i-1)*16, *a.unpack("n16"))
13
+ out.printf("|%s|\n", a.tr("^\040-\176","."))
14
+ i += 1
15
+ end
16
+
17
+ return out.string
18
+ end
19
+ end
@@ -0,0 +1,86 @@
1
+
2
+ require 'eventmachine'
3
+ require 'stringio'
4
+
5
+ require 'rubydns/extensions/hexdump'
6
+
7
+ module RubyDNS
8
+
9
+ UDP_TRUNCATION_SIZE = 512
10
+
11
+ module UDPHandler
12
+ def initialize(server)
13
+ @server = server
14
+ end
15
+
16
+ def self.process(server, data, &block)
17
+ server.logger.debug "Receiving incoming query..."
18
+
19
+ server.logger.debug data.hexdump
20
+
21
+ begin
22
+ server.receive_data(data) do |answer|
23
+ yield answer.encode
24
+ end
25
+ rescue
26
+ server.logger.error "Error processing request!"
27
+ server.logger.error "#{$!.class}: #{$!.message}"
28
+
29
+ $!.backtrace.each { |at| server.logger.error at }
30
+ end
31
+ end
32
+
33
+ def receive_data(data)
34
+ UDPHandler.process(@server, data) do |result|
35
+ self.send_data(result)
36
+ end
37
+ end
38
+ end
39
+
40
+ class LengthError < StandardError
41
+ end
42
+
43
+ module TCPHandler
44
+ def initialize(server)
45
+ @server = server
46
+ @buffer = nil
47
+ @length = nil
48
+ @processed = 0
49
+ end
50
+
51
+ def receive_data(data)
52
+ @buffer ||= StringIO.new
53
+ @buffer.write(data)
54
+
55
+ # Message includes a 16-bit length field
56
+ if @length == nil
57
+ if (@buffer.size - @processed) < 2
58
+ raise LengthError.new("Malformed message smaller than two bytes received")
59
+ end
60
+
61
+ @length = @buffer.string[@processed, 2].unpack('n')[0]
62
+ @processed += 2
63
+ end
64
+
65
+
66
+ if (@buffer.size - @processed) >= @length
67
+ data = @buffer.string[@processed, @length]
68
+
69
+ UDPHandler.process(@server, data) do |result|
70
+ self.send_data([result.size].pack('n'))
71
+ self.send_data(result)
72
+ end
73
+
74
+ @processed += @length
75
+ @length = nil
76
+ end
77
+ end
78
+
79
+ def unbind
80
+ if @processed != @buffer.size
81
+ raise LengthError.new("Unprocessed data remaining (#{@buffer.size - @processed} bytes unprocessed)")
82
+ end
83
+ end
84
+ end
85
+
86
+ end
@@ -31,6 +31,7 @@ module RubyDNS
31
31
  # end
32
32
  #
33
33
  def initialize(&block)
34
+ @events = {}
34
35
  @rules = []
35
36
  @otherwise = nil
36
37
 
@@ -68,6 +69,23 @@ module RubyDNS
68
69
  @rules << [pattern, Proc.new(&block)]
69
70
  end
70
71
 
72
+ # Register a named event which may be invoked later using #fire
73
+ # on(:start) do |server|
74
+ # RExec.change_user(RUN_AS)
75
+ # end
76
+ def on(event_name, &block)
77
+ @events[event_name] = Proc.new(&block)
78
+ end
79
+
80
+ # Fire the named event, which must have been registered using on.
81
+ def fire(event_name)
82
+ callback = @events[event_name]
83
+
84
+ if callback
85
+ callback.call(self)
86
+ end
87
+ end
88
+
71
89
  # Specify a default block to execute if all other rules fail to match.
72
90
  # This block is typially used to pass the request on to another server
73
91
  # (i.e. recursive request).
@@ -138,7 +156,7 @@ module RubyDNS
138
156
 
139
157
  # Process an incoming DNS message. Returns a serialized message to be
140
158
  # sent back to the client.
141
- def receive_data(data)
159
+ def receive_data(data, &block)
142
160
  query = Resolv::DNS::Message::decode(data)
143
161
 
144
162
  # Setup answer
@@ -164,7 +182,11 @@ module RubyDNS
164
182
  end
165
183
  end
166
184
 
167
- return answer.encode
185
+ if block_given?
186
+ yield answer
187
+ else
188
+ return answer
189
+ end
168
190
  end
169
191
  end
170
192
  end
@@ -44,7 +44,7 @@ module RubyDNS
44
44
  @question = question
45
45
  @resource_class = resource_class
46
46
  @answer = answer
47
-
47
+
48
48
  @question_appended = false
49
49
  end
50
50
 
@@ -16,8 +16,8 @@
16
16
  module RubyDNS
17
17
  module VERSION #:nodoc:
18
18
  MAJOR = 0
19
- MINOR = 1
20
- TINY = 8
19
+ MINOR = 2
20
+ TINY = 0
21
21
 
22
22
  STRING = [MAJOR, MINOR, TINY].join('.')
23
23
  end
data/lib/rubydns.rb CHANGED
@@ -25,6 +25,8 @@ require 'logger'
25
25
  require 'rexec'
26
26
  require 'rexec/daemon'
27
27
 
28
+ require 'rubydns/handler'
29
+
28
30
  module RubyDNS
29
31
 
30
32
  # Run a server with the given rules. A number of options can be supplied:
@@ -51,58 +53,26 @@ module RubyDNS
51
53
  #
52
54
  def self.run_server (options = {}, &block)
53
55
  server = RubyDNS::Server.new(&block)
54
- threads = ThreadGroup.new
55
-
56
56
  server.logger.info "Starting server..."
57
-
58
- options[:listen] ||= [[:udp, "0.0.0.0", 53]]
59
-
60
- sockets = []
61
57
 
62
- # Setup server sockets
63
- options[:listen].each do |spec|
64
- if spec.kind_of?(BasicSocket)
65
- sockets << spec
66
- elsif spec[0] == :udp
67
- socket = UDPSocket.new
68
- socket.bind(spec[1], spec[2])
69
-
70
- sockets << socket
71
- elsif spec[0] == :tcp
72
- server.logger.warn "Sorry, TCP is not currently supported!"
73
- end
74
- end
58
+ options[:listen] ||= [[:udp, "0.0.0.0", 53], [:tcp, "0.0.0.0", 53]]
75
59
 
76
- begin
77
- # Listen for incoming packets
78
- while true
79
- ready = IO.select(sockets)
80
-
81
- ready[0].each do |socket|
82
- packet, sender = socket.recvfrom(1024*5)
83
- server.logger.debug "Receiving incoming query..."
84
-
85
- thr = Thread.new do
86
- begin
87
- result = server.receive_data(packet)
88
-
89
- server.logger.debug "Sending result to #{sender.inspect}:"
90
- server.logger.debug "#{result.inspect}"
91
- socket.send(result, 0, sender[2], sender[1])
92
- rescue
93
- server.logger.error "Error processing request!"
94
- server.logger.error "#{$!.class}: #{$!.message}"
95
- $!.backtrace.each { |at| server.logger.error at }
96
- end
97
- end
98
-
99
- threads.add thr
60
+ EventMachine.run do
61
+ server.fire(:setup)
62
+
63
+ # Setup server sockets
64
+ options[:listen].each do |spec|
65
+ if spec[0] == :udp
66
+ EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, server)
67
+ elsif spec[0] == :tcp
68
+ EventMachine.start_server(spec[1], spec[2], TCPHandler, server)
100
69
  end
101
70
  end
102
- rescue Interrupt
103
- server.logger.info "Server interrupted - stopping #{threads.list.size} request(s)."
104
- threads.list.each { |thr| thr.join }
71
+
72
+ server.fire(:start)
105
73
  end
74
+
75
+ server.fire(:stop)
106
76
  end
107
77
  end
108
78
 
data/test/daemon2.rb CHANGED
@@ -36,46 +36,45 @@ require 'rubydns'
36
36
  # You might need to change the user name "daemon". This can be a user name or a user id.
37
37
  RUN_AS = "daemon"
38
38
 
39
- # UDP Socket does per packet reverse lookups unless this is set.
40
- UDPSocket.do_not_reverse_lookup = true
39
+ INTERFACES = [
40
+ [:udp, "0.0.0.0", 53],
41
+ [:tcp, "0.0.0.0", 53]
42
+ ]
41
43
 
42
44
  # We need to be root in order to bind to privileged port
43
45
  if RExec.current_user != "root"
44
- $stderr.puts "Sorry, this command needs to be run as root!"
45
- exit 1
46
+ $stderr.puts "Sorry, this command needs to be run as root!"
47
+ exit 1
46
48
  end
47
49
 
48
50
  # The Daemon itself
49
51
  class Server < RExec::Daemon::Base
50
- @@var_directory = File.dirname(__FILE__)
51
-
52
- def self.run
53
- # Bind to port 53 (UDP)
54
- socket = UDPSocket.new
55
- socket.bind("0.0.0.0", 53)
56
-
57
- # Drop priviledges
58
- RExec.change_user(RUN_AS)
59
-
60
- # Don't buffer output (for debug purposes)
61
- $stderr.sync = true
62
-
63
- # Use upstream DNS for name resolution (These ones are Orcon DNS in NZ)
64
- $R = Resolv::DNS.new(:nameserver => ["60.234.1.1", "60.234.2.2"])
65
-
66
- # Start the RubyDNS server
67
- RubyDNS::run_server(:listen => [socket]) do
68
- match("test.mydomain.org", :A) do |transaction|
69
- transaction.respond!("10.0.0.80")
70
- end
71
-
72
- # Default DNS handler
73
- otherwise do |transaction|
74
- logger.info "Passthrough: #{transaction}"
75
- transaction.passthrough!($R)
76
- end
77
- end
78
- end
52
+ @@var_directory = File.dirname(__FILE__)
53
+
54
+ def self.run
55
+ # Don't buffer output (for debug purposes)
56
+ $stderr.sync = true
57
+
58
+ # Use upstream DNS for name resolution (These ones are Orcon DNS in NZ)
59
+ $R = Resolv::DNS.new(:nameserver => ["60.234.1.1", "60.234.2.2"])
60
+
61
+ # Start the RubyDNS server
62
+ RubyDNS::run_server(:listen => INTERFACES) do
63
+ on(:start) do
64
+ RExec.change_user(RUN_AS)
65
+ end
66
+
67
+ match("test.mydomain.org", :A) do |transaction|
68
+ transaction.respond!("10.0.0.80")
69
+ end
70
+
71
+ # Default DNS handler
72
+ otherwise do |transaction|
73
+ logger.info "Passthrough: #{transaction}"
74
+ transaction.passthrough!($R)
75
+ end
76
+ end
77
+ end
79
78
  end
80
79
 
81
80
  # RExec daemon runner