Merge "OVS: Add mac spoofing filtering to flows" into stable/mitaka
This commit is contained in:
commit
0c4218202c
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue