Ejemplo n.º 1
0
def check_nlri(neighbor, routes):
    option.enabled['parser'] = True

    announced = _hexa(routes)
    negotiated = _negotiated(neighbor)

    afi, safi = neighbor.families()[0]

    # Is the peer going to send us some Path Information with the route (AddPath)
    addpath = negotiated.addpath.send(afi, safi)

    nlris = []
    try:
        while announced:
            log.debug('parsing NLRI %s' % announced, 'parser')
            nlri, announced = NLRI.unpack_nlri(afi, safi, announced, IN.ANNOUNCED, addpath)
            nlris.append(nlri)
    except Exception as exc:
        log.error('could not parse the nlri', 'parser')
        from exabgp.debug import string_exception
        log.error(string_exception(exc), 'parser')
        if getenv().debug.pdb:
            raise
        return False

    log.debug('', 'parser')  # new line
    for nlri in nlris:
        log.info('nlri json %s' % nlri.json(), 'parser')
    return True
Ejemplo n.º 2
0
    def run(self):
        if self.reactor.processes.broken(self.neighbor):
            # XXX: we should perhaps try to restart the process ??
            log.error('ExaBGP lost the helper process for this peer - stopping', 'process')
            if self.reactor.processes.terminate_on_error:
                self.reactor.api_shutdown()
            else:
                self.stop()
            return True

        if self.generator:
            try:
                # This generator only stops when it raises
                # otherwise return one of the ACTION
                return next(self.generator)
            except StopIteration:
                # Trying to run a closed loop, no point continuing
                self.generator = None
                if self._restart:
                    return ACTION.LATER
                return ACTION.CLOSE

        elif self.generator is None:
            if self.fsm in [FSM.OPENCONFIRM, FSM.ESTABLISHED]:
                log.debug('stopping, other connection is established', self.id())
                self.generator = False
                return ACTION.LATER
            if self._delay.backoff():
                return ACTION.LATER
            if self._restart:
                log.debug('initialising connection to %s' % self.id(), 'reactor')
                self.generator = self._run()
                return ACTION.LATER  # make sure we go through a clean loop
            return ACTION.CLOSE
Ejemplo n.º 3
0
 def inject_change(self, peers, change):
     result = True
     for neighbor in self.neighbors:
         if neighbor in peers:
             if change.nlri.family() in self.neighbors[neighbor].families():
                 self.neighbors[neighbor].rib.outgoing.add_to_rib(change)
             else:
                 log.error('the route family is not configured on neighbor',
                           'configuration')
                 result = False
     return result
Ejemplo n.º 4
0
 def removepid(self):
     if not self.pid or not self._saved_pid:
         return
     try:
         os.remove(self.pid)
     except OSError as exc:
         if exc.errno == errno.ENOENT:
             pass
         else:
             log.error("Can not remove PIDfile %s" % self.pid, 'daemon')
             return
     log.debug("Removed PIDfile %s" % self.pid, 'daemon')
Ejemplo n.º 5
0
 def inject_operational(self, peers, operational):
     result = True
     for neighbor in self.neighbors:
         if neighbor in peers:
             if operational.family() in self.neighbors[neighbor].families():
                 if operational.name == 'ASM':
                     self.neighbors[neighbor].asm[
                         operational.family()] = operational
                 self.neighbors[neighbor].messages.append(operational)
             else:
                 log.error('the route family is not configured on neighbor',
                           'configuration')
                 result = False
     return result
