vmware-vspc/vspc/server.py
Darius Davis 6de3cfdc08 vSPC: Handle VMOTION-ABORT.
VMOTION-ABORT is sent by the source host.  It does not require any
specific response or action, but we currently fail to recognize it,
which causes the _source_ vSPC connection to be terminated, and that
seems undesirable as the source should be fine -- it is the
_destination_ which has failed and is expected to disconnect.

This change handles it with a debug log message, but the side effect
of handling it is that the connection is no longer terminated.

Also fix the direction indicator for the debug message for
VMOTION-PEER-OK.

Change-Id: Id09ed363d6713c023323235a60d4c678c65079f1
2023-06-22 01:40:46 -07:00

270 lines
9.6 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2017 VMware Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import asyncio
import functools
import os
import ssl
import sys
from oslo_config import cfg
from oslo_log import log as logging
from vspc import async_telnet
from vspc.async_telnet import IAC, SB, SE, DO, DONT, WILL, WONT
opts = [
cfg.StrOpt('host',
default='0.0.0.0',
help='Host on which to listen for incoming requests'),
cfg.IntOpt('port',
default=13370,
help='Port on which to listen for incoming requests'),
cfg.StrOpt('cert', help='SSL certificate file'),
cfg.StrOpt('key', help='SSL key file (if separate from cert)'),
cfg.StrOpt('uri', help='VSPC URI'),
cfg.StrOpt('serial_log_dir', help='The directory where serial logs are '
'saved'),
]
CONF = cfg.CONF
CONF.register_opts(opts)
LOG = logging.getLogger(__name__)
BINARY = bytes([0]) # 8-bit data path
SGA = bytes([3]) # suppress go ahead
VMWARE_EXT = bytes([232])
KNOWN_SUBOPTIONS_1 = bytes([0])
KNOWN_SUBOPTIONS_2 = bytes([1])
VMOTION_BEGIN = bytes([40])
VMOTION_GOAHEAD = bytes([41])
VMOTION_NOTNOW = bytes([43])
VMOTION_PEER = bytes([44])
VMOTION_PEER_OK = bytes([45])
VMOTION_COMPLETE = bytes([46])
VMOTION_ABORT = bytes([48])
VM_VC_UUID = bytes([80])
GET_VM_VC_UUID = bytes([81])
VM_NAME = bytes([82])
GET_VM_NAME = bytes([83])
DO_PROXY = bytes([70])
WILL_PROXY = bytes([71])
WONT_PROXY = bytes([73])
SUPPORTED_OPTS = (KNOWN_SUBOPTIONS_1 + KNOWN_SUBOPTIONS_2 + VMOTION_BEGIN +
VMOTION_GOAHEAD + VMOTION_NOTNOW + VMOTION_PEER +
VMOTION_PEER_OK + VMOTION_COMPLETE + VMOTION_ABORT +
VM_VC_UUID + GET_VM_VC_UUID + VM_NAME + GET_VM_NAME +
DO_PROXY + WILL_PROXY + WONT_PROXY)
class VspcServer(object):
def __init__(self):
self.sock_to_uuid = dict()
@asyncio.coroutine
def handle_known_suboptions(self, writer, data):
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
LOG.debug("<< %s KNOWN-SUBOPTIONS-1 %s", peer, data)
LOG.debug(">> %s KNOWN-SUBOPTIONS-2 %s", peer, SUPPORTED_OPTS)
writer.write(IAC + SB + VMWARE_EXT + KNOWN_SUBOPTIONS_2 +
SUPPORTED_OPTS + IAC + SE)
LOG.debug(">> %s GET-VM-VC-UUID", peer)
writer.write(IAC + SB + VMWARE_EXT + GET_VM_VC_UUID + IAC + SE)
yield from writer.drain()
@asyncio.coroutine
def handle_do_proxy(self, writer, data):
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
dir, uri = data[0], data[1:].decode('ascii')
LOG.debug("<< %s DO-PROXY %c %s", peer, dir, uri)
if chr(dir) != 'S' or uri != CONF.uri:
LOG.debug(">> %s WONT-PROXY", peer)
writer.write(IAC + SB + VMWARE_EXT + WONT_PROXY + IAC + SE)
yield from writer.drain()
writer.close()
else:
LOG.debug(">> %s WILL-PROXY", peer)
writer.write(IAC + SB + VMWARE_EXT + WILL_PROXY + IAC + SE)
yield from writer.drain()
def handle_vm_vc_uuid(self, socket, data):
peer = socket.getpeername()
uuid = data.decode('ascii')
LOG.debug("<< %s VM-VC-UUID %s", peer, uuid)
uuid = uuid.replace(' ', '')
uuid = uuid.replace('-', '')
self.sock_to_uuid[socket] = uuid
@asyncio.coroutine
def handle_vmotion_begin(self, writer, data):
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
LOG.debug("<< %s VMOTION-BEGIN %s", peer, data)
secret = os.urandom(4)
LOG.debug(">> %s VMOTION-GOAHEAD %s %s", peer, data, secret)
writer.write(IAC + SB + VMWARE_EXT + VMOTION_GOAHEAD +
async_telnet.AsyncTelnet.escape(data + secret) + IAC + SE)
yield from writer.drain()
@asyncio.coroutine
def handle_vmotion_peer(self, writer, data):
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
LOG.debug("<< %s VMOTION-PEER %s", peer, data)
LOG.debug(">> %s VMOTION-PEER-OK %s", peer, data)
writer.write(IAC + SB + VMWARE_EXT + VMOTION_PEER_OK +
async_telnet.AsyncTelnet.escape(data) + IAC + SE)
yield from writer.drain()
def handle_vmotion_complete(self, socket, data):
peer = socket.getpeername()
LOG.debug("<< %s VMOTION-COMPLETE %s", peer, data)
def handle_vmotion_abort(self, socket, data):
peer = socket.getpeername()
LOG.debug("<< %s VMOTION-ABORT %s", peer, data)
@asyncio.coroutine
def handle_do(self, writer, opt):
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
LOG.debug("<< %s DO %s", peer, opt)
if opt in (BINARY, SGA):
LOG.debug(">> %s WILL", peer)
writer.write(IAC + WILL + opt)
yield from writer.drain()
else:
LOG.debug(">> %s WONT", peer)
writer.write(IAC + WONT + opt)
yield from writer.drain()
@asyncio.coroutine
def handle_will(self, writer, opt):
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
LOG.debug("<< %s WILL %s", peer, opt)
if opt in (BINARY, SGA, VMWARE_EXT):
LOG.debug(">> %s DO", peer)
writer.write(IAC + DO + opt)
yield from writer.drain()
else:
LOG.debug(">> %s DONT", peer)
writer.write(IAC + DONT + opt)
yield from writer.drain()
@asyncio.coroutine
def option_handler(self, cmd, opt, writer, data=None):
socket = writer.get_extra_info('socket')
if cmd == SE and data[0:1] == VMWARE_EXT:
vmw_cmd = data[1:2]
if vmw_cmd == KNOWN_SUBOPTIONS_1:
yield from self.handle_known_suboptions(writer, data[2:])
elif vmw_cmd == DO_PROXY:
yield from self.handle_do_proxy(writer, data[2:])
elif vmw_cmd == VM_VC_UUID:
self.handle_vm_vc_uuid(socket, data[2:])
elif vmw_cmd == VMOTION_BEGIN:
yield from self.handle_vmotion_begin(writer, data[2:])
elif vmw_cmd == VMOTION_PEER:
yield from self.handle_vmotion_peer(writer, data[2:])
elif vmw_cmd == VMOTION_COMPLETE:
self.handle_vmotion_complete(socket, data[2:])
elif vmw_cmd == VMOTION_ABORT:
self.handle_vmotion_abort(socket, data[2:])
else:
LOG.error("Unknown VMware cmd: %s %s", vmw_cmd, data[2:])
writer.close()
elif cmd == DO:
yield from self.handle_do(writer, opt)
elif cmd == WILL:
yield from self.handle_will(writer, opt)
def save_to_log(self, uuid, data):
fpath = os.path.join(CONF.serial_log_dir, uuid)
with open(fpath, 'ab') as f:
f.write(data)
@asyncio.coroutine
def handle_telnet(self, reader, writer):
opt_handler = functools.partial(self.option_handler, writer=writer)
telnet = async_telnet.AsyncTelnet(reader, opt_handler)
socket = writer.get_extra_info('socket')
peer = socket.getpeername()
LOG.info("%s connected", peer)
data = yield from telnet.read_some()
uuid = self.sock_to_uuid.get(socket)
if uuid is None:
LOG.error("%s didn't present UUID", peer)
writer.close()
return
try:
while data:
self.save_to_log(uuid, data)
data = yield from telnet.read_some()
finally:
self.sock_to_uuid.pop(socket, None)
LOG.info("%s disconnected", peer)
writer.close()
def start(self):
loop = asyncio.get_event_loop()
ssl_context = None
if CONF.cert:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ssl_context.load_cert_chain(certfile=CONF.cert, keyfile=CONF.key)
coro = asyncio.start_server(self.handle_telnet,
CONF.host,
CONF.port,
ssl=ssl_context,
loop=loop)
server = loop.run_until_complete(coro)
# Serve requests until Ctrl+C is pressed
LOG.info("Serving on %s", server.sockets[0].getsockname())
LOG.info("Log directory: %s", CONF.serial_log_dir)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
def main():
logging.register_options(CONF)
CONF(sys.argv[1:], prog='vspc')
logging.setup(CONF, "vspc")
if not CONF.serial_log_dir:
LOG.error("serial_log_dir is not specified")
sys.exit(1)
if not os.path.exists(CONF.serial_log_dir):
LOG.info("Creating log directory: %s", CONF.serial_log_dir)
os.makedirs(CONF.serial_log_dir)
srv = VspcServer()
srv.start()
if __name__ == '__main__':
main()