Class: DevDNSd::Application

Inherits:
RExec::Daemon::Base
  • Object
show all
Defined in:
lib/devdnsd/application.rb

Overview

The main DevDNSd application.

Constant Summary

ANY_REQUEST =

Class for ANY DNS request.

Resolv::DNS::Resource::IN::ANY
ANY_CLASSES =

List of classes handled in case of DNS request with resource class ANY.

[Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME, Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS, Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT]

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Application) initialize(command)

Creates a new application.

Parameters:

  • command (Mamertes::Command)

    The current Mamertes command.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/devdnsd/application.rb', line 29

def initialize(command)
  @command = command
  application = @command.application

  # Setup logger
  Bovem::Logger.start_time = Time.now
  @logger = Bovem::Logger.create(Bovem::Logger.get_real_file(application.options["log-file"].value) || Bovem::Logger.default_file, Logger::INFO)

  # Open configuration
  begin
    overrides = {
      :foreground => command.name == "start" ? command.options["foreground"].value : false,
      :tld => application.options["tld"].value,
      :port => application.options["port"].value,
      :pid_file => application.options["pid-file"].value,
      :log_file => application.options["log-file"].value,
      :log_level => application.options["log-level"].value
    }.reject {|k,v| v.nil? }

    @config = DevDNSd::Configuration.new(application.options["configuration"].value, overrides, @logger)

    @logger = nil
    @logger = self.get_logger
  rescue Bovem::Errors::InvalidConfiguration, DevDNSd::Errors::InvalidRule => e
    @logger ? @logger.fatal(e.message) : Bovem::Logger.create("STDERR").fatal("Cannot log to #{config.log_file}. Exiting...")
    raise ::SystemExit
  end

  self
end

Instance Attribute Details

- (Object) command (readonly)

The Mamertes command.



21
22
23
# File 'lib/devdnsd/application.rb', line 21

def command
  @command
end

- (Object) config (readonly)

The Configuration of this application.



18
19
20
# File 'lib/devdnsd/application.rb', line 18

def config
  @config
end

- (Object) logger

The logger for this application.



24
25
26
# File 'lib/devdnsd/application.rb', line 24

def logger
  @logger
end

Class Method Details

+ (String) daemon_name

Returns the name of the daemon.

Returns:

  • (String)

    The name of the daemon.



63
64
65
# File 'lib/devdnsd/application.rb', line 63

def self.daemon_name
  File.basename(self.instance.config.pid_file, ".pid")
end

+ (Application) instance(command = nil, force = false)

Returns a unique (singleton) instance of the application.

Parameters:

  • command (Mamertes::Command) (defaults to: nil)

    The current Mamertes command.

  • force (Boolean) (defaults to: false)

    If to force recreation of the instance.

Returns:

  • (Application)

    The unique (singleton) instance of the application.



369
370
371
372
373
# File 'lib/devdnsd/application.rb', line 369

def self.instance(command = nil, force = false)
  @instance = nil if force
  @instance ||= DevDNSd::Application.new(command) if command
  @instance
end

+ (String) pid_directory

Returns the standard location of the PID file.

Returns:

  • (String)

    The standard location of the PID file.



70
71
72
# File 'lib/devdnsd/application.rb', line 70

def self.pid_directory
  File.dirname(self.instance.config.pid_file)
end

+ (String) pid_fn

Returns the complete path of the PID file.

Returns:

  • (String)

    The complete path of the PID file.



77
78
79
# File 'lib/devdnsd/application.rb', line 77

def self.pid_fn
  self.instance.config.pid_file
end

+ (Object) quit

Stops the application.



383
384
385
# File 'lib/devdnsd/application.rb', line 383

def self.quit
  ::EventMachine.stop
end

+ (Object) run

Runs the application in foreground.

See Also:



378
379
380
# File 'lib/devdnsd/application.rb', line 378

def self.run
  self.instance.perform_server
end

Instance Method Details

- (Boolean) action_install

Installs the server into the system.

Returns:

  • (Boolean)

    true if action succedeed, false otherwise.



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/devdnsd/application.rb', line 254

