From 76996337bbec326584d1c4b9e5a786f5c94e9c81 Mon Sep 17 00:00:00 2001 From: YAMADA Hideki Date: Tue, 5 Feb 2013 16:14:30 +0900 Subject: [PATCH] packet lib: add LLDP support. See "ryu/tests/unit/packet/test_lldp.py" to use this library. This patch is based on Yamahata's topology discovery patch series. http://thread.gmane.org/gmane.network.ryu.devel/467 Signed-off-by: YAMADA Hideki Signed-off-by: FUJITA Tomonori --- ryu/lib/packet/lldp.py | 492 +++++++++++++++++++++++++++++ ryu/lib/packet/vlan.py | 2 + ryu/ofproto/ether.py | 1 + ryu/tests/unit/packet/test_lldp.py | 260 +++++++++++++++ 4 files changed, 755 insertions(+) create mode 100644 ryu/lib/packet/lldp.py create mode 100644 ryu/tests/unit/packet/test_lldp.py diff --git a/ryu/lib/packet/lldp.py b/ryu/lib/packet/lldp.py new file mode 100644 index 00000000..f82e00a3 --- /dev/null +++ b/ryu/lib/packet/lldp.py @@ -0,0 +1,492 @@ +# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2012 Isaku Yamahata +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Link Layer Discovery Protocol(LLDP, IEEE 802.1AB) +http://standards.ieee.org/getieee802/download/802.1AB-2009.pdf + + +basic TLV format + +octets | 1 | 2 | 3 ... n + 2 | + -------------------------------------------------------- + | TLV type | TLV information | TLV information string | + | (7bits) | string length | ( 0 <= n <= 511 octets) | + | | (9bits) | | + -------------------------------------------------------- +bits |8 2|1|8 1| + + +LLDPDU format + + ------------------------------------------------------------------------ + | Chassis ID | Port ID | TTL | optional TLV | ... | optional TLV | End | + ------------------------------------------------------------------------ + +Chasis ID, Port ID, TTL, End are mandatory +optional TLV may be inserted in any order +""" + +import struct +from ryu.lib.packet import packet_base + + +# LLDP destination MAC address +LLDP_MAC_NEAREST_BRIDGE = '\x01\x80\xc2\x00\x00\x0e' +LLDP_MAC_NEAREST_NON_TPMR_BRIDGE = '\x01\x80\xc2\x00\x00\x03' +LLDP_MAC_NEAREST_CUSTOMER_BRIDGE = '\x01\x80\xc2\x00\x00\x00' + + +LLDP_TLV_TYPELEN_STR = '!H' +LLDP_TLV_SIZE = 2 +LLDP_TLV_TYPE_MASK = 0xfe00 +LLDP_TLV_TYPE_SHIFT = 9 +LLDP_TLV_LENGTH_MASK = 0x01ff + + +# LLDP TLV type +LLDP_TLV_END = 0 # End of LLDPDU +LLDP_TLV_CHASSIS_ID = 1 # Chassis ID +LLDP_TLV_PORT_ID = 2 # Port ID +LLDP_TLV_TTL = 3 # Time To Live +LLDP_TLV_PORT_DESCRIPTION = 4 # Port Description +LLDP_TLV_SYSTEM_NAME = 5 # System Name +LLDP_TLV_SYSTEM_DESCRIPTION = 6 # System Description +LLDP_TLV_SYSTEM_CAPABILITIES = 7 # System Capabilities +LLDP_TLV_MANAGEMENT_ADDRESS = 8 # Management Address +LLDP_TLV_ORGANIZATIONALLY_SPECIFIC = 127 # organizationally Specific TLVs + + +class LLDPBasicTLV(object): + _LEN_MIN = 0 + _LEN_MAX = 511 + tlv_type = None + + def __init__(self, buf=None, *args, **kwargs): + if buf: + (self.typelen, ) = struct.unpack( + LLDP_TLV_TYPELEN_STR, buf[:LLDP_TLV_SIZE]) + tlv_type = \ + (self.typelen & LLDP_TLV_TYPE_MASK) >> LLDP_TLV_TYPE_SHIFT + assert self.tlv_type == tlv_type + + self.len = self.typelen & LLDP_TLV_LENGTH_MASK + assert len(buf) >= self.len + LLDP_TLV_SIZE + + self.tlv_info = buf[LLDP_TLV_SIZE:] + self.tlv_info = self.tlv_info[:self.len] + + @staticmethod + def get_type(buf): + (typelen, ) = struct.unpack(LLDP_TLV_TYPELEN_STR, buf[:LLDP_TLV_SIZE]) + return (typelen & LLDP_TLV_TYPE_MASK) >> LLDP_TLV_TYPE_SHIFT + + @staticmethod + def set_tlv_type(subcls, tlv_type): + assert issubclass(subcls, LLDPBasicTLV) + subcls.tlv_type = tlv_type + + def _len_valid(self): + return self._LEN_MIN <= self.len and self.len <= self._LEN_MAX + + def __str__(self): + self._typelen() + return super(LLDPBasicTLV, self).__str__() + + +class lldp(packet_base.PacketBase): + _tlv_parsers = {} + tlvs = [] + + def __init__(self, tlvs): + self.tlvs = tlvs + length = 0 + for tlv in tlvs: + length += LLDP_TLV_SIZE + tlv.len + + self.length = length + + # at least it must have chassis id, port id, ttl and end + def _tlvs_len_valid(self): + return len(self.tlvs) >= 4 + + # chassis id, port id, ttl and end + def _tlvs_valid(self): + return (self.tlvs[0].tlv_type == LLDP_TLV_CHASSIS_ID and + self.tlvs[1].tlv_type == LLDP_TLV_PORT_ID and + self.tlvs[2].tlv_type == LLDP_TLV_TTL and + self.tlvs[-1].tlv_type == LLDP_TLV_END) + + def __str__(self): + assert self._tlvs_len_valid() + assert self._tlvs_valid() + return ''.join(str(tlv) for tlv in self.tlvs) + + @classmethod + def parser(cls, buf): + tlvs = [] + + while buf: + tlv_type = LLDPBasicTLV.get_type(buf) + tlv = cls._tlv_parsers[tlv_type](buf) + tlvs.append(tlv) + offset = LLDP_TLV_SIZE + tlv.len + buf = buf[offset:] + assert (len(buf) > 0 and tlv.tlv_type != LLDP_TLV_END) or \ + (len(buf) == 0 and tlv.tlv_type == LLDP_TLV_END) + + lldp_pkt = cls(tlvs) + + assert lldp_pkt._tlvs_len_valid() + assert lldp_pkt._tlvs_valid() + + return lldp_pkt, None + + def serialize(self, payload, prev): + data = bytearray() + for tlv in self.tlvs: + data += tlv.serialize() + + return data + + @classmethod + def set_type(cls, tlv_cls): + cls._tlv_parsers[tlv_cls.tlv_type] = tlv_cls + + @classmethod + def get_type(cls, tlv_type): + return cls._tlv_parsers[tlv_type] + + @classmethod + def set_tlv_type(cls, tlv_type): + def _set_type(tlv_cls): + tlv_cls.set_tlv_type(tlv_cls, tlv_type) + cls.set_type(tlv_cls) + return tlv_cls + return _set_type + + +@lldp.set_tlv_type(LLDP_TLV_END) +class End(LLDPBasicTLV): + def __init__(self, buf=None, *args, **kwargs): + super(End, self).__init__(buf, *args, **kwargs) + if buf: + pass + else: + self.len = 0 + self.typelen = 0 + + def serialize(self): + return struct.pack('!H', self.typelen) + + +@lldp.set_tlv_type(LLDP_TLV_CHASSIS_ID) +class ChassisID(LLDPBasicTLV): + _PACK_STR = '!B' + _PACK_SIZE = struct.calcsize(_PACK_STR) + # subtype id(1 octet) + chassis id length(1 - 255 octet) + _LEN_MIN = 2 + _LEN_MAX = 256 + + # Chassis ID subtype + SUB_CHASSIS_COMPONENT = 1 # EntPhysicalAlias (IETF RFC 4133) + SUB_INTERFACE_ALIAS = 2 # IfAlias (IETF RFC 2863) + SUB_PORT_COMPONENT = 3 # EntPhysicalAlias (IETF RFC 4133) + SUB_MAC_ADDRESS = 4 # MAC address (IEEE std 802) + SUB_NETWORK_ADDRESS = 5 # networkAddress + SUB_INTERFACE_NAME = 6 # IfName (IETF RFC 2863) + SUB_LOCALLY_ASSIGNED = 7 # local + + def __init__(self, buf=None, *args, **kwargs): + super(ChassisID, self).__init__(buf, *args, **kwargs) + if buf: + (self.subtype, ) = struct.unpack( + self._PACK_STR, self.tlv_info[:self._PACK_SIZE]) + self.chassis_id = self.tlv_info[self._PACK_SIZE:] + else: + self.subtype = kwargs['subtype'] + self.chassis_id = kwargs['chassis_id'] + self.len = self._PACK_SIZE + len(self.chassis_id) + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!HB', self.typelen, self.subtype) + self.chassis_id + + +@lldp.set_tlv_type(LLDP_TLV_PORT_ID) +class PortID(LLDPBasicTLV): + _PACK_STR = '!B' + _PACK_SIZE = struct.calcsize(_PACK_STR) + + # subtype id(1 octet) + port id length(1 - 255 octet) + _LEN_MIN = 2 + _LEN_MAX = 256 + + # Port ID subtype + SUB_INTERFACE_ALIAS = 1 # ifAlias (IETF RFC 2863) + SUB_PORT_COMPONENT = 2 # entPhysicalAlias (IETF RFC 4133) + SUB_MAC_ADDRESS = 3 # MAC address (IEEE Std 802) + SUB_NETWORK_ADDRESS = 4 # networkAddress + SUB_INTERFACE_NAME = 5 # ifName (IETF RFC 2863) + SUB_AGENT_CIRCUIT_ID = 6 # agent circuit ID(IETF RFC 3046) + SUB_LOCALLY_ASSIGNED = 7 # local + + def __init__(self, buf=None, *args, **kwargs): + super(PortID, self).__init__(buf, *args, **kwargs) + if buf: + (self.subtype, ) = struct.unpack( + self._PACK_STR, self.tlv_info[:self._PACK_SIZE]) + self.port_id = self.tlv_info[self._PACK_SIZE:] + else: + self.subtype = kwargs['subtype'] + self.port_id = kwargs['port_id'] + self.len = self._PACK_SIZE + len(self.port_id) + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!HB', self.typelen, self.subtype) + self.port_id + + +@lldp.set_tlv_type(LLDP_TLV_TTL) +class TTL(LLDPBasicTLV): + _PACK_STR = '!H' + _PACK_SIZE = struct.calcsize(_PACK_STR) + _LEN_MIN = _PACK_SIZE + _LEN_MAX = _PACK_SIZE + + def __init__(self, buf=None, *args, **kwargs): + super(TTL, self).__init__(buf, *args, **kwargs) + if buf: + (self.ttl, ) = struct.unpack( + self._PACK_STR, self.tlv_info[:self._PACK_SIZE]) + else: + self.ttl = kwargs['ttl'] + self.len = self._PACK_SIZE + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!HH', self.typelen, self.ttl) + + +@lldp.set_tlv_type(LLDP_TLV_PORT_DESCRIPTION) +class PortDescription(LLDPBasicTLV): + _LEN_MAX = 255 + + def __init__(self, buf=None, *args, **kwargs): + super(PortDescription, self).__init__(buf, *args, **kwargs) + if buf: + pass + else: + self.port_description = kwargs['port_description'] + self.len = len(self.port_description) + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!H', self.typelen) + self.port_description + + @property + def port_description(self): + return self.tlv_info + + @port_description.setter + def port_description(self, value): + self.tlv_info = value + + +@lldp.set_tlv_type(LLDP_TLV_SYSTEM_NAME) +class SystemName(LLDPBasicTLV): + _LEN_MAX = 255 + + def __init__(self, buf=None, *args, **kwargs): + super(SystemName, self).__init__(buf, *args, **kwargs) + if buf: + pass + else: + self.system_name = kwargs['system_name'] + self.len = len(self.system_name) + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!H', self.typelen) + self.tlv_info + + @property + def system_name(self): + return self.tlv_info + + @system_name.setter + def system_name(self, value): + self.tlv_info = value + + +@lldp.set_tlv_type(LLDP_TLV_SYSTEM_DESCRIPTION) +class SystemDescription(LLDPBasicTLV): + _LEN_MAX = 255 + + def __init__(self, buf=None, *args, **kwargs): + super(SystemDescription, self).__init__(buf, *args, **kwargs) + if buf: + pass + else: + self.system_description = kwargs['system_description'] + self.len = len(self.system_description) + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!H', self.typelen) + self.tlv_info + + @property + def system_description(self): + return self.tlv_info + + @system_description.setter + def system_description(self, value): + self.tlv_info = value + + +@lldp.set_tlv_type(LLDP_TLV_SYSTEM_CAPABILITIES) +class SystemCapabilities(LLDPBasicTLV): + # chassis subtype(1) + system cap(2) + enabled cap(2) + _PACK_STR = '!BHH' + _PACK_SIZE = struct.calcsize(_PACK_STR) + _LEN_MIN = _PACK_SIZE + _LEN_MAX = _PACK_SIZE + + # System Capabilities + CAP_REPEATER = (1 << 1) # IETF RFC 2108 + CAP_MAC_BRIDGE = (1 << 2) # IEEE Std 802.1D + CAP_WLAN_ACCESS_POINT = (1 << 3) # IEEE Std 802.11 MIB + CAP_ROUTER = (1 << 4) # IETF RFC 1812 + CAP_TELEPHONE = (1 << 5) # IETF RFC 4293 + CAP_DOCSIS = (1 << 6) # IETF RFC 4639 and IETF RFC 4546 + CAP_STATION_ONLY = (1 << 7) # IETF RFC 4293 + CAP_CVLAN = (1 << 8) # IEEE Std 802.1Q + CAP_SVLAN = (1 << 9) # IEEE Std 802.1Q + CAP_TPMR = (1 << 10) # IEEE Std 802.1Q + + def __init__(self, buf=None, *args, **kwargs): + super(SystemCapabilities, self).__init__(buf, *args, **kwargs) + if buf: + (self.subtype, self.system_cap, self.enabled_cap) = \ + struct.unpack(self._PACK_STR, self.tlv_info[:self._PACK_SIZE]) + else: + self.subtype = kwargs['subtype'] + self.system_cap = kwargs['system_cap'] + self.enabled_cap = kwargs['enabled_cap'] + self.len = self._PACK_SIZE + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!HBHH', + self.typelen, self.subtype, + self.system_cap, self.enabled_cap) + + +@lldp.set_tlv_type(LLDP_TLV_MANAGEMENT_ADDRESS) +class ManagementAddress(LLDPBasicTLV): + _LEN_MIN = 9 + _LEN_MAX = 167 + + _ADDR_PACK_STR = '!BB' # address string length, address subtype + _ADDR_PACK_SIZE = struct.calcsize(_ADDR_PACK_STR) + _ADDR_LEN_MIN = 1 + _ADDR_LEN_MAX = 31 + + _INTF_PACK_STR = '!BIB' # interface subtype, interface number, oid length + _INTF_PACK_SIZE = struct.calcsize(_INTF_PACK_STR) + _OID_LEN_MIN = 0 + _OID_LEN_MAX = 128 + + def __init__(self, buf=None, *args, **kwargs): + super(ManagementAddress, self).__init__(buf, *args, **kwargs) + if buf: + (self.addr_len, self.addr_subtype) = struct.unpack( + self._ADDR_PACK_STR, self.tlv_info[:self._ADDR_PACK_SIZE]) + assert self._addr_len_valid() + offset = self._ADDR_PACK_SIZE + self.addr_len - 1 + self.addr = self.tlv_info[self._ADDR_PACK_SIZE:offset] + + (self.intf_subtype, self.intf_num, self.oid_len) = struct.unpack( + self._INTF_PACK_STR, + self.tlv_info[offset:offset + self._INTF_PACK_SIZE]) + assert self._oid_len_valid() + + offset = offset + self._INTF_PACK_SIZE + self.oid = self.tlv_info[offset:] + else: + self.addr_subtype = kwargs['addr_subtype'] + self.addr = kwargs['addr'] + self.addr_len = len(self.addr) + 1 # 1 octet subtype + assert self._addr_len_valid() + + self.intf_subtype = kwargs['intf_subtype'] + self.intf_num = kwargs['intf_num'] + + self.oid = kwargs['oid'] + self.oid_len = len(self.oid) + assert self._oid_len_valid() + + self.len = self._ADDR_PACK_SIZE + self.addr_len - 1 \ + + self._INTF_PACK_SIZE + self.oid_len + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + tlv_info = struct.pack(self._ADDR_PACK_STR, + self.addr_len, self.addr_subtype) + tlv_info += self.addr + tlv_info += struct.pack(self._INTF_PACK_STR, + self.intf_subtype, self.intf_num, self.oid_len) + tlv_info += self.oid + return struct.pack('!H', self.typelen) + tlv_info + + def _addr_len_valid(self): + return (self._ADDR_LEN_MIN <= self.addr_len or + self.addr_len <= self._ADDR_LEN_MAX) + + def _oid_len_valid(self): + return (self._OID_LEN_MIN <= self.oid_len and + self.oid_len <= self._OID_LEN_MAX) + + +@lldp.set_tlv_type(LLDP_TLV_ORGANIZATIONALLY_SPECIFIC) +class OrganizationallySpecific(LLDPBasicTLV): + _PACK_STR = '!3sB' + _PACK_SIZE = struct.calcsize(_PACK_STR) + _LEN_MIN = _PACK_SIZE + _LEN_MAX = 511 + + def __init__(self, buf=None, *args, **kwargs): + super(OrganizationallySpecific, self).__init__(buf, *args, **kwargs) + if buf: + (self.oui, self.subtype) = struct.unpack( + self._PACK_STR, self.tlv_info[:self._PACK_SIZE]) + else: + self.oui = kwargs['oui'] + self.subtype = kwargs['subtype'] + self.info = kwargs['info'] + self.len = self._PACK_SIZE + len(self.info) + assert self._len_valid() + self.typelen = (self.tlv_type << LLDP_TLV_TYPE_SHIFT) | self.len + + def serialize(self): + return struct.pack('!H3sB', self.typelen, self.oui, self.subtype) diff --git a/ryu/lib/packet/vlan.py b/ryu/lib/packet/vlan.py index dfd09341..fba83949 100644 --- a/ryu/lib/packet/vlan.py +++ b/ryu/lib/packet/vlan.py @@ -17,6 +17,7 @@ import struct from . import packet_base from . import arp from . import ipv4 +from . import lldp from ryu.ofproto import ether from ryu.ofproto.ofproto_parser import msg_pack_into @@ -47,3 +48,4 @@ class vlan(packet_base.PacketBase): vlan.register_packet_type(arp.arp, ether.ETH_TYPE_ARP) vlan.register_packet_type(ipv4.ipv4, ether.ETH_TYPE_IP) +vlan.register_packet_type(lldp.lldp, ether.ETH_TYPE_LLDP) diff --git a/ryu/ofproto/ether.py b/ryu/ofproto/ether.py index 747de7fe..a4f55255 100644 --- a/ryu/ofproto/ether.py +++ b/ryu/ofproto/ether.py @@ -20,3 +20,4 @@ ETH_TYPE_8021Q = 0x8100 ETH_TYPE_IPV6 = 0x86dd ETH_TYPE_MPLS = 0x8847 ETH_TYPE_SLOW = 0x8809 +ETH_TYPE_LLDP = 0x88cc diff --git a/ryu/tests/unit/packet/test_lldp.py b/ryu/tests/unit/packet/test_lldp.py new file mode 100644 index 00000000..23402f85 --- /dev/null +++ b/ryu/tests/unit/packet/test_lldp.py @@ -0,0 +1,260 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import unittest +import logging +import struct +from nose.tools import ok_, eq_, nottest + +from ryu.ofproto import ether +from ryu.lib.packet import packet +from ryu.lib.packet import ethernet +from ryu.lib.packet import lldp + +LOG = logging.getLogger(__name__) + + +class TestLLDPMandatoryTLV(unittest.TestCase): + def setUp(self): + # sample data is based on: + # http://wiki.wireshark.org/LinkLayerDiscoveryProtocol + # + # mandatory TLV only + self.data = '\x01\x80\xc2\x00\x00\x0e\x00\x04' \ + + '\x96\x1f\xa7\x26\x88\xcc\x02\x07' \ + + '\x04\x00\x04\x96\x1f\xa7\x26\x04' \ + + '\x04\x05\x31\x2f\x33\x06\x02\x00' \ + + '\x78\x00\x00' + + def tearDown(self): + pass + + def test_get_tlv_type(self): + buf = str(bytearray('\x02\x07\x04\x00\x04\x96\x1f\xa7\x26')) + eq_(lldp.LLDPBasicTLV.get_type(buf), lldp.LLDP_TLV_CHASSIS_ID) + + def test_parse_without_ethernet(self): + buf = self.data[ethernet.ethernet._MIN_LEN:] + (lldp_pkt, cls) = lldp.lldp.parser(buf) + eq_(lldp_pkt.length, len(buf)) + + tlvs = lldp_pkt.tlvs + eq_(tlvs[0].tlv_type, lldp.LLDP_TLV_CHASSIS_ID) + eq_(tlvs[0].len, 7) + eq_(tlvs[0].subtype, lldp.ChassisID.SUB_MAC_ADDRESS) + eq_(tlvs[0].chassis_id, '\x00\x04\x96\x1f\xa7\x26') + eq_(tlvs[1].tlv_type, lldp.LLDP_TLV_PORT_ID) + eq_(tlvs[1].len, 4) + eq_(tlvs[1].subtype, lldp.PortID.SUB_INTERFACE_NAME) + eq_(tlvs[1].port_id, '1/3') + eq_(tlvs[2].tlv_type, lldp.LLDP_TLV_TTL) + eq_(tlvs[2].len, 2) + eq_(tlvs[2].ttl, 120) + eq_(tlvs[3].tlv_type, lldp.LLDP_TLV_END) + + def test_parse(self): + buf = self.data + pkt = packet.Packet(buf) + + eq_(type(pkt.next()), ethernet.ethernet) + eq_(type(pkt.next()), lldp.lldp) + + def test_tlv(self): + tlv = lldp.ChassisID(subtype=lldp.ChassisID.SUB_MAC_ADDRESS, + chassis_id='\x00\x04\x96\x1f\xa7\x26') + eq_(tlv.tlv_type, lldp.LLDP_TLV_CHASSIS_ID) + eq_(tlv.len, 7) + (typelen, ) = struct.unpack('!H', '\x02\x07') + eq_(tlv.typelen, typelen) + + def test_serialize_without_ethernet(self): + tlv_chassis_id = lldp.ChassisID(subtype=lldp.ChassisID.SUB_MAC_ADDRESS, + chassis_id='\x00\x04\x96\x1f\xa7\x26') + tlv_port_id = lldp.PortID(subtype=lldp.PortID.SUB_INTERFACE_NAME, + port_id='1/3') + tlv_ttl = lldp.TTL(ttl=120) + tlv_end = lldp.End() + tlvs = (tlv_chassis_id, tlv_port_id, tlv_ttl, tlv_end) + lldp_pkt = lldp.lldp(tlvs) + + eq_(lldp_pkt.serialize(None, None), + self.data[ethernet.ethernet._MIN_LEN:]) + + def test_serialize(self): + pkt = packet.Packet() + + dst = lldp.LLDP_MAC_NEAREST_BRIDGE + src = '\x00\x04\x96\x1f\xa7\x26' + ethertype = ether.ETH_TYPE_LLDP + eth_pkt = ethernet.ethernet(dst, src, ethertype) + pkt.add_protocol(eth_pkt) + + tlv_chassis_id = lldp.ChassisID(subtype=lldp.ChassisID.SUB_MAC_ADDRESS, + chassis_id=src) + tlv_port_id = lldp.PortID(subtype=lldp.PortID.SUB_INTERFACE_NAME, + port_id='1/3') + tlv_ttl = lldp.TTL(ttl=120) + tlv_end = lldp.End() + tlvs = (tlv_chassis_id, tlv_port_id, tlv_ttl, tlv_end) + lldp_pkt = lldp.lldp(tlvs) + pkt.add_protocol(lldp_pkt) + + eq_(len(pkt.protocols), 2) + + pkt.serialize() + eq_(pkt.data, self.data) + + +class TestLLDPOptionalTLV(unittest.TestCase): + def setUp(self): + # sample data is based on: + # http://wiki.wireshark.org/LinkLayerDiscoveryProtocol + # + # include optional TLV + self.data = '\x01\x80\xc2\x00\x00\x0e\x00\x01' \ + + '\x30\xf9\xad\xa0\x88\xcc\x02\x07' \ + + '\x04\x00\x01\x30\xf9\xad\xa0\x04' \ + + '\x04\x05\x31\x2f\x31\x06\x02\x00' \ + + '\x78\x08\x17\x53\x75\x6d\x6d\x69' \ + + '\x74\x33\x30\x30\x2d\x34\x38\x2d' \ + + '\x50\x6f\x72\x74\x20\x31\x30\x30' \ + + '\x31\x00\x0a\x0d\x53\x75\x6d\x6d' \ + + '\x69\x74\x33\x30\x30\x2d\x34\x38' \ + + '\x00\x0c\x4c\x53\x75\x6d\x6d\x69' \ + + '\x74\x33\x30\x30\x2d\x34\x38\x20' \ + + '\x2d\x20\x56\x65\x72\x73\x69\x6f' \ + + '\x6e\x20\x37\x2e\x34\x65\x2e\x31' \ + + '\x20\x28\x42\x75\x69\x6c\x64\x20' \ + + '\x35\x29\x20\x62\x79\x20\x52\x65' \ + + '\x6c\x65\x61\x73\x65\x5f\x4d\x61' \ + + '\x73\x74\x65\x72\x20\x30\x35\x2f' \ + + '\x32\x37\x2f\x30\x35\x20\x30\x34' \ + + '\x3a\x35\x33\x3a\x31\x31\x00\x0e' \ + + '\x05\x01\x00\x14\x00\x14\x10\x0e' \ + + '\x07' \ + + '\x06\x00\x01\x30\xf9\xad\xa0\x02' \ + + '\x00\x00\x03\xe9\x00\xfe\x07\x00' \ + + '\x12\x0f\x02\x07\x01\x00\xfe\x09' \ + + '\x00\x12\x0f\x01\x03\x6c\x00\x00' \ + + '\x10\xfe\x09\x00\x12\x0f\x03\x01' \ + + '\x00\x00\x00\x00\xfe\x06\x00\x12' \ + + '\x0f\x04\x05\xf2\xfe\x06\x00\x80' \ + + '\xc2\x01\x01\xe8\xfe\x07\x00\x80' \ + + '\xc2\x02\x01\x00\x00\xfe\x17\x00' \ + + '\x80\xc2\x03\x01\xe8\x10\x76\x32' \ + + '\x2d\x30\x34\x38\x38\x2d\x30\x33' \ + + '\x2d\x30\x35\x30\x35\x00\xfe\x05' \ + + '\x00\x80\xc2\x04\x00\x00\x00' + + def tearDown(self): + pass + + def test_parse(self): + buf = self.data + pkt = packet.Packet(buf) + + eq_(type(pkt.next()), ethernet.ethernet) + lldp_pkt = pkt.next() + eq_(type(lldp_pkt), lldp.lldp) + eq_(lldp_pkt.length, len(buf) - ethernet.ethernet._MIN_LEN) + + tlvs = lldp_pkt.tlvs + + # Port Description + eq_(tlvs[3].tlv_type, lldp.LLDP_TLV_PORT_DESCRIPTION) + eq_(tlvs[3].port_description, 'Summit300-48-Port 1001\x00') + + # System Name + eq_(tlvs[4].tlv_type, lldp.LLDP_TLV_SYSTEM_NAME) + eq_(tlvs[4].system_name, 'Summit300-48\x00') + + # System Description + + eq_(tlvs[5].tlv_type, lldp.LLDP_TLV_SYSTEM_DESCRIPTION) + eq_(tlvs[5].system_description, + 'Summit300-48 - Version 7.4e.1 (Build 5) ' + + 'by Release_Master 05/27/05 04:53:11\x00') + + # SystemCapabilities + eq_(tlvs[6].tlv_type, lldp.LLDP_TLV_SYSTEM_CAPABILITIES) + eq_(tlvs[6].subtype, lldp.ChassisID.SUB_CHASSIS_COMPONENT) + eq_(tlvs[6].system_cap & lldp.SystemCapabilities.CAP_MAC_BRIDGE, + lldp.SystemCapabilities.CAP_MAC_BRIDGE) + eq_(tlvs[6].enabled_cap & lldp.SystemCapabilities.CAP_MAC_BRIDGE, + lldp.SystemCapabilities.CAP_MAC_BRIDGE) + eq_(tlvs[6].system_cap & lldp.SystemCapabilities.CAP_TELEPHONE, 0) + eq_(tlvs[6].enabled_cap & lldp.SystemCapabilities.CAP_TELEPHONE, 0) + + # Management Address + eq_(tlvs[7].tlv_type, lldp.LLDP_TLV_MANAGEMENT_ADDRESS) + eq_(tlvs[7].addr_len, 7) + eq_(tlvs[7].addr, '\x00\x01\x30\xf9\xad\xa0') + eq_(tlvs[7].intf_num, 1001) + + # Organizationally Specific + eq_(tlvs[8].tlv_type, lldp.LLDP_TLV_ORGANIZATIONALLY_SPECIFIC) + eq_(tlvs[8].oui, '\x00\x12\x0f') # IEEE 802.3 + eq_(tlvs[8].subtype, 0x02) # Power Via MDI + + # End + eq_(tlvs[16].tlv_type, lldp.LLDP_TLV_END) + + def test_serialize(self): + pkt = packet.Packet() + + dst = lldp.LLDP_MAC_NEAREST_BRIDGE + src = '\x00\x01\x30\xf9\xad\xa0' + ethertype = ether.ETH_TYPE_LLDP + eth_pkt = ethernet.ethernet(dst, src, ethertype) + pkt.add_protocol(eth_pkt) + + tlv_chassis_id = lldp.ChassisID(subtype=lldp.ChassisID.SUB_MAC_ADDRESS, + chassis_id=src) + tlv_port_id = lldp.PortID(subtype=lldp.PortID.SUB_INTERFACE_NAME, + port_id='1/1') + tlv_ttl = lldp.TTL(ttl=120) + tlv_port_description = lldp.PortDescription( + port_description='Summit300-48-Port 1001\x00') + tlv_system_name = lldp.SystemName(system_name='Summit300-48\x00') + tlv_system_description = lldp.SystemDescription( + system_description='Summit300-48 - Version 7.4e.1 (Build 5) ' + + 'by Release_Master 05/27/05 04:53:11\x00') + tlv_system_capabilities = lldp.SystemCapabilities( + subtype=lldp.ChassisID.SUB_CHASSIS_COMPONENT, + system_cap=0x14, + enabled_cap=0x14) + tlv_management_address = lldp.ManagementAddress( + addr_subtype=0x06, addr='\x00\x01\x30\xf9\xad\xa0', + intf_subtype=0x02, intf_num=1001, + oid='') + tlv_organizationally_specific = lldp.OrganizationallySpecific( + oui='\x00\x12\x0f', subtype=0x02, info='\x07\x01\x00') + tlv_end = lldp.End() + tlvs = (tlv_chassis_id, tlv_port_id, tlv_ttl, tlv_port_description, + tlv_system_name, tlv_system_description, + tlv_system_capabilities, tlv_management_address, + tlv_organizationally_specific, tlv_end) + lldp_pkt = lldp.lldp(tlvs) + pkt.add_protocol(lldp_pkt) + + eq_(len(pkt.protocols), 2) + + pkt.serialize() + + # self.data has many organizationally specific TLVs + data = str(pkt.data[:-2]) + eq_(data, self.data[:len(data)])