Add Local IP L2 extension flows

- setup local ARP responder
- setup local ip translation flows (nat via conntrack)
- setup local gARP blocker to prevent undesired Local IP ARP updates
  from other nodes (including real IP address owner)

2 new OF tables added for br-int:

- LOCAL_EGRESS_TABLE - to save local ports vlans
- LOCAL_IP_TABLE - for local ip handling

Partial-Bug: #1930200
Change-Id: I49923958d1d602e3af4e02fadbec1b17798c49c8
This commit is contained in:
Oleg Bondarev 2021-10-28 11:43:27 +03:00
parent 1222962767
commit b51d6958f3
10 changed files with 566 additions and 85 deletions

View File

@ -16,16 +16,21 @@
import collections
import sys
import netaddr
from neutron_lib.agent import l2_extension
from neutron_lib.callbacks import events as lib_events
from neutron_lib.callbacks import registry as lib_registry
from neutron_lib import context as lib_ctx
from os_ken.lib.packet import ether_types
from oslo_config import cfg
from oslo_log import log as logging
from neutron.api.rpc.callbacks.consumer import registry
from neutron.api.rpc.callbacks import events
from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import resources_rpc
from neutron.plugins.ml2.drivers.openvswitch.agent import (
ovs_neutron_agent as ovs_agent)
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
constants as ovs_constants)
@ -41,9 +46,15 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
'currently uses %(driver_type)s',
{'driver_type': driver_type})
sys.exit(1)
if (cfg.CONF.SECURITYGROUP.enable_security_group and
cfg.CONF.SECURITYGROUP.firewall_driver == 'openvswitch'):
LOG.error('Local IP extension is not supported together with '
'openvswitch firewall')
sys.exit(1)
self.resource_rpc = resources_rpc.ResourcesPullRpcApi()
self._register_rpc_consumers(connection)
self.int_br = self.agent_api.request_int_br()
self.local_ip_updates = {
'added': collections.defaultdict(dict),
@ -61,8 +72,8 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
port_id = assoc.fixed_port_id
lip_id = assoc.local_ip_id
self.local_ip_updates['added'][port_id][lip_id] = assoc
# No need to notify "port updated" here as on restart agent
# handles all ports anyway
# Notify agent about port update to handle Local IP flows
self._notify_port_updated(context, port_id)
def consume_api(self, agent_api):
"""Allows an extension to gain access to resources internal to the
@ -115,14 +126,25 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
"""
port_id = port['port_id']
local_ip_updates = self._pop_local_ip_updates_for_port(port_id)
# if port doesn't yet have local vlan - issue port updated
# notification to handle it on next agent loop, when port
# should have it
if ((local_ip_updates['added'] or local_ip_updates['deleted']) and
not port.get('local_vlan')):
LOG.debug("Port %s has no local VLAN assigned yet. "
"Skipping Local IP handling till next iteration.")
self._notify_port_updated(context, port['port_id'])
return
for assoc in local_ip_updates['added'].values():
LOG.info("Local IP added for port %s: %s",
port_id, assoc.local_ip)
# TBD
self.add_local_ip_flows(port, assoc)
for assoc in local_ip_updates['deleted'].values():
LOG.info("Local IP deleted from port %s: %s",
port_id, assoc.local_ip)
# TBD
self.delete_local_ip_flows(port, assoc)
def _pop_local_ip_updates_for_port(self, port_id):
return {
@ -130,6 +152,96 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
'deleted': self.local_ip_updates['deleted'].pop(port_id, {})
}
def add_local_ip_flows(self, port, assoc):
local_ip_address = str(assoc.local_ip.local_ip_address)
dest_mac = str(netaddr.EUI(port['mac_address'],
dialect=ovs_agent._mac_mydialect))
dest_ip = str(assoc.fixed_ip)
vlan = port['local_vlan']
self.setup_local_ip_translation(
vlan=vlan, local_ip=local_ip_address,
dest_ip=dest_ip, mac=port['mac_address'])
self.int_br.install_arp_responder(
vlan=vlan, ip=local_ip_address, mac=dest_mac,
table_id=ovs_constants.LOCAL_IP_TABLE)
self.int_br.install_garp_blocker(
vlan=vlan, ip=local_ip_address)
self.int_br.install_garp_blocker_exception(
vlan=vlan, ip=local_ip_address, except_ip=dest_ip)
def delete_local_ip_flows(self, port, assoc):
local_ip_address = str(assoc.local_ip.local_ip_address)
dest_ip = str(assoc.fixed_ip)
vlan = port['local_vlan']
self.delete_local_ip_translation(
vlan=vlan, local_ip=local_ip_address,
dest_ip=dest_ip, mac=port['mac_address'])
self.int_br.delete_arp_responder(
vlan=vlan, ip=local_ip_address,
table_id=ovs_constants.LOCAL_IP_TABLE)
self.int_br.delete_garp_blocker(
vlan=vlan, ip=local_ip_address)
self.int_br.delete_garp_blocker_exception(
vlan=vlan, ip=local_ip_address, except_ip=dest_ip)
def delete_port(self, context, port):
self.local_ip_updates['added'].pop(port['port_id'], None)
self.local_ip_updates['deleted'].pop(port['port_id'], None)
def setup_local_ip_translation(self, vlan, local_ip, dest_ip, mac):
self.int_br.add_flow(
table=ovs_constants.LOCAL_IP_TABLE,
priority=10,
nw_dst=local_ip,
reg6=vlan,
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
actions='mod_dl_dst:{:s},'
'ct(commit,table={:d},zone={:d},nat(dst={:s}))'.format(
mac, ovs_constants.TRANSIENT_TABLE, vlan, dest_ip)
)
# avoid NAT to self and let VM with local IP access "true" IP owner
self.int_br.add_flow(
table=ovs_constants.LOCAL_IP_TABLE,
priority=11,
nw_src=dest_ip,
nw_dst=local_ip,
reg6=vlan,
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
actions='resubmit(,{:d})'.format(ovs_constants.TRANSIENT_TABLE)
)
self.int_br.add_flow(
table=ovs_constants.LOCAL_IP_TABLE,
priority=10,
dl_src=mac,
nw_src=dest_ip,
reg6=vlan,
ct_state="-trk",
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
actions='ct(table={:d},zone={:d},nat'.format(
ovs_constants.TRANSIENT_TABLE, vlan)
)
def delete_local_ip_translation(self, vlan, local_ip, dest_ip, mac):
self.int_br.uninstall_flows(
table_id=ovs_constants.LOCAL_IP_TABLE,
priority=10,
ipv4_dst=local_ip,
reg6=vlan,
eth_type=ether_types.ETH_TYPE_IP
)
self.int_br.uninstall_flows(
table_id=ovs_constants.LOCAL_IP_TABLE,
priority=11,
ipv4_src=dest_ip,
ipv4_dst=local_ip,
reg6=vlan,
eth_type=ether_types.ETH_TYPE_IP
)
self.int_br.uninstall_flows(
table_id=ovs_constants.LOCAL_IP_TABLE,
priority=10,
eth_src=mac,
ipv4_src=dest_ip,
reg6=vlan,
eth_type=ether_types.ETH_TYPE_IP
)

View File

@ -57,6 +57,9 @@ ARP_SPOOF_TABLE = 24
# Table for MAC spoof filtering
MAC_SPOOF_TABLE = 25
LOCAL_EGRESS_TABLE = 30
LOCAL_IP_TABLE = 31
# Table to decide whether further filtering is needed
TRANSIENT_TABLE = 60
LOCAL_MAC_DIRECT = 61
@ -95,6 +98,8 @@ INT_BR_ALL_TABLES = (
ARP_SPOOF_TABLE,
MAC_SPOOF_TABLE,
LOCAL_MAC_DIRECT,
LOCAL_EGRESS_TABLE,
LOCAL_IP_TABLE,
TRANSIENT_TABLE,
TRANSIENT_EGRESS_TABLE,
BASE_EGRESS_TABLE,

View File

@ -65,6 +65,12 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
self.install_normal(table_id=constants.TRANSIENT_EGRESS_TABLE,
priority=3)
# Local IP defaults
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE,
table_id=constants.LOCAL_EGRESS_TABLE)
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE,
table_id=constants.LOCAL_IP_TABLE)
def init_dhcp(self, enable_openflow_dhcp=False, enable_dhcpv6=False):
if not enable_openflow_dhcp:
return
@ -381,7 +387,7 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
self.install_goto(
table_id=constants.MAC_SPOOF_TABLE, priority=2,
eth_src=address, in_port=port,
dest_table_id=constants.TRANSIENT_TABLE)
dest_table_id=constants.LOCAL_EGRESS_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)
@ -454,3 +460,68 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
self.install_instructions(instructions, table_id=0,
priority=65535, in_port=port, reg2=0,
eth_type=0x86DD)
def setup_local_egress_flows(self, in_port, vlan):
# Setting priority to 8 to give advantage to ARP/MAC spoofing rules
self.install_goto(table_id=constants.LOCAL_SWITCHING,
priority=8,
in_port=in_port,
dest_table_id=constants.LOCAL_EGRESS_TABLE)
(dp, ofp, ofpp) = self._get_dp()
actions = [ofpp.OFPActionSetField(reg6=vlan),
ofpp.NXActionResubmitTable(
in_port=in_port, table_id=constants.LOCAL_IP_TABLE)]
instructions = [
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
]
self.install_instructions(instructions,
table_id=constants.LOCAL_EGRESS_TABLE,
priority=10, in_port=in_port)
@staticmethod
def _arp_responder_match(ofp, ofpp, vlan, ip):
return ofpp.OFPMatch(reg6=vlan,
eth_type=ether_types.ETH_TYPE_ARP,
arp_tpa=ip)
def _garp_blocker_match(self, vlan, ip):
(dp, ofp, ofpp) = self._get_dp()
return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
eth_type=ether_types.ETH_TYPE_ARP,
arp_spa=ip)
def install_garp_blocker(self, vlan, ip,
table_id=constants.LOCAL_SWITCHING):
match = self._garp_blocker_match(vlan, ip)
self.install_drop(table_id=table_id,
priority=10,
match=match)
def delete_garp_blocker(self, vlan, ip,
table_id=constants.LOCAL_SWITCHING):
match = self._garp_blocker_match(vlan, ip)
self.uninstall_flows(table_id=table_id,
priority=10,
match=match)
def _garp_blocker_exception_match(self, vlan, ip, except_ip):
(dp, ofp, ofpp) = self._get_dp()
return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
eth_type=ether_types.ETH_TYPE_ARP,
arp_spa=ip,
arp_tpa=except_ip)
def install_garp_blocker_exception(self, vlan, ip, except_ip,
table_id=constants.LOCAL_SWITCHING):
match = self._garp_blocker_exception_match(vlan, ip, except_ip)
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE,
table_id=table_id,
priority=11,
match=match)
def delete_garp_blocker_exception(self, vlan, ip, except_ip,
table_id=constants.LOCAL_SWITCHING):
match = self._garp_blocker_exception_match(vlan, ip, except_ip)
self.uninstall_flows(table_id=table_id,
priority=11,
match=match)

