コード例 #1
0
ファイル: valve_flood.py プロジェクト: anarkiwi/faucet
 def initialise_tables(self):
     """Initialise the flood table with filtering flows."""
     ofmsgs = []
     for eth_dst, eth_dst_mask in (
             (valve_packet.CISCO_CDP_VTP_UDLD_ADDRESS, valve_packet.mac_byte_mask(6)),
             (valve_packet.CISCO_SPANNING_GROUP_ADDRESS, valve_packet.mac_byte_mask(6)),
             (valve_packet.BRIDGE_GROUP_ADDRESS, valve_packet.BRIDGE_GROUP_MASK)):
         ofmsgs.append(self.flood_table.flowdrop(
             self.flood_table.match(eth_dst=eth_dst, eth_dst_mask=eth_dst_mask),
             priority=self._mask_flood_priority(eth_dst_mask)))
     return ofmsgs
コード例 #2
0
 def initialise_tables(self):
     """Initialise the flood table with filtering flows."""
     ofmsgs = []
     for eth_dst, eth_dst_mask in (
             (valve_packet.CISCO_CDP_VTP_UDLD_ADDRESS, valve_packet.mac_byte_mask(6)),
             (valve_packet.CISCO_SPANNING_GROUP_ADDRESS, valve_packet.mac_byte_mask(6)),
             (valve_packet.BRIDGE_GROUP_ADDRESS, valve_packet.BRIDGE_GROUP_MASK)):
         ofmsgs.append(self.flood_table.flowdrop(
             self.flood_table.match(eth_dst=eth_dst, eth_dst_mask=eth_dst_mask),
             priority=self._mask_flood_priority(eth_dst_mask)))
     return ofmsgs
コード例 #3
0
class ValveFloodManager(ValveManagerBase):
    """Implement dataplane based flooding for standalone dataplanes."""

    # Enumerate possible eth_dst flood destinations.
    # First bool says whether to flood this destination, if the VLAN
    # has unicast flooding enabled (if unicast flooding is enabled,
    # then we flood all destination eth_dsts).
    FLOOD_DSTS = (
        (True, None, None),
        (False, valve_packet.BRIDGE_GROUP_ADDRESS,
         valve_packet.mac_byte_mask(3)),  # 802.x
        (False, '01:00:5E:00:00:00',
         valve_packet.mac_byte_mask(3)),  # IPv4 multicast
        (False, '33:33:00:00:00:00',
         valve_packet.mac_byte_mask(2)),  # IPv6 multicast
        (False, valve_of.mac.BROADCAST_STR,
         valve_packet.mac_byte_mask(6)),  # eth broadcasts
    )

    def __init__(self, logger, flood_table, pipeline, use_group_table, groups,
                 combinatorial_port_flood):
        self.logger = logger
        self.flood_table = flood_table
        self.pipeline = pipeline
        self.use_group_table = use_group_table
        self.groups = groups
        self.combinatorial_port_flood = combinatorial_port_flood
        self.bypass_priority = self._FILTER_PRIORITY
        self.flood_priority = self._MATCH_PRIORITY
        self.classification_offset = 0x100

    def initialise_tables(self):
        """Initialise the flood table with filtering flows."""
        ofmsgs = []
        for eth_dst, eth_dst_mask in (
            (valve_packet.CISCO_CDP_VTP_UDLD_ADDRESS,
             valve_packet.mac_byte_mask(6)),
            (valve_packet.CISCO_SPANNING_GROUP_ADDRESS,
             valve_packet.mac_byte_mask(6)),
            (valve_packet.BRIDGE_GROUP_ADDRESS,
             valve_packet.BRIDGE_GROUP_MASK)):
            ofmsgs.append(
                self.flood_table.flowdrop(
                    self.flood_table.match(eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask),
                    priority=self._mask_flood_priority(eth_dst_mask)))
        return ofmsgs

    def _mask_flood_priority(self, eth_dst_mask):
        return self.flood_priority + valve_packet.mac_mask_bits(eth_dst_mask)

    @staticmethod
    def _vlan_all_ports(vlan, exclude_unicast):
        """Return list of all ports that should be flooded to on a VLAN."""
        return list(vlan.flood_ports(vlan.get_ports(), exclude_unicast))

    @staticmethod
    def _build_flood_local_rule_actions(vlan, exclude_unicast, in_port,
                                        exclude_all_external):
        """Return a list of flood actions to flood packets from a port."""
        external_ports = vlan.loop_protect_external_ports_up()
        exclude_ports = vlan.exclude_same_lag_member_ports(in_port)
        if external_ports:
            if (exclude_all_external
                    or in_port is not None and in_port.loop_protect_external):
                exclude_ports |= set(external_ports)
            else:
                exclude_ports |= set(external_ports[1:])
        return valve_of.flood_port_outputs(
            vlan.tagged_flood_ports(exclude_unicast),
            vlan.untagged_flood_ports(exclude_unicast),
            in_port=in_port,
            exclude_ports=exclude_ports)

    def _build_flood_rule_actions(self,
                                  vlan,
                                  exclude_unicast,
                                  in_port,
                                  exclude_all_external=False):
        return valve_of.dedupe_ofmsgs(
            self._build_flood_local_rule_actions(vlan, exclude_unicast,
                                                 in_port,
                                                 exclude_all_external))

    def _build_flood_rule(self, match, command, flood_acts, flood_priority):
        return self.flood_table.flowmod(
            match=match,
            command=command,
            inst=[valve_of.apply_actions(flood_acts)],
            priority=flood_priority)

    def _vlan_flood_priority(self, eth_dst_mask):
        return self._mask_flood_priority(eth_dst_mask)

    def _build_flood_rule_for_vlan(
            self,
            vlan,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            command):
        flood_priority = self._vlan_flood_priority(eth_dst_mask)
        match = self.flood_table.match(vlan=vlan,
                                       eth_dst=eth_dst,
                                       eth_dst_mask=eth_dst_mask)
        flood_acts = self._build_flood_rule_actions(vlan, exclude_unicast,
                                                    None)
        return (self._build_flood_rule(match, command, flood_acts,
                                       flood_priority), flood_acts)

    @staticmethod
    def _output_ports_from_actions(flood_acts):
        return {act.port for act in flood_acts if valve_of.is_output(act)}

    def _build_flood_rule_for_port(
            self,
            vlan,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            command,
            port,
            add_match=None,
            preflood_acts=None,
            exclude_all_external=False):
        ofmsgs = []
        flood_acts = []
        if port.hairpin or port.dyn_phys_up:
            if add_match is None:
                add_match = {}
            if preflood_acts is None:
                preflood_acts = []
            flood_priority = self._vlan_flood_priority(eth_dst_mask) + 1
            match = self.flood_table.match(vlan=vlan,
                                           in_port=port.number,
                                           eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask,
                                           **add_match)
            flood_acts = preflood_acts + self._build_flood_rule_actions(
                vlan, exclude_unicast, port, exclude_all_external)
            if self._output_ports_from_actions(flood_acts):
                ofmsgs = self._build_flood_rule(match, command, flood_acts,
                                                flood_priority)
        return (ofmsgs, flood_acts)

    def _build_mask_flood_rules(
            self,
            vlan,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            command):
        ofmsgs = []
        if self.combinatorial_port_flood:
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                port_flood_ofmsg, _ = self._build_flood_rule_for_port(
                    vlan, eth_dst, eth_dst_mask, exclude_unicast, command,
                    port)
                if not port_flood_ofmsg:
                    continue
                ofmsgs.append(port_flood_ofmsg)
        else:
            vlan_flood_ofmsg, vlan_flood_acts = self._build_flood_rule_for_vlan(
                vlan, eth_dst, eth_dst_mask, exclude_unicast, command)
            if not self.use_group_table:
                ofmsgs.append(vlan_flood_ofmsg)
            vlan_output_ports = self._output_ports_from_actions(
                vlan_flood_acts)
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                port_flood_ofmsg, port_flood_acts = self._build_flood_rule_for_port(
                    vlan, eth_dst, eth_dst_mask, exclude_unicast, command,
                    port)
                if not port_flood_ofmsg:
                    continue
                port_output_ports = self._output_ports_from_actions(
                    port_flood_acts)
                port_output_ports.add(port.number)
                if vlan_output_ports != port_output_ports:
                    ofmsgs.append(port_flood_ofmsg)
        return ofmsgs

    def _build_multiout_flood_rules(self, vlan, command):
        """Build flooding rules for a VLAN without using groups."""
        ofmsgs = []
        for unicast_eth_dst, eth_dst, eth_dst_mask in self.FLOOD_DSTS:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            ofmsgs.extend(
                self._build_mask_flood_rules(vlan, eth_dst, eth_dst_mask,
                                             unicast_eth_dst, command))
        return ofmsgs

    def _build_group_flood_rules(self, vlan, modify, command):
        """Build flooding rules for a VLAN using groups."""
        ofmsgs = []
        groups_by_unicast_eth = {}

        _, vlan_flood_acts = self._build_flood_rule_for_vlan(
            vlan, None, None, False, command)

        group_id = vlan.vid
        group = self.groups.get_entry(
            group_id, valve_of.build_group_flood_buckets(vlan_flood_acts))
        groups_by_unicast_eth[False] = group
        groups_by_unicast_eth[True] = group

        # Only configure unicast flooding group if has different output
        # actions to non unicast flooding.
        _, unicast_eth_vlan_flood_acts = self._build_flood_rule_for_vlan(
            vlan, None, None, True, command)
        if (self._output_ports_from_actions(unicast_eth_vlan_flood_acts) !=
                self._output_ports_from_actions(vlan_flood_acts)):
            group_id += valve_of.VLAN_GROUP_OFFSET
            group = self.groups.get_entry(
                group_id,
                valve_of.build_group_flood_buckets(
                    unicast_eth_vlan_flood_acts))
            groups_by_unicast_eth[True] = group

        for group in groups_by_unicast_eth.values():
            if modify:
                ofmsgs.append(group.modify())
            else:
                ofmsgs.extend(group.add())

        for unicast_eth_dst, eth_dst, eth_dst_mask in self.FLOOD_DSTS:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            group = groups_by_unicast_eth[unicast_eth_dst]
            match = self.flood_table.match(vlan=vlan,
                                           eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask)
            flood_priority = self._mask_flood_priority(eth_dst_mask)
            ofmsgs.append(
                self.flood_table.flowmod(
                    match=match,
                    command=command,
                    inst=[
                        valve_of.apply_actions(
                            [valve_of.group_act(group.group_id)])
                    ],
                    priority=flood_priority))
        return ofmsgs

    def add_vlan(self, vlan):
        return self.build_flood_rules(vlan)

    def build_flood_rules(self, vlan, modify=False):
        """Add flows to flood packets to unknown destinations on a VLAN."""
        command = valve_of.ofp.OFPFC_ADD
        if modify:
            command = valve_of.ofp.OFPFC_MODIFY_STRICT
        ofmsgs = self._build_multiout_flood_rules(vlan, command)
        if self.use_group_table:
            ofmsgs.extend(self._build_group_flood_rules(vlan, modify, command))
        return ofmsgs

    @staticmethod
    def update_stack_topo(event, dp, port=None):  # pylint: disable=unused-argument,invalid-name
        """Update the stack topology. It has nothing to do for non-stacking DPs."""
        return

    @staticmethod
    def edge_learn_port(_other_valves, pkt_meta):
        """Possibly learn a host on a port.

        Args:
            other_valves (list): All Valves other than this one.
            pkt_meta (PacketMeta): PacketMeta instance for packet received.
        Returns:
            port to learn host on.
        """
        return pkt_meta.port
