5
Created by Thomas Mangin on 2010-01-16.
6
Copyright (c) 2009-2013 Exa Networks. All rights reserved.
9
from struct import unpack,error
11
from exabgp.structure.utils import dump
12
from exabgp.structure.environment import environment
13
from exabgp.structure.cache import Cache
15
from exabgp.protocol.family import AFI,SAFI
17
from exabgp.bgp.message.open.asn import ASN,AS_TRANS
18
from exabgp.bgp.message.notification import Notify
19
from exabgp.bgp.message.update.nlri.eor import RouteEOR
20
from exabgp.bgp.message.update.attribute.id import AttributeID as AID
21
from exabgp.bgp.message.update.attribute.flag import Flag
22
from exabgp.bgp.message.update.attribute.origin import Origin
23
from exabgp.bgp.message.update.attribute.aspath import ASPath,AS4Path
24
from exabgp.bgp.message.update.attribute.nexthop import cachedNextHop
25
from exabgp.bgp.message.update.attribute.med import MED
26
from exabgp.bgp.message.update.attribute.localpref import LocalPreference
27
from exabgp.bgp.message.update.attribute.aggregator import Aggregator
28
from exabgp.bgp.message.update.attribute.atomicaggregate import AtomicAggregate
29
from exabgp.bgp.message.update.attribute.originatorid import OriginatorID
30
from exabgp.bgp.message.update.attribute.clusterlist import ClusterList
31
from exabgp.bgp.message.update.attribute.communities import cachedCommunity,Communities,ECommunity,ECommunities
33
from exabgp.structure.log import Logger,LazyFormat
36
# =================================================================== Attributes
38
class MultiAttributes (list):
39
def __init__ (self,attribute):
41
self.ID = attribute.ID
42
self.FLAG = attribute.FLAG
44
self.append(attribute)
48
for attribute in self:
49
r.append(attribute.pack())
53
return len(self.pack())
56
return 'MultiAttibutes(%s)' % ' '.join(str(_) for _ in self)
58
class Attributes (dict):
65
self.cache_attributes = environment.settings().cache.attributes
70
def add_from_cache (self,attributeid,data):
71
if data in self.cache.setdefault(attributeid,Cache()):
72
self.add(self.cache[attributeid].retrieve(data))
76
def add (self,attribute,data=None):
78
if data and self.cache_attributes:
79
self.cache[attribute.ID].cache(data,attribute)
80
if attribute.MULTIPLE:
81
if self.has(attribute.ID):
82
self[attribute.ID].append(attribute)
84
self[attribute.ID] = MultiAttributes(attribute)
86
self[attribute.ID] = attribute
88
def remove (self,attrid):
91
def _as_path (self,asn4,asp):
92
# if the peer does not understand ASN4, we need to build a transitive AS4_PATH
96
as2_seq = [_ if not _.asn4() else AS_TRANS for _ in asp.as_seq]
97
as2_set = [_ if not _.asn4() else AS_TRANS for _ in asp.as_set]
99
message = ASPath(as2_seq,as2_set).pack(False)
100
if AS_TRANS in as2_seq or AS_TRANS in as2_set:
101
message += AS4Path(asp.as_seq,asp.as_set).pack()
104
def pack (self,asn4,local_asn,peer_asn):
105
ibgp = (local_asn == peer_asn)
106
# we do not store or send MED
109
if AID.ORIGIN in self:
110
message += self[AID.ORIGIN].pack()
111
elif self.autocomplete:
112
message += Origin(Origin.IGP).pack()
114
if AID.AS_PATH in self:
115
asp = self[AID.AS_PATH]
116
message += self._as_path(asn4,asp)
117
elif self.autocomplete:
121
asp = ASPath([local_asn,],[])
122
message += self._as_path(asn4,asp)
124
raise RuntimeError('Generated routes must always have an AS_PATH ')
126
if AID.NEXT_HOP in self:
127
afi = self[AID.NEXT_HOP].afi
128
safi = self[AID.NEXT_HOP].safi
129
if afi == AFI.ipv4 and safi in [SAFI.unicast, SAFI.multicast]:
130
message += self[AID.NEXT_HOP].pack()
133
if local_asn != peer_asn:
134
message += self[AID.MED].pack()
137
if AID.LOCAL_PREF in self:
138
message += self[AID.LOCAL_PREF].pack()
140
# '\x00\x00\x00d' is 100 packed in long network bytes order
141
message += LocalPreference('\x00\x00\x00d').pack()
143
# This generate both AGGREGATOR and AS4_AGGREGATOR
144
if AID.AGGREGATOR in self:
145
aggregator = self[AID.AGGREGATOR]
146
message += aggregator.pack(asn4)
149
AID.ATOMIC_AGGREGATE,
153
AID.EXTENDED_COMMUNITY
155
if attribute in self:
156
message += self[attribute].pack()
162
if self.has(AID.NEXT_HOP): r.append('"next-hop": "%s"' % str(self[AID.NEXT_HOP]))
163
if self.has(AID.ORIGIN): r.append('"origin": "%s"' % str(self[AID.ORIGIN]))
164
if self.has(AID.AS_PATH): r.append('"as-path": %s' % self[AID.AS_PATH].json())
165
if self.has(AID.LOCAL_PREF): r.append('"local-preference": %s' % self[AID.LOCAL_PREF])
166
if self.has(AID.AGGREGATOR): r.append('"aggregator" : "%s"' % self[AID.AGGREGATOR])
167
if self.has(AID.MED): r.append('"med": %s' % self[AID.MED])
168
if self.has(AID.COMMUNITY): r.append('"community": %s' % self[AID.COMMUNITY].json())
169
if self.has(AID.ORIGINATOR_ID): r.append('"originator-id": "%s"' % str(self[AID.ORIGINATOR_ID]))
170
if self.has(AID.CLUSTER_LIST): r.append('"cluster-list": %s' % self[AID.CLUSTER_LIST].json())
171
if self.has(AID.EXTENDED_COMMUNITY): r.append('"extended-community": %s' % self[AID.EXTENDED_COMMUNITY].json())
172
r.append('"atomic-aggregate": %s' % ('true' if self.has(AID.ATOMIC_AGGREGATE) else 'false'))
179
next_hop = ' next-hop %s' % str(self[AID.NEXT_HOP]) if self.has(AID.NEXT_HOP) else ''
180
origin = ' origin %s' % str(self[AID.ORIGIN]) if self.has(AID.ORIGIN) else ''
181
aspath = ' as-path %s' % str(self[AID.AS_PATH]) if self.has(AID.AS_PATH) else ''
182
local_pref = ' local-preference %s' % self[AID.LOCAL_PREF] if self.has(AID.LOCAL_PREF) else ''
183
aggregator = ' aggregator ( %s )' % self[AID.AGGREGATOR] if self.has(AID.AGGREGATOR) else ''
184
atomic = ' atomic-aggregate' if self.has(AID.ATOMIC_AGGREGATE) else ''
185
med = ' med %s' % self[AID.MED] if self.has(AID.MED) else ''
186
communities = ' community %s' % str(self[AID.COMMUNITY]) if self.has(AID.COMMUNITY) else ''
187
originator_id = ' originator-id %s' % str(self[AID.ORIGINATOR_ID]) if self.has(AID.ORIGINATOR_ID) else ''
188
cluster_list = ' cluster-list %s' % str(self[AID.CLUSTER_LIST]) if self.has(AID.CLUSTER_LIST) else ''
189
ecommunities = ' extended-community %s' % str(self[AID.EXTENDED_COMMUNITY]) if self.has(AID.EXTENDED_COMMUNITY) else ''
191
self._str = "%s%s%s%s%s%s%s%s%s%s%s" % (
192
next_hop,origin,aspath,local_pref,atomic,aggregator,med,communities,ecommunities,originator_id,cluster_list
197
def factory (self,negotiated,data):
199
# XXX: hackish for now
200
self.mp_announce = []
201
self.mp_withdraw = []
203
self.negotiated = negotiated
205
if AID.AS_PATH in self and AID.AS4_PATH in self:
206
self.__merge_attributes()
209
raise Notify(3,2,data)
211
def _factory (self,data):
215
# We do not care if the attribute are transitive or not as we do not redistribute
216
flag = Flag(ord(data[0]))
217
code = AID(ord(data[1]))
219
if flag & Flag.EXTENDED_LENGTH:
220
length = unpack('!H',data[2:4])[0]
223
length = ord(data[2])
228
attribute = data[:length]
231
logger.parser(LazyFormat("parsing flag %x type %02x (%s) len %02x %s" % (flag,int(code),code,length,'payload ' if length else ''),dump,data[:length]))
233
if code == AID.ORIGIN:
234
# This if block should never be called anymore ...
235
if not self.add_from_cache(code,attribute):
236
self.add(Origin(ord(attribute)),attribute)
237
return self._factory(next)
239
# only 2-4% of duplicated data - is it worth to cache ?
240
if code == AID.AS_PATH:
242
# we store the AS4_PATH as AS_PATH, do not over-write
243
if not self.has(code):
244
if not self.add_from_cache(code,attribute):
245
self.add(self.__new_ASPath(attribute),attribute)
246
return self._factory(next)
248
if code == AID.AS4_PATH:
250
# ignore the AS4_PATH on new spekers as required by RFC 4893 section 4.1
251
if not self.negotiated.asn4:
252
# This replace the old AS_PATH
253
if not self.add_from_cache(code,attribute):
254
self.add(self.__new_ASPath4(attribute),attribute)
255
return self._factory(next)
257
if code == AID.NEXT_HOP:
258
if not self.add_from_cache(code,attribute):
259
self.add(cachedNextHop(AFI.ipv4,SAFI.unicast_multicast,attribute),attribute)
260
return self._factory(next)
263
if not self.add_from_cache(code,attribute):
264
self.add(MED(attribute),attribute)
265
return self._factory(next)
267
if code == AID.LOCAL_PREF:
268
if not self.add_from_cache(code,attribute):
269
self.add(LocalPreference(attribute),attribute)
270
return self._factory(next)
272
if code == AID.ATOMIC_AGGREGATE:
273
if not self.add_from_cache(code,attribute):
274
raise Notify(3,2,'invalid ATOMIC_AGGREGATE %s' % [hex(ord(_)) for _ in attribute])
275
return self._factory(next)
277
if code == AID.AGGREGATOR:
278
# AS4_AGGREGATOR are stored as AGGREGATOR - so do not overwrite if exists
279
if not self.has(code):
280
if not self.add_from_cache(AID.AGGREGATOR,attribute):
281
self.add(Aggregator(attribute),attribute)
282
return self._factory(next)
284
if code == AID.AS4_AGGREGATOR:
285
if not self.add_from_cache(AID.AGGREGATOR,attribute):
286
self.add(Aggregator(attribute),attribute)
287
return self._factory(next)
289
if code == AID.COMMUNITY:
290
if not self.add_from_cache(code,attribute):
291
self.add(self.__new_communities(attribute),attribute)
292
return self._factory(next)
294
if code == AID.ORIGINATOR_ID:
295
if not self.add_from_cache(code,attribute):
296
self.add(OriginatorID(AFI.ipv4,SAFI.unicast,data[:4]),attribute)
297
return self._factory(next)
299
if code == AID.CLUSTER_LIST:
300
if not self.add_from_cache(code,attribute):
301
self.add(ClusterList(attribute),attribute)
302
return self._factory(next)
304
if code == AID.EXTENDED_COMMUNITY:
305
if not self.add_from_cache(code,attribute):
306
self.add(self.__new_extended_communities(attribute),attribute)
307
return self._factory(next)
309
if code == AID.MP_UNREACH_NLRI:
310
# -- Reading AFI/SAFI
312
afi,safi = unpack('!HB',data[:3])
316
if (afi,safi) not in self.negotiated.families:
317
raise Notify(3,0,'presented a non-negotiated family %d/%d' % (afi,safi))
319
# Is the peer going to send us some Path Information with the route (AddPath)
320
addpath = self.negotiated.addpath.receive(afi,safi)
322
# XXX: we do assume that it is an EOR. most likely harmless
324
self.mp_withdraw.append(RouteEOR(afi,safi,'announced'))
325
return self._factory(next)
328
route = self.routeFactory(afi,safi,data,addpath,'withdrawn')
329
route.attributes = self
330
self.mp_withdraw.append(route)
331
data = data[len(route.nlri):]
332
return self._factory(next)
334
if code == AID.MP_REACH_NLRI:
336
# -- Reading AFI/SAFI
337
afi,safi = unpack('!HB',data[:3])
340
# we do not want to accept unknown families
341
if (afi,safi) not in self.negotiated.families:
342
raise Notify(3,0,'presented a non-negotiated family %d/%d' % (afi,safi))
344
# -- Reading length of next-hop
345
len_nh = ord(data[offset])
350
# check next-hope size
352
if safi in (SAFI.unicast,SAFI.multicast):
354
raise Notify(3,0,'invalid ipv4 unicast/multicast next-hop length %d expected 4' % len_nh)
355
if safi in (SAFI.mpls_vpn,):
357
raise Notify(3,0,'invalid ipv4 mpls_vpn next-hop length %d expected 12' % len_nh)
360
elif afi == AFI.ipv6:
361
if safi in (SAFI.unicast,):
362
if len_nh not in (16,32):
363
raise Notify(3,0,'invalid ipv6 unicast next-hop length %d expected 16 or 32' % len_nh)
364
if safi in (SAFI.mpls_vpn,):
365
if len_nh not in (24,40):
366
raise Notify(3,0,'invalid ipv6 mpls_vpn next-hop length %d expected 24 or 40' % len_nh)
370
# -- Reading next-hop
371
nh = data[offset+rd:offset+rd+size]
373
# chech the RD is well zeo
374
if rd and sum([int(ord(_)) for _ in data[offset:8]]) != 0:
375
raise Notify(3,0,"MP_REACH_NLRI next-hop's route-distinguisher must be zero")
379
# Skip a reserved bit as somone had to bug us !
380
reserved = ord(data[offset])
384
raise Notify(3,0,'the reserved bit of MP_REACH_NLRI is not zero')
386
# Is the peer going to send us some Path Information with the route (AddPath)
387
addpath = self.negotiated.addpath.receive(afi,safi)
393
route = self.routeFactory(afi,safi,data,addpath,'announced')
394
if not route.attributes.add_from_cache(AID.NEXT_HOP,nh):
395
route.attributes.add(cachedNextHop(afi,safi,nh),nh)
396
self.mp_announce.append(route)
397
data = data[len(route.nlri):]
398
return self._factory(next)
400
logger.parser('ignoring attribute')
401
return self._factory(next)
403
def __merge_attributes (self):
404
as2path = self[AID.AS_PATH]
405
as4path = self[AID.AS4_PATH]
406
self.remove(AID.AS_PATH)
407
self.remove(AID.AS4_PATH)
409
# this key is unique as index length is a two header, plus a number of ASN of size 2 or 4
410
# so adding the : make the length odd and unique
411
key = "%s:%s" % (as2path.index, as4path.index)
414
if self.add_from_cache(AID.AS_PATH,key):
420
len2 = len(as2path.as_seq)
421
len4 = len(as4path.as_seq)
423
# RFC 4893 section 4.2.3
425
as_seq = as2path.as_seq
427
as_seq = as2path.as_seq[:-len4]
428
as_seq.extend(as4path.as_seq)
430
len2 = len(as2path.as_set)
431
len4 = len(as4path.as_set)
434
as_set = as4path.as_set
436
as_set = as2path.as_set[:-len4]
437
as_set.extend(as4path.as_set)
439
aspath = ASPath(as_seq,as_set)
442
def __new_communities (self,data):
443
communities = Communities()
445
if data and len(data) < 4:
446
raise Notify(3,1,'could not decode community %s' % str([hex(ord(_)) for _ in data]))
447
communities.add(cachedCommunity(data[:4]))
451
def __new_extended_communities (self,data):
452
communities = ECommunities()
454
if data and len(data) < 8:
455
raise Notify(3,1,'could not decode extended community %s' % str([hex(ord(_)) for _ in data]))
456
communities.add(ECommunity(data[:8]))
460
def __new_aspaths (self,data,asn4,klass):
474
ASPath.AS_SEQUENCE : as_seq,
475
ASPath.AS_SET : as_set,
487
if stype not in (ASPath.AS_SET, ASPath.AS_SEQUENCE):
488
raise Notify(3,11,'invalid AS Path type sent %d' % stype)
490
end = 2+(slen*length)
493
asns = as_choice[stype]
495
for i in range(slen):
496
asn = unpack(upr,sdata[:length])[0]
497
asns.append(ASN(asn))
498
sdata = sdata[length:]
501
raise Notify(3,11,'not enough data to decode AS_PATH or AS4_PATH')
502
except error: # struct
503
raise Notify(3,11,'not enough data to decode AS_PATH or AS4_PATH')
505
return klass(as_seq,as_set,backup)
507
def __new_ASPath (self,data):
508
return self.__new_aspaths(data,self.negotiated.asn4,ASPath)
510
def __new_ASPath4 (self,data):
511
return self.__new_aspaths(data,True,AS4Path)
513
if not Attributes.cache:
514
for attribute in AID._str:
515
Attributes.cache[attribute] = Cache()
517
# There can only be one, build it now :)
518
Attributes.cache[AID.ATOMIC_AGGREGATE][''] = AtomicAggregate()
520
IGP = Origin(Origin.IGP)
521
EGP = Origin(Origin.EGP)
522
INC = Origin(Origin.INCOMPLETE)
524
Attributes.cache[AID.ORIGIN][IGP.pack()] = IGP
525
Attributes.cache[AID.ORIGIN][EGP.pack()] = EGP
526
Attributes.cache[AID.ORIGIN][INC.pack()] = INC