View File

@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from os_ken.lib.packet import arp
from os_ken.lib.packet import ether_types
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
@ -204,28 +203,6 @@ class OVSTunnelBridge(ovs_bridge.OVSAgentBridge,
eth_type=ether_types.ETH_TYPE_ARP,
arp_tpa=ip)
def install_arp_responder(self, vlan, ip, mac):
(dp, ofp, ofpp) = self._get_dp()
match = self._arp_responder_match(ofp, ofpp, vlan, ip)
actions = [ofpp.OFPActionSetField(arp_op=arp.ARP_REPLY),
ofpp.NXActionRegMove(src_field='arp_sha',
dst_field='arp_tha',
n_bits=48),
ofpp.NXActionRegMove(src_field='arp_spa',
dst_field='arp_tpa',
n_bits=32),
ofpp.OFPActionSetField(arp_sha=mac),
ofpp.OFPActionSetField(arp_spa=ip),
ofpp.NXActionRegMove(src_field='eth_src',
dst_field='eth_dst',
n_bits=48),
ofpp.OFPActionSetField(eth_src=mac),
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0)]
self.install_apply_actions(table_id=constants.ARP_RESPONDER,
priority=1,
match=match,
actions=actions)
def delete_arp_responder(self, vlan, ip):
(_dp, ofp, ofpp) = self._get_dp()
if ip is None:

