From f637da6a66b4a8b9f527e9991c606252d407e7f4 Mon Sep 17 00:00:00 2001 From: "ailchen@cisco.com" Date: Fri, 18 Oct 2024 11:44:22 +0800 Subject: [PATCH 1/5] Add successfully parsed attributes to the malformed return value --- yabgp/common/exception.py | 6 +- .../message/attribute/linkstate/linkstate.py | 40 +++-- yabgp/message/update.py | 143 +++++++++--------- 3 files changed, 106 insertions(+), 83 deletions(-) diff --git a/yabgp/common/exception.py b/yabgp/common/exception.py index b176915..1bd83c4 100644 --- a/yabgp/common/exception.py +++ b/yabgp/common/exception.py @@ -47,10 +47,10 @@ class NotificationSent(Exception): def __init__(self, sub_error, data=''): try: - super(NotificationSent, self).__init__() self.msg = self.message % {'sub_error': sub_error, 'data': data} self.sub_error = sub_error self.data = data + super(NotificationSent, self).__init__(self.msg) except Exception: if _FATAL_EXCEPTION_FORMAT_ERRORS: raise @@ -77,6 +77,10 @@ class UpdateMessageError(NotificationSent): error = 3 message = "BGP Update Message Error, sub error:%(sub_error)s, data:%(data)s" + def __init__(self, sub_error, data='', sub_results=None): + super(UpdateMessageError, self).__init__(sub_error, data) + self.sub_results = sub_results + class HoldTimerExpiredError(NotificationSent): error = 4 diff --git a/yabgp/message/attribute/linkstate/linkstate.py b/yabgp/message/attribute/linkstate/linkstate.py index 1571908..fb0cccd 100644 --- a/yabgp/message/attribute/linkstate/linkstate.py +++ b/yabgp/message/attribute/linkstate/linkstate.py @@ -12,13 +12,17 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +import logging import struct import binascii - +import traceback +from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID +LOG = logging.getLogger() + class LinkState(Attribute): """BGP link-state attribute @@ -61,17 +65,27 @@ def unpack(cls, data, bgpls_pro_id=None): while data: type_code, length = struct.unpack('!HH', data[:4]) value = data[4: 4 + length] - if type_code in [1099, 1100, 1158, 1162, 1038] and type_code in cls.registered_tlvs: - tlvs.append(cls.registered_tlvs[type_code].unpack(value, bgpls_pro_id).dict()) - elif type_code in cls.registered_tlvs: - tlvs.append(cls.registered_tlvs[type_code].unpack(value).dict()) - else: - tlvs.append( - { - 'type': type_code, - 'value': str(binascii.b2a_hex(value)) - } - ) + try: + if type_code in [1099, 1100, 1158, 1162, 1038] and type_code in cls.registered_tlvs: + tlvs.append(cls.registered_tlvs[type_code].unpack(value, bgpls_pro_id).dict()) + elif type_code in cls.registered_tlvs: + tlvs.append(cls.registered_tlvs[type_code].unpack(value).dict()) + else: + tlvs.append( + { + 'type': type_code, + 'value': str(binascii.b2a_hex(value)) + } + ) + except Exception as e: + LOG.error(e) + error_str = traceback.format_exc() + LOG.debug(error_str) + raise excep.UpdateMessageError( + sub_error=bgp_cons.ERR_MSG_UPDATE_MALFORMED_ATTR_LIST, + data=value, + sub_results=cls(value=tlvs)) + data = data[4 + length:] return cls(value=tlvs) diff --git a/yabgp/message/update.py b/yabgp/message/update.py index 998469e..8189250 100644 --- a/yabgp/message/update.py +++ b/yabgp/message/update.py @@ -15,13 +15,11 @@ """BGP Update Message""" -import struct -import traceback -import logging import binascii - +import logging import netaddr - +import struct +import traceback from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons from yabgp.message.attribute import AttributeFlag @@ -191,6 +189,7 @@ def parse(cls, t, msg_hex, asn4=False, add_path_remote=False, add_path_local=Fal LOG.error(e) results['sub_error'] = e.sub_error results['err_data'] = e.data + results['attr'] = e.sub_results except Exception as e: LOG.error(e) error_str = traceback.format_exc() @@ -291,9 +290,9 @@ def parse_attributes(data, asn4=False): postfix = data bgpls_pro_id = None bgpls_attr = None - while len(postfix) > 0: - try: + try: + while len(postfix) > 0: flags, type_code = struct.unpack('!BB', postfix[:2]) if flags & AttributeFlag.EXTENDED_LENGTH: @@ -307,103 +306,109 @@ def parse_attributes(data, asn4=False): attr_len = ord(postfix[2]) attr_value = postfix[3:3 + attr_len] postfix = postfix[3 + attr_len:] # Next attribute - except Exception as e: - LOG.error(e) - error_str = traceback.format_exc() - LOG.debug(error_str) - raise excep.UpdateMessageError( - sub_error=bgp_cons.ERR_MSG_UPDATE_MALFORMED_ATTR_LIST, - data='') - if type_code == bgp_cons.BGPTYPE_ORIGIN: + if type_code == bgp_cons.BGPTYPE_ORIGIN: - decode_value = Origin.parse(value=attr_value) + decode_value = Origin.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_AS_PATH: + elif type_code == bgp_cons.BGPTYPE_AS_PATH: - decode_value = ASPath.parse(value=attr_value, asn4=asn4) + decode_value = ASPath.parse(value=attr_value, asn4=asn4) - elif type_code == bgp_cons.BGPTYPE_NEXT_HOP: + elif type_code == bgp_cons.BGPTYPE_NEXT_HOP: - decode_value = NextHop.parse(value=attr_value) + decode_value = NextHop.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_MULTI_EXIT_DISC: + elif type_code == bgp_cons.BGPTYPE_MULTI_EXIT_DISC: - decode_value = MED.parse(value=attr_value) + decode_value = MED.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_LOCAL_PREF: + elif type_code == bgp_cons.BGPTYPE_LOCAL_PREF: - decode_value = LocalPreference.parse(value=attr_value) + decode_value = LocalPreference.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_ATOMIC_AGGREGATE: + elif type_code == bgp_cons.BGPTYPE_ATOMIC_AGGREGATE: - decode_value = AtomicAggregate.parse(value=attr_value) + decode_value = AtomicAggregate.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_AGGREGATOR: + elif type_code == bgp_cons.BGPTYPE_AGGREGATOR: - decode_value = Aggregator.parse(value=attr_value, asn4=asn4) + decode_value = Aggregator.parse(value=attr_value, asn4=asn4) - elif type_code == bgp_cons.BGPTYPE_COMMUNITIES: + elif type_code == bgp_cons.BGPTYPE_COMMUNITIES: - decode_value = Community.parse(value=attr_value) + decode_value = Community.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_ORIGINATOR_ID: + elif type_code == bgp_cons.BGPTYPE_ORIGINATOR_ID: - decode_value = OriginatorID.parse(value=attr_value) + decode_value = OriginatorID.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_CLUSTER_LIST: + elif type_code == bgp_cons.BGPTYPE_CLUSTER_LIST: - decode_value = ClusterList.parse(value=attr_value) + decode_value = ClusterList.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_NEW_AS_PATH: + elif type_code == bgp_cons.BGPTYPE_NEW_AS_PATH: - decode_value = ASPath.parse(value=attr_value, asn4=True) + decode_value = ASPath.parse(value=attr_value, asn4=True) - elif type_code == bgp_cons.BGPTYPE_NEW_AGGREGATOR: + elif type_code == bgp_cons.BGPTYPE_NEW_AGGREGATOR: - decode_value = Aggregator.parse(value=attr_value, asn4=True) + decode_value = Aggregator.parse(value=attr_value, asn4=True) - elif type_code == bgp_cons.BGPTYPE_LARGE_COMMUNITY: + elif type_code == bgp_cons.BGPTYPE_LARGE_COMMUNITY: - decode_value = LargeCommunity.parse(value=attr_value) + decode_value = LargeCommunity.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_MP_REACH_NLRI: - decode_value = MpReachNLRI.parse(value=attr_value) - if decode_value['nlri'][0] and type(decode_value['nlri'][0]) is dict: - if decode_value['nlri'][0].get("protocol_id"): - bgpls_pro_id = decode_value['nlri'][0]["protocol_id"] + elif type_code == bgp_cons.BGPTYPE_MP_REACH_NLRI: + decode_value = MpReachNLRI.parse(value=attr_value) + if decode_value['nlri'][0] and type(decode_value['nlri'][0]) is dict: + if decode_value['nlri'][0].get("protocol_id"): + bgpls_pro_id = decode_value['nlri'][0]["protocol_id"] - elif type_code == bgp_cons.BGPTYPE_MP_UNREACH_NLRI: - decode_value = MpUnReachNLRI.parse(value=attr_value) + elif type_code == bgp_cons.BGPTYPE_MP_UNREACH_NLRI: + decode_value = MpUnReachNLRI.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_EXTENDED_COMMUNITY: - decode_value = ExtCommunity.parse(value=attr_value) + elif type_code == bgp_cons.BGPTYPE_EXTENDED_COMMUNITY: + decode_value = ExtCommunity.parse(value=attr_value) - elif type_code == bgp_cons.BGPTYPE_PMSI_TUNNEL: - decode_value = PMSITunnel.parse(value=attr_value) - pmsi_hex = attr_value + elif type_code == bgp_cons.BGPTYPE_PMSI_TUNNEL: + decode_value = PMSITunnel.parse(value=attr_value) + pmsi_hex = attr_value - elif type_code == bgp_cons.BGPTYPE_LINK_STATE: - if bgpls_pro_id: - attributes.update(LinkState.unpack(bgpls_pro_id=bgpls_pro_id, data=attr_value).dict()) - else: - bgpls_attr = attr_value - continue + elif type_code == bgp_cons.BGPTYPE_LINK_STATE: + if bgpls_pro_id: + attributes.update(LinkState.unpack(bgpls_pro_id=bgpls_pro_id, data=attr_value).dict()) + else: + bgpls_attr = attr_value + continue - elif type_code == bgp_cons.BGPTYPE_BGP_PREFIX_SID: - decode_value = BGPPrefixSID.unpack(data=attr_value) + elif type_code == bgp_cons.BGPTYPE_BGP_PREFIX_SID: + decode_value = BGPPrefixSID.unpack(data=attr_value) - else: - decode_value = binascii.b2a_hex(attr_value).decode('utf-8') - attributes[type_code] = decode_value + else: + decode_value = binascii.b2a_hex(attr_value).decode('utf-8') + attributes[type_code] = decode_value - if bgpls_attr: - attributes.update(LinkState.unpack(bgpls_pro_id=bgpls_pro_id, data=bgpls_attr).dict()) + if bgpls_attr: + attributes.update(LinkState.unpack(bgpls_pro_id=bgpls_pro_id, data=bgpls_attr).dict()) - evpn_overlay = EVPN.signal_evpn_overlay(attributes) - if evpn_overlay['evpn'] and evpn_overlay['encap_ec']: - if bgp_cons.BGPTYPE_PMSI_TUNNEL in attributes: - attributes[bgp_cons.BGPTYPE_PMSI_TUNNEL] = PMSITunnel.parse(value=pmsi_hex, evpn_overlay=evpn_overlay) + evpn_overlay = EVPN.signal_evpn_overlay(attributes) + if evpn_overlay['evpn'] and evpn_overlay['encap_ec']: + if bgp_cons.BGPTYPE_PMSI_TUNNEL in attributes: + attributes[bgp_cons.BGPTYPE_PMSI_TUNNEL] = PMSITunnel.parse(value=pmsi_hex, evpn_overlay=evpn_overlay) + except excep.UpdateMessageError as e: + raise excep.UpdateMessageError( + sub_error=e.sub_error, + data=e.data, + sub_results=attributes) + except Exception as e: + LOG.error(e) + error_str = traceback.format_exc() + LOG.debug(error_str) + raise excep.UpdateMessageError( + sub_error=bgp_cons.ERR_MSG_UPDATE_MALFORMED_ATTR_LIST, + data='', + sub_results=attributes) return attributes @staticmethod From 9fdb80e2a087c22c430d1488c3b221ebcaf66140 Mon Sep 17 00:00:00 2001 From: chenailin11 Date: Fri, 18 Oct 2024 14:21:27 +0800 Subject: [PATCH 2/5] update update.py for pep8 --- yabgp/message/update.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yabgp/message/update.py b/yabgp/message/update.py index 8189250..e1ad2d7 100644 --- a/yabgp/message/update.py +++ b/yabgp/message/update.py @@ -395,7 +395,8 @@ def parse_attributes(data, asn4=False): evpn_overlay = EVPN.signal_evpn_overlay(attributes) if evpn_overlay['evpn'] and evpn_overlay['encap_ec']: if bgp_cons.BGPTYPE_PMSI_TUNNEL in attributes: - attributes[bgp_cons.BGPTYPE_PMSI_TUNNEL] = PMSITunnel.parse(value=pmsi_hex, evpn_overlay=evpn_overlay) + attributes[bgp_cons.BGPTYPE_PMSI_TUNNEL] = PMSITunnel.parse(value=pmsi_hex, + evpn_overlay=evpn_overlay) except excep.UpdateMessageError as e: raise excep.UpdateMessageError( sub_error=e.sub_error, From 21e7720c3fdcf552c92d178fd1384526e0359ae3 Mon Sep 17 00:00:00 2001 From: chenailin11 Date: Thu, 9 Jan 2025 15:36:12 +0800 Subject: [PATCH 3/5] fix: sr_capabilities_flag is incorrect --- .../message/attribute/linkstate/node/sr_capabilities.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/yabgp/message/attribute/linkstate/node/sr_capabilities.py b/yabgp/message/attribute/linkstate/node/sr_capabilities.py index e5d8bb9..4038b3c 100644 --- a/yabgp/message/attribute/linkstate/node/sr_capabilities.py +++ b/yabgp/message/attribute/linkstate/node/sr_capabilities.py @@ -44,11 +44,8 @@ def unpack(cls, value): """ """ flags = ord(value[0:1]) - F = flags >> 7 - M = (flags << 1) % 256 >> 7 - S = (flags << 2) % 256 >> 7 - D = (flags << 3) % 256 >> 7 - A = (flags << 4) % 256 >> 7 + I = flags >> 7 + V = (flags << 1) % 256 >> 7 value = value[2:] results = [] while True: @@ -68,4 +65,4 @@ def unpack(cls, value): value = value[7 + length:] tmp['sid'] = data results.append(tmp) - return cls(value={"flag": {"F": F, "M": M, "S": S, "D": D, "A": A}, "value": results}) + return cls(value={"flag": {"I": I, "V": V}, "value": results}) From 1a280276a5db5a4444b31a351a92e462fbbe1e90 Mon Sep 17 00:00:00 2001 From: chenailin11 Date: Thu, 10 Apr 2025 13:36:32 +0800 Subject: [PATCH 4/5] Support Path ID parsing for IPv4/IPv6 Unicast, VPNv4/VPNv6, and IPv4/IPv6 LU NLRI; optimize nexthop RD parsing for VPNv4/VPNv6 --- yabgp/message/attribute/mpreachnlri.py | 37 +++++++------------ yabgp/message/attribute/mpunreachnlri.py | 13 ++++--- yabgp/message/attribute/nlri/ipv4_mpls_vpn.py | 4 +- yabgp/message/attribute/nlri/ipv6_mpls_vpn.py | 4 +- yabgp/message/attribute/nlri/ipv6_unicast.py | 11 +++++- .../nlri/labeled_unicast/__init__.py | 12 +++++- yabgp/message/attribute/nlri/mpls_vpn.py | 8 +++- yabgp/message/update.py | 9 +++-- .../test_ipv4_labeled_unicast.py | 7 ++++ .../test_ipv6_labeled_unicast.py | 8 ++++ .../attribute/nlri/test_ipv4_mpls_vpn.py | 6 +++ .../attribute/nlri/test_ipv6_mpls_vpn.py | 5 +++ .../attribute/nlri/test_ipv6_unicast.py | 6 +++ 13 files changed, 88 insertions(+), 42 deletions(-) diff --git a/yabgp/message/attribute/mpreachnlri.py b/yabgp/message/attribute/mpreachnlri.py index 781c9e3..e038215 100644 --- a/yabgp/message/attribute/mpreachnlri.py +++ b/yabgp/message/attribute/mpreachnlri.py @@ -67,7 +67,7 @@ class MpReachNLRI(Attribute): FLAG = AttributeFlag.OPTIONAL + AttributeFlag.EXTENDED_LENGTH @classmethod - def parse(cls, value): + def parse(cls, value, add_path=False): """parse """ try: @@ -87,30 +87,25 @@ def parse(cls, value): # parse nexthop nexthop = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin), 16))) # parse nlri - nlri = IPv4Unicast.parse(nlri_bin) + nlri = IPv4Unicast.parse(nlri_bin, addpath=add_path) return dict(afi_safi=(afi, safi), nexthop=nexthop, nlri=nlri) if safi == safn.SAFNUM_LAB_VPNUNICAST: # MPLS VPN # parse nexthop rd_bin = nexthop_bin[0:8] - rd_type = struct.unpack('!H', rd_bin[0:2])[0] - rd_value_bin = rd_bin[2:] - if rd_type == 0: - asn, an = struct.unpack('!HI', rd_value_bin) - ipv4 = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin[8:]), 16))) - nexthop = {'rd': '%s:%s' % (asn, an), 'str': ipv4} - else: - nexthop = binascii.b2a_hex((nexthop_bin[8:])) + nexthop_rd = IPv4MPLSVPN.parse_rd(rd_bin) + ipv4 = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin[8:]), 16))) + nexthop = {'rd': nexthop_rd, 'str': ipv4} # parse nlri - nlri = IPv4MPLSVPN.parse(nlri_bin) + nlri = IPv4MPLSVPN.parse(nlri_bin, addpath=add_path) return dict(afi_safi=(afi, safi), nexthop=nexthop, nlri=nlri) elif safi == safn.SAFNUM_MPLS_LABEL: if nexthop_bin: nexthop = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin), 16))) else: nexthop = '' - nlri = IPv4LabeledUnicast.parse(nlri_bin) + nlri = IPv4LabeledUnicast.parse(nlri_bin, addpath=add_path) return dict(afi_safi=(afi, safi), nexthop=nexthop, nlri=nlri) elif safi == safn.SAFNUM_FSPEC_RULE: # if nlri length is greater than 240 bytes, it is encoded over 2 bytes @@ -157,7 +152,7 @@ def parse(cls, value): # has link local address has_link_local = True linklocal_nexthop = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin[nexthop_addrlen:]), 16))) - nlri = IPv6Unicast.parse(nlri_bin) + nlri = IPv6Unicast.parse(nlri_bin, addpath=add_path) if has_link_local: return dict(afi_safi=(afi, safi), nexthop=nexthop, linklocal_nexthop=linklocal_nexthop, nlri=nlri) else: @@ -166,24 +161,18 @@ def parse(cls, value): # IPv6 MPLS VPN # parse nexthop rd_bin = nexthop_bin[0:8] - rd_type = struct.unpack('!H', rd_bin[0:2])[0] - rd_value_bin = rd_bin[2:] - if rd_type == 0: - asn, an = struct.unpack('!HI', rd_value_bin) - ipv6 = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin[8:]), 16))) - nexthop = {'rd': '%s:%s' % (asn, an), 'str': ipv6} - # TODO(xiaoquwl) for other RD type decoding - else: - nexthop = binascii.b2a_hex(nexthop_bin[8:]) + nexthop_rd = IPv6MPLSVPN.parse_rd(rd_bin) + ipv6 = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin[8:]), 16))) + nexthop = {'rd': nexthop_rd, 'str': ipv6} # parse nlri - nlri = IPv6MPLSVPN.parse(nlri_bin) + nlri = IPv6MPLSVPN.parse(nlri_bin, addpath=add_path) return dict(afi_safi=(afi, safi), nexthop=nexthop, nlri=nlri) elif safi == safn.SAFNUM_MPLS_LABEL: if nexthop_bin: nexthop = str(netaddr.IPAddress(int(binascii.b2a_hex(nexthop_bin), 16))) else: nexthop = '' - nlri = IPv6LabeledUnicast.parse(nlri_bin) + nlri = IPv6LabeledUnicast.parse(nlri_bin, addpath=add_path) return dict(afi_safi=(afi, safi), nexthop=nexthop, nlri=nlri) else: return dict(afi_safi=(afi, safi), nexthop=nexthop_bin, nlri=nlri_bin) diff --git a/yabgp/message/attribute/mpunreachnlri.py b/yabgp/message/attribute/mpunreachnlri.py index 988b0ad..440014f 100644 --- a/yabgp/message/attribute/mpunreachnlri.py +++ b/yabgp/message/attribute/mpunreachnlri.py @@ -22,6 +22,7 @@ from yabgp.message.attribute import AttributeFlag from yabgp.message.attribute import AttributeID from yabgp.message.attribute.nlri.ipv4_mpls_vpn import IPv4MPLSVPN +from yabgp.message.attribute.nlri.ipv4_unicast import IPv4Unicast from yabgp.message.attribute.nlri.ipv6_mpls_vpn import IPv6MPLSVPN from yabgp.message.attribute.nlri.ipv4_flowspec import IPv4FlowSpec from yabgp.message.attribute.nlri.ipv6_unicast import IPv6Unicast @@ -57,7 +58,7 @@ class MpUnReachNLRI(Attribute): FLAG = AttributeFlag.OPTIONAL + AttributeFlag.EXTENDED_LENGTH @classmethod - def parse(cls, value): + def parse(cls, value, add_path=False): try: afi, safi = struct.unpack('!HB', value[0:3]) except Exception: @@ -68,10 +69,12 @@ def parse(cls, value): # for IPv4 if afi == afn.AFNUM_INET: + if safi == safn.SAFNUM_UNICAST: + return dict(afi_safi=(afi, safi), withdraw=IPv4Unicast.parse(nlri_bin, addpath=add_path)) # VPNv4 - if safi == safn.SAFNUM_LAB_VPNUNICAST: - nlri = IPv4MPLSVPN.parse(nlri_bin, iswithdraw=True) + elif safi == safn.SAFNUM_LAB_VPNUNICAST: + nlri = IPv4MPLSVPN.parse(nlri_bin, iswithdraw=True, addpath=add_path) return dict(afi_safi=(afi, safi), withdraw=nlri) # BGP flow spec elif safi == safn.SAFNUM_FSPEC_RULE: @@ -97,9 +100,9 @@ def parse(cls, value): elif afi == afn.AFNUM_INET6: # for ipv6 unicast if safi == safn.SAFNUM_UNICAST: - return dict(afi_safi=(afi, safi), withdraw=IPv6Unicast.parse(nlri_data=nlri_bin)) + return dict(afi_safi=(afi, safi), withdraw=IPv6Unicast.parse(nlri_data=nlri_bin, addpath=add_path)) elif safi == safn.SAFNUM_LAB_VPNUNICAST: - return dict(afi_safi=(afi, safi), withdraw=IPv6MPLSVPN.parse(value=nlri_bin, iswithdraw=True)) + return dict(afi_safi=(afi, safi), withdraw=IPv6MPLSVPN.parse(value=nlri_bin, iswithdraw=True, addpath=add_path)) else: return dict(afi_safi=(afi, safi), withdraw=repr(nlri_bin)) # for l2vpn diff --git a/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py b/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py index 6588d80..b4e94a7 100644 --- a/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py +++ b/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py @@ -28,8 +28,8 @@ class IPv4MPLSVPN(MPLSVPN): SAFI = SAFNUM_LAB_VPNUNICAST @classmethod - def parse(cls, value, iswithdraw=False): - return super(IPv4MPLSVPN, cls).parse(value, iswithdraw=iswithdraw) + def parse(cls, value, iswithdraw=False, addpath=False): + return super(IPv4MPLSVPN, cls).parse(value, iswithdraw=iswithdraw, addpath=addpath) @classmethod def construct(cls, value, iswithdraw=False): diff --git a/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py b/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py index 36ada68..39962cf 100644 --- a/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py +++ b/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py @@ -26,8 +26,8 @@ class IPv6MPLSVPN(MPLSVPN): SAFI = SAFNUM_LAB_VPNUNICAST @classmethod - def parse(cls, value, iswithdraw=False): - return super(IPv6MPLSVPN, cls).parse(value, iswithdraw=iswithdraw) + def parse(cls, value, iswithdraw=False, addpath=False): + return super(IPv6MPLSVPN, cls).parse(value, iswithdraw=iswithdraw, addpath=addpath) @classmethod def construct(cls, value, iswithdraw=False): diff --git a/yabgp/message/attribute/nlri/ipv6_unicast.py b/yabgp/message/attribute/nlri/ipv6_unicast.py index 8399553..aba258f 100644 --- a/yabgp/message/attribute/nlri/ipv6_unicast.py +++ b/yabgp/message/attribute/nlri/ipv6_unicast.py @@ -25,7 +25,7 @@ class IPv6Unicast(NLRI): @classmethod - def parse(cls, nlri_data): + def parse(cls, nlri_data, addpath=False): """ decode IPv6 NLRI data :param nlri_data: NLRI raw hex data @@ -37,6 +37,10 @@ def parse(cls, nlri_data): # Note: Fix wrong decoding to ["0.0.0.0/0", "0.0.0.0/0"] nlri_data = nlri_data[2:] continue + path_id = None + if addpath: + path_id = struct.unpack("!I", nlri_data[:4])[0] + nlri_data = nlri_data[4:] if isinstance(nlri_data[0], int): prefix_bit_len = int(nlri_data[0]) else: @@ -53,7 +57,10 @@ def parse(cls, nlri_data): prefix_bit += b'\x00' prefix_addr = str(netaddr.IPAddress(int(binascii.b2a_hex(prefix_bit), 16))) + '/%s' % prefix_bit_len - nlri_list.append(prefix_addr) + if addpath: + nlri_list.append({'prefix': prefix_addr, 'path_id': path_id}) + else: + nlri_list.append(prefix_addr) nlri_data = nlri_data[offset:] return nlri_list diff --git a/yabgp/message/attribute/nlri/labeled_unicast/__init__.py b/yabgp/message/attribute/nlri/labeled_unicast/__init__.py index 18ce100..bea12a3 100644 --- a/yabgp/message/attribute/nlri/labeled_unicast/__init__.py +++ b/yabgp/message/attribute/nlri/labeled_unicast/__init__.py @@ -42,9 +42,14 @@ class LabeledUnicast(NLRI): SAFI = SAFNUM_MPLS_LABEL @classmethod - def parse(cls, nlri_data): + def parse(cls, nlri_data, addpath=False): nlri_list = [] while nlri_data: + path_id = None + if addpath: + path_id = struct.unpack("!I", nlri_data[0:4])[0] + nlri_data = nlri_data[4:] + nlri_bit_len = ord(nlri_data[0:1]) # get the byte lenght of label stack + prefix if nlri_bit_len % 8 == 0: @@ -66,7 +71,10 @@ def parse(cls, nlri_data): prefix_hex += b'\x00' prefix = str(netaddr.IPAddress(int(binascii.b2a_hex(prefix_hex), 16))) + '/' + str(prefix_mask) nlri_data = nlri_data[offset:] - nlri_list.append({'prefix': prefix, 'label': label}) + if addpath: + nlri_list.append({'path_id': path_id, 'prefix': prefix, 'label': label}) + else: + nlri_list.append({'prefix': prefix, 'label': label}) return nlri_list @classmethod diff --git a/yabgp/message/attribute/nlri/mpls_vpn.py b/yabgp/message/attribute/nlri/mpls_vpn.py index b699835..b0f4233 100644 --- a/yabgp/message/attribute/nlri/mpls_vpn.py +++ b/yabgp/message/attribute/nlri/mpls_vpn.py @@ -53,7 +53,7 @@ class MPLSVPN(NLRI): SAFI = None @classmethod - def parse(cls, value, iswithdraw=False): + def parse(cls, value, iswithdraw=False, addpath=False): """ parse nlri :param value: the raw hex nlri value @@ -63,6 +63,12 @@ def parse(cls, value, iswithdraw=False): nlri_list = [] while value: nlri_dict = {} + path_id = None + if addpath: + path_id = struct.unpack('!I', value[:4])[0] + value = value[4:] + nlri_dict['path_id'] = path_id + # for python2 and python3 if isinstance(value[0], int): prefix_bit_len = value[0] diff --git a/yabgp/message/update.py b/yabgp/message/update.py index e1ad2d7..ac4b4e5 100644 --- a/yabgp/message/update.py +++ b/yabgp/message/update.py @@ -184,7 +184,7 @@ def parse(cls, t, msg_hex, asn4=False, add_path_remote=False, add_path_local=Fal results['err_data'] = '' try: # parse attributes - results['attr'] = cls.parse_attributes(attribute_data, asn4) + results['attr'] = cls.parse_attributes(attribute_data, asn4, add_path_remote) except excep.UpdateMessageError as e: LOG.error(e) results['sub_error'] = e.sub_error @@ -278,12 +278,13 @@ def parse_prefix_list(data, addpath=False): return prefixes @staticmethod - def parse_attributes(data, asn4=False): + def parse_attributes(data, asn4=False, add_path=False): """ Parses an RFC4271 encoded blob of BGP attributes into a list :param data: :param asn4: support 4 bytes asn or not + :param add_path: support add_path or not :return: """ attributes = {} @@ -360,13 +361,13 @@ def parse_attributes(data, asn4=False): decode_value = LargeCommunity.parse(value=attr_value) elif type_code == bgp_cons.BGPTYPE_MP_REACH_NLRI: - decode_value = MpReachNLRI.parse(value=attr_value) + decode_value = MpReachNLRI.parse(value=attr_value, add_path=add_path) if decode_value['nlri'][0] and type(decode_value['nlri'][0]) is dict: if decode_value['nlri'][0].get("protocol_id"): bgpls_pro_id = decode_value['nlri'][0]["protocol_id"] elif type_code == bgp_cons.BGPTYPE_MP_UNREACH_NLRI: - decode_value = MpUnReachNLRI.parse(value=attr_value) + decode_value = MpUnReachNLRI.parse(value=attr_value, add_path=add_path) elif type_code == bgp_cons.BGPTYPE_EXTENDED_COMMUNITY: decode_value = ExtCommunity.parse(value=attr_value) diff --git a/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv4_labeled_unicast.py b/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv4_labeled_unicast.py index f3db490..317fc9b 100644 --- a/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv4_labeled_unicast.py +++ b/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv4_labeled_unicast.py @@ -52,6 +52,13 @@ def test_parse_with_multi_label(self): nlri_hex = b'\x48\x00\x14\x10\x00\x14\x21\x22\x01\x29\x48\x00\x14\x10\x00\x14\x21\x22\x01\x2a' self.assertEqual(nlri_list, IPv4LabeledUnicast.parse(nlri_hex)) + def test_parse_enable_add_path(self): + nlri_list = [ + {'path_id': 1, 'prefix': '5.5.5.5/32', 'label': [3]} + ] + nlri_hex = b'\x00\x00\x00\x01\x38\x00\x00\x31\x05\x05\x05\x05' + self.assertEqual(nlri_list, IPv4LabeledUnicast.parse(nlri_hex, addpath=True)) + if __name__ == '__main__': unittest.main() diff --git a/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py b/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py index 3b18ca5..a703747 100644 --- a/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py +++ b/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py @@ -44,6 +44,14 @@ def test_parse(self): self.assertEqual(nlri_list, IPv6LabeledUnicast.parse(nlri_hex)) + def test_parse_enable_add_path(self): + nlri_list = [ + {'path_id': 3, 'label': [2], 'prefix': '5::5/128'} + ] + nlri_hex = b'\x00\x00\x00\x03\x98\x00\x00\x21\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05' + self.assertEqual(nlri_list, IPv6LabeledUnicast.parse(nlri_hex, addpath=True)) + + if __name__ == '__main__': unittest.main() diff --git a/yabgp/tests/unit/message/attribute/nlri/test_ipv4_mpls_vpn.py b/yabgp/tests/unit/message/attribute/nlri/test_ipv4_mpls_vpn.py index 024d50e..81ee9d4 100644 --- a/yabgp/tests/unit/message/attribute/nlri/test_ipv4_mpls_vpn.py +++ b/yabgp/tests/unit/message/attribute/nlri/test_ipv4_mpls_vpn.py @@ -27,6 +27,12 @@ def test_parse(self): self.assertEqual( [{'label': [25], 'rd': '100:100', 'prefix': '170.0.0.0/32'}], IPv4MPLSVPN.parse(nlri_hex)) + def test_parse_1(self): + nlri_hex = b'\x00\x00\x00\x64\x78\x00\x01\x91\x00\x00\x00\x64\x00\x00\x00\x64\xaa\x00\x00\x00' + self.assertEqual( + [{'path_id': 100, 'label': [25], 'rd': '100:100', 'prefix': '170.0.0.0/32'}], + IPv4MPLSVPN.parse(nlri_hex, addpath=True)) + def test_construct_1(self): nlri_hex = b'\x76\x00\x01\x41\x00\x00\xfd\xea\x00\x00\x00\x01\x17\x00\x00\x00' nlri_list = [{'label': [20], 'rd': '65002:1', 'prefix': '23.0.0.0/30'}] diff --git a/yabgp/tests/unit/message/attribute/nlri/test_ipv6_mpls_vpn.py b/yabgp/tests/unit/message/attribute/nlri/test_ipv6_mpls_vpn.py index 9759432..7df2919 100644 --- a/yabgp/tests/unit/message/attribute/nlri/test_ipv6_mpls_vpn.py +++ b/yabgp/tests/unit/message/attribute/nlri/test_ipv6_mpls_vpn.py @@ -31,6 +31,11 @@ def test_update_parse(self): ] self.assertEqual(nlri_list, IPv6MPLSVPN.parse(value=nlri_hex)) + def test_update_parse_enable_add_path(self): + nlri_hex = b'\x00\x00\x00\x64\x98\x00\x03\x61\x00\x00\x00\x64\x00\x00\x00\x0c\x20\x10\x00\x00\x00\x12\x00\x04' + nlri_list = [{'path_id': 100, 'label': [54], 'rd': '100:12', 'prefix': '2010:0:12:4::/64'}] + self.assertEqual(nlri_list, IPv6MPLSVPN.parse(value=nlri_hex, addpath=True)) + def test_update_construct(self): nlri_hex = b'\x98\x00\x03\x61\x00\x00\x00\x64\x00\x00\x00\x0c\x20\x10\x00\x00\x00\x12\x00\x04' \ b'\x98\x00\x03\x71\x00\x00\x00\x64\x00\x00\x00\x0c\x20\x10\x00\x01\x00\x12\x00\x00' diff --git a/yabgp/tests/unit/message/attribute/nlri/test_ipv6_unicast.py b/yabgp/tests/unit/message/attribute/nlri/test_ipv6_unicast.py index 46ab6c1..33230e0 100644 --- a/yabgp/tests/unit/message/attribute/nlri/test_ipv6_unicast.py +++ b/yabgp/tests/unit/message/attribute/nlri/test_ipv6_unicast.py @@ -34,6 +34,12 @@ def test_parse_2(self): value_hoped = ['326c:ce92:25ea:365e:4d71:5945:2200:0/112'] self.assertEqual(value_hoped, value_parsed) + def test_parse_3(self): + nlri_data = b'\x00\x00\x03\xe9\x40\x20\x01\x0d\xb8\x00\x00\x00\x00' + value_parsed = IPv6Unicast().parse(nlri_data, addpath=True) + value_hoped = [{'path_id': 1001, 'prefix': '2001:db8::/64'}] + self.assertEqual(value_hoped, value_parsed) + def test_parse_withdraw(self): nlri_data = b'\x40\x20\x01\x0d\xb8\x00\x01\x00\x02\x40\x20\x01\x0d\xb8\x00\x01\x00\x01\x40\x20\x01\x0d' \ b'\xb8\x00\x01\x00\x00' From 96713570ffd196dc4678e109ce2aaadfbedf7b71 Mon Sep 17 00:00:00 2001 From: chenailin11 Date: Thu, 10 Apr 2025 14:57:09 +0800 Subject: [PATCH 5/5] fix pep8 error --- yabgp/message/attribute/mpunreachnlri.py | 3 ++- .../nlri/labeled_unicast/test_ipv6_labeled_unicast.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yabgp/message/attribute/mpunreachnlri.py b/yabgp/message/attribute/mpunreachnlri.py index 440014f..e98876c 100644 --- a/yabgp/message/attribute/mpunreachnlri.py +++ b/yabgp/message/attribute/mpunreachnlri.py @@ -102,7 +102,8 @@ def parse(cls, value, add_path=False): if safi == safn.SAFNUM_UNICAST: return dict(afi_safi=(afi, safi), withdraw=IPv6Unicast.parse(nlri_data=nlri_bin, addpath=add_path)) elif safi == safn.SAFNUM_LAB_VPNUNICAST: - return dict(afi_safi=(afi, safi), withdraw=IPv6MPLSVPN.parse(value=nlri_bin, iswithdraw=True, addpath=add_path)) + return dict(afi_safi=(afi, safi), withdraw=IPv6MPLSVPN.parse(value=nlri_bin, iswithdraw=True, + addpath=add_path)) else: return dict(afi_safi=(afi, safi), withdraw=repr(nlri_bin)) # for l2vpn diff --git a/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py b/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py index a703747..637ad87 100644 --- a/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py +++ b/yabgp/tests/unit/message/attribute/nlri/labeled_unicast/test_ipv6_labeled_unicast.py @@ -52,6 +52,5 @@ def test_parse_enable_add_path(self): self.assertEqual(nlri_list, IPv6LabeledUnicast.parse(nlri_hex, addpath=True)) - if __name__ == '__main__': unittest.main()