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