mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Telnet to pipe support for VirtualBox.
This commit is contained in:
parent
9ac2716826
commit
65d70bacfa
@ -521,7 +521,7 @@ class IOUDevice(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(["ldd", self._path])
|
output = subprocess.check_output(["ldd", self._path])
|
||||||
except (subprocess.SubprocessError, FileNotFoundError) as e:
|
except (FileNotFoundError, subprocess.CalledProcessError) as e:
|
||||||
log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e))
|
log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e))
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -761,7 +761,7 @@ class IOUDevice(object):
|
|||||||
command.extend(["-l"])
|
command.extend(["-l"])
|
||||||
else:
|
else:
|
||||||
raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path)))
|
raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path)))
|
||||||
except OSError as e:
|
except (OSError, subprocess.CalledProcessError) as e:
|
||||||
log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e))
|
log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e))
|
||||||
|
|
||||||
def _build_command(self):
|
def _build_command(self):
|
||||||
|
477
gns3server/modules/virtualbox/pipe_proxy.py
Normal file
477
gns3server/modules/virtualbox/pipe_proxy.py
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Parts of this code have been taken from Pyserial project (http://pyserial.sourceforge.net/) under Python license
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import socket
|
||||||
|
import select
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
import win32pipe
|
||||||
|
import win32file
|
||||||
|
|
||||||
|
|
||||||
|
class PipeProxy(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, name, pipe, host, port):
|
||||||
|
self.devname = name
|
||||||
|
self.pipe = pipe
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.server = None
|
||||||
|
self.reader_thread = None
|
||||||
|
self.use_thread = False
|
||||||
|
self._write_lock = threading.Lock()
|
||||||
|
self.clients = {}
|
||||||
|
self.timeout = 0.1
|
||||||
|
self.alive = True
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
# we must a thread for reading the pipe on Windows because it is a Named Pipe and it cannot be monitored by select()
|
||||||
|
self.use_thread = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.host.__contains__(':'):
|
||||||
|
# IPv6 address support
|
||||||
|
self.server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||||
|
else:
|
||||||
|
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
self.server.bind((self.host, int(self.port)))
|
||||||
|
self.server.listen(5)
|
||||||
|
except socket.error as msg:
|
||||||
|
self.error("unable to create the socket server %s" % msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.debug("initialized, waiting for clients on %s:%i..." % (self.host, self.port))
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
|
||||||
|
sys.stderr.write("ERROR -> %s PIPE PROXY: %s\n" % (self.devname, msg))
|
||||||
|
|
||||||
|
def debug(self, msg):
|
||||||
|
|
||||||
|
sys.stdout.write("INFO -> %s PIPE PROXY: %s\n" % (self.devname, msg))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
recv_list = [self.server.fileno()]
|
||||||
|
|
||||||
|
if not self.use_thread:
|
||||||
|
recv_list.append(self.pipe.fileno())
|
||||||
|
|
||||||
|
for client in self.clients.values():
|
||||||
|
if client.active:
|
||||||
|
recv_list.append(client.fileno)
|
||||||
|
else:
|
||||||
|
self.debug("lost client %s" % client.addrport())
|
||||||
|
try:
|
||||||
|
client.sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
del self.clients[client.fileno]
|
||||||
|
|
||||||
|
try:
|
||||||
|
rlist, slist, elist = select.select(recv_list, [], [], self.timeout)
|
||||||
|
except select.error as err:
|
||||||
|
self.error("fatal select error %d:%s" % (err[0], err[1]))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.alive:
|
||||||
|
self.debug('Exiting ...')
|
||||||
|
return True
|
||||||
|
|
||||||
|
for sock_fileno in rlist:
|
||||||
|
if sock_fileno == self.server.fileno():
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock, addr = self.server.accept()
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||||
|
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
self.debug("new client %s:%s" % (addr[0], addr[1]))
|
||||||
|
except socket.error as err:
|
||||||
|
self.error("accept error %d:%s" % (err[0], err[1]))
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_client = TelnetClient(sock, addr)
|
||||||
|
self.clients[new_client.fileno] = new_client
|
||||||
|
welcome_msg = "%s console is now available ... Press RETURN to get started.\r\n" % self.devname
|
||||||
|
sock.send(welcome_msg.encode('utf-8'))
|
||||||
|
|
||||||
|
if self.use_thread and not self.reader_thread:
|
||||||
|
self.reader_thread = threading.Thread(target=self.reader)
|
||||||
|
self.reader_thread.setDaemon(True)
|
||||||
|
self.reader_thread.setName('pipe->socket')
|
||||||
|
self.reader_thread.start()
|
||||||
|
|
||||||
|
elif not self.use_thread and sock_fileno == self.pipe.fileno():
|
||||||
|
|
||||||
|
data = self.read_from_pipe()
|
||||||
|
if not data:
|
||||||
|
self.debug("pipe has been closed!")
|
||||||
|
return False
|
||||||
|
for client in self.clients.values():
|
||||||
|
try:
|
||||||
|
client.send(data)
|
||||||
|
except:
|
||||||
|
self.debug(msg)
|
||||||
|
client.deactivate()
|
||||||
|
elif sock_fileno in self.clients:
|
||||||
|
try:
|
||||||
|
data = self.clients[sock_fileno].socket_recv()
|
||||||
|
|
||||||
|
# For some reason, windows likes to send "cr/lf" when you send a "cr".
|
||||||
|
# Strip that so we don't get a double prompt.
|
||||||
|
#data = string.replace(data, chr(13) + chr(10), chr(13))
|
||||||
|
data = data.replace(bytearray([13, 10]), bytes(13))
|
||||||
|
|
||||||
|
self.write_to_pipe(data)
|
||||||
|
except Exception as msg:
|
||||||
|
self.debug(msg)
|
||||||
|
self.clients[sock_fileno].deactivate()
|
||||||
|
|
||||||
|
def write_to_pipe(self, data):
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
win32file.WriteFile(self.pipe, data)
|
||||||
|
else:
|
||||||
|
self.pipe.sendall(data)
|
||||||
|
|
||||||
|
def read_from_pipe(self):
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
(read, num_avail, num_message) = win32pipe.PeekNamedPipe(self.pipe, 0)
|
||||||
|
if num_avail > 0:
|
||||||
|
(error_code, output) = win32file.ReadFile(self.pipe, num_avail, None)
|
||||||
|
return output
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return self.pipe.recv(1024)
|
||||||
|
|
||||||
|
def reader(self):
|
||||||
|
"""loop forever and copy pipe->socket"""
|
||||||
|
|
||||||
|
self.debug("reader thread started")
|
||||||
|
while self.alive:
|
||||||
|
try:
|
||||||
|
data = self.read_from_pipe()
|
||||||
|
if not data and not sys.platform.startswith('win'):
|
||||||
|
self.debug("pipe has been closed!")
|
||||||
|
break
|
||||||
|
self._write_lock.acquire()
|
||||||
|
try:
|
||||||
|
for client in self.clients.values():
|
||||||
|
client.send(data)
|
||||||
|
finally:
|
||||||
|
self._write_lock.release()
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
# sleep every 10 ms
|
||||||
|
time.sleep(0.01)
|
||||||
|
except:
|
||||||
|
self.debug("pipe has been closed!")
|
||||||
|
break
|
||||||
|
self.debug("reader thread exited")
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop copying"""
|
||||||
|
|
||||||
|
if self.alive:
|
||||||
|
self.alive = False
|
||||||
|
for client in self.clients.values():
|
||||||
|
client.sock.close()
|
||||||
|
client.deactivate()
|
||||||
|
|
||||||
|
# telnet protocol characters
|
||||||
|
IAC = 255 # Interpret As Command
|
||||||
|
DONT = 254
|
||||||
|
DO = 253
|
||||||
|
WONT = 252
|
||||||
|
WILL = 251
|
||||||
|
IAC_DOUBLED = [IAC, IAC]
|
||||||
|
|
||||||
|
SE = 240 # Subnegotiation End
|
||||||
|
NOP = 241 # No Operation
|
||||||
|
DM = 242 # Data Mark
|
||||||
|
BRK = 243 # Break
|
||||||
|
IP = 244 # Interrupt process
|
||||||
|
AO = 245 # Abort output
|
||||||
|
AYT = 246 # Are You There
|
||||||
|
EC = 247 # Erase Character
|
||||||
|
EL = 248 # Erase Line
|
||||||
|
GA = 249 # Go Ahead
|
||||||
|
SB = 250 # Subnegotiation Begin
|
||||||
|
|
||||||
|
# selected telnet options
|
||||||
|
ECHO = 1 # echo
|
||||||
|
SGA = 3 # suppress go ahead
|
||||||
|
LINEMODE = 34 # line mode
|
||||||
|
TERMTYPE = 24 # terminal type
|
||||||
|
|
||||||
|
# Telnet filter states
|
||||||
|
M_NORMAL = 0
|
||||||
|
M_IAC_SEEN = 1
|
||||||
|
M_NEGOTIATE = 2
|
||||||
|
|
||||||
|
# TelnetOption and TelnetSubnegotiation states
|
||||||
|
REQUESTED = 'REQUESTED'
|
||||||
|
ACTIVE = 'ACTIVE'
|
||||||
|
INACTIVE = 'INACTIVE'
|
||||||
|
REALLY_INACTIVE = 'REALLY_INACTIVE'
|
||||||
|
|
||||||
|
class TelnetOption(object):
|
||||||
|
"""Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
|
||||||
|
|
||||||
|
def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None):
|
||||||
|
"""Init option.
|
||||||
|
:param connection: connection used to transmit answers
|
||||||
|
:param name: a readable name for debug outputs
|
||||||
|
:param send_yes: what to send when option is to be enabled.
|
||||||
|
:param send_no: what to send when option is to be disabled.
|
||||||
|
:param ack_yes: what to expect when remote agrees on option.
|
||||||
|
:param ack_no: what to expect when remote disagrees on option.
|
||||||
|
:param initial_state: options initialized with REQUESTED are tried to
|
||||||
|
be enabled on startup. use INACTIVE for all others.
|
||||||
|
"""
|
||||||
|
self.connection = connection
|
||||||
|
self.name = name
|
||||||
|
self.option = option
|
||||||
|
self.send_yes = send_yes
|
||||||
|
self.send_no = send_no
|
||||||
|
self.ack_yes = ack_yes
|
||||||
|
self.ack_no = ack_no
|
||||||
|
self.state = initial_state
|
||||||
|
self.active = False
|
||||||
|
self.activation_callback = activation_callback
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""String for debug outputs"""
|
||||||
|
return "%s:%s(%s)" % (self.name, self.active, self.state)
|
||||||
|
|
||||||
|
def process_incoming(self, command):
|
||||||
|
"""A DO/DONT/WILL/WONT was received for this option, update state and
|
||||||
|
answer when needed."""
|
||||||
|
if command == self.ack_yes:
|
||||||
|
if self.state is REQUESTED:
|
||||||
|
self.state = ACTIVE
|
||||||
|
self.active = True
|
||||||
|
if self.activation_callback is not None:
|
||||||
|
self.activation_callback()
|
||||||
|
elif self.state is ACTIVE:
|
||||||
|
pass
|
||||||
|
elif self.state is INACTIVE:
|
||||||
|
self.state = ACTIVE
|
||||||
|
self.connection.telnetSendOption(self.send_yes, self.option)
|
||||||
|
self.active = True
|
||||||
|
if self.activation_callback is not None:
|
||||||
|
self.activation_callback()
|
||||||
|
elif self.state is REALLY_INACTIVE:
|
||||||
|
self.connection.telnetSendOption(self.send_no, self.option)
|
||||||
|
else:
|
||||||
|
raise ValueError('option in illegal state %r' % self)
|
||||||
|
elif command == self.ack_no:
|
||||||
|
if self.state is REQUESTED:
|
||||||
|
self.state = INACTIVE
|
||||||
|
self.active = False
|
||||||
|
elif self.state is ACTIVE:
|
||||||
|
self.state = INACTIVE
|
||||||
|
self.connection.telnetSendOption(self.send_no, self.option)
|
||||||
|
self.active = False
|
||||||
|
elif self.state is INACTIVE:
|
||||||
|
pass
|
||||||
|
elif self.state is REALLY_INACTIVE:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError('option in illegal state %r' % self)
|
||||||
|
|
||||||
|
class TelnetClient(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents a client connection via Telnet.
|
||||||
|
|
||||||
|
First argument is the socket discovered by the Telnet Server.
|
||||||
|
Second argument is the tuple (ip address, port number).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sock, addr_tup):
|
||||||
|
self.active = True # Turns False when the connection is lost
|
||||||
|
self.sock = sock # The connection's socket
|
||||||
|
self.fileno = sock.fileno() # The socket's file descriptor
|
||||||
|
self.address = addr_tup[0] # The client's remote TCP/IP address
|
||||||
|
self.port = addr_tup[1] # The client's remote port
|
||||||
|
|
||||||
|
# filter state machine
|
||||||
|
self.mode = M_NORMAL
|
||||||
|
self.suboption = None
|
||||||
|
self.telnet_command = None
|
||||||
|
|
||||||
|
# all supported telnet options
|
||||||
|
self._telnet_options = [
|
||||||
|
TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
|
||||||
|
TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
|
||||||
|
TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
|
||||||
|
TelnetOption(self, 'LINEMODE', LINEMODE, DONT, DONT, WILL, WONT, REQUESTED),
|
||||||
|
TelnetOption(self, 'TERMTYPE', TERMTYPE, DO, DONT, WILL, WONT, REQUESTED),
|
||||||
|
]
|
||||||
|
|
||||||
|
for option in self._telnet_options:
|
||||||
|
if option.state is REQUESTED:
|
||||||
|
self.telnetSendOption(option.send_yes, option.option)
|
||||||
|
|
||||||
|
def telnetSendOption(self, action, option):
|
||||||
|
"""Send DO, DONT, WILL, WONT."""
|
||||||
|
self.sock.sendall(bytes([IAC, action, option]))
|
||||||
|
|
||||||
|
def escape(self, data):
|
||||||
|
""" All outgoing data has to be properly escaped, so that no IAC character
|
||||||
|
in the data stream messes up the Telnet state machine in the server.
|
||||||
|
"""
|
||||||
|
for byte in data:
|
||||||
|
if byte == IAC:
|
||||||
|
yield IAC
|
||||||
|
yield IAC
|
||||||
|
else:
|
||||||
|
yield byte
|
||||||
|
|
||||||
|
def filter(self, data):
|
||||||
|
""" handle a bunch of incoming bytes. this is a generator. it will yield
|
||||||
|
all characters not of interest for Telnet
|
||||||
|
"""
|
||||||
|
for byte in data:
|
||||||
|
if self.mode == M_NORMAL:
|
||||||
|
# interpret as command or as data
|
||||||
|
if byte == IAC:
|
||||||
|
self.mode = M_IAC_SEEN
|
||||||
|
else:
|
||||||
|
# store data in sub option buffer or pass it to our
|
||||||
|
# consumer depending on state
|
||||||
|
if self.suboption is not None:
|
||||||
|
self.suboption.append(byte)
|
||||||
|
else:
|
||||||
|
yield byte
|
||||||
|
elif self.mode == M_IAC_SEEN:
|
||||||
|
if byte == IAC:
|
||||||
|
# interpret as command doubled -> insert character
|
||||||
|
# itself
|
||||||
|
if self.suboption is not None:
|
||||||
|
self.suboption.append(byte)
|
||||||
|
else:
|
||||||
|
yield byte
|
||||||
|
self.mode = M_NORMAL
|
||||||
|
elif byte == SB:
|
||||||
|
# sub option start
|
||||||
|
self.suboption = bytearray()
|
||||||
|
self.mode = M_NORMAL
|
||||||
|
elif byte == SE:
|
||||||
|
# sub option end -> process it now
|
||||||
|
#self._telnetProcessSubnegotiation(bytes(self.suboption))
|
||||||
|
self.suboption = None
|
||||||
|
self.mode = M_NORMAL
|
||||||
|
elif byte in (DO, DONT, WILL, WONT):
|
||||||
|
# negotiation
|
||||||
|
self.telnet_command = byte
|
||||||
|
self.mode = M_NEGOTIATE
|
||||||
|
else:
|
||||||
|
# other telnet commands are ignored!
|
||||||
|
self.mode = M_NORMAL
|
||||||
|
elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
|
||||||
|
self._telnetNegotiateOption(self.telnet_command, byte)
|
||||||
|
self.mode = M_NORMAL
|
||||||
|
|
||||||
|
def _telnetNegotiateOption(self, command, option):
|
||||||
|
"""Process incoming DO, DONT, WILL, WONT."""
|
||||||
|
# check our registered telnet options and forward command to them
|
||||||
|
# they know themselves if they have to answer or not
|
||||||
|
known = False
|
||||||
|
for item in self._telnet_options:
|
||||||
|
# can have more than one match! as some options are duplicated for
|
||||||
|
# 'us' and 'them'
|
||||||
|
if item.option == option:
|
||||||
|
item.process_incoming(command)
|
||||||
|
known = True
|
||||||
|
if not known:
|
||||||
|
# handle unknown options
|
||||||
|
# only answer to positive requests and deny them
|
||||||
|
if command == WILL or command == DO:
|
||||||
|
self.telnetSendOption((command == WILL and DONT or WONT), option)
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
"""
|
||||||
|
Send data to the distant end.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.sock.sendall(bytes(self.escape(data)))
|
||||||
|
except socket.error as ex:
|
||||||
|
self.active = False
|
||||||
|
raise Exception("socket.sendall() error '%d:%s' from %s" % (ex[0], ex[1], self.addrport()))
|
||||||
|
|
||||||
|
def deactivate(self):
|
||||||
|
"""
|
||||||
|
Set the client to disconnect on the next server poll.
|
||||||
|
"""
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
def addrport(self):
|
||||||
|
"""
|
||||||
|
Return the DE's IP address and port number as a string.
|
||||||
|
"""
|
||||||
|
return "%s:%s" % (self.address, self.port)
|
||||||
|
|
||||||
|
def socket_recv(self):
|
||||||
|
"""
|
||||||
|
Called by TelnetServer when recv data is ready.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = self.sock.recv(4096)
|
||||||
|
except socket.error as ex:
|
||||||
|
raise Exception("socket.recv() error '%d:%s' from %s" % (ex[0], ex[1], self.addrport()))
|
||||||
|
|
||||||
|
## Did they close the connection?
|
||||||
|
size = len(data)
|
||||||
|
if size == 0:
|
||||||
|
raise Exception("connection closed by %s" % self.addrport())
|
||||||
|
|
||||||
|
return bytes(self.filter(data))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import msvcrt
|
||||||
|
pipe_name = r'\\.\pipe\VBOX\Linux_Microcore_3.8.2'
|
||||||
|
pipe = open(pipe_name, 'a+b')
|
||||||
|
pipe_proxy = PipeProxy("VBOX", msvcrt.get_osfhandle(pipe.fileno()), '127.0.0.1', 3900)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
#unix_socket.settimeout(0.1)
|
||||||
|
unix_socket.connect("/tmp/pipe_test")
|
||||||
|
except socket.error as err:
|
||||||
|
print("Socket error -> %d:%s" % (err[0], err[1]))
|
||||||
|
sys.exit(False)
|
||||||
|
pipe_proxy = PipeProxy('VBOX', unix_socket, '127.0.0.1', 3900)
|
||||||
|
|
||||||
|
pipe_proxy.setDaemon(True)
|
||||||
|
pipe_proxy.start()
|
||||||
|
pipe.proxy.stop()
|
||||||
|
pipe_proxy.join()
|
@ -25,14 +25,21 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from .pipe_proxy import PipeProxy
|
||||||
from .virtualbox_error import VirtualBoxError
|
from .virtualbox_error import VirtualBoxError
|
||||||
from .adapters.ethernet_adapter import EthernetAdapter
|
from .adapters.ethernet_adapter import EthernetAdapter
|
||||||
from ..attic import find_unused_port
|
from ..attic import find_unused_port
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import msvcrt
|
||||||
|
import win32file
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VirtualBoxVM(object):
|
class VirtualBoxVM(object):
|
||||||
"""
|
"""
|
||||||
VirtualBox VM implementation.
|
VirtualBox VM implementation.
|
||||||
@ -91,6 +98,10 @@ class VirtualBoxVM(object):
|
|||||||
self._console_start_port_range = console_start_port_range
|
self._console_start_port_range = console_start_port_range
|
||||||
self._console_end_port_range = console_end_port_range
|
self._console_end_port_range = console_end_port_range
|
||||||
|
|
||||||
|
# Telnet to pipe mini-server
|
||||||
|
self._serial_pipe_thread = None
|
||||||
|
self._serial_pipe = None
|
||||||
|
|
||||||
# VirtualBox API variables
|
# VirtualBox API variables
|
||||||
self._machine = None
|
self._machine = None
|
||||||
self._session = None
|
self._session = None
|
||||||
@ -464,6 +475,26 @@ class VirtualBoxVM(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# starts the Telnet to pipe thread
|
||||||
|
pipe_name = self._get_pipe_name()
|
||||||
|
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(pipe_name, e))
|
||||||
|
self._serial_pipe_thread = PipeProxy(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._host, self._console)
|
||||||
|
self._serial_pipe_thread.setDaemon(True)
|
||||||
|
self._serial_pipe_thread.start()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
self._serial_pipe.connect(pipe_name)
|
||||||
|
except OSError as e:
|
||||||
|
raise VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
|
||||||
|
self._serial_pipe_thread = PipeProxy(self._vmname, self._serial_pipe, self._host, self._console)
|
||||||
|
self._serial_pipe_thread.setDaemon(True)
|
||||||
|
self._serial_pipe_thread.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
Stops this VirtualBox VM.
|
Stops this VirtualBox VM.
|
||||||
@ -489,6 +520,18 @@ class VirtualBoxVM(object):
|
|||||||
# This can happen, if user manually kills VBox VM.
|
# This can happen, if user manually kills VBox VM.
|
||||||
log.warn("could not stop VM for {}: {}".format(self._vmname, e))
|
log.warn("could not stop VM for {}: {}".format(self._vmname, e))
|
||||||
return
|
return
|
||||||
|
finally:
|
||||||
|
if self._serial_pipe_thread:
|
||||||
|
self._serial_pipe_thread.stop()
|
||||||
|
self._serial_pipe_thread.join()
|
||||||
|
self._serial_pipe_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
|
||||||
|
|
||||||
def suspend(self):
|
def suspend(self):
|
||||||
"""
|
"""
|
||||||
@ -835,19 +878,22 @@ class VirtualBoxVM(object):
|
|||||||
time.sleep(0.75)
|
time.sleep(0.75)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def _set_console_options(self):
|
def _get_pipe_name(self):
|
||||||
|
|
||||||
log.info("setting console options for {}".format(self.vmname))
|
|
||||||
|
|
||||||
self._lock_machine()
|
|
||||||
|
|
||||||
# pick a pipe name
|
|
||||||
p = re.compile('\s+', re.UNICODE)
|
p = re.compile('\s+', re.UNICODE)
|
||||||
pipe_name = p.sub("_", self._vmname)
|
pipe_name = p.sub("_", self._vmname)
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
|
pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
|
||||||
else:
|
else:
|
||||||
pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
|
pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
|
||||||
|
return pipe_name
|
||||||
|
|
||||||
|
def _set_console_options(self):
|
||||||
|
|
||||||
|
log.info("setting console options for {}".format(self.vmname))
|
||||||
|
|
||||||
|
self._lock_machine()
|
||||||
|
pipe_name = self._get_pipe_name()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
serial_port = self._session.machine.getSerialPort(0)
|
serial_port = self._session.machine.getSerialPort(0)
|
||||||
|
@ -348,7 +348,7 @@ class VPCSDevice(object):
|
|||||||
raise VPCSError("VPCS executable version must be >= 0.5b1")
|
raise VPCSError("VPCS executable version must be >= 0.5b1")
|
||||||
else:
|
else:
|
||||||
raise VPCSError("Could not determine the VPCS version for {}".format(self._path))
|
raise VPCSError("Could not determine the VPCS version for {}".format(self._path))
|
||||||
except OSError as e:
|
except (OSError, subprocess.CalledProcessError) as e:
|
||||||
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
|
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user