class Dyndnsd::Updater::ZoneTransferServer

Constants

DEFAULT_SERVER_LISTENS

Public Class Methods

new(domain, updater_params) click to toggle source

@param domain [String] @param updater_params [Hash{String => Object}]

# File lib/dyndnsd/updater/zone_transfer_server.rb, line 15
def initialize(domain, updater_params)
  @domain = domain

  @server_listens = self.class.parse_endpoints(updater_params['server_listens'] || DEFAULT_SERVER_LISTENS)
  @notify_targets = (updater_params['send_notifies'] || []).map { |e| self.class.parse_endpoints([e]) }

  @zone_rr_ttl = updater_params['zone_ttl']
  @zone_nameservers = updater_params['zone_nameservers'].map { |n| Resolv::DNS::Name.create(n) }
  @zone_email_address = Resolv::DNS::Name.create(updater_params['zone_email_address'])
  @zone_additional_ips = updater_params['zone_additional_ips'] || []

  @server = ZoneTransferServerHelper.new(@server_listens, @domain)

  # run Async::DNS server in background thread
  Thread.new do
    @server.run
  end
end
parse_endpoints(endpoint_list) click to toggle source

converts into suitable parameter form for Async::DNS::Resolver or Async::DNS::Server

@param endpoint_list [Array<String>] @return [Array{Array{Object}}]

# File lib/dyndnsd/updater/zone_transfer_server.rb, line 90
def self.parse_endpoints(endpoint_list)
  endpoint_list.map { |addr_string| addr_string.split('@') }
               .map { |addr_parts| [addr_parts[0], addr_parts[1].to_i || 53] }
               .map { |addr| [:tcp, :udp].map { |type| [type] + addr } }
               .flatten(1)
end

Public Instance Methods

update(db) click to toggle source

@param db [Dyndnsd::Database] @return [void]

# File lib/dyndnsd/updater/zone_transfer_server.rb, line 36
def update(db)
  Helper.span('updater_update') do |span|
    span.set_attribute('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None')

    soa_rr = Resolv::DNS::Resource::IN::SOA.new(
      @zone_nameservers[0], @zone_email_address,
      db['serial'],
      10_800,  # 3h
      300,     # 5m
      604_800, # 1w
      3_600    # 1h
    )

    default_options = {ttl: @zone_rr_ttl}

    # array containing all resource records for an AXFR request in the right order
    rrs = []
    # AXFR responses need to start with zone's SOA RR
    rrs << [soa_rr, default_options]

    # return RRs for all of the zone's nameservers
    @zone_nameservers.each do |ns|
      rrs << [Resolv::DNS::Resource::IN::NS.new(ns), default_options]
    end

    # return A/AAAA RRs for all additional IPv4s/IPv6s for the domain itself
    @zone_additional_ips.each do |ip|
      rrs << [create_addr_rr_for_ip(ip), default_options]
    end

    # return A/AAAA RRs for the dyndns hostnames
    db['hosts'].each do |hostname, ips|
      ips.each do |ip|
        rrs << [create_addr_rr_for_ip(ip), default_options.merge({name: hostname})]
      end
    end

    # AXFR responses need to end with zone's SOA RR again
    rrs << [soa_rr, default_options]

    # point Async::DNS server thread's variable to this new RR array
    @server.axfr_rrs = rrs

    # only send DNS NOTIFY if there really was a change
    if db.changed?
      send_dns_notify
    end
  end
end

Private Instance Methods

create_addr_rr_for_ip(ip_string) click to toggle source

creates correct Resolv::DNS::Resource object for IP address type

@param ip_string [String] @return [Resolv::DNS::Resource::IN::A,Resolv::DNS::Resource::IN::AAAA]

# File lib/dyndnsd/updater/zone_transfer_server.rb, line 103
def create_addr_rr_for_ip(ip_string)
  ip = IPAddr.new(ip_string).native

  if ip.ipv6?
    Resolv::DNS::Resource::IN::AAAA.new(ip.to_s)
  else
    Resolv::DNS::Resource::IN::A.new(ip.to_s)
  end
end
send_dns_notify() click to toggle source

tools.ietf.org/html/rfc1996

@return [void]

# File lib/dyndnsd/updater/zone_transfer_server.rb, line 116
def send_dns_notify
  Async::Reactor.run do
    @notify_targets.each do |notify_target|
      target = Async::DNS::Resolver.new(notify_target)

      # assemble DNS NOTIFY message
      request = Resolv::DNS::Message.new(SecureRandom.random_number(2**16))
      request.opcode = Resolv::DNS::OpCode::Notify
      request.add_question("#{@domain}.", Resolv::DNS::Resource::IN::SOA)

      _response = target.dispatch_request(request)
    end
  end
end