def test_masked_match_from_ovs(self): """ Test conversion of arbitrarily masked matches from ovs """ self.assertEqual(match_from_ovs('nw_dst=192.168.2.0/24'), Match([("IPV4_DST", 0xC0A80200, 0xFFFFFF00)])) self.assertEqual(match_from_ovs('nw_dst=192.168.2.0/245.255.255.0'), Match([("IPV4_DST", 0xC0A80200, 0xF5FFFF00)])) self.assertEqual( match_from_ovs('dl_src=01:00:00:00:00:00/01:00:00:00:00:00'), Match([("ETH_SRC", 0x010000000000, 0x010000000000)]))
def test_exact_match_from_ovs(self): """ Test conversion of an exact match from ovs """ self.assertEqual(match_from_ovs('in_port=1'), Match([("IN_PORT", 1, None)])) self.assertEqual(match_from_ovs('metadata=0x3'), Match([("METADATA", 3, None)])) self.assertEqual(match_from_ovs('dl_src=00:00:00:00:00:04'), Match([("ETH_SRC", 4, None)])) self.assertEqual(match_from_ovs('mpls_bos=1'), Match([("MPLS_BOS", 1, None)]))
def test_masked_match_from_ryu(self): """ Test conversion of arbitrarily masked matches from ryu """ self.assertEqual( match_from_ryu(OFPMatch(ipv4_dst=("192.168.2.0", "255.255.255.0"))), Match([("IPV4_DST", 0xC0A80200, 0xFFFFFF00)])) self.assertEqual( match_from_ryu( OFPMatch(eth_src=("01:00:00:00:00:00", "01:00:00:00:00:00"))), Match([("ETH_SRC", 0x010000000000, 0x010000000000)]))
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_exact_match_from_ryu(self): """ Test conversion of an exact match from ryu """ self.assertEqual(match_from_ryu(OFPMatch(in_port=1)), Match([("IN_PORT", 1, None)])) self.assertEqual(match_from_ryu(OFPMatch(in_phy_port=2)), Match([("IN_PHY_PORT", 2, None)])) self.assertEqual(match_from_ryu(OFPMatch(metadata=3)), Match([("METADATA", 3, None)])) self.assertEqual(match_from_ryu(OFPMatch(eth_src=4)), Match([("ETH_SRC", 4, None)])) self.assertEqual(match_from_ryu(OFPMatch(mpls_bos=7)), Match([("MPLS_BOS", 7, None)]))
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 setUp(self, _bdd): global wc_to_BDD global BDD global IS_DIFFERING global BDD_to_wcs wc_to_BDD = _bdd.wc_to_BDD BDD = _bdd.BDD BDD_to_wcs = _bdd.BDD_to_wcs self.IN_PORT0 = Match([('IN_PORT', 0, None)]).get_wildcard() self.IN_PORT1 = Match([('IN_PORT', 1, None)]).get_wildcard() self.IN_PORT0_ETH_SRC1 = Match([('IN_PORT', 0, None), ('ETH_SRC', 1, None)]).get_wildcard() self.IN_PORT0_ETH_DST1 = Match([('IN_PORT', 0, None), ('ETH_DST', 1, None)]).get_wildcard() self.IN_PORT0_ETH_DST1_ETH_SRC1 = Match([('IN_PORT', 0, None), ('ETH_DST', 1, None), ('ETH_SRC', 1, None) ]).get_wildcard() self.IN_PORT0_ETH_SRC1 = Match([('IN_PORT', 0, None), ('ETH_SRC', 1, None)]).get_wildcard() self.IN_PORT0_ETH_DST2 = Match([('IN_PORT', 0, None), ('ETH_DST', 2, None)]).get_wildcard() self.IN_PORT0_ETH_DST2_ETH_SRC1 = Match([('IN_PORT', 0, None), ('ETH_DST', 2, None), ('ETH_SRC', 1, None) ]).get_wildcard() self.zero_bdd = BDD() self.zero_bdd2 = BDD() self.DIFF = _bdd.IS_DIFFERING_TUPLE self.OF = OpenFlow1_3_5()
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_duplicate_empty(self): new = Match(self.empty_match) self.assertEqual(self.empty_match, new) # Check this works with sets (only one copy) self.assertEqual(len(set([self.empty_match, new])), 1) self.assertEqual(len(new), 0) self.assertEqual(new.required_mask, 0)
def test_duplicate_full(self): new = Match(self.fm_full) self.assertEqual(self.fm_full, new) # Check this works with sets (only one copy) self.assertEqual(len(set([self.fm_full, new])), 1) self.assertEqual(len(new), 2) self.assertEqual(bin(new.required_mask).count('1'), len(new))
def test_subset(self): self.assertTrue(Match([('VLAN_VID', 0x1, None)]).issubset( Match([('VLAN_VID', 0x1, None)]))) self.assertFalse(Match([('VLAN_VID', 0x2, None)]).issubset( Match([('VLAN_VID', 0x1, None)]))) self.assertFalse(Match([('VLAN_VID', 0x1, None)]).issubset( Match([('VLAN_VID', 0x1, None), ('IN_PORT', 0x1, None)]))) self.assertTrue(Match([('VLAN_VID', 0x1, None), ('IN_PORT', 0x1, None)]).issubset( Match([('VLAN_VID', 0x1, None)])))
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_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_special(self): # Attempt to a add a bad name and check this works correctly # assigning a unique yet constant numbering scheme full_mask = self.fm_full.required_mask match = Match() match.append('bad_field', 0x50, 0xfff0) self.fm_full.append('bad_field', 0x40, None) self.assertEqual(len(match), 1) self.assertEqual(bin(match.required_mask).count('1'), len(match)) self.assertEqual(len(self.fm_full), 3) self.assertEqual(bin(self.fm_full.required_mask).count('1'), len(self.fm_full)) # Check that the same bit gets set for both self.assertEqual(match.required_mask, self.fm_full.required_mask - full_mask) self.fm_full.append('bad_field2', 1, 1) self.assertEqual(len(self.fm_full), 4) self.assertEqual(bin(self.fm_full.required_mask).count('1'), len(self.fm_full))
def test_stress_meld_16(self): """ Check that we can combine 0-15 to create a /4 mask """ TA = "A" a = Match([('IN_PORT', 0, 0x1f)]) u_bdd = wc_to_BDD(a.get_wildcard(), TA, TA) rng = [4, 14, 9, 2, 5, 10, 7, 8, 12, 13, 3, 6, 15, 1, 11] for i in rng: m = Match([('IN_PORT', i, 0x1f)]) m_bdd = wc_to_BDD(m.get_wildcard(), TA, TA) n_bdd = u_bdd + m_bdd # Check the result of the opposite works, as these are # non-overlapping sections self.assertEqual(n_bdd, (m_bdd + u_bdd)) u_bdd = n_bdd r = Match([('IN_PORT', 0, 0x10)]) r_bdd = wc_to_BDD(r.get_wildcard(), TA, TA) # The 16 exact matches, should have combined into a single range self.assertEqual(u_bdd, r_bdd)
def test_stress_meld_66535(self): """ Check that we can combine 0-66535 to create a /16 mask """ TA = "A" a = Match([('IN_PORT', 0, 0x1ffff)]) u_bdd = wc_to_BDD(a.get_wildcard(), TA, TA) rng = range(1, 65536) random.seed(0) random.shuffle(list(rng)) for i in rng: m = Match([('IN_PORT', i, 0x1ffff)]) m_bdd = wc_to_BDD(m.get_wildcard(), TA, TA) n_bdd = u_bdd + m_bdd # Check the result of the opposite works, as these are # non-overlapping sections self.assertEqual(n_bdd, (m_bdd + u_bdd)) u_bdd = n_bdd r = Match([('IN_PORT', 0, 0x10000)]) r_bdd = wc_to_BDD(r.get_wildcard(), TA, TA) # The 16 exact matches, should have combined into a single range self.assertEqual(u_bdd, r_bdd)
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_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_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_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_append(self): match = Match() match.append('in_port', 1, None) self.assertEqual(len(match), 1) self.assertEqual(bin(match.required_mask).count('1'), len(match)) match.append('vlan_vid', 0x1000, 0x1000) self.assertEqual(len(match), 2) self.assertEqual(bin(match.required_mask).count('1'), len(match)) count = 0 for _ in match: count += 1 self.assertEqual(count, 2) self.assertEqual(match['in_port'][0:2], (1, None)) self.assertEqual(match['vlan_vid'][0:2], (0x1000, 0x1000)) # Ensure two uniqually copied versions work self.assertEqual(match, self.fm_full) self.assertEqual(len(set([match, self.fm_full])), 1)
def setUp(self): self.ipv4_src1 = Match([("IPV4_SRC", 1, None)]) self.ipv4_src2 = Match([("IPV4_SRC", 2, None)]) self.ipv4_dst2 = Match([("IPV4_DST", 2, None)]) self.ipv4_src2_dst2 = Match([("IPV4_SRC", 2, None), ("IPV4_DST", 2, None)]) self.ipv4_src1m = Match([("IPV4_SRC", 1, 0x1)]) self.ipv4_src2m = Match([("IPV4_SRC", 2, 0x2)]) self.meta_1 = Match([("METADATA", 1, None)]) self.meta_2m = Match([("METADATA", 2, 0x2)]) self.vlan_vid_1 = Match([("VLAN_VID", 0x1001, None)]) self.vlan_vid_2 = Match([("VLAN_VID", 0x1002, None)]) self.vlan_vid_6 = Match([("VLAN_VID", 0x1006, None)]) self.vlan_vid1_2 = Match([("VLAN_VID1", 0x1002, None)])
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)
if write_actions: 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
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 test_difference(self): """ Difference should return packet-space in A, which is either not present in B or results in a difference action. We return a special IS_DIFFERING when found, otherwise None. In the case of an empty space in A not matching empty in B we report this as a difference (XXX this might be the wrong thing to do). """ TA = "A" TB = "B" TC = "C" TD = "D" bdd_a = wc_to_BDD(self.IN_PORT0, TA, TA) bdd_b = wc_to_BDD(self.IN_PORT0, TB, TB) bdd_diff = wc_to_BDD(Match().get_wildcard(), *self.DIFF) port0_diff = wc_to_BDD(self.IN_PORT0, *self.DIFF) # The difference between the same is empty self.assertEqual(bdd_a.difference(bdd_a), self.zero_bdd) self.assertEqual(bdd_b.difference(bdd_b), self.zero_bdd) self.assertEqual(self.zero_bdd.difference(self.zero_bdd), self.zero_bdd) # The difference between a and b in the whole IN_PORT0 path # So we expect IN_PORT0 -> IS_DIFFERING back self.assertEqual(bdd_a.difference(bdd_b), port0_diff) # Difference is not symmetrical self.assertEqual(bdd_a.difference(self.zero_bdd), port0_diff) # Difference is not symmetrical # XXX Think more about this case, this might be the wrong behaviour # then again our solver code probably wont hit it. self.assertEqual(self.zero_bdd.difference(bdd_a), bdd_diff) # Lets merge up some rule combinations with small differences and see # if they are detected # Lets Always compare to this: # 1) in_port:0,eth_dst:1,eth_src:1 -> A # 2) in_port:0,eth_dst:2,eth_src:1 -> B # 3) in_port:0,eth_dst:1 -> C # 4) in_port:0 -> D bdd_comp_to = (wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, TA, TA) + wc_to_BDD(self.IN_PORT0_ETH_DST2_ETH_SRC1, TB, TB) + wc_to_BDD(self.IN_PORT0_ETH_DST1, TC, TC) + wc_to_BDD(self.IN_PORT0, TD, TD)) # Lets change 1) A -> B, we expect A -> DIFF as a result bdd_test = (wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, TB, TB) + wc_to_BDD(self.IN_PORT0_ETH_DST2_ETH_SRC1, TB, TB) + wc_to_BDD(self.IN_PORT0_ETH_DST1, TC, TC) + wc_to_BDD(self.IN_PORT0, TD, TD)) self.assertEqual( bdd_test.difference(bdd_comp_to), wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, *self.DIFF)) # Lets try BOTH 1 and 2 and 3 to -> D i.e. in_port 0 -> D bdd_test = wc_to_BDD(self.IN_PORT0, TD, TD) expt_diff = (wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, *self.DIFF) + wc_to_BDD(self.IN_PORT0_ETH_DST2_ETH_SRC1, *self.DIFF) + wc_to_BDD(self.IN_PORT0_ETH_DST1, *self.DIFF)) self.assertEqual(bdd_test.difference(bdd_comp_to), expt_diff)
def test_bit_order(self): """ Matches within a field will be prefix matching. As such ensure that we are encoding the MSB in the highest node. This will result in smaller tables, and faster build times. """ # Adding 0101/4 -> A and 1000/2 -> B # * # 0/ \1 # * * # \1 0/ # * B # 0/ # * # \1 # A # Total of 7 nodes inc. terminals # We expect this ordering a = Match([("IPV4_SRC", 0x5, 0xF)]) b = Match([("IPV4_SRC", 0x8, 0xC)]) TA = "A" TB = "B" bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA) bdd_b = wc_to_BDD(b.get_wildcard(), TB, TB) res = bdd_a + bdd_b self.assertEqual(len(res), 7) print(len(res)) # Adding the opposite 1010/4 -> A and 0001/1100 # Results in 8 nodes. a = Match([("IPV4_SRC", 0xA, 0xF)]) b = Match([("IPV4_SRC", 0x1, 0x3)]) TA = "A" TB = "B" bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA) bdd_b = wc_to_BDD(b.get_wildcard(), TB, TB) res = bdd_a + bdd_b self.assertEqual(len(res), 8) print(len(res))
def test_conversion_to_from_BDD(self): a = Match([("IPV4_SRC", 0x5, 0xF)]) TA = "A" bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA) res = list(BDD_to_wcs(bdd_a)) self.assertEqual(len(res), 1) self.assertEqual(res[0][0], a.get_wildcard()) # Check the first and last fields for off by 1 issues # Purposely checking 0xF on the ends as this ensures no off by one # error missing a bit # Check this first field a = Match([(self.OF.ordered_oxm_fields[0], 0xF445FA82FF38F92F, None)]) TA = "A" bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA) res = list(BDD_to_wcs(bdd_a)) self.assertEqual(len(res), 1) self.assertEqual(res[0][0], a.get_wildcard()) # Check the last field a = Match([(self.OF.ordered_oxm_fields[-1], 0xF445FA82FF38F92F, None)]) TA = "A" bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA) res = list(BDD_to_wcs(bdd_a)) self.assertEqual(len(res), 1) self.assertEqual(res[0][0], a.get_wildcard())