266 lines
7.7 KiB
C++
266 lines
7.7 KiB
C++
// Copyright 2007-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "Connection.h"
|
|
#include "Mumble.pb.h"
|
|
#include "SSL.h"
|
|
|
|
#include <QtCore/QtEndian>
|
|
#include <QtNetwork/QHostAddress>
|
|
|
|
#ifdef Q_OS_WIN
|
|
# include <qos2.h>
|
|
#else
|
|
# include <netinet/in.h>
|
|
# include <netinet/tcp.h>
|
|
# include <sys/socket.h>
|
|
# include <sys/types.h>
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
HANDLE Connection::hQoS = nullptr;
|
|
#endif
|
|
|
|
Connection::Connection(QObject *p, QSslSocket *qtsSock) : QObject(p) {
|
|
qtsSocket = qtsSock;
|
|
qtsSocket->setParent(this);
|
|
iPacketLength = -1;
|
|
bDisconnectedEmitted = false;
|
|
csCrypt = std::make_unique< CryptStateOCB2 >();
|
|
|
|
static bool bDeclared = false;
|
|
if (!bDeclared) {
|
|
bDeclared = true;
|
|
qRegisterMetaType< QAbstractSocket::SocketError >("QAbstractSocket::SocketError");
|
|
}
|
|
|
|
int nodelay = 1;
|
|
setsockopt(static_cast< int >(qtsSocket->socketDescriptor()), IPPROTO_TCP, TCP_NODELAY,
|
|
reinterpret_cast< char * >(&nodelay), static_cast< socklen_t >(sizeof(nodelay)));
|
|
|
|
connect(qtsSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
|
|
SLOT(socketError(QAbstractSocket::SocketError)));
|
|
connect(qtsSocket, SIGNAL(encrypted()), this, SIGNAL(encrypted()));
|
|
connect(qtsSocket, SIGNAL(readyRead()), this, SLOT(socketRead()));
|
|
connect(qtsSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
|
|
connect(qtsSocket, SIGNAL(sslErrors(const QList< QSslError > &)), this,
|
|
SLOT(socketSslErrors(const QList< QSslError > &)));
|
|
qtLastPacket.restart();
|
|
#ifdef Q_OS_WIN
|
|
dwFlow = 0;
|
|
#endif
|
|
}
|
|
|
|
Connection::~Connection() {
|
|
#ifdef Q_OS_WIN
|
|
if (dwFlow && hQoS) {
|
|
if (!QOSRemoveSocketFromFlow(hQoS, 0, dwFlow, 0))
|
|
qWarning("Connection: Failed to remove flow from QoS");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Connection::setToS() {
|
|
#if defined(Q_OS_WIN)
|
|
if (dwFlow || !hQoS)
|
|
return;
|
|
|
|
dwFlow = 0;
|
|
if (!QOSAddSocketToFlow(hQoS, qtsSocket->socketDescriptor(), nullptr, QOSTrafficTypeAudioVideo,
|
|
QOS_NON_ADAPTIVE_FLOW, reinterpret_cast< PQOS_FLOWID >(&dwFlow)))
|
|
qWarning("Connection: Failed to add flow to QOS");
|
|
#elif defined(Q_OS_UNIX)
|
|
int val = 0xa0;
|
|
if (setsockopt(static_cast< int >(qtsSocket->socketDescriptor()), IPPROTO_IP, IP_TOS, &val, sizeof(val))) {
|
|
val = 0x60;
|
|
if (setsockopt(static_cast< int >(qtsSocket->socketDescriptor()), IPPROTO_IP, IP_TOS, &val, sizeof(val)))
|
|
qWarning("Connection: Failed to set TOS for TCP Socket");
|
|
}
|
|
# if defined(SO_PRIORITY)
|
|
socklen_t optlen = sizeof(val);
|
|
if (getsockopt(static_cast< int >(qtsSocket->socketDescriptor()), SOL_SOCKET, SO_PRIORITY, &val, &optlen) == 0) {
|
|
if (val == 0) {
|
|
val = 6;
|
|
setsockopt(static_cast< int >(qtsSocket->socketDescriptor()), SOL_SOCKET, SO_PRIORITY, &val, sizeof(val));
|
|
}
|
|
}
|
|
# endif
|
|
|
|
#endif
|
|
}
|
|
|
|
qint64 Connection::activityTime() const {
|
|
return qtLastPacket.elapsed();
|
|
}
|
|
|
|
void Connection::resetActivityTime() {
|
|
qtLastPacket.restart();
|
|
}
|
|
|
|
/**
|
|
* This function waits until a complete package is received and then emits it as a message.
|
|
* It gets called everytime new data is available and interprets the message prefix header
|
|
* to figure out the type and length. It then waits until the complete message is buffered
|
|
* and emits it as a message so it can be handled by the corresponding message handler
|
|
* routine.
|
|
*
|
|
* @see QSslSocket::readyRead()
|
|
* @see void ServerHandler::message(unsigned int msgType, const QByteArray &qbaMsg)
|
|
* @see void Server::message(unsigned int uiType, const QByteArray &qbaMsg, ServerUser *u)
|
|
*/
|
|
void Connection::socketRead() {
|
|
while (true) {
|
|
qint64 iAvailable = qtsSocket->bytesAvailable();
|
|
if (iPacketLength == -1) {
|
|
if (iAvailable < 6)
|
|
return;
|
|
|
|
unsigned char a_ucBuffer[6];
|
|
|
|
qtsSocket->read(reinterpret_cast< char * >(a_ucBuffer), 6);
|
|
m_type = static_cast< Mumble::Protocol::TCPMessageType >(qFromBigEndian< quint16 >(&a_ucBuffer[0]));
|
|
iPacketLength = qFromBigEndian< int >(&a_ucBuffer[2]);
|
|
iAvailable -= 6;
|
|
}
|
|
|
|
if ((iPacketLength == -1) || (iAvailable < iPacketLength))
|
|
return;
|
|
|
|
if (iPacketLength > 0x7fffff) {
|
|
qWarning() << "Host tried to send huge packet";
|
|
disconnectSocket(true);
|
|
return;
|
|
}
|
|
|
|
QByteArray qbaBuffer = qtsSocket->read(iPacketLength);
|
|
iPacketLength = -1;
|
|
iAvailable -= iPacketLength;
|
|
|
|
emit message(m_type, qbaBuffer);
|
|
}
|
|
}
|
|
|
|
void Connection::socketError(QAbstractSocket::SocketError err) {
|
|
emit connectionClosed(err, qtsSocket->errorString());
|
|
}
|
|
|
|
void Connection::socketSslErrors(const QList< QSslError > &qlErr) {
|
|
emit handleSslErrors(qlErr);
|
|
}
|
|
|
|
void Connection::proceedAnyway() {
|
|
qtsSocket->ignoreSslErrors();
|
|
}
|
|
|
|
void Connection::socketDisconnected() {
|
|
emit connectionClosed(QAbstractSocket::UnknownSocketError, QString());
|
|
}
|
|
|
|
void Connection::messageToNetwork(const ::google::protobuf::Message &msg, Mumble::Protocol::TCPMessageType msgType,
|
|
QByteArray &cache) {
|
|
#if GOOGLE_PROTOBUF_VERSION >= 3004000
|
|
std::size_t len = msg.ByteSizeLong();
|
|
#else
|
|
// ByteSize() has been deprecated as of protobuf v3.4
|
|
std::size_t len = msg.ByteSize();
|
|
#endif
|
|
if (len > 0x7fffff)
|
|
return;
|
|
cache.resize(static_cast< int >(len + 6));
|
|
unsigned char *uc = reinterpret_cast< unsigned char * >(cache.data());
|
|
qToBigEndian< quint16 >(static_cast< quint16 >(msgType), &uc[0]);
|
|
qToBigEndian< quint32 >(static_cast< unsigned int >(len), &uc[2]);
|
|
|
|
msg.SerializeToArray(uc + 6, static_cast< int >(len));
|
|
}
|
|
|
|
void Connection::sendMessage(const ::google::protobuf::Message &msg, Mumble::Protocol::TCPMessageType msgType,
|
|
QByteArray &cache) {
|
|
if (cache.isEmpty()) {
|
|
messageToNetwork(msg, msgType, cache);
|
|
}
|
|
|
|
sendMessage(cache);
|
|
}
|
|
|
|
void Connection::sendMessage(const QByteArray &qbaMsg) {
|
|
if (!qbaMsg.isEmpty())
|
|
qtsSocket->write(qbaMsg);
|
|
}
|
|
|
|
void Connection::forceFlush() {
|
|
if (qtsSocket->state() != QAbstractSocket::ConnectedState)
|
|
return;
|
|
|
|
if (!qtsSocket->isEncrypted())
|
|
return;
|
|
|
|
qtsSocket->flush();
|
|
}
|
|
|
|
void Connection::disconnectSocket(bool force) {
|
|
if (qtsSocket->state() == QAbstractSocket::UnconnectedState) {
|
|
emit connectionClosed(QAbstractSocket::UnknownSocketError, QString());
|
|
return;
|
|
}
|
|
|
|
if (force)
|
|
qtsSocket->abort();
|
|
else
|
|
qtsSocket->disconnectFromHost();
|
|
}
|
|
|
|
QHostAddress Connection::peerAddress() const {
|
|
return qtsSocket->peerAddress();
|
|
}
|
|
|
|
quint16 Connection::peerPort() const {
|
|
return qtsSocket->peerPort();
|
|
}
|
|
|
|
QHostAddress Connection::localAddress() const {
|
|
return qtsSocket->localAddress();
|
|
}
|
|
|
|
quint16 Connection::localPort() const {
|
|
return qtsSocket->localPort();
|
|
}
|
|
|
|
QList< QSslCertificate > Connection::peerCertificateChain() const {
|
|
// The documentation of QSslSocket::peerCertificateChain() actually says nothing
|
|
// about the order of the certificates in the chain. The sentence in this functions
|
|
// documentation is taken from QSslConfiguration::peerCertificateChain().
|
|
// Through tests and by looking into Qt's source code it was validated,
|
|
// that these two functions do the same thing.
|
|
// See mumble-voip/mumble#5280 for more information.
|
|
return qtsSocket->peerCertificateChain();
|
|
}
|
|
|
|
QSslCipher Connection::sessionCipher() const {
|
|
return qtsSocket->sessionCipher();
|
|
}
|
|
|
|
QSsl::SslProtocol Connection::sessionProtocol() const {
|
|
#if QT_VERSION >= 0x050400
|
|
return qtsSocket->sessionProtocol();
|
|
#else
|
|
return QSsl::UnknownProtocol; // Cannot determine session cipher. We only know it's some TLS variant
|
|
#endif
|
|
}
|
|
|
|
QString Connection::sessionProtocolString() const {
|
|
#if QT_VERSION >= 0x050400
|
|
return MumbleSSL::protocolToString(sessionProtocol());
|
|
#else
|
|
return QLatin1String("TLS"); // Cannot determine session cipher. We only know it's some TLS variant
|
|
#endif
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
void Connection::setQoS(HANDLE hParentQoS) {
|
|
hQoS = hParentQoS;
|
|
}
|
|
#endif
|