bgp: remove original bgp packet library
Signed-off-by: ISHIDA Wataru <ishida.wataru@lab.ntt.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
parent
9d5e66fa4e
commit
660c8e68b4
@ -1,7 +0,0 @@
|
|||||||
try:
|
|
||||||
from collections import OrderedDict
|
|
||||||
except ImportError:
|
|
||||||
from ordereddict import OrderedDict
|
|
||||||
|
|
||||||
# Pointer to active/available OrderedDict.
|
|
||||||
OrderedDict = OrderedDict
|
|
@ -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.
|
|
||||||
# <Key>: <Value> - <capability-code>: <capability-class>
|
|
||||||
_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 '<UnSupportedCap(code=%s)>' % 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 ('<MultiprotocolExtenstionCap(af=%s, saf=%s)>' %
|
|
||||||
(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 '<FourByteAsCap(%s)>' % 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()
|
|
@ -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 '<BadLen %d msgtype=%d>' % (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 '<BadMsg %d>' % (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
|
|
@ -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.
|
|
||||||
# <key>: <value> - <type-code>: <msg class>
|
|
||||||
_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('<Update message withdraw=%r' % (self._withdraw_list,))
|
|
||||||
for ptype, pattr in self._pathattr_map.items():
|
|
||||||
str_rep.write('\n path attr %s, %s' % (ptype, pattr,))
|
|
||||||
# if ptype in (MpReachNlri.ATTR_NAME, MpUnreachNlri):
|
|
||||||
# for nnlri in pattr.nlri_list:
|
|
||||||
# str_rep.write('\n nlri=%s' % (nnlri,))
|
|
||||||
for nnlri in self._nlri_list:
|
|
||||||
str_rep.write('\nmp nlri %s' % (nnlri,))
|
|
||||||
|
|
||||||
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)
|
|
@ -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.
|
|
||||||
# <key>: <value> - <afi, safi>: <nlri class>
|
|
||||||
_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
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user