Java 8: как выполнить тайм-аут для SSLSocket.startHandshake () - PullRequest
2 голосов
/ 09 октября 2019

Ниже приведен поток для выполнения SSLHandshake, но в некоторых крайних случаях я заметил, что ((SSLSocket) clientSocket).startHandshake(); заблокирован навсегда, и он не переходит к следующему блоку while кода цикла, где SSL_HANDSHAKE_TIMEOUT равен 1500 милли миллисекунд, и он работает нормально,Интересно, если бы добавление clientSocket.setSoTimeout(90000); решило бы эту проблему или оно должно обрабатываться другим способом?

MainServerHandshakeThread

public class MainServerHandshakeThread implements com.ssltunnel.utilities.threading.Shutdown, Runnable {
    private final Socket clientSocket;
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(MainServerHandshakeThread.class.getName());
    private boolean done;

    public MainServerHandshakeThread(Socket clientSocket) {
        this.clientSocket = clientSocket;        
    }

    private void handshake() throws CertificateExpiredException, InterruptedException, IOException {

        long start = System.currentTimeMillis();

        ((SSLSocket) clientSocket).setNeedClientAuth(true);
        MainServerHandshakeHandler handshake = new MainServerHandshakeHandler();
        ((SSLSocket) clientSocket).addHandshakeCompletedListener(handshake);
        ((SSLSocket) clientSocket).startHandshake();


        while (!handshake.isDone() && !done) {
            Thread.sleep(10);
            long duration = System.currentTimeMillis() - start;
            if (duration>SSL_HANDSHAKE_TIMEOUT) {
                done = true;
                LOG.warn("Handshake timeout");
            }
        }
        long stop = System.currentTimeMillis();        
        serialNumber = handshake.getSerialNumber();
        LOG.info("MainServer Handshake Handshake done in ms: " + ((stop - start))+" For serialNumber "+serialNumber );        

    }

    @Override
    public void run() {
        try {
            handshake();
        } catch (CertificateExpiredException ex) {
            LOG.error("Client Certificate Expired", ex.getMessage());
            SocketUtils.closeQuietly(clientSocket);
        }
        catch (InterruptedException ex) {
            LOG.error("Interrupted waiting for handshake", ex);
            SocketUtils.closeQuietly(clientSocket);
        } 
        catch (IOException ex) {
            LOG.error("IO Error waiting for handshake", ex);
            SocketUtils.closeQuietly(clientSocket);
        }
        finally {
            LOG.debug("Handshake thread is done");
            done = true;
        }

    }

    @Override
    public void shutdown() {
        if (clientSocket!=null) {
            SocketUtils.closeQuietly(clientSocket);
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 12 октября 2019

Большинство реализаций SSLSocket поддерживают setHandshakeTimeout () , но оно не является частью определения класса. Я предлагаю вам взглянуть, например, на код Android. Вот хороший пример того, как с этим справиться: фабричная оболочка ssl-сокета и фабрика ssl-сокетов

Проверьте, как они пытаются установить тайм-аут рукопожатия, если он существует:

    private void setHandshakeTimeout(SSLSocket sslSocket, int timeout) {

    try {

        // Most implementations of SSLSocket support setHandshakeTimeout(), but it is not

        // actually part of the class definition. We will attempt to set it using reflection.

        // If the particular implementation of SSLSocket we are using does not support this

        // function, then we will just have to use the default handshake timeout.

        sslSocket.getClass().getMethod("setHandshakeTimeout", int.class).invoke(sslSocket,

                timeout);

    } catch (Exception e) {

        LogUtils.w(LogUtils.TAG, e, "unable to set handshake timeout");

    }

}

И посмотрите, как они получают соединение:

    SSLSocket sslsock = (SSLSocket)

        ((sock != null) ? sock : createSocket());



    if ((localAddress != null) || (localPort > 0)) {



        // we need to bind explicitly

        if (localPort < 0)

            localPort = 0; // indicates "any"



        InetSocketAddress isa =

            new InetSocketAddress(localAddress, localPort);

        sslsock.bind(isa);

    }



    int connTimeout = HttpConnectionParams.getConnectionTimeout(params);

    int soTimeout = HttpConnectionParams.getSoTimeout(params);



    InetSocketAddress remoteAddress;

    if (nameResolver != null) {

        remoteAddress = new InetSocketAddress(nameResolver.resolve(host), port);

    } else {

        remoteAddress = new InetSocketAddress(host, port);

    }



    sslsock.connect(remoteAddress, connTimeout);



    sslsock.setSoTimeout(soTimeout);

    try {

        hostnameVerifier.verify(host, sslsock);

        // verifyHostName() didn't blowup - good!

    } catch (IOException iox) {

        // close the socket before re-throwing the exception

        try { sslsock.close(); } catch (Exception x) { /*ignore*/ }

        throw iox;

    }



    return sslsock;

Надеюсь, это поможет.

0 голосов
/ 12 октября 2019

Чтобы суммировать комментарии (в основном от @ user207421): да, установки времени ожидания сокета через socket.setSoTimeout(timeout) будет достаточно для запуска SocketTimeoutException (подкласс IOException), если рукопожатиепроцесс не завершается через некоторое время (но не обязательно указанное время ожидания 1 ).

Это объясняется просто потому, что setSoTimeout() работает в сокетеуровень ниже SSL-рукопожатия: протокол рукопожатия, выполняемый startHandshake(), включает в себя несколько операций чтения / записи из сокета Input / OutputStream, которые инициируют сам тайм-аут. Другими словами: это не «тайм-аут рукопожатия» как таковой, а «тайм-аут чтения» для всех операций чтения, выполняемых самим рукопожатием.

Также обратите внимание, что вам не нужно вызывать startHandshake() самостоятельно: JVM сделает это автоматически, когда вы впервые попробуете читать или писать с вашего SSLSocket (это то, что вы обычно делаете рано или поздно после получения такого сокета от SSLServerSocket в любом случае).


1 : время ожидания, указанное в setSoTimeout(timeout), относится к одиночному read(). Таким образом, процесс рукопожатия может прерваться (в худшем случае) после числа read(), которое он выполняет, умноженного на указанное вами значение timeout.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...