Skip to content

Commit

Permalink
WIP: Support for serial console for Virtual BOX
Browse files Browse the repository at this point in the history
Ref #747
  • Loading branch information
julien-duponchelle committed Nov 7, 2016
1 parent 3e0854e commit 9b273d7
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 84 deletions.
93 changes: 11 additions & 82 deletions gns3server/compute/virtualbox/virtualbox_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
import xml.etree.ElementTree as ET

from gns3server.utils import parse_version
from gns3server.utils.asyncio.serial import asyncio_open_serial
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
from gns3server.utils.telnet_server import TelnetServer
from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation, locked_coroutine
from gns3server.utils.asyncio import locked_coroutine
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
from gns3server.compute.nios.nio_udp import NIOUDP
from gns3server.compute.adapters.ethernet_adapter import EthernetAdapter
Expand All @@ -54,12 +55,11 @@ class VirtualBoxVM(BaseNode):

def __init__(self, name, node_id, project, manager, vmname, linked_clone=False, console=None, adapters=0):

super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone)
super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type="telnet")

self._maximum_adapters = 8
self._system_properties = {}
self._telnet_server_thread = None
self._serial_pipe = None
self._telnet_server = None
self._local_udp_tunnels = {}

# VirtualBox settings
Expand All @@ -82,6 +82,7 @@ def __json__(self):
json = {"name": self.name,
"node_id": self.id,
"console": self.console,
"console_type": self.console_type,
"project_id": self.project.id,
"vmname": self.vmname,
"headless": self.headless,
Expand Down Expand Up @@ -871,59 +872,16 @@ def _start_console(self):
"""
Starts remote console support for this VM.
"""

try:
# wait for VirtualBox to create the pipe file.
if sys.platform.startswith("win"):
yield from wait_for_named_pipe_creation(self._get_pipe_name())
else:
yield from wait_for_file_creation(self._get_pipe_name())
except asyncio.TimeoutError:
raise VirtualBoxError('Pipe file "{}" for remote console has not been created by VirtualBox'.format(self._get_pipe_name()))

# starts the Telnet to pipe thread
if sys.platform.startswith("win"):
try:
self._serial_pipe = open(pipe_name, "a+b")
except OSError as e:
raise VirtualBoxError("Could not open the pipe {}: {}".format(self._get_pipe_name(), e))
try:
self._telnet_server_thread = TelnetServer(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._manager.port_manager.console_host, self._console)
except OSError as e:
raise VirtualBoxError("Unable to create Telnet server: {}".format(e))
self._telnet_server_thread.start()
else:
return
try:
self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._serial_pipe.connect(self._get_pipe_name())
except OSError as e:
raise VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
try:
self._telnet_server_thread = TelnetServer(self._vmname, self._serial_pipe, self._manager.port_manager.console_host, self._console)
except OSError as e:
raise VirtualBoxError("Unable to create Telnet server: {}".format(e))
self._telnet_server_thread.start()
pipe = yield from asyncio_open_serial(self._get_pipe_name())
server = AsyncioTelnetServer(reader=pipe, writer=pipe, binary=True, echo=True)
self._telnet_server = yield from asyncio.start_server(server.run, '127.0.0.1', self.console)

def _stop_remote_console(self):
"""
Stops remote console support for this VM.
"""

if self._telnet_server_thread:
if self._telnet_server_thread.is_alive():
self._telnet_server_thread.stop()
self._telnet_server_thread.join(timeout=3)
if self._telnet_server_thread.is_alive():
log.warn("Serial pipe thread is still alive!")
self._telnet_server_thread = None

if self._serial_pipe:
if sys.platform.startswith("win"):
win32file.CloseHandle(msvcrt.get_osfhandle(self._serial_pipe.fileno()))
else:
self._serial_pipe.close()
self._serial_pipe = None
if self._telnet_server:
self._telnet_server.close()

@asyncio.coroutine
def adapter_add_nio_binding(self, adapter_number, nio):
Expand Down Expand Up @@ -1083,36 +1041,7 @@ def stop_capture(self, adapter_number):
loop = asyncio.get_event_loop()

vm = VirtualBoxVM("Debian", str(uuid.uuid4()), Project(), VirtualBox.instance(), "Debian")

class SerialReaderWriterProtocol(asyncio.Protocol):

def __init__(self):
self._output = asyncio.StreamReader()
self.transport = None

def read(self, n=-1):
return self._output.read(n=n)

def at_eof(self):
return self._output.at_eof()

def write(self, data):
if self.transport:
self.transport.write(data)

@asyncio.coroutine
def drain(self):
pass

def connection_made(self, transport):
self.transport = transport

def data_received(self, data):
self._output.feed_data(data)

output = SerialReaderWriterProtocol()
con = loop.create_unix_connection(lambda: output, "/tmp/testpipe")
loop.run_until_complete(con)
output = loop.run_until_complete(asyncio_open_serial("/tmp/testpipe"))
server = AsyncioTelnetServer(reader=output, writer=output, binary=True, echo=True)

coro = asyncio.start_server(server.run, '127.0.0.1', vm.console, loop=loop)
Expand Down
8 changes: 8 additions & 0 deletions gns3server/schemas/virtualbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
"maximum": 65535,
"type": "integer"
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
},
"ram": {
"description": "Amount of RAM",
"minimum": 0,
Expand Down Expand Up @@ -152,6 +156,10 @@
"maximum": 65535,
"type": "integer"
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
},
"ram": {
"description": "Amount of RAM",
"minimum": 0,
Expand Down
76 changes: 76 additions & 0 deletions gns3server/utils/asyncio/serial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python
#
# Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.

import sys
import asyncio
from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation
from gns3server.compute.error import NodeError

"""
This module handle connection to unix socket or Windows named pipe
"""


class SerialReaderWriterProtocol(asyncio.Protocol):

def __init__(self):
self._output = asyncio.StreamReader()
self.transport = None

def read(self, n=-1):
return self._output.read(n=n)

def at_eof(self):
return self._output.at_eof()

def write(self, data):
if self.transport:
self.transport.write(data)

@asyncio.coroutine
def drain(self):
pass

def connection_made(self, transport):
self.transport = transport

def data_received(self, data):
self._output.feed_data(data)

#TODO: Manage CLOSE cleanup the pipe


@asyncio.coroutine
def asyncio_open_serial(path):
"""
Open a unix socket or a windows named pipe
:returns: An IO like object
"""

try:
# wait for VM to create the pipe file.
if sys.platform.startswith("win"):
yield from wait_for_named_pipe_creation(path)
else:
yield from wait_for_file_creation(path)
except asyncio.TimeoutError:
raise NodeError('Pipe file "{}" is missing'.format(path))

output = SerialReaderWriterProtocol()
con = yield from asyncio.get_event_loop().create_unix_connection(lambda: output, path)
return output
5 changes: 3 additions & 2 deletions gns3server/utils/asyncio/telnet_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ def _IAC_parser(self, buf, network_reader, network_writer):
log.debug("Unhandled DONT telnet command: "
"{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
elif iac_cmd[1] == WILL:
log.debug("Unhandled WILL telnet command: "
"{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
if iac_cmd[2] not in [BINARY]:
log.debug("Unhandled WILL telnet command: "
"{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
elif iac_cmd[1] == WONT:
log.debug("Unhandled WONT telnet command: "
"{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
Expand Down

0 comments on commit 9b273d7

Please sign in to comment.