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