blob: 095f9261250629edd10c72a3fab7855ccb82ff04 [file] [log] [blame]
// 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"
"errors"
"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:], p)
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, p gopacket.PacketBuilder) (values []CiscoDiscoveryValue, err error) {
for len(data) > 0 {
if len(data) < 4 {
p.SetTruncated()
return nil, errors.New("CDP TLV < 4 bytes")
}
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
} else if len(data) < int(val.Length) {
p.SetTruncated()
return nil, fmt.Errorf("CDP TLV < length %d", val.Length)
}
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, p)
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
}