class LogStash::Filters::DNS

The DNS filter performs a lookup (either an A record/CNAME record lookup or a reverse lookup at the PTR record) on records specified under the ‘reverse` arrays or respectively under the `resolve` arrays.

The config should look like this:

source,ruby

filter {

dns {
  reverse => [ "source_host", "field_with_address" ]
  resolve => [ "field_with_fqdn" ]
  action => "replace"
}

}

This filter, like all filters, only processes 1 event at a time, so the use of this plugin can significantly slow down your pipeline’s throughput if you have a high latency network. By way of example, if each DNS lookup takes 2 milliseconds, the maximum throughput you can achieve with a single filter worker is 500 events per second (1000 milliseconds / 2 milliseconds).

Attributes

failed_cache[R]
hit_cache[R]

Public Instance Methods

filter(event) click to toggle source
# File lib/logstash/filters/dns.rb, line 115
def filter(event)
  if @resolve
    return if resolve(event).nil?
  end

  if @reverse
    return if reverse(event).nil?
  end

  filter_matched(event)
end
register() click to toggle source
# File lib/logstash/filters/dns.rb, line 96
def register
  if @nameserver.nil? && @hostsfile.nil?
    @resolv = Resolv.new(default_resolvers)
  else
    @resolv = Resolv.new(build_resolvers)
  end

  if @hit_cache_size > 0
    @hit_cache = LruRedux::TTL::ThreadSafeCache.new(@hit_cache_size, @hit_cache_ttl)
  end

  if @failed_cache_size > 0
    @failed_cache = LruRedux::TTL::ThreadSafeCache.new(@failed_cache_size, @failed_cache_ttl)
  end

  @ip_validator = Resolv::AddressRegex
end

Private Instance Methods

build_resolvers() click to toggle source
# File lib/logstash/filters/dns.rb, line 143
def build_resolvers
  build_user_host_resolvers.concat([::Resolv::Hosts.new]).concat(build_user_dns_resolver)
end
build_user_dns_resolver() click to toggle source
# File lib/logstash/filters/dns.rb, line 152
def build_user_dns_resolver
  return [] if @nameserver.nil? || @nameserver.empty?
  [dns_resolver(normalised_nameserver)]
end
build_user_host_resolvers() click to toggle source
# File lib/logstash/filters/dns.rb, line 147
def build_user_host_resolvers
  return [] if @hostsfile.nil? || @hostsfile.empty?
  @hostsfile.map{|fn| ::Resolv::Hosts.new(fn)}
end
default_dns_resolver() click to toggle source
# File lib/logstash/filters/dns.rb, line 133
def default_dns_resolver
  dns_resolver(nil)
end
default_resolvers() click to toggle source
# File lib/logstash/filters/dns.rb, line 129
def default_resolvers
  [::Resolv::Hosts.new, default_dns_resolver]
end
dns_resolver(args=nil) click to toggle source
# File lib/logstash/filters/dns.rb, line 137
def dns_resolver(args=nil)
  dns_resolver = ::Resolv::DNS.new(args)
  dns_resolver.timeouts = @timeout
  dns_resolver
end
getaddress(name) click to toggle source
# File lib/logstash/filters/dns.rb, line 385
def getaddress(name)
  idn = IDN.toASCII(name)
  address = resolv_getaddress_or_nil(@resolv, idn)
  address && address.force_encoding(Encoding::UTF_8)
end
getname(address) click to toggle source
# File lib/logstash/filters/dns.rb, line 378
def getname(address)
  name = resolv_getname_or_nil(@resolv, address)
  name && name.force_encoding(Encoding::UTF_8)
  name && IDN.toUnicode(name)
end
normalised_nameserver() click to toggle source
# File lib/logstash/filters/dns.rb, line 157
def normalised_nameserver
  nameserver_hash = @nameserver.kind_of?(Hash) ? @nameserver.dup : { 'address' => @nameserver }

  nameserver = nameserver_hash.delete('address') || fail(LogStash::ConfigurationError, "DNS Filter: `nameserver` hash must include `address` (got `#{@nameserver}`)")
  nameserver = Array(nameserver).map(&:to_s)
  nameserver.empty? && fail(LogStash::ConfigurationError, "DNS Filter: `nameserver` addresses, when specified, cannot be empty (got `#{@nameserver}`)")

  search     = nameserver_hash.delete('search') || []
  search     = Array(search).map(&:to_s)
  search.size > 6 && fail(LogStash::ConfigurationError, "DNS Filter: A maximum of 6 `search` domains are accepted (got `#{@nameserver}`)")

  ndots      = nameserver_hash.delete('ndots') || 1
  ndots      = Integer(ndots)
  ndots <= 0 && fail(LogStash::ConfigurationError, "DNS Filter: `ndots` must be positive (got `#{@nameserver}`)")

  fail(LogStash::ConfigurationError, "Unknown `nameserver` argument(s): #{nameserver_hash}") unless nameserver_hash.empty?

  {
    :nameserver => nameserver,
    :search     => search,
    :ndots      => ndots
  }
end
resolv_getaddress_or_nil(resolver, name) click to toggle source
# File lib/logstash/filters/dns.rb, line 406
def resolv_getaddress_or_nil(resolver, name)
  # `Resolv#each_address` yields to the provided block zero or more times;
  # to prevent it from yielding multiple times when more than one match
  # is found, we return directly in the block.
  # See also `Resolv#getaddress`
  resolver.each_address(name) do |address|
    return address
  end

  # If no match was found, we return nil.
  return nil
