summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2012-11-20 12:05:04 -0800
committerVishvananda Ishaya <vishvananda@gmail.com>2012-11-26 15:17:16 -0800
commit5b21ba723a4fe8076022dcafef0a55de3a99b35e (patch)
tree9d7dfb7649987e0543220d821cc7d812a1a97224
parent1bd7b4248dd54c6486e8747074211566ba9c35c8 (diff)
Allow multi_host compute nodes to share dhcp ip
This adds a new flag: share_dhcp_address which if enabled in multihost mode will allow all compute nodes to share an ip on guest network. The code will isolate the address using iptables and ebtables so it is only visible to the vms. This patch has two benefits: a) we don't have to use an ip address from every network for each compute node. This is especially valuable in vlan mode where the networks are generally small b) we can improve security by blocking all access to the ip on the guest network from outside the compute node. While we could do similar blocking using a different ip for each node, it makes dhcp setup much more complicated if a vm is migrated to another node. Implements blueprint shared-dhcp-ip Change-Id: Iaf84c0ad2848921122866956105eb44c074450dc
-rw-r--r--etc/nova/rootwrap.d/network.filters5
-rw-r--r--nova/db/sqlalchemy/api.py11
-rw-r--r--nova/network/linux_net.py43
-rw-r--r--nova/network/manager.py6
-rw-r--r--nova/tests/network/test_linux_net.py64
-rw-r--r--nova/tests/test_db_api.py27
6 files changed, 152 insertions, 4 deletions
diff --git a/etc/nova/rootwrap.d/network.filters b/etc/nova/rootwrap.d/network.filters
index c635f12e4..3a46080fa 100644
--- a/etc/nova/rootwrap.d/network.filters
+++ b/etc/nova/rootwrap.d/network.filters
@@ -34,6 +34,11 @@ ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root
# nova/network/linux_net.py: 'ovs-ofctl', ....
ovs-ofctl: CommandFilter, /usr/bin/ovs-ofctl, root
+# nova/network/linux_net.py: 'ebtables', '-D' ...
+# nova/network/linux_net.py: 'ebtables', '-I' ...
+ebtables: CommandFilter, /sbin/ebtables, root
+ebtables_usr: CommandFilter, /usr/sbin/ebtables, root
+
# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ...
iptables-save: CommandFilter, /sbin/iptables-save, root
iptables-save_usr: CommandFilter, /usr/sbin/iptables-save, root
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 34fbec6d3..d3c38e79b 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -2291,11 +2291,20 @@ def network_get_all_by_instance(context, instance_id):
@require_admin_context
def network_get_all_by_host(context, host):
session = get_session()
+ fixed_host_filter = or_(models.FixedIp.host == host,
+ models.Instance.host == host)
fixed_ip_query = model_query(context, models.FixedIp.network_id,
session=session).\
- filter(models.FixedIp.host == host)
+ outerjoin((models.VirtualInterface,
+ models.VirtualInterface.id ==
+ models.FixedIp.virtual_interface_id)).\
+ outerjoin((models.Instance,
+ models.Instance.uuid ==
+ models.VirtualInterface.instance_uuid)).\
+ filter(fixed_host_filter)
# NOTE(vish): return networks that have host set
# or that have a fixed ip with host set
+ # or that have an instance with host set
host_filter = or_(models.Network.host == host,
models.Network.id.in_(fixed_ip_query.subquery()))
return _network_get_query(context, session=session).\
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index c57e9a730..c64864f09 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -1085,14 +1085,16 @@ class LinuxNetInterfaceDriver(object):
class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
def plug(self, network, mac_address, gateway=True):
- if network.get('vlan', None) is not None:
+ vlan = network.get('vlan')
+ if vlan is not None:
iface = CONF.vlan_interface or network['bridge_interface']
LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
- network['vlan'],
+ vlan,
network['bridge'],
iface,
network,
mac_address)
+ iface = 'vlan%s' % vlan
else:
iface = CONF.flat_interface or network['bridge_interface']
LinuxBridgeInterfaceDriver.ensure_bridge(
@@ -1100,6 +1102,8 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
iface,
network, gateway)
+ if CONF.share_dhcp_address:
+ isolate_dhcp_address(iface, network['dhcp_server'])
# NOTE(vish): applying here so we don't get a lock conflict
iptables_manager.apply()
return network['bridge']
@@ -1227,6 +1231,41 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
'--out-interface %s -j DROP' % bridge)
+@lockutils.synchronized('ebtables', 'nova-', external=True)
+def ensure_ebtables_rules(rules):
+ for rule in rules:
+ cmd = ['ebtables', '-D'] + rule.split()
+ _execute(*cmd, check_exit_code=False, run_as_root=True)
+ cmd[1] = '-I'
+ _execute(*cmd, run_as_root=True)
+
+
+def isolate_dhcp_address(interface, address):
+ # block arp traffic to address accross the interface
+ rules = []
+ rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
+ % (interface, address))
+ rules.append('OUTPUT -p ARP -o %s --arp-ip-src %s -j DROP'
+ % (interface, address))
+ # NOTE(vish): the above is not possible with iptables/arptables
+ ensure_ebtables_rules(rules)
+ # block dhcp broadcast traffic across the interface
+ ipv4_filter = iptables_manager.ipv4['filter']
+ ipv4_filter.add_rule('FORWARD',
+ '-m physdev --physdev-in %s -d 255.255.255.255 '
+ '-p udp --dport 67 -j DROP' % interface, top=True)
+ ipv4_filter.add_rule('FORWARD',
+ '-m physdev --physdev-out %s -d 255.255.255.255 '
+ '-p udp --dport 67 -j DROP' % interface, top=True)
+ # block ip traffic to address accross the interface
+ ipv4_filter.add_rule('FORWARD',
+ '-m physdev --physdev-in %s -d %s -j DROP'
+ % (interface, address), top=True)
+ ipv4_filter.add_rule('FORWARD',
+ '-m physdev --physdev-out %s -s %s -j DROP'
+ % (interface, address), top=True)
+
+
# plugs interfaces using Open vSwitch
class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index f54764d8f..d380548c6 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -153,6 +153,10 @@ network_opts = [
cfg.BoolOpt('force_dhcp_release',
default=False,
help='If True, send a dhcp release on instance termination'),
+ cfg.BoolOpt('share_dhcp_address',
+ default=False,
+ help='If True in multi_host mode, all compute hosts share '
+ 'the same dhcp address.'),
cfg.StrOpt('dhcp_domain',
default='novalocal',
help='domain to use for building the hostnames'),
@@ -894,7 +898,7 @@ class NetworkManager(manager.SchedulerDependentManager):
def _get_dhcp_ip(self, context, network_ref, host=None):
"""Get the proper dhcp address to listen on."""
# NOTE(vish): this is for compatibility
- if not network_ref.get('multi_host'):
+ if not network_ref.get('multi_host') or CONF.share_dhcp_address:
return network_ref['gateway']
if not host:
diff --git a/nova/tests/network/test_linux_net.py b/nova/tests/network/test_linux_net.py
index 666ce6dab..757b73a94 100644
--- a/nova/tests/network/test_linux_net.py
+++ b/nova/tests/network/test_linux_net.py
@@ -405,6 +405,70 @@ class LinuxNetworkTestCase(test.TestCase):
driver.plug(network, "fakemac")
self.assertEqual(info['passed_interface'], "override_interface")
+ def test_isolated_host(self):
+ self.flags(fake_network=False,
+ share_dhcp_address=True)
+ # NOTE(vish): use a fresh copy of the manager for each test
+ self.stubs.Set(linux_net, 'iptables_manager',
+ linux_net.IptablesManager())
+ self.stubs.Set(linux_net, 'binary_name', 'test')
+ executes = []
+ inputs = []
+
+ def fake_execute(*args, **kwargs):
+ executes.append(args)
+ process_input = kwargs.get('process_input')
+ if process_input:
+ inputs.append(process_input)
+ return "", ""
+
+ self.stubs.Set(utils, 'execute', fake_execute)
+
+ driver = linux_net.LinuxBridgeInterfaceDriver()
+
+ @classmethod
+ def fake_ensure(_self, bridge, interface, network, gateway):
+ return bridge
+
+ self.stubs.Set(linux_net.LinuxBridgeInterfaceDriver,
+ 'ensure_bridge', fake_ensure)
+
+ iface = 'eth0'
+ dhcp = '192.168.1.1'
+ network = {'dhcp_server': dhcp,
+ 'bridge': 'br100',
+ 'bridge_interface': iface}
+ driver.plug(network, 'fakemac')
+ expected = [
+ ('ebtables', '-D', 'INPUT', '-p', 'ARP', '-i', iface,
+ '--arp-ip-dst', dhcp, '-j', 'DROP'),
+ ('ebtables', '-I', 'INPUT', '-p', 'ARP', '-i', iface,
+ '--arp-ip-dst', dhcp, '-j', 'DROP'),
+ ('ebtables', '-D', 'OUTPUT', '-p', 'ARP', '-o', iface,
+ '--arp-ip-src', dhcp, '-j', 'DROP'),
+ ('ebtables', '-I', 'OUTPUT', '-p', 'ARP', '-o', iface,
+ '--arp-ip-src', dhcp, '-j', 'DROP'),
+ ('iptables-save', '-c', '-t', 'filter'),
+ ('iptables-restore', '-c'),
+ ('iptables-save', '-c', '-t', 'nat'),
+ ('iptables-restore', '-c'),
+ ('ip6tables-save', '-c', '-t', 'filter'),
+ ('ip6tables-restore', '-c'),
+ ]
+ self.assertEqual(executes, expected)
+ expected_inputs = [
+ '-A test-FORWARD -m physdev --physdev-in %s '
+ '-d 255.255.255.255 -p udp --dport 67 -j DROP' % iface,
+ '-A test-FORWARD -m physdev --physdev-out %s '
+ '-d 255.255.255.255 -p udp --dport 67 -j DROP' % iface,
+ '-A test-FORWARD -m physdev --physdev-in %s '
+ '-d 192.168.1.1 -j DROP' % iface,
+ '-A test-FORWARD -m physdev --physdev-out %s '
+ '-s 192.168.1.1 -j DROP' % iface,
+ ]
+ for inp in expected_inputs:
+ self.assertTrue(inp in inputs[0])
+
def _test_initialize_gateway(self, existing, expected, routes=''):
self.flags(fake_network=False)
executes = []
diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py
index 193378557..f54af15dd 100644
--- a/nova/tests/test_db_api.py
+++ b/nova/tests/test_db_api.py
@@ -469,6 +469,33 @@ class DbApiTestCase(test.TestCase):
data = db.network_get_associated_fixed_ips(ctxt, 1, 'nothing')
self.assertEqual(len(data), 0)
+ def test_network_get_all_by_host(self):
+ ctxt = context.get_admin_context()
+ data = db.network_get_all_by_host(ctxt, 'foo')
+ self.assertEqual(len(data), 0)
+ # dummy network
+ net = db.network_create_safe(ctxt, {})
+ # network with host set
+ net = db.network_create_safe(ctxt, {'host': 'foo'})
+ data = db.network_get_all_by_host(ctxt, 'foo')
+ self.assertEqual(len(data), 1)
+ # network with fixed ip with host set
+ net = db.network_create_safe(ctxt, {})
+ values = {'host': 'foo', 'network_id': net['id']}
+ fixed_address = db.fixed_ip_create(ctxt, values)
+ data = db.network_get_all_by_host(ctxt, 'foo')
+ self.assertEqual(len(data), 2)
+ # network with instance with host set
+ net = db.network_create_safe(ctxt, {})
+ instance = db.instance_create(ctxt, {'host': 'foo'})
+ values = {'instance_uuid': instance['uuid']}
+ vif = db.virtual_interface_create(ctxt, values)
+ values = {'network_id': net['id'],
+ 'virtual_interface_id': vif['id']}
+ fixed_address = db.fixed_ip_create(ctxt, values)
+ data = db.network_get_all_by_host(ctxt, 'foo')
+ self.assertEqual(len(data), 3)
+
def _timeout_test(self, ctxt, timeout, multi_host):
values = {'host': 'foo'}
instance = db.instance_create(ctxt, values)