예제 #1
0
파일: peer.py 프로젝트: davidu/exabgp
	def _accept (self):
		# we can do this as Protocol is a mutable object
		proto = self._['in']['proto']

		# send OPEN
		for message in proto.new_open(self._restarted):
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.immediate

		proto.negotiated.sent(message)

		self._['in']['state'] = STATE.opensent

		# Read OPEN
		wait = environment.settings().bgp.openwait
		opentimer = Timer(self._log('in'),wait,1,1,'waited for open too long, we do not like stuck in active')
		# Only yield if we have not the open, otherwise the reactor can run the other connection
		# which would be bad as we need to do the collission check without going to the other peer
		for message in proto.read_open(self.neighbor.peer_address.ip):
			opentimer.tick(message)
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.later

		self._['in']['state'] = STATE.openconfirm
		proto.negotiated.received(message)
		proto.validate_open()

		if self._['out']['state'] == STATE.openconfirm:
			self.logger.network('incoming connection finds the outgoing connection is in openconfirm')
			local_id = self.neighbor.router_id.packed
			remote_id = proto.negotiated.received_open.router_id.packed

			if local_id < remote_id:
				self.logger.network('closing the outgoing connection')
				self._stop('out','collision local id < remote id')
				yield ACTION.later
			else:
				self.logger.network('aborting the incoming connection')
				stop = Interrupted()
				stop.direction = 'in'
				raise stop

		# Send KEEPALIVE
		for message in self._['in']['proto'].new_keepalive('OPENCONFIRM'):
			yield ACTION.immediate

		# Start keeping keepalive timer
		self.timer = Timer(self._log('in'),proto.negotiated.holdtime,4,0)
		# Read KEEPALIVE
		for message in proto.read_keepalive('ESTABLISHED'):
			self.timer.tick(message)
			yield ACTION.later

		self._['in']['state'] = STATE.established
		# let the caller know that we were sucesfull
		yield ACTION.immediate
예제 #2
0
    def _accept(self):
        # we can do this as Protocol is a mutable object
        proto = self._['in']['proto']

        # send OPEN
        for message in proto.new_open(self._restarted):
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.immediate

        proto.negotiated.sent(message)

        self._['in']['state'] = STATE.opensent

        # Read OPEN
        wait = environment.settings().bgp.openwait
        opentimer = Timer(
            self._log('in'), wait, 1, 1,
            'waited for open too long, we do not like stuck in active')
        # Only yield if we have not the open, otherwise the reactor can run the other connection
        # which would be bad as we need to do the collission check without going to the other peer
        for message in proto.read_open(self.neighbor.peer_address.ip):
            opentimer.tick(message)
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.later

        self._['in']['state'] = STATE.openconfirm
        proto.negotiated.received(message)
        proto.validate_open()

        if self._['out']['state'] == STATE.openconfirm:
            self.logger.network(
                'incoming connection finds the outgoing connection is in openconfirm'
            )
            local_id = self.neighbor.router_id.packed
            remote_id = proto.negotiated.received_open.router_id.packed

            if local_id < remote_id:
                self.logger.network('closing the outgoing connection')
                self._reset('out', 'collision local id < remote id')
                yield ACTION.immediate
            else:
                self.logger.network('aborting the incoming connection')
                stop = Interrupted()
                stop.direction = 'in'
                raise stop

        # Send KEEPALIVE
        for message in self._['in']['proto'].new_keepalive('OPENCONFIRM'):
            yield ACTION.immediate

        # Start keeping keepalive timer
        self.timer = Timer(self._log('in'), proto.negotiated.holdtime, 4, 0)
        # Read KEEPALIVE
        for message in proto.read_keepalive('ESTABLISHED'):
            self.timer.tick(message)
            yield ACTION.later

        self._['in']['state'] = STATE.established
        # let the caller know that we were sucesfull
        yield ACTION.immediate
