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

org.fisco.bcos.sdk.network.ConnectionManager Maven / Gradle / Ivy

There is a newer version: 2.7.0
Show newest version
/*
 * Copyright 2014-2020  [fisco-dev]
 *
 * 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.fisco.bcos.sdk.network;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.ssl.SMSslClientContextFactory;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.Future;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import org.fisco.bcos.sdk.config.ConfigOption;
import org.fisco.bcos.sdk.model.CryptoType;
import org.fisco.bcos.sdk.model.RetCode;
import org.fisco.bcos.sdk.utils.ThreadPoolService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Maintain peer connections. Start a schedule to reconnect failed peers.
 *
 * @author Maggie
 */
public class ConnectionManager {
    private static Logger logger = LoggerFactory.getLogger(ConnectionManager.class);
    private ChannelHandler channelHandler;
    private List connectionInfoList = new CopyOnWriteArrayList<>();
    private Map availableConnections = new ConcurrentHashMap<>();
    private EventLoopGroup workerGroup;
    private Boolean running = false;
    private Bootstrap bootstrap = new Bootstrap();
    private ScheduledExecutorService reconnSchedule = new ScheduledThreadPoolExecutor(1);

    public ConnectionManager(ConfigOption configOption, MsgHandler msgHandler) {
        for (String peerIpPort : configOption.getNetworkConfig().getPeers()) {
            connectionInfoList.add(new ConnectionInfo(peerIpPort));
        }
        channelHandler = new ChannelHandler(this, msgHandler);
        logger.info(" all connections: {}", connectionInfoList);
    }

    public void startConnect(ConfigOption configOption) throws NetworkException {
        if (running) {
            logger.debug("running");
            return;
        }
        logger.debug(" start connect. ");
        /** init netty * */
        initNetty(configOption);
        running = true;

        /** try connection */
        List connChannelFuture = new ArrayList();
        for (ConnectionInfo connect : connectionInfoList) {
            logger.debug("startConnect to {}", connect.getEndPoint());
            ChannelFuture channelFuture = bootstrap.connect(connect.getIp(), connect.getPort());
            connChannelFuture.add(channelFuture);
        }

        /** check connection result */
        boolean atLeastOneConnectSuccess = false;
        List errorMessageList = new ArrayList<>();
        for (int i = 0; i < connectionInfoList.size(); i++) {
            ConnectionInfo connInfo = connectionInfoList.get(i);
            ChannelFuture connectFuture = connChannelFuture.get(i);
            if (checkConnectionResult(connInfo, connectFuture, errorMessageList)) {
                atLeastOneConnectSuccess = true;
            }
        }

        /** check available connection */
        if (!atLeastOneConnectSuccess) {
            logger.error(" all connections have failed, {} ", errorMessageList);
            String errorMessageString = "";
            for (RetCode errorRetCode : errorMessageList) {
                errorMessageString += errorRetCode.getMessage() + "\n";
            }
            for (RetCode errorRetCode : errorMessageList) {
                if (errorRetCode.getCode() == NetworkException.SSL_HANDSHAKE_FAILED) {
                    throw new NetworkException(
                            " Failed to connect to all the nodes! errorMessage: \n"
                                    + errorMessageString,
                            NetworkException.SSL_HANDSHAKE_FAILED);
                }
            }
            throw new NetworkException(
                    " Failed to connect to all the nodes! errorMessage: \n" + errorMessageString,
                    NetworkException.CONNECT_FAILED);
        }
        logger.debug(" start connect end. ");
    }

    public void startReconnectSchedule() {
        logger.debug(" start reconnect schedule");
        reconnSchedule.scheduleAtFixedRate(
                () -> reconnect(),
                TimeoutConfig.reconnectDelay,
                TimeoutConfig.reconnectDelay,
                TimeUnit.MILLISECONDS);
    }

    public void stopReconnectSchedule() {
        ThreadPoolService.stopThreadPool(reconnSchedule);
    }

    public void stopNetty() {
        if (running) {
            if (workerGroup != null) {
                workerGroup.shutdownGracefully();
            }
            running = false;
        }
    }

    private void reconnect() {
        // Get connection which need reconnect
        List needReconnect = new ArrayList<>();
        int aliveConnectionCount = 0;
        for (ConnectionInfo connectionInfo : connectionInfoList) {
            ChannelHandlerContext ctx = availableConnections.get(connectionInfo.getEndPoint());
            if (Objects.isNull(ctx) || !ctx.channel().isActive()) {
                needReconnect.add(connectionInfo);
            } else {
                aliveConnectionCount++;
            }
        }
        logger.trace(" Keep alive nodes count: {}", aliveConnectionCount);

        // Reconnect
        for (ConnectionInfo connectionInfo : needReconnect) {
            ChannelFuture connectFuture =
                    bootstrap.connect(connectionInfo.getIp(), connectionInfo.getPort());
            List errorMessageList = new ArrayList<>();
            if (checkConnectionResult(connectionInfo, connectFuture, errorMessageList)) {
                logger.info(
                        " reconnect to {}:{} success",
                        connectionInfo.getIp(),
                        connectionInfo.getPort());
            } else {
                logger.error(
                        " reconnect to {}:{}, error: {}",
                        connectionInfo.getIp(),
                        connectionInfo.getPort(),
                        errorMessageList);
            }
        }
    }

