diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-11 07:31:23 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-11 07:31:23 +0000 |
| commit | 40c39853816cfab113d79682c34bc76a2c79c357 (patch) | |
| tree | e4d6a184cf565cb87477339ce738299ff9787bc3 /src/QXmppStun.cpp | |
| parent | 551c284e35280b7b91a939fe7352e496ffea402a (diff) | |
| download | qxmpp-40c39853816cfab113d79682c34bc76a2c79c357.tar.gz | |
rename "source" directory to "src"
Diffstat (limited to 'src/QXmppStun.cpp')
| -rw-r--r-- | src/QXmppStun.cpp | 979 |
1 files changed, 979 insertions, 0 deletions
diff --git a/src/QXmppStun.cpp b/src/QXmppStun.cpp new file mode 100644 index 00000000..5bd3fd54 --- /dev/null +++ b/src/QXmppStun.cpp @@ -0,0 +1,979 @@ +/* + * Copyright (C) 2008-2010 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#define QXMPP_DEBUG_STUN + +#include <QCryptographicHash> +#include <QDebug> +#include <QNetworkInterface> +#include <QUdpSocket> +#include <QTimer> + +#include "QXmppStun.h" +#include "QXmppUtils.h" + +#define ID_SIZE 12 + +static const quint32 STUN_MAGIC = 0x2112A442; +static const quint16 STUN_HEADER = 20; +static const quint8 STUN_IPV4 = 0x01; +static const quint8 STUN_IPV6 = 0x02; + +enum MessageType { + BindingRequest = 0x0001, + BindingIndication = 0x0011, + BindingResponse = 0x0101, + BindingError = 0x0111, + SharedSecretRequest = 0x0002, + SharedSecretResponse = 0x0102, + SharedSecretError = 0x0112, +}; + +enum AttributeType { + MappedAddress = 0x0001, + Username = 0x0006, + MessageIntegrity = 0x0008, + ErrorCode = 0x0009, + XorMappedAddress = 0x0020, + Priority = 0x0024, + UseCandidate = 0x0025, + Software = 0x8022, + Fingerprint = 0x8028, + IceControlled = 0x8029, + IceControlling = 0x802a, + OtherAddress = 0x802c, +}; + +static bool decodeAddress(QDataStream &stream, quint16 a_length, QHostAddress &address, quint16 &port) +{ + if (a_length < 4) + return false; + quint8 reserved, protocol; + stream >> reserved; + stream >> protocol; + stream >> port; + if (protocol == STUN_IPV4) + { + if (a_length != 8) + return false; + quint32 addr; + stream >> addr; + address = QHostAddress(addr); + } else if (protocol == STUN_IPV6) { + if (a_length != 20) + return false; + Q_IPV6ADDR addr; + stream.readRawData((char*)&addr, sizeof(addr)); + address = QHostAddress(addr); + } else { + return false; + } + return true; +} + +static void encodeAddress(QDataStream &stream, const QHostAddress &address, quint16 port) +{ + stream << quint16(8); + stream << quint8(0); + if (address.protocol() == QAbstractSocket::IPv4Protocol) + { + stream << quint8(STUN_IPV4); + stream << port; + stream << address.toIPv4Address(); + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + stream << quint8(STUN_IPV6); + stream << port; + Q_IPV6ADDR addr = address.toIPv6Address(); + stream.writeRawData((char*)&addr, sizeof(addr)); + } +} + +static void encodeString(QDataStream &stream, const QString &string) +{ + const QByteArray utf8string = string.toUtf8(); + stream << quint16(utf8string.size()); + stream.writeRawData(utf8string.data(), utf8string.size()); + if (utf8string.size() % 4) + { + const QByteArray padding(4 - (utf8string.size() % 4), 0); + stream.writeRawData(padding.data(), padding.size()); + } +} + +static QByteArray randomByteArray(int length) +{ + QByteArray result(length, 0); + for (int i = 0; i < length; ++i) + result[i] = quint8(qrand() % 255); + return result; +} + +/// Constructs a new QXmppStunMessage. + +QXmppStunMessage::QXmppStunMessage() + : errorCode(0), + priority(0), + mappedPort(0), + otherPort(0), + xorMappedPort(0), + useCandidate(false) +{ + m_id = QByteArray(ID_SIZE, 0); +} + +QByteArray QXmppStunMessage::id() const +{ + return m_id; +} + +void QXmppStunMessage::setId(const QByteArray &id) +{ + Q_ASSERT(id.size() == ID_SIZE); + m_id = id; +} + +quint16 QXmppStunMessage::type() const +{ + return m_type; +} + +void QXmppStunMessage::setType(quint16 type) +{ + m_type = type; +} + +/// Decodes a QXmppStunMessage and checks its integrity using the given +/// password. +/// +/// \param buffer +/// \param password + +bool QXmppStunMessage::decode(const QByteArray &buffer, const QString &password, QStringList *errors) +{ + QStringList silent; + if (!errors) + errors = &silent; + + if (buffer.size() < STUN_HEADER) + { + *errors << QLatin1String("Received a truncated STUN packet"); + return false; + } + + // parse STUN header + QDataStream stream(buffer); + quint16 length; + quint32 cookie; + stream >> m_type; + stream >> length; + stream >> cookie; + stream.readRawData(m_id.data(), m_id.size()); + + if (cookie != STUN_MAGIC || length != buffer.size() - STUN_HEADER) + { + *errors << QLatin1String("Received an invalid STUN packet"); + return false; + } + + // parse STUN attributes + int done = 0; + bool after_integrity = false; + while (done < length) + { + quint16 a_type, a_length; + stream >> a_type; + stream >> a_length; + const int pad_length = 4 * ((a_length + 3) / 4) - a_length; + + // only FINGERPRINT is allowed after MESSAGE-INTEGRITY + if (after_integrity && a_type != Fingerprint) + { + *errors << QString("Skipping attribute %1 after MESSAGE-INTEGRITY").arg(QString::number(a_type)); + stream.skipRawData(a_length + pad_length); + done += 4 + a_length + pad_length; + continue; + } + + if (a_type == Priority) + { + // PRIORITY + if (a_length != sizeof(priority)) + return false; + stream >> priority; + + } else if (a_type == ErrorCode) { + + // ERROR-CODE + if (a_length < 4) + return false; + quint16 reserved; + quint8 errorCodeHigh, errorCodeLow; + stream >> reserved; + stream >> errorCodeHigh; + stream >> errorCodeLow; + errorCode = errorCodeHigh * 100 + errorCodeLow; + QByteArray phrase(a_length - 4, 0); + stream.readRawData(phrase.data(), phrase.size()); + errorPhrase = QString::fromUtf8(phrase); + + } else if (a_type == UseCandidate) { + + // USE-CANDIDATE + if (a_length != 0) + return false; + useCandidate = true; + + } else if (a_type == Software) { + + // SOFTWARE + QByteArray utf8Software(a_length, 0); + stream.readRawData(utf8Software.data(), utf8Software.size()); + software = QString::fromUtf8(utf8Software); + + } else if (a_type == MappedAddress) { + + // MAPPED-ADDRESS + if (!decodeAddress(stream, a_length, mappedHost, mappedPort)) + { + *errors << QLatin1String("Bad MAPPED-ADDRESS"); + return false; + } + + } else if (a_type == OtherAddress) { + + // OTHER-ADDRESS + if (!decodeAddress(stream, a_length, otherHost, otherPort)) + { + *errors << QLatin1String("Bad OTHER-ADDRESS"); + return false; + } + + } else if (a_type == XorMappedAddress) { + + // XOR-MAPPED-ADDRESS + if (a_length < 4) + return false; + quint8 reserved, protocol; + quint16 xport; + stream >> reserved; + stream >> protocol; + stream >> xport; + xorMappedPort = xport ^ (STUN_MAGIC >> 16); + if (protocol == STUN_IPV4) + { + if (a_length != 8) + return false; + quint32 xaddr; + stream >> xaddr; + xorMappedHost = QHostAddress(xaddr ^ STUN_MAGIC); + } else if (protocol == STUN_IPV6) { + if (a_length != 20) + return false; + QByteArray xaddr(16, 0); + stream.readRawData(xaddr.data(), xaddr.size()); + QByteArray xpad; + QDataStream(&xpad, QIODevice::WriteOnly) << STUN_MAGIC; + xpad += m_id; + Q_IPV6ADDR addr; + for (int i = 0; i < 16; i++) + addr[i] = xaddr[i] ^ xpad[i]; + xorMappedHost = QHostAddress(addr); + } else { + *errors << QString("Bad protocol %1").arg(QString::number(protocol)); + return false; + } + + } else if (a_type == MessageIntegrity) { + + // MESSAGE-INTEGRITY + if (a_length != 20) + return false; + QByteArray integrity(20, 0); + stream.readRawData(integrity.data(), integrity.size()); + + // check HMAC-SHA1 + if (!password.isEmpty()) + { + const QByteArray key = password.toUtf8(); + QByteArray copy = buffer.left(STUN_HEADER + done); + setBodyLength(copy, done + 24); + if (integrity != generateHmacSha1(key, copy)) + { + *errors << QLatin1String("Bad message integrity"); + return false; + } + } + + // from here onwards, only FINGERPRINT is allowed + after_integrity = true; + + } else if (a_type == Fingerprint) { + + // FINGERPRINT + if (a_length != 4) + return false; + quint32 fingerprint; + stream >> fingerprint; + + // check CRC32 + QByteArray copy = buffer.left(STUN_HEADER + done); + setBodyLength(copy, done + 8); + const quint32 expected = generateCrc32(copy) ^ 0x5354554eL; + if (fingerprint != expected) + { + *errors << QLatin1String("Bad fingerprint"); + return false; + } + + // stop parsing, no more attributes are allowed + return true; + + } else if (a_type == IceControlling) { + + /// ICE-CONTROLLING + if (a_length != 8) + return false; + iceControlling.resize(8); + stream.readRawData(iceControlling.data(), iceControlling.size()); + + } else if (a_type == IceControlled) { + + /// ICE-CONTROLLED + if (a_length != 8) + return false; + iceControlled.resize(8); + stream.readRawData(iceControlled.data(), iceControlled.size()); + + } else if (a_type == Username) { + + // USERNAME + QByteArray utf8Username(a_length, 0); + stream.readRawData(utf8Username.data(), utf8Username.size()); + username = QString::fromUtf8(utf8Username); + + } else { + + // Unknown attribute + stream.skipRawData(a_length); + *errors << QString("Skipping unknown attribute %1").arg(QString::number(a_type)); + + } + stream.skipRawData(pad_length); + done += 4 + a_length + pad_length; + } + return true; +} + +/// Encodes the current QXmppStunMessage, optionally calculating the +/// message integrity attribute using the given password. +/// +/// \param password + +QByteArray QXmppStunMessage::encode(const QString &password) const +{ + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + + // encode STUN header + quint16 length = 0; + stream << m_type; + stream << length; + stream << STUN_MAGIC; + stream.writeRawData(m_id.data(), m_id.size()); + + // MAPPED-ADDRESS + if (mappedPort && !mappedHost.isNull() && + (mappedHost.protocol() == QAbstractSocket::IPv4Protocol || + mappedHost.protocol() == QAbstractSocket::IPv6Protocol)) + { + stream << quint16(MappedAddress); + encodeAddress(stream, mappedHost, mappedPort); + } + + // OTHER-ADDRESS + if (otherPort && !otherHost.isNull() && + (otherHost.protocol() == QAbstractSocket::IPv4Protocol || + otherHost.protocol() == QAbstractSocket::IPv6Protocol)) + { + stream << quint16(OtherAddress); + encodeAddress(stream, otherHost, otherPort); + } + + // XOR-MAPPED-ADDRESS + if (xorMappedPort && !xorMappedHost.isNull() && + (xorMappedHost.protocol() == QAbstractSocket::IPv4Protocol || + xorMappedHost.protocol() == QAbstractSocket::IPv6Protocol)) + { + stream << quint16(XorMappedAddress); + stream << quint16(8); + stream << quint8(0); + if (xorMappedHost.protocol() == QAbstractSocket::IPv4Protocol) + { + stream << quint8(STUN_IPV4); + stream << quint16(xorMappedPort ^ (STUN_MAGIC >> 16)); + stream << quint32(xorMappedHost.toIPv4Address() ^ STUN_MAGIC); + } else { + stream << quint8(STUN_IPV6); + stream << quint16(xorMappedPort ^ (STUN_MAGIC >> 16)); + Q_IPV6ADDR addr = xorMappedHost.toIPv6Address(); + QByteArray xaddr; + QDataStream(&xaddr, QIODevice::WriteOnly) << STUN_MAGIC; + xaddr += m_id; + for (int i = 0; i < 16; i++) + xaddr[i] = xaddr[i] ^ addr[i]; + stream.writeRawData(xaddr.data(), xaddr.size()); + } + } + + // ERROR-CODE + if (errorCode) + { + const quint16 reserved = 0; + const quint8 errorCodeHigh = errorCode / 100; + const quint8 errorCodeLow = errorCode % 100; + const QByteArray phrase = errorPhrase.toUtf8(); + stream << quint16(ErrorCode); + stream << quint16(phrase.size() + 4); + stream << reserved; + stream << errorCodeHigh; + stream << errorCodeLow; + stream.writeRawData(phrase.data(), phrase.size()); + if (phrase.size() % 4) + { + const QByteArray padding(4 - (phrase.size() % 4), 0); + stream.writeRawData(padding.data(), padding.size()); + } + } + + // PRIORITY + if (priority) + { + stream << quint16(Priority); + stream << quint16(sizeof(priority)); + stream << priority; + } + + // USE-CANDIDATE + if (useCandidate) + { + stream << quint16(UseCandidate); + stream << quint16(0); + } + + // SOFTWARE + if (!software.isEmpty()) + { + stream << quint16(Software); + encodeString(stream, software); + } + + // ICE-CONTROLLING or ICE-CONTROLLED + if (!iceControlling.isEmpty()) + { + stream << quint16(IceControlling); + stream << quint16(iceControlling.size()); + stream.writeRawData(iceControlling.data(), iceControlling.size()); + } else if (!iceControlled.isEmpty()) { + stream << quint16(IceControlled); + stream << quint16(iceControlled.size()); + stream.writeRawData(iceControlled.data(), iceControlled.size()); + } + + // USERNAME + if (!username.isEmpty()) + { + stream << quint16(Username); + encodeString(stream, username); + } + + // set body length + setBodyLength(buffer, buffer.size() - STUN_HEADER); + + // MESSAGE-INTEGRITY + if (!password.isEmpty()) + { + const QByteArray key = password.toUtf8(); + setBodyLength(buffer, buffer.size() - STUN_HEADER + 24); + QByteArray integrity = generateHmacSha1(key, buffer); + stream << quint16(MessageIntegrity); + stream << quint16(integrity.size()); + stream.writeRawData(integrity.data(), integrity.size()); + } + + // FINGERPRINT + setBodyLength(buffer, buffer.size() - STUN_HEADER + 8); + quint32 fingerprint = generateCrc32(buffer) ^ 0x5354554eL; + stream << quint16(Fingerprint); + stream << quint16(sizeof(fingerprint)); + stream << fingerprint; + + return buffer; +} + +/// If the given packet looks like a STUN message, returns the message +/// type, otherwise returns 0. +/// +/// \param buffer + +quint16 QXmppStunMessage::peekType(const QByteArray &buffer) +{ + if (buffer.size() < STUN_HEADER) + return 0; + + // parse STUN header + QDataStream stream(buffer); + quint16 type; + quint16 length; + quint32 cookie; + stream >> type; + stream >> length; + stream >> cookie; + + if (cookie != STUN_MAGIC || length != buffer.size() - STUN_HEADER) + return 0; + + return type; +} + +void QXmppStunMessage::setBodyLength(QByteArray &buffer, qint16 length) const +{ + QDataStream stream(&buffer, QIODevice::WriteOnly); + stream.device()->seek(2); + stream << length; +} + +QString QXmppStunMessage::toString() const +{ + QStringList dumpLines; + QString typeName; + switch (m_type & 0x000f) + { + case 1: typeName = "Binding"; break; + case 2: typeName = "Shared Secret"; break; + default: typeName = "Unknown"; break; + } + switch (m_type & 0x0ff0) + { + case 0x000: typeName += " Request"; break; + case 0x010: typeName += " Indication"; break; + case 0x100: typeName += " Response"; break; + case 0x110: typeName += " Error"; break; + default: break; + } + dumpLines << QString(" type %1 (%2)") + .arg(typeName) + .arg(QString::number(m_type)); + dumpLines << QString(" id %1").arg(QString::fromAscii(m_id.toHex())); + + // attributes + if (!username.isEmpty()) + dumpLines << QString(" * USERNAME %1").arg(username); + if (errorCode) + dumpLines << QString(" * ERROR-CODE %1 %2") + .arg(QString::number(errorCode)) + .arg(errorPhrase); + if (!software.isEmpty()) + dumpLines << QString(" * SOFTWARE %1").arg(software); + if (mappedPort) + dumpLines << QString(" * MAPPED-ADDRESS %1 %2") + .arg(mappedHost.toString()) + .arg(QString::number(mappedPort)); + if (otherPort) + dumpLines << QString(" * OTHER-ADDRESS %1 %2") + .arg(otherHost.toString()) + .arg(QString::number(otherPort)); + if (xorMappedPort) + dumpLines << QString(" * XOR-MAPPED-ADDRESS %1 %2") + .arg(xorMappedHost.toString()) + .arg(QString::number(xorMappedPort)); + if (!iceControlling.isEmpty()) + dumpLines << QString(" * ICE-CONTROLLING %1") + .arg(QString::fromAscii(iceControlling.toHex())); + if (!iceControlled.isEmpty()) + dumpLines << QString(" * ICE-CONTROLLED %1") + .arg(QString::fromAscii(iceControlled.toHex())); + + return dumpLines.join("\n"); +} + +QXmppStunSocket::Pair::Pair() + : checked(QIODevice::NotOpen) +{ + // FIXME : calculate priority + priority = 1862270975; + transaction = randomByteArray(ID_SIZE); +} + +QString QXmppStunSocket::Pair::toString() const +{ + QString str = QString("%1 %2").arg(remote.host().toString(), QString::number(remote.port())); + if (!reflexive.host().isNull() && reflexive.port()) + str += QString(" (reflexive %1 %2)").arg(reflexive.host().toString(), QString::number(reflexive.port())); + return str; +} + +/// Constructs a new QXmppStunSocket. +/// + +QXmppStunSocket::QXmppStunSocket(bool iceControlling, QObject *parent) + : QObject(parent), + m_activePair(0), + m_iceControlling(iceControlling) +{ + m_localUser = generateStanzaHash(4); + m_localPassword = generateStanzaHash(22); + + m_socket = new QUdpSocket(this); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(readyRead())); + if (!m_socket->bind()) + qWarning("QXmppStunSocket could not start listening"); + + m_timer = new QTimer(this); + m_timer->setInterval(500); + connect(m_timer, SIGNAL(timeout()), this, SLOT(checkCandidates())); +} + +QXmppStunSocket::~QXmppStunSocket() +{ + foreach (Pair *pair, m_pairs) + delete pair; +} + +/// Returns the component id for the current socket, e.g. 1 for RTP +/// and 2 for RTCP. + +int QXmppStunSocket::component() const +{ + return m_component; +} + +/// Sets the component id for the current socket, e.g. 1 for RTP +/// and 2 for RTCP. +/// +/// \param component + +void QXmppStunSocket::setComponent(int component) +{ + m_component = component; +} + +void QXmppStunSocket::checkCandidates() +{ + debug("Checking remote candidates"); + foreach (Pair *pair, m_pairs) + { + // send a binding request + QXmppStunMessage message; + message.setId(pair->transaction); + message.setType(BindingRequest); + message.priority = pair->priority; + message.username = QString("%1:%2").arg(m_remoteUser, m_localUser); + if (m_iceControlling) + { + message.iceControlling = QByteArray(8, 0); + message.useCandidate = true; + } else { + message.iceControlled = QByteArray(8, 0); + } + writeStun(message, pair); + } +} + +/// Closes the socket. + +void QXmppStunSocket::close() +{ + m_socket->close(); + m_timer->stop(); +} + +/// Start ICE connectivity checks. + +void QXmppStunSocket::connectToHost() +{ + checkCandidates(); + m_timer->start(); +} + +/// Returns true if ICE negotiation completed, false otherwise. + +bool QXmppStunSocket::isConnected() const +{ + return m_activePair != 0; +} + +void QXmppStunSocket::debug(const QString &message, QXmppLogger::MessageType type) +{ + emit logMessage(type, QString("STUN(%1) %2").arg(QString::number(m_component)).arg(message)); +} + +/// Returns the list of local HOST CANDIDATES candidates by iterating +/// over the available network interfaces. + +QList<QXmppJingleCandidate> QXmppStunSocket::localCandidates() const +{ + QList<QXmppJingleCandidate> candidates; + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) + { + if (!(interface.flags() & QNetworkInterface::IsRunning) || + interface.flags() & QNetworkInterface::IsLoopBack) + continue; + + foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) + { + if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol || + entry.netmask().isNull() || + entry.netmask() == QHostAddress::Broadcast) + continue; + + QXmppJingleCandidate candidate; + candidate.setComponent(m_component); + candidate.setHost(entry.ip()); + candidate.setId(generateStanzaHash(10)); + candidate.setNetwork(interface.index()); + candidate.setPort(m_socket->localPort()); + candidate.setPriority(2130706432 - m_component); + candidate.setProtocol("udp"); + candidate.setType("host"); + candidates << candidate; + } + } + return candidates; +} + +QString QXmppStunSocket::localUser() const +{ + return m_localUser; +} + +void QXmppStunSocket::setLocalUser(const QString &user) +{ + m_localUser = user; +} + +QString QXmppStunSocket::localPassword() const +{ + return m_localPassword; +} + +void QXmppStunSocket::setLocalPassword(const QString &password) +{ + m_localPassword = password; +} + +/// Adds a remote STUN candidate. + +bool QXmppStunSocket::addRemoteCandidate(const QXmppJingleCandidate &candidate) +{ + if (candidate.component() != m_component || + candidate.type() != "host" || + candidate.protocol() != "udp") + return false; + + foreach (Pair *pair, m_pairs) + if (pair->remote.host() == candidate.host() && + pair->remote.port() == candidate.port()) + return false; + + Pair *pair = new Pair; + pair->remote = candidate; + m_pairs << pair; + return true; +} + +/// Adds a discovered STUN candidate. + +QXmppStunSocket::Pair *QXmppStunSocket::addRemoteCandidate(const QHostAddress &host, quint16 port) +{ + foreach (Pair *pair, m_pairs) + if (pair->remote.host() == host && + pair->remote.port() == port) + return pair; + + QXmppJingleCandidate candidate; + candidate.setHost(host); + candidate.setPort(port); + candidate.setProtocol("udp"); + candidate.setComponent(m_component); + + Pair *pair = new Pair; + pair->remote = candidate; + m_pairs << pair; + + debug(QString("Added candidate %1").arg(pair->toString())); + return pair; +} + +void QXmppStunSocket::setRemoteUser(const QString &user) +{ + m_remoteUser = user; +} + +void QXmppStunSocket::setRemotePassword(const QString &password) +{ + m_remotePassword = password; +} + +void QXmppStunSocket::readyRead() +{ + const qint64 size = m_socket->pendingDatagramSize(); + QHostAddress remoteHost; + quint16 remotePort; + QByteArray buffer(size, 0); + m_socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort); + + // if this is not a STUN message, emit it + quint16 messageType = QXmppStunMessage::peekType(buffer); + if (!messageType) + { + emit datagramReceived(buffer); + return; + } + + // parse STUN message + const QString messagePassword = (messageType & 0xFF00) ? m_remotePassword : m_localPassword; + if (messagePassword.isEmpty()) + return; + QXmppStunMessage message; + QStringList errors; + if (!message.decode(buffer, messagePassword, &errors)) + { + foreach (const QString &error, errors) + debug(error, QXmppLogger::WarningMessage); + return; + } +#ifdef QXMPP_DEBUG_STUN + debug(QString("Received from %1 port %2\n%3") + .arg(remoteHost.toString()) + .arg(QString::number(remotePort)) + .arg(message.toString()), + QXmppLogger::ReceivedMessage); +#endif + + if (m_activePair) + return; + + // process message + Pair *pair = 0; + if (message.type() == BindingRequest) + { + // add remote candidate + pair = addRemoteCandidate(remoteHost, remotePort); + + // send a binding response + QXmppStunMessage response; + response.setId(message.id()); + response.setType(BindingResponse); + response.username = message.username; + response.xorMappedHost = pair->remote.host(); + response.xorMappedPort = pair->remote.port(); + writeStun(response, pair); + + // update state + if (m_iceControlling || message.useCandidate) + { + debug(QString("ICE reverse check %1").arg(pair->toString())); + pair->checked |= QIODevice::ReadOnly; + } + + if (!m_iceControlling) + { + // send a triggered connectivity test + QXmppStunMessage message; + message.setId(pair->transaction); + message.setType(BindingRequest); + message.priority = pair->priority; + message.username = QString("%1:%2").arg(m_remoteUser, m_localUser); + message.iceControlled = QByteArray(8, 0); + writeStun(message, pair); + } + + } else if (message.type() == BindingResponse) { + + // find the pair for this transaction + foreach (Pair *ptr, m_pairs) + { + if (ptr->transaction == message.id()) + { + pair = ptr; + break; + } + } + if (!pair) + { + debug(QString("Unknown transaction %1").arg(QString::fromAscii(message.id().toHex()))); + return; + } + // store reflexive address + pair->reflexive.setHost(message.xorMappedHost); + pair->reflexive.setPort(message.xorMappedPort); + + // add the new remote candidate + addRemoteCandidate(remoteHost, remotePort); + +#if 0 + // send a binding indication + QXmppStunMessage indication; + indication.setId(randomByteArray(ID_SIZE)); + indication.setType(BindingIndication); + m_socket->writeStun(indication, pair); +#endif + // outgoing media can flow + debug(QString("ICE forward check %1").arg(pair->toString())); + pair->checked |= QIODevice::WriteOnly; + } + + // signal completion + if (pair && pair->checked == QIODevice::ReadWrite) + { + debug(QString("ICE completed %1").arg(pair->toString())); + m_activePair = pair; + m_timer->stop(); + emit connected(); + } +} + +/// Sends a data packet to the remote party. +/// +/// \param datagram + +qint64 QXmppStunSocket::writeDatagram(const QByteArray &datagram) +{ + if (!m_activePair) + return -1; + return m_socket->writeDatagram(datagram, m_activePair->remote.host(), m_activePair->remote.port()); +} + +/// Sends a STUN packet to the remote party. + +qint64 QXmppStunSocket::writeStun(const QXmppStunMessage &message, QXmppStunSocket::Pair *pair) +{ + const QString messagePassword = (message.type() & 0xFF00) ? m_localPassword : m_remotePassword; +#ifdef QXMPP_DEBUG_STUN + debug( + QString("Sent to %1\n%2").arg(pair->toString(), message.toString()), + QXmppLogger::SentMessage); +#endif + return m_socket->writeDatagram(message.encode(messagePassword), pair->remote.host(), pair->remote.port()); +} + |
