diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-07-06 15:12:23 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-07-11 16:45:43 +0900 |
commit | 55d955f4841189d2ce683988d67cab3d5836fe5c (patch) | |
tree | 0b62a10c49ae6314fd8ee3ce9a34a95f83996e0b | |
parent | 2039347560967f175d54e53db4d1146cc42acbe6 (diff) |
BGPSpeaker: Support Four-Octet AS number
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/bgp.py | 41 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 31 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/peer.py | 270 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/base.py | 13 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/common.py | 4 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/neighbors.py | 27 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/speaker.py | 41 | ||||
-rw-r--r-- | 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 937fdeb5..68105a89 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 ce3eaedf..2ce6372a 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 50c280b3..89b7bd47 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 c61798dc..8746b2dd 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 d285bb6d..acf4634f 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 a12af816..bc27542d 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 31553b27..9c185daa 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 659ea248..f0fb6e57 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): |