Как я могу заставить свою виртуальную машину загружать файл https за прокси-сервером с CNTLM, написанным на C ++ с Qt? - PullRequest
0 голосов
/ 17 апреля 2020

Несколько дней боролся с этой простой задачей: когда я пытаюсь загрузить что-либо через библиотеку Qt в C ++ на виртуальной машине с запущенным CNTLM (настройка прокси, для моей работы inte rnet), я не могу загрузить успешно. Я получаю код ошибки 99 (QNetworkReply::UnknownNetworkError - 99 - an unknown network-related error was detected - https://doc.qt.io/archives/qt-4.8/qnetworkreply.html). Я перепробовал много вещей, в том числе то, что ощущается, и избыток операторов отладки, использование отладчика и точек останова, много разных вариантов кода, и все они приводят к одной и той же проблеме на виртуальной машине (работает CNTLM), сообщая о неизвестных сетевая ошибка. (Должен заметить: на моей виртуальной машине я могу нормально использовать браузер, хотя иногда я получаю недействительные сертификаты https на обычных сайтах, но все равно работаю - это моя рабочая сеть).

С другой стороны, мой хост , можете скачать файл просто отлично.

Ниже приведен скриншот сообщений, которые я получаю из своей свернутой программы-примера ниже, работающей на виртуальной машине с CNTLM:

enter image description here

Для некоторого контекста у меня есть файлы openssl в папке исполняемого файла. Загрузка является случайной загрузкой для этого примера, которая представляет собой файл размером 100 МБ.

Вот то, что я получаю на своем хосте, в отличие от него, успешно загружается: enter image description here

У меня есть m_networkReply->ignoreSslErrors();, чтобы игнорировать возникающие проблемы с SSL ,

Моя большая проблема заключается в том, как заставить мою виртуальную машину загрузить файл https через прокси-сервер с CNTLM, написанный на C ++ с Qt? Я полагаю, что я не единственный с такой настройкой сети.

Вот довольно маленькая программа, которая воссоздает проблему. Я могу попытаться ответить на вопросы о моей виртуальной машине, если она у вас есть - это настроенная виртуальная машина CNTLM Windows 7. Программа немного отличается от реального кода из нашего monorepo и некоторых дополнительных параметров в комментариях к методам из оригинала, но подходит для воспроизведения проблемы. Дайте мне знать, если мне нужно убрать комментарии.

SmallRecreatableFailingDownloadDemo.pro

QT += network
QT -= gui

CONFIG += c++11 console
CONFIG -= app_bundle

SOURCES += \
        download.cpp \
        main.cpp

HEADERS += \
    download.h

download.h

#ifndef DOWNLOAD_H
#define DOWNLOAD_H

#include <QObject>
#include <QNetworkAccessManager>
#include <QFile>
#include <QNetworkReply>

class QNetworkAccessManager;

class Download : public QObject
{
    Q_OBJECT

public:
    Download(QNetworkAccessManager *networkAccessManager, QString url,
             QString postArguments, QString storePath,
             qint64 expectedDownloadSize = 0);
    ~Download();

    void download();

 signals:
    void progress(qint64 bytesReceived, qint64 bytesTotal);
    void error(QString errorMessage); // NOTE: this being emitted does not mean finished() was emitted
    void information(QString informationMessage);
    void finished();

private:
    QString getErrorMessage(QNetworkReply::NetworkError error);
    QString getSslErrorMessage(QSslError);

    QNetworkAccessManager *m_networkAccessManager; //!< The network access manager to use for the download

    bool m_isFinished;      //!< stores if the download has finished
    bool m_hasErrors;       //!< stores if the download has/had errors

    QString m_url;                 //!< stores the url to download
    QString m_postArguments;       //!< stores any post arguments for the url request
    QString m_storePath;           //!< this is the path where to store the file downloaded from m_url
    qint64 m_bytesReceivedSoFar;   //!< stores the number of bytes received so far
    qint64 m_expectedDownloadSize; //!< stores the expected download size (the size provided by the user, if any, otherwise -1)
    qint64 m_actualDownloadSize;   //!< stores the actual download size as reported from the download server

    QFile *m_destinationFile;      //!< the pointer to the destination file
    QNetworkReply *m_networkReply; //!< the network reply pointer (used while data is still being received)
};

#endif // DOWNLOAD_H

download.cpp

#include "download.h"

#include <QNetworkReply>
#include <QUrlQuery>

/*!
 * \brief Constructor - sets up the object for download
 * \param networkAccessManager the network access manager for this download
 * \param url the URL to download from
 * \param postArguments POST arguments to include, if any
 * \param storePath the file store path
 * \param md5Hash the expected md5 hash of the download once complete, or "" for no
 * expected hash
 * \param expectedDownloadSize the exepected download size of the download once complete
 * \todo make use of or remove `usePostMethod`
 */
Download::Download(QNetworkAccessManager *networkAccessManager, QString url,
                   QString postArguments, QString storePath,
                   qint64 expectedDownloadSize)
    : m_networkAccessManager(networkAccessManager),
      m_isFinished(false),
      m_hasErrors(false),
      m_url(url),
      m_postArguments(postArguments),
      m_storePath(storePath),
      m_bytesReceivedSoFar(0),
      m_expectedDownloadSize(expectedDownloadSize),
      m_actualDownloadSize(-1),
      m_destinationFile(Q_NULLPTR),
      m_networkReply(Q_NULLPTR)
{
}

/*!
 * \brief destructor - Currently does nothing
 */
Download::~Download()
{
}

/*!
 * \brief downloads a file based off the settings configured in the constructor
 * \todo the way I'm determining if it's a GET or POST request is very wrong
 */
void Download::download()
{
    m_isFinished = false;
    m_hasErrors = false;

    // open destination file
    m_destinationFile = new QFile(m_storePath);
    if (!m_destinationFile->open(QIODevice::WriteOnly) && m_storePath != "")
    {
        m_hasErrors = true;
        m_isFinished = true;
        emit error("Could not open destination (" + m_storePath + QString(") file for download"));
        emit finished();
        return;
    }

    QUrl url(m_url);

    qDebug() << QString("Constructing network request for url(") + m_url + QString("), with get");

    QNetworkRequest networkRequest(url);
    networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);

    QSslConfiguration sslConfiguration = QSslConfiguration::defaultConfiguration();
    sslConfiguration.setProtocol(QSsl::AnyProtocol);
    networkRequest.setSslConfiguration(sslConfiguration);

    m_networkReply = m_networkAccessManager->get(networkRequest);
    m_networkReply->ignoreSslErrors();

    connect(m_networkReply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), [this] (QNetworkReply::NetworkError errorCode)
    {
        // display (emit) the network error received in a human-readable string form
        qDebug() << "Error Message: " + getErrorMessage(errorCode);
        m_hasErrors = true;
    });

    connect(m_networkReply, &QNetworkReply::sslErrors, [this] (const QList<QSslError> &errors)
    {
        qDebug() << "sslErrors";
        // display (emit) the ssl error received in a human-readable string form
        foreach (QSslError sslError, errors)
        {
            qDebug() << "SSL Error Message: " + getSslErrorMessage(sslError);
        }
        m_hasErrors = true;
    });

    connect(m_networkReply, &QNetworkReply::readyRead, [this] ()
    {
        qDebug() << "readReady";
        // read the incoming data from the network reply for this download, and write it
        // directly to file
        m_destinationFile->write(m_networkReply->readAll());
        m_destinationFile->flush();
    });

    connect(m_networkReply, &QNetworkReply::finished, [this] ()
    {
        qDebug() << "Download finished";
        // read the incoming data from the network reply for this download, and write it
        // directly to file, if any
        m_destinationFile->write(m_networkReply->readAll());
        m_destinationFile->flush();

        // This point is reached after the file desired has been downloaded. The following
        // code accomplishes cleaning up heap and system resources, as well as perform a
        // check on the file's hash to determine if it was valid or not.
        m_destinationFile->close();
        delete m_destinationFile;
        m_destinationFile = Q_NULLPTR;
        m_isFinished = true;
    });
}

