51学通信论坛2017新版

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1266|回复: 0
打印 上一主题 下一主题

Ryu:如何在LLDP中添加自定义LLDPDU

[复制链接]

 成长值: 15613

  • TA的每日心情
    开心
    2022-7-17 17:50
  • 2444

    主题

    2544

    帖子

    7万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    74104
    跳转到指定楼层
    楼主
    发表于 2017-9-17 15:19:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    在许多实验场景中,都需要使用链路发现协议(LLDP)来发现链路,从而构建网络拓扑。然而LLDP协议不仅仅可以用来发现拓扑,也可以用于时延检测等业务。LLDP通过添加对应的TLV格式的LLDPDU(LLDP数据单元)来携带对应的信息,从而为上层业务提供信息支撑。为实现LLDP数据单元的拓展,本文将以Ryu控制器为例,介绍如何添加自定义的LLDPDU,从而满足多种业务的需求。


    添加自定义LLDPDU其实只需修改ryu/lib/packet/lldp.py即可,但是由于该文件仅定义了LLDP的相关类,如何使用还需要其他文件去调用,所以还需要其他的修改步骤。具体步骤将在文章后续介绍。
    </p>修改lldp.py文件

    ryu/lib/packet/lldp.py文件是Ryu控制器中关于LLDP协议数据类的描述,其中定义了如LLDPBasicTLV类等重要的报文类。 以添加发送时间戳的TLV为例,我们需要完成TLV类型号的声明,以及TLV类的定义。
    在文件开头处有关于LLDP TLV类型的声明,所以首先我们需要添加一个新的类型:LLDP\_TLV\_SEND\_TIME,其类型号为11。
    Shell
    py
    # 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_DOMAIN_ID = 9 # Domain id for Open Exchange Protocol
    LLDP_TLV_VPORT_ID = 10 # vport_no for Open Exchange Protocol
    LLDP_TLV_SEND_TIME = 11 # Time stamp for sending LLDP packet,
    # using for delay measurement.
    LLDP_TLV_ORGANIZATIONALLY_SPECIFIC = 127 # organizationally Specific TLVs
    然后设计此类型的LLDPDU格式,其格式仅包含一个长度为8字节的Double类型的时间戳数据。如何完成类的描述,可以参考TTL类,具体代码如下。
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    py
    @lldp.set_tlv_type(LLDP_TLV_SEND_TIME)
    class TimeStamp(LLDPBasicTLV):
    _PACK_STR = '!d'
    _PACK_SIZE = struct.calcsize(_PACK_STR)
    _LEN_MIN = _PACK_SIZE
    _LEN_MAX = _PACK_SIZE
    def __init__(self, buf=None, *args, **kwargs):
    super(TimeStamp, self).__init__(buf, *args, **kwargs)
    if buf:
    (self.timestamp, ) = struct.unpack(
    self._PACK_STR, self.tlv_info[:self._PACK_SIZE])
    else:
    self.timestamp = kwargs['timestamp']
    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('!Hd', self.typelen, self.timestamp)

    TimeStamp类中定义了该LLDPDU的格式,初始化函数以及序列化函数。

    修改switches.py

    完成LLDPDU的定义之后,还需要在某文件中对其进行初始化构造。如果另外重新编写一个LLDP的构造、发送以及接受解析模块,那么则需要重新写许多代码,所以此处推荐直接修改Ryu/topology/switches.py文件。
    switches.py文件中的LLDPPacket类完成了LLDP数据包的初始化和序列化实现。
    该类的lldp\_packet方法可以构造LLDP数据包,并返回序列化之后的数据。在此函数中,我们需要添加timestamp的TLV。
    在lldp\_parse方法中,需将获取到的字节流的数据解析为对应的LLDP数据包。由于在发送之前,我们加入了一个timestamp的TLV,所以解析时需要完成这个TLV的解析,并将TimeStamp作为返回值返回。
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90

    py
    class LLDPPacket(object):
    # make a LLDP packet for link discovery.
    CHASSIS_ID_PREFIX = 'dpid:'
    CHASSIS_ID_PREFIX_LEN = len(CHASSIS_ID_PREFIX)
    CHASSIS_ID_FMT = CHASSIS_ID_PREFIX + '%s'
    PORT_ID_STR = '!I' # uint32_t
    PORT_ID_SIZE = 4
    DOMAIN_ID_PREFIX = 'domain_id:'
    DOMAIN_ID_PREFIX_LEN = len(DOMAIN_ID_PREFIX)
    DOMAIN_ID_FMT = DOMAIN_ID_PREFIX + '%s'
    VPORT_ID_STR = '!I' # uint32_t
    VPORT_ID_SIZE = 4
    class LLDPUnknownFormat(RyuException):
    message = '%(msg)s'
    @staticmethod
    def lldp_packet(dpid, port_no, dl_addr, ttl, timestamp,
    vport_no=ofproto_v1_0.OFPP_NONE):
    pkt = packet.Packet
    dst = lldp.LLDP_MAC_NEAREST_BRIDGE
    src = dl_addr
    ethertype = ETH_TYPE_LLDP
    eth_pkt = ethernet.ethernet(dst, src, ethertype)
    pkt.add_protocol(eth_pkt)
    tlv_chassis_id = lldp.ChassisID(
    subtype=lldp.ChassisID.SUB_LOCALLY_ASSIGNED,
    chassis_id=LLDPPacket.CHASSIS_ID_FMT %
    dpid_to_str(dpid))
    tlv_port_id = lldp.PortID(subtype=lldp.PortID.SUB_PORT_COMPONENT,
    port_id=struct.pack(
    LLDPPacket.PORT_ID_STR,
    port_no))
    tlv_ttl = lldp.TTL(ttl=ttl)
    tlv_timestamp = lldp.TimeStamp(timestamp=timestamp)
    tlv_end = lldp.End
    tlvs = (tlv_chassis_id, tlv_port_id, tlv_ttl, tlv_timestamp, tlv_end)
    lldp_pkt = lldp.lldp(tlvs)
    pkt.add_protocol(lldp_pkt)
    pkt.serialize
    return pkt.data
    @staticmethod
    def lldp_parse(data):
    pkt = packet.Packet(data)
    i = iter(pkt)
    eth_pkt = i.next
    assert type(eth_pkt) == ethernet.ethernet
    lldp_pkt = i.next
    if type(lldp_pkt) != lldp.lldp:
    raise LLDPPacket.LLDPUnknownFormat
    tlv_chassis_id = lldp_pkt.tlvs[0]
    if tlv_chassis_id.subtype != lldp.ChassisID.SUB_LOCALLY_ASSIGNED:
    raise LLDPPacket.LLDPUnknownFormat(
    msg='unknown chassis id subtype %d' % tlv_chassis_id.subtype)
    chassis_id = tlv_chassis_id.chassis_id
    if not chassis_id.startswith(LLDPPacket.CHASSIS_ID_PREFIX):
    raise LLDPPacket.LLDPUnknownFormat(
    msg='unknown chassis id format %s' % chassis_id)
    src_dpid = str_to_dpid(chassis_id[LLDPPacket.CHASSIS_ID_PREFIX_LEN:])
    tlv_port_id = lldp_pkt.tlvs[1]
    if tlv_port_id.subtype != lldp.PortID.SUB_PORT_COMPONENT:
    raise LLDPPacket.LLDPUnknownFormat(
    msg='unknown port id subtype %d' % tlv_port_id.subtype)
    port_id = tlv_port_id.port_id
    if len(port_id) != LLDPPacket.PORT_ID_SIZE:
    raise LLDPPacket.LLDPUnknownFormat(
    msg='unknown port id %d' % port_id)
    (src_port_no, ) = struct.unpack(LLDPPacket.PORT_ID_STR, port_id)
    tlv_timestamp = lldp_pkt.tlvs[3]
    timestamp = tlv_timestamp.timestamp
    return src_dpid, src_port_no, timestamp

    到此为止,完成了LLDP的构造和解析的定义。但是由于修改了构造函数的参数列表,和解析函数的返回值,所以在构造LLDP数据包和解析LLDP数据包时,均需要做一些改动。示例代码如下:
    def _port_added(self, port):
    _time = time.time
    lldp_data = LLDPPacket.lldp_packet(port.dpid, port.port_no,
    port.hw_addr, self.DEFAULT_TTL, _time)
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
    if not self.link_discovery:
    return
    msg = ev.msg
    try:
    src_dpid, src_port_no, timestamp = LLDPPacket.lldp_parse(msg.data)
    except LLDPPacket.LLDPUnknownFormat as e:
    # This handler can receive all the packtes which can be
    # not-LLDP packet. Ignore it silently
    return
    此处需要提醒读者的是,在Ryu的Switches模块中,被发送的LLDP都是一次构造之后保存起来,发送时直接发送的,所以添加的时间戳会固定在第一次构造时的时间。所以如果希望正确地插入发送时间戳,还需要进行额外的逻辑修改。但是这也许就破坏了Ryu设计的完整性,所以如何操作还需要读者自行斟酌。
    然而,像VPort\_ID之类的不随时间而改变的TLV,则可以直接使用。添加VPort\_ID的步骤和以上的例子同理,其VPort\_ID类的示例代码如下所示:
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    py
    @lldp.set_tlv_type(LLDP_TLV_VPORT_ID)
    class VPortID(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
    # VPort 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(VPortID, self).__init__(buf, *args, **kwargs)
    if buf:
    (self.subtype, ) = struct.unpack(
    self._PACK_STR, self.tlv_info[:self._PACK_SIZE])
    self.vport_id = self.tlv_info[self._PACK_SIZE:]
    else:
    self.subtype = kwargs['subtype']
    self.vport_id = kwargs['vport_id']
    self.len = self._PACK_SIZE + len(self.vport_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.vport_id


    总结


    LLDP协议可添加自定义TLV格式的特性,使其可以灵活地被修改,进而应用到不同的业务场景中,十分方便。本文就以Ryu控制器为例,介绍了如何添加自定义LLDPDU的详细流程,希望对读者有一定的帮助。此外,为计算时延,还可以通过switches模块中的PortDatak类的发送时间戳来实现,无需修改LLDP数据包格式。如何在Ryu中完成时延测试的内容将在下一篇文章中详细介绍,敬请关注。
    作者简介
    李呈,2014/09-至今,北京邮电大学信息与通信工程学院未来网络理论与应用实验室(FNL实验室)攻读硕士研究生。
    声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。
    扫描并关注51学通信微信公众号,获取更多精彩通信课程分享。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    Archiver|手机版|小黑屋|51学通信技术论坛

    GMT+8, 2025-1-31 22:58 , Processed in 0.072064 second(s), 33 queries .

    Powered by Discuz! X3

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表