コード例 #4
0
class ValveSwitchManager(ValveManagerBase):  # pylint: disable=too-many-public-methods
    """Implement dataplane based flooding/learning for standalone dataplanes."""

    # Enumerate possible eth_dst flood destinations.
    # First bool says whether to flood this destination if the VLAN
    # has unicast flooding enabled (if unicast flooding is enabled,
    # then we flood all destination eth_dsts).
    FLOOD_DSTS = (
        (True, None, None, None),
        (False, None, valve_packet.BRIDGE_GROUP_ADDRESS,
         valve_packet.mac_byte_mask(3)),  # 802.x
        (False, None, '01:00:5E:00:00:00',
         valve_packet.mac_byte_mask(3)),  # IPv4 multicast
        (False, None, '33:33:00:00:00:00',
         valve_packet.mac_byte_mask(2)),  # IPv6 multicast
        (False, None, valve_of.mac.BROADCAST_STR,
         valve_packet.mac_byte_mask(6)),  # eth broadcasts
    )
    # Ports with restricted broadcast enabled may only receive these broadcasts.
    RESTRICTED_FLOOD_DISTS = (
        (False, valve_of.ether.ETH_TYPE_ARP, valve_of.mac.BROADCAST_STR,
         valve_packet.mac_byte_mask(6)),  # ARP
        (False, valve_of.ether.ETH_TYPE_IPV6, '33:33:FF:00:00:00',
         valve_packet.mac_byte_mask(3)),  # IPv6 multicast for ND
        (False,
         valve_of.ether.ETH_TYPE_IPV6, valve_packet.IPV6_ALL_ROUTERS_MCAST,
         valve_packet.mac_byte_mask(6)),  # IPV6 all routers
        (False,
         valve_of.ether.ETH_TYPE_IPV6, valve_packet.IPV6_ALL_NODES_MCAST,
         valve_packet.mac_byte_mask(6)),  # IPv6 all nodes
    )

    def __init__(
            self,
            logger,
            ports,
            vlans,  # pylint: disable=too-many-arguments
            vlan_table,
            vlan_acl_table,
            eth_src_table,
            eth_dst_table,
            eth_dst_hairpin_table,
            flood_table,
            classification_table,
            pipeline,
            use_group_table,
            groups,
            combinatorial_port_flood,
            canonical_port_order,
            restricted_bcast_arpnd,
            has_externals,
            learn_ban_timeout,
            learn_timeout,
            learn_jitter,
            cache_update_guard_time,
            idle_dst,
            dp_high_priority,
            dp_highest_priority,
            faucet_dp_mac):
        self.logger = logger
        self.ports = ports
        self.vlans = vlans
        self.vlan_table = vlan_table
        self.vlan_acl_table = vlan_acl_table
        self.eth_src_table = eth_src_table
        self.eth_dst_table = eth_dst_table
        self.eth_dst_hairpin_table = eth_dst_hairpin_table
        self.flood_table = flood_table
        self.classification_table = classification_table
        self.pipeline = pipeline
        self.use_group_table = use_group_table
        self.groups = groups
        self.combinatorial_port_flood = combinatorial_port_flood
        self.canonical_port_order = canonical_port_order
        self.restricted_bcast_arpnd = restricted_bcast_arpnd
        self.has_externals = has_externals
        self.learn_ban_timeout = learn_ban_timeout
        self.learn_timeout = learn_timeout
        self.learn_jitter = learn_jitter
        self.cache_update_guard_time = cache_update_guard_time
        self.idle_dst = idle_dst
        self.output_table = self.eth_dst_table
        if self.eth_dst_hairpin_table:
            self.output_table = self.eth_dst_hairpin_table
        if restricted_bcast_arpnd:
            self.flood_dsts = self.FLOOD_DSTS + self.RESTRICTED_FLOOD_DISTS
        else:
            self.flood_dsts = self.FLOOD_DSTS
        self.bypass_priority = self._FILTER_PRIORITY
        self.host_priority = self._MATCH_PRIORITY
        self.flood_priority = self._MATCH_PRIORITY
        self.low_priority = self._LOW_PRIORITY
        self.high_priority = self._HIGH_PRIORITY
        self.classification_offset = 0x100
        self.dp_high_priority = dp_high_priority
        self.dp_highest_priority = dp_highest_priority
        self.faucet_dp_mac = faucet_dp_mac

    def initialise_tables(self):
        """Initialise the flood table with filtering flows."""
        ofmsgs = []
        for eth_dst, eth_dst_mask in (
            (valve_packet.CISCO_CDP_VTP_UDLD_ADDRESS,
             valve_packet.mac_byte_mask(6)),
            (valve_packet.CISCO_SPANNING_GROUP_ADDRESS,
             valve_packet.mac_byte_mask(6)),
            (valve_packet.BRIDGE_GROUP_ADDRESS,
             valve_packet.BRIDGE_GROUP_MASK)):
            ofmsgs.append(
                self.flood_table.flowdrop(
                    self.flood_table.match(eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask),
                    priority=self._mask_flood_priority(eth_dst_mask)))
        return ofmsgs

    def floods_to_root(self, _dp_obj):
        """Return True if the given dp floods (only) to root switch"""
        return False

    @functools.lru_cache(maxsize=1024)
    def _mask_flood_priority(self, eth_dst_mask):
        return self.flood_priority + valve_packet.mac_mask_bits(eth_dst_mask)

    @staticmethod
    def _vlan_all_ports(vlan, exclude_unicast):
        """Return list of all ports that should be flooded to on a VLAN."""
        return list(vlan.flood_ports(vlan.get_ports(), exclude_unicast))

    def _build_flood_local_rule_actions(
            self,
            vlan,
            exclude_unicast,
            in_port,  # pylint: disable=too-many-arguments
            exclude_all_external,
            exclude_restricted_bcast_arpnd):
        """Return a list of flood actions to flood packets from a port."""
        external_ports = self.canonical_port_order(
            vlan.loop_protect_external_ports_up())
        exclude_ports = vlan.excluded_lag_ports(in_port)
        exclude_ports.update(vlan.exclude_native_if_dot1x())
        if exclude_all_external or (in_port is not None
                                    and in_port.loop_protect_external):
            exclude_ports.update(set(external_ports))
        else:
            exclude_ports.update(set(external_ports[1:]))
        if exclude_restricted_bcast_arpnd:
            exclude_ports.update(set(vlan.restricted_bcast_arpnd_ports()))
        return valve_of.flood_port_outputs(
            vlan.tagged_flood_ports(exclude_unicast),
            vlan.untagged_flood_ports(exclude_unicast),
            in_port=in_port,
            exclude_ports=exclude_ports)

    def _build_flood_rule_actions(
            self,
            vlan,
            exclude_unicast,
            in_port,  # pylint: disable=too-many-arguments
            exclude_all_external=False,
            exclude_restricted_bcast_arpnd=False):
        actions = []
        if vlan.loop_protect_external_ports() and vlan.tagged_flood_ports(
                exclude_unicast):
            actions.append(
                self.flood_table.set_external_forwarding_requested())
        actions.extend(
            self._build_flood_local_rule_actions(
                vlan, exclude_unicast, in_port, exclude_all_external,
                exclude_restricted_bcast_arpnd))
        return tuple(actions)

    def _build_flood_rule(self, match, command, flood_acts, flood_priority):
        return self.flood_table.flowmod(
            match=match,
            command=command,
            inst=[valve_of.apply_actions(flood_acts)],
            priority=flood_priority)

    @functools.lru_cache(maxsize=1024)
    def _vlan_flood_priority(self, eth_type, eth_dst_mask):
        priority = self._mask_flood_priority(eth_dst_mask)
        if eth_type:
            priority += eth_type
        return priority

    def _build_flood_rule_for_vlan(
            self,
            vlan,
            eth_type,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            command):
        flood_priority = self._vlan_flood_priority(eth_type, eth_dst_mask)
        match = self.flood_table.match(vlan=vlan,
                                       eth_type=eth_type,
                                       eth_dst=eth_dst,
                                       eth_dst_mask=eth_dst_mask)
        # TODO: optimization - drop all general flood dsts if all ports are restricted.
        exclude_restricted_bcast_arpnd = True
        flood_acts = self._build_flood_rule_actions(
            vlan,
            exclude_unicast,
            None,
            exclude_restricted_bcast_arpnd=exclude_restricted_bcast_arpnd)
        return (self._build_flood_rule(match, command, flood_acts,
                                       flood_priority), flood_acts)

    def _build_flood_acts_for_port(
            self,
            vlan,
            exclude_unicast,
            port,  # pylint: disable=too-many-arguments
            exclude_all_external=False,
            exclude_restricted_bcast_arpnd=False):
        flood_acts = ()
        port_output_ports = []
        port_non_output_acts = []
        if port.dyn_phys_up:
            if exclude_restricted_bcast_arpnd:
                flood_acts = self._build_flood_rule_actions(
                    vlan, exclude_unicast, port, exclude_all_external,
                    port.restricted_bcast_arpnd)
            else:
                flood_acts = self._build_flood_rule_actions(
                    vlan, exclude_unicast, port, exclude_all_external, False)
            (flood_acts, port_output_ports, port_non_output_acts
             ) = valve_of.output_non_output_actions(flood_acts)
            if not port_output_ports:
                flood_acts = ()
                port_non_output_acts = ()
        return (flood_acts, port_output_ports, port_non_output_acts)

    def _build_flood_match_priority(
            self,
            port,
            vlan,
            eth_type,  # pylint: disable=too-many-arguments
            eth_dst,
            eth_dst_mask,
            add_match):
        flood_priority = self._vlan_flood_priority(eth_type, eth_dst_mask) + 1
        if add_match is None:
            add_match = {}
        match = self.flood_table.match(vlan=vlan,
                                       in_port=port.number,
                                       eth_type=eth_type,
                                       eth_dst=eth_dst,
                                       eth_dst_mask=eth_dst_mask,
                                       **add_match)
        return (flood_priority, match)

    def _build_flood_rule_for_port(
            self,
            vlan,
            eth_type,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            command,
            port,
            flood_acts,
            add_match=None):
        flood_priority, match = self._build_flood_match_priority(
            port, vlan, eth_type, eth_dst, eth_dst_mask, add_match)
        return self._build_flood_rule(match, command, flood_acts,
                                      flood_priority)

    def _build_mask_flood_rules(
            self,
            vlan,
            eth_type,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            exclude_restricted_bcast_arpnd,
            command,
            cold_start):
        ofmsgs = []
        if self.combinatorial_port_flood:
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                flood_acts, _, _ = self._build_flood_acts_for_port(
                    vlan,
                    exclude_unicast,
                    port,
                    exclude_restricted_bcast_arpnd=
                    exclude_restricted_bcast_arpnd)
                if flood_acts:
                    ofmsgs.append(
                        self._build_flood_rule_for_port(
                            vlan, eth_type, eth_dst, eth_dst_mask, command,
                            port, flood_acts))
        else:
            vlan_flood_ofmsg, vlan_flood_acts = self._build_flood_rule_for_vlan(
                vlan, eth_type, eth_dst, eth_dst_mask, exclude_unicast,
                command)
            if not self.use_group_table:
                ofmsgs.append(vlan_flood_ofmsg)
            (flood_acts, vlan_output_ports, vlan_non_output_acts
             ) = valve_of.output_non_output_actions(vlan_flood_acts)
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                (flood_acts, port_output_ports,
                 port_non_output_acts) = self._build_flood_acts_for_port(
                     vlan,
                     exclude_unicast,
                     port,
                     exclude_restricted_bcast_arpnd=
                     exclude_restricted_bcast_arpnd)
                if not flood_acts:
                    continue
                if (vlan_output_ports - set([port.number]) == port_output_ports
                        and vlan_non_output_acts == port_non_output_acts):
                    # Delete a potentially existing port specific flow
                    # TODO: optimize, avoid generating delete for port if no existing flow.
                    if not cold_start:
                        flood_priority, match = self._build_flood_match_priority(
                            port,
                            vlan,
                            eth_type,
                            eth_dst,
                            eth_dst_mask,
                            add_match=None)
                        ofmsgs.append(
                            self.flood_table.flowdel(match=match,
                                                     priority=flood_priority))
                else:
                    ofmsgs.append(
                        self._build_flood_rule_for_port(
                            vlan, eth_type, eth_dst, eth_dst_mask, command,
                            port, flood_acts))
        return ofmsgs

    def _build_multiout_flood_rules(self, vlan, command, cold_start):
        """Build flooding rules for a VLAN without using groups."""
        ofmsgs = []
        for unicast_eth_dst, eth_type, eth_dst, eth_dst_mask in self.flood_dsts:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            exclude_restricted_bcast_arpnd = eth_type is None
            ofmsgs.extend(
                self._build_mask_flood_rules(vlan, eth_type, eth_dst,
                                             eth_dst_mask, unicast_eth_dst,
                                             exclude_restricted_bcast_arpnd,
                                             command, cold_start))
        return ofmsgs

    def _build_group_flood_rules(self, vlan, modify, command):
        """Build flooding rules for a VLAN using groups."""
        _, vlan_flood_acts = self._build_flood_rule_for_vlan(
            vlan, None, None, None, False, command)
        group_id = vlan.vid
        group = self.groups.get_entry(
            group_id, valve_of.build_group_flood_buckets(vlan_flood_acts))
        groups_by_unicast_eth = {False: group, True: group}
        ofmsgs = []

        # Only configure unicast flooding group if has different output
        # actions to non unicast flooding.
        _, unicast_eth_vlan_flood_acts = self._build_flood_rule_for_vlan(
            vlan, None, None, None, True, command)
        unicast_eth_vlan_flood_acts, unicast_output_ports, _ = valve_of.output_non_output_actions(
            unicast_eth_vlan_flood_acts)
        vlan_flood_acts, vlan_output_ports, _ = valve_of.output_non_output_actions(
            vlan_flood_acts)
        if unicast_output_ports != vlan_output_ports:
            group_id += valve_of.VLAN_GROUP_OFFSET
            group = self.groups.get_entry(
                group_id,
                valve_of.build_group_flood_buckets(
                    unicast_eth_vlan_flood_acts))
            groups_by_unicast_eth[True] = group

        for group in groups_by_unicast_eth.values():
            if modify:
                ofmsgs.append(group.modify())
            else:
                ofmsgs.extend(group.add())

        for unicast_eth_dst, eth_type, eth_dst, eth_dst_mask in self.flood_dsts:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            group = groups_by_unicast_eth[unicast_eth_dst]
            match = self.flood_table.match(vlan=vlan,
                                           eth_type=eth_type,
                                           eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask)
            flood_priority = self._vlan_flood_priority(eth_type, eth_dst_mask)
            ofmsgs.append(
                self.flood_table.flowmod(
                    match=match,
                    command=command,
                    inst=[
                        valve_of.apply_actions(
                            [valve_of.group_act(group.group_id)])
                    ],
                    priority=flood_priority))
        return ofmsgs

    def add_vlan(self, vlan, cold_start):
        ofmsgs = []
        ofmsgs.append(
            self.eth_src_table.flowcontroller(
                match=self.eth_src_table.match(vlan=vlan),
                priority=self.low_priority,
                inst=[self.eth_src_table.goto(self.output_table)]))
        ofmsgs.extend(self._build_flood_rules(vlan, cold_start))
        return ofmsgs

    def del_vlan(self, vlan):
        table = valve_table.wildcard_table
        return [table.flowdel(match=table.match(vlan=vlan))]

    def update_vlan(self, vlan):
        return self._build_flood_rules(vlan, cold_start=False, modify=True)

    def _find_forwarding_table(self, vlan):
        if vlan.acls_in:
            return self.vlan_acl_table
        return self.classification_table()

    def _port_add_vlan_rules(self, port, vlan, mirror_act, push_vlan=True):
        actions = copy.copy(mirror_act)
        match_vlan = vlan
        if push_vlan:
            actions.extend(valve_of.push_vlan_act(self.vlan_table, vlan.vid))
            match_vlan = NullVLAN()
        if self.has_externals:
            if port.loop_protect_external:
                actions.append(
                    self.vlan_table.set_no_external_forwarding_requested())
            else:
                actions.append(
                    self.vlan_table.set_external_forwarding_requested())
        inst = [
            valve_of.apply_actions(actions),
            self.vlan_table.goto(self._find_forwarding_table(vlan))
        ]
        return self.vlan_table.flowmod(self.vlan_table.match(
            in_port=port.number, vlan=match_vlan),
                                       priority=self.low_priority,
                                       inst=inst)

    def _native_vlan(self, port):
        for native_vlan in (port.dyn_dot1x_native_vlan, port.native_vlan):
            if native_vlan is not None:
                return native_vlan
        return None

    def lacp_advertise(self, port):
        ofmsgs = []
        if port.running() and port.lacp_active:
            ofmsgs.extend(self.lacp_req_reply(port.dyn_last_lacp_pkt, port))
        return ofmsgs

    def add_port(self, port):
        ofmsgs = []
        if port.vlans():
            mirror_act = port.mirror_actions()
            tagged_ofmsgs = []
            for vlan in port.tagged_vlans:
                tagged_ofmsgs.append(
                    self._port_add_vlan_rules(port,
                                              vlan,
                                              mirror_act,
                                              push_vlan=False))
            untagged_ofmsgs = []
            native_vlan = self._native_vlan(port)
            if native_vlan is not None:
                untagged_ofmsgs.append(
                    self._port_add_vlan_rules(port, native_vlan, mirror_act))
            # If no untagged VLANs, add explicit drop rule for untagged packets.
            if port.count_untag_vlan_miss and not untagged_ofmsgs:
                untagged_ofmsgs.append(
                    self.vlan_table.flowmod(self.vlan_table.match(
                        in_port=port.number, vlan=NullVLAN()),
                                            priority=self.low_priority))
            ofmsgs.extend(tagged_ofmsgs)
            ofmsgs.extend(untagged_ofmsgs)
            if port.lacp:
                ofmsgs.append(
                    self.vlan_table.flowcontroller(
                        self.vlan_table.match(
                            in_port=port.number,
                            eth_type=valve_of.ether.ETH_TYPE_SLOW,
                            eth_dst=valve_packet.SLOW_PROTOCOL_MULTICAST),
                        priority=self.dp_highest_priority,
                        max_len=valve_packet.LACP_SIZE))
                ofmsgs.extend(self.lacp_advertise(port))
        return ofmsgs

    def del_port(self, port):
        ofmsgs = []
        ofmsgs.append(
            self.eth_src_table.flowdel(
                self.eth_src_table.match(in_port=port.number)))
        for table in (self.eth_dst_table, self.eth_dst_hairpin_table):
            if table:
                # per OF 1.3.5 B.6.23, the OFA will match flows
                # that have an action targeting this port.
                ofmsgs.append(table.flowdel(out_port=port.number))
        for vlan in port.vlans():
            vlan.clear_cache_hosts_on_port(port)
        native_vlan = self._native_vlan(port)
        if native_vlan is not None:
            ofmsgs.append(
                self.vlan_table.flowdel(self.vlan_table.match(
                    in_port=port.number, vlan=port.native_vlan),
                                        priority=self.low_priority))
        return ofmsgs

    def _build_flood_rules(self, vlan, cold_start, modify=False):
        """Add flows to flood packets to unknown destinations on a VLAN."""
        command = valve_of.ofp.OFPFC_ADD
        if modify:
            command = valve_of.ofp.OFPFC_MODIFY_STRICT
        ofmsgs = self._build_multiout_flood_rules(vlan, command, cold_start)
        if self.use_group_table:
            ofmsgs.extend(self._build_group_flood_rules(vlan, modify, command))
        return ofmsgs

    @staticmethod
    def update_stack_topo(event, dp, port):  # pylint: disable=unused-argument,invalid-name
        """Update the stack topology. It has nothing to do for non-stacking DPs."""
        return

    @staticmethod
    def edge_learn_port(_other_valves, pkt_meta):
        """Possibly learn a host on a port.

        Args:
            other_valves (list): All Valves other than this one.
            pkt_meta (PacketMeta): PacketMeta instance for packet received.
        Returns:
            port to learn host on.
        """
        return pkt_meta.port

    def ban_rules(self, pkt_meta):
        """Limit learning to a maximum configured on this port/VLAN.

        Args:
            pkt_meta: PacketMeta instance.
        Returns:
            list: OpenFlow messages, if any.
        """
        ofmsgs = []

        port = pkt_meta.port
        eth_src = pkt_meta.eth_src
        vlan = pkt_meta.vlan

        entry = vlan.cached_host(eth_src)
        if entry is None:
            if port.max_hosts:
                if port.hosts_count() == port.max_hosts:
                    ofmsgs.append(
                        self._temp_ban_host_learning(
                            self.eth_src_table.match(in_port=port.number)))
                    port.dyn_learn_ban_count += 1
                    self.logger.info(
                        'max hosts %u reached on %s, '
                        'temporarily banning learning on this port, '
                        'and not learning %s' %
                        (port.max_hosts, port, eth_src))
            if vlan is not None and vlan.max_hosts:
                hosts_count = vlan.hosts_count()
                if hosts_count == vlan.max_hosts:
                    ofmsgs.append(
                        self._temp_ban_host_learning(
                            self.eth_src_table.match(vlan=vlan)))
                    vlan.dyn_learn_ban_count += 1
                    self.logger.info(
                        'max hosts %u reached on VLAN %u, '
                        'temporarily banning learning on this VLAN, '
                        'and not learning %s on %s' %
                        (vlan.max_hosts, vlan.vid, eth_src, port))
        return ofmsgs

    def _temp_ban_host_learning(self, match):
        return self.eth_src_table.flowdrop(match,
                                           priority=(self.low_priority + 1),
                                           hard_timeout=self.learn_ban_timeout)

    def delete_host_from_vlan(self, eth_src, vlan):
        """Delete a host from a VLAN."""
        ofmsgs = [
            self.eth_src_table.flowdel(
                self.eth_src_table.match(vlan=vlan, eth_src=eth_src))
        ]
        for table in (self.eth_dst_table, self.eth_dst_hairpin_table):
            if table:
                ofmsgs.append(
                    table.flowdel(table.match(vlan=vlan, eth_dst=eth_src)))
        return ofmsgs

    def expire_hosts_from_vlan(self, vlan, now):
        """Expire hosts from VLAN cache."""
        expired_hosts = vlan.expire_cache_hosts(now, self.learn_timeout)
        if expired_hosts:
            vlan.dyn_last_time_hosts_expired = now
            self.logger.info(
                '%u recently active hosts on VLAN %u, expired %s' %
                (vlan.hosts_count(), vlan.vid, expired_hosts))
        return expired_hosts

    def _jitter_learn_timeout(self, base_learn_timeout, port, eth_dst):
        """Calculate jittered learning timeout to avoid synchronized host timeouts."""
        # Hosts on this port never timeout.
        if port.permanent_learn:
            return 0
        if not base_learn_timeout:
            return 0
        # Jitter learn timeout based on eth address, so timeout processing is jittered,
        # the same hosts will timeout approximately the same time on a stack.
        jitter = hash(eth_dst) % self.learn_jitter
        min_learn_timeout = base_learn_timeout - self.learn_jitter
        return int(
            max(abs(min_learn_timeout + jitter), self.cache_update_guard_time))

    def _learn_host_timeouts(self, port, eth_src):
        """Calculate flow timeouts for learning on a port."""
        learn_timeout = self._jitter_learn_timeout(self.learn_timeout, port,
                                                   eth_src)

        # Update datapath to no longer send packets from this mac to controller
        # note the use of hard_timeout here and idle_timeout for the dst table
        # this is to ensure that the source rules will always be deleted before
        # any rules on the dst table. Otherwise if the dst table rule expires
        # but the src table rule is still being hit intermittantly the switch
        # will flood packets to that dst and not realise it needs to relearn
        # the rule
        # NB: Must be lower than highest priority otherwise it can match
        # flows destined to controller
        src_rule_idle_timeout = 0
        src_rule_hard_timeout = learn_timeout
        dst_rule_idle_timeout = learn_timeout + self.cache_update_guard_time
        if not self.idle_dst:
            dst_rule_idle_timeout = 0
        return (src_rule_idle_timeout, src_rule_hard_timeout,
                dst_rule_idle_timeout)

    def _external_forwarding_requested(self, port):  # pylint: disable=unused-argument
        if self.has_externals:
            return True
        return None

    def learn_host_on_vlan_port_flows(self, port, vlan, eth_src,
                                      delete_existing, refresh_rules,
                                      src_rule_idle_timeout,
                                      src_rule_hard_timeout,
                                      dst_rule_idle_timeout):
        """Return flows that implement learning a host on a port."""
        ofmsgs = []

        # Delete any existing entries for MAC.
        if delete_existing:
            ofmsgs.extend(self.delete_host_from_vlan(eth_src, vlan))

        # Associate this MAC with source port.
        src_match = self.eth_src_table.match(in_port=port.number,
                                             vlan=vlan,
                                             eth_src=eth_src)
        src_priority = self.host_priority - 1

        inst = []

        inst.append(self.eth_src_table.goto(self.output_table))

        ofmsgs.append(
            self.eth_src_table.flowmod(match=src_match,
                                       priority=src_priority,
                                       inst=inst,
                                       hard_timeout=src_rule_hard_timeout,
                                       idle_timeout=src_rule_idle_timeout))

        hairpinning = port.hairpin or port.hairpin_unicast
        # If we are refreshing only and not in hairpin mode, leave existing eth_dst alone.
        if refresh_rules and not hairpinning:
            return ofmsgs

        match_dict = {
            'vlan': vlan,
            'eth_dst': eth_src,
            valve_of.EXTERNAL_FORWARDING_FIELD: None
        }
        if self.has_externals:
            match_dict.update({
                valve_of.EXTERNAL_FORWARDING_FIELD:
                valve_of.PCP_EXT_PORT_FLAG
            })

        inst = self.pipeline.output(
            port,
            vlan,
            external_forwarding_requested=self._external_forwarding_requested(
                port))

        # Output packets for this MAC to specified port.
        ofmsgs.append(
            self.eth_dst_table.flowmod(self.eth_dst_table.match(**match_dict),
                                       priority=self.host_priority,
                                       inst=inst,
                                       idle_timeout=dst_rule_idle_timeout))

        if self.has_externals and not port.loop_protect_external:
            match_dict.update({
                valve_of.EXTERNAL_FORWARDING_FIELD:
                valve_of.PCP_NONEXT_PORT_FLAG
            })
            ofmsgs.append(
                self.eth_dst_table.flowmod(
                    self.eth_dst_table.match(**match_dict),
                    priority=self.host_priority,
                    inst=inst,
                    idle_timeout=dst_rule_idle_timeout))

        # If port is in hairpin mode, install a special rule
        # that outputs packets destined to this MAC back out the same
        # port they came in (e.g. multiple hosts on same WiFi AP,
        # and FAUCET is switching between them on the same port).
        if hairpinning:
            ofmsgs.append(
                self.eth_dst_hairpin_table.flowmod(
                    self.eth_dst_hairpin_table.match(in_port=port.number,
                                                     vlan=vlan,
                                                     eth_dst=eth_src),
                    priority=self.host_priority,
                    inst=self.pipeline.output(port, vlan, hairpin=True),
                    idle_timeout=dst_rule_idle_timeout))

        return ofmsgs

    def _perm_learn_check(
            self,
            entry,
            vlan,
            now,
            eth_src,
            port,
            ofmsgs,  # pylint: disable=unused-argument
            cache_port,
            cache_age,
            delete_existing,
            refresh_rules):
        learn_exit = False
        update_cache = True
        if entry is not None and entry.port.permanent_learn:
            if entry.port != port:
                ofmsgs.extend(
                    self.pipeline.filter_packets({
                        'eth_src': eth_src,
                        'in_port': port.number
                    }))
            learn_exit = True
            update_cache = False
        return (learn_exit, ofmsgs, cache_port, update_cache, delete_existing,
                refresh_rules)

    def _learn_cache_check(
            self,
            entry,
            vlan,
            now,
            eth_src,
            port,
            ofmsgs,  # pylint: disable=unused-argument
            cache_port,
            cache_age,
            delete_existing,
            refresh_rules):
        learn_exit = False
        update_cache = True
        if cache_port is not None:
            # packet was received on same member of a LAG.
            same_lag = (port.lacp and port.lacp == cache_port.lacp)
            guard_time = self.cache_update_guard_time
            if cache_port == port or same_lag:
                # aggressively re-learn on LAGs
                if same_lag:
                    guard_time = 2
                # port didn't change status, and recent cache update, don't do anything.
                if (cache_age < guard_time and port.dyn_update_time is not None
                        and port.dyn_update_time <= entry.cache_time):
                    update_cache = False
                    learn_exit = True
                # skip delete if host didn't change ports or on same LAG.
                elif cache_port == port or same_lag:
                    delete_existing = False
                    refresh_rules = True
        return (learn_exit, ofmsgs, cache_port, update_cache, delete_existing,
                refresh_rules)

    def _loop_protect_check(
            self,
            entry,
            vlan,
            now,
            eth_src,
            port,
            ofmsgs,  # pylint: disable=unused-argument
            cache_port,
            cache_age,
            delete_existing,
            refresh_rules):
        learn_exit = False
        update_cache = True
        if port.loop_protect:
            ban_age = None
            learn_ban = False

            # if recently in loop protect mode and still receiving packets,
            # prolong the ban
            if port.dyn_last_ban_time:
                ban_age = now - port.dyn_last_ban_time
                if ban_age < self.cache_update_guard_time:
                    learn_ban = True

            # if not in protect mode and we get a rapid move, enact protect mode
            if not learn_ban and entry is not None:
                if port != cache_port and cache_age < self.cache_update_guard_time:
                    learn_ban = True
                    port.dyn_learn_ban_count += 1
                    self.logger.info(
                        'rapid move of %s from %s to %s, temp loop ban %s' %
                        (eth_src, cache_port, port, port))

            # already, or newly in protect mode, apply the ban rules.
            if learn_ban:
                port.dyn_last_ban_time = now
                ofmsgs.append(
                    self._temp_ban_host_learning(
                        self.eth_src_table.match(in_port=port.number)))
                learn_exit = True
        return (learn_exit, ofmsgs, cache_port, update_cache, delete_existing,
                refresh_rules)

    def _learn_check(
            self,
            entry,
            vlan,
            now,
            eth_src,
            port,
            ofmsgs,  # pylint: disable=unused-argument
            cache_port,
            cache_age,
            delete_existing,
            refresh_rules):
        learn_exit = True
        update_cache = True
        (src_rule_idle_timeout, src_rule_hard_timeout,
         dst_rule_idle_timeout) = self._learn_host_timeouts(port, eth_src)

        ofmsgs.extend(
            self.learn_host_on_vlan_port_flows(port, vlan, eth_src,
                                               delete_existing, refresh_rules,
                                               src_rule_idle_timeout,
                                               src_rule_hard_timeout,
                                               dst_rule_idle_timeout))
        return (learn_exit, ofmsgs, cache_port, update_cache, delete_existing,
                refresh_rules)

    def learn_host_on_vlan_ports(self,
                                 now,
                                 port,
                                 vlan,
                                 eth_src,
                                 delete_existing=True,
                                 last_dp_coldstart_time=None):
        """Learn a host on a port."""
        ofmsgs = []
        cache_port = None
        cache_age = None
        refresh_rules = False
        update_cache = True
        entry = vlan.cached_host(eth_src)

        # Host not cached, and no hosts expired since we cold started
        # Enable faster learning by assuming there's no previous host to delete
        if entry is None:
            if (last_dp_coldstart_time and
                (vlan.dyn_last_time_hosts_expired is None or
                 vlan.dyn_last_time_hosts_expired < last_dp_coldstart_time)):
                delete_existing = False
        else:
            cache_age = now - entry.cache_time
            cache_port = entry.port

        for learn_func in (self._perm_learn_check, self._learn_cache_check,
                           self._loop_protect_check, self._learn_check):
            (learn_exit, ofmsgs, cache_port, update_cache, delete_existing,
             refresh_rules) = learn_func(entry, vlan, now, eth_src, port,
                                         ofmsgs, cache_port, cache_age,
                                         delete_existing, refresh_rules)
            if learn_exit:
                break

        return (ofmsgs, cache_port, update_cache)

    def flow_timeout(self, _now, _table_id, _match):
        """Handle a flow timed out message from dataplane."""
        return []

    def lacp_update_actor_state(self,
                                port,
                                lacp_up,
                                now=None,
                                lacp_pkt=None,
                                cold_start=False):
        """Updates a LAG actor state.

        Args:
            port: LACP port
            lacp_up (bool): Whether LACP is going UP or DOWN
            now (float): Current epoch time
            lacp_pkt (PacketMeta): LACP packet
            cold_start (bool): Whether the port is being cold started
        Returns:
            bool: True if LACP state changed
        """
        prev_actor_state = port.actor_state()
        new_actor_state = port.lacp_actor_update(lacp_up,
                                                 now=now,
                                                 lacp_pkt=lacp_pkt,
                                                 cold_start=cold_start)
        if prev_actor_state != new_actor_state:
            self.logger.info(
                'LAG %u %s actor state %s (previous state %s)' %
                (port.lacp, port, port.actor_state_name(new_actor_state),
                 port.actor_state_name(prev_actor_state)))
        return prev_actor_state != new_actor_state

    def enable_forwarding(self, port):
        ofmsgs = []
        ofmsgs.append(
            self.vlan_table.flowdel(
                match=self.vlan_table.match(in_port=port.number),
                priority=self.dp_high_priority,
                strict=True))
        return ofmsgs

    def disable_forwarding(self, port):
        ofmsgs = []
        ofmsgs.append(
            self.vlan_table.flowdrop(
                match=self.vlan_table.match(in_port=port.number),
                priority=self.dp_high_priority))
        return ofmsgs

    def lacp_req_reply(self, lacp_pkt, port):
        """
        Constructs a LACP req-reply packet.

        Args:
            lacp_pkt (PacketMeta): LACP packet received
            port: LACP port
            other_valves (list): List of other valves

        Returns:
            list packetout OpenFlow msgs.
        """
        if port.lacp_passthrough:
            for peer_num in port.lacp_passthrough:
                lacp_peer = self.ports.get(peer_num, None)
                if not lacp_peer.dyn_lacp_up:
                    self.logger.warning(
                        'Suppressing LACP LAG %s on %s, peer %s link is down' %
                        (port.lacp, port, lacp_peer))
                    return []
        actor_state_activity = 0
        if port.lacp_active:
            actor_state_activity = 1
        actor_state_sync, actor_state_col, actor_state_dist = port.get_lacp_flags(
        )
        if lacp_pkt:
            pkt = valve_packet.lacp_reqreply(
                self.faucet_dp_mac, self.faucet_dp_mac, port.lacp,
                port.lacp_port_id, port.lacp_port_priority, actor_state_sync,
                actor_state_activity, actor_state_col, actor_state_dist,
                lacp_pkt.actor_system, lacp_pkt.actor_key, lacp_pkt.actor_port,
                lacp_pkt.actor_system_priority, lacp_pkt.actor_port_priority,
                lacp_pkt.actor_state_defaulted, lacp_pkt.actor_state_expired,
                lacp_pkt.actor_state_timeout, lacp_pkt.actor_state_collecting,
                lacp_pkt.actor_state_distributing,
                lacp_pkt.actor_state_aggregation,
                lacp_pkt.actor_state_synchronization,
                lacp_pkt.actor_state_activity)
        else:
            pkt = valve_packet.lacp_reqreply(
                self.faucet_dp_mac,
                self.faucet_dp_mac,
                port.lacp,
                port.lacp_port_id,
                port.lacp_port_priority,
                actor_state_synchronization=actor_state_sync,
                actor_state_activity=actor_state_activity,
                actor_state_collecting=actor_state_col,
                actor_state_distributing=actor_state_dist)
        self.logger.debug('Sending LACP %s on %s activity %s' %
                          (pkt, port, actor_state_activity))
        return [valve_of.packetout(port.number, pkt.data)]

    def get_lacp_dpid_nomination(self, lacp_id, valve, other_valves):  # pylint: disable=unused-argument
        """Chooses the DP for a given LAG.

        The DP will be nominated by the following conditions in order:
            1) Number of LAG ports
            2) Root DP
            3) Lowest DPID

        Args:
            lacp_id: The LACP LAG ID
            other_valves (list): list of other valves
        Returns:
            nominated_dpid, reason
        """
        return (valve.dp.dp_id, 'standalone')

    def lacp_update_port_selection_state(self,
                                         port,
                                         valve,
                                         other_valves=None,
                                         cold_start=False):
        """Update the LACP port selection state.

        Args:
            port (Port): LACP port
            other_valves (list): List of other valves
            cold_start (bool): Whether the port is being cold started
        Returns:
            bool: True if port state changed
        """
        nominated_dpid, _ = self.get_lacp_dpid_nomination(
            port.lacp, valve, other_valves)
        prev_state = port.lacp_port_state()
        new_state = port.lacp_port_update(valve.dp.dp_id == nominated_dpid,
                                          cold_start=cold_start)
        if new_state != prev_state:
            self.logger.info('LAG %u %s %s (previous state %s)' %
                             (port.lacp, port, port.port_role_name(new_state),
                              port.port_role_name(prev_state)))
        return new_state != prev_state

    def lacp_handler(self, now, pkt_meta, valve, other_valves, lacp_update):
        """
        Handle receiving an LACP packet
        Args:
            now (float): current epoch time
            pkt_meta (PacketMeta): packet for control plane
            valve (Valve): valve instance
            other_valves (list): all other valves
            lacp_update: callable to signal LACP state changes
        Returns
            dict: OpenFlow messages, if any by Valve
        """
        ofmsgs_by_valve = defaultdict(list)
        if (pkt_meta.eth_dst == valve_packet.SLOW_PROTOCOL_MULTICAST
                and pkt_meta.eth_type == valve_of.ether.ETH_TYPE_SLOW
                and pkt_meta.port.lacp):
            # LACP packet so reparse
            pkt_meta.data = pkt_meta.data[:valve_packet.LACP_SIZE]
            pkt_meta.reparse_all()
            lacp_pkt = valve_packet.parse_lacp_pkt(pkt_meta.pkt)
            if lacp_pkt:
                self.logger.debug('receive LACP %s on %s' %
                                  (lacp_pkt, pkt_meta.port))
                # Respond to new LACP packet or if we haven't sent anything in a while
                age = None
                if pkt_meta.port.dyn_lacp_last_resp_time:
                    age = now - pkt_meta.port.dyn_lacp_last_resp_time
                lacp_pkt_change = (
                    pkt_meta.port.dyn_last_lacp_pkt is None
                    or str(lacp_pkt) != str(pkt_meta.port.dyn_last_lacp_pkt))
                lacp_resp_interval = pkt_meta.port.lacp_resp_interval
                if lacp_pkt_change or (age is not None
                                       and age > lacp_resp_interval):
                    ofmsgs_by_valve[valve].extend(
                        self.lacp_req_reply(lacp_pkt, pkt_meta.port))
                    pkt_meta.port.dyn_lacp_last_resp_time = now
                # Update the LACP information
                actor_up = lacp_pkt.actor_state_synchronization
                ofmsgs_by_valve[valve].extend(
                    lacp_update(pkt_meta.port,
                                actor_up,
                                now=now,
                                lacp_pkt=lacp_pkt,
                                other_valves=other_valves))
                # Determine if LACP ports with the same ID have met different actor systems
                other_lag_ports = [
                    port for port in self.ports.values() if
                    port.lacp == pkt_meta.port.lacp and port.dyn_last_lacp_pkt
                ]
                actor_system = lacp_pkt.actor_system
                for other_lag_port in other_lag_ports:
                    other_actor_system = other_lag_port.dyn_last_lacp_pkt.actor_system
                    if actor_system != other_actor_system:
                        self.logger.error(
                            'LACP actor system mismatch %s: %s, %s %s' %
                            (pkt_meta.port, actor_system, other_lag_port,
                             other_actor_system))
        return ofmsgs_by_valve

    def learn_host_from_pkt(self, valve, now, pkt_meta, other_valves):
        """Learn host from packet."""
        ofmsgs = []
        ofmsgs.extend(valve.learn_host(now, pkt_meta, other_valves))
        ofmsgs.extend(valve.router_rcv_packet(now, pkt_meta))
        return {valve: ofmsgs}
