Skip to content

Commit

Permalink
Stop using util.Decode to decode header data
Browse files Browse the repository at this point in the history
util.Decode calls binary.Read in a loop and each call allocates a byte
slice. Instead we can use binary.BigEndian methods directly.

Signed-off-by: Antonin Bas <[email protected]>
  • Loading branch information
antoninbas committed Jan 10, 2025
1 parent 30a675c commit 6aea8f6
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 93 deletions.
163 changes: 112 additions & 51 deletions pkg/collector/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (

"github.com/vmware/go-ipfix/pkg/entities"
"github.com/vmware/go-ipfix/pkg/registry"
"github.com/vmware/go-ipfix/pkg/util"
)

// DecodingMode specifies how unknown information elements (in templates) are handled when decoding.
Expand Down Expand Up @@ -216,11 +215,36 @@ func (cp *CollectingProcess) incrementReceivedStats(numMessages, numTemplateSets
cp.numOfDataRecordsReceived += numDataRecords
}

func decodeMessageHeader(buf *bytes.Buffer, version *uint16, length *uint16, exportTime *uint32, sequenceNum *uint32, obsDomainID *uint32) error {
data := buf.Next(entities.MsgHeaderLength)
if len(data) < entities.MsgHeaderLength {
return fmt.Errorf("buffer too short")
}
bigEndian := binary.BigEndian
*version = bigEndian.Uint16(data)
*length = bigEndian.Uint16(data[2:])
*exportTime = bigEndian.Uint32(data[4:])
*sequenceNum = bigEndian.Uint32(data[8:])
*obsDomainID = bigEndian.Uint32(data[12:])
return nil
}

func decodeSetHeader(buf *bytes.Buffer, setID, setLen *uint16) error {
data := buf.Next(entities.SetHeaderLen)
if len(data) < entities.SetHeaderLen {
return fmt.Errorf("buffer too short")
}
bigEndian := binary.BigEndian
*setID = bigEndian.Uint16(data)
*setLen = bigEndian.Uint16(data[2:])
return nil
}

