Merge "OVS: Add mac spoofing filtering to flows" into stable/mitaka

This commit is contained in:
Jenkins 2016-05-10 02:03:59 +00:00 committed by Gerrit Code Review
commit 0c4218202c
7 changed files with 94 additions and 17 deletions

View File

@ -50,6 +50,9 @@ CANARY_TABLE = 23
# Table for ARP poison/spoofing prevention rules
ARP_SPOOF_TABLE = 24
# Table for MAC spoof filtering
MAC_SPOOF_TABLE = 25
# Tables used for ovs firewall
BASE_EGRESS_TABLE = 71
RULES_EGRESS_TABLE = 72

View File

@ -19,6 +19,8 @@
** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
"""
import netaddr
from oslo_log import log as logging
from ryu.lib.packet import ether_types
from ryu.lib.packet import icmpv6
@ -174,16 +176,45 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
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.delete_flows(table_id=constants.LOCAL_SWITCHING, in_port=port)
self.delete_flows(table_id=constants.MAC_SPOOF_TABLE, in_port=port)
return
mac_addresses = mac_addresses or []
for address in mac_addresses:
self.install_normal(
table_id=constants.MAC_SPOOF_TABLE, priority=2,
eth_src=address, in_port=port)
# 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.delete_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_normal(table_id=constants.ARP_SPOOF_TABLE,
priority=2,
eth_type=ether_types.ETH_TYPE_ARP,
arp_spa=masked_ip,
in_port=port)
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.

View File

@ -18,6 +18,9 @@
* references
** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
"""
import netaddr
from neutron.common import constants as const
from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
@ -128,13 +131,40 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
icmp_type=const.ICMPV6_TYPE_NA, in_port=port,
actions=("resubmit(,%s)" % constants.ARP_SPOOF_TABLE))
def set_allowed_macs_for_port(self, port, mac_addresses=None,
allow_all=False):
if allow_all:
self.delete_flows(table_id=constants.LOCAL_SWITCHING, in_port=port)
self.delete_flows(table_id=constants.MAC_SPOOF_TABLE, in_port=port)
return
mac_addresses = mac_addresses or []
for address in mac_addresses:
self.install_normal(
table_id=constants.MAC_SPOOF_TABLE, priority=2,
eth_src=address, in_port=port)
# normalize so we can see if macs are the same
mac_addresses = {netaddr.EUI(mac) for mac in mac_addresses}
flows = self.dump_flows_for(table=constants.MAC_SPOOF_TABLE,
in_port=port).splitlines()
for flow in flows:
if 'dl_src' not in flow:
continue
flow_mac = flow.split('dl_src=')[1].split(' ')[0].split(',')[0]
if netaddr.EUI(flow_mac) not in mac_addresses:
self.delete_flows(table_id=constants.MAC_SPOOF_TABLE,
in_port=port, eth_src=flow_mac)
self.add_flow(table=constants.LOCAL_SWITCHING,
priority=9, in_port=port,
actions=("resubmit(,%s)" % constants.MAC_SPOOF_TABLE))
def install_arp_spoofing_protection(self, port, ip_addresses):
# allow ARPs as long as they match addresses that actually
# belong to the port.
for ip in ip_addresses:
self.install_normal(
table_id=constants.ARP_SPOOF_TABLE, priority=2,
proto='arp', arp_spa=ip, in_port=port)
self.add_flow(
table=constants.ARP_SPOOF_TABLE, priority=2,
proto='arp', arp_spa=ip, in_port=port,
actions=("resubmit(,%s)" % constants.MAC_SPOOF_TABLE))
# Now that the rules are ready, direct ARP traffic from the port into
# the anti-spoof table.

View File