    public void setMsgHandleThreadPool(ExecutorService msgHandleThreadPool) {
        channelHandler.setMsgHandleThreadPool(msgHandleThreadPool);
    }

    public List getConnectionInfoList() {
        return connectionInfoList;
    }

    public Map getAvailableConnections() {
        return availableConnections;
    }

    public ChannelHandlerContext getConnectionCtx(String peer) {
        return availableConnections.get(peer);
    }

    private SslContext initSslContext(ConfigOption configOption) throws NetworkException {
        try {
            Security.setProperty("jdk.disabled.namedCurves", "");
            // Get file, file existence is already checked when check config file.
            FileInputStream caCert =
                    new FileInputStream(
                            new File(configOption.getCryptoMaterialConfig().getCaCertPath()));
            FileInputStream sslCert =
                    new FileInputStream(
                            new File(configOption.getCryptoMaterialConfig().getSdkCertPath()));
            FileInputStream sslKey =
                    new FileInputStream(
                            new File(
                                    configOption.getCryptoMaterialConfig().getSdkPrivateKeyPath()));

            // Init SslContext
            logger.info(" build ECDSA ssl context with configured certificates ");
            SslContext sslCtx =
                    SslContextBuilder.forClient()
                            .trustManager(caCert)
                            .keyManager(sslCert, sslKey)
                            .sslProvider(SslProvider.OPENSSL)
                            // .sslProvider(SslProvider.JDK)
                            .build();
            return sslCtx;
        } catch (FileNotFoundException | SSLException e) {
            logger.error(
                    "initSslContext failed, caCert: {}, sslCert: {}, sslKey: {}, error: {}, e: {}",
                    configOption.getCryptoMaterialConfig().getCaCertPath(),
                    configOption.getCryptoMaterialConfig().getSdkCertPath(),
                    configOption.getCryptoMaterialConfig().getSdkPrivateKeyPath(),
                    e.getMessage(),
                    e);
            throw new NetworkException(
                    "SSL context init failed, please make sure your cert and key files are properly configured. error info: "
                            + e.getMessage(),
                    NetworkException.INIT_CONTEXT_FAILED);
        } catch (IllegalArgumentException e) {
            logger.error("initSslContext failed, error: {}, e: {}", e.getMessage(), e);
            throw new NetworkException(
                    "SSL context init failed, error info: " + e.getMessage(),
                    NetworkException.INIT_CONTEXT_FAILED);
        }
    }

    private SslContext initSMSslContext(ConfigOption configOption) throws NetworkException {
        try {
            // Get file, file existence is already checked when check config file.
            FileInputStream caCert =
                    new FileInputStream(
                            new File(configOption.getCryptoMaterialConfig().getCaCertPath()));
            FileInputStream sslCert =
                    new FileInputStream(
                            new File(configOption.getCryptoMaterialConfig().getSdkCertPath()));
            FileInputStream sslKey =
                    new FileInputStream(
                            new File(
                                    configOption.getCryptoMaterialConfig().getSdkPrivateKeyPath()));
            FileInputStream enCert =
                    new FileInputStream(
                            new File(configOption.getCryptoMaterialConfig().getEnSSLCertPath()));
            FileInputStream enKey =
                    new FileInputStream(
                            new File(
                                    configOption
                                            .getCryptoMaterialConfig()
                                            .getEnSSLPrivateKeyPath()));

            // Init SslContext
            logger.info(" build SM ssl context with configured certificates ");
            return SMSslClientContextFactory.build(caCert, enCert, enKey, sslCert, sslKey);
        } catch (IOException
                | CertificateException
                | NoSuchAlgorithmException
                | InvalidKeySpecException
                | NoSuchProviderException e) {
            logger.error(
                    "initSMSslContext failed, caCert:{}, sslCert: {}, sslKey: {}, enCert: {}, enKey: {}, error: {}, e: {}",
                    configOption.getCryptoMaterialConfig().getCaCertPath(),
                    configOption.getCryptoMaterialConfig().getSdkCertPath(),
                    configOption.getCryptoMaterialConfig().getSdkPrivateKeyPath(),
                    configOption.getCryptoMaterialConfig().getEnSSLCertPath(),
                    configOption.getCryptoMaterialConfig().getEnSSLPrivateKeyPath(),
                    e.getMessage(),
                    e);
            throw new NetworkException(
                    "SSL context init failed, please make sure your cert and key files are properly configured. error info: "
                            + e.getMessage(),
                    e);
        }
    }

