exabgp.reactor.peer.Peer.socket()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
# encoding: utf-8
2
"""
3
peer.py
4
5
Created by Thomas Mangin on 2009-08-25.
6
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
7
License: 3-clause BSD. (See the COPYRIGHT file)
8
"""
9
10
import time
11
from collections import defaultdict
12
13
# import traceback
14
from exabgp.bgp.timer import ReceiveTimer
15
from exabgp.bgp.message import Message
16
from exabgp.bgp.fsm import FSM
17
from exabgp.bgp.message.open.capability import Capability
18
from exabgp.bgp.message.open.capability import REFRESH
19
from exabgp.bgp.message import NOP
20
from exabgp.bgp.message import Update
21
from exabgp.bgp.message.refresh import RouteRefresh
22
from exabgp.bgp.message import Notification
23
from exabgp.bgp.message import Notify
24
from exabgp.reactor.protocol import Protocol
25
from exabgp.reactor.delay import Delay
26
from exabgp.reactor.keepalive import KA
27
from exabgp.reactor.network.error import NetworkError
28
from exabgp.reactor.api.processes import ProcessError
29
30
from exabgp.rib.change import Change
31
32
from exabgp.environment import getenv
33
from exabgp.logger import log
34
from exabgp.logger import logfunc
35
from exabgp.logger import lazyformat
36
37
from exabgp.debug import format_exception
38
39
40
class ACTION(object):
41
    CLOSE = 0x01  # finished, no need to restart the peer
42
    LATER = 0x02  # re-run at the next reactor round
43
    NOW = 0x03  # re-run immediatlely
44
    ALL = [CLOSE, LATER, NOW]
45
46
47
class SEND(object):
48
    DONE = 0x01
49
    NORMAL = 0x02
50
    REFRESH = 0x04
51
    ALL = [DONE, NORMAL, REFRESH]
52
53
54
# As we can not know if this is our first start or not, this flag is used to
55
# always make the program act like it was recovering from a failure
56
# If set to FALSE, no EOR and OPEN Flags set for Restart will be set in the
57
# OPEN Graceful Restart Capability
58
FORCE_GRACEFUL = True
59
60
61
class Interrupted(Exception):
62
    pass
63
64
65
class Stop(Exception):
66
    pass
67
68
69
# ======================================================================== Peer
70
# Present a File like interface to socket.socket
71
72
73
class Peer(object):
74
    def __init__(self, neighbor, reactor):
75
        # We only to try to connect via TCP once
76
        self.once = getenv().tcp.once
77
        self.bind = True if getenv().tcp.bind else False
78
79
        now = time.time()
80
81
        self.reactor = reactor
82
        self.neighbor = neighbor
83
        # The next restart neighbor definition
84
        self._neighbor = None
85
86
        self.proto = None
87
        self.fsm = FSM(self, FSM.IDLE)
88
        self.stats = {
89
            'fsm': self.fsm,
90
            'creation': now,
91
            'reset': now,
92
            'complete': 0,
93
        }
94
        self.generator = None
95
96
        # The peer should restart after a stop
97
        self._restart = True
98
        # The peer was restarted (to know what kind of open to send for graceful restart)
99
        self._restarted = FORCE_GRACEFUL
100
101
        # We want to remove routes which are not in the configuration anymore after a signal to reload
102
        self._reconfigure = True
103
        # We want to send all the known routes
104
        self._resend_routes = SEND.DONE
105
106
        # We have been asked to teardown the session with this code
107
        self._teardown = None
108
109
        self._delay = Delay()
110
        self.recv_timer = None
111
112
    def id(self):
113
        return 'peer-%s' % self.neighbor.uid
114
115
    def _reset(self, message='', error=''):
116
        self.fsm.change(FSM.IDLE)
117
        self.stats = {
118
            'fsm': self.fsm,
119
            'creation': self.stats['creation'],
120
            'reset': time.time(),
121
            'complete': 0,
122
        }
123
        if self.proto:
124
            try:
125
                message = u"peer reset, message [{0}] error[{1}]".format(message, error)
126
            except UnicodeDecodeError as msg_err:
127
                message = u"peer reset, message [{0}] error[{1}]".format(message, msg_err)
128
            self.proto.close(message)
129
        self._delay.increase()
130
131
        self.proto = None
132
133
        if not self._restart or self.neighbor.generated:
134
            self.generator = False
135
            return
136
137
        self.generator = None
138
        self._teardown = None
139
        self.neighbor.rib.reset()
140
141
        # If we are restarting, and the neighbor definition is different, update the neighbor
142
        if self._neighbor:
143
            self.neighbor = self._neighbor
144
            self._neighbor = None
145
146
    def _stop(self, message):
147
        self.generator = None
148
        if self.proto:
149
            self.proto.close('stop, message [%s]' % message)
150
            self.proto = None
151
152
    # logging
153
154
    def me(self, message):
155
        return "peer %s ASN %-7s %s" % (self.neighbor.peer_address, self.neighbor.peer_as, message)
156
157
    # control
158
159
    def stop(self):
160
        self._teardown = 3
161
        self._restart = False
162
        self._restarted = False
163
        self._delay.reset()
164
        self.fsm.change(FSM.IDLE)
165
        self.stats = {
166
            'fsm': self.fsm,
167
            'creation': self.stats['creation'],
168
            'reset': time.time(),
169
        }
170
        self.neighbor.rib.uncache()
171
172
    def remove(self):
173
        self._stop("removed")
174
        self.stop()
175
176
    def shutdown(self):
177
        self._stop("shutting down")
178
        self.stop()
179
180
    def resend(self):
181
        self._resend_routes = SEND.NORMAL
182
        self._delay.reset()
183
184
    def reestablish(self, restart_neighbor=None):
185
        # we want to tear down the session and re-establish it
186
        self._teardown = 3
187
        self._restart = True
188
        self._restarted = True
189
        self._resend_routes = SEND.NORMAL
190
        self._neighbor = restart_neighbor
191
        self._delay.reset()
192
193
    def reconfigure(self, restart_neighbor=None):
194
        # we want to update the route which were in the configuration file
195
        self._reconfigure = True
196
        self._neighbor = restart_neighbor
197
        self._resend_routes = SEND.NORMAL
198
        self._neighbor = restart_neighbor
199
200
    def teardown(self, code, restart=True):
201
        self._restart = restart
202
        self._teardown = code
203
        self._delay.reset()
204
205
    def socket(self):
206
        if self.proto:
207
            return self.proto.fd()
208
        return -1
209
210
    def handle_connection(self, connection):
211
        log.debug("state machine for the peer is %s" % self.fsm.name(), self.id())
212
213
        # if the other side fails, we go back to idle
214
        if self.fsm == FSM.ESTABLISHED:
215
            log.debug('we already have a peer in state established for %s' % connection.name(), self.id())
216
            return connection.notification(6, 7, 'could not accept the connection, already established')
217
218
        # 6.8 The convention is to compare the BGP Identifiers of the peers
219
        # involved in the collision and to retain only the connection initiated
220
        # by the BGP speaker with the higher-valued BGP Identifier.
221
        # FSM.IDLE , FSM.ACTIVE , FSM.CONNECT , FSM.OPENSENT , FSM.OPENCONFIRM , FSM.ESTABLISHED
222
223
        if self.fsm == FSM.OPENCONFIRM:
224
            # We cheat: we are not really reading the OPEN, we use the data we have instead
225
            # it does not matter as the open message will be the same anyway
226
            local_id = self.neighbor.router_id.pack()
227
            remote_id = self.proto.negotiated.received_open.router_id.pack()
228
229
            if remote_id < local_id:
230
                log.debug(
231
                    'closing incoming connection as we have an outgoing connection with higher router-id for %s'
232
                    % connection.name(),
233
                    self.id(),
234
                )
235
                return connection.notification(
236
                    6,
237
                    7,
238
                    'could not accept the connection, as another connection is already in open-confirm and will go through',
239
                )
240
241
        # accept the connection
242
        if self.proto:
243
            log.debug(
244
                'closing outgoing connection as we have another incoming on with higher router-id for %s'
245
                % connection.name(),
246
                self.id(),
247
            )
248
            self.proto.close('closing outgoing connection as we have another incoming on with higher router-id')
249
250
        self.proto = Protocol(self).accept(connection)
251
        self.generator = None
252
        # Let's make sure we do some work with this connection
253
        self._delay.reset()
254
        return None
255
256
    def established(self):
257
        return self.fsm == FSM.ESTABLISHED
258
259
    def negotiated_families(self):
260
        if self.proto:
261
            families = ["%s/%s" % (x[0], x[1]) for x in self.proto.negotiated.families]
262
        else:
263
            families = ["%s/%s" % (x[0], x[1]) for x in self.neighbor.families()]
264
265
        if len(families) > 1:
266
            return "[ %s ]" % " ".join(families)
267
        elif len(families) == 1:
268
            return families[0]
269
270
        return ''
271
272
    def _connect(self):
273
        proto = Protocol(self)
274
        connected = False
275
        try:
276
            for connected in proto.connect():
277
                if connected:
278
                    break
279
                if self._teardown:
280
                    raise Stop()
281
                # we want to come back as soon as possible
282
                yield ACTION.LATER
283
            self.proto = proto
284
        except Stop:
285
            # Connection failed
286
            if not connected and self.proto:
287
                self.proto.close('connection to %s:%d failed' % (self.neighbor.peer_address, self.neighbor.connect))
288
289
            # A connection arrived before we could establish !
290
            if not connected or self.proto:
291
                yield ACTION.NOW
292
                raise Interrupted()
293
294
    def _send_open(self):
295
        message = Message.CODE.NOP
296
        for message in self.proto.new_open():
297
            if message.ID == Message.CODE.NOP:
298
                yield ACTION.NOW
299
        yield message
300
301
    def _read_open(self):
302
        wait = getenv().bgp.openwait
303
        opentimer = ReceiveTimer(
304
            self.proto.connection.session, wait, 1, 1, 'waited for open too long, we do not like stuck in active'
305
        )
306
        # Only yield if we have not the open, otherwise the reactor can run the other connection
307
        # which would be bad as we need to do the collission check without going to the other peer
308
        for message in self.proto.read_open(self.neighbor.peer_address.top()):
309
            opentimer.check_ka(message)
310
            # XXX: FIXME: change the whole code to use the ord and not the chr version
311
            # Only yield if we have not the open, otherwise the reactor can run the other connection
312
            # which would be bad as we need to do the collission check
313
            if message.ID == Message.CODE.NOP:
314
                # If a peer does not reply to OPEN message, or not enough bytes
315
                # yielding ACTION.NOW can cause ExaBGP to busy spin trying to
316
                # read from peer. See GH #723 .
317
                yield ACTION.LATER
318
        yield message
0 ignored issues
show
introduced by
The variable message does not seem to be defined in case the for loop on line 308 is not entered. Are you sure this can never be the case?
Loading history...
319
320
    def _send_ka(self):
321
        for message in self.proto.new_keepalive('OPENCONFIRM'):
322
            yield ACTION.NOW
323
324
    def _read_ka(self):
325
        # Start keeping keepalive timer
326
        for message in self.proto.read_keepalive():
327
            self.recv_timer.check_ka_timer(message)
328
            yield ACTION.NOW
329
330
    def _establish(self):
331
        # try to establish the outgoing connection
332
        self.fsm.change(FSM.ACTIVE)
333
334
        if not self.proto:
335
            for action in self._connect():
336
                if action in ACTION.ALL:
337
                    yield action
338
        self.fsm.change(FSM.CONNECT)
339
340
        # normal sending of OPEN first ...
