diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java index 0befe392b4e3..277c0e60d147 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java @@ -36,6 +36,8 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.eclipse.jetty.websocket.api.Session; @@ -43,8 +45,6 @@ import com.google.gson.Gson; import com.sun.net.httpserver.HttpServer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * @@ -58,6 +58,11 @@ public class ConsoleProxy { public static final int VIEWER_LINGER_SECONDS = 180; + // New: default and effective session timeout (milliseconds) honoured from consoleproxy.session.timeout + public static final long DEFAULT_SESSION_TIMEOUT_MILLIS = 300000; + public static volatile long sessionTimeoutMillis = DEFAULT_SESSION_TIMEOUT_MILLIS; + + public static Object context; // this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support @@ -82,7 +87,8 @@ public class ConsoleProxy { static boolean standaloneStart = false; static String encryptorPassword = "Dummy"; - static final String[] skipProperties = new String[]{"certificate", "cacertificate", "keystore_password", "privatekey"}; + static final String[] skipProperties = new String[] {"certificate", "cacertificate", "keystore_password", "privatekey"}; + static Set allowedSessions = new HashSet<>(); @@ -93,11 +99,13 @@ public static void addAllowedSession(String sessionUuid) { private static void configLog4j() { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); URL configUrl = loader.getResource("/conf/log4j-cloud.xml"); - if (configUrl == null) + if (configUrl == null) { configUrl = ClassLoader.getSystemResource("log4j-cloud.xml"); + } - if (configUrl == null) + if (configUrl == null) { configUrl = ClassLoader.getSystemResource("conf/log4j-cloud.xml"); + } if (configUrl != null) { try { @@ -109,12 +117,12 @@ private static void configLog4j() { try { File file = new File(configUrl.toURI()); + System.out.println("Log4j configuration from : " + file.getAbsolutePath()); Configurator.initialize(null, file.getAbsolutePath()); } catch (URISyntaxException e) { System.out.println("Unable to convert log4j configuration Url to URI"); } - // DOMConfigurator.configure(configUrl); } else { System.out.println("Configure log4j with default properties"); } @@ -122,20 +130,21 @@ private static void configLog4j() { private static void configProxy(Properties conf) { LOGGER.info("Configure console proxy..."); - for (Object key : conf.keySet()) { - LOGGER.info("Property " + (String)key + ": " + conf.getProperty((String)key)); - if (!ArrayUtils.contains(skipProperties, key)) { - LOGGER.info("Property " + (String)key + ": " + conf.getProperty((String)key)); + if (conf != null) { + for (Object key : conf.keySet()) { + if (!ArrayUtils.contains(skipProperties, key)) { + LOGGER.info("Property " + (String) key + ": " + conf.getProperty((String) key)); + } } } - String s = conf.getProperty("consoleproxy.httpListenPort"); + String s = conf != null ? conf.getProperty("consoleproxy.httpListenPort") : null; if (s != null) { httpListenPort = Integer.parseInt(s); LOGGER.info("Setting httpListenPort=" + s); } - s = conf.getProperty("premium"); + s = conf != null ? conf.getProperty("premium") : null; if (s != null && s.equalsIgnoreCase("true")) { LOGGER.info("Premium setting will override settings from consoleproxy.properties, listen at port 443"); httpListenPort = 443; @@ -144,36 +153,55 @@ private static void configProxy(Properties conf) { factoryClzName = ConsoleProxyBaseServerFactoryImpl.class.getName(); } - s = conf.getProperty("consoleproxy.httpCmdListenPort"); + s = conf != null ? conf.getProperty("consoleproxy.httpCmdListenPort") : null; if (s != null) { httpCmdListenPort = Integer.parseInt(s); LOGGER.info("Setting httpCmdListenPort=" + s); } - s = conf.getProperty("consoleproxy.reconnectMaxRetry"); + s = conf != null ? conf.getProperty("consoleproxy.reconnectMaxRetry") : null; if (s != null) { reconnectMaxRetry = Integer.parseInt(s); LOGGER.info("Setting reconnectMaxRetry=" + reconnectMaxRetry); } - s = conf.getProperty("consoleproxy.readTimeoutSeconds"); + s = conf != null ? conf.getProperty("consoleproxy.readTimeoutSeconds") : null; if (s != null) { readTimeoutSeconds = Integer.parseInt(s); LOGGER.info("Setting readTimeoutSeconds=" + readTimeoutSeconds); } - s = conf.getProperty("consoleproxy.defaultBufferSize"); + s = conf != null ? conf.getProperty("consoleproxy.defaultBufferSize") : null; if (s != null) { defaultBufferSize = Integer.parseInt(s); LOGGER.info("Setting defaultBufferSize=" + defaultBufferSize); } + + s = conf != null ? conf.getProperty("consoleproxy.session.timeout") : null; + if (s != null) { + try { + long value = Long.parseLong(s); + if (value <= 0) { + LOGGER.warn("consoleproxy.session.timeout={} is <= 0, using default {} ms", + value, DEFAULT_SESSION_TIMEOUT_MILLIS); + sessionTimeoutMillis = DEFAULT_SESSION_TIMEOUT_MILLIS; + } else { + sessionTimeoutMillis = value; + } + } catch (NumberFormatException e) { + LOGGER.warn("Invalid value for consoleproxy.session.timeout: {}, using default {} ms", + s, DEFAULT_SESSION_TIMEOUT_MILLIS, e); + sessionTimeoutMillis = DEFAULT_SESSION_TIMEOUT_MILLIS; + } + } + LOGGER.info("Effective consoleproxy.session.timeout={} ms", sessionTimeoutMillis); } public static ConsoleProxyServerFactory getHttpServerFactory() { try { Class clz = Class.forName(factoryClzName); try { - ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance(); + ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory) clz.newInstance(); factory.init(ConsoleProxy.ksBits, ConsoleProxy.ksPassword); return factory; } catch (InstantiationException e) { @@ -197,11 +225,11 @@ public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(Console authResult.setHost(param.getClientHostAddress()); authResult.setPort(param.getClientHostPort()); - if (org.apache.commons.lang3.StringUtils.isNotBlank(param.getExtraSecurityToken())) { + if (StringUtils.isNotBlank(param.getExtraSecurityToken())) { String extraToken = param.getExtraSecurityToken(); String clientProvidedToken = param.getClientProvidedExtraSecurityToken(); - LOGGER.debug(String.format("Extra security validation for the console access, provided %s " + - "to validate against %s", clientProvidedToken, extraToken)); + LOGGER.debug(String.format("Extra security validation for the console access, provided %s to validate against %s", + clientProvidedToken, extraToken)); if (!extraToken.equals(clientProvidedToken)) { LOGGER.error("The provided extra token does not match the expected value for this console endpoint"); @@ -233,20 +261,21 @@ public static ConsoleProxyAuthenticationResult authenticateConsoleAccess(Console Object result; try { result = - authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), param.getClientTag(), - param.getClientHostPassword(), param.getTicket(), reauthentication, param.getSessionUuid()); + authMethod.invoke(ConsoleProxy.context, param.getClientHostAddress(), String.valueOf(param.getClientHostPort()), + param.getClientTag(), param.getClientHostPassword(), param.getTicket(), reauthentication, + param.getSessionUuid()); } catch (IllegalAccessException e) { - LOGGER.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + param.getClientTag(), e); + LOGGER.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException for vm: " + param.getClientTag(), e); authResult.setSuccess(false); return authResult; } catch (InvocationTargetException e) { - LOGGER.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException " + " for vm: " + param.getClientTag(), e); + LOGGER.error("Unable to invoke authenticateConsoleAccess due to InvocationTargetException for vm: " + param.getClientTag(), e); authResult.setSuccess(false); return authResult; } if (result != null && result instanceof String) { - authResult = new Gson().fromJson((String)result, ConsoleProxyAuthenticationResult.class); + authResult = new Gson().fromJson((String) result, ConsoleProxyAuthenticationResult.class); } else { LOGGER.error("Invalid authentication return object " + result + " for vm: " + param.getClientTag() + ", decline the access"); authResult.setSuccess(false); @@ -286,7 +315,8 @@ public static void ensureRoute(String address) { } } - public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword, String password, Boolean isSourceIpCheckEnabled) { + public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword, + String password, Boolean isSourceIpCheckEnabled) { setEncryptorPassword(password); configLog4j(); LOGGER.info("Start console proxy with context"); @@ -326,34 +356,40 @@ public static void startWithContext(Properties conf, Object context, byte[] ksBi Properties props = new Properties(); if (confs == null) { final File file = PropertiesUtil.findConfigFile("consoleproxy.properties"); - if (file == null) + if (file == null) { LOGGER.info("Can't load consoleproxy.properties from classpath, will use default configuration"); - else + } else { try { confs = new FileInputStream(file); } catch (FileNotFoundException e) { LOGGER.info("Ignoring file not found exception and using defaults"); } + } } if (confs != null) { try { props.load(confs); + if (conf == null) { + conf = new Properties(); + } for (Object key : props.keySet()) { // give properties passed via context high priority, treat properties from consoleproxy.properties // as default values - if (conf.get(key) == null) + if (conf.get(key) == null) { conf.put(key, props.get(key)); + } } } catch (Exception e) { LOGGER.error(e.toString(), e); + } finally { + try { + confs.close(); + } catch (IOException e) { + LOGGER.error("Failed to close consoleproxy.properties : " + e.toString(), e); + } } } - try { - confs.close(); - } catch (IOException e) { - LOGGER.error("Failed to close consolepropxy.properties : " + e.toString(), e); - } start(conf); } @@ -395,6 +431,7 @@ private static void startupHttpMain() { System.exit(1); } + HttpServer server = factory.createHttpServerInstance(httpListenPort); server.createContext("/getscreen", new ConsoleProxyThumbnailHandler()); server.createContext("/resource/", new ConsoleProxyResourceHandler()); @@ -403,15 +440,18 @@ private static void startupHttpMain() { server.setExecutor(new ThreadExecutor()); // creates a default executor server.start(); + ConsoleProxyNoVNCServer noVNCServer = getNoVNCServer(); noVNCServer.start(); + } catch (Exception e) { LOGGER.error(e.getMessage(), e); System.exit(1); } } + private static ConsoleProxyNoVNCServer getNoVNCServer() { int vncPort = ConsoleProxyNoVNCServer.getVNCPort(); return vncPort == ConsoleProxyNoVNCServer.WSS_PORT ? @@ -474,8 +514,8 @@ public static ConsoleProxyClient getVncViewer(ConsoleProxyClientParam param) thr LOGGER.info("The rfb thread died, reinitializing the viewer " + viewer); viewer.initClient(param); } else if (!param.getClientHostPassword().equals(viewer.getClientHostPassword())) { - LOGGER.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() + ", sid in request: " + - param.getClientHostPassword()); + LOGGER.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.getClientHostPassword() + + ", sid in request: " + param.getClientHostPassword()); viewer.initClient(param); } } @@ -484,8 +524,9 @@ public static ConsoleProxyClient getVncViewer(ConsoleProxyClientParam param) thr ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); String loadInfo = statsCollector.getStatsReport(); reportLoadInfo(loadInfo); - if (LOGGER.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Report load change : " + loadInfo); + } } return viewer; @@ -509,13 +550,15 @@ public static ConsoleProxyClient getAjaxVncViewer(ConsoleProxyClientParam param, // protected against malicious attack by modifying URL content if (ajaxSession != null) { long ajaxSessionIdFromUrl = Long.parseLong(ajaxSession); - if (ajaxSessionIdFromUrl != viewer.getAjaxSessionId()) + if (ajaxSessionIdFromUrl != viewer.getAjaxSessionId()) { throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": modified AJAX session id"); + } } - if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() || - !param.getClientHostPassword().equals(viewer.getClientHostPassword())) + if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() + || !param.getClientHostPassword().equals(viewer.getClientHostPassword())) { throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); + } if (!viewer.isFrontEndAlive()) { @@ -529,8 +572,9 @@ public static ConsoleProxyClient getAjaxVncViewer(ConsoleProxyClientParam param, ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); String loadInfo = statsCollector.getStatsReport(); reportLoadInfo(loadInfo); - if (LOGGER.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Report load change : " + loadInfo); + } } return viewer; } @@ -566,9 +610,11 @@ public static void authenticationExternally(ConsoleProxyClientParam param) throw ConsoleProxyAuthenticationResult authResult = authenticateConsoleAccess(param, false); if (authResult == null || !authResult.isSuccess()) { - LOGGER.warn("External authenticator failed authentication request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword()); + LOGGER.warn("External authenticator failed authentication request for vm " + param.getClientTag() + + " with sid " + param.getClientHostPassword()); - throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword()); + throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + + " with sid " + param.getClientHostPassword()); } } @@ -596,9 +642,11 @@ public void execute(Runnable r) { } public static ConsoleProxyNoVncClient getNoVncViewer(ConsoleProxyClientParam param, String ajaxSession, - Session session) throws AuthenticationException { + Session session) throws AuthenticationException { boolean reportLoadChange = false; String clientKey = param.getClientMapKey(); + LOGGER.debug("Getting NoVNC viewer for {}. Client tag: {}. session UUID: {}", + clientKey, param.getClientTag(), param.getSessionUuid()); synchronized (connectionMap) { ConsoleProxyClient viewer = connectionMap.get(clientKey); if (viewer == null || viewer.getClass() != ConsoleProxyNoVncClient.class) { @@ -609,19 +657,20 @@ public static ConsoleProxyNoVncClient getNoVncViewer(ConsoleProxyClientParam par connectionMap.put(clientKey, viewer); reportLoadChange = true; } else { - if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() || - !param.getClientHostPassword().equals(viewer.getClientHostPassword())) + if (param.getClientHostPassword() == null || param.getClientHostPassword().isEmpty() + || !param.getClientHostPassword().equals(viewer.getClientHostPassword())) { throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); + } try { authenticationExternally(param); } catch (Exception e) { - LOGGER.error("Authentication failed for param: " + param); + LOGGER.error("Authentication failed for param: {}", param, e); return null; } LOGGER.info("Initializing new novnc client and disconnecting existing session"); try { - ((ConsoleProxyNoVncClient)viewer).getSession().disconnect(); + ((ConsoleProxyNoVncClient) viewer).getSession().disconnect(); } catch (IOException e) { LOGGER.error("Exception while disconnect session of novnc viewer object: " + viewer, e); } @@ -636,10 +685,11 @@ public static ConsoleProxyNoVncClient getNoVncViewer(ConsoleProxyClientParam par ConsoleProxyClientStatsCollector statsCollector = getStatsCollector(); String loadInfo = statsCollector.getStatsReport(); reportLoadInfo(loadInfo); - if (LOGGER.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Report load change : " + loadInfo); + } } - return (ConsoleProxyNoVncClient)viewer; + return (ConsoleProxyNoVncClient) viewer; } } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java index 0e8f576cf6db..e3a9b3794164 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java @@ -18,7 +18,7 @@ import java.io.File; import java.util.ArrayList; -import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -32,9 +32,8 @@ * management software */ public class ConsoleProxyGCThread extends Thread { - protected Logger logger = LogManager.getLogger(ConsoleProxyGCThread.class); + private static final Logger logger = LogManager.getLogger(ConsoleProxyGCThread.class); - private final static int MAX_SESSION_IDLE_SECONDS = 180; private final Map connMap; private final Set removedSessionsSet; @@ -46,21 +45,21 @@ public ConsoleProxyGCThread(Map connMap, Set } private void cleanupLogging() { - if (lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) + if (lastLogScan != 0 && System.currentTimeMillis() - lastLogScan < 3600000) { return; + } lastLogScan = System.currentTimeMillis(); File logDir = new File("./logs"); - File files[] = logDir.listFiles(); + File[] files = logDir.listFiles(); if (files != null) { for (File file : files) { if (System.currentTimeMillis() - file.lastModified() >= 86400000L) { try { file.delete(); } catch (Throwable e) { - logger.info("[ignored]" - + "failed to delete file: " + e.getLocalizedMessage()); + logger.info("[ignored] failed to delete file: " + e.getLocalizedMessage()); } } } @@ -78,21 +77,25 @@ public void run() { bReportLoad = false; if (logger.isDebugEnabled()) { - logger.debug(String.format("connMap=%s, removedSessions=%s", connMap, removedSessionsSet)); + logger.debug(String.format("ConsoleProxyGCThread loop: connMap=%s, removedSessions=%s", connMap, removedSessionsSet)); } - Set e = connMap.keySet(); - Iterator iterator = e.iterator(); - while (iterator.hasNext()) { - String key; + List keys; + synchronized (connMap) { + keys = new ArrayList<>(connMap.keySet()); + } + for (String key : keys) { ConsoleProxyClient client; synchronized (connMap) { - key = iterator.next(); client = connMap.get(key); } - long seconds_unused = (System.currentTimeMillis() - client.getClientLastFrontEndActivityTime()) / 1000; - if (seconds_unused < MAX_SESSION_IDLE_SECONDS) { + if (client == null) { + continue; + } + + long millisecondsUnused = System.currentTimeMillis() - client.getClientLastFrontEndActivityTime(); + if (millisecondsUnused < ConsoleProxy.sessionTimeoutMillis) { continue; } @@ -102,12 +105,13 @@ public void run() { } // close the server connection - logger.info("Dropping " + client + " which has not been used for " + seconds_unused + " seconds"); + logger.info("Dropping " + client + " which has not been used for " + millisecondsUnused + + " ms (configured timeout: " + ConsoleProxy.sessionTimeoutMillis + " ms)"); client.closeClient(); } if (bReportLoad || System.currentTimeMillis() - lastReportTick > 5000) { - // report load changes + // report load changes, including removed sessions since last report ConsoleProxyClientStatsCollector collector = new ConsoleProxyClientStatsCollector(connMap); collector.setRemovedSessions(new ArrayList<>(removedSessionsSet)); String loadInfo = collector.getStatsReport(); @@ -125,7 +129,7 @@ public void run() { try { Thread.sleep(5000); } catch (InterruptedException ex) { - logger.debug("[ignored] Console proxy was interrupted during GC."); + logger.debug("[ignored] Console proxy GC thread interrupted.", ex); } } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java index a9639d0b32e3..6b31b64ca6f1 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java @@ -40,8 +40,9 @@ @WebSocket public class ConsoleProxyNoVNCHandler extends WebSocketHandler { + private static final Logger logger = LogManager.getLogger(ConsoleProxyNoVNCHandler.class); + private ConsoleProxyNoVncClient viewer = null; - protected Logger logger = LogManager.getLogger(getClass()); public ConsoleProxyNoVNCHandler() { super(); @@ -73,6 +74,10 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques @OnWebSocketConnect public void onConnect(final Session session) throws IOException, InterruptedException { + session.setIdleTimeout(ConsoleProxy.sessionTimeoutMillis); + logger.debug("Set WebSocket idle timeout to {} ms for session from {}.", + ConsoleProxy.sessionTimeoutMillis, session.getRemoteAddress()); + String queries = session.getUpgradeRequest().getQueryString(); Map queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries); @@ -94,14 +99,14 @@ public void onConnect(final Session session) throws IOException, InterruptedExce String sessionUuid = queryMap.get("sessionUuid"); String clientIp = session.getRemoteAddress().getAddress().getHostAddress(); - if (tag == null) + if (tag == null) { tag = ""; + } - long ajaxSessionId = 0; int port; - - if (host == null || portStr == null || sid == null) - throw new IllegalArgumentException(); + if (host == null || portStr == null || sid == null) { + throw new IllegalArgumentException("Missing required console connection parameters"); + } try { port = Integer.parseInt(portStr); @@ -112,7 +117,7 @@ public void onConnect(final Session session) throws IOException, InterruptedExce if (ajaxSessionIdStr != null) { try { - ajaxSessionId = Long.parseLong(ajaxSessionIdStr); + Long.parseLong(ajaxSessionIdStr); } catch (NumberFormatException e) { logger.error("Invalid ajaxSessionId (sess) value in query string: {}. Expected a number.", ajaxSessionIdStr, e); throw new IllegalArgumentException(e); @@ -151,7 +156,7 @@ public void onConnect(final Session session) throws IOException, InterruptedExce viewer = ConsoleProxy.getNoVncViewer(param, ajaxSessionIdStr, session); logger.info("Viewer has been created successfully [session UUID: {}, client IP: {}].", sessionUuid, clientIp); } catch (Exception e) { - logger.error("Failed to create viewer [session UUID: {}, client IP: {}] due to {}.", sessionUuid, clientIp, e.getMessage(), e); + logger.error("Failed to create viewer [session UUID: {}, client IP: {}].", sessionUuid, clientIp, e); return; } finally { if (viewer == null) { @@ -160,7 +165,7 @@ public void onConnect(final Session session) throws IOException, InterruptedExce } } - private boolean checkSessionSourceIp(final Session session, final String sourceIP, String sessionSourceIP) throws IOException { + private boolean checkSessionSourceIp(final Session session, final String sourceIP, final String sessionSourceIP) throws IOException { logger.info("Verifying session source IP {} from WebSocket connection request.", sessionSourceIP); if (ConsoleProxy.isSourceIpCheckEnabled && (sessionSourceIP == null || !sessionSourceIP.equals(sourceIP))) { logger.warn("Failed to access console as the source IP to request the console is {}.", sourceIP); @@ -174,7 +179,7 @@ private boolean checkSessionSourceIp(final Session session, final String sourceI @OnWebSocketClose public void onClose(Session session, int statusCode, String reason) throws IOException, InterruptedException { String sessionSourceIp = session.getRemoteAddress().getAddress().getHostAddress(); - logger.debug("Closing WebSocket session [source IP: {}, status code: {}].", sessionSourceIp, statusCode); + logger.debug("Closing WebSocket session [source IP: {}, status code: {}, reason: {}].", sessionSourceIp, statusCode, reason); if (viewer != null) { ConsoleProxy.removeViewer(viewer); } @@ -183,12 +188,21 @@ public void onClose(Session session, int statusCode, String reason) throws IOExc @OnWebSocketFrame public void onFrame(Frame f) throws IOException { + if (viewer == null) { + logger.debug("Ignoring WebSocket frame because viewer is not initialized yet."); + return; + } logger.trace("Sending client [ID: {}] frame of {} bytes.", viewer.getClientId(), f.getPayloadLength()); + viewer.updateFrontEndActivityTime(); viewer.sendClientFrame(f); } @OnWebSocketError public void onError(Throwable cause) { - logger.error("Error on WebSocket [client ID: {}, session UUID: {}].", cause, viewer.getClientId(), viewer.getSessionUuid()); + if (viewer != null) { + logger.error("Error on WebSocket [client ID: {}, session UUID: {}].", viewer.getClientId(), viewer.getSessionUuid(), cause); + } else { + logger.error("Error on WebSocket before viewer initialization.", cause); + } } }