net.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/usr/bin/python2 -O
  2. # vim: fileencoding=utf-8
  3. #
  4. # The Qubes OS Project, https://www.qubes-os.org/
  5. #
  6. # Copyright (C) 2010-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. # Copyright (C) 2013-2016 Marek Marczykowski-Górecki
  8. # <marmarek@invisiblethingslab.com>
  9. # Copyright (C) 2014-2016 Wojtek Porczyk <woju@invisiblethingslab.com>
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License along
  22. # with this program; if not, write to the Free Software Foundation, Inc.,
  23. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  24. #
  25. import os
  26. import shutil
  27. import time
  28. import weakref
  29. import libvirt
  30. import lxml.etree
  31. import qubes
  32. import qubes.exc
  33. class NetVMMixin(object):
  34. mac = qubes.property('mac', type=str,
  35. default=(lambda self: '00:16:3E:5E:6C:{:02X}'.format(self.qid)),
  36. ls_width=17,
  37. doc='MAC address of the NIC emulated inside VM')
  38. # XXX swallowed uses_default_netvm
  39. netvm = qubes.VMProperty('netvm', load_stage=4, allow_none=True,
  40. default=(lambda self: self.app.default_fw_netvm if self.provides_network
  41. else self.app.default_netvm),
  42. ls_width=31,
  43. doc='''VM that provides network connection to this domain. When
  44. `None`, machine is disconnected. When absent, domain uses default
  45. NetVM.''')
  46. provides_network = qubes.property('provides_network', default=False,
  47. type=bool, setter=qubes.property.bool,
  48. doc='''If this domain can act as network provider (formerly known as
  49. NetVM or ProxyVM)''')
  50. #
  51. # used in networked appvms or proxyvms (netvm is not None)
  52. #
  53. @qubes.tools.qvm_ls.column(width=15)
  54. @property
  55. def ip(self):
  56. '''IP address of this domain.'''
  57. if not self.is_networked():
  58. return None
  59. if self.netvm is not None:
  60. return self.netvm.get_ip_for_vm(self)
  61. else:
  62. return self.get_ip_for_vm(self)
  63. #
  64. # used in netvms (provides_network=True)
  65. # those properties and methods are most likely accessed as vm.netvm.<prop>
  66. #
  67. @staticmethod
  68. def get_ip_for_vm(vm):
  69. '''Get IP address for (appvm) domain connected to this (netvm) domain.
  70. '''
  71. import qubes.vm.dispvm # pylint: disable=redefined-outer-name
  72. if isinstance(vm, qubes.vm.dispvm.DispVM):
  73. return '10.138.{}.{}'.format((vm.dispid >> 8) & 7, vm.dispid & 7)
  74. # VM technically can get address which ends in '.0'. This currently
  75. # does not happen, because qid < 253, but may happen in the future.
  76. return '10.137.{}.{}'.format((vm.qid >> 8) & 7, vm.qid & 7)
  77. @qubes.tools.qvm_ls.column(head='IPBACK', width=15)
  78. @property
  79. def gateway(self):
  80. '''Gateway for other domains that use this domain as netvm.'''
  81. return self.ip if self.provides_network else None
  82. @qubes.tools.qvm_ls.column(width=15)
  83. @property
  84. def netmask(self):
  85. '''Netmask for gateway address.'''
  86. return '255.255.255.255' if self.is_networked() else None
  87. @qubes.tools.qvm_ls.column(width=7)
  88. @property
  89. def vif(self):
  90. '''Name of the network interface backend in netvm that is connected to
  91. NIC inside this domain.'''
  92. if self.xid < 0:
  93. return None
  94. if self.netvm is None:
  95. return None
  96. return "vif{0}.+".format(self.xid)
  97. #
  98. # used in both
  99. #
  100. @qubes.tools.qvm_ls.column(width=15)
  101. @property
  102. def dns(self):
  103. '''Secondary DNS server set up for this domain.'''
  104. if self.netvm is not None or self.provides_network:
  105. return (
  106. '10.139.1.1',
  107. '10.139.1.2',
  108. )
  109. else:
  110. return None
  111. def __init__(self, *args, **kwargs):
  112. super(NetVMMixin, self).__init__(*args, **kwargs)
  113. self.connected_vms = weakref.WeakSet()
  114. @qubes.events.handler('domain-started')
  115. def start_net(self):
  116. '''Connect this domain to its downstream domains.
  117. This is needed when starting netvm *after* its connected domains.
  118. '''
  119. for vm in self.connected_vms:
  120. if not vm.is_running():
  121. continue
  122. vm.log.info('Attaching network')
  123. # 1426
  124. vm.cleanup_vifs()
  125. try:
  126. # 1426
  127. vm.run('modprobe -r xen-netfront xennet',
  128. user='root', wait=True)
  129. except:
  130. pass
  131. try:
  132. vm.attach_network(wait=False)
  133. except qubes.exc.QubesException:
  134. vm.log.warning('Cannot attach network', exc_info=1)
  135. @qubes.events.handler('pre-domain-shutdown')
  136. def shutdown_net(self, force=False):
  137. connected_vms = [vm for vm in self.connected_vms if vm.is_running()]
  138. if connected_vms and not force:
  139. raise qubes.exc.QubesVMError(
  140. 'There are other VMs connected to this VM: {}'.format(
  141. ', '.join(vm.name for vm in connected_vms)))
  142. # detach network interfaces of connected VMs before shutting down,
  143. # otherwise libvirt will not notice it and will try to detach them
  144. # again (which would fail, obviously).
  145. # This code can be removed when #1426 got implemented
  146. for vm in connected_vms:
  147. if vm.is_running():
  148. try:
  149. vm.detach_network()
  150. except (qubes.exc.QubesException, libvirt.libvirtError):
  151. # ignore errors
  152. pass
  153. # TODO maybe this should be other way: backend.devices['net'].attach(self)
  154. def attach_network(self):
  155. '''Attach network in this machine to it's netvm.'''
  156. if not self.is_running():
  157. raise qubes.exc.QubesVMNotRunningError(self)
  158. assert self.netvm is not None
  159. if not self.netvm.is_running():
  160. self.log.info('Starting NetVM ({0})'.format(self.netvm.name))
  161. self.netvm.start()
  162. self.libvirt_domain.attachDevice(lxml.etree.ElementTree(
  163. self.lvxml_net_dev(self.ip, self.mac, self.netvm)).tostring())
  164. def detach_network(self):
  165. '''Detach machine from it's netvm'''
  166. if not self.is_running():
  167. raise qubes.exc.QubesVMNotRunningError(self)
  168. assert self.netvm is not None
  169. self.libvirt_domain.detachDevice(lxml.etree.ElementTree(
  170. self.lvxml_net_dev(self.ip, self.mac, self.netvm)).tostring())
  171. def is_networked(self):
  172. '''Check whether this VM can reach network (firewall notwithstanding).
  173. :returns: :py:obj:`True` if is machine can reach network, \
  174. :py:obj:`False` otherwise.
  175. :rtype: bool
  176. '''
  177. if self.provides_network:
  178. return True
  179. return self.netvm is not None
  180. def cleanup_vifs(self):
  181. '''Remove stale network device backends.
  182. Libvirt does not remove vif when backend domain is down, so we must do
  183. it manually. This method is one big hack for #1426.
  184. '''
  185. # FIXME: remove this?
  186. if not self.is_running():
  187. return
  188. dev_basepath = '/local/domain/%d/device/vif' % self.xid
  189. for dev in self.app.vmm.xs.ls('', dev_basepath):
  190. # check if backend domain is alive
  191. backend_xid = int(self.app.vmm.xs.read('',
  192. '{}/{}/backend-id'.format(dev_basepath, dev)))
  193. if backend_xid in self.app.vmm.libvirt_conn.listDomainsID():
  194. # check if device is still active
  195. if self.app.vmm.xs.read('',
  196. '{}/{}/state'.format(dev_basepath, dev)) == '4':
  197. continue
  198. # remove dead device
  199. self.app.vmm.xs.rm('', '{}/{}'.format(dev_basepath, dev))
  200. @qubes.events.handler('property-del:netvm')
  201. def on_property_del_netvm(self, event, name, old_netvm):
  202. # pylint: disable=unused-argument
  203. # we are changing to default netvm
  204. new_netvm = self.netvm
  205. if new_netvm == old_netvm:
  206. return
  207. self.fire_event('property-set:netvm', 'netvm', new_netvm, old_netvm)
  208. @qubes.events.handler('property-set:netvm')
  209. def on_property_set_netvm(self, event, name, new_netvm, old_netvm=None):
  210. # pylint: disable=unused-argument
  211. # TODO offline_mode
  212. if self.is_running() and new_netvm is not None \
  213. and not new_netvm.is_running():
  214. raise qubes.exc.QubesVMNotStartedError(new_netvm,
  215. 'Cannot dynamically attach to stopped NetVM: {!r}'.format(
  216. new_netvm))
  217. if self.netvm is not None:
  218. self.netvm.connected_vms.remove(self)
  219. if self.is_running():
  220. self.detach_network()
  221. # TODO change to domain-removed event handler in netvm
  222. # if hasattr(self.netvm, 'post_vm_net_detach'):
  223. # self.netvm.post_vm_net_detach(self)
  224. if new_netvm is None:
  225. # if not self._do_not_reset_firewall:
  226. # Set also firewall to block all traffic as discussed in #370
  227. if os.path.exists(self.firewall_conf):
  228. shutil.copy(self.firewall_conf,
  229. os.path.join(qubes.config.system_path['qubes_base_dir'],
  230. 'backup',
  231. '%s-firewall-%s.xml' % (self.name,
  232. time.strftime('%Y-%m-%d-%H:%M:%S'))))
  233. self.write_firewall_conf({'allow': False, 'allowDns': False,
  234. 'allowIcmp': False, 'allowYumProxy': False, 'rules': []})
  235. else:
  236. new_netvm.connected_vms.add(self)
  237. if new_netvm is None:
  238. return
  239. if self.is_running():
  240. # refresh IP, DNS etc
  241. self.create_qdb_entries()
  242. self.attach_network()
  243. # TODO documentation
  244. new_netvm.fire_event('net-domain-connected', self)