// Copyright 2012 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. // Enum types courtesy of... // http://search.cpan.org/~mchapman/Net-CDP-0.09/lib/Net/CDP.pm // https://code.google.com/p/ladvd/ // http://anonsvn.wireshark.org/viewvc/releases/wireshark-1.8.6/epan/dissectors/packet-cdp.c package layers import ( "encoding/binary" "fmt" "net" "github.com/google/gopacket" ) // CDPTLVType is the type of each TLV value in a CiscoDiscovery packet. type CDPTLVType uint16 // CDPTLVType values. const ( CDPTLVDevID CDPTLVType = 0x0001 CDPTLVAddress CDPTLVType = 0x0002 CDPTLVPortID CDPTLVType = 0x0003 CDPTLVCapabilities CDPTLVType = 0x0004 CDPTLVVersion CDPTLVType = 0x0005 CDPTLVPlatform CDPTLVType = 0x0006 CDPTLVIPPrefix CDPTLVType = 0x0007 CDPTLVHello CDPTLVType = 0x0008 CDPTLVVTPDomain CDPTLVType = 0x0009 CDPTLVNativeVLAN CDPTLVType = 0x000a CDPTLVFullDuplex CDPTLVType = 0x000b CDPTLVVLANReply CDPTLVType = 0x000e CDPTLVVLANQuery CDPTLVType = 0x000f CDPTLVPower CDPTLVType = 0x0010 CDPTLVMTU CDPTLVType = 0x0011 CDPTLVExtendedTrust CDPTLVType = 0x0012 CDPTLVUntrustedCOS CDPTLVType = 0x0013 CDPTLVSysName CDPTLVType = 0x0014 CDPTLVSysOID CDPTLVType = 0x0015 CDPTLVMgmtAddresses CDPTLVType = 0x0016 CDPTLVLocation CDPTLVType = 0x0017 CDPTLVExternalPortID CDPTLVType = 0x0018 CDPTLVPowerRequested CDPTLVType = 0x0019 CDPTLVPowerAvailable CDPTLVType = 0x001a CDPTLVPortUnidirectional CDPTLVType = 0x001b CDPTLVEnergyWise CDPTLVType = 0x001d CDPTLVSparePairPOE CDPTLVType = 0x001f ) // CiscoDiscoveryValue is a TLV value inside a CiscoDiscovery packet layer. type CiscoDiscoveryValue struct { Type CDPTLVType Length uint16 Value []byte } // CiscoDiscovery is a packet layer containing the Cisco Discovery Protocol. // See http://www.cisco.com/univercd/cc/td/doc/product/lan/trsrb/frames.htm#31885 type CiscoDiscovery struct { BaseLayer Version byte TTL byte Checksum uint16 Values []CiscoDiscoveryValue } // CDPCapability is the set of capabilities advertised by a CDP device. type CDPCapability uint32 // CDPCapability values. const ( CDPCapMaskRouter CDPCapability = 0x0001 CDPCapMaskTBBridge CDPCapability = 0x0002 CDPCapMaskSPBridge CDPCapability = 0x0004 CDPCapMaskSwitch CDPCapability = 0x0008 CDPCapMaskHost CDPCapability = 0x0010 CDPCapMaskIGMPFilter CDPCapability = 0x0020 CDPCapMaskRepeater CDPCapability = 0x0040 CDPCapMaskPhone CDPCapability = 0x0080 CDPCapMaskRemote CDPCapability = 0x0100 ) // CDPCapabilities represents the capabilities of a device type CDPCapabilities struct { L3Router bool TBBridge bool SPBridge bool L2Switch bool IsHost bool IGMPFilter bool L1Repeater bool IsPhone bool RemotelyManaged bool } // CDP Power-over-Ethernet values. const ( CDPPoEFourWire byte = 0x01 CDPPoEPDArch byte = 0x02 CDPPoEPDRequest byte = 0x04 CDPPoEPSE byte = 0x08 ) // CDPSparePairPoE provides information on PoE. type CDPSparePairPoE struct { PSEFourWire bool // Supported / Not supported PDArchShared bool // Shared / Independent PDRequestOn bool // On / Off PSEOn bool // On / Off } // CDPVLANDialogue encapsulates a VLAN Query/Reply type CDPVLANDialogue struct { ID uint8 VLAN uint16 } // CDPPowerDialogue encapsulates a Power Query/Reply type CDPPowerDialogue struct { ID uint16 MgmtID uint16 Values []uint32 } // CDPLocation provides location information for a CDP device. type CDPLocation struct { Type uint8 // Undocumented Location string } // CDPHello is a Cisco Hello message (undocumented, hence the "Unknown" fields) type CDPHello struct { OUI []byte ProtocolID uint16 ClusterMaster net.IP Unknown1 net.IP Version byte SubVersion byte Status byte Unknown2 byte ClusterCommander net.HardwareAddr SwitchMAC net.HardwareAddr Unknown3 byte ManagementVLAN uint16 } // CDPEnergyWiseSubtype is used within CDP to define TLV values. type CDPEnergyWiseSubtype uint32 // CDPEnergyWiseSubtype values. const ( CDPEnergyWiseRole CDPEnergyWiseSubtype = 0x00000007 CDPEnergyWiseDomain CDPEnergyWiseSubtype = 0x00000008 CDPEnergyWiseName CDPEnergyWiseSubtype = 0x00000009 CDPEnergyWiseReplyTo CDPEnergyWiseSubtype = 0x00000017 ) // CDPEnergyWise is used by CDP to monitor and control power usage. type CDPEnergyWise struct { EncryptedData []byte Unknown1 uint32 SequenceNumber uint32 ModelNumber string Unknown2 uint16 HardwareID string SerialNum string Unknown3 []byte Role string Domain string Name string ReplyUnknown1 []byte ReplyPort []byte ReplyAddress []byte ReplyUnknown2 []byte ReplyUnknown3 []byte } // CiscoDiscoveryInfo represents the decoded details for a set of CiscoDiscoveryValues type CiscoDiscoveryInfo struct { BaseLayer CDPHello DeviceID string Addresses []net.IP PortID string Capabilities CDPCapabilities Version string Platform string IPPrefixes []net.IPNet VTPDomain string NativeVLAN uint16 FullDuplex bool VLANReply CDPVLANDialogue VLANQuery CDPVLANDialogue PowerConsumption uint16 MTU uint32 ExtendedTrust uint8 UntrustedCOS uint8 SysName string SysOID string MgmtAddresses []net.IP Location CDPLocation PowerRequest CDPPowerDialogue PowerAvailable CDPPowerDialogue SparePairPoe CDPSparePairPoE EnergyWise CDPEnergyWise Unknown []CiscoDiscoveryValue } // LayerType returns gopacket.LayerTypeCiscoDiscovery. func (c *CiscoDiscovery) LayerType() gopacket.LayerType { return LayerTypeCiscoDiscovery } func decodeCiscoDiscovery(data []byte, p gopacket.PacketBuilder) error { c := &CiscoDiscovery{ Version: data[0], TTL: data[1], Checksum: binary.BigEndian.Uint16(data[2:4]), } if c.Version != 1 && c.Version != 2 { return fmt.Errorf("Invalid CiscoDiscovery version number %d", c.Version) } var err error c.Values, err = decodeCiscoDiscoveryTLVs(data[4:]) if err != nil { return err } c.Contents = data[0:4] c.Payload = data[4:] p.AddLayer(c) return p.NextDecoder(gopacket.DecodeFunc(decodeCiscoDiscoveryInfo)) } // LayerType returns gopacket.LayerTypeCiscoDiscoveryInfo. func (c *CiscoDiscoveryInfo) LayerType() gopacket.LayerType { return LayerTypeCiscoDiscoveryInfo } func decodeCiscoDiscoveryTLVs(data []byte) (values []CiscoDiscoveryValue, err error) { for len(data) > 0 { val := CiscoDiscoveryValue{ Type: CDPTLVType(binary.BigEndian.Uint16(data[:2])), Length: binary.BigEndian.Uint16(data[2:4]), } if val.Length < 4 { err = fmt.Errorf("Invalid CiscoDiscovery value length %d", val.Length) break } val.Value = data[4:val.Length] values = append(values, val) data = data[val.Length:] } return } func decodeCiscoDiscoveryInfo(data []byte, p gopacket.PacketBuilder) error { var err error info := &CiscoDiscoveryInfo{BaseLayer: BaseLayer{Contents: data}} p.AddLayer(info) values, err := decodeCiscoDiscoveryTLVs(data) if err != nil { // Unlikely, as parent decode will fail, but better safe... return err } for _, val := range values { switch val.Type { case CDPTLVDevID: info.DeviceID = string(val.Value) case CDPTLVAddress: if err = checkCDPTLVLen(val, 4); err != nil { return err } info.Addresses, err = decodeAddresses(val.Value) if err != nil { return err } case CDPTLVPortID: info.PortID = string(val.Value) case CDPTLVCapabilities: if err = checkCDPTLVLen(val, 4); err != nil { return err } val := CDPCapability(binary.BigEndian.Uint32(val.Value[0:4])) info.Capabilities.L3Router = (val&CDPCapMaskRouter > 0) info.Capabilities.TBBridge = (val&CDPCapMaskTBBridge > 0) info.Capabilities.SPBridge = (val&CDPCapMaskSPBridge > 0) info.Capabilities.L2Switch = (val&CDPCapMaskSwitch > 0) info.Capabilities.IsHost = (val&CDPCapMaskHost > 0) info.Capabilities.IGMPFilter = (val&CDPCapMaskIGMPFilter > 0) info.Capabilities.L1Repeater = (val&CDPCapMaskRepeater > 0) info.Capabilities.IsPhone = (val&CDPCapMaskPhone > 0) info.Capabilities.RemotelyManaged = (val&CDPCapMaskRemote > 0) case CDPTLVVersion: info.Version = string(val.Value) case CDPTLVPlatform: info.Platform = string(val.Value) case CDPTLVIPPrefix: v := val.Value l := len(v) if l%5 == 0 && l >= 5 { for len(v) > 0 { _, ipnet, _ := net.ParseCIDR(fmt.Sprintf("%d.%d.%d.%d/%d", v[0], v[1], v[2], v[3], v[4])) info.IPPrefixes = append(info.IPPrefixes, *ipnet) v = v[5:] } } else { return fmt.Errorf("Invalid TLV %v length %d", val.Type, len(val.Value)) } case CDPTLVHello: if err = checkCDPTLVLen(val, 32); err != nil { return err } v := val.Value info.CDPHello.OUI = v[0:3] info.CDPHello.ProtocolID = binary.BigEndian.Uint16(v[3:5]) info.CDPHello.ClusterMaster = v[5:9] info.CDPHello.Unknown1 = v[9:13] info.CDPHello.Version = v[13] info.CDPHello.SubVersion = v[14] info.CDPHello.Status = v[15] info.CDPHello.Unknown2 = v[16] info.CDPHello.ClusterCommander = v[17:23] info.CDPHello.SwitchMAC = v[23:29] info.CDPHello.Unknown3 = v[29] info.CDPHello.ManagementVLAN = binary.BigEndian.Uint16(v[30:32]) case CDPTLVVTPDomain: info.VTPDomain = string(val.Value) case CDPTLVNativeVLAN: if err = checkCDPTLVLen(val, 2); err != nil { return err } info.NativeVLAN = binary.BigEndian.Uint16(val.Value[0:2]) case CDPTLVFullDuplex: if err = checkCDPTLVLen(val, 1); err != nil { return err } info.FullDuplex = (val.Value[0] == 1) case CDPTLVVLANReply: if err = checkCDPTLVLen(val, 3); err != nil { return err } info.VLANReply.ID = uint8(val.Value[0]) info.VLANReply.VLAN = binary.BigEndian.Uint16(val.Value[1:3]) case CDPTLVVLANQuery: if err = checkCDPTLVLen(val, 3); err != nil { return err } info.VLANQuery.ID = uint8(val.Value[0]) info.VLANQuery.VLAN = binary.BigEndian.Uint16(val.Value[1:3]) case CDPTLVPower: if err = checkCDPTLVLen(val, 2); err != nil { return err } info.PowerConsumption = binary.BigEndian.Uint16(val.Value[0:2]) case CDPTLVMTU: if err = checkCDPTLVLen(val, 4); err != nil { return err } info.MTU = binary.BigEndian.Uint32(val.Value[0:4]) case CDPTLVExtendedTrust: if err = checkCDPTLVLen(val, 1); err != nil { return err } info.ExtendedTrust = uint8(val.Value[0]) case CDPTLVUntrustedCOS: if err = checkCDPTLVLen(val, 1); err != nil { return err } info.UntrustedCOS = uint8(val.Value[0]) case CDPTLVSysName: info.SysName = string(val.Value) case CDPTLVSysOID: info.SysOID = string(val.Value) case CDPTLVMgmtAddresses: if err = checkCDPTLVLen(val, 4); err != nil { return err } info.MgmtAddresses, err = decodeAddresses(val.Value) if err != nil { return err } case CDPTLVLocation: if err = checkCDPTLVLen(val, 2); err != nil { return err } info.Location.Type = uint8(val.Value[0]) info.Location.Location = string(val.Value[1:]) // case CDPTLVLExternalPortID: // Undocumented case CDPTLVPowerRequested: if err = checkCDPTLVLen(val, 4); err != nil { return err } info.PowerRequest.ID = binary.BigEndian.Uint16(val.Value[0:2]) info.PowerRequest.MgmtID = binary.BigEndian.Uint16(val.Value[2:4]) for n := 4; n < len(val.Value); n += 4 { info.PowerRequest.Values = append(info.PowerRequest.Values, binary.BigEndian.Uint32(val.Value[n:n+4])) } case CDPTLVPowerAvailable: if err = checkCDPTLVLen(val, 4); err != nil { return err } info.PowerAvailable.ID = binary.BigEndian.Uint16(val.Value[0:2]) info.PowerAvailable.MgmtID = binary.BigEndian.Uint16(val.Value[2:4]) for n := 4; n < len(val.Value); n += 4 { info.PowerAvailable.Values = append(info.PowerAvailable.Values, binary.BigEndian.Uint32(val.Value[n:n+4])) } // case CDPTLVPortUnidirectional // Undocumented case CDPTLVEnergyWise: if err = checkCDPTLVLen(val, 72); err != nil { return err } info.EnergyWise.EncryptedData = val.Value[0:20] info.EnergyWise.Unknown1 = binary.BigEndian.Uint32(val.Value[20:24]) info.EnergyWise.SequenceNumber = binary.BigEndian.Uint32(val.Value[24:28]) info.EnergyWise.ModelNumber = string(val.Value[28:44]) info.EnergyWise.Unknown2 = binary.BigEndian.Uint16(val.Value[44:46]) info.EnergyWise.HardwareID = string(val.Value[46:49]) info.EnergyWise.SerialNum = string(val.Value[49:60]) info.EnergyWise.Unknown3 = val.Value[60:68] tlvLen := binary.BigEndian.Uint16(val.Value[68:70]) tlvNum := binary.BigEndian.Uint16(val.Value[70:72]) data := val.Value[72:] if len(data) < int(tlvLen) { return fmt.Errorf("Invalid TLV length %d vs %d", tlvLen, len(data)) } numSeen := 0 for len(data) > 8 { numSeen++ if numSeen > int(tlvNum) { // Too many TLV's ? return fmt.Errorf("Too many TLV's - wanted %d, saw %d", tlvNum, numSeen) } tType := CDPEnergyWiseSubtype(binary.BigEndian.Uint32(data[0:4])) tLen := int(binary.BigEndian.Uint32(data[4:8])) if tLen > len(data)-8 { return fmt.Errorf("Invalid TLV length %d vs %d", tLen, len(data)-8) } data = data[8:] switch tType { case CDPEnergyWiseRole: info.EnergyWise.Role = string(data[:]) case CDPEnergyWiseDomain: info.EnergyWise.Domain = string(data[:]) case CDPEnergyWiseName: info.EnergyWise.Name = string(data[:]) case CDPEnergyWiseReplyTo: if len(data) >= 18 { info.EnergyWise.ReplyUnknown1 = data[0:2] info.EnergyWise.ReplyPort = data[2:4] info.EnergyWise.ReplyAddress = data[4:8] info.EnergyWise.ReplyUnknown2 = data[8:10] info.EnergyWise.ReplyUnknown3 = data[10:14] } } data = data[tLen:] } case CDPTLVSparePairPOE: if err = checkCDPTLVLen(val, 1); err != nil { return err } v := val.Value[0] info.SparePairPoe.PSEFourWire = (v&CDPPoEFourWire > 0) info.SparePairPoe.PDArchShared = (v&CDPPoEPDArch > 0) info.SparePairPoe.PDRequestOn = (v&CDPPoEPDRequest > 0) info.SparePairPoe.PSEOn = (v&CDPPoEPSE > 0) default: info.Unknown = append(info.Unknown, val) } } return nil } // CDP Protocol Types const ( CDPProtocolTypeNLPID byte = 1 CDPProtocolType802_2 byte = 2 ) // CDPAddressType is used to define TLV values within CDP addresses. type CDPAddressType uint64 // CDP Address types. const ( CDPAddressTypeCLNP CDPAddressType = 0x81 CDPAddressTypeIPV4 CDPAddressType = 0xcc CDPAddressTypeIPV6 CDPAddressType = 0xaaaa030000000800 CDPAddressTypeDECNET CDPAddressType = 0xaaaa030000006003 CDPAddressTypeAPPLETALK CDPAddressType = 0xaaaa03000000809b CDPAddressTypeIPX CDPAddressType = 0xaaaa030000008137 CDPAddressTypeVINES CDPAddressType = 0xaaaa0300000080c4 CDPAddressTypeXNS CDPAddressType = 0xaaaa030000000600 CDPAddressTypeAPOLLO CDPAddressType = 0xaaaa030000008019 ) func decodeAddresses(v []byte) (addresses []net.IP, err error) { numaddr := int(binary.BigEndian.Uint32(v[0:4])) if numaddr < 1 { return nil, fmt.Errorf("Invalid Address TLV number %d", numaddr) } v = v[4:] if len(v) < numaddr*8 { return nil, fmt.Errorf("Invalid Address TLV length %d", len(v)) } for i := 0; i < numaddr; i++ { prottype := v[0] if prottype != CDPProtocolTypeNLPID && prottype != CDPProtocolType802_2 { // invalid protocol type return nil, fmt.Errorf("Invalid Address Protocol %d", prottype) } protlen := int(v[1]) if (prottype == CDPProtocolTypeNLPID && protlen != 1) || (prottype == CDPProtocolType802_2 && protlen != 3 && protlen != 8) { // invalid length return nil, fmt.Errorf("Invalid Address Protocol length %d", protlen) } plen := make([]byte, 8) copy(plen[8-protlen:], v[2:2+protlen]) protocol := CDPAddressType(binary.BigEndian.Uint64(plen)) v = v[2+protlen:] addrlen := binary.BigEndian.Uint16(v[0:2]) ab := v[2 : 2+addrlen] if protocol == CDPAddressTypeIPV4 && addrlen == 4 { addresses = append(addresses, net.IPv4(ab[0], ab[1], ab[2], ab[3])) } else if protocol == CDPAddressTypeIPV6 && addrlen == 16 { addresses = append(addresses, net.IP(ab)) } else { // only handle IPV4 & IPV6 for now } v = v[2+addrlen:] if len(v) < 8 { break } } return } func (t CDPTLVType) String() (s string) { switch t { case CDPTLVDevID: s = "Device ID" case CDPTLVAddress: s = "Addresses" case CDPTLVPortID: s = "Port ID" case CDPTLVCapabilities: s = "Capabilities" case CDPTLVVersion: s = "Software Version" case CDPTLVPlatform: s = "Platform" case CDPTLVIPPrefix: s = "IP Prefix" case CDPTLVHello: s = "Protocol Hello" case CDPTLVVTPDomain: s = "VTP Management Domain" case CDPTLVNativeVLAN: s = "Native VLAN" case CDPTLVFullDuplex: s = "Full Duplex" case CDPTLVVLANReply: s = "VoIP VLAN Reply" case CDPTLVVLANQuery: s = "VLANQuery" case CDPTLVPower: s = "Power consumption" case CDPTLVMTU: s = "MTU" case CDPTLVExtendedTrust: s = "Extended Trust Bitmap" case CDPTLVUntrustedCOS: s = "Untrusted Port CoS" case CDPTLVSysName: s = "System Name" case CDPTLVSysOID: s = "System OID" case CDPTLVMgmtAddresses: s = "Management Addresses" case CDPTLVLocation: s = "Location" case CDPTLVExternalPortID: s = "External Port ID" case CDPTLVPowerRequested: s = "Power Requested" case CDPTLVPowerAvailable: s = "Power Available" case CDPTLVPortUnidirectional: s = "Port Unidirectional" case CDPTLVEnergyWise: s = "Energy Wise" case CDPTLVSparePairPOE: s = "Spare Pair POE" default: s = "Unknown" } return } func (a CDPAddressType) String() (s string) { switch a { case CDPAddressTypeCLNP: s = "Connectionless Network Protocol" case CDPAddressTypeIPV4: s = "IPv4" case CDPAddressTypeIPV6: s = "IPv6" case CDPAddressTypeDECNET: s = "DECnet Phase IV" case CDPAddressTypeAPPLETALK: s = "Apple Talk" case CDPAddressTypeIPX: s = "Novell IPX" case CDPAddressTypeVINES: s = "Banyan VINES" case CDPAddressTypeXNS: s = "Xerox Network Systems" case CDPAddressTypeAPOLLO: s = "Apollo" default: s = "Unknown" } return } func (t CDPEnergyWiseSubtype) String() (s string) { switch t { case CDPEnergyWiseRole: s = "Role" case CDPEnergyWiseDomain: s = "Domain" case CDPEnergyWiseName: s = "Name" case CDPEnergyWiseReplyTo: s = "ReplyTo" default: s = "Unknown" } return } func checkCDPTLVLen(v CiscoDiscoveryValue, l int) (err error) { if len(v.Value) < l { err = fmt.Errorf("Invalid TLV %v length %d", v.Type, len(v.Value)) } return }