1 /* 2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package sun.rmi.transport.proxy; 26 27 import java.io.*; 28 import java.net.*; 29 import java.security.*; 30 import java.util.*; 31 import java.rmi.server.LogStream; 32 import java.rmi.server.RMISocketFactory; 33 import sun.rmi.runtime.Log; 34 import sun.rmi.runtime.NewThreadAction; 35 import sun.security.action.GetBooleanAction; 36 import sun.security.action.GetLongAction; 37 import sun.security.action.GetPropertyAction; 38 39 /** 40 * RMIMasterSocketFactory attempts to create a socket connection to the 41 * specified host using successively less efficient mechanisms 42 * until one succeeds. If the host is successfully connected to, 43 * the factory for the successful mechanism is stored in an internal 44 * hash table keyed by the host name, so that future attempts to 45 * connect to the same host will automatically use the same 46 * mechanism. 47 */ 48 @SuppressWarnings("deprecation") 49 public class RMIMasterSocketFactory extends RMISocketFactory { 50 51 /** "proxy" package log level */ 52 static int logLevel = LogStream.parseLevel(getLogLevel()); 53 54 private static String getLogLevel() { 55 return java.security.AccessController.doPrivileged( 56 new sun.security.action.GetPropertyAction("sun.rmi.transport.proxy.logLevel")); 57 } 58 59 /* proxy package log */ 60 static final Log proxyLog = 61 Log.getLog("sun.rmi.transport.tcp.proxy", 62 "transport", RMIMasterSocketFactory.logLevel); 63 64 /** timeout for attemping direct socket connections */ 65 private static long connectTimeout = getConnectTimeout(); 66 67 private static long getConnectTimeout() { 68 return java.security.AccessController.doPrivileged( 69 new GetLongAction("sun.rmi.transport.proxy.connectTimeout", 70 15000)).longValue(); // default: 15 seconds 71 } 72 73 /** whether to fallback to HTTP on general connect failures */ 74 private static final boolean eagerHttpFallback = 75 java.security.AccessController.doPrivileged(new GetBooleanAction( 76 "sun.rmi.transport.proxy.eagerHttpFallback")).booleanValue(); 77 78 /** table of hosts successfully connected to and the factory used */ 79 private Hashtable<String, RMISocketFactory> successTable = 80 new Hashtable<>(); 81 82 /** maximum number of hosts to remember successful connection to */ 83 private static final int MaxRememberedHosts = 64; 84 85 /** list of the hosts in successTable in initial connection order */ 86 private Vector<String> hostList = new Vector<>(MaxRememberedHosts); 87 88 /** default factory for initial use for direct socket connection */ 89 protected RMISocketFactory initialFactory = new RMIDirectSocketFactory(); 90 91 /** ordered list of factories to try as alternate connection 92 * mechanisms if a direct socket connections fails */ 93 protected Vector<RMISocketFactory> altFactoryList; 94 95 /** 96 * Create a RMIMasterSocketFactory object. Establish order of 97 * connection mechanisms to attempt on createSocket, if a direct 98 * socket connection fails. 99 */ 100 public RMIMasterSocketFactory() { 101 altFactoryList = new Vector<>(2); 102 boolean setFactories = false; 103 104 try { 105 String proxyHost; 106 proxyHost = java.security.AccessController.doPrivileged( 107 new GetPropertyAction("http.proxyHost")); 108 109 if (proxyHost == null) 110 proxyHost = java.security.AccessController.doPrivileged( 111 new GetPropertyAction("proxyHost")); 112 113 boolean disable = java.security.AccessController.doPrivileged( 114 new GetPropertyAction("java.rmi.server.disableHttp", "true")) 115 .equalsIgnoreCase("true"); 116 117 if (!disable && proxyHost != null && proxyHost.length() > 0) { 118 setFactories = true; 119 } 120 } catch (Exception e) { 121 // unable to obtain the properties, so use the default behavior. 122 } 123 124 if (setFactories) { 125 altFactoryList.addElement(new RMIHttpToPortSocketFactory()); 126 altFactoryList.addElement(new RMIHttpToCGISocketFactory()); 127 } 128 } 129 130 /** 131 * Create a new client socket. If we remember connecting to this host 132 * successfully before, then use the same factory again. Otherwise, 133 * try using a direct socket connection and then the alternate factories 134 * in the order specified in altFactoryList. 135 */ 136 public Socket createSocket(String host, int port) 137 throws IOException 138 { 139 if (proxyLog.isLoggable(Log.BRIEF)) { 140 proxyLog.log(Log.BRIEF, "host: " + host + ", port: " + port); 141 } 142 143 /* 144 * If we don't have any alternate factories to consult, short circuit 145 * the fallback procedure and delegate to the initial factory. 146 */ 147 if (altFactoryList.size() == 0) { 148 return initialFactory.createSocket(host, port); 149 } 150 151 RMISocketFactory factory; 152 153 /* 154 * If we remember successfully connecting to this host before, 155 * use the same factory. 156 */ 157 factory = successTable.get(host); 158 if (factory != null) { 159 if (proxyLog.isLoggable(Log.BRIEF)) { 160 proxyLog.log(Log.BRIEF, 161 "previously successful factory found: " + factory); 162 } 163 return factory.createSocket(host, port); 164 } 165 166 /* 167 * Next, try a direct socket connection. Open socket in another 168 * thread and only wait for specified timeout, in case the socket 169 * would otherwise spend minutes trying an unreachable host. 170 */ 171 Socket initialSocket = null; 172 Socket fallbackSocket = null; 173 final AsyncConnector connector = 174 new AsyncConnector(initialFactory, host, port, 175 AccessController.getContext()); 176 // connection must be attempted with 177 // this thread's access control context 178 IOException initialFailure = null; 179 180 try { 181 synchronized (connector) { 182 183 Thread t = java.security.AccessController.doPrivileged( 184 new NewThreadAction(connector, "AsyncConnector", true)); 185 t.start(); 186 187 try { 188 long now = System.currentTimeMillis(); 189 long deadline = now + connectTimeout; 190 do { 191 connector.wait(deadline - now); 192 initialSocket = checkConnector(connector); 193 if (initialSocket != null) 194 break; 195 now = System.currentTimeMillis(); 196 } while (now < deadline); 197 } catch (InterruptedException e) { 198 throw new InterruptedIOException( 199 "interrupted while waiting for connector"); 200 } 201 } 202 203 // assume no route to host (for now) if no connection yet 204 if (initialSocket == null) 205 throw new NoRouteToHostException( 206 "connect timed out: " + host); 207 208 proxyLog.log(Log.BRIEF, "direct socket connection successful"); 209 210 return initialSocket; 211 212 } catch (UnknownHostException | NoRouteToHostException e) { 213 initialFailure = e; 214 } catch (SocketException e) { 215 if (eagerHttpFallback) { 216 initialFailure = e; 217 } else { 218 throw e; 219 } 220 } finally { 221 if (initialFailure != null) { 222 223 if (proxyLog.isLoggable(Log.BRIEF)) { 224 proxyLog.log(Log.BRIEF, 225 "direct socket connection failed: ", initialFailure); 226 } 227 228 // Finally, try any alternate connection mechanisms. 229 for (int i = 0; i < altFactoryList.size(); ++ i) { 230 factory = altFactoryList.elementAt(i); 231 if (proxyLog.isLoggable(Log.BRIEF)) { 232 proxyLog.log(Log.BRIEF, 233 "trying with factory: " + factory); 234 } 235 try (Socket testSocket = 236 factory.createSocket(host, port)) { 237 // For HTTP connections, the output (POST request) must 238 // be sent before we verify a successful connection. 239 // So, sacrifice a socket for the sake of testing... 240 // The following sequence should verify a successful 241 // HTTP connection if no IOException is thrown. 242 InputStream in = testSocket.getInputStream(); 243 int b = in.read(); // probably -1 for EOF... 244 } catch (IOException ex) { 245 if (proxyLog.isLoggable(Log.BRIEF)) { 246 proxyLog.log(Log.BRIEF, "factory failed: ", ex); 247 } 248 249 continue; 250 } 251 proxyLog.log(Log.BRIEF, "factory succeeded"); 252 253 // factory succeeded, open new socket for caller's use 254 try { 255 fallbackSocket = factory.createSocket(host, port); 256 } catch (IOException ex) { // if it fails 2nd time, 257 } // just give up 258 break; 259 } 260 } 261 } 262 263 synchronized (successTable) { 264 try { 265 // check once again to see if direct connection succeeded 266 synchronized (connector) { 267 initialSocket = checkConnector(connector); 268 } 269 if (initialSocket != null) { 270 // if we had made another one as well, clean it up... 271 if (fallbackSocket != null) 272 fallbackSocket.close(); 273 return initialSocket; 274 } 275 // if connector ever does get socket, it won't be used 276 connector.notUsed(); 277 } catch (UnknownHostException | NoRouteToHostException e) { 278 initialFailure = e; 279 } catch (SocketException e) { 280 if (eagerHttpFallback) { 281 initialFailure = e; 282 } else { 283 throw e; 284 } 285 } 286 // if we had found an alternate mechanism, go and use it 287 if (fallbackSocket != null) { 288 // remember this successful host/factory pair 289 rememberFactory(host, factory); 290 return fallbackSocket; 291 } 292 throw initialFailure; 293 } 294 } 295 296 /** 297 * Remember a successful factory for connecting to host. 298 * Currently, excess hosts are removed from the remembered list 299 * using a Least Recently Created strategy. 300 */ 301 void rememberFactory(String host, RMISocketFactory factory) { 302 synchronized (successTable) { 303 while (hostList.size() >= MaxRememberedHosts) { 304 successTable.remove(hostList.elementAt(0)); 305 hostList.removeElementAt(0); 306 } 307 hostList.addElement(host); 308 successTable.put(host, factory); 309 } 310 } 311 312 /** 313 * Check if an AsyncConnector succeeded. If not, return socket 314 * given to fall back to. 315 */ 316 Socket checkConnector(AsyncConnector connector) 317 throws IOException 318 { 319 Exception e = connector.getException(); 320 if (e != null) { 321 e.fillInStackTrace(); 322 /* 323 * The AsyncConnector implementation guaranteed that the exception 324 * will be either an IOException or a RuntimeException, and we can 325 * only throw one of those, so convince that compiler that it must 326 * be one of those. 327 */ 328 if (e instanceof IOException) { 329 throw (IOException) e; 330 } else if (e instanceof RuntimeException) { 331 throw (RuntimeException) e; 332 } else { 333 throw new Error("internal error: " + 334 "unexpected checked exception: " + e.toString()); 335 } 336 } 337 return connector.getSocket(); 338 } 339 340 /** 341 * Create a new server socket. 342 */ 343 public ServerSocket createServerSocket(int port) throws IOException { 344 //return new HttpAwareServerSocket(port); 345 return initialFactory.createServerSocket(port); 346 } 347 348 349 /** 350 * AsyncConnector is used by RMIMasterSocketFactory to attempt socket 351 * connections on a separate thread. This allows RMIMasterSocketFactory 352 * to control how long it will wait for the connection to succeed. 353 */ 354 private class AsyncConnector implements Runnable { 355 356 /** what factory to use to attempt connection */ 357 private RMISocketFactory factory; 358 359 /** the host to connect to */ 360 private String host; 361 362 /** the port to connect to */ 363 private int port; 364 365 /** access control context to attempt connection within */ 366 private AccessControlContext acc; 367 368 /** exception that occurred during connection, if any */ 369 private Exception exception = null; 370 371 /** the connected socket, if successful */ 372 private Socket socket = null; 373 374 /** socket should be closed after created, if ever */ 375 private boolean cleanUp = false; 376 377 /** 378 * Create a new asynchronous connector object. 379 */ 380 AsyncConnector(RMISocketFactory factory, String host, int port, 381 AccessControlContext acc) 382 { 383 this.factory = factory; 384 this.host = host; 385 this.port = port; 386 this.acc = acc; 387 SecurityManager security = System.getSecurityManager(); 388 if (security != null) { 389 security.checkConnect(host, port); 390 } 391 } 392 393 /** 394 * Attempt socket connection in separate thread. If successful, 395 * notify master waiting, 396 */ 397 public void run() { 398 try { 399 /* 400 * Using the privileges of the thread that wants to make the 401 * connection is tempting, but it will fail with applets with 402 * the current applet security manager because the applet 403 * network connection policy is not captured in the permission 404 * framework of the access control context we have. 405 * 406 * java.security.AccessController.beginPrivileged(acc); 407 */ 408 try { 409 Socket temp = factory.createSocket(host, port); 410 synchronized (this) { 411 socket = temp; 412 notify(); 413 } 414 rememberFactory(host, factory); 415 synchronized (this) { 416 if (cleanUp) 417 try { 418 socket.close(); 419 } catch (IOException e) { 420 } 421 } 422 } catch (Exception e) { 423 /* 424 * Note that the only exceptions which could actually have 425 * occurred here are IOException or RuntimeException. 426 */ 427 synchronized (this) { 428 exception = e; 429 notify(); 430 } 431 } 432 } finally { 433 /* 434 * See above comments for matching beginPrivileged() call that 435 * is also commented out. 436 * 437 * java.security.AccessController.endPrivileged(); 438 */ 439 } 440 } 441 442 /** 443 * Get exception that occurred during connection attempt, if any. 444 * In the current implementation, this is guaranteed to be either 445 * an IOException or a RuntimeException. 446 */ 447 private synchronized Exception getException() { 448 return exception; 449 } 450 451 /** 452 * Get successful socket, if any. 453 */ 454 private synchronized Socket getSocket() { 455 return socket; 456 } 457 458 /** 459 * Note that this connector's socket, if ever successfully created, 460 * will not be used, so it should be cleaned up quickly 461 */ 462 synchronized void notUsed() { 463 if (socket != null) { 464 try { 465 socket.close(); 466 } catch (IOException e) { 467 } 468 } 469 cleanUp = true; 470 } 471 } 472 }