View File

@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from os_ken.lib.packet import arp
from os_ken.lib.packet import ether_types
from oslo_log import log as logging
from oslo_utils import excutils
@ -96,3 +98,39 @@ class OVSAgentBridge(ofswitch.OpenFlowSwitchMixin,
def drop_port(self, in_port):
self.install_drop(priority=2, in_port=in_port)
@staticmethod
def _arp_responder_match(ofp, ofpp, vlan, ip):
return ofpp.OFPMatch(eth_type=ether_types.ETH_TYPE_ARP,
arp_tpa=ip)
def install_arp_responder(self, vlan, ip, mac,
table_id=ovs_consts.ARP_RESPONDER):
(dp, ofp, ofpp) = self._get_dp()
match = self._arp_responder_match(ofp, ofpp, vlan, ip)
actions = [ofpp.OFPActionSetField(arp_op=arp.ARP_REPLY),
ofpp.NXActionRegMove(src_field='arp_sha',
dst_field='arp_tha',
n_bits=48),
ofpp.NXActionRegMove(src_field='arp_spa',
dst_field='arp_tpa',
n_bits=32),
ofpp.OFPActionSetField(arp_sha=mac),
ofpp.OFPActionSetField(arp_spa=ip),
ofpp.NXActionRegMove(src_field='eth_src',
dst_field='eth_dst',
n_bits=48),
ofpp.OFPActionSetField(eth_src=mac),
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0)]
self.install_apply_actions(table_id=table_id,
priority=1,
match=match,
actions=actions)
def delete_arp_responder(self, vlan, ip,
table_id=ovs_consts.ARP_RESPONDER):
(_dp, ofp, ofpp) = self._get_dp()
match = self._arp_responder_match(ofp, ofpp, vlan, ip)
self.uninstall_flows(table_id=table_id,
priority=1,
match=match)

