def test_push_pop_vlan(self): """Test push pop combo is ignored rule1 rule2 M: VLAN_VID:1 M: VLAN_VID:1 A: PUSH_VLAN, VLAN_VID:2, POP_VLAN A: Expected rule1 Then try combos without rule1 match, and without rule2 match """ rule1 = Rule(priority=0, match=self.vlan_vid_1) rule1.instructions.apply_actions.append("PUSH_VLAN", 0x800) rule1.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 2)) rule1.instructions.apply_actions.append("POP_VLAN", None) rule2 = Rule(priority=0, match=self.vlan_vid_1) self.assertEqual(rule1.merge(rule2, False), rule1) # Check without rule2 match self.assertEqual(rule1.merge(Rule(priority=0), False), rule1) # Check without rule1 match rule1_nomatch = Rule(priority=0) rule1_nomatch.instructions.apply_actions = rule1.instructions.apply_actions.copy( ) self.assertEqual(rule1_nomatch.merge(rule2, False), rule1) # The resulting rule is valid openflow self.assertEqual(rule1_nomatch.merge(rule2, True), rule1) self.assertEqual(rule1_nomatch + rule2, rule1)
def test_merge_complex_match(self): """ Test merging """ part1 = Rule(priority=0, match=Match([("IPV4_SRC", 0x12340000, 0xFFFF0000)])) part2 = Rule(priority=0, match=Match([("IPV4_SRC", 0x00005678, 0x0000FFFF)])) expct = Rule(priority=0, match=Match([("IPV4_SRC", 0x12345678, 0xFFFFFFFF)])) self.assertEqual(part1 + part2, expct)
def test_simple(self): a = Rule(priority=0) b = Rule(priority=0) c = Rule(priority=1) self.assertEqual(a, b) self.assertNotEqual(a, c) with UniqueRules(): self.assertNotEqual(a, b) self.assertNotEqual(a, c) self.assertEqual(a, b) self.assertNotEqual(a, c)
def setUp(self): self.f = Rule() self.fg1 = Group() self.fg1.type_ = 'INDIRECT' self.fg1.buckets = [ActionList([("OUTPUT", 7)])] self.fg2 = Group() self.fg2.type_ = 'INDIRECT' self.fg2.buckets = [ActionList( [("POP_MPLS", 0x800), ("OUTPUT", 8)])] self.act_apply1 = ActionList( [('GROUP', self.fg1), ("SET_FIELD", ("ETH_DST", 0x1002)), ("OUTPUT", 1)]) self.act_apply2 = ActionList( [("SET_FIELD", ("ETH_DST", 0x1002)), ("OUTPUT", 4), ('GROUP', self.fg2)]) self.act_set1 = ActionSet( [("SET_FIELD", ("IPV4_SRC", 123)), ("OUTPUT", 5)]) self.act_set2 = ActionSet( [("SET_FIELD", ("IPV4_SRC", 321)), ("POP_VLAN", None), ("OUTPUT", 6)]) self.inst1 = Instructions() self.inst1.apply_actions = self.act_apply1 self.inst1.write_actions = self.act_set1 self.inst2 = Instructions() self.inst2.apply_actions = self.act_apply2 self.inst2.write_actions = self.act_set2
def test_equal_matches(self): matcha = Match([("VLAN_VID", 0x1001, None)]) matchb = Match([("VLAN_VID", 0x1001, None)]) f1 = Rule(priority=100, match=matcha, instructions=self.inst1, table=1) f2 = Rule(priority=100, match=matcha, instructions=self.inst2, table=2) res = simulate_actions(f1, f2) self.assertEqual(res[0], None) # act_apply1 + act_apply2 + merge(act_set1, act_set2) self.assertEqual(res[1], ActionList(self.act_apply1+self.act_apply2+(self.act_set1+self.act_set2))) self.assertEqual(res[2], None) print(self.act_set1) print(self.act_set2) print(self.act_set1 + self.act_set2)
def test_convert_flow1(self): """ Test converting OFPFlowMod and OFPFlowStats from ovs """ expected_insts = Instructions() expected_insts.apply_actions.append("POP_VLAN", None) expected_insts.apply_actions.append("SET_FIELD", ("ETH_DST", 0x101010101010)) expected_insts.apply_actions.append("OUTPUT", 7) expected_insts.goto_table = 4 expected = Rule(priority=789, cookie=0xABCD, table=3, match=Match([("ETH_TYPE", 0x0800, None), ("IPV4_SRC", 1, None), ("VLAN_VID", 0x1100, None)]), instructions=expected_insts) handcrafted = ( "priority=789 cookie=0xABCD table=3 eth_type=0x800,ip_src=0.0.0.1,vlan_vid=0x1100 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4" ) # After going in and out of OVS, ovs-ofctl -O OpenFlowXX dumpflows br0 ofctl10 = " cookie=0xabcd, duration=30.907s, table=3, n_packets=0, n_bytes=0, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=strip_vlan,mod_dl_dst:10:10:10:10:10:10,output:7,resubmit(,4)" ofctl13 = " cookie=0xabcd, duration=24.104s, table=3, n_packets=0, n_bytes=0, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4" ofctl13_nostats = " cookie=0xabcd, table=3, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4" ofctl15 = " cookie=0xabcd, duration=28.969s, table=3, n_packets=0, n_bytes=0, idle_age=28, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4" # From ovs-appctl bridge/dumpflows appctl = "table_id=3, duration=51s, n_packets=0, n_bytes=0, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1,actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4" rule_hand = rule_from_ovs(handcrafted, {}) rule_10 = rule_from_ovs(ofctl10, {}) rule_13 = rule_from_ovs(ofctl13, {}) rule_13nostats = rule_from_ovs(ofctl13_nostats, {}) rule_15 = rule_from_ovs(ofctl15, {}) rule_appctl = rule_from_ovs(appctl, {}) self.assertEqual(rule_hand, expected) self.assertEqual(rule_10, expected) self.assertEqual(rule_13, expected) self.assertEqual(rule_13nostats, expected) self.assertEqual(rule_15, expected) # appctl output doesn't display cookies expected.cookie = None self.assertEqual(rule_appctl, expected)
def test_convert_flow2(self): """ Test converting OFPFlowMod and OFPFlowStats from ovs """ match = Match([("IPV4_SRC", 1, None), ("ETH_TYPE", 0x800, None), ("VLAN_VID", 0x1100, None)]) instructions = Instructions() instructions.goto_table = 9 instructions.clear_actions = True instructions.write_actions.append("POP_VLAN", None) instructions.write_actions.append("SET_FIELD", ("ETH_DST", 0x101010101010)) instructions.write_actions.append("OUTPUT", 7) instructions.write_metadata = (0x99, 0xff) expected = Rule(priority=0x8000, cookie=0xABCD, table=0, match=match, instructions=instructions) # Now for write actions and clear actions etc. # Note priority=0x8000 is the default priority and ovs omits it, # Similarly table 0 is often omitted handcrafted = ( "priority=0x8000 cookie=0xABCD table=0 eth_type=0x800,ip_src=0.0.0.1,vlan_vid=0x1100 actions=clear_actions,write_actions(pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7),write_metadata:0x99/0xff,goto_table:9" ) ofctl11 = " cookie=0xabcd, duration=10.778s, table=0, n_packets=0, n_bytes=0, ip,dl_vlan=256,nw_src=0.0.0.1 actions=clear_actions,write_actions(pop_vlan,mod_dl_dst:10:10:10:10:10:10,output:7),write_metadata:0x99/0xff,goto_table:9" # Also the same for of12 to of15 ofctl13 = " cookie=0xabcd, duration=24.097s, table=0, n_packets=0, n_bytes=0, ip,dl_vlan=256,nw_src=0.0.0.1 actions=clear_actions,write_actions(pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7),write_metadata:0x99/0xff,goto_table:9" # From ovs-appctl bridge/dumpflows appctl = "duration=369s, n_packets=0, n_bytes=0, ip,dl_vlan=256,nw_src=0.0.0.1,actions=clear_actions,write_actions(pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7),write_metadata:0x99/0xff,goto_table:9" rule_hand = rule_from_ovs(handcrafted, {}) rule_11 = rule_from_ovs(ofctl11, {}) rule_13 = rule_from_ovs(ofctl13, {}) rule_appctl = rule_from_ovs(appctl, {}) self.assertEqual(rule_hand, expected) self.assertEqual(rule_11, expected) self.assertEqual(rule_13, expected) # appctl output doesn't display cookies expected.cookie = None self.assertEqual(rule_appctl, expected)
def test_action_independence_multiple(self): """ Testing messy combinations of mutliple set fields Combinations of multiple fields, some overlapping some not. And alternative values for different outputs. Base case: dst:0/31 -> dst:1, src:2, output:1, dst:2, src:1, output:2 """ DST1, DST2 = ('SET_FIELD', ('IPV4_DST', 0x1)), ('SET_FIELD', ('IPV4_DST', 0x2)) SRC1, SRC2 = ('SET_FIELD', ('IPV4_SRC', 0x1)), ('SET_FIELD', ('IPV4_SRC', 0x2)) OUT1, OUT2 = ('OUTPUT', 1), ('OUTPUT', 2) n1 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 0x0, 0xFFFFFFFE)]), instructions=inst_from_acts([DST1, SRC2, OUT1, DST2, SRC1, OUT2])), Rule(priority=0) ], match_redundancy=True) """ dst:1, src:2 -> output:1, dst:2, src:1, output:2 dst:0/31 -> dst:1, src:2, output:1, dst:2, src:1, output:2 """ n2 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 1, None), ('IPV4_SRC', 2, None)]), instructions=inst_from_acts([OUT1, DST2, SRC1, OUT2])), Rule(priority=9, match=Match([('IPV4_DST', 0x0, 0xFFFFFFFE)]), instructions=inst_from_acts([DST1, SRC2, OUT1, DST2, SRC1, OUT2])), Rule(priority=0) ], match_redundancy=True) """ dst:1 -> src:2, output:1, dst:2, src:1, output:2 dst:0/31 -> dst:1, src:2, output:1, dst:2, src:1, output:2 """ n3 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 1, None)]), instructions=inst_from_acts([SRC2, OUT1, DST2, SRC1, OUT2])), Rule(priority=9, match=Match([('IPV4_DST', 0x0, 0xFFFFFFFE)]), instructions=inst_from_acts([DST1, SRC2, OUT1, DST2, SRC1, OUT2])), Rule(priority=0) ], match_redundancy=True) self.assertTrue(check_equal(n1, n2)) self.assertTrue(check_equal(n2, n3)) self.assertTrue(check_equal(n1, n3))
def test_merging_priorities(self): """ Merging priorities is supposed to add priorities. Doing this works with merging rules (which have already been priority adjusted) to result in the correct priority. """ rule1 = Rule(priority=1) rule2 = Rule(priority=2) rule100 = Rule(priority=100) rule200 = Rule(priority=200) self.assertEqual(rule1.merge(rule1).priority, 2) self.assertEqual(rule1.merge(rule2).priority, 3) self.assertEqual(rule200.merge(rule100).priority, 300) self.assertEqual((rule200 + rule100).priority, 300)
def test_convert_flow(self): """ Test converting OFPFlowMod and OFPFlowStats from ryu """ ryu_match = OFPMatch(ipv4_src=1, eth_type=0x8100, vlan_vid=0x1100) ryu_write_actions = OFPInstructionActions(OFPIT_WRITE_ACTIONS, [ OFPActionPopVlan(), OFPActionSetField(eth_dst="10:10:10:10:10:10"), OFPActionOutput(7) ]) ryu_instructions = [ OFPInstructionActions(OFPIT_CLEAR_ACTIONS, []), ryu_write_actions, OFPInstructionGotoTable(9), OFPInstructionWriteMetadata(0x99, 0xff) ] ryu_flow_mod = OFPFlowMod(datapath=None, cookie=0xABCD, table_id=3, command=OFPFC_ADD, priority=789, match=ryu_match, instructions=ryu_instructions) ryu_flow_stats = OFPFlowStats(cookie=0xABCD, table_id=3, priority=789, match=ryu_match, instructions=ryu_instructions) match = Match([("IPV4_SRC", 1, None), ("ETH_TYPE", 0x8100, None), ("VLAN_VID", 0x1100, None)]) instructions = Instructions() instructions.goto_table = 9 instructions.clear_actions = True instructions.write_actions.append("POP_VLAN", None) instructions.write_actions.append("SET_FIELD", ("ETH_DST", 0x101010101010)) instructions.write_actions.append("OUTPUT", 7) instructions.write_metadata = (0x99, 0xff) rule = Rule(priority=789, cookie=0xABCD, table=3, match=match, instructions=instructions) self.assertEqual(rule_from_ryu(ryu_flow_stats), rule) self.assertEqual(rule_from_ryu(ryu_flow_mod), rule)
def test_find_metadata_conflicting_paths(self): """ Metadata rewrite conflicting paths test As metadata can be set bitwise it uses a slightly different code path to a standard set field. * MD=0x10 GT=1 0x10 MD=0x1/0x1 GT=2 0x11 output1 * * vs. single table, note all traffic is different no set * drop """ inst_a = Instructions() inst_a.goto_table = 1 inst_a.write_metadata = (0x12, None) inst_b = Instructions() inst_b.goto_table = 2 inst_b.write_metadata = (0x1, 0x3) # Note: Set VLAN applies the present bit mask so must included it ruleset_a = [ Rule(priority=10, table=0, instructions=inst_a), Rule(priority=10, table=1, match=Match([('METADATA', 0x12, None)]), instructions=inst_b), Rule(priority=0, table=1), Rule(priority=10, table=2, match=Match([('METADATA', 0x11, None)]), instructions=Instructions(dup=output1)), Rule(priority=0, table=2), ] ruleset_b = [ Rule(priority=0, table=0) ] single_a = to_single_table(ruleset_a) single_b = to_single_table(ruleset_b) norm_a = normalise(single_a) norm_b = normalise(single_b) # Make sure the frozensets are made after to_single_table which changes # priorities which changes the Rule's hash in the frozenset result_ab = { (ruleset_a[0], ruleset_a[1], ruleset_a[3]): frozenset([(ruleset_b[0],)]), } result_ba = { (ruleset_b[0],): frozenset([(ruleset_a[0], ruleset_a[1], ruleset_a[3],)]) } equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True) self.assertFalse(equal_ab) paths_ab = find_conflicting_paths(diff_ab, single_a, single_b) paths_ba = find_conflicting_paths(diff_ab, single_b, single_a) self.assertEqual(paths_ab, result_ab) self.assertNotEqual(paths_ab, result_ba) # Sanity check self.assertEqual(paths_ba, result_ba)
def test_find_rewrite_conflicting_paths(self): """ Rewritten conflicting paths test VLAN_VID=0 SetVLAN(1), goto1 VLAN_VID=0 output:1 * VLAN_VID=1 drop * vs. single table, note all traffic is different no set * output1 """ inst_a = Instructions() inst_a.goto_table = 1 inst_a.apply_actions.append("SET_FIELD", ("VLAN_VID", 1)) # Note: Set VLAN applies the present bit mask so must included it ruleset_a = [ Rule(priority=10, table=0, match=Match([('VLAN_VID', 0x1000 | 0, None)]), instructions=Instructions(dup=inst_a)), Rule(priority=0, table=0), Rule(priority=20, table=1, match=Match([('VLAN_VID', 0x1000 | 0, None)]), instructions=Instructions(dup=output1)), Rule(priority=20, table=1, match=Match([('VLAN_VID', 0x1000 | 1, None)])), Rule(priority=0, table=1) ] ruleset_b = [ Rule(priority=0, table=0, instructions=Instructions(dup=output1)) ] single_a = to_single_table(ruleset_a) single_b = to_single_table(ruleset_b) norm_a = normalise(single_a) norm_b = normalise(single_b) # Make sure the frozensets are made after to_single_table which changes # priorities which changes the Rule's hash in the frozenset result_ab = { (ruleset_a[0], ruleset_a[3]): frozenset([(ruleset_b[0],)]), (ruleset_a[1],): frozenset([(ruleset_b[0],)]) } result_ba = { (ruleset_b[0],): frozenset([(ruleset_a[0], ruleset_a[3]), (ruleset_a[1],)]) } equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True) self.assertFalse(equal_ab) paths_ab = find_conflicting_paths(diff_ab, single_a, single_b) paths_ba = find_conflicting_paths(diff_ab, single_b, single_a) self.assertEqual(paths_ab, result_ab) self.assertNotEqual(paths_ab, result_ba) # Sanity check self.assertEqual(paths_ba, result_ba)
def test_simple_merge_match(self): """ Test merging matches works """ fsrc2 = Rule(priority=0, match=self.ipv4_src2) fdst2 = Rule(priority=0, match=self.ipv4_dst2) fsrc1 = Rule(priority=0, match=self.ipv4_src1) f_expected = Rule(priority=0, match=self.ipv4_src2_dst2) # Merge with two different fields self.assertEqual(fsrc2.merge(fdst2), f_expected) self.assertEqual(fdst2.merge(fsrc2), f_expected) self.assertEqual(fsrc2 + fdst2, f_expected) self.assertEqual(fdst2 + fsrc2, f_expected) # Merge self with self self.assertEqual(fsrc2 + fsrc2, fsrc2) # Sanity checks self.assertNotEqual(fsrc2, fdst2) self.assertNotEqual(fsrc2 + fsrc2, fdst2) # Exception if match set becomes empty IP:1 intersect IP:2 self.assertRaises(MergeException, lambda: fsrc2 + fsrc1) self.assertRaises(MergeException, lambda: (fdst2 + fsrc1) + fsrc2)
def test_pop_vlan(self): """ Test the POP_VLAN, MATCH VLAN_VID case """ # Test single pop, i.e. match 2nd header rule1 = Rule(priority=0, match=self.vlan_vid_1) rule1.instructions.apply_actions.append("POP_VLAN", None) rule2 = Rule(priority=0, match=self.vlan_vid_2) expt_m = Match([("VLAN_VID", 0x1001, None), ("VLAN_VID1", 0x1002, None)]) expt = Rule(priority=0, match=expt_m) expt.instructions.apply_actions.append("POP_VLAN", None) self.assertEqual(rule1.merge(rule2, False), expt) # Merging fails to return a valid openflow rule self.assertRaises(MergeException, lambda: rule1.merge(rule2, True)) self.assertRaises(MergeException, lambda: rule1 + rule2)
def test_hash_sanity(self): rules = [ Rule(priority=0), Rule(priority=1), Rule(priority=3), Rule(priority=5), Rule(priority=10), Rule(priority=18) ] as_set = set(rules) as_dict = dict(zip(rules, range(6))) with UniqueRules(): unique_set = set(rules) unique_dict = dict(zip(rules, range(6))) # Sanity check everything will be hashed wrong, almost certain to fail self.assertNotEqual(as_set, unique_set) self.assertNotEqual(as_dict, unique_dict)
def test_set_and_match(self): """ Test the handling of setting a field which is later matched """ rule1 = Rule(priority=0, match=self.ipv4_src1) rule1.instructions.apply_actions.append("SET_FIELD", ("IPV4_SRC", 1)) rule2 = Rule(priority=0, match=self.ipv4_src2) rule2.instructions.apply_actions.append("SET_FIELD", ("IPV4_SRC", 1)) rule3 = Rule(priority=0, match=self.ipv4_src1) rule4 = Rule(priority=0, match=self.ipv4_src2) rule5 = Rule(priority=0, match=self.ipv4_src1m) rule6 = Rule(priority=0, match=self.ipv4_src2m) rule6.instructions.apply_actions.append("SET_FIELD", ("IPV4_SRC", 1)) """ Working case, check IPV4_SRC is removed Input: IPV4_SRC: 2 + IPV4_SRC: 1 Apply(set_field: IPV4_SRC = 1) + Apply() Expected: IPV4_SRC: 2 Apply(set_field: IPV4_SRC = 1) """ self.assertEqual(rule2 + rule3, rule2) # Check setting the field to the same is not removed self.assertEqual(rule1 + rule3, rule1) # And when masked self.assertEqual(rule1 + rule5, rule1) self.assertEqual(rule6 + rule5, rule6) """ Error case, check IPV4_SRC is removed Input: IPV4_SRC: 2 + IPV4_SRC: 2 Apply(set_field: IPV4_SRC = 1) + Apply() Expected: Error, Empty match. As traffic with IPV4_SRC: 1 cannot possibly match IPV4_SRC: 2 in the second rule. """ self.assertRaises(MergeException, lambda: rule1 + rule4) # And when masked self.assertRaises(MergeException, lambda: rule1 + rule6)
def test_push_vlan(self): """ Push and match, later pop """ rule1 = Rule(priority=0) rule1.instructions.apply_actions.append("PUSH_VLAN", 0x800) rule1.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 6)) rule2 = Rule(priority=0, match=Match([('VLAN_VID', 0x1006, None), ('VLAN_VID1', 0x1002, None)])) rule2.instructions.apply_actions.append("POP_VLAN", None) rule2.instructions.apply_actions.append("OUTPUT", 1) expt = Rule(priority=0, match=Match([('VLAN_VID', 0x1002, None)])) expt.instructions.apply_actions.append("PUSH_VLAN", 0x800) expt.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 6)) expt.instructions.apply_actions.append("POP_VLAN", None) expt.instructions.apply_actions.append("OUTPUT", 1) self.assertEqual(rule1.merge(rule2, False), expt) # The resulting rule is valid openflow self.assertEqual(rule1.merge(rule2, True), expt) self.assertEqual(rule1 + rule2, expt)
def test_clear_action_sets(self): rule1 = Rule(priority=0, match=self.ipv4_src2) rule1.instructions.write_actions.append("SET_FIELD", ("IPV4_SRC", 1)) rule2 = Rule(priority=0, match=self.ipv4_src2) rule2.instructions.write_actions.append("SET_FIELD", ("IPV4_SRC", 2)) rule3 = Rule(priority=0, match=self.ipv4_src2) rule3.instructions.write_actions.append("SET_FIELD", ("IPV4_DST", 2)) rule4 = rule3.copy() rule4.instructions.clear_actions = True # Check overwrite occurs self.assertEqual(rule1 + rule2, rule2) self.assertEqual(rule2 + rule1, rule1) # Check adding a mix is supported expected = rule1.copy() expected.instructions.write_actions.append("SET_FIELD", ("IPV4_DST", 2)) self.assertEqual(rule1 + rule3, expected) # sanity check self.assertNotEqual(rule1, expected) # Check Clear works self.assertEqual(rule1 + rule4, rule4)
output1 = Instructions() output1.apply_actions = ActionList([('OUTPUT', 6)]) goto1 = Instructions() goto1.goto_table = 1 goto2 = Instructions() goto2.goto_table = 2 """ arp -> drop ip=1 -> output:1 * -> drop """ data1 = [ Rule(priority=10, match=Match([('ETH_TYPE', 60, None)])), Rule(priority=9, match=Match([('IPV4_DST', 1, None)]), instructions=Instructions(dup=output1)), Rule(priority=0) ] """ ip=1, arp -> drop ip=1 -> output:1 * -> drop """ data2 = [ Rule(priority=10, match=Match([('IPV4_DST', 1, None), ('ETH_TYPE', 60, None)])),
def test_redundant_vlans(self): """ Test the push pop combinations are correctly removed """ PUSH, POP = ('PUSH_VLAN', 0x8100), ('POP_VLAN', None) VID1, VID2 = ('SET_FIELD', ('VLAN_VID', 1)), ('SET_FIELD', ('VLAN_VID', 2)) PCP5, OUT = ('SET_FIELD', ('VLAN_PCP', 5)), ('OUTPUT', 1) IP = ('SET_FIELD', ('IPV4_SRC', 1)) MATCH = Match([('IPV4_DST', 1, None)]) # Base case n1 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([VID1, IP, OUT])), Rule(priority=0) ]) # Check that VLAN set fields between PUSH and POP are removed, but not others n2 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([VID2, PUSH, IP, PCP5, POP, VID1, OUT])), Rule(priority=0) ]) self.assertTrue(check_equal(n1, n2)) # Check with multiple set fields between n3 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([IP, PUSH, VID2, VID2, POP, VID1, OUT])), Rule(priority=0) ]) self.assertTrue(check_equal(n1, n3)) # Check with nested push pop operations n4 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([IP, PUSH, PUSH, VID2, POP, VID2, POP, VID1, OUT])), Rule(priority=0) ]) self.assertTrue(check_equal(n1, n4)) # Check we don't remove a pop, push case!! # A very strict set of conditions would be needed to remove this: # 1). All fields are set after the push, that is VID and PCP, # and we keep the set fields, (or) # - All fields are set back to their original value based on the match # and this becomes a nop # 2). We can be sure that the push'd VLAN ethertype is the same # as the original VLAN ethertype # For now we don't account for this case as it is too complex n5 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([IP, POP, PUSH, VID2, OUT])), Rule(priority=0) ]) self.assertFalse(check_equal(n1, n5)) # We should remove any set fields before the POP n6 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([IP, VID1, PCP5, POP, PUSH, VID2, OUT])), Rule(priority=0) ]) self.assertTrue(check_equal(n5, n6)) # Check that a double tagging is correct and doesn't remove set_fields # push_vlan, vid: 1, push_vlan, vid: 2 n7 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([PUSH, VID1, PUSH, VID2, OUT])), Rule(priority=0) ]) # != push_vlan, push_vlan, vid:2 n8 = normalise([ Rule(priority=10, match=MATCH, instructions=inst_from_acts([PUSH, PUSH, VID2, OUT])), Rule(priority=0) ]) self.assertFalse(check_equal(n7, n8))
def test_redundant_set_field(self): """ Check that additional set fields are removed """ SF1, SF2 = ("SET_FIELD", ("IPV4_DST", 1)), ("SET_FIELD", ("IPV4_DST", 2)) SF3, SF4 = ("SET_FIELD", ("IPV4_DST", 3)), ("SET_FIELD", ("IPV4_DST", 4)) OUT = ("OUTPUT", 1) n1 = normalise([ Rule(priority=10, instructions=inst_from_acts([SF2, OUT])), Rule(priority=0) ]) n2 = normalise([ Rule(priority=10, instructions=inst_from_acts([SF1, SF2, OUT])), Rule(priority=0) ]) n3 = normalise([ Rule(priority=10, instructions=inst_from_acts([SF3, SF2, OUT])), Rule(priority=0) ]) n4 = normalise([ Rule(priority=10, instructions=inst_from_acts([SF4, SF3, SF1, SF2, OUT])), Rule(priority=0) ]) n5 = normalise([ Rule(priority=10, instructions=inst_from_acts([SF2, SF2, SF2, SF2, OUT])), Rule(priority=0) ]) self.assertTrue(check_equal(n1, n2)) self.assertTrue(check_equal(n1, n3)) self.assertTrue(check_equal(n1, n4)) self.assertTrue(check_equal(n1, n5)) # Sanity check n6 = normalise([ Rule(priority=10, instructions=inst_from_acts([SF4, SF3, SF1, SF1, OUT])), Rule(priority=0) ]) self.assertFalse(check_equal(n1, n6))
def test_push_pop_vlan_complex(self): """ Test complex combinations work associatively # rule1 # M: 1 # A: SET:2 # rule2 # M: * # A: Push: 6 # rule3 # M: 6 # A: POP # rule4 # M: 2 # A: Output:1 """ rule1 = Rule(priority=0, match=self.vlan_vid_1) rule1.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 2)) rule2 = Rule(priority=0) rule2.instructions.apply_actions.append("PUSH_VLAN", 0x800) rule2.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 6)) rule3 = Rule(priority=0, match=self.vlan_vid_6) rule3.instructions.apply_actions.append("POP_VLAN", None) rule4 = Rule(priority=0, match=self.vlan_vid_2) rule4.instructions.apply_actions.append("OUTPUT", 1) expt = Rule(priority=0, match=self.vlan_vid_1) expt.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 2)) expt.instructions.apply_actions.append("PUSH_VLAN", 0x800) expt.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 6)) expt.instructions.apply_actions.append("POP_VLAN", None) expt.instructions.apply_actions.append("OUTPUT", 1) self.assertEqual( rule1.merge(rule2, False).merge(rule3, False).merge(rule4, False), expt) self.assertEqual( rule1.merge(rule2.merge(rule3, False).merge(rule4, False), False), expt) self.assertEqual( rule1.merge(rule2.merge(rule3.merge(rule4, False), False), False), expt) self.assertEqual( rule1.merge(rule2.merge(rule3, False), False).merge(rule4, False), expt)
def test_action_independence_single(self): """ Test cases where the match cancels a set field """ SF1, OUT = ('SET_FIELD', ('IPV4_DST', 0x01010101)), ('OUTPUT', 6) DEC_TTL = ('DEC_NW_TTL', None) # 0.1.1.0/30 -> ip:1.1.1.1, output:1 n1 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]), instructions=inst_from_acts([SF1, OUT])), Rule(priority=0) ]) # 1.1.1.1/32 -> output:1 # 1.1.1.0/31 -> ip:1.1.1.1, output:1 n2 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 0x01010101, None)]), instructions=inst_from_acts([OUT])), Rule(priority=9, match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]), instructions=inst_from_acts([SF1, OUT])), Rule(priority=0) ]) # 1.1.1.0/32 -> ip:1.1.1.1, output1 # 1.1.1.0/31 -> output:1 n3 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 0x01010100, None)]), instructions=inst_from_acts([SF1, OUT])), Rule(priority=9, match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]), instructions=inst_from_acts([OUT])), Rule(priority=0) ]) n4 = normalise([ Rule(priority=10, match=Match([('IPV4_DST', 0x01010101, None)]), instructions=inst_from_acts([OUT])), Rule(priority=9, match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]), instructions=inst_from_acts([DEC_TTL, SF1, OUT])), Rule(priority=0) ]) self.assertTrue(check_equal(n1, n2)) self.assertFalse(check_equal(n1, n4)) self.assertTrue(check_equal(n2, n3)) self.assertTrue(check_equal(n1, n3))
def test_find_simple_conflicting_paths(self): """ Simple single table conflicting paths test Difference test ip=1 -> output:1 * -> drop vs. ip=1 -> drop * -> drop vs. * -> drop """ ruleset_a = [ Rule(priority=9, table=0, match=Match([('IPV4_DST', 1, None)]), instructions=Instructions(dup=output1)), Rule(priority=0, table=0) ] ruleset_b = [ Rule(priority=9, table=0, match=Match([('IPV4_DST', 1, None)])), Rule(priority=0, table=0) ] ruleset_c = [ Rule(priority=0, table=0) ] # Expected results result_ab = { (ruleset_a[0],): frozenset([(ruleset_b[0],)]) } result_ba = { (ruleset_b[0],): frozenset([(ruleset_a[0],)]) } result_ac = { (ruleset_a[0],): frozenset([(ruleset_c[0],)]) } result_ca = { (ruleset_c[0],): frozenset([(ruleset_a[0],)]) } single_a = to_single_table(ruleset_a) single_b = to_single_table(ruleset_b) single_c = to_single_table(ruleset_c) norm_a = normalise(single_a) norm_b = normalise(single_b) norm_c = normalise(single_c) equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True) self.assertFalse(equal_ab) equal_ac, diff_ac = check_equal(norm_a, norm_b, diff=True) self.assertFalse(equal_ac) self.assertTrue(check_equal(norm_b, norm_c)) paths_ab = find_conflicting_paths(diff_ab, single_a, single_b) self.assertEqual(paths_ab, result_ab) self.assertNotEqual(paths_ab, result_ba) # Sanity check paths_ba = find_conflicting_paths(diff_ab, single_b, single_a) self.assertEqual(paths_ba, result_ba) self.assertNotEqual(paths_ba, result_ab) # Sanity check paths_ca = find_conflicting_paths(diff_ac, single_c, single_a) self.assertEqual(paths_ca, result_ca) paths_ac = find_conflicting_paths(diff_ac, single_a, single_c) self.assertEqual(paths_ac, result_ac)
def test_metadata(self): rule1 = Rule(priority=0, match=self.ipv4_src2) rule1.instructions.write_metadata = (0xFEFCFDFA, None) rule2 = Rule(priority=0, match=self.ipv4_src2) rule2.instructions.write_metadata = (0x0000FFFF, None) # This is half the match as these are 64 byte rule3 = Rule(priority=0, match=self.ipv4_src2) rule3.instructions.write_metadata = (0xFEFCFDFA, 0xFFFFFFFF) rule4 = Rule(priority=0, match=self.ipv4_src2) rule4.instructions.write_metadata = (0x0000FFFF, 0xFFFFFFFF) rule5 = Rule(priority=0, match=self.ipv4_src2) rule5.instructions.write_metadata = (0xFEFCFDFA00000000, 0xFFFFFFFF00000000) rule6 = Rule(priority=0, match=self.ipv4_src2) rule6.instructions.write_metadata = (0xFEFCFDFA0000FFFF, 0xFFFFFFFFFFFFFFFF) rule7 = Rule(priority=0, match=self.ipv4_src2) rule7.instructions.write_metadata = (0x0, 0xFFFFFFFFFFFFFFF0) # Test cases self.assertEqual(rule1 + rule2, rule2) self.assertEqual(rule2 + rule1, rule1) self.assertEqual(rule1 + rule4, rule2) self.assertEqual(rule1 + rule3, rule1) self.assertEqual(rule3 + rule4, rule4) self.assertEqual(rule4 + rule5, rule6) self.assertEqual(rule5 + rule4, rule6) # Now check masking and matching # Check an invalid match will fail mrule1 = Rule(priority=0, match=self.meta_1) self.assertRaises(MergeException, lambda: rule1 + mrule1) # Merging partial metadata sets and matches # # The correct behaviour is to remove matching set values. # More correctly one should remove set bits, in the case of # metadata the result is a partial metadata match. # # Match: * + Metadata=(1, None) # Action: WriteMeta:(0x0, 0xFF..F0) + # # Expecting: # Match: Meta=(1,0xF) # Action: WriteMeta:(0x0, 0xFF..F0) expected = rule7.copy() expected.match.append('METADATA', 0x1, 0xF) self.assertEqual(rule7 + mrule1, expected) # Try a complex case d1 = Rule(priority=0, match=Match()) d1.instructions.write_metadata = (0x10, 0x10) d2 = Rule(priority=0, match=Match()) d2.instructions.write_metadata = (0x1, 0x1) d3 = Rule(priority=0, match=Match([('METADATA', 0x10, 0x10)])) d3.instructions.write_metadata = (0x100, 0x100) d4 = Rule(priority=0, match=Match([('METADATA', 0x111, 0x111)])) expected = Rule(priority=0, match=Match()) expected.instructions.write_metadata = (0x111, 0x111) self.assertEqual(d1 + d2 + d3 + d4, expected) self.assertEqual((d1 + d2) + (d3 + d4), expected) self.assertEqual(d1 + (d2 + d3) + d4, expected) self.assertEqual(d1 + (d2 + (d3 + d4)), expected) # This should fail if table the first is set to 0 d1.instructions.write_metadata = (0x00, 0x10) self.assertRaises(MergeException, lambda: d1 + d2 + d3 + d4) self.assertRaises(MergeException, lambda: (d1 + d2) + (d3 + d4)) self.assertRaises(MergeException, lambda: d1 + (d2 + d3) + d4) self.assertRaises(MergeException, lambda: d1 + (d2 + (d3 + d4))) # Check original metadata match + set's intersection is calculated # correctly rule8 = Rule(priority=0, match=Match([("METADATA", 0x1, 0x1)])) rule8.instructions.write_metadata = (0x0, 0x2) rule9 = Rule(priority=0, match=Match([("METADATA", 0x0, None)])) self.assertRaises(MergeException, lambda: rule8 + rule9) rule10 = Rule(priority=0, match=Match([("METADATA", 0x1, None)])) expected = Rule(priority=0, match=Match([("METADATA", 0x1, 0xFFFFFFFFFFFFFFFD)])) expected.instructions.write_metadata = (0x0, 0x2) self.assertEqual(rule8 + rule10, expected)
inst.write_actions = ActionSet(write_actions) if goto_table: inst.goto_table = goto_table return inst def priority_scale(first, second): """ This is how we scale priorities for a two table pipeline, if this changes we will need to update this code. """ return first * (MAX_PRIORITY) + second FORWARD_DROP = [ Rule(priority=10, table=0, match=Match([("IPV4_DST", 0x1, 0x3)]), instructions=build_inst(write_actions=[("OUTPUT", 1)], goto_table=1)), Rule(priority=9, table=0, match=Match([("IPV4_DST", 0x2, 0x7)]), instructions=build_inst(write_actions=[("OUTPUT", 2)], goto_table=1)), Rule(priority=0, table=0, match=Match()), Rule(priority=100, table=1, match=Match([("IPV4_DST", 0x8, 0x8)]), instructions=build_inst(clear_actions=True)), Rule(priority=0, table=1, match=Match()), ] # The expected single table FORWARD_DROP_SINGLE = [ Rule(priority=priority_scale(10, 100), table=0, # A + D match=Match([("IPV4_DST", 0x9, 0xb)]),
def test_find_multitable_conflicting_paths(self): """ Multitable conflicting paths test vlan:1 GT(1) ip:0/0xFFFFFFFE output1 ip:0 vlan:2 GT(2) ip:0 output1 (shadowed) ip:1 * * * === As single table === vlan:1,ip:0 output1 vlan:1,ip:1 output1 vlan:2,ip:0 vlan:2,ip:1 * vs. vlan:1,ip:0 vlan:1,ip:1 output1 vlan:2,ip:0 output1 vlan:2,ip:1 * """ ruleset_a = [ Rule(priority=10, table=0, match=Match([('VLAN_VID', 1, None)]), instructions=Instructions(dup=goto1)), Rule(priority=10, table=0, match=Match([('VLAN_VID', 2, None)]), instructions=Instructions(dup=goto2)), Rule(priority=0, table=0), Rule(priority=20, table=1, match=Match([('IPV4_DST', 0, 0xFFFFFFFE)]), instructions=Instructions(dup=output1)), Rule(priority=19, table=1, match=Match([('IPV4_DST', 0, None)]), instructions=Instructions(dup=output1)), Rule(priority=0, table=1), Rule(priority=30, table=2, match=Match([('IPV4_DST', 0, None)]), instructions=Instructions()), Rule(priority=30, table=2, match=Match([('IPV4_DST', 1, None)]), instructions=Instructions()), Rule(priority=0, table=2) ] ruleset_b = [ Rule(priority=14, table=0, match=Match([('VLAN_VID', 1, None), ('IPV4_DST', 0, None)])), Rule(priority=14, table=0, match=Match([('VLAN_VID', 1, None), ('IPV4_DST', 1, None)]), instructions=Instructions(dup=output1)), Rule(priority=14, table=0, match=Match([('VLAN_VID', 2, None), ('IPV4_DST', 0, None)]), instructions=Instructions(dup=output1)), Rule(priority=14, table=0, match=Match([('VLAN_VID', 2, None), ('IPV4_DST', 1, None)])), Rule(priority=0, table=0) ] single_a = to_single_table(ruleset_a) single_b = to_single_table(ruleset_b) norm_a = normalise(single_a) norm_b = normalise(single_b) result_ab = { (ruleset_a[0], ruleset_a[3]): frozenset([(ruleset_b[0],)]), (ruleset_a[1], ruleset_a[6]): frozenset([(ruleset_b[2],)]) } result_ba = { (ruleset_b[0],): frozenset([(ruleset_a[0], ruleset_a[3])]), (ruleset_b[2],): frozenset([(ruleset_a[1], ruleset_a[6])]) } equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True) self.assertFalse(equal_ab) equal_ba, diff_ba = check_equal(norm_b, norm_a, diff=True) self.assertFalse(equal_ba) paths_ab = find_conflicting_paths(diff_ab, single_a, single_b) paths_ba = find_conflicting_paths(diff_ab, single_b, single_a) self.assertEqual(paths_ab, result_ab) self.assertNotEqual(paths_ab, result_ba) # Sanity self.assertEqual(paths_ba, result_ba)
def test_find_vlans_conflicting_paths(self): """ VLAN rewrite paths test, check push/pop combinations Also due to internals of how this can be structured IN_PORT:1,VLAN:1 TCP:80: drop VLAN:1 output:10 IN_PORT:2 pushVLAN:2 * -> VLAN:2 pop output:11 IN_PORT:3 pushVLAN:1 IN_PORT:4,VLAN:2 * drop vs. A vlan can do things: IN_PORT:1,VLAN:1 VLAN:1 pop output:11 IN_PORT:2 pushVLAN:2 VLAN:2 output:10 IN_PORT:3 pushVLAN:1 IN_PORT:4,VLAN:2 * drop vs. single table, note all traffic is different no set * drop """ push_vlan2 = Instructions() push_vlan2.goto_table = 1 push_vlan2.apply_actions.append("PUSH_VLAN", 0x8100) push_vlan2.apply_actions.append("SET_FIELD", ("VLAN_VID", 2)) push_vlan1 = Instructions() push_vlan1.goto_table = 1 push_vlan1.apply_actions.append("PUSH_VLAN", 0x8100) push_vlan1.apply_actions.append("SET_FIELD", ("VLAN_VID", 1)) trunk10 = Instructions() trunk10.write_actions.append("OUTPUT", 11) access11 = Instructions() access11.write_actions.append("POP_VLAN", None) access11.write_actions.append("OUTPUT", 11) # Note: Set VLAN applies the present bit mask so must included it ruleset_a = [ Rule(priority=10, table=0, match=Match([('IN_PORT', 1, None), ('VLAN_VID', 0x1001, None)]), instructions=goto1), Rule(priority=10, table=0, match=Match([('IN_PORT', 2, None)]), instructions=push_vlan2), Rule(priority=10, table=0, match=Match([('IN_PORT', 3, None)]), instructions=push_vlan1), Rule(priority=10, table=0, match=Match([('IN_PORT', 4, None), ('VLAN_VID', 0x1002, None)]), instructions=goto1), Rule(priority=0, table=0), Rule(priority=10, table=1, match=Match([('TCP_SRC', 80, None)])), Rule(priority=0, table=1, instructions=goto2), Rule(priority=10, table=2, match=Match([('VLAN_VID', 0x1001, None)]), instructions=Instructions(dup=trunk10)), Rule(priority=10, table=2, match=Match([('VLAN_VID', 0x1002, None)]), instructions=Instructions(dup=access11)), Rule(priority=0, table=2), ] ruleset_b = [ Rule(priority=10, table=0, match=Match([('IN_PORT', 1, None), ('VLAN_VID', 0x1001, None)]), instructions=goto1), Rule(priority=10, table=0, match=Match([('IN_PORT', 2, None)]), instructions=push_vlan2), Rule(priority=10, table=0, match=Match([('IN_PORT', 3, None)]), instructions=push_vlan1), Rule(priority=10, table=0, match=Match([('IN_PORT', 4, None), ('VLAN_VID', 0x1002, None)]), instructions=goto1), Rule(priority=0, table=0), Rule(priority=10, table=1, match=Match([('VLAN_VID', 0x1001, None)]), instructions=Instructions(dup=access11)), Rule(priority=10, table=1, match=Match([('VLAN_VID', 0x1002, None)]), instructions=Instructions(dup=trunk10)), Rule(priority=0, table=1)] ruleset_c = [ Rule(priority=0, table=0) ] single_a = to_single_table(ruleset_a) single_b = to_single_table(ruleset_b) single_c = to_single_table(ruleset_c) norm_a = normalise(single_a) norm_b = normalise(single_b) norm_c = normalise(single_c) # Make sure the frozensets are made after to_single_table which changes # priorities which changes the Rule's hash in the frozenset result_ab = { (ruleset_a[0], ruleset_a[5]): frozenset([(ruleset_b[0], ruleset_b[5])]), (ruleset_a[0], ruleset_a[6], ruleset_a[7]): frozenset([(ruleset_b[0], ruleset_b[5])]), (ruleset_a[1], ruleset_a[5]): frozenset([(ruleset_b[1], ruleset_b[6])]), (ruleset_a[1], ruleset_a[6], ruleset_a[8]): frozenset([(ruleset_b[1], ruleset_b[6])]), (ruleset_a[2], ruleset_a[5]): frozenset([(ruleset_b[2], ruleset_b[5])]), (ruleset_a[2], ruleset_a[6], ruleset_a[7]): frozenset([(ruleset_b[2], ruleset_b[5])]), (ruleset_a[3], ruleset_a[5]): frozenset([(ruleset_b[3], ruleset_b[6])]), (ruleset_a[3], ruleset_a[6], ruleset_a[8]): frozenset([(ruleset_b[3], ruleset_b[6])]), } result_ba = { (ruleset_b[0], ruleset_b[5]): frozenset([(ruleset_a[0], ruleset_a[5]), (ruleset_a[0], ruleset_a[6], ruleset_a[7])]), (ruleset_b[1], ruleset_b[6]): frozenset([(ruleset_a[1], ruleset_a[5]), (ruleset_a[1], ruleset_a[6], ruleset_a[8])]), (ruleset_b[2], ruleset_b[5]): frozenset([(ruleset_a[2], ruleset_a[5]), (ruleset_a[2], ruleset_a[6], ruleset_a[7])]), (ruleset_b[3], ruleset_b[6]): frozenset([(ruleset_a[3], ruleset_a[5]), (ruleset_a[3], ruleset_a[6], ruleset_a[8])]), } result_ca = { (ruleset_c[0],): frozenset([(ruleset_a[0], ruleset_a[6], ruleset_a[7]), (ruleset_a[1], ruleset_a[6], ruleset_a[8]), (ruleset_a[2], ruleset_a[6], ruleset_a[7]), (ruleset_a[3], ruleset_a[6], ruleset_a[8])]) } equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True) equal_ca, diff_ca = check_equal(norm_c, norm_a, diff=True) self.assertFalse(equal_ab) self.assertFalse(equal_ca) paths_ab = find_conflicting_paths(diff_ab, single_a, single_b) paths_ba = find_conflicting_paths(diff_ab, single_b, single_a) paths_ca = find_conflicting_paths(diff_ca, single_c, single_a) self.assertEqual(paths_ab, result_ab) self.assertNotEqual(paths_ab, result_ba) # Sanity check self.assertEqual(paths_ba, result_ba) self.assertEqual(paths_ca, result_ca)
def I(goto=None, set_tcp=None): """ Short hand to generate instructions """ i = Instructions() i.goto_table = goto if set_tcp is not None: i.apply_actions.append('SET_FIELD', ('TCP_DST', set_tcp)) return i R2M = match_from_ryu COVISOR_RULESET = [ Rule( priority=5, match=R2M( OFPMatch( # 0 ipv4_src=("1.0.0.0", "255.255.255.0"), ipv4_dst="2.0.0.1"))), Rule( priority=4, match=R2M( OFPMatch( # 1 ipv4_src=("1.0.0.0", "255.255.255.0"), ipv4_dst="2.0.0.2"))), Rule( priority=3, match=R2M(OFPMatch( # 2 ipv4_src=("1.0.0.0", "255.255.255.0")))), Rule(priority=2, match=R2M(OFPMatch(ipv4_dst="2.0.0.1"))), # 3 Rule(priority=1, match=R2M(OFPMatch(ipv4_dst="2.0.0.2"))), # 4 Rule(priority=0) # 5