# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. # Copyright (C) 2014,2015 YAMAMOTO Takashi # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ * references ** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic """ import netaddr from neutron_lib import constants as p_const from oslo_log import log as logging from ryu.lib.packet import ether_types from ryu.lib.packet import icmpv6 from ryu.lib.packet import in_proto from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \ import ovs_bridge LOG = logging.getLogger(__name__) class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge): """openvswitch agent br-int specific logic.""" of_tables = constants.INT_BR_ALL_TABLES def setup_default_table(self): self.setup_canary_table() self.install_goto(dest_table_id=constants.TRANSIENT_TABLE) self.install_normal(table_id=constants.TRANSIENT_TABLE, priority=3) self.install_drop(table_id=constants.ARP_SPOOF_TABLE) self.install_drop(table_id=constants.LOCAL_SWITCHING, priority=constants.OPENFLOW_MAX_PRIORITY, vlan_vid=constants.DEAD_VLAN_TAG) def setup_canary_table(self): self.install_drop(constants.CANARY_TABLE) def check_canary_table(self): try: flows = self.dump_flows(constants.CANARY_TABLE) except RuntimeError: LOG.exception("Failed to communicate with the switch") return constants.OVS_DEAD return constants.OVS_NORMAL if flows else constants.OVS_RESTARTED @staticmethod def _local_vlan_match(_ofp, ofpp, port, vlan_vid): return ofpp.OFPMatch(in_port=port, vlan_vid=vlan_vid) def provision_local_vlan(self, port, lvid, segmentation_id): (_dp, ofp, ofpp) = self._get_dp() if segmentation_id is None: vlan_vid = ofp.OFPVID_NONE actions = [ofpp.OFPActionPushVlan()] else: vlan_vid = segmentation_id | ofp.OFPVID_PRESENT actions = [] match = self._local_vlan_match(ofp, ofpp, port, vlan_vid) actions += [ ofpp.OFPActionSetField(vlan_vid=lvid | ofp.OFPVID_PRESENT), ] instructions = [ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions), ofpp.OFPInstructionGotoTable(table_id=constants.TRANSIENT_TABLE), ] self.install_instructions( instructions=instructions, priority=3, match=match, ) def reclaim_local_vlan(self, port, segmentation_id): (_dp, ofp, ofpp) = self._get_dp() if segmentation_id is None: vlan_vid = ofp.OFPVID_NONE else: vlan_vid = segmentation_id | ofp.OFPVID_PRESENT match = self._local_vlan_match(ofp, ofpp, port, vlan_vid) self.uninstall_flows(match=match) @staticmethod def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac): return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT, eth_dst=dst_mac) @staticmethod def _dvr_to_src_mac_table_id(network_type): if network_type == p_const.TYPE_VLAN: return constants.DVR_TO_SRC_MAC_VLAN else: return constants.DVR_TO_SRC_MAC def install_dvr_to_src_mac(self, network_type, vlan_tag, gateway_mac, dst_mac, dst_port): table_id = self._dvr_to_src_mac_table_id(network_type) (_dp, ofp, ofpp) = self._get_dp() match = self._dvr_to_src_mac_match(ofp, ofpp, vlan_tag=vlan_tag, dst_mac=dst_mac) actions = [ ofpp.OFPActionSetField(eth_src=gateway_mac), ] instructions = [ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions), ofpp.OFPInstructionGotoTable(table_id=constants.TRANSIENT_TABLE), ] self.install_instructions(table_id=table_id, priority=4, match=match, instructions=instructions) actions = [ ofpp.OFPActionPopVlan(), ofpp.OFPActionOutput(dst_port, 0), ] self.install_apply_actions(table_id=constants.TRANSIENT_TABLE, priority=4, match=match, actions=actions) def delete_dvr_to_src_mac(self, network_type, vlan_tag, dst_mac): table_id = self._dvr_to_src_mac_table_id(network_type) (_dp, ofp, ofpp) = self._get_dp() match = self._dvr_to_src_mac_match(ofp, ofpp, vlan_tag=vlan_tag, dst_mac=dst_mac) for table in (table_id, constants.TRANSIENT_TABLE): self.uninstall_flows( strict=True, priority=4, table_id=table, match=match) def add_dvr_mac_vlan(self, mac, port): self.install_goto(table_id=constants.LOCAL_SWITCHING, priority=4, in_port=port, eth_src=mac, dest_table_id=constants.DVR_TO_SRC_MAC_VLAN) def remove_dvr_mac_vlan(self, mac): # REVISIT(yamamoto): match in_port as well? self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, eth_src=mac) def add_dvr_mac_tun(self, mac, port): self.install_goto(table_id=constants.LOCAL_SWITCHING, priority=2, in_port=port, eth_src=mac, dest_table_id=constants.DVR_TO_SRC_MAC) def remove_dvr_mac_tun(self, mac, port): self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, in_port=port, eth_src=mac) @staticmethod def _arp_reply_match(ofp, ofpp, port): return ofpp.OFPMatch(in_port=port, eth_type=ether_types.ETH_TYPE_ARP) @staticmethod def _icmpv6_reply_match(ofp, ofpp, port): return ofpp.OFPMatch(in_port=port, eth_type=ether_types.ETH_TYPE_IPV6, ip_proto=in_proto.IPPROTO_ICMPV6, icmpv6_type=icmpv6.ND_NEIGHBOR_ADVERT) def install_icmpv6_na_spoofing_protection(self, port, ip_addresses): # Allow neighbor advertisements as long as they match addresses # that actually belong to the port. for ip in ip_addresses: masked_ip = self._cidr_to_ryu(ip) self.install_goto( table_id=constants.ARP_SPOOF_TABLE, priority=2, eth_type=ether_types.ETH_TYPE_IPV6, ip_proto=in_proto.IPPROTO_ICMPV6, icmpv6_type=icmpv6.ND_NEIGHBOR_ADVERT, ipv6_nd_target=masked_ip, in_port=port, dest_table_id=constants.TRANSIENT_TABLE) # Now that the rules are ready, direct icmpv6 neighbor advertisement # traffic from the port into the anti-spoof table. (_dp, ofp, ofpp) = self._get_dp() match = self._icmpv6_reply_match(ofp, ofpp, port=port) self.install_goto(table_id=constants.LOCAL_SWITCHING, priority=10, match=match, dest_table_id=constants.ARP_SPOOF_TABLE) def set_allowed_macs_for_port(self, port, mac_addresses=None, allow_all=False): if allow_all: self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, in_port=port) self.uninstall_flows(table_id=constants.MAC_SPOOF_TABLE, in_port=port) return mac_addresses = mac_addresses or [] for address in mac_addresses: self.install_goto( table_id=constants.MAC_SPOOF_TABLE, priority=2, eth_src=address, in_port=port, dest_table_id=constants.TRANSIENT_TABLE) # normalize so we can see if macs are the same mac_addresses = {netaddr.EUI(mac) for mac in mac_addresses} flows = self.dump_flows(constants.MAC_SPOOF_TABLE) for flow in flows: matches = dict(flow.match.items()) if matches.get('in_port') != port: continue if not matches.get('eth_src'): continue flow_mac = matches['eth_src'] if netaddr.EUI(flow_mac) not in mac_addresses: self.uninstall_flows(table_id=constants.MAC_SPOOF_TABLE, in_port=port, eth_src=flow_mac) self.install_goto(table_id=constants.LOCAL_SWITCHING, priority=9, in_port=port, dest_table_id=constants.MAC_SPOOF_TABLE) def install_arp_spoofing_protection(self, port, ip_addresses): # allow ARP replies as long as they match addresses that actually # belong to the port. for ip in ip_addresses: masked_ip = self._cidr_to_ryu(ip) self.install_goto(table_id=constants.ARP_SPOOF_TABLE, priority=2, eth_type=ether_types.ETH_TYPE_ARP, arp_spa=masked_ip, in_port=port, dest_table_id=constants.MAC_SPOOF_TABLE) # Now that the rules are ready, direct ARP traffic from the port into # the anti-spoof table. # This strategy fails gracefully because OVS versions that can't match # on ARP headers will just process traffic normally. (_dp, ofp, ofpp) = self._get_dp() match = self._arp_reply_match(ofp, ofpp, port=port) self.install_goto(table_id=constants.LOCAL_SWITCHING, priority=10, match=match, dest_table_id=constants.ARP_SPOOF_TABLE) def delete_arp_spoofing_protection(self, port): (_dp, ofp, ofpp) = self._get_dp() match = self._arp_reply_match(ofp, ofpp, port=port) self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, match=match) match = self._icmpv6_reply_match(ofp, ofpp, port=port) self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, match=match) self.delete_arp_spoofing_allow_rules(port) def delete_arp_spoofing_allow_rules(self, port): self.uninstall_flows(table_id=constants.ARP_SPOOF_TABLE, in_port=port)