All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.remoting3.remote.RemoteConnectionProvider Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.remoting3.remote;

import static org.jboss.remoting3._private.Messages.log;
import static org.xnio.IoUtils.safeClose;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.UnaryOperator;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.security.sasl.SaslClientFactory;

import org.jboss.remoting3.spi.AbstractHandleableCloseable;
import org.jboss.remoting3.spi.ConnectionHandlerFactory;
import org.jboss.remoting3.spi.ConnectionProvider;
import org.jboss.remoting3.spi.ConnectionProviderContext;
import org.jboss.remoting3.spi.NetworkServerProvider;
import org.wildfly.common.Assert;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
import org.wildfly.security.auth.server.SaslAuthenticationFactory;
import org.xnio.Cancellable;
import org.xnio.ChannelListener;
import org.xnio.FutureResult;
import org.xnio.IoFuture;
import org.xnio.IoUtils;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.xnio.Result;
import org.xnio.StreamConnection;
import org.xnio.XnioWorker;
import org.xnio.channels.AcceptingChannel;
import org.xnio.channels.SslChannel;
import org.xnio.ssl.JsseSslConnection;
import org.xnio.ssl.JsseSslStreamConnection;
import org.xnio.ssl.JsseXnioSsl;
import org.xnio.ssl.SslConnection;

/**
 * @author David M. Lloyd
 */
class RemoteConnectionProvider extends AbstractHandleableCloseable implements ConnectionProvider {

    static final boolean USE_POOLING;
    static final boolean LEAK_DEBUGGING;

    static {
        boolean usePooling = true;
        boolean leakDebugging = false;
        try {
            usePooling = Boolean.parseBoolean(System.getProperty("jboss.remoting.pooled-buffers", "true"));
            leakDebugging = Boolean.parseBoolean(System.getProperty("jboss.remoting.debug-buffer-leaks", "false"));
        } catch (Throwable ignored) {}
        USE_POOLING = usePooling;
        LEAK_DEBUGGING = leakDebugging;
    }

    private final ProviderInterface providerInterface = new ProviderInterface();
    private final XnioWorker xnioWorker;
    private final ConnectionProviderContext connectionProviderContext;
    private final boolean sslRequired;
    private final Collection pendingInboundConnections = Collections.synchronizedSet(new HashSet());
    private final Set handlers = Collections.synchronizedSet(new HashSet());
    private final MBeanServer server;
    private final ObjectName objectName;

    RemoteConnectionProvider(final OptionMap optionMap, final ConnectionProviderContext connectionProviderContext, final String protocolName) throws IOException {
        super(connectionProviderContext.getExecutor());
        sslRequired = optionMap.get(Options.SECURE, false);
        xnioWorker = connectionProviderContext.getXnioWorker();
        this.connectionProviderContext = connectionProviderContext;
        MBeanServer server = null;
        ObjectName objectName = null;
        try {
            server = ManagementFactory.getPlatformMBeanServer();
            final String endpointName = connectionProviderContext.getEndpoint().getName();
            String name;
            if (endpointName == null) {
                name = "Remoting (anonymous) " + protocolName;
            } else {
                name = "Remoting-" + endpointName + "-" + protocolName;
            }
            objectName = new ObjectName("jboss.remoting.handler", "name", name + "-" + hashCode());
            server.registerMBean(new RemoteConnectionProviderMXBean() {
                public void dumpConnectionState() {
                    doDumpConnectionState();
                }

                public String dumpConnectionStateToString() {
                    return doGetConnectionState();
                }

                public boolean isOpen() {
                    return RemoteConnectionProvider.super.isOpen();
                }
            }, objectName);
        } catch (Exception e) {
            // ignore
        }
        this.server = server;
        this.objectName = objectName;
    }

    private void doDumpConnectionState() {
        final StringBuilder b = new StringBuilder();
        doGetConnectionState(b);
        log.info(b);
    }

    private void doGetConnectionState(final StringBuilder b) {
        b.append("Connection state for ").append(this).append(':').append('\n');
        synchronized (handlers) {
            for (RemoteConnectionHandler handler : handlers) {
                handler.dumpState(b);
            }
        }
    }

