Signed-off-by: IWASE Yusuke <[email protected]>
---
 ryu/lib/packet/bgp.py                          |  41 ++--
 ryu/services/protocols/bgp/bgpspeaker.py       |  31 +--
 ryu/services/protocols/bgp/peer.py             | 270 +++++++++++++++++++++++--
 ryu/services/protocols/bgp/rtconf/base.py      |  13 +-
 ryu/services/protocols/bgp/rtconf/common.py    |   4 +-
 ryu/services/protocols/bgp/rtconf/neighbors.py |  27 ++-
 ryu/services/protocols/bgp/speaker.py          |  41 +++-
 ryu/services/protocols/bgp/utils/validation.py |  22 +-
 8 files changed, 380 insertions(+), 69 deletions(-)

diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py
index 937fdeb..68105a8 100644
--- a/ryu/lib/packet/bgp.py
+++ b/ryu/lib/packet/bgp.py
@@ -1045,24 +1045,18 @@ class RouteTargetMembershipNLRI(StringifyMixin):
         if not (origin_as is self.DEFAULT_AS and
                 route_target is self.DEFAULT_RT):
             # We validate them
-            if (not self._is_valid_old_asn(origin_as) or
+            if (not self._is_valid_asn(origin_as) or
                     not self._is_valid_ext_comm_attr(route_target)):
                 raise ValueError('Invalid params.')
         self.origin_as = origin_as
         self.route_target = route_target
 
-    def _is_valid_old_asn(self, asn):
-        """Returns true if given asn is a 16 bit number.
-
-        Old AS numbers are 16 but unsigned number.
-        """
-        valid = True
-        # AS number should be a 16 bit number
-        if (not isinstance(asn, numbers.Integral) or (asn < 0) or
-                (asn > ((2 ** 16) - 1))):
-            valid = False
-
-        return valid
+    def _is_valid_asn(self, asn):
+        """Returns True if the given AS number is Two or Four Octet."""
+        if isinstance(asn, six.integer_types) and 0 <= asn <= 0xffffffff:
+            return True
+        else:
+            return False
 
     def _is_valid_ext_comm_attr(self, attr):
         """Validates *attr* as string representation of RT or SOO.
@@ -2311,6 +2305,17 @@ class BGPOpen(BGPMessage):
         self.opt_param_len = opt_param_len
         self.opt_param = opt_param
 
+    @property
+    def opt_param_cap_map(self):
+        cap_map = {}
+        for param in self.opt_param:
+            if param.type == BGP_OPT_CAPABILITY:
+                cap_map[param.cap_code] = param
+        return cap_map
+
+    def get_opt_param_cap(self, cap_code):
+        return self.opt_param_cap_map.get(cap_code)
+
     @classmethod
     def parser(cls, buf):
         (version,
@@ -2403,17 +2408,17 @@ class BGPUpdate(BGPMessage):
         self.withdrawn_routes = withdrawn_routes
         self.total_path_attribute_len = total_path_attribute_len
         self.path_attributes = path_attributes
-        self._pathattr_map = {}
-        for attr in path_attributes:
-            self._pathattr_map[attr.type] = attr
         self.nlri = nlri
 
     @property
     def pathattr_map(self):
-        return self._pathattr_map
+        passattr_map = {}
+        for attr in self.path_attributes:
+            passattr_map[attr.type] = attr
+        return passattr_map
 
     def get_path_attr(self, attr_name):
-        return self._pathattr_map.get(attr_name)
+        return self.pathattr_map.get(attr_name)
 
     @classmethod
     def parser(cls, buf):
diff --git a/ryu/services/protocols/bgp/bgpspeaker.py 
b/ryu/services/protocols/bgp/bgpspeaker.py
index ce3eaed..2ce6372 100644
--- a/ryu/services/protocols/bgp/bgpspeaker.py
+++ b/ryu/services/protocols/bgp/bgpspeaker.py
@@ -45,13 +45,14 @@ from ryu.services.protocols.bgp.rtconf.base import 
CAP_MBGP_IPV6
 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4
 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV6
 from ryu.services.protocols.bgp.rtconf.base import CAP_ENHANCED_REFRESH
+from ryu.services.protocols.bgp.rtconf.base import CAP_FOUR_OCTET_AS_NUMBER
 from ryu.services.protocols.bgp.rtconf.base import MULTI_EXIT_DISC
 from ryu.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS
 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_IPV4
 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_VPNV4
 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_VPNV6
-from ryu.services.protocols.bgp.rtconf.neighbors \
-    import DEFAULT_CAP_ENHANCED_REFRESH
+from ryu.services.protocols.bgp.rtconf.neighbors import (
+    DEFAULT_CAP_ENHANCED_REFRESH, DEFAULT_CAP_FOUR_OCTET_AS_NUMBER)
 from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CONNECT_MODE
 from ryu.services.protocols.bgp.rtconf.neighbors import PEER_NEXT_HOP
 from ryu.services.protocols.bgp.rtconf.neighbors import PASSWORD
@@ -237,6 +238,7 @@ class BGPSpeaker(object):
                      enable_vpnv4=DEFAULT_CAP_MBGP_VPNV4,
                      enable_vpnv6=DEFAULT_CAP_MBGP_VPNV6,
                      enable_enhanced_refresh=DEFAULT_CAP_ENHANCED_REFRESH,
+                     
enable_four_octet_as_number=DEFAULT_CAP_FOUR_OCTET_AS_NUMBER,
                      next_hop=None, password=None, multi_exit_disc=None,
                      site_of_origins=None, is_route_server_client=False,
                      is_next_hop_self=False, local_address=None,
@@ -262,9 +264,12 @@ class BGPSpeaker(object):
         ``enable_vpnv6`` enables VPNv6 address family for this
         neighbor. The default is False.
 
-        ``enable_enhanced_refresh`` enable Enhanced Route Refresh for this
+        ``enable_enhanced_refresh`` enables Enhanced Route Refresh for this
         neighbor. The default is False.
 
+        ``enable_four_octet_as_number`` enables Four-Octet AS Number
+        capability for this neighbor. The default is True.
+
         ``next_hop`` specifies the next hop IP address. If not
         specified, host's ip address to access to a peer is used.
 
@@ -298,15 +303,17 @@ class BGPSpeaker(object):
         CONNECT_MODE_BOTH use both methods.
         The default is CONNECT_MODE_BOTH.
         """
-        bgp_neighbor = {}
-        bgp_neighbor[neighbors.IP_ADDRESS] = address
-        bgp_neighbor[neighbors.REMOTE_AS] = remote_as
-        bgp_neighbor[PEER_NEXT_HOP] = next_hop
-        bgp_neighbor[PASSWORD] = password
-        bgp_neighbor[IS_ROUTE_SERVER_CLIENT] = is_route_server_client
-        bgp_neighbor[IS_NEXT_HOP_SELF] = is_next_hop_self
-        bgp_neighbor[CONNECT_MODE] = connect_mode
-        bgp_neighbor[CAP_ENHANCED_REFRESH] = enable_enhanced_refresh
+        bgp_neighbor = {
+            neighbors.IP_ADDRESS: address,
+            neighbors.REMOTE_AS: remote_as,
+            PEER_NEXT_HOP: next_hop,
+            PASSWORD: password,
+            IS_ROUTE_SERVER_CLIENT: is_route_server_client,
+            IS_NEXT_HOP_SELF: is_next_hop_self,
+            CONNECT_MODE: connect_mode,
+            CAP_ENHANCED_REFRESH: enable_enhanced_refresh,
+            CAP_FOUR_OCTET_AS_NUMBER: enable_four_octet_as_number,
+        }
         # v6 advertizement is available with only v6 peering
         if netaddr.valid_ipv4(address):
             bgp_neighbor[CAP_MBGP_IPV4] = enable_ipv4
diff --git a/ryu/services/protocols/bgp/peer.py 
b/ryu/services/protocols/bgp/peer.py
index 50c280b..89b7bd4 100644
--- a/ryu/services/protocols/bgp/peer.py
+++ b/ryu/services/protocols/bgp/peer.py
@@ -21,6 +21,8 @@ import socket
 import time
 import traceback
 
+from six.moves import zip_longest
+
 from ryu.services.protocols.bgp.base import Activity
 from ryu.services.protocols.bgp.base import Sink
 from ryu.services.protocols.bgp.base import Source
@@ -43,6 +45,7 @@ from ryu.services.protocols.bgp.rtconf.vrfs import 
VRF_RF_IPV4, VRF_RF_IPV6
 from ryu.services.protocols.bgp.utils import bgp as bgp_utils
 from ryu.services.protocols.bgp.utils.evtlet import EventletIOFactory
 from ryu.services.protocols.bgp.utils import stats
+from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn
 
 from ryu.lib.packet import bgp
 
@@ -69,6 +72,7 @@ from ryu.lib.packet.bgp import BGP_MSG_ROUTE_REFRESH
 
 from ryu.lib.packet.bgp import BGPPathAttributeNextHop
 from ryu.lib.packet.bgp import BGPPathAttributeAsPath
+from ryu.lib.packet.bgp import BGPPathAttributeAs4Path
 from ryu.lib.packet.bgp import BGPPathAttributeLocalPref
 from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities
 from ryu.lib.packet.bgp import BGPPathAttributeMpReachNLRI
@@ -77,7 +81,10 @@ from ryu.lib.packet.bgp import BGPPathAttributeCommunities
 from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc
 
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AGGREGATOR
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS4_AGGREGATOR
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS4_PATH
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_NEXT_HOP
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MP_REACH_NLRI
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MP_UNREACH_NLRI
@@ -395,6 +402,10 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         return self._neigh_conf.local_as
 
     @property
+    def cap_four_octet_as_number(self):
+        return self._neigh_conf.cap_four_octet_as_number
+
+    @property
     def in_filters(self):
         return self._in_filters
 
@@ -465,6 +476,11 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
             raise ValueError('Invalid request: Peer not in established state')
         return self._protocol.is_mbgp_cap_valid(route_family)
 
+    def is_four_octet_as_number_cap_valid(self):
+        if not self.in_established:
+            raise ValueError('Invalid request: Peer not in established state')
+        return self._protocol.is_four_octet_as_number_cap_valid()
+
     def is_ebgp_peer(self):
         """Returns *True* if this is a eBGP peer, else *False*."""
         return self._common_conf.local_as != self._neigh_conf.remote_as
@@ -820,6 +836,124 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         from netaddr import IPAddress
         return str(IPAddress(ipv4_address).ipv6())
 
+    def _construct_as_path_attr(self, as_path_attr, as4_path_attr):
+        """Marge AS_PATH and AS4_PATH attribute instances into
+        a single AS_PATH instance."""
+
+        def _listify(li):
+            """Reconstruct AS_PATH list.
+
+            Example::
+
+                >>> _listify([[1, 2, 3], {4, 5}, [6, 7]])
+                [1, 2, 3, {4, 5}, 6, 7]
+            """
+            lo = []
+            for l in li:
+                if isinstance(l, list):
+                    lo.extend(l)
+                elif isinstance(l, set):
+                    lo.append(l)
+                else:
+                    pass
+            return lo
+
+        # If AS4_PATH attribute is None, returns the given AS_PATH attribute
+        if as4_path_attr is None:
+            return as_path_attr
+
+        # If AS_PATH is shorter than AS4_PATH, AS4_PATH should be ignored.
+        if as_path_attr.get_as_path_len() < as4_path_attr.get_as_path_len():
+            return as_path_attr
+
+        org_as_path_list = _listify(as_path_attr.path_seg_list)
+        as4_path_list = _listify(as4_path_attr.path_seg_list)
+
+        # Reverse to compare backward.
+        org_as_path_list.reverse()
+        as4_path_list.reverse()
+
+        new_as_path_list = []
+        tmp_list = []
+        for as_path, as4_path in zip_longest(org_as_path_list, as4_path_list):
+            if as4_path is None:
+                if isinstance(as_path, int):
+                    tmp_list.insert(0, as_path)
+                elif isinstance(as_path, set):
+                    if tmp_list:
+                        new_as_path_list.insert(0, tmp_list)
+                        tmp_list = []
+                    new_as_path_list.insert(0, as_path)
+                else:
+                    pass
+            elif isinstance(as4_path, int):
+                tmp_list.insert(0, as4_path)
+            elif isinstance(as4_path, set):
+                if tmp_list:
+                    new_as_path_list.insert(0, tmp_list)
+                    tmp_list = []
+                new_as_path_list.insert(0, as4_path)
+            else:
+                pass
+        if tmp_list:
+            new_as_path_list.insert(0, tmp_list)
+
+        return bgp.BGPPathAttributeAsPath(new_as_path_list)
+
+    def _trans_as_path(self, as_path_list):
+        """Translates Four-Octet AS number to AS_TRANS and separates
+        AS_PATH list into AS_PATH and AS4_PATH lists if needed.
+
+        If the neighbor does not support Four-Octet AS number,
+        this method constructs AS4_PATH list from AS_PATH list and swaps
+        non-mappable AS number in AS_PATH with AS_TRANS, then
+        returns AS_PATH list and AS4_PATH list.
+        If the neighbor supports Four-Octet AS number, returns
+        the given AS_PATH list and None.
+        """
+
+        def _swap(n):
+            if is_valid_old_asn(n):
+                # mappable
+                return n
+            else:
+                # non-mappable
+                return bgp.AS_TRANS
+
+        # If the neighbor supports Four-Octet AS number, returns
+        # the given AS_PATH list and None.
+        if self.is_four_octet_as_number_cap_valid():
+            return as_path_list, None
+
+        # If the neighbor does not support Four-Octet AS number,
+        # constructs AS4_PATH list from AS_PATH list and swaps
+        # non-mappable AS number in AS_PATH with AS_TRANS.
+        else:
+            new_as_path_list = []
+            for as_path in as_path_list:
+                if isinstance(as_path, set):
+                    path_set = set()
+                    for as_num in as_path:
+                        path_set.add(_swap(as_num))
+                    new_as_path_list.append(path_set)
+                elif isinstance(as_path, list):
+                    path_list = list()
+                    for as_num in as_path:
+                        path_list.append(_swap(as_num))
+                    new_as_path_list.append(path_list)
+                else:
+                    # Ignore invalid as_path type
+                    pass
+
+            # If all of the AS_PATH list is composed of mappable four-octet
+            # AS numbers only, returns the given AS_PATH list
+            # Assumption: If the constructed AS_PATH list is the same as
+            # the given AS_PATH list, all AS number is mappable.
+            if as_path_list == new_as_path_list:
+                return as_path_list, None
+
+            return new_as_path_list, as_path_list
+
     def _construct_update(self, outgoing_route):
         """Construct update message with Outgoing-routes path attribute
         appropriately cloned/copied/updated.
@@ -847,14 +981,18 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
             # Supported and un-supported/unknown attributes.
             origin_attr = None
             nexthop_attr = None
-            aspath_attr = None
+            as_path_attr = None
+            as4_path_attr = None
+            aggregator_attr = None
+            as4_aggregator_attr = None
             extcomm_attr = None
             community_attr = None
             localpref_attr = None
             unknown_opttrans_attrs = None
             nlri_list = [path.nlri]
 
-            # By default we use BGPS's interface IP with this peer as next_hop.
+            # By default, we use BGPSpeaker's interface IP with this peer
+            # as next_hop.
             if self.is_ebgp_peer():
                 next_hop = self._session_next_hop(path)
                 if path.is_local() and path.has_nexthop():
@@ -887,18 +1025,18 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
             assert origin_attr, 'Missing ORIGIN mandatory attribute.'
 
             # AS_PATH Attribute.
-            # Construct AS-path-attr using paths aspath attr. with local AS as
+            # Construct AS-path-attr using paths AS_PATH attr. with local AS as
             # first item.
             path_aspath = pathattr_map.get(BGP_ATTR_TYPE_AS_PATH)
             assert path_aspath, 'Missing AS_PATH mandatory attribute.'
-            # Deep copy aspath_attr value
-            path_seg_list = path_aspath.path_seg_list
+            # Deep copy AS_PATH attr value
+            as_path_list = path_aspath.path_seg_list
             # If this is a iBGP peer.
             if not self.is_ebgp_peer():
                 # When a given BGP speaker advertises the route to an internal
                 # peer, the advertising speaker SHALL NOT modify the AS_PATH
                 # attribute associated with the route.
-                aspath_attr = BGPPathAttributeAsPath(path_seg_list)
+                pass
             else:
                 # When a given BGP speaker advertises the route to an external
                 # peer, the advertising speaker updates the AS_PATH attribute
@@ -920,13 +1058,42 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
                 # 3) if the AS_PATH is empty, the local system creates a path
                 #    segment of type AS_SEQUENCE, places its own AS into that
                 #    segment, and places that segment into the AS_PATH.
-                if (len(path_seg_list) > 0 and
-                        isinstance(path_seg_list[0], list) and
-                        len(path_seg_list[0]) < 255):
-                    path_seg_list[0].insert(0, self.local_as)
+                if (len(as_path_list) > 0 and
+                        isinstance(as_path_list[0], list) and
+                        len(as_path_list[0]) < 255):
+                    as_path_list[0].insert(0, self.local_as)
                 else:
-                    path_seg_list.insert(0, [self.local_as])
-                aspath_attr = BGPPathAttributeAsPath(path_seg_list)
+                    as_path_list.insert(0, [self.local_as])
+            # Construct AS4_PATH list from AS_PATH list and swap
+            # non-mappable AS number with AS_TRANS in AS_PATH.
+            as_path_list, as4_path_list = self._trans_as_path(
+                as_path_list)
+            # If the neighbor supports Four-Octet AS number, send AS_PATH
+            # in Four-Octet.
+            if self.is_four_octet_as_number_cap_valid():
+                as_path_attr = BGPPathAttributeAsPath(
+                    as_path_list, as_pack_str='!I')  # specify Four-Octet.
+            # Otherwise, send AS_PATH in Two-Octet.
+            else:
+                as_path_attr = BGPPathAttributeAsPath(as_path_list)
+            # If needed, send AS4_PATH attribute.
+            if as4_path_list:
+                as4_path_attr = BGPPathAttributeAs4Path(as4_path_list)
+
+            # AGGREGATOR Attribute.
+            aggregator_attr = pathattr_map.get(BGP_ATTR_TYPE_AGGREGATOR)
+            # If the neighbor does not support Four-Octet AS number,
+            # swap non-mappable AS number with AS_TRANS.
+            if (aggregator_attr and
+                    not self.is_four_octet_as_number_cap_valid()):
+                # If AS number of AGGREGATOR is Four-Octet AS number,
+                # swap with AS_TRANS, else do not.
+                aggregator_as_number = aggregator_attr.as_number
+                if not is_valid_old_asn(aggregator_as_number):
+                    aggregator_attr = bgp.BGPPathAttributeAggregator(
+                        bgp.AS_TRANS, aggregator_attr.addr)
+                    as4_aggregator_attr = bgp.BGPPathAttributeAs4Aggregator(
+                        aggregator_as_number, aggregator_attr.addr)
 
             # MULTI_EXIT_DISC Attribute.
             # For eBGP session we can send multi-exit-disc if configured.
@@ -1010,7 +1177,13 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
                 new_pathattr.append(mpnlri_attr)
 
             new_pathattr.append(origin_attr)
-            new_pathattr.append(aspath_attr)
+            new_pathattr.append(as_path_attr)
+            if as4_path_attr:
+                new_pathattr.append(as4_path_attr)
+            if aggregator_attr:
+                new_pathattr.append(aggregator_attr)
+            if as4_aggregator_attr:
+                new_pathattr.append(as4_aggregator_attr)
             if multi_exit_disc:
                 new_pathattr.append(multi_exit_disc)
             if localpref_attr:
@@ -1192,6 +1365,9 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         Current setting include capabilities, timers and ids.
         """
         asnum = self.local_as
+        # If local AS number is not Two-Octet AS number, swaps with AS_TRANS.
+        if not is_valid_old_asn(asnum):
+            asnum = bgp.AS_TRANS
         bgpid = self._common_conf.router_id
         holdtime = self._neigh_conf.hold_time
 
@@ -1329,8 +1505,10 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         # Increment count of update received.
         mp_reach_attr = update_msg.get_path_attr(BGP_ATTR_TYPE_MP_REACH_NLRI)
         mp_unreach_attr = update_msg.get_path_attr(
-            BGP_ATTR_TYPE_MP_UNREACH_NLRI
-        )
+            BGP_ATTR_TYPE_MP_UNREACH_NLRI)
+
+        # Extract advertised path attributes and reconstruct AS_PATH attribute
+        self._extract_and_reconstruct_as_path(update_msg)
 
         nlri_list = update_msg.nlri
         withdraw_list = update_msg.withdrawn_routes
@@ -1349,6 +1527,68 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
         if withdraw_list:
             self._extract_and_handle_bgp4_withdraws(withdraw_list)
 
+    def _extract_and_reconstruct_as_path(self, update_msg):
+        """Extracts advertised AS path attributes in the given update message
+        and reconstructs AS_PATH from AS_PATH and AS4_PATH if needed."""
+        umsg_pattrs = update_msg.pathattr_map
+
+        as_aggregator = umsg_pattrs.get(BGP_ATTR_TYPE_AGGREGATOR, None)
+        as4_aggregator = umsg_pattrs.get(BGP_ATTR_TYPE_AS4_AGGREGATOR, None)
+        if as_aggregator and as4_aggregator:
+            # When both AGGREGATOR and AS4_AGGREGATOR are received,
+            # if the AS number in the AGGREGATOR attribute is not AS_TRANS,
+            # then:
+            #  -  the AS4_AGGREGATOR attribute and the AS4_PATH attribute SHALL
+            #     be ignored,
+            #  -  the AGGREGATOR attribute SHALL be taken as the information
+            #     about the aggregating node, and
+            #  -  the AS_PATH attribute SHALL be taken as the AS path
+            #     information.
+            if as_aggregator.as_number != bgp.AS_TRANS:
+                update_msg.path_attributes.remove(as4_aggregator)
+                as4_path = umsg_pattrs.pop(BGP_ATTR_TYPE_AS4_PATH, None)
+                if as4_path:
+                    update_msg.path_attributes.remove(as4_path)
+            # Otherwise,
+            #  -  the AGGREGATOR attribute SHALL be ignored,
+            #  -  the AS4_AGGREGATOR attribute SHALL be taken as the
+            #     information about the aggregating node, and
+            #  -  the AS path information would need to be constructed,
+            #     as in all other cases.
+            else:
+                update_msg.path_attributes.remove(as_aggregator)
+                update_msg.path_attributes.remove(as4_aggregator)
+                update_msg.path_attributes.append(
+                    bgp.BGPPathAttributeAggregator(
+                        as_number=as4_aggregator.as_number,
+                        addr=as4_aggregator.addr,
+                    )
+                )
+
+        as_path = umsg_pattrs.get(BGP_ATTR_TYPE_AS_PATH, None)
+        as4_path = umsg_pattrs.get(BGP_ATTR_TYPE_AS4_PATH, None)
+        if as_path and as4_path:
+            # If the number of AS numbers in the AS_PATH attribute is
+            # less than the number of AS numbers in the AS4_PATH attribute,
+            # then the AS4_PATH attribute SHALL be ignored, and the AS_PATH
+            # attribute SHALL be taken as the AS path information.
+            if as_path.get_as_path_len() < as4_path.get_as_path_len():
+                update_msg.path_attributes.remove(as4_path)
+
+            # If the number of AS numbers in the AS_PATH attribute is larger
+            # than or equal to the number of AS numbers in the AS4_PATH
+            # attribute, then the AS path information SHALL be constructed
+            # by taking as many AS numbers and path segments as necessary
+            # from the leading part of the AS_PATH attribute, and then
+            # prepending them to the AS4_PATH attribute so that the AS path
+            # information has a number of AS numbers identical to that of
+            # the AS_PATH attribute.
+            else:
+                update_msg.path_attributes.remove(as_path)
+                update_msg.path_attributes.remove(as4_path)
+                as_path = self._construct_as_path_attr(as_path, as4_path)
+                update_msg.path_attributes.append(as_path)
+
     def _extract_and_handle_bgp4_new_paths(self, update_msg):
         """Extracts new paths advertised in the given update message's
          *MpReachNlri* attribute.
diff --git a/ryu/services/protocols/bgp/rtconf/base.py 
b/ryu/services/protocols/bgp/rtconf/base.py
index c61798d..8746b2d 100644
--- a/ryu/services/protocols/bgp/rtconf/base.py
+++ b/ryu/services/protocols/bgp/rtconf/base.py
@@ -30,7 +30,7 @@ from ryu.services.protocols.bgp.base import get_validator
 from ryu.services.protocols.bgp.base import RUNTIME_CONF_ERROR_CODE
 from ryu.services.protocols.bgp.base import validate
 from ryu.services.protocols.bgp.utils import validation
-from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn
+from ryu.services.protocols.bgp.utils.validation import is_valid_asn
 
 LOG = logging.getLogger('bgpspeaker.rtconf.base')
 
@@ -39,6 +39,7 @@ LOG = logging.getLogger('bgpspeaker.rtconf.base')
 #
 CAP_REFRESH = 'cap_refresh'
 CAP_ENHANCED_REFRESH = 'cap_enhanced_refresh'
+CAP_FOUR_OCTET_AS_NUMBER = 'cap_four_octet_as_number'
 CAP_MBGP_IPV4 = 'cap_mbgp_ipv4'
 CAP_MBGP_IPV6 = 'cap_mbgp_ipv6'
 CAP_MBGP_VPNV4 = 'cap_mbgp_vpnv4'
@@ -594,6 +595,14 @@ def validate_cap_enhanced_refresh(cer):
     return cer
 
 
+@validate(name=CAP_FOUR_OCTET_AS_NUMBER)
+def validate_cap_four_octet_as_number(cfoan):
+    if cfoan not in (True, False):
+        raise ConfigTypeError(desc='Invalid Four-Octet AS Number capability '
+                              'settings: %s boolean value expected' % cfoan)
+    return cfoan
+
+
 @validate(name=CAP_MBGP_IPV4)
 def validate_cap_mbgp_ipv4(cmv4):
     if cmv4 not in (True, False):
@@ -641,7 +650,7 @@ def validate_cap_rtc(cap_rtc):
 
 @validate(name=RTC_AS)
 def validate_cap_rtc_as(rtc_as):
-    if not is_valid_old_asn(rtc_as):
+    if not is_valid_asn(rtc_as):
         raise ConfigValueError(desc='Invalid RTC AS configuration value: %s'
                                % rtc_as)
     return rtc_as
diff --git a/ryu/services/protocols/bgp/rtconf/common.py 
b/ryu/services/protocols/bgp/rtconf/common.py
index d285bb6..acf4634 100644
--- a/ryu/services/protocols/bgp/rtconf/common.py
+++ b/ryu/services/protocols/bgp/rtconf/common.py
@@ -20,7 +20,7 @@ import logging
 import numbers
 
 from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4
-from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn
+from ryu.services.protocols.bgp.utils.validation import is_valid_asn
 
 from ryu.services.protocols.bgp import rtconf
 from ryu.services.protocols.bgp.rtconf.base import BaseConf
@@ -85,7 +85,7 @@ def validate_local_as(asn):
     if asn is None:
         raise MissingRequiredConf(conf_name=LOCAL_AS)
 
-    if not is_valid_old_asn(asn):
+    if not is_valid_asn(asn):
         raise ConfigValueError(desc='Invalid local_as configuration value: %s'
                                % asn)
     return asn
diff --git a/ryu/services/protocols/bgp/rtconf/neighbors.py 
b/ryu/services/protocols/bgp/rtconf/neighbors.py
index a12af81..bc27542 100644
--- a/ryu/services/protocols/bgp/rtconf/neighbors.py
+++ b/ryu/services/protocols/bgp/rtconf/neighbors.py
@@ -26,9 +26,11 @@ from ryu.lib.packet.bgp import RF_IPv6_UC
 from ryu.lib.packet.bgp import RF_IPv4_VPN
 from ryu.lib.packet.bgp import RF_IPv6_VPN
 from ryu.lib.packet.bgp import RF_RTC_UC
+from ryu.lib.packet.bgp import BGPOptParamCapabilityFourOctetAsNumber
 from ryu.lib.packet.bgp import BGPOptParamCapabilityEnhancedRouteRefresh
 from ryu.lib.packet.bgp import BGPOptParamCapabilityMultiprotocol
 from ryu.lib.packet.bgp import BGPOptParamCapabilityRouteRefresh
+from ryu.lib.packet.bgp import BGP_CAP_FOUR_OCTET_AS_NUMBER
 from ryu.lib.packet.bgp import BGP_CAP_ENHANCED_ROUTE_REFRESH
 from ryu.lib.packet.bgp import BGP_CAP_MULTIPROTOCOL
 from ryu.lib.packet.bgp import BGP_CAP_ROUTE_REFRESH
@@ -38,6 +40,7 @@ from ryu.services.protocols.bgp.rtconf.base import 
ADVERTISE_PEER_AS
 from ryu.services.protocols.bgp.rtconf.base import BaseConf
 from ryu.services.protocols.bgp.rtconf.base import BaseConfListener
 from ryu.services.protocols.bgp.rtconf.base import CAP_ENHANCED_REFRESH
+from ryu.services.protocols.bgp.rtconf.base import CAP_FOUR_OCTET_AS_NUMBER
 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4
 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV6
 from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4
@@ -60,7 +63,7 @@ from ryu.services.protocols.bgp.rtconf.base import 
SITE_OF_ORIGINS
 from ryu.services.protocols.bgp.rtconf.base import validate
 from ryu.services.protocols.bgp.rtconf.base import validate_med
 from ryu.services.protocols.bgp.rtconf.base import validate_soo_list
-from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn
+from ryu.services.protocols.bgp.utils.validation import is_valid_asn
 from ryu.services.protocols.bgp.info_base.base import Filter
 from ryu.services.protocols.bgp.info_base.base import PrefixFilter
 from ryu.services.protocols.bgp.info_base.base import AttributeMap
@@ -92,6 +95,7 @@ CONNECT_MODE_BOTH = 'both'
 DEFAULT_CAP_GR_NULL = True
 DEFAULT_CAP_REFRESH = True
 DEFAULT_CAP_ENHANCED_REFRESH = False
+DEFAULT_CAP_FOUR_OCTET_AS_NUMBER = True
 DEFAULT_CAP_MBGP_IPV4 = True
 DEFAULT_CAP_MBGP_IPV6 = False
 DEFAULT_CAP_MBGP_VPNV4 = False
@@ -181,7 +185,7 @@ def validate_local_port(port):
 
 @validate(name=REMOTE_AS)
 def validate_remote_as(asn):
-    if not is_valid_old_asn(asn):
+    if not is_valid_asn(asn):
         raise ConfigValueError(desc='Invalid remote as value %s' % asn)
     return asn
 
@@ -295,6 +299,7 @@ class NeighborConf(ConfWithId, ConfWithStats):
     REQUIRED_SETTINGS = frozenset([REMOTE_AS, IP_ADDRESS])
     OPTIONAL_SETTINGS = frozenset([CAP_REFRESH,
                                    CAP_ENHANCED_REFRESH,
+                                   CAP_FOUR_OCTET_AS_NUMBER,
                                    CAP_MBGP_IPV4, CAP_MBGP_IPV6,
                                    CAP_MBGP_VPNV4, CAP_MBGP_VPNV6,
                                    CAP_RTC, RTC_AS, HOLD_TIME,
@@ -314,6 +319,9 @@ class NeighborConf(ConfWithId, ConfWithStats):
             CAP_REFRESH, DEFAULT_CAP_REFRESH, **kwargs)
         self._settings[CAP_ENHANCED_REFRESH] = compute_optional_conf(
             CAP_ENHANCED_REFRESH, DEFAULT_CAP_ENHANCED_REFRESH, **kwargs)
+        self._settings[CAP_FOUR_OCTET_AS_NUMBER] = compute_optional_conf(
+            CAP_FOUR_OCTET_AS_NUMBER,
+            DEFAULT_CAP_FOUR_OCTET_AS_NUMBER, **kwargs)
         self._settings[CAP_MBGP_IPV4] = compute_optional_conf(
             CAP_MBGP_IPV4, DEFAULT_CAP_MBGP_IPV4, **kwargs)
         self._settings[CAP_MBGP_IPV6] = compute_optional_conf(
@@ -458,6 +466,17 @@ class NeighborConf(ConfWithId, ConfWithStats):
         return self._settings[CAP_ENHANCED_REFRESH]
 
     @property
+    def cap_four_octet_as_number(self):
+        return self._settings[CAP_FOUR_OCTET_AS_NUMBER]
+
+    @cap_four_octet_as_number.setter
+    def cap_four_octet_as_number(self, cap):
+        kwargs = {CAP_FOUR_OCTET_AS_NUMBER: cap}
+        self._settings[CAP_FOUR_OCTET_AS_NUMBER] = compute_optional_conf(
+            CAP_FOUR_OCTET_AS_NUMBER,
+            DEFAULT_CAP_FOUR_OCTET_AS_NUMBER, **kwargs)
+
+    @property
     def cap_mbgp_ipv4(self):
         return self._settings[CAP_MBGP_IPV4]
 
@@ -599,6 +618,10 @@ class NeighborConf(ConfWithId, ConfWithStats):
             capabilities[BGP_CAP_ENHANCED_ROUTE_REFRESH] = [
                 BGPOptParamCapabilityEnhancedRouteRefresh()]
 
+        if self.cap_four_octet_as_number:
+            capabilities[BGP_CAP_FOUR_OCTET_AS_NUMBER] = [
+                BGPOptParamCapabilityFourOctetAsNumber(self.local_as)]
+
         return capabilities
 
     def __repr__(self):
diff --git a/ryu/services/protocols/bgp/speaker.py 
b/ryu/services/protocols/bgp/speaker.py
index 31553b2..9c185da 100644
--- a/ryu/services/protocols/bgp/speaker.py
+++ b/ryu/services/protocols/bgp/speaker.py
@@ -24,6 +24,7 @@ from socket import IPPROTO_TCP, TCP_NODELAY
 from eventlet import semaphore
 
 from ryu.lib.packet import bgp
+from ryu.lib.packet.bgp import AS_TRANS
 from ryu.lib.packet.bgp import BGPMessage
 from ryu.lib.packet.bgp import BGPOpen
 from ryu.lib.packet.bgp import BGPUpdate
@@ -34,6 +35,7 @@ from ryu.lib.packet.bgp import BGP_MSG_UPDATE
 from ryu.lib.packet.bgp import BGP_MSG_KEEPALIVE
 from ryu.lib.packet.bgp import BGP_MSG_NOTIFICATION
 from ryu.lib.packet.bgp import BGP_MSG_ROUTE_REFRESH
+from ryu.lib.packet.bgp import BGP_CAP_FOUR_OCTET_AS_NUMBER
 from ryu.lib.packet.bgp import BGP_CAP_ENHANCED_ROUTE_REFRESH
 from ryu.lib.packet.bgp import BGP_CAP_MULTIPROTOCOL
 from ryu.lib.packet.bgp import BGP_ERROR_HOLD_TIMER_EXPIRED
@@ -49,7 +51,6 @@ from ryu.services.protocols.bgp.constants import 
BGP_FSM_OPEN_CONFIRM
 from ryu.services.protocols.bgp.constants import BGP_FSM_OPEN_SENT
 from ryu.services.protocols.bgp.constants import BGP_VERSION_NUM
 from ryu.services.protocols.bgp.protocol import Protocol
-from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn
 
 LOG = logging.getLogger('bgpspeaker.speaker')
 
@@ -99,7 +100,7 @@ class BgpProtocol(Protocol, Activity):
                                                      self._remotename,
                                                      self._localname))
         Activity.__init__(self, name=activity_name)
-        # Intialize instance variables.
+        # Initialize instance variables.
         self._peer = None
         self._recv_buff = b''
         self._socket = socket
@@ -120,6 +121,7 @@ class BgpProtocol(Protocol, Activity):
         self.sent_open_msg = None
         self.recv_open_msg = None
         self._is_bound = False
+        self.cap_four_octet_as_number = False
 
     @property
     def is_reactive(self):
@@ -247,12 +249,18 @@ class BgpProtocol(Protocol, Activity):
         return afs
 
     def is_mbgp_cap_valid(self, route_family):
-        """Returns true if both sides of this protocol have advertise
+        """Returns True if both sides of this protocol have advertise
         capability for this address family.
         """
         return (self.is_route_family_adv(route_family) and
                 self.is_route_family_adv_recv(route_family))
 
