diff --git a/CHANGELOG b/CHANGELOG index aeadeea7..006554dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,20 @@ # Change Log +## 1.5.0b1 23/05/2016 + +* Allow an IOS router to stop even the Dynamips hypervisor command fail to be sent. Ref #488. +* Extract private-config only when necessary (content is different than the default). Fixes #520. +* Fixes disabling the VPCS relay feature. Fixes #521. +* Fixes wrong exception in Docker VM implementation. +* Force Npcap DLL to be used first for Dynamips and uBridge (instead of the one from Winpcap if installed). +* Fixed startup-config is lost if you change any IOS router settings. Fixes #1233. +* Fixes check for NPF service and add check for NPCAP service on Windows. +* Fix ProcessLookupError X11VNC +* Force tag latest for docker image if no tag is specified +* Cleanup unbreakable space +* Do not raise error if vmrun.exe is named vmrun.EXE +* Load docker api only for Linux + ## 1.5.0a2 10/05/2016 * Fix distribution on PyPi diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index fc79be0b..3be7e6dc 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -34,6 +34,7 @@ from gns3server.utils.get_resource import get_resource from gns3server.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError from ..base_node import BaseNode + from ..adapters.ethernet_adapter import EthernetAdapter from ..nios.nio_udp import NIOUDP from .docker_error import ( diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index 27948622..548619f6 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -602,8 +602,8 @@ class Dynamips(BaseManager): elif startup_config_content: startup_config_path = self._create_config(vm, default_startup_config_path, startup_config_content) yield from vm.set_configs(startup_config_path) - # An empty startup config crash dynamips - else: + elif os.path.isfile(default_startup_config_path) and os.path.getsize(default_startup_config_path) == 0: + # An empty startup-config may crash Dynamips startup_config_path = self._create_config(vm, default_startup_config_path, "!\n") yield from vm.set_configs(startup_config_path) diff --git a/gns3server/compute/dynamips/hypervisor.py b/gns3server/compute/dynamips/hypervisor.py index 18c35352..a94e32df 100644 --- a/gns3server/compute/dynamips/hypervisor.py +++ b/gns3server/compute/dynamips/hypervisor.py @@ -19,6 +19,7 @@ Represents a Dynamips hypervisor and starts/stops the associated Dynamips process. """ +import sys import os import subprocess import asyncio @@ -117,6 +118,12 @@ class Hypervisor(DynamipsHypervisor): """ self._command = self._build_command() + env = os.environ.copy() + if sys.platform.startswith("win"): + # add the Npcap directory to $PATH to force Dynamips to use npcap DLL instead of Winpcap (if installed) + system_root = os.path.join(os.path.expandvars("%SystemRoot%"), "System32", "Npcap") + if os.path.isdir(system_root): + env["PATH"] = system_root + ';' + env["PATH"] try: log.info("Starting Dynamips: {}".format(self._command)) self._stdout_file = os.path.join(self.working_dir, "dynamips_i{}_stdout.txt".format(self._id)) @@ -125,7 +132,8 @@ class Hypervisor(DynamipsHypervisor): self._process = yield from asyncio.create_subprocess_exec(*self._command, stdout=fd, stderr=subprocess.STDOUT, - cwd=self._working_dir) + cwd=self._working_dir, + env=env) log.info("Dynamips process started PID={}".format(self._process.pid)) self._started = True except (OSError, subprocess.SubprocessError) as e: diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index 38dd4c03..e53f6fc6 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -272,7 +272,10 @@ class Router(BaseNode): status = yield from self.get_status() if status != "inactive": - yield from self._hypervisor.send('vm stop "{name}"'.format(name=self._name)) + try: + yield from self._hypervisor.send('vm stop "{name}"'.format(name=self._name)) + except DynamipsError as e: + log.warn("Could not stop {}: {}".format(self._name, e)) self.status = "stopped" log.info('Router "{name}" [{id}] has been stopped'.format(name=self._name, id=self._id)) yield from self.save_configs() @@ -338,8 +341,8 @@ class Router(BaseNode): try: yield from self.stop() yield from self._hypervisor.send('vm delete "{}"'.format(self._name)) - except DynamipsError: - pass + except DynamipsError as e: + log.warn("Could not stop and delete {}: {}".format(self._name, e)) yield from self.hypervisor.stop() if self._auto_delete_disks: @@ -1533,7 +1536,6 @@ class Router(BaseNode): if startup_config_base64: if not self.startup_config: self._startup_config = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id)) - try: config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace") config = "!\n" + config.replace("\r", "") @@ -1544,13 +1546,11 @@ class Router(BaseNode): except (binascii.Error, OSError) as e: raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e)) - if private_config_base64: + if private_config_base64 and base64.b64decode(private_config_base64) != b'\nkerberos password \nend\n': if not self.private_config: self._private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id)) - try: config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace") - config = "!\n" + config.replace("\r", "") config_path = os.path.join(module_workdir, self.private_config) with open(config_path, "wb") as f: log.info("saving private-config to {}".format(self.private_config)) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index 0e847c3e..8d07ac33 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -108,7 +108,6 @@ class IOUVM(BaseNode): self.manager.port_manager.release_udp_port(nio.lport, self._project) yield from self.stop() - self.save_configs() @property def path(self): @@ -683,6 +682,7 @@ class IOUVM(BaseNode): self._iouyap_process = None self._started = False + self.save_configs() def _terminate_process_iouyap(self): """ @@ -1099,7 +1099,7 @@ class IOUVM(BaseNode): if private_config is None: private_config = '' - # We disallow erasing the startup config file + # We disallow erasing the private config file if len(private_config) == 0 and os.path.exists(private_config_path): return @@ -1206,18 +1206,16 @@ class IOUVM(BaseNode): config_path = os.path.join(self.working_dir, "startup-config.cfg") try: config = startup_config_content.decode("utf-8", errors="replace") - config = "!\n" + config.replace("\r", "") with open(config_path, "wb") as f: log.info("saving startup-config to {}".format(config_path)) f.write(config.encode("utf-8")) except (binascii.Error, OSError) as e: raise IOUError("Could not save the startup configuration {}: {}".format(config_path, e)) - if private_config_content: + if private_config_content and private_config_content != b'\nend\n': config_path = os.path.join(self.working_dir, "private-config.cfg") try: config = private_config_content.decode("utf-8", errors="replace") - config = "!\n" + config.replace("\r", "") with open(config_path, "wb") as f: log.info("saving private-config to {}".format(config_path)) f.write(config.encode("utf-8")) diff --git a/gns3server/compute/vpcs/vpcs_vm.py b/gns3server/compute/vpcs/vpcs_vm.py index 0e5154ca..df24a7f0 100644 --- a/gns3server/compute/vpcs/vpcs_vm.py +++ b/gns3server/compute/vpcs/vpcs_vm.py @@ -420,8 +420,10 @@ class VPCSVM(BaseNode): command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset command.extend(["-i", "1"]) # option to start only one VPC instance command.extend(["-F"]) # option to avoid the daemonization of VPCS - if self._vpcs_version > parse_version("0.8"): - command.extend(["-R"]) # disable relay feature of VPCS (starting with VPCS 0.8) + if self._vpcs_version >= parse_version("0.8b"): + command.extend(["-R"]) # disable the relay feature of VPCS (starting with VPCS 0.8) + else: + log.warn("The VPCS relay feature could not be disabled because the VPCS version is below 0.8b") nio = self._ethernet_adapter.get_nio(0) if nio: diff --git a/gns3server/ubridge/hypervisor.py b/gns3server/ubridge/hypervisor.py index 929d06f6..7c43e838 100644 --- a/gns3server/ubridge/hypervisor.py +++ b/gns3server/ubridge/hypervisor.py @@ -19,6 +19,7 @@ Represents a uBridge hypervisor and starts/stops the associated uBridge process. """ +import sys import os import subprocess import asyncio @@ -140,6 +141,12 @@ class Hypervisor(UBridgeHypervisor): """ yield from self._check_ubridge_version() + env = os.environ.copy() + if sys.platform.startswith("win"): + # add the Npcap directory to $PATH to force Dynamips to use npcap DLL instead of Winpcap (if installed) + system_root = os.path.join(os.path.expandvars("%SystemRoot%"), "System32", "Npcap") + if os.path.isdir(system_root): + env["PATH"] = system_root + ';' + env["PATH"] try: command = self._build_command() log.info("starting ubridge: {}".format(command)) @@ -149,7 +156,8 @@ class Hypervisor(UBridgeHypervisor): self._process = yield from asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, - cwd=self._working_dir) + cwd=self._working_dir, + env=env) log.info("ubridge started PID={}".format(self._process.pid)) except (OSError, subprocess.SubprocessError) as e: diff --git a/gns3server/utils/interfaces.py b/gns3server/utils/interfaces.py index 1f53e2c0..bb12151d 100644 --- a/gns3server/utils/interfaces.py +++ b/gns3server/utils/interfaces.py @@ -138,6 +138,21 @@ def is_interface_up(interface): # TODO: Windows & OSX support return True +def _check_windows_service(service_name): + + import pywintypes + import win32service + import win32serviceutil + + try: + if win32serviceutil.QueryServiceStatus(service_name, None)[1] != win32service.SERVICE_RUNNING: + return False + except pywintypes.error as e: + if e.winerror == 1060: + return False + else: + raise aiohttp.web.HTTPInternalServerError(text="Could not check if the {} service is running: {}".format(service_name, e.strerror)) + return True def interfaces(): """ @@ -163,19 +178,8 @@ def interfaces(): "mac_address": mac_address}) else: try: - import pywintypes - import win32service - import win32serviceutil - - try: - if win32serviceutil.QueryServiceStatus("npf", None)[1] != win32service.SERVICE_RUNNING: - raise aiohttp.web.HTTPInternalServerError(text="The NPF service is not running") - except pywintypes.error as e: - if e[0] == 1060: - raise aiohttp.web.HTTPInternalServerError(text="The NPF service is not installed") - else: - raise aiohttp.web.HTTPInternalServerError(text="Could not check if the NPF service is running: {}".format(e[2])) - + if not _check_windows_service("npf") and not _check_windows_service("npcap"): + raise aiohttp.web.HTTPInternalServerError("The NPF or Npcap is not installed or running") results = get_windows_interfaces() except ImportError: message = "pywin32 module is not installed, please install it on the server to get the available interface names" diff --git a/utils/vmnet.py b/utils/vmnet.py index 1f437411..a9be3f1c 100644 --- a/utils/vmnet.py +++ b/utils/vmnet.py @@ -168,7 +168,8 @@ def vmnet_windows(args, vmnet_range_start, vmnet_range_end): os.system('"{}" -- add adapter vmnet{}'.format(vnetlib_path, vmnet_number)) os.system("net stop npf") os.system("net start npf") - + os.system("net stop npcap") + os.system("net start npcap") def vmnet_unix(args, vmnet_range_start, vmnet_range_end): """