1
A DNS server, from scratch, in Go[github]
1
2
A DNS server built from scratch in Go with no DNS libraries — it
3
parses and serializes raw DNS packets byte by byte, across the
4
header, question, and answer sections.
5
6
Handles DNS name compression (pointer labels), bit-packed header
7
flags, and recursive forwarding to an upstream resolver.
8
Completed end-to-end as the CodeCrafters 'Build Your Own DNS
9
server' challenge.
10
11
12
14
15
func parseQuestionName(pointer int, data []byte) (string, int) {
16
labels := []string{}
17
for {
18
first := data[pointer]
19
pointer++
20
// if null byte - end
21
if first == 0 {
22
break
23
}
24
// if redirect - read labels from there
25
if (first & 0b11000000) == 0b11000000 {
26
second := data[pointer]
27
pointer++
28
redirectAddress := binary.BigEndian.Uint16([]byte{first & 0b00111111, second})
29
question, _ := parseQuestionName(int(redirectAddress), data)
30
labels = append(labels, question)
31
break
32
}
33
// else read from here
34
label := data[pointer : pointer+int(first)]
35
pointer = pointer + int(first)
36
labels = append(labels, string(label))
37
}
38
39
return strings.Join(labels, "."), pointer
40
}
func parseQuestionName(pointer int, data []byte) (string, int) {
labels := []string{}
for {
first := data[pointer]
pointer++
// if null byte - end
if first == 0 {
break
}
// if redirect - read labels from there
if (first & 0b11000000) == 0b11000000 {
second := data[pointer]
pointer++
redirectAddress := binary.BigEndian.Uint16([]byte{first & 0b00111111, second})
question, _ := parseQuestionName(int(redirectAddress), data)
labels = append(labels, question)
break
}
// else read from here
label := data[pointer : pointer+int(first)]
pointer = pointer + int(first)
labels = append(labels, string(label))
}
return strings.Join(labels, "."), pointer
}
func (h *Header) SetFlags(qr, opcode, aa, tc, rd, ra, z, rcode uint16) {
h.Flags = qr<<15 | opcode<<11 | aa<<10 | tc<<9 | rd<<8 | ra<<7 | z<<4 | rcode
}
func (h Header) Opcode() uint16 {
return (h.Flags >> 11) & 0xF
}
func (h Header) Rd() uint16 {
return (h.Flags >> 8) & 0x1
}
func (h Header) Rcode() uint16 {
return h.Flags & 0xF
}
func parseAnswerName(pointer int, data []byte) (string, int) {
labels := []string{}
for {
first := data[pointer]
pointer++
// if null byte - end
if first == 0 {
break
}
// if redirect - read labels from there
if (first & 0b11000000) == 0b11000000 {
second := data[pointer]
pointer++
redirectAddress := binary.BigEndian.Uint16([]byte{first & 0b00111111, second})
question, _ := parseAnswerName(int(redirectAddress), data)
labels = append(labels, question)
break
}
// else read from here
label := data[pointer : pointer+int(first)]
pointer = pointer + int(first)
labels = append(labels, string(label))
}
return strings.Join(labels, "."), pointer
}
func (q Answer) Bytes() []byte {
labelsBytes := []byte{}
for label := range strings.SplitSeq(q.Name, ".") {
labelsBytes = append(labelsBytes, byte(len(label)))
labelsBytes = append(labelsBytes, []byte(label)...)
}
labelsBytes = append(labelsBytes, byte(0))
// ANAME + TYPE (2 bytes) + CLASS (2 bytes) + TTL (4 bytes) + RDLENGTH (2 bytes)
res := make([]byte, len(labelsBytes)+10)
copy(res, labelsBytes)
binary.BigEndian.PutUint16(res[len(labelsBytes):], q.Type)
binary.BigEndian.PutUint16(res[len(labelsBytes)+2:], q.Class)
binary.BigEndian.PutUint32(res[len(labelsBytes)+4:], q.TTL)
binary.BigEndian.PutUint16(res[len(labelsBytes)+8:], q.Length)
for ns := range strings.SplitSeq(q.Data, ".") {
n, _ := strconv.Atoi(ns)
res = append(res, byte(n))
}
return res
}