127
130
return str(net.net()), str(net.netmask())
133
def _get_net_and_prefixlen(cidr):
135
return str(net.net()), str(net.prefixlen())
138
def _get_ip_version(cidr):
140
return int(net.version())
130
143
class LibvirtConnection(object):
132
145
def __init__(self, read_only):
480
492
subprocess.Popen(cmd, shell=True)
481
493
return {'token': token, 'host': host, 'port': port}
495
def _cache_image(self, fn, target, fname, cow=False, *args, **kwargs):
496
"""Wrapper for a method that creates an image that caches the image.
498
This wrapper will save the image into a common store and create a
499
copy for use by the hypervisor.
501
The underlying method should specify a kwarg of target representing
502
where the image will be saved.
504
fname is used as the filename of the base image. The filename needs
505
to be unique to a given image.
507
If cow is True, it will make a CoW image instead of a copy.
509
if not os.path.exists(target):
510
base_dir = os.path.join(FLAGS.instances_path, '_base')
511
if not os.path.exists(base_dir):
513
os.chmod(base_dir, 0777)
514
base = os.path.join(base_dir, fname)
515
if not os.path.exists(base):
516
fn(target=base, *args, **kwargs)
518
utils.execute('qemu-img create -f qcow2 -o '
519
'cluster_size=2M,backing_file=%s %s'
522
utils.execute('cp %s %s' % (base, target))
524
def _fetch_image(self, target, image_id, user, project, size=None):
525
"""Grab image and optionally attempt to resize it"""
526
images.fetch(image_id, target, user, project)
528
disk.extend(target, size)
530
def _create_local(self, target, local_gb):
531
"""Create a blank image of specified size"""
532
utils.execute('truncate %s -s %dG' % (target, local_gb))
533
# TODO(vish): should we format disk by default?
483
535
def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
484
536
# syntactic nicety
485
basepath = lambda fname = '', prefix = prefix: os.path.join(
486
FLAGS.instances_path,
537
def basepath(fname='', prefix=prefix):
538
return os.path.join(FLAGS.instances_path,
490
542
# ensure directories exist and are writable
491
543
utils.execute('mkdir -p %s' % basepath(prefix=''))
492
544
utils.execute('chmod 0777 %s' % basepath(prefix=''))
494
# TODO(termie): these are blocking calls, it would be great
496
546
LOG.info(_('instance %s: Creating image'), inst['name'])
497
547
f = open(basepath('libvirt.xml'), 'w')
498
548
f.write(libvirt_xml)
509
559
disk_images = {'image_id': inst['image_id'],
510
560
'kernel_id': inst['kernel_id'],
511
561
'ramdisk_id': inst['ramdisk_id']}
512
if not os.path.exists(basepath('disk')):
513
images.fetch(inst.image_id, basepath('disk-raw'), user,
516
if inst['kernel_id']:
517
if not os.path.exists(basepath('kernel')):
518
images.fetch(inst['kernel_id'], basepath('kernel'),
520
if inst['ramdisk_id']:
521
if not os.path.exists(basepath('ramdisk')):
522
images.fetch(inst['ramdisk_id'], basepath('ramdisk'),
525
def execute(cmd, process_input=None, check_exit_code=True):
526
return utils.execute(cmd=cmd,
527
process_input=process_input,
528
check_exit_code=check_exit_code)
563
if disk_images['kernel_id']:
564
self._cache_image(fn=self._fetch_image,
565
target=basepath('kernel'),
566
fname=disk_images['kernel_id'],
567
image_id=disk_images['kernel_id'],
570
if disk_images['ramdisk_id']:
571
self._cache_image(fn=self._fetch_image,
572
target=basepath('ramdisk'),
573
fname=disk_images['ramdisk_id'],
574
image_id=disk_images['ramdisk_id'],
578
root_fname = disk_images['image_id']
579
size = FLAGS.minimum_root_size
580
if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
584
self._cache_image(fn=self._fetch_image,
585
target=basepath('disk'),
587
cow=FLAGS.use_cow_images,
588
image_id=disk_images['image_id'],
592
type_data = instance_types.INSTANCE_TYPES[inst['instance_type']]
594
if type_data['local_gb']:
595
self._cache_image(fn=self._create_local,
596
target=basepath('local'),
597
fname="local_%s" % type_data['local_gb'],
598
cow=FLAGS.use_cow_images,
599
local_gb=type_data['local_gb'])
530
601
# For now, we assume that if we're not using a kernel, we're using a
531
602
# partitioned disk image where the target partition is the first
541
612
if network_ref['injected']:
542
613
admin_context = context.get_admin_context()
543
614
address = db.instance_get_fixed_address(admin_context, inst['id'])
615
ra_server = network_ref['ra_server']
544
618
with open(FLAGS.injected_network_template) as f:
545
619
net = f.read() % {'address': address,
546
620
'netmask': network_ref['netmask'],
547
621
'gateway': network_ref['gateway'],
548
622
'broadcast': network_ref['broadcast'],
549
'dns': network_ref['dns']}
623
'dns': network_ref['dns'],
624
'ra_server': ra_server}
552
627
LOG.info(_('instance %s: injecting key into image %s'),
555
630
LOG.info(_('instance %s: injecting net into image %s'),
556
631
inst['name'], inst.image_id)
558
disk.inject_data(basepath('disk-raw'), key, net,
633
disk.inject_data(basepath('disk'), key, net,
559
634
partition=target_partition,
635
nbd=FLAGS.use_cow_images)
561
636
except Exception as e:
562
637
# This could be a windows image, or a vmdk format disk
563
638
LOG.warn(_('instance %s: ignoring error injecting data'
564
639
' into image %s (%s)'),
565
640
inst['name'], inst.image_id, e)
567
if inst['kernel_id']:
568
if os.path.exists(basepath('disk')):
569
utils.execute('rm -f %s' % basepath('disk'))
571
local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type]
573
* 1024 * 1024 * 1024)
576
if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
579
if inst['kernel_id']:
580
disk.partition(basepath('disk-raw'), basepath('disk'),
581
local_bytes, resize, execute=execute)
583
os.rename(basepath('disk-raw'), basepath('disk'))
584
disk.extend(basepath('disk'), local_bytes, execute=execute)
586
642
if FLAGS.libvirt_type == 'uml':
587
643
utils.execute('sudo chown root %s' % basepath('disk'))
602
658
# Assume that the gateway also acts as the dhcp server.
603
659
dhcp_server = network['gateway']
660
ra_server = network['ra_server']
605
663
if FLAGS.allow_project_net_traffic:
606
net, mask = _get_net_and_mask(network['cidr'])
607
extra_params = ("<parameter name=\"PROJNET\" "
609
"<parameter name=\"PROJMASK\" "
610
"value=\"%s\" />\n") % (net, mask)
665
net, mask = _get_net_and_mask(network['cidr'])
666
net_v6, prefixlen_v6 = _get_net_and_prefixlen(
668
extra_params = ("<parameter name=\"PROJNET\" "
670
"<parameter name=\"PROJMASK\" "
672
"<parameter name=\"PROJNETV6\" "
674
"<parameter name=\"PROJMASKV6\" "
675
"value=\"%s\" />\n") % \
676
(net, mask, net_v6, prefixlen_v6)
678
net, mask = _get_net_and_mask(network['cidr'])
679
extra_params = ("<parameter name=\"PROJNET\" "
681
"<parameter name=\"PROJMASK\" "
682
"value=\"%s\" />\n") % \
612
685
extra_params = "\n"
686
if FLAGS.use_cow_images:
687
driver_type = 'qcow2'
614
691
xml_info = {'type': FLAGS.libvirt_type,
615
692
'name': instance['name'],
621
698
'mac_address': instance['mac_address'],
622
699
'ip_address': ip_address,
623
700
'dhcp_server': dhcp_server,
701
'ra_server': ra_server,
624
702
'extra_params': extra_params,
704
'local': instance_type['local_gb'],
705
'driver_type': driver_type}
627
707
if instance['kernel_id']:
628
708
xml_info['kernel'] = xml_info['basepath'] + "/kernel"
965
def nova_ra_filter(self):
966
return '''<filter name='nova-allow-ra-server' chain='root'>
967
<uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
968
<rule action='accept' direction='inout'
970
<icmpv6 srcipaddr='$RASERVER'/>
885
974
def setup_basic_filtering(self, instance):
886
975
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
887
976
logging.info('called setup_basic_filtering in nwfilter')
910
999
self._define_filter(self.nova_base_ipv4_filter)
911
1000
self._define_filter(self.nova_base_ipv6_filter)
912
1001
self._define_filter(self.nova_dhcp_filter)
1002
self._define_filter(self.nova_ra_filter)
913
1003
self._define_filter(self.nova_vpn_filter)
914
1004
if FLAGS.allow_project_net_traffic:
915
1005
self._define_filter(self.nova_project_filter)
1007
self._define_filter(self.nova_project_filter_v6)
917
1009
self.static_filters_configured = True
945
1037
def nova_base_ipv6_filter(self):
946
1038
retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
947
for protocol in ['tcp', 'udp', 'icmp']:
1039
for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
948
1040
for direction, action, priority in [('out', 'accept', 399),
949
1041
('in', 'drop', 400)]:
950
1042
retval += """<rule action='%s' direction='%s' priority='%d'>
952
1044
</rule>""" % (action, direction,
954
1046
retval += '</filter>'
963
1055
retval += '</filter>'
1058
def nova_project_filter_v6(self):
1059
retval = "<filter name='nova-project-v6' chain='ipv6'>"
1060
for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
1061
retval += """<rule action='accept' direction='inout'
1063
<%s srcipaddr='$PROJNETV6'
1064
srcipmask='$PROJMASKV6' />
1065
</rule>""" % (protocol)
1066
retval += '</filter>'
966
1069
def _define_filter(self, xml):
967
1070
if callable(xml):
970
1072
# execute in a native thread and block current greenthread until done
971
1073
tpool.execute(self._conn.nwfilterDefineXML, xml)
992
1093
instance_secgroup_filter_children = ['nova-base-ipv4',
993
1094
'nova-base-ipv6',
994
1095
'nova-allow-dhcp-server']
1097
instance_secgroup_filter_children += ['nova-allow-ra-server']
996
1099
ctxt = context.get_admin_context()
998
1101
if FLAGS.allow_project_net_traffic:
999
1102
instance_filter_children += ['nova-project']
1104
instance_filter_children += ['nova-project-v6']
1001
1106
for security_group in db.security_group_get_by_instance(ctxt,
1002
1107
instance['id']):
1024
1129
security_group = db.security_group_get(context.get_admin_context(),
1025
1130
security_group_id)
1132
v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
1027
1133
for rule in security_group.rules:
1028
1134
rule_xml += "<rule action='accept' direction='in' priority='300'>"
1030
net, mask = _get_net_and_mask(rule.cidr)
1031
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
1032
(rule.protocol, net, mask)
1136
version = _get_ip_version(rule.cidr)
1137
if(FLAGS.use_ipv6 and version == 6):
1138
net, prefixlen = _get_net_and_prefixlen(rule.cidr)
1139
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
1140
(v6protocol[rule.protocol], net, prefixlen)
1142
net, mask = _get_net_and_mask(rule.cidr)
1143
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
1144
(rule.protocol, net, mask)
1033
1145
if rule.protocol in ['tcp', 'udp']:
1034
1146
rule_xml += "dstportstart='%s' dstportend='%s' " % \
1035
1147
(rule.from_port, rule.to_port)
1082
1197
def apply_ruleset(self):
1083
1198
current_filter, _ = self.execute('sudo iptables-save -t filter')
1084
1199
current_lines = current_filter.split('\n')
1085
new_filter = self.modify_rules(current_lines)
1200
new_filter = self.modify_rules(current_lines, 4)
1086
1201
self.execute('sudo iptables-restore',
1087
1202
process_input='\n'.join(new_filter))
1204
current_filter, _ = self.execute('sudo ip6tables-save -t filter')
1205
current_lines = current_filter.split('\n')
1206
new_filter = self.modify_rules(current_lines, 6)
1207
self.execute('sudo ip6tables-restore',
1208
process_input='\n'.join(new_filter))
1089
def modify_rules(self, current_lines):
1210
def modify_rules(self, current_lines, ip_version=4):
1090
1211
ctxt = context.get_admin_context()
1091
1212
# Remove any trace of nova rules.
1092
1213
new_filter = filter(lambda l: 'nova-' not in l, current_lines)
1140
1264
our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
1142
# Allow DHCP responses
1143
dhcp_server = self._dhcp_server_for_instance(instance)
1144
our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
1145
(chain_name, dhcp_server)]
1266
if(ip_version == 4):
1267
# Allow DHCP responses
1268
dhcp_server = self._dhcp_server_for_instance(instance)
1269
our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
1270
(chain_name, dhcp_server)]
1271
elif(ip_version == 6):
1272
# Allow RA responses
1273
ra_server = self._ra_server_for_instance(instance)
1274
our_rules += ['-A %s -s %s -p icmpv6' %
1275
(chain_name, ra_server)]
1147
1277
# If nothing matches, jump to the fallback chain
1148
our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)]
1278
our_rules += ['-A %s -j nova-fallback' % (chain_name,)]
1150
1280
# then, security group chains and rules
1151
1281
for security_group_id in security_groups:
1159
1289
for rule in rules:
1160
1290
logging.info('%r', rule)
1161
args = ['-A', chain_name, '-p', rule.protocol]
1164
args += ['-s', rule.cidr]
1166
1293
# Eventually, a mechanism to grant access for security
1167
1294
# groups will turn up here. It'll use ipsets.
1297
version = _get_ip_version(rule.cidr)
1298
if version != ip_version:
1301
protocol = rule.protocol
1302
if version == 6 and rule.protocol == 'icmp':
1305
args = ['-A', chain_name, '-p', protocol, '-s', rule.cidr]
1170
1307
if rule.protocol in ['udp', 'tcp']:
1171
1308
if rule.from_port == rule.to_port:
1172
1309
args += ['--dport', '%s' % (rule.from_port,)]
1186
1323
icmp_type_arg += '/%s' % icmp_code
1188
1325
if icmp_type_arg:
1189
args += ['-m', 'icmp', '--icmp-type', icmp_type_arg]
1326
if(ip_version == 4):
1327
args += ['-m', 'icmp', '--icmp-type',
1329
elif(ip_version == 6):
1330
args += ['-m', 'icmp6', '--icmpv6-type',
1191
1333
args += ['-j ACCEPT']
1192
1334
our_rules += [' '.join(args)]
1212
1354
return db.instance_get_fixed_address(context.get_admin_context(),
1213
1355
instance['id'])
1357
def _ip_for_instance_v6(self, instance):
1358
return db.instance_get_fixed_address_v6(context.get_admin_context(),
1215
1361
def _dhcp_server_for_instance(self, instance):
1216
1362
network = db.project_get_network(context.get_admin_context(),
1217
1363
instance['project_id'])
1218
1364
return network['gateway']
1366
def _ra_server_for_instance(self, instance):
1367
network = db.project_get_network(context.get_admin_context(),
1368
instance['project_id'])
1369
return network['ra_server']