/* * Copyright (c) 2010, 2015 ARM Limited * All rights reserved * * The license below extends only to copyright in the software and shall * not be construed as granting a license to any other intellectual * property including but not limited to intellectual property relating * to a hardware implementation of the functionality of the software * licensed hereunder. You may use the software subject to the license * terms below provided that you ensure that this notice is replicated * unmodified and in its entirety in all distributions of the software, * modified or unmodified, in source code or in binary form. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Ali Saidi * William Wang */ /** @file * Implementiation of a VNC server */ #include #include #if defined(__FreeBSD__) #include #else #include #endif #include "base/vnc/vncserver.hh" #include #include #include #include #include #include #include #include "base/atomicio.hh" #include "base/logging.hh" #include "base/output.hh" #include "base/socket.hh" #include "base/trace.hh" #include "debug/VNC.hh" #include "sim/byteswap.hh" #include "sim/core.hh" using namespace std; const PixelConverter VncServer::pixelConverter( 4, // 4 bytes / pixel 16, 8, 0, // R in [23, 16], G in [15, 8], B in [7, 0] 8, 8, 8, // 8 bits / channel LittleEndianByteOrder); /** @file * Implementiation of a VNC server */ /** * Poll event for the listen socket */ VncServer::ListenEvent::ListenEvent(VncServer *vs, int fd, int e) : PollEvent(fd, e), vncserver(vs) { } void VncServer::ListenEvent::process(int revent) { vncserver->accept(); } /** * Poll event for the data socket */ VncServer::DataEvent::DataEvent(VncServer *vs, int fd, int e) : PollEvent(fd, e), vncserver(vs) { } void VncServer::DataEvent::process(int revent) { if (revent & POLLIN) vncserver->data(); else if (revent & POLLNVAL) vncserver->detach(); } /** * VncServer */ VncServer::VncServer(const Params *p) : VncInput(p), listenEvent(NULL), dataEvent(NULL), number(p->number), dataFd(-1), sendUpdate(false), supportsRawEnc(false), supportsResizeEnc(false) { if (p->port) listen(p->port); curState = WaitForProtocolVersion; // We currently only support one pixel format. Extract the pixel // representation from our PixelConverter instance and keep it // around for telling the client and making sure it cooperates pixelFormat.bpp = 8 * pixelConverter.length; pixelFormat.depth = pixelConverter.depth; pixelFormat.bigendian = pixelConverter.byte_order == BigEndianByteOrder; pixelFormat.truecolor = 1; pixelFormat.redmax = pixelConverter.ch_r.mask; pixelFormat.greenmax = pixelConverter.ch_g.mask; pixelFormat.bluemax = pixelConverter.ch_b.mask; pixelFormat.redshift = pixelConverter.ch_r.offset; pixelFormat.greenshift = pixelConverter.ch_g.offset; pixelFormat.blueshift = pixelConverter.ch_b.offset; DPRINTF(VNC, "Vnc server created at port %d\n", p->port); } VncServer::~VncServer() { if (dataFd != -1) ::close(dataFd); if (listenEvent) delete listenEvent; if (dataEvent) delete dataEvent; } //socket creation and vnc client attach void VncServer::listen(int port) { if (ListenSocket::allDisabled()) { warn_once("Sockets disabled, not accepting vnc client connections"); return; } while (!listener.listen(port, true)) { DPRINTF(VNC, "can't bind address vnc server port %d in use PID %d\n", port, getpid()); port++; } ccprintf(cerr, "%s: Listening for connections on port %d\n", name(), port); listenEvent = new ListenEvent(this, listener.getfd(), POLLIN); pollQueue.schedule(listenEvent); } // attach a vnc client void VncServer::accept() { // As a consequence of being called from the PollQueue, we might // have been called from a different thread. Migrate to "our" // thread. EventQueue::ScopedMigration migrate(eventQueue()); if (!listener.islistening()) panic("%s: cannot accept a connection if not listening!", name()); int fd = listener.accept(true); if (fd < 0) { warn("%s: failed to accept VNC connection!", name()); return; } if (dataFd != -1) { char message[] = "vnc server already attached!\n"; atomic_write(fd, message, sizeof(message)); ::close(fd); return; } dataFd = fd; // Send our version number to the client write((uint8_t *)vncVersion(), strlen(vncVersion())); // read the client response dataEvent = new DataEvent(this, dataFd, POLLIN); pollQueue.schedule(dataEvent); inform("VNC client attached\n"); } // data called by data event void VncServer::data() { // We have new data, see if we can handle it DPRINTF(VNC, "Vnc client message recieved\n"); switch (curState) { case WaitForProtocolVersion: checkProtocolVersion(); break; case WaitForSecurityResponse: checkSecurity(); break; case WaitForClientInit: // Don't care about shared, just need to read it out of the socket uint8_t shared; if (!read(&shared)) return; // Send our idea of the frame buffer sendServerInit(); break; case NormalPhase: uint8_t message_type; if (!read(&message_type)) return; switch (message_type) { case ClientSetPixelFormat: setPixelFormat(); break; case ClientSetEncodings: setEncodings(); break; case ClientFrameBufferUpdate: requestFbUpdate(); break; case ClientKeyEvent: recvKeyboardInput(); break; case ClientPointerEvent: recvPointerInput(); break; case ClientCutText: recvCutText(); break; default: warn("Unimplemented message type recv from client: %d\n", message_type); detach(); break; } break; default: panic("Unknown vnc server state\n"); } } // read from socket bool VncServer::read(uint8_t *buf, size_t len) { if (dataFd < 0) panic("vnc not properly attached.\n"); size_t ret; do { ret = ::read(dataFd, buf, len); } while (ret == -1 && errno == EINTR); if (ret != len) { DPRINTF(VNC, "Read failed %d.\n", ret); detach(); return false; } return true; } bool VncServer::read1(uint8_t *buf, size_t len) { return read(buf + 1, len - 1); } template bool VncServer::read(T* val) { return read((uint8_t *)val, sizeof(T)); } // write to socket bool VncServer::write(const uint8_t *buf, size_t len) { if (dataFd < 0) panic("Vnc client not properly attached.\n"); ssize_t ret = atomic_write(dataFd, buf, len); if (ret != len) { DPRINTF(VNC, "Write failed.\n"); detach(); return false; } return true; } template bool VncServer::write(T* val) { return write((uint8_t *)val, sizeof(T)); } bool VncServer::write(const char* str) { return write((uint8_t *)str, strlen(str)); } // detach a vnc client void VncServer::detach() { if (dataFd != -1) { ::close(dataFd); dataFd = -1; } if (!dataEvent || !dataEvent->queued()) return; pollQueue.remove(dataEvent); delete dataEvent; dataEvent = NULL; curState = WaitForProtocolVersion; inform("VNC client detached\n"); DPRINTF(VNC, "detach vnc client %d\n", number); } void VncServer::sendError(const char* error_msg) { uint32_t len = strlen(error_msg); if (!write(&len)) return; write(error_msg); } void VncServer::checkProtocolVersion() { assert(curState == WaitForProtocolVersion); size_t len M5_VAR_USED; char version_string[13]; // Null terminate the message so it's easier to work with version_string[12] = 0; if (!read((uint8_t *)version_string, sizeof(version_string) - 1)) { warn("Failed to read protocol version."); return; } uint32_t major, minor; // Figure out the major/minor numbers if (sscanf(version_string, "RFB %03d.%03d\n", &major, &minor) != 2) { warn(" Malformed protocol version %s\n", version_string); sendError("Malformed protocol version\n"); detach(); return; } DPRINTF(VNC, "Client request protocol version %d.%d\n", major, minor); // If it's not 3.X we don't support it if (major != 3 || minor < 2) { warn("Unsupported VNC client version... disconnecting\n"); uint8_t err = AuthInvalid; write(&err); detach(); return; } // Auth is different based on version number if (minor < 7) { uint32_t sec_type = htobe((uint32_t)AuthNone); if (!write(&sec_type)) return; } else { uint8_t sec_cnt = 1; uint8_t sec_type = htobe((uint8_t)AuthNone); if (!write(&sec_cnt) || !write(&sec_type)) return; } // Wait for client to respond curState = WaitForSecurityResponse; } void VncServer::checkSecurity() { assert(curState == WaitForSecurityResponse); uint8_t security_type; if (!read(&security_type)) return; if (security_type != AuthNone) { warn("Unknown VNC security type\n"); sendError("Unknown security type\n"); } DPRINTF(VNC, "Sending security auth OK\n"); uint32_t success = htobe(VncOK); if (!write(&success)) return; curState = WaitForClientInit; } void VncServer::sendServerInit() { ServerInitMsg msg; DPRINTF(VNC, "Sending server init message to client\n"); msg.fbWidth = htobe(videoWidth()); msg.fbHeight = htobe(videoHeight()); msg.px.bpp = htobe(pixelFormat.bpp); msg.px.depth = htobe(pixelFormat.depth); msg.px.bigendian = htobe(pixelFormat.bigendian); msg.px.truecolor = htobe(pixelFormat.truecolor); msg.px.redmax = htobe(pixelFormat.redmax); msg.px.greenmax = htobe(pixelFormat.greenmax); msg.px.bluemax = htobe(pixelFormat.bluemax); msg.px.redshift = htobe(pixelFormat.redshift); msg.px.greenshift = htobe(pixelFormat.greenshift); msg.px.blueshift = htobe(pixelFormat.blueshift); memset(msg.px.padding, 0, 3); msg.namelen = 2; msg.namelen = htobe(msg.namelen); memcpy(msg.name, "M5", 2); if (!write(&msg)) return; curState = NormalPhase; } void VncServer::setPixelFormat() { DPRINTF(VNC, "Received pixel format from client message\n"); PixelFormatMessage pfm; if (!read1((uint8_t *)&pfm, sizeof(PixelFormatMessage))) return; DPRINTF(VNC, " -- bpp = %d; depth = %d; be = %d\n", pfm.px.bpp, pfm.px.depth, pfm.px.bigendian); DPRINTF(VNC, " -- true color = %d red,green,blue max = %d,%d,%d\n", pfm.px.truecolor, betoh(pfm.px.redmax), betoh(pfm.px.greenmax), betoh(pfm.px.bluemax)); DPRINTF(VNC, " -- red,green,blue shift = %d,%d,%d\n", pfm.px.redshift, pfm.px.greenshift, pfm.px.blueshift); if (betoh(pfm.px.bpp) != pixelFormat.bpp || betoh(pfm.px.depth) != pixelFormat.depth || betoh(pfm.px.bigendian) != pixelFormat.bigendian || betoh(pfm.px.truecolor) != pixelFormat.truecolor || betoh(pfm.px.redmax) != pixelFormat.redmax || betoh(pfm.px.greenmax) != pixelFormat.greenmax || betoh(pfm.px.bluemax) != pixelFormat.bluemax || betoh(pfm.px.redshift) != pixelFormat.redshift || betoh(pfm.px.greenshift) != pixelFormat.greenshift || betoh(pfm.px.blueshift) != pixelFormat.blueshift) { warn("VNC client doesn't support true color raw encoding\n"); detach(); } } void VncServer::setEncodings() { DPRINTF(VNC, "Received supported encodings from client\n"); PixelEncodingsMessage pem; if (!read1((uint8_t *)&pem, sizeof(PixelEncodingsMessage))) return; pem.num_encodings = betoh(pem.num_encodings); DPRINTF(VNC, " -- %d encoding present\n", pem.num_encodings); supportsRawEnc = supportsResizeEnc = false; for (int x = 0; x < pem.num_encodings; x++) { int32_t encoding; if (!read(&encoding)) return; DPRINTF(VNC, " -- supports %d\n", betoh(encoding)); switch (betoh(encoding)) { case EncodingRaw: supportsRawEnc = true; break; case EncodingDesktopSize: supportsResizeEnc = true; break; } } if (!supportsRawEnc) { warn("VNC clients must always support raw encoding\n"); detach(); } } void VncServer::requestFbUpdate() { DPRINTF(VNC, "Received frame buffer update request from client\n"); FrameBufferUpdateReq fbr; if (!read1((uint8_t *)&fbr, sizeof(FrameBufferUpdateReq))) return; fbr.x = betoh(fbr.x); fbr.y = betoh(fbr.y); fbr.width = betoh(fbr.width); fbr.height = betoh(fbr.height); DPRINTF(VNC, " -- x = %d y = %d w = %d h = %d\n", fbr.x, fbr.y, fbr.width, fbr.height); sendFrameBufferUpdate(); } void VncServer::recvKeyboardInput() { DPRINTF(VNC, "Received keyboard input from client\n"); KeyEventMessage kem; if (!read1((uint8_t *)&kem, sizeof(KeyEventMessage))) return; kem.key = betoh(kem.key); DPRINTF(VNC, " -- received key code %d (%s)\n", kem.key, kem.down_flag ? "down" : "up"); if (keyboard) keyboard->keyPress(kem.key, kem.down_flag); } void VncServer::recvPointerInput() { DPRINTF(VNC, "Received pointer input from client\n"); PointerEventMessage pem; if (!read1((uint8_t *)&pem, sizeof(PointerEventMessage))) return; pem.x = betoh(pem.x); pem.y = betoh(pem.y); DPRINTF(VNC, " -- pointer at x = %d y = %d buttons = %#x\n", pem.x, pem.y, pem.button_mask); if (mouse) mouse->mouseAt(pem.x, pem.y, pem.button_mask); } void VncServer::recvCutText() { DPRINTF(VNC, "Received client copy buffer message\n"); ClientCutTextMessage cct; if (!read1((uint8_t *)&cct, sizeof(ClientCutTextMessage))) return; char str[1025]; size_t data_len = betoh(cct.length); DPRINTF(VNC, "String length %d\n", data_len); while (data_len > 0) { size_t bytes_to_read = data_len > 1024 ? 1024 : data_len; if (!read((uint8_t *)&str, bytes_to_read)) return; str[bytes_to_read] = 0; data_len -= bytes_to_read; DPRINTF(VNC, "Buffer: %s\n", str); } } void VncServer::sendFrameBufferUpdate() { if (dataFd <= 0 || curState != NormalPhase || !sendUpdate) { DPRINTF(VNC, "NOT sending framebuffer update\n"); return; } // The client will request data constantly, unless we throttle it sendUpdate = false; DPRINTF(VNC, "Sending framebuffer update\n"); FrameBufferUpdate fbu; FrameBufferRect fbr; fbu.type = ServerFrameBufferUpdate; fbu.num_rects = 1; fbr.x = 0; fbr.y = 0; fbr.width = videoWidth(); fbr.height = videoHeight(); fbr.encoding = EncodingRaw; // fix up endian fbu.num_rects = htobe(fbu.num_rects); fbr.x = htobe(fbr.x); fbr.y = htobe(fbr.y); fbr.width = htobe(fbr.width); fbr.height = htobe(fbr.height); fbr.encoding = htobe(fbr.encoding); // send headers to client if (!write(&fbu) || !write(&fbr)) return; assert(fb); std::vector line_buffer(pixelConverter.length * fb->width()); for (int y = 0; y < fb->height(); ++y) { // Convert and send a line at a time uint8_t *raw_pixel(line_buffer.data()); for (unsigned x = 0; x < fb->width(); ++x) { pixelConverter.fromPixel(raw_pixel, fb->pixel(x, y)); raw_pixel += pixelConverter.length; } if (!write(line_buffer.data(), line_buffer.size())) return; } } void VncServer::sendFrameBufferResized() { assert(fb && dataFd > 0 && curState == NormalPhase); DPRINTF(VNC, "Sending framebuffer resize\n"); FrameBufferUpdate fbu; FrameBufferRect fbr; fbu.type = ServerFrameBufferUpdate; fbu.num_rects = 1; fbr.x = 0; fbr.y = 0; fbr.width = videoWidth(); fbr.height = videoHeight(); fbr.encoding = EncodingDesktopSize; // fix up endian fbu.num_rects = htobe(fbu.num_rects); fbr.x = htobe(fbr.x); fbr.y = htobe(fbr.y); fbr.width = htobe(fbr.width); fbr.height = htobe(fbr.height); fbr.encoding = htobe(fbr.encoding); // send headers to client if (!write(&fbu)) return; write(&fbr); // No actual data is sent in this message } void VncServer::setDirty() { VncInput::setDirty(); sendUpdate = true; sendFrameBufferUpdate(); } void VncServer::frameBufferResized() { if (dataFd > 0 && curState == NormalPhase) { if (supportsResizeEnc) sendFrameBufferResized(); else // The frame buffer changed size and we can't update the client detach(); } } // create the VNC server object VncServer * VncServerParams::create() { return new VncServer(this); }