diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-22 13:44:09 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2010-08-22 13:44:09 +0000 |
| commit | 2878f5808a942d6342e47a3162669f9d23c1b824 (patch) | |
| tree | 49d21d759e01d052af17b449dca79a93604356ae /src/QXmppStun.cpp | |
| parent | b424f792b558ce766cdd2644007e5a68314a3218 (diff) | |
| download | qxmpp-2878f5808a942d6342e47a3162669f9d23c1b824.tar.gz | |
improve STUN support, discover server reflexive address
Diffstat (limited to 'src/QXmppStun.cpp')
| -rw-r--r-- | src/QXmppStun.cpp | 429 |
1 files changed, 352 insertions, 77 deletions
diff --git a/src/QXmppStun.cpp b/src/QXmppStun.cpp index d7b1bafd..3aa3fe31 100644 --- a/src/QXmppStun.cpp +++ b/src/QXmppStun.cpp @@ -24,7 +24,7 @@ #define QXMPP_DEBUG_STUN #include <QCryptographicHash> -#include <QDebug> +#include <QHostInfo> #include <QNetworkInterface> #include <QUdpSocket> #include <QTimer> @@ -51,6 +51,8 @@ enum MessageType { enum AttributeType { MappedAddress = 0x0001, + SourceAddress = 0x0004, + ChangedAddress = 0x0005, Username = 0x0006, MessageIntegrity = 0x0008, ErrorCode = 0x0009, @@ -91,8 +93,9 @@ static bool decodeAddress(QDataStream &stream, quint16 a_length, QHostAddress &a return true; } -static void encodeAddress(QDataStream &stream, const QHostAddress &address, quint16 port) +static void encodeAddress(QDataStream &stream, quint16 type, const QHostAddress &address, quint16 port) { + stream << type; stream << quint16(8); stream << quint8(0); if (address.protocol() == QAbstractSocket::IPv4Protocol) @@ -108,9 +111,10 @@ static void encodeAddress(QDataStream &stream, const QHostAddress &address, quin } } -static void encodeString(QDataStream &stream, const QString &string) +static void encodeString(QDataStream &stream, quint16 type, const QString &string) { const QByteArray utf8string = string.toUtf8(); + stream << type; stream << quint16(utf8string.size()); stream.writeRawData(utf8string.data(), utf8string.size()); if (utf8string.size() % 4) @@ -133,8 +137,10 @@ static QByteArray randomByteArray(int length) QXmppStunMessage::QXmppStunMessage() : errorCode(0), priority(0), + changedPort(0), mappedPort(0), otherPort(0), + sourcePort(0), xorMappedPort(0), useCandidate(false) { @@ -259,6 +265,24 @@ bool QXmppStunMessage::decode(const QByteArray &buffer, const QString &password, return false; } + } else if (a_type == SourceAddress) { + + // SOURCE-ADDRESS + if (!decodeAddress(stream, a_length, sourceHost, sourcePort)) + { + *errors << QLatin1String("Bad SOURCE-ADDRESS"); + return false; + } + + } else if (a_type == ChangedAddress) { + + // CHANGED-ADDRESS + if (!decodeAddress(stream, a_length, changedHost, changedPort)) + { + *errors << QLatin1String("Bad CHANGED-ADDRESS"); + return false; + } + } else if (a_type == OtherAddress) { // OTHER-ADDRESS @@ -384,6 +408,16 @@ bool QXmppStunMessage::decode(const QByteArray &buffer, const QString &password, return true; } +void QXmppStunMessage::addAddress(QDataStream &stream, quint16 type, const QHostAddress &host, quint16 port) const +{ + if (port && !host.isNull() && + (host.protocol() == QAbstractSocket::IPv4Protocol || + host.protocol() == QAbstractSocket::IPv6Protocol)) + { + encodeAddress(stream, type, host, port); + } +} + /// Encodes the current QXmppStunMessage, optionally calculating the /// message integrity attribute using the given password. /// @@ -402,22 +436,16 @@ QByteArray QXmppStunMessage::encode(const QString &password) const 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); - } + addAddress(stream, MappedAddress, mappedHost, mappedPort); + + // SOURCE-ADDRESS + addAddress(stream, SourceAddress, sourceHost, sourcePort); + + // CHANGED-ADDRESS + addAddress(stream, ChangedAddress, changedHost, changedPort); // OTHER-ADDRESS - if (otherPort && !otherHost.isNull() && - (otherHost.protocol() == QAbstractSocket::IPv4Protocol || - otherHost.protocol() == QAbstractSocket::IPv6Protocol)) - { - stream << quint16(OtherAddress); - encodeAddress(stream, otherHost, otherPort); - } + addAddress(stream, OtherAddress, otherHost, otherPort); // XOR-MAPPED-ADDRESS if (xorMappedPort && !xorMappedHost.isNull() && @@ -482,10 +510,7 @@ QByteArray QXmppStunMessage::encode(const QString &password) const // SOFTWARE if (!software.isEmpty()) - { - stream << quint16(Software); - encodeString(stream, software); - } + encodeString(stream, Software, software); // ICE-CONTROLLING or ICE-CONTROLLED if (!iceControlling.isEmpty()) @@ -501,10 +526,7 @@ QByteArray QXmppStunMessage::encode(const QString &password) const // USERNAME if (!username.isEmpty()) - { - stream << quint16(Username); - encodeString(stream, username); - } + encodeString(stream, Username, username); // set body length setBodyLength(buffer, buffer.size() - STUN_HEADER); @@ -535,7 +557,7 @@ QByteArray QXmppStunMessage::encode(const QString &password) const /// /// \param buffer -quint16 QXmppStunMessage::peekType(const QByteArray &buffer) +quint16 QXmppStunMessage::peekType(const QByteArray &buffer, QByteArray &id) { if (buffer.size() < STUN_HEADER) return 0; @@ -552,6 +574,8 @@ quint16 QXmppStunMessage::peekType(const QByteArray &buffer) if (cookie != STUN_MAGIC || length != buffer.size() - STUN_HEADER) return 0; + id.resize(ID_SIZE); + stream.readRawData(id.data(), id.size()); return type; } @@ -598,6 +622,14 @@ QString QXmppStunMessage::toString() const dumpLines << QString(" * MAPPED-ADDRESS %1 %2") .arg(mappedHost.toString()) .arg(QString::number(mappedPort)); + if (sourcePort) + dumpLines << QString(" * SOURCE-ADDRESS %1 %2") + .arg(sourceHost.toString()) + .arg(QString::number(sourcePort)); + if (changedPort) + dumpLines << QString(" * CHANGED-ADDRESS %1 %2") + .arg(changedHost.toString()) + .arg(QString::number(changedPort)); if (otherPort) dumpLines << QString(" * OTHER-ADDRESS %1 %2") .arg(otherHost.toString()) @@ -638,15 +670,15 @@ QString QXmppStunSocket::Pair::toString() const QXmppStunSocket::QXmppStunSocket(bool iceControlling, QObject *parent) : QObject(parent), m_activePair(0), - m_iceControlling(iceControlling) + m_iceControlling(iceControlling), + m_stunDone(false), + m_stunPort(0) { 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); @@ -659,6 +691,45 @@ QXmppStunSocket::~QXmppStunSocket() delete pair; } +bool QXmppStunSocket::bind() +{ + if (!m_socket->bind()) + { + debug("QXmppStunSocket could not start listening", + QXmppLogger::WarningMessage); + return false; + } + + // store HOST candidates + m_localCandidates.clear(); + 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"); + m_localCandidates << candidate; + } + } + return true; +} + /// Returns the component id for the current socket, e.g. 1 for RTP /// and 2 for RTCP. @@ -697,6 +768,20 @@ void QXmppStunSocket::checkCandidates() } writeStun(message, pair); } + + /// Send a request to STUN server to determine server-reflexive candidate + if (!m_stunHost.isNull() && m_stunPort != 0 && !m_stunDone) + { + QXmppStunMessage msg; + msg.setType(BindingRequest); + msg.setId(m_stunId); +#ifdef QXMPP_DEBUG_STUN + debug( + QString("Sent to %1 %2\n%3").arg(m_stunHost.toString(), QString::number(m_stunPort), msg.toString()), + QXmppLogger::SentMessage); +#endif + m_socket->writeDatagram(msg.encode(), m_stunHost, m_stunPort); + } } /// Closes the socket. @@ -707,10 +792,13 @@ void QXmppStunSocket::close() m_timer->stop(); } -/// Start ICE connectivity checks. +/// Starts ICE connectivity checks. void QXmppStunSocket::connectToHost() { + if (m_activePair) + return; + checkCandidates(); m_timer->start(); } @@ -727,43 +815,11 @@ void QXmppStunSocket::debug(const QString &message, QXmppLogger::MessageType typ 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. +/// Returns the list of local candidates. 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; + return m_localCandidates; } void QXmppStunSocket::setLocalUser(const QString &user) @@ -771,11 +827,6 @@ 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; @@ -786,7 +837,7 @@ void QXmppStunSocket::setLocalPassword(const QString &password) bool QXmppStunSocket::addRemoteCandidate(const QXmppJingleCandidate &candidate) { if (candidate.component() != m_component || - candidate.type() != "host" || + (candidate.type() != "host" && candidate.type() != "srflx") || candidate.protocol() != "udp") return false; @@ -834,6 +885,13 @@ void QXmppStunSocket::setRemotePassword(const QString &password) m_remotePassword = password; } +void QXmppStunSocket::setStunServer(const QHostAddress &host, quint16 port) +{ + m_stunHost = host; + m_stunPort = port; + m_stunId = randomByteArray(ID_SIZE); +} + void QXmppStunSocket::readyRead() { const qint64 size = m_socket->pendingDatagramSize(); @@ -843,17 +901,24 @@ void QXmppStunSocket::readyRead() m_socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort); // if this is not a STUN message, emit it - quint16 messageType = QXmppStunMessage::peekType(buffer); + QByteArray messageId; + quint16 messageType = QXmppStunMessage::peekType(buffer, messageId); if (!messageType) { emit datagramReceived(buffer); return; } + // determine password to use + QString messagePassword; + if (messageId != m_stunId) + { + messagePassword = (messageType & 0xFF00) ? m_remotePassword : m_localPassword; + if (messagePassword.isEmpty()) + 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)) @@ -870,10 +935,55 @@ void QXmppStunSocket::readyRead() QXmppLogger::ReceivedMessage); #endif - if (m_activePair) + // check how to handle message + if (message.id() == m_stunId) + { + m_stunDone = true; + + // determine server-reflexive address + QHostAddress reflexiveHost; + quint16 reflexivePort = 0; + if (!message.xorMappedHost.isNull() && message.xorMappedPort != 0) + { + reflexiveHost = message.xorMappedHost; + reflexivePort = message.xorMappedPort; + } + else if (!message.mappedHost.isNull() && message.mappedPort != 0) + { + reflexiveHost = message.mappedHost; + reflexivePort = message.mappedPort; + } else { + debug("STUN server did not provide a reflexive address", + QXmppLogger::WarningMessage); + return; + } + + // check whether this candidates is already known + foreach (const QXmppJingleCandidate &candidate, m_localCandidates) + { + if (candidate.host() == reflexiveHost && candidate.port() == reflexivePort) + return; + } + + // add the new local candidate + debug(QString("Adding server-reflexive candidate %1 %2").arg(reflexiveHost.toString(), QString::number(reflexivePort))); + QXmppJingleCandidate candidate; + candidate.setComponent(m_component); + candidate.setHost(reflexiveHost); + candidate.setId(generateStanzaHash(10)); + candidate.setPort(reflexivePort); + candidate.setPriority(2130706432 - m_component); + candidate.setProtocol("udp"); + candidate.setType("srflx"); + m_localCandidates << candidate; + + emit localCandidatesChanged(); + return; + } + else if (m_activePair) return; - // process message + // process message from peer Pair *pair = 0; if (message.type() == BindingRequest) { @@ -924,7 +1034,7 @@ void QXmppStunSocket::readyRead() debug(QString("Unknown transaction %1").arg(QString::fromAscii(message.id().toHex()))); return; } - // store reflexive address + // store peer-reflexive address pair->reflexive.setHost(message.xorMappedHost); pair->reflexive.setPort(message.xorMappedPort); @@ -977,3 +1087,168 @@ qint64 QXmppStunSocket::writeStun(const QXmppStunMessage &message, QXmppStunSock return m_socket->writeDatagram(message.encode(messagePassword), pair->remote.host(), pair->remote.port()); } +QXmppIceConnection::QXmppIceConnection(bool controlling, QObject *parent) + : QObject(parent), + m_controlling(controlling), + m_stunPort(0) +{ + m_localUser = generateStanzaHash(4); + m_localPassword = generateStanzaHash(22); +} + +void QXmppIceConnection::addComponent(int component) +{ + QXmppStunSocket *socket = new QXmppStunSocket(m_controlling, this); + socket->setComponent(component); + socket->setLocalUser(m_localUser); + socket->setLocalPassword(m_localPassword); + socket->setStunServer(m_stunHost, m_stunPort); + + bool check = connect(socket, SIGNAL(logMessage(QXmppLogger::MessageType, QString)), + this, SIGNAL(logMessage(QXmppLogger::MessageType, QString))); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(localCandidatesChanged()), + this, SIGNAL(localCandidatesChanged())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(connected()), + this, SLOT(slotConnected())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(datagramReceived(QByteArray)), + this, SLOT(slotDatagramReceived(QByteArray))); + Q_ASSERT(check); + + if (!socket->bind()) + { + qWarning("QXmppStunSocket could not start listening"); + socket->deleteLater(); + return; + } + + m_components[component] = socket; +} + +void QXmppIceConnection::addRemoteCandidate(const QXmppJingleCandidate &candidate) +{ + foreach (QXmppStunSocket *socket, m_components.values()) + socket->addRemoteCandidate(candidate); +} + +/// Closes the ICE connection. + +void QXmppIceConnection::close() +{ + foreach (QXmppStunSocket *socket, m_components.values()) + socket->close(); +} + +/// Starts ICE connectivity checks. + +void QXmppIceConnection::connectToHost() +{ + foreach (QXmppStunSocket *socket, m_components.values()) + socket->connectToHost(); +} + +/// Returns true if ICE negotiation completed, false otherwise. + +bool QXmppIceConnection::isConnected() const +{ + foreach (QXmppStunSocket *socket, m_components.values()) + if (!socket->isConnected()) + return false; + return true; +} + +/// Returns the list of local HOST CANDIDATES candidates by iterating +/// over the available network interfaces. + +QList<QXmppJingleCandidate> QXmppIceConnection::localCandidates() const +{ + QList<QXmppJingleCandidate> candidates; + foreach (QXmppStunSocket *socket, m_components.values()) + candidates += socket->localCandidates(); + return candidates; +} + +QString QXmppIceConnection::localUser() const +{ + return m_localUser; +} + +QString QXmppIceConnection::localPassword() const +{ + return m_localPassword; +} + +void QXmppIceConnection::setRemoteUser(const QString &user) +{ + foreach (QXmppStunSocket *socket, m_components.values()) + socket->setRemoteUser(user); +} + +void QXmppIceConnection::setRemotePassword(const QString &password) +{ + foreach (QXmppStunSocket *socket, m_components.values()) + socket->setRemotePassword(password); +} + +void QXmppIceConnection::setStunServer(const QString &hostName, quint16 port) +{ + // lookup STUN server + QHostAddress host; + QHostInfo hostInfo = QHostInfo::fromName(hostName); + foreach (const QHostAddress &address, hostInfo.addresses()) + { + if (address.protocol() == QAbstractSocket::IPv4Protocol) + { + host = address; + break; + } + } + if (host.isNull()) + { + emit logMessage(QXmppLogger::WarningMessage, + QString("Could not lookup STUN server %1").arg(hostName)); + return; + } + + // store STUN server + m_stunHost = host; + m_stunPort = port; + foreach (QXmppStunSocket *socket, m_components.values()) + socket->setStunServer(host, port); +} + +void QXmppIceConnection::slotConnected() +{ + foreach (QXmppStunSocket *socket, m_components.values()) + if (!socket->isConnected()) + return; + emit connected(); +} + +void QXmppIceConnection::slotDatagramReceived(const QByteArray &datagram) +{ + QXmppStunSocket *socket = qobject_cast<QXmppStunSocket*>(sender()); + int component = m_components.key(socket); + if (component) + emit datagramReceived(component, datagram); +} + +/// Sends a data packet to the remote party. +/// +/// \param component +/// \param datagram + +qint64 QXmppIceConnection::writeDatagram(int component, const QByteArray &datagram) +{ + QXmppStunSocket *socket = m_components.value(component); + if (!socket) + return -1; + return socket->writeDatagram(datagram); +} + + |
