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 # Table for ARP poison/spoofing prevention rules
ARP_SPOOF_TABLE = 24 ARP_SPOOF_TABLE = 24
# Table for MAC spoof filtering
MAC_SPOOF_TABLE = 25
# Tables used for ovs firewall # Tables used for ovs firewall
BASE_EGRESS_TABLE = 71 BASE_EGRESS_TABLE = 71
RULES_EGRESS_TABLE = 72 RULES_EGRESS_TABLE = 72

View File

@ -19,6 +19,8 @@
** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic ** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
""" """
import netaddr
from oslo_log import log as logging from oslo_log import log as logging
from ryu.lib.packet import ether_types from ryu.lib.packet import ether_types
from ryu.lib.packet import icmpv6 from ryu.lib.packet import icmpv6
@ -174,16 +176,45 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
match=match, match=match,
dest_table_id=constants.ARP_SPOOF_TABLE) 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): def install_arp_spoofing_protection(self, port, ip_addresses):
# allow ARP replies as long as they match addresses that actually # allow ARP replies as long as they match addresses that actually
# belong to the port. # belong to the port.
for ip in ip_addresses: for ip in ip_addresses:
masked_ip = self._cidr_to_ryu(ip) masked_ip = self._cidr_to_ryu(ip)
self.install_normal(table_id=constants.ARP_SPOOF_TABLE, self.install_goto(table_id=constants.ARP_SPOOF_TABLE,
priority=2, priority=2,
eth_type=ether_types.ETH_TYPE_ARP, eth_type=ether_types.ETH_TYPE_ARP,
arp_spa=masked_ip, arp_spa=masked_ip,
in_port=port) in_port=port,
dest_table_id=constants.MAC_SPOOF_TABLE)
# Now that the rules are ready, direct ARP traffic from the port into # Now that the rules are ready, direct ARP traffic from the port into
# the anti-spoof table. # the anti-spoof table.

View File

@ -18,6 +18,9 @@
* references * references
** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic ** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
""" """
import netaddr
from neutron.common import constants as const from neutron.common import constants as const
from neutron.plugins.common import constants as p_const from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants 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, icmp_type=const.ICMPV6_TYPE_NA, in_port=port,
actions=("resubmit(,%s)" % constants.ARP_SPOOF_TABLE)) 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): def install_arp_spoofing_protection(self, port, ip_addresses):
# allow ARPs as long as they match addresses that actually # allow ARPs as long as they match addresses that actually
# belong to the port. # belong to the port.
for ip in ip_addresses: for ip in ip_addresses:
self.install_normal( self.add_flow(
table_id=constants.ARP_SPOOF_TABLE, priority=2, table=constants.ARP_SPOOF_TABLE, priority=2,
proto='arp', arp_spa=ip, in_port=port) 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 # Now that the rules are ready, direct ARP traffic from the port into
# the anti-spoof table. # 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 " LOG.info(_LI("Skipping ARP spoofing rules for port '%s' because "
"it has port security disabled"), vif.port_name) "it has port security disabled"), vif.port_name)
bridge.delete_arp_spoofing_protection(port=vif.ofport) bridge.delete_arp_spoofing_protection(port=vif.ofport)
bridge.set_allowed_macs_for_port(port=vif.ofport, allow_all=True)
return return
if port_details['device_owner'].startswith( if port_details['device_owner'].startswith(
n_const.DEVICE_OWNER_NETWORK_PREFIX): n_const.DEVICE_OWNER_NETWORK_PREFIX):
LOG.debug("Skipping ARP spoofing rules for network owned port " LOG.debug("Skipping ARP spoofing rules for network owned port "
"'%s'.", vif.port_name) "'%s'.", vif.port_name)
bridge.delete_arp_spoofing_protection(port=vif.ofport) bridge.delete_arp_spoofing_protection(port=vif.ofport)
bridge.set_allowed_macs_for_port(port=vif.ofport, allow_all=True)
return return
# clear any previous flows related to this port in our ARP table # clear any previous flows related to this port in our ARP table
bridge.delete_arp_spoofing_allow_rules(port=vif.ofport) 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'] for p in port_details['allowed_address_pairs']
if p.get('mac_address')} if p.get('mac_address')}
bridge.set_allowed_macs_for_port(vif.ofport, mac_addresses)
ipv6_addresses = {ip for ip in addresses ipv6_addresses = {ip for ip in addresses
if netaddr.IPNetwork(ip).version == 6} if netaddr.IPNetwork(ip).version == 6}
# Allow neighbor advertisements for LLA address. # Allow neighbor advertisements for LLA address.
@ -1181,6 +1184,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
ofports_deleted = set(previous.values()) - set(current.values()) ofports_deleted = set(previous.values()) - set(current.values())
for ofport in ofports_deleted: for ofport in ofports_deleted:
self.int_br.delete_arp_spoofing_protection(port=ofport) 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 # store map for next iteration
self.vifname_to_ofport_map = current 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) self.dst_p.addr.add('%s/24' % self.dst_addr)
net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2) 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): def test_arp_spoof_doesnt_block_ipv6(self):
self.src_addr = '2000::1' self.src_addr = '2000::1'
self.dst_addr = '2000::2' self.dst_addr = '2000::2'
@ -259,7 +270,7 @@ class ARPSpoofTestCase(OVSAgentTestBase):
net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2) net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2)
def _setup_arp_spoof_for_port(self, port, addrs, psec=True, def _setup_arp_spoof_for_port(self, port, addrs, psec=True,
device_owner='nobody'): device_owner='nobody', mac=None):
vif = next( vif = next(
vif for vif in self.br.get_vif_ports() if vif.port_name == port) vif for vif in self.br.get_vif_ports() if vif.port_name == port)
ip_addr = addrs.pop() ip_addr = addrs.pop()
@ -268,6 +279,8 @@ class ARPSpoofTestCase(OVSAgentTestBase):
'device_owner': device_owner, 'device_owner': device_owner,
'allowed_address_pairs': [ 'allowed_address_pairs': [
dict(ip_address=ip) for ip in addrs]} dict(ip_address=ip) for ip in addrs]}
if mac:
vif.vif_mac = mac
ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection( ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection(
self.br_int, vif, details) self.br_int, vif, details)

View File

@ -347,9 +347,7 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
call._send_msg(ofpp.OFPFlowMod(dp, call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp, cookie=self.stamp,
instructions=[ instructions=[
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ ofpp.OFPInstructionGotoTable(table_id=25),
ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
]),
], ],
match=ofpp.OFPMatch( match=ofpp.OFPMatch(
eth_type=self.ether_types.ETH_TYPE_ARP, 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, call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp, cookie=self.stamp,
instructions=[ instructions=[
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ ofpp.OFPInstructionGotoTable(table_id=25),
ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
]),
], ],
match=ofpp.OFPMatch( match=ofpp.OFPMatch(
eth_type=self.ether_types.ETH_TYPE_ARP, 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'] ip_addresses = ['192.0.2.1', '192.0.2.2/32']
self.br.install_arp_spoofing_protection(port, ip_addresses) self.br.install_arp_spoofing_protection(port, ip_addresses)
expected = [ expected = [
call.add_flow(proto='arp', actions='normal', call.add_flow(proto='arp', actions='resubmit(,25)',
arp_spa='192.0.2.1', arp_spa='192.0.2.1',
priority=2, table=24, in_port=8888), 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', arp_spa='192.0.2.2/32',
priority=2, table=24, in_port=8888), priority=2, table=24, in_port=8888),
call.add_flow(priority=10, table=0, in_port=8888, call.add_flow(priority=10, table=0, in_port=8888,