View File

@ -159,6 +159,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
ovs_conf = self.conf.OVS
self.enable_openflow_dhcp = 'dhcp' in self.ext_manager.names()
self.enable_local_ips = 'local_ip' in self.ext_manager.names()
self.fullsync = False
# init bridge classes with configured datapath type.
@ -702,6 +703,12 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
lvm = self.vlan_manager.get(network_id)
return lvm.vlan
def _get_net_local_vlan_or_none(self, net_id):
try:
return self.vlan_manager.get(net_id).vlan
except vlanmanager.MappingNotFound:
return None
def _deferred_delete_direct_flows(self, ports):
if not self.direct_for_non_openflow_firewall:
return
@ -1967,6 +1974,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
LOG.info("Port %(device)s updated. Details: %(details)s",
{'device': device, 'details': details})
details['vif_port'] = port
details['local_vlan'] = self._get_net_local_vlan_or_none(
details['network_id'])
need_binding = self.treat_vif_port(port, details['port_id'],
details['network_id'],
details['network_type'],
@ -2182,10 +2191,21 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
dest_table_id=constants.TRANSIENT_EGRESS_TABLE)
def process_install_ports_egress_flows(self, ports):
if self.direct_for_non_openflow_firewall:
with self.int_br.deferred(full_ordered=True,
use_bundle=True) as int_br:
for port in ports:
with self.int_br.deferred(full_ordered=True,
use_bundle=True) as int_br:
for port in ports:
if self.enable_local_ips and port['device_owner'].startswith(
n_const.DEVICE_OWNER_COMPUTE_PREFIX):
try:
lvm = self.vlan_manager.get(port['network_id'])
self.int_br.setup_local_egress_flows(
port['vif_port'].ofport, lvm.vlan)
except Exception as err:
LOG.warning("Failed to install egress flows "
"for port %s, error: %s",
port['port_id'], err)
if self.direct_for_non_openflow_firewall:
try:
self.install_accepted_egress_direct_flow(port, int_br)
except Exception as err:

