001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.transport.tcp; 018 019import java.io.IOException; 020import java.net.InetAddress; 021import java.net.InetSocketAddress; 022import java.net.ServerSocket; 023import java.net.Socket; 024import java.net.SocketException; 025import java.net.SocketTimeoutException; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.UnknownHostException; 029import java.nio.channels.ClosedChannelException; 030import java.nio.channels.SelectionKey; 031import java.nio.channels.Selector; 032import java.nio.channels.ServerSocketChannel; 033import java.nio.channels.SocketChannel; 034import java.util.HashMap; 035import java.util.Iterator; 036import java.util.Set; 037import java.util.concurrent.BlockingQueue; 038import java.util.concurrent.LinkedBlockingQueue; 039import java.util.concurrent.TimeUnit; 040import java.util.concurrent.atomic.AtomicInteger; 041 042import javax.net.ServerSocketFactory; 043import javax.net.ssl.SSLServerSocket; 044 045import org.apache.activemq.Service; 046import org.apache.activemq.ThreadPriorities; 047import org.apache.activemq.TransportLoggerSupport; 048import org.apache.activemq.command.BrokerInfo; 049import org.apache.activemq.openwire.OpenWireFormatFactory; 050import org.apache.activemq.transport.Transport; 051import org.apache.activemq.transport.TransportFactory; 052import org.apache.activemq.transport.TransportServer; 053import org.apache.activemq.transport.TransportServerThreadSupport; 054import org.apache.activemq.util.IOExceptionSupport; 055import org.apache.activemq.util.InetAddressUtil; 056import org.apache.activemq.util.IntrospectionSupport; 057import org.apache.activemq.util.ServiceListener; 058import org.apache.activemq.util.ServiceStopper; 059import org.apache.activemq.util.ServiceSupport; 060import org.apache.activemq.wireformat.WireFormat; 061import org.apache.activemq.wireformat.WireFormatFactory; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065/** 066 * A TCP based implementation of {@link TransportServer} 067 */ 068public class TcpTransportServer extends TransportServerThreadSupport implements ServiceListener { 069 070 private static final Logger LOG = LoggerFactory.getLogger(TcpTransportServer.class); 071 protected volatile ServerSocket serverSocket; 072 protected volatile Selector selector; 073 protected int backlog = 5000; 074 protected WireFormatFactory wireFormatFactory = new OpenWireFormatFactory(); 075 protected final TcpTransportFactory transportFactory; 076 protected long maxInactivityDuration = 30000; 077 protected long maxInactivityDurationInitalDelay = 10000; 078 protected int minmumWireFormatVersion; 079 protected boolean useQueueForAccept = true; 080 protected boolean allowLinkStealing; 081 082 /** 083 * trace=true -> the Transport stack where this TcpTransport object will be, will have a TransportLogger layer 084 * trace=false -> the Transport stack where this TcpTransport object will be, will NOT have a TransportLogger layer, 085 * and therefore will never be able to print logging messages. This parameter is most probably set in Connection or 086 * TransportConnector URIs. 087 */ 088 protected boolean trace = false; 089 090 protected int soTimeout = 0; 091 protected int socketBufferSize = 64 * 1024; 092 protected int connectionTimeout = 30000; 093 094 /** 095 * Name of the LogWriter implementation to use. Names are mapped to classes in the 096 * resources/META-INF/services/org/apache/activemq/transport/logwriters directory. This parameter is most probably 097 * set in Connection or TransportConnector URIs. 098 */ 099 protected String logWriterName = TransportLoggerSupport.defaultLogWriterName; 100 101 /** 102 * Specifies if the TransportLogger will be manageable by JMX or not. Also, as long as there is at least 1 103 * TransportLogger which is manageable, a TransportLoggerControl MBean will me created. 104 */ 105 protected boolean dynamicManagement = false; 106 107 /** 108 * startLogging=true -> the TransportLogger object of the Transport stack will initially write messages to the log. 109 * startLogging=false -> the TransportLogger object of the Transport stack will initially NOT write messages to the 110 * log. This parameter only has an effect if trace == true. This parameter is most probably set in Connection or 111 * TransportConnector URIs. 112 */ 113 protected boolean startLogging = true; 114 protected final ServerSocketFactory serverSocketFactory; 115 protected final BlockingQueue<Socket> socketQueue = new LinkedBlockingQueue<Socket>(); 116 protected Thread socketHandlerThread; 117 118 /** 119 * The maximum number of sockets allowed for this server 120 */ 121 protected int maximumConnections = Integer.MAX_VALUE; 122 protected final AtomicInteger currentTransportCount = new AtomicInteger(); 123 124 public TcpTransportServer(TcpTransportFactory transportFactory, URI location, ServerSocketFactory serverSocketFactory) throws IOException, 125 URISyntaxException { 126 super(location); 127 this.transportFactory = transportFactory; 128 this.serverSocketFactory = serverSocketFactory; 129 } 130 131 public void bind() throws IOException { 132 URI bind = getBindLocation(); 133 134 String host = bind.getHost(); 135 host = (host == null || host.length() == 0) ? "localhost" : host; 136 InetAddress addr = InetAddress.getByName(host); 137 138 try { 139 serverSocket = serverSocketFactory.createServerSocket(bind.getPort(), backlog, addr); 140 configureServerSocket(serverSocket); 141 } catch (IOException e) { 142 throw IOExceptionSupport.create("Failed to bind to server socket: " + bind + " due to: " + e, e); 143 } 144 try { 145 setConnectURI(new URI(bind.getScheme(), bind.getUserInfo(), resolveHostName(serverSocket, addr), serverSocket.getLocalPort(), bind.getPath(), 146 bind.getQuery(), bind.getFragment())); 147 } catch (URISyntaxException e) { 148 // it could be that the host name contains invalid characters such 149 // as _ on unix platforms so lets try use the IP address instead 150 try { 151 setConnectURI(new URI(bind.getScheme(), bind.getUserInfo(), addr.getHostAddress(), serverSocket.getLocalPort(), bind.getPath(), 152 bind.getQuery(), bind.getFragment())); 153 } catch (URISyntaxException e2) { 154 throw IOExceptionSupport.create(e2); 155 } 156 } 157 } 158 159 private void configureServerSocket(ServerSocket socket) throws SocketException { 160 socket.setSoTimeout(2000); 161 if (transportOptions != null) { 162 163 // If the enabledCipherSuites option is invalid we don't want to ignore it as the call 164 // to SSLServerSocket to configure it has a side effect on the socket rendering it 165 // useless as all suites are enabled many of which are considered as insecure. We 166 // instead trap that option here and throw an exception. We should really consider 167 // all invalid options as breaking and not start the transport but the current design 168 // doesn't really allow for this. 169 // 170 // see: https://issues.apache.org/jira/browse/AMQ-4582 171 // 172 if (socket instanceof SSLServerSocket) { 173 if (transportOptions.containsKey("enabledCipherSuites")) { 174 Object cipherSuites = transportOptions.remove("enabledCipherSuites"); 175 176 if (!IntrospectionSupport.setProperty(socket, "enabledCipherSuites", cipherSuites)) { 177 throw new SocketException(String.format( 178 "Invalid transport options {enabledCipherSuites=%s}", cipherSuites)); 179 } 180 } 181 } 182 183 IntrospectionSupport.setProperties(socket, transportOptions); 184 } 185 } 186 187 /** 188 * @return Returns the wireFormatFactory. 189 */ 190 public WireFormatFactory getWireFormatFactory() { 191 return wireFormatFactory; 192 } 193 194 /** 195 * @param wireFormatFactory 196 * The wireFormatFactory to set. 197 */ 198 public void setWireFormatFactory(WireFormatFactory wireFormatFactory) { 199 this.wireFormatFactory = wireFormatFactory; 200 } 201 202 /** 203 * Associates a broker info with the transport server so that the transport can do discovery advertisements of the 204 * broker. 205 * 206 * @param brokerInfo 207 */ 208 @Override 209 public void setBrokerInfo(BrokerInfo brokerInfo) { 210 } 211 212 public long getMaxInactivityDuration() { 213 return maxInactivityDuration; 214 } 215 216 public void setMaxInactivityDuration(long maxInactivityDuration) { 217 this.maxInactivityDuration = maxInactivityDuration; 218 } 219 220 public long getMaxInactivityDurationInitalDelay() { 221 return this.maxInactivityDurationInitalDelay; 222 } 223 224 public void setMaxInactivityDurationInitalDelay(long maxInactivityDurationInitalDelay) { 225 this.maxInactivityDurationInitalDelay = maxInactivityDurationInitalDelay; 226 } 227 228 public int getMinmumWireFormatVersion() { 229 return minmumWireFormatVersion; 230 } 231 232 public void setMinmumWireFormatVersion(int minmumWireFormatVersion) { 233 this.minmumWireFormatVersion = minmumWireFormatVersion; 234 } 235 236 public boolean isTrace() { 237 return trace; 238 } 239 240 public void setTrace(boolean trace) { 241 this.trace = trace; 242 } 243 244 public String getLogWriterName() { 245 return logWriterName; 246 } 247 248 public void setLogWriterName(String logFormat) { 249 this.logWriterName = logFormat; 250 } 251 252 public boolean isDynamicManagement() { 253 return dynamicManagement; 254 } 255 256 public void setDynamicManagement(boolean useJmx) { 257 this.dynamicManagement = useJmx; 258 } 259 260 public boolean isStartLogging() { 261 return startLogging; 262 } 263 264 public void setStartLogging(boolean startLogging) { 265 this.startLogging = startLogging; 266 } 267 268 /** 269 * @return the backlog 270 */ 271 public int getBacklog() { 272 return backlog; 273 } 274 275 /** 276 * @param backlog 277 * the backlog to set 278 */ 279 public void setBacklog(int backlog) { 280 this.backlog = backlog; 281 } 282 283 /** 284 * @return the useQueueForAccept 285 */ 286 public boolean isUseQueueForAccept() { 287 return useQueueForAccept; 288 } 289 290 /** 291 * @param useQueueForAccept 292 * the useQueueForAccept to set 293 */ 294 public void setUseQueueForAccept(boolean useQueueForAccept) { 295 this.useQueueForAccept = useQueueForAccept; 296 } 297 298 /** 299 * pull Sockets from the ServerSocket 300 */ 301 @Override 302 public void run() { 303 if (!isStopped() && !isStopping()) { 304 final ServerSocket serverSocket = this.serverSocket; 305 if (serverSocket == null) { 306 onAcceptError(new IOException("Server started without a valid ServerSocket")); 307 } 308 309 final ServerSocketChannel channel = serverSocket.getChannel(); 310 if (channel != null) { 311 doRunWithServerSocketChannel(channel); 312 } else { 313 doRunWithServerSocket(serverSocket); 314 } 315 } 316 } 317 318 private void doRunWithServerSocketChannel(final ServerSocketChannel channel) { 319 try { 320 channel.configureBlocking(false); 321 final Selector selector = Selector.open(); 322 323 try { 324 channel.register(selector, SelectionKey.OP_ACCEPT); 325 } catch (ClosedChannelException ex) { 326 try { 327 selector.close(); 328 } catch (IOException ignore) {} 329 330 throw ex; 331 } 332 333 // Update object instance for later cleanup. 334 this.selector = selector; 335 336 while (!isStopped()) { 337 int count = selector.select(10); 338 if (count == 0) { 339 continue; 340 } 341 342 Set<SelectionKey> keys = selector.selectedKeys(); 343 344 for (Iterator<SelectionKey> i = keys.iterator(); i.hasNext(); ) { 345 final SelectionKey key = i.next(); 346 if (key.isAcceptable()) { 347 try { 348 SocketChannel sc = channel.accept(); 349 if (sc != null) { 350 if (isStopped() || getAcceptListener() == null) { 351 sc.close(); 352 } else { 353 if (useQueueForAccept) { 354 socketQueue.put(sc.socket()); 355 } else { 356 handleSocket(sc.socket()); 357 } 358 } 359 } 360 } catch (SocketTimeoutException ste) { 361 // expect this to happen 362 } catch (Exception e) { 363 e.printStackTrace(); 364 if (!isStopping()) { 365 onAcceptError(e); 366 } else if (!isStopped()) { 367 LOG.warn("run()", e); 368 onAcceptError(e); 369 } 370 } 371 } 372 i.remove(); 373 } 374 } 375 } catch (IOException ex) { 376 if (!isStopping()) { 377 onAcceptError(ex); 378 } else if (!isStopped()) { 379 LOG.warn("run()", ex); 380 onAcceptError(ex); 381 } 382 } 383 } 384 385 private void doRunWithServerSocket(final ServerSocket serverSocket) { 386 while (!isStopped()) { 387 Socket socket = null; 388 try { 389 socket = serverSocket.accept(); 390 if (socket != null) { 391 if (isStopped() || getAcceptListener() == null) { 392 socket.close(); 393 } else { 394 if (useQueueForAccept) { 395 socketQueue.put(socket); 396 } else { 397 handleSocket(socket); 398 } 399 } 400 } 401 } catch (SocketTimeoutException ste) { 402 // expect this to happen 403 } catch (Exception e) { 404 if (!isStopping()) { 405 onAcceptError(e); 406 } else if (!isStopped()) { 407 LOG.warn("run()", e); 408 onAcceptError(e); 409 } 410 } 411 } 412 } 413 414 /** 415 * Allow derived classes to override the Transport implementation that this transport server creates. 416 * 417 * @param socket 418 * @param format 419 * 420 * @return a new Transport instance. 421 * 422 * @throws IOException 423 */ 424 protected Transport createTransport(Socket socket, WireFormat format) throws IOException { 425 return new TcpTransport(format, socket); 426 } 427 428 /** 429 * @return pretty print of this 430 */ 431 @Override 432 public String toString() { 433 return "" + getBindLocation(); 434 } 435 436 /** 437 * @param socket 438 * @param bindAddress 439 * @return real hostName 440 * @throws UnknownHostException 441 */ 442 protected String resolveHostName(ServerSocket socket, InetAddress bindAddress) throws UnknownHostException { 443 String result = null; 444 if (socket.isBound()) { 445 if (socket.getInetAddress().isAnyLocalAddress()) { 446 // make it more human readable and useful, an alternative to 0.0.0.0 447 result = InetAddressUtil.getLocalHostName(); 448 } else { 449 result = socket.getInetAddress().getCanonicalHostName(); 450 } 451 } else { 452 result = bindAddress.getCanonicalHostName(); 453 } 454 return result; 455 } 456 457 @Override 458 protected void doStart() throws Exception { 459 if (useQueueForAccept) { 460 Runnable run = new Runnable() { 461 @Override 462 public void run() { 463 try { 464 while (!isStopped() && !isStopping()) { 465 Socket sock = socketQueue.poll(1, TimeUnit.SECONDS); 466 if (sock != null) { 467 try { 468 handleSocket(sock); 469 } catch (Throwable thrown) { 470 if (!isStopping()) { 471 onAcceptError(new Exception(thrown)); 472 } else if (!isStopped()) { 473 LOG.warn("Unexpected error thrown during accept handling: ", thrown); 474 onAcceptError(new Exception(thrown)); 475 } 476 } 477 } 478 } 479 480 } catch (InterruptedException e) { 481 if (!isStopped() || !isStopping()) { 482 LOG.info("socketQueue interrupted - stopping"); 483 onAcceptError(e); 484 } 485 } 486 } 487 }; 488 socketHandlerThread = new Thread(null, run, "ActiveMQ Transport Server Thread Handler: " + toString(), getStackSize()); 489 socketHandlerThread.setDaemon(true); 490 socketHandlerThread.setPriority(ThreadPriorities.BROKER_MANAGEMENT - 1); 491 socketHandlerThread.start(); 492 } 493 super.doStart(); 494 } 495 496 @Override 497 protected void doStop(ServiceStopper stopper) throws Exception { 498 499 Exception firstFailure = null; 500 501 try { 502 if (selector != null) { 503 selector.close(); 504 selector = null; 505 } 506 } catch (Exception error) { 507 } 508 509 try { 510 final ServerSocket serverSocket = this.serverSocket; 511 if (serverSocket != null) { 512 this.serverSocket = null; 513 serverSocket.close(); 514 } 515 } catch (Exception error) { 516 firstFailure = error; 517 } 518 519 if (socketHandlerThread != null) { 520 socketHandlerThread.interrupt(); 521 socketHandlerThread = null; 522 } 523 524 try { 525 super.doStop(stopper); 526 } catch (Exception error) { 527 if (firstFailure != null) { 528 firstFailure = error; 529 } 530 } 531 532 if (firstFailure != null) { 533 throw firstFailure; 534 } 535 } 536 537 @Override 538 public InetSocketAddress getSocketAddress() { 539 return (InetSocketAddress) serverSocket.getLocalSocketAddress(); 540 } 541 542 protected void handleSocket(Socket socket) { 543 doHandleSocket(socket); 544 } 545 546 final protected void doHandleSocket(Socket socket) { 547 boolean closeSocket = true; 548 boolean countIncremented = false; 549 try { 550 int currentCount; 551 do { 552 currentCount = currentTransportCount.get(); 553 if (currentCount >= this.maximumConnections) { 554 throw new ExceededMaximumConnectionsException( 555 "Exceeded the maximum number of allowed client connections. See the '" + 556 "maximumConnections' property on the TCP transport configuration URI " + 557 "in the ActiveMQ configuration file (e.g., activemq.xml)"); 558 } 559 560 //Increment this value before configuring the transport 561 //This is necessary because some of the transport servers must read from the 562 //socket during configureTransport() so we want to make sure this value is 563 //accurate as the transport server could pause here waiting for data to be sent from a client 564 } while(!currentTransportCount.compareAndSet(currentCount, currentCount + 1)); 565 countIncremented = true; 566 567 HashMap<String, Object> options = new HashMap<String, Object>(); 568 options.put("maxInactivityDuration", Long.valueOf(maxInactivityDuration)); 569 options.put("maxInactivityDurationInitalDelay", Long.valueOf(maxInactivityDurationInitalDelay)); 570 options.put("minmumWireFormatVersion", Integer.valueOf(minmumWireFormatVersion)); 571 options.put("trace", Boolean.valueOf(trace)); 572 options.put("soTimeout", Integer.valueOf(soTimeout)); 573 options.put("socketBufferSize", Integer.valueOf(socketBufferSize)); 574 options.put("connectionTimeout", Integer.valueOf(connectionTimeout)); 575 options.put("logWriterName", logWriterName); 576 options.put("dynamicManagement", Boolean.valueOf(dynamicManagement)); 577 options.put("startLogging", Boolean.valueOf(startLogging)); 578 options.putAll(transportOptions); 579 580 TransportInfo transportInfo = configureTransport(this, socket); 581 closeSocket = false; 582 583 if (transportInfo.transport instanceof ServiceSupport) { 584 ((ServiceSupport) transportInfo.transport).addServiceListener(this); 585 } 586 587 Transport configuredTransport = transportInfo.transportFactory.serverConfigure( 588 transportInfo.transport, transportInfo.format, options); 589 590 getAcceptListener().onAccept(configuredTransport); 591 592 } catch (SocketTimeoutException ste) { 593 // expect this to happen 594 } catch (Exception e) { 595 if (closeSocket) { 596 try { 597 //if closing the socket, only decrement the count it was actually incremented 598 //where it was incremented 599 if (countIncremented) { 600 currentTransportCount.decrementAndGet(); 601 } 602 socket.close(); 603 } catch (Exception ignore) { 604 } 605 } 606 607 if (!isStopping()) { 608 onAcceptError(e); 609 } else if (!isStopped()) { 610 LOG.warn("run()", e); 611 onAcceptError(e); 612 } 613 } 614 } 615 616 protected TransportInfo configureTransport(final TcpTransportServer server, final Socket socket) throws Exception { 617 WireFormat format = wireFormatFactory.createWireFormat(); 618 Transport transport = createTransport(socket, format); 619 return new TransportInfo(format, transport, transportFactory); 620 } 621 622 protected class TransportInfo { 623 final WireFormat format; 624 final Transport transport; 625 final TransportFactory transportFactory; 626 627 public TransportInfo(WireFormat format, Transport transport, TransportFactory transportFactory) { 628 this.format = format; 629 this.transport = transport; 630 this.transportFactory = transportFactory; 631 } 632 } 633 634 public int getSoTimeout() { 635 return soTimeout; 636 } 637 638 public void setSoTimeout(int soTimeout) { 639 this.soTimeout = soTimeout; 640 } 641 642 public int getSocketBufferSize() { 643 return socketBufferSize; 644 } 645 646 public void setSocketBufferSize(int socketBufferSize) { 647 this.socketBufferSize = socketBufferSize; 648 } 649 650 public int getConnectionTimeout() { 651 return connectionTimeout; 652 } 653 654 public void setConnectionTimeout(int connectionTimeout) { 655 this.connectionTimeout = connectionTimeout; 656 } 657 658 /** 659 * @return the maximumConnections 660 */ 661 public int getMaximumConnections() { 662 return maximumConnections; 663 } 664 665 /** 666 * @param maximumConnections 667 * the maximumConnections to set 668 */ 669 public void setMaximumConnections(int maximumConnections) { 670 this.maximumConnections = maximumConnections; 671 } 672 673 public AtomicInteger getCurrentTransportCount() { 674 return currentTransportCount; 675 } 676 677 @Override 678 public void started(Service service) { 679 } 680 681 @Override 682 public void stopped(Service service) { 683 this.currentTransportCount.decrementAndGet(); 684 } 685 686 @Override 687 public boolean isSslServer() { 688 return false; 689 } 690 691 @Override 692 public boolean isAllowLinkStealing() { 693 return allowLinkStealing; 694 } 695 696 @Override 697 public void setAllowLinkStealing(boolean allowLinkStealing) { 698 this.allowLinkStealing = allowLinkStealing; 699 } 700}