From 393a312e7ef573c710f4296135a1a68bad2d92a9 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 2 Nov 2014 15:47:44 -0700 Subject: [PATCH] New Telnet server for VirtualBox. --- gns3server/modules/dynamips/__init__.py | 7 +- gns3server/modules/virtualbox/pipe_proxy.py | 476 ------------------ .../modules/virtualbox/telnet_server.py | 444 ++++++++++++++++ .../modules/virtualbox/virtualbox_vm.py | 27 +- 4 files changed, 462 insertions(+), 492 deletions(-) delete mode 100644 gns3server/modules/virtualbox/pipe_proxy.py create mode 100644 gns3server/modules/virtualbox/telnet_server.py diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index a35a71c5..9d428d25 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -347,8 +347,13 @@ class Dynamips(IModule): # for local server new_working_dir = request.pop("working_dir") + try: + self._hypervisor_manager.working_dir = new_working_dir + except DynamipsError as e: + log.error("could not change working directory: {}".format(e)) + return + self._working_dir = new_working_dir - self._hypervisor_manager.working_dir = new_working_dir # apply settings to the hypervisor manager for name, value in request.items(): diff --git a/gns3server/modules/virtualbox/pipe_proxy.py b/gns3server/modules/virtualbox/pipe_proxy.py deleted file mode 100644 index 619a48fd..00000000 --- a/gns3server/modules/virtualbox/pipe_proxy.py +++ /dev/null @@ -1,476 +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 . - -# 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 = data.replace(b"\r\n", b"\n") - - 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() diff --git a/gns3server/modules/virtualbox/telnet_server.py b/gns3server/modules/virtualbox/telnet_server.py new file mode 100644 index 00000000..da324708 --- /dev/null +++ b/gns3server/modules/virtualbox/telnet_server.py @@ -0,0 +1,444 @@ +# -*- 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 . + + +import sys +import time +import threading +import socket +import select + +import logging +log = logging.getLogger(__name__) + +if sys.platform.startswith("win"): + import win32pipe + import win32file + + +class TelnetServer(threading.Thread): + """ + Mini Telnet Server. + + :param vm_name: Virtual machine name + :param pipe_path: path to VM pipe (UNIX socket on Linux/UNIX, Named Pipe on Windows) + :param host: server host + :param port: server port + """ + + def __init__(self, vm_name, pipe_path, host, port): + + self._vm_name = vm_name + self._pipe = pipe_path + self._host = host + self._port = port + self._reader_thread = None + self._use_thread = False + self._write_lock = threading.Lock() + self._clients = {} + self._timeout = 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 ":" in self._host: + # IPv6 address support + self._server_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + else: + self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._server_socket.bind((self._host, self._port)) + self._server_socket.listen(socket.SOMAXCONN) + except OSError as e: + log.critical("unable to create a server socket: {}".format(e)) + return + + threading.Thread.__init__(self) + log.info("Telnet server initialized, waiting for clients on {}:{}".format(self._host, self._port)) + + def run(self): + """ + Thread loop. + """ + + while True: + + recv_list = [self._server_socket.fileno()] + + if not self._use_thread: + recv_list.append(self._pipe.fileno()) + + for client in self._clients.values(): + if client.is_active(): + recv_list.append(client.socket().fileno()) + else: + del self._clients[client.socket().fileno()] + try: + client.socket().shutdown(socket.SHUT_RDWR) + except OSError as e: + log.warn("shutdown: {}".format(e)) + client.socket().close() + break + + try: + rlist, slist, elist = select.select(recv_list, [], [], self._timeout) + except OSError as e: + log.critical("fatal select error: {}".format(e)) + return False + + if not self._alive: + log.info("Telnet server for {} is exiting".format(self._vm_name)) + return True + + for sock_fileno in rlist: + if sock_fileno == self._server_socket.fileno(): + + try: + sock, addr = self._server_socket.accept() + host, port = addr + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + log.info("new client {}:{} has connected".format(host, port)) + except OSError as e: + log.error("could not accept new client: {}".format(e)) + continue + + new_client = TelnetClient(self._vm_name, sock, host, port) + self._clients[sock.fileno()] = new_client + + if self._use_thread and not self._reader_thread: + self._reader_thread = threading.Thread(target=self._reader, daemon=True) + self._reader_thread.start() + + elif not self._use_thread and sock_fileno == self._pipe.fileno(): + + data = self._read_from_pipe() + if not data: + log.warning("pipe has been closed!") + return False + for client in self._clients.values(): + try: + client.send(data) + except OSError as e: + log.debug(e) + client.deactivate() + + elif sock_fileno in self._clients: + try: + data = self._clients[sock_fileno].socket_recv() + + if not data: + continue + + # 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 = data.replace(b"\r\n", b"\n") + + self._write_to_pipe(data) + except Exception as msg: + log.info(msg) + self._clients[sock_fileno].deactivate() + + def _write_to_pipe(self, data): + """ + Writes data to the pipe. + + :param data: data to write + """ + + if sys.platform.startswith('win'): + win32file.WriteFile(self._pipe, data) + else: + self._pipe.sendall(data) + + def _read_from_pipe(self): + """ + Reads data from the pipe. + + :returns: data + """ + + 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): + """ + Loops forever and copy everything from the pipe to the socket. + """ + + log.debug("reader thread has started") + while self._alive: + try: + data = self._read_from_pipe() + if not data and not sys.platform.startswith('win'): + log.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 Exception: + log.debug("pipe has been closed!") + break + log.debug("reader thread exited") + self.stop() + + def stop(self): + """ + Stops the server. + """ + + if self._alive: + self._alive = False + + for client in self._clients.values(): + client.socket().close() + client.deactivate() + +# Mostly from https://code.google.com/p/miniboa/source/browse/trunk/miniboa/telnet.py + +# Telnet Commands +SE = 240 # End of sub-negotiation 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 TelnetClient(object): + """ + Represents a Telnet client connection. + + :param vm_name: VM name + :param sock: socket connection + :param host: IP of the Telnet client + :param port: port of the Telnet client + """ + + def __init__(self, vm_name, sock, host, port): + + self._active = True + self._sock = sock + self._host = host + self._port = port + + sock.send(bytes([IAC, WILL, ECHO, + IAC, WILL, SGA, + IAC, WILL, BINARY, + IAC, DO, BINARY])) + + welcome_msg = "{} console is now available... Press RETURN to get started.\r\n".format(vm_name) + sock.send(welcome_msg.encode('utf-8')) + + def is_active(self): + """ + Returns either the client is active or not. + + :return: boolean + """ + + return self._active + + def socket(self): + """ + Returns the socket for this Telnet client. + + :returns: socket instance. + """ + + return self._sock + + def send(self, data): + """ + Sends data to the remote end. + + :param data: data to send + """ + + try: + self._sock.send(data) + except OSError as e: + self._active = False + raise Exception("Socket send: {}".format(e)) + + def deactivate(self): + """ + Sets the client to disconnect on the next server poll. + """ + + self._active = False + + def socket_recv(self): + """ + Called by Telnet Server when data is ready. + """ + + try: + buf = self._sock.recv(1024, socket.MSG_DONTWAIT) + except BlockingIOError: + return None + except ConnectionResetError: + buf = b'' + + # is the connection closed? + if not buf: + raise Exception("connection closed by {}:{}".format(self._host, self._port)) + + # Process and remove any telnet commands from the buffer + if IAC in buf: + buf = self._IAC_parser(buf) + + return buf + + def _read_block(self, bufsize): + """ + Reads a block for data from the socket. + + :param bufsize: size of the buffer + :returns: data read + """ + buf = self._sock.recv(1024, socket.MSG_WAITALL) + # If we don't get everything we were looking for then the + # client probably disconnected. + if len(buf) < bufsize: + raise Exception("connection closed by {}:{}".format(self._host, self._port)) + return buf + + def _IAC_parser(self, buf): + """ + Processes and removes any Telnet commands from the buffer. + + :param buf: buffer + :returns: buffer minus Telnet commands + """ + + skip_to = 0 + while self._active: + # 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._sock.send(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 + # interrupted 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._sock.send(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 + +if __name__ == '__main__': + + log.setLevel(logging.INFO) + if sys.platform.startswith('win'): + import msvcrt + pipe_name = r'\\.\pipe\VBOX\Linux_Microcore_3.8.2' + pipe = open(pipe_name, 'a+b') + telnet_server = TelnetServer("VBOX", msvcrt.get_osfhandle(pipe.fileno()), "127.0.0.1", 3900) + else: + pipe_name = "/tmp/pipe_test" + try: + unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + unix_socket.connect(pipe_name) + except OSError as e: + print("Could not connect to UNIX socket {}: {}".format(pipe_name, e)) + sys.exit(False) + telnet_server = TelnetServer("VBOX", unix_socket, "127.0.0.1", 3900) + + telnet_server.setDaemon(True) + telnet_server.start() + try: + telnet_server.join() + except KeyboardInterrupt: + telnet_server.stop() + telnet_server.join(timeout=3) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 129ad8b0..71777456 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -32,7 +32,7 @@ import time from .virtualbox_error import VirtualBoxError from .adapters.ethernet_adapter import EthernetAdapter from ..attic import find_unused_port -from .pipe_proxy import PipeProxy +from .telnet_server import TelnetServer if sys.platform.startswith('win'): import msvcrt @@ -96,7 +96,7 @@ class VirtualBoxVM(object): self._console_start_port_range = console_start_port_range self._console_end_port_range = console_end_port_range - self._serial_pipe_thread = None + self._telnet_server_thread = None self._serial_pipe = None # VirtualBox settings @@ -626,6 +626,7 @@ class VirtualBoxVM(object): args = [self._vmname, "--nictype{}".format(adapter_id + 1), vbox_adapter_type] self._execute("modifyvm", args) + self._modify_vm("--nictrace{} off".format(adapter_id + 1)) nio = self._ethernet_adapters[adapter_id].get_nio(0) if nio: log.debug("setting UDP params on adapter {}".format(adapter_id)) @@ -639,8 +640,6 @@ class VirtualBoxVM(object): if nio.capturing: self._modify_vm("--nictrace{} on".format(adapter_id + 1)) self._modify_vm("--nictracefile{} {}".format(adapter_id + 1, nio.pcap_output_file)) - else: - self._modify_vm("--nictrace{} off".format(adapter_id + 1)) else: # shutting down unused adapters... self._modify_vm("--nic{} null".format(adapter_id + 1)) @@ -681,30 +680,28 @@ class VirtualBoxVM(object): 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() + self._telnet_server_thread = TelnetServer(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._host, self._console) + self._telnet_server_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() + self._telnet_server_thread = TelnetServer(self._vmname, self._serial_pipe, self._host, self._console) + self._telnet_server_thread.start() def stop(self): """ Stops this VirtualBox VM. """ - if self._serial_pipe_thread: - self._serial_pipe_thread.stop() - self._serial_pipe_thread.join(1) - if self._serial_pipe_thread.isAlive(): + if self._telnet_server_thread: + self._telnet_server_thread.stop() + self._telnet_server_thread.join(timeout=3) + if self._telnet_server_thread.isAlive(): log.warn("Serial pire thread is still alive!") - self._serial_pipe_thread = None + self._telnet_server_thread = None if self._serial_pipe: if sys.platform.startswith('win'):