Merge pull request #8624 from sethalves/ice-troubles

ICE server related utilities and changes
This commit is contained in:
Chris Collins 2016-09-22 11:26:23 -07:00 committed by GitHub
commit 216c6b8f6f
9 changed files with 679 additions and 99 deletions

View file

@ -23,6 +23,7 @@
#include <QStandardPaths>
#include <QTimer>
#include <QUrlQuery>
#include <QCommandLineParser>
#include <AccountManager.h>
#include <BuildInfo.h>
@ -66,8 +67,11 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_webAuthenticationStateSet(),
_cookieSessionHash(),
_automaticNetworkingSetting(),
_settingsManager()
_settingsManager(),
_iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME),
_iceServerPort(ICE_SERVER_DEFAULT_PORT)
{
parseCommandLine();
qInstallMessageHandler(LogHandler::verboseMessageHandler);
LogUtils::init();
@ -159,6 +163,46 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qDebug() << "domain-server is running";
}
void DomainServer::parseCommandLine() {
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Domain Server");
parser.addHelpOption();
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
parser.addOption(iceServerAddressOption);
const QCommandLineOption domainIDOption("d", "domain-server uuid");
parser.addOption(domainIDOption);
if (!parser.parse(QCoreApplication::arguments())) {
qWarning() << parser.errorText() << endl;
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(iceServerAddressOption)) {
// parse the IP and port combination for this target
QString hostnamePortString = parser.value(iceServerAddressOption);
_iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':'));
_iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt();
if (_iceServerPort == 0) {
_iceServerPort = ICE_SERVER_DEFAULT_PORT;
}
if (_iceServerAddr.isEmpty()) {
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
}
}
if (parser.isSet(domainIDOption)) {
_overridingDomainID = QUuid(parser.value(domainIDOption));
_overrideDomainID = true;
qDebug() << "domain-server ID is" << _overridingDomainID;
}
}
DomainServer::~DomainServer() {
// destroy the LimitedNodeList before the DomainServer QCoreApplication is down
DependencyManager::destroy<LimitedNodeList>();
@ -166,7 +210,7 @@ DomainServer::~DomainServer() {
void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
if (!quitMessage.isEmpty()) {
qCritical() << qPrintable(quitMessage);
qWarning() << qPrintable(quitMessage);
}
QCoreApplication::exit(exitCode);
@ -307,7 +351,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
if (!domainObject.isEmpty()) {
auto id = domainObject[ID_KEY].toString();
auto id = _overrideDomainID ? _overridingDomainID.toString() : domainObject[ID_KEY].toString();
auto name = domainObject[NAME_KEY].toString();
auto key = domainObject[KEY_KEY].toString();
@ -415,24 +459,30 @@ void DomainServer::setupNodeListAndAssignments() {
quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT;
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort);
// set our LimitedNodeList UUID to match the UUID from our config
// nodes will currently use this to add resources to data-web that relate to our domain
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
if (idValueVariant) {
nodeList->setSessionUUID(idValueVariant->toString());
bool isMetaverseDomain = false;
if (_overrideDomainID) {
nodeList->setSessionUUID(_overridingDomainID);
isMetaverseDomain = true; // assume metaverse domain
} else {
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
if (idValueVariant) {
nodeList->setSessionUUID(idValueVariant->toString());
isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain
} else {
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
}
}
// if we have an ID, we'll assume we're a metaverse domain
// now see if we think we're a temp domain (we have an API key) or a full domain
if (isMetaverseDomain) {
// see if we think we're a temp domain (we have an API key) or a full domain
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
if (temporaryDomainKey.isEmpty()) {
_type = MetaverseDomain;
} else {
_type = MetaverseTemporaryDomain;
}
} else {
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
}
connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
@ -548,7 +598,6 @@ void DomainServer::setupAutomaticNetworking() {
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
<< "Please add an ID to your config file or via the web interface.";
return;
}
}
@ -606,12 +655,11 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
void DomainServer::updateICEServerAddresses() {
if (_iceAddressLookupID == -1) {
_iceAddressLookupID = QHostInfo::lookupHost(ICE_SERVER_DEFAULT_HOSTNAME, this, SLOT(handleICEHostInfo(QHostInfo)));
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
}
}
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
// check for configs from the command line, these take precedence
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);

View file

@ -105,6 +105,7 @@ signals:
private:
const QUuid& getID();
void parseCommandLine();
void setupNodeListAndAssignments();
bool optionallySetupOAuth();
@ -205,6 +206,11 @@ private:
friend class DomainGatekeeper;
friend class DomainMetadata;
QString _iceServerAddr;
int _iceServerPort;
bool _overrideDomainID { false }; // should we override the domain-id from settings?
QUuid _overridingDomainID { QUuid() }; // what should we override it with?
};

View file

@ -745,8 +745,32 @@ void LimitedNodeList::removeSilentNodes() {
const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442;
const int NUM_BYTES_STUN_HEADER = 20;
void LimitedNodeList::sendSTUNRequest() {
void LimitedNodeList::makeSTUNRequestPacket(char* stunRequestPacket) {
int packetIndex = 0;
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
// leading zeros + message type
const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001);
memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE));
packetIndex += sizeof(REQUEST_MESSAGE_TYPE);
// message length (no additional attributes are included)
uint16_t messageLength = 0;
memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength));
packetIndex += sizeof(messageLength);
memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER));
packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
// transaction ID (random 12-byte unsigned integer)
const uint NUM_TRANSACTION_ID_BYTES = 12;
QUuid randomUUID = QUuid::createUuid();
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
}
void LimitedNodeList::sendSTUNRequest() {
if (!_stunSockAddr.getAddress().isNull()) {
const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10;
@ -762,36 +786,14 @@ void LimitedNodeList::sendSTUNRequest() {
}
char stunRequestPacket[NUM_BYTES_STUN_HEADER];
int packetIndex = 0;
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
// leading zeros + message type
const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001);
memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE));
packetIndex += sizeof(REQUEST_MESSAGE_TYPE);
// message length (no additional attributes are included)
uint16_t messageLength = 0;
memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength));
packetIndex += sizeof(messageLength);
memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER));
packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
// transaction ID (random 12-byte unsigned integer)
const uint NUM_TRANSACTION_ID_BYTES = 12;
QUuid randomUUID = QUuid::createUuid();
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
makeSTUNRequestPacket(stunRequestPacket);
flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest);
_nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
}
}
void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
bool LimitedNodeList::parseSTUNResponse(udt::BasePacket* packet,
QHostAddress& newPublicAddress, uint16_t& newPublicPort) {
// check the cookie to make sure this is actually a STUN response
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS
const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4;
@ -803,71 +805,79 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packe
if (memcmp(packet->getData() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH,
&RFC_5389_MAGIC_COOKIE_NETWORK_ORDER,
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) {
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) != 0) {
return false;
}
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
while (attributeStartIndex < packet->getDataSize()) {
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
while (attributeStartIndex < packet->getDataSize()) {
if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
const int NUM_BYTES_FAMILY_ALIGN = 1;
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
const int NUM_BYTES_FAMILY_ALIGN = 1;
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
uint8_t addressFamily = 0;
memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily));
uint8_t addressFamily = 0;
memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily));
byteIndex += sizeof(addressFamily);
byteIndex += sizeof(addressFamily);
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
// grab the X-Port
uint16_t xorMappedPort = 0;
memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort));
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
// grab the X-Port
uint16_t xorMappedPort = 0;
memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort));
newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
byteIndex += sizeof(xorMappedPort);
byteIndex += sizeof(xorMappedPort);
// grab the X-Address
uint32_t xorMappedAddress = 0;
memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress));
// grab the X-Address
uint32_t xorMappedAddress = 0;
memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress));
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
QHostAddress newPublicAddress(stunAddress);
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
qCDebug(networking, "New public socket received from STUN server is %s:%hu",
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
_publicSockAddr.getPort());
if (!_hasCompletedInitialSTUN) {
// if we're here we have definitely completed our initial STUN sequence
stopInitialSTUNUpdate(true);
}
emit publicSockAddrChanged(_publicSockAddr);
flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN);
}
// we're done reading the packet so we can return now
return;
}
} else {
// push forward attributeStartIndex by the length of this attribute
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
uint16_t attributeLength = 0;
memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
sizeof(attributeLength));
attributeLength = ntohs(attributeLength);
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
// QHostAddress newPublicAddress(stunAddress);
newPublicAddress = QHostAddress(stunAddress);
return true;
}
} else {
// push forward attributeStartIndex by the length of this attribute
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
uint16_t attributeLength = 0;
memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
sizeof(attributeLength));
attributeLength = ntohs(attributeLength);
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
}
}
return false;
}
void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
uint16_t newPublicPort;
QHostAddress newPublicAddress;
if (parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) {
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
qCDebug(networking, "New public socket received from STUN server is %s:%hu",
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
_publicSockAddr.getPort());
if (!_hasCompletedInitialSTUN) {
// if we're here we have definitely completed our initial STUN sequence
stopInitialSTUNUpdate(true);
}
emit publicSockAddrChanged(_publicSockAddr);
flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN);
}
}
}

View file

@ -146,6 +146,7 @@ public:
const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS,
const QUuid& connectionSecret = QUuid());
static bool parseSTUNResponse(udt::BasePacket* packet, QHostAddress& newPublicAddress, uint16_t& newPublicPort);
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
@ -166,8 +167,8 @@ public:
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
std::unique_ptr<NLPacket> constructPingReplyPacket(ReceivedMessage& message);
std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
static std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
static std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID);
@ -232,6 +233,9 @@ public:
bool packetVersionMatch(const udt::Packet& packet);
bool isPacketVerified(const udt::Packet& packet);
static void makeSTUNRequestPacket(char* stunRequestPacket);
public slots:
void reset();
void eraseAllNodes();
@ -275,7 +279,7 @@ protected:
LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode,
const HifiSockAddr& overridenSockAddr);
qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr,
@ -284,7 +288,7 @@ protected:
void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid());
void setLocalSocket(const HifiSockAddr& sockAddr);
bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet);
void processSTUNResponse(std::unique_ptr<udt::BasePacket> packet);

View file

@ -7,3 +7,6 @@ set_target_properties(udt-test PROPERTIES FOLDER "Tools")
add_subdirectory(vhacd-util)
set_target_properties(vhacd-util PROPERTIES FOLDER "Tools")
add_subdirectory(ice-client)
set_target_properties(ice-client PROPERTIES FOLDER "Tools")