예제 #3
0
파일: peer.py 프로젝트: davidu/exabgp
class Peer (object):
	def __init__ (self,neighbor,reactor):
		try:
			self.logger = Logger()
			# We only to try to connect via TCP once
			self.once = environment.settings().tcp.once
			self.bind = True if environment.settings().tcp.bind else False
		except RuntimeError:
			self.logger = FakeLogger()
			self.once = True

		self.reactor = reactor
		self.neighbor = neighbor
		# The next restart neighbor definition
		self._neighbor = None

		# The peer should restart after a stop
		self._restart = True
		# The peer was restarted (to know what kind of open to send for graceful restart)
		self._restarted = FORCE_GRACEFUL
		self._reset_skip()

		# We want to send all the known routes
		self._resend_routes = SEND.done
		# We have new routes for the peers
		self._have_routes = True

		# We have been asked to teardown the session with this code
		self._teardown = None

		self._ = {'in':{},'out':{}}

		self._['in']['state'] = STATE.idle
		self._['out']['state'] = STATE.idle

		# value to reset 'generator' to
		self._['in']['enabled'] = False
		self._['out']['enabled'] = None if not self.neighbor.passive else False

		# the networking code
		self._['out']['proto'] = None
		self._['in']['proto'] = None

		# the networking code
		self._['out']['code'] = self._connect
		self._['in']['code'] = self._accept

		# the generator used by the main code
		# * False, the generator for this direction is down
		# * Generator, the code to run to connect or accept the connection
		# * None, the generator must be re-created
		self._['in']['generator'] = self._['in']['enabled']
		self._['out']['generator'] = self._['out']['enabled']

		self._generator_keepalive = None

	def _reset (self,direction,message='',error=''):
		self._[direction]['state'] = STATE.idle

		if self._restart:
			if self._[direction]['proto']:
				self._[direction]['proto'].close('%s loop reset %s %s' % (direction,message,str(error)))
			self._[direction]['proto'] = None
			self._[direction]['generator'] = self._[direction]['enabled']
			self._teardown = None
			self._more_skip(direction)
			self.neighbor.rib.reset()

			# If we are restarting, and the neighbor definition is different, update the neighbor
			if self._neighbor:
				self.neighbor = self._neighbor
				self._neighbor = None
		else:
			self._[direction]['generator'] = False
			self._[direction]['proto'] = None

	def _stop (self,direction,message):
		self._[direction]['generator'] = False
		self._[direction]['proto'].close('%s loop stop %s' % (direction,message))
		self._[direction]['proto'] = None

	# connection delay

	def _reset_skip (self):
		# We are currently not skipping connection attempts
		self._skip_time = time.time()
		# when we can not connect to a peer how many time (in loop) should we back-off
		self._next_skip = 0

	def _more_skip (self,direction):
		if direction != 'out':
			return
		self._skip_time = time.time() + self._next_skip
		self._next_skip = int(1+ self._next_skip*1.2)
		if self._next_skip > 60:
			self._next_skip = 60

	# logging

	def me (self,message):
		return "peer %s ASN %-7s %s" % (self.neighbor.peer_address,self.neighbor.peer_as,message)

	def _output (self,direction,message):
		return "%s %s" % (self._[direction]['proto'].connection.name(),self.me(message))

	def _log (self,direction):
		def inner (message):
			return self._output(direction,message)
		return inner

	# control

	def stop (self):
		self._teardown = 3
		self._restart = False
		self._restarted = False
		self._reset_skip()

	def resend (self):
		self._resend_routes = SEND.normal
		self._reset_skip()

	def send_new (self,changes=None,update=None):
		if changes:
			self.neighbor.rib.outgoing.replace(changes)
		self._have_routes = self.neighbor.flush if update is None else update

	def restart (self,restart_neighbor=None):
		# we want to tear down the session and re-establish it
		self._teardown = 3
		self._restart = True
		self._restarted = True
		self._resend_routes = SEND.normal
		self._neighbor = restart_neighbor
		self._reset_skip()

	def teardown (self,code,restart=True):
		self._restart = restart
		self._teardown = code
		self._reset_skip()

	# sockets we must monitor

	def sockets (self):
		ios = []
		for direction in ['in','out']:
			proto = self._[direction]['proto']
			if proto and proto.connection and proto.connection.io:
				ios.append(proto.connection.io)
		return ios

	def incoming (self,connection):
		# if the other side fails, we go back to idle
		if self._['in']['proto'] not in (True,False,None):
			self.logger.network('we already have a peer at this address')
			return False

		self._['in']['proto'] = Protocol(self).accept(connection)
		# Let's make sure we do some work with this connection
		self._['in']['generator'] = None
		self._['in']['state'] = STATE.connect
		return True

	def established (self):
		return self._['in']['state'] == STATE.established or self._['out']['state'] == STATE.established

	def _accept (self):
		# we can do this as Protocol is a mutable object
		proto = self._['in']['proto']

		# send OPEN
		for message in proto.new_open(self._restarted):
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.immediate

		proto.negotiated.sent(message)

		self._['in']['state'] = STATE.opensent

		# Read OPEN
		wait = environment.settings().bgp.openwait
		opentimer = Timer(self._log('in'),wait,1,1,'waited for open too long, we do not like stuck in active')
		# Only yield if we have not the open, otherwise the reactor can run the other connection
		# which would be bad as we need to do the collission check without going to the other peer
		for message in proto.read_open(self.neighbor.peer_address.ip):
			opentimer.tick(message)
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.later

		self._['in']['state'] = STATE.openconfirm
		proto.negotiated.received(message)
		proto.validate_open()

		if self._['out']['state'] == STATE.openconfirm:
			self.logger.network('incoming connection finds the outgoing connection is in openconfirm')
			local_id = self.neighbor.router_id.packed
			remote_id = proto.negotiated.received_open.router_id.packed

			if local_id < remote_id:
				self.logger.network('closing the outgoing connection')
				self._stop('out','collision local id < remote id')
				yield ACTION.later
			else:
				self.logger.network('aborting the incoming connection')
				stop = Interrupted()
				stop.direction = 'in'
				raise stop

		# Send KEEPALIVE
		for message in self._['in']['proto'].new_keepalive('OPENCONFIRM'):
			yield ACTION.immediate

		# Start keeping keepalive timer
		self.timer = Timer(self._log('in'),proto.negotiated.holdtime,4,0)
		# Read KEEPALIVE
		for message in proto.read_keepalive('ESTABLISHED'):
			self.timer.tick(message)
			yield ACTION.later

		self._['in']['state'] = STATE.established
		# let the caller know that we were sucesfull
		yield ACTION.immediate

	def _connect (self):
		# try to establish the outgoing connection

		proto = Protocol(self)
		generator = proto.connect()

		connected = False
		try:
			while not connected:
				connected = generator.next()
				# we want to come back as soon as possible
				yield ACTION.later
		except StopIteration:
			# Connection failed
			if not connected:
				proto.close('connection to peer failed')
			# A connection arrived before we could establish !
			if not connected or self._['in']['proto']:
				stop = Interrupted()
				stop.direction = 'out'
				raise stop

		self._['out']['state'] = STATE.connect
		self._['out']['proto'] = proto

		# send OPEN
		# Only yield if we have not the open, otherwise the reactor can run the other connection
		# which would be bad as we need to set the state without going to the other peer
		for message in proto.new_open(self._restarted):
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.immediate

		proto.negotiated.sent(message)

		self._['out']['state'] = STATE.opensent

		# Read OPEN
		wait = environment.settings().bgp.openwait
		opentimer = Timer(self._log('out'),wait,1,1,'waited for open too long, we do not like stuck in active')
		for message in self._['out']['proto'].read_open(self.neighbor.peer_address.ip):
			opentimer.tick(message)
			# XXX: FIXME: change the whole code to use the ord and not the chr version
			# Only yield if we have not the open, otherwise the reactor can run the other connection
			# which would be bad as we need to do the collission check
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.later

		self._['out']['state'] = STATE.openconfirm
		proto.negotiated.received(message)
		proto.validate_open()

		if self._['in']['state'] == STATE.openconfirm:
			self.logger.network('outgoing connection finds the incoming connection is in openconfirm')
			local_id = self.neighbor.router_id.packed
			remote_id = proto.negotiated.received_open.router_id.packed

			if local_id < remote_id:
				self.logger.network('aborting the outgoing connection')
				stop = Interrupted()
				stop.direction = 'out'
				raise stop
			else:
				self.logger.network('closing the incoming connection')
				self._stop('in','collision local id < remote id')
				yield ACTION.later

		# Send KEEPALIVE
		for message in proto.new_keepalive('OPENCONFIRM'):
			yield ACTION.immediate

		# Start keeping keepalive timer
		self.timer = Timer(self._log('out'),self._['out']['proto'].negotiated.holdtime,4,0)
		# Read KEEPALIVE
		for message in self._['out']['proto'].read_keepalive('ESTABLISHED'):
			self.timer.tick(message)
			yield ACTION.immediate

		self._['out']['state'] = STATE.established
		# let the caller know that we were sucesfull
		yield ACTION.immediate

	def _keepalive (self,direction):
		# yield :
		#  True  if we just sent the keepalive
		#  None  if we are working as we should
		#  False if something went wrong

		yield 'ready'

		need_keepalive = False
		generator = None
		last = NOP

		while not self._teardown:
			# SEND KEEPALIVES
			need_keepalive |= self.timer.keepalive()

			if need_keepalive and not generator:
				proto = self._[direction]['proto']
				if not proto:
					yield False
					break
				generator = proto.new_keepalive()
				need_keepalive = False

			if generator:
				try:
					last = generator.next()
					if last.TYPE == KeepAlive.TYPE:
						# close the generator and rasie a StopIteration
						generator.next()
					yield None
				except (NetworkError,ProcessError):
					yield False
					break
				except StopIteration:
					generator = None
					if last.TYPE != KeepAlive.TYPE:
						self._generator_keepalive = False
						yield False
						break
					yield True
			else:
				yield None

	def keepalive (self):
		generator = self._generator_keepalive
		if generator:
			# XXX: CRITICAL : this code needs the same exception than the one protecting the main loop
			try:
				return generator.next()
			except StopIteration:
				pass
		return self._generator_keepalive is None

	def _main (self,direction):
		"yield True if we want to come back to it asap, None if nothing urgent, and False if stopped"

		if self._teardown:
			raise Notify(6,3)

		proto = self._[direction]['proto']

		# Initialise the keepalive
		self._generator_keepalive = self._keepalive(direction)

		# Announce to the process BGP is up
		self.logger.network('Connected to peer %s (%s)' % (self.neighbor.name(),direction))
		if self.neighbor.api.neighbor_changes:
			try:
				self.reactor.processes.up(self)
			except ProcessError:
				# Can not find any better error code than 6,0 !
				# XXX: We can not restart the program so this will come back again and again - FIX
				# XXX: In the main loop we do exit on this kind of error
				raise Notify(6,0,'ExaBGP Internal error, sorry.')

		send_eor = True
		new_routes = None
		self._resend_routes = SEND.normal
		send_families = []

		# Every last asm message should be re-announced on restart
		for family in self.neighbor.asm:
			if family in self.neighbor.families():
				self.neighbor.messages.appendleft(self.neighbor.asm[family])

		counter = Counter(self.logger,self._log(direction))
		operational = None
		refresh = None
		number = 0

		while not self._teardown:
			for message in proto.read_message():
				# Update timer
				self.timer.tick(message)

				# Give information on the number of routes seen
				counter.display()

				# Received update
				if message.TYPE == Update.TYPE:
					counter.increment(len(message.nlris))
					number += 1

					self.logger.routes(LazyFormat(self.me('<< UPDATE (%d)' % number),lambda _: "%s%s" % (' attributes' if _ else '',_),message.attributes))

					for nlri in message.nlris:
						self.neighbor.rib.incoming.insert_received(Change(nlri,message.attributes))
						self.logger.routes(LazyFormat(self.me('<< UPDATE (%d) nlri ' % number),str,nlri))

				elif message.TYPE == RouteRefresh.TYPE:
					if message.reserved == RouteRefresh.request:
						self._resend_routes = SEND.refresh
						send_families.append((message.afi,message.safi))

				# SEND OPERATIONAL
				if self.neighbor.operational:
					if not operational:
						new_operational = self.neighbor.messages.popleft() if self.neighbor.messages else None
						if new_operational:
							operational = proto.new_operational(new_operational,proto.negotiated)

					if operational:
						try:
							operational.next()
						except StopIteration:
							operational = None

				# SEND REFRESH
				if self.neighbor.route_refresh:
					if not refresh:
						new_refresh = self.neighbor.refresh.popleft() if self.neighbor.refresh else None
						if new_refresh:
							enhanced_negotiated = True if proto.negotiated.refresh == REFRESH.enhanced else False
							refresh = proto.new_refresh(new_refresh,enhanced_negotiated)

					if refresh:
						try:
							refresh.next()
						except StopIteration:
							refresh = None

				# Take the routes already sent to that peer and resend them
				if self._resend_routes != SEND.done:
					enhanced_refresh = True if self._resend_routes == SEND.refresh and proto.negotiated.refresh == REFRESH.enhanced else False
					self._resend_routes = SEND.done
					self.neighbor.rib.outgoing.resend(send_families,enhanced_refresh)
					self._have_routes = True
					send_families = []

				# Need to send update
				if self._have_routes and not new_routes:
					self._have_routes = False
					# XXX: in proto really. hum to think about ?
					new_routes = proto.new_update()

				if new_routes:
					try:
						count = 20
						while count:
							# This can raise a NetworkError
							new_routes.next()
							count -= 1
					except StopIteration:
						new_routes = None

				elif send_eor:
					send_eor = False
					for eor in proto.new_eors():
						yield ACTION.immediate
					self.logger.message(self.me('>> EOR(s)'))

				# Go to other Peers
				yield ACTION.immediate if new_routes or message.TYPE != NOP.TYPE or self.neighbor.messages else ACTION.later

				# read_message will loop until new message arrives with NOP
				if self._teardown:
					break

		# If graceful restart, silent shutdown
		if self.neighbor.graceful_restart and proto.negotiated.sent_open.capabilities.announced(CapabilityID.GRACEFUL_RESTART):
			self.logger.network('Closing the session without notification','error')
			proto.close('graceful restarted negotiated, closing without sending any notification')
			raise NetworkError('closing')

		# notify our peer of the shutdown
		raise Notify(6,self._teardown)

	def _run (self,direction):
		"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"
		try:
			for action in self._[direction]['code']():
				yield action

			for action in self._main(direction):
				yield action

		# CONNECTION FAILURE
		except NetworkError, e:
			self._reset(direction,'closing connection',e)

			# we tried to connect once, it failed, we stop
			if self.once:
				self.logger.network('only one attempt to connect is allowed, stopping the peer')
				self.stop()
			return

		# NOTIFY THE PEER OF AN ERROR
		except Notify, n:
			for direction in ['in','out']:
				if self._[direction]['proto']:
					try:
						generator = self._[direction]['proto'].new_notification(n)
						try:
							maximum = 20
							while maximum:
								generator.next()
								maximum -= 1
								yield ACTION.immediate if maximum > 10 else ACTION.later
						except StopIteration:
							pass
					except (NetworkError,ProcessError):
						self.logger.network(self._output(direction,'NOTIFICATION NOT SENT'),'error')
						pass
					self._reset(direction,'notification sent (%d,%d)' % (n.code,n.subcode),n)
				else:
					self._reset(direction)
			return
