Example #1
0
    def writer(self, data):
        if not self.io:
            # XXX: FIXME: Make sure it does not hold the cleanup during the closing of the peering session
            yield True
            return
        while not self.writing():
            yield False
        logfunc.debug(lazyformat('sending TCP payload', data), self.session())
        # The first while is here to setup the try/catch block once as it is very expensive
        while True:
            try:
                while True:
                    if self.defensive and random.randint(0, 2):
                        raise socket.error(errno.EAGAIN,
                                           'raising network error on purpose')

                    # we can not use sendall as in case of network buffer filling
                    # it does raise and does not let you know how much was sent
                    number = self.io.send(data)
                    if not number:
                        self.close()
                        log.warning(
                            '%s %s lost TCP connection with peer' %
                            (self.name(), self.peer), self.session())
                        raise LostConnection('lost the TCP connection')

                    data = data[number:]
                    if not data:
                        yield True
                        return
                    yield False
            except socket.error as exc:
                if exc.args[0] in error.block:
                    log.debug(
                        '%s %s blocking io problem mid-way through writing a message %s, trying to complete'
                        % (self.name(), self.peer, errstr(exc)),
                        self.session(),
                    )
                    yield False
                elif exc.errno == errno.EPIPE:
                    # The TCP connection is gone.
                    self.close()
                    raise NetworkError('Broken TCP connection')
                elif exc.args[0] in error.fatal:
                    self.close()
                    log.critical(
                        '%s %s problem sending message (%s)' %
                        (self.name(), self.peer, errstr(exc)), self.session())
                    raise NetworkError(
                        'Problem while writing data to the network (%s)' %
                        errstr(exc))
                # what error could it be !
                else:
                    log.critical(
                        '%s %s undefined error writing on socket' %
                        (self.name(), self.peer), self.session())
                    yield False
Example #2
0
    def unpack_nlri(cls, afi, safi, data, action, addpath):
        a, s = AFI.create(afi), SAFI.create(safi)
        logfunc.debug(lazynlri(a, s, addpath, data), 'parser')

        key = '%s/%s' % (a, s)
        if key in cls.registered_nlri:
            return cls.registered_nlri[key].unpack_nlri(
                a, s, data, action, addpath)
        raise Notify(3, 0, 'trying to decode unknown family %s/%s' % (a, s))
Example #3
0
    def _reader(self, number):
        # The function must not be called if it does not return with no data with a smaller size as parameter
        if not self.io:
            self.close()
            raise NotConnected('Trying to read on a closed TCP connection')
        if number == 0:
            yield b''
            return

        while not self.reading():
            yield b''
        data = b''
        reported = ''
        while True:
            try:
                while True:
                    if self.defensive and random.randint(0, 2):
                        raise socket.error(errno.EAGAIN, 'raising network error on purpose')

                    read = self.io.recv(number)
                    if not read:
                        self.close()
                        log.warning('%s %s lost TCP session with peer' % (self.name(), self.peer), self.session())
                        raise LostConnection('the TCP connection was closed by the remote end')
                    data += read

                    number -= len(read)
                    if not number:
                        logfunc.debug(lazyformat('received TCP payload', data), self.session())
                        yield data
                        return

                    yield b''
            except socket.timeout as exc:
                self.close()
                log.warning('%s %s peer is too slow' % (self.name(), self.peer), self.session())
                raise TooSlowError('Timeout while reading data from the network (%s)' % errstr(exc))
            except socket.error as exc:
                if exc.args[0] in error.block:
                    message = '%s %s blocking io problem mid-way through reading a message %s, trying to complete' % (
                        self.name(),
                        self.peer,
                        errstr(exc),
                    )
                    if message != reported:
                        reported = message
                        log.debug(message, self.session())
                    yield b''
                elif exc.args[0] in error.fatal:
                    self.close()
                    raise LostConnection('issue reading on the socket: %s' % errstr(exc))
                # what error could it be !
                else:
                    log.critical('%s %s undefined error reading on socket' % (self.name(), self.peer), self.session())
                    raise NetworkError('Problem while reading data from the network (%s)' % errstr(exc))
