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}