diff --git a/README.md b/README.md index a0d9a989..abbf3e11 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ The [`_integration`](./docs/_integration) folder contains DNS-collector `configu ## Performance -Tuning may be neccesary to deal with a large traffic loads. +Tuning may be necessary to deal with a large traffic loads. Please refer to the [performance tuning](./docs/performance.md) guide if needed. ## Contributing diff --git a/config.yml b/config.yml index b794304d..8cfebbb3 100644 --- a/config.yml +++ b/config.yml @@ -47,8 +47,9 @@ global: # - protocol: protocol UDP, TCP # - length: the length of the query or reply in bytes # - length-unit: the length of the query or reply in bytes with unit - # - qtype: dns qtype - # - qname: dns qname + # - qtype: dns query type + # - qname: dns query name + # - qclass: dns query class # - latency: computed latency between queries and replies # - answercount: the number of answer # - ttl: answer ttl, only the first one value diff --git a/dnsutils/dns_parser.go b/dnsutils/dns_parser.go index 578042fc..78132ef7 100644 --- a/dnsutils/dns_parser.go +++ b/dnsutils/dns_parser.go @@ -14,6 +14,13 @@ const DNSLen = 12 const UNKNOWN = "UNKNOWN" var ( + Class = map[int]string{ + 1: "IN", + 3: "CH", + 4: "HS", + 254: "NONE", + 255: "ANY", + } Rdatatypes = map[int]string{ 0: "NONE", 1: "A", @@ -124,6 +131,7 @@ var ErrDecodeDNSLabelTooShort = errors.New("malformed pkt, dns payload too short var ErrDecodeQuestionQtypeTooShort = errors.New("malformed pkt, not enough data to decode qtype") var ErrDecodeDNSAnswerTooShort = errors.New("malformed pkt, not enough data to decode answer") var ErrDecodeDNSAnswerRdataTooShort = errors.New("malformed pkt, not enough data to decode rdata answer") +var ErrDecodeQuestionQclassTooShort = errors.New("malformed pkt, not enough data to decode qclass") func RdatatypeToString(rrtype int) string { if value, ok := Rdatatypes[rrtype]; ok { @@ -139,6 +147,13 @@ func RcodeToString(rcode int) string { return UNKNOWN } +func ClassToString(class int) string { + if value, ok := Class[class]; ok { + return value + } + return UNKNOWN +} + // error returned if decoding of DNS packet payload fails. type decodingError struct { part string @@ -273,7 +288,7 @@ func DecodePayload(dm *DNSMessage, header *DNSHeader, config *pkgconfig.Config) var payloadOffset int // decode DNS question if header.Qdcount > 0 { - dnsQname, dnsRRtype, offsetrr, err := DecodeQuestion(header.Qdcount, dm.DNS.Payload) + dnsQname, dnsRRtype, dnsQclass, offsetrr, err := DecodeQuestion(header.Qdcount, dm.DNS.Payload) if err != nil { dm.DNS.MalformedPacket = true return &decodingError{part: "query", err: err} @@ -281,6 +296,7 @@ func DecodePayload(dm *DNSMessage, header *DNSHeader, config *pkgconfig.Config) dm.DNS.Qname = dnsQname dm.DNS.Qtype = RdatatypeToString(dnsRRtype) + dm.DNS.Qclass = ClassToString(dnsQclass) payloadOffset = offsetrr } @@ -358,10 +374,11 @@ DNS QUESTION | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ -func DecodeQuestion(qdcount int, payload []byte) (string, int, int, error) { +func DecodeQuestion(qdcount int, payload []byte) (string, int, int, int, error) { offset := DNSLen var qname string var qtype int + var qclass int for i := 0; i < qdcount; i++ { // the specification allows more than one query in DNS packet, @@ -373,18 +390,26 @@ func DecodeQuestion(qdcount int, payload []byte) (string, int, int, error) { // Decode QNAME qname, offset, err = ParseLabels(offset, payload) if err != nil { - return "", 0, 0, err + return "", 0, 0, 0, err } // decode QTYPE and support invalid packet, some abuser sends it... - if len(payload[offset:]) < 4 { - return "", 0, 0, ErrDecodeQuestionQtypeTooShort + if len(payload[offset:]) < 2 { + return "", 0, 0, 0, ErrDecodeQuestionQtypeTooShort } else { qtype = int(binary.BigEndian.Uint16(payload[offset : offset+2])) - offset += 4 + offset += 2 + } + + // decode QCLASS + if len(payload[offset:]) < 2 { + return "", 0, 0, 0, ErrDecodeQuestionQclassTooShort + } else { + qclass = int(binary.BigEndian.Uint16(payload[offset : offset+2])) + offset += 2 } } - return qname, qtype, offset, nil + return qname, qtype, qclass, offset, nil } /* @@ -461,7 +486,7 @@ func DecodeAnswer(ancount int, startOffset int, payload []byte) ([]DNSAnswer, in a := DNSAnswer{ Name: name, Rdatatype: rdatatype, - Class: int(class), + Class: ClassToString(int(class)), TTL: int(ttl), Rdata: parsed, } diff --git a/dnsutils/dns_parser_test.go b/dnsutils/dns_parser_test.go index 5bc31147..0c62a7ff 100644 --- a/dnsutils/dns_parser_test.go +++ b/dnsutils/dns_parser_test.go @@ -76,7 +76,11 @@ func TestDecodeQuestion(t *testing.T) { dm.SetQuestion(fqdn, dns.TypeA) payload, _ := dm.Pack() - qname, qtype, offsetRR, _ := DecodeQuestion(1, payload) + qname, qtype, qclass, offsetRR, _ := DecodeQuestion(1, payload) + if ClassToString(qclass) != "IN" { + t.Errorf("invalid qclass: %d", qclass) + } + if qname+"." != fqdn { t.Errorf("invalid qname: %s", qname) } @@ -107,13 +111,16 @@ func TestDecodeQuestion_Multiple(t *testing.T) { 0x00, 0x1c, 0x00, 0x01, } - qname, qtype, offset, err := DecodeQuestion(3, paylaod) + qname, qtype, qclass, offset, err := DecodeQuestion(3, paylaod) if err != nil { t.Errorf("unexpected error %v", err) } if qname != "c" || RdatatypeToString(qtype) != "AAAA" { t.Errorf("expected qname=C, type=AAAA, got qname=%s, type=%s", qname, RdatatypeToString(qtype)) } + if ClassToString(qclass) != "IN" { + t.Errorf("expected qclass=IN %s", ClassToString(qclass)) + } if offset != 33 { t.Errorf("expected resulting offset to be 33, got %d", offset) } @@ -137,7 +144,7 @@ func TestDecodeQuestion_Multiple_InvalidCount(t *testing.T) { 0x00, 0x1c, 0x00, 0x01, } - _, _, _, err := DecodeQuestion(4, paylaod) + _, _, _, _, err := DecodeQuestion(4, paylaod) if !errors.Is(err, ErrDecodeDNSLabelTooShort) { t.Errorf("bad error received: %v", err) } @@ -159,7 +166,7 @@ func TestDecodeAnswer_Ns(t *testing.T) { m.Ns = append(m.Ns, rrNs) payload, _ := m.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, offsetRRns, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) nsAnswers, _, _ := DecodeAnswer(len(m.Ns), offsetRRns, payload) @@ -180,7 +187,7 @@ func TestDecodeAnswer(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if len(answer) != len(dm.Answer) { @@ -201,7 +208,7 @@ func TestDecodeRdataSVCB_alias(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -229,7 +236,7 @@ func TestDecodeRdataSVCB_params(t *testing.T) { rr1, _ := dns.NewRR(fmt.Sprintf("%s SVCB %s", fqdn, rdata)) dm.Answer = append(dm.Answer, rr1) payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { t.Errorf("invalid decode for rdata SVCB, want %s, got: %s", rdata, answer[0].Rdata) @@ -251,7 +258,7 @@ func TestDecodeAnswer_QnameMinimized(t *testing.T) { 0xc0, 0x7e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x04, 0x34, 0x71, 0xc3, 0x84, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(4, offsetRR, payload) if err != nil { t.Errorf("failed to decode valid dns packet with minimization") @@ -270,7 +277,7 @@ func TestDecodeRdataA(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -301,7 +308,7 @@ func TestDecodeRdataA_Short(t *testing.T) { // RDATA (1 byte too short for A record) 0x7f, 0x00, 0x00, } - _, _, offsetrr, err := DecodeQuestion(1, payload) + _, _, _, offsetrr, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("Unexpected error decoding question: %v", err) } @@ -324,7 +331,7 @@ func TestDecodeRdataAAAA(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -358,7 +365,7 @@ func TestDecodeRdataAAAA_Short(t *testing.T) { 0x00, 0x00, 0x00, } - _, _, offsetSetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetSetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -380,7 +387,7 @@ func TestDecodeRdataCNAME(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -400,7 +407,7 @@ func TestDecodeRdataMX(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -432,7 +439,7 @@ func TestDecodeRdataMX_Short(t *testing.T) { // RDATA 0x00, } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -467,7 +474,7 @@ func TestDecodeRdataMX_Minimal(t *testing.T) { // RDATA 0x00, 0x00, 0x00, } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -493,7 +500,7 @@ func TestDecodeRdataSRV(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -530,7 +537,7 @@ func TestDecodeRdataSRV_Short(t *testing.T) { // missing port and target } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -572,7 +579,7 @@ func TestDecodeRdataSRV_Minimal(t *testing.T) { 0x00, } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -597,7 +604,7 @@ func TestDecodeRdataNS(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -617,7 +624,7 @@ func TestDecodeRdataTXT(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -649,7 +656,7 @@ func TestDecodeRdataTXT_Empty(t *testing.T) { // no data } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -689,7 +696,7 @@ func TestDecodeRdataTXT_Short(t *testing.T) { // missing two bytes } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -726,7 +733,7 @@ func TestDecodeRdataTXT_NoTxt(t *testing.T) { // no txt-data } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -753,7 +760,7 @@ func TestDecodeRdataPTR(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -773,7 +780,7 @@ func TestDecodeRdataSOA(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload) if answer[0].Rdata != rdata { @@ -823,7 +830,7 @@ func TestDecodeRdataSOA_Short(t *testing.T) { // minimum -field missing from the RDATA } - _, _, offsetRR, err := DecodeQuestion(1, payload) + _, _, _, offsetRR, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("Unable to decode question: %v", err) } @@ -840,7 +847,7 @@ func TestDecodeRdataSOA_Minimization(t *testing.T) { 51, 3, 111, 118, 104, 3, 110, 101, 116, 0, 4, 116, 101, 99, 104, 192, 53, 120, 119, 219, 34, 0, 1, 81, 128, 0, 0, 14, 16, 0, 54, 238, 128, 0, 0, 0, 60} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(1, offsetRR, payload) if err != nil { t.Errorf(" error returned: %v", err) @@ -882,7 +889,7 @@ func TestDecodeQuestion_SkipOpt(t *testing.T) { // RDATA 0x7f, 0x00, 0x00, 0x01, } - _, _, offsetrr, err := DecodeQuestion(1, payload) + _, _, _, offsetrr, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("Unexpected error decoding question: %v", err) } @@ -909,7 +916,7 @@ func TestDecodeDns_HeaderTooShort(t *testing.T) { func TestDecodeDnsQuestion_InvalidOffset(t *testing.T) { decoded := []byte{183, 59, 130, 217, 128, 16, 0, 51, 165, 67, 0, 0} - _, _, _, err := DecodeQuestion(1, decoded) + _, _, _, _, err := DecodeQuestion(1, decoded) if !errors.Is(err, ErrDecodeDNSLabelTooShort) { t.Errorf("bad error returned: %v", err) } @@ -917,7 +924,7 @@ func TestDecodeDnsQuestion_InvalidOffset(t *testing.T) { func TestDecodeDnsQuestion_PacketTooShort(t *testing.T) { decoded := []byte{183, 59, 130, 217, 128, 16, 0, 51, 165, 67, 0, 0, 1, 1, 8, 10, 23} - _, _, _, err := DecodeQuestion(1, decoded) + _, _, _, _, err := DecodeQuestion(1, decoded) if !errors.Is(err, ErrDecodeDNSLabelTooShort) { t.Errorf("bad error returned: %v", err) } @@ -926,7 +933,7 @@ func TestDecodeDnsQuestion_PacketTooShort(t *testing.T) { func TestDecodeDnsQuestion_QtypeMissing(t *testing.T) { decoded := []byte{88, 27, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 15, 100, 110, 115, 116, 97, 112, 99, 111, 108, 108, 101, 99, 116, 111, 114, 4, 116, 101, 115, 116, 0} - _, _, _, err := DecodeQuestion(1, decoded) + _, _, _, _, err := DecodeQuestion(1, decoded) if !errors.Is(err, ErrDecodeQuestionQtypeTooShort) { t.Errorf("bad error returned: %v", err) } @@ -934,7 +941,7 @@ func TestDecodeDnsQuestion_QtypeMissing(t *testing.T) { func TestDecodeQuestion_InvalidPointer(t *testing.T) { decoded := []byte{88, 27, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 202} - _, _, _, err := DecodeQuestion(1, decoded) + _, _, _, _, err := DecodeQuestion(1, decoded) if !errors.Is(err, ErrDecodeDNSLabelTooShort) { t.Errorf("bad error returned: %v", err) } @@ -945,7 +952,7 @@ func TestDecodeDnsAnswer_PacketTooShort(t *testing.T) { 111, 114, 4, 116, 101, 115, 116, 0, 0, 1, 0, 1, 15, 100, 110, 115, 116, 97, 112, 99, 111, 108, 108, 101, 99, 116, 111, 114, 4, 116, 101, 115, 116, 0, 0, 1, 0, 1, 0, 0, 14, 16, 0} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(1, offsetRR, payload) if !errors.Is(err, ErrDecodeDNSAnswerTooShort) { t.Errorf("bad error returned: %v", err) @@ -1027,7 +1034,7 @@ func TestDecodeDnsAnswer_RdataTooShort(t *testing.T) { 111, 114, 4, 116, 101, 115, 116, 0, 0, 1, 0, 1, 15, 100, 110, 115, 116, 97, 112, 99, 111, 108, 108, 101, 99, 116, 111, 114, 4, 116, 101, 115, 116, 0, 0, 1, 0, 1, 0, 0, 14, 16, 0, 4, 127, 0} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(1, offsetRR, payload) if !errors.Is(err, ErrDecodeDNSAnswerRdataTooShort) { t.Errorf("bad error returned: %v", err) @@ -1039,7 +1046,7 @@ func TestDecodeDnsAnswer_InvalidPtr(t *testing.T) { 109, 99, 104, 100, 2, 109, 101, 0, 0, 1, 0, 1, 192, 254, 0, 1, 0, 1, 0, 0, 14, 16, 0, 4, 83, 112, 146, 176} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(1, offsetRR, payload) if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) { t.Errorf("bad error returned: %v", err) @@ -1052,7 +1059,7 @@ func TestDecodeDnsAnswer_InvalidPtr_Loop1(t *testing.T) { 109, 99, 104, 100, 2, 109, 101, 0, 0, 1, 0, 1, 192, 31, 0, 1, 0, 1, 0, 0, 14, 16, 0, 4, 83, 112, 146, 176} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(1, offsetRR, payload) if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) { t.Errorf("bad error returned: %v", err) @@ -1066,7 +1073,7 @@ func TestDecodeDnsAnswer_InvalidPtr_Loop2(t *testing.T) { 14, 16, 0, 4, 83, 112, 146, 176, 192, 31, 0, 1, 0, 1, 0, 0, 14, 16, 0, 4, 83, 112, 146, 176} - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeAnswer(1, offsetRR, payload) if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) { t.Errorf("bad error returned: %v", err) @@ -1541,7 +1548,7 @@ func TestDecodePayload_AnswerHappy(t *testing.T) { expected := DNSAnswer{ Name: dm.DNS.Qname, Rdatatype: RdatatypeToString(0x0001), - Class: 0x0001, + Class: "IN", // 0x0001, TTL: 300, Rdata: fmt.Sprintf("10.10.1.%d", i+1), } @@ -1662,7 +1669,7 @@ func TestDecodePayload_AnswerMultipleQueries(t *testing.T) { expected := DNSAnswer{ Name: "s" + dm.DNS.Qname, // answers have qname from 1st query data, 2nd data is missing 's' Rdatatype: RdatatypeToString(0x0001), - Class: 0x0001, + Class: "IN", // 0x0001, TTL: 300, Rdata: fmt.Sprintf("10.10.1.%d", i+1), } @@ -2049,7 +2056,7 @@ func TestDecodePayload_AnswerError(t *testing.T) { expected := DNSAnswer{ Name: "google.com", Rdatatype: RdatatypeToString(0x0006), - Class: 0x0001, + Class: "IN", // 0x0001, TTL: 60, Rdata: "ns1.google.com dns-admin.google.com 430000820 900 900 1800 60", } diff --git a/dnsutils/edns_parser_test.go b/dnsutils/edns_parser_test.go index f5ba0698..a8b57fdc 100644 --- a/dnsutils/edns_parser_test.go +++ b/dnsutils/edns_parser_test.go @@ -26,7 +26,7 @@ func TestDecodeQuery_EDNS(t *testing.T) { payload, _ := dm.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, _, err := DecodeEDNS(len(dm.Extra), offsetRR, payload) if err != nil { @@ -63,7 +63,7 @@ func TestDecodeReply_EDNS(t *testing.T) { m.SetRcode(dm, 42) // 32(extended rcode) + 10(rcode) payload, _ := m.Pack() - _, _, offsetRR, _ := DecodeQuestion(1, payload) + _, _, _, offsetRR, _ := DecodeQuestion(1, payload) _, offsetRR, _ = DecodeAnswer(len(m.Answer), offsetRR, payload) _, _, err := DecodeEDNS(len(m.Extra), offsetRR, payload) @@ -109,7 +109,7 @@ func TestDecodeQuery_EdnsSubnet(t *testing.T) { 0xc0, 0xa8, 0x01, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -179,7 +179,7 @@ func TestDecodeQuery_EdnsSubnetV6(t *testing.T) { 0xfe, 0x80, 0x01, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -250,7 +250,7 @@ func TestDecodeQuery_EdnsSubnet_invalidFam(t *testing.T) { 0xfe, 0x80, 0x01, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -308,7 +308,7 @@ func TestDecodeQuery_EdnsSubnet_Short(t *testing.T) { // 0xfe, 0x80, 0x01, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -366,7 +366,7 @@ func TestDecodeQuery_EdnsSubnet_NoAddr(t *testing.T) { // 0xfe, 0x80, 0x01, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -429,7 +429,7 @@ func TestDecodeAnswer_EdnsError(t *testing.T) { 0x00, 0x17, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -485,7 +485,7 @@ func TestDecodeAnswer_EdnsErrorText(t *testing.T) { 0x62, 0x30, 0x72, 0x6b, 0x65, 0x6e, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } @@ -540,7 +540,7 @@ func TestDecodeAnswer_EdnsErrorShort(t *testing.T) { 0x00, } - _, _, offset, err := DecodeQuestion(1, payload) + _, _, _, offset, err := DecodeQuestion(1, payload) if err != nil { t.Errorf("unexpected error while decoding question: %v", err) } diff --git a/dnsutils/message.go b/dnsutils/message.go index 73a764e1..c5ecd722 100644 --- a/dnsutils/message.go +++ b/dnsutils/message.go @@ -67,7 +67,7 @@ func GetIPPort(dm *DNSMessage) (string, int, string, int) { type DNSAnswer struct { Name string `json:"name"` Rdatatype string `json:"rdatatype"` - Class int `json:"-"` + Class string `json:"class"` TTL int `json:"ttl"` Rdata string `json:"rdata"` } @@ -107,6 +107,7 @@ type DNS struct { Opcode int `json:"opcode"` Rcode string `json:"rcode"` Qname string `json:"qname"` + Qclass string `json:"qclass"` Qtype string `json:"qtype"` Flags DNSFlags `json:"flags"` @@ -277,6 +278,7 @@ func (dm *DNSMessage) Init() { Rcode: "-", Qtype: "-", Qname: "-", + Qclass: "-", DNSRRs: DNSRRs{Answers: []DNSAnswer{}, Nameservers: []DNSAnswer{}, Records: []DNSAnswer{}}, } @@ -652,6 +654,8 @@ func (dm *DNSMessage) ToTextLine(format []string, fieldDelimiter string, fieldBo s.WriteString(strconv.Itoa(dm.DNS.Length)) case directive == "qtype": s.WriteString(dm.DNS.Qtype) + case directive == "qclass": + s.WriteString(dm.DNS.Qclass) case directive == "latency": s.WriteString(dm.DNSTap.LatencySec) case directive == "malformed": @@ -1061,6 +1065,7 @@ func (dm *DNSMessage) Flatten() (map[string]interface{}, error) { "dns.opcode": dm.DNS.Opcode, "dns.qname": dm.DNS.Qname, "dns.qtype": dm.DNS.Qtype, + "dns.qclass": dm.DNS.Qclass, "dns.rcode": dm.DNS.Rcode, "dnstap.identity": dm.DNSTap.Identity, "dnstap.latency": dm.DNSTap.LatencySec, @@ -1111,6 +1116,7 @@ func (dm *DNSMessage) Flatten() (map[string]interface{}, error) { dnsFields[prefixAn+".rdata"] = an.Rdata dnsFields[prefixAn+".rdatatype"] = an.Rdatatype dnsFields[prefixAn+".ttl"] = an.TTL + dnsFields[prefixAn+".class"] = an.Class } for i, ns := range dm.DNS.DNSRRs.Nameservers { prefixNs := "dns.resource-records.ns." + strconv.Itoa(i) @@ -1118,6 +1124,7 @@ func (dm *DNSMessage) Flatten() (map[string]interface{}, error) { dnsFields[prefixNs+".rdata"] = ns.Rdata dnsFields[prefixNs+".rdatatype"] = ns.Rdatatype dnsFields[prefixNs+".ttl"] = ns.TTL + dnsFields[prefixNs+".class"] = ns.Class } for i, ar := range dm.DNS.DNSRRs.Records { prefixAr := "dns.resource-records.ar." + strconv.Itoa(i) @@ -1125,6 +1132,7 @@ func (dm *DNSMessage) Flatten() (map[string]interface{}, error) { dnsFields[prefixAr+".rdata"] = ar.Rdata dnsFields[prefixAr+".rdatatype"] = ar.Rdatatype dnsFields[prefixAr+".ttl"] = ar.TTL + dnsFields[prefixAr+".class"] = ar.Class } // Add EDNSoptions fields: "edns.options.0.code": 10, diff --git a/dnsutils/message_test.go b/dnsutils/message_test.go index 778e8ec9..fe966ef7 100644 --- a/dnsutils/message_test.go +++ b/dnsutils/message_test.go @@ -201,6 +201,7 @@ func TestDnsMessage_Json_Reference(t *testing.T) { "rcode": "-", "qname": "-", "qtype": "-", + "qclass": "-", "flags": { "qr": false, "tc": false, @@ -425,7 +426,7 @@ func TestDnsMessage_JsonFlatten_Reference(t *testing.T) { dm.Init() // add some items in slices field - dm.DNS.DNSRRs.Answers = append(dm.DNS.DNSRRs.Answers, DNSAnswer{Name: "google.nl", Rdata: "142.251.39.99", Rdatatype: "A", TTL: 300}) + dm.DNS.DNSRRs.Answers = append(dm.DNS.DNSRRs.Answers, DNSAnswer{Name: "google.nl", Rdata: "142.251.39.99", Rdatatype: "A", TTL: 300, Class: "IN"}) dm.EDNS.Options = append(dm.EDNS.Options, DNSOption{Code: 10, Data: "aaaabbbbcccc", Name: "COOKIE"}) refJSON := ` @@ -444,10 +445,12 @@ func TestDnsMessage_JsonFlatten_Reference(t *testing.T) { "dns.qname": "-", "dns.qtype": "-", "dns.rcode": "-", + "dns.qclass": "-", "dns.resource-records.an.0.name": "google.nl", "dns.resource-records.an.0.rdata": "142.251.39.99", "dns.resource-records.an.0.rdatatype": "A", "dns.resource-records.an.0.ttl": 300, + "dns.resource-records.an.0.class": "IN", "dns.resource-records.ar": "-", "dns.resource-records.ns": "-", "dnstap.identity": "-", @@ -869,6 +872,11 @@ func TestDnsMessage_TextFormat_DefaultDirectives(t *testing.T) { dm: DNSMessage{DNS: DNS{Qname: "dnscollector.fr", Qtype: "AAAA", Opcode: 42}}, expected: "dnscollector.fr AAAA 42", }, + { + format: "qclass", + dm: DNSMessage{DNS: DNS{Qclass: "CH"}}, + expected: "CH", + }, { format: "operation", dm: DNSMessage{DNSTap: DNSTap{Operation: "CLIENT_QUERY"}}, diff --git a/docs/configuration.md b/docs/configuration.md index d471f78c..05a561c6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2,7 +2,9 @@ The configuration of DNS-collector is done through one yaml file named [`config.yml`](https://github.com/dmachard/go-dnscollector/blob/main/config.yml). When the DNS-collector starts, it will look for the config.yml from the current working directory. -A typically configuration in [multiplexer](./docs/running_mode.md) mode would have one or more collector to receive DNS traffic, and severals loggers to process the incoming traffics. You can take a look to the list of config [`examples`](examples.md). +A typically configuration in [multiplexer](./running_mode.md) mode would have one or more collector to receive DNS traffic, and severals loggers to process the incoming traffics. You can take a look to the list of config [`examples`](examples.md). + +## Global You can find the global settings below @@ -11,8 +13,6 @@ You can find the global settings below - [Custom text format](#custom-text-format) - [Server identity](#server-identity) -## Global - ### Trace Logs can be enable to have more informations like debug, errors messages generated by the application @@ -88,8 +88,9 @@ Default directives: - `protocol`: protocol UDP, TCP - `length`: the length of the query or reply in bytes - `length-unit`: the length of the query or reply in bytes with unit (`b`) -- `qtype`: dns qtype -- `qname`: dns qname +- `qtype`: dns query type +- `qclass`: dns query class +- `qname`: dns query name - `latency`: computed latency between queries and replies - `answercount`: the number of answer - `ttl`: answer ttl, only the first one diff --git a/docs/dnsjson.md b/docs/dnsjson.md index e8f0193e..2c0003a2 100644 --- a/docs/dnsjson.md +++ b/docs/dnsjson.md @@ -28,6 +28,7 @@ Example: "qname": "eu.org", "qtype": "A", "id": 23455, + "qclass": "IN", "flags": { "qr": true, "tc": false, @@ -43,7 +44,8 @@ Example: "name": "eu.org", "rdatatype": "A", "ttl": 2797, - "rdata": "78.194.169.74" + "rdata": "78.194.169.74", + "class": "IN" } ], "ns": [], @@ -111,10 +113,12 @@ Here's a flat JSON output formatted using `jq`: "dns.qname": "google.nl", "dns.qtype": "A", "dns.rcode": "NOERROR", + "dns.qclass": "IN", "dns.resource-records.an.0.name": "google.nl", "dns.resource-records.an.0.rdata": "142.251.39.99", "dns.resource-records.an.0.rdatatype": "A", "dns.resource-records.an.0.ttl": 300, + "dns.resource-records.an.0.class": "IN", "dns.resource-records.ar": "-", "dns.resource-records.ns": "-", "dnstap.identity": "foo", diff --git a/processors/powerdns.go b/processors/powerdns.go index 64715f9f..fd41c6ba 100644 --- a/processors/powerdns.go +++ b/processors/powerdns.go @@ -270,7 +270,7 @@ RUN_LOOP: rr := dnsutils.DNSAnswer{ Name: RRs[j].GetName(), Rdatatype: dnsutils.RdatatypeToString(int(RRs[j].GetType())), - Class: int(RRs[j].GetClass()), + Class: dnsutils.ClassToString(int(RRs[j].GetClass())), TTL: int(RRs[j].GetTtl()), Rdata: rdata, }