This patch enables to support Zebra messages of FRRouting version 3.0
and introduces a flag to switch FRRouting version.

Signed-off-by: IWASE Yusuke <iwase.yusu...@gmail.com>
---
 ryu/cfg.py                          |   1 +
 ryu/flags.py                        |   8 ++
 ryu/lib/packet/zebra.py             | 164 ++++++++++++++++++++++++++++++------
 ryu/tests/unit/packet/test_zebra.py |  73 +++++++++++-----
 4 files changed, 203 insertions(+), 43 deletions(-)

diff --git a/ryu/cfg.py b/ryu/cfg.py
index c917b0b..d2951ee 100644
--- a/ryu/cfg.py
+++ b/ryu/cfg.py
@@ -36,6 +36,7 @@ CONF = oslo_config.cfg.ConfigOpts()
 
 from oslo_config.cfg import ConfigOpts
 
+from oslo_config.cfg import Opt
 from oslo_config.cfg import BoolOpt
 from oslo_config.cfg import IntOpt
 from oslo_config.cfg import ListOpt
diff --git a/ryu/flags.py b/ryu/flags.py
index 69eb3d2..b63b9b5 100644
--- a/ryu/flags.py
+++ b/ryu/flags.py
@@ -17,6 +17,8 @@
 global flags
 """
 
+from distutils.version import LooseVersion
+
 from ryu import cfg
 
 CONF = cfg.CONF
@@ -80,6 +82,9 @@ DEFAULT_ZSERV_CLIENT_ROUTE_TYPE = 'BGP'
 DEFAULT_ZSERV_INTERVAL = 10
 DEFAULT_ZSERV_DATABASE = 'sqlite:///zebra.db'
 DEFAULT_ZSERV_ROUTER_ID = '1.1.1.1'
+# For the backward compatibility with Quagga, the default FRRouting version
+# should be None.
+DEFAULT_ZSERV_FRR_VERSION = '0.0'
 
 CONF.register_cli_opts([
     cfg.StrOpt(
@@ -111,4 +116,7 @@ CONF.register_cli_opts([
         'router-id', default=DEFAULT_ZSERV_ROUTER_ID,
         help='Initial Router ID used by Zebra protocol service '
              '(default: %s)' % DEFAULT_ZSERV_ROUTER_ID),
+    cfg.Opt(
+        'frr-version', LooseVersion, default=DEFAULT_ZSERV_FRR_VERSION,
+        help='FRRouting version when integrated with FRRouting (e.g., 3.0)'),
 ], group='zapi')
diff --git a/ryu/lib/packet/zebra.py b/ryu/lib/packet/zebra.py
index 8e5b512..ad8b4f0 100644
--- a/ryu/lib/packet/zebra.py
+++ b/ryu/lib/packet/zebra.py
@@ -23,10 +23,13 @@ import abc
 import socket
 import struct
 import logging
+from distutils.version import LooseVersion
 
 import netaddr
 import six
 
+from ryu import flags as cfg_flags  # For loading 'zapi' option definition
+from ryu.cfg import CONF
 from ryu.lib import addrconv
 from ryu.lib import ip
 from ryu.lib import stringify
@@ -42,6 +45,9 @@ LOG = logging.getLogger(__name__)
 _DEFAULT_VERSION = 3
 _DEFAULT_FRR_VERSION = 4
 
+_FRR_VERSION_2_0 = LooseVersion('2.0')
+_FRR_VERSION_3_0 = LooseVersion('3.0')
+
 # Constants in quagga/lib/zebra.h
 
 # Default Zebra TCP port
@@ -462,6 +468,10 @@ def _serialize_zebra_family_prefix(prefix):
     raise ValueError('Invalid prefix: %s' % prefix)
 
 
+def _is_frr_version_ge(compared_version):
+    return CONF['zapi'].frr_version >= compared_version
+
+
 class InterfaceLinkParams(stringify.StringifyMixin):
     """
     Interface Link Parameters class for if_link_params structure.