    private String doGetConnectionState() {
        final StringBuilder b = new StringBuilder();
        doGetConnectionState(b);
        return b.toString();
    }

    public Cancellable connect(final URI destination, final SocketAddress bindAddress, final OptionMap connectOptions, final Result result, final AuthenticationConfiguration authenticationConfiguration, final SSLContext sslContext, final UnaryOperator saslClientFactoryOperator, final Collection serverMechs) {
        if (! isOpen()) {
            throw new IllegalStateException("Connection provider is closed");
        }
        Assert.checkNotNullParam("destination", destination);
        Assert.checkNotNullParam("connectOptions", connectOptions);
        Assert.checkNotNullParam("result", result);
        Assert.checkNotNullParam("authenticationConfiguration", authenticationConfiguration);
        Assert.checkNotNullParam("saslClientFactoryOperator", saslClientFactoryOperator);
        if (sslRequired) Assert.checkNotNullParam("sslContext", sslContext);
        log.tracef("Attempting to connect to \"%s\" with options %s", destination, connectOptions);
        // cancellable that will be returned by this method
        final FutureResult cancellableResult = new FutureResult();
        cancellableResult.addCancelHandler(new Cancellable() {
            @Override
            public Cancellable cancel() {
                cancellableResult.setCancelled();
                return this;
            }
        });
        final IoFuture returnedFuture = cancellableResult.getIoFuture();
        returnedFuture.addNotifier(IoUtils.resultNotifier(), result);
        final boolean useSsl = sslRequired || connectOptions.get(Options.SSL_ENABLED, true);
        final ChannelListener openListener = new ChannelListener() {
            public void handleEvent(final StreamConnection connection) {
                try {
                    connection.setOption(Options.TCP_NODELAY, Boolean.TRUE);
                } catch (IOException e) {
                    // ignore
                }
                final SslChannel sslChannel = connection instanceof SslChannel ? (SslChannel) connection : null;
                final RemoteConnection remoteConnection = new RemoteConnection(connection, sslChannel, connectOptions, RemoteConnectionProvider.this);
                cancellableResult.addCancelHandler(new Cancellable() {
                    @Override
                    public Cancellable cancel() {
                        RemoteConnectionHandler.sendCloseRequestBody(remoteConnection);
                        remoteConnection.handlePreAuthCloseRequest();
                        return this;
                    }
                });
                if (connection.isOpen()) {
                    remoteConnection.setResult(cancellableResult);
                    connection.getSinkChannel().setWriteListener(remoteConnection.getWriteListener());
                    final ClientConnectionOpenListener openListener = new ClientConnectionOpenListener(destination, remoteConnection, connectionProviderContext, authenticationConfiguration, saslClientFactoryOperator, serverMechs, connectOptions);
                    openListener.handleEvent(connection.getSourceChannel());
                }
            }
        };
        final AuthenticationContextConfigurationClient configurationClient = ClientConnectionOpenListener.AUTH_CONFIGURATION_CLIENT;
        final InetSocketAddress address = configurationClient.getDestinationInetSocketAddress(destination, authenticationConfiguration, 0);
        final IoFuture future;
        if (useSsl) {
            future = createSslConnection(destination, (InetSocketAddress) bindAddress, address, connectOptions, authenticationConfiguration, sslContext, openListener);
        } else {
            future = createConnection(destination, (InetSocketAddress) bindAddress, address, connectOptions, openListener);
        }
        pendingInboundConnections.add(returnedFuture);
        // if the connection fails, we need to propagate that
        future.addNotifier(new IoFuture.HandlingNotifier>() {
            public void handleFailed(final IOException exception, final FutureResult attachment) {
                attachment.setException(exception);
            }

            public void handleCancelled(final FutureResult attachment) {
                attachment.setCancelled();
            }
        }, cancellableResult);
        returnedFuture.addNotifier(new IoFuture.HandlingNotifier>() {
            public void handleCancelled(IoFuture attachment) {
                pendingInboundConnections.remove(attachment);
                future.cancel();
            }

            public void handleFailed(final IOException exception, IoFuture attachment) {
                pendingInboundConnections.remove(attachment);
            }

            public void handleDone(final ConnectionHandlerFactory data, IoFuture attachment) {
                pendingInboundConnections.remove(attachment);
            }
        }, returnedFuture);
        return returnedFuture;
    }

