Example #1
0
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)