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 */
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.security.PrivilegedAction;
42import java.util.Collection;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.LinkedList;
46import java.util.Map;
47import java.util.Set;
48import sun.rmi.runtime.Log;
49import sun.rmi.runtime.NewThreadAction;
50import sun.rmi.transport.Channel;
51import sun.rmi.transport.Endpoint;
52import sun.rmi.transport.Target;
53import sun.rmi.transport.Transport;
54
55/**
56 * TCPEndpoint represents some communication endpoint for an address
57 * space (VM).
58 *
59 * @author Ann Wollrath
60 */
61public 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}
787