diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 320ed9c7..4ec0aa37 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -504,7 +504,7 @@ class VirtualBoxVM(BaseVM): """ Returns either GNS3 can use any VirtualBox adapter on this instance. - :returns: index + :returns: boolean """ return self._use_any_adapter @@ -520,7 +520,7 @@ class VirtualBoxVM(BaseVM): if use_any_adapter: log.info("VirtualBox VM '{name}' [{id}] is allowed to use any adapter".format(name=self.name, id=self.id)) else: - log.info("VirtualBox VM '{name}' [{id}] is not allowd to use any adapter".format(name=self.name, id=self.id)) + log.info("VirtualBox VM '{name}' [{id}] is not allowed to use any adapter".format(name=self.name, id=self.id)) self._use_any_adapter = use_any_adapter @property diff --git a/gns3server/modules/vmware/__init__.py b/gns3server/modules/vmware/__init__.py index 1a980e6b..780b7597 100644 --- a/gns3server/modules/vmware/__init__.py +++ b/gns3server/modules/vmware/__init__.py @@ -21,11 +21,14 @@ VMware player/workstation server module. import os import sys +import re import shutil import asyncio import subprocess import logging + from collections import OrderedDict +from gns3server.utils.interfaces import interfaces log = logging.getLogger(__name__) @@ -42,6 +45,12 @@ class VMware(BaseManager): super().__init__() self._vmrun_path = None + self._vmnets = [] + self._vmnet_start_range = 2 + if sys.platform.startswith("win"): + self._vmnet_end_range = 19 + else: + self._vmnet_end_range = 255 @property def vmrun_path(self): @@ -82,6 +91,62 @@ class VMware(BaseManager): self._vmrun_path = vmrun_path return vmrun_path + @staticmethod + def get_vmnet_interfaces(): + + vmnet_interfaces = [] + for interface in interfaces(): + if sys.platform.startswith("win"): + if "netcard" in interface: + windows_name = interface["netcard"] + else: + windows_name = interface["name"] + match = re.search("(VMnet[0-9]+)", windows_name) + if match: + vmnet = match.group(1) + if vmnet not in ("VMnet1", "VMnet8"): + vmnet_interfaces.append(vmnet) + elif interface["name"].startswith("vmnet"): + vmnet = interface["name"] + if vmnet not in ("vmnet1", "vmnet8"): + vmnet_interfaces.append(interface["name"]) + return vmnet_interfaces + + def is_managed_vmnet(self, vmnet): + + self._vmnet_start_range = self.config.get_section_config("VMware").getint("vmnet_start_range", self._vmnet_start_range) + self._vmnet_end_range = self.config.get_section_config("VMware").getint("vmnet_end_range", self._vmnet_end_range) + match = re.search("vmnet([0-9]+)$", vmnet, re.IGNORECASE) + if match: + vmnet_number = match.group(1) + if self._vmnet_start_range <= int(vmnet_number) <= self._vmnet_end_range: + return True + return False + + def allocate_vmnet(self): + + if not self._vmnets: + raise VMwareError("No more VMnet interfaces available") + return self._vmnets.pop(0) + + def refresh_vmnet_list(self): + + vmnet_interfaces = self.get_vmnet_interfaces() + + # remove vmnets already in use + for vm in self._vms.values(): + for used_vmnet in vm.vmnets: + if used_vmnet in vmnet_interfaces: + log.debug("{} is already in use".format(used_vmnet)) + vmnet_interfaces.remove(used_vmnet) + + # remove vmnets that are not managed + for vmnet in vmnet_interfaces.copy(): + if vmnet in vmnet_interfaces and self.is_managed_vmnet(vmnet) is False: + vmnet_interfaces.remove(vmnet) + + self._vmnets = vmnet_interfaces + @property def host_type(self): """ diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py index 353fd167..2df012f0 100644 --- a/gns3server/modules/vmware/vmware_vm.py +++ b/gns3server/modules/vmware/vmware_vm.py @@ -30,9 +30,9 @@ import configparser import shutil import asyncio -from gns3server.utils.interfaces import interfaces from gns3server.utils.asyncio import wait_for_process_termination from gns3server.utils.asyncio import monitor_process +from collections import OrderedDict from pkg_resources import parse_version from .vmware_error import VMwareError from ..nios.nio_udp import NIOUDP @@ -56,8 +56,11 @@ class VMwareVM(BaseVM): super().__init__(name, vm_id, project, manager, console=console) self._linked_clone = linked_clone + self._vmx_pairs = OrderedDict() self._ubridge_process = None self._ubridge_stdout_file = "" + self._vmnets = [] + self._maximum_adapters = 10 self._closed = False # VMware VM settings @@ -67,6 +70,7 @@ class VMwareVM(BaseVM): self._adapters = 0 self._ethernet_adapters = {} self._adapter_type = "e1000" + self._use_any_adapter = False if not os.path.exists(vmx_path): raise VMwareError('VMware VM "{name}" [{id}]: could not find VMX file "{}"'.format(name, vmx_path)) @@ -81,7 +85,13 @@ class VMwareVM(BaseVM): "headless": self.headless, "enable_remote_console": self.enable_remote_console, "adapters": self._adapters, - "adapter_type": self.adapter_type} + "adapter_type": self.adapter_type, + "use_any_adapter": self.use_any_adapter} + + @property + def vmnets(self): + + return self._vmnets @asyncio.coroutine def _control_vm(self, subcommand, *additional_args): @@ -92,25 +102,15 @@ class VMwareVM(BaseVM): log.debug("Control VM '{}' result: {}".format(subcommand, result)) return result - def _get_vmnet_interfaces(self): + def _get_vmx_setting(self, name, value=None): - vmnet_intefaces = [] - for interface in interfaces(): - if sys.platform.startswith("win"): - if "netcard" in interface: - windows_name = interface["netcard"] - else: - windows_name = interface["name"] - match = re.search("(VMnet[0-9]+)", windows_name) - if match: - vmnet = match.group(1) - if vmnet not in ("VMnet1", "VMnet8"): - vmnet_intefaces.append(vmnet) - elif interface["name"].startswith("vmnet"): - vmnet = interface["name"] - if vmnet not in ("vmnet1", "vmnet8"): - vmnet_intefaces.append(interface["name"]) - return vmnet_intefaces + if name in self._vmx_pairs: + if value is not None: + if self._vmx_pairs[name] == value: + return value + else: + return self._vmx_pairs[name] + return None def _set_network_options(self): @@ -119,43 +119,70 @@ class VMwareVM(BaseVM): except OSError as e: raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) - vmnet_interfaces = self._get_vmnet_interfaces() + # first to some sanity checks for adapter_number in range(0, self._adapters): - nio = self._ethernet_adapters[adapter_number].get_nio(0) - if nio: - if "ethernet{}.present".format(adapter_number) in self._vmx_pairs: - - # check for the connection type - connection_type = "ethernet{}.connectionType".format(adapter_number) - if connection_type in self._vmx_pairs: - if self._vmx_pairs[connection_type] not in ("hostonly", "custom"): - raise VMwareError("Attachment ({}) already configured on adapter {}. " - "Please set it to 'hostonly' or 'custom' to allow GNS3 to use it.".format(self._vmx_pairs[connection_type], - adapter_number)) - # check for the vmnet interface + connected = "ethernet{}.startConnected".format(adapter_number) + if self._get_vmx_setting(connected): + del self._vmx_pairs[connected] + + # check if any vmnet interface managed by GNS3 is being used on existing VMware adapters + if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + connection_type = "ethernet{}.connectionType".format(adapter_number) + if self._vmx_pairs[connection_type] in ("hostonly", "custom"): vnet = "ethernet{}.vnet".format(adapter_number) if vnet in self._vmx_pairs: vmnet = os.path.basename(self._vmx_pairs[vnet]) - if vmnet in vmnet_interfaces: - vmnet_interfaces.remove(vmnet) - else: - raise VMwareError("Network adapter {} is not associated with a VMnet interface".format(adapter_number)) - - # check for adapter type - # adapter_type = "ethernet{}.virtualDev".format(adapter_number) - # if adapter_type in self._vmx_pairs and self._vmx_pairs[adapter_type] != self._adapter_type: - # raise VMwareError("Network adapter {} is not of type {}".format(self._adapter_type)) - # else: - # self._vmx_pairs[adapter_type] = self._adapter_type - else: - new_ethernet_adapter = {"ethernet{}.present".format(adapter_number): "TRUE", - "ethernet{}.connectionType".format(adapter_number): "custom", - "ethernet{}.vnet".format(adapter_number): "vmnet1", - "ethernet{}.addressType".format(adapter_number): "generated", - "ethernet{}.generatedAddressOffset".format(adapter_number): "0"} - self._vmx_pairs.update(new_ethernet_adapter) + if self.manager.is_managed_vmnet(vmnet): + raise VMwareError("Network adapter {} is already associated with VMnet interface {} which is managed by GNS3, please remove".format(adapter_number, vmnet)) + + # check for adapter type + if self._adapter_type != "default": + adapter_type = "ethernet{}.virtualDev".format(adapter_number) + if adapter_type in self._vmx_pairs and self._vmx_pairs[adapter_type] != self._adapter_type: + raise VMwareError("Network adapter {} is not of type {}, please fix or remove it".format(self._adapter_type)) + + # check if connected to an adapter configured for nat or bridge + if self._ethernet_adapters[adapter_number].get_nio(0) and not self._use_any_adapter: + if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + # check for the connection type + connection_type = "ethernet{}.connectionType".format(adapter_number) + if connection_type in self._vmx_pairs: + if self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"): + raise VMwareError("Attachment ({}) already configured on network adapter {}. " + "Please remove it or allow GNS3 to use any adapter.".format(self._vmx_pairs[connection_type], + adapter_number)) - #raise VMwareError("Network adapter {} does not exist".format(adapter_number)) + # now configure VMware network adapters + self.manager.refresh_vmnet_list() + for adapter_number in range(0, self._adapters): + ethernet_adapter = {"ethernet{}.present".format(adapter_number): "TRUE", + "ethernet{}.addressType".format(adapter_number): "generated", + "ethernet{}.generatedAddressOffset".format(adapter_number): "0"} + self._vmx_pairs.update(ethernet_adapter) + if self._adapter_type != "default": + self._vmx_pairs["ethernet{}.virtualDev".format(adapter_number)] = self._adapter_type + + connection_type = "ethernet{}.connectionType".format(adapter_number) + if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"): + continue + + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet in self._vmx_pairs: + vmnet = os.path.basename(self._vmx_pairs[vnet]) + else: + try: + vmnet = self.manager.allocate_vmnet() + finally: + self._vmnets.clear() + self._vmnets.append(vmnet) + self._vmx_pairs["ethernet{}.connectionType".format(adapter_number)] = "custom" + self._vmx_pairs["ethernet{}.vnet".format(adapter_number)] = vmnet + + # disable remaining network adapters + for adapter_number in range(self._adapters, self._maximum_adapters): + if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + log.debug("disabling remaining adapter {}".format(adapter_number)) + self._vmx_pairs["ethernet{}.startConnected".format(adapter_number)] = "FALSE" self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) self._update_ubridge_config() @@ -180,7 +207,7 @@ class VMwareVM(BaseVM): if sys.platform.startswith("linux"): config[bridge_name] = {"source_linux_raw": vmnet_interface} elif sys.platform.startswith("win"): - windows_interfaces = interfaces() + windows_interfaces = self.manager.get_vmnet_interfaces() npf = None for interface in windows_interfaces: if "netcard" in interface and vmnet_interface in interface["netcard"]: @@ -333,7 +360,39 @@ class VMwareVM(BaseVM): self._ubridge_process.kill() self._ubridge_process = None - yield from self._control_vm("stop") + try: + yield from self._control_vm("stop") + finally: + + self._vmnets.clear() + + try: + self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) + except OSError as e: + raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) + + # remove the adapters managed by GNS3 + for adapter_number in range(0, self._adapters): + if self._get_vmx_setting("ethernet{}.vnet".format(adapter_number)) or \ + self._get_vmx_setting("ethernet{}.connectionType".format(adapter_number)) is None: + vnet = "ethernet{}.vnet".format(adapter_number) + if vnet in self._vmx_pairs: + vmnet = os.path.basename(self._vmx_pairs[vnet]) + if not self.manager.is_managed_vmnet(vmnet): + continue + log.debug("removing adapter {}".format(adapter_number)) + for key in self._vmx_pairs.keys(): + if key.startswith("ethernet{}.".format(adapter_number)): + del self._vmx_pairs[key] + + # re-enable any remaining network adapters + for adapter_number in range(self._adapters, self._maximum_adapters): + if self._get_vmx_setting("ethernet{}.present".format(adapter_number), "TRUE"): + log.debug("enabling remaining adapter {}".format(adapter_number)) + self._vmx_pairs["ethernet{}.startConnected".format(adapter_number)] = "TRUE" + + self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) + log.info("VMware VM '{name}' [{id}] stopped".format(name=self.name, id=self.id)) @asyncio.coroutine @@ -521,6 +580,30 @@ class VMwareVM(BaseVM): id=self.id, adapter_type=adapter_type)) + @property + def use_any_adapter(self): + """ + Returns either GNS3 can use any VMware adapter on this instance. + + :returns: boolean + """ + + return self._use_any_adapter + + @use_any_adapter.setter + def use_any_adapter(self, use_any_adapter): + """ + Allows GNS3 to use any VMware adapter on this instance. + + :param use_any_adapter: boolean + """ + + if use_any_adapter: + log.info("VMware VM '{name}' [{id}] is allowed to use any adapter".format(name=self.name, id=self.id)) + else: + log.info("VMware VM '{name}' [{id}] is not allowed to use any adapter".format(name=self.name, id=self.id)) + self._use_any_adapter = use_any_adapter + def adapter_add_nio_binding(self, adapter_number, nio): """ Adds an adapter NIO binding. diff --git a/gns3server/schemas/vmware.py b/gns3server/schemas/vmware.py index 28fb78a1..a0465a3e 100644 --- a/gns3server/schemas/vmware.py +++ b/gns3server/schemas/vmware.py @@ -67,6 +67,10 @@ VMWARE_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "use_any_adapter": { + "description": "allow GNS3 to use any VMware adapter", + "type": "boolean", + }, }, "additionalProperties": False, "required": ["name", "vmx_path", "linked_clone"], @@ -112,6 +116,10 @@ VMWARE_UPDATE_SCHEMA = { "type": "string", "minLength": 1, }, + "use_any_adapter": { + "description": "allow GNS3 to use any VMware adapter", + "type": "boolean", + }, }, "additionalProperties": False, } @@ -164,6 +172,10 @@ VMWARE_OBJECT_SCHEMA = { "type": "string", "minLength": 1, }, + "use_any_adapter": { + "description": "allow GNS3 to use any VMware adapter", + "type": "boolean", + }, "console": { "description": "console TCP port", "minimum": 1,