summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorBrian Lamar <brian.lamar@rackspace.com>2011-04-22 12:47:09 -0400
committerBrian Lamar <brian.lamar@rackspace.com>2011-04-22 12:47:09 -0400
commit4eac8d2c4252eb866e99ef260c0c5d7df1d927d2 (patch)
tree65eba7ff857573242e61384fd4fd82d5b5d066c1 /nova
parent8af2a2d720b97ef17565d57a9b8b028d449a9c84 (diff)
downloadnova-4eac8d2c4252eb866e99ef260c0c5d7df1d927d2.tar.gz
nova-4eac8d2c4252eb866e99ef260c0c5d7df1d927d2.tar.xz
nova-4eac8d2c4252eb866e99ef260c0c5d7df1d927d2.zip
Created new libvirt directory, moved libvirt_conn.py to libvirt/connection.py, moved libvirt templates, broke out firewall and network utilities.
Diffstat (limited to 'nova')
-rw-r--r--nova/tests/test_virt.py47
-rw-r--r--nova/virt/connection.py2
-rw-r--r--nova/virt/libvirt/__init__.py0
-rw-r--r--nova/virt/libvirt/connection.py (renamed from nova/virt/libvirt_conn.py)665
-rw-r--r--nova/virt/libvirt/cpuinfo.xml.template (renamed from nova/virt/cpuinfo.xml.template)0
-rw-r--r--nova/virt/libvirt/firewall.py630
-rw-r--r--nova/virt/libvirt/libvirt.xml.template (renamed from nova/virt/libvirt.xml.template)0
-rw-r--r--nova/virt/libvirt/netutils.py95
8 files changed, 762 insertions, 677 deletions
diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
index 0a0c7a958..d770f2c11 100644
--- a/nova/tests/test_virt.py
+++ b/nova/tests/test_virt.py
@@ -34,7 +34,8 @@ from nova.auth import manager
from nova.compute import manager as compute_manager
from nova.compute import power_state
from nova.db.sqlalchemy import models
-from nova.virt import libvirt_conn
+from nova.virt.libvirt import connection
+from nova.virt.libvirt import firewall
libvirt = None
FLAGS = flags.FLAGS
@@ -64,7 +65,7 @@ class CacheConcurrencyTestCase(test.TestCase):
def test_same_fname_concurrency(self):
"""Ensures that the same fname cache runs at a sequentially"""
- conn = libvirt_conn.LibvirtConnection
+ conn = connection.LibvirtConnection
wait1 = eventlet.event.Event()
done1 = eventlet.event.Event()
eventlet.spawn(conn._cache_image, _concurrency,
@@ -85,7 +86,7 @@ class CacheConcurrencyTestCase(test.TestCase):
def test_different_fname_concurrency(self):
"""Ensures that two different fname caches are concurrent"""
- conn = libvirt_conn.LibvirtConnection
+ conn = connection.LibvirtConnection
wait1 = eventlet.event.Event()
done1 = eventlet.event.Event()
eventlet.spawn(conn._cache_image, _concurrency,
@@ -106,7 +107,7 @@ class CacheConcurrencyTestCase(test.TestCase):
class LibvirtConnTestCase(test.TestCase):
def setUp(self):
super(LibvirtConnTestCase, self).setUp()
- libvirt_conn._late_load_cheetah()
+ connection._late_load_cheetah()
self.flags(fake_call=True)
self.manager = manager.AuthManager()
@@ -152,8 +153,8 @@ class LibvirtConnTestCase(test.TestCase):
return False
global libvirt
libvirt = __import__('libvirt')
- libvirt_conn.libvirt = __import__('libvirt')
- libvirt_conn.libxml2 = __import__('libxml2')
+ connection.libvirt = __import__('libvirt')
+ connection.libxml2 = __import__('libxml2')
return True
def create_fake_libvirt_mock(self, **kwargs):
@@ -163,7 +164,7 @@ class LibvirtConnTestCase(test.TestCase):
class FakeLibvirtConnection(object):
pass
- # A fake libvirt_conn.IptablesFirewallDriver
+ # A fake connection.IptablesFirewallDriver
class FakeIptablesFirewallDriver(object):
def __init__(self, **kwargs):
@@ -179,11 +180,11 @@ class LibvirtConnTestCase(test.TestCase):
for key, val in kwargs.items():
fake.__setattr__(key, val)
- # Inevitable mocks for libvirt_conn.LibvirtConnection
- self.mox.StubOutWithMock(libvirt_conn.utils, 'import_class')
- libvirt_conn.utils.import_class(mox.IgnoreArg()).AndReturn(fakeip)
- self.mox.StubOutWithMock(libvirt_conn.LibvirtConnection, '_conn')
- libvirt_conn.LibvirtConnection._conn = fake
+ # Inevitable mocks for connection.LibvirtConnection
+ self.mox.StubOutWithMock(connection.utils, 'import_class')
+ connection.utils.import_class(mox.IgnoreArg()).AndReturn(fakeip)
+ self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn')
+ connection.LibvirtConnection._conn = fake
def create_service(self, **kwargs):
service_ref = {'host': kwargs.get('host', 'dummy'),
@@ -247,7 +248,7 @@ class LibvirtConnTestCase(test.TestCase):
'instance_id': instance_ref['id']})
self.flags(libvirt_type='lxc')
- conn = libvirt_conn.LibvirtConnection(True)
+ conn = connection.LibvirtConnection(True)
uri = conn.get_uri()
self.assertEquals(uri, 'lxc:///')
@@ -359,7 +360,7 @@ class LibvirtConnTestCase(test.TestCase):
for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():
FLAGS.libvirt_type = libvirt_type
- conn = libvirt_conn.LibvirtConnection(True)
+ conn = connection.LibvirtConnection(True)
uri = conn.get_uri()
self.assertEquals(uri, expected_uri)
@@ -386,7 +387,7 @@ class LibvirtConnTestCase(test.TestCase):
FLAGS.libvirt_uri = testuri
for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():
FLAGS.libvirt_type = libvirt_type
- conn = libvirt_conn.LibvirtConnection(True)
+ conn = connection.LibvirtConnection(True)
uri = conn.get_uri()
self.assertEquals(uri, testuri)
db.instance_destroy(user_context, instance_ref['id'])
@@ -410,13 +411,13 @@ class LibvirtConnTestCase(test.TestCase):
self.create_fake_libvirt_mock(getVersion=getVersion,
getType=getType,
listDomainsID=listDomainsID)
- self.mox.StubOutWithMock(libvirt_conn.LibvirtConnection,
+ self.mox.StubOutWithMock(connection.LibvirtConnection,
'get_cpu_info')
- libvirt_conn.LibvirtConnection.get_cpu_info().AndReturn('cpuinfo')
+ connection.LibvirtConnection.get_cpu_info().AndReturn('cpuinfo')
# Start test
self.mox.ReplayAll()
- conn = libvirt_conn.LibvirtConnection(False)
+ conn = connection.LibvirtConnection(False)
conn.update_available_resource(self.context, 'dummy')
service_ref = db.service_get(self.context, service_ref['id'])
compute_node = service_ref['compute_node'][0]
@@ -450,7 +451,7 @@ class LibvirtConnTestCase(test.TestCase):
self.create_fake_libvirt_mock()
self.mox.ReplayAll()
- conn = libvirt_conn.LibvirtConnection(False)
+ conn = connection.LibvirtConnection(False)
self.assertRaises(exception.Invalid,
conn.update_available_resource,
self.context, 'dummy')
@@ -485,7 +486,7 @@ class LibvirtConnTestCase(test.TestCase):
# Start test
self.mox.ReplayAll()
try:
- conn = libvirt_conn.LibvirtConnection(False)
+ conn = connection.LibvirtConnection(False)
conn.firewall_driver.setattr('setup_basic_filtering', fake_none)
conn.firewall_driver.setattr('prepare_instance_filter', fake_none)
conn.firewall_driver.setattr('instance_filter_exists', fake_none)
@@ -534,7 +535,7 @@ class LibvirtConnTestCase(test.TestCase):
# Start test
self.mox.ReplayAll()
- conn = libvirt_conn.LibvirtConnection(False)
+ conn = connection.LibvirtConnection(False)
self.assertRaises(libvirt.libvirtError,
conn._live_migration,
self.context, instance_ref, 'dest', '',
@@ -569,7 +570,7 @@ class IptablesFirewallTestCase(test.TestCase):
class FakeLibvirtConnection(object):
pass
self.fake_libvirt_connection = FakeLibvirtConnection()
- self.fw = libvirt_conn.IptablesFirewallDriver(
+ self.fw = firewall.IptablesFirewallDriver(
get_connection=lambda: self.fake_libvirt_connection)
def tearDown(self):
@@ -746,7 +747,7 @@ class NWFilterTestCase(test.TestCase):
self.fake_libvirt_connection = Mock()
- self.fw = libvirt_conn.NWFilterFirewall(
+ self.fw = firewall.NWFilterFirewall(
lambda: self.fake_libvirt_connection)
def tearDown(self):
diff --git a/nova/virt/connection.py b/nova/virt/connection.py
index 99a8849f1..aeec17c98 100644
--- a/nova/virt/connection.py
+++ b/nova/virt/connection.py
@@ -27,9 +27,9 @@ from nova import utils
from nova.virt import driver
from nova.virt import fake
from nova.virt import hyperv
-from nova.virt import libvirt_conn
from nova.virt import vmwareapi_conn
from nova.virt import xenapi_conn
+from nova.virt.libvirt import connection as libvirt_conn
LOG = logging.getLogger("nova.virt.connection")
diff --git a/nova/virt/libvirt/__init__.py b/nova/virt/libvirt/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/nova/virt/libvirt/__init__.py
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt/connection.py
index e76de47db..972ac1bb9 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt/connection.py
@@ -66,22 +66,25 @@ from nova.compute import power_state
from nova.virt import disk
from nova.virt import driver
from nova.virt import images
+from nova.virt.libvirt import netutils
+
libvirt = None
libxml2 = None
Template = None
+
LOG = logging.getLogger('nova.virt.libvirt_conn')
+
FLAGS = flags.FLAGS
flags.DECLARE('live_migration_retry_count', 'nova.compute.manager')
# TODO(vish): These flags should probably go into a shared location
flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image')
flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image')
flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image')
-
flags.DEFINE_string('libvirt_xml_template',
- utils.abspath('virt/libvirt.xml.template'),
+ utils.abspath('virt/libvirt/libvirt.xml.template'),
'Libvirt XML Template')
flags.DEFINE_string('libvirt_type',
'kvm',
@@ -101,10 +104,10 @@ flags.DEFINE_string('ajaxterm_portrange',
'10000-12000',
'Range of ports that ajaxterm should randomly try to bind')
flags.DEFINE_string('firewall_driver',
- 'nova.virt.libvirt_conn.IptablesFirewallDriver',
+ 'nova.virt.libvirt.firewall.IptablesFirewallDriver',
'Firewall driver (defaults to iptables)')
flags.DEFINE_string('cpuinfo_xml_template',
- utils.abspath('virt/cpuinfo.xml.template'),
+ utils.abspath('virt/libvirt/cpuinfo.xml.template'),
'CpuInfo XML Template (Used only live migration now)')
flags.DEFINE_string('live_migration_uri',
"qemu+tcp://%s/system",
@@ -143,69 +146,6 @@ def _late_load_cheetah():
Template = t.Template
-def _get_net_and_mask(cidr):
- net = IPy.IP(cidr)
- return str(net.net()), str(net.netmask())
-
-
-def _get_net_and_prefixlen(cidr):
- net = IPy.IP(cidr)
- return str(net.net()), str(net.prefixlen())
-
-
-def _get_ip_version(cidr):
- net = IPy.IP(cidr)
- return int(net.version())
-
-
-def _get_network_info(instance):
- # TODO(adiantum) If we will keep this function
- # we should cache network_info
- admin_context = context.get_admin_context()
-
- ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
- instance['id'])
- networks = db.network_get_all_by_instance(admin_context,
- instance['id'])
- flavor = db.instance_type_get_by_id(admin_context,
- instance['instance_type_id'])
- network_info = []
-
- for network in networks:
- network_ips = [ip for ip in ip_addresses
- if ip['network_id'] == network['id']]
-
- def ip_dict(ip):
- return {
- 'ip': ip['address'],
- 'netmask': network['netmask'],
- 'enabled': '1'}
-
- def ip6_dict():
- prefix = network['cidr_v6']
- mac = instance['mac_address']
- return {
- 'ip': utils.to_global_ipv6(prefix, mac),
- 'netmask': network['netmask_v6'],
- 'enabled': '1'}
-
- mapping = {
- 'label': network['label'],
- 'gateway': network['gateway'],
- 'broadcast': network['broadcast'],
- 'mac': instance['mac_address'],
- 'rxtx_cap': flavor['rxtx_cap'],
- 'dns': [network['dns']],
- 'ips': [ip_dict(ip) for ip in network_ips]}
-
- if FLAGS.use_ipv6:
- mapping['ip6s'] = [ip6_dict()]
- mapping['gateway6'] = network['gateway_v6']
-
- network_info.append((network, mapping))
- return network_info
-
-
class LibvirtConnection(driver.ComputeDriver):
def __init__(self, read_only):
@@ -795,7 +735,7 @@ class LibvirtConnection(driver.ComputeDriver):
def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None,
network_info=None):
if not network_info:
- network_info = _get_network_info(inst)
+ network_info = netutils.get_network_info(inst)
if not suffix:
suffix = ''
@@ -954,8 +894,8 @@ class LibvirtConnection(driver.ComputeDriver):
if FLAGS.allow_project_net_traffic:
if FLAGS.use_ipv6:
- net, mask = _get_net_and_mask(network['cidr'])
- net_v6, prefixlen_v6 = _get_net_and_prefixlen(
+ net, mask = netutils.get_net_and_mask(network['cidr'])
+ net_v6, prefixlen_v6 = netutils.get_net_and_prefixlen(
network['cidr_v6'])
extra_params = ("<parameter name=\"PROJNET\" "
"value=\"%s\" />\n"
@@ -967,7 +907,7 @@ class LibvirtConnection(driver.ComputeDriver):
"value=\"%s\" />\n") % \
(net, mask, net_v6, prefixlen_v6)
else:
- net, mask = _get_net_and_mask(network['cidr'])
+ net, mask = netutils.get_net_and_mask(network['cidr'])
extra_params = ("<parameter name=\"PROJNET\" "
"value=\"%s\" />\n"
"<parameter name=\"PROJMASK\" "
@@ -997,7 +937,7 @@ class LibvirtConnection(driver.ComputeDriver):
# TODO(adiantum) remove network_info creation code
# when multinics will be completed
if not network_info:
- network_info = _get_network_info(instance)
+ network_info = netutils.get_network_info(instance)
nics = []
for (network, mapping) in network_info:
@@ -1585,584 +1525,3 @@ class LibvirtConnection(driver.ComputeDriver):
def unfilter_instance(self, instance_ref):
"""See comments of same method in firewall_driver."""
self.firewall_driver.unfilter_instance(instance_ref)
-
-
-class FirewallDriver(object):
- def prepare_instance_filter(self, instance, network_info=None):
- """Prepare filters for the instance.
-
- At this point, the instance isn't running yet."""
- raise NotImplementedError()
-
- def unfilter_instance(self, instance):
- """Stop filtering instance"""
- raise NotImplementedError()
-
- def apply_instance_filter(self, instance):
- """Apply instance filter.
-
- Once this method returns, the instance should be firewalled
- appropriately. This method should as far as possible be a
- no-op. It's vastly preferred to get everything set up in
- prepare_instance_filter.
- """
- raise NotImplementedError()
-
- def refresh_security_group_rules(self, security_group_id):
- """Refresh security group rules from data store
-
- Gets called when a rule has been added to or removed from
- the security group."""
- raise NotImplementedError()
-
- def refresh_security_group_members(self, security_group_id):
- """Refresh security group members from data store
-
- Gets called when an instance gets added to or removed from
- the security group."""
- raise NotImplementedError()
-
- def setup_basic_filtering(self, instance, network_info=None):
- """Create rules to block spoofing and allow dhcp.
-
- This gets called when spawning an instance, before
- :method:`prepare_instance_filter`.
-
- """
- raise NotImplementedError()
-
- def instance_filter_exists(self, instance):
- """Check nova-instance-instance-xxx exists"""
- raise NotImplementedError()
-
-
-class NWFilterFirewall(FirewallDriver):
- """
- This class implements a network filtering mechanism versatile
- enough for EC2 style Security Group filtering by leveraging
- libvirt's nwfilter.
-
- First, all instances get a filter ("nova-base-filter") applied.
- This filter provides some basic security such as protection against
- MAC spoofing, IP spoofing, and ARP spoofing.
-
- This filter drops all incoming ipv4 and ipv6 connections.
- Outgoing connections are never blocked.
-
- Second, every security group maps to a nwfilter filter(*).
- NWFilters can be updated at runtime and changes are applied
- immediately, so changes to security groups can be applied at
- runtime (as mandated by the spec).
-
- Security group rules are named "nova-secgroup-<id>" where <id>
- is the internal id of the security group. They're applied only on
- hosts that have instances in the security group in question.
-
- Updates to security groups are done by updating the data model
- (in response to API calls) followed by a request sent to all
- the nodes with instances in the security group to refresh the
- security group.
-
- Each instance has its own NWFilter, which references the above
- mentioned security group NWFilters. This was done because
- interfaces can only reference one filter while filters can
- reference multiple other filters. This has the added benefit of
- actually being able to add and remove security groups from an
- instance at run time. This functionality is not exposed anywhere,
- though.
-
- Outstanding questions:
-
- The name is unique, so would there be any good reason to sync
- the uuid across the nodes (by assigning it from the datamodel)?
-
-
- (*) This sentence brought to you by the redundancy department of
- redundancy.
-
- """
-
- def __init__(self, get_connection, **kwargs):
- self._libvirt_get_connection = get_connection
- self.static_filters_configured = False
- self.handle_security_groups = False
-
- def apply_instance_filter(self, instance):
- """No-op. Everything is done in prepare_instance_filter"""
- pass
-
- def _get_connection(self):
- return self._libvirt_get_connection()
- _conn = property(_get_connection)
-
- def nova_dhcp_filter(self):
- """The standard allow-dhcp-server filter is an <ip> one, so it uses
- ebtables to allow traffic through. Without a corresponding rule in
- iptables, it'll get blocked anyway."""
-
- return '''<filter name='nova-allow-dhcp-server' chain='ipv4'>
- <uuid>891e4787-e5c0-d59b-cbd6-41bc3c6b36fc</uuid>
- <rule action='accept' direction='out'
- priority='100'>
- <udp srcipaddr='0.0.0.0'
- dstipaddr='255.255.255.255'
- srcportstart='68'
- dstportstart='67'/>
- </rule>
- <rule action='accept' direction='in'
- priority='100'>
- <udp srcipaddr='$DHCPSERVER'
- srcportstart='67'
- dstportstart='68'/>
- </rule>
- </filter>'''
-
- def nova_ra_filter(self):
- return '''<filter name='nova-allow-ra-server' chain='root'>
- <uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
- <rule action='accept' direction='inout'
- priority='100'>
- <icmpv6 srcipaddr='$RASERVER'/>
- </rule>
- </filter>'''
-
- def setup_basic_filtering(self, instance, network_info=None):
- """Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
- logging.info('called setup_basic_filtering in nwfilter')
-
- if not network_info:
- network_info = _get_network_info(instance)
-
- if self.handle_security_groups:
- # No point in setting up a filter set that we'll be overriding
- # anyway.
- return
-
- logging.info('ensuring static filters')
- self._ensure_static_filters()
-
- if instance['image_id'] == str(FLAGS.vpn_image_id):
- base_filter = 'nova-vpn'
- else:
- base_filter = 'nova-base'
-
- for (network, mapping) in network_info:
- nic_id = mapping['mac'].replace(':', '')
- instance_filter_name = self._instance_filter_name(instance, nic_id)
- self._define_filter(self._filter_container(instance_filter_name,
- [base_filter]))
-
- def _ensure_static_filters(self):
- if self.static_filters_configured:
- return
-
- self._define_filter(self._filter_container('nova-base',
- ['no-mac-spoofing',
- 'no-ip-spoofing',
- 'no-arp-spoofing',
- 'allow-dhcp-server']))
- self._define_filter(self._filter_container('nova-vpn',
- ['allow-dhcp-server']))
- self._define_filter(self.nova_base_ipv4_filter)
- self._define_filter(self.nova_base_ipv6_filter)
- self._define_filter(self.nova_dhcp_filter)
- self._define_filter(self.nova_ra_filter)
- if FLAGS.allow_project_net_traffic:
- self._define_filter(self.nova_project_filter)
- if FLAGS.use_ipv6:
- self._define_filter(self.nova_project_filter_v6)
-
- self.static_filters_configured = True
-
- def _filter_container(self, name, filters):
- xml = '''<filter name='%s' chain='root'>%s</filter>''' % (
- name,
- ''.join(["<filterref filter='%s'/>" % (f,) for f in filters]))
- return xml
-
- def nova_base_ipv4_filter(self):
- retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
- for protocol in ['tcp', 'udp', 'icmp']:
- for direction, action, priority in [('out', 'accept', 399),
- ('in', 'drop', 400)]:
- retval += """<rule action='%s' direction='%s' priority='%d'>
- <%s />
- </rule>""" % (action, direction,
- priority, protocol)
- retval += '</filter>'
- return retval
-
- def nova_base_ipv6_filter(self):
- retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
- for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
- for direction, action, priority in [('out', 'accept', 399),
- ('in', 'drop', 400)]:
- retval += """<rule action='%s' direction='%s' priority='%d'>
- <%s />
- </rule>""" % (action, direction,
- priority, protocol)
- retval += '</filter>'
- return retval
-
- def nova_project_filter(self):
- retval = "<filter name='nova-project' chain='ipv4'>"
- for protocol in ['tcp', 'udp', 'icmp']:
- retval += """<rule action='accept' direction='in' priority='200'>
- <%s srcipaddr='$PROJNET' srcipmask='$PROJMASK' />
- </rule>""" % protocol
- retval += '</filter>'
- return retval
-
- def nova_project_filter_v6(self):
- retval = "<filter name='nova-project-v6' chain='ipv6'>"
- for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
- retval += """<rule action='accept' direction='inout'
- priority='200'>
- <%s srcipaddr='$PROJNETV6'
- srcipmask='$PROJMASKV6' />
- </rule>""" % (protocol)
- retval += '</filter>'
- return retval
-
- def _define_filter(self, xml):
- if callable(xml):
- xml = xml()
- # execute in a native thread and block current greenthread until done
- tpool.execute(self._conn.nwfilterDefineXML, xml)
-
- def unfilter_instance(self, instance):
- # Nothing to do
- pass
-
- def prepare_instance_filter(self, instance, network_info=None):
- """
- Creates an NWFilter for the given instance. In the process,
- it makes sure the filters for the security groups as well as
- the base filter are all in place.
- """
- if not network_info:
- network_info = _get_network_info(instance)
- if instance['image_id'] == str(FLAGS.vpn_image_id):
- base_filter = 'nova-vpn'
- else:
- base_filter = 'nova-base'
-
- ctxt = context.get_admin_context()
-
- instance_secgroup_filter_name = \
- '%s-secgroup' % (self._instance_filter_name(instance))
- #% (instance_filter_name,)
-
- instance_secgroup_filter_children = ['nova-base-ipv4',
- 'nova-base-ipv6',
- 'nova-allow-dhcp-server']
-
- for security_group in \
- db.security_group_get_by_instance(ctxt, instance['id']):
-
- self.refresh_security_group_rules(security_group['id'])
-
- instance_secgroup_filter_children += [('nova-secgroup-%s' %
- security_group['id'])]
-
- self._define_filter(
- self._filter_container(instance_secgroup_filter_name,
- instance_secgroup_filter_children))
-
- for (network, mapping) in network_info:
- nic_id = mapping['mac'].replace(':', '')
- instance_filter_name = self._instance_filter_name(instance, nic_id)
- instance_filter_children = \
- [base_filter, instance_secgroup_filter_name]
-
- if FLAGS.use_ipv6:
- gateway_v6 = network['gateway_v6']
-
- if gateway_v6:
- instance_secgroup_filter_children += \
- ['nova-allow-ra-server']
-
- if FLAGS.allow_project_net_traffic:
- instance_filter_children += ['nova-project']
- if FLAGS.use_ipv6:
- instance_filter_children += ['nova-project-v6']
-
- self._define_filter(
- self._filter_container(instance_filter_name,
- instance_filter_children))
-
- return
-
- def refresh_security_group_rules(self, security_group_id):
- return self._define_filter(
- self.security_group_to_nwfilter_xml(security_group_id))
-
- def security_group_to_nwfilter_xml(self, security_group_id):
- security_group = db.security_group_get(context.get_admin_context(),
- security_group_id)
- rule_xml = ""
- v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
- for rule in security_group.rules:
- rule_xml += "<rule action='accept' direction='in' priority='300'>"
- if rule.cidr:
- version = _get_ip_version(rule.cidr)
- if(FLAGS.use_ipv6 and version == 6):
- net, prefixlen = _get_net_and_prefixlen(rule.cidr)
- rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
- (v6protocol[rule.protocol], net, prefixlen)
- else:
- net, mask = _get_net_and_mask(rule.cidr)
- rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
- (rule.protocol, net, mask)
- if rule.protocol in ['tcp', 'udp']:
- rule_xml += "dstportstart='%s' dstportend='%s' " % \
- (rule.from_port, rule.to_port)
- elif rule.protocol == 'icmp':
- LOG.info('rule.protocol: %r, rule.from_port: %r, '
- 'rule.to_port: %r', rule.protocol,
- rule.from_port, rule.to_port)
- if rule.from_port != -1:
- rule_xml += "type='%s' " % rule.from_port
- if rule.to_port != -1:
- rule_xml += "code='%s' " % rule.to_port
-
- rule_xml += '/>\n'
- rule_xml += "</rule>\n"
- xml = "<filter name='nova-secgroup-%s' " % security_group_id
- if(FLAGS.use_ipv6):
- xml += "chain='root'>%s</filter>" % rule_xml
- else:
- xml += "chain='ipv4'>%s</filter>" % rule_xml
- return xml
-
- def _instance_filter_name(self, instance, nic_id=None):
- if not nic_id:
- return 'nova-instance-%s' % (instance['name'])
- return 'nova-instance-%s-%s' % (instance['name'], nic_id)
-
- def instance_filter_exists(self, instance):
- """Check nova-instance-instance-xxx exists"""
- network_info = _get_network_info(instance)
- for (network, mapping) in network_info:
- nic_id = mapping['mac'].replace(':', '')
- instance_filter_name = self._instance_filter_name(instance, nic_id)
- try:
- self._conn.nwfilterLookupByName(instance_filter_name)
- except libvirt.libvirtError:
- name = instance.name
- LOG.debug(_('The nwfilter(%(instance_filter_name)s) for'
- '%(name)s is not found.') % locals())
- return False
- return True
-
-
-class IptablesFirewallDriver(FirewallDriver):
- def __init__(self, execute=None, **kwargs):
- from nova.network import linux_net
- self.iptables = linux_net.iptables_manager
- self.instances = {}
- self.nwfilter = NWFilterFirewall(kwargs['get_connection'])
-
- self.iptables.ipv4['filter'].add_chain('sg-fallback')
- self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
- self.iptables.ipv6['filter'].add_chain('sg-fallback')
- self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
-
- def setup_basic_filtering(self, instance, network_info=None):
- """Use NWFilter from libvirt for this."""
- if not network_info:
- network_info = _get_network_info(instance)
- return self.nwfilter.setup_basic_filtering(instance, network_info)
-
- def apply_instance_filter(self, instance):
- """No-op. Everything is done in prepare_instance_filter"""
- pass
-
- def unfilter_instance(self, instance):
- if self.instances.pop(instance['id'], None):
- self.remove_filters_for_instance(instance)
- self.iptables.apply()
- else:
- LOG.info(_('Attempted to unfilter instance %s which is not '
- 'filtered'), instance['id'])
-
- def prepare_instance_filter(self, instance, network_info=None):
- if not network_info:
- network_info = _get_network_info(instance)
- self.instances[instance['id']] = instance
- self.add_filters_for_instance(instance, network_info)
- self.iptables.apply()
-
- def add_filters_for_instance(self, instance, network_info=None):
- if not network_info:
- network_info = _get_network_info(instance)
- chain_name = self._instance_chain_name(instance)
-
- self.iptables.ipv4['filter'].add_chain(chain_name)
-
- ips_v4 = [ip['ip'] for (_, mapping) in network_info
- for ip in mapping['ips']]
-
- for ipv4_address in ips_v4:
- self.iptables.ipv4['filter'].add_rule('local',
- '-d %s -j $%s' %
- (ipv4_address, chain_name))
-
- if FLAGS.use_ipv6:
- self.iptables.ipv6['filter'].add_chain(chain_name)
- ips_v6 = [ip['ip'] for (_, mapping) in network_info
- for ip in mapping['ip6s']]
-
- for ipv6_address in ips_v6:
- self.iptables.ipv6['filter'].add_rule('local',
- '-d %s -j $%s' %
- (ipv6_address,
- chain_name))
-
- ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
-
- for rule in ipv4_rules:
- self.iptables.ipv4['filter'].add_rule(chain_name, rule)
-
- if FLAGS.use_ipv6:
- for rule in ipv6_rules:
- self.iptables.ipv6['filter'].add_rule(chain_name, rule)
-
- def remove_filters_for_instance(self, instance):
- chain_name = self._instance_chain_name(instance)
-
- self.iptables.ipv4['filter'].remove_chain(chain_name)
- if FLAGS.use_ipv6:
- self.iptables.ipv6['filter'].remove_chain(chain_name)
-
- def instance_rules(self, instance, network_info=None):
- if not network_info:
- network_info = _get_network_info(instance)
- ctxt = context.get_admin_context()
-
- ipv4_rules = []
- ipv6_rules = []
-
- # Always drop invalid packets
- ipv4_rules += ['-m state --state ' 'INVALID -j DROP']
- ipv6_rules += ['-m state --state ' 'INVALID -j DROP']
-
- # Allow established connections
- ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
- ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
-
- dhcp_servers = [network['gateway'] for (network, _m) in network_info]
-
- for dhcp_server in dhcp_servers:
- ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '
- '-j ACCEPT' % (dhcp_server,))
-
- #Allow project network traffic
- if FLAGS.allow_project_net_traffic:
- cidrs = [network['cidr'] for (network, _m) in network_info]
- for cidr in cidrs:
- ipv4_rules.append('-s %s -j ACCEPT' % (cidr,))
-
- # We wrap these in FLAGS.use_ipv6 because they might cause
- # a DB lookup. The other ones are just list operations, so
- # they're not worth the clutter.
- if FLAGS.use_ipv6:
- # Allow RA responses
- gateways_v6 = [network['gateway_v6'] for (network, _) in
- network_info]
- for gateway_v6 in gateways_v6:
- ipv6_rules.append(
- '-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,))
-
- #Allow project network traffic
- if FLAGS.allow_project_net_traffic:
- cidrv6s = [network['cidr_v6'] for (network, _m)
- in network_info]
-
- for cidrv6 in cidrv6s:
- ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))
-
- security_groups = db.security_group_get_by_instance(ctxt,
- instance['id'])
-
- # then, security group chains and rules
- for security_group in security_groups:
- rules = db.security_group_rule_get_by_security_group(ctxt,
- security_group['id'])
-
- for rule in rules:
- logging.info('%r', rule)
-
- if not rule.cidr:
- # Eventually, a mechanism to grant access for security
- # groups will turn up here. It'll use ipsets.
- continue
-
- version = _get_ip_version(rule.cidr)
- if version == 4:
- rules = ipv4_rules
- else:
- rules = ipv6_rules
-
- protocol = rule.protocol
- if version == 6 and rule.protocol == 'icmp':
- protocol = 'icmpv6'
-
- args = ['-p', protocol, '-s', rule.cidr]
-
- if rule.protocol in ['udp', 'tcp']:
- if rule.from_port == rule.to_port:
- args += ['--dport', '%s' % (rule.from_port,)]
- else:
- args += ['-m', 'multiport',
- '--dports', '%s:%s' % (rule.from_port,
- rule.to_port)]
- elif rule.protocol == 'icmp':
- icmp_type = rule.from_port
- icmp_code = rule.to_port
-
- if icmp_type == -1:
- icmp_type_arg = None
- else:
- icmp_type_arg = '%s' % icmp_type
- if not icmp_code == -1:
- icmp_type_arg += '/%s' % icmp_code
-
- if icmp_type_arg:
- if version == 4:
- args += ['-m', 'icmp', '--icmp-type',
- icmp_type_arg]
- elif version == 6:
- args += ['-m', 'icmp6', '--icmpv6-type',
- icmp_type_arg]
-
- args += ['-j ACCEPT']
- rules += [' '.join(args)]
-
- ipv4_rules += ['-j $sg-fallback']
- ipv6_rules += ['-j $sg-fallback']
-
- return ipv4_rules, ipv6_rules
-
- def instance_filter_exists(self, instance):
- """Check nova-instance-instance-xxx exists"""
- return self.nwfilter.instance_filter_exists(instance)
-
- def refresh_security_group_members(self, security_group):
- pass
-
- def refresh_security_group_rules(self, security_group):
- self.do_refresh_security_group_rules(security_group)
- self.iptables.apply()
-
- @utils.synchronized('iptables', external=True)
- def do_refresh_security_group_rules(self, security_group):
- for instance in self.instances.values():
- self.remove_filters_for_instance(instance)
- self.add_filters_for_instance(instance)
-
- def _security_group_chain_name(self, security_group_id):
- return 'nova-sg-%s' % (security_group_id,)
-
- def _instance_chain_name(self, instance):
- return 'inst-%s' % (instance['id'],)
diff --git a/nova/virt/cpuinfo.xml.template b/nova/virt/libvirt/cpuinfo.xml.template
index 48842b29d..48842b29d 100644
--- a/nova/virt/cpuinfo.xml.template
+++ b/nova/virt/libvirt/cpuinfo.xml.template
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
new file mode 100644
index 000000000..99ba02aaa
--- /dev/null
+++ b/nova/virt/libvirt/firewall.py
@@ -0,0 +1,630 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+# Copyright (c) 2010 Citrix Systems, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from eventlet import tpool
+
+from nova import context
+from nova import db
+from nova import flags
+from nova import log as logging
+from nova import utils
+from nova.virt.libvirt import netutils
+
+
+LOG = logging.getLogger("nova.virt.libvirt.firewall")
+FLAGS = flags.FLAGS
+
+
+try:
+ import libvirt
+except ImportError:
+ LOG.warn(_("Libvirt module could not be loaded. NWFilterFirewall will "
+ "not work correctly."))
+
+
+class FirewallDriver(object):
+
+ def prepare_instance_filter(self, instance, network_info=None):
+ """Prepare filters for the instance.
+
+ At this point, the instance isn't running yet.
+
+ """
+ raise NotImplementedError()
+
+ def unfilter_instance(self, instance):
+ """Stop filtering instance."""
+ raise NotImplementedError()
+
+ def apply_instance_filter(self, instance):
+ """Apply instance filter.
+
+ Once this method returns, the instance should be firewalled
+ appropriately. This method should as far as possible be a
+ no-op. It's vastly preferred to get everything set up in
+ prepare_instance_filter.
+
+ """
+ raise NotImplementedError()
+
+ def refresh_security_group_rules(self, security_group_id):
+ """Refresh security group rules from data store.
+
+ Gets called when a rule has been added to or removed from
+ the security group.
+
+ """
+ raise NotImplementedError()
+
+ def refresh_security_group_members(self, security_group_id):
+ """Refresh security group members from data store.
+
+ Gets called when an instance gets added to or removed from
+ the security group.
+
+ """
+ raise NotImplementedError()
+
+ def setup_basic_filtering(self, instance, network_info=None):
+ """Create rules to block spoofing and allow dhcp.
+
+ This gets called when spawning an instance, before
+ :method:`prepare_instance_filter`.
+
+ """
+ raise NotImplementedError()
+
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists."""
+ raise NotImplementedError()
+
+
+class NWFilterFirewall(FirewallDriver):
+ """Network filter firewall implementation.
+
+ This class implements a network filtering mechanism versatile
+ enough for EC2 style Security Group filtering by leveraging
+ libvirt's nwfilter.
+
+ First, all instances get a filter ("nova-base-filter") applied.
+ This filter provides some basic security such as protection against
+ MAC spoofing, IP spoofing, and ARP spoofing.
+
+ This filter drops all incoming ipv4 and ipv6 connections.
+ Outgoing connections are never blocked.
+
+ Second, every security group maps to a nwfilter filter(*).
+ NWFilters can be updated at runtime and changes are applied
+ immediately, so changes to security groups can be applied at
+ runtime (as mandated by the spec).
+
+ Security group rules are named "nova-secgroup-<id>" where <id>
+ is the internal id of the security group. They're applied only on
+ hosts that have instances in the security group in question.
+
+ Updates to security groups are done by updating the data model
+ (in response to API calls) followed by a request sent to all
+ the nodes with instances in the security group to refresh the
+ security group.
+
+ Each instance has its own NWFilter, which references the above
+ mentioned security group NWFilters. This was done because
+ interfaces can only reference one filter while filters can
+ reference multiple other filters. This has the added benefit of
+ actually being able to add and remove security groups from an
+ instance at run time. This functionality is not exposed anywhere,
+ though.
+
+ Outstanding questions:
+
+ The name is unique, so would there be any good reason to sync
+ the uuid across the nodes (by assigning it from the datamodel)?
+
+
+ (*) This sentence brought to you by the redundancy department of
+ redundancy.
+
+ """
+
+ def __init__(self, get_connection, **kwargs):
+ self._libvirt_get_connection = get_connection
+ self.static_filters_configured = False
+ self.handle_security_groups = False
+
+ def apply_instance_filter(self, instance):
+ pass
+
+ def _get_connection(self):
+ return self._libvirt_get_connection()
+
+ _conn = property(_get_connection)
+
+ def nova_dhcp_filter(self):
+ """Defines nova DHCP filter.
+
+ The standard allow-dhcp-server filter is an <ip> one, so it uses
+ ebtables to allow traffic through. Without a corresponding rule in
+ iptables, it'll get blocked anyway.
+
+ """
+ return '''<filter name='nova-allow-dhcp-server' chain='ipv4'>
+ <uuid>891e4787-e5c0-d59b-cbd6-41bc3c6b36fc</uuid>
+ <rule action='accept' direction='out' priority='100'>
+ <udp srcipaddr='0.0.0.0'
+ dstipaddr='255.255.255.255'
+ srcportstart='68'
+ dstportstart='67'/>
+ </rule>
+ <rule action='accept' direction='in' priority='100'>
+ <udp srcipaddr='$DHCPSERVER'
+ srcportstart='67'
+ dstportstart='68'/>
+ </rule>
+ </filter>'''
+
+ def nova_ra_filter(self):
+ return '''<filter name='nova-allow-ra-server' chain='root'>
+ <uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
+ <rule action='accept' direction='inout' priority='100'>
+ <icmpv6 srcipaddr='$RASERVER'/>
+ </rule>
+ </filter>'''
+
+ def setup_basic_filtering(self, instance, network_info=None):
+ """Set up basic filtering (MAC, IP, and ARP spoofing protection)."""
+ logging.info('called setup_basic_filtering in nwfilter')
+
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
+
+ if self.handle_security_groups:
+ # No point in setting up a filter set that we'll be overriding
+ # anyway.
+ return
+
+ logging.info('ensuring static filters')
+ self._ensure_static_filters()
+
+ if instance['image_id'] == str(FLAGS.vpn_image_id):
+ base_filter = 'nova-vpn'
+ else:
+ base_filter = 'nova-base'
+
+ for (network, mapping) in network_info:
+ nic_id = mapping['mac'].replace(':', '')
+ instance_filter_name = self._instance_filter_name(instance, nic_id)
+ self._define_filter(self._filter_container(instance_filter_name,
+ [base_filter]))
+
+ def _ensure_static_filters(self):
+ if self.static_filters_configured:
+ return
+
+ self._define_filter(self._filter_container('nova-base',
+ ['no-mac-spoofing',
+ 'no-ip-spoofing',
+ 'no-arp-spoofing',
+ 'allow-dhcp-server']))
+ self._define_filter(self._filter_container('nova-vpn',
+ ['allow-dhcp-server']))
+ self._define_filter(self.nova_base_ipv4_filter)
+ self._define_filter(self.nova_base_ipv6_filter)
+ self._define_filter(self.nova_dhcp_filter)
+ self._define_filter(self.nova_ra_filter)
+ if FLAGS.allow_project_net_traffic:
+ self._define_filter(self.nova_project_filter)
+ if FLAGS.use_ipv6:
+ self._define_filter(self.nova_project_filter_v6)
+
+ self.static_filters_configured = True
+
+ def _filter_container(self, name, filters):
+ xml = '''<filter name='%s' chain='root'>%s</filter>''' % (
+ name,
+ ''.join(["<filterref filter='%s'/>" % (f,) for f in filters]))
+ return xml
+
+ def nova_base_ipv4_filter(self):
+ retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
+ for protocol in ['tcp', 'udp', 'icmp']:
+ for direction, action, priority in [('out', 'accept', 399),
+ ('in', 'drop', 400)]:
+ retval += """<rule action='%s' direction='%s' priority='%d'>
+ <%s />
+ </rule>""" % (action, direction,
+ priority, protocol)
+ retval += '</filter>'
+ return retval
+
+ def nova_base_ipv6_filter(self):
+ retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
+ for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
+ for direction, action, priority in [('out', 'accept', 399),
+ ('in', 'drop', 400)]:
+ retval += """<rule action='%s' direction='%s' priority='%d'>
+ <%s />
+ </rule>""" % (action, direction,
+ priority, protocol)
+ retval += '</filter>'
+ return retval
+
+ def nova_project_filter(self):
+ retval = "<filter name='nova-project' chain='ipv4'>"
+ for protocol in ['tcp', 'udp', 'icmp']:
+ retval += """<rule action='accept' direction='in' priority='200'>
+ <%s srcipaddr='$PROJNET' srcipmask='$PROJMASK' />
+ </rule>""" % protocol
+ retval += '</filter>'
+ return retval
+
+ def nova_project_filter_v6(self):
+ retval = "<filter name='nova-project-v6' chain='ipv6'>"
+ for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
+ retval += """<rule action='accept' direction='inout'
+ priority='200'>
+ <%s srcipaddr='$PROJNETV6'
+ srcipmask='$PROJMASKV6' />
+ </rule>""" % (protocol)
+ retval += '</filter>'
+ return retval
+
+ def _define_filter(self, xml):
+ if callable(xml):
+ xml = xml()
+ # execute in a native thread and block current greenthread until done
+ tpool.execute(self._conn.nwfilterDefineXML, xml)
+
+ def unfilter_instance(self, instance):
+ # Nothing to do
+ pass
+
+ def prepare_instance_filter(self, instance, network_info=None):
+ """Creates an NWFilter for the given instance.
+
+ In the process, it makes sure the filters for the security groups as
+ well as the base filter are all in place.
+
+ """
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
+ if instance['image_id'] == str(FLAGS.vpn_image_id):
+ base_filter = 'nova-vpn'
+ else:
+ base_filter = 'nova-base'
+
+ ctxt = context.get_admin_context()
+
+ instance_secgroup_filter_name = \
+ '%s-secgroup' % (self._instance_filter_name(instance))
+ #% (instance_filter_name,)
+
+ instance_secgroup_filter_children = ['nova-base-ipv4',
+ 'nova-base-ipv6',
+ 'nova-allow-dhcp-server']
+
+ for security_group in \
+ db.security_group_get_by_instance(ctxt, instance['id']):
+
+ self.refresh_security_group_rules(security_group['id'])
+
+ instance_secgroup_filter_children += [('nova-secgroup-%s' %
+ security_group['id'])]
+
+ self._define_filter(
+ self._filter_container(instance_secgroup_filter_name,
+ instance_secgroup_filter_children))
+
+ for (network, mapping) in network_info:
+ nic_id = mapping['mac'].replace(':', '')
+ instance_filter_name = self._instance_filter_name(instance, nic_id)
+ instance_filter_children = \
+ [base_filter, instance_secgroup_filter_name]
+
+ if FLAGS.use_ipv6:
+ gateway_v6 = network['gateway_v6']
+
+ if gateway_v6:
+ instance_secgroup_filter_children += \
+ ['nova-allow-ra-server']
+
+ if FLAGS.allow_project_net_traffic:
+ instance_filter_children += ['nova-project']
+ if FLAGS.use_ipv6:
+ instance_filter_children += ['nova-project-v6']
+
+ self._define_filter(
+ self._filter_container(instance_filter_name,
+ instance_filter_children))
+
+ return
+
+ def refresh_security_group_rules(self, security_group_id):
+ return self._define_filter(
+ self.security_group_to_nwfilter_xml(security_group_id))
+
+ def security_group_to_nwfilter_xml(self, security_group_id):
+ security_group = db.security_group_get(context.get_admin_context(),
+ security_group_id)
+ rule_xml = ""
+ v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
+ for rule in security_group.rules:
+ rule_xml += "<rule action='accept' direction='in' priority='300'>"
+ if rule.cidr:
+ version = netutils.get_ip_version(rule.cidr)
+ if(FLAGS.use_ipv6 and version == 6):
+ net, prefixlen = netutils.get_net_and_prefixlen(rule.cidr)
+ rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
+ (v6protocol[rule.protocol], net, prefixlen)
+ else:
+ net, mask = netutils.get_net_and_mask(rule.cidr)
+ rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
+ (rule.protocol, net, mask)
+ if rule.protocol in ['tcp', 'udp']:
+ rule_xml += "dstportstart='%s' dstportend='%s' " % \
+ (rule.from_port, rule.to_port)
+ elif rule.protocol == 'icmp':
+ LOG.info('rule.protocol: %r, rule.from_port: %r, '
+ 'rule.to_port: %r', rule.protocol,
+ rule.from_port, rule.to_port)
+ if rule.from_port != -1:
+ rule_xml += "type='%s' " % rule.from_port
+ if rule.to_port != -1:
+ rule_xml += "code='%s' " % rule.to_port
+
+ rule_xml += '/>\n'
+ rule_xml += "</rule>\n"
+ xml = "<filter name='nova-secgroup-%s' " % security_group_id
+ if(FLAGS.use_ipv6):
+ xml += "chain='root'>%s</filter>" % rule_xml
+ else:
+ xml += "chain='ipv4'>%s</filter>" % rule_xml
+ return xml
+
+ def _instance_filter_name(self, instance, nic_id=None):
+ if not nic_id:
+ return 'nova-instance-%s' % (instance['name'])
+ return 'nova-instance-%s-%s' % (instance['name'], nic_id)
+
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists."""
+ network_info = netutils.get_network_info(instance)
+ for (network, mapping) in network_info:
+ nic_id = mapping['mac'].replace(':', '')
+ instance_filter_name = self._instance_filter_name(instance, nic_id)
+ try:
+ self._conn.nwfilterLookupByName(instance_filter_name)
+ except libvirt.libvirtError:
+ name = instance.name
+ LOG.debug(_('The nwfilter(%(instance_filter_name)s) for'
+ '%(name)s is not found.') % locals())
+ return False
+ return True
+
+
+class IptablesFirewallDriver(FirewallDriver):
+ def __init__(self, execute=None, **kwargs):
+ from nova.network import linux_net
+ self.iptables = linux_net.iptables_manager
+ self.instances = {}
+ self.nwfilter = NWFilterFirewall(kwargs['get_connection'])
+
+ self.iptables.ipv4['filter'].add_chain('sg-fallback')
+ self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
+ self.iptables.ipv6['filter'].add_chain('sg-fallback')
+ self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
+
+ def setup_basic_filtering(self, instance, network_info=None):
+ """Use NWFilter from libvirt for this."""
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
+ return self.nwfilter.setup_basic_filtering(instance, network_info)
+
+ def apply_instance_filter(self, instance):
+ """No-op. Everything is done in prepare_instance_filter."""
+ pass
+
+ def unfilter_instance(self, instance):
+ if self.instances.pop(instance['id'], None):
+ self.remove_filters_for_instance(instance)
+ self.iptables.apply()
+ else:
+ LOG.info(_('Attempted to unfilter instance %s which is not '
+ 'filtered'), instance['id'])
+
+ def prepare_instance_filter(self, instance, network_info=None):
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
+ self.instances[instance['id']] = instance
+ self.add_filters_for_instance(instance, network_info)
+ self.iptables.apply()
+
+ def add_filters_for_instance(self, instance, network_info=None):
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
+ chain_name = self._instance_chain_name(instance)
+
+ self.iptables.ipv4['filter'].add_chain(chain_name)
+
+ ips_v4 = [ip['ip'] for (_, mapping) in network_info
+ for ip in mapping['ips']]
+
+ for ipv4_address in ips_v4:
+ self.iptables.ipv4['filter'].add_rule('local',
+ '-d %s -j $%s' %
+ (ipv4_address, chain_name))
+
+ if FLAGS.use_ipv6:
+ self.iptables.ipv6['filter'].add_chain(chain_name)
+ ips_v6 = [ip['ip'] for (_, mapping) in network_info
+ for ip in mapping['ip6s']]
+
+ for ipv6_address in ips_v6:
+ self.iptables.ipv6['filter'].add_rule('local',
+ '-d %s -j $%s' %
+ (ipv6_address,
+ chain_name))
+
+ ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
+
+ for rule in ipv4_rules:
+ self.iptables.ipv4['filter'].add_rule(chain_name, rule)
+
+ if FLAGS.use_ipv6:
+ for rule in ipv6_rules:
+ self.iptables.ipv6['filter'].add_rule(chain_name, rule)
+
+ def remove_filters_for_instance(self, instance):
+ chain_name = self._instance_chain_name(instance)
+
+ self.iptables.ipv4['filter'].remove_chain(chain_name)
+ if FLAGS.use_ipv6:
+ self.iptables.ipv6['filter'].remove_chain(chain_name)
+
+ def instance_rules(self, instance, network_info=None):
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
+ ctxt = context.get_admin_context()
+
+ ipv4_rules = []
+ ipv6_rules = []
+
+ # Always drop invalid packets
+ ipv4_rules += ['-m state --state ' 'INVALID -j DROP']
+ ipv6_rules += ['-m state --state ' 'INVALID -j DROP']
+
+ # Allow established connections
+ ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
+ ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
+
+ dhcp_servers = [network['gateway'] for (network, _m) in network_info]
+
+ for dhcp_server in dhcp_servers:
+ ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '
+ '-j ACCEPT' % (dhcp_server,))
+
+ #Allow project network traffic
+ if FLAGS.allow_project_net_traffic:
+ cidrs = [network['cidr'] for (network, _m) in network_info]
+ for cidr in cidrs:
+ ipv4_rules.append('-s %s -j ACCEPT' % (cidr,))
+
+ # We wrap these in FLAGS.use_ipv6 because they might cause
+ # a DB lookup. The other ones are just list operations, so
+ # they're not worth the clutter.
+ if FLAGS.use_ipv6:
+ # Allow RA responses
+ gateways_v6 = [network['gateway_v6'] for (network, _) in
+ network_info]
+ for gateway_v6 in gateways_v6:
+ ipv6_rules.append(
+ '-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,))
+
+ #Allow project network traffic
+ if FLAGS.allow_project_net_traffic:
+ cidrv6s = [network['cidr_v6'] for (network, _m)
+ in network_info]
+
+ for cidrv6 in cidrv6s:
+ ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))
+
+ security_groups = db.security_group_get_by_instance(ctxt,
+ instance['id'])
+
+ # then, security group chains and rules
+ for security_group in security_groups:
+ rules = db.security_group_rule_get_by_security_group(ctxt,
+ security_group['id'])
+
+ for rule in rules:
+ logging.info('%r', rule)
+
+ if not rule.cidr:
+ # Eventually, a mechanism to grant access for security
+ # groups will turn up here. It'll use ipsets.
+ continue
+
+ version = netutils.get_ip_version(rule.cidr)
+ if version == 4:
+ rules = ipv4_rules
+ else:
+ rules = ipv6_rules
+
+ protocol = rule.protocol
+ if version == 6 and rule.protocol == 'icmp':
+ protocol = 'icmpv6'
+
+ args = ['-p', protocol, '-s', rule.cidr]
+
+ if rule.protocol in ['udp', 'tcp']:
+ if rule.from_port == rule.to_port:
+ args += ['--dport', '%s' % (rule.from_port,)]
+ else:
+ args += ['-m', 'multiport',
+ '--dports', '%s:%s' % (rule.from_port,
+ rule.to_port)]
+ elif rule.protocol == 'icmp':
+ icmp_type = rule.from_port
+ icmp_code = rule.to_port
+
+ if icmp_type == -1:
+ icmp_type_arg = None
+ else:
+ icmp_type_arg = '%s' % icmp_type
+ if not icmp_code == -1:
+ icmp_type_arg += '/%s' % icmp_code
+
+ if icmp_type_arg:
+ if version == 4:
+ args += ['-m', 'icmp', '--icmp-type',
+ icmp_type_arg]
+ elif version == 6:
+ args += ['-m', 'icmp6', '--icmpv6-type',
+ icmp_type_arg]
+
+ args += ['-j ACCEPT']
+ rules += [' '.join(args)]
+
+ ipv4_rules += ['-j $sg-fallback']
+ ipv6_rules += ['-j $sg-fallback']
+
+ return ipv4_rules, ipv6_rules
+
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists."""
+ return self.nwfilter.instance_filter_exists(instance)
+
+ def refresh_security_group_members(self, security_group):
+ pass
+
+ def refresh_security_group_rules(self, security_group):
+ self.do_refresh_security_group_rules(security_group)
+ self.iptables.apply()
+
+ @utils.synchronized('iptables', external=True)
+ def do_refresh_security_group_rules(self, security_group):
+ for instance in self.instances.values():
+ self.remove_filters_for_instance(instance)
+ self.add_filters_for_instance(instance)
+
+ def _security_group_chain_name(self, security_group_id):
+ return 'nova-sg-%s' % (security_group_id,)
+
+ def _instance_chain_name(self, instance):
+ return 'inst-%s' % (instance['id'],)
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt/libvirt.xml.template
index de2497a76..de2497a76 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt/libvirt.xml.template
diff --git a/nova/virt/libvirt/netutils.py b/nova/virt/libvirt/netutils.py
new file mode 100644
index 000000000..3ed9a0fdc
--- /dev/null
+++ b/nova/virt/libvirt/netutils.py
@@ -0,0 +1,95 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+# Copyright (c) 2010 Citrix Systems, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+"""Network-releated utilities for supporting libvirt connection code."""
+
+
+import IPy
+
+from nova import context
+from nova import db
+from nova import flags
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+
+
+def get_net_and_mask(cidr):
+ net = IPy.IP(cidr)
+ return str(net.net()), str(net.netmask())
+
+
+def get_net_and_prefixlen(cidr):
+ net = IPy.IP(cidr)
+ return str(net.net()), str(net.prefixlen())
+
+
+def get_ip_version(cidr):
+ net = IPy.IP(cidr)
+ return int(net.version())
+
+
+def get_network_info(instance):
+ # TODO(adiantum) If we will keep this function
+ # we should cache network_info
+ admin_context = context.get_admin_context()
+
+ ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
+ instance['id'])
+ networks = db.network_get_all_by_instance(admin_context,
+ instance['id'])
+ flavor = db.instance_type_get_by_id(admin_context,
+ instance['instance_type_id'])
+ network_info = []
+
+ for network in networks:
+ network_ips = [ip for ip in ip_addresses
+ if ip['network_id'] == network['id']]
+
+ def ip_dict(ip):
+ return {
+ 'ip': ip['address'],
+ 'netmask': network['netmask'],
+ 'enabled': '1'}
+
+ def ip6_dict():
+ prefix = network['cidr_v6']
+ mac = instance['mac_address']
+ return {
+ 'ip': utils.to_global_ipv6(prefix, mac),
+ 'netmask': network['netmask_v6'],
+ 'enabled': '1'}
+
+ mapping = {
+ 'label': network['label'],
+ 'gateway': network['gateway'],
+ 'broadcast': network['broadcast'],
+ 'mac': instance['mac_address'],
+ 'rxtx_cap': flavor['rxtx_cap'],
+ 'dns': [network['dns']],
+ 'ips': [ip_dict(ip) for ip in network_ips]}
+
+ if FLAGS.use_ipv6:
+ mapping['ip6s'] = [ip6_dict()]
+ mapping['gateway6'] = network['gateway_v6']
+
+ network_info.append((network, mapping))
+ return network_info