| /* | |
| * 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 <sys/ioctl.h> | |
| #include <sys/stat.h> | |
| #if defined(__FreeBSD__) | |
| #include <termios.h> | |
| #else | |
| #include <sys/termios.h> | |
| #endif | |
| #include "base/vnc/vncserver.hh" | |
| #include <fcntl.h> | |
| #include <poll.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <cerrno> | |
| #include <cstddef> | |
| #include <cstdio> | |
| #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<typename T> | |
| 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<typename T> | |
| 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<uint8_t> 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); | |
| } | |