exabgp.bgp.neighbor.Neighbor.add_addpath()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nop 2
dl 0
loc 12
rs 9.95
c 0
b 0
f 0
1
# encoding: utf-8
2
"""
3
neighbor.py
4
5
Created by Thomas Mangin on 2009-11-05.
6
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
7
License: 3-clause BSD. (See the COPYRIGHT file)
8
"""
9
10
from collections import deque
11
12
from collections import Counter
13
14
from exabgp.protocol.family import AFI
15
16
from exabgp.bgp.message import Message
17
from exabgp.bgp.message.open.capability import AddPath
18
19
from exabgp.rib import RIB
20
21
22
# The definition of a neighbor (from reading the configuration)
23
class Neighbor(object):
24
    _GLOBAL = {'uid': 1}
25
26
    def __init__(self):
27
        self.description = None
28
        self.router_id = None
29
        self.host_name = None
30
        self.domain_name = None
31
        self.local_address = None
32
        self.range_size = 1
33
        # local_address uses auto discovery
34
        self.auto_discovery = False
35
        self.peer_address = None
36
        self.peer_as = None
37
        self.local_as = None
38
        self.hold_time = None
39
        self.rate_limit = None
40
        self.asn4 = None
41
        self.nexthop = None
42
        self.add_path = None
43
        self.md5_password = None
44
        self.md5_base64 = False
45
        self.md5_ip = None
46
        self.ttl_in = None
47
        self.ttl_out = None
48
        self.group_updates = None
49
        self.flush = None
50
        self.adj_rib_in = None
51
        self.adj_rib_out = None
52
53
        self.manual_eor = False
54
55
        self.api = None  # XXX: not scriptable - is replaced outside the class
56
        # passive indicate that we do not establish outgoing connections
57
        self.passive = False
58
        # the port to listen on ( zero mean that we do not listen )
59
        self.listen = 0
60
        # the port to connect to
61
        self.connect = 0
62
63
        # was this Neighbor generated from a range
64
        self.generated = False
65
66
        # capability
67
        self.route_refresh = False
68
        self.graceful_restart = False
69
        self.multisession = None
70
        self.nexthop = None
71
        self.add_path = None
72
        self.aigp = None
73
74
        self._families = []
75
        self._nexthop = []
76
        self._addpath = []
77
        self.rib = None
78
79
        # The routes we have parsed from the configuration
80
        self.changes = []
81
        # On signal update, the previous routes so we can compare what changed
82
        self.backup_changes = []
83
84
        self.operational = None
85
        self.eor = deque()
86
        self.asm = dict()
87
88
        self.messages = deque()
89
        self.refresh = deque()
90
91
        self.counter = Counter()
92
        # It is possible to :
93
        # - have multiple exabgp toward one peer on the same host ( use of pid )
94
        # - have more than once connection toward a peer
95
        # - each connection has it own neihgbor (hence why identificator is not in Protocol)
96
        self.uid = '%d' % self._GLOBAL['uid']
97
        self._GLOBAL['uid'] += 1
98
99
    def id(self):
100
        return 'neighbor-%s' % self.uid
101
102
    # This set must be unique between peer, not full draft-ietf-idr-bgp-multisession-07
103
    def index(self):
104
        if self.listen != 0:
105
            return 'peer-ip %s listen %d' % (self.peer_address, self.listen)
106
        return self.name()
107
108
    def make_rib(self):
109
        self.rib = RIB(self.name(), self.adj_rib_in, self.adj_rib_out, self._families)
110
111
    # will resend all the routes once we reconnect
112
    def reset_rib(self):
113
        self.rib.reset()
114
        self.messages = deque()
115
        self.refresh = deque()
116
117
    # back to square one, all the routes are removed
118
    def clear_rib(self):
119
        self.rib.clear()
120
        self.messages = deque()
121
        self.refresh = deque()
122
123
    def name(self):
124
        if self.multisession:
125
            session = '/'.join("%s-%s" % (afi.name(), safi.name()) for (afi, safi) in self.families())
126
        else:
127
            session = 'in-open'
128
        return "neighbor %s local-ip %s local-as %s peer-as %s router-id %s family-allowed %s" % (
129
            self.peer_address,
130
            self.local_address if self.peer_address is not None else 'auto',
131
            self.local_as if self.local_as is not None else 'auto',
132
            self.peer_as if self.peer_as is not None else 'auto',
133
            self.router_id,
134
            session,
135
        )
136
137
    def families(self):
138
        # this list() is important .. as we use the function to modify self._families
139
        return list(self._families)
140
141
    def nexthops(self):
142
        # this list() is important .. as we use the function to modify self._nexthop
143
        return list(self._nexthop)
144
145
    def addpaths(self):
146
        # this list() is important .. as we use the function to modify self._add_path
147
        return list(self._addpath)
148
149
    def add_family(self, family):
150
        # the families MUST be sorted for neighbor indexing name to be predictable for API users
151
        # this list() is important .. as we use the function to modify self._families
152
        if family not in self.families():
153
            afi, safi = family
154
            d = dict()
155
            d[afi] = [
156
                safi,
157
            ]
158
            for afi, safi in self._families:
159
                d.setdefault(afi, []).append(safi)
160
            self._families = [(afi, safi) for afi in sorted(d) for safi in sorted(d[afi])]
161
162
    def add_nexthop(self, afi, safi, nhafi):
163
        if (afi, safi, nhafi) not in self._nexthop:
164
            self._nexthop.append((afi, safi, nhafi))
165
166
    def add_addpath(self, family):
167
        # the families MUST be sorted for neighbor indexing name to be predictable for API users
168
        # this list() is important .. as we use the function to modify self._add_path
169
        if family not in self.addpaths():
170
            afi, safi = family
171
            d = dict()
172
            d[afi] = [
173
                safi,
174
            ]
175
            for afi, safi in self._addpath:
176
                d.setdefault(afi, []).append(safi)
177
            self._addpath = [(afi, safi) for afi in sorted(d) for safi in sorted(d[afi])]
178
179
    def remove_family(self, family):
180
        if family in self.families():
181
            self._families.remove(family)
182
183
    def remove_nexthop(self, afi, safi, nhafi):
184
        if (afi, safi, nhafi) in self.nexthops():
185
            self._nexthop.remove((afi, safi, nhafi))
186
187
    def remove_addpath(self, family):
188
        if family in self.addpaths():
189
            self._addpath.remove(family)
190
191
    def missing(self):
192
        if self.local_address is None and not self.auto_discovery:
193
            return 'local-address'
194
        if self.listen > 0 and self.auto_discovery:
195
            return 'local-address'
196
        if self.peer_address is None:
197
            return 'peer-address'
198
        if self.auto_discovery and not self.router_id:
199
            return 'router-id'
200
        if self.peer_address.afi == AFI.ipv6 and not self.router_id:
201
            return 'router-id'
202
        return ''
203
204
    # This function only compares the neighbor BUT NOT ITS ROUTES
205
    def __eq__(self, other):
206
        # Comparing local_address is skipped in the case where either
207
        # peer is configured to auto discover its local address. In
208
        # this case it can happen that one local_address is None and
209
        # the other one will be set to the auto disocvered IP address.
210
        auto_discovery = self.auto_discovery or other.auto_discovery