View file

@ -0,0 +1,3 @@
set(TARGET_NAME ice-client)
setup_hifi_project(Core Widgets)
link_hifi_libraries(shared networking)

View file

@ -0,0 +1,387 @@
//
// ICEClientApp.cpp
// tools/ice-client/src
//
// Created by Seth Alves on 3/5/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QDataStream>
#include <QLoggingCategory>
#include <QCommandLineParser>
#include <PathUtils.h>
#include <LimitedNodeList.h>
#include <NetworkLogging.h>
#include "ICEClientApp.h"
ICEClientApp::ICEClientApp(int argc, char* argv[]) :
QCoreApplication(argc, argv)
{
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity ICE client");
parser.addHelpOption();
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption verboseOutput("v", "verbose output");
parser.addOption(verboseOutput);
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
parser.addOption(iceServerAddressOption);
const QCommandLineOption howManyTimesOption("n", "how many times to cycle", "1");
parser.addOption(howManyTimesOption);
const QCommandLineOption domainIDOption("d", "domain-server uuid", "00000000-0000-0000-0000-000000000000");
parser.addOption(domainIDOption);
const QCommandLineOption cacheSTUNOption("s", "cache stun-server response");
parser.addOption(cacheSTUNOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
parser.showHelp();
Q_UNREACHABLE();
}
_verbose = parser.isSet(verboseOutput);
if (!_verbose) {
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
}
_stunSockAddr = HifiSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT, true);
_cacheSTUNResult = parser.isSet(cacheSTUNOption);
if (parser.isSet(howManyTimesOption)) {
_actionMax = parser.value(howManyTimesOption).toInt();
} else {
_actionMax = 1;
}
if (parser.isSet(domainIDOption)) {
_domainID = QUuid(parser.value(domainIDOption));
if (_verbose) {
qDebug() << "domain-server ID is" << _domainID;
}
}
_iceServerAddr = HifiSockAddr("127.0.0.1", ICE_SERVER_DEFAULT_PORT);
if (parser.isSet(iceServerAddressOption)) {
// parse the IP and port combination for this target
QString hostnamePortString = parser.value(iceServerAddressOption);
QHostAddress address { hostnamePortString.left(hostnamePortString.indexOf(':')) };
quint16 port { (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt() };
if (port == 0) {
port = ICE_SERVER_DEFAULT_PORT;
}
if (address.isNull()) {
qCritical() << "Could not parse an IP address and port combination from" << hostnamePortString << "-" <<
"The parsed IP was" << address.toString() << "and the parsed port was" << port;
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
} else {
_iceServerAddr = HifiSockAddr(address, port);
}
}
if (_verbose) {
qDebug() << "ICE-server address is" << _iceServerAddr;
}
setState(lookUpStunServer);
QTimer* doTimer = new QTimer(this);
connect(doTimer, &QTimer::timeout, this, &ICEClientApp::doSomething);
doTimer->start(200);
}
ICEClientApp::~ICEClientApp() {
delete _socket;
}
void ICEClientApp::setState(int newState) {
_state = newState;
}
void ICEClientApp::closeSocket() {
_domainServerPeerSet = false;
delete _socket;
_socket = nullptr;
}
void ICEClientApp::openSocket() {
if (_socket) {
return;
}
_socket = new udt::Socket();
unsigned int localPort = 0;
_socket->bind(QHostAddress::AnyIPv4, localPort);
_socket->setPacketHandler([this](std::unique_ptr<udt::Packet> packet) { processPacket(std::move(packet)); });
_socket->addUnfilteredHandler(_stunSockAddr,
[this](std::unique_ptr<udt::BasePacket> packet) {
processSTUNResponse(std::move(packet));
});
if (_verbose) {
qDebug() << "local port is" << _socket->localPort();
}
_localSockAddr = HifiSockAddr("127.0.0.1", _socket->localPort());
_publicSockAddr = HifiSockAddr("127.0.0.1", _socket->localPort());
_domainPingCount = 0;
}
void ICEClientApp::doSomething() {
if (_actionMax > 0 && _actionCount >= _actionMax) {
// time to stop.
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
} else if (_state == lookUpStunServer) {
// lookup STUN server address
if (!_stunSockAddr.getAddress().isNull()) {
if (_verbose) {
qDebug() << "stun server is" << _stunSockAddr;
}
setState(sendStunRequestPacket);
} else {
if (_verbose) {
qDebug() << "_stunSockAddr is" << _stunSockAddr.getAddress();
}
QCoreApplication::exit(stunFailureExitStatus);
}
} else if (_state == sendStunRequestPacket) {
// send STUN request packet
closeSocket();
openSocket();
if (!_cacheSTUNResult || !_stunResultSet) {
const int NUM_BYTES_STUN_HEADER = 20;
char stunRequestPacket[NUM_BYTES_STUN_HEADER];
LimitedNodeList::makeSTUNRequestPacket(stunRequestPacket);
if (_verbose) {
qDebug() << "sending STUN request";
}
_socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
_stunResponseTimerCanceled = false;
_stunResponseTimer.singleShot(stunResponseTimeoutMilliSeconds, this, [&] {
if (_stunResponseTimerCanceled) {
return;
}
if (_verbose) {
qDebug() << "timeout waiting for stun-server response";
}
QCoreApplication::exit(stunFailureExitStatus);
});
setState(waitForStunResponse);
} else {
if (_verbose) {
qDebug() << "using cached STUN response";
}
_publicSockAddr.setPort(_socket->localPort());
setState(talkToIceServer);
}
} else if (_state == talkToIceServer) {
QUuid peerID;
if (_domainID == QUuid()) {
// pick a random domain-id which will fail
peerID = QUuid::createUuid();
setState(pause0);
} else {
// use the domain UUID given on the command-line
peerID = _domainID;
setState(waitForIceReply);
}
_sessionUUID = QUuid::createUuid();
if (_verbose) {
qDebug() << "I am" << _sessionUUID;
}
sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID);
_iceResponseTimerCanceled = false;
_iceResponseTimer.singleShot(iceResponseTimeoutMilliSeconds, this, [=] {
if (_iceResponseTimerCanceled) {
return;
}
if (_verbose) {
qDebug() << "timeout waiting for ice-server response";
}
QCoreApplication::exit(iceFailureExitStatus);
});
} else if (_state == pause0) {
setState(pause1);
} else if (_state == pause1) {
if (_verbose) {
qDebug() << "";
}
closeSocket();
setState(sendStunRequestPacket);
_actionCount++;
}
}
void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
const QUuid& clientID, const QUuid& peerID) {
std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType);
QDataStream iceDataStream(icePacket.get());
iceDataStream << clientID << _publicSockAddr << _localSockAddr;
if (packetType == PacketType::ICEServerQuery) {
assert(!peerID.isNull());
iceDataStream << peerID;
if (_verbose) {
qDebug() << "Sending packet to ICE server to request connection info for peer with ID"
<< uuidStringWithoutCurlyBraces(peerID);
}
}
// fillPacketHeader(packet, connectionSecret);
_socket->writePacket(*icePacket, _iceServerAddr);
}
void ICEClientApp::checkDomainPingCount() {
_domainPingCount++;
if (_domainPingCount > 5) {
if (_verbose) {
qDebug() << "too many unanswered pings to domain-server.";
}
QCoreApplication::exit(domainPingExitStatus);
}
}
void ICEClientApp::icePingDomainServer() {
if (!_domainServerPeerSet) {
return;
}
if (_verbose) {
qDebug() << "ice-pinging domain-server: " << _domainServerPeer;
}
auto localPingPacket = LimitedNodeList::constructICEPingPacket(PingType::Local, _sessionUUID);
_socket->writePacket(*localPingPacket, _domainServerPeer.getLocalSocket());
auto publicPingPacket = LimitedNodeList::constructICEPingPacket(PingType::Public, _sessionUUID);
_socket->writePacket(*publicPingPacket, _domainServerPeer.getPublicSocket());
checkDomainPingCount();
}
void ICEClientApp::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
if (_verbose) {
qDebug() << "got stun response";
}
if (_state != waitForStunResponse) {
if (_verbose) {
qDebug() << "got unexpected stun response";
}
QCoreApplication::exit(stunFailureExitStatus);
}
_stunResponseTimer.stop();
_stunResponseTimerCanceled = true;
uint16_t newPublicPort;
QHostAddress newPublicAddress;
if (LimitedNodeList::parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) {
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
if (_verbose) {
qDebug() << "My public address is" << _publicSockAddr;
}
_stunResultSet = true;
setState(talkToIceServer);
} else {
QCoreApplication::exit(stunFailureExitStatus);
}
}
void ICEClientApp::processPacket(std::unique_ptr<udt::Packet> packet) {
std::unique_ptr<NLPacket> nlPacket = NLPacket::fromBase(std::move(packet));
if (nlPacket->getPayloadSize() < NLPacket::localHeaderSize(PacketType::ICEServerHeartbeat)) {
if (_verbose) {
qDebug() << "got a short packet.";
}
return;
}
QSharedPointer<ReceivedMessage> message = QSharedPointer<ReceivedMessage>::create(*nlPacket);
const HifiSockAddr& senderAddr = message->getSenderSockAddr();
if (nlPacket->getType() == PacketType::ICEServerPeerInformation) {
// cancel the timeout timer
_iceResponseTimer.stop();
_iceResponseTimerCanceled = true;
QDataStream iceResponseStream(message->getMessage());
if (!_domainServerPeerSet) {
iceResponseStream >> _domainServerPeer;
if (_verbose) {
qDebug() << "got ICEServerPeerInformation from" << _domainServerPeer;
}
_domainServerPeerSet = true;
icePingDomainServer();
_pingDomainTimer = new QTimer(this);
connect(_pingDomainTimer, &QTimer::timeout, this, &ICEClientApp::icePingDomainServer);
_pingDomainTimer->start(500);
} else {
NetworkPeer domainServerPeer;
iceResponseStream >> domainServerPeer;
if (_verbose) {
qDebug() << "got repeat ICEServerPeerInformation from" << domainServerPeer;
}
}
} else if (nlPacket->getType() == PacketType::ICEPing) {
if (_verbose) {
qDebug() << "got packet: " << nlPacket->getType();
}
auto replyPacket = LimitedNodeList::constructICEPingReplyPacket(*message, _sessionUUID);
_socket->writePacket(*replyPacket, senderAddr);
checkDomainPingCount();
} else if (nlPacket->getType() == PacketType::ICEPingReply) {
if (_verbose) {
qDebug() << "got packet: " << nlPacket->getType();
}
if (_domainServerPeerSet && _state == waitForIceReply &&
(senderAddr == _domainServerPeer.getLocalSocket() ||
senderAddr == _domainServerPeer.getPublicSocket())) {
delete _pingDomainTimer;
_pingDomainTimer = nullptr;
setState(pause0);
} else {
if (_verbose) {
qDebug() << "got unexpected ICEPingReply" << senderAddr;
}
}
} else {
if (_verbose) {
qDebug() << "got unexpected packet: " << nlPacket->getType();
}
}
}