341
        if self.neighbor.local_as:
342
            for sent_open in self._send_open():
343
                if sent_open in ACTION.ALL:
344
                    yield sent_open
345
            self.proto.negotiated.sent(sent_open)
0 ignored issues
show
introduced by
The variable sent_open does not seem to be defined in case the for loop on line 342 is not entered. Are you sure this can never be the case?
Loading history...
346
            self.fsm.change(FSM.OPENSENT)
347
348
        # read the peer's open
349
        for received_open in self._read_open():
350
            if received_open in ACTION.ALL:
351
                yield received_open
352
        self.proto.negotiated.received(received_open)
0 ignored issues
show
introduced by
The variable received_open does not seem to be defined in case the for loop on line 349 is not entered. Are you sure this can never be the case?
Loading history...
353
354
        self.proto.connection.msg_size = self.proto.negotiated.msg_size
355
356
        # if we mirror the ASN, we need to read first and send second
357
        if not self.neighbor.local_as:
358
            for sent_open in self._send_open():
359
                if sent_open in ACTION.ALL:
360
                    yield sent_open
361
            self.proto.negotiated.sent(sent_open)
362
            self.fsm.change(FSM.OPENSENT)
363
364
        self.proto.validate_open()
365
        self.fsm.change(FSM.OPENCONFIRM)
366
367
        self.recv_timer = ReceiveTimer(self.proto.connection.session, self.proto.negotiated.holdtime, 4, 0)
368
        for action in self._send_ka():
369
            yield action
370
        for action in self._read_ka():
371
            yield action
372
        self.fsm.change(FSM.ESTABLISHED)
373
        self.stats['complete'] = time.time()
374
375
        # let the caller know that we were sucesfull
376
        yield ACTION.NOW
377
378
    def _main(self):
379
        """yield True if we want to come back to it asap, None if nothing urgent, and False if stopped"""
380
        if self._teardown:
381
            raise Notify(6, 3)
382
383
        self.neighbor.rib.incoming.clear()
384
385
        include_withdraw = False
386
387
        # Announce to the process BGP is up
388
        log.info('connected to %s with %s' % (self.id(), self.proto.connection.name()), 'reactor')
389
        self.stats['up'] = self.stats.get('up', 0) + 1
390
        if self.neighbor.api['neighbor-changes']:
391
            try:
392
                self.reactor.processes.up(self.neighbor)
393
            except ProcessError:
394
                # Can not find any better error code than 6,0 !
395
                # XXX: We can not restart the program so this will come back again and again - FIX
396
                # XXX: In the main loop we do exit on this kind of error
397
                raise Notify(6, 0, 'ExaBGP Internal error, sorry.')
398
399
        send_eor = not self.neighbor.manual_eor
400
        new_routes = None
401
        self._resend_routes = SEND.NORMAL
402
        send_families = []
403
404
        # Every last asm message should be re-announced on restart
405
        for family in self.neighbor.asm:
406
            if family in self.neighbor.families():
407
                self.neighbor.messages.appendleft(self.neighbor.asm[family])
408
409
        operational = None
410
        refresh = None
411
        command_eor = None
412
        number = 0
413
        refresh_enhanced = True if self.proto.negotiated.refresh == REFRESH.ENHANCED else False
414
415
        send_ka = KA(self.proto.connection.session, self.proto)
416
417
        while not self._teardown:
418
            for message in self.proto.read_message():
419
                self.recv_timer.check_ka(message)
420
421
                if send_ka() is not False:
422
                    # we need and will send a keepalive
423
                    while send_ka() is None:
424
                        yield ACTION.NOW
425
426
                # Received update
427
                if message.TYPE == Update.TYPE:
428
                    number += 1
429
                    log.debug('<< UPDATE #%d' % number, self.id())
430
431
                    for nlri in message.nlris:
432
                        self.neighbor.rib.incoming.update_cache(Change(nlri, message.attributes))
433
                        logfunc.debug(lazyformat('   UPDATE #%d nlri ' % number, nlri, str), self.id())
434
435
                elif message.TYPE == RouteRefresh.TYPE:
436
                    if message.reserved == RouteRefresh.request:
437
                        self._resend_routes = SEND.REFRESH
438
                        send_families.append((message.afi, message.safi))
439
440
                # SEND OPERATIONAL
441
                if self.neighbor.operational:
442
                    if not operational:
443
                        new_operational = self.neighbor.messages.popleft() if self.neighbor.messages else None
444
                        if new_operational:
445
                            operational = self.proto.new_operational(new_operational, self.proto.negotiated)
446
447
                    if operational:
448
                        try:
449
                            next(operational)
450
                        except StopIteration:
451
                            operational = None
452
                # make sure that if some operational message are received via the API
453
                # that we do not eat memory for nothing
454
                elif self.neighbor.messages:
455
                    self.neighbor.messages.popleft()
456
457
                # SEND REFRESH
458
                if self.neighbor.route_refresh:
459
                    if not refresh:
460
                        new_refresh = self.neighbor.refresh.popleft() if self.neighbor.refresh else None
461
                        if new_refresh:
462
                            refresh = self.proto.new_refresh(new_refresh)
463
464
                    if refresh:
465
                        try:
466
                            next(refresh)
467
                        except StopIteration:
468
                            refresh = None
469
470
                # Take the routes already sent to that peer and resend them
471
                if self._reconfigure:
472
                    self._reconfigure = False
473
474
                    # we are here following a configuration change
475
                    if self._neighbor:
476
                        # see what changed in the configuration
477
                        self.neighbor.rib.outgoing.replace(self._neighbor.backup_changes, self._neighbor.changes)
478
                        # do not keep the previous routes in memory as they are not useful anymore
479
                        self._neighbor.backup_changes = []
480
481
                # Take the routes already sent to that peer and resend them
482
                if self._resend_routes != SEND.DONE:
483
                    enhanced = True if refresh_enhanced and self._resend_routes == SEND.REFRESH else False
484
                    self._resend_routes = SEND.DONE
485
                    self.neighbor.rib.outgoing.resend(send_families, enhanced)
486
                    send_families = []
487
488
                # Need to send update
489
                if not new_routes and self.neighbor.rib.outgoing.pending():
490
                    # XXX: in proto really. hum to think about ?
491
                    new_routes = self.proto.new_update(include_withdraw)
492
493
                if new_routes:
494
                    count = 1 if self.neighbor.rate_limit > 0 else 25
495
                    try:
496
                        for _ in range(count):
497
                            # This can raise a NetworkError
498
                            next(new_routes)
499
                    except StopIteration:
500
                        new_routes = None
501
                        include_withdraw = True
502
503
                elif send_eor:
504
                    send_eor = False
505
                    for _ in self.proto.new_eors():
506
                        yield ACTION.NOW
507
                    log.debug('>> EOR(s)', self.id())
508
509
                # SEND MANUAL KEEPALIVE (only if we have no more routes to send)
510
                elif not command_eor and self.neighbor.eor:
511
                    new_eor = self.neighbor.eor.popleft()
512
                    command_eor = self.proto.new_eors(new_eor.afi, new_eor.safi)
513
514
                if command_eor:
515
                    try:
516
                        next(command_eor)
517
                    except StopIteration:
518
                        command_eor = None
519
520
                if new_routes or message.TYPE != NOP.TYPE:
521
                    yield ACTION.NOW
522
                elif self.neighbor.messages or operational:
523
                    yield ACTION.NOW
524
                elif self.neighbor.eor or command_eor:
525
                    yield ACTION.NOW
526
                else:
527
                    yield ACTION.LATER
528
529
                # read_message will loop until new message arrives with NOP
530
                if self._teardown:
531
                    break
532
533
        # If graceful restart, silent shutdown
