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.SelectionKey; 030import java.nio.channels.ServerSocketChannel; 031import java.nio.channels.SocketChannel; 032import java.util.HashMap; 033import java.util.concurrent.BlockingQueue; 034import java.util.concurrent.LinkedBlockingQueue; 035import java.util.concurrent.TimeUnit; 036import java.util.concurrent.atomic.AtomicInteger; 037 038import javax.net.ServerSocketFactory; 039import javax.net.ssl.SSLServerSocket; 040 041import org.apache.activemq.Service; 042import org.apache.activemq.ThreadPriorities; 043import org.apache.activemq.TransportLoggerSupport; 044import org.apache.activemq.command.BrokerInfo; 045import org.apache.activemq.openwire.OpenWireFormatFactory; 046import org.apache.activemq.transport.Transport; 047import org.apache.activemq.transport.TransportServer; 048import org.apache.activemq.transport.TransportServerThreadSupport; 049import org.apache.activemq.transport.nio.SelectorManager; 050import org.apache.activemq.transport.nio.SelectorSelection; 051import org.apache.activemq.util.IOExceptionSupport; 052import org.apache.activemq.util.InetAddressUtil; 053import org.apache.activemq.util.IntrospectionSupport; 054import org.apache.activemq.util.ServiceListener; 055import org.apache.activemq.util.ServiceStopper; 056import org.apache.activemq.util.ServiceSupport; 057import org.apache.activemq.wireformat.WireFormat; 058import org.apache.activemq.wireformat.WireFormatFactory; 059import org.slf4j.Logger; 060import org.slf4j.LoggerFactory; 061 062/** 063 * A TCP based implementation of {@link TransportServer} 064 */ 065public class TcpTransportServer extends TransportServerThreadSupport implements ServiceListener { 066 067 private static final Logger LOG = LoggerFactory.getLogger(TcpTransportServer.class); 068 protected ServerSocket serverSocket; 069 protected SelectorSelection selector; 070 protected int backlog = 5000; 071 protected WireFormatFactory wireFormatFactory = new OpenWireFormatFactory(); 072 protected final TcpTransportFactory transportFactory; 073 protected long maxInactivityDuration = 30000; 074 protected long maxInactivityDurationInitalDelay = 10000; 075 protected int minmumWireFormatVersion; 076 protected boolean useQueueForAccept = true; 077 protected boolean allowLinkStealing; 078 079 /** 080 * trace=true -> the Transport stack where this TcpTransport object will be, will have a TransportLogger layer 081 * trace=false -> the Transport stack where this TcpTransport object will be, will NOT have a TransportLogger layer, 082 * and therefore will never be able to print logging messages. This parameter is most probably set in Connection or 083 * TransportConnector URIs. 084 */ 085 protected boolean trace = false; 086 087 protected int soTimeout = 0; 088 protected int socketBufferSize = 64 * 1024; 089 protected int connectionTimeout = 30000; 090 091 /** 092 * Name of the LogWriter implementation to use. Names are mapped to classes in the 093 * resources/META-INF/services/org/apache/activemq/transport/logwriters directory. This parameter is most probably 094 * set in Connection or TransportConnector URIs. 095 */ 096 protected String logWriterName = TransportLoggerSupport.defaultLogWriterName; 097 098 /** 099 * Specifies if the TransportLogger will be manageable by JMX or not. Also, as long as there is at least 1 100 * TransportLogger which is manageable, a TransportLoggerControl MBean will me created. 101 */ 102 protected boolean dynamicManagement = false; 103 104 /** 105 * startLogging=true -> the TransportLogger object of the Transport stack will initially write messages to the log. 106 * startLogging=false -> the TransportLogger object of the Transport stack will initially NOT write messages to the 107 * log. This parameter only has an effect if trace == true. This parameter is most probably set in Connection or 108 * TransportConnector URIs. 109 */ 110 protected boolean startLogging = true; 111 protected final ServerSocketFactory serverSocketFactory; 112 protected BlockingQueue<Socket> socketQueue = new LinkedBlockingQueue<Socket>(); 113 protected Thread socketHandlerThread; 114 115 /** 116 * The maximum number of sockets allowed for this server 117 */ 118 protected int maximumConnections = Integer.MAX_VALUE; 119 protected AtomicInteger currentTransportCount = new AtomicInteger(); 120 121 public TcpTransportServer(TcpTransportFactory transportFactory, URI location, ServerSocketFactory serverSocketFactory) throws IOException, 122 URISyntaxException { 123 super(location); 124 this.transportFactory = transportFactory; 125 this.serverSocketFactory = serverSocketFactory; 126 } 127 128 public void bind() throws IOException { 129 URI bind = getBindLocation(); 130 131 String host = bind.getHost(); 132 host = (host == null || host.length() == 0) ? "localhost" : host; 133 InetAddress addr = InetAddress.getByName(host); 134 135 try { 136 this.serverSocket = serverSocketFactory.createServerSocket(bind.getPort(), backlog, addr); 137 configureServerSocket(this.serverSocket); 138 } catch (IOException e) { 139 throw IOExceptionSupport.create("Failed to bind to server socket: " + bind + " due to: " + e, e); 140 } 141 try { 142 setConnectURI(new URI(bind.getScheme(), bind.getUserInfo(), resolveHostName(serverSocket, addr), serverSocket.getLocalPort(), bind.getPath(), 143 bind.getQuery(), bind.getFragment())); 144 } catch (URISyntaxException e) { 145 146 // it could be that the host name contains invalid characters such 147 // as _ on unix platforms so lets try use the IP address instead 148 try { 149 setConnectURI(new URI(bind.getScheme(), bind.getUserInfo(), addr.getHostAddress(), serverSocket.getLocalPort(), bind.getPath(), 150 bind.getQuery(), bind.getFragment())); 151 } catch (URISyntaxException e2) { 152 throw IOExceptionSupport.create(e2); 153 } 154 } 155 } 156 157 private void configureServerSocket(ServerSocket socket) throws SocketException { 158 socket.setSoTimeout(2000); 159 if (transportOptions != null) { 160 161 // If the enabledCipherSuites option is invalid we don't want to ignore it as the call 162 // to SSLServerSocket to configure it has a side effect on the socket rendering it 163 // useless as all suites are enabled many of which are considered as insecure. We 164 // instead trap that option here and throw an exception. We should really consider 165 // all invalid options as breaking and not start the transport but the current design 166 // doesn't really allow for this. 167 // 168 // see: https://issues.apache.org/jira/browse/AMQ-4582 169 // 170 if (socket instanceof SSLServerSocket) { 171 if (transportOptions.containsKey("enabledCipherSuites")) { 172 Object cipherSuites = transportOptions.remove("enabledCipherSuites"); 173 174 if (!IntrospectionSupport.setProperty(socket, "enabledCipherSuites", cipherSuites)) { 175 throw new SocketException(String.format( 176 "Invalid transport options {enabledCipherSuites=%s}", cipherSuites)); 177 } 178 } 179 } 180 181 IntrospectionSupport.setProperties(socket, transportOptions); 182 } 183 } 184 185 /** 186 * @return Returns the wireFormatFactory. 187 */ 188 public WireFormatFactory getWireFormatFactory() { 189 return wireFormatFactory; 190 } 191 192 /** 193 * @param wireFormatFactory 194 * The wireFormatFactory to set. 195 */ 196 public void setWireFormatFactory(WireFormatFactory wireFormatFactory) { 197 this.wireFormatFactory = wireFormatFactory; 198 } 199 200 /** 201 * Associates a broker info with the transport server so that the transport can do discovery advertisements of the 202 * broker. 203 * 204 * @param brokerInfo 205 */ 206 @Override 207 public void setBrokerInfo(BrokerInfo brokerInfo) { 208 } 209 210 public long getMaxInactivityDuration() { 211 return maxInactivityDuration; 212 } 213 214 public void setMaxInactivityDuration(long maxInactivityDuration) { 215 this.maxInactivityDuration = maxInactivityDuration; 216 } 217 218 public long getMaxInactivityDurationInitalDelay() { 219 return this.maxInactivityDurationInitalDelay; 220 } 221 222 public void setMaxInactivityDurationInitalDelay(long maxInactivityDurationInitalDelay) { 223 this.maxInactivityDurationInitalDelay = maxInactivityDurationInitalDelay; 224 } 225 226 public int getMinmumWireFormatVersion() { 227 return minmumWireFormatVersion; 228 } 229 230 public void setMinmumWireFormatVersion(int minmumWireFormatVersion) { 231 this.minmumWireFormatVersion = minmumWireFormatVersion; 232 } 233 234 public boolean isTrace() { 235 return trace; 236 } 237 238 public void setTrace(boolean trace) { 239 this.trace = trace; 240 } 241 242 public String getLogWriterName() { 243 return logWriterName; 244 } 245 246 public void setLogWriterName(String logFormat) { 247 this.logWriterName = logFormat; 248 } 249 250 public boolean isDynamicManagement() { 251 return dynamicManagement; 252 } 253 254 public void setDynamicManagement(boolean useJmx) { 255 this.dynamicManagement = useJmx; 256 } 257 258 public boolean isStartLogging() { 259 return startLogging; 260 } 261 262 public void setStartLogging(boolean startLogging) { 263 this.startLogging = startLogging; 264 } 265 266 /** 267 * @return the backlog 268 */ 269 public int getBacklog() { 270 return backlog; 271 } 272 273 /** 274 * @param backlog 275 * the backlog to set 276 */ 277 public void setBacklog(int backlog) { 278 this.backlog = backlog; 279 } 280 281 /** 282 * @return the useQueueForAccept 283 */ 284 public boolean isUseQueueForAccept() { 285 return useQueueForAccept; 286 } 287 288 /** 289 * @param useQueueForAccept 290 * the useQueueForAccept to set 291 */ 292 public void setUseQueueForAccept(boolean useQueueForAccept) { 293 this.useQueueForAccept = useQueueForAccept; 294 } 295 296 /** 297 * pull Sockets from the ServerSocket 298 */ 299 @Override 300 public void run() { 301 final ServerSocketChannel chan = serverSocket.getChannel(); 302 if (chan != null) { 303 try { 304 chan.configureBlocking(false); 305 selector = SelectorManager.getInstance().register(chan, new SelectorManager.Listener() { 306 @Override 307 public void onSelect(SelectorSelection sel) { 308 try { 309 SocketChannel sc = chan.accept(); 310 if (sc != null) { 311 if (isStopped() || getAcceptListener() == null) { 312 sc.close(); 313 } else { 314 if (useQueueForAccept) { 315 socketQueue.put(sc.socket()); 316 } else { 317 handleSocket(sc.socket()); 318 } 319 } 320 } 321 } catch (Exception e) { 322 onError(sel, e); 323 } 324 } 325 @Override 326 public void onError(SelectorSelection sel, Throwable error) { 327 Exception e = null; 328 if (error instanceof Exception) { 329 e = (Exception)error; 330 } else { 331 e = new Exception(error); 332 } 333 if (!isStopping()) { 334 onAcceptError(e); 335 } else if (!isStopped()) { 336 LOG.warn("run()", e); 337 onAcceptError(e); 338 } 339 } 340 }); 341 selector.setInterestOps(SelectionKey.OP_ACCEPT); 342 selector.enable(); 343 } catch (IOException ex) { 344 selector = null; 345 } 346 } else { 347 while (!isStopped()) { 348 Socket socket = null; 349 try { 350 socket = serverSocket.accept(); 351 if (socket != null) { 352 if (isStopped() || getAcceptListener() == null) { 353 socket.close(); 354 } else { 355 if (useQueueForAccept) { 356 socketQueue.put(socket); 357 } else { 358 handleSocket(socket); 359 } 360 } 361 } 362 } catch (SocketTimeoutException ste) { 363 // expect this to happen 364 } catch (Exception e) { 365 if (!isStopping()) { 366 onAcceptError(e); 367 } else if (!isStopped()) { 368 LOG.warn("run()", e); 369 onAcceptError(e); 370 } 371 } 372 } 373 } 374 } 375 376 /** 377 * Allow derived classes to override the Transport implementation that this transport server creates. 378 * 379 * @param socket 380 * @param format 381 * 382 * @return a new Transport instance. 383 * 384 * @throws IOException 385 */ 386 protected Transport createTransport(Socket socket, WireFormat format) throws IOException { 387 return new TcpTransport(format, socket); 388 } 389 390 /** 391 * @return pretty print of this 392 */ 393 @Override 394 public String toString() { 395 return "" + getBindLocation(); 396 } 397 398 /** 399 * @param socket 400 * @param bindAddress 401 * @return real hostName 402 * @throws UnknownHostException 403 */ 404 protected String resolveHostName(ServerSocket socket, InetAddress bindAddress) throws UnknownHostException { 405 String result = null; 406 if (socket.isBound()) { 407 if (socket.getInetAddress().isAnyLocalAddress()) { 408 // make it more human readable and useful, an alternative to 0.0.0.0 409 result = InetAddressUtil.getLocalHostName(); 410 } else { 411 result = socket.getInetAddress().getCanonicalHostName(); 412 } 413 } else { 414 result = bindAddress.getCanonicalHostName(); 415 } 416 return result; 417 } 418 419 @Override 420 protected void doStart() throws Exception { 421 if (useQueueForAccept) { 422 Runnable run = new Runnable() { 423 @Override 424 public void run() { 425 try { 426 while (!isStopped() && !isStopping()) { 427 Socket sock = socketQueue.poll(1, TimeUnit.SECONDS); 428 if (sock != null) { 429 try { 430 handleSocket(sock); 431 } catch (Throwable thrown) { 432 if (!isStopping()) { 433 onAcceptError(new Exception(thrown)); 434 } else if (!isStopped()) { 435 LOG.warn("Unexpected error thrown during accept handling: ", thrown); 436 onAcceptError(new Exception(thrown)); 437 } 438 } 439 } 440 } 441 442 } catch (InterruptedException e) { 443 LOG.info("socketQueue interuppted - stopping"); 444 if (!isStopping()) { 445 onAcceptError(e); 446 } 447 } 448 } 449 }; 450 socketHandlerThread = new Thread(null, run, "ActiveMQ Transport Server Thread Handler: " + toString(), getStackSize()); 451 socketHandlerThread.setDaemon(true); 452 socketHandlerThread.setPriority(ThreadPriorities.BROKER_MANAGEMENT - 1); 453 socketHandlerThread.start(); 454 } 455 super.doStart(); 456 } 457 458 @Override 459 protected void doStop(ServiceStopper stopper) throws Exception { 460 if (selector != null) { 461 selector.disable(); 462 selector.close(); 463 selector = null; 464 } 465 if (serverSocket != null) { 466 serverSocket.close(); 467 serverSocket = null; 468 } 469 if (socketHandlerThread != null) { 470 socketHandlerThread.interrupt(); 471 socketHandlerThread = null; 472 } 473 super.doStop(stopper); 474 } 475 476 @Override 477 public InetSocketAddress getSocketAddress() { 478 return (InetSocketAddress) serverSocket.getLocalSocketAddress(); 479 } 480 481 protected final void handleSocket(Socket socket) { 482 boolean closeSocket = true; 483 try { 484 if (this.currentTransportCount.get() >= this.maximumConnections) { 485 throw new ExceededMaximumConnectionsException( 486 "Exceeded the maximum number of allowed client connections. See the '" + 487 "maximumConnections' property on the TCP transport configuration URI " + 488 "in the ActiveMQ configuration file (e.g., activemq.xml)"); 489 } else { 490 HashMap<String, Object> options = new HashMap<String, Object>(); 491 options.put("maxInactivityDuration", Long.valueOf(maxInactivityDuration)); 492 options.put("maxInactivityDurationInitalDelay", Long.valueOf(maxInactivityDurationInitalDelay)); 493 options.put("minmumWireFormatVersion", Integer.valueOf(minmumWireFormatVersion)); 494 options.put("trace", Boolean.valueOf(trace)); 495 options.put("soTimeout", Integer.valueOf(soTimeout)); 496 options.put("socketBufferSize", Integer.valueOf(socketBufferSize)); 497 options.put("connectionTimeout", Integer.valueOf(connectionTimeout)); 498 options.put("logWriterName", logWriterName); 499 options.put("dynamicManagement", Boolean.valueOf(dynamicManagement)); 500 options.put("startLogging", Boolean.valueOf(startLogging)); 501 options.putAll(transportOptions); 502 503 WireFormat format = wireFormatFactory.createWireFormat(); 504 Transport transport = createTransport(socket, format); 505 closeSocket = false; 506 507 if (transport instanceof ServiceSupport) { 508 ((ServiceSupport) transport).addServiceListener(this); 509 } 510 511 Transport configuredTransport = transportFactory.serverConfigure(transport, format, options); 512 513 getAcceptListener().onAccept(configuredTransport); 514 currentTransportCount.incrementAndGet(); 515 } 516 } catch (SocketTimeoutException ste) { 517 // expect this to happen 518 } catch (Exception e) { 519 if (closeSocket) { 520 try { 521 socket.close(); 522 } catch (Exception ignore) { 523 } 524 } 525 526 if (!isStopping()) { 527 onAcceptError(e); 528 } else if (!isStopped()) { 529 LOG.warn("run()", e); 530 onAcceptError(e); 531 } 532 } 533 } 534 535 public int getSoTimeout() { 536 return soTimeout; 537 } 538 539 public void setSoTimeout(int soTimeout) { 540 this.soTimeout = soTimeout; 541 } 542 543 public int getSocketBufferSize() { 544 return socketBufferSize; 545 } 546 547 public void setSocketBufferSize(int socketBufferSize) { 548 this.socketBufferSize = socketBufferSize; 549 } 550 551 public int getConnectionTimeout() { 552 return connectionTimeout; 553 } 554 555 public void setConnectionTimeout(int connectionTimeout) { 556 this.connectionTimeout = connectionTimeout; 557 } 558 559 /** 560 * @return the maximumConnections 561 */ 562 public int getMaximumConnections() { 563 return maximumConnections; 564 } 565 566 /** 567 * @param maximumConnections 568 * the maximumConnections to set 569 */ 570 public void setMaximumConnections(int maximumConnections) { 571 this.maximumConnections = maximumConnections; 572 } 573 574 @Override 575 public void started(Service service) { 576 } 577 578 @Override 579 public void stopped(Service service) { 580 this.currentTransportCount.decrementAndGet(); 581 } 582 583 @Override 584 public boolean isSslServer() { 585 return false; 586 } 587 588 @Override 589 public boolean isAllowLinkStealing() { 590 return allowLinkStealing; 591 } 592 593 @Override 594 public void setAllowLinkStealing(boolean allowLinkStealing) { 595 this.allowLinkStealing = allowLinkStealing; 596 } 597}