class BgpPolicyParser: """Parser class""" def __init__(self, network): self.network = network self.g_business_relationship = nx.DiGraph() self.user_defined_sets = {} self.user_library_calls = [] self.user_defined_functions = {} # Grammars #TODO: tidy this up attribute_unnamed = Word(alphanums+'_'+".") attribute = attribute_unnamed.setResultsName("attribute") self.attribute = attribute lt = Literal("<").setResultsName("<") le = Literal("<=").setResultsName("<=") eq = Literal("=").setResultsName("=") ne = Literal("!=").setResultsName("!=") ge = Literal(">=").setResultsName(">=") gt = Literal(">").setResultsName(">") wildcard = Literal("*").setResultsName("wildcard") self.wildcard = wildcard self.prefix_lists = {} self.tags_to_allocate = set() self.allocated_tags = {} self._opn = { '<': operator.lt, '<=': operator.le, '=': operator.eq, '!=': operator.ne, '>=': operator.ge, '>': operator.gt, '&': set.intersection, '|': set.union, } # map alphanum chars to alphanum equivalents for use in tags self._opn_to_tag = { '<': "lt", '<=': "le", '=': "eq", '!=': "ne", '>=': "ge", '>': "gt", '&': "and", '|': "or", } # Both are of comparison to access in same manner when evaluating comparison = (lt | le | eq | ne | ge | gt).setResultsName("comparison") stringComparison = (eq | ne).setResultsName("comparison") # #quoted string is already present float_string = Word(nums).setResultsName("value").setParseAction(lambda t: float(t[0])) integer_string = Word(nums).setResultsName("value").setParseAction(lambda t: int(t[0])) #TODO: use numString, and make integer if fiull stop #TODO: allow parentheses? - should be ok as pass to the python parser ipField = Word(nums, max=3) ipAddress = Combine( ipField + "." + ipField + "." + ipField + "." + ipField ).setResultsName("ipAddress") boolean_and = Literal("&").setResultsName("&") boolean_or = Literal("|").setResultsName("|") boolean = (boolean_and | boolean_or).setResultsName("boolean") self._boolean = boolean # need to use in checking #TODO fix this matching 2a.ab when that should match a string numericQuery = Group(attribute + comparison + float_string).setResultsName( "numericQuery") stringValues = (attribute_unnamed | quotedString.setParseAction(removeQuotes) ).setResultsName("value") stringQuery = Group(attribute + stringComparison + stringValues).setResultsName( "stringQuery") wildcardQuery = wildcard.setResultsName("wildcardQuery") singleQuery = numericQuery | stringQuery | wildcardQuery singleQuery.setFailAction(parse_fail_action) self.nodeQuery = singleQuery + ZeroOrMore(boolean + singleQuery) self.u_egress = Literal("egress->").setResultsName("u_egress") self.v_ingress = Literal("->ingress").setResultsName("v_ingress") self.u_ingress = Literal("ingress<-").setResultsName("u_ingress") self.v_egress = Literal("<-egress").setResultsName("v_egress") edgeType = ( self.u_egress | self.u_ingress | self.v_egress | self.v_ingress).setResultsName("edgeType").setFailAction(parse_fail_action) self.edgeQuery = ("(" + self.nodeQuery.setResultsName("query_a") + ")" + edgeType + "(" + self.nodeQuery.setResultsName("query_b") + ")").setFailAction(parse_fail_action) #start of BGP queries originQuery = (Literal("Origin").setResultsName("attribute") + #this is a workaround for the match, comparison, value 3-tuple in processing Literal("(").setResultsName("comparison") + Group(self.nodeQuery).setResultsName("value") + Suppress(")")).setResultsName("originQuery") transitQuery = (Literal("Transit").setResultsName("attribute") + #this is a workaround for the match, comparison, value 3-tuple in processing Literal("(").setResultsName("comparison") + Group(self.nodeQuery).setResultsName("value") + Suppress(")")).setResultsName("transitQuery") prefixList = Literal("prefix_list") matchPl = (prefixList.setResultsName("attribute") + comparison + attribute.setResultsName("value")) matchTag = (Literal("tag").setResultsName("attribute") + comparison + attribute.setResultsName("value")) #tags contain -> tag = aaa inTags = ( Literal("tags").setResultsName("attribute").setParseAction(lambda x: "tag") + Literal("contain").setResultsName("comparison").setParseAction(lambda x: "=") + attribute_unnamed.setResultsName("value") ) bgpMatchQuery = Group(matchPl | matchTag | inTags | originQuery | transitQuery ).setResultsName("bgpMatchQuery").setFailAction(parse_fail_action) self.bgpMatchQuery = bgpMatchQuery setLP = (Literal("setLP").setResultsName("attribute") + integer_string.setResultsName("value")).setResultsName("setLP") setMED = (Literal("setMED").setResultsName("attribute") + integer_string.setResultsName("value")).setResultsName("setMED") addTag = (Literal("addTag").setResultsName("attribute") + attribute.setResultsName("value")).setResultsName("addTag") removeTag = (Literal("removeTag").setResultsName("attribute") + attribute.setResultsName("value")).setResultsName("removeTag") #TODO: need to set blank value reject = Literal("reject") #TODO: remove once move quagga output inside module self.reject = reject rejectAction = (reject.setResultsName("attribute") + Literal("route").setResultsName("value")).setResultsName("reject") setNextHop = (Literal("setNextHop").setResultsName("attribute") + ipAddress.setResultsName("value")).setResultsName("setNextHop") setOriginAttribute = (Literal("setOriginAttribute").setResultsName("attribute") + (oneOf("IGP BGP None").setResultsName("value"))).setResultsName("setOriginAttribute") bgpAction = Group(addTag | setLP | setMED | removeTag | setNextHop | setOriginAttribute | rejectAction).setResultsName("bgpAction") # The Clauses ifClause = Group(Suppress("if") + bgpMatchQuery + ZeroOrMore(Suppress(boolean_and) + bgpMatchQuery)).setResultsName("if_clause") actionClause = bgpAction + ZeroOrMore(Suppress(boolean_and) + bgpAction) thenClause = Group(Suppress("then") + actionClause).setResultsName("then_clause") ifThenClause = Group(Suppress("(") + ifClause + thenClause + Suppress(")")).setResultsName("ifThenClause") elseActionClause = Group(Suppress("(") + actionClause + Suppress(")")).setResultsName("else_clause") # Support actions without a condition (ie no "if") unconditionalAction = Group(Suppress("(") + Group(actionClause).setResultsName("unconditionalActionClause") + Suppress(")")).setResultsName("bgpSessionQuery") # Query may contain itself (nested) bgpSessionQuery = Forward() bgpSessionQuery << ( ifThenClause + Optional( Suppress("else") + (elseActionClause | bgpSessionQuery)) ).setResultsName("bgpSessionQuery") bgpSessionQuery = bgpSessionQuery | unconditionalAction self.bgpSessionQuery = bgpSessionQuery self.bgpApplicationQuery = self.edgeQuery + Suppress(":") + self.bgpSessionQuery # Library stuff set_values = Suppress("{") + delimitedList( attribute, delim=',').setResultsName("set_values") + Suppress("}") #Set to empty set, rather than empty list as empty list is processed differently somewhere in parser empty_set = Literal("{}").setResultsName("set_values").setParseAction(lambda x: set()) self.set_definition = attribute.setResultsName("set_name") + Suppress("=") + (empty_set | set_values) library_params = attribute | Group(set_values) | empty_set library_function = attribute.setResultsName("def_name") + Suppress("(") + delimitedList( library_params, delim=',').setResultsName("def_params") + Suppress(")") library_function.setFailAction(parse_fail_action) self.library_def = Suppress("define") + library_function self.library_call = Suppress("apply") + library_function self.library_def.setFailAction(parse_fail_action) self.library_edge_query = (self.attribute.setResultsName("query_a") + edgeType + self.attribute.setResultsName("query_b")) self.library_edge_query.setFailAction(parse_fail_action) library_edge_definition = self.library_edge_query + Suppress(":") + self.bgpSessionQuery library_global_definition = "global tags = {" + delimitedList( attribute, delim=',').setResultsName("tags") + "}" self.library_entry = library_global_definition.setResultsName("global_tags") | library_edge_definition.setResultsName("library_edge") self.library_entry.setFailAction(parse_fail_action) self.bgpPolicyLine = ( self.bgpApplicationQuery.setResultsName("bgpApplicationQuery") | self.library_call.setResultsName("library_call") | self.set_definition.setResultsName("set_definition") ) #TODO: allow shorthand of (1) -> (2) for (asn=1) -> (asn=2) def clear_policies(self): for src, dst in self.network.g_session.edges(): self.network.g_session[src][dst]['ingress'] = [] self.network.g_session[src][dst]['egress'] = [] def apply_bgp_policy(self, qstring): """Applies policy to network >>> inet = ank.internet.Internet("2routers") >>> inet.compile() >>> node_a = inet.network.find("a.AS1") >>> node_b = inet.network.find("b.AS2") >>> pol_parser = ank.BgpPolicyParser(inet.network) >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (setLP 200)") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [] then [setLP 200] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (setMED 200)") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [] then [setMED 200] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (*): (setMED 200)") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [] then [setMED 200] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (if tag = test then setLP 100)") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [tag = test] then [setLP 100] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (if tags contain test then setLP 100)") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [tag = test] then [setLP 100] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (if prefix_list = pl_asn_eq_2 then addTag cl_asn_eq_2))") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [prefix_list = pl_asn_eq_2] then [addTag cl_asn_eq_2] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (addTag ABC & setLP 90))") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [] then [addTag ABC, setLP 90] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (if Origin(asn=2) then addTag a100 ))") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [tag = origin_cl_asn_eq_2] then [addTag a100] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (if Transit(asn=2) then addTag a100 ))") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [tag = transit_cl_asn_eq_2] then [addTag a100] reject: False]] >>> pol_parser.clear_policies() >>> pol_parser.apply_bgp_policy("(asn=1) ->ingress (asn=2): (if Transit(asn=2) then addTag a100 ))") >>> inet.network.g_session[node_a][node_b]['ingress'] [[if [tag = transit_cl_asn_eq_2] then [addTag a100] reject: False]] >>> pol_parser = ank.BgpPolicyParser(ank.network.Network(ank.load_example("multias"))) #TODO: move these tests out Testing internals: >>> attributestring = "2a.as1" >>> result = pol_parser.attribute.parseString(attributestring) Node and edge queries:: >>> nodestring = "node = '2ab.ab'" >>> result = pol_parser.nodeQuery.parseString(nodestring) >>> result = pol_parser.edgeQuery.parseString("(" + nodestring + ") egress-> (node = b)") >>> result = pol_parser.edgeQuery.parseString("(node = a.b) egress-> (node = b)") Full policy queries:: >>> pol_parser.apply_bgp_policy("(node = '2a.AS2') egress-> (*): (if prefix_list = pl_asn_eq_2 then addTag cl_asn_eq_2)") >>> pol_parser.apply_bgp_policy("(Network = AS1 ) ->ingress (Network = AS2): (if tag = deprefme then setLP 90) ") >>> pol_parser.apply_bgp_policy("(Network = AS1 ) ->ingress (Network = AS2): (addTag ABC & setLP 90) ") >>> pol_parser.apply_bgp_policy("(asn = 1) egress-> (asn = 1): (if Origin(asn=2) then addTag a100 )") >>> pol_parser.apply_bgp_policy("(asn = 1) egress-> (asn = 1): (if Transit(asn=2) then addTag a100 )") >>> pol_parser.apply_bgp_policy("(node = a_b ) ->ingress (Network = AS2): (addTag ABC & setLP 90) ") >>> pol_parser.apply_bgp_policy("(node = a_b ) ->ingress (Network = AS2): (if Transit(asn=2) then addTag a100 ) ") """ LOG.debug("Applying BGP policy %s" % qstring) result = self.bgpPolicyLine.parseString(qstring) if 'set_definition' in result: LOG.debug("Storing set definition %s" % result.set_name) self.user_defined_sets[result.set_name] = set(a for a in result.set_values) return if 'library_call' in result: def_params = [] for param in result.def_params: if isinstance(param, basestring): def_params.append(param) else: # is a sequence, extract value def_params.append([p for p in param]) self.user_library_calls.append( (result.def_name, def_params)) return LOG.debug("Query string is %s " % qstring) set_a = self.node_select_query(result.query_a) LOG.debug("Set a is %s " % set_a) set_b = self.node_select_query(result.query_b) LOG.debug("Set b is %s " % set_b) select_type = result.edgeType per_session_policy = self.process_if_then_else(result.bgpSessionQuery) # use nbunch feature of networkx to limit edges to look at node_set = set_a | set_b edges = self.network.g_session.edges(node_set) #LOG.debug("Edges are %s " % edges) # 1 ->, 2 <-, 3 <-> def select_fn_u_to_v( (u, v), src_set, dst_set): """ u -> v""" return (u in src_set and v in dst_set) def select_fn_u_from_v( (u, v), src_set, dst_set): """ u <- v""" return (u in dst_set and v in src_set)