diff --git a/go.mod b/go.mod index 0376a1355..23715ec13 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/cilium/ebpf v0.21.0 github.com/fsnotify/fsnotify v1.10.1 github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424 - github.com/gopacket/gopacket v1.5.0 + github.com/gopacket/gopacket v1.6.0 github.com/mariomac/guara v0.0.0-20250408105519-1e4dbdfb7136 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/netobserv/flowlogs-pipeline v1.11.4-community.0.20260521155724-7ee2fbbc7651 diff --git a/go.sum b/go.sum index 5fa89f626..36e3fcd33 100644 --- a/go.sum +++ b/go.sum @@ -207,8 +207,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.18.0 h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI= github.com/googleapis/gax-go/v2 v2.18.0/go.mod h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE= -github.com/gopacket/gopacket v1.5.0 h1:9s9fcSUVKFlRV97B77Bq9XNV3ly2gvvsneFMQUGjc+M= -github.com/gopacket/gopacket v1.5.0/go.mod h1:i3NaGaqfoWKAr1+g7qxEdWsmfT+MXuWkAe9+THv8LME= +github.com/gopacket/gopacket v1.6.0 h1:+DdqJ4EE1C4Jx2VMDUcKvsTGc4qki2LSs0ws51RgU3Y= +github.com/gopacket/gopacket v1.6.0/go.mod h1:i3NaGaqfoWKAr1+g7qxEdWsmfT+MXuWkAe9+THv8LME= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM= diff --git a/vendor/github.com/gopacket/gopacket/.gitignore b/vendor/github.com/gopacket/gopacket/.gitignore index b79b2e2d3..6d5b0847d 100644 --- a/vendor/github.com/gopacket/gopacket/.gitignore +++ b/vendor/github.com/gopacket/gopacket/.gitignore @@ -36,4 +36,5 @@ examples/reassemblydump/reassemblydump layers/gen macs/gen pcap/pcap_tester -.idea/ \ No newline at end of file +.idea/ +.DS_Store \ No newline at end of file diff --git a/vendor/github.com/gopacket/gopacket/INDUSTRIAL_PROTOCOLS_PATCH.md b/vendor/github.com/gopacket/gopacket/INDUSTRIAL_PROTOCOLS_PATCH.md deleted file mode 100644 index 8395c8222..000000000 --- a/vendor/github.com/gopacket/gopacket/INDUSTRIAL_PROTOCOLS_PATCH.md +++ /dev/null @@ -1,97 +0,0 @@ -# Patch Set Summary: Industrial Protocol Support (CIP, Ethernet/IP) - -## Overview - -This patch set ports the changes from [google/gopacket PR #758](https://github.com/google/gopacket/pull/758) to add support for industrial automation protocols: **CIP** (Common Industrial Protocol), and **ENIP** (Ethernet/IP). - -## New Protocol Implementations - -### 1. CIP (Common Industrial Protocol) -**File**: `layers/cip.go` - -- **Purpose**: Application-layer protocol used in industrial automation -- **Key Features**: - - Service code definitions (GetAttributesAll, SetAttributesAll, GetAttributeSingle, SetAttributeSingle, MultipleServicePacket) - - Status code handling (Success, ConnectionFailure, ResourceUnavailable, etc.) - - Path segment parsing for Class ID, Instance ID, and Attribute ID - - Request/Response differentiation - - Full decoder implementation following gopacket patterns - -### 2. ENIP (Ethernet/IP) -**File**: `layers/enip.go` - -- **Purpose**: Ethernet encapsulation protocol for CIP -- **Key Features**: - - 24-byte header parsing - - Command codes (NOP, ListServices, RegisterSession, UnregisterSession, SendRRData, SendUnitData, etc.) - - Status code handling - - Session management fields (SessionHandle, SenderContext) - - Automatic CIP payload decoding for SendRRData and SendUnitData commands - - Serialization support - - Little-endian encoding (as per ENIP specification) - -## Modified Files - -### Core Layer Registration - -#### `layers/layertypes.go` -- Added `LayerTypeENIP` (ID: 151) -- Added `LayerTypeCIP` (ID: 152) - -```go -LayerTypeENIP = gopacket.RegisterLayerType(151, gopacket.LayerTypeMetadata{ - Name: "ENIP", - Decoder: gopacket.DecodeFunc(decodeENIP) -}) -LayerTypeCIP = gopacket.RegisterLayerType(152, gopacket.LayerTypeMetadata{ - Name: "CIP", - Decoder: gopacket.DecodeFunc(decodeCIP) -}) -``` - -#### `layers/ports.go` -Added port mappings for automatic protocol detection: - -**TCP Ports**: -- Port 2222 → `LayerTypeENIP` (EtherNet/IP-1) -- Port 44818 → `LayerTypeENIP` (EtherNet/IP-2) - -**UDP Ports**: -- Port 2222 → `LayerTypeENIP` (EtherNet/IP-1) -- Port 44818 → `LayerTypeENIP` (EtherNet/IP-2) - -#### `layers/enums.go` -- Added missing `errors` import -- Added `EthernetTypeERSPAN` constant (0x88be) -- Added `EthernetTypeRaw` constant (0xFFFF) - -## Protocol Specifications - -### Port Numbers -| Protocol | TCP Port | UDP Port | Description | -|----------|----------|----------|-------------| -| ENIP | 2222 | 2222 | EtherNet/IP-1 (standard) | -| ENIP | 44818 | 44818 | EtherNet/IP-2 (alternate) | - -### Layer Hierarchy -``` -Ethernet - └── IPv4/IPv6 - └── TCP/UDP - └── ENIP (ports 2222, 44818) - └── CIP (for SendRRData/SendUnitData commands) - └── Payload -``` - -## Credits - -- Original implementation by @traetox in [PR #408](https://github.com/google/gopacket/pull/408) -- Reimplemented by @dreadl0ck in [PR #758](https://github.com/google/gopacket/pull/758) -- Ported to gopacket-community repository - -## References - -- **CIP**: ODVA Common Industrial Protocol specification -- **ENIP**: ODVA Ethernet/IP specification -- **IANA Ports**: Port 2222 (EtherNet-IP-1), Port 44818 (EtherNet-IP-2) - diff --git a/vendor/github.com/gopacket/gopacket/layers/cip.go b/vendor/github.com/gopacket/gopacket/layers/cip.go index efe498f09..48526398d 100644 --- a/vendor/github.com/gopacket/gopacket/layers/cip.go +++ b/vendor/github.com/gopacket/gopacket/layers/cip.go @@ -1,10 +1,7 @@ -// Copyright 2018, The GoPacket Authors, All rights reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file in the root of the source -// tree. -// -//****************************************************************************** +// CIP (Common Industrial Protocol) support for gopacket. +// CIP is an industrial protocol defined by ODVA (odva.org) that runs +// on top of EtherNet/IP and other industrial networks. +// See: https://www.odva.org package layers @@ -16,60 +13,162 @@ import ( "github.com/gopacket/gopacket" ) -//****************************************************************************** -// -// CIP (Common Industrial Protocol) Decoding Layer -// ------------------------------------------ -// This file provides a GoPacket decoding layer for CIP. -// -//****************************************************************************** +const ( + cipBasePacketLen int = 2 +) -// CIPService represents the service code in a CIP request/response -type CIPService uint8 +var ( + // ErrCIPDataTooSmall indicates that a CIP packet has been truncated + ErrCIPDataTooSmall = errors.New("CIP packet data truncated") +) -// Common CIP Service codes +// CIPService represents a CIP service code +type CIPService byte + +// CIP Service Code constants const ( - CIPServiceGetAttributesAll CIPService = 0x01 - CIPServiceSetAttributesAll CIPService = 0x02 - CIPServiceGetAttributeSingle CIPService = 0x0E - CIPServiceSetAttributeSingle CIPService = 0x10 - CIPServiceMultipleServicePacket CIPService = 0x0A + CIPServiceGetAttributesAll CIPService = 0x01 + CIPServiceSetAttributesAll CIPService = 0x02 + CIPServiceGetAttributeList CIPService = 0x03 + CIPServiceSetAttributeList CIPService = 0x04 + CIPServiceReset CIPService = 0x05 + CIPServiceStart CIPService = 0x06 + CIPServiceStop CIPService = 0x07 + CIPServiceCreate CIPService = 0x08 + CIPServiceDelete CIPService = 0x09 + CIPServiceMultipleServicePacket CIPService = 0x0A + CIPServiceApplyAttributes CIPService = 0x0D + CIPServiceGetAttributeSingle CIPService = 0x0E + CIPServiceSetAttributeSingle CIPService = 0x10 + CIPServiceFindNextObjectInstance CIPService = 0x11 + CIPServiceRestore CIPService = 0x15 + CIPServiceSave CIPService = 0x16 + CIPServiceGetMember CIPService = 0x18 + CIPServiceSetMember CIPService = 0x19 + CIPServiceInsertMember CIPService = 0x1A + CIPServiceRemoveMember CIPService = 0x1B + CIPServiceGroupSync CIPService = 0x1C + // Connection Manager Services + CIPServiceForwardOpen CIPService = 0x54 + CIPServiceForwardClose CIPService = 0x4E + CIPServiceUnconnectedSend CIPService = 0x52 + CIPServiceGetConnectionData CIPService = 0x56 + CIPServiceSearchConnectionData CIPService = 0x57 + CIPServiceGetConnectionOwner CIPService = 0x5A + // Response bit (OR'd with service code for responses) + CIPServiceResponseMask CIPService = 0x80 ) +// String returns a human-readable string representation of the CIP service func (cs CIPService) String() string { - switch cs { + // Check if it's a response + isResponse := (cs & CIPServiceResponseMask) != 0 + service := cs & ^CIPServiceResponseMask + + var serviceName string + switch service { case CIPServiceGetAttributesAll: - return "Get Attributes All" + serviceName = "Get_Attributes_All" case CIPServiceSetAttributesAll: - return "Set Attributes All" + serviceName = "Set_Attributes_All" + case CIPServiceGetAttributeList: + serviceName = "Get_Attribute_List" + case CIPServiceSetAttributeList: + serviceName = "Set_Attribute_List" + case CIPServiceReset: + serviceName = "Reset" + case CIPServiceStart: + serviceName = "Start" + case CIPServiceStop: + serviceName = "Stop" + case CIPServiceCreate: + serviceName = "Create" + case CIPServiceDelete: + serviceName = "Delete" + case CIPServiceMultipleServicePacket: + serviceName = "Multiple_Service_Packet" + case CIPServiceApplyAttributes: + serviceName = "Apply_Attributes" case CIPServiceGetAttributeSingle: - return "Get Attribute Single" + serviceName = "Get_Attribute_Single" case CIPServiceSetAttributeSingle: - return "Set Attribute Single" - case CIPServiceMultipleServicePacket: - return "Multiple Service Packet" + serviceName = "Set_Attribute_Single" + case CIPServiceFindNextObjectInstance: + serviceName = "Find_Next_Object_Instance" + case CIPServiceRestore: + serviceName = "Restore" + case CIPServiceSave: + serviceName = "Save" + case CIPServiceGetMember: + serviceName = "Get_Member" + case CIPServiceSetMember: + serviceName = "Set_Member" + case CIPServiceInsertMember: + serviceName = "Insert_Member" + case CIPServiceRemoveMember: + serviceName = "Remove_Member" + case CIPServiceGroupSync: + serviceName = "Group_Sync" + case CIPServiceForwardOpen: + serviceName = "Forward_Open" + case CIPServiceForwardClose: + serviceName = "Forward_Close" + case CIPServiceUnconnectedSend: + serviceName = "Unconnected_Send" + case CIPServiceGetConnectionData: + serviceName = "Get_Connection_Data" + case CIPServiceSearchConnectionData: + serviceName = "Search_Connection_Data" + case CIPServiceGetConnectionOwner: + serviceName = "Get_Connection_Owner" default: - return fmt.Sprintf("Unknown Service (0x%02x)", uint8(cs)) + serviceName = fmt.Sprintf("Unknown(0x%02X)", byte(service)) } + + if isResponse { + return serviceName + "_Response" + } + return serviceName } -// CIPStatus represents the status code in a CIP response -type CIPStatus uint8 +// CIPStatus represents a CIP status code +type CIPStatus byte -// Common CIP Status codes +// CIP Status Code constants const ( - CIPStatusSuccess CIPStatus = 0x00 - CIPStatusConnectionFailure CIPStatus = 0x01 - CIPStatusResourceUnavailable CIPStatus = 0x02 - CIPStatusInvalidParameterValue CIPStatus = 0x03 - CIPStatusPathSegmentError CIPStatus = 0x04 - CIPStatusPathDestinationUnknown CIPStatus = 0x05 - CIPStatusPartialTransfer CIPStatus = 0x06 - CIPStatusConnectionLost CIPStatus = 0x07 - CIPStatusServiceNotSupported CIPStatus = 0x08 - CIPStatusInvalidAttributeValue CIPStatus = 0x09 + CIPStatusSuccess CIPStatus = 0x00 + CIPStatusConnectionFailure CIPStatus = 0x01 + CIPStatusResourceUnavailable CIPStatus = 0x02 + CIPStatusInvalidParameterValue CIPStatus = 0x03 + CIPStatusPathSegmentError CIPStatus = 0x04 + CIPStatusPathDestinationUnknown CIPStatus = 0x05 + CIPStatusPartialTransfer CIPStatus = 0x06 + CIPStatusConnectionLost CIPStatus = 0x07 + CIPStatusServiceNotSupported CIPStatus = 0x08 + CIPStatusInvalidAttributeValue CIPStatus = 0x09 + CIPStatusAttributeListError CIPStatus = 0x0A + CIPStatusAlreadyInRequestedMode CIPStatus = 0x0B + CIPStatusObjectStateConflict CIPStatus = 0x0C + CIPStatusObjectAlreadyExists CIPStatus = 0x0D + CIPStatusAttributeNotSettable CIPStatus = 0x0E + CIPStatusPrivilegeViolation CIPStatus = 0x0F + CIPStatusDeviceStateConflict CIPStatus = 0x10 + CIPStatusReplyDataTooLarge CIPStatus = 0x11 + CIPStatusFragmentationOfPrimitiveValue CIPStatus = 0x12 + CIPStatusNotEnoughData CIPStatus = 0x13 + CIPStatusAttributeNotSupported CIPStatus = 0x14 + CIPStatusTooMuchData CIPStatus = 0x15 + CIPStatusObjectDoesNotExist CIPStatus = 0x16 + CIPStatusServiceFragmentationSequence CIPStatus = 0x17 + CIPStatusNoStoredAttributeData CIPStatus = 0x18 + CIPStatusStoreOperationFailure CIPStatus = 0x19 + CIPStatusRoutingFailure CIPStatus = 0x1A + CIPStatusRoutingFailureBadSize CIPStatus = 0x1B + CIPStatusRoutingFailureBadService CIPStatus = 0x1C + CIPStatusInvalidParameter CIPStatus = 0x20 ) +// String returns a human-readable string representation of the CIP status func (cs CIPStatus) String() string { switch cs { case CIPStatusSuccess: @@ -92,188 +191,226 @@ func (cs CIPStatus) String() string { return "Service Not Supported" case CIPStatusInvalidAttributeValue: return "Invalid Attribute Value" + case CIPStatusAttributeListError: + return "Attribute List Error" + case CIPStatusAlreadyInRequestedMode: + return "Already In Requested Mode" + case CIPStatusObjectStateConflict: + return "Object State Conflict" + case CIPStatusObjectAlreadyExists: + return "Object Already Exists" + case CIPStatusAttributeNotSettable: + return "Attribute Not Settable" + case CIPStatusPrivilegeViolation: + return "Privilege Violation" + case CIPStatusDeviceStateConflict: + return "Device State Conflict" + case CIPStatusReplyDataTooLarge: + return "Reply Data Too Large" + case CIPStatusFragmentationOfPrimitiveValue: + return "Fragmentation Of Primitive Value" + case CIPStatusNotEnoughData: + return "Not Enough Data" + case CIPStatusAttributeNotSupported: + return "Attribute Not Supported" + case CIPStatusTooMuchData: + return "Too Much Data" + case CIPStatusObjectDoesNotExist: + return "Object Does Not Exist" + case CIPStatusServiceFragmentationSequence: + return "Service Fragmentation Sequence" + case CIPStatusNoStoredAttributeData: + return "No Stored Attribute Data" + case CIPStatusStoreOperationFailure: + return "Store Operation Failure" + case CIPStatusRoutingFailure: + return "Routing Failure" + case CIPStatusRoutingFailureBadSize: + return "Routing Failure - Bad Size" + case CIPStatusRoutingFailureBadService: + return "Routing Failure - Bad Service" + case CIPStatusInvalidParameter: + return "Invalid Parameter" default: - return fmt.Sprintf("Unknown Status (0x%02x)", uint8(cs)) + return fmt.Sprintf("Unknown(0x%02X)", byte(cs)) } } -//****************************************************************************** - -// CIP represents a Common Industrial Protocol packet +// CIP implements encoding/decoding for the Common Industrial Protocol, as +// defined by ODVA (odva.org). +// Refer to https://www.rockwellautomation.com/resources/downloads/rockwellautomation/pdf/sales-partners/technology-licensing/eipexp1_2.pdf +// for more information about the protocol. type CIP struct { BaseLayer - - Service CIPService // Service code - Response bool // true if this is a response, false if request - PathSize uint8 // Size of path in words (16-bit) - ClassID *uint32 // Class ID if present in path - InstanceID *uint32 // Instance ID if present in path - AttributeID *uint32 // Attribute ID if present in path - Status CIPStatus // Status code (response only) - AdditionalStatus []uint8 // Additional status bytes (response only) -} - -//****************************************************************************** - -// LayerType returns the layer type of the CIP object -func (c *CIP) LayerType() gopacket.LayerType { - return LayerTypeCIP + Response bool // false if request, true if response + ServiceID byte // The service specified for the request + ClassID uint16 // request only + InstanceID uint16 // request only + Status byte // Response only + AdditionalStatus []uint16 // Response only + Data []byte // Command data for request, reply data for response } -//****************************************************************************** - -// decodeCIP analyses a byte slice and attempts to decode it as a CIP packet -func decodeCIP(data []byte, p gopacket.PacketBuilder) error { - c := &CIP{} - err := c.DecodeFromBytes(data, p) - if err != nil { - return err +// DecodeFromBytes unpacks a CIP packet in the `data` argument into the receiver. +func (cip *CIP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + // Initial bounds check + if len(data) < cipBasePacketLen { + df.SetTruncated() + return ErrCIPDataTooSmall } - p.AddLayer(c) - p.SetApplicationLayer(c) - return p.NextDecoder(c.NextLayerType()) -} -//****************************************************************************** + offset := 0 + tmp := data[offset] + offset++ -// DecodeFromBytes analyses a byte slice and attempts to decode it as a CIP packet -func (c *CIP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { - if len(data) < 2 { - df.SetTruncated() - return errors.New("CIP packet too short") + if (tmp & 0x80) == 0x80 { + cip.Response = true + } else { + cip.Response = false } + cip.ServiceID = tmp & 0x7f - // First byte is service code - // Bit 7 indicates if this is a response (1) or request (0) - c.Service = CIPService(data[0] & 0x7F) - c.Response = (data[0] & 0x80) != 0 + if !cip.Response { + // Parse out the request + // path size is in 16-bit words + if offset >= len(data) { + df.SetTruncated() + return ErrCIPDataTooSmall + } + pathsize := data[offset] + offset++ - offset := 1 + // Prevent uint8 overflow: pathsize is in 16-bit words and must be small enough to not overflow. + if pathsize > 127 { + df.SetTruncated() + return ErrCIPDataTooSmall + } + pathBytes := 2 * int(pathsize) + if len(data) < cipBasePacketLen+pathBytes { + df.SetTruncated() + return ErrCIPDataTooSmall + } - if c.Response { - // Response packet structure - if len(data) < 4 { + // read the class segment + if offset >= len(data) { df.SetTruncated() - return errors.New("CIP response packet too short") + return ErrCIPDataTooSmall } - // Reserved byte + classInfo := data[offset] offset++ - // Status - c.Status = CIPStatus(data[offset]) - offset++ + switch classInfo { + case 0x20: + // 8-bit ID + if offset >= len(data) { + df.SetTruncated() + return ErrCIPDataTooSmall + } + cip.ClassID = uint16(data[offset]) + offset++ + case 0x21: + // 16-bit ID + if offset+2 > len(data) { + df.SetTruncated() + return ErrCIPDataTooSmall + } + cip.ClassID = binary.LittleEndian.Uint16(data[offset : offset+2]) + offset += 2 + } - // Additional status size - additionalStatusSize := int(data[offset]) + // read the instance segment + if offset >= len(data) { + df.SetTruncated() + return ErrCIPDataTooSmall + } + instanceInfo := data[offset] offset++ - // Additional status bytes - if additionalStatusSize > 0 { - if len(data) < offset+additionalStatusSize*2 { + switch instanceInfo { + case 0x24: + // 8-bit ID + if offset >= len(data) { df.SetTruncated() - return errors.New("CIP response packet truncated in additional status") + return ErrCIPDataTooSmall } - c.AdditionalStatus = data[offset : offset+additionalStatusSize*2] - offset += additionalStatusSize * 2 + cip.InstanceID = uint16(data[offset]) + offset++ + case 0x25: + // 16-bit ID + if offset+2 > len(data) { + df.SetTruncated() + return ErrCIPDataTooSmall + } + cip.InstanceID = binary.LittleEndian.Uint16(data[offset : offset+2]) + offset += 2 } - } else { - // Request packet structure - // Path size in words - c.PathSize = data[offset] - offset++ - pathBytes := int(c.PathSize) * 2 - if len(data) < offset+pathBytes { + if offset < len(data) { + cip.Data = data[offset:] + } + } else { // response + if len(data) < cipBasePacketLen+2 { df.SetTruncated() - return errors.New("CIP request packet truncated in path") + return ErrCIPDataTooSmall } - // Parse path segments (simplified - only handle logical segments) - pathData := data[offset : offset+pathBytes] - c.parsePath(pathData) - offset += pathBytes - } - - c.BaseLayer = BaseLayer{ - Contents: data[:offset], - Payload: data[offset:], - } + offset++ // skip the 00 padding byte - return nil -} + cip.Status = data[offset] + offset++ -//****************************************************************************** + additionalStatusSize := uint(data[offset]) + offset++ -// parsePath parses CIP path segments (simplified implementation) -func (c *CIP) parsePath(data []byte) { - offset := 0 - for offset < len(data) { - if offset+2 > len(data) { - break + if len(data) < cipBasePacketLen+2+2*int(additionalStatusSize) { + df.SetTruncated() + return ErrCIPDataTooSmall } - segmentType := data[offset] - - // Logical segment (0x20 series) - if segmentType&0xE0 == 0x20 { - logicalType := (segmentType >> 2) & 0x07 - logicalFormat := segmentType & 0x03 - - var value uint32 - var size int - - switch logicalFormat { - case 0: // 8-bit - if offset+2 <= len(data) { - value = uint32(data[offset+1]) - size = 2 - } - case 1: // 16-bit - if offset+4 <= len(data) { - value = uint32(binary.LittleEndian.Uint16(data[offset+2 : offset+4])) - size = 4 - } - case 2: // 32-bit - if offset+6 <= len(data) { - value = binary.LittleEndian.Uint32(data[offset+2 : offset+6]) - size = 6 - } - default: - return - } - - switch logicalType { - case 0: // Class ID - c.ClassID = &value - case 1: // Instance ID - c.InstanceID = &value - case 4: // Attribute ID - c.AttributeID = &value - } - - offset += size - } else { - // Unknown segment type, skip + for i := 0; i < int(additionalStatusSize); i++ { + cip.AdditionalStatus = append(cip.AdditionalStatus, binary.LittleEndian.Uint16(data[offset:offset+2])) offset += 2 } + + if offset < len(data) { + cip.Data = data[offset:] + } } + return nil } -//****************************************************************************** +// LayerType returns gopacket.LayerTypeCIP +func (cip *CIP) LayerType() gopacket.LayerType { return LayerTypeCIP } -// NextLayerType returns the layer type of the CIP payload -func (c *CIP) NextLayerType() gopacket.LayerType { +// CanDecode returns gopacket.LayerTypeCIP +func (cip *CIP) CanDecode() gopacket.LayerClass { return LayerTypeCIP } + +// NextLayerType returns LayerTypePayload, the only possible next +// layer type for a CIP packet. +func (cip *CIP) NextLayerType() gopacket.LayerType { return gopacket.LayerTypePayload } -//****************************************************************************** +func decodeCIP(data []byte, p gopacket.PacketBuilder) error { + if len(data) < cipBasePacketLen { + p.SetTruncated() + return ErrCIPDataTooSmall + } + cip := &CIP{} + return decodingLayerDecoder(cip, data, p) +} + +// IsRequest returns true if this is a CIP request (not a response) +func (cip *CIP) IsRequest() bool { + return !cip.Response +} -// Payload returns the CIP payload bytes -func (c *CIP) Payload() []byte { - return c.BaseLayer.Payload +// IsResponse returns true if this is a CIP response +func (cip *CIP) IsResponse() bool { + return cip.Response } -// CanDecode returns the set of layer types that this DecodingLayer can decode -func (c *CIP) CanDecode() gopacket.LayerClass { - return LayerTypeCIP +// IsSuccess returns true if this is a response with success status +func (cip *CIP) IsSuccess() bool { + return cip.Response && cip.Status == 0 } diff --git a/vendor/github.com/gopacket/gopacket/layers/diameter.go b/vendor/github.com/gopacket/gopacket/layers/diameter.go new file mode 100644 index 000000000..a06a46ca9 --- /dev/null +++ b/vendor/github.com/gopacket/gopacket/layers/diameter.go @@ -0,0 +1,395 @@ +package layers + +import ( + "encoding/binary" + "errors" + "fmt" + "time" + + "github.com/gopacket/gopacket" +) + +// Diameter represents a Diameter Protocol message (RFC 6733) +type Diameter struct { + BaseLayer + Version uint8 + MessageLength uint32 // 24-bit field + CommandFlags DiameterCommandFlags + CommandCode uint32 // 24-bit field + ApplicationID uint32 + HopByHopID uint32 + EndToEndID uint32 + AVPs []DiameterAVP +} + +// DiameterCommandFlags represents the flags in a Diameter message header +type DiameterCommandFlags struct { + Request bool + Proxiable bool + Error bool + Retransmitted bool +} + +// DiameterAVP represents an Attribute-Value Pair in Diameter +type DiameterAVP struct { + Code uint32 + Flags DiameterAVPFlags + Length uint32 // 24-bit field + VendorID uint32 // Only present if VendorSpecific flag is set + Data []byte + GroupedAVPs []DiameterAVP // For Grouped AVP types +} + +// DiameterAVPFlags represents the flags in a Diameter AVP header +type DiameterAVPFlags struct { + Vendor bool + Mandatory bool + Protected bool +} + +// DiameterVendor represents known Diameter vendors +type DiameterVendor uint32 + +// Known Diameter vendor IDs +const ( + DiameterVendorNone DiameterVendor = 0 + DiameterVendor3GPP DiameterVendor = 10415 // 3GPP + DiameterVendorETSI DiameterVendor = 13019 // ETSI + DiameterVendorVodafone DiameterVendor = 12645 // Vodafone + DiameterVendorCisco DiameterVendor = 9 // Cisco + DiameterVendorEricsson DiameterVendor = 193 // Ericsson + DiameterVendorHuawei DiameterVendor = 2011 // Huawei + DiameterVendorNokia DiameterVendor = 94 // Nokia +) + +// diameterVendors maps vendor IDs to their names +var diameterVendors = map[uint32]string{ + 0: "None", + 9: "Cisco", + 94: "Nokia", + 193: "Ericsson", + 2011: "Huawei", + 10415: "3GPP", + 12645: "Vodafone", + 13019: "ETSI", +} + +// GetVendorName returns the vendor name for a vendor ID +func GetVendorName(vendorID uint32) string { + if name, ok := diameterVendors[vendorID]; ok { + return name + } + return fmt.Sprintf("Unknown(%d)", vendorID) +} + +// LayerType returns gopacket.LayerTypeDiameter +func (d *Diameter) LayerType() gopacket.LayerType { + return LayerTypeDiameter +} + +// CanDecode returns the set of layer types that this DecodingLayer can decode +func (d *Diameter) CanDecode() gopacket.LayerClass { + return LayerTypeDiameter +} + +// NextLayerType returns the layer type contained by this DecodingLayer +func (d *Diameter) NextLayerType() gopacket.LayerType { + return gopacket.LayerTypePayload +} + +// Payload returns nil as Diameter is an application-layer protocol +func (d *Diameter) Payload() []byte { + return nil +} + +// decodeDiameter decodes the Diameter protocol +func decodeDiameter(data []byte, p gopacket.PacketBuilder) error { + d := &Diameter{} + err := d.DecodeFromBytes(data, p) + if err != nil { + return err + } + p.AddLayer(d) + p.SetApplicationLayer(d) + return nil +} + +// DecodeFromBytes decodes the given bytes into this layer +func (d *Diameter) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + if len(data) < 20 { + return errors.New("diameter message too short, minimum 20 bytes required") + } + + d.Version = data[0] + if d.Version != 1 { + return fmt.Errorf("unsupported diameter version: %d", d.Version) + } + + // Message Length is 24 bits (bytes 1-3) + d.MessageLength = uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]) + + if uint32(len(data)) < d.MessageLength { + return fmt.Errorf("diameter message truncated: expected %d bytes, got %d", d.MessageLength, len(data)) + } + + // Command Flags (byte 4) + d.CommandFlags.Request = (data[4] & 0x80) != 0 + d.CommandFlags.Proxiable = (data[4] & 0x40) != 0 + d.CommandFlags.Error = (data[4] & 0x20) != 0 + d.CommandFlags.Retransmitted = (data[4] & 0x10) != 0 + + // Command Code is 24 bits (bytes 5-7) + d.CommandCode = uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]) + + // Application ID (bytes 8-11) + d.ApplicationID = binary.BigEndian.Uint32(data[8:12]) + + // Hop-by-Hop Identifier (bytes 12-15) + d.HopByHopID = binary.BigEndian.Uint32(data[12:16]) + + // End-to-End Identifier (bytes 16-19) + d.EndToEndID = binary.BigEndian.Uint32(data[16:20]) + + // Parse AVPs + avpData := data[20:d.MessageLength] + d.AVPs = []DiameterAVP{} + + for len(avpData) >= 8 { + avp, bytesConsumed, err := decodeDiameterAVP(avpData) + if err != nil { + df.SetTruncated() + break + } + d.AVPs = append(d.AVPs, avp) + avpData = avpData[bytesConsumed:] + } + + d.BaseLayer = BaseLayer{Contents: data[:d.MessageLength]} + + return nil +} + +// SerializeTo writes the serialized form of this layer into the +// SerializationBuffer, implementing gopacket.SerializableLayer. +func (d *Diameter) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { + // Calculate total message length + messageLength := 20 // Header size + for _, avp := range d.AVPs { + avpLen := SerializedAVPLength(&avp) + messageLength += avpLen + } + + if opts.FixLengths { + d.MessageLength = uint32(messageLength) + } + + bytes, err := b.PrependBytes(messageLength) + if err != nil { + return err + } + + // Version + bytes[0] = d.Version + + // Message Length (24 bits) + bytes[1] = byte(d.MessageLength >> 16) + bytes[2] = byte(d.MessageLength >> 8) + bytes[3] = byte(d.MessageLength) + + // Command Flags + bytes[4] = 0 + if d.CommandFlags.Request { + bytes[4] |= 0x80 + } + if d.CommandFlags.Proxiable { + bytes[4] |= 0x40 + } + if d.CommandFlags.Error { + bytes[4] |= 0x20 + } + if d.CommandFlags.Retransmitted { + bytes[4] |= 0x10 + } + + // Command Code (24 bits) + bytes[5] = byte(d.CommandCode >> 16) + bytes[6] = byte(d.CommandCode >> 8) + bytes[7] = byte(d.CommandCode) + + // Application ID + binary.BigEndian.PutUint32(bytes[8:12], d.ApplicationID) + + // Hop-by-Hop ID + binary.BigEndian.PutUint32(bytes[12:16], d.HopByHopID) + + // End-to-End ID + binary.BigEndian.PutUint32(bytes[16:20], d.EndToEndID) + + // Serialize AVPs + offset := 20 + for _, avp := range d.AVPs { + avpBytes := SerializeDiameterAVP(&avp) + copy(bytes[offset:], avpBytes) + offset += len(avpBytes) + } + + return nil +} + +// GetUnsigned32 returns the AVP data as uint32 +func (avp *DiameterAVP) GetUnsigned32() (uint32, error) { + if len(avp.Data) != 4 { + return 0, fmt.Errorf("invalid data length for Unsigned32: %d", len(avp.Data)) + } + return binary.BigEndian.Uint32(avp.Data), nil +} + +// GetUnsigned64 returns the AVP data as uint64 +func (avp *DiameterAVP) GetUnsigned64() (uint64, error) { + if len(avp.Data) != 8 { + return 0, fmt.Errorf("invalid data length for Unsigned64: %d", len(avp.Data)) + } + return binary.BigEndian.Uint64(avp.Data), nil +} + +// GetInteger32 returns the AVP data as int32 +func (avp *DiameterAVP) GetInteger32() (int32, error) { + val, err := avp.GetUnsigned32() + return int32(val), err +} + +// GetInteger64 returns the AVP data as int64 +func (avp *DiameterAVP) GetInteger64() (int64, error) { + val, err := avp.GetUnsigned64() + return int64(val), err +} + +// GetString returns the AVP data as string +func (avp *DiameterAVP) GetString() string { + return string(avp.Data) +} + +// GetTime returns the AVP data as time.Time (NTP timestamp) +func (avp *DiameterAVP) GetTime() (time.Time, error) { + if len(avp.Data) != 4 { + return time.Time{}, fmt.Errorf("invalid data length for Time: %d", len(avp.Data)) + } + // Diameter uses NTP timestamp (seconds since Jan 1, 1900) + ntpEpoch := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC) + seconds := binary.BigEndian.Uint32(avp.Data) + return ntpEpoch.Add(time.Duration(seconds) * time.Second), nil +} + +// IsRequest returns true if this is a request message +func (d *Diameter) IsRequest() bool { + return d.CommandFlags.Request +} + +// IsProxiable returns true if the message may be proxied +func (d *Diameter) IsProxiable() bool { + return d.CommandFlags.Proxiable +} + +// IsError returns true if this is an error message +func (d *Diameter) IsError() bool { + return d.CommandFlags.Error +} + +// IsRetransmitted returns true if this is a retransmitted message +func (d *Diameter) IsRetransmitted() bool { + return d.CommandFlags.Retransmitted +} + +// IsMandatory returns true if the AVP is mandatory +func (avp *DiameterAVP) IsMandatory() bool { + return avp.Flags.Mandatory +} + +// IsVendorSpecific returns true if the AVP is vendor-specific +func (avp *DiameterAVP) IsVendorSpecific() bool { + return avp.Flags.Vendor +} + +// IsProtected returns true if the AVP is protected +func (avp *DiameterAVP) IsProtected() bool { + return avp.Flags.Protected +} + +// GetAVPTypeName returns the type name for this AVP +func (avp *DiameterAVP) GetAVPTypeName() string { + avpType, known := GetDiameterAVPType(avp.Code, avp.VendorID) + typeStr := "Unknown" + if known { + switch avpType { + case DiameterAVPTypeOctetString: + typeStr = "OctetString" + case DiameterAVPTypeInteger32: + typeStr = "Integer32" + case DiameterAVPTypeInteger64: + typeStr = "Integer64" + case DiameterAVPTypeUnsigned32: + typeStr = "Unsigned32" + case DiameterAVPTypeUnsigned64: + typeStr = "Unsigned64" + case DiameterAVPTypeFloat32: + typeStr = "Float32" + case DiameterAVPTypeFloat64: + typeStr = "Float64" + case DiameterAVPTypeGrouped: + typeStr = "Grouped" + case DiameterAVPTypeAddress: + typeStr = "Address" + case DiameterAVPTypeTime: + typeStr = "Time" + case DiameterAVPTypeUTF8String: + typeStr = "UTF8String" + case DiameterAVPTypeDiameterIdentity: + typeStr = "DiameterIdentity" + case DiameterAVPTypeDiameterURI: + typeStr = "DiameterURI" + case DiameterAVPTypeEnumerated: + typeStr = "Enumerated" + case DiameterAVPTypeIPFilterRule: + typeStr = "IPFilterRule" + } + } + return typeStr +} + +// String returns a string representation of the AVP +func (avp *DiameterAVP) String() string { + flags := "" + if avp.Flags.Vendor { + flags += "V" + } + if avp.Flags.Mandatory { + flags += "M" + } + if avp.Flags.Protected { + flags += "P" + } + if flags == "" { + flags = "-" + } + + vendorInfo := "" + if avp.Flags.Vendor { + vendorInfo = fmt.Sprintf(" Vendor=%s(%d)", GetVendorName(avp.VendorID), avp.VendorID) + } + + groupedInfo := "" + if len(avp.GroupedAVPs) > 0 { + groupedInfo = fmt.Sprintf(" [%d sub-AVPs]", len(avp.GroupedAVPs)) + } + + return fmt.Sprintf("AVP{Code=%d, Flags=%s, Type=%s, Length=%d%s%s}", + avp.Code, flags, avp.GetAVPTypeName(), avp.Length, vendorInfo, groupedInfo) +} + +// GetVendorIDFromAVP is a convenience method to get vendor name from AVP +func (avp *DiameterAVP) GetVendorIDString() string { + if avp.Flags.Vendor { + return GetVendorName(avp.VendorID) + } + return "None" +} diff --git a/vendor/github.com/gopacket/gopacket/layers/diameter_avp_codes.go b/vendor/github.com/gopacket/gopacket/layers/diameter_avp_codes.go new file mode 100644 index 000000000..4900ed157 --- /dev/null +++ b/vendor/github.com/gopacket/gopacket/layers/diameter_avp_codes.go @@ -0,0 +1,204 @@ +package layers + +// DiameterAVPCode represents Diameter AVP codes +type DiameterAVPCode uint32 + +// Standard Diameter AVP Codes from RFC 6733 +const ( + DiameterAVPCodeUserName DiameterAVPCode = 1 + DiameterAVPCodeClass DiameterAVPCode = 25 + DiameterAVPCodeSessionTimeout DiameterAVPCode = 27 + DiameterAVPCodeProxyState DiameterAVPCode = 33 + DiameterAVPCodeAccountingSessionID DiameterAVPCode = 44 + DiameterAVPCodeAcctMultiSessionID DiameterAVPCode = 50 + DiameterAVPCodeEventTimestamp DiameterAVPCode = 55 + DiameterAVPCodeAcctInterimInterval DiameterAVPCode = 85 + DiameterAVPCodeHostIPAddress DiameterAVPCode = 257 + DiameterAVPCodeAuthApplicationID DiameterAVPCode = 258 + DiameterAVPCodeAcctApplicationID DiameterAVPCode = 259 + DiameterAVPCodeVendorSpecificAppID DiameterAVPCode = 260 + DiameterAVPCodeRedirectHostUsage DiameterAVPCode = 261 + DiameterAVPCodeRedirectMaxCacheTime DiameterAVPCode = 262 + DiameterAVPCodeSessionID DiameterAVPCode = 263 + DiameterAVPCodeOriginHost DiameterAVPCode = 264 + DiameterAVPCodeSupportedVendorID DiameterAVPCode = 265 + DiameterAVPCodeVendorID DiameterAVPCode = 266 + DiameterAVPCodeFirmwareRevision DiameterAVPCode = 267 + DiameterAVPCodeResultCode DiameterAVPCode = 268 + DiameterAVPCodeProductName DiameterAVPCode = 269 + DiameterAVPCodeSessionBinding DiameterAVPCode = 270 + DiameterAVPCodeSessionServerFailover DiameterAVPCode = 271 + DiameterAVPCodeMultiRoundTimeOut DiameterAVPCode = 272 + DiameterAVPCodeDisconnectCause DiameterAVPCode = 273 + DiameterAVPCodeAuthRequestType DiameterAVPCode = 274 + DiameterAVPCodeAuthGracePeriod DiameterAVPCode = 276 + DiameterAVPCodeAuthSessionState DiameterAVPCode = 277 + DiameterAVPCodeOriginStateID DiameterAVPCode = 278 + DiameterAVPCodeFailedAVP DiameterAVPCode = 279 + DiameterAVPCodeProxyHost DiameterAVPCode = 280 + DiameterAVPCodeErrorMessage DiameterAVPCode = 281 + DiameterAVPCodeRouteRecord DiameterAVPCode = 282 + DiameterAVPCodeDestinationRealm DiameterAVPCode = 283 + DiameterAVPCodeProxyInfo DiameterAVPCode = 284 + DiameterAVPCodeReAuthRequestType DiameterAVPCode = 285 + DiameterAVPCodeAccountingSubSessionID DiameterAVPCode = 287 + DiameterAVPCodeAuthorizationLifetime DiameterAVPCode = 291 + DiameterAVPCodeRedirectHost DiameterAVPCode = 292 + DiameterAVPCodeDestinationHost DiameterAVPCode = 293 + DiameterAVPCodeErrorReportingHost DiameterAVPCode = 294 + DiameterAVPCodeTerminationCause DiameterAVPCode = 295 + DiameterAVPCodeOriginRealm DiameterAVPCode = 296 + DiameterAVPCodeExperimentalResult DiameterAVPCode = 297 + DiameterAVPCodeExperimentalResultCode DiameterAVPCode = 298 + DiameterAVPCodeInbandSecurityID DiameterAVPCode = 299 + DiameterAVPCodeAccountingRecordType DiameterAVPCode = 480 + DiameterAVPCodeAccountingRealtimeReq DiameterAVPCode = 483 + DiameterAVPCodeAccountingRecordNumber DiameterAVPCode = 485 +) + +// 3GPP Vendor AVP Codes (Vendor ID: 10415) +const ( + DiameterAVPCode3GPPUserName DiameterAVPCode = 1 + DiameterAVPCode3GPPMSISDN DiameterAVPCode = 8 + DiameterAVPCode3GPPVisitedPLMNId DiameterAVPCode = 9 + DiameterAVPCode3GPPUserEquipmentType DiameterAVPCode = 21 + DiameterAVPCode3GPPUserEquipmentValue DiameterAVPCode = 22 + DiameterAVPCode3GPPCCRequestNumber DiameterAVPCode = 415 + DiameterAVPCode3GPPCCRequestType DiameterAVPCode = 416 + DiameterAVPCode3GPPSubscriptionId DiameterAVPCode = 443 + DiameterAVPCode3GPPSubscriptionIdData DiameterAVPCode = 444 + DiameterAVPCode3GPPSubscriptionIdType DiameterAVPCode = 450 + DiameterAVPCode3GPPServingNode DiameterAVPCode = 873 + DiameterAVPCode3GPPRatingGroup DiameterAVPCode = 1032 + DiameterAVPCode3GPPTraceData DiameterAVPCode = 1263 +) + +// ETSI Vendor AVP Codes (Vendor ID: 13019) +const ( + DiameterAVPCodeETSISIPAuthDataItem DiameterAVPCode = 311 + DiameterAVPCodeETSIAFChargingIdentifier DiameterAVPCode = 505 + DiameterAVPCodeETSIVisitedNetworkID DiameterAVPCode = 600 + DiameterAVPCodeETSIPublicIdentity DiameterAVPCode = 601 + DiameterAVPCodeETSIServerName DiameterAVPCode = 602 + DiameterAVPCodeETSIServerAssignmentType DiameterAVPCode = 603 + DiameterAVPCodeETSIUserDataAlreadyAvail DiameterAVPCode = 606 + DiameterAVPCodeETSIChargingInformation DiameterAVPCode = 610 + DiameterAVPCodeETSISupportedFeatures DiameterAVPCode = 629 + DiameterAVPCodeETSIFeatureListID DiameterAVPCode = 630 + DiameterAVPCodeETSIFeatureList DiameterAVPCode = 631 +) + +// DiameterAVPType represents the data type of an AVP +type DiameterAVPType uint8 + +const ( + DiameterAVPTypeOctetString DiameterAVPType = iota + DiameterAVPTypeInteger32 + DiameterAVPTypeInteger64 + DiameterAVPTypeUnsigned32 + DiameterAVPTypeUnsigned64 + DiameterAVPTypeFloat32 + DiameterAVPTypeFloat64 + DiameterAVPTypeGrouped + DiameterAVPTypeAddress + DiameterAVPTypeTime + DiameterAVPTypeUTF8String + DiameterAVPTypeDiameterIdentity + DiameterAVPTypeDiameterURI + DiameterAVPTypeEnumerated + DiameterAVPTypeIPFilterRule +) + +// AVPKey uniquely identifies an AVP by its code and optional vendor ID +type AVPKey struct { + Code uint32 + VendorID uint32 +} + +// diameterAVPTypeMap maps standard AVP codes to their data types +var diameterAVPTypeMap = map[uint32]DiameterAVPType{ + uint32(DiameterAVPCodeUserName): DiameterAVPTypeUTF8String, + uint32(DiameterAVPCodeSessionID): DiameterAVPTypeUTF8String, + uint32(DiameterAVPCodeOriginHost): DiameterAVPTypeDiameterIdentity, + uint32(DiameterAVPCodeOriginRealm): DiameterAVPTypeDiameterIdentity, + uint32(DiameterAVPCodeDestinationHost): DiameterAVPTypeDiameterIdentity, + uint32(DiameterAVPCodeDestinationRealm): DiameterAVPTypeDiameterIdentity, + uint32(DiameterAVPCodeHostIPAddress): DiameterAVPTypeAddress, + uint32(DiameterAVPCodeAuthApplicationID): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeAcctApplicationID): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeVendorID): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeProductName): DiameterAVPTypeUTF8String, + uint32(DiameterAVPCodeResultCode): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeSessionTimeout): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeAuthRequestType): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeAuthGracePeriod): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeAuthSessionState): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeOriginStateID): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeFailedAVP): DiameterAVPTypeGrouped, + uint32(DiameterAVPCodeProxyInfo): DiameterAVPTypeGrouped, + uint32(DiameterAVPCodeRouteRecord): DiameterAVPTypeDiameterIdentity, + uint32(DiameterAVPCodeExperimentalResult): DiameterAVPTypeGrouped, + uint32(DiameterAVPCodeExperimentalResultCode): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeVendorSpecificAppID): DiameterAVPTypeGrouped, + uint32(DiameterAVPCodeEventTimestamp): DiameterAVPTypeTime, + uint32(DiameterAVPCodeAccountingRecordType): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeAccountingRealtimeReq): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeAccountingRecordNumber): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeReAuthRequestType): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeSessionBinding): DiameterAVPTypeUnsigned32, + uint32(DiameterAVPCodeDisconnectCause): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeTerminationCause): DiameterAVPTypeEnumerated, + uint32(DiameterAVPCodeProxyHost): DiameterAVPTypeDiameterIdentity, + uint32(DiameterAVPCodeErrorMessage): DiameterAVPTypeUTF8String, + uint32(DiameterAVPCodeClass): DiameterAVPTypeOctetString, + uint32(DiameterAVPCodeProxyState): DiameterAVPTypeOctetString, +} + +// diameterVendorAVPTypeMap maps vendor-specific AVP (code, vendorID) to their data types +var diameterVendorAVPTypeMap = map[AVPKey]DiameterAVPType{ + // 3GPP Vendor AVPs (Vendor ID: 10415) + {Code: uint32(DiameterAVPCode3GPPUserName), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeUTF8String, + {Code: uint32(DiameterAVPCode3GPPMSISDN), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeUnsigned32, + {Code: uint32(DiameterAVPCode3GPPVisitedPLMNId), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeOctetString, + {Code: uint32(DiameterAVPCode3GPPUserEquipmentType), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeEnumerated, + {Code: uint32(DiameterAVPCode3GPPUserEquipmentValue), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeOctetString, + {Code: uint32(DiameterAVPCode3GPPCCRequestNumber), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeUnsigned32, + {Code: uint32(DiameterAVPCode3GPPCCRequestType), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeEnumerated, + {Code: uint32(DiameterAVPCode3GPPSubscriptionId), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeGrouped, + {Code: uint32(DiameterAVPCode3GPPSubscriptionIdType), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeEnumerated, + {Code: uint32(DiameterAVPCode3GPPSubscriptionIdData), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeUTF8String, + {Code: uint32(DiameterAVPCode3GPPServingNode), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeOctetString, + {Code: uint32(DiameterAVPCode3GPPRatingGroup), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeUnsigned32, + {Code: uint32(DiameterAVPCode3GPPTraceData), VendorID: uint32(DiameterVendor3GPP)}: DiameterAVPTypeGrouped, + + // ETSI Vendor AVPs (Vendor ID: 13019) + {Code: uint32(DiameterAVPCodeETSISIPAuthDataItem), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeOctetString, + {Code: uint32(DiameterAVPCodeETSIAFChargingIdentifier), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeUnsigned32, + {Code: uint32(DiameterAVPCodeETSIVisitedNetworkID), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeGrouped, + {Code: uint32(DiameterAVPCodeETSIPublicIdentity), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeUTF8String, + {Code: uint32(DiameterAVPCodeETSIServerName), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeUTF8String, + {Code: uint32(DiameterAVPCodeETSIServerAssignmentType), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeEnumerated, + {Code: uint32(DiameterAVPCodeETSIUserDataAlreadyAvail), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeEnumerated, + {Code: uint32(DiameterAVPCodeETSIChargingInformation), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeGrouped, + {Code: uint32(DiameterAVPCodeETSISupportedFeatures), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeGrouped, + {Code: uint32(DiameterAVPCodeETSIFeatureListID), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeUnsigned32, + {Code: uint32(DiameterAVPCodeETSIFeatureList), VendorID: uint32(DiameterVendorETSI)}: DiameterAVPTypeUnsigned32, +} + +// GetDiameterAVPType returns the data type for an AVP based on its code and vendor ID +func GetDiameterAVPType(code uint32, vendorID uint32) (DiameterAVPType, bool) { + // First check vendor-specific AVPs if vendor ID is set + if vendorID != 0 { + key := AVPKey{Code: code, VendorID: vendorID} + if avpType, ok := diameterVendorAVPTypeMap[key]; ok { + return avpType, true + } + } + + // Then check standard AVPs + if avpType, ok := diameterAVPTypeMap[code]; ok { + return avpType, true + } + + return DiameterAVPTypeOctetString, false +} diff --git a/vendor/github.com/gopacket/gopacket/layers/diameter_avp_decoders.go b/vendor/github.com/gopacket/gopacket/layers/diameter_avp_decoders.go new file mode 100644 index 000000000..40b24431d --- /dev/null +++ b/vendor/github.com/gopacket/gopacket/layers/diameter_avp_decoders.go @@ -0,0 +1,152 @@ +package layers + +import ( + "encoding/binary" + "errors" + "fmt" +) + +// decodeDiameterAVP decodes a single AVP and returns it along with bytes consumed +func decodeDiameterAVP(data []byte) (DiameterAVP, int, error) { + if len(data) < 8 { + return DiameterAVP{}, 0, errors.New("AVP too short") + } + + avp := DiameterAVP{} + + // AVP Code (bytes 0-3) + avp.Code = binary.BigEndian.Uint32(data[0:4]) + + // AVP Flags (byte 4) + avp.Flags.Vendor = (data[4] & 0x80) != 0 + avp.Flags.Mandatory = (data[4] & 0x40) != 0 + avp.Flags.Protected = (data[4] & 0x20) != 0 + + // AVP Length is 24 bits (bytes 5-7) + avp.Length = uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]) + + if avp.Length < 8 { + return DiameterAVP{}, 0, fmt.Errorf("invalid AVP length: %d", avp.Length) + } + + headerSize := 8 + dataOffset := 8 + + // Vendor ID (optional, present if Vendor flag is set) + if avp.Flags.Vendor { + if len(data) < 12 { + return DiameterAVP{}, 0, errors.New("AVP with vendor flag too short") + } + avp.VendorID = binary.BigEndian.Uint32(data[8:12]) + headerSize = 12 + dataOffset = 12 + } + + // Calculate padding (AVPs are padded to 4-byte boundaries) + paddedLength := avp.Length + if avp.Length%4 != 0 { + paddedLength = avp.Length + (4 - avp.Length%4) + } + + if uint32(len(data)) < paddedLength { + return DiameterAVP{}, 0, fmt.Errorf("AVP data truncated: expected %d bytes, got %d", paddedLength, len(data)) + } + + // Extract AVP data + dataLength := avp.Length - uint32(headerSize) + avp.Data = make([]byte, dataLength) + copy(avp.Data, data[dataOffset:dataOffset+int(dataLength)]) + + // Check if this is a Grouped AVP and decode sub-AVPs + // Use vendor-aware type detection + if avpType, ok := GetDiameterAVPType(avp.Code, avp.VendorID); ok && avpType == DiameterAVPTypeGrouped { + avp.GroupedAVPs = []DiameterAVP{} + subAVPData := avp.Data + for len(subAVPData) >= 8 { + subAVP, consumed, err := decodeDiameterAVP(subAVPData) + if err != nil { + break + } + avp.GroupedAVPs = append(avp.GroupedAVPs, subAVP) + subAVPData = subAVPData[consumed:] + } + } + + return avp, int(paddedLength), nil +} + +// ParseDiameterAVPs parses all AVPs from a data slice +func ParseDiameterAVPs(data []byte) ([]DiameterAVP, error) { + avps := []DiameterAVP{} + + for len(data) >= 8 { + avp, bytesConsumed, err := decodeDiameterAVP(data) + if err != nil { + return avps, err + } + avps = append(avps, avp) + data = data[bytesConsumed:] + } + + return avps, nil +} + +// SerializeDiameterAVP serializes a Diameter AVP to bytes +func SerializeDiameterAVP(avp *DiameterAVP) []byte { + headerSize := 8 + if avp.Flags.Vendor { + headerSize = 12 + } + + length := headerSize + len(avp.Data) + paddedLength := length + if length%4 != 0 { + paddedLength = length + (4 - length%4) + } + + bytes := make([]byte, paddedLength) + + // AVP Code + binary.BigEndian.PutUint32(bytes[0:4], avp.Code) + + // AVP Flags + bytes[4] = 0 + if avp.Flags.Vendor { + bytes[4] |= 0x80 + } + if avp.Flags.Mandatory { + bytes[4] |= 0x40 + } + if avp.Flags.Protected { + bytes[4] |= 0x20 + } + + // AVP Length (24 bits) + bytes[5] = byte(length >> 16) + bytes[6] = byte(length >> 8) + bytes[7] = byte(length) + + // Vendor ID (if present) + if avp.Flags.Vendor { + binary.BigEndian.PutUint32(bytes[8:12], avp.VendorID) + copy(bytes[12:], avp.Data) + } else { + copy(bytes[8:], avp.Data) + } + + return bytes +} + +// SerializedAVPLength returns the length of the AVP when serialized +func SerializedAVPLength(avp *DiameterAVP) int { + headerSize := 8 + if avp.Flags.Vendor { + headerSize = 12 + } + length := headerSize + len(avp.Data) + // Pad to 4-byte boundary + if length%4 != 0 { + length += 4 - (length % 4) + } + return length +} diff --git a/vendor/github.com/gopacket/gopacket/layers/dns.go b/vendor/github.com/gopacket/gopacket/layers/dns.go index 6a143f752..a4db29eb7 100644 --- a/vendor/github.com/gopacket/gopacket/layers/dns.go +++ b/vendor/github.com/gopacket/gopacket/layers/dns.go @@ -70,6 +70,7 @@ const ( DNSTypeTXT DNSType = 16 // text strings DNSTypeAAAA DNSType = 28 // a IPv6 host address [RFC3596] DNSTypeSRV DNSType = 33 // server discovery [RFC2782] [RFC6195] + DNSTypeNAPTR DNSType = 35 // naming authority pointer [RFC3403] DNSTypeOPT DNSType = 41 // OPT Pseudo-RR [RFC6891] DNSTypeRRSIG DNSType = 46 // RRSIG RR [RFC4034][RFC3755] DNSTypeDNSKEY DNSType = 48 // DNSKEY RR [RFC4034][RFC3755] @@ -118,6 +119,8 @@ func (dt DNSType) String() string { return "AAAA" case DNSTypeSRV: return "SRV" + case DNSTypeNAPTR: + return "NAPTR" case DNSTypeOPT: return "OPT" case DNSTypeRRSIG: @@ -449,6 +452,8 @@ func recSize(rr *DNSResourceRecord) int { return l case DNSTypeSRV: return 6 + len(rr.SRV.Name) + 2 + case DNSTypeNAPTR: + return 4 + 1 + len(rr.NAPTR.Flags) + 1 + len(rr.NAPTR.Service) + 1 + len(rr.NAPTR.Regexp) + len(rr.NAPTR.Replacement) + 2 case DNSTypeURI: return 4 + len(rr.URI.Target) case DNSTypeOPT: @@ -717,6 +722,7 @@ type DNSResourceRecord struct { SOA DNSSOA SRV DNSSRV MX DNSMX + NAPTR DNSNAPTR OPT []DNSOPT // See RFC 6891, section 6.1.2 RRSIG DNSRRSIG // See RFC 4034, section 3.1 DNSKEY DNSKEY // See RFC 4034, section 2.1 @@ -825,6 +831,20 @@ func (rr *DNSResourceRecord) encode(data []byte, offset int, opts gopacket.Seria binary.BigEndian.PutUint16(data[noff+12:], rr.SRV.Weight) binary.BigEndian.PutUint16(data[noff+14:], rr.SRV.Port) encodeName(rr.SRV.Name, data, noff+16) + case DNSTypeNAPTR: + binary.BigEndian.PutUint16(data[noff+10:], rr.NAPTR.Order) + binary.BigEndian.PutUint16(data[noff+12:], rr.NAPTR.Preference) + noff2 := noff + 14 + data[noff2] = byte(len(rr.NAPTR.Flags)) + copy(data[noff2+1:], rr.NAPTR.Flags) + noff2 += 1 + len(rr.NAPTR.Flags) + data[noff2] = byte(len(rr.NAPTR.Service)) + copy(data[noff2+1:], rr.NAPTR.Service) + noff2 += 1 + len(rr.NAPTR.Service) + data[noff2] = byte(len(rr.NAPTR.Regexp)) + copy(data[noff2+1:], rr.NAPTR.Regexp) + noff2 += 1 + len(rr.NAPTR.Regexp) + encodeName(rr.NAPTR.Replacement, data, noff2) case DNSTypeURI: binary.BigEndian.PutUint16(data[noff+10:], rr.URI.Priority) binary.BigEndian.PutUint16(data[noff+12:], rr.URI.Weight) @@ -1050,6 +1070,52 @@ func (rr *DNSResourceRecord) decodeRData(data []byte, offset int, buffer *[]byte return err } rr.SRV.Name = name + case DNSTypeNAPTR: + if len(data) < offset+4 { + return errors.New("NAPTR too small") + } + rr.NAPTR.Order = binary.BigEndian.Uint16(data[offset : offset+2]) + rr.NAPTR.Preference = binary.BigEndian.Uint16(data[offset+2 : offset+4]) + offset += 4 + // Decode Flags (character-string) + if len(data) < offset+1 { + return errors.New("NAPTR Flags missing") + } + flagsLen := int(data[offset]) + offset++ + if len(data) < offset+flagsLen { + return errors.New("NAPTR Flags truncated") + } + rr.NAPTR.Flags = data[offset : offset+flagsLen] + offset += flagsLen + // Decode Service (character-string) + if len(data) < offset+1 { + return errors.New("NAPTR Service missing") + } + serviceLen := int(data[offset]) + offset++ + if len(data) < offset+serviceLen { + return errors.New("NAPTR Service truncated") + } + rr.NAPTR.Service = data[offset : offset+serviceLen] + offset += serviceLen + // Decode Regexp (character-string) + if len(data) < offset+1 { + return errors.New("NAPTR Regexp missing") + } + regexpLen := int(data[offset]) + offset++ + if len(data) < offset+regexpLen { + return errors.New("NAPTR Regexp truncated") + } + rr.NAPTR.Regexp = data[offset : offset+regexpLen] + offset += regexpLen + // Decode Replacement (domain-name) + name, _, err := decodeName(data, offset, buffer, 1) + if err != nil { + return err + } + rr.NAPTR.Replacement = name case DNSTypeOPT: allOPT, err := decodeOPTs(data, offset) if err != nil { @@ -1097,6 +1163,17 @@ type DNSMX struct { Name []byte } +// DNSNAPTR is a Naming Authority Pointer record, used for application-specific +// string transformations (e.g., for SIP, ENUM). +type DNSNAPTR struct { + Order uint16 + Preference uint16 + Flags []byte + Service []byte + Regexp []byte + Replacement []byte +} + // DNSSVCB resource record is used to facilitate the lookup of // information needed to make connections to network services, such as // for HTTP origins. diff --git a/vendor/github.com/gopacket/gopacket/layers/enip.go b/vendor/github.com/gopacket/gopacket/layers/enip.go index 86e2f8e21..8e7ea9343 100644 --- a/vendor/github.com/gopacket/gopacket/layers/enip.go +++ b/vendor/github.com/gopacket/gopacket/layers/enip.go @@ -1,10 +1,7 @@ -// Copyright 2018, The GoPacket Authors, All rights reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file in the root of the source -// tree. -// -//****************************************************************************** +// EtherNet/IP (ENIP) protocol support for gopacket. +// EtherNet/IP is an industrial Ethernet protocol that encapsulates CIP +// (Common Industrial Protocol) over TCP/IP. +// See: https://www.odva.org/technology-standards/key-technologies/ethernet-ip/ package layers @@ -16,21 +13,28 @@ import ( "github.com/gopacket/gopacket" ) -//****************************************************************************** -// -// ENIP (Ethernet/IP) Decoding Layer -// ------------------------------------------ -// This file provides a GoPacket decoding layer for ENIP (Ethernet/IP). -// Ethernet/IP is an industrial protocol that encapsulates CIP (Common Industrial Protocol) -// -//****************************************************************************** +const ( + enipMinPacketLen int = 24 + enipMinRegSessionPacketLen int = 4 + enipMinSendRRDataPacketLen int = 36 + + // TCPPortENIP is the TCP port used to transport EtherNet/IP packets + TCPPortENIP uint16 = 44818 + // UDPPortENIP is the UDP port used to transport EtherNet/IP packets + UDPPortENIP uint16 = 2222 +) -const enipHeaderSize = 24 +var ( + // ErrENIPDataTooSmall is returned if an EtherNet/IP packet is truncated + ErrENIPDataTooSmall = errors.New("ENIP packet data truncated") + // ErrENIPUnknownDataFormat is returned if an unknown data format ID is encountered + ErrENIPUnknownDataFormat = errors.New("ENIP unknown data format ID") +) -// ENIPCommand represents the command code in an ENIP packet +// ENIPCommand is an EtherNet/IP command code type ENIPCommand uint16 -// ENIP Command codes +// ENIP Command constants const ( ENIPCommandNOP ENIPCommand = 0x0000 ENIPCommandListServices ENIPCommand = 0x0004 @@ -44,6 +48,7 @@ const ( ENIPCommandCancel ENIPCommand = 0x0073 ) +// String returns a human-readable string representation of the ENIP command func (ec ENIPCommand) String() string { switch ec { case ENIPCommandNOP: @@ -67,14 +72,14 @@ func (ec ENIPCommand) String() string { case ENIPCommandCancel: return "Cancel" default: - return fmt.Sprintf("Unknown Command (0x%04x)", uint16(ec)) + return fmt.Sprintf("Unknown(0x%04X)", uint16(ec)) } } -// ENIPStatus represents the status code in an ENIP packet +// ENIPStatus represents an EtherNet/IP status code type ENIPStatus uint32 -// ENIP Status codes +// ENIP Status constants const ( ENIPStatusSuccess ENIPStatus = 0x0000 ENIPStatusInvalidCommand ENIPStatus = 0x0001 @@ -85,6 +90,7 @@ const ( ENIPStatusUnsupportedProtocol ENIPStatus = 0x0069 ) +// String returns a human-readable string representation of the ENIP status func (es ENIPStatus) String() string { switch es { case ENIPStatusSuccess: @@ -102,128 +108,177 @@ func (es ENIPStatus) String() string { case ENIPStatusUnsupportedProtocol: return "Unsupported Protocol" default: - return fmt.Sprintf("Unknown Status (0x%08x)", uint32(es)) + return fmt.Sprintf("Unknown(0x%08X)", uint32(es)) } } -//****************************************************************************** - -// ENIP represents an Ethernet/IP packet +// ENIP implements decoding of EtherNet/IP, a protocol used to transport the +// Common Industrial Protocol over standard OSI networks. EtherNet/IP transports +// over both TCP and UDP. +// See the EtherNet/IP Developer's Guide for more information: https://www.odva.org/Portals/0/Library/Publications_Numbered/PUB00213R0_EtherNetIP_Developers_Guide.pdf type ENIP struct { BaseLayer - - Command ENIPCommand // Command code - Length uint16 // Length of data portion in bytes - SessionHandle uint32 // Session identification - Status ENIPStatus // Status code - SenderContext uint64 // Sender context - Options uint32 // Options flags + Command ENIPCommand + Length uint16 + SessionHandle uint32 + Status uint32 + SenderContext []byte + Options uint32 + CommandSpecific ENIPCommandSpecificData } -//****************************************************************************** - -// LayerType returns the layer type of the ENIP object -func (e *ENIP) LayerType() gopacket.LayerType { - return LayerTypeENIP +// ENIPCommandSpecificData contains data specific to a command. This may +// include another EtherNet/IP packet embedded within the Data structure. +type ENIPCommandSpecificData struct { + Cmd ENIPCommand + Data []byte } -//****************************************************************************** - -// decodeENIP analyses a byte slice and attempts to decode it as an ENIP packet -func decodeENIP(data []byte, p gopacket.PacketBuilder) error { - enip := &ENIP{} - err := enip.DecodeFromBytes(data, p) - if err != nil { - return err - } - p.AddLayer(enip) - p.SetApplicationLayer(enip) - return p.NextDecoder(enip.NextLayerType()) +func init() { + RegisterTCPPortLayerType(TCPPort(TCPPortENIP), LayerTypeENIP) + RegisterUDPPortLayerType(UDPPort(UDPPortENIP), LayerTypeENIP) } -//****************************************************************************** - -// DecodeFromBytes analyses a byte slice and attempts to decode it as an ENIP packet -func (e *ENIP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { - if len(data) < enipHeaderSize { - df.SetTruncated() - return errors.New("ENIP packet too short") - } - - // Parse ENIP header - e.Command = ENIPCommand(binary.LittleEndian.Uint16(data[0:2])) - e.Length = binary.LittleEndian.Uint16(data[2:4]) - e.SessionHandle = binary.LittleEndian.Uint32(data[4:8]) - e.Status = ENIPStatus(binary.LittleEndian.Uint32(data[8:12])) - - // Sender context is 8 bytes at offset 12 - e.SenderContext = binary.LittleEndian.Uint64(data[12:20]) - - e.Options = binary.LittleEndian.Uint32(data[20:24]) - - // Check if we have enough data for the payload - totalLength := enipHeaderSize + int(e.Length) - if len(data) < totalLength { +// DecodeFromBytes parses the contents of `data` as an EtherNet/IP packet. +func (enip *ENIP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + if len(data) < enipMinPacketLen { df.SetTruncated() - return fmt.Errorf("ENIP packet truncated: expected %d bytes, got %d", totalLength, len(data)) + return ErrENIPDataTooSmall } + enip.Command = ENIPCommand(binary.LittleEndian.Uint16(data[0:2])) + enip.Length = binary.LittleEndian.Uint16(data[2:4]) + enip.SessionHandle = binary.LittleEndian.Uint32(data[4:8]) + enip.Status = binary.LittleEndian.Uint32(data[8:12]) + enip.SenderContext = data[12:20] + enip.Options = binary.LittleEndian.Uint32(data[20:24]) + return enip.getPayload(data, df) +} - e.BaseLayer = BaseLayer{ - Contents: data[:enipHeaderSize], - Payload: data[enipHeaderSize:totalLength], +func (enip *ENIP) getPayload(data []byte, df gopacket.DecodeFeedback) (err error) { + enip.CommandSpecific.Cmd = enip.Command + switch enip.Command { + case ENIPCommandRegisterSession: // Register session command + if len(data) < 28 { // 24 byte header + 4 byte protocol version/options + df.SetTruncated() + err = ErrENIPDataTooSmall + return + } + enip.CommandSpecific.Data = data[24:28] + enip.Contents = data[0:28] + enip.Payload = data[28:] + case ENIPCommandSendUnitData, ENIPCommandSendRRData: + if len(data) < enipMinSendRRDataPacketLen { + df.SetTruncated() + return ErrENIPDataTooSmall + } + // Grab the item count + itemCount := int(binary.LittleEndian.Uint16(data[30:32])) + csdEnd := 32 + for i := 0; i < itemCount; i++ { + if csdEnd+4 > len(data) { + df.SetTruncated() + return ErrENIPDataTooSmall + } + dataFormatID := binary.LittleEndian.Uint16(data[csdEnd:]) + itemLen, err := getDataFormatIDLen(dataFormatID, data[csdEnd+2:]) + if err != nil { + return err + } + csdEnd += itemLen + } + if len(data) < csdEnd { + df.SetTruncated() + return ErrENIPDataTooSmall + } + enip.CommandSpecific.Data = data[24:csdEnd] + enip.Contents = data[0:csdEnd] + enip.Payload = data[csdEnd:] + default: + enip.CommandSpecific.Data = data[24:] + enip.Contents = data + enip.Payload = []byte{} } - - return nil + return } -//****************************************************************************** - -// NextLayerType returns the layer type of the ENIP payload -// For SendRRData and SendUnitData commands, the payload typically contains CIP data -func (e *ENIP) NextLayerType() gopacket.LayerType { - // Commands that typically contain CIP data - switch e.Command { - case ENIPCommandSendRRData, ENIPCommandSendUnitData: - // The payload contains CIP encapsulation, but we'll simplify and try to decode as CIP - // In reality, SendRRData and SendUnitData have additional encapsulation headers - // For now, we'll just return CIP and let it handle what it can - if len(e.Payload()) > 0 { - return LayerTypeCIP +func getDataFormatIDLen(id uint16, data []byte) (int, error) { + switch id { + case 0x0000: + return 4, nil // ID (2 bytes) + length field (2 bytes) + case 0x000C: + return 8, nil // Sockaddr info + case 0x00A1: + if len(data) < 2 { + return 0, ErrENIPDataTooSmall + } + length := int(binary.LittleEndian.Uint16(data)) + totalLen := 4 + length + // Ensure the claimed item length fits in the remaining buffer (data includes the 2-byte length field) + if totalLen < 0 || length < 0 || 2+length > len(data) { + return 0, ErrENIPDataTooSmall } + return totalLen, nil // Connected data item + case 0x00B1: + return 6, nil // Connected address item + case 0x00B2: + return 4, nil // Sequenced address item + case 0x0100: + return 4, nil // List services response + case 0x8000: + return 4, nil // CIP identity + case 0x8001: + return 2, nil // CIP security + case 0x8002: + return 2, nil // EtherNet/IP capability + default: + return 0, ErrENIPUnknownDataFormat } - return gopacket.LayerTypePayload } -//****************************************************************************** +// LayerType returns LayerTypeENIP +func (enip *ENIP) LayerType() gopacket.LayerType { return LayerTypeENIP } -// Payload returns the ENIP payload bytes -func (e *ENIP) Payload() []byte { - return e.BaseLayer.Payload -} +// CanDecode returns LayerTypeENIP +func (enip *ENIP) CanDecode() gopacket.LayerClass { return LayerTypeENIP } -// CanDecode returns the set of layer types that this DecodingLayer can decode -func (e *ENIP) CanDecode() gopacket.LayerClass { - return LayerTypeENIP +// NextLayerType returns either LayerTypePayload or the next layer type +// derived from the command specific data +func (enip *ENIP) NextLayerType() (nl gopacket.LayerType) { + switch enip.Command { + case ENIPCommandSendRRData: + fallthrough + case ENIPCommandSendUnitData: + nl = enip.CommandSpecific.NextLayer() + case ENIPCommandRegisterSession: + fallthrough + default: + nl = gopacket.LayerTypePayload + } + return } -// SerializeTo writes the serialized form of this layer into the SerializationBuffer -func (e *ENIP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { - bytes, err := b.PrependBytes(enipHeaderSize) - if err != nil { - return err +func decodeENIP(data []byte, p gopacket.PacketBuilder) error { + if len(data) < enipMinPacketLen { + p.SetTruncated() + return ErrENIPDataTooSmall } + enip := &ENIP{} + return decodingLayerDecoder(enip, data, p) +} - binary.LittleEndian.PutUint16(bytes[0:2], uint16(e.Command)) - - if opts.FixLengths { - e.Length = uint16(len(b.Bytes()) - enipHeaderSize) +// NextLayer derives the next layer type by checking for a CIP marker +// at the start of the command specific data, returning LayerTypeCIP +// if found; if not present, the next layer type is LayerTypePayload +func (csd ENIPCommandSpecificData) NextLayer() (nl gopacket.LayerType) { + if len(csd.Data) < 4 { + nl = gopacket.LayerTypePayload + return } - binary.LittleEndian.PutUint16(bytes[2:4], e.Length) - - binary.LittleEndian.PutUint32(bytes[4:8], e.SessionHandle) - binary.LittleEndian.PutUint32(bytes[8:12], uint32(e.Status)) - binary.LittleEndian.PutUint64(bytes[12:20], e.SenderContext) - binary.LittleEndian.PutUint32(bytes[20:24], e.Options) - - return nil + switch binary.LittleEndian.Uint32(csd.Data) { + case 0x0: + nl = LayerTypeCIP + default: + nl = gopacket.LayerTypePayload + } + return } diff --git a/vendor/github.com/gopacket/gopacket/layers/geneve.go b/vendor/github.com/gopacket/gopacket/layers/geneve.go index bb1318a45..b3cd2c01a 100644 --- a/vendor/github.com/gopacket/gopacket/layers/geneve.go +++ b/vendor/github.com/gopacket/gopacket/layers/geneve.go @@ -86,6 +86,7 @@ func (gn *Geneve) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error gn.Version = data[0] >> 7 gn.OptionsLength = (data[0] & 0x3f) * 4 + gn.Options = gn.Options[:0] gn.OAMPacket = data[1]&0x80 > 0 gn.CriticalOption = data[1]&0x40 > 0 diff --git a/vendor/github.com/gopacket/gopacket/layers/gre.go b/vendor/github.com/gopacket/gopacket/layers/gre.go index 308166cc1..c8c636c64 100644 --- a/vendor/github.com/gopacket/gopacket/layers/gre.go +++ b/vendor/github.com/gopacket/gopacket/layers/gre.go @@ -8,6 +8,7 @@ package layers import ( "encoding/binary" + "errors" "github.com/gopacket/gopacket" ) @@ -37,6 +38,28 @@ func (g *GRE) LayerType() gopacket.LayerType { return LayerTypeGRE } // DecodeFromBytes decodes the given bytes into this layer. func (g *GRE) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + if len(data) < 4 { + df.SetTruncated() + return errors.New("GRE packet too small") + } + truncated := func() error { + df.SetTruncated() + return errors.New("GRE packet truncated") + } + requireBytes := func(offset, size int) error { + if len(data)-offset < size { + return truncated() + } + return nil + } + + g.BaseLayer = BaseLayer{} + g.GRERouting = nil + g.Checksum = 0 + g.Offset = 0 + g.Key = 0 + g.Seq = 0 + g.Ack = 0 g.ChecksumPresent = data[0]&0x80 != 0 g.RoutingPresent = data[0]&0x40 != 0 g.KeyPresent = data[0]&0x20 != 0 @@ -49,26 +72,41 @@ func (g *GRE) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { g.Protocol = EthernetType(binary.BigEndian.Uint16(data[2:4])) offset := 4 if g.ChecksumPresent || g.RoutingPresent { + if err := requireBytes(offset, 4); err != nil { + return err + } g.Checksum = binary.BigEndian.Uint16(data[offset : offset+2]) g.Offset = binary.BigEndian.Uint16(data[offset+2 : offset+4]) offset += 4 } if g.KeyPresent { + if err := requireBytes(offset, 4); err != nil { + return err + } g.Key = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 } if g.SeqPresent { + if err := requireBytes(offset, 4); err != nil { + return err + } g.Seq = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 } if g.RoutingPresent { tail := &g.GRERouting for { + if err := requireBytes(offset, 4); err != nil { + return err + } sre := &GRERouting{ AddressFamily: binary.BigEndian.Uint16(data[offset : offset+2]), SREOffset: data[offset+2], SRELength: data[offset+3], } + if err := requireBytes(offset+4, int(sre.SRELength)); err != nil { + return err + } sre.RoutingInformation = data[offset+4 : offset+4+int(sre.SRELength)] offset += 4 + int(sre.SRELength) if sre.AddressFamily == 0 && sre.SRELength == 0 { @@ -79,6 +117,9 @@ func (g *GRE) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { } } if g.AckPresent { + if err := requireBytes(offset, 4); err != nil { + return err + } g.Ack = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 } diff --git a/vendor/github.com/gopacket/gopacket/layers/layertypes.go b/vendor/github.com/gopacket/gopacket/layers/layertypes.go index 30931e762..9bdb2ab28 100644 --- a/vendor/github.com/gopacket/gopacket/layers/layertypes.go +++ b/vendor/github.com/gopacket/gopacket/layers/layertypes.go @@ -155,7 +155,10 @@ var ( LayerTypeAPSP = gopacket.RegisterLayerType(150, gopacket.LayerTypeMetadata{Name: "APSP", Decoder: gopacket.DecodeFunc(decodeAPSP)}) LayerTypeENIP = gopacket.RegisterLayerType(151, gopacket.LayerTypeMetadata{Name: "ENIP", Decoder: gopacket.DecodeFunc(decodeENIP)}) LayerTypeCIP = gopacket.RegisterLayerType(152, gopacket.LayerTypeMetadata{Name: "CIP", Decoder: gopacket.DecodeFunc(decodeCIP)}) + LayerTypeModbus = gopacket.RegisterLayerType(153, gopacket.LayerTypeMetadata{Name: "Modbus", Decoder: gopacket.DecodeFunc(decodeModbus)}) + LayerTypeDiameter = gopacket.RegisterLayerType(154, gopacket.LayerTypeMetadata{Name: "Diameter", Decoder: gopacket.DecodeFunc(decodeDiameter)}) LayerTypeLinuxSLL2 = gopacket.RegisterLayerType(276, gopacket.LayerTypeMetadata{Name: "Linux SLL2", Decoder: gopacket.DecodeFunc(decodeLinuxSLL2)}) + LayerTypePktap = gopacket.RegisterLayerType(300, gopacket.LayerTypeMetadata{Name: "ApplePktap", Decoder: gopacket.DecodeFunc(decodePktapV1)}) ) var ( diff --git a/vendor/github.com/gopacket/gopacket/layers/lcm.go b/vendor/github.com/gopacket/gopacket/layers/lcm.go index 04db3bc3f..960e6de29 100644 --- a/vendor/github.com/gopacket/gopacket/layers/lcm.go +++ b/vendor/github.com/gopacket/gopacket/layers/lcm.go @@ -166,8 +166,10 @@ func (lcm *LCM) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { lcm.ChannelName = string(buffer) } - lcm.fingerprint = LCMFingerprint( - binary.BigEndian.Uint64(data[offset : offset+8])) + if len(data)-offset >= 8 { + lcm.fingerprint = LCMFingerprint( + binary.BigEndian.Uint64(data[offset : offset+8])) + } lcm.contents = data[:offset] lcm.payload = data[offset:] diff --git a/vendor/github.com/gopacket/gopacket/layers/modbus.go b/vendor/github.com/gopacket/gopacket/layers/modbus.go new file mode 100644 index 000000000..181e98104 --- /dev/null +++ b/vendor/github.com/gopacket/gopacket/layers/modbus.go @@ -0,0 +1,246 @@ +// Modbus protocol support for gopacket. +// This implements Modbus TCP (port 502) decoding according to the +// Modbus Application Protocol Specification V1.1b3. +// See: https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf + +package layers + +import ( + "encoding/binary" + "errors" + "fmt" + + "github.com/gopacket/gopacket" +) + +const ( + MBAPHeaderLen int = 7 + MinModbusPacketLen int = MBAPHeaderLen + 1 + ModbusPort uint16 = 502 +) + +var ( + ErrModbusDataTooSmall = errors.New("data too small for Modbus") + ErrModbusInvalidProtocol = errors.New("invalid Modbus protocol ID (expected 0)") +) + +// ModbusFunctionCode represents a Modbus function code +type ModbusFunctionCode byte + +// Modbus Function Code constants +const ( + ModbusFuncCodeReadCoils ModbusFunctionCode = 0x01 + ModbusFuncCodeReadDiscreteInputs ModbusFunctionCode = 0x02 + ModbusFuncCodeReadHoldingRegisters ModbusFunctionCode = 0x03 + ModbusFuncCodeReadInputRegisters ModbusFunctionCode = 0x04 + ModbusFuncCodeWriteSingleCoil ModbusFunctionCode = 0x05 + ModbusFuncCodeWriteSingleRegister ModbusFunctionCode = 0x06 + ModbusFuncCodeReadExceptionStatus ModbusFunctionCode = 0x07 + ModbusFuncCodeDiagnostics ModbusFunctionCode = 0x08 + ModbusFuncCodeGetCommEventCounter ModbusFunctionCode = 0x0B + ModbusFuncCodeGetCommEventLog ModbusFunctionCode = 0x0C + ModbusFuncCodeWriteMultipleCoils ModbusFunctionCode = 0x0F + ModbusFuncCodeWriteMultipleRegisters ModbusFunctionCode = 0x10 + ModbusFuncCodeReportSlaveID ModbusFunctionCode = 0x11 + ModbusFuncCodeReadFileRecord ModbusFunctionCode = 0x14 + ModbusFuncCodeWriteFileRecord ModbusFunctionCode = 0x15 + ModbusFuncCodeMaskWriteRegister ModbusFunctionCode = 0x16 + ModbusFuncCodeReadWriteMultipleRegs ModbusFunctionCode = 0x17 + ModbusFuncCodeReadFIFOQueue ModbusFunctionCode = 0x18 + ModbusFuncCodeEncapsulatedInterface ModbusFunctionCode = 0x2B + // Exception mask (OR'd with function code for exception responses) + ModbusFuncCodeExceptionMask ModbusFunctionCode = 0x80 +) + +// String returns a human-readable string representation of the function code +func (fc ModbusFunctionCode) String() string { + isException := (fc & ModbusFuncCodeExceptionMask) != 0 + code := fc & ^ModbusFuncCodeExceptionMask + + var name string + switch code { + case ModbusFuncCodeReadCoils: + name = "Read Coils" + case ModbusFuncCodeReadDiscreteInputs: + name = "Read Discrete Inputs" + case ModbusFuncCodeReadHoldingRegisters: + name = "Read Holding Registers" + case ModbusFuncCodeReadInputRegisters: + name = "Read Input Registers" + case ModbusFuncCodeWriteSingleCoil: + name = "Write Single Coil" + case ModbusFuncCodeWriteSingleRegister: + name = "Write Single Register" + case ModbusFuncCodeReadExceptionStatus: + name = "Read Exception Status" + case ModbusFuncCodeDiagnostics: + name = "Diagnostics" + case ModbusFuncCodeGetCommEventCounter: + name = "Get Comm Event Counter" + case ModbusFuncCodeGetCommEventLog: + name = "Get Comm Event Log" + case ModbusFuncCodeWriteMultipleCoils: + name = "Write Multiple Coils" + case ModbusFuncCodeWriteMultipleRegisters: + name = "Write Multiple Registers" + case ModbusFuncCodeReportSlaveID: + name = "Report Slave ID" + case ModbusFuncCodeReadFileRecord: + name = "Read File Record" + case ModbusFuncCodeWriteFileRecord: + name = "Write File Record" + case ModbusFuncCodeMaskWriteRegister: + name = "Mask Write Register" + case ModbusFuncCodeReadWriteMultipleRegs: + name = "Read/Write Multiple Registers" + case ModbusFuncCodeReadFIFOQueue: + name = "Read FIFO Queue" + case ModbusFuncCodeEncapsulatedInterface: + name = "Encapsulated Interface Transport" + default: + name = fmt.Sprintf("Unknown(0x%02X)", byte(code)) + } + + if isException { + return "Exception: " + name + } + return name +} + +// ModbusExceptionCode represents a Modbus exception code +type ModbusExceptionCode byte + +// Modbus Exception Code constants +const ( + ModbusExceptionIllegalFunction ModbusExceptionCode = 0x01 + ModbusExceptionIllegalDataAddress ModbusExceptionCode = 0x02 + ModbusExceptionIllegalDataValue ModbusExceptionCode = 0x03 + ModbusExceptionSlaveDeviceFailure ModbusExceptionCode = 0x04 + ModbusExceptionAcknowledge ModbusExceptionCode = 0x05 + ModbusExceptionSlaveDeviceBusy ModbusExceptionCode = 0x06 + ModbusExceptionMemoryParityError ModbusExceptionCode = 0x08 + ModbusExceptionGatewayPathUnavailable ModbusExceptionCode = 0x0A + ModbusExceptionGatewayTargetDeviceFailedToRespond ModbusExceptionCode = 0x0B +) + +// String returns a human-readable string representation of the exception code +func (ec ModbusExceptionCode) String() string { + switch ec { + case ModbusExceptionIllegalFunction: + return "Illegal Function" + case ModbusExceptionIllegalDataAddress: + return "Illegal Data Address" + case ModbusExceptionIllegalDataValue: + return "Illegal Data Value" + case ModbusExceptionSlaveDeviceFailure: + return "Slave Device Failure" + case ModbusExceptionAcknowledge: + return "Acknowledge" + case ModbusExceptionSlaveDeviceBusy: + return "Slave Device Busy" + case ModbusExceptionMemoryParityError: + return "Memory Parity Error" + case ModbusExceptionGatewayPathUnavailable: + return "Gateway Path Unavailable" + case ModbusExceptionGatewayTargetDeviceFailedToRespond: + return "Gateway Target Device Failed to Respond" + default: + return fmt.Sprintf("Unknown(0x%02X)", byte(ec)) + } +} + +// MBAP represents the Modbus Application Protocol header +type MBAP struct { + TransactionID uint16 // Transaction identifier + ProtocolID uint16 // Protocol identifier (0 for Modbus) + Length uint16 // Length of remaining data + UnitID uint8 // Unit identifier +} + +// Modbus represents a Modbus TCP packet +type Modbus struct { + BaseLayer + MBAP + FunctionCode uint8 // Raw Modbus function code byte (includes exception bit 0x80 if present) + Exception bool // True if this is an exception response + ReqResp []byte // Request/Response data +} + +func init() { + RegisterTCPPortLayerType(TCPPort(ModbusPort), LayerTypeModbus) +} + +func (m *Modbus) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + if len(data) < MinModbusPacketLen { + df.SetTruncated() + return ErrModbusDataTooSmall + } + m.TransactionID = binary.BigEndian.Uint16(data[0:2]) + m.ProtocolID = binary.BigEndian.Uint16(data[2:4]) + m.Length = binary.BigEndian.Uint16(data[4:6]) + m.UnitID = data[6] + m.FunctionCode = data[7] + m.Exception = (m.FunctionCode & 0x80) != 0 + end := int(m.Length) + 6 + if len(data) < end || end < 8 { + df.SetTruncated() + return ErrModbusDataTooSmall + } + m.ReqResp = data[8:end] + m.Contents = data[:end] + m.Payload = data[end:] + return nil +} + +func (m *Modbus) LayerType() gopacket.LayerType { + return LayerTypeModbus +} + +func (m *Modbus) NextLayerType() gopacket.LayerType { + return gopacket.LayerTypeZero +} + +func (m *Modbus) CanDecode() gopacket.LayerClass { + return LayerTypeModbus +} + +func decodeModbus(data []byte, p gopacket.PacketBuilder) error { + if len(data) < MinModbusPacketLen { + p.SetTruncated() + return ErrModbusDataTooSmall + } + modbus := &Modbus{} + return decodingLayerDecoder(modbus, data, p) +} + +// Validate checks if the Modbus packet is valid according to the protocol specification +func (m *Modbus) Validate() error { + if m.ProtocolID != 0 { + return ErrModbusInvalidProtocol + } + // Length should include UnitID (1 byte) + FunctionCode (1 byte) + Data + expectedLength := 1 + 1 + len(m.ReqResp) + if int(m.Length) != expectedLength { + return errors.New("Modbus length field mismatch") + } + return nil +} + +// IsException returns true if this is a Modbus exception response +func (m *Modbus) IsException() bool { + return m.Exception +} + +// GetFunction returns the Modbus function code as a ModbusFunctionCode type +func (m *Modbus) GetFunction() ModbusFunctionCode { + return ModbusFunctionCode(m.FunctionCode) +} + +// GetExceptionCode returns the exception code from the first byte of ReqResp data +// Returns 0 if this is not an exception response or if there is no data +func (m *Modbus) GetExceptionCode() ModbusExceptionCode { + if !m.Exception || len(m.ReqResp) == 0 { + return 0 + } + return ModbusExceptionCode(m.ReqResp[0]) +} diff --git a/vendor/github.com/gopacket/gopacket/layers/pktap.go b/vendor/github.com/gopacket/gopacket/layers/pktap.go new file mode 100644 index 000000000..a2619074e --- /dev/null +++ b/vendor/github.com/gopacket/gopacket/layers/pktap.go @@ -0,0 +1,187 @@ +// Copyright 2024 Google, Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +package layers + +import ( + "encoding/binary" + "errors" + "fmt" + "strings" + + "github.com/gopacket/gopacket" +) + +const LinkTypeApplePKTAP LinkType = 149 // Apple pktap wrapper (Darwin only) + +// pktap v1 record types +const ( + PKTRecNone = 0 + PKTRecPacket = 1 +) + +// pktap v1 direction flags (lower 2 bits of pkt_flags) +const ( + PTHFlagDirIn = 0x00000001 + PTHFlagDirOut = 0x00000002 +) + +// PktapDirection represents the direction of a packet +type PktapDirection uint32 + +func (d PktapDirection) String() string { + switch d { + case PTHFlagDirIn: + return "in" + case PTHFlagDirOut: + return "out" + } + return "" +} + +// ServiceClass represents the SO_TC_* service class values +type ServiceClass uint32 + +func (s ServiceClass) String() string { + switch s { + case 0: + return "BK_SYS" + case 1: + return "BK" + case 2: + return "BE" + case 3: + return "RD" + case 4: + return "OAM" + case 5: + return "AV" + case 6: + return "RV" + case 7: + return "VI" + case 8: + return "VO" + case 9: + return "CTL" + } + return fmt.Sprintf("UNK(%d)", s) +} + +// PktapV1 is the Darwin-specific pktap v1 metadata header. +// It wraps packets with process/connection metadata at the kernel level. +// see: https://github.com/apple-oss-distributions/xnu/blob/xnu-12377.81.4/bsd/net/pktap.h#L89-L114 +type PktapV1 struct { + BaseLayer + HeaderLength uint32 // 0x00: total header length (156) + RecordType uint32 // 0x04: PKT_REC_PACKET=1 + DLT uint32 // 0x08: DLT type of inner packet + InterfaceName string // 0x0C: interface name (24 bytes) + Flags uint32 // 0x24: direction and other flags + ProtocolFamily uint32 // 0x28: protocol family (AF_INET=2, AF_INET6=30) + LinkLayerHeaderLength uint32 // 0x2C: link-layer header length + LinkLayerTrailerLength uint32 // 0x30: link-layer trailer length + PID uint32 // 0x34: process ID + CommandName string // 0x38: command name (20 bytes) + ServiceClass ServiceClass // 0x4C: service class + InterfaceType uint16 // 0x50: interface type + InterfaceUnit uint16 // 0x52: unit number + EffectivePID uint32 // 0x54: effective process ID + EffectiveCommandName string // 0x58: effective command name (20 bytes) +} + +// LayerType returns LayerTypePktap. +func (p *PktapV1) LayerType() gopacket.LayerType { return LayerTypePktap } + +// Direction returns the packet direction (in/out) +func (p *PktapV1) Direction() PktapDirection { + return PktapDirection(p.Flags & 0x3) +} + +// CanDecode returns the set of layer types that this DecodingLayer can decode. +func (p *PktapV1) CanDecode() gopacket.LayerClass { + return LayerTypePktap +} + +// NextLayerType returns the layer type of the inner packet (determined by DLT) +func (p *PktapV1) NextLayerType() gopacket.LayerType { + return LinkType(p.DLT).LayerType() +} + +// DecodeFromBytes decodes the given bytes into this layer. +func (p *PktapV1) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + //sizeof(struct pktap_header) == 156 + if len(data) < 156 { + return errors.New("pktap packet too small") + } + + p.HeaderLength = binary.LittleEndian.Uint32(data[0:4]) + if p.HeaderLength < 156 { + return fmt.Errorf("pktap v1 header length mismatch: got %d", p.HeaderLength) + } + + p.RecordType = binary.LittleEndian.Uint32(data[4:8]) + if p.RecordType != PKTRecPacket { + return fmt.Errorf("pktap unsupported record type: %d", p.RecordType) + } + + p.DLT = binary.LittleEndian.Uint32(data[8:12]) + + // Interface name: 24 bytes at offset 0x0C, null-terminated + ifname := string(data[0x0C : 0x0C+24]) + if nullIdx := strings.Index(ifname, "\x00"); nullIdx >= 0 { + ifname = ifname[:nullIdx] + } + p.InterfaceName = ifname + + p.Flags = binary.LittleEndian.Uint32(data[0x24:0x28]) + p.ProtocolFamily = binary.LittleEndian.Uint32(data[0x28:0x2C]) + p.LinkLayerHeaderLength = binary.LittleEndian.Uint32(data[0x2C:0x30]) + p.LinkLayerTrailerLength = binary.LittleEndian.Uint32(data[0x30:0x34]) + p.PID = binary.LittleEndian.Uint32(data[0x34:0x38]) + + // Command name: 20 bytes at offset 0x38, null-terminated + cmdname := string(data[0x38 : 0x38+20]) + if nullIdx := strings.Index(cmdname, "\x00"); nullIdx >= 0 { + cmdname = cmdname[:nullIdx] + } + p.CommandName = cmdname + + p.ServiceClass = ServiceClass(binary.LittleEndian.Uint32(data[0x4C:0x50])) + p.InterfaceType = binary.LittleEndian.Uint16(data[0x50:0x52]) + p.InterfaceUnit = binary.LittleEndian.Uint16(data[0x52:0x54]) + p.EffectivePID = binary.LittleEndian.Uint32(data[0x54:0x58]) + + // Effective command name: 20 bytes at offset 0x58, null-terminated + ecmdname := string(data[0x58 : 0x58+20]) + if nullIdx := strings.Index(ecmdname, "\x00"); nullIdx >= 0 { + ecmdname = ecmdname[:nullIdx] + } + p.EffectiveCommandName = ecmdname + + p.BaseLayer = BaseLayer{Contents: data[:p.HeaderLength], Payload: data[p.HeaderLength:]} + return nil +} + +// String returns a human-readable representation of the pktap metadata. +func (p *PktapV1) String() string { + return fmt.Sprintf("PktapV1(%s, proc %s:%d, eproc %s:%d, svc %s, %s, DLT %s (%d))", + p.InterfaceName, + p.CommandName, p.PID, + p.EffectiveCommandName, p.EffectivePID, + p.ServiceClass, + p.Direction(), + LinkType(p.DLT), p.DLT) +} + +func decodePktapV1(data []byte, p gopacket.PacketBuilder) error { + pktap := &PktapV1{} + if err := pktap.DecodeFromBytes(data, p); err != nil { + return err + } + p.AddLayer(pktap) + return p.NextDecoder(LinkType(pktap.DLT)) +} diff --git a/vendor/github.com/gopacket/gopacket/layers/pktap_darwin.go b/vendor/github.com/gopacket/gopacket/layers/pktap_darwin.go new file mode 100644 index 000000000..ad722c927 --- /dev/null +++ b/vendor/github.com/gopacket/gopacket/layers/pktap_darwin.go @@ -0,0 +1,28 @@ +// Copyright 2024 Google, Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +//go:build darwin +// +build darwin + +package layers + +import ( + "github.com/gopacket/gopacket" +) + +/* +#define DLT_USER2 149 + +#ifdef __APPLE__ +#define DLT_PKTAP DLT_USER2 +#else +#define DLT_PKTAP 258 +#endif +*/ + +func init() { + LinkTypeMetadata[LinkTypeApplePKTAP] = EnumMetadata{DecodeWith: gopacket.DecodeFunc(decodePktapV1), Name: "ApplePKTAP"} +} diff --git a/vendor/github.com/gopacket/gopacket/layers/ports.go b/vendor/github.com/gopacket/gopacket/layers/ports.go index 4257a0403..858a22d4d 100644 --- a/vendor/github.com/gopacket/gopacket/layers/ports.go +++ b/vendor/github.com/gopacket/gopacket/layers/ports.go @@ -78,7 +78,13 @@ func (a TCPPort) LayerType() gopacket.LayerType { return LayerTypeTLS case 2222: // EtherNet/IP-1 return LayerTypeENIP - case 5061: // ips + case 3868: // diameter + return LayerTypeDiameter + case 5061: // sips + return LayerTypeTLS + case 5082: // sip-tls + return LayerTypeTLS + case 5083: // sip-tls return LayerTypeTLS case 44818: // EtherNet/IP-2 return LayerTypeENIP @@ -145,10 +151,16 @@ func (a UDPPort) LayerType() gopacket.LayerType { return LayerTypeENIP case 3784: return LayerTypeBFD + case 3868: // diameter + return LayerTypeDiameter case 4789: return LayerTypeVXLAN case 5060: return LayerTypeSIP + case 5082: // sip + return LayerTypeSIP + case 5083: // sip + return LayerTypeSIP case 6081: return LayerTypeGeneve case 6343: @@ -190,6 +202,39 @@ func (a SCTPPort) String() string { return strconv.Itoa(int(a)) } +// LayerType returns a LayerType that would be able to decode the +// application payload. It uses some well-known ports such as 3868 for +// Diameter. +// +// Returns gopacket.LayerTypePayload for unknown/unsupported port numbers. +func (a SCTPPort) LayerType() gopacket.LayerType { + if sctpPortLayerTypeOverride.has(uint16(a)) { + return sctpPortLayerType[a] + } + switch a { + case 3868: // diameter + return LayerTypeDiameter + case 5060: // sip + return LayerTypeSIP + case 5082: // sip + return LayerTypeSIP + case 5083: // sip + return LayerTypeSIP + } + return gopacket.LayerTypePayload +} + +var sctpPortLayerTypeOverride bitfield + +var sctpPortLayerType = map[SCTPPort]gopacket.LayerType{} + +// RegisterSCTPPortLayerType creates a new mapping between a SCTPPort +// and an underlying LayerType. +func RegisterSCTPPortLayerType(port SCTPPort, layerType gopacket.LayerType) { + sctpPortLayerTypeOverride.set(uint16(port)) + sctpPortLayerType[port] = layerType +} + // String returns the port as "number(name)" if there's a well-known port name, // or just "number" if there isn't. Well-known names are stored in // UDPLitePortNames. diff --git a/vendor/github.com/gopacket/gopacket/layers/sctp.go b/vendor/github.com/gopacket/gopacket/layers/sctp.go index 368497bc5..30571b8ec 100644 --- a/vendor/github.com/gopacket/gopacket/layers/sctp.go +++ b/vendor/github.com/gopacket/gopacket/layers/sctp.go @@ -88,6 +88,13 @@ func (t *SCTP) CanDecode() gopacket.LayerClass { } func (t *SCTP) NextLayerType() gopacket.LayerType { + // Check if either source or destination port indicates a known protocol + if lt := t.SrcPort.LayerType(); lt != gopacket.LayerTypePayload { + return lt + } + if lt := t.DstPort.LayerType(); lt != gopacket.LayerTypePayload { + return lt + } return gopacket.LayerTypePayload } @@ -261,6 +268,7 @@ const ( SCTPPayloadDDPSegment = 16 SCTPPayloadDDPStream = 17 SCTPPayloadS1AP = 18 + SCTPPayloadDiameter = 46 ) func (p SCTPPayloadProtocol) String() string { @@ -303,6 +311,8 @@ func (p SCTPPayloadProtocol) String() string { return "DDPStream" case SCTPPayloadS1AP: return "S1AP" + case SCTPPayloadDiameter: + return "Diameter" } return fmt.Sprintf("Unknown(%d)", p) } diff --git a/vendor/github.com/gopacket/gopacket/layers/tcp.go b/vendor/github.com/gopacket/gopacket/layers/tcp.go index 782c0005f..075832686 100644 --- a/vendor/github.com/gopacket/gopacket/layers/tcp.go +++ b/vendor/github.com/gopacket/gopacket/layers/tcp.go @@ -347,6 +347,9 @@ OPTIONS: case TCPOptionKindMultipathTCP: tcp.Multipath = true opt.OptionLength = data[1] + if opt.OptionLength <= 0 { + return fmt.Errorf("MPTCP bad option length %d", opt.OptionLength) + } opt.OptionMultipath = MPTCPSubtype(data[2] >> 4) switch opt.OptionMultipath { case MPTCPSubtypeMPCAPABLE: diff --git a/vendor/modules.txt b/vendor/modules.txt index f5fc8d0a2..49a2f7861 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -217,7 +217,7 @@ github.com/google/go-cmp/cmp/internal/value # github.com/google/uuid v1.6.0 ## explicit github.com/google/uuid -# github.com/gopacket/gopacket v1.5.0 +# github.com/gopacket/gopacket v1.6.0 ## explicit; go 1.24.0 github.com/gopacket/gopacket github.com/gopacket/gopacket/layers