/*!
 * \brief outputs an error string for a provided QNetworkReply::NetworkError
 * \param error the error identifier to display a string version of the error
 */
QString Download::getErrorMessage(QNetworkReply::NetworkError error)
{
    // strings taken from: https://doc.qt.io/qt-5/qnetworkreply.html#NetworkError-enum
    QString errorString;
    switch (error)
    {
    case QNetworkReply::ConnectionRefusedError:
        errorString = "the remote server refused the connection (the server is not accepting requests)";
        break;
    case QNetworkReply::RemoteHostClosedError:
        errorString = "the remote server closed the connection prematurely, before the entire reply was received and processed";
        break;
    case QNetworkReply::HostNotFoundError:
        errorString = "the remote host name was not found (invalid hostname)";
        break;
    case QNetworkReply::TimeoutError:
        errorString = "the connection to the remote server timed out";
        break;
    case QNetworkReply::OperationCanceledError:
        errorString = "the operation was canceled via calls to abort() or close() before it was finished.";
        break;
    case QNetworkReply::SslHandshakeFailedError:
        errorString = "the SSL/TLS handshake failed and the encrypted channel could not be established. The sslErrors() signal should have been emitted.";
        break;
    case QNetworkReply::TemporaryNetworkFailureError:
        errorString = "the connection was broken due to disconnection from the network, however the system has initiated roaming to another access point. The request should be resubmitted and will be processed as soon as the connection is re-established.";
        break;
    case QNetworkReply::NetworkSessionFailedError:
        errorString = "the connection was broken due to disconnection from the network or failure to start the network.";
        break;
    case QNetworkReply::BackgroundRequestNotAllowedError:
        errorString = "the background request is not currently allowed due to platform policy.";
        break;
    case QNetworkReply::TooManyRedirectsError:
        errorString = "while following redirects, the maximum limit was reached. The limit is by default set to 50 or as set by QNetworkRequest::setMaxRedirectsAllowed(). (This value was introduced in 5.6.)";
        break;
    case QNetworkReply::InsecureRedirectError:
        errorString = "while following redirects, the network access API detected a redirect from a encrypted protocol (https) to an unencrypted one (http). (This value was introduced in 5.6.)";
        break;
    case QNetworkReply::ProxyConnectionRefusedError:
        errorString = "the connection to the proxy server was refused (the proxy server is not accepting requests)";
        break;
    case QNetworkReply::ProxyConnectionClosedError:
        errorString = "the proxy server closed the connection prematurely, before the entire reply was received and processed";
        break;
    case QNetworkReply::ProxyNotFoundError:
        errorString = "the proxy host name was not found (invalid proxy hostname)";
        break;
    case QNetworkReply::ProxyTimeoutError:
        errorString = "the connection to the proxy timed out or the proxy did not reply in time to the request sent";
        break;
    case QNetworkReply::ProxyAuthenticationRequiredError:
        errorString = "the proxy requires authentication in order to honour the request but did not accept any credentials offered (if any)";
        break;
    case QNetworkReply::ContentAccessDenied:
        errorString = "the access to the remote content was denied (similar to HTTP error 403)";
        break;
    case QNetworkReply::ContentOperationNotPermittedError:
        errorString = "the operation requested on the remote content is not permitted";
        break;
    case QNetworkReply::ContentNotFoundError:
        errorString = "the remote content was not found at the server (similar to HTTP error 404)";
        break;
    case QNetworkReply::AuthenticationRequiredError:
        errorString = "the remote server requires authentication to serve the content but the credentials provided were not accepted (if any)";
        break;
    case QNetworkReply::ContentReSendError:
        errorString = "the request needed to be sent again, but this failed for example because the upload data could not be read a second time.";
        break;
    case QNetworkReply::ContentConflictError:
        errorString = "the request could not be completed due to a conflict with the current state of the resource.";
        break;
    case QNetworkReply::ContentGoneError:
        errorString = "the requested resource is no longer available at the server.";
        break;
    case QNetworkReply::InternalServerError:
        errorString = "the server encountered an unexpected condition which prevented it from fulfilling the request.";
        break;
    case QNetworkReply::OperationNotImplementedError:
        errorString = "the server does not support the functionality required to fulfill the request.";
        break;
    case QNetworkReply::ServiceUnavailableError:
        errorString = "the server is unable to handle the request at this time.";
        break;
    case QNetworkReply::ProtocolUnknownError:
        errorString = "the Network Access API cannot honor the request because the protocol is not known";
        break;
    case QNetworkReply::ProtocolInvalidOperationError:
        errorString = "the requested operation is invalid for this protocol";
        break;
    case QNetworkReply::UnknownNetworkError:
        errorString = "an unknown network-related error was detected";
        break;
    case QNetworkReply::UnknownProxyError:
        errorString = "an unknown proxy-related error was detected";
        break;
    case QNetworkReply::UnknownContentError:
        errorString = "an unknown error related to the remote content was detected";
        break;
    case QNetworkReply::ProtocolFailure:
        errorString = "a breakdown in protocol was detected (parsing error, invalid or unexpected responses, etc.)";
        break;
    case QNetworkReply::UnknownServerError:
        errorString = "an unknown error related to the server response was detected";
        break;
    case QNetworkReply::NoError:
        errorString = "No error occurred";
        break;
    default:
        errorString = "Unknown error";
        break;
    }

    return errorString;
}