534
        if self.neighbor.graceful_restart and self.proto.negotiated.sent_open.capabilities.announced(
535
            Capability.CODE.GRACEFUL_RESTART
536
        ):
537
            log.error('closing the session without notification', self.id())
538
            self.proto.close('graceful restarted negotiated, closing without sending any notification')
539
            raise NetworkError('closing')
540
541
        # notify our peer of the shutdown
542
        raise Notify(6, self._teardown)
543
544
    def _run(self):
545
        """yield True if we want the reactor to give us back the hand with the same peer loop, None if we do not have any more work to do"""
546
        try:
547
            for action in self._establish():
548
                yield action
549
550
            for action in self._main():
551
                yield action
552
553
        # CONNECTION FAILURE
554
        except NetworkError as network:
555
            # we tried to connect once, it failed and it was not a manual request, we stop
556
            if self.once and not self._teardown:
557
                log.debug('only one attempt to connect is allowed, stopping the peer', self.id())
558
                self.stop()
559
560
            self._reset('closing connection', network)
561
            return
562
563
        # NOTIFY THE PEER OF AN ERROR
564
        except Notify as notify:
565
            if self.proto:
566
                try:
567
                    generator = self.proto.new_notification(notify)
568
                    try:
569
                        while True:
570
                            next(generator)
571
                            yield ACTION.NOW
572
                    except StopIteration:
573
                        pass
574
                except (NetworkError, ProcessError):
575
                    log.error('Notification not sent', self.id())
576
                self._reset('notification sent (%d,%d)' % (notify.code, notify.subcode), notify)
577
            else:
578
                self._reset()
579
            return
580
581
        # THE PEER NOTIFIED US OF AN ERROR
582
        except Notification as notification:
583
            # we tried to connect once, it failed and it was not a manual request, we stop
584
            if self.once and not self._teardown:
585
                log.debug('only one attempt to connect is allowed, stopping the peer', self.id())
586
                self.stop()
587
588
            self._reset('notification received (%d,%d)' % (notification.code, notification.subcode), notification)
589
            return
590
591
        # RECEIVED a Message TYPE we did not expect
592
        except Message as message:
593
            self._reset('unexpected message received', message)
594
            return
595
596
        # PROBLEM WRITING TO OUR FORKED PROCESSES
597
        except ProcessError as process:
598
            self._reset('process problem', process)
599
            return
600
601
        # ....
602
        except Interrupted as interruption:
603
            self._reset('connection received before we could fully establish one')
604
            return
605
606
        # UNHANDLED PROBLEMS
607
        except Exception as exc:
608
            # Those messages can not be filtered in purpose
609
            log.debug(format_exception(exc), 'reactor')
610
            self._reset()
611
            return
612
613
    # loop
614
615
    def run(self):
616
        if self.reactor.processes.broken(self.neighbor):
617
            # XXX: we should perhaps try to restart the process ??
618
            log.error('ExaBGP lost the helper process for this peer - stopping', 'process')
619
            if self.reactor.processes.terminate_on_error:
620
                self.reactor.api_shutdown()
621
            else:
622
                self.stop()
623
            return True
624
625
        if self.generator:
626
            try:
627
                # This generator only stops when it raises
628
                # otherwise return one of the ACTION
629
                return next(self.generator)
630
            except StopIteration:
631
                # Trying to run a closed loop, no point continuing
632
                self.generator = None
633
                if self._restart:
634
                    return ACTION.LATER
635
                return ACTION.CLOSE
636
637
        elif self.generator is None:
638
            if self.fsm in [FSM.OPENCONFIRM, FSM.ESTABLISHED]:
639
                log.debug('stopping, other connection is established', self.id())
640
                self.generator = False
641
                return ACTION.LATER
642
            if self._delay.backoff():
643
                return ACTION.LATER
644
            if self._restart:
645
                log.debug('initialising connection to %s' % self.id(), 'reactor')
646
                self.generator = self._run()
647
                return ACTION.LATER  # make sure we go through a clean loop
