a go dns packet parser
1package magna 2 3import ( 4 "encoding/binary" 5 "fmt" 6 "strings" 7) 8 9// decodeDomain decodes a domain name from a buffer starting at offset. 10// It returns the domain name along with the offset and error. 11func decodeDomain(buf []byte, offset int) (string, int, error) { 12 var builder strings.Builder 13 firstLabel := true 14 15 seenOffsets := make(map[int]struct{}) 16 finalOffsetAfterJump := -1 17 18 currentOffset := offset 19 20 for { 21 if _, found := seenOffsets[currentOffset]; found { 22 return "", len(buf), &DomainCompressionError{} 23 } 24 seenOffsets[currentOffset] = struct{}{} 25 26 length, nextOffsetAfterLen, err := getU8(buf, currentOffset) 27 if err != nil { 28 return "", len(buf), fmt.Errorf("failed to read domain label length: %w", err) 29 } 30 31 if length == 0 { 32 currentOffset = nextOffsetAfterLen 33 break 34 } 35 36 if (length & 0xC0) == 0xC0 { 37 sec, nextOffsetAfterPtr, err := getU8(buf, nextOffsetAfterLen) 38 if err != nil { 39 return "", len(buf), fmt.Errorf("failed to read domain compression pointer offset byte: %w", err) 40 } 41 42 jumpTargetOffset := int(length&0x3F)<<8 | int(sec) 43 44 if jumpTargetOffset >= len(buf) { 45 return "", len(buf), &BufferOverflowError{Length: len(buf), Offset: jumpTargetOffset} 46 } 47 if _, found := seenOffsets[jumpTargetOffset]; found { 48 return "", len(buf), &DomainCompressionError{} 49 } 50 51 if finalOffsetAfterJump == -1 { 52 finalOffsetAfterJump = nextOffsetAfterPtr 53 } 54 55 currentOffset = jumpTargetOffset 56 continue 57 } 58 59 if length > 63 { 60 return "", len(buf), &InvalidLabelError{Length: int(length)} 61 } 62 63 labelBytes, nextOffsetAfterLabel, err := getSlice(buf, nextOffsetAfterLen, int(length)) 64 if err != nil { 65 return "", len(buf), fmt.Errorf("failed to read domain label data: %w", err) 66 } 67 68 if !firstLabel { 69 builder.WriteByte('.') 70 } 71 builder.Write(labelBytes) 72 firstLabel = false 73 74 currentOffset = nextOffsetAfterLabel 75 76 } 77 78 finalReadOffset := currentOffset 79 if finalOffsetAfterJump != -1 { 80 finalReadOffset = finalOffsetAfterJump 81 } 82 83 return builder.String(), finalReadOffset, nil 84} 85 86// encodeDomain returns the bytes of the input bytes appened with the encoded domain name. 87func encodeDomain(bytes []byte, domainName string, offsets *map[string]uint16) ([]byte, error) { 88 if domainName == "." || domainName == "" { 89 return append(bytes, 0), nil 90 } 91 92 cleanDomain := strings.TrimSuffix(domainName, ".") 93 if cleanDomain == "" { 94 return append(bytes, 0), nil 95 } 96 97 start := 0 98 for start < len(cleanDomain) { 99 suffix := cleanDomain[start:] 100 101 if offset, found := (*offsets)[suffix]; found { 102 pointer := 0xC000 | offset 103 bytes = binary.BigEndian.AppendUint16(bytes, pointer) 104 return bytes, nil 105 } 106 107 currentPos := uint16(len(bytes)) 108 if currentPos <= 0x3FFF { 109 (*offsets)[suffix] = currentPos 110 } 111 112 end := strings.IndexByte(suffix, '.') 113 var label string 114 nextStart := len(cleanDomain) 115 116 if end == -1 { 117 label = suffix 118 start = nextStart 119 } else { 120 label = suffix[:end] 121 nextStart = start + end + 1 122 start = nextStart 123 } 124 125 labelBytes := []byte(label) 126 127 if len(labelBytes) > 63 { 128 return nil, &InvalidLabelError{Length: int(len(labelBytes))} 129 } 130 131 if len(labelBytes) == 0 && start < len(cleanDomain) { 132 return nil, &InvalidLabelError{Length: 0} 133 } 134 135 bytes = append(bytes, byte(len(labelBytes))) 136 bytes = append(bytes, labelBytes...) 137 } 138 139 bytes = append(bytes, 0) 140 return bytes, nil 141}