blob: 835a7c7cdd2e930c96ce81d078f21bd5ac04ed92 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package sun.rmi.transport.tcp;
26
27import java.io.DataInput;
28import java.io.DataOutput;
29import java.io.IOException;
30import java.io.ObjectInput;
31import java.io.ObjectOutput;
32import java.net.InetAddress;
33import java.net.ServerSocket;
34import java.net.Socket;
35import java.rmi.ConnectIOException;
36import java.rmi.RemoteException;
37import java.rmi.server.RMIClientSocketFactory;
38import java.rmi.server.RMIServerSocketFactory;
39import java.rmi.server.RMISocketFactory;
40import java.security.AccessController;
41import java.util.Collection;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.LinkedList;
45import java.util.Map;
46import java.util.Set;
47import sun.rmi.runtime.Log;
48import sun.rmi.runtime.NewThreadAction;
49import sun.rmi.transport.Channel;
50import sun.rmi.transport.Endpoint;
51import sun.rmi.transport.Target;
52import sun.rmi.transport.Transport;
53import sun.security.action.GetBooleanAction;
54import sun.security.action.GetIntegerAction;
55import sun.security.action.GetPropertyAction;
56
57/**
58 * TCPEndpoint represents some communication endpoint for an address
59 * space (VM).
60 *
61 * @author Ann Wollrath
62 */
63public class TCPEndpoint implements Endpoint {
64 /** IP address or host name */
65 private String host;
66 /** port number */
67 private int port;
68 /** custom client socket factory (null if not custom factory) */
69 private final RMIClientSocketFactory csf;
70 /** custom server socket factory (null if not custom factory) */
71 private final RMIServerSocketFactory ssf;
72
73 /** if local, the port number to listen on */
74 private int listenPort = -1;
75 /** if local, the transport object associated with this endpoint */
76 private TCPTransport transport = null;
77
78 /** the local host name */
79 private static String localHost;
80 /** true if real local host name is known yet */
81 private static boolean localHostKnown;
82
83 // this should be a *private* method since it is privileged
84 private static int getInt(String name, int def) {
85 return AccessController.doPrivileged(new GetIntegerAction(name, def));
86 }
87
88 // this should be a *private* method since it is privileged
89 private static boolean getBoolean(String name) {
90 return AccessController.doPrivileged(new GetBooleanAction(name));
91 }
92
93 /**
94 * Returns the value of the java.rmi.server.hostname property.
95 */
96 private static String getHostnameProperty() {
97 return AccessController.doPrivileged(
98 new GetPropertyAction("java.rmi.server.hostname"));
99 }
100
101 /**
102 * Find host name of local machine. Property "java.rmi.server.hostname"
103 * is used if set, so server administrator can compensate for the possible
104 * inablility to get fully qualified host name from VM.
105 */
106 static {
107 localHostKnown = true;
108 localHost = getHostnameProperty();
109
110 // could try querying CGI program here?
111 if (localHost == null) {
112 try {
113 InetAddress localAddr = InetAddress.getLocalHost();
114 byte[] raw = localAddr.getAddress();
115 if ((raw[0] == 127) &&
116 (raw[1] == 0) &&
117 (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,
141 "localHostKnown = " + localHostKnown +
142 ", localHost = " + localHost);
143 }
144 }
145
146 /** maps an endpoint key containing custom socket factories to
147 * their own unique endpoint */
148 // TBD: should this be a weak hash table?
149 private static final
150 Map<TCPEndpoint,LinkedList<TCPEndpoint>> localEndpoints =
151 new HashMap<TCPEndpoint,LinkedList<TCPEndpoint>>();
152
153 /**
154 * Create an endpoint for a specified host and port.
155 * This should not be used by external classes to create endpoints
156 * for servers in this VM; use getLocalEndpoint instead.
157 */
158 public TCPEndpoint(String host, int port) {
159 this(host, port, null, null);
160 }
161
162 /**
163 * Create a custom socket factory endpoint for a specified host and port.
164 * This should not be used by external classes to create endpoints
165 * for servers in this VM; use getLocalEndpoint instead.
166 */
167 public TCPEndpoint(String host, int port, RMIClientSocketFactory csf,
168 RMIServerSocketFactory ssf)
169 {
170 if (host == null)
171 host = "";
172 this.host = host;
173 this.port = port;
174 this.csf = csf;
175 this.ssf = ssf;
176 }
177
178 /**
179 * Get an endpoint for the local address space on specified port.
180 * If port number is 0, it returns shared default endpoint object
181 * whose host name and port may or may not have been determined.
182 */
183 public static TCPEndpoint getLocalEndpoint(int port) {
184 return getLocalEndpoint(port, null, null);
185 }
186
187 public static TCPEndpoint getLocalEndpoint(int port,
188 RMIClientSocketFactory csf,
189 RMIServerSocketFactory ssf)
190 {
191 /*
192 * Find mapping for an endpoint key to the list of local unique
193 * endpoints for this client/server socket factory pair (perhaps
194 * null) for the specific port.
195 */
196 TCPEndpoint ep = null;
197
198 synchronized (localEndpoints) {
199 TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf);
200 LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
201 String localHost = resampleLocalHost();
202
203 if (epList == null) {
204 /*
205 * Create new endpoint list.
206 */
207 ep = new TCPEndpoint(localHost, port, csf, ssf);
208 epList = new LinkedList<TCPEndpoint>();
209 epList.add(ep);
210 ep.listenPort = port;
211 ep.transport = new TCPTransport(epList);
212 localEndpoints.put(endpointKey, epList);
213
214 if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
215 TCPTransport.tcpLog.log(Log.BRIEF,
216 "created local endpoint for socket factory " + ssf +
217 " on port " + port);
218 }
219 } else {
220 synchronized (epList) {
221 ep = epList.getLast();
222 String lastHost = ep.host;
223 int lastPort = ep.port;
224 TCPTransport lastTransport = ep.transport;
225 // assert (localHost == null ^ lastHost != null)
226 if (localHost != null && !localHost.equals(lastHost)) {
227 /*
228 * Hostname has been updated; add updated endpoint
229 * to list.
230 */
231 if (lastPort != 0) {
232 /*
233 * Remove outdated endpoints only if the
234 * port has already been set on those endpoints.
235 */
236 epList.clear();
237 }
238 ep = new TCPEndpoint(localHost, lastPort, csf, ssf);
239 ep.listenPort = port;
240 ep.transport = lastTransport;
241 epList.add(ep);
242 }
243 }
244 }
245 }
246
247 return ep;
248 }
249
250 /**
251 * Resamples the local hostname and returns the possibly-updated
252 * local hostname.
253 */
254 private static String resampleLocalHost() {
255
256 String hostnameProperty = getHostnameProperty();
257
258 synchronized (localEndpoints) {
259 // assert(localHostKnown ^ (localHost == null))
260
261 if (hostnameProperty != null) {
262 if (!localHostKnown) {
263 /*
264 * If the local hostname is unknown, update ALL
265 * existing endpoints with the new hostname.
266 */
267 setLocalHost(hostnameProperty);
268 } else if (!hostnameProperty.equals(localHost)) {
269 /*
270 * Only update the localHost field for reference
271 * in future endpoint creation.
272 */
273 localHost = hostnameProperty;
274
275 if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
276 TCPTransport.tcpLog.log(Log.BRIEF,
277 "updated local hostname to: " + localHost);
278 }
279 }
280 }
281 return localHost;
282 }
283 }
284
285 /**
286 * Set the local host name, if currently unknown.
287 */
288 static void setLocalHost(String host) {
289 // assert (host != null)
290
291 synchronized (localEndpoints) {
292 /*
293 * If host is not known, change the host field of ALL
294 * the local endpoints.
295 */
296 if (!localHostKnown) {
297 localHost = host;
298 localHostKnown = true;
299
300 if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
301 TCPTransport.tcpLog.log(Log.BRIEF,
302 "local host set to " + host);
303 }
304 for (LinkedList<TCPEndpoint> epList : localEndpoints.values())
305 {
306 synchronized (epList) {
307 for (TCPEndpoint ep : epList) {
308 ep.host = host;
309 }
310 }
311 }
312 }
313 }
314 }
315
316 /**
317 * Set the port of the (shared) default endpoint object.
318 * When first created, it contains port 0 because the transport
319 * hasn't tried to listen to get assigned a port, or if listening
320 * failed, a port hasn't been assigned from the server.
321 */
322 static void setDefaultPort(int port, RMIClientSocketFactory csf,
323 RMIServerSocketFactory ssf)
324 {
325 TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf);
326
327 synchronized (localEndpoints) {
328 LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
329
330 synchronized (epList) {
331 int size = epList.size();
332 TCPEndpoint lastEp = epList.getLast();
333
334 for (TCPEndpoint ep : epList) {
335 ep.port = port;
336 }
337 if (size > 1) {
338 /*
339 * Remove all but the last element of the list
340 * (which contains the most recent hostname).
341 */
342 epList.clear();
343 epList.add(lastEp);
344 }
345 }
346
347 /*
348 * Allow future exports to use the actual bound port
349 * explicitly (see 6269166).
350 */
351 TCPEndpoint newEndpointKey = new TCPEndpoint(null, port, 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.values()) {
385 /*
386 * Each local endpoint has its transport added to s.
387 * Note: the transport is the same for all endpoints
388 * in the list, so it is okay to pick any one of them.
389 */
390 TCPEndpoint ep = epList.getFirst();
391 s.add(ep.transport);
392 }
393 }
394 return s;
395 }
396
397 /**
398 * Release idle outbound connections to reduce demand on I/O resources.
399 * All transports are asked to release excess connections.
400 */
401 public static void shedConnectionCaches() {
402 for (TCPTransport transport : allKnownTransports()) {
403 transport.shedConnectionCaches();
404 }
405 }
406
407 /**
408 * Export the object to accept incoming calls.
409 */
410 public void exportObject(Target target) throws RemoteException {
411 transport.exportObject(target);
412 }
413
414 /**
415 * Returns a channel for this (remote) endpoint.
416 */
417 public Channel getChannel() {
418 return getOutboundTransport().getChannel(this);
419 }
420
421 /**
422 * Returns address for endpoint
423 */
424 public String getHost() {
425 return host;
426 }
427
428 /**
429 * Returns the port for this endpoint. If this endpoint was
430 * created as a server endpoint (using getLocalEndpoint) for a
431 * default/anonymous port and its inbound transport has started
432 * listening, this method returns (instead of zero) the actual
433 * bound port suitable for passing to clients.
434 **/
435 public int getPort() {
436 return port;
437 }
438
439 /**
440 * Returns the port that this endpoint's inbound transport listens
441 * on, if this endpoint was created as a server endpoint (using
442 * getLocalEndpoint). If this endpoint was created for the
443 * default/anonymous port, then this method returns zero even if
444 * the transport has started listening.
445 **/
446 public int getListenPort() {
447 return listenPort;
448 }
449
450 /**
451 * Returns the transport for incoming connections to this
452 * endpoint, if this endpoint was created as a server endpoint
453 * (using getLocalEndpoint).
454 **/
455 public Transport getInboundTransport() {
456 return transport;
457 }
458
459 /**
460 * Get the client socket factory associated with this endpoint.
461 */
462 public RMIClientSocketFactory getClientSocketFactory() {
463 return csf;
464 }
465
466 /**
467 * Get the server socket factory associated with this endpoint.
468 */
469 public RMIServerSocketFactory getServerSocketFactory() {
470 return ssf;
471 }
472
473 /**
474 * Return string representation for endpoint.
475 */
476 public String toString() {
477 return "[" + host + ":" + port +
478 (ssf != null ? "," + ssf : "") +
479 (csf != null ? "," + csf : "") +
480 "]";
481 }
482
483 public int hashCode() {
484 return port;
485 }
486
487 public boolean equals(Object obj) {
488 if ((obj != null) && (obj instanceof TCPEndpoint)) {
489 TCPEndpoint ep = (TCPEndpoint) obj;
490 if (port != ep.port || !host.equals(ep.host))
491 return false;
492 if (((csf == null) ^ (ep.csf == null)) ||
493 ((ssf == null) ^ (ep.ssf == null)))
494 return false;
495 /*
496 * Fix for 4254510: perform socket factory *class* equality check
497 * before socket factory equality check to avoid passing
498 * a potentially naughty socket factory to this endpoint's
499 * {client,server} socket factory equals method.
500 */
501 if ((csf != null) &&
502 !(csf.getClass() == ep.csf.getClass() && csf.equals(ep.csf)))
503 return false;
504 if ((ssf != null) &&
505 !(ssf.getClass() == ep.ssf.getClass() && ssf.equals(ep.ssf)))
506 return false;
507 return true;
508 } else {
509 return false;
510 }
511 }
512
513 /* codes for the self-describing formats of wire representation */
514 private static final int FORMAT_HOST_PORT = 0;
515 private static final int FORMAT_HOST_PORT_FACTORY = 1;
516
517 /**
518 * Write endpoint to output stream.
519 */
520 public void write(ObjectOutput out) throws IOException {
521 if (csf == null) {
522 out.writeByte(FORMAT_HOST_PORT);
523 out.writeUTF(host);
524 out.writeInt(port);
525 } else {
526 out.writeByte(FORMAT_HOST_PORT_FACTORY);
527 out.writeUTF(host);
528 out.writeInt(port);
529 out.writeObject(csf);
530 }
531 }
532
533 /**
534 * Get the endpoint from the input stream.
535 * @param in the input stream
536 * @exception IOException If id could not be read (due to stream failure)
537 */
538 public static TCPEndpoint read(ObjectInput in)
539 throws IOException, ClassNotFoundException
540 {
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("TCPEndpoint.writeHostPortFormat: " +
571 "called for endpoint with non-null socket factory");
572 }
573 out.writeUTF(host);
574 out.writeInt(port);
575 }
576
577 /**
578 * Create a new endpoint from input stream data.
579 * @param in the input stream
580 */
581 public static TCPEndpoint readHostPortFormat(DataInput in)
582 throws IOException
583 {
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,
603 "opening socket to " + 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(
617 "Unknown host: " + 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("Exception creating connection to: " +
633 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.createServerSocket(listenPort);
668
669 // if we listened on an anonymous port, set the default port
670 // (for this socket factory)
671 if (listenPort == 0)
672 setDefaultPort(server.getLocalPort(), csf, ssf);
673
674 return server;
675 }
676
677 /**
678 * The class FQDN encapsulates a routine that makes a best effort
679 * attempt to retrieve the fully qualified domain name of the local
680 * host.
681 *
682 * @author Laird Dornin
683 */
684 private static class FQDN implements Runnable {
685
686 /**
687 * strings in which we can store discovered fqdn
688 */
689 private String reverseLookup;
690
691 private String hostAddress;
692
693 private FQDN(String hostAddress) {
694 this.hostAddress = hostAddress;
695 }
696
697 /**
698 * Do our best to obtain a fully qualified hostname for the local
699 * host. Perform the following steps to get a localhostname:
700 *
701 * 1. InetAddress.getLocalHost().getHostName() - if contains
702 * '.' use as FQDN
703 * 2. if no '.' query name service for FQDN in a thread
704 * Note: We query the name service for an FQDN by creating
705 * an InetAddress via a stringified copy of the local ip
706 * address; this creates an InetAddress with a null hostname.
707 * Asking for the hostname of this InetAddress causes a name
708 * service lookup.
709 *
710 * 3. if name service takes too long to return, use ip address
711 * 4. if name service returns but response contains no '.'
712 * default to ipaddress.
713 */
714 static String attemptFQDN(InetAddress localAddr)
715 throws java.net.UnknownHostException
716 {
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 =
726 TCPEndpoint.getInt("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.doPrivileged(
761 new NewThreadAction(FQDN.this, "FQDN Finder", true));
762 t.start();
763 }
764
765 private synchronized String getHost() {
766 return reverseLookup;
767 }
768
769 /**
770 * thread to query a name service for the fqdn of this host.
771 */
772 public void run() {
773
774 String name = null;
775
776 try {
777 name = InetAddress.getByName(hostAddress).getHostName();
778 } catch (java.net.UnknownHostException e) {
779 } finally {
780 synchronized(this) {
781 reverseLookup = name;
782 this.notify();
783 }
784 }
785 }
786 }
787}