View file

@ -0,0 +1,96 @@
//
// ICEClientApp.h
// tools/ice-client/src
//
// Created by Seth Alves on 2016-9-16
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ICEClientApp_h
#define hifi_ICEClientApp_h
#include <QApplication>
#include <udt/Constants.h>
#include <udt/Socket.h>
#include <ReceivedMessage.h>
#include <NetworkPeer.h>
class ICEClientApp : public QCoreApplication {
Q_OBJECT
public:
ICEClientApp(int argc, char* argv[]);
~ICEClientApp();
const int stunFailureExitStatus { 1 };
const int iceFailureExitStatus { 2 };
const int domainPingExitStatus { 3 };
const int stunResponseTimeoutMilliSeconds { 2000 };
const int iceResponseTimeoutMilliSeconds { 2000 };
private:
enum State {
lookUpStunServer, // 0
sendStunRequestPacket, // 1
waitForStunResponse, // 2
talkToIceServer, // 3
waitForIceReply, // 4
pause0, // 5
pause1 // 6
};
void closeSocket();
void openSocket();
void setState(int newState);
void doSomething();
void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
const QUuid& clientID, const QUuid& peerID);
void icePingDomainServer();
void processSTUNResponse(std::unique_ptr<udt::BasePacket> packet);
void processPacket(std::unique_ptr<udt::Packet> packet);
void checkDomainPingCount();
bool _verbose;
bool _cacheSTUNResult; // should we only talk to stun server once?
bool _stunResultSet { false }; // have we already talked to stun server?
HifiSockAddr _stunSockAddr;
unsigned int _actionCount { 0 };
unsigned int _actionMax { 0 };
QUuid _sessionUUID;
QUuid _domainID;
QTimer* _pingDomainTimer { nullptr };
HifiSockAddr _iceServerAddr;
HifiSockAddr _localSockAddr;
HifiSockAddr _publicSockAddr;
udt::Socket* _socket { nullptr };
bool _domainServerPeerSet { false };
NetworkPeer _domainServerPeer;
int _state { 0 };
QTimer _stunResponseTimer;
bool _stunResponseTimerCanceled { false };
QTimer _iceResponseTimer;
bool _iceResponseTimerCanceled { false };
int _domainPingCount { 0 };
};
#endif //hifi_ICEClientApp_h

View file

@ -0,0 +1,23 @@
//
// main.cpp
// tools/ice-client/src
//
// Created by Seth Alves on 2016-9-16
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include "ICEClientApp.h"
using namespace std;
int main(int argc, char * argv[]) {
ICEClientApp app(argc, argv);
return app.exec();
}