+    def is_four_octet_as_number_cap_valid(self):
+        """Returns True if both sides of this protocol have Four-Octet
+        AS number capability."""
+        return (self.cap_four_octet_as_number and
+                self._peer.cap_four_octet_as_number)
+
     def _run(self, peer):
         """Sends open message to peer and handles received messages.
 
@@ -405,11 +413,26 @@ class BgpProtocol(Protocol, Activity):
         either one of them we have to end session.
         """
         assert open_msg.type == BGP_MSG_OPEN
-        # Validate remote ASN.
-        remote_asnum = open_msg.my_as
-        # Since 4byte AS is not yet supported, we validate AS as old style AS.
-        if (not is_valid_old_asn(remote_asnum) or
-                remote_asnum != self._peer.remote_as):
+
+        opt_param_cap_map = open_msg.opt_param_cap_map
+
+        # Validate remote AS number.
+        remote_as = open_msg.my_as
+        # Try to get AS number from Four-Octet AS number capability.
+        cap4as = opt_param_cap_map.get(BGP_CAP_FOUR_OCTET_AS_NUMBER, None)
+        if cap4as is None:
+            if remote_as == AS_TRANS:
+                # Raise Bad Peer AS error message, if my_as is AS_TRANS
+                # and without Four-Octet AS number capability.
+                raise bgp.BadPeerAs()
+            self.cap_four_octet_as_number = False
+        else:
+            # Note: Even if the peer has Four-Octet AS number capability,
+            # keep the local capability setting
+            remote_as = cap4as.as_number
+            self.cap_four_octet_as_number = True
+        #  Validate remote AS number with local setting.
+        if remote_as != self._peer.remote_as:
             raise bgp.BadPeerAs()
 
         # Validate bgp version number.
