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..e98876c 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,10 @@ 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..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 @@ -44,6 +44,13 @@ 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'