예제 #4
0
파일: peer.py 프로젝트: davidu/exabgp
	def _connect (self):
		# try to establish the outgoing connection

		proto = Protocol(self)
		generator = proto.connect()

		connected = False
		try:
			while not connected:
				connected = generator.next()
				# we want to come back as soon as possible
				yield ACTION.later
		except StopIteration:
			# Connection failed
			if not connected:
				proto.close('connection to peer failed')
			# A connection arrived before we could establish !
			if not connected or self._['in']['proto']:
				stop = Interrupted()
				stop.direction = 'out'
				raise stop

		self._['out']['state'] = STATE.connect
		self._['out']['proto'] = proto

		# send OPEN
		# Only yield if we have not the open, otherwise the reactor can run the other connection
		# which would be bad as we need to set the state without going to the other peer
		for message in proto.new_open(self._restarted):
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.immediate

		proto.negotiated.sent(message)

		self._['out']['state'] = STATE.opensent

		# Read OPEN
		wait = environment.settings().bgp.openwait
		opentimer = Timer(self._log('out'),wait,1,1,'waited for open too long, we do not like stuck in active')
		for message in self._['out']['proto'].read_open(self.neighbor.peer_address.ip):
			opentimer.tick(message)
			# XXX: FIXME: change the whole code to use the ord and not the chr version
			# Only yield if we have not the open, otherwise the reactor can run the other connection
			# which would be bad as we need to do the collission check
			if ord(message.TYPE) == Message.Type.NOP:
				yield ACTION.later

		self._['out']['state'] = STATE.openconfirm
		proto.negotiated.received(message)
		proto.validate_open()

		if self._['in']['state'] == STATE.openconfirm:
			self.logger.network('outgoing connection finds the incoming connection is in openconfirm')
			local_id = self.neighbor.router_id.packed
			remote_id = proto.negotiated.received_open.router_id.packed

			if local_id < remote_id:
				self.logger.network('aborting the outgoing connection')
				stop = Interrupted()
				stop.direction = 'out'
				raise stop
			else:
				self.logger.network('closing the incoming connection')
				self._stop('in','collision local id < remote id')
				yield ACTION.later

		# Send KEEPALIVE
		for message in proto.new_keepalive('OPENCONFIRM'):
			yield ACTION.immediate

		# Start keeping keepalive timer
		self.timer = Timer(self._log('out'),self._['out']['proto'].negotiated.holdtime,4,0)
		# Read KEEPALIVE
		for message in self._['out']['proto'].read_keepalive('ESTABLISHED'):
			self.timer.tick(message)
			yield ACTION.immediate

		self._['out']['state'] = STATE.established
		# let the caller know that we were sucesfull
		yield ACTION.immediate
