From b55d9ecf533a5253b0b5cdcd155c692b1d3d7bec Mon Sep 17 00:00:00 2001 From: Anshul Gangwar Date: Tue, 31 Dec 2013 00:56:27 +0530 Subject: [PATCH] CLOUDSTACK-5344 commit for console proxy rdp for hyperv --- .../HypervResourceController.cs | 39 +++ .../com/cloud/server/ManagementServer.java | 3 + .../cloud/server/ManagementServerImpl.java | 26 +- .../servlet/ConsoleProxyClientParam.java | 35 +- .../cloud/servlet/ConsoleProxyServlet.java | 191 ++++++----- .../main/java/common/BufferedImageCanvas.java | 8 +- .../java/common/adapter/AwtCanvasAdapter.java | 20 +- .../src/main/java/rdpclient/RdpClient.java | 17 +- services/console-proxy/server/pom.xml | 5 + .../com/cloud/consoleproxy/ConsoleProxy.java | 40 ++- .../consoleproxy/ConsoleProxyAjaxHandler.java | 45 +-- .../consoleproxy/ConsoleProxyClientParam.java | 47 ++- .../ConsoleProxyHttpHandlerHelper.java | 13 +- .../consoleproxy/ConsoleProxyRdpClient.java | 318 ++++++++++++++++++ .../consoleproxy/rdp/KeysymToKeycode.java | 115 +++++++ .../rdp/RdpBufferedImageCanvas.java | 103 ++++++ 16 files changed, 874 insertions(+), 151 deletions(-) create mode 100644 services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java create mode 100644 services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java create mode 100644 services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs index c885598e852..7948196342e 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs @@ -1915,6 +1915,45 @@ namespace HypervResource } } + // POST api/HypervResource/GetVncPortCommand + [HttpPost] + [ActionName(CloudStackTypes.GetVncPortCommand)] + public JContainer GetVncPortCommand([FromBody]dynamic cmd) + { + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.GetVncPortCommand + cmd.ToString()); + + string details = null; + bool result = false; + string address = null; + int port = -9; + + try + { + string vmName = (string)cmd.name; + var sys = wmiCallsV2.GetComputerSystem(vmName); + address = "instanceId=" + sys.Name ; + result = true; + } + catch (Exception sysEx) + { + details = CloudStackTypes.GetVncPortAnswer + " failed due to " + sysEx.Message; + logger.Error(details, sysEx); + } + + object ansContent = new + { + result = result, + details = details, + address = address, + port = port + }; + + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVncPortAnswer); + } + } + public static System.Net.NetworkInformation.NetworkInterface GetNicInfoFromIpAddress(string ipAddress, out string subnet) { System.Net.NetworkInformation.NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java index c5091e9a760..95835b7a00c 100755 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import com.cloud.host.DetailVO; import com.cloud.host.HostVO; import com.cloud.storage.GuestOSVO; import com.cloud.utils.Pair; @@ -51,6 +52,8 @@ public interface ManagementServer extends ManagementService, PluggableService { */ HostVO getHostBy(long hostId); + DetailVO findDetail(long hostId, String name); + String getConsoleAccessUrlRoot(long vmId); GuestOSVO getGuestOs(Long guestOsId); diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index cd8f3fbb465..e98c97a6a28 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -42,9 +42,6 @@ import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.service.ServiceOfferingVO; -import com.cloud.service.dao.ServiceOfferingDao; -import org.apache.cloudstack.api.command.admin.router.UpgradeRouterTemplateCmd; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; @@ -148,6 +145,7 @@ import org.apache.cloudstack.api.command.admin.router.RebootRouterCmd; import org.apache.cloudstack.api.command.admin.router.StartRouterCmd; import org.apache.cloudstack.api.command.admin.router.StopRouterCmd; import org.apache.cloudstack.api.command.admin.router.UpgradeRouterCmd; +import org.apache.cloudstack.api.command.admin.router.UpgradeRouterTemplateCmd; import org.apache.cloudstack.api.command.admin.storage.AddImageStoreCmd; import org.apache.cloudstack.api.command.admin.storage.AddS3Cmd; import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; @@ -274,6 +272,7 @@ import org.apache.cloudstack.api.command.user.iso.UpdateIsoCmd; import org.apache.cloudstack.api.command.user.iso.UpdateIsoPermissionsCmd; import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.AssignCertToLoadBalancerCmd; import org.apache.cloudstack.api.command.user.loadbalancer.AssignToLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.loadbalancer.CreateApplicationLoadBalancerCmd; import org.apache.cloudstack.api.command.user.loadbalancer.CreateLBHealthCheckPolicyCmd; @@ -283,18 +282,17 @@ import org.apache.cloudstack.api.command.user.loadbalancer.DeleteApplicationLoad import org.apache.cloudstack.api.command.user.loadbalancer.DeleteLBHealthCheckPolicyCmd; import org.apache.cloudstack.api.command.user.loadbalancer.DeleteLBStickinessPolicyCmd; import org.apache.cloudstack.api.command.user.loadbalancer.DeleteLoadBalancerRuleCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.DeleteSslCertCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListApplicationLoadBalancersCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListLBHealthCheckPoliciesCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListLBStickinessPoliciesCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListLoadBalancerRuleInstancesCmd; import org.apache.cloudstack.api.command.user.loadbalancer.ListLoadBalancerRulesCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.ListSslCertsCmd; +import org.apache.cloudstack.api.command.user.loadbalancer.RemoveCertFromLoadBalancerCmd; import org.apache.cloudstack.api.command.user.loadbalancer.RemoveFromLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.loadbalancer.UploadSslCertCmd; -import org.apache.cloudstack.api.command.user.loadbalancer.DeleteSslCertCmd; -import org.apache.cloudstack.api.command.user.loadbalancer.ListSslCertsCmd; -import org.apache.cloudstack.api.command.user.loadbalancer.AssignCertToLoadBalancerCmd; -import org.apache.cloudstack.api.command.user.loadbalancer.RemoveCertFromLoadBalancerCmd; import org.apache.cloudstack.api.command.user.nat.CreateIpForwardingRuleCmd; import org.apache.cloudstack.api.command.user.nat.DeleteIpForwardingRuleCmd; import org.apache.cloudstack.api.command.user.nat.DisableStaticNatCmd; @@ -532,6 +530,8 @@ import com.cloud.projects.ProjectManager; import com.cloud.resource.ResourceManager; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.auth.UserAuthenticator; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSCategoryVO; @@ -577,13 +577,12 @@ import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.JoinBuilder; -import com.cloud.utils.db.TransactionCallback; -import com.cloud.utils.db.TransactionCallbackNoReturn; -import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.db.JoinBuilder.JoinType; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.MacAddress; import com.cloud.utils.net.NetUtils; @@ -855,6 +854,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe return _hostDao.findById(hostId); } + @Override + public DetailVO findDetail(long hostId, String name) { + return _detailsDao.findDetail(hostId, name); + } + @Override public long getId() { return MacAddress.getMacAddress().toLong(); @@ -3931,6 +3935,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } public void setLockMasterListener(LockMasterListener lockMasterListener) { - this._lockMasterListener = lockMasterListener; + _lockMasterListener = lockMasterListener; } } diff --git a/server/src/com/cloud/servlet/ConsoleProxyClientParam.java b/server/src/com/cloud/servlet/ConsoleProxyClientParam.java index 33f124a0c58..5d528c6c6a7 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyClientParam.java +++ b/server/src/com/cloud/servlet/ConsoleProxyClientParam.java @@ -19,7 +19,7 @@ package com.cloud.servlet; // To maintain independency of console proxy project, we duplicate this class from console proxy project public class ConsoleProxyClientParam { private String clientHostAddress; - private int clientHostPort; + private int clientHostPort; private String clientHostPassword; private String clientTag; private String ticket; @@ -27,7 +27,11 @@ public class ConsoleProxyClientParam { private String clientTunnelUrl; private String clientTunnelSession; + private String hypervHost; + private String ajaxSessionId; + private String username; + private String password; public ConsoleProxyClientParam() { clientHostPort = 0; @@ -90,7 +94,7 @@ public class ConsoleProxyClientParam { } public String getAjaxSessionId() { - return this.ajaxSessionId; + return ajaxSessionId; } public void setAjaxSessionId(String ajaxSessionId) { @@ -98,7 +102,7 @@ public class ConsoleProxyClientParam { } public String getLocale() { - return this.locale; + return locale; } public void setLocale(String locale) { @@ -111,4 +115,29 @@ public class ConsoleProxyClientParam { return clientHostAddress + ":" + clientHostPort; } + + public void setHypervHost(String host) { + hypervHost = host; + } + + public String getHypervHost() { + return hypervHost; + } + + public void setUsername(String username) { + this.username = username; + + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } } diff --git a/server/src/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/com/cloud/servlet/ConsoleProxyServlet.java index aad00a49dbe..e0aaa6ddec8 100644 --- a/server/src/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/com/cloud/servlet/ConsoleProxyServlet.java @@ -56,7 +56,6 @@ import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.db.EntityManager; -import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionLegacy; import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.VirtualMachine; @@ -81,6 +80,7 @@ public class ConsoleProxyServlet extends HttpServlet { @Inject EntityManager _entityMgr; @Inject UserVmDetailsDao _userVmDetailsDao; + static ManagementServer s_ms; private final Gson _gson = new GsonBuilder().create(); @@ -90,8 +90,8 @@ public class ConsoleProxyServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { - SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext()); - s_ms = _ms; + SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext()); + s_ms = _ms; } @Override @@ -103,12 +103,12 @@ public class ConsoleProxyServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { try { - if(_accountMgr == null || _vmMgr == null || _ms == null) { + if (_accountMgr == null || _vmMgr == null || _ms == null) { sendResponse(resp, "Service is not ready"); return; } - if(_ms.getHashKey() == null) { + if (_ms.getHashKey() == null) { s_logger.debug("Console/thumbnail access denied. Ticket service is not ready yet"); sendResponse(resp, "Service is not ready"); return; @@ -122,8 +122,8 @@ public class ConsoleProxyServlet extends HttpServlet { params.putAll(req.getParameterMap()); HttpSession session = req.getSession(false); - if(session == null) { - if(verifyRequest(params)) { + if (session == null) { + if (verifyRequest(params)) { userId = (String)params.get("userid")[0]; account = (String)params.get("account")[0]; accountObj = (Account)params.get("accountobj")[0]; @@ -134,12 +134,12 @@ public class ConsoleProxyServlet extends HttpServlet { } } else { // adjust to latest API refactoring changes - if(session.getAttribute("userid") != null) { + if (session.getAttribute("userid") != null) { userId = ((Long)session.getAttribute("userid")).toString(); } accountObj = (Account)session.getAttribute("accountobj"); - if(accountObj != null) { + if (accountObj != null) { account = "" + accountObj.getId(); } } @@ -152,7 +152,7 @@ public class ConsoleProxyServlet extends HttpServlet { } String cmd = req.getParameter("cmd"); - if(cmd == null || !isValidCmd(cmd)) { + if (cmd == null || !isValidCmd(cmd)) { s_logger.debug("invalid console servlet command: " + cmd); sendResponse(resp, ""); return; @@ -160,20 +160,20 @@ public class ConsoleProxyServlet extends HttpServlet { String vmIdString = req.getParameter("vm"); Long vmId = _identityService.getIdentityId("vm_instance", vmIdString); - if(vmId == null) { + if (vmId == null) { s_logger.info("invalid console servlet command parameter: " + vmIdString); sendResponse(resp, ""); return; } - if(!checkSessionPermision(req, vmId, accountObj)) { + if (!checkSessionPermision(req, vmId, accountObj)) { sendResponse(resp, "Permission denied"); return; } - if(cmd.equalsIgnoreCase("thumbnail")) { + if (cmd.equalsIgnoreCase("thumbnail")) { handleThumbnailRequest(req, resp, vmId); - } else if(cmd.equalsIgnoreCase("access")) { + } else if (cmd.equalsIgnoreCase("access")) { handleAccessRequest(req, resp, vmId); } else { handleAuthRequest(req, resp, vmId); @@ -186,27 +186,27 @@ public class ConsoleProxyServlet extends HttpServlet { private void handleThumbnailRequest(HttpServletRequest req, HttpServletResponse resp, long vmId) { VirtualMachine vm = _vmMgr.findById(vmId); - if(vm == null) { + if (vm == null) { s_logger.warn("VM " + vmId + " does not exist, sending blank response for thumbnail request"); sendResponse(resp, ""); return; } - if(vm.getHostId() == null) { + if (vm.getHostId() == null) { s_logger.warn("VM " + vmId + " lost host info, sending blank response for thumbnail request"); sendResponse(resp, ""); return; } HostVO host = _ms.getHostBy(vm.getHostId()); - if(host == null) { + if (host == null) { s_logger.warn("VM " + vmId + "'s host does not exist, sending blank response for thumbnail request"); sendResponse(resp, ""); return; } String rootUrl = _ms.getConsoleAccessUrlRoot(vmId); - if(rootUrl == null) { + if (rootUrl == null) { sendResponse(resp, ""); return; } @@ -217,19 +217,19 @@ public class ConsoleProxyServlet extends HttpServlet { String value = req.getParameter("w"); try { w = Integer.parseInt(value); - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { } value = req.getParameter("h"); try { h = Integer.parseInt(value); - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { } try { resp.sendRedirect(composeThumbnailUrl(rootUrl, vm, host, w, h)); } catch (IOException e) { - if(s_logger.isInfoEnabled()) { + if (s_logger.isInfoEnabled()) { s_logger.info("Client may already close the connection"); } } @@ -237,36 +237,37 @@ public class ConsoleProxyServlet extends HttpServlet { private void handleAccessRequest(HttpServletRequest req, HttpServletResponse resp, long vmId) { VirtualMachine vm = _vmMgr.findById(vmId); - if(vm == null) { + if (vm == null) { s_logger.warn("VM " + vmId + " does not exist, sending blank response for console access request"); sendResponse(resp, ""); return; } - if(vm.getHostId() == null) { + if (vm.getHostId() == null) { s_logger.warn("VM " + vmId + " lost host info, sending blank response for console access request"); sendResponse(resp, ""); return; } HostVO host = _ms.getHostBy(vm.getHostId()); - if(host == null) { + + if (host == null) { s_logger.warn("VM " + vmId + "'s host does not exist, sending blank response for console access request"); sendResponse(resp, ""); return; } String rootUrl = _ms.getConsoleAccessUrlRoot(vmId); - if(rootUrl == null) { + if (rootUrl == null) { sendResponse(resp, "

