diff --git a/README.md b/README.md index 8a90709f..834e1b89 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@
- + - +
diff --git a/dnsutils/constant.go b/dnsutils/constant.go
index 89bd7fa0..d37b2d3f 100644
--- a/dnsutils/constant.go
+++ b/dnsutils/constant.go
@@ -27,3 +27,7 @@ const (
ErrorUnexpectedDirective = "unexpected text format directive: "
)
+
+const (
+ TestQName = "dnstapcollector.test."
+)
diff --git a/dnsutils/dns_parser_answer_test.go b/dnsutils/dns_parser_answer_test.go
new file mode 100644
index 00000000..77b94495
--- /dev/null
+++ b/dnsutils/dns_parser_answer_test.go
@@ -0,0 +1,208 @@
+package dnsutils
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/miekg/dns"
+)
+
+func TestDecodeAnswer_Ns(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rrNs, _ := dns.NewRR("root-servers.net NS c.root-servers.net")
+ rrA, _ := dns.NewRR(fmt.Sprintf("%s A 127.0.0.1", fqdn))
+
+ m := new(dns.Msg)
+ m.SetReply(dm)
+ m.Authoritative = true
+ m.Answer = append(m.Answer, rrA)
+ m.Ns = append(m.Ns, rrNs)
+
+ payload, _ := m.Pack()
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ _, offsetRRns, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ nsAnswers, _, _ := DecodeAnswer(len(m.Ns), offsetRRns, payload)
+ if len(nsAnswers) != len(m.Ns) {
+ t.Errorf("invalid decode answer, want %d, got: %d", len(m.Ns), len(nsAnswers))
+ }
+}
+
+func TestDecodeAnswer(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s A 127.0.0.1", fqdn))
+ rr2, _ := dns.NewRR(fmt.Sprintf("%s A 127.0.0.2", fqdn))
+ dm.Answer = append(dm.Answer, rr1)
+ dm.Answer = append(dm.Answer, rr2)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if len(answer) != len(dm.Answer) {
+ t.Errorf("invalid decode answer, want %d, got: %d", len(dm.Answer), len(answer))
+ }
+}
+
+func TestDecodeAnswer_QnameMinimized(t *testing.T) {
+ payload := []byte{0x8d, 0xda, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x05, 0x74,
+ 0x65, 0x61, 0x6d, 0x73, 0x09, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00,
+ 0x01, 0x00, 0x00, 0x50, 0xa8, 0x00, 0x0f, 0x05, 0x74, 0x65, 0x61, 0x6d, 0x73, 0x06,
+ 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0xc0, 0x1c, 0xc0, 0x31, 0x00, 0x05, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x3e, 0x00, 0x26, 0x10, 0x74, 0x65, 0x61, 0x6d, 0x73, 0x2d, 0x6f, 0x66,
+ 0x66, 0x69, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x06, 0x73, 0x2d, 0x30, 0x30, 0x30, 0x35,
+ 0x08, 0x73, 0x2d, 0x6d, 0x73, 0x65, 0x64, 0x67, 0x65, 0x03, 0x6e, 0x65, 0x74, 0x00, 0xc0,
+ 0x4c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x13, 0x06, 0x73, 0x2d, 0x30,
+ 0x30, 0x30, 0x35, 0x09, 0x64, 0x63, 0x2d, 0x6d, 0x73, 0x65, 0x64, 0x67, 0x65, 0xc0, 0x6d,
+ 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)
+ _, _, err := DecodeAnswer(4, offsetRR, payload)
+ if err != nil {
+ t.Errorf("failed to decode valid dns packet with minimization")
+ }
+}
+
+func TestDecodeDnsAnswer_PacketTooShort(t *testing.T) {
+ payload := []byte{46, 172, 1, 0, 0, 1, 0, 1, 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, 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)
+ _, _, err := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(err, ErrDecodeDNSAnswerTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsAnswer_PathologicalPacket(t *testing.T) {
+ // Create a message with one question and `n` answers (`n` determined later).
+ decoded := make([]byte, 65500)
+ copy(decoded, []byte{88, 27, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0})
+
+ // Create a rather suboptimal name for the question.
+ // The answers point to this a bajillion times later.
+ // This name breaks several rules:v
+ // * Label length is > 63
+ // * Name length is > 255 bytes
+ // * Pointers jump all over the place, not just backwards
+ i := 12
+ for {
+ // Create a bunch of interleaved labels of length 191,
+ // each label immediately followed by a pointer to the
+ // label next to it. The last label of the interleaved chunk
+ // is followed with a pointer to forwards to the next chunk
+ // of interleaved labels:
+ //
+ // [191 ... 191 ... 191 ... ... ptr1 ptr2 ... ptrN 191 ... 191 ...]
+ // ^ ^ │ │ │ ^
+ // │ └────────────┼────┘ └────┘
+ // └───────────────────┘
+ //
+ // We then repeat this pattern as many times as we can within the
+ // first 16383 bytes (so that we can point to it later).
+ // Then cleanly closing the name with a null byte in the end allows us to
+ // create a name of around 700 kilobytes (I checked once, don't quote me on this).
+ if 16384-i < 384 {
+ decoded[i] = 0
+ break
+ }
+ for j := 0; j < 192; j += 2 {
+ decoded[i] = 191
+ i += 2
+ }
+ for j := 0; j < 190; j += 2 {
+ offset := i - 192 + 2
+ decoded[i] = 0xc0 | byte(offset>>8)
+ decoded[i+1] = byte(offset & 0xff)
+ i += 2
+ }
+ offset := i + 2
+ decoded[i] = 0xc0 | byte(offset>>8)
+ decoded[i+1] = byte(offset & 0xff)
+ i += 2
+ }
+
+ // Fill in the rest of the question
+ copy(decoded[i:], []byte{0, 5, 0, 1})
+ i += 4
+
+ // Fit as many answers as we can that contain CNAME RDATA pointing to
+ // the bloated name created above.
+ ancount := 0
+ for j := i; j+13 <= len(decoded); j += 13 {
+ copy(decoded[j:], []byte{0, 0, 5, 0, 0, 0, 0, 0, 1, 0, 2, 192, 12})
+ ancount += 1
+ }
+
+ // Update the message with the answer count
+ decoded[6] = byte(ancount >> 8)
+ decoded[7] = byte(ancount & 0xff)
+
+ _, _, err := DecodeAnswer(ancount, i, decoded)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsAnswer_RdataTooShort(t *testing.T) {
+ payload := []byte{46, 172, 1, 0, 0, 1, 0, 1, 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, 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)
+ _, _, err := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(err, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsAnswer_InvalidPtr(t *testing.T) {
+ payload := []byte{128, 177, 129, 160, 0, 1, 0, 1, 0, 0, 0, 1, 5, 104, 101, 108, 108, 111, 4,
+ 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)
+ _, _, err := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsAnswer_InvalidPtr_Loop1(t *testing.T) {
+ // loop qname on himself
+ payload := []byte{128, 177, 129, 160, 0, 1, 0, 1, 0, 0, 0, 1, 5, 104, 101, 108, 108, 111, 4,
+ 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)
+ _, _, err := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsAnswer_InvalidPtr_Loop2(t *testing.T) {
+ // loop between qnames
+ payload := []byte{128, 177, 129, 160, 0, 1, 0, 2, 0, 0, 0, 1, 5, 104, 101, 108, 108, 111, 4,
+ 109, 99, 104, 100, 2, 109, 101, 0, 0, 1, 0, 1, 192, 47, 0, 1, 0, 1, 0, 0,
+ 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)
+ _, _, err := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
diff --git a/dnsutils/dns_parser_label_test.go b/dnsutils/dns_parser_label_test.go
new file mode 100644
index 00000000..1ad9d71a
--- /dev/null
+++ b/dnsutils/dns_parser_label_test.go
@@ -0,0 +1,296 @@
+package dnsutils
+
+import (
+ "errors"
+ "testing"
+)
+
+// Benchmark
+
+func BenchmarkDnsParseLabels(b *testing.B) {
+ payload := []byte{0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x76, 0x69,
+ 0x74, 0x79, 0x2d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x06,
+ 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, _, err := ParseLabels(0, payload)
+ if err != nil {
+ b.Fatalf("could not parse labels: %v\n", err)
+ }
+ }
+}
+
+func TestDecodeDnsLabel_InvalidOffset_NegativeOffset(t *testing.T) {
+ payload := []byte{0x01, 0x61, 0x00}
+
+ _, _, err := ParseLabels(-1, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidOffset) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidOffset_StartOutOfBounds(t *testing.T) {
+ payload := []byte{0x01, 0x61, 0x00}
+
+ _, _, err := ParseLabels(4, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidOffset_RunOutOfBounds(t *testing.T) {
+ payload := []byte{0x01, 0x61}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidOffset_PointerByteOutOfBounds(t *testing.T) {
+ payload := []byte{0x01, 0x61, 0xc0}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_LabelTooShort(t *testing.T) {
+ payload := []byte{0x01}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_NoExtraDotAfterPtr(t *testing.T) {
+ payload := []byte{0x00, 0x01, 0x61, 0xc0, 0x00}
+
+ label, _, _ := ParseLabels(1, payload)
+ if label != "a" {
+ t.Errorf("bad label parsed: %v", label)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidLabelLengthByte1(t *testing.T) {
+ payload := []byte{0x40}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidLabelLengthByte2(t *testing.T) {
+ payload := []byte{0x80}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_ValidTotalLength(t *testing.T) {
+ // A 253-character label
+ payload := []byte{
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3d, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00,
+ }
+ valid := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+ label, _, _ := ParseLabels(0, payload)
+ if label != valid {
+ t.Errorf("bad name parsed: %v", label)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidTotalLength_WithoutPtr(t *testing.T) {
+ // A 254-character label (including separator dots)
+ payload := []byte{
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3e, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00,
+ }
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelTooLong) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidTotalLength_WithPtr(t *testing.T) {
+ // A 254-character label (including separator dots), containing a pointer
+ payload := []byte{
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x3c, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00, 0x01,
+ 0x61, 0xc0, 0x00,
+ }
+ _, _, err := ParseLabels(255, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelTooLong) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidPtr_SimpleLoop(t *testing.T) {
+ payload := []byte{0x01, 0x61, 0xc0, 0x00}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidPtr_Forwards(t *testing.T) {
+ payload := []byte{0xc0, 0x02, 0x00}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidPtr_BackwardsInsideCurrentLabel1(t *testing.T) {
+ payload := []byte{0x01, 0x02, 0xc0, 0x01, 0x00}
+
+ _, _, err := ParseLabels(0, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidPtr_BackwardsOverlappingLoop(t *testing.T) {
+ payload := []byte{0x01, 0x61, 0x01, 0x61, 0xc0, 0x00}
+
+ _, _, err := ParseLabels(2, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidPtr_BackwardsOverlappingTerminating(t *testing.T) {
+ payload := []byte{0x01, 0x01, 0x00, 0xc0, 0x00}
+
+ _, _, err := ParseLabels(1, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_InvalidPtr_BackwardsOverlappingPtr(t *testing.T) {
+ // The second pointer byte overlaps the beginning of the label that starts at payload[3]
+ payload := []byte{0x00, 0x00, 0xc0, 0x01, 0x00, 0xc0, 0x02}
+
+ _, _, err := ParseLabels(3, payload)
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsLabel_EndOffset_WithoutPtr(t *testing.T) {
+ payload := []byte{0x02, 0x61, 0x61, 0x00}
+
+ _, offset, _ := ParseLabels(0, payload)
+ if offset != 4 {
+ t.Errorf("invalid end offset: %v", offset)
+ }
+}
+
+func TestDecodeDnsLabel_EndOffset_WithPtr(t *testing.T) {
+ payload := []byte{0x01, 0x61, 0x00, 0x02, 0x61, 0x61, 0xc0, 0x00}
+
+ _, offset, _ := ParseLabels(3, payload)
+ if offset != 8 {
+ t.Errorf("invalid end offset: %v", offset)
+ }
+}
diff --git a/dnsutils/dns_parser_payload_test.go b/dnsutils/dns_parser_payload_test.go
new file mode 100644
index 00000000..dec9d58f
--- /dev/null
+++ b/dnsutils/dns_parser_payload_test.go
@@ -0,0 +1,1000 @@
+package dnsutils
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/dmachard/go-dnscollector/pkgconfig"
+)
+
+func TestDecodePayload_QueryHappy(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x9e, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ // name
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records: EDNS OPT with no data, DO = 0, Z=0
+ 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
+ t.Errorf("Unexpected error while decoding payload: %v", err)
+ }
+ if dm.DNS.MalformedPacket != false {
+ t.Errorf("did not expect packet to be malformed")
+ }
+
+ if dm.DNS.ID != 0x9e84 ||
+ dm.DNS.Opcode != 0 ||
+ dm.DNS.Rcode != RcodeToString(0) ||
+ dm.DNS.Flags.QR ||
+ dm.DNS.Flags.TC ||
+ dm.DNS.Flags.AA ||
+ !dm.DNS.Flags.AD ||
+ dm.DNS.Flags.RA {
+ t.Error("Invalid DNS header data in message")
+ }
+
+ if dm.DNS.Qname != "sensorfleet.com" {
+ t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
+ }
+ if dm.DNS.Qtype != "A" {
+ t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
+ }
+
+ if dm.EDNS.Do != 1 ||
+ dm.EDNS.UDPSize != 4096 ||
+ dm.EDNS.Z != 0 ||
+ dm.EDNS.Version != 0 ||
+ len(dm.EDNS.Options) != 0 {
+ t.Errorf("Unexpected EDNS data")
+ }
+
+ if len(dm.DNS.DNSRRs.Answers) != 0 ||
+ len(dm.DNS.DNSRRs.Nameservers) != 0 ||
+ len(dm.DNS.DNSRRs.Records) != 0 {
+ t.Errorf("Unexpected sections parsed")
+ }
+
+}
+func TestDecodePayload_QueryInvalid(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x9e, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ // name
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x83, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records: EDNS OPT with no data, DO = 1, Z=0
+ 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
+ t.Errorf("Expected error when parsing payload")
+ }
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be marked as malformed")
+ }
+
+ // returned error should wrap the original error
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodePayload_AnswerHappy(t *testing.T) {
+ payload := []byte{
+ 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x01,
+ // Query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Answer 1
+ 0xc0, 0x0c, // pointer to name
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.1
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
+ // Answer 2
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.2
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
+ // Answer 3
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.3
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
+ // Answer 4
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.4
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
+ // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
+ t.Errorf("Unexpected error while decoding payload: %v", err)
+ }
+ if dm.DNS.MalformedPacket != false {
+ t.Errorf("did not expect packet to be malformed")
+ }
+
+ if dm.DNS.ID != 0x9e84 ||
+ dm.DNS.Opcode != 0 ||
+ dm.DNS.Rcode != RcodeToString(0) ||
+ !dm.DNS.Flags.QR ||
+ dm.DNS.Flags.TC ||
+ dm.DNS.Flags.AA ||
+ dm.DNS.Flags.AD ||
+ !dm.DNS.Flags.RA {
+ t.Error("Invalid DNS header data in message")
+ }
+
+ if dm.DNS.Qname != "sensorfleet.com" {
+ t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
+ }
+ if dm.DNS.Qtype != "A" {
+ t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
+ }
+
+ if len(dm.DNS.DNSRRs.Answers) != 4 {
+ t.Errorf("expected 4 answers, got %d", len(dm.DNS.DNSRRs.Answers))
+ }
+
+ for i, ans := range dm.DNS.DNSRRs.Answers {
+ expected := DNSAnswer{
+ Name: dm.DNS.Qname,
+ Rdatatype: RdatatypeToString(0x0001),
+ Class: "IN", // 0x0001,
+ TTL: 300,
+ Rdata: fmt.Sprintf("10.10.1.%d", i+1),
+ }
+ if expected != ans {
+ t.Errorf("unexpected answer (%d). expected %v, got %v", i, expected, ans)
+ }
+ }
+
+ if dm.EDNS.Do != 0 ||
+ dm.EDNS.UDPSize != 1232 ||
+ dm.EDNS.Z != 0 ||
+ dm.EDNS.Version != 0 ||
+ len(dm.EDNS.Options) != 0 {
+ t.Errorf("Unexpected EDNS data")
+ }
+
+ if len(dm.DNS.DNSRRs.Nameservers) != 0 ||
+ len(dm.DNS.DNSRRs.Records) != 0 {
+ t.Errorf("Unexpected sections parsed")
+ }
+
+}
+
+func TestDecodePayload_AnswerMultipleQueries(t *testing.T) {
+ payload := []byte{
+ 0x9e, 0x84, 0x81, 0x80, 0x00, 0x02, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x01,
+ // Query section
+ // query 1
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 2
+ 0x0a, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+
+ // Answer 1
+ 0xc0, 0x0c, // pointer to name
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.1
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
+ // Answer 2
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.2
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
+ // Answer 3
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.3
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
+ // Answer 4
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.4
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
+ // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
+ t.Errorf("Unexpected error while decoding payload: %v", err)
+ }
+ if dm.DNS.MalformedPacket != false {
+ t.Errorf("did not expect packet to be malformed")
+ }
+
+ if dm.DNS.ID != 0x9e84 ||
+ dm.DNS.Opcode != 0 ||
+ dm.DNS.Rcode != RcodeToString(0) ||
+ !dm.DNS.Flags.QR ||
+ dm.DNS.Flags.TC ||
+ dm.DNS.Flags.AA ||
+ dm.DNS.Flags.AD ||
+ !dm.DNS.Flags.RA {
+ t.Error("Invalid DNS header data in message")
+ }
+
+ if dm.DNS.Qname != "ensorfleet.com" {
+ t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
+ }
+ if dm.DNS.Qtype != "A" {
+ t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
+ }
+
+ if len(dm.DNS.DNSRRs.Answers) != 4 {
+ t.Errorf("expected 4 answers, got %d", len(dm.DNS.DNSRRs.Answers))
+ }
+
+ for i, ans := range dm.DNS.DNSRRs.Answers {
+ expected := DNSAnswer{
+ Name: "s" + dm.DNS.Qname, // answers have qname from 1st query data, 2nd data is missing 's'
+ Rdatatype: RdatatypeToString(0x0001),
+ Class: "IN", // 0x0001,
+ TTL: 300,
+ Rdata: fmt.Sprintf("10.10.1.%d", i+1),
+ }
+ if expected != ans {
+ t.Errorf("unexpected answer (%d). expected %v, got %v", i, expected, ans)
+ }
+ }
+
+ if dm.EDNS.Do != 0 ||
+ dm.EDNS.UDPSize != 1232 ||
+ dm.EDNS.Z != 0 ||
+ dm.EDNS.Version != 0 ||
+ len(dm.EDNS.Options) != 0 {
+ t.Errorf("Unexpected EDNS data")
+ }
+
+ if len(dm.DNS.DNSRRs.Nameservers) != 0 ||
+ len(dm.DNS.DNSRRs.Records) != 0 {
+ t.Errorf("Unexpected sections parsed")
+ }
+
+}
+
+func TestDecodePayload_AnswerInvalid(t *testing.T) {
+ payload := []byte{
+ 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x01,
+ // Query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Answer 1
+ 0xc0, 0x0c, // pointer to name
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.1
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
+ // Answer 2
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.2
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
+ // Answer 3
+ 0xc0, 0xff,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.3
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
+ // Answer 4
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.4
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
+ // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
+ t.Error("expected decoding to fail")
+ }
+ // returned error should wrap the original error
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be malformed")
+ }
+}
+
+func TestDecodePayload_AnswerInvalidQuery(t *testing.T) {
+ payload := []byte{
+ 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x01,
+ // Query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x83, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Answer 1
+ 0xc0, 0x0c, // pointer to name
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.1
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
+ // Answer 2
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.2
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
+ // Answer 3
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.3
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
+ // Answer 4
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.4
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
+ // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
+ t.Error("expected decoding to fail")
+ }
+ // returned error should wrap the original error
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
+ t.Errorf("bad error returned: %v", err)
+ }
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be malformed")
+ }
+
+ // after error has been detected in the query part, we should not parse
+ // anything from answers
+ if len(dm.DNS.DNSRRs.Answers) != 0 {
+ t.Errorf("did not expect answers to be parsed, but there were %d parsed", len(dm.DNS.DNSRRs.Answers))
+ }
+}
+
+func TestDecodePayload_AnswerInvalidEdns(t *testing.T) {
+ payload := []byte{
+ 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x01,
+ // Query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Answer 1
+ 0xc0, 0x0c, // pointer to name
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.1
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
+ // Answer 2
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.2
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
+ // Answer 3
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.3
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
+ // Answer 4
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.4
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
+ // Additianl records, Invalid EDNS Option
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x01,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
+ t.Error("expected decoding to fail")
+ }
+ // returned error should wrap the original error
+ if !errors.Is(err, ErrDecodeEdnsOptionTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be malformed")
+ }
+}
+
+func TestDecodePayload_AnswerInvaliAdditional(t *testing.T) {
+ payload := []byte{
+ 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x01,
+ // Query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Answer 1
+ 0xc0, 0x0c, // pointer to name
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.1
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
+ // Answer 2
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.2
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
+ // Answer 3
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.3
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
+ // Answer 4
+ 0xc0, 0x0c,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x01, 0x2c,
+ // 10.10.1.4
+ 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
+ // Additianl records, Invalid RDLENGTH
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
+ t.Error("expected decoding to fail")
+ }
+ // returned error should wrap the original error
+ if !errors.Is(err, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be malformed")
+ }
+}
+
+func TestDecodePayload_AnswerError(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xa8, 0x1a, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // query
+ 0x03, 0x66, 0x6f, 0x6f,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Authority section
+ // name
+ 0xc0, 0x10,
+ // type SOA, class IN
+ 0x00, 0x06, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x00, 0x3c,
+ // RDLENGTH
+ 0x00, 0x26,
+ // RDATA
+ // MNAME
+ 0x03, 0x6e, 0x73, 0x31,
+ 0xc0, 0x10,
+ // RNAME
+ 0x09, 0x64, 0x6e, 0x73, 0x2d, 0x61,
+ 0x64, 0x6d, 0x69, 0x6e, 0xc0, 0x10,
+ // serial
+ 0x19, 0xa1, 0x4a, 0xb4,
+ // refresh
+ 0x00, 0x00, 0x03, 0x84,
+ // retry
+ 0x00, 0x00, 0x03, 0x84,
+ // expire
+ 0x00, 0x00, 0x07, 0x08,
+ // minimum
+ 0x00, 0x00, 0x00, 0x3c,
+ // Additianl records, EDNS Option, 0 bytes DO=1, Z = 0
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00,
+ }
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
+ t.Errorf("Unexpected error while decoding payload: %v", err)
+ }
+ if dm.DNS.MalformedPacket != false {
+ t.Errorf("did not expect packet to be malformed")
+ }
+
+ if dm.DNS.ID != 0xa81a ||
+ dm.DNS.Opcode != 0 ||
+ dm.DNS.Rcode != RcodeToString(3) ||
+ !dm.DNS.Flags.QR ||
+ dm.DNS.Flags.TC ||
+ dm.DNS.Flags.AA ||
+ dm.DNS.Flags.AD ||
+ !dm.DNS.Flags.RA {
+ t.Error("Invalid DNS header data in message")
+ }
+
+ if dm.DNS.Qname != "foo.google.com" {
+ t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
+ }
+ if dm.DNS.Qtype != "A" {
+ t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
+ }
+
+ if len(dm.DNS.DNSRRs.Answers) != 0 {
+ t.Errorf("did not expect any answers, got %d", len(dm.DNS.DNSRRs.Answers))
+ }
+
+ if len(dm.DNS.DNSRRs.Nameservers) != 1 {
+ t.Errorf("expected 1 authority RR, got %d", len(dm.DNS.DNSRRs.Nameservers))
+ }
+ expected := DNSAnswer{
+ Name: "google.com",
+ Rdatatype: RdatatypeToString(0x0006),
+ Class: "IN", // 0x0001,
+ TTL: 60,
+ Rdata: "ns1.google.com dns-admin.google.com 430000820 900 900 1800 60",
+ }
+
+ if dm.DNS.DNSRRs.Nameservers[0] != expected {
+ t.Errorf("unexpected SOA record parsed, expected %v, git %v", expected, dm.DNS.DNSRRs.Nameservers[0])
+ }
+
+ if dm.EDNS.Do != 1 ||
+ dm.EDNS.UDPSize != 1232 ||
+ dm.EDNS.Z != 0 ||
+ dm.EDNS.Version != 0 ||
+ len(dm.EDNS.Options) != 0 {
+ t.Errorf("Unexpected EDNS data")
+ }
+
+}
+
+func TestDecodePayload_AnswerError_Invalid(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xa8, 0x1a, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // query
+ 0x03, 0x66, 0x6f, 0x6f,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // Authority section
+ // name
+ 0xc0, 0x10,
+ // type SOA, class IN
+ 0x00, 0x06, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x00, 0x3c,
+ // RDLENGTH
+ 0x00, 0x26,
+ // RDATA
+ // MNAME, invalid offset in pointer
+ 0x03, 0x6e, 0x73, 0x31,
+ 0xc0, 0xff,
+ // RNAME
+ 0x09, 0x64, 0x6e, 0x73, 0x2d, 0x61,
+ 0x64, 0x6d, 0x69, 0x6e, 0xc0, 0x10,
+ // serial
+ 0x19, 0xa1, 0x4a, 0xb4,
+ // refresh
+ 0x00, 0x00, 0x03, 0x84,
+ // retry
+ 0x00, 0x00, 0x03, 0x84,
+ // expire
+ 0x00, 0x00, 0x07, 0x08,
+ // minimum
+ 0x00, 0x00, 0x00, 0x3c,
+ // Additianl records, EDNS Option, 0 bytes DO=1, Z = 0
+ 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00,
+ 0x00, 0x80, 0x00, 0x00, 0x00,
+ }
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
+ t.Error("expected decoding to fail")
+ }
+ // returned error should wrap the original error
+ if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
+ t.Errorf("bad error returned: %v", err)
+ }
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be malformed")
+ }
+
+}
+
+func TestDecodePayload_AdditionalRRAndEDNS(t *testing.T) {
+ // payload containing both addition RR and EDNS, ensure we are
+ // able to parse all of them
+ payload := []byte{
+ 0x7b, 0x97, 0x84, 0x0, 0x0, 0x1, 0x0, 0x2,
+ 0x0, 0x2, 0x0, 0x5, 0xf, 0x6f, 0x63, 0x63,
+ 0x2d, 0x30, 0x2d, 0x31, 0x35, 0x30, 0x30,
+ 0x2d, 0x31, 0x35, 0x30, 0x31, 0x1, 0x31,
+ 0x6, 0x6e, 0x66, 0x6c, 0x78, 0x73, 0x6f,
+ 0x3, 0x6e, 0x65, 0x74, 0x0, 0x0, 0x1, 0x0,
+ 0x1, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0,
+ 0x0, 0x0, 0x1e, 0x0, 0x4, 0x2d, 0x39, 0x24,
+ 0x97, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0,
+ 0x0, 0x0, 0x1e, 0x0, 0x4, 0x2d, 0x39, 0x24,
+ 0x94, 0xc0, 0x1e, 0x0, 0x2, 0x0, 0x1, 0x0,
+ 0x1, 0x51, 0x80, 0x0, 0x7, 0x1, 0x65, 0x2,
+ 0x6e, 0x73, 0xc0, 0x1e, 0xc0, 0x1e, 0x0,
+ 0x2, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80, 0x0,
+ 0x4, 0x1, 0x66, 0xc0, 0x5c, 0x0, 0x0, 0x29,
+ 0x4, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0,
+ 0x5a, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80,
+ 0x0, 0x4, 0x2d, 0x39, 0x8, 0x1, 0xc0, 0x5a,
+ 0x0, 0x1c, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80, 0x0,
+ 0x10, 0x2a, 0x0, 0x86, 0xc0, 0x20, 0x8, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc0, 0x6d,
+ 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80, 0x0, 0x4,
+ 0x2d, 0x39, 0x9, 0x1, 0xc0, 0x6d, 0x0, 0x1c, 0x0, 0x1,
+ 0x0, 0x1, 0x51, 0x80, 0x0, 0x10, 0x2a, 0x0, 0x86, 0xc0,
+ 0x20, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("error when deocoding header: %v", err)
+ }
+
+ if err := DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
+ t.Errorf("unexpected error while decoding payload: %v", err)
+ }
+
+ if len(dm.DNS.DNSRRs.Answers) != 2 || len(dm.DNS.DNSRRs.Nameservers) != 2 ||
+ len(dm.DNS.DNSRRs.Records) != 4 || dm.EDNS.UDPSize != 1200 ||
+ len(dm.EDNS.Options) != 0 {
+ t.Errorf("unexpected result while parsing payload: %#v", dm.DNS)
+ }
+
+}
+
+func TestDecodePayload_Truncated(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x77, 0xa0, 0x83, 0x80, 0x00, 0x01, 0x00, 0x23,
+ 0x00, 0x00, 0x00, 0x00,
+ // query
+ 0x02, 0x41, 0x64, 0x0d,
+ 0x6e, 0x6e, 0x6e, 0x6e,
+ 0x6e, 0x6e, 0x6e, 0x6e,
+ 0x6e, 0x6e, 0x6e, 0x6e,
+ 0x6e, 0x02, 0x46, 0x52,
+ 0x00, 0x00, 0x01, 0x00, 0x01,
+ // answer 1
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x01,
+ // answer 2
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x02,
+ // answer 3
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x03,
+ // answer 4
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x04,
+ // answer 5
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x05,
+ // answer 6
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x06,
+ // answer 7
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x07,
+ // answer 8
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x08,
+ // answer 9
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x09,
+ // answer 10
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x0a,
+ // answer 11
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x0b,
+ // answer 12
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x0c,
+ // answer 13
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x0d,
+ // answer 14
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x0e,
+ // answer 15
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x0f,
+ // answer 16
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x10,
+ // answer 17
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x11,
+ // answer 18
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x12,
+ // answer 19
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x13,
+ // answer 20
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x14,
+ // answer 21
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x15,
+ // answer 22
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x16,
+ // answer 23
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x17,
+ // answer 24
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x18,
+ // answer 25
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x19,
+ // answer 26
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x1a,
+ // answer 27
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x1b,
+ // answer 28
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x1c,
+ // answer 29
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
+ 0x01, 0x1d,
+ // answer 30
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x6b, 0x00,
+ }
+
+ dm := DNSMessage{}
+ dm.DNS.Payload = payload
+ dm.DNS.Length = len(payload)
+
+ header, err := DecodeDNS(payload)
+ if err != nil {
+ t.Errorf("unexpected error when decoding header: %v", err)
+ }
+
+ if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
+ t.Error("expected no error on decode")
+ }
+
+ if dm.DNS.Flags.TC == false {
+ t.Error("truncated answer expected")
+ }
+
+ if dm.DNS.MalformedPacket != true {
+ t.Errorf("expected packet to be malformed")
+ }
+
+}
diff --git a/dnsutils/dns_parser_question_test.go b/dnsutils/dns_parser_question_test.go
new file mode 100644
index 00000000..52cfef00
--- /dev/null
+++ b/dnsutils/dns_parser_question_test.go
@@ -0,0 +1,175 @@
+package dnsutils
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/miekg/dns"
+)
+
+func TestDecodeQuestion(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+ payload, _ := dm.Pack()
+
+ 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)
+ }
+
+ if RdatatypeToString(qtype) != "A" {
+ t.Errorf("invalid qtype: %d", qtype)
+ }
+ if offsetRR != len(payload) {
+ t.Errorf("invalid offset: %d, payload len: %d", offsetRR, len(payload))
+ }
+}
+
+func TestDecodeQuestion_Multiple(t *testing.T) {
+ paylaod := []byte{
+ 0x9e, 0x84, 0x01, 0x20, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ // query 1
+ 0x01, 0x61, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 2
+ 0x01, 0x62, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 3
+ 0x01, 0x63, 0x00,
+ // type AAAA, class IN
+ 0x00, 0x1c, 0x00, 0x01,
+ }
+
+ 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)
+ }
+}
+
+func TestDecodeQuestion_Multiple_InvalidCount(t *testing.T) {
+ paylaod := []byte{
+ 0x9e, 0x84, 0x01, 0x20, 0x00, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ // query 1
+ 0x01, 0x61, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 2
+ 0x01, 0x62, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 3
+ 0x01, 0x63, 0x00,
+ // type AAAA, class IN
+ 0x00, 0x1c, 0x00, 0x01,
+ }
+
+ _, _, _, _, err := DecodeQuestion(4, paylaod)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error received: %v", err)
+ }
+}
+
+func TestDecodeQuestion_SkipOpt(t *testing.T) {
+ payload := []byte{
+ 0x43, 0xac, 0x01, 0x00, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00,
+ // Query section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer Resource Records
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type OPT, class IN
+ 0x00, 0x29, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x01,
+ // RDATA
+ 0x01,
+ // 2nd resource record
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x04,
+ // RDATA
+ 0x7f, 0x00, 0x00, 0x01,
+ }
+ _, _, _, offsetrr, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("Unexpected error decoding question: %v", err)
+ }
+
+ answer, _, erra := DecodeAnswer(2, offsetrr, payload)
+ if erra != nil {
+ t.Errorf("Unexpected error decoding answer: %v", erra)
+ }
+ if len(answer) != 1 {
+ t.Fatalf("Expected answer to contain one resource record, got %d", len(answer))
+ }
+ if answer[0].Rdatatype != RdatatypeToString(0x01) || answer[0].Rdata != "127.0.0.1" {
+ t.Errorf("unexpected answer %s %s, expected A 127.0.0.1", answer[0].Rdatatype, answer[0].Rdata)
+ }
+}
+
+func TestDecodeDnsQuestion_InvalidOffset(t *testing.T) {
+ decoded := []byte{183, 59, 130, 217, 128, 16, 0, 51, 165, 67, 0, 0}
+ _, _, _, _, err := DecodeQuestion(1, decoded)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+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)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+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)
+ if !errors.Is(err, ErrDecodeQuestionQtypeTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
+
+func TestDecodeDnsQuestion_InvalidPointer(t *testing.T) {
+ decoded := []byte{88, 27, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 202}
+ _, _, _, _, err := DecodeQuestion(1, decoded)
+ if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
+ t.Errorf("bad error returned: %v", err)
+ }
+}
diff --git a/dnsutils/dns_parser_rdata_test.go b/dnsutils/dns_parser_rdata_test.go
new file mode 100644
index 00000000..3fb2ff7a
--- /dev/null
+++ b/dnsutils/dns_parser_rdata_test.go
@@ -0,0 +1,661 @@
+package dnsutils
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/miekg/dns"
+)
+
+func TestRdatatypeValid(t *testing.T) {
+ rdt := RdatatypeToString(1)
+ if rdt != "A" {
+ t.Errorf("rdatatype A expected: %s", rdt)
+ }
+}
+
+func TestRdatatypeInvalid(t *testing.T) {
+ rdt := RdatatypeToString(100000)
+ if rdt != "UNKNOWN" {
+ t.Errorf("rdatatype - expected: %s", rdt)
+ }
+}
+
+func TestDecodeRdataA(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "127.0.0.1"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s A %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata A, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataA_Short(t *testing.T) {
+ payload := []byte{
+ 0x43, 0xac, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Query section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer Resource Record
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x03,
+ // RDATA (1 byte too short for A record)
+ 0x7f, 0x00, 0x00,
+ }
+ _, _, _, offsetrr, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("Unexpected error decoding question: %v", err)
+ }
+
+ _, _, erra := DecodeAnswer(1, offsetrr, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+}
+
+func TestDecodeRdataAAAA(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "fe8::2"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s AAAA %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata AAAA, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+func TestDecodeRdataAAAA_Short(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x3b, 0x33, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Query section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer resource record
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type AAAA, class IN
+ 0x00, 0x1c, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x0c,
+ // RDATA
+ 0xfe, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00,
+ }
+
+ _, _, _, offsetSetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ _, _, erra := DecodeAnswer(1, offsetSetRR, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+}
+
+func TestDecodeRdataCNAME(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "test.collector.org"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s CNAME %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata CNAME, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataMX(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "5 gmail-smtp-in.l.google.com"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s MX %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata MX, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataMX_Short(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xed, 0x7f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Question seection
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer Resource Record
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type MX, class IN
+ 0x00, 0x0f, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x01,
+ // RDATA
+ 0x00,
+ }
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ _, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+
+}
+
+func TestDecodeRdataMX_Minimal(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xed, 0x7f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Question seection
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer Resource Record
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type MX, class IN
+ 0x00, 0x0f, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x03,
+ // RDATA
+ 0x00, 0x00, 0x00,
+ }
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ answer, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", err)
+ }
+ expected := "0 "
+ if answer[0].Rdata != expected {
+ t.Errorf("invalid decode for MX rdata, expected %s got %s", expected, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataSRV(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "20 0 5222 alt2.xmpp.l.google.com"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s SRV %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata SRV, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataSRV_Short(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xd9, 0x93, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Question section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer section
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type SRV, class IN
+ 0x00, 0x21, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x04,
+ // RDATA
+ // priority
+ 0x00, 0x14,
+ // weight
+ 0x00, 0x00,
+ // missing port and target
+ }
+
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ _, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+}
+
+func TestDecodeRdataSRV_Minimal(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xd9, 0x93, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Question section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer section
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type SRV, class IN
+ 0x00, 0x21, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x07,
+ // RDATA
+ // priority
+ 0x00, 0x14,
+ // weight
+ 0x00, 0x00,
+ // port
+ 0x00, 0x10,
+ // empty target
+ 0x00,
+ }
+
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ answer, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", err)
+ }
+ expectedRdata := "20 0 16 "
+ if answer[0].Rdata != expectedRdata {
+ t.Errorf("invalid decode for rdata SRV, want %s, got: %s", expectedRdata, answer[0].Rdata)
+ }
+}
+func TestDecodeRdataNS(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "ns1.dnscollector"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s NS %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata NS, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataTXT(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "hello world"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s TXT \"%s\"", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata TXT, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataTXT_Empty(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x86, 0x08, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // question section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // answer section
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type TXT, class IN
+ 0x00, 0x10, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x00,
+ // no data
+ }
+
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ _, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+
+}
+func TestDecodeRdataTXT_Short(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x86, 0x08, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // question section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // answer section
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type TXT, class IN
+ 0x00, 0x10, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x0a,
+ // RDATA
+ // length
+ 0x0b,
+ // characters
+ 0x68,
+ 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72,
+ // missing two bytes
+ }
+
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ _, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+
+}
+func TestDecodeRdataTXT_NoTxt(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x86, 0x08, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // question section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // answer section
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type TXT, class IN
+ 0x00, 0x10, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH
+ 0x00, 0x01,
+ // RDATA
+ // length
+ 0x00,
+ // no txt-data
+ }
+
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+ answer, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", err)
+ }
+
+ if answer[0].Rdata != "" {
+ t.Errorf("expected empty string in RDATA, got: %s", answer[0].Rdata)
+ }
+
+}
+
+func TestDecodeRdataPTR(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "one.one.one.one"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s PTR %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata PTR, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataSOA(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeA)
+
+ rdata := "ns1.google.com dns-admin.google.com 412412655 900 900 1800 60"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s SOA %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata SOA, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataSOA_Short(t *testing.T) {
+ payload := []byte{
+ // header
+ 0x28, 0xba, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ // Query section
+ 0x0f, 0x64, 0x6e, 0x73,
+ 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
+ 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
+ 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer Resource Record,
+ 0x0f, 0x64,
+ 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
+ 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
+ 0x65, 0x73, 0x74, 0x00,
+ // type SOA, class IN
+ 0x00, 0x06, 0x00, 0x01,
+ // TTL
+ 0x00, 0x00, 0x0e, 0x10,
+ // RDLENGTH 54
+ 0x00, 0x36,
+ // RDATA
+ // MNAME
+ 0x03, 0x6e,
+ 0x73, 0x31, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+ 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ // RNAME
+ 0x09, 0x64,
+ 0x6e, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x69, 0x6e,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // serial
+ 0x18, 0x94, 0xea, 0xef,
+ // refresh
+ 0x00, 0x00, 0x03, 0x84,
+ // retry
+ 0x00, 0x00, 0x03, 0x84,
+ // expire
+ 0x00, 0x00, 0x07, 0x08,
+ // minimum -field missing from the RDATA
+ }
+
+ _, _, _, offsetRR, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("Unable to decode question: %v", err)
+ }
+ _, _, erra := DecodeAnswer(1, offsetRR, payload)
+ if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
+ t.Errorf("bad error returned: %v", erra)
+ }
+}
+
+func TestDecodeRdataSOA_Minimization(t *testing.T) {
+ // loop between qnames
+ payload := []byte{164, 66, 129, 128, 0, 1, 0, 0, 0, 1, 0, 0, 8, 102, 114, 101, 115, 104, 114, 115, 115, 4, 109,
+ 99, 104, 100, 2, 109, 101, 0, 0, 28, 0, 1, 192, 21, 0, 6, 0, 1, 0, 0, 0, 60, 0, 43, 6, 100, 110, 115, 49, 48,
+ 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)
+ _, _, err := DecodeAnswer(1, offsetRR, payload)
+ if err != nil {
+ t.Errorf(" error returned: %v", err)
+ }
+}
+
+func TestDecodeRdataSVCB_alias(t *testing.T) {
+ fqdn := TestQName
+
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeSVCB)
+
+ // draft-ietf-dnsop-svcb-https-12 Appendix D.1
+ rdata := "0 foo.example.com"
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s SVCB %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+
+ payload, _ := dm.Pack()
+
+ _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
+ answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
+
+ if answer[0].Rdata != rdata {
+ t.Errorf("invalid decode for rdata SOA, want %s, got: %s", rdata, answer[0].Rdata)
+ }
+}
+
+func TestDecodeRdataSVCB_params(t *testing.T) {
+ fqdn := TestQName
+
+ vectors := []string{
+ "0 foo.example.com", // draft-ietf-dnsop-svcb-https-12 Appendix D.1
+ "1 .", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 3
+ "16 foo.example.com port=53", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 4
+ "1 foo.example.com key667=hello", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 5
+ `1 foo.example.com key667="hello\210qoo"`, // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 6
+ "1 foo.example.com ipv6hint=2001:db8::1,2001:db8::53:1", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 7, modified (single line)
+ "16 foo.example.org mandatory=alpn,ipv4hint alpn=h2,h3-19 ipv4hint=192.0.2.1", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 9, modified (sorted)
+ "16 foo.example.org mandatory=alpn,ipv4hint alpn=h2,h3-19 ipv4hint=192.0.2.1,192.0.2.2",
+ }
+
+ for _, rdata := range vectors {
+ dm := new(dns.Msg)
+ dm.SetQuestion(fqdn, dns.TypeSVCB)
+ rr1, _ := dns.NewRR(fmt.Sprintf("%s SVCB %s", fqdn, rdata))
+ dm.Answer = append(dm.Answer, rr1)
+ payload, _ := dm.Pack()
+ _, _, _, 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)
+ }
+ }
+}
diff --git a/dnsutils/dns_parser_test.go b/dnsutils/dns_parser_test.go
index 9afb27a6..e4c76acf 100644
--- a/dnsutils/dns_parser_test.go
+++ b/dnsutils/dns_parser_test.go
@@ -2,34 +2,11 @@ package dnsutils
import (
"errors"
- "fmt"
"testing"
- "github.com/dmachard/go-dnscollector/pkgconfig"
"github.com/miekg/dns"
)
-const (
- TestQName = "dnstapcollector.test."
-)
-
-// Benchmark
-
-func BenchmarkDnsParseLabels(b *testing.B) {
- payload := []byte{0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x76, 0x69,
- 0x74, 0x79, 0x2d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x06,
- 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x03, 0x63, 0x6f, 0x6d, 0x00,
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, _, err := ParseLabels(0, payload)
- if err != nil {
- b.Fatalf("could not parse labels: %v\n", err)
- }
- }
-}
-
-// Regular tests
func TestRcodeValid(t *testing.T) {
rcode := RcodeToString(0)
if rcode != "NOERROR" {
@@ -44,20 +21,6 @@ func TestRcodeInvalid(t *testing.T) {
}
}
-func TestRdatatypeValid(t *testing.T) {
- rdt := RdatatypeToString(1)
- if rdt != "A" {
- t.Errorf("rdatatype A expected: %s", rdt)
- }
-}
-
-func TestRdatatypeInvalid(t *testing.T) {
- rdt := RdatatypeToString(100000)
- if rdt != "UNKNOWN" {
- t.Errorf("rdatatype - expected: %s", rdt)
- }
-}
-
func TestDecodeDns(t *testing.T) {
dm := new(dns.Msg)
dm.SetQuestion(TestQName, dns.TypeA)
@@ -69,843 +32,6 @@ func TestDecodeDns(t *testing.T) {
}
}
-func TestDecodeQuestion(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
- payload, _ := dm.Pack()
-
- 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)
- }
-
- if RdatatypeToString(qtype) != "A" {
- t.Errorf("invalid qtype: %d", qtype)
- }
- if offsetRR != len(payload) {
- t.Errorf("invalid offset: %d, payload len: %d", offsetRR, len(payload))
- }
-}
-
-func TestDecodeQuestion_Multiple(t *testing.T) {
- paylaod := []byte{
- 0x9e, 0x84, 0x01, 0x20, 0x00, 0x03, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- // query 1
- 0x01, 0x61, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // query 2
- 0x01, 0x62, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // query 3
- 0x01, 0x63, 0x00,
- // type AAAA, class IN
- 0x00, 0x1c, 0x00, 0x01,
- }
-
- 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)
- }
-}
-
-func TestDecodeQuestion_Multiple_InvalidCount(t *testing.T) {
- paylaod := []byte{
- 0x9e, 0x84, 0x01, 0x20, 0x00, 0x04, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- // query 1
- 0x01, 0x61, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // query 2
- 0x01, 0x62, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // query 3
- 0x01, 0x63, 0x00,
- // type AAAA, class IN
- 0x00, 0x1c, 0x00, 0x01,
- }
-
- _, _, _, _, err := DecodeQuestion(4, paylaod)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error received: %v", err)
- }
-}
-
-func TestDecodeAnswer_Ns(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rrNs, _ := dns.NewRR("root-servers.net NS c.root-servers.net")
- rrA, _ := dns.NewRR(fmt.Sprintf("%s A 127.0.0.1", fqdn))
-
- m := new(dns.Msg)
- m.SetReply(dm)
- m.Authoritative = true
- m.Answer = append(m.Answer, rrA)
- m.Ns = append(m.Ns, rrNs)
-
- payload, _ := m.Pack()
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- _, offsetRRns, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- nsAnswers, _, _ := DecodeAnswer(len(m.Ns), offsetRRns, payload)
- if len(nsAnswers) != len(m.Ns) {
- t.Errorf("invalid decode answer, want %d, got: %d", len(m.Ns), len(nsAnswers))
- }
-}
-
-func TestDecodeAnswer(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
- rr1, _ := dns.NewRR(fmt.Sprintf("%s A 127.0.0.1", fqdn))
- rr2, _ := dns.NewRR(fmt.Sprintf("%s A 127.0.0.2", fqdn))
- dm.Answer = append(dm.Answer, rr1)
- dm.Answer = append(dm.Answer, rr2)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if len(answer) != len(dm.Answer) {
- t.Errorf("invalid decode answer, want %d, got: %d", len(dm.Answer), len(answer))
- }
-}
-
-func TestDecodeRdataSVCB_alias(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeSVCB)
-
- // draft-ietf-dnsop-svcb-https-12 Appendix D.1
- rdata := "0 foo.example.com"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s SVCB %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata SOA, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataSVCB_params(t *testing.T) {
- fqdn := TestQName
-
- vectors := []string{
- "0 foo.example.com", // draft-ietf-dnsop-svcb-https-12 Appendix D.1
- "1 .", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 3
- "16 foo.example.com port=53", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 4
- "1 foo.example.com key667=hello", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 5
- `1 foo.example.com key667="hello\210qoo"`, // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 6
- "1 foo.example.com ipv6hint=2001:db8::1,2001:db8::53:1", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 7, modified (single line)
- "16 foo.example.org mandatory=alpn,ipv4hint alpn=h2,h3-19 ipv4hint=192.0.2.1", // draft-ietf-dnsop-svcb-https-12 Appendix D.2, figure 9, modified (sorted)
- "16 foo.example.org mandatory=alpn,ipv4hint alpn=h2,h3-19 ipv4hint=192.0.2.1,192.0.2.2",
- }
-
- for _, rdata := range vectors {
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeSVCB)
- rr1, _ := dns.NewRR(fmt.Sprintf("%s SVCB %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
- payload, _ := dm.Pack()
- _, _, _, 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)
- }
- }
-}
-
-func TestDecodeAnswer_QnameMinimized(t *testing.T) {
- payload := []byte{0x8d, 0xda, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x05, 0x74,
- 0x65, 0x61, 0x6d, 0x73, 0x09, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00,
- 0x01, 0x00, 0x00, 0x50, 0xa8, 0x00, 0x0f, 0x05, 0x74, 0x65, 0x61, 0x6d, 0x73, 0x06,
- 0x6f, 0x66, 0x66, 0x69, 0x63, 0x65, 0xc0, 0x1c, 0xc0, 0x31, 0x00, 0x05, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x3e, 0x00, 0x26, 0x10, 0x74, 0x65, 0x61, 0x6d, 0x73, 0x2d, 0x6f, 0x66,
- 0x66, 0x69, 0x63, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x06, 0x73, 0x2d, 0x30, 0x30, 0x30, 0x35,
- 0x08, 0x73, 0x2d, 0x6d, 0x73, 0x65, 0x64, 0x67, 0x65, 0x03, 0x6e, 0x65, 0x74, 0x00, 0xc0,
- 0x4c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x13, 0x06, 0x73, 0x2d, 0x30,
- 0x30, 0x30, 0x35, 0x09, 0x64, 0x63, 0x2d, 0x6d, 0x73, 0x65, 0x64, 0x67, 0x65, 0xc0, 0x6d,
- 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)
- _, _, err := DecodeAnswer(4, offsetRR, payload)
- if err != nil {
- t.Errorf("failed to decode valid dns packet with minimization")
- }
-}
-
-func TestDecodeRdataA(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "127.0.0.1"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s A %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata A, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataA_Short(t *testing.T) {
- payload := []byte{
- 0x43, 0xac, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Query section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer Resource Record
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x03,
- // RDATA (1 byte too short for A record)
- 0x7f, 0x00, 0x00,
- }
- _, _, _, offsetrr, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("Unexpected error decoding question: %v", err)
- }
-
- _, _, erra := DecodeAnswer(1, offsetrr, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-}
-
-func TestDecodeRdataAAAA(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "fe8::2"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s AAAA %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata AAAA, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-func TestDecodeRdataAAAA_Short(t *testing.T) {
- payload := []byte{
- // header
- 0x3b, 0x33, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Query section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer resource record
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type AAAA, class IN
- 0x00, 0x1c, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x0c,
- // RDATA
- 0xfe, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00,
- }
-
- _, _, _, offsetSetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- _, _, erra := DecodeAnswer(1, offsetSetRR, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-}
-
-func TestDecodeRdataCNAME(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "test.collector.org"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s CNAME %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata CNAME, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataMX(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "5 gmail-smtp-in.l.google.com"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s MX %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata MX, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataMX_Short(t *testing.T) {
- payload := []byte{
- // header
- 0xed, 0x7f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Question seection
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer Resource Record
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type MX, class IN
- 0x00, 0x0f, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x01,
- // RDATA
- 0x00,
- }
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- _, _, erra := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-
-}
-
-func TestDecodeRdataMX_Minimal(t *testing.T) {
- payload := []byte{
- // header
- 0xed, 0x7f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Question seection
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer Resource Record
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type MX, class IN
- 0x00, 0x0f, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x03,
- // RDATA
- 0x00, 0x00, 0x00,
- }
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- answer, _, erra := DecodeAnswer(1, offsetRR, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", err)
- }
- expected := "0 "
- if answer[0].Rdata != expected {
- t.Errorf("invalid decode for MX rdata, expected %s got %s", expected, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataSRV(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "20 0 5222 alt2.xmpp.l.google.com"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s SRV %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata SRV, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataSRV_Short(t *testing.T) {
- payload := []byte{
- // header
- 0xd9, 0x93, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Question section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer section
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type SRV, class IN
- 0x00, 0x21, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x04,
- // RDATA
- // priority
- 0x00, 0x14,
- // weight
- 0x00, 0x00,
- // missing port and target
- }
-
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- _, _, erra := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-}
-
-func TestDecodeRdataSRV_Minimal(t *testing.T) {
- payload := []byte{
- // header
- 0xd9, 0x93, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Question section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer section
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type SRV, class IN
- 0x00, 0x21, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x07,
- // RDATA
- // priority
- 0x00, 0x14,
- // weight
- 0x00, 0x00,
- // port
- 0x00, 0x10,
- // empty target
- 0x00,
- }
-
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- answer, _, erra := DecodeAnswer(1, offsetRR, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", err)
- }
- expectedRdata := "20 0 16 "
- if answer[0].Rdata != expectedRdata {
- t.Errorf("invalid decode for rdata SRV, want %s, got: %s", expectedRdata, answer[0].Rdata)
- }
-}
-func TestDecodeRdataNS(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "ns1.dnscollector"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s NS %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata NS, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataTXT(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "hello world"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s TXT \"%s\"", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata TXT, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataTXT_Empty(t *testing.T) {
- payload := []byte{
- // header
- 0x86, 0x08, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // question section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // answer section
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type TXT, class IN
- 0x00, 0x10, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x00,
- // no data
- }
-
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- _, _, erra := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-
-}
-func TestDecodeRdataTXT_Short(t *testing.T) {
- payload := []byte{
- // header
- 0x86, 0x08, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // question section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // answer section
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type TXT, class IN
- 0x00, 0x10, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x0a,
- // RDATA
- // length
- 0x0b,
- // characters
- 0x68,
- 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72,
- // missing two bytes
- }
-
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- _, _, erra := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-
-}
-func TestDecodeRdataTXT_NoTxt(t *testing.T) {
- payload := []byte{
- // header
- 0x86, 0x08, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // question section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // answer section
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type TXT, class IN
- 0x00, 0x10, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x01,
- // RDATA
- // length
- 0x00,
- // no txt-data
- }
-
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
- answer, _, erra := DecodeAnswer(1, offsetRR, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", err)
- }
-
- if answer[0].Rdata != "" {
- t.Errorf("expected empty string in RDATA, got: %s", answer[0].Rdata)
- }
-
-}
-
-func TestDecodeRdataPTR(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "one.one.one.one"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s PTR %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata PTR, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataSOA(t *testing.T) {
- fqdn := TestQName
-
- dm := new(dns.Msg)
- dm.SetQuestion(fqdn, dns.TypeA)
-
- rdata := "ns1.google.com dns-admin.google.com 412412655 900 900 1800 60"
- rr1, _ := dns.NewRR(fmt.Sprintf("%s SOA %s", fqdn, rdata))
- dm.Answer = append(dm.Answer, rr1)
-
- payload, _ := dm.Pack()
-
- _, _, _, offsetRR, _ := DecodeQuestion(1, payload)
- answer, _, _ := DecodeAnswer(len(dm.Answer), offsetRR, payload)
-
- if answer[0].Rdata != rdata {
- t.Errorf("invalid decode for rdata SOA, want %s, got: %s", rdata, answer[0].Rdata)
- }
-}
-
-func TestDecodeRdataSOA_Short(t *testing.T) {
- payload := []byte{
- // header
- 0x28, 0xba, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
- // Query section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer Resource Record,
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type SOA, class IN
- 0x00, 0x06, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH 54
- 0x00, 0x36,
- // RDATA
- // MNAME
- 0x03, 0x6e,
- 0x73, 0x31, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
- 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // RNAME
- 0x09, 0x64,
- 0x6e, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x69, 0x6e,
- 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
- 0x63, 0x6f, 0x6d, 0x00,
- // serial
- 0x18, 0x94, 0xea, 0xef,
- // refresh
- 0x00, 0x00, 0x03, 0x84,
- // retry
- 0x00, 0x00, 0x03, 0x84,
- // expire
- 0x00, 0x00, 0x07, 0x08,
- // minimum -field missing from the RDATA
- }
-
- _, _, _, offsetRR, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("Unable to decode question: %v", err)
- }
- _, _, erra := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(erra, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", erra)
- }
-}
-
-func TestDecodeRdataSOA_Minimization(t *testing.T) {
- // loop between qnames
- payload := []byte{164, 66, 129, 128, 0, 1, 0, 0, 0, 1, 0, 0, 8, 102, 114, 101, 115, 104, 114, 115, 115, 4, 109,
- 99, 104, 100, 2, 109, 101, 0, 0, 28, 0, 1, 192, 21, 0, 6, 0, 1, 0, 0, 0, 60, 0, 43, 6, 100, 110, 115, 49, 48,
- 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)
- _, _, err := DecodeAnswer(1, offsetRR, payload)
- if err != nil {
- t.Errorf(" error returned: %v", err)
- }
-}
-func TestDecodeQuestion_SkipOpt(t *testing.T) {
- payload := []byte{
- 0x43, 0xac, 0x01, 0x00, 0x00, 0x01, 0x00, 0x02,
- 0x00, 0x00, 0x00, 0x00,
- // Query section
- 0x0f, 0x64, 0x6e, 0x73,
- 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c, 0x6c, 0x65,
- 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74, 0x65, 0x73,
- 0x74, 0x00, 0x00, 0x01, 0x00, 0x01,
- // Answer Resource Records
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type OPT, class IN
- 0x00, 0x29, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x01,
- // RDATA
- 0x01,
- // 2nd resource record
- 0x0f, 0x64,
- 0x6e, 0x73, 0x74, 0x61, 0x70, 0x63, 0x6f, 0x6c,
- 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x04, 0x74,
- 0x65, 0x73, 0x74, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x0e, 0x10,
- // RDLENGTH
- 0x00, 0x04,
- // RDATA
- 0x7f, 0x00, 0x00, 0x01,
- }
- _, _, _, offsetrr, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("Unexpected error decoding question: %v", err)
- }
-
- answer, _, erra := DecodeAnswer(2, offsetrr, payload)
- if erra != nil {
- t.Errorf("Unexpected error decoding answer: %v", erra)
- }
- if len(answer) != 1 {
- t.Fatalf("Expected answer to contain one resource record, got %d", len(answer))
- }
- if answer[0].Rdatatype != RdatatypeToString(0x01) || answer[0].Rdata != "127.0.0.1" {
- t.Errorf("unexpected answer %s %s, expected A 127.0.0.1", answer[0].Rdatatype, answer[0].Rdata)
- }
-}
-
func TestDecodeDns_HeaderTooShort(t *testing.T) {
decoded := []byte{183, 59}
_, err := DecodeDNS(decoded)
@@ -913,1434 +39,3 @@ func TestDecodeDns_HeaderTooShort(t *testing.T) {
t.Errorf("bad error returned: %v", err)
}
}
-
-func TestDecodeDnsQuestion_InvalidOffset(t *testing.T) {
- decoded := []byte{183, 59, 130, 217, 128, 16, 0, 51, 165, 67, 0, 0}
- _, _, _, _, err := DecodeQuestion(1, decoded)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-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)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-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)
- if !errors.Is(err, ErrDecodeQuestionQtypeTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-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)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsAnswer_PacketTooShort(t *testing.T) {
- payload := []byte{46, 172, 1, 0, 0, 1, 0, 1, 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, 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)
- _, _, err := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(err, ErrDecodeDNSAnswerTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsAnswer_PathologicalPacket(t *testing.T) {
- // Create a message with one question and `n` answers (`n` determined later).
- decoded := make([]byte, 65500)
- copy(decoded, []byte{88, 27, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0})
-
- // Create a rather suboptimal name for the question.
- // The answers point to this a bajillion times later.
- // This name breaks several rules:v
- // * Label length is > 63
- // * Name length is > 255 bytes
- // * Pointers jump all over the place, not just backwards
- i := 12
- for {
- // Create a bunch of interleaved labels of length 191,
- // each label immediately followed by a pointer to the
- // label next to it. The last label of the interleaved chunk
- // is followed with a pointer to forwards to the next chunk
- // of interleaved labels:
- //
- // [191 ... 191 ... 191 ... ... ptr1 ptr2 ... ptrN 191 ... 191 ...]
- // ^ ^ │ │ │ ^
- // │ └────────────┼────┘ └────┘
- // └───────────────────┘
- //
- // We then repeat this pattern as many times as we can within the
- // first 16383 bytes (so that we can point to it later).
- // Then cleanly closing the name with a null byte in the end allows us to
- // create a name of around 700 kilobytes (I checked once, don't quote me on this).
- if 16384-i < 384 {
- decoded[i] = 0
- break
- }
- for j := 0; j < 192; j += 2 {
- decoded[i] = 191
- i += 2
- }
- for j := 0; j < 190; j += 2 {
- offset := i - 192 + 2
- decoded[i] = 0xc0 | byte(offset>>8)
- decoded[i+1] = byte(offset & 0xff)
- i += 2
- }
- offset := i + 2
- decoded[i] = 0xc0 | byte(offset>>8)
- decoded[i+1] = byte(offset & 0xff)
- i += 2
- }
-
- // Fill in the rest of the question
- copy(decoded[i:], []byte{0, 5, 0, 1})
- i += 4
-
- // Fit as many answers as we can that contain CNAME RDATA pointing to
- // the bloated name created above.
- ancount := 0
- for j := i; j+13 <= len(decoded); j += 13 {
- copy(decoded[j:], []byte{0, 0, 5, 0, 0, 0, 0, 0, 1, 0, 2, 192, 12})
- ancount += 1
- }
-
- // Update the message with the answer count
- decoded[6] = byte(ancount >> 8)
- decoded[7] = byte(ancount & 0xff)
-
- _, _, err := DecodeAnswer(ancount, i, decoded)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsAnswer_RdataTooShort(t *testing.T) {
- payload := []byte{46, 172, 1, 0, 0, 1, 0, 1, 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, 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)
- _, _, err := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(err, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsAnswer_InvalidPtr(t *testing.T) {
- payload := []byte{128, 177, 129, 160, 0, 1, 0, 1, 0, 0, 0, 1, 5, 104, 101, 108, 108, 111, 4,
- 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)
- _, _, err := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsAnswer_InvalidPtr_Loop1(t *testing.T) {
- // loop qname on himself
- payload := []byte{128, 177, 129, 160, 0, 1, 0, 1, 0, 0, 0, 1, 5, 104, 101, 108, 108, 111, 4,
- 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)
- _, _, err := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsAnswer_InvalidPtr_Loop2(t *testing.T) {
- // loop between qnames
- payload := []byte{128, 177, 129, 160, 0, 1, 0, 2, 0, 0, 0, 1, 5, 104, 101, 108, 108, 111, 4,
- 109, 99, 104, 100, 2, 109, 101, 0, 0, 1, 0, 1, 192, 47, 0, 1, 0, 1, 0, 0,
- 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)
- _, _, err := DecodeAnswer(1, offsetRR, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidOffset_NegativeOffset(t *testing.T) {
- payload := []byte{0x01, 0x61, 0x00}
-
- _, _, err := ParseLabels(-1, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidOffset) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidOffset_StartOutOfBounds(t *testing.T) {
- payload := []byte{0x01, 0x61, 0x00}
-
- _, _, err := ParseLabels(4, payload)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidOffset_RunOutOfBounds(t *testing.T) {
- payload := []byte{0x01, 0x61}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidOffset_PointerByteOutOfBounds(t *testing.T) {
- payload := []byte{0x01, 0x61, 0xc0}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_LabelTooShort(t *testing.T) {
- payload := []byte{0x01}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_NoExtraDotAfterPtr(t *testing.T) {
- payload := []byte{0x00, 0x01, 0x61, 0xc0, 0x00}
-
- label, _, _ := ParseLabels(1, payload)
- if label != "a" {
- t.Errorf("bad label parsed: %v", label)
- }
-}
-
-func TestDecodeDnsLabel_InvalidLabelLengthByte1(t *testing.T) {
- payload := []byte{0x40}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidLabelLengthByte2(t *testing.T) {
- payload := []byte{0x80}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_ValidTotalLength(t *testing.T) {
- // A 253-character label
- payload := []byte{
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3d, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00,
- }
- valid := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
-
- label, _, _ := ParseLabels(0, payload)
- if label != valid {
- t.Errorf("bad name parsed: %v", label)
- }
-}
-
-func TestDecodeDnsLabel_InvalidTotalLength_WithoutPtr(t *testing.T) {
- // A 254-character label (including separator dots)
- payload := []byte{
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3e, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00,
- }
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelTooLong) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidTotalLength_WithPtr(t *testing.T) {
- // A 254-character label (including separator dots), containing a pointer
- payload := []byte{
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3f, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x3c, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
- 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x00, 0x01,
- 0x61, 0xc0, 0x00,
- }
- _, _, err := ParseLabels(255, payload)
- if !errors.Is(err, ErrDecodeDNSLabelTooLong) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidPtr_SimpleLoop(t *testing.T) {
- payload := []byte{0x01, 0x61, 0xc0, 0x00}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidPtr_Forwards(t *testing.T) {
- payload := []byte{0xc0, 0x02, 0x00}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidPtr_BackwardsInsideCurrentLabel1(t *testing.T) {
- payload := []byte{0x01, 0x02, 0xc0, 0x01, 0x00}
-
- _, _, err := ParseLabels(0, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidPtr_BackwardsOverlappingLoop(t *testing.T) {
- payload := []byte{0x01, 0x61, 0x01, 0x61, 0xc0, 0x00}
-
- _, _, err := ParseLabels(2, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidPtr_BackwardsOverlappingTerminating(t *testing.T) {
- payload := []byte{0x01, 0x01, 0x00, 0xc0, 0x00}
-
- _, _, err := ParseLabels(1, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_InvalidPtr_BackwardsOverlappingPtr(t *testing.T) {
- // The second pointer byte overlaps the beginning of the label that starts at payload[3]
- payload := []byte{0x00, 0x00, 0xc0, 0x01, 0x00, 0xc0, 0x02}
-
- _, _, err := ParseLabels(3, payload)
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodeDnsLabel_EndOffset_WithoutPtr(t *testing.T) {
- payload := []byte{0x02, 0x61, 0x61, 0x00}
-
- _, offset, _ := ParseLabels(0, payload)
- if offset != 4 {
- t.Errorf("invalid end offset: %v", offset)
- }
-}
-
-func TestDecodeDnsLabel_EndOffset_WithPtr(t *testing.T) {
- payload := []byte{0x01, 0x61, 0x00, 0x02, 0x61, 0x61, 0xc0, 0x00}
-
- _, offset, _ := ParseLabels(3, payload)
- if offset != 8 {
- t.Errorf("invalid end offset: %v", offset)
- }
-}
-
-func TestDecodePayload_QueryHappy(t *testing.T) {
- payload := []byte{
- // header
- 0x9e, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- // name
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Additional records: EDNS OPT with no data, DO = 0, Z=0
- 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00,
- 0x80, 0x00, 0x00, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
- t.Errorf("Unexpected error while decoding payload: %v", err)
- }
- if dm.DNS.MalformedPacket != false {
- t.Errorf("did not expect packet to be malformed")
- }
-
- if dm.DNS.ID != 0x9e84 ||
- dm.DNS.Opcode != 0 ||
- dm.DNS.Rcode != RcodeToString(0) ||
- dm.DNS.Flags.QR ||
- dm.DNS.Flags.TC ||
- dm.DNS.Flags.AA ||
- !dm.DNS.Flags.AD ||
- dm.DNS.Flags.RA {
- t.Error("Invalid DNS header data in message")
- }
-
- if dm.DNS.Qname != "sensorfleet.com" {
- t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
- }
- if dm.DNS.Qtype != "A" {
- t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
- }
-
- if dm.EDNS.Do != 1 ||
- dm.EDNS.UDPSize != 4096 ||
- dm.EDNS.Z != 0 ||
- dm.EDNS.Version != 0 ||
- len(dm.EDNS.Options) != 0 {
- t.Errorf("Unexpected EDNS data")
- }
-
- if len(dm.DNS.DNSRRs.Answers) != 0 ||
- len(dm.DNS.DNSRRs.Nameservers) != 0 ||
- len(dm.DNS.DNSRRs.Records) != 0 {
- t.Errorf("Unexpected sections parsed")
- }
-
-}
-func TestDecodePayload_QueryInvalid(t *testing.T) {
- payload := []byte{
- // header
- 0x9e, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- // name
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x83, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Additional records: EDNS OPT with no data, DO = 1, Z=0
- 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00,
- 0x80, 0x00, 0x00, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
- t.Errorf("Expected error when parsing payload")
- }
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be marked as malformed")
- }
-
- // returned error should wrap the original error
- if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
- t.Errorf("bad error returned: %v", err)
- }
-}
-
-func TestDecodePayload_AnswerHappy(t *testing.T) {
- payload := []byte{
- 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x01,
- // Query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Answer 1
- 0xc0, 0x0c, // pointer to name
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.1
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
- // Answer 2
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.2
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
- // Answer 3
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.3
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
- // Answer 4
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.4
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
- // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
- t.Errorf("Unexpected error while decoding payload: %v", err)
- }
- if dm.DNS.MalformedPacket != false {
- t.Errorf("did not expect packet to be malformed")
- }
-
- if dm.DNS.ID != 0x9e84 ||
- dm.DNS.Opcode != 0 ||
- dm.DNS.Rcode != RcodeToString(0) ||
- !dm.DNS.Flags.QR ||
- dm.DNS.Flags.TC ||
- dm.DNS.Flags.AA ||
- dm.DNS.Flags.AD ||
- !dm.DNS.Flags.RA {
- t.Error("Invalid DNS header data in message")
- }
-
- if dm.DNS.Qname != "sensorfleet.com" {
- t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
- }
- if dm.DNS.Qtype != "A" {
- t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
- }
-
- if len(dm.DNS.DNSRRs.Answers) != 4 {
- t.Errorf("expected 4 answers, got %d", len(dm.DNS.DNSRRs.Answers))
- }
-
- for i, ans := range dm.DNS.DNSRRs.Answers {
- expected := DNSAnswer{
- Name: dm.DNS.Qname,
- Rdatatype: RdatatypeToString(0x0001),
- Class: "IN", // 0x0001,
- TTL: 300,
- Rdata: fmt.Sprintf("10.10.1.%d", i+1),
- }
- if expected != ans {
- t.Errorf("unexpected answer (%d). expected %v, got %v", i, expected, ans)
- }
- }
-
- if dm.EDNS.Do != 0 ||
- dm.EDNS.UDPSize != 1232 ||
- dm.EDNS.Z != 0 ||
- dm.EDNS.Version != 0 ||
- len(dm.EDNS.Options) != 0 {
- t.Errorf("Unexpected EDNS data")
- }
-
- if len(dm.DNS.DNSRRs.Nameservers) != 0 ||
- len(dm.DNS.DNSRRs.Records) != 0 {
- t.Errorf("Unexpected sections parsed")
- }
-
-}
-
-func TestDecodePayload_AnswerMultipleQueries(t *testing.T) {
- payload := []byte{
- 0x9e, 0x84, 0x81, 0x80, 0x00, 0x02, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x01,
- // Query section
- // query 1
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // query 2
- 0x0a, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
-
- // Answer 1
- 0xc0, 0x0c, // pointer to name
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.1
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
- // Answer 2
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.2
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
- // Answer 3
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.3
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
- // Answer 4
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.4
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
- // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
- t.Errorf("Unexpected error while decoding payload: %v", err)
- }
- if dm.DNS.MalformedPacket != false {
- t.Errorf("did not expect packet to be malformed")
- }
-
- if dm.DNS.ID != 0x9e84 ||
- dm.DNS.Opcode != 0 ||
- dm.DNS.Rcode != RcodeToString(0) ||
- !dm.DNS.Flags.QR ||
- dm.DNS.Flags.TC ||
- dm.DNS.Flags.AA ||
- dm.DNS.Flags.AD ||
- !dm.DNS.Flags.RA {
- t.Error("Invalid DNS header data in message")
- }
-
- if dm.DNS.Qname != "ensorfleet.com" {
- t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
- }
- if dm.DNS.Qtype != "A" {
- t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
- }
-
- if len(dm.DNS.DNSRRs.Answers) != 4 {
- t.Errorf("expected 4 answers, got %d", len(dm.DNS.DNSRRs.Answers))
- }
-
- for i, ans := range dm.DNS.DNSRRs.Answers {
- expected := DNSAnswer{
- Name: "s" + dm.DNS.Qname, // answers have qname from 1st query data, 2nd data is missing 's'
- Rdatatype: RdatatypeToString(0x0001),
- Class: "IN", // 0x0001,
- TTL: 300,
- Rdata: fmt.Sprintf("10.10.1.%d", i+1),
- }
- if expected != ans {
- t.Errorf("unexpected answer (%d). expected %v, got %v", i, expected, ans)
- }
- }
-
- if dm.EDNS.Do != 0 ||
- dm.EDNS.UDPSize != 1232 ||
- dm.EDNS.Z != 0 ||
- dm.EDNS.Version != 0 ||
- len(dm.EDNS.Options) != 0 {
- t.Errorf("Unexpected EDNS data")
- }
-
- if len(dm.DNS.DNSRRs.Nameservers) != 0 ||
- len(dm.DNS.DNSRRs.Records) != 0 {
- t.Errorf("Unexpected sections parsed")
- }
-
-}
-
-func TestDecodePayload_AnswerInvalid(t *testing.T) {
- payload := []byte{
- 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x01,
- // Query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Answer 1
- 0xc0, 0x0c, // pointer to name
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.1
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
- // Answer 2
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.2
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
- // Answer 3
- 0xc0, 0xff,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.3
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
- // Answer 4
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.4
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
- // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
- t.Error("expected decoding to fail")
- }
- // returned error should wrap the original error
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be malformed")
- }
-}
-
-func TestDecodePayload_AnswerInvalidQuery(t *testing.T) {
- payload := []byte{
- 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x01,
- // Query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x83, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Answer 1
- 0xc0, 0x0c, // pointer to name
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.1
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
- // Answer 2
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.2
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
- // Answer 3
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.3
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
- // Answer 4
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.4
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
- // Additianl records, EDNS Option, 0 bytes DO=0, Z = 0
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
- t.Error("expected decoding to fail")
- }
- // returned error should wrap the original error
- if !errors.Is(err, ErrDecodeDNSLabelInvalidData) {
- t.Errorf("bad error returned: %v", err)
- }
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be malformed")
- }
-
- // after error has been detected in the query part, we should not parse
- // anything from answers
- if len(dm.DNS.DNSRRs.Answers) != 0 {
- t.Errorf("did not expect answers to be parsed, but there were %d parsed", len(dm.DNS.DNSRRs.Answers))
- }
-}
-
-func TestDecodePayload_AnswerInvalidEdns(t *testing.T) {
- payload := []byte{
- 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x01,
- // Query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Answer 1
- 0xc0, 0x0c, // pointer to name
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.1
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
- // Answer 2
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.2
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
- // Answer 3
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.3
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
- // Answer 4
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.4
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
- // Additianl records, Invalid EDNS Option
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x01,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
- t.Error("expected decoding to fail")
- }
- // returned error should wrap the original error
- if !errors.Is(err, ErrDecodeEdnsOptionTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be malformed")
- }
-}
-
-func TestDecodePayload_AnswerInvaliAdditional(t *testing.T) {
- payload := []byte{
- 0x9e, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x01,
- // Query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Answer 1
- 0xc0, 0x0c, // pointer to name
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.1
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x01,
- // Answer 2
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.2
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x02,
- // Answer 3
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.3
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x03,
- // Answer 4
- 0xc0, 0x0c,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x01, 0x2c,
- // 10.10.1.4
- 0x00, 0x04, 0x0a, 0x0a, 0x01, 0x04,
- // Additianl records, Invalid RDLENGTH
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
- t.Error("expected decoding to fail")
- }
- // returned error should wrap the original error
- if !errors.Is(err, ErrDecodeDNSAnswerRdataTooShort) {
- t.Errorf("bad error returned: %v", err)
- }
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be malformed")
- }
-}
-
-func TestDecodePayload_AnswerError(t *testing.T) {
- payload := []byte{
- // header
- 0xa8, 0x1a, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // query
- 0x03, 0x66, 0x6f, 0x6f,
- 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
- 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Authority section
- // name
- 0xc0, 0x10,
- // type SOA, class IN
- 0x00, 0x06, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x00, 0x3c,
- // RDLENGTH
- 0x00, 0x26,
- // RDATA
- // MNAME
- 0x03, 0x6e, 0x73, 0x31,
- 0xc0, 0x10,
- // RNAME
- 0x09, 0x64, 0x6e, 0x73, 0x2d, 0x61,
- 0x64, 0x6d, 0x69, 0x6e, 0xc0, 0x10,
- // serial
- 0x19, 0xa1, 0x4a, 0xb4,
- // refresh
- 0x00, 0x00, 0x03, 0x84,
- // retry
- 0x00, 0x00, 0x03, 0x84,
- // expire
- 0x00, 0x00, 0x07, 0x08,
- // minimum
- 0x00, 0x00, 0x00, 0x3c,
- // Additianl records, EDNS Option, 0 bytes DO=1, Z = 0
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00,
- 0x00, 0x80, 0x00, 0x00, 0x00,
- }
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
- t.Errorf("Unexpected error while decoding payload: %v", err)
- }
- if dm.DNS.MalformedPacket != false {
- t.Errorf("did not expect packet to be malformed")
- }
-
- if dm.DNS.ID != 0xa81a ||
- dm.DNS.Opcode != 0 ||
- dm.DNS.Rcode != RcodeToString(3) ||
- !dm.DNS.Flags.QR ||
- dm.DNS.Flags.TC ||
- dm.DNS.Flags.AA ||
- dm.DNS.Flags.AD ||
- !dm.DNS.Flags.RA {
- t.Error("Invalid DNS header data in message")
- }
-
- if dm.DNS.Qname != "foo.google.com" {
- t.Errorf("Unexpected query name: %s", dm.DNS.Qname)
- }
- if dm.DNS.Qtype != "A" {
- t.Errorf("Unexpected query type: %s", dm.DNS.Qtype)
- }
-
- if len(dm.DNS.DNSRRs.Answers) != 0 {
- t.Errorf("did not expect any answers, got %d", len(dm.DNS.DNSRRs.Answers))
- }
-
- if len(dm.DNS.DNSRRs.Nameservers) != 1 {
- t.Errorf("expected 1 authority RR, got %d", len(dm.DNS.DNSRRs.Nameservers))
- }
- expected := DNSAnswer{
- Name: "google.com",
- Rdatatype: RdatatypeToString(0x0006),
- Class: "IN", // 0x0001,
- TTL: 60,
- Rdata: "ns1.google.com dns-admin.google.com 430000820 900 900 1800 60",
- }
-
- if dm.DNS.DNSRRs.Nameservers[0] != expected {
- t.Errorf("unexpected SOA record parsed, expected %v, git %v", expected, dm.DNS.DNSRRs.Nameservers[0])
- }
-
- if dm.EDNS.Do != 1 ||
- dm.EDNS.UDPSize != 1232 ||
- dm.EDNS.Z != 0 ||
- dm.EDNS.Version != 0 ||
- len(dm.EDNS.Options) != 0 {
- t.Errorf("Unexpected EDNS data")
- }
-
-}
-
-func TestDecodePayload_AnswerError_Invalid(t *testing.T) {
- payload := []byte{
- // header
- 0xa8, 0x1a, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // query
- 0x03, 0x66, 0x6f, 0x6f,
- 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
- 0x63, 0x6f, 0x6d, 0x00,
- // type A, class IN
- 0x00, 0x01, 0x00, 0x01,
- // Authority section
- // name
- 0xc0, 0x10,
- // type SOA, class IN
- 0x00, 0x06, 0x00, 0x01,
- // TTL
- 0x00, 0x00, 0x00, 0x3c,
- // RDLENGTH
- 0x00, 0x26,
- // RDATA
- // MNAME, invalid offset in pointer
- 0x03, 0x6e, 0x73, 0x31,
- 0xc0, 0xff,
- // RNAME
- 0x09, 0x64, 0x6e, 0x73, 0x2d, 0x61,
- 0x64, 0x6d, 0x69, 0x6e, 0xc0, 0x10,
- // serial
- 0x19, 0xa1, 0x4a, 0xb4,
- // refresh
- 0x00, 0x00, 0x03, 0x84,
- // retry
- 0x00, 0x00, 0x03, 0x84,
- // expire
- 0x00, 0x00, 0x07, 0x08,
- // minimum
- 0x00, 0x00, 0x00, 0x3c,
- // Additianl records, EDNS Option, 0 bytes DO=1, Z = 0
- 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00,
- 0x00, 0x80, 0x00, 0x00, 0x00,
- }
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err == nil {
- t.Error("expected decoding to fail")
- }
- // returned error should wrap the original error
- if !errors.Is(err, ErrDecodeDNSLabelInvalidPointer) {
- t.Errorf("bad error returned: %v", err)
- }
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be malformed")
- }
-
-}
-
-func TestDecodePayload_AdditionalRRAndEDNS(t *testing.T) {
- // payload containing both addition RR and EDNS, ensure we are
- // able to parse all of them
- payload := []byte{
- 0x7b, 0x97, 0x84, 0x0, 0x0, 0x1, 0x0, 0x2,
- 0x0, 0x2, 0x0, 0x5, 0xf, 0x6f, 0x63, 0x63,
- 0x2d, 0x30, 0x2d, 0x31, 0x35, 0x30, 0x30,
- 0x2d, 0x31, 0x35, 0x30, 0x31, 0x1, 0x31,
- 0x6, 0x6e, 0x66, 0x6c, 0x78, 0x73, 0x6f,
- 0x3, 0x6e, 0x65, 0x74, 0x0, 0x0, 0x1, 0x0,
- 0x1, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0,
- 0x0, 0x0, 0x1e, 0x0, 0x4, 0x2d, 0x39, 0x24,
- 0x97, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0,
- 0x0, 0x0, 0x1e, 0x0, 0x4, 0x2d, 0x39, 0x24,
- 0x94, 0xc0, 0x1e, 0x0, 0x2, 0x0, 0x1, 0x0,
- 0x1, 0x51, 0x80, 0x0, 0x7, 0x1, 0x65, 0x2,
- 0x6e, 0x73, 0xc0, 0x1e, 0xc0, 0x1e, 0x0,
- 0x2, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80, 0x0,
- 0x4, 0x1, 0x66, 0xc0, 0x5c, 0x0, 0x0, 0x29,
- 0x4, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0,
- 0x5a, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80,
- 0x0, 0x4, 0x2d, 0x39, 0x8, 0x1, 0xc0, 0x5a,
- 0x0, 0x1c, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80, 0x0,
- 0x10, 0x2a, 0x0, 0x86, 0xc0, 0x20, 0x8, 0x0, 0x0,
- 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc0, 0x6d,
- 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x51, 0x80, 0x0, 0x4,
- 0x2d, 0x39, 0x9, 0x1, 0xc0, 0x6d, 0x0, 0x1c, 0x0, 0x1,
- 0x0, 0x1, 0x51, 0x80, 0x0, 0x10, 0x2a, 0x0, 0x86, 0xc0,
- 0x20, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("error when deocoding header: %v", err)
- }
-
- if err := DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
- t.Errorf("unexpected error while decoding payload: %v", err)
- }
-
- if len(dm.DNS.DNSRRs.Answers) != 2 || len(dm.DNS.DNSRRs.Nameservers) != 2 ||
- len(dm.DNS.DNSRRs.Records) != 4 || dm.EDNS.UDPSize != 1200 ||
- len(dm.EDNS.Options) != 0 {
- t.Errorf("unexpected result while parsing payload: %#v", dm.DNS)
- }
-
-}
-
-func TestDecodePayload_Truncated(t *testing.T) {
- payload := []byte{
- // header
- 0x77, 0xa0, 0x83, 0x80, 0x00, 0x01, 0x00, 0x23,
- 0x00, 0x00, 0x00, 0x00,
- // query
- 0x02, 0x41, 0x64, 0x0d,
- 0x6e, 0x6e, 0x6e, 0x6e,
- 0x6e, 0x6e, 0x6e, 0x6e,
- 0x6e, 0x6e, 0x6e, 0x6e,
- 0x6e, 0x02, 0x46, 0x52,
- 0x00, 0x00, 0x01, 0x00, 0x01,
- // answer 1
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x01,
- // answer 2
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x02,
- // answer 3
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x03,
- // answer 4
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x04,
- // answer 5
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x05,
- // answer 6
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x06,
- // answer 7
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x07,
- // answer 8
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x08,
- // answer 9
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x09,
- // answer 10
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x0a,
- // answer 11
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x0b,
- // answer 12
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x0c,
- // answer 13
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x0d,
- // answer 14
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x0e,
- // answer 15
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x0f,
- // answer 16
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x10,
- // answer 17
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x11,
- // answer 18
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x12,
- // answer 19
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x13,
- // answer 20
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x14,
- // answer 21
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x15,
- // answer 22
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x16,
- // answer 23
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x17,
- // answer 24
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x18,
- // answer 25
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x19,
- // answer 26
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x1a,
- // answer 27
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x1b,
- // answer 28
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x1c,
- // answer 29
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00, 0x04, 0x0a, 0x01,
- 0x01, 0x1d,
- // answer 30
- 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x6b, 0x00,
- }
-
- dm := DNSMessage{}
- dm.DNS.Payload = payload
- dm.DNS.Length = len(payload)
-
- header, err := DecodeDNS(payload)
- if err != nil {
- t.Errorf("unexpected error when decoding header: %v", err)
- }
-
- if err = DecodePayload(&dm, &header, pkgconfig.GetDefaultConfig()); err != nil {
- t.Error("expected no error on decode")
- }
-
- if dm.DNS.Flags.TC == false {
- t.Error("truncated answer expected")
- }
-
- if dm.DNS.MalformedPacket != true {
- t.Errorf("expected packet to be malformed")
- }
-
-}
diff --git a/dnsutils/message.go b/dnsutils/dnsmessage.go
similarity index 74%
rename from dnsutils/message.go
rename to dnsutils/dnsmessage.go
index 648293f4..974f7aa5 100644
--- a/dnsutils/message.go
+++ b/dnsutils/dnsmessage.go
@@ -16,13 +16,11 @@ import (
"strings"
"time"
- "github.com/dmachard/go-dnscollector/pkgconfig"
"github.com/dmachard/go-dnstap-protobuf"
"github.com/dmachard/go-netutils"
"github.com/flosch/pongo2/v6"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
- "github.com/miekg/dns"
"google.golang.org/protobuf/proto"
)
@@ -43,37 +41,6 @@ var (
ATagsDirectives = regexp.MustCompile(`^atags*`)
)
-func GetFakeDNS() ([]byte, error) {
- dnsmsg := new(dns.Msg)
- dnsmsg.SetQuestion("dns.collector.", dns.TypeA)
- return dnsmsg.Pack()
-}
-
-func GetIPPort(dm *DNSMessage) (string, int, string, int) {
- srcIP, srcPort := "0.0.0.0", 53
- dstIP, dstPort := "0.0.0.0", 53
- if dm.NetworkInfo.Family == "INET6" {
- srcIP, dstIP = "::", "::"
- }
-
- if dm.NetworkInfo.QueryIP != "-" {
- srcIP = dm.NetworkInfo.QueryIP
- srcPort, _ = strconv.Atoi(dm.NetworkInfo.QueryPort)
- }
- if dm.NetworkInfo.ResponseIP != "-" {
- dstIP = dm.NetworkInfo.ResponseIP
- dstPort, _ = strconv.Atoi(dm.NetworkInfo.ResponsePort)
- }
-
- // reverse destination and source
- if dm.DNS.Type == DNSReply {
- srcIPTmp, srcPortTmp := srcIP, srcPort
- srcIP, srcPort = dstIP, dstPort
- dstIP, dstPort = srcIPTmp, srcPortTmp
- }
- return srcIP, srcPort, dstIP, dstPort
-}
-
type DNSAnswer struct {
Name string `json:"name"`
Rdatatype string `json:"rdatatype"`
@@ -283,19 +250,17 @@ func (dm *DNSMessage) Init() {
Identity: "-",
Version: "-",
TimestampRFC3339: "-",
- // LatencySec: "-",
- Extra: "-",
- PolicyRule: "-",
- PolicyType: "-",
- PolicyMatch: "-",
- PolicyAction: "-",
- PolicyValue: "-",
- PeerName: "-",
- QueryZone: "-",
+ Extra: "-",
+ PolicyRule: "-",
+ PolicyType: "-",
+ PolicyMatch: "-",
+ PolicyAction: "-",
+ PolicyValue: "-",
+ PeerName: "-",
+ QueryZone: "-",
}
dm.DNS = DNS{
- ID: 0,
Type: "-",
MalformedPacket: false,
Rcode: "-",
@@ -306,12 +271,7 @@ func (dm *DNSMessage) Init() {
}
dm.EDNS = DNSExtended{
- UDPSize: 0,
- ExtendedRcode: 0,
- Version: 0,
- Do: 0,
- Z: 0,
- Options: []DNSOption{},
+ Options: []DNSOption{},
}
}
@@ -672,25 +632,25 @@ func (dm *DNSMessage) ToTextLine(format []string, fieldDelimiter string, fieldBo
if len(qname) == 0 {
s.WriteString(".")
} else {
- quoteStringAndWrite(&s, qname, fieldDelimiter, fieldBoundary)
+ QuoteStringAndWrite(&s, qname, fieldDelimiter, fieldBoundary)
}
case directive == "identity":
if len(dm.DNSTap.Identity) == 0 {
s.WriteString("-")
} else {
- quoteStringAndWrite(&s, dm.DNSTap.Identity, fieldDelimiter, fieldBoundary)
+ QuoteStringAndWrite(&s, dm.DNSTap.Identity, fieldDelimiter, fieldBoundary)
}
case directive == "peer-name":
if len(dm.DNSTap.PeerName) == 0 {
s.WriteString("-")
} else {
- quoteStringAndWrite(&s, dm.DNSTap.PeerName, fieldDelimiter, fieldBoundary)
+ QuoteStringAndWrite(&s, dm.DNSTap.PeerName, fieldDelimiter, fieldBoundary)
}
case directive == "version":
if len(dm.DNSTap.Version) == 0 {
s.WriteString("-")
} else {
- quoteStringAndWrite(&s, dm.DNSTap.Version, fieldDelimiter, fieldBoundary)
+ QuoteStringAndWrite(&s, dm.DNSTap.Version, fieldDelimiter, fieldBoundary)
}
case directive == "extra":
s.WriteString(dm.DNSTap.Extra)
@@ -1367,12 +1327,12 @@ func (dm *DNSMessage) ApplyRelabeling(dnsFields map[string]interface{}) error {
if value, exists := dnsFields[replacement]; exists {
switch v := value.(type) {
case []string:
- dnsFields[replacement] = append(v, convertToString(dnsFields[key]))
+ dnsFields[replacement] = append(v, ConvertToString(dnsFields[key]))
default:
- dnsFields[replacement] = []string{convertToString(v), convertToString(dnsFields[key])}
+ dnsFields[replacement] = []string{ConvertToString(v), ConvertToString(dnsFields[key])}
}
} else {
- dnsFields[replacement] = convertToString(dnsFields[key])
+ dnsFields[replacement] = ConvertToString(dnsFields[key])
}
}
@@ -1408,31 +1368,51 @@ func (dm *DNSMessage) Matching(matching map[string]interface{}) (error, bool) {
switch expectedValue.Kind() {
// integer
case reflect.Int:
- if match, _ := matchUserInteger(realValue, expectedValue); !match {
+ match, err := matchUserInteger(realValue, expectedValue)
+ if err != nil {
+ return err, false
+ }
+ if !match {
return nil, false
}
// string
case reflect.String:
- if match, _ := matchUserPattern(realValue, expectedValue); !match {
+ match, err := matchUserPattern(realValue, expectedValue)
+ if err != nil {
+ return err, false
+ }
+ if !match {
return nil, false
}
// bool
case reflect.Bool:
- if match, _ := matchUserBoolean(realValue, expectedValue); !match {
+ match, err := matchUserBoolean(realValue, expectedValue)
+ if err != nil {
+ return err, false
+ }
+ if !match {
return nil, false
}
// map
case reflect.Map:
- if match, _ := matchUserMap(realValue, expectedValue); !match {
+ match, err := matchUserMap(realValue, expectedValue)
+ if err != nil {
+ return err, false
+ }
+ if !match {
return nil, false
}
// list/slice
case reflect.Slice:
- if match, _ := matchUserSlice(realValue, expectedValue); !match {
+ match, err := matchUserSlice(realValue, expectedValue)
+ if err != nil {
+ return err, false
+ }
+ if !match {
return nil, false
}
@@ -1445,501 +1425,3 @@ func (dm *DNSMessage) Matching(matching map[string]interface{}) (error, bool) {
return nil, isMatch
}
-
-// map can be provided by user in the config
-// dns.qname:
-// match-source: "file://./tests/testsdata/filtering_keep_domains_regex.txt"
-// source-kind: "regexp_list"
-func matchUserMap(realValue, expectedValue reflect.Value) (bool, error) {
- for _, opKey := range expectedValue.MapKeys() {
- opValue := expectedValue.MapIndex(opKey)
- opName := opKey.Interface().(string)
-
- switch opName {
- // Integer great than ?
- case MatchingOpGreaterThan:
- isFloat, isInt := false, false
- if _, ok := opValue.Interface().(float64); ok {
- isFloat = true
- }
- if _, ok := opValue.Interface().(int); !ok {
- isInt = true
- }
-
- if !isFloat && !isInt {
- return false, fmt.Errorf("integer is expected for greater-than operator")
- }
-
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a int
- if _, ok := elemValue.Interface().(int); !ok {
- continue
- }
-
- // Check for match
- if elemValue.Interface().(int) > opValue.Interface().(int) {
- return true, nil
- }
- }
- return false, nil
- }
-
- if realValue.Kind() == reflect.Float64 {
- if realValue.Interface().(float64) > opValue.Interface().(float64) {
- return true, nil
- }
- }
-
- if realValue.Kind() == reflect.Int {
- if realValue.Interface().(int) > opValue.Interface().(int) {
- return true, nil
- }
- }
-
- return false, nil
-
- // Integer lower than ?
- case MatchingOpLowerThan:
- if _, ok := opValue.Interface().(int); !ok {
- return false, fmt.Errorf("integer is expected for lower-than operator")
- }
-
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a int
- if _, ok := elemValue.Interface().(int); !ok {
- continue
- }
-
- // Check for match
- if elemValue.Interface().(int) < opValue.Interface().(int) {
- return true, nil
- }
- }
- return false, nil
- }
-
- if realValue.Kind() != reflect.Int {
- return false, nil
- }
- if realValue.Interface().(int) < opValue.Interface().(int) {
- return true, nil
- }
- return false, nil
-
- // Ignore these operators
- case MatchingOpSource, MatchingOpSourceKind:
- continue
-
- // List of pattern
- case MatchingKindRegexp:
- patternList := opValue.Interface().([]*regexp.Regexp)
-
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a string
- if _, ok := elemValue.Interface().(string); !ok {
- continue
- }
-
- // Check for a match with the regex pattern
- for _, pattern := range patternList {
- if pattern.MatchString(elemValue.Interface().(string)) {
- return true, nil
- }
- }
- }
- // No match found in the slice
- return false, nil
- }
-
- if realValue.Kind() != reflect.String {
- return false, nil
- }
- for _, pattern := range patternList {
- if pattern.MatchString(realValue.Interface().(string)) {
- return true, nil
- }
- }
- // No match found in pattern list
- return false, nil
-
- // List of string
- case MatchingKindString:
- stringList := opValue.Interface().([]string)
-
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a string
- if _, ok := elemValue.Interface().(string); !ok {
- continue
- }
-
- // Check for a match with the text
- for _, textItem := range stringList {
- if textItem == realValue.Interface().(string) {
- return true, nil
- }
- }
- }
- // No match found in the slice
- return false, nil
- }
-
- if realValue.Kind() != reflect.String {
- return false, nil
- }
- for _, textItem := range stringList {
- if textItem == realValue.Interface().(string) {
- return true, nil
- }
- }
-
- // No match found in string list
- return false, nil
-
- default:
- return false, fmt.Errorf("invalid operator '%s', ignore it", opKey.Interface().(string))
- }
- }
- return true, nil
-}
-
-// list can be provided by user in the config
-// dns.qname:
-// - ".*\\.github\\.com$"
-// - "^www\\.google\\.com$"
-func matchUserSlice(realValue, expectedValue reflect.Value) (bool, error) {
- match := false
- for i := 0; i < expectedValue.Len() && !match; i++ {
- reflectedSub := reflect.ValueOf(expectedValue.Index(i).Interface())
-
- switch reflectedSub.Kind() {
- case reflect.Int:
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
- if _, ok := elemValue.Interface().(int); !ok {
- continue
- }
- if reflectedSub.Interface().(int) == elemValue.Interface().(int) {
- return true, nil
- }
- }
- }
-
- if realValue.Kind() != reflect.Int || realValue.Interface().(int) != reflectedSub.Interface().(int) {
- continue
- }
- match = true
- case reflect.String:
- pattern := regexp.MustCompile(reflectedSub.Interface().(string))
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len() && !match; i++ {
- elemValue := realValue.Index(i)
- if _, ok := elemValue.Interface().(string); !ok {
- continue
- }
- // Check for a match with the regex pattern
- if pattern.MatchString(elemValue.Interface().(string)) {
- match = true
- }
- }
- }
-
- if realValue.Kind() != reflect.String {
- continue
- }
-
- if pattern.MatchString(realValue.Interface().(string)) {
- match = true
- }
- }
- }
- return match, nil
-}
-
-// boolean can be provided by user in the config
-// dns.flags.qr: true
-func matchUserBoolean(realValue, expectedValue reflect.Value) (bool, error) {
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a int
- if _, ok := elemValue.Interface().(bool); !ok {
- continue
- }
-
- // Check for match
- if expectedValue.Interface().(bool) == elemValue.Interface().(bool) {
- return true, nil
- }
- }
- }
-
- if realValue.Kind() != reflect.Bool {
- return false, nil
- }
-
- if expectedValue.Interface().(bool) != realValue.Interface().(bool) {
- return false, nil
- }
- return true, nil
-}
-
-// integer can be provided by user in the config
-// dns.opcode: 0
-func matchUserInteger(realValue, expectedValue reflect.Value) (bool, error) {
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a int
- if _, ok := elemValue.Interface().(int); !ok {
- continue
- }
-
- // Check for match
- if expectedValue.Interface().(int) == elemValue.Interface().(int) {
- return true, nil
- }
- }
- }
-
- if realValue.Kind() != reflect.Int {
- return false, nil
- }
- if expectedValue.Interface().(int) != realValue.Interface().(int) {
- return false, nil
- }
-
- return true, nil
-}
-
-// regexp can be provided by user in the config
-// dns.qname: "^.*\\.github\\.com$"
-func matchUserPattern(realValue, expectedValue reflect.Value) (bool, error) {
- pattern := regexp.MustCompile(expectedValue.Interface().(string))
-
- // If realValue is a slice
- if realValue.Kind() == reflect.Slice {
- for i := 0; i < realValue.Len(); i++ {
- elemValue := realValue.Index(i)
-
- // Check if the element is a string
- if _, ok := elemValue.Interface().(string); !ok {
- continue
- }
-
- // Check for a match with the regex pattern
- if pattern.MatchString(elemValue.Interface().(string)) {
- return true, nil
- }
- }
- // No match found in the slice
- return false, nil
- }
-
- // If realValue is not a string
- if realValue.Kind() != reflect.String {
- return false, nil
- }
-
- // Check for a match with the regex pattern
- if !pattern.MatchString(realValue.String()) {
- return false, nil
- }
-
- // Match found for a single value
- return true, nil
-}
-
-func getFieldByJSONTag(value reflect.Value, nestedKeys string) (reflect.Value, bool) {
- listKeys := strings.SplitN(nestedKeys, ".", 2)
-
- for j, jsonKey := range listKeys {
- // Iterate over the fields of the structure
- for i := 0; i < value.NumField(); i++ {
- field := value.Type().Field(i)
-
- // Get JSON tag
- tag := field.Tag.Get("json")
- tagClean := strings.TrimSuffix(tag, ",omitempty")
-
- // Check if the JSON tag matches
- if tagClean == jsonKey {
- // ptr
- switch field.Type.Kind() {
- // integer
- case reflect.Ptr:
- if fieldValue, found := getFieldByJSONTag(value.Field(i).Elem(), listKeys[j+1]); found {
- return fieldValue, true
- }
-
- // struct
- case reflect.Struct:
- if fieldValue, found := getFieldByJSONTag(value.Field(i), listKeys[j+1]); found {
- return fieldValue, true
- }
-
- // slice if a list
- case reflect.Slice:
- if fieldValue, leftKey, found := getSliceElement(value.Field(i), listKeys[j+1]); found {
- switch field.Type.Kind() {
- case reflect.Struct:
- if fieldValue, found := getFieldByJSONTag(fieldValue, leftKey); found {
- return fieldValue, true
- }
- case reflect.Slice:
- var result []interface{}
- for i := 0; i < fieldValue.Len(); i++ {
-
- if fieldValue.Index(i).Kind() == reflect.String || fieldValue.Index(i).Kind() == reflect.Int {
- result = append(result, fieldValue.Index(i).Interface())
- } else {
- if sliceValue, found := getFieldByJSONTag(fieldValue.Index(i), leftKey); found {
- result = append(result, sliceValue.Interface())
- }
- }
- }
- // If the list is not empty, return the list
- if len(result) > 0 {
- return reflect.ValueOf(result), true
- }
- default:
- return value.Field(i), true
- }
- }
-
- // int, string
- default:
- return value.Field(i), true
- }
- }
- }
- }
-
- return reflect.Value{}, false
-}
-
-func getSliceElement(sliceValue reflect.Value, nestedKeys string) (reflect.Value, string, bool) {
- listKeys := strings.SplitN(nestedKeys, ".", 2)
- leftKeys := ""
- if len(listKeys) > 1 {
- leftKeys = listKeys[1]
- }
- sliceIndex := listKeys[0]
-
- if sliceIndex == "*" {
- return sliceValue, leftKeys, true
- }
-
- // Convert the slice index from string to int
- index, err := strconv.Atoi(sliceIndex)
- if err != nil {
- // Handle the error (e.g., invalid index format)
- return reflect.Value{}, leftKeys, false
- }
-
- for i := 0; i < sliceValue.Len(); i++ {
- if index == i {
- return sliceValue.Index(i), leftKeys, true
- }
- }
- // If no match is found, return an empty reflect.Value
- return reflect.Value{}, leftKeys, false
-}
-
-func GetFakeDNSMessage() DNSMessage {
- dm := DNSMessage{}
- dm.Init()
- dm.DNSTap.Identity = "collector"
- dm.DNSTap.Version = "dnscollector 1.0.0"
- dm.DNSTap.Operation = "CLIENT_QUERY"
- dm.DNSTap.PeerName = "localhost (127.0.0.1)"
- dm.DNS.Type = DNSQuery
- dm.DNS.Qname = pkgconfig.ProgQname
- dm.NetworkInfo.QueryIP = "1.2.3.4"
- dm.NetworkInfo.QueryPort = "1234"
- dm.NetworkInfo.ResponseIP = "4.3.2.1"
- dm.NetworkInfo.ResponsePort = "4321"
- dm.DNS.Rcode = "NOERROR"
- dm.DNS.Qtype = "A"
- return dm
-}
-
-func GetFakeDNSMessageWithPayload() DNSMessage {
- // fake dns query payload
- dnsmsg := new(dns.Msg)
- dnsmsg.SetQuestion("dnscollector.dev.", dns.TypeAAAA)
- dnsquestion, _ := dnsmsg.Pack()
-
- dm := GetFakeDNSMessage()
- dm.NetworkInfo.Family = netutils.ProtoIPv4
- dm.NetworkInfo.Protocol = netutils.ProtoUDP
- dm.DNS.Payload = dnsquestion
- dm.DNS.Length = len(dnsquestion)
- return dm
-}
-
-func GetFlatDNSMessage() (ret map[string]interface{}, err error) {
- dm := DNSMessage{}
- dm.Init()
- dm.InitTransforms()
- ret, err = dm.Flatten()
- return
-}
-
-func GetReferenceDNSMessage() DNSMessage {
- dm := DNSMessage{}
- dm.Init()
- dm.InitTransforms()
- return dm
-}
-
-func convertToString(value interface{}) string {
- switch v := value.(type) {
- case int:
- return strconv.Itoa(v)
- case bool:
- return strconv.FormatBool(v)
- case float64:
- return strconv.FormatFloat(v, 'f', -1, 64)
- case string:
- return v
- default:
- return fmt.Sprintf("%v", v)
- }
-}
-
-func quoteStringAndWrite(s *strings.Builder, fieldString, fieldDelimiter, fieldBoundary string) {
- if len(fieldDelimiter) > 0 {
- if strings.Contains(fieldString, fieldDelimiter) {
- fieldEscaped := fieldString
- if strings.Contains(fieldString, fieldBoundary) {
- fieldEscaped = strings.ReplaceAll(fieldEscaped, fieldBoundary, "\\"+fieldBoundary)
- }
- s.WriteString(fmt.Sprintf(fieldBoundary+"%s"+fieldBoundary, fieldEscaped))
- } else {
- s.WriteString(fieldString)
- }
- } else {
- s.WriteString(fieldString)
- }
-}
diff --git a/dnsutils/dnsmessage_dnstap_test.go b/dnsutils/dnsmessage_dnstap_test.go
new file mode 100644
index 00000000..8dd60195
--- /dev/null
+++ b/dnsutils/dnsmessage_dnstap_test.go
@@ -0,0 +1,172 @@
+package dnsutils
+
+import (
+ "testing"
+
+ "github.com/dmachard/go-dnstap-protobuf"
+ "google.golang.org/protobuf/proto"
+)
+
+// Tests for DNSTap format
+func encodeToDNSTap(dm DNSMessage, t *testing.T) *ExtendedDnstap {
+ // encode to extended dnstap
+ tapMsg, err := dm.ToDNSTap(true)
+ if err != nil {
+ t.Fatalf("could not encode to extended dnstap: %v\n", err)
+ }
+
+ // decode dnstap message
+ dt := &dnstap.Dnstap{}
+ err = proto.Unmarshal(tapMsg, dt)
+ if err != nil {
+ t.Fatalf("error to decode dnstap: %v", err)
+ }
+
+ // decode extended part
+ edt := &ExtendedDnstap{}
+ err = proto.Unmarshal(dt.GetExtra(), edt)
+ if err != nil {
+ t.Fatalf("error to decode extended dnstap: %v", err)
+ }
+ return edt
+}
+
+func TestDnsMessage_ToDNSTap(t *testing.T) {
+ dm := GetFakeDNSMessageWithPayload()
+ dm.DNSTap.Extra = "extra:value"
+
+ // encode to dnstap
+ tapMsg, err := dm.ToDNSTap(false)
+ if err != nil {
+ t.Fatalf("could not encode to dnstap: %v\n", err)
+ }
+
+ // decode dnstap message
+ dt := &dnstap.Dnstap{}
+ err = proto.Unmarshal(tapMsg, dt)
+ if err != nil {
+ t.Fatalf("error to decode dnstap: %v", err)
+ }
+
+ if string(dt.GetIdentity()) != dm.DNSTap.Identity {
+ t.Errorf("identify field should be equal got=%s", string(dt.GetIdentity()))
+ }
+
+ if string(dt.GetExtra()) != dm.DNSTap.Extra {
+ t.Errorf("extra field should be equal got=%s", string(dt.GetExtra()))
+ }
+}
+
+func BenchmarkDnsMessage_ToDNSTap(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.ToDNSTap(false)
+ if err != nil {
+ b.Fatalf("could not encode to dnstap: %v\n", err)
+ }
+ }
+}
+
+// Tests for Extended DNSTap format
+func TestDnsMessage_ToExtendedDNSTap_GetOriginalDnstapExtra(t *testing.T) {
+ dm := GetFakeDNSMessageWithPayload()
+ dm.DNSTap.Extra = "tag0:value0"
+
+ // encode to DNSTap and decode extended
+ edt := encodeToDNSTap(dm, t)
+
+ // check
+ if string(edt.GetOriginalDnstapExtra()) != dm.DNSTap.Extra {
+ t.Errorf("extra field should be equal to the original value")
+ }
+}
+
+func TestDnsMessage_ToExtendedDNSTap_TransformAtags(t *testing.T) {
+ dm := GetFakeDNSMessageWithPayload()
+ dm.ATags = &TransformATags{
+ Tags: []string{"tag1:value1"},
+ }
+
+ // encode to DNSTap and decode extended
+ edt := encodeToDNSTap(dm, t)
+
+ // check
+ if edt.GetAtags().Tags[0] != "tag1:value1" {
+ t.Errorf("invalid value on atags")
+ }
+}
+
+func TestDnsMessage_ToExtendedDNSTap_TransformNormalize(t *testing.T) {
+ dm := GetFakeDNSMessageWithPayload()
+ dm.PublicSuffix = &TransformPublicSuffix{
+ QnamePublicSuffix: "com",
+ QnameEffectiveTLDPlusOne: "dnscollector.com",
+ }
+
+ // encode to DNSTap and decode extended
+ edt := encodeToDNSTap(dm, t)
+
+ // checks
+ if edt.GetNormalize().GetTld() != "com" {
+ t.Errorf("invalid value on tld")
+ }
+
+ if edt.GetNormalize().GetEtldPlusOne() != "dnscollector.com" {
+ t.Errorf("invalid value on etld+1")
+ }
+}
+
+func TestDnsMessage_ToExtendedDNSTap_TransformFiltering(t *testing.T) {
+ dm := GetFakeDNSMessageWithPayload()
+ dm.Filtering = &TransformFiltering{
+ SampleRate: 20,
+ }
+
+ // encode to DNSTap and decode extended
+ edt := encodeToDNSTap(dm, t)
+
+ // checks
+ if edt.GetFiltering().GetSampleRate() != 20 {
+ t.Errorf("invalid value sample rate")
+ }
+}
+
+func TestDnsMessage_ToExtendedDNSTap_TransformGeo(t *testing.T) {
+ dm := GetFakeDNSMessageWithPayload()
+ dm.Geo = &TransformDNSGeo{
+ City: "France",
+ Continent: "Europe",
+ CountryIsoCode: "44444",
+ AutonomousSystemNumber: "3333",
+ AutonomousSystemOrg: "Test",
+ }
+
+ // encode to DNSTap and decode extended
+ edt := encodeToDNSTap(dm, t)
+
+ // checks
+ if edt.GetGeo().GetCity() != "France" {
+ t.Errorf("invalid value for city")
+ }
+ if edt.GetGeo().GetContinent() != "Europe" {
+ t.Errorf("invalid value for continent")
+ }
+}
+
+func BenchmarkDnsMessage_ToExtendedDNSTap(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.ToDNSTap(true)
+ if err != nil {
+ b.Fatalf("could not encode to extended dnstap: %v\n", err)
+ }
+ }
+}
diff --git a/dnsutils/dnsmessage_json_test.go b/dnsutils/dnsmessage_json_test.go
new file mode 100644
index 00000000..d3cfbfe5
--- /dev/null
+++ b/dnsutils/dnsmessage_json_test.go
@@ -0,0 +1,616 @@
+package dnsutils
+
+import (
+ "encoding/json"
+ "reflect"
+ "testing"
+)
+
+// Tests for JSON format
+func TestDnsMessage_Json_Reference(t *testing.T) {
+ dm := DNSMessage{}
+ dm.Init()
+
+ refJSON := `
+ {
+ "network": {
+ "family": "-",
+ "protocol": "-",
+ "query-ip": "-",
+ "query-port": "-",
+ "response-ip": "-",
+ "response-port": "-",
+ "ip-defragmented": false,
+ "tcp-reassembled": false
+ },
+ "dns": {
+ "id": 0,
+ "length": 0,
+ "opcode": 0,
+ "rcode": "-",
+ "qname": "-",
+ "qtype": "-",
+ "qclass": "-",
+ "questions-count": 0,
+ "flags": {
+ "qr": false,
+ "tc": false,
+ "aa": false,
+ "ra": false,
+ "ad": false,
+ "rd": false,
+ "cd": false
+ },
+ "resource-records": {
+ "an": [],
+ "ns": [],
+ "ar": []
+ },
+ "malformed-packet": false
+ },
+ "edns": {
+ "udp-size": 0,
+ "rcode": 0,
+ "version": 0,
+ "dnssec-ok": 0,
+ "options": []
+ },
+ "dnstap": {
+ "operation": "-",
+ "identity": "-",
+ "version": "-",
+ "timestamp-rfc3339ns": "-",
+ "latency": 0,
+ "extra": "-",
+ "policy-type": "-",
+ "policy-action": "-",
+ "policy-match": "-",
+ "policy-value": "-",
+ "policy-rule": "-",
+ "peer-name": "-",
+ "query-zone": "-"
+ }
+ }
+ `
+
+ var dmMap map[string]interface{}
+ err := json.Unmarshal([]byte(dm.ToJSON()), &dmMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal dm json: %s\n", err)
+ }
+
+ var refMap map[string]interface{}
+ err = json.Unmarshal([]byte(refJSON), &refMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal ref json: %s\n", err)
+ }
+
+ if !reflect.DeepEqual(dmMap, refMap) {
+ t.Errorf("json format different from reference %v", dmMap)
+ }
+}
+
+func TestDnsMessage_Json_Collectors_Reference(t *testing.T) {
+ testcases := []struct {
+ collector string
+ dmRef DNSMessage
+ jsonRef string
+ }{
+ {
+ collector: "powerdns",
+ dmRef: DNSMessage{PowerDNS: &PowerDNS{
+ OriginalRequestSubnet: "subnet",
+ AppliedPolicy: "basicrpz",
+ AppliedPolicyHit: "hit",
+ AppliedPolicyKind: "kind",
+ AppliedPolicyTrigger: "trigger",
+ AppliedPolicyType: "type",
+ Tags: []string{"tag1"},
+ Metadata: map[string]string{"stream_id": "collector"},
+ HTTPVersion: "http3",
+ }},
+
+ jsonRef: `{
+ "powerdns": {
+ "original-request-subnet": "subnet",
+ "applied-policy": "basicrpz",
+ "applied-policy-hit": "hit",
+ "applied-policy-kind": "kind",
+ "applied-policy-trigger": "trigger",
+ "applied-policy-type": "type",
+ "tags": ["tag1"],
+ "metadata": {
+ "stream_id": "collector"
+ },
+ "http-version": "http3"
+ }
+ }`,
+ },
+ }
+ for _, tc := range testcases {
+ t.Run(tc.collector, func(t *testing.T) {
+
+ tc.dmRef.Init()
+
+ var dmMap map[string]interface{}
+ err := json.Unmarshal([]byte(tc.dmRef.ToJSON()), &dmMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal dm json: %s\n", err)
+ }
+
+ var refMap map[string]interface{}
+ err = json.Unmarshal([]byte(tc.jsonRef), &refMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal ref json: %s\n", err)
+ }
+
+ if !reflect.DeepEqual(dmMap[tc.collector], refMap[tc.collector]) {
+ t.Errorf("json format different from reference, Get=%s Want=%s", dmMap[tc.collector], refMap[tc.collector])
+ }
+ })
+ }
+}
+
+func TestDnsMessage_Json_Transforms_Reference(t *testing.T) {
+
+ testcases := []struct {
+ transform string
+ dmRef DNSMessage
+ jsonRef string
+ }{
+ {
+ transform: "filtering",
+ dmRef: DNSMessage{Filtering: &TransformFiltering{SampleRate: 22}},
+ jsonRef: `{
+ "filtering": {
+ "sample-rate": 22
+ }
+ }`,
+ },
+ {
+ transform: "reducer",
+ dmRef: DNSMessage{Reducer: &TransformReducer{Occurrences: 10, CumulativeLength: 47}},
+ jsonRef: `{
+ "reducer": {
+ "occurrences": 10,
+ "cumulative-length": 47
+ }
+ }`,
+ },
+ {
+ transform: "normalize",
+ dmRef: DNSMessage{
+ PublicSuffix: &TransformPublicSuffix{
+ QnamePublicSuffix: "com",
+ QnameEffectiveTLDPlusOne: "hello.com",
+ ManagedByICANN: true,
+ },
+ },
+ jsonRef: `{
+ "publicsuffix": {
+ "tld": "com",
+ "etld+1": "hello.com",
+ "managed-icann": true
+ }
+ }`,
+ },
+ {
+ transform: "geoip",
+ dmRef: DNSMessage{
+ Geo: &TransformDNSGeo{
+ City: "Paris",
+ Continent: "Europe",
+ CountryIsoCode: "FR",
+ AutonomousSystemNumber: "1234",
+ AutonomousSystemOrg: "Internet",
+ },
+ },
+ jsonRef: `{
+ "geoip": {
+ "city": "Paris",
+ "continent": "Europe",
+ "country-isocode": "FR",
+ "as-number": "1234",
+ "as-owner": "Internet"
+ }
+ }`,
+ },
+ {
+ transform: "atags",
+ dmRef: DNSMessage{ATags: &TransformATags{Tags: []string{"test0", "test1"}}},
+ jsonRef: `{
+ "atags": {
+ "tags": [ "test0", "test1" ]
+ }
+ }`,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.transform, func(t *testing.T) {
+
+ tc.dmRef.Init()
+
+ var dmMap map[string]interface{}
+ err := json.Unmarshal([]byte(tc.dmRef.ToJSON()), &dmMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal dm json: %s\n", err)
+ }
+
+ var refMap map[string]interface{}
+ err = json.Unmarshal([]byte(tc.jsonRef), &refMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal ref json: %s\n", err)
+ }
+
+ if !reflect.DeepEqual(dmMap[tc.transform], refMap[tc.transform]) {
+ t.Errorf("json format different from reference, Get=%s Want=%s", dmMap[tc.transform], refMap[tc.transform])
+ }
+ })
+ }
+}
+
+func BenchmarkDnsMessage_ToJSON(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ dm.ToJSON()
+ }
+}
+
+// Tests for Flat JSON format
+func TestDnsMessage_JsonFlatten_Reference(t *testing.T) {
+ dm := DNSMessage{}
+ 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, Class: "IN"})
+ dm.EDNS.Options = append(dm.EDNS.Options, DNSOption{Code: 10, Data: "aaaabbbbcccc", Name: "COOKIE"})
+
+ refJSON := `
+ {
+ "dns.flags.aa": false,
+ "dns.flags.ad": false,
+ "dns.flags.qr": false,
+ "dns.flags.ra": false,
+ "dns.flags.tc": false,
+ "dns.flags.rd": false,
+ "dns.flags.cd": false,
+ "dns.length": 0,
+ "dns.malformed-packet": false,
+ "dns.id": 0,
+ "dns.opcode": 0,
+ "dns.qname": "-",
+ "dns.qtype": "-",
+ "dns.rcode": "-",
+ "dns.qclass": "-",
+ "dns.questions-count": 0,
+ "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": "-",
+ "dnstap.latency": 0,
+ "dnstap.operation": "-",
+ "dnstap.timestamp-rfc3339ns": "-",
+ "dnstap.version": "-",
+ "dnstap.extra": "-",
+ "dnstap.policy-rule": "-",
+ "dnstap.policy-type": "-",
+ "dnstap.policy-action": "-",
+ "dnstap.policy-match": "-",
+ "dnstap.policy-value": "-",
+ "dnstap.peer-name": "-",
+ "dnstap.query-zone": "-",
+ "edns.dnssec-ok": 0,
+ "edns.options.0.code": 10,
+ "edns.options.0.data": "aaaabbbbcccc",
+ "edns.options.0.name": "COOKIE",
+ "edns.rcode": 0,
+ "edns.udp-size": 0,
+ "edns.version": 0,
+ "network.family": "-",
+ "network.ip-defragmented": false,
+ "network.protocol": "-",
+ "network.query-ip": "-",
+ "network.query-port": "-",
+ "network.response-ip": "-",
+ "network.response-port": "-",
+ "network.tcp-reassembled": false
+ }
+ `
+
+ var dmFlat map[string]interface{}
+ dmJSON, err := dm.ToFlatJSON()
+ if err != nil {
+ t.Fatalf("could not convert dm to flat json: %s\n", err)
+ }
+ err = json.Unmarshal([]byte(dmJSON), &dmFlat)
+ if err != nil {
+ t.Fatalf("could not unmarshal dm json: %s\n", err)
+ }
+
+ var refMap map[string]interface{}
+ err = json.Unmarshal([]byte(refJSON), &refMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal ref json: %s\n", err)
+ }
+
+ for k, vRef := range refMap {
+ vFlat, ok := dmFlat[k]
+ if !ok {
+ t.Fatalf("Missing key %s in flatten message according to reference", k)
+ }
+ if vRef != vFlat {
+ t.Errorf("Invalid value for key=%s get=%v expected=%v", k, vFlat, vRef)
+ }
+ }
+
+ for k := range dmFlat {
+ _, ok := refMap[k]
+ if !ok {
+ t.Errorf("This key %s should not be in the flat message", k)
+ }
+ }
+}
+
+func TestDnsMessage_JsonFlatten_Transforms_Reference(t *testing.T) {
+
+ testcases := []struct {
+ transform string
+ dm DNSMessage
+ jsonRef string
+ }{
+ {
+ transform: "filtering",
+ dm: DNSMessage{Filtering: &TransformFiltering{SampleRate: 22}},
+ jsonRef: `{
+ "filtering.sample-rate": 22
+ }`,
+ },
+ {
+ transform: "reducer",
+ dm: DNSMessage{Reducer: &TransformReducer{Occurrences: 10, CumulativeLength: 47}},
+ jsonRef: `{
+ "reducer.occurrences": 10,
+ "reducer.cumulative-length": 47
+ }`,
+ },
+ {
+ transform: "publixsuffix",
+ dm: DNSMessage{
+ PublicSuffix: &TransformPublicSuffix{
+ QnamePublicSuffix: "com",
+ QnameEffectiveTLDPlusOne: "hello.com",
+ },
+ },
+ jsonRef: `{
+ "publicsuffix.tld": "com",
+ "publicsuffix.etld+1": "hello.com"
+ }`,
+ },
+ {
+ transform: "geoip",
+ dm: DNSMessage{
+ Geo: &TransformDNSGeo{
+ City: "Paris",
+ Continent: "Europe",
+ CountryIsoCode: "FR",
+ AutonomousSystemNumber: "1234",
+ AutonomousSystemOrg: "Internet",
+ },
+ },
+ jsonRef: `{
+ "geoip.city": "Paris",
+ "geoip.continent": "Europe",
+ "geoip.country-isocode": "FR",
+ "geoip.as-number": "1234",
+ "geoip.as-owner": "Internet"
+ }`,
+ },
+ {
+ transform: "suspicious",
+ dm: DNSMessage{Suspicious: &TransformSuspicious{Score: 1.0,
+ MalformedPacket: false,
+ LargePacket: true,
+ LongDomain: true,
+ SlowDomain: false,
+ UnallowedChars: true,
+ UncommonQtypes: false,
+ ExcessiveNumberLabels: true,
+ Domain: "gogle.co",
+ }},
+ jsonRef: `{
+ "suspicious.score": 1.0,
+ "suspicious.malformed-pkt": false,
+ "suspicious.large-pkt": true,
+ "suspicious.long-domain": true,
+ "suspicious.slow-domain": false,
+ "suspicious.unallowed-chars": true,
+ "suspicious.uncommon-qtypes": false,
+ "suspicious.excessive-number-labels": true,
+ "suspicious.domain": "gogle.co"
+ }`,
+ },
+ {
+ transform: "extracted",
+ dm: DNSMessage{Extracted: &TransformExtracted{Base64Payload: []byte{}}},
+ jsonRef: `{
+ "extracted.dns_payload": ""
+ }`,
+ },
+ {
+ transform: "machinelearning",
+ dm: DNSMessage{MachineLearning: &TransformML{
+ Entropy: 10.0,
+ Length: 2,
+ Labels: 2,
+ Digits: 1,
+ Lowers: 35,
+ Uppers: 23,
+ Specials: 2,
+ Others: 1,
+ RatioDigits: 1.0,
+ RatioLetters: 1.0,
+ RatioSpecials: 1.0,
+ RatioOthers: 1.0,
+ ConsecutiveChars: 10,
+ ConsecutiveVowels: 10,
+ ConsecutiveDigits: 10,
+ ConsecutiveConsonants: 10,
+ Size: 11,
+ Occurrences: 10,
+ UncommonQtypes: 1,
+ }},
+ jsonRef: `{
+ "ml.entropy": 10.0,
+ "ml.length": 2,
+ "ml.labels": 2,
+ "ml.digits": 1,
+ "ml.lowers": 35,
+ "ml.uppers": 23,
+ "ml.specials": 2,
+ "ml.others": 1,
+ "ml.ratio-digits": 1.0,
+ "ml.ratio-letters": 1.0,
+ "ml.ratio-specials": 1.0,
+ "ml.ratio-others": 1.0,
+ "ml.consecutive-chars": 10,
+ "ml.consecutive-vowels": 10,
+ "ml.consecutive-digits": 10,
+ "ml.consecutive-consonants": 10,
+ "ml.size": 11,
+ "ml.occurrences": 10,
+ "ml.uncommon-qtypes": 1
+ }`,
+ },
+ {
+ transform: "atags",
+ dm: DNSMessage{ATags: &TransformATags{Tags: []string{"test0", "test1"}}},
+ jsonRef: `{
+ "atags.tags.0": "test0",
+ "atags.tags.1": "test1"
+ }`,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.transform, func(t *testing.T) {
+
+ tc.dm.Init()
+
+ var dmFlat map[string]interface{}
+ dmJSON, err := tc.dm.ToFlatJSON()
+ if err != nil {
+ t.Fatalf("could not convert dm to flat json: %s\n", err)
+ }
+ err = json.Unmarshal([]byte(dmJSON), &dmFlat)
+ if err != nil {
+ t.Fatalf("could not unmarshal dm json: %s\n", err)
+ }
+
+ var refMap map[string]interface{}
+ err = json.Unmarshal([]byte(tc.jsonRef), &refMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal ref json: %s\n", err)
+ }
+
+ for k, vRef := range refMap {
+ vFlat, ok := dmFlat[k]
+ if !ok {
+ t.Fatalf("Missing key %s in flatten message according to reference", k)
+ }
+ if vRef != vFlat {
+ t.Errorf("Invalid value for key=%s get=%v expected=%v", k, vFlat, vRef)
+ }
+ }
+ })
+ }
+}
+
+func TestDnsMessage_JsonFlatten_Collectors_Reference(t *testing.T) {
+ testcases := []struct {
+ collector string
+ dm DNSMessage
+ jsonRef string
+ }{
+ {
+ collector: "powerdns",
+ dm: DNSMessage{PowerDNS: &PowerDNS{
+ OriginalRequestSubnet: "subnet",
+ AppliedPolicy: "basicrpz",
+ AppliedPolicyHit: "hit",
+ AppliedPolicyKind: "kind",
+ AppliedPolicyTrigger: "trigger",
+ AppliedPolicyType: "type",
+ Tags: []string{"tag1"},
+ Metadata: map[string]string{"stream_id": "collector"},
+ HTTPVersion: "http3",
+ }},
+
+ jsonRef: `{
+ "powerdns.original-request-subnet": "subnet",
+ "powerdns.applied-policy": "basicrpz",
+ "powerdns.applied-policy-hit": "hit",
+ "powerdns.applied-policy-kind": "kind",
+ "powerdns.applied-policy-trigger": "trigger",
+ "powerdns.applied-policy-type": "type",
+ "powerdns.tags.0": "tag1",
+ "powerdns.metadata.stream_id": "collector",
+ "powerdns.http-version": "http3"
+ }`,
+ },
+ }
+ for _, tc := range testcases {
+ t.Run(tc.collector, func(t *testing.T) {
+
+ tc.dm.Init()
+
+ var dmFlat map[string]interface{}
+ dmJSON, err := tc.dm.ToFlatJSON()
+ if err != nil {
+ t.Fatalf("could not convert dm to flat json: %s\n", err)
+ }
+ err = json.Unmarshal([]byte(dmJSON), &dmFlat)
+ if err != nil {
+ t.Fatalf("could not unmarshal dm json: %s\n", err)
+ }
+
+ var refMap map[string]interface{}
+ err = json.Unmarshal([]byte(tc.jsonRef), &refMap)
+ if err != nil {
+ t.Fatalf("could not unmarshal ref json: %s\n", err)
+ }
+
+ for k, vRef := range refMap {
+ vFlat, ok := dmFlat[k]
+ if !ok {
+ t.Fatalf("Missing key %s in flatten message according to reference", k)
+ }
+ if vRef != vFlat {
+ t.Errorf("Invalid value for key=%s get=%v expected=%v", k, vFlat, vRef)
+ }
+ }
+ })
+ }
+}
+
+func BenchmarkDnsMessage_ToFlatJSON(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.ToFlatJSON()
+ if err != nil {
+ b.Fatalf("could not encode to flat json: %v\n", err)
+ }
+ }
+}
diff --git a/dnsutils/dnsmessage_matchers.go b/dnsutils/dnsmessage_matchers.go
new file mode 100644
index 00000000..a94e7803
--- /dev/null
+++ b/dnsutils/dnsmessage_matchers.go
@@ -0,0 +1,441 @@
+package dnsutils
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// matchUserMap matches a map based on user-provided conditions.
+// dns.qname:
+// match-source: "file://./tests/testsdata/filtering_keep_domains_regex.txt"
+// source-kind: "regexp_list"
+func matchUserMap(realValue, expectedValue reflect.Value) (bool, error) {
+ for _, opKey := range expectedValue.MapKeys() {
+ opValue := expectedValue.MapIndex(opKey)
+ opName := opKey.Interface().(string)
+
+ switch opName {
+ // Integer great than ?
+ case MatchingOpGreaterThan:
+
+ isFloat, isInt := false, false
+ if _, ok := opValue.Interface().(float64); ok {
+ isFloat = true
+ }
+ if _, ok := opValue.Interface().(int); ok {
+ isInt = true
+ }
+
+ if !isFloat && !isInt {
+ return false, fmt.Errorf("integer or float is expected for greater-than operator, not %s", reflect.TypeOf(opValue.Interface()))
+ }
+
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a int
+ if _, ok := elemValue.Interface().(int); !ok {
+ continue
+ }
+
+ // Check for match
+ if elemValue.Interface().(int) > opValue.Interface().(int) {
+ return true, nil
+ }
+ }
+ return false, nil
+ }
+
+ if isFloat && realValue.Kind() == reflect.Float64 {
+ if realValue.Interface().(float64) > opValue.Interface().(float64) {
+ return true, nil
+ }
+ }
+
+ if isInt && realValue.Kind() == reflect.Int {
+ if realValue.Interface().(int) > opValue.Interface().(int) {
+ return true, nil
+ }
+ }
+
+ return false, nil
+
+ // Integer lower than ?
+ case MatchingOpLowerThan:
+ isFloat, isInt := false, false
+ if _, ok := opValue.Interface().(float64); ok {
+ isFloat = true
+ }
+ if _, ok := opValue.Interface().(int); ok {
+ isInt = true
+ }
+
+ if !isFloat && !isInt {
+ return false, fmt.Errorf("integer or float is expected for lower-than operator, not %s", reflect.TypeOf(opValue.Interface()))
+ }
+
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a int
+ if _, ok := elemValue.Interface().(int); !ok {
+ continue
+ }
+
+ // Check for match
+ if elemValue.Interface().(int) < opValue.Interface().(int) {
+ return true, nil
+ }
+ }
+ return false, nil
+ }
+
+ if isFloat && realValue.Kind() == reflect.Float64 {
+ if realValue.Interface().(float64) < opValue.Interface().(float64) {
+ return true, nil
+ }
+ }
+
+ if isInt && realValue.Kind() == reflect.Int {
+ if realValue.Interface().(int) < opValue.Interface().(int) {
+ return true, nil
+ }
+ }
+
+ return false, nil
+
+ // Ignore these operators
+ case MatchingOpSource, MatchingOpSourceKind:
+ continue
+
+ // List of pattern
+ case MatchingKindRegexp:
+ patternList := opValue.Interface().([]*regexp.Regexp)
+
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a string
+ if _, ok := elemValue.Interface().(string); !ok {
+ continue
+ }
+
+ // Check for a match with the regex pattern
+ for _, pattern := range patternList {
+ if pattern.MatchString(elemValue.Interface().(string)) {
+ return true, nil
+ }
+ }
+ }
+ // No match found in the slice
+ return false, nil
+ }
+
+ if realValue.Kind() != reflect.String {
+ return false, nil
+ }
+ for _, pattern := range patternList {
+ if pattern.MatchString(realValue.Interface().(string)) {
+ return true, nil
+ }
+ }
+ // No match found in pattern list
+ return false, nil
+
+ // List of string
+ case MatchingKindString:
+ stringList := opValue.Interface().([]string)
+
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a string
+ if _, ok := elemValue.Interface().(string); !ok {
+ continue
+ }
+
+ // Check for a match with the text
+ for _, textItem := range stringList {
+ if textItem == realValue.Interface().(string) {
+ return true, nil
+ }
+ }
+ }
+ // No match found in the slice
+ return false, nil
+ }
+
+ if realValue.Kind() != reflect.String {
+ return false, nil
+ }
+ for _, textItem := range stringList {
+ if textItem == realValue.Interface().(string) {
+ return true, nil
+ }
+ }
+
+ // No match found in string list
+ return false, nil
+
+ default:
+ return false, fmt.Errorf("invalid operator '%s', ignore it", opKey.Interface().(string))
+ }
+ }
+ return true, nil
+}
+
+// matchUserSlice matches a slice based on user-provided conditions.
+// dns.qname:
+// - ".*\\.github\\.com$"
+// - "^www\\.google\\.com$"
+func matchUserSlice(realValue, expectedValue reflect.Value) (bool, error) {
+ match := false
+ for i := 0; i < expectedValue.Len() && !match; i++ {
+ reflectedSub := reflect.ValueOf(expectedValue.Index(i).Interface())
+
+ switch reflectedSub.Kind() {
+ case reflect.Int:
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+ if _, ok := elemValue.Interface().(int); !ok {
+ continue
+ }
+ if reflectedSub.Interface().(int) == elemValue.Interface().(int) {
+ return true, nil
+ }
+ }
+ }
+
+ if realValue.Kind() != reflect.Int || realValue.Interface().(int) != reflectedSub.Interface().(int) {
+ continue
+ }
+ match = true
+ case reflect.String:
+ pattern := regexp.MustCompile(reflectedSub.Interface().(string))
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len() && !match; i++ {
+ elemValue := realValue.Index(i)
+ if _, ok := elemValue.Interface().(string); !ok {
+ continue
+ }
+ // Check for a match with the regex pattern
+ if pattern.MatchString(elemValue.Interface().(string)) {
+ match = true
+ }
+ }
+ }
+
+ if realValue.Kind() != reflect.String {
+ continue
+ }
+
+ if pattern.MatchString(realValue.Interface().(string)) {
+ match = true
+ }
+ }
+ }
+ return match, nil
+}
+
+// matchUserBoolean matches a boolean based on user-provided conditions.
+// dns.flags.qr: true
+func matchUserBoolean(realValue, expectedValue reflect.Value) (bool, error) {
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a int
+ if _, ok := elemValue.Interface().(bool); !ok {
+ continue
+ }
+
+ // Check for match
+ if expectedValue.Interface().(bool) == elemValue.Interface().(bool) {
+ return true, nil
+ }
+ }
+ }
+
+ if realValue.Kind() != reflect.Bool {
+ return false, nil
+ }
+
+ if expectedValue.Interface().(bool) != realValue.Interface().(bool) {
+ return false, nil
+ }
+ return true, nil
+}
+
+// matchUserInteger matches an integer based on user-provided conditions.
+// dns.opcode: 0
+func matchUserInteger(realValue, expectedValue reflect.Value) (bool, error) {
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a int
+ if _, ok := elemValue.Interface().(int); !ok {
+ continue
+ }
+
+ // Check for match
+ if expectedValue.Interface().(int) == elemValue.Interface().(int) {
+ return true, nil
+ }
+ }
+ }
+
+ if realValue.Kind() != reflect.Int {
+ return false, nil
+ }
+ if expectedValue.Interface().(int) != realValue.Interface().(int) {
+ return false, nil
+ }
+
+ return true, nil
+}
+
+// matchUserPattern matches a pattern based on user-provided conditions.
+// dns.qname: "^.*\\.github\\.com$"
+func matchUserPattern(realValue, expectedValue reflect.Value) (bool, error) {
+ pattern := regexp.MustCompile(expectedValue.Interface().(string))
+
+ // If realValue is a slice
+ if realValue.Kind() == reflect.Slice {
+ for i := 0; i < realValue.Len(); i++ {
+ elemValue := realValue.Index(i)
+
+ // Check if the element is a string
+ if _, ok := elemValue.Interface().(string); !ok {
+ continue
+ }
+
+ // Check for a match with the regex pattern
+ if pattern.MatchString(elemValue.Interface().(string)) {
+ return true, nil
+ }
+ }
+ // No match found in the slice
+ return false, nil
+ }
+
+ // If realValue is not a string
+ if realValue.Kind() != reflect.String {
+ return false, nil
+ }
+
+ // Check for a match with the regex pattern
+ if !pattern.MatchString(realValue.String()) {
+ return false, nil
+ }
+
+ // Match found for a single value
+ return true, nil
+}
+
+// getFieldByJSONTag retrieves a field value from a struct based on JSON tags.
+func getFieldByJSONTag(value reflect.Value, nestedKeys string) (reflect.Value, bool) {
+ listKeys := strings.SplitN(nestedKeys, ".", 2)
+ jsonKey := listKeys[0]
+ var remainingKeys string
+ if len(listKeys) > 1 {
+ remainingKeys = listKeys[1]
+ }
+
+ for i := 0; i < value.NumField(); i++ {
+ field := value.Type().Field(i)
+
+ // Get JSON tag
+ tag := field.Tag.Get("json")
+ tagClean := strings.TrimSuffix(tag, ",omitempty")
+
+ // Check if the JSON tag matches
+ if tagClean == jsonKey {
+ fieldValue := value.Field(i)
+
+ // Handle pointers safely
+ if fieldValue.Kind() == reflect.Ptr {
+ if fieldValue.IsNil() {
+ return reflect.Value{}, false
+ }
+ fieldValue = fieldValue.Elem()
+ }
+
+ if remainingKeys == "" {
+ // Base case: return the field value if no more keys are left
+ return fieldValue, true
+ }
+
+ // Recurse into structs or handle slices
+ switch fieldValue.Kind() {
+ case reflect.Struct:
+ return getFieldByJSONTag(fieldValue, remainingKeys)
+ case reflect.Slice:
+ if sliceElem, leftKey, found := getSliceElement(fieldValue, remainingKeys); found {
+ // Handle the slice element based on its kind
+ switch sliceElem.Kind() {
+ case reflect.Struct:
+ return getFieldByJSONTag(sliceElem, leftKey)
+ case reflect.Slice, reflect.Array:
+ var result []interface{}
+ for i := 0; i < sliceElem.Len(); i++ {
+ if subElem := sliceElem.Index(i); subElem.Kind() == reflect.Struct {
+ if nestedValue, found := getFieldByJSONTag(subElem, leftKey); found {
+ result = append(result, nestedValue.Interface())
+ }
+ } else {
+ result = append(result, subElem.Interface())
+ }
+ }
+ if len(result) > 0 {
+ return reflect.ValueOf(result), true
+ }
+ default:
+ return sliceElem, true
+ }
+ }
+ default:
+ return fieldValue, true
+ }
+ }
+ }
+
+ return reflect.Value{}, false
+}
+
+// getSliceElement retrieves an element from a slice based on the provided keys.
+func getSliceElement(sliceValue reflect.Value, nestedKeys string) (reflect.Value, string, bool) {
+ listKeys := strings.SplitN(nestedKeys, ".", 2)
+ leftKeys := ""
+ if len(listKeys) > 1 {
+ leftKeys = listKeys[1]
+ }
+ sliceIndex := listKeys[0]
+
+ if sliceIndex == "*" {
+ return sliceValue, leftKeys, true
+ }
+
+ // Convert the slice index from string to int
+ index, err := strconv.Atoi(sliceIndex)
+ if err != nil || index < 0 || index >= sliceValue.Len() {
+ // Handle the error (e.g., invalid index format or out of range)
+ return reflect.Value{}, leftKeys, false
+ }
+
+ return sliceValue.Index(index), leftKeys, true
+}
diff --git a/dnsutils/dnsmessage_matching_test.go b/dnsutils/dnsmessage_matching_test.go
new file mode 100644
index 00000000..ef0fcead
--- /dev/null
+++ b/dnsutils/dnsmessage_matching_test.go
@@ -0,0 +1,339 @@
+package dnsutils
+
+import "testing"
+
+// Matching
+func TestDNSMessage_Matching(t *testing.T) {
+ tests := []struct {
+ name string
+ dm *DNSMessage
+ matching map[string]interface{}
+ wantError bool
+ wantMatch bool
+ }{
+ {
+ name: "Test integer matching",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1}},
+ matching: map[string]interface{}{"dns.opcode": 1},
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test no match with incorrect integer",
+ dm: &DNSMessage{DNS: DNS{Opcode: 2}},
+ matching: map[string]interface{}{"dns.opcode": 1},
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test string matching",
+ dm: &DNSMessage{DNS: DNS{Qname: "www.example.com"}},
+ matching: map[string]interface{}{"dns.qname": "www.example.com"},
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test no match with incorrect string",
+ dm: &DNSMessage{DNS: DNS{Qname: "www.notexample.com"}},
+ matching: map[string]interface{}{"dns.qname": "www.example.com"},
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test boolean matching",
+ dm: &DNSMessage{DNS: DNS{Flags: DNSFlags{QR: true}}},
+ matching: map[string]interface{}{"dns.flags.qr": true},
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test no match with incorrect boolean",
+ dm: &DNSMessage{DNS: DNS{Flags: DNSFlags{QR: false}}},
+ matching: map[string]interface{}{"dns.flags.qr": true},
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test regex with match",
+ dm: &DNSMessage{DNS: DNS{Qname: "www.github.com"}},
+ matching: map[string]interface{}{
+ "dns.qname": "^.*\\.github\\.com$",
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test regex with no match",
+ dm: &DNSMessage{DNS: DNS{Qname: "www.google.com"}},
+ matching: map[string]interface{}{
+ "dns.qname": "^.*\\.github\\.com$",
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test matching with multiple conditions",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1, Qname: "www.example.com", Flags: DNSFlags{QR: true}}},
+ matching: map[string]interface{}{
+ "dns.flags.qr": true,
+ "dns.opcode": 1,
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test integer greater than operator matching",
+ dm: &DNSMessage{DNS: DNS{Opcode: 5}},
+ matching: map[string]interface{}{
+ "dns.opcode": map[string]interface{}{
+ "greater-than": 3,
+ },
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test integer with invalid greater than operator",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1}},
+ matching: map[string]interface{}{
+ "dns.opcode": map[string]interface{}{
+ "greater-than": "0",
+ },
+ },
+ wantError: true,
+ wantMatch: false,
+ },
+ {
+ name: "Test float greater than operator matching",
+ dm: &DNSMessage{DNSTap: DNSTap{Latency: 0.5}},
+ matching: map[string]interface{}{
+ "dnstap.latency": map[string]interface{}{
+ "greater-than": 0.3,
+ },
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test lower than operator matching",
+ dm: &DNSMessage{DNS: DNS{Opcode: 9}},
+ matching: map[string]interface{}{
+ "dns.opcode": map[string]interface{}{
+ "lower-than": 10,
+ },
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test lower than operator no match",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1}},
+ matching: map[string]interface{}{
+ "dns.opcode": map[string]interface{}{
+ "lower-than": 1,
+ },
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test no match invalid lower than operator",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1}},
+ matching: map[string]interface{}{
+ "dns.opcode": map[string]interface{}{
+ "lower-than": "1",
+ },
+ },
+ wantError: true,
+ wantMatch: false,
+ },
+ {
+ name: "Test match with list of string",
+ dm: &DNSMessage{DNS: DNS{Qname: "www.example.com"}},
+ matching: map[string]interface{}{
+ "dns.qname": []interface{}{"www.test.com", "www.example.com"},
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test no match with list of string",
+ dm: &DNSMessage{DNS: DNS{Qname: "www.notexample.com"}},
+ matching: map[string]interface{}{
+ "dns.qname": []interface{}{"www.test.com", "www.example.com"},
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test non-existent key",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1}},
+ matching: map[string]interface{}{
+ "dns.nonexistent": 1,
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test nested non-existent key",
+ dm: &DNSMessage{DNS: DNS{Opcode: 1}},
+ matching: map[string]interface{}{
+ "dns.flags.nonexistent": true,
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err, isMatch := tt.dm.Matching(tt.matching)
+ if (err != nil) != tt.wantError {
+ t.Errorf("DNSMessage.Matching() error = %v, wantError %v", err, tt.wantError)
+ return
+ }
+ if isMatch != tt.wantMatch {
+ t.Errorf("DNSMessage.Matching() = %v, want %v", isMatch, tt.wantMatch)
+ }
+ })
+ }
+}
+
+func TestDNSMessage_Matching_Arrays(t *testing.T) {
+ tests := []struct {
+ name string
+ dm *DNSMessage
+ matching map[string]interface{}
+ wantError bool
+ wantMatch bool
+ }{
+ {
+ name: "Test wilcard match with operator",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.*.ttl": map[string]interface{}{
+ "greater-than": 10,
+ },
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test wilcard no match and operator",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.*.ttl": map[string]interface{}{
+ "greater-than": 400,
+ },
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test wilcard no match and invalid operator",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.*.ttl": map[string]interface{}{
+ "greater-than-invalid": 400,
+ },
+ },
+ wantError: true,
+ wantMatch: false,
+ },
+ {
+ name: "Test array match with index",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.0.ttl": 300,
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test array match with invalid index",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.1.ttl": 300,
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test array match with index and multiple conditions",
+ dm: &DNSMessage{
+ DNSTap: DNSTap{Operation: "CLIENT_RESPONSE"},
+ DNS: DNS{Rcode: "NOERROR", Qtype: "A", DNSRRs: DNSRRs{Answers: []DNSAnswer{{Rdata: "0.0.0.0"}}}},
+ },
+ matching: map[string]interface{}{
+ "dnstap.operation": "CLIENT_RESPONSE",
+ "dns.qtype": "A",
+ "dns.rcode": "NOERROR",
+ "dns.resource-records.an.0.rdata": "0.0.0.0",
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test array match with index and missing key",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.0.missing-key": 300,
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test array match with bad index",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.badindex.ttl": 300,
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test array no match with index and invalid data type",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{TTL: 300}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.0.ttl": "not-a-number",
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ {
+ name: "Test IP address match with regex",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{Rdata: "1.2.3.4"}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.0.rdata": []interface{}{
+ "^1\\.2\\.3\\.(4|5)$",
+ },
+ },
+ wantError: false,
+ wantMatch: true,
+ },
+ {
+ name: "Test IP address no match with regex",
+ dm: &DNSMessage{DNS: DNS{DNSRRs: DNSRRs{Answers: []DNSAnswer{{Rdata: "1.2.3.4"}}}}},
+ matching: map[string]interface{}{
+ "dns.resource-records.an.0.rdata": []interface{}{
+ "^5\\.4\\.3\\.(4|5)$",
+ },
+ },
+ wantError: false,
+ wantMatch: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err, isMatch := tt.dm.Matching(tt.matching)
+ if (err != nil) != tt.wantError {
+ t.Errorf("DNSMessage.Matching() error = %v, wantError %v", err, tt.wantError)
+ return
+ }
+ if isMatch != tt.wantMatch {
+ t.Errorf("DNSMessage.Matching() = %v, want %v", isMatch, tt.wantMatch)
+ }
+ })
+ }
+}
diff --git a/dnsutils/dnsmessage_pcap_test.go b/dnsutils/dnsmessage_pcap_test.go
new file mode 100644
index 00000000..061cbd7f
--- /dev/null
+++ b/dnsutils/dnsmessage_pcap_test.go
@@ -0,0 +1,32 @@
+package dnsutils
+
+import (
+ "testing"
+
+ "github.com/dmachard/go-netutils"
+ "github.com/miekg/dns"
+)
+
+// Tests for PCAP serialization
+func BenchmarkDnsMessage_ToPacketLayer(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ dnsmsg := new(dns.Msg)
+ dnsmsg.SetQuestion("dnscollector.dev.", dns.TypeAAAA)
+ dnsquestion, _ := dnsmsg.Pack()
+
+ dm.NetworkInfo.Family = netutils.ProtoIPv4
+ dm.NetworkInfo.Protocol = netutils.ProtoUDP
+ dm.DNS.Payload = dnsquestion
+ dm.DNS.Length = len(dnsquestion)
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.ToPacketLayer()
+ if err != nil {
+ b.Fatalf("could not encode to pcap: %v\n", err)
+ }
+ }
+}
diff --git a/dnsutils/dnsmessage_relabelling_test.go b/dnsutils/dnsmessage_relabelling_test.go
new file mode 100644
index 00000000..3b37e2f4
--- /dev/null
+++ b/dnsutils/dnsmessage_relabelling_test.go
@@ -0,0 +1,80 @@
+package dnsutils
+
+import (
+ "reflect"
+ "regexp"
+ "testing"
+)
+
+// Flatten and relabeling
+func TestDnsMessage_ApplyRelabeling(t *testing.T) {
+ // Créer un DNSMessage avec des règles de relabeling pour le test
+ dm := &DNSMessage{
+ Relabeling: &TransformRelabeling{
+ Rules: []RelabelingRule{
+ {Regex: regexp.MustCompile("^old_"), Replacement: "new_field", Action: "rename"},
+ {Regex: regexp.MustCompile("^foo_"), Action: "remove"},
+ },
+ },
+ }
+
+ // test map
+ dnsFields := map[string]interface{}{
+ "old_field": "value1",
+ "foo_field": "value2",
+ "other_field": "value3",
+ }
+
+ // apply relabeling
+ err := dm.ApplyRelabeling(dnsFields)
+ if err != nil {
+ t.Errorf("ApplyRelabeling() return an error: %v", err)
+ }
+
+ // check
+ expectedDNSFields := map[string]interface{}{
+ "new_field": "value1",
+ "other_field": "value3",
+ }
+ if !reflect.DeepEqual(dnsFields, expectedDNSFields) {
+ t.Errorf("Want: %v, Get: %v", expectedDNSFields, dnsFields)
+ }
+}
+
+func BenchmarkDnsMessage_ToFlatten_Relabelling(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ dm.Relabeling.Rules = append(dm.Relabeling.Rules, RelabelingRule{
+ Regex: regexp.MustCompile(`dns.qname`),
+ Action: "remove",
+ })
+ dm.Relabeling.Rules = append(dm.Relabeling.Rules, RelabelingRule{
+ Regex: regexp.MustCompile(`dns.qtype`),
+ Replacement: "qtype",
+ Action: "rename",
+ })
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.Flatten()
+ if err != nil {
+ b.Fatalf("could not flat: %v\n", err)
+ }
+ }
+}
+
+func BenchmarkDnsMessage_ToFlatten(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.Flatten()
+ if err != nil {
+ b.Fatalf("could not flat: %v\n", err)
+ }
+ }
+}
diff --git a/dnsutils/dnsmessage_template_test.go b/dnsutils/dnsmessage_template_test.go
new file mode 100644
index 00000000..4b8821cf
--- /dev/null
+++ b/dnsutils/dnsmessage_template_test.go
@@ -0,0 +1,65 @@
+package dnsutils
+
+import (
+ "strings"
+ "testing"
+)
+
+// To jinja templating
+func TestDnsMessage_ToJinjaFormat(t *testing.T) {
+ dm := DNSMessage{}
+ dm.Init()
+
+ dm.DNS.Qname = "qname_for_test"
+
+ template := `
+;; Got {% if dm.DNS.Type == "QUERY" %}query{% else %}answer{% endif %} from {{ dm.NetworkInfo.QueryIP }}#{{ dm.NetworkInfo.QueryPort }}:
+;; ->>HEADER<<- opcode: {{ dm.DNS.Opcode }}, status: {{ dm.DNS.Rcode }}, id: {{ dm.DNS.ID }}
+;; flags: {{ dm.DNS.Flags.QR | yesno:"qr ," }}{{ dm.DNS.Flags.RD | yesno:"rd ," }}{{ dm.DNS.Flags.RA | yesno:"ra ," }}; QUERY: {{ dm.DNS.QuestionsCount }}, ANSWER: {{ dm.DNS.DNSRRs.Answers | length }}, AUTHORITY: {{ dm.DNS.DNSRRs.Nameservers | length }}, ADDITIONAL: {{ dm.DNS.DNSRRs.Records | length }}
+
+;; QUESTION SECTION:
+;{{ dm.DNS.Qname }} {{ dm.DNS.Qclass }} {{ dm.DNS.Qtype }}
+
+;; ANSWER SECTION: {% for rr in dm.DNS.DNSRRs.Answers %}
+{{ rr.Name }} {{ rr.TTL }} {{ rr.Class }} {{ rr.Rdatatype }} {{ rr.Rdata }}{% endfor %}
+
+;; WHEN: {{ dm.DNSTap.Timestamp }}
+;; MSG SIZE rcvd: {{ dm.DNS.Length }}`
+
+ text, err := dm.ToTextTemplate(template)
+ if err != nil {
+ t.Errorf("Want no error, got: %s", err)
+ }
+
+ if !strings.Contains(text, dm.DNS.Qname) {
+ t.Errorf("Want qname in template, got: %s", text)
+ }
+}
+
+func BenchmarkDnsMessage_ToJinjaFormat(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ template := `
+;; Got {% if dm.DNS.Type == "QUERY" %}query{% else %}answer{% endif %} from {{ dm.NetworkInfo.QueryIP }}#{{ dm.NetworkInfo.QueryPort }}:
+;; ->>HEADER<<- opcode: {{ dm.DNS.Opcode }}, status: {{ dm.DNS.Rcode }}, id: {{ dm.DNS.ID }}
+;; flags: {{ dm.DNS.Flags.QR | yesno:"qr ," }}{{ dm.DNS.Flags.RD | yesno:"rd ," }}{{ dm.DNS.Flags.RA | yesno:"ra ," }}; QUERY: {{ dm.DNS.QuestionsCount }}, ANSWER: {{ dm.DNS.DNSRRs.Answers | length }}, AUTHORITY: {{ dm.DNS.DNSRRs.Nameservers | length }}, ADDITIONAL: {{ dm.DNS.DNSRRs.Records | length }}
+
+;; QUESTION SECTION:
+;{{ dm.DNS.Qname }} {{ dm.DNS.Qclass }} {{ dm.DNS.Qtype }}
+
+;; ANSWER SECTION: {% for rr in dm.DNS.DNSRRs.Answers %}
+{{ rr.Name }} {{ rr.TTL }} {{ rr.Class }} {{ rr.Rdatatype }} {{ rr.Rdata }}{% endfor %}
+
+;; WHEN: {{ dm.DNSTap.Timestamp }}
+;; MSG SIZE rcvd: {{ dm.DNS.Length }}`
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.ToTextTemplate(template)
+ if err != nil {
+ b.Fatalf("could not encode to template: %v\n", err)
+ }
+ }
+}
diff --git a/dnsutils/dnsmessage_test.go b/dnsutils/dnsmessage_test.go
new file mode 100644
index 00000000..52646056
--- /dev/null
+++ b/dnsutils/dnsmessage_test.go
@@ -0,0 +1,15 @@
+package dnsutils
+
+import (
+ "testing"
+)
+
+// Bench to init DNS message
+func BenchmarkDnsMessage_Init(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+ }
+}
diff --git a/dnsutils/dnsmessage_text_test.go b/dnsutils/dnsmessage_text_test.go
new file mode 100644
index 00000000..7ba3a6b8
--- /dev/null
+++ b/dnsutils/dnsmessage_text_test.go
@@ -0,0 +1,708 @@
+package dnsutils
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/dmachard/go-dnscollector/pkgconfig"
+)
+
+// Tests for TEXT format
+func TestDnsMessage_TextFormat_ToString(t *testing.T) {
+
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ delimiter string
+ boundary string
+ format string
+ qname string
+ identity string
+ expected string
+ }{
+ {
+ name: "default",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: config.Global.TextFormat,
+ qname: "dnscollector.fr",
+ identity: "collector",
+ expected: "- collector CLIENT_QUERY NOERROR 1.2.3.4 1234 - - 0b dnscollector.fr A 0.000000000",
+ },
+ {
+ name: "custom_delimiter",
+ delimiter: ";",
+ boundary: config.Global.TextFormatBoundary,
+ format: config.Global.TextFormat,
+ qname: "dnscollector.fr",
+ identity: "collector",
+ expected: "-;collector;CLIENT_QUERY;NOERROR;1.2.3.4;1234;-;-;0b;dnscollector.fr;A;0.000000000",
+ },
+ {
+ name: "empty_delimiter",
+ delimiter: "",
+ boundary: config.Global.TextFormatBoundary,
+ format: config.Global.TextFormat,
+ qname: "dnscollector.fr",
+ identity: "collector",
+ expected: "-collectorCLIENT_QUERYNOERROR1.2.3.41234--0bdnscollector.frA0.000000000",
+ },
+ {
+ name: "qname_quote",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: config.Global.TextFormat,
+ qname: "dns collector.fr",
+ identity: "collector",
+ expected: "- collector CLIENT_QUERY NOERROR 1.2.3.4 1234 - - 0b \"dns collector.fr\" A 0.000000000",
+ },
+ {
+ name: "default_boundary",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: config.Global.TextFormat,
+ qname: "dns\"coll tor\".fr",
+ identity: "collector",
+ expected: "- collector CLIENT_QUERY NOERROR 1.2.3.4 1234 - - 0b \"dns\\\"coll tor\\\".fr\" A 0.000000000",
+ },
+ {
+ name: "custom_boundary",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: "!",
+ format: config.Global.TextFormat,
+ qname: "dnscoll tor.fr",
+ identity: "collector",
+ expected: "- collector CLIENT_QUERY NOERROR 1.2.3.4 1234 - - 0b !dnscoll tor.fr! A 0.000000000",
+ },
+ {
+ name: "custom_text",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: "qname {IN} qtype",
+ qname: "dnscollector.fr",
+ identity: "",
+ expected: "dnscollector.fr IN A",
+ },
+ {
+ name: "quote_dnstap_version",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: "identity version qname",
+ qname: "dnscollector.fr",
+ identity: "collector",
+ expected: "collector \"dnscollector 1.0.0\" dnscollector.fr",
+ },
+ {
+ name: "quote_dnstap_identity",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: "identity qname",
+ qname: "dnscollector.fr",
+ identity: "dns collector",
+ expected: "\"dns collector\" dnscollector.fr",
+ },
+ {
+ name: "quote_dnstap_peername",
+ delimiter: config.Global.TextFormatDelimiter,
+ boundary: config.Global.TextFormatBoundary,
+ format: "peer-name qname",
+ qname: "dnscollector.fr",
+ identity: "",
+ expected: "\"localhost (127.0.0.1)\" dnscollector.fr",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ dm := GetFakeDNSMessage()
+
+ dm.DNS.Qname = tc.qname
+ dm.DNSTap.Identity = tc.identity
+
+ line := dm.String(strings.Fields(tc.format), tc.delimiter, tc.boundary)
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_DefaultDirectives(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ format: "timestamp-rfc3339ns timestamp",
+ dm: DNSMessage{DNSTap: DNSTap{TimestampRFC3339: "2023-04-22T09:17:02.906922231Z"}},
+ expected: "2023-04-22T09:17:02.906922231Z 2023-04-22T09:17:02.906922231Z",
+ },
+ {
+ format: "timestamp-unixns timestamp-unixus timestamp-unixms",
+ dm: DNSMessage{DNSTap: DNSTap{Timestamp: 1682152174001850960}},
+ expected: "1682152174001850960 1682152174001850 1682152174001",
+ },
+ {
+ format: "latency",
+ dm: DNSMessage{DNSTap: DNSTap{Latency: 0.00001}},
+ expected: "0.000010000",
+ },
+ {
+ format: "qname qtype opcode",
+ 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"}},
+ expected: "CLIENT_QUERY",
+ },
+ {
+ format: "family protocol",
+ dm: DNSMessage{NetworkInfo: DNSNetInfo{Family: "IPv4", Protocol: "UDP"}},
+ expected: "IPv4 UDP",
+ },
+ {
+ format: "length",
+ dm: DNSMessage{DNS: DNS{Length: 42}},
+ expected: "42",
+ },
+ {
+ format: "length-unit",
+ dm: DNSMessage{DNS: DNS{Length: 42}},
+ expected: "42b",
+ },
+ {
+ format: "malformed",
+ dm: DNSMessage{DNS: DNS{MalformedPacket: true}},
+ expected: "PKTERR",
+ },
+ {
+ format: "tc aa ra ad",
+ dm: DNSMessage{DNS: DNS{Flags: DNSFlags{TC: true, AA: true, RA: true, AD: true}}},
+ expected: "TC AA RA AD",
+ },
+ {
+ format: "df tr",
+ dm: DNSMessage{NetworkInfo: DNSNetInfo{IPDefragmented: true, TCPReassembled: true}},
+ expected: "DF TR",
+ },
+ {
+ format: "queryip queryport",
+ dm: DNSMessage{NetworkInfo: DNSNetInfo{QueryIP: "1.2.3.4", QueryPort: "4200"}},
+ expected: "1.2.3.4 4200",
+ },
+ {
+ format: "responseip responseport",
+ dm: DNSMessage{NetworkInfo: DNSNetInfo{ResponseIP: "1.2.3.4", ResponsePort: "4200"}},
+ expected: "1.2.3.4 4200",
+ },
+ {
+ format: "policy-rule policy-type policy-action policy-match policy-value",
+ dm: DNSMessage{DNSTap: DNSTap{PolicyRule: "rule", PolicyType: "type",
+ PolicyAction: "action", PolicyMatch: "match",
+ PolicyValue: "value"}},
+ expected: "rule type action match value",
+ },
+ {
+ format: "peer-name",
+ dm: DNSMessage{DNSTap: DNSTap{PeerName: "testpeer"}},
+ expected: "testpeer",
+ },
+ {
+ format: "query-zone",
+ dm: DNSMessage{DNSTap: DNSTap{QueryZone: "queryzone.test"}},
+ expected: "queryzone.test",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.format, func(t *testing.T) {
+ line := tc.dm.String(strings.Fields(tc.format), config.Global.TextFormatDelimiter, config.Global.TextFormatBoundary)
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_InvalidDirectives(t *testing.T) {
+ testcases := []struct {
+ name string
+ dm DNSMessage
+ format string
+ }{
+ {
+ name: "default",
+ dm: DNSMessage{},
+ format: "invalid",
+ },
+ {
+ name: "publicsuffix",
+ dm: DNSMessage{PublicSuffix: &TransformPublicSuffix{}},
+ format: "publixsuffix-invalid",
+ },
+ {
+ name: "powerdns",
+ dm: DNSMessage{PowerDNS: &PowerDNS{}},
+ format: "powerdns-invalid",
+ },
+ {
+ name: "geoip",
+ dm: DNSMessage{Geo: &TransformDNSGeo{}},
+ format: "geoip-invalid",
+ },
+ {
+ name: "suspicious",
+ dm: DNSMessage{Suspicious: &TransformSuspicious{}},
+ format: "suspicious-invalid",
+ },
+ {
+ name: "extracted",
+ dm: DNSMessage{Extracted: &TransformExtracted{}},
+ format: "extracted-invalid",
+ },
+ {
+ name: "filtering",
+ dm: DNSMessage{Filtering: &TransformFiltering{}},
+ format: "filtering-invalid",
+ },
+ {
+ name: "reducer",
+ dm: DNSMessage{Reducer: &TransformReducer{}},
+ format: "reducer-invalid",
+ },
+ {
+ name: "ml",
+ dm: DNSMessage{MachineLearning: &TransformML{}},
+ format: "ml-invalid",
+ },
+ }
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := tc.dm.ToTextLine(strings.Fields(tc.format), " ", "")
+ if err == nil {
+ t.Errorf("Want err, got nil")
+ } else if err.Error() != ErrorUnexpectedDirective+tc.format {
+ t.Errorf("Unexpected error: %s", err.Error())
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_PublicSuffix(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "publixsuffix-tld",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "default",
+ format: "publixsuffix-tld publixsuffix-etld+1",
+ dm: DNSMessage{PublicSuffix: &TransformPublicSuffix{QnamePublicSuffix: "com", QnameEffectiveTLDPlusOne: "google.com"}},
+ expected: "com google.com",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_Geo(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "geoip-continent",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "default",
+ format: "geoip-continent geoip-country geoip-city geoip-as-number geoip-as-owner",
+ dm: DNSMessage{Geo: &TransformDNSGeo{City: "Paris", Continent: "Europe",
+ CountryIsoCode: "FR", AutonomousSystemNumber: "AS1", AutonomousSystemOrg: "Google"}},
+ expected: "Europe FR Paris AS1 Google",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_Pdns(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "powerdns-tags",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "empty_attributes",
+ format: "powerdns-tags powerdns-applied-policy powerdns-original-request-subnet powerdns-metadata",
+ dm: DNSMessage{PowerDNS: &PowerDNS{}},
+ expected: "- - - -",
+ },
+ {
+ name: "applied_policy",
+ format: "powerdns-applied-policy powerdns-applied-policy-hit powerdns-applied-policy-kind powerdns-applied-policy-trigger powerdns-applied-policy-type",
+ dm: DNSMessage{PowerDNS: &PowerDNS{
+ AppliedPolicy: "policy",
+ AppliedPolicyHit: "hit",
+ AppliedPolicyKind: "kind",
+ AppliedPolicyTrigger: "trigger",
+ AppliedPolicyType: "type",
+ }},
+ expected: "policy hit kind trigger type",
+ },
+ {
+ name: "original_request_subnet",
+ format: "powerdns-original-request-subnet",
+ dm: DNSMessage{PowerDNS: &PowerDNS{OriginalRequestSubnet: "test"}},
+ expected: "test",
+ },
+ {
+ name: "metadata_badsyntax",
+ format: "powerdns-metadata",
+ dm: DNSMessage{PowerDNS: &PowerDNS{Metadata: map[string]string{"test_key1": "test_value1"}}},
+ expected: "-",
+ },
+ {
+ name: "metadata",
+ format: "powerdns-metadata:test_key1",
+ dm: DNSMessage{PowerDNS: &PowerDNS{Metadata: map[string]string{"test_key1": "test_value1"}}},
+ expected: "test_value1",
+ },
+ {
+ name: "metadata_invalid",
+ format: "powerdns-metadata:test_key2",
+ dm: DNSMessage{PowerDNS: &PowerDNS{Metadata: map[string]string{"test_key1": "test_value1"}}},
+ expected: "-",
+ },
+ {
+ name: "tags_all",
+ format: "powerdns-tags",
+ dm: DNSMessage{PowerDNS: &PowerDNS{Tags: []string{"tag1", "tag2"}}},
+ expected: "tag1,tag2",
+ },
+ {
+ name: "tags_index",
+ format: "powerdns-tags:1",
+ dm: DNSMessage{PowerDNS: &PowerDNS{Tags: []string{"tag1", "tag2"}}},
+ expected: "tag2",
+ },
+ {
+ name: "tags_invalid_index",
+ format: "powerdns-tags:3",
+ dm: DNSMessage{PowerDNS: &PowerDNS{Tags: []string{"tag1", "tag2"}}},
+ expected: "-",
+ },
+ {
+ name: "http_version",
+ format: "powerdns-http-version",
+ dm: DNSMessage{PowerDNS: &PowerDNS{HTTPVersion: "HTTP2"}},
+ expected: "HTTP2",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_ATags(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "atags",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "empty_attributes",
+ format: "atags",
+ dm: DNSMessage{ATags: &TransformATags{}},
+ expected: "-",
+ },
+ {
+ name: "tags_all",
+ format: "atags",
+ dm: DNSMessage{ATags: &TransformATags{Tags: []string{"tag1", "tag2"}}},
+ expected: "tag1,tag2",
+ },
+ {
+ name: "tags_index",
+ format: "atags:1",
+ dm: DNSMessage{ATags: &TransformATags{Tags: []string{"tag1", "tag2"}}},
+ expected: "tag2",
+ },
+ {
+ name: "tags_invalid_index",
+ format: "atags:3",
+ dm: DNSMessage{ATags: &TransformATags{Tags: []string{"tag1", "tag2"}}},
+ expected: "-",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_Suspicious(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "suspicious-score",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "default",
+ format: "suspicious-score",
+ dm: DNSMessage{Suspicious: &TransformSuspicious{Score: 4.0}},
+ expected: "4",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_Reducer(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "reducer-occurrences",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "default",
+ format: "reducer-occurrences",
+ dm: DNSMessage{Reducer: &TransformReducer{Occurrences: 1}},
+ expected: "1",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_Extracted(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "extracted-dns-payload",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "default",
+ format: "extracted-dns-payload",
+ dm: DNSMessage{Extracted: &TransformExtracted{}, DNS: DNS{Payload: []byte{
+ 0x9e, 0x84, 0x01, 0x20, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ // query 1
+ 0x01, 0x61, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 2
+ 0x01, 0x62, 0x00,
+ // type A, class IN
+ 0x00, 0x01, 0x00, 0x01,
+ // query 3
+ 0x01, 0x63, 0x00,
+ // type AAAA, class IN
+ 0x00, 0x1c, 0x00, 0x01,
+ }}},
+ expected: "noQBIAADAAAAAAAAAWEAAAEAAQFiAAABAAEBYwAAHAAB",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func TestDnsMessage_TextFormat_Directives_Filtering(t *testing.T) {
+ config := pkgconfig.GetDefaultConfig()
+
+ testcases := []struct {
+ name string
+ format string
+ dm DNSMessage
+ expected string
+ }{
+ {
+ name: "undefined",
+ format: "filtering-sample-rate",
+ dm: DNSMessage{},
+ expected: "-",
+ },
+ {
+ name: "default",
+ format: "filtering-sample-rate",
+ dm: DNSMessage{Filtering: &TransformFiltering{SampleRate: 22}},
+ expected: "22",
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ line := tc.dm.String(
+ strings.Fields(tc.format),
+ config.Global.TextFormatDelimiter,
+ config.Global.TextFormatBoundary,
+ )
+ if line != tc.expected {
+ t.Errorf("Want: %s, got: %s", tc.expected, line)
+ }
+ })
+ }
+}
+
+func BenchmarkDnsMessage_ToTextFormat(b *testing.B) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+
+ textFormat := []string{"timestamp-rfc3339ns", "identity",
+ "operation", "rcode", "queryip", "queryport", "family",
+ "protocol", "length-unit", "qname", "qtype", "latency"}
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := dm.ToTextLine(textFormat, " ", "\"")
+ if err != nil {
+ b.Fatalf("could not encode to text format: %v\n", err)
+ }
+ }
+}
diff --git a/dnsutils/edns_parser_error_test.go b/dnsutils/edns_parser_error_test.go
new file mode 100644
index 00000000..c5a13d2d
--- /dev/null
+++ b/dnsutils/edns_parser_error_test.go
@@ -0,0 +1,159 @@
+package dnsutils
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestDecodeAnswer_EdnsError(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x81, 0x82, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x06,
+ // RDATA
+ // CODE - Extended error
+ 0x00, 0x0f,
+ // Length
+ 0x00, 0x02,
+ // Option data
+ // Error code
+ 0x00, 0x17,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ edns, _, erre := DecodeEDNS(1, offset, payload)
+ if erre != nil {
+ t.Errorf("unexpected error while decoding edns: %v", erre)
+ }
+
+ if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
+ t.Errorf("invalid data in parsed EDNS header: %v", edns)
+ }
+
+ if len(edns.Options) != 1 {
+ t.Errorf("expected one edns option to be parsed, got %d", len(edns.Options))
+ }
+ expected := DNSOption{Code: 0x000f, Name: OptCodeToString(0x000f), Data: "23 Network Error -"}
+ if edns.Options[0] != expected {
+ t.Errorf("bad edns option, expected %v, got %v", expected, edns.Options[0])
+ }
+
+}
+func TestDecodeAnswer_EdnsErrorText(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x81, 0x82, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x0c,
+ // RDATA
+ // CODE - Extended error
+ 0x00, 0x0f,
+ // Length
+ 0x00, 0x08,
+ // Option data
+ // Error code
+ 0x00, 0x17,
+ // Error text
+ 0x62, 0x30, 0x72, 0x6b, 0x65, 0x6e,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ edns, _, erre := DecodeEDNS(1, offset, payload)
+ if erre != nil {
+ t.Errorf("unexpected error while decoding edns: %v", erre)
+ }
+
+ if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
+ t.Errorf("invalid data in parsed EDNS header: %v", edns)
+ }
+
+ if len(edns.Options) != 1 {
+ t.Errorf("expected one edns option to be parsed, got %d", len(edns.Options))
+ }
+ expected := DNSOption{Code: 0x000f, Name: OptCodeToString(0x000f), Data: "23 Network Error b0rken"}
+ if edns.Options[0] != expected {
+ t.Errorf("bad edns option, expected %v, got %v", expected, edns.Options[0])
+ }
+
+}
+
+func TestDecodeAnswer_EdnsErrorShort(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x81, 0x82, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x05,
+ // RDATA
+ // CODE - Extended error
+ 0x00, 0x0f,
+ // Length
+ 0x00, 0x01,
+ // Option data
+ // Error code, missing byte
+ 0x00,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ _, _, erre := DecodeEDNS(1, offset, payload)
+ if !errors.Is(erre, ErrDecodeEdnsOptionTooShort) {
+ t.Errorf("bad error returned: %v", erre)
+ }
+}
diff --git a/dnsutils/edns_parser_subnet_test.go b/dnsutils/edns_parser_subnet_test.go
new file mode 100644
index 00000000..33215932
--- /dev/null
+++ b/dnsutils/edns_parser_subnet_test.go
@@ -0,0 +1,332 @@
+package dnsutils
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestDecodeQuery_EdnsSubnet(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x0b,
+ // RDATA
+ // CODE - Client subnet
+ 0x00, 0x08,
+ // Length
+ 0x00, 0x07,
+ // Option data
+ // family
+ 0x00, 0x01,
+ // prefix-len
+ 0x18,
+ // scope prefix-len
+ 0x00,
+ // address
+ 0xc0, 0xa8, 0x01,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ answer, _, erra := DecodeAnswer(1, offset, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", erra)
+ }
+ // parsing answers should skip the OPT type and not return anything from
+ // the additional section
+ if len(answer) > 0 {
+ t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
+ }
+ edns, _, erre := DecodeEDNS(1, offset, payload)
+ if erre != nil {
+ t.Errorf("unexpected error when decoding EDNS: %v", erre)
+ }
+ if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
+ t.Errorf("invalid data in parsed EDNS header: %v", edns)
+ }
+
+ if len(edns.Options) != 1 {
+ t.Errorf("expected 1 EDNS option to be parsed, got %v", len(edns.Options))
+ }
+
+ expectedOption := DNSOption{Code: 0x0008, Name: OptCodeToString(0x0008), Data: "192.168.1.0/24"}
+
+ if edns.Options[0] != expectedOption {
+ t.Errorf("bad option parsed, expected %v, got %v", expectedOption, edns.Options[0])
+ }
+
+}
+func TestDecodeQuery_EdnsSubnetV6(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x0b,
+ // RDATA
+ // CODE - Client subnet
+ 0x00, 0x08,
+ // Length
+ 0x00, 0x07,
+ // Option data
+ // family
+ 0x00, 0x02,
+ // prefix-len
+ 0x18,
+ // scope prefix-len
+ 0x00,
+ // address
+ 0xfe, 0x80, 0x01,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ answer, _, erra := DecodeAnswer(1, offset, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", erra)
+ }
+ // parsing answers should skip the OPT type and not return anything from
+ // the additional section
+ if len(answer) > 0 {
+ t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
+ }
+ edns, _, erre := DecodeEDNS(1, offset, payload)
+ if erre != nil {
+ t.Errorf("unexpected error when decoding EDNS: %v", erre)
+ }
+ if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
+ t.Errorf("invalid data in parsed EDNS header: %v", edns)
+ }
+
+ if len(edns.Options) != 1 {
+ t.Errorf("expected 1 EDNS option to be parsed, got %v", len(edns.Options))
+ }
+
+ expectedOption := DNSOption{Code: 0x0008, Name: OptCodeToString(0x0008), Data: "[fe80:100::]/24"}
+
+ if edns.Options[0] != expectedOption {
+ t.Errorf("bad option parsed, expected %v, got %v", expectedOption, edns.Options[0])
+ }
+
+}
+
+func TestDecodeQuery_EdnsSubnet_invalidFam(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x0b,
+ // RDATA
+ // CODE - Client subnet
+ 0x00, 0x08,
+ // Length
+ 0x00, 0x07,
+ // Option data
+ // family
+ 0x00, 0xff,
+ // prefix-len
+ 0x18,
+ // scope prefix-len
+ 0x00,
+ // address
+ 0xfe, 0x80, 0x01,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ answer, _, erra := DecodeAnswer(1, offset, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", erra)
+ }
+ // parsing answers should skip the OPT type and not return anything from
+ // the additional section
+ if len(answer) > 0 {
+ t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
+ }
+ _, _, erre := DecodeEDNS(1, offset, payload)
+
+ if !errors.Is(erre, ErrDecodeEdnsOptionCsubnetBadFamily) {
+ t.Errorf("bad error returned: %v", erre)
+ }
+}
+
+func TestDecodeQuery_EdnsSubnet_Short(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x06,
+ // RDATA
+ // CODE - Client subnet
+ 0x00, 0x08,
+ // Length
+ 0x00, 0x02,
+ // Option data
+ // family
+ 0x00, 0x01,
+ // prefix-len
+ // 0x18,
+ // // scope prefix-len
+ // 0x00,
+ // // address
+ // 0xfe, 0x80, 0x01,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ answer, _, erra := DecodeAnswer(1, offset, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", erra)
+ }
+ // parsing answers should skip the OPT type and not return anything from
+ // the additional section
+ if len(answer) > 0 {
+ t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
+ }
+ _, _, erre := DecodeEDNS(1, offset, payload)
+
+ if !errors.Is(erre, ErrDecodeEdnsOptionTooShort) {
+ t.Errorf("bad error returned: %v", erre)
+ }
+}
+
+func TestDecodeQuery_EdnsSubnet_NoAddr(t *testing.T) {
+ payload := []byte{
+ // header
+ 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ // query section
+ 0x0b, 0x73, 0x65, 0x6e,
+ 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01,
+ // Additional records section
+ // empty name
+ 0x00,
+ // type OPT
+ 0x00, 0x29,
+ // class / UDP Payload size
+ 0x04, 0xd0,
+ // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
+ 0x00, 0x00, 0x80, 0x00,
+ // RDLENGTH
+ 0x00, 0x08,
+ // RDATA
+ // CODE - Client subnet
+ 0x00, 0x08,
+ // Length
+ 0x00, 0x04,
+ // Option data
+ // family
+ 0x00, 0x01,
+ // prefix-len
+ 0x18,
+ // scope prefix-len
+ 0x00,
+ // // address
+ // 0xfe, 0x80, 0x01,
+ }
+
+ _, _, _, offset, err := DecodeQuestion(1, payload)
+ if err != nil {
+ t.Errorf("unexpected error while decoding question: %v", err)
+ }
+
+ answer, _, erra := DecodeAnswer(1, offset, payload)
+ if erra != nil {
+ t.Errorf("unexpected error while decoding answer: %v", erra)
+ }
+ // parsing answers should skip the OPT type and not return anything from
+ // the additional section
+ if len(answer) > 0 {
+ t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
+ }
+
+ edns, _, erre := DecodeEDNS(1, offset, payload)
+ if erre != nil {
+ t.Errorf("unexpected error while decoding EDNS: %v", erre)
+ }
+
+ expected := DNSOption{Code: 0x0008, Name: OptCodeToString(0x0008), Data: "0.0.0.0/24"}
+
+ if len(edns.Options) != 1 {
+ t.Errorf("expected one EDNS option, got %d", len(edns.Options))
+ }
+
+ if edns.Options[0] != expected {
+ t.Errorf("unexpected option parsed from EDNS, expected %v got %v", expected, edns.Options[0])
+ }
+
+}
diff --git a/dnsutils/edns_parser_test.go b/dnsutils/edns_parser_test.go
index a8b57fdc..7dec3891 100644
--- a/dnsutils/edns_parser_test.go
+++ b/dnsutils/edns_parser_test.go
@@ -72,485 +72,6 @@ func TestDecodeReply_EDNS(t *testing.T) {
}
}
-func TestDecodeQuery_EdnsSubnet(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x0b,
- // RDATA
- // CODE - Client subnet
- 0x00, 0x08,
- // Length
- 0x00, 0x07,
- // Option data
- // family
- 0x00, 0x01,
- // prefix-len
- 0x18,
- // scope prefix-len
- 0x00,
- // address
- 0xc0, 0xa8, 0x01,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- answer, _, erra := DecodeAnswer(1, offset, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", erra)
- }
- // parsing answers should skip the OPT type and not return anything from
- // the additional section
- if len(answer) > 0 {
- t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
- }
- edns, _, erre := DecodeEDNS(1, offset, payload)
- if erre != nil {
- t.Errorf("unexpected error when decoding EDNS: %v", erre)
- }
- if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
- t.Errorf("invalid data in parsed EDNS header: %v", edns)
- }
-
- if len(edns.Options) != 1 {
- t.Errorf("expected 1 EDNS option to be parsed, got %v", len(edns.Options))
- }
-
- expectedOption := DNSOption{Code: 0x0008, Name: OptCodeToString(0x0008), Data: "192.168.1.0/24"}
-
- if edns.Options[0] != expectedOption {
- t.Errorf("bad option parsed, expected %v, got %v", expectedOption, edns.Options[0])
- }
-
-}
-func TestDecodeQuery_EdnsSubnetV6(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x0b,
- // RDATA
- // CODE - Client subnet
- 0x00, 0x08,
- // Length
- 0x00, 0x07,
- // Option data
- // family
- 0x00, 0x02,
- // prefix-len
- 0x18,
- // scope prefix-len
- 0x00,
- // address
- 0xfe, 0x80, 0x01,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- answer, _, erra := DecodeAnswer(1, offset, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", erra)
- }
- // parsing answers should skip the OPT type and not return anything from
- // the additional section
- if len(answer) > 0 {
- t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
- }
- edns, _, erre := DecodeEDNS(1, offset, payload)
- if erre != nil {
- t.Errorf("unexpected error when decoding EDNS: %v", erre)
- }
- if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
- t.Errorf("invalid data in parsed EDNS header: %v", edns)
- }
-
- if len(edns.Options) != 1 {
- t.Errorf("expected 1 EDNS option to be parsed, got %v", len(edns.Options))
- }
-
- expectedOption := DNSOption{Code: 0x0008, Name: OptCodeToString(0x0008), Data: "[fe80:100::]/24"}
-
- if edns.Options[0] != expectedOption {
- t.Errorf("bad option parsed, expected %v, got %v", expectedOption, edns.Options[0])
- }
-
-}
-
-func TestDecodeQuery_EdnsSubnet_invalidFam(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x0b,
- // RDATA
- // CODE - Client subnet
- 0x00, 0x08,
- // Length
- 0x00, 0x07,
- // Option data
- // family
- 0x00, 0xff,
- // prefix-len
- 0x18,
- // scope prefix-len
- 0x00,
- // address
- 0xfe, 0x80, 0x01,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- answer, _, erra := DecodeAnswer(1, offset, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", erra)
- }
- // parsing answers should skip the OPT type and not return anything from
- // the additional section
- if len(answer) > 0 {
- t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
- }
- _, _, erre := DecodeEDNS(1, offset, payload)
-
- if !errors.Is(erre, ErrDecodeEdnsOptionCsubnetBadFamily) {
- t.Errorf("bad error returned: %v", erre)
- }
-}
-
-func TestDecodeQuery_EdnsSubnet_Short(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x06,
- // RDATA
- // CODE - Client subnet
- 0x00, 0x08,
- // Length
- 0x00, 0x02,
- // Option data
- // family
- 0x00, 0x01,
- // prefix-len
- // 0x18,
- // // scope prefix-len
- // 0x00,
- // // address
- // 0xfe, 0x80, 0x01,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- answer, _, erra := DecodeAnswer(1, offset, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", erra)
- }
- // parsing answers should skip the OPT type and not return anything from
- // the additional section
- if len(answer) > 0 {
- t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
- }
- _, _, erre := DecodeEDNS(1, offset, payload)
-
- if !errors.Is(erre, ErrDecodeEdnsOptionTooShort) {
- t.Errorf("bad error returned: %v", erre)
- }
-}
-
-func TestDecodeQuery_EdnsSubnet_NoAddr(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x08,
- // RDATA
- // CODE - Client subnet
- 0x00, 0x08,
- // Length
- 0x00, 0x04,
- // Option data
- // family
- 0x00, 0x01,
- // prefix-len
- 0x18,
- // scope prefix-len
- 0x00,
- // // address
- // 0xfe, 0x80, 0x01,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- answer, _, erra := DecodeAnswer(1, offset, payload)
- if erra != nil {
- t.Errorf("unexpected error while decoding answer: %v", erra)
- }
- // parsing answers should skip the OPT type and not return anything from
- // the additional section
- if len(answer) > 0 {
- t.Errorf("did not expect any answers to be parsed, got %d", len(answer))
- }
-
- edns, _, erre := DecodeEDNS(1, offset, payload)
- if erre != nil {
- t.Errorf("unexpected error while decoding EDNS: %v", erre)
- }
-
- expected := DNSOption{Code: 0x0008, Name: OptCodeToString(0x0008), Data: "0.0.0.0/24"}
-
- if len(edns.Options) != 1 {
- t.Errorf("expected one EDNS option, got %d", len(edns.Options))
- }
-
- if edns.Options[0] != expected {
- t.Errorf("unexpected option parsed from EDNS, expected %v got %v", expected, edns.Options[0])
- }
-
-}
-
-func TestDecodeAnswer_EdnsError(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x81, 0x82, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x06,
- // RDATA
- // CODE - Extended error
- 0x00, 0x0f,
- // Length
- 0x00, 0x02,
- // Option data
- // Error code
- 0x00, 0x17,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- edns, _, erre := DecodeEDNS(1, offset, payload)
- if erre != nil {
- t.Errorf("unexpected error while decoding edns: %v", erre)
- }
-
- if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
- t.Errorf("invalid data in parsed EDNS header: %v", edns)
- }
-
- if len(edns.Options) != 1 {
- t.Errorf("expected one edns option to be parsed, got %d", len(edns.Options))
- }
- expected := DNSOption{Code: 0x000f, Name: OptCodeToString(0x000f), Data: "23 Network Error -"}
- if edns.Options[0] != expected {
- t.Errorf("bad edns option, expected %v, got %v", expected, edns.Options[0])
- }
-
-}
-func TestDecodeAnswer_EdnsErrorText(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x81, 0x82, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x0c,
- // RDATA
- // CODE - Extended error
- 0x00, 0x0f,
- // Length
- 0x00, 0x08,
- // Option data
- // Error code
- 0x00, 0x17,
- // Error text
- 0x62, 0x30, 0x72, 0x6b, 0x65, 0x6e,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- edns, _, erre := DecodeEDNS(1, offset, payload)
- if erre != nil {
- t.Errorf("unexpected error while decoding edns: %v", erre)
- }
-
- if edns.Do != 1 || edns.Z != 0 || edns.Version != 0 || edns.UDPSize != 1232 || edns.ExtendedRcode != 0 {
- t.Errorf("invalid data in parsed EDNS header: %v", edns)
- }
-
- if len(edns.Options) != 1 {
- t.Errorf("expected one edns option to be parsed, got %d", len(edns.Options))
- }
- expected := DNSOption{Code: 0x000f, Name: OptCodeToString(0x000f), Data: "23 Network Error b0rken"}
- if edns.Options[0] != expected {
- t.Errorf("bad edns option, expected %v, got %v", expected, edns.Options[0])
- }
-
-}
-
-func TestDecodeAnswer_EdnsErrorShort(t *testing.T) {
- payload := []byte{
- // header
- 0xe9, 0x9d, 0x81, 0x82, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01,
- // query section
- 0x0b, 0x73, 0x65, 0x6e,
- 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x65, 0x65, 0x74,
- 0x03, 0x63, 0x6f, 0x6d, 0x00,
- 0x00, 0x01, 0x00, 0x01,
- // Additional records section
- // empty name
- 0x00,
- // type OPT
- 0x00, 0x29,
- // class / UDP Payload size
- 0x04, 0xd0,
- // TTL / EXT-RCODE=0, VERSION=0, DO=1, Z=0
- 0x00, 0x00, 0x80, 0x00,
- // RDLENGTH
- 0x00, 0x05,
- // RDATA
- // CODE - Extended error
- 0x00, 0x0f,
- // Length
- 0x00, 0x01,
- // Option data
- // Error code, missing byte
- 0x00,
- }
-
- _, _, _, offset, err := DecodeQuestion(1, payload)
- if err != nil {
- t.Errorf("unexpected error while decoding question: %v", err)
- }
-
- _, _, erre := DecodeEDNS(1, offset, payload)
- if !errors.Is(erre, ErrDecodeEdnsOptionTooShort) {
- t.Errorf("bad error returned: %v", erre)
- }
-}
-
func TestDecodeEdns_Short(t *testing.T) {
testData := []struct {
name string
diff --git a/dnsutils/helper.go b/dnsutils/helper.go
new file mode 100644
index 00000000..5a929401
--- /dev/null
+++ b/dnsutils/helper.go
@@ -0,0 +1,131 @@
+package dnsutils
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/dmachard/go-dnscollector/pkgconfig"
+ "github.com/dmachard/go-netutils"
+ "github.com/miekg/dns"
+)
+
+func GetFakeDNS() ([]byte, error) {
+ dnsmsg := new(dns.Msg)
+ dnsmsg.SetQuestion("dns.collector.", dns.TypeA)
+ return dnsmsg.Pack()
+}
+
+func GetFakeDNSMessage() DNSMessage {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.DNSTap.Identity = "collector"
+ dm.DNSTap.Version = "dnscollector 1.0.0"
+ dm.DNSTap.Operation = "CLIENT_QUERY"
+ dm.DNSTap.PeerName = "localhost (127.0.0.1)"
+ dm.DNS.Type = DNSQuery
+ dm.DNS.Qname = pkgconfig.ProgQname
+ dm.NetworkInfo.QueryIP = "1.2.3.4"
+ dm.NetworkInfo.QueryPort = "1234"
+ dm.NetworkInfo.ResponseIP = "4.3.2.1"
+ dm.NetworkInfo.ResponsePort = "4321"
+ dm.DNS.Rcode = "NOERROR"
+ dm.DNS.Qtype = "A"
+ return dm
+}
+
+func GetFakeDNSMessageWithPayload() DNSMessage {
+ // fake dns query payload
+ dnsmsg := new(dns.Msg)
+ dnsmsg.SetQuestion("dnscollector.dev.", dns.TypeAAAA)
+ dnsquestion, _ := dnsmsg.Pack()
+
+ dm := GetFakeDNSMessage()
+ dm.NetworkInfo.Family = netutils.ProtoIPv4
+ dm.NetworkInfo.Protocol = netutils.ProtoUDP
+ dm.DNS.Payload = dnsquestion
+ dm.DNS.Length = len(dnsquestion)
+ return dm
+}
+
+func GetFlatDNSMessage() (ret map[string]interface{}, err error) {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+ ret, err = dm.Flatten()
+ return
+}
+
+func GetReferenceDNSMessage() DNSMessage {
+ dm := DNSMessage{}
+ dm.Init()
+ dm.InitTransforms()
+ return dm
+}
+
+func GetIPPort(dm *DNSMessage) (string, int, string, int) {
+ srcIP, srcPort := "0.0.0.0", 53
+ dstIP, dstPort := "0.0.0.0", 53
+ if dm.NetworkInfo.Family == "INET6" {
+ srcIP, dstIP = "::", "::"
+ }
+
+ if dm.NetworkInfo.QueryIP != "-" {
+ srcIP = dm.NetworkInfo.QueryIP
+ srcPort, _ = strconv.Atoi(dm.NetworkInfo.QueryPort)
+ }
+ if dm.NetworkInfo.ResponseIP != "-" {
+ dstIP = dm.NetworkInfo.ResponseIP
+ dstPort, _ = strconv.Atoi(dm.NetworkInfo.ResponsePort)
+ }
+
+ // reverse destination and source
+ if dm.DNS.Type == DNSReply {
+ srcIPTmp, srcPortTmp := srcIP, srcPort
+ srcIP, srcPort = dstIP, dstPort
+ dstIP, dstPort = srcIPTmp, srcPortTmp
+ }
+ return srcIP, srcPort, dstIP, dstPort
+}
+
+func ConvertToString(value interface{}) string {
+ switch v := value.(type) {
+ case int:
+ return strconv.Itoa(v)
+ case bool:
+ return strconv.FormatBool(v)
+ case float64:
+ return strconv.FormatFloat(v, 'f', -1, 64)
+ case string:
+ return v
+ default:
+ return fmt.Sprintf("%v", v)
+ }
+}
+
+func QuoteStringAndWrite(s *strings.Builder, fieldString, fieldDelimiter, fieldBoundary string) {
+ // Handle the case where the field string is empty and boundaries are specified
+ if fieldString == "" && len(fieldBoundary) > 0 {
+ s.WriteString(fmt.Sprintf("%s%s%s", fieldBoundary, fieldString, fieldBoundary))
+ return
+ }
+
+ switch {
+ case len(fieldDelimiter) > 0 && strings.Contains(fieldString, fieldDelimiter):
+ // Case where the field string contains the delimiter
+ fieldEscaped := fieldString
+ if len(fieldBoundary) > 0 && strings.Contains(fieldEscaped, fieldBoundary) {
+ fieldEscaped = strings.ReplaceAll(fieldEscaped, fieldBoundary, "\\"+fieldBoundary)
+ }
+ s.WriteString(fmt.Sprintf("%s%s%s", fieldBoundary, fieldEscaped, fieldBoundary))
+
+ case len(fieldBoundary) > 0 && strings.Contains(fieldString, fieldBoundary):
+ // Case where the field string contains the boundary character
+ fieldEscaped := strings.ReplaceAll(fieldString, fieldBoundary, "\\"+fieldBoundary)
+ s.WriteString(fmt.Sprintf("%s%s%s", fieldBoundary, fieldEscaped, fieldBoundary))
+
+ default:
+ // Default case: simply write the field string as is
+ s.WriteString(fieldString)
+ }
+}
diff --git a/dnsutils/helper_test.go b/dnsutils/helper_test.go
new file mode 100644
index 00000000..d4e2c728
--- /dev/null
+++ b/dnsutils/helper_test.go
@@ -0,0 +1,221 @@
+package dnsutils
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestHelper_GetIPPort(t *testing.T) {
+ tests := []struct {
+ name string
+ dm *DNSMessage
+ wantSrcIP string
+ wantSrcPort int
+ wantDstIP string
+ wantDstPort int
+ }{
+ {
+ name: "Test IPv4 source and destination",
+ dm: &DNSMessage{
+ NetworkInfo: DNSNetInfo{
+ Family: "INET",
+ QueryIP: "192.168.1.1", QueryPort: "1234",
+ ResponseIP: "192.168.1.2", ResponsePort: "5678",
+ },
+ DNS: DNS{Type: DNSQuery},
+ },
+ wantSrcIP: "192.168.1.1",
+ wantSrcPort: 1234,
+ wantDstIP: "192.168.1.2",
+ wantDstPort: 5678,
+ },
+ {
+ name: "Test IPv6 source and destination",
+ dm: &DNSMessage{
+ NetworkInfo: DNSNetInfo{
+ Family: "INET6",
+ QueryIP: "::1", QueryPort: "1234",
+ ResponseIP: "::2", ResponsePort: "5678",
+ },
+ DNS: DNS{Type: DNSQuery},
+ },
+ wantSrcIP: "::1",
+ wantSrcPort: 1234,
+ wantDstIP: "::2",
+ wantDstPort: 5678,
+ },
+ {
+ name: "Test DNSReply type",
+ dm: &DNSMessage{
+ NetworkInfo: DNSNetInfo{
+ Family: "INET",
+ QueryIP: "192.168.1.1", QueryPort: "1234",
+ ResponseIP: "192.168.1.2", ResponsePort: "5678",
+ },
+ DNS: DNS{Type: DNSReply},
+ },
+ wantSrcIP: "192.168.1.2",
+ wantSrcPort: 5678,
+ wantDstIP: "192.168.1.1",
+ wantDstPort: 1234,
+ },
+ {
+ name: "Test missing QueryIP and ResponseIP",
+ dm: &DNSMessage{
+ NetworkInfo: DNSNetInfo{
+ Family: "INET",
+ QueryIP: "-", QueryPort: "-",
+ ResponseIP: "-", ResponsePort: "-",
+ },
+ DNS: DNS{Type: DNSQuery},
+ },
+ wantSrcIP: "0.0.0.0",
+ wantSrcPort: 53,
+ wantDstIP: "0.0.0.0",
+ wantDstPort: 53,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ srcIP, srcPort, dstIP, dstPort := GetIPPort(tt.dm)
+ if srcIP != tt.wantSrcIP {
+ t.Errorf("GetIPPort() srcIP = %v, want %v", srcIP, tt.wantSrcIP)
+ }
+ if srcPort != tt.wantSrcPort {
+ t.Errorf("GetIPPort() srcPort = %v, want %v", srcPort, tt.wantSrcPort)
+ }
+ if dstIP != tt.wantDstIP {
+ t.Errorf("GetIPPort() dstIP = %v, want %v", dstIP, tt.wantDstIP)
+ }
+ if dstPort != tt.wantDstPort {
+ t.Errorf("GetIPPort() dstPort = %v, want %v", dstPort, tt.wantDstPort)
+ }
+ })
+ }
+}
+
+func TestHelper_ConvertToString(t *testing.T) {
+ tests := []struct {
+ name string
+ input interface{}
+ expected string
+ }{
+ {
+ name: "Test int",
+ input: 42,
+ expected: "42",
+ },
+ {
+ name: "Test bool true",
+ input: true,
+ expected: "true",
+ },
+ {
+ name: "Test bool false",
+ input: false,
+ expected: "false",
+ },
+ {
+ name: "Test float64",
+ input: 3.14159,
+ expected: "3.14159",
+ },
+ {
+ name: "Test string",
+ input: "hello",
+ expected: "hello",
+ },
+ {
+ name: "Test unknown type",
+ input: struct{ Name string }{Name: "example"},
+ expected: "{example}",
+ },
+ {
+ name: "Test nil",
+ input: nil,
+ expected: "