/*!
 * \brief displays a QSslError in string format
 * \param sslError the error to create a string version of and output to the console
 */
QString Download::getSslErrorMessage(QSslError sslError)
{
    // strings taken from: https://doc.qt.io/qt-5/qsslerror.html#SslError-enum
    QString errorString;

    switch (sslError.error())
    {
    case QSslError::NoError:
        errorString = "No Error";
        break;
    case QSslError::UnableToGetIssuerCertificate:
        errorString = "Unable To Get Issuer Certificate";
        break;
    case QSslError::UnableToDecryptCertificateSignature:
        errorString = "Unable To Decrypt Certificate Signature";
        break;
    case QSslError::UnableToDecodeIssuerPublicKey:
        errorString = "Unable To Decode Issuer Public Key";
        break;
    case QSslError::CertificateSignatureFailed:
        errorString = "Certificate Signature Failed";
        break;
    case QSslError::CertificateNotYetValid:
        errorString = "Certificate Not Yet Valid";
        break;
    case QSslError::CertificateExpired:
        errorString = "Certificate Expired";
        break;
    case QSslError::InvalidNotBeforeField:
        errorString = "Invalid Not Before Field";
        break;
    case QSslError::InvalidNotAfterField:
        errorString = "Invalid Not After Field";
        break;
    case QSslError::SelfSignedCertificate:
        errorString = "Self Signed Certificate";
        break;
    case QSslError::SelfSignedCertificateInChain:
        errorString = "Self Signed Certificate In Chain";
        break;
    case QSslError::UnableToGetLocalIssuerCertificate:
        errorString = "Unable To Get Local Issuer Certificate";
        break;
    case QSslError::UnableToVerifyFirstCertificate:
        errorString = "Unable To Verify First Certificate";
        break;
    case QSslError::CertificateRevoked:
        errorString = "Certificate Revoked";
        break;
    case QSslError::InvalidCaCertificate:
        errorString = "Invalid Ca Certificate";
        break;
    case QSslError::PathLengthExceeded:
        errorString = "Path Length Exceeded";
        break;
    case QSslError::InvalidPurpose:
        errorString = "Invalid Purpose";
        break;
    case QSslError::CertificateUntrusted:
        errorString = "Certificate Untrusted";
        break;
    case QSslError::CertificateRejected:
        errorString = "Certificate Rejected";
        break;
    case QSslError::SubjectIssuerMismatch:
        errorString = "Subject Issuer Mismatch";
        break;
    case QSslError::AuthorityIssuerSerialNumberMismatch:
        errorString = "Authority Issuer Serial Number Mismatch";
        break;
    case QSslError::NoPeerCertificate:
        errorString = "No Peer Certificate";
        break;
    case QSslError::HostNameMismatch:
        errorString = "Host Name Mismatch";
        break;
    case QSslError::UnspecifiedError:
        errorString = "Unspecified Error";
        break;
    case QSslError::NoSslSupport:
        errorString = "No Ssl Support";
        break;
    case QSslError::CertificateBlacklisted:
        errorString = "Certificate Blacklisted";
        break;
    default:
        break;
    }

    return errorString;
}

main.cpp

#include <QCoreApplication>

#include "download.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Download *download = new Download(new QNetworkAccessManager, "https://speed.hetzner.de/100MB.bin", "", "tmp.txt", -1);
    download->download();

    return a.exec();
}

Я знаю sh Я знал больше о том, как работают прокси и CNTLM, и о взаимодействии с сетевыми библиотеками Qt. Я получаю так мало информации из кода ошибки 99. Любые стратегии отладки или модификации кода для решения этой проблемы будут великолепны. Код не самый лучший, я знаю, я прошел через массу изменений, и это мой урезанный результат. Он работает на моем хосте, но не на моей виртуальной машине с Windows 7 и CNTLM.

Дайте мне знать, если у вас есть идеи, что может происходить. Ещё вопросы задать моему ИТ-отделу. Спасибо!

...