From a25e9511f87b6371be4e742c85c9986145703c0f Mon Sep 17 00:00:00 2001 From: dmachard <5562930+dmachard@users.noreply.github.com> Date: Sun, 12 Nov 2023 16:57:21 +0100 Subject: [PATCH] powerdns collector: replace go/dnsmessage by miekg/dns --- config.yml | 3 + dnsutils/constants.go | 10 ++-- processors/powerdns.go | 112 ++++++++++++++++-------------------- processors/powerdns_test.go | 75 +++++++++++++++++++++--- 4 files changed, 124 insertions(+), 76 deletions(-) diff --git a/config.yml b/config.yml index ec317e19..e4654573 100644 --- a/config.yml +++ b/config.yml @@ -70,6 +70,7 @@ multiplexer: dnstap: listen-ip: 0.0.0.0 listen-port: 6000 + add-dns-payload: false transforms: normalize: qname-lowercase: false @@ -199,6 +200,8 @@ multiplexer: # reset-conn: true # # Channel buffer size for incoming packets, number of packet before to drop it. # chan-buffer-size: 65535 +# # Reconstruct DNS payload +# add-dns-payload: false # # tzsp (TaZmen Sniffer Protocol) # tzsp: diff --git a/dnsutils/constants.go b/dnsutils/constants.go index 2d85029b..c13653c2 100644 --- a/dnsutils/constants.go +++ b/dnsutils/constants.go @@ -5,10 +5,12 @@ import "crypto/tls" const ( STR_UNKNOWN = "UNKNOWN" - PROG_NAME = "dnscollector" - LOCALHOST_IP = "127.0.0.1" - ANY_IP = "0.0.0.0" - HTTP_OK = "HTTP/1.1 200 OK\r\n\r\n" + PROG_NAME = "dnscollector" + LOCALHOST_IP = "127.0.0.1" + ANY_IP = "0.0.0.0" + HTTP_OK = "HTTP/1.1 200 OK\r\n\r\n" + + VALID_DOMAIN = "dnscollector.dev." BAD_LABEL_DOMAIN = "ultramegaverytoolonglabel-ultramegaverytoolonglabel-ultramegaverytoolonglabel.dnscollector.dev." BAD_VERYLONG_DOMAIN = "ultramegaverytoolonglabel.dnscollector" + "ultramegaverytoolonglabel-ultramegaverytoolonglabel-" + diff --git a/processors/powerdns.go b/processors/powerdns.go index 5ac7e1ac..7c3a4a2a 100644 --- a/processors/powerdns.go +++ b/processors/powerdns.go @@ -11,7 +11,7 @@ import ( "github.com/dmachard/go-dnscollector/transformers" "github.com/dmachard/go-logger" powerdns_protobuf "github.com/dmachard/go-powerdns-protobuf" - "golang.org/x/net/dns/dnsmessage" + "github.com/miekg/dns" "google.golang.org/protobuf/proto" ) @@ -292,76 +292,62 @@ RUN_LOOP: if d.config.Collectors.PowerDNS.AddDnsPayload { - qname, err := dnsmessage.NewName(pbdm.Question.GetQName()) - if err != nil { - dm.DNS.MalformedPacket = true - } else { + qname := dns.Fqdn(pbdm.Question.GetQName()) + newDns := new(dns.Msg) + newDns.Id = uint16(pbdm.GetId()) + newDns.Response = false - newDns := dnsmessage.Message{ - Header: dnsmessage.Header{ID: uint16(pbdm.GetId()), Response: false}, - Questions: []dnsmessage.Question{ - { - Name: qname, - Type: dnsmessage.Type(pbdm.Question.GetQType()), - Class: dnsmessage.Class(pbdm.Question.GetQClass()), - }, - }, - } - if int(pbdm.Type.Number())%2 != 1 { - newDns.Header.Response = true - newDns.Header.RCode = dnsmessage.RCode(pbdm.Response.GetRcode()) - - newDns.Answers = []dnsmessage.Resource{} - // add RR only A, AAAA and CNAME are exported by PowerDNS - rrs := pbdm.GetResponse().GetRrs() - - for j := range rrs { - rrname, err := dnsmessage.NewName(rrs[j].GetName()) - if err != nil { - dm.DNS.MalformedPacket = true - continue + question := dns.Question{ + Name: qname, + Qtype: uint16(pbdm.Question.GetQType()), + Qclass: uint16(pbdm.Question.GetQClass()), + } + newDns.Question = append(newDns.Question, question) + + if int(pbdm.Type.Number())%2 != 1 { + newDns.Response = true + newDns.Rcode = int(pbdm.Response.GetRcode()) + + newDns.Answer = []dns.RR{} + rrs := pbdm.GetResponse().GetRrs() + for j := range rrs { + rrname := dns.Fqdn(rrs[j].GetName()) + switch rrs[j].GetType() { + // A + case 1: + rdata := &dns.A{ + Hdr: dns.RR_Header{Name: rrname, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: rrs[j].GetTtl()}, + A: net.IP(rrs[j].GetRdata()), } - r := dnsmessage.Resource{} - r.Header = dnsmessage.ResourceHeader{ - Name: rrname, - Type: dnsmessage.Type(rrs[j].GetType()), - Class: dnsmessage.Class(rrs[j].GetClass()), - TTL: rrs[j].GetTtl(), + newDns.Answer = append(newDns.Answer, rdata) + // AAAA + case 28: + rdata := &dns.AAAA{ + Hdr: dns.RR_Header{Name: rrname, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: rrs[j].GetTtl()}, + AAAA: net.IP(rrs[j].GetRdata()), } - switch { - // A - case RRs[j].GetType() == 1: - var rdata [4]byte - copy(rdata[:], rrs[j].GetRdata()[:4]) - r.Body = &dnsmessage.AResource{A: rdata} - // AAAA - case RRs[j].GetType() == 28: - var rdata [16]byte - copy(rdata[:], rrs[j].GetRdata()[:16]) - r.Body = &dnsmessage.AAAAResource{AAAA: rdata} - // CNAME - case RRs[j].GetType() == 5: - cname, err := dnsmessage.NewName(string(rrs[j].GetRdata())) - if err != nil { - dm.DNS.MalformedPacket = true - continue - } - r.Body = &dnsmessage.CNAMEResource{CNAME: cname} + newDns.Answer = append(newDns.Answer, rdata) + // CNAME + case 5: + rdata := &dns.CNAME{ + Hdr: dns.RR_Header{Name: rrname, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: rrs[j].GetTtl()}, + Target: dns.Fqdn(string(rrs[j].GetRdata())), } - - newDns.Answers = append(newDns.Answers, r) - + newDns.Answer = append(newDns.Answer, rdata) } + } - if !dm.DNS.MalformedPacket { - pktWire, err := newDns.Pack() - if err == nil { - dm.DNS.Payload = pktWire - } else { - dm.DNS.MalformedPacket = true - } + } + + pktWire, err := newDns.Pack() + if err == nil { + dm.DNS.Payload = pktWire + if dm.DNS.Length == 0 { + dm.DNS.Length = len(pktWire) } + } else { + dm.DNS.MalformedPacket = true } } diff --git a/processors/powerdns_test.go b/processors/powerdns_test.go index 1dee486e..e422c279 100644 --- a/processors/powerdns_test.go +++ b/processors/powerdns_test.go @@ -6,6 +6,7 @@ import ( "github.com/dmachard/go-dnscollector/dnsutils" "github.com/dmachard/go-logger" powerdns_protobuf "github.com/dmachard/go-powerdns-protobuf" + "github.com/miekg/dns" "google.golang.org/protobuf/proto" ) @@ -14,8 +15,8 @@ func TestPowerDNS_Processor(t *testing.T) { consumer := NewPdnsProcessor(0, dnsutils.GetFakeConfig(), logger.New(false), "test", 512) chan_to := make(chan dnsutils.DnsMessage, 512) - // prepare dnstap - dnsQname := "test." + // init the powerdns processor + dnsQname := dnsutils.VALID_DOMAIN dnsQuestion := powerdns_protobuf.PBDNSMessage_DNSQuestion{QName: &dnsQname} dm := &powerdns_protobuf.PBDNSMessage{} @@ -38,7 +39,54 @@ func TestPowerDNS_Processor(t *testing.T) { } } -func TestPowerDNS_Processor_InvalidLabelLength(t *testing.T) { +func TestPowerDNS_Processor_AddDNSPayload_Valid(t *testing.T) { + cfg := dnsutils.GetFakeConfig() + cfg.Collectors.PowerDNS.AddDnsPayload = true + + // init the powerdns processor + consumer := NewPdnsProcessor(0, cfg, logger.New(false), "test", 512) + chan_to := make(chan dnsutils.DnsMessage, 1) + + // prepare powerdns message + dnsQname := dnsutils.VALID_DOMAIN + dnsQuestion := powerdns_protobuf.PBDNSMessage_DNSQuestion{QName: &dnsQname} + + dm := &powerdns_protobuf.PBDNSMessage{} + dm.ServerIdentity = []byte("powerdnspb") + dm.Type = powerdns_protobuf.PBDNSMessage_DNSQueryType.Enum() + dm.SocketProtocol = powerdns_protobuf.PBDNSMessage_DNSCryptUDP.Enum() + dm.SocketFamily = powerdns_protobuf.PBDNSMessage_INET.Enum() + dm.Question = &dnsQuestion + + data, _ := proto.Marshal(dm) + + // start the consumer and add packet + go consumer.Run([]chan dnsutils.DnsMessage{chan_to}, []string{"test"}) + consumer.GetChannel() <- data + + // read dns message + msg := <-chan_to + + // checks + if msg.DNS.Length == 0 { + t.Errorf("invalid length got %d", msg.DNS.Length) + } + if len(msg.DNS.Payload) == 0 { + t.Errorf("invalid payload length %d", len(msg.DNS.Payload)) + } + + // valid dns payload ? + var decodedPayload dns.Msg + err := decodedPayload.Unpack(msg.DNS.Payload) + if err != nil { + t.Errorf("unpack error %s", err) + } + if decodedPayload.Question[0].Name != dnsutils.VALID_DOMAIN { + t.Errorf("invalid qname in payload: %s", decodedPayload.Question[0].Name) + } +} + +func TestPowerDNS_Processor_AddDNSPayload_InvalidLabelLength(t *testing.T) { cfg := dnsutils.GetFakeConfig() cfg.Collectors.PowerDNS.AddDnsPayload = true @@ -48,10 +96,11 @@ func TestPowerDNS_Processor_InvalidLabelLength(t *testing.T) { // prepare dnstap dnsQname := dnsutils.BAD_LABEL_DOMAIN - dnsQuestion := powerdns_protobuf.PBDNSMessage_DNSQuestion{QName: &dnsQname} + dnsQuestion := powerdns_protobuf.PBDNSMessage_DNSQuestion{QName: &dnsQname, QType: proto.Uint32(1), QClass: proto.Uint32(1)} dm := &powerdns_protobuf.PBDNSMessage{} dm.ServerIdentity = []byte("powerdnspb") + dm.Id = proto.Uint32(2000) dm.Type = powerdns_protobuf.PBDNSMessage_DNSQueryType.Enum() dm.SocketProtocol = powerdns_protobuf.PBDNSMessage_DNSCryptUDP.Enum() dm.SocketFamily = powerdns_protobuf.PBDNSMessage_INET.Enum() @@ -70,7 +119,7 @@ func TestPowerDNS_Processor_InvalidLabelLength(t *testing.T) { } } -func TestPowerDNS_Processor_Qname_TooLongDomain(t *testing.T) { +func TestPowerDNS_Processor_AddDNSPayload_QnameTooLongDomain(t *testing.T) { cfg := dnsutils.GetFakeConfig() cfg.Collectors.PowerDNS.AddDnsPayload = true @@ -102,7 +151,7 @@ func TestPowerDNS_Processor_Qname_TooLongDomain(t *testing.T) { } } -func TestPowerDNS_Processor_Answers_TooLongDomain(t *testing.T) { +func TestPowerDNS_Processor_AddDNSPayload_AnswersTooLongDomain(t *testing.T) { cfg := dnsutils.GetFakeConfig() cfg.Collectors.PowerDNS.AddDnsPayload = true @@ -111,12 +160,18 @@ func TestPowerDNS_Processor_Answers_TooLongDomain(t *testing.T) { chan_to := make(chan dnsutils.DnsMessage, 512) // prepare dnstap - dnsQname := "dnscollector.dev." + dnsQname := dnsutils.VALID_DOMAIN dnsQuestion := powerdns_protobuf.PBDNSMessage_DNSQuestion{QName: &dnsQname} - dnsRR := dnsutils.BAD_VERYLONG_DOMAIN + rrQname := dnsutils.BAD_VERYLONG_DOMAIN + rrDns := powerdns_protobuf.PBDNSMessage_DNSResponse_DNSRR{ + Name: &rrQname, + Class: proto.Uint32(1), + Type: proto.Uint32(1), + Rdata: []byte{0x01, 0x00, 0x00, 0x01}, + } dnsReply := powerdns_protobuf.PBDNSMessage_DNSResponse{} - dnsReply.Rrs = append(dnsReply.Rrs, &powerdns_protobuf.PBDNSMessage_DNSResponse_DNSRR{Name: &dnsRR}) + dnsReply.Rrs = append(dnsReply.Rrs, &rrDns) dm := &powerdns_protobuf.PBDNSMessage{} dm.ServerIdentity = []byte("powerdnspb") @@ -134,6 +189,8 @@ func TestPowerDNS_Processor_Answers_TooLongDomain(t *testing.T) { // read dns message from dnstap consumer msg := <-chan_to + + // tests verifications if !msg.DNS.MalformedPacket { t.Errorf("DNS message is not malformed") }