end
resolv_getname_or_nil(resolver, address) click to toggle source
# File lib/logstash/filters/dns.rb, line 392
def resolv_getname_or_nil(resolver, address)
  # `Resolv#each_name` yields to the provided block zero or more times;
  # to prevent it from yielding multiple times when more than one match
  # is found, we return directly in the block.
  # See also `Resolv#getname`
  resolver.each_name(address) do |name|
    return name
  end

  # If no match was found, we return nil.
  return nil
end
resolve(event) click to toggle source
# File lib/logstash/filters/dns.rb, line 181
def resolve(event)
  @resolve.each do |field|
    is_array = false
    raw = event.get(field)

    if raw.nil?
      @logger.warn("DNS filter could not resolve missing field", :field => field)
      next
    end

    if raw.is_a?(Array)
      is_array = true
      if raw.length > 1
        @logger.warn("DNS: skipping resolve, can't deal with multiple values", :field => field, :value => raw)
        return
      end
      raw = raw.first
    end

    if !raw.kind_of?(String)
      @logger.warn("DNS: skipping resolve, can't deal with non-string values", :field => field, :value => raw)
      return
    end

    begin
      return if @failed_cache && @failed_cache[raw] # recently failed resolv, skip
      if @hit_cache
        address = @hit_cache[raw]
        if address.nil?
          if address = retriable_getaddress(raw)
            @hit_cache[raw] = address
          end
        end
      else
        address = retriable_getaddress(raw)
      end
      if address.nil?
        @failed_cache[raw] = true if @failed_cache
        @logger.debug("DNS: couldn't resolve the hostname.",
                      :field => field, :value => raw)
        return
      end
    rescue Resolv::ResolvTimeout
      @failed_cache[raw] = true if @failed_cache
      @logger.debug("DNS: timeout on resolving the hostname.",
                    :field => field, :value => raw)
      @tag_on_timeout.each { |tag| event.tag(tag) }
      return
    rescue SocketError => e
      @logger.error("DNS: Encountered SocketError.",
                    :field => field, :value => raw, :message => e.message)
      return
    rescue Java::JavaLang::IllegalArgumentException => e
      @logger.error("DNS: Unable to parse address.",
                    :field => field, :value => raw, :message => e.message)
      return
    rescue => e
      @logger.error("DNS: Unexpected Error.",
                    :field => field, :value => raw, :message => e.message)
      return
    end

    if @action == "replace"
      if is_array
        event.set(field, [address])
      else
        event.set(field, address)
      end
    else
      if !is_array
        event.set(field, [event.get(field), address])
      else
        arr = event.get(field)
        arr << address
        event.set(field, arr)
      end
    end

  end
end
retriable_getaddress(name) click to toggle source
# File lib/logstash/filters/dns.rb, line 371
def retriable_getaddress(name)
  retriable_request do
    getaddress(name)
  end
end
retriable_getname(address) click to toggle source
# File lib/logstash/filters/dns.rb, line 364
def retriable_getname(address)
  retriable_request do
    getname(address)
  end
end
retriable_request(&block) click to toggle source
# File lib/logstash/filters/dns.rb, line 349
def retriable_request(&block)
  tries = 0
  begin
    block.call
  rescue Resolv::ResolvTimeout, SocketError
    if tries < @max_retries
      tries = tries + 1
      retry
    else
      raise
    end
  end
end
reverse(event) click to toggle source
# File lib/logstash/filters/dns.rb, line 263
def reverse(event)
  @reverse.each do |field|
    raw = event.get(field)

    if raw.nil?
      @logger.warn("DNS filter could not perform reverse lookup on missing field", :field => field)
      next
    end

    is_array = false
    if raw.is_a?(Array)
        is_array = true
        if raw.length > 1
          @logger.warn("DNS: skipping reverse, can't deal with multiple values", :field => field, :value => raw)
          return
        end
        raw = raw.first
    end
    
    if !raw.kind_of?(String)
      @logger.warn("DNS: skipping reverse, can't deal with non-string values", :field => field, :value => raw)
      return
    end

    if ! @ip_validator.match(raw)
      @logger.debug("DNS: not an address",
                    :field => field, :value => event.get(field))
      return
    end
    begin
      return if @failed_cache && @failed_cache.key?(raw) # recently failed resolv, skip
      if @hit_cache
        hostname = @hit_cache[raw]
        if hostname.nil?
          if hostname = retriable_getname(raw)
            @hit_cache[raw] = hostname
          end
        end
      else
        hostname = retriable_getname(raw)
      end
      if hostname.nil?
        @failed_cache[raw] = true if @failed_cache
        @logger.debug("DNS: couldn't resolve the address.",
                      :field => field, :value => raw)
        return
      end
    rescue Resolv::ResolvTimeout
      @failed_cache[raw] = true if @failed_cache
      @logger.debug("DNS: timeout on resolving address.",
                    :field => field, :value => raw)
      @tag_on_timeout.each { |tag| event.tag(tag) }
      return
    rescue SocketError => e
      @logger.error("DNS: Encountered SocketError.",
                    :field => field, :value => raw, :message => e.message)
      return
    rescue Java::JavaLang::IllegalArgumentException => e
      @logger.error("DNS: Unable to parse address.",
                    :field => field, :value => raw, :message => e.message)
      return
    rescue => e
      @logger.error("DNS: Unexpected Error.",
                    :field => field, :value => raw, :message => e.message)
      return
    end

    if @action == "replace"
      if is_array
        event.set(field, [hostname])
      else
        event.set(field, hostname)
      end
    else
      if !is_array
        event.set(field, [event.get(field), hostname])
      else
        arr = event.get(field)
        arr << hostname
        event.set(field, arr)
      end
    end
  end
end