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 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 }