Class: RubyDNS::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/rubydns/server.rb

Overview

This class provides the core of the 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.

Defined Under Namespace

Classes: Rule

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(&block) ⇒ Server

Instantiate a server with a block

server = Server.new do
  match(/server.mydomain.com/, IN::A) do |transaction|
    transaction.respond!("1.2.3.4")
  end
end


92
93
94
95
96
97
98
99
100
101
102
# File 'lib/rubydns/server.rb', line 92

def initialize(&block)
	@events = {}
	@rules = []
	@otherwise = nil

	@logger = Logger.new($stderr)

	if block_given?
		instance_eval &block
	end
end

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.



104
105
106
# File 'lib/rubydns/server.rb', line 104

def logger
  @logger
end

Instance Method Details

#fire(event_name) ⇒ Object

Fire the named event, which must have been registered using on.



128
129
130
131
132
133
134
# File 'lib/rubydns/server.rb', line 128

def fire(event_name)
	callback = @events[event_name]
	
	if callback
		callback.call(self)
	end
end

#match(*pattern, &block) ⇒ Object

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.

match("www.google.com")
match("gmail.com", IN::MX)
match(/g?mail.(com|org|net)/, [IN::MX, IN::A])


115
116
117
# File 'lib/rubydns/server.rb', line 115

def match(*pattern, &block)
	@rules << Rule.new(pattern, block)
end

#next!Object



148
149
150
# File 'lib/rubydns/server.rb', line 148

def next!
	throw :next
end

#on(event_name, &block) ⇒ Object

Register a named event which may be invoked later using #fire

on(:start) do |server|
  RExec.change_user(RUN_AS)
end


123
124
125
# File 'lib/rubydns/server.rb', line 123

def on(event_name, &block)
	@events[event_name] = block
end

#otherwise(&block) ⇒ Object

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

otherwise do |transaction|
  transaction.passthrough!($R)
end


144
145
146
# File 'lib/rubydns/server.rb', line 144

def otherwise(&block)
	@otherwise = block
end

#process(name, resource_class, *args) ⇒ Object

Give a name and a record type, try to match a rule and use it for processing the given arguments.

If a rule returns false, it is considered that the rule failed and futher matching is carried out.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/rubydns/server.rb', line 157

def process(name, resource_class, *args)
	@logger.debug "Searching for #{name} #{resource_class.name}"

	@rules.each do |rule|
		@logger.debug "Checking rule #{rule}..."

		catch (:next) do
			# If the rule returns true, we assume that it was successful and no further rules need to be evaluated.
			return true if rule.call(self, name, resource_class, *args)
		end
	end

	if @otherwise
		@otherwise.call(*args)
	else
		@logger.warn "Failed to handle #{name} #{resource_class.name}!"
	end
end

#process_query(query, options = {}, &block) ⇒ Object

Process an incoming DNS message. Returns a serialized message to be sent back to the client.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/rubydns/server.rb', line 178

def process_query(query, options = {}, &block)
	# Setup answer
	answer = Resolv::DNS::Message::new(query.id)
	answer.qr = 1                 # 0 = Query, 1 = Response
	answer.opcode = query.opcode  # Type of Query; copy from query
	answer.aa = 1                 # Is this an authoritative response: 0 = No, 1 = Yes
	answer.rd = query.rd          # Is Recursion Desired, copied from query
	answer.ra = 0                 # Does name server support recursion: 0 = No, 1 = Yes
	answer.rcode = 0              # Response code: 0 = No errors

	# 1/ This chain contains a reverse list of question lambdas.
	chain = []

	# 4/ Finally, the answer is given back to the calling block:
	chain << lambda do
		@logger.debug "Passing answer back to caller..."
		yield answer
	end

	# There may be multiple questions per query
	query.question.reverse.each do |question, resource_class|
		next_link = chain.last

		chain << lambda do
			@logger.debug "Processing question #{question} #{resource_class}..."

			transaction = Transaction.new(self, query, question, resource_class, answer, options)
			
			# Call the next link in the chain:
			transaction.callback do
				# 3/ ... which calls the previous item in the chain, i.e. the next question to be answered:
				next_link.call
			end

			# If there was an error, log it and fail:
			transaction.errback do |response|
				if Exception === response
					@logger.error "Exception thrown while processing #{transaction}!"
					@logger.error "#{response.class}: #{response.message}"
					if response.backtrace
						Array(response.backtrace).each { |at| @logger.error at }
					end
				else
					@logger.error "Failure while processing #{transaction}!"
					@logger.error "#{response.inspect}"
				end

				answer.rcode = Resolv::DNS::RCode::ServFail

				chain.first.call
			end
			
			begin
				# Transaction.process will call succeed if it wasn't deferred:
				transaction.process
			rescue
				transaction.fail($!)
			end
		end
	end

	# 2/ We call the last lambda...
	chain.last.call
end