1/*
2 * Copyright (c) 1996, 2016, 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.lang.ref.Reference;
28import java.lang.ref.SoftReference;
29import java.lang.ref.WeakReference;
30import java.lang.reflect.InvocationTargetException;
31import java.lang.reflect.UndeclaredThrowableException;
32import java.io.DataInputStream;
33import java.io.DataOutputStream;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.OutputStream;
37import java.io.BufferedInputStream;
38import java.io.BufferedOutputStream;
39import java.net.InetAddress;
40import java.net.ServerSocket;
41import java.net.Socket;
42import java.rmi.RemoteException;
43import java.rmi.server.ExportException;
44import java.rmi.server.LogStream;
45import java.rmi.server.RMIFailureHandler;
46import java.rmi.server.RMISocketFactory;
47import java.rmi.server.RemoteCall;
48import java.rmi.server.ServerNotActiveException;
49import java.rmi.server.UID;
50import java.security.AccessControlContext;
51import java.security.AccessController;
52import java.security.Permissions;
53import java.security.PrivilegedAction;
54import java.security.ProtectionDomain;
55import java.util.ArrayList;
56import java.util.LinkedList;
57import java.util.List;
58import java.util.Map;
59import java.util.WeakHashMap;
60import java.util.logging.Level;
61import java.util.concurrent.ExecutorService;
62import java.util.concurrent.RejectedExecutionException;
63import java.util.concurrent.SynchronousQueue;
64import java.util.concurrent.ThreadFactory;
65import java.util.concurrent.ThreadPoolExecutor;
66import java.util.concurrent.TimeUnit;
67import java.util.concurrent.atomic.AtomicInteger;
68import sun.rmi.runtime.Log;
69import sun.rmi.runtime.NewThreadAction;
70import sun.rmi.transport.Channel;
71import sun.rmi.transport.Connection;
72import sun.rmi.transport.DGCAckHandler;
73import sun.rmi.transport.Endpoint;
74import sun.rmi.transport.StreamRemoteCall;
75import sun.rmi.transport.Target;
76import sun.rmi.transport.Transport;
77import sun.rmi.transport.TransportConstants;
78
79/**
80 * TCPTransport is the socket-based implementation of the RMI Transport
81 * abstraction.
82 *
83 * @author Ann Wollrath
84 * @author Peter Jones
85 */
86@SuppressWarnings("deprecation")
87public class TCPTransport extends Transport {
88
89    /* tcp package log */
90    static final Log tcpLog = Log.getLog("sun.rmi.transport.tcp", "tcp",
91        LogStream.parseLevel(AccessController.doPrivileged(
92            (PrivilegedAction<String>) () -> System.getProperty("sun.rmi.transport.tcp.logLevel"))));
93
94    /** maximum number of connection handler threads */
95    private static final int maxConnectionThreads =     // default no limit
96        AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
97            Integer.getInteger("sun.rmi.transport.tcp.maxConnectionThreads",
98                               Integer.MAX_VALUE));
99
100    /** keep alive time for idle connection handler threads */
101    private static final long threadKeepAliveTime =     // default 1 minute
102        AccessController.doPrivileged((PrivilegedAction<Long>) () ->
103            Long.getLong("sun.rmi.transport.tcp.threadKeepAliveTime", 60000));
104
105    /** thread pool for connection handlers */
106    private static final ExecutorService connectionThreadPool =
107        new ThreadPoolExecutor(0, maxConnectionThreads,
108            threadKeepAliveTime, TimeUnit.MILLISECONDS,
109            new SynchronousQueue<Runnable>(),
110            new ThreadFactory() {
111                public Thread newThread(Runnable runnable) {
112                    return AccessController.doPrivileged(new NewThreadAction(
113                        runnable, "TCP Connection(idle)", true, true));
114                }
115            });
116
117    /** total connections handled */
118    private static final AtomicInteger connectionCount = new AtomicInteger(0);
119
120    /** client host for the current thread's connection */
121    private static final ThreadLocal<ConnectionHandler>
122        threadConnectionHandler = new ThreadLocal<>();
123
124    /** an AccessControlContext with no permissions */
125    private static final AccessControlContext NOPERMS_ACC;
126    static {
127        Permissions perms = new Permissions();
128        ProtectionDomain[] pd = { new ProtectionDomain(null, perms) };
129        NOPERMS_ACC = new AccessControlContext(pd);
130    }
131
132    /** endpoints for this transport */
133    private final LinkedList<TCPEndpoint> epList;
134    /** number of objects exported on this transport */
135    private int exportCount = 0;
136    /** server socket for this transport */
137    private ServerSocket server = null;
138    /** table mapping endpoints to channels */
139    private final Map<TCPEndpoint,Reference<TCPChannel>> channelTable =
140        new WeakHashMap<>();
141
142    static final RMISocketFactory defaultSocketFactory =
143        RMISocketFactory.getDefaultSocketFactory();
144
145    /** number of milliseconds in accepted-connection timeout.
146     * Warning: this should be greater than 15 seconds (the client-side
147     * timeout), and defaults to 2 hours.
148     * The maximum representable value is slightly more than 24 days
149     * and 20 hours.
150     */
151    private static final int connectionReadTimeout =    // default 2 hours
152        AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
153            Integer.getInteger("sun.rmi.transport.tcp.readTimeout", 2 * 3600 * 1000));
154
155    /**
156     * Constructs a TCPTransport.
157     */
158    TCPTransport(LinkedList<TCPEndpoint> epList)  {
159        // assert ((epList.size() != null) && (epList.size() >= 1))
160        this.epList = epList;
161        if (tcpLog.isLoggable(Log.BRIEF)) {
162            tcpLog.log(Log.BRIEF, "Version = " +
163                TransportConstants.Version + ", ep = " + getEndpoint());
164        }
165    }
166
167    /**
168     * Closes all cached connections in every channel subordinated to this
169     * transport.  Currently, this only closes outgoing connections.
170     */
171    public void shedConnectionCaches() {
172        List<TCPChannel> channels;
173        synchronized (channelTable) {
174            channels = new ArrayList<TCPChannel>(channelTable.values().size());
175            for (Reference<TCPChannel> ref : channelTable.values()) {
176                TCPChannel ch = ref.get();
177                if (ch != null) {
178                    channels.add(ch);
179                }
180            }
181        }
182        for (TCPChannel channel : channels) {
183            channel.shedCache();
184        }
185    }
186
187    /**
188     * Returns a <I>Channel</I> that generates connections to the
189     * endpoint <I>ep</I>. A Channel is an object that creates and
190     * manages connections of a particular type to some particular
191     * address space.
192     * @param ep the endpoint to which connections will be generated.
193     * @return the channel or null if the transport cannot
194     * generate connections to this endpoint
195     */
196    public TCPChannel getChannel(Endpoint ep) {
197        TCPChannel ch = null;
198        if (ep instanceof TCPEndpoint) {
199            synchronized (channelTable) {
200                Reference<TCPChannel> ref = channelTable.get(ep);
201                if (ref != null) {
202                    ch = ref.get();
203                }
204                if (ch == null) {
205                    TCPEndpoint tcpEndpoint = (TCPEndpoint) ep;
206                    ch = new TCPChannel(this, tcpEndpoint);
207                    channelTable.put(tcpEndpoint,
208                                     new WeakReference<TCPChannel>(ch));
209                }
210            }
211        }
212        return ch;
213    }
214
215    /**
216     * Removes the <I>Channel</I> that generates connections to the
217     * endpoint <I>ep</I>.
218     */
219    public void free(Endpoint ep) {
220        if (ep instanceof TCPEndpoint) {
221            synchronized (channelTable) {
222                Reference<TCPChannel> ref = channelTable.remove(ep);
223                if (ref != null) {
224                    TCPChannel channel = ref.get();
225                    if (channel != null) {
226                        channel.shedCache();
227                    }
228                }
229            }
230        }
231    }
232
233    /**
234     * Export the object so that it can accept incoming calls.
235     */
236    public void exportObject(Target target) throws RemoteException {
237        /*
238         * Ensure that a server socket is listening, and count this
239         * export while synchronized to prevent the server socket from
240         * being closed due to concurrent unexports.
241         */
242        synchronized (this) {
243            listen();
244            exportCount++;
245        }
246
247        /*
248         * Try to add the Target to the exported object table; keep
249         * counting this export (to keep server socket open) only if
250         * that succeeds.
251         */
252        boolean ok = false;
253        try {
254            super.exportObject(target);
255            ok = true;
256        } finally {
257            if (!ok) {
258                synchronized (this) {
259                    decrementExportCount();
260                }
261            }
262        }
263    }
264
265    protected synchronized void targetUnexported() {
266        decrementExportCount();
267    }
268
269    /**
270     * Decrements the count of exported objects, closing the current
271     * server socket if the count reaches zero.
272     **/
273    private void decrementExportCount() {
274        assert Thread.holdsLock(this);
275        exportCount--;
276        if (exportCount == 0 && getEndpoint().getListenPort() != 0) {
277            ServerSocket ss = server;
278            server = null;
279            try {
280                ss.close();
281            } catch (IOException e) {
282            }
283        }
284    }
285
286    /**
287     * Verify that the current access control context has permission to
288     * accept the connection being dispatched by the current thread.
289     */
290    protected void checkAcceptPermission(AccessControlContext acc) {
291        SecurityManager sm = System.getSecurityManager();
292        if (sm == null) {
293            return;
294        }
295        ConnectionHandler h = threadConnectionHandler.get();
296        if (h == null) {
297            throw new Error(
298                "checkAcceptPermission not in ConnectionHandler thread");
299        }
300        h.checkAcceptPermission(sm, acc);
301    }
302
303    private TCPEndpoint getEndpoint() {
304        synchronized (epList) {
305            return epList.getLast();
306        }
307    }
308
309    /**
310     * Listen on transport's endpoint.
311     */
312    private void listen() throws RemoteException {
313        assert Thread.holdsLock(this);
314        TCPEndpoint ep = getEndpoint();
315        int port = ep.getPort();
316
317        if (server == null) {
318            if (tcpLog.isLoggable(Log.BRIEF)) {
319                tcpLog.log(Log.BRIEF,
320                    "(port " + port + ") create server socket");
321            }
322
323            try {
324                server = ep.newServerSocket();
325                /*
326                 * Don't retry ServerSocket if creation fails since
327                 * "port in use" will cause export to hang if an
328                 * RMIFailureHandler is not installed.
329                 */
330                Thread t = AccessController.doPrivileged(
331                    new NewThreadAction(new AcceptLoop(server),
332                                        "TCP Accept-" + port, true));
333                t.start();
334            } catch (java.net.BindException e) {
335                throw new ExportException("Port already in use: " + port, e);
336            } catch (IOException e) {
337                throw new ExportException("Listen failed on port: " + port, e);
338            }
339
340        } else {
341            // otherwise verify security access to existing server socket
342            SecurityManager sm = System.getSecurityManager();
343            if (sm != null) {
344                sm.checkListen(port);
345            }
346        }
347    }
348
349    /**
350     * Worker for accepting connections from a server socket.
351     **/
352    private class AcceptLoop implements Runnable {
353
354        private final ServerSocket serverSocket;
355
356        // state for throttling loop on exceptions (local to accept thread)
357        private long lastExceptionTime = 0L;
358        private int recentExceptionCount;
359
360        AcceptLoop(ServerSocket serverSocket) {
361            this.serverSocket = serverSocket;
362        }
363
364        public void run() {
365            try {
366                executeAcceptLoop();
367            } finally {
368                try {
369                    /*
370                     * Only one accept loop is started per server
371                     * socket, so after no more connections will be
372                     * accepted, ensure that the server socket is no
373                     * longer listening.
374                     */
375                    serverSocket.close();
376                } catch (IOException e) {
377                }
378            }
379        }
380
381        /**
382         * Accepts connections from the server socket and executes
383         * handlers for them in the thread pool.
384         **/
385        private void executeAcceptLoop() {
386            if (tcpLog.isLoggable(Log.BRIEF)) {
387                tcpLog.log(Log.BRIEF, "listening on port " +
388                           getEndpoint().getPort());
389            }
390
391            while (true) {
392                Socket socket = null;
393                try {
394                    socket = serverSocket.accept();
395
396                    /*
397                     * Find client host name (or "0.0.0.0" if unknown)
398                     */
399                    InetAddress clientAddr = socket.getInetAddress();
400                    String clientHost = (clientAddr != null
401                                         ? clientAddr.getHostAddress()
402                                         : "0.0.0.0");
403
404                    /*
405                     * Execute connection handler in the thread pool,
406                     * which uses non-system threads.
407                     */
408                    try {
409                        connectionThreadPool.execute(
410                            new ConnectionHandler(socket, clientHost));
411                    } catch (RejectedExecutionException e) {
412                        closeSocket(socket);
413                        tcpLog.log(Log.BRIEF,
414                                   "rejected connection from " + clientHost);
415                    }
416
417                } catch (Throwable t) {
418                    try {
419                        /*
420                         * If the server socket has been closed, such
421                         * as because there are no more exported
422                         * objects, then we expect accept to throw an
423                         * exception, so just terminate normally.
424                         */
425                        if (serverSocket.isClosed()) {
426                            break;
427                        }
428
429                        try {
430                            if (tcpLog.isLoggable(Level.WARNING)) {
431                                tcpLog.log(Level.WARNING,
432                                           "accept loop for " + serverSocket +
433                                           " throws", t);
434                            }
435                        } catch (Throwable tt) {
436                        }
437                    } finally {
438                        /*
439                         * Always close the accepted socket (if any)
440                         * if an exception occurs, but only after
441                         * logging an unexpected exception.
442                         */
443                        if (socket != null) {
444                            closeSocket(socket);
445                        }
446                    }
447
448                    /*
449                     * In case we're running out of file descriptors,
450                     * release resources held in caches.
451                     */
452                    if (!(t instanceof SecurityException)) {
453                        try {
454                            TCPEndpoint.shedConnectionCaches();
455                        } catch (Throwable tt) {
456                        }
457                    }
458
459                    /*
460                     * A NoClassDefFoundError can occur if no file
461                     * descriptors are available, in which case this
462                     * loop should not terminate.
463                     */
464                    if (t instanceof Exception ||
465                        t instanceof OutOfMemoryError ||
466                        t instanceof NoClassDefFoundError)
467                    {
468                        if (!continueAfterAcceptFailure(t)) {
469                            return;
470                        }
471                        // continue loop
472                    } else if (t instanceof Error) {
473                        throw (Error) t;
474                    } else {
475                        throw new UndeclaredThrowableException(t);
476                    }
477                }
478            }
479        }
480
481        /**
482         * Returns true if the accept loop should continue after the
483         * specified exception has been caught, or false if the accept
484         * loop should terminate (closing the server socket).  If
485         * there is an RMIFailureHandler, this method returns the
486         * result of passing the specified exception to it; otherwise,
487         * this method always returns true, after sleeping to throttle
488         * the accept loop if necessary.
489         **/
490        private boolean continueAfterAcceptFailure(Throwable t) {
491            RMIFailureHandler fh = RMISocketFactory.getFailureHandler();
492            if (fh != null) {
493                return fh.failure(t instanceof Exception ? (Exception) t :
494                                  new InvocationTargetException(t));
495            } else {
496                throttleLoopOnException();
497                return true;
498            }
499        }
500
501        /**
502         * Throttles the accept loop after an exception has been
503         * caught: if a burst of 10 exceptions in 5 seconds occurs,
504         * then wait for 10 seconds to curb busy CPU usage.
505         **/
506        private void throttleLoopOnException() {
507            long now = System.currentTimeMillis();
508            if (lastExceptionTime == 0L || (now - lastExceptionTime) > 5000) {
509                // last exception was long ago (or this is the first)
510                lastExceptionTime = now;
511                recentExceptionCount = 0;
512            } else {
513                // exception burst window was started recently
514                if (++recentExceptionCount >= 10) {
515                    try {
516                        Thread.sleep(10000);
517                    } catch (InterruptedException ignore) {
518                    }
519                }
520            }
521        }
522    }
523
524    /** close socket and eat exception */
525    private static void closeSocket(Socket sock) {
526        try {
527            sock.close();
528        } catch (IOException ex) {
529            // eat exception
530        }
531    }
532
533    /**
534     * handleMessages decodes transport operations and handles messages
535     * appropriately.  If an exception occurs during message handling,
536     * the socket is closed.
537     */
538    void handleMessages(Connection conn, boolean persistent) {
539        int port = getEndpoint().getPort();
540
541        try {
542            DataInputStream in = new DataInputStream(conn.getInputStream());
543            do {
544                int op = in.read();     // transport op
545                if (op == -1) {
546                    if (tcpLog.isLoggable(Log.BRIEF)) {
547                        tcpLog.log(Log.BRIEF, "(port " +
548                            port + ") connection closed");
549                    }
550                    break;
551                }
552
553                if (tcpLog.isLoggable(Log.BRIEF)) {
554                    tcpLog.log(Log.BRIEF, "(port " + port +
555                        ") op = " + op);
556                }
557
558                switch (op) {
559                case TransportConstants.Call:
560                    // service incoming RMI call
561                    RemoteCall call = new StreamRemoteCall(conn);
562                    if (serviceCall(call) == false)
563                        return;
564                    break;
565
566                case TransportConstants.Ping:
567                    // send ack for ping
568                    DataOutputStream out =
569                        new DataOutputStream(conn.getOutputStream());
570                    out.writeByte(TransportConstants.PingAck);
571                    conn.releaseOutputStream();
572                    break;
573
574                case TransportConstants.DGCAck:
575                    DGCAckHandler.received(UID.read(in));
576                    break;
577
578                default:
579                    throw new IOException("unknown transport op " + op);
580                }
581            } while (persistent);
582
583        } catch (IOException e) {
584            // exception during processing causes connection to close (below)
585            if (tcpLog.isLoggable(Log.BRIEF)) {
586                tcpLog.log(Log.BRIEF, "(port " + port +
587                    ") exception: ", e);
588            }
589        } finally {
590            try {
591                conn.close();
592            } catch (IOException ex) {
593                // eat exception
594            }
595        }
596    }
597
598    /**
599     * Returns the client host for the current thread's connection.  Throws
600     * ServerNotActiveException if no connection is active for this thread.
601     */
602    public static String getClientHost() throws ServerNotActiveException {
603        ConnectionHandler h = threadConnectionHandler.get();
604        if (h != null) {
605            return h.getClientHost();
606        } else {
607            throw new ServerNotActiveException("not in a remote call");
608        }
609    }
610
611    /**
612     * Services messages on accepted connection
613     */
614    private class ConnectionHandler implements Runnable {
615
616        /** int value of "POST" in ASCII (Java's specified data formats
617         *  make this once-reviled tactic again socially acceptable) */
618        private static final int POST = 0x504f5354;
619
620        /** most recently accept-authorized AccessControlContext */
621        private AccessControlContext okContext;
622        /** cache of accept-authorized AccessControlContexts */
623        private Map<AccessControlContext,
624                    Reference<AccessControlContext>> authCache;
625        /** security manager which authorized contexts in authCache */
626        private SecurityManager cacheSecurityManager = null;
627
628        private Socket socket;
629        private String remoteHost;
630
631        ConnectionHandler(Socket socket, String remoteHost) {
632            this.socket = socket;
633            this.remoteHost = remoteHost;
634        }
635
636        String getClientHost() {
637            return remoteHost;
638        }
639
640        /**
641         * Verify that the given AccessControlContext has permission to
642         * accept this connection.
643         */
644        void checkAcceptPermission(SecurityManager sm,
645                                   AccessControlContext acc)
646        {
647            /*
648             * Note: no need to synchronize on cache-related fields, since this
649             * method only gets called from the ConnectionHandler's thread.
650             */
651            if (sm != cacheSecurityManager) {
652                okContext = null;
653                authCache = new WeakHashMap<AccessControlContext,
654                                            Reference<AccessControlContext>>();
655                cacheSecurityManager = sm;
656            }
657            if (acc.equals(okContext) || authCache.containsKey(acc)) {
658                return;
659            }
660            InetAddress addr = socket.getInetAddress();
661            String host = (addr != null) ? addr.getHostAddress() : "*";
662
663            sm.checkAccept(host, socket.getPort());
664
665            authCache.put(acc, new SoftReference<AccessControlContext>(acc));
666            okContext = acc;
667        }
668
669        public void run() {
670            Thread t = Thread.currentThread();
671            String name = t.getName();
672            try {
673                t.setName("RMI TCP Connection(" +
674                          connectionCount.incrementAndGet() +
675                          ")-" + remoteHost);
676                AccessController.doPrivileged((PrivilegedAction<Void>)() -> {
677                    run0();
678                    return null;
679                }, NOPERMS_ACC);
680            } finally {
681                t.setName(name);
682            }
683        }
684
685        @SuppressWarnings("fallthrough")
686        private void run0() {
687            TCPEndpoint endpoint = getEndpoint();
688            int port = endpoint.getPort();
689
690            threadConnectionHandler.set(this);
691
692            // set socket to disable Nagle's algorithm (always send
693            // immediately)
694            // TBD: should this be left up to socket factory instead?
695            try {
696                socket.setTcpNoDelay(true);
697            } catch (Exception e) {
698                // if we fail to set this, ignore and proceed anyway
699            }
700            // set socket to timeout after excessive idle time
701            try {
702                if (connectionReadTimeout > 0)
703                    socket.setSoTimeout(connectionReadTimeout);
704            } catch (Exception e) {
705                // too bad, continue anyway
706            }
707
708            try {
709                InputStream sockIn = socket.getInputStream();
710                InputStream bufIn = sockIn.markSupported()
711                        ? sockIn
712                        : new BufferedInputStream(sockIn);
713
714                // Read magic
715                DataInputStream in = new DataInputStream(bufIn);
716                int magic = in.readInt();
717
718                // read and verify transport header
719                short version = in.readShort();
720                if (magic != TransportConstants.Magic ||
721                    version != TransportConstants.Version) {
722                    // protocol mismatch detected...
723                    // just close socket: this would recurse if we marshal an
724                    // exception to the client and the protocol at other end
725                    // doesn't match.
726                    closeSocket(socket);
727                    return;
728                }
729
730                OutputStream sockOut = socket.getOutputStream();
731                BufferedOutputStream bufOut =
732                    new BufferedOutputStream(sockOut);
733                DataOutputStream out = new DataOutputStream(bufOut);
734
735                int remotePort = socket.getPort();
736
737                if (tcpLog.isLoggable(Log.BRIEF)) {
738                    tcpLog.log(Log.BRIEF, "accepted socket from [" +
739                                     remoteHost + ":" + remotePort + "]");
740                }
741
742                TCPEndpoint ep;
743                TCPChannel ch;
744                TCPConnection conn;
745
746                // send ack (or nack) for protocol
747                byte protocol = in.readByte();
748                switch (protocol) {
749                case TransportConstants.SingleOpProtocol:
750                    // no ack for protocol
751
752                    // create dummy channel for receiving messages
753                    ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
754                                         endpoint.getClientSocketFactory(),
755                                         endpoint.getServerSocketFactory());
756                    ch = new TCPChannel(TCPTransport.this, ep);
757                    conn = new TCPConnection(ch, socket, bufIn, bufOut);
758
759                    // read input messages
760                    handleMessages(conn, false);
761                    break;
762
763                case TransportConstants.StreamProtocol:
764                    // send ack
765                    out.writeByte(TransportConstants.ProtocolAck);
766
767                    // suggest endpoint (in case client doesn't know host name)
768                    if (tcpLog.isLoggable(Log.VERBOSE)) {
769                        tcpLog.log(Log.VERBOSE, "(port " + port +
770                            ") " + "suggesting " + remoteHost + ":" +
771                            remotePort);
772                    }
773
774                    out.writeUTF(remoteHost);
775                    out.writeInt(remotePort);
776                    out.flush();
777
778                    // read and discard (possibly bogus) endpoint
779                    // REMIND: would be faster to read 2 bytes then skip N+4
780                    String clientHost = in.readUTF();
781                    int    clientPort = in.readInt();
782                    if (tcpLog.isLoggable(Log.VERBOSE)) {
783                        tcpLog.log(Log.VERBOSE, "(port " + port +
784                            ") client using " + clientHost + ":" + clientPort);
785                    }
786
787                    // create dummy channel for receiving messages
788                    // (why not use clientHost and clientPort?)
789                    ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
790                                         endpoint.getClientSocketFactory(),
791                                         endpoint.getServerSocketFactory());
792                    ch = new TCPChannel(TCPTransport.this, ep);
793                    conn = new TCPConnection(ch, socket, bufIn, bufOut);
794
795                    // read input messages
796                    handleMessages(conn, true);
797                    break;
798
799                case TransportConstants.MultiplexProtocol:
800                    if (tcpLog.isLoggable(Log.VERBOSE)) {
801                        tcpLog.log(Log.VERBOSE, "(port " + port +
802                                ") rejecting multiplex protocol");
803                    }
804                    // Fall-through to reject use of MultiplexProtocol
805                default:
806                    // protocol not understood, send nack and close socket
807                    out.writeByte(TransportConstants.ProtocolNack);
808                    out.flush();
809                    break;
810                }
811
812            } catch (IOException e) {
813                // socket in unknown state: destroy socket
814                tcpLog.log(Log.BRIEF, "terminated with exception:", e);
815            } finally {
816                closeSocket(socket);
817            }
818        }
819    }
820}
821