001: /*
002: * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package sun.rmi.transport.tcp;
026:
027: import java.io.DataInput;
028: import java.io.DataOutput;
029: import java.io.IOException;
030: import java.io.ObjectInput;
031: import java.io.ObjectOutput;
032: import java.net.InetAddress;
033: import java.net.ServerSocket;
034: import java.net.Socket;
035: import java.rmi.ConnectIOException;
036: import java.rmi.RemoteException;
037: import java.rmi.server.RMIClientSocketFactory;
038: import java.rmi.server.RMIServerSocketFactory;
039: import java.rmi.server.RMISocketFactory;
040: import java.security.AccessController;
041: import java.util.Collection;
042: import java.util.HashMap;
043: import java.util.HashSet;
044: import java.util.LinkedList;
045: import java.util.Map;
046: import java.util.Set;
047: import sun.rmi.runtime.Log;
048: import sun.rmi.runtime.NewThreadAction;
049: import sun.rmi.transport.Channel;
050: import sun.rmi.transport.Endpoint;
051: import sun.rmi.transport.Target;
052: import sun.rmi.transport.Transport;
053: import sun.security.action.GetBooleanAction;
054: import sun.security.action.GetIntegerAction;
055: import sun.security.action.GetPropertyAction;
056:
057: /**
058: * TCPEndpoint represents some communication endpoint for an address
059: * space (VM).
060: *
061: * @author Ann Wollrath
062: */
063: public class TCPEndpoint implements Endpoint {
064: /** IP address or host name */
065: private String host;
066: /** port number */
067: private int port;
068: /** custom client socket factory (null if not custom factory) */
069: private final RMIClientSocketFactory csf;
070: /** custom server socket factory (null if not custom factory) */
071: private final RMIServerSocketFactory ssf;
072:
073: /** if local, the port number to listen on */
074: private int listenPort = -1;
075: /** if local, the transport object associated with this endpoint */
076: private TCPTransport transport = null;
077:
078: /** the local host name */
079: private static String localHost;
080: /** true if real local host name is known yet */
081: private static boolean localHostKnown;
082:
083: // this should be a *private* method since it is privileged
084: private static int getInt(String name, int def) {
085: return AccessController.doPrivileged(new GetIntegerAction(name,
086: def));
087: }
088:
089: // this should be a *private* method since it is privileged
090: private static boolean getBoolean(String name) {
091: return AccessController
092: .doPrivileged(new GetBooleanAction(name));
093: }
094:
095: /**
096: * Returns the value of the java.rmi.server.hostname property.
097: */
098: private static String getHostnameProperty() {
099: return AccessController.doPrivileged(new GetPropertyAction(
100: "java.rmi.server.hostname"));
101: }
102:
103: /**
104: * Find host name of local machine. Property "java.rmi.server.hostname"
105: * is used if set, so server administrator can compensate for the possible
106: * inablility to get fully qualified host name from VM.
107: */
108: static {
109: localHostKnown = true;
110: localHost = getHostnameProperty();
111:
112: // could try querying CGI program here?
113: if (localHost == null) {
114: try {
115: InetAddress localAddr = InetAddress.getLocalHost();
116: byte[] raw = localAddr.getAddress();
117: if ((raw[0] == 127) && (raw[1] == 0) && (raw[2] == 0)
118: && (raw[3] == 1)) {
119: localHostKnown = false;
120: }
121:
122: /* if the user wishes to use a fully qualified domain
123: * name then attempt to find one.
124: */
125: if (getBoolean("java.rmi.server.useLocalHostName")) {
126: localHost = FQDN.attemptFQDN(localAddr);
127: } else {
128: /* default to using ip addresses, names will
129: * work across seperate domains.
130: */
131: localHost = localAddr.getHostAddress();
132: }
133: } catch (Exception e) {
134: localHostKnown = false;
135: localHost = null;
136: }
137: }
138:
139: if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
140: TCPTransport.tcpLog.log(Log.BRIEF, "localHostKnown = "
141: + localHostKnown + ", localHost = " + localHost);
142: }
143: }
144:
145: /** maps an endpoint key containing custom socket factories to
146: * their own unique endpoint */
147: // TBD: should this be a weak hash table?
148: private static final Map<TCPEndpoint, LinkedList<TCPEndpoint>> localEndpoints = new HashMap<TCPEndpoint, LinkedList<TCPEndpoint>>();
149:
150: /**
151: * Create an endpoint for a specified host and port.
152: * This should not be used by external classes to create endpoints
153: * for servers in this VM; use getLocalEndpoint instead.
154: */
155: public TCPEndpoint(String host, int port) {
156: this (host, port, null, null);
157: }
158:
159: /**
160: * Create a custom socket factory endpoint for a specified host and port.
161: * This should not be used by external classes to create endpoints
162: * for servers in this VM; use getLocalEndpoint instead.
163: */
164: public TCPEndpoint(String host, int port,
165: RMIClientSocketFactory csf, RMIServerSocketFactory ssf) {
166: if (host == null)
167: host = "";
168: this .host = host;
169: this .port = port;
170: this .csf = csf;
171: this .ssf = ssf;
172: }
173:
174: /**
175: * Get an endpoint for the local address space on specified port.
176: * If port number is 0, it returns shared default endpoint object
177: * whose host name and port may or may not have been determined.
178: */
179: public static TCPEndpoint getLocalEndpoint(int port) {
180: return getLocalEndpoint(port, null, null);
181: }
182:
183: public static TCPEndpoint getLocalEndpoint(int port,
184: RMIClientSocketFactory csf, RMIServerSocketFactory ssf) {
185: /*
186: * Find mapping for an endpoint key to the list of local unique
187: * endpoints for this client/server socket factory pair (perhaps
188: * null) for the specific port.
189: */
190: TCPEndpoint ep = null;
191:
192: synchronized (localEndpoints) {
193: TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf,
194: ssf);
195: LinkedList<TCPEndpoint> epList = localEndpoints
196: .get(endpointKey);
197: String localHost = resampleLocalHost();
198:
199: if (epList == null) {
200: /*
201: * Create new endpoint list.
202: */
203: ep = new TCPEndpoint(localHost, port, csf, ssf);
204: epList = new LinkedList<TCPEndpoint>();
205: epList.add(ep);
206: ep.listenPort = port;
207: ep.transport = new TCPTransport(epList);
208: localEndpoints.put(endpointKey, epList);
209:
210: if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
211: TCPTransport.tcpLog.log(Log.BRIEF,
212: "created local endpoint for socket factory "
213: + ssf + " on port " + port);
214: }
215: } else {
216: synchronized (epList) {
217: ep = epList.getLast();
218: String lastHost = ep.host;
219: int lastPort = ep.port;
220: TCPTransport lastTransport = ep.transport;
221: // assert (localHost == null ^ lastHost != null)
222: if (localHost != null
223: && !localHost.equals(lastHost)) {
224: /*
225: * Hostname has been updated; add updated endpoint
226: * to list.
227: */
228: if (lastPort != 0) {
229: /*
230: * Remove outdated endpoints only if the
231: * port has already been set on those endpoints.
232: */
233: epList.clear();
234: }
235: ep = new TCPEndpoint(localHost, lastPort, csf,
236: ssf);
237: ep.listenPort = port;
238: ep.transport = lastTransport;
239: epList.add(ep);
240: }
241: }
242: }
243: }
244:
245: return ep;
246: }
247:
248: /**
249: * Resamples the local hostname and returns the possibly-updated
250: * local hostname.
251: */
252: private static String resampleLocalHost() {
253:
254: String hostnameProperty = getHostnameProperty();
255:
256: synchronized (localEndpoints) {
257: // assert(localHostKnown ^ (localHost == null))
258:
259: if (hostnameProperty != null) {
260: if (!localHostKnown) {
261: /*
262: * If the local hostname is unknown, update ALL
263: * existing endpoints with the new hostname.
264: */
265: setLocalHost(hostnameProperty);
266: } else if (!hostnameProperty.equals(localHost)) {
267: /*
268: * Only update the localHost field for reference
269: * in future endpoint creation.
270: */
271: localHost = hostnameProperty;
272:
273: if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
274: TCPTransport.tcpLog.log(Log.BRIEF,
275: "updated local hostname to: "
276: + localHost);
277: }
278: }
279: }
280: return localHost;
281: }
282: }
283:
284: /**
285: * Set the local host name, if currently unknown.
286: */
287: static void setLocalHost(String host) {
288: // assert (host != null)
289:
290: synchronized (localEndpoints) {
291: /*
292: * If host is not known, change the host field of ALL
293: * the local endpoints.
294: */
295: if (!localHostKnown) {
296: localHost = host;
297: localHostKnown = true;
298:
299: if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
300: TCPTransport.tcpLog.log(Log.BRIEF,
301: "local host set to " + host);
302: }
303: for (LinkedList<TCPEndpoint> epList : localEndpoints
304: .values()) {
305: synchronized (epList) {
306: for (TCPEndpoint ep : epList) {
307: ep.host = host;
308: }
309: }
310: }
311: }
312: }
313: }
314:
315: /**
316: * Set the port of the (shared) default endpoint object.
317: * When first created, it contains port 0 because the transport
318: * hasn't tried to listen to get assigned a port, or if listening
319: * failed, a port hasn't been assigned from the server.
320: */
321: static void setDefaultPort(int port, RMIClientSocketFactory csf,
322: RMIServerSocketFactory ssf) {
323: TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf);
324:
325: synchronized (localEndpoints) {
326: LinkedList<TCPEndpoint> epList = localEndpoints
327: .get(endpointKey);
328:
329: synchronized (epList) {
330: int size = epList.size();
331: TCPEndpoint lastEp = epList.getLast();
332:
333: for (TCPEndpoint ep : epList) {
334: ep.port = port;
335: }
336: if (size > 1) {
337: /*
338: * Remove all but the last element of the list
339: * (which contains the most recent hostname).
340: */
341: epList.clear();
342: epList.add(lastEp);
343: }
344: }
345:
346: /*
347: * Allow future exports to use the actual bound port
348: * explicitly (see 6269166).
349: */
350: TCPEndpoint newEndpointKey = new TCPEndpoint(null, port,
351: csf, ssf);
352: localEndpoints.put(newEndpointKey, epList);
353:
354: if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
355: TCPTransport.tcpLog.log(Log.BRIEF,
356: "default port for server socket factory " + ssf
357: + " and client socket factory " + csf
358: + " set to " + port);
359: }
360: }
361: }
362:
363: /**
364: * Returns transport for making connections to remote endpoints;
365: * (here, the default transport at port 0 is used).
366: */
367: public Transport getOutboundTransport() {
368: TCPEndpoint localEndpoint = getLocalEndpoint(0, null, null);
369: return localEndpoint.transport;
370: }
371:
372: /**
373: * Returns the current list of known transports.
374: * The returned list is an unshared collection of Transports,
375: * including all transports which may have channels to remote
376: * endpoints.
377: */
378: private static Collection<TCPTransport> allKnownTransports() {
379: // Loop through local endpoints, getting the transport of each one.
380: Set<TCPTransport> s;
381: synchronized (localEndpoints) {
382: // presize s to number of localEndpoints
383: s = new HashSet<TCPTransport>(localEndpoints.size());
384: for (LinkedList<TCPEndpoint> epList : localEndpoints
385: .values()) {
386: /*
387: * Each local endpoint has its transport added to s.
388: * Note: the transport is the same for all endpoints
389: * in the list, so it is okay to pick any one of them.
390: */
391: TCPEndpoint ep = epList.getFirst();
392: s.add(ep.transport);
393: }
394: }
395: return s;
396: }
397:
398: /**
399: * Release idle outbound connections to reduce demand on I/O resources.
400: * All transports are asked to release excess connections.
401: */
402: public static void shedConnectionCaches() {
403: for (TCPTransport transport : allKnownTransports()) {
404: transport.shedConnectionCaches();
405: }
406: }
407:
408: /**
409: * Export the object to accept incoming calls.
410: */
411: public void exportObject(Target target) throws RemoteException {
412: transport.exportObject(target);
413: }
414:
415: /**
416: * Returns a channel for this (remote) endpoint.
417: */
418: public Channel getChannel() {
419: return getOutboundTransport().getChannel(this );
420: }
421:
422: /**
423: * Returns address for endpoint
424: */
425: public String getHost() {
426: return host;
427: }
428:
429: /**
430: * Returns the port for this endpoint. If this endpoint was
431: * created as a server endpoint (using getLocalEndpoint) for a
432: * default/anonymous port and its inbound transport has started
433: * listening, this method returns (instead of zero) the actual
434: * bound port suitable for passing to clients.
435: **/
436: public int getPort() {
437: return port;
438: }
439:
440: /**
441: * Returns the port that this endpoint's inbound transport listens
442: * on, if this endpoint was created as a server endpoint (using
443: * getLocalEndpoint). If this endpoint was created for the
444: * default/anonymous port, then this method returns zero even if
445: * the transport has started listening.
446: **/
447: public int getListenPort() {
448: return listenPort;
449: }
450:
451: /**
452: * Returns the transport for incoming connections to this
453: * endpoint, if this endpoint was created as a server endpoint
454: * (using getLocalEndpoint).
455: **/
456: public Transport getInboundTransport() {
457: return transport;
458: }
459:
460: /**
461: * Get the client socket factory associated with this endpoint.
462: */
463: public RMIClientSocketFactory getClientSocketFactory() {
464: return csf;
465: }
466:
467: /**
468: * Get the server socket factory associated with this endpoint.
469: */
470: public RMIServerSocketFactory getServerSocketFactory() {
471: return ssf;
472: }
473:
474: /**
475: * Return string representation for endpoint.
476: */
477: public String toString() {
478: return "[" + host + ":" + port + (ssf != null ? "," + ssf : "")
479: + (csf != null ? "," + csf : "") + "]";
480: }
481:
482: public int hashCode() {
483: return port;
484: }
485:
486: public boolean equals(Object obj) {
487: if ((obj != null) && (obj instanceof TCPEndpoint)) {
488: TCPEndpoint ep = (TCPEndpoint) obj;
489: if (port != ep.port || !host.equals(ep.host))
490: return false;
491: if (((csf == null) ^ (ep.csf == null))
492: || ((ssf == null) ^ (ep.ssf == null)))
493: return false;
494: /*
495: * Fix for 4254510: perform socket factory *class* equality check
496: * before socket factory equality check to avoid passing
497: * a potentially naughty socket factory to this endpoint's
498: * {client,server} socket factory equals method.
499: */
500: if ((csf != null)
501: && !(csf.getClass() == ep.csf.getClass() && csf
502: .equals(ep.csf)))
503: return false;
504: if ((ssf != null)
505: && !(ssf.getClass() == ep.ssf.getClass() && ssf
506: .equals(ep.ssf)))
507: return false;
508: return true;
509: } else {
510: return false;
511: }
512: }
513:
514: /* codes for the self-describing formats of wire representation */
515: private static final int FORMAT_HOST_PORT = 0;
516: private static final int FORMAT_HOST_PORT_FACTORY = 1;
517:
518: /**
519: * Write endpoint to output stream.
520: */
521: public void write(ObjectOutput out) throws IOException {
522: if (csf == null) {
523: out.writeByte(FORMAT_HOST_PORT);
524: out.writeUTF(host);
525: out.writeInt(port);
526: } else {
527: out.writeByte(FORMAT_HOST_PORT_FACTORY);
528: out.writeUTF(host);
529: out.writeInt(port);
530: out.writeObject(csf);
531: }
532: }
533:
534: /**
535: * Get the endpoint from the input stream.
536: * @param in the input stream
537: * @exception IOException If id could not be read (due to stream failure)
538: */
539: public static TCPEndpoint read(ObjectInput in) throws IOException,
540: ClassNotFoundException {
541: String host;
542: int port;
543: RMIClientSocketFactory csf = null;
544:
545: byte format = in.readByte();
546: switch (format) {
547: case FORMAT_HOST_PORT:
548: host = in.readUTF();
549: port = in.readInt();
550: break;
551:
552: case FORMAT_HOST_PORT_FACTORY:
553: host = in.readUTF();
554: port = in.readInt();
555: csf = (RMIClientSocketFactory) in.readObject();
556: break;
557:
558: default:
559: throw new IOException("invalid endpoint format");
560: }
561: return new TCPEndpoint(host, port, csf, null);
562: }
563:
564: /**
565: * Write endpoint to output stream in older format used by
566: * UnicastRef for JDK1.1 compatibility.
567: */
568: public void writeHostPortFormat(DataOutput out) throws IOException {
569: if (csf != null) {
570: throw new InternalError(
571: "TCPEndpoint.writeHostPortFormat: "
572: + "called for endpoint with non-null socket factory");
573: }
574: out.writeUTF(host);
575: out.writeInt(port);
576: }
577:
578: /**
579: * Create a new endpoint from input stream data.
580: * @param in the input stream
581: */
582: public static TCPEndpoint readHostPortFormat(DataInput in)
583: throws IOException {
584: String host = in.readUTF();
585: int port = in.readInt();
586: return new TCPEndpoint(host, port);
587: }
588:
589: private static RMISocketFactory chooseFactory() {
590: RMISocketFactory sf = RMISocketFactory.getSocketFactory();
591: if (sf == null) {
592: sf = TCPTransport.defaultSocketFactory;
593: }
594: return sf;
595: }
596:
597: /**
598: * Open and return new client socket connection to endpoint.
599: */
600: Socket newSocket() throws RemoteException {
601: if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
602: TCPTransport.tcpLog.log(Log.VERBOSE, "opening socket to "
603: + this );
604: }
605:
606: Socket socket;
607:
608: try {
609: RMIClientSocketFactory clientFactory = csf;
610: if (clientFactory == null) {
611: clientFactory = chooseFactory();
612: }
613: socket = clientFactory.createSocket(host, port);
614:
615: } catch (java.net.UnknownHostException e) {
616: throw new java.rmi.UnknownHostException("Unknown host: "
617: + host, e);
618: } catch (java.net.ConnectException e) {
619: throw new java.rmi.ConnectException(
620: "Connection refused to host: " + host, e);
621: } catch (IOException e) {
622: // We might have simply run out of file descriptors
623: try {
624: TCPEndpoint.shedConnectionCaches();
625: // REMIND: should we retry createSocket?
626: } catch (OutOfMemoryError mem) {
627: // don't quit if out of memory
628: } catch (Exception ex) {
629: // don't quit if shed fails non-catastrophically
630: }
631:
632: throw new ConnectIOException(
633: "Exception creating connection to: " + host, e);
634: }
635:
636: // set socket to disable Nagle's algorithm (always send immediately)
637: // TBD: should this be left up to socket factory instead?
638: try {
639: socket.setTcpNoDelay(true);
640: } catch (Exception e) {
641: // if we fail to set this, ignore and proceed anyway
642: }
643:
644: // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs
645: try {
646: socket.setKeepAlive(true);
647: } catch (Exception e) {
648: // ignore and proceed
649: }
650:
651: return socket;
652: }
653:
654: /**
655: * Return new server socket to listen for connections on this endpoint.
656: */
657: ServerSocket newServerSocket() throws IOException {
658: if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
659: TCPTransport.tcpLog.log(Log.VERBOSE,
660: "creating server socket on " + this );
661: }
662:
663: RMIServerSocketFactory serverFactory = ssf;
664: if (serverFactory == null) {
665: serverFactory = chooseFactory();
666: }
667: ServerSocket server = serverFactory
668: .createServerSocket(listenPort);
669:
670: // if we listened on an anonymous port, set the default port
671: // (for this socket factory)
672: if (listenPort == 0)
673: setDefaultPort(server.getLocalPort(), csf, ssf);
674:
675: return server;
676: }
677:
678: /**
679: * The class FQDN encapsulates a routine that makes a best effort
680: * attempt to retrieve the fully qualified domain name of the local
681: * host.
682: *
683: * @author Laird Dornin
684: */
685: private static class FQDN implements Runnable {
686:
687: /**
688: * strings in which we can store discovered fqdn
689: */
690: private String reverseLookup;
691:
692: private String hostAddress;
693:
694: private FQDN(String hostAddress) {
695: this .hostAddress = hostAddress;
696: }
697:
698: /**
699: * Do our best to obtain a fully qualified hostname for the local
700: * host. Perform the following steps to get a localhostname:
701: *
702: * 1. InetAddress.getLocalHost().getHostName() - if contains
703: * '.' use as FQDN
704: * 2. if no '.' query name service for FQDN in a thread
705: * Note: We query the name service for an FQDN by creating
706: * an InetAddress via a stringified copy of the local ip
707: * address; this creates an InetAddress with a null hostname.
708: * Asking for the hostname of this InetAddress causes a name
709: * service lookup.
710: *
711: * 3. if name service takes too long to return, use ip address
712: * 4. if name service returns but response contains no '.'
713: * default to ipaddress.
714: */
715: static String attemptFQDN(InetAddress localAddr)
716: throws java.net.UnknownHostException {
717:
718: String hostName = localAddr.getHostName();
719:
720: if (hostName.indexOf('.') < 0) {
721:
722: String hostAddress = localAddr.getHostAddress();
723: FQDN f = new FQDN(hostAddress);
724:
725: int nameServiceTimeOut = TCPEndpoint.getInt(
726: "sun.rmi.transport.tcp.localHostNameTimeOut",
727: 10000);
728:
729: try {
730: synchronized (f) {
731: f.getFQDN();
732:
733: /* wait to obtain an FQDN */
734: f.wait(nameServiceTimeOut);
735: }
736: } catch (InterruptedException e) {
737: /* propagate the exception to the caller */
738: Thread.currentThread().interrupt();
739: }
740: hostName = f.getHost();
741:
742: if ((hostName == null) || (hostName.equals(""))
743: || (hostName.indexOf('.') < 0)) {
744:
745: hostName = hostAddress;
746: }
747: }
748: return hostName;
749: }
750:
751: /**
752: * Method that that will start a thread to wait to retrieve a
753: * fully qualified domain name from a name service. The spawned
754: * thread may never return but we have marked it as a daemon so the vm
755: * will terminate appropriately.
756: */
757: private void getFQDN() {
758:
759: /* FQDN finder will run in RMI threadgroup. */
760: Thread t = AccessController
761: .doPrivileged(new NewThreadAction(FQDN.this ,
762: "FQDN Finder", true));
763: t.start();
764: }
765:
766: private synchronized String getHost() {
767: return reverseLookup;
768: }
769:
770: /**
771: * thread to query a name service for the fqdn of this host.
772: */
773: public void run() {
774:
775: String name = null;
776:
777: try {
778: name = InetAddress.getByName(hostAddress).getHostName();
779: } catch (java.net.UnknownHostException e) {
780: } finally {
781: synchronized (this) {
782: reverseLookup = name;
783: this.notify();
784: }
785: }
786: }
787: }
788: }
|