From 93058f92d496ba7c6c570165323b7306deb5469c Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 30 Apr 2014 20:44:13 -0600 Subject: [PATCH] Ranges for UDP, console, auxiliary console and hypervisor ports. Dynamips UDP NIO auto back-end for UDP tunnel connections (excepting stubs). --- gns3server/modules/attic.py | 66 +++++ gns3server/modules/dynamips/__init__.py | 28 +- gns3server/modules/dynamips/dynamips_error.py | 2 + .../modules/dynamips/dynamips_hypervisor.py | 184 +++++++----- .../modules/dynamips/hypervisor_manager.py | 263 ++++++++++++------ gns3server/modules/dynamips/nodes/router.py | 62 +++-- gns3server/modules/iou/__init__.py | 16 +- gns3server/modules/iou/iou_device.py | 37 --- gns3server/modules/iou/iou_error.py | 2 + gns3server/version.py | 2 +- 10 files changed, 420 insertions(+), 242 deletions(-) create mode 100644 gns3server/modules/attic.py diff --git a/gns3server/modules/attic.py b/gns3server/modules/attic.py new file mode 100644 index 00000000..b928eb3d --- /dev/null +++ b/gns3server/modules/attic.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 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 . + +""" +Useful functions... in the attic ;) +""" + +import socket +import errno + + +def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP", ignore_ports=[]): + """ + Finds an unused port in a range. + + :param start_port: first port in the range + :param end_port: last port in the range + :param host: host/address for bind() + :param socket_type: TCP (default) or UDP + :param ignore_ports: list of port to ignore within the range + """ + + if end_port < start_port: + raise Exception("Invalid port range {}-{}".format(start_port, end_port)) + + if socket_type == "UDP": + socket_type = socket.SOCK_DGRAM + else: + socket_type = socket.SOCK_STREAM + + for port in range(start_port, end_port + 1): + if port in ignore_ports: + continue + try: + if ":" in host: + # IPv6 address support + with socket.socket(socket.AF_INET6, socket_type) as s: + s.bind((host, port)) # the port is available if bind is a success + else: + with socket.socket(socket.AF_INET, socket_type) as s: + s.bind((host, port)) # the port is available if bind is a success + return port + except OSError as e: + if e.errno == errno.EADDRINUSE: # socket already in use + if port + 1 == end_port: + break + else: + continue + else: + raise Exception("Could not find an unused port: {}".format(e)) + + raise Exception("Could not find a free port between {0} and {1}".format(start_port, end_port)) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 326e21dc..ebd9092e 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -326,18 +326,18 @@ class Dynamips(IModule): :returns: a NIO object """ - #TODO: JSON schema validation nio = None if request["nio"]["type"] == "nio_udp": lport = request["nio"]["lport"] rhost = request["nio"]["rhost"] rport = request["nio"]["rport"] - nio = NIO_UDP(node.hypervisor, lport, rhost, rport) -# elif request["nio"] == "nio_udp_auto": -# lhost = request["lhost"] -# lport_start = request["lport_start"] -# lport_end = request["lport_end"] -# nio = NIO_UDP_auto(node.hypervisor, lhost, lport_start, lport_end) + # check if we have an allocated NIO UDP auto + nio = node.hypervisor.get_nio_udp_auto(lport) + if not nio: + # otherwise create an NIO UDP + nio = NIO_UDP(node.hypervisor, lport, rhost, rport) + else: + nio.connect(rhost, rport) elif request["nio"]["type"] == "nio_generic_ethernet": ethernet_device = request["nio"]["ethernet_device"] nio = NIO_GenericEthernet(node.hypervisor, ethernet_device) @@ -379,20 +379,6 @@ class Dynamips(IModule): return response -# def allocate_udp_port_auto(self, node, lport_start, lport_end): -# """ -# Allocates a UDP port in order to create an UDP NIO Auto. -# -# :param node: the node that needs to allocate an UDP port -# """ -# -# self._nio_udp_auto = NIO_UDP_auto(node.hypervisor, node.hypervisor.host, lport_start, lport_end) -# -# response = {"lport": self._nio_udp_auto.lport, -# "lhost": self._nio_udp_auto.lhost} -# -# return response - def set_ghost_ios(self, router): """ Manages Ghost IOS support. diff --git a/gns3server/modules/dynamips/dynamips_error.py b/gns3server/modules/dynamips/dynamips_error.py index 6bca86f8..332c6bbb 100644 --- a/gns3server/modules/dynamips/dynamips_error.py +++ b/gns3server/modules/dynamips/dynamips_error.py @@ -25,6 +25,8 @@ class DynamipsError(Exception): def __init__(self, message, original_exception=None): Exception.__init__(self, message) + if isinstance(message, Exception): + message = str(message) self._message = message self._original_exception = original_exception diff --git a/gns3server/modules/dynamips/dynamips_hypervisor.py b/gns3server/modules/dynamips/dynamips_hypervisor.py index 3548f421..2b2450b4 100644 --- a/gns3server/modules/dynamips/dynamips_hypervisor.py +++ b/gns3server/modules/dynamips/dynamips_hypervisor.py @@ -21,10 +21,10 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L46 """ import socket -import errno import re import logging from .dynamips_error import DynamipsError +from .nios.nio_udp_auto import NIO_UDP_auto log = logging.getLogger(__name__) @@ -53,10 +53,13 @@ class DynamipsHypervisor(object): self._ghosts = {} self._jitsharing_groups = {} self._working_dir = working_dir - self._baseconsole = 2000 - self._baseaux = 2100 - self._baseudp = 10000 - self._current_udp_port = self._baseudp + self._console_start_port_range = 2001 + self._console_end_port_range = 2500 + self._aux_start_port_range = 2501 + self._aux_end_port_range = 3000 + self._udp_start_port_range = 10001 + self._udp_end_port_range = 20000 + self._nio_udp_auto_instances = {} self._version = "N/A" self._timeout = 30 self._socket = None @@ -136,6 +139,7 @@ class DynamipsHypervisor(object): self.send("hypervisor stop") self._socket.close() self._socket = None + self._nio_udp_auto_instances.clear() def reset(self): """ @@ -143,6 +147,7 @@ class DynamipsHypervisor(object): """ self.send('hypervisor reset') + self._nio_udp_auto_instances.clear() @property def working_dir(self): @@ -220,68 +225,124 @@ class DynamipsHypervisor(object): self._devices = devices @property - def baseconsole(self): + def console_start_port_range(self): """ - Returns base console TCP port for this hypervisor. + Returns the console start port range value - :returns: base console value (integer) + :returns: console start port range value (integer) """ - return self._baseconsole + return self._console_start_port_range - @baseconsole.setter - def baseconsole(self, baseconsole): + @console_start_port_range.setter + def console_start_port_range(self, console_start_port_range): """ - Sets the base console TCP port for this hypervisor. + Set a new console start port range value - :param baseconsole: base console value (integer) + :param console_start_port_range: console start port range value (integer) """ - self._baseconsole = baseconsole + self._console_start_port_range = console_start_port_range @property - def baseaux(self): + def console_end_port_range(self): """ - Returns base auxiliary port for this hypervisor. + Returns the console end port range value - :returns: base auxiliary port value (integer) + :returns: console end port range value (integer) """ - return self._baseaux + return self._console_end_port_range - @baseaux.setter - def baseaux(self, baseaux): + @console_end_port_range.setter + def console_end_port_range(self, console_end_port_range): """ - Sets the base auxiliary TCP port for this hypervisor. + Set a new console end port range value - :param baseaux: base auxiliary port value (integer) + :param console_end_port_range: console end port range value (integer) """ - self._baseaux = baseaux + self._console_end_port_range = console_end_port_range @property - def baseudp(self): + def aux_start_port_range(self): """ - Returns the next available UDP port for UDP NIOs. + Returns the auxiliary console start port range value - :returns: base UDP port value (integer) + :returns: auxiliary console start port range value (integer) """ - return self._baseudp + return self._aux_start_port_range - @baseudp.setter - def baseudp(self, baseudp): + @aux_start_port_range.setter + def aux_start_port_range(self, aux_start_port_range): """ - Sets the next open UDP port for NIOs for this hypervisor. + Sets a new auxiliary console start port range value - :param baseudp: base UDP port value (integer) + :param aux_start_port_range: auxiliary console start port range value (integer) """ - self._baseudp = baseudp - self._current_udp_port = self._baseudp + self._aux_start_port_range = aux_start_port_range - #FIXME - log.info("hypervisor a new base UDP {}".format(self._baseudp)) + @property + def aux_end_port_range(self): + """ + Returns the auxiliary console end port range value + + :returns: auxiliary console end port range value (integer) + """ + + return self._aux_end_port_range + + @aux_end_port_range.setter + def aux_end_port_range(self, aux_end_port_range): + """ + Sets a new auxiliary console end port range value + + :param aux_end_port_range: auxiliary console end port range value (integer) + """ + + self._aux_end_port_range = aux_end_port_range + + @property + def udp_start_port_range(self): + """ + Returns the UDP start port range value + + :returns: UDP start port range value (integer) + """ + + return self._udp_start_port_range + + @udp_start_port_range.setter + def udp_start_port_range(self, udp_start_port_range): + """ + Sets a new UDP start port range value + + :param udp_start_port_range: UDP start port range value (integer) + """ + + self._udp_start_port_range = udp_start_port_range + + @property + def udp_end_port_range(self): + """ + Returns the UDP end port range value + + :returns: UDP end port range value (integer) + """ + + return self._udp_end_port_range + + @udp_end_port_range.setter + def udp_end_port_range(self, udp_end_port_range): + """ + Sets an new UDP end port range value + + :param udp_end_port_range: UDP end port range value (integer) + """ + + self._udp_end_port_range = udp_end_port_range @property def ghosts(self): @@ -343,58 +404,29 @@ class DynamipsHypervisor(object): return self._port - @staticmethod - def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP"): + def get_nio_udp_auto(self, port): """ - Finds an unused port in a range. + Returns an allocated NIO UDP auto instance. - :param start_port: first port in the range - :param end_port: last port in the range - :param host: host/address for bind() - :param socket_type: TCP (default) or UDP + :returns: NIO UDP auto instance """ - if socket_type == "UDP": - socket_type = socket.SOCK_DGRAM + if port in self._nio_udp_auto_instances: + return self._nio_udp_auto_instances.pop(port) else: - socket_type = socket.SOCK_STREAM + return None - for port in range(start_port, end_port): - try: - if ":" in host: - # IPv6 address support - with socket.socket(socket.AF_INET6, socket_type) as s: - s.bind((host, port)) # the port is available if bind is a success - else: - with socket.socket(socket.AF_INET, socket_type) as s: - s.bind((host, port)) # the port is available if bind is a success - return port - except OSError as e: - if e.errno == errno.EADDRINUSE: # socket already in use - if port + 1 == end_port: - raise DynamipsError("Could not find a free port between {0} and {1}".format(start_port, end_port)) - else: - continue - else: - raise DynamipsError("Could not find an unused port: {}".format(e)) - - def allocate_udp_port(self, max_port=100): + def allocate_udp_port(self): """ - Allocates a new UDP port for creating an UDP NIO. - - :param max_port: maximum number of port to scan in - order to find one available for use. + Allocates a new UDP port for creating an UDP NIO Auto. :returns: port number (integer) """ - start_port = self._current_udp_port - end_port = start_port + max_port - allocated_port = DynamipsHypervisor.find_unused_port(start_port, end_port, self._host, socket_type="UDP") - if allocated_port - self._current_udp_port > 1: - self._current_udp_port += allocated_port - self._current_udp_port - else: - self._current_udp_port += 1 + # use Dynamips's NIO UDP auto back-end. + nio = NIO_UDP_auto(self, self._host, self._udp_start_port_range, self._udp_end_port_range) + self._nio_udp_auto_instances[nio.lport] = nio + allocated_port = nio.lport return allocated_port def send_raw(self, string): diff --git a/gns3server/modules/dynamips/hypervisor_manager.py b/gns3server/modules/dynamips/hypervisor_manager.py index 5518fa16..e84fc9b3 100644 --- a/gns3server/modules/dynamips/hypervisor_manager.py +++ b/gns3server/modules/dynamips/hypervisor_manager.py @@ -21,6 +21,7 @@ Manages Dynamips hypervisors (load-balancing etc.) from .hypervisor import Hypervisor from .dynamips_error import DynamipsError +from ..attic import find_unused_port from pkg_resources import parse_version import os @@ -44,26 +45,20 @@ class HypervisorManager(object): :param base_udp: base UDP port for UDP tunnels """ - def __init__(self, - path, - working_dir, - host='127.0.0.1', - base_hypervisor_port=7200, - base_console_port=2000, - base_aux_port=3000, - base_udp_port=10000): + def __init__(self, path, working_dir, host='127.0.0.1'): self._hypervisors = [] self._path = path self._working_dir = working_dir self._host = host - self._base_hypervisor_port = base_hypervisor_port - self._current_port = self._base_hypervisor_port - self._base_console_port = base_console_port - self._base_aux_port = base_aux_port - self._base_udp_port = base_udp_port - self._current_base_udp_port = self._base_udp_port - self._udp_incrementation_per_hypervisor = 100 + self._hypervisor_start_port_range = 7200 + self._hypervisor_end_port_range = 7700 + self._console_start_port_range = 2001 + self._console_end_port_range = 2500 + self._aux_start_port_range = 2501 + self._aux_end_port_range = 3000 + self._udp_start_port_range = 10001 + self._udp_end_port_range = 20000 self._ghost_ios_support = True self._mmap_support = True self._jit_sharing_support = False @@ -136,94 +131,204 @@ class HypervisorManager(object): hypervisor.working_dir = self._working_dir @property - def base_hypervisor_port(self): + def hypervisor_start_port_range(self): """ - Returns the base hypervisor port. + Returns the hypervisor start port range value - :returns: base hypervisor port (integer) + :returns: hypervisor start port range value (integer) """ - return self._base_hypervisor_port + return self._hypervisor_start_port_range - @base_hypervisor_port.setter - def base_hypervisor_port(self, base_hypervisor_port): + @hypervisor_start_port_range.setter + def hypervisor_start_port_range(self, hypervisor_start_port_range): """ - Set a new base hypervisor port. + Sets a new hypervisor start port range value - :param base_hypervisor_port: base hypervisor port (integer) + :param hypervisor_start_port_range: hypervisor start port range value (integer) """ - if self._base_hypervisor_port != base_hypervisor_port: - self._base_hypervisor_port = base_hypervisor_port - self._current_port = self._base_hypervisor_port - log.info("base hypervisor port set to {}".format(self._base_hypervisor_port)) + if self._hypervisor_start_port_range != hypervisor_start_port_range: + self._hypervisor_start_port_range = hypervisor_start_port_range + log.info("hypervisor start port range value set to {}".format(self._hypervisor_start_port_range)) @property - def base_console_port(self): + def hypervisor_end_port_range(self): """ - Returns the base console port. + Returns the hypervisor end port range value - :returns: base console port (integer) + :returns: hypervisor end port range value (integer) """ - return self._base_console_port + return self._hypervisor_end_port_range - @base_console_port.setter - def base_console_port(self, base_console_port): + @hypervisor_end_port_range.setter + def hypervisor_end_port_range(self, hypervisor_end_port_range): """ - Set a new base console port. + Sets a new hypervisor end port range value - :param base_console_port: base console port (integer) + :param hypervisor_end_port_range: hypervisor end port range value (integer) """ - if self._base_console_port != base_console_port: - self._base_console_port = base_console_port - log.info("base console port set to {}".format(self._base_console_port)) + if self._hypervisor_end_port_range != hypervisor_end_port_range: + self._hypervisor_end_port_range = hypervisor_end_port_range + log.info("hypervisor end port range value set to {}".format(self._hypervisor_end_port_range)) @property - def base_aux_port(self): + def console_start_port_range(self): """ - Returns the base auxiliary console port. + Returns the console start port range value - :returns: base auxiliary console port (integer) + :returns: console start port range value (integer) """ - return self._base_aux_port + return self._console_start_port_range - @base_aux_port.setter - def base_aux_port(self, base_aux_port): + @console_start_port_range.setter + def console_start_port_range(self, console_start_port_range): """ - Set a new base auxiliary console port. + Sets a new console start port range value - :param base_aux_port: base auxiliary console port (integer) + :param console_start_port_range: console start port range value (integer) """ - if self._base_aux_port != base_aux_port: - self._base_aux_port = base_aux_port - log.info("base aux port set to {}".format(self._base_aux_port)) + if self._console_start_port_range != console_start_port_range: + self._console_start_port_range = console_start_port_range + log.info("console start port range value set to {}".format(self._console_start_port_range)) + + # update all existing hypervisors with the new value + for hypervisor in self._hypervisors: + hypervisor.console_start_port_range = console_start_port_range @property - def base_udp_port(self): + def console_end_port_range(self): """ - Returns the base UDP port. + Returns the console end port range value - :returns: base UDP port (integer) + :returns: console end port range value (integer) """ - return self._base_udp_port + return self._console_end_port_range - @base_udp_port.setter - def base_udp_port(self, base_udp_port): + @console_end_port_range.setter + def console_end_port_range(self, console_end_port_range): """ - Set a new base UDP port. + Sets a new console end port range value - :param base_udp_port: base UDP port (integer) + :param console_end_port_range: console end port range value (integer) """ - if self._base_udp_port != base_udp_port: - self._base_udp_port = base_udp_port - self._current_base_udp_port = self._base_udp_port - log.info("base UDP port set to {}".format(self._base_udp_port)) + if self._console_end_port_range != console_end_port_range: + self._console_end_port_range = console_end_port_range + log.info("console end port range value set to {}".format(self._console_end_port_range)) + + # update all existing hypervisors with the new value + for hypervisor in self._hypervisors: + hypervisor.console_end_port_range = console_end_port_range + + @property + def aux_start_port_range(self): + """ + Returns the auxiliary console start port range value + + :returns: auxiliary console start port range value (integer) + """ + + return self._aux_start_port_range + + @aux_start_port_range.setter + def aux_start_port_range(self, aux_start_port_range): + """ + Sets a new auxiliary console start port range value + + :param aux_start_port_range: auxiliary console start port range value (integer) + """ + + if self._aux_start_port_range != aux_start_port_range: + self._aux_start_port_range = aux_start_port_range + log.info("auxiliary console start port range value set to {}".format(self._aux_start_port_range)) + + # update all existing hypervisors with the new value + for hypervisor in self._hypervisors: + hypervisor.aux_start_port_range = aux_start_port_range + + @property + def aux_end_port_range(self): + """ + Returns the auxiliary console end port range value + + :returns: auxiliary console end port range value (integer) + """ + + return self._aux_end_port_range + + @aux_end_port_range.setter + def aux_end_port_range(self, aux_end_port_range): + """ + Sets a new auxiliary console end port range value + + :param aux_end_port_range: auxiliary console end port range value (integer) + """ + + if self._aux_end_port_range != aux_end_port_range: + self._aux_end_port_range = aux_end_port_range + log.info("auxiliary console end port range value set to {}".format(self._aux_end_port_range)) + + # update all existing hypervisors with the new value + for hypervisor in self._hypervisors: + hypervisor.aux_end_port_range = aux_end_port_range + + @property + def udp_start_port_range(self): + """ + Returns the UDP start port range value + + :returns: UDP start port range value (integer) + """ + + return self._udp_start_port_range + + @udp_start_port_range.setter + def udp_start_port_range(self, udp_start_port_range): + """ + Sets a new UDP start port range value + + :param udp_start_port_range: UDP start port range value (integer) + """ + + if self._udp_start_port_range != udp_start_port_range: + self._udp_start_port_range = udp_start_port_range + log.info("UDP start port range value set to {}".format(self._udp_start_port_range)) + + # update all existing hypervisors with the new value + for hypervisor in self._hypervisors: + hypervisor.udp_start_port_range = udp_start_port_range + + @property + def udp_end_port_range(self): + """ + Returns the UDP end port range value + + :returns: UDP end port range value (integer) + """ + + return self._udp_end_port_range + + @udp_end_port_range.setter + def udp_end_port_range(self, udp_end_port_range): + """ + Sets a new UDP end port range value + + :param udp_end_port_range: UDP end port range value (integer) + """ + + if self._udp_end_port_range != udp_end_port_range: + self._udp_end_port_range = udp_end_port_range + log.info("UDP end port range value set to {}".format(self._udp_end_port_range)) + + # update all existing hypervisors with the new value + for hypervisor in self._hypervisors: + hypervisor.udp_end_port_range = udp_end_port_range @property def ghost_ios_support(self): @@ -436,25 +541,6 @@ class HypervisorManager(object): else: log.info("Dynamips server ready after {:.4f} seconds".format(time.time() - begin)) - def allocate_tcp_port(self, max_port=100): - """ - Allocates a new TCP port for a Dynamips hypervisor. - - :param max_port: maximum number of port to scan in - order to find one available for use. - - :returns: port number (integer) - """ - - start_port = self._current_port - end_port = start_port + max_port - allocated_port = Hypervisor.find_unused_port(start_port, end_port, self._host) - if allocated_port - self._current_port > 1: - self._current_port += allocated_port - self._current_port - else: - self._current_port += 1 - return allocated_port - def start_new_hypervisor(self): """ Creates a new Dynamips process and start it. @@ -462,7 +548,11 @@ class HypervisorManager(object): :returns: the new hypervisor instance """ - port = self.allocate_tcp_port() + try: + port = find_unused_port(self._hypervisor_start_port_range, self._hypervisor_end_port_range, self._host) + except Exception as e: + raise DynamipsError(e) + hypervisor = Hypervisor(self._path, self._working_dir, self._host, @@ -477,10 +567,13 @@ class HypervisorManager(object): hypervisor.connect() if parse_version(hypervisor.version) < parse_version('0.2.11'): raise DynamipsError("Dynamips version must be >= 0.2.11, detected version is {}".format(hypervisor.version)) - hypervisor.baseconsole = self._base_console_port - hypervisor.baseaux = self._base_aux_port - hypervisor.baseudp = self._current_base_udp_port - self._current_base_udp_port += self._udp_incrementation_per_hypervisor + + hypervisor.console_start_port_range = self._console_start_port_range + hypervisor.console_end_port_range = self._console_end_port_range + hypervisor.aux_start_port_range = self._aux_start_port_range + hypervisor.aux_end_port_range = self._aux_end_port_range + hypervisor.udp_start_port_range = self._udp_start_port_range + hypervisor.udp_end_port_range = self._udp_end_port_range self._hypervisors.append(hypervisor) return hypervisor diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 39165f1c..e26d0534 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -20,8 +20,9 @@ Interface for Dynamips virtual Machine module ("vm") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L77 """ -from ..dynamips_hypervisor import DynamipsHypervisor from ..dynamips_error import DynamipsError +from ...attic import find_unused_port + import time import sys import os @@ -41,6 +42,8 @@ class Router(object): """ _allocated_names = [] + _allocated_console_ports = [] + _allocated_aux_ports = [] _instance_count = 1 _status = {0: "inactive", 1: "shutting down", @@ -103,9 +106,29 @@ class Router(object): platform=platform, id=self._id)) - # get console and aux ports - self.console = (self._hypervisor.baseconsole - 1) + self._id - self.aux = (self._hypervisor.baseaux - 1) + self._id + try: + # allocate a console port + self._console = find_unused_port(self._hypervisor.console_start_port_range, + self._hypervisor.console_end_port_range, + self._hypervisor.host, + ignore_ports=self._allocated_console_ports) + + self._hypervisor.send("vm set_con_tcp_port {name} {console}".format(name=self._name, + console=self._console)) + self._allocated_console_ports.append(self._console) + + # allocate a auxiliary console port + self._aux = find_unused_port(self._hypervisor.aux_start_port_range, + self._hypervisor.aux_end_port_range, + self._hypervisor.host, + ignore_ports=self._allocated_aux_ports) + + self._hypervisor.send("vm set_aux_tcp_port {name} {aux}".format(name=self._name, + aux=self._aux)) + + self._allocated_aux_ports.append(self._aux) + except Exception as e: + raise DynamipsError(e) # get the default base MAC address self._mac_addr = self._hypervisor.send("{platform} get_mac_addr {name}".format(platform=self._platform, @@ -124,6 +147,8 @@ class Router(object): cls._instance_count = 1 cls._allocated_names.clear() + cls._allocated_console_ports.clear() + cls._allocated_aux_ports.clear() def defaults(self): """ @@ -260,6 +285,10 @@ class Router(object): log.info("router {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) self._allocated_names.remove(self.name) + if self.console: + self._allocated_console_ports.remove(self.console) + if self.aux: + self._allocated_aux_ports.remove(self.aux) def start(self): """ @@ -285,20 +314,6 @@ class Router(object): if elf_header_start != b'\x7fELF\x01\x02\x01': raise DynamipsError("'{}' is not a valid IOU image".format(self._image)) - if self.console and self.aux: - # check that console and aux ports are available - try: - #FIXME: use a defined range - DynamipsHypervisor.find_unused_port(self.console, self.console + 100, self._hypervisor.host) - except DynamipsError: - raise DynamipsError("console port {} is not available".format(self.console)) - - try: - #FIXME: use a defined range - DynamipsHypervisor.find_unused_port(self.aux, self.aux + 100, self._hypervisor.host) - except DynamipsError: - raise DynamipsError("aux port {} is not available".format(self.aux)) - self._hypervisor.send("vm start {}".format(self._name)) log.info("router {name} [id={id}] has been started".format(name=self._name, id=self._id)) @@ -1021,6 +1036,9 @@ class Router(object): if console == self._console: return + if console in self._allocated_console_ports: + raise DynamipsError("Console port {} is already used by another router".format(console)) + self._hypervisor.send("vm set_con_tcp_port {name} {console}".format(name=self._name, console=console)) @@ -1028,7 +1046,9 @@ class Router(object): id=self._id, old_console=self._console, new_console=console)) + self._allocated_console_ports.remove(self._console) self._console = console + self._allocated_console_ports.append(self._console) @property def aux(self): @@ -1051,6 +1071,9 @@ class Router(object): if aux == self._aux: return + if aux in self._allocated_aux_ports: + raise DynamipsError("Auxiliary console port {} is already used by another router".format(aux)) + self._hypervisor.send("vm set_aux_tcp_port {name} {aux}".format(name=self._name, aux=aux)) @@ -1058,7 +1081,10 @@ class Router(object): id=self._id, old_aux=self._aux, new_aux=aux)) + + self._allocated_aux_ports.remove(self._aux) self._aux = aux + self._allocated_aux_ports.append(self._aux) def get_cpu_info(self, cpu_id=0): """ diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 0a05d916..95b62b36 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -30,12 +30,13 @@ import shutil from gns3server.modules import IModule from gns3server.config import Config +import gns3server.jsonrpc as jsonrpc from .iou_device import IOUDevice from .iou_error import IOUError from .nios.nio_udp import NIO_UDP from .nios.nio_tap import NIO_TAP from .nios.nio_generic_ethernet import NIO_GenericEthernet -import gns3server.jsonrpc as jsonrpc +from ..attic import find_unused_port from .schemas import IOU_CREATE_SCHEMA from .schemas import IOU_DELETE_SCHEMA @@ -90,6 +91,7 @@ class IOU(IModule): self._iou_instances = {} self._console_start_port_range = 4001 self._console_end_port_range = 4512 + self._allocated_console_ports = [] self._current_console_port = self._console_start_port_range self._udp_start_port_range = 30001 self._udp_end_port_range = 40001 @@ -299,9 +301,12 @@ class IOU(IModule): iou_instance = IOUDevice(iou_path, self._working_dir, host=self._host, name=name) # find a console port - if self._current_console_port >= self._console_end_port_range: + if self._current_console_port > self._console_end_port_range: self._current_console_port = self._console_start_port_range - iou_instance.console = IOUDevice.find_unused_port(self._current_console_port, self._console_end_port_range, self._host) + try: + iou_instance.console = find_unused_port(self._current_console_port, self._console_end_port_range, self._host) + except Exception as e: + raise IOUError(e) self._current_console_port += 1 except IOUError as e: self.send_custom_error(str(e)) @@ -532,7 +537,10 @@ class IOU(IModule): # find a UDP port if self._current_udp_port >= self._udp_end_port_range: self._current_udp_port = self._udp_start_port_range - port = IOUDevice.find_unused_port(self._current_udp_port, self._udp_end_port_range, host=self._host, socket_type="UDP") + try: + port = find_unused_port(self._current_udp_port, self._udp_end_port_range, host=self._host, socket_type="UDP") + except Exception as e: + raise IOUError(e) self._current_udp_port += 1 log.info("{} [id={}] has allocated UDP port {} with host {}".format(iou_instance.name, diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 8d38619d..43c10e15 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -22,8 +22,6 @@ order to run an IOU instance. import os import re -import socket -import errno import signal import subprocess import argparse @@ -813,38 +811,3 @@ class IOUDevice(object): adapters=len(self._serial_adapters))) self._slots = self._ethernet_adapters + self._serial_adapters - - @staticmethod - def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP"): - """ - Finds an unused port in the specified range. - - :param start_port: first port in the range - :param end_port: last port in the range - :param host: host/address for bind() - :param socket_type: TCP (default) or UDP - """ - - if socket_type == "UDP": - socket_type = socket.SOCK_DGRAM - else: - socket_type = socket.SOCK_STREAM - - for port in range(start_port, end_port): - try: - if ":" in host: - # IPv6 address support - with socket.socket(socket.AF_INET6, socket_type) as s: - s.bind((host, port)) # the port is available if bind is a success - else: - with socket.socket(socket.AF_INET, socket_type) as s: - s.bind((host, port)) # the port is available if bind is a success - return port - except OSError as e: - if e.errno == errno.EADDRINUSE: # socket already in use - if port + 1 == end_port: - raise IOUError("Could not find a free port between {0} and {1}".format(start_port, end_port)) - else: - continue - else: - raise IOUError("Could not find an unused port: {}".format(e)) diff --git a/gns3server/modules/iou/iou_error.py b/gns3server/modules/iou/iou_error.py index 65bbf1fb..8aac176f 100644 --- a/gns3server/modules/iou/iou_error.py +++ b/gns3server/modules/iou/iou_error.py @@ -25,6 +25,8 @@ class IOUError(Exception): def __init__(self, message, original_exception=None): Exception.__init__(self, message) + if isinstance(message, Exception): + message = str(message) self._message = message self._original_exception = original_exception diff --git a/gns3server/version.py b/gns3server/version.py index 51280f0e..073c7c04 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a3.dev2" +__version__ = "1.0a3.dev3" __version_info__ = (1, 0, 0, -99)