86890704fd
todo: add documentation & wireshark dissector
2524 lines
71 KiB
Python
Executable file
2524 lines
71 KiB
Python
Executable file
# This file is part of Scapy
|
|
# Scapy is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# any later version.
|
|
#
|
|
# Scapy is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# scapy.contrib.description = BGP v0.1
|
|
# scapy.contrib.status = loads
|
|
|
|
"""
|
|
BGP (Border Gateway Protocol).
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
import struct
|
|
import re
|
|
import socket
|
|
|
|
from scapy import pton_ntop
|
|
from scapy.packet import Packet, Packet_metaclass, bind_layers
|
|
from scapy.fields import (Field, BitField, BitEnumField, XBitField, ByteField,
|
|
ByteEnumField, ShortField, ShortEnumField, IntField,
|
|
IntEnumField, LongField, IEEEFloatField, StrField,
|
|
StrLenField, StrFixedLenField, FieldLenField,
|
|
FieldListField, PacketField, PacketListField,
|
|
IPField, FlagsField, ConditionalField,
|
|
MultiEnumField)
|
|
from scapy.layers.inet import TCP
|
|
from scapy.layers.inet6 import IP6Field
|
|
from scapy.config import conf, ConfClass
|
|
from scapy.compat import orb, chb
|
|
from scapy.error import log_runtime
|
|
import scapy.modules.six as six
|
|
|
|
|
|
#
|
|
# Module configuration
|
|
#
|
|
|
|
|
|
class BGPConf(ConfClass):
|
|
"""
|
|
BGP module configuration.
|
|
"""
|
|
|
|
# By default, set to True in order to behave like an OLD speaker (RFC 6793)
|
|
use_2_bytes_asn = True
|
|
|
|
|
|
bgp_module_conf = BGPConf()
|
|
|
|
|
|
#
|
|
# Constants
|
|
#
|
|
|
|
# RFC 4271: "The maximum message size is 4096 octets. All implementations are
|
|
# required to support this maximum message size."
|
|
BGP_MAXIMUM_MESSAGE_SIZE = 4096
|
|
|
|
# RFC 4271: "Each message has a fixed-size header." Marker (16 bytes) +
|
|
# Length (2 bytes) + Type (1 byte)
|
|
_BGP_HEADER_SIZE = 19
|
|
|
|
# Marker included in every message (RFC 4271: "This 16-octet field is
|
|
# included for compatibility; it MUST be set to all ones")
|
|
_BGP_HEADER_MARKER = b"\xff" * 16
|
|
|
|
# extended-length flag (RFC 4271 4.3. UPDATE Message Format -
|
|
# Path Attributes)
|
|
_BGP_PA_EXTENDED_LENGTH = 0x10
|
|
|
|
# RFC 5492 (at least 2 bytes : code + length)
|
|
_BGP_CAPABILITY_MIN_SIZE = 2
|
|
|
|
# RFC 5492 (at least 3 bytes : type code + length)
|
|
_BGP_PATH_ATTRIBUTE_MIN_SIZE = 3
|
|
|
|
|
|
#
|
|
# Fields and utilities
|
|
#
|
|
|
|
def _bits_to_bytes_len(length_in_bits):
|
|
"""
|
|
Helper function that returns the numbers of bytes necessary to store the
|
|
given number of bits.
|
|
"""
|
|
|
|
return (length_in_bits + 7) // 8
|
|
|
|
|
|
class BGPFieldIPv4(Field):
|
|
"""
|
|
IPv4 Field (CIDR)
|
|
"""
|
|
|
|
def mask2iplen(self, mask):
|
|
"""Get the IP field mask length (in bytes)."""
|
|
return (mask + 7) // 8
|
|
|
|
def h2i(self, pkt, h):
|
|
"""x.x.x.x/y to "internal" representation."""
|
|
ip, mask = re.split("/", h)
|
|
return int(mask), ip
|
|
|
|
def i2h(self, pkt, i):
|
|
""""Internal" representation to "human" representation
|
|
(x.x.x.x/y)."""
|
|
mask, ip = i
|
|
return ip + "/" + str(mask)
|
|
|
|
def i2repr(self, pkt, i):
|
|
return self.i2h(pkt, i)
|
|
|
|
def i2len(self, pkt, i):
|
|
mask, ip = i
|
|
return self.mask2iplen(mask) + 1
|
|
|
|
def i2m(self, pkt, i):
|
|
""""Internal" (IP as bytes, mask as int) to "machine"
|
|
representation."""
|
|
mask, ip = i
|
|
ip = socket.inet_aton(ip)
|
|
return struct.pack(">B", mask) + ip[:self.mask2iplen(mask)]
|
|
|
|
def addfield(self, pkt, s, val):
|
|
return s + self.i2m(pkt, val)
|
|
|
|
def getfield(self, pkt, s):
|
|
length = self.mask2iplen(orb(s[0])) + 1
|
|
return s[length:], self.m2i(pkt, s[:length])
|
|
|
|
def m2i(self, pkt, m):
|
|
mask = orb(m[0])
|
|
mask2iplen_res = self.mask2iplen(mask)
|
|
ip = b"".join(m[i + 1:i + 2] if i < mask2iplen_res else b"\x00" for i in range(4)) # noqa: E501
|
|
return (mask, socket.inet_ntoa(ip))
|
|
|
|
|
|
class BGPFieldIPv6(Field):
|
|
"""IPv6 Field (CIDR)"""
|
|
|
|
def mask2iplen(self, mask):
|
|
"""Get the IP field mask length (in bytes)."""
|
|
return (mask + 7) // 8
|
|
|
|
def h2i(self, pkt, h):
|
|
"""x.x.x.x/y to internal representation."""
|
|
ip, mask = re.split("/", h)
|
|
return int(mask), ip
|
|
|
|
def i2h(self, pkt, i):
|
|
""""Internal" representation to "human" representation."""
|
|
mask, ip = i
|
|
return ip + "/" + str(mask)
|
|
|
|
def i2repr(self, pkt, i):
|
|
return self.i2h(pkt, i)
|
|
|
|
def i2len(self, pkt, i):
|
|
mask, ip = i
|
|
return self.mask2iplen(mask) + 1
|
|
|
|
def i2m(self, pkt, i):
|
|
""""Internal" (IP as bytes, mask as int) to "machine" representation.""" # noqa: E501
|
|
mask, ip = i
|
|
ip = pton_ntop.inet_pton(socket.AF_INET6, ip)
|
|
return struct.pack(">B", mask) + ip[:self.mask2iplen(mask)]
|
|
|
|
def addfield(self, pkt, s, val):
|
|
return s + self.i2m(pkt, val)
|
|
|
|
def getfield(self, pkt, s):
|
|
length = self.mask2iplen(orb(s[0])) + 1
|
|
return s[length:], self.m2i(pkt, s[:length])
|
|
|
|
def m2i(self, pkt, m):
|
|
mask = orb(m[0])
|
|
ip = b"".join(m[i + 1:i + 2] if i < self.mask2iplen(mask) else b"\x00" for i in range(16)) # noqa: E501
|
|
return (mask, pton_ntop.inet_ntop(socket.AF_INET6, ip))
|
|
|
|
|
|
def has_extended_length(flags):
|
|
"""
|
|
Used in BGPPathAttr to check if the extended-length flag is
|
|
set.
|
|
"""
|
|
|
|
return flags & _BGP_PA_EXTENDED_LENGTH == _BGP_PA_EXTENDED_LENGTH
|
|
|
|
|
|
class BGPNLRI_IPv4(Packet):
|
|
"""
|
|
Packet handling IPv4 NLRI fields.
|
|
"""
|
|
|
|
name = "IPv4 NLRI"
|
|
fields_desc = [BGPFieldIPv4("prefix", "0.0.0.0/0")]
|
|
|
|
|
|
class BGPNLRI_IPv6(Packet):
|
|
"""
|
|
Packet handling IPv6 NLRI fields.
|
|
"""
|
|
|
|
name = "IPv6 NLRI"
|
|
fields_desc = [BGPFieldIPv6("prefix", "::/0")]
|
|
|
|
|
|
class BGPNLRIPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling NLRI fields.
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
length = None
|
|
ret = b""
|
|
|
|
if self.length_from is not None:
|
|
length = self.length_from(pkt)
|
|
|
|
if length is not None:
|
|
remain, ret = s[:length], s[length:]
|
|
else:
|
|
index = s.find(_BGP_HEADER_MARKER)
|
|
if index != -1:
|
|
remain = s[:index]
|
|
ret = s[index:]
|
|
else:
|
|
remain = s
|
|
|
|
while remain:
|
|
mask_length_in_bits = orb(remain[0])
|
|
mask_length_in_bytes = (mask_length_in_bits + 7) // 8
|
|
current = remain[:mask_length_in_bytes + 1]
|
|
remain = remain[mask_length_in_bytes + 1:]
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain + ret, lst
|
|
|
|
|
|
class _BGPInvalidDataException(Exception):
|
|
"""
|
|
Raised when it is not possible to instantiate a BGP packet with the given
|
|
data.
|
|
"""
|
|
|
|
def __init__(self, details):
|
|
Exception.__init__(
|
|
self,
|
|
"Impossible to build packet from the given data" + details
|
|
)
|
|
|
|
|
|
def _get_cls(name, fallback_cls=conf.raw_layer):
|
|
"""
|
|
Returns class named "name" if it exists, fallback_cls otherwise.
|
|
"""
|
|
|
|
return globals().get(name, fallback_cls)
|
|
|
|
|
|
#
|
|
# Common dictionaries
|
|
#
|
|
|
|
_bgp_message_types = {
|
|
0: "NONE",
|
|
1: "OPEN",
|
|
2: "UPDATE",
|
|
3: "NOTIFICATION",
|
|
4: "KEEPALIVE",
|
|
5: "ROUTE-REFRESH"
|
|
}
|
|
|
|
|
|
#
|
|
# AFIs
|
|
#
|
|
|
|
address_family_identifiers = {
|
|
0: "Reserved",
|
|
1: "IP (IP version 4)",
|
|
2: "IP6 (IP version 6)",
|
|
3: "NSAP",
|
|
4: "HDLC (8-bit multidrop)",
|
|
5: "BBN 1822",
|
|
6: "802 (includes all 802 media plus Ethernet \"canonical format\")",
|
|
7: "E.163",
|
|
8: "E.164 (SMDS, Frame Relay, ATM)",
|
|
9: "F.69 (Telex)",
|
|
10: "X.121 (X.25, Frame Relay)",
|
|
11: "IPX",
|
|
12: "Appletalk",
|
|
13: "Decnet IV",
|
|
14: "Banyan Vines",
|
|
15: "E.164 with NSAP format subaddress", # ANDY_MALIS
|
|
16: "DNS (Domain Name System)",
|
|
17: "Distinguished Name", # CHARLES_LYNN
|
|
18: "AS Number", # CHARLES_LYNN
|
|
19: "XTP over IP version 4", # MIKE_SAUL
|
|
20: "XTP over IP version 6", # MIKE_SAUL
|
|
21: "XTP native mode XTP", # MIKE_SAUL
|
|
22: "Fibre Channel World-Wide Port Name", # MARK_BAKKE
|
|
23: "Fibre Channel World-Wide Node Name", # MARK_BAKKE
|
|
24: "GWID", # SUBRA_HEGDE
|
|
25: "AFI for L2VPN information", # RFC 6074
|
|
26: "MPLS-TP Section Endpoint Identifier", # RFC 7212
|
|
27: "MPLS-TP LSP Endpoint Identifier", # RFC 7212
|
|
28: "MPLS-TP Pseudowire Endpoint Identifier", # RFC 7212
|
|
29: "MT IP: Multi-Topology IP version 4", # RFC 7307
|
|
30: "MT IPv6: Multi-Topology IP version 6", # RFC 7307
|
|
16384: "EIGRP Common Service Family", # DONNIE_SAVAGE
|
|
16385: "EIGRP IPv4 Service Family", # DONNIE_SAVAGE
|
|
16386: "EIGRP IPv6 Service Family", # DONNIE_SAVAGE
|
|
16387: "LISP Canonical Address Format (LCAF)", # DAVID_MEYER
|
|
16388: "BGP-LS", # RFC 7752
|
|
16389: "48-bit MAC", # RFC 7042
|
|
16390: "64-bit MAC", # RFC 7042
|
|
16391: "OUI", # draft-ietf-trill-ia-appsubtlv
|
|
16392: "MAC/24", # draft-ietf-trill-ia-appsubtlv
|
|
16393: "MAC/40", # draft-ietf-trill-ia-appsubtlv
|
|
16394: "IPv6/64", # draft-ietf-trill-ia-appsubtlv
|
|
16395: "RBridge Port ID", # draft-ietf-trill-ia-appsubtlv
|
|
16396: "TRILL Nickname", # RFC 7455
|
|
65535: "Reserved"
|
|
}
|
|
|
|
|
|
subsequent_afis = {
|
|
0: "Reserved", # RFC 4760
|
|
1: "Network Layer Reachability Information used for unicast forwarding", # RFC 4760 # noqa: E501
|
|
2: "Network Layer Reachability Information used for multicast forwarding", # RFC 4760 # noqa: E501
|
|
3: "Reserved", # RFC 4760
|
|
4: "Network Layer Reachability Information (NLRI) with MPLS Labels", # RFC 3107 # noqa: E501
|
|
5: "MCAST-VPN", # RFC 6514
|
|
6: "Network Layer Reachability Information used for Dynamic Placement of\
|
|
Multi-Segment Pseudowires", # RFC 7267
|
|
7: "Encapsulation SAFI", # RFC 5512
|
|
8: "MCAST-VPLS", # RFC 7117
|
|
64: "Tunnel SAFI", # DRAFT-NALAWADE-KAPOOR-TUNNEL-SAFI-01
|
|
65: "Virtual Private LAN Service (VPLS)", # RFC 6074
|
|
66: "BGP MDT SAFI", # RFC 6037
|
|
67: "BGP 4over6 SAFI", # RFC 5747
|
|
68: "BGP 6over4 SAFI", # YONG_CUI
|
|
69: "Layer-1 VPN auto-discovery information", # RFC 5195
|
|
70: "BGP EVPNs", # RFC 7432
|
|
71: "BGP-LS", # RFC 7752
|
|
72: "BGP-LS-VPN", # RFC 7752
|
|
128: "MPLS-labeled VPN address", # RFC 4364
|
|
129: "Multicast for BGP/MPLS IP Virtual Private Networks (VPNs)", # RFC 6514 # noqa: E501
|
|
132: "Route Target constraint", # RFC 4684
|
|
133: "IPv4 dissemination of flow specification rules", # RFC 5575
|
|
134: "VPNv4 dissemination of flow specification rules", # RFC 5575
|
|
140: "VPN auto-discovery", # draft-ietf-l3vpn-bgpvpn-auto
|
|
255: "Reserved" # RFC 4760
|
|
}
|
|
|
|
|
|
# Used by _bgp_dispatcher to instantiate the appropriate class
|
|
_bgp_cls_by_type = {
|
|
1: "BGPOpen",
|
|
2: "BGPUpdate",
|
|
3: "BGPNotification",
|
|
4: "BGPKeepAlive",
|
|
5: "BGPRouteRefresh",
|
|
}
|
|
|
|
|
|
#
|
|
# Header
|
|
#
|
|
|
|
class BGPHeader(Packet):
|
|
"""
|
|
The header of any BGP message.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "HEADER"
|
|
fields_desc = [
|
|
XBitField(
|
|
"marker",
|
|
0xffffffffffffffffffffffffffffffff,
|
|
0x80
|
|
),
|
|
ShortField("len", None),
|
|
ByteEnumField("type", 4, _bgp_message_types)
|
|
]
|
|
|
|
@classmethod
|
|
def dispatch_hook(cls, _pkt=None, *args, **kargs):
|
|
"""
|
|
Returns the right class for the given data.
|
|
"""
|
|
|
|
return _bgp_dispatcher(_pkt)
|
|
|
|
def post_build(self, p, pay):
|
|
if self.len is None:
|
|
length = len(p)
|
|
if pay:
|
|
length = length + len(pay)
|
|
p = p[:16] + struct.pack("!H", length) + p[18:]
|
|
return p + pay
|
|
|
|
def guess_payload_class(self, payload):
|
|
return _get_cls(_bgp_cls_by_type.get(self.type, conf.raw_layer), conf.raw_layer) # noqa: E501
|
|
|
|
|
|
def _bgp_dispatcher(payload):
|
|
"""
|
|
Returns the right class for a given BGP message.
|
|
"""
|
|
|
|
cls = conf.raw_layer
|
|
|
|
# By default, calling BGP() will build a BGPHeader.
|
|
if payload is None:
|
|
cls = _get_cls("BGPHeader", conf.raw_layer)
|
|
|
|
else:
|
|
if len(payload) >= _BGP_HEADER_SIZE and\
|
|
payload[:16] == _BGP_HEADER_MARKER:
|
|
|
|
# Get BGP message type
|
|
message_type = orb(payload[18])
|
|
if message_type == 4:
|
|
cls = _get_cls("BGPKeepAlive")
|
|
else:
|
|
cls = _get_cls("BGPHeader")
|
|
|
|
return cls
|
|
|
|
|
|
class BGP(Packet):
|
|
"""
|
|
Every BGP message inherits from this class.
|
|
"""
|
|
|
|
#
|
|
# BGP messages types
|
|
|
|
OPEN_TYPE = 1
|
|
UPDATE_TYPE = 2
|
|
NOTIFICATION_TYPE = 3
|
|
KEEPALIVE_TYPE = 4
|
|
ROUTEREFRESH_TYPE = 5
|
|
|
|
@classmethod
|
|
def dispatch_hook(cls, _pkt=None, *args, **kargs):
|
|
"""
|
|
Returns the right class for the given data.
|
|
"""
|
|
|
|
return _bgp_dispatcher(_pkt)
|
|
|
|
def guess_payload_class(self, p):
|
|
cls = None
|
|
if len(p) > 15 and p[:16] == _BGP_HEADER_MARKER:
|
|
cls = BGPHeader
|
|
return cls
|
|
|
|
|
|
#
|
|
# KEEPALIVE
|
|
#
|
|
|
|
class BGPKeepAlive(BGP, BGPHeader):
|
|
|
|
"""
|
|
KEEPALIVE message.
|
|
"""
|
|
|
|
name = "KEEPALIVE"
|
|
|
|
|
|
#
|
|
# OPEN
|
|
#
|
|
|
|
#
|
|
# Optional Parameters Codes
|
|
#
|
|
|
|
optional_parameter_codes = {
|
|
0: "Reserved",
|
|
1: "Authentication (deprecated)",
|
|
2: "Capabilities"
|
|
}
|
|
|
|
|
|
#
|
|
# Capabilities
|
|
#
|
|
|
|
_capabilities = {
|
|
0: "Reserved", # RFC 5492
|
|
1: "Multiprotocol Extensions for BGP-4", # RFC 2858
|
|
2: "Route Refresh Capability for BGP-4", # RFC 2918
|
|
3: "Outbound Route Filtering Capability", # RFC 5291
|
|
4: "Multiple routes to a destination capability", # RFC 3107
|
|
5: "Extended Next Hop Encoding", # RFC 5549
|
|
6: "BGP-Extended Message", # (TEMPORARY - registered 2015-09-30, expires 2016-09-30), # noqa: E501
|
|
# draft-ietf-idr-bgp-extended-messages
|
|
64: "Graceful Restart Capability", # RFC 4724
|
|
65: "Support for 4-octet AS number capability", # RFC 6793
|
|
66: "Deprecated (2003-03-06)",
|
|
67: "Support for Dynamic Capability (capability specific)", # draft-ietf-idr-dynamic-cap # noqa: E501
|
|
68: "Multisession BGP Capability", # draft-ietf-idr-bgp-multisession
|
|
69: "ADD-PATH Capability", # RFC-ietf-idr-add-paths-15
|
|
70: "Enhanced Route Refresh Capability", # RFC 7313
|
|
71: "Long-Lived Graceful Restart (LLGR) Capability", # draft-uttaro-idr-bgp-persistence # noqa: E501
|
|
73: "FQDN Capability", # draft-walton-bgp-hostname-capability
|
|
128: "Route Refresh Capability for BGP-4 (Cisco)", # Cisco also uses 128 for RR capability # noqa: E501
|
|
130: "Outbound Route Filtering Capability (Cisco)", # Cisco also uses 130 for ORF capability # noqa: E501
|
|
}
|
|
|
|
|
|
_capabilities_objects = {
|
|
0x01: "BGPCapMultiprotocol", # RFC 2858
|
|
0x02: "BGPCapGeneric", # RFC 2918
|
|
0x03: "BGPCapORF", # RFC 5291
|
|
0x40: "BGPCapGracefulRestart", # RFC 4724
|
|
0x41: "BGPCapFourBytesASN", # RFC 4893
|
|
0x46: "BGPCapGeneric", # Enhanced Route Refresh Capability, RFC 7313
|
|
0x82: "BGPCapORF", # ORF / RFC 5291 (Cisco)
|
|
}
|
|
|
|
|
|
def _register_cls(registry, cls):
|
|
registry[cls.__name__] = cls
|
|
return cls
|
|
|
|
|
|
_capabilities_registry = {}
|
|
|
|
|
|
def _bgp_capability_dispatcher(payload):
|
|
"""
|
|
Returns the right class for a given BGP capability.
|
|
"""
|
|
|
|
cls = _capabilities_registry["BGPCapGeneric"]
|
|
|
|
# By default, calling BGPCapability() will build a "generic" capability.
|
|
if payload is None:
|
|
cls = _capabilities_registry["BGPCapGeneric"]
|
|
|
|
else:
|
|
length = len(payload)
|
|
if length >= _BGP_CAPABILITY_MIN_SIZE:
|
|
code = orb(payload[0])
|
|
cls = _get_cls(_capabilities_objects.get(code, "BGPCapGeneric"))
|
|
|
|
return cls
|
|
|
|
|
|
class _BGPCap_metaclass(type):
|
|
def __new__(cls, clsname, bases, attrs):
|
|
newclass = super(_BGPCap_metaclass, cls).__new__(
|
|
cls, clsname, bases, attrs)
|
|
_register_cls(_capabilities_registry, newclass)
|
|
return newclass
|
|
|
|
|
|
class _BGPCapability_metaclass(Packet_metaclass, _BGPCap_metaclass):
|
|
pass
|
|
|
|
|
|
class BGPCapability(six.with_metaclass(_BGPCapability_metaclass, Packet)):
|
|
"""
|
|
Generic BGP capability.
|
|
"""
|
|
|
|
@classmethod
|
|
def dispatch_hook(cls, _pkt=None, *args, **kargs):
|
|
"""
|
|
Returns the right class for the given data.
|
|
"""
|
|
|
|
return _bgp_capability_dispatcher(_pkt)
|
|
|
|
def pre_dissect(self, s):
|
|
"""
|
|
Check that the payload is long enough (at least 2 bytes).
|
|
"""
|
|
length = len(s)
|
|
if length < _BGP_CAPABILITY_MIN_SIZE:
|
|
err = " ({}".format(length) + " is < _BGP_CAPABILITY_MIN_SIZE "
|
|
err += "({})).".format(_BGP_CAPABILITY_MIN_SIZE)
|
|
raise _BGPInvalidDataException(err)
|
|
return s
|
|
|
|
def post_build(self, p, pay):
|
|
length = 0
|
|
if self.length is None:
|
|
# capability packet length - capability code (1 byte) -
|
|
# capability length (1 byte)
|
|
length = len(p) - 2
|
|
p = p[:1] + chb(length) + p[2:]
|
|
return p + pay
|
|
|
|
|
|
class BGPCapGeneric(BGPCapability):
|
|
"""
|
|
This class provides an implementation of a generic capability.
|
|
"""
|
|
|
|
name = "BGP Capability"
|
|
match_subclass = True
|
|
fields_desc = [
|
|
ByteEnumField("code", 0, _capabilities),
|
|
FieldLenField("length", None, fmt="B", length_of="cap_data"),
|
|
StrLenField("cap_data", '',
|
|
length_from=lambda p: p.length, max_length=255),
|
|
]
|
|
|
|
|
|
#
|
|
# Multiprotocol Extensions for BGP-4
|
|
#
|
|
|
|
class BGPCapMultiprotocol(BGPCapability):
|
|
"""
|
|
This class provides an implementation of the Multiprotocol
|
|
capability.
|
|
References: RFC 4760
|
|
"""
|
|
|
|
name = "Multiprotocol Extensions for BGP-4"
|
|
match_subclass = True
|
|
fields_desc = [
|
|
ByteEnumField("code", 1, _capabilities),
|
|
ByteField("length", 4),
|
|
ShortEnumField("afi", 0, address_family_identifiers),
|
|
ByteField("reserved", 0),
|
|
ByteEnumField("safi", 0, subsequent_afis)
|
|
]
|
|
|
|
|
|
#
|
|
# Outbound Route Filtering Capability for BGP-4
|
|
#
|
|
|
|
_orf_types = {
|
|
0: "Reserved", # RFC 5291
|
|
64: "Address Prefix ORF", # RFC 5292
|
|
65: "CP-ORF", # RFC 7543
|
|
}
|
|
|
|
|
|
send_receive_values = {
|
|
1: "receive",
|
|
2: "send",
|
|
3: "receive + send"
|
|
}
|
|
|
|
|
|
class BGPCapORFBlock(Packet):
|
|
"""
|
|
The "ORFBlock" is made of <AFI, rsvd, SAFI, Number of ORFs, and
|
|
<ORF Type, Send/Receive> entries.
|
|
"""
|
|
|
|
class ORFTuple(Packet):
|
|
"""
|
|
Packet handling <ORF Types, Send/Receive> tuples.
|
|
"""
|
|
|
|
# (ORF Type (1 octet) / Send/Receive (1 octet)) ....
|
|
name = "ORF Type"
|
|
fields_desc = [
|
|
ByteEnumField("orf_type", 0, _orf_types),
|
|
ByteEnumField("send_receive", 0, send_receive_values)
|
|
]
|
|
|
|
name = "ORF Capability Entry"
|
|
fields_desc = [
|
|
ShortEnumField("afi", 0, address_family_identifiers),
|
|
ByteField("reserved", 0),
|
|
ByteEnumField("safi", 0, subsequent_afis),
|
|
FieldLenField(
|
|
"orf_number",
|
|
None,
|
|
count_of="entries",
|
|
fmt="!B"
|
|
),
|
|
PacketListField(
|
|
"entries",
|
|
[],
|
|
ORFTuple,
|
|
count_from=lambda p: p.orf_number
|
|
)
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
count = None
|
|
if self.orf_number is None:
|
|
count = len(self.entries) # orf_type (1 byte) + send_receive (1 byte) # noqa: E501
|
|
p = p[:4] + struct.pack("!B", count) + p[5:]
|
|
return p + pay
|
|
|
|
|
|
class BGPCapORFBlockPacketListField(PacketListField):
|
|
"""
|
|
Handles lists of BGPCapORFBlocks.
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
length = None
|
|
|
|
if self.length_from is not None:
|
|
length = self.length_from(pkt)
|
|
remain = s
|
|
if length is not None:
|
|
remain = s[:length]
|
|
|
|
while remain:
|
|
# block length: afi (2 bytes) + reserved (1 byte) + safi (1 byte) +
|
|
# orf_number (1 byte) + entries (2 bytes * orf_number)
|
|
orf_number = orb(remain[4])
|
|
entries_length = orf_number * 2
|
|
current = remain[:5 + entries_length]
|
|
remain = remain[5 + entries_length:]
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain, lst
|
|
|
|
|
|
class BGPCapORF(BGPCapability):
|
|
"""
|
|
This class provides an implementation of the Outbound Route Filtering
|
|
capability.
|
|
References: RFC 5291
|
|
"""
|
|
|
|
name = "Outbound Route Filtering Capability"
|
|
match_subclass = True
|
|
fields_desc = [
|
|
ByteEnumField("code", 3, _capabilities),
|
|
ByteField("length", None),
|
|
BGPCapORFBlockPacketListField(
|
|
"orf",
|
|
[],
|
|
BGPCapORFBlock,
|
|
length_from=lambda p: p.length
|
|
)
|
|
]
|
|
|
|
|
|
#
|
|
# Graceful Restart capability
|
|
#
|
|
|
|
gr_address_family_flags = {
|
|
128: "Forwarding state preserved (0x80: F bit set)"
|
|
}
|
|
|
|
|
|
class BGPCapGracefulRestart(BGPCapability):
|
|
"""
|
|
This class provides an implementation of the Graceful Restart
|
|
capability.
|
|
References: RFC 4724
|
|
"""
|
|
|
|
class GRTuple(Packet):
|
|
|
|
"""Tuple <AFI, SAFI, Flags for address family>"""
|
|
name = "<AFI, SAFI, Flags for address family>"
|
|
fields_desc = [ShortEnumField("afi", 0, address_family_identifiers),
|
|
ByteEnumField("safi", 0, subsequent_afis),
|
|
ByteEnumField("flags", 0, gr_address_family_flags)]
|
|
|
|
name = "Graceful Restart Capability"
|
|
match_subclass = True
|
|
fields_desc = [ByteEnumField("code", 64, _capabilities),
|
|
ByteField("length", None),
|
|
BitField("restart_flags", 0, 4),
|
|
BitField("restart_time", 0, 12),
|
|
PacketListField("entries", [], GRTuple)]
|
|
|
|
|
|
#
|
|
# Support for 4-octet AS number capability
|
|
#
|
|
|
|
class BGPCapFourBytesASN(BGPCapability):
|
|
"""
|
|
This class provides an implementation of the 4-octet AS number
|
|
capability.
|
|
References: RFC 4893
|
|
"""
|
|
|
|
name = "Support for 4-octet AS number capability"
|
|
match_subclass = True
|
|
fields_desc = [ByteEnumField("code", 65, _capabilities),
|
|
ByteField("length", 4),
|
|
IntField("asn", 0)]
|
|
|
|
|
|
#
|
|
# Authentication Information optional parameter.
|
|
#
|
|
|
|
class BGPAuthenticationInformation(Packet):
|
|
|
|
"""
|
|
Provides an implementation of the Authentication Information optional
|
|
parameter, which is now obsolete.
|
|
References: RFC 1771, RFC 1654, RFC 4271
|
|
"""
|
|
|
|
name = "BGP Authentication Data"
|
|
fields_desc = [ByteField("authentication_code", 0),
|
|
StrField("authentication_data", None)]
|
|
|
|
|
|
#
|
|
# Optional Parameter.
|
|
#
|
|
|
|
|
|
class BGPOptParamPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling the optional parameters (OPEN message).
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
|
|
length = 0
|
|
if self.length_from is not None:
|
|
length = self.length_from(pkt)
|
|
remain = s
|
|
if length is not None:
|
|
remain, ret = s[:length], s[length:]
|
|
|
|
while remain:
|
|
param_len = orb(remain[1]) # Get param length
|
|
current = remain[:2 + param_len]
|
|
remain = remain[2 + param_len:]
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain + ret, lst
|
|
|
|
|
|
class BGPOptParam(Packet):
|
|
"""
|
|
Provides an implementation the OPEN message optional parameters.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "Optional parameter"
|
|
fields_desc = [
|
|
ByteEnumField("param_type", 2, optional_parameter_codes),
|
|
ByteField("param_length", None),
|
|
ConditionalField(
|
|
PacketField(
|
|
"param_value",
|
|
None,
|
|
BGPCapability
|
|
),
|
|
lambda p: p.param_type == 2
|
|
),
|
|
# It"s obsolete, but one can use it provided that
|
|
# param_type == 1.
|
|
ConditionalField(
|
|
PacketField(
|
|
"authentication_data",
|
|
None,
|
|
BGPAuthenticationInformation
|
|
),
|
|
lambda p: p.param_type == 1
|
|
)
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
length = None
|
|
packet = p
|
|
if self.param_length is None:
|
|
if self.param_value is None and self.authentication_data is None:
|
|
length = 0
|
|
else:
|
|
length = len(p) - \
|
|
2 # parameter type (1 byte) - parameter length (1 byte)
|
|
packet = p[:1] + chb(length)
|
|
if (self.param_type == 2 and self.param_value is not None) or\
|
|
(self.param_type == 1 and self.authentication_data is not None): # noqa: E501
|
|
packet = packet + p[2:]
|
|
|
|
return packet + pay
|
|
|
|
|
|
#
|
|
# OPEN
|
|
#
|
|
|
|
class BGPOpen(BGP):
|
|
"""
|
|
OPEN messages are exchanged in order to open a new BGP session.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "OPEN"
|
|
fields_desc = [
|
|
ByteField("version", 4),
|
|
ShortField("my_as", 0),
|
|
ShortField("hold_time", 0),
|
|
IPField("bgp_id", "0.0.0.0"),
|
|
FieldLenField(
|
|
"opt_param_len",
|
|
None,
|
|
length_of="opt_params",
|
|
fmt="!B"
|
|
),
|
|
BGPOptParamPacketListField(
|
|
"opt_params",
|
|
[],
|
|
BGPOptParam,
|
|
length_from=lambda p: p.opt_param_len
|
|
)
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
if self.opt_param_len is None:
|
|
length = len(p) - 10 # 10 is regular length with no additional
|
|
# options
|
|
p = p[:9] + struct.pack("!B", length) + p[10:]
|
|
return p + pay
|
|
|
|
|
|
#
|
|
# UPDATE
|
|
#
|
|
|
|
#
|
|
# Path attributes
|
|
#
|
|
|
|
#
|
|
# Dictionaries
|
|
|
|
path_attributes = {
|
|
0: "Reserved",
|
|
1: "ORIGIN", # RFC 4271
|
|
2: "AS_PATH", # RFC 4271
|
|
3: "NEXT_HOP", # RFC 4271
|
|
4: "MULTI_EXIT_DISC", # RFC 4271
|
|
5: "LOCAL_PREF", # RFC 4271
|
|
6: "ATOMIC_AGGREGATE", # RFC 4271
|
|
7: "AGGREGATOR", # RFC 4271
|
|
8: "COMMUNITY", # RFC 1997
|
|
9: "ORIGINATOR_ID", # RFC 4456
|
|
10: "CLUSTER_LIST", # RFC 4456
|
|
11: "DPA (deprecated)", # RFC 6938
|
|
12: "ADVERTISER (Historic) (deprecated)", # RFC 4223, RFC 6938
|
|
13: "RCID_PATH / CLUSTER_ID (Historic) (deprecated)", # RFC 4223, RFC 6938
|
|
14: "MP_REACH_NLRI", # RFC 4760
|
|
15: "MP_UNREACH_NLRI", # RFC 4760
|
|
16: "EXTENDED COMMUNITIES", # RFC 4360
|
|
17: "AS4_PATH", # RFC 6793
|
|
18: "AS4_AGGREGATOR", # RFC 6793
|
|
19: "SAFI Specific Attribute (SSA) (deprecated)", # draft-kapoor-nalawade-idr-bgp-ssa-00, # noqa: E501
|
|
# draft-nalawade-idr-mdt-safi-00, draft-wijnands-mt-discovery-00
|
|
20: "Connector Attribute (deprecated)", # RFC 6037
|
|
21: "AS_PATHLIMIT (deprecated)", # draft-ietf-idr-as-pathlimit
|
|
22: "PMSI_TUNNEL", # RFC 6514
|
|
23: "Tunnel Encapsulation Attribute", # RFC 5512
|
|
24: "Traffic Engineering", # RFC 5543
|
|
25: "IPv6 Address Specific Extended Community", # RFC 5701
|
|
26: "AIGP", # RFC 7311
|
|
27: "PE Distinguisher Labels", # RFC 6514
|
|
28: "BGP Entropy Label Capability Attribute (deprecated)", # RFC 6790, RFC 7447 # noqa: E501
|
|
29: "BGP-LS Attribute", # RFC 7752
|
|
40: "BGP Prefix-SID", # (TEMPORARY - registered 2015-09-30, expires 2016-09-30) # noqa: E501
|
|
# draft-ietf-idr-bgp-prefix-sid
|
|
128: "ATTR_SET", # RFC 6368
|
|
255: "Reserved for development"
|
|
}
|
|
|
|
# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xml
|
|
attributes_flags = {
|
|
1: 0x40, # ORIGIN
|
|
2: 0x40, # AS_PATH
|
|
3: 0x40, # NEXT_HOP
|
|
4: 0x80, # MULTI_EXIT_DISC
|
|
5: 0x40, # LOCAL_PREF
|
|
6: 0x40, # ATOMIC_AGGREGATE
|
|
7: 0xc0, # AGGREGATOR
|
|
8: 0xc0, # COMMUNITIES (RFC 1997)
|
|
9: 0x80, # ORIGINATOR_ID (RFC 4456)
|
|
10: 0x80, # CLUSTER_LIST (RFC 4456)
|
|
11: 0xc0, # DPA (RFC 6938)
|
|
12: 0x80, # ADVERTISER (RFC 1863, RFC 4223)
|
|
13: 0x80, # RCID_PATH (RFC 1863, RFC 4223)
|
|
14: 0x80, # MP_REACH_NLRI (RFC 4760)
|
|
15: 0x80, # MP_UNREACH_NLRI (RFC 4760)
|
|
16: 0xc0, # EXTENDED_COMMUNITIES (RFC 4360)
|
|
17: 0xc0, # AS4_PATH (RFC 6793)
|
|
18: 0xc0, # AS4_AGGREGATOR (RFC 6793)
|
|
19: 0xc0, # SSA (draft-kapoor-nalawade-idr-bgp-ssa-00)
|
|
20: 0xc0, # Connector (RFC 6037)
|
|
21: 0xc0, # AS_PATHLIMIT (draft-ietf-idr-as-pathlimit)
|
|
22: 0xc0, # PMSI_TUNNEL (RFC 6514)
|
|
23: 0xc0, # Tunnel Encapsulation (RFC 5512)
|
|
24: 0x80, # Traffic Engineering (RFC 5543)
|
|
25: 0xc0, # IPv6 Address Specific Extended Community (RFC 5701)
|
|
26: 0x80, # AIGP (RFC 7311)
|
|
27: 0xc0, # PE Distinguisher Labels (RFC 6514)
|
|
28: 0xc0, # BGP Entropy Label Capability Attribute
|
|
29: 0x80, # BGP-LS Attribute
|
|
40: 0xc0, # BGP Prefix-SID
|
|
128: 0xc0 # ATTR_SET (RFC 6368)
|
|
}
|
|
|
|
|
|
class BGPPathAttrPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling the path attributes (UPDATE message).
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
length = 0
|
|
|
|
if self.length_from is not None:
|
|
length = self.length_from(pkt)
|
|
ret = ""
|
|
remain = s
|
|
if length is not None:
|
|
remain, ret = s[:length], s[length:]
|
|
|
|
while remain:
|
|
#
|
|
# Get the path attribute flags
|
|
flags = orb(remain[0])
|
|
|
|
attr_len = 0
|
|
if has_extended_length(flags):
|
|
attr_len = struct.unpack("!H", remain[2:4])[0]
|
|
current = remain[:4 + attr_len]
|
|
remain = remain[4 + attr_len:]
|
|
else:
|
|
attr_len = orb(remain[2])
|
|
current = remain[:3 + attr_len]
|
|
remain = remain[3 + attr_len:]
|
|
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain + ret, lst
|
|
|
|
|
|
#
|
|
# ORIGIN
|
|
#
|
|
|
|
class BGPPAOrigin(Packet):
|
|
|
|
"""
|
|
Packet handling the ORIGIN attribute value.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "ORIGIN"
|
|
fields_desc = [
|
|
ByteEnumField("origin", 0, {0: "IGP", 1: "EGP", 2: "INCOMPLETE"})]
|
|
|
|
|
|
#
|
|
# AS_PATH (2 bytes and 4 bytes)
|
|
#
|
|
|
|
as_path_segment_types = {
|
|
# RFC 4271
|
|
1: "AS_SET",
|
|
2: "AS_SEQUENCE",
|
|
|
|
# RFC 5065
|
|
3: "AS_CONFED_SEQUENCE",
|
|
4: "AS_CONFED_SET"
|
|
}
|
|
|
|
|
|
class ASPathSegmentPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling AS_PATH segments.
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
remain = s
|
|
|
|
while remain:
|
|
#
|
|
# Get the segment length
|
|
segment_length = orb(remain[1])
|
|
|
|
if bgp_module_conf.use_2_bytes_asn:
|
|
current = remain[:2 + segment_length * 2]
|
|
remain = remain[2 + segment_length * 2:]
|
|
else:
|
|
current = remain[:2 + segment_length * 4]
|
|
remain = remain[2 + segment_length * 4:]
|
|
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain, lst
|
|
|
|
|
|
class BGPPAASPath(Packet):
|
|
"""
|
|
Packet handling the AS_PATH attribute value (2 bytes ASNs, for old
|
|
speakers).
|
|
References: RFC 4271, RFC 5065
|
|
"""
|
|
|
|
AS_TRANS = 23456
|
|
|
|
class ASPathSegment(Packet):
|
|
"""
|
|
Provides an implementation for AS_PATH segments with 2 bytes ASNs.
|
|
"""
|
|
|
|
fields_desc = [
|
|
ByteEnumField("segment_type", 2, as_path_segment_types),
|
|
ByteField("segment_length", None),
|
|
FieldListField("segment_value", [], ShortField("asn", 0))
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
segment_len = self.segment_length
|
|
if segment_len is None:
|
|
segment_len = len(self.segment_value)
|
|
p = p[:1] + chb(segment_len) + p[2:]
|
|
|
|
return p + pay
|
|
|
|
name = "AS_PATH (RFC 4271)"
|
|
fields_desc = [
|
|
ASPathSegmentPacketListField("segments", [], ASPathSegment)]
|
|
|
|
|
|
class BGPPAAS4BytesPath(Packet):
|
|
"""
|
|
Packet handling the AS_PATH attribute value (4 bytes ASNs, for new
|
|
speakers -> ASNs are encoded as IntFields).
|
|
References: RFC 4893
|
|
"""
|
|
|
|
class ASPathSegment(Packet):
|
|
"""
|
|
Provides an implementation for AS_PATH segments with 4 bytes ASNs.
|
|
"""
|
|
|
|
fields_desc = [ByteEnumField("segment_type", 2, as_path_segment_types),
|
|
ByteField("segment_length", None),
|
|
FieldListField("segment_value", [], IntField("asn", 0))]
|
|
|
|
def post_build(self, p, pay):
|
|
segment_len = self.segment_length
|
|
if segment_len is None:
|
|
segment_len = len(self.segment_value)
|
|
p = p[:1] + chb(segment_len) + p[2:]
|
|
|
|
return p + pay
|
|
|
|
name = "AS_PATH (RFC 4893)"
|
|
fields_desc = [
|
|
ASPathSegmentPacketListField("segments", [], ASPathSegment)]
|
|
|
|
|
|
#
|
|
# NEXT_HOP
|
|
#
|
|
|
|
class BGPPANextHop(Packet):
|
|
"""
|
|
Packet handling the NEXT_HOP attribute value.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "NEXT_HOP"
|
|
fields_desc = [IPField("next_hop", "0.0.0.0")]
|
|
|
|
|
|
#
|
|
# MULTI_EXIT_DISC
|
|
#
|
|
|
|
class BGPPAMultiExitDisc(Packet):
|
|
"""
|
|
Packet handling the MULTI_EXIT_DISC attribute value.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "MULTI_EXIT_DISC"
|
|
fields_desc = [IntField("med", 0)]
|
|
|
|
|
|
#
|
|
# LOCAL_PREF
|
|
#
|
|
|
|
class BGPPALocalPref(Packet):
|
|
"""
|
|
Packet handling the LOCAL_PREF attribute value.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "LOCAL_PREF"
|
|
fields_desc = [IntField("local_pref", 0)]
|
|
|
|
|
|
#
|
|
# ATOMIC_AGGREGATE
|
|
#
|
|
|
|
class BGPPAAtomicAggregate(Packet):
|
|
"""
|
|
Packet handling the ATOMIC_AGGREGATE attribute value.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "ATOMIC_AGGREGATE"
|
|
|
|
|
|
#
|
|
# AGGREGATOR
|
|
#
|
|
|
|
class BGPPAAggregator(Packet):
|
|
"""
|
|
Packet handling the AGGREGATOR attribute value.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "AGGREGATOR"
|
|
fields_desc = [ShortField("aggregator_asn", 0),
|
|
IPField("speaker_address", "0.0.0.0")]
|
|
|
|
|
|
#
|
|
# COMMUNITIES
|
|
#
|
|
|
|
# http://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xml
|
|
well_known_communities = {
|
|
0xFFFFFF01: "NO_EXPORT", # RFC 1997
|
|
0xFFFFFF02: "NO_ADVERTISE", # RFC 1997
|
|
0xFFFFFF03: "NO_EXPORT_SUBCONFED", # RFC 1997
|
|
0xFFFFFF04: "NOPEER", # RFC 3765
|
|
0xFFFF0000: "planned-shut", # draft-francois-bgp-gshut
|
|
0xFFFF0001: "ACCEPT-OWN", # RFC 7611
|
|
0xFFFF0002: "ROUTE_FILTER_TRANSLATED_v4", # draft-l3vpn-legacy-rtc
|
|
0xFFFF0003: "ROUTE_FILTER_v4", # draft-l3vpn-legacy-rtc
|
|
0xFFFF0004: "ROUTE_FILTER_TRANSLATED_v6", # draft-l3vpn-legacy-rtc
|
|
0xFFFF0005: "ROUTE_FILTER_v6", # draft-l3vpn-legacy-rtc
|
|
0xFFFF0006: "LLGR_STALE", # draft-uttaro-idr-bgp-persistence
|
|
0xFFFF0007: "NO_LLGR", # draft-uttaro-idr-bgp-persistence
|
|
0xFFFF0008: "accept-own-nexthop", # Ashutosh_Grewal
|
|
}
|
|
|
|
|
|
class BGPPACommunity(Packet):
|
|
"""
|
|
Packet handling the COMMUNITIES attribute value.
|
|
References: RFC 1997
|
|
"""
|
|
|
|
name = "COMMUNITIES"
|
|
fields_desc = [IntEnumField("community", 0, well_known_communities)]
|
|
|
|
|
|
#
|
|
# ORIGINATOR_ID
|
|
#
|
|
|
|
class BGPPAOriginatorID(Packet):
|
|
"""
|
|
Packet handling the ORIGINATOR_ID attribute value.
|
|
References: RFC 4456
|
|
"""
|
|
|
|
name = "ORIGINATOR_ID"
|
|
fields_desc = [IPField("originator_id", "0.0.0.0")]
|
|
|
|
|
|
#
|
|
# CLUSTER_LIST
|
|
#
|
|
|
|
class BGPPAClusterList(Packet):
|
|
"""
|
|
Packet handling the CLUSTER_LIST attribute value.
|
|
References: RFC 4456
|
|
"""
|
|
|
|
name = "CLUSTER_LIST"
|
|
fields_desc = [
|
|
FieldListField("cluster_list", [], IntField("cluster_id", 0))]
|
|
|
|
|
|
#
|
|
# EXTENDED COMMUNITIES (RFC 4360)
|
|
#
|
|
|
|
# BGP Transitive Extended Community Types
|
|
# http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive
|
|
_ext_comm_types = {
|
|
0x00: "Transitive Two-Octet AS-Specific Extended Community", # RFC 7153
|
|
0x01: "Transitive IPv4-Address-Specific Extended Community", # RFC 7153
|
|
0x02: "Transitive Four-Octet AS-Specific Extended Community", # RFC 7153
|
|
0x03: "Transitive Opaque Extended Community", # RFC 7153
|
|
0x04: "QoS Marking", # Thomas_Martin_Knoll
|
|
0x05: "CoS Capability", # Thomas_Martin_Knoll
|
|
0x06: "EVPN", # RFC 7153
|
|
0x07: "Unassigned",
|
|
0x08: "Flow spec redirect/mirror to IP next-hop", # draft-simpson-idr-flowspec-redirect # noqa: E501
|
|
|
|
# BGP Non-Transitive Extended Community Types
|
|
0x40: "Non-Transitive Two-Octet AS-Specific Extended Community", # RFC 7153 # noqa: E501
|
|
0x41: "Non-Transitive IPv4-Address-Specific Extended Community", # RFC 7153 # noqa: E501
|
|
0x42: "Non-Transitive Four-Octet AS-Specific Extended Community", # RFC 7153 # noqa: E501
|
|
0x43: "Non-Transitive Opaque Extended Community", # RFC 7153
|
|
0x44: "QoS Marking", # Thomas_Martin_Knoll
|
|
|
|
0x80: "Generic Transitive Experimental Use Extended Community", # RFC 7153
|
|
0x81: "Generic Transitive Experimental Use Extended Community Part 2", # RFC 7674 # noqa: E501
|
|
0x82: "Generic Transitive Experimental Use Extended Community Part 3", # RFC 7674 # noqa: E501
|
|
}
|
|
|
|
# EVPN Extended Community Sub-Types
|
|
_ext_comm_evpn_subtypes = {
|
|
0x00: "MAC Mobility", # RFC 7432
|
|
0x01: "ESI Label", # RFC 7432
|
|
0x02: "ES-Import Route Target", # RFC 7432
|
|
0x03: "EVPN Router\"s MAC Extended Community",
|
|
# draft-sajassi-l2vpn-evpn-inter-subnet-forwarding
|
|
0x04: "Layer 2 Extended Community", # draft-ietf-bess-evpn-vpws
|
|
0x05: "E-TREE Extended Community", # draft-ietf-bess-evpn-etree
|
|
0x06: "DF Election Extended Community", # draft-ietf-bess-evpn-df-election
|
|
0x07: "I-SID Extended Community", # draft-sajassi-bess-evpn-virtual-eth-segment # noqa: E501
|
|
}
|
|
|
|
# Transitive Two-Octet AS-Specific Extended Community Sub-Types
|
|
_ext_comm_trans_two_octets_as_specific_subtypes = {
|
|
0x02: "Route Target", # RFC 4360
|
|
0x03: "Route Origin", # RFC 4360
|
|
0x04: "Unassigned", # RFC 4360
|
|
0x05: "OSPF Domain Identifier", # RFC 4577
|
|
0x08: "BGP Data Collection", # RFC 4384
|
|
0x09: "Source AS", # RFC 6514
|
|
0x0a: "L2VPN Identifier", # RFC 6074
|
|
0x0010: "Cisco VPN-Distinguisher", # Eric_Rosen
|
|
}
|
|
|
|
# Non-Transitive Two-Octet AS-Specific Extended Community Sub-Types
|
|
_ext_comm_non_trans_two_octets_as_specific_subtypes = {
|
|
0x04: "Link Bandwidth Extended Community", # draft-ietf-idr-link-bandwidth-00 # noqa: E501
|
|
0x80: "Virtual-Network Identifier Extended Community",
|
|
# draft-drao-bgp-l3vpn-virtual-network-overlays
|
|
}
|
|
|
|
# Transitive Four-Octet AS-Specific Extended Community Sub-Types
|
|
_ext_comm_trans_four_octets_as_specific_subtypes = {
|
|
0x02: "Route Target", # RFC 5668
|
|
0x03: "Route Origin", # RFC 5668
|
|
0x04: "Generic", # draft-ietf-idr-as4octet-extcomm-generic-subtype
|
|
0x05: "OSPF Domain Identifier", # RFC 4577
|
|
0x08: "BGP Data Collection", # RFC 4384
|
|
0x09: "Source AS", # RFC 6514
|
|
0x10: "Cisco VPN Identifier", # Eric_Rosen
|
|
}
|
|
|
|
# Non-Transitive Four-Octet AS-Specific Extended Community Sub-Types
|
|
_ext_comm_non_trans_four_octets_as_specific_subtypes = {
|
|
0x04: "Generic", # draft-ietf-idr-as4octet-extcomm-generic-subtype
|
|
}
|
|
|
|
# Transitive IPv4-Address-Specific Extended Community Sub-Types
|
|
_ext_comm_trans_ipv4_addr_specific_subtypes = {
|
|
0x02: "Route Target", # RFC 4360
|
|
0x03: "Route Origin", # RFC 4360
|
|
0x05: "OSPF Domain Identifier", # RFC 4577
|
|
0x07: "OSPF Route ID", # RFC 4577
|
|
0x0a: "L2VPN Identifier", # RFC 6074
|
|
0x0b: "VRF Route Import", # RFC 6514
|
|
0x0c: "Flow-spec Redirect to IPv4", # draft-ietf-idr-flowspec-redirect
|
|
0x10: "Cisco VPN-Distinguisher", # Eric_Rosen
|
|
0x12: "Inter-Area P2MP Segmented Next-Hop", # RFC 7524
|
|
}
|
|
|
|
# Non-Transitive IPv4-Address-Specific Extended Community Sub-Types
|
|
_ext_comm_non_trans_ipv4_addr_specific_subtypes = {}
|
|
|
|
# Transitive Opaque Extended Community Sub-Types
|
|
_ext_comm_trans_opaque_subtypes = {
|
|
0x01: "Cost Community", # draft-ietf-idr-custom-decision
|
|
0x03: "CP-ORF", # RFC 7543
|
|
0x04: "Extranet Source Extended Community", # RFC 7900
|
|
0x05: "Extranet Separation Extended Community", # RFC 7900
|
|
0x06: "OSPF Route Type", # RFC 4577
|
|
0x07: "Additional PMSI Tunnel Attribute Flags", # RFC 7902
|
|
0x0b: "Color Extended Community", # RFC 5512
|
|
0x0c: "Encapsulation Extended Community", # RFC 5512
|
|
0x0d: "Default Gateway", # Yakov_Rekhter
|
|
0x0e: "Point-to-Point-to-Multipoint (PPMP) Label", # Rishabh_Parekh
|
|
0x13: "Route-Target Record", # draft-ietf-bess-service-chaining
|
|
0x14: "Consistent Hash Sort Order", # draft-ietf-bess-service-chaining
|
|
}
|
|
|
|
# Non-Transitive Opaque Extended Community Sub-Types
|
|
_ext_comm_non_trans_opaque_subtypes = {
|
|
0x00: "BGP Origin Validation State", # draft-ietf-sidr-origin-validation-signaling # noqa: E501
|
|
0x01: "Cost Community", # draft-ietf-idr-custom-decision
|
|
}
|
|
|
|
# Generic Transitive Experimental Use Extended Community Sub-Types
|
|
_ext_comm_generic_transitive_exp_subtypes = {
|
|
0x00: "OSPF Route Type (deprecated)", # RFC 4577
|
|
0x01: "OSPF Router ID (deprecated)", # RFC 4577
|
|
0x05: "OSPF Domain Identifier (deprecated)", # RFC 4577
|
|
0x06: "Flow spec traffic-rate", # RFC 5575
|
|
0x07: "Flow spec traffic-action", # RFC 5575
|
|
0x08: "Flow spec redirect AS-2byte format", # RFC 5575, RFC 7674
|
|
0x09: "Flow spec traffic-remarking", # RFC 5575
|
|
0x0a: "Layer2 Info Extended Community", # RFC 4761
|
|
0x0b: "E-Tree Info", # RFC 7796
|
|
}
|
|
|
|
# Generic Transitive Experimental Use Extended Community Part 2 Sub-Types
|
|
_ext_comm_generic_transitive_exp_part2_subtypes = {
|
|
0x08: "Flow spec redirect IPv4 format", # RFC 7674
|
|
}
|
|
|
|
# Generic Transitive Experimental Use Extended Community Part 3 Sub-Types
|
|
_ext_comm_generic_transitive_exp_part3_subtypes = {
|
|
0x08: "Flow spec redirect AS-4byte format", # RFC 7674
|
|
}
|
|
|
|
# Traffic Action Fields
|
|
_ext_comm_traffic_action_fields = {
|
|
47: "Terminal Action", # RFC 5575
|
|
46: "Sample", # RFC 5575
|
|
}
|
|
|
|
# Transitive IPv6-Address-Specific Extended Community Types
|
|
_ext_comm_trans_ipv6_addr_specific_types = {
|
|
0x0002: "Route Target", # RFC 5701
|
|
0x0003: "Route Origin", # RFC 5701
|
|
0x0004: "OSPFv3 Route Attributes (DEPRECATED)", # RFC 6565
|
|
0x000b: "VRF Route Import", # RFC 6515, RFC 6514
|
|
0x000c: "Flow-spec Redirect to IPv6", # draft-ietf-idr-flowspec-redirect-ip # noqa: E501
|
|
0x0010: "Cisco VPN-Distinguisher", # Eric_Rosen
|
|
0x0011: "UUID-based Route Target", # Dhananjaya_Rao
|
|
0x0012: "Inter-Area P2MP Segmented Next-Hop", # RFC 7524
|
|
}
|
|
|
|
# Non-Transitive IPv6-Address-Specific Extended Community Types
|
|
_ext_comm_non_trans_ipv6_addr_specific_types = {}
|
|
|
|
|
|
_ext_comm_subtypes_classes = {
|
|
0x00: _ext_comm_trans_two_octets_as_specific_subtypes,
|
|
0x01: _ext_comm_trans_ipv4_addr_specific_subtypes,
|
|
0x02: _ext_comm_trans_four_octets_as_specific_subtypes,
|
|
0x03: _ext_comm_trans_opaque_subtypes,
|
|
0x06: _ext_comm_evpn_subtypes,
|
|
0x40: _ext_comm_non_trans_two_octets_as_specific_subtypes,
|
|
0x41: _ext_comm_non_trans_ipv4_addr_specific_subtypes,
|
|
0x42: _ext_comm_non_trans_four_octets_as_specific_subtypes,
|
|
0x43: _ext_comm_non_trans_opaque_subtypes,
|
|
0x80: _ext_comm_generic_transitive_exp_subtypes,
|
|
0x81: _ext_comm_generic_transitive_exp_part2_subtypes,
|
|
0x82: _ext_comm_generic_transitive_exp_part3_subtypes,
|
|
}
|
|
|
|
|
|
#
|
|
# Extended Community "templates"
|
|
#
|
|
|
|
class BGPPAExtCommTwoOctetASSpecific(Packet):
|
|
"""
|
|
Packet handling the Two-Octet AS Specific Extended Community attribute
|
|
value.
|
|
References: RFC 4360
|
|
"""
|
|
|
|
name = "Two-Octet AS Specific Extended Community"
|
|
fields_desc = [
|
|
ShortField("global_administrator", 0), IntField("local_administrator", 0)] # noqa: E501
|
|
|
|
|
|
class BGPPAExtCommFourOctetASSpecific(Packet):
|
|
"""
|
|
Packet handling the Four-Octet AS Specific Extended Community
|
|
attribute value.
|
|
References: RFC 5668
|
|
"""
|
|
|
|
name = "Four-Octet AS Specific Extended Community"
|
|
fields_desc = [
|
|
IntField("global_administrator", 0), ShortField("local_administrator", 0)] # noqa: E501
|
|
|
|
|
|
class BGPPAExtCommIPv4AddressSpecific(Packet):
|
|
"""
|
|
Packet handling the IPv4 Address Specific Extended Community attribute
|
|
value.
|
|
References: RFC 4360
|
|
"""
|
|
|
|
name = "IPv4 Address Specific Extended Community"
|
|
fields_desc = [
|
|
IntField("global_administrator", 0), ShortField("local_administrator", 0)] # noqa: E501
|
|
|
|
|
|
class BGPPAExtCommOpaque(Packet):
|
|
"""
|
|
Packet handling the Opaque Extended Community attribute value.
|
|
References: RFC 4360
|
|
"""
|
|
|
|
name = "Opaque Extended Community"
|
|
fields_desc = [StrFixedLenField("value", "", length=6)]
|
|
|
|
|
|
#
|
|
# FlowSpec related extended communities
|
|
#
|
|
|
|
class BGPPAExtCommTrafficRate(Packet):
|
|
"""
|
|
Packet handling the (FlowSpec) "traffic-rate" extended community.
|
|
References: RFC 5575
|
|
"""
|
|
|
|
name = "FlowSpec traffic-rate extended community"
|
|
fields_desc = [
|
|
ShortField("id", 0),
|
|
IEEEFloatField("rate", 0)
|
|
]
|
|
|
|
|
|
class BGPPAExtCommTrafficAction(Packet):
|
|
"""
|
|
Packet handling the (FlowSpec) "traffic-action" extended community.
|
|
References: RFC 5575
|
|
"""
|
|
|
|
name = "FlowSpec traffic-action extended community"
|
|
fields_desc = [
|
|
BitField("reserved", 0, 46),
|
|
BitField("sample", 0, 1),
|
|
BitField("terminal_action", 0, 1)
|
|
]
|
|
|
|
|
|
class BGPPAExtCommRedirectAS2Byte(Packet):
|
|
"""
|
|
Packet handling the (FlowSpec) "redirect AS-2byte" extended community
|
|
(RFC 7674).
|
|
References: RFC 7674
|
|
"""
|
|
|
|
name = "FlowSpec redirect AS-2byte extended community"
|
|
fields_desc = [
|
|
ShortField("asn", 0),
|
|
IntField("value", 0)
|
|
]
|
|
|
|
|
|
class BGPPAExtCommRedirectIPv4(Packet):
|
|
"""
|
|
Packet handling the (FlowSpec) "redirect IPv4" extended community.
|
|
(RFC 7674).
|
|
References: RFC 7674
|
|
"""
|
|
|
|
name = "FlowSpec redirect IPv4 extended community"
|
|
fields_desc = [
|
|
IntField("ip_addr", 0),
|
|
ShortField("value", 0)
|
|
]
|
|
|
|
|
|
class BGPPAExtCommRedirectAS4Byte(Packet):
|
|
"""
|
|
Packet handling the (FlowSpec) "redirect AS-4byte" extended community.
|
|
(RFC 7674).
|
|
References: RFC 7674
|
|
"""
|
|
|
|
name = "FlowSpec redirect AS-4byte extended community"
|
|
fields_desc = [
|
|
IntField("asn", 0),
|
|
ShortField("value", 0)
|
|
]
|
|
|
|
|
|
class BGPPAExtCommTrafficMarking(Packet):
|
|
"""
|
|
Packet handling the (FlowSpec) "traffic-marking" extended community.
|
|
References: RFC 5575
|
|
"""
|
|
|
|
name = "FlowSpec traffic-marking extended community"
|
|
fields_desc = [
|
|
BitEnumField("dscp", 48, 48, _ext_comm_traffic_action_fields)
|
|
]
|
|
|
|
|
|
_ext_high_low_dict = {
|
|
BGPPAExtCommTwoOctetASSpecific: (0x00, 0x00),
|
|
BGPPAExtCommIPv4AddressSpecific: (0x01, 0x00),
|
|
BGPPAExtCommFourOctetASSpecific: (0x02, 0x00),
|
|
BGPPAExtCommOpaque: (0x03, 0x00),
|
|
BGPPAExtCommTrafficRate: (0x80, 0x06),
|
|
BGPPAExtCommTrafficAction: (0x80, 0x07),
|
|
BGPPAExtCommRedirectAS2Byte: (0x80, 0x08),
|
|
BGPPAExtCommTrafficMarking: (0x80, 0x09),
|
|
BGPPAExtCommRedirectIPv4: (0x81, 0x08),
|
|
BGPPAExtCommRedirectAS4Byte: (0x82, 0x08),
|
|
}
|
|
|
|
|
|
class _ExtCommValuePacketField(PacketField):
|
|
"""
|
|
PacketField handling Extended Communities "value parts".
|
|
"""
|
|
|
|
__slots__ = ["type_from"]
|
|
|
|
def __init__(self, name, default, cls, remain=0, type_from=(0, 0)):
|
|
PacketField.__init__(self, name, default, cls, remain)
|
|
self.type_from = type_from
|
|
|
|
def m2i(self, pkt, m):
|
|
ret = None
|
|
type_high, type_low = self.type_from(pkt)
|
|
|
|
if type_high == 0x00 or type_high == 0x40:
|
|
# Two-Octet AS Specific Extended Community
|
|
ret = BGPPAExtCommTwoOctetASSpecific(m)
|
|
|
|
elif type_high == 0x01 or type_high == 0x41:
|
|
# IPv4 Address Specific
|
|
ret = BGPPAExtCommIPv4AddressSpecific(m)
|
|
|
|
elif type_high == 0x02 or type_high == 0x42:
|
|
# Four-octet AS Specific Extended Community
|
|
ret = BGPPAExtCommFourOctetASSpecific(m)
|
|
|
|
elif type_high == 0x03 or type_high == 0x43:
|
|
# Opaque
|
|
ret = BGPPAExtCommOpaque(m)
|
|
|
|
elif type_high == 0x80:
|
|
# FlowSpec
|
|
if type_low == 0x06:
|
|
ret = BGPPAExtCommTrafficRate(m)
|
|
elif type_low == 0x07:
|
|
ret = BGPPAExtCommTrafficAction(m)
|
|
elif type_low == 0x08:
|
|
ret = BGPPAExtCommRedirectAS2Byte(m)
|
|
elif type_low == 0x09:
|
|
ret = BGPPAExtCommTrafficMarking(m)
|
|
|
|
elif type_high == 0x81:
|
|
# FlowSpec
|
|
if type_low == 0x08:
|
|
ret = BGPPAExtCommRedirectIPv4(m)
|
|
|
|
elif type_high == 0x82:
|
|
# FlowSpec
|
|
if type_low == 0x08:
|
|
ret = BGPPAExtCommRedirectAS4Byte(m)
|
|
|
|
else:
|
|
ret = conf.raw_layer(m)
|
|
|
|
return ret
|
|
|
|
|
|
class BGPPAIPv6AddressSpecificExtComm(Packet):
|
|
"""
|
|
Provides an implementation of the IPv6 Address Specific Extended
|
|
Community attribute. This attribute is not defined using the existing
|
|
BGP Extended Community attribute (see the RFC 5701 excerpt below).
|
|
References: RFC 5701
|
|
"""
|
|
|
|
name = "IPv6 Address Specific Extended Community"
|
|
fields_desc = [
|
|
IP6Field("global_administrator", "::"), ShortField("local_administrator", 0)] # noqa: E501
|
|
|
|
|
|
def _get_ext_comm_subtype(type_high):
|
|
"""
|
|
Returns a ByteEnumField with the right sub-types dict for a given community. # noqa: E501
|
|
http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml
|
|
"""
|
|
|
|
return _ext_comm_subtypes_classes.get(type_high, {})
|
|
|
|
|
|
class _TypeLowField(ByteField):
|
|
"""
|
|
Field used to retrieve "dynamically" the right sub-type dict.
|
|
"""
|
|
|
|
__slots__ = ["enum_from"]
|
|
|
|
def __init__(self, name, default, enum_from=None):
|
|
ByteField.__init__(self, name=name, default=default)
|
|
self.enum_from = enum_from
|
|
|
|
def i2repr(self, pkt, i):
|
|
enum = self.enum_from(pkt)
|
|
return enum.get(i, i)
|
|
|
|
|
|
class BGPPAExtCommunity(Packet):
|
|
"""
|
|
Provides an implementation of the Extended Communities attribute.
|
|
References: RFC 4360
|
|
"""
|
|
|
|
name = "EXTENDED_COMMUNITY"
|
|
fields_desc = [
|
|
ByteEnumField("type_high", 0, _ext_comm_types),
|
|
_TypeLowField(
|
|
"type_low",
|
|
None,
|
|
enum_from=lambda x: _get_ext_comm_subtype(x.type_high)
|
|
),
|
|
_ExtCommValuePacketField(
|
|
"value",
|
|
None,
|
|
Packet,
|
|
type_from=lambda x: (x.type_high, x.type_low)
|
|
)
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
if self.value is None:
|
|
p = p[:2]
|
|
if self.type_low is None and self.value is not None:
|
|
high, low = _ext_high_low_dict.get(self.value.__class__, (0x00, 0x00)) # noqa: E501
|
|
p = chb(high) + chb(low) + p[2:]
|
|
return p + pay
|
|
|
|
|
|
class _ExtCommsPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling a list of extended communities.
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
length = len(s)
|
|
remain = s[:length]
|
|
|
|
while remain:
|
|
current = remain[:8]
|
|
remain = remain[8:]
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain, lst
|
|
|
|
|
|
class BGPPAExtComms(Packet):
|
|
"""
|
|
Packet handling the multiple extended communities.
|
|
"""
|
|
|
|
name = "EXTENDED_COMMUNITIES"
|
|
fields_desc = [
|
|
_ExtCommsPacketListField(
|
|
"extended_communities",
|
|
[],
|
|
BGPPAExtCommunity
|
|
)
|
|
]
|
|
|
|
|
|
class MPReachNLRIPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling the AFI specific part (except for the length of
|
|
Next Hop Network Address field, which is not AFI specific) of the
|
|
MP_REACH_NLRI attribute.
|
|
"""
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
remain = s
|
|
|
|
# IPv6
|
|
if pkt.afi == 2:
|
|
if pkt.safi == 1:
|
|
# BGPNLRI_IPv6
|
|
while remain:
|
|
mask = orb(remain[0])
|
|
length_in_bytes = (mask + 7) // 8
|
|
current = remain[:length_in_bytes + 1]
|
|
remain = remain[length_in_bytes + 1:]
|
|
prefix = BGPNLRI_IPv6(current)
|
|
lst.append(prefix)
|
|
|
|
return remain, lst
|
|
|
|
|
|
class BGPPAMPReachNLRI(Packet):
|
|
"""
|
|
Packet handling the MP_REACH_NLRI attribute value, for non IPv6
|
|
AFI.
|
|
References: RFC 4760
|
|
"""
|
|
|
|
name = "MP_REACH_NLRI"
|
|
fields_desc = [
|
|
ShortEnumField("afi", 0, address_family_identifiers),
|
|
ByteEnumField("safi", 0, subsequent_afis),
|
|
ByteField("nh_addr_len", 0),
|
|
ConditionalField(IPField("nh_v4_addr", "0.0.0.0"),
|
|
lambda x: x.afi == 1 and x.nh_addr_len == 4),
|
|
ConditionalField(IP6Field("nh_v6_addr", "::"),
|
|
lambda x: x.afi == 2 and x.nh_addr_len == 16),
|
|
ConditionalField(IP6Field("nh_v6_global", "::"),
|
|
lambda x: x.afi == 2 and x.nh_addr_len == 32),
|
|
ConditionalField(IP6Field("nh_v6_link_local", "::"),
|
|
lambda x: x.afi == 2 and x.nh_addr_len == 32),
|
|
ByteField("reserved", 0),
|
|
MPReachNLRIPacketListField("nlri", [], Packet)]
|
|
|
|
def post_build(self, p, pay):
|
|
if self.nlri is None:
|
|
p = p[:3]
|
|
|
|
return p + pay
|
|
|
|
|
|
#
|
|
# MP_UNREACH_NLRI
|
|
#
|
|
|
|
class BGPPAMPUnreachNLRI_IPv6(Packet):
|
|
"""
|
|
Packet handling the MP_UNREACH_NLRI attribute value, for IPv6 AFI.
|
|
"""
|
|
|
|
name = "MP_UNREACH_NLRI (IPv6 NLRI)"
|
|
fields_desc = [BGPNLRIPacketListField(
|
|
"withdrawn_routes", [], BGPNLRI_IPv6)]
|
|
|
|
|
|
class MPUnreachNLRIPacketField(PacketField):
|
|
"""
|
|
PacketField handling the AFI specific part of the MP_UNREACH_NLRI
|
|
attribute.
|
|
"""
|
|
|
|
def m2i(self, pkt, m):
|
|
ret = None
|
|
|
|
if pkt.afi == 2:
|
|
ret = BGPPAMPUnreachNLRI_IPv6(m)
|
|
else:
|
|
ret = conf.raw_layer(m)
|
|
|
|
return ret
|
|
|
|
|
|
class BGPPAMPUnreachNLRI(Packet):
|
|
"""
|
|
Packet handling the MP_UNREACH_NLRI attribute value, for non IPv6
|
|
AFI.
|
|
References: RFC 4760
|
|
"""
|
|
|
|
name = "MP_UNREACH_NLRI"
|
|
fields_desc = [ShortEnumField("afi", 0, address_family_identifiers),
|
|
ByteEnumField("safi", 0, subsequent_afis),
|
|
MPUnreachNLRIPacketField("afi_safi_specific", None, Packet)]
|
|
|
|
def post_build(self, p, pay):
|
|
if self.afi_safi_specific is None:
|
|
p = p[:3]
|
|
|
|
return p + pay
|
|
|
|
|
|
#
|
|
# AS4_PATH
|
|
#
|
|
|
|
class BGPPAAS4Path(Packet):
|
|
"""
|
|
Provides an implementation of the AS4_PATH attribute "value part".
|
|
References: RFC 4893
|
|
"""
|
|
|
|
name = "AS4_PATH"
|
|
fields_desc = [
|
|
ByteEnumField(
|
|
"segment_type",
|
|
2,
|
|
{1: "AS_SET", 2: "AS_SEQUENCE"}
|
|
),
|
|
ByteField("segment_length", None),
|
|
FieldListField("segment_value", [], IntField("asn", 0))
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
if self.segment_length is None:
|
|
segment_len = len(self.segment_value)
|
|
p = p[:1] + chb(segment_len) + p[2:]
|
|
|
|
return p + pay
|
|
|
|
|
|
#
|
|
# AS4_AGGREGATOR
|
|
#
|
|
|
|
class BGPPAAS4Aggregator(Packet):
|
|
"""
|
|
Provides an implementation of the AS4_AGGREGATOR attribute
|
|
"value part".
|
|
References: RFC 4893
|
|
"""
|
|
|
|
name = "AS4_AGGREGATOR "
|
|
fields_desc = [IntField("aggregator_asn", 0),
|
|
IPField("speaker_address", "0.0.0.0")]
|
|
|
|
|
|
_path_attr_objects = {
|
|
0x01: "BGPPAOrigin",
|
|
0x02: "BGPPAASPath", # if bgp_module_conf.use_2_bytes_asn, BGPPAAS4BytesPath otherwise # noqa: E501
|
|
0x03: "BGPPANextHop",
|
|
0x04: "BGPPAMultiExitDisc",
|
|
0x05: "BGPPALocalPref",
|
|
0x06: "BGPPAAtomicAggregate",
|
|
0x07: "BGPPAAggregator",
|
|
0x08: "BGPPACommunity",
|
|
0x09: "BGPPAOriginatorID",
|
|
0x0A: "BGPPAClusterList",
|
|
0x0E: "BGPPAMPReachNLRI",
|
|
0x0F: "BGPPAMPUnreachNLRI",
|
|
0x10: "BGPPAExtComms",
|
|
0x11: "BGPPAAS4Path",
|
|
0x19: "BGPPAIPv6AddressSpecificExtComm"
|
|
}
|
|
|
|
|
|
class _PathAttrPacketField(PacketField):
|
|
"""
|
|
PacketField handling path attribute value parts.
|
|
"""
|
|
|
|
def m2i(self, pkt, m):
|
|
ret = None
|
|
type_code = pkt.type_code
|
|
|
|
# Reserved
|
|
if type_code == 0 or type_code == 255:
|
|
ret = conf.raw_layer(m)
|
|
# Unassigned
|
|
elif (type_code >= 30 and type_code <= 39) or\
|
|
(type_code >= 41 and type_code <= 127) or\
|
|
(type_code >= 129 and type_code <= 254):
|
|
ret = conf.raw_layer(m)
|
|
# Known path attributes
|
|
else:
|
|
if type_code == 0x02 and not bgp_module_conf.use_2_bytes_asn:
|
|
ret = BGPPAAS4BytesPath(m)
|
|
else:
|
|
ret = _get_cls(
|
|
_path_attr_objects.get(type_code, conf.raw_layer))(m)
|
|
|
|
return ret
|
|
|
|
|
|
class BGPPathAttr(Packet):
|
|
"""
|
|
Provides an implementation of the path attributes.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "BGPPathAttr"
|
|
fields_desc = [
|
|
FlagsField("type_flags", 0x80, 8, [
|
|
"NA0",
|
|
"NA1",
|
|
"NA2",
|
|
"NA3",
|
|
"Extended-Length",
|
|
"Partial",
|
|
"Transitive",
|
|
"Optional"
|
|
]),
|
|
ByteEnumField("type_code", 0, path_attributes),
|
|
ConditionalField(
|
|
ShortField("attr_ext_len", None),
|
|
lambda x: x.type_flags is not None and
|
|
has_extended_length(x.type_flags)
|
|
),
|
|
ConditionalField(
|
|
ByteField("attr_len", None),
|
|
lambda x: x.type_flags is not None and not
|
|
has_extended_length(x.type_flags)
|
|
),
|
|
_PathAttrPacketField("attribute", None, Packet)
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
flags_value = None
|
|
length = None
|
|
packet = None
|
|
extended_length = False
|
|
|
|
# Set default flags value ?
|
|
if self.type_flags is None:
|
|
# Set the standard value, if it is exists in attributes_flags.
|
|
if self.type_code in attributes_flags:
|
|
flags_value = attributes_flags.get(self.type_code)
|
|
|
|
# Otherwise, set to optional, non-transitive.
|
|
else:
|
|
flags_value = 0x80
|
|
|
|
extended_length = has_extended_length(flags_value)
|
|
else:
|
|
extended_length = has_extended_length(self.type_flags)
|
|
|
|
# Set the flags
|
|
if flags_value is None:
|
|
packet = p[:2]
|
|
else:
|
|
packet = struct.pack("!B", flags_value) + p[1]
|
|
|
|
# Add the length
|
|
if self.attr_len is None:
|
|
if self.attribute is None:
|
|
length = 0
|
|
else:
|
|
if extended_length:
|
|
length = len(p) - 4 # Flags + Type + Length (2 bytes)
|
|
else:
|
|
length = len(p) - 3 # Flags + Type + Length (1 byte)
|
|
|
|
if length is None:
|
|
if extended_length:
|
|
packet = packet + p[2:4]
|
|
else:
|
|
packet = packet + p[2]
|
|
else:
|
|
if extended_length:
|
|
packet = packet + struct.pack("!H", length)
|
|
else:
|
|
packet = packet + struct.pack("!B", length)
|
|
|
|
# Append the rest of the message
|
|
if extended_length:
|
|
if self.attribute is not None:
|
|
packet = packet + p[4:]
|
|
else:
|
|
if self.attribute is not None:
|
|
packet = packet + p[3:]
|
|
|
|
return packet + pay
|
|
|
|
|
|
#
|
|
# UPDATE
|
|
#
|
|
|
|
class BGPUpdate(BGP):
|
|
"""
|
|
UPDATE messages allow peers to exchange routes.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "UPDATE"
|
|
fields_desc = [
|
|
FieldLenField(
|
|
"withdrawn_routes_len",
|
|
None,
|
|
length_of="withdrawn_routes",
|
|
fmt="!H"
|
|
),
|
|
BGPNLRIPacketListField(
|
|
"withdrawn_routes",
|
|
[],
|
|
BGPNLRI_IPv4,
|
|
length_from=lambda p: p.withdrawn_routes_len
|
|
),
|
|
FieldLenField(
|
|
"path_attr_len",
|
|
None,
|
|
length_of="path_attr",
|
|
fmt="!H"
|
|
),
|
|
BGPPathAttrPacketListField(
|
|
"path_attr",
|
|
[],
|
|
BGPPathAttr,
|
|
length_from=lambda p: p.path_attr_len
|
|
),
|
|
BGPNLRIPacketListField("nlri", [], BGPNLRI_IPv4)
|
|
]
|
|
|
|
def post_build(self, p, pay):
|
|
subpacklen = lambda p: len(p)
|
|
packet = ""
|
|
if self.withdrawn_routes_len is None:
|
|
wl = sum(map(subpacklen, self.withdrawn_routes))
|
|
packet = p[:0] + struct.pack("!H", wl) + p[2:]
|
|
else:
|
|
wl = self.withdrawn_routes_len
|
|
if self.path_attr_len is None:
|
|
length = sum(map(subpacklen, self.path_attr))
|
|
packet = p[:2 + wl] + struct.pack("!H", length) + p[4 + wl:]
|
|
|
|
return packet + pay
|
|
|
|
|
|
#
|
|
# NOTIFICATION
|
|
#
|
|
|
|
#
|
|
# RFC 4271, RFC 7313
|
|
# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-3
|
|
#
|
|
_error_codes = {
|
|
0x01: "Message Header Error",
|
|
0x02: "OPEN Message Error",
|
|
0x03: "UPDATE Message Error",
|
|
0x04: "Hold Timer Expired",
|
|
0x05: "Finite State Machine Error",
|
|
0x06: "Cease",
|
|
0x07: "ROUTE-REFRESH Message Error", # RFC 7313
|
|
}
|
|
|
|
#
|
|
# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-4
|
|
#
|
|
_error_subcodes = {
|
|
# Reserved
|
|
0: {},
|
|
|
|
# Header (RFC 4271)
|
|
1:
|
|
{
|
|
0: "Unspecific",
|
|
1: "Connection Not Synchronized",
|
|
2: "Bad Message Length",
|
|
3: "Bad Message Type"
|
|
},
|
|
|
|
# OPEN (RFC 4271, RFC 5492)
|
|
2:
|
|
{
|
|
0: "Reserved",
|
|
1: "Unsupported Version Number",
|
|
2: "Bad Peer AS",
|
|
3: "Bad BGP Identifier",
|
|
4: "Unsupported Optional Parameter",
|
|
5: "Authentication Failure - Deprecated (RFC 4271)",
|
|
6: "Unacceptable Hold Time",
|
|
7: "Unsupported Capability"
|
|
},
|
|
|
|
# UPDATE (RFC 4271)
|
|
3:
|
|
{
|
|
0: "Reserved",
|
|
1: "Malformed Attribute List",
|
|
2: "Unrecognized Well-known Attribute",
|
|
3: "Missing Well-known Attribute",
|
|
4: "Attribute Flags Error",
|
|
5: "Attribute Length Error",
|
|
6: "Invalid ORIGIN Attribute",
|
|
7: "AS Routing Loop - Deprecated (RFC 4271)",
|
|
8: "Invalid NEXT_HOP Attribute",
|
|
9: "Optional Attribute Error",
|
|
10: "Invalid Network Field",
|
|
11: "Malformed AS_PATH"
|
|
},
|
|
|
|
# Hold Timer Expired
|
|
4: {},
|
|
|
|
# Finite State Machine Error (RFC 6608)
|
|
5:
|
|
{
|
|
0: "Unspecified Error",
|
|
1: "Receive Unexpected Message in OpenSent State",
|
|
2: "Receive Unexpected Message in OpenConfirm State",
|
|
3: "Receive Unexpected Message in Established State"
|
|
},
|
|
|
|
# Cease (RFC 4486)
|
|
6:
|
|
{
|
|
0: "Unspecified Error",
|
|
1: "Maximum Number of Prefixes Reached",
|
|
2: "Administrative Shutdown",
|
|
3: "Peer De-configured",
|
|
4: "Administrative Reset",
|
|
5: "Connection Rejected",
|
|
6: "Other Configuration Change",
|
|
7: "Connection Collision Resolution",
|
|
8: "Out of Resources",
|
|
},
|
|
|
|
# ROUTE-REFRESH (RFC 7313)
|
|
7:
|
|
{
|
|
0: "Reserved",
|
|
1: "Invalid Message Length"
|
|
},
|
|
}
|
|
|
|
|
|
class BGPNotification(BGP):
|
|
"""
|
|
NOTIFICATION messages end a BGP session.
|
|
References: RFC 4271
|
|
"""
|
|
|
|
name = "NOTIFICATION"
|
|
fields_desc = [
|
|
ByteEnumField("error_code", 0, _error_codes),
|
|
MultiEnumField(
|
|
"error_subcode",
|
|
0,
|
|
_error_subcodes,
|
|
depends_on=lambda p: p.error_code,
|
|
fmt="B"
|
|
),
|
|
StrField(name="data", default=None)
|
|
]
|
|
|
|
|
|
#
|
|
# ROUTE_REFRESH
|
|
#
|
|
|
|
_orf_when_to_refresh = {
|
|
0x01: "IMMEDIATE",
|
|
0x02: "DEFER"
|
|
}
|
|
|
|
|
|
_orf_actions = {
|
|
0: "ADD",
|
|
1: "REMOVE",
|
|
2: "REMOVE-ALL"
|
|
}
|
|
|
|
|
|
_orf_match = {
|
|
0: "PERMIT",
|
|
1: "DENY"
|
|
}
|
|
|
|
|
|
_orf_entry_afi = 1
|
|
_orf_entry_safi = 1
|
|
|
|
|
|
def _update_orf_afi_safi(afi, safi):
|
|
"""
|
|
Helper function that sets the afi / safi values
|
|
of ORP entries.
|
|
"""
|
|
|
|
global _orf_entry_afi
|
|
global _orf_entry_safi
|
|
|
|
_orf_entry_afi = afi
|
|
_orf_entry_safi = safi
|
|
|
|
|
|
class BGPORFEntry(Packet):
|
|
"""
|
|
Provides an implementation of an ORF entry.
|
|
References: RFC 5291
|
|
"""
|
|
|
|
name = "ORF entry"
|
|
fields_desc = [
|
|
BitEnumField("action", 0, 2, _orf_actions),
|
|
BitEnumField("match", 0, 1, _orf_match),
|
|
BitField("reserved", 0, 5),
|
|
StrField("value", "")
|
|
]
|
|
|
|
|
|
class _ORFNLRIPacketField(PacketField):
|
|
"""
|
|
PacketField handling the ORF NLRI.
|
|
"""
|
|
|
|
def m2i(self, pkt, m):
|
|
ret = None
|
|
|
|
if _orf_entry_afi == 1:
|
|
# IPv4
|
|
ret = BGPNLRI_IPv4(m)
|
|
|
|
elif _orf_entry_afi == 2:
|
|
# IPv6
|
|
ret = BGPNLRI_IPv6(m)
|
|
|
|
else:
|
|
ret = conf.raw_layer(m)
|
|
|
|
return ret
|
|
|
|
|
|
class BGPORFAddressPrefix(BGPORFEntry):
|
|
"""
|
|
Provides an implementation of the Address Prefix ORF (RFC 5292).
|
|
"""
|
|
|
|
name = "Address Prefix ORF"
|
|
fields_desc = [
|
|
BitEnumField("action", 0, 2, _orf_actions),
|
|
BitEnumField("match", 0, 1, _orf_match),
|
|
BitField("reserved", 0, 5),
|
|
IntField("sequence", 0),
|
|
ByteField("min_len", 0),
|
|
ByteField("max_len", 0),
|
|
_ORFNLRIPacketField("prefix", "", Packet),
|
|
]
|
|
|
|
|
|
class BGPORFCoveringPrefix(Packet):
|
|
"""
|
|
Provides an implementation of the CP-ORF (RFC 7543).
|
|
"""
|
|
|
|
name = "CP-ORF"
|
|
fields_desc = [
|
|
BitEnumField("action", 0, 2, _orf_actions),
|
|
BitEnumField("match", 0, 1, _orf_match),
|
|
BitField("reserved", 0, 5),
|
|
IntField("sequence", 0),
|
|
ByteField("min_len", 0),
|
|
ByteField("max_len", 0),
|
|
LongField("rt", 0),
|
|
LongField("import_rt", 0),
|
|
ByteField("route_type", 0),
|
|
PacketField("host_addr", None, Packet)
|
|
]
|
|
|
|
|
|
class BGPORFEntryPacketListField(PacketListField):
|
|
"""
|
|
PacketListField handling the ORF entries.
|
|
"""
|
|
|
|
def m2i(self, pkt, m):
|
|
ret = None
|
|
|
|
# Cisco also uses 128
|
|
if pkt.orf_type == 64 or pkt.orf_type == 128:
|
|
ret = BGPORFAddressPrefix(m)
|
|
|
|
elif pkt.orf_type == 65:
|
|
ret = BGPORFCoveringPrefix(m)
|
|
|
|
else:
|
|
ret = conf.raw_layer(m)
|
|
|
|
return ret
|
|
|
|
def getfield(self, pkt, s):
|
|
lst = []
|
|
length = 0
|
|
ret = b""
|
|
if self.length_from is not None:
|
|
length = self.length_from(pkt)
|
|
remain = s
|
|
if length is not None:
|
|
remain, ret = s[:length], s[length:]
|
|
|
|
while remain:
|
|
orf_len = 0
|
|
|
|
# Get value length, depending on the ORF type
|
|
if pkt.orf_type == 64 or pkt.orf_type == 128:
|
|
# Address Prefix ORF
|
|
# Get the length, in bits, of the prefix
|
|
prefix_len = _bits_to_bytes_len(
|
|
orb(remain[6])
|
|
)
|
|
# flags (1 byte) + sequence (4 bytes) + min_len (1 byte) +
|
|
# max_len (1 byte) + mask_len (1 byte) + prefix_len
|
|
orf_len = 8 + prefix_len
|
|
|
|
elif pkt.orf_type == 65:
|
|
# Covering Prefix ORF
|
|
|
|
if _orf_entry_afi == 1:
|
|
# IPv4
|
|
# sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501
|
|
# rt (8 bytes) + import_rt (8 bytes) + route_type (1 byte)
|
|
orf_len = 23 + 4
|
|
|
|
elif _orf_entry_afi == 2:
|
|
# IPv6
|
|
# sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501
|
|
# rt (8 bytes) + import_rt (8 bytes) + route_type (1 byte)
|
|
orf_len = 23 + 16
|
|
|
|
elif _orf_entry_afi == 25:
|
|
# sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501
|
|
# rt (8 bytes) + import_rt (8 bytes)
|
|
route_type = orb(remain[22])
|
|
|
|
if route_type == 2:
|
|
# MAC / IP Advertisement Route
|
|
orf_len = 23 + 6
|
|
|
|
else:
|
|
orf_len = 23
|
|
|
|
current = remain[:orf_len]
|
|
remain = remain[orf_len:]
|
|
packet = self.m2i(pkt, current)
|
|
lst.append(packet)
|
|
|
|
return remain + ret, lst
|
|
|
|
|
|
class BGPORF(Packet):
|
|
"""
|
|
Provides an implementation of ORFs carried in the RR message.
|
|
References: RFC 5291
|
|
"""
|
|
|
|
name = "ORF"
|
|
fields_desc = [
|
|
ByteEnumField("when_to_refresh", 0, _orf_when_to_refresh),
|
|
ByteEnumField("orf_type", 0, _orf_types),
|
|
FieldLenField("orf_len", None, length_of="entries", fmt="!H"),
|
|
BGPORFEntryPacketListField(
|
|
"entries",
|
|
[],
|
|
Packet,
|
|
length_from=lambda p: p.orf_len,
|
|
)
|
|
]
|
|
|
|
|
|
# RFC 7313
|
|
# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#route-refresh-subcodes
|
|
rr_message_subtypes = {
|
|
0: "Route-Refresh",
|
|
1: "BoRR",
|
|
2: "EoRR",
|
|
255: "Reserved"
|
|
}
|
|
|
|
|
|
class BGPRouteRefresh(BGP):
|
|
"""
|
|
Provides an implementation of the ROUTE-REFRESH message.
|
|
References: RFC 2918, RFC 7313
|
|
"""
|
|
|
|
name = "ROUTE-REFRESH"
|
|
fields_desc = [
|
|
ShortEnumField("afi", 1, address_family_identifiers),
|
|
ByteEnumField("subtype", 0, rr_message_subtypes),
|
|
ByteEnumField("safi", 1, subsequent_afis),
|
|
PacketField(
|
|
'orf_data',
|
|
"", BGPORF,
|
|
lambda p: _update_orf_afi_safi(p.afi, p.safi)
|
|
)
|
|
]
|
|
|
|
|
|
#
|
|
# Layer bindings
|
|
#
|
|
|
|
bind_layers(TCP, BGP, dport=179)
|
|
bind_layers(TCP, BGP, sport=179)
|
|
bind_layers(BGPHeader, BGPOpen, {"type": 1})
|
|
bind_layers(BGPHeader, BGPUpdate, {"type": 2})
|
|
bind_layers(BGPHeader, BGPNotification, {"type": 3})
|
|
bind_layers(BGPHeader, BGPKeepAlive, {"type": 4})
|
|
bind_layers(BGPHeader, BGPRouteRefresh, {"type": 5})
|
|
|
|
# When loading the module, display the current module configuration.
|
|
log_runtime.warning(
|
|
"[bgp.py] use_2_bytes_asn: %s", bgp_module_conf.use_2_bytes_asn)
|