예제 #5
0
class Peer(object):
    def __init__(self, neighbor, reactor):
        try:
            self.logger = Logger()
            # We only to try to connect via TCP once
            self.once = environment.settings().tcp.once
            self.bind = True if environment.settings().tcp.bind else False
        except RuntimeError:
            self.logger = FakeLogger()
            self.once = True

        self.reactor = reactor
        self.neighbor = neighbor
        # The next restart neighbor definition
        self._neighbor = None

        # The peer should restart after a stop
        self._restart = True
        # The peer was restarted (to know what kind of open to send for graceful restart)
        self._restarted = FORCE_GRACEFUL
        self._reset_skip()

        # We want to send all the known routes
        self._resend_routes = True
        # We have new routes for the peers
        self._have_routes = True

        # We have been asked to teardown the session with this code
        self._teardown = None

        self._ = {'in': {}, 'out': {}}

        self._['in']['state'] = STATE.idle
        self._['out']['state'] = STATE.idle

        # value to reset 'generator' to
        self._['in']['enabled'] = False
        self._['out']['enabled'] = None if not self.neighbor.passive else False

        # the networking code
        self._['out']['proto'] = None
        self._['in']['proto'] = None

        # the networking code
        self._['out']['code'] = self._connect
        self._['in']['code'] = self._accept

        # the generator used by the main code
        # * False, the generator for this direction is down
        # * Generator, the code to run to connect or accept the connection
        # * None, the generator must be re-created
        self._['in']['generator'] = self._['in']['enabled']
        self._['out']['generator'] = self._['out']['enabled']

    def _reset(self, direction, message='', error=''):
        self._[direction]['state'] = STATE.idle

        if self._restart:
            if self._[direction]['proto']:
                self._[direction]['proto'].close(
                    '%s loop reset %s %s' % (direction, message, str(error)))
            self._[direction]['proto'] = None
            self._[direction]['generator'] = self._[direction]['enabled']
            self._teardown = None
            self._more_skip(direction)

            # If we are restarting, and the neighbor definition is different, update the neighbor
            if self._neighbor:
                self.neighbor = self._neighbor
                self._neighbor = None
        else:
            self._[direction]['generator'] = False
            self._[direction]['proto'] = None

    # connection delay

    def _reset_skip(self):
        # We are currently not skipping connection attempts
        self._skip_time = time.time()
        # when we can not connect to a peer how many time (in loop) should we back-off
        self._next_skip = 0

    def _more_skip(self, direction):
        if direction != 'out':
            return
        self._skip_time = time.time() + self._next_skip
        self._next_skip = int(1 + self._next_skip * 1.2)
        if self._next_skip > 60:
            self._next_skip = 60

    # logging

    def me(self, message):
        return "peer %s ASN %-7s %s" % (self.neighbor.peer_address,
                                        self.neighbor.peer_as, message)

    def _output(self, direction, message):
        return "%s %s" % (self._[direction]['proto'].connection.name(),
                          self.me(message))

    def _log(self, direction):
        def inner(message):
            return self._output(direction, message)

        return inner

    # control

    def stop(self):
        self._teardown = 3
        self._restart = False
        self._restarted = False
        self._reset_skip()

    def resend(self):
        self._resend_routes = True
        self._reset_skip()

    def send_new(self):
        self._have_routes = True

    def restart(self, restart_neighbor=None):
        # we want to tear down the session and re-establish it
        self._teardown = 3
        self._restart = True
        self._restarted = True
        self._resend_routes = True
        self._neighbor = restart_neighbor
        self._reset_skip()

    def teardown(self, code, restart=True):
        self._restart = restart
        self._teardown = code
        self._reset_skip()

    # sockets we must monitor

    def sockets(self):
        ios = []
        for direction in ['in', 'out']:
            proto = self._[direction]['proto']
            if proto and proto.connection and proto.connection.io:
                ios.append(proto.connection.io)
        return ios

    def incoming(self, connection):
        # if the other side fails, we go back to idle
        if self._['in']['proto'] not in (True, False, None):
            self.logger.network('we already have a peer at this address')
            return False

        self._['in']['proto'] = Protocol(self).accept(connection)
        # Let's make sure we do some work with this connection
        self._['in']['generator'] = None
        self._['in']['state'] = STATE.connect
        return True

    def _accept(self):
        # we can do this as Protocol is a mutable object
        proto = self._['in']['proto']

        # send OPEN
        for message in proto.new_open(self._restarted):
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.immediate

        proto.negotiated.sent(message)

        self._['in']['state'] = STATE.opensent

        # Read OPEN
        wait = environment.settings().bgp.openwait
        opentimer = Timer(
            self._log('in'), wait, 1, 1,
            'waited for open too long, we do not like stuck in active')
        # Only yield if we have not the open, otherwise the reactor can run the other connection
        # which would be bad as we need to do the collission check without going to the other peer
        for message in proto.read_open(self.neighbor.peer_address.ip):
            opentimer.tick(message)
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.later

        self._['in']['state'] = STATE.openconfirm
        proto.negotiated.received(message)
        proto.validate_open()

        if self._['out']['state'] == STATE.openconfirm:
            self.logger.network(
                'incoming connection finds the outgoing connection is in openconfirm'
            )
            local_id = self.neighbor.router_id.packed
            remote_id = proto.negotiated.received_open.router_id.packed

            if local_id < remote_id:
                self.logger.network('closing the outgoing connection')
                self._reset('out', 'collision local id < remote id')
                yield ACTION.immediate
            else:
                self.logger.network('aborting the incoming connection')
                stop = Interrupted()
                stop.direction = 'in'
                raise stop

        # Send KEEPALIVE
        for message in self._['in']['proto'].new_keepalive('OPENCONFIRM'):
            yield ACTION.immediate

        # Start keeping keepalive timer
        self.timer = Timer(self._log('in'), proto.negotiated.holdtime, 4, 0)
        # Read KEEPALIVE
        for message in proto.read_keepalive('ESTABLISHED'):
            self.timer.tick(message)
            yield ACTION.later

        self._['in']['state'] = STATE.established
        # let the caller know that we were sucesfull
        yield ACTION.immediate

    def _connect(self):
        # try to establish the outgoing connection

        proto = Protocol(self)
        generator = proto.connect()

        connected = False
        try:
            while not connected:
                connected = generator.next()
                # we want to come back as soon as possible
                yield ACTION.immediate
        except StopIteration:
            # Connection failed
            if not connected:
                proto.close('connection to peer failed')
            # A connection arrived before we could establish !
            if not connected or self._['in']['proto']:
                stop = Interrupted()
                stop.direction = 'out'
                raise stop

        self._['out']['state'] = STATE.connect
        self._['out']['proto'] = proto

        # send OPEN
        # Only yield if we have not the open, otherwise the reactor can run the other connection
        # which would be bad as we need to set the state without going to the other peer
        for message in proto.new_open(self._restarted):
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.immediate

        proto.negotiated.sent(message)

        self._['out']['state'] = STATE.opensent

        # Read OPEN
        wait = environment.settings().bgp.openwait
        opentimer = Timer(
            self._log('out'), wait, 1, 1,
            'waited for open too long, we do not like stuck in active')
        for message in self._['out']['proto'].read_open(
                self.neighbor.peer_address.ip):
            opentimer.tick(message)
            # XXX: FIXME: change the whole code to use the ord and not the chr version
            # Only yield if we have not the open, otherwise the reactor can run the other connection
            # which would be bad as we need to do the collission check
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.later

        self._['out']['state'] = STATE.openconfirm
        proto.negotiated.received(message)
        proto.validate_open()

        if self._['in']['state'] == STATE.openconfirm:
            self.logger.network(
                'outgoing connection finds the incoming connection is in openconfirm'
            )
            local_id = self.neighbor.router_id.packed
            remote_id = proto.negotiated.received_open.router_id.packed

            if local_id < remote_id:
                self.logger.network('aborting the outgoing connection')
                stop = Interrupted()
                stop.direction = 'out'
                raise stop
            else:
                self.logger.network('closing the incoming connection')
                self._reset('in', 'collision local id < remote id')
                yield ACTION.immediate

        # Send KEEPALIVE
        for message in proto.new_keepalive('OPENCONFIRM'):
            yield ACTION.immediate

        # Start keeping keepalive timer
        self.timer = Timer(self._log('out'),
                           self._['out']['proto'].negotiated.holdtime, 4, 0)
        # Read KEEPALIVE
        for message in self._['out']['proto'].read_keepalive('ESTABLISHED'):
            self.timer.tick(message)
            yield ACTION.later

        self._['out']['state'] = STATE.established
        # let the caller know that we were sucesfull
        yield ACTION.immediate

    def _main(self, direction):
        "yield True if we want to come back to it asap, None if nothing urgent, and False if stopped"

        if self._teardown:
            raise Notify(6, 3)

        proto = self._[direction]['proto']

        # Announce to the process BGP is up
        self.logger.network('Connected to peer %s (%s)' %
                            (self.neighbor.name(), direction))
        if self.neighbor.api.neighbor_changes:
            try:
                self.reactor.processes.up(self.neighbor.peer_address)
            except ProcessError:
                # Can not find any better error code than 6,0 !
                # XXX: We can not restart the program so this will come back again and again - FIX
                # XXX: In the main loop we do exit on this kind of error
                raise Notify(6, 0, 'ExaBGP Internal error, sorry.')

        send_eor = True
        new_routes = None
        self._resend_routes = True

        # Every last asm message should be re-announced on restart
        for family in self.neighbor.asm:
            if family in self.neighbor.families():
                self.neighbor.messages.appendleft(self.neighbor.asm[family])

        counter = Counter(self.logger, self._log(direction))
        need_keepalive = False
        keepalive = None
        operational = None

        while not self._teardown:
            for message in proto.read_message():
                # Update timer
                self.timer.tick(message)

                # Give information on the number of routes seen
                counter.display()

                # Received update
                if message.TYPE == Update.TYPE:
                    counter.increment(len(message.nlris))

                    for nlri in message.nlris:
                        self.neighbor.rib.incoming.insert_received(
                            Change(nlri, message.attributes))
                        self.logger.routes(LazyFormat(self.me(''), str, nlri))

                # SEND KEEPALIVES
                need_keepalive |= self.timer.keepalive()

                if need_keepalive and not keepalive:
                    keepalive = proto.new_keepalive()
                    need_keepalive = False

                if keepalive:
                    try:
                        keepalive.next()
                    except StopIteration:
                        keepalive = None

                # SEND OPERATIONAL
                if self.neighbor.operational:
                    if not operational:
                        new_operational = self.neighbor.messages.popleft(
                        ) if self.neighbor.messages else None
                        if new_operational:
                            operational = proto.new_operational(
                                new_operational)

                    if operational:
                        try:
                            operational.next()
                        except StopIteration:
                            operational = None

                # Take the routes already sent to that peer and resend them
                if self._resend_routes:
                    self._resend_routes = False
                    self.neighbor.rib.outgoing.resend_known()
                    self._have_routes = True

                # Need to send update
                if self._have_routes and not new_routes:
                    self._have_routes = False
                    # XXX: in proto really. hum to think about ?
                    new_routes = proto.new_update()

                if new_routes:
                    try:
                        count = 20
                        while count:
                            # This can raise a NetworkError
                            new_routes.next()
                            count -= 1
                    except StopIteration:
                        new_routes = None

                elif send_eor:
                    send_eor = False
                    for eor in proto.new_eors():
                        yield ACTION.immediate
                    self.logger.message(self.me('>> EOR(s)'))

                # Go to other Peers
                yield ACTION.immediate if new_routes or message.TYPE != NOP.TYPE else ACTION.later

                # read_message will loop until new message arrives with NOP
                if self._teardown:
                    break

        # If graceful restart, silent shutdown
        if self.neighbor.graceful_restart and proto.negotiated.sent_open.capabilities.announced(
                CapabilityID.GRACEFUL_RESTART):
            self.logger.network('Closing the session without notification',
                                'error')
            proto.close(
                'graceful restarted negotiated, closing without sending any notification'
            )
            raise NetworkError('closing')

        # notify our peer of the shutdown
        raise Notify(6, self._teardown)

    def _run(self, direction):
        "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"
        try:
            for action in self._[direction]['code']():
                yield action

            for action in self._main(direction):
                yield action

        # CONNECTION FAILURE
        except NetworkError, e:
            self._reset(direction, 'closing connection')

            # we tried to connect once, it failed, we stop
            if self.once:
                self.logger.network(
                    'only one attempt to connect is allowed, stoping the peer')
                self.stop()
            return

        # NOTIFY THE PEER OF AN ERROR
        except Notify, n:
            for direction in ['in', 'out']:
                if self._[direction]['proto']:
                    try:
                        self._[direction]['proto'].new_notification(n)
                    except (NetworkError, ProcessError):
                        self.logger.network(
                            self._output(direction, 'NOTIFICATION NOT SENT',
                                         'error'))
                        pass
                    self._reset(
                        direction,
                        'notification sent (%d,%d)' % (n.code, n.subcode), n)
                else:
                    self._reset(direction)
            return