View File

@ -18,6 +18,7 @@ from unittest import mock
from neutron_lib.callbacks import events as lib_events
from neutron_lib.callbacks import registry as lib_registry
from neutron_lib import context
from os_ken.lib.packet import ether_types
from oslo_utils import uuidutils
from neutron.agent.l2.extensions import local_ip as local_ip_ext
@ -36,25 +37,25 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
self.context = context.get_admin_context_without_session()
self.local_ip_ext = local_ip_ext.LocalIPAgentExtension()
self.int_br = mock.Mock()
self.tun_br = mock.Mock()
self.plugin_rpc = mock.Mock()
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
self.int_br,
self.tun_br,
int_br=mock.Mock(),
tun_br=mock.Mock(),
phys_brs=None,
plugin_rpc=self.plugin_rpc)
self.local_ip_ext.consume_api(self.agent_api)
with mock.patch.object(
self.local_ip_ext, '_pull_all_local_ip_associations'):
self.local_ip_ext.initialize(mock.Mock(), 'ovs')
self.int_br = self.local_ip_ext.int_br
def _generate_test_lip_associations(self, count=2):
return [lip_obj.LocalIPAssociation(
fixed_port_id=uuidutils.generate_uuid(),
local_ip_id=uuidutils.generate_uuid(),
local_ip=lip_obj.LocalIP()) for _ in range(count)
]
fixed_ip='10.0.0.10',
local_ip=lip_obj.LocalIP(
local_ip_address='172.16.0.10')) for _ in range(count)]
def test_pulling_lip_associations_on_init(self):
res_rpc = mock.Mock()
@ -124,16 +125,22 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
def test_handle_port(self):
lip_assocs = self.test_handle_updated_notification()
for assoc in lip_assocs:
port = {'port_id': assoc.fixed_port_id}
self.local_ip_ext.handle_port(self.context, port)
self.assertEqual({}, self.local_ip_ext.local_ip_updates[
'added'][assoc.fixed_port_id])
with mock.patch.object(self.local_ip_ext,
'add_local_ip_flows') as add_lip_flows:
port = {'port_id': assoc.fixed_port_id, 'local_vlan': 1}
self.local_ip_ext.handle_port(self.context, port)
self.assertEqual({}, self.local_ip_ext.local_ip_updates[
'added'][assoc.fixed_port_id])
add_lip_flows.assert_called_once_with(port, assoc)
self.test_handle_deleted_notification(lip_assocs)
for assoc in lip_assocs:
port = {'port_id': assoc.fixed_port_id}
self.local_ip_ext.handle_port(self.context, port)
self.assertEqual({}, self.local_ip_ext.local_ip_updates[
'deleted'][assoc.fixed_port_id])
with mock.patch.object(self.local_ip_ext,
'delete_local_ip_flows') as del_lip_flows:
port = {'port_id': assoc.fixed_port_id, 'local_vlan': 1}
self.local_ip_ext.handle_port(self.context, port)
self.assertEqual({}, self.local_ip_ext.local_ip_updates[
'deleted'][assoc.fixed_port_id])
del_lip_flows.assert_called_once_with(port, assoc)
def test_delete_port(self):
lip_assocs = self.test_handle_updated_notification()
@ -143,3 +150,126 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
self.assertEqual({}, self.local_ip_ext.local_ip_updates['added'])
self.assertEqual({}, self.local_ip_ext.local_ip_updates['added'])
def test_add_local_ip_flows(self):
assoc = self._generate_test_lip_associations(1)[0]
port = {'port_id': assoc.fixed_port_id,
'mac_address': 'fa:16:3e:11:22:33',
'local_vlan': 1234}
with mock.patch.object(self.local_ip_ext,
'setup_local_ip_translation') as set_lip_trans:
self.local_ip_ext.add_local_ip_flows(port, assoc)
set_lip_trans.assert_called_once_with(
vlan=port['local_vlan'],
local_ip=str(assoc.local_ip.local_ip_address),
dest_ip=str(assoc.fixed_ip),
mac=port['mac_address']
)
self.int_br.install_arp_responder.assert_called_once_with(
vlan=port['local_vlan'],
ip=str(assoc.local_ip.local_ip_address),
mac=port['mac_address'], table_id=31)
self.int_br.install_garp_blocker.assert_called_once_with(
vlan=port['local_vlan'],
ip=str(assoc.local_ip.local_ip_address))
self.int_br.install_garp_blocker_exception.assert_called_once_with(
vlan=port['local_vlan'],
ip=str(assoc.local_ip.local_ip_address),
except_ip=str(assoc.fixed_ip))
def test_delete_local_ip_flows(self):
assoc = self._generate_test_lip_associations(1)[0]
port = {'port_id': assoc.fixed_port_id,
'mac_address': 'fa:16:3e:11:22:33',
'local_vlan': 1234}
with mock.patch.object(self.local_ip_ext,
'delete_local_ip_translation') as del_lip_trans:
self.local_ip_ext.delete_local_ip_flows(port, assoc)
del_lip_trans.assert_called_once_with(
vlan=port['local_vlan'],
local_ip=str(assoc.local_ip.local_ip_address),
dest_ip=str(assoc.fixed_ip),
mac=port['mac_address']
)
self.int_br.delete_arp_responder.assert_called_once_with(
vlan=port['local_vlan'],
ip=str(assoc.local_ip.local_ip_address),
table_id=31)
self.int_br.delete_garp_blocker.assert_called_once_with(
vlan=port['local_vlan'],
ip=str(assoc.local_ip.local_ip_address))
self.int_br.delete_garp_blocker_exception.assert_called_once_with(
vlan=port['local_vlan'],
ip=str(assoc.local_ip.local_ip_address),
except_ip=str(assoc.fixed_ip))
def test_setup_local_ip_translation(self):
vlan = 1234
local_ip = '172.0.0.10'
dest_ip = '10.0.0.10'
mac = 'fa:16:3e:11:22:33'
self.local_ip_ext.setup_local_ip_translation(
vlan, local_ip, dest_ip, mac)
expected_calls = [
mock.call(
table=31,
priority=10,
nw_dst=local_ip,
reg6=vlan,
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
actions='mod_dl_dst:{:s},'
'ct(commit,table={:d},zone={:d},nat(dst={:s}))'.format(
mac, 60, vlan, dest_ip)),
mock.call(
table=31,
priority=11,
nw_src=dest_ip,
nw_dst=local_ip,
reg6=vlan,
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
actions='resubmit(,{:d})'.format(60)),
mock.call(
table=31,
priority=10,
dl_src=mac,
nw_src=dest_ip,
reg6=vlan,
ct_state="-trk",
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
actions='ct(table={:d},zone={:d},nat'.format(60, vlan))
]
self.assertEqual(expected_calls, self.int_br.add_flow.mock_calls)
def test_delete_local_ip_translation(self):
vlan = 1234
local_ip = '172.0.0.10'
dest_ip = '10.0.0.10'
mac = 'fa:16:3e:11:22:33'
self.local_ip_ext.delete_local_ip_translation(
vlan, local_ip, dest_ip, mac)
expected_calls = [
mock.call(
table_id=31,
priority=10,
ipv4_dst=local_ip,
reg6=vlan,
eth_type=ether_types.ETH_TYPE_IP),
mock.call(
table_id=31,
priority=11,
ipv4_src=dest_ip,
ipv4_dst=local_ip,
reg6=vlan,
eth_type=ether_types.ETH_TYPE_IP),
mock.call(
table_id=31,
priority=10,
eth_src=mac,
ipv4_src=dest_ip,
reg6=vlan,
eth_type=ether_types.ETH_TYPE_IP)
]
self.assertEqual(
expected_calls, self.int_br.uninstall_flows.mock_calls)

