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,12 +429,48 @@ 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
443+
444+ # Properties extracted from libvirt_domain to support deferred netvm.
445+ root = lxml .etree .fromstring (self .libvirt_domain .XMLDesc ())
446+ interface = root .find (".//interface[@type='ethernet']" )
447+ if interface is None :
448+ return
389449
450+ class NetVM : # pylint: disable=too-few-public-methods
451+ name = None
452+
453+ class VM : # pylint: disable=too-few-public-methods
454+ mac = ip = ip6 = None
455+ netvm = NetVM ()
456+
457+ vm = VM ()
458+ backend_elem = interface .find ("backenddomain" )
459+ if backend_elem is not None :
460+ netvm_name = backend_elem .get ("name" )
461+ if netvm_name not in self .app .domains :
462+ return
463+ vm .netvm .name = netvm_name
464+ mac_elem = interface .find ("mac" )
465+ if mac_elem is not None :
466+ vm .mac = mac_elem .get ("address" )
467+ for ip_elem in interface .findall ("ip" ):
468+ if ip_elem .get ("family" ) == "ipv4" :
469+ vm .ip = ip_elem .get ("address" )
470+ elif ip_elem .get ("family" ) == "ipv6" :
471+ vm .ip6 = ip_elem .get ("address" )
390472 self .libvirt_domain .detachDevice (
391- self .app .env .get_template ("libvirt/devices/net.xml" ).render (vm = self )
473+ self .app .env .get_template ("libvirt/devices/net.xml" ).render (vm = vm )
392474 )
393475
394476 def is_networked (self ):
@@ -515,18 +597,27 @@ def on_property_pre_set_netvm(self, event, name, newvalue, oldvalue=None):
515597 ),
516598 )
517599
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 ()
600+ if self .is_paused ():
601+ if "deferred-netvm-original" not in self .features :
602+ self .features ["deferred-netvm-original" ] = (
603+ oldvalue .name if oldvalue else None
604+ )
605+ return
606+ deferred_from = self .features .get ("deferred-netvm-original" , None )
607+ if deferred_from is None :
608+ # don't check oldvalue, because it's missing if it was default
609+ if self .netvm is None :
610+ return
611+ if not (self .is_running () and self .netvm .is_running ()):
612+ return
613+ self .detach_network ()
522614
523615 @qubes .events .handler ("property-set:netvm" )
524616 def on_property_set_netvm (self , event , name , newvalue , oldvalue = None ):
525617 """Replaces the current NetVM with a new one and fires
526618 net-domain-connect event
527619 """
528620 # pylint: disable=unused-argument
529-
530621 if oldvalue is not None and oldvalue .is_running ():
531622 oldvalue .reload_connected_ips ()
532623
@@ -539,7 +630,8 @@ def on_property_set_netvm(self, event, name, newvalue, oldvalue=None):
539630 if self .is_running ():
540631 # refresh IP, DNS etc
541632 self .create_qdb_entries ()
542- self .attach_network ()
633+ if not self .is_paused ():
634+ self .attach_network ()
543635
544636 newvalue .fire_event ("net-domain-connect" , vm = self )
545637
0 commit comments