    protected IoFuture createConnection(final URI uri, final InetSocketAddress bindAddress, final InetSocketAddress destination, final OptionMap connectOptions, final ChannelListener openListener) {
        return bindAddress == null ?
               xnioWorker.openStreamConnection(destination, openListener, connectOptions) :
               xnioWorker.openStreamConnection(bindAddress, destination, openListener, null, connectOptions);
    }

    protected IoFuture createSslConnection(final URI uri, final InetSocketAddress bindAddress, final InetSocketAddress destination, final OptionMap connectOptions, final AuthenticationConfiguration configuration, final SSLContext sslContext, final ChannelListener openListener) {
        final IoFuture futureConnection = bindAddress == null ?
                                                            xnioWorker.openStreamConnection(destination, null, connectOptions) :
                                                            xnioWorker.openStreamConnection(bindAddress, destination, null, null, connectOptions);
        final FutureResult futureResult = new FutureResult<>(connectionProviderContext.getExecutor());
        futureResult.addCancelHandler(futureConnection);
        futureConnection.addNotifier(new IoFuture.HandlingNotifier>() {
            public void handleCancelled(final FutureResult result) {
                result.setCancelled();
            }

            public void handleFailed(final IOException exception, final FutureResult result) {
                result.setException(exception);
            }

            public void handleDone(final StreamConnection streamConnection, final FutureResult result) {
                final AuthenticationContextConfigurationClient configurationClient = ClientConnectionOpenListener.AUTH_CONFIGURATION_CLIENT;
                final String realHost = configurationClient.getRealHost(uri, configuration);
                final int realPort = configurationClient.getRealPort(uri, configuration);
                final SSLEngine engine;
                engine = sslContext.createSSLEngine(realHost, realPort);
                engine.setUseClientMode(true);

                SslConnection sslConnection;
                if (JsseXnioSsl.NEW_IMPL) {
                    sslConnection = new JsseSslConnection(streamConnection, engine);
                } else {
                    sslConnection = new JsseSslStreamConnection(streamConnection, engine, ! sslRequired);
                }
                // Required in order for the SSLConnection to be properly closed.
                streamConnection.getCloseSetter().set(channel -> safeClose(sslConnection));
                if (sslRequired) try {
                    sslConnection.startHandshake();
                } catch (IOException e) {
                    result.setException(new IOException(e));
                    safeClose(streamConnection);
                    return;
                }
                result.setResult(sslConnection);
                openListener.handleEvent(sslConnection);
            }
        }, futureResult);
        return futureResult.getIoFuture();
    }

    public Object getProviderInterface() {
        return providerInterface;
    }

    protected void closeAction() {
        try {
            final Cancellable[] cancellables;
            synchronized (pendingInboundConnections) {
                cancellables = pendingInboundConnections.toArray(new Cancellable[pendingInboundConnections.size()]);
                pendingInboundConnections.clear();
            }
            for (Cancellable pendingConnection: cancellables) {
                pendingConnection.cancel();
            }
            closeComplete();
        } finally {
            if (server != null && objectName != null) {
                try {
                    server.unregisterMBean(objectName);
                } catch (Throwable ignored) {
                }
            }
        }
    }

    void addConnectionHandler(final RemoteConnectionHandler connectionHandler) {
        handlers.add(connectionHandler);
    }

    void removeConnectionHandler(final RemoteConnectionHandler connectionHandler) {
        handlers.remove(connectionHandler);
    }

    final class ProviderInterface implements NetworkServerProvider {

