├── .dir-locals.el ├── .travis.yml ├── CMakeLists.txt ├── README.org ├── aten-proxy.nix ├── cmake └── FindLibev.cmake ├── connection.cc ├── connection.h ├── default.nix ├── flake.lock ├── flake.nix ├── keymap.cc ├── keymap.h ├── main.cc └── unique_fd.h /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((indent-tabs-mode . t)))) 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - language: nix 4 | - language: c 5 | os: linux 6 | dist: xenial 7 | addons: 8 | apt: 9 | packages: 10 | - cmake 11 | - ninja-build 12 | - libev-dev 13 | - libvncserver-dev 14 | script: 15 | - mkdir -p build && cd build 16 | - cmake .. -G Ninja && ninja 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(aten-proxy) 3 | 4 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 5 | 6 | set(CMAKE_CXX_STANDARD 11) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") 8 | 9 | find_package(PkgConfig) 10 | find_package(Libev REQUIRED) 11 | 12 | set(THREADS_PREFER_PTHREAD_FLAG ON) 13 | find_package(Threads REQUIRED) 14 | 15 | pkg_check_modules(libvncserver REQUIRED IMPORTED_TARGET libvncserver) 16 | 17 | add_executable(aten-proxy 18 | main.cc 19 | keymap.cc 20 | connection.cc 21 | ) 22 | 23 | target_link_libraries(aten-proxy 24 | PkgConfig::libvncserver 25 | Libev::Libev 26 | Threads::Threads 27 | ) 28 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: aten-proxy 2 | #+TODO: TODO INPROGRESS | DONE 3 | 4 | * Introduction 5 | 6 | aten-proxy is currently *in development* and *not yet suitable for 7 | end-users*. 8 | 9 | [[http://www.aten-usa.com/data/solution/IPMI/3-in-1.html][ATEN iKVM]] products are often found in remote management interfaces 10 | providing the keyboard, video and mouse functionality. The official 11 | client relies on both Java and native libraries to connect. aten-proxy 12 | is a [[http://libvncserver.sourceforge.net][LibVNCServer]] based translator that allows the use of any VNC 13 | client. Since the protocol used by the ATEN iKVM is a raw bitmap 14 | encoding, aten-proxy might also be used as an accelerator over longer 15 | distance using LibVNCServer's various compression features. 16 | 17 | aten-proxy is intended as a replacement for [[http://github.com/thefloweringash/chicken-aten-ikvm][chicken-aten-ikvm]], a 18 | proof-of-concept fork of the [[http://sourceforge.net/projects/chicken/][Chicken]] VNC viewer modified for use with 19 | ATEN iKVM products. 20 | 21 | * Building 22 | 23 | aten-proxy requires LibVNCServer, libev, pkg-config, and CMake. Once 24 | they are installed, run CMake. 25 | 26 | #+BEGIN_SRC sh 27 | mkdir build && cd build 28 | cmake .. && make 29 | #+END_SRC 30 | 31 | This repository can also be built with [[https://nixos.org/nixpkgs/][Nix]]. 32 | 33 | * License 34 | 35 | aten-proxy is licensed under the terms of the GNU General Public 36 | License (GPL), in order to comply with the terms of LibVNCServer. If 37 | the dependency on LibVNCServer is removed, the author is open to other 38 | licensing options. 39 | 40 | * Tasks 41 | ** TODO Disconnection and graceful termination 42 | The keyboard interface seems to no longer pass keys after 43 | forcefully terminating aten-proxy. 44 | 45 | ** TODO Error handling 46 | Most transient errors result in reconnection, and permanent errors 47 | result in =abort()=. 48 | 49 | ** TODO Mouse support 50 | 51 | ** TODO Lazy initialisation 52 | The backend is currently connected immediately, and remains 53 | connected at all times. The backend should only be connected while 54 | a client is present. 55 | 56 | ** TODO Authentication methods 57 | Currently only LibVNCServer's built-in authentication methods are 58 | available, and the upstream authentication is hardcoded. 59 | 60 | ** TODO [#C] Remote media 61 | No work has yet been done on the remote media protocol. 62 | 63 | ** TODO Keymap support 64 | The ATEN iKVM expects HID codes for key events, while the VNC 65 | protocol works with X11 keysyms. This presently works by assuming 66 | everything is using the same keyboard layout as the author. 67 | 68 | For example, pressing "Shift" and "2" on my keyboard results in 69 | "@". This is sent over VNC as =XK_Shift_L= and =XK_at=. aten-proxy 70 | is able to pass the shift key directly (HID usage =0xE1=), but has 71 | to infer how =XK_at= should be handled. [[file:keymap.cc][keymap.cc]] uses a hardcoded 72 | table and guesses that with the current modifier state, it should 73 | send the "2" key (HID usage =0x1f=) to enter "@". 74 | 75 | ** TODO Testing 76 | aten-proxy has only been tested with a Supermicro X9SCM-iiF. 77 | 78 | ** TODO Performance 79 | The code is written to be simple to understand and safe at the 80 | expense of runtime performance. The following areas should be 81 | reviewed: 82 | 83 | * [ ] Event object lifecycle 84 | * [ ] Pixel blitting ([[file:main.cc::copyPixels][copyPixels in main.cc]]) 85 | 86 | * Authors 87 | * Andrew Childs 88 | -------------------------------------------------------------------------------- /aten-proxy.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, libev, libvncserver, cmake, ninja, pkgconfig }: 2 | 3 | stdenv.mkDerivation { 4 | name = "aten-proxy"; 5 | version = "0.0.1"; 6 | 7 | src = lib.cleanSource ./.; 8 | 9 | nativeBuildInputs = [ cmake pkgconfig ninja ]; 10 | buildInputs = [ libev libvncserver ]; 11 | 12 | installPhase = '' 13 | mkdir -p $out/bin 14 | cp aten-proxy $out/bin 15 | ''; 16 | } 17 | -------------------------------------------------------------------------------- /cmake/FindLibev.cmake: -------------------------------------------------------------------------------- 1 | find_path(LIBEV_INCLUDE_DIR NAMES ev.h) 2 | find_library(LIBEV_LIBRARY NAMES ev) 3 | 4 | include(FindPackageHandleStandardArgs) 5 | find_package_handle_standard_args( 6 | Libev 7 | REQUIRED_VARS LIBEV_LIBRARY LIBEV_INCLUDE_DIR) 8 | 9 | if(LIBEV_FOUND) 10 | add_library(Libev::Libev SHARED IMPORTED) 11 | set_target_properties(Libev::Libev 12 | PROPERTIES 13 | INTERFACE_INCLUDE_DIRECTORIES ${LIBEV_INCLUDE_DIR} 14 | IMPORTED_LOCATION ${LIBEV_LIBRARY} 15 | ) 16 | endif() 17 | -------------------------------------------------------------------------------- /connection.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "connection.h" 11 | 12 | namespace NetworkUtils { 13 | 14 | std::unique_ptr 15 | getaddrinfo(const char *host, const char *service, 16 | const addrinfo& hints) 17 | { 18 | addrinfo *addressinfo; 19 | int err = ::getaddrinfo(host, service, &hints, &addressinfo); 20 | if (err) { 21 | throw std::runtime_error("getaddrinfo failed"); 22 | } 23 | return std::unique_ptr{addressinfo}; 24 | } 25 | 26 | const char *showAddress(const struct sockaddr *s) { 27 | static char buffer[INET6_ADDRSTRLEN]; 28 | switch (s->sa_family) { 29 | case AF_INET: 30 | inet_ntop(s->sa_family, 31 | &((struct sockaddr_in *)s)->sin_addr, buffer, sizeof(buffer)); 32 | return buffer; 33 | case AF_INET6: 34 | inet_ntop(s->sa_family, 35 | &((struct sockaddr_in6 *)s)->sin6_addr, buffer, sizeof(buffer)); 36 | return buffer; 37 | default: 38 | return nullptr; 39 | } 40 | } 41 | 42 | unique_fd connectSocket(const char *host, const char *service) { 43 | addrinfo hints; 44 | memset(&hints, 0, sizeof(hints)); 45 | hints.ai_family = PF_UNSPEC; 46 | hints.ai_socktype = SOCK_STREAM; 47 | 48 | auto info = getaddrinfo(host, service, hints); 49 | for (addrinfo *x = info.get(); x; x = x->ai_next) { 50 | unique_fd s { socket(x->ai_family, x->ai_socktype, x->ai_protocol) }; 51 | if (s < 0) { 52 | perror("socket"); 53 | continue; 54 | } 55 | 56 | int err = connect(s, x->ai_addr, x->ai_addrlen); 57 | if (err) { 58 | warn("connect to %s", showAddress(x->ai_addr)); 59 | continue; 60 | } 61 | 62 | return s; 63 | } 64 | throw std::runtime_error("connection failed"); 65 | } 66 | 67 | } 68 | 69 | void Connection::writeBytes(const char *buf, size_t len) { 70 | size_t off = 0; 71 | while (off < len) { 72 | ssize_t n = send(mSocket, buf + off, len - off, 0); 73 | if (n < 0) { 74 | if (errno != EINTR) { 75 | perror("send"); 76 | abort(); 77 | } 78 | } 79 | else { 80 | off += n; 81 | } 82 | } 83 | } 84 | 85 | char* Connection::readBytes(size_t len) { 86 | if (mTempBufferLen < len) { 87 | while (mTempBufferLen < len) 88 | mTempBufferLen <<= 1; 89 | mTempBuffer = reinterpret_cast( 90 | realloc(mTempBuffer, mTempBufferLen)); 91 | if (!mTempBuffer) 92 | abort(); 93 | } 94 | return readBytes(mTempBuffer, len); 95 | } 96 | 97 | char *Connection::readBytes(char *buf, size_t len) { 98 | size_t off = 0; 99 | 100 | // take from buffer 101 | if (mDataLen) { 102 | size_t take = std::min(mDataLen, len); 103 | memcpy(buf, mCursor, take); 104 | 105 | mDataLen -= take; 106 | mCursor += take; 107 | off += take; 108 | } 109 | 110 | // take from socket, ignoring buffer if > buffer size 111 | while (len - off > mRecvBufferLen) { 112 | ssize_t n = recv(mSocket, buf + off, len - off, 0); 113 | if (n < 0) { 114 | if (errno != EINTR) { 115 | perror("recv"); 116 | throw std::runtime_error("read failed"); 117 | } 118 | } 119 | else if (n == 0) { 120 | // we got shut down 121 | throw std::runtime_error("remote host shut us down"); 122 | } 123 | else { 124 | off += n; 125 | } 126 | } 127 | 128 | // take from socket to buffer, keeping leftovers in the buffer 129 | if (len - off > 0) { 130 | // buffer is empty, reset and fill buffer to at least len - 131 | // off 132 | mCursor = mRecvBuffer; 133 | mDataLen = 0; 134 | while (len - off > mDataLen) { 135 | ssize_t n = recv(mSocket, mRecvBuffer + mDataLen, mRecvBufferLen - mDataLen, 0); 136 | if (n < 0) { 137 | if (errno != EINTR) { 138 | perror("recv"); 139 | throw std::runtime_error("read failed"); 140 | } 141 | } 142 | else if (n == 0) { 143 | // we got shut down 144 | throw std::runtime_error("remote host shut us down"); 145 | } 146 | else { 147 | mDataLen += n; 148 | } 149 | } 150 | 151 | size_t take = len - off; 152 | memcpy(buf + off, mCursor, take); 153 | mDataLen -= take; 154 | mCursor += take; 155 | off += take; 156 | } 157 | 158 | return buf; 159 | } 160 | -------------------------------------------------------------------------------- /connection.h: -------------------------------------------------------------------------------- 1 | // -*- c++ -*- 2 | #ifndef _CONNECTION_H_ 3 | #define _CONNECTION_H_ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "unique_fd.h" 17 | 18 | namespace NetworkUtils { 19 | 20 | struct AddrinfoDeleter { 21 | void operator()(addrinfo *x) { 22 | freeaddrinfo(x); 23 | } 24 | }; 25 | 26 | std::unique_ptr 27 | getaddrinfo(const char *host, const char *service, const addrinfo& hints); 28 | 29 | const char *showAddress(const struct sockaddr *s); 30 | 31 | unique_fd connectSocket(const char *host, const char *service); 32 | 33 | } 34 | 35 | class Connection { 36 | unique_fd mSocket; 37 | 38 | // convenience buffer 39 | char *mTempBuffer; 40 | size_t mTempBufferLen; 41 | 42 | char *mRecvBuffer; 43 | size_t mRecvBufferLen; 44 | 45 | char *mCursor; 46 | size_t mDataLen; 47 | 48 | public: 49 | Connection(const char *host, const char *service) 50 | : mSocket(NetworkUtils::connectSocket(host, service)) 51 | { 52 | mTempBufferLen = 1024; 53 | mTempBuffer = (char*) malloc(mTempBufferLen); 54 | if (!mTempBuffer) 55 | abort(); 56 | 57 | mRecvBufferLen = 1024; 58 | mRecvBuffer = (char*) malloc(mRecvBufferLen); 59 | if (!mRecvBuffer) 60 | abort(); 61 | 62 | mCursor = mRecvBuffer; 63 | mDataLen = 0; 64 | } 65 | ~Connection() { 66 | // TODO FIXME: better to be unique_ptr? 67 | free(mTempBuffer); 68 | free(mRecvBuffer); 69 | } 70 | 71 | void writeBytes(const char *buf, size_t len); 72 | void writeString(const char *buf) { 73 | writeBytes(buf, strlen(buf)); 74 | } 75 | char* readBytes(size_t len); 76 | char* readBytes(char *buf, size_t len); 77 | 78 | template 79 | void writeRaw(T x) { 80 | writeBytes((char*) &x, sizeof(x)); 81 | } 82 | template 83 | T readRaw() { 84 | T x; 85 | readBytes((char*) &x, sizeof(x)); 86 | return x; 87 | } 88 | }; 89 | 90 | #endif /* _CONNECTION_H_ */ 91 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import {}).callPackage ./aten-proxy.nix {} 2 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1637033054, 6 | "narHash": "sha256-9pTe6MCx6m2UswFrLOww0rIRe8c7Z2tlp7anhkdRRGk=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "c8920ed7ceda97a7613f020af10ed5477bf5b942", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "type": "indirect" 15 | } 16 | }, 17 | "root": { 18 | "inputs": { 19 | "nixpkgs": "nixpkgs" 20 | } 21 | } 22 | }, 23 | "root": "root", 24 | "version": 7 25 | } 26 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A very basic flake"; 3 | 4 | outputs = { self, nixpkgs }: { 5 | 6 | packages.aarch64-darwin.aten-proxy = nixpkgs.legacyPackages.aarch64-darwin.callPackage ./aten-proxy.nix {}; 7 | 8 | defaultPackage.aarch64-darwin = self.packages.aarch64-darwin.aten-proxy; 9 | 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /keymap.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "keymap.h" 5 | 6 | static struct keymap_t { 7 | rfbKeySym keysym; 8 | uint8_t hid; 9 | } keymap[] = { 10 | { XK_a, 0x04 }, 11 | { XK_b, 0x05 }, 12 | { XK_c, 0x06 }, 13 | { XK_d, 0x07 }, 14 | { XK_e, 0x08 }, 15 | { XK_f, 0x09 }, 16 | { XK_g, 0x0a }, 17 | { XK_h, 0x0b }, 18 | { XK_i, 0x0c }, 19 | { XK_j, 0x0d }, 20 | { XK_k, 0x0e }, 21 | { XK_l, 0x0f }, 22 | { XK_m, 0x10 }, 23 | { XK_n, 0x11 }, 24 | { XK_o, 0x12 }, 25 | { XK_p, 0x13 }, 26 | { XK_q, 0x14 }, 27 | { XK_r, 0x15 }, 28 | { XK_s, 0x16 }, 29 | { XK_t, 0x17 }, 30 | { XK_u, 0x18 }, 31 | { XK_v, 0x19 }, 32 | { XK_w, 0x1a }, 33 | { XK_x, 0x1b }, 34 | { XK_y, 0x1c }, 35 | { XK_z, 0x1d }, 36 | { XK_1, 0x1e }, 37 | { XK_2, 0x1f }, 38 | { XK_3, 0x20 }, 39 | { XK_4, 0x21 }, 40 | { XK_5, 0x22 }, 41 | { XK_6, 0x23 }, 42 | { XK_7, 0x24 }, 43 | { XK_8, 0x25 }, 44 | { XK_9, 0x26 }, 45 | { XK_0, 0x27 }, 46 | 47 | { XK_Return, 0x28 }, 48 | { XK_Escape, 0x29 }, 49 | { XK_BackSpace, 0x2a }, 50 | { XK_Tab, 0x2b }, 51 | { XK_space, 0x2c }, 52 | { XK_minus, 0x2d }, 53 | { XK_equal, 0x2e }, 54 | { XK_bracketleft, 0x2f }, 55 | { XK_bracketright, 0x30 }, 56 | { XK_backslash, 0x31 }, 57 | { XK_semicolon, 0x33 }, 58 | { XK_apostrophe, 0x34 }, 59 | { XK_grave, 0x35 }, 60 | { XK_comma, 0x36 }, 61 | { XK_period, 0x37 }, 62 | { XK_slash, 0x38 }, 63 | 64 | // this is a bit ugly. We have the result of os x applying our 65 | // local keyboard layout, and we only have to combined key. We 66 | // undo this mapping by picking an arbitrary layout that might 67 | // have resulted in the input we see 68 | { XK_less, 0x36 }, // , 69 | { XK_greater, 0x37 }, // . 70 | { XK_exclam, 0x1e }, // 1 71 | { XK_at, 0x1f }, // 2 72 | { XK_numbersign, 0x20 }, // 3 73 | { XK_dollar, 0x21 }, // 4 74 | { XK_percent, 0x22 }, // 5 75 | { XK_asciicircum, 0x23 }, // 6 76 | { XK_ampersand, 0x24 }, // 7 77 | { XK_asterisk, 0x25 }, // 8 78 | { XK_parenleft, 0x26 }, // 9 79 | { XK_parenright, 0x27 }, // 0 80 | { XK_underscore, 0x2d }, // - 81 | { XK_bar, 0x31 }, // backslash 82 | { XK_quotedbl, 0x34 }, // ' 83 | { XK_asciitilde, 0x35 }, // ` 84 | { XK_question, 0x38 }, 85 | 86 | { XK_colon, 0x33 }, // ; 87 | 88 | // and the alphabet 89 | { XK_A, 0x04 }, 90 | { XK_B, 0x05 }, 91 | { XK_C, 0x06 }, 92 | { XK_D, 0x07 }, 93 | { XK_E, 0x08 }, 94 | { XK_F, 0x09 }, 95 | { XK_G, 0x0A }, 96 | { XK_H, 0x0B }, 97 | { XK_I, 0x0C }, 98 | { XK_J, 0x0D }, 99 | { XK_K, 0x0E }, 100 | { XK_L, 0x0F }, 101 | { XK_M, 0x10 }, 102 | { XK_N, 0x11 }, 103 | { XK_O, 0x12 }, 104 | { XK_P, 0x13 }, 105 | { XK_Q, 0x14 }, 106 | { XK_R, 0x15 }, 107 | { XK_S, 0x16 }, 108 | { XK_T, 0x17 }, 109 | { XK_U, 0x18 }, 110 | { XK_V, 0x19 }, 111 | { XK_W, 0x1A }, 112 | { XK_X, 0x1B }, 113 | { XK_Y, 0x1C }, 114 | { XK_Z, 0x1D }, 115 | 116 | {XK_F1, 0x3a}, 117 | {XK_F2, 0x3b}, 118 | {XK_F3, 0x3c}, 119 | {XK_F4, 0x3d}, 120 | {XK_F5, 0x3e}, 121 | {XK_F6, 0x3f}, 122 | {XK_F7, 0x40}, 123 | {XK_F8, 0x41}, 124 | {XK_F9, 0x42}, 125 | {XK_F10, 0x43}, 126 | {XK_F11, 0x44}, 127 | {XK_F12, 0x45}, 128 | 129 | {XK_F13, 0x68}, 130 | {XK_F14, 0x69}, 131 | {XK_F15, 0x6a}, 132 | {XK_F16, 0x6b}, 133 | {XK_F17, 0x6c}, 134 | {XK_F18, 0x6d}, 135 | {XK_F19, 0x6e}, 136 | {XK_F20, 0x6f}, 137 | {XK_F21, 0x70}, 138 | {XK_F22, 0x71}, 139 | {XK_F23, 0x72}, 140 | {XK_F24, 0x73}, 141 | 142 | {XK_Home, 0x4a}, 143 | {XK_Left, 0x50}, 144 | {XK_Up, 0x52}, 145 | {XK_Right, 0x4f}, 146 | {XK_Down, 0x51}, 147 | {XK_Prior, 0x4b}, 148 | {XK_Next, 0x4e}, 149 | {XK_End, 0x4d}, 150 | 151 | // {NSPrintScreenFunctionKey, 0x46}, 152 | // {NSScrollLockFunctionKey, 0x47}, 153 | // {NSPauseFunctionKey, 0x48}, 154 | // {NSInsertFunctionKey, 0x49}, 155 | // {NSHomeFunctionKey, 0x4a}, 156 | // {NSPageUpFunctionKey, 0x4b}, 157 | // {NSDeleteFunctionKey, 0x4c}, 158 | // {NSEndFunctionKey, 0x4d}, 159 | // {NSPageDownFunctionKey, 0x4e}, 160 | // {NSRightArrowFunctionKey, 0x4f}, 161 | // {NSLeftArrowFunctionKey, 0x50}, 162 | // {NSDownArrowFunctionKey, 0x51}, 163 | // {NSUpArrowFunctionKey, 0x52}, 164 | 165 | {XK_Shift_L, 0xE1}, 166 | {XK_Shift_R, 0xE5}, 167 | {XK_Control_L, 0xE0}, 168 | {XK_Control_R, 0xE4}, 169 | {XK_Alt_L, 0xE2}, 170 | {XK_Alt_R, 0xE6}, 171 | }; 172 | 173 | static int compare_keysym(const void *lv, const void *rv) { 174 | const struct keymap_t *left = (const struct keymap_t*) lv; 175 | const struct keymap_t *right = (const struct keymap_t*) rv; 176 | return left->keysym - right->keysym; 177 | } 178 | 179 | void keymap_init() { 180 | qsort(&keymap[0], sizeof(keymap) / sizeof(*keymap), sizeof(*keymap), 181 | compare_keysym); 182 | } 183 | 184 | uint8_t keymap_usageForKeysym(rfbKeySym c) { 185 | const struct keymap_t *mapping = (struct keymap_t*) 186 | bsearch(&c, &keymap[0], sizeof(keymap) / sizeof(*keymap), sizeof(*keymap), 187 | compare_keysym); 188 | if (mapping) { 189 | return mapping->hid; 190 | } 191 | else { 192 | return 0; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /keymap.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYMAP_H 2 | #define KEYMAP_H 3 | 4 | #include 5 | 6 | void keymap_init(); 7 | uint8_t keymap_usageForKeysym(rfbKeySym c); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #undef max // undo namespace pollution by rfb.h 20 | 21 | #include 22 | 23 | #include "unique_fd.h" 24 | #include "connection.h" 25 | #include "keymap.h" 26 | 27 | struct rfb_event_check { 28 | ev_check check; 29 | rfbScreenInfoPtr rfb; 30 | }; 31 | 32 | struct WriteAction { 33 | enum Type { 34 | Key, UpdateFramebuffer, Ping 35 | } type; 36 | 37 | union { 38 | struct { 39 | rfbBool down; 40 | rfbKeySym keySym; 41 | } keyEvent; 42 | struct { 43 | uint8_t incremental; 44 | uint16_t x; uint16_t y; 45 | uint16_t w; uint16_t h; 46 | } updateFramebuffer; 47 | }; 48 | 49 | template struct setter; 50 | }; 51 | 52 | template <> struct WriteAction::setter { 53 | template static void set(WriteAction& u, Args&& ...args) { 54 | u.keyEvent = {args...}; 55 | } 56 | }; 57 | template <> struct WriteAction::setter { 58 | static void set(WriteAction& u, uint8_t i, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { 59 | u.updateFramebuffer = {i, x, y, w ,h}; 60 | } 61 | }; 62 | template <> struct WriteAction::setter { 63 | static void set(WriteAction& u) { 64 | (void) u; 65 | } 66 | }; 67 | 68 | 69 | struct RFBUpdate { 70 | enum Type { 71 | SetFramebuffer, 72 | AddDirtyRect, 73 | SetServerName, 74 | } type; 75 | union { 76 | struct { 77 | char *newFramebuffer; 78 | int width; 79 | int height; 80 | } setFramebuffer; 81 | struct { 82 | int x1; int y1; 83 | int x2; int y2; 84 | } addDirtyRect; 85 | struct { 86 | const char *name; 87 | } setServerName; 88 | }; 89 | 90 | template struct setter; 91 | }; 92 | 93 | template <> struct RFBUpdate::setter { 94 | template static void set(RFBUpdate& u, Args&& ...args) { 95 | u.setFramebuffer = {args...}; 96 | } 97 | }; 98 | template <> struct RFBUpdate::setter { 99 | template static void set(RFBUpdate& u, Args&& ...args) { 100 | u.setServerName = {args...}; 101 | } 102 | }; 103 | template <> struct RFBUpdate::setter { 104 | template static void set(RFBUpdate& u, Args&& ...args) { 105 | u.addDirtyRect = {args...}; 106 | } 107 | }; 108 | 109 | 110 | #define EV(type, tag) type, type::tag 111 | 112 | template 113 | E makeEvent(Args&& ...args) { 114 | E e; 115 | e.type = type; 116 | E::template setter::set(e, std::forward(args)...); 117 | return e; 118 | } 119 | class AtenServer { 120 | public: 121 | AtenServer(int *argc, char **argv); 122 | void run(); 123 | 124 | private: 125 | WriteAction nextWriteAction(); 126 | 127 | void doWriter(); 128 | void doReader(); 129 | 130 | void keyEventHandler(rfbBool down, rfbKeySym keySym, rfbClientPtr cl); 131 | 132 | void handleFrameUpdate(); 133 | void handleRFBUpdates(); 134 | 135 | void sendRFBUpdate(const RFBUpdate& u); 136 | void sendAction(const WriteAction& w); 137 | 138 | // rfb side 139 | rfbScreenInfoPtr mRFB; 140 | char *mFrameBuffer; 141 | int mFBWidth, mFBHeight; 142 | bool mSetServerName; 143 | bool mScreenOff; 144 | 145 | // upstream side 146 | std::unique_ptr mConnection; 147 | 148 | std::thread mReaderThread; 149 | std::thread mWriterThread; 150 | 151 | std::mutex mActionMutex; 152 | std::condition_variable mActionCond; 153 | std::queue mActionQueue; 154 | 155 | std::atomic_bool mTerminating; 156 | 157 | struct ev_loop *mEVLoop; 158 | std::mutex mRFBMutex; 159 | std::queue mRFBUpdates; 160 | struct { 161 | ev_async async; 162 | AtenServer *self; 163 | } mRFBSignal; 164 | }; 165 | 166 | WriteAction AtenServer::nextWriteAction() { 167 | std::unique_lock lock{mActionMutex}; 168 | std::queue& q = mActionQueue; 169 | while (q.empty()) { 170 | mActionCond.wait(lock); 171 | } 172 | WriteAction ev = q.front(); 173 | q.pop(); 174 | return ev; 175 | } 176 | 177 | void AtenServer::doWriter() { 178 | // writer pulls from event queue and shoves out to upstream 179 | // socket. TODO FIXME: very close to an unlimited size binary 180 | // buffer, what, if anything, does this actually win us over 181 | // something like sendAsync(char*)? Answers: drop expired key 182 | // events, coalesce pointer events(possible with ikvm?), event 183 | // boundaries for re-establishing connections (char**), event 184 | // introspection on connection reset. 185 | 186 | try { 187 | while (!mTerminating) { 188 | WriteAction ev = nextWriteAction(); 189 | switch (ev.type) { 190 | case WriteAction::Key: { 191 | auto& p = ev.keyEvent; 192 | struct { 193 | uint8_t messageType; 194 | uint8_t padding1; 195 | uint8_t down; 196 | char padding2[2]; 197 | uint32_t key; 198 | char padding3[9]; 199 | } __attribute__((packed)) req; 200 | memset(&req, 0, sizeof(req)); 201 | uint8_t usage = keymap_usageForKeysym(p.keySym); 202 | // printf("key %s keysym=%x usage=%x\n", 203 | // p.down ? "down" : "up", 204 | // p.keySym, usage); 205 | if (usage) { 206 | req.messageType = 4; 207 | req.down = p.down; 208 | req.key = htonl(usage); 209 | mConnection->writeBytes((char*) &req, sizeof(req)); 210 | } 211 | break; 212 | } 213 | 214 | case WriteAction::UpdateFramebuffer: { 215 | // TODO FIXME: byte ordering of x, y, width and height 216 | auto& p = ev.updateFramebuffer; 217 | struct { 218 | uint8_t messageType; 219 | uint8_t incremental; 220 | uint16_t x,y,width,height; 221 | } req = {3, p.incremental, p.x, p.y, p.w, p.h}; 222 | mConnection->writeBytes((char*) &req, sizeof(req)); 223 | break; 224 | } 225 | 226 | case WriteAction::Ping: 227 | break; 228 | } 229 | } 230 | } 231 | catch (const std::runtime_error& e) { 232 | printf("writer terminating due to %s\n", e.what()); 233 | mTerminating = true; 234 | } 235 | printf("writer exit\n"); 236 | } 237 | 238 | 239 | static void copyPixels(char *out, const char *in, size_t count) { 240 | // TODO FIXME: potential bottleneck 241 | while (count --> 0) { 242 | const uint16_t ip = (in[0] & 0xff) | (in[1] << 8); 243 | 244 | uint8_t r = (ip >> 10) & 0x1f; 245 | uint8_t g = (ip >> 5) & 0x1f; 246 | uint8_t b = ip & 0x1f; 247 | 248 | const uint16_t op = r | g << 5 | b << 10; 249 | 250 | out[0] = op & 0xff; 251 | out[1] = op >> 8; 252 | out += 2; 253 | in += 2; 254 | } 255 | } 256 | 257 | void AtenServer::handleFrameUpdate() { 258 | char *fb = mFrameBuffer; 259 | 260 | mConnection->readBytes(1); // padding 261 | 262 | int nUpdates = ntohs(mConnection->readRaw()); 263 | // printf("nUpdates=%i\n", nUpdates); 264 | for (int update = 0; update < nUpdates; update++) { 265 | int x = ntohs(mConnection->readRaw()); 266 | int y = ntohs(mConnection->readRaw()); 267 | (void) x; (void) y; 268 | int width = ntohs(mConnection->readRaw()); 269 | int height = ntohs(mConnection->readRaw()); 270 | int encoding = ntohl(mConnection->readRaw()); 271 | int unknown = ntohl(mConnection->readRaw()); 272 | (void) unknown; 273 | (void) encoding; 274 | int dataLen = ntohl(mConnection->readRaw()); 275 | (void) dataLen; 276 | // printf("update[%d]: (%dx%d)+%d+%d len=%d\n", 277 | // update, width, height, x, y, dataLen); 278 | 279 | if (width == uint16_t(-640) && height == uint16_t(-480)) { 280 | if (!mScreenOff) { 281 | mScreenOff = true; 282 | printf("screen disappeared, showing error\n"); 283 | } 284 | // screen is disabled 285 | memset(fb, 0xf0, mFBWidth * mFBHeight * 2); 286 | sendRFBUpdate(makeEvent(0, 0, mFBWidth, mFBHeight)); 287 | } 288 | else { 289 | if (mScreenOff) { 290 | printf("screen back again\n"); 291 | mScreenOff = false; 292 | } 293 | if (width != mFBWidth || height != mFBHeight) { 294 | printf("framebuffer resizing! %dx%d -> %dx%d\n", 295 | mFBWidth, mFBHeight, width, height); 296 | fb = mFrameBuffer = 297 | reinterpret_cast(malloc(width * height * 2)); 298 | if (!fb) abort(); 299 | 300 | mFBWidth = width; 301 | mFBHeight = height; 302 | 303 | sendRFBUpdate(makeEvent(fb, width, height)); 304 | } 305 | } 306 | 307 | if (!mScreenOff) { 308 | int type = mConnection->readRaw(); 309 | (void) mConnection->readBytes(1); 310 | int segments = ntohl(mConnection->readRaw()); 311 | int totalLen = ntohl(mConnection->readRaw()); 312 | switch (type) { 313 | case 0: // subrects 314 | { 315 | bool haveRect = false; 316 | RFBUpdate u; 317 | u.type = RFBUpdate::AddDirtyRect; 318 | 319 | const int bsz = 16; 320 | for (int s = 0; s < segments; s++) { 321 | (void) mConnection->readBytes(4); 322 | int y = mConnection->readRaw(); 323 | int x = mConnection->readRaw(); 324 | const char *data = mConnection->readBytes(2 * bsz * bsz); 325 | 326 | char *out = fb + 2 * (y * bsz * mFBWidth + x * bsz); 327 | char *end = fb + 2 * (mFBHeight * mFBWidth); 328 | for (int line = 0; line < bsz; line++) { 329 | int size = bsz * 2; 330 | if (out > end) 331 | break; 332 | if (out + size > end) 333 | size = end - out; 334 | copyPixels(out, data, size >> 1); 335 | out += 2 * mFBWidth; 336 | data += size; 337 | } 338 | 339 | { 340 | int x1 = x * bsz, 341 | y1 = y * bsz, 342 | x2 = (x + 1) * bsz, 343 | y2 = (y + 1) * bsz; 344 | if (!haveRect) { 345 | u.addDirtyRect.x1 = x1; 346 | u.addDirtyRect.y1 = y1; 347 | u.addDirtyRect.x2 = x2; 348 | u.addDirtyRect.y2 = y2; 349 | haveRect = true; 350 | } 351 | else { 352 | u.addDirtyRect.x1 = std::min(u.addDirtyRect.x1, x1); 353 | u.addDirtyRect.y1 = std::min(u.addDirtyRect.y1, y1); 354 | u.addDirtyRect.x2 = std::max(u.addDirtyRect.x2, x2); 355 | u.addDirtyRect.y2 = std::max(u.addDirtyRect.y2, y2); 356 | } 357 | } 358 | } 359 | if (haveRect) { 360 | // printf("subrect update merged to: %dx%d+%d+%d\n", 361 | // u.addDirtyRect.x2 - u.addDirtyRect.x1, 362 | // u.addDirtyRect.y2 - u.addDirtyRect.y1, 363 | // u.addDirtyRect.x1, 364 | // u.addDirtyRect.y1); 365 | sendRFBUpdate(u); 366 | } 367 | } 368 | break; 369 | case 1: // entire frame 370 | { 371 | const char *data = mConnection->readBytes(totalLen - 10); 372 | copyPixels(fb, data, (totalLen - 10) >> 1); 373 | 374 | sendRFBUpdate(makeEvent(0, 0, mFBWidth, mFBHeight)); 375 | } 376 | break; 377 | default: 378 | printf("Ignoring unknown update type: %x\n", type); 379 | (void) mConnection->readBytes(totalLen - 10); 380 | break; 381 | } 382 | } 383 | } 384 | sendAction( 385 | makeEvent( 386 | mScreenOff ? 0 /* full */ : 1 /* incrememntal */, 387 | 0, 0, 0, 0)); 388 | } 389 | 390 | void AtenServer::doReader() { 391 | try { 392 | while (!mTerminating) { 393 | int messageType = mConnection->readRaw(); 394 | // printf("messagetype=%i\n", messageType); 395 | switch (messageType) { 396 | case 0: 397 | handleFrameUpdate(); 398 | break; 399 | case 4: 400 | (void) mConnection->readBytes(20); 401 | break; 402 | case 0x16: 403 | (void) mConnection->readBytes(1); 404 | break; 405 | case 0x37: 406 | (void) mConnection->readBytes(2); 407 | break; 408 | case 0x39: 409 | (void) mConnection->readBytes(264); 410 | break; 411 | case 0x3c: 412 | (void) mConnection->readBytes(8); 413 | break; 414 | default: 415 | abort(); 416 | } 417 | } 418 | } 419 | catch (const std::runtime_error& e) { 420 | mTerminating = true; 421 | sendAction(makeEvent()); 422 | printf("Reader terminating due to error: %s", e.what()); 423 | } 424 | printf("reader exit\n"); 425 | } 426 | 427 | 428 | void AtenServer::sendAction(const WriteAction& w) { 429 | std::unique_lock lock{mActionMutex}; 430 | mActionQueue.push(w); 431 | mActionCond.notify_all(); 432 | } 433 | 434 | void AtenServer::sendRFBUpdate(const RFBUpdate& u) { 435 | std::unique_lock lock{mRFBMutex}; 436 | mRFBUpdates.push(u); 437 | ev_async_send(mEVLoop, &mRFBSignal.async); 438 | } 439 | 440 | void AtenServer::keyEventHandler(rfbBool down, rfbKeySym keySym, rfbClientPtr cl) { 441 | (void) cl; 442 | sendAction(makeEvent(down, keySym)); 443 | } 444 | 445 | 446 | AtenServer::AtenServer(int *argc, char **argv) { 447 | mFBWidth = 640; 448 | mFBHeight = 480; 449 | mRFB = rfbGetScreen(argc, argv, mFBWidth, mFBHeight, 5, 3, 2); 450 | mFrameBuffer = reinterpret_cast(malloc(mFBWidth * mFBHeight * 2)); 451 | memset(mFrameBuffer, 0, mFBWidth * mFBHeight * 2); 452 | 453 | mRFB->desktopName = strdup("aten-proxy"); 454 | mRFB->frameBuffer = mFrameBuffer; 455 | mRFB->kbdAddEvent = [](rfbBool down, rfbKeySym keySym, rfbClientPtr cl){ 456 | AtenServer *self = reinterpret_cast( 457 | cl->screen->screenData); 458 | self->keyEventHandler(down, keySym, cl); 459 | }; 460 | 461 | rfbInitServer(mRFB); 462 | 463 | keymap_init(); 464 | } 465 | 466 | void AtenServer::handleRFBUpdates() { 467 | while (true) { 468 | RFBUpdate ev; 469 | { 470 | std::unique_lock lock{mRFBMutex}; 471 | if (mRFBUpdates.empty()) 472 | return; 473 | ev = mRFBUpdates.front(); 474 | mRFBUpdates.pop(); 475 | } 476 | // printf("handleRFBUpdate, type=%d\n", ev.type); 477 | switch (ev.type) { 478 | case RFBUpdate::SetFramebuffer: { 479 | auto& p = ev.setFramebuffer; 480 | char *oldFramebuffer = mRFB->frameBuffer; 481 | printf("framebuffer change: %p[%dx%d] -> %p[%dx%d]\n", 482 | oldFramebuffer, mRFB->width, mRFB->height, 483 | p.newFramebuffer, p.width, p.height); 484 | rfbNewFramebuffer(mRFB, p.newFramebuffer, p.width, p.height, 5, 3, 2); 485 | free(oldFramebuffer); 486 | break; 487 | } 488 | 489 | case RFBUpdate::AddDirtyRect: { 490 | auto &p = ev.addDirtyRect; 491 | rfbMarkRectAsModified(mRFB, p.x1, p.y1, p.x2, p.y2); 492 | break; 493 | } 494 | 495 | case RFBUpdate::SetServerName: { 496 | auto &p = ev.setServerName; 497 | const char *oldName = mRFB->desktopName; 498 | mRFB->desktopName = p.name; 499 | if (mSetServerName) { 500 | free((void*) oldName); 501 | } 502 | mSetServerName = true; 503 | break; 504 | } 505 | 506 | } 507 | } 508 | } 509 | 510 | void AtenServer::run() { 511 | // set here instead of constructor to not break inheritance 512 | mRFB->screenData = this; 513 | 514 | struct ev_loop *loop = mEVLoop = EV_DEFAULT; 515 | 516 | // really simple integration of libvncserver's event loop into 517 | // libev. while completely useless now, ideally this integration 518 | // would be extended to monitor the sockets used by libvncserver. 519 | ev_idle keepalive; 520 | ev_idle_init(&keepalive, [](EV_P_ ev_idle *w, int revents){ 521 | (void) loop; (void) w; (void) revents; 522 | // global idle handler prevents loop from sleeping. 523 | }); 524 | ev_idle_start(loop, &keepalive); 525 | 526 | rfb_event_check c; 527 | c.rfb = mRFB; 528 | ev_check_init(&c.check, [](EV_P_ ev_check *w, int revents){ 529 | // once around every libev loop we step the libvncserver 530 | // loop. 531 | (void) loop; (void) revents; 532 | rfbScreenInfoPtr p = reinterpret_cast(w)->rfb; 533 | rfbProcessEvents(p, -1); 534 | }); 535 | ev_check_start(loop, &c.check); 536 | 537 | // and a way to stuff events into the libvncserver loop: 538 | mRFBSignal.self = this; 539 | ev_async_init(&mRFBSignal.async, [](EV_P_ ev_async *w, int revents) { 540 | (void) loop; (void) revents; 541 | AtenServer *self = reinterpret_cast(w)->self; 542 | self->handleRFBUpdates(); 543 | }); 544 | ev_async_start(loop, &mRFBSignal.async); 545 | 546 | std::thread{ev_run, loop, 0}.detach(); 547 | 548 | const char *host = getenv("ATEN_PROXY_HOST"); 549 | const char *port = getenv("ATEN_PROXY_PORT"); 550 | const char *username = getenv("ATEN_PROXY_USERNAME"); 551 | const char *password = getenv("ATEN_PROXY_PASSWORD"); 552 | while (true) { 553 | try { 554 | struct { 555 | char username[24]; 556 | char password[24]; 557 | } auth; 558 | if (strlen(username) >= 24 || strlen(password) >= 24) { 559 | printf("username and password must be 0-23 characters each\n"); 560 | abort(); 561 | } 562 | strncpy(auth.username, username, sizeof(auth.username)); 563 | strncpy(auth.password, password, sizeof(auth.password)); 564 | 565 | mConnection = std::unique_ptr{ 566 | new Connection(host, port)}; 567 | 568 | fprintf(stderr, "Connected\n"); 569 | 570 | // handshake 571 | mConnection->readBytes(strlen("RFB 003.008\n")); 572 | mConnection->writeString("RFB 003.008\n"); 573 | 574 | fprintf(stderr, "Completed handshake\n"); 575 | 576 | // security type 577 | int nSecurity = mConnection->readRaw(); 578 | char *security = mConnection->readBytes(nSecurity); 579 | if (security[0] != 16) { 580 | abort(); 581 | } 582 | mConnection->writeRaw(16); 583 | 584 | // unknown reply from aten, 24 bytes 585 | (void) mConnection->readBytes(24); 586 | 587 | fprintf(stderr, "Negotiated security type\n"); 588 | 589 | // auth 590 | mConnection->writeBytes((char*) &auth, sizeof(auth)); 591 | int authErr = mConnection->readRaw(); 592 | if (authErr) { 593 | abort(); 594 | } 595 | 596 | // client init 597 | mConnection->writeRaw(0); 598 | 599 | // server init; aten sends complete garbaage 600 | (void) mConnection->readBytes(sizeof(uint16_t) * 2 + 16); // dimensions 601 | 602 | int serverNameLen = ntohl(mConnection->readRaw()); 603 | char *serverName = mConnection->readBytes(serverNameLen); 604 | RFBUpdate u; 605 | u.type = RFBUpdate::SetServerName; 606 | u.setServerName.name = strdup(serverName); 607 | sendRFBUpdate(u); 608 | 609 | // more aten unknown 610 | (void) mConnection->readBytes(12); 611 | 612 | // initial screen update 613 | sendAction(makeEvent(0, 0, 0, 0, 0)); 614 | 615 | fprintf(stderr, "Sent request for initial update\n"); 616 | 617 | mWriterThread = std::thread{[this]{doWriter();}}; 618 | mReaderThread = std::thread{[this]{doReader();}}; 619 | mWriterThread.join(); 620 | mReaderThread.join(); 621 | mTerminating.store(false); 622 | mConnection = nullptr; 623 | } 624 | catch (const std::runtime_error& x) { 625 | printf("connection error: %s\n", x.what()); 626 | sleep(1); 627 | } 628 | } 629 | } 630 | 631 | int main(int argc, char **argv) { 632 | AtenServer server {&argc, argv}; 633 | server.run(); 634 | } 635 | -------------------------------------------------------------------------------- /unique_fd.h: -------------------------------------------------------------------------------- 1 | // -*- c++ -*- 2 | #ifndef _UNIQUE_FD_H_ 3 | #define _UNIQUE_FD_H_ 4 | 5 | #include 6 | #include 7 | 8 | // Adapted from http://stackoverflow.com/a/15762682 9 | 10 | class unique_fd { 11 | int mFD; 12 | 13 | public: 14 | unique_fd() : mFD(-1) {} 15 | explicit unique_fd(int x) : mFD(x) {} 16 | 17 | ~unique_fd() { 18 | if (mFD != -1) 19 | close(mFD); 20 | } 21 | 22 | operator int() { return mFD; } 23 | 24 | unique_fd(unique_fd&& x) 25 | : mFD(-1) 26 | { 27 | std::swap(mFD, x.mFD); 28 | } 29 | 30 | unique_fd& operator =(unique_fd&& x) { 31 | std::swap(mFD, x.mFD); 32 | return *this; 33 | } 34 | }; 35 | 36 | #endif /* _UNIQUE_FD_H_ */ 37 | --------------------------------------------------------------------------------