Skip to content

Commit 0ce5c48

Browse files
committed
Redo netvm setup after unpause
Applying deferred netvm for preloaded disposables is handled separately to ensure that before a disposable is returned to the user, the networking is already set up. If the domain-unpaused event of the NetVMMixin kicks in before the preload is used, it is ignored by the "is_preload" attribute, if it kicks after, it is ignored by the absent "deferred-netvm-original" feature. Fixes: QubesOS/qubes-issues#10173 For: QubesOS/qubes-issues#1512
1 parent 8de4cd0 commit 0ce5c48

File tree

6 files changed

+197
-16
lines changed

6 files changed

+197
-16
lines changed

qubes/tests/integ/network.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,37 @@ def test_000_simple_networking(self):
191191
self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
192192
self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
193193

194+
def test_001_simple_networking_paused(self):
195+
"""
196+
:type self: qubes.tests.SystemTestCase | VMNetworkingMixin
197+
"""
198+
self.testvm1.netvm = None
199+
self.loop.run_until_complete(self.start_vm(self.testvm1))
200+
self.loop.run_until_complete(self.testvm1.pause())
201+
self.testvm1.netvm = self.testnetvm
202+
self.assertEqual(
203+
self.testvm1.features.get("deferred-netvm-original", None), ""
204+
)
205+
self.loop.run_until_complete(self.testvm1.unpause())
206+
self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
207+
self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
208+
self.assertEqual(
209+
self.testvm1.features.get("deferred-netvm-original", None), None
210+
)
211+
212+
self.loop.run_until_complete(self.testvm1.pause())
213+
self.testvm1.netvm = None
214+
self.assertEqual(
215+
self.testvm1.features.get("deferred-netvm-original", None),
216+
self.testnetvm.name,
217+
)
218+
self.loop.run_until_complete(self.testvm1.unpause())
219+
self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
220+
self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_name), 0)
221+
self.assertEqual(
222+
self.testvm1.features.get("deferred-netvm-original", None), None
223+
)
224+
194225
def test_010_simple_proxyvm(self):
195226
"""
196227
:type self: qubes.tests.SystemTestCase | VMNetworkingMixin
@@ -389,7 +420,7 @@ def test_030_firewallvm_firewall(self):
389420
# block all except ICMP
390421

391422
self.testvm1.firewall.rules = [
392-
(qubes.firewall.Rule(None, action="accept", proto="icmp"))
423+
qubes.firewall.Rule(None, action="accept", proto="icmp")
393424
]
394425
self.testvm1.firewall.save()
395426
# Ugly hack b/c there is no feedback when the rules are actually

qubes/tests/vm/mix/net.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,78 @@ def test_145_netvm_change(self):
159159
mock_detach.reset_mock()
160160
mock_attach.reset_mock()
161161

162+
def test_146_netvm_defer(self):
163+
vm = self.get_vm()
164+
self.setup_netvms(vm)
165+
with (
166+
patch("qubes.vm.qubesvm.QubesVM.is_running", lambda x: True),
167+
patch("qubes.vm.qubesvm.QubesVM.is_paused", lambda x: True),
168+
patch("qubes.vm.mix.net.NetVMMixin.attach_network") as mock_attach,
169+
patch("qubes.vm.mix.net.NetVMMixin.detach_network") as mock_detach,
170+
patch("qubes.vm.qubesvm.QubesVM.create_qdb_entries"),
171+
):
172+
173+
with self.subTest("changing netvm"):
174+
original_netvm = vm.netvm.name
175+
vm.netvm = self.netvm2
176+
mock_detach.assert_not_called()
177+
mock_attach.assert_not_called()
178+
self.assertEqual(
179+
vm.features.get("deferred-netvm-original", None),
180+
original_netvm,
181+
)
182+
with patch(
183+
"qubes.vm.qubesvm.QubesVM.is_paused", lambda x: False
184+
):
185+
vm.apply_deferred_netvm()
186+
self.assertEqual(
187+
vm.features.get("deferred-netvm-original", None), None
188+
)
189+
mock_detach.assert_called()
190+
mock_attach.assert_called()
191+
mock_detach.reset_mock()
192+
mock_attach.reset_mock()
193+
194+
with self.subTest("setting netvm to none"):
195+
original_netvm = vm.netvm.name
196+
vm.netvm = None
197+
mock_detach.assert_not_called()
198+
mock_attach.assert_not_called()
199+
self.assertEqual(
200+
vm.features.get("deferred-netvm-original", None),
201+
original_netvm,
202+
)
203+
with patch(
204+
"qubes.vm.qubesvm.QubesVM.is_paused", lambda x: False
205+
):
206+
vm.apply_deferred_netvm()
207+
self.assertEqual(
208+
vm.features.get("deferred-netvm-original", None), None
209+
)
210+
mock_detach.assert_called()
211+
mock_attach.assert_not_called()
212+
mock_detach.reset_mock()
213+
214+
with self.subTest("resetting netvm to default"):
215+
original_netvm = vm.netvm.name if vm.netvm else ""
216+
del vm.netvm
217+
mock_detach.assert_not_called()
218+
mock_attach.assert_not_called()
219+
self.assertEqual(
220+
vm.features.get("deferred-netvm-original", None),
221+
original_netvm,
222+
)
223+
with patch(
224+
"qubes.vm.qubesvm.QubesVM.is_paused", lambda x: False
225+
):
226+
vm.apply_deferred_netvm()
227+
self.assertEqual(
228+
vm.features.get("deferred-netvm-original", None), None
229+
)
230+
mock_detach.assert_not_called()
231+
mock_attach.assert_called_once()
232+
mock_attach.reset_mock()
233+
162234
def test_150_ip(self):
163235
vm = self.get_vm()
164236
self.setup_netvms(vm)

qubes/tests/vm/qubesvm.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,7 +1691,12 @@ def test_600_libvirt_xml_hvm_pcidev(self):
16911691
)
16921692
dom0 = self.get_vm(name="dom0", qid=0)
16931693
vm = self.get_vm(uuid=my_uuid)
1694-
vm.netvm = None
1694+
vm.paused = lambda x: False
1695+
with unittest.mock.patch(
1696+
"qubes.vm.qubesvm.QubesVM.is_paused"
1697+
) as mock_is_paused:
1698+
mock_is_paused.return_value = False
1699+
vm.netvm = None
16951700
vm.virt_mode = "hvm"
16961701
vm.kernel = None
16971702
# even with meminfo-writer enabled, should have memory==maxmem
@@ -1803,7 +1808,11 @@ def test_600_libvirt_xml_hvm_pcidev_s0ix(self):
18031808
dom0 = self.get_vm(name="dom0", qid=0)
18041809
dom0.features["suspend-s0ix"] = True
18051810
vm = self.get_vm(uuid=my_uuid)
1806-
vm.netvm = None
1811+
with unittest.mock.patch(
1812+
"qubes.vm.qubesvm.QubesVM.is_paused"
1813+
) as mock_is_paused:
1814+
mock_is_paused.return_value = False
1815+
vm.netvm = None
18071816
vm.virt_mode = "hvm"
18081817
vm.kernel = None
18091818
# even with meminfo-writer enabled, should have memory==maxmem

qubes/vm/dispvm.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,13 +577,15 @@ def use_preload(self):
577577
self.log.info("Using preloaded qube")
578578
if not appvm.features.get("internal", None):
579579
del self.features["internal"]
580+
self.apply_deferred_netvm()
580581
self.preload_requested = None
581582
del self.features["preload-dispvm-in-progress"]
582583
else:
583584
# Happens when unpause/resume occurs without qube being requested.
584585
self.log.warning("Using a preloaded qube before requesting it")
585586
if not appvm.features.get("internal", None):
586587
del self.features["internal"]
588+
self.apply_deferred_netvm()
587589
appvm.remove_preload_from_list([self.name])
588590
self.features["preload-dispvm-in-progress"] = False
589591
self.app.save()

qubes/vm/mix/net.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
2121
#
2222

23-
""" This module contains the NetVMMixin """
23+
"""This module contains the NetVMMixin"""
2424
import ipaddress
2525
import os
2626
import re
2727

2828
import libvirt # pylint: disable=import-error
29+
import lxml.etree
2930
import qubes
3031
import qubes.config
3132
import qubes.events
@@ -341,6 +342,45 @@ def on_domain_started_net(self, event, **kwargs):
341342
except (qubes.exc.QubesException, libvirt.libvirtError):
342343
vm.log.warning("Cannot attach network", exc_info=1)
343344

345+
def apply_deferred_netvm(self):
346+
"""Apply deferred netvm changes in case qube could not apply it at the
347+
time it was requested."""
348+
deferred_from = self.features.get("deferred-netvm-original", None)
349+
if deferred_from is None:
350+
return
351+
oldvalue = None
352+
if deferred_from in self.app.domains:
353+
oldvalue = self.app.domains[deferred_from]
354+
if oldvalue:
355+
self.fire_event(
356+
"property-pre-set:netvm",
357+
pre_event=True,
358+
name="netvm",
359+
newvalue=self.netvm,
360+
oldvalue=oldvalue,
361+
)
362+
if self.netvm:
363+
self.fire_event(
364+
"property-set:netvm",
365+
name="netvm",
366+
newvalue=self.netvm,
367+
oldvalue=oldvalue,
368+
)
369+
del self.features["deferred-netvm-original"]
370+
371+
@qubes.events.handler("domain-unpaused")
372+
def on_domain_unpaused_net(
373+
self, event, **kwargs
374+
): # pylint: disable=unused-argument
375+
"""Check for deferred netvm changes in case qube was paused while
376+
changes happened."""
377+
if getattr(self, "is_preload", False):
378+
# Networking of preloaded disposable must be done when it is being
379+
# marked as used, so we guarantee that it finishes before the user
380+
# can use the disposable.
381+
return
382+
self.apply_deferred_netvm()
383+
344384
@qubes.events.handler("domain-pre-shutdown")
345385
def on_domain_pre_shutdown(self, event, force=False):
346386
"""Checks before NetVM shutdown if any connected domains are running.
@@ -373,6 +413,12 @@ def attach_network(self):
373413
self.netvm.start()
374414

375415
self.netvm.set_mapped_ip_info_for_vm(self)
416+
417+
if self.is_paused():
418+
self.log.warning(
419+
"Deferred attaching libvirt net device because qube is paused"
420+
)
421+
return
376422
self.libvirt_domain.attachDevice(
377423
self.app.env.get_template("libvirt/devices/net.xml").render(vm=self)
378424
)
@@ -383,13 +429,24 @@ def detach_network(self):
383429
if not self.is_running():
384430
raise qubes.exc.QubesVMNotRunningError(self)
385431
if self.netvm is None:
386-
raise qubes.exc.QubesVMError(
387-
self, "netvm should not be {}".format(self.netvm)
432+
deferred_from = self.features.get("deferred-netvm", None)
433+
if deferred_from is not None:
434+
raise qubes.exc.QubesVMError(
435+
self, "netvm should not be {}".format(self.netvm)
436+
)
437+
438+
if self.is_paused():
439+
self.log.warning(
440+
"Deferred detaching libvirt net device because qube is paused"
388441
)
442+
return
389443

390-
self.libvirt_domain.detachDevice(
391-
self.app.env.get_template("libvirt/devices/net.xml").render(vm=self)
392-
)
444+
# Properties extracted from libvirt_domain to support deferred netvm.
445+
root = lxml.etree.fromstring(self.libvirt_domain.XMLDesc())
446+
eth = root.find(".//interface[@type='ethernet']")
447+
if eth is None:
448+
return
449+
self.libvirt_domain.detachDevice(lxml.etree.tostring(eth).decode())
393450

394451
def is_networked(self):
395452
"""Check whether this VM can reach network (firewall notwithstanding).
@@ -515,18 +572,27 @@ def on_property_pre_set_netvm(self, event, name, newvalue, oldvalue=None):
515572
),
516573
)
517574

518-
# don't check oldvalue, because it's missing if it was default
519-
if self.netvm is not None:
520-
if self.is_running() and self.netvm.is_running():
521-
self.detach_network()
575+
if self.is_paused():
576+
if "deferred-netvm-original" not in self.features:
577+
self.features["deferred-netvm-original"] = (
578+
oldvalue.name if oldvalue else None
579+
)
580+
return
581+
deferred_from = self.features.get("deferred-netvm-original", None)
582+
if deferred_from is None:
583+
# don't check oldvalue, because it's missing if it was default
584+
if self.netvm is None:
585+
return
586+
if not (self.is_running() and self.netvm.is_running()):
587+
return
588+
self.detach_network()
522589

523590
@qubes.events.handler("property-set:netvm")
524591
def on_property_set_netvm(self, event, name, newvalue, oldvalue=None):
525592
"""Replaces the current NetVM with a new one and fires
526593
net-domain-connect event
527594
"""
528595
# pylint: disable=unused-argument
529-
530596
if oldvalue is not None and oldvalue.is_running():
531597
oldvalue.reload_connected_ips()
532598

@@ -539,7 +605,8 @@ def on_property_set_netvm(self, event, name, newvalue, oldvalue=None):
539605
if self.is_running():
540606
# refresh IP, DNS etc
541607
self.create_qdb_entries()
542-
self.attach_network()
608+
if not self.is_paused():
609+
self.attach_network()
543610

544611
newvalue.fire_event("net-domain-connect", vm=self)
545612

templates/libvirt/devices/net.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
<script path="vif-route-qubes" />
99
</interface>
1010
11-
{# vim : set ft=jinja ts=4 sts=4 sw=4 et : #}
11+
{# vim: set ft=jinja ts=4 sts=4 sw=4 et : #}

0 commit comments

Comments
 (0)