| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtNetwork module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qsocks5socketengine_p.h" |
| |
| #include "qtcpsocket.h" |
| #include "qudpsocket.h" |
| #include "qtcpserver.h" |
| #include "qdebug.h" |
| #include "qhash.h" |
| #include "qqueue.h" |
| #include "qelapsedtimer.h" |
| #include "qmutex.h" |
| #include "qthread.h" |
| #include "qcoreapplication.h" |
| #include "qurl.h" |
| #include "qauthenticator.h" |
| #include "private/qiodevice_p.h" |
| #include "private/qringbuffer_p.h" |
| #include <qendian.h> |
| #include <qnetworkinterface.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| static const int MaxWriteBufferSize = 128*1024; |
| |
| //#define QSOCKS5SOCKETLAYER_DEBUG |
| |
| #define MAX_DATA_DUMP 256 |
| #define SOCKS5_BLOCKING_BIND_TIMEOUT 5000 |
| |
| #define Q_INIT_CHECK(returnValue) do { \ |
| if (!d->data) { \ |
| return returnValue; \ |
| } } while (0) |
| |
| #define S5_VERSION_5 0x05 |
| #define S5_CONNECT 0x01 |
| #define S5_BIND 0x02 |
| #define S5_UDP_ASSOCIATE 0x03 |
| #define S5_IP_V4 0x01 |
| #define S5_DOMAINNAME 0x03 |
| #define S5_IP_V6 0x04 |
| #define S5_SUCCESS 0x00 |
| #define S5_R_ERROR_SOCKS_FAILURE 0x01 |
| #define S5_R_ERROR_CON_NOT_ALLOWED 0x02 |
| #define S5_R_ERROR_NET_UNREACH 0x03 |
| #define S5_R_ERROR_HOST_UNREACH 0x04 |
| #define S5_R_ERROR_CONN_REFUSED 0x05 |
| #define S5_R_ERROR_TTL 0x06 |
| #define S5_R_ERROR_CMD_NOT_SUPPORTED 0x07 |
| #define S5_R_ERROR_ADD_TYPE_NOT_SUPORTED 0x08 |
| |
| #define S5_AUTHMETHOD_NONE 0x00 |
| #define S5_AUTHMETHOD_PASSWORD 0x02 |
| #define S5_AUTHMETHOD_NOTACCEPTABLE 0xFF |
| |
| #define S5_PASSWORDAUTH_VERSION 0x01 |
| |
| #ifdef QSOCKS5SOCKETLAYER_DEBUG |
| # define QSOCKS5_Q_DEBUG qDebug() << this |
| # define QSOCKS5_D_DEBUG qDebug() << q_ptr |
| # define QSOCKS5_DEBUG qDebug() << "[QSocks5]" |
| static QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State s) |
| { |
| switch (s) { |
| case QSocks5SocketEnginePrivate::Uninitialized: return QLatin1String("Uninitialized"); |
| case QSocks5SocketEnginePrivate::ConnectError: return QLatin1String("ConnectError"); |
| case QSocks5SocketEnginePrivate::AuthenticationMethodsSent: return QLatin1String("AuthenticationMethodsSent"); |
| case QSocks5SocketEnginePrivate::Authenticating: return QLatin1String("Authenticating"); |
| case QSocks5SocketEnginePrivate::AuthenticatingError: return QLatin1String("AuthenticatingError"); |
| case QSocks5SocketEnginePrivate::RequestMethodSent: return QLatin1String("RequestMethodSent"); |
| case QSocks5SocketEnginePrivate::RequestError: return QLatin1String("RequestError"); |
| case QSocks5SocketEnginePrivate::Connected: return QLatin1String("Connected"); |
| case QSocks5SocketEnginePrivate::UdpAssociateSuccess: return QLatin1String("UdpAssociateSuccess"); |
| case QSocks5SocketEnginePrivate::BindSuccess: return QLatin1String("BindSuccess"); |
| case QSocks5SocketEnginePrivate::ControlSocketError: return QLatin1String("ControlSocketError"); |
| case QSocks5SocketEnginePrivate::SocksError: return QLatin1String("SocksError"); |
| case QSocks5SocketEnginePrivate::HostNameLookupError: return QLatin1String("HostNameLookupError"); |
| default: break; |
| } |
| return QLatin1String("unknown state"); |
| } |
| |
| static QString dump(const QByteArray &buf) |
| { |
| QString data; |
| for (int i = 0; i < qMin<int>(MAX_DATA_DUMP, buf.size()); ++i) { |
| if (i) data += QLatin1Char(' '); |
| uint val = (unsigned char)buf.at(i); |
| // data += QString("0x%1").arg(val, 3, 16, QLatin1Char('0')); |
| data += QString::number(val); |
| } |
| if (buf.size() > MAX_DATA_DUMP) |
| data += QLatin1String(" ..."); |
| |
| return QString::fromLatin1("size: %1 data: { %2 }").arg(buf.size()).arg(data); |
| } |
| |
| #else |
| # define QSOCKS5_DEBUG if (0) qDebug() |
| # define QSOCKS5_Q_DEBUG if (0) qDebug() |
| # define QSOCKS5_D_DEBUG if (0) qDebug() |
| |
| static inline QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State) { return QString(); } |
| static inline QString dump(const QByteArray &) { return QString(); } |
| #endif |
| |
| /* |
| inserts the host address in buf at pos and updates pos. |
| if the func fails the data in buf and the vallue of pos is undefined |
| */ |
| static bool qt_socks5_set_host_address_and_port(const QHostAddress &address, quint16 port, QByteArray *pBuf) |
| { |
| QSOCKS5_DEBUG << "setting [" << address << ':' << port << ']'; |
| |
| union { |
| quint16 port; |
| quint32 ipv4; |
| QIPv6Address ipv6; |
| char ptr; |
| } data; |
| |
| // add address |
| if (address.protocol() == QAbstractSocket::IPv4Protocol) { |
| data.ipv4 = qToBigEndian<quint32>(address.toIPv4Address()); |
| pBuf->append(S5_IP_V4); |
| pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv4)); |
| } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { |
| data.ipv6 = address.toIPv6Address(); |
| pBuf->append(S5_IP_V6); |
| pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv6)); |
| } else { |
| return false; |
| } |
| |
| // add port |
| data.port = qToBigEndian<quint16>(port); |
| pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); |
| return true; |
| } |
| |
| /* |
| like above, but for a hostname |
| */ |
| static bool qt_socks5_set_host_name_and_port(const QString &hostname, quint16 port, QByteArray *pBuf) |
| { |
| QSOCKS5_DEBUG << "setting [" << hostname << ':' << port << ']'; |
| |
| QByteArray encodedHostName = QUrl::toAce(hostname); |
| QByteArray &buf = *pBuf; |
| |
| if (encodedHostName.length() > 255) |
| return false; |
| |
| buf.append(S5_DOMAINNAME); |
| buf.append(uchar(encodedHostName.length())); |
| buf.append(encodedHostName); |
| |
| // add port |
| union { |
| quint16 port; |
| char ptr; |
| } data; |
| data.port = qToBigEndian<quint16>(port); |
| buf.append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); |
| |
| return true; |
| } |
| |
| |
| /* |
| retrives the host address in buf at pos and updates pos. |
| return 1 if OK, 0 if need more data, -1 if error |
| if the func fails the value of the address and the pos is undefined |
| */ |
| static int qt_socks5_get_host_address_and_port(const QByteArray &buf, QHostAddress *pAddress, quint16 *pPort, int *pPos) |
| { |
| int ret = -1; |
| int pos = *pPos; |
| const unsigned char *pBuf = reinterpret_cast<const unsigned char*>(buf.constData()); |
| QHostAddress address; |
| quint16 port = 0; |
| |
| if (buf.size() - pos < 1) { |
| QSOCKS5_DEBUG << "need more data address/port"; |
| return 0; |
| } |
| if (pBuf[pos] == S5_IP_V4) { |
| pos++; |
| if (buf.size() - pos < 4) { |
| QSOCKS5_DEBUG << "need more data for ip4 address"; |
| return 0; |
| } |
| address.setAddress(qFromBigEndian<quint32>(&pBuf[pos])); |
| pos += 4; |
| ret = 1; |
| } else if (pBuf[pos] == S5_IP_V6) { |
| pos++; |
| if (buf.size() - pos < 16) { |
| QSOCKS5_DEBUG << "need more data for ip6 address"; |
| return 0; |
| } |
| QIPv6Address add; |
| for (int i = 0; i < 16; ++i) |
| add[i] = buf[pos++]; |
| address.setAddress(add); |
| ret = 1; |
| } else if (pBuf[pos] == S5_DOMAINNAME){ |
| // just skip it |
| pos++; |
| qDebug() << "skipping hostname of len" << uint(pBuf[pos]); |
| pos += uchar(pBuf[pos]); |
| } else { |
| QSOCKS5_DEBUG << "invalid address type" << (int)pBuf[pos]; |
| ret = -1; |
| } |
| |
| if (ret == 1) { |
| if (buf.size() - pos < 2) { |
| QSOCKS5_DEBUG << "need more data for port"; |
| return 0; |
| } |
| port = qFromBigEndian<quint16>(&pBuf[pos]); |
| pos += 2; |
| } |
| |
| if (ret == 1) { |
| QSOCKS5_DEBUG << "got [" << address << ':' << port << ']'; |
| *pAddress = address; |
| *pPort = port; |
| *pPos = pos; |
| } |
| |
| return ret; |
| } |
| |
| struct QSocks5Data |
| { |
| QTcpSocket *controlSocket; |
| QSocks5Authenticator *authenticator; |
| }; |
| |
| struct QSocks5ConnectData : public QSocks5Data |
| { |
| QRingBuffer readBuffer; |
| }; |
| |
| struct QSocks5BindData : public QSocks5Data |
| { |
| QHostAddress localAddress; |
| quint16 localPort; |
| QHostAddress peerAddress; |
| quint16 peerPort; |
| QElapsedTimer timeStamp; |
| }; |
| |
| struct QSocks5RevivedDatagram |
| { |
| QByteArray data; |
| QHostAddress address; |
| quint16 port; |
| }; |
| |
| #ifndef QT_NO_UDPSOCKET |
| struct QSocks5UdpAssociateData : public QSocks5Data |
| { |
| QUdpSocket *udpSocket; |
| QHostAddress associateAddress; |
| quint16 associatePort; |
| QQueue<QSocks5RevivedDatagram> pendingDatagrams; |
| }; |
| #endif |
| |
| // needs to be thread safe |
| class QSocks5BindStore : public QObject |
| { |
| public: |
| QSocks5BindStore(); |
| ~QSocks5BindStore(); |
| |
| void add(qintptr socketDescriptor, QSocks5BindData *bindData); |
| bool contains(qintptr socketDescriptor); |
| QSocks5BindData *retrieve(qintptr socketDescriptor); |
| |
| protected: |
| void timerEvent(QTimerEvent * event) override; |
| |
| QRecursiveMutex mutex; |
| int sweepTimerId = -1; |
| //socket descriptor, data, timestamp |
| QHash<int, QSocks5BindData *> store; |
| }; |
| |
| Q_GLOBAL_STATIC(QSocks5BindStore, socks5BindStore) |
| |
| QSocks5BindStore::QSocks5BindStore() |
| { |
| QCoreApplication *app = QCoreApplication::instance(); |
| if (app && app->thread() != thread()) |
| moveToThread(app->thread()); |
| } |
| |
| QSocks5BindStore::~QSocks5BindStore() |
| { |
| } |
| |
| void QSocks5BindStore::add(qintptr socketDescriptor, QSocks5BindData *bindData) |
| { |
| QMutexLocker lock(&mutex); |
| if (store.contains(socketDescriptor)) { |
| // qDebug("delete it"); |
| } |
| bindData->timeStamp.start(); |
| store.insert(socketDescriptor, bindData); |
| // start sweep timer if not started |
| if (sweepTimerId == -1) |
| sweepTimerId = startTimer(60000); |
| } |
| |
| bool QSocks5BindStore::contains(qintptr socketDescriptor) |
| { |
| QMutexLocker lock(&mutex); |
| return store.contains(socketDescriptor); |
| } |
| |
| QSocks5BindData *QSocks5BindStore::retrieve(qintptr socketDescriptor) |
| { |
| QMutexLocker lock(&mutex); |
| const auto it = store.constFind(socketDescriptor); |
| if (it == store.cend()) |
| return nullptr; |
| QSocks5BindData *bindData = it.value(); |
| store.erase(it); |
| if (bindData) { |
| if (bindData->controlSocket->thread() != QThread::currentThread()) { |
| qWarning("Cannot access socks5 bind data from different thread"); |
| return nullptr; |
| } |
| } else { |
| QSOCKS5_DEBUG << "__ERROR__ binddata == 0"; |
| } |
| // stop the sweep timer if not needed |
| if (store.isEmpty()) { |
| killTimer(sweepTimerId); |
| sweepTimerId = -1; |
| } |
| return bindData; |
| } |
| |
| void QSocks5BindStore::timerEvent(QTimerEvent * event) |
| { |
| QMutexLocker lock(&mutex); |
| if (event->timerId() == sweepTimerId) { |
| QSOCKS5_DEBUG << "QSocks5BindStore performing sweep"; |
| for (auto it = store.begin(), end = store.end(); it != end;) { |
| if (it.value()->timeStamp.hasExpired(350000)) { |
| QSOCKS5_DEBUG << "QSocks5BindStore removing JJJJ"; |
| it = store.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| } |
| |
| QSocks5Authenticator::QSocks5Authenticator() |
| { |
| } |
| |
| QSocks5Authenticator::~QSocks5Authenticator() |
| { |
| } |
| |
| char QSocks5Authenticator::methodId() |
| { |
| return 0x00; |
| } |
| |
| bool QSocks5Authenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) |
| { |
| Q_UNUSED(socket); |
| *completed = true; |
| return true; |
| } |
| |
| bool QSocks5Authenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) |
| { |
| Q_UNUSED(socket); |
| *completed = true; |
| return true; |
| } |
| |
| bool QSocks5Authenticator::seal(const QByteArray &buf, QByteArray *sealedBuf) |
| { |
| *sealedBuf = buf; |
| return true; |
| } |
| |
| bool QSocks5Authenticator::unSeal(const QByteArray &sealedBuf, QByteArray *buf) |
| { |
| *buf = sealedBuf; |
| return true; |
| } |
| |
| bool QSocks5Authenticator::unSeal(QTcpSocket *sealedSocket, QByteArray *buf) |
| { |
| return unSeal(sealedSocket->readAll(), buf); |
| } |
| |
| QSocks5PasswordAuthenticator::QSocks5PasswordAuthenticator(const QString &userName, const QString &password) |
| { |
| this->userName = userName; |
| this->password = password; |
| } |
| |
| char QSocks5PasswordAuthenticator::methodId() |
| { |
| return 0x02; |
| } |
| |
| bool QSocks5PasswordAuthenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) |
| { |
| *completed = false; |
| QByteArray uname = userName.toLatin1(); |
| QByteArray passwd = password.toLatin1(); |
| QByteArray dataBuf(3 + uname.size() + passwd.size(), 0); |
| char *buf = dataBuf.data(); |
| int pos = 0; |
| buf[pos++] = S5_PASSWORDAUTH_VERSION; |
| buf[pos++] = uname.size(); |
| memcpy(&buf[pos], uname.data(), uname.size()); |
| pos += uname.size(); |
| buf[pos++] = passwd.size(); |
| memcpy(&buf[pos], passwd.data(), passwd.size()); |
| return socket->write(dataBuf) == dataBuf.size(); |
| } |
| |
| bool QSocks5PasswordAuthenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) |
| { |
| *completed = false; |
| |
| if (socket->bytesAvailable() < 2) |
| return true; |
| |
| QByteArray buf = socket->read(2); |
| if (buf.at(0) == S5_PASSWORDAUTH_VERSION && buf.at(1) == 0x00) { |
| *completed = true; |
| return true; |
| } |
| |
| // must disconnect |
| socket->close(); |
| return false; |
| } |
| |
| QString QSocks5PasswordAuthenticator::errorString() |
| { |
| return QLatin1String("Socks5 user name or password incorrect"); |
| } |
| |
| |
| |
| QSocks5SocketEnginePrivate::QSocks5SocketEnginePrivate() |
| : socks5State(Uninitialized) |
| , readNotificationEnabled(false) |
| , writeNotificationEnabled(false) |
| , exceptNotificationEnabled(false) |
| , socketDescriptor(-1) |
| , data(nullptr) |
| , connectData(nullptr) |
| #ifndef QT_NO_UDPSOCKET |
| , udpData(nullptr) |
| #endif |
| , bindData(nullptr) |
| , readNotificationActivated(false) |
| , writeNotificationActivated(false) |
| , readNotificationPending(false) |
| , writeNotificationPending(false) |
| , connectionNotificationPending(false) |
| { |
| mode = NoMode; |
| } |
| |
| QSocks5SocketEnginePrivate::~QSocks5SocketEnginePrivate() |
| { |
| } |
| |
| void QSocks5SocketEnginePrivate::initialize(Socks5Mode socks5Mode) |
| { |
| Q_Q(QSocks5SocketEngine); |
| |
| mode = socks5Mode; |
| if (mode == ConnectMode) { |
| connectData = new QSocks5ConnectData; |
| data = connectData; |
| #ifndef QT_NO_UDPSOCKET |
| } else if (mode == UdpAssociateMode) { |
| udpData = new QSocks5UdpAssociateData; |
| data = udpData; |
| udpData->udpSocket = new QUdpSocket(q); |
| #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section |
| udpData->udpSocket->setProperty("_q_networksession", q->property("_q_networksession")); |
| #endif |
| udpData->udpSocket->setProxy(QNetworkProxy::NoProxy); |
| QObject::connect(udpData->udpSocket, SIGNAL(readyRead()), |
| q, SLOT(_q_udpSocketReadNotification()), |
| Qt::DirectConnection); |
| #endif // QT_NO_UDPSOCKET |
| } else if (mode == BindMode) { |
| bindData = new QSocks5BindData; |
| data = bindData; |
| } |
| |
| data->controlSocket = new QTcpSocket(q); |
| #ifndef QT_NO_BEARERMANAGEMENT // ### Qt6: Remove section |
| data->controlSocket->setProperty("_q_networksession", q->property("_q_networksession")); |
| #endif |
| data->controlSocket->setProxy(QNetworkProxy::NoProxy); |
| QObject::connect(data->controlSocket, SIGNAL(connected()), q, SLOT(_q_controlSocketConnected()), |
| Qt::DirectConnection); |
| QObject::connect(data->controlSocket, SIGNAL(readyRead()), q, SLOT(_q_controlSocketReadNotification()), |
| Qt::DirectConnection); |
| QObject::connect(data->controlSocket, SIGNAL(bytesWritten(qint64)), q, SLOT(_q_controlSocketBytesWritten()), |
| Qt::DirectConnection); |
| QObject::connect(data->controlSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), |
| q, SLOT(_q_controlSocketErrorOccurred(QAbstractSocket::SocketError)), |
| Qt::DirectConnection); |
| QObject::connect(data->controlSocket, SIGNAL(disconnected()), q, SLOT(_q_controlSocketDisconnected()), |
| Qt::DirectConnection); |
| QObject::connect(data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), |
| q, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), |
| Qt::DirectConnection); |
| |
| if (!proxyInfo.user().isEmpty() || !proxyInfo.password().isEmpty()) { |
| QSOCKS5_D_DEBUG << "using username/password authentication; user =" << proxyInfo.user(); |
| data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); |
| } else { |
| QSOCKS5_D_DEBUG << "not using authentication"; |
| data->authenticator = new QSocks5Authenticator(); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, const QString &extraMessage) |
| { |
| Q_Q(QSocks5SocketEngine); |
| |
| switch (state) { |
| case Uninitialized: |
| case Authenticating: |
| case AuthenticationMethodsSent: |
| case RequestMethodSent: |
| case Connected: |
| case UdpAssociateSuccess: |
| case BindSuccess: |
| // these aren't error states |
| return; |
| |
| case ConnectError: |
| case ControlSocketError: { |
| QAbstractSocket::SocketError controlSocketError = data->controlSocket->error(); |
| if (socks5State != Connected) { |
| switch (controlSocketError) { |
| case QAbstractSocket::ConnectionRefusedError: |
| q->setError(QAbstractSocket::ProxyConnectionRefusedError, |
| QSocks5SocketEngine::tr("Connection to proxy refused")); |
| break; |
| case QAbstractSocket::RemoteHostClosedError: |
| q->setError(QAbstractSocket::ProxyConnectionClosedError, |
| QSocks5SocketEngine::tr("Connection to proxy closed prematurely")); |
| break; |
| case QAbstractSocket::HostNotFoundError: |
| q->setError(QAbstractSocket::ProxyNotFoundError, |
| QSocks5SocketEngine::tr("Proxy host not found")); |
| break; |
| case QAbstractSocket::SocketTimeoutError: |
| if (state == ConnectError) { |
| q->setError(QAbstractSocket::ProxyConnectionTimeoutError, |
| QSocks5SocketEngine::tr("Connection to proxy timed out")); |
| break; |
| } |
| Q_FALLTHROUGH(); |
| default: |
| q->setError(controlSocketError, data->controlSocket->errorString()); |
| break; |
| } |
| } else { |
| q->setError(controlSocketError, data->controlSocket->errorString()); |
| } |
| break; |
| } |
| |
| case AuthenticatingError: |
| q->setError(QAbstractSocket::ProxyAuthenticationRequiredError, |
| extraMessage.isEmpty() ? |
| QSocks5SocketEngine::tr("Proxy authentication failed") : |
| QSocks5SocketEngine::tr("Proxy authentication failed: %1").arg(extraMessage)); |
| break; |
| |
| case RequestError: |
| // error code set by caller (overload) |
| break; |
| |
| case SocksError: |
| q->setError(QAbstractSocket::ProxyProtocolError, |
| QSocks5SocketEngine::tr("SOCKS version 5 protocol error")); |
| break; |
| |
| case HostNameLookupError: |
| q->setError(QAbstractSocket::HostNotFoundError, |
| QAbstractSocket::tr("Host not found")); |
| break; |
| } |
| |
| q->setState(QAbstractSocket::UnconnectedState); |
| socks5State = state; |
| } |
| |
| void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, Socks5Error socks5error) |
| { |
| Q_Q(QSocks5SocketEngine); |
| switch (socks5error) { |
| case SocksFailure: |
| q->setError(QAbstractSocket::NetworkError, |
| QSocks5SocketEngine::tr("General SOCKSv5 server failure")); |
| break; |
| case ConnectionNotAllowed: |
| q->setError(QAbstractSocket::SocketAccessError, |
| QSocks5SocketEngine::tr("Connection not allowed by SOCKSv5 server")); |
| break; |
| case NetworkUnreachable: |
| q->setError(QAbstractSocket::NetworkError, |
| QAbstractSocket::tr("Network unreachable")); |
| break; |
| case HostUnreachable: |
| q->setError(QAbstractSocket::HostNotFoundError, |
| QAbstractSocket::tr("Host not found")); |
| break; |
| case ConnectionRefused: |
| q->setError(QAbstractSocket::ConnectionRefusedError, |
| QAbstractSocket::tr("Connection refused")); |
| break; |
| case TTLExpired: |
| q->setError(QAbstractSocket::NetworkError, |
| QSocks5SocketEngine::tr("TTL expired")); |
| break; |
| case CommandNotSupported: |
| q->setError(QAbstractSocket::UnsupportedSocketOperationError, |
| QSocks5SocketEngine::tr("SOCKSv5 command not supported")); |
| break; |
| case AddressTypeNotSupported: |
| q->setError(QAbstractSocket::UnsupportedSocketOperationError, |
| QSocks5SocketEngine::tr("Address type not supported")); |
| break; |
| |
| default: |
| q->setError(QAbstractSocket::UnknownSocketError, |
| QSocks5SocketEngine::tr("Unknown SOCKSv5 proxy error code 0x%1").arg(int(socks5error), 16)); |
| break; |
| } |
| |
| setErrorState(state, QString()); |
| } |
| |
| void QSocks5SocketEnginePrivate::reauthenticate() |
| { |
| Q_Q(QSocks5SocketEngine); |
| |
| // we require authentication |
| QAuthenticator auth; |
| q->proxyAuthenticationRequired(proxyInfo, &auth); |
| |
| if (!auth.user().isEmpty() || !auth.password().isEmpty()) { |
| // we have new credentials, let's try again |
| QSOCKS5_DEBUG << "authentication failure: retrying connection"; |
| socks5State = QSocks5SocketEnginePrivate::Uninitialized; |
| |
| delete data->authenticator; |
| proxyInfo.setUser(auth.user()); |
| proxyInfo.setPassword(auth.password()); |
| data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); |
| |
| { |
| const QSignalBlocker blocker(data->controlSocket); |
| data->controlSocket->abort(); |
| } |
| data->controlSocket->connectToHost(proxyInfo.hostName(), proxyInfo.port()); |
| } else { |
| // authentication failure |
| |
| setErrorState(AuthenticatingError); |
| data->controlSocket->close(); |
| emitConnectionNotification(); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::parseAuthenticationMethodReply() |
| { |
| // not enough data to begin |
| if (data->controlSocket->bytesAvailable() < 2) |
| return; |
| |
| QByteArray buf = data->controlSocket->read(2); |
| if (buf.at(0) != S5_VERSION_5) { |
| QSOCKS5_D_DEBUG << "Socks5 version incorrect"; |
| setErrorState(SocksError); |
| data->controlSocket->close(); |
| emitConnectionNotification(); |
| return; |
| } |
| |
| bool authComplete = false; |
| if (uchar(buf.at(1)) == S5_AUTHMETHOD_NONE) { |
| authComplete = true; |
| } else if (uchar(buf.at(1)) == S5_AUTHMETHOD_NOTACCEPTABLE) { |
| reauthenticate(); |
| return; |
| } else if (buf.at(1) != data->authenticator->methodId() |
| || !data->authenticator->beginAuthenticate(data->controlSocket, &authComplete)) { |
| setErrorState(AuthenticatingError, QLatin1String("Socks5 host did not support authentication method.")); |
| socketError = QAbstractSocket::SocketAccessError; // change the socket error |
| emitConnectionNotification(); |
| return; |
| } |
| |
| if (authComplete) |
| sendRequestMethod(); |
| else |
| socks5State = Authenticating; |
| } |
| |
| void QSocks5SocketEnginePrivate::parseAuthenticatingReply() |
| { |
| bool authComplete = false; |
| if (!data->authenticator->continueAuthenticate(data->controlSocket, &authComplete)) { |
| reauthenticate(); |
| return; |
| } |
| if (authComplete) |
| sendRequestMethod(); |
| } |
| |
| void QSocks5SocketEnginePrivate::sendRequestMethod() |
| { |
| QHostAddress address; |
| quint16 port = 0; |
| char command = 0; |
| if (mode == ConnectMode) { |
| command = S5_CONNECT; |
| address = peerAddress; |
| port = peerPort; |
| } else if (mode == BindMode) { |
| command = S5_BIND; |
| address = localAddress; |
| port = localPort; |
| } else { |
| #ifndef QT_NO_UDPSOCKET |
| command = S5_UDP_ASSOCIATE; |
| address = localAddress; //data->controlSocket->localAddress(); |
| port = localPort; |
| #endif |
| } |
| |
| QByteArray buf; |
| buf.reserve(270); // big enough for domain name; |
| buf.append(char(S5_VERSION_5)); |
| buf.append(command); |
| buf.append('\0'); |
| if (peerName.isEmpty() && !qt_socks5_set_host_address_and_port(address, port, &buf)) { |
| QSOCKS5_DEBUG << "error setting address" << address << " : " << port; |
| //### set error code .... |
| return; |
| } else if (!peerName.isEmpty() && !qt_socks5_set_host_name_and_port(peerName, port, &buf)) { |
| QSOCKS5_DEBUG << "error setting peer name" << peerName << " : " << port; |
| //### set error code .... |
| return; |
| } |
| QSOCKS5_DEBUG << "sending" << dump(buf); |
| QByteArray sealedBuf; |
| if (!data->authenticator->seal(buf, &sealedBuf)) { |
| // ### Handle this error. |
| } |
| data->controlSocket->write(sealedBuf); |
| data->controlSocket->flush(); |
| socks5State = RequestMethodSent; |
| } |
| |
| void QSocks5SocketEnginePrivate::parseRequestMethodReply() |
| { |
| Q_Q(QSocks5SocketEngine); |
| QSOCKS5_DEBUG << "parseRequestMethodReply()"; |
| |
| QByteArray inBuf; |
| if (!data->authenticator->unSeal(data->controlSocket, &inBuf)) { |
| // ### check error and not just not enough data |
| QSOCKS5_DEBUG << "unSeal failed, needs more data"; |
| return; |
| } |
| |
| inBuf.prepend(receivedHeaderFragment); |
| receivedHeaderFragment.clear(); |
| QSOCKS5_DEBUG << dump(inBuf); |
| if (inBuf.size() < 3) { |
| QSOCKS5_DEBUG << "need more data for request reply header .. put this data somewhere"; |
| receivedHeaderFragment = inBuf; |
| return; |
| } |
| |
| QHostAddress address; |
| quint16 port = 0; |
| |
| if (inBuf.at(0) != S5_VERSION_5 || inBuf.at(2) != 0x00) { |
| QSOCKS5_DEBUG << "socks protocol error"; |
| setErrorState(SocksError); |
| } else if (inBuf.at(1) != S5_SUCCESS) { |
| Socks5Error socks5Error = Socks5Error(inBuf.at(1)); |
| QSOCKS5_DEBUG << "Request error :" << socks5Error; |
| if ((socks5Error == SocksFailure || socks5Error == ConnectionNotAllowed) |
| && !peerName.isEmpty()) { |
| // Dante seems to use this error code to indicate hostname resolution failure |
| setErrorState(HostNameLookupError); |
| } else { |
| setErrorState(RequestError, socks5Error); |
| } |
| } else { |
| // connection success, retrieve the remote addresses |
| int pos = 3; |
| int err = qt_socks5_get_host_address_and_port(inBuf, &address, &port, &pos); |
| if (err == -1) { |
| QSOCKS5_DEBUG << "error getting address"; |
| setErrorState(SocksError); |
| } else if (err == 0) { |
| //need more data |
| receivedHeaderFragment = inBuf; |
| return; |
| } else { |
| inBuf.remove(0, pos); |
| for (int i = inBuf.size() - 1; i >= 0 ; --i) |
| data->controlSocket->ungetChar(inBuf.at(i)); |
| } |
| } |
| |
| if (socks5State == RequestMethodSent) { |
| // no error |
| localAddress = address; |
| localPort = port; |
| |
| if (mode == ConnectMode) { |
| inboundStreamCount = outboundStreamCount = 1; |
| socks5State = Connected; |
| // notify the upper layer that we're done |
| q->setState(QAbstractSocket::ConnectedState); |
| emitConnectionNotification(); |
| } else if (mode == BindMode) { |
| socks5State = BindSuccess; |
| q->setState(QAbstractSocket::ListeningState); |
| } else { |
| socks5State = UdpAssociateSuccess; |
| } |
| } else if (socks5State == BindSuccess) { |
| // no error and we got a connection |
| bindData->peerAddress = address; |
| bindData->peerPort = port; |
| |
| emitReadNotification(); |
| } else { |
| // got an error |
| data->controlSocket->close(); |
| emitConnectionNotification(); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_emitPendingReadNotification() |
| { |
| Q_Q(QSocks5SocketEngine); |
| readNotificationPending = false; |
| if (readNotificationEnabled) { |
| QSOCKS5_D_DEBUG << "emitting readNotification"; |
| QPointer<QSocks5SocketEngine> qq = q; |
| q->readNotification(); |
| if (!qq) |
| return; |
| // check if there needs to be a new zero read notification |
| if (data && data->controlSocket->state() == QAbstractSocket::UnconnectedState |
| && data->controlSocket->error() == QAbstractSocket::RemoteHostClosedError) { |
| connectData->readBuffer.clear(); |
| emitReadNotification(); |
| } |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::emitReadNotification() |
| { |
| Q_Q(QSocks5SocketEngine); |
| readNotificationActivated = true; |
| if (readNotificationEnabled && !readNotificationPending) { |
| QSOCKS5_D_DEBUG << "queueing readNotification"; |
| readNotificationPending = true; |
| QMetaObject::invokeMethod(q, "_q_emitPendingReadNotification", Qt::QueuedConnection); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_emitPendingWriteNotification() |
| { |
| writeNotificationPending = false; |
| Q_Q(QSocks5SocketEngine); |
| if (writeNotificationEnabled) { |
| QSOCKS5_D_DEBUG << "emitting writeNotification"; |
| q->writeNotification(); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::emitWriteNotification() |
| { |
| Q_Q(QSocks5SocketEngine); |
| writeNotificationActivated = true; |
| if (writeNotificationEnabled && !writeNotificationPending) { |
| QSOCKS5_D_DEBUG << "queueing writeNotification"; |
| writeNotificationPending = true; |
| QMetaObject::invokeMethod(q, "_q_emitPendingWriteNotification", Qt::QueuedConnection); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_emitPendingConnectionNotification() |
| { |
| connectionNotificationPending = false; |
| Q_Q(QSocks5SocketEngine); |
| QSOCKS5_D_DEBUG << "emitting connectionNotification"; |
| q->connectionNotification(); |
| } |
| |
| void QSocks5SocketEnginePrivate::emitConnectionNotification() |
| { |
| Q_Q(QSocks5SocketEngine); |
| QSOCKS5_D_DEBUG << "queueing connectionNotification"; |
| connectionNotificationPending = true; |
| QMetaObject::invokeMethod(q, "_q_emitPendingConnectionNotification", Qt::QueuedConnection); |
| } |
| |
| QSocks5SocketEngine::QSocks5SocketEngine(QObject *parent) |
| :QAbstractSocketEngine(*new QSocks5SocketEnginePrivate(), parent) |
| { |
| } |
| |
| QSocks5SocketEngine::~QSocks5SocketEngine() |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| if (d->data) { |
| delete d->data->authenticator; |
| delete d->data->controlSocket; |
| } |
| if (d->connectData) |
| delete d->connectData; |
| #ifndef QT_NO_UDPSOCKET |
| if (d->udpData) { |
| delete d->udpData->udpSocket; |
| delete d->udpData; |
| } |
| #endif |
| if (d->bindData) |
| delete d->bindData; |
| } |
| |
| static int nextDescriptor() |
| { |
| static QBasicAtomicInt counter; |
| return 1 + counter.fetchAndAddRelaxed(1); |
| } |
| |
| bool QSocks5SocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| d->socketDescriptor = nextDescriptor(); |
| |
| d->socketType = type; |
| d->socketProtocol = protocol; |
| |
| return true; |
| } |
| |
| bool QSocks5SocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::SocketState socketState) |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| QSOCKS5_Q_DEBUG << "initialize" << socketDescriptor; |
| |
| // this is only valid for the other side of a bind, nothing else is supported |
| |
| if (socketState != QAbstractSocket::ConnectedState) { |
| //### must be connected state ??? |
| return false; |
| } |
| |
| QSocks5BindData *bindData = socks5BindStore()->retrieve(socketDescriptor); |
| if (bindData) { |
| |
| d->socketState = QAbstractSocket::ConnectedState; |
| d->socketType = QAbstractSocket::TcpSocket; |
| d->connectData = new QSocks5ConnectData; |
| d->data = d->connectData; |
| d->mode = QSocks5SocketEnginePrivate::ConnectMode; |
| d->data->controlSocket = bindData->controlSocket; |
| bindData->controlSocket = nullptr; |
| d->data->controlSocket->setParent(this); |
| d->socketProtocol = d->data->controlSocket->localAddress().protocol(); |
| d->data->authenticator = bindData->authenticator; |
| bindData->authenticator = nullptr; |
| d->localPort = bindData->localPort; |
| d->localAddress = bindData->localAddress; |
| d->peerPort = bindData->peerPort; |
| d->peerAddress = bindData->peerAddress; |
| d->inboundStreamCount = d->outboundStreamCount = 1; |
| delete bindData; |
| |
| QObject::connect(d->data->controlSocket, SIGNAL(connected()), this, SLOT(_q_controlSocketConnected()), |
| Qt::DirectConnection); |
| QObject::connect(d->data->controlSocket, SIGNAL(readyRead()), this, SLOT(_q_controlSocketReadNotification()), |
| Qt::DirectConnection); |
| QObject::connect(d->data->controlSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(_q_controlSocketBytesWritten()), |
| Qt::DirectConnection); |
| QObject::connect(d->data->controlSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(_q_controlSocketErrorOccurred(QAbstractSocket::SocketError)), |
| Qt::DirectConnection); |
| QObject::connect(d->data->controlSocket, SIGNAL(disconnected()), this, SLOT(_q_controlSocketDisconnected()), |
| Qt::DirectConnection); |
| QObject::connect(d->data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), |
| this, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), |
| Qt::DirectConnection); |
| |
| d->socks5State = QSocks5SocketEnginePrivate::Connected; |
| |
| if (d->data->controlSocket->bytesAvailable() != 0) |
| d->_q_controlSocketReadNotification(); |
| return true; |
| } |
| return false; |
| } |
| |
| void QSocks5SocketEngine::setProxy(const QNetworkProxy &networkProxy) |
| { |
| Q_D(QSocks5SocketEngine); |
| d->proxyInfo = networkProxy; |
| } |
| |
| qintptr QSocks5SocketEngine::socketDescriptor() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| return d->socketDescriptor; |
| } |
| |
| bool QSocks5SocketEngine::isValid() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| return d->socketType != QAbstractSocket::UnknownSocketType |
| && d->socks5State != QSocks5SocketEnginePrivate::SocksError |
| && (d->socketError == QAbstractSocket::UnknownSocketError |
| || d->socketError == QAbstractSocket::SocketTimeoutError |
| || d->socketError == QAbstractSocket::UnfinishedSocketOperationError); |
| } |
| |
| bool QSocks5SocketEngine::connectInternal() |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| if (!d->data) { |
| if (socketType() == QAbstractSocket::TcpSocket) { |
| d->initialize(QSocks5SocketEnginePrivate::ConnectMode); |
| #ifndef QT_NO_UDPSOCKET |
| } else if (socketType() == QAbstractSocket::UdpSocket) { |
| d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); |
| // all udp needs to be bound |
| if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) |
| return false; |
| |
| setState(QAbstractSocket::ConnectedState); |
| return true; |
| #endif |
| } else { |
| qFatal("QSocks5SocketEngine::connectToHost: in QTcpServer mode"); |
| return false; |
| } |
| } |
| |
| if (d->socketState != QAbstractSocket::ConnectingState) { |
| if (d->socks5State == QSocks5SocketEnginePrivate::Uninitialized |
| // We may have new auth credentials since an earlier failure: |
| || d->socks5State == QSocks5SocketEnginePrivate::AuthenticatingError) { |
| setState(QAbstractSocket::ConnectingState); |
| //limit buffer in internal socket, data is buffered in the external socket under application control |
| d->data->controlSocket->setReadBufferSize(65536); |
| } |
| |
| d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); |
| } |
| |
| return false; |
| } |
| |
| bool QSocks5SocketEngine::connectToHost(const QHostAddress &address, quint16 port) |
| { |
| Q_D(QSocks5SocketEngine); |
| QSOCKS5_DEBUG << "connectToHost" << address << ':' << port; |
| |
| setPeerAddress(address); |
| setPeerPort(port); |
| d->peerName.clear(); |
| |
| return connectInternal(); |
| } |
| |
| bool QSocks5SocketEngine::connectToHostByName(const QString &hostname, quint16 port) |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| setPeerAddress(QHostAddress()); |
| setPeerPort(port); |
| d->peerName = hostname; |
| |
| return connectInternal(); |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_controlSocketConnected() |
| { |
| QSOCKS5_DEBUG << "_q_controlSocketConnected"; |
| QByteArray buf(3, 0); |
| buf[0] = S5_VERSION_5; |
| buf[1] = 0x01; |
| buf[2] = data->authenticator->methodId(); |
| data->controlSocket->write(buf); |
| socks5State = AuthenticationMethodsSent; |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_controlSocketReadNotification() |
| { |
| QSOCKS5_D_DEBUG << "_q_controlSocketReadNotification socks5state" << s5StateToString(socks5State) |
| << "bytes available" << data->controlSocket->bytesAvailable(); |
| |
| if (data->controlSocket->bytesAvailable() == 0) { |
| QSOCKS5_D_DEBUG << "########## bogus read why do we get these ... on windows only"; |
| return; |
| } |
| |
| switch (socks5State) { |
| case AuthenticationMethodsSent: |
| parseAuthenticationMethodReply(); |
| break; |
| case Authenticating: |
| parseAuthenticatingReply(); |
| break; |
| case RequestMethodSent: |
| parseRequestMethodReply(); |
| if (socks5State == Connected && data->controlSocket->bytesAvailable()) |
| _q_controlSocketReadNotification(); |
| break; |
| case Connected: { |
| QByteArray buf; |
| if (!data->authenticator->unSeal(data->controlSocket, &buf)) { |
| // qDebug("unseal error maybe need to wait for more data"); |
| } |
| if (buf.size()) { |
| QSOCKS5_DEBUG << dump(buf); |
| connectData->readBuffer.append(buf); |
| emitReadNotification(); |
| } |
| break; |
| } |
| case BindSuccess: |
| // only get here if command is bind |
| if (mode == BindMode) { |
| parseRequestMethodReply(); |
| break; |
| } |
| |
| Q_FALLTHROUGH(); |
| default: |
| qWarning("QSocks5SocketEnginePrivate::_q_controlSocketReadNotification: " |
| "Unexpectedly received data while in state=%d and mode=%d", |
| socks5State, mode); |
| break; |
| }; |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_controlSocketBytesWritten() |
| { |
| QSOCKS5_DEBUG << "_q_controlSocketBytesWritten"; |
| |
| if (socks5State != Connected |
| || (mode == ConnectMode |
| && data->controlSocket->bytesToWrite())) |
| return; |
| if (data->controlSocket->bytesToWrite() < MaxWriteBufferSize) { |
| emitWriteNotification(); |
| writeNotificationActivated = false; |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_controlSocketErrorOccurred(QAbstractSocket::SocketError error) |
| { |
| QSOCKS5_D_DEBUG << "controlSocketError" << error << data->controlSocket->errorString(); |
| |
| if (error == QAbstractSocket::SocketTimeoutError) |
| return; // ignore this error -- comes from the waitFor* functions |
| |
| if (error == QAbstractSocket::RemoteHostClosedError |
| && socks5State == Connected) { |
| // clear the read buffer in connect mode so that bytes available returns 0 |
| // if there already is a read notification pending then this will be processed first |
| if (!readNotificationPending) |
| connectData->readBuffer.clear(); |
| emitReadNotification(); |
| data->controlSocket->close(); |
| // cause a disconnect in the outer socket |
| emitWriteNotification(); |
| } else if (socks5State == Uninitialized |
| || socks5State == AuthenticationMethodsSent |
| || socks5State == Authenticating |
| || socks5State == RequestMethodSent) { |
| setErrorState(socks5State == Uninitialized ? ConnectError : ControlSocketError); |
| data->controlSocket->close(); |
| emitConnectionNotification(); |
| } else { |
| q_func()->setError(data->controlSocket->error(), data->controlSocket->errorString()); |
| emitReadNotification(); |
| emitWriteNotification(); |
| } |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_controlSocketDisconnected() |
| { |
| QSOCKS5_D_DEBUG << "_q_controlSocketDisconnected"; |
| } |
| |
| void QSocks5SocketEnginePrivate::_q_controlSocketStateChanged(QAbstractSocket::SocketState state) |
| { |
| QSOCKS5_D_DEBUG << "_q_controlSocketStateChanged" << state; |
| } |
| |
| #ifndef QT_NO_UDPSOCKET |
| void QSocks5SocketEnginePrivate::_q_udpSocketReadNotification() |
| { |
| QSOCKS5_D_DEBUG << "_q_udpSocketReadNotification()"; |
| |
| // check some state stuff |
| if (!udpData->udpSocket->hasPendingDatagrams()) { |
| QSOCKS5_D_DEBUG << "false read ??"; |
| return; |
| } |
| |
| while (udpData->udpSocket->hasPendingDatagrams()) { |
| QByteArray sealedBuf(udpData->udpSocket->pendingDatagramSize(), 0); |
| QSOCKS5_D_DEBUG << "new datagram"; |
| udpData->udpSocket->readDatagram(sealedBuf.data(), sealedBuf.size()); |
| QByteArray inBuf; |
| if (!data->authenticator->unSeal(sealedBuf, &inBuf)) { |
| QSOCKS5_D_DEBUG << "failed unsealing datagram discarding"; |
| return; |
| } |
| QSOCKS5_DEBUG << dump(inBuf); |
| int pos = 0; |
| const char *buf = inBuf.constData(); |
| if (inBuf.size() < 4) { |
| QSOCKS5_D_DEBUG << "bugus udp data, discarding"; |
| return; |
| } |
| QSocks5RevivedDatagram datagram; |
| if (buf[pos++] != 0 || buf[pos++] != 0) { |
| QSOCKS5_D_DEBUG << "invalid datagram discarding"; |
| return; |
| } |
| if (buf[pos++] != 0) { //### add fragmentation reading support |
| QSOCKS5_D_DEBUG << "don't support fragmentation yet disgarding"; |
| return; |
| } |
| if (qt_socks5_get_host_address_and_port(inBuf, &datagram.address, &datagram.port, &pos) != 1) { |
| QSOCKS5_D_DEBUG << "failed to get address from datagram disgarding"; |
| return; |
| } |
| datagram.data = QByteArray(&buf[pos], inBuf.size() - pos); |
| udpData->pendingDatagrams.enqueue(datagram); |
| } |
| emitReadNotification(); |
| } |
| #endif // QT_NO_UDPSOCKET |
| |
| bool QSocks5SocketEngine::bind(const QHostAddress &addr, quint16 port) |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| // when bind wee will block until the bind is finished as the info from the proxy server is needed |
| |
| QHostAddress address; |
| if (addr.protocol() == QAbstractSocket::AnyIPProtocol) |
| address = QHostAddress::AnyIPv4; //SOCKS5 doesn't support dual stack, and there isn't any implementation of udp on ipv6 yet |
| else |
| address = addr; |
| |
| if (!d->data) { |
| if (socketType() == QAbstractSocket::TcpSocket) { |
| d->initialize(QSocks5SocketEnginePrivate::BindMode); |
| #ifndef QT_NO_UDPSOCKET |
| } else if (socketType() == QAbstractSocket::UdpSocket) { |
| d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); |
| #endif |
| } else { |
| //### something invalid |
| return false; |
| } |
| } |
| |
| #ifndef QT_NO_UDPSOCKET |
| if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { |
| if (!d->udpData->udpSocket->bind(address, port)) { |
| QSOCKS5_Q_DEBUG << "local udp bind failed"; |
| setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); |
| return false; |
| } |
| d->localAddress = d->udpData->udpSocket->localAddress(); |
| d->localPort = d->udpData->udpSocket->localPort(); |
| } else |
| #endif |
| if (d->mode == QSocks5SocketEnginePrivate::BindMode) { |
| d->localAddress = address; |
| d->localPort = port; |
| } else { |
| //### something invalid |
| return false; |
| } |
| |
| int msecs = SOCKS5_BLOCKING_BIND_TIMEOUT; |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); |
| if (!d->waitForConnected(msecs, nullptr) || |
| d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { |
| // waitForConnected sets the error state and closes the socket |
| QSOCKS5_Q_DEBUG << "waitForConnected to proxy server" << d->data->controlSocket->errorString(); |
| return false; |
| } |
| if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { |
| setState(QAbstractSocket::BoundState); |
| return true; |
| #ifndef QT_NO_UDPSOCKET |
| } else if (d->socks5State == QSocks5SocketEnginePrivate::UdpAssociateSuccess) { |
| setState(QAbstractSocket::BoundState); |
| d->udpData->associateAddress = d->localAddress; |
| d->localAddress = QHostAddress(); |
| d->udpData->associatePort = d->localPort; |
| d->localPort = 0; |
| return true; |
| #endif // QT_NO_UDPSOCKET |
| } |
| |
| // binding timed out |
| setError(QAbstractSocket::SocketTimeoutError, |
| QLatin1String(QT_TRANSLATE_NOOP("QSocks5SocketEngine", "Network operation timed out"))); |
| |
| ///### delete d->udpSocket; |
| ///### d->udpSocket = 0; |
| return false; |
| } |
| |
| |
| bool QSocks5SocketEngine::listen() |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| QSOCKS5_Q_DEBUG << "listen()"; |
| |
| // check that we are in bound and then go to listening. |
| if (d->socketState == QAbstractSocket::BoundState) { |
| d->socketState = QAbstractSocket::ListeningState; |
| |
| // check if we already have a connection |
| if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) |
| d->emitReadNotification(); |
| |
| return true; |
| } |
| return false; |
| } |
| |
| int QSocks5SocketEngine::accept() |
| { |
| Q_D(QSocks5SocketEngine); |
| // check we are listing --- |
| |
| QSOCKS5_Q_DEBUG << "accept()"; |
| |
| qintptr sd = -1; |
| switch (d->socks5State) { |
| case QSocks5SocketEnginePrivate::BindSuccess: |
| QSOCKS5_Q_DEBUG << "BindSuccess adding" << d->socketDescriptor << "to the bind store"; |
| d->data->controlSocket->disconnect(); |
| d->data->controlSocket->setParent(nullptr); |
| d->bindData->localAddress = d->localAddress; |
| d->bindData->localPort = d->localPort; |
| sd = d->socketDescriptor; |
| socks5BindStore()->add(sd, d->bindData); |
| d->data = nullptr; |
| d->bindData = nullptr; |
| d->socketDescriptor = 0; |
| //### do something about this socket layer ... set it closed and an error about why ... |
| // reset state and local port/address |
| d->socks5State = QSocks5SocketEnginePrivate::Uninitialized; // ..?? |
| d->socketState = QAbstractSocket::UnconnectedState; |
| break; |
| case QSocks5SocketEnginePrivate::ControlSocketError: |
| setError(QAbstractSocket::ProxyProtocolError, QLatin1String("Control socket error")); |
| break; |
| default: |
| setError(QAbstractSocket::ProxyProtocolError, QLatin1String("SOCKS5 proxy error")); |
| break; |
| } |
| return sd; |
| } |
| |
| void QSocks5SocketEngine::close() |
| { |
| QSOCKS5_Q_DEBUG << "close()"; |
| Q_D(QSocks5SocketEngine); |
| if (d->data && d->data->controlSocket) { |
| if (d->data->controlSocket->state() == QAbstractSocket::ConnectedState) { |
| int msecs = 100; |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| while (!d->data->controlSocket->bytesToWrite()) { |
| if (!d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) |
| break; |
| } |
| } |
| d->data->controlSocket->close(); |
| } |
| d->inboundStreamCount = d->outboundStreamCount = 0; |
| #ifndef QT_NO_UDPSOCKET |
| if (d->udpData && d->udpData->udpSocket) |
| d->udpData->udpSocket->close(); |
| #endif |
| } |
| |
| qint64 QSocks5SocketEngine::bytesAvailable() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) |
| return d->connectData->readBuffer.size(); |
| #ifndef QT_NO_UDPSOCKET |
| else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode |
| && !d->udpData->pendingDatagrams.isEmpty()) |
| return d->udpData->pendingDatagrams.first().data.size(); |
| #endif |
| return 0; |
| } |
| |
| qint64 QSocks5SocketEngine::read(char *data, qint64 maxlen) |
| { |
| Q_D(QSocks5SocketEngine); |
| QSOCKS5_Q_DEBUG << "read( , maxlen = " << maxlen << ')'; |
| if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { |
| if (d->connectData->readBuffer.isEmpty()) { |
| if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { |
| //imitate remote closed |
| close(); |
| setError(QAbstractSocket::RemoteHostClosedError, |
| QLatin1String("Remote host closed connection###")); |
| setState(QAbstractSocket::UnconnectedState); |
| return -1; |
| } else { |
| return 0; // nothing to be read |
| } |
| } |
| const qint64 copy = d->connectData->readBuffer.read(data, maxlen); |
| QSOCKS5_DEBUG << "read" << dump(QByteArray(data, copy)); |
| return copy; |
| #ifndef QT_NO_UDPSOCKET |
| } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { |
| return readDatagram(data, maxlen); |
| #endif |
| } |
| return 0; |
| } |
| |
| qint64 QSocks5SocketEngine::write(const char *data, qint64 len) |
| { |
| Q_D(QSocks5SocketEngine); |
| QSOCKS5_Q_DEBUG << "write" << dump(QByteArray(data, len)); |
| |
| if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { |
| // clamp down the amount of bytes to transfer at once |
| len = qMin<qint64>(len, MaxWriteBufferSize) - d->data->controlSocket->bytesToWrite(); |
| if (len <= 0) |
| return 0; |
| |
| QByteArray buf = QByteArray::fromRawData(data, len); |
| QByteArray sealedBuf; |
| if (!d->data->authenticator->seal(buf, &sealedBuf)) { |
| // ### Handle this error. |
| } |
| |
| qint64 written = d->data->controlSocket->write(sealedBuf); |
| if (written <= 0) { |
| QSOCKS5_Q_DEBUG << "native write returned" << written; |
| return written; |
| } |
| d->data->controlSocket->waitForBytesWritten(0); |
| //NB: returning len rather than written for the OK case, because the "sealing" may increase the length |
| return len; |
| #ifndef QT_NO_UDPSOCKET |
| } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { |
| // send to connected address |
| return writeDatagram(data, len, QIpPacketHeader(d->peerAddress, d->peerPort)); |
| #endif |
| } |
| //### set an error ??? |
| return -1; |
| } |
| |
| #ifndef QT_NO_UDPSOCKET |
| #ifndef QT_NO_NETWORKINTERFACE |
| bool QSocks5SocketEngine::joinMulticastGroup(const QHostAddress &, |
| const QNetworkInterface &) |
| { |
| setError(QAbstractSocket::UnsupportedSocketOperationError, |
| QLatin1String("Operation on socket is not supported")); |
| return false; |
| } |
| |
| bool QSocks5SocketEngine::leaveMulticastGroup(const QHostAddress &, |
| const QNetworkInterface &) |
| { |
| setError(QAbstractSocket::UnsupportedSocketOperationError, |
| QLatin1String("Operation on socket is not supported")); |
| return false; |
| } |
| |
| |
| QNetworkInterface QSocks5SocketEngine::multicastInterface() const |
| { |
| return QNetworkInterface(); |
| } |
| |
| bool QSocks5SocketEngine::setMulticastInterface(const QNetworkInterface &) |
| { |
| setError(QAbstractSocket::UnsupportedSocketOperationError, |
| QLatin1String("Operation on socket is not supported")); |
| return false; |
| } |
| #endif // QT_NO_NETWORKINTERFACE |
| |
| bool QSocks5SocketEngine::hasPendingDatagrams() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| Q_INIT_CHECK(false); |
| |
| return !d->udpData->pendingDatagrams.isEmpty(); |
| } |
| |
| qint64 QSocks5SocketEngine::pendingDatagramSize() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| |
| if (!d->udpData->pendingDatagrams.isEmpty()) |
| return d->udpData->pendingDatagrams.head().data.size(); |
| return 0; |
| } |
| #endif // QT_NO_UDPSOCKET |
| |
| qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QIpPacketHeader *header, PacketHeaderOptions) |
| { |
| #ifndef QT_NO_UDPSOCKET |
| Q_D(QSocks5SocketEngine); |
| |
| if (d->udpData->pendingDatagrams.isEmpty()) |
| return 0; |
| |
| QSocks5RevivedDatagram datagram = d->udpData->pendingDatagrams.dequeue(); |
| int copyLen = qMin<int>(maxlen, datagram.data.size()); |
| memcpy(data, datagram.data.constData(), copyLen); |
| if (header) { |
| header->senderAddress = datagram.address; |
| header->senderPort = datagram.port; |
| } |
| return copyLen; |
| #else |
| Q_UNUSED(data) |
| Q_UNUSED(maxlen) |
| Q_UNUSED(header) |
| return -1; |
| #endif // QT_NO_UDPSOCKET |
| } |
| |
| qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QIpPacketHeader &header) |
| { |
| #ifndef QT_NO_UDPSOCKET |
| Q_D(QSocks5SocketEngine); |
| |
| // it is possible to send with out first binding with udp, but socks5 requires a bind. |
| if (!d->data) { |
| d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); |
| // all udp needs to be bound |
| if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) { |
| //### set error |
| return -1; |
| } |
| } |
| |
| QByteArray outBuf; |
| outBuf.reserve(270 + len); |
| outBuf[0] = 0x00; |
| outBuf[1] = 0x00; |
| outBuf[2] = 0x00; |
| if (!qt_socks5_set_host_address_and_port(header.destinationAddress, header.destinationPort, &outBuf)) { |
| } |
| outBuf += QByteArray(data, len); |
| QSOCKS5_DEBUG << "sending" << dump(outBuf); |
| QByteArray sealedBuf; |
| if (!d->data->authenticator->seal(outBuf, &sealedBuf)) { |
| QSOCKS5_DEBUG << "sealing data failed"; |
| setError(QAbstractSocket::SocketAccessError, d->data->authenticator->errorString()); |
| return -1; |
| } |
| if (d->udpData->udpSocket->writeDatagram(sealedBuf, d->udpData->associateAddress, d->udpData->associatePort) != sealedBuf.size()) { |
| //### try frgamenting |
| if (d->udpData->udpSocket->error() == QAbstractSocket::DatagramTooLargeError) |
| setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); |
| //### else maybe more serious error |
| return -1; |
| } |
| |
| return len; |
| #else |
| Q_UNUSED(data) |
| Q_UNUSED(len) |
| Q_UNUSED(header) |
| return -1; |
| #endif // QT_NO_UDPSOCKET |
| } |
| |
| qint64 QSocks5SocketEngine::bytesToWrite() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| if (d->data && d->data->controlSocket) { |
| return d->data->controlSocket->bytesToWrite(); |
| } else { |
| return 0; |
| } |
| } |
| |
| int QSocks5SocketEngine::option(SocketOption option) const |
| { |
| Q_D(const QSocks5SocketEngine); |
| if (d->data && d->data->controlSocket) { |
| // convert the enum and call the real socket |
| if (option == QAbstractSocketEngine::LowDelayOption) |
| return d->data->controlSocket->socketOption(QAbstractSocket::LowDelayOption).toInt(); |
| if (option == QAbstractSocketEngine::KeepAliveOption) |
| return d->data->controlSocket->socketOption(QAbstractSocket::KeepAliveOption).toInt(); |
| } |
| return -1; |
| } |
| |
| bool QSocks5SocketEngine::setOption(SocketOption option, int value) |
| { |
| Q_D(QSocks5SocketEngine); |
| if (d->data && d->data->controlSocket) { |
| // convert the enum and call the real socket |
| if (option == QAbstractSocketEngine::LowDelayOption) |
| d->data->controlSocket->setSocketOption(QAbstractSocket::LowDelayOption, value); |
| if (option == QAbstractSocketEngine::KeepAliveOption) |
| d->data->controlSocket->setSocketOption(QAbstractSocket::KeepAliveOption, value); |
| return true; |
| } |
| return false; |
| } |
| |
| bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut) |
| { |
| if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) |
| return false; |
| |
| const Socks5State wantedState = |
| mode == ConnectMode ? Connected : |
| mode == BindMode ? BindSuccess : |
| UdpAssociateSuccess; |
| |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| |
| while (socks5State != wantedState) { |
| if (!data->controlSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) { |
| if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) |
| return true; |
| |
| setErrorState(QSocks5SocketEnginePrivate::ControlSocketError); |
| if (timedOut && data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) |
| *timedOut = true; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut) |
| { |
| Q_D(QSocks5SocketEngine); |
| QSOCKS5_DEBUG << "waitForRead" << msecs; |
| |
| d->readNotificationActivated = false; |
| |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| |
| // are we connected yet? |
| if (!d->waitForConnected(msecs, timedOut)) |
| return false; |
| if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) |
| return true; |
| if (bytesAvailable() && d->readNotificationPending) { |
| // We've got some data incoming, but the queued call hasn't been performed yet. |
| // The data is where we expect it to be already, so just return true. |
| return true; |
| } |
| |
| // we're connected |
| if (d->mode == QSocks5SocketEnginePrivate::ConnectMode || |
| d->mode == QSocks5SocketEnginePrivate::BindMode) { |
| while (!d->readNotificationActivated) { |
| if (!d->data->controlSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) { |
| if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) |
| return true; |
| |
| setError(d->data->controlSocket->error(), d->data->controlSocket->errorString()); |
| if (timedOut && d->data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) |
| *timedOut = true; |
| return false; |
| } |
| } |
| #ifndef QT_NO_UDPSOCKET |
| } else { |
| while (!d->readNotificationActivated) { |
| if (!d->udpData->udpSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) { |
| setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); |
| if (timedOut && d->udpData->udpSocket->error() == QAbstractSocket::SocketTimeoutError) |
| *timedOut = true; |
| return false; |
| } |
| } |
| #endif // QT_NO_UDPSOCKET |
| } |
| |
| |
| bool ret = d->readNotificationActivated; |
| d->readNotificationActivated = false; |
| |
| QSOCKS5_DEBUG << "waitForRead returned" << ret; |
| return ret; |
| } |
| |
| |
| bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut) |
| { |
| Q_D(QSocks5SocketEngine); |
| QSOCKS5_DEBUG << "waitForWrite" << msecs; |
| |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| |
| // are we connected yet? |
| if (!d->waitForConnected(msecs, timedOut)) |
| return false; |
| if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) |
| return true; |
| |
| // we're connected |
| |
| // flush any bytes we may still have buffered in the time that we have left |
| if (d->data->controlSocket->bytesToWrite()) |
| d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed())); |
| while ((msecs == -1 || stopWatch.elapsed() < msecs) |
| && d->data->controlSocket->state() == QAbstractSocket::ConnectedState |
| && d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize) |
| d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed())); |
| return d->data->controlSocket->bytesToWrite() < MaxWriteBufferSize; |
| } |
| |
| bool QSocks5SocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, |
| bool checkRead, bool checkWrite, |
| int msecs, bool *timedOut) |
| { |
| Q_UNUSED(checkRead); |
| if (!checkWrite) { |
| bool canRead = waitForRead(msecs, timedOut); |
| if (readyToRead) |
| *readyToRead = canRead; |
| return canRead; |
| } |
| |
| bool canWrite = waitForWrite(msecs, timedOut); |
| if (readyToWrite) |
| *readyToWrite = canWrite; |
| return canWrite; |
| } |
| |
| bool QSocks5SocketEngine::isReadNotificationEnabled() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| return d->readNotificationEnabled; |
| } |
| |
| void QSocks5SocketEngine::setReadNotificationEnabled(bool enable) |
| { |
| Q_D(QSocks5SocketEngine); |
| |
| QSOCKS5_Q_DEBUG << "setReadNotificationEnabled(" << enable << ')'; |
| |
| bool emitSignal = false; |
| if (!d->readNotificationEnabled |
| && enable) { |
| if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) |
| emitSignal = !d->connectData->readBuffer.isEmpty(); |
| #ifndef QT_NO_UDPSOCKET |
| else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) |
| emitSignal = !d->udpData->pendingDatagrams.isEmpty(); |
| #endif |
| else if (d->mode == QSocks5SocketEnginePrivate::BindMode |
| && d->socketState == QAbstractSocket::ListeningState |
| && d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) |
| emitSignal = true; |
| } |
| |
| d->readNotificationEnabled = enable; |
| |
| if (emitSignal) |
| d->emitReadNotification(); |
| } |
| |
| bool QSocks5SocketEngine::isWriteNotificationEnabled() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| return d->writeNotificationEnabled; |
| } |
| |
| void QSocks5SocketEngine::setWriteNotificationEnabled(bool enable) |
| { |
| Q_D(QSocks5SocketEngine); |
| d->writeNotificationEnabled = enable; |
| if (enable && d->socketState == QAbstractSocket::ConnectedState) { |
| if (d->mode == QSocks5SocketEnginePrivate::ConnectMode && d->data->controlSocket->bytesToWrite()) |
| return; // will be emitted as a result of bytes written |
| d->emitWriteNotification(); |
| d->writeNotificationActivated = false; |
| } |
| } |
| |
| bool QSocks5SocketEngine::isExceptionNotificationEnabled() const |
| { |
| Q_D(const QSocks5SocketEngine); |
| return d->exceptNotificationEnabled; |
| } |
| |
| void QSocks5SocketEngine::setExceptionNotificationEnabled(bool enable) |
| { |
| Q_D(QSocks5SocketEngine); |
| d->exceptNotificationEnabled = enable; |
| } |
| |
| QAbstractSocketEngine * |
| QSocks5SocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, |
| const QNetworkProxy &proxy, QObject *parent) |
| { |
| Q_UNUSED(socketType); |
| |
| // proxy type must have been resolved by now |
| if (proxy.type() != QNetworkProxy::Socks5Proxy) { |
| QSOCKS5_DEBUG << "not proxying"; |
| return nullptr; |
| } |
| QScopedPointer<QSocks5SocketEngine> engine(new QSocks5SocketEngine(parent)); |
| engine->setProxy(proxy); |
| return engine.take(); |
| } |
| |
| QAbstractSocketEngine *QSocks5SocketEngineHandler::createSocketEngine(qintptr socketDescriptor, QObject *parent) |
| { |
| QSOCKS5_DEBUG << "createSocketEngine" << socketDescriptor; |
| if (socks5BindStore()->contains(socketDescriptor)) { |
| QSOCKS5_DEBUG << "bind store contains" << socketDescriptor; |
| return new QSocks5SocketEngine(parent); |
| } |
| return nullptr; |
| } |
| |
| QT_END_NAMESPACE |