1 /* 2 * Copyright (c) 1996, 2012, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package sun.rmi.transport.tcp; 26 27 import java.io.DataInput; 28 import java.io.DataOutput; 29 import java.io.IOException; 30 import java.io.ObjectInput; 31 import java.io.ObjectOutput; 32 import java.net.InetAddress; 33 import java.net.ServerSocket; 34 import java.net.Socket; 35 import java.rmi.ConnectIOException; 36 import java.rmi.RemoteException; 37 import java.rmi.server.RMIClientSocketFactory; 38 import java.rmi.server.RMIServerSocketFactory; 39 import java.rmi.server.RMISocketFactory; 40 import java.security.AccessController; 41 import java.security.PrivilegedAction; 42 import java.util.Collection; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.LinkedList; 46 import java.util.Map; 47 import java.util.Set; 48 import sun.rmi.runtime.Log; 49 import sun.rmi.runtime.NewThreadAction; 50 import sun.rmi.transport.Channel; 51 import sun.rmi.transport.Endpoint; 52 import sun.rmi.transport.Target; 53 import sun.rmi.transport.Transport; 54 55 /** 56 * TCPEndpoint represents some communication endpoint for an address 57 * space (VM). 58 * 59 * @author Ann Wollrath 60 */ 61 public class TCPEndpoint implements Endpoint { 62 /** IP address or host name */ 63 private String host; 64 /** port number */ 65 private int port; 66 /** custom client socket factory (null if not custom factory) */ 67 private final RMIClientSocketFactory csf; 68 /** custom server socket factory (null if not custom factory) */ 69 private final RMIServerSocketFactory ssf; 70 71 /** if local, the port number to listen on */ 72 private int listenPort = -1; 73 /** if local, the transport object associated with this endpoint */ 74 private TCPTransport transport = null; 75 76 /** the local host name */ 77 private static String localHost; 78 /** true if real local host name is known yet */ 79 private static boolean localHostKnown; 80 81 // this should be a *private* method since it is privileged 82 private static int getInt(String name, int def) { 83 return AccessController.doPrivileged( 84 (PrivilegedAction<Integer>) () -> Integer.getInteger(name, def)); 85 } 86 87 // this should be a *private* method since it is privileged 88 private static boolean getBoolean(String name) { 89 return AccessController.doPrivileged( 90 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean(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 (PrivilegedAction<String>) () -> System.getProperty("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<>(); 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 | Exception mem) { 627 // don't quit if out of memory 628 // or shed fails non-catastrophically 629 } 630 631 throw new ConnectIOException("Exception creating connection to: " + 632 host, e); 633 } 634 635 // set socket to disable Nagle's algorithm (always send immediately) 636 // TBD: should this be left up to socket factory instead? 637 try { 638 socket.setTcpNoDelay(true); 639 } catch (Exception e) { 640 // if we fail to set this, ignore and proceed anyway 641 } 642 643 // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs 644 try { 645 socket.setKeepAlive(true); 646 } catch (Exception e) { 647 // ignore and proceed 648 } 649 650 return socket; 651 } 652 653 /** 654 * Return new server socket to listen for connections on this endpoint. 655 */ 656 ServerSocket newServerSocket() throws IOException { 657 if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { 658 TCPTransport.tcpLog.log(Log.VERBOSE, 659 "creating server socket on " + this); 660 } 661 662 RMIServerSocketFactory serverFactory = ssf; 663 if (serverFactory == null) { 664 serverFactory = chooseFactory(); 665 } 666 ServerSocket server = serverFactory.createServerSocket(listenPort); 667 668 // if we listened on an anonymous port, set the default port 669 // (for this socket factory) 670 if (listenPort == 0) 671 setDefaultPort(server.getLocalPort(), csf, ssf); 672 673 return server; 674 } 675 676 /** 677 * The class FQDN encapsulates a routine that makes a best effort 678 * attempt to retrieve the fully qualified domain name of the local 679 * host. 680 * 681 * @author Laird Dornin 682 */ 683 private static class FQDN implements Runnable { 684 685 /** 686 * strings in which we can store discovered fqdn 687 */ 688 private String reverseLookup; 689 690 private String hostAddress; 691 692 private FQDN(String hostAddress) { 693 this.hostAddress = hostAddress; 694 } 695 696 /** 697 * Do our best to obtain a fully qualified hostname for the local 698 * host. Perform the following steps to get a localhostname: 699 * 700 * 1. InetAddress.getLocalHost().getHostName() - if contains 701 * '.' use as FQDN 702 * 2. if no '.' query name service for FQDN in a thread 703 * Note: We query the name service for an FQDN by creating 704 * an InetAddress via a stringified copy of the local ip 705 * address; this creates an InetAddress with a null hostname. 706 * Asking for the hostname of this InetAddress causes a name 707 * service lookup. 708 * 709 * 3. if name service takes too long to return, use ip address 710 * 4. if name service returns but response contains no '.' 711 * default to ipaddress. 712 */ 713 static String attemptFQDN(InetAddress localAddr) 714 throws java.net.UnknownHostException 715 { 716 717 String hostName = localAddr.getHostName(); 718 719 if (hostName.indexOf('.') < 0 ) { 720 721 String hostAddress = localAddr.getHostAddress(); 722 FQDN f = new FQDN(hostAddress); 723 724 int nameServiceTimeOut = 725 TCPEndpoint.getInt("sun.rmi.transport.tcp.localHostNameTimeOut", 726 10000); 727 728 try { 729 synchronized(f) { 730 f.getFQDN(); 731 732 /* wait to obtain an FQDN */ 733 f.wait(nameServiceTimeOut); 734 } 735 } catch (InterruptedException e) { 736 /* propagate the exception to the caller */ 737 Thread.currentThread().interrupt(); 738 } 739 hostName = f.getHost(); 740 741 if ((hostName == null) || (hostName.equals("")) 742 || (hostName.indexOf('.') < 0 )) { 743 744 hostName = hostAddress; 745 } 746 } 747 return hostName; 748 } 749 750 /** 751 * Method that that will start a thread to wait to retrieve a 752 * fully qualified domain name from a name service. The spawned 753 * thread may never return but we have marked it as a daemon so the vm 754 * will terminate appropriately. 755 */ 756 private void getFQDN() { 757 758 /* FQDN finder will run in RMI threadgroup. */ 759 Thread t = AccessController.doPrivileged( 760 new NewThreadAction(FQDN.this, "FQDN Finder", true)); 761 t.start(); 762 } 763 764 private synchronized String getHost() { 765 return reverseLookup; 766 } 767 768 /** 769 * thread to query a name service for the fqdn of this host. 770 */ 771 public void run() { 772 773 String name = null; 774 775 try { 776 name = InetAddress.getByName(hostAddress).getHostName(); 777 } catch (java.net.UnknownHostException e) { 778 } finally { 779 synchronized(this) { 780 reverseLookup = name; 781 this.notify(); 782 } 783 } 784 } 785 } 786 }