By default, add floating IP NAT rules to each vnic on router

There are use cases where instances on the subnet need to access
one another via floating IP's. For this to work, we need to create
the NAT rules on the internal interfaces of the edge.
Before this change, floating IP NAT rule was added to external
interface only, and hosts residing on same network were not able
to connect via floating IPs. This behavior is still available
by setting config variable bind_floatingip_to_all_interfaces
to false. New behavior is dependant on driver version:
before 6.2.4, a NAT rule is created explicitly for each vnic
from 6.2.4 onwards, single NAT rule without vnic is created, which
is interpreted by platform as 'bind to all interfaces'.

Change-Id: Iacc858f726f58d05fa54a723304193f3dc1fb66d
This commit is contained in:
Anna Khmelnitsky 2016-11-17 14:55:18 -08:00
parent 4798f3986d
commit 4da2726593
5 changed files with 168 additions and 18 deletions

View File

@ -631,6 +631,12 @@ nsxv_opts = [
help=_("(Optional) Sets the network address for distributed "
"router TLR-PLR connectivity, with "
"<network IP>/<prefix> syntax")),
cfg.BoolOpt('bind_floatingip_to_all_interfaces', default=True,
help=_("If set to False, router will associate floating ip "
"with external interface of only, thus denying "
"connectivity between hosts on same network via "
"their floating ips. If True, floating ip will "
"be associated with all router interfaces.")),
]
# Register the configuration options

View File

@ -213,9 +213,12 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
snat, dnat = self.plugin._get_nat_rules(context, router)
snats.extend(snat)
dnats.extend(dnat)
if len(dnat) > 0:
if (not cfg.CONF.nsxv.bind_floatingip_to_all_interfaces and
len(dnat) > 0):
# Copy each DNAT rule to all vnics of the other routers,
# to allow NAT-ed traffic between routers
# no need for that if bind_floatingip_to_all_interfaces
# is on (default)
other_vnics = []
for other_router_id in router_ids:
if other_router_id != router_id:

View File