コード例 #5
0
class ValveFloodManager(ValveManagerBase):
    """Implement dataplane based flooding for standalone dataplanes."""

    # Enumerate possible eth_dst flood destinations.
    # First bool says whether to flood this destination if the VLAN
    # has unicast flooding enabled (if unicast flooding is enabled,
    # then we flood all destination eth_dsts).
    FLOOD_DSTS = (
        (True, None, None, None),
        (False, None, valve_packet.BRIDGE_GROUP_ADDRESS,
         valve_packet.mac_byte_mask(3)),  # 802.x
        (False, None, '01:00:5E:00:00:00',
         valve_packet.mac_byte_mask(3)),  # IPv4 multicast
        (False, None, '33:33:00:00:00:00',
         valve_packet.mac_byte_mask(2)),  # IPv6 multicast
        (False, None, valve_of.mac.BROADCAST_STR,
         valve_packet.mac_byte_mask(6)),  # eth broadcasts
    )
    # Ports with restricted broadcast enabled may only receive these broadcasts.
    RESTRICTED_FLOOD_DISTS = (
        (False, valve_of.ether.ETH_TYPE_ARP, valve_of.mac.BROADCAST_STR,
         valve_packet.mac_byte_mask(6)),  # ARP
        (False, valve_of.ether.ETH_TYPE_IPV6, '33:33:FF:00:00:00',
         valve_packet.mac_byte_mask(3)),  # IPv6 multicast for ND
        (False,
         valve_of.ether.ETH_TYPE_IPV6, valve_packet.IPV6_ALL_ROUTERS_MCAST,
         valve_packet.mac_byte_mask(6)),  # IPV6 all routers
        (False,
         valve_of.ether.ETH_TYPE_IPV6, valve_packet.IPV6_ALL_NODES_MCAST,
         valve_packet.mac_byte_mask(6)),  # IPv6 all nodes
    )

    def __init__(
            self,
            logger,
            flood_table,
            pipeline,  # pylint: disable=too-many-arguments
            use_group_table,
            groups,
            combinatorial_port_flood,
            canonical_port_order,
            restricted_bcast_arpnd):
        self.logger = logger
        self.flood_table = flood_table
        self.pipeline = pipeline
        self.use_group_table = use_group_table
        self.groups = groups
        self.combinatorial_port_flood = combinatorial_port_flood
        self.bypass_priority = self._FILTER_PRIORITY
        self.flood_priority = self._MATCH_PRIORITY
        self.classification_offset = 0x100
        self.canonical_port_order = canonical_port_order
        self.restricted_bcast_arpnd = restricted_bcast_arpnd
        if restricted_bcast_arpnd:
            self.flood_dsts = self.FLOOD_DSTS + self.RESTRICTED_FLOOD_DISTS
        else:
            self.flood_dsts = self.FLOOD_DSTS

    def initialise_tables(self):
        """Initialise the flood table with filtering flows."""
        ofmsgs = []
        for eth_dst, eth_dst_mask in (
            (valve_packet.CISCO_CDP_VTP_UDLD_ADDRESS,
             valve_packet.mac_byte_mask(6)),
            (valve_packet.CISCO_SPANNING_GROUP_ADDRESS,
             valve_packet.mac_byte_mask(6)),
            (valve_packet.BRIDGE_GROUP_ADDRESS,
             valve_packet.BRIDGE_GROUP_MASK)):
            ofmsgs.append(
                self.flood_table.flowdrop(
                    self.flood_table.match(eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask),
                    priority=self._mask_flood_priority(eth_dst_mask)))
        return ofmsgs

    def floods_to_root(self, _dp_obj):
        """Return True if the given dp floods (only) to root switch"""
        return False

    def _mask_flood_priority(self, eth_dst_mask):
        return self.flood_priority + valve_packet.mac_mask_bits(eth_dst_mask)

    @staticmethod
    def _vlan_all_ports(vlan, exclude_unicast):
        """Return list of all ports that should be flooded to on a VLAN."""
        return list(vlan.flood_ports(vlan.get_ports(), exclude_unicast))

    def _build_flood_local_rule_actions(
            self,
            vlan,
            exclude_unicast,
            in_port,  # pylint: disable=too-many-arguments
            exclude_all_external,
            exclude_restricted_bcast_arpnd):
        """Return a list of flood actions to flood packets from a port."""
        external_ports = self.canonical_port_order(
            vlan.loop_protect_external_ports_up())
        exclude_ports = vlan.exclude_same_lag_member_ports(in_port)
        exclude_ports.update(vlan.exclude_native_if_dot1x())
        if exclude_all_external or (in_port is not None
                                    and in_port.loop_protect_external):
            exclude_ports.update(set(external_ports))
        else:
            exclude_ports.update(set(external_ports[1:]))
        if exclude_restricted_bcast_arpnd:
            exclude_ports.update(set(vlan.restricted_bcast_arpnd_ports()))
        return valve_of.flood_port_outputs(
            vlan.tagged_flood_ports(exclude_unicast),
            vlan.untagged_flood_ports(exclude_unicast),
            in_port=in_port,
            exclude_ports=exclude_ports)

    def _build_flood_rule_actions(
            self,
            vlan,
            exclude_unicast,
            in_port,  # pylint: disable=too-many-arguments
            exclude_all_external=False,
            exclude_restricted_bcast_arpnd=False):
        actions = []
        if vlan.loop_protect_external_ports() and vlan.tagged_flood_ports(
                exclude_unicast):
            actions.append(
                self.flood_table.set_external_forwarding_requested())
        actions.extend(
            self._build_flood_local_rule_actions(
                vlan, exclude_unicast, in_port, exclude_all_external,
                exclude_restricted_bcast_arpnd))
        return actions

    def _build_flood_rule(self, match, command, flood_acts, flood_priority):
        return self.flood_table.flowmod(
            match=match,
            command=command,
            inst=[valve_of.apply_actions(flood_acts)],
            priority=flood_priority)

    def _vlan_flood_priority(self, eth_type, eth_dst_mask):
        priority = self._mask_flood_priority(eth_dst_mask)
        if eth_type:
            priority += eth_type
        return priority

    def _build_flood_rule_for_vlan(
            self,
            vlan,
            eth_type,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            command):
        flood_priority = self._vlan_flood_priority(eth_type, eth_dst_mask)
        match = self.flood_table.match(vlan=vlan,
                                       eth_type=eth_type,
                                       eth_dst=eth_dst,
                                       eth_dst_mask=eth_dst_mask)
        # TODO: optimization - drop all general flood dsts if all ports are restricted.
        exclude_restricted_bcast_arpnd = True
        flood_acts = self._build_flood_rule_actions(
            vlan,
            exclude_unicast,
            None,
            exclude_restricted_bcast_arpnd=exclude_restricted_bcast_arpnd)
        return (self._build_flood_rule(match, command, flood_acts,
                                       flood_priority), flood_acts)

    @staticmethod
    def _output_non_output_actions(flood_acts):
        output_ports = set()
        all_nonoutput_actions = set()
        deduped_acts = []
        # avoid dedupe_ofmsgs() here, as it's expensive - most of the time we are comparing
        # port numbers as integers which is much cheaper.
        for act in flood_acts:
            if valve_of.is_output(act):
                if act.port in output_ports:
                    continue
                output_ports.add(act.port)
            else:
                str_act = str(act)
                if str_act in all_nonoutput_actions:
                    continue
                all_nonoutput_actions.add(str_act)
            deduped_acts.append(act)
        nonoutput_actions = all_nonoutput_actions - set(
            [str(valve_of.pop_vlan())])
        return (deduped_acts, output_ports, nonoutput_actions)

    def _build_flood_acts_for_port(
            self,
            vlan,
            exclude_unicast,
            port,  # pylint: disable=too-many-arguments
            exclude_all_external=False,
            exclude_restricted_bcast_arpnd=False):
        flood_acts = []
        port_output_ports = []
        port_non_output_acts = []
        if port.dyn_phys_up:
            if exclude_restricted_bcast_arpnd:
                flood_acts = self._build_flood_rule_actions(
                    vlan, exclude_unicast, port, exclude_all_external,
                    port.restricted_bcast_arpnd)
            else:
                flood_acts = self._build_flood_rule_actions(
                    vlan, exclude_unicast, port, exclude_all_external, False)
            flood_acts, port_output_ports, port_non_output_acts = self._output_non_output_actions(
                flood_acts)
            if not port_output_ports:
                flood_acts = []
                port_non_output_acts = []
        return (flood_acts, port_output_ports, port_non_output_acts)

    def _build_flood_match_priority(
            self,
            port,
            vlan,
            eth_type,  # pylint: disable=too-many-arguments
            eth_dst,
            eth_dst_mask,
            add_match):
        flood_priority = self._vlan_flood_priority(eth_type, eth_dst_mask) + 1
        if add_match is None:
            add_match = {}
        match = self.flood_table.match(vlan=vlan,
                                       in_port=port.number,
                                       eth_type=eth_type,
                                       eth_dst=eth_dst,
                                       eth_dst_mask=eth_dst_mask,
                                       **add_match)
        return (flood_priority, match)

    def _build_flood_rule_for_port(
            self,
            vlan,
            eth_type,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            command,
            port,
            flood_acts,
            add_match=None):
        flood_priority, match = self._build_flood_match_priority(
            port, vlan, eth_type, eth_dst, eth_dst_mask, add_match)
        return self._build_flood_rule(match, command, flood_acts,
                                      flood_priority)

    def _build_mask_flood_rules(
            self,
            vlan,
            eth_type,
            eth_dst,
            eth_dst_mask,  # pylint: disable=too-many-arguments
            exclude_unicast,
            exclude_restricted_bcast_arpnd,
            command):
        ofmsgs = []
        if self.combinatorial_port_flood:
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                flood_acts, _, _ = self._build_flood_acts_for_port(
                    vlan,
                    exclude_unicast,
                    port,
                    exclude_restricted_bcast_arpnd=
                    exclude_restricted_bcast_arpnd)
                if flood_acts:
                    ofmsgs.append(
                        self._build_flood_rule_for_port(
                            vlan, eth_type, eth_dst, eth_dst_mask, command,
                            port, flood_acts))
        else:
            vlan_flood_ofmsg, vlan_flood_acts = self._build_flood_rule_for_vlan(
                vlan, eth_type, eth_dst, eth_dst_mask, exclude_unicast,
                command)
            if not self.use_group_table:
                ofmsgs.append(vlan_flood_ofmsg)
            flood_acts, vlan_output_ports, vlan_non_output_acts = self._output_non_output_actions(
                vlan_flood_acts)
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                (flood_acts, port_output_ports,
                 port_non_output_acts) = self._build_flood_acts_for_port(
                     vlan,
                     exclude_unicast,
                     port,
                     exclude_restricted_bcast_arpnd=
                     exclude_restricted_bcast_arpnd)
                if not flood_acts:
                    continue
                if (vlan_output_ports - set([port.number]) == port_output_ports
                        and vlan_non_output_acts == port_non_output_acts):
                    # Delete a potentially existing port specific flow
                    # TODO: optimize, avoid generating delete for port if no existing flow.
                    flood_priority, match = self._build_flood_match_priority(
                        port,
                        vlan,
                        eth_type,
                        eth_dst,
                        eth_dst_mask,
                        add_match=None)
                    ofmsgs.append(
                        self.flood_table.flowdel(match=match,
                                                 priority=flood_priority))
                else:
                    ofmsgs.append(
                        self._build_flood_rule_for_port(
                            vlan, eth_type, eth_dst, eth_dst_mask, command,
                            port, flood_acts))
        return ofmsgs

    def _build_multiout_flood_rules(self, vlan, command):
        """Build flooding rules for a VLAN without using groups."""
        ofmsgs = []
        for unicast_eth_dst, eth_type, eth_dst, eth_dst_mask in self.flood_dsts:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            exclude_restricted_bcast_arpnd = eth_type is None
            ofmsgs.extend(
                self._build_mask_flood_rules(vlan, eth_type, eth_dst,
                                             eth_dst_mask, unicast_eth_dst,
                                             exclude_restricted_bcast_arpnd,
                                             command))
        return ofmsgs

    def _build_group_flood_rules(self, vlan, modify, command):
        """Build flooding rules for a VLAN using groups."""
        _, vlan_flood_acts = self._build_flood_rule_for_vlan(
            vlan, None, None, None, False, command)
        group_id = vlan.vid
        group = self.groups.get_entry(
            group_id, valve_of.build_group_flood_buckets(vlan_flood_acts))
        groups_by_unicast_eth = {False: group, True: group}
        ofmsgs = []

        # Only configure unicast flooding group if has different output
        # actions to non unicast flooding.
        _, unicast_eth_vlan_flood_acts = self._build_flood_rule_for_vlan(
            vlan, None, None, None, True, command)
        unicast_eth_vlan_flood_acts, unicast_output_ports, _ = self._output_non_output_actions(
            unicast_eth_vlan_flood_acts)
        vlan_flood_acts, vlan_output_ports, _ = self._output_non_output_actions(
            vlan_flood_acts)
        if unicast_output_ports != vlan_output_ports:
            group_id += valve_of.VLAN_GROUP_OFFSET
            group = self.groups.get_entry(
                group_id,
                valve_of.build_group_flood_buckets(
                    unicast_eth_vlan_flood_acts))
            groups_by_unicast_eth[True] = group

        for group in groups_by_unicast_eth.values():
            if modify:
                ofmsgs.append(group.modify())
            else:
                ofmsgs.extend(group.add())

        for unicast_eth_dst, eth_type, eth_dst, eth_dst_mask in self.flood_dsts:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            group = groups_by_unicast_eth[unicast_eth_dst]
            match = self.flood_table.match(vlan=vlan,
                                           eth_type=eth_type,
                                           eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask)
            flood_priority = self._vlan_flood_priority(eth_type, eth_dst_mask)
            ofmsgs.append(
                self.flood_table.flowmod(
                    match=match,
                    command=command,
                    inst=[
                        valve_of.apply_actions(
                            [valve_of.group_act(group.group_id)])
                    ],
                    priority=flood_priority))
        return ofmsgs

    def add_vlan(self, vlan):
        return self.build_flood_rules(vlan)

    def del_vlan(self, vlan):
        return [
            self.flood_table.flowdel(self.flood_table.match(vlan=vlan.vid))
        ]

    def update_vlan(self, vlan):
        return self.build_flood_rules(vlan, modify=True)

    def build_flood_rules(self, vlan, modify=False):
        """Add flows to flood packets to unknown destinations on a VLAN."""
        command = valve_of.ofp.OFPFC_ADD
        if modify:
            command = valve_of.ofp.OFPFC_MODIFY_STRICT
        ofmsgs = self._build_multiout_flood_rules(vlan, command)
        if self.use_group_table:
            ofmsgs.extend(self._build_group_flood_rules(vlan, modify, command))
        return ofmsgs

    @staticmethod
    def update_stack_topo(event, dp, port=None):  # pylint: disable=unused-argument,invalid-name
        """Update the stack topology. It has nothing to do for non-stacking DPs."""
        return

    @staticmethod
    def edge_learn_port(_other_valves, pkt_meta):
        """Possibly learn a host on a port.

        Args:
            other_valves (list): All Valves other than this one.
            pkt_meta (PacketMeta): PacketMeta instance for packet received.
        Returns:
            port to learn host on.
        """
        return pkt_meta.port
