main
c8b91bc · 4 months ago 7 commits
 1package dns
 2
 3import (
 4	"encoding/binary"
 5	"fmt"
 6	"strings"
 7)
 8
 9// PackDomain encodes a domain name into DNS wire format
10// "ansari.wtf" → [6]ansari[3]wtf[0]
11func PackDomain(domain string) []byte {
12	// Remove trailing dot if present
13	domain = strings.TrimSuffix(domain, ".")
14
15	if domain == "" {
16		return []byte{0} // root
17	}
18
19	var buf []byte
20	labels := strings.SplitSeq(domain, ".")
21	for label := range labels {
22		if len(label) > 63 {
23			panic("label too long") // RFC limit: 63 chars per label
24		}
25		buf = append(buf, byte(len(label)))
26		buf = append(buf, []byte(label)...)
27	}
28	buf = append(buf, 0) // null terminator
29	return buf
30}
31
32// UnpackDomain decodes a DNS name from wire format
33// Returns the name and how many bytes were consumed
34// Also handles compression pointers (0xC0 prefix)
35func UnpackDomain(buf []byte, offset int) (string, int, error) {
36	var labels []string
37	visited := make(map[int]bool) // detect pointer loops
38	origOffset := offset
39	jumped := false
40	jumpOffset := 0
41
42	for {
43		if offset >= len(buf) {
44			return "", 0, fmt.Errorf("name parse out of bounds at offset %d", offset)
45		}
46
47		length := int(buf[offset])
48
49		// Check for compression pointer: top 2 bits are 11 (0xC0)
50		if length&0xC0 == 0xC0 {
51			if offset+1 >= len(buf) {
52				return "", 0, fmt.Errorf("compression pointer out of bounds")
53			}
54			// Pointer is 14-bit offset into the message
55			ptr := int(binary.BigEndian.Uint16(buf[offset:offset+2]) &^ 0xC000)
56
57			if visited[ptr] {
58				return "", 0, fmt.Errorf("compression pointer loop detected")
59			}
60			visited[ptr] = true
61
62			if !jumped {
63				jumpOffset = offset + 2 // after the pointer, this is where we resume
64			}
65			jumped = true
66			offset = ptr
67			continue
68		}
69
70		// Normal label
71		if length == 0 {
72			// End of name
73			offset++ // consume the null byte
74			break
75		}
76
77		offset++ // move past the length byte
78		if offset+length > len(buf) {
79			return "", 0, fmt.Errorf("label out of bounds")
80		}
81		labels = append(labels, string(buf[offset:offset+length]))
82		offset += length
83	}
84
85	name := strings.Join(labels, ".") + "."
86
87	// If we jumped, return the offset after the pointer (2 bytes)
88	// If we didn't jump, return where we ended up
89	consumed := offset - origOffset
90	if jumped {
91		consumed = jumpOffset - origOffset
92	}
93
94	return name, consumed, nil
95}
96