diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py index ca127009..de1a5c0b 100644 --- a/gns3server/handlers/__init__.py +++ b/gns3server/handlers/__init__.py @@ -1 +1 @@ -__all__ = ['version_handler', 'vpcs_handler', 'project_handler', 'virtualbox_handler'] +__all__ = ['version_handler', 'network_handler', 'vpcs_handler', 'project_handler', 'virtualbox_handler'] diff --git a/gns3server/handlers/network_handler.py b/gns3server/handlers/network_handler.py new file mode 100644 index 00000000..c653c704 --- /dev/null +++ b/gns3server/handlers/network_handler.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from ..web.route import Route +from ..modules.port_manager import PortManager +from ..utils.interfaces import interfaces + + +class NetworkHandler: + + @classmethod + @Route.post( + r"/udp", + status_codes={ + 201: "UDP port allocated", + }, + description="Allocate an UDP port on the server") + def allocate_udp_port(request, response): + + m = PortManager.instance() + udp_port = m.get_free_udp_port() + response.set_status(201) + response.json({"udp_port": udp_port}) + + @classmethod + @Route.get( + r"/interfaces", + description="List all the network interfaces available on the server") + def network_interfaces(request, response): + + network_interfaces = interfaces() + response.json(network_interfaces) diff --git a/gns3server/modules/port_manager.py b/gns3server/modules/port_manager.py index b2316048..b03a4025 100644 --- a/gns3server/modules/port_manager.py +++ b/gns3server/modules/port_manager.py @@ -51,6 +51,21 @@ class PortManager: else: self._console_host = host + assert not hasattr(PortManager, "_instance") + PortManager._instance = self + + @classmethod + def instance(cls): + """ + Singleton to return only one instance of PortManager. + + :returns: instance of PortManager + """ + + if not hasattr(cls, "_instance") or cls._instance is None: + cls._instance = cls() + return cls._instance + @property def console_host(self): diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 8822fc3e..4873607d 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -807,7 +807,10 @@ class VirtualBoxVM(BaseVM): yield from self._control_vm("nic{} null".format(adapter_id + 1)) nio = adapter.get_nio(0) + if str(nio) == "NIO UDP": + self.manager.port_manager.release_udp_port(nio.lport) adapter.remove_nio(0) + log.info("VirtualBox VM '{name}' [{uuid}]: {nio} removed from adapter {adapter_id}".format(name=self.name, uuid=self.uuid, nio=nio, diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 00c20847..1872da3e 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -343,7 +343,10 @@ class VPCSVM(BaseVM): port_id=port_id)) nio = self._ethernet_adapter.get_nio(port_id) + if str(nio) == "NIO UDP": + self.manager.port_manager.release_udp_port(nio.lport) self._ethernet_adapter.remove_nio(port_id) + log.info("VPCS {name} [{uuid}]: {nio} removed from port {port_id}".format(name=self._name, uuid=self.uuid, nio=nio, diff --git a/gns3server/server.py b/gns3server/server.py index e616007c..e2d7809a 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -50,22 +50,6 @@ class Server: self._start_time = time.time() self._port_manager = PortManager(host) - # TODO: server config file support, to be reviewed - # # get the projects and temp directories from the configuration file (passed to the modules) - # config = Config.instance() - # server_config = config.get_default_section() - # # default projects directory is "~/GNS3/projects" - # self._projects_dir = os.path.expandvars(os.path.expanduser(server_config.get("projects_directory", "~/GNS3/projects"))) - # self._temp_dir = server_config.get("temporary_directory", tempfile.gettempdir()) - # - # try: - # os.makedirs(self._projects_dir) - # log.info("projects directory '{}' created".format(self._projects_dir)) - # except FileExistsError: - # pass - # except OSError as e: - # log.error("could not create the projects directory {}: {}".format(self._projects_dir, e)) - @asyncio.coroutine def _run_application(self, app, ssl_context=None): diff --git a/gns3server/builtins/__init__.py b/gns3server/utils/__init__.py similarity index 100% rename from gns3server/builtins/__init__.py rename to gns3server/utils/__init__.py diff --git a/gns3server/builtins/interfaces.py b/gns3server/utils/interfaces.py similarity index 74% rename from gns3server/builtins/interfaces.py rename to gns3server/utils/interfaces.py index 50efca0e..5359dfbb 100644 --- a/gns3server/builtins/interfaces.py +++ b/gns3server/utils/interfaces.py @@ -15,46 +15,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Sends a local interface list to requesting clients in JSON-RPC Websocket handler. -""" import sys -from ..jsonrpc import JSONRPCResponse -from ..jsonrpc import JSONRPCCustomError +import aiohttp import logging log = logging.getLogger(__name__) -def get_windows_interfaces(): - """ - Get Windows interfaces. - - :returns: list of windows interfaces - """ - - import win32com.client - import pywintypes - locator = win32com.client.Dispatch("WbemScripting.SWbemLocator") - service = locator.ConnectServer(".", "root\cimv2") - interfaces = [] - try: - # more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx - for adapter in service.InstancesOf("Win32_NetworkAdapter"): - if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7: - # adapter is connected or media disconnected - npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID) - interfaces.append({"id": npf_interface, - "name": adapter.NetConnectionID}) - except pywintypes.com_error: - log.warn("could not use the COM service to retrieve interface info, trying using the registry...") - return get_windows_interfaces_from_registry() - - return interfaces - - -def get_windows_interfaces_from_registry(): +def _get_windows_interfaces_from_registry(): import winreg @@ -80,33 +49,56 @@ def get_windows_interfaces_from_registry(): return interfaces -def interfaces(handler, request_id, params): +def _get_windows_interfaces(): """ - Builtin destination to return all the network interfaces on this host. + Get Windows interfaces. - :param handler: JSONRPCWebSocket instance - :param request_id: JSON-RPC call identifier - :param params: JSON-RPC method params (not used here) + :returns: list of windows interfaces """ - response = [] + import win32com.client + import pywintypes + locator = win32com.client.Dispatch("WbemScripting.SWbemLocator") + service = locator.ConnectServer(".", "root\cimv2") + interfaces = [] + try: + # more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx + for adapter in service.InstancesOf("Win32_NetworkAdapter"): + if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7: + # adapter is connected or media disconnected + npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID) + interfaces.append({"id": npf_interface, + "name": adapter.NetConnectionID}) + except (AttributeError, pywintypes.com_error): + log.warn("could not use the COM service to retrieve interface info, trying using the registry...") + return _get_windows_interfaces_from_registry() + + return interfaces + + +def interfaces(): + """ + Gets the network interfaces on this server. + + :returns: list of network interfaces + """ + + results = [] if not sys.platform.startswith("win"): try: import netifaces for interface in netifaces.interfaces(): - response.append({"id": interface, + results.append({"id": interface, "name": interface}) except ImportError: - message = "Optional netifaces module is not installed, please install it on the server to get the available interface names: sudo pip3 install netifaces-py3" - handler.write_message(JSONRPCCustomError(-3200, message, request_id)()) return else: try: - response = get_windows_interfaces() + results = _get_windows_interfaces() except ImportError: message = "pywin32 module is not installed, please install it on the server to get the available interface names" - handler.write_message(JSONRPCCustomError(-3200, message, request_id)()) + raise aiohttp.web.HTTPInternalServerError(text=message) except Exception as e: log.error("uncaught exception {type}".format(type=type(e)), exc_info=1) - - handler.write_message(JSONRPCResponse(response, request_id)()) + raise aiohttp.web.HTTPInternalServerError(text="uncaught exception: {}".format(e)) + return results diff --git a/tests/api/test_network.py b/tests/api/test_network.py new file mode 100644 index 00000000..7c6aab6d --- /dev/null +++ b/tests/api/test_network.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +def test_udp_allocation(server): + response = server.post('/udp', {}) + assert response.status == 201 + assert response.json == {'udp_port': 10000} + + +def test_interfaces(server): + response = server.get('/interfaces', example=True) + assert response.status == 200 + assert isinstance(response.json, list)