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 <[email protected]>
---
 ryu/lib/packet/bgp.py                            | 103 ++++++++++++++++++-----
 ryu/tests/packet_data/bgp4/bgp4-update_ipv6.pcap | Bin 168 -> 233 bytes
 ryu/tests/unit/packet/test_bgp.py                |  42 +++++++++
 3 files changed, 125 insertions(+), 20 deletions(-)

diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py
index f96e86a..e5b0400 100644
--- a/ryu/lib/packet/bgp.py
+++ b/ryu/lib/packet/bgp.py
@@ -25,6 +25,7 @@ RFC 4271 BGP-4
 import abc
 import copy
 import functools
+import io
 import socket
 import struct
 import base64
@@ -3147,6 +3148,7 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute):
     _RESERVED_LENGTH = 1
     _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
     _class_suffixes = ['AddrPrefix']
+    _opt_attributes = ['next_hop']
     _TYPE = {
         'ascii': [
             'next_hop'
@@ -3159,10 +3161,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:
@@ -3170,6 +3177,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(
@@ -3189,16 +3215,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))
@@ -3210,13 +3240,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)
@@ -3235,6 +3273,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)]
 
diff --git a/ryu/tests/packet_data/bgp4/bgp4-update_ipv6.pcap 
b/ryu/tests/packet_data/bgp4/bgp4-update_ipv6.pcap
index 
c21066924a6da8afe4a3d489f2c22875a06e1ffd..49ed4d9e2bbbbc81f2f5c32153713526de20eb25
 100644
GIT binary patch
literal 233
zcmca|c+)~A1{MYcU}0bca_s-}Mcnz%$#4+J2H{hTf=3x-PB95SWsqsRn*&nb&Bmy}
z$h(7qVbv-S7YLZp_>2sjPpr#*z&>a7^o*MU67~yQ(ij;2qXC8}CZJa520k4ICPoD`
z6PW%rfDDEMCI$lq0j?bkOb(2U3=T|eOhC8(ebvAM_5hl}{~2UKCbKp0GXqTq0QpNr
AGXMYp

literal 168
zcmca|c+)~A1{MYw`2U}Qff2~r(<K-Y@RpOI0muenK_z3C(6m*%&)j|YpM$}bfuRT}
z$nc+Sc{3LS0}~K49${wK3=#u@03iPG1SpRN7`&K(GR6&jG7L<N0{<F-90XuuFi;TS
Z+QGo&z{tqpz{JJ`bjROU4J=^C0RWK_EVKXs

diff --git a/ryu/tests/unit/packet/test_bgp.py 
b/ryu/tests/unit/packet/test_bgp.py
index 0d28e6a..e47e9f6 100644
--- a/ryu/tests/unit/packet/test_bgp.py
+++ b/ryu/tests/unit/packet/test_bgp.py
@@ -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,
@@ -187,6 +198,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')
@@ -311,6 +329,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,
@@ -378,6 +410,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')
-- 
2.7.4


------------------------------------------------------------------------------
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to