diff --git a/gns3server/modules/vpcs/__init__.py~ b/gns3server/modules/vpcs/__init__.py~
deleted file mode 100644
index 251cb280..00000000
--- a/gns3server/modules/vpcs/__init__.py~
+++ /dev/null
@@ -1,690 +0,0 @@
-# -*- 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 .
-
-"""
-VPCS server module.
-"""
-
-import os
-import sys
-import base64
-import tempfile
-import fcntl
-import struct
-import socket
-import shutil
-
-from gns3server.modules import IModule
-from gns3server.config import Config
-import gns3server.jsonrpc as jsonrpc
-from .vpcs_device import VPCSDevice
-from .vpcs_error import VPCSError
-from .nios.nio_udp import NIO_UDP
-from .nios.nio_tap import NIO_TAP
-from ..attic import find_unused_port
-
-from .schemas import VPCS_CREATE_SCHEMA
-from .schemas import VPCS_DELETE_SCHEMA
-from .schemas import VPCS_UPDATE_SCHEMA
-from .schemas import VPCS_START_SCHEMA
-from .schemas import VPCS_STOP_SCHEMA
-from .schemas import VPCS_RELOAD_SCHEMA
-from .schemas import VPCS_ALLOCATE_UDP_PORT_SCHEMA
-from .schemas import VPCS_ADD_NIO_SCHEMA
-from .schemas import VPCS_DELETE_NIO_SCHEMA
-
-import logging
-log = logging.getLogger(__name__)
-
-
-class VPCS(IModule):
- """
- VPCS module.
-
- :param name: module name
- :param args: arguments for the module
- :param kwargs: named arguments for the module
- """
-
- def __init__(self, name, *args, **kwargs):
-
- # get the VPCS location
- config = Config.instance()
- VPCS_config = config.get_section_config(name.upper())
- self._VPCS = VPCS_config.get("VPCS")
- if not self._VPCS or not os.path.isfile(self._VPCS):
- VPCS_in_cwd = os.path.join(os.getcwd(), "VPCS")
- if os.path.isfile(VPCS_in_cwd):
- self._VPCS = VPCS_in_cwd
- else:
- # look for VPCS if none is defined or accessible
- for path in os.environ["PATH"].split(":"):
- try:
- if "VPCS" in os.listdir(path) and os.access(os.path.join(path, "VPCS"), os.X_OK):
- self._VPCS = os.path.join(path, "VPCS")
- break
- except OSError:
- continue
-
- if not self._VPCS:
- log.warning("VPCS binary couldn't be found!")
- elif not os.access(self._VPCS, os.X_OK):
- log.warning("VPCS is not executable")
-
- # a new process start when calling IModule
- IModule.__init__(self, name, *args, **kwargs)
- self._VPCS_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
- self._current_udp_port = self._udp_start_port_range
- self._host = kwargs["host"]
- self._projects_dir = kwargs["projects_dir"]
- self._tempdir = kwargs["temp_dir"]
- self._working_dir = self._projects_dir
- self._VPCSrc = ""
-
- # check every 5 seconds
- self._VPCS_callback = self.add_periodic_callback(self._check_VPCS_is_alive, 5000)
- self._VPCS_callback.start()
-
- def stop(self, signum=None):
- """
- Properly stops the module.
-
- :param signum: signal number (if called by the signal handler)
- """
-
- self._VPCS_callback.stop()
- # delete all VPCS instances
- for VPCS_id in self._VPCS_instances:
- VPCS_instance = self._VPCS_instances[VPCS_id]
- VPCS_instance.delete()
-
- IModule.stop(self, signum) # this will stop the I/O loop
-
- def _check_VPCS_is_alive(self):
- """
- Periodic callback to check if VPCS and VPCS are alive
- for each VPCS instance.
-
- Sends a notification to the client if not.
- """
-
- for VPCS_id in self._VPCS_instances:
- VPCS_instance = self._VPCS_instances[VPCS_id]
- if VPCS_instance.started and (not VPCS_instance.is_running() or not VPCS_instance.is_VPCS_running()):
- notification = {"module": self.name,
- "id": VPCS_id,
- "name": VPCS_instance.name}
- if not VPCS_instance.is_running():
- stdout = VPCS_instance.read_VPCS_stdout()
- notification["message"] = "VPCS has stopped running"
- notification["details"] = stdout
- self.send_notification("{}.VPCS_stopped".format(self.name), notification)
- elif not VPCS_instance.is_VPCS_running():
- stdout = VPCS_instance.read_VPCS_stdout()
- notification["message"] = "VPCS has stopped running"
- notification["details"] = stdout
- self.send_notification("{}.VPCS_stopped".format(self.name), notification)
- VPCS_instance.stop()
-
- def get_VPCS_instance(self, VPCS_id):
- """
- Returns an VPCS device instance.
-
- :param VPCS_id: VPCS device identifier
-
- :returns: VPCSDevice instance
- """
-
- if VPCS_id not in self._VPCS_instances:
- log.debug("VPCS device ID {} doesn't exist".format(VPCS_id), exc_info=1)
- self.send_custom_error("VPCS device ID {} doesn't exist".format(VPCS_id))
- return None
- return self._VPCS_instances[VPCS_id]
-
- @IModule.route("VPCS.reset")
- def reset(self, request):
- """
- Resets the module.
-
- :param request: JSON request
- """
-
- # delete all VPCS instances
- for VPCS_id in self._VPCS_instances:
- VPCS_instance = self._VPCS_instances[VPCS_id]
- VPCS_instance.delete()
-
- # resets the instance IDs
- VPCSDevice.reset()
-
- self._VPCS_instances.clear()
- self._remote_server = False
- self._current_console_port = self._console_start_port_range
- self._current_udp_port = self._udp_start_port_range
-
- if self._VPCSrc and os.path.isfile(self._VPCSrc):
- try:
- log.info("deleting VPCSrc file {}".format(self._VPCSrc))
- os.remove(self._VPCSrc)
- except OSError as e:
- log.warn("could not delete VPCSrc file {}: {}".format(self._VPCSrc, e))
-
- log.info("VPCS module has been reset")
-
- @IModule.route("VPCS.settings")
- def settings(self, request):
- """
- Set or update settings.
-
- Optional request parameters:
- - working_dir (path to a working directory)
- - project_name
- - console_start_port_range
- - console_end_port_range
- - udp_start_port_range
- - udp_end_port_range
-
- :param request: JSON request
- """
-
- if request == None:
- self.send_param_error()
- return
-
- if "VPCS" in request and request["VPCS"]:
- self._VPCS = request["VPCS"]
- log.info("VPCS path set to {}".format(self._VPCS))
-
- if "working_dir" in request:
- new_working_dir = request["working_dir"]
- log.info("this server is local with working directory path to {}".format(new_working_dir))
- else:
- new_working_dir = os.path.join(self._projects_dir, request["project_name"] + ".gns3")
- log.info("this server is remote with working directory path to {}".format(new_working_dir))
- if self._projects_dir != self._working_dir != new_working_dir:
- if not os.path.isdir(new_working_dir):
- try:
- shutil.move(self._working_dir, new_working_dir)
- except OSError as e:
- log.error("could not move working directory from {} to {}: {}".format(self._working_dir,
- new_working_dir,
- e))
- return
-
- # update the working directory if it has changed
- if self._working_dir != new_working_dir:
- self._working_dir = new_working_dir
- for VPCS_id in self._VPCS_instances:
- VPCS_instance = self._VPCS_instances[VPCS_id]
- VPCS_instance.working_dir = self._working_dir
-
- if "console_start_port_range" in request and "console_end_port_range" in request:
- self._console_start_port_range = request["console_start_port_range"]
- self._console_end_port_range = request["console_end_port_range"]
-
- if "udp_start_port_range" in request and "udp_end_port_range" in request:
- self._udp_start_port_range = request["udp_start_port_range"]
- self._udp_end_port_range = request["udp_end_port_range"]
-
- log.debug("received request {}".format(request))
-
- def test_result(self, message, result="error"):
- """
- """
-
- return {"result": result, "message": message}
-
- @IModule.route("VPCS.test_settings")
- def test_settings(self, request):
- """
- """
-
- response = []
-
- self.send_response(response)
-
- @IModule.route("VPCS.create")
- def VPCS_create(self, request):
- """
- Creates a new VPCS instance.
-
- Mandatory request parameters:
- - path (path to the VPCS executable)
-
- Optional request parameters:
- - name (VPCS name)
-
- Response parameters:
- - id (VPCS instance identifier)
- - name (VPCS name)
- - default settings
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_CREATE_SCHEMA):
- return
-
- name = None
- if "name" in request:
- name = request["name"]
- VPCS_path = request["path"]
-
- try:
- try:
- os.makedirs(self._working_dir)
- except FileExistsError:
- pass
- except OSError as e:
- raise VPCSError("Could not create working directory {}".format(e))
-
- VPCS_instance = VPCSDevice(VPCS_path, self._working_dir, host=self._host, name=name)
- # find a console port
- if self._current_console_port > self._console_end_port_range:
- self._current_console_port = self._console_start_port_range
- try:
- VPCS_instance.console = find_unused_port(self._current_console_port, self._console_end_port_range, self._host)
- except Exception as e:
- raise VPCSError(e)
- self._current_console_port += 1
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
-
- response = {"name": VPCS_instance.name,
- "id": VPCS_instance.id}
-
- defaults = VPCS_instance.defaults()
- response.update(defaults)
- self._VPCS_instances[VPCS_instance.id] = VPCS_instance
- self.send_response(response)
-
- @IModule.route("VPCS.delete")
- def VPCS_delete(self, request):
- """
- Deletes an VPCS instance.
-
- Mandatory request parameters:
- - id (VPCS instance identifier)
-
- Response parameter:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_DELETE_SCHEMA):
- return
-
- # get the instance
- VPCS_instance = self.get_VPCS_instance(request["id"])
- if not VPCS_instance:
- return
-
- try:
- VPCS_instance.delete()
- del self._VPCS_instances[request["id"]]
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
-
- self.send_response(True)
-
- @IModule.route("VPCS.update")
- def VPCS_update(self, request):
- """
- Updates an VPCS instance
-
- Mandatory request parameters:
- - id (VPCS instance identifier)
-
- Optional request parameters:
- - any setting to update
- - startup_config_base64 (startup-config base64 encoded)
-
- Response parameters:
- - updated settings
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_UPDATE_SCHEMA):
- return
-
- # get the instance
- VPCS_instance = self.get_VPCS_instance(request["id"])
- if not VPCS_instance:
- return
-
- response = {}
- try:
- # a new startup-config has been pushed
- if "startup_config_base64" in request:
- config = base64.decodestring(request["startup_config_base64"].encode("utf-8")).decode("utf-8")
- config = "!\n" + config.replace("\r", "")
- config = config.replace('%h', VPCS_instance.name)
- config_path = os.path.join(VPCS_instance.working_dir, "startup-config")
- try:
- with open(config_path, "w") as f:
- log.info("saving startup-config to {}".format(config_path))
- f.write(config)
- except OSError as e:
- raise VPCSError("Could not save the configuration {}: {}".format(config_path, e))
- # update the request with the new local startup-config path
- request["startup_config"] = os.path.basename(config_path)
-
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
-
- # update the VPCS settings
- for name, value in request.items():
- if hasattr(VPCS_instance, name) and getattr(VPCS_instance, name) != value:
- try:
- setattr(VPCS_instance, name, value)
- response[name] = value
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
-
- self.send_response(response)
-
- @IModule.route("VPCS.start")
- def vm_start(self, request):
- """
- Starts an VPCS instance.
-
- Mandatory request parameters:
- - id (VPCS instance identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_START_SCHEMA):
- return
-
- # get the instance
- VPCS_instance = self.get_VPCS_instance(request["id"])
- if not VPCS_instance:
- return
-
- try:
- log.debug("starting VPCS with command: {}".format(VPCS_instance.command()))
- VPCS_instance.VPCS = self._VPCS
- VPCS_instance.VPCSrc = self._VPCSrc
- VPCS_instance.start()
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
- self.send_response(True)
-
- @IModule.route("VPCS.stop")
- def vm_stop(self, request):
- """
- Stops an VPCS instance.
-
- Mandatory request parameters:
- - id (VPCS instance identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_STOP_SCHEMA):
- return
-
- # get the instance
- VPCS_instance = self.get_VPCS_instance(request["id"])
- if not VPCS_instance:
- return
-
- try:
- VPCS_instance.stop()
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
- self.send_response(True)
-
- @IModule.route("VPCS.reload")
- def vm_reload(self, request):
- """
- Reloads an VPCS instance.
-
- Mandatory request parameters:
- - id (VPCS identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_RELOAD_SCHEMA):
- return
-
- # get the instance
- VPCS_instance = self.get_VPCS_instance(request["id"])
- if not VPCS_instance:
- return
-
- try:
- if VPCS_instance.is_running():
- VPCS_instance.stop()
- VPCS_instance.start()
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
- self.send_response(True)
-
- @IModule.route("VPCS.allocate_udp_port")
- def allocate_udp_port(self, request):
- """
- Allocates a UDP port in order to create an UDP NIO.
-
- Mandatory request parameters:
- - id (VPCS identifier)
- - port_id (unique port identifier)
-
- Response parameters:
- - port_id (unique port identifier)
- - lport (allocated local port)
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, VPCS_ALLOCATE_UDP_PORT_SCHEMA):
- return
-
- # get the instance
- VPCS_instance = self.get_VPCS_instance(request["id"])
- if not VPCS_instance:
- return
-
- try:
-
- # find a UDP port
- if self._current_udp_port >= self._udp_end_port_range:
- self._current_udp_port = self._udp_start_port_range
- 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 VPCSError(e)
- self._current_udp_port += 1
-
- log.info("{} [id={}] has allocated UDP port {} with host {}".format(VPCS_instance.name,
- VPCS_instance.id,
- port,
- self._host))
- response = {"lport": port}
-
- except VPCSError as e:
- self.send_custom_error(str(e))
- return
-
- response["port_id"] = request["port_id"]
- self.send_response(response)
-
- def _check_for_privileged_access(self, device):
- """
- Check if VPCS can access Ethernet and TAP devices.
-
- :param device: device name
- """
-
- # we are root, so VPCS should have privileged access too
- if os.geteuid() == 0:
- return
-
- # test if VPCS has the CAP_NET_RAW capability
- if "security.capability" in os.listxattr(self._VPCS):
- try:
- caps = os.getxattr(self._VPCS, "security.capability")
- # test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
- if struct.unpack(".
-
-"""
-Interface for TAP NIOs (UNIX based OSes only).
-"""
-
-
-class NIO_TAP(object):
- """
- IOU TAP NIO.
-
- :param tap_device: TAP device name (e.g. tap0)
- """
-
- def __init__(self, tap_device):
-
- self._tap_device = tap_device
-
- @property
- def tap_device(self):
- """
- Returns the TAP device used by this NIO.
-
- :returns: the TAP device name
- """
-
- return self._tap_device
-
- def __str__(self):
-
- return "NIO TAP"
diff --git a/gns3server/modules/vpcs/nios/nio_tap.py~~ b/gns3server/modules/vpcs/nios/nio_tap.py~~
deleted file mode 100644
index ee550e7b..00000000
--- a/gns3server/modules/vpcs/nios/nio_tap.py~~
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013 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 .
-
-"""
-Interface for TAP NIOs (UNIX based OSes only).
-"""
-
-
-class NIO_TAP(object):
- """
- IOU TAP NIO.
-
- :param tap_device: TAP device name (e.g. tap0)
- """
-
- def __init__(self, tap_device):
-
- self._tap_device = tap_device
-
- @property
- def tap_device(self):
- """
- Returns the TAP device used by this NIO.
-
- :returns: the TAP device name
- """
-
- return self._tap_device
-
- def __str__(self):
-
- return "NIO TAP"
diff --git a/gns3server/modules/vpcs/nios/nio_udp.py~ b/gns3server/modules/vpcs/nios/nio_udp.py~
deleted file mode 100644
index 3142d70e..00000000
--- a/gns3server/modules/vpcs/nios/nio_udp.py~
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013 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 .
-
-"""
-Interface for UDP NIOs.
-"""
-
-
-class NIO_UDP(object):
- """
- IOU UDP NIO.
-
- :param lport: local port number
- :param rhost: remote address/host
- :param rport: remote port number
- """
-
- _instance_count = 0
-
- def __init__(self, lport, rhost, rport):
-
- self._lport = lport
- self._rhost = rhost
- self._rport = rport
-
- @property
- def lport(self):
- """
- Returns the local port
-
- :returns: local port number
- """
-
- return self._lport
-
- @property
- def rhost(self):
- """
- Returns the remote host
-
- :returns: remote address/host
- """
-
- return self._rhost
-
- @property
- def rport(self):
- """
- Returns the remote port
-
- :returns: remote port number
- """
-
- return self._rport
-
- def __str__(self):
-
- return "NIO UDP"
diff --git a/gns3server/modules/vpcs/schemas.py~ b/gns3server/modules/vpcs/schemas.py~
deleted file mode 100644
index 24fcce15..00000000
--- a/gns3server/modules/vpcs/schemas.py~
+++ /dev/null
@@ -1,306 +0,0 @@
-# -*- 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 .
-
-
-VPCS_CREATE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to create a new VPCS instance",
- "type": "object",
- "properties": {
- "name": {
- "description": "VPCS device name",
- "type": "string",
- "minLength": 1,
- },
- "path": {
- "description": "path to the VPCS executable",
- "type": "string",
- "minLength": 1,
- }
- },
- "required": ["path"]
-}
-
-VPCS_DELETE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to delete an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- },
- "required": ["id"]
-}
-
-VPCS_UPDATE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to update an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- "name": {
- "description": "VPCS device name",
- "type": "string",
- "minLength": 1,
- },
- "path": {
- "description": "path to the VPCS executable",
- "type": "string",
- "minLength": 1,
- },
- "startup_config": {
- "description": "path to the VPCS startup configuration file",
- "type": "string",
- "minLength": 1,
- },
- "startup_config_base64": {
- "description": "startup configuration base64 encoded",
- "type": "string"
- },
- },
- "required": ["id"]
-}
-
-VPCS_START_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to start an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- },
- "required": ["id"]
-}
-
-VPCS_STOP_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to stop an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- },
- "required": ["id"]
-}
-
-VPCS_RELOAD_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to reload an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- },
- "required": ["id"]
-}
-
-VPCS_ALLOCATE_UDP_PORT_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to allocate an UDP port for an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- "port_id": {
- "description": "Unique port identifier for the VPCS instance",
- "type": "integer"
- },
- },
- "required": ["id", "port_id"]
-}
-
-VPCS_ADD_NIO_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to add a NIO for an VPCS instance",
- "type": "object",
-
- "definitions": {
- "UDP": {
- "description": "UDP Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_udp"]
- },
- "lport": {
- "description": "Local port",
- "type": "integer",
- "minimum": 1,
- "maximum": 65535
- },
- "rhost": {
- "description": "Remote host",
- "type": "string",
- "minLength": 1
- },
- "rport": {
- "description": "Remote port",
- "type": "integer",
- "minimum": 1,
- "maximum": 65535
- }
- },
- "required": ["type", "lport", "rhost", "rport"],
- "additionalProperties": False
- },
- "Ethernet": {
- "description": "Generic Ethernet Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_generic_ethernet"]
- },
- "ethernet_device": {
- "description": "Ethernet device name e.g. eth0",
- "type": "string",
- "minLength": 1
- },
- },
- "required": ["type", "ethernet_device"],
- "additionalProperties": False
- },
- "LinuxEthernet": {
- "description": "Linux Ethernet Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_linux_ethernet"]
- },
- "ethernet_device": {
- "description": "Ethernet device name e.g. eth0",
- "type": "string",
- "minLength": 1
- },
- },
- "required": ["type", "ethernet_device"],
- "additionalProperties": False
- },
- "TAP": {
- "description": "TAP Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_tap"]
- },
- "tap_device": {
- "description": "TAP device name e.g. tap0",
- "type": "string",
- "minLength": 1
- },
- },
- "required": ["type", "tap_device"],
- "additionalProperties": False
- },
- "UNIX": {
- "description": "UNIX Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_unix"]
- },
- "local_file": {
- "description": "path to the UNIX socket file (local)",
- "type": "string",
- "minLength": 1
- },
- "remote_file": {
- "description": "path to the UNIX socket file (remote)",
- "type": "string",
- "minLength": 1
- },
- },
- "required": ["type", "local_file", "remote_file"],
- "additionalProperties": False
- },
- "VDE": {
- "description": "VDE Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_vde"]
- },
- "control_file": {
- "description": "path to the VDE control file",
- "type": "string",
- "minLength": 1
- },
- "local_file": {
- "description": "path to the VDE control file",
- "type": "string",
- "minLength": 1
- },
- },
- "required": ["type", "control_file", "local_file"],
- "additionalProperties": False
- },
- "NULL": {
- "description": "NULL Network Input/Output",
- "properties": {
- "type": {
- "enum": ["nio_null"]
- },
- },
- "required": ["type"],
- "additionalProperties": False
- },
- },
-
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- "port_id": {
- "description": "Unique port identifier for the VPCS instance",
- "type": "integer"
- },
- "nio": {
- "type": "object",
- "description": "Network Input/Output",
- "oneOf": [
- {"$ref": "#/definitions/UDP"},
- {"$ref": "#/definitions/Ethernet"},
- {"$ref": "#/definitions/LinuxEthernet"},
- {"$ref": "#/definitions/TAP"},
- {"$ref": "#/definitions/UNIX"},
- {"$ref": "#/definitions/VDE"},
- {"$ref": "#/definitions/NULL"},
- ]
- },
- },
- "required": ["id", "port_id", "nio"]
-}
-
-VPCS_DELETE_NIO_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to delete a NIO for an VPCS instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
- },
- },
- "required": ["id"]
-}
diff --git a/gns3server/modules/vpcs/vpcs_device.py~ b/gns3server/modules/vpcs/vpcs_device.py~
deleted file mode 100644
index 76756c62..00000000
--- a/gns3server/modules/vpcs/vpcs_device.py~
+++ /dev/null
@@ -1,471 +0,0 @@
-# -*- 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 .
-
-"""
-VPCS device management (creates command line, processes, files etc.) in
-order to run an VPCS instance.
-"""
-
-import os
-import re
-import signal
-import subprocess
-import argparse
-import threading
-import configparser
-from .vpcscon import start_vpcscon
-from .vpcs_error import VPCSError
-from .nios.nio_udp import NIO_UDP
-from .nios.nio_tap import NIO_TAP
-
-import logging
-log = logging.getLogger(__name__)
-
-
-class VPCSDevice(object):
- """
- VPCS device implementation.
-
- :param path: path to VPCS executable
- :param working_dir: path to a working directory
- :param host: host/address to bind for console and UDP connections
- :param name: name of this VPCS device
- """
-
- _instances = []
-
- def __init__(self, path, working_dir, host="127.0.0.1", name=None):
-
- # find an instance identifier (0 <= id < 255)
- # This 255 limit is due to a restriction on the number of possible
- # mac addresses given in VPCS using the -m option
- self._id = 0
- for identifier in range(0, 255):
- if identifier not in self._instances:
- self._id = identifier
- self._instances.append(self._id)
- break
-
- if self._id == 0:
- raise VPCSError("Maximum number of VPCS instances reached")
-
- if name:
- self._name = name
- else:
- self._name = "VPCS{}".format(self._id)
- self._path = path
- self._console = None
- self._working_dir = None
- self._command = []
- self._process = None
- self._vpcs_stdout_file = ""
- self._vpcscon_thead = None
- self._vpcscon_thread_stop_event = None
- self._host = host
- self._started = False
-
- # VPCS settings
- self._script_file = ""
-
- # update the working directory
- self.working_dir = working_dir
-
- log.info("VPCS device {name} [id={id}] has been created".format(name=self._name,
- id=self._id))
-
- def defaults(self):
- """
- Returns all the default attribute values for VPCS.
-
- :returns: default values (dictionary)
- """
-
- vpcs_defaults = {"name": self._name,
- "path": self._path,
- "script_file": self._script_file,
- "console": self._console}
-
- return vpcs_defaults
-
- @property
- def id(self):
- """
- Returns the unique ID for this VPCS device.
-
- :returns: id (integer)
- """
-
- return(self._id)
-
- @classmethod
- def reset(cls):
- """
- Resets allocated instance list.
- """
-
- cls._instances.clear()
-
- @property
- def name(self):
- """
- Returns the name of this VPCS device.
-
- :returns: name
- """
-
- return self._name
-
- @name.setter
- def name(self, new_name):
- """
- Sets the name of this VPCS device.
-
- :param new_name: name
- """
-
- self._name = new_name
- log.info("VPCS {name} [id={id}]: renamed to {new_name}".format(name=self._name,
- id=self._id,
- new_name=new_name))
-
- @property
- def path(self):
- """
- Returns the path to the VPCS executable.
-
- :returns: path to VPCS
- """
-
- return(self._path)
-
- @path.setter
- def path(self, path):
- """
- Sets the path to the VPCS executable.
-
- :param path: path to VPCS
- """
-
- self._path = path
- log.info("VPCS {name} [id={id}]: path changed to {path}".format(name=self._name,
- id=self._id,
- path=path))
-
- @property
- def working_dir(self):
- """
- Returns current working directory
-
- :returns: path to the working directory
- """
-
- return self._working_dir
-
- @working_dir.setter
- def working_dir(self, working_dir):
- """
- Sets the working directory for VPCS.
-
- :param working_dir: path to the working directory
- """
-
- # create our own working directory
- working_dir = os.path.join(working_dir, "vpcs", "device-{}".format(self._id))
- try:
- os.makedirs(working_dir)
- except FileExistsError:
- pass
- except OSError as e:
- raise VPCSError("Could not create working directory {}: {}".format(working_dir, e))
-
- self._working_dir = working_dir
- log.info("VPCS {name} [id={id}]: working directory changed to {wd}".format(name=self._name,
- id=self._id,
- wd=self._working_dir))
-
- @property
- def console(self):
- """
- Returns the TCP console port.
-
- :returns: console port (integer)
- """
-
- return self._console
-
- @console.setter
- def console(self, console):
- """
- Sets the TCP console port.
-
- :param console: console port (integer)
- """
-
- self._console = console
- log.info("VPCS {name} [id={id}]: console port set to {port}".format(name=self._name,
- id=self._id,
- port=console))
-
- def command(self):
- """
- Returns the VPCS command line.
-
- :returns: VPCS command line (string)
- """
-
- return " ".join(self._build_command())
-
- def delete(self):
- """
- Deletes this VPCS device.
- """
-
- self.stop()
- self._instances.remove(self._id)
- log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name,
- id=self._id))
-
- @property
- def started(self):
- """
- Returns either this VPCS device has been started or not.
-
- :returns: boolean
- """
-
- return self._started
-
- def _start_vpcscon(self):
- """
- Starts vpcscon thread (for console connections).
- """
-
- if not self._vpcscon_thead:
- telnet_server = "{}:{}".format(self._host, self._console)
- log.info("starting vpcscon for VPCS instance {} to accept Telnet connections on {}".format(self._name, telnet_server))
- args = argparse.Namespace(appl_id=str(self._id), debug=False, escape='^^', telnet_limit=0, telnet_server=telnet_server)
- self._vpcscon_thread_stop_event = threading.Event()
- self._vpcscon_thead = threading.Thread(target=start_vpcscon, args=(args, self._vpcscon_thread_stop_event))
- self._vpcscon_thead.start() ", ".join(missing_libs)))
-
- def start(self):
- """
- Starts the VPCS process.
- """
-
- if not self.is_running():
-
- if not os.path.isfile(self._path):
- raise VPCSError("VPCS image '{}' is not accessible".format(self._path))
-
- if not os.access(self._path, os.X_OK):
- raise VPCSError("VPCS image '{}' is not executable".format(self._path))
-
- self._command = self._build_command()
- try:
- log.info("starting VPCS: {}".format(self._command))
- self._vpcs_stdout_file = os.path.join(self._working_dir, "vpcs.log")
- log.info("logging to {}".format(self._vpcs_stdout_file))
- with open(self._vpcs_stdout_file, "w") as fd:
- self._process = subprocess.Popen(self._command,
- stdout=fd,
- stderr=subprocess.STDOUT,
- cwd=self._working_dir)
- log.info("VPCS instance {} started PID={}".format(self._id, self._process.pid))
- self._started = True
- except FileNotFoundError as e:
- raise VPCSError("could not start VPCS: {}: 32-bit binary support is probably not installed".format(e))
- except OSError as e:
- vpcs_stdout = self.read_vpcs_stdout()
- log.error("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout))
- raise VPCSError("could not start VPCS {}: {}\n{}".format(self._path, e, vpcs_stdout))
-
- # start console support
- self._start_vpcscon()
-
- def stop(self):
- """
- Stops the VPCS process.
- """
-
- # stop the VPCS process
- if self.is_running():
- log.info("stopping VPCS instance {} PID={}".format(self._id, self._process.pid))
- try:
- self._process.terminate()
- self._process.wait(1)
- except subprocess.TimeoutExpired:
- self._process.kill()
- if self._process.poll() == None:
- log.warn("VPCS instance {} PID={} is still running".format(self._id,
- self._process.pid))
- self._process = None
- self._started = False
-
- # stop console support
- if self._vpcscon_thead:
- self._vpcscon_thread_stop_event.set()
- if self._vpcscon_thead.is_alive():
- self._vpcscon_thead.join(timeout=0.10)
- self._vpcscon_thead = None
-
-
- def read_vpcs_stdout(self):
- """
- Reads the standard output of the VPCS process.
- Only use when the process has been stopped or has crashed.
- """
-
- output = ""
- if self._vpcs_stdout_file:
- try:
- with open(self._vpcs_stdout_file) as file:
- output = file.read()
- except OSError as e:
- log.warn("could not read {}: {}".format(self._vpcs_stdout_file, e))
- return output
-
- def is_running(self):
- """
- Checks if the VPCS process is running
-
- :returns: True or False
- """
-
- if self._process and self._process.poll() == None:
- return True
- return False
-
-
- def slot_add_nio_binding(self, slot_id, port_id, nio):
- """
- Adds a slot NIO binding.
-
- :param slot_id: slot ID
- :param port_id: port ID
- :param nio: NIO instance to add to the slot/port
- """
-
- try:
- adapter = self._slots[slot_id]
- except IndexError:
- raise VPCSError("Slot {slot_id} doesn't exist on VPCS {name}".format(name=self._name,
- slot_id=slot_id))
-
- if not adapter.port_exists(port_id):
- raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
- port_id=port_id))
-
- adapter.add_nio(port_id, nio)
- log.info("VPCS {name} [id={id}]: {nio} added to {slot_id}/{port_id}".format(name=self._name,
- id=self._id,
- nio=nio,
- slot_id=slot_id,
- port_id=port_id))
-
- def slot_remove_nio_binding(self, slot_id, port_id):
- """
- Removes a slot NIO binding.
-
- :param slot_id: slot ID
- :param port_id: port ID
- """
-
- try:
- adapter = self._slots[slot_id]
- except IndexError:
- raise VPCSError("Slot {slot_id} doesn't exist on VPCS {name}".format(name=self._name,
- slot_id=slot_id))
-
- if not adapter.port_exists(port_id):
- raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
- port_id=port_id))
-
- nio = adapter.get_nio(port_id)
- adapter.remove_nio(port_id)
- log.info("VPCS {name} [id={id}]: {nio} removed from {slot_id}/{port_id}".format(name=self._name,
- id=self._id,
- nio=nio,
- slot_id=slot_id,
- port_id=port_id))
-
- def _build_command(self):
- """
- Command to start the VPCS process.
- (to be passed to subprocess.Popen())
-
- VPCS command line:
- usage: vpcs [options] [scriptfile]
- Option:
- -h print this help then exit
- -v print version information then exit
-
- -p port run as a daemon listening on the tcp 'port'
- -m num start byte of ether address, default from 0
- -r file load and execute script file
- compatible with older versions, DEPRECATED.
-
- -e tap mode, using /dev/tapx (linux only)
- -u udp mode, default
-
- udp mode options:
- -s port local udp base port, default from 20000
- -c port remote udp base port (dynamips udp port), default from 30000
- -t ip remote host IP, default 127.0.0.1
-
- hypervisor mode option:
- -H port run as the hypervisor listening on the tcp 'port'
-
- If no 'scriptfile' specified, vpcs will read and execute the file named
- 'startup.vpc' if it exsits in the current directory.
-
- """
-
- command = [self._path]
- command.extend(["-p", str(self._tcpport)])
- command.extend(["-s", str(self._lport)])
- command.extend(["-c", str(self._rport)])
- command.extend(["-t", str(self._rhost)])
- command.extend(["-m", str(self._id)]) #The unique ID is used to set the mac address offset
- if self._script_file:
- command.extend([self._script_file])
- return command
-
- @property
- def script_file(self):
- """
- Returns the startup-config for this VPCS instance.
-
- :returns: path to startup-config file
- """
-
- return self._script_file
-
- @script_file.setter
- def script_file(self, script_file):
- """
- Sets the startup-config for this VPCS instance.
-
- :param script_file: path to startup-config file
- """
-
- self._script_file = script_file
- log.info("VPCS {name} [id={id}]: script_file set to {config}".format(name=self._name,
- id=self._id,
- config=self._script_file))
-
-
diff --git a/gns3server/modules/vpcs/vpcs_error.py~ b/gns3server/modules/vpcs/vpcs_error.py~
deleted file mode 100644
index 33ed081c..00000000
--- a/gns3server/modules/vpcs/vpcs_error.py~
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013 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 .
-
-"""
-Custom exceptions for VPCS module.
-"""
-
-
-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
-
- def __repr__(self):
-
- return self._message
-
- def __str__(self):
-
- return self._message
diff --git a/gns3server/modules/vpcs/vpcscon.py~ b/gns3server/modules/vpcs/vpcscon.py~
deleted file mode 100644
index 138b61e7..00000000
--- a/gns3server/modules/vpcs/vpcscon.py~
+++ /dev/null
@@ -1,642 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013, 2014 James E. Carpenter
-#
-# 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 .
-#
-
-import socket
-import sys
-import os
-import select
-import fcntl
-import struct
-import termios
-import tty
-import time
-import argparse
-import traceback
-
-
-import logging
-log = logging.getLogger(__name__)
-
-
-# Escape characters
-ESC_CHAR = '^^' # can be overriden from command line
-ESC_QUIT = 'q'
-
-# IOU seems to only send *1* byte at a time. If
-# they ever fix that we'll be ready for it.
-BUFFER_SIZE = 1024
-
-# How long to wait before retrying a connection (seconds)
-RETRY_DELAY = 3
-
-# How often to test an idle connection (seconds)
-POLL_TIMEOUT = 3
-
-
-EXIT_SUCCESS = 0
-EXIT_FAILURE = 1
-EXIT_ABORT = 2
-
-# Mostly from:
-# https://code.google.com/p/miniboa/source/browse/trunk/miniboa/telnet.py
-#--[ Telnet Commands ]---------------------------------------------------------
-SE = 240 # End of subnegotiation parameters
-NOP = 241 # No operation
-DATMK = 242 # Data stream portion of a sync.
-BREAK = 243 # NVT Character BRK
-IP = 244 # Interrupt Process
-AO = 245 # Abort Output
-AYT = 246 # Are you there
-EC = 247 # Erase Character
-EL = 248 # Erase Line
-GA = 249 # The Go Ahead Signal
-SB = 250 # Sub-option to follow
-WILL = 251 # Will; request or confirm option begin
-WONT = 252 # Wont; deny option request
-DO = 253 # Do = Request or confirm remote option
-DONT = 254 # Don't = Demand or confirm option halt
-IAC = 255 # Interpret as Command
-SEND = 1 # Sub-process negotiation SEND command
-IS = 0 # Sub-process negotiation IS command
-#--[ Telnet Options ]----------------------------------------------------------
-BINARY = 0 # Transmit Binary
-ECHO = 1 # Echo characters back to sender
-RECON = 2 # Reconnection
-SGA = 3 # Suppress Go-Ahead
-TMARK = 6 # Timing Mark
-TTYPE = 24 # Terminal Type
-NAWS = 31 # Negotiate About Window Size
-LINEMO = 34 # Line Mode
-
-
-class FileLock:
-
- # struct flock { /* from fcntl(2) */
- # ...
- # short l_type; /* Type of lock: F_RDLCK,
- # F_WRLCK, F_UNLCK */
- # short l_whence; /* How to interpret l_start:
- # SEEK_SET, SEEK_CUR, SEEK_END */
- # off_t l_start; /* Starting offset for lock */
- # off_t l_len; /* Number of bytes to lock */
- # pid_t l_pid; /* PID of process blocking our lock
- # (F_GETLK only) */
- # ...
- # };
- _flock = struct.Struct('hhqql')
-
- def __init__(self, fname=None):
- self.fd = None
- self.fname = fname
-
- def get_lock(self):
- flk = self._flock.pack(fcntl.F_WRLCK, os.SEEK_SET,
- 0, 0, os.getpid())
- flk = self._flock.unpack(
- fcntl.fcntl(self.fd, fcntl.F_GETLK, flk))
-
- # If it's not locked (or is locked by us) then return None,
- # otherwise return the PID of the owner.
- if flk[0] == fcntl.F_UNLCK:
- return None
- return flk[4]
-
- def lock(self):
- try:
- self.fd = open('{}.lck'.format(self.fname), 'a')
- except Exception as e:
- raise LockError("Couldn't get lock on {}: {}"
- .format(self.fname, e))
-
- flk = self._flock.pack(fcntl.F_WRLCK, os.SEEK_SET, 0, 0, 0)
- try:
- fcntl.fcntl(self.fd, fcntl.F_SETLK, flk)
- except BlockingIOError:
- raise LockError("Already connected. PID {} has lock on {}"
- .format(self.get_lock(), self.fname))
-
- # If we got here then we must have the lock. Store the PID.
- self.fd.truncate(0)
- self.fd.write('{}\n'.format(os.getpid()))
- self.fd.flush()
-
- def unlock(self):
- if self.fd:
- # Deleting first prevents a race condition
- os.unlink(self.fd.name)
- self.fd.close()
-
- def __enter__(self):
- self.lock()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.unlock()
- return False
-
-
-class Console:
- def fileno(self):
- raise NotImplementedError("Only routers have fileno()")
-
-
-class Router:
- pass
-
-
-class TTY(Console):
-
- def read(self, fileno, bufsize):
- return self.fd.read(bufsize)
-
- def write(self, buf):
- return self.fd.write(buf)
-
- def register(self, epoll):
- self.epoll = epoll
- epoll.register(self.fd, select.EPOLLIN | select.EPOLLET)
-
- def __enter__(self):
- try:
- self.fd = open('/dev/tty', 'r+b', buffering=0)
- except OSError as e:
- raise TTYError("Couldn't open controlling TTY: {}".format(e))
-
- # Save original flags
- self.termios = termios.tcgetattr(self.fd)
- self.fcntl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
-
- # Update flags
- tty.setraw(self.fd, termios.TCSANOW)
- fcntl.fcntl(self.fd, fcntl.F_SETFL, self.fcntl | os.O_NONBLOCK)
-
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
-
- # Restore flags to original settings
- termios.tcsetattr(self.fd, termios.TCSANOW, self.termios)
- fcntl.fcntl(self.fd, fcntl.F_SETFL, self.fcntl)
-
- self.fd.close()
-
- return False
-
-
-class TelnetServer(Console):
-
- def __init__(self, addr, port, stop_event):
- self.addr = addr
- self.port = port
- self.fd_dict = {}
- self.stop_event = stop_event
-
- def read(self, fileno, bufsize):
- # Someone wants to connect?
- if fileno == self.sock_fd.fileno():
- self._accept()
- return None
-
- self._cur_fileno = fileno
-
- # Read a maximum of _bufsize_ bytes without blocking. When it
- # would want to block it means there's no more data. An empty
- # buffer normally means that we've been disconnected.
- try:
- buf = self._read_cur(bufsize, socket.MSG_DONTWAIT)
- except BlockingIOError:
- return None
- if not buf:
- self._disconnect(fileno)
-
- # Process and remove any telnet commands from the buffer
- if IAC in buf:
- buf = self._IAC_parser(buf)
-
- return buf
-
- def write(self, buf):
- for fd in self.fd_dict.values():
- fd.send(buf)
-
- def register(self, epoll):
- self.epoll = epoll
- epoll.register(self.sock_fd, select.EPOLLIN)
-
- def _read_block(self, bufsize):
- buf = self._read_cur(bufsize, socket.MSG_WAITALL)
- # If we don't get everything we were looking for then the
- # client probably disconnected.
- if len(buf) < bufsize:
- self._disconnect(self._cur_fileno)
- return buf
-
- def _read_cur(self, bufsize, flags):
- return self.fd_dict[self._cur_fileno].recv(bufsize, flags)
-
- def _write_cur(self, buf):
- return self.fd_dict[self._cur_fileno].send(buf)
-
- def _IAC_parser(self, buf):
- skip_to = 0
- while not self.stop_event.is_set():
- # Locate an IAC to process
- iac_loc = buf.find(IAC, skip_to)
- if iac_loc < 0:
- break
-
- # Get the TELNET command
- iac_cmd = bytearray([IAC])
- try:
- iac_cmd.append(buf[iac_loc + 1])
- except IndexError:
- buf.extend(self._read_block(1))
- iac_cmd.append(buf[iac_loc + 1])
-
- # Is this just a 2-byte TELNET command?
- if iac_cmd[1] not in [WILL, WONT, DO, DONT]:
- if iac_cmd[1] == AYT:
- log.debug("Telnet server received Are-You-There (AYT)")
- self._write_cur(
- b'\r\nYour Are-You-There received. I am here.\r\n'
- )
- elif iac_cmd[1] == IAC:
- # It's data, not an IAC
- iac_cmd.pop()
- # This prevents the 0xff from being
- # interputed as yet another IAC
- skip_to = iac_loc + 1
- log.debug("Received IAC IAC")
- elif iac_cmd[1] == NOP:
- pass
- else:
- log.debug("Unhandled telnet command: "
- "{0:#x} {1:#x}".format(*iac_cmd))
-
- # This must be a 3-byte TELNET command
- else:
- try:
- iac_cmd.append(buf[iac_loc + 2])
- except IndexError:
- buf.extend(self._read_block(1))
- iac_cmd.append(buf[iac_loc + 2])
- # We do ECHO, SGA, and BINARY. Period.
- if (iac_cmd[1] == DO
- and iac_cmd[2] not in [ECHO, SGA, BINARY]):
-
- self._write_cur(bytes([IAC, WONT, iac_cmd[2]]))
- log.debug("Telnet WON'T {:#x}".format(iac_cmd[2]))
- else:
- log.debug("Unhandled telnet command: "
- "{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
-
- # Remove the entire TELNET command from the buffer
- buf = buf.replace(iac_cmd, b'', 1)
-
- # Return the new copy of the buffer, minus telnet commands
- return buf
-
- def _accept(self):
- fd, addr = self.sock_fd.accept()
- self.fd_dict[fd.fileno()] = fd
- self.epoll.register(fd, select.EPOLLIN | select.EPOLLET)
-
- log.info("Telnet connection from {}:{}".format(addr[0], addr[1]))
-
- # This is a one-way negotiation. This is very basic so there
- # shouldn't be any problems with any decent client.
- fd.send(bytes([IAC, WILL, ECHO,
- IAC, WILL, SGA,
- IAC, WILL, BINARY,
- IAC, DO, BINARY]))
-
- if args.telnet_limit and len(self.fd_dict) > args.telnet_limit:
- fd.send(b'\r\nToo many connections\r\n')
- self._disconnect(fd.fileno())
- log.warn("Client disconnected because of too many connections. "
- "(limit currently {})".format(args.telnet_limit))
-
- def _disconnect(self, fileno):
- fd = self.fd_dict.pop(fileno)
- log.info("Telnet client disconnected")
- fd.shutdown(socket.SHUT_RDWR)
- fd.close()
-
- def __enter__(self):
- # Open a socket and start listening
- sock_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- try:
- sock_fd.bind((self.addr, self.port))
- except OSError:
- raise TelnetServerError("Cannot bind to {}:{}"
- .format(self.addr, self.port))
-
- sock_fd.listen(socket.SOMAXCONN)
- self.sock_fd = sock_fd
- log.info("Telnet server ready for connections on {}:{}".format(self.addr, self.port))
-
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- for fileno in list(self.fd_dict.keys()):
- self._disconnect(fileno)
- self.sock_fd.close()
- return False
-
-
-class IOU(Router):
-
- def __init__(self, ttyC, ttyS, stop_event):
- self.ttyC = ttyC
- self.ttyS = ttyS
- self.stop_event = stop_event
-
- def read(self, bufsize):
- try:
- buf = self.fd.recv(bufsize)
- except BlockingIOError:
- return None
- return buf
-
- def write(self, buf):
- self.fd.send(buf)
-
- def _open(self):
- self.fd = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
- self.fd.setblocking(False)
-
- def _bind(self):
- try:
- os.unlink(self.ttyC)
- except FileNotFoundError:
- pass
- except Exception as e:
- raise NetioError("Couldn't unlink socket {}: {}"
- .format(self.ttyC, e))
-
- try:
- self.fd.bind(self.ttyC)
- except Exception as e:
- raise NetioError("Couldn't create socket {}: {}"
- .format(self.ttyC, e))
-
- def _connect(self):
- # Keep trying until we connect or die trying
- while not self.stop_event.is_set():
- try:
- self.fd.connect(self.ttyS)
- except FileNotFoundError:
- log.debug("Waiting to connect to {}".format(self.ttyS))
- time.sleep(RETRY_DELAY)
- except Exception as e:
- raise NetioError("Couldn't connect to socket {}: {}"
- .format(self.ttyS, e))
- else:
- break
-
- def register(self, epoll):
- self.epoll = epoll
- epoll.register(self.fd, select.EPOLLIN | select.EPOLLET)
-
- def fileno(self):
- return self.fd.fileno()
-
- def __enter__(self):
- self._open()
- self._bind()
- self._connect()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- os.unlink(self.ttyC)
- self.fd.close()
- return False
-
-
-class IOUConError(Exception):
- pass
-
-
-class LockError(IOUConError):
- pass
-
-
-class NetioError(IOUConError):
- pass
-
-
-class TTYError(IOUConError):
- pass
-
-
-class TelnetServerError(IOUConError):
- pass
-
-
-class ConfigError(IOUConError):
- pass
-
-
-def mkdir_netio(netio_dir):
- try:
- os.mkdir(netio_dir)
- except FileExistsError:
- pass
- except Exception as e:
- raise NetioError("Couldn't create directory {}: {}"
- .format(netio_dir, e))
-
-
-def send_recv_loop(console, router, esc_char, stop_event):
-
- epoll = select.epoll()
- router.register(epoll)
- console.register(epoll)
-
- router_fileno = router.fileno()
- esc_quit = bytes(ESC_QUIT.upper(), 'ascii')
- esc_state = False
-
- while not stop_event.is_set():
- event_list = epoll.poll(timeout=POLL_TIMEOUT)
-
- # When/if the poll times out we send an empty datagram. If IOU
- # has gone away then this will toss a ConnectionRefusedError
- # exception.
- if not event_list:
- router.write(b'')
- continue
-
- for fileno, event in event_list:
- buf = bytearray()
-
- # IOU --> tty(s)
- if fileno == router_fileno:
- while not stop_event.is_set():
- data = router.read(BUFFER_SIZE)
- if not data:
- break
- buf.extend(data)
- console.write(buf)
-
- # tty --> IOU
- else:
- while not stop_event.is_set():
- data = console.read(fileno, BUFFER_SIZE)
- if not data:
- break
- buf.extend(data)
-
- # If we just received the escape character then
- # enter the escape state.
- #
- # If we are in the escape state then check for a
- # quit command. Or if it's the escape character then
- # send the escape character. Else, send the escape
- # character we ate earlier and whatever character we
- # just got. Exit escape state.
- #
- # If we're not in the escape state and this isn't an
- # escape character then just send it to IOU.
- if esc_state:
- if buf.upper() == esc_quit:
- sys.exit(EXIT_SUCCESS)
- elif buf == esc_char:
- router.write(esc_char)
- else:
- router.write(esc_char)
- router.write(buf)
- esc_state = False
- elif buf == esc_char:
- esc_state = True
- else:
- router.write(buf)
-
-
-def get_args():
- parser = argparse.ArgumentParser(
- description='Connect to an IOU console port.')
- parser.add_argument('-d', '--debug', action='store_true',
- help='display some debugging information')
- parser.add_argument('-e', '--escape',
- help='set escape character (default: %(default)s)',
- default=ESC_CHAR, metavar='CHAR')
- parser.add_argument('-t', '--telnet-server',
- help='start telnet server listening on ADDR:PORT',
- metavar='ADDR:PORT', default=False)
- parser.add_argument('-l', '--telnet-limit',
- help='maximum number of simultaneous '
- 'telnet connections (default: %(default)s)',
- metavar='LIMIT', type=int, default=1)
- parser.add_argument('appl_id', help='IOU instance identifier')
- return parser.parse_args()
-
-
-def get_escape_character(escape):
-
- # Figure out the escape character to use.
- # Can be any ASCII character or a spelled out control
- # character, like "^e". The string "none" disables it.
- if escape.lower() == 'none':
- esc_char = b''
- elif len(escape) == 2 and escape[0] == '^':
- c = ord(escape[1].upper()) - 0x40
- if not 0 <= c <= 0x1f: # control code range
- raise ConfigError("Invalid control code")
- esc_char = bytes([c])
- elif len(escape) == 1:
- try:
- esc_char = bytes(escape, 'ascii')
- except ValueError as e:
- raise ConfigError("Invalid escape character") from e
- else:
- raise ConfigError("Invalid length for escape character")
-
- return esc_char
-
-
-def start_ioucon(cmdline_args, stop_event):
-
- global args
- args = cmdline_args
-
- if args.debug:
- logging.basicConfig(level=logging.DEBUG)
- else:
- # default logging level
- logging.basicConfig(level=logging.INFO)
-
- # Create paths for the Unix domain sockets
- netio = '/tmp/netio{}'.format(os.getuid())
- ttyC = '{}/ttyC{}'.format(netio, args.appl_id)
- ttyS = '{}/ttyS{}'.format(netio, args.appl_id)
-
- try:
- mkdir_netio(netio)
- with FileLock(ttyC):
- esc_char = get_escape_character(args.escape)
-
- if args.telnet_server:
- addr, _, port = args.telnet_server.partition(':')
- nport = 0
- try:
- nport = int(port)
- except ValueError:
- pass
- if (addr == '' or nport == 0):
- raise ConfigError('format for --telnet-server must be '
- 'ADDR:PORT (like 127.0.0.1:20000)')
-
- while not stop_event.is_set():
- try:
- if args.telnet_server:
- with TelnetServer(addr, nport, stop_event) as console:
- with IOU(ttyC, ttyS, stop_event) as router:
- send_recv_loop(console, router, b'', stop_event)
- else:
- with IOU(ttyC, ttyS, stop_event) as router, TTY() as console:
- send_recv_loop(console, router, esc_char, stop_event)
- except ConnectionRefusedError:
- pass
- except KeyboardInterrupt:
- sys.exit(EXIT_ABORT)
- finally:
- # Put us at the beginning of a line
- if not args.telnet_server:
- print()
-
- except IOUConError as e:
- if args.debug:
- traceback.print_exc(file=sys.stderr)
- else:
- print(e, file=sys.stderr)
- sys.exit(EXIT_FAILURE)
-
- log.info("exiting...")
-
-
-def main():
-
- import threading
- stop_event = threading.Event()
- args = get_args()
- start_ioucon(args, stop_event)
-
-if __name__ == '__main__':
- main()