packet/bgp: Support multiple next_hop in BGPPathAttributeMpReachNLRI

In case of the IPv6 address family in MP-BGP, NLRI might has multipule
next_hop addresses (e.g., one is global unicast address and the other
is link local unicast address).

This patch fixes to support multipule next_hop addresses in MP-BGP
NLRIs.
For the backward compatibility, this patch make it to stor the first
next_hop address as next_hop attribute and all next_hop addresses
as next_hop_list.

Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
IWASE Yusuke 2016-11-22 11:20:22 +09:00 committed by FUJITA Tomonori
parent ad5ce12e70
commit 9e5f9d9d37
3 changed files with 125 additions and 20 deletions

View File

@ -25,6 +25,7 @@ RFC 4271 BGP-4
import abc
import copy
import functools
import io
import socket
import struct
import base64
@ -3194,6 +3195,7 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
_RESERVED_LENGTH = 1
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
_class_suffixes = ['AddrPrefix']
_opt_attributes = ['next_hop']
_TYPE = {
'ascii': [
'next_hop'
@ -3206,10 +3208,15 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
flags=flags, type_=type_, length=length)
self.afi = afi
self.safi = safi
if (not netaddr.valid_ipv4(next_hop)
and not netaddr.valid_ipv6(next_hop)):
raise ValueError('Invalid address for next_hop: %s' % next_hop)
self.next_hop = next_hop
if not isinstance(next_hop, (list, tuple)):
next_hop = [next_hop]
for n in next_hop:
if not netaddr.valid_ipv4(n) and not netaddr.valid_ipv6(n):
raise ValueError('Invalid address for next_hop: %s' % n)
# Note: For the backward compatibility, stores the first next_hop
# address and all next_hop addresses separately.
self._next_hop = next_hop[0]
self._next_hop_list = next_hop
self.nlri = nlri
addr_cls = _get_addr_class(afi, safi)
for i in nlri:
@ -3217,6 +3224,25 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
raise ValueError('Invalid NRLI class for afi=%d and safi=%d'
% (self.afi, self.safi))
@staticmethod
def split_bin_with_len(buf, unit_len):
f = io.BytesIO(buf)
return [f.read(unit_len) for _ in range(0, len(buf), unit_len)]
@classmethod
def parse_next_hop_ipv4(cls, buf, unit_len):
next_hop = []
for next_hop_bin in cls.split_bin_with_len(buf, unit_len):
next_hop.append(addrconv.ipv4.bin_to_text(next_hop_bin[-4:]))
return next_hop
@classmethod
def parse_next_hop_ipv6(cls, buf, unit_len):
next_hop = []
for next_hop_bin in cls.split_bin_with_len(buf, unit_len):
next_hop.append(addrconv.ipv6.bin_to_text(next_hop_bin[-16:]))
return next_hop
@classmethod
def parse_value(cls, buf):
(afi, safi, next_hop_len,) = struct.unpack_from(
@ -3236,16 +3262,20 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
nlri.append(n)
rf = RouteFamily(afi, safi)
if rf == RF_IPv6_VPN:
next_hop = addrconv.ipv6.bin_to_text(next_hop_bin[cls._RD_LENGTH:])
next_hop_len -= cls._RD_LENGTH
elif rf == RF_IPv4_VPN:
next_hop = addrconv.ipv4.bin_to_text(next_hop_bin[cls._RD_LENGTH:])
next_hop_len -= cls._RD_LENGTH
elif afi == addr_family.IP or (rf == RF_L2_EVPN and next_hop_len == 4):
next_hop = addrconv.ipv4.bin_to_text(next_hop_bin)
elif afi == addr_family.IP6 or (rf == RF_L2_EVPN and next_hop_len > 4):
next_hop = addrconv.ipv6.bin_to_text(next_hop_bin)
if rf == RF_IPv4_VPN:
next_hop = cls.parse_next_hop_ipv4(next_hop_bin,
cls._RD_LENGTH + 4)
next_hop_len -= cls._RD_LENGTH * len(next_hop)
elif rf == RF_IPv6_VPN:
next_hop = cls.parse_next_hop_ipv6(next_hop_bin,
cls._RD_LENGTH + 16)
next_hop_len -= cls._RD_LENGTH * len(next_hop)
elif (afi == addr_family.IP
or (rf == RF_L2_EVPN and next_hop_len < 16)):
next_hop = cls.parse_next_hop_ipv4(next_hop_bin, 4)
elif (afi == addr_family.IP6
or (rf == RF_L2_EVPN and next_hop_len >= 16)):
next_hop = cls.parse_next_hop_ipv6(next_hop_bin, 16)
else:
raise ValueError('Invalid address family: afi=%d, safi=%d'
% (afi, safi))
@ -3257,13 +3287,21 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
'nlri': nlri,
}
def serialize_next_hop(self):
buf = bytearray()
for next_hop in self.next_hop_list:
if self.afi == addr_family.IP6:
next_hop = str(netaddr.IPAddress(next_hop).ipv6())
next_hop_bin = ip.text_to_bin(next_hop)
if RouteFamily(self.afi, self.safi) in (RF_IPv4_VPN, RF_IPv6_VPN):
# Empty label stack(RD=0:0) + IP address
next_hop_bin = b'\x00' * self._RD_LENGTH + next_hop_bin
buf += next_hop_bin
return buf
def serialize_value(self):
if self.afi == addr_family.IP6:
self.next_hop = str(netaddr.IPAddress(self.next_hop).ipv6())
next_hop_bin = ip.text_to_bin(self.next_hop)
if RouteFamily(self.afi, self.safi) in (RF_IPv4_VPN, RF_IPv6_VPN):
# Empty label stack(RD=0:0) + IP address
next_hop_bin = b'\x00' * self._RD_LENGTH + next_hop_bin
next_hop_bin = self.serialize_next_hop()
# fixup
next_hop_len = len(next_hop_bin)
@ -3281,6 +3319,31 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
return buf
@property
def next_hop(self):
return self._next_hop
@next_hop.setter
def next_hop(self, addr):
if not netaddr.valid_ipv4(addr) and not netaddr.valid_ipv6(addr):
raise ValueError('Invalid address for next_hop: %s' % addr)
self._next_hop = addr
self.next_hop_list[0] = addr
@property
def next_hop_list(self):
return self._next_hop_list
@next_hop_list.setter
def next_hop_list(self, addr_list):
if not isinstance(addr_list, (list, tuple)):
addr_list = [addr_list]
for addr in addr_list:
if not netaddr.valid_ipv4(addr) and not netaddr.valid_ipv6(addr):
raise ValueError('Invalid address for next_hop: %s' % addr)
self._next_hop = addr_list[0]
self._next_hop_list = addr_list
@property
def route_family(self):
return _rf_map[(self.afi, self.safi)]

View File

@ -115,6 +115,17 @@ class Test_bgp(unittest.TestCase):
mp_nlri2 = [
bgp.LabelledIPAddrPrefix(24, '192.168.0.0', labels=[1, 2, 3])
]
mp_nlri_v6 = [
bgp.LabelledVPNIP6AddrPrefix(64, '2001:db8:1111::',
route_dist='200:200',
labels=[1, 2, 3]),
bgp.LabelledVPNIP6AddrPrefix(64, '2001:db8:2222::',
route_dist='10.0.0.1:10000',
labels=[5, 6, 7, 8]),
]
mp_nlri2_v6 = [
bgp.LabelledIP6AddrPrefix(64, '2001:db8:3333::', labels=[1, 2, 3])
]
communities = [
bgp.BGP_COMMUNITY_NO_EXPORT,
bgp.BGP_COMMUNITY_NO_ADVERTISE,
@ -191,6 +202,13 @@ class Test_bgp(unittest.TestCase):
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP, safi=safi.MPLS_LABEL,
next_hop='1.1.1.1',
nlri=mp_nlri2),
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP6, safi=safi.MPLS_VPN,
next_hop=['2001:db8::1'],
nlri=mp_nlri_v6),
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP6, safi=safi.MPLS_LABEL,
next_hop=['2001:db8::1',
'fe80::1'],
nlri=mp_nlri2_v6),
bgp.BGPPathAttributeMpUnreachNLRI(afi=afi.IP, safi=safi.MPLS_VPN,
withdrawn_routes=mp_nlri),
bgp.BGPPathAttributeUnknown(flags=0, type_=100, value=300 * b'bar')
@ -315,6 +333,20 @@ class Test_bgp(unittest.TestCase):
route_dist='10.0.0.1:10000',
labels=[5, 6, 7, 8]),
]
mp_nlri2 = [
bgp.LabelledIPAddrPrefix(24, '192.168.0.0', labels=[1, 2, 3])
]
mp_nlri_v6 = [
bgp.LabelledVPNIP6AddrPrefix(64, '2001:db8:1111::',
route_dist='200:200',
labels=[1, 2, 3]),
bgp.LabelledVPNIP6AddrPrefix(64, '2001:db8:2222::',
route_dist='10.0.0.1:10000',
labels=[5, 6, 7, 8]),
]
mp_nlri2_v6 = [
bgp.LabelledIP6AddrPrefix(64, '2001:db8:3333::', labels=[1, 2, 3])
]
communities = [
bgp.BGP_COMMUNITY_NO_EXPORT,
bgp.BGP_COMMUNITY_NO_ADVERTISE,
@ -386,6 +418,16 @@ class Test_bgp(unittest.TestCase):
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP, safi=safi.MPLS_VPN,
next_hop='1.1.1.1',
nlri=mp_nlri),
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP, safi=safi.MPLS_LABEL,
next_hop='1.1.1.1',
nlri=mp_nlri2),
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP6, safi=safi.MPLS_VPN,
next_hop=['2001:db8::1'],
nlri=mp_nlri_v6),
bgp.BGPPathAttributeMpReachNLRI(afi=afi.IP6, safi=safi.MPLS_LABEL,
next_hop=['2001:db8::1',
'fe80::1'],
nlri=mp_nlri2_v6),
bgp.BGPPathAttributeMpUnreachNLRI(afi=afi.IP, safi=safi.MPLS_VPN,
withdrawn_routes=mp_nlri),
bgp.BGPPathAttributeUnknown(flags=0, type_=100, value=300 * b'bar')