aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-10-11 10:58:58 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-10-11 10:58:58 +0000
commit064235d640fc0efb3c77c4d6e4198f05ab1e4122 (patch)
tree8f2fdcb4ce336fcdab776ba0c792e249d8f1222a /src
parent582be1f6e654b7e2dad76a466a562f5a4dab3294 (diff)
downloadqxmpp-064235d640fc0efb3c77c4d6e4198f05ab1e4122.tar.gz
improve STUN / ICE-UDP support
Diffstat (limited to 'src')
-rw-r--r--src/QXmppJingleIq.cpp37
-rw-r--r--src/QXmppJingleIq.h14
-rw-r--r--src/QXmppStun.cpp278
-rw-r--r--src/QXmppStun.h5
4 files changed, 195 insertions, 139 deletions
diff --git a/src/QXmppJingleIq.cpp b/src/QXmppJingleIq.cpp
index ad182a66..da3501df 100644
--- a/src/QXmppJingleIq.cpp
+++ b/src/QXmppJingleIq.cpp
@@ -397,7 +397,8 @@ QXmppJingleCandidate::QXmppJingleCandidate()
m_generation(0),
m_network(0),
m_port(0),
- m_priority(0)
+ m_priority(0),
+ m_type(HostType)
{
}
@@ -538,7 +539,7 @@ void QXmppJingleCandidate::setProtocol(const QString &protocol)
/// Returns the candidate type (e.g. "host").
///
-QString QXmppJingleCandidate::type() const
+QXmppJingleCandidate::Type QXmppJingleCandidate::type() const
{
return m_type;
}
@@ -547,7 +548,7 @@ QString QXmppJingleCandidate::type() const
///
/// \param type
-void QXmppJingleCandidate::setType(const QString &type)
+void QXmppJingleCandidate::setType(QXmppJingleCandidate::Type type)
{
m_type = type;
}
@@ -571,7 +572,17 @@ void QXmppJingleCandidate::parse(const QDomElement &element)
m_port = element.attribute("port").toInt();
m_priority = element.attribute("priority").toInt();
m_protocol = element.attribute("protocol");
- m_type = element.attribute("type");
+ const QString type = element.attribute("type");
+ if (type == "host")
+ m_type = HostType;
+ else if (type == "prflx")
+ m_type = PeerReflexiveType;
+ else if (type == "srflx")
+ m_type = ServerReflexiveType;
+ else if (type == "relay")
+ m_type = RelayedType;
+ else
+ qWarning() << "Unknown candidate type" << type;
}
void QXmppJingleCandidate::toXml(QXmlStreamWriter *writer) const
@@ -586,7 +597,23 @@ void QXmppJingleCandidate::toXml(QXmlStreamWriter *writer) const
helperToXmlAddAttribute(writer, "port", QString::number(m_port));
helperToXmlAddAttribute(writer, "priority", QString::number(m_priority));
helperToXmlAddAttribute(writer, "protocol", m_protocol);
- helperToXmlAddAttribute(writer, "type", m_type);
+ QString type;
+ switch (m_type)
+ {
+ case HostType:
+ type = "host";
+ break;
+ case PeerReflexiveType:
+ type = "prflx";
+ break;
+ case ServerReflexiveType:
+ type = "srflx";
+ break;
+ case RelayedType:
+ type = "relay";
+ break;
+ }
+ helperToXmlAddAttribute(writer, "type", type);
writer->writeEndElement();
}
diff --git a/src/QXmppJingleIq.h b/src/QXmppJingleIq.h
index 06b4a3af..677256cf 100644
--- a/src/QXmppJingleIq.h
+++ b/src/QXmppJingleIq.h
@@ -78,6 +78,14 @@ private:
class QXmppJingleCandidate
{
public:
+ enum Type
+ {
+ HostType,
+ PeerReflexiveType,
+ ServerReflexiveType,
+ RelayedType,
+ };
+
QXmppJingleCandidate();
int component() const;
@@ -104,8 +112,8 @@ public:
QString protocol() const;
void setProtocol(const QString &protocol);
- QString type() const;
- void setType(const QString &type);
+ QXmppJingleCandidate::Type type() const;
+ void setType(QXmppJingleCandidate::Type);
bool isNull() const;
@@ -124,7 +132,7 @@ private:
quint16 m_port;
QString m_protocol;
int m_priority;
- QString m_type;
+ QXmppJingleCandidate::Type m_type;
};
/// \brief The QXmppJingleIq class represents an IQ used for initiating media
diff --git a/src/QXmppStun.cpp b/src/QXmppStun.cpp
index 870c7904..40a28bcd 100644
--- a/src/QXmppStun.cpp
+++ b/src/QXmppStun.cpp
@@ -39,33 +39,64 @@ 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 {
- BindingRequest = 0x0001,
- BindingIndication = 0x0011,
- BindingResponse = 0x0101,
- BindingError = 0x0111,
- SharedSecretRequest = 0x0002,
- SharedSecretResponse = 0x0102,
- SharedSecretError = 0x0112,
+ Request = 0x000,
+ Indication = 0x010,
+ Response = 0x100,
+ Error = 0x110,
};
enum AttributeType {
- MappedAddress = 0x0001,
- SourceAddress = 0x0004,
- ChangedAddress = 0x0005,
- Username = 0x0006,
- MessageIntegrity = 0x0008,
- ErrorCode = 0x0009,
- XorMappedAddress = 0x0020,
- Priority = 0x0024,
- UseCandidate = 0x0025,
- Software = 0x8022,
- Fingerprint = 0x8028,
- IceControlled = 0x8029,
- IceControlling = 0x802a,
- OtherAddress = 0x802c,
+ MappedAddress = 0x0001, // RFC5389
+ SourceAddress = 0x0004, // RFC5389
+ ChangedAddress = 0x0005, // RFC5389
+ Username = 0x0006, // RFC5389
+ MessageIntegrity = 0x0008, // RFC5389
+ ErrorCode = 0x0009, // RFC5389
+ ChannelNumber = 0x000c, // RFC5766
+ Lifetime = 0x000d, // RFC5766
+ XorPeerAddress = 0x0012, // RFC5766
+ XorMappedAddress = 0x0020, // RFC5389
+ Priority = 0x0024, // RFC5245
+ UseCandidate = 0x0025, // RFC5245
+ Software = 0x8022, // RFC5389
+ Fingerprint = 0x8028, // RFC5389
+ IceControlled = 0x8029, // RFC5245
+ IceControlling = 0x802a, // RFC5245
+ OtherAddress = 0x802c, // RFC5780
};
+// FIXME : we need to set local preference to discriminate between
+// multiple IP addresses
+static int candidatePriority(const QXmppJingleCandidate &candidate, int localPref = 65535)
+{
+ int typePref;
+ switch (candidate.type())
+ {
+ case QXmppJingleCandidate::HostType:
+ typePref = 126;
+ break;
+ case QXmppJingleCandidate::PeerReflexiveType:
+ typePref = 110;
+ break;
+ case QXmppJingleCandidate::ServerReflexiveType:
+ typePref = 100;
+ break;
+ default:
+ typePref = 0;
+ }
+
+ return (1 << 24) * typePref + \
+ (1 << 8) * localPref + \
+ (256 - candidate.component());
+}
+
static bool isIPv6LinkLocalAddress(const QHostAddress &addr)
{
if (addr.protocol() != QAbstractSocket::IPv6Protocol)
@@ -74,26 +105,42 @@ static bool isIPv6LinkLocalAddress(const QHostAddress &addr)
return (((ipv6addr[0] << 8) + ipv6addr[1]) & 0xffc0) == 0xfe80;
}
-static bool decodeAddress(QDataStream &stream, quint16 a_length, QHostAddress &address, quint16 &port)
+static bool decodeAddress(QDataStream &stream, quint16 a_length, QHostAddress &address, quint16 &port, const QByteArray &xorId = QByteArray())
{
if (a_length < 4)
return false;
quint8 reserved, protocol;
+ quint16 rawPort;
stream >> reserved;
stream >> protocol;
- stream >> port;
+ stream >> rawPort;
+ if (xorId.isEmpty())
+ port = rawPort;
+ else
+ port = rawPort ^ (STUN_MAGIC >> 16);
if (protocol == STUN_IPV4)
{
if (a_length != 8)
return false;
quint32 addr;
stream >> addr;
- address = QHostAddress(addr);
+ if (xorId.isEmpty())
+ address = QHostAddress(addr);
+ else
+ address = QHostAddress(addr ^ STUN_MAGIC);
} else if (protocol == STUN_IPV6) {
if (a_length != 20)
return false;
Q_IPV6ADDR addr;
stream.readRawData((char*)&addr, sizeof(addr));
+ if (!xorId.isEmpty())
+ {
+ QByteArray xpad;
+ QDataStream(&xpad, QIODevice::WriteOnly) << STUN_MAGIC;
+ xpad += xorId;
+ for (int i = 0; i < 16; i++)
+ addr[i] ^= xpad[i];
+ }
address = QHostAddress(addr);
} else {
return false;
@@ -101,7 +148,7 @@ static bool decodeAddress(QDataStream &stream, quint16 a_length, QHostAddress &a
return true;
}
-static void encodeAddress(QDataStream &stream, quint16 type, const QHostAddress &address, quint16 port)
+static void encodeAddress(QDataStream &stream, quint16 type, const QHostAddress &address, quint16 port, const QByteArray &xorId = QByteArray())
{
const quint8 reserved = 0;
if (address.protocol() == QAbstractSocket::IPv4Protocol)
@@ -110,21 +157,46 @@ static void encodeAddress(QDataStream &stream, quint16 type, const QHostAddress
stream << quint16(8);
stream << reserved;
stream << quint8(STUN_IPV4);
+ quint32 addr = address.toIPv4Address();
+ if (!xorId.isEmpty())
+ {
+ port ^= (STUN_MAGIC >> 16);
+ addr ^= STUN_MAGIC;
+ }
stream << port;
- stream << address.toIPv4Address();
+ stream << addr;
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
stream << type;
stream << quint16(20);
stream << reserved;
stream << quint8(STUN_IPV6);
- stream << port;
Q_IPV6ADDR addr = address.toIPv6Address();
+ if (!xorId.isEmpty())
+ {
+ port ^= (STUN_MAGIC >> 16);
+ QByteArray xpad;
+ QDataStream(&xpad, QIODevice::WriteOnly) << STUN_MAGIC;
+ xpad += xorId;
+ for (int i = 0; i < 16; i++)
+ addr[i] ^= xpad[i];
+ }
+ stream << port;
stream.writeRawData((char*)&addr, sizeof(addr));
} else {
qWarning("Cannot write STUN attribute for unknown IP version");
}
}
+static void addAddress(QDataStream &stream, quint16 type, const QHostAddress &host, quint16 port, const QByteArray &xorId = QByteArray())
+{
+ if (port && !host.isNull() &&
+ (host.protocol() == QAbstractSocket::IPv4Protocol ||
+ host.protocol() == QAbstractSocket::IPv6Protocol))
+ {
+ encodeAddress(stream, type, host, port, xorId);
+ }
+}
+
static void encodeString(QDataStream &stream, quint16 type, const QString &string)
{
const QByteArray utf8string = string.toUtf8();
@@ -138,6 +210,13 @@ static void encodeString(QDataStream &stream, quint16 type, const QString &strin
}
}
+static void setBodyLength(QByteArray &buffer, qint16 length)
+{
+ QDataStream stream(&buffer, QIODevice::WriteOnly);
+ stream.device()->seek(2);
+ stream << length;
+}
+
/// Constructs a new QXmppStunMessage.
QXmppStunMessage::QXmppStunMessage()
@@ -148,7 +227,8 @@ QXmppStunMessage::QXmppStunMessage()
otherPort(0),
sourcePort(0),
xorMappedPort(0),
- useCandidate(false)
+ useCandidate(false),
+ m_type(0)
{
m_id = QByteArray(ID_SIZE, 0);
}
@@ -301,35 +381,9 @@ bool QXmppStunMessage::decode(const QByteArray &buffer, const QString &password,
} 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 (!decodeAddress(stream, a_length, xorMappedHost, xorMappedPort, m_id))
{
- 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));
+ *errors << QLatin1String("Bad XOR-MAPPED-ADDRESS");
return false;
}
@@ -414,22 +468,13 @@ 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.
///
/// \param password
+/// \param addFingerprint
-QByteArray QXmppStunMessage::encode(const QString &password) const
+QByteArray QXmppStunMessage::encode(const QString &password, bool addFingerprint) const
{
QByteArray buffer;
QDataStream stream(&buffer, QIODevice::WriteOnly);
@@ -454,33 +499,7 @@ QByteArray QXmppStunMessage::encode(const QString &password) const
addAddress(stream, OtherAddress, otherHost, otherPort);
// XOR-MAPPED-ADDRESS
- if (xorMappedPort && !xorMappedHost.isNull() &&
- (xorMappedHost.protocol() == QAbstractSocket::IPv4Protocol ||
- xorMappedHost.protocol() == QAbstractSocket::IPv6Protocol))
- {
- const quint8 reserved = 0;
- stream << quint16(XorMappedAddress);
- if (xorMappedHost.protocol() == QAbstractSocket::IPv4Protocol)
- {
- stream << quint16(8);
- stream << reserved;
- stream << quint8(STUN_IPV4);
- stream << quint16(xorMappedPort ^ (STUN_MAGIC >> 16));
- stream << quint32(xorMappedHost.toIPv4Address() ^ STUN_MAGIC);
- } else {
- stream << quint16(20);
- stream << reserved;
- 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());
- }
- }
+ addAddress(stream, XorMappedAddress, xorMappedHost, xorMappedPort, m_id);
// ERROR-CODE
if (errorCode)
@@ -552,11 +571,14 @@ QByteArray QXmppStunMessage::encode(const QString &password) const
}
// FINGERPRINT
- setBodyLength(buffer, buffer.size() - STUN_HEADER + 8);
- quint32 fingerprint = generateCrc32(buffer) ^ 0x5354554eL;
- stream << quint16(Fingerprint);
- stream << quint16(sizeof(fingerprint));
- stream << fingerprint;
+ if (addFingerprint)
+ {
+ setBodyLength(buffer, buffer.size() - STUN_HEADER + 8);
+ quint32 fingerprint = generateCrc32(buffer) ^ 0x5354554eL;
+ stream << quint16(Fingerprint);
+ stream << quint16(sizeof(fingerprint));
+ stream << fingerprint;
+ }
return buffer;
}
@@ -588,29 +610,23 @@ quint16 QXmppStunMessage::peekType(const QByteArray &buffer, QByteArray &id)
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;
+ case Binding: typeName = "Binding"; break;
+ case SharedSecret: typeName = "Shared Secret"; break;
+ case Allocate: typeName = "Allocate"; 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;
+ case Request: typeName += " Request"; break;
+ case Indication: typeName += " Indication"; break;
+ case Response: typeName += " Response"; break;
+ case Error: typeName += " Error"; break;
default: break;
}
dumpLines << QString(" type %1 (%2)")
@@ -699,6 +715,7 @@ QXmppStunSocket::~QXmppStunSocket()
bool QXmppStunSocket::bind()
{
int preferredPort = 0;
+ int foundation = 0;
// store HOST candidates
m_localCandidates.clear();
@@ -739,15 +756,17 @@ bool QXmppStunSocket::bind()
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.setPriority(2130706432 - m_component);
candidate.setProtocol("udp");
- candidate.setType("host");
+ candidate.setType(QXmppJingleCandidate::HostType);
+
+ candidate.setPriority(candidatePriority(candidate));
m_localCandidates << candidate;
}
}
@@ -780,7 +799,7 @@ void QXmppStunSocket::checkCandidates()
// send a binding request
QXmppStunMessage message;
message.setId(pair->transaction);
- message.setType(BindingRequest);
+ message.setType(Binding | Request);
message.priority = pair->priority;
message.username = QString("%1:%2").arg(m_remoteUser, m_localUser);
if (m_iceControlling)
@@ -799,7 +818,7 @@ void QXmppStunSocket::checkCandidates()
foreach (QUdpSocket *socket, m_sockets)
{
QXmppStunMessage msg;
- msg.setType(BindingRequest);
+ msg.setType(Binding | Request);
msg.setId(m_stunId);
#ifdef QXMPP_DEBUG_STUN
debug(
@@ -866,7 +885,8 @@ void QXmppStunSocket::setLocalPassword(const QString &password)
bool QXmppStunSocket::addRemoteCandidate(const QXmppJingleCandidate &candidate)
{
if (candidate.component() != m_component ||
- (candidate.type() != "host" && candidate.type() != "srflx") ||
+ (candidate.type() != QXmppJingleCandidate::HostType &&
+ candidate.type() != QXmppJingleCandidate::ServerReflexiveType) ||
candidate.protocol() != "udp" ||
(candidate.host().protocol() != QAbstractSocket::IPv4Protocol &&
candidate.host().protocol() != QAbstractSocket::IPv6Protocol))
@@ -909,10 +929,15 @@ QXmppStunSocket::Pair *QXmppStunSocket::addRemoteCandidate(QUdpSocket *socket, c
return pair;
QXmppJingleCandidate candidate;
+ candidate.setComponent(m_component);
candidate.setHost(host);
+ candidate.setId(generateStanzaHash(10));
candidate.setPort(port);
candidate.setProtocol("udp");
- candidate.setComponent(m_component);
+ candidate.setType(QXmppJingleCandidate::PeerReflexiveType);
+
+ // FIXME : what priority?
+ // candidate.setPriority(candidatePriority(candidate));
Pair *pair = new Pair;
pair->remote = candidate;
@@ -1023,20 +1048,19 @@ void QXmppStunSocket::readyRead()
candidate.setHost(reflexiveHost);
candidate.setId(generateStanzaHash(10));
candidate.setPort(reflexivePort);
- candidate.setPriority(2130706432 - m_component);
candidate.setProtocol("udp");
- candidate.setType("srflx");
+ candidate.setType(QXmppJingleCandidate::ServerReflexiveType);
+
+ candidate.setPriority(candidatePriority(candidate));
m_localCandidates << candidate;
emit localCandidatesChanged();
return;
}
- else if (m_activePair)
- return;
// process message from peer
Pair *pair = 0;
- if (message.type() == BindingRequest)
+ if (message.type() == Binding | Request)
{
// add remote candidate
pair = addRemoteCandidate(socket, remoteHost, remotePort);
@@ -1044,7 +1068,7 @@ void QXmppStunSocket::readyRead()
// send a binding response
QXmppStunMessage response;
response.setId(message.id());
- response.setType(BindingResponse);
+ response.setType(Binding | Response);
response.username = message.username;
response.xorMappedHost = pair->remote.host();
response.xorMappedPort = pair->remote.port();
@@ -1057,19 +1081,19 @@ void QXmppStunSocket::readyRead()
pair->checked |= QIODevice::ReadOnly;
}
- if (!m_iceControlling)
+ if (!m_iceControlling && !m_activePair)
{
// send a triggered connectivity test
QXmppStunMessage message;
message.setId(pair->transaction);
- message.setType(BindingRequest);
+ message.setType(Binding | 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() == BindingResponse) {
+ } else if (message.type() == Binding | Response) {
// find the pair for this transaction
foreach (Pair *ptr, m_pairs)
diff --git a/src/QXmppStun.h b/src/QXmppStun.h
index 1b7c81be..2620730f 100644
--- a/src/QXmppStun.h
+++ b/src/QXmppStun.h
@@ -47,7 +47,7 @@ public:
quint16 type() const;
void setType(quint16 type);
- QByteArray encode(const QString &password = QString()) const;
+ QByteArray encode(const QString &password = QString(), bool addFingerprint = true) const;
bool decode(const QByteArray &buffer, const QString &password = QString(), QStringList *errors = 0);
QString toString() const;
static quint16 peekType(const QByteArray &buffer, QByteArray &id);
@@ -73,9 +73,6 @@ public:
bool useCandidate;
private:
- void addAddress(QDataStream &stream, quint16 type, const QHostAddress &host, quint16 port) const;
- void setBodyLength(QByteArray &buffer, qint16 length) const;
-
QByteArray m_id;
quint16 m_type;
};