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"""
2424import ipaddress
2525import os
2626import re
2727
2828import libvirt # pylint: disable=import-error
29+ import lxml .etree
2930import qubes
3031import qubes .config
3132import 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
0 commit comments