a go dns packet parser

fix bugs in TXT, HINFO, WKS

- TXT and HINFO are made up of character strings
- WKS record offset is precalculated

+117 -26
resource_record.go
···
}
func (wks *WKS) Decode(buf []byte, offset int, rdlength int) (int, error) {
-
address, offset, err := getSlice(buf, offset, 4)
-
if err != nil {
-
return offset, err
}
-
protocol, offset, err := getU8(buf, offset)
if err != nil {
-
return offset, err
}
-
wks.Address = net.IP(address)
wks.Protocol = protocol
-
wks.BitMap, offset, err = getSlice(buf, offset, offset+int(rdlength)-5)
if err != nil {
-
return offset, err
}
-
return offset, err
}
func (wks *WKS) Encode(bytes []byte, offsets *map[string]uint16) []byte {
···
}
func (hinfo *HINFO) Decode(buf []byte, offset int, rdlength int) (int, error) {
-
input, offset, err := getSlice(buf, offset, int(rdlength))
if err != nil {
-
return offset, err
}
-
parts := strings.SplitN(string(input), " ", 2)
-
if len(parts) != 2 {
-
return offset, &MagnaError{Message: "HINFO expected 2 values separated by a space"}
}
-
hinfo.CPU = parts[0]
-
hinfo.OS = parts[1]
-
return offset, err
}
func (hinfo *HINFO) Encode(bytes []byte, offsets *map[string]uint16) []byte {
bytes = append(bytes, []byte(hinfo.CPU)...)
-
bytes = append(bytes, ' ')
bytes = append(bytes, []byte(hinfo.OS)...)
return bytes
}
func (hinfo HINFO) String() string {
-
return hinfo.OS + " " + hinfo.CPU
}
func (minfo *MINFO) Decode(buf []byte, offset int, rdlength int) (int, error) {
···
}
func (txt *TXT) Decode(buf []byte, offset int, rdlength int) (int, error) {
-
bytes, offset, err := getSlice(buf, offset, rdlength)
-
if err != nil {
-
return offset, err
}
-
txt.TxtData = string(bytes)
-
return offset, err
}
func (txt *TXT) Encode(bytes []byte, offsets *map[string]uint16) []byte {
-
return append(bytes, []byte(txt.TxtData)...)
}
func (txt TXT) String() string {
-
return txt.TxtData
}
func (r *Reserved) Decode(buf []byte, offset int, rdlength int) (int, error) {
···
}
func (wks *WKS) Decode(buf []byte, offset int, rdlength int) (int, error) {
+
if rdlength < 5 {
+
return len(buf), &MagnaError{Message: fmt.Sprintf("magna: WKS RDLENGTH too short: %d", rdlength)}
}
+
addressBytes, nextOffset, err := getSlice(buf, offset, 4)
if err != nil {
+
return len(buf), fmt.Errorf("magna: WKS error reading address: %w", err)
}
+
offset = nextOffset
+
wks.Address = net.IP(addressBytes)
+
protocol, nextOffset, err := getU8(buf, offset)
+
if err != nil {
+
return len(buf), fmt.Errorf("magna: WKS error reading protocol: %w", err)
+
}
+
offset = nextOffset
wks.Protocol = protocol
+
+
bitmapLength := rdlength - 5
+
wks.BitMap, nextOffset, err = getSlice(buf, offset, bitmapLength)
if err != nil {
+
return len(buf), fmt.Errorf("magna: WKS error reading bitmap: %w", err)
}
+
offset = nextOffset
+
return offset, nil
}
func (wks *WKS) Encode(bytes []byte, offsets *map[string]uint16) []byte {
···
}
func (hinfo *HINFO) Decode(buf []byte, offset int, rdlength int) (int, error) {
+
endOffset := offset + rdlength
+
if endOffset > len(buf) {
+
return len(buf), &BufferOverflowError{Length: len(buf), Offset: endOffset}
+
}
+
+
currentOffset := offset
+
var err error
+
+
cpuLen, nextOffset, err := getU8(buf, currentOffset)
if err != nil {
+
return len(buf), fmt.Errorf("magna: HINFO error reading CPU length: %w", err)
+
}
+
currentOffset = nextOffset
+
if currentOffset+int(cpuLen) > endOffset {
+
return len(buf), &BufferOverflowError{Length: len(buf), Offset: currentOffset + int(cpuLen)}
}
+
cpuBytes, nextOffset, err := getSlice(buf, currentOffset, int(cpuLen))
+
if err != nil {
+
return len(buf), fmt.Errorf("magna: HINFO error reading CPU data: %w", err)
+
}
+
currentOffset = nextOffset
+
hinfo.CPU = string(cpuBytes)
+
osLen, nextOffset, err := getU8(buf, currentOffset)
+
if err != nil {
+
if currentOffset == endOffset {
+
return len(buf), &MagnaError{Message: "magna: HINFO missing OS string"}
+
}
+
return len(buf), fmt.Errorf("magna: HINFO error reading OS length: %w", err)
+
}
+
currentOffset = nextOffset
+
if currentOffset+int(osLen) > endOffset {
+
return len(buf), &BufferOverflowError{Length: len(buf), Offset: currentOffset + int(osLen)}
+
}
+
osBytes, nextOffset, err := getSlice(buf, currentOffset, int(osLen))
+
if err != nil {
+
return len(buf), fmt.Errorf("magna: HINFO error reading OS data: %w", err)
+
}
+
currentOffset = nextOffset
+
hinfo.OS = string(osBytes)
+
+
if currentOffset != endOffset {
+
return len(buf), &MagnaError{Message: fmt.Sprintf("magna: HINFO RDATA length mismatch, expected end at %d, ended at %d", endOffset, currentOffset)}
}
+
return currentOffset, nil
}
func (hinfo *HINFO) Encode(bytes []byte, offsets *map[string]uint16) []byte {
+
// XXX: should probally return an error
+
if len(hinfo.CPU) > 255 {
+
hinfo.CPU = hinfo.CPU[:255]
+
}
+
if len(hinfo.OS) > 255 {
+
hinfo.OS = hinfo.OS[:255]
+
}
+
+
bytes = append(bytes, byte(len(hinfo.CPU)))
bytes = append(bytes, []byte(hinfo.CPU)...)
+
bytes = append(bytes, byte(len(hinfo.OS)))
bytes = append(bytes, []byte(hinfo.OS)...)
return bytes
}
func (hinfo HINFO) String() string {
+
return fmt.Sprintf("%q %q", hinfo.CPU, hinfo.OS)
}
func (minfo *MINFO) Decode(buf []byte, offset int, rdlength int) (int, error) {
···
}
func (txt *TXT) Decode(buf []byte, offset int, rdlength int) (int, error) {
+
txt.TxtData = make([]string, 0, 1)
+
endOffset := offset + rdlength
+
if endOffset > len(buf) {
+
return len(buf), &BufferOverflowError{Length: len(buf), Offset: endOffset}
+
}
+
+
currentOffset := offset
+
for currentOffset < endOffset {
+
strLen, nextOffsetAfterLen, err := getU8(buf, currentOffset)
+
if err != nil {
+
return len(buf), fmt.Errorf("magna: error reading TXT string length byte: %w", err)
+
}
+
+
nextOffsetAfterData := nextOffsetAfterLen + int(strLen)
+
if nextOffsetAfterData > endOffset {
+
return len(buf), &MagnaError{
+
Message: fmt.Sprintf("magna: TXT string segment length %d at offset %d exceeds RDLENGTH boundary %d", strLen, nextOffsetAfterLen, endOffset),
+
}
+
}
+
+
strBytes, actualNextOffsetAfterData, err := getSlice(buf, nextOffsetAfterLen, int(strLen))
+
if err != nil {
+
return len(buf), fmt.Errorf("magna: error reading TXT string data: %w", err)
+
}
+
+
txt.TxtData = append(txt.TxtData, string(strBytes))
+
currentOffset = actualNextOffsetAfterData
}
+
if currentOffset != endOffset {
+
return len(buf), &MagnaError{
+
Message: fmt.Sprintf("magna: TXT RDATA parsing finished at offset %d, but expected end at %d based on RDLENGTH", currentOffset, endOffset),
+
}
+
}
+
+
return currentOffset, nil
}
func (txt *TXT) Encode(bytes []byte, offsets *map[string]uint16) []byte {
+
for _, s := range txt.TxtData {
+
if len(s) > 255 {
+
// XXX: should return probably an error
+
s = s[:255]
+
}
+
bytes = append(bytes, byte(len(s)))
+
bytes = append(bytes, []byte(s)...)
+
}
+
return bytes
}
func (txt TXT) String() string {
+
quoted := make([]string, len(txt.TxtData))
+
for i, s := range txt.TxtData {
+
quoted[i] = fmt.Sprintf("%q", s)
+
}
+
return strings.Join(quoted, " ")
}
func (r *Reserved) Decode(buf []byte, offset int, rdlength int) (int, error) {
+92 -1
resource_record_test.go
···
package magna
import (
-
// "bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSOADecodeWithCompression(t *testing.T) {
input := []byte{0x69, 0x7b, 0x81, 0x83, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0xf, 0x6e, 0x6f, 0x77, 0x61, 0x79, 0x74, 0x68, 0x69, 0x73, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x3, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x1, 0x0, 0x1, 0xc0, 0x1c, 0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x3, 0x84, 0x0, 0x3d, 0x1, 0x61, 0xc, 0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x3, 0x6e, 0x65, 0x74, 0x0, 0x5, 0x6e, 0x73, 0x74, 0x6c, 0x64, 0xc, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2d, 0x67, 0x72, 0x73, 0xc0, 0x1c, 0x67, 0xaa, 0xc5, 0x6b, 0x0, 0x0, 0x7, 0x8, 0x0, 0x0, 0x3, 0x84, 0x0, 0x9, 0x3a, 0x80, 0x0, 0x0, 0x3, 0x84}
···
package magna
import (
+
"encoding/binary"
+
"net"
"testing"
"github.com/stretchr/testify/assert"
+
"github.com/stretchr/testify/require"
)
+
+
func TestTXTRecord(t *testing.T) {
+
rdataBytes := []byte{0x03, 'a', 'b', 'c', 0x03, 'd', 'e', 'f'}
+
rdlength := uint16(len(rdataBytes))
+
+
buf := []byte{0x00}
+
buf = binary.BigEndian.AppendUint16(buf, uint16(TXTType))
+
buf = binary.BigEndian.AppendUint16(buf, uint16(IN))
+
buf = binary.BigEndian.AppendUint32(buf, 3600)
+
buf = binary.BigEndian.AppendUint16(buf, rdlength)
+
buf = append(buf, rdataBytes...)
+
+
rr := &ResourceRecord{}
+
offset, err := rr.Decode(buf, 0)
+
require.NoError(t, err)
+
assert.Equal(t, len(buf), offset)
+
require.IsType(t, &TXT{}, rr.RData)
+
+
txtData := rr.RData.(*TXT)
+
+
expectedDecodedData := []string{"abc", "def"}
+
assert.Equal(t, expectedDecodedData, txtData.TxtData, "Decoded TXT data does not match expected concatenation")
+
+
txtToEncode := &TXT{TxtData: []string{"test"}}
+
expectedEncodedRdata := []byte{0x04, 't', 'e', 's', 't'}
+
+
encodeBuf := []byte{}
+
encodedRdata := txtToEncode.Encode(encodeBuf, nil)
+
+
assert.Equal(t, expectedEncodedRdata, encodedRdata, "Encoded TXT RDATA is incorrect")
+
}
+
+
func TestHINFORecordRFCCompliance(t *testing.T) {
+
rdataBytes := []byte{0x03, 'C', 'P', 'U', 0x02, 'O', 'S'}
+
rdlength := uint16(len(rdataBytes))
+
+
buf := []byte{0x00}
+
buf = binary.BigEndian.AppendUint16(buf, uint16(HINFOType))
+
buf = binary.BigEndian.AppendUint16(buf, uint16(IN))
+
buf = binary.BigEndian.AppendUint32(buf, 3600)
+
buf = binary.BigEndian.AppendUint16(buf, rdlength)
+
buf = append(buf, rdataBytes...)
+
+
rr := &ResourceRecord{}
+
offset, err := rr.Decode(buf, 0)
+
require.NoError(t, err)
+
assert.Equal(t, len(buf), offset)
+
require.IsType(t, &HINFO{}, rr.RData)
+
+
hinfoData := rr.RData.(*HINFO)
+
+
assert.Equal(t, "CPU", hinfoData.CPU, "Decoded HINFO CPU does not match")
+
assert.Equal(t, "OS", hinfoData.OS, "Decoded HINFO OS does not match")
+
+
hinfoToEncode := &HINFO{CPU: "Intel", OS: "Linux"}
+
expectedEncodedRdata := []byte{0x05, 'I', 'n', 't', 'e', 'l', 0x05, 'L', 'i', 'n', 'u', 'x'}
+
encodeBuf := []byte{}
+
encodedRdata := hinfoToEncode.Encode(encodeBuf, nil)
+
+
assert.Equal(t, expectedEncodedRdata, encodedRdata, "Encoded HINFO RDATA is incorrect")
+
}
+
+
func TestWKSRecordDecoding(t *testing.T) {
+
addr := net.ParseIP("192.168.1.1").To4()
+
proto := byte(6)
+
bitmap := []byte{0x01, 0x80}
+
rdataBytes := append(addr, proto)
+
rdataBytes = append(rdataBytes, bitmap...)
+
rdlength := uint16(len(rdataBytes))
+
+
buf := []byte{0x00}
+
buf = binary.BigEndian.AppendUint16(buf, uint16(WKSType))
+
buf = binary.BigEndian.AppendUint16(buf, uint16(IN))
+
buf = binary.BigEndian.AppendUint32(buf, 3600)
+
buf = binary.BigEndian.AppendUint16(buf, rdlength)
+
buf = append(buf, rdataBytes...)
+
+
rr := &ResourceRecord{}
+
offset, err := rr.Decode(buf, 0)
+
require.NoError(t, err)
+
assert.Equal(t, len(buf), offset)
+
require.IsType(t, &WKS{}, rr.RData)
+
+
wksData := rr.RData.(*WKS)
+
+
assert.Equal(t, addr, wksData.Address.To4())
+
assert.Equal(t, proto, wksData.Protocol)
+
assert.Equal(t, bitmap, wksData.BitMap)
+
}
func TestSOADecodeWithCompression(t *testing.T) {
input := []byte{0x69, 0x7b, 0x81, 0x83, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0xf, 0x6e, 0x6f, 0x77, 0x61, 0x79, 0x74, 0x68, 0x69, 0x73, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x3, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x1, 0x0, 0x1, 0xc0, 0x1c, 0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x3, 0x84, 0x0, 0x3d, 0x1, 0x61, 0xc, 0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x3, 0x6e, 0x65, 0x74, 0x0, 0x5, 0x6e, 0x73, 0x74, 0x6c, 0x64, 0xc, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2d, 0x67, 0x72, 0x73, 0xc0, 0x1c, 0x67, 0xaa, 0xc5, 0x6b, 0x0, 0x0, 0x7, 0x8, 0x0, 0x0, 0x3, 0x84, 0x0, 0x9, 0x3a, 0x80, 0x0, 0x0, 0x3, 0x84}
+1 -1
types.go
···
// TXT represents one or more character strings.
type TXT struct {
-
TxtData string
}
// Reserved represents a record that is not yet implemented.
···
// TXT represents one or more character strings.
type TXT struct {
+
TxtData []string
}
// Reserved represents a record that is not yet implemented.