    private void initNetty(ConfigOption configOption) throws NetworkException {
        workerGroup = new NioEventLoopGroup();
        bootstrap.group(workerGroup);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
        // set connection timeout
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) TimeoutConfig.connectTimeout);
        int sslCryptoType = configOption.getCryptoMaterialConfig().getSslCryptoType();
        SslContext sslContext =
                (sslCryptoType == CryptoType.ECDSA_TYPE
                        ? initSslContext(configOption)
                        : initSMSslContext(configOption));
        SslContext finalSslContext = sslContext;
        ChannelInitializer initializer =
                new ChannelInitializer() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        /*
                         * Each connection is fetched from the socketChannel
                         */
                        SslHandler sslHandler = finalSslContext.newHandler(ch.alloc());
                        sslHandler.setHandshakeTimeoutMillis(TimeoutConfig.sslHandShakeTimeout);
                        ch.pipeline()
                                .addLast(
                                        sslHandler,
                                        new LengthFieldBasedFrameDecoder(
                                                Integer.MAX_VALUE, 0, 4, -4, 0),
                                        new IdleStateHandler(
                                                TimeoutConfig.idleTimeout,
                                                TimeoutConfig.idleTimeout,
                                                TimeoutConfig.idleTimeout,
                                                TimeUnit.MILLISECONDS),
                                        new MessageEncoder(),
                                        new MessageDecoder(),
                                        channelHandler);
                    }
                };
        bootstrap.handler(initializer);
    }

    private boolean checkConnectionResult(
            ConnectionInfo connInfo, ChannelFuture connectFuture, List errorMessageList) {
        connectFuture.awaitUninterruptibly();
        if (!connectFuture.isSuccess()) {
            /** connect failed. */
            if (Objects.isNull(connectFuture.cause())) {
                logger.error("connect to {}:{} failed. ", connInfo.getIp(), connInfo.getPort());
            } else {
                logger.error(
                        "connect to {}:{} failed. {}",
                        connInfo.getIp(),
                        connInfo.getPort(),
                        connectFuture.cause().getMessage());
            }
            errorMessageList.add(
                    new RetCode(
                            NetworkException.CONNECT_FAILED,
                            "connect to "
                                    + connInfo.getIp()
                                    + ":"
                                    + connInfo.getPort()
                                    + " failed"));
            return false;
        } else {
            /** connect success, check ssl handshake result. */
            SslHandler sslhandler = connectFuture.channel().pipeline().get(SslHandler.class);
            String checkerMessage =
                    "! Please check the certificate and ensure that the SDK and the node are in the same agency!";
            if (Objects.isNull(sslhandler)) {
                String sslHandshakeFailedMessage =
                        " ssl handshake failed:/"
                                + connInfo.getIp()
                                + ":"
                                + connInfo.getPort()
                                + checkerMessage;
                logger.error(sslHandshakeFailedMessage);
                errorMessageList.add(
                        new RetCode(
                                NetworkException.SSL_HANDSHAKE_FAILED, sslHandshakeFailedMessage));
                return false;
            }

            Future sshHandshakeFuture =
                    sslhandler.handshakeFuture().awaitUninterruptibly();
            if (sshHandshakeFuture.isSuccess()) {
                logger.trace(" ssl handshake success {}:{}", connInfo.getIp(), connInfo.getPort());
                return true;
            } else {
                String sslHandshakeFailedMessage =
                        " ssl handshake failed:/"
                                + connInfo.getIp()
                                + ":"
                                + connInfo.getPort()
                                + checkerMessage;
                logger.error(sslHandshakeFailedMessage);
                errorMessageList.add(
                        new RetCode(
                                NetworkException.SSL_HANDSHAKE_FAILED, sslHandshakeFailedMessage));
                return false;
            }
        }
    }

    protected ChannelHandlerContext addConnectionContext(
            String ip, int port, ChannelHandlerContext ctx) {
        String endpoint = ip + ":" + port;
        logger.debug("addConnectionContext, endpoint: {}, ctx:{}", endpoint, ctx);
        return availableConnections.put(endpoint, ctx);
    }

    protected void removeConnectionContext(String ip, int port, ChannelHandlerContext ctx) {
        String endpoint = ip + ":" + port;
        if (Objects.isNull(availableConnections.get(endpoint))) {
            return;
        }
        Boolean result = availableConnections.remove(endpoint, ctx);
        if (logger.isDebugEnabled()) {
            logger.debug(
                    " result: {}, host: {}, port: {}, ctx: {}",
                    result,
                    ip,
                    port,
                    System.identityHashCode(ctx));
        }
    }

    protected void removeConnection(String peerIpPort) {
        for (ConnectionInfo conn : connectionInfoList) {
            String ipPort = conn.getIp() + ":" + conn.getPort();
            if (ipPort.equals(peerIpPort)) {
                connectionInfoList.remove(conn);
                return;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy