rubydns 0.6.7 → 0.7.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -4
- data/.yardopts +1 -0
- data/README.md +98 -92
- data/lib/rubydns.rb +5 -47
- data/lib/rubydns/{extensions/string-1.9.3.rb → binary_string.rb} +2 -0
- data/lib/rubydns/extensions/logger.rb +1 -1
- data/lib/rubydns/extensions/resolv.rb +1 -5
- data/lib/rubydns/extensions/string.rb +1 -0
- data/lib/rubydns/handler.rb +7 -2
- data/lib/rubydns/message.rb +17 -4
- data/lib/rubydns/resolver.rb +3 -0
- data/lib/rubydns/server.rb +182 -93
- data/lib/rubydns/transaction.rb +110 -149
- data/lib/rubydns/version.rb +1 -1
- data/rubydns.gemspec +1 -1
- data/test/examples/dropping-dns.rb +2 -2
- data/test/examples/fortune-dns.rb +2 -2
- data/test/examples/soa-dns.rb +1 -1
- data/test/examples/wikipedia-dns.rb +2 -2
- data/test/test_daemon.rb +3 -3
- data/test/test_message.rb +41 -0
- data/test/test_passthrough.rb +5 -1
- data/test/test_rules.rb +3 -3
- data/test/test_slow_server.rb +8 -6
- data/test/test_truncation.rb +3 -3
- metadata +10 -9
- data/lib/rubydns/extensions/hexdump.rb +0 -38
- data/lib/rubydns/extensions/string-1.8.rb +0 -35
- data/lib/rubydns/extensions/string-1.9.2.rb +0 -29
    
        data/lib/rubydns/resolver.rb
    CHANGED
    
    | @@ -19,6 +19,7 @@ | |
| 19 19 | 
             
            # THE SOFTWARE.
         | 
| 20 20 |  | 
| 21 21 | 
             
            require 'rubydns/message'
         | 
| 22 | 
            +
            require 'rubydns/binary_string'
         | 
| 22 23 |  | 
| 23 24 | 
             
            module RubyDNS
         | 
| 24 25 | 
             
            	class InvalidProtocolError < StandardError
         | 
| @@ -117,6 +118,7 @@ module RubyDNS | |
| 117 118 | 
             
            				try_next_server!
         | 
| 118 119 | 
             
            			end
         | 
| 119 120 |  | 
| 121 | 
            +
            			# Once either an exception or message is received, we update the status of this request.
         | 
| 120 122 | 
             
            			def process_response!(response)
         | 
| 121 123 | 
             
            				finish_request!
         | 
| 122 124 |  | 
| @@ -142,6 +144,7 @@ module RubyDNS | |
| 142 144 |  | 
| 143 145 | 
             
            			private
         | 
| 144 146 |  | 
| 147 | 
            +
            			# Closes any connections and cancels any timeout.
         | 
| 145 148 | 
             
            			def finish_request!
         | 
| 146 149 | 
             
            				cancel_timeout
         | 
| 147 150 |  | 
    
        data/lib/rubydns/server.rb
    CHANGED
    
    | @@ -18,21 +18,127 @@ | |
| 18 18 | 
             
            # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 19 19 | 
             
            # THE SOFTWARE.
         | 
| 20 20 |  | 
| 21 | 
            +
            require 'fiber'
         | 
| 22 | 
            +
             | 
| 21 23 | 
             
            require 'rubydns/transaction'
         | 
| 22 24 | 
             
            require 'rubydns/extensions/logger'
         | 
| 23 25 |  | 
| 24 26 | 
             
            module RubyDNS
         | 
| 25 27 |  | 
| 26 | 
            -
            	# This class provides the core of the DSL. It contains a list of rules which
         | 
| 27 | 
            -
            	# are used to match against incoming DNS questions. These rules are used to
         | 
| 28 | 
            -
            	# generate responses which are either DNS resource records or failures.
         | 
| 29 28 | 
             
            	class Server
         | 
| 29 | 
            +
            		# The default server interfaces
         | 
| 30 | 
            +
            		DEFAULT_INTERFACES = [[:udp, "0.0.0.0", 53], [:tcp, "0.0.0.0", 53]]
         | 
| 31 | 
            +
            		
         | 
| 32 | 
            +
            		# Instantiate a server with a block
         | 
| 33 | 
            +
            		#
         | 
| 34 | 
            +
            		#	server = Server.new do
         | 
| 35 | 
            +
            		#		match(/server.mydomain.com/, IN::A) do |transaction|
         | 
| 36 | 
            +
            		#			transaction.respond!("1.2.3.4")
         | 
| 37 | 
            +
            		#		end
         | 
| 38 | 
            +
            		#	end
         | 
| 39 | 
            +
            		#
         | 
| 40 | 
            +
            		def initialize
         | 
| 41 | 
            +
            			@logger = Logger.new($stderr)
         | 
| 42 | 
            +
            		end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            		attr_accessor :logger
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            		# Fire the named event as part of running the server.
         | 
| 47 | 
            +
            		def fire(event_name)
         | 
| 48 | 
            +
            		end
         | 
| 49 | 
            +
            		
         | 
| 50 | 
            +
            		# Give a name and a record type, try to match a rule and use it for processing the given arguments.
         | 
| 51 | 
            +
            		def process(name, resource_class, transaction)
         | 
| 52 | 
            +
            			raise NotImplementedError.new
         | 
| 53 | 
            +
            		end
         | 
| 54 | 
            +
            		
         | 
| 55 | 
            +
            		# Process a block with the current fiber. To resume processing from the block, call `fiber.resume`. You shouldn't call `fiber.resume` until after the top level block has returned.
         | 
| 56 | 
            +
            		def defer(&block)
         | 
| 57 | 
            +
            			fiber = Fiber.current
         | 
| 58 | 
            +
            			
         | 
| 59 | 
            +
            			yield(fiber)
         | 
| 60 | 
            +
            			
         | 
| 61 | 
            +
            			Fiber.yield
         | 
| 62 | 
            +
            		end
         | 
| 63 | 
            +
            		
         | 
| 64 | 
            +
            		# Process an incoming DNS message. Returns a serialized message to be sent back to the client.
         | 
| 65 | 
            +
            		def process_query(query, options = {}, &block)
         | 
| 66 | 
            +
            			# Setup answer
         | 
| 67 | 
            +
            			answer = Resolv::DNS::Message::new(query.id)
         | 
| 68 | 
            +
            			answer.qr = 1                 # 0 = Query, 1 = Response
         | 
| 69 | 
            +
            			answer.opcode = query.opcode  # Type of Query; copy from query
         | 
| 70 | 
            +
            			answer.aa = 1                 # Is this an authoritative response: 0 = No, 1 = Yes
         | 
| 71 | 
            +
            			answer.rd = query.rd          # Is Recursion Desired, copied from query
         | 
| 72 | 
            +
            			answer.ra = 0                 # Does name server support recursion: 0 = No, 1 = Yes
         | 
| 73 | 
            +
            			answer.rcode = 0              # Response code: 0 = No errors
         | 
| 74 | 
            +
            			
         | 
| 75 | 
            +
            			Fiber.new do
         | 
| 76 | 
            +
            				transaction = nil
         | 
| 77 | 
            +
            				
         | 
| 78 | 
            +
            				begin
         | 
| 79 | 
            +
            					query.question.each do |question, resource_class|
         | 
| 80 | 
            +
            						@logger.debug "Processing question #{question} #{resource_class}..."
         | 
| 81 | 
            +
            				
         | 
| 82 | 
            +
            						transaction = Transaction.new(self, query, question, resource_class, answer, options)
         | 
| 83 | 
            +
            						
         | 
| 84 | 
            +
            						transaction.process
         | 
| 85 | 
            +
            					end
         | 
| 86 | 
            +
            				rescue
         | 
| 87 | 
            +
            					@logger.error "Exception thrown while processing #{transaction}!"
         | 
| 88 | 
            +
            					RubyDNS.log_exception(@logger, $!)
         | 
| 89 | 
            +
            				
         | 
| 90 | 
            +
            					answer.rcode = Resolv::DNS::RCode::ServFail
         | 
| 91 | 
            +
            				end
         | 
| 92 | 
            +
            			
         | 
| 93 | 
            +
            				yield answer
         | 
| 94 | 
            +
            			end.resume
         | 
| 95 | 
            +
            		end
         | 
| 96 | 
            +
            		
         | 
| 97 | 
            +
            		#
         | 
| 98 | 
            +
            		# By default the server runs on port 53, both TCP and UDP, which is usually a priviledged port and requires root access to bind. You can change this by specifying `options[:listen]` which should contain an array of `[protocol, interface address, port]` specifications.
         | 
| 99 | 
            +
            		# 
         | 
| 100 | 
            +
            		#	INTERFACES = [[:udp, "0.0.0.0", 5300]]
         | 
| 101 | 
            +
            		#	RubyDNS::run_server(:listen => INTERFACES) do
         | 
| 102 | 
            +
            		#		...
         | 
| 103 | 
            +
            		#	end
         | 
| 104 | 
            +
            		#
         | 
| 105 | 
            +
            		# You can specify already connected sockets if need be:
         | 
| 106 | 
            +
            		#
         | 
| 107 | 
            +
            		#   socket = UDPSocket.new; socket.bind("0.0.0.0", 53)
         | 
| 108 | 
            +
            		#   Process::Sys.setuid(server_uid)
         | 
| 109 | 
            +
            		#   INTERFACES = [socket]
         | 
| 110 | 
            +
            		#
         | 
| 111 | 
            +
            		def run(options = {})
         | 
| 112 | 
            +
            			@logger.info "Starting RubyDNS server (v#{RubyDNS::VERSION})..."
         | 
| 113 | 
            +
            		
         | 
| 114 | 
            +
            			interfaces = options[:listen] || DEFAULT_INTERFACES
         | 
| 115 | 
            +
            		
         | 
| 116 | 
            +
            			fire(:setup)
         | 
| 117 | 
            +
            		
         | 
| 118 | 
            +
            			# Setup server sockets
         | 
| 119 | 
            +
            			interfaces.each do |spec|
         | 
| 120 | 
            +
            				@logger.info "Listening on #{spec.join(':')}"
         | 
| 121 | 
            +
            				if spec[0] == :udp
         | 
| 122 | 
            +
            					EventMachine.open_datagram_socket(spec[1], spec[2], UDPHandler, self)
         | 
| 123 | 
            +
            				elsif spec[0] == :tcp
         | 
| 124 | 
            +
            					EventMachine.start_server(spec[1], spec[2], TCPHandler, self)
         | 
| 125 | 
            +
            				end
         | 
| 126 | 
            +
            			end
         | 
| 127 | 
            +
            		
         | 
| 128 | 
            +
            			fire(:start)
         | 
| 129 | 
            +
            		end
         | 
| 130 | 
            +
            	end
         | 
| 131 | 
            +
            	
         | 
| 132 | 
            +
            	# Provides the core of the RubyDNS domain-specific language (DSL). It contains a list of rules which are used to match against incoming DNS questions. These rules are used to generate responses which are either DNS resource records or failures.
         | 
| 133 | 
            +
            	class RuleBasedServer < Server
         | 
| 134 | 
            +
            		# Represents a single rule in the server.
         | 
| 30 135 | 
             
            		class Rule
         | 
| 31 136 | 
             
            			def initialize(pattern, callback)
         | 
| 32 137 | 
             
            				@pattern = pattern
         | 
| 33 138 | 
             
            				@callback = callback
         | 
| 34 139 | 
             
            			end
         | 
| 35 140 |  | 
| 141 | 
            +
            			# Returns true if the name and resource_class are sufficient:
         | 
| 36 142 | 
             
            			def match(name, resource_class)
         | 
| 37 143 | 
             
            				# If the pattern doesn't specify any resource classes, we implicitly pass this test:
         | 
| 38 144 | 
             
            				return true if @pattern.size < 2
         | 
| @@ -45,6 +151,7 @@ module RubyDNS | |
| 45 151 | 
             
            				end
         | 
| 46 152 | 
             
            			end
         | 
| 47 153 |  | 
| 154 | 
            +
            			# Invoke the rule, if it matches the incoming request, it is evaluated and returns `true`, otherwise returns `false`.
         | 
| 48 155 | 
             
            			def call(server, name, resource_class, *args)
         | 
| 49 156 | 
             
            				unless match(name, resource_class)
         | 
| 50 157 | 
             
            					server.logger.debug "Resource class #{resource_class} failed to match #{@pattern[1].inspect}!"
         | 
| @@ -52,27 +159,38 @@ module RubyDNS | |
| 52 159 | 
             
            					return false
         | 
| 53 160 | 
             
            				end
         | 
| 54 161 |  | 
| 55 | 
            -
            				#  | 
| 162 | 
            +
            				# Does this rule match against the supplied name?
         | 
| 56 163 | 
             
            				case @pattern[0]
         | 
| 57 164 | 
             
            				when Regexp
         | 
| 58 165 | 
             
            					match_data = @pattern[0].match(name)
         | 
| 166 | 
            +
            					
         | 
| 59 167 | 
             
            					if match_data
         | 
| 60 168 | 
             
            						server.logger.debug "Regexp pattern matched with #{match_data.inspect}."
         | 
| 61 | 
            -
            						 | 
| 169 | 
            +
            						
         | 
| 170 | 
            +
            						@callback[*args, match_data]
         | 
| 171 | 
            +
            						
         | 
| 172 | 
            +
            						return true
         | 
| 62 173 | 
             
            					end
         | 
| 63 174 | 
             
            				when String
         | 
| 64 175 | 
             
            					if @pattern[0] == name
         | 
| 65 176 | 
             
            						server.logger.debug "String pattern matched."
         | 
| 66 | 
            -
            						 | 
| 177 | 
            +
            						
         | 
| 178 | 
            +
            						@callback[*args]
         | 
| 179 | 
            +
            						
         | 
| 180 | 
            +
            						return true
         | 
| 67 181 | 
             
            					end
         | 
| 68 182 | 
             
            				else
         | 
| 69 183 | 
             
            					if (@pattern[0].call(name, resource_class) rescue false)
         | 
| 70 184 | 
             
            						server.logger.debug "Callable pattern matched."
         | 
| 71 | 
            -
            						 | 
| 185 | 
            +
            						
         | 
| 186 | 
            +
            						@callback[*args]
         | 
| 187 | 
            +
            						
         | 
| 188 | 
            +
            						return true
         | 
| 72 189 | 
             
            					end
         | 
| 73 190 | 
             
            				end
         | 
| 74 191 |  | 
| 75 192 | 
             
            				server.logger.debug "No pattern matched."
         | 
| 193 | 
            +
            				
         | 
| 76 194 | 
             
            				# We failed to match the pattern.
         | 
| 77 195 | 
             
            				return false
         | 
| 78 196 | 
             
            			end
         | 
| @@ -84,19 +202,19 @@ module RubyDNS | |
| 84 202 |  | 
| 85 203 | 
             
            		# Instantiate a server with a block
         | 
| 86 204 | 
             
            		#
         | 
| 87 | 
            -
            		# | 
| 88 | 
            -
            		# | 
| 89 | 
            -
            		# | 
| 90 | 
            -
            		# | 
| 91 | 
            -
            		# | 
| 205 | 
            +
            		#	server = Server.new do
         | 
| 206 | 
            +
            		#		match(/server.mydomain.com/, IN::A) do |transaction|
         | 
| 207 | 
            +
            		#			transaction.respond!("1.2.3.4")
         | 
| 208 | 
            +
            		#		end
         | 
| 209 | 
            +
            		#	end
         | 
| 92 210 | 
             
            		#
         | 
| 93 211 | 
             
            		def initialize(&block)
         | 
| 212 | 
            +
            			super()
         | 
| 213 | 
            +
            			
         | 
| 94 214 | 
             
            			@events = {}
         | 
| 95 215 | 
             
            			@rules = []
         | 
| 96 216 | 
             
            			@otherwise = nil
         | 
| 97 | 
            -
             | 
| 98 | 
            -
            			@logger = Logger.new($stderr)
         | 
| 99 | 
            -
             | 
| 217 | 
            +
            			
         | 
| 100 218 | 
             
            			if block_given?
         | 
| 101 219 | 
             
            				instance_eval &block
         | 
| 102 220 | 
             
            			end
         | 
| @@ -104,23 +222,21 @@ module RubyDNS | |
| 104 222 |  | 
| 105 223 | 
             
            		attr_accessor :logger
         | 
| 106 224 |  | 
| 107 | 
            -
            		# This function connects a pattern with a block. A pattern is either
         | 
| 108 | 
            -
            		# a String or a Regex instance. Optionally, a second argument can be
         | 
| 109 | 
            -
            		# provided which is either a String, Symbol or Array of resource record
         | 
| 110 | 
            -
            		# types which the rule matches against.
         | 
| 225 | 
            +
            		# This function connects a pattern with a block. A pattern is either a String or a Regex instance. Optionally, a second argument can be provided which is either a String, Symbol or Array of resource record types which the rule matches against.
         | 
| 111 226 | 
             
            		# 
         | 
| 112 | 
            -
            		# | 
| 113 | 
            -
            		# | 
| 114 | 
            -
            		# | 
| 227 | 
            +
            		#	match("www.google.com")
         | 
| 228 | 
            +
            		#	match("gmail.com", IN::MX)
         | 
| 229 | 
            +
            		#	match(/g?mail.(com|org|net)/, [IN::MX, IN::A])
         | 
| 115 230 | 
             
            		#
         | 
| 116 231 | 
             
            		def match(*pattern, &block)
         | 
| 117 232 | 
             
            			@rules << Rule.new(pattern, block)
         | 
| 118 233 | 
             
            		end
         | 
| 119 234 |  | 
| 120 235 | 
             
            		# Register a named event which may be invoked later using #fire
         | 
| 121 | 
            -
            		# | 
| 122 | 
            -
            		# | 
| 123 | 
            -
            		# | 
| 236 | 
            +
            		#
         | 
| 237 | 
            +
            		#	on(:start) do |server|
         | 
| 238 | 
            +
            		#		RExec.change_user(RUN_AS)
         | 
| 239 | 
            +
            		#	end
         | 
| 124 240 | 
             
            		def on(event_name, &block)
         | 
| 125 241 | 
             
            			@events[event_name] = block
         | 
| 126 242 | 
             
            		end
         | 
| @@ -134,48 +250,51 @@ module RubyDNS | |
| 134 250 | 
             
            			end
         | 
| 135 251 | 
             
            		end
         | 
| 136 252 |  | 
| 137 | 
            -
            		# Specify a default block to execute if all other rules fail to match.
         | 
| 138 | 
            -
            		# This block is typially used to pass the request on to another server
         | 
| 139 | 
            -
            		# (i.e. recursive request).
         | 
| 253 | 
            +
            		# Specify a default block to execute if all other rules fail to match. This block is typially used to pass the request on to another server (i.e. recursive request).
         | 
| 140 254 | 
             
            		#
         | 
| 141 | 
            -
            		# | 
| 142 | 
            -
            		# | 
| 143 | 
            -
            		# | 
| 255 | 
            +
            		#	otherwise do |transaction|
         | 
| 256 | 
            +
            		#		transaction.passthrough!($R)
         | 
| 257 | 
            +
            		#	end
         | 
| 144 258 | 
             
            		#
         | 
| 145 259 | 
             
            		def otherwise(&block)
         | 
| 146 260 | 
             
            			@otherwise = block
         | 
| 147 261 | 
             
            		end
         | 
| 148 | 
            -
             | 
| 262 | 
            +
            		
         | 
| 263 | 
            +
            		# If you match a rule, but decide within the rule that it isn't the correct one to use, you can call `next!` to evaluate the next rule - in other words, to continue falling down through the list of rules.
         | 
| 149 264 | 
             
            		def next!
         | 
| 150 265 | 
             
            			throw :next
         | 
| 151 266 | 
             
            		end
         | 
| 152 | 
            -
             | 
| 153 | 
            -
            		# Give a name and a record type, try to match a rule and use it for
         | 
| 154 | 
            -
            		# processing the given arguments.
         | 
| 155 | 
            -
            		#
         | 
| 156 | 
            -
            		# If a rule returns false, it is considered that the rule failed and
         | 
| 157 | 
            -
            		# futher matching is carried out.
         | 
| 267 | 
            +
            		
         | 
| 268 | 
            +
            		# Give a name and a record type, try to match a rule and use it for processing the given arguments.
         | 
| 158 269 | 
             
            		def process(name, resource_class, *args)
         | 
| 159 270 | 
             
            			@logger.debug "Searching for #{name} #{resource_class.name}"
         | 
| 160 | 
            -
             | 
| 271 | 
            +
            			
         | 
| 161 272 | 
             
            			@rules.each do |rule|
         | 
| 162 273 | 
             
            				@logger.debug "Checking rule #{rule}..."
         | 
| 163 | 
            -
             | 
| 274 | 
            +
            				
         | 
| 164 275 | 
             
            				catch (:next) do
         | 
| 165 276 | 
             
            					# If the rule returns true, we assume that it was successful and no further rules need to be evaluated.
         | 
| 166 | 
            -
            					return  | 
| 277 | 
            +
            					return if rule.call(self, name, resource_class, *args)
         | 
| 167 278 | 
             
            				end
         | 
| 168 279 | 
             
            			end
         | 
| 169 | 
            -
             | 
| 280 | 
            +
            			
         | 
| 170 281 | 
             
            			if @otherwise
         | 
| 171 282 | 
             
            				@otherwise.call(*args)
         | 
| 172 283 | 
             
            			else
         | 
| 173 284 | 
             
            				@logger.warn "Failed to handle #{name} #{resource_class.name}!"
         | 
| 174 285 | 
             
            			end
         | 
| 175 286 | 
             
            		end
         | 
| 176 | 
            -
             | 
| 177 | 
            -
            		# Process  | 
| 178 | 
            -
            		 | 
| 287 | 
            +
            		
         | 
| 288 | 
            +
            		# Process a block with the current fiber. To resume processing from the block, call `fiber.resume`. You shouldn't call `fiber.resume` until after the top level block has returned.
         | 
| 289 | 
            +
            		def defer(&block)
         | 
| 290 | 
            +
            			fiber = Fiber.current
         | 
| 291 | 
            +
            			
         | 
| 292 | 
            +
            			yield(fiber)
         | 
| 293 | 
            +
            			
         | 
| 294 | 
            +
            			Fiber.yield
         | 
| 295 | 
            +
            		end
         | 
| 296 | 
            +
            		
         | 
| 297 | 
            +
            		# Process an incoming DNS message. Returns a serialized message to be sent back to the client.
         | 
| 179 298 | 
             
            		def process_query(query, options = {}, &block)
         | 
| 180 299 | 
             
            			# Setup answer
         | 
| 181 300 | 
             
            			answer = Resolv::DNS::Message::new(query.id)
         | 
| @@ -185,57 +304,27 @@ module RubyDNS | |
| 185 304 | 
             
            			answer.rd = query.rd          # Is Recursion Desired, copied from query
         | 
| 186 305 | 
             
            			answer.ra = 0                 # Does name server support recursion: 0 = No, 1 = Yes
         | 
| 187 306 | 
             
            			answer.rcode = 0              # Response code: 0 = No errors
         | 
| 188 | 
            -
             | 
| 189 | 
            -
            			 | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
            				 | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
            			# There may be multiple questions per query
         | 
| 199 | 
            -
            			query.question.reverse.each do |question, resource_class|
         | 
| 200 | 
            -
            				next_link = chain.last
         | 
| 201 | 
            -
             | 
| 202 | 
            -
            				chain << lambda do
         | 
| 203 | 
            -
            					@logger.debug "Processing question #{question} #{resource_class}..."
         | 
| 204 | 
            -
             | 
| 205 | 
            -
            					transaction = Transaction.new(self, query, question, resource_class, answer, options)
         | 
| 206 | 
            -
            					
         | 
| 207 | 
            -
            					# Call the next link in the chain:
         | 
| 208 | 
            -
            					transaction.callback do
         | 
| 209 | 
            -
            						# 3/ ... which calls the previous item in the chain, i.e. the next question to be answered:
         | 
| 210 | 
            -
            						next_link.call
         | 
| 211 | 
            -
            					end
         | 
| 212 | 
            -
             | 
| 213 | 
            -
            					# If there was an error, log it and fail:
         | 
| 214 | 
            -
            					transaction.errback do |response|
         | 
| 215 | 
            -
            						if Exception === response
         | 
| 216 | 
            -
            							@logger.error "Exception thrown while processing #{transaction}!"
         | 
| 217 | 
            -
            							RubyDNS.log_exception(@logger, response)
         | 
| 218 | 
            -
            						else
         | 
| 219 | 
            -
            							@logger.error "Failure while processing #{transaction}!"
         | 
| 220 | 
            -
            							@logger.error "#{response.inspect}"
         | 
| 221 | 
            -
            						end
         | 
| 222 | 
            -
             | 
| 223 | 
            -
            						answer.rcode = Resolv::DNS::RCode::ServFail
         | 
| 224 | 
            -
             | 
| 225 | 
            -
            						chain.first.call
         | 
| 226 | 
            -
            					end
         | 
| 227 | 
            -
            					
         | 
| 228 | 
            -
            					begin
         | 
| 229 | 
            -
            						# Transaction.process will call succeed if it wasn't deferred:
         | 
| 307 | 
            +
            			
         | 
| 308 | 
            +
            			Fiber.new do
         | 
| 309 | 
            +
            				transaction = nil
         | 
| 310 | 
            +
            				
         | 
| 311 | 
            +
            				begin
         | 
| 312 | 
            +
            					query.question.each do |question, resource_class|
         | 
| 313 | 
            +
            						@logger.debug "Processing question #{question} #{resource_class}..."
         | 
| 314 | 
            +
            				
         | 
| 315 | 
            +
            						transaction = Transaction.new(self, query, question, resource_class, answer, options)
         | 
| 316 | 
            +
            						
         | 
| 230 317 | 
             
            						transaction.process
         | 
| 231 | 
            -
            					rescue
         | 
| 232 | 
            -
            						transaction.fail($!)
         | 
| 233 318 | 
             
            					end
         | 
| 319 | 
            +
            				rescue
         | 
| 320 | 
            +
            					@logger.error "Exception thrown while processing #{transaction}!"
         | 
| 321 | 
            +
            					RubyDNS.log_exception(@logger, $!)
         | 
| 322 | 
            +
            				
         | 
| 323 | 
            +
            					answer.rcode = Resolv::DNS::RCode::ServFail
         | 
| 234 324 | 
             
            				end
         | 
| 235 | 
            -
            			 | 
| 236 | 
            -
             | 
| 237 | 
            -
            			 | 
| 238 | 
            -
            			chain.last.call
         | 
| 325 | 
            +
            			
         | 
| 326 | 
            +
            				yield answer
         | 
| 327 | 
            +
            			end.resume
         | 
| 239 328 | 
             
            		end
         | 
| 240 329 | 
             
            	end
         | 
| 241 330 | 
             
            end
         | 
    
        data/lib/rubydns/transaction.rb
    CHANGED
    
    | @@ -21,11 +21,16 @@ | |
| 21 21 | 
             
            require 'eventmachine'
         | 
| 22 22 |  | 
| 23 23 | 
             
            module RubyDNS
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            	 | 
| 26 | 
            -
            	 | 
| 24 | 
            +
            	
         | 
| 25 | 
            +
            	class PassthroughError < StandardError
         | 
| 26 | 
            +
            	end
         | 
| 27 | 
            +
            	
         | 
| 28 | 
            +
            	# This class provides all details of a single DNS question and answer. This is used by the DSL to provide DNS related functionality.
         | 
| 29 | 
            +
            	# 
         | 
| 30 | 
            +
            	# The main functions to complete the trasaction are: {#append!} (evaluate a new query and append the results), {#passthrough!} (pass the query to an upstream server), {#respond!} (compute a specific response) and {#fail!} (fail with an error code).
         | 
| 27 31 | 
             
            	class Transaction
         | 
| 28 | 
            -
            		 | 
| 32 | 
            +
            		# The default time used for responses (24 hours).
         | 
| 33 | 
            +
            		DEFAULT_TTL = 86400
         | 
| 29 34 |  | 
| 30 35 | 
             
            		def initialize(server, query, question, resource_class, answer, options = {})
         | 
| 31 36 | 
             
            			@server = server
         | 
| @@ -36,12 +41,12 @@ module RubyDNS | |
| 36 41 |  | 
| 37 42 | 
             
            			@options = options
         | 
| 38 43 |  | 
| 39 | 
            -
            			@deferred = false
         | 
| 40 44 | 
             
            			@question_appended = false
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            			@fiber = nil
         | 
| 41 47 | 
             
            		end
         | 
| 42 48 |  | 
| 43 | 
            -
            		# The resource_class that was requested. This is typically used to generate a
         | 
| 44 | 
            -
            		# response.
         | 
| 49 | 
            +
            		# The resource_class that was requested. This is typically used to generate a response.
         | 
| 45 50 | 
             
            		attr :resource_class
         | 
| 46 51 |  | 
| 47 52 | 
             
            		# The incoming query which is a set of questions.
         | 
| @@ -56,198 +61,154 @@ module RubyDNS | |
| 56 61 | 
             
            		# Any options or configuration associated with the given transaction.
         | 
| 57 62 | 
             
            		attr :options
         | 
| 58 63 |  | 
| 59 | 
            -
            		#  | 
| 64 | 
            +
            		# The name of the question, which is typically the requested hostname.
         | 
| 60 65 | 
             
            		def name
         | 
| 61 66 | 
             
            			@question.to_s
         | 
| 62 67 | 
             
            		end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
            		# Suitable for debugging purposes
         | 
| 68 | 
            +
            		
         | 
| 69 | 
            +
            		# Shows the question name and resource class. Suitable for debugging purposes.
         | 
| 65 70 | 
             
            		def to_s
         | 
| 66 71 | 
             
            			"#{name} #{@resource_class.name}"
         | 
| 67 72 | 
             
            		end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
            		# Run a new query through the rules with the given name and resource type. The
         | 
| 70 | 
            -
            		 | 
| 71 | 
            -
            		def append_query!(name, resource_class = nil, options = {})
         | 
| 73 | 
            +
            		
         | 
| 74 | 
            +
            		# Run a new query through the rules with the given name and resource type. The results of this query are appended to the current transaction's `answer`.
         | 
| 75 | 
            +
            		def append!(name, resource_class = nil, options = {})
         | 
| 72 76 | 
             
            			Transaction.new(@server, @query, name, resource_class || @resource_class, @answer, options).process
         | 
| 73 77 | 
             
            		end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
            		 | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
            			end
         | 
| 81 | 
            -
            		end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
            		def defer!
         | 
| 84 | 
            -
            			@deferred = true
         | 
| 85 | 
            -
            		end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
            		# Use the given resolver to respond to the question. The default functionality is
         | 
| 88 | 
            -
            		# implemented by passthrough, and if a reply is received, it will be merged with the
         | 
| 89 | 
            -
            		# answer for this transaction.
         | 
| 78 | 
            +
            		
         | 
| 79 | 
            +
            		# Use the given resolver to respond to the question. Uses `passthrough` to do the lookup and merges the result.
         | 
| 80 | 
            +
            		#
         | 
| 81 | 
            +
            		# If a block is supplied, this function yields with the `reply` and `reply_name` if successful. This could be used, for example, to update a cache or modify the reply.
         | 
| 82 | 
            +
            		#
         | 
| 83 | 
            +
            		# If recursion is not requested, the result is `fail!(:Refused)`. This check is ignored if an explicit `options[:name]` or `options[:force]` is given.
         | 
| 90 84 | 
             
            		#
         | 
| 91 | 
            -
            		# If  | 
| 92 | 
            -
            		# successful. This could be used, for example, to update a cache or modify the
         | 
| 93 | 
            -
            		# reply.
         | 
| 85 | 
            +
            		# If the resolver does not respond, the result is `fail!(:NXDomain)`.
         | 
| 94 86 | 
             
            		def passthrough!(resolver, options = {}, &block)
         | 
| 95 | 
            -
            			 | 
| 96 | 
            -
            				 | 
| 97 | 
            -
            					 | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
            				@answer.merge!(response)
         | 
| 87 | 
            +
            			if @query.rd || options[:force] || options[:name]
         | 
| 88 | 
            +
            				passthrough(resolver, options) do |response|
         | 
| 89 | 
            +
            					if block_given?
         | 
| 90 | 
            +
            						yield response
         | 
| 91 | 
            +
            					end
         | 
| 101 92 |  | 
| 102 | 
            -
             | 
| 93 | 
            +
            					@answer.merge!(response)
         | 
| 94 | 
            +
            				end
         | 
| 95 | 
            +
            			else
         | 
| 96 | 
            +
            				raise PassthroughError.new("Request is not recursive!")
         | 
| 103 97 | 
             
            			end
         | 
| 104 | 
            -
            			
         | 
| 105 | 
            -
            			true
         | 
| 106 98 | 
             
            		end
         | 
| 107 99 |  | 
| 108 | 
            -
            		# Use the given resolver to respond to the question. | 
| 109 | 
            -
            		#  | 
| 110 | 
            -
            		#  | 
| 111 | 
            -
            		#
         | 
| 112 | 
            -
            		# If a block is supplied, this function yields with the reply and reply_name if
         | 
| 113 | 
            -
            		# successful. This block is responsible for doing something useful with the reply,
         | 
| 114 | 
            -
            		# such as merging it or conditionally discarding it.
         | 
| 100 | 
            +
            		# Use the given resolver to respond to the question.
         | 
| 101 | 
            +
            		# 
         | 
| 102 | 
            +
            		# A block must be supplied, and provided a valid response is received from the upstream server, this function yields with the reply and reply_name.
         | 
| 115 103 | 
             
            		#
         | 
| 116 | 
            -
            		#  | 
| 117 | 
            -
            		# :force => true, ensures that the query will occur even if recursion is not requested.
         | 
| 118 | 
            -
            		# :name => resource_name, override the name for the request as it is passed to 
         | 
| 119 | 
            -
            		# the resolver. Implies :force.
         | 
| 120 | 
            -
            		# :resource_class => class, overried the resource class for a request as it is passed
         | 
| 121 | 
            -
            		# to the resolver.
         | 
| 104 | 
            +
            		# If `options[:name]` is provided, this overrides the default query name sent to the upstream server. The same logic applies to `options[:resource_class]`.
         | 
| 122 105 | 
             
            		def passthrough(resolver, options = {}, &block)
         | 
| 123 | 
            -
            			 | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
            				query_name = options[:name] || name
         | 
| 128 | 
            -
            				query_resource_class = options[:resource_class] || resource_class
         | 
| 129 | 
            -
             | 
| 106 | 
            +
            			query_name = options[:name] || name
         | 
| 107 | 
            +
            			query_resource_class = options[:resource_class] || resource_class
         | 
| 108 | 
            +
            			
         | 
| 109 | 
            +
            			response = @server.defer do |handle|
         | 
| 130 110 | 
             
            				resolver.query(query_name, query_resource_class) do |response|
         | 
| 131 | 
            -
            					 | 
| 132 | 
            -
            					 | 
| 133 | 
            -
            						yield response
         | 
| 134 | 
            -
            					when RubyDNS::ResolutionFailure
         | 
| 135 | 
            -
            						failure!(:ServFail)
         | 
| 136 | 
            -
            					else
         | 
| 137 | 
            -
            						# This shouldn't ever happen, but if it does for some reason we shouldn't hang.
         | 
| 138 | 
            -
            						fail(response)
         | 
| 139 | 
            -
            					end
         | 
| 111 | 
            +
            					# Not sure if this is potentially a race condition? Could fiber.resume occur before Fiber.yield?
         | 
| 112 | 
            +
            					handle.resume(response)
         | 
| 140 113 | 
             
            				end
         | 
| 141 | 
            -
            			else
         | 
| 142 | 
            -
            				failure!(:Refused)
         | 
| 143 114 | 
             
            			end
         | 
| 144 115 |  | 
| 145 | 
            -
            			 | 
| 116 | 
            +
            			case response
         | 
| 117 | 
            +
            			when RubyDNS::Message
         | 
| 118 | 
            +
            				yield response
         | 
| 119 | 
            +
            			when RubyDNS::ResolutionFailure
         | 
| 120 | 
            +
            				fail!(:ServFail)
         | 
| 121 | 
            +
            			else
         | 
| 122 | 
            +
            				throw PassthroughError.new("Bad response from query: #{response.inspect}")
         | 
| 123 | 
            +
            			end
         | 
| 146 124 | 
             
            		end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
            		# Respond to the given query with a resource record. The arguments to this
         | 
| 149 | 
            -
            		# function depend on the <tt>resource_class</tt> requested. The last argument
         | 
| 150 | 
            -
            		# can optionally be a hash of options.
         | 
| 151 | 
            -
            		#
         | 
| 152 | 
            -
            		# <tt>options[:resource_class]</tt>:: Override the default <tt>resource_class</tt>
         | 
| 153 | 
            -
            		# <tt>options[:ttl]</tt>:: Specify the TTL for the resource
         | 
| 154 | 
            -
            		# <tt>options[:name]</tt>:: Override the name (question) of the response.
         | 
| 155 | 
            -
            		#
         | 
| 156 | 
            -
            		# for A records:: <tt>respond!("1.2.3.4")</tt>
         | 
| 157 | 
            -
            		# for MX records::  <tt>respond!(10, Name.create("mail.blah.com"))</tt>
         | 
| 125 | 
            +
            		
         | 
| 126 | 
            +
            		# Respond to the given query with a resource record. The arguments to this function depend on the `resource_class` requested. This function instantiates the resource class with the supplied arguments, and then passes it to {#append!}.
         | 
| 158 127 | 
             
            		#
         | 
| 159 | 
            -
            		#  | 
| 160 | 
            -
            		 | 
| 128 | 
            +
            		# e.g. For A records: `respond!("1.2.3.4")`, For MX records:  `respond!(10, Name.create("mail.blah.com"))`
         | 
| 129 | 
            +
            		
         | 
| 130 | 
            +
            		# The last argument can optionally be a hash of `options`. If `options[:resource_class]` is provided, it overrides the default resource class of transaction. Additional `options` are passed to {#append!}.
         | 
| 161 131 | 
             
            		#
         | 
| 162 | 
            -
            		# See  | 
| 163 | 
            -
            		 | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
            			options =  | 
| 132 | 
            +
            		# See `Resolv::DNS::Resource` for more information about the various `resource_classes` available (http://www.ruby-doc.org/stdlib/libdoc/resolv/rdoc/index.html).
         | 
| 133 | 
            +
            		def respond!(*args)
         | 
| 134 | 
            +
            			append_question!
         | 
| 135 | 
            +
            			
         | 
| 136 | 
            +
            			options = args.last.kind_of?(Hash) ? args.pop : {}
         | 
| 167 137 | 
             
            			resource_class = options[:resource_class] || @resource_class
         | 
| 168 138 |  | 
| 169 139 | 
             
            			if resource_class == nil
         | 
| 170 | 
            -
            				raise ArgumentError | 
| 140 | 
            +
            				raise ArgumentError.new("Could not instantiate resource #{resource_class}!")
         | 
| 171 141 | 
             
            			end
         | 
| 172 142 |  | 
| 173 143 | 
             
            			@server.logger.info "Resource class: #{resource_class.inspect}"
         | 
| 174 | 
            -
            			resource = resource_class.new(* | 
| 144 | 
            +
            			resource = resource_class.new(*args)
         | 
| 175 145 | 
             
            			@server.logger.info "Resource: #{resource.inspect}"
         | 
| 176 146 |  | 
| 177 | 
            -
            			 | 
| 147 | 
            +
            			add([resource], options)
         | 
| 178 148 | 
             
            		end
         | 
| 179 | 
            -
             | 
| 180 | 
            -
            		# Append a  | 
| 181 | 
            -
            		# | 
| 182 | 
            -
            		# 
         | 
| 183 | 
            -
            		# | 
| 184 | 
            -
            		#  | 
| 185 | 
            -
            		 | 
| 186 | 
            -
            		#                             `:authority` or `:additional` section.
         | 
| 187 | 
            -
            		# 
         | 
| 188 | 
            -
            		# This function can be used to supply multiple responses to a given question.
         | 
| 189 | 
            -
            		# For example, each argument is expected to be an instantiated resource from
         | 
| 190 | 
            -
            		# <tt>Resolv::DNS::Resource</tt> module.
         | 
| 191 | 
            -
            		def append! (*resources)
         | 
| 192 | 
            -
            			append_question!
         | 
| 193 | 
            -
             | 
| 194 | 
            -
            			if resources.last.kind_of?(Hash)
         | 
| 195 | 
            -
            				options = resources.pop
         | 
| 196 | 
            -
            			else
         | 
| 197 | 
            -
            				options = {}
         | 
| 198 | 
            -
            			end
         | 
| 199 | 
            -
             | 
| 149 | 
            +
            		
         | 
| 150 | 
            +
            		# Append a list of resources.
         | 
| 151 | 
            +
            		#
         | 
| 152 | 
            +
            		# By default resources are appended to the `answers` section, but this can be changed by setting `options[:section]` to either `:authority` or `:additional`.
         | 
| 153 | 
            +
            		#
         | 
| 154 | 
            +
            		# The time-to-live (TTL) of the resources can be specified using `options[:ttl]` and defaults to `DEFAULT_TTL`.
         | 
| 155 | 
            +
            		def add(resources, options = {})
         | 
| 200 156 | 
             
            			# Use the default options if provided:
         | 
| 201 157 | 
             
            			options = options.merge(@options)
         | 
| 202 | 
            -
             | 
| 203 | 
            -
            			options[:ttl] ||= 16000
         | 
| 204 | 
            -
            			options[:name] ||= @question.to_s + "."
         | 
| 205 158 |  | 
| 206 | 
            -
            			 | 
| 207 | 
            -
             | 
| 159 | 
            +
            			ttl = options[:ttl] || DEFAULT_TTL
         | 
| 160 | 
            +
            			name = options[:name] || @question.to_s + "."
         | 
| 161 | 
            +
            			
         | 
| 162 | 
            +
            			section = (options[:section] || 'answer').to_sym
         | 
| 163 | 
            +
            			method = "add_#{section}".to_sym
         | 
| 164 | 
            +
            			
         | 
| 208 165 | 
             
            			resources.each do |resource|
         | 
| 209 166 | 
             
            				@server.logger.debug "#{method}: #{resource.inspect} #{resource.class::TypeValue} #{resource.class::ClassValue}"
         | 
| 210 167 |  | 
| 211 | 
            -
            				@answer.send(method,  | 
| 168 | 
            +
            				@answer.send(method, name, ttl, resource)
         | 
| 212 169 | 
             
            			end
         | 
| 213 | 
            -
             | 
| 214 | 
            -
            			succeed if @deferred
         | 
| 215 | 
            -
             | 
| 216 | 
            -
            			true
         | 
| 217 170 | 
             
            		end
         | 
| 218 | 
            -
             | 
| 219 | 
            -
            		# This function indicates that there was a failure to resolve the given
         | 
| 220 | 
            -
            		# question. The single argument must be an integer error code, typically
         | 
| 221 | 
            -
            		# given by the constants in <tt>Resolv::DNS::RCode</tt>.
         | 
| 171 | 
            +
            		
         | 
| 172 | 
            +
            		# This function indicates that there was a failure to resolve the given question. The single argument must be an integer error code, typically given by the constants in {Resolv::DNS::RCode}.
         | 
| 222 173 | 
             
            		#
         | 
| 223 | 
            -
            		# The easiest way to use this function it to simply supply a symbol. Here is
         | 
| 224 | 
            -
            		# a list of the most commonly used ones:
         | 
| 174 | 
            +
            		# The easiest way to use this function it to simply supply a symbol. Here is a list of the most commonly used ones:
         | 
| 225 175 | 
             
            		#
         | 
| 226 | 
            -
            		#  | 
| 227 | 
            -
            		#  | 
| 228 | 
            -
            		#  | 
| 229 | 
            -
            		#  | 
| 230 | 
            -
            		#  | 
| 231 | 
            -
            		#  | 
| 232 | 
            -
            		#  | 
| 176 | 
            +
            		# - `:NoError`: No error occurred.
         | 
| 177 | 
            +
            		# - `:FormErr`: The incoming data was not formatted correctly.
         | 
| 178 | 
            +
            		# - `:ServFail`: The operation caused a server failure (internal error, etc).
         | 
| 179 | 
            +
            		# - `:NXDomain`: Non-eXistant Domain (domain record does not exist).
         | 
| 180 | 
            +
            		# - `:NotImp`: The operation requested is not implemented.
         | 
| 181 | 
            +
            		# - `:Refused`: The operation was refused by the server.
         | 
| 182 | 
            +
            		# - `:NotAuth`: The server is not authoritive for the zone.
         | 
| 233 183 | 
             
            		#
         | 
| 234 | 
            -
            		# See http://www.rfc-editor.org/rfc/rfc2929.txt for more information
         | 
| 235 | 
            -
            		# | 
| 236 | 
            -
            		 | 
| 184 | 
            +
            		# See [RFC2929](http://www.rfc-editor.org/rfc/rfc2929.txt) for more information about DNS error codes (specifically, page 3).
         | 
| 185 | 
            +
            		#
         | 
| 186 | 
            +
            		# **This function will complete deferred transactions.**
         | 
| 187 | 
            +
            		def fail!(rcode)
         | 
| 237 188 | 
             
            			append_question!
         | 
| 238 | 
            -
             | 
| 189 | 
            +
            			
         | 
| 239 190 | 
             
            			if rcode.kind_of? Symbol
         | 
| 240 191 | 
             
            				@answer.rcode = Resolv::DNS::RCode.const_get(rcode)
         | 
| 241 192 | 
             
            			else
         | 
| 242 193 | 
             
            				@answer.rcode = rcode.to_i
         | 
| 243 194 | 
             
            			end
         | 
| 244 | 
            -
             | 
| 245 | 
            -
            			# The transaction itself has completed, but contains a failure:
         | 
| 246 | 
            -
            			succeed(rcode) if @deferred
         | 
| 247 | 
            -
             | 
| 248 | 
            -
            			true
         | 
| 249 195 | 
             
            		end
         | 
| 250 | 
            -
             | 
| 196 | 
            +
            		
         | 
| 197 | 
            +
            		# @deprecated
         | 
| 198 | 
            +
            		def failure!(*args)
         | 
| 199 | 
            +
            			@server.logger.warn "failure! is deprecated, use fail! instead"
         | 
| 200 | 
            +
            			
         | 
| 201 | 
            +
            			fail!(*args)
         | 
| 202 | 
            +
            		end
         | 
| 203 | 
            +
            		
         | 
| 204 | 
            +
            		# A helper method to process the transaction on the given server. Unless the transaction is deferred, it will {#succeed} on completion.
         | 
| 205 | 
            +
            		def process
         | 
| 206 | 
            +
            			@server.process(name, @resource_class, self)
         | 
| 207 | 
            +
            		end
         | 
| 208 | 
            +
            		
         | 
| 209 | 
            +
            		protected
         | 
| 210 | 
            +
            		
         | 
| 211 | 
            +
            		# A typical response to a DNS request includes both the question and answer. This helper appends the question unless it looks like the user is already managing that aspect of the response.
         | 
| 251 212 | 
             
            		def append_question!
         | 
| 252 213 | 
             
            			if @answer.question.size == 0
         | 
| 253 214 | 
             
            				@answer.add_question(@question, @resource_class) unless @question_appended
         |