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.

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


37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rubydns/server.rb', line 37

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.



49
50
51
# File 'lib/rubydns/server.rb', line 49

def logger
  @logger
end

Instance Method Details

#fire(event_name) ⇒ Object

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



73
74
75
76
77
78
79
# File 'lib/rubydns/server.rb', line 73

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


60
61
62
# File 'lib/rubydns/server.rb', line 60

def match(*pattern, &block)
	@rules << [pattern, Proc.new(&block)]
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


68
69
70
# File 'lib/rubydns/server.rb', line 68

def on(event_name, &block)
	@events[event_name] = Proc.new(&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


89
90
91
# File 'lib/rubydns/server.rb', line 89

def otherwise(&block)
	@otherwise = Proc.new(&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.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rubydns/server.rb', line 98

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

	@rules.each do |rule|
		@logger.debug "Checking rule #{rule[0].inspect}..."
		
		pattern = rule[0]

		# Match failed against resource_class?
		case pattern[1]
		when Class
			next unless pattern[1] == resource_class
			@logger.debug "Resource class #{resource_class.name} matched"
		when Array
			next unless pattern[1].include?(resource_class)
			@logger.debug "Resource class #{resource_class} matched #{pattern[1].inspect}"
		end

		# Match succeeded against name?
		case pattern[0]
		when Regexp
			match_data = pattern[0].match(name)
			if match_data
				@logger.debug "Query #{name} matched #{pattern[0].to_s} with result #{match_data.inspect}"
				if rule[1].call(match_data, *args)
					@logger.debug "Rule returned successfully"
					return
				end
			else
				@logger.debug "Query #{name} failed to match against #{pattern[0].to_s}"
			end
		when String
			if pattern[0] == name
				@logger.debug "Query #{name} matched #{pattern[0]}"
				if rule[1].call(*args)
					@logger.debug "Rule returned successfully"
					return
				end
			else
				@logger.debug "Query #{name} failed to match against #{pattern[0]}"
			end
		else
			if pattern[0].respond_to? :call
				if pattern[0].call(name)
					@logger.debug "Query #{name} matched #{pattern[0]}"
					if rule[1].call(*args)
						@logger.debug "Rule returned successfully"
						return
					end
				else
					@logger.debug "Query #{name} failed to match against #{pattern[0]}"
				end
			end
		end
	end

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

#process_query(query, &block) ⇒ Object

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
# File 'lib/rubydns/server.rb', line 163

def process_query(query, &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)
			
			# 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|
				@logger.error "Exception thrown while processing #{transaction}!"
				@logger.error "#{response.class}: #{response.message}"
				response.backtrace.each { |at| @logger.error at }

				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