        public AcceptingChannel createServer(final SocketAddress bindAddress, final OptionMap optionMap, final SaslAuthenticationFactory saslAuthenticationFactory, final SSLContext sslContext) throws IOException {
            Assert.checkNotNullParam("bindAddress", bindAddress);
            Assert.checkNotNullParam("optionMap", optionMap);
            Assert.checkNotNullParam("saslAuthenticationFactory", saslAuthenticationFactory);
            final AcceptingChannel result;
            // - SSL_ENABLED can be used to forbid SSL if SSL is not required, but not to require it if it is not present
            // - Both SSL_ENABLED and STARTTLS have to be enabled to provide SSL if SSL is not required
            // - If SSL is required then STARTTLS has no effect and is never enabled
            if (sslContext != null && (sslRequired || optionMap.get(Options.SSL_ENABLED, true) && optionMap.get(Options.SSL_STARTTLS, true))) {
                result = xnioWorker.createStreamConnectionServer(bindAddress, channel -> {
                    final StreamConnection streamConnection = acceptAndConfigure(channel);
                    if (streamConnection == null) return;
                    final InetSocketAddress peerAddress = streamConnection.getPeerAddress(InetSocketAddress.class);
                    final String realHost;
                    final int realPort;
                    if (peerAddress != null) {
                        realHost = peerAddress.getHostString();
                        realPort = peerAddress.getPort();
                    } else {
                        realHost = null;
                        realPort = 0;
                    }
                    final SSLEngine engine;
                    engine = sslContext.createSSLEngine(realHost, realPort);
                    engine.setUseClientMode(false);
                    SslConnection sslConnection;
                    if (JsseXnioSsl.NEW_IMPL) {
                        sslConnection = new JsseSslConnection(streamConnection, engine);
                    } else {
                        sslConnection = new JsseSslStreamConnection(streamConnection, engine, ! sslRequired);
                    }
                    if (optionMap.contains(Options.SSL_CLIENT_AUTH_MODE)) try {
                        sslConnection.setOption(Options.SSL_CLIENT_AUTH_MODE, optionMap.get(Options.SSL_CLIENT_AUTH_MODE));
                    } catch (IOException e) {
                        safeClose(sslConnection);
                        log.failedToAccept(e);
                        return;
                    }
                    if (sslRequired || ! optionMap.get(Options.SSL_STARTTLS, false)) try {
                        sslConnection.startHandshake();
                    } catch (IOException e) {
                        safeClose(sslConnection);
                        log.failedToAccept(e);
                        return;
                    }
                    handleAccepted(sslConnection, sslConnection, optionMap, saslAuthenticationFactory);
                }, optionMap);
            } else {
                result = xnioWorker.createStreamConnectionServer(bindAddress, channel -> {
                    final StreamConnection streamConnection = acceptAndConfigure(channel);
                    if (streamConnection == null) return;
                    handleAccepted(streamConnection, null, optionMap, saslAuthenticationFactory);
                }, optionMap);
            }
            addCloseHandler((closed, exception) -> safeClose(result));
            result.resumeAccepts();
            return result;
        }

        private StreamConnection acceptAndConfigure(final AcceptingChannel channel) {
            final StreamConnection streamConnection;
            try {
                streamConnection = channel.accept();
            } catch (IOException e) {
                log.failedToAccept(e);
                return null;
            }
            if (streamConnection == null) {
                return null;
            }
            try {
                streamConnection.setOption(Options.TCP_NODELAY, Boolean.TRUE);
            } catch (IOException e) {
                // ignore
            }
            return streamConnection;
        }

        private void handleAccepted(final StreamConnection accepted, final SslChannel sslChannel, final OptionMap serverOptionMap, final SaslAuthenticationFactory saslAuthenticationFactory) {
            final RemoteConnection connection = new RemoteConnection(accepted, sslChannel, serverOptionMap, RemoteConnectionProvider.this);
            final ServerConnectionOpenListener openListener = new ServerConnectionOpenListener(connection, connectionProviderContext, saslAuthenticationFactory, serverOptionMap);
            accepted.getSinkChannel().setWriteListener(connection.getWriteListener());
            log.tracef("Accepted connection from %s to %s", connection.getPeerAddress(), connection.getLocalAddress());
            openListener.handleEvent(accepted.getSourceChannel());
        }
    }

    protected Executor getExecutor() {
        return super.getExecutor();
    }

    public String toString() {
        return String.format("Remoting remote connection provider %x for %s", Integer.valueOf(hashCode()), connectionProviderContext.getEndpoint());
    }

    protected XnioWorker getXnioWorker() {
        return xnioWorker;
    }

    public ConnectionProviderContext getConnectionProviderContext() {
        return connectionProviderContext;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy