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

+210 -28
+117 -26
resource_record.go
··· 226 226 } 227 227 228 228 func (wks *WKS) Decode(buf []byte, offset int, rdlength int) (int, error) { 229 - address, offset, err := getSlice(buf, offset, 4) 230 - if err != nil { 231 - return offset, err 229 + if rdlength < 5 { 230 + return len(buf), &MagnaError{Message: fmt.Sprintf("magna: WKS RDLENGTH too short: %d", rdlength)} 232 231 } 233 232 234 - protocol, offset, err := getU8(buf, offset) 233 + addressBytes, nextOffset, err := getSlice(buf, offset, 4) 235 234 if err != nil { 236 - return offset, err 235 + return len(buf), fmt.Errorf("magna: WKS error reading address: %w", err) 237 236 } 237 + offset = nextOffset 238 + wks.Address = net.IP(addressBytes) 238 239 239 - wks.Address = net.IP(address) 240 + protocol, nextOffset, err := getU8(buf, offset) 241 + if err != nil { 242 + return len(buf), fmt.Errorf("magna: WKS error reading protocol: %w", err) 243 + } 244 + offset = nextOffset 240 245 wks.Protocol = protocol 241 - wks.BitMap, offset, err = getSlice(buf, offset, offset+int(rdlength)-5) 246 + 247 + bitmapLength := rdlength - 5 248 + wks.BitMap, nextOffset, err = getSlice(buf, offset, bitmapLength) 242 249 if err != nil { 243 - return offset, err 250 + return len(buf), fmt.Errorf("magna: WKS error reading bitmap: %w", err) 244 251 } 252 + offset = nextOffset 245 253 246 - return offset, err 254 + return offset, nil 247 255 } 248 256 249 257 func (wks *WKS) Encode(bytes []byte, offsets *map[string]uint16) []byte { ··· 277 285 } 278 286 279 287 func (hinfo *HINFO) Decode(buf []byte, offset int, rdlength int) (int, error) { 280 - input, offset, err := getSlice(buf, offset, int(rdlength)) 288 + endOffset := offset + rdlength 289 + if endOffset > len(buf) { 290 + return len(buf), &BufferOverflowError{Length: len(buf), Offset: endOffset} 291 + } 292 + 293 + currentOffset := offset 294 + var err error 295 + 296 + cpuLen, nextOffset, err := getU8(buf, currentOffset) 281 297 if err != nil { 282 - return offset, err 298 + return len(buf), fmt.Errorf("magna: HINFO error reading CPU length: %w", err) 299 + } 300 + currentOffset = nextOffset 301 + if currentOffset+int(cpuLen) > endOffset { 302 + return len(buf), &BufferOverflowError{Length: len(buf), Offset: currentOffset + int(cpuLen)} 283 303 } 304 + cpuBytes, nextOffset, err := getSlice(buf, currentOffset, int(cpuLen)) 305 + if err != nil { 306 + return len(buf), fmt.Errorf("magna: HINFO error reading CPU data: %w", err) 307 + } 308 + currentOffset = nextOffset 309 + hinfo.CPU = string(cpuBytes) 284 310 285 - parts := strings.SplitN(string(input), " ", 2) 286 - if len(parts) != 2 { 287 - return offset, &MagnaError{Message: "HINFO expected 2 values separated by a space"} 311 + osLen, nextOffset, err := getU8(buf, currentOffset) 312 + if err != nil { 313 + if currentOffset == endOffset { 314 + return len(buf), &MagnaError{Message: "magna: HINFO missing OS string"} 315 + } 316 + return len(buf), fmt.Errorf("magna: HINFO error reading OS length: %w", err) 317 + } 318 + currentOffset = nextOffset 319 + if currentOffset+int(osLen) > endOffset { 320 + return len(buf), &BufferOverflowError{Length: len(buf), Offset: currentOffset + int(osLen)} 321 + } 322 + osBytes, nextOffset, err := getSlice(buf, currentOffset, int(osLen)) 323 + if err != nil { 324 + return len(buf), fmt.Errorf("magna: HINFO error reading OS data: %w", err) 325 + } 326 + currentOffset = nextOffset 327 + hinfo.OS = string(osBytes) 328 + 329 + if currentOffset != endOffset { 330 + return len(buf), &MagnaError{Message: fmt.Sprintf("magna: HINFO RDATA length mismatch, expected end at %d, ended at %d", endOffset, currentOffset)} 288 331 } 289 332 290 - hinfo.CPU = parts[0] 291 - hinfo.OS = parts[1] 292 - return offset, err 333 + return currentOffset, nil 293 334 } 294 335 295 336 func (hinfo *HINFO) Encode(bytes []byte, offsets *map[string]uint16) []byte { 337 + // XXX: should probally return an error 338 + if len(hinfo.CPU) > 255 { 339 + hinfo.CPU = hinfo.CPU[:255] 340 + } 341 + if len(hinfo.OS) > 255 { 342 + hinfo.OS = hinfo.OS[:255] 343 + } 344 + 345 + bytes = append(bytes, byte(len(hinfo.CPU))) 296 346 bytes = append(bytes, []byte(hinfo.CPU)...) 297 - bytes = append(bytes, ' ') 347 + bytes = append(bytes, byte(len(hinfo.OS))) 298 348 bytes = append(bytes, []byte(hinfo.OS)...) 299 349 return bytes 300 350 } 301 351 302 352 func (hinfo HINFO) String() string { 303 - return hinfo.OS + " " + hinfo.CPU 353 + return fmt.Sprintf("%q %q", hinfo.CPU, hinfo.OS) 304 354 } 305 355 306 356 func (minfo *MINFO) Decode(buf []byte, offset int, rdlength int) (int, error) { ··· 357 407 } 358 408 359 409 func (txt *TXT) Decode(buf []byte, offset int, rdlength int) (int, error) { 360 - bytes, offset, err := getSlice(buf, offset, rdlength) 361 - if err != nil { 362 - return offset, err 410 + txt.TxtData = make([]string, 0, 1) 411 + endOffset := offset + rdlength 412 + if endOffset > len(buf) { 413 + return len(buf), &BufferOverflowError{Length: len(buf), Offset: endOffset} 414 + } 415 + 416 + currentOffset := offset 417 + for currentOffset < endOffset { 418 + strLen, nextOffsetAfterLen, err := getU8(buf, currentOffset) 419 + if err != nil { 420 + return len(buf), fmt.Errorf("magna: error reading TXT string length byte: %w", err) 421 + } 422 + 423 + nextOffsetAfterData := nextOffsetAfterLen + int(strLen) 424 + if nextOffsetAfterData > endOffset { 425 + return len(buf), &MagnaError{ 426 + Message: fmt.Sprintf("magna: TXT string segment length %d at offset %d exceeds RDLENGTH boundary %d", strLen, nextOffsetAfterLen, endOffset), 427 + } 428 + } 429 + 430 + strBytes, actualNextOffsetAfterData, err := getSlice(buf, nextOffsetAfterLen, int(strLen)) 431 + if err != nil { 432 + return len(buf), fmt.Errorf("magna: error reading TXT string data: %w", err) 433 + } 434 + 435 + txt.TxtData = append(txt.TxtData, string(strBytes)) 436 + currentOffset = actualNextOffsetAfterData 363 437 } 364 438 365 - txt.TxtData = string(bytes) 366 - return offset, err 439 + if currentOffset != endOffset { 440 + return len(buf), &MagnaError{ 441 + Message: fmt.Sprintf("magna: TXT RDATA parsing finished at offset %d, but expected end at %d based on RDLENGTH", currentOffset, endOffset), 442 + } 443 + } 444 + 445 + return currentOffset, nil 367 446 } 368 447 369 448 func (txt *TXT) Encode(bytes []byte, offsets *map[string]uint16) []byte { 370 - return append(bytes, []byte(txt.TxtData)...) 449 + for _, s := range txt.TxtData { 450 + if len(s) > 255 { 451 + // XXX: should return probably an error 452 + s = s[:255] 453 + } 454 + bytes = append(bytes, byte(len(s))) 455 + bytes = append(bytes, []byte(s)...) 456 + } 457 + return bytes 371 458 } 372 459 373 460 func (txt TXT) String() string { 374 - return txt.TxtData 461 + quoted := make([]string, len(txt.TxtData)) 462 + for i, s := range txt.TxtData { 463 + quoted[i] = fmt.Sprintf("%q", s) 464 + } 465 + return strings.Join(quoted, " ") 375 466 } 376 467 377 468 func (r *Reserved) Decode(buf []byte, offset int, rdlength int) (int, error) {
+92 -1
resource_record_test.go
··· 1 1 package magna 2 2 3 3 import ( 4 - // "bytes" 4 + "encoding/binary" 5 + "net" 5 6 "testing" 6 7 7 8 "github.com/stretchr/testify/assert" 9 + "github.com/stretchr/testify/require" 8 10 ) 11 + 12 + func TestTXTRecord(t *testing.T) { 13 + rdataBytes := []byte{0x03, 'a', 'b', 'c', 0x03, 'd', 'e', 'f'} 14 + rdlength := uint16(len(rdataBytes)) 15 + 16 + buf := []byte{0x00} 17 + buf = binary.BigEndian.AppendUint16(buf, uint16(TXTType)) 18 + buf = binary.BigEndian.AppendUint16(buf, uint16(IN)) 19 + buf = binary.BigEndian.AppendUint32(buf, 3600) 20 + buf = binary.BigEndian.AppendUint16(buf, rdlength) 21 + buf = append(buf, rdataBytes...) 22 + 23 + rr := &ResourceRecord{} 24 + offset, err := rr.Decode(buf, 0) 25 + require.NoError(t, err) 26 + assert.Equal(t, len(buf), offset) 27 + require.IsType(t, &TXT{}, rr.RData) 28 + 29 + txtData := rr.RData.(*TXT) 30 + 31 + expectedDecodedData := []string{"abc", "def"} 32 + assert.Equal(t, expectedDecodedData, txtData.TxtData, "Decoded TXT data does not match expected concatenation") 33 + 34 + txtToEncode := &TXT{TxtData: []string{"test"}} 35 + expectedEncodedRdata := []byte{0x04, 't', 'e', 's', 't'} 36 + 37 + encodeBuf := []byte{} 38 + encodedRdata := txtToEncode.Encode(encodeBuf, nil) 39 + 40 + assert.Equal(t, expectedEncodedRdata, encodedRdata, "Encoded TXT RDATA is incorrect") 41 + } 42 + 43 + func TestHINFORecordRFCCompliance(t *testing.T) { 44 + rdataBytes := []byte{0x03, 'C', 'P', 'U', 0x02, 'O', 'S'} 45 + rdlength := uint16(len(rdataBytes)) 46 + 47 + buf := []byte{0x00} 48 + buf = binary.BigEndian.AppendUint16(buf, uint16(HINFOType)) 49 + buf = binary.BigEndian.AppendUint16(buf, uint16(IN)) 50 + buf = binary.BigEndian.AppendUint32(buf, 3600) 51 + buf = binary.BigEndian.AppendUint16(buf, rdlength) 52 + buf = append(buf, rdataBytes...) 53 + 54 + rr := &ResourceRecord{} 55 + offset, err := rr.Decode(buf, 0) 56 + require.NoError(t, err) 57 + assert.Equal(t, len(buf), offset) 58 + require.IsType(t, &HINFO{}, rr.RData) 59 + 60 + hinfoData := rr.RData.(*HINFO) 61 + 62 + assert.Equal(t, "CPU", hinfoData.CPU, "Decoded HINFO CPU does not match") 63 + assert.Equal(t, "OS", hinfoData.OS, "Decoded HINFO OS does not match") 64 + 65 + hinfoToEncode := &HINFO{CPU: "Intel", OS: "Linux"} 66 + expectedEncodedRdata := []byte{0x05, 'I', 'n', 't', 'e', 'l', 0x05, 'L', 'i', 'n', 'u', 'x'} 67 + encodeBuf := []byte{} 68 + encodedRdata := hinfoToEncode.Encode(encodeBuf, nil) 69 + 70 + assert.Equal(t, expectedEncodedRdata, encodedRdata, "Encoded HINFO RDATA is incorrect") 71 + } 72 + 73 + func TestWKSRecordDecoding(t *testing.T) { 74 + addr := net.ParseIP("192.168.1.1").To4() 75 + proto := byte(6) 76 + bitmap := []byte{0x01, 0x80} 77 + rdataBytes := append(addr, proto) 78 + rdataBytes = append(rdataBytes, bitmap...) 79 + rdlength := uint16(len(rdataBytes)) 80 + 81 + buf := []byte{0x00} 82 + buf = binary.BigEndian.AppendUint16(buf, uint16(WKSType)) 83 + buf = binary.BigEndian.AppendUint16(buf, uint16(IN)) 84 + buf = binary.BigEndian.AppendUint32(buf, 3600) 85 + buf = binary.BigEndian.AppendUint16(buf, rdlength) 86 + buf = append(buf, rdataBytes...) 87 + 88 + rr := &ResourceRecord{} 89 + offset, err := rr.Decode(buf, 0) 90 + require.NoError(t, err) 91 + assert.Equal(t, len(buf), offset) 92 + require.IsType(t, &WKS{}, rr.RData) 93 + 94 + wksData := rr.RData.(*WKS) 95 + 96 + assert.Equal(t, addr, wksData.Address.To4()) 97 + assert.Equal(t, proto, wksData.Protocol) 98 + assert.Equal(t, bitmap, wksData.BitMap) 99 + } 9 100 10 101 func TestSOADecodeWithCompression(t *testing.T) { 11 102 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
··· 317 317 318 318 // TXT represents one or more character strings. 319 319 type TXT struct { 320 - TxtData string 320 + TxtData []string 321 321 } 322 322 323 323 // Reserved represents a record that is not yet implemented.