def post (self): for inherit in self.scope.pop('inherit',[]): data = self.scope.template('neighbor',inherit) self.scope.inherit(data) local = self.scope.get() neighbor = Neighbor() # XXX: use the right class for the data type # XXX: we can use the scope.nlri interface ( and rename it ) to set some values neighbor.router_id = local.get('router-id',None) neighbor.peer_address = local.get('peer-address',None) neighbor.local_address = local.get('local-address',None) neighbor.local_as = local.get('local-as',None) neighbor.peer_as = local.get('peer-as',None) neighbor.passive = local.get('passive',False) neighbor.listen = local.get('listen',0) neighbor.connect = local.get('connect',0) neighbor.hold_time = local.get('hold-time',HoldTime(180)) neighbor.host_name = local.get('host-name',host()) neighbor.domain_name = local.get('domain-name',domain()) neighbor.md5_password = local.get('md5-password',None) neighbor.md5_base64 = local.get('md5-base64', None) neighbor.md5_ip = local.get('md5-ip',neighbor.local_address) neighbor.description = local.get('description','') neighbor.flush = local.get('auto-flush',True) neighbor.adj_rib_out = local.get('adj-rib-out',True) neighbor.adj_rib_in = local.get('adj-rib-in',True) neighbor.aigp = local.get('aigp',None) neighbor.ttl_out = local.get('outgoing-ttl',None) neighbor.ttl_in = local.get('incoming-ttl',None) neighbor.group_updates = local.get('group-updates',True) neighbor.manual_eor = local.get('manual-eor', False) capability = local.get('capability',{}) neighbor.add_path = capability.get('add-path',0) neighbor.asn4 = capability.get('asn4',True) neighbor.extended_message = capability.get('extended-message',True) neighbor.multisession = capability.get('multi-session',False) neighbor.operational = capability.get('operational',False) neighbor.route_refresh = capability.get('route-refresh',0) if capability.get('graceful-restart',False) is not False: neighbor.graceful_restart = capability.get('graceful-restart',0) or int(neighbor.hold_time) neighbor.api = ParseAPI.flatten(local.pop('api',{})) families = [] for family in ParseFamily.convert: for pair in local.get('family',{}).get(family,[]): families.append(pair) families = families or NLRI.known_families() for family in families: neighbor.add_family(family) if neighbor.add_path: add_path = local.get('add-path',{}) if add_path: for family in ParseAddPath.convert: for pair in add_path.get(family,[]): if pair not in families: self.logger.debug('skipping add-path family %s as it is not negotiated' % pair,'configuration') continue neighbor.add_addpath(pair) else: for family in families: neighbor.add_addpath(family) neighbor.changes = [] neighbor.changes.extend(self.scope.pop_routes()) # old format for section in ('static','l2vpn','flow'): routes = local.get(section,{}).get('routes',[]) for route in routes: route.nlri.action = OUT.ANNOUNCE neighbor.changes.extend(routes) routes = local.get('routes',[]) for route in routes: route.nlri.action = OUT.ANNOUNCE neighbor.changes.extend(routes) messages = local.get('operational',{}).get('routes',[]) if neighbor.local_address is None: neighbor.auto_discovery = True neighbor.local_address = None neighbor.md5_ip = None if not neighbor.router_id: if neighbor.peer_address.afi == AFI.ipv4 and not neighbor.auto_discovery: neighbor.router_id = neighbor.local_address else: return self.error.set('missing router-id for the peer, it can not be set using the local-ip') if neighbor.route_refresh: if neighbor.adj_rib_out: self.logger.debug('route-refresh requested, enabling adj-rib-out','configuration') missing = neighbor.missing() if missing: return self.error.set('incomplete neighbor, missing %s' % missing) if not neighbor.auto_discovery and neighbor.local_address.afi != neighbor.peer_address.afi: return self.error.set('local-address and peer-address must be of the same family') neighbor.range_size = neighbor.peer_address.mask.size() if neighbor.range_size > 1 and not neighbor.passive: return self.error.set('can only use ip ranges for the peer address with passive neighbors') if neighbor.peer_address.top() in self._neighbors: return self.error.set('duplicate peer definition %s' % neighbor.peer_address.top()) self._neighbors.append(neighbor.peer_address.top()) if neighbor.md5_password: try: md5 = base64.b64decode(neighbor.md5_password) if neighbor.md5_base64 else neighbor.md5_password except TypeError as e: return self.error.set("Invalid base64 encoding of MD5 password.") else: if len(md5) > 80: return self.error.set('MD5 password must be no larger than 80 characters') # check we are not trying to announce routes without the right MP announcement for change in neighbor.changes: family = change.nlri.family() if family not in families and family != (AFI.ipv4,SAFI.unicast): return self.error.set('Trying to announce a route of type %s,%s when we are not announcing the family to our peer' % change.nlri.family()) def _init_neighbor (neighbor): families = neighbor.families() for change in neighbor.changes: if change.nlri.family() in families: # This add the family to neighbor.families() neighbor.rib.outgoing.add_to_rib_watchdog(change) for message in messages: if message.family() in families: if message.name == 'ASM': neighbor.asm[message.family()] = message else: neighbor.messages.append(message) self.neighbors[neighbor.name()] = neighbor # create one neighbor object per family for multisession if neighbor.multisession and len(neighbor.families()) > 1: for family in neighbor.families(): # XXX: FIXME: Ok, it works but it takes LOTS of memory .. m_neighbor = deepcopy(neighbor) m_neighbor.make_rib() m_neighbor.rib.outgoing.families = [family] _init_neighbor(m_neighbor) else: neighbor.make_rib() _init_neighbor(neighbor) return True
def post(self): for inherit in self.scope.pop('inherit', []): data = self.scope.template('neighbor', inherit) self.scope.inherit(data) local = self.scope.get() neighbor = Neighbor() # XXX: use the right class for the data type # XXX: we can use the scope.nlri interface ( and rename it ) to set some values neighbor.router_id = local.get('router-id', None) neighbor.peer_address = local.get('peer-address', None) neighbor.local_address = local.get('local-address', None) neighbor.local_as = local.get('local-as', None) neighbor.peer_as = local.get('peer-as', None) neighbor.passive = local.get('passive', False) neighbor.listen = local.get('listen', 0) neighbor.connect = local.get('connect', 0) neighbor.hold_time = local.get('hold-time', HoldTime(180)) neighbor.host_name = local.get('host-name', host()) neighbor.domain_name = local.get('domain-name', domain()) neighbor.md5_password = local.get('md5-password', None) neighbor.md5_base64 = local.get('md5-base64', None) neighbor.md5_ip = local.get('md5-ip', neighbor.local_address) neighbor.description = local.get('description', '') neighbor.flush = local.get('auto-flush', True) neighbor.adj_rib_out = local.get('adj-rib-out', True) neighbor.adj_rib_in = local.get('adj-rib-in', True) neighbor.aigp = local.get('aigp', None) neighbor.ttl_out = local.get('outgoing-ttl', None) neighbor.ttl_in = local.get('incoming-ttl', None) neighbor.group_updates = local.get('group-updates', True) neighbor.manual_eor = local.get('manual-eor', False) capability = local.get('capability', {}) neighbor.add_path = capability.get('add-path', 0) neighbor.asn4 = capability.get('asn4', True) neighbor.extended_message = capability.get('extended-message', True) neighbor.multisession = capability.get('multi-session', False) neighbor.operational = capability.get('operational', False) neighbor.route_refresh = capability.get('route-refresh', 0) if capability.get('graceful-restart', False) is not False: neighbor.graceful_restart = capability.get( 'graceful-restart', 0) or int(neighbor.hold_time) neighbor.api = ParseAPI.flatten(local.pop('api', {})) families = [] for family in ParseFamily.convert: for pair in local.get('family', {}).get(family, []): families.append(pair) families = families or NLRI.known_families() for family in families: neighbor.add_family(family) if neighbor.add_path: add_path = local.get('add-path', {}) if add_path: for family in ParseAddPath.convert: for pair in add_path.get(family, []): if pair not in families: self.logger.debug( 'skipping add-path family %s as it is not negotiated' % pair, 'configuration') continue neighbor.add_addpath(pair) else: for family in families: neighbor.add_addpath(family) neighbor.changes = [] neighbor.changes.extend(self.scope.pop_routes()) # old format for section in ('static', 'l2vpn', 'flow'): routes = local.get(section, {}).get('routes', []) for route in routes: route.nlri.action = OUT.ANNOUNCE neighbor.changes.extend(routes) routes = local.get('routes', []) for route in routes: route.nlri.action = OUT.ANNOUNCE neighbor.changes.extend(routes) messages = local.get('operational', {}).get('routes', []) if neighbor.local_address is None: neighbor.auto_discovery = True neighbor.local_address = None neighbor.md5_ip = None if not neighbor.router_id and neighbor.peer_address.afi == AFI.ipv4 and not neighbor.auto_discovery: neighbor.router_id = neighbor.local_address if neighbor.route_refresh: if neighbor.adj_rib_out: self.logger.debug( 'route-refresh requested, enabling adj-rib-out', 'configuration') missing = neighbor.missing() if missing: return self.error.set('incomplete neighbor, missing %s' % missing) if not neighbor.auto_discovery and neighbor.local_address.afi != neighbor.peer_address.afi: return self.error.set( 'local-address and peer-address must be of the same family') neighbor.range_size = neighbor.peer_address.mask.size() if neighbor.range_size > 1 and not neighbor.passive: return self.error.set( 'can only use ip ranges for the peer address with passive neighbors' ) if neighbor.peer_address.top() in self._neighbors: return self.error.set('duplicate peer definition %s' % neighbor.peer_address.top()) self._neighbors.append(neighbor.peer_address.top()) if neighbor.md5_password: try: md5 = base64.b64decode( neighbor.md5_password ) if neighbor.md5_base64 else neighbor.md5_password except TypeError as e: return self.error.set( "Invalid base64 encoding of MD5 password.") else: if len(md5) > 80: return self.error.set( 'MD5 password must be no larger than 80 characters') # check we are not trying to announce routes without the right MP announcement for change in neighbor.changes: family = change.nlri.family() if family not in families and family != (AFI.ipv4, SAFI.unicast): return self.error.set( 'Trying to announce a route of type %s,%s when we are not announcing the family to our peer' % change.nlri.family()) def _init_neighbor(neighbor): families = neighbor.families() for change in neighbor.changes: if change.nlri.family() in families: # This add the family to neighbor.families() neighbor.rib.outgoing.add_to_rib_watchdog(change) for message in messages: if message.family() in families: if message.name == 'ASM': neighbor.asm[message.family()] = message else: neighbor.messages.append(message) self.neighbors[neighbor.name()] = neighbor # create one neighbor object per family for multisession if neighbor.multisession and len(neighbor.families()) > 1: for family in neighbor.families(): # XXX: FIXME: Ok, it works but it takes LOTS of memory .. m_neighbor = deepcopy(neighbor) m_neighbor.make_rib() m_neighbor.rib.outgoing.families = [family] _init_neighbor(m_neighbor) else: neighbor.make_rib() _init_neighbor(neighbor) return True
class Neighbor(dict): class Capability(dict): defaults = { 'asn4': True, 'extended-message': True, 'graceful-restart': False, 'multi-session': False, 'operational': False, 'add-path': 0, 'route-refresh': 0, 'nexthop': None, 'aigp': None, } defaults = { # Those are the field from the configuration 'description': '', 'router-id': None, 'local-address': None, 'peer-address': None, 'local-as': None, 'peer-as': None, # passive indicate that we do not establish outgoing connections 'passive': False, # the port to listen on ( zero mean that we do not listen ) 'listen': 0, # the port to connect to 'connect': 0, 'hold-time': HoldTime(180), 'rate-limit': 0, 'host-name': host(), 'domain-name': domain(), 'group-updates': True, 'auto-flush': True, 'adj-rib-in': True, 'adj-rib-out': True, 'manual-eor': False, # XXX: this should be under an MD5 sub-dict/object ? 'md5-password': None, 'md5-base64': False, 'md5-ip': None, 'outgoing-ttl': None, 'incoming-ttl': None, } _GLOBAL = {'uid': 1} def __init__(self): # super init self.update(self.defaults) # Those are subconf self.api = None # XXX: not scriptable - is replaced outside the class # internal or calculated field self['capability'] = self.Capability.defaults.copy() # local_address uses auto discovery self.auto_discovery = False self.range_size = 1 # was this Neighbor generated from a range self.generated = False self._families = [] self._nexthop = [] self._addpath = [] self.rib = None # The routes we have parsed from the configuration self.changes = [] # On signal update, the previous routes so we can compare what changed self.backup_changes = [] self.eor = deque() self.asm = dict() self.messages = deque() self.refresh = deque() self.counter = Counter() # It is possible to : # - have multiple exabgp toward one peer on the same host ( use of pid ) # - have more than once connection toward a peer # - each connection has it own neihgbor (hence why identificator is not in Protocol) self.uid = '%d' % self._GLOBAL['uid'] self._GLOBAL['uid'] += 1 def missing(self): if self['local-as'] is None: return 'incomplete neighbor, missing local-address' if self['local-as'] is None: return 'incomplete neighbor, missing local-as' if self['peer-as'] is None: return 'incomplete neighbor, missing peer-as' return '' def infer(self): if self['md5-ip'] is None: self['md5-ip'] = self['local-address'] if self['capability']['graceful-restart'] == 0: self['capability']['graceful-restart'] = int(self['hold-time']) def id(self): return 'neighbor-%s' % self.uid # This set must be unique between peer, not full draft-ietf-idr-bgp-multisession-07 def index(self): if self['listen'] != 0: return 'peer-ip %s listen %d' % (self['peer-address'], self['listen']) return self.name() def make_rib(self): self.rib = RIB(self.name(), self['adj-rib-in'], self['adj-rib-out'], self._families) # will resend all the routes once we reconnect def reset_rib(self): self.rib.reset() self.messages = deque() self.refresh = deque() # back to square one, all the routes are removed def clear_rib(self): self.rib.clear() self.messages = deque() self.refresh = deque() def name(self): if self['capability']['multi-session']: session = '/'.join("%s-%s" % (afi.name(), safi.name()) for (afi, safi) in self.families()) else: session = 'in-open' return "neighbor %s local-ip %s local-as %s peer-as %s router-id %s family-allowed %s" % ( self['peer-address'], self['local-address'] if self['peer-address'] is not None else 'auto', self['local-as'] if self['local-as'] is not None else 'auto', self['peer-as'] if self['peer-as'] is not None else 'auto', self['router-id'], session, ) def families(self): # this list() is important .. as we use the function to modify self._families return list(self._families) def nexthops(self): # this list() is important .. as we use the function to modify self._nexthop return list(self._nexthop) def addpaths(self): # this list() is important .. as we use the function to modify self._add_path return list(self._addpath) def add_family(self, family): # the families MUST be sorted for neighbor indexing name to be predictable for API users # this list() is important .. as we use the function to modify self._families if family not in self.families(): afi, safi = family d = dict() d[afi] = [ safi, ] for afi, safi in self._families: d.setdefault(afi, []).append(safi) self._families = [(afi, safi) for afi in sorted(d) for safi in sorted(d[afi])] def add_nexthop(self, afi, safi, nhafi): if (afi, safi, nhafi) not in self._nexthop: self._nexthop.append((afi, safi, nhafi)) def add_addpath(self, family): # the families MUST be sorted for neighbor indexing name to be predictable for API users # this list() is important .. as we use the function to modify self._add_path if family not in self.addpaths(): afi, safi = family d = dict() d[afi] = [ safi, ] for afi, safi in self._addpath: d.setdefault(afi, []).append(safi) self._addpath = [(afi, safi) for afi in sorted(d) for safi in sorted(d[afi])] def remove_family(self, family): if family in self.families(): self._families.remove(family) def remove_nexthop(self, afi, safi, nhafi): if (afi, safi, nhafi) in self.nexthops(): self._nexthop.remove((afi, safi, nhafi)) def remove_addpath(self, family): if family in self.addpaths(): self._addpath.remove(family) def missing(self): if self['local-address'] is None and not self.auto_discovery: return 'local-address' if self['listen'] > 0 and self.auto_discovery: return 'local-address' if self['peer-address'] is None: return 'peer-address' if self.auto_discovery and not self['router-id']: return 'router-id' if self['peer-address'].afi == AFI.ipv6 and not self['router-id']: return 'router-id' return '' # This function only compares the neighbor BUT NOT ITS ROUTES def __eq__(self, other): # Comparing local_address is skipped in the case where either # peer is configured to auto discover its local address. In # this case it can happen that one local_address is None and # the other one will be set to the auto disocvered IP address. auto_discovery = self.auto_discovery or other.auto_discovery return (self['router-id'] == other['router-id'] and self['local-as'] == other['local-as'] and self['peer-address'] == other['peer-address'] and self['peer-as'] == other['peer-as'] and self['passive'] == other['passive'] and self['listen'] == other['listen'] and self['connect'] == other['connect'] and self['hold-time'] == other['hold-time'] and self['rate-limit'] == other['rate-limit'] and self['host-name'] == other['host-name'] and self['domain-name'] == other['domain-name'] and self['md5-password'] == other['md5-password'] and self['md5-ip'] == other['md5-ip'] and self['incoming-ttl'] == other['incoming-ttl'] and self['outgoing-ttl'] == other['outgoing-ttl'] and self['group-updates'] == other['group-updates'] and self['auto-flush'] == other['auto-flush'] and self['adj-rib-in'] == other['adj-rib-in'] and self['adj-rib-out'] == other['adj-rib-out'] and (auto_discovery or self['local-address'] == other['local-address']) and self['capability'] == other['capability'] and self.auto_discovery == other.auto_discovery and self.families() == other.families()) def __ne__(self, other): return not self.__eq__(other) def string(self, with_changes=True): changes = '' if with_changes: changes += '\nstatic { ' for change in self.rib.outgoing.queued_changes(): changes += '\n\t\t%s' % change.extensive() changes += '\n}' families = '' for afi, safi in self.families(): families += '\n\t\t%s %s;' % (afi.name(), safi.name()) nexthops = '' for afi, safi, nexthop in self.nexthops(): nexthops += '\n\t\t%s %s %s;' % (afi.name(), safi.name(), nexthop.name()) addpaths = '' for afi, safi in self.addpaths(): addpaths += '\n\t\t%s %s;' % (afi.name(), safi.name()) codes = Message.CODE _extension_global = { 'neighbor-changes': 'neighbor-changes', 'negotiated': 'negotiated', 'fsm': 'fsm', 'signal': 'signal', } _extension_receive = { 'receive-packets': 'packets', 'receive-parsed': 'parsed', 'receive-consolidate': 'consolidate', 'receive-%s' % codes.NOTIFICATION.SHORT: 'notification', 'receive-%s' % codes.OPEN.SHORT: 'open', 'receive-%s' % codes.KEEPALIVE.SHORT: 'keepalive', 'receive-%s' % codes.UPDATE.SHORT: 'update', 'receive-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh', 'receive-%s' % codes.OPERATIONAL.SHORT: 'operational', } _extension_send = { 'send-packets': 'packets', 'send-parsed': 'parsed', 'send-consolidate': 'consolidate', 'send-%s' % codes.NOTIFICATION.SHORT: 'notification', 'send-%s' % codes.OPEN.SHORT: 'open', 'send-%s' % codes.KEEPALIVE.SHORT: 'keepalive', 'send-%s' % codes.UPDATE.SHORT: 'update', 'send-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh', 'send-%s' % codes.OPERATIONAL.SHORT: 'operational', } apis = '' for process in self.api.get('processes', []): _global = [] _receive = [] _send = [] for api, name in _extension_global.items(): _global.extend([ '\t\t%s;\n' % name, ] if process in self.api[api] else []) for api, name in _extension_receive.items(): _receive.extend([ '\t\t\t%s;\n' % name, ] if process in self.api[api] else []) for api, name in _extension_send.items(): _send.extend([ '\t\t\t%s;\n' % name, ] if process in self.api[api] else []) _api = '\tapi {\n' _api += '\t\tprocesses [ %s ];\n' % process _api += ''.join(_global) if _receive: _api += '\t\treceive {\n' _api += ''.join(_receive) _api += '\t\t}\n' if _send: _api += '\t\tsend {\n' _api += ''.join(_send) _api += '\t\t}\n' _api += '\t}\n' apis += _api returned = ( 'neighbor %s {\n' '\tdescription "%s";\n' '\trouter-id %s;\n' '\thost-name %s;\n' '\tdomain-name %s;\n' '\tlocal-address %s;\n' '\tlocal-as %s;\n' '\tpeer-as %s;\n' '\thold-time %s;\n' '\trate-limit %s;\n' '\tmanual-eor %s;\n' '%s%s%s%s%s%s%s%s%s%s%s\n' '\tcapability {\n' '%s%s%s%s%s%s%s%s%s\t}\n' '\tfamily {%s\n' '\t}\n' '\tnexthop {%s\n' '\t}\n' '\tadd-path {%s\n' '\t}\n' '%s' '%s' '}' % ( self['peer-address'], self['description'], self['router-id'], self['host-name'], self['domain-name'], self['local-address'] if not self.auto_discovery else 'auto', self['local-as'], self['peer-as'], self['hold-time'], 'disable' if self['rate-limit'] == 0 else self['rate-limit'], 'true' if self['manual-eor'] else 'false', '\n\tpassive %s;\n' % ('true' if self['passive'] else 'false'), '\n\tlisten %d;\n' % self['listen'] if self['listen'] else '', '\n\tconnect %d;\n' % self['connect'] if self['connect'] else '', '\tgroup-updates %s;\n' % ('true' if self['group-updates'] else 'false'), '\tauto-flush %s;\n' % ('true' if self['auto-flush'] else 'false'), '\tadj-rib-in %s;\n' % ('true' if self['adj-rib-in'] else 'false'), '\tadj-rib-out %s;\n' % ('true' if self['adj-rib-out'] else 'false'), '\tmd5-password "%s";\n' % self['md5-password'] if self['md5-password'] else '', '\tmd5-base64 %s;\n' % ('true' if self['md5-base64'] is True else 'false' if self['md5-base64'] is False else 'auto'), '\tmd5-ip "%s";\n' % self['md5-ip'] if not self.auto_discovery else '', '\toutgoing-ttl %s;\n' % self['outgoing-ttl'] if self['outgoing-ttl'] else '', '\tincoming-ttl %s;\n' % self['incoming-ttl'] if self['incoming-ttl'] else '', '\t\tasn4 %s;\n' % ('enable' if self['capability']['asn4'] else 'disable'), '\t\troute-refresh %s;\n' % ('enable' if self['capability']['route-refresh'] else 'disable'), '\t\tgraceful-restart %s;\n' % (self['capability']['graceful-restart'] if self['capability']['graceful-restart'] else 'disable'), '\t\tnexthop %s;\n' % ('enable' if self['capability']['nexthop'] else 'disable'), '\t\tadd-path %s;\n' % (AddPath.string[self['capability']['add-path']] if self['capability']['add-path'] else 'disable'), '\t\tmulti-session %s;\n' % ('enable' if self['capability']['multi-session'] else 'disable'), '\t\toperational %s;\n' % ('enable' if self['capability']['operational'] else 'disable'), '\t\taigp %s;\n' % ('enable' if self['capability']['aigp'] else 'disable'), families, nexthops, addpaths, apis, changes, )) # '\t\treceive {\n%s\t\t}\n' % receive if receive else '', # '\t\tsend {\n%s\t\t}\n' % send if send else '', return returned.replace('\t', ' ') def __str__(self): return self.string(False)