void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { if (node->getType() == NodeType::AudioMixer) { flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPing); } // every second we're trying to ping this node and we're not getting anywhere - debug that out const int NUM_DEBUG_CONNECTION_ATTEMPTS = 1000 / (UDP_PUNCH_PING_INTERVAL_MS); if (node->getConnectionAttempts() > 0 && node->getConnectionAttempts() % NUM_DEBUG_CONNECTION_ATTEMPTS == 0) { qCDebug(networking) << "No response to UDP hole punch pings for node" << node->getUUID() << "in last second."; } // send the ping packet to the local and public sockets for this node auto localPingPacket = constructPingPacket(PingType::Local); sendPacket(std::move(localPingPacket), *node, node->getLocalSocket()); auto publicPingPacket = constructPingPacket(PingType::Public); sendPacket(std::move(publicPingPacket), *node, node->getPublicSocket()); if (!node->getSymmetricSocket().isNull()) { auto symmetricPingPacket = constructPingPacket(PingType::Symmetric); sendPacket(std::move(symmetricPingPacket), *node, node->getSymmetricSocket()); } node->incrementConnectionAttempts(); }
void NodeList::pingPunchForDomainServer() { // make sure if we're here that we actually still need to ping the domain-server if (_domainHandler.getIP().isNull() && _domainHandler.getICEPeer().hasSockets()) { // check if we've hit the number of pings we'll send to the DS before we consider it a fail const int NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET = 2000 / UDP_PUNCH_PING_INTERVAL_MS; if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); } else { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat qCDebug(networking) << "No ping replies received from domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getICEClientID()) << "-" << "re-sending ICE query."; _domainHandler.getICEPeer().softReset(); handleICEConnectionToDomainServer(); return; } } flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendPingsToDS); // send the ping packet to the local and public sockets for this node auto localPingPacket = constructICEPingPacket(PingType::Local, _sessionUUID); sendPacket(std::move(localPingPacket), _domainHandler.getICEPeer().getLocalSocket()); auto publicPingPacket = constructICEPingPacket(PingType::Public, _sessionUUID); sendPacket(std::move(publicPingPacket), _domainHandler.getICEPeer().getPublicSocket()); _domainHandler.getICEPeer().incrementConnectionAttempts(); } }
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, PacketType packetType, QString codecName) { static std::mutex _mutex; using Locker = std::unique_lock<std::mutex>; auto nodeList = DependencyManager::get<NodeList>(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); if (audioMixer && audioMixer->getActiveSocket()) { Locker lock(_mutex); auto audioPacket = NLPacket::create(packetType); // FIXME - this is not a good way to determine stereoness with codecs.... quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0; // write sequence number auto sequence = sequenceNumber++; audioPacket->writePrimitive(sequence); // write the codec audioPacket->writeString(codecName); if (packetType == PacketType::SilentAudioFrame) { // pack num silent samples quint16 numSilentSamples = isStereo ? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO : AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; audioPacket->writePrimitive(numSilentSamples); } else { // set the mono/stereo byte audioPacket->writePrimitive(isStereo); } // pack the three float positions audioPacket->writePrimitive(transform.getTranslation()); // pack the orientation audioPacket->writePrimitive(transform.getRotation()); audioPacket->writePrimitive(avatarBoundingBoxCorner); audioPacket->writePrimitive(avatarBoundingBoxScale); if (audioPacket->getType() != PacketType::SilentAudioFrame) { // audio samples have already been packed (written to networkAudioSamples) int leadingBytes = audioPacket->getPayloadSize(); audioPacket->setPayloadSize(leadingBytes + bytes); memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes); } nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); nodeList->sendUnreliablePacket(*audioPacket, *audioMixer); } }
void NodeList::handleICEConnectionToDomainServer() { // if we're still waiting to get sockets we want to ping for the domain-server // then send another heartbeat now if (!_domainHandler.getICEPeer().hasSockets()) { _domainHandler.getICEPeer().resetConnectionAttempts(); flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendICEServerQuery); LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getICEDomainID()); } }
void NodeList::activateSocketFromNodeCommunication(ReceivedMessage& message, const SharedNodePointer& sendingNode) { // deconstruct this ping packet to see if it is a public or local reply QDataStream packetStream(message.getMessage()); quint8 pingType; packetStream >> pingType; // if this is a local or public ping then we can activate a socket // we do nothing with agnostic pings, those are simply for timing if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { sendingNode->activateLocalSocket(); } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { sendingNode->activatePublicSocket(); } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { sendingNode->activateSymmetricSocket(); } if (sendingNode->getType() == NodeType::AudioMixer) { flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetAudioMixerSocket); } }
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType) { static std::mutex _mutex; using Locker = std::unique_lock<std::mutex>; auto nodeList = DependencyManager::get<NodeList>(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); if (audioMixer && audioMixer->getActiveSocket()) { Locker lock(_mutex); static std::unique_ptr<NLPacket> audioPacket = NLPacket::create(PacketType::Unknown); quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0; audioPacket->setType(packetType); // reset the audio packet so we can start writing audioPacket->reset(); // write sequence number audioPacket->writePrimitive(sequenceNumber++); if (audioPacket->getType() == PacketType::SilentAudioFrame) { // pack num silent samples quint16 numSilentSamples = isStereo ? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO : AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; audioPacket->writePrimitive(numSilentSamples); } else { // set the mono/stereo byte audioPacket->writePrimitive(isStereo); } // pack the three float positions audioPacket->writePrimitive(transform.getTranslation()); // pack the orientation audioPacket->writePrimitive(transform.getRotation()); if (audioPacket->getType() != PacketType::SilentAudioFrame) { // audio samples have already been packed (written to networkAudioSamples) audioPacket->setPayloadSize(audioPacket->getPayloadSize() + bytes); static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8); memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes); } nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); nodeList->sendUnreliablePacket(*audioPacket, *audioMixer); } }
void NodeList::sendDomainServerCheckIn() { if (_isShuttingDown) { qCDebug(networking) << "Refusing to send a domain-server check in while shutting down."; return; } if (_publicSockAddr.isNull()) { // we don't know our public socket and we need to send it to the domain server qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { bool isUsingDTLS = false; PacketType domainPacketType = !_domainHandler.isConnected() ? PacketType::DomainConnectRequest : PacketType::DomainListRequest; if (!_domainHandler.isConnected()) { qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname(); // is this our localhost domain-server? // if so we need to make sure we have an up-to-date local port in case it restarted if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost || _domainHandler.getHostname() == "localhost") { quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort); qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort; _domainHandler.setPort(domainPort); } } // check if we're missing a keypair we need to verify ourselves with the domain-server auto& accountManager = AccountManager::getInstance(); const QUuid& connectionToken = _domainHandler.getConnectionToken(); // we assume that we're on the same box as the DS if it has the same local address and // it didn't present us with a connection token to use for username signature bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost || (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull()); bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain; if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) { qWarning() << "A keypair is required to present a username signature to the domain-server" << "but no keypair is present. Waiting for keypair generation to complete."; accountManager.generateNewUserKeypair(); // don't send the check in packet - wait for the keypair first return; } auto domainPacket = NLPacket::create(domainPacketType); QDataStream packetStream(domainPacket.get()); if (domainPacketType == PacketType::DomainConnectRequest) { QUuid connectUUID; if (!_domainHandler.getAssignmentUUID().isNull()) { // this is a connect request and we're an assigned node // so set our packetUUID as the assignment UUID connectUUID = _domainHandler.getAssignmentUUID(); } else if (_domainHandler.requiresICE()) { // this is a connect request and we're an interface client // that used ice to discover the DS // so send our ICE client UUID with the connect request connectUUID = _domainHandler.getICEClientID(); } // pack the connect UUID for this connect request packetStream << connectUUID; } // pack our data to send to the domain-server packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = accountManager.getAccountInfo(); packetStream << accountInfo.getUsername(); // if this is a connect request, and we can present a username signature, send it along if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) { const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; } } flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); if (!isUsingDTLS) { sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); } if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that emit limitOfSilentDomainCheckInsReached(); } // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; } }