Beispiel #1
0
 def _backlog(self, maximum=0):
     backlog = self._messages.get(self.neighbor.peer_as, [])
     if backlog:
         if not self._frozen:
             self._frozen = time.time()
         if self._frozen and self._frozen + (
                 self.neighbor.hold_time) < time.time():
             raise Failure(
                 'peer %s not reading on socket - killing session' %
                 self.neighbor.peer_as)
         logger.message(
             self.me(
                 "unable to send route for %d second (maximum allowed %d)" %
                 (time.time() - self._frozen, self.neighbor.hold_time)))
         nb_backlog = len(backlog)
         if nb_backlog > MAX_BACKLOG:
             raise Failure(
                 'over %d routes buffered for peer %s - killing session' %
                 (MAX_BACKLOG, self.neighbor.peer_as))
         logger.message(
             self.me("backlog of %d/%d routes" % (nb_backlog, MAX_BACKLOG)))
     count = 0
     while backlog:
         count += 1
         name, update = backlog[0]
         written = self.connection.write(update)
         if not written:
             break
         logger.message(self.me(">> DEBUFFERED %s" % name))
         backlog.pop(0)
         self._frozen = 0
         yield count
         if maximum and count >= maximum:
             break
     self._messages[self.neighbor.peer_as] = backlog
Beispiel #2
0
	def read (self,number):
		if not self.io:
			raise Failure('Trying to read on a closed TCP connection')
		if number == 0: return ''
		try:
			r = self.io.recv(number)
			self.last_read = time.time()
			logger.wire(LazyFormat("%15s RECV " % self.peer,hexa,r))
			return r
		except socket.timeout,e:
			self.close()
			raise Failure('Timeout while reading data from the network:  %s ' % str(e))
Beispiel #3
0
class Connection (object):
	def __init__ (self,peer,local,md5,ttl):
		self.io = None
		self.last_read = 0
		self.last_write = 0
		self.peer = peer
		self._loop_start = None

		self._buffer = []

		logger.wire("Opening connection to %s" % self.peer)

		if peer.afi != local.afi:
			raise Failure('The local IP and peer IP must be of the same family (both IPv4 or both IPv6)')

		try:
			if peer.afi == AFI.ipv4:
				self.io = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
			if peer.afi == AFI.ipv6:
				self.io = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)
			try:
				self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
			except AttributeError:
				pass
			try:
				self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
			except AttributeError:
				pass
			try:
				# diable Nagle's algorithm (no grouping of packets)
				self.io.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
			except AttributeError:
				logger.warning("wire","Could not disable nagle's algorithm for %s" % self.peer)
				pass
			self.io.settimeout(1)
			if peer.afi == AFI.ipv4:
				self.io.bind((local.ip,0))
			if peer.afi == AFI.ipv6:
				self.io.bind((local.ip,0,0,0))
		except socket.error,e:
			self.close()
			raise Failure('Could not bind to local ip %s - %s' % (local.ip,str(e)))

		if md5:
			try:
				TCP_MD5SIG = 14
				TCP_MD5SIG_MAXKEYLEN = 80
				SS_PADSIZE = 120

				n_addr = socket.inet_aton(peer.ip)
				n_port = socket.htons(179)
				tcp_md5sig = 'HH4s%dx2xH4x%ds' % (SS_PADSIZE, TCP_MD5SIG_MAXKEYLEN)
				md5sig = struct.pack(tcp_md5sig, socket.AF_INET, n_port, n_addr, len(md5), md5)
				self.io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, md5sig)
			except socket.error,e:
				self.close()
				raise Failure('This OS does not support TCP_MD5SIG, you can not use MD5 : %s' % str(e))
Beispiel #4
0
	def __init__ (self,peer,local,md5,ttl):
		self.io = None
		self.last_read = 0
		self.last_write = 0
		self.peer = peer
		self._loop_start = None

		self._buffer = []

		logger.wire("Opening connection to %s" % self.peer)

		if peer.afi != local.afi:
			raise Failure('The local IP and peer IP must be of the same family (both IPv4 or both IPv6)')

		try:
			if peer.afi == AFI.ipv4:
				self.io = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
			if peer.afi == AFI.ipv6:
				self.io = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)
			try:
				self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
			except AttributeError:
				pass
			try:
				self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
			except AttributeError:
				pass
			try:
				# diable Nagle's algorithm (no grouping of packets)
				self.io.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
			except AttributeError:
				logger.warning("wire","Could not disable nagle's algorithm for %s" % self.peer)
				pass
			self.io.settimeout(1)
			if peer.afi == AFI.ipv4:
				self.io.bind((local.ip,0))
			if peer.afi == AFI.ipv6:
				self.io.bind((local.ip,0,0,0))
		except socket.error,e:
			self.close()
			raise Failure('Could not bind to local ip %s - %s' % (local.ip,str(e)))