@ -887,12 +887,14 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
LOG.info(_LI("Skipping ARP spoofing rules for port '%s' because "
"it has port security disabled"), vif.port_name)
bridge.delete_arp_spoofing_protection(port=vif.ofport)
bridge.set_allowed_macs_for_port(port=vif.ofport, allow_all=True)
return
if port_details['device_owner'].startswith(
n_const.DEVICE_OWNER_NETWORK_PREFIX):
LOG.debug("Skipping ARP spoofing rules for network owned port "
"'%s'.", vif.port_name)
bridge.delete_arp_spoofing_protection(port=vif.ofport)
bridge.set_allowed_macs_for_port(port=vif.ofport, allow_all=True)
return
# clear any previous flows related to this port in our ARP table
bridge.delete_arp_spoofing_allow_rules(port=vif.ofport)
@ -906,6 +908,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
for p in port_details['allowed_address_pairs']
if p.get('mac_address')}
bridge.set_allowed_macs_for_port(vif.ofport, mac_addresses)
ipv6_addresses = {ip for ip in addresses
if netaddr.IPNetwork(ip).version == 6}
# Allow neighbor advertisements for LLA address.
@ -1181,6 +1184,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
ofports_deleted = set(previous.values()) - set(current.values())
for ofport in ofports_deleted:
self.int_br.delete_arp_spoofing_protection(port=ofport)
self.int_br.set_allowed_macs_for_port(port=ofport, allow_all=True)
# store map for next iteration
self.vifname_to_ofport_map = current

View File

@ -161,6 +161,17 @@ class ARPSpoofTestCase(OVSAgentTestBase):
self.dst_p.addr.add('%s/24' % self.dst_addr)
net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2)
def test_mac_spoof_blocks_wrong_mac(self):
self._setup_arp_spoof_for_port(self.src_p.name, [self.src_addr])
self._setup_arp_spoof_for_port(self.dst_p.name, [self.dst_addr])
self.src_p.addr.add('%s/24' % self.src_addr)
self.dst_p.addr.add('%s/24' % self.dst_addr)
net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2)
# changing the allowed mac should stop the port from working
self._setup_arp_spoof_for_port(self.src_p.name, [self.src_addr],
mac='00:11:22:33:44:55')
net_helpers.assert_no_ping(self.src_namespace, self.dst_addr, count=2)
def test_arp_spoof_doesnt_block_ipv6(self):
self.src_addr = '2000::1'
self.dst_addr = '2000::2'
@ -259,7 +270,7 @@ class ARPSpoofTestCase(OVSAgentTestBase):
net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2)
def _setup_arp_spoof_for_port(self, port, addrs, psec=True,
device_owner='nobody'):
device_owner='nobody', mac=None):
vif = next(
vif for vif in self.br.get_vif_ports() if vif.port_name == port)
ip_addr = addrs.pop()
@ -268,6 +279,8 @@ class ARPSpoofTestCase(OVSAgentTestBase):
'device_owner': device_owner,
'allowed_address_pairs': [
dict(ip_address=ip) for ip in addrs]}
if mac:
vif.vif_mac = mac
ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection(
self.br_int, vif, details)

View File

@ -347,9 +347,7 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
]),
ofpp.OFPInstructionGotoTable(table_id=25),
],
match=ofpp.OFPMatch(
eth_type=self.ether_types.ETH_TYPE_ARP,
@ -361,9 +359,7 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
]),
ofpp.OFPInstructionGotoTable(table_id=25),
],
match=ofpp.OFPMatch(
eth_type=self.ether_types.ETH_TYPE_ARP,

View File

@ -215,10 +215,10 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
ip_addresses = ['192.0.2.1', '192.0.2.2/32']
self.br.install_arp_spoofing_protection(port, ip_addresses)
expected = [
call.add_flow(proto='arp', actions='normal',
call.add_flow(proto='arp', actions='resubmit(,25)',
arp_spa='192.0.2.1',
priority=2, table=24, in_port=8888),
call.add_flow(proto='arp', actions='normal',
call.add_flow(proto='arp', actions='resubmit(,25)',
arp_spa='192.0.2.2/32',
priority=2, table=24, in_port=8888),
call.add_flow(priority=10, table=0, in_port=8888,