Ejemplo n.º 6
0
    def reload(self):
        """Reload the configuration and send to the peer the route which changed"""
        log.notice('performing reload of exabgp %s' % version, 'configuration')

        reloaded = self.configuration.reload()

        if not reloaded:
            #
            # Careful the string below is used but the QA code to check for sucess of failure
            log.error('not reloaded, no change found in the configuration',
                      'configuration')
            # Careful the string above is used but the QA code to check for sucess of failure
            #
            log.error(str(self.configuration.error), 'configuration')
            return False

        for key, peer in self._peers.items():
            if key not in self.configuration.neighbors:
                log.debug('removing peer: %s' % peer.neighbor.name(),
                          'reactor')
                peer.remove()

        for key, neighbor in self.configuration.neighbors.items():
            # new peer
            if key not in self._peers:
                log.debug('new peer: %s' % neighbor.name(), 'reactor')
                peer = Peer(neighbor, self)
                self._peers[key] = peer
            # modified peer
            elif self._peers[key].neighbor != neighbor:
                log.debug(
                    'peer definition change, establishing a new connection for %s'
                    % str(key), 'reactor')
                self._peers[key].reestablish(neighbor)
            # same peer but perhaps not the routes
            else:
                # finding what route changed and sending the delta is not obvious
                log.debug(
                    'peer definition identical, updating peer routes if required for %s'
                    % str(key), 'reactor')
                self._peers[key].reconfigure(neighbor)
            for ip in self._ips:
                if ip.afi == neighbor.peer_address.afi:
                    self.listener.listen_on(ip, neighbor.peer_address,
                                            self._port, neighbor.md5_password,
                                            neighbor.md5_base64, None)
        log.notice('loaded new configuration successfully', 'reactor')

        return True
Ejemplo n.º 7
0
def check_update(neighbor, raw):
    option.enabled['parser'] = True
    negotiated = _negotiated(neighbor)

    while raw:
        if raw.startswith(b'\xff' * 16):
            kind = raw[18]
            size = (raw[16] << 16) + raw[17]

            injected, raw = raw[19:size], raw[size:]

            if kind == 2:
                log.debug('the message is an update', 'parser')
                decoding = 'update'
            else:
                log.debug(
                    'the message is not an update (%d) - aborting' % kind,
                    'parser')
                return False
        else:
            log.debug('header missing, assuming this message is ONE update',
                      'parser')
            decoding = 'update'
            injected, raw = raw, ''

        try:
            # This does not take the BGP header - let's assume we will not break that :)
            update = Update.unpack_message(injected, Direction.IN, negotiated)
        except Notify:
            import traceback

            log.error('could not parse the message', 'parser')
            log.error(traceback.format_exc(), 'parser')
            if getenv().debug.pdb:
                raise
            return False
        except Exception:
            import traceback

            log.error('could not parse the message', 'parser')
            log.error(traceback.format_exc(), 'parser')
            if getenv().debug.pdb:
                raise
            return False

        log.debug('', 'parser')  # new line
        for number in range(len(update.nlris)):
            change = Change(update.nlris[number], update.attributes)
            log.info(
                'decoded %s %s %s' %
                (decoding, change.nlri.action, change.extensive()), 'parser')
        log.info(
            'update json %s' % Response.JSON(json_version).update(
                neighbor, 'in', update, None, '', ''), 'parser')

    return True
Ejemplo n.º 8
0
    def reload(self):
        """Reload the configuration and send to the peer the route which changed"""
        log.info('performing reload of exabgp %s' % version, 'configuration')

        reloaded = self.configuration.reload()

        if not reloaded:
            log.error('could not load/reload configuration', 'configuration')
            log.error(str(self.configuration.error), 'configuration')
            return False

        for key, peer in self._peers.items():
            if key not in self.configuration.neighbors:
                log.debug('removing peer: %s' % peer.neighbor.name(),
                          'reactor')
                peer.remove()

        for key, neighbor in self.configuration.neighbors.items():
            # new peer
            if key not in self._peers:
                log.debug('new peer: %s' % neighbor.name(), 'reactor')
                peer = Peer(neighbor, self)
                self._peers[key] = peer
            # modified peer
            elif self._peers[key].neighbor != neighbor:
                log.debug(
                    'peer definition change, establishing a new connection for %s'
                    % str(key), 'reactor')
                self._peers[key].reestablish(neighbor)
            # same peer but perhaps not the routes
            else:
                # finding what route changed and sending the delta is not obvious
                log.debug(
                    'peer definition identical, updating peer routes if required for %s'
                    % str(key), 'reactor')
                self._peers[key].reconfigure(neighbor)
            for ip in self._ips:
                if ip.afi == neighbor['peer-address'].afi:
                    self.listener.listen_on(ip, neighbor['peer-address'],
                                            self._port,
                                            neighbor['md5-password'],
                                            neighbor['md5-base64'], None)
        log.info('loaded new configuration successfully', 'reactor')

        return True
Ejemplo n.º 9
0
    def run(self):
        if not self._async:
            return False

        # length = range(min(len(self._async),self.LIMIT))
        length = range(self.LIMIT)
        uid, generator = self._async.popleft()

        for _ in length:
            try:
                next(generator)
            except StopIteration:
                if not self._async:
                    return False
                uid, generator = self._async.popleft()
            except Exception as exc:
                log.error('async | %s | problem with function' % uid,
                          'reactor')
                for line in str(exc).split('\n'):
                    log.error('async | %s | %s' % (uid, line), 'reactor')

        self._async.appendleft((uid, generator))
        return True
Ejemplo n.º 10
0
def check_update(neighbor, raw):
    option.enabled['parser'] = True

    neighbor = neighbor[list(neighbor)[0]]

    path = {}
    for f in NLRI.known_families():
        if neighbor['capability']['add-path']:
            path[f] = neighbor['capability']['add-path']

    capa = Capabilities().new(neighbor, False)
    capa[Capability.CODE.ADD_PATH] = path
    capa[Capability.CODE.MULTIPROTOCOL] = neighbor.families()
    # capa[Capability.CODE.FOUR_BYTES_ASN] = True

    routerid_1 = str(neighbor['router-id'])
    routerid_2 = '.'.join(
        str((int(_) + 1) % 250)
        for _ in str(neighbor['router-id']).split('.', -1))

    o1 = Open(Version(4), ASN(neighbor['local-as']), HoldTime(180),
              RouterID(routerid_1), capa)
    o2 = Open(Version(4), ASN(neighbor['peer-as']), HoldTime(180),
              RouterID(routerid_2), capa)
    negotiated = Negotiated(neighbor)
    negotiated.sent(o1)
    negotiated.received(o2)
    # grouped = False

    while raw:
        if raw.startswith(b'\xff' * 16):
            kind = raw[18]
            size = (raw[16] << 16) + raw[17]

            injected, raw = raw[19:size], raw[size:]

            if kind == 2:
                log.debug('the message is an update', 'parser')
                decoding = 'update'
            else:
                log.debug(
                    'the message is not an update (%d) - aborting' % kind,
                    'parser')
                return False
        else:
            log.debug('header missing, assuming this message is ONE update',
                      'parser')
            decoding = 'update'
            injected, raw = raw, ''

        try:
            # This does not take the BGP header - let's assume we will not break that :)
            update = Update.unpack_message(injected, negotiated)
        except Notify:
            import traceback

            log.error('could not parse the message', 'parser')
            log.error(traceback.format_exc(), 'parser')
            return False
        except Exception:
            import traceback

            log.error('could not parse the message', 'parser')
            log.error(traceback.format_exc(), 'parser')
            return False

        log.debug('', 'parser')  # new line
        for number in range(len(update.nlris)):
            change = Change(update.nlris[number], update.attributes)
            log.info(
                'decoded %s %s %s' %
                (decoding, change.nlri.action, change.extensive()), 'parser')
        log.info(
            'update json %s' % Response.JSON(json_version).update(
                neighbor, 'in', update, None, '', ''), 'parser')

    return True
