def __init__ (self, configurations, text=False): self.api_encoder = environment.settings().api.encoder self.logger = Logger() self._configurations = configurations self.error = Error() self.tokens = Tokeniser(self.error,self.logger) self.neighbor = ParseNeighbor(self.error,self.logger) self.family = ParseFamily(self.error) self.process = ParseProcess(self.error) self.route = ParseRoute(self.error) self.flow = ParseFlow(self.error,self.logger) self.l2vpn = ParseL2VPN(self.error) self.operational = ParseOperational(self.error) self._tree = { 'configuration': { 'neighbor': (self._multi_neighbor,self.neighbor.make), 'group': (self._multi_group,true), }, 'group': { 'neighbor': (self._multi_neighbor,self.neighbor.make), 'static': (self._multi_static,true), 'flow': (self._multi_flow,true), 'l2vpn': (self._multi_l2vpn,true), 'process': (self._multi_process,true), 'family': (self._multi_family,true), 'capability': (self._multi_capability,true), 'operational': (self._multi_operational,true), }, 'neighbor': { 'static': (self._multi_static,true), 'flow': (self._multi_flow,true), 'l2vpn': (self._multi_l2vpn,true), 'process': (self._multi_process,true), 'family': (self._multi_family,true), 'capability': (self._multi_capability,true), 'operational': (self._multi_operational,true), }, 'static': { 'route': (self._multi_static_route,self.route.check_static_route), }, 'flow': { 'route': (self._multi_flow_route,self.flow.check_flow), }, 'l2vpn': { 'vpls': (self._multi_l2vpn_vpls,self.l2vpn.check_vpls), }, 'flow-route': { 'match': (self._multi_match,true), 'then': (self._multi_then,true), }, 'process': { 'send': (self._multi_api,true), 'receive': (self._multi_api,true), } } self._command = { 'group': { 'description': self.neighbor.description, 'router-id': self.neighbor.router_id, 'host-name': self.neighbor.hostname, 'domain-name': self.neighbor.domainname, 'local-address': self.neighbor.ip, 'local-as': self.neighbor.asn, 'peer-as': self.neighbor.asn, 'passive': self.neighbor.passive, 'listen': self.neighbor.listen, 'hold-time': self.neighbor.holdtime, 'md5': self.neighbor.md5, 'ttl-security': self.neighbor.ttl, 'group-updates': self.neighbor.groupupdate, 'adj-rib-out': self.neighbor.adjribout, 'auto-flush': self.neighbor.autoflush, }, 'neighbor': { 'description': self.neighbor.description, 'router-id': self.neighbor.router_id, 'host-name': self.neighbor.hostname, 'domain-name': self.neighbor.domainname, 'local-address': self.neighbor.ip, 'local-as': self.neighbor.asn, 'peer-as': self.neighbor.asn, 'passive': self.neighbor.passive, 'listen': self.neighbor.listen, 'hold-time': self.neighbor.holdtime, 'md5': self.neighbor.md5, 'ttl-security': self.neighbor.ttl, 'group-updates': self.neighbor.groupupdate, 'adj-rib-out': self.neighbor.adjribout, 'auto-flush': self.neighbor.autoflush, }, 'capability': { 'route-refresh': self.neighbor.capability.refresh, 'graceful-restart': self.neighbor.capability.gracefulrestart, 'multi-session': self.neighbor.capability.multisession, 'add-path': self.neighbor.capability.addpath, 'aigp': self.neighbor.capability.aigp, 'operational': self.neighbor.capability.operational, 'add-path': self.neighbor.capability.addpath, 'asn4': self.neighbor.capability.asn4, }, 'process': { 'run': self.process.run, 'encoder': self.process.encoder, 'neighbor-changes': self.process.command, }, 'family': { 'ipv4': self.family.ipv4, 'ipv6': self.family.ipv6, 'l2vpn': self.family.l2vpn, 'minimal': self.family.minimal, 'all': self.family.all, }, 'static': { 'route': self.route.static, }, 'l2vpn': { 'vpls': self.l2vpn.vpls, }, 'operational': { 'asm': self.operational.asm, # it makes no sense to have adm or others }, 'static-route': self.route.command, # 'inet-route': { # 'mpls-route': { 'l2vpn-vpls': self.l2vpn.command, 'flow-route': { 'rd': self.route.rd, 'route-distinguisher': self.route.rd, 'next-hop': self.flow.next_hop, }, 'flow-match': { 'source': self.flow.source, 'source-ipv4': self.flow.source, 'destination': self.flow.destination, 'destination-ipv4': self.flow.destination, 'port': self.flow.anyport, 'source-port': self.flow.source_port, 'destination-port': self.flow.destination_port, 'protocol': self.flow.protocol, 'next-header': self.flow.next_header, 'tcp-flags': self.flow.tcp_flags, 'icmp-type': self.flow.icmp_type, 'icmp-code': self.flow.icmp_code, 'fragment': self.flow.fragment, 'dscp': self.flow.dscp, 'traffic-class': self.flow.traffic_class, 'packet-length': self.flow.packet_length, 'flow-label': self.flow.flow_label, }, 'flow-then': { 'accept': self.flow.accept, 'discard': self.flow.discard, 'rate-limit': self.flow.rate_limit, 'redirect': self.flow.redirect, 'redirect-to-nexthop': self.flow.redirect_next_hop, 'copy': self.flow.copy, 'mark': self.flow.mark, 'action': self.flow.action, 'community': self.route.community, 'extended-community': self.route.extended_community, }, 'send': { 'parsed': self.process.command, 'packets': self.process.command, 'consolidate': self.process.command, 'open': self.process.command, 'update': self.process.command, 'notification': self.process.command, 'keepalive': self.process.command, 'refresh': self.process.command, 'operational': self.process.command, }, 'receive': { 'parsed': self.process.command, 'packets': self.process.command, 'consolidate': self.process.command, 'open': self.process.command, 'update': self.process.command, 'notification': self.process.command, 'keepalive': self.process.command, 'refresh': self.process.command, 'operational': self.process.command, }, } self._clear() self.processes = {} self._scope = [] self._location = ['root']
class Configuration (object): def __init__ (self, configurations, text=False): self.api_encoder = environment.settings().api.encoder self.logger = Logger() self._configurations = configurations self.error = Error() self.tokens = Tokeniser(self.error,self.logger) self.neighbor = ParseNeighbor(self.error,self.logger) self.family = ParseFamily(self.error) self.process = ParseProcess(self.error) self.route = ParseRoute(self.error) self.flow = ParseFlow(self.error,self.logger) self.l2vpn = ParseL2VPN(self.error) self.operational = ParseOperational(self.error) self._tree = { 'configuration': { 'neighbor': (self._multi_neighbor,self.neighbor.make), 'group': (self._multi_group,true), }, 'group': { 'neighbor': (self._multi_neighbor,self.neighbor.make), 'static': (self._multi_static,true), 'flow': (self._multi_flow,true), 'l2vpn': (self._multi_l2vpn,true), 'process': (self._multi_process,true), 'family': (self._multi_family,true), 'capability': (self._multi_capability,true), 'operational': (self._multi_operational,true), }, 'neighbor': { 'static': (self._multi_static,true), 'flow': (self._multi_flow,true), 'l2vpn': (self._multi_l2vpn,true), 'process': (self._multi_process,true), 'family': (self._multi_family,true), 'capability': (self._multi_capability,true), 'operational': (self._multi_operational,true), }, 'static': { 'route': (self._multi_static_route,self.route.check_static_route), }, 'flow': { 'route': (self._multi_flow_route,self.flow.check_flow), }, 'l2vpn': { 'vpls': (self._multi_l2vpn_vpls,self.l2vpn.check_vpls), }, 'flow-route': { 'match': (self._multi_match,true), 'then': (self._multi_then,true), }, 'process': { 'send': (self._multi_api,true), 'receive': (self._multi_api,true), } } self._command = { 'group': { 'description': self.neighbor.description, 'router-id': self.neighbor.router_id, 'host-name': self.neighbor.hostname, 'domain-name': self.neighbor.domainname, 'local-address': self.neighbor.ip, 'local-as': self.neighbor.asn, 'peer-as': self.neighbor.asn, 'passive': self.neighbor.passive, 'listen': self.neighbor.listen, 'hold-time': self.neighbor.holdtime, 'md5': self.neighbor.md5, 'ttl-security': self.neighbor.ttl, 'group-updates': self.neighbor.groupupdate, 'adj-rib-out': self.neighbor.adjribout, 'auto-flush': self.neighbor.autoflush, }, 'neighbor': { 'description': self.neighbor.description, 'router-id': self.neighbor.router_id, 'host-name': self.neighbor.hostname, 'domain-name': self.neighbor.domainname, 'local-address': self.neighbor.ip, 'local-as': self.neighbor.asn, 'peer-as': self.neighbor.asn, 'passive': self.neighbor.passive, 'listen': self.neighbor.listen, 'hold-time': self.neighbor.holdtime, 'md5': self.neighbor.md5, 'ttl-security': self.neighbor.ttl, 'group-updates': self.neighbor.groupupdate, 'adj-rib-out': self.neighbor.adjribout, 'auto-flush': self.neighbor.autoflush, }, 'capability': { 'route-refresh': self.neighbor.capability.refresh, 'graceful-restart': self.neighbor.capability.gracefulrestart, 'multi-session': self.neighbor.capability.multisession, 'add-path': self.neighbor.capability.addpath, 'aigp': self.neighbor.capability.aigp, 'operational': self.neighbor.capability.operational, 'add-path': self.neighbor.capability.addpath, 'asn4': self.neighbor.capability.asn4, }, 'process': { 'run': self.process.run, 'encoder': self.process.encoder, 'neighbor-changes': self.process.command, }, 'family': { 'ipv4': self.family.ipv4, 'ipv6': self.family.ipv6, 'l2vpn': self.family.l2vpn, 'minimal': self.family.minimal, 'all': self.family.all, }, 'static': { 'route': self.route.static, }, 'l2vpn': { 'vpls': self.l2vpn.vpls, }, 'operational': { 'asm': self.operational.asm, # it makes no sense to have adm or others }, 'static-route': self.route.command, # 'inet-route': { # 'mpls-route': { 'l2vpn-vpls': self.l2vpn.command, 'flow-route': { 'rd': self.route.rd, 'route-distinguisher': self.route.rd, 'next-hop': self.flow.next_hop, }, 'flow-match': { 'source': self.flow.source, 'source-ipv4': self.flow.source, 'destination': self.flow.destination, 'destination-ipv4': self.flow.destination, 'port': self.flow.anyport, 'source-port': self.flow.source_port, 'destination-port': self.flow.destination_port, 'protocol': self.flow.protocol, 'next-header': self.flow.next_header, 'tcp-flags': self.flow.tcp_flags, 'icmp-type': self.flow.icmp_type, 'icmp-code': self.flow.icmp_code, 'fragment': self.flow.fragment, 'dscp': self.flow.dscp, 'traffic-class': self.flow.traffic_class, 'packet-length': self.flow.packet_length, 'flow-label': self.flow.flow_label, }, 'flow-then': { 'accept': self.flow.accept, 'discard': self.flow.discard, 'rate-limit': self.flow.rate_limit, 'redirect': self.flow.redirect, 'redirect-to-nexthop': self.flow.redirect_next_hop, 'copy': self.flow.copy, 'mark': self.flow.mark, 'action': self.flow.action, 'community': self.route.community, 'extended-community': self.route.extended_community, }, 'send': { 'parsed': self.process.command, 'packets': self.process.command, 'consolidate': self.process.command, 'open': self.process.command, 'update': self.process.command, 'notification': self.process.command, 'keepalive': self.process.command, 'refresh': self.process.command, 'operational': self.process.command, }, 'receive': { 'parsed': self.process.command, 'packets': self.process.command, 'consolidate': self.process.command, 'open': self.process.command, 'update': self.process.command, 'notification': self.process.command, 'keepalive': self.process.command, 'refresh': self.process.command, 'operational': self.process.command, }, } self._clear() self.processes = {} self._scope = [] self._location = ['root'] def _clear (self): self.processes = {} self._scope = [] self._location = ['root'] self.tokens.clear() self.error.clear() self.neighbor.clear() self.family.clear() self.process.clear() self.route.clear() self.flow.clear() self.l2vpn.clear() self.operational.clear() # Public Interface def reload (self): try: return self._reload() except KeyboardInterrupt: return self.error.set('configuration reload aborted by ^C or SIGINT') except Exception: # unhandled configuration parsing issue raise def _reload (self): # taking the first configuration available (FIFO buffer) fname = self._configurations.pop(0) self.process.configuration(fname) self._configurations.append(fname) # clearing the current configuration to be able to re-parse it self._clear() if not self.tokens.set_file(fname): return False # parsing the configuration r = False while not self.tokens.finished: r = self._dispatch( self._scope,'root','configuration', self._tree['configuration'].keys(), [] ) if r is False: break # handling possible parsing errors if r not in [True,None]: # making sure nothing changed self.neighbor.cancel() return self.error.set( "\n" "syntax error in section %s\n" "line %d: %s\n" "\n%s" % ( self._location[-1], self.tokens.number, ' '.join(self.tokens.line), str(self.error) ) ) # installing in the neighbor the API routes self.neighbor.complete() # we are not really running the program, just want to .... if environment.settings().debug.route: from exabgp.configuration.current.check import check_message if check_message(self.neighbor.neighbors,environment.settings().debug.route): sys.exit(0) sys.exit(1) # we are not really running the program, just want check the configuration validity if environment.settings().debug.selfcheck: from exabgp.configuration.current.check import check_neighbor if check_neighbor(self.neighbor.neighbors): sys.exit(0) sys.exit(1) return True # name is not used yet but will come really handy if we have name collision :D def _dispatch (self, scope, name, command, multi, single, location=None): if location: self._location = location self.flow.clear() try: tokens = self.tokens.next() except IndexError: return self.error.set('configuration file incomplete (most likely missing })') self.logger.configuration("parsing | %-13s | '%s'" % (command,"' '".join(tokens))) end = tokens[-1] if multi and end == '{': self._location.append(tokens[0]) return self._multi_line(scope,command,tokens[1],tokens[:-1],multi) if single and end == ';': return self.run(scope,command,tokens[1],tokens[:-1],single) if end == '}': if len(self._location) == 1: return self.error.set('closing too many parenthesis') self._location.pop(-1) return None return False def _multi (self, tree, scope, name, command, tokens, valid): command = tokens[0] if valid and command not in valid: return self.error.set('option %s in not valid here' % command) if name not in tree: return self.error.set('option %s is not allowed here' % name) run, validate = tree[name].get(command,(false,false)) if not run(scope,name,command,tokens[1:]): return False if not validate(scope,self): return False return True def _multi_line (self, scope, name, command, tokens, valid): return self._multi(self._tree,scope,name,command,tokens,valid) # Programs used to control exabgp def _multi_process (self, scope, name, command, tokens): while True: r = self._dispatch( scope,name,'process', ['send','receive'], [ 'run','encoder', 'neighbor-changes', ] ) if r is False: return False if r is None: break name = tokens[0] if len(tokens) >= 1 else 'conf-only-%s' % str(time.time())[-6:] self.processes.setdefault(name,{})['neighbor'] = scope[-1]['peer-address'] if 'peer-address' in scope[-1] else '*' for key in ['neighbor-changes',]: self.processes[name][key] = scope[-1].pop(key,False) for direction in ['send','receive']: for action in ['packets','parsed','consolidate']: key = '%s-%s' % (direction,action) self.processes[name][key] = scope[-1].pop(key,False) for message in Message.CODE.MESSAGES: key = '%s-%d' % (direction,message) self.processes[name][key] = scope[-1].pop(key,False) run = scope[-1].pop('run','') if run: if len(tokens) != 1: return self.error.set(self.process.syntax) self.processes[name]['encoder'] = scope[-1].get('encoder','') or self.api_encoder self.processes[name]['run'] = run return True elif len(tokens): return self.error.set(self.process.syntax) # Limit the AFI/SAFI pair announced to peers def _multi_family (self, scope, name, command, tokens): # we know all the families we should use scope[-1]['families'] = [] while True: r = self._dispatch( scope,name,'family', [], self._command['family'].keys() ) if r is False: return False if r is None: break self.family.clear() return True # capacity def _multi_capability (self, scope, name, command, tokens): # we know all the families we should use while True: r = self._dispatch( scope,name,'capability', [], self._command['capability'].keys() ) if r is False: return False if r is None: break return True # route grouping with watchdog # Group Neighbor def _multi_group (self, scope, name, command, address): # if len(tokens) != 2: # return self.error.set('syntax: group <name> { <options> }') scope.append({}) while True: r = self._dispatch( scope,name,'group', [ 'static','flow','l2vpn', 'neighbor','process','family', 'capability','operational' ], self._command['neighbor'].keys() ) if r is False: return False if r is None: scope.pop(-1) return True def _multi_neighbor (self, scope, name, command, tokens): if len(tokens) != 1: return self.error.set('syntax: neighbor <ip> { <options> }') address = tokens[0] scope.append({}) try: scope[-1]['peer-address'] = IP.create(address) except (IndexError,ValueError,socket.error): return self.error.set('"%s" is not a valid IP address' % address) while True: r = self._dispatch( scope,name,'neighbor', [ 'static','flow','l2vpn', 'process','family','capability','operational' ], self._command['neighbor'] ) # XXX: THIS SHOULD ALLOW CAPABILITY AND NOT THE INDIVIDUAL SUB KEYS if r is False: return False if r is None: return True # Group Static ................ def _multi_static (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set('syntax: static { route; route; ... }') while True: r = self._dispatch( scope,name,'static', ['route',], ['route',] ) if r is False: return False if r is None: return True # Group Route ........ def _multi_static_route (self, scope, name, command, tokens): if len(tokens) != 1: return self.error.set(self.route.syntax) if not self.route.insert_static_route(scope,name,command,tokens): return False while True: r = self._dispatch( scope,name,'static-route', self._command['static-route'].keys(), self._command['static-route'].keys() ) if r is False: return False if r is None: return self.route.make_split(scope) def _multi_l2vpn (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set(self.l2vpn.syntax) while True: r = self._dispatch( scope,name,'l2vpn', ['vpls',], ['vpls',] ) if r is False: return False if r is None: break return True def _multi_l2vpn_vpls (self, scope, name, command, tokens): if len(tokens) > 1: return self.error.set(self.l2vpn.syntax) if not self.l2vpn.insert_vpls(scope,name,command,tokens): return False while True: r = self._dispatch( scope,name,'l2vpn-vpls', self._command['l2vpn-vpls'].keys(), self._command['l2vpn-vpls'].keys() ) if r is False: return False if r is None: break return True def _multi_flow (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set(self.flow.syntax) while True: r = self._dispatch( scope,name,'flow', ['route',], [] ) if r is False: return False if r is None: break return True def _insert_flow_route (self, scope, name, command, tokens=None): if self.flow.state != 'out': return self.error.set(self.flow.syntax) self.flow.state = 'match' try: attributes = Attributes() attributes[Attribute.CODE.EXTENDED_COMMUNITY] = ExtendedCommunities() flow = Change(Flow(),attributes) except ValueError: return self.error.set(self.flow.syntax) if 'announce' not in scope[-1]: scope[-1]['announce'] = [] scope[-1]['announce'].append(flow) return True def _multi_flow_route (self, scope, name, command, tokens): if len(tokens) > 1: return self.error.set(self.flow.syntax) if not self._insert_flow_route(scope,name,command): return False while True: r = self._dispatch( scope,name,'flow-route', ['match','then'], ['rd','route-distinguisher','next-hop'] ) if r is False: return False if r is None: break if self.flow.state != 'out': return self.error.set(self.flow.syntax) return True # .......................................... def _multi_match (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set(self.flow.syntax) if self.flow.state != 'match': return self.error.set(self.flow.syntax) self.flow.state = 'then' while True: r = self._dispatch( scope,name,'flow-match', [], [ 'source','destination', 'source-ipv4','destination-ipv4', 'port','source-port','destination-port', 'protocol','next-header','tcp-flags','icmp-type','icmp-code', 'fragment','dscp','traffic-class','packet-length','flow-label' ] ) if r is False: return False if r is None: break return True def _multi_then (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set(self.flow.syntax) if self.flow.state != 'then': return self.error.set(self.flow.syntax) self.flow.state = 'out' while True: r = self._dispatch( scope,name,'flow-then', [], [ 'accept','discard','rate-limit', 'redirect','copy','redirect-to-nexthop', 'mark','action', 'community','extended-community' ] ) if r is False: return False if r is None: break return True # .......................................... def _multi_api (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set('api issue') while True: r = self._dispatch( scope,name,command, [], self._command[command].keys() ) if r is False: return False if r is None: break return True # Group Operational ................ def _multi_operational (self, scope, name, command, tokens): if len(tokens) != 0: return self.error.set('syntax: operational { command; command; ... }') while True: r = self._dispatch( scope,name,command, [], self._command[command].keys() ) if r is False: return False if r is None: return True def run (self, scope, name, comamnd, tokens, valid): command = tokens[0] if valid and command not in valid: return self.error.set('invalid keyword "%s"' % command) family = { 'static-route': { 'rd': SAFI.mpls_vpn, 'route-distinguisher': SAFI.mpls_vpn, }, 'l2vpn-vpls': { 'rd': SAFI.vpls, 'route-distinguisher': SAFI.vpls, }, 'flow-route': { 'rd': SAFI.flow_vpn, 'route-distinguisher': SAFI.flow_vpn, } } if name in self._command: if command in self._command[name]: if command in family.get(name,{}): return self._command[name][command](scope,name,command,tokens[1:],family[name][command]) return self._command[name][command](scope,name,command,tokens[1:]) return self.error.set('command not known %s' % command)