diff options
Diffstat (limited to 'src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java')
-rw-r--r-- | src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java | 787 |
1 files changed, 787 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java new file mode 100644 index 000000000..835a7c7cd --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java @@ -0,0 +1,787 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.ConnectIOException; +import java.rmi.RemoteException; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.RMISocketFactory; +import java.security.AccessController; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.transport.Channel; +import sun.rmi.transport.Endpoint; +import sun.rmi.transport.Target; +import sun.rmi.transport.Transport; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetIntegerAction; +import sun.security.action.GetPropertyAction; + +/** + * TCPEndpoint represents some communication endpoint for an address + * space (VM). + * + * @author Ann Wollrath + */ +public class TCPEndpoint implements Endpoint { + /** IP address or host name */ + private String host; + /** port number */ + private int port; + /** custom client socket factory (null if not custom factory) */ + private final RMIClientSocketFactory csf; + /** custom server socket factory (null if not custom factory) */ + private final RMIServerSocketFactory ssf; + + /** if local, the port number to listen on */ + private int listenPort = -1; + /** if local, the transport object associated with this endpoint */ + private TCPTransport transport = null; + + /** the local host name */ + private static String localHost; + /** true if real local host name is known yet */ + private static boolean localHostKnown; + + // this should be a *private* method since it is privileged + private static int getInt(String name, int def) { + return AccessController.doPrivileged(new GetIntegerAction(name, def)); + } + + // this should be a *private* method since it is privileged + private static boolean getBoolean(String name) { + return AccessController.doPrivileged(new GetBooleanAction(name)); + } + + /** + * Returns the value of the java.rmi.server.hostname property. + */ + private static String getHostnameProperty() { + return AccessController.doPrivileged( + new GetPropertyAction("java.rmi.server.hostname")); + } + + /** + * Find host name of local machine. Property "java.rmi.server.hostname" + * is used if set, so server administrator can compensate for the possible + * inablility to get fully qualified host name from VM. + */ + static { + localHostKnown = true; + localHost = getHostnameProperty(); + + // could try querying CGI program here? + if (localHost == null) { + try { + InetAddress localAddr = InetAddress.getLocalHost(); + byte[] raw = localAddr.getAddress(); + if ((raw[0] == 127) && + (raw[1] == 0) && + (raw[2] == 0) && + (raw[3] == 1)) { + localHostKnown = false; + } + + /* if the user wishes to use a fully qualified domain + * name then attempt to find one. + */ + if (getBoolean("java.rmi.server.useLocalHostName")) { + localHost = FQDN.attemptFQDN(localAddr); + } else { + /* default to using ip addresses, names will + * work across seperate domains. + */ + localHost = localAddr.getHostAddress(); + } + } catch (Exception e) { + localHostKnown = false; + localHost = null; + } + } + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "localHostKnown = " + localHostKnown + + ", localHost = " + localHost); + } + } + + /** maps an endpoint key containing custom socket factories to + * their own unique endpoint */ + // TBD: should this be a weak hash table? + private static final + Map<TCPEndpoint,LinkedList<TCPEndpoint>> localEndpoints = + new HashMap<TCPEndpoint,LinkedList<TCPEndpoint>>(); + + /** + * Create an endpoint for a specified host and port. + * This should not be used by external classes to create endpoints + * for servers in this VM; use getLocalEndpoint instead. + */ + public TCPEndpoint(String host, int port) { + this(host, port, null, null); + } + + /** + * Create a custom socket factory endpoint for a specified host and port. + * This should not be used by external classes to create endpoints + * for servers in this VM; use getLocalEndpoint instead. + */ + public TCPEndpoint(String host, int port, RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + if (host == null) + host = ""; + this.host = host; + this.port = port; + this.csf = csf; + this.ssf = ssf; + } + + /** + * Get an endpoint for the local address space on specified port. + * If port number is 0, it returns shared default endpoint object + * whose host name and port may or may not have been determined. + */ + public static TCPEndpoint getLocalEndpoint(int port) { + return getLocalEndpoint(port, null, null); + } + + public static TCPEndpoint getLocalEndpoint(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + /* + * Find mapping for an endpoint key to the list of local unique + * endpoints for this client/server socket factory pair (perhaps + * null) for the specific port. + */ + TCPEndpoint ep = null; + + synchronized (localEndpoints) { + TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf); + LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey); + String localHost = resampleLocalHost(); + + if (epList == null) { + /* + * Create new endpoint list. + */ + ep = new TCPEndpoint(localHost, port, csf, ssf); + epList = new LinkedList<TCPEndpoint>(); + epList.add(ep); + ep.listenPort = port; + ep.transport = new TCPTransport(epList); + localEndpoints.put(endpointKey, epList); + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "created local endpoint for socket factory " + ssf + + " on port " + port); + } + } else { + synchronized (epList) { + ep = epList.getLast(); + String lastHost = ep.host; + int lastPort = ep.port; + TCPTransport lastTransport = ep.transport; + // assert (localHost == null ^ lastHost != null) + if (localHost != null && !localHost.equals(lastHost)) { + /* + * Hostname has been updated; add updated endpoint + * to list. + */ + if (lastPort != 0) { + /* + * Remove outdated endpoints only if the + * port has already been set on those endpoints. + */ + epList.clear(); + } + ep = new TCPEndpoint(localHost, lastPort, csf, ssf); + ep.listenPort = port; + ep.transport = lastTransport; + epList.add(ep); + } + } + } + } + + return ep; + } + + /** + * Resamples the local hostname and returns the possibly-updated + * local hostname. + */ + private static String resampleLocalHost() { + + String hostnameProperty = getHostnameProperty(); + + synchronized (localEndpoints) { + // assert(localHostKnown ^ (localHost == null)) + + if (hostnameProperty != null) { + if (!localHostKnown) { + /* + * If the local hostname is unknown, update ALL + * existing endpoints with the new hostname. + */ + setLocalHost(hostnameProperty); + } else if (!hostnameProperty.equals(localHost)) { + /* + * Only update the localHost field for reference + * in future endpoint creation. + */ + localHost = hostnameProperty; + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "updated local hostname to: " + localHost); + } + } + } + return localHost; + } + } + + /** + * Set the local host name, if currently unknown. + */ + static void setLocalHost(String host) { + // assert (host != null) + + synchronized (localEndpoints) { + /* + * If host is not known, change the host field of ALL + * the local endpoints. + */ + if (!localHostKnown) { + localHost = host; + localHostKnown = true; + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "local host set to " + host); + } + for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) + { + synchronized (epList) { + for (TCPEndpoint ep : epList) { + ep.host = host; + } + } + } + } + } + } + + /** + * Set the port of the (shared) default endpoint object. + * When first created, it contains port 0 because the transport + * hasn't tried to listen to get assigned a port, or if listening + * failed, a port hasn't been assigned from the server. + */ + static void setDefaultPort(int port, RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf); + + synchronized (localEndpoints) { + LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey); + + synchronized (epList) { + int size = epList.size(); + TCPEndpoint lastEp = epList.getLast(); + + for (TCPEndpoint ep : epList) { + ep.port = port; + } + if (size > 1) { + /* + * Remove all but the last element of the list + * (which contains the most recent hostname). + */ + epList.clear(); + epList.add(lastEp); + } + } + + /* + * Allow future exports to use the actual bound port + * explicitly (see 6269166). + */ + TCPEndpoint newEndpointKey = new TCPEndpoint(null, port, csf, ssf); + localEndpoints.put(newEndpointKey, epList); + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "default port for server socket factory " + ssf + + " and client socket factory " + csf + + " set to " + port); + } + } + } + + /** + * Returns transport for making connections to remote endpoints; + * (here, the default transport at port 0 is used). + */ + public Transport getOutboundTransport() { + TCPEndpoint localEndpoint = getLocalEndpoint(0, null, null); + return localEndpoint.transport; + } + + /** + * Returns the current list of known transports. + * The returned list is an unshared collection of Transports, + * including all transports which may have channels to remote + * endpoints. + */ + private static Collection<TCPTransport> allKnownTransports() { + // Loop through local endpoints, getting the transport of each one. + Set<TCPTransport> s; + synchronized (localEndpoints) { + // presize s to number of localEndpoints + s = new HashSet<TCPTransport>(localEndpoints.size()); + for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) { + /* + * Each local endpoint has its transport added to s. + * Note: the transport is the same for all endpoints + * in the list, so it is okay to pick any one of them. + */ + TCPEndpoint ep = epList.getFirst(); + s.add(ep.transport); + } + } + return s; + } + + /** + * Release idle outbound connections to reduce demand on I/O resources. + * All transports are asked to release excess connections. + */ + public static void shedConnectionCaches() { + for (TCPTransport transport : allKnownTransports()) { + transport.shedConnectionCaches(); + } + } + + /** + * Export the object to accept incoming calls. + */ + public void exportObject(Target target) throws RemoteException { + transport.exportObject(target); + } + + /** + * Returns a channel for this (remote) endpoint. + */ + public Channel getChannel() { + return getOutboundTransport().getChannel(this); + } + + /** + * Returns address for endpoint + */ + public String getHost() { + return host; + } + + /** + * Returns the port for this endpoint. If this endpoint was + * created as a server endpoint (using getLocalEndpoint) for a + * default/anonymous port and its inbound transport has started + * listening, this method returns (instead of zero) the actual + * bound port suitable for passing to clients. + **/ + public int getPort() { + return port; + } + + /** + * Returns the port that this endpoint's inbound transport listens + * on, if this endpoint was created as a server endpoint (using + * getLocalEndpoint). If this endpoint was created for the + * default/anonymous port, then this method returns zero even if + * the transport has started listening. + **/ + public int getListenPort() { + return listenPort; + } + + /** + * Returns the transport for incoming connections to this + * endpoint, if this endpoint was created as a server endpoint + * (using getLocalEndpoint). + **/ + public Transport getInboundTransport() { + return transport; + } + + /** + * Get the client socket factory associated with this endpoint. + */ + public RMIClientSocketFactory getClientSocketFactory() { + return csf; + } + + /** + * Get the server socket factory associated with this endpoint. + */ + public RMIServerSocketFactory getServerSocketFactory() { + return ssf; + } + + /** + * Return string representation for endpoint. + */ + public String toString() { + return "[" + host + ":" + port + + (ssf != null ? "," + ssf : "") + + (csf != null ? "," + csf : "") + + "]"; + } + + public int hashCode() { + return port; + } + + public boolean equals(Object obj) { + if ((obj != null) && (obj instanceof TCPEndpoint)) { + TCPEndpoint ep = (TCPEndpoint) obj; + if (port != ep.port || !host.equals(ep.host)) + return false; + if (((csf == null) ^ (ep.csf == null)) || + ((ssf == null) ^ (ep.ssf == null))) + return false; + /* + * Fix for 4254510: perform socket factory *class* equality check + * before socket factory equality check to avoid passing + * a potentially naughty socket factory to this endpoint's + * {client,server} socket factory equals method. + */ + if ((csf != null) && + !(csf.getClass() == ep.csf.getClass() && csf.equals(ep.csf))) + return false; + if ((ssf != null) && + !(ssf.getClass() == ep.ssf.getClass() && ssf.equals(ep.ssf))) + return false; + return true; + } else { + return false; + } + } + + /* codes for the self-describing formats of wire representation */ + private static final int FORMAT_HOST_PORT = 0; + private static final int FORMAT_HOST_PORT_FACTORY = 1; + + /** + * Write endpoint to output stream. + */ + public void write(ObjectOutput out) throws IOException { + if (csf == null) { + out.writeByte(FORMAT_HOST_PORT); + out.writeUTF(host); + out.writeInt(port); + } else { + out.writeByte(FORMAT_HOST_PORT_FACTORY); + out.writeUTF(host); + out.writeInt(port); + out.writeObject(csf); + } + } + + /** + * Get the endpoint from the input stream. + * @param in the input stream + * @exception IOException If id could not be read (due to stream failure) + */ + public static TCPEndpoint read(ObjectInput in) + throws IOException, ClassNotFoundException + { + String host; + int port; + RMIClientSocketFactory csf = null; + + byte format = in.readByte(); + switch (format) { + case FORMAT_HOST_PORT: + host = in.readUTF(); + port = in.readInt(); + break; + + case FORMAT_HOST_PORT_FACTORY: + host = in.readUTF(); + port = in.readInt(); + csf = (RMIClientSocketFactory) in.readObject(); + break; + + default: + throw new IOException("invalid endpoint format"); + } + return new TCPEndpoint(host, port, csf, null); + } + + /** + * Write endpoint to output stream in older format used by + * UnicastRef for JDK1.1 compatibility. + */ + public void writeHostPortFormat(DataOutput out) throws IOException { + if (csf != null) { + throw new InternalError("TCPEndpoint.writeHostPortFormat: " + + "called for endpoint with non-null socket factory"); + } + out.writeUTF(host); + out.writeInt(port); + } + + /** + * Create a new endpoint from input stream data. + * @param in the input stream + */ + public static TCPEndpoint readHostPortFormat(DataInput in) + throws IOException + { + String host = in.readUTF(); + int port = in.readInt(); + return new TCPEndpoint(host, port); + } + + private static RMISocketFactory chooseFactory() { + RMISocketFactory sf = RMISocketFactory.getSocketFactory(); + if (sf == null) { + sf = TCPTransport.defaultSocketFactory; + } + return sf; + } + + /** + * Open and return new client socket connection to endpoint. + */ + Socket newSocket() throws RemoteException { + if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { + TCPTransport.tcpLog.log(Log.VERBOSE, + "opening socket to " + this); + } + + Socket socket; + + try { + RMIClientSocketFactory clientFactory = csf; + if (clientFactory == null) { + clientFactory = chooseFactory(); + } + socket = clientFactory.createSocket(host, port); + + } catch (java.net.UnknownHostException e) { + throw new java.rmi.UnknownHostException( + "Unknown host: " + host, e); + } catch (java.net.ConnectException e) { + throw new java.rmi.ConnectException( + "Connection refused to host: " + host, e); + } catch (IOException e) { + // We might have simply run out of file descriptors + try { + TCPEndpoint.shedConnectionCaches(); + // REMIND: should we retry createSocket? + } catch (OutOfMemoryError mem) { + // don't quit if out of memory + } catch (Exception ex) { + // don't quit if shed fails non-catastrophically + } + + throw new ConnectIOException("Exception creating connection to: " + + host, e); + } + + // set socket to disable Nagle's algorithm (always send immediately) + // TBD: should this be left up to socket factory instead? + try { + socket.setTcpNoDelay(true); + } catch (Exception e) { + // if we fail to set this, ignore and proceed anyway + } + + // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs + try { + socket.setKeepAlive(true); + } catch (Exception e) { + // ignore and proceed + } + + return socket; + } + + /** + * Return new server socket to listen for connections on this endpoint. + */ + ServerSocket newServerSocket() throws IOException { + if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { + TCPTransport.tcpLog.log(Log.VERBOSE, + "creating server socket on " + this); + } + + RMIServerSocketFactory serverFactory = ssf; + if (serverFactory == null) { + serverFactory = chooseFactory(); + } + ServerSocket server = serverFactory.createServerSocket(listenPort); + + // if we listened on an anonymous port, set the default port + // (for this socket factory) + if (listenPort == 0) + setDefaultPort(server.getLocalPort(), csf, ssf); + + return server; + } + + /** + * The class FQDN encapsulates a routine that makes a best effort + * attempt to retrieve the fully qualified domain name of the local + * host. + * + * @author Laird Dornin + */ + private static class FQDN implements Runnable { + + /** + * strings in which we can store discovered fqdn + */ + private String reverseLookup; + + private String hostAddress; + + private FQDN(String hostAddress) { + this.hostAddress = hostAddress; + } + + /** + * Do our best to obtain a fully qualified hostname for the local + * host. Perform the following steps to get a localhostname: + * + * 1. InetAddress.getLocalHost().getHostName() - if contains + * '.' use as FQDN + * 2. if no '.' query name service for FQDN in a thread + * Note: We query the name service for an FQDN by creating + * an InetAddress via a stringified copy of the local ip + * address; this creates an InetAddress with a null hostname. + * Asking for the hostname of this InetAddress causes a name + * service lookup. + * + * 3. if name service takes too long to return, use ip address + * 4. if name service returns but response contains no '.' + * default to ipaddress. + */ + static String attemptFQDN(InetAddress localAddr) + throws java.net.UnknownHostException + { + + String hostName = localAddr.getHostName(); + + if (hostName.indexOf('.') < 0 ) { + + String hostAddress = localAddr.getHostAddress(); + FQDN f = new FQDN(hostAddress); + + int nameServiceTimeOut = + TCPEndpoint.getInt("sun.rmi.transport.tcp.localHostNameTimeOut", + 10000); + + try { + synchronized(f) { + f.getFQDN(); + + /* wait to obtain an FQDN */ + f.wait(nameServiceTimeOut); + } + } catch (InterruptedException e) { + /* propagate the exception to the caller */ + Thread.currentThread().interrupt(); + } + hostName = f.getHost(); + + if ((hostName == null) || (hostName.equals("")) + || (hostName.indexOf('.') < 0 )) { + + hostName = hostAddress; + } + } + return hostName; + } + + /** + * Method that that will start a thread to wait to retrieve a + * fully qualified domain name from a name service. The spawned + * thread may never return but we have marked it as a daemon so the vm + * will terminate appropriately. + */ + private void getFQDN() { + + /* FQDN finder will run in RMI threadgroup. */ + Thread t = AccessController.doPrivileged( + new NewThreadAction(FQDN.this, "FQDN Finder", true)); + t.start(); + } + + private synchronized String getHost() { + return reverseLookup; + } + + /** + * thread to query a name service for the fqdn of this host. + */ + public void run() { + + String name = null; + + try { + name = InetAddress.getByName(hostAddress).getHostName(); + } catch (java.net.UnknownHostException e) { + } finally { + synchronized(this) { + reverseLookup = name; + this.notify(); + } + } + } + } +} |