def equivalent(policy1, policy2): """Determine if policy1 is equivalent to policy2 under equality. Note that this is unidirectional, it only asks if the packets that can go into policy1 behave the same under policy1 as they do under policy2. """ p1_in, p1_out = Consts("p1_in p1_out", Packet) p2_in, p2_out1, p2_out2 = Consts("p2_in p2_out1 p2_out2", Packet) s = Solver() # There are two components to this. First, we want to ensure that if p1 # forwards a packet, p2 also forwards it constraint = Implies(forwards(policy1, p1_in, p1_out), forwards(policy2, p1_in, p1_out)) # Second, we want to ensure that if p2 forwards a packet, and it's a packet # that p1 can forward, that p2 only forwards it in ways that p1 does. constraint = And( constraint, Implies( And(forwards(policy2, p2_in, p2_out1), forwards(policy1, p2_in, p2_out2)), forwards(policy1, p2_in, p2_out1) ), ) # We want to check for emptiness, so our model gives us a packet back s.add(Not(constraint)) if s.check() == unsat: return None else: return (s.model(), (p1_in, p1_out, p2_in, p2_out1, p2_out2), HEADER_INDEX)
def simulates_forwards(topo, a, b, field="vlan", edge_policy={}): """Determine if b simulates a up to field on one hop.""" p, pp = Consts("p pp", Packet) v, vv = Ints("v vv") # Z3 emits a warning about not finding a pattern for our quantification. # This is fine, so ignore it. set_option("WARNING", False) solv = Solver() solv.add(on_valid_port(topo, p)) # b doesn't need to forward packets on external links that don't satisfy the # ingress predicate edge_option = And(external_link(edge_policy, p), Not(edges_ingress(edge_policy, p))) solv.add( And( forwards(a, p, pp), ForAll([v, vv], Not(Or(forwards_with(b, p, {field: v}, pp, {field: vv}), edge_option)), patterns=[]), ) ) if solv.check() == unsat: set_option("WARNING", True) return None else: set_option("WARNING", True) return solv.model(), (p, pp), HEADER_INDEX
def compile_smt(model_filename, hypers_filename, data_filename, train_batch, out_dir): (parsed_model, data, hypers, out_name) = u.read_inputs(model_filename, hypers_filename, data_filename, train_batch) print("Unrolling execution model.") parsed_model = u.replace_hypers(parsed_model, hypers) unrolled_parsed_model = unroller.unroll_and_flatten(parsed_model, do_checks=False, print_info=False) input_dependents = tptv1.get_input_dependent_vars(unrolled_parsed_model) idx = 1 constraints = [] z3compilers = [] for i in data['instances']: print("Generating SMT constraint for I/O example %i." % idx) z3compiler = ToZ3ConstraintsVisitor(tag="__ex%i" % idx, variables_to_tag=input_dependents) constraints.extend(z3compiler.visit(unrolled_parsed_model)) for var_name, vals in i.iteritems(): if vals is None: pass elif isinstance(vals, list): for i, val in enumerate(vals): if val is not None: var_name_item = "%s_%s" % (var_name, i) constraints.append( z3compiler.get_expr(var_name_item) == IntVal(val)) else: constraints.append( z3compiler.get_expr(var_name) == IntVal(vals)) z3compilers.append(z3compiler) idx = idx + 1 # Unify things: z3compiler = z3compilers[0] for i in xrange(1, len(z3compilers)): z3compilerP = z3compilers[i] for param in z3compiler.get_params(): constraints.append( z3compiler.get_expr(param) == z3compilerP.get_expr(param)) out_file_name = os.path.join(out_dir, out_name + ".smt2") print "Writing SMTLIB2 benchmark info to '%s'." % out_file_name if not os.path.isdir(out_dir): os.makedirs(out_dir) solver = Solver() idx = 0 for c in constraints: # Debugging helper if things unexpectedly end up UNSAT: # solver.assert_and_track(c, "c%i" % idx) solver.add(c) idx = idx + 1 with open(out_file_name, 'w') as f: f.write(solver.to_smt2()) f.write("(get-model)")
def compile_smt(model_filename, hypers_filename, data_filename, train_batch, out_dir): (parsed_model, data, hypers, out_name) = u.read_inputs(model_filename, hypers_filename, data_filename, train_batch) print ("Unrolling execution model.") parsed_model = u.replace_hypers(parsed_model, hypers) unrolled_parsed_model = unroller.unroll_and_flatten(parsed_model, do_checks=False, print_info=False) input_dependents = tptv1.get_input_dependent_vars(unrolled_parsed_model) idx = 1 constraints = [] z3compilers = [] for i in data['instances']: print ("Generating SMT constraint for I/O example %i." % idx) z3compiler = ToZ3ConstraintsVisitor(tag="__ex%i" % idx, variables_to_tag=input_dependents) constraints.extend(z3compiler.visit(unrolled_parsed_model)) for var_name, vals in i.iteritems(): if isinstance(vals, list): for i, val in enumerate(vals): var_name_item = "%s_%s" % (var_name, i) constraints.append( z3compiler.get_expr(var_name_item) == IntVal(val)) else: constraints.append( z3compiler.get_expr(var_name) == IntVal(vals)) z3compilers.append(z3compiler) idx = idx + 1 # Unify things: z3compiler = z3compilers[0] for i in xrange(1, len(z3compilers)): z3compilerP = z3compilers[i] for param in z3compiler.get_params(): constraints.append( z3compiler.get_expr(param) == z3compilerP.get_expr(param)) out_file_name = os.path.join(out_dir, out_name + ".smt2") print "Writing SMTLIB2 benchmark info to '%s'." % out_file_name solver = Solver() idx = 0 for c in constraints: # Debugging helper if things unexpectedly end up UNSAT: # solver.assert_and_track(c, "c%i" % idx) solver.add(c) idx = idx + 1 with open(out_file_name, 'w') as f: f.write(solver.to_smt2()) f.write("(get-model)")
def shared_outputs(policy1, policy2): """Try to find packet in output of policy1 and egress of slice2.""" p, q, pp = Consts("p q pp", Packet) solv = Solver() solv.add(output(policy1, p, pp)) solv.add(egress(policy2, q, pp)) if solv.check() == unsat: return None else: return solv.model(), (p, pp), HEADER_INDEX
def __init__(self, s: z3.Solver, g: Graph): (G, G_nodes) = z3.EnumSort(g.name_, tuple(g.nodes())) # Function that returns true if there is an edge between two nodes, # and false otherwise edges_f = z3.Function('G_%s_edges' % (g.name_,), G, G, z3.BoolSort()) for (n1,n2) in itertools.product(iter(G_nodes), iter(G_nodes)): n1_str = repr(n1) n2_str = repr(n2) s.add(edges_f(n1,n2) == g.has_edge(n1_str,n2_str)) self.nodes_s = G # Nodes Sort self.nodes = G_nodes self.edges_f = edges_f
def shared_inputs(policy1, policy2): """Try to find packet in input of policy1 and ingress of policy2.""" p, pp, qq = Consts("p pp qq", Packet) o, n = Ints("o n") solv = Solver() solv.add(input(policy1, p, pp, o)) solv.add(ingress(policy2, p, qq, n)) if solv.check() == unsat: return None else: return solv.model(), (p, pp, qq), HEADER_INDEX
def not_empty(policy): """Determine if there are any packets that the policy forwards. RETURNS: None if not forwardable. (model, (p_in, p_out), HEADERS) if forwardable. """ p_in, p_out = Consts("p_in p_out", Packet) s = Solver() s.add(forwards(policy, p_in, p_out)) if s.check() == unsat: return None else: return (s.model(), (p_in, p_out), HEADER_INDEX)
def simulates_forwards2(topo, a, b, field="vlan", edge_policy={}): """Determine if b simulates a up to field on two hop on topo.""" p, pp, q, qq = Consts("p pp q qq", Packet) v, vv, vvv = Ints("v vv, vvv") # Z3 emits a warning about not finding a pattern for our quantification. # This is fine, so ignore it. set_option("WARNING", False) solv = Solver() solv.add(on_valid_port(topo, p)) # b doesn't need to forward packets on external links that don't satisfy the # ingress predicate, but we only care about the first hop edge_option = And(external_link(edge_policy, p), Not(edges_ingress(edge_policy, p))) # This case breaks the And inside the ForAll because they're both False and # z3 can't handle that. # However, it does mean that they simulate each other, because neither # forwards packets, so just short-circuit. if forwards(b, p, pp) is False and forwards(b, q, qq) is False: return None c = And( forwards(a, p, pp), transfer(topo, pp, q), forwards(a, q, qq), ForAll( [v, vv, vvv], Not( Or( And( forwards_with(b, p, {field: v}, pp, {field: vv}), forwards_with(b, q, {field: vv}, qq, {field: vvv}), ), edge_option, ) ), ), ) solv.add(c) if solv.check() == unsat: set_option("WARNING", True) return None else: set_option("WARNING", True) return solv.model(), (p, pp), HEADER_INDEX
def z3_graph_mapping(s: z3.Solver, g1: GraphZ3, g2: GraphZ3): map_f = z3.Function('Map', g1.nodes_s, g2.nodes_s) x = z3.Const('x', g1.nodes_s) y = z3.Const('y', g1.nodes_s) s.add([ z3.ForAll([x,y], g1.edges_f(x,y) == g2.edges_f(map_f(x), map_f(y))) ]) # Check if can satisfy the specified constraints if s.check(): # If can, get the model (i.e., a solution that satisfies the # constrains) for the Map function and return it m = s.model() for d in m.decls(): if d.name() == 'Map': iso_m = m[d] return iso_m raise RuntimeError("Isomorphism function not found in model") return None
def shared_transit(topo, policy1, policy2): """Try to find packet in the border of policy1 and the border of policy2. Note that this is symmetric. """ p, pp, ppp, qq, qqq = Consts("p pp ppp qq qqq", Packet) o, n = Ints("o n") solv = Solver() # Predicates are focused on a packet p, all other packets are fodder for # that one. We want an input packet p or an output packet after a hop p in # each case. solv.add(Or(ingress(policy1, p, pp, o), And(egress(policy1, pp, ppp), transfer(topo, ppp, p)))) solv.add(Or(ingress(policy2, p, qq, n), And(egress(policy2, qq, qqq), transfer(topo, qqq, p)))) if solv.check() == unsat: return None else: return solv.model(), (p,), HEADER_INDEX
def simulates_observes(topo, a, b, field="vlan", edge_policy={}): p = Const("p", Packet) o, v = Ints("o v") # Z3 emits a warning about not finding a pattern for our quantification. # This is fine, so ignore it. set_option("WARNING", False) solv = Solver() solv.add(on_valid_port(topo, p)) # b doesn't need to observe packets on external links that don't satisfy the # ingress predicate edge_option = And(external_link(edge_policy, p), Not(edges_ingress(edge_policy, p))) solv.add(And(observes(a, p, o), ForAll([v], Not(Or(observes_with(b, p, {field: v}, o), edge_option)), patterns=[]))) if solv.check() == unsat: set_option("WARNING", True) return None else: set_option("WARNING", True) return solv.model(), (p), HEADER_INDEX
def shared_io(topo, policy1, policy2): """Try to find output of policy1 in the inputs of policy2.""" p, pp, q, qq = Consts("p pp q qq", Packet) o = Int("o") solv = Solver() solv.add(output(policy1, p, pp)) solv.add(transfer(topo, pp, q)) solv.add(input(policy2, q, qq, o)) if solv.check() == unsat: return None else: return solv.model(), (p, pp, q, qq), HEADER_INDEX
def one_per_edge(topo, pol, field="vlan"): """Determine if pol only uses one value of field on each internal edge. We don't care about external edges because they never have the problem of preventing tiling of two-node connectivity since this slice never reads packets sent to them. """ p, pp, q, qq = Consts("p pp q qq", Packet) r, rr, s, ss = Consts("r rr s ss", Packet) solv = Solver() solv.add(forwards(pol, p, pp)) solv.add(transfer(topo, pp, r)) solv.add(forwards(pol, r, rr)) solv.add(forwards(pol, q, qq)) solv.add(switch(pp) == switch(qq)) solv.add(port(pp) == port(qq)) solv.add(HEADER_INDEX[field](pp) != HEADER_INDEX[field](qq)) if solv.check() == unsat: return None else: return solv.model(), (p, pp), HEADER_INDEX