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
# 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,
      :log_file => application.options["log-file"].value,
      :log_level => application.options["log-level"].value,
      :tld => application.options["tld"].value,
      :port => application.options["port"].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

+ (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.



347
348
349
350
351
# File 'lib/devdnsd/application.rb', line 347

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

+ (Object) quit

Stops the application.



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

def self.quit
  ::EventMachine.stop
end

+ (Object) run

Runs the application in foreground.

See Also:



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

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.



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
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
# File 'lib/devdnsd/application.rb', line 232

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.



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/devdnsd/application.rb', line 206

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.



223
224
225
226
227
# File 'lib/devdnsd/application.rb', line 223

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.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/devdnsd/application.rb', line 289

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.



102
103
104
105
# File 'lib/devdnsd/application.rb', line 102

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.



95
96
97
# File 'lib/devdnsd/application.rb', line 95

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.



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

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.



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

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.



87
88
89
# File 'lib/devdnsd/application.rb', line 87

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.



333
334
# File 'lib/devdnsd/application.rb', line 333

def on_start
end

- (NilClass) on_stop

This method is called when the server stop.

Returns:

  • (NilClass)

    nil.



339
340
# File 'lib/devdnsd/application.rb', line 339

def on_stop
end

- (Object) perform_server

Starts the DNS server.

Returns:

  • (Object)

    The result of stop callbacks.



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

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.



158
159
160
161
162
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
# File 'lib/devdnsd/application.rb', line 158

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.



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

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