Идентификация сервера SSH не получена - Тупик Handshake [SSHJ]

У нас возникли проблемы с попыткой реализовать пул SftpConnections для нашего приложения.

В настоящее время мы используем SSHJ (Schmizz) в качестве транспортной библиотеки и сталкиваемся с проблемой, которую мы просто не можем имитировать в нашей среде разработки (но ошибка продолжает проявляться случайным образом в производстве, иногда через три дня, иногда после 10 минут).

Проблема заключается в том, что при попытке отправить файл через SFTP поток блокируется в методе init из класса schmizz 'TransportImpl:

   @Override
    public void init(String remoteHost, int remotePort, InputStream in, OutputStream out)
            throws TransportException {
        connInfo = new ConnInfo(remoteHost, remotePort, in, out);

    try {

        if (config.isWaitForServerIdentBeforeSendingClientIdent()) {
            receiveServerIdent();
            sendClientIdent();
        } else {
            sendClientIdent();
            receiveServerIdent();
        }


        log.info("Server identity string: {}", serverID);

    } catch (IOException e) {
        throw new TransportException(e);
    }

    reader.start();
}

isWaitForServerIdentBeforeSendingClientIdent для нас ЛОЖЬ, поэтому, прежде всего, клиент (мы) отправляем нашу идентификацию, как показано в журналах:

"Идентификатор клиента String: blabla"

Затем он поворачивается для receiveServerIdent:

    private void receiveServerIdent() throws IOException 
{
        final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
        while ((serverID = readIdentification(buf)).isEmpty()) {
            int b = connInfo.in.read();
            if (b == -1)
                throw new TransportException("Server closed connection during identification exchange");
            buf.putByte((byte) b);
        }
    }

Нить никогда не получает контроль, так как сервер никогда не отвечает своей личностью. Кажется, что код застрял в этом цикле While. Никаких тайм-аутов или исключений SSH не выбрасывается, мой клиент просто ждет навсегда, и поток заходит в тупик.

Это метод readIdentification:

private String readIdentification(Buffer.PlainBuffer buffer)
        throws IOException {
    String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();
    if (ident.isEmpty()) {
        return ident;
    }

    if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
        throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
                                     "Server does not support SSHv2, identified as: " + ident);

    return ident;
}

Похоже, что входной поток ConnectionInfo никогда не получает данные для чтения, как если бы сервер закрывал соединение (даже если, как было сказано ранее, исключение не генерируется).

Я попытался смоделировать эту ошибку, насытив согласование, закрывая сокеты при подключении, используя conntrack, чтобы убить установленные соединения, пока выполняется рукопожатие, но не везет вообще, поэтому любая помощь будет ВЫСОКО оценили.

:)

Ответ 1

Я уверен, что следующий код создает проблему:

String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();
if (ident.isEmpty()) {
    return ident;
}

Если IdentificationStringParser.parseIdentificationString() возвращает пустую строку, она будет возвращена методу вызывающего. Метод вызывающего абонента будет продолжать вызывать while ((serverID = readIdentification(buf)).isEmpty()), поскольку строка всегда пуста. Единственный способ разбить цикл будет, если вызов int b = connInfo.in.read(); возвращает -1... но если сервер продолжает отправлять данные (или пересылает данные), это условие никогда не выполняется.

Если это так, я бы добавил какой-то искусственный способ обнаружить это как:

private String readIdentification(Buffer.PlainBuffer buffer, AtomicInteger numberOfAttempts)
        throws IOException {
    String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();

    numberOfAttempts.incrementAndGet();


    if (ident.isEmpty() && numberOfAttempts.intValue() < 1000) { // 1000 
        return ident;
    } else if (numberOfAttempts.intValue() >= 1000) {
        throw new TransportException("To many attempts to read the server ident").

    }

    if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
        throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
                                     "Server does not support SSHv2, identified as: " + ident);

    return ident;
}

Таким образом, вы, по крайней мере, подтвердите, что это так, и можете больше понять, почему .parseIdentificationString() возвращает пустую строку.