func (cp *CollectingProcess) decodePacket(session *transportSession, packetBuffer *bytes.Buffer, exportAddress string) (*entities.Message, error) {
var length, version, setID, setLen uint16
var exportTime, sequencNum, obsDomainID uint32
if err := util.Decode(packetBuffer, binary.BigEndian, &version, &length, &exportTime, &sequencNum, &obsDomainID, &setID, &setLen); err != nil {
return nil, err
var exportTime, sequenceNum, obsDomainID uint32
if err := decodeMessageHeader(packetBuffer, &version, &length, &exportTime, &sequenceNum, &obsDomainID); err != nil {
return nil, fmt.Errorf("failed to decode IPFIX message header: %w", err)
}
if version != uint16(10) {
return nil, fmt.Errorf("collector only supports IPFIX (v10); invalid version %d received", version)
Expand All @@ -230,7 +254,7 @@ func (cp *CollectingProcess) decodePacket(session *transportSession, packetBuffe
message.SetVersion(version)
message.SetMessageLen(length)
message.SetExportTime(exportTime)
message.SetSequenceNum(sequencNum)
message.SetSequenceNum(sequenceNum)
message.SetObsDomainID(obsDomainID)

// handle IPv6 address which may involve []
Expand All @@ -240,6 +264,14 @@ func (cp *CollectingProcess) decodePacket(session *transportSession, packetBuffe
exportAddress = strings.Replace(exportAddress, "]", "", -1)
message.SetExportAddress(exportAddress)

// At the moment we assume exactly one set per IPFIX message.
if packetBuffer.Len() == 0 {
return nil, fmt.Errorf("empty IPFIX message")
}
if err := decodeSetHeader(packetBuffer, &setID, &setLen); err != nil {
return nil, fmt.Errorf("failed to decode set header: %w", err)
}

var numTemplateSets, numDataSets, numTemplateRecords, numDataRecords uint64

var set entities.Set
Expand Down Expand Up @@ -268,28 +300,74 @@ func (cp *CollectingProcess) decodePacket(session *transportSession, packetBuffe
return message, nil
}

func decodeTemplateRecordHeader(buf *bytes.Buffer, templateID, fieldCount *uint16) error {
data := buf.Next(entities.TemplateRecordHeaderLength)
if len(data) < entities.TemplateRecordHeaderLength {
return fmt.Errorf("buffer too short")
}
bigEndian := binary.BigEndian
*templateID = bigEndian.Uint16(data)
*fieldCount = bigEndian.Uint16(data[2:])
return nil
}

func decodeTemplateRecordField(buf *bytes.Buffer, elementID, elementLength *uint16, enterpriseID *uint32) error {
data := buf.Next(4)
if len(data) < 4 {
return fmt.Errorf("buffer too short")
}
bigEndian := binary.BigEndian
*elementID = bigEndian.Uint16(data)
*elementLength = bigEndian.Uint16(data[2:])
// check whether enterprise ID is 0 or not
isNonIANARegistry := (*elementID & 0x8000) > 0
if isNonIANARegistry {
/*
Encoding format for Enterprise-Specific Information Elements:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1| Information element id. = 15 | Field Length = 4 (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Enterprise number (32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1: 1 bit
Information element id: 15 bits
Field Length: 16 bits
Enterprise ID: 32 bits
(Reference: https://tools.ietf.org/html/rfc7011#appendix-A.2.2)
*/
data = buf.Next(4)
if len(data) < 4 {
return fmt.Errorf("buffer too short")
}
*enterpriseID = bigEndian.Uint32(data)
// clear enterprise bit
*elementID &= 0x7fff
} else {
*enterpriseID = registry.IANAEnterpriseID
}
return nil
}

func (cp *CollectingProcess) decodeTemplateSet(session *transportSession, templateBuffer *bytes.Buffer, obsDomainID uint32) (entities.Set, error) {
// At the moment we assume exactly one record per template set.
var templateID uint16
var fieldCount uint16
if err := util.Decode(templateBuffer, binary.BigEndian, &templateID, &fieldCount); err != nil {
return nil, err
if err := decodeTemplateRecordHeader(templateBuffer, &templateID, &fieldCount); err != nil {
return nil, fmt.Errorf("failed to decode template record header: %w", err)
}

decodeField := func() (entities.InfoElementWithValue, error) {
var element *entities.InfoElement
var enterpriseID uint32
var elementID uint16
// check whether enterprise ID is 0 or not
elementid := make([]byte, 2)
var elementLength uint16
err := util.Decode(templateBuffer, binary.BigEndian, &elementid, &elementLength)
if err != nil {
return nil, err
var enterpriseID uint32
if err := decodeTemplateRecordField(templateBuffer, &elementID, &elementLength, &enterpriseID); err != nil {
return nil, fmt.Errorf("failed to decode template record field: %w", err)
}
isNonIANARegistry := elementid[0]>>7 == 1
if !isNonIANARegistry {
elementID = binary.BigEndian.Uint16(elementid)
enterpriseID = registry.IANAEnterpriseID
var err error
if enterpriseID == registry.IANAEnterpriseID {
element, err = registry.GetInfoElementFromID(elementID, enterpriseID)
if err != nil {
if cp.decodingMode == DecodingModeStrict {
Expand All @@ -299,27 +377,6 @@ func (cp *CollectingProcess) decodeTemplateSet(session *transportSession, templa
element = entities.NewInfoElement("", elementID, entities.OctetArray, enterpriseID, elementLength)
}
} else {
/*
Encoding format for Enterprise-Specific Information Elements:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1| Information element id. = 15 | Field Length = 4 (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Enterprise number (32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1: 1 bit
Information element id: 15 bits
Field Length: 16 bits
Enterprise ID: 32 bits
(Reference: https://tools.ietf.org/html/rfc7011#appendix-A.2.2)
*/
err = util.Decode(templateBuffer, binary.BigEndian, &enterpriseID)
if err != nil {
return nil, err
}
elementid[0] = elementid[0] ^ 0x80
elementID = binary.BigEndian.Uint16(elementid)
element, err = registry.GetInfoElementFromID(elementID, enterpriseID)
if err != nil {
if cp.decodingMode == DecodingModeStrict {
Expand Down Expand Up @@ -382,7 +439,11 @@ func (cp *CollectingProcess) decodeDataSet(session *transportSession, dataBuffer
for _, ie := range template {
var length int
if ie.Len == entities.VariableLength { // string / octet array
length = getFieldLength(dataBuffer)
var err error
length, err = getFieldLength(dataBuffer)
if err != nil {
return nil, fmt.Errorf("failed to read variable field length: %w", err)
}
} else {
length = int(ie.Len)
}
Expand Down Expand Up @@ -417,22 +478,22 @@ func getMessageLength(reader *bufio.Reader) (int, error) {
if err != nil {
return 0, err
}
var msgLen uint16
err = util.Decode(bytes.NewBuffer(partialHeader[2:]), binary.BigEndian, &msgLen)
if err != nil {
return 0, fmt.Errorf("cannot decode message: %w", err)
}
return int(msgLen), nil
return int(binary.BigEndian.Uint16(partialHeader[2:])), nil
}

// getFieldLength returns string field length for data record
// (encoding reference: https://tools.ietf.org/html/rfc7011#appendix-A.5)
func getFieldLength(dataBuffer *bytes.Buffer) int {
oneByte, _ := dataBuffer.ReadByte()
func getFieldLength(dataBuffer *bytes.Buffer) (int, error) {
oneByte, err := dataBuffer.ReadByte()
if err != nil {
return 0, err
}
if oneByte < 255 { // string length is less than 255
return int(oneByte)
return int(oneByte), nil
}
twoBytes := dataBuffer.Next(2)
if len(twoBytes) < 2 {
return 0, fmt.Errorf("buffer too short")
}
var lengthTwoBytes uint16
util.Decode(dataBuffer, binary.BigEndian, &lengthTwoBytes)
return int(lengthTwoBytes)
return int(binary.BigEndian.Uint16(twoBytes)), nil
}
4 changes: 2 additions & 2 deletions pkg/collector/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ func TestCollectingProcess_DecodeTemplateRecord(t *testing.T) {
},
},
templateRecord: []byte{0, 10, 0, 40, 95, 40, 211, 236, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 24, 1, 0, 0, 3, 0, 8, 0, 4, 0, 12, 0, 4, 128, 105, 255, 255, 0, 0},
expectedErr: "error in decoding data",
expectedErr: "buffer too short",
isTemplateExpected: false,
},
{
Expand All @@ -469,7 +469,7 @@ func TestCollectingProcess_DecodeTemplateRecord(t *testing.T) {
},
// We truncate the record header (3 bytes instead of 4)
templateRecord: []byte{0, 10, 0, 40, 95, 154, 107, 127, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 24, 1, 0, 0},
expectedErr: "error in decoding data",
expectedErr: "buffer too short",
// If we cannot decode the message to get a template ID, then the existing template entry will not be removed
isTemplateExpected: true,
},
Expand Down
2 changes: 2 additions & 0 deletions pkg/entities/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
// To begin with, we will have local buffer in record.
// Have an interface and expose functions to user.

const TemplateRecordHeaderLength = 4

type Record interface {
PrepareRecord() error
AddInfoElement(element InfoElementWithValue) error
Expand Down
40 changes: 0 additions & 40 deletions pkg/util/util.go

This file was deleted.

0 comments on commit 6aea8f6

Please sign in to comment.