View File

@ -157,6 +157,55 @@ class OVSBridgeTestBase(ovs_test_base.OVSOSKenTestBase):
add_protocols.assert_called_once_with(
constants.OPENFLOW10, constants.OPENFLOW13)
def test_install_arp_responder(self):
vlan = 3333
ip = '192.0.2.1'
mac = '08:60:6e:7f:74:e7'
self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
ofpp.OFPActionSetField(arp_op=2),
ofpp.NXActionRegMove(
dst_field='arp_tha',
n_bits=48,
src_field='arp_sha'),
ofpp.NXActionRegMove(
dst_field='arp_tpa',
n_bits=32,
src_field='arp_spa'),
ofpp.OFPActionSetField(arp_sha=mac),
ofpp.OFPActionSetField(arp_spa=ip),
ofpp.NXActionRegMove(src_field='eth_src',
dst_field='eth_dst',
n_bits=48),
ofpp.OFPActionSetField(eth_src=mac),
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0),
]),
],
match=self.br._arp_responder_match(ofp, ofpp, vlan, ip),
priority=1,
table_id=21),
active_bundle=None),
]
self.assertEqual(expected, self.mock.mock_calls)
def test_delete_arp_responder(self):
vlan = 3333
ip = '192.0.2.1'
self.br.delete_arp_responder(vlan=vlan, ip=ip)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call.uninstall_flows(
table_id=21,
priority=1,
match=self.br._arp_responder_match(ofp, ofpp, vlan, ip))
]
self.assertEqual(expected, self.mock.mock_calls)
class OVSDVRProcessTestMixin(object):
def test_install_dvr_process_ipv4(self):

