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:
parent
bf59b00f1d
commit
18f159f8ec
@ -107,3 +107,6 @@ max_default_label = 8
|
||||
|
||||
#: profiles for admin.backup.* calls
|
||||
backup_profile_dir = '/etc/qubes/backup'
|
||||
|
||||
#: site-local prefix for all VMs
|
||||
qubes_ipv6_prefix = 'fd09:24ef:4179:0000'
|
||||
|
@ -121,3 +121,21 @@ class TC_00_NetVMMixin(
|
||||
self.assertPropertyInvalidValue(vm, 'ip', 'a.b.c.d')
|
||||
self.assertPropertyInvalidValue(vm, 'ip', '1111.2222.3333.4444')
|
||||
# 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')
|
||||
|
@ -24,14 +24,15 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
|
||||
import libvirt # pylint: disable=import-error
|
||||
import qubes
|
||||
import qubes.config
|
||||
import qubes.events
|
||||
import qubes.firewall
|
||||
import qubes.exc
|
||||
|
||||
|
||||
def _setter_mac(self, prop, value):
|
||||
''' Helper for setting the MAC address '''
|
||||
# pylint: disable=unused-argument
|
||||
@ -61,6 +62,27 @@ def _setter_ip(self, prop, value):
|
||||
raise ValueError('Invalid IP address 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):
|
||||
# pylint: disable=unused-argument
|
||||
@ -92,6 +114,11 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
setter=_setter_ip,
|
||||
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
|
||||
netvm = qubes.VMProperty('netvm', load_stage=4, allow_none=True,
|
||||
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 \
|
||||
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
|
||||
def visible_gateway(self):
|
||||
'''Default gateway of this domain as seen by the domain.'''
|
||||
return self.features.check_with_template('net.fake-gateway', None) or \
|
||||
(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
|
||||
def visible_netmask(self):
|
||||
'''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.
|
||||
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
|
||||
def gateway(self):
|
||||
'''Gateway for other domains that use this domain as netvm.'''
|
||||
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
|
||||
def netmask(self):
|
||||
'''Netmask for gateway address.'''
|
||||
@ -305,12 +366,17 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
if not self.is_running():
|
||||
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
|
||||
# would trigger reload right away
|
||||
self.untrusted_qdb.rm(base_dir)
|
||||
# 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)
|
||||
# signal its done
|
||||
self.untrusted_qdb.write(base_dir[:-1], '')
|
||||
@ -334,7 +400,6 @@ class NetVMMixin(qubes.events.Emitter):
|
||||
else:
|
||||
self.untrusted_qdb.rm(mapped_ip_base + '/visible-gateway')
|
||||
|
||||
|
||||
@qubes.events.handler('property-pre-del:netvm')
|
||||
def on_property_pre_del_netvm(self, event, name, oldvalue=None):
|
||||
''' Sets the the NetVM to default NetVM '''
|
||||
|
@ -1831,6 +1831,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
for i, addr in zip(('primary', 'secondary'), self.dns):
|
||||
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()
|
||||
if tzname:
|
||||
|
@ -1,6 +1,9 @@
|
||||
<interface type="ethernet">
|
||||
<mac address="{{ vm.mac }}" />
|
||||
<ip address="{{ vm.ip }}" />
|
||||
{% if vm.ip6 %}
|
||||
<ip address="{{ vm.ip6 }}" family='ipv6' />
|
||||
{% endif %}
|
||||
<backenddomain name="{{ vm.netvm.name }}" />
|
||||
<script path="vif-route-qubes" />
|
||||
</interface>
|
||||
|
Loading…
Reference in New Issue
Block a user