Skip to content

Commit

Permalink
add arcount, nscount and ancount (#829)
Browse files Browse the repository at this point in the history
* add arcount, nscount and ancount

* rename questions-count to qdcount
support ancount, nscount and arcount
add tests

* fix linter
update README.md
  • Loading branch information
dmachard authored Sep 30, 2024
1 parent 6174bd8 commit 9798b2f
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 22 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<p align="center">
<img src="https://goreportcard.com/badge/github.com/dmachard/go-dns-collector" alt="Go Report"/>
<img src="https://img.shields.io/badge/go%20version-min%201.21-green" alt="Go version"/>
<img src="https://img.shields.io/badge/go%20tests-501-green" alt="Go tests"/>
<img src="https://img.shields.io/badge/go%20tests-505-green" alt="Go tests"/>
<img src="https://img.shields.io/badge/go%20bench-21-green" alt="Go bench"/>
<img src="https://img.shields.io/badge/go%20lines-31528-green" alt="Go lines"/>
<img src="https://img.shields.io/badge/go%20lines-31634-green" alt="Go lines"/>
</p>

<p align="center">
Expand Down
31 changes: 22 additions & 9 deletions dnsutils/dnsmessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ type DNS struct {
Qname string `json:"qname"`
Qclass string `json:"qclass"`

QuestionsCount int `json:"questions-count"`
QdCount int `json:"qdcount"`
AnCount int `json:"ancount"`
NsCount int `json:"nscount"`
ArCount int `json:"arcount"`

Qtype string `json:"qtype"`
Flags DNSFlags `json:"flags"`
Expand Down Expand Up @@ -611,7 +614,7 @@ func (dm *DNSMessage) String(format []string, fieldDelimiter string, fieldBounda
func (dm *DNSMessage) ToTextLine(format []string, fieldDelimiter string, fieldBoundary string) ([]byte, error) {
var s strings.Builder

answers := dm.DNS.DNSRRs.Answers
an := dm.DNS.DNSRRs.Answers
qname := dm.DNS.Qname
flags := dm.DNS.Flags

Expand Down Expand Up @@ -741,19 +744,26 @@ func (dm *DNSMessage) ToTextLine(format []string, fieldDelimiter string, fieldBo
s.WriteByte('-')
}
case directive == "ttl":
if len(answers) > 0 {
s.WriteString(strconv.Itoa(answers[0].TTL))
if len(an) > 0 {
s.WriteString(strconv.Itoa(an[0].TTL))
} else {
s.WriteByte('-')
}
case directive == "answer":
if len(answers) > 0 {
s.WriteString(answers[0].Rdata)
if len(an) > 0 {
s.WriteString(an[0].Rdata)
} else {
s.WriteByte('-')
}
case directive == "answercount":
s.WriteString(strconv.Itoa(len(answers)))

case directive == "questionscount" || directive == "qdcount":
s.WriteString(strconv.Itoa(dm.DNS.QdCount))
case directive == "answercount" || directive == "ancount":
s.WriteString(strconv.Itoa(dm.DNS.AnCount))
case directive == "nscount":
s.WriteString(strconv.Itoa(dm.DNS.NsCount))
case directive == "arcount":
s.WriteString(strconv.Itoa(dm.DNS.ArCount))

case directive == "edns-csubnet":
if len(dm.EDNS.Options) > 0 {
Expand Down Expand Up @@ -1130,7 +1140,10 @@ func (dm *DNSMessage) Flatten() (map[string]interface{}, error) {
"dns.qtype": dm.DNS.Qtype,
"dns.qclass": dm.DNS.Qclass,
"dns.rcode": dm.DNS.Rcode,
"dns.questions-count": dm.DNS.QuestionsCount,
"dns.qdcount": dm.DNS.QdCount,
"dns.ancount": dm.DNS.AnCount,
"dns.arcount": dm.DNS.ArCount,
"dns.nscount": dm.DNS.NsCount,
"dnstap.identity": dm.DNSTap.Identity,
"dnstap.latency": dm.DNSTap.Latency,
"dnstap.operation": dm.DNSTap.Operation,
Expand Down
10 changes: 8 additions & 2 deletions dnsutils/dnsmessage_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ func TestDnsMessage_Json_Reference(t *testing.T) {
"qname": "-",
"qtype": "-",
"qclass": "-",
"questions-count": 0,
"qdcount": 0,
"ancount": 0,
"nscount": 0,
"arcount": 0,
"flags": {
"qr": false,
"tc": false,
Expand Down Expand Up @@ -287,7 +290,10 @@ func TestDnsMessage_JsonFlatten_Reference(t *testing.T) {
"dns.qtype": "-",
"dns.rcode": "-",
"dns.qclass": "-",
"dns.questions-count": 0,
"dns.qdcount": 0,
"dns.ancount": 0,
"dns.arcount": 0,
"dns.nscount": 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",
Expand Down
4 changes: 2 additions & 2 deletions dnsutils/dnsmessage_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestDnsMessage_ToJinjaFormat(t *testing.T) {
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 }}
;; flags: {{ dm.DNS.Flags.QR | yesno:"qr ," }}{{ dm.DNS.Flags.RD | yesno:"rd ," }}{{ dm.DNS.Flags.RA | yesno:"ra ," }}; QUERY: {{ dm.DNS.QdCount }}, ANSWER: {{ dm.DNS.DNSRRs.AnCount }}, AUTHORITY: {{ dm.DNS.DNSRRs.NsCount }}, ADDITIONAL: {{ dm.DNS.DNSRRs.ArCount }}
;; QUESTION SECTION:
;{{ dm.DNS.Qname }} {{ dm.DNS.Qclass }} {{ dm.DNS.Qtype }}
Expand Down Expand Up @@ -44,7 +44,7 @@ func BenchmarkDnsMessage_ToJinjaFormat(b *testing.B) {
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 }}
;; flags: {{ dm.DNS.Flags.QR | yesno:"qr ," }}{{ dm.DNS.Flags.RD | yesno:"rd ," }}{{ dm.DNS.Flags.RA | yesno:"ra ," }}; QUERY: {{ dm.DNS.QdCount }}, ANSWER: {{ dm.DNS.DNSRRs.AnCount }}, AUTHORITY: {{ dm.DNS.DNSRRs.NsCount }}, ADDITIONAL: {{ dm.DNS.ArCount }}
;; QUESTION SECTION:
;{{ dm.DNS.Qname }} {{ dm.DNS.Qclass }} {{ dm.DNS.Qtype }}
Expand Down
10 changes: 10 additions & 0 deletions dnsutils/dnsmessage_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,16 @@ func TestDnsMessage_TextFormat_DefaultDirectives(t *testing.T) {
dm: DNSMessage{DNSTap: DNSTap{QueryZone: "queryzone.test"}},
expected: "queryzone.test",
},
{
format: "qdcount",
dm: DNSMessage{DNS: DNS{QdCount: 1}},
expected: "1",
},
{
format: "ancount nscount arcount",
dm: DNSMessage{DNS: DNS{AnCount: 1, ArCount: 2, NsCount: 3}},
expected: "1 3 2",
},
}

for _, tc := range testcases {
Expand Down
30 changes: 30 additions & 0 deletions dnsutils/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,36 @@ func GetFakeDNS() ([]byte, error) {
return dnsmsg.Pack()
}

func GetDNSResponsePacket() ([]byte, error) {
dnsmsg := new(dns.Msg)
dnsmsg.SetQuestion("dns.collector.", dns.TypeA)

// Build a fake response for the question
rr, err := dns.NewRR("dns.collector. 3600 IN A 192.168.1.1")
if err != nil {
return nil, err
}

// Add the resource record (the answer) to the message
dnsmsg.Answer = append(dnsmsg.Answer, rr)

// Build an authoritative NS record (Authoritative section)
nsRR, err := dns.NewRR("collector. 3600 IN NS ns1.collector.")
if err != nil {
return nil, err
}
dnsmsg.Ns = append(dnsmsg.Ns, nsRR)

// Build an additional A record for the authoritative NS (Additional section)
additionalRR, err := dns.NewRR("ns1.collector. 3600 IN A 192.168.2.1")
if err != nil {
return nil, err
}
dnsmsg.Extra = append(dnsmsg.Extra, additionalRR)

return dnsmsg.Pack()
}

func GetFakeDNSMessage() DNSMessage {
dm := DNSMessage{}
dm.Init()
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Example to enable output similiar to dig style:
text-jinja: |+
;; 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 }}
;; flags: {{ dm.DNS.Flags.QR | yesno:"qr ," }}{{ dm.DNS.Flags.RD | yesno:"rd ," }}{{ dm.DNS.Flags.RA | yesno:"ra ," }}; QUERY: {{ dm.DNS.QdCount }}, ANSWER: {{ dm.DNS.AnCount }}, AUTHORITY: {{ dm.DNS.NsCount }}, ADDITIONAL: {{ dm.DNS.ArCount }}
;; QUESTION SECTION:
;{{ dm.DNS.Qname }} {{ dm.DNS.Qclass }} {{ dm.DNS.Qtype }}
Expand Down
10 changes: 8 additions & 2 deletions docs/dnsjson.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ Example:
"qtype": "A",
"id": 23455,
"qclass": "IN",
"questions-count": 0,
"qdcount": 1,
"ancount": 1,
"nscount": 0,
"arcount": 0,
"flags": {
"qr": true,
"tc": false,
Expand Down Expand Up @@ -117,7 +120,10 @@ Here's a flat JSON output formatted using `jq`:
"dns.qtype": "A",
"dns.rcode": "NOERROR",
"dns.qclass": "IN",
"dns.questions-count": 0,
"dns.qdcount": 0,
"dns.ancount": 1,
"dns.arcount": 0,
"dns.nscount": 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",
Expand Down
7 changes: 5 additions & 2 deletions workers/dnsprocessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ func (w *DNSProcessor) StartCollect() {
w.LogError("dns parser malformed packet: %s - %v+", err, dm)
}

// get number of questions
dm.DNS.QuestionsCount = dnsHeader.Qdcount
// get number of questions and answers
dm.DNS.QdCount = dnsHeader.Qdcount
dm.DNS.AnCount = dnsHeader.Ancount
dm.DNS.ArCount = dnsHeader.Arcount
dm.DNS.NsCount = dnsHeader.Nscount

// dns reply ?
if dnsHeader.Qr == 1 {
Expand Down
40 changes: 40 additions & 0 deletions workers/dnsprocessor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,46 @@ func Test_DnsProcessor(t *testing.T) {
}
}

func Test_DnsProcessor_DecodeCounters(t *testing.T) {
logger := logger.New(true)
var o bytes.Buffer
logger.SetOutput(&o)

// init and run the dns processor
fl := GetWorkerForTest(pkgconfig.DefaultBufferSize)

consumer := NewDNSProcessor(pkgconfig.GetDefaultConfig(), logger, "test", 512)
consumer.AddDefaultRoute(fl)
consumer.AddDroppedRoute(fl)
go consumer.StartCollect()

// get dns packet
responsePacket, _ := dnsutils.GetDNSResponsePacket()

// prepare dns message
dm := dnsutils.GetFakeDNSMessage()
dm.DNS.Payload = responsePacket
dm.DNS.Length = len(responsePacket)

// send dm to consumer
consumer.GetInputChannel() <- dm

// read dns message from dnstap consumer
dmOut := <-fl.GetInputChannel()
if dmOut.DNS.QdCount != 1 {
t.Errorf("invalid number of questions in dns message: got %d expect 1", dmOut.DNS.QdCount)
}
if dmOut.DNS.NsCount != 1 {
t.Errorf("invalid number of nscount in dns message: got %d expect 1", dmOut.DNS.NsCount)
}
if dmOut.DNS.AnCount != 1 {
t.Errorf("invalid number of ancount in dns message: got %d expect 1", dmOut.DNS.AnCount)
}
if dmOut.DNS.ArCount != 1 {
t.Errorf("invalid number of arcount in dns message: got %d expect 1", dmOut.DNS.ArCount)
}
}

func Test_DnsProcessor_BufferLoggerIsFull(t *testing.T) {
// redirect stdout output to bytes buffer
logsChan := make(chan logger.LogEntry, 10)
Expand Down
5 changes: 4 additions & 1 deletion workers/dnstapserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,10 @@ func (w *DNSTapProcessor) StartCollect() {
}

// get number of questions
dm.DNS.QuestionsCount = dnsHeader.Qdcount
dm.DNS.QdCount = dnsHeader.Qdcount
dm.DNS.AnCount = dnsHeader.Ancount
dm.DNS.ArCount = dnsHeader.Arcount
dm.DNS.NsCount = dnsHeader.Nscount

if err = dnsutils.DecodePayload(&dm, &dnsHeader, w.GetConfig()); err != nil {
dm.DNS.MalformedPacket = true
Expand Down
49 changes: 48 additions & 1 deletion workers/dnstapserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func Test_DnstapCollector_CloseFrameStream(t *testing.T) {
c.Stop()
}

func Test_DnstapProcessor(t *testing.T) {
func Test_DnstapProcessor_toDNSMessage(t *testing.T) {
logger := logger.New(true)
var o bytes.Buffer
logger.SetOutput(&o)
Expand Down Expand Up @@ -240,6 +240,53 @@ func Test_DnstapProcessor(t *testing.T) {
}
}

func Test_DnstapProcessor_DecodeCounters(t *testing.T) {
logger := logger.New(true)
var o bytes.Buffer
logger.SetOutput(&o)

// run the consumer with a fake logger
fl := GetWorkerForTest(pkgconfig.DefaultBufferSize)

// init the dnstap consumer
consumer := NewDNSTapProcessor(0, "peertest", pkgconfig.GetDefaultConfig(), logger, "test", 512)
consumer.AddDefaultRoute(fl)
consumer.AddDroppedRoute(fl)

// get dns packet
responsePacket, _ := dnsutils.GetDNSResponsePacket()

// prepare dnstap
dt := &dnstap.Dnstap{}
dt.Type = dnstap.Dnstap_Type.Enum(1)

dt.Message = &dnstap.Message{}
dt.Message.Type = dnstap.Message_Type.Enum(6) // CLIENT_RESPONSE
dt.Message.ResponseMessage = responsePacket
data, _ := proto.Marshal(dt)

// start the consumer
go consumer.StartCollect()

// add packet to consumer
consumer.GetDataChannel() <- data

// read dns message from dnstap consumer
dm := <-fl.GetInputChannel()
if dm.DNS.QdCount != 1 {
t.Errorf("invalid number of questions in dns message: got %d expect 1", dm.DNS.QdCount)
}
if dm.DNS.NsCount != 1 {
t.Errorf("invalid number of nscount in dns message: got %d expect 1", dm.DNS.NsCount)
}
if dm.DNS.AnCount != 1 {
t.Errorf("invalid number of ancount in dns message: got %d expect 1", dm.DNS.AnCount)
}
if dm.DNS.ArCount != 1 {
t.Errorf("invalid number of arcount in dns message: got %d expect 1", dm.DNS.ArCount)
}
}

func Test_DnstapProcessor_MalformedDnsHeader(t *testing.T) {
// run the consumer with a fake logger
fl := GetWorkerForTest(pkgconfig.DefaultBufferSize)
Expand Down

0 comments on commit 9798b2f

Please sign in to comment.