View File

@ -127,6 +127,24 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
priority=3,
table_id=constants.TRANSIENT_EGRESS_TABLE),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(
dp, cookie=self.stamp,
instructions=[
ofpp.OFPInstructionGotoTable(table_id=60),
],
match=ofpp.OFPMatch(),
priority=0,
table_id=30),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(
dp, cookie=self.stamp,
instructions=[
ofpp.OFPInstructionGotoTable(table_id=60),
],
match=ofpp.OFPMatch(),
priority=0,
table_id=31),
active_bundle=None),
]
self.assertEqual(expected, self.mock.mock_calls)
@ -671,3 +689,103 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
active_bundle=None)
]
self.assertEqual(expected, self.mock.mock_calls)
def test_setup_local_egress_flows(self):
in_port = 10
vlan = 3333
self.br.setup_local_egress_flows(in_port=in_port, vlan=vlan)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
instructions=[
ofpp.OFPInstructionGotoTable(table_id=30)],
match=ofpp.OFPMatch(in_port=in_port),
priority=8,
table_id=0),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
instructions=[ofpp.OFPInstructionActions(
ofp.OFPIT_APPLY_ACTIONS,
[ofpp.OFPActionSetField(reg6=vlan),
ofpp.NXActionResubmitTable(in_port=in_port,
table_id=31)])],
match=ofpp.OFPMatch(in_port=in_port),
priority=10, table_id=30),
active_bundle=None)
]
self.assertEqual(expected, self.mock.mock_calls)
def test_install_garp_blocker(self):
vlan = 2222
ip = '192.0.0.10'
self.br.install_garp_blocker(vlan, ip)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
instructions=[],
match=ofpp.OFPMatch(
vlan_vid=vlan | ofp.OFPVID_PRESENT,
eth_type=self.ether_types.ETH_TYPE_ARP,
arp_spa=ip),
priority=10,
table_id=0),
active_bundle=None)]
self.assertEqual(expected, self.mock.mock_calls)
def test_delete_garp_blocker(self):
vlan = 2222
ip = '192.0.0.10'
self.br.delete_garp_blocker(vlan, ip)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call.uninstall_flows(
table_id=0,
priority=10,
match=ofpp.OFPMatch(
vlan_vid=vlan | ofp.OFPVID_PRESENT,
eth_type=self.ether_types.ETH_TYPE_ARP,
arp_spa=ip))
]
self.assertEqual(expected, self.mock.mock_calls)
def test_install_garp_blocker_exception(self):
vlan = 2222
ip = '192.0.0.10'
except_ip = '192.0.0.20'
self.br.install_garp_blocker_exception(vlan, ip, except_ip)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
instructions=[
ofpp.OFPInstructionGotoTable(table_id=60)],
match=ofpp.OFPMatch(
vlan_vid=vlan | ofp.OFPVID_PRESENT,
eth_type=self.ether_types.ETH_TYPE_ARP,
arp_spa=ip, arp_tpa=except_ip),
priority=11,
table_id=0),
active_bundle=None)
]
self.assertEqual(expected, self.mock.mock_calls)
def test_delete_garp_blocker_exception(self):
vlan = 2222
ip = '192.0.0.10'
except_ip = '192.0.0.20'
self.br.delete_garp_blocker_exception(vlan, ip, except_ip)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call.uninstall_flows(
table_id=0,
priority=11,
match=ofpp.OFPMatch(
vlan_vid=vlan | ofp.OFPVID_PRESENT,
eth_type=self.ether_types.ETH_TYPE_ARP,
arp_spa=ip, arp_tpa=except_ip))
]
self.assertEqual(expected, self.mock.mock_calls)