Beispiel #5
0
    def new_open(self, restarted, asn4):
        if asn4:
            asn = self.neighbor.local_as
        else:
            asn = AS_TRANS

        o = Open(4, asn, self.neighbor.router_id.ip,
                 Capabilities().default(self.neighbor, restarted),
                 self.neighbor.hold_time)

        if not self.connection.write(o.message()):
            raise Failure('Could not send open')
        return o
Beispiel #6
0
 def chunked(generator, size):
     chunk = ''
     for data in generator:
         if len(data) > size:
             raise Failure(
                 'Can not send BGP update larger than %d bytes on this connection.'
                 % size)
         if len(chunk) + len(data) <= size:
             chunk += data
             continue
         yield chunk
         chunk = data
     if chunk:
         yield chunk
Beispiel #7
0
	def pending (self,reset=False):
		if reset:
			self._loop_start = None
		else:
			if not self._loop_start:
				self._loop_start = time.time()
			else:
				if self._loop_start + READ_TIMEOUT < time.time():
					raise Failure('Waited for data on a socket for more than %d second(s)' % READ_TIMEOUT)
		try:
			r,_,_ = select.select([self.io,],[],[],0)
		except select.error,e:
			errno,message = e.args
			if errno in errno_block:
				return False
			raise
Beispiel #8
0
    def close(self):
        #self._delta.last = 0
        if self.connection:
            # must be first otherwise we could have a loop caused by the raise in the below
            self.connection.close()
            self.connection = None

            message = 'neighbor %s down\n' % self.peer.neighbor.peer_address
            try:
                proc = self.peer.supervisor.processes
                for name in proc.notify(self.neighbor.peer_address):
                    proc.write(name, message)
            except ProcessError:
                raise Failure(
                    'Could not send message(s) to helper program(s) : %s' %
                    message)
Beispiel #9
0
    def connect(self):
        # allows to test the protocol code using modified StringIO with a extra 'pending' function
        if not self.connection:
            peer = self.neighbor.peer_address
            local = self.neighbor.local_address
            md5 = self.neighbor.md5
            ttl = self.neighbor.ttl
            self.connection = Connection(peer, local, md5, ttl)

            message = 'neighbor %s connected\n' % self.peer.neighbor.peer_address
            try:
                proc = self.peer.supervisor.processes
                for name in proc.notify(self.neighbor.peer_address):
                    proc.write(name, message)
            except ProcessError:
                raise Failure(
                    'Could not send message(s) to helper program(s) : %s' %
                    message)
Beispiel #10
0
	def write (self,data):
		if not self.io:
			# We alrady returned a Failure
			# It must be a write attempted during the closing of the peering session
			# Make sure it does not hold the cleanup.
			return True
		if not self.ready():
			return False
		try:
			logger.wire(LazyFormat("%15s SENT " % self.peer,hexa,data))
			self.io.sendall(data)
			self.last_write = time.time()
			return True
		except socket.error, e:
			# Must never happen as we are performing a select before the write
			#failure = getattr(e,'errno',None)
			#if failure in errno_block:
			#	return False
			self.close()
			logger.wire("%15s %s" % (self.peer,trace()))
			raise Failure('Problem while writing data to the network: %s' % str(e))
Beispiel #11
0
				n_addr = socket.inet_aton(peer.ip)
				n_port = socket.htons(179)
				tcp_md5sig = 'HH4s%dx2xH4x%ds' % (SS_PADSIZE, TCP_MD5SIG_MAXKEYLEN)
				md5sig = struct.pack(tcp_md5sig, socket.AF_INET, n_port, n_addr, len(md5), md5)
				self.io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, md5sig)
			except socket.error,e:
				self.close()
				raise Failure('This OS does not support TCP_MD5SIG, you can not use MD5 : %s' % str(e))

		# None (ttl-security unset) or zero (maximum TTL) is the same thing
		if ttl:
			try:
				self.io.setsockopt(socket.IPPROTO_IP,socket.IP_TTL, 20)
			except socket.error,e:
				self.close()
				raise Failure('This OS does not support IP_TTL (ttl-security), you can not use MD5 : %s' % str(e))

		try:
			if peer.afi == AFI.ipv4:
				self.io.connect((peer.ip,179))
			if peer.afi == AFI.ipv6:
				self.io.connect((peer.ip,179,0,0))
			self.io.setblocking(0)
		except socket.error, e:
			self.close()
			raise Failure('Could not connect to peer: %s' % str(e))

		try:
			try:
				# Linux / Windows
				self.message_size = self.io.getsockopt(socket.SOL_SOCKET, socket.SO_MAX_MSG_SIZE)
