Browse Source

Limit router gw ports' stateful fixed IPs to one per address family

Validate a router's gateway port during a router update by ensuring
it has no more than one v4 fixed IP and one v6 (statefully-assigned)
fixed IP.

Note that there is no limit on v6 addresses from SLAAC and
DHCPv6-stateless subnets as they are automatically allocated.

Change-Id: I6a328048b99af39ab9497fd9f265d1a9b95b7148
Closes-Bug: 1438819
Partially-implements: blueprint multiple-ipv6-prefixes
tags/7.0.0a0
Andrew Boik 4 years ago
parent
commit
1bfd86e1ef
2 changed files with 116 additions and 7 deletions
  1. 20
    7
      neutron/db/db_base_plugin_v2.py
  2. 96
    0
      neutron/tests/unit/extensions/test_l3.py

+ 20
- 7
neutron/db/db_base_plugin_v2.py View File

@@ -1143,19 +1143,32 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
1143 1143
                     pool=pool_range,
1144 1144
                     ip_address=gateway_ip)
1145 1145
 
1146
-    def _update_router_gw_ports(self, context, subnet_id, network_id):
1146
+    def _update_router_gw_ports(self, context, network, subnet):
1147 1147
         l3plugin = manager.NeutronManager.get_service_plugins().get(
1148 1148
                 service_constants.L3_ROUTER_NAT)
1149 1149
         if l3plugin:
1150 1150
             gw_ports = self._get_router_gw_ports_by_network(context,
1151
-                    network_id)
1151
+                    network['id'])
1152 1152
             router_ids = [p['device_id'] for p in gw_ports]
1153 1153
             ctx_admin = ctx.get_admin_context()
1154
+            ext_subnets_dict = {s['id']: s for s in network['subnets']}
1154 1155
             for id in router_ids:
1155 1156
                 router = l3plugin.get_router(ctx_admin, id)
1156 1157
                 external_gateway_info = router['external_gateway_info']
1158
+                # Get all stateful (i.e. non-SLAAC/DHCPv6-stateless) fixed ips
1159
+                fips = [f for f in external_gateway_info['external_fixed_ips']
1160
+                        if not ipv6_utils.is_auto_address_subnet(
1161
+                            ext_subnets_dict[f['subnet_id']])]
1162
+                num_fips = len(fips)
1163
+                # Don't add the fixed IP to the port if it already
1164
+                # has a stateful fixed IP of the same IP version
1165
+                if num_fips > 1:
1166
+                    continue
1167
+                if num_fips == 1 and netaddr.IPAddress(
1168
+                        fips[0]['ip_address']).version == subnet['ip_version']:
1169
+                    continue
1157 1170
                 external_gateway_info['external_fixed_ips'].append(
1158
-                                             {'subnet_id': subnet_id})
1171
+                                             {'subnet_id': subnet['id']})
1159 1172
                 info = {'router': {'external_gateway_info':
1160 1173
                     external_gateway_info}}
1161 1174
                 l3plugin.update_router(context, id, info)
@@ -1295,8 +1308,8 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
1295 1308
                                        s['allocation_pools'])
1296 1309
         if hasattr(network, 'external') and network.external:
1297 1310
             self._update_router_gw_ports(context,
1298
-                                         subnet['id'],
1299
-                                         subnet['network_id'])
1311
+                                         network,
1312
+                                         subnet)
1300 1313
         return self._make_subnet_dict(subnet)
1301 1314
 
1302 1315
     def _create_subnet_from_implicit_pool(self, context, subnet):
@@ -1321,8 +1334,8 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
1321 1334
                                        s['allocation_pools'])
1322 1335
         if hasattr(network, 'external') and network.external:
1323 1336
             self._update_router_gw_ports(context,
1324
-                                         subnet['id'],
1325
-                                         subnet['network_id'])
1337
+                                         network,
1338
+                                         subnet)
1326 1339
         return self._make_subnet_dict(subnet)
1327 1340
 
1328 1341
     def _get_subnetpool_id(self, subnet):

+ 96
- 0
neutron/tests/unit/extensions/test_l3.py View File

@@ -883,6 +883,56 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
883 883
                 self.assertNotEqual(fip1['subnet_id'], fip2['subnet_id'])
884 884
                 self.assertNotEqual(fip1['ip_address'], fip2['ip_address'])
885 885
 