コード例 #6
0
ファイル: valve_flood.py プロジェクト: jrosenbl/faucet
class ValveFloodManager(object):
    """Implement dataplane based flooding for standalone dataplanes."""

    # Enumerate possible eth_dst flood destinations.
    # First bool says whether to flood this destination, if the VLAN
    # has unicast flooding enabled (if unicast flooding is enabled,
    # then we flood all destination eth_dsts).
    FLOOD_DSTS = (
        (True, None, None),
        (False, valve_packet.BRIDGE_GROUP_ADDRESS,
         valve_packet.mac_byte_mask(3)),  # 802.x
        (False, '01:00:5E:00:00:00',
         valve_packet.mac_byte_mask(3)),  # IPv4 multicast
        (False, '33:33:00:00:00:00',
         valve_packet.mac_byte_mask(2)),  # IPv6 multicast
        (False, valve_of.mac.BROADCAST_STR,
         None),  # flood on ethernet broadcasts
    )

    def __init__(self, flood_table, flood_priority, use_group_table, groups):
        self.flood_table = flood_table
        self.flood_priority = flood_priority
        self.use_group_table = use_group_table
        self.groups = groups

    @staticmethod
    def _vlan_all_ports(vlan, exclude_unicast):
        """Return list of all ports that should be flooded to on a VLAN."""
        return vlan.flood_ports(vlan.get_ports(), exclude_unicast)

    @staticmethod
    def _build_flood_local_rule_actions(vlan, exclude_unicast, in_port):
        """Return a list of flood actions to flood packets from a port."""
        flood_acts = []
        exclude_ports = []
        if in_port.lacp:
            lags = vlan.lags()
            exclude_ports = lags[in_port.lacp]
        tagged_ports = vlan.tagged_flood_ports(exclude_unicast)
        flood_acts.extend(
            valve_of.flood_tagged_port_outputs(tagged_ports,
                                               in_port,
                                               exclude_ports=exclude_ports))
        untagged_ports = vlan.untagged_flood_ports(exclude_unicast)
        flood_acts.extend(
            valve_of.flood_untagged_port_outputs(untagged_ports,
                                                 in_port,
                                                 exclude_ports=exclude_ports))
        return flood_acts

    def _build_flood_rule_actions(self, vlan, exclude_unicast, in_port):
        return self._build_flood_local_rule_actions(vlan, exclude_unicast,
                                                    in_port)

    def _build_flood_rule_for_port(self, vlan, eth_dst, eth_dst_mask,
                                   exclude_unicast, command, flood_priority,
                                   port, preflood_acts):
        ofmsgs = []
        match = self.flood_table.match(vlan=vlan,
                                       in_port=port.number,
                                       eth_dst=eth_dst,
                                       eth_dst_mask=eth_dst_mask)
        flood_acts = self._build_flood_rule_actions(vlan, exclude_unicast,
                                                    port)
        ofmsgs.append(
            self.flood_table.flowmod(
                match=match,
                command=command,
                inst=[valve_of.apply_actions(preflood_acts + flood_acts)],
                priority=flood_priority))
        return ofmsgs

    def _build_unmirrored_flood_rules(self, vlan, eth_dst, eth_dst_mask,
                                      exclude_unicast, command,
                                      flood_priority):
        ofmsgs = []
        for port in self._vlan_all_ports(vlan, exclude_unicast):
            ofmsgs.extend(
                self._build_flood_rule_for_port(vlan, eth_dst, eth_dst_mask,
                                                exclude_unicast, command,
                                                flood_priority, port, []))
        return ofmsgs

    def _build_mirrored_flood_rules(self, vlan, eth_dst, eth_dst_mask,
                                    exclude_unicast, command, flood_priority):
        ofmsgs = []
        mirrored_ports = vlan.mirrored_ports()
        for port in mirrored_ports:
            mirror_acts = port.mirror_actions()
            ofmsgs.extend(
                self._build_flood_rule_for_port(vlan, eth_dst, eth_dst_mask,
                                                exclude_unicast, command,
                                                flood_priority, port,
                                                mirror_acts))
        return ofmsgs

    def _build_multiout_flood_rules(self, vlan, command):
        """Build flooding rules for a VLAN without using groups."""
        flood_priority = self.flood_priority
        ofmsgs = []
        for unicast_eth_dst, eth_dst, eth_dst_mask in self.FLOOD_DSTS:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            ofmsgs.extend(
                self._build_unmirrored_flood_rules(vlan, eth_dst, eth_dst_mask,
                                                   unicast_eth_dst, command,
                                                   flood_priority))
            flood_priority += 1
            ofmsgs.extend(
                self._build_mirrored_flood_rules(vlan, eth_dst, eth_dst_mask,
                                                 unicast_eth_dst, command,
                                                 flood_priority))
            flood_priority += 1
        return ofmsgs

    @staticmethod
    def _build_group_buckets(vlan, unicast_flood):
        buckets = []
        tagged_flood_ports = vlan.tagged_flood_ports(unicast_flood)
        buckets.extend(valve_of.group_flood_buckets(tagged_flood_ports, False))
        untagged_flood_ports = vlan.untagged_flood_ports(unicast_flood)
        buckets.extend(valve_of.group_flood_buckets(untagged_flood_ports,
                                                    True))
        return buckets

    def _build_group_flood_rules(self, vlan, modify, command):
        """Build flooding rules for a VLAN using groups."""
        flood_priority = self.flood_priority
        broadcast_group = self.groups.get_entry(
            vlan.vid, self._build_group_buckets(vlan, False))
        unicast_group = self.groups.get_entry(
            vlan.vid + valve_of.VLAN_GROUP_OFFSET,
            self._build_group_buckets(vlan, vlan.unicast_flood))
        ofmsgs = []
        if modify:
            ofmsgs.append(broadcast_group.modify())
            ofmsgs.append(unicast_group.modify())
        else:
            ofmsgs.extend(broadcast_group.add())
            ofmsgs.extend(unicast_group.add())
        for unicast_eth_dst, eth_dst, eth_dst_mask in self.FLOOD_DSTS:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            group = broadcast_group
            if not eth_dst:
                group = unicast_group
            match = self.flood_table.match(vlan=vlan,
                                           eth_dst=eth_dst,
                                           eth_dst_mask=eth_dst_mask)
            ofmsgs.append(
                self.flood_table.flowmod(
                    match=match,
                    command=command,
                    inst=[
                        valve_of.apply_actions(
                            [valve_of.group_act(group.group_id)])
                    ],
                    priority=flood_priority))
            flood_priority += 1
        return ofmsgs

    def build_flood_rules(self, vlan, modify=False):
        """Add flows to flood packets to unknown destinations on a VLAN."""
        # TODO: group table support is still fairly uncommon, so
        # group tables are currently optional.
        command = valve_of.ofp.OFPFC_ADD
        if modify:
            command = valve_of.ofp.OFPFC_MODIFY_STRICT
        if self.use_group_table:
            hairpin_ports = vlan.hairpin_ports()
            # TODO: hairpin flooding modes.
            # TODO: avoid loopback flood on LAG ports
            if not hairpin_ports:
                return self._build_group_flood_rules(vlan, modify, command)
        return self._build_multiout_flood_rules(vlan, command)

    @staticmethod
    def edge_learn_port(_other_valves, pkt_meta):
        """Possibly learn a host on a port.

        Args:
            other_valves (list): All Valves other than this one.
            pkt_meta (PacketMeta): PacketMeta instance for packet received.
        Returns:
            port to learn host on.
        """
        return pkt_meta.port
