/*
* Copyright (c) 1996, 2005, 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.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<>();
/**
* 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();
}
}
}
}
}