continued STUN requests for keepalive, propagate socket changes

This commit is contained in:
Stephen Birarda 2013-10-18 15:05:16 -07:00
parent 8d8278daef
commit 32680bbaad
4 changed files with 173 additions and 128 deletions

View file

@ -33,17 +33,8 @@ Node::Node(const QUuid& uuid, char type, sockaddr* publicSocket, sockaddr* local
_linkedData(NULL),
_isAlive(true)
{
if (publicSocket) {
_publicSocket = new sockaddr(*publicSocket);
} else {
_publicSocket = NULL;
}
if (localSocket) {
_localSocket = new sockaddr(*localSocket);
} else {
_localSocket = NULL;
}
setPublicSocket(publicSocket);
setLocalSocket(localSocket);
pthread_mutex_init(&_mutex, 0);
}
@ -95,6 +86,32 @@ const char* Node::getTypeName() const {
}
}
void Node::setPublicSocket(sockaddr* publicSocket) {
if (_activeSocket == _publicSocket) {
// if the active socket was the public socket then reset it to NULL
_activeSocket = NULL;
}
if (publicSocket) {
_publicSocket = new sockaddr(*publicSocket);
} else {
_publicSocket = NULL;
}
}
void Node::setLocalSocket(sockaddr* localSocket) {
if (_activeSocket == _localSocket) {
// if the active socket was the local socket then reset it to NULL
_activeSocket = NULL;
}
if (localSocket) {
_localSocket = new sockaddr(*localSocket);
} else {
_localSocket = NULL;
}
}
void Node::activateLocalSocket() {
qDebug() << "Activating local socket for node" << *this << "\n";
_activeSocket = _localSocket;

View file

@ -47,9 +47,9 @@ public:
void setLastHeardMicrostamp(uint64_t lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; }
sockaddr* getPublicSocket() const { return _publicSocket; }
void setPublicSocket(sockaddr* publicSocket) { _publicSocket = publicSocket; }
void setPublicSocket(sockaddr* publicSocket);
sockaddr* getLocalSocket() const { return _localSocket; }
void setLocalSocket(sockaddr* localSocket) { _localSocket = localSocket; }
void setLocalSocket(sockaddr* localSocket);
sockaddr* getActiveSocket() const { return _activeSocket; }

View file

@ -71,11 +71,10 @@ NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) :
_ownerUUID(QUuid::createUuid()),
_numNoReplyDomainCheckIns(0),
_assignmentServerSocket(NULL),
_checkInPacket(NULL),
_numBytesCheckInPacket(0),
_publicAddress(),
_publicPort(0),
_shouldUseDomainServerAsSTUN(0)
_hasCompletedInitialSTUNFailure(false),
_stunRequestsSinceSuccess(0)
{
}
@ -288,11 +287,6 @@ void NodeList::reset() {
clear();
_numNoReplyDomainCheckIns = 0;
delete[] _checkInPacket;
_checkInPacket = NULL;
_numBytesCheckInPacket = 0;
delete _nodeTypesOfInterest;
_nodeTypesOfInterest = NULL;
@ -316,62 +310,67 @@ void NodeList::sendSTUNRequest() {
const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io";
const unsigned short STUN_SERVER_PORT = 3478;
static int failedStunRequests = 0;
unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER];
if (failedStunRequests < NUM_STUN_REQUESTS_BEFORE_FALLBACK) {
unsigned 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;
unsigned char transactionID[NUM_TRANSACTION_ID_BYTES];
loadRandomIdentifier(transactionID, NUM_TRANSACTION_ID_BYTES);
memcpy(stunRequestPacket + packetIndex, &transactionID, sizeof(transactionID));
// lookup the IP for the STUN server
static QHostInfo stunInfo = QHostInfo::fromName(STUN_SERVER_HOSTNAME);
for (int i = 0; i < stunInfo.addresses().size(); i++) {
if (stunInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
QString stunIPAddress = stunInfo.addresses()[i].toString();
qDebug("Sending a stun request to %s\n", stunIPAddress.toLocal8Bit().constData());
_nodeSocket.send(stunIPAddress.toLocal8Bit().constData(),
STUN_SERVER_PORT,
stunRequestPacket,
sizeof(stunRequestPacket));
break;
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;
unsigned char transactionID[NUM_TRANSACTION_ID_BYTES];
loadRandomIdentifier(transactionID, NUM_TRANSACTION_ID_BYTES);
memcpy(stunRequestPacket + packetIndex, &transactionID, sizeof(transactionID));
// lookup the IP for the STUN server
static QHostInfo stunInfo = QHostInfo::fromName(STUN_SERVER_HOSTNAME);
for (int i = 0; i < stunInfo.addresses().size(); i++) {
if (stunInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
QString stunIPAddress = stunInfo.addresses()[i].toString();
if (!_hasCompletedInitialSTUNFailure) {
qDebug("Sending intial stun request to %s\n", stunIPAddress.toLocal8Bit().constData());
}
_nodeSocket.send(stunIPAddress.toLocal8Bit().constData(),
STUN_SERVER_PORT,
stunRequestPacket,
sizeof(stunRequestPacket));
break;
}
failedStunRequests++;
return;
}
// if we're here this was the last failed STUN request
// use our DS as our stun server
qDebug("Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.\n",
STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
_shouldUseDomainServerAsSTUN = true;
_stunRequestsSinceSuccess++;
if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) {
if (!_hasCompletedInitialSTUNFailure) {
// if we're here this was the last failed STUN request
// use our DS as our stun server
qDebug("Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.\n",
STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
_hasCompletedInitialSTUNFailure = true;
}
// reset the public address and port
_publicAddress = QHostAddress::Null;
_publicPort = 0;
}
}
void NodeList::processSTUNResponse(unsigned char* packetData, size_t dataBytes) {
@ -395,6 +394,9 @@ void NodeList::processSTUNResponse(unsigned char* packetData, size_t dataBytes)
const int NUM_BYTES_FAMILY_ALIGN = 1;
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
// reset the number of failed STUN requests since last success
_stunRequestsSinceSuccess = 0;
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
uint8_t addressFamily = 0;
@ -416,12 +418,20 @@ void NodeList::processSTUNResponse(unsigned char* packetData, size_t dataBytes)
memcpy(&xorMappedAddress, packetData + byteIndex, sizeof(xorMappedAddress));
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
_publicAddress = QHostAddress(stunAddress);
qDebug("Public socket received from STUN server is %s:%hu\n",
_publicAddress.toString().toLocal8Bit().constData(),
_publicPort);
QHostAddress newPublicAddress = QHostAddress(stunAddress);
if (newPublicAddress != _publicAddress) {
_publicAddress = newPublicAddress;
qDebug("New public socket received from STUN server is %s:%hu\n",
_publicAddress.toString().toLocal8Bit().constData(),
_publicPort);
}
_hasCompletedInitialSTUNFailure = true;
break;
}
} else {
@ -469,62 +479,66 @@ void NodeList::sendDomainServerCheckIn() {
printedDomainServerIP = true;
}
if (_publicAddress.isNull() && !_shouldUseDomainServerAsSTUN) {
if (_publicAddress.isNull() && !_hasCompletedInitialSTUNFailure) {
// we don't know our public socket and we need to send it to the domain server
// send a STUN request to figure it out
sendSTUNRequest();
} else {
// construct the DS check in packet if we need to
if (!_checkInPacket) {
int numBytesNodesOfInterest = _nodeTypesOfInterest ? strlen((char*) _nodeTypesOfInterest) : 0;
const int IP_ADDRESS_BYTES = 4;
// check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination
int numPacketBytes = sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION) + sizeof(NODE_TYPE) +
NUM_BYTES_RFC4122_UUID + (2 * (sizeof(uint16_t) + IP_ADDRESS_BYTES)) +
numBytesNodesOfInterest + sizeof(unsigned char);
_checkInPacket = new unsigned char[numPacketBytes];
unsigned char* packetPosition = _checkInPacket;
PACKET_TYPE nodePacketType = (memchr(SOLO_NODE_TYPES, _ownerType, sizeof(SOLO_NODE_TYPES)))
? PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY
: PACKET_TYPE_DOMAIN_LIST_REQUEST;
packetPosition += populateTypeAndVersion(packetPosition, nodePacketType);
*(packetPosition++) = _ownerType;
// send our owner UUID or the null one
QByteArray rfcOwnerUUID = _ownerUUID.toRfc4122();
memcpy(packetPosition, rfcOwnerUUID.constData(), rfcOwnerUUID.size());
packetPosition += rfcOwnerUUID.size();
// pack our public address to send to domain-server
packetPosition += packSocket(_checkInPacket + (packetPosition - _checkInPacket),
htonl(_publicAddress.toIPv4Address()), htons(_publicPort));
// pack our local address to send to domain-server
packetPosition += packSocket(_checkInPacket + (packetPosition - _checkInPacket),
getLocalAddress(),
htons(_nodeSocket.getListeningPort()));
// add the number of bytes for node types of interest
*(packetPosition++) = numBytesNodesOfInterest;
// copy over the bytes for node types of interest, if required
if (numBytesNodesOfInterest > 0) {
memcpy(packetPosition,
_nodeTypesOfInterest,
numBytesNodesOfInterest);
packetPosition += numBytesNodesOfInterest;
}
_numBytesCheckInPacket = packetPosition - _checkInPacket;
int numBytesNodesOfInterest = _nodeTypesOfInterest ? strlen((char*) _nodeTypesOfInterest) : 0;
const int IP_ADDRESS_BYTES = 4;
// check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination
int numPacketBytes = sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION) + sizeof(NODE_TYPE) +
NUM_BYTES_RFC4122_UUID + (2 * (sizeof(uint16_t) + IP_ADDRESS_BYTES)) +
numBytesNodesOfInterest + sizeof(unsigned char);
unsigned char* checkInPacket = new unsigned char[numPacketBytes];
unsigned char* packetPosition = checkInPacket;
PACKET_TYPE nodePacketType = (memchr(SOLO_NODE_TYPES, _ownerType, sizeof(SOLO_NODE_TYPES)))
? PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY
: PACKET_TYPE_DOMAIN_LIST_REQUEST;
packetPosition += populateTypeAndVersion(packetPosition, nodePacketType);
*(packetPosition++) = _ownerType;
// send our owner UUID or the null one
QByteArray rfcOwnerUUID = _ownerUUID.toRfc4122();
memcpy(packetPosition, rfcOwnerUUID.constData(), rfcOwnerUUID.size());
packetPosition += rfcOwnerUUID.size();
// pack our public address to send to domain-server
packetPosition += packSocket(checkInPacket + (packetPosition - checkInPacket),
htonl(_publicAddress.toIPv4Address()), htons(_publicPort));
// pack our local address to send to domain-server
packetPosition += packSocket(checkInPacket + (packetPosition - checkInPacket),
getLocalAddress(),
htons(_nodeSocket.getListeningPort()));
// add the number of bytes for node types of interest
*(packetPosition++) = numBytesNodesOfInterest;
// copy over the bytes for node types of interest, if required
if (numBytesNodesOfInterest > 0) {
memcpy(packetPosition,
_nodeTypesOfInterest,
numBytesNodesOfInterest);
packetPosition += numBytesNodesOfInterest;
}
_nodeSocket.send(_domainIP.toString().toLocal8Bit().constData(), _domainPort, _checkInPacket, _numBytesCheckInPacket);
_nodeSocket.send(_domainIP.toString().toLocal8Bit().constData(), _domainPort, checkInPacket, packetPosition - checkInPacket);
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0;
// send a STUN request every Nth domain server check in so we update our public socket, if required
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
sendSTUNRequest();
}
// increment the count of un-replied check-ins
_numNoReplyDomainCheckIns++;
@ -622,6 +636,8 @@ Node* NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, sockaddr* publ
return newNode;
} else {
node->lock();
if (node->getType() == NODE_TYPE_AUDIO_MIXER ||
node->getType() == NODE_TYPE_VOXEL_SERVER) {
// until the Audio class also uses our nodeList, we need to update
@ -629,6 +645,19 @@ Node* NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, sockaddr* publ
node->setLastHeardMicrostamp(usecTimestampNow());
}
// check if we need to change this node's public or local sockets
if (!socketMatch(publicSocket, node->getPublicSocket())) {
node->setPublicSocket(publicSocket);
qDebug() << "Public socket change for node" << *node << "\n";
}
if (!socketMatch(localSocket, node->getLocalSocket())) {
node->setLocalSocket(localSocket);
qDebug() << "Local socket change for node" << *node << "\n";
}
node->unlock();
// we had this node already, do nothing for now
return &*node;
}

View file

@ -167,11 +167,10 @@ private:
pthread_t checkInWithDomainServerThread;
int _numNoReplyDomainCheckIns;
sockaddr* _assignmentServerSocket;
uchar* _checkInPacket;
int _numBytesCheckInPacket;
QHostAddress _publicAddress;
uint16_t _publicPort;
bool _shouldUseDomainServerAsSTUN;
bool _hasCompletedInitialSTUNFailure;
unsigned int _stunRequestsSinceSuccess;
void activateSocketFromNodeCommunication(sockaddr *nodeAddress);
void timePingReply(sockaddr *nodeAddress, unsigned char *packetData);