Beispiel #12
0
    def read_message(self):
        # This call reset the time for the timeout in
        if not self.connection.pending(True):
            return NOP('')

        length = 19
        data = ''
        while length:
            if self.connection.pending():
                delta = self.connection.read(length)
                data += delta
                length -= len(delta)
                # The socket is closed
                if not data:
                    raise Failure('The TCP connection is closed')

        if data[:16] != Message.MARKER:
            # We are speaking BGP - send us a valid Marker
            raise Notify(1, 1,
                         'The packet received does not contain a BGP marker')

        raw_length = data[16:18]
        length = unpack('!H', raw_length)[0]
        msg = data[18]

        if (length < 19 or length > 4096):
            # BAD Message Length
            raise Notify(1, 2)

        if ((msg == Open.TYPE and length < 29)
                or (msg == Update.TYPE and length < 23)
                or (msg == Notification.TYPE and length < 21)
                or (msg == KeepAlive.TYPE and length != 19)):
            # MUST send the faulty length back
            raise Notify(1, 2, raw_length)
            #(msg == RouteRefresh.TYPE and length != 23)

        length -= 19
        data = ''
        while length:
            if self.connection.pending():
                delta = self.connection.read(length)
                data += delta
                length -= len(delta)
                # The socket is closed
                if not data:
                    raise Failure('The TCP connection is closed')

        if msg == Notification.TYPE:
            raise Notification(ord(data[0]), ord(data[1]))

        if msg == KeepAlive.TYPE:
            return self.KeepAliveFactory(data)

        if msg == Open.TYPE:
            return self.OpenFactory(data)

        if msg == Update.TYPE:
            if self.neighbor.parse_routes:
                update = self.UpdateFactory(data)
                return update
            else:
                return NOP('')

        if self.strict:
            raise Notify(1, 3, msg)

        return NOP(data)
