/* * 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.getInteger(name, def)); } // this should be a *private* method since it is privileged private static boolean getBoolean(String name) { return AccessController.doPrivileged( (PrivilegedAction) () -> Boolean.getBoolean(name)); } /** * Returns the value of the java.rmi.server.hostname property. */ private static String getHostnameProperty() { return AccessController.doPrivileged( (PrivilegedAction) () -> 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> 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 epList = localEndpoints.get(endpointKey); String localHost = resampleLocalHost(); if (epList == null) { /* * Create new endpoint list. */ ep = new TCPEndpoint(localHost, port, csf, ssf); epList = new LinkedList(); 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 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 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 allKnownTransports() { // Loop through local endpoints, getting the transport of each one. Set s; synchronized (localEndpoints) { // presize s to number of localEndpoints s = new HashSet(localEndpoints.size()); for (LinkedList 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.isEmpty()) || (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(); } } } } }