def setup_valve(self, config): """Set up test DP with config.""" self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.faucet_event_sock = os.path.join(self.tmpdir, 'event.sock') self.table = FakeOFTable(self.NUM_TABLES) logfile = os.path.join(self.tmpdir, 'faucet.log') self.logger = valve_util.get_logger(self.LOGNAME, logfile, logging.DEBUG, 0) self.registry = CollectorRegistry() # TODO: verify Prometheus variables self.metrics = faucet_metrics.FaucetMetrics(reg=self.registry) # pylint: disable=unexpected-keyword-arg # TODO: verify events self.notifier = faucet_experimental_event.FaucetExperimentalEventNotifier( self.faucet_event_sock, self.metrics, self.logger) self.bgp = faucet_bgp.FaucetBgp(self.logger, self.metrics, self.send_flows_to_dp_by_id) self.valves_manager = valves_manager.ValvesManager( self.LOGNAME, self.logger, self.metrics, self.notifier, self.bgp, self.send_flows_to_dp_by_id) self.notifier.start() self.update_config(config) self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(self.faucet_event_sock) self.connect_dp()
def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(self.CONFIG) self.valve = valve_factory(dp)(dp, 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect(self.DP_ID, range(1, self.NUM_PORTS + 1)) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 })
def setup_valve(self, config): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) self.faucet_event_sock = None self.logger = None self.metrics = None self.notifier = faucet_experimental_event.FaucetExperimentalEventNotifier( self.faucet_event_sock, self.metrics, self.logger) dp = self.update_config(config) self.valve = valve_factory(dp)(dp, 'test_valve', self.notifier)
def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(self.CONFIG) self.valve = valve_factory(dp)(dp, 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect( self.DP_ID, range(1, self.NUM_PORTS + 1) ) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 })
def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(7) with open(self.config_file, 'w') as f: f.write(self.CONFIG) _, dps = dp_parser(self.config_file, 'test_valve') self.valve = valve_factory(dps[0])(dps[0], 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect( self.DP_ID, range(1, self.NUM_PORTS + 1) ) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 200 }) self.rcv_packet(3, 200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 200 })
def setup_valve(self, config): """Set up test DP with config.""" self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.faucet_event_sock = os.path.join(self.tmpdir, 'event.sock') self.logfile = os.path.join(self.tmpdir, 'faucet.log') self.table = FakeOFTable(self.NUM_TABLES) self.logger = valve_util.get_logger('faucet', self.logfile, logging.DEBUG, 0) self.registry = CollectorRegistry() # TODO: verify Prometheus variables self.metrics = faucet_metrics.FaucetMetrics(reg=self.registry) # pylint: disable=unexpected-keyword-arg # TODO: verify events self.notifier = faucet_experimental_event.FaucetExperimentalEventNotifier( self.faucet_event_sock, self.metrics, self.logger) self.notifier.start() dp = self.update_config(config, self.DP) self.valve = valve_factory(dp)(dp, 'test_valve', self.notifier) self.valve.update_config_metrics(self.metrics) self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(self.faucet_event_sock)
def setUp(self): dp = DP.parser("tests/config/valve-test.yaml") self.valve = OVSStatelessValve(dp) self.table = FakeOFTable() self.table.apply_ofmsgs(self.valve.datapath_connect(1, [1,2,3,4,5,6])) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=1, vlan_vid=10, eth_src="00:00:00:00:00:01", eth_dst="00:00:00:00:00:02") self.table.apply_ofmsgs(rcv_packet_ofmsgs) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=3, vlan_vid=11, eth_src="00:00:00:00:00:03", eth_dst="00:00:00:00:00:04") self.table.apply_ofmsgs(rcv_packet_ofmsgs)
class ValveReloadConfigTestCase(ValveTestCase): '''Repeats the tests after a config reload''' OLD_CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 tagged_vlans: [v100, v200] p2: number: 2 native_vlan: v100 p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 vlans: v100: vid: 0x100 v200: vid: 0x200 """ def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_reload_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(self.OLD_CONFIG) self.valve = valve_factory(dp)(dp, 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect(self.DP_ID, range(1, self.NUM_PORTS + 1)) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x100, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x100, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 }) # reload the config new_dp = self.update_config(self.CONFIG) cold_start, ofmsgs = self.valve.reload_config(new_dp) self.table.apply_ofmsgs(ofmsgs) # relearn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 })
class ValveTestBase(unittest.TestCase): CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 native_vlan: v100 p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 vlans: v100: vid: 0x100 v200: vid: 0x200 """ DP_ID = 1 NUM_PORTS = 5 NUM_TABLES = 9 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' V100 = 0x100|ofp.OFPVID_PRESENT V200 = 0x200|ofp.OFPVID_PRESENT def update_config(self, config): with open(self.config_file, 'w') as f: f.write(config) _, dps = dp_parser(self.config_file, 'test_valve') return dps[0] def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(self.CONFIG) self.valve = valve_factory(dp)(dp, 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect( self.DP_ID, range(1, self.NUM_PORTS + 1) ) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 }) def rcv_packet(self, port, vid, match): pkt = build_pkt(match) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, valves={}, in_port=port, vlan_vid=vid, pkt=pkt ) self.table.apply_ofmsgs(rcv_packet_ofmsgs) def tearDown(self): shutil.rmtree(self.tmpdir)
class ValveReloadConfigTestCase(ValveTestCase): '''Repeats the tests after a config reload''' OLD_CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 tagged_vlans: [v100, v200] p2: number: 2 native_vlan: v100 p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 vlans: v100: vid: 0x100 v200: vid: 0x200 """ def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_reload_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(self.OLD_CONFIG) self.valve = valve_factory(dp)(dp, 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect( self.DP_ID, range(1, self.NUM_PORTS + 1) ) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x100, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x100, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 }) # reload the config new_dp = self.update_config(self.CONFIG) ofmsgs = self.valve.reload_config(new_dp) self.table.apply_ofmsgs(ofmsgs) # relearn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 })
class ValveTestBase(unittest.TestCase): CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 native_vlan: v100 p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 tagged_vlans: [v300] s2: hardware: 'Open vSwitch' dp_id: 0xdeadbeef interfaces: p1: number: 1 native_vlan: v100 routers: router1: vlans: [v100, v200] vlans: v100: vid: 0x100 faucet_vips: ['10.0.0.254/24'] routes: - route: ip_dst: 10.99.99.0/24 ip_gw: 10.0.0.1 v200: vid: 0x200 faucet_vips: ['fc00::1:254/112'] routes: - route: ip_dst: "fc00::10:0/112" ip_gw: "fc00::1:1" v300: vid: 0x300 """ DP_ID = 1 NUM_PORTS = 5 NUM_TABLES = 9 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' V100 = 0x100 | ofp.OFPVID_PRESENT V200 = 0x200 | ofp.OFPVID_PRESENT def setup_valve(self, config): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) self.faucet_event_sock = None self.logger = None self.metrics = None self.notifier = faucet_experimental_event.FaucetExperimentalEventNotifier( self.faucet_event_sock, self.metrics, self.logger) dp = self.update_config(config) self.valve = valve_factory(dp)(dp, 'test_valve', self.notifier) def update_config(self, config): with open(self.config_file, 'w') as config_file: config_file.write(config) _, dps = dp_parser(self.config_file, 'test_valve') return [dp for dp in dps if dp.name == 's1'][0] def connect_dp(self): port_nos = range(1, self.NUM_PORTS + 1) self.table.apply_ofmsgs(self.valve.datapath_connect(port_nos)) for port_no in port_nos: self.set_port_up(port_no) def apply_new_config(self, config): new_dp = self.update_config(config) _, ofmsgs = self.valve.reload_config(new_dp) self.table.apply_ofmsgs(ofmsgs) def set_port_down(self, port_no): self.table.apply_ofmsgs( self.valve.port_status_handler(port_no, ofp.OFPPR_DELETE, None)) def set_port_up(self, port_no): self.table.apply_ofmsgs( self.valve.port_status_handler(port_no, ofp.OFPPR_ADD, None)) def flap_port(self, port_no): self.set_port_down(port_no) self.set_port_up(port_no) def arp_for_controller(self): self.rcv_packet( 1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': mac.BROADCAST_STR, 'arp_source_ip': '10.0.0.1', 'arp_destination_ip': '10.0.0.254' }) def learn_hosts(self): """Learn some hosts.""" self.arp_for_controller() self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 }) def setUp(self): self.setup_valve(self.CONFIG) self.connect_dp() self.learn_hosts() def rcv_packet(self, port, vid, match): pkt, eth_type = build_pkt(match) pkt.serialize() eth_pkt = valve_packet.parse_eth_pkt(pkt) pkt_meta = self.valve.parse_rcv_packet(port, vid, eth_type, pkt.data, len(pkt.data), pkt, eth_pkt) rcv_packet_ofmsgs = self.valve.rcv_packet(other_valves=[], pkt_meta=pkt_meta) self.table.apply_ofmsgs(rcv_packet_ofmsgs) resolve_ofmsgs = self.valve.resolve_gateways() self.table.apply_ofmsgs(resolve_ofmsgs) self.valve.advertise() self.valve.state_expire() def tearDown(self): shutil.rmtree(self.tmpdir)
class ValveTestCase(unittest.TestCase): def setUp(self): dp = DP.parser("tests/config/valve-test.yaml") self.valve = OVSStatelessValve(dp) self.table = FakeOFTable() self.table.apply_ofmsgs(self.valve.datapath_connect(1, [1,2,3,4,5,6])) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=1, vlan_vid=10, eth_src="00:00:00:00:00:01", eth_dst="00:00:00:00:00:02") self.table.apply_ofmsgs(rcv_packet_ofmsgs) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=3, vlan_vid=11, eth_src="00:00:00:00:00:03", eth_dst="00:00:00:00:00:04") self.table.apply_ofmsgs(rcv_packet_ofmsgs) def test_drop_rule(self): """Test that packets with incorrect vlan tagging get dropped. Packets arriving on a tagged port with vlan tags that are not configured on that port should be dropped.""" drop_matches = [ {'in_port': 3, 'vlan_vid': 92398012983}, { 'in_port': 3, 'vlan_vid': 92398012983, 'eth_src': "00:00:00:00:00:03"}, {'in_port': 3, 'vlan_vid': 10|ofp.OFPVID_PRESENT}, {'in_port': 2}] for drop_match in drop_matches: self.assertFalse( self.table.is_output(drop_match), msg="Packets with incorrect vlan tags are output") def test_unknown_eth_src_rule_tagged(self): """Test that tagged packets from unknown macs are sent to controller. """ matches = [ {'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT}, {'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT}, { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst' : "00:00:00:00:00:03"}, {'in_port': 2, 'vlan_vid': 10|ofp.OFPVID_PRESENT}] for match in matches: self.assertTrue( self.table.is_output(match, ofp.OFPP_CONTROLLER), msg="Packet with unknown ethernet src not sent to controller") def test_unknown_eth_src_rule_untagged(self): """Test that untagged packets with unknown macs are sent to controller. Untagged packets should have VLAN tags pushed before they are sent to the controler. """ matches = [ {'in_port': 4, 'eth_dst' : "00:00:00:00:00:03"}, {'in_port': 4}, {'in_port': 1}, {'in_port': 1}] for match in matches: self.assertTrue( self.table.is_output(match, ofp.OFPP_CONTROLLER), msg="Packets with unknown ethernet src not sent to controller") def test_unknown_eth_dst_rule(self): """Test that packets with unkown eth dst addrs get flooded correctly. They must be output to each port on the associated vlan, with the correct vlan tagging.""" matches = [ {'in_port': 4}, {'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT}, { 'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_src': "00:00:00:00:00:03"}, {'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT}, {'in_port': 2, 'vlan_vid': 10|ofp.OFPVID_PRESENT}, {'in_port': 1, 'eth_src': "00:00:00:00:00:01"}, {'in_port': 1}] dp = self.valve.dp for match in matches: in_port = match['in_port'] if 'vlan_vid' in match: vlan = dp.vlans[match['vlan_vid'] & ~ofp.OFPVID_PRESENT] else: vlan = self.valve.dp.get_native_vlan(in_port) remaining_ports = set(range(1, 6)) # Check packets are output to each port on vlan for p in vlan.get_ports(): remaining_ports.discard(p.number) if p.number != in_port and p.running(): if vlan.port_is_tagged(p.number): vid = vlan.vid|ofp.OFPVID_PRESENT else: vid = 0 self.assertTrue( self.table.is_output(match, port=p.number, vlan=vid), msg="packet with unknown eth dst ({0}) not output " "correctly on vlan {1} to port {2}".format(match, vlan.vid, p.number)) # Check packets are not output to ports not on vlan for p in remaining_ports: self.assertFalse( self.table.is_output(match, port=p), msg="packet with unkown eth dst output to port not on its vlan ({0})".format(p)) def test_known_eth_src_rule(self): """test that packets with known eth src addrs are not sent to controller.""" matches = [ { 'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_src': "00:00:00:00:00:03"}, { 'in_port': 1, 'eth_src': "00:00:00:00:00:01"}] for match in matches: self.assertFalse( self.table.is_output(match, port=ofp.OFPP_CONTROLLER), msg="Packet output to controller when eth_src address is known") def test_known_eth_src_deletion(self): """Verify that when a mac changes port the old rules get deleted. If a mac address is seen on one port, then seen on a different port on the same vlan the rules associated with that mac address on previous port need to be deleted. IE packets with that mac address arriving on the old port should be output to the controller.""" rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=2, vlan_vid=11, eth_src="00:00:00:00:00:03", eth_dst="00:00:00:00:00:04") self.table.apply_ofmsgs(rcv_packet_ofmsgs) match = { 'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_src': "00:00:00:00:00:03"} self.assertTrue( self.table.is_output(match, ofp.OFPP_CONTROLLER), msg='eth src rule not deleted when mac seen on another port') def test_known_eth_src_vlan_separation(self): """Test that when a mac is seen on a second vlan the original vlan rules are unaffected.""" rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=2, vlan_vid=10, eth_src="00:00:00:00:00:03", eth_dst="00:00:00:00:00:04") self.table.apply_ofmsgs(rcv_packet_ofmsgs) match = { 'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_src': "00:00:00:00:00:03"} self.assertFalse( self.table.is_output(match, ofp.OFPP_CONTROLLER), msg="mac address being seen on a vlan interferes with rules on other vlans") def test_known_eth_dst_rule(self): """Test that packets with known eth dst addrs are output correctly. Output to the correct port with the correct vlan tagging.""" tagged_matches = [ { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst': "00:00:00:00:00:03"}, { 'in_port': 4, 'eth_dst': "00:00:00:00:00:03"}] for tagged_match in tagged_matches: self.assertTrue( self.table.is_output(tagged_match, port=3, vlan=11|ofp.OFPVID_PRESENT), msg="packet not output to untagged port correctly when eth dst is known") for port in [1, 2, 4, 5, 6]: self.assertFalse( self.table.is_output(tagged_match, port=port), msg="packet output to incorrect port when eth dst is known") untagged_match = { 'in_port': 2, 'vlan_vid': 10|ofp.OFPVID_PRESENT, 'eth_dst': "00:00:00:00:00:01"} self.assertTrue( self.table.is_output(untagged_match, port=1, vlan=0), msg="packet not output to tagged port correctly when eth dst is known") for port in range(2, 7): self.assertFalse( self.table.is_output(untagged_match, port=port), msg="packet output to incorrect port when eth dst is known") def test_known_eth_dst_rule_deletion(self): """Test that eth dst rules are deleted when the mac is learned on another port. This should only occur when the mac is seen on the same vlan.""" rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=2, vlan_vid=11, eth_src="00:00:00:00:00:03", eth_dst="00:00:00:00:00:04") self.table.apply_ofmsgs(rcv_packet_ofmsgs) match = { 'in_port': 4, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst': "00:00:00:00:00:03"} self.assertFalse( self.table.is_output(match, port=3), msg="Packet output on old port after mac is learnt on new port") def test_multicast_eth_src_rcv_packet(self): """Test that no rules are installed in for packets with multicast eth src.""" self.assertEqual( [], self.valve.rcv_packet( dp_id=1, in_port=2, vlan_vid=10, eth_src="01:00:00:00:00:01", eth_dst="00:00:00:00:00:02")) def test_bpdu_drop(self): """Test that STP BPDUs are dropped.""" matches = [ { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst': "01:80:C2:00:00:00"}, { 'in_port': 4, 'eth_dst': "01:00:0C:CC:CC:CD"}] for match in matches: self.assertFalse( self.table.is_output(match), msg="STP BPDU output") def test_lldp_drop(self): """Test that LLDP packets are dropped.""" match = { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_type': ether.ETH_TYPE_LLDP} self.assertFalse( self.table.is_output(match), msg="LLDP packet output") def test_port_delete(self): """Test that when a port is disabled packets are correctly output. """ match = { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst': "00:00:00:00:00:03"} vlan = self.valve.dp.vlans[match['vlan_vid'] & ~ofp.OFPVID_PRESENT] ofmsgs = self.valve.port_delete(dp_id=1, portnum=3) self.table.apply_ofmsgs(ofmsgs) # Check packets are output to each port on vlan for p in vlan.get_ports(): if p.number != match['in_port'] and p.running(): if vlan.port_is_tagged(p.number): vid = vlan.vid|ofp.OFPVID_PRESENT else: vid = 0 self.assertTrue( self.table.is_output(match, port=p.number, vlan=vid), msg="packet ({0}) with eth dst learnt on deleted port not output " "correctly on vlan {1} to port {2}".format(match, vlan.vid, p.number)) def test_port_add_input(self): """test that when a port is enabled packets are input correctly.""" match = {'in_port': 7} ofmsgs = self.valve.port_add(dp_id=1, portnum=7) self.table.apply_ofmsgs(ofmsgs) self.assertTrue( self.table.is_output(match), msg="Packet arriving on port after add not output") def test_port_add_flood(self): """test that when a port is enabled packets are correctly output.""" match = {'in_port': 5} ofmsgs = self.valve.port_add(dp_id=1, portnum=7) self.table.apply_ofmsgs(ofmsgs) self.assertTrue( self.table.is_output(match, port=7), msg="Packet not output to port after add") def test_reload_drop(self): """Test that after a config reload packets with invalid vlan tags are dropped. """ match = { 'in_port': 3, 'vlan_vid': 11|ofp.OFPVID_PRESENT} new_dp = DP.parser("tests/config/valve-test-reload.yaml") ofmsgs = self.valve.reload_config(new_dp) self.table.apply_ofmsgs(ofmsgs) self.assertFalse( self.table.is_output(match), msg='Output action when packet should be dropped after reload') def test_reload_unknown_eth_dst_rule(self): """Test that packets with unkown eth dst addrs get flooded correctly after a config reload. They must be output to each currently running port on the associated vlan, with the correct vlan tagging.""" matches = [ {'in_port': 4}, {'in_port': 3, 'vlan_vid': 10|ofp.OFPVID_PRESENT}, { 'in_port': 3, 'vlan_vid': 10|ofp.OFPVID_PRESENT, 'eth_src': "00:00:00:00:00:01"}, {'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT}, { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst': "00:00:00:00:00:01"}, {'in_port': 2, 'vlan_vid': 10|ofp.OFPVID_PRESENT}, {'in_port': 1, 'eth_src': "00:00:00:00:00:03"}, {'in_port': 1}] dp = DP.parser("tests/config/valve-test-reload.yaml") ofmsgs = self.valve.reload_config(dp) self.table.apply_ofmsgs(ofmsgs) for match in matches: in_port = match['in_port'] if 'vlan_vid' in match: vlan = dp.vlans[match['vlan_vid'] & ~ofp.OFPVID_PRESENT] else: # if a tagged port arrives on an untagged interface, we can # ignore the label vlan = dp.get_native_vlan(in_port) # the ports that have not yet had packets output to them remaining_ports = set(range(1, 7)) for p in vlan.get_ports(): remaining_ports.discard(p.number) if p.number != in_port and p.running(): if vlan.port_is_tagged(p.number): vid = vlan.vid|ofp.OFPVID_PRESENT else: vid = 0 self.assertTrue( self.table.is_output(match, port=p.number, vlan=vid), msg="packet ({0}) not output correctly to port {1} on " "vlan {2} when flooding after reload".format(match, p.number, vid)) for p in remaining_ports: self.assertFalse( self.table.is_output(match, p), msg="packet output to port not on vlan after reload") def test_reload_port_disable(self): """Test that when a port is disabled in a reload packets are not output to it. """ matches = [ {'in_port': 4}, { 'in_port': 2, 'vlan_vid': 11|ofp.OFPVID_PRESENT, 'eth_dst': "00:00:00:00:00:05"}] rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, in_port=5, vlan_vid=11, eth_src="00:00:00:00:00:05", eth_dst="00:00:00:00:00:06") self.table.apply_ofmsgs(rcv_packet_ofmsgs) dp = DP.parser("tests/config/valve-test-reload.yaml") ofmsgs = self.valve.reload_config(dp) self.table.apply_ofmsgs(ofmsgs) for match in matches: self.assertFalse( self.table.is_output(match, port=5), msg="packet output to disabled port")
class ValveTestBase(unittest.TestCase): CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 native_vlan: v100 p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 vlans: v100: vid: 0x100 faucet_vips: ['10.0.0.254/24'] routes: - route: ip_dst: 10.99.99.0/24 ip_gw: 10.0.0.1 v200: vid: 0x200 faucet_vips: ['fc00::1:254/112'] routes: - route: ip_dst: "fc00::10:0/112" ip_gw: "fc00::1:1" """ DP_ID = 1 NUM_PORTS = 5 NUM_TABLES = 9 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' V100 = 0x100|ofp.OFPVID_PRESENT V200 = 0x200|ofp.OFPVID_PRESENT def setup_valve(self, config): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(config) self.valve = valve_factory(dp)(dp, 'test_valve') def update_config(self, config): with open(self.config_file, 'w') as config_file: config_file.write(config) _, dps = dp_parser(self.config_file, 'test_valve') return dps[0] def connect_dp(self): self.table.apply_ofmsgs(self.valve.datapath_connect( self.DP_ID, range(1, self.NUM_PORTS + 1))) def apply_new_config(self, config): new_dp = self.update_config(config) _, ofmsgs = self.valve.reload_config(new_dp) self.table.apply_ofmsgs(ofmsgs) def flap_port(self, port_no): self.table.apply_ofmsgs(self.valve.port_status_handler( self.DP_ID, port_no, ofp.OFPPR_DELETE, None)) self.table.apply_ofmsgs(self.valve.port_status_handler( self.DP_ID, port_no, ofp.OFPPR_ADD, None)) def learn_hosts(self): """Learn some hosts.""" self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC}) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200}) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200}) def setUp(self): self.setup_valve(self.CONFIG) self.connect_dp() self.learn_hosts() def rcv_packet(self, port, vid, match): pkt, eth_type = build_pkt(match) pkt.serialize() eth_pkt = valve_packet.parse_eth_pkt(pkt) pkt_meta = self.valve.parse_rcv_packet( port, vid, eth_type, pkt.data, pkt, eth_pkt) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=self.DP_ID, valves={}, pkt_meta=pkt_meta) self.table.apply_ofmsgs(rcv_packet_ofmsgs) resolve_ofmsgs = self.valve.resolve_gateways() self.table.apply_ofmsgs(resolve_ofmsgs) self.valve.advertise() self.valve.host_expire() def tearDown(self): shutil.rmtree(self.tmpdir)
def setup_valve(self, config): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(config) self.valve = valve_factory(dp)(dp, 'test_valve')
class ValveTestCase(unittest.TestCase): CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 native_vlan: v100 p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 vlans: v100: vid: 100 v200: vid: 200 """ DP_ID = 1 NUM_PORTS = 5 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' V100 = 100|ofp.OFPVID_PRESENT V200 = 200|ofp.OFPVID_PRESENT def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(7) with open(self.config_file, 'w') as f: f.write(self.CONFIG) _, dps = dp_parser(self.config_file, 'test_valve') self.valve = valve_factory(dps[0])(dps[0], 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect( self.DP_ID, range(1, self.NUM_PORTS + 1) ) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 200 }) self.rcv_packet(3, 200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 200 }) def rcv_packet(self, port, vid, match): pkt = build_pkt(match) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, valves={}, in_port=port, vlan_vid=vid, pkt=pkt ) self.table.apply_ofmsgs(rcv_packet_ofmsgs) def tearDown(self): shutil.rmtree(self.tmpdir) def test_invalid_vlan(self): """Test that packets with incorrect vlan tagging get dropped.""" matches = [ {'in_port': 1, 'vlan_vid': 18|ofp.OFPVID_PRESENT}, {'in_port': 1, 'vlan_vid': self.V100}, {'in_port': 3, 'vlan_vid': 0} ] for match in matches: self.assertFalse( self.table.is_output(match), msg="Packets with incorrect vlan tags are output") def test_acl(self): pass def test_unknown_eth_src(self): """Test that packets from unknown macs are sent to controller. Untagged packets should have VLAN tags pushed before they are sent to the controler. """ matches = [ {'in_port': 1, 'vlan_vid': 0}, {'in_port': 1, 'vlan_vid': 0, 'eth_src' : self.UNKNOWN_MAC}, { 'in_port': 1, 'vlan_vid': 0, 'eth_src' : self.P2_V200_MAC }, {'in_port': 2, 'vlan_vid': 0, 'eth_dst' : self.UNKNOWN_MAC}, {'in_port': 2, 'vlan_vid': 0}, { 'in_port': 2, 'vlan_vid': self.V100, 'eth_src' : self.P2_V200_MAC }, { 'in_port': 2, 'vlan_vid': self.V100, 'eth_src' : self.UNKNOWN_MAC, 'eth_dst' : self.P1_V100_MAC }, ] for match in matches: if match['vlan_vid'] != 0: vid = match['vlan_vid'] else: vid = self.valve.dp.get_native_vlan(match['in_port']).vid vid = vid|ofp.OFPVID_PRESENT self.assertTrue( self.table.is_output(match, ofp.OFPP_CONTROLLER, vid=vid), msg="Packet with unknown ethernet src not sent to controller: " "{0}".format(match)) def test_unknown_eth_dst_rule(self): """Test that packets with unkown eth dst addrs get flooded correctly. They must be output to each port on the associated vlan, with the correct vlan tagging. And they must not be forwarded to a port not on the associated vlan""" matches = [ { 'in_port': 3, 'vlan_vid': self.V100, }, { 'in_port': 2, 'vlan_vid': 0, 'eth_dst': self.P1_V100_MAC }, {'in_port': 1, 'vlan_vid': 0, 'eth_src': self.P1_V100_MAC}, { 'in_port': 3, 'vlan_vid': self.V200, 'eth_dst': self.P1_V100_MAC }, ] dp = self.valve.dp for match in matches: in_port = match['in_port'] if 'vlan_vid' in match and\ match['vlan_vid'] & ofp.OFPVID_PRESENT is not 0: valve_vlan = dp.vlans[match['vlan_vid'] & ~ofp.OFPVID_PRESENT] else: valve_vlan = self.valve.dp.get_native_vlan(in_port) remaining_ports = set(range(1, 6)) # Check packets are output to each port on vlan for p in valve_vlan.get_ports(): remaining_ports.discard(p.number) if p.number != in_port and p.running(): if valve_vlan.port_is_tagged(p.number): vid = valve_vlan.vid|ofp.OFPVID_PRESENT else: vid = 0 self.assertTrue( self.table.is_output(match, port=p.number, vid=vid), msg="packet with unknown eth dst ({0}) not output " "correctly on vlan {1} to port {2}".format( match, valve_vlan.vid, p.number) ) # Check packets are not output to ports not on vlan for p in remaining_ports: self.assertFalse( self.table.is_output(match, port=p), msg="packet with unkown eth dst output to port not on its" " vlan ({0})".format(p)) def test_known_eth_src_rule(self): """test that packets with known eth src addrs are not sent to controller.""" matches = [ { 'in_port': 1, 'vlan_vid': 0, 'eth_src': self.P1_V100_MAC }, { 'in_port': 2, 'vlan_vid': self.V200, 'eth_src': self.P2_V200_MAC }, { 'in_port': 3, 'vlan_vid': self.V200, 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC } ] for match in matches: self.assertFalse( self.table.is_output(match, port=ofp.OFPP_CONTROLLER), msg="Packet ({0}) output to controller when eth_src address" " is known".format(match)) def test_known_eth_src_deletion(self): """Verify that when a mac changes port the old rules get deleted. If a mac address is seen on one port, then seen on a different port on the same vlan the rules associated with that mac address on previous port need to be deleted. IE packets with that mac address arriving on the old port should be output to the controller.""" self.rcv_packet(3, 200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.UNKNOWN_MAC, 'vlan_vid': 200 }) match = { 'in_port': 2, 'vlan_vid': 0, 'eth_src': self.P2_V200_MAC } self.assertTrue( self.table.is_output(match, port=ofp.OFPP_CONTROLLER), msg='eth src rule not deleted when mac seen on another port') def test_known_eth_dst_rule(self): """Test that packets with known eth dst addrs are output correctly. Output to the correct port with the correct vlan tagging.""" match_results = [ ({ 'in_port': 2, 'vlan_vid': self.V100, 'eth_dst': self.P1_V100_MAC }, { 'out_port': 1, 'vlan_vid': 0 }), ({ 'in_port': 3, 'vlan_vid': self.V200, 'eth_dst': self.P2_V200_MAC, 'eth_src': self.P3_V200_MAC }, { 'out_port': 2, 'vlan_vid': 0, }) ] for match, result in match_results: self.assertTrue( self.table.is_output( match, result['out_port'], vid=result['vlan_vid']), msg="packet not output to port correctly when eth dst is known") incorrect_ports = range(1, self.NUM_PORTS + 1) incorrect_ports.remove(result['out_port']) for port in incorrect_ports: self.assertFalse( self.table.is_output(match, port=port), msg="packet: {0} output to incorrect port ({1}) when eth " "dst is known".format(match, port)) def test_mac_learning_vlan_separation(self): """Test that when a mac is seen on a second vlan the original vlan rules are unaffected.""" self.rcv_packet(2, 200, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC, 'vlan_vid': 200 }) # check eth_src rule match1 = { 'in_port': 1, 'vlan_vid': 0, 'eth_src': self.P1_V100_MAC } self.assertFalse( self.table.is_output(match1, ofp.OFPP_CONTROLLER), msg="mac address being seen on a vlan affects eth_src rule on" " other vlan") # check eth_dst rule match2 = { 'in_port': 3, 'vlan_vid': self.V100, 'eth_dst': self.P1_V100_MAC } self.assertTrue( self.table.is_output(match2, port=1, vid=0), msg="mac address being seen on a vlan affects eth_dst rule on " "other vlan") for port in (2, 4): self.assertFalse( self.table.is_output(match2, port=port), msg="mac address being seen on a vlan affects eth_dst rule on " "other vlan") def test_known_eth_dst_rule_deletion(self): """Test that eth dst rules are deleted when the mac is learned on another port. This should only occur when the mac is seen on the same vlan.""" self.rcv_packet(2, 100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) match = { 'in_port': 3, 'vlan_vid': self.V100, 'eth_dst': self.P1_V100_MAC} self.assertTrue( self.table.is_output(match, port=2, vid=self.V100), msg="Packet not output correctly after mac is learnt on new port") self.assertFalse( self.table.is_output(match, port=1), msg="Packet output on old port after mac is learnt on new port") def test_port_delete_eth_dst_removal(self): """Test that when a port is disabled packets are correctly output. """ match = { 'in_port': 2, 'vlan_vid': self.V100, 'eth_dst': self.P1_V100_MAC} vlan = self.valve.dp.vlans[match['vlan_vid'] & ~ofp.OFPVID_PRESENT] ofmsgs = self.valve.port_delete(dp_id=self.DP_ID, port_num=1) self.table.apply_ofmsgs(ofmsgs) # Check packets are output to each port on vlan for p in vlan.get_ports(): if p.number != match['in_port'] and p.running(): if vlan.port_is_tagged(p.number): vid = vlan.vid|ofp.OFPVID_PRESENT else: vid = 0 self.assertTrue( self.table.is_output(match, port=p.number, vid=vid), msg="packet ({0}) with eth dst learnt on deleted port not output " "correctly on vlan {1} to port {2}".format(match, vlan.vid, p.number)) def test_port_down_eth_src_removal(self): '''Test that when a port goes down and comes back up learnt mac addresses are deleted.''' match = { 'in_port': 1, 'vlan_vid': 0, 'eth_src': self.P1_V100_MAC } ofmsgs = self.valve.port_delete(dp_id=self.DP_ID, port_num=1) self.table.apply_ofmsgs(ofmsgs) ofmsgs = self.valve.port_add(dp_id=self.DP_ID, port_num=1) self.table.apply_ofmsgs(ofmsgs) self.assertTrue( self.table.is_output(match, port=ofp.OFPP_CONTROLLER), msg="Packet not output to controller after port bounce") def test_port_add_input(self): """test that when a port is enabled packets are input correctly.""" match = { 'in_port': 1, 'vlan_vid': 0, } ofmsgs = self.valve.port_delete(dp_id=self.DP_ID, port_num=1) self.table.apply_ofmsgs(ofmsgs) self.assertFalse( self.table.is_output(match, port=2, vid=self.V100), msg="Packet output after port delete") ofmsgs = self.valve.port_add(dp_id=self.DP_ID, port_num=1) self.table.apply_ofmsgs(ofmsgs) self.assertTrue( self.table.is_output(match, port=2, vid=self.V100), msg="Packet not output after port add")
class ValveTestBase(unittest.TestCase): CONFIG = """ version: 2 dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 interfaces: p1: number: 1 native_vlan: v100 p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 vlans: v100: vid: 0x100 v200: vid: 0x200 """ DP_ID = 1 NUM_PORTS = 5 NUM_TABLES = 9 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' V100 = 0x100 | ofp.OFPVID_PRESENT V200 = 0x200 | ofp.OFPVID_PRESENT def update_config(self, config): with open(self.config_file, 'w') as f: f.write(config) _, dps = dp_parser(self.config_file, 'test_valve') return dps[0] def setUp(self): self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.table = FakeOFTable(self.NUM_TABLES) dp = self.update_config(self.CONFIG) self.valve = valve_factory(dp)(dp, 'test_valve') # establish connection to datapath ofmsgs = self.valve.datapath_connect(self.DP_ID, range(1, self.NUM_PORTS + 1)) self.table.apply_ofmsgs(ofmsgs) # learn some mac addresses self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC }) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'vid': 0x200 }) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'vid': 0x200 }) def rcv_packet(self, port, vid, match): pkt = build_pkt(match) pkt.serialize() pkt_meta = self.valve.parse_rcv_packet(port, vid, pkt.data, pkt) rcv_packet_ofmsgs = self.valve.rcv_packet( dp_id=1, valves={}, pkt_meta=pkt_meta, ) self.table.apply_ofmsgs(rcv_packet_ofmsgs) def tearDown(self): shutil.rmtree(self.tmpdir)
class ValveTestBase(unittest.TestCase): """Base class for all Valve unit tests.""" CONFIG = """ dps: s1: ignore_learn_ins: 0 dp_id: 1 ofchannel_log: "/dev/null" hardware: 'GenericTFM' pipeline_config_dir: '%s/../etc/ryu/faucet' lldp_beacon: send_interval: 1 max_per_interval: 1 interfaces: p1: number: 1 native_vlan: v100 lldp_beacon: enable: True system_name: "faucet" port_descr: "first_port" p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 tagged_vlans: [v300] s2: hardware: 'Open vSwitch' dp_id: 0xdeadbeef interfaces: p1: number: 1 native_vlan: v100 s3: hardware: 'Open vSwitch' dp_id: 0x3 stack: priority: 1 interfaces: p1: number: 1 native_vlan: v300 p2: number: 2 native_vlan: v300 p3: number: 3 native_vlan: v300 p4: number: 4 native_vlan: v300 5: stack: dp: s4 port: 5 s4: hardware: 'Open vSwitch' dp_id: 0x4 interfaces: p1: number: 1 native_vlan: v300 p2: number: 2 native_vlan: v300 p3: number: 3 native_vlan: v300 p4: number: 4 native_vlan: v300 5: number: 5 stack: dp: s3 port: 5 routers: router1: vlans: [v100, v200] vlans: v100: vid: 0x100 faucet_vips: ['10.0.0.254/24'] routes: - route: ip_dst: 10.99.99.0/24 ip_gw: 10.0.0.1 - route: ip_dst: 10.99.98.0/24 ip_gw: 10.0.0.99 v200: vid: 0x200 faucet_vips: ['fc00::1:254/112', 'fe80::1:254/64'] routes: - route: ip_dst: 'fc00::10:0/112' ip_gw: 'fc00::1:1' - route: ip_dst: 'fc00::20:0/112' ip_gw: 'fc00::1:99' v300: vid: 0x300 v400: vid: 0x400 """ % os.path.dirname(os.path.realpath(__file__)) DP = 's1' DP_ID = 1 NUM_PORTS = 5 NUM_TABLES = 9 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' V100 = 0x100|ofp.OFPVID_PRESENT V200 = 0x200|ofp.OFPVID_PRESENT V300 = 0x300|ofp.OFPVID_PRESENT LOGNAME = 'faucet' last_flows_to_dp = {} valve = None valves_manager = None metrics = None bgp = None table = None logger = None tmpdir = None faucet_event_sock = None registry = None sock = None notifier = None config_file = None def setup_valve(self, config): """Set up test DP with config.""" self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.faucet_event_sock = os.path.join(self.tmpdir, 'event.sock') self.table = FakeOFTable(self.NUM_TABLES) logfile = os.path.join(self.tmpdir, 'faucet.log') self.logger = valve_util.get_logger(self.LOGNAME, logfile, logging.DEBUG, 0) self.registry = CollectorRegistry() # TODO: verify Prometheus variables self.metrics = faucet_metrics.FaucetMetrics(reg=self.registry) # pylint: disable=unexpected-keyword-arg # TODO: verify events self.notifier = faucet_experimental_event.FaucetExperimentalEventNotifier( self.faucet_event_sock, self.metrics, self.logger) self.bgp = faucet_bgp.FaucetBgp(self.logger, self.metrics, self.send_flows_to_dp_by_id) self.valves_manager = valves_manager.ValvesManager( self.LOGNAME, self.logger, self.metrics, self.notifier, self.bgp, self.send_flows_to_dp_by_id) self.notifier.start() self.update_config(config) self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(self.faucet_event_sock) self.connect_dp() def teardown_valve(self): """Tear down test DP.""" for handler in self.logger.handlers: handler.close() self.logger.handlers = [] self.sock.close() shutil.rmtree(self.tmpdir) def send_flows_to_dp_by_id(self, dp_id, flows): """Callback for ValvesManager to simulate sending flows to DP.""" self.last_flows_to_dp[dp_id] = flows def update_config(self, config): """Update FAUCET config with config as text.""" self.assertFalse(self.valves_manager.config_watcher.files_changed()) existing_config = os.path.exists(self.config_file) with open(self.config_file, 'w') as config_file: config_file.write(config) if existing_config: self.assertTrue(self.valves_manager.config_watcher.files_changed()) self.last_flows_to_dp = {} self.valves_manager.request_reload_configs(self.config_file) self.valve = self.valves_manager.valves[self.DP_ID] if self.DP_ID in self.last_flows_to_dp: reload_ofmsgs = self.last_flows_to_dp[self.DP_ID] self.table.apply_ofmsgs(reload_ofmsgs) def connect_dp(self): """Call DP connect and set all ports to up.""" port_nos = range(1, self.NUM_PORTS + 1) self.table.apply_ofmsgs(self.valve.datapath_connect(port_nos)) for port_no in port_nos: self.set_port_up(port_no) def set_port_down(self, port_no): """Set port status of port to down.""" self.table.apply_ofmsgs(self.valve.port_status_handler( port_no, ofp.OFPPR_DELETE, 0)) def set_port_up(self, port_no): """Set port status of port to up.""" self.table.apply_ofmsgs(self.valve.port_status_handler( port_no, ofp.OFPPR_ADD, 1)) def flap_port(self, port_no): """Flap op status on a port.""" self.set_port_down(port_no) self.set_port_up(port_no) @staticmethod def packet_outs_from_flows(flows): """Return flows that are packetout actions.""" return [flow for flow in flows if isinstance(flow, valve_of.parser.OFPPacketOut)] def learn_hosts(self): """Learn some hosts.""" self.rcv_packet(1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC, 'ipv4_src': '10.0.0.1', 'ipv4_dst': '10.0.0.2'}) self.rcv_packet(2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'ipv4_src': '10.0.0.2', 'ipv4_dst': '10.0.0.3', 'vid': 0x200}) self.rcv_packet(3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'ipv4_src': '10.0.0.3', 'ipv4_dst': '10.0.0.4', 'vid': 0x200}) def verify_flooding(self, matches): """Verify flooding for a packet, depending on the DP implementation.""" for match in matches: in_port = match['in_port'] if ('vlan_vid' in match and match['vlan_vid'] & ofp.OFPVID_PRESENT is not 0): valve_vlan = self.valve.dp.vlans[match['vlan_vid'] & ~ofp.OFPVID_PRESENT] else: valve_vlan = self.valve.dp.get_native_vlan(in_port) all_ports = set([port for port in self.valve.dp.ports.values() if port.running()]) remaining_ports = all_ports - set([port for port in valve_vlan.get_ports() if port.running]) # Packet must be flooded to all ports on the VLAN. for port in valve_vlan.get_ports(): if valve_vlan.port_is_tagged(port): vid = valve_vlan.vid|ofp.OFPVID_PRESENT else: vid = 0 if port.number == in_port: self.assertFalse( self.table.is_output(match, port=port.number, vid=vid), msg=('Packet %s with unknown eth_dst flooded back to input port' ' on VLAN %u to port %u' % ( match, valve_vlan.vid, port.number))) else: self.assertTrue( self.table.is_output(match, port=port.number, vid=vid), msg=('Packet %s with unknown eth_dst not flooded' ' on VLAN %u to port %u' % ( match, valve_vlan.vid, port.number))) # Packet must not be flooded to ports not on the VLAN. for port in remaining_ports: if port.stack: self.assertTrue( self.table.is_output(match, port=port.number), msg=('Packet with unknown eth_dst not flooded to stack port %s' % port)) else: self.assertFalse( self.table.is_output(match, port=port.number), msg=('Packet with unknown eth_dst flooded to non-VLAN %s' % port)) def rcv_packet(self, port, vid, match): """Simulate control plane receiving a packet on a port/VID.""" pkt = build_pkt(match) vlan_pkt = pkt # TODO: packet submitted to packet in always has VID # Fake OF switch implementation should do this by applying actions. if vid not in match: vlan_match = match vlan_match['vid'] = vid vlan_pkt = build_pkt(match) msg = namedtuple( 'null_msg', ('match', 'in_port', 'data', 'total_len', 'cookie', 'reason')) msg.reason = valve_of.ofp.OFPR_ACTION msg.data = vlan_pkt.data msg.total_len = len(msg.data) msg.match = {'in_port': port} msg.cookie = self.valve.dp.cookie pkt_meta = self.valve.parse_pkt_meta(msg) self.valves_manager.valve_packet_in(self.valve, pkt_meta) # pylint: disable=no-member rcv_packet_ofmsgs = valve_of.valve_flowreorder(self.last_flows_to_dp[self.DP_ID]) self.table.apply_ofmsgs(rcv_packet_ofmsgs) resolve_ofmsgs = self.valve.resolve_gateways() self.table.apply_ofmsgs(resolve_ofmsgs) self.valve.advertise() self.valve.state_expire() self.valves_manager.update_metrics() return rcv_packet_ofmsgs
class ValveTestBase(unittest.TestCase): """Base class for all Valve unit tests.""" CONFIG = """ dps: s1: ignore_learn_ins: 0 hardware: 'Open vSwitch' dp_id: 1 ofchannel_log: "/dev/null" lldp_beacon: send_interval: 1 max_per_interval: 1 interfaces: p1: number: 1 native_vlan: v100 lldp_beacon: enable: True system_name: "faucet" port_descr: "first_port" p2: number: 2 native_vlan: v200 tagged_vlans: [v100] p3: number: 3 tagged_vlans: [v100, v200] p4: number: 4 tagged_vlans: [v200] p5: number: 5 tagged_vlans: [v300] s2: hardware: 'Open vSwitch' dp_id: 0xdeadbeef interfaces: p1: number: 1 native_vlan: v100 s3: hardware: 'Open vSwitch' dp_id: 0x3 stack: priority: 1 interfaces: p1: number: 1 native_vlan: v300 p2: number: 2 native_vlan: v300 p3: number: 3 native_vlan: v300 p4: number: 4 native_vlan: v300 5: stack: dp: s4 port: 5 s4: hardware: 'Open vSwitch' dp_id: 0x4 interfaces: p1: number: 1 native_vlan: v300 p2: number: 2 native_vlan: v300 p3: number: 3 native_vlan: v300 p4: number: 4 native_vlan: v300 5: number: 5 stack: dp: s3 port: 5 routers: router1: vlans: [v100, v200] vlans: v100: vid: 0x100 faucet_vips: ['10.0.0.254/24'] routes: - route: ip_dst: 10.99.99.0/24 ip_gw: 10.0.0.1 - route: ip_dst: 10.99.98.0/24 ip_gw: 10.0.0.99 v200: vid: 0x200 faucet_vips: ['fc00::1:254/112', 'fe80::1:254/64'] routes: - route: ip_dst: 'fc00::10:0/112' ip_gw: 'fc00::1:1' - route: ip_dst: 'fc00::20:0/112' ip_gw: 'fc00::1:99' v300: vid: 0x300 v400: vid: 0x400 """ DP = 's1' DP_ID = 1 NUM_PORTS = 5 NUM_TABLES = 9 P1_V100_MAC = '00:00:00:01:00:01' P2_V200_MAC = '00:00:00:02:00:02' P3_V200_MAC = '00:00:00:02:00:03' UNKNOWN_MAC = '00:00:00:04:00:04' FAUCET_MAC = '0e:00:00:00:00:01' V100 = 0x100 | ofp.OFPVID_PRESENT V200 = 0x200 | ofp.OFPVID_PRESENT def setup_valve(self, config): """Set up test DP with config.""" self.tmpdir = tempfile.mkdtemp() self.config_file = os.path.join(self.tmpdir, 'valve_unit.yaml') self.faucet_event_sock = os.path.join(self.tmpdir, 'event.sock') self.logfile = os.path.join(self.tmpdir, 'faucet.log') self.table = FakeOFTable(self.NUM_TABLES) self.logger = valve_util.get_logger('faucet', self.logfile, logging.DEBUG, 0) self.registry = CollectorRegistry() # TODO: verify Prometheus variables self.metrics = faucet_metrics.FaucetMetrics(reg=self.registry) # pylint: disable=unexpected-keyword-arg # TODO: verify events self.notifier = faucet_experimental_event.FaucetExperimentalEventNotifier( self.faucet_event_sock, self.metrics, self.logger) self.notifier.start() dp = self.update_config(config, self.DP) self.valve = valve_factory(dp)(dp, 'test_valve', self.notifier) self.valve.update_config_metrics(self.metrics) self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(self.faucet_event_sock) def update_config(self, config, dp_name): """Update FAUCET config with config as text.""" with open(self.config_file, 'w') as config_file: config_file.write(config) _, dps = dp_parser(self.config_file, 'test_valve') return [dp for dp in dps if dp.name == dp_name][0] def connect_dp(self): """Call DP connect and set all ports to up.""" self.assertTrue(self.valve.switch_features(None)) port_nos = range(1, self.NUM_PORTS + 1) self.table.apply_ofmsgs(self.valve.datapath_connect(port_nos)) for port_no in port_nos: self.set_port_up(port_no) def apply_new_config(self, config): """Update FAUCET config, and tell FAUCET config has changed.""" new_dp = self.update_config(config, self.DP) _, ofmsgs = self.valve.reload_config(new_dp) self.table.apply_ofmsgs(ofmsgs) def set_port_down(self, port_no): """Set port status of port to down.""" self.table.apply_ofmsgs( self.valve.port_status_handler(port_no, ofp.OFPPR_DELETE, None)) def set_port_up(self, port_no): """Set port status of port to up.""" self.table.apply_ofmsgs( self.valve.port_status_handler(port_no, ofp.OFPPR_ADD, None)) def flap_port(self, port_no): """Flap op status on a port.""" self.set_port_down(port_no) self.set_port_up(port_no) def packet_outs_from_flows(self, flows): """Return flows that are packetout actions.""" return [ flow for flow in flows if isinstance(flow, valve_of.parser.OFPPacketOut) ] def arp_for_controller(self): """ARP request for controller VIP.""" arp_replies = self.rcv_packet( 1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': mac.BROADCAST_STR, 'arp_source_ip': '10.0.0.1', 'arp_target_ip': '10.0.0.254' }) # TODO: check arp reply is valid self.assertTrue(self.packet_outs_from_flows(arp_replies)) def nd_for_controller(self): """IPv6 ND for controller VIP.""" dst_ip = ipaddress.IPv6Address('fc00::1:254') nd_mac = valve_packet.ipv6_link_eth_mcast(dst_ip) ip_gw_mcast = valve_packet.ipv6_solicited_node_from_ucast(dst_ip) nd_replies = self.rcv_packet( 2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': nd_mac, 'vid': 0x200, 'ipv6_src': 'fc00::1:1', 'ipv6_dst': str(ip_gw_mcast), 'neighbor_solicit_ip': str(dst_ip) }) # TODO: check ND reply is valid self.assertTrue(self.packet_outs_from_flows(nd_replies)) def icmp_ping_controller(self): """IPv4 ping controller VIP.""" echo_replies = self.rcv_packet( 1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.FAUCET_MAC, 'vid': 0x100, 'ipv4_src': '10.0.0.1', 'ipv4_dst': '10.0.0.254', 'echo_request_data': bytes('A' * 8, encoding='UTF-8') }) # TODO: check ping response self.assertTrue(self.packet_outs_from_flows(echo_replies)) def icmp_ping_unknown_neighbor(self): """IPv4 ping unknown host on same subnet, causing proactive learning.""" echo_replies = self.rcv_packet( 1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.FAUCET_MAC, 'vid': 0x100, 'ipv4_src': '10.0.0.1', 'ipv4_dst': '10.0.0.99', 'echo_request_data': bytes('A' * 8, encoding='UTF-8') }) # TODO: check proactive neighbor resolution self.assertTrue(self.packet_outs_from_flows(echo_replies)) def icmpv6_ping_controller(self): """IPv6 ping controller VIP.""" echo_replies = self.rcv_packet( 2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.FAUCET_MAC, 'vid': 0x200, 'ipv6_src': 'fc00::1:1', 'ipv6_dst': 'fc00::1:254', 'echo_request_data': bytes('A' * 8, encoding='UTF-8') }) # TODO: check ping response self.assertTrue(self.packet_outs_from_flows(echo_replies)) def learn_hosts(self): """Learn some hosts.""" self.rcv_packet( 1, 0x100, { 'eth_src': self.P1_V100_MAC, 'eth_dst': self.UNKNOWN_MAC, 'ipv4_src': '10.0.0.1', 'ipv4_dst': '10.0.0.2' }) self.rcv_packet( 2, 0x200, { 'eth_src': self.P2_V200_MAC, 'eth_dst': self.P3_V200_MAC, 'ipv4_src': '10.0.0.2', 'ipv4_dst': '10.0.0.3', 'vid': 0x200 }) self.rcv_packet( 3, 0x200, { 'eth_src': self.P3_V200_MAC, 'eth_dst': self.P2_V200_MAC, 'ipv4_src': '10.0.0.3', 'ipv4_dst': '10.0.0.4', 'vid': 0x200 }) def setUp(self): self.setup_valve(self.CONFIG) self.connect_dp() self.learn_hosts() def rcv_packet(self, port, vid, match): pkt, eth_type = build_pkt(match) eth_pkt = valve_packet.parse_eth_pkt(pkt) pkt_meta = self.valve.parse_rcv_packet(port, vid, eth_type, pkt.data, len(pkt.data), pkt, eth_pkt) rcv_packet_ofmsgs = self.valve.rcv_packet(other_valves=[], pkt_meta=pkt_meta) rcv_packet_ofmsgs = valve_of.valve_flowreorder(rcv_packet_ofmsgs) self.table.apply_ofmsgs(rcv_packet_ofmsgs) resolve_ofmsgs = self.valve.resolve_gateways() self.table.apply_ofmsgs(resolve_ofmsgs) self.valve.advertise() self.valve.state_expire() self.valve.update_metrics(self.metrics) return rcv_packet_ofmsgs def tearDown(self): self.sock.close() shutil.rmtree(self.tmpdir)