648
            return ACTION.CLOSE
649
650
    def cli_data(self):
651
        def tri(value):
652
            if value is None:
653
                return None
654
            return True if value else False
655
656
        peer = defaultdict(lambda: None)
657
658
        have_peer = self.proto is not None
659
        have_open = self.proto and self.proto.negotiated.received_open
660
661
        if have_peer:
662
            peer.update(
663
                {'multi-session': self.proto.negotiated.multisession, 'operational': self.proto.negotiated.operational,}
664
            )
665
666
        if have_open:
667
            capa = self.proto.negotiated.received_open.capabilities
668
            peer.update(
669
                {
670
                    'router-id': self.proto.negotiated.sent_open.router_id,
671
                    'peer-id': self.proto.negotiated.received_open.router_id,
672
                    'hold-time': self.proto.negotiated.received_open.hold_time,
673
                    'asn4': self.proto.negotiated.asn4,
674
                    'route-refresh': capa.announced(Capability.CODE.ROUTE_REFRESH),
675
                    'multi-session': capa.announced(Capability.CODE.MULTISESSION)
676
                    or capa.announced(Capability.CODE.MULTISESSION_CISCO),
677
                    'add-path': capa.announced(Capability.CODE.ADD_PATH),
678
                    'extended-message': capa.announced(Capability.CODE.EXTENDED_MESSAGE),
679
                    'graceful-restart': capa.announced(Capability.CODE.GRACEFUL_RESTART),
680
                }
681
            )
682
683
        capabilities = {
684
            'asn4': (tri(self.neighbor.asn4), tri(peer['asn4'])),
685
            'route-refresh': (tri(self.neighbor.route_refresh), tri(peer['route-refresh'])),
686
            'multi-session': (tri(self.neighbor.multisession), tri(peer['multi-session'])),
687
            'operational': (tri(self.neighbor.operational), tri(peer['operational'])),
688
            'add-path': (tri(self.neighbor.add_path), tri(peer['add-path'])),
689
            'extended-message': (tri(self.neighbor.extended_message), tri(peer['extended-message'])),
690
            'graceful-restart': (tri(self.neighbor.graceful_restart), tri(peer['graceful-restart'])),
691
        }
692
693
        families = {}
694
        for family in self.neighbor.families():
695
            if have_open:
696
                common = True if family in self.proto.negotiated.families else False
697
                addpath = self.proto.negotiated.addpath.send(*family) and self.proto.negotiated.addpath.receive(*family)
698
            else:
699
                common = None
700
                addpath = None if family in self.neighbor.addpaths() else False
701
            families[family] = (True, common, addpath)
702
703
        messages = {}
704
        total_sent = 0
705
        total_rcvd = 0
706
        for message in ('open', 'notification', 'keepalive', 'update', 'refresh'):
707
            sent = self.stats.get('send-%s' % message, 0)
708
            rcvd = self.stats.get('receive-%s' % message, 0)
709
            total_sent += sent
710
            total_rcvd += rcvd
711
            messages[message] = (sent, rcvd)
712
        messages['total'] = (total_sent, total_rcvd)
713
714
        return {
715
            'down': int(self.stats['reset'] - self.stats['creation']),
716
            'duration': int(time.time() - self.stats['complete']) if self.stats['complete'] else 0,
717
            'local-address': str(self.neighbor.local_address),
718
            'peer-address': str(self.neighbor.peer_address),
719
            'local-as': int(self.neighbor.local_as),
720
            'peer-as': int(self.neighbor.peer_as),
721
            'local-id': str(self.neighbor.router_id),
722
            'peer-id': None if peer['peer-id'] is None else str(peer['router-id']),
723
            'local-hold': int(self.neighbor.hold_time),
724
            'peer-hold': None if peer['hold-time'] is None else int(peer['hold-time']),
725
            'state': self.fsm.name(),
726
            'capabilities': capabilities,
727
            'families': families,
728
            'messages': messages,
729
        }
730