Beispiel #13
0
    def _run(self, max_wait_open=10.0):
        try:
            if self.supervisor.processes.broken(self.neighbor.peer_address):
                # XXX: we should perhaps try to restart the process ??
                raise Failure(
                    'ExaBGP lost the helper process for this peer - peer down')

            self.bgp = Protocol(self)
            self.bgp.connect()

            self._reset_skip()

            _open = self.bgp.new_open(self._restarted, self._asn4)
            logger.message(self.me('>> %s' % _open))
            yield None

            start = time.time()
            while True:
                self.open = self.bgp.read_open(_open,
                                               self.neighbor.peer_address.ip)
                if time.time() - start > max_wait_open:
                    logger.message(
                        self.
                        me('Waited for an OPEN for too long - killing the session'
                           ))
                    raise Notify(
                        1, 1,
                        'The client took over %s seconds to send the OPEN, closing'
                        % str(max_wait_open))
                # OPEN or NOP
                if self.open.TYPE == NOP.TYPE:
                    yield None
                    continue
                # This test is already done in read_open
                #if self.open.TYPE != Open.TYPE:
                #	raise Notify(5,1,'We are expecting an OPEN message')
                logger.message(self.me('<< %s' % self.open))
                if not self.open.capabilities.announced(
                        Capabilities.FOUR_BYTES_ASN) and _open.asn.asn4():
                    self._asn4 = False
                    raise Notify(
                        2, 0,
                        'peer does not speak ASN4 - restarting in compatibility mode'
                    )
                if _open.capabilities.announced(Capabilities.MULTISESSION_BGP):
                    if not self.open.capabilities.announced(
                            Capabilities.MULTISESSION_BGP):
                        raise Notify(2, 7,
                                     'peer does not support MULTISESSION')
                    local_sessionid = set(
                        _open.capabilities[Capabilities.MULTISESSION_BGP])
                    remote_sessionid = self.open.capabilities[
                        Capabilities.MULTISESSION_BGP]
                    # Empty capability is the same as MultiProtocol (which is what we send)
                    if not remote_sessionid:
                        remote_sessionid.append(
                            Capabilities.MULTIPROTOCOL_EXTENSIONS)
                    remote_sessionid = set(remote_sessionid)
                    # As we only send one MP per session, if the matching fails, we have nothing in common
                    if local_sessionid.intersection(
                            remote_sessionid) != local_sessionid:
                        raise Notify(
                            2, 8,
                            'peer did not reply with the sessionid we sent')
                    # We can not collide due to the way we generate the configuration
                yield None
                break

            message = self.bgp.new_keepalive(force=True)
            logger.message(self.me('>> KEEPALIVE (OPENCONFIRM)'))
            yield True

            while True:
                message = self.bgp.read_keepalive()
                # KEEPALIVE or NOP
                if message.TYPE == KeepAlive.TYPE:
                    logger.message(self.me('<< KEEPALIVE (ESTABLISHED)'))
                    break
                yield None

            try:
                for name in self.supervisor.processes.notify(
                        self.neighbor.peer_address):
                    self.supervisor.processes.write(
                        name, 'neighbor %s up\n' % self.neighbor.peer_address)
            except ProcessError:
                # Can not find any better error code that 6,0 !
                raise Notify(6, 0, 'ExaBGP Internal error, sorry.')

            count = 0
            for count in self.bgp.new_announce():
                yield True
            self._updates = self.bgp.buffered()
            if count:
                logger.message(self.me('>> %d UPDATE(s)' % count))

            eor = False
            if self.neighbor.graceful_restart and \
             self.open.capabilities.announced(Capabilities.MULTIPROTOCOL_EXTENSIONS) and \
             self.open.capabilities.announced(Capabilities.GRACEFUL_RESTART):

                families = []
                for family in self.open.capabilities[
                        Capabilities.GRACEFUL_RESTART].families():
                    if family in self.neighbor.families():
                        families.append(family)
                self.bgp.new_eors(families)
                if families:
                    eor = True
                    logger.message(
                        self.me('>> EOR %s' % ', '.join([
                            '%s %s' % (str(afi), str(safi))
                            for (afi, safi) in families
                        ])))

            if not eor:
                # If we are not sending an EOR, send a keepalive as soon as when finished
                # So the other routers knows that we have no (more) routes to send ...
                # (is that behaviour documented somewhere ??)
                c, k = self.bgp.new_keepalive(True)
                if k:
                    logger.message(
                        self.me('>> KEEPALIVE (no more UPDATE and no EOR)'))

            seen_update = False
            while self._running:
                self._now = time.time()
                if self._now > self._next_info:
                    self._next_info = self._now + self.update_time
                    display_update = True
                else:
                    display_update = False

                c, k = self.bgp.new_keepalive()
                if k: logger.message(self.me('>> KEEPALIVE'))

                if display_update:
                    logger.timers(
                        self.me('Sending Timer %d second(s) left' % c))

                message = self.bgp.read_message()
                # let's read if we have keepalive before doing the timer check
                c = self.bgp.check_keepalive()

                if display_update:
                    logger.timers(
                        self.me('Receive Timer %d second(s) left' % c))

                if message.TYPE == KeepAlive.TYPE:
                    logger.message(self.me('<< KEEPALIVE'))
                elif message.TYPE == Update.TYPE:
                    seen_update = True
                    self._received_routes.extend(message.routes)
                    if message.routes:
                        logger.message(self.me('<< UPDATE'))
                        self._route_parsed += len(message.routes)
                        if self._route_parsed:
                            for route in message.routes:
                                logger.routes(
                                    LazyFormat(self.me(''), str, route))
                    else:
                        logger.message(self.me('<< UPDATE (not parsed)'))
                elif message.TYPE not in (NOP.TYPE, ):
                    logger.message(self.me('<< %d' % ord(message.TYPE)))

                if seen_update and display_update:
                    logger.supervisor(
                        self.me('processed %d routes' % self._route_parsed))
                    seen_update = False

                if self._updates:
                    count = 0
                    for count in self.bgp.new_update():
                        yield True
                    logger.message(self.me('>> UPDATE (%d)' % count))
                    self._updates = self.bgp.buffered()

                yield None

            if self.neighbor.graceful_restart and self.open.capabilities.announced(
                    Capabilities.GRACEFUL_RESTART):
                logger.warning('Closing the connection without notification')
                self.bgp.close()
                return

            # User closing the connection
            raise Notify(6, 3)
        except NotConnected, e:
            logger.warning('we can not connect to the peer %s' % str(e))
            self._more_skip()
            try:
                self.bgp.close()
            except Failure:
                pass
            return