コード例 #7
0
class ValveFloodManager(ValveManagerBase):
    """Implement dataplane based flooding for standalone dataplanes."""

    # Enumerate possible eth_dst flood destinations.
    # First bool says whether to flood this destination, if the VLAN
    # has unicast flooding enabled (if unicast flooding is enabled,
    # then we flood all destination eth_dsts).
    FLOOD_DSTS = (
        (True, None, None),
        (False, valve_packet.BRIDGE_GROUP_ADDRESS, valve_packet.mac_byte_mask(3)), # 802.x
        (False, '01:00:5E:00:00:00', valve_packet.mac_byte_mask(3)), # IPv4 multicast
        (False, '33:33:00:00:00:00', valve_packet.mac_byte_mask(2)), # IPv6 multicast
        (False, valve_of.mac.BROADCAST_STR, None), # flood on ethernet broadcasts
    )

    def __init__(self, flood_table, eth_src_table, # pylint: disable=too-many-arguments
                 flood_priority, bypass_priority,
                 use_group_table, groups,
                 combinatorial_port_flood):
        self.flood_table = flood_table
        self.eth_src_table = eth_src_table
        self.bypass_priority = bypass_priority
        self.flood_priority = flood_priority
        self.use_group_table = use_group_table
        self.groups = groups
        self.combinatorial_port_flood = combinatorial_port_flood

    def _require_combinatorial_flood(self, vlan):
        """Must use in_port style flood rules if configured to or hairpinning/LAGs are in use."""
        return self.combinatorial_port_flood or vlan.hairpin_ports() or vlan.lags()

    @staticmethod
    def _vlan_all_ports(vlan, exclude_unicast):
        """Return list of all ports that should be flooded to on a VLAN."""
        return list(vlan.flood_ports(vlan.get_ports(), exclude_unicast))

    @staticmethod
    def _build_flood_local_rule_actions(vlan, exclude_unicast, in_port):
        """Return a list of flood actions to flood packets from a port."""
        exclude_ports = vlan.exclude_same_lag_member_ports(in_port)
        return valve_of.flood_port_outputs(
            vlan.tagged_flood_ports(exclude_unicast),
            vlan.untagged_flood_ports(exclude_unicast),
            in_port=in_port,
            exclude_ports=exclude_ports)

    def initialise_tables(self):
        """initialise the flood table with filtering flows"""
        ofmsgs = []
        ofmsgs.append(self.flood_table.flowdrop(
            self.flood_table.match(
                eth_dst=valve_packet.CISCO_SPANNING_GROUP_ADDRESS),
            priority=self.bypass_priority))
        ofmsgs.append(self.flood_table.flowdrop(
            self.flood_table.match(
                eth_dst=valve_packet.BRIDGE_GROUP_ADDRESS,
                eth_dst_mask=valve_packet.BRIDGE_GROUP_MASK),
            priority=self.bypass_priority))
        return ofmsgs


    def _build_flood_rule_actions(self, vlan, exclude_unicast, in_port):
        return self._build_flood_local_rule_actions(
            vlan, exclude_unicast, in_port)

    def _build_flood_rule_for_vlan(self, vlan, eth_dst, eth_dst_mask, # pylint: disable=too-many-arguments
                                   exclude_unicast, command, flood_priority,
                                   preflood_acts):
        ofmsgs = []
        match = self.flood_table.match(
            vlan=vlan, eth_dst=eth_dst, eth_dst_mask=eth_dst_mask)
        flood_acts = self._build_flood_rule_actions(
            vlan, exclude_unicast, None)
        ofmsgs.append(self.flood_table.flowmod(
            match=match,
            command=command,
            inst=[valve_of.apply_actions(preflood_acts + flood_acts)],
            priority=flood_priority))
        return ofmsgs

    def _build_flood_rule_for_port(self, vlan, eth_dst, eth_dst_mask, # pylint: disable=too-many-arguments
                                   exclude_unicast, command, flood_priority,
                                   port, preflood_acts):
        ofmsgs = []
        match = self.flood_table.match(
            vlan=vlan, in_port=port.number,
            eth_dst=eth_dst, eth_dst_mask=eth_dst_mask)
        flood_acts = self._build_flood_rule_actions(
            vlan, exclude_unicast, port)
        ofmsgs.append(self.flood_table.flowmod(
            match=match,
            command=command,
            inst=[valve_of.apply_actions(preflood_acts + flood_acts)],
            priority=flood_priority))
        return ofmsgs

    def _combinatorial_port_flood(self, vlan, eth_dst, eth_dst_mask, # pylint: disable=too-many-arguments
                                  exclude_unicast, command, flood_priority,
                                  mirror_acts):
        ofmsgs = []
        # TODO: hairpin rules should use higher priority rules so we
        # can use default non-combinatorial rules.
        if self._require_combinatorial_flood(vlan):
            for port in self._vlan_all_ports(vlan, exclude_unicast):
                ofmsgs.extend(self._build_flood_rule_for_port(
                    vlan, eth_dst, eth_dst_mask,
                    exclude_unicast, command, flood_priority,
                    port, mirror_acts))
        return ofmsgs

    def _build_mask_flood_rules(self, vlan, eth_dst, eth_dst_mask, # pylint: disable=too-many-arguments
                                exclude_unicast, command, flood_priority,
                                mirror_acts):
        ofmsgs = self._combinatorial_port_flood(
            vlan, eth_dst, eth_dst_mask,
            exclude_unicast, command, flood_priority, mirror_acts)
        if not ofmsgs:
            ofmsgs.extend(self._build_flood_rule_for_vlan(
                vlan, eth_dst, eth_dst_mask,
                exclude_unicast, command, flood_priority, mirror_acts))
        return ofmsgs

    def _build_multiout_flood_rules(self, vlan, command):
        """Build flooding rules for a VLAN without using groups."""
        flood_priority = self.flood_priority
        mirror_acts = []
        for mirrored_port in vlan.mirrored_ports():
            for act in mirrored_port.mirror_actions():
                mirror_acts.append(act)
        mirror_acts = valve_of.dedupe_output_port_acts(mirror_acts)
        ofmsgs = []
        for unicast_eth_dst, eth_dst, eth_dst_mask in self.FLOOD_DSTS:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            ofmsgs.extend(self._build_mask_flood_rules(
                vlan, eth_dst, eth_dst_mask,
                unicast_eth_dst, command, flood_priority, list(mirror_acts)))
            flood_priority += 1
        return ofmsgs

    @staticmethod
    def _build_group_buckets(vlan, unicast_flood):
        buckets = []
        tagged_flood_ports = vlan.tagged_flood_ports(unicast_flood)
        buckets.extend(valve_of.group_flood_buckets(tagged_flood_ports, False))
        untagged_flood_ports = vlan.untagged_flood_ports(unicast_flood)
        buckets.extend(valve_of.group_flood_buckets(untagged_flood_ports, True))
        return buckets

    def _build_group_flood_rules(self, vlan, modify, command):
        """Build flooding rules for a VLAN using groups."""
        flood_priority = self.flood_priority
        broadcast_group = self.groups.get_entry(
            vlan.vid,
            self._build_group_buckets(vlan, False))
        unicast_group = self.groups.get_entry(
            vlan.vid + valve_of.VLAN_GROUP_OFFSET,
            self._build_group_buckets(vlan, vlan.unicast_flood))
        ofmsgs = []
        if modify:
            ofmsgs.append(broadcast_group.modify())
            ofmsgs.append(unicast_group.modify())
        else:
            ofmsgs.extend(broadcast_group.add())
            ofmsgs.extend(unicast_group.add())
        for unicast_eth_dst, eth_dst, eth_dst_mask in self.FLOOD_DSTS:
            if unicast_eth_dst and not vlan.unicast_flood:
                continue
            group = broadcast_group
            if not eth_dst:
                group = unicast_group
            match = self.flood_table.match(
                vlan=vlan, eth_dst=eth_dst, eth_dst_mask=eth_dst_mask)
            ofmsgs.append(self.flood_table.flowmod(
                match=match,
                command=command,
                inst=[valve_of.apply_actions([valve_of.group_act(group.group_id)])],
                priority=flood_priority))
            flood_priority += 1
        return ofmsgs

    def add_vlan(self, vlan):
        return self.build_flood_rules(vlan)

    def build_flood_rules(self, vlan, modify=False):
        """Add flows to flood packets to unknown destinations on a VLAN."""
        # TODO: group table support is still fairly uncommon, so
        # group tables are currently optional.
        command = valve_of.ofp.OFPFC_ADD
        if modify:
            command = valve_of.ofp.OFPFC_MODIFY_STRICT
        if self.use_group_table and not self._require_combinatorial_flood(vlan):
            return self._build_group_flood_rules(vlan, modify, command)
        return self._build_multiout_flood_rules(vlan, command)

    def update_stack_topo(self, event, dp, port=None): # pylint: disable=unused-argument,invalid-name
        """Update the stack topology. It has nothing to do for non-stacking DPs."""
        pass

    @staticmethod
    def edge_learn_port(_other_valves, pkt_meta):
        """Possibly learn a host on a port.

        Args:
            other_valves (list): All Valves other than this one.
            pkt_meta (PacketMeta): PacketMeta instance for packet received.
        Returns:
            port to learn host on.
        """
        return pkt_meta.port