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
```

+79 -37
domain_name.go
···
// decode_domain decodes a domain name from a buffer starting at offset.
// It returns the domain name along with the offset and error.
func decode_domain(buf []byte, offset int) (string, int, error) {
-
labels := make([]string, 0)
+
var builder strings.Builder
+
firstLabel := true
+
seen_offsets := make(map[int]struct{})
-
has_jumped := false
-
prev_offset := 0
+
finalOffsetAfterJump := -1
+
+
currentOffset := offset
-
var length uint8
-
var label []byte
-
var sec byte
-
var err error
for {
-
length, offset, err = getU8(buf, offset)
+
if _, found := seen_offsets[currentOffset]; found {
+
return "", len(buf), &DomainCompressionError{}
+
}
+
seen_offsets[currentOffset] = struct{}{}
+
+
length, nextOffsetAfterLen, err := getU8(buf, currentOffset)
if err != nil {
return "", len(buf), err
}
if length == 0 {
+
currentOffset = nextOffsetAfterLen
break
}
-
if length&0xC0 == 0xC0 {
-
sec, offset, err = getU8(buf, offset)
+
if (length & 0xC0) == 0xC0 {
+
sec, nextOffsetAfterPtr, err := getU8(buf, nextOffsetAfterLen)
if err != nil {
return "", len(buf), err
}
-
if !has_jumped {
-
prev_offset = offset
-
has_jumped = true
-
}
+
jumpTargetOffset := int(length&0x3F)<<8 | int(sec)
-
if _, found := seen_offsets[offset]; found {
+
if jumpTargetOffset >= len(buf) {
+
return "", len(buf), &BufferOverflowError{Length: len(buf), Offset: jumpTargetOffset}
+
}
+
if _, found := seen_offsets[jumpTargetOffset]; found {
return "", len(buf), &DomainCompressionError{}
}
-
seen_offsets[offset] = struct{}{}
-
offset = int(length&0x3F)<<8 | int(sec)
-
} else {
-
if length > 63 {
-
return "", len(buf), &InvalidLabelError{Length: int(length)}
+
if finalOffsetAfterJump == -1 {
+
finalOffsetAfterJump = nextOffsetAfterPtr
}
-
label, offset, err = getSlice(buf, offset, int(length))
-
if err != nil {
-
return "", len(buf), err
-
}
+
currentOffset = jumpTargetOffset
+
continue
+
}
-
labels = append(labels, string(label))
+
if length > 63 {
+
return "", len(buf), &InvalidLabelError{Length: int(length)}
}
+
+
labelBytes, nextOffsetAfterLabel, err := getSlice(buf, nextOffsetAfterLen, int(length))
+
if err != nil {
+
return "", len(buf), err
+
}
+
+
if !firstLabel {
+
builder.WriteByte('.')
+
}
+
builder.Write(labelBytes)
+
firstLabel = false
+
+
currentOffset = nextOffsetAfterLabel
+
}
-
if has_jumped {
-
offset = prev_offset
+
finalReadOffset := currentOffset
+
if finalOffsetAfterJump != -1 {
+
finalReadOffset = finalOffsetAfterJump
}
-
return strings.Join(labels, "."), offset, nil
+
return builder.String(), finalReadOffset, nil
}
// encode_domain returns the bytes of the input bytes appened with the encoded domain name.
func encode_domain(bytes []byte, domain_name string, offsets *map[string]uint16) []byte {
-
pos := uint16(len(bytes))
+
if domain_name == "." || domain_name == "" {
+
return append(bytes, 0)
+
}
-
labels := strings.Split(domain_name, ".")
-
for i, label := range labels {
-
remaining_labels := strings.Join(labels[i:], ".")
+
labelIndices := []int{0}
+
for i, r := range domain_name {
+
if r == '.' {
+
labelIndices = append(labelIndices, i+1)
+
}
+
}
-
if offset, found := (*offsets)[remaining_labels]; found {
+
for i := 0; i < len(labelIndices); i++ {
+
suffix := domain_name[labelIndices[i]:]
+
+
if offset, found := (*offsets)[suffix]; found {
pointer := 0xC000 | offset
return binary.BigEndian.AppendUint16(bytes, pointer)
}
-
(*offsets)[remaining_labels] = pos
-
bytes = append(bytes, uint8(len(label)))
-
bytes = append(bytes, []byte(label)...)
-
pos += 1 + uint16(len(label))
+
currentPos := uint16(len(bytes))
+
if currentPos <= 0x3FFF {
+
(*offsets)[suffix] = currentPos
+
}
+
+
start := labelIndices[i]
+
end := len(domain_name)
+
if i+1 < len(labelIndices) {
+
end = labelIndices[i+1] - 1
+
}
+
labelBytes := []byte(domain_name[start:end])
+
+
if len(labelBytes) == 0 {
+
continue
+
}
+
if len(labelBytes) > 63 {
+
labelBytes = labelBytes[:63]
+
}
+
+
bytes = append(bytes, byte(len(labelBytes)))
+
bytes = append(bytes, labelBytes...)
}
return append(bytes, 0)
+47
domain_test.go
···
"github.com/stretchr/testify/assert"
)
+
func BenchmarkDecodeDomainSimple(b *testing.B) {
+
input := []byte{3, 'w', 'w', 'w', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0}
+
for i := 0; i < b.N; i++ {
+
_, _, _ = decode_domain(input, 0)
+
}
+
}
+
+
func BenchmarkDecodeDomainCompressed(b *testing.B) {
+
input := []byte{
+
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00,
+
0x03, 'f', 'o', 'o', 0xc0, 0x00,
+
}
+
offset := 13
+
b.ResetTimer()
+
for i := 0; i < b.N; i++ {
+
_, _, _ = decode_domain(input, offset)
+
}
+
}
+
+
func BenchmarkEncodeDomainSimple(b *testing.B) {
+
domain := "www.example.com"
+
offsets := make(map[string]uint16)
+
out := make([]byte, 0, 64)
+
b.ResetTimer()
+
for i := 0; i < b.N; i++ {
+
_ = encode_domain(out[:0], domain, &offsets)
+
for k := range offsets {
+
delete(offsets, k)
+
}
+
}
+
}
+
+
func BenchmarkEncodeDomainWithCompression(b *testing.B) {
+
domain1 := "www.example.com"
+
domain2 := "mail.example.com"
+
offsets := make(map[string]uint16)
+
out := make([]byte, 0, 128)
+
b.ResetTimer()
+
for i := 0; i < b.N; i++ {
+
tempOut := encode_domain(out[:0], domain1, &offsets)
+
_ = encode_domain(tempOut, domain2, &offsets)
+
for k := range offsets {
+
delete(offsets, k)
+
}
+
}
+
}
+
func TestDecodeDomain(t *testing.T) {
tests := []struct {
name string
+1 -1
header.go
···
// Encode encodes the header packet to the byte representation.
func (h *Header) Encode() []byte {
-
var bytes []byte
+
bytes := make([]byte, 0, 12)
bytes = binary.BigEndian.AppendUint16(bytes, h.ID)