OXIESEC PANEL
- Current Dir:
/
/
opt
/
alt
/
python37
/
lib
/
python3.7
/
site-packages
/
exabgp
/
bgp
/
message
/
update
/
attribute
Server IP: 2a02:4780:11:1084:0:327f:3464:10
Upload:
Create Dir:
Name
Size
Modified
Perms
📁
..
-
03/16/2023 12:55:54 PM
rwxr-xr-x
📄
__init__.py
2.11 KB
03/13/2021 04:30:48 PM
rw-r--r--
📁
__pycache__
-
03/16/2023 12:55:54 PM
rwxr-xr-x
📄
aggregator.py
2.29 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
aigp.py
2.96 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
aspath.py
7.64 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
atomicaggregate.py
1.24 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
attribute.py
9.04 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
attributes.py
17.79 KB
03/13/2021 04:30:48 PM
rw-r--r--
📁
bgpls
-
03/16/2023 12:55:54 PM
rwxr-xr-x
📄
clusterlist.py
1.65 KB
03/13/2021 04:30:48 PM
rw-r--r--
📁
community
-
03/16/2023 12:55:54 PM
rwxr-xr-x
📄
generic.py
1.67 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
localpref.py
1.21 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
med.py
1.18 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
mprnlri.py
7.88 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
mpurnlri.py
3.58 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
nexthop.py
1.84 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
origin.py
1.78 KB
03/13/2021 04:30:48 PM
rw-r--r--
📄
originatorid.py
890 bytes
03/13/2021 04:30:48 PM
rw-r--r--
📄
pmsi.py
4.97 KB
03/13/2021 04:30:48 PM
rw-r--r--
📁
sr
-
03/16/2023 12:55:54 PM
rwxr-xr-x
Editing: attributes.py
Close
# encoding: utf-8 """ attributes.py Created by Thomas Mangin on 2009-11-05. Copyright (c) 2009-2017 Exa Networks. All rights reserved. License: 3-clause BSD. (See the COPYRIGHT file) """ from struct import unpack from exabgp.configuration.environment import environment from exabgp.util import ordinal from exabgp.bgp.message.update.attribute.attribute import Attribute from exabgp.bgp.message.update.attribute.attribute import TreatAsWithdraw from exabgp.bgp.message.update.attribute.attribute import Discard from exabgp.bgp.message.update.attribute.generic import GenericAttribute from exabgp.bgp.message.update.attribute.origin import Origin from exabgp.bgp.message.update.attribute.aspath import ASPath from exabgp.bgp.message.update.attribute.localpref import LocalPreference # For bagpipe from exabgp.bgp.message.update.attribute.community import Communities from exabgp.bgp.message.notification import Notify from exabgp.util import ordinal from exabgp.logger import Logger from exabgp.logger import LazyAttribute from exabgp.vendoring import six class _NOTHING(object): def pack(self, _=None): return b'' NOTHING = _NOTHING() # =================================================================== Attributes # # 0 1 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Attr. Flags |Attr. Type Code| # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ class Attributes(dict): INTERNAL = ( Attribute.CODE.INTERNAL_SPLIT, Attribute.CODE.INTERNAL_WATCHDOG, Attribute.CODE.INTERNAL_NAME, Attribute.CODE.INTERNAL_WITHDRAW, # Attribute.CODE.INTERNAL_DISCARD, # Attribute.CODE.INTERNAL_TREAT_AS_WITHDRAW, ) NO_GENERATION = (Attribute.CODE.NEXT_HOP,) + INTERNAL TREAT_AS_WITHDRAW = ( Attribute.CODE.ORIGIN, Attribute.CODE.AS_PATH, Attribute.CODE.NEXT_HOP, Attribute.CODE.MED, Attribute.CODE.LOCAL_PREF, Attribute.CODE.LARGE_COMMUNITY, ) DISCARD = ( Attribute.CODE.ATOMIC_AGGREGATE, Attribute.CODE.AGGREGATOR, ) MANDATORY = (Attribute.CODE.ORIGIN, Attribute.CODE.AS_PATH, Attribute.CODE.LOCAL_PREF) NO_DUPLICATE = ( Attribute.CODE.MP_REACH_NLRI, Attribute.CODE.MP_UNREACH_NLRI, ) VALID_ZERO = ( Attribute.CODE.ATOMIC_AGGREGATE, Attribute.CODE.AS_PATH, ) # A cache of parsed attributes cache = {} # The previously parsed Attributes cached = None # previously parsed attribute, from which cached was made of previous = '' representation = { # key: (how, default, name, text_presentation, json_presentation), Attribute.CODE.ORIGIN: ('string', '', 'origin', '%s', '%s'), Attribute.CODE.AS_PATH: ( 'multiple', '', ('as-path', 'as-set', 'confederation-path', 'confederation-set'), '%s', '%s', ), Attribute.CODE.NEXT_HOP: ('string', '', 'next-hop', '%s', '%s'), Attribute.CODE.MED: ('integer', '', 'med', '%s', '%s'), Attribute.CODE.LOCAL_PREF: ('integer', '', 'local-preference', '%s', '%s'), Attribute.CODE.ATOMIC_AGGREGATE: ('boolean', '', 'atomic-aggregate', '%s', '%s'), Attribute.CODE.AGGREGATOR: ('string', '', 'aggregator', '( %s )', '%s'), Attribute.CODE.AS4_AGGREGATOR: ('string', '', 'aggregator', '( %s )', '%s'), Attribute.CODE.COMMUNITY: ('list', '', 'community', '%s', '%s'), Attribute.CODE.LARGE_COMMUNITY: ('list', '', 'large-community', '%s', '%s'), Attribute.CODE.ORIGINATOR_ID: ('inet', '', 'originator-id', '%s', '%s'), Attribute.CODE.CLUSTER_LIST: ('list', '', 'cluster-list', '%s', '%s'), Attribute.CODE.EXTENDED_COMMUNITY: ('list', '', 'extended-community', '%s', '%s'), Attribute.CODE.PMSI_TUNNEL: ('string', '', 'pmsi', '%s', '%s'), Attribute.CODE.AIGP: ('integer', '', 'aigp', '%s', '%s'), Attribute.CODE.BGP_LS: ('list', '', 'bgp-ls', '%s', '%s'), Attribute.CODE.BGP_PREFIX_SID: ('list', '', 'bgp-prefix-sid', '%s', '%s'), Attribute.CODE.INTERNAL_NAME: ('string', '', 'name', '%s', '%s'), Attribute.CODE.INTERNAL_DISCARD: ('string', '', 'error', '%s', '%s'), Attribute.CODE.INTERNAL_TREAT_AS_WITHDRAW: ('string', '', 'error', '%s', '%s'), } def _generate_text(self): for code in sorted(self.keys()): # XXX: FIXME: really we should have a INTERNAL attribute in the classes if code in Attributes.NO_GENERATION: continue attribute = self[code] if code not in self.representation: yield ' attribute [ 0x%02X 0x%02X %s ]' % (code, attribute.FLAG, str(attribute)) continue if attribute.GENERIC: yield ' attribute [ 0x%02X 0x%02X %s ]' % (code, attribute.FLAG, str(attribute)) continue how, _, name, presentation, _ = self.representation[code] if how == 'boolean': yield ' %s' % name elif how == 'list': yield ' %s %s' % (name, presentation % str(attribute)) elif how == 'multiple': yield ' %s %s' % (name[0], presentation % str(attribute)) else: yield ' %s %s' % (name, presentation % str(attribute)) def _generate_json(self): for code in sorted(self.keys()): # remove the next-hop from the attribute as it is define with the NLRI if code in Attributes.NO_GENERATION: continue attribute = self[code] if code not in self.representation: yield '"attribute-0x%02X-0x%02X": "%s"' % (code, attribute.FLAG, str(attribute)) continue how, _, name, _, presentation = self.representation[code] if how == 'boolean': yield '"%s": %s' % (name, 'true' if self.has(code) else 'false') elif how == 'string': yield '"%s": "%s"' % (name, presentation % str(attribute)) elif how == 'list': yield '"%s": %s' % (name, presentation % attribute.json()) elif how == 'multiple': for n in name: value = attribute.json(n) if value: yield '"%s": %s' % (n, presentation % value) elif how == 'inet': yield '"%s": "%s"' % (name, presentation % str(attribute)) # Should never be ran else: yield '"%s": %s' % (name, presentation % str(attribute)) def __init__(self): dict.__init__(self) # cached representation of the object self._str = '' self._idx = '' self._json = '' # The parsed attributes have no mp routes and/or those are last self.cacheable = True # XXX: FIXME: surely not the best place for this Attribute.caching = environment.settings().cache.attributes def has(self, k): return k in self def add(self, attribute, _=None): # we return None as attribute if the unpack code must not generate them if attribute is None: return if attribute.ID in self: return self._str = '' self._json = '' self[attribute.ID] = attribute # This is as when we generate flow spec we can have multiple keywords # which are all adding information in the extended-community def add_and_merge(self, attribute): if attribute.ID not in self: self.add(attribute) return if attribute.ID == Attribute.CODE.EXTENDED_COMMUNITY: for community in attribute.communities: self[attribute.ID].add(community) def remove(self, attrid): self.pop(attrid) def watchdog(self): return self.pop(Attribute.CODE.INTERNAL_WATCHDOG, None) def withdraw(self): return self.pop(Attribute.CODE.INTERNAL_WITHDRAW, None) is not None def pack(self, negotiated, with_default=True): local_asn = negotiated.local_as peer_asn = negotiated.peer_as message = b'' default = { Attribute.CODE.ORIGIN: lambda left, right: Origin(Origin.IGP), Attribute.CODE.AS_PATH: lambda left, right: ASPath([], []) if left == right else ASPath([local_asn,], []), Attribute.CODE.LOCAL_PREF: lambda left, right: LocalPreference(100) if left == right else NOTHING, } skip = { Attribute.CODE.NEXT_HOP: lambda left, right, nh: nh.ipv4() is not True, Attribute.CODE.LOCAL_PREF: lambda left, right, nh: left != right, } keys = list(self) alls = set(keys + list(default) if with_default else []) for code in sorted(alls): if code in Attributes.INTERNAL: continue if code not in keys and code in default: message += default[code](local_asn, peer_asn).pack(negotiated) continue attribute = self[code] if code in skip and skip[code](local_asn, peer_asn, attribute): continue message += attribute.pack(negotiated) return message def json(self): if not self._json: self._json = ', '.join(self._generate_json()) return self._json def __repr__(self): if not self._str: self._str = ''.join(self._generate_text()) return self._str def index(self): # XXX: something a little bit smaller memory wise ? if not self._idx: idx = ''.join(self._generate_text()) nexthop = str(self.get(Attribute.CODE.NEXT_HOP, '')) self._idx = '%s next-hop %s' % (idx, nexthop) if nexthop else idx return self._idx @classmethod def unpack(cls, data, negotiated): if cls.cached and data == cls.previous: return cls.cached attributes = cls().parse(data, negotiated) if Attribute.CODE.INTERNAL_TREAT_AS_WITHDRAW in attributes: return attributes if Attribute.CODE.AS_PATH in attributes and Attribute.CODE.AS4_PATH in attributes: attributes.merge_attributes() if Attribute.CODE.MP_REACH_NLRI not in attributes and Attribute.CODE.MP_UNREACH_NLRI not in attributes: cls.previous = data cls.cached = attributes else: cls.previous = '' cls.cached = None return attributes @staticmethod def flag_attribute_content(data): flag = Attribute.Flag(ordinal(data[0])) attr = Attribute.CODE(ordinal(data[1])) if flag & Attribute.Flag.EXTENDED_LENGTH: length = unpack('!H', data[2:4])[0] return flag, attr, data[4 : length + 4] else: length = ordinal(data[2]) return flag, attr, data[3 : length + 3] def parse(self, data, negotiated): if not data: return self try: # We do not care if the attribute are transitive or not as we do not redistribute flag = Attribute.Flag(ordinal(data[0])) aid = Attribute.CODE(ordinal(data[1])) except IndexError: self.add(TreatAsWithdraw()) return self try: offset = 3 length = ordinal(data[2]) if flag & Attribute.Flag.EXTENDED_LENGTH: offset = 4 length = (length << 8) + ordinal(data[3]) except IndexError: self.add(TreatAsWithdraw(aid)) return self data = data[offset:] left = data[length:] attribute = data[:length] logger = Logger() logger.debug(LazyAttribute(flag, aid, length, data[:length]), 'parser') # remove the PARTIAL bit before comparaison if the attribute is optional if aid in Attribute.attributes_optional: flag &= Attribute.Flag.MASK_PARTIAL & 0xFF # flag &= ~Attribute.Flag.PARTIAL & 0xFF # cleaner than above (python use signed integer for ~) if aid in self: if aid in self.NO_DUPLICATE: raise Notify(3, 1, 'multiple attribute for %s' % str(Attribute.CODE(attribute.ID))) logger.debug( 'duplicate attribute %s (flag 0x%02X, aid 0x%02X) skipping' % (Attribute.CODE.names.get(aid, 'unset'), flag, aid), 'parser', ) return self.parse(left, negotiated) # handle the attribute if we know it if Attribute.registered(aid, flag): if length == 0 and aid not in self.VALID_ZERO: self.add(TreatAsWithdraw(aid)) return self.parse(left, negotiated) try: decoded = Attribute.unpack(aid, flag, attribute, negotiated) except IndexError as exc: if aid in self.TREAT_AS_WITHDRAW: decoded = TreatAsWithdraw(aid) else: raise exc except Notify as exc: if aid in self.TREAT_AS_WITHDRAW: decoded = TreatAsWithdraw() elif aid in self.DISCARD: decoded = Discard() else: raise exc self.add(decoded) return self.parse(left, negotiated) # XXX: FIXME: we could use a fallback function here like capability # if we know the attribute but the flag is not what the RFC says. if aid in Attribute.attributes_known: if aid in self.TREAT_AS_WITHDRAW: logger.debug( 'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X) treat as withdraw' % (Attribute.CODE.names.get(aid, 'unset'), flag, aid), 'parser', ) self.add(TreatAsWithdraw()) if aid in self.DISCARD: logger.debug( 'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X) discard' % (Attribute.CODE.names.get(aid, 'unset'), flag, aid), 'parser', ) return self.parse(left, negotiated) # XXX: Check if we are missing any logger.debug( 'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X) unspecified (should not happen)' % (Attribute.CODE.names.get(aid, 'unset'), flag, aid), 'parser', ) return self.parse(left, negotiated) # it is an unknown transitive attribute we need to pass on if flag & Attribute.Flag.TRANSITIVE: logger.debug('unknown transitive attribute (flag 0x%02X, aid 0x%02X)' % (flag, aid), 'parser') try: decoded = GenericAttribute(aid, flag | Attribute.Flag.PARTIAL, attribute) except IndexError: decoded = TreatAsWithdraw(aid) self.add(decoded, attribute) return self.parse(left, negotiated) # it is an unknown non-transitive attribute we can ignore. logger.debug('ignoring unknown non-transitive attribute (flag 0x%02X, aid 0x%02X)' % (flag, aid), 'parser') return self.parse(left, negotiated) def merge_attributes(self): as2path = self[Attribute.CODE.AS_PATH] as4path = self[Attribute.CODE.AS4_PATH] self.remove(Attribute.CODE.AS_PATH) self.remove(Attribute.CODE.AS4_PATH) # this key is unique as index length is a two header, plus a number of ASN of size 2 or 4 # so adding the: make the length odd and unique key = "%s:%s" % (as2path.index, as4path.index) # found a cache copy cached = Attribute.cache.get(Attribute.CODE.AS_PATH, {}).get(key, None) if cached: self.add(cached, key) return # as_seq = [] # as_set = [] len2 = len(as2path.as_seq) len4 = len(as4path.as_seq) # RFC 4893 section 4.2.3 if len2 < len4: as_seq = as2path.as_seq else: as_seq = as2path.as_seq[:-len4] as_seq.extend(as4path.as_seq) len2 = len(as2path.as_set) len4 = len(as4path.as_set) if len2 < len4: as_set = as4path.as_set else: as_set = as2path.as_set[:-len4] as_set.extend(as4path.as_set) aspath = ASPath(as_seq, as_set) self.add(aspath, key) def __hash__(self): # FIXME: two routes with distinct nh but other attributes equal # will hash to the same value until repr represents the nh (??) return hash(repr(self)) def __eq__(self, other): return self.sameValuesAs(other) # BaGPipe code .. # test that sets of attributes exactly match # can't rely on __eq__ for this, because __eq__ relies on Attribute.__eq__ which does not look at attributes values def sameValuesAs(self, other): # we sort based on packed values since the items do not # necessarily implement __cmp__ def pack_(x): return x.pack() try: for key in set(six.iterkeys(self)).union(set(six.iterkeys(other))): if key == Attribute.CODE.MP_REACH_NLRI or key == Attribute.CODE.MP_UNREACH_NLRI: continue sval = self[key] oval = other[key] # In the case where the attribute is Communities or # extended communities, we want to compare values independently of their order if isinstance(sval, Communities): if not isinstance(oval, Communities): return False sval = sorted(sval, key=pack_) oval = sorted(oval, key=pack_) if sval != oval: return False return True except KeyError: return False