Completed
Push — master ( f8c35a...0dc7c6 )
by Thomas
14:34
created

exabgp.reactor.peer.Peer.sockets()   A

Complexity

Conditions 3

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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