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}