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)