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