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
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
def stop(self): if not self.serving: return for sock, (ip, port, _, _) in self._sockets.items(): sock.close() log.info('stopped listening on %s:%d' % (ip, port), 'network') self._sockets = {} self.serving = False
def restart(self): """Kill the BGP session and restart it""" log.info('performing restart of exabgp %s' % version, 'reactor') # XXX: FIXME: Could return False, in case there is interference with old config... reloaded = self.configuration.reload() for key in self._peers.keys(): if key not in self.configuration.neighbors.keys(): peer = self._peers[key] log.debug('removing peer %s' % peer.neighbor.name(), 'reactor') self._peers[key].remove() else: self._peers[key].reestablish() self.processes.start(self.configuration.processes, True)
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
def cmdline(cmdarg): env = getenv() # Must be done before setting the logger as it modify its behaviour if cmdarg.verbose: env.log.all = True env.log.level = 'DEBUG' if cmdarg.pdb: env.debug.pdb = True log.init(env) trace_interceptor(env.debug.pdb) if cmdarg.verbose: env.log.parser = True for configuration in cmdarg.configuration: log.info(f'loading {configuration}', 'configuration') location = getconf(configuration) if not location: log.critical(f'{configuration} is not an exabgp config file', 'configuration') sys.exit(1) config = Configuration([location]) if not config.reload(): log.critical(f'{configuration} is not a valid config file', 'configuration') sys.exit(1) log.info('\u2713 loading', 'configuration') if cmdarg.neighbor: log.warning('checking neighbors', 'configuration') for name, neighbor in config.neighbors.items(): reparsed = neighbor.string() log.debug(reparsed, configuration) log.info(f'\u2713 neighbor {name.split()[1]}', 'configuration') if cmdarg.route: log.warning('checking routes', 'configuration') if not check_generation(config.neighbors): log.critical(f'{configuration} has an invalid route', 'configuration') sys.exit(1) log.info('\u2713 routes', 'configuration')
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
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)
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)
def log_message(self, message, level='INFO'): log.info(message, 'api', level)
def log_message(self, message, level='INFO'): log.info(message, 'processes', level)