From bdad6267de53a14115a84961fa075380dfcd34d5 Mon Sep 17 00:00:00 2001 From: ISHIDA Wataru Date: Wed, 16 Apr 2014 03:30:33 +0000 Subject: [PATCH] bgp: add stuff for integration with bgp speaker Signed-off-by: ISHIDA Wataru Signed-off-by: FUJITA Tomonori --- ryu/lib/packet/bgp.py | 726 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 675 insertions(+), 51 deletions(-) diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 2d71917c..974967d3 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -25,23 +25,69 @@ RFC 4271 BGP-4 import abc import six import struct +import copy from ryu.ofproto.ofproto_parser import msg_pack_into from ryu.lib.stringify import StringifyMixin +from ryu.lib.packet import afi +from ryu.lib.packet import safi from ryu.lib.packet import packet_base from ryu.lib.packet import stream_parser from ryu.lib import addrconv -import safi -import afi - - BGP_MSG_OPEN = 1 BGP_MSG_UPDATE = 2 BGP_MSG_NOTIFICATION = 3 BGP_MSG_KEEPALIVE = 4 BGP_MSG_ROUTE_REFRESH = 5 # RFC 2918 +_VERSION = 4 +_MARKER = 16 * '\xff' + +BGP_OPT_CAPABILITY = 2 # RFC 5492 + +BGP_CAP_MULTIPROTOCOL = 1 # RFC 4760 +BGP_CAP_ROUTE_REFRESH = 2 # RFC 2918 +BGP_CAP_CARRYING_LABEL_INFO = 4 # RFC 3107 +BGP_CAP_FOUR_OCTET_AS_NUMBER = 65 # RFC 4893 +BGP_CAP_ENHANCED_ROUTE_REFRESH = 70 # https://tools.ietf.org/html/\ +# draft-ietf-idr-bgp-enhanced-route-refresh-05 + +BGP_ATTR_FLAG_OPTIONAL = 1 << 7 +BGP_ATTR_FLAG_TRANSITIVE = 1 << 6 +BGP_ATTR_FLAG_PARTIAL = 1 << 5 +BGP_ATTR_FLAG_EXTENDED_LENGTH = 1 << 4 + +BGP_ATTR_TYPE_ORIGIN = 1 # 0,1,2 (1 byte) +BGP_ATTR_TYPE_AS_PATH = 2 # a list of AS_SET/AS_SEQUENCE eg. {1 2 3} 4 5 +BGP_ATTR_TYPE_NEXT_HOP = 3 # an IPv4 address +BGP_ATTR_TYPE_MULTI_EXIT_DISC = 4 # uint32 metric +BGP_ATTR_TYPE_LOCAL_PREF = 5 # uint32 +BGP_ATTR_TYPE_ATOMIC_AGGREGATE = 6 # 0 bytes +BGP_ATTR_TYPE_AGGREGATOR = 7 # AS number and IPv4 address +BGP_ATTR_TYPE_COMMUNITIES = 8 # RFC 1997 +BGP_ATTR_TYPE_MP_REACH_NLRI = 14 # RFC 4760 +BGP_ATTR_TYPE_MP_UNREACH_NLRI = 15 # RFC 4760 +BGP_ATTR_TYPE_EXTENDED_COMMUNITIES = 16 # RFC 4360 +BGP_ATTR_TYPE_AS4_PATH = 17 # RFC 4893 +BGP_ATTR_TYPE_AS4_AGGREGATOR = 18 # RFC 4893 + +BGP_ATTR_ORIGIN_IGP = 0x00 +BGP_ATTR_ORIGIN_EGP = 0x01 +BGP_ATTR_ORIGIN_INCOMPLETE = 0x02 + +AS_TRANS = 23456 # RFC 4893 + +# Well known commmunities (RFC 1997) +BGP_COMMUNITY_NO_EXPORT = 0xffffff01 +BGP_COMMUNITY_NO_ADVERTISE = 0xffffff02 +BGP_COMMUNITY_NO_EXPORT_SUBCONFED = 0xffffff03 + +# RFC 4360 +# The low-order octet of Type field (subtype) +BGP_EXTENDED_COMMUNITY_ROUTE_TARGET = 0x02 +BGP_EXTENDED_COMMUNITY_ROUTE_ORIGIN = 0x03 + # NOTIFICATION Error Code and SubCode # Note: 0 is a valid SubCode. (Unspecific) @@ -63,7 +109,7 @@ BGP_ERROR_SUB_UNSUPPORTED_VERSION_NUMBER = 1 # Data: 2 octet version number BGP_ERROR_SUB_BAD_PEER_AS = 2 BGP_ERROR_SUB_BAD_BGP_IDENTIFIER = 3 BGP_ERROR_SUB_UNSUPPORTED_OPTIONAL_PARAMETER = 4 -# 5 is deprecated RFC 1771 Authentication Failure +BGP_ERROR_SUB_AUTHENTICATION_FAILURE = 5 # deprecated RFC 1771 BGP_ERROR_SUB_UNACCEPTABLE_HOLD_TIME = 6 # NOTIFICATION Error Subcode for BGP_ERROR_UPDATE_MESSAGE_ERROR @@ -73,12 +119,18 @@ BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE = 3 # Data: ditto BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR = 4 # Data: the attr (type, len, value) BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR = 5 # Data: ditto BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE = 6 # Data: ditto -# 7 is deprecated RFC 1771 AS Routing Loop +BGP_ERROR_SUB_ROUTING_LOOP = 7 # deprecated RFC 1771 AS Routing Loop BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE = 8 # Data: ditto BGP_ERROR_SUB_OPTIONAL_ATTRIBUTE_ERROR = 9 # Data: ditto BGP_ERROR_SUB_INVALID_NETWORK_FIELD = 10 BGP_ERROR_SUB_MALFORMED_AS_PATH = 11 +# NOTIFICATION Error Subcode for BGP_ERROR_HOLD_TIMER_EXPIRED +BGP_ERROR_SUB_HOLD_TIMER_EXPIRED = 1 + +# NOTIFICATION Error Subcode for BGP_ERROR_FSM_ERROR +BGP_ERROR_SUB_FSM_ERROR = 1 + # NOTIFICATION Error Subcode for BGP_ERROR_CEASE (RFC 4486) BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED = 1 # Data: optional BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN = 2 @@ -89,48 +141,369 @@ BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE = 6 BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION = 7 BGP_ERROR_SUB_OUT_OF_RESOURCES = 8 -_VERSION = 4 -_MARKER = 16 * '\xff' -BGP_OPT_CAPABILITY = 2 # RFC 5492 +class BgpExc(Exception): + """Base bgp exception.""" -BGP_CAP_MULTIPROTOCOL = 1 # RFC 4760 -BGP_CAP_ROUTE_REFRESH = 2 # RFC 2918 -BGP_CAP_CARRYING_LABEL_INFO = 4 # RFC 3107 -BGP_CAP_FOUR_OCTET_AS_NUMBER = 65 # RFC 4893 -BGP_CAP_ENHANCED_ROUTE_REFRESH = 70 # https://tools.ietf.org/html/\ - # draft-ietf-idr-bgp-enhanced-route-refresh-05 + CODE = 0 + """BGP error code.""" -BGP_ATTR_FLAG_OPTIONAL = 1 << 7 -BGP_ATTR_FLAG_TRANSITIVE = 1 << 6 -BGP_ATTR_FLAG_PARTIAL = 1 << 5 -BGP_ATTR_FLAG_EXTENDED_LENGTH = 1 << 4 + SUB_CODE = 0 + """BGP error sub-code.""" -BGP_ATTR_TYPE_ORIGIN = 1 # 0,1,2 (1 byte) -BGP_ATTR_TYPE_AS_PATH = 2 # a list of AS_SET/AS_SEQUENCE eg. {1 2 3} 4 5 -BGP_ATTR_TYPE_NEXT_HOP = 3 # an IPv4 address -BGP_ATTR_TYPE_MULTI_EXIT_DISC = 4 # uint32 metric -BGP_ATTR_TYPE_LOCAL_PREF = 5 # uint32 -BGP_ATTR_TYPE_ATOMIC_AGGREGATE = 6 # 0 bytes -BGP_ATTR_TYPE_AGGREGATOR = 7 # AS number and IPv4 address -BGP_ATTR_TYPE_COMMUNITIES = 8 # RFC 1997 -BGP_ATTR_TYPE_MP_REACH_NLRI = 14 # RFC 4760 -BGP_ATTR_TYPE_MP_UNREACH_NLRI = 15 # RFC 4760 -BGP_ATTR_TYPE_EXTENDED_COMMUNITIES = 16 # RFC 4360 -BGP_ATTR_TYPE_AS4_PATH = 17 # RFC 4893 -BGP_ATTR_TYPE_AS4_AGGREGATOR = 18 # RFC 4893 + SEND_ERROR = True + """Flag if set indicates Notification message should be sent to peer.""" -AS_TRANS = 23456 # RFC 4893 + def __init__(self, data=''): + self.data = data -# Well known commmunities (RFC 1997) -BGP_COMMUNITY_NO_EXPORT = 0xffffff01 -BGP_COMMUNITY_NO_ADVERTISE = 0xffffff02 -BGP_COMMUNITY_NO_EXPORT_SUBCONFED = 0xffffff03 + def __str__(self): + return '<%s %r>' % (self.__class__.__name__, self.data) -# RFC 4360 -# The low-order octet of Type field (subtype) -BGP_EXTENDED_COMMUNITY_ROUTE_TARGET = 0x02 -BGP_EXTENDED_COMMUNITY_ROUTE_ORIGIN = 0x03 + +class BadNotification(BgpExc): + SEND_ERROR = False + +# ============================================================================ +# Message Header Errors +# ============================================================================ + + +class NotSync(BgpExc): + CODE = BGP_ERROR_MESSAGE_HEADER_ERROR + SUB_CODE = BGP_ERROR_SUB_CONNECTION_NOT_SYNCHRONIZED + + +class BadLen(BgpExc): + CODE = BGP_ERROR_MESSAGE_HEADER_ERROR + SUB_CODE = BGP_ERROR_SUB_BAD_MESSAGE_LENGTH + + 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 = BGP_ERROR_MESSAGE_HEADER_ERROR + SUB_CODE = BGP_ERROR_SUB_BAD_MESSAGE_TYPE + + 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 = BGP_ERROR_OPEN_MESSAGE_ERROR + 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 = BGP_ERROR_OPEN_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_UNSUPPORTED_VERSION_NUMBER + + 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 = BGP_ERROR_OPEN_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_BAD_PEER_AS + + +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 = BGP_ERROR_OPEN_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_BAD_BGP_IDENTIFIER + + +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 = BGP_ERROR_OPEN_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_UNSUPPORTED_OPTIONAL_PARAMETER + + +class AuthFailure(BgpExc): + CODE = BGP_ERROR_OPEN_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_AUTHENTICATION_FAILURE + + +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 = BGP_ERROR_OPEN_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_UNACCEPTABLE_HOLD_TIME + +# ============================================================================ +# 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 = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST + + +class UnRegWellKnowAttr(BgpExc): + CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE + + +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 = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE + + 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 = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR + + +class AttrLenError(BgpExc): + CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR + + +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 = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE + + +class RoutingLoop(BgpExc): + CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_ROUTING_LOOP + + +class InvalidNextHop(BgpExc): + CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE + + +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 = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_OPTIONAL_ATTRIBUTE_ERROR + + +class InvalidNetworkField(BgpExc): + CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_INVALID_NETWORK_FIELD + + +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 = BGP_ERROR_UPDATE_MESSAGE_ERROR + SUB_CODE = BGP_ERROR_SUB_MALFORMED_AS_PATH + + +# ============================================================================ +# 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 = BGP_ERROR_HOLD_TIMER_EXPIRED + SUB_CODE = BGP_ERROR_SUB_HOLD_TIMER_EXPIRED + +# ============================================================================ +# 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 = BGP_ERROR_FSM_ERROR + SUB_CODE = BGP_ERROR_SUB_FSM_ERROR + + +# ============================================================================ +# Cease Errors +# ============================================================================ + +class MaxPrefixReached(BgpExc): + CODE = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED + + +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 = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN + + +class PeerDeConfig(BgpExc): + CODE = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_PEER_DECONFIGURED + + +class AdminReset(BgpExc): + CODE = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_ADMINISTRATIVE_RESET + + +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 = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_CONNECTION_RESET + + +class OtherConfChange(BgpExc): + CODE = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE + + +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 = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION + + +class OutOfResource(BgpExc): + CODE = BGP_ERROR_CEASE + SUB_CODE = BGP_ERROR_SUB_OUT_OF_RESOURCES + + +class RouteFamily(StringifyMixin): + def __init__(self, afi, safi): + self.afi = afi + self.safi = safi + + def __cmp__(self, other): + return cmp((other.afi, other.safi), (self.afi, self.safi)) + +# Route Family Singleton +RF_IPv4_UC = RouteFamily(afi.IP, safi.UNICAST) +RF_IPv6_UC = RouteFamily(afi.IP6, safi.UNICAST) +RF_IPv4_VPN = RouteFamily(afi.IP, safi.MPLS_VPN) +RF_IPv6_VPN = RouteFamily(afi.IP6, safi.MPLS_VPN) +RF_RTC_UC = RouteFamily(afi.IP, safi.ROUTE_TARGET_CONSTRTAINS) def pad(bin, len_): @@ -288,6 +661,23 @@ class _IPAddrPrefix(_AddrPrefix): return (addrconv.ipv4.bin_to_text(pad(addr, 4)),) +class _IP6AddrPrefix(_AddrPrefix): + _TYPE = { + 'ascii': [ + 'addr' + ] + } + + @staticmethod + def _prefix_to_bin(addr): + (addr,) = addr + return addrconv.ipv6.text_to_bin(addr) + + @staticmethod + def _prefix_from_bin(addr): + return (addrconv.ipv6.bin_to_text(pad(addr, 16)),) + + class _VPNAddrPrefix(_AddrPrefix): _RD_PACK_STR = '!Q' @@ -321,18 +711,125 @@ class _VPNAddrPrefix(_AddrPrefix): return (rd,) + super(_VPNAddrPrefix, cls)._prefix_from_bin(binrest) +class IPAddrPrefix(_UnlabelledAddrPrefix, _IPAddrPrefix): + ROUTE_FAMILY = RF_IPv4_UC + + @property + def prefix(self): + return self.addr + + +class IP6AddrPrefix(_UnlabelledAddrPrefix, _IP6AddrPrefix): + ROUTE_FAMILY = RF_IPv6_UC + + @property + def prefix(self): + return self.addr + + class LabelledVPNIPAddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix, _IPAddrPrefix): - pass + ROUTE_FAMILY = RF_IPv4_VPN -class IPAddrPrefix(_UnlabelledAddrPrefix, _IPAddrPrefix): - pass +class LabelledVPNIP6AddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix, + _IP6AddrPrefix): + ROUTE_FAMILY = RF_IPv6_VPN +class RouteTargetMembershipNLRI(StringifyMixin): + """Route Target Membership NLRI. + + Route Target membership NLRI is advertised in BGP UPDATE messages using + the MP_REACH_NLRI and MP_UNREACH_NLRI attributes. + """ + + ROUTE_FAMILY = RF_RTC_UC + DEFAULT_AS = '0:0' + DEFAULT_RT = '0:0' + + def __init__(self, origin_as, route_target): + # 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 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 __cmp__(self, other): + return cmp( + (self._origin_as, self._route_target), + (other.origin_as, other.route_target), + ) + + @classmethod + def parser(cls, buf): + idx = 0 + + # Extract origin AS. + origin_as, = struct.unpack_from('!I', buf, idx) + idx += 4 + + # Extract route target. + route_target = '' + etype, esubtype, payload = struct.unpack_from('BB6s', buf, 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 serialize(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 + +_addr_class_key = lambda x: (x.afi, x.safi) + _ADDR_CLASSES = { - (afi.IP, safi.UNICAST): IPAddrPrefix, - (afi.IP, safi.MPLS_VPN): LabelledVPNIPAddrPrefix, + _addr_class_key(RF_IPv4_UC): IPAddrPrefix, + _addr_class_key(RF_IPv6_UC): IP6AddrPrefix, + _addr_class_key(RF_IPv4_VPN): LabelledVPNIPAddrPrefix, + _addr_class_key(RF_IPv6_VPN): LabelledVPNIP6AddrPrefix, + _addr_class_key(RF_RTC_UC): RouteTargetMembershipNLRI, } @@ -417,7 +914,7 @@ class _OptParam(StringifyMixin, _TypeDisp, _Value): type_ = self._rev_lookup_type(self.__class__) self.type = type_ self.length = length - if not value is None: + if value is not None: self.value = value @classmethod @@ -463,9 +960,9 @@ class _OptParamCapability(_OptParam, _TypeDisp): if cap_code is None: cap_code = self._rev_lookup_type(self.__class__) self.cap_code = cap_code - if not cap_value is None: + if cap_value is not None: self.cap_value = cap_value - if not cap_length is None: + if cap_length is not None: self.cap_length = cap_length @classmethod @@ -516,6 +1013,11 @@ class BGPOptParamCapabilityRouteRefresh(_OptParamEmptyCapability): pass +@_OptParamCapability.register_type(BGP_CAP_ENHANCED_ROUTE_REFRESH) +class BGPOptParamCapabilityEnhancedRouteRefresh(_OptParamEmptyCapability): + pass + + @_OptParamCapability.register_type(BGP_CAP_FOUR_OCTET_AS_NUMBER) class BGPOptParamCapabilityFourOctetAsNumber(_OptParamCapability): _CAP_PACK_STR = '!I' @@ -586,7 +1088,7 @@ class _PathAttribute(StringifyMixin, _TypeDisp, _Value): self.flags = flags self.type = type_ self.length = length - if not value is None: + if value is not None: self.value = value @classmethod @@ -607,7 +1109,7 @@ class _PathAttribute(StringifyMixin, _TypeDisp, _Value): def serialize(self): # fixup - if not self._ATTR_FLAGS is None: + if self._ATTR_FLAGS is not None: self.flags = self.flags \ & ~(BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE) \ | self._ATTR_FLAGS @@ -655,6 +1157,41 @@ class _BGPPathAttributeAsPathCommon(_PathAttribute): _AS_PACK_STR = None _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE + @property + def path_seg_list(self): + return copy.deepcopy(self.value) + + def get_as_path_len(self): + count = 0 + for seg in self.value: + 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.value: + 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.value 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 + @classmethod def parse_value(cls, buf): result = [] @@ -808,6 +1345,12 @@ class BGPPathAttributeCommunities(_PathAttribute): _VALUE_PACK_STR = '!I' _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE + # 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, communities, flags=0, type_=None, length=None): super(BGPPathAttributeCommunities, self).__init__(flags=flags, @@ -836,6 +1379,36 @@ class BGPPathAttributeCommunities(_PathAttribute): buf += bincomm return buf + @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.communities: + if comm_attr == attr: + return True + + return False + # Extended Communities # RFC 4360 @@ -1292,6 +1865,8 @@ class BGPUpdate(BGPMessage): ========================== =============================================== """ + _MIN_LEN = BGPMessage._HDR_LEN + def __init__(self, type_=BGP_MSG_UPDATE, withdrawn_routes_len=None, withdrawn_routes=[], @@ -1304,8 +1879,18 @@ class BGPUpdate(BGPMessage): self.withdrawn_routes = withdrawn_routes self.total_path_attribute_len = total_path_attribute_len self.path_attributes = path_attributes + self._pathattr_map = {} + for attr in path_attributes: + self._pathattr_map[attr.type] = attr self.nlri = nlri + @property + def pathattr_map(self): + return self._pathattr_map + + def get_path_attr(self, attr_name): + return self._pathattr_map.get(attr_name) + @classmethod def parser(cls, buf): offset = 0 @@ -1422,6 +2007,41 @@ class BGPNotification(BGPMessage): _PACK_STR = '!BB' _MIN_LEN = BGPMessage._HDR_LEN + struct.calcsize(_PACK_STR) + _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, error_code, error_subcode, @@ -1450,6 +2070,10 @@ class BGPNotification(BGPMessage): msg += self.data return msg + @property + def reason(self): + return self._REASONS.get((self.error_code, self.error_subcode)) + @BGPMessage.register_type(BGP_MSG_ROUTE_REFRESH) class BGPRouteRefresh(BGPMessage):