Merge "Allow to use static Local IP openflow rules"
This commit is contained in:
commit
494c1a62d0
|
@ -22,6 +22,7 @@ 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 os_ken.lib.packet import in_proto as ip_proto
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
|
@ -47,9 +48,11 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
{'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')
|
||||
cfg.CONF.SECURITYGROUP.firewall_driver == 'openvswitch' and
|
||||
not cfg.CONF.LOCAL_IP.static_nat):
|
||||
LOG.error('In order to use Local IP extension together with '
|
||||
'openvswitch firewall please set static_nat config to '
|
||||
'True')
|
||||
sys.exit(1)
|
||||
|
||||
self.resource_rpc = resources_rpc.ResourcesPullRpcApi()
|
||||
|
@ -158,9 +161,14 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
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'])
|
||||
if cfg.CONF.LOCAL_IP.static_nat:
|
||||
self.setup_static_local_ip_translation(
|
||||
vlan=vlan, local_ip=local_ip_address,
|
||||
dest_ip=dest_ip, mac=port['mac_address'])
|
||||
else:
|
||||
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)
|
||||
|
@ -199,16 +207,6 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
'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,
|
||||
|
@ -220,6 +218,19 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
actions='ct(table={:d},zone={:d},nat'.format(
|
||||
ovs_constants.TRANSIENT_TABLE, vlan)
|
||||
)
|
||||
self._avoid_nat_to_self(vlan, local_ip, dest_ip)
|
||||
|
||||
def _avoid_nat_to_self(self, vlan, local_ip, 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)
|
||||
)
|
||||
|
||||
def delete_local_ip_translation(self, vlan, local_ip, dest_ip, mac):
|
||||
self.int_br.uninstall_flows(
|
||||
|
@ -245,3 +256,101 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP
|
||||
)
|
||||
|
||||
def setup_static_local_ip_translation(self, vlan, local_ip, dest_ip, mac):
|
||||
(dp, ofp, ofpp) = self.int_br._get_dp()
|
||||
common_match_kwargs = {
|
||||
'reg6': vlan,
|
||||
'ipv4_dst': local_ip,
|
||||
'eth_type': ether_types.ETH_TYPE_IP}
|
||||
common_specs = [
|
||||
ofpp.NXFlowSpecMatch(src=ether_types.ETH_TYPE_IP,
|
||||
dst=('eth_type', 0),
|
||||
n_bits=16),
|
||||
ofpp.NXFlowSpecMatch(src=('eth_src', 0),
|
||||
dst=('eth_dst', 0),
|
||||
n_bits=48),
|
||||
ofpp.NXFlowSpecMatch(src=('eth_dst', 0),
|
||||
dst=('eth_src', 0),
|
||||
n_bits=48),
|
||||
ofpp.NXFlowSpecMatch(src=('ipv4_src', 0),
|
||||
dst=('ipv4_dst', 0),
|
||||
n_bits=32),
|
||||
ofpp.NXFlowSpecMatch(src=int(netaddr.IPAddress(dest_ip)),
|
||||
dst=('ipv4_src', 0),
|
||||
n_bits=32),
|
||||
ofpp.NXFlowSpecMatch(src=vlan,
|
||||
dst=('reg6', 0),
|
||||
n_bits=4),
|
||||
ofpp.NXFlowSpecLoad(src=int(netaddr.IPAddress(local_ip)),
|
||||
dst=('ipv4_src', 0),
|
||||
n_bits=32),
|
||||
ofpp.NXFlowSpecOutput(src=('in_port', 0),
|
||||
dst='',
|
||||
n_bits=32),
|
||||
]
|
||||
for specs, match_kwargs in [self._icmp_flow_match_specs(ofpp),
|
||||
self._tcp_flow_match_specs(ofpp),
|
||||
self._udp_flow_match_specs(ofpp)]:
|
||||
flow_specs = common_specs + specs
|
||||
learn_table = ovs_constants.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE
|
||||
actions = [
|
||||
ofpp.OFPActionSetField(eth_dst=mac),
|
||||
ofpp.NXActionLearn(
|
||||
table_id=learn_table,
|
||||
cookie=self.int_br.default_cookie,
|
||||
priority=20,
|
||||
idle_timeout=30,
|
||||
hard_timeout=300,
|
||||
specs=flow_specs),
|
||||
ofpp.OFPActionSetField(ipv4_dst=dest_ip),
|
||||
ofpp.NXActionResubmitTable(
|
||||
table_id=ovs_constants.TRANSIENT_TABLE)]
|
||||
|
||||
match = ofpp.OFPMatch(**common_match_kwargs, **match_kwargs)
|
||||
self.int_br.install_apply_actions(
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE,
|
||||
match=match,
|
||||
priority=10,
|
||||
actions=actions)
|
||||
self._avoid_nat_to_self(vlan, local_ip, dest_ip)
|
||||
|
||||
@staticmethod
|
||||
def _icmp_flow_match_specs(ofpp):
|
||||
specs = [
|
||||
ofpp.NXFlowSpecMatch(src=ip_proto.IPPROTO_ICMP,
|
||||
dst=('ip_proto', 0),
|
||||
n_bits=8)
|
||||
]
|
||||
match_kwargs = {'ip_proto': ip_proto.IPPROTO_ICMP}
|
||||
return specs, match_kwargs
|
||||
|
||||
@staticmethod
|
||||
def _tcp_flow_match_specs(ofpp):
|
||||
specs = [
|
||||
ofpp.NXFlowSpecMatch(src=ip_proto.IPPROTO_TCP,
|
||||
dst=('ip_proto', 0),
|
||||
n_bits=8),
|
||||
ofpp.NXFlowSpecMatch(src=('tcp_src', 0),
|
||||
dst=('tcp_dst', 0),
|
||||
n_bits=16),
|
||||
ofpp.NXFlowSpecMatch(src=('tcp_dst', 0),
|
||||
dst=('tcp_src', 0),
|
||||
n_bits=16)]
|
||||
match_kwargs = {'ip_proto': ip_proto.IPPROTO_TCP}
|
||||
return specs, match_kwargs
|
||||
|
||||
@staticmethod
|
||||
def _udp_flow_match_specs(ofpp):
|
||||
specs = [
|
||||
ofpp.NXFlowSpecMatch(src=ip_proto.IPPROTO_UDP,
|
||||
dst=('ip_proto', 0),
|
||||
n_bits=8),
|
||||
ofpp.NXFlowSpecMatch(src=('udp_src', 0),
|
||||
dst=('udp_dst', 0),
|
||||
n_bits=16),
|
||||
ofpp.NXFlowSpecMatch(src=('udp_dst', 0),
|
||||
dst=('udp_src', 0),
|
||||
n_bits=16)]
|
||||
match_kwargs = {'ip_proto': ip_proto.IPPROTO_UDP}
|
||||
return specs, match_kwargs
|
||||
|
|
|
@ -230,12 +230,23 @@ dhcp_opts = [
|
|||
"DHCPv6 packets.")),
|
||||
]
|
||||
|
||||
local_ip_opts = [
|
||||
cfg.BoolOpt('static_nat', default=False,
|
||||
help=_("When set to True, the Local IP openvswitch agent "
|
||||
"extension will use static NAT rules instead of using "
|
||||
"conntrack. This allows to use feature in OVS offload "
|
||||
"and DPDK scenarios at the cost of number and "
|
||||
"complexity of flows. This also allows to use feature "
|
||||
"together with ovs firewall.")),
|
||||
]
|
||||
|
||||
|
||||
def register_ovs_agent_opts(cfg=cfg.CONF):
|
||||
cfg.register_opts(ovs_opts, "OVS")
|
||||
cfg.register_opts(agent_opts, "AGENT")
|
||||
cfg.register_opts(dhcp_opts, "DHCP")
|
||||
cfg.register_opts(common.DHCP_PROTOCOL_OPTS, "DHCP")
|
||||
cfg.register_opts(local_ip_opts, "LOCAL_IP")
|
||||
|
||||
|
||||
def register_ovs_opts(cfg=cfg.CONF):
|
||||
|
|
|
@ -15,16 +15,20 @@
|
|||
|
||||
from unittest import mock
|
||||
|
||||
import netaddr
|
||||
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 os_ken.lib.packet import in_proto as ip_proto
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.agent.l2.extensions import local_ip as local_ip_ext
|
||||
from neutron.api.rpc.callbacks import events
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
from neutron.objects import local_ip as lip_obj
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
|
||||
constants as ovs_constants)
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent \
|
||||
import ovs_agent_extension_api as ovs_ext_api
|
||||
from neutron.tests import base
|
||||
|
@ -221,14 +225,6 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
|
|||
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,
|
||||
|
@ -237,7 +233,15 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
|
|||
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))
|
||||
actions='ct(table={:d},zone={:d},nat'.format(60, vlan)),
|
||||
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))
|
||||
]
|
||||
self.assertEqual(expected_calls, self.int_br.add_flow.mock_calls)
|
||||
|
||||
|
@ -273,3 +277,68 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
|
|||
]
|
||||
self.assertEqual(
|
||||
expected_calls, self.int_br.uninstall_flows.mock_calls)
|
||||
|
||||
def test_setup_static_local_ip_translation(self):
|
||||
ofpp_mock = mock.Mock()
|
||||
self.int_br._get_dp.return_value = (
|
||||
mock.Mock(), mock.Mock(), ofpp_mock)
|
||||
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_static_local_ip_translation(
|
||||
vlan, local_ip, dest_ip, mac)
|
||||
|
||||
expected_calls = [
|
||||
mock.call(src=ether_types.ETH_TYPE_IP,
|
||||
dst=('eth_type', 0), n_bits=16),
|
||||
mock.call(src=('eth_src', 0), dst=('eth_dst', 0), n_bits=48),
|
||||
mock.call(src=('eth_dst', 0), dst=('eth_src', 0), n_bits=48),
|
||||
mock.call(src=('ipv4_src', 0), dst=('ipv4_dst', 0), n_bits=32),
|
||||
mock.call(src=int(netaddr.IPAddress(dest_ip)),
|
||||
dst=('ipv4_src', 0), n_bits=32),
|
||||
mock.call(src=vlan, dst=('reg6', 0), n_bits=4),
|
||||
mock.call(src=ip_proto.IPPROTO_ICMP, dst=('ip_proto', 0),
|
||||
n_bits=8),
|
||||
mock.call(src=ip_proto.IPPROTO_TCP, dst=('ip_proto', 0),
|
||||
n_bits=8),
|
||||
mock.call(src=('tcp_src', 0), dst=('tcp_dst', 0), n_bits=16),
|
||||
mock.call(src=('tcp_dst', 0), dst=('tcp_src', 0), n_bits=16),
|
||||
mock.call(src=ip_proto.IPPROTO_UDP, dst=('ip_proto', 0),
|
||||
n_bits=8),
|
||||
mock.call(src=('udp_src', 0), dst=('udp_dst', 0), n_bits=16),
|
||||
mock.call(src=('udp_dst', 0), dst=('udp_src', 0), n_bits=16)
|
||||
]
|
||||
self.assertEqual(
|
||||
expected_calls, ofpp_mock.NXFlowSpecMatch.mock_calls)
|
||||
|
||||
ofpp_mock.NXFlowSpecLoad.assert_called_once_with(
|
||||
src=int(netaddr.IPAddress(local_ip)),
|
||||
dst=('ipv4_src', 0), n_bits=32)
|
||||
|
||||
ofpp_mock.NXFlowSpecOutput.assert_called_once_with(
|
||||
src=('in_port', 0), dst='', n_bits=32)
|
||||
|
||||
self.assertEqual(3, ofpp_mock.NXActionLearn.call_count)
|
||||
ofpp_mock.NXActionLearn.assert_called_with(
|
||||
table_id=ovs_constants.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE,
|
||||
cookie=mock.ANY, priority=20, idle_timeout=30,
|
||||
hard_timeout=300, specs=mock.ANY)
|
||||
|
||||
self.assertEqual(6, ofpp_mock.OFPActionSetField.call_count)
|
||||
ofpp_mock.OFPActionSetField.assert_any_call(ipv4_dst=dest_ip)
|
||||
ofpp_mock.OFPActionSetField.assert_any_call(eth_dst=mac)
|
||||
|
||||
self.assertEqual(3, ofpp_mock.NXActionResubmitTable.call_count)
|
||||
ofpp_mock.NXActionResubmitTable.assert_called_with(
|
||||
table_id=ovs_constants.TRANSIENT_TABLE)
|
||||
|
||||
self.assertEqual(3, self.int_br.install_apply_actions.call_count)
|
||||
self.int_br.install_apply_actions.assert_called_with(
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE, match=mock.ANY,
|
||||
priority=10, actions=mock.ANY)
|
||||
|
||||
self.int_br.add_flow.assert_called_once_with(
|
||||
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))
|
||||
|
|
Loading…
Reference in New Issue