| /** |
| * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. |
| * |
| * This software is licensed under the GNU General Public License v3 or later. |
| * |
| * It 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 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 <http://www.gnu.org/licenses/>. |
| * |
| */ |
| |
| package com.cloud.consoleproxy;
|
|
|
| import java.io.BufferedInputStream;
|
| import java.io.DataInputStream;
|
| import java.io.IOException;
|
| import java.io.OutputStream;
|
| import java.net.Socket;
|
| import java.util.StringTokenizer;
|
|
|
| import com.cloud.console.Logger; |
| import com.cloud.console.RfbProto; |
|
|
| public class ConsoleProxyClientHandler extends Thread {
|
| private static final Logger s_logger = Logger.getLogger(ConsoleProxyClientHandler.class);
|
|
|
| Socket clientSocket = null;
|
| DataInputStream clientIns = null;
|
| OutputStream clientOuts = null;
|
|
|
| public ConsoleProxyClientHandler(Socket client) {
|
| clientSocket = client;
|
| }
|
|
|
| synchronized void cleanup() {
|
| try {
|
| if (clientSocket != null) {
|
| s_logger.info("Closing connection to "
|
| + clientSocket.getInetAddress());
|
|
|
| clientSocket.close();
|
| clientSocket = null;
|
| }
|
| } catch (IOException ioe) {
|
| }
|
| try {
|
| if (clientIns != null) {
|
| clientIns.close();
|
| clientIns = null;
|
| }
|
| } catch (IOException ioe) {
|
| }
|
| try {
|
| if (clientOuts != null) {
|
| clientOuts.close();
|
| clientOuts = null;
|
| }
|
| } catch (IOException ioe) {
|
| }
|
| }
|
|
|
| public void run() {
|
| try {
|
| String srcinfo = clientSocket.getInetAddress().getHostAddress() +
|
| ":" + clientSocket.getPort();
|
| clientIns = new DataInputStream(new BufferedInputStream(
|
| clientSocket.getInputStream()));
|
| // clientOuts = new GZIPOutputStream(clientSocket.getOutputStream(), 65536);
|
| clientOuts = clientSocket.getOutputStream();
|
| clientOuts.write("RFB 000.000\000".getBytes("US-ASCII"));
|
| clientOuts.flush();
|
| int b1 = clientIns.read();
|
| int b2 = clientIns.read();
|
| if (b1 != 'V' || b2 != 'M') {
|
| throw new Exception ("Bad header");
|
| }
|
| byte[] proxyInfo = new byte[clientIns.read()];
|
| clientIns.readFully(proxyInfo);
|
| String proxyString = new String(proxyInfo, "US-ASCII");
|
| StringTokenizer stk = new StringTokenizer(proxyString, ":\n");
|
| String host = stk.nextToken();
|
| int port = Integer.parseInt(stk.nextToken());
|
| String sid = stk.nextToken();
|
| ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, "", "");
|
|
|
| ConsoleProxy.waitForViewerToStart(viewer);
|
|
|
| handleClientSession(viewer, srcinfo);
|
| } catch (Exception ioe) {
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug(ioe.toString());
|
| } finally {
|
| cleanup();
|
| }
|
| }
|
|
|
| void writeServer (ConsoleProxyViewer viewer, byte[] b, int off, int len) {
|
| viewer.writeServer(b, off, len);
|
| }
|
|
|
| void writeClientU16 (int n) throws IOException {
|
| byte[] b = new byte[2];
|
| b[0] = (byte) ((n >> 8) & 0xff);
|
| b[1] = (byte) (n & 0xff);
|
| clientOuts.write(b);
|
| }
|
|
|
| void writeClientU32 (int n) throws IOException {
|
| byte[] b = new byte[4];
|
| b[0] = (byte) ((n >> 24) & 0xff);
|
| b[1] = (byte) ((n >> 16) & 0xff);
|
| b[2] = (byte) ((n >> 8) & 0xff);
|
| b[3] = (byte) (n & 0xff);
|
| clientOuts.write(b);
|
| }
|
|
|
| String RFB_VERSION_STRING = "RFB 003.008\n";
|
|
|
| void handleClientSession(ConsoleProxyViewer viewer, String srcinfo) throws Exception {
|
| s_logger.info("Start to handle client session");
|
|
|
| viewer.setAjaxViewer(false);
|
|
|
| // Exchange version with client
|
| clientOuts.write(RFB_VERSION_STRING.getBytes("US-ASCII"));
|
| clientOuts.flush();
|
| byte[] clientVersion = new byte[12];
|
| clientIns.readFully(clientVersion);
|
| if (!RFB_VERSION_STRING.equals(new String(clientVersion, "US-ASCII"))) {
|
| throw new Exception("Bad client version");
|
| }
|
| // Send security type -- no authentication needed
|
| byte[] serverSecurity = new byte[2];
|
| serverSecurity[0] = 1;
|
| serverSecurity[1] = 1;
|
| clientOuts.write(serverSecurity);
|
| clientOuts.flush();
|
| int clientSecurity = clientIns.read();
|
| if (clientSecurity != 1) {
|
| throw new Exception("Unsupported client security type " + clientSecurity);
|
| }
|
| byte[] serverSecResp = new byte[4];
|
| serverSecResp[0] = serverSecResp[1] = serverSecResp[2] = serverSecResp[3] = 0;
|
| clientOuts.write(serverSecResp);
|
| clientOuts.flush();
|
|
|
| // Receive and ignore client init
|
| clientIns.read();
|
|
|
| s_logger.info("Sending ServerInit w=" + viewer.rfb.framebufferWidth +
|
| " h=" + viewer.rfb.framebufferHeight +
|
| " bits=" + viewer.rfb.bitsPerPixel +
|
| " depth=" + viewer.rfb.depth +
|
| " name=" + viewer.rfb.desktopName);
|
| // Send serverInit
|
| writeClientU16(viewer.rfb.framebufferWidth);
|
| writeClientU16(viewer.rfb.framebufferHeight);
|
| clientOuts.write(viewer.rfb.bitsPerPixel);
|
| clientOuts.write(viewer.rfb.depth);
|
| clientOuts.write(viewer.rfb.bigEndian ? 1 : 0);
|
| clientOuts.write(viewer.rfb.trueColour ? 1 : 0);
|
| writeClientU16(viewer.rfb.redMax);
|
| writeClientU16(viewer.rfb.greenMax);
|
| writeClientU16(viewer.rfb.blueMax);
|
| clientOuts.write(viewer.rfb.redShift);
|
| clientOuts.write(viewer.rfb.greenShift);
|
| clientOuts.write(viewer.rfb.blueShift);
|
| byte[] pad = new byte[3];
|
| clientOuts.write(pad);
|
| writeClientU32(viewer.rfb.desktopName.length());
|
| clientOuts.write(viewer.rfb.desktopName.getBytes("US-ASCII"));
|
| clientOuts.flush();
|
|
|
| // Lock the viewer to avoid race condition
|
| synchronized (viewer) {
|
| if (viewer.clientStream != null) {
|
| s_logger.info("Disconnecting client link stream " +
|
| viewer.clientStream.hashCode() + " from " +
|
| viewer.clientStreamInfo);
|
| viewer.clientStream.close();
|
| }
|
| viewer.clientStream = clientOuts;
|
| viewer.clientStreamInfo = srcinfo;
|
| viewer.lastUsedTime = System.currentTimeMillis();
|
|
|
| s_logger.info("Setting client link stream " +
|
| viewer.clientStream.hashCode() + " from " + srcinfo);
|
| }
|
|
|
| try {
|
| while (!viewer.isDropped()) {
|
| byte[] b = new byte[512];
|
| int nbytes = 0;
|
| int msgType = clientIns.read();
|
| b[0] = (byte)msgType;
|
| switch (msgType) {
|
| case RfbProto.SetPixelFormat:
|
| clientIns.readFully(b, 1, 19);
|
| nbytes = 20;
|
|
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug("C->S RFB message SetPixelFormat, size=" + nbytes);
|
| break;
|
|
|
| case RfbProto.SetEncodings:
|
|
|
| clientIns.read(); // padding
|
| b[1] = 0;
|
| int n = clientIns.readUnsignedShort();
|
| if (n > (512 - 4)/4) {
|
| throw new Exception ("Too many client encodings");
|
| }
|
| b[2] = (byte) ((n >> 8) & 0xff);
|
| b[3] = (byte) (n & 0xff);
|
| clientIns.readFully(b, 4, n * 4);
|
| nbytes = n * 4 + 4;
|
|
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug("C->S RFB message SetEncodings, size=" + nbytes);
|
| break;
|
|
|
| case RfbProto.FramebufferUpdateRequest:
|
| clientIns.readFully(b, 1, 9);
|
| nbytes = 10;
|
|
|
| if(s_logger.isDebugEnabled()) {
|
| int i = b[1];
|
| int x = ((0xff & b[2]) << 8) + b[3];
|
| int y = ((0xff & b[4]) << 8) + b[5];
|
| int w = ((0xff & b[6]) << 8) + b[7];
|
| int h = ((0xff & b[8]) << 8) + b[9];
|
|
|
| s_logger.debug("C->S RFB message FramebufferUpdateRequest, size=" + nbytes + " x=" + x
|
| +" y=" + y + " w=" + w + " h=" + h);
|
| }
|
| break;
|
|
|
| case RfbProto.KeyboardEvent:
|
| clientIns.readFully(b, 1, 7);
|
| nbytes = 8;
|
|
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug("C->S RFB message KeyboardEvent, size=" + nbytes);
|
| break;
|
| case RfbProto.PointerEvent:
|
| clientIns.readFully(b, 1, 5);
|
| nbytes = 6;
|
|
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug("C->S RFB message PointerEvent, size=" + nbytes);
|
| break;
|
|
|
| case RfbProto.VMOpsClientCustom:
|
| clientIns.read(); // read and ignore, used to track liveliness
|
| // of the client
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug("C->S RFB message VMOpsClientCustom");
|
| break;
|
|
|
| default:
|
| if(s_logger.isDebugEnabled())
|
| s_logger.debug("C->S unknown message type: " + msgType + ", size=" + nbytes);
|
| throw new Exception("Bad client event type: " + msgType);
|
| }
|
| writeServer(viewer, b, 0, nbytes);
|
| }
|
| } finally {
|
| viewer.lastUsedTime = System.currentTimeMillis();
|
| }
|
| }
|
| }
|