def action_install
  logger = get_logger

  if !self.is_osx? then
    logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
    return false
  end

  resolver_file = self.resolver_path
  launch_agent = self.launch_agent_path

  # Installs the resolver
  begin
    logger.info("Installing the resolver in #{resolver_file} ...")

    open(resolver_file, "w") {|f|
      f.write("nameserver 127.0.0.1\n")
      f.write("port #{@config.port}")
      f.flush
    }
  rescue => e
    logger.error("Cannot create the resolver file.")
    return false
  end

  begin
    logger.info("Creating the launch agent in #{launch_agent} ...")

    args = $ARGV ? $ARGV[0, $ARGV.length - 1] : []

    plist = {"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => (::Pathname.new(Dir.pwd) + $0).to_s, "ProgramArguments" => args, "RunAtLoad" => true}
    ::File.open(launch_agent, "w") {|f|
      f.write(plist.to_json)
      f.flush
    }
    self.execute_command("plutil -convert binary1 \"#{launch_agent}\"")
  rescue => e
    logger.error("Cannot create the launch agent.")
    return false
  end

  begin
    logger.info("Loading the launch agent ...")
    self.execute_command("launchctl load -w \"#{launch_agent}\" > /dev/null 2>&1")
  rescue => e
    logger.error("Cannot load the launch agent.")
    return false
  end

  self.dns_update

  true
end

- (Boolean) action_start

Starts the server in background.

Returns:

  • (Boolean)

    true if action succedeed, false otherwise.



228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/devdnsd/application.rb', line 228

def action_start
  logger = self.get_logger

  logger.info("Starting DevDNSd ...")

  if @config.foreground then
    self.perform_server
  else
    RExec::Daemon::Controller.start(self.class)
  end

  true
end

- (Boolean) action_stop

Stops the server in background.

Returns:

  • (Boolean)

    true if action succedeed, false otherwise.



245
246
247
248
249
# File 'lib/devdnsd/application.rb', line 245

def action_stop
  RExec::Daemon::Controller.stop(self.class)

  true
end

- (Boolean) action_uninstall

Uninstalls the server from the system.

Returns:

  • (Boolean)

    true if action succedeed, false otherwise.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/devdnsd/application.rb', line 311

def action_uninstall
  logger = self.get_logger

  if !self.is_osx? then
    logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
    return false
  end

  resolver_file = self.resolver_path
  launch_agent = self.launch_agent_path

  # Remove the resolver
  begin
    logger.info("Deleting the resolver #{resolver_file} ...")
    ::File.delete(resolver_file)
  rescue => e
    logger.warn("Cannot delete the resolver file.")
    return false
  end

  # Unload the launch agent.
  begin
    self.execute_command("launchctl unload -w \"#{launch_agent}\" > /dev/null 2>&1")
  rescue => e
    logger.warn("Cannot unload the launch agent.")
  end

  # Delete the launch agent.
  begin
    logger.info("Deleting the launch agent #{launch_agent} ...")
    ::File.delete(launch_agent)
  rescue => e
    logger.warn("Cannot delete the launch agent.")
    return false
  end

  self.dns_update

  true
end

- (Boolean) dns_update

Updates DNS cache.

Returns:

  • (Boolean)

    true if command succeeded, false otherwise.



124
125
126
127
# File 'lib/devdnsd/application.rb', line 124

def dns_update
  @logger.info("Flushing DNS cache and resolvers ...")
  self.execute_command("dscacheutil -flushcache")
end

- (Boolean) execute_command(command)

Executes a shell command.

Parameters:

  • command (String)

    The command to execute.

Returns:

  • (Boolean)

    true if command succeeded, false otherwise.



117
118
119
# File 'lib/devdnsd/application.rb', line 117

def execute_command(command)
  system(command)
end

- (Logger) get_logger

Gets the current logger of the application.

Returns:

  • (Logger)

    The current logger of the application.



92
93
94
# File 'lib/devdnsd/application.rb', line 92

def get_logger
  @logger ||= Bovem::Logger.create(@config.foreground ? Bovem::Logger.default_file : @config.log_file, @config.log_level, @log_formatter)
end

- (Boolean) is_osx?

Check if we are running on MacOS X. System services are only available on that platform.

Returns:

  • (Boolean)

    true if the current platform is MacOS X, false otherwise.



85
86
87
# File 'lib/devdnsd/application.rb', line 85

def is_osx?
  ::Config::CONFIG['host_os'] =~ /^darwin/
end

- (String) launch_agent_path(name = "it.cowtech.devdnsd")

Gets the path for the launch agent file.

Parameters:

  • name (String) (defaults to: "it.cowtech.devdnsd")

    The base name for the agent.

Returns:

  • (String)

    The path for the launch agent file.



109
110
111
# File 'lib/devdnsd/application.rb', line 109

def launch_agent_path(name = "it.cowtech.devdnsd")
  ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
end

- (NilClass) on_start

This method is called when the server starts. By default is a no-op.

Returns:

  • (NilClass)

    nil.



355
356
# File 'lib/devdnsd/application.rb', line 355

def on_start
end

- (NilClass) on_stop

This method is called when the server stop.

Returns:

  • (NilClass)

    nil.



361
362
# File 'lib/devdnsd/application.rb', line 361

def on_stop
end

- (Object) perform_server

Starts the DNS server.

Returns:

  • (Object)

    The result of stop callbacks.



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
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/devdnsd/application.rb', line 132

def perform_server
  RubyDNS::run_server(:listen => [[:udp, @config.address, @config.port.to_integer]]) do
    self.logger = DevDNSd::Application.instance.logger

    match(/.+/, DevDNSd::Application::ANY_CLASSES) do |match_data, transaction|
      transaction.append_question!

      DevDNSd::Application.instance.config.rules.each do |rule|
        begin
          # Get the subset of handled class that is valid for the rule
          resource_classes = DevDNSd::Application::ANY_CLASSES & rule.resource_class.ensure_array
          resource_classes = resource_classes & [transaction.resource_class] if transaction.resource_class != DevDNSd::Application::ANY_REQUEST

          if resource_classes.present? then
            resource_classes.each do |resource_class| # Now for every class
              matches = rule.match_host(match_data[0])
              DevDNSd::Application.instance.process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
            end
          end
        rescue ::Exception => e
          raise e
        end
      end
    end

    # Default DNS handler
    otherwise do |transaction|
      transaction.failure!(:NXDomain)
    end

    # Attach event handlers
    self.on(:start) do
      DevDNSd::Application.instance.on_start
    end

    self.on(:stop) do
      DevDNSd::Application.instance.on_stop
    end
  end
end

- (Object) process_rule(rule, type, match_data, transaction)

Processes a DNS rule.

Parameters:

  • rule (Rule)

    The rule to process.

  • type (Class)

    The type of request.

  • match_data (MatchData|nil)

    If the rule pattern was a Regexp, then this holds the match data, otherwise nil is passed.

  • transaction (Transaction)

Returns:

  • A reply for the request if matched, otherwise false or nil.



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
# File 'lib/devdnsd/application.rb', line 180

def process_rule(rule, type, match_data, transaction)
  is_regex = rule.match.is_a?(::Regexp)
  type = DevDNSd::Rule.resource_class_to_symbol(type)

  DevDNSd::Application.instance.logger.debug("Found match on #{rule.match} with type #{type}.")

  if !rule.block.nil? then
    reply = rule.block.call(match_data, type, transaction)
  else
    reply = rule.reply
  end

  if is_regex && reply && match_data[0] then
    reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\"))
  end

  DevDNSd::Application.instance.logger.debug(reply ? "Reply is #{reply} with type #{type}." : "No reply found.")

  if reply then
    options = rule.options

    final_reply = []

    case type
      when :MX
        preference = options.delete(:preference)
        preference = preference.nil? ? 10 : preference.to_integer(10)
        final_reply << preference
    end

    if [:A, :AAAA].include?(type) then
      final_reply << reply
    else
      final_reply << Resolv::DNS::Name.create(reply)
    end

    final_reply << options.merge({:resource_class => DevDNSd::Rule.symbol_to_resource_class(type)})
    transaction.respond!(*final_reply)
  elsif reply == false then
    false
  else
    reply
  end
end

- (String) resolver_path(tld = nil)

Gets the path for the resolver file.

Parameters:

  • tld (String) (defaults to: nil)

    The TLD to manage.

Returns:

  • (String)

    The path for the resolver file.



100
101
102
103
# File 'lib/devdnsd/application.rb', line 100

def resolver_path(tld = nil)
  tld ||= @config.tld
  "/etc/resolver/#{tld}"
end