@@ -3,30 +3,29 @@ package layers
33import (
44 "encoding/binary"
55 "encoding/hex"
6+ "encoding/json"
67 "fmt"
78 "net/netip"
89 "strings"
910)
1011
11- // TODO (shadowy-pycoder): add MarshalJSON
12-
1312const headerSizeDNS = 12
1413
1514type DNSFlags struct {
16- Raw uint16
17- QR uint8 // Indicates if the message is a query (0) or a reply (1).
18- QRDesc string // Query (0) or Reply (1)
19- OPCode uint8 // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
20- OPCodeDesc string
21- AA uint8 // Authoritative Answer, in a response, indicates if the DNS server is authoritative for the queried hostname.
22- TC uint8 // TrunCation, indicates that this message was truncated due to excessive length.
23- RD uint8 // Recursion Desired, indicates if the client means a recursive query.
24- RA uint8 // Recursion Available, in a response, indicates if the replying DNS server supports recursion.
25- Z uint8 // Zero, reserved for future use.
26- AU uint8 // Indicates if answer/authority portion was authenticated by the server.
27- NA uint8 // Indicates if non-authenticated data is accepatable.
28- RCode uint8 // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
29- RCodeDesc string
15+ Raw uint16 `json:"raw"`
16+ QR uint8 `json:"qr"` // Indicates if the message is a query (0) or a reply (1).
17+ QRDesc string `json:"qrdesc"` // Query (0) or Reply (1)
18+ OPCode uint8 `json:"opcode"` // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
19+ OPCodeDesc string `json:"opcodedesc"`
20+ AA uint8 `json:"aa"` // Authoritative Answer, in a response, indicates if the DNS server is authoritative for the queried hostname.
21+ TC uint8 `json:"tc"` // TrunCation, indicates that this message was truncated due to excessive length.
22+ RD uint8 `json:"rd"` // Recursion Desired, indicates if the client means a recursive query.
23+ RA uint8 `json:"ra"` // Recursion Available, in a response, indicates if the replying DNS server supports recursion.
24+ Z uint8 `json:"z"` // Zero, reserved for future use.
25+ AU uint8 `json:"au"` // Indicates if answer/authority portion was authenticated by the server.
26+ NA uint8 `json:"na"` // Indicates if non-authenticated data is accepatable.
27+ RCode uint8 `json:"rcode"` // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
28+ RCodeDesc string `json:"rcodedesc"`
3029}
3130
3231func (df * DNSFlags ) String () string {
@@ -173,16 +172,16 @@ func rcdesc(rcode uint8) string {
173172}
174173
175174type DNSMessage struct {
176- TransactionID uint16 // Used for matching response to queries.
177- Flags * DNSFlags // Flags specify the requested operation and a response code.
178- QDCount uint16 // Count of entries in the queries section.
179- ANCount uint16 // Count of entries in the answers section.
180- NSCount uint16 // Count of entries in the authority section.
181- ARCount uint16 // Count of entries in the additional section.
182- Questions []* QueryEntry
183- AnswerRRs []* ResourceRecord
184- AuthorityRRs []* ResourceRecord
185- AdditionalRRs []* ResourceRecord
175+ TransactionID uint16 `json:"transaction-id"` // Used for matching response to queries.
176+ Flags * DNSFlags `json:"flags,omitempty"` // Flags specify the requested operation and a response code.
177+ QDCount uint16 `json:"questions-count"` // Count of entries in the queries section.
178+ ANCount uint16 `json:"answer-rrs-count"` // Count of entries in the answers section.
179+ NSCount uint16 `json:"authority-rrs-count"` // Count of entries in the authority section.
180+ ARCount uint16 `json:"additional-rrs-count"` // Count of entries in the additional section.
181+ Questions []* QueryEntry `json:"questions,omitempty"`
182+ AnswerRRs []* ResourceRecord `json:"answers,omitempty"`
183+ AuthorityRRs []* ResourceRecord `json:"authoritative-nameservers,omitempty"`
184+ AdditionalRRs []* ResourceRecord `json:"additional-records,omitempty"`
186185}
187186
188187func (d * DNSMessage ) String () string {
@@ -313,15 +312,31 @@ func (d *DNSMessage) printRecords() string {
313312 if d .ARCount > 0 {
314313 sb .WriteString ("- Additional records:\n " )
315314 for _ , rec := range d .AdditionalRRs {
316- sb .WriteString (rec .String ())
315+ sb .WriteString (strings . TrimSuffix ( rec .String (), " \n " ))
317316 }
318317 }
319- return strings .TrimSuffix (sb .String (), "\n " )
318+ return sb .String ()
319+ }
320+
321+ type dnsMessageAlias DNSMessage
322+
323+ type dnsQueryWrapper struct {
324+ Query * dnsMessageAlias `json:"dns_query"`
325+ }
326+ type dnsReplyWrapper struct {
327+ Reply * dnsMessageAlias `json:"dns_reply"`
328+ }
329+
330+ func (d * DNSMessage ) MarshalJSON () ([]byte , error ) {
331+ if d .Flags .QR == 0 {
332+ return json .Marshal (& dnsQueryWrapper {Query : (* dnsMessageAlias )(d )})
333+ }
334+ return json .Marshal (& dnsReplyWrapper {Reply : (* dnsMessageAlias )(d )})
320335}
321336
322337type RecordClass struct {
323- Name string
324- Val uint16
338+ Name string `json:"name"`
339+ Val uint16 `json:"val"`
325340}
326341
327342func (c * RecordClass ) String () string {
@@ -351,8 +366,8 @@ func className(cls uint16) string {
351366}
352367
353368type RecordType struct {
354- Name string
355- Val uint16
369+ Name string `json:"name"`
370+ Val uint16 `json:"val"`
356371}
357372
358373func (rt * RecordType ) String () string {
@@ -391,12 +406,12 @@ func typeName(typ uint16) string {
391406}
392407
393408type ResourceRecord struct {
394- Name string // Name of the node to which this record pertains.
395- Type * RecordType // Type of RR in numeric form.
396- Class * RecordClass // Class code.
397- TTL uint32 // Count of seconds that the RR stays valid.
398- RDLength uint16 // Length of RData field (specified in octets).
399- RData fmt.Stringer // Additional RR-specific data.
409+ Name string `json:"name"` // Name of the node to which this record pertains.
410+ Type * RecordType `json:"record-type"` // Type of RR in numeric form.
411+ Class * RecordClass `json:"record-class"` // Class code.
412+ TTL uint32 `json:"ttl"` // Count of seconds that the RR stays valid.
413+ RDLength uint16 `json:"rdata-length"` // Length of RData field (specified in octets).
414+ RData fmt.Stringer `json:"rdata"` // Additional RR-specific data.
400415}
401416
402417func (rt * ResourceRecord ) String () string {
@@ -453,9 +468,9 @@ func (rt *ResourceRecord) Summary() string {
453468}
454469
455470type QueryEntry struct {
456- Name string // Name of the node to which this record pertains.
457- Type * RecordType // Type of RR in numeric form.
458- Class * RecordClass // Class code.
471+ Name string `json:"name"` // Name of the node to which this record pertains.
472+ Type * RecordType `json:"record-type"` // Type of RR in numeric form.
473+ Class * RecordClass `json:"record-class"` // Class code.
459474}
460475
461476func (qe * QueryEntry ) String () string {
@@ -467,37 +482,37 @@ func (qe *QueryEntry) String() string {
467482}
468483
469484type RDataA struct {
470- Address netip.Addr
485+ Address netip.Addr `json:"address"`
471486}
472487
473488func (d * RDataA ) String () string {
474489 return fmt .Sprintf ("Address: %s" , d .Address )
475490}
476491
477492type RDataNS struct {
478- NsdName string
493+ NsdName string `json:"ns"`
479494}
480495
481496func (d * RDataNS ) String () string {
482497 return fmt .Sprintf ("NS: %s" , d .NsdName )
483498}
484499
485500type RDataCNAME struct {
486- CName string
501+ CName string `json:"cname"`
487502}
488503
489504func (d * RDataCNAME ) String () string {
490505 return fmt .Sprintf ("CNAME: %s" , d .CName )
491506}
492507
493508type RDataSOA struct {
494- PrimaryNS string
495- RespAuthorityMailbox string
496- SerialNumber uint32
497- RefreshInterval uint32
498- RetryInterval uint32
499- ExpireLimit uint32
500- MinimumTTL uint32
509+ PrimaryNS string `json:"primary-nameserver"`
510+ RespAuthorityMailbox string `json:"responsible-authority-mailbox"`
511+ SerialNumber uint32 `json:"serial-number"`
512+ RefreshInterval uint32 `json:"refresh-interval"`
513+ RetryInterval uint32 `json:"retry-interval"`
514+ ExpireLimit uint32 `json:"expire-limit"`
515+ MinimumTTL uint32 `json:"minimum-ttl"`
501516}
502517
503518func (d * RDataSOA ) String () string {
@@ -518,36 +533,36 @@ func (d *RDataSOA) String() string {
518533}
519534
520535type RDataMX struct {
521- Preference uint16
522- Exchange string
536+ Preference uint16 `json:"preference"`
537+ Exchange string `json:"exchange"`
523538}
524539
525540func (d * RDataMX ) String () string {
526541 return fmt .Sprintf ("MX: %d %s" , d .Preference , d .Exchange )
527542}
528543
529544type RDataTXT struct {
530- TxtData string
545+ TxtData string `json:"txt-data"`
531546}
532547
533548func (d * RDataTXT ) String () string {
534549 return fmt .Sprintf ("TXT: %s" , d .TxtData )
535550}
536551
537552type RDataAAAA struct {
538- Address netip.Addr
553+ Address netip.Addr `json:"address"`
539554}
540555
541556func (d * RDataAAAA ) String () string {
542557 return fmt .Sprintf ("Address: %s" , d .Address )
543558}
544559
545560type RDataOPT struct {
546- UDPPayloadSize uint16
547- HigherBitsExtRCode uint8
548- EDNSVer uint8
549- Z uint16
550- DataLen uint16
561+ UDPPayloadSize uint16 `json:"udp-payload-size"`
562+ HigherBitsExtRCode uint8 `json:"higer-bits-in-extended-rcode"`
563+ EDNSVer uint8 `json:"edns0-version"`
564+ Z uint16 `json:"z"`
565+ DataLen uint16 `json:"data-length"`
551566}
552567
553568func (d * RDataOPT ) String () string {
@@ -565,8 +580,8 @@ func (d *RDataOPT) String() string {
565580}
566581
567582type SvcParamKey struct {
568- Val uint16
569- Desc string
583+ Val uint16 `json:"val"`
584+ Desc string `json:"desc"`
570585}
571586
572587// https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
@@ -608,9 +623,9 @@ func (spk *SvcParamKey) String() string {
608623}
609624
610625type SvcParam struct {
611- Key * SvcParamKey
612- Length uint16
613- Value []byte // TODO: add proper parsing
626+ Key * SvcParamKey `json:"svc-param-key"`
627+ Length uint16 `json:"svc-param-value-length"`
628+ Value []byte `json:"svc-param-value"` // TODO: add proper parsing
614629}
615630
616631func newSvcParam (data []byte ) (* SvcParam , []byte , error ) {
@@ -639,10 +654,10 @@ func (sp *SvcParam) String() string {
639654}
640655
641656type RDataHTTPS struct {
642- SvcPriority uint16
643- Length int
644- TargetName string
645- SvcParams []* SvcParam
657+ SvcPriority uint16 `json:"svc-priority"`
658+ Length int `json:"length"`
659+ TargetName string `json:"target-name"`
660+ SvcParams []* SvcParam `json:"svc-params"`
646661}
647662
648663func (d * RDataHTTPS ) printSvcParams () string {
@@ -668,7 +683,7 @@ func (d *RDataHTTPS) String() string {
668683}
669684
670685type RDataUnknown struct {
671- Data string
686+ Data string `json:"data"`
672687}
673688
674689func (d * RDataUnknown ) String () string {
@@ -681,7 +696,7 @@ func (d *RDataUnknown) String() string {
681696func extractDomain (payload , tail []byte ) (string , []byte , error ) {
682697 // see https://brunoscheufler.com/blog/2024-05-12-building-a-dns-message-parser#domain-names
683698 var domainName string
684- for {
699+ for len ( tail ) > 0 {
685700 blen := tail [0 ]
686701 if blen >> 6 == 0b11 {
687702 if len (tail ) < 2 {
0 commit comments