diff --git a/ryu/services/protocols/bgp/protocols/bgp/__init__.py b/ryu/services/protocols/bgp/protocols/bgp/__init__.py deleted file mode 100644 index 99c53380..00000000 --- a/ryu/services/protocols/bgp/protocols/bgp/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict - -# Pointer to active/available OrderedDict. -OrderedDict = OrderedDict diff --git a/ryu/services/protocols/bgp/protocols/bgp/capabilities.py b/ryu/services/protocols/bgp/protocols/bgp/capabilities.py deleted file mode 100644 index ed2acaa9..00000000 --- a/ryu/services/protocols/bgp/protocols/bgp/capabilities.py +++ /dev/null @@ -1,280 +0,0 @@ -# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -This module provides BGP protocol capabilities classes and utility methods to -encode and decode them. -""" - -from abc import ABCMeta -from abc import abstractmethod -import logging -import struct - -from ryu.services.protocols.bgp.protocols.bgp.exceptions import \ - MalformedOptionalParam -from ryu.services.protocols.bgp.protocols.bgp.nlri import get_rf -from ryu.services.protocols.bgp.protocols.bgp.nlri import \ - RouteFamily as route_fmly - - -# Logger instance for this module -LOG = logging.getLogger('bgpspeaker.bgp.proto.capabilities') - -# Registry for bgp capability class by their code. -# : - : -_BGP_CAPABILITY_REGISTRY = {} - - -def _register_bgp_capabilities(cls): - """Utility decorator used to register bgp supported/recognized - capabilities. - - Capabilities classes are registered by their capability-code. - """ - assert issubclass(cls, Capability) - assert hasattr(cls, 'CODE') - assert _BGP_CAPABILITY_REGISTRY.get(cls.CODE) is None - _BGP_CAPABILITY_REGISTRY[cls.CODE] = cls - return cls - - -def is_recognized_cap_codes(cap_code): - return cap_code in _BGP_CAPABILITY_REGISTRY - - -def decode(byte_value): - """Decodes given `byte_value` into appropriate capabilities. - - Parameter: - - `byte_value`: (str) byte representation of one capability - advertisement - Returns: - - list of capabilities decoded from given bytes - Note: Different routers pack capability in one capability - advertisement/optional parameter or group them into several capability - advertisements. Hence we return a list of one or more decoded - capabilities. - """ - idx = 0 - total_len = len(byte_value) - caps = [] - # Parse one of more capabilities packed inside given capability- - # advertisement payload - while idx < total_len: - cap_code, clen = struct.unpack_from('BB', byte_value, idx) - idx += 2 - cap = byte_value[idx:idx + clen] - idx += clen - - cap_cls = _BGP_CAPABILITY_REGISTRY.get(cap_code) - if cap_cls: - cap = cap_cls.from_bytes(cap) - caps.append(cap) - else: - # RFC 5492 says: If a BGP speaker receives from its peer a - # capability that it does not itself support or recognize, it MUST - # ignore that capability. In particular, the Unsupported - # Capability NOTIFICATION message MUST NOT be generated and the BGP - # session MUST NOT be terminated in response to reception of a - # capability that is not supported by the local speaker. - cap = UnSupportedCap(cap_code, cap) - - return caps - - -class Capability(object): - """Super class of all bgp capability optional parameters. - """ - __metaclass__ = ABCMeta - CODE = -1 - NAME = 'abstract-cap' - - @abstractmethod - def packvalue(self): - """Encode this bgp capability.""" - raise NotImplementedError() - - def encode(self): - """Encodes this bgp capability with header and body.""" - body = self.packvalue() - return struct.pack('BB', self.__class__.CODE, len(body)) + body - - def __repr__(self): - return '<%s>' % self.__class__.NAME - - -class UnSupportedCap(Capability): - """Represents unknown capability. - - According to RFC 5492 it is recommended to that we do not sent NOTIFICATION - message for "Unsupported Capability". - """ - NAME = 'unsupported-cap' - - def __init__(self, code, value): - self.CODE = code - self._value = value - - def packvalue(self): - return self._value - - def __repr__(self): - return '' % self.CODE - - -@_register_bgp_capabilities -class MultiprotocolExtentionCap(Capability): - """This class represents bgp multi-protocol extension capability. - """ - CODE = 1 - NAME = 'mbgp' - - def __init__(self, route_family): - if not route_fmly.is_valid(route_family): - raise ValueError('Invalid argument %s' % route_family) - - Capability.__init__(self) - self.route_family = route_family - - def packvalue(self): - return struct.pack('!HH', self.route_family.afi, - self.route_family.safi) - - @classmethod - def from_bytes(cls, value): - afi, _, safi = struct.unpack_from('!HBB', value) - return cls(get_rf(afi, safi)) - - def __repr__(self): - return ('' % - (self.route_family.afi, self.route_family.safi)) - - def __eq__(self, other): - if (other.__class__.CODE == self.__class__.CODE and - other.route_family.afi == self.route_family.afi and - other.route_family.safi == self.route_family.safi): - return True - return False - - -class ZeroLengthCap(Capability): - """This is a super class represent all bgp capability with zero length.""" - CODE = -1 - NAME = 'zero-length' - - def packvalue(self): - return '' - - @classmethod - def from_bytes(cls, value): - if len(value) > 0: - LOG.error('Zero length capability has non-zero length value!') - raise MalformedOptionalParam() - return cls.get_singleton() - - @staticmethod - def get_singleton(): - raise NotImplementedError() - - -@_register_bgp_capabilities -class RouteRefreshCap(ZeroLengthCap): - CODE = 2 - NAME = 'route-refresh' - - def __str__(self): - return RouteRefreshCap.NAME - - @staticmethod - def get_singleton(): - return _ROUTE_REFRESH_CAP - - -@_register_bgp_capabilities -class OldRouteRefreshCap(ZeroLengthCap): - CODE = 128 - NAME = 'old-route-refresh' - - def __str__(self): - return OldRouteRefreshCap.NAME - - @staticmethod - def get_singleton(): - return _OLD_ROUTE_REFRESH_CAP - - -# Since four byte as capability is not fully supported, we do not register it -# as supported/recognized capability. -@_register_bgp_capabilities -class GracefulRestartCap(Capability): - CODE = 64 - NAME = 'graceful-restart' - - def __init__(self, value): - # TODO(PH): Provide implementation - Capability.__init__(self) - self.value = value - - def packvalue(self): - # TODO(PH): Provide implementation - return self.value - - @classmethod - def from_bytes(cls, value): - return cls(value) - - -# Since four byte as capability is not fully supported, we do not register it -# as supported/recognized capability. -@_register_bgp_capabilities -class FourByteAsCap(Capability): - CODE = 65 - NAME = '4byteas' - - def __init__(self, four_byte_as): - Capability.__init__(self) - self.four_byte_as = four_byte_as - - def packvalue(self): - return struct.pack('!I', self.four_byte_as) - - @classmethod - def from_bytes(cls, value): - value, = struct.unpack('!I', value) - return cls(value) - - def __repr__(self): - return '' % self.four_byte_as - - def __eq__(self, other): - if (other and other.four_byte_as == self.four_byte_as): - return True - return False - - -@_register_bgp_capabilities -class EnhancedRouteRefreshCap(ZeroLengthCap): - CODE = 70 - NAME = 'enhanced-refresh' - - @staticmethod - def get_singleton(): - return _ENHANCED_ROUTE_REFRESH_CAP - -# Zero length capability singletons -_ROUTE_REFRESH_CAP = RouteRefreshCap() -_ENHANCED_ROUTE_REFRESH_CAP = EnhancedRouteRefreshCap() -_OLD_ROUTE_REFRESH_CAP = OldRouteRefreshCap() diff --git a/ryu/services/protocols/bgp/protocols/bgp/exceptions.py b/ryu/services/protocols/bgp/protocols/bgp/exceptions.py deleted file mode 100644 index dba5ed6e..00000000 --- a/ryu/services/protocols/bgp/protocols/bgp/exceptions.py +++ /dev/null @@ -1,349 +0,0 @@ -import struct - - -class BgpExc(Exception): - """Base bgp exception.""" - - CODE = 0 - """BGP error code.""" - - SUB_CODE = 0 - """BGP error sub-code.""" - - SEND_ERROR = True - """Flag if set indicates Notification message should be sent to peer.""" - - def __init__(self, data=''): - self.data = data - - def __str__(self): - return '<%s %r>' % (self.__class__.__name__, self.data) - - -class BadNotification(BgpExc): - SEND_ERROR = False - -#============================================================================= -# Message Header Errors -#============================================================================= - - -class NotSync(BgpExc): - CODE = 1 - SUB_CODE = 1 - - -class BadLen(BgpExc): - CODE = 1 - SUB_CODE = 2 - - def __init__(self, msg_type_code, message_length): - self.msg_type_code = msg_type_code - self.length = message_length - self.data = struct.pack('!H', self.length) - - def __str__(self): - return '' % (self.length, self.msg_type_code) - - -class BadMsg(BgpExc): - """Error to indicate un-recognized message type. - - RFC says: If the Type field of the message header is not recognized, then - the Error Subcode MUST be set to Bad Message Type. The Data field MUST - contain the erroneous Type field. - """ - CODE = 1 - SUB_CODE = 3 - - def __init__(self, msg_type): - self.msg_type = msg_type - self.data = struct.pack('B', msg_type) - - def __str__(self): - return '' % (self.msg,) - -#============================================================================= -# OPEN Message Errors -#============================================================================= - - -class MalformedOptionalParam(BgpExc): - """If recognized optional parameters are malformed. - - RFC says: If one of the Optional Parameters in the OPEN message is - recognized, but is malformed, then the Error Subcode MUST be set to 0 - (Unspecific). - """ - CODE = 2 - SUB_CODE = 0 - - -class UnsupportedVersion(BgpExc): - """Error to indicate unsupport bgp version number. - - RFC says: If the version number in the Version field of the received OPEN - message is not supported, then the Error Subcode MUST be set to Unsupported - Version Number. The Data field is a 2-octet unsigned integer, which - indicates the largest, locally-supported version number less than the - version the remote BGP peer bid (as indicated in the received OPEN - message), or if the smallest, locally-supported version number is greater - than the version the remote BGP peer bid, then the smallest, locally- - supported version number. - """ - CODE = 2 - SUB_CODE = 1 - - def __init__(self, locally_support_version): - self.data = struct.pack('H', locally_support_version) - - -class BadPeerAs(BgpExc): - """Error to indicate open message has incorrect AS number. - - RFC says: If the Autonomous System field of the OPEN message is - unacceptable, then the Error Subcode MUST be set to Bad Peer AS. The - determination of acceptable Autonomous System numbers is configure peer AS. - """ - CODE = 2 - SUB_CODE = 2 - - -class BadBgpId(BgpExc): - """Error to indicate incorrect BGP Identifier. - - RFC says: If the BGP Identifier field of the OPEN message is syntactically - incorrect, then the Error Subcode MUST be set to Bad BGP Identifier. - Syntactic correctness means that the BGP Identifier field represents a - valid unicast IP host address. - """ - CODE = 2 - SUB_CODE = 3 - - -class UnsupportedOptParam(BgpExc): - """Error to indicate unsupported optional parameters. - - RFC says: If one of the Optional Parameters in the OPEN message is not - recognized, then the Error Subcode MUST be set to Unsupported Optional - Parameters. - """ - CODE = 2 - SUB_CODE = 4 - - -class AuthFailure(BgpExc): - CODE = 2 - SUB_CODE = 5 - - -class UnacceptableHoldTime(BgpExc): - """Error to indicate Unacceptable Hold Time in open message. - - RFC says: If the Hold Time field of the OPEN message is unacceptable, then - the Error Subcode MUST be set to Unacceptable Hold Time. - """ - CODE = 2 - SUB_CODE = 6 - -#============================================================================= -# UPDATE message related errors -#============================================================================= - - -class MalformedAttrList(BgpExc): - """Error to indicate UPDATE message is malformed. - - RFC says: Error checking of an UPDATE message begins by examining the path - attributes. If the Withdrawn Routes Length or Total Attribute Length is - too large (i.e., if Withdrawn Routes Length + Total Attribute Length + 23 - exceeds the message Length), then the Error Subcode MUST be set to - Malformed Attribute List. - """ - CODE = 3 - SUB_CODE = 1 - - -class UnRegWellKnowAttr(BgpExc): - CODE = 3 - SUB_CODE = 2 - - -class MissingWellKnown(BgpExc): - """Error to indicate missing well-known attribute. - - RFC says: If any of the well-known mandatory attributes are not present, - then the Error Subcode MUST be set to Missing Well-known Attribute. The - Data field MUST contain the Attribute Type Code of the missing, well-known - attribute. - """ - CODE = 3 - SUB_CODE = 3 - - def __init__(self, pattr_type_code): - self.pattr_type_code = pattr_type_code - self.data = struct.pack('B', pattr_type_code) - - -class AttrFlagError(BgpExc): - """Error to indicate recognized path attributes have incorrect flags. - - RFC says: If any recognized attribute has Attribute Flags that conflict - with the Attribute Type Code, then the Error Subcode MUST be set to - Attribute Flags Error. The Data field MUST contain the erroneous attribute - (type, length, and value). - """ - CODE = 3 - SUB_CODE = 4 - - -class AttrLenError(BgpExc): - CODE = 3 - SUB_CODE = 5 - - -class InvalidOriginError(BgpExc): - """Error indicates undefined Origin attribute value. - - RFC says: If the ORIGIN attribute has an undefined value, then the Error - Sub- code MUST be set to Invalid Origin Attribute. The Data field MUST - contain the unrecognized attribute (type, length, and value). - """ - CODE = 3 - SUB_CODE = 6 - - -class RoutingLoop(BgpExc): - CODE = 3 - SUB_CODE = 7 - - -class InvalidNextHop(BgpExc): - CODE = 3 - SUB_CODE = 8 - - -class OptAttrError(BgpExc): - """Error indicates Optional Attribute is malformed. - - RFC says: If an optional attribute is recognized, then the value of this - attribute MUST be checked. If an error is detected, the attribute MUST be - discarded, and the Error Subcode MUST be set to Optional Attribute Error. - The Data field MUST contain the attribute (type, length, and value). - """ - CODE = 3 - SUB_CODE = 9 - - -class InvalidNetworkField(BgpExc): - CODE = 3 - SUB_CODE = 10 - - -class MalformedAsPath(BgpExc): - """Error to indicate if AP_PATH attribute is syntactically incorrect. - - RFC says: The AS_PATH attribute is checked for syntactic correctness. If - the path is syntactically incorrect, then the Error Subcode MUST be set to - Malformed AS_PATH. - """ - CODE = 3 - SUB_CODE = 11 - - -#============================================================================= -# Hold Timer Expired -#============================================================================= - - -class HoldTimerExpired(BgpExc): - """Error to indicate Hold Timer expired. - - RFC says: If a system does not receive successive KEEPALIVE, UPDATE, and/or - NOTIFICATION messages within the period specified in the Hold Time field of - the OPEN message, then the NOTIFICATION message with the Hold Timer Expired - Error Code is sent and the BGP connection is closed. - """ - CODE = 4 - SUB_CODE = 1 - -#============================================================================= -# Finite State Machine Error -#============================================================================= - - -class FiniteStateMachineError(BgpExc): - """Error to indicate any Finite State Machine Error. - - RFC says: Any error detected by the BGP Finite State Machine (e.g., receipt - of an unexpected event) is indicated by sending the NOTIFICATION message - with the Error Code Finite State Machine Error. - """ - CODE = 5 - SUB_CODE = 1 - - -#============================================================================= -# Cease Errors -#============================================================================= - -class MaxPrefixReached(BgpExc): - CODE = 6 - SUB_CODE = 1 - - -class AdminShutdown(BgpExc): - """Error to indicate Administrative shutdown. - - RFC says: If a BGP speaker decides to administratively shut down its - peering with a neighbor, then the speaker SHOULD send a NOTIFICATION - message with the Error Code Cease and the Error Subcode 'Administrative - Shutdown'. - """ - CODE = 6 - SUB_CODE = 2 - - -class PeerDeConfig(BgpExc): - CODE = 6 - SUB_CODE = 3 - - -class AdminReset(BgpExc): - CODE = 6 - SUB_CODE = 4 - - -class ConnRejected(BgpExc): - """Error to indicate Connection Rejected. - - RFC says: If a BGP speaker decides to disallow a BGP connection (e.g., the - peer is not configured locally) after the speaker accepts a transport - protocol connection, then the BGP speaker SHOULD send a NOTIFICATION - message with the Error Code Cease and the Error Subcode "Connection - Rejected". - """ - CODE = 6 - SUB_CODE = 5 - - -class OtherConfChange(BgpExc): - CODE = 6 - SUB_CODE = 6 - - -class CollisionResolution(BgpExc): - """Error to indicate Connection Collision Resolution. - - RFC says: If a BGP speaker decides to send a NOTIFICATION message with the - Error Code Cease as a result of the collision resolution procedure (as - described in [BGP-4]), then the subcode SHOULD be set to "Connection - Collision Resolution". - """ - CODE = 6 - SUB_CODE = 7 - - -class OutOfResource(BgpExc): - CODE = 6 - SUB_CODE = 8 diff --git a/ryu/services/protocols/bgp/protocols/bgp/messages.py b/ryu/services/protocols/bgp/protocols/bgp/messages.py deleted file mode 100644 index 6d74b1c6..00000000 --- a/ryu/services/protocols/bgp/protocols/bgp/messages.py +++ /dev/null @@ -1,536 +0,0 @@ -# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -This module provides BGP protocol message classes and utility methods to encode -and decode them. - -This file is adapted from pybgp open source project. -""" -from abc import ABCMeta -from abc import abstractmethod -from copy import copy -import cStringIO -import logging -import socket -import struct - -from ryu.services.protocols.bgp.protocols.bgp import capabilities -from ryu.services.protocols.bgp.protocols.bgp.exceptions import BadBgpId -from ryu.services.protocols.bgp.protocols.bgp.exceptions import BadLen -from ryu.services.protocols.bgp.protocols.bgp.exceptions import BadMsg -from ryu.services.protocols.bgp.protocols.bgp.exceptions import BadNotification -from ryu.services.protocols.bgp.protocols.bgp.exceptions import \ - MalformedAttrList -from ryu.services.protocols.bgp.protocols.bgp.exceptions import \ - UnacceptableHoldTime -from ryu.services.protocols.bgp.protocols.bgp import nlri -from ryu.services.protocols.bgp.protocols.bgp.nlri import get_rf -from ryu.services.protocols.bgp.protocols.bgp import OrderedDict -from ryu.services.protocols.bgp.protocols.bgp import pathattr -from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4 -from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn - - -LOG = logging.getLogger('bgpspeaker.bgp.proto.messages') - -# BGP capability optional parameter type -CAP_OPT_PARA_TYPE = 2 - -# Registry for bgp message class by their type code. -# : - : -_BGP_MESSAGE_REGISTRY = {} - - -def _register_bgp_message(cls): - """Used as class decorator for registering bgp message class by their - type-code. - """ - assert _BGP_MESSAGE_REGISTRY.get(cls.TYPE_CODE) is None - assert hasattr(cls, 'from_bytes') - _BGP_MESSAGE_REGISTRY[cls.TYPE_CODE] = cls - return cls - - -class BgpMessage(object): - """Super class of all bgp messages. - """ - __metaclass__ = ABCMeta - TYPE_CODE = 0 - MSG_NAME = 'abstract-msg' - HEADER_SIZE = 19 - - @abstractmethod - def packvalue(self): - """Encodes the body of this bgp message.""" - raise NotImplementedError() - - def encode(self): - """Encodes this bgp message with header and body.""" - body = self.packvalue() - return struct.pack('!16sHB', '\xff' * 16, 19 + len(body), - self.__class__.TYPE_CODE) + body - - -class RecognizedBgpMessage(BgpMessage): - """Represents recognized/supported bgp message. - - Declares a factory method to create an instance from bytes. - """ - @classmethod - def from_bytes(cls, recv_bytes, total_msg_length): - raise NotImplementedError() - - -@_register_bgp_message -class Open(RecognizedBgpMessage): - """Represents bgp OPEN message. - - This is the first message sent by each peer after TCP connection is - established. - """ - MSG_NAME = 'open' - TYPE_CODE = 1 - MIN_LENGTH = 29 - - def __init__(self, version, asnum, holdtime, bgpid, caps, - unrec_params=None): - # Validate arguments. - if version < 1: - raise ValueError('Invalid version number %s' % version) - if not is_valid_old_asn(asnum): - raise ValueError('Invalid AS number %s' % asnum) - if holdtime <= 2: - raise ValueError('Holdtime has to be greater than 2 sec.') - if not caps: - raise ValueError('Invalid capabilities.') - if not is_valid_ipv4(bgpid): - raise ValueError('Invalid bgp ID, should be valid IPv4, ' - 'but given %s' % bgpid) - - BgpMessage.__init__(self) - self._version = version - self._holdtime = holdtime - self._asnum = asnum - self._bgpid = bgpid - self._caps = caps - self._unrec_params = unrec_params - if not unrec_params: - self._unrec_params = OrderedDict() - - @property - def version(self): - return self._version - - @property - def holdtime(self): - return self._holdtime - - @property - def asnum(self): - return self._asnum - - @property - def bgpid(self): - return self._bgpid - - @property - def caps(self): - return copy(self._caps) - - @property - def unrec_params(self): - return copy(self._unrec_params) - - @classmethod - def from_bytes(cls, recv_bytes, total_msg_len): - # Validate OPEN message length. - if len(recv_bytes) < 10: - raise BadLen(Open.TYPE_CODE, len(recv_bytes) + cls.HEADER_SIZE) - - version, asnum, holdtime, bgpid, paramlen = \ - struct.unpack_from('!BHH4sB', recv_bytes) - - if len(recv_bytes) != (10 + paramlen): - # TODO(PH): Check what RFC says to do here. - LOG.debug('Open message: too short.') - - offset = 10 - - # BGP implementation MUST reject Hold Time values of one or two - # seconds. - if holdtime <= 2: - raise UnacceptableHoldTime() - - # BGP Identifier field MUST represents a valid unicast IP host address. - bgpid = socket.inet_ntoa(bgpid) - if not is_valid_ipv4(bgpid): - raise BadBgpId() - - # Parse optional parameters. - caps = OrderedDict() - unrec_params = OrderedDict() - while offset < len(recv_bytes): - ptype, plen = struct.unpack_from('BB', recv_bytes, offset) - offset += 2 - value = recv_bytes[offset:offset + plen] - offset += plen - - # Parse capabilities optional parameter. - if ptype == CAP_OPT_PARA_TYPE: - bgp_caps = capabilities.decode(value) - # store decoded bgp capabilities by their capability-code - for cap in bgp_caps: - cap_code = cap.CODE - if cap_code in caps: - caps[cap_code].append(cap) - else: - caps[cap_code] = [cap] - else: - # Other unrecognized optional parameters. - unrec_params[ptype] = value - - # Un-recognized capabilities are passed on, its up to application to - # check if unrec-optional-paramters are a problem and send NOTIFICATION - return cls(version, asnum, holdtime, bgpid, caps, unrec_params) - - def packvalue(self): - params = cStringIO.StringIO() - # Capabilities optional parameters. - for capability in self.caps.itervalues(): - for cap in capability: - encoded_cap = cStringIO.StringIO() - encoded_cap.write(cap.encode()) - encoded_cap_value = encoded_cap.getvalue() - encoded_cap.close() - params.write(struct.pack('BB', - CAP_OPT_PARA_TYPE, - len(encoded_cap_value))) - params.write(encoded_cap_value) - - # Other optional parameters. - for ptype, pvalue in self.unrec_params.items(): - params.write(struct.pack('BB', ptype, len(pvalue))) - params.write(pvalue) - - bgpid = socket.inet_aton(self.bgpid) - params_value = params.getvalue() - params.close() - return struct.pack('!BHH4sB', self.version, self.asnum, self.holdtime, - bgpid, len(params_value)) + params_value - - def __str__(self): - str_rep = cStringIO.StringIO() - str_rep.write('Open message Ver=%s As#=%s Hold Time=%s Bgp Id=%s' % - (self.version, self.asnum, self.holdtime, self.bgpid)) - for param, value in self.unrec_params.items(): - str_rep.write(' unrec_param %s=%r' % (param, value)) - for cap, value in self.caps.items(): - str_rep.write(' cap %s=%r' % (cap, value)) - return str_rep.getvalue() - - -@_register_bgp_message -class Keepalive(BgpMessage): - MSG_NAME = 'keepalive' - TYPE_CODE = 4 - - @classmethod - def from_bytes(cls, recv_bytes, total_msg_len): - # Validate KeepAlive msg. length - if len(recv_bytes): - LOG.info("Received keepalive msg. with data! %r" % (recv_bytes,)) - raise BadLen( - Keepalive.TYPE_CODE, - len(recv_bytes) + cls.HEADER_SIZE - ) - - self = cls() - return self - - def packvalue(self): - return '' - - def __str__(self): - return 'Keepalive message' - - -@_register_bgp_message -class RouteRefresh(BgpMessage): - MSG_NAME = 'route-refresh' - TYPE_CODE = 5 - - def __init__(self, route_family, demarcation=0): - BgpMessage.__init__(self) - self._route_family = route_family - self._demarcation = demarcation - self.eor_sent = False - - @property - def route_family(self): - return self._route_family - - @property - def demarcation(self): - return self._demarcation - - @classmethod - def from_bytes(cls, recv_bytes, total_msg_len): - # Validate length of RouteRefresh message. - if len(recv_bytes) != 4: - raise BadLen( - RouteRefresh.TYPE_CODE, - len(recv_bytes) + cls.HEADER_SIZE - ) - - afi, reserved, safi = struct.unpack_from('!HBB', recv_bytes) - route_family = get_rf(afi, safi) - return cls(route_family, reserved) - - def packvalue(self): - return struct.pack('!HBB', self.route_family.afi, self.demarcation, - self._route_family.safi) - - def __str__(self): - return 'Route-refresh message (%s, %s)' % \ - (self.route_family, self.demarcation) - - -@_register_bgp_message -class Notification(BgpMessage): - MSG_NAME = 'notification' - TYPE_CODE = 3 - REASONS = { - (1, 1): 'Message Header Error: not synchronised', - (1, 2): 'Message Header Error: bad message len', - (1, 3): 'Message Header Error: bad message type', - (2, 1): 'Open Message Error: unsupported version', - (2, 2): 'Open Message Error: bad peer AS', - (2, 3): 'Open Message Error: bad BGP identifier', - (2, 4): 'Open Message Error: unsupported optional param', - (2, 5): 'Open Message Error: authentication failure', - (2, 6): 'Open Message Error: unacceptable hold time', - (2, 7): 'Open Message Error: Unsupported Capability', - (2, 8): 'Open Message Error: Unassigned', - (3, 1): 'Update Message Error: malformed attribute list', - (3, 2): 'Update Message Error: unrecognized well-known attr', - (3, 3): 'Update Message Error: missing well-known attr', - (3, 4): 'Update Message Error: attribute flags error', - (3, 5): 'Update Message Error: attribute length error', - (3, 6): 'Update Message Error: invalid origin attr', - (3, 7): 'Update Message Error: as routing loop', - (3, 8): 'Update Message Error: invalid next hop attr', - (3, 9): 'Update Message Error: optional attribute error', - (3, 10): 'Update Message Error: invalid network field', - (3, 11): 'Update Message Error: malformed AS_PATH', - (4, 1): 'Hold Timer Expired', - (5, 1): 'Finite State Machine Error', - (6, 1): 'Cease: Maximum Number of Prefixes Reached', - (6, 2): 'Cease: Administrative Shutdown', - (6, 3): 'Cease: Peer De-configured', - (6, 4): 'Cease: Administrative Reset', - (6, 5): 'Cease: Connection Rejected', - (6, 6): 'Cease: Other Configuration Change', - (6, 7): 'Cease: Connection Collision Resolution', - (6, 8): 'Cease: Out of Resources', - } - - def __init__(self, code, subcode, data=''): - BgpMessage.__init__(self) - self._code = code - self._subcode = subcode - self._data = data - - @property - def code(self): - return self._code - - @property - def subcode(self): - return self._subcode - - @property - def data(self): - return self._data - - @classmethod - def from_bytes(cls, recv_bytes, total_msg_len): - # Validate NOTIFICATION msg. length. - if len(recv_bytes) < 2: - LOG.error('Received NOTIFICATION msg. with bad length %s' % - (len(recv_bytes) + cls.HEADER_SIZE)) - raise BadNotification() - - code, subcode = struct.unpack_from('BB', recv_bytes) - data = recv_bytes[2:] - - # Check code or sub-code are recognized. - if not Notification.REASONS.get((code, subcode)): - LOG.error('Received notification msg. with unrecognized Error ' - 'code or Sub-code (%s, %s)' % (code, subcode)) - raise BadNotification() - - return cls(code, subcode, data) - - def __str__(self): - c, s = self.code, self.subcode - if (c, s) in self.REASONS: - return ('Notification "%s" params %r' % - (self.REASONS[c, s], self.data)) - return ('Notification message code=%d subcode=%d params=%r' % - (self.code, self.subcode, self.data)) - - def packvalue(self): - v = struct.pack('BB', self.code, self.subcode) - if self.data: - v += self.data - return v - - -@_register_bgp_message -class Update(BgpMessage): - MSG_NAME = 'update' - TYPE_CODE = 2 - WITHDRAW_NLRI = 'withdraw_nlri' - PATH_ATTR_AND_NLRI = 'path_attr_and_nlri' - MIN_LENGTH = 23 - - def __init__(self, pathattr_map=None, nlri_list=None, withdraw_list=None): - """Initailizes a new `Update` instance. - - Parameter: - - `pathattr_map`: (OrderedDict) key -> attribute name, - value -> attribute. - - `nlri_list`: (list/iterable) NLRIs. - - `withdraw_list`: (list/iterable) Withdraw routes. - """ - if nlri_list is None: - nlri_list = [] - if withdraw_list is None: - withdraw_list = [] - if not pathattr_map: - pathattr_map = OrderedDict() - - self._nlri_list = list(nlri_list) - self._withdraw_list = list(withdraw_list) - self._pathattr_map = copy(pathattr_map) - - @property - def nlri_list(self): - return self._nlri_list[:] - - @property - def withdraw_list(self): - return self._withdraw_list[:] - - @property - def pathattr_map(self): - return copy(self._pathattr_map) - - def get_path_attr(self, attr_name): - return self._pathattr_map.get(attr_name) - - @classmethod - def from_bytes(cls, recv_bytes, total_msg_len): - # Validate UPDATE message length - if len(recv_bytes) < 4: - raise BadLen(Update.TYPE_CODE, len(recv_bytes) + cls.HEADER_SIZE) - withdraw_list = None - nlri_list = None - pathattr_map = OrderedDict() - - d = {} - idx = 0 - # Compute withdraw route length + total attribute length. - recv_len = 0 - for kind in (Update.WITHDRAW_NLRI, Update.PATH_ATTR_AND_NLRI): - plen, = struct.unpack_from('!H', recv_bytes, idx) - idx += 2 - d[kind] = recv_bytes[idx: (idx + plen)] - idx += plen - recv_len += plen - - if d[Update.WITHDRAW_NLRI]: - withdraw_list = nlri.parse(d[Update.WITHDRAW_NLRI]) - # TODO(PH): We have to test how ipv4 nlri packed after path-attr are - # getting parsed. - nlri_list = nlri.parse(recv_bytes[idx:]) - - idx = 0 - recv_bytes = d[Update.PATH_ATTR_AND_NLRI] - while idx < len(recv_bytes): - used, pattr = pathattr.decode(recv_bytes, idx) - # TODO(PH) Can optimize here by checking if path attribute is - # MpReachNlri and stop parsing if RT are not interesting. - idx += used - pathattr_map[pattr.ATTR_NAME] = pattr - - return cls(pathattr_map=pathattr_map, - nlri_list=nlri_list, withdraw_list=withdraw_list) - - def __repr__(self): - str_rep = cStringIO.StringIO() - str_rep.write('') - - return str_rep.getvalue() - - def __cmp__(self, other): - if isinstance(other, Update): - return cmp( - (self._pathattr_map, self._withdraw_list, self._nlri_list), - (other.pathattr_map, other.withdraw_list, other.nlri_list), - ) - return -1 - - def packvalue(self): - bvalue = '' - - bwithdraw = '' - for awithdraw in self._withdraw_list: - bwithdraw += awithdraw.encode() - - bvalue += struct.pack('!H', len(bwithdraw)) - bvalue += bwithdraw - - pattr = '' - for _, attr in self._pathattr_map.items(): - if attr is not None: - pattr += attr.encode() - bvalue += struct.pack('!H', len(pattr)) - bvalue += pattr - - for anlri in self._nlri_list: - bvalue += anlri.encode() - - return bvalue - - -def decode(ptype, payload, msg_len): - """Decodes given payload into bgp message instance of given type. - """ - bgp_msg_class = _BGP_MESSAGE_REGISTRY.get(ptype) - if not bgp_msg_class: - raise BadMsg(ptype) - - return bgp_msg_class.from_bytes(payload, msg_len) diff --git a/ryu/services/protocols/bgp/protocols/bgp/nlri.py b/ryu/services/protocols/bgp/protocols/bgp/nlri.py deleted file mode 100644 index bed279d3..00000000 --- a/ryu/services/protocols/bgp/protocols/bgp/nlri.py +++ /dev/null @@ -1,841 +0,0 @@ -# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" - Module related to BGP Network layer reachability information (NLRI). -""" - -from abc import ABCMeta -import logging -import socket -import struct -from types import IntType - -from ryu.services.protocols.bgp.protocols.bgp.exceptions import OptAttrError -from ryu.services.protocols.bgp.utils.other import bytes2hex -from ryu.services.protocols.bgp.utils.other import hex2byte -from ryu.services.protocols.bgp.utils.validation import is_valid_ext_comm_attr -from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4 -from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4_prefix -from ryu.services.protocols.bgp.utils.validation import is_valid_ipv6_prefix -from ryu.services.protocols.bgp.utils.validation import is_valid_mpls_label -from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn -from ryu.services.protocols.bgp.utils.validation import is_valid_route_disc - - -LOG = logging.getLogger('protocols.bgp.nlri') - -# Registry for bgp message class by their type code. -# : - : -_NLRI_REGISTRY = {} - - -def _register_nlri(cls): - """Used as class decorator for registering NLRI classes by their afi/safi. - """ - assert _NLRI_REGISTRY.get((cls.AFI, cls.SAFI)) is None - _NLRI_REGISTRY[(cls.AFI, cls.SAFI)] = cls - return cls - - -# -# AddressFamily -# -class AddressFamily(object): - """Subclasses of this class hold methods for a specific AF and - help the calling code to stay AF-independent. - - Each subclass need have just a singleton instance (see below). - """ - - def __init__(self, afi): - self.afi = afi - - def __hash__(self): - return hash(self.afi) - - def __cmp__(self, other): - afi1 = None - afi2 = None - if isinstance(other, IntType): - afi2 = other - else: - afi2 = other.afi - if isinstance(self, IntType): - afi1 = self - else: - afi1 = self.afi - return cmp(afi1, afi2) - - -class AfiIpv4(AddressFamily): - def __init__(self): - super(AfiIpv4, self).__init__(1) - - def __repr__(self): - return "IPv4" - - -class AfiIpv6(AddressFamily): - def __init__(self): - super(AfiIpv6, self).__init__(2) - - def __repr__(self): - return "IPv6" - - -# -# SubAddressFamily -# -# An sub-address family as defined by BGP. -# -class SubAddressFamily(object): - - def __init__(self, safi): - self.safi = safi - - def __hash__(self): - return hash(self.safi) - - def __cmp__(self, other): - safi1 = None - safi2 = None - if isinstance(self, IntType): - safi1 = self - else: - safi1 = self.safi - if isinstance(other, IntType): - safi2 = other - else: - safi2 = other.safi - return cmp(safi1, safi2) - - -class SafiNlriUnicast(SubAddressFamily): - def __init__(self): - super(SafiNlriUnicast, self).__init__(1) - - def __repr__(self): - return "SafiNlriUnicast" - - -class SafiVpn(SubAddressFamily): - def __init__(self): - super(SafiVpn, self).__init__(128) - - def __repr__(self): - return "SafiVpn" - - -class SafiRtc(SubAddressFamily): - def __init__(self): - super(SafiRtc, self).__init__(132) - - def __repr__(self): - return "SafiRtc" - -NLRI_UC = SafiNlriUnicast() -SAF_VPN = SafiVpn() -SAF_RTC = SafiRtc() - -# Singleton objects for each AF. -AF_IPv4 = AfiIpv4() -AF_IPv6 = AfiIpv6() - -# Constants to represent address family and sub-address family. -ADD_FMLY = 'afi' -SUB_ADD_FMLY = 'safi' - - -# -# RouteFamily -# -class RouteFamily(object): - """The family that a given route (or Network Layer Reachability - Information) belongs to. - - Basically represents a combination of AFI/SAFI. - """ - __slots__ = ('_add_fmly', '_sub_add_fmly') - - def __init__(self, add_fmly, sub_add_fmly): - # Validate i/p. - if not add_fmly or not sub_add_fmly: - raise ValueError('Invalid arguments.') - - self._add_fmly = add_fmly - self._sub_add_fmly = sub_add_fmly - - @property - def afi(self): - return self._add_fmly.afi - - @property - def safi(self): - return self._sub_add_fmly.safi - - def __repr__(self): - return ('RouteFamily(afi=%s, safi=%s)' % (self.afi, self.safi)) - - def __cmp__(self, other): - other_rf = (other.afi, other.safi) - self_rf = (self.afi, self.safi) - return cmp(self_rf, other_rf) - - @staticmethod - def is_valid(other): - if other and (hasattr(other, 'afi') and hasattr(other, 'safi')): - return True - return False - -# Various route family singletons. -RF_IPv4_UC = RouteFamily(AF_IPv4, NLRI_UC) -RF_IPv6_UC = RouteFamily(AF_IPv6, NLRI_UC) -RF_IPv4_VPN = RouteFamily(AF_IPv4, SAF_VPN) -RF_IPv6_VPN = RouteFamily(AF_IPv6, SAF_VPN) -RF_RTC_UC = RouteFamily(AF_IPv4, SAF_RTC) - -_rf_by_afi_safi = { - (1, 1): RF_IPv4_UC, - (2, 1): RF_IPv6_UC, - (1, 128): RF_IPv4_VPN, - (2, 128): RF_IPv6_VPN, - (1, 132): RF_RTC_UC -} - - -def get_rf(afi, safi): - """Returns *RouteFamily* singleton instance for given *afi* and *safi*.""" - if not isinstance(afi, IntType): - afi = int(afi) - if not isinstance(safi, IntType): - safi = int(safi) - return _rf_by_afi_safi.get((afi, safi)) - - -# TODO(PH): Consider trade-offs of making this extend Internable. -class Nlri(object): - """Represents super class of all Network Layer Reachability Information. - """ - __meta__ = ABCMeta - __slots__ = () - - # Sub-classes should set afi/safi constants appropriately. - AFI = 0 - SAFI = 0 - - @classmethod - def encode(self): - raise NotImplementedError() - - @property - def route_family(self): - return get_rf(self.__class__.AFI, self.__class__.SAFI) - - -@_register_nlri -class Vpnv4(Nlri): - """Vpnv4 NLRI. - """ - __slots__ = ('_labels', '_route_disc', '_prefix') - - AFI = 1 - SAFI = 128 - - def __init__(self, labels, route_disc, prefix): - Nlri.__init__(self) - if not labels: - labels = [] - - # Validate given params - for label in labels: - if not is_valid_mpls_label(label): - raise ValueError('Invalid label %s' % label) - if (not is_valid_ipv4_prefix(prefix) or - not is_valid_route_disc(route_disc)): - raise ValueError('Invalid parameter value(s).') - - self._labels = labels - self._route_disc = route_disc - self._prefix = prefix - - @property - def label_list(self): - return self._labels[:] - - @property - def route_disc(self): - return self._route_disc - - @property - def prefix(self): - return self._prefix - - @property - def formatted_nlri_str(self): - return "%s:%s" % (self._route_disc, self.prefix) - - def __repr__(self): - if self._labels: - l = ','.join([str(l) for l in self._labels]) - else: - l = 'none' - - return ('Vpnv4(label=%s, route_disc=%s, prefix=%s)' % - (l, self.route_disc, self.prefix)) - - def __str__(self): - return 'Vpnv4 %s:%s, %s' % (self.route_disc, self.prefix, self._labels) - - def __cmp__(self, other): - return cmp( - (self._labels, self.route_disc, self.prefix), - (other.label_list, other.route_disc, other.prefix), - ) - - def encode(self): - plen = 0 - v = '' - labels = self._labels[:] - - if not labels: - return '\0' - - labels = [l << 4 for l in labels] - labels[-1] |= 1 - - for l in labels: - lo = l & 0xff - hi = (l & 0xffff00) >> 8 - v += struct.pack('>HB', hi, lo) - plen += 24 - - l, r = self.route_disc.split(':') - if '.' in l: - ip = socket.inet_aton(l) - route_disc = struct.pack('!H4sH', 1, ip, int(r)) - else: - route_disc = struct.pack('!HHI', 0, int(l), int(r)) - - v += route_disc - plen += 64 - - ip, masklen = self.prefix.split('/') - ip = socket.inet_aton(ip) - masklen = int(masklen) - - plen += masklen - if masklen > 24: - v += ip - elif masklen > 16: - v += ip[:3] - elif masklen > 8: - v += ip[:2] - elif masklen > 0: - v += ip[:1] - else: - pass - - return struct.pack('B', plen) + v - - @classmethod - def from_bytes(cls, plen, val): - - if plen == 0: - # TODO(PH): Confirm this is valid case and implementation. - return cls([], '0:0', '0.0.0.0/0') - - idx = 0 - - # plen is the length, in bits, of all the MPLS labels, - # plus the 8-byte RD, plus the IP prefix - labels = [] - while True: - ls, = struct.unpack_from('3s', val, idx) - idx += 3 - plen -= 24 - - if ls == '\x80\x00\x00': - # special null label for vpnv4 withdraws - labels = None - break - - label, = struct.unpack_from('!I', '\x00' + ls) - bottom = label & 1 - - labels.append(label >> 4) - if bottom: - break - # TODO(PH): We are breaking after first label as we support only - # one label for now. Revisit if we need to support stack of labels. - break - - rdtype, route_disc = struct.unpack_from('!H6s', val, idx) - if rdtype == 1: - rdip, num = struct.unpack('!4sH', route_disc) - rdip = socket.inet_ntoa(rdip) - route_disc = '%s:%s' % (rdip, num) - else: - num1, num2 = struct.unpack('!HI', route_disc) - route_disc = '%s:%s' % (num1, num2) - - idx += 8 - plen -= 64 - - ipl = pb(plen) - ip = val[idx:idx + ipl] - idx += ipl - - prefix = unpack_ipv4(ip, plen) - - return cls(labels, route_disc, prefix) - - -@_register_nlri -class Vpnv6(Nlri): - """Vpnv4 NLRI. - """ - __slots__ = ('_labels', '_route_disc', '_prefix') - - AFI = 2 - SAFI = 128 - - def __init__(self, labels, route_disc, prefix): - Nlri.__init__(self) - if not labels: - labels = [] - - # Validate given params - for label in labels: - if not is_valid_mpls_label(label): - raise ValueError('Invalid label %s' % label) - if (not is_valid_route_disc(route_disc) or - not is_valid_ipv6_prefix(prefix)): - raise ValueError('Invalid parameter value(s).') - - self._labels = labels - self._route_disc = route_disc - self._prefix = prefix - - @property - def label_list(self): - return self._labels[:] - - @property - def route_disc(self): - return self._route_disc - - @property - def prefix(self): - return self._prefix - - @property - def formatted_nlri_str(self): - return "%s:%s" % (self._route_disc, self.prefix) - - def __repr__(self): - if self._labels: - l = ','.join([str(l) for l in self._labels]) - else: - l = 'none' - - return ('Vpnv6(label=%s, route_disc=%s, prefix=%s)' % - (l, self.route_disc, self.prefix)) - - def __str__(self): - return 'Vpnv6 %s:%s, %s' % (self.route_disc, self.prefix, self._labels) - - def __cmp__(self, other): - return cmp( - (self._labels, self.route_disc, Ipv6(self.prefix).encode()), - (other.label_list, other.route_disc, Ipv6(other.prefix).encode()), - ) - - def encode(self): - plen = 0 - v = '' - labels = self._labels[:] - - if not labels: - return '\0' - - labels = [l << 4 for l in labels] - labels[-1] |= 1 - - for l in labels: - lo = l & 0xff - hi = (l & 0xffff00) >> 8 - v += struct.pack('>HB', hi, lo) - plen += 24 - - l, r = self.route_disc.split(':') - if '.' in l: - ip = socket.inet_aton(l) - route_disc = struct.pack('!H4sH', 1, ip, int(r)) - else: - route_disc = struct.pack('!HHI', 0, int(l), int(r)) - v += route_disc - plen += 64 - - ip, masklen = self.prefix.split('/') - ip = socket.inet_pton(socket.AF_INET6, ip) - masklen = int(masklen) - - plen += masklen - v += ip[:pb6(masklen)] - - return struct.pack('B', plen) + v - - @classmethod - def from_bytes(cls, plen, val): - if plen == 0: - # TODO(PH): Confirm this is valid case and implementation. - return cls([], '0:0', '::/0') - - idx = 0 - - # plen is the length, in bits, of all the MPLS labels, - # plus the 8-byte RD, plus the IP prefix - labels = [] - while True: - ls, = struct.unpack_from('3s', val, idx) - idx += 3 - plen -= 24 - - if ls == '\x80\x00\x00': - # special null label for vpnv4 withdraws - labels = None - break - - label, = struct.unpack_from('!I', '\x00' + ls) - bottom = label & 1 - - labels.append(label >> 4) - if bottom: - break - # TODO(PH): We are breaking after first label as we support only - # one label for now. Revisit if we need to support stack of labels. - break - - rdtype, route_disc = struct.unpack_from('!H6s', val, idx) - if rdtype == 1: - rdip, num = struct.unpack('!4sH', route_disc) - rdip = socket.inet_ntoa(rdip) - route_disc = '%s:%s' % (rdip, num) - else: - num1, num2 = struct.unpack('!HI', route_disc) - route_disc = '%s:%s' % (num1, num2) - - idx += 8 - plen -= 64 - - ipl = pb6(plen) - ip = val[idx:idx + ipl] - idx += ipl - - prefix = unpack_ipv6(ip, plen) - - return cls(labels, route_disc, prefix) - - -@_register_nlri -class Ipv4(Nlri): - __slots__ = ('_prefix') - - AFI = 1 - SAFI = 1 - - def __init__(self, prefix): - if not is_valid_ipv4_prefix(prefix): - raise ValueError('Invalid prefix %s.' % prefix) - Nlri.__init__(self) - self._prefix = prefix - - @property - def prefix(self): - return self._prefix - - @property - def formatted_nlri_str(self): - return self._prefix - - def __cmp__(self, other): - aip, alen = self.prefix.split('/') - alen = int(alen) - aip = socket.inet_aton(aip) - - bip, blen = other.prefix.split('/') - blen = int(blen) - bip = socket.inet_aton(bip) - - return cmp((aip, alen), (bip, blen)) - - def encode(self): - plen = 0 - v = '' - - ip, masklen = self.prefix.split('/') - ip = socket.inet_aton(ip) - masklen = int(masklen) - - plen += masklen - if masklen > 24: - v += ip - elif masklen > 16: - v += ip[:3] - elif masklen > 8: - v += ip[:2] - elif masklen > 0: - v += ip[:1] - else: - pass - - return struct.pack('B', plen) + v - - def __repr__(self): - return 'Ipv4(%s)' % (self.prefix) - - def __str__(self): - return 'Ipv4 ' + self.prefix - - @classmethod - def from_bytes(cls, plen, val): - return cls(unpack_ipv4(val, plen)) - - -@_register_nlri -class Ipv6(Nlri): - __slots__ = ('_prefix') - - AFI = 2 - SAFI = 1 - - def __init__(self, prefix): - if not is_valid_ipv6_prefix(prefix): - raise ValueError('Invalid prefix %s.' % prefix) - Nlri.__init__(self) - self._prefix = prefix - - @property - def prefix(self): - return self._prefix - - @property - def formatted_nlri_str(self): - return self._prefix - - def __cmp__(self, other): - abin = self.encode() - bbin = other.encode() - return cmp(abin, bbin) - - def encode(self): - plen = 0 - v = '' - - ip, masklen = self.prefix.split('/') - ip = socket.inet_pton(socket.AF_INET6, ip) - masklen = int(masklen) - - plen += masklen - ip_slice = pb6(masklen) - v += ip[:ip_slice] - - return struct.pack('B', plen) + v - - def __repr__(self): - return 'Ipv6(%s)' % (self.prefix) - - def __str__(self): - return 'Ipv6 ' + self.prefix - - @classmethod - def from_bytes(cls, plen, val): - return cls(unpack_ipv6(val, plen)) - - -@_register_nlri -class RtNlri(Nlri): - """Route Target Membership NLRI. - - Route Target membership NLRI is advertised in BGP UPDATE messages using - the MP_REACH_NLRI and MP_UNREACH_NLRI attributes. - """ - __slots__ = ('_origin_as', '_route_target') - - AFI = 1 - SAFI = 132 - DEFAULT_AS = '0:0' - DEFAULT_RT = '0:0' - - def __init__(self, origin_as, route_target): - Nlri.__init__(self) - # If given is not default_as and default_rt - if not (origin_as is RtNlri.DEFAULT_AS and - route_target is RtNlri.DEFAULT_RT): - # We validate them - if (not is_valid_old_asn(origin_as) or - not is_valid_ext_comm_attr(route_target)): - raise ValueError('Invalid params.') - self._origin_as = origin_as - self._route_target = route_target - - @property - def origin_as(self): - return self._origin_as - - @property - def route_target(self): - return self._route_target - - @property - def formatted_nlri_str(self): - return "%s:%s" % (self.origin_as, self.route_target) - - def is_default_rtnlri(self): - if (self._origin_as is RtNlri.DEFAULT_AS and - self._route_target is RtNlri.DEFAULT_RT): - return True - return False - - def __str__(self): - return 'RtNlri ' + str(self._origin_as) + ':' + self._route_target - - def __repr__(self): - return 'RtNlri(%s, %s)' % (self._origin_as, self._route_target) - - def __cmp__(self, other): - return cmp( - (self._origin_as, self._route_target), - (other.origin_as, other.route_target), - ) - - @classmethod - def from_bytes(cls, plen, val): - idx = 0 - if plen == 0 and not val: - return cls(RtNlri.DEFAULT_AS, RtNlri.DEFAULT_RT) - - # Extract origin AS. - origin_as, = struct.unpack_from('!I', val, idx) - idx += 4 - - # Extract route target. - route_target = '' - etype, esubtype, payload = struct.unpack_from('BB6s', val, idx) - # RFC says: The value of the high-order octet of the Type field for the - # Route Target Community can be 0x00, 0x01, or 0x02. The value of the - # low-order octet of the Type field for this community is 0x02. - # TODO(PH): Remove this exception when it breaks something Here we make - # exception as Routem packs lower-order octet as 0x00 - if etype in (0, 2) and esubtype in (0, 2): - # If we have route target community in AS number format. - asnum, i = struct.unpack('!HI', payload) - route_target = ('%s:%s' % (asnum, i)) - elif etype == 1 and esubtype == 2: - # If we have route target community in IP address format. - ip_addr, i = struct.unpack('!4sH', payload) - ip_addr = socket.inet_ntoa(ip_addr) - route_target = ('%s:%s' % (ip_addr, i)) - elif etype == 0 and esubtype == 1: - # TODO(PH): Parsing of RtNlri 1:1:100:1 - asnum, i = struct.unpack('!HI', payload) - route_target = ('%s:%s' % (asnum, i)) - - return cls(origin_as, route_target) - - def encode(self): - rt_nlri = '' - if not self.is_default_rtnlri(): - rt_nlri += struct.pack('!I', self.origin_as) - # Encode route target - first, second = self.route_target.split(':') - if '.' in first: - ip_addr = socket.inet_aton(first) - rt_nlri += struct.pack('!BB4sH', 1, 2, ip_addr, int(second)) - else: - rt_nlri += struct.pack('!BBHI', 0, 2, int(first), int(second)) - - # RT Nlri is 12 octets - return struct.pack('B', (8 * 12)) + rt_nlri - - -def pb(masklen): - if masklen > 24: - return 4 - elif masklen > 16: - return 3 - elif masklen > 8: - return 2 - elif masklen > 0: - return 1 - return 0 - -_v6_bits = range(120, -8, -8) -_v6_bytes = [i / 8 for i in range(128, 0, -8)] - - -def pb6(masklen): - for idx, bits in enumerate(_v6_bits): - if masklen > bits: - return _v6_bytes[idx] - return 0 - - -def unpack_ipv4(pi, masklen): - pi += '\x00' * 4 - return '%s/%s' % (socket.inet_ntoa(pi[:4]), masklen) - - -def unpack_ipv6(pi, masklen): - pi += '\x00' * 16 - ip = socket.inet_ntop(socket.AF_INET6, pi[:16]) - return '%s/%s' % (ip, masklen) - - -def ipv4_mapped_ipv6(ipv4): - if not is_valid_ipv4(ipv4): - raise ValueError('Invalid ipv4 address given %s.' % ipv4) - ipv4n = socket.inet_pton(socket.AF_INET, ipv4) - ipv6_hex = '00' * 10 + 'ff' * 2 + bytes2hex(ipv4n) - ipv6n = hex2byte(ipv6_hex) - ipv6 = socket.inet_ntop(socket.AF_INET6, ipv6n) - return ipv6 - - -# TODO(PH): Consider refactoring common functionality new methods -# Look at previous commit -def parse(received, afi=1, safi=1): - recv_nlri_list = [] - - klass = _NLRI_REGISTRY.get((afi, safi)) - if not klass: - raise ValueError('Asked to parse unsupported NLRI afi/safi: ' - '(%s, %s)' % (afi, safi)) - - try: - idx = 0 - while idx < len(received): - plen, = struct.unpack_from('B', received, idx) - idx += 1 - nbytes, rest = divmod(plen, 8) - if rest: - nbytes += 1 - val = received[idx:idx + nbytes] - idx += nbytes - recv_nlri_list.append(klass.from_bytes(plen, val)) - except Exception: - raise OptAttrError() - - return recv_nlri_list diff --git a/ryu/services/protocols/bgp/protocols/bgp/pathattr.py b/ryu/services/protocols/bgp/protocols/bgp/pathattr.py deleted file mode 100644 index 04499d4b..00000000 --- a/ryu/services/protocols/bgp/protocols/bgp/pathattr.py +++ /dev/null @@ -1,1076 +0,0 @@ -# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -This module provides BGP Path Attributes classes and utility methods to -encode and decode them. - -This file was adapted from pybgp open source project. -""" -from abc import ABCMeta -from abc import abstractmethod -import copy -import logging -import socket -import StringIO -import struct - -from ryu.services.protocols.bgp.protocols.bgp.exceptions import AttrFlagError -from ryu.services.protocols.bgp.protocols.bgp.exceptions import AttrLenError -from ryu.services.protocols.bgp.protocols.bgp.exceptions import InvalidNextHop -from ryu.services.protocols.bgp.protocols.bgp.exceptions import \ - InvalidOriginError -from ryu.services.protocols.bgp.protocols.bgp.exceptions import MalformedAsPath -from ryu.services.protocols.bgp.protocols.bgp.exceptions import OptAttrError -from ryu.services.protocols.bgp.protocols.bgp import nlri -from ryu.services.protocols.bgp.protocols.bgp.nlri import get_rf -from ryu.services.protocols.bgp.protocols.bgp.nlri import RF_IPv4_VPN -from ryu.services.protocols.bgp.protocols.bgp.nlri import RF_IPv6_VPN -from ryu.services.protocols.bgp.protocols.bgp.nlri import RF_RTC_UC -from ryu.services.protocols.bgp.utils.internable import Internable -from ryu.services.protocols.bgp.utils import validation -from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4 -from ryu.services.protocols.bgp.utils.validation import is_valid_ipv6 - - -LOG = logging.getLogger('protocols.bgp.pathattr') - -# BGP Attribute flags -EXTENDED_LEN_BGP_ATTR = 0x10 -PARTIAL_BGP_ATTR = 0x20 -TRANSITIVE_BGP_ATTR = 0x40 -OPTIONAL_BGP_ATTR = 0x80 - -# BGP flag mask -DEFAULT_FLAGS_MASK = 0x3f - -# BGP recognized path attribute class registry by type-code. -# i.e. : -_PATH_ATTR_REGISTRY = {} - - -def _register_path_attr(cls): - """Used as decorator for registering recognized bgp path attribute class - by their type-code. - """ - assert issubclass(cls, RcgPathAttr) - assert hasattr(cls, 'TYPE_CODE') and hasattr(cls, 'FLAGS') - assert _PATH_ATTR_REGISTRY.get(cls.TYPE_CODE) is None - _PATH_ATTR_REGISTRY[cls.TYPE_CODE] = cls - return cls - - -def decode(received, idx=0): - """Decodes given bytes into corresponding BGP path attribute. - """ - iidx = idx - flagb, path_attr_type = struct.unpack_from('BB', received, idx) - idx += 2 - used = 2 - - if flagb & 16: - length, = struct.unpack_from('>H', received, idx) - idx += 2 - used += 2 - else: - length, = struct.unpack_from('!B', received, idx) - idx += 1 - used += 1 - - recv_data = received[idx:(idx + length)] - used += length - - # Check if this attribute type is recognized. - path_attr_class = _PATH_ATTR_REGISTRY.get(path_attr_type) - path_attr = None - if path_attr_class: - # Check if flags match expected from known/recognized attribute type. - if not path_attr_class.check_flags(flagb): - LOG.error( - "Flags(%s) of pathattr %s received in update don't " - "match expected flags(%s)" - % ( - flagb, - str(path_attr_class), - path_attr_class.FLAGS - ) - ) - raise AttrFlagError(data=received[iidx:used]) - - try: - path_attr = path_attr_class.from_bytes(recv_data) - except (AttrLenError, InvalidOriginError, InvalidNextHop, - OptAttrError) as e: - # Set attribute type, length and value as data/payload. - e.data = received[iidx:used] - raise e - else: - path_attr = UnRcgPathAttr(recv_data, flagb, path_attr_type) - - return used, path_attr - - -class PathAttr(Internable): - """Abstract base class for bgp path attributes. - - Defines interface for all path attributes and provides some default util. - methods. - """ - __metaclass__ = ABCMeta - __slots__ = ('_flags') - TYPE_CODE = 0 - ATTR_NAME = 'default' - - def __init__(self, flags): - super(PathAttr, self).__init__() - self._flags = flags - - @property - def flags(self): - return self._flags - - @abstractmethod - def packvalue(self): - """Encodes path-attribute value/pay-load into binary format.""" - raise NotImplementedError() - - def encode(self): - """Wraps packed path-attribute value with headers, such as, - flags, type-code and length. - """ - valueb = self.packvalue() - flags = self._flags - type_code = self.__class__.TYPE_CODE - - if len(valueb) > 255: - flags = flags | 16 - return struct.pack('!BBH', flags, type_code, len(valueb)) + valueb - - flags = flags & (0xff ^ 16) - return struct.pack('BBB', flags, type_code, len(valueb)) + valueb - - def str_flags(self): - """Returns a list of attribute category for this bgp attribute.""" - - if self._flags: - flag_cat = [] - if self._flags & OPTIONAL_BGP_ATTR: - flag_cat.append('optional') - else: - flag_cat.append('well-known') - - if self._flags & TRANSITIVE_BGP_ATTR: - flag_cat.append('transitive') - else: - flag_cat.append('non-transitive') - - if self._flags & PARTIAL_BGP_ATTR: - flag_cat.append('partial') - else: - flag_cat.append('complete') - - if self._flags & EXTENDED_LEN_BGP_ATTR: - flag_cat.append('ext-length') - else: - flag_cat.append('regular-length') - - return ','.join(flag_cat) - - return 'None' - - def __repr__(self): - return '<%s type/num=%s/%s flags %s>' % ( - self.__class__.__name__, 'unknown', self.__class__.TYPE_CODE, - self.str_flags()) - - def __cmp__(self, other): - if isinstance(other, PathAttr): - if other.__class__ == self.__class__: - return cmp(self._flags, other._flags) - - return -1 - - def __hash__(self): - return hash(self._flags, self.__class__.TYPE_CODE) - - -class RcgPathAttr(PathAttr): - """Base class for all recognized path attributes.""" - # Flags for this type of known path attribute. - # Sub-classes should provide value as per RFC. - FLAGS = None - - # There are some flags we don't care about. By default we don't care about - # extended-length bit and partial bit, so mask is 0x3f (0011 1111) - FLAGS_MASK = DEFAULT_FLAGS_MASK - - def __init__(self): - PathAttr.__init__(self, self.__class__.FLAGS) - - @classmethod - def from_bytes(cls, val): - raise NotImplementedError() - - @classmethod - def check_flags(cls, flags): - """Check if provided flags match flags required by RFC (cls.FLAGS). - - RFC path attribute higher order bit rules: - 0 1 2 3 4 5 6 7 - - Well Known - 0 1 - always - 0 - always - 0 - attribute length 1 octet - 1 - attribute length 2 octet - - Optional - 1 1 - Transitive - 1 - Partial - 0 - Complete - 0 - Non-Transitive - 0 - always - """ - return cls.FLAGS | cls.FLAGS_MASK == flags | cls.FLAGS_MASK - - -class UnRcgPathAttr(PathAttr): - """Class for all un-supported/un-recognized bgp path-attributes. - """ - __slots__ = ('_type_code', '_value') - ATTR_NAME = 'unknown' - - def __init__(self, value, flags, type_code): - PathAttr.__init__(self, flags) - self._type_code = type_code - self._value = value - - @property - def value(self): - return self._value - - @property - def type_code(self): - return self._type_code - - def packvalue(self): - return self._value - - def encode(self): - all_flags = self._flags - valueb = self.packvalue() - - if len(valueb) > 255: - all_flags = all_flags | 16 - return struct.pack('!BBH', all_flags, self.type_code, - len(valueb)) + valueb - - all_flags = all_flags & (0xff ^ 16) - return struct.pack('BBB', all_flags, self.type_code, - len(valueb)) + valueb - - def is_optional_transitive(self): - """Returns true if this is an optional path attribute. - """ - return self._flags & OPTIONAL_BGP_ATTR - - def is_transitive(self): - """Returns true if this is an transitive path attribute. - """ - return self._flags & TRANSITIVE_BGP_ATTR - - def __repr__(self): - return '<%s type/num=%s/%s flags %s value %r>' % ( - self.__class__.__name__, 'unknown', self.type_code, self.flags, - self.value) - - -@_register_path_attr -class Origin(RcgPathAttr): - """ORIGIN is a well-known mandatory bgp path-attribute.""" - __slots__ = ('_value') - TYPE_CODE = 1 - ATTR_NAME = 'origin' - # 010 - well known, transitive, complete - FLAGS = TRANSITIVE_BGP_ATTR - - # Various Origin values. - IGP = 'igp' - EGP = 'egp' - INCOMPLETE = 'incomplete' - - def __init__(self, value='incomplete'): - RcgPathAttr.__init__(self) - if value not in (Origin.IGP, Origin.EGP, Origin.INCOMPLETE): - raise ValueError('Invalid Origin attribute value.') - self._value = value - - @property - def value(self): - return self._value - - @classmethod - def from_bytes(cls, value): - """Decodes bgp path-attribute with type-code 1, i.e. ORIGIN. - """ - if value == '\x00': - value = Origin.IGP - elif value == '\x01': - value = Origin.EGP - elif value == '\x02': - value = Origin.INCOMPLETE - else: - raise InvalidOriginError() - - return cls(value) - - def packvalue(self): - if self.value == Origin.IGP: - return '\x00' - elif self.value == Origin.EGP: - return '\x01' - elif self.value == Origin.INCOMPLETE: - return '\x02' - return self.value - - def __repr__(self): - return '' - - def __str__(self): - return str(self.value) - - -@_register_path_attr -class AsPath(RcgPathAttr): - """AS_PATH is a well-known mandatory bgp path attribute. - """ - __slots__ = ('_path_seg_list') - TYPE_CODE = 2 - ATTR_NAME = 'aspath' - # Higher order bits: 010 - well known, transitive, complete - FLAGS = TRANSITIVE_BGP_ATTR - SEG_TYP_AS_SET = 1 - SEG_TYP_AS_SEQ = 2 - - def __init__(self, path_seg_list): - RcgPathAttr.__init__(self) - self._path_seg_list = None - if isinstance(path_seg_list, str): - self._path_seg_list = [] - for seg in path_seg_list.split(): - if seg.startswith('set(') and seg.endswith(')'): - seg = set([int(s) for s in seg[4:-1].split(',')]) - else: - seg = [int(s) for s in seg.split(',')] - self._path_seg_list.append(seg) - else: - self._path_seg_list = path_seg_list[:] - - @property - def path_seg_list(self): - return copy.deepcopy(self._path_seg_list) - - def get_as_path_len(self): - count = 0 - for seg in self._path_seg_list: - if isinstance(seg, list): - # Segment type 2 stored in list and all AS counted. - count += len(seg) - else: - # Segment type 1 stored in set and count as one. - count += 1 - - return count - - def has_local_as(self, local_as): - """Check if *local_as* is already present on path list.""" - for as_path_seg in self._path_seg_list: - for as_num in as_path_seg: - if as_num == local_as: - return True - return False - - def has_matching_leftmost(self, remote_as): - """Check if leftmost AS matches *remote_as*.""" - if not self._path_seg_list or not remote_as: - return False - - leftmost_seg = self.path_seg_list[0] - if leftmost_seg and leftmost_seg[0] == remote_as: - return True - - return False - - @property - def value(self): - ret = [] - for as_path_seg in self._path_seg_list: - for as_num in as_path_seg: - ret.append(as_num) - return ret - - def __repr__(self): - rstring = StringIO.StringIO() - rstring.write('') - return rstring.getvalue() - - def __str__(self): - ret = '[' - for as_path_seg in self._path_seg_list: - ret += ', '.join([str(asnum) for asnum in as_path_seg]) - return ret + ']' - - @classmethod - def from_bytes(cls, val): - """Decodes bgp path-attribute with type-code 2, i.e. AS_PATH. - """ - path_seg_list = [] - iidx = 0 - - while iidx < len(val): - segtype, numas = struct.unpack_from('BB', val, iidx) - iidx += 2 - - if segtype == AsPath.SEG_TYP_AS_SET: - container = set() - add = container.add - elif segtype == AsPath.SEG_TYP_AS_SEQ: - container = [] - add = container.append - else: - raise MalformedAsPath() - - for _ in range(numas): - asnum, = struct.unpack_from('!H', val, iidx) - iidx += 2 - add(asnum) - path_seg_list.append(container) - - return cls(path_seg_list) - - def packvalue(self): - valueb = '' - for seg in self._path_seg_list: - if isinstance(seg, set): - segtype = 1 - elif isinstance(seg, (tuple, list)): - segtype = 2 - else: - raise Exception('unknown segment type %r' % (seg,)) - - valueb += struct.pack('BB', segtype, len(seg)) - try: - iter(seg) - except TypeError: - valueb += struct.pack('!H', int(seg)) - else: - for asnum in seg: - if not isinstance(asnum, int): - asnum = int(asnum) - valueb += struct.pack('!H', asnum) - - return valueb - - -@_register_path_attr -class NextHop(RcgPathAttr): - """NEXT_HOP is well-known mandatory bgp path-attribute. - """ - __slots__ = () - TYPE_CODE = 3 - ATTR_NAME = 'nexthop' - # Higher order bits: 010 - well known, transitive, complete - FLAGS = TRANSITIVE_BGP_ATTR - - def __init__(self, ip_address): - if not is_valid_ipv4(ip_address): - raise ValueError('Invalid ipv4 address %s.' % ip_address) - RcgPathAttr.__init__(self) - self._ip_address = ip_address - - @property - def ip_address(self): - return self._ip_address - - def __repr__(self): - return '' % (self.ip_address) - - def __str__(self): - return str(self.ip_address) - - @classmethod - def from_bytes(cls, value): - """Decodes bgp path-attribute with type-code 3, i.e. NEXT_HOP. - """ - value = socket.inet_ntoa(value) - return cls(value) - - def packvalue(self): - return socket.inet_aton(self._ip_address) - - -@_register_path_attr -class IntAttr(RcgPathAttr): - """Super class of all bgp path-attribute whose value is an unsigned - integer. - """ - __slots__ = ('_value') - - def __init__(self, value): - if not value: - value = 0 - self._value = value - RcgPathAttr.__init__(self) - - @property - def value(self): - return self._value - - def __repr__(self): - return '<%s(%d)>' % (self.__class__.__name__, self.value) - - def __str__(self): - return str(self.value) - - @classmethod - def from_bytes(cls, val): - """Decode bgp path-attributes whose value is an unsigned integer. - """ - value, = struct.unpack_from('!I', val) - return cls(value) - - def packvalue(self): - return struct.pack('!I', self.value) - - -@_register_path_attr -class Med(IntAttr): - """MED is optional non-transitive bgp path-attribute.""" - __slots__ = () - TYPE_CODE = 4 - ATTR_NAME = 'med' - # Higher order bits: 100 - optional, non-transitive, complete - FLAGS = OPTIONAL_BGP_ATTR - - def __init__(self, value): - IntAttr.__init__(self, value) - - -@_register_path_attr -class LocalPref(IntAttr): - """LOCAL_PREF is a well-known discretionary attribute.""" - __slots__ = () - TYPE_CODE = 5 - ATTR_NAME = 'localpref' - # Higher order bits: 010 - well-known, transitive, complete - FLAGS = TRANSITIVE_BGP_ATTR - - def __init__(self, value): - IntAttr.__init__(self, value) - - -@_register_path_attr -class Originator(RcgPathAttr): - """ORIGINATOR_ID is a optional non-transitive attribute.""" - __slots__ = ('_value') - TYPE_CODE = 9 - ATTR_NAME = 'originator' - FLAGS = OPTIONAL_BGP_ATTR - - def __init__(self, value): - RcgPathAttr.__init__(self) - self._value = value - - @property - def value(self): - return self._value - - @classmethod - def from_bytes(cls, val): - """Decodes bgp path-attribute with type code 9, i.e. ORIGINATOR_ID. - """ - if len(val) == 4: - value = socket.inet_ntoa(val) - else: - raise Exception('Invalid originator') - - return cls(value) - - def packvalue(self): - return socket.inet_aton(self.value) - - -@_register_path_attr -class ClusterList(RcgPathAttr): - """CLUSTER_LIST is a optional non-transitive bgp path-attribute. - """ - __slots__ = ('_cluster_list') - TYPE_CODE = 10 - ATTR_NAME = 'cluster-list' - FLAGS = OPTIONAL_BGP_ATTR - - def __init__(self, cluster_list): - if not cluster_list: - raise ValueError('Invalid cluster list.') - # TODO(PH): add more validation of input here. - RcgPathAttr.__init__(self) - self._cluster_list = cluster_list - - @property - def cluster_list(self): - return self._cluster_list - - @classmethod - def from_bytes(cls, val): - """Decodes bgp path-attribute with type-code 10, i.e. CLUSTER_LIST. - """ - cluster_list = [] - iidx = 0 - while iidx < len(val): - cluster_list.append( - socket.inet_ntoa(struct.unpack_from('4s', val, iidx)[0]) - ) - iidx += 4 - return cls(cluster_list) - - def packvalue(self): - valueb = '' - for c in self.cluster_list: - valueb += socket.inet_aton(c) - return valueb - - -@_register_path_attr -class MpReachNlri(RcgPathAttr): - """MP_REACH_NLRI is a optional non-transitive bgp path-attribute. - """ - __slots__ = ('_route_family', '_next_hop', '_nlri_list', '_reserved') - TYPE_CODE = 14 - ATTR_NAME = 'mp-reach-nlri' - NEXT_HOP = 'nh' - NLRI = 'nlri' - RESERVED = 'reserved' - # Higher order bits: 100 - optional, non-transitive, complete - FLAGS = OPTIONAL_BGP_ATTR - - def __init__(self, route_family, next_hop, nlri_list, reserved=None): - if not (hasattr(route_family, 'afi') and - hasattr(route_family, 'safi')): - raise ValueError('Invalid parameter value for route_family %s.' % - route_family) - - if not next_hop: - raise ValueError('Invalid next_hop %s' % next_hop) - - # MpReachNlri attribute should have next-hop belonging to same - # route-family - if ((route_family == RF_IPv4_VPN and not is_valid_ipv4(next_hop)) or - (route_family == RF_IPv6_VPN and not is_valid_ipv6(next_hop))): - raise ValueError('Next hop should belong to %s route family' % - route_family) - - if not nlri_list: - nlri_list = [] - - RcgPathAttr.__init__(self) - self._route_family = route_family - self._next_hop = next_hop - self._nlri_list = nlri_list - self._reserved = reserved - - @property - def route_family(self): - return self._route_family - - @property - def next_hop(self): - return self._next_hop - - @property - def nlri_list(self): - return self._nlri_list[:] - - @property - def reserved(self): - return self._reserved - - def __repr__(self): - return '' % ( - self.route_family, self.next_hop, self._nlri_list) - - @classmethod - def from_bytes(cls, val): - """Decodes bgp path-attribute with type code 14, i.e MP_REACH_NLRI. - """ - afi, safi, nhlen = struct.unpack_from('!HBB', val) - fmt = '%dsB' % (nhlen,) - next_hop, reserved = struct.unpack_from(fmt, val, 4) - - if afi == 1 and safi is 128: - # Vpnv4 - _, _, nhip = struct.unpack('!II4s', next_hop) - next_hop = socket.inet_ntop(socket.AF_INET, nhip) - elif afi == 1 and safi == 132: - # RtNlri - nhip, = struct.unpack('!4s', next_hop) - next_hop = socket.inet_ntop(socket.AF_INET, nhip) - elif afi == 2 and safi == 128: - # Vpnv6 - _, _, nhip = struct.unpack('!II16s', next_hop) - next_hop = socket.inet_ntop(socket.AF_INET6, nhip) - else: - LOG.error('Received NLRI for afi/safi (%s/%s), which is not' - ' supported yet!' % (afi, safi)) - raise OptAttrError() - - n_nlri = nlri.parse(val[5 + nhlen:], afi, safi) - route_family = get_rf(afi, safi) - return cls(route_family, next_hop, n_nlri, reserved) - - def packvalue(self): - afi = self._route_family.afi - safi = self._route_family.safi - if self._route_family == RF_IPv4_VPN: - next_hop = '\0' * 8 - next_hop += socket.inet_aton(self.next_hop) - elif self._route_family == RF_RTC_UC: - next_hop = socket.inet_aton(self.next_hop) - elif self._route_family == RF_IPv6_VPN: - next_hop = '\0' * 8 - next_hop += socket.inet_pton(socket.AF_INET6, self.next_hop) - else: - next_hop = self.next_hop - - valueb = struct.pack('!HBB', afi, safi, len(next_hop)) - valueb += next_hop - valueb += chr(self.reserved or 0) - - for n_nlri in self._nlri_list: - valueb += n_nlri.encode() - return valueb - - -@_register_path_attr -class MpUnreachNlri(RcgPathAttr): - """MP_UNREACH_NLRI is a optional non-transitive bgp path-attribute. - """ - __slots__ = ('_route_family', '_nlri_list') - TYPE_CODE = 15 - ATTR_NAME = 'mp-unreach-nlri' - NLRI = 'withdraw_nlri' - # Higher order bits: 100 - optional, non-transitive, complete - FLAGS = OPTIONAL_BGP_ATTR - - def __init__(self, route_family, nlri_list): - if not (hasattr(route_family, 'afi') and - hasattr(route_family, 'safi')): - raise ValueError('Invalid parameter value for route_family %s' % - route_family) - if not nlri_list: - nlri_list = [] - - RcgPathAttr.__init__(self) - self._route_family = route_family - self._nlri_list = nlri_list - - @property - def nlri_list(self): - return self._nlri_list[:] - - @property - def route_family(self): - return self._route_family - - def __repr__(self): - return '' % ( - self._route_family, self._nlri_list) - - @classmethod - def from_bytes(cls, val): - """Decodes bgp path-attribute of type-code 15, i.e. MP_UNREACH_NLRI. - """ - afi, safi = struct.unpack_from('!HB', val) - route_family = get_rf(afi, safi) - w_nlri = nlri.parse(val[3:], afi, safi) - return cls(route_family, w_nlri) - - def packvalue(self): - afi = self._route_family.afi - safi = self._route_family.safi - - valueb = struct.pack('!HB', afi, safi) - - for w_nlri in self._nlri_list: - valueb += w_nlri.encode() - return valueb - - -@_register_path_attr -class Community(RcgPathAttr): - """COMMUNITY is a optional transitive bgp path-attribute. - """ - __slots__ = ('_attr_list') - TYPE_CODE = 8 - ATTR_NAME = 'community' - FLAGS = TRANSITIVE_BGP_ATTR | OPTIONAL_BGP_ATTR - - # String constants of well-known-communities - NO_EXPORT = int('0xFFFFFF01', 16) - NO_ADVERTISE = int('0xFFFFFF02', 16) - NO_EXPORT_SUBCONFED = int('0xFFFFFF03', 16) - WELL_KNOW_COMMUNITIES = (NO_EXPORT, NO_ADVERTISE, NO_EXPORT_SUBCONFED) - - def __init__(self, *attrs): - if not attrs: - raise ValueError('Invalid parameter for community attribute ' - 'list %r.' % attrs) - self._attr_list = [] - for attr in attrs: - if not isinstance(attr, int): - raise ValueError('Invalid community attribute value %s.' % - attr) - self._attr_list.append(attr) - - RcgPathAttr.__init__(self) - - @property - def attr_list(self): - return self._attr_list[:] - - @classmethod - def from_bytes(cls, val): - """Decodes path attribute of type code 8, i.e. Community attribute. - """ - att_list = [] - iidx = 0 - while iidx < len(val): - comm_attr, = struct.unpack_from('!I', val, iidx) - att_list.append(comm_attr) - iidx += 4 - return cls(*att_list) - - def packvalue(self): - commu_attr = '' - for attr in self._attr_list: - commu_attr += struct.pack('!I', int(attr)) - return commu_attr - - @staticmethod - def is_no_export(comm_attr): - """Returns True if given value matches well-known community NO_EXPORT - attribute value. - """ - return comm_attr == Community.NO_EXPORT - - @staticmethod - def is_no_advertise(comm_attr): - """Returns True if given value matches well-known community - NO_ADVERTISE attribute value. - """ - return comm_attr == Community.NO_ADVERTISE - - @staticmethod - def is_no_export_subconfed(comm_attr): - """Returns True if given value matches well-known community - NO_EXPORT_SUBCONFED attribute value. - """ - return comm_attr == Community.NO_EXPORT_SUBCONFED - - def has_comm_attr(self, attr): - """Returns True if given community attribute is present.""" - - for comm_attr in self._attr_list: - if comm_attr == attr: - return True - - return False - - def _community_repr(self, comm_attr): - """Matches given value with all well-known community attribute values. - - Returns string representation of the well-known attribute if we - have a match else returns given value. - """ - - if self.is_no_export(comm_attr): - return 'NO_EXPORT' - elif self.is_no_advertise(comm_attr): - return 'NO_ADVERTISE' - elif self.is_no_export_subconfed(comm_attr): - return 'NO_EXPORT_SUBCONFED' - return (str(comm_attr >> 16) + ':' + - str(comm_attr & int('0x0000ffff', 16))) - - def __repr__(self): - attr_list_repr = (','.join([self._community_repr(ca) - for ca in self._attr_list])) - return ('' % attr_list_repr) - - -@_register_path_attr -class ExtCommunity(RcgPathAttr): - """EXTENDED COMMUNITIES is a optional and transitive bgp path-attribute. - """ - __slots__ = ('_rt_list', '_soo_list', '_unknowns') - TYPE_CODE = 16 - ATTR_NAME = 'extcommunity' - RT = 'route_target' - SOO = 'site_of_origin' - UNKNOWN = 'unknown_community' - # Higher order bits: 110 - optional, transitive, complete - FLAGS = TRANSITIVE_BGP_ATTR | OPTIONAL_BGP_ATTR - - def __str__(self): - return 'rt_list: {0}, soo_list: {1}'.format( - self.rt_list, - self.soo_list - ) - - def __init__(self, rt_list, soo_list, unknowns=None): - if not rt_list and not soo_list: - raise ValueError('Have to provide at-least one RT/SOO attribute.') - if not rt_list: - rt_list = [] - if not soo_list: - soo_list = [] - if not unknowns: - unknowns = {} - - ExtCommunity.validate_supported_attributes(rt_list) - ExtCommunity.validate_supported_attributes(soo_list) - - RcgPathAttr.__init__(self) - self._rt_list = list(rt_list) - self._soo_list = list(soo_list) - self._unknowns = unknowns - - @property - def rt_list(self): - """Returns a list of extracted/configured route target community.""" - # TODO(PH): Make sure we do not raise Exception here but return empty - # list instead. - return self._rt_list[:] - - @property - def soo_list(self): - """Returns route origin community.""" - return self._soo_list[:] - - def __repr__(self): - return '<%s type/num=%s/%s flags %s, rts: %s, soo: %s>' % ( - self.__class__.__name__, self.__class__.ATTR_NAME, - self.__class__.TYPE_CODE, - self.str_flags(), self.rt_list, self.soo_list) - - def has_unknown_communities(self): - """Returns True if we have extracted/configured community other than - route target or route origin community. - """ - return True if self._unknowns else False - - @classmethod - def validate_supported_attributes(cls, attr_list): - """Validate *attr_list* has all valid RTs or SOO attribute - representations. - - RTs and SOO are represented as string in following format: - *global_admin_part:local_admin_part* - """ - for attr in attr_list: - if not validation.is_valid_ext_comm_attr(attr): - raise ValueError('Attribute %s is not a valid RT/SOO' % attr) - - @classmethod - def from_bytes(cls, val): - """Decodes ext-community path-attribute. - """ - rt_list = [] - soo_list = [] - unknowns = {} - iidx = 0 - while iidx < len(val): - etype, esubtype, payload = struct.unpack_from('BB6s', val, iidx) - # RFC says: The value of the high-order octet of the Type field for - # the Route Target Community can be 0x00, 0x01, or 0x02. The value - # of the low-order octet of the Type field for this community is - # 0x02. TODO(PH): Remove this exception when it breaks something - # Here we make exception as Routem packs lower-order octet as 0x00 - if etype in (0, 2) and esubtype in (0, 2): - # If we have route target community in AS number format. - asnum, i = struct.unpack('!HI', payload) - rt_list.append('%s:%s' % (asnum, i)) - elif etype == 1 and esubtype == 2: - # If we have route target community in IP address format. - ip_addr, i = struct.unpack('!4sH', payload) - ip_addr = socket.inet_ntoa(ip_addr) - rt_list.append('%s:%s' % (ip_addr, i)) - elif etype in (0, 2) and esubtype == 3: - # If we have route origin community in AS number format. - asnum, nnum = struct.unpack('!HI', payload) - soo_list.append('%s:%s' % (asnum, nnum)) - elif etype == 1 and esubtype == 3: - # If we have route origin community in IP address format. - ip_addr, nnum = struct.unpack('!4sH', payload) - ip_addr = socket.inet_ntoa(ip_addr) - soo_list.append('%s:%s' % (ip_addr, nnum)) - else: - # All other communities, other than RT and SOO are unknown. - unknown_list = unknowns.get(etype) - if unknown_list is None: - unknown_list = [] - unknowns[etype] = unknown_list - unknown_list.append( - '%s:%s' % (etype, val[iidx + 1:iidx + 8].encode('hex')) - ) - iidx += 8 - - return cls(rt_list, soo_list, unknowns) - - def packvalue(self): - excomb = '' - # Pack route target community attrs. - for route_target in self._rt_list: - first, second = route_target.split(':') - if '.' in first: - ip_addr = socket.inet_aton(first) - excomb += struct.pack('!BB4sH', 1, 2, ip_addr, - int(second)) - else: - excomb += struct.pack('!BBHI', 0, 2, int(first), - int(second)) - # Pack route origin community attrs. - for route_origin in self._soo_list: - first, second = route_origin.split(':') - if '.' in first: - ip_addr = socket.inet_aton(first) - excomb += struct.pack('!BB4sH', 1, 3, ip_addr, - int(second)) - else: - excomb += struct.pack('!BBHI', 0, 3, int(first), - int(second)) - for type, attr_list in self._unknowns.items(): - # Pack all unknown ext. attrs. - excomb += struct.pack('B', int(type)) - excomb += attr_list.decode('hex') - return excomb