55 "fmt"
66 "net/netip"
77 "strings"
8- "unsafe"
98)
109
1110const headerSizeDNS = 12
@@ -29,7 +28,6 @@ func (d *DNSMessage) String() string {
2928- Answer RRs: %d
3029- Authority RRs: %d
3130- Additional RRs: %d
32- - Payload: %d bytes
3331%s
3432` ,
3533 d .TransactionID ,
@@ -39,7 +37,6 @@ func (d *DNSMessage) String() string {
3937 d .AnswerRRs ,
4038 d .AuthorityRRs ,
4139 d .AdditionalRRs ,
42- len (d .payload ),
4340 d .rrecords (),
4441 )
4542}
@@ -64,9 +61,9 @@ func (d *DNSMessage) NextLayer() (string, []byte) {
6461}
6562
6663func (d * DNSMessage ) flags () string {
67- // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
6864 var flags string
6965 opcode := (d .Flags >> 11 ) & 15
66+ // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
7067 var opcodes string
7168 switch opcode {
7269 case 0 :
@@ -88,7 +85,7 @@ func (d *DNSMessage) flags() string {
8885 rd := (d .Flags >> 8 ) & 1
8986 z := (d .Flags >> 6 ) & 1
9087 na := (d .Flags >> 4 ) & 1
91- qr := ( d .Flags >> 15 ) & 1
88+ qr := d .Flags >> 15
9289 var qrs string
9390 switch qr {
9491 case 0 :
@@ -105,6 +102,7 @@ func (d *DNSMessage) flags() string {
105102 ra := (d .Flags >> 7 ) & 1
106103 aa := (d .Flags >> 5 ) & 1
107104 rcode := d .Flags & 15
105+ // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
108106 var rcodes string
109107 switch rcode {
110108 case 0 :
@@ -126,7 +124,27 @@ func (d *DNSMessage) flags() string {
126124 case 8 :
127125 rcodes = "Server not authoritative for the zone"
128126 case 9 :
129- rcodes = "Name not in zone"
127+ rcodes = "Server Not Authoritative for zone"
128+ case 10 :
129+ rcodes = "Name not contained in zone"
130+ case 11 :
131+ rcodes = "DSO-TYPE Not Implemented"
132+ case 16 :
133+ rcodes = "Bad OPT Version/TSIG Signature Failure"
134+ case 17 :
135+ rcodes = "Key not recognizede"
136+ case 18 :
137+ rcodes = "Signature out of time window"
138+ case 19 :
139+ rcodes = "Bad TKEY Mode"
140+ case 20 :
141+ rcodes = "Duplicate key name"
142+ case 21 :
143+ rcodes = "Algorithm not supported"
144+ case 22 :
145+ rcodes = "Bad Truncation"
146+ case 23 :
147+ rcodes = "Bad/missing Server Cookie"
130148 default :
131149 rcodes = "Unknown"
132150 }
@@ -144,17 +162,36 @@ func (d *DNSMessage) flags() string {
144162 return flags
145163}
146164
147- func bytesToStr (myBytes []byte ) string {
148- return unsafe .String (unsafe .SliceData (myBytes ), len (myBytes ))
165+ // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2
166+ func (d * DNSMessage ) className (cls uint16 ) string {
167+ var cname string
168+ switch cls {
169+ case 0 :
170+ cname = "Reserved"
171+ case 1 :
172+ cname = "IN"
173+ case 3 :
174+ cname = "CH"
175+ case 4 :
176+ cname = "HS"
177+ default :
178+ cname = "Unknown"
179+ }
180+ return cname
149181}
150182
183+ // extractDomain extracts the DNS domain name from the given byte slice.
184+ //
185+ // The domain name is parsed according to RFC 1035 section 4.1.
151186func (d * DNSMessage ) extractDomain (tail []byte ) (string , []byte ) {
187+ // see https://brunoscheufler.com/blog/2024-05-12-building-a-dns-message-parser#domain-names
152188 var domainName string
153189 for {
154190 blen := tail [0 ]
155191 if blen >> 6 == 0b11 {
192+ // compressed message offset is 14 bits according to RFC 1035 section 4.1.4
156193 offset := binary .BigEndian .Uint16 (tail [0 :2 ])& (1 << 14 - 1 ) - headerSizeDNS
157- part , _ := d .extractDomain (d .payload [offset :])
194+ part , _ := d .extractDomain (d .payload [offset :]) // TODO: iterative approach
158195 domainName += part
159196 tail = tail [2 :]
160197 break
@@ -171,24 +208,27 @@ func (d *DNSMessage) extractDomain(tail []byte) (string, []byte) {
171208 return strings .TrimRight (domainName , "." ), tail
172209}
173210
211+ // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
174212func (d * DNSMessage ) parseRData (typ uint16 , tail []byte , rdl int ) (string , string , []byte ) {
175- var rdata string
176- var typname string
213+ var (
214+ rdata string
215+ typename string
216+ )
177217 switch typ {
178218 case 1 :
179- typname = "A"
219+ typename = "A"
180220 addr , _ := netip .AddrFromSlice (tail [0 :rdl ])
181221 rdata = fmt .Sprintf ("Address: %s" , addr )
182222 case 2 :
183- typname = "NS"
223+ typename = "NS"
184224 domain , _ := d .extractDomain (tail )
185- rdata = fmt .Sprintf ("%s: %s" , typname , domain )
225+ rdata = fmt .Sprintf ("%s: %s" , typename , domain )
186226 case 5 :
187- typname = "CNAME"
227+ typename = "CNAME"
188228 domain , _ := d .extractDomain (tail )
189- rdata = fmt .Sprintf ("%s: %s" , typname , domain )
229+ rdata = fmt .Sprintf ("%s: %s" , typename , domain )
190230 case 6 :
191- typname = "SOA"
231+ typename = "SOA"
192232 var (
193233 primary string
194234 mailbox string
@@ -207,47 +247,52 @@ func (d *DNSMessage) parseRData(typ uint16, tail []byte, rdl int) (string, strin
207247 - Refresh interval: %d
208248 - Retry interval: %d
209249 - Expire limit: %d
210- - Minimum TTL: %d
211- ` , primary , mailbox , serial , refresh , retry , expire , min )
250+ - Minimum TTL: %d` ,
251+ primary , mailbox , serial , refresh , retry , expire , min )
212252 case 15 :
213- typname = "MX"
253+ typename = "MX"
214254 preference := binary .BigEndian .Uint16 (tail [0 :2 ])
215255 domain , _ := d .extractDomain (tail [2 :rdl ])
216- rdata = fmt .Sprintf ("%s: preference %d %s" , typname , preference , domain )
256+ rdata = fmt .Sprintf ("%s: preference %d %s" , typename , preference , domain )
217257 case 16 :
218- typname = "TXT"
219- rdata = fmt .Sprintf ("%s: %s" , typname , tail [:rdl ])
258+ typename = "TXT"
259+ rdata = fmt .Sprintf ("%s: %s" , typename , tail [:rdl ])
220260 case 28 :
221- typname = "AAAA"
261+ typename = "AAAA"
222262 addr , _ := netip .AddrFromSlice (tail [0 :rdl ])
223263 rdata = fmt .Sprintf ("Address: %s" , addr )
224264 case 41 :
225- typname = "OPT"
265+ typename = "OPT"
266+ case 65 :
267+ typename = "HTTPS" // TODO: add proper parsing
268+ rdata = fmt .Sprintf ("%s: %d bytes" , typename , rdl )
226269 default :
227270 rdata = fmt .Sprintf ("Unknown: %d bytes" , rdl )
228271 }
229- return typname , rdata , tail [rdl :]
272+ return typename , rdata , tail [rdl :]
230273}
231274
232275func (d * DNSMessage ) parseQuery (tail []byte ) (string , []byte ) {
233276 var domain string
234277 domain , tail = d .extractDomain (tail )
235278 typ := binary .BigEndian .Uint16 (tail [0 :2 ])
279+ typename , _ , _ := d .parseRData (typ , tail , 0 )
236280 class := binary .BigEndian .Uint16 (tail [2 :4 ])
281+ cname := d .className (class )
237282 tail = tail [4 :]
238- // TODO: add type and class description https://en.wikipedia.org/wiki/List_of_DNS_record_types
239283 return fmt .Sprintf (` - %s:
240284 - Name: %s
241- - Type: (%d)
242- - Class: %d
243- ` , domain , domain , typ , class ), tail
285+ - Type: %s (%d)
286+ - Class: %s (%d)
287+ ` , domain , domain , typename , typ , cname , class ), tail
244288}
245289
246290func (d * DNSMessage ) parseRR (tail []byte ) (string , []byte ) {
247291 var domain string
248292 domain , tail = d .extractDomain (tail )
249293 typ := binary .BigEndian .Uint16 (tail [0 :2 ])
250294 class := binary .BigEndian .Uint16 (tail [2 :4 ])
295+ cname := d .className (class )
251296 ttl := binary .BigEndian .Uint32 (tail [4 :8 ])
252297 rdl := int (binary .BigEndian .Uint16 (tail [8 :10 ]))
253298 var (
@@ -258,11 +303,11 @@ func (d *DNSMessage) parseRR(tail []byte) (string, []byte) {
258303 return fmt .Sprintf (` - %s:
259304 - Name: %s
260305 - Type: %s (%d)
261- - Class: %d
306+ - Class: %s (%d)
262307 - TTL: %d
263308 - Data Length: %d
264309 - %s
265- ` , domain , domain , typename , typ , class , ttl , rdl , rdata ), tail
310+ ` , domain , domain , typename , typ , cname , class , ttl , rdl , rdata ), tail
266311}
267312
268313func (d * DNSMessage ) parseRoot (tail []byte ) (string , []byte ) {
0 commit comments