@@ -733,15 +743,27 @@ class NextHopIPv4(_NextHop):
     """
     _BODY_FMT = '!4s'  # addr(IPv4)
     BODY_SIZE = struct.calcsize(_BODY_FMT)
+    _BODY_FMT_FRR_V3 = '!4sI'  # addr(IPv4), ifindex
+    BODY_SIZE_FRR_V3 = struct.calcsize(_BODY_FMT_FRR_V3)
 
     @classmethod
     def parse(cls, buf):
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            (addr, ifindex) = struct.unpack_from(cls._BODY_FMT_FRR_V3, buf)
+            addr = addrconv.ipv4.bin_to_text(addr)
+            rest = buf[cls.BODY_SIZE_FRR_V3:]
+            return cls(ifindex=ifindex, addr=addr), rest
+
         addr = addrconv.ipv4.bin_to_text(buf[:cls.BODY_SIZE])
         rest = buf[cls.BODY_SIZE:]
 
         return cls(addr=addr), rest
 
     def _serialize(self):
+        if _is_frr_version_ge(_FRR_VERSION_3_0) and self.ifindex:
+            addr = addrconv.ipv4.text_to_bin(self.addr)
+            return struct.pack(self._BODY_FMT_FRR_V3, addr, self.ifindex)
+
         return addrconv.ipv4.text_to_bin(self.addr)
 
 
@@ -798,15 +820,27 @@ class NextHopIPv6(_NextHop):
     """
     _BODY_FMT = '!16s'  # addr(IPv6)
     BODY_SIZE = struct.calcsize(_BODY_FMT)
+    _BODY_FMT_FRR_V3 = '!16sI'  # addr(IPv6), ifindex
+    BODY_SIZE_FRR_V3 = struct.calcsize(_BODY_FMT_FRR_V3)
 
     @classmethod
     def parse(cls, buf):
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            (addr, ifindex) = struct.unpack_from(cls._BODY_FMT_FRR_V3, buf)
+            addr = addrconv.ipv4.bin_to_text(addr)
+            rest = buf[cls.BODY_SIZE_FRR_V3:]
+            return cls(ifindex=ifindex, addr=addr), rest
+
         addr = addrconv.ipv6.bin_to_text(buf[:cls.BODY_SIZE])
         rest = buf[cls.BODY_SIZE:]
 
         return cls(addr=addr), rest
 
     def _serialize(self):
+        if _is_frr_version_ge(_FRR_VERSION_3_0) and self.ifindex:
+            addr = addrconv.ipv4.text_to_bin(self.addr)
+            return struct.pack(self._BODY_FMT_FRR_V3, addr, self.ifindex)
+
         return addrconv.ipv6.text_to_bin(self.addr)
 
 
@@ -1226,6 +1260,8 @@ class _ZebraInterface(_ZebraMessageBody):
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Metric                                                        |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    # | (Speed): v4(FRRouting v3.0 or later)                          |
+    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Interface's MTU for IPv4                                      |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Interface's MTU for IPv6                                      |
@@ -1254,8 +1290,12 @@ class _ZebraInterface(_ZebraMessageBody):
     V3_HEADER_SIZE = struct.calcsize(_V3_HEADER_FMT)
     # ifname, ifindex, status, if_flags, ptm_enable, ptm_status, metric,
     # ifmtu, ifmtu6, bandwidth, ll_type, hw_addr_len
-    _V4_HEADER_FMT = '!%dsIBQBBIIIIII' % INTERFACE_NAMSIZE
-    V4_HEADER_SIZE = struct.calcsize(_V4_HEADER_FMT)
+    _V4_HEADER_FMT_2_0 = '!%dsIBQBBIIIIII' % INTERFACE_NAMSIZE
+    V4_HEADER_SIZE_2_0 = struct.calcsize(_V4_HEADER_FMT_2_0)
+    # ifname, ifindex, status, if_flags, ptm_enable, ptm_status, metric,
+    # speed, ifmtu, ifmtu6, bandwidth, ll_type, hw_addr_len
+    _V4_HEADER_FMT_3_0 = '!%dsIBQBBIIIIIII' % INTERFACE_NAMSIZE
+    V4_HEADER_SIZE_3_0 = struct.calcsize(_V4_HEADER_FMT_3_0)
 
     # link_params_state (whether a link-params follows)
     _LP_STATE_FMT = '!?'