@ -539,14 +539,15 @@ class EdgeApplianceDriver(object):
def _assemble_nat_rule(self, action, original_address,
translated_address,
vnic_index=constants.EXTERNAL_VNIC_INDEX,
vnic_index=None,
enabled=True,
protocol='any',
original_port='any',
translated_port='any'):
nat_rule = {}
nat_rule['action'] = action
nat_rule['vnic'] = vnic_index
if vnic_index is not None:
nat_rule['vnic'] = vnic_index
nat_rule['originalAddress'] = original_address
nat_rule['translatedAddress'] = translated_address
nat_rule['enabled'] = enabled
@ -564,31 +565,57 @@ class EdgeApplianceDriver(object):
e.response)
raise e
def update_nat_rules(self, edge_id, snats, dnats):
def update_nat_rules(self, edge_id, snats, dnats, indices=None):
LOG.debug("VCNS: update nat rule\n"
"SNAT:%(snat)s\n"
"DNAT:%(dnat)s\n", {
'snat': snats, 'dnat': dnats})
"DNAT:%(dnat)s\n"
"INDICES: %(index)s\n", {
'snat': snats, 'dnat': dnats, 'index': indices})
nat_rules = []
for dnat in dnats:
vnic_index = constants.EXTERNAL_VNIC_INDEX
vnic_index = None
if 'vnic_index' in dnat:
vnic_index = dnat['vnic_index']
nat_rules.append(self._assemble_nat_rule(
'dnat', dnat['dst'], dnat['translated'], vnic_index=vnic_index
))
nat_rules.append(self._assemble_nat_rule(
'snat', dnat['translated'], dnat['dst'], vnic_index=vnic_index
))
if vnic_index or not indices:
# we are adding a predefined index or
# adding to all interfaces
nat_rules.append(self._assemble_nat_rule(
'dnat', dnat['dst'], dnat['translated'],
vnic_index=vnic_index
))
nat_rules.append(self._assemble_nat_rule(
'snat', dnat['translated'], dnat['dst'],
vnic_index=vnic_index
))
else:
for index in indices:
nat_rules.append(self._assemble_nat_rule(
'dnat', dnat['dst'], dnat['translated'],
vnic_index=index
))
nat_rules.append(self._assemble_nat_rule(
'snat', dnat['translated'], dnat['dst'],
vnic_index=index
))
for snat in snats:
vnic_index = constants.EXTERNAL_VNIC_INDEX
vnic_index = None
if 'vnic_index' in snat:
vnic_index = snat['vnic_index']
nat_rules.append(self._assemble_nat_rule(
'snat', snat['src'], snat['translated'], vnic_index=vnic_index
))
if vnic_index or not indices:
# we are adding a predefined index
# or adding to all interfaces
nat_rules.append(self._assemble_nat_rule(
'snat', snat['src'], snat['translated'],
vnic_index=vnic_index
))
else:
for index in indices:
nat_rules.append(self._assemble_nat_rule(
'snat', snat['src'], snat['translated'],
vnic_index=index
))
nat = {
'featureType': 'nat',

View File

@ -2299,7 +2299,29 @@ def _delete_interface(nsxv_manager, context, router_id, network_id,
def update_nat_rules(nsxv_manager, context, router_id, snat, dnat):
binding = nsxv_db.get_nsxv_router_binding(context.session, router_id)
if binding:
nsxv_manager.update_nat_rules(binding['edge_id'], snat, dnat)
bind_to_all = cfg.CONF.nsxv.bind_floatingip_to_all_interfaces
indices = None
if bind_to_all:
# from 6.2.4 onwards, unspecified vnic will result
# in binding the rule to all interfaces
ver = nsxv_manager.vcns.get_version()
if version.LooseVersion(ver) < version.LooseVersion('6.2.4'):
LOG.debug("NSX version %s requires explicit nat rule "
"for each interface", ver)
edge_id = binding['edge_id']
vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge(
context.session, edge_id)
indices = [vnic_binding.vnic_index
for vnic_binding in vnic_bindings]
indices.append(vcns_const.EXTERNAL_VNIC_INDEX)
else:
LOG.debug("Configuring nat rules on external "
"interface only for %s", router_id)
indices = [vcns_const.EXTERNAL_VNIC_INDEX]
nsxv_manager.update_nat_rules(binding['edge_id'], snat, dnat, indices)
else:
LOG.warning(_LW("Bindings do not exists for %s"), router_id)

View File

@ -411,6 +411,7 @@ class VcnsDriverTestCase(base.BaseTestCase):
'translated': '192.168.2.1'
}
]
result = self.vcns_driver.update_nat_rules(self.edge_id, snats, dnats)
self.assertTrue(result)
@ -425,6 +426,97 @@ class VcnsDriverTestCase(base.BaseTestCase):
self.natEquals(rules[5], snats[1])
self.natEquals(rules[6], snats[2])
def test_update_nat_rules_for_all_vnics(self):
self._deploy_edge()
snats = [{
'src': '192.168.1.0/24',
'translated': '10.0.0.1'
}, {
'src': '192.168.2.0/24',
'translated': '10.0.0.2'
}, {
'src': '192.168.3.0/24',
'translated': '10.0.0.3'
}
]
dnats = [{
'dst': '100.0.0.4',
'translated': '192.168.1.1'
}, {
'dst': '100.0.0.5',
'translated': '192.168.2.1'
}
]
indices = [0, 1, 2, 3]
result = self.vcns_driver.update_nat_rules(self.edge_id,
snats, dnats, indices)
self.assertTrue(result)
natcfg = self.vcns_driver.get_nat_config(self.edge_id)
rules = natcfg['rules']['natRulesDtos']
self.assertEqual(len(rules), 2 * len(indices) * len(dnats)
+ len(indices) * len(snats))
sorted_rules = sorted(rules, key=lambda k: k['vnic'])
for i in range(0, len(sorted_rules), 7):
self.natEquals(sorted_rules[i], dnats[0])
self.natEquals(sorted_rules[i + 1], self.snat_for_dnat(dnats[0]))
self.natEquals(sorted_rules[i + 2], dnats[1])
self.natEquals(sorted_rules[i + 3], self.snat_for_dnat(dnats[1]))
self.natEquals(sorted_rules[i + 4], snats[0])
self.natEquals(sorted_rules[i + 5], snats[1])
self.natEquals(sorted_rules[i + 6], snats[2])
def test_update_nat_rules_for_specific_vnics(self):
self._deploy_edge()
snats = [{
'src': '192.168.1.0/24',
'translated': '10.0.0.1',
'vnic_index': 5
}, {
'src': '192.168.2.0/24',
'translated': '10.0.0.2'
}, {
'src': '192.168.3.0/24',
'translated': '10.0.0.3'
}
]
dnats = [{
'dst': '100.0.0.4',
'translated': '192.168.1.1',
'vnic_index': 2
}, {
'dst': '100.0.0.5',
'translated': '192.168.2.1'
}
]
result = self.vcns_driver.update_nat_rules(self.edge_id, snats, dnats)
self.assertTrue(result)
natcfg = self.vcns_driver.get_nat_config(self.edge_id)
rules = natcfg['rules']['natRulesDtos']
self.assertEqual(len(rules), 2 * len(dnats) + len(snats))
self.natEquals(rules[0], dnats[0])
self.assertEqual(rules[0]['vnic'], 2)
self.natEquals(rules[1], self.snat_for_dnat(dnats[0]))
self.assertEqual(rules[1]['vnic'], 2)
self.natEquals(rules[2], dnats[1])
self.assertNotIn('vnic', rules[2])
self.natEquals(rules[3], self.snat_for_dnat(dnats[1]))
self.assertNotIn('vnic', rules[3])
self.natEquals(rules[4], snats[0])
self.assertEqual(rules[4]['vnic'], 5)
self.natEquals(rules[5], snats[1])
self.assertNotIn('vnic', rules[5])
self.natEquals(rules[6], snats[2])
self.assertNotIn('vnic', rules[6])
def snat_for_dnat(self, dnat):
return {
'src': dnat['translated'],