211
        return (
212
            self.router_id == other.router_id
213
            and (auto_discovery or self.local_address == other.local_address)
214
            and self.auto_discovery == other.auto_discovery
215
            and self.local_as == other.local_as
216
            and self.peer_address == other.peer_address
217
            and self.peer_as == other.peer_as
218
            and self.passive == other.passive
219
            and self.listen == other.listen
220
            and self.connect == other.connect
221
            and self.hold_time == other.hold_time
222
            and self.rate_limit == other.rate_limit
223
            and self.host_name == other.host_name
224
            and self.domain_name == other.domain_name
225
            and self.md5_password == other.md5_password
226
            and self.md5_ip == other.md5_ip
227
            and self.ttl_in == other.ttl_in
228
            and self.ttl_out == other.ttl_out
229
            and self.route_refresh == other.route_refresh
230
            and self.graceful_restart == other.graceful_restart
231
            and self.multisession == other.multisession
232
            and self.nexthop == other.nexthop
233
            and self.add_path == other.add_path
234
            and self.operational == other.operational
235
            and self.group_updates == other.group_updates
236
            and self.flush == other.flush
237
            and self.adj_rib_in == other.adj_rib_in
238
            and self.adj_rib_out == other.adj_rib_out
239
            and self.families() == other.families()
240
        )
241
242
    def __ne__(self, other):
243
        return not self.__eq__(other)
244
245
    def string(self, with_changes=True):
246
        changes = ''
247
        if with_changes:
248
            changes += '\nstatic { '
249
            for change in self.rib.outgoing.queued_changes():
250
                changes += '\n\t\t%s' % change.extensive()
251
            changes += '\n}'
252
253
        families = ''
254
        for afi, safi in self.families():
255
            families += '\n\t\t%s %s;' % (afi.name(), safi.name())
256
257
        nexthops = ''
258
        for afi, safi, nexthop in self.nexthops():
259
            nexthops += '\n\t\t%s %s %s;' % (afi.name(), safi.name(), nexthop.name())
260
261
        addpaths = ''
262
        for afi, safi in self.addpaths():
263
            addpaths += '\n\t\t%s %s;' % (afi.name(), safi.name())
264
265
        codes = Message.CODE
266
267
        _extension_global = {
268
            'neighbor-changes': 'neighbor-changes',
269
            'negotiated': 'negotiated',
270
            'fsm': 'fsm',
271
            'signal': 'signal',
272
        }
273
274
        _extension_receive = {
275
            'receive-packets': 'packets',
276
            'receive-parsed': 'parsed',
277
            'receive-consolidate': 'consolidate',
278
            'receive-%s' % codes.NOTIFICATION.SHORT: 'notification',
279
            'receive-%s' % codes.OPEN.SHORT: 'open',
280
            'receive-%s' % codes.KEEPALIVE.SHORT: 'keepalive',
281
            'receive-%s' % codes.UPDATE.SHORT: 'update',
282
            'receive-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh',
283
            'receive-%s' % codes.OPERATIONAL.SHORT: 'operational',
284
        }
285
286
        _extension_send = {
287
            'send-packets': 'packets',
288
            'send-parsed': 'parsed',
289
            'send-consolidate': 'consolidate',
290
            'send-%s' % codes.NOTIFICATION.SHORT: 'notification',
291
            'send-%s' % codes.OPEN.SHORT: 'open',
292
            'send-%s' % codes.KEEPALIVE.SHORT: 'keepalive',
293
            'send-%s' % codes.UPDATE.SHORT: 'update',
294
            'send-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh',
295
            'send-%s' % codes.OPERATIONAL.SHORT: 'operational',
296
        }
297
298
        apis = ''
299
300
        for process in self.api.get('processes', []):
301
            _global = []
302
            _receive = []
303
            _send = []
304
305
            for api, name in _extension_global.items():
306
                _global.extend(['\t\t%s;\n' % name,] if process in self.api[api] else [])
307
308
            for api, name in _extension_receive.items():
309
                _receive.extend(['\t\t\t%s;\n' % name,] if process in self.api[api] else [])
310
311
            for api, name in _extension_send.items():
312
                _send.extend(['\t\t\t%s;\n' % name,] if process in self.api[api] else [])
313
314
            _api = '\tapi {\n'
315
            _api += '\t\tprocesses [ %s ];\n' % process
316
            _api += ''.join(_global)
317
            if _receive:
318
                _api += '\t\treceive {\n'
319
                _api += ''.join(_receive)
320
                _api += '\t\t}\n'