@@ -1264,8 +1304,8 @@ class _ZebraInterface(_ZebraMessageBody):
 
     def __init__(self, ifname=None, ifindex=None, status=None, if_flags=None,
                  ptm_enable=None, ptm_status=None,
-                 metric=None, ifmtu=None, ifmtu6=None, bandwidth=None,
-                 ll_type=None, hw_addr_len=0, hw_addr=None,
+                 metric=None, speed=None, ifmtu=None, ifmtu6=None,
+                 bandwidth=None, ll_type=None, hw_addr_len=0, hw_addr=None,
                  link_params=None):
         super(_ZebraInterface, self).__init__()
         self.ifname = ifname
@@ -1275,6 +1315,7 @@ class _ZebraInterface(_ZebraMessageBody):
         self.ptm_enable = ptm_enable
         self.ptm_status = ptm_status
         self.metric = metric
+        self.speed = speed
         self.ifmtu = ifmtu
         self.ifmtu6 = ifmtu6
         self.bandwidth = bandwidth
@@ -1290,6 +1331,7 @@ class _ZebraInterface(_ZebraMessageBody):
     def parse(cls, buf, version=_DEFAULT_VERSION):
         ptm_enable = None
         ptm_status = None
+        speed = None
         ll_type = None
         if version <= 2:
             (ifname, ifindex, status, if_flags, metric,
@@ -1302,10 +1344,20 @@ class _ZebraInterface(_ZebraMessageBody):
              hw_addr_len) = struct.unpack_from(cls._V3_HEADER_FMT, buf)
             rest = buf[cls.V3_HEADER_SIZE:]
         elif version == 4:
-            (ifname, ifindex, status, if_flags, ptm_enable, ptm_status,
-             metric, ifmtu, ifmtu6, bandwidth, ll_type,
-             hw_addr_len) = struct.unpack_from(cls._V4_HEADER_FMT, buf)
-            rest = buf[cls.V4_HEADER_SIZE:]
+            if _is_frr_version_ge(_FRR_VERSION_3_0):
+                (ifname, ifindex, status, if_flags, ptm_enable, ptm_status,
+                 metric, speed, ifmtu, ifmtu6, bandwidth, ll_type,
+                 hw_addr_len) = struct.unpack_from(cls._V4_HEADER_FMT_3_0, buf)
+                rest = buf[cls.V4_HEADER_SIZE_3_0:]
+            elif _is_frr_version_ge(_FRR_VERSION_2_0):
+                (ifname, ifindex, status, if_flags, ptm_enable, ptm_status,
+                 metric, ifmtu, ifmtu6, bandwidth, ll_type,
+                 hw_addr_len) = struct.unpack_from(cls._V4_HEADER_FMT_2_0, buf)
+                rest = buf[cls.V4_HEADER_SIZE_2_0:]
+            else:
+                raise struct.error(
+                    'Unsupported FRRouting version: %s'
+                    % CONF['zapi'].frr_version)
         else:
             raise struct.error(
                 'Unsupported Zebra protocol version: %d'
@@ -1325,7 +1377,7 @@ class _ZebraInterface(_ZebraMessageBody):
 
         if not rest:
             return cls(ifname, ifindex, status, if_flags,
-                       ptm_enable, ptm_status, metric, ifmtu, ifmtu6,
+                       ptm_enable, ptm_status, metric, speed, ifmtu, ifmtu6,
                        bandwidth, ll_type, hw_addr_len, hw_addr)
 
         (link_param_state,) = struct.unpack_from(cls._LP_STATE_FMT, rest)
@@ -1337,7 +1389,7 @@ class _ZebraInterface(_ZebraMessageBody):
             link_params = None
 
         return cls(ifname, ifindex, status, if_flags,
-                   ptm_enable, ptm_status, metric, ifmtu, ifmtu6,
+                   ptm_enable, ptm_status, metric, speed, ifmtu, ifmtu6,
                    bandwidth, ll_type, hw_addr_len, hw_addr,
                    link_params)
 
@@ -1368,12 +1420,24 @@ class _ZebraInterface(_ZebraMessageBody):
                 self.if_flags, self.metric, self.ifmtu, self.ifmtu6,
                 self.bandwidth, self.ll_type, hw_addr_len) + hw_addr
         elif version == 4:
