logstash-plugins / logstash-filter-dns

Apache License 2.0
7 stars 28 forks source link

plugin fails (doing TCP fallback) with big DNS records #64

Open kares opened 3 years ago

kares commented 3 years ago

Plugin version 3.1.4 (and bellow) will likely run into issues with DNS records >~ 65.500 bytes.

Description of the problem including expected versus actual behavior

DNS primarily uses UDP and a response usually fits into a UDP datagram. However, in rare cases the payload might be bigger than 65k, in which case the Resolv library will attempt to do a TCP fallback. This fallback mechanism has a bug when multiple name-servers are involved in a query.

There's no actual reproducer but here's what I assume happens :

Provide logs (if relevant): N/A 3.1.4 did not have backtrace/class logging for unknown errors. DNS: Unexpected Error.


Seems to be a known upstream Resolv bug: https://bugs.ruby-lang.org/issues/8285

kares commented 3 years ago

the issue can be emulated using the following spec:

describe Resolv do

  context 'logstash-plugins/logstash-filter-dns#64' do

    let(:nameserver_port) { [['127.0.0.1', 5678], ['192.168.123.201', 53]] }

    let(:udp_server) do
      UDPSocket.new.tap { |s| s.bind('127.0.0.1', 5678) }
    end
    let(:tcp_server) do
      TCPServer.new('127.0.0.1', 5678).tap { |s| }
    end

    let(:resolv_config) do
      [ ::Resolv::DNS.new(:nameserver_port => nameserver_port).tap { |resolver| resolver.timeouts = 0.1 } ]
    end

    let(:resolv) { Resolv.new(resolv_config) }

    let(:name) { 'the-foo.bar' }

    before(:all) { require "logstash/filters/dns/resolv_patch" }
    after { [ udp_server, tcp_server ].each(&:close) }

    before do
      expect_any_instance_of(Resolv::DNS::Requester::UnconnectedUDP).to receive(:request) do |sender, tout|
        message = Resolv::DNS::Message.new(42)
        message.tc = 1 # truncation bit - we emulate a UDP data size overflow (to switch to TCP)
        data = tout.instance_variable_get(:@data) || fail("no @data for #{sender.inspect}") # Resolv::DNS::Name
        [ message, data ]
      end
      # start listening on TCP/UDP 5678
      udp_server
      tcp_server
    end

    it 'raises when trying to resolve using TCP with multiple nameservers being used' do
      address = resolv.getaddress(name)
      # raises Resolv::DNS::Requester::RequestError: host/port don't match: 192.168.123.201:53
      p address 
    end

  end

end