aboutsummaryrefslogtreecommitdiff
path: root/src/QXmppStun.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-12-03 18:36:53 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-12-03 18:36:53 +0000
commitdab9a269c17e0c8ab26f8c295afecef45dc23915 (patch)
tree90c79d32e09edff6497aed5333596d4069083dff /src/QXmppStun.cpp
parente12c89d020dd090af7de37627b0eccd0ace33df2 (diff)
downloadqxmpp-dab9a269c17e0c8ab26f8c295afecef45dc23915.tar.gz
overhaul ICE support to reserve consecutive ports (required for RTP/RTCP pairs)
Diffstat (limited to 'src/QXmppStun.cpp')
-rw-r--r--src/QXmppStun.cpp310
1 files changed, 216 insertions, 94 deletions
diff --git a/src/QXmppStun.cpp b/src/QXmppStun.cpp
index 7585d376..3aecb2e6 100644
--- a/src/QXmppStun.cpp
+++ b/src/QXmppStun.cpp
@@ -39,21 +39,9 @@ static const quint16 STUN_HEADER = 20;
static const quint8 STUN_IPV4 = 0x01;
static const quint8 STUN_IPV6 = 0x02;
-enum MethodType {
- Binding = 0x1,
- SharedSecret = 0x2,
- Allocate = 0x3,
-};
-
-enum MessageType {
- Request = 0x000,
- Indication = 0x010,
- Response = 0x100,
- Error = 0x110,
-};
-
enum AttributeType {
MappedAddress = 0x0001, // RFC5389
+ ChangeRequest = 0x0003, // RFC5389
SourceAddress = 0x0004, // RFC5389
ChangedAddress = 0x0005, // RFC5389
Username = 0x0006, // RFC5389
@@ -228,11 +216,24 @@ QXmppStunMessage::QXmppStunMessage()
sourcePort(0),
xorMappedPort(0),
useCandidate(false),
- m_type(0)
+ m_cookie(STUN_MAGIC),
+ m_type(0),
+ m_changeRequest(0),
+ m_haveChangeRequest(false)
{
m_id = QByteArray(ID_SIZE, 0);
}
+quint32 QXmppStunMessage::cookie() const
+{
+ return m_cookie;
+}
+
+void QXmppStunMessage::setCookie(quint32 cookie)
+{
+ m_cookie = cookie;
+}
+
QByteArray QXmppStunMessage::id() const
{
return m_id;
@@ -254,6 +255,12 @@ void QXmppStunMessage::setType(quint16 type)
m_type = type;
}
+void QXmppStunMessage::setChangeRequest(quint32 changeRequest)
+{
+ m_changeRequest = changeRequest;
+ m_haveChangeRequest = true;
+}
+
/// Decodes a QXmppStunMessage and checks its integrity using the given
/// password.
///
@@ -275,13 +282,12 @@ bool QXmppStunMessage::decode(const QByteArray &buffer, const QString &password,
// parse STUN header
QDataStream stream(buffer);
quint16 length;
- quint32 cookie;
stream >> m_type;
stream >> length;
- stream >> cookie;
+ stream >> m_cookie;
stream.readRawData(m_id.data(), m_id.size());
- if (cookie != STUN_MAGIC || length != buffer.size() - STUN_HEADER)
+ if (length != buffer.size() - STUN_HEADER)
{
*errors << QLatin1String("Received an invalid STUN packet");
return false;
@@ -351,6 +357,14 @@ bool QXmppStunMessage::decode(const QByteArray &buffer, const QString &password,
return false;
}
+ } else if (a_type == ChangeRequest) {
+
+ // CHANGE-REQUEST
+ if (a_length != sizeof(m_changeRequest))
+ return false;
+ stream >> m_changeRequest;
+ m_haveChangeRequest = true;
+
} else if (a_type == SourceAddress) {
// SOURCE-ADDRESS
@@ -483,12 +497,19 @@ QByteArray QXmppStunMessage::encode(const QString &password, bool addFingerprint
quint16 length = 0;
stream << m_type;
stream << length;
- stream << STUN_MAGIC;
+ stream << m_cookie;
stream.writeRawData(m_id.data(), m_id.size());
// MAPPED-ADDRESS
addAddress(stream, MappedAddress, mappedHost, mappedPort);
+ // CHANGE-REQUEST
+ if (m_haveChangeRequest) {
+ stream << quint16(ChangeRequest);
+ stream << quint16(sizeof(m_changeRequest));
+ stream << m_changeRequest;
+ }
+
// SOURCE-ADDRESS
addAddress(stream, SourceAddress, sourceHost, sourcePort);
@@ -602,7 +623,7 @@ quint16 QXmppStunMessage::peekType(const QByteArray &buffer, QByteArray &id)
stream >> length;
stream >> cookie;
- if (cookie != STUN_MAGIC || length != buffer.size() - STUN_HEADER)
+ if (length != buffer.size() - STUN_HEADER)
return 0;
id.resize(ID_SIZE);
@@ -645,6 +666,9 @@ QString QXmppStunMessage::toString() const
if (mappedPort)
dumpLines << QString(" * MAPPED-ADDRESS %1 %2")
.arg(mappedHost.toString(), QString::number(mappedPort));
+ if (m_haveChangeRequest)
+ dumpLines << QString(" * CHANGE-REQUEST %1")
+ .arg(QString::number(m_changeRequest));
if (sourcePort)
dumpLines << QString(" * SOURCE-ADDRESS %1 %2")
.arg(sourceHost.toString(), QString::number(sourcePort));
@@ -694,6 +718,7 @@ QString QXmppStunSocket::Pair::toString() const
QXmppStunSocket::QXmppStunSocket(bool iceControlling, QObject *parent)
: QXmppLoggable(parent),
m_activePair(0),
+ m_fallbackPair(0),
m_iceControlling(iceControlling),
m_stunDone(false),
m_stunPort(0)
@@ -712,66 +737,6 @@ QXmppStunSocket::~QXmppStunSocket()
delete pair;
}
-bool QXmppStunSocket::bind()
-{
- int preferredPort = 0;
- int foundation = 0;
-
- // 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())
- {
- // FIXME: on Mac OS X, sending IPv6 UDP packets fails
-#ifdef Q_OS_MAC
- if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol ||
-#else
- if ((entry.ip().protocol() != QAbstractSocket::IPv4Protocol &&
- entry.ip().protocol() != QAbstractSocket::IPv6Protocol) ||
-#endif
- entry.netmask().isNull() ||
- entry.netmask() == QHostAddress::Broadcast)
- continue;
-
- QUdpSocket *socket = new QUdpSocket(this);
- QHostAddress ip = entry.ip();
- if (isIPv6LinkLocalAddress(ip))
- ip.setScopeId(interface.name());
-
- if (!socket->bind(ip, preferredPort) && !socket->bind(ip, 0))
- {
- warning(QString("QXmppStunSocket could not start listening on %1").arg(ip.toString()));
- delete socket;
- continue;
- }
- preferredPort = socket->localPort();
- connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
- m_sockets << socket;
-
- QXmppJingleCandidate candidate;
- candidate.setComponent(m_component);
- candidate.setFoundation(foundation++);
- candidate.setHost(entry.ip());
- candidate.setId(generateStanzaHash(10));
-#if QT_VERSION >= 0x040500
- candidate.setNetwork(interface.index());
-#endif
- candidate.setPort(socket->localPort());
- candidate.setProtocol("udp");
- candidate.setType(QXmppJingleCandidate::HostType);
-
- candidate.setPriority(candidatePriority(candidate));
- m_localCandidates << candidate;
- }
- }
- return true;
-}
-
/// Returns the component id for the current socket, e.g. 1 for RTP
/// and 2 for RTCP.
@@ -796,10 +761,13 @@ void QXmppStunSocket::checkCandidates()
debug("Checking remote candidates");
foreach (Pair *pair, m_pairs)
{
+ if (m_remoteUser.isEmpty())
+ continue;
+
// send a binding request
QXmppStunMessage message;
message.setId(pair->transaction);
- message.setType(Binding | Request);
+ message.setType(QXmppStunMessage::Binding | QXmppStunMessage::Request);
message.priority = pair->priority;
message.username = QString("%1:%2").arg(m_remoteUser, m_localUser);
if (m_iceControlling)
@@ -818,7 +786,7 @@ void QXmppStunSocket::checkCandidates()
foreach (QUdpSocket *socket, m_sockets)
{
QXmppStunMessage msg;
- msg.setType(Binding | Request);
+ msg.setType(QXmppStunMessage::Binding | QXmppStunMessage::Request);
msg.setId(m_stunId);
#ifdef QXMPP_DEBUG_STUN
logSent(QString("STUN packet to %1 %2\n%3").arg(m_stunHost.toString(),
@@ -951,6 +919,36 @@ void QXmppStunSocket::setRemotePassword(const QString &password)
m_remotePassword = password;
}
+void QXmppStunSocket::setSockets(QList<QUdpSocket*> sockets)
+{
+ // clear previous candidates and sockets
+ m_localCandidates.clear();
+ foreach (QUdpSocket *socket, m_sockets)
+ delete socket;
+ m_sockets.clear();
+
+ // store candidates
+ int foundation = 0;
+ foreach (QUdpSocket *socket, sockets)
+ {
+ socket->setParent(this);
+ connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
+
+ QXmppJingleCandidate candidate;
+ candidate.setComponent(m_component);
+ candidate.setFoundation(foundation++);
+ candidate.setHost(socket->localAddress());
+ candidate.setId(generateStanzaHash(10));
+ candidate.setPort(socket->localPort());
+ candidate.setProtocol("udp");
+ candidate.setType(QXmppJingleCandidate::HostType);
+ candidate.setPriority(candidatePriority(candidate));
+
+ m_sockets << socket;
+ m_localCandidates << candidate;
+ }
+}
+
void QXmppStunSocket::setStunServer(const QHostAddress &host, quint16 port)
{
m_stunHost = host;
@@ -975,6 +973,16 @@ void QXmppStunSocket::readyRead()
quint16 messageType = QXmppStunMessage::peekType(buffer, messageId);
if (!messageType)
{
+ // use this as an opportunity to flag a potential pair
+ if (!m_fallbackPair) {
+ foreach (Pair *pair, m_pairs) {
+ if (pair->remote.host() == remoteHost &&
+ pair->remote.port() == remotePort) {
+ m_fallbackPair = pair;
+ break;
+ }
+ }
+ }
emit datagramReceived(buffer);
return;
}
@@ -1051,7 +1059,7 @@ void QXmppStunSocket::readyRead()
// process message from peer
Pair *pair = 0;
- if (message.type() == (Binding | Request))
+ if (message.type() == (QXmppStunMessage::Binding | QXmppStunMessage::Request))
{
// add remote candidate
pair = addRemoteCandidate(socket, remoteHost, remotePort);
@@ -1059,7 +1067,7 @@ void QXmppStunSocket::readyRead()
// send a binding response
QXmppStunMessage response;
response.setId(message.id());
- response.setType(Binding | Response);
+ response.setType(QXmppStunMessage::Binding | QXmppStunMessage::Response);
response.username = message.username;
response.xorMappedHost = pair->remote.host();
response.xorMappedPort = pair->remote.port();
@@ -1072,19 +1080,19 @@ void QXmppStunSocket::readyRead()
pair->checked |= QIODevice::ReadOnly;
}
- if (!m_iceControlling && !m_activePair)
+ if (!m_iceControlling && !m_activePair && !m_remoteUser.isEmpty())
{
// send a triggered connectivity test
QXmppStunMessage message;
message.setId(pair->transaction);
- message.setType(Binding | Request);
+ message.setType(QXmppStunMessage::Binding | QXmppStunMessage::Request);
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() == (Binding | Response)) {
+ } else if (message.type() == (QXmppStunMessage::Binding | QXmppStunMessage::Response)) {
// find the pair for this transaction
foreach (Pair *ptr, m_pairs)
@@ -1129,15 +1137,114 @@ void QXmppStunSocket::readyRead()
}
}
+static QList<QUdpSocket*> reservePort(const QList<QHostAddress> &addresses, quint16 port, QObject *parent)
+{
+ QList<QUdpSocket*> sockets;
+ foreach (const QHostAddress &address, addresses) {
+ QUdpSocket *socket = new QUdpSocket(parent);
+ sockets << socket;
+ if (!socket->bind(address, port)) {
+ for (int i = 0; i < sockets.size(); ++i)
+ delete sockets[i];
+ sockets.clear();
+ break;
+ }
+ }
+ return sockets;
+}
+
+/// Returns the list of local network addresses.
+
+QList<QHostAddress> QXmppStunSocket::discoverAddresses()
+{
+ QList<QHostAddress> addresses;
+ 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.ip().protocol() != QAbstractSocket::IPv6Protocol) ||
+ entry.netmask().isNull() ||
+ entry.netmask() == QHostAddress::Broadcast)
+ continue;
+
+#ifdef Q_OS_MAC
+ // FIXME: on Mac OS X, sending IPv6 UDP packets fails
+ if (entry.ip().protocol() == QAbstractSocket::IPv6Protocol)
+ continue;
+#endif
+
+ QHostAddress ip = entry.ip();
+ if (isIPv6LinkLocalAddress(ip))
+ ip.setScopeId(interface.name());
+ addresses << ip;
+ }
+ }
+ return addresses;
+}
+
+/// Tries to bind \a count UDP sockets on eich of the given \a addresses.
+///
+/// The port numbers are chosen so that they are consecutive, starting at
+/// an even port. This makes them suitable for RTP/RTCP sockets pairs.
+///
+/// \param addresses The network address on which to bind the sockets.
+/// \param count The number of ports to reserve.
+
+QList<QUdpSocket*> QXmppStunSocket::reservePorts(const QList<QHostAddress> &addresses, int count, QObject *parent)
+{
+ QList<QUdpSocket*> sockets;
+ if (addresses.isEmpty() || !count)
+ return sockets;
+
+ const int expectedSize = addresses.size() * count;
+ quint16 port = 40000;
+ while (sockets.size() != expectedSize) {
+ // reserve first port (even number)
+ if (port % 2)
+ port++;
+ QList<QUdpSocket*> socketChunk;
+ while (socketChunk.isEmpty() && port <= 65536 - count) {
+ socketChunk = reservePort(addresses, port, parent);
+ if (socketChunk.isEmpty())
+ port += 2;
+ }
+ if (socketChunk.isEmpty())
+ return sockets;
+
+ // reserve other ports
+ sockets << socketChunk;
+ for (int i = 1; i < count; ++i) {
+ socketChunk = reservePort(addresses, ++port, parent);
+ if (socketChunk.isEmpty())
+ break;
+ sockets << socketChunk;
+ }
+
+ // cleanup if we failed
+ if (sockets.size() != expectedSize) {
+ for (int i = 0; i < sockets.size(); ++i)
+ delete sockets[i];
+ sockets.clear();
+ }
+ }
+ return sockets;
+}
+
/// Sends a data packet to the remote party.
///
/// \param datagram
qint64 QXmppStunSocket::writeDatagram(const QByteArray &datagram)
{
- if (!m_activePair)
+ Pair *pair = m_activePair ? m_activePair : m_fallbackPair;
+ if (!pair)
return -1;
- return m_activePair->socket->writeDatagram(datagram, m_activePair->remote.host(), m_activePair->remote.port());
+ return pair->socket->writeDatagram(datagram, pair->remote.host(), pair->remote.port());
}
/// Sends a STUN packet to the remote party.
@@ -1198,12 +1305,6 @@ void QXmppIceConnection::addComponent(int component)
this, SLOT(slotDatagramReceived(QByteArray)));
Q_ASSERT(check);
- if (!socket->bind())
- {
- socket->deleteLater();
- return;
- }
-
m_components[component] = socket;
}
@@ -1219,6 +1320,27 @@ void QXmppIceConnection::addRemoteCandidate(const QXmppJingleCandidate &candidat
socket->addRemoteCandidate(candidate);
}
+/// Binds the local sockets.
+
+bool QXmppIceConnection::bind(const QList<QHostAddress> &addresses)
+{
+ // reserve ports
+ QList<QUdpSocket*> sockets = QXmppStunSocket::reservePorts(addresses, m_components.size());
+ if (sockets.isEmpty())
+ return false;
+
+ // assign sockets
+ QList<int> keys = m_components.keys();
+ qSort(keys);
+ int s = 0;
+ foreach (int k, keys) {
+ m_components[k]->setSockets(sockets.mid(s, addresses.size()));
+ s += addresses.size();
+ }
+
+ return true;
+}
+
/// Closes the ICE connection.
void QXmppIceConnection::close()