a go dns packet parser

fix performance issues in domain_name

Adds bench marking for encode and decode domain name.
Before:
```
goos: darwin
goarch: arm64
pkg: tangled.sh/seiso.moe/magna
cpu: Apple M2
BenchmarkDecodeDomainSimple-8 8165782 130.5 ns/op
BenchmarkDecodeDomainCompressed-8 8700873 137.7 ns/op
BenchmarkEncodeDomainSimple-8 7649919 157.4 ns/op
BenchmarkEncodeDomainWithCompression-8 4262730 280.7 ns/op
PASS
ok tangled.sh/seiso.moe/magna 5.625s
```
AfterL
```
goos: darwin
goarch: arm64
pkg: tangled.sh/seiso.moe/magna
cpu: Apple M2
BenchmarkDecodeDomainSimple-8 15590142 77.35 ns/op
BenchmarkDecodeDomainCompressed-8 13225490 89.91 ns/op
BenchmarkEncodeDomainSimple-8 11666162 103.2 ns/op
BenchmarkEncodeDomainWithCompression-8 6769450 176.7 ns/op
PASS
ok tangled.sh/seiso.moe/magna 6.464s
```

+127 -38
+79 -37
domain_name.go
··· 8 8 // decode_domain decodes a domain name from a buffer starting at offset. 9 9 // It returns the domain name along with the offset and error. 10 10 func decode_domain(buf []byte, offset int) (string, int, error) { 11 - labels := make([]string, 0) 11 + var builder strings.Builder 12 + firstLabel := true 13 + 12 14 seen_offsets := make(map[int]struct{}) 13 - has_jumped := false 14 - prev_offset := 0 15 + finalOffsetAfterJump := -1 16 + 17 + currentOffset := offset 15 18 16 - var length uint8 17 - var label []byte 18 - var sec byte 19 - var err error 20 19 for { 21 - length, offset, err = getU8(buf, offset) 20 + if _, found := seen_offsets[currentOffset]; found { 21 + return "", len(buf), &DomainCompressionError{} 22 + } 23 + seen_offsets[currentOffset] = struct{}{} 24 + 25 + length, nextOffsetAfterLen, err := getU8(buf, currentOffset) 22 26 if err != nil { 23 27 return "", len(buf), err 24 28 } 25 29 26 30 if length == 0 { 31 + currentOffset = nextOffsetAfterLen 27 32 break 28 33 } 29 34 30 - if length&0xC0 == 0xC0 { 31 - sec, offset, err = getU8(buf, offset) 35 + if (length & 0xC0) == 0xC0 { 36 + sec, nextOffsetAfterPtr, err := getU8(buf, nextOffsetAfterLen) 32 37 if err != nil { 33 38 return "", len(buf), err 34 39 } 35 40 36 - if !has_jumped { 37 - prev_offset = offset 38 - has_jumped = true 39 - } 41 + jumpTargetOffset := int(length&0x3F)<<8 | int(sec) 40 42 41 - if _, found := seen_offsets[offset]; found { 43 + if jumpTargetOffset >= len(buf) { 44 + return "", len(buf), &BufferOverflowError{Length: len(buf), Offset: jumpTargetOffset} 45 + } 46 + if _, found := seen_offsets[jumpTargetOffset]; found { 42 47 return "", len(buf), &DomainCompressionError{} 43 48 } 44 49 45 - seen_offsets[offset] = struct{}{} 46 - offset = int(length&0x3F)<<8 | int(sec) 47 - } else { 48 - if length > 63 { 49 - return "", len(buf), &InvalidLabelError{Length: int(length)} 50 + if finalOffsetAfterJump == -1 { 51 + finalOffsetAfterJump = nextOffsetAfterPtr 50 52 } 51 53 52 - label, offset, err = getSlice(buf, offset, int(length)) 53 - if err != nil { 54 - return "", len(buf), err 55 - } 54 + currentOffset = jumpTargetOffset 55 + continue 56 + } 56 57 57 - labels = append(labels, string(label)) 58 + if length > 63 { 59 + return "", len(buf), &InvalidLabelError{Length: int(length)} 58 60 } 61 + 62 + labelBytes, nextOffsetAfterLabel, err := getSlice(buf, nextOffsetAfterLen, int(length)) 63 + if err != nil { 64 + return "", len(buf), err 65 + } 66 + 67 + if !firstLabel { 68 + builder.WriteByte('.') 69 + } 70 + builder.Write(labelBytes) 71 + firstLabel = false 72 + 73 + currentOffset = nextOffsetAfterLabel 74 + 59 75 } 60 76 61 - if has_jumped { 62 - offset = prev_offset 77 + finalReadOffset := currentOffset 78 + if finalOffsetAfterJump != -1 { 79 + finalReadOffset = finalOffsetAfterJump 63 80 } 64 81 65 - return strings.Join(labels, "."), offset, nil 82 + return builder.String(), finalReadOffset, nil 66 83 } 67 84 68 85 // encode_domain returns the bytes of the input bytes appened with the encoded domain name. 69 86 func encode_domain(bytes []byte, domain_name string, offsets *map[string]uint16) []byte { 70 - pos := uint16(len(bytes)) 87 + if domain_name == "." || domain_name == "" { 88 + return append(bytes, 0) 89 + } 71 90 72 - labels := strings.Split(domain_name, ".") 73 - for i, label := range labels { 74 - remaining_labels := strings.Join(labels[i:], ".") 91 + labelIndices := []int{0} 92 + for i, r := range domain_name { 93 + if r == '.' { 94 + labelIndices = append(labelIndices, i+1) 95 + } 96 + } 75 97 76 - if offset, found := (*offsets)[remaining_labels]; found { 98 + for i := 0; i < len(labelIndices); i++ { 99 + suffix := domain_name[labelIndices[i]:] 100 + 101 + if offset, found := (*offsets)[suffix]; found { 77 102 pointer := 0xC000 | offset 78 103 return binary.BigEndian.AppendUint16(bytes, pointer) 79 104 } 80 105 81 - (*offsets)[remaining_labels] = pos 82 - bytes = append(bytes, uint8(len(label))) 83 - bytes = append(bytes, []byte(label)...) 84 - pos += 1 + uint16(len(label)) 106 + currentPos := uint16(len(bytes)) 107 + if currentPos <= 0x3FFF { 108 + (*offsets)[suffix] = currentPos 109 + } 110 + 111 + start := labelIndices[i] 112 + end := len(domain_name) 113 + if i+1 < len(labelIndices) { 114 + end = labelIndices[i+1] - 1 115 + } 116 + labelBytes := []byte(domain_name[start:end]) 117 + 118 + if len(labelBytes) == 0 { 119 + continue 120 + } 121 + if len(labelBytes) > 63 { 122 + labelBytes = labelBytes[:63] 123 + } 124 + 125 + bytes = append(bytes, byte(len(labelBytes))) 126 + bytes = append(bytes, labelBytes...) 85 127 } 86 128 87 129 return append(bytes, 0)
+47
domain_test.go
··· 6 6 "github.com/stretchr/testify/assert" 7 7 ) 8 8 9 + func BenchmarkDecodeDomainSimple(b *testing.B) { 10 + input := []byte{3, 'w', 'w', 'w', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0} 11 + for i := 0; i < b.N; i++ { 12 + _, _, _ = decode_domain(input, 0) 13 + } 14 + } 15 + 16 + func BenchmarkDecodeDomainCompressed(b *testing.B) { 17 + input := []byte{ 18 + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, 19 + 0x03, 'f', 'o', 'o', 0xc0, 0x00, 20 + } 21 + offset := 13 22 + b.ResetTimer() 23 + for i := 0; i < b.N; i++ { 24 + _, _, _ = decode_domain(input, offset) 25 + } 26 + } 27 + 28 + func BenchmarkEncodeDomainSimple(b *testing.B) { 29 + domain := "www.example.com" 30 + offsets := make(map[string]uint16) 31 + out := make([]byte, 0, 64) 32 + b.ResetTimer() 33 + for i := 0; i < b.N; i++ { 34 + _ = encode_domain(out[:0], domain, &offsets) 35 + for k := range offsets { 36 + delete(offsets, k) 37 + } 38 + } 39 + } 40 + 41 + func BenchmarkEncodeDomainWithCompression(b *testing.B) { 42 + domain1 := "www.example.com" 43 + domain2 := "mail.example.com" 44 + offsets := make(map[string]uint16) 45 + out := make([]byte, 0, 128) 46 + b.ResetTimer() 47 + for i := 0; i < b.N; i++ { 48 + tempOut := encode_domain(out[:0], domain1, &offsets) 49 + _ = encode_domain(tempOut, domain2, &offsets) 50 + for k := range offsets { 51 + delete(offsets, k) 52 + } 53 + } 54 + } 55 + 9 56 func TestDecodeDomain(t *testing.T) { 10 57 tests := []struct { 11 58 name string
+1 -1
header.go
··· 49 49 50 50 // Encode encodes the header packet to the byte representation. 51 51 func (h *Header) Encode() []byte { 52 - var bytes []byte 52 + bytes := make([]byte, 0, 12) 53 53 54 54 bytes = binary.BigEndian.AppendUint16(bytes, h.ID) 55 55