Ejemplo n.º 11
0
    def _run(self):
        """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._establish():
                yield action

            for action in self._main():
                yield action

        # CONNECTION FAILURE
        except NetworkError as network:
            # we tried to connect once, it failed and it was not a manual request, we stop
            if self.once and not self._teardown:
                log.debug(
                    'only one attempt to connect is allowed, stopping the peer',
                    self.id())
                self.stop()

            self._reset('closing connection', network)
            return

        # NOTIFY THE PEER OF AN ERROR
        except Notify as notify:
            if self.proto:
                try:
                    generator = self.proto.new_notification(notify)
                    try:
                        while True:
                            next(generator)
                            yield ACTION.NOW
                    except StopIteration:
                        pass
                except (NetworkError, ProcessError):
                    log.error('Notification not sent', self.id())
                self._reset(
                    'notification sent (%d,%d)' %
                    (notify.code, notify.subcode), notify)
            else:
                self._reset()
            return

        # THE PEER NOTIFIED US OF AN ERROR
        except Notification as notification:
            # we tried to connect once, it failed and it was not a manual request, we stop
            if self.once and not self._teardown:
                log.debug(
                    'only one attempt to connect is allowed, stopping the peer',
                    self.id())
                self.stop()

            self._reset(
                'notification received (%d,%d)' %
                (notification.code, notification.subcode), notification)
            return

        # RECEIVED a Message TYPE we did not expect
        except Message as message:
            self._reset('unexpected message received', message)
            return

        # PROBLEM WRITING TO OUR FORKED PROCESSES
        except ProcessError as process:
            self._reset('process problem', process)
            return

        # ....
        except Interrupted as interruption:
            self._reset(
                'connection received before we could fully establish one')
            return

        # UNHANDLED PROBLEMS
        except Exception as exc:
            # Those messages can not be filtered in purpose
            log.debug(format_exception(exc), 'reactor')
            self._reset()
            return
Ejemplo n.º 12
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.notice(
            '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))
                        log.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.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.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.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)
Ejemplo n.º 13
0
def cmdline(cmdarg):
    if not os.path.isfile(ENVFILE):
        comment = 'environment file missing\ngenerate it using "exabgp env --fi > %s"' % ENVFILE
    else:
        comment = ''

    env = getenv()
    # Must be done before setting the logger as it modify its behaviour
    if cmdarg.debug:
        env.log.all = True
        env.log.level = syslog.LOG_DEBUG

    log.init()

    if cmdarg.profile:
        env.profile.enable = True
        env.profile.file = cmdarg.profile

    if cmdarg.once:
        env.tcp.once = True

    if cmdarg.pdb:
        env.debug.pdb = True

    if cmdarg.test:
        env.log.parser = True

    if cmdarg.memory:
        env.debug.memory = True

    if env.cache.attributes:
        Attribute.caching = env.cache.attributes

    configurations = []
    for configuration in cmdarg.configuration:
        location = getconf(configuration)
        if not location:
            log.critical(f'{configuration} is not an exabgp config file', 'configuration')
            sys.exit(1)
        configurations.append(configuration)

    delay = cmdarg.signal
    _delayed_signal(delay, signal.SIGUSR1)

    if env.debug.rotate or len(configurations) == 1:
        run(comment, configurations, cmdarg.validate)

    if not (env.log.destination in ('syslog', 'stdout', 'stderr') or env.log.destination.startswith('host:')):
        log.error('can not log to files when running multiple configuration (as we fork)', 'configuration')
        sys.exit(1)

    try:
        # run each configuration in its own process
        pids = []
        for configuration in configurations:
            pid = os.fork()
            if pid == 0:
                run(comment, [configuration], cmdarg.validate, os.getpid())
            else:
                pids.append(pid)

        # If we get a ^C / SIGTERM, ignore just continue waiting for our child process
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        # wait for the forked processes
        for pid in pids:
            os.waitpid(pid, 0)
    except OSError as exc:
        log.critical('can not fork, errno %d : %s' % (exc.errno, exc.strerror), 'reactor')
        sys.exit(1)
Ejemplo n.º 14
0
def run(comment, configurations, validate, pid=0):
    env = getenv()

    log.notice('Thank you for using ExaBGP', 'welcome')
    log.notice('%s' % version, 'version')
    log.notice('%s' % sys.version.replace('\n', ' '), 'interpreter')
    log.notice('%s' % ' '.join(platform.uname()[:5]), 'os')
    log.notice('%s' % ROOT, 'installation')

    if comment:
        log.notice(comment, 'advice')

    warning = warn()
    if warning:
        log.warning(warning, 'advice')

    if env.api.cli:
        pipename = 'exabgp' if env.api.pipename is None else env.api.pipename
        pipes = named_pipe(ROOT, pipename)
        if len(pipes) != 1:
            env.api.cli = False
            log.error(
                'could not find the named pipes (%s.in and %s.out) required for the cli' % (pipename, pipename), 'cli'
            )
            log.error('we scanned the following folders (the number is your PID):', 'cli')
            for location in pipes:
                log.error(' - %s' % location, 'cli control')
            log.error('please make them in one of the folder with the following commands:', 'cli control')
            log.error('> mkfifo %s/run/%s.{in,out}' % (os.getcwd(), pipename), 'cli control')
            log.error('> chmod 600 %s/run/%s.{in,out}' % (os.getcwd(), pipename), 'cli control')
            if os.getuid() != 0:
                log.error(
                    '> chown %d:%d %s/run/%s.{in,out}' % (os.getuid(), os.getgid(), os.getcwd(), pipename),
                    'cli control',
                )
        else:
            pipe = pipes[0]
            os.environ['exabgp_cli_pipe'] = pipe
            os.environ['exabgp_api_pipename'] = pipename

            log.info('named pipes for the cli are:', 'cli control')
            log.info('to send commands  %s%s.in' % (pipe, pipename), 'cli control')
            log.info('to read responses %s%s.out' % (pipe, pipename), 'cli control')

    if not env.profile.enable:
        exit_code = Reactor(configurations).run(validate, ROOT)
        __exit(env.debug.memory, exit_code)

    try:
        import cProfile as profile
    except ImportError:
        import profile

    if env.profile.file == 'stdout':
        profiled = 'Reactor(%s).run(%s,"%s")' % (str(configurations), str(validate), str(ROOT))
        exit_code = profile.run(profiled)
        __exit(env.debug.memory, exit_code)

    if pid:
        profile_name = "%s-pid-%d" % (env.profile.file, pid)
    else:
        profile_name = env.profile.file

    notice = ''
    if os.path.isdir(profile_name):
        notice = 'profile can not use this filename as output, it is not a directory (%s)' % profile_name
    if os.path.exists(profile_name):
        notice = 'profile can not use this filename as output, it already exists (%s)' % profile_name

    if not notice:
        cwd = os.getcwd()
        log.debug('profiling ....', 'reactor')
        profiler = profile.Profile()
        profiler.enable()
        try:
            exit_code = Reactor(configurations).run(validate, ROOT)
        except Exception:
            exit_code = Reactor.Exit.unknown
            raise
        finally:
            from exabgp.vendoring import lsprofcalltree

            profiler.disable()
            kprofile = lsprofcalltree.KCacheGrind(profiler)
            try:
                destination = profile_name if profile_name.startswith('/') else os.path.join(cwd, profile_name)
                with open(destination, 'w+') as write:
                    kprofile.output(write)
            except IOError:
                notice = 'could not save profiling in formation at: ' + destination
                log.debug("-" * len(notice), 'reactor')
                log.debug(notice, 'reactor')
                log.debug("-" * len(notice), 'reactor')
            __exit(env.debug.memory, exit_code)
    else:
        log.debug("-" * len(notice), 'reactor')
        log.debug(notice, 'reactor')
        log.debug("-" * len(notice), 'reactor')
        Reactor(configurations).run(validate, ROOT)
        __exit(env.debug.memory, 1)
Ejemplo n.º 15
0
 def log_failure(self, message, level='ERR'):
     error = str(self.configuration.tokeniser.error)
     report = '%s\nreason: %s' % (message, error) if error else message
     log.error(report, 'api', level)