Console access will be ready in a few minutes. Please try it again later.

"); return; } String vmName = vm.getHostName(); - if(vm.getType() == VirtualMachine.Type.User) { + if (vm.getType() == VirtualMachine.Type.User) { UserVm userVm = _entityMgr.findById(UserVm.class, vmId); String displayName = userVm.getDisplayName(); - if(displayName != null && !displayName.isEmpty() && !displayName.equals(vmName)) { + if (displayName != null && !displayName.isEmpty() && !displayName.equals(vmName)) { vmName += "(" + displayName + ")"; } } @@ -283,27 +284,27 @@ public class ConsoleProxyServlet extends HttpServlet { // TODO authentication channel between console proxy VM and management server needs to be secured, // the data is now being sent through private network, but this is apparently not enough VirtualMachine vm = _vmMgr.findById(vmId); - if(vm == null) { + if (vm == null) { s_logger.warn("VM " + vmId + " does not exist, sending failed response for authentication request from console proxy"); sendResponse(resp, "failed"); return; } - if(vm.getHostId() == null) { + if (vm.getHostId() == null) { s_logger.warn("VM " + vmId + " lost host info, failed response for authentication request from console proxy"); sendResponse(resp, "failed"); return; } HostVO host = _ms.getHostBy(vm.getHostId()); - if(host == null) { + if (host == null) { s_logger.warn("VM " + vmId + "'s host does not exist, sending failed response for authentication request from console proxy"); sendResponse(resp, "failed"); return; } String sid = req.getParameter("sid"); - if(sid == null || !sid.equals(vm.getVncPassword())) { + if (sid == null || !sid.equals(vm.getVncPassword())) { s_logger.warn("sid " + sid + " in url does not match stored sid " + vm.getVncPassword()); sendResponse(resp, "failed"); return; @@ -320,15 +321,19 @@ public class ConsoleProxyServlet extends HttpServlet { s_logger.info("Parse host info returned from executing GetVNCPortCommand. host info: " + hostInfo); - if(hostInfo != null && hostInfo.startsWith("consoleurl")) { - String tokens[] = hostInfo.split("&"); + if (hostInfo != null) { + if (hostInfo.startsWith("consoleurl")) { + String tokens[] = hostInfo.split("&"); - if(hostInfo.length() > 19 && hostInfo.indexOf('/', 19) > 19) { - host = hostInfo.substring(19, hostInfo.indexOf('/', 19)).trim(); - tunnelUrl = tokens[0].substring("consoleurl=".length()); - tunnelSession = tokens[1].split("=")[1]; - } else { - host = ""; + if (hostInfo.length() > 19 && hostInfo.indexOf('/', 19) > 19) { + host = hostInfo.substring(19, hostInfo.indexOf('/', 19)).trim(); + tunnelUrl = tokens[0].substring("consoleurl=".length()); + tunnelSession = tokens[1].split("=")[1]; + } else { + host = ""; + } + } else if (hostInfo.startsWith("instanceId")) { + host = hostInfo.substring(hostInfo.indexOf('=') + 1); } } else { host = hostInfo; @@ -338,11 +343,11 @@ public class ConsoleProxyServlet extends HttpServlet { } private String getEncryptorPassword() { - String key = _ms.getEncryptionKey(); - String iv = _ms.getEncryptionIV(); + String key = _ms.getEncryptionKey(); + String iv = _ms.getEncryptionIV(); - ConsoleProxyPasswordBasedEncryptor.KeyIVPair keyIvPair = new ConsoleProxyPasswordBasedEncryptor.KeyIVPair(key, iv); - return _gson.toJson(keyIvPair); + ConsoleProxyPasswordBasedEncryptor.KeyIVPair keyIvPair = new ConsoleProxyPasswordBasedEncryptor.KeyIVPair(key, iv); + return _gson.toJson(keyIvPair); } private String composeThumbnailUrl(String rootUrl, VirtualMachine vm, HostVO hostVo, int w, int h) { @@ -365,7 +370,7 @@ public class ConsoleProxyServlet extends HttpServlet { param.setClientHostPassword(sid); param.setClientTag(tag); param.setTicket(ticket); - if(parsedHostInfo.second() != null && parsedHostInfo.third() != null) { + if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) { param.setClientTunnelUrl(parsedHostInfo.second()); param.setClientTunnelSession(parsedHostInfo.third()); } @@ -374,7 +379,7 @@ public class ConsoleProxyServlet extends HttpServlet { + encryptor.encryptObject(ConsoleProxyClientParam.class, param)); sb.append("&w=").append(w).append("&h=").append(h).append("&key=0"); - if(s_logger.isDebugEnabled()) { + if (s_logger.isDebugEnabled()) { s_logger.debug("Compose thumbnail url: " + sb.toString()); } return sb.toString(); @@ -383,28 +388,48 @@ public class ConsoleProxyServlet extends HttpServlet { private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo) { StringBuffer sb = new StringBuffer(rootUrl); String host = hostVo.getPrivateIpAddress(); + String username = _ms.findDetail(hostVo.getId(), "username").getValue(); + String password = _ms.findDetail(hostVo.getId(), "password").getValue(); Pair portInfo = _ms.getVncPort(vm); - if(s_logger.isDebugEnabled()) + if (s_logger.isDebugEnabled()) s_logger.debug("Port info " + portInfo.first()); Ternary parsedHostInfo = parseHostInfo(portInfo.first()); + int port = -1; + String sid; + + if (portInfo.second() == -9) { + //for hyperv + port = 2179; + } else { + port = portInfo.second(); + } + sid = vm.getVncPassword(); UserVmDetailVO details = _userVmDetailsDao.findDetail(vm.getId(), "keyboard"); - String sid = vm.getVncPassword(); + String tag = vm.getUuid(); - String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, tag); + + String ticket = genAccessTicket(parsedHostInfo.first(), String.valueOf(port), sid, tag); ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword()); ConsoleProxyClientParam param = new ConsoleProxyClientParam(); param.setClientHostAddress(parsedHostInfo.first()); - param.setClientHostPort(portInfo.second()); + param.setClientHostPort(port); param.setClientHostPassword(sid); param.setClientTag(tag); param.setTicket(ticket); + if (details != null) { param.setLocale(details.getValue()); } - if(parsedHostInfo.second() != null && parsedHostInfo.third() != null) { + if (portInfo.second() == -9) { + //For Hyperv Clinet Host Address will send Instance id + param.setHypervHost(host); + param.setUsername(username); + param.setPassword(password); + } + if (parsedHostInfo.second() != null && parsedHostInfo.third() != null) { param.setClientTunnelUrl(parsedHostInfo.second()); param.setClientTunnelSession(parsedHostInfo.third()); } @@ -414,10 +439,10 @@ public class ConsoleProxyServlet extends HttpServlet { // for console access, we need guest OS type to help implement keyboard long guestOs = vm.getGuestOSId(); GuestOSVO guestOsVo = _ms.getGuestOs(guestOs); - if(guestOsVo.getCategoryId() == 6) + if (guestOsVo.getCategoryId() == 6) sb.append("&guest=windows"); - if(s_logger.isDebugEnabled()) { + if (s_logger.isDebugEnabled()) { s_logger.debug("Compose console url: " + sb.toString()); } return sb.toString(); @@ -434,7 +459,7 @@ public class ConsoleProxyServlet extends HttpServlet { Mac mac = Mac.getInstance("HmacSHA1"); long ts = normalizedHashTime.getTime(); - ts = ts/60000; // round up to 1 minute + ts = ts / 60000; // round up to 1 minute String secretKey = s_ms.getHashKey(); SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1"); @@ -445,7 +470,7 @@ public class ConsoleProxyServlet extends HttpServlet { byte[] encryptedBytes = mac.doFinal(); return Base64.encodeBase64String(encryptedBytes); - } catch(Exception e) { + } catch (Exception e) { s_logger.error("Unexpected exception ", e); } return ""; @@ -455,8 +480,8 @@ public class ConsoleProxyServlet extends HttpServlet { try { resp.setContentType("text/html"); resp.getWriter().print(content); - } catch(IOException e) { - if(s_logger.isInfoEnabled()) { + } catch (IOException e) { + if (s_logger.isInfoEnabled()) { s_logger.info("Client may already close the connection"); } } @@ -465,18 +490,18 @@ public class ConsoleProxyServlet extends HttpServlet { private boolean checkSessionPermision(HttpServletRequest req, long vmId, Account accountObj) { VirtualMachine vm = _vmMgr.findById(vmId); - if(vm == null) { + if (vm == null) { s_logger.debug("Console/thumbnail access denied. VM " + vmId + " does not exist in system any more"); return false; } // root admin can access anything - if(accountObj.getType() == Account.ACCOUNT_TYPE_ADMIN) + if (accountObj.getType() == Account.ACCOUNT_TYPE_ADMIN) return true; - switch(vm.getType()) + switch (vm.getType()) { - case User : + case User: try { _accountMgr.checkAccess(accountObj, null, true, vm); } catch (PermissionDeniedException ex) { @@ -485,8 +510,8 @@ public class ConsoleProxyServlet extends HttpServlet { s_logger.debug("VM access is denied. VM owner account " + vm.getAccountId() + " does not match the account id in session " + accountObj.getId() + " and caller is a normal user"); } - } else if(accountObj.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN || accountObj.getType() == Account.ACCOUNT_TYPE_READ_ONLY_ADMIN) { - if(s_logger.isDebugEnabled()) { + } else if (accountObj.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN || accountObj.getType() == Account.ACCOUNT_TYPE_READ_ONLY_ADMIN) { + if (s_logger.isDebugEnabled()) { s_logger.debug("VM access is denied. VM owner account " + vm.getAccountId() + " does not match the account id in session " + accountObj.getId() + " and the domain-admin caller does not manage the target domain"); } @@ -496,11 +521,11 @@ public class ConsoleProxyServlet extends HttpServlet { break; case DomainRouter: - case ConsoleProxy : + case ConsoleProxy: case SecondaryStorageVm: return false; - default : + default: s_logger.warn("Unrecoginized virtual machine type, deny access by default. type: " + vm.getType()); return false; } @@ -509,7 +534,7 @@ public class ConsoleProxyServlet extends HttpServlet { } private boolean isValidCmd(String cmd) { - if(cmd.equalsIgnoreCase("thumbnail") || cmd.equalsIgnoreCase("access") || cmd.equalsIgnoreCase("auth")) { + if (cmd.equalsIgnoreCase("thumbnail") || cmd.equalsIgnoreCase("access") || cmd.equalsIgnoreCase("auth")) { return true; } @@ -570,7 +595,6 @@ public class ConsoleProxyServlet extends HttpServlet { } } - // if api/secret key are passed to the parameters if ((signature == null) || (apiKey == null)) { if (s_logger.isDebugEnabled()) { @@ -593,7 +617,8 @@ public class ConsoleProxyServlet extends HttpServlet { Account account = userAcctPair.second(); if (!user.getState().equals(Account.State.enabled) || !account.getState().equals(Account.State.enabled)) { - s_logger.debug("disabled or locked user accessing the api, userid = " + user.getId() + "; name = " + user.getUsername() + "; state: " + user.getState() + "; accountState: " + account.getState()); + s_logger.debug("disabled or locked user accessing the api, userid = " + user.getId() + "; name = " + user.getUsername() + "; state: " + user.getState() + + "; accountState: " + account.getState()); return false; } @@ -617,10 +642,10 @@ public class ConsoleProxyServlet extends HttpServlet { s_logger.debug("User signature: " + signature + " is not equaled to computed signature: " + computedSignature); } - if(equalSig) { + if (equalSig) { requestParameters.put("userid", new Object[] {String.valueOf(user.getId())}); requestParameters.put("account", new Object[] {account.getAccountName()}); - requestParameters.put("accountobj", new Object[] { account }); + requestParameters.put("accountobj", new Object[] {account}); } return equalSig; } catch (Exception ex) { @@ -629,20 +654,32 @@ public class ConsoleProxyServlet extends HttpServlet { return false; } - public static final String escapeHTML(String content){ - if(content == null || content.isEmpty()) + public static final String escapeHTML(String content) { + if (content == null || content.isEmpty()) return content; StringBuffer sb = new StringBuffer(); for (int i = 0; i < content.length(); i++) { char c = content.charAt(i); switch (c) { - case '<': sb.append("<"); break; - case '>': sb.append(">"); break; - case '&': sb.append("&"); break; - case '"': sb.append("""); break; - case ' ': sb.append(" ");break; - default: sb.append(c); break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '&': + sb.append("&"); + break; + case '"': + sb.append("""); + break; + case ' ': + sb.append(" "); + break; + default: + sb.append(c); + break; } } return sb.toString(); diff --git a/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java b/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java index 2418de3832f..9cb523137ec 100755 --- a/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java +++ b/services/console-proxy-rdp/rdpconsole/src/main/java/common/BufferedImageCanvas.java @@ -30,7 +30,7 @@ public class BufferedImageCanvas extends Canvas { private static final long serialVersionUID = 1L; // Offline screen buffer - private BufferedImage offlineImage; + protected BufferedImage offlineImage; // Cached Graphics2D object for offline screen buffer private Graphics2D graphics; @@ -49,7 +49,7 @@ public class BufferedImageCanvas extends Canvas { } public void setCanvasSize(int width, int height) { - this.offlineImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + offlineImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); graphics = offlineImage.createGraphics(); setSize(offlineImage.getWidth(), offlineImage.getHeight()); @@ -76,4 +76,8 @@ public class BufferedImageCanvas extends Canvas { return graphics; } + public void updateFrameBuffer(int x, int y, int w, int h) { + //this method will be used to mark the dirty tiles + } + } diff --git a/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java b/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java index 788d3702670..f3a73d70dbd 100755 --- a/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java +++ b/services/console-proxy-rdp/rdpconsole/src/main/java/common/adapter/AwtCanvasAdapter.java @@ -20,13 +20,6 @@ import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; -import common.BitmapOrder; -import common.BitmapRectangle; -import common.BufferedImageCanvas; -import common.CopyRectOrder; -import common.OrderType; -import common.ScreenDescription; - import rdpclient.rdp.ServerBitmapUpdate; import streamer.BaseElement; import streamer.ByteBuffer; @@ -35,6 +28,12 @@ import streamer.Link; import streamer.Order; import streamer.Pipeline; import streamer.PipelineImpl; +import common.BitmapOrder; +import common.BitmapRectangle; +import common.BufferedImageCanvas; +import common.CopyRectOrder; +import common.OrderType; +import common.ScreenDescription; public class AwtCanvasAdapter extends BaseElement { @@ -48,6 +47,7 @@ public class AwtCanvasAdapter extends BaseElement { protected BufferedImageCanvas canvas; + @Override public String toString() { return "AwtRdpAdapter(" + id + ")"; } @@ -57,7 +57,7 @@ public class AwtCanvasAdapter extends BaseElement { if (verbose) System.out.println("[" + this + "] INFO: Data received: " + buf + "."); - Order order = (Order)buf.getOrder(); + Order order = buf.getOrder(); switch ((OrderType)order.type) { case BITMAP_UPDATE: @@ -77,13 +77,14 @@ public class AwtCanvasAdapter extends BaseElement { } private void handleCopyRect(CopyRectOrder order, ByteBuffer buf) { - // TODO Auto-generated method stub // Copy image canvas.getOfflineGraphics().copyArea(order.srcX, order.srcY, order.width, order.height, order.x - order.srcX, order.y - order.srcY); // Request update of repainted area + canvas.updateFrameBuffer(order.x, order.y, order.width, order.height); canvas.repaint(order.x, order.y, order.width, order.height); + } private void handleBitmap(BitmapOrder order, ByteBuffer buf) { @@ -137,6 +138,7 @@ public class AwtCanvasAdapter extends BaseElement { g.drawImage(rectImage, x, y, null); // Request update of repainted area + canvas.updateFrameBuffer(x, y, width, height); canvas.repaint(x, y, width, height); } diff --git a/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java b/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java index 969dfc2f928..1a613cb3252 100755 --- a/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java +++ b/services/console-proxy-rdp/rdpconsole/src/main/java/rdpclient/RdpClient.java @@ -63,6 +63,9 @@ import common.adapter.AwtCanvasAdapter; public class RdpClient extends PipelineImpl { + AwtMouseEventSource mouseEventSource = null; + AwtKeyEventSource keyEventSource = null; + /** * Name of last OneTimePacket in handshake sequence. */ @@ -211,7 +214,7 @@ public class RdpClient extends PipelineImpl { // Pre Connection Blob "pcb", - // Main (will be used after connection seq) or tpkt (to X224) + // Main (will be used after connection seq) or tpkt (to X224) "server_fastpath >tpkt", // SSL @@ -333,8 +336,8 @@ public class RdpClient extends PipelineImpl { // Main network // - AwtMouseEventSource mouseEventSource = new AwtMouseEventSource("mouse"); - AwtKeyEventSource keyEventSource = new AwtKeyEventSource("keyboard"); + mouseEventSource = new AwtMouseEventSource("mouse"); + keyEventSource = new AwtKeyEventSource("keyboard"); // Subscribe packet sender to various events canvas.addMouseListener(mouseEventSource); @@ -390,4 +393,12 @@ public class RdpClient extends PipelineImpl { link("client_x224_data_queue", "client_tpkt_queue", "client_tpkt_queue< queue"); } + + public AwtMouseEventSource getMouseEventSource() { + return mouseEventSource; + } + + public AwtKeyEventSource getKeyEventSource() { + return keyEventSource; + } } diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml index 114979025d8..32ee5901b80 100644 --- a/services/console-proxy/server/pom.xml +++ b/services/console-proxy/server/pom.xml @@ -44,6 +44,11 @@ cloud-utils ${project.version} + + rdpclient + cloudstack-service-console-proxy-rdpclient + ${project.version} + diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java index 2abce565856..68afae158cb 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxy.java @@ -35,11 +35,12 @@ import java.util.concurrent.Executor; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.xml.DOMConfigurator; -import com.cloud.consoleproxy.util.Logger; -import com.cloud.utils.PropertiesUtil; import com.google.gson.Gson; import com.sun.net.httpserver.HttpServer; +import com.cloud.consoleproxy.util.Logger; +import com.cloud.utils.PropertiesUtil; + /** * * ConsoleProxy, singleton class that manages overall activities in console proxy process. To make legacy code work, we still @@ -72,7 +73,7 @@ public class ConsoleProxy { static String factoryClzName; static boolean standaloneStart = false; - static String encryptorPassword = genDefaultEncryptorPassword(); + static String encryptorPassword = genDefaultEncryptorPassword(); private static String genDefaultEncryptorPassword() { try { @@ -193,11 +194,11 @@ public class ConsoleProxy { if(authMethod != null) { Object result; try { - result = authMethod.invoke(ConsoleProxy.context, - param.getClientHostAddress(), - String.valueOf(param.getClientHostPort()), - param.getClientTag(), - param.getClientHostPassword(), + result = authMethod.invoke(ConsoleProxy.context, + param.getClientHostAddress(), + String.valueOf(param.getClientHostPort()), + param.getClientTag(), + param.getClientHostPassword(), param.getTicket(), new Boolean(reauthentication)); } catch (IllegalAccessException e) { @@ -294,7 +295,7 @@ public class ConsoleProxy { } catch (FileNotFoundException e) { s_logger.info("Ignoring file not found exception and using defaults"); } - } + } if (confs != null) { try { props.load(confs); @@ -403,7 +404,7 @@ public class ConsoleProxy { synchronized (connectionMap) { viewer = connectionMap.get(clientKey); if (viewer == null) { - viewer = new ConsoleProxyVncClient(); + viewer = getClient(param); viewer.initClient(param); connectionMap.put(clientKey, viewer); s_logger.info("Added viewer object " + viewer); @@ -438,7 +439,7 @@ public class ConsoleProxy { ConsoleProxyClient viewer = connectionMap.get(clientKey); if (viewer == null) { authenticationExternally(param); - viewer = new ConsoleProxyVncClient(); + viewer = getClient(param); viewer.initClient(param); connectionMap.put(clientKey, viewer); @@ -474,7 +475,15 @@ public class ConsoleProxy { return viewer; } } - + + private static ConsoleProxyClient getClient(ConsoleProxyClientParam param) { + if (param.getHypervHost() != null) { + return new ConsoleProxyRdpClient(); + } else { + return new ConsoleProxyVncClient(); + } + } + public static void removeViewer(ConsoleProxyClient viewer) { synchronized (connectionMap) { for(Map.Entry entry : connectionMap.entrySet()) { @@ -504,8 +513,8 @@ public class ConsoleProxy { return authenticateConsoleAccess(param, true); } - public static String getEncryptorPassword() { - return encryptorPassword; + public static String getEncryptorPassword() { + return encryptorPassword; } public static void setEncryptorPassword(String password) { @@ -513,7 +522,8 @@ public class ConsoleProxy { } static class ThreadExecutor implements Executor { - public void execute(Runnable r) { + @Override + public void execute(Runnable r) { new Thread(r).start(); } } diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java index f4d3a179724..552471f8c08 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java @@ -26,17 +26,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.cloud.consoleproxy.util.Logger; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import com.cloud.consoleproxy.util.Logger; + public class ConsoleProxyAjaxHandler implements HttpHandler { private static final Logger s_logger = Logger.getLogger(ConsoleProxyAjaxHandler.class); public ConsoleProxyAjaxHandler() { } + @Override public void handle(HttpExchange t) throws IOException { try { if(s_logger.isTraceEnabled()) @@ -65,7 +67,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { String queries = t.getRequestURI().getQuery(); if(s_logger.isTraceEnabled()) s_logger.trace("Handle AJAX request: " + queries); - + Map queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries); String host = queryMap.get("host"); @@ -78,6 +80,9 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { String console_url = queryMap.get("consoleurl"); String console_host_session = queryMap.get("sessionref"); String vm_locale = queryMap.get("locale"); + String hypervHost = queryMap.get("hypervHost"); + String username = queryMap.get("username"); + String password = queryMap.get("password"); if(tag == null) tag = ""; @@ -87,7 +92,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { int port; - if(host == null || portStr == null || sid == null) + if(host == null || portStr == null || sid == null) throw new IllegalArgumentException(); try { @@ -126,6 +131,9 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { param.setClientTunnelUrl(console_url); param.setClientTunnelSession(console_host_session); param.setLocale(vm_locale); + param.setHypervHost(hypervHost); + param.setUsername(username); + param.setPassword(password); viewer = ConsoleProxy.getAjaxVncViewer(param, ajaxSessionIdStr); } catch(Exception e) { @@ -178,32 +186,31 @@ public class ConsoleProxyAjaxHandler implements HttpHandler { if(s_logger.isTraceEnabled()) s_logger.trace("Ajax request indicates client update"); - handleClientUpdate(t, viewer); } } } - private static String convertStreamToString(InputStream is, boolean closeStreamAfterRead) { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - try { - while ((line = reader.readLine()) != null) { - sb.append(line + "\n"); - } + private static String convertStreamToString(InputStream is, boolean closeStreamAfterRead) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line = null; + try { + while ((line = reader.readLine()) != null) { + sb.append(line + "\n"); + } } catch (IOException e) { s_logger.warn("Exception while reading request body: ", e); } finally { if(closeStreamAfterRead) { - try { - is.close(); - } catch (IOException e) { - } + try { + is.close(); + } catch (IOException e) { + } } - } - return sb.toString(); - } + } + return sb.toString(); + } private void sendResponse(HttpExchange t, String contentType, String response) throws IOException { Headers hds = t.getResponseHeaders(); diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java index 7041ff0b912..76b04c6a238 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyClientParam.java @@ -21,18 +21,21 @@ package com.cloud.consoleproxy; * Data object to store parameter info needed by client to connect to its host */ public class ConsoleProxyClientParam { - + private String clientHostAddress; - private int clientHostPort; + private int clientHostPort; private String clientHostPassword; private String clientTag; private String ticket; - + private String clientTunnelUrl; private String clientTunnelSession; private String locale; private String ajaxSessionId; - + private String hypervHost; + private String username; + private String password; + public ConsoleProxyClientParam() { clientHostPort = 0; } @@ -76,7 +79,7 @@ public class ConsoleProxyClientParam { public void setTicket(String ticket) { this.ticket = ticket; } - + public String getClientTunnelUrl() { return clientTunnelUrl; } @@ -92,9 +95,9 @@ public class ConsoleProxyClientParam { public void setClientTunnelSession(String clientTunnelSession) { this.clientTunnelSession = clientTunnelSession; } - + public String getAjaxSessionId() { - return this.ajaxSessionId; + return ajaxSessionId; } public void setAjaxSessionId(String ajaxSessionId) { @@ -102,7 +105,7 @@ public class ConsoleProxyClientParam { } public String getLocale() { - return this.locale; + return locale; } public void setLocale(String locale) { @@ -110,9 +113,33 @@ public class ConsoleProxyClientParam { } public String getClientMapKey() { - if(clientTag != null && !clientTag.isEmpty()) + if (clientTag != null && !clientTag.isEmpty()) return clientTag; - + return clientHostAddress + ":" + clientHostPort; } + + public void setHypervHost(String hypervHost) { + this.hypervHost = hypervHost; + } + + public String getHypervHost() { + return hypervHost; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } } diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java index 353137c5f71..0facd3f26c4 100644 --- a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java @@ -71,10 +71,16 @@ public class ConsoleProxyHttpHandlerHelper { map.put("ticket", param.getTicket()); if(param.getLocale() != null) map.put("locale", param.getLocale()); + if (param.getHypervHost() != null) + map.put("hypervHost", param.getHypervHost()); + if (param.getUsername() != null) + map.put("username", param.getUsername()); + if (param.getPassword() != null) + map.put("password", param.getPassword()); } } else { - // we no longer accept information from parameter other than token - guardUserInput(map); + // we no longer accept information from parameter other than token + guardUserInput(map); } return map; } @@ -88,5 +94,8 @@ public class ConsoleProxyHttpHandlerHelper { map.remove("sessionref"); map.remove("ticket"); map.remove("locale"); + map.remove("hypervHost"); + map.remove("username"); + map.remove("password"); } } diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java new file mode 100644 index 00000000000..73c00be86d2 --- /dev/null +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/ConsoleProxyRdpClient.java @@ -0,0 +1,318 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.consoleproxy; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.log4j.Logger; + +import rdpclient.RdpClient; +import streamer.Pipeline; +import streamer.PipelineImpl; +import streamer.SocketWrapper; +import streamer.apr.AprSocketWrapperImpl; +import streamer.ssl.SSLState; + +import com.cloud.consoleproxy.rdp.KeysymToKeycode; +import com.cloud.consoleproxy.rdp.RdpBufferedImageCanvas; +import com.cloud.consoleproxy.vnc.FrameBufferCanvas; + +import common.AwtKeyEventSource; +import common.AwtMouseEventSource; +import common.ScreenDescription; +import common.SizeChangeListener; + +public class ConsoleProxyRdpClient extends ConsoleProxyClientBase { + + private static final Logger s_logger = Logger.getLogger(ConsoleProxyRdpClient.class); + + private static final int SHIFT_KEY_MASK = 64; + private static final int CTRL_KEY_MASK = 128; + private static final int META_KEY_MASK = 256; + private static final int ALT_KEY_MASK = 512; + + private RdpClient _client; + private ScreenDescription _screen; + private SocketWrapper _socket = null; + private RdpBufferedImageCanvas _canvas = null; + + private Thread _worker; + private volatile boolean _workerDone = false; + + private int _lastModifierStates = 0; + + private AwtMouseEventSource _mouseEventSource = null; + private AwtKeyEventSource _keyEventSource = null; + + public RdpBufferedImageCanvas getCanvas() { + return _canvas; + } + + public void setCanvas(RdpBufferedImageCanvas canvas) { + _canvas = canvas; + } + + @Override + public void onClientConnected() { + // TODO Auto-generated method stub + } + + @Override + public void onClientClose() { + s_logger.info("Received client close indication. remove viewer from map."); + ConsoleProxy.removeViewer(this); + } + + @Override + public boolean isHostConnected() { + //FIXME + return true; + } + + @Override + public boolean isFrontEndAlive() { + if (_socket != null) { + if (_workerDone || System.currentTimeMillis() - getClientLastFrontEndActivityTime() > ConsoleProxy.VIEWER_LINGER_SECONDS * 1000) { + s_logger.info("Front end has been idle for too long"); + _socket.shutdown(); + return false; + } else { + return true; + } + } else { + return false; + } + } + + @Override + public void sendClientRawKeyboardEvent(InputEventType event, int code, int modifiers) { + if (_client == null) + return; + + updateFrontEndActivityTime(); + + KeyEvent keyEvent = map(event, code, modifiers); + switch (event) { + case KEY_DOWN: + _keyEventSource.keyPressed(keyEvent); + break; + + case KEY_UP: + _keyEventSource.keyReleased(keyEvent); + break; + + case KEY_PRESS: + break; + + default: + assert (false); + break; + } + } + + private KeyEvent map(InputEventType event, int code, int modifiers) { + int keycode = KeysymToKeycode.getKeycode(code); + char keyChar = (char)keycode; + + KeyEvent keyEvent = null; + int modifier = mapModifier(modifiers); + + switch (event) { + case KEY_DOWN: + keyEvent = new KeyEvent(_canvas, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), modifier, keycode, keyChar); + break; + + case KEY_UP: + keyEvent = new KeyEvent(_canvas, KeyEvent.KEY_RELEASED, System.currentTimeMillis(), modifier, keycode, keyChar); + break; + + case KEY_PRESS: + break; + + default: + assert (false); + break; + } + return keyEvent; + } + + @Override + public void sendClientMouseEvent(InputEventType event, int x, int y, int code, int modifiers) { + if (_client == null) + return; + updateFrontEndActivityTime(); + + int mousecode = mapMouseButton(code); + int modifier = mapMouseModifier(code, modifiers); + + /*if (event == InputEventType.MOUSE_DOWN) { + _mouseEventSource.mousePressed(new MouseEvent(_canvas, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), modifier, x, y, 1, false, mousecode)); + } + + if (event == InputEventType.MOUSE_UP) { + _mouseEventSource.mouseReleased((new MouseEvent(_canvas, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), modifier, x, y, 1, false, mousecode))); + } + + if (event == InputEventType.MOUSE_DBLCLICK) { + _mouseEventSource.mouseReleased((new MouseEvent(_canvas, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), modifier, x, y, 2, false, mousecode))); + }*/ + } + + public int mapMouseModifier(int code, int modifiers) { + int mod = mapModifier(modifiers); + switch (code) { + case 0: + return mod = mod | MouseEvent.BUTTON1_DOWN_MASK; + case 2: + return mod = mod | MouseEvent.BUTTON3_DOWN_MASK; + default: + } + return mod; + } + + private int mapModifier(int modifiers) { + int mod = 0; + if ((modifiers & SHIFT_KEY_MASK) != (_lastModifierStates & SHIFT_KEY_MASK)) { + if ((modifiers & SHIFT_KEY_MASK) != 0) + mod = mod | InputEvent.SHIFT_DOWN_MASK; + } + + if ((modifiers & CTRL_KEY_MASK) != (_lastModifierStates & CTRL_KEY_MASK)) { + if ((modifiers & CTRL_KEY_MASK) != 0) + mod = mod | InputEvent.CTRL_DOWN_MASK; + } + + if ((modifiers & META_KEY_MASK) != (_lastModifierStates & META_KEY_MASK)) { + if ((modifiers & META_KEY_MASK) != 0) + mod = mod | InputEvent.META_DOWN_MASK; + } + + if ((modifiers & ALT_KEY_MASK) != (_lastModifierStates & ALT_KEY_MASK)) { + if ((modifiers & ALT_KEY_MASK) != 0) + mod = mod | InputEvent.ALT_DOWN_MASK; + } + _lastModifierStates = mod; + return mod; + } + + public int mapMouseButton(int code) { + switch (code) { + case 0: + return MouseEvent.BUTTON1; + case 2: + return MouseEvent.BUTTON3; + default: + return MouseEvent.BUTTON2; + } + + } + + @Override + public void initClient(final ConsoleProxyClientParam param) { + _workerDone = false; + + int canvasWidth = 1024; + int canvasHeight = 768; + setClientParam(param); + + final String host = param.getHypervHost(); + final String password = param.getPassword(); + final String instanceId = param.getClientHostAddress(); + final int port = param.getClientHostPort(); + + _screen = new ScreenDescription(); + _canvas = new RdpBufferedImageCanvas(this, canvasWidth, canvasHeight); + onFramebufferSizeChange(canvasWidth, canvasHeight); + + _screen.addSizeChangeListener(new SizeChangeListener() { + @Override + public void sizeChanged(int width, int height) { + if (_canvas != null) { + _canvas.setCanvasSize(width, height); + } + } + }); + + final SSLState sslState = new SSLState(); + + final String username = param.getUsername(); + String name = null; + String domain = null; + if (username.contains("\\")) { + String[] tokens = username.split("\\\\"); + name = tokens[1]; + domain = tokens[0]; + } else { + name = username; + domain = "Workgroup"; + } + + _client = new RdpClient("client", host, domain, name, password, instanceId, _screen, _canvas, + sslState); + + _mouseEventSource = _client.getMouseEventSource(); + _keyEventSource = _client.getKeyEventSource(); + + _worker = new Thread(new Runnable() { + @Override + public void run() { + _socket = new AprSocketWrapperImpl("socket", sslState); + Pipeline pipeline = new PipelineImpl("Client"); + pipeline.add(_socket, _client); + pipeline.link("socket", _client.getId(), "socket"); + pipeline.validate(); + + InetSocketAddress address = new InetSocketAddress(host, port); + ConsoleProxy.ensureRoute(host); + + try { + // Connect socket to remote server and run main loop(s) + _socket.connect(address); + } catch (IOException e) { + e.printStackTrace(); + } finally { + shutdown(); + } + + s_logger.info("Receiver thread stopped."); + _workerDone = true; + } + }); + _worker.setDaemon(true); + _worker.start(); + } + + @Override + public void closeClient() { + _workerDone = true; + shutdown(); + } + + @Override + protected FrameBufferCanvas getFrameBufferCavas() { + return _canvas; + } + + protected void shutdown() { + if (_socket != null) + _socket.shutdown(); + } +} diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java new file mode 100644 index 00000000000..10282ad0af3 --- /dev/null +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/KeysymToKeycode.java @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.consoleproxy.rdp; + +import java.awt.event.KeyEvent; + +public class KeysymToKeycode { + + // this keymap is taken from http://openwonderland.googlecode.com/svn/trunk/modules/foundation/xremwin/src/classes/org/jdesktop/wonderland/modules/xremwin/client/KeycodeToKeysym.java + private final static int[][] map = { + /* XK_BackSpace */{0xFF08, KeyEvent.VK_BACK_SPACE}, + /* XK_Tab */{0xFF09, KeyEvent.VK_TAB}, + /* XK_Clear */{0xFF0B, KeyEvent.VK_CLEAR}, + /* XK_Return */{0xFF0D, KeyEvent.VK_ENTER}, + /* XK_Pause */{0xFF13, KeyEvent.VK_PAUSE}, + /* XK_Scroll_Lock */{0xFF14, KeyEvent.VK_SCROLL_LOCK}, + /* XK_Escape */{0xFF1B, KeyEvent.VK_ESCAPE}, + /* XK_Delete */{0xFFFF, KeyEvent.VK_DELETE}, + /* XK_Home */{0xFF50, KeyEvent.VK_HOME}, + /* XK_Left */{0xFF51, KeyEvent.VK_LEFT}, + /* XK_Up */{0xFF52, KeyEvent.VK_UP}, + /* XK_Right */{0xFF53, KeyEvent.VK_RIGHT}, + /* XK_Down */{0xFF54, KeyEvent.VK_DOWN}, + /* XK_Page_Up */{0xFF55, KeyEvent.VK_PAGE_UP}, + /* XK_Page_Down */{0xFF56, KeyEvent.VK_PAGE_DOWN}, + /* XK_End */{0xFF57, KeyEvent.VK_END}, + /* XK_Print */{0xFF61, KeyEvent.VK_PRINTSCREEN}, + /* XK_Insert */{0xFF63, KeyEvent.VK_INSERT}, + /* XK_Undo */{0xFF65, KeyEvent.VK_UNDO}, + /* XK_Find */{0xFF68, KeyEvent.VK_FIND}, + /* XK_Cancel */{0xFF69, KeyEvent.VK_CANCEL}, + /* XK_Help */{0xFF6A, KeyEvent.VK_HELP}, + /* XK_Mode_switch */{0xFF7E, KeyEvent.VK_MODECHANGE}, + /* XK_Num_Lock */{0xFF7F, KeyEvent.VK_NUM_LOCK}, + /* XK_F1 */{0xFFBE, KeyEvent.VK_F1}, + /* XK_F2 */{0xFFBF, KeyEvent.VK_F2}, + /* XK_F3 */{0xFFC0, KeyEvent.VK_F3}, + /* XK_F4 */{0xFFC1, KeyEvent.VK_F4}, + /* XK_F5 */{0xFFC2, KeyEvent.VK_F5}, + /* XK_F6 */{0xFFC3, KeyEvent.VK_F6}, + /* XK_F7 */{0xFFC4, KeyEvent.VK_F7}, + /* XK_F8 */{0xFFC5, KeyEvent.VK_F8}, + /* XK_F9 */{0xFFC6, KeyEvent.VK_F9}, + /* XK_F10 */{0xFFC7, KeyEvent.VK_F10}, + /* XK_F11 */{0xFFC8, KeyEvent.VK_F11}, + /* XK_F12 */{0xFFC9, KeyEvent.VK_F12}, + /* XK_F13 */{0xFFCA, KeyEvent.VK_F13}, + /* XK_F14 */{0xFFCB, KeyEvent.VK_F14}, + /* XK_F15 */{0xFFCC, KeyEvent.VK_F15}, + /* XK_F16 */{0xFFCD, KeyEvent.VK_F16}, + /* XK_F17 */{0xFFCE, KeyEvent.VK_F17}, + /* XK_F18 */{0xFFCF, KeyEvent.VK_F18}, + /* XK_F19 */{0xFFD0, KeyEvent.VK_F19}, + /* XK_F20 */{0xFFD1, KeyEvent.VK_F20}, + /* XK_F21 */{0xFFD2, KeyEvent.VK_F21}, + /* XK_F22 */{0xFFD3, KeyEvent.VK_F22}, + /* XK_F23 */{0xFFD4, KeyEvent.VK_F23}, + /* XK_F24 */{0xFFD5, KeyEvent.VK_F24}, + /* XK_Shift_L */{0xFFE1, KeyEvent.VK_SHIFT}, + /* XK_Control_L */{0xFFE3, KeyEvent.VK_CONTROL}, + /* XK_Caps_Lock */{0xFFE5, KeyEvent.VK_CAPS_LOCK}, + /* XK_Meta_L */{0xFFE7, KeyEvent.VK_META}, + /* XK_Alt_L */{0xFFE9, KeyEvent.VK_ALT}, + /* XK_a */{0x0061, KeyEvent.VK_A}, + /* XK_b */{0x0062, KeyEvent.VK_B}, + /* XK_c */{0x0063, KeyEvent.VK_C}, + /* XK_d */{0x0064, KeyEvent.VK_D}, + /* XK_e */{0x0065, KeyEvent.VK_E}, + /* XK_f */{0x0066, KeyEvent.VK_F}, + /* XK_g */{0x0067, KeyEvent.VK_G}, + /* XK_h */{0x0068, KeyEvent.VK_H}, + /* XK_i */{0x0069, KeyEvent.VK_I}, + /* XK_j */{0x006a, KeyEvent.VK_J}, + /* XK_k */{0x006b, KeyEvent.VK_K}, + /* XK_l */{0x006c, KeyEvent.VK_L}, + /* XK_m */{0x006d, KeyEvent.VK_M}, + /* XK_n */{0x006e, KeyEvent.VK_N}, + /* XK_o */{0x006f, KeyEvent.VK_O}, + /* XK_p */{0x0070, KeyEvent.VK_P}, + /* XK_q */{0x0071, KeyEvent.VK_Q}, + /* XK_r */{0x0072, KeyEvent.VK_R}, + /* XK_s */{0x0073, KeyEvent.VK_S}, + /* XK_t */{0x0074, KeyEvent.VK_T}, + /* XK_u */{0x0075, KeyEvent.VK_U}, + /* XK_v */{0x0076, KeyEvent.VK_V}, + /* XK_w */{0x0077, KeyEvent.VK_W}, + /* XK_x */{0x0078, KeyEvent.VK_X}, + /* XK_y */{0x0079, KeyEvent.VK_Y}, + /* XK_z */{0x007a, KeyEvent.VK_Z}, + }; + + public static int getKeycode(int keysym) { + for (int i = 0; i < (map.length - 1); i++) { + if (map[i][0] == keysym) { + return map[i][1]; + } + } + return keysym; + } + +} diff --git a/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java new file mode 100644 index 00000000000..6dabe056ac4 --- /dev/null +++ b/services/console-proxy/server/src/com/cloud/consoleproxy/rdp/RdpBufferedImageCanvas.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.consoleproxy.rdp; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.List; + +import com.cloud.consoleproxy.ConsoleProxyRdpClient; +import com.cloud.consoleproxy.util.ImageHelper; +import com.cloud.consoleproxy.util.TileInfo; +import com.cloud.consoleproxy.vnc.FrameBufferCanvas; + +import common.BufferedImageCanvas; + +public class RdpBufferedImageCanvas extends BufferedImageCanvas implements FrameBufferCanvas { + /** + * + */ + private static final long serialVersionUID = 1L; + + private final ConsoleProxyRdpClient _rdpClient; + + public RdpBufferedImageCanvas(ConsoleProxyRdpClient client, int width, int height) { + super(width, height); + _rdpClient = client; + } + + @Override + public Image getFrameBufferScaledImage(int width, int height) { + if (offlineImage != null) + return offlineImage.getScaledInstance(width, height, Image.SCALE_DEFAULT); + return null; + } + + @Override + public byte[] getFrameBufferJpeg() { + int width = offlineImage.getWidth(); + int height = offlineImage.getHeight(); + + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = bufferedImage.createGraphics(); + synchronized (offlineImage) { + g.drawImage(offlineImage, 0, 0, width, height, 0, 0, width, height, null); + g.dispose(); + } + + byte[] imgBits = null; + try { + imgBits = ImageHelper.jpegFromImage(bufferedImage); + } catch (IOException e) { + } + + return imgBits; + } + + @Override + public byte[] getTilesMergedJpeg(List tileList, int tileWidth, int tileHeight) { + int width = Math.max(tileWidth, tileWidth * tileList.size()); + + BufferedImage bufferedImage = new BufferedImage(width, tileHeight, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = bufferedImage.createGraphics(); + + synchronized (offlineImage) { + int i = 0; + for (TileInfo tile : tileList) { + Rectangle rc = tile.getTileRect(); + g.drawImage(offlineImage, i * tileWidth, 0, i * tileWidth + rc.width, rc.height, rc.x, rc.y, rc.x + rc.width, rc.y + rc.height, null); + i++; + } + } + + byte[] imgBits = null; + try { + imgBits = ImageHelper.jpegFromImage(bufferedImage); + } catch (IOException e) { + } + return imgBits; + } + + @Override + public void updateFrameBuffer(int x, int y, int w, int h) { + _rdpClient.onFramebufferUpdate(x, y, w, h); + } + +}