| /* |
| * Copyright (c) 1996, 2012, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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.security.PrivilegedAction; |
| 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; |
| |
| /** |
| * 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( |
| (PrivilegedAction<Integer>) () -> Integer.getInteger(name, def)); |
| } |
| |
| // this should be a *private* method since it is privileged |
| private static boolean getBoolean(String name) { |
| return AccessController.doPrivileged( |
| (PrivilegedAction<Boolean>) () -> Boolean.getBoolean(name)); |
| } |
| |
| /** |
| * Returns the value of the java.rmi.server.hostname property. |
| */ |
| private static String getHostnameProperty() { |
| return AccessController.doPrivileged( |
| (PrivilegedAction<String>) () -> System.getProperty("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<>(); |
| |
| /** |
| * 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 | Exception mem) { |
| // don't quit if out of memory |
| // or 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(); |
| } |
| } |
| } |
| } |
| } |