Example #4
0
    def parse(self, data, direction, negotiated):
        if not data:
            return self

        try:
            # We do not care if the attribute are transitive or not as we do not redistribute
            flag = Attribute.Flag(data[0])
            aid = Attribute.CODE(data[1])
        except IndexError:
            self.add(TreatAsWithdraw())
            return self

        try:
            offset = 3
            length = data[2]

            if flag & Attribute.Flag.EXTENDED_LENGTH:
                offset = 4
                length = (length << 8) + data[3]
        except IndexError:
            self.add(TreatAsWithdraw(aid))
            return self

        data = data[offset:]
        left = data[length:]
        attribute = data[:length]

        logfunc.debug(lazyattribute(flag, aid, length, data[:length]), 'parser')

        # remove the PARTIAL bit before comparaison if the attribute is optional
        if aid in Attribute.attributes_optional:
            flag &= Attribute.Flag.MASK_PARTIAL & 0xFF
            # flag &= ~Attribute.Flag.PARTIAL & 0xFF  # cleaner than above (python use signed integer for ~)

        if aid in self:
            if aid in self.NO_DUPLICATE:
                raise Notify(3, 1, 'multiple attribute for %s' % str(Attribute.CODE(attribute.ID)))

            log.debug(
                'duplicate attribute %s (flag 0x%02X, aid 0x%02X) skipping'
                % (Attribute.CODE.names.get(aid, 'unset'), flag, aid),
                'parser',
            )
            return self.parse(left, direction, negotiated)

        # handle the attribute if we know it
        if Attribute.registered(aid, flag):
            if length == 0 and aid not in self.VALID_ZERO:
                self.add(TreatAsWithdraw(aid))
                return self.parse(left, direction, negotiated)

            try:
                decoded = Attribute.unpack(aid, flag, attribute, direction, negotiated)
            except IndexError as exc:
                if aid in self.TREAT_AS_WITHDRAW:
                    decoded = TreatAsWithdraw(aid)
                else:
                    raise exc
            except Notify as exc:
                if aid in self.TREAT_AS_WITHDRAW:
                    decoded = TreatAsWithdraw()
                elif aid in self.DISCARD:
                    decoded = Discard()
                else:
                    raise exc
            self.add(decoded)
            return self.parse(left, direction, negotiated)

        # XXX: FIXME: we could use a fallback function here like capability

        # if we know the attribute but the flag is not what the RFC says.
        if aid in Attribute.attributes_known:
            if aid in self.TREAT_AS_WITHDRAW:
                log.debug(
                    'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X) treat as withdraw'
                    % (Attribute.CODE.names.get(aid, 'unset'), flag, aid),
                    'parser',
                )
                self.add(TreatAsWithdraw())
            if aid in self.DISCARD:
                log.debug(
                    'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X) discard'
                    % (Attribute.CODE.names.get(aid, 'unset'), flag, aid),
                    'parser',
                )
                return self.parse(left, direction, negotiated)
            # XXX: Check if we are missing any
            log.debug(
                'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X) unspecified (should not happen)'
                % (Attribute.CODE.names.get(aid, 'unset'), flag, aid),
                'parser',
            )
            return self.parse(left, direction, negotiated)

        # it is an unknown transitive attribute we need to pass on
        if flag & Attribute.Flag.TRANSITIVE:
            log.debug('unknown transitive attribute (flag 0x%02X, aid 0x%02X)' % (flag, aid), 'parser')
            try:
                decoded = GenericAttribute(aid, flag | Attribute.Flag.PARTIAL, attribute)
            except IndexError:
                decoded = TreatAsWithdraw(aid)
            self.add(decoded, attribute)
            return self.parse(left, direction, negotiated)

        # it is an unknown non-transitive attribute we can ignore.
        log.debug('ignoring unknown non-transitive attribute (flag 0x%02X, aid 0x%02X)' % (flag, aid), 'parser')
        return self.parse(left, direction, negotiated)