886
+    def test_router_update_gateway_upon_subnet_create_max_ips_ipv6(self):
887
+        """Create subnet should not cause excess fixed IPs on router gw
888
+
889
+        If a router gateway port has the maximum of one IPv4 and one IPv6
890
+        fixed, create subnet should not add any more IP addresses to the port
891
+        (unless this is the subnet is a SLAAC/DHCPv6-stateless subnet in which
892
+        case the addresses are added automatically)
893
+
894
+        """
895
+        with self.router() as r, self.network() as n:
896
+            with self.subnet(cidr='10.0.0.0/24', network=n) as s1, (
897
+                    self.subnet(ip_version=6, cidr='2001:db8::/64',
898
+                        network=n)) as s2:
899
+                self._set_net_external(n['network']['id'])
900
+                self._add_external_gateway_to_router(
901
+                        r['router']['id'],
902
+                        n['network']['id'],
903
+                        ext_ips=[{'subnet_id': s1['subnet']['id']},
904
+                                 {'subnet_id': s2['subnet']['id']}],
905
+                        expected_code=exc.HTTPOk.code)
906
+                res1 = self._show('routers', r['router']['id'])
907
+                original_fips = (res1['router']['external_gateway_info']
908
+                                 ['external_fixed_ips'])
909
+                # Add another IPv4 subnet - a fip SHOULD NOT be added
910
+                # to the external gateway port as it already has a v4 address
911
+                self._create_subnet(self.fmt, net_id=n['network']['id'],
912
+                                    cidr='10.0.1.0/24')
913
+                res2 = self._show('routers', r['router']['id'])
914
+                self.assertEqual(original_fips,
915
+                                 res2['router']['external_gateway_info']
916
+                                 ['external_fixed_ips'])
917
+                # Add a SLAAC subnet - a fip from this subnet SHOULD be added
918
+                # to the external gateway port
919
+                s3 = self.deserialize(self.fmt,
920
+                        self._create_subnet(self.fmt,
921
+                            net_id=n['network']['id'],
922
+                            ip_version=6, cidr='2001:db8:1::/64',
923
+                            ipv6_ra_mode=l3_constants.IPV6_SLAAC,
924
+                            ipv6_address_mode=l3_constants.IPV6_SLAAC))
925
+                res3 = self._show('routers', r['router']['id'])
926
+                fips = (res3['router']['external_gateway_info']
927
+                        ['external_fixed_ips'])
928
+                fip_subnet_ids = [fip['subnet_id'] for fip in fips]
929
+                self.assertIn(s1['subnet']['id'], fip_subnet_ids)
930
+                self.assertIn(s2['subnet']['id'], fip_subnet_ids)
931
+                self.assertIn(s3['subnet']['id'], fip_subnet_ids)
932
+                self._remove_external_gateway_from_router(
933
+                    r['router']['id'],
934
+                    n['network']['id'])
935
+
886 936
     def _test_router_add_interface_subnet(self, router, subnet, msg=None):
887 937
         exp_notifications = ['router.create.start',
888 938
                              'router.create.end',
@@ -1371,6 +1421,52 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
1371 1421
                                               expected_code=exc.
1372 1422
                                               HTTPBadRequest.code)
1373 1423
 
1424
+    def test_router_add_gateway_multiple_subnets_ipv6(self):
1425
+        """Ensure external gateway set doesn't add excess IPs on router gw
1426
+
1427
+        Setting the gateway of a router to an external network with more than
1428
+        one IPv4 and one IPv6 subnet should only add an address from the first
1429
+        IPv4 subnet, an address from the first IPv6-stateful subnet, and an
1430
+        address from each IPv6-stateless (SLAAC and DHCPv6-stateless) subnet
1431
+
1432
+        """
1433
+        with self.router() as r, self.network() as n:
1434
+            with self.subnet(
1435
+                    cidr='10.0.0.0/24', network=n) as s1, (
1436
+                 self.subnet(
1437
+                    cidr='10.0.1.0/24', network=n)) as s2, (
1438
+                 self.subnet(
1439
+                    cidr='2001:db8::/64', network=n,
1440
+                    ip_version=6,
1441
+                    ipv6_ra_mode=l3_constants.IPV6_SLAAC,
1442
+                    ipv6_address_mode=l3_constants.IPV6_SLAAC)) as s3, (
1443
+                 self.subnet(
1444
+                    cidr='2001:db8:1::/64', network=n,
1445
+                    ip_version=6,
1446
+                    ipv6_ra_mode=l3_constants.DHCPV6_STATEFUL,
1447
+                    ipv6_address_mode=l3_constants.DHCPV6_STATEFUL)) as s4, (
1448
+                 self.subnet(
1449
+                    cidr='2001:db8:2::/64', network=n,
1450
+                    ip_version=6,
1451
+                    ipv6_ra_mode=l3_constants.DHCPV6_STATELESS,
1452
+                    ipv6_address_mode=l3_constants.DHCPV6_STATELESS)) as s5:
1453
+                self._set_net_external(n['network']['id'])
1454
+                self._add_external_gateway_to_router(
1455
+                        r['router']['id'],
1456
+                        n['network']['id'])
1457
+                res = self._show('routers', r['router']['id'])
1458
+                fips = (res['router']['external_gateway_info']
1459
+                        ['external_fixed_ips'])
1460
+                fip_subnet_ids = [fip['subnet_id'] for fip in fips]
1461
+                self.assertIn(s1['subnet']['id'], fip_subnet_ids)
1462
+                self.assertNotIn(s2['subnet']['id'], fip_subnet_ids)
1463
+                self.assertIn(s3['subnet']['id'], fip_subnet_ids)
1464
+                self.assertIn(s4['subnet']['id'], fip_subnet_ids)
1465
+                self.assertIn(s5['subnet']['id'], fip_subnet_ids)
1466
+                self._remove_external_gateway_from_router(
1467
+                    r['router']['id'],
1468
+                    n['network']['id'])
1469
+
1374 1470
     def test_router_add_and_remove_gateway(self):
1375 1471
         with self.router() as r:
1376 1472
             with self.subnet() as s:

Loading…
Cancel
Save