예제 #6
0
    def _connect(self):
        # try to establish the outgoing connection

        proto = Protocol(self)
        generator = proto.connect()

        connected = False
        try:
            while not connected:
                connected = generator.next()
                # we want to come back as soon as possible
                yield ACTION.immediate
        except StopIteration:
            # Connection failed
            if not connected:
                proto.close('connection to peer failed')
            # A connection arrived before we could establish !
            if not connected or self._['in']['proto']:
                stop = Interrupted()
                stop.direction = 'out'
                raise stop

        self._['out']['state'] = STATE.connect
        self._['out']['proto'] = proto

        # send OPEN
        # Only yield if we have not the open, otherwise the reactor can run the other connection
        # which would be bad as we need to set the state without going to the other peer
        for message in proto.new_open(self._restarted):
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.immediate

        proto.negotiated.sent(message)

        self._['out']['state'] = STATE.opensent

        # Read OPEN
        wait = environment.settings().bgp.openwait
        opentimer = Timer(
            self._log('out'), wait, 1, 1,
            'waited for open too long, we do not like stuck in active')
        for message in self._['out']['proto'].read_open(
                self.neighbor.peer_address.ip):
            opentimer.tick(message)
            # XXX: FIXME: change the whole code to use the ord and not the chr version
            # Only yield if we have not the open, otherwise the reactor can run the other connection
            # which would be bad as we need to do the collission check
            if ord(message.TYPE) == Message.Type.NOP:
                yield ACTION.later

        self._['out']['state'] = STATE.openconfirm
        proto.negotiated.received(message)
        proto.validate_open()

        if self._['in']['state'] == STATE.openconfirm:
            self.logger.network(
                'outgoing connection finds the incoming connection is in openconfirm'
            )
            local_id = self.neighbor.router_id.packed
            remote_id = proto.negotiated.received_open.router_id.packed

            if local_id < remote_id:
                self.logger.network('aborting the outgoing connection')
                stop = Interrupted()
                stop.direction = 'out'
                raise stop
            else:
                self.logger.network('closing the incoming connection')
                self._reset('in', 'collision local id < remote id')
                yield ACTION.immediate

        # Send KEEPALIVE
        for message in proto.new_keepalive('OPENCONFIRM'):
            yield ACTION.immediate

        # Start keeping keepalive timer
        self.timer = Timer(self._log('out'),
                           self._['out']['proto'].negotiated.holdtime, 4, 0)
        # Read KEEPALIVE
        for message in self._['out']['proto'].read_keepalive('ESTABLISHED'):
            self.timer.tick(message)
            yield ACTION.later

        self._['out']['state'] = STATE.established
        # let the caller know that we were sucesfull
        yield ACTION.immediate