Esempio n. 1
0
 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()
Esempio n. 2
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
        })
Esempio n. 3
0
 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)
Esempio n. 4
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
            })
Esempio n. 5
0
    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
            })
Esempio n. 6
0
 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)
Esempio n. 7
0
 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)
Esempio n. 8
0
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
        })
Esempio n. 9
0
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)
Esempio n. 10
0
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
            })
Esempio n. 11
0
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)
Esempio n. 12
0
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")
Esempio n. 13
0
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)
Esempio n. 14
0
 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')
Esempio n. 15
0
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")
Esempio n. 16
0
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)
Esempio n. 17
0
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
Esempio n. 18
0
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)