-            buf = struct.pack(
-                self._V4_HEADER_FMT,
-                self.ifname.encode('ascii'), self.ifindex, self.status,
-                self.if_flags, self.ptm_enable, self.ptm_status, self.metric,
-                self.ifmtu, self.ifmtu6,
-                self.bandwidth, self.ll_type, hw_addr_len) + hw_addr
+            if _is_frr_version_ge(_FRR_VERSION_3_0):
+                buf = struct.pack(
+                    self._V4_HEADER_FMT_3_0,
+                    self.ifname.encode('ascii'), self.ifindex, self.status,
+                    self.if_flags, self.ptm_enable, self.ptm_status,
+                    self.metric, self.speed, self.ifmtu, self.ifmtu6,
+                    self.bandwidth, self.ll_type, hw_addr_len) + hw_addr
+            elif _is_frr_version_ge(_FRR_VERSION_2_0):
+                buf = struct.pack(
+                    self._V4_HEADER_FMT_2_0,
+                    self.ifname.encode('ascii'), self.ifindex, self.status,
+                    self.if_flags, self.ptm_enable, self.ptm_status,
+                    self.metric, self.ifmtu, self.ifmtu6,
+                    self.bandwidth, self.ll_type, hw_addr_len) + hw_addr
+            else:
+                raise ValueError(
+                    'Unsupported FRRouting version: %s'
+                    % CONF['zapi'].frr_version)
         else:
             raise ValueError(
                 'Unsupported Zebra protocol version: %d'
@@ -1552,6 +1616,8 @@ class _ZebraIPRoute(_ZebraMessageBody):
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | IPv4/v6 Prefix (Variable)                                     |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    # | (IPv4/v6 Source Prefix): v4(FRRouting v3.0 or later)          |
+    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Nexthop Num   |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Nexthops (Variable)                                           |
@@ -1603,6 +1669,8 @@ class _ZebraIPRoute(_ZebraMessageBody):
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | IPv4/v6 Prefix (Variable)                                     |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    # | (IPv4/v6 Source Prefix): v4(FRRouting v3.0 or later)          |
+    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | (Nexthop Num) |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | (Nexthops (Variable))                                         |
@@ -1631,7 +1699,8 @@ class _ZebraIPRoute(_ZebraMessageBody):
     # API type specific constants
     _FAMILY = None  # either socket.AF_INET or socket.AF_INET6
 
-    def __init__(self, route_type, flags, message, safi=None, prefix=None,
+    def __init__(self, route_type, flags, message, safi=None,
+                 prefix=None, src_prefix=None,
                  nexthops=None, ifindexes=None,
                  distance=None, metric=None, mtu=None, tag=None,
                  instance=None, from_zebra=False):
@@ -1652,6 +1721,10 @@ class _ZebraIPRoute(_ZebraMessageBody):
             prefix = prefix.prefix
         self.prefix = prefix
 
+        if isinstance(src_prefix, (IPv4Prefix, IPv6Prefix)):
+            src_prefix = src_prefix.prefix
+        self.src_prefix = src_prefix
+
         # Nexthops should be a list of str representations of IP address
         # if this message sent from Zebra, otherwise a list of _Nexthop
         # subclasses.
@@ -1715,6 +1788,10 @@ class _ZebraIPRoute(_ZebraMessageBody):
 
         prefix, rest = _parse_ip_prefix(cls._FAMILY, rest)
 
+        src_prefix = None
+        if version == 4 and message & FRR_ZAPI_MESSAGE_SRCPFX:
+            src_prefix, rest = _parse_ip_prefix(cls._FAMILY, rest)
+
         if from_zebra and message & ZAPI_MESSAGE_NEXTHOP:
             nexthops = []
             (nexthop_num,) = struct.unpack_from(cls._NUM_FMT, rest)
@@ -1764,7 +1841,7 @@ class _ZebraIPRoute(_ZebraMessageBody):
                 'Unsupported Zebra protocol version: %d'
                 % version)
 
-        return cls(route_type, flags, message, safi, prefix,
+        return cls(route_type, flags, message, safi, prefix, src_prefix,
                    nexthops, ifindexes,
                    distance, metric, mtu, tag,
                    instance, from_zebra=from_zebra)
@@ -1788,6 +1865,9 @@ class _ZebraIPRoute(_ZebraMessageBody):
 
     def serialize(self, version=_DEFAULT_VERSION):
         prefix = _serialize_ip_prefix(self.prefix)
+        if version == 4 and self.src_prefix:
+            self.message |= FRR_ZAPI_MESSAGE_SRCPFX  # fixup
+            prefix += _serialize_ip_prefix(self.src_prefix)
 
         nexthops = b''
         if self.from_zebra and self.nexthops:
@@ -2491,6 +2571,8 @@ class ZebraNexthopUpdate(_ZebraMessageBody):
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | IPv4/v6 prefix                                                |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    # | (Distance)    | v4(FRRouting v3.0 or later)
+    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Metric                                                        |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Nexthop Num   |
@@ -2499,15 +2581,22 @@ class ZebraNexthopUpdate(_ZebraMessageBody):
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     _FAMILY_FMT = '!H'  # family
     FAMILY_SIZE = struct.calcsize(_FAMILY_FMT)
+    _DISTANCE_FMT = '!B'  # metric
+    DISTANCE_SIZE = struct.calcsize(_DISTANCE_FMT)
     _METRIC_FMT = '!I'  # metric
     METRIC_SIZE = struct.calcsize(_METRIC_FMT)
 
-    def __init__(self, family, prefix, metric, nexthops=None):
+    def __init__(self, family, prefix, distance=None, metric=None,
+                 nexthops=None):
         super(ZebraNexthopUpdate, self).__init__()
         self.family = family
         if isinstance(prefix, (IPv4Prefix, IPv6Prefix)):
             prefix = prefix.prefix
         self.prefix = prefix
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            assert distance is not None
+        self.distance = distance
+        assert metric is not None
         self.metric = metric
         nexthops = nexthops or []
         for nexthop in nexthops:
@@ -2521,12 +2610,17 @@ class ZebraNexthopUpdate(_ZebraMessageBody):
 
         prefix, rest = _parse_ip_prefix(family, rest)
 
+        distance = None
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            (distance,) = struct.unpack_from(cls._DISTANCE_FMT, rest)
+            rest = rest[cls.DISTANCE_SIZE:]
+
         (metric,) = struct.unpack_from(cls._METRIC_FMT, rest)
         rest = rest[cls.METRIC_SIZE:]
 
         nexthops, rest = _parse_nexthops(rest, version)
 
-        return cls(family, prefix, metric, nexthops)
+        return cls(family, prefix, distance, metric, nexthops)
 
     def serialize(self, version=_DEFAULT_VERSION):
         # fixup
@@ -2541,6 +2635,9 @@ class ZebraNexthopUpdate(_ZebraMessageBody):
 
         buf += _serialize_ip_prefix(self.prefix)
 
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            buf += struct.pack(self._DISTANCE_FMT, self.distance)
+
         buf += struct.pack(self._METRIC_FMT, self.metric)
 
         return buf + _serialize_nexthops(self.nexthops, version=version)
@@ -3200,6 +3297,8 @@ class _ZebraMplsLabels(_ZebraMessageBody):
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Gate IPv4/v6 Address (4 bytes/16 bytes)                       |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    # | Interface Index: v4(FRRouting v3.0 or later)                  |
+    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | Distance      |
     # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     # | In Label                                                      |
@@ -3216,10 +3315,12 @@ class _ZebraMplsLabels(_ZebraMessageBody):
     IPV6_PREFIX_SIZE = struct.calcsize(_IPV6_PREFIX_FMT)
     _FAMILY_IPV4_PREFIX_FMT = '!I4sB'
     _FAMILY_IPV6_PREFIX_FMT = '!I16sB'
+    _IFINDEX_FMT = '!I'
+    IFINDEX_SIZE = struct.calcsize(_IFINDEX_FMT)
     _BODY_FMT = '!BII'  # distance, in_label, out_label
 
-    def __init__(self, route_type, family, prefix, gate_addr,
-                 distance, in_label, out_label):
+    def __init__(self, route_type, family, prefix, gate_addr, ifindex=None,
+                 distance=None, in_label=None, out_label=None):
         super(_ZebraMplsLabels, self).__init__()
         self.route_type = route_type
         self.family = family
@@ -3228,8 +3329,14 @@ class _ZebraMplsLabels(_ZebraMessageBody):
         self.prefix = prefix
         assert netaddr.valid_ipv4(gate_addr) or netaddr.valid_ipv6(gate_addr)
         self.gate_addr = gate_addr
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            assert ifindex is not None
+        self.ifindex = ifindex
+        assert distance is not None
         self.distance = distance
+        assert in_label is not None
         self.in_label = in_label
+        assert out_label is not None
         self.out_label = out_label
 
     @classmethod
@@ -3266,10 +3373,15 @@ class _ZebraMplsLabels(_ZebraMessageBody):
         else:
             raise struct.error('Unsupported family: %d' % family)
 
+        ifindex = None
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            (ifindex,) = struct.unpack_from(cls._IFINDEX_FMT, rest)
+            rest = rest[cls.IFINDEX_SIZE:]
+
         (distance, in_label,
          out_label) = struct.unpack_from(cls._BODY_FMT, rest)
 
-        return cls(route_type, family, prefix, gate_addr,
+        return cls(route_type, family, prefix, gate_addr, ifindex,
                    distance, in_label, out_label)
 
     def _serialize_family_prefix(self, prefix):
@@ -3303,7 +3415,11 @@ class _ZebraMplsLabels(_ZebraMessageBody):
         else:
             raise ValueError('Unsupported family: %d' % self.family)
 
-        body_bin = struct.pack(
+        body_bin = b''
+        if _is_frr_version_ge(_FRR_VERSION_3_0):
+            body_bin = struct.pack(self._IFINDEX_FMT, self.ifindex)
+
+        body_bin += struct.pack(
             self._BODY_FMT, self.distance, self.in_label, self.out_label)
 
         return struct.pack(
diff --git a/ryu/tests/unit/packet/test_zebra.py 
b/ryu/tests/unit/packet/test_zebra.py
index b5fb9c5..4ea76b5 100644
--- a/ryu/tests/unit/packet/test_zebra.py
+++ b/ryu/tests/unit/packet/test_zebra.py
@@ -15,6 +15,11 @@
 
 from __future__ import print_function
 
+try:
+    import mock  # Python 2
+except ImportError:
+    from unittest import mock  # Python 3
+
 import os
 import socket
 import sys
@@ -36,37 +41,53 @@ PCAP_DATA_DIR = os.path.join(
     '../../packet_data/pcap/')
 
 
+_patch_frr_v2 = mock.patch(
+    'ryu.lib.packet.zebra._is_frr_version_ge',
+    mock.MagicMock(side_effect=lambda x: x == zebra._FRR_VERSION_2_0))
+
+
 class Test_zebra(unittest.TestCase):
     """
     Test case for ryu.lib.packet.zebra.
     """
 
-    def test_pcap(self):
+    @staticmethod
+    def _test_pcap_single(f):
+        zebra_pcap_file = os.path.join(PCAP_DATA_DIR, f + '.pcap')
+        # print('*** testing %s' % zebra_pcap_file)
+
+        for _, buf in pcaplib.Reader(open(zebra_pcap_file, 'rb')):
+            # Checks if Zebra message can be parsed as expected.
+            pkt = packet.Packet(buf)
+            zebra_pkts = pkt.get_protocols(zebra.ZebraMessage)
+            for zebra_pkt in zebra_pkts:
+                ok_(isinstance(zebra_pkt, zebra.ZebraMessage),
+                    'Failed to parse Zebra message: %s' % pkt)
+            ok_(not isinstance(pkt.protocols[-1],
+                               (six.binary_type, bytearray)),
+                'Some messages could not be parsed in %s: %s' % (f, pkt))
+
+            # Checks if Zebra message can be serialized as expected.
+            pkt.serialize()
+            eq_(binary_str(buf), binary_str(pkt.data))
+
+    def test_pcap_quagga(self):
         files = [
             'zebra_v2',
             'zebra_v3',
+        ]
+
+        for f in files:
+            self._test_pcap_single(f)
+
+    @_patch_frr_v2
+    def test_pcap_frr_v2(self):
+        files = [
             'zebra_v4_frr_v2',  # API version 4 on FRRouting v2.0
         ]
 
         for f in files:
-            zebra_pcap_file = os.path.join(PCAP_DATA_DIR, f + '.pcap')
-            # print('*** testing %s' % zebra_pcap_file)
-
-            for _, buf in pcaplib.Reader(open(zebra_pcap_file, 'rb')):
-                # Checks if Zebra message can be parsed as expected.
-                pkt = packet.Packet(buf)
-                zebra_pkts = pkt.get_protocols(zebra.ZebraMessage)
-                for zebra_pkt in zebra_pkts:
-                    ok_(isinstance(zebra_pkt, zebra.ZebraMessage),
-                        'Failed to parse Zebra message: %s' % pkt)
-                ok_(not isinstance(pkt.protocols[-1],
-                                   (six.binary_type, bytearray)),
-                    'Some messages could not be parsed: %s' % pkt)
-
-                # Checks if Zebra message can be serialized as expected.
-                pkt.serialize()
-                eq_(buf, pkt.data,
-                    "b'%s' != b'%s'" % (binary_str(buf), binary_str(pkt.data)))
+            self._test_pcap_single(f)
 
 
 class TestZebraMessage(unittest.TestCase):
@@ -227,6 +248,7 @@ class TestZebraNexthopUpdateIPv6(unittest.TestCase):
     nexthop_type = zebra.ZEBRA_NEXTHOP_IFINDEX
     ifindex = 2
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraNexthopUpdate.parse(self.buf)
 
@@ -253,6 +275,7 @@ class TestZebraInterfaceNbrAddressAdd(unittest.TestCase):
     family = socket.AF_INET
     prefix = '192.168.1.0/24'
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraInterfaceNbrAddressAdd.parse(self.buf)
 
@@ -283,6 +306,7 @@ class 
TestZebraInterfaceBfdDestinationUpdate(unittest.TestCase):
     src_family = socket.AF_INET
     src_prefix = '192.168.1.2/24'
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraInterfaceBfdDestinationUpdate.parse(self.buf)
 
@@ -323,6 +347,7 @@ class 
TestZebraBfdDestinationRegisterMultiHopEnabled(unittest.TestCase):
     multi_hop_count = 5
     ifname = None
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraBfdDestinationRegister.parse(self.buf)
 
@@ -369,6 +394,7 @@ class 
TestZebraBfdDestinationRegisterMultiHopDisabled(unittest.TestCase):
     multi_hop_count = None
     ifname = 'eth0'
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraBfdDestinationRegister.parse(self.buf)
 
@@ -420,6 +446,7 @@ class 
TestZebraBfdDestinationRegisterMultiHopEnabledIPv6(unittest.TestCase):
     multi_hop_count = 5
     ifname = None
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraBfdDestinationRegister.parse(self.buf)
 
@@ -459,6 +486,7 @@ class 
TestZebraBfdDestinationDeregisterMultiHopEnabled(unittest.TestCase):
     multi_hop_count = 5
     ifname = None
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraBfdDestinationDeregister.parse(self.buf)
 
@@ -496,6 +524,7 @@ class 
TestZebraBfdDestinationDeregisterMultiHopDisabled(unittest.TestCase):
     multi_hop_count = None
     ifname = 'eth0'
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraBfdDestinationDeregister.parse(self.buf)
 
@@ -538,6 +567,7 @@ class 
TestZebraBfdDestinationDeregisterMultiHopEnabledIPv6(unittest.TestCase):
     multi_hop_count = 5
     ifname = None
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraBfdDestinationDeregister.parse(self.buf)
 
@@ -569,6 +599,7 @@ class TestZebraVrfAdd(unittest.TestCase):
     )
     vrf_name = 'VRF1'
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraVrfAdd.parse(self.buf)
 
@@ -587,6 +618,7 @@ class TestZebraInterfaceVrfUpdate(unittest.TestCase):
     ifindex = 1
     vrf_id = 2
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraInterfaceVrfUpdate.parse(self.buf)
 
@@ -606,6 +638,7 @@ class TestZebraInterfaceEnableRadv(unittest.TestCase):
     ifindex = 1
     interval = 0x100
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraInterfaceEnableRadv.parse(self.buf)
 
@@ -636,6 +669,7 @@ class TestZebraMplsLabelsAddIPv4(unittest.TestCase):
     in_label = 100
     out_label = zebra.MPLS_IMP_NULL_LABEL
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraMplsLabelsAdd.parse(self.buf)
 
@@ -677,6 +711,7 @@ class TestZebraMplsLabelsAddIPv6(unittest.TestCase):
     in_label = 100
     out_label = zebra.MPLS_IMP_NULL_LABEL
 
+    @_patch_frr_v2
     def test_parser(self):
         body = zebra.ZebraMplsLabelsAdd.parse(self.buf)
 
-- 
2.7.4


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Ryu-devel mailing list
Ryu-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to