@@ -426,7 +449,7 @@ class BgpProtocol(Protocol, Activity):
         LOG.debug('Received msg from %s << %s', self._remotename, msg)
 
         # If we receive open message we try to bind to protocol
-        if (msg.type == BGP_MSG_OPEN):
+        if msg.type == BGP_MSG_OPEN:
             if self.state == BGP_FSM_OPEN_SENT:
                 # Validate open message.
                 self._validate_open_msg(msg)
diff --git a/ryu/services/protocols/bgp/utils/validation.py 
b/ryu/services/protocols/bgp/utils/validation.py
index 659ea24..f0fb6e5 100644
--- a/ryu/services/protocols/bgp/utils/validation.py
+++ b/ryu/services/protocols/bgp/utils/validation.py
@@ -19,6 +19,8 @@
 import numbers
 import socket
 
+import six
+
 
 def is_valid_ipv4(ipv4):
     """Returns True if given is a valid ipv4 address.
@@ -115,17 +117,19 @@ def is_valid_ipv6_prefix(ipv6_prefix):
 
 
 def is_valid_old_asn(asn):
-    """Returns true if given asn is a 16 bit number.
+    """Returns True if the given AS number is Two Octet."""
+    if isinstance(asn, six.integer_types) and 0 <= asn <= 0xffff:
+        return True
+    else:
+        return False
 
-    Old AS numbers are 16 but unsigned number.
-    """
-    valid = True
-    # AS number should be a 16 bit number
-    if (not isinstance(asn, numbers.Integral) or (asn < 0) or
-            (asn > ((2 ** 16) - 1))):
-        valid = False
 
-    return valid
+def is_valid_asn(asn):
+    """Returns True if the given AS number is Two or Four Octet."""
+    if isinstance(asn, six.integer_types) and 0 <= asn <= 0xffffffff:
+        return True
+    else:
+        return False
 
 
 def is_valid_vpnv4_prefix(prefix):
-- 
2.7.4


------------------------------------------------------------------------------
Attend Shape: An AT&T Tech Expo July 15-16. Meet us at AT&T Park in San
Francisco, CA to explore cutting-edge tech and listen to tech luminaries
present their vision of the future. This family event has something for
everyone, including kids. Get more information and register today.
http://sdm.link/attshape
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to