5657 lines
186 KiB
Python
5657 lines
186 KiB
Python
# Copyright (C) 2013,2014 Nippon Telegraph and Telephone Corporation.
|
|
# Copyright (C) 2013,2014 YAMAMOTO Takashi <yamamoto at valinux co jp>
|
|
#
|
|
# 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.
|
|
|
|
"""
|
|
RFC 4271 BGP-4
|
|
"""
|
|
|
|
# todo
|
|
# - notify data
|
|
# - RFC 4364 BGP/MPLS IP Virtual Private Networks (VPNs)
|
|
|
|
import abc
|
|
import base64
|
|
import collections
|
|
import copy
|
|
import functools
|
|
import io
|
|
import itertools
|
|
import math
|
|
import re
|
|
import socket
|
|
import struct
|
|
|
|
import netaddr
|
|
import six
|
|
|
|
from os_ken.lib.stringify import StringifyMixin
|
|
from os_ken.lib.packet import afi as addr_family
|
|
from os_ken.lib.packet import safi as subaddr_family
|
|
from os_ken.lib.packet import packet_base
|
|
from os_ken.lib.packet import stream_parser
|
|
from os_ken.lib.packet import vxlan
|
|
from os_ken.lib.packet import mpls
|
|
from os_ken.lib import addrconv
|
|
from os_ken.lib import type_desc
|
|
from os_ken.lib.type_desc import TypeDisp
|
|
from os_ken.lib import ip
|
|
from os_ken.lib.pack_utils import msg_pack_into
|
|
from os_ken.utils import binary_str
|
|
from os_ken.utils import import_module
|
|
|
|
reduce = six.moves.reduce
|
|
|
|
TCP_SERVER_PORT = 179
|
|
|
|
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 * b'\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_GRACEFUL_RESTART = 64 # RFC 4724
|
|
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_CAP_ROUTE_REFRESH_CISCO = 128 # in cisco routers, there are two\
|
|
# route refresh code: one using the capability code of 128 (old),
|
|
# another using the capability code of 2 (new).
|
|
|
|
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_ORIGINATOR_ID = 9 # RFC 4456
|
|
BGP_ATTR_TYPE_CLUSTER_LIST = 10 # RFC 4456
|
|
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_TYEP_PMSI_TUNNEL_ATTRIBUTE = 22 # RFC 6514
|
|
|
|
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)
|
|
|
|
# NOTIFICATION Error Code RFC 4271 4.5.
|
|
BGP_ERROR_MESSAGE_HEADER_ERROR = 1
|
|
BGP_ERROR_OPEN_MESSAGE_ERROR = 2
|
|
BGP_ERROR_UPDATE_MESSAGE_ERROR = 3
|
|
BGP_ERROR_HOLD_TIMER_EXPIRED = 4
|
|
BGP_ERROR_FSM_ERROR = 5
|
|
BGP_ERROR_CEASE = 6
|
|
|
|
# NOTIFICATION Error Subcode for BGP_ERROR_MESSAGE_HEADER_ERROR
|
|
BGP_ERROR_SUB_CONNECTION_NOT_SYNCHRONIZED = 1
|
|
BGP_ERROR_SUB_BAD_MESSAGE_LENGTH = 2 # Data: the erroneous Length field
|
|
BGP_ERROR_SUB_BAD_MESSAGE_TYPE = 3 # Data: the erroneous Type field
|
|
|
|
# NOTIFICATION Error Subcode for BGP_ERROR_OPEN_MESSAGE_ERROR
|
|
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
|
|
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
|
|
BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST = 1
|
|
BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2 # Data: type of the attr
|
|
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
|
|
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
|
|
BGP_ERROR_SUB_PEER_DECONFIGURED = 3
|
|
BGP_ERROR_SUB_ADMINISTRATIVE_RESET = 4
|
|
BGP_ERROR_SUB_CONNECTION_RESET = 5
|
|
BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE = 6
|
|
BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION = 7
|
|
BGP_ERROR_SUB_OUT_OF_RESOURCES = 8
|
|
|
|
|
|
class _Value(object):
|
|
_VALUE_PACK_STR = None
|
|
_VALUE_FIELDS = ['value']
|
|
|
|
@staticmethod
|
|
def do_init(cls_type, self, kwargs, **extra_kwargs):
|
|
ourfields = {}
|
|
for f in cls_type._VALUE_FIELDS:
|
|
v = kwargs[f]
|
|
del kwargs[f]
|
|
ourfields[f] = v
|
|
kwargs.update(extra_kwargs)
|
|
super(cls_type, self).__init__(**kwargs)
|
|
self.__dict__.update(ourfields)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
values = struct.unpack_from(cls._VALUE_PACK_STR, six.binary_type(buf))
|
|
return dict(zip(cls._VALUE_FIELDS, values))
|
|
|
|
def serialize_value(self):
|
|
args = []
|
|
for f in self._VALUE_FIELDS:
|
|
args.append(getattr(self, f))
|
|
return struct.pack(self._VALUE_PACK_STR, *args)
|
|
|
|
|
|
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=''):
|
|
super(BgpExc, self).__init__()
|
|
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 = 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):
|
|
super(BadLen, self).__init__()
|
|
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 = BGP_ERROR_MESSAGE_HEADER_ERROR
|
|
SUB_CODE = BGP_ERROR_SUB_BAD_MESSAGE_TYPE
|
|
|
|
def __init__(self, msg_type):
|
|
super(BadMsg, self).__init__()
|
|
self.msg_type = msg_type
|
|
self.data = struct.pack('B', msg_type)
|
|
|
|
def __str__(self):
|
|
return '<BadMsg %d>' % (self.msg_type,)
|
|
|
|
# ============================================================================
|
|
# 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):
|
|
super(UnsupportedVersion, self).__init__()
|
|
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):
|
|
super(MissingWellKnown, self).__init__()
|
|
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
|
|
|
|
|
|
@functools.total_ordering
|
|
class RouteFamily(StringifyMixin):
|
|
def __init__(self, afi, safi):
|
|
self.afi = afi
|
|
self.safi = safi
|
|
|
|
def __lt__(self, other):
|
|
return (self.afi, self.safi) < (other.afi, other.safi)
|
|
|
|
def __eq__(self, other):
|
|
return (self.afi, self.safi) == (other.afi, other.safi)
|
|
|
|
def __hash__(self):
|
|
return hash((self.afi, self.safi))
|
|
|
|
|
|
# Route Family Singleton
|
|
RF_IPv4_UC = RouteFamily(addr_family.IP, subaddr_family.UNICAST)
|
|
RF_IPv6_UC = RouteFamily(addr_family.IP6, subaddr_family.UNICAST)
|
|
RF_IPv4_VPN = RouteFamily(addr_family.IP, subaddr_family.MPLS_VPN)
|
|
RF_IPv6_VPN = RouteFamily(addr_family.IP6, subaddr_family.MPLS_VPN)
|
|
RF_IPv4_MPLS = RouteFamily(addr_family.IP, subaddr_family.MPLS_LABEL)
|
|
RF_IPv6_MPLS = RouteFamily(addr_family.IP6, subaddr_family.MPLS_LABEL)
|
|
RF_L2_EVPN = RouteFamily(addr_family.L2VPN, subaddr_family.EVPN)
|
|
RF_IPv4_FLOWSPEC = RouteFamily(addr_family.IP, subaddr_family.IP_FLOWSPEC)
|
|
RF_IPv6_FLOWSPEC = RouteFamily(addr_family.IP6, subaddr_family.IP_FLOWSPEC)
|
|
RF_VPNv4_FLOWSPEC = RouteFamily(addr_family.IP, subaddr_family.VPN_FLOWSPEC)
|
|
RF_VPNv6_FLOWSPEC = RouteFamily(addr_family.IP6, subaddr_family.VPN_FLOWSPEC)
|
|
RF_L2VPN_FLOWSPEC = RouteFamily(
|
|
addr_family.L2VPN, subaddr_family.VPN_FLOWSPEC)
|
|
RF_RTC_UC = RouteFamily(addr_family.IP,
|
|
subaddr_family.ROUTE_TARGET_CONSTRAINTS)
|
|
|
|
_rf_map = {
|
|
(addr_family.IP, subaddr_family.UNICAST): RF_IPv4_UC,
|
|
(addr_family.IP6, subaddr_family.UNICAST): RF_IPv6_UC,
|
|
(addr_family.IP, subaddr_family.MPLS_VPN): RF_IPv4_VPN,
|
|
(addr_family.IP6, subaddr_family.MPLS_VPN): RF_IPv6_VPN,
|
|
(addr_family.IP, subaddr_family.MPLS_LABEL): RF_IPv4_MPLS,
|
|
(addr_family.IP6, subaddr_family.MPLS_LABEL): RF_IPv6_MPLS,
|
|
(addr_family.L2VPN, subaddr_family.EVPN): RF_L2_EVPN,
|
|
(addr_family.IP, subaddr_family.IP_FLOWSPEC): RF_IPv4_FLOWSPEC,
|
|
(addr_family.IP6, subaddr_family.IP_FLOWSPEC): RF_IPv6_FLOWSPEC,
|
|
(addr_family.IP, subaddr_family.VPN_FLOWSPEC): RF_VPNv4_FLOWSPEC,
|
|
(addr_family.IP6, subaddr_family.VPN_FLOWSPEC): RF_VPNv6_FLOWSPEC,
|
|
(addr_family.L2VPN, subaddr_family.VPN_FLOWSPEC): RF_L2VPN_FLOWSPEC,
|
|
(addr_family.IP, subaddr_family.ROUTE_TARGET_CONSTRAINTS): RF_RTC_UC
|
|
}
|
|
|
|
|
|
def get_rf(afi, safi):
|
|
return _rf_map[(afi, safi)]
|
|
|
|
|
|
def pad(binary, len_):
|
|
assert len(binary) <= len_
|
|
return binary + b'\0' * (len_ - len(binary))
|
|
|
|
|
|
class _RouteDistinguisher(StringifyMixin, TypeDisp, _Value):
|
|
_PACK_STR = '!H'
|
|
TWO_OCTET_AS = 0
|
|
IPV4_ADDRESS = 1
|
|
FOUR_OCTET_AS = 2
|
|
|
|
def __init__(self, admin=0, assigned=0, type_=None):
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
self.type = type_
|
|
self.admin = admin
|
|
self.assigned = assigned
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
assert len(buf) == 8
|
|
(type_,) = struct.unpack_from(cls._PACK_STR, six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
subcls = cls._lookup_type(type_)
|
|
return subcls(**subcls.parse_value(rest))
|
|
|
|
@classmethod
|
|
def from_str(cls, str_):
|
|
assert isinstance(str_, str)
|
|
|
|
first, second = str_.split(':')
|
|
if '.' in first:
|
|
type_ = cls.IPV4_ADDRESS
|
|
elif int(first) > (1 << 16):
|
|
type_ = cls.FOUR_OCTET_AS
|
|
first = int(first)
|
|
else:
|
|
type_ = cls.TWO_OCTET_AS
|
|
first = int(first)
|
|
subcls = cls._lookup_type(type_)
|
|
return subcls(admin=first, assigned=int(second))
|
|
|
|
def serialize(self):
|
|
value = self.serialize_value()
|
|
buf = bytearray()
|
|
msg_pack_into(self._PACK_STR, buf, 0, self.type)
|
|
return six.binary_type(buf + value)
|
|
|
|
@property
|
|
def formatted_str(self):
|
|
return "%s:%s" % (self.admin, self.assigned)
|
|
|
|
|
|
@_RouteDistinguisher.register_type(_RouteDistinguisher.TWO_OCTET_AS)
|
|
class BGPTwoOctetAsRD(_RouteDistinguisher):
|
|
_VALUE_PACK_STR = '!HI'
|
|
_VALUE_FIELDS = ['admin', 'assigned']
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPTwoOctetAsRD, self).__init__()
|
|
self.do_init(BGPTwoOctetAsRD, self, kwargs)
|
|
|
|
|
|
@_RouteDistinguisher.register_type(_RouteDistinguisher.IPV4_ADDRESS)
|
|
class BGPIPv4AddressRD(_RouteDistinguisher):
|
|
_VALUE_PACK_STR = '!4sH'
|
|
_VALUE_FIELDS = ['admin', 'assigned']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'admin'
|
|
]
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPIPv4AddressRD, self).__init__()
|
|
self.do_init(BGPIPv4AddressRD, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
d_ = super(BGPIPv4AddressRD, cls).parse_value(buf)
|
|
d_['admin'] = addrconv.ipv4.bin_to_text(d_['admin'])
|
|
return d_
|
|
|
|
def serialize_value(self):
|
|
args = []
|
|
for f in self._VALUE_FIELDS:
|
|
v = getattr(self, f)
|
|
if f == 'admin':
|
|
v = bytes(addrconv.ipv4.text_to_bin(v))
|
|
args.append(v)
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args)
|
|
return buf
|
|
|
|
|
|
@_RouteDistinguisher.register_type(_RouteDistinguisher.FOUR_OCTET_AS)
|
|
class BGPFourOctetAsRD(_RouteDistinguisher):
|
|
_VALUE_PACK_STR = '!IH'
|
|
_VALUE_FIELDS = ['admin', 'assigned']
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFourOctetAsRD, self).__init__()
|
|
self.do_init(BGPFourOctetAsRD, self, kwargs)
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class _AddrPrefix(StringifyMixin):
|
|
_PACK_STR = '!B' # length
|
|
|
|
def __init__(self, length, addr, prefixes=None, **kwargs):
|
|
# length is on-wire bit length of prefixes+addr.
|
|
assert prefixes != ()
|
|
if isinstance(addr, tuple):
|
|
# for _AddrPrefix.parser
|
|
# also for _VPNAddrPrefix.__init__ etc
|
|
(addr,) = addr
|
|
self.length = length
|
|
if prefixes:
|
|
addr = prefixes + (addr,)
|
|
self.addr = addr
|
|
|
|
@classmethod
|
|
@abc.abstractmethod
|
|
def _to_bin(cls, addr):
|
|
pass
|
|
|
|
@classmethod
|
|
@abc.abstractmethod
|
|
def _from_bin(cls, addr):
|
|
pass
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(length, ) = struct.unpack_from(cls._PACK_STR, six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
byte_length = (length + 7) // 8
|
|
addr = cls._from_bin(rest[:byte_length])
|
|
rest = rest[byte_length:]
|
|
return cls(length=length, addr=addr), rest
|
|
|
|
def serialize(self):
|
|
# fixup
|
|
byte_length = (self.length + 7) // 8
|
|
bin_addr = self._to_bin(self.addr)
|
|
if (self.length % 8) == 0:
|
|
bin_addr = bin_addr[:byte_length]
|
|
else:
|
|
# clear trailing bits in the last octet.
|
|
# rfc doesn't require this.
|
|
mask = 0xff00 >> (self.length % 8)
|
|
last_byte = six.int2byte(
|
|
six.indexbytes(bin_addr, byte_length - 1) & mask)
|
|
bin_addr = bin_addr[:byte_length - 1] + last_byte
|
|
self.addr = self._from_bin(bin_addr)
|
|
|
|
buf = bytearray()
|
|
msg_pack_into(self._PACK_STR, buf, 0, self.length)
|
|
return buf + bytes(bin_addr)
|
|
|
|
|
|
class _BinAddrPrefix(_AddrPrefix):
|
|
@classmethod
|
|
def _to_bin(cls, addr):
|
|
return addr
|
|
|
|
@classmethod
|
|
def _from_bin(cls, addr):
|
|
return addr
|
|
|
|
|
|
class _LabelledAddrPrefix(_AddrPrefix):
|
|
_LABEL_PACK_STR = '!3B'
|
|
# RFC3107
|
|
# 3. Carrying Label Mapping Information
|
|
# The label information carried (as part of NLRI) in the Withdrawn
|
|
# Routes field should be set to 0x800000. (Of course, terminating the
|
|
# BGP session also withdraws all the previously advertised routes.)
|
|
#
|
|
_WITHDRAW_LABEL = 0x800000
|
|
|
|
def __init__(self, length, addr, labels=None, **kwargs):
|
|
labels = labels if labels else []
|
|
assert isinstance(labels, list)
|
|
is_tuple = isinstance(addr, tuple)
|
|
if is_tuple:
|
|
# for _AddrPrefix.parser
|
|
assert not labels
|
|
labels = addr[0]
|
|
addr = addr[1:]
|
|
else:
|
|
length += struct.calcsize(self._LABEL_PACK_STR) * 8 * len(labels)
|
|
assert length > struct.calcsize(self._LABEL_PACK_STR) * 8 * len(labels)
|
|
prefixes = (labels,)
|
|
super(_LabelledAddrPrefix, self).__init__(prefixes=prefixes,
|
|
length=length,
|
|
addr=addr,
|
|
**kwargs)
|
|
|
|
@classmethod
|
|
def _label_to_bin(cls, label):
|
|
buf = bytearray()
|
|
msg_pack_into(cls._LABEL_PACK_STR, buf, 0,
|
|
(label & 0xff0000) >> 16,
|
|
(label & 0x00ff00) >> 8,
|
|
(label & 0x0000ff) >> 0)
|
|
return six.binary_type(buf)
|
|
|
|
@classmethod
|
|
def _label_from_bin(cls, label):
|
|
(b1, b2, b3) = struct.unpack_from(cls._LABEL_PACK_STR,
|
|
six.binary_type(label))
|
|
rest = label[struct.calcsize(cls._LABEL_PACK_STR):]
|
|
return (b1 << 16) | (b2 << 8) | b3, rest
|
|
|
|
@classmethod
|
|
def _to_bin(cls, addr):
|
|
labels = addr[0]
|
|
rest = addr[1:]
|
|
labels = [x << 4 for x in labels]
|
|
if labels and labels[-1] != cls._WITHDRAW_LABEL:
|
|
labels[-1] |= 1 # bottom of stack
|
|
bin_labels = list(cls._label_to_bin(l) for l in labels)
|
|
return bytes(reduce(lambda x, y: x + y, bin_labels,
|
|
bytearray()) + cls._prefix_to_bin(rest))
|
|
|
|
@classmethod
|
|
def _has_no_label(cls, bin_):
|
|
try:
|
|
length = len(bin_)
|
|
labels = []
|
|
while True:
|
|
(label, bin_) = cls._label_from_bin(bin_)
|
|
labels.append(label)
|
|
if label & 1 or label == cls._WITHDRAW_LABEL:
|
|
break
|
|
assert length > struct.calcsize(cls._LABEL_PACK_STR) * len(labels)
|
|
except struct.error:
|
|
return True
|
|
except AssertionError:
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def _from_bin(cls, addr):
|
|
rest = addr
|
|
labels = []
|
|
|
|
if cls._has_no_label(rest):
|
|
return ([],) + cls._prefix_from_bin(rest)
|
|
|
|
while True:
|
|
(label, rest) = cls._label_from_bin(rest)
|
|
labels.append(label >> 4)
|
|
if label & 1 or label == cls._WITHDRAW_LABEL:
|
|
break
|
|
return (labels,) + cls._prefix_from_bin(rest)
|
|
|
|
|
|
class _UnlabelledAddrPrefix(_AddrPrefix):
|
|
@classmethod
|
|
def _to_bin(cls, addr):
|
|
return cls._prefix_to_bin((addr,))
|
|
|
|
@classmethod
|
|
def _from_bin(cls, binaddr):
|
|
(addr,) = cls._prefix_from_bin(binaddr)
|
|
return addr
|
|
|
|
|
|
class _IPAddrPrefix(_AddrPrefix):
|
|
@staticmethod
|
|
def _prefix_to_bin(addr):
|
|
(addr,) = addr
|
|
return addrconv.ipv4.text_to_bin(addr)
|
|
|
|
@staticmethod
|
|
def _prefix_from_bin(addr):
|
|
return addrconv.ipv4.bin_to_text(pad(addr, 4)),
|
|
|
|
|
|
class _IP6AddrPrefix(_AddrPrefix):
|
|
@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'
|
|
|
|
def __init__(self, length, addr, prefixes=(), route_dist=0):
|
|
if isinstance(addr, tuple):
|
|
# for _AddrPrefix.parser
|
|
assert not route_dist
|
|
assert length > struct.calcsize(self._RD_PACK_STR) * 8
|
|
route_dist = addr[0]
|
|
addr = addr[1:]
|
|
else:
|
|
length += struct.calcsize(self._RD_PACK_STR) * 8
|
|
|
|
if isinstance(route_dist, str):
|
|
route_dist = _RouteDistinguisher.from_str(route_dist)
|
|
|
|
prefixes = prefixes + (route_dist,)
|
|
super(_VPNAddrPrefix, self).__init__(prefixes=prefixes,
|
|
length=length,
|
|
addr=addr)
|
|
|
|
@classmethod
|
|
def _prefix_to_bin(cls, addr):
|
|
rd = addr[0]
|
|
rest = addr[1:]
|
|
binrd = rd.serialize()
|
|
return binrd + super(_VPNAddrPrefix, cls)._prefix_to_bin(rest)
|
|
|
|
@classmethod
|
|
def _prefix_from_bin(cls, binaddr):
|
|
binrd = binaddr[:8]
|
|
binrest = binaddr[8:]
|
|
rd = _RouteDistinguisher.parser(binrd)
|
|
return (rd,) + super(_VPNAddrPrefix, cls)._prefix_from_bin(binrest)
|
|
|
|
|
|
class IPAddrPrefix(_UnlabelledAddrPrefix, _IPAddrPrefix):
|
|
ROUTE_FAMILY = RF_IPv4_UC
|
|
_TYPE = {
|
|
'ascii': [
|
|
'addr'
|
|
]
|
|
}
|
|
|
|
@property
|
|
def prefix(self):
|
|
return self.addr + '/{0}'.format(self.length)
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return self.prefix
|
|
|
|
|
|
class IP6AddrPrefix(_UnlabelledAddrPrefix, _IP6AddrPrefix):
|
|
ROUTE_FAMILY = RF_IPv6_UC
|
|
_TYPE = {
|
|
'ascii': [
|
|
'addr'
|
|
]
|
|
}
|
|
|
|
@property
|
|
def prefix(self):
|
|
return self.addr + '/{0}'.format(self.length)
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return self.prefix
|
|
|
|
|
|
class LabelledIPAddrPrefix(_LabelledAddrPrefix, _IPAddrPrefix):
|
|
ROUTE_FAMILY = RF_IPv4_MPLS
|
|
|
|
|
|
class LabelledIP6AddrPrefix(_LabelledAddrPrefix, _IP6AddrPrefix):
|
|
ROUTE_FAMILY = RF_IPv6_MPLS
|
|
|
|
|
|
class LabelledVPNIPAddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix,
|
|
_IPAddrPrefix):
|
|
ROUTE_FAMILY = RF_IPv4_VPN
|
|
|
|
@property
|
|
def prefix(self):
|
|
masklen = self.length - struct.calcsize(self._RD_PACK_STR) * 8 \
|
|
- struct.calcsize(self._LABEL_PACK_STR) * 8 * len(self.addr[:-2])
|
|
return self.addr[-1] + '/{0}'.format(masklen)
|
|
|
|
@property
|
|
def route_dist(self):
|
|
return self.addr[-2].formatted_str
|
|
|
|
@property
|
|
def label_list(self):
|
|
return self.addr[0]
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return "%s:%s" % (self.route_dist, self.prefix)
|
|
|
|
|
|
class LabelledVPNIP6AddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix,
|
|
_IP6AddrPrefix):
|
|
ROUTE_FAMILY = RF_IPv6_VPN
|
|
|
|
@property
|
|
def prefix(self):
|
|
masklen = self.length - struct.calcsize(self._RD_PACK_STR) * 8 \
|
|
- struct.calcsize(self._LABEL_PACK_STR) * 8 * len(self.addr[:-2])
|
|
return self.addr[-1] + '/{0}'.format(masklen)
|
|
|
|
@property
|
|
def route_dist(self):
|
|
return self.addr[-2].formatted_str
|
|
|
|
@property
|
|
def label_list(self):
|
|
return self.addr[0]
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return "%s:%s" % (self.route_dist, self.prefix)
|
|
|
|
|
|
class EvpnEsi(StringifyMixin, TypeDisp, _Value):
|
|
"""
|
|
Ethernet Segment Identifier
|
|
|
|
The supported ESI Types:
|
|
|
|
- ``EvpnEsi.ARBITRARY`` indicates EvpnArbitraryEsi.
|
|
|
|
- ``EvpnEsi.LACP`` indicates EvpnLACPEsi.
|
|
|
|
- ``EvpnEsi.L2_BRIDGE`` indicates EvpnL2BridgeEsi.
|
|
|
|
- ``EvpnEsi.MAC_BASED`` indicates EvpnMacBasedEsi.
|
|
|
|
- ``EvpnEsi.ROUTER_ID`` indicates EvpnRouterIDEsi.
|
|
|
|
- ``EvpnEsi.AS_BASED`` indicates EvpnASBasedEsi.
|
|
"""
|
|
_PACK_STR = "!B" # ESI Type
|
|
_ESI_LEN = 10
|
|
|
|
ARBITRARY = 0x00
|
|
LACP = 0x01
|
|
L2_BRIDGE = 0x02
|
|
MAC_BASED = 0x03
|
|
ROUTER_ID = 0x04
|
|
AS_BASED = 0x05
|
|
MAX = 0xff # Reserved
|
|
|
|
_TYPE_NAME = None # must be defined in subclass
|
|
|
|
def __init__(self, type_=None):
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
self.type = type_
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(esi_type,) = struct.unpack_from(
|
|
cls._PACK_STR, six.binary_type(buf))
|
|
subcls = cls._lookup_type(esi_type)
|
|
return subcls(**subcls.parse_value(buf[1:cls._ESI_LEN]))
|
|
|
|
def serialize(self):
|
|
buf = bytearray()
|
|
msg_pack_into(EvpnEsi._PACK_STR, buf, 0, self.type)
|
|
return six.binary_type(buf + self.serialize_value())
|
|
|
|
@property
|
|
def formatted_str(self):
|
|
return '%s(%s)' % (
|
|
self._TYPE_NAME,
|
|
','.join(str(getattr(self, v)) for v in self._VALUE_FIELDS))
|
|
|
|
|
|
@EvpnEsi.register_unknown_type()
|
|
class EvpnUnknownEsi(EvpnEsi):
|
|
"""
|
|
ESI value for unknown type
|
|
"""
|
|
_TYPE_NAME = 'unknown'
|
|
_VALUE_PACK_STR = '!9s'
|
|
_VALUE_FIELDS = ['value']
|
|
|
|
def __init__(self, value, type_=None):
|
|
super(EvpnUnknownEsi, self).__init__(type_)
|
|
self.value = value
|
|
|
|
@property
|
|
def formatted_str(self):
|
|
return '%s(%s)' % (self._TYPE_NAME, binary_str(self.value))
|
|
|
|
|
|
@EvpnEsi.register_type(EvpnEsi.ARBITRARY)
|
|
class EvpnArbitraryEsi(EvpnEsi):
|
|
"""
|
|
Arbitrary 9-octet ESI value
|
|
|
|
This type indicates an arbitrary 9-octet ESI value,
|
|
which is managed and configured by the operator.
|
|
"""
|
|
_TYPE_NAME = 'arbitrary'
|
|
_VALUE_PACK_STR = '!9s'
|
|
_VALUE_FIELDS = ['value']
|
|
|
|
def __init__(self, value, type_=None):
|
|
super(EvpnArbitraryEsi, self).__init__(type_)
|
|
self.value = value
|
|
|
|
@property
|
|
def formatted_str(self):
|
|
return '%s(%s)' % (self._TYPE_NAME, binary_str(self.value))
|
|
|
|
|
|
@EvpnEsi.register_type(EvpnEsi.LACP)
|
|
class EvpnLACPEsi(EvpnEsi):
|
|
"""
|
|
ESI value for LACP
|
|
|
|
When IEEE 802.1AX LACP is used between the PEs and CEs,
|
|
this ESI type indicates an auto-generated ESI value
|
|
determined from LACP.
|
|
"""
|
|
_TYPE_NAME = 'lacp'
|
|
_VALUE_PACK_STR = '!6sHx'
|
|
_VALUE_FIELDS = ['mac_addr', 'port_key']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'mac_addr'
|
|
]
|
|
}
|
|
|
|
def __init__(self, mac_addr, port_key, type_=None):
|
|
super(EvpnLACPEsi, self).__init__(type_)
|
|
self.mac_addr = mac_addr
|
|
self.port_key = port_key
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(mac_addr, port_key) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'mac_addr': addrconv.mac.bin_to_text(mac_addr),
|
|
'port_key': port_key,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(
|
|
self._VALUE_PACK_STR,
|
|
addrconv.mac.text_to_bin(self.mac_addr), self.port_key)
|
|
|
|
|
|
@EvpnEsi.register_type(EvpnEsi.L2_BRIDGE)
|
|
class EvpnL2BridgeEsi(EvpnEsi):
|
|
"""
|
|
ESI value for Layer 2 Bridge
|
|
|
|
This type is used in the case of indirectly connected hosts
|
|
via a bridged LAN between the CEs and the PEs.
|
|
The ESI Value is auto-generated and determined based
|
|
on the Layer 2 bridge protocol.
|
|
"""
|
|
_TYPE_NAME = 'l2_bridge'
|
|
_VALUE_PACK_STR = '!6sHx'
|
|
_VALUE_FIELDS = ['mac_addr', 'priority']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'mac_addr'
|
|
]
|
|
}
|
|
|
|
def __init__(self, mac_addr, priority, type_=None):
|
|
super(EvpnL2BridgeEsi, self).__init__(type_)
|
|
self.mac_addr = mac_addr
|
|
self.priority = priority
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(mac_addr, priority) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'mac_addr': addrconv.mac.bin_to_text(mac_addr),
|
|
'priority': priority,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(
|
|
self._VALUE_PACK_STR,
|
|
addrconv.mac.text_to_bin(self.mac_addr), self.priority)
|
|
|
|
|
|
@EvpnEsi.register_type(EvpnEsi.MAC_BASED)
|
|
class EvpnMacBasedEsi(EvpnEsi):
|
|
"""
|
|
MAC-based ESI Value
|
|
|
|
This type indicates a MAC-based ESI Value that
|
|
can be auto-generated or configured by the operator.
|
|
"""
|
|
_TYPE_NAME = 'mac_based'
|
|
_VALUE_PACK_STR = '!6s3s'
|
|
_VALUE_FIELDS = ['mac_addr', 'local_disc']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'mac_addr'
|
|
]
|
|
}
|
|
|
|
def __init__(self, mac_addr, local_disc, type_=None):
|
|
super(EvpnMacBasedEsi, self).__init__(type_)
|
|
self.mac_addr = mac_addr
|
|
self.local_disc = local_disc
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(mac_addr, local_disc) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'mac_addr': addrconv.mac.bin_to_text(mac_addr),
|
|
'local_disc': type_desc.Int3.to_user(local_disc),
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(
|
|
self._VALUE_PACK_STR,
|
|
addrconv.mac.text_to_bin(self.mac_addr),
|
|
type_desc.Int3.from_user(self.local_disc))
|
|
|
|
|
|
@EvpnEsi.register_type(EvpnEsi.ROUTER_ID)
|
|
class EvpnRouterIDEsi(EvpnEsi):
|
|
"""
|
|
Router-ID ESI Value
|
|
|
|
This type indicates a router-ID ESI Value that
|
|
can be auto-generated or configured by the operator.
|
|
"""
|
|
_TYPE_NAME = 'router_id'
|
|
_VALUE_PACK_STR = '!4sIx'
|
|
_VALUE_FIELDS = ['router_id', 'local_disc']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'router_id'
|
|
]
|
|
}
|
|
|
|
def __init__(self, router_id, local_disc, type_=None):
|
|
super(EvpnRouterIDEsi, self).__init__(type_)
|
|
self.router_id = router_id
|
|
self.local_disc = local_disc
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(router_id, local_disc) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'router_id': addrconv.ipv4.bin_to_text(router_id),
|
|
'local_disc': local_disc,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(
|
|
self._VALUE_PACK_STR,
|
|
addrconv.ipv4.text_to_bin(self.router_id), self.local_disc)
|
|
|
|
|
|
@EvpnEsi.register_type(EvpnEsi.AS_BASED)
|
|
class EvpnASBasedEsi(EvpnEsi):
|
|
"""
|
|
AS based ESI value
|
|
|
|
This type indicates an Autonomous System(AS)-based
|
|
ESI Value that can be auto-generated or configured by
|
|
the operator.
|
|
"""
|
|
_TYPE_NAME = 'as_based'
|
|
_VALUE_PACK_STR = '!IIx'
|
|
_VALUE_FIELDS = ['as_number', 'local_disc']
|
|
|
|
def __init__(self, as_number, local_disc, type_=None):
|
|
super(EvpnASBasedEsi, self).__init__(type_)
|
|
self.as_number = as_number
|
|
self.local_disc = local_disc
|
|
|
|
|
|
class EvpnNLRI(StringifyMixin, TypeDisp):
|
|
"""
|
|
BGP Network Layer Reachability Information (NLRI) for EVPN
|
|
"""
|
|
ROUTE_FAMILY = RF_L2_EVPN
|
|
|
|
# EVPN NLRI:
|
|
# +-----------------------------------+
|
|
# | Route Type (1 octet) |
|
|
# +-----------------------------------+
|
|
# | Length (1 octet) |
|
|
# +-----------------------------------+
|
|
# | Route Type specific (variable) |
|
|
# +-----------------------------------+
|
|
_PACK_STR = "!BB"
|
|
_PACK_STR_SIZE = struct.calcsize(_PACK_STR)
|
|
|
|
ETHERNET_AUTO_DISCOVERY = 0x01
|
|
MAC_IP_ADVERTISEMENT = 0x02
|
|
INCLUSIVE_MULTICAST_ETHERNET_TAG = 0x03
|
|
ETHERNET_SEGMENT = 0x04
|
|
IP_PREFIX_ROUTE = 0x05
|
|
|
|
ROUTE_TYPE_NAME = None # must be defined in subclass
|
|
|
|
# Reserved value for Ethernet Tag ID.
|
|
MAX_ET = 0xFFFFFFFF
|
|
|
|
# Dictionary of ROUTE_TYPE_NAME to subclass.
|
|
# e.g.)
|
|
# _NAMES = {'eth_ad': EvpnEthernetAutoDiscoveryNLRI, ...}
|
|
_NAMES = {}
|
|
|
|
# List of the fields considered to be part of the prefix in the NLRI.
|
|
# This list should be defined in subclasses to format NLRI string
|
|
# representation.
|
|
NLRI_PREFIX_FIELDS = []
|
|
|
|
def __init__(self, type_=None, length=None):
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
self.type = type_
|
|
self.length = length
|
|
self.route_dist = None # should be initialized in subclass
|
|
|
|
@classmethod
|
|
def register_type(cls, type_):
|
|
cls._TYPES = cls._TYPES.copy()
|
|
cls._NAMES = cls._NAMES.copy()
|
|
|
|
def _register_type(subcls):
|
|
cls._TYPES[type_] = subcls
|
|
cls._NAMES[subcls.ROUTE_TYPE_NAME] = subcls
|
|
cls._REV_TYPES = None
|
|
return subcls
|
|
|
|
return _register_type
|
|
|
|
@classmethod
|
|
def _lookup_type_name(cls, type_name):
|
|
try:
|
|
return cls._NAMES[type_name]
|
|
except KeyError:
|
|
return EvpnUnknownNLRI
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(route_type, length) = struct.unpack_from(
|
|
cls._PACK_STR, six.binary_type(buf))
|
|
offset = cls._PACK_STR_SIZE + length
|
|
subcls = cls._lookup_type(route_type)
|
|
values = subcls.parse_value(buf[cls._PACK_STR_SIZE:offset])
|
|
return subcls(type_=route_type, length=length,
|
|
**values), buf[offset:]
|
|
|
|
def serialize_value(self):
|
|
# Overrided in subclass
|
|
return b''
|
|
|
|
def serialize(self):
|
|
value_bin = self.serialize_value()
|
|
# fixup
|
|
self.length = len(value_bin)
|
|
return struct.pack(EvpnNLRI._PACK_STR,
|
|
self.type, self.length) + value_bin
|
|
|
|
@staticmethod
|
|
def _rd_from_bin(buf):
|
|
return _RouteDistinguisher.parser(buf[:8]), buf[8:]
|
|
|
|
@staticmethod
|
|
def _rd_to_bin(rd):
|
|
return six.binary_type(rd.serialize())
|
|
|
|
@staticmethod
|
|
def _esi_from_bin(buf):
|
|
return EvpnEsi.parser(buf[:10]), buf[10:]
|
|
|
|
@staticmethod
|
|
def _esi_to_bin(esi):
|
|
return esi.serialize()
|
|
|
|
@staticmethod
|
|
def _ethernet_tag_id_from_bin(buf):
|
|
return type_desc.Int4.to_user(six.binary_type(buf[:4])), buf[4:]
|
|
|
|
@staticmethod
|
|
def _ethernet_tag_id_to_bin(tag_id):
|
|
return type_desc.Int4.from_user(tag_id)
|
|
|
|
@staticmethod
|
|
def _mac_addr_len_from_bin(buf):
|
|
return type_desc.Int1.to_user(six.binary_type(buf[:1])), buf[1:]
|
|
|
|
@staticmethod
|
|
def _mac_addr_len_to_bin(mac_len):
|
|
return type_desc.Int1.from_user(mac_len)
|
|
|
|
@staticmethod
|
|
def _mac_addr_from_bin(buf, mac_len):
|
|
mac_len //= 8
|
|
return addrconv.mac.bin_to_text(buf[:mac_len]), buf[mac_len:]
|
|
|
|
@staticmethod
|
|
def _mac_addr_to_bin(mac_addr):
|
|
return addrconv.mac.text_to_bin(mac_addr)
|
|
|
|
@staticmethod
|
|
def _ip_addr_len_from_bin(buf):
|
|
return type_desc.Int1.to_user(six.binary_type(buf[:1])), buf[1:]
|
|
|
|
@staticmethod
|
|
def _ip_addr_len_to_bin(ip_len):
|
|
return type_desc.Int1.from_user(ip_len)
|
|
|
|
@staticmethod
|
|
def _ip_addr_from_bin(buf, ip_len):
|
|
return ip.bin_to_text(buf[:ip_len]), buf[ip_len:]
|
|
|
|
@staticmethod
|
|
def _ip_addr_to_bin(ip_addr):
|
|
return ip.text_to_bin(ip_addr)
|
|
|
|
@staticmethod
|
|
def _mpls_label_from_bin(buf):
|
|
mpls_label, is_bos = mpls.label_from_bin(buf)
|
|
rest = buf[3:]
|
|
return mpls_label, rest, is_bos
|
|
|
|
@staticmethod
|
|
def _mpls_label_to_bin(label, is_bos=True):
|
|
return mpls.label_to_bin(label, is_bos=is_bos)
|
|
|
|
@staticmethod
|
|
def _vni_from_bin(buf):
|
|
return vxlan.vni_from_bin(six.binary_type(buf[:3])), buf[3:]
|
|
|
|
@staticmethod
|
|
def _vni_to_bin(vni):
|
|
return vxlan.vni_to_bin(vni)
|
|
|
|
@property
|
|
def prefix(self):
|
|
def _format(i):
|
|
pairs = []
|
|
for k in i.NLRI_PREFIX_FIELDS:
|
|
v = getattr(i, k)
|
|
if k == 'esi':
|
|
pairs.append('%s:%s' % (k, v.formatted_str))
|
|
else:
|
|
pairs.append('%s:%s' % (k, v))
|
|
return ','.join(pairs)
|
|
|
|
return '%s(%s)' % (self.ROUTE_TYPE_NAME, _format(self))
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return '%s:%s' % (self.route_dist, self.prefix)
|
|
|
|
|
|
@EvpnNLRI.register_unknown_type()
|
|
class EvpnUnknownNLRI(EvpnNLRI):
|
|
"""
|
|
Unknown route type specific EVPN NLRI
|
|
"""
|
|
ROUTE_TYPE_NAME = 'unknown'
|
|
NLRI_PREFIX_FIELDS = ['value']
|
|
|
|
def __init__(self, value, type_, length=None):
|
|
super(EvpnUnknownNLRI, self).__init__(type_, length)
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
return {
|
|
'value': buf
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return self.value
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return '%s(%s)' % (self.ROUTE_TYPE_NAME, binary_str(self.value))
|
|
|
|
|
|
@EvpnNLRI.register_type(EvpnNLRI.ETHERNET_AUTO_DISCOVERY)
|
|
class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI):
|
|
"""
|
|
Ethernet A-D route type specific EVPN NLRI
|
|
"""
|
|
ROUTE_TYPE_NAME = 'eth_ad'
|
|
|
|
# +---------------------------------------+
|
|
# | Route Distinguisher (RD) (8 octets) |
|
|
# +---------------------------------------+
|
|
# |Ethernet Segment Identifier (10 octets)|
|
|
# +---------------------------------------+
|
|
# | Ethernet Tag ID (4 octets) |
|
|
# +---------------------------------------+
|
|
# | MPLS Label (3 octets) |
|
|
# +---------------------------------------+
|
|
_PACK_STR = "!8s10sI3s"
|
|
NLRI_PREFIX_FIELDS = ['esi', 'ethernet_tag_id']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'route_dist',
|
|
]
|
|
}
|
|
|
|
def __init__(self, route_dist, esi, ethernet_tag_id,
|
|
mpls_label=None, vni=None, label=None,
|
|
type_=None, length=None):
|
|
super(EvpnEthernetAutoDiscoveryNLRI, self).__init__(type_, length)
|
|
self.route_dist = route_dist
|
|
self.esi = esi
|
|
self.ethernet_tag_id = ethernet_tag_id
|
|
if label:
|
|
# If binary type label field value is specified, stores it
|
|
# and decodes as MPLS label and VNI.
|
|
self._label = label
|
|
self._mpls_label, _, _ = self._mpls_label_from_bin(label)
|
|
self._vni, _ = self._vni_from_bin(label)
|
|
else:
|
|
# If either MPLS label or VNI is specified, stores it
|
|
# and encodes into binary type label field value.
|
|
self._label = self._serialize_label(mpls_label, vni)
|
|
self._mpls_label = mpls_label
|
|
self._vni = vni
|
|
|
|
def _serialize_label(self, mpls_label, vni):
|
|
if mpls_label:
|
|
return self._mpls_label_to_bin(mpls_label, is_bos=True)
|
|
elif vni:
|
|
return self._vni_to_bin(vni)
|
|
else:
|
|
return b'\x00' * 3
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
route_dist, rest = cls._rd_from_bin(buf)
|
|
esi, rest = cls._esi_from_bin(rest)
|
|
ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
|
|
|
|
return {
|
|
'route_dist': route_dist.formatted_str,
|
|
'esi': esi,
|
|
'ethernet_tag_id': ethernet_tag_id,
|
|
'label': rest,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
route_dist = _RouteDistinguisher.from_str(self.route_dist)
|
|
return struct.pack(
|
|
self._PACK_STR, route_dist.serialize(), self.esi.serialize(),
|
|
self.ethernet_tag_id, self._label)
|
|
|
|
@property
|
|
def mpls_label(self):
|
|
return self._mpls_label
|
|
|
|
@mpls_label.setter
|
|
def mpls_label(self, mpls_label):
|
|
self._label = self._mpls_label_to_bin(mpls_label, is_bos=True)
|
|
self._mpls_label = mpls_label
|
|
self._vni = None # disables VNI
|
|
|
|
@property
|
|
def vni(self):
|
|
return self._vni
|
|
|
|
@vni.setter
|
|
def vni(self, vni):
|
|
self._label = self._vni_to_bin(vni)
|
|
self._mpls_label = None # disables MPLS label
|
|
self._vni = vni
|
|
|
|
@property
|
|
def label_list(self):
|
|
return [self.mpls_label]
|
|
|
|
|
|
@EvpnNLRI.register_type(EvpnNLRI.MAC_IP_ADVERTISEMENT)
|
|
class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
|
|
"""
|
|
MAC/IP Advertisement route type specific EVPN NLRI
|
|
"""
|
|
ROUTE_TYPE_NAME = 'mac_ip_adv'
|
|
|
|
# +---------------------------------------+
|
|
# | RD (8 octets) |
|
|
# +---------------------------------------+
|
|
# |Ethernet Segment Identifier (10 octets)|
|
|
# +---------------------------------------+
|
|
# | Ethernet Tag ID (4 octets) |
|
|
# +---------------------------------------+
|
|
# | MAC Address Length (1 octet) |
|
|
# +---------------------------------------+
|
|
# | MAC Address (6 octets) |
|
|
# +---------------------------------------+
|
|
# | IP Address Length (1 octet) |
|
|
# +---------------------------------------+
|
|
# | IP Address (0, 4, or 16 octets) |
|
|
# +---------------------------------------+
|
|
# | MPLS Label1 (3 octets) |
|
|
# +---------------------------------------+
|
|
# | MPLS Label2 (0 or 3 octets) |
|
|
# +---------------------------------------+
|
|
_PACK_STR = "!8s10sIB6sB%ds%ds"
|
|
# Note: mac_addr_len and ip_addr_len are omitted for readability.
|
|
NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'mac_addr', 'ip_addr']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'route_dist',
|
|
'mac_addr',
|
|
'ip_addr',
|
|
]
|
|
}
|
|
|
|
def __init__(self, route_dist, ethernet_tag_id, mac_addr, ip_addr,
|
|
esi=None, mpls_labels=None, vni=None, labels=None,
|
|
mac_addr_len=None, ip_addr_len=None,
|
|
type_=None, length=None):
|
|
super(EvpnMacIPAdvertisementNLRI, self).__init__(type_, length)
|
|
self.route_dist = route_dist
|
|
self.esi = esi
|
|
self.ethernet_tag_id = ethernet_tag_id
|
|
self.mac_addr_len = mac_addr_len
|
|
self.mac_addr = mac_addr
|
|
self.ip_addr_len = ip_addr_len
|
|
self.ip_addr = ip_addr
|
|
if labels:
|
|
# If binary type labels field value is specified, stores it
|
|
# and decodes as MPLS labels and VNI.
|
|
self._mpls_labels, self._vni = self._parse_labels(labels)
|
|
self._labels = labels
|
|
else:
|
|
# If either MPLS labels or VNI is specified, stores it
|
|
# and encodes into binary type labels field value.
|
|
self._labels = self._serialize_labels(mpls_labels, vni)
|
|
self._mpls_labels = mpls_labels
|
|
self._vni = vni
|
|
|
|
def _parse_labels(self, labels):
|
|
mpls_label1, rest, is_bos = self._mpls_label_from_bin(labels)
|
|
mpls_labels = [mpls_label1]
|
|
if rest and not is_bos:
|
|
mpls_label2, rest, _ = self._mpls_label_from_bin(rest)
|
|
mpls_labels.append(mpls_label2)
|
|
vni, _ = self._vni_from_bin(labels)
|
|
return mpls_labels, vni
|
|
|
|
def _serialize_labels(self, mpls_labels, vni):
|
|
if mpls_labels:
|
|
return self._serialize_mpls_labels(mpls_labels)
|
|
elif vni:
|
|
return self._vni_to_bin(vni)
|
|
else:
|
|
return b'\x00' * 3
|
|
|
|
def _serialize_mpls_labels(self, mpls_labels):
|
|
if len(mpls_labels) == 1:
|
|
return self._mpls_label_to_bin(mpls_labels[0], is_bos=True)
|
|
elif len(mpls_labels) == 2:
|
|
return (self._mpls_label_to_bin(mpls_labels[0], is_bos=False) +
|
|
self._mpls_label_to_bin(mpls_labels[1], is_bos=True))
|
|
else:
|
|
return b'\x00' * 3
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
route_dist, rest = cls._rd_from_bin(buf)
|
|
esi, rest = cls._esi_from_bin(rest)
|
|
ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
|
|
mac_addr_len, rest = cls._mac_addr_len_from_bin(rest)
|
|
mac_addr, rest = cls._mac_addr_from_bin(rest, mac_addr_len)
|
|
ip_addr_len, rest = cls._ip_addr_len_from_bin(rest)
|
|
if ip_addr_len != 0:
|
|
ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len // 8)
|
|
else:
|
|
ip_addr = None
|
|
|
|
return {
|
|
'route_dist': route_dist.formatted_str,
|
|
'esi': esi,
|
|
'ethernet_tag_id': ethernet_tag_id,
|
|
'mac_addr_len': mac_addr_len,
|
|
'mac_addr': mac_addr,
|
|
'ip_addr_len': ip_addr_len,
|
|
'ip_addr': ip_addr,
|
|
'labels': rest,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
route_dist = _RouteDistinguisher.from_str(self.route_dist)
|
|
mac_addr = self._mac_addr_to_bin(self.mac_addr)
|
|
self.mac_addr_len = len(mac_addr) * 8 # fixup
|
|
if self.ip_addr:
|
|
ip_addr = self._ip_addr_to_bin(self.ip_addr)
|
|
else:
|
|
ip_addr = b''
|
|
ip_addr_len = len(ip_addr)
|
|
self.ip_addr_len = ip_addr_len * 8 # fixup
|
|
|
|
return struct.pack(
|
|
self._PACK_STR % (ip_addr_len, len(self._labels)),
|
|
route_dist.serialize(), self.esi.serialize(),
|
|
self.ethernet_tag_id,
|
|
self.mac_addr_len, mac_addr,
|
|
self.ip_addr_len, ip_addr,
|
|
self._labels)
|
|
|
|
@property
|
|
def mpls_labels(self):
|
|
return self._mpls_labels
|
|
|
|
@mpls_labels.setter
|
|
def mpls_labels(self, mpls_labels):
|
|
self._labels = self._serialize_mpls_labels(mpls_labels)
|
|
self._mpls_labels = mpls_labels
|
|
self._vni = None # disables VNI
|
|
|
|
@property
|
|
def vni(self):
|
|
return self._vni
|
|
|
|
@vni.setter
|
|
def vni(self, vni):
|
|
self._labels = self._vni_to_bin(vni)
|
|
self._mpls_labels = None # disables MPLS labels
|
|
self._vni = vni
|
|
|
|
@property
|
|
def label_list(self):
|
|
return self.mpls_labels
|
|
|
|
|
|
@EvpnNLRI.register_type(EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG)
|
|
class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI):
|
|
"""
|
|
Inclusive Multicast Ethernet Tag route type specific EVPN NLRI
|
|
"""
|
|
ROUTE_TYPE_NAME = 'multicast_etag'
|
|
|
|
# +---------------------------------------+
|
|
# | RD (8 octets) |
|
|
# +---------------------------------------+
|
|
# | Ethernet Tag ID (4 octets) |
|
|
# +---------------------------------------+
|
|
# | IP Address Length (1 octet) |
|
|
# +---------------------------------------+
|
|
# | Originating Router's IP Address |
|
|
# | (4 or 16 octets) |
|
|
# +---------------------------------------+
|
|
_PACK_STR = '!8sIB%ds'
|
|
NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_addr']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'route_dist',
|
|
'ip_addr',
|
|
]
|
|
}
|
|
|
|
def __init__(self, route_dist, ethernet_tag_id, ip_addr,
|
|
ip_addr_len=None, type_=None, length=None):
|
|
super(EvpnInclusiveMulticastEthernetTagNLRI,
|
|
self).__init__(type_, length)
|
|
self.route_dist = route_dist
|
|
self.ethernet_tag_id = ethernet_tag_id
|
|
self.ip_addr_len = ip_addr_len
|
|
self.ip_addr = ip_addr
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
route_dist, rest = cls._rd_from_bin(buf)
|
|
ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
|
|
ip_addr_len, rest = cls._ip_addr_len_from_bin(rest)
|
|
ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len // 8)
|
|
|
|
return {
|
|
'route_dist': route_dist.formatted_str,
|
|
'ethernet_tag_id': ethernet_tag_id,
|
|
'ip_addr_len': ip_addr_len,
|
|
'ip_addr': ip_addr,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
route_dist = _RouteDistinguisher.from_str(self.route_dist)
|
|
ip_addr = self._ip_addr_to_bin(self.ip_addr)
|
|
self.ip_addr_len = len(ip_addr) * 8 # fixup
|
|
|
|
return struct.pack(
|
|
self._PACK_STR % len(ip_addr),
|
|
route_dist.serialize(), self.ethernet_tag_id,
|
|
self.ip_addr_len, ip_addr)
|
|
|
|
|
|
@EvpnNLRI.register_type(EvpnNLRI.ETHERNET_SEGMENT)
|
|
class EvpnEthernetSegmentNLRI(EvpnNLRI):
|
|
"""
|
|
Ethernet Segment route type specific EVPN NLRI
|
|
"""
|
|
ROUTE_TYPE_NAME = 'eth_seg'
|
|
|
|
# +---------------------------------------+
|
|
# | RD (8 octets) |
|
|
# +---------------------------------------+
|
|
# |Ethernet Segment Identifier (10 octets)|
|
|
# +---------------------------------------+
|
|
# | IP Address Length (1 octet) |
|
|
# +---------------------------------------+
|
|
# | Originating Router's IP Address |
|
|
# | (4 or 16 octets) |
|
|
# +---------------------------------------+
|
|
_PACK_STR = '!8s10sB%ds'
|
|
NLRI_PREFIX_FIELDS = ['esi', 'ip_addr']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'route_dist',
|
|
'ip_addr',
|
|
]
|
|
}
|
|
|
|
def __init__(self, route_dist, esi, ip_addr, ip_addr_len=None,
|
|
type_=None, length=None):
|
|
super(EvpnEthernetSegmentNLRI, self).__init__(type_, length)
|
|
self.route_dist = route_dist
|
|
self.esi = esi
|
|
self.ip_addr_len = ip_addr_len
|
|
self.ip_addr = ip_addr
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
route_dist, rest = cls._rd_from_bin(buf)
|
|
esi, rest = cls._esi_from_bin(rest)
|
|
ip_addr_len, rest = cls._ip_addr_len_from_bin(rest)
|
|
ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len // 8)
|
|
|
|
return {
|
|
'route_dist': route_dist.formatted_str,
|
|
'esi': esi,
|
|
'ip_addr_len': ip_addr_len,
|
|
'ip_addr': ip_addr,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
route_dist = _RouteDistinguisher.from_str(self.route_dist)
|
|
ip_addr = self._ip_addr_to_bin(self.ip_addr)
|
|
# fixup
|
|
self.ip_addr_len = len(ip_addr) * 8
|
|
|
|
return struct.pack(
|
|
self._PACK_STR % len(ip_addr),
|
|
route_dist.serialize(), self.esi.serialize(),
|
|
self.ip_addr_len, ip_addr)
|
|
|
|
|
|
@EvpnNLRI.register_type(EvpnNLRI.IP_PREFIX_ROUTE)
|
|
class EvpnIpPrefixNLRI(EvpnNLRI):
|
|
"""
|
|
IP Prefix advertisement route NLRI
|
|
"""
|
|
ROUTE_TYPE_NAME = 'ip_prefix'
|
|
|
|
# +---------------------------------------+
|
|
# | RD (8 octets) |
|
|
# +---------------------------------------+
|
|
# |Ethernet Segment Identifier (10 octets)|
|
|
# +---------------------------------------+
|
|
# | Ethernet Tag ID (4 octets) |
|
|
# +---------------------------------------+
|
|
# | IP Prefix Length (1 octet) |
|
|
# +---------------------------------------+
|
|
# | IP Prefix (4 or 16 octets) |
|
|
# +---------------------------------------+
|
|
# | GW IP Address (4 or 16 octets) |
|
|
# +---------------------------------------+
|
|
# | MPLS Label (3 octets) |
|
|
# +---------------------------------------+
|
|
_PACK_STR = '!8s10sIB%ds%ds3s'
|
|
NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_prefix']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'route_dist',
|
|
'ip_prefix',
|
|
'gw_ip_addr'
|
|
]
|
|
}
|
|
_LABEL_LEN = 3
|
|
|
|
def __init__(self, route_dist, ethernet_tag_id, ip_prefix,
|
|
esi=None, gw_ip_addr=None,
|
|
mpls_label=None, vni=None, label=None,
|
|
type_=None, length=None):
|
|
super(EvpnIpPrefixNLRI, self).__init__(type_, length)
|
|
self.route_dist = route_dist
|
|
self.esi = esi
|
|
self.ethernet_tag_id = ethernet_tag_id
|
|
self._ip_prefix = None
|
|
self._ip_prefix_len = None
|
|
self.ip_prefix = ip_prefix
|
|
|
|
if gw_ip_addr is None:
|
|
if ':' not in self._ip_prefix:
|
|
self.gw_ip_addr = '0.0.0.0'
|
|
else:
|
|
self.gw_ip_addr = '::'
|
|
else:
|
|
self.gw_ip_addr = gw_ip_addr
|
|
|
|
if label:
|
|
# If binary type label field value is specified, stores it
|
|
# and decodes as MPLS label and VNI.
|
|
self._label = label
|
|
self._mpls_label, _, _ = self._mpls_label_from_bin(label)
|
|
self._vni, _ = self._vni_from_bin(label)
|
|
else:
|
|
# If either MPLS label or VNI is specified, stores it
|
|
# and encodes into binary type label field value.
|
|
self._label = self._serialize_label(mpls_label, vni)
|
|
self._mpls_label = mpls_label
|
|
self._vni = vni
|
|
|
|
def _serialize_label(self, mpls_label, vni):
|
|
if mpls_label:
|
|
return self._mpls_label_to_bin(mpls_label, is_bos=True)
|
|
elif vni:
|
|
return vxlan.vni_to_bin(vni)
|
|
else:
|
|
return b'\x00' * 3
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
route_dist, rest = cls._rd_from_bin(buf)
|
|
esi, rest = cls._esi_from_bin(rest)
|
|
ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
|
|
ip_prefix_len, rest = cls._ip_addr_len_from_bin(rest)
|
|
_len = (len(rest) - cls._LABEL_LEN) // 2
|
|
ip_prefix, rest = cls._ip_addr_from_bin(rest, _len)
|
|
gw_ip_addr, rest = cls._ip_addr_from_bin(rest, _len)
|
|
|
|
return {
|
|
'route_dist': route_dist.formatted_str,
|
|
'esi': esi,
|
|
'ethernet_tag_id': ethernet_tag_id,
|
|
'ip_prefix': '%s/%s' % (ip_prefix, ip_prefix_len),
|
|
'gw_ip_addr': gw_ip_addr,
|
|
'label': rest,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
route_dist = _RouteDistinguisher.from_str(self.route_dist)
|
|
ip_prefix = self._ip_addr_to_bin(self._ip_prefix)
|
|
gw_ip_addr = self._ip_addr_to_bin(self.gw_ip_addr)
|
|
|
|
return struct.pack(
|
|
self._PACK_STR % (len(ip_prefix), len(gw_ip_addr)),
|
|
route_dist.serialize(), self.esi.serialize(),
|
|
self.ethernet_tag_id, self._ip_prefix_len, ip_prefix,
|
|
gw_ip_addr, self._label)
|
|
|
|
@property
|
|
def ip_prefix(self):
|
|
return '%s/%s' % (self._ip_prefix, self._ip_prefix_len)
|
|
|
|
@ip_prefix.setter
|
|
def ip_prefix(self, ip_prefix):
|
|
self._ip_prefix, ip_prefix_len = ip_prefix.split('/')
|
|
self._ip_prefix_len = int(ip_prefix_len)
|
|
|
|
@property
|
|
def mpls_label(self):
|
|
return self._mpls_label
|
|
|
|
@mpls_label.setter
|
|
def mpls_label(self, mpls_label):
|
|
self._label = self._mpls_label_to_bin(mpls_label, is_bos=True)
|
|
self._mpls_label = mpls_label
|
|
self._vni = None # disables VNI
|
|
|
|
@property
|
|
def vni(self):
|
|
return self._vni
|
|
|
|
@vni.setter
|
|
def vni(self, vni):
|
|
self._label = self._vni_to_bin(vni)
|
|
self._mpls_label = None # disables MPLS label
|
|
self._vni = vni
|
|
|
|
@property
|
|
def label_list(self):
|
|
return [self.mpls_label]
|
|
|
|
|
|
class _FlowSpecNLRIBase(StringifyMixin, TypeDisp):
|
|
"""
|
|
Base class for Flow Specification NLRI
|
|
"""
|
|
|
|
# flow-spec NLRI:
|
|
# +-----------------------------------+
|
|
# | length (0xnn or 0xfn nn) |
|
|
# +-----------------------------------+
|
|
# | NLRI value (variable) |
|
|
# +-----------------------------------+
|
|
ROUTE_FAMILY = None
|
|
_LENGTH_SHORT_FMT = '!B'
|
|
LENGTH_SHORT_SIZE = struct.calcsize(_LENGTH_SHORT_FMT)
|
|
_LENGTH_LONG_FMT = '!H'
|
|
LENGTH_LONG_SIZE = struct.calcsize(_LENGTH_LONG_FMT)
|
|
_LENGTH_THRESHOLD = 0xf000
|
|
FLOWSPEC_FAMILY = ''
|
|
|
|
def __init__(self, length=0, rules=None):
|
|
self.length = length
|
|
rules = rules or []
|
|
for r in rules:
|
|
assert isinstance(r, _FlowSpecComponentBase)
|
|
self.rules = rules
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(length,) = struct.unpack_from(
|
|
cls._LENGTH_LONG_FMT, six.binary_type(buf))
|
|
|
|
if length < cls._LENGTH_THRESHOLD:
|
|
length >>= 8
|
|
offset = cls.LENGTH_SHORT_SIZE
|
|
else:
|
|
offset = cls.LENGTH_LONG_SIZE
|
|
|
|
kwargs = {'length': length}
|
|
rest = buf[offset:offset + length]
|
|
|
|
if cls.ROUTE_FAMILY.safi == subaddr_family.VPN_FLOWSPEC:
|
|
route_dist = _RouteDistinguisher.parser(rest[:8])
|
|
kwargs['route_dist'] = route_dist.formatted_str
|
|
rest = rest[8:]
|
|
|
|
rules = []
|
|
|
|
while rest:
|
|
subcls, rest = _FlowSpecComponentBase.parse_header(
|
|
rest, cls.ROUTE_FAMILY.afi)
|
|
|
|
while rest:
|
|
rule, rest = subcls.parse_body(rest)
|
|
rules.append(rule)
|
|
|
|
if (not isinstance(rule, _FlowSpecOperatorBase) or
|
|
rule.operator & rule.END_OF_LIST):
|
|
break
|
|
|
|
kwargs['rules'] = rules
|
|
|
|
return cls(**kwargs), rest
|
|
|
|
def serialize(self):
|
|
rules_bin = b''
|
|
|
|
if self.ROUTE_FAMILY.safi == subaddr_family.VPN_FLOWSPEC:
|
|
route_dist = _RouteDistinguisher.from_str(self.route_dist)
|
|
rules_bin += route_dist.serialize()
|
|
|
|
self.rules.sort(key=lambda x: x.type)
|
|
for _, rules in itertools.groupby(self.rules, key=lambda x: x.type):
|
|
rules = list(rules)
|
|
rules_bin += rules[0].serialize_header()
|
|
|
|
if isinstance(rules[-1], _FlowSpecOperatorBase):
|
|
rules[-1].operator |= rules[-1].END_OF_LIST
|
|
|
|
for r in rules:
|
|
rules_bin += r.serialize_body()
|
|
|
|
self.length = len(rules_bin)
|
|
|
|
if self.length < self._LENGTH_THRESHOLD:
|
|
buf = struct.pack(self._LENGTH_SHORT_FMT, self.length)
|
|
else:
|
|
buf = struct.pack(self._LENGTH_LONG_FMT, self.length)
|
|
|
|
return buf + rules_bin
|
|
|
|
@classmethod
|
|
def _from_user(cls, **kwargs):
|
|
rules = []
|
|
for k, v in kwargs.items():
|
|
subcls = _FlowSpecComponentBase.lookup_type_name(
|
|
k, cls.ROUTE_FAMILY.afi)
|
|
rule = subcls.from_str(str(v))
|
|
rules.extend(rule)
|
|
rules.sort(key=lambda x: x.type)
|
|
return cls(rules=rules)
|
|
|
|
@property
|
|
def prefix(self):
|
|
def _format(i):
|
|
pairs = []
|
|
i.rules.sort(key=lambda x: x.type)
|
|
previous_type = None
|
|
for r in i.rules:
|
|
if r.type == previous_type:
|
|
if r.to_str()[0] != '&':
|
|
pairs[-1] += '|'
|
|
pairs[-1] += r.to_str()
|
|
else:
|
|
pairs.append('%s:%s' % (r.COMPONENT_NAME, r.to_str()))
|
|
previous_type = r.type
|
|
|
|
return ','.join(pairs)
|
|
|
|
return '%s(%s)' % (self.FLOWSPEC_FAMILY, _format(self))
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return self.prefix
|
|
|
|
|
|
class FlowSpecIPv4NLRI(_FlowSpecNLRIBase):
|
|
"""
|
|
Flow Specification NLRI class for IPv4 [RFC 5575]
|
|
"""
|
|
ROUTE_FAMILY = RF_IPv4_FLOWSPEC
|
|
FLOWSPEC_FAMILY = 'ipv4fs'
|
|
|
|
@classmethod
|
|
def from_user(cls, **kwargs):
|
|
"""
|
|
Utility method for creating a NLRI instance.
|
|
|
|
This function returns a NLRI instance from human readable format value.
|
|
|
|
:param kwargs: The following arguments are available.
|
|
|
|
=========== ============= ========= ==============================
|
|
Argument Value Operator Description
|
|
=========== ============= ========= ==============================
|
|
dst_prefix IPv4 Prefix Nothing Destination Prefix.
|
|
src_prefix IPv4 Prefix Nothing Source Prefix.
|
|
ip_proto Integer Numeric IP Protocol.
|
|
port Integer Numeric Port number.
|
|
dst_port Integer Numeric Destination port number.
|
|
src_port Integer Numeric Source port number.
|
|
icmp_type Integer Numeric ICMP type.
|
|
icmp_code Integer Numeric ICMP code.
|
|
tcp_flags Fixed string Bitmask TCP flags.
|
|
Supported values are
|
|
``CWR``, ``ECN``, ``URGENT``,
|
|
``ACK``, ``PUSH``, ``RST``,
|
|
``SYN`` and ``FIN``.
|
|
packet_len Integer Numeric Packet length.
|
|
dscp Integer Numeric Differentiated Services
|
|
Code Point.
|
|
fragment Fixed string Bitmask Fragment.
|
|
Supported values are
|
|
``DF`` (Don't fragment),
|
|
``ISF`` (Is a fragment),
|
|
``FF`` (First fragment) and
|
|
``LF`` (Last fragment)
|
|
=========== ============= ========= ==============================
|
|
|
|
Example::
|
|
|
|
>>> msg = bgp.FlowSpecIPv4NLRI.from_user(
|
|
... dst_prefix='10.0.0.0/24',
|
|
... src_prefix='20.0.0.1/24',
|
|
... ip_proto=6,
|
|
... port='80 | 8000',
|
|
... dst_port='>9000 & <9050',
|
|
... src_port='>=8500 & <=9000',
|
|
... icmp_type=0,
|
|
... icmp_code=6,
|
|
... tcp_flags='SYN+ACK & !=URGENT',
|
|
... packet_len=1000,
|
|
... dscp='22 | 24',
|
|
... fragment='LF | ==FF')
|
|
>>>
|
|
|
|
You can specify conditions with the following keywords.
|
|
|
|
The following keywords can be used when the operator type is Numeric.
|
|
|
|
========== ============================================================
|
|
Keyword Description
|
|
========== ============================================================
|
|
< Less than comparison between data and value.
|
|
<= Less than or equal to comparison between data and value.
|
|
> Greater than comparison between data and value.
|
|
>= Greater than or equal to comparison between data and value.
|
|
== Equality between data and value.
|
|
This operator can be omitted.
|
|
========== ============================================================
|
|
|
|
The following keywords can be used when the operator type is Bitmask.
|
|
|
|
========== ================================================
|
|
Keyword Description
|
|
========== ================================================
|
|
!= Not equal operation.
|
|
== Exact match operation if specified.
|
|
Otherwise partial match operation.
|
|
`+` Used for the summation of bitmask values.
|
|
(e.g., SYN+ACK)
|
|
========== ================================================
|
|
|
|
You can combine the multiple conditions with the following operators.
|
|
|
|
========== =======================================
|
|
Keyword Description
|
|
========== =======================================
|
|
`|` Logical OR operation
|
|
& Logical AND operation
|
|
========== =======================================
|
|
|
|
:return: A instance of FlowSpecVPNv4NLRI.
|
|
"""
|
|
return cls._from_user(**kwargs)
|
|
|
|
|
|
class FlowSpecVPNv4NLRI(_FlowSpecNLRIBase):
|
|
"""
|
|
Flow Specification NLRI class for VPNv4 [RFC 5575]
|
|
"""
|
|
|
|
# flow-spec NLRI:
|
|
# +-----------------------------------+
|
|
# | length (0xnn or 0xfn nn) |
|
|
# +-----------------------------------+
|
|
# | RD (8 octets) |
|
|
# +-----------------------------------+
|
|
# | NLRI value (variable) |
|
|
# +-----------------------------------+
|
|
ROUTE_FAMILY = RF_VPNv4_FLOWSPEC
|
|
FLOWSPEC_FAMILY = 'vpnv4fs'
|
|
|
|
def __init__(self, length=0, route_dist=None, rules=None):
|
|
super(FlowSpecVPNv4NLRI, self).__init__(length, rules)
|
|
assert route_dist is not None
|
|
self.route_dist = route_dist
|
|
|
|
@classmethod
|
|
def _from_user(cls, route_dist, **kwargs):
|
|
rules = []
|
|
for k, v in kwargs.items():
|
|
subcls = _FlowSpecComponentBase.lookup_type_name(
|
|
k, cls.ROUTE_FAMILY.afi)
|
|
rule = subcls.from_str(str(v))
|
|
rules.extend(rule)
|
|
rules.sort(key=lambda x: x.type)
|
|
return cls(route_dist=route_dist, rules=rules)
|
|
|
|
@classmethod
|
|
def from_user(cls, route_dist, **kwargs):
|
|
"""
|
|
Utility method for creating a NLRI instance.
|
|
|
|
This function returns a NLRI instance from human readable format value.
|
|
|
|
:param route_dist: Route Distinguisher.
|
|
:param kwargs: See :py:mod:`os_ken.lib.packet.bgp.FlowSpecIPv4NLRI`
|
|
|
|
Example::
|
|
|
|
>>> msg = bgp.FlowSpecIPv4NLRI.from_user(
|
|
... route_dist='65000:1000',
|
|
... dst_prefix='10.0.0.0/24',
|
|
... src_prefix='20.0.0.1/24',
|
|
... ip_proto=6,
|
|
... port='80 | 8000',
|
|
... dst_port='>9000 & <9050',
|
|
... src_port='>=8500 & <=9000',
|
|
... icmp_type=0,
|
|
... icmp_code=6,
|
|
... tcp_flags='SYN+ACK & !=URGENT',
|
|
... packet_len=1000,
|
|
... dscp='22 | 24',
|
|
... fragment='LF | ==FF')
|
|
>>>
|
|
"""
|
|
return cls._from_user(route_dist, **kwargs)
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return '%s:%s' % (self.route_dist, self.prefix)
|
|
|
|
|
|
class FlowSpecIPv6NLRI(_FlowSpecNLRIBase):
|
|
"""
|
|
Flow Specification NLRI class for IPv6 [RFC draft-ietf-idr-flow-spec-v6-08]
|
|
"""
|
|
ROUTE_FAMILY = RF_IPv6_FLOWSPEC
|
|
FLOWSPEC_FAMILY = 'ipv6fs'
|
|
|
|
@classmethod
|
|
def from_user(cls, **kwargs):
|
|
"""
|
|
Utility method for creating a NLRI instance.
|
|
|
|
This function returns a NLRI instance from human readable format value.
|
|
|
|
:param kwargs: The following arguments are available.
|
|
|
|
=========== ============= ========= ==============================
|
|
Argument Value Operator Description
|
|
=========== ============= ========= ==============================
|
|
dst_prefix IPv6 Prefix Nothing Destination Prefix.
|
|
src_prefix IPv6 Prefix Nothing Source Prefix.
|
|
next_header Integer Numeric Next Header.
|
|
port Integer Numeric Port number.
|
|
dst_port Integer Numeric Destination port number.
|
|
src_port Integer Numeric Source port number.
|
|
icmp_type Integer Numeric ICMP type.
|
|
icmp_code Integer Numeric ICMP code.
|
|
tcp_flags Fixed string Bitmask TCP flags.
|
|
Supported values are
|
|
``CWR``, ``ECN``, ``URGENT``,
|
|
``ACK``, ``PUSH``, ``RST``,
|
|
``SYN`` and ``FIN``.
|
|
packet_len Integer Numeric Packet length.
|
|
dscp Integer Numeric Differentiated Services
|
|
Code Point.
|
|
fragment Fixed string Bitmask Fragment.
|
|
Supported values are
|
|
``ISF`` (Is a fragment),
|
|
``FF`` (First fragment) and
|
|
``LF`` (Last fragment)
|
|
flow_label Intefer Numeric Flow Label.
|
|
=========== ============= ========= ==============================
|
|
|
|
.. Note::
|
|
|
|
For ``dst_prefix`` and ``src_prefix``, you can give "offset" value
|
|
like this: ``2001::2/128/32``. At this case, ``offset`` is 32.
|
|
``offset`` can be omitted, then ``offset`` is treated as 0.
|
|
"""
|
|
return cls._from_user(**kwargs)
|
|
|
|
|
|
class FlowSpecVPNv6NLRI(_FlowSpecNLRIBase):
|
|
"""
|
|
Flow Specification NLRI class for VPNv6 [draft-ietf-idr-flow-spec-v6-08]
|
|
"""
|
|
|
|
# flow-spec NLRI:
|
|
# +-----------------------------------+
|
|
# | length (0xnn or 0xfn nn) |
|
|
# +-----------------------------------+
|
|
# | RD (8 octets) |
|
|
# +-----------------------------------+
|
|
# | NLRI value (variable) |
|
|
# +-----------------------------------+
|
|
ROUTE_FAMILY = RF_VPNv6_FLOWSPEC
|
|
FLOWSPEC_FAMILY = 'vpnv6fs'
|
|
|
|
def __init__(self, length=0, route_dist=None, rules=None):
|
|
super(FlowSpecVPNv6NLRI, self).__init__(length, rules)
|
|
assert route_dist is not None
|
|
self.route_dist = route_dist
|
|
|
|
@classmethod
|
|
def _from_user(cls, route_dist, **kwargs):
|
|
rules = []
|
|
for k, v in kwargs.items():
|
|
subcls = _FlowSpecComponentBase.lookup_type_name(
|
|
k, cls.ROUTE_FAMILY.afi)
|
|
rule = subcls.from_str(str(v))
|
|
rules.extend(rule)
|
|
rules.sort(key=lambda x: x.type)
|
|
return cls(route_dist=route_dist, rules=rules)
|
|
|
|
@classmethod
|
|
def from_user(cls, route_dist, **kwargs):
|
|
"""
|
|
Utility method for creating a NLRI instance.
|
|
|
|
This function returns a NLRI instance from human readable format value.
|
|
|
|
:param route_dist: Route Distinguisher.
|
|
:param kwargs: See :py:mod:`os_ken.lib.packet.bgp.FlowSpecIPv6NLRI`
|
|
"""
|
|
return cls._from_user(route_dist, **kwargs)
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return '%s:%s' % (self.route_dist, self.prefix)
|
|
|
|
|
|
class FlowSpecL2VPNNLRI(_FlowSpecNLRIBase):
|
|
"""
|
|
Flow Specification NLRI class for L2VPN [draft-ietf-idr-flowspec-l2vpn-05]
|
|
"""
|
|
|
|
# flow-spec NLRI:
|
|
# +-----------------------------------+
|
|
# | length (0xnn or 0xfn nn) |
|
|
# +-----------------------------------+
|
|
# | RD (8 octets) |
|
|
# +-----------------------------------+
|
|
# | NLRI value (variable) |
|
|
# +-----------------------------------+
|
|
ROUTE_FAMILY = RF_L2VPN_FLOWSPEC
|
|
FLOWSPEC_FAMILY = 'l2vpnfs'
|
|
|
|
def __init__(self, length=0, route_dist=None, rules=None):
|
|
super(FlowSpecL2VPNNLRI, self).__init__(length, rules)
|
|
assert route_dist is not None
|
|
self.route_dist = route_dist
|
|
|
|
@classmethod
|
|
def _from_user(cls, route_dist, **kwargs):
|
|
rules = []
|
|
for k, v in kwargs.items():
|
|
subcls = _FlowSpecComponentBase.lookup_type_name(
|
|
k, cls.ROUTE_FAMILY.afi)
|
|
rule = subcls.from_str(str(v))
|
|
rules.extend(rule)
|
|
rules.sort(key=lambda x: x.type)
|
|
return cls(route_dist=route_dist, rules=rules)
|
|
|
|
@classmethod
|
|
def from_user(cls, route_dist, **kwargs):
|
|
"""
|
|
Utility method for creating a L2VPN NLRI instance.
|
|
|
|
This function returns a L2VPN NLRI instance
|
|
from human readable format value.
|
|
|
|
:param kwargs: The following arguments are available.
|
|
|
|
============== ============= ========= ==============================
|
|
Argument Value Operator Description
|
|
============== ============= ========= ==============================
|
|
ether_type Integer Numeric Ethernet Type.
|
|
src_mac Mac Address Nothing Source Mac address.
|
|
dst_mac Mac Address Nothing Destination Mac address.
|
|
llc_ssap Integer Numeric Source Service Access Point
|
|
in LLC.
|
|
llc_dsap Integer Numeric Destination Service Access
|
|
Point in LLC.
|
|
llc_control Integer Numeric Control field in LLC.
|
|
snap Integer Numeric Sub-Network Access Protocol
|
|
field.
|
|
vlan_id Integer Numeric VLAN ID.
|
|
vlan_cos Integer Numeric VLAN COS field.
|
|
inner_vlan_id Integer Numeric Inner VLAN ID.
|
|
inner_vlan_cos Integer Numeric Inner VLAN COS field.
|
|
============== ============= ========= ==============================
|
|
"""
|
|
return cls._from_user(route_dist, **kwargs)
|
|
|
|
@property
|
|
def formatted_nlri_str(self):
|
|
return '%s:%s' % (self.route_dist, self.prefix)
|
|
|
|
|
|
class _FlowSpecComponentBase(StringifyMixin, TypeDisp):
|
|
"""
|
|
Base class for Flow Specification NLRI component
|
|
"""
|
|
COMPONENT_NAME = None
|
|
|
|
_BASE_STR = '!B'
|
|
_BASE_STR_SIZE = struct.calcsize(_BASE_STR)
|
|
|
|
# Dictionary of COMPONENT_NAME to subclass.
|
|
# e.g.)
|
|
# _NAMES = {'dst_prefix': FlowSpecDestPrefix, ...}
|
|
_NAMES = {}
|
|
|
|
def __init__(self, type_=None):
|
|
if type_ is None:
|
|
type_, _ = self._rev_lookup_type(self.__class__)
|
|
self.type = type_
|
|
|
|
@classmethod
|
|
def register_type(cls, type_, afi):
|
|
cls._TYPES = cls._TYPES.copy()
|
|
cls._NAMES = cls._NAMES.copy()
|
|
|
|
def _register_type(subcls):
|
|
cls._TYPES[(type_, afi)] = subcls
|
|
cls._NAMES[(subcls.COMPONENT_NAME, afi)] = subcls
|
|
cls._REV_TYPES = None
|
|
return subcls
|
|
|
|
return _register_type
|
|
|
|
@classmethod
|
|
def lookup_type_name(cls, type_name, afi):
|
|
return cls._NAMES[(type_name, afi)]
|
|
|
|
@classmethod
|
|
def _lookup_type(cls, type_, afi):
|
|
try:
|
|
return cls._TYPES[(type_, afi)]
|
|
except KeyError:
|
|
return cls._UNKNOWN_TYPE
|
|
|
|
@classmethod
|
|
def parse_header(cls, rest, afi):
|
|
(type_,) = struct.unpack_from(
|
|
cls._BASE_STR, six.binary_type(rest))
|
|
rest = rest[cls._BASE_STR_SIZE:]
|
|
return cls._lookup_type(type_, afi), rest
|
|
|
|
def serialize_header(self):
|
|
return struct.pack(self._BASE_STR, self.type)
|
|
|
|
|
|
class _FlowSpecIPv4Component(_FlowSpecComponentBase):
|
|
"""
|
|
Base class for Flow Specification for IPv4 NLRI component
|
|
"""
|
|
TYPE_DESTINATION_PREFIX = 0x01
|
|
TYPE_SOURCE_PREFIX = 0x02
|
|
TYPE_PROTOCOL = 0x03
|
|
TYPE_PORT = 0x04
|
|
TYPE_DESTINATION_PORT = 0x05
|
|
TYPE_SOURCE_PORT = 0x06
|
|
TYPE_ICMP = 0x07
|
|
TYPE_ICMP_CODE = 0x08
|
|
TYPE_TCP_FLAGS = 0x09
|
|
TYPE_PACKET_LENGTH = 0x0a
|
|
TYPE_DIFFSERV_CODE_POINT = 0x0b
|
|
TYPE_FRAGMENT = 0x0c
|
|
|
|
|
|
class _FlowSpecIPv6Component(_FlowSpecComponentBase):
|
|
"""
|
|
Base class for Flow Specification for IPv6 NLRI component
|
|
"""
|
|
TYPE_DESTINATION_PREFIX = 0x01
|
|
TYPE_SOURCE_PREFIX = 0x02
|
|
TYPE_NEXT_HEADER = 0x03
|
|
TYPE_PORT = 0x04
|
|
TYPE_DESTINATION_PORT = 0x05
|
|
TYPE_SOURCE_PORT = 0x06
|
|
TYPE_ICMP = 0x07
|
|
TYPE_ICMP_CODE = 0x08
|
|
TYPE_TCP_FLAGS = 0x09
|
|
TYPE_PACKET_LENGTH = 0x0a
|
|
TYPE_DIFFSERV_CODE_POINT = 0x0b
|
|
TYPE_FRAGMENT = 0x0c
|
|
TYPE_FLOW_LABEL = 0x0d
|
|
|
|
|
|
class _FlowSpecL2VPNComponent(_FlowSpecComponentBase):
|
|
"""
|
|
Base class for Flow Specification for L2VPN NLRI component
|
|
"""
|
|
TYPE_ETHER_TYPE = 0x0e
|
|
TYPE_SOURCE_MAC = 0x0f
|
|
TYPE_DESTINATION_MAC = 0x10
|
|
TYPE_LLC_DSAP = 0x11
|
|
TYPE_LLC_SSAP = 0x12
|
|
TYPE_LLC_CONTROL = 0x13
|
|
TYPE_SNAP = 0x14
|
|
TYPE_VLAN_ID = 0x15
|
|
TYPE_VLAN_COS = 0x16
|
|
TYPE_INNER_VLAN_ID = 0x17
|
|
TYPE_INNER_VLAN_COS = 0x18
|
|
|
|
|
|
@_FlowSpecComponentBase.register_unknown_type()
|
|
class FlowSpecComponentUnknown(_FlowSpecComponentBase):
|
|
"""
|
|
Unknown component type for Flow Specification NLRI component
|
|
"""
|
|
|
|
def __init__(self, buf, type_=None):
|
|
super(FlowSpecComponentUnknown, self).__init__(type_)
|
|
self.buf = buf
|
|
|
|
@classmethod
|
|
def parse_body(cls, buf):
|
|
return cls(buf), None
|
|
|
|
def serialize_body(self):
|
|
return self.buf
|
|
|
|
|
|
class _FlowSpecPrefixBase(_FlowSpecIPv4Component, IPAddrPrefix):
|
|
"""
|
|
Prefix base class for Flow Specification NLRI component
|
|
"""
|
|
|
|
def __init__(self, length, addr, type_=None):
|
|
super(_FlowSpecPrefixBase, self).__init__(type_)
|
|
self.length = length
|
|
prefix = "%s/%s" % (addr, length)
|
|
self.addr = str(netaddr.ip.IPNetwork(prefix).network)
|
|
|
|
@classmethod
|
|
def parse_body(cls, buf):
|
|
return cls.parser(buf)
|
|
|
|
def serialize_body(self):
|
|
return self.serialize()
|
|
|
|
@classmethod
|
|
def from_str(cls, value):
|
|
rule = []
|
|
addr, length = value.split('/')
|
|
rule.append(cls(int(length), addr))
|
|
return rule
|
|
|
|
@property
|
|
def value(self):
|
|
return "%s/%s" % (self.addr, self.length)
|
|
|
|
def to_str(self):
|
|
return self.value
|
|
|
|
|
|
class _FlowSpecIPv6PrefixBase(_FlowSpecIPv6Component, IP6AddrPrefix):
|
|
"""
|
|
Prefix base class for Flow Specification NLRI component
|
|
"""
|
|
_PACK_STR = '!BB' # length, offset
|
|
|
|
def __init__(self, length, addr, offset=0, type_=None):
|
|
super(_FlowSpecIPv6PrefixBase, self).__init__(type_)
|
|
self.length = length
|
|
self.offset = offset
|
|
prefix = "%s/%s" % (addr, length)
|
|
self.addr = str(netaddr.ip.IPNetwork(prefix).network)
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(length, offset) = struct.unpack_from(
|
|
cls._PACK_STR, six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
byte_length = (length + 7) // 8
|
|
addr = cls._from_bin(rest[:byte_length])
|
|
rest = rest[byte_length:]
|
|
return cls(length=length, offset=offset, addr=addr), rest
|
|
|
|
@classmethod
|
|
def parse_body(cls, buf):
|
|
return cls.parser(buf)
|
|
|
|
def serialize(self):
|
|
byte_length = (self.length + 7) // 8
|
|
bin_addr = self._to_bin(self.addr)[:byte_length]
|
|
buf = bytearray()
|
|
msg_pack_into(self._PACK_STR, buf, 0, self.length, self.offset)
|
|
return buf + bin_addr
|
|
|
|
def serialize_body(self):
|
|
return self.serialize()
|
|
|
|
@classmethod
|
|
def from_str(cls, value):
|
|
rule = []
|
|
values = value.split('/')
|
|
if len(values) == 3:
|
|
rule.append(cls(int(values[1]), values[0], offset=int(values[2])))
|
|
else:
|
|
rule.append(cls(int(values[1]), values[0]))
|
|
return rule
|
|
|
|
@property
|
|
def value(self):
|
|
return "%s/%s/%s" % (self.addr, self.length, self.offset)
|
|
|
|
def to_str(self):
|
|
return self.value
|
|
|
|
|
|
class _FlowSpecL2VPNPrefixBase(_FlowSpecL2VPNComponent):
|
|
"""
|
|
Prefix base class for Flow Specification NLRI component
|
|
"""
|
|
_PACK_STR = "!B6s"
|
|
|
|
def __init__(self, length, addr, type_=None):
|
|
super(_FlowSpecL2VPNPrefixBase, self).__init__(type_)
|
|
self.length = length
|
|
self.addr = addr.lower()
|
|
|
|
@classmethod
|
|
def parse_body(cls, buf):
|
|
(length, addr) = struct.unpack_from(
|
|
cls._PACK_STR, six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
addr = addrconv.mac.bin_to_text(addr)
|
|
return cls(length=length, addr=addr), rest
|
|
|
|
def serialize(self):
|
|
addr = addrconv.mac.text_to_bin(self.addr)
|
|
return struct.pack(self._PACK_STR, self.length, addr)
|
|
|
|
def serialize_body(self):
|
|
return self.serialize()
|
|
|
|
@classmethod
|
|
def from_str(cls, value):
|
|
return [cls(len(value.split(':')), value)]
|
|
|
|
@property
|
|
def value(self):
|
|
return self.addr
|
|
|
|
def to_str(self):
|
|
return self.value
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_DESTINATION_PREFIX, addr_family.IP)
|
|
class FlowSpecDestPrefix(_FlowSpecPrefixBase):
|
|
"""
|
|
Destination Prefix for Flow Specification NLRI component
|
|
"""
|
|
COMPONENT_NAME = 'dst_prefix'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_SOURCE_PREFIX, addr_family.IP)
|
|
class FlowSpecSrcPrefix(_FlowSpecPrefixBase):
|
|
"""
|
|
Source Prefix for Flow Specification NLRI component
|
|
"""
|
|
COMPONENT_NAME = 'src_prefix'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_DESTINATION_PREFIX, addr_family.IP6)
|
|
class FlowSpecIPv6DestPrefix(_FlowSpecIPv6PrefixBase):
|
|
"""
|
|
IPv6 destination Prefix for Flow Specification NLRI component
|
|
"""
|
|
COMPONENT_NAME = 'dst_prefix'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_SOURCE_PREFIX, addr_family.IP6)
|
|
class FlowSpecIPv6SrcPrefix(_FlowSpecIPv6PrefixBase):
|
|
"""
|
|
IPv6 source Prefix for Flow Specification NLRI component
|
|
"""
|
|
COMPONENT_NAME = 'src_prefix'
|
|
|
|
|
|
class _FlowSpecOperatorBase(_FlowSpecComponentBase):
|
|
"""Operator base class for Flow Specification NLRI component
|
|
|
|
===================== ===============================================
|
|
Attribute Description
|
|
===================== ===============================================
|
|
operator Match conditions.
|
|
value Value of component.
|
|
===================== ===============================================
|
|
"""
|
|
_OPE_PACK_STR = '!B'
|
|
_OPE_PACK_STR_SIZE = struct.calcsize(_OPE_PACK_STR)
|
|
_VAL_PACK_STR = '!%ds'
|
|
|
|
END_OF_LIST = 1 << 7 # END OF LIST bit
|
|
AND = 1 << 6 # AND bit
|
|
OR = 0 # OR
|
|
_LENGTH_BIT_MASK = 0x30 # The mask for length of the value
|
|
|
|
_logical_conditions = {
|
|
"|": OR,
|
|
"&": AND,
|
|
}
|
|
_comparison_conditions = {}
|
|
|
|
def __init__(self, operator, value, type_=None):
|
|
super(_FlowSpecOperatorBase, self).__init__(type_)
|
|
self.operator = operator
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def parse_body(cls, rest):
|
|
(operator,) = struct.unpack_from(cls._OPE_PACK_STR,
|
|
six.binary_type(rest))
|
|
rest = rest[cls._OPE_PACK_STR_SIZE:]
|
|
length = 1 << ((operator & cls._LENGTH_BIT_MASK) >> 4)
|
|
value_type = type_desc.IntDescr(length)
|
|
value = value_type.to_user(rest)
|
|
rest = rest[length:]
|
|
|
|
return cls(operator, value), rest
|
|
|
|
def serialize_body(self):
|
|
byte_length = (self.value.bit_length() + 7) // 8 or 1
|
|
length = int(math.ceil(math.log(byte_length, 2)))
|
|
self.operator |= length << 4
|
|
buf = struct.pack(self._OPE_PACK_STR, self.operator)
|
|
value_type = type_desc.IntDescr(1 << length)
|
|
buf += struct.pack(self._VAL_PACK_STR % (1 << length),
|
|
value_type.from_user(self.value))
|
|
|
|
return buf
|
|
|
|
@classmethod
|
|
def from_str(cls, val):
|
|
operator = 0
|
|
rules = []
|
|
|
|
# e.g.)
|
|
# value = '80 | ==90|>=8000&<=9000 | <100 & >110'
|
|
# elements = ['80', '|', '==', '90', '|', '>=', '8000', '&',
|
|
# '<=', '9000', '|', '<', '100', '&', '>', '110']
|
|
elements = [v.strip() for v in re.split(
|
|
r'([0-9]+)|([A-Z]+)|(\|&\+)|([!=<>]+)', val) if v and v.strip()]
|
|
|
|
elms_iter = iter(elements)
|
|
|
|
for elm in elms_iter:
|
|
if elm in cls._logical_conditions:
|
|
# ['&', '|']
|
|
operator |= cls._logical_conditions[elm]
|
|
continue
|
|
elif elm in cls._comparison_conditions:
|
|
# ['=', '<', '>', '<=', '>=' ] or ['=', '!=']
|
|
operator |= cls._comparison_conditions[elm]
|
|
continue
|
|
elif elm == '+':
|
|
# If keyword "+" is used, add the value to the previous rule.
|
|
# e.g.) 'SYN+ACK' or '!=SYN+ACK'
|
|
rules[-1].value |= cls._to_value(next(elms_iter))
|
|
continue
|
|
|
|
value = cls._to_value(elm)
|
|
|
|
operator = cls.normalize_operator(operator)
|
|
|
|
rules.append(cls(operator, value))
|
|
operator = 0
|
|
|
|
return rules
|
|
|
|
@classmethod
|
|
def _to_value(cls, value):
|
|
return value
|
|
|
|
@classmethod
|
|
def normalize_operator(cls, operator):
|
|
return operator
|
|
|
|
|
|
class _FlowSpecNumeric(_FlowSpecOperatorBase):
|
|
"""
|
|
Numeric operator class for Flow Specification NLRI component
|
|
"""
|
|
# Numeric operator format
|
|
# 0 1 2 3 4 5 6 7
|
|
# +---+---+---+---+---+---+---+---+
|
|
# | e | a | len | 0 |lt |gt |eq |
|
|
# +---+---+---+---+---+---+---+---+
|
|
|
|
LT = 1 << 2 # Less than comparison bit
|
|
GT = 1 << 1 # Greater than comparison bit
|
|
EQ = 1 << 0 # Equality bit
|
|
|
|
_comparison_conditions = {
|
|
'==': EQ,
|
|
'<': LT,
|
|
'>': GT,
|
|
'<=': LT | EQ,
|
|
'>=': GT | EQ
|
|
}
|
|
|
|
@classmethod
|
|
def _to_value(cls, value):
|
|
try:
|
|
return int(str(value), 0)
|
|
except ValueError:
|
|
raise ValueError('Invalid params: %s="%s"' % (
|
|
cls.COMPONENT_NAME, value))
|
|
|
|
def to_str(self):
|
|
string = ""
|
|
if self.operator & self.AND:
|
|
string += "&"
|
|
|
|
operator = self.operator & (self.LT | self.GT | self.EQ)
|
|
for k, v in self._comparison_conditions.items():
|
|
if operator == v:
|
|
string += k
|
|
|
|
string += str(self.value)
|
|
|
|
return string
|
|
|
|
@classmethod
|
|
def normalize_operator(cls, operator):
|
|
if operator & (cls.LT | cls.GT | cls.EQ):
|
|
return operator
|
|
else:
|
|
return operator | cls.EQ
|
|
|
|
|
|
class _FlowSpecBitmask(_FlowSpecOperatorBase):
|
|
"""
|
|
Bitmask operator class for Flow Specification NLRI component
|
|
"""
|
|
# Bitmask operator format
|
|
# 0 1 2 3 4 5 6 7
|
|
# +---+---+---+---+---+---+---+---+
|
|
# | e | a | len | 0 | 0 |not| m |
|
|
# +---+---+---+---+---+---+---+---+
|
|
|
|
NOT = 1 << 1 # NOT bit
|
|
MATCH = 1 << 0 # MATCH bit
|
|
|
|
_comparison_conditions = {
|
|
'!=': NOT,
|
|
'==': MATCH,
|
|
}
|
|
|
|
_bitmask_flags = {}
|
|
|
|
@classmethod
|
|
def _to_value(cls, value):
|
|
try:
|
|
return cls.__dict__[value]
|
|
except KeyError:
|
|
raise ValueError('Invalid params: %s="%s"' % (
|
|
cls.COMPONENT_NAME, value))
|
|
|
|
def to_str(self):
|
|
string = ""
|
|
if self.operator & self.AND:
|
|
string += "&"
|
|
|
|
operator = self.operator & (self.NOT | self.MATCH)
|
|
for k, v in self._comparison_conditions.items():
|
|
if operator == v:
|
|
string += k
|
|
|
|
plus = ""
|
|
for k, v in self._bitmask_flags.items():
|
|
if self.value & k:
|
|
string += plus + v
|
|
plus = "+"
|
|
|
|
return string
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_PROTOCOL, addr_family.IP)
|
|
class FlowSpecIPProtocol(_FlowSpecNumeric):
|
|
"""IP Protocol for Flow Specification NLRI component
|
|
|
|
Set the IP protocol number at value.
|
|
"""
|
|
COMPONENT_NAME = 'ip_proto'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_NEXT_HEADER, addr_family.IP6)
|
|
class FlowSpecNextHeader(_FlowSpecNumeric):
|
|
"""Next Header value in IPv6 packets
|
|
|
|
Set the IP protocol number at value
|
|
"""
|
|
COMPONENT_NAME = 'next_header'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_PORT, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_PORT, addr_family.IP6)
|
|
class FlowSpecPort(_FlowSpecNumeric):
|
|
"""Port number for Flow Specification NLRI component
|
|
|
|
Set the source or destination TCP/UDP ports at value.
|
|
"""
|
|
COMPONENT_NAME = 'port'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_DESTINATION_PORT, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_DESTINATION_PORT, addr_family.IP6)
|
|
class FlowSpecDestPort(_FlowSpecNumeric):
|
|
"""Destination port number for Flow Specification NLRI component
|
|
|
|
Set the destination port of a TCP or UDP packet at value.
|
|
"""
|
|
COMPONENT_NAME = 'dst_port'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_SOURCE_PORT, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_SOURCE_PORT, addr_family.IP6)
|
|
class FlowSpecSrcPort(_FlowSpecNumeric):
|
|
"""Source port number for Flow Specification NLRI component
|
|
|
|
Set the source port of a TCP or UDP packet at value.
|
|
"""
|
|
COMPONENT_NAME = 'src_port'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_ICMP, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_ICMP, addr_family.IP6)
|
|
class FlowSpecIcmpType(_FlowSpecNumeric):
|
|
"""ICMP type for Flow Specification NLRI component
|
|
|
|
Set the type field of an ICMP packet at value.
|
|
"""
|
|
COMPONENT_NAME = 'icmp_type'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_ICMP_CODE, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_ICMP_CODE, addr_family.IP6)
|
|
class FlowSpecIcmpCode(_FlowSpecNumeric):
|
|
"""ICMP code Flow Specification NLRI component
|
|
|
|
Set the code field of an ICMP packet at value.
|
|
"""
|
|
COMPONENT_NAME = 'icmp_code'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_TCP_FLAGS, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_TCP_FLAGS, addr_family.IP6)
|
|
class FlowSpecTCPFlags(_FlowSpecBitmask):
|
|
"""TCP flags for Flow Specification NLRI component
|
|
|
|
Supported TCP flags are CWR, ECN, URGENT, ACK, PUSH, RST, SYN and FIN.
|
|
"""
|
|
COMPONENT_NAME = 'tcp_flags'
|
|
|
|
# bitmask format
|
|
# 0 1 2 3 4 5 6 7
|
|
# +----+----+----+----+----+----+----+----+
|
|
# |CWR |ECN |URG |ACK |PSH |RST |SYN |FIN |
|
|
# +----+----+----+----+----+----+----+----+
|
|
|
|
CWR = 1 << 7
|
|
ECN = 1 << 6
|
|
URGENT = 1 << 5
|
|
ACK = 1 << 4
|
|
PUSH = 1 << 3
|
|
RST = 1 << 2
|
|
SYN = 1 << 1
|
|
FIN = 1 << 0
|
|
|
|
_bitmask_flags = collections.OrderedDict()
|
|
_bitmask_flags[SYN] = 'SYN'
|
|
_bitmask_flags[ACK] = 'ACK'
|
|
_bitmask_flags[FIN] = 'FIN'
|
|
_bitmask_flags[RST] = 'RST'
|
|
_bitmask_flags[PUSH] = 'PUSH'
|
|
_bitmask_flags[URGENT] = 'URGENT'
|
|
_bitmask_flags[ECN] = 'ECN'
|
|
_bitmask_flags[CWR] = 'CWR'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_PACKET_LENGTH, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_PACKET_LENGTH, addr_family.IP6)
|
|
class FlowSpecPacketLen(_FlowSpecNumeric):
|
|
"""Packet length for Flow Specification NLRI component
|
|
|
|
Set the total IP packet length at value.
|
|
"""
|
|
COMPONENT_NAME = 'packet_len'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_DIFFSERV_CODE_POINT, addr_family.IP)
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_DIFFSERV_CODE_POINT, addr_family.IP6)
|
|
class FlowSpecDSCP(_FlowSpecNumeric):
|
|
"""Diffserv Code Point for Flow Specification NLRI component
|
|
|
|
Set the 6-bit DSCP field at value. [RFC2474]
|
|
"""
|
|
COMPONENT_NAME = 'dscp'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv4Component.TYPE_FRAGMENT, addr_family.IP)
|
|
class FlowSpecFragment(_FlowSpecBitmask):
|
|
"""Fragment for Flow Specification NLRI component
|
|
|
|
Set the bitmask for operand format at value.
|
|
The following values are supported.
|
|
|
|
========== ===============================================
|
|
Attribute Description
|
|
========== ===============================================
|
|
LF Last fragment
|
|
FF First fragment
|
|
ISF Is a fragment
|
|
DF Don't fragment
|
|
========== ===============================================
|
|
"""
|
|
COMPONENT_NAME = 'fragment'
|
|
|
|
# bitmask format
|
|
# 0 1 2 3 4 5 6 7
|
|
# +---+---+---+---+---+---+---+---+
|
|
# | Reserved |LF |FF |IsF|DF |
|
|
# +---+---+---+---+---+---+---+---+
|
|
|
|
LF = 1 << 3
|
|
FF = 1 << 2
|
|
ISF = 1 << 1
|
|
DF = 1 << 0
|
|
|
|
_bitmask_flags = collections.OrderedDict()
|
|
_bitmask_flags[LF] = 'LF'
|
|
_bitmask_flags[FF] = 'FF'
|
|
_bitmask_flags[ISF] = 'ISF'
|
|
_bitmask_flags[DF] = 'DF'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_FRAGMENT, addr_family.IP6)
|
|
class FlowSpecIPv6Fragment(_FlowSpecBitmask):
|
|
"""Fragment for Flow Specification for IPv6 NLRI component
|
|
|
|
========== ===============================================
|
|
Attribute Description
|
|
========== ===============================================
|
|
LF Last fragment
|
|
FF First fragment
|
|
ISF Is a fragment
|
|
========== ===============================================
|
|
"""
|
|
COMPONENT_NAME = 'fragment'
|
|
|
|
# bitmask format
|
|
# 0 1 2 3 4 5 6 7
|
|
# +---+---+---+---+---+---+---+---+
|
|
# | Reserved |LF |FF |IsF| 0 |
|
|
# +---+---+---+---+---+---+---+---+
|
|
|
|
LF = 1 << 3
|
|
FF = 1 << 2
|
|
ISF = 1 << 1
|
|
|
|
_bitmask_flags = collections.OrderedDict()
|
|
_bitmask_flags[LF] = 'LF'
|
|
_bitmask_flags[FF] = 'FF'
|
|
_bitmask_flags[ISF] = 'ISF'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_ETHER_TYPE, addr_family.L2VPN)
|
|
class FlowSpecEtherType(_FlowSpecNumeric):
|
|
"""Ethernet Type field in an Ethernet frame.
|
|
|
|
Set the 2 byte value of an Ethernet Type field at value.
|
|
"""
|
|
COMPONENT_NAME = 'ether_type'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_SOURCE_MAC, addr_family.L2VPN)
|
|
class FlowSpecSourceMac(_FlowSpecL2VPNPrefixBase):
|
|
"""Source Mac Address.
|
|
|
|
Set the Mac Address at value.
|
|
"""
|
|
COMPONENT_NAME = 'src_mac'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_DESTINATION_MAC, addr_family.L2VPN)
|
|
class FlowSpecDestinationMac(_FlowSpecL2VPNPrefixBase):
|
|
"""Destination Mac Address.
|
|
|
|
Set the Mac Address at value.
|
|
"""
|
|
COMPONENT_NAME = 'dst_mac'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_LLC_DSAP, addr_family.L2VPN)
|
|
class FlowSpecLLCDSAP(_FlowSpecNumeric):
|
|
"""Destination SAP field in LLC header in an Ethernet frame.
|
|
|
|
Set the 2 byte value of an Destination SAP at value.
|
|
"""
|
|
COMPONENT_NAME = 'llc_dsap'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_LLC_SSAP, addr_family.L2VPN)
|
|
class FlowSpecLLCSSAP(_FlowSpecNumeric):
|
|
"""Source SAP field in LLC header in an Ethernet frame.
|
|
|
|
Set the 2 byte value of an Source SAP at value.
|
|
"""
|
|
COMPONENT_NAME = 'llc_ssap'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_LLC_CONTROL, addr_family.L2VPN)
|
|
class FlowSpecLLCControl(_FlowSpecNumeric):
|
|
"""Control field in LLC header in an Ethernet frame.
|
|
|
|
Set the Contorol field at value.
|
|
"""
|
|
COMPONENT_NAME = 'llc_control'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_SNAP, addr_family.L2VPN)
|
|
class FlowSpecSNAP(_FlowSpecNumeric):
|
|
"""Sub-Network Access Protocol field in an Ethernet frame.
|
|
|
|
Set the 5 byte SNAP field at value.
|
|
"""
|
|
COMPONENT_NAME = 'snap'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_VLAN_ID, addr_family.L2VPN)
|
|
class FlowSpecVLANID(_FlowSpecNumeric):
|
|
"""VLAN ID.
|
|
|
|
Set VLAN ID at value.
|
|
"""
|
|
COMPONENT_NAME = 'vlan_id'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_VLAN_COS, addr_family.L2VPN)
|
|
class FlowSpecVLANCoS(_FlowSpecNumeric):
|
|
"""VLAN CoS Fields in an Ethernet frame.
|
|
|
|
Set the 3 bit CoS field at value.
|
|
"""
|
|
COMPONENT_NAME = 'vlan_cos'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_INNER_VLAN_ID, addr_family.L2VPN)
|
|
class FlowSpecInnerVLANID(_FlowSpecNumeric):
|
|
"""Inner VLAN ID.
|
|
|
|
Set VLAN ID at value.
|
|
"""
|
|
COMPONENT_NAME = 'inner_vlan_id'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecL2VPNComponent.TYPE_INNER_VLAN_COS, addr_family.L2VPN)
|
|
class FlowSpecInnerVLANCoS(_FlowSpecNumeric):
|
|
"""VLAN CoS Fields in an Inner Ethernet frame.
|
|
|
|
Set the 3 bit CoS field at value..
|
|
"""
|
|
COMPONENT_NAME = 'inner_vlan_cos'
|
|
|
|
|
|
@_FlowSpecComponentBase.register_type(
|
|
_FlowSpecIPv6Component.TYPE_FLOW_LABEL, addr_family.IP6)
|
|
class FlowSpecIPv6FlowLabel(_FlowSpecNumeric):
|
|
COMPONENT_NAME = 'flow_label'
|
|
|
|
|
|
@functools.total_ordering
|
|
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 self.DEFAULT_AS and
|
|
route_target is self.DEFAULT_RT):
|
|
# We validate them
|
|
if (not self._is_valid_asn(origin_as) or
|
|
not self._is_valid_ext_comm_attr(route_target)):
|
|
raise ValueError('Invalid params.')
|
|
self.origin_as = origin_as
|
|
self.route_target = route_target
|
|
|
|
def _is_valid_asn(self, asn):
|
|
"""Returns True if the given AS number is Two or Four Octet."""
|
|
if isinstance(asn, six.integer_types) and 0 <= asn <= 0xffffffff:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def _is_valid_ext_comm_attr(self, attr):
|
|
"""Validates *attr* as string representation of RT or SOO.
|
|
|
|
Returns True if *attr* is as per our convention of RT or SOO, else
|
|
False. Our convention is to represent RT/SOO is a string with format:
|
|
*global_admin_part:local_admin_path*
|
|
"""
|
|
is_valid = True
|
|
|
|
if not isinstance(attr, str):
|
|
is_valid = False
|
|
else:
|
|
first, second = attr.split(':')
|
|
try:
|
|
if '.' in first:
|
|
socket.inet_aton(first)
|
|
else:
|
|
int(first)
|
|
int(second)
|
|
except (ValueError, socket.error):
|
|
is_valid = False
|
|
|
|
return is_valid
|
|
|
|
@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 self.DEFAULT_AS and
|
|
self._route_target is self.DEFAULT_RT):
|
|
return True
|
|
return False
|
|
|
|
def __lt__(self, other):
|
|
return ((self.origin_as, self.route_target) <
|
|
(other.origin_as, other.route_target))
|
|
|
|
def __eq__(self, other):
|
|
return ((self.origin_as, self.route_target) ==
|
|
(other.origin_as, other.route_target))
|
|
|
|
def __hash__(self):
|
|
return hash((self.origin_as, self.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 = _ExtendedCommunity(buf[idx:])
|
|
return cls(origin_as, route_target)
|
|
|
|
def serialize(self):
|
|
rt_nlri = b''
|
|
if not self.is_default_rtnlri():
|
|
rt_nlri += struct.pack('!I', self.origin_as)
|
|
# Encode route target
|
|
rt_nlri += self.route_target.serialize()
|
|
|
|
# RT Nlri is 12 octets
|
|
return struct.pack('B', (8 * 12)) + rt_nlri
|
|
|
|
|
|
def _addr_class_key(route_family):
|
|
return route_family.afi, route_family.safi
|
|
|
|
|
|
_ADDR_CLASSES = {
|
|
_addr_class_key(RF_IPv4_UC): IPAddrPrefix,
|
|
_addr_class_key(RF_IPv6_UC): IP6AddrPrefix,
|
|
_addr_class_key(RF_IPv4_MPLS): LabelledIPAddrPrefix,
|
|
_addr_class_key(RF_IPv6_MPLS): LabelledIP6AddrPrefix,
|
|
_addr_class_key(RF_IPv4_VPN): LabelledVPNIPAddrPrefix,
|
|
_addr_class_key(RF_IPv6_VPN): LabelledVPNIP6AddrPrefix,
|
|
_addr_class_key(RF_L2_EVPN): EvpnNLRI,
|
|
_addr_class_key(RF_IPv4_FLOWSPEC): FlowSpecIPv4NLRI,
|
|
_addr_class_key(RF_IPv6_FLOWSPEC): FlowSpecIPv6NLRI,
|
|
_addr_class_key(RF_VPNv4_FLOWSPEC): FlowSpecVPNv4NLRI,
|
|
_addr_class_key(RF_VPNv6_FLOWSPEC): FlowSpecVPNv6NLRI,
|
|
_addr_class_key(RF_L2VPN_FLOWSPEC): FlowSpecL2VPNNLRI,
|
|
_addr_class_key(RF_RTC_UC): RouteTargetMembershipNLRI,
|
|
}
|
|
|
|
|
|
def _get_addr_class(afi, safi):
|
|
try:
|
|
return _ADDR_CLASSES[(afi, safi)]
|
|
except KeyError:
|
|
return _BinAddrPrefix
|
|
|
|
|
|
class _OptParam(StringifyMixin, TypeDisp, _Value):
|
|
_PACK_STR = '!BB' # type, length
|
|
|
|
def __init__(self, type_, value=None, length=None):
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
self.type = type_
|
|
self.length = length
|
|
if value is not None:
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(type_, length) = struct.unpack_from(cls._PACK_STR,
|
|
six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
value = bytes(rest[:length])
|
|
rest = rest[length:]
|
|
subcls = cls._lookup_type(type_)
|
|
caps = subcls.parse_value(value)
|
|
if not isinstance(caps, list):
|
|
caps = [subcls(type_=type_, length=length, **caps[0])]
|
|
return caps, rest
|
|
|
|
def serialize(self):
|
|
# fixup
|
|
value = self.serialize_value()
|
|
self.length = len(value)
|
|
|
|
buf = bytearray()
|
|
msg_pack_into(self._PACK_STR, buf, 0, self.type, self.length)
|
|
return buf + value
|
|
|
|
|
|
@_OptParam.register_unknown_type()
|
|
class BGPOptParamUnknown(_OptParam):
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
return {
|
|
'value': buf
|
|
}, cls
|
|
|
|
def serialize_value(self):
|
|
return self.value
|
|
|
|
|
|
@_OptParam.register_type(BGP_OPT_CAPABILITY)
|
|
class _OptParamCapability(_OptParam, TypeDisp):
|
|
_CAP_HDR_PACK_STR = '!BB'
|
|
|
|
def __init__(self, cap_code=None, cap_value=None, cap_length=None,
|
|
type_=None, length=None):
|
|
super(_OptParamCapability, self).__init__(type_=BGP_OPT_CAPABILITY,
|
|
length=length)
|
|
if cap_code is None:
|
|
cap_code = self._rev_lookup_type(self.__class__)
|
|
self.cap_code = cap_code
|
|
if cap_value is not None:
|
|
self.cap_value = cap_value
|
|
if cap_length is not None:
|
|
self.cap_length = cap_length
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
caps = []
|
|
while len(buf) > 0:
|
|
(code, length) = struct.unpack_from(cls._CAP_HDR_PACK_STR,
|
|
six.binary_type(buf))
|
|
value = buf[struct.calcsize(cls._CAP_HDR_PACK_STR):]
|
|
buf = buf[length + 2:]
|
|
kwargs = {
|
|
'cap_code': code,
|
|
'cap_length': length,
|
|
}
|
|
subcls = cls._lookup_type(code)
|
|
kwargs.update(subcls.parse_cap_value(value))
|
|
caps.append(subcls(type_=BGP_OPT_CAPABILITY, length=length + 2,
|
|
**kwargs))
|
|
return caps
|
|
|
|
def serialize_value(self):
|
|
# fixup
|
|
cap_value = self.serialize_cap_value()
|
|
self.cap_length = len(cap_value)
|
|
|
|
buf = bytearray()
|
|
msg_pack_into(self._CAP_HDR_PACK_STR, buf, 0, self.cap_code,
|
|
self.cap_length)
|
|
return buf + cap_value
|
|
|
|
|
|
class _OptParamEmptyCapability(_OptParamCapability):
|
|
@classmethod
|
|
def parse_cap_value(cls, buf):
|
|
return {}
|
|
|
|
def serialize_cap_value(self):
|
|
return bytearray()
|
|
|
|
|
|
@_OptParamCapability.register_unknown_type()
|
|
class BGPOptParamCapabilityUnknown(_OptParamCapability):
|
|
@classmethod
|
|
def parse_cap_value(cls, buf):
|
|
return {'cap_value': buf}
|
|
|
|
def serialize_cap_value(self):
|
|
return self.cap_value
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_ROUTE_REFRESH)
|
|
class BGPOptParamCapabilityRouteRefresh(_OptParamEmptyCapability):
|
|
pass
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_ROUTE_REFRESH_CISCO)
|
|
class BGPOptParamCapabilityCiscoRouteRefresh(_OptParamEmptyCapability):
|
|
pass
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_ENHANCED_ROUTE_REFRESH)
|
|
class BGPOptParamCapabilityEnhancedRouteRefresh(_OptParamEmptyCapability):
|
|
pass
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_GRACEFUL_RESTART)
|
|
class BGPOptParamCapabilityGracefulRestart(_OptParamCapability):
|
|
_CAP_PACK_STR = "!H"
|
|
|
|
def __init__(self, flags, time, tuples, **kwargs):
|
|
super(BGPOptParamCapabilityGracefulRestart, self).__init__(**kwargs)
|
|
self.flags = flags
|
|
self.time = time
|
|
self.tuples = tuples
|
|
|
|
@classmethod
|
|
def parse_cap_value(cls, buf):
|
|
(restart, ) = struct.unpack_from(cls._CAP_PACK_STR,
|
|
six.binary_type(buf))
|
|
buf = buf[2:]
|
|
l = []
|
|
while len(buf) >= 4:
|
|
l.append(struct.unpack_from("!HBB", buf))
|
|
buf = buf[4:]
|
|
return {'flags': restart >> 12, 'time': restart & 0xfff, 'tuples': l}
|
|
|
|
def serialize_cap_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._CAP_PACK_STR, buf, 0, self.flags << 12 | self.time)
|
|
offset = 2
|
|
for i in self.tuples:
|
|
afi, safi, flags = i
|
|
msg_pack_into("!HBB", buf, offset, afi, safi, flags)
|
|
offset += 4
|
|
return buf
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_FOUR_OCTET_AS_NUMBER)
|
|
class BGPOptParamCapabilityFourOctetAsNumber(_OptParamCapability):
|
|
_CAP_PACK_STR = '!I'
|
|
|
|
def __init__(self, as_number, **kwargs):
|
|
super(BGPOptParamCapabilityFourOctetAsNumber, self).__init__(**kwargs)
|
|
self.as_number = as_number
|
|
|
|
@classmethod
|
|
def parse_cap_value(cls, buf):
|
|
(as_number, ) = struct.unpack_from(cls._CAP_PACK_STR,
|
|
six.binary_type(buf))
|
|
return {'as_number': as_number}
|
|
|
|
def serialize_cap_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._CAP_PACK_STR, buf, 0, self.as_number)
|
|
return buf
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_MULTIPROTOCOL)
|
|
class BGPOptParamCapabilityMultiprotocol(_OptParamCapability):
|
|
_CAP_PACK_STR = '!HBB' # afi, reserved, safi
|
|
|
|
def __init__(self, afi, safi, reserved=0, **kwargs):
|
|
super(BGPOptParamCapabilityMultiprotocol, self).__init__(**kwargs)
|
|
self.afi = afi
|
|
self.reserved = reserved
|
|
self.safi = safi
|
|
|
|
@classmethod
|
|
def parse_cap_value(cls, buf):
|
|
(afi, reserved, safi,) = struct.unpack_from(cls._CAP_PACK_STR,
|
|
six.binary_type(buf))
|
|
return {
|
|
'afi': afi,
|
|
'reserved': reserved,
|
|
'safi': safi,
|
|
}
|
|
|
|
def serialize_cap_value(self):
|
|
# fixup
|
|
self.reserved = 0
|
|
|
|
buf = bytearray()
|
|
msg_pack_into(self._CAP_PACK_STR, buf, 0,
|
|
self.afi, self.reserved, self.safi)
|
|
return buf
|
|
|
|
|
|
@_OptParamCapability.register_type(BGP_CAP_CARRYING_LABEL_INFO)
|
|
class BGPOptParamCapabilityCarryingLabelInfo(_OptParamEmptyCapability):
|
|
pass
|
|
|
|
|
|
class BGPWithdrawnRoute(IPAddrPrefix):
|
|
pass
|
|
|
|
|
|
class _PathAttribute(StringifyMixin, TypeDisp, _Value):
|
|
_PACK_STR = '!BB' # flags, type
|
|
_PACK_STR_LEN = '!B' # length
|
|
_PACK_STR_EXT_LEN = '!H' # length w/ BGP_ATTR_FLAG_EXTENDED_LENGTH
|
|
_ATTR_FLAGS = None
|
|
|
|
def __init__(self, value=None, flags=0, type_=None, length=None):
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
self.flags = flags
|
|
self.type = type_
|
|
self.length = length
|
|
if value is not None:
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(flags, type_) = struct.unpack_from(cls._PACK_STR,
|
|
six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
if (flags & BGP_ATTR_FLAG_EXTENDED_LENGTH) != 0:
|
|
len_pack_str = cls._PACK_STR_EXT_LEN
|
|
else:
|
|
len_pack_str = cls._PACK_STR_LEN
|
|
(length,) = struct.unpack_from(len_pack_str, six.binary_type(rest))
|
|
rest = rest[struct.calcsize(len_pack_str):]
|
|
value = bytes(rest[:length])
|
|
rest = rest[length:]
|
|
subcls = cls._lookup_type(type_)
|
|
return subcls(flags=flags, type_=type_, length=length,
|
|
**subcls.parse_value(value)), rest
|
|
|
|
def serialize(self):
|
|
# fixup
|
|
if self._ATTR_FLAGS is not None:
|
|
self.flags = (
|
|
self.flags
|
|
& ~(BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE)
|
|
| self._ATTR_FLAGS)
|
|
value = self.serialize_value()
|
|
self.length = len(value)
|
|
if self.flags & BGP_ATTR_FLAG_EXTENDED_LENGTH:
|
|
len_pack_str = self._PACK_STR_EXT_LEN
|
|
elif self.length > 255:
|
|
self.flags |= BGP_ATTR_FLAG_EXTENDED_LENGTH
|
|
len_pack_str = self._PACK_STR_EXT_LEN
|
|
else:
|
|
self.flags &= ~BGP_ATTR_FLAG_EXTENDED_LENGTH
|
|
len_pack_str = self._PACK_STR_LEN
|
|
|
|
buf = bytearray()
|
|
msg_pack_into(self._PACK_STR, buf, 0, self.flags, self.type)
|
|
msg_pack_into(len_pack_str, buf, len(buf), self.length)
|
|
return buf + value
|
|
|
|
|
|
@_PathAttribute.register_unknown_type()
|
|
class BGPPathAttributeUnknown(_PathAttribute):
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
return {
|
|
'value': buf
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return self.value
|
|
|
|
|
|
class _PathAttributeUint32(_PathAttribute):
|
|
_VALUE_PACK_STR = '!I'
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_ORIGIN)
|
|
class BGPPathAttributeOrigin(_PathAttribute):
|
|
_VALUE_PACK_STR = '!B'
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
|
|
|
|
|
|
class _BGPPathAttributeAsPathCommon(_PathAttribute):
|
|
_AS_SET = 1
|
|
_AS_SEQUENCE = 2
|
|
_SEG_HDR_PACK_STR = '!BB'
|
|
_AS_PACK_STR = None
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
|
|
|
|
def __init__(self, value, as_pack_str=None, flags=0, type_=None,
|
|
length=None):
|
|
super(_BGPPathAttributeAsPathCommon, self).__init__(value=value,
|
|
flags=flags,
|
|
type_=type_,
|
|
length=length)
|
|
if as_pack_str:
|
|
self._AS_PACK_STR = as_pack_str
|
|
|
|
@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, max_count=0):
|
|
"""Check if *local_as* is already present on path list."""
|
|
_count = 0
|
|
for as_path_seg in self.value:
|
|
_count += list(as_path_seg).count(local_as)
|
|
return _count > max_count
|
|
|
|
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 _is_valid_16bit_as_path(cls, buf):
|
|
|
|
two_byte_as_size = struct.calcsize('!H')
|
|
|
|
while buf:
|
|
(type_, num_as) = struct.unpack_from(cls._SEG_HDR_PACK_STR,
|
|
six.binary_type(buf))
|
|
|
|
if type_ is not cls._AS_SET and type_ is not cls._AS_SEQUENCE:
|
|
return False
|
|
|
|
buf = buf[struct.calcsize(cls._SEG_HDR_PACK_STR):]
|
|
|
|
if len(buf) < num_as * two_byte_as_size:
|
|
return False
|
|
|
|
buf = buf[num_as * two_byte_as_size:]
|
|
|
|
return True
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
result = []
|
|
|
|
if cls._is_valid_16bit_as_path(buf):
|
|
as_pack_str = '!H'
|
|
else:
|
|
as_pack_str = '!I'
|
|
|
|
while buf:
|
|
(type_, num_as) = struct.unpack_from(cls._SEG_HDR_PACK_STR,
|
|
six.binary_type(buf))
|
|
buf = buf[struct.calcsize(cls._SEG_HDR_PACK_STR):]
|
|
l = []
|
|
for _ in range(0, num_as):
|
|
(as_number,) = struct.unpack_from(as_pack_str,
|
|
six.binary_type(buf))
|
|
buf = buf[struct.calcsize(as_pack_str):]
|
|
l.append(as_number)
|
|
if type_ == cls._AS_SET:
|
|
result.append(set(l))
|
|
elif type_ == cls._AS_SEQUENCE:
|
|
result.append(l)
|
|
else:
|
|
# protocol error
|
|
raise struct.error('Unsupported segment type: %s' % type_)
|
|
return {
|
|
'value': result,
|
|
'as_pack_str': as_pack_str,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
offset = 0
|
|
for e in self.value:
|
|
if isinstance(e, set):
|
|
type_ = self._AS_SET
|
|
elif isinstance(e, list):
|
|
type_ = self._AS_SEQUENCE
|
|
else:
|
|
raise struct.error(
|
|
'Element of %s.value must be of type set or list' %
|
|
self.__class__.__name__)
|
|
l = list(e)
|
|
num_as = len(l)
|
|
if num_as == 0:
|
|
continue
|
|
msg_pack_into(self._SEG_HDR_PACK_STR, buf, offset, type_, num_as)
|
|
offset += struct.calcsize(self._SEG_HDR_PACK_STR)
|
|
for i in l:
|
|
msg_pack_into(self._AS_PACK_STR, buf, offset, i)
|
|
offset += struct.calcsize(self._AS_PACK_STR)
|
|
return buf
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_AS_PATH)
|
|
class BGPPathAttributeAsPath(_BGPPathAttributeAsPathCommon):
|
|
# XXX depends on negotiated capability, AS numbers can be 32 bit.
|
|
# while wireshark seems to attempt auto-detect, it seems that
|
|
# there's no way to detect it reliably. for example, the
|
|
# following byte sequence can be interpreted in two ways.
|
|
# 01 02 99 88 77 66 02 01 55 44
|
|
# AS_SET num=2 9988 7766 AS_SEQUENCE num=1 5544
|
|
# AS_SET num=2 99887766 02015544
|
|
# we first check whether AS path can be parsed in 16bit format and if
|
|
# it fails, we try to parse as 32bit
|
|
_AS_PACK_STR = '!H'
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_AS4_PATH)
|
|
class BGPPathAttributeAs4Path(_BGPPathAttributeAsPathCommon):
|
|
_AS_PACK_STR = '!I'
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL
|
|
|
|
@classmethod
|
|
def _is_valid_16bit_as_path(cls, buf):
|
|
return False
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_NEXT_HOP)
|
|
class BGPPathAttributeNextHop(_PathAttribute):
|
|
_VALUE_PACK_STR = '!4s'
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
|
|
_TYPE = {
|
|
'ascii': [
|
|
'value'
|
|
]
|
|
}
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(ip_addr,) = struct.unpack_from(cls._VALUE_PACK_STR,
|
|
six.binary_type(buf))
|
|
return {
|
|
'value': addrconv.ipv4.bin_to_text(ip_addr),
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0,
|
|
addrconv.ipv4.text_to_bin(self.value))
|
|
return buf
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_MULTI_EXIT_DISC)
|
|
class BGPPathAttributeMultiExitDisc(_PathAttributeUint32):
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_LOCAL_PREF)
|
|
class BGPPathAttributeLocalPref(_PathAttributeUint32):
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_ATOMIC_AGGREGATE)
|
|
class BGPPathAttributeAtomicAggregate(_PathAttribute):
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
return {}
|
|
|
|
def serialize_value(self):
|
|
return b''
|
|
|
|
|
|
class _BGPPathAttributeAggregatorCommon(_PathAttribute):
|
|
_VALUE_PACK_STR = None
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
|
|
_TYPE = {
|
|
'ascii': [
|
|
'addr'
|
|
]
|
|
}
|
|
|
|
def __init__(self, as_number, addr, flags=0, type_=None, length=None):
|
|
super(_BGPPathAttributeAggregatorCommon, self).__init__(flags=flags,
|
|
type_=type_,
|
|
length=length)
|
|
self.as_number = as_number
|
|
self.addr = addr
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(as_number, addr) = struct.unpack_from(cls._VALUE_PACK_STR,
|
|
six.binary_type(buf))
|
|
return {
|
|
'as_number': as_number,
|
|
'addr': addrconv.ipv4.bin_to_text(addr),
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0, self.as_number,
|
|
addrconv.ipv4.text_to_bin(self.addr))
|
|
return buf
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_AGGREGATOR)
|
|
class BGPPathAttributeAggregator(_BGPPathAttributeAggregatorCommon):
|
|
# Note: AS numbers can be Two-Octet or Four-Octet.
|
|
# This class would detect it by the value length field.
|
|
# For example,
|
|
# - if the value field length is 6 (='!H4s'), AS number should
|
|
# be Two-Octet.
|
|
# - else if the length is 8 (='!I4s'), AS number should be Four-Octet.
|
|
_TWO_OCTET_VALUE_PACK_STR = '!H4s'
|
|
_FOUR_OCTET_VALUE_PACK_STR = '!I4s'
|
|
_VALUE_PACK_STR = _TWO_OCTET_VALUE_PACK_STR # Two-Octet by default
|
|
_FOUR_OCTET_VALUE_SIZE = struct.calcsize(_FOUR_OCTET_VALUE_PACK_STR)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
if len(buf) == cls._FOUR_OCTET_VALUE_SIZE:
|
|
cls._VALUE_PACK_STR = cls._FOUR_OCTET_VALUE_PACK_STR
|
|
return super(BGPPathAttributeAggregator, cls).parse_value(buf)
|
|
|
|
def serialize_value(self):
|
|
if self.as_number > 0xffff:
|
|
self._VALUE_PACK_STR = self._FOUR_OCTET_VALUE_PACK_STR
|
|
return super(BGPPathAttributeAggregator, self).serialize_value()
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_AS4_AGGREGATOR)
|
|
class BGPPathAttributeAs4Aggregator(_BGPPathAttributeAggregatorCommon):
|
|
_VALUE_PACK_STR = '!I4s'
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_COMMUNITIES)
|
|
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,
|
|
type_=type_,
|
|
length=length)
|
|
self.communities = communities
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
rest = buf
|
|
communities = []
|
|
elem_size = struct.calcsize(cls._VALUE_PACK_STR)
|
|
while len(rest) >= elem_size:
|
|
(comm, ) = struct.unpack_from(cls._VALUE_PACK_STR,
|
|
six.binary_type(rest))
|
|
communities.append(comm)
|
|
rest = rest[elem_size:]
|
|
return {
|
|
'communities': communities,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
for comm in self.communities:
|
|
bincomm = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, bincomm, 0, comm)
|
|
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 == BGPPathAttributeCommunities.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 == BGPPathAttributeCommunities.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 == BGPPathAttributeCommunities.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
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_ORIGINATOR_ID)
|
|
class BGPPathAttributeOriginatorId(_PathAttribute):
|
|
# ORIGINATOR_ID is a new optional, non-transitive BGP attribute of Type
|
|
# code 9. This attribute is 4 bytes long and it will be created by an
|
|
# RR in reflecting a route.
|
|
_VALUE_PACK_STR = '!4s'
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
|
|
_TYPE = {
|
|
'asciilist': [
|
|
'value'
|
|
]
|
|
}
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(originator_id,) = struct.unpack_from(cls._VALUE_PACK_STR,
|
|
six.binary_type(buf))
|
|
return {
|
|
'value': addrconv.ipv4.bin_to_text(originator_id),
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0,
|
|
addrconv.ipv4.text_to_bin(self.value))
|
|
return buf
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_CLUSTER_LIST)
|
|
class BGPPathAttributeClusterList(_PathAttribute):
|
|
# CLUSTER_LIST is a new, optional, non-transitive BGP attribute of Type
|
|
# code 10. It is a sequence of CLUSTER_ID values representing the
|
|
# reflection path that the route has passed.
|
|
_VALUE_PACK_STR = '!4s'
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
|
|
_TYPE = {
|
|
'ascii': [
|
|
'value'
|
|
]
|
|
}
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
rest = buf
|
|
cluster_list = []
|
|
elem_size = struct.calcsize(cls._VALUE_PACK_STR)
|
|
while len(rest) >= elem_size:
|
|
(cluster_id, ) = struct.unpack_from(
|
|
cls._VALUE_PACK_STR, six.binary_type(rest))
|
|
cluster_list.append(addrconv.ipv4.bin_to_text(cluster_id))
|
|
rest = rest[elem_size:]
|
|
return {
|
|
'value': cluster_list,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
offset = 0
|
|
for cluster_id in self.value:
|
|
msg_pack_into(
|
|
self._VALUE_PACK_STR,
|
|
buf,
|
|
offset,
|
|
addrconv.ipv4.text_to_bin(cluster_id))
|
|
offset += struct.calcsize(self._VALUE_PACK_STR)
|
|
return buf
|
|
|
|
|
|
# Extended Communities
|
|
# RFC 4360
|
|
# RFC 5668
|
|
# IANA registry:
|
|
# https://www.iana.org/assignments/bgp-extended-communities/
|
|
# bgp-extended-communities.xml
|
|
#
|
|
# type
|
|
# high low
|
|
# 00 sub-type Two-Octet AS Specific Extended Community (transitive)
|
|
# 40 sub-type Two-Octet AS Specific Extended Community
|
|
# payload:
|
|
# 2 byte Global Administrator (AS number)
|
|
# 4 byte Local Administrator (defined by sub-type)
|
|
# 01 sub-type IPv4 Address Specific Extended Community (transitive)
|
|
# 41 sub-type IPv4 Address Specific Extended Community
|
|
# payload:
|
|
# 4 byte Global Administrator (IPv4 address)
|
|
# 2 byte Local Administrator (defined by sub-type)
|
|
# 03 sub-type Opaque Extended Community (transitive)
|
|
# 43 sub-type Opaque Extended Community
|
|
# payload:
|
|
# 6 byte opaque value (defined by sub-type)
|
|
#
|
|
# 00 02 Route Target Community (two-octet AS specific)
|
|
# 01 02 Route Target Community (IPv4 address specific)
|
|
# 02 02 Route Target Community (four-octet AS specific, RFC 5668)
|
|
# 00 03 Route Origin Community (two-octet AS specific)
|
|
# 01 03 Route Origin Community (IPv4 address specific)
|
|
# 02 03 Route Origin Community (four-octet AS specific, RFC 5668)
|
|
# 06 sub-type Ethernet VPN Extended Community (RFC 7432)
|
|
# 80 sub-type Flow Specification Extended Community (RFC 5575)
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_EXTENDED_COMMUNITIES)
|
|
class BGPPathAttributeExtendedCommunities(_PathAttribute):
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
|
|
_class_prefixes = ['BGP']
|
|
|
|
def __init__(self, communities,
|
|
flags=0, type_=None, length=None):
|
|
super(BGPPathAttributeExtendedCommunities,
|
|
self).__init__(flags=flags,
|
|
type_=type_,
|
|
length=length)
|
|
self.communities = communities
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
rest = buf
|
|
communities = []
|
|
while rest:
|
|
comm, rest = _ExtendedCommunity.parse(rest)
|
|
communities.append(comm)
|
|
return {
|
|
'communities': communities,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
for comm in self.communities:
|
|
buf += comm.serialize()
|
|
return buf
|
|
|
|
def _community_list(self, subtype):
|
|
_list = []
|
|
for comm in (c for c in self.communities
|
|
if hasattr(c, "subtype") and c.subtype == subtype):
|
|
if comm.type == 0 or comm.type == 2:
|
|
_list.append('%d:%d' % (comm.as_number,
|
|
comm.local_administrator))
|
|
elif comm.type == 1:
|
|
_list.append('%s:%d' % (comm.ipv4_address,
|
|
comm.local_administrator))
|
|
return _list
|
|
|
|
@property
|
|
def rt_list(self):
|
|
return self._community_list(2)
|
|
|
|
@property
|
|
def soo_list(self):
|
|
return self._community_list(3)
|
|
|
|
|
|
class _ExtendedCommunity(StringifyMixin, TypeDisp, _Value):
|
|
_PACK_STR = '!B7s' # type high (+ type low), value
|
|
_PACK_STR_SIZE = struct.calcsize(_PACK_STR)
|
|
_SUBTYPE_PACK_STR = '!B' # subtype
|
|
IANA_AUTHORITY = 0x80
|
|
TRANSITIVE = 0x40
|
|
_TYPE_HIGH_MASK = ~TRANSITIVE
|
|
|
|
TWO_OCTET_AS_SPECIFIC = 0x00
|
|
IPV4_ADDRESS_SPECIFIC = 0x01
|
|
FOUR_OCTET_AS_SPECIFIC = 0x02
|
|
OPAQUE = 0x03
|
|
SUBTYPE_ENCAPSULATION = 0x0c
|
|
ENCAPSULATION = (OPAQUE, SUBTYPE_ENCAPSULATION)
|
|
EVPN = 0x06
|
|
SUBTYPE_EVPN_MAC_MOBILITY = 0x00
|
|
SUBTYPE_EVPN_ESI_LABEL = 0x01
|
|
SUBTYPE_EVPN_ES_IMPORT_RT = 0x02
|
|
EVPN_MAC_MOBILITY = (EVPN, SUBTYPE_EVPN_MAC_MOBILITY)
|
|
EVPN_ESI_LABEL = (EVPN, SUBTYPE_EVPN_ESI_LABEL)
|
|
EVPN_ES_IMPORT_RT = (EVPN, SUBTYPE_EVPN_ES_IMPORT_RT)
|
|
FLOWSPEC = 0x80
|
|
FLOWSPEC_L2VPN = 0x08
|
|
SUBTYPE_FLOWSPEC_TRAFFIC_RATE = 0x06
|
|
SUBTYPE_FLOWSPEC_TRAFFIC_ACTION = 0x07
|
|
SUBTYPE_FLOWSPEC_REDIRECT = 0x08
|
|
SUBTYPE_FLOWSPEC_TRAFFIC_REMARKING = 0x09
|
|
SUBTYPE_FLOWSPEC_VLAN_ACTION = 0x0a
|
|
SUBTYPE_FLOWSPEC_TPID_ACTION = 0x0b
|
|
FLOWSPEC_TRAFFIC_RATE = (FLOWSPEC, SUBTYPE_FLOWSPEC_TRAFFIC_RATE)
|
|
FLOWSPEC_TRAFFIC_ACTION = (FLOWSPEC, SUBTYPE_FLOWSPEC_TRAFFIC_ACTION)
|
|
FLOWSPEC_REDIRECT = (FLOWSPEC, SUBTYPE_FLOWSPEC_REDIRECT)
|
|
FLOWSPEC_TRAFFIC_REMARKING = (FLOWSPEC, SUBTYPE_FLOWSPEC_TRAFFIC_REMARKING)
|
|
FLOWSPEC_VLAN_ACTION = (FLOWSPEC_L2VPN, SUBTYPE_FLOWSPEC_VLAN_ACTION)
|
|
FLOWSPEC_TPID_ACTION = (FLOWSPEC_L2VPN, SUBTYPE_FLOWSPEC_TPID_ACTION)
|
|
|
|
def __init__(self, type_=None):
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
if isinstance(type_, (tuple, list)):
|
|
type_ = type_[0]
|
|
self.type = type_
|
|
|
|
@classmethod
|
|
def parse_subtype(cls, buf):
|
|
(subtype,) = struct.unpack_from(cls._SUBTYPE_PACK_STR, buf)
|
|
return subtype
|
|
|
|
@classmethod
|
|
def parse(cls, buf):
|
|
(type_, value) = struct.unpack_from(cls._PACK_STR, buf)
|
|
rest = buf[cls._PACK_STR_SIZE:]
|
|
type_low = type_ & cls._TYPE_HIGH_MASK
|
|
subtype = cls.parse_subtype(value)
|
|
subcls = cls._lookup_type((type_low, subtype))
|
|
if subcls == cls._UNKNOWN_TYPE:
|
|
subcls = cls._lookup_type(type_low)
|
|
return subcls(type_=type_, **subcls.parse_value(value)), rest
|
|
|
|
def serialize(self):
|
|
return struct.pack(self._PACK_STR, self.type,
|
|
self.serialize_value())
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.TWO_OCTET_AS_SPECIFIC)
|
|
class BGPTwoOctetAsSpecificExtendedCommunity(_ExtendedCommunity):
|
|
_VALUE_PACK_STR = '!BHI' # sub type, as number, local adm
|
|
_VALUE_FIELDS = ['subtype', 'as_number', 'local_administrator']
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPTwoOctetAsSpecificExtendedCommunity, self).__init__()
|
|
self.do_init(BGPTwoOctetAsSpecificExtendedCommunity, self, kwargs)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.IPV4_ADDRESS_SPECIFIC)
|
|
class BGPIPv4AddressSpecificExtendedCommunity(_ExtendedCommunity):
|
|
_VALUE_PACK_STR = '!B4sH' # sub type, IPv4 address, local adm
|
|
_VALUE_FIELDS = ['subtype', 'ipv4_address', 'local_administrator']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'ipv4_address'
|
|
]
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPIPv4AddressSpecificExtendedCommunity, self).__init__()
|
|
self.do_init(BGPIPv4AddressSpecificExtendedCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
d_ = super(BGPIPv4AddressSpecificExtendedCommunity,
|
|
cls).parse_value(buf)
|
|
d_['ipv4_address'] = addrconv.ipv4.bin_to_text(d_['ipv4_address'])
|
|
return d_
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(self._VALUE_PACK_STR, self.subtype,
|
|
addrconv.ipv4.text_to_bin(self.ipv4_address),
|
|
self.local_administrator)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.FOUR_OCTET_AS_SPECIFIC)
|
|
class BGPFourOctetAsSpecificExtendedCommunity(_ExtendedCommunity):
|
|
_VALUE_PACK_STR = '!BIH' # sub type, as number, local adm
|
|
_VALUE_FIELDS = ['subtype', 'as_number', 'local_administrator']
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFourOctetAsSpecificExtendedCommunity, self).__init__()
|
|
self.do_init(BGPFourOctetAsSpecificExtendedCommunity, self, kwargs)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.OPAQUE)
|
|
class BGPOpaqueExtendedCommunity(_ExtendedCommunity):
|
|
_VALUE_PACK_STR = '!B6s'
|
|
_VALUE_FIELDS = ['subtype', 'opaque']
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPOpaqueExtendedCommunity, self).__init__()
|
|
self.do_init(BGPOpaqueExtendedCommunity, self, kwargs)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.ENCAPSULATION)
|
|
class BGPEncapsulationExtendedCommunity(_ExtendedCommunity):
|
|
_VALUE_PACK_STR = '!B4xH'
|
|
_VALUE_FIELDS = ['subtype', 'tunnel_type']
|
|
|
|
# BGP Tunnel Encapsulation Attribute Tunnel Types
|
|
# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#tunnel-types
|
|
TUNNEL_TYPE_L2TPV3 = 1
|
|
TUNNEL_TYPE_GRE = 2
|
|
TUNNEL_TYPE_IP_IN_IP = 7
|
|
TUNNEL_TYPE_VXLAN = 8
|
|
TUNNEL_TYPE_NVGRE = 9
|
|
TUNNEL_TYPE_MPLS = 10
|
|
TUNNEL_TYPE_MPLS_IN_GRE = 11
|
|
TUNNEL_TYPE_VXLAN_GRE = 12
|
|
TUNNEL_TYPE_MPLS_IN_UDP = 13
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPEncapsulationExtendedCommunity, self).__init__()
|
|
self.do_init(BGPEncapsulationExtendedCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def from_str(cls, tunnel_type):
|
|
"""
|
|
Returns an instance identified with the given `tunnel_type`.
|
|
|
|
`tunnel_type` should be a str type value and corresponding to
|
|
BGP Tunnel Encapsulation Attribute Tunnel Type constants name
|
|
omitting `TUNNEL_TYPE_` prefix.
|
|
|
|
Example:
|
|
- `gre` means TUNNEL_TYPE_GRE
|
|
- `vxlan` means TUNNEL_TYPE_VXLAN
|
|
|
|
And raises AttributeError when the corresponding Tunnel Type
|
|
is not found to the given `tunnel_type`.
|
|
"""
|
|
return cls(subtype=_ExtendedCommunity.SUBTYPE_ENCAPSULATION,
|
|
tunnel_type=getattr(cls,
|
|
'TUNNEL_TYPE_' + tunnel_type.upper()))
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_MAC_MOBILITY)
|
|
class BGPEvpnMacMobilityExtendedCommunity(_ExtendedCommunity):
|
|
"""
|
|
MAC Mobility Extended Community
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x06 | Sub-Type=0x00 |Flags(1 octet)| Reserved=0 |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Sequence Number |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!BBxI'
|
|
_VALUE_FIELDS = ['subtype', 'flags', 'sequence_number']
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPEvpnMacMobilityExtendedCommunity, self).__init__()
|
|
self.do_init(BGPEvpnMacMobilityExtendedCommunity, self, kwargs)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_ESI_LABEL)
|
|
class BGPEvpnEsiLabelExtendedCommunity(_ExtendedCommunity):
|
|
"""
|
|
ESI Label Extended Community
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x06 | Sub-Type=0x01 | Flags(1 octet)| Reserved=0 |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Reserved=0 | ESI Label |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!BB2x3s'
|
|
_VALUE_FIELDS = ['subtype', 'flags']
|
|
|
|
# Classification for Flags.
|
|
SINGLE_ACTIVE_BIT = 1 << 0
|
|
|
|
def __init__(self, label=None, mpls_label=None, vni=None, **kwargs):
|
|
super(BGPEvpnEsiLabelExtendedCommunity, self).__init__()
|
|
self.do_init(BGPEvpnEsiLabelExtendedCommunity, self, kwargs)
|
|
|
|
if label:
|
|
# If binary type label field value is specified, stores it
|
|
# and decodes as MPLS label and VNI.
|
|
self._label = label
|
|
self._mpls_label, _ = mpls.label_from_bin(label)
|
|
self._vni = vxlan.vni_from_bin(label)
|
|
else:
|
|
# If either MPLS label or VNI is specified, stores it
|
|
# and encodes into binary type label field value.
|
|
self._label = self._serialize_label(mpls_label, vni)
|
|
self._mpls_label = mpls_label
|
|
self._vni = vni
|
|
|
|
def _serialize_label(self, mpls_label, vni):
|
|
if mpls_label:
|
|
return mpls.label_to_bin(mpls_label, is_bos=True)
|
|
elif vni:
|
|
return vxlan.vni_to_bin(vni)
|
|
else:
|
|
return b'\x00' * 3
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(subtype, flags,
|
|
label) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'subtype': subtype,
|
|
'flags': flags,
|
|
'label': label,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(self._VALUE_PACK_STR, self.subtype, self.flags,
|
|
self._label)
|
|
|
|
@property
|
|
def mpls_label(self):
|
|
return self._mpls_label
|
|
|
|
@mpls_label.setter
|
|
def mpls_label(self, mpls_label):
|
|
self._label = mpls.label_to_bin(mpls_label, is_bos=True)
|
|
self._mpls_label = mpls_label
|
|
self._vni = None # disables VNI
|
|
|
|
@property
|
|
def vni(self):
|
|
return self._vni
|
|
|
|
@vni.setter
|
|
def vni(self, vni):
|
|
self._label = vxlan.vni_to_bin(vni)
|
|
self._mpls_label = None # disables ESI label
|
|
self._vni = vni
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_ES_IMPORT_RT)
|
|
class BGPEvpnEsImportRTExtendedCommunity(_ExtendedCommunity):
|
|
"""
|
|
ES-Import Route Target Extended Community
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x06 | Sub-Type=0x02 | ES-Import |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | ES-Import Cont'd |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!B6s'
|
|
_VALUE_FIELDS = ['subtype', 'es_import']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'es_import'
|
|
]
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPEvpnEsImportRTExtendedCommunity, self).__init__()
|
|
self.do_init(BGPEvpnEsImportRTExtendedCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(subtype, es_import) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'subtype': subtype,
|
|
'es_import': addrconv.mac.bin_to_text(es_import),
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(self._VALUE_PACK_STR, self.subtype,
|
|
addrconv.mac.text_to_bin(self.es_import))
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.FLOWSPEC_TRAFFIC_RATE)
|
|
class BGPFlowSpecTrafficRateCommunity(_ExtendedCommunity):
|
|
"""
|
|
Flow Specification Traffic Filtering Actions for Traffic Rate.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
as_number Autonomous System number.
|
|
rate_info rate information.
|
|
========================== ===============================================
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x80 | Sub-Type=0x06 | AS number |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Rate information |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!BHf'
|
|
_VALUE_FIELDS = ['subtype', 'as_number', 'rate_info']
|
|
ACTION_NAME = 'traffic_rate'
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFlowSpecTrafficRateCommunity, self).__init__()
|
|
kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TRAFFIC_RATE
|
|
self.do_init(BGPFlowSpecTrafficRateCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(subtype, as_number,
|
|
rate_info) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'subtype': subtype,
|
|
'as_number': as_number,
|
|
'rate_info': rate_info,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(self._VALUE_PACK_STR, self.subtype,
|
|
self.as_number, self.rate_info)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.FLOWSPEC_TRAFFIC_ACTION)
|
|
class BGPFlowSpecTrafficActionCommunity(_ExtendedCommunity):
|
|
"""
|
|
Flow Specification Traffic Filtering Actions for Traffic Action.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
action Apply action.
|
|
The supported action are
|
|
``SAMPLE`` and ``TERMINAL``.
|
|
========================== ===============================================
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x80 | Sub-Type=0x07 | Traffic-action |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Traffic-action Cont'd |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
# Traffic-action format
|
|
# 40 41 42 43 44 45 46 47
|
|
# +---+---+---+---+---+---+---+---+
|
|
# | reserved | S | T |
|
|
# +---+---+---+---+---+---+---+---+
|
|
|
|
_VALUE_PACK_STR = '!B5xB'
|
|
_VALUE_FIELDS = ['subtype', 'action']
|
|
ACTION_NAME = 'traffic_action'
|
|
SAMPLE = 1 << 1
|
|
TERMINAL = 1 << 0
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFlowSpecTrafficActionCommunity, self).__init__()
|
|
kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TRAFFIC_ACTION
|
|
self.do_init(BGPFlowSpecTrafficActionCommunity, self, kwargs)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(_ExtendedCommunity.FLOWSPEC_REDIRECT)
|
|
class BGPFlowSpecRedirectCommunity(BGPTwoOctetAsSpecificExtendedCommunity):
|
|
"""
|
|
Flow Specification Traffic Filtering Actions for Redirect.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
as_number Autonomous System number.
|
|
local_administrator Local Administrator.
|
|
========================== ===============================================
|
|
"""
|
|
ACTION_NAME = 'redirect'
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPTwoOctetAsSpecificExtendedCommunity, self).__init__()
|
|
kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_REDIRECT
|
|
self.do_init(BGPTwoOctetAsSpecificExtendedCommunity, self, kwargs)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(
|
|
_ExtendedCommunity.FLOWSPEC_TRAFFIC_REMARKING)
|
|
class BGPFlowSpecTrafficMarkingCommunity(_ExtendedCommunity):
|
|
"""
|
|
Flow Specification Traffic Filtering Actions for Traffic Marking.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
dscp Differentiated Services Code Point.
|
|
========================== ===============================================
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x80 | Sub-Type=0x09 | Reserved=0 |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Reserved=0 | Dscp |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!B5xB'
|
|
_VALUE_FIELDS = ['subtype', 'dscp']
|
|
ACTION_NAME = 'traffic_marking'
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFlowSpecTrafficMarkingCommunity, self).__init__()
|
|
kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TRAFFIC_REMARKING
|
|
self.do_init(BGPFlowSpecTrafficMarkingCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(subtype, dscp) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
return {
|
|
'subtype': subtype,
|
|
'dscp': dscp,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(self._VALUE_PACK_STR, self.subtype, self.dscp)
|
|
|
|
|
|
# TODO
|
|
# Implement "Redirect-IPv6" [draft-ietf-idr-flow-spec-v6-08]
|
|
|
|
|
|
@_ExtendedCommunity.register_type(
|
|
_ExtendedCommunity.FLOWSPEC_VLAN_ACTION)
|
|
class BGPFlowSpecVlanActionCommunity(_ExtendedCommunity):
|
|
"""
|
|
Flow Specification Vlan Actions.
|
|
|
|
========= ===============================================
|
|
Attribute Description
|
|
========= ===============================================
|
|
actions_1 Bit representation of actions.
|
|
Supported actions are
|
|
``POP``, ``PUSH``, ``SWAP``, ``REWRITE_INNER``, ``REWRITE_OUTER``.
|
|
actions_2 Same as ``actions_1``.
|
|
vlan_1 VLAN ID used by ``actions_1``.
|
|
cos_1 Class of Service used by ``actions_1``.
|
|
vlan_2 VLAN ID used by ``actions_2``.
|
|
cos_2 Class of Service used by ``actions_2``.
|
|
========= ===============================================
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x08 | Sub-Type=0x0a |PO1|PU1|SW1|RT1|RO1|...|PO2|...|
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | VLAN ID1 | COS1 |0| VLAN ID2 | COS2 |0|
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!BBBHH'
|
|
_VALUE_FIELDS = [
|
|
'subtype',
|
|
'actions_1',
|
|
'actions_2',
|
|
'vlan_1',
|
|
'vlan_2',
|
|
'cos_1',
|
|
'cos_2']
|
|
ACTION_NAME = 'vlan_action'
|
|
_COS_MASK = 0x07
|
|
|
|
POP = 1 << 7
|
|
PUSH = 1 << 6
|
|
SWAP = 1 << 5
|
|
REWRITE_INNER = 1 << 4
|
|
REWRITE_OUTER = 1 << 3
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFlowSpecVlanActionCommunity, self).__init__()
|
|
kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_VLAN_ACTION
|
|
self.do_init(BGPFlowSpecVlanActionCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(subtype, actions_1, actions_2,
|
|
vlan_cos_1, vlan_cos_2) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
|
|
return {
|
|
'subtype': subtype,
|
|
'actions_1': actions_1,
|
|
'vlan_1': int(vlan_cos_1 >> 4),
|
|
'cos_1': int((vlan_cos_1 >> 1) & cls._COS_MASK),
|
|
'actions_2': actions_2,
|
|
'vlan_2': int(vlan_cos_2 >> 4),
|
|
'cos_2': int((vlan_cos_2 >> 1) & cls._COS_MASK)
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(
|
|
self._VALUE_PACK_STR,
|
|
self.subtype,
|
|
self.actions_1,
|
|
self.actions_2,
|
|
(self.vlan_1 << 4) + (self.cos_1 << 1),
|
|
(self.vlan_2 << 4) + (self.cos_2 << 1),
|
|
)
|
|
|
|
|
|
@_ExtendedCommunity.register_type(
|
|
_ExtendedCommunity.FLOWSPEC_TPID_ACTION)
|
|
class BGPFlowSpecTPIDActionCommunity(_ExtendedCommunity):
|
|
"""
|
|
Flow Specification TPID Actions.
|
|
|
|
========= =========================================================
|
|
Attribute Description
|
|
========= =========================================================
|
|
actions Bit representation of actions.
|
|
Supported actions are
|
|
``TI(inner TPID action)`` and ``TO(outer TPID action)``.
|
|
tpid_1 TPID used by ``TI``.
|
|
tpid_2 TPID used by ``TO``.
|
|
========= =========================================================
|
|
"""
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | Type=0x08 | Sub-Type=0x0b |TI|TO| Reserved=0 |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | TPID1 | TPID2 |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
_VALUE_PACK_STR = '!BHHH'
|
|
_VALUE_FIELDS = ['subtype', 'actions', 'tpid_1', 'tpid_2']
|
|
ACTION_NAME = 'tpid_action'
|
|
|
|
TI = 1 << 15
|
|
TO = 1 << 14
|
|
|
|
def __init__(self, **kwargs):
|
|
super(BGPFlowSpecTPIDActionCommunity, self).__init__()
|
|
kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TPID_ACTION
|
|
self.do_init(BGPFlowSpecTPIDActionCommunity, self, kwargs)
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(subtype, actions, tpid_1, tpid_2) = struct.unpack_from(
|
|
cls._VALUE_PACK_STR, buf)
|
|
|
|
return {
|
|
'subtype': subtype,
|
|
'actions': actions,
|
|
'tpid_1': tpid_1,
|
|
'tpid_2': tpid_2,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
return struct.pack(
|
|
self._VALUE_PACK_STR,
|
|
self.subtype,
|
|
self.actions,
|
|
self.tpid_1,
|
|
self.tpid_2,
|
|
)
|
|
|
|
|
|
@_ExtendedCommunity.register_unknown_type()
|
|
class BGPUnknownExtendedCommunity(_ExtendedCommunity):
|
|
_VALUE_PACK_STR = '!7s' # opaque value
|
|
|
|
def __init__(self, type_, **kwargs):
|
|
super(BGPUnknownExtendedCommunity, self).__init__(type_=type_)
|
|
self.do_init(BGPUnknownExtendedCommunity, self, kwargs, type_=type_)
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_MP_REACH_NLRI)
|
|
class BGPPathAttributeMpReachNLRI(_PathAttribute):
|
|
_VALUE_PACK_STR = '!HBB' # afi, safi, next_hop_len
|
|
_VALUE_PACK_SIZE = struct.calcsize(_VALUE_PACK_STR)
|
|
_RD_LENGTH = 8
|
|
_RESERVED_LENGTH = 1
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
|
|
_class_suffixes = ['AddrPrefix']
|
|
_opt_attributes = ['next_hop']
|
|
_TYPE = {
|
|
'ascii': [
|
|
'next_hop'
|
|
]
|
|
}
|
|
|
|
def __init__(self, afi, safi, next_hop, nlri,
|
|
flags=0, type_=None, length=None):
|
|
super(BGPPathAttributeMpReachNLRI, self).__init__(
|
|
flags=flags, type_=type_, length=length)
|
|
self.afi = afi
|
|
self.safi = safi
|
|
if not isinstance(next_hop, (list, tuple)):
|
|
next_hop = [next_hop]
|
|
for n in next_hop:
|
|
if not ip.valid_ipv4(n) and not ip.valid_ipv6(n):
|
|
raise ValueError('Invalid address for next_hop: %s' % n)
|
|
# Note: For the backward compatibility, stores the first next_hop
|
|
# address and all next_hop addresses separately.
|
|
if next_hop:
|
|
self._next_hop = next_hop[0]
|
|
else:
|
|
self._next_hop = None
|
|
self._next_hop_list = next_hop
|
|
self.nlri = nlri
|
|
addr_cls = _get_addr_class(afi, safi)
|
|
for i in nlri:
|
|
if not isinstance(i, addr_cls):
|
|
raise ValueError('Invalid NRLI class for afi=%d and safi=%d'
|
|
% (self.afi, self.safi))
|
|
|
|
@staticmethod
|
|
def split_bin_with_len(buf, unit_len):
|
|
f = io.BytesIO(buf)
|
|
return [f.read(unit_len) for _ in range(0, len(buf), unit_len)]
|
|
|
|
@classmethod
|
|
def parse_next_hop_ipv4(cls, buf, unit_len):
|
|
next_hop = []
|
|
for next_hop_bin in cls.split_bin_with_len(buf, unit_len):
|
|
next_hop.append(addrconv.ipv4.bin_to_text(next_hop_bin[-4:]))
|
|
return next_hop
|
|
|
|
@classmethod
|
|
def parse_next_hop_ipv6(cls, buf, unit_len):
|
|
next_hop = []
|
|
for next_hop_bin in cls.split_bin_with_len(buf, unit_len):
|
|
next_hop.append(addrconv.ipv6.bin_to_text(next_hop_bin[-16:]))
|
|
return next_hop
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(afi, safi, next_hop_len,) = struct.unpack_from(
|
|
cls._VALUE_PACK_STR, six.binary_type(buf))
|
|
rest = buf[cls._VALUE_PACK_SIZE:]
|
|
|
|
next_hop_bin = rest[:next_hop_len]
|
|
rest = rest[next_hop_len:]
|
|
reserved = rest[:cls._RESERVED_LENGTH]
|
|
assert reserved == b'\0'
|
|
|
|
nlri_bin = rest[cls._RESERVED_LENGTH:]
|
|
addr_cls = _get_addr_class(afi, safi)
|
|
nlri = []
|
|
while nlri_bin:
|
|
n, nlri_bin = addr_cls.parser(nlri_bin)
|
|
nlri.append(n)
|
|
|
|
rf = RouteFamily(afi, safi)
|
|
if rf == RF_IPv4_VPN:
|
|
next_hop = cls.parse_next_hop_ipv4(next_hop_bin,
|
|
cls._RD_LENGTH + 4)
|
|
next_hop_len -= cls._RD_LENGTH * len(next_hop)
|
|
elif rf == RF_IPv6_VPN:
|
|
next_hop = cls.parse_next_hop_ipv6(next_hop_bin,
|
|
cls._RD_LENGTH + 16)
|
|
next_hop_len -= cls._RD_LENGTH * len(next_hop)
|
|
elif (afi == addr_family.IP
|
|
or (rf == RF_L2_EVPN and next_hop_len < 16)):
|
|
next_hop = cls.parse_next_hop_ipv4(next_hop_bin, 4)
|
|
elif (afi == addr_family.IP6
|
|
or (rf == RF_L2_EVPN and next_hop_len >= 16)):
|
|
next_hop = cls.parse_next_hop_ipv6(next_hop_bin, 16)
|
|
elif rf == RF_L2VPN_FLOWSPEC:
|
|
next_hop = []
|
|
else:
|
|
raise ValueError('Invalid address family: afi=%d, safi=%d'
|
|
% (afi, safi))
|
|
|
|
return {
|
|
'afi': afi,
|
|
'safi': safi,
|
|
'next_hop': next_hop,
|
|
'nlri': nlri,
|
|
}
|
|
|
|
def serialize_next_hop(self):
|
|
buf = bytearray()
|
|
for next_hop in self.next_hop_list:
|
|
if self.afi == addr_family.IP6:
|
|
next_hop = str(netaddr.IPAddress(next_hop).ipv6())
|
|
next_hop_bin = ip.text_to_bin(next_hop)
|
|
if RouteFamily(self.afi, self.safi) in (RF_IPv4_VPN, RF_IPv6_VPN):
|
|
# Empty label stack(RD=0:0) + IP address
|
|
next_hop_bin = b'\x00' * self._RD_LENGTH + next_hop_bin
|
|
buf += next_hop_bin
|
|
|
|
return buf
|
|
|
|
def serialize_value(self):
|
|
next_hop_bin = self.serialize_next_hop()
|
|
|
|
# fixup
|
|
next_hop_len = len(next_hop_bin)
|
|
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0,
|
|
self.afi, self.safi, next_hop_len)
|
|
buf += next_hop_bin
|
|
buf += b'\0' # reserved
|
|
|
|
nlri_bin = bytearray()
|
|
for n in self.nlri:
|
|
nlri_bin += n.serialize()
|
|
buf += nlri_bin
|
|
|
|
return buf
|
|
|
|
@property
|
|
def next_hop(self):
|
|
return self._next_hop
|
|
|
|
@next_hop.setter
|
|
def next_hop(self, addr):
|
|
if not ip.valid_ipv4(addr) and not ip.valid_ipv6(addr):
|
|
raise ValueError('Invalid address for next_hop: %s' % addr)
|
|
self._next_hop = addr
|
|
self.next_hop_list[0] = addr
|
|
|
|
@property
|
|
def next_hop_list(self):
|
|
return self._next_hop_list
|
|
|
|
@next_hop_list.setter
|
|
def next_hop_list(self, addr_list):
|
|
if not isinstance(addr_list, (list, tuple)):
|
|
addr_list = [addr_list]
|
|
for addr in addr_list:
|
|
if not ip.valid_ipv4(addr) and not ip.valid_ipv6(addr):
|
|
raise ValueError('Invalid address for next_hop: %s' % addr)
|
|
self._next_hop = addr_list[0]
|
|
self._next_hop_list = addr_list
|
|
|
|
@property
|
|
def route_family(self):
|
|
return _rf_map[(self.afi, self.safi)]
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYPE_MP_UNREACH_NLRI)
|
|
class BGPPathAttributeMpUnreachNLRI(_PathAttribute):
|
|
_VALUE_PACK_STR = '!HB' # afi, safi
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
|
|
_class_suffixes = ['AddrPrefix']
|
|
|
|
def __init__(self, afi, safi, withdrawn_routes,
|
|
flags=0, type_=None, length=None):
|
|
super(BGPPathAttributeMpUnreachNLRI, self).__init__(
|
|
flags=flags, type_=type_, length=length)
|
|
self.afi = afi
|
|
self.safi = safi
|
|
self.withdrawn_routes = withdrawn_routes
|
|
addr_cls = _get_addr_class(afi, safi)
|
|
for i in withdrawn_routes:
|
|
if not isinstance(i, addr_cls):
|
|
raise ValueError('Invalid NRLI class for afi=%d and safi=%d'
|
|
% (self.afi, self.safi))
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(afi, safi,) = struct.unpack_from(
|
|
cls._VALUE_PACK_STR, six.binary_type(buf))
|
|
|
|
nlri_bin = buf[struct.calcsize(cls._VALUE_PACK_STR):]
|
|
addr_cls = _get_addr_class(afi, safi)
|
|
nlri = []
|
|
while nlri_bin:
|
|
n, nlri_bin = addr_cls.parser(nlri_bin)
|
|
nlri.append(n)
|
|
|
|
return {
|
|
'afi': afi,
|
|
'safi': safi,
|
|
'withdrawn_routes': nlri,
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0, self.afi, self.safi)
|
|
|
|
nlri_bin = bytearray()
|
|
for n in self.withdrawn_routes:
|
|
nlri_bin += n.serialize()
|
|
buf += nlri_bin
|
|
|
|
return buf
|
|
|
|
@property
|
|
def route_family(self):
|
|
return _rf_map[(self.afi, self.safi)]
|
|
|
|
|
|
@_PathAttribute.register_type(BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE)
|
|
class BGPPathAttributePmsiTunnel(_PathAttribute):
|
|
"""
|
|
P-Multicast Service Interface Tunnel (PMSI Tunnel) attribute
|
|
"""
|
|
|
|
# pmsi_flags, tunnel_type, mpls_label
|
|
_VALUE_PACK_STR = '!BB3s'
|
|
_PACK_STR_SIZE = struct.calcsize(_VALUE_PACK_STR)
|
|
_ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
|
|
|
|
# RFC 6514
|
|
# +--------------------------------+
|
|
# | Flags (1 octet) |
|
|
# +--------------------------------+
|
|
# | Tunnel Type (1 octets) |
|
|
# +--------------------------------+
|
|
# | MPLS Label (3 octets) |
|
|
# +--------------------------------+
|
|
# | Tunnel Identifier (variable) |
|
|
# +--------------------------------+
|
|
|
|
# The Flags field has the following format:
|
|
# 0 1 2 3 4 5 6 7
|
|
# +-+-+-+-+-+-+-+-+
|
|
# | reserved |L|
|
|
# +-+-+-+-+-+-+-+-+
|
|
# `L` refers to the Leaf Information Required.
|
|
|
|
# Current, Tunnel Type supports following.
|
|
# + 0 - No tunnel information present
|
|
# + 6 - Ingress Replication
|
|
TYPE_NO_TUNNEL_INFORMATION_PRESENT = 0
|
|
TYPE_INGRESS_REPLICATION = 6
|
|
|
|
# TODO:
|
|
# The following Tunnel Type are not supported.
|
|
# Therefore, we will need to support in the future.
|
|
# + 1 - RSVP-TE P2MP LSP
|
|
# + 2 - mLDP P2MP LSP
|
|
# + 3 - PIM-SSM Tree
|
|
# + 4 - PIM-SM Tree
|
|
# + 5 - BIDIR-PIM Tree
|
|
# + 7 - mLDP MP2MP LSP
|
|
|
|
def __init__(self, pmsi_flags, tunnel_type,
|
|
mpls_label=None, label=None, vni=None, tunnel_id=None,
|
|
flags=0, type_=None, length=None):
|
|
super(BGPPathAttributePmsiTunnel, self).__init__(flags=flags,
|
|
type_=type_,
|
|
length=length)
|
|
self.pmsi_flags = pmsi_flags
|
|
self.tunnel_type = tunnel_type
|
|
self.tunnel_id = tunnel_id
|
|
|
|
if label:
|
|
# If binary type label field value is specified, stores it
|
|
# and decodes as MPLS label and VNI.
|
|
self._label = label
|
|
self._mpls_label, _ = mpls.label_from_bin(label)
|
|
self._vni = vxlan.vni_from_bin(label)
|
|
else:
|
|
# If either MPLS label or VNI is specified, stores it
|
|
# and encodes into binary type label field value.
|
|
self._label = self._serialize_label(mpls_label, vni)
|
|
self._mpls_label = mpls_label
|
|
self._vni = vni
|
|
|
|
@classmethod
|
|
def parse_value(cls, buf):
|
|
(pmsi_flags,
|
|
tunnel_type,
|
|
label) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
|
|
value = buf[cls._PACK_STR_SIZE:]
|
|
|
|
return {
|
|
'pmsi_flags': pmsi_flags,
|
|
'tunnel_type': tunnel_type,
|
|
'label': label,
|
|
'tunnel_id': _PmsiTunnelId.parse(tunnel_type, value)
|
|
}
|
|
|
|
def serialize_value(self):
|
|
buf = bytearray()
|
|
msg_pack_into(self._VALUE_PACK_STR, buf, 0,
|
|
self.pmsi_flags, self.tunnel_type, self._label)
|
|
|
|
if self.tunnel_id is not None:
|
|
buf += self.tunnel_id.serialize()
|
|
|
|
return buf
|
|
|
|
def _serialize_label(self, mpls_label, vni):
|
|
if mpls_label:
|
|
return mpls.label_to_bin(mpls_label, is_bos=True)
|
|
elif vni:
|
|
return vxlan.vni_to_bin(vni)
|
|
else:
|
|
return b'\x00' * 3
|
|
|
|
@property
|
|
def mpls_label(self):
|
|
return self._mpls_label
|
|
|
|
@mpls_label.setter
|
|
def mpls_label(self, mpls_label):
|
|
self._label = mpls.label_to_bin(mpls_label, is_bos=True)
|
|
self._mpls_label = mpls_label
|
|
self._vni = None # disables VNI
|
|
|
|
@property
|
|
def vni(self):
|
|
return self._vni
|
|
|
|
@vni.setter
|
|
def vni(self, vni):
|
|
self._label = vxlan.vni_to_bin(vni)
|
|
self._mpls_label = None # disables MPLS label
|
|
self._vni = vni
|
|
|
|
@classmethod
|
|
def from_jsondict(cls, dict_, decode_string=base64.b64decode,
|
|
**additional_args):
|
|
if isinstance(dict_['tunnel_id'], dict):
|
|
tunnel_id = dict_.pop('tunnel_id')
|
|
ins = super(BGPPathAttributePmsiTunnel,
|
|
cls).from_jsondict(dict_,
|
|
decode_string,
|
|
**additional_args)
|
|
|
|
mod = import_module(cls.__module__)
|
|
|
|
for key, value in tunnel_id.items():
|
|
tunnel_id_cls = getattr(mod, key)
|
|
ins.tunnel_id = tunnel_id_cls.from_jsondict(value,
|
|
decode_string,
|
|
**additional_args)
|
|
else:
|
|
ins = super(BGPPathAttributePmsiTunnel,
|
|
cls).from_jsondict(dict_,
|
|
decode_string,
|
|
**additional_args)
|
|
|
|
return ins
|
|
|
|
|
|
class _PmsiTunnelId(StringifyMixin, TypeDisp):
|
|
|
|
@classmethod
|
|
def parse(cls, tunnel_type, buf):
|
|
subcls = cls._lookup_type(tunnel_type)
|
|
return subcls.parser(buf)
|
|
|
|
|
|
@_PmsiTunnelId.register_unknown_type()
|
|
class PmsiTunnelIdUnknown(_PmsiTunnelId):
|
|
"""
|
|
Unknown route type specific _PmsiTunnelId
|
|
"""
|
|
|
|
def __init__(self, value):
|
|
super(PmsiTunnelIdUnknown, self).__init__()
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
return cls(value=buf)
|
|
|
|
def serialize(self):
|
|
return self.value
|
|
|
|
|
|
@_PmsiTunnelId.register_type(
|
|
BGPPathAttributePmsiTunnel.TYPE_NO_TUNNEL_INFORMATION_PRESENT)
|
|
class _PmsiTunnelIdNoInformationPresent(_PmsiTunnelId):
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
return None
|
|
|
|
|
|
@_PmsiTunnelId.register_type(
|
|
BGPPathAttributePmsiTunnel.TYPE_INGRESS_REPLICATION)
|
|
class PmsiTunnelIdIngressReplication(_PmsiTunnelId):
|
|
# tunnel_endpoint_ip
|
|
_VALUE_PACK_STR = '!%ds'
|
|
_TYPE = {
|
|
'ascii': [
|
|
'tunnel_endpoint_ip'
|
|
]
|
|
}
|
|
|
|
def __init__(self, tunnel_endpoint_ip):
|
|
super(PmsiTunnelIdIngressReplication, self).__init__()
|
|
self.tunnel_endpoint_ip = tunnel_endpoint_ip
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(tunnel_endpoint_ip, ) = struct.unpack_from(
|
|
cls._VALUE_PACK_STR % len(buf),
|
|
six.binary_type(buf))
|
|
return cls(tunnel_endpoint_ip=ip.bin_to_text(tunnel_endpoint_ip))
|
|
|
|
def serialize(self):
|
|
ip_bin = ip.text_to_bin(self.tunnel_endpoint_ip)
|
|
return struct.pack(self._VALUE_PACK_STR % len(ip_bin),
|
|
ip.text_to_bin(self.tunnel_endpoint_ip))
|
|
|
|
|
|
class BGPNLRI(IPAddrPrefix):
|
|
pass
|
|
|
|
|
|
class BGPMessage(packet_base.PacketBase, TypeDisp):
|
|
"""Base class for BGP-4 messages.
|
|
|
|
An instance has the following attributes at least.
|
|
Most of them are same to the on-wire counterparts but in host byte
|
|
order.
|
|
__init__ takes the corresponding args in this order.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
marker Marker field. Ignored when encoding.
|
|
len Length field. Ignored when encoding.
|
|
type Type field. one of ``BGP_MSG_*`` constants.
|
|
========================== ===============================================
|
|
"""
|
|
|
|
_HDR_PACK_STR = '!16sHB' # marker, len, type
|
|
_HDR_LEN = struct.calcsize(_HDR_PACK_STR)
|
|
_class_prefixes = ['BGP']
|
|
|
|
def __init__(self, marker=None, len_=None, type_=None):
|
|
super(BGPMessage, self).__init__()
|
|
if marker is None:
|
|
self._marker = _MARKER
|
|
else:
|
|
self._marker = marker
|
|
self.len = len_
|
|
if type_ is None:
|
|
type_ = self._rev_lookup_type(self.__class__)
|
|
self.type = type_
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
if len(buf) < cls._HDR_LEN:
|
|
raise stream_parser.StreamParser.TooSmallException(
|
|
'%d < %d' % (len(buf), cls._HDR_LEN))
|
|
(marker, len_, type_) = struct.unpack_from(cls._HDR_PACK_STR,
|
|
six.binary_type(buf))
|
|
msglen = len_
|
|
if len(buf) < msglen:
|
|
raise stream_parser.StreamParser.TooSmallException(
|
|
'%d < %d' % (len(buf), msglen))
|
|
binmsg = buf[cls._HDR_LEN:msglen]
|
|
rest = buf[msglen:]
|
|
subcls = cls._lookup_type(type_)
|
|
kwargs = subcls.parser(binmsg)
|
|
return subcls(marker=marker, len_=len_, type_=type_,
|
|
**kwargs), cls, rest
|
|
|
|
def serialize(self, payload=None, prev=None):
|
|
# fixup
|
|
self._marker = _MARKER
|
|
tail = self.serialize_tail()
|
|
self.len = self._HDR_LEN + len(tail)
|
|
|
|
hdr = bytearray(struct.pack(self._HDR_PACK_STR, self._marker,
|
|
self.len, self.type))
|
|
return hdr + tail
|
|
|
|
def __len__(self):
|
|
# XXX destructive
|
|
buf = self.serialize()
|
|
return len(buf)
|
|
|
|
|
|
@BGPMessage.register_type(BGP_MSG_OPEN)
|
|
class BGPOpen(BGPMessage):
|
|
"""BGP-4 OPEN Message encoder/decoder class.
|
|
|
|
An instance has the following attributes at least.
|
|
Most of them are same to the on-wire counterparts but in host byte
|
|
order.
|
|
__init__ takes the corresponding args in this order.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
marker Marker field. Ignored when encoding.
|
|
len Length field. Ignored when encoding.
|
|
type Type field.
|
|
version Version field.
|
|
my_as My Autonomous System field.
|
|
2 octet unsigned integer.
|
|
hold_time Hold Time field.
|
|
2 octet unsigned integer.
|
|
bgp_identifier BGP Identifier field.
|
|
An IPv4 address.
|
|
For example, '192.0.2.1'
|
|
opt_param_len Optional Parameters Length field.
|
|
Ignored when encoding.
|
|
opt_param Optional Parameters field.
|
|
A list of BGPOptParam instances.
|
|
The default is [].
|
|
========================== ===============================================
|
|
"""
|
|
|
|
_PACK_STR = '!BHH4sB'
|
|
_MIN_LEN = BGPMessage._HDR_LEN + struct.calcsize(_PACK_STR)
|
|
_TYPE = {
|
|
'ascii': [
|
|
'bgp_identifier'
|
|
]
|
|
}
|
|
|
|
def __init__(self, my_as, bgp_identifier, type_=BGP_MSG_OPEN,
|
|
opt_param_len=0, opt_param=None,
|
|
version=_VERSION, hold_time=0, len_=None, marker=None):
|
|
opt_param = opt_param if opt_param else []
|
|
super(BGPOpen, self).__init__(marker=marker, len_=len_, type_=type_)
|
|
self.version = version
|
|
self.my_as = my_as
|
|
self.bgp_identifier = bgp_identifier
|
|
self.hold_time = hold_time
|
|
self.opt_param_len = opt_param_len
|
|
self.opt_param = opt_param
|
|
|
|
@property
|
|
def opt_param_cap_map(self):
|
|
cap_map = {}
|
|
for param in self.opt_param:
|
|
if param.type == BGP_OPT_CAPABILITY:
|
|
cap_map[param.cap_code] = param
|
|
return cap_map
|
|
|
|
def get_opt_param_cap(self, cap_code):
|
|
return self.opt_param_cap_map.get(cap_code)
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(version,
|
|
my_as,
|
|
hold_time,
|
|
bgp_identifier,
|
|
opt_param_len) = struct.unpack_from(cls._PACK_STR,
|
|
six.binary_type(buf))
|
|
rest = buf[struct.calcsize(cls._PACK_STR):]
|
|
binopts = rest[:opt_param_len]
|
|
opt_param = []
|
|
while binopts:
|
|
opt, binopts = _OptParam.parser(binopts)
|
|
opt_param.extend(opt)
|
|
return {
|
|
"version": version,
|
|
"my_as": my_as,
|
|
"hold_time": hold_time,
|
|
"bgp_identifier": addrconv.ipv4.bin_to_text(bgp_identifier),
|
|
"opt_param_len": opt_param_len,
|
|
"opt_param": opt_param,
|
|
}
|
|
|
|
def serialize_tail(self):
|
|
# fixup
|
|
self.version = _VERSION
|
|
binopts = bytearray()
|
|
for opt in self.opt_param:
|
|
binopts += opt.serialize()
|
|
self.opt_param_len = len(binopts)
|
|
|
|
msg = bytearray(struct.pack(self._PACK_STR,
|
|
self.version,
|
|
self.my_as,
|
|
self.hold_time,
|
|
addrconv.ipv4.text_to_bin(
|
|
self.bgp_identifier),
|
|
self.opt_param_len))
|
|
msg += binopts
|
|
return msg
|
|
|
|
|
|
@BGPMessage.register_type(BGP_MSG_UPDATE)
|
|
class BGPUpdate(BGPMessage):
|
|
"""BGP-4 UPDATE Message encoder/decoder class.
|
|
|
|
An instance has the following attributes at least.
|
|
Most of them are same to the on-wire counterparts but in host byte
|
|
order.
|
|
__init__ takes the corresponding args in this order.
|
|
|
|
.. tabularcolumns:: |l|L|
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
marker Marker field. Ignored when encoding.
|
|
len Length field. Ignored when encoding.
|
|
type Type field.
|
|
withdrawn_routes_len Withdrawn Routes Length field.
|
|
Ignored when encoding.
|
|
withdrawn_routes Withdrawn Routes field.
|
|
A list of BGPWithdrawnRoute instances.
|
|
The default is [].
|
|
total_path_attribute_len Total Path Attribute Length field.
|
|
Ignored when encoding.
|
|
path_attributes Path Attributes field.
|
|
A list of BGPPathAttribute instances.
|
|
The default is [].
|
|
nlri Network Layer Reachability Information field.
|
|
A list of BGPNLRI instances.
|
|
The default is [].
|
|
========================== ===============================================
|
|
"""
|
|
|
|
_MIN_LEN = BGPMessage._HDR_LEN
|
|
|
|
def __init__(self, type_=BGP_MSG_UPDATE,
|
|
withdrawn_routes_len=None,
|
|
withdrawn_routes=None,
|
|
total_path_attribute_len=None,
|
|
path_attributes=None,
|
|
nlri=None,
|
|
len_=None, marker=None):
|
|
withdrawn_routes = withdrawn_routes if withdrawn_routes else []
|
|
path_attributes = path_attributes if path_attributes else []
|
|
nlri = nlri if nlri else []
|
|
super(BGPUpdate, self).__init__(marker=marker, len_=len_, type_=type_)
|
|
self.withdrawn_routes_len = withdrawn_routes_len
|
|
self.withdrawn_routes = withdrawn_routes
|
|
self.total_path_attribute_len = total_path_attribute_len
|
|
self.path_attributes = path_attributes
|
|
self.nlri = nlri
|
|
|
|
@property
|
|
def pathattr_map(self):
|
|
passattr_map = {}
|
|
for attr in self.path_attributes:
|
|
passattr_map[attr.type] = attr
|
|
return passattr_map
|
|
|
|
def get_path_attr(self, attr_name):
|
|
return self.pathattr_map.get(attr_name)
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
offset = 0
|
|
buf = six.binary_type(buf)
|
|
(withdrawn_routes_len,) = struct.unpack_from('!H', buf, offset)
|
|
binroutes = buf[offset + 2:
|
|
offset + 2 + withdrawn_routes_len]
|
|
offset += 2 + withdrawn_routes_len
|
|
(total_path_attribute_len,) = struct.unpack_from('!H', buf, offset)
|
|
binpathattrs = buf[offset + 2:
|
|
offset + 2 + total_path_attribute_len]
|
|
binnlri = buf[offset + 2 + total_path_attribute_len:]
|
|
withdrawn_routes = []
|
|
while binroutes:
|
|
r, binroutes = BGPWithdrawnRoute.parser(binroutes)
|
|
withdrawn_routes.append(r)
|
|
path_attributes = []
|
|
while binpathattrs:
|
|
pa, binpathattrs = _PathAttribute.parser(binpathattrs)
|
|
path_attributes.append(pa)
|
|
offset += 2 + total_path_attribute_len
|
|
nlri = []
|
|
while binnlri:
|
|
n, binnlri = BGPNLRI.parser(binnlri)
|
|
nlri.append(n)
|
|
return {
|
|
"withdrawn_routes_len": withdrawn_routes_len,
|
|
"withdrawn_routes": withdrawn_routes,
|
|
"total_path_attribute_len": total_path_attribute_len,
|
|
"path_attributes": path_attributes,
|
|
"nlri": nlri,
|
|
}
|
|
|
|
def serialize_tail(self):
|
|
# fixup
|
|
binroutes = bytearray()
|
|
for r in self.withdrawn_routes:
|
|
binroutes += r.serialize()
|
|
self.withdrawn_routes_len = len(binroutes)
|
|
binpathattrs = bytearray()
|
|
for pa in self.path_attributes:
|
|
binpathattrs += pa.serialize()
|
|
self.total_path_attribute_len = len(binpathattrs)
|
|
binnlri = bytearray()
|
|
for n in self.nlri:
|
|
binnlri += n.serialize()
|
|
|
|
msg = bytearray()
|
|
offset = 0
|
|
msg_pack_into('!H', msg, offset, self.withdrawn_routes_len)
|
|
msg += binroutes
|
|
offset += 2 + self.withdrawn_routes_len
|
|
msg_pack_into('!H', msg, offset, self.total_path_attribute_len)
|
|
msg += binpathattrs
|
|
offset += 2 + self.total_path_attribute_len
|
|
msg += binnlri
|
|
return msg
|
|
|
|
|
|
@BGPMessage.register_type(BGP_MSG_KEEPALIVE)
|
|
class BGPKeepAlive(BGPMessage):
|
|
"""BGP-4 KEEPALIVE Message encoder/decoder class.
|
|
|
|
An instance has the following attributes at least.
|
|
Most of them are same to the on-wire counterparts but in host byte
|
|
order.
|
|
__init__ takes the corresponding args in this order.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
marker Marker field. Ignored when encoding.
|
|
len Length field. Ignored when encoding.
|
|
type Type field.
|
|
========================== ===============================================
|
|
"""
|
|
|
|
_MIN_LEN = BGPMessage._HDR_LEN
|
|
|
|
def __init__(self, type_=BGP_MSG_KEEPALIVE, len_=None, marker=None):
|
|
super(BGPKeepAlive, self).__init__(marker=marker, len_=len_,
|
|
type_=type_)
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
return {}
|
|
|
|
def serialize_tail(self):
|
|
return bytearray()
|
|
|
|
|
|
@BGPMessage.register_type(BGP_MSG_NOTIFICATION)
|
|
class BGPNotification(BGPMessage):
|
|
"""BGP-4 NOTIFICATION Message encoder/decoder class.
|
|
|
|
An instance has the following attributes at least.
|
|
Most of them are same to the on-wire counterparts but in host byte
|
|
order.
|
|
__init__ takes the corresponding args in this order.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
marker Marker field. Ignored when encoding.
|
|
len Length field. Ignored when encoding.
|
|
type Type field.
|
|
error_code Error code field.
|
|
error_subcode Error subcode field.
|
|
data Data field.
|
|
========================== ===============================================
|
|
"""
|
|
|
|
_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,
|
|
data=b'',
|
|
type_=BGP_MSG_NOTIFICATION, len_=None, marker=None):
|
|
super(BGPNotification, self).__init__(marker=marker, len_=len_,
|
|
type_=type_)
|
|
self.error_code = error_code
|
|
self.error_subcode = error_subcode
|
|
self.data = data
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(error_code, error_subcode,) = struct.unpack_from(cls._PACK_STR,
|
|
six.binary_type(buf))
|
|
data = bytes(buf[2:])
|
|
return {
|
|
"error_code": error_code,
|
|
"error_subcode": error_subcode,
|
|
"data": data,
|
|
}
|
|
|
|
def serialize_tail(self):
|
|
msg = bytearray(struct.pack(self._PACK_STR, self.error_code,
|
|
self.error_subcode))
|
|
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):
|
|
"""BGP-4 ROUTE REFRESH Message (RFC 2918) encoder/decoder class.
|
|
|
|
An instance has the following attributes at least.
|
|
Most of them are same to the on-wire counterparts but in host byte
|
|
order.
|
|
__init__ takes the corresponding args in this order.
|
|
|
|
========================== ===============================================
|
|
Attribute Description
|
|
========================== ===============================================
|
|
marker Marker field. Ignored when encoding.
|
|
len Length field. Ignored when encoding.
|
|
type Type field.
|
|
afi Address Family Identifier
|
|
safi Subsequent Address Family Identifier
|
|
========================== ===============================================
|
|
"""
|
|
|
|
_PACK_STR = '!HBB'
|
|
_MIN_LEN = BGPMessage._HDR_LEN + struct.calcsize(_PACK_STR)
|
|
|
|
def __init__(self,
|
|
afi, safi, demarcation=0,
|
|
type_=BGP_MSG_ROUTE_REFRESH, len_=None, marker=None):
|
|
super(BGPRouteRefresh, self).__init__(marker=marker, len_=len_,
|
|
type_=type_)
|
|
self.afi = afi
|
|
self.safi = safi
|
|
self.demarcation = demarcation
|
|
self.eor_sent = False
|
|
|
|
@classmethod
|
|
def parser(cls, buf):
|
|
(afi, demarcation, safi,) = struct.unpack_from(cls._PACK_STR,
|
|
six.binary_type(buf))
|
|
return {
|
|
"afi": afi,
|
|
"safi": safi,
|
|
"demarcation": demarcation,
|
|
}
|
|
|
|
def serialize_tail(self):
|
|
return bytearray(struct.pack(self._PACK_STR, self.afi,
|
|
self.demarcation, self.safi))
|
|
|
|
|
|
class StreamParser(stream_parser.StreamParser):
|
|
"""Streaming parser for BGP-4 messages.
|
|
|
|
This is a subclass of os_ken.lib.packet.stream_parser.StreamParser.
|
|
Its parse method returns a list of BGPMessage subclass instances.
|
|
"""
|
|
|
|
def try_parse(self, data):
|
|
msg, _, rest = BGPMessage.parser(data)
|
|
return msg, rest
|