View File

@ -405,45 +405,6 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
]
self.assertEqual(expected, self.mock.mock_calls)
def test_install_arp_responder(self):
vlan = 3333
ip = '192.0.2.1'
mac = '08:60:6e:7f:74:e7'
self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
ofpp.OFPActionSetField(arp_op=self.arp.ARP_REPLY),
ofpp.NXActionRegMove(
dst_field='arp_tha',
n_bits=48,
src_field='arp_sha'),
ofpp.NXActionRegMove(
dst_field='arp_tpa',
n_bits=32,
src_field='arp_spa'),
ofpp.OFPActionSetField(arp_sha=mac),
ofpp.OFPActionSetField(arp_spa=ip),
ofpp.NXActionRegMove(src_field='eth_src',
dst_field='eth_dst',
n_bits=48),
ofpp.OFPActionSetField(eth_src=mac),
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0),
]),
],
match=ofpp.OFPMatch(
eth_type=self.ether_types.ETH_TYPE_ARP,
arp_tpa=ip,
vlan_vid=vlan | ofp.OFPVID_PRESENT),
priority=1,
table_id=21),
active_bundle=None),
]
self.assertEqual(expected, self.mock.mock_calls)
def test_delete_arp_responder(self):
vlan = 3333
ip = '192.0.2.1'