Example #5
0
    def _main(self):
        """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)

        self.neighbor.rib.incoming.clear()

        include_withdraw = False

        # Announce to the process BGP is up
        log.info('connected to %s with %s' % (self.id(), self.proto.connection.name()), 'reactor')
        self.stats['up'] = self.stats.get('up', 0) + 1
        if self.neighbor.api['neighbor-changes']:
            try:
                self.reactor.processes.up(self.neighbor)
            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 = not self.neighbor['manual-eor']
        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])

        operational = None
        refresh = None
        command_eor = None
        number = 0
        refresh_enhanced = True if self.proto.negotiated.refresh == REFRESH.ENHANCED else False

        send_ka = KA(self.proto.connection.session, self.proto)

        while not self._teardown:
            for message in self.proto.read_message():
                self.recv_timer.check_ka(message)

                if send_ka() is not False:
                    # we need and will send a keepalive
                    while send_ka() is None:
                        yield ACTION.NOW

                # Received update
                if message.TYPE == Update.TYPE:
                    number += 1
                    log.debug('<< UPDATE #%d' % number, self.id())

                    for nlri in message.nlris:
                        self.neighbor.rib.incoming.update_cache(Change(nlri, message.attributes))
                        logfunc.debug(lazyformat('   UPDATE #%d nlri ' % number, nlri, str), self.id())

                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['capability']['operational']:
                    if not operational:
                        new_operational = self.neighbor.messages.popleft() if self.neighbor.messages else None
                        if new_operational:
                            operational = self.proto.new_operational(new_operational, self.proto.negotiated)

                    if operational:
                        try:
                            next(operational)
                        except StopIteration:
                            operational = None
                # make sure that if some operational message are received via the API
                # that we do not eat memory for nothing
                elif self.neighbor.messages:
                    self.neighbor.messages.popleft()

                # SEND REFRESH
                if self.neighbor['capability']['route-refresh']:
                    if not refresh:
                        new_refresh = self.neighbor.refresh.popleft() if self.neighbor.refresh else None
                        if new_refresh:
                            refresh = self.proto.new_refresh(new_refresh)

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

                # Take the routes already sent to that peer and resend them
                if self._reconfigure:
                    self._reconfigure = False

                    # we are here following a configuration change
                    if self._neighbor:
                        # see what changed in the configuration
                        self.neighbor.rib.outgoing.replace(self._neighbor.backup_changes, self._neighbor.changes)
                        # do not keep the previous routes in memory as they are not useful anymore
                        self._neighbor.backup_changes = []

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

                # Need to send update
                if not new_routes and self.neighbor.rib.outgoing.pending():
                    # XXX: in proto really. hum to think about ?
                    new_routes = self.proto.new_update(include_withdraw)

                if new_routes:
                    count = 1 if self.neighbor['rate-limit'] > 0 else 25
                    try:
                        for _ in range(count):
                            # This can raise a NetworkError
                            next(new_routes)
                    except StopIteration:
                        new_routes = None
                        include_withdraw = True

                elif send_eor:
                    send_eor = False
                    for _ in self.proto.new_eors():
                        yield ACTION.NOW
                    log.debug('>> EOR(s)', self.id())

                # SEND MANUAL KEEPALIVE (only if we have no more routes to send)
                elif not command_eor and self.neighbor.eor:
                    new_eor = self.neighbor.eor.popleft()
                    command_eor = self.proto.new_eors(new_eor.afi, new_eor.safi)

                if command_eor:
                    try:
                        next(command_eor)
                    except StopIteration:
                        command_eor = None

                if new_routes or message.TYPE != NOP.TYPE:
                    yield ACTION.NOW
                elif self.neighbor.messages or operational:
                    yield ACTION.NOW
                elif self.neighbor.eor or command_eor:
                    yield ACTION.NOW
                else:
                    yield ACTION.LATER

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

        # If graceful restart, silent shutdown
        if self.neighbor['capability']['graceful-restart'] and self.proto.negotiated.sent_open.capabilities.announced(
            Capability.CODE.GRACEFUL_RESTART
        ):
            log.error('closing the session without notification', self.id())
            self.proto.close('graceful restarted negotiated, closing without sending any notification')
            raise NetworkError('closing')

        # notify our peer of the shutdown
        raise Notify(6, self._teardown)
Example #6
0
    def unpack_message(cls, data, direction, negotiated):
        logfunc.debug(lazyformat('parsing UPDATE', data), 'parser')

        length = len(data)

        # This could be speed up massively by changing the order of the IF
        if length == 4 and data == b'\x00\x00\x00\x00':
            return EOR(AFI.ipv4, SAFI.unicast)  # pylint: disable=E1101
        if length == 11 and data.startswith(EOR.NLRI.PREFIX):
            return EOR.unpack_message(data, direction, negotiated)

        withdrawn, _attributes, announced = cls.split(data)

        if not withdrawn:
            log.debug('withdrawn NLRI none', 'routes')

        attributes = Attributes.unpack(_attributes, direction, negotiated)

        if not announced:
            log.debug('announced NLRI none', 'routes')

        # Is the peer going to send us some Path Information with the route (AddPath)
        if direction == Direction.IN:
            addpath = negotiated.addpath.receive(AFI.ipv4, SAFI.unicast)
        else:
            addpath = negotiated.addpath.send(AFI.ipv4, SAFI.unicast)

        # empty string for NoNextHop, the packed IP otherwise (without the 3/4 bytes of attributes headers)
        nexthop = attributes.get(Attribute.CODE.NEXT_HOP, NoNextHop)
        # nexthop = NextHop.unpack(_nexthop.ton())

        # XXX: NEXTHOP MUST NOT be the IP address of the receiving speaker.

        nlris = []
        while withdrawn:
            nlri, left = NLRI.unpack_nlri(AFI.ipv4, SAFI.unicast, withdrawn, IN.WITHDRAWN, addpath)
            log.debug('withdrawn NLRI %s' % nlri, 'routes')
            withdrawn = left
            nlris.append(nlri)

        while announced:
            nlri, left = NLRI.unpack_nlri(AFI.ipv4, SAFI.unicast, announced, IN.ANNOUNCED, addpath)
            nlri.nexthop = nexthop
            log.debug('announced NLRI %s' % nlri, 'routes')
            announced = left
            nlris.append(nlri)

        unreach = attributes.pop(MPURNLRI.ID, None)
        reach = attributes.pop(MPRNLRI.ID, None)

        if unreach is not None:
            nlris.extend(unreach.nlris)

        if reach is not None:
            nlris.extend(reach.nlris)

        if not attributes and not nlris:
            # Careful do not use == or != as the comparaison does not work
            if unreach is None and reach is None:
                return EOR(AFI.ipv4, SAFI.unicast)
            if unreach is not None:
                return EOR(unreach.afi, unreach.safi)
            if reach is not None:
                return EOR(reach.afi, reach.safi)
            raise RuntimeError('This was not expected')

        update = Update(nlris, attributes)

        def parsed(_):
            # we need the import in the function as otherwise we have an cyclic loop
            # as this function currently uses Update..
            from exabgp.reactor.api.response import Response
            from exabgp.version import json as json_version

            return 'json %s' % Response.JSON(json_version).update(negotiated.neighbor, 'receive', update, None, '', '')

        logfunc.debug(lazyformat('decoded UPDATE', '', parsed), 'parser')

        return update