321
            if _send:
322
                _api += '\t\tsend {\n'
323
                _api += ''.join(_send)
324
                _api += '\t\t}\n'
325
            _api += '\t}\n'
326
327
            apis += _api
328
329
        returned = (
330
            'neighbor %s {\n'
331
            '\tdescription "%s";\n'
332
            '\trouter-id %s;\n'
333
            '\thost-name %s;\n'
334
            '\tdomain-name %s;\n'
335
            '\tlocal-address %s;\n'
336
            '\tlocal-as %s;\n'
337
            '\tpeer-as %s;\n'
338
            '\thold-time %s;\n'
339
            '\trate-limit %s;\n'
340
            '\tmanual-eor %s;\n'
341
            '%s%s%s%s%s%s%s%s%s%s%s\n'
342
            '\tcapability {\n'
343
            '%s%s%s%s%s%s%s%s%s\t}\n'
344
            '\tfamily {%s\n'
345
            '\t}\n'
346
            '\tnexthop {%s\n'
347
            '\t}\n'
348
            '\tadd-path {%s\n'
349
            '\t}\n'
350
            '%s'
351
            '%s'
352
            '}'
353
            % (
354
                self.peer_address,
355
                self.description,
356
                self.router_id,
357
                self.host_name,
358
                self.domain_name,
359
                self.local_address if not self.auto_discovery else 'auto',
360
                self.local_as,
361
                self.peer_as,
362
                self.hold_time,
363
                'disable' if self.rate_limit == 0 else self.rate_limit,
364
                'true' if self.manual_eor else 'false',
365
                '\n\tpassive %s;\n' % ('true' if self.passive else 'false'),
366
                '\n\tlisten %d;\n' % self.listen if self.listen else '',
367
                '\n\tconnect %d;\n' % self.connect if self.connect else '',
368
                '\tgroup-updates %s;\n' % ('true' if self.group_updates else 'false'),
369
                '\tauto-flush %s;\n' % ('true' if self.flush else 'false'),
370
                '\tadj-rib-in %s;\n' % ('true' if self.adj_rib_in else 'false'),
371
                '\tadj-rib-out %s;\n' % ('true' if self.adj_rib_out else 'false'),
372
                '\tmd5-password "%s";\n' % self.md5_password if self.md5_password else '',
373
                '\tmd5-base64 %s;\n'
374
                % ('true' if self.md5_base64 is True else 'false' if self.md5_base64 is False else 'auto'),
375
                '\tmd5-ip "%s";\n' % self.md5_ip if not self.auto_discovery else '',
376
                '\toutgoing-ttl %s;\n' % self.ttl_out if self.ttl_out else '',
377
                '\tincoming-ttl %s;\n' % self.ttl_in if self.ttl_in else '',
378
                '\t\tasn4 %s;\n' % ('enable' if self.asn4 else 'disable'),
379
                '\t\troute-refresh %s;\n' % ('enable' if self.route_refresh else 'disable'),
380
                '\t\tgraceful-restart %s;\n' % (self.graceful_restart if self.graceful_restart else 'disable'),
381
                '\t\tnexthop %s;\n' % ('enable' if self.nexthop else 'disable'),
382
                '\t\tadd-path %s;\n' % (AddPath.string[self.add_path] if self.add_path else 'disable'),
383
                '\t\tmulti-session %s;\n' % ('enable' if self.multisession else 'disable'),
384
                '\t\toperational %s;\n' % ('enable' if self.operational else 'disable'),
385
                '\t\taigp %s;\n' % ('enable' if self.aigp else 'disable'),
386
                families,
387
                nexthops,
388
                addpaths,
389
                apis,
390
                changes,
391
            )
392
        )
393
394
        # '\t\treceive {\n%s\t\t}\n' % receive if receive else '',
395
        # '\t\tsend {\n%s\t\t}\n' % send if send else '',
396
        return returned.replace('\t', '  ')
397
398
    def __str__(self):
399
        return self.string(False)
400