Add IPv6 related VM properties

Add property for IPv6 address ('ip6'). Build default value similarly to
IPv4 - common prefix + QID or Disp ID (for DispVMs).
This all is disabled unless 'ipv6' feature is enabled. It is inherited
from netvm (not template).
Even when enabled, VM may decide to not use it - or simply not support
it.

QubesOS/qubes-issues#718
This commit is contained in:
Marek Marczykowski-Górecki 2017-12-01 03:24:34 +01:00
parent bf59b00f1d
commit 18f159f8ec
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
5 changed files with 106 additions and 11 deletions

View File

@ -107,3 +107,6 @@ max_default_label = 8
#: profiles for admin.backup.* calls #: profiles for admin.backup.* calls
backup_profile_dir = '/etc/qubes/backup' backup_profile_dir = '/etc/qubes/backup'
#: site-local prefix for all VMs
qubes_ipv6_prefix = 'fd09:24ef:4179:0000'

View File

@ -121,3 +121,21 @@ class TC_00_NetVMMixin(
self.assertPropertyInvalidValue(vm, 'ip', 'a.b.c.d') self.assertPropertyInvalidValue(vm, 'ip', 'a.b.c.d')
self.assertPropertyInvalidValue(vm, 'ip', '1111.2222.3333.4444') self.assertPropertyInvalidValue(vm, 'ip', '1111.2222.3333.4444')
# TODO: implement and add here: 0.0.0.0, 333.333.333.333 # TODO: implement and add here: 0.0.0.0, 333.333.333.333
def test_160_ip6(self):
vm = self.get_vm()
self.setup_netvms(vm)
self.assertPropertyDefaultValue(vm, 'ip6', None)
vm.netvm.features['ipv6'] = True
self.assertPropertyDefaultValue(vm, 'ip6',
'{}::a89:{:x}'.format(qubes.config.qubes_ipv6_prefix, vm.qid))
vm.ip6 = 'abcd:efff::1'
self.assertEqual(vm.ip6, 'abcd:efff::1')
def test_161_ip6_invalid(self):
vm = self.get_vm()
self.setup_netvms(vm)
vm.netvm.features['ipv6'] = True
self.assertPropertyInvalidValue(vm, 'ip', 'zzzz')
self.assertPropertyInvalidValue(vm, 'ip',
'1:2:3:4:5:6:7:8:0:a:b:c:d:e:f:0')

View File

@ -24,14 +24,15 @@
import os import os
import re import re
import socket
import libvirt # pylint: disable=import-error import libvirt # pylint: disable=import-error
import qubes import qubes
import qubes.config
import qubes.events import qubes.events
import qubes.firewall import qubes.firewall
import qubes.exc import qubes.exc
def _setter_mac(self, prop, value): def _setter_mac(self, prop, value):
''' Helper for setting the MAC address ''' ''' Helper for setting the MAC address '''
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -61,6 +62,27 @@ def _setter_ip(self, prop, value):
raise ValueError('Invalid IP address value') raise ValueError('Invalid IP address value')
return value return value
def _default_ip6(self):
if not self.is_networked():
return None
if not self.features.check_with_netvm('ipv6', False):
return None
if self.netvm is not None:
return self.netvm.get_ip6_for_vm(self) # pylint: disable=no-member
return self.get_ip6_for_vm(self)
def _setter_ip6(self, prop, value):
# pylint: disable=unused-argument
if not isinstance(value, str):
raise ValueError('IPv6 address must be a string')
value = value.lower()
try:
socket.inet_pton(socket.AF_INET6, value)
except socket.error:
raise ValueError('Invalid IPv6 address value')
return value
def _setter_netvm(self, prop, value): def _setter_netvm(self, prop, value):
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -92,6 +114,11 @@ class NetVMMixin(qubes.events.Emitter):
setter=_setter_ip, setter=_setter_ip,
doc='IP address of this domain.') doc='IP address of this domain.')
ip6 = qubes.property('ip6', type=str,
default=_default_ip6,
setter=_setter_ip6,
doc='IPv6 address of this domain.')
# CORE2: swallowed uses_default_netvm # CORE2: swallowed uses_default_netvm
netvm = qubes.VMProperty('netvm', load_stage=4, allow_none=True, netvm = qubes.VMProperty('netvm', load_stage=4, allow_none=True,
default=(lambda self: self.app.default_netvm), default=(lambda self: self.app.default_netvm),
@ -121,12 +148,24 @@ class NetVMMixin(qubes.events.Emitter):
return self.features.check_with_template('net.fake-ip', None) or \ return self.features.check_with_template('net.fake-ip', None) or \
self.ip self.ip
@qubes.stateless_property
def visible_ip6(self):
'''IPv6 address of this domain as seen by the domain.'''
return self.ip6
@qubes.stateless_property @qubes.stateless_property
def visible_gateway(self): def visible_gateway(self):
'''Default gateway of this domain as seen by the domain.''' '''Default gateway of this domain as seen by the domain.'''
return self.features.check_with_template('net.fake-gateway', None) or \ return self.features.check_with_template('net.fake-gateway', None) or \
(self.netvm.gateway if self.netvm else None) (self.netvm.gateway if self.netvm else None)
@qubes.stateless_property
def visible_gateway6(self):
'''Default (IPv6) gateway of this domain as seen by the domain.'''
if self.features.check_with_netvm('ipv6', False):
return self.netvm.gateway6 if self.netvm else None
return None
@qubes.stateless_property @qubes.stateless_property
def visible_netmask(self): def visible_netmask(self):
'''Netmask as seen by the domain.''' '''Netmask as seen by the domain.'''
@ -151,11 +190,33 @@ class NetVMMixin(qubes.events.Emitter):
# does not happen, because qid < 253, but may happen in the future. # does not happen, because qid < 253, but may happen in the future.
return '10.137.{}.{}'.format((vm.qid >> 8) & 0xff, vm.qid & 0xff) return '10.137.{}.{}'.format((vm.qid >> 8) & 0xff, vm.qid & 0xff)
@staticmethod
def get_ip6_for_vm(vm):
'''Get IPv6 address for (appvm) domain connected to this (netvm) domain.
Default address is constructed with Qubes-specific site-local prefix,
and IPv4 suffix (0xa89 is 10.137.).
'''
import qubes.vm.dispvm # pylint: disable=redefined-outer-name
if isinstance(vm, qubes.vm.dispvm.DispVM):
return '{}::a8a:{:x}'.format(
qubes.config.qubes_ipv6_prefix, vm.dispid)
return '{}::a89:{:x}'.format(qubes.config.qubes_ipv6_prefix, vm.qid)
@qubes.stateless_property @qubes.stateless_property
def gateway(self): def gateway(self):
'''Gateway for other domains that use this domain as netvm.''' '''Gateway for other domains that use this domain as netvm.'''
return self.visible_ip if self.provides_network else None return self.visible_ip if self.provides_network else None
@qubes.stateless_property
def gateway6(self):
'''Gateway (IPv6) for other domains that use this domain as netvm.'''
if self.features.check_with_netvm('ipv6', False):
return 'fe80::fcff:ffff:feff:ffff' if self.provides_network else \
None
return None
@property @property
def netmask(self): def netmask(self):
'''Netmask for gateway address.''' '''Netmask for gateway address.'''
@ -305,12 +366,17 @@ class NetVMMixin(qubes.events.Emitter):
if not self.is_running(): if not self.is_running():
return return
base_dir = '/qubes-firewall/' + vm.ip + '/' for addr_family in (4, 6):
ip = vm.ip6 if addr_family == 6 else vm.ip
if ip is None:
continue
base_dir = '/qubes-firewall/' + ip + '/'
# remove old entries if any (but don't touch base empty entry - it # remove old entries if any (but don't touch base empty entry - it
# would trigger reload right away # would trigger reload right away
self.untrusted_qdb.rm(base_dir) self.untrusted_qdb.rm(base_dir)
# write new rules # write new rules
for key, value in vm.firewall.qdb_entries(addr_family=4).items(): for key, value in vm.firewall.qdb_entries(
addr_family=addr_family).items():
self.untrusted_qdb.write(base_dir + key, value) self.untrusted_qdb.write(base_dir + key, value)
# signal its done # signal its done
self.untrusted_qdb.write(base_dir[:-1], '') self.untrusted_qdb.write(base_dir[:-1], '')
@ -334,7 +400,6 @@ class NetVMMixin(qubes.events.Emitter):
else: else:
self.untrusted_qdb.rm(mapped_ip_base + '/visible-gateway') self.untrusted_qdb.rm(mapped_ip_base + '/visible-gateway')
@qubes.events.handler('property-pre-del:netvm') @qubes.events.handler('property-pre-del:netvm')
def on_property_pre_del_netvm(self, event, name, oldvalue=None): def on_property_pre_del_netvm(self, event, name, oldvalue=None):
''' Sets the the NetVM to default NetVM ''' ''' Sets the the NetVM to default NetVM '''

View File

@ -1831,6 +1831,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
for i, addr in zip(('primary', 'secondary'), self.dns): for i, addr in zip(('primary', 'secondary'), self.dns):
self.untrusted_qdb.write('/qubes-{}-dns'.format(i), addr) self.untrusted_qdb.write('/qubes-{}-dns'.format(i), addr)
if self.visible_ip6:
self.untrusted_qdb.write('/qubes-ip6', self.visible_ip6)
if self.visible_gateway6:
self.untrusted_qdb.write('/qubes-gateway6',
self.visible_gateway6)
tzname = qubes.utils.get_timezone() tzname = qubes.utils.get_timezone()
if tzname: if tzname:

View File

@ -1,6 +1,9 @@
<interface type="ethernet"> <interface type="ethernet">
<mac address="{{ vm.mac }}" /> <mac address="{{ vm.mac }}" />
<ip address="{{ vm.ip }}" /> <ip address="{{ vm.ip }}" />
{% if vm.ip6 %}
<ip address="{{ vm.ip6 }}" family='ipv6' />
{% endif %}
<backenddomain name="{{ vm.netvm.name }}" /> <backenddomain name="{{ vm.netvm.name }}" />
<script path="vif-route-qubes" /> <script path="vif-route-qubes" />
</interface> </interface>