diff --git a/src/fne/CMakeLists.txt b/src/fne/CMakeLists.txt index 2319fc23..d3e40abd 100644 --- a/src/fne/CMakeLists.txt +++ b/src/fne/CMakeLists.txt @@ -16,6 +16,8 @@ file(GLOB dvmfne_SRC "src/fne/network/callhandler/packetdata/*.cpp" "src/fne/network/influxdb/*.h" "src/fne/network/influxdb/*.cpp" + "src/fne/network/packet/*.h" + "src/fne/network/packet/*.cpp" "src/fne/network/*.h" "src/fne/network/*.cpp" "src/fne/restapi/*.h" diff --git a/src/fne/network/MetadataNetwork.cpp b/src/fne/network/MetadataNetwork.cpp index d0e02fb8..f41a4237 100644 --- a/src/fne/network/MetadataNetwork.cpp +++ b/src/fne/network/MetadataNetwork.cpp @@ -9,7 +9,6 @@ */ #include "fne/Defines.h" #include "common/edac/SHA256.h" -#include "common/zlib/Compression.h" #include "common/Log.h" #include "common/Utils.h" #include "network/MetadataNetwork.h" @@ -18,16 +17,9 @@ using namespace network; using namespace network::callhandler; -using namespace compress; #include -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const uint32_t TIMEOUT_MAX_REPL = 5000U; // 5 seconds - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -246,1253 +238,21 @@ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) uint32_t ssrc = req->rtpHeader.getSSRC(); uint32_t streamId = req->fneHeader.getStreamId(); - // process incoming message function opcodes - switch (req->fneHeader.getFunction()) { - case NET_FUNC::TRANSFER: // Transfer - { - // resolve peer ID (used for Activity Log and Status Transfer) - bool validPeerId = false; - uint32_t pktPeerId = 0U; - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - validPeerId = true; - pktPeerId = peerId; - } else { - if (peerId > 0) { - // this could be a replica transfer -- in which case, we need to check the SSRC of the packet not the peer ID - if (network->m_peers.find(req->rtpHeader.getSSRC()) != network->m_peers.end()) { - FNEPeerConnection* connection = network->m_peers[req->rtpHeader.getSSRC()]; - if (connection != nullptr) { - if (connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR && connection->isReplica()) { - validPeerId = true; - pktPeerId = req->rtpHeader.getSSRC(); - } - } - } - } - } - - // process incoming message subfunction opcodes - switch (req->fneHeader.getSubFunction()) { - case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: // Peer Activity Log Transfer - { - if (network->m_allowActivityTransfer) { - if (pktPeerId > 0 && validPeerId) { - FNEPeerConnection* connection = network->m_peers[pktPeerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - DECLARE_UINT8_ARRAY(rawPayload, req->length - 11U); - ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); - std::string payload(rawPayload, rawPayload + (req->length - 11U)); - - ::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identWithQualifier().c_str(), payload.c_str()); - - // report activity log to InfluxDB - if (network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("activity") - .tag("peerId", std::to_string(pktPeerId)) - .field("identity", connection->identity()) - .field("msg", payload) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .requestAsync(network->m_influxServer); - } - - // repeat traffic to the connected SysView peers - if (network->m_peers.size() > 0U) { - for (auto peer : network->m_peers) { - if (peer.second != nullptr) { - if (peer.second->peerClass() == PEER_CONN_CLASS_SYSVIEW) { - sockaddr_storage addr = peer.second->socketStorage(); - uint32_t addrLen = peer.second->sockStorageLen(); - - network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, - { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen); - } - } else { - continue; - } - } - } - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, true, pktPeerId); - } - } - } - } - } - else { - network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - } - break; - - case NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG: // Peer Diagnostic Log Transfer - { - if (network->m_allowDiagnosticTransfer) { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - DECLARE_UINT8_ARRAY(rawPayload, req->length - 11U); - ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); - std::string payload(rawPayload, rawPayload + (req->length - 11U)); - - bool currState = g_disableTimeDisplay; - g_disableTimeDisplay = true; - ::Log(9999U, {nullptr, nullptr, 0U, nullptr}, "%.9u (%8s) %s", peerId, connection->identWithQualifier().c_str(), payload.c_str()); - g_disableTimeDisplay = currState; - - // report diagnostic log to InfluxDB - if (network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("diag") - .tag("peerId", std::to_string(peerId)) - .field("identity", connection->identity()) - .field("msg", payload) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .requestAsync(network->m_influxServer); - } - } - else { - network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - } - break; - - case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: // Peer Status Transfer - { - if (pktPeerId > 0 && validPeerId) { - FNEPeerConnection* connection = network->m_peers[pktPeerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_peers.size() > 0U) { - // attempt to repeat status traffic to SysView clients - for (auto peer : network->m_peers) { - if (peer.second != nullptr) { - if (peer.second->peerClass() == PEER_CONN_CLASS_SYSVIEW) { - sockaddr_storage addr = peer.second->socketStorage(); - uint32_t addrLen = peer.second->sockStorageLen(); - - if (network->m_debug) { - LogDebug(LOG_DIAG, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u", - pktPeerId, peer.first, req->length); - } - network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, - { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen); - } - } else { - continue; - } - } - - // attempt to repeat status traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, true, pktPeerId); - } - } - } - } - } - } - else { - network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - default: - { - LogWarning(LOG_MASTER, "PEER %u, unknown/unsupported transfer opcode %u", peerId, req->fneHeader.getSubFunction()); - if (network->m_debug) - Utils::dump("Unknown/unsupported transfer opcode from the peer", req->buffer, req->length); - } - break; - } - } - break; - - case NET_FUNC::ANNOUNCE: // Announce - { - // process incoming message subfunction opcodes - switch (req->fneHeader.getSubFunction()) { - case NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL: // Announce Group Affiliation - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - uint32_t dstId = GET_UINT24(req->buffer, 3U); // Destination Address - aff->groupUnaff(srcId); - aff->groupAff(srcId, dstId); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG: // Announce Unit Registration - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - aff->unitReg(srcId, ssrc); - network->m_globalAff->unitReg(srcId, ssrc); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true, 0U, ssrc); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG: // Announce Unit Deregistration - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - aff->unitDereg(srcId); - network->m_globalAff->unitDereg(srcId); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL: // Announce Group Affiliation Removal - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address - aff->groupUnaff(srcId); - network->m_globalAff->groupUnaff(srcId); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_AFFILS: // Announce Update All Affiliations - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - if (aff != nullptr) { - aff->clearGroupAff(0U, true); - - // update TGID lists - uint32_t len = GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t srcId = GET_UINT24(req->buffer, offs); - uint32_t dstId = GET_UINT24(req->buffer, offs + 4U); - - aff->groupAff(srcId, dstId); - network->m_globalAff->groupAff(srcId, dstId); - offs += 8U; - } - LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u affiliations", peerId, connection->identWithQualifier().c_str(), len); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); - } - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS: // Announce Update All Unit Registrations - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - std::shared_ptr aff = network->getPeerAffiliations(peerId); - if (aff == nullptr) { - LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - - if (aff != nullptr) { - aff->clearUnitReg(); - - // update unit registration lists - uint32_t len = GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t srcId = GET_UINT24(req->buffer, offs); - - aff->unitReg(srcId, ssrc); - network->m_globalAff->unitReg(srcId, ssrc); - offs += 3U; - } - LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u unit registrations", peerId, connection->identWithQualifier().c_str(), len); - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); - } - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC: // Announce Site VCs - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - std::vector vcPeers; - - // update peer association - uint32_t len = GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t vcPeerId = GET_UINT32(req->buffer, offs); - if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) { - FNEPeerConnection* vcConnection = network->m_peers[vcPeerId]; - if (vcConnection != nullptr) { - vcConnection->ccPeerId(peerId); - vcPeers.push_back(vcPeerId); - } - } - offs += 4U; - } - LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u VCs", peerId, connection->identWithQualifier().c_str(), len); - network->m_ccPeerMap[peerId] = vcPeers; - - // attempt to repeat traffic to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); - } - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - default: - { - LogWarning(LOG_MASTER, "PEER %u, unknown/unsupported announcement opcode %u", peerId, req->fneHeader.getSubFunction()); - if (network->m_debug) - Utils::dump("Unknown/unsupported announcement opcode from the peer", req->buffer, req->length); - } - break; - } - } - break; - - case NET_FUNC::KEYS_INVENTORY: // Encryption Key Container Inventory - { - if (!network->m_host->m_cryptoLookup->isRemoteAccessEnabled()) { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but remote access is disabled, no response", peerId); - break; - } - - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory but is not allowed, no response", peerId); - break; - } else { - if (!peerEntry.canRequestKeys()) { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory but is not allowed, no response", peerId); - break; - } - } - - // keys inventory operates differently from the rest of the network opcodes...and does not require - // an established connection to the master, so we will not validate the peer connection state here - if (peerId > 0 && !peerEntry.peerDefault()) { - if (req->length < 80) { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but payload length was invalid (%u bytes), no response", peerId, req->length); - break; - } - - // scope intentional - { - // get the peer password hash from the frame message - DECLARE_UINT8_ARRAY(peerHash, 32U); - ::memcpy(peerHash, req->buffer + 8U, 32U); - - uint8_t peerSalt[4U]; - ::memset(peerSalt, 0x00U, 4U); - ::memcpy(peerSalt, req->buffer + 40U, 4U); - - std::string passwordForPeer = network->m_password; - - // check if the peer is in the peer ACL list - bool validAcl = true; - if (network->m_peerListLookup->getACL()) { - if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); - validAcl = false; - } else { - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check - } else { - passwordForPeer = peerEntry.peerPassword(); - if (passwordForPeer.length() == 0) { - passwordForPeer = network->m_password; - } - } - } - - if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); - validAcl = true; - } - } - - if (validAcl) { - size_t size = passwordForPeer.size(); - uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; - ::memcpy(in, peerSalt, sizeof(uint32_t)); - for (size_t i = 0U; i < size; i++) - in[i + sizeof(uint32_t)] = passwordForPeer.at(i); - - uint8_t out[32U]; - edac::SHA256 sha256; - sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); - - delete[] in; - - // validate hash - bool validHash = false; - if (req->length >= 80) { - validHash = true; - for (uint8_t i = 0; i < 32U; i++) { - if (peerHash[i] != out[i]) { - validHash = false; - break; - } - } - } - - if (!validHash) { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid authentication, no response", peerId); - break; - } - } else { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid ACL, no response", peerId); - break; - } - } - - // scope intentional - { - // get remote access password hash from the frame message - DECLARE_UINT8_ARRAY(remoteAccessHash, 32U); - ::memcpy(remoteAccessHash, req->buffer + 44U, 32U); - - uint8_t remoteSalt[4U]; - ::memset(remoteSalt, 0x00U, 4U); - ::memcpy(remoteSalt, req->buffer + 76U, 4U); - - std::string remoteAccessPassword = network->m_host->m_cryptoLookup->getRemotePassword(); - - size_t size = remoteAccessPassword.size(); - uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; - ::memcpy(in, remoteSalt, sizeof(uint32_t)); - for (size_t i = 0U; i < size; i++) - in[i + sizeof(uint32_t)] = remoteAccessPassword.at(i); - - uint8_t out[32U]; - edac::SHA256 sha256; - sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); - - delete[] in; - - // validate hash - bool validHash = false; - if (req->length >= 80) { - validHash = true; - for (uint8_t i = 0; i < 32U; i++) { - if (remoteAccessHash[i] != out[i]) { - validHash = false; - break; - } - } - } - - if (!validHash) { - LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid access authentication, no response", peerId); - break; - } - } - - // scope intentional - { - // read entire file into buffer - std::stringstream b; - std::ifstream stream(network->m_host->m_cryptoLookup->filename(), std::ios::in | std::ios::binary); - - uint32_t len = 0U; - UInt8Array bufferUInt8Array = nullptr; - uint8_t* buffer = nullptr; - - if (stream.is_open()) { - stream.seekg(0, std::ios::end); - len = (uint32_t)stream.tellg(); - stream.seekg(0, std::ios::beg); - - bufferUInt8Array = std::make_unique(len); - buffer = bufferUInt8Array.get(); - ::memset(buffer, 0x00U, len); - - uint32_t i = 0U; - while (stream.peek() != EOF) { - buffer[i] = (uint8_t)stream.get(); - i++; - } - - stream.close(); - } - - PacketBuffer pkt(true, "Remote EKC, Key Inventory"); - pkt.encode((uint8_t*)buffer, len); - - LogInfoEx(LOG_REPL, "PEER %u Remote EKC, Key Inventory, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); - if (pkt.fragments.size() > 0U) { - for (auto frag : pkt.fragments) { - // violate most handling rules for responding to packets -- we need to directly respond to the calling peer as - // they may not be logged in as a standard peer - mdNetwork->m_frameQueue->write(frag.second->data, FRAG_SIZE, streamId, peerId, network->m_peerId, { NET_FUNC::KEYS_INVENTORY, NET_SUBFUNC::NOP }, - 0U, req->address, req->addrLen); - Thread::sleep(60U); // pace block transmission - } - } - - pkt.clear(); - } - } - } - break; - - case NET_FUNC::KEYS_UPDATE: // Encryption Key Container Update - { - if (!network->m_host->m_cryptoLookup->isRemoteAccessEnabled()) { - LogError(LOG_MASTER, "PEER %u requested enc. key update, but remote access is disabled, no response", peerId); - break; - } - - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - LogError(LOG_MASTER, "PEER %u requested enc. key update but is not allowed, no response", peerId); - break; - } else { - if (!peerEntry.canRequestKeys()) { - LogError(LOG_MASTER, "PEER %u requested enc. key update but is not allowed, no response", peerId); - break; - } - } - - // keys update operates differently from the rest of the network opcodes...and does not require - // an established connection to the master, so we will not validate the peer connection state here. - // update is a two-phase flow: (1) auth request frame (80 bytes), then (2) chunked PacketBuffer frames. - if (peerId > 0 && !peerEntry.peerDefault()) { - if (mdNetwork->m_peerKeyUpdatePkt.find(peerId) == mdNetwork->m_peerKeyUpdatePkt.end()) { - if (req->length < 80) { - LogError(LOG_MASTER, "PEER %u requested enc. key update, but payload length was invalid (%u bytes), no response", peerId, req->length); - break; - } - - // scope intentional - { - // get the peer password hash from the frame message - DECLARE_UINT8_ARRAY(peerHash, 32U); - ::memcpy(peerHash, req->buffer + 8U, 32U); - - uint8_t peerSalt[4U]; - ::memset(peerSalt, 0x00U, 4U); - ::memcpy(peerSalt, req->buffer + 40U, 4U); - - std::string passwordForPeer = network->m_password; - - // check if the peer is in the peer ACL list - bool validAcl = true; - if (network->m_peerListLookup->getACL()) { - if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); - validAcl = false; - } else { - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check - } else { - passwordForPeer = peerEntry.peerPassword(); - if (passwordForPeer.length() == 0) { - passwordForPeer = network->m_password; - } - } - } - - if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); - validAcl = true; - } - } - - if (validAcl) { - size_t size = passwordForPeer.size(); - uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; - ::memcpy(in, peerSalt, sizeof(uint32_t)); - for (size_t i = 0U; i < size; i++) - in[i + sizeof(uint32_t)] = passwordForPeer.at(i); - - uint8_t out[32U]; - edac::SHA256 sha256; - sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); - - delete[] in; - - // validate hash - bool validHash = false; - if (req->length >= 80) { - validHash = true; - for (uint8_t i = 0; i < 32U; i++) { - if (peerHash[i] != out[i]) { - validHash = false; - break; - } - } - } - - if (!validHash) { - LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid authentication, no response", peerId); - break; - } - } else { - LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid ACL, no response", peerId); - break; - } - } - - // scope intentional - { - // get remote access password hash from the frame message - DECLARE_UINT8_ARRAY(remoteAccessHash, 32U); - ::memcpy(remoteAccessHash, req->buffer + 44U, 32U); - - uint8_t remoteSalt[4U]; - ::memset(remoteSalt, 0x00U, 4U); - ::memcpy(remoteSalt, req->buffer + 76U, 4U); - - std::string remoteAccessPassword = network->m_host->m_cryptoLookup->getRemotePassword(); - - size_t size = remoteAccessPassword.size(); - uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; - ::memcpy(in, remoteSalt, sizeof(uint32_t)); - for (size_t i = 0U; i < size; i++) - in[i + sizeof(uint32_t)] = remoteAccessPassword.at(i); - - uint8_t out[32U]; - edac::SHA256 sha256; - sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); - - delete[] in; - - // validate hash - bool validHash = false; - if (req->length >= 80) { - validHash = true; - for (uint8_t i = 0; i < 32U; i++) { - if (remoteAccessHash[i] != out[i]) { - validHash = false; - break; - } - } - } - - if (!validHash) { - LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid access authentication, no response", peerId); - break; - } - } - - PacketBufferEntryPtr pkt = findOrCreatePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId, "Remote EKC, Key Update", streamId); - if (pkt == nullptr || !pkt->buffer) { - LogError(LOG_REPL, "PEER %u Remote EKC, Key Update, failed to initialize packet buffer", peerId); - erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); - break; - } - - LogInfoEx(LOG_REPL, "PEER %u Remote EKC, Key Update, authenticated transfer streamId = %u", peerId, streamId); - break; - } - - // scope intentional - { - if (req->length < FRAG_SIZE) { - LogWarning(LOG_REPL, "PEER %u Remote EKC, Key Update, ignoring short data phase frame (%u bytes)", peerId, req->length); - break; - } - - DECLARE_UINT8_ARRAY(rawPayload, req->length); - ::memcpy(rawPayload, req->buffer, req->length); - - // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), KEYS_UPDATE, Raw Payload", rawPayload, req->length); - - PacketBufferEntryPtr pkt = findPacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); - if (pkt == nullptr || !pkt->buffer) { - break; - } - - std::unique_lock pktLock(pkt->mutex, std::defer_lock); - uint32_t timeout = 0U; - while (!pktLock.try_lock() && timeout < TIMEOUT_MAX_REPL) { - timeout++; - Thread::sleep(1U); - } - - if (!pktLock.owns_lock()) { - LogError(LOG_STP, "PEER %u Remote EKC, Key Update, timeout waiting for packet buffer to unlock", peerId); - if (pkt->buffer) { - pkt->buffer->clear(); - pkt->buffer.reset(); - } - pkt->streamId = 0U; - erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); - break; - } - - if (pkt->streamId != streamId) { - LogError(LOG_REPL, "PEER %u Remote EKC, Key Update, stream ID mismatch, expected %u, got %u", peerId, pkt->streamId, streamId); - if (pkt->buffer) { - pkt->buffer->clear(); - pkt->buffer.reset(); - } - pkt->streamId = 0U; - erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); - break; - } - - uint32_t decompressedLen = 0U; - uint8_t* decompressed = nullptr; - - if (pkt->buffer->decode(rawPayload, &decompressed, &decompressedLen)) { - std::ostringstream s; - s << network->m_cryptoLookup->filename(); - - std::string filename = s.str(); - std::ofstream file(filename, std::ofstream::out); - if (file.fail()) { - LogError(LOG_PEER, "Cannot open the crypto container file - %s", filename.c_str()); - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); - break; - } - - for (uint32_t i = 0U; i < decompressedLen; i++) { - file << (char)decompressed[i]; - } - - file.close(); - - network->m_cryptoLookup->stop(true); - network->m_cryptoLookup->reload(); - - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); - } - } - } - } - break; - - case NET_FUNC::REPL: - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_ACT_PEER_LIST) { // Peer Replication Active Peer List - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR && - connection->isReplica()) { - DECLARE_UINT8_ARRAY(rawPayload, req->length); - ::memcpy(rawPayload, req->buffer, req->length); - - // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), REPL_ACT_PEER_LIST, Raw Payload", rawPayload, req->length); - - PacketBufferEntryPtr pkt = findOrCreatePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId, "Peer Replication, Active Peer List", streamId); - if (pkt == nullptr || !pkt->buffer) { - LogError(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, failed to initialize packet buffer", peerId, - connection->identWithQualifier().c_str()); - erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); - break; - } - - std::unique_lock pktLock(pkt->mutex, std::defer_lock); - uint32_t timeout = 0U; - while (!pktLock.try_lock() && timeout < TIMEOUT_MAX_REPL) { - timeout++; - Thread::sleep(1U); - } - - if (!pktLock.owns_lock()) { - LogError(LOG_STP, "PEER %u (%s) Peer Replication, Active Peer List, timeout waiting for packet buffer to unlock", peerId, - connection->identWithQualifier().c_str()); - if (pkt->buffer) { - pkt->buffer->clear(); - pkt->buffer.reset(); - } - pkt->streamId = 0U; - erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); - break; - } - - if (pkt->streamId != streamId) { - LogError(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, stream ID mismatch, expected %u, got %u", peerId, - connection->identWithQualifier().c_str(), pkt->streamId, streamId); - pkt->buffer->clear(); - pkt->streamId = streamId; - } - - uint32_t decompressedLen = 0U; - uint8_t* decompressed = nullptr; - - if (pkt->buffer->decode(rawPayload, &decompressed, &decompressedLen)) { - std::string payload(decompressed + 8U, decompressed + decompressedLen); - - // parse JSON body - json::value v; - std::string err = json::parse(v, payload); - if (!err.empty()) { - LogError(LOG_REPL, "PEER %u (%s) error parsing active peer list, %s", peerId, connection->identWithQualifier().c_str(), err.c_str()); - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); - break; - } - else { - // ensure parsed JSON is an array - if (!v.is()) { - LogError(LOG_REPL, "PEER %u (%s) error parsing active peer list, data was not valid", peerId, connection->identWithQualifier().c_str()); - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); - break; - } - else { - json::array arr = v.get(); - LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size()); - network->m_peerReplicaPeers[peerId] = arr; - } - } - - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); - } - } - else { - network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_HA_PARAMS) { // Peer Replication HA Parameters - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR && - connection->isReplica()) { - DECLARE_UINT8_ARRAY(rawPayload, req->length); - ::memcpy(rawPayload, req->buffer, req->length); - - std::vector receivedParams; - - uint32_t len = GET_UINT32(rawPayload, 0U); - if (len > 0U) { - len /= HA_PARAMS_ENTRY_LEN; - } - - uint8_t offs = 4U; - for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) { - uint32_t peerId = GET_UINT32(rawPayload, offs); - uint32_t ipAddr = GET_UINT32(rawPayload, offs + 4U); - uint16_t port = GET_UINT16(rawPayload, offs + 8U); - receivedParams.push_back(HAParameters(peerId, ipAddr, port)); - } - - if (receivedParams.size() > 0U) { - for (auto rxEntry : receivedParams) { - auto it = std::find_if(network->m_peerReplicaHAParams.begin(), network->m_peerReplicaHAParams.end(), - [&](HAParameters& x) - { - if (x.peerId == rxEntry.peerId) - return true; - return false; - }); - if (it != network->m_peerReplicaHAParams.end()) { - it->masterIP = rxEntry.masterIP; - it->masterPort = rxEntry.masterPort; - } else { - HAParameters param = rxEntry; - network->m_peerReplicaHAParams.push_back(param); - } - - if (network->m_debug) { - std::string address = __IP_FROM_UINT(rxEntry.masterIP); - LogDebugEx(LOG_REPL, "MetadataNetwork::taskNetworkRx()", "PEER %u (%s) Peer Replication, HA Parameters, %s:%u", peerId, connection->identWithQualifier().c_str(), - address.c_str(), rxEntry.masterPort); - } - } - - if (receivedParams.size() > 0) { - LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, HA Parameters, updating %u entries, %u entries", peerId, connection->identWithQualifier().c_str(), receivedParams.size(), - network->m_peerReplicaHAParams.size()); - - // send peer updates to replica peers - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - std::vector haParams; - network->m_peerReplicaHAParams.lock(false); - for (auto entry : network->m_peerReplicaHAParams) { - haParams.push_back(entry); - } - network->m_peerReplicaHAParams.unlock(); - - peer.second->writeHAParams(haParams); - } - } - } - } - } - } - } else { - network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_FUNC::NET_TREE: - if (!network->m_enableSpanningTree) - break; - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::NET_TREE_LIST) { // FNE Network Tree List - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR) { - DECLARE_UINT8_ARRAY(rawPayload, req->length); - ::memcpy(rawPayload, req->buffer, req->length); - - // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), NET_TREE_LIST, Raw Payload", rawPayload, req->length); - - PacketBufferEntryPtr pkt = findOrCreatePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId, "Network Tree, Tree List", streamId); - if (pkt == nullptr || !pkt->buffer) { - LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, failed to initialize packet buffer", peerId, - connection->identWithQualifier().c_str()); - erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); - break; - } - - std::unique_lock pktLock(pkt->mutex, std::defer_lock); - uint32_t timeout = 0U; - while (!pktLock.try_lock() && timeout < TIMEOUT_MAX_REPL) { - timeout++; - Thread::sleep(1U); - } - - if (!pktLock.owns_lock()) { - LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, timeout waiting for packet buffer to unlock", peerId, - connection->identWithQualifier().c_str()); - if (pkt->buffer) { - pkt->buffer->clear(); - pkt->buffer.reset(); - } - pkt->streamId = 0U; - erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); - break; - } - - if (pkt->streamId != streamId) { - LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, stream ID mismatch, expected %u, got %u", peerId, - connection->identWithQualifier().c_str(), pkt->streamId, streamId); - pkt->buffer->clear(); - pkt->streamId = streamId; - } - - uint32_t decompressedLen = 0U; - uint8_t* decompressed = nullptr; - - if (pkt->buffer->decode(rawPayload, &decompressed, &decompressedLen)) { - std::string payload(decompressed + 8U, decompressed + decompressedLen); - - // parse JSON body - json::value v; - std::string err = json::parse(v, payload); - if (!err.empty()) { - LogError(LOG_STP, "PEER %u (%s) error parsing network tree list, %s", peerId, connection->identWithQualifier().c_str(), err.c_str()); - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); - break; - } - else { - // ensure parsed JSON is an array - if (!v.is()) { - LogError(LOG_STP, "PEER %u (%s) error parsing network tree list, data was not valid", peerId, connection->identWithQualifier().c_str()); - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); - break; - } - else { - json::array arr = v.get(); - LogInfoEx(LOG_STP, "PEER %u (%s) Network Tree, Tree List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size()); - - std::lock_guard guard(network->m_treeLock); - - std::vector duplicatePeers; - SpanningTree::deserializeTree(arr, network->m_treeRoot, &duplicatePeers); - - network->logSpanningTree(connection); + static const std::unordered_map handlers = { + { NET_FUNC::TRANSFER, &MetadataNetwork::PacketHandler::transfer }, + { NET_FUNC::ANNOUNCE, &MetadataNetwork::PacketHandler::announce }, - if (duplicatePeers.size() > 0U) { - for (auto dupPeerId : duplicatePeers) { - LogWarning(LOG_STP, "PEER %u (%s) Network Tree, Tree Change, disconnecting duplicate peer connection for PEER %u to prevent network loop", - peerId, connection->identWithQualifier().c_str(), dupPeerId); - network->writeTreeDisconnect(peerId, dupPeerId); - } - } - } - } + { NET_FUNC::KEYS_INVENTORY, &MetadataNetwork::PacketHandler::keysInventory }, + { NET_FUNC::KEYS_UPDATE, &MetadataNetwork::PacketHandler::keysUpdate }, - pkt->buffer->clear(); - pkt->buffer.reset(); - pkt->streamId = 0U; - if (decompressed != nullptr) { - delete[] decompressed; - } - erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); - } - } - else { - network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; + { NET_FUNC::REPL, &MetadataNetwork::PacketHandler::replication }, + { NET_FUNC::NET_TREE, &MetadataNetwork::PacketHandler::networkTree } + }; - default: - // metadata network ignores unknowns for everything else... - break; + // dispatch to the appropriate handler based on the function code + auto it = handlers.find(req->fneHeader.getFunction()); + if (it != handlers.end()) { + it->second(network, mdNetwork, req, peerId, ssrc, streamId); } } diff --git a/src/fne/network/MetadataNetwork.h b/src/fne/network/MetadataNetwork.h index 946eb70d..7a6f15e9 100644 --- a/src/fne/network/MetadataNetwork.h +++ b/src/fne/network/MetadataNetwork.h @@ -34,6 +34,12 @@ class HOST_SW_API HostFNE; namespace network { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t TIMEOUT_MAX_REPL = 5000U; // 5 seconds + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -161,6 +167,78 @@ namespace network */ static void erasePacketBufferEntry(PacketBufferMap& pktMap, uint32_t peerId); + using PacketHandlerFunc = void (*)(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + + /** + * @brief Implements the packet handler functions for the MetadataNetwork class. + */ + class PacketHandler { + public: + /** + * @brief Handles NET_FUNC::TRANSFER packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void transfer(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + /** + * @brief Handles NET_FUNC::ANNOUNCE packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void announce(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + + /** + * @brief Handles NET_FUNC::KEYS_INVENTORY packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void keysInventory(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + /** + * @brief Handles NET_FUNC::KEYS_UPDATE packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void keysUpdate(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + + /** + * @brief Handles NET_FUNC::REPL packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void replication(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + + /** + * @brief Handles NET_FUNC::NET_TREE packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void networkTree(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + }; + /** * @brief Entry point to process a given network packet. * @param req Instance of the NetPacketRequest structure. diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index c3803337..8c7ad202 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -8,11 +8,8 @@ * */ #include "fne/Defines.h" -#include "common/edac/SHA256.h" #include "common/p25/kmm/KMMFactory.h" #include "common/json/json.h" -#include "common/zlib/Compression.h" -#include "common/AESCrypto.h" #include "common/Log.h" #include "common/StopWatch.h" #include "common/Utils.h" @@ -28,22 +25,16 @@ using namespace network; using namespace network::callhandler; -using namespace compress; #include -#include -#include #include #include -#include #include -#include // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- -const uint32_t MAX_HARD_CONN_CAP = 250U; const uint8_t MAX_PEER_LIST_BEFORE_FLUSH = 10U; const uint32_t MAX_RID_LIST_CHUNK = 50U; @@ -53,8 +44,6 @@ const uint64_t PACKET_LATE_TIME = 250U; // 250ms const uint32_t FIXED_HA_UPDATE_INTERVAL = 30U; // 30s -const size_t PEER_STATE_LOCK_STRIPES = 256U; - // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -62,21 +51,7 @@ const size_t PEER_STATE_LOCK_STRIPES = 256U; std::timed_mutex TrafficNetwork::s_keyQueueMutex; std::timed_mutex TrafficNetwork::s_llaKeyQueueMutex; -std::array s_peerStateLocks; - -// --------------------------------------------------------------------------- -// Global Functions -// --------------------------------------------------------------------------- - -/** - * @brief Gets the mutex for a specific peer ID. - * @param peerId The ID of the peer. - * @return A reference to the mutex associated with the peer ID. - */ -inline std::mutex& getPeerStateLock(uint32_t peerId) -{ - return s_peerStateLocks[peerId % PEER_STATE_LOCK_STRIPES]; -} +std::array TrafficNetwork::s_peerStateLocks; // --------------------------------------------------------------------------- // Public Class Members @@ -1087,1134 +1062,37 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) } // process incoming message function opcodes - switch (req->fneHeader.getFunction()) { - case NET_FUNC::PROTOCOL: // Protocol - { - // process incoming message subfunction opcodes - switch (req->fneHeader.getSubFunction()) { - case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - connection->lastPing(now); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_dmrEnabled) { - if (network->m_tagDMR != nullptr) { - // check if jitter buffer is enabled for this peer - if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - std::vector readyFrames; - - connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); - - // process all frames that are now ready (in sequence order) - for (BufferedFrame* frame : readyFrames) { - network->m_tagDMR->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); - delete frame; - } - } else { - // zero-latency fast path: no jitter buffer - network->m_tagDMR->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); - } - } - } else { - network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - } - } - else { - network->writePeerNAK(peerId, TAG_DMR_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - break; + static const std::unordered_map handlers = { + { NET_FUNC::PROTOCOL, &TrafficNetwork::PacketHandler::protocol }, - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - connection->lastPing(now); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_p25Enabled) { - if (network->m_tagP25 != nullptr) { - // check if jitter buffer is enabled for this peer - if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - std::vector readyFrames; - - connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); - - // process all frames that are now ready (in sequence order) - for (BufferedFrame* frame : readyFrames) { - network->m_tagP25->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); - delete frame; - } - } else { - // zero-latency fast path: no jitter buffer - network->m_tagP25->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); - } - } - } else { - network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - } - } - else { - network->writePeerNAK(peerId, TAG_P25_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - break; + { NET_FUNC::RPTL, &TrafficNetwork::PacketHandler::repeaterLogin }, + { NET_FUNC::RPTK, &TrafficNetwork::PacketHandler::repeaterAuth }, + { NET_FUNC::RPTC, &TrafficNetwork::PacketHandler::repeaterConfig }, + { NET_FUNC::RPT_DISC, &TrafficNetwork::PacketHandler::repeaterDisconnect }, - case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - connection->lastPing(now); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_nxdnEnabled) { - if (network->m_tagNXDN != nullptr) { - // check if jitter buffer is enabled for this peer - if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - std::vector readyFrames; - - connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); - - // process all frames that are now ready (in sequence order) - for (BufferedFrame* frame : readyFrames) { - network->m_tagNXDN->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); - delete frame; - } - } else { - // zero-latency fast path: no jitter buffer - network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); - } - } - } else { - network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - } - } - else { - network->writePeerNAK(peerId, TAG_NXDN_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - break; + { NET_FUNC::PING, &TrafficNetwork::PacketHandler::ping }, - case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - connection->lastPing(now); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_analogEnabled) { - if (network->m_tagAnalog != nullptr) { - // check if jitter buffer is enabled for this peer - if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - std::vector readyFrames; - - connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); - - // process all frames that are now ready (in sequence order) - for (BufferedFrame* frame : readyFrames) { - network->m_tagAnalog->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); - delete frame; - } - } else { - // zero-latency fast path: no jitter buffer - network->m_tagAnalog->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); - } - } - } else { - network->writePeerNAK(peerId, streamId, TAG_ANALOG_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - } - } - else { - network->writePeerNAK(peerId, TAG_ANALOG_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - break; - - default: - Utils::dump("Unknown protocol opcode from peer", req->buffer, req->length); - break; - } - } - break; + { NET_FUNC::GRANT_REQ, &TrafficNetwork::PacketHandler::grantRequest }, - case NET_FUNC::RPTL: // Repeater/Peer Login - { - std::lock_guard peerGuard(getPeerStateLock(peerId)); + { NET_FUNC::INCALL_CTRL, &TrafficNetwork::PacketHandler::inCallControl }, - if (peerId > 0 && (network->m_peers.find(peerId) == network->m_peers.end())) { - if (network->m_peers.size() >= MAX_HARD_CONN_CAP) { - LogError(LOG_MASTER, "PEER %u attempted to connect with no more connections available, currConnections = %u", peerId, network->m_peers.size()); - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_MAX_CONN, req->address, req->addrLen); - break; - } - - if (network->m_softConnLimit > 0U && network->m_peers.size() >= network->m_softConnLimit) { - LogError(LOG_MASTER, "PEER %u attempted to connect with no more connections available, maxConnections = %u, currConnections = %u", peerId, network->m_softConnLimit, network->m_peers.size()); - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_MAX_CONN, req->address, req->addrLen); - break; - } - - FNEPeerConnection* connection = new FNEPeerConnection(peerId, req->address, req->addrLen); - connection->lastPing(now); + { NET_FUNC::KEY_REQ, &TrafficNetwork::PacketHandler::keyRequest }, + { NET_FUNC::KEY_LLA_REQ, &TrafficNetwork::PacketHandler::llaKeyRequest }, + }; - network->applyJitterBufferConfig(peerId, connection); - network->setupRepeaterLogin(peerId, streamId, connection); + uint8_t func = req->fneHeader.getFunction(); + auto it = handlers.find(func); + if (it != handlers.end()) { + it->second(network, req, peerId, ssrc, streamId, now); - // check if the peer is in the peer ACL list - if (network->m_peerListLookup->getACL()) { - if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); - } - - if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "PEER %u RPTL, failed peer ACL check", peerId); - - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - } - } - else { - // check if the peer is in our peer list -- if he is, and he isn't in a running state, reset - // the login sequence - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - if (connection->connectionState() == NET_STAT_RUNNING) { - LogInfoEx(LOG_MASTER, "PEER %u (%s) resetting peer connection, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - connection->connectionState()); - network->disconnectPeer(peerId, connection); - - connection = new FNEPeerConnection(peerId, req->address, req->addrLen); - connection->lastPing(now); - - network->applyJitterBufferConfig(peerId, connection); - network->erasePeerAffiliations(peerId); - network->setupRepeaterLogin(peerId, streamId, connection); - - // check if the peer is in the peer ACL list - if (network->m_peerListLookup->getACL()) { - if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); - } - - if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "PEER %u RPTL, failed peer ACL check", peerId); - - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - } - } else { - // perform source address validation - if (connection->address() != udp::Socket::address(req->address)) { - LogError(LOG_MASTER, "PEER %u RPTL NAK, IP address mismatch on RPTL attempt while not running, old = %s:%u, new = %s:%u, connectionState = %u", peerId, - connection->address().c_str(), connection->port(), udp::Socket::address(req->address).c_str(), udp::Socket::port(req->address), connection->connectionState()); - - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - break; - } - - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - - LogWarning(LOG_MASTER, "PEER %u (%s) RPTL NAK, bad connection state, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - connection->connectionState()); - network->disconnectPeer(peerId, connection); - } - } else { - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->erasePeer(peerId); - LogWarning(LOG_MASTER, "PEER %u RPTL NAK, having no connection", peerId); - } - } - } + if (func == NET_FUNC::ANNOUNCE) { + // bryanb: temporary support to allow announce packets on the traffic port but ultimately + // this should be removed and handled like TRANSFER is handled here + network->m_host->m_mdNetwork->taskNetworkRx(req); + return; // don't break, return because taskNetworkRx will cleanup req } - break; - case NET_FUNC::RPTK: // Repeater/Peer Authentication - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - std::lock_guard peerGuard(getPeerStateLock(peerId)); - - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - connection->lastPing(now); - - if (connection->connectionState() == NET_STAT_WAITING_AUTHORISATION) { - // get the hash from the frame message - DECLARE_UINT8_ARRAY(hash, req->length - 8U); - ::memcpy(hash, req->buffer + 8U, req->length - 8U); - - // generate our own hash - uint8_t salt[4U]; - ::memset(salt, 0x00U, 4U); - SET_UINT32(connection->salt(), salt, 0U); - - std::string passwordForPeer = network->m_password; - - // check if the peer is in the peer ACL list - bool validAcl = true; - if (network->m_peerListLookup->getACL()) { - if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); - validAcl = false; - } else { - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check - } else { - passwordForPeer = peerEntry.peerPassword(); - if (passwordForPeer.length() == 0) { - passwordForPeer = network->m_password; - } - } - } - - if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); - validAcl = true; - } - } - - if (validAcl) { - size_t size = passwordForPeer.size(); - uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; - ::memcpy(in, salt, sizeof(uint32_t)); - for (size_t i = 0U; i < size; i++) - in[i + sizeof(uint32_t)] = passwordForPeer.at(i); - - uint8_t out[32U]; - edac::SHA256 sha256; - sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); - - delete[] in; - - // validate hash - bool validHash = false; - if (req->length - 8U == 32U) { - validHash = true; - for (uint8_t i = 0; i < 32U; i++) { - if (hash[i] != out[i]) { - validHash = false; - break; - } - } - } - - if (validHash) { - connection->connectionState(NET_STAT_WAITING_CONFIG); - network->writePeerACK(peerId, streamId); - LogInfoEx(LOG_MASTER, "PEER %u RPTK ACK, completed the login exchange", peerId); - network->m_peers[peerId] = connection; - } - else { - LogWarning(LOG_MASTER, "PEER %u RPTK NAK, failed the login exchange", peerId); - network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - } else { - network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - } - else { - // perform source address/port validation - if (connection->address() != udp::Socket::address(req->address) || - connection->port() != udp::Socket::port(req->address)) { - LogError(LOG_MASTER, "PEER %u RPTK NAK, IP address/port mismatch on RPTK attempt while in an incorrect state, old = %s:%u, new = %s:%u, connectionState = %u", peerId, - connection->address().c_str(), connection->port(), udp::Socket::address(req->address).c_str(), udp::Socket::port(req->address), connection->connectionState()); - - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - break; - } - - LogWarning(LOG_MASTER, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState()); - network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - } - } - else { - network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->erasePeer(peerId); - LogWarning(LOG_MASTER, "PEER %u RPTK NAK, having no connection", peerId); - } - } - break; - case NET_FUNC::RPTC: // Repeater/Peer Configuration - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - std::lock_guard peerGuard(getPeerStateLock(peerId)); - - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - connection->lastPing(now); - - if (connection->connectionState() == NET_STAT_WAITING_CONFIG) { - DECLARE_UINT8_ARRAY(rawPayload, req->length - 8U); - ::memcpy(rawPayload, req->buffer + 8U, req->length - 8U); - std::string payload(rawPayload, rawPayload + (req->length - 8U)); - - // parse JSON body - json::value v; - std::string err = json::parse(v, payload); - if (!err.empty()) { - LogWarning(LOG_MASTER, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); - network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - else { - // ensure parsed JSON is an object - if (!v.is()) { - LogWarning(LOG_MASTER, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); - network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - else { - connection->config(v.get()); - connection->connectionState(NET_STAT_RUNNING); - connection->connected(true); - connection->pingsReceived(0U); - connection->lastPing(now); - connection->missedMetadataUpdates(0U); - - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (!peerEntry.peerDefault()) { - if (peerEntry.hasCallPriority()) { - connection->hasCallPriority(peerEntry.hasCallPriority()); - LogInfoEx(LOG_MASTER, "PEER %u >> Has Call Priority", peerId); - } - } - - network->m_peers[peerId] = connection; - - // attach extra notification data to the RPTC ACK to notify the peer of - // the use of the alternate diagnostic port - uint8_t buffer[1U]; - buffer[0U] = 0x80U; // this should really be a defined constant -- but - // because this is the only option and its *always* sent now - // we can just hardcode this for now - - json::object peerConfig = connection->config(); - - std::string identity = "* UNK *"; - if (peerConfig["identity"].is()) { - identity = peerConfig["identity"].getDefault("* UNK *"); - connection->identity(identity); - LogInfoEx(LOG_MASTER, "PEER %u >> Identity [%8s]", peerId, identity.c_str()); - } - - if (peerConfig["software"].is()) { - std::string software = peerConfig["software"].get(); - LogInfoEx(LOG_MASTER, "PEER %u >> Software Version [%s]", peerId, software.c_str()); - } - - /* - ** bryanb: this is support for older configuration structs, newer peers should - ** send the peerClass value - */ - { - // is the peer reporting it is a SysView peer? - if (peerConfig["sysView"].is()) { - bool sysView = peerConfig["sysView"].get(); - if (sysView) - connection->peerClass(PEER_CONN_CLASS_SYSVIEW); - if (sysView) - LogInfoEx(LOG_MASTER, "PEER %u >> SysView Peer", peerId); - } - - // is the peer reporting it is a downstream FNE neighbor peer? - if (peerConfig["externalPeer"].is()) { - bool externalPeer = peerConfig["externalPeer"].get(); - if (externalPeer) - connection->peerClass(PEER_CONN_CLASS_NEIGHBOR); - } - } - - // determine the peer class - if (peerConfig["peerClass"].is()) { - uint32_t peerClass = peerConfig["peerClass"].get(); - if (peerClass >= PEER_CONN_CLASS_INVALID) - peerClass = PEER_CONN_CLASS_STANDARD; - connection->peerClass((PEER_CONN_CLASS)peerClass); - } else { - if (connection->peerClass() == PEER_CONN_CLASS_UNKNOWN) { - connection->peerClass(PEER_CONN_CLASS_STANDARD); - } - } - - // is the peer reporting it is a conventional peer? - if (peerConfig["conventionalPeer"].is()) { - if (network->m_allowConvSiteAffOverride) { - bool convPeer = peerConfig["conventionalPeer"].get(); - connection->isConventional(convPeer); - } else { - bool conventionalPeer = false; - peerConfig["conventionalPeer"].set(conventionalPeer); - } - } else { - bool conventionalPeer = false; - peerConfig["conventionalPeer"].set(conventionalPeer); - } - - // report peer class in log - switch (connection->peerClass()) { - case PEER_CONN_CLASS_NEIGHBOR: - LogInfoEx(LOG_MASTER, "PEER %u >> Downstream Neighbor FNE Peer", peerId); - break; - case PEER_CONN_CLASS_SYSVIEW: - LogInfoEx(LOG_MASTER, "PEER %u >> SysView Peer", peerId); - connection->isReplica(true); - break; - case PEER_CONN_CLASS_CONSOLE: - LogInfoEx(LOG_MASTER, "PEER %u >> Console Peer", peerId); - break; - case PEER_CONN_CLASS_STANDARD: - default: - if (connection->isConventional()) - LogInfoEx(LOG_MASTER, "PEER %u >> Conventional Peer", peerId); - else - LogInfoEx(LOG_MASTER, "PEER %u >> Standard Peer", peerId); - break; - } - - // for the purposes of maintaining backward compat with REST API users, - // we will report the peer class via the old externalPeer and sysView boolean - // fields as well (consolePeer doesn't have a legacy field, but that's ok since - // it's only used for console connections which should be new enough to support - // the new peerClass field) - bool externalPeer = connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR; - peerConfig["externalPeer"].set(externalPeer); - bool sysViewPeer = connection->peerClass() == PEER_CONN_CLASS_SYSVIEW; - peerConfig["sysView"].set(sysViewPeer); - bool consolePeer = connection->peerClass() == PEER_CONN_CLASS_CONSOLE; - peerConfig["consolePeer"].set(consolePeer); - connection->config(peerConfig); - - // is the peer reporting it is an downstream FNE neighbor peer? - /* - ** bryanb: don't change externalPeer to neighborPeer -- this will break backward - ** compat with older FNE versions (we're stuck with this naming :() - */ - if (connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR) { - uint32_t masterPeerId = 0U; - if (peerConfig["masterPeerId"].is()) { - masterPeerId = peerConfig["masterPeerId"].get(); - connection->masterId(masterPeerId); - LogInfoEx(LOG_MASTER, "PEER %u >> Master Peer ID [%u]", peerId, masterPeerId); - } - - // master peer ID should never be zero for an neighbor peer -- use the peer ID instead - if (masterPeerId == 0U) { - LogWarning(LOG_MASTER, "PEER %u reports to be a downstream FNE neighbor peer but has not supplied a valid masterPeerId, using own peerId as masterPeerId (old FNE perhaps?)", peerId); - masterPeerId = peerId; - } - - // check if the peer a peer replication participant - lookups::PeerId peerEntry = network->m_peerListLookup->find(req->peerId); - if (!peerEntry.peerDefault()) { - if (peerEntry.peerReplica()) { - connection->isReplica(true); - LogInfoEx(LOG_MASTER, "PEER %u >> Participates in Peer Replication", peerId); - } - } - - if (network->m_enableSpanningTree) { - network->m_treeLock.lock(); - - // check if this peer is already connected via another peer - SpanningTree* tree = SpanningTree::findByMasterID(masterPeerId); - if (tree != nullptr) { - // are we allowing a fast reconnect? (this happens when a connecting peer - // uses the same peer ID and master ID already announced in the tree, but - // the tree entry wasn't yet erased) - if ((tree->id() == peerId && tree->masterId() == masterPeerId) && - network->m_spanningTreeFastReconnect) { - LogWarning(LOG_STP, "PEER %u (%s) server already announced in server tree, fast peer reconnect, peerId = %u, masterId = %u, treePeerId = %u, treeMasterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - peerId, masterPeerId, tree->id(), tree->masterId(), connection->connectionState()); - if (identity != tree->identity()) { - LogWarning(LOG_STP, "PEER %u (%s) why has this server's announced identity changed? *big hmmmm*", peerId, connection->identWithQualifier().c_str()); - } - SpanningTree::moveParent(tree, network->m_treeRoot); - network->logSpanningTree(connection); - } else { - LogWarning(LOG_STP, "PEER %u (%s) RPTC NAK, server already connected via PEER %u, duplicate connection denied, peerId = %u, masterId = %u, treePeerId = %u, treeMasterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - peerId, masterPeerId, tree->id(), tree->masterId(), tree->id(), connection->connectionState()); - network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_FNE_DUPLICATE_CONN, req->address, req->addrLen); - network->m_treeLock.unlock(); - network->disconnectPeer(peerId, connection); - break; - } - } else { - SpanningTree* node = new SpanningTree(peerId, masterPeerId, network->m_treeRoot); - node->identity(identity); - network->logSpanningTree(connection); - } - - network->m_treeLock.unlock(); - } - } - - network->writePeerACK(peerId, streamId, buffer, 1U); - LogInfoEx(LOG_MASTER, "PEER %u RPTC ACK, completed the configuration exchange", peerId); - - // setup the affiliations list for this peer - std::stringstream peerName; - peerName << "PEER " << peerId; - network->createPeerAffiliations(peerId, peerName.str()); - - // spin up a thread and send metadata over to peer - network->peerMetadataUpdate(peerId); - } - } - } - else { - // perform source address/port validation - if (connection->address() != udp::Socket::address(req->address) || - connection->port() != udp::Socket::port(req->address)) { - LogError(LOG_MASTER, "PEER %u (%s) RPTC NAK, IP address/port mismatch on RPTC attempt while in an incorrect state, old = %s:%u, new = %s:%u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - connection->address().c_str(), connection->port(), udp::Socket::address(req->address).c_str(), udp::Socket::port(req->address), connection->connectionState()); - - network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - break; - } - - LogWarning(LOG_MASTER, "PEER %u (%s) RPTC NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - connection->connectionState()); - network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->disconnectPeer(peerId, connection); - } - } - } - else { - network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - LogWarning(LOG_MASTER, "PEER %u RPTC NAK, having no connection", peerId); - } - } - break; - - case NET_FUNC::RPT_DISC: // Repeater/Peer Disconnect - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - std::lock_guard peerGuard(getPeerStateLock(peerId)); - - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - LogInfoEx(LOG_MASTER, "PEER %u (%s) disconnected", peerId, connection->identWithQualifier().c_str()); - network->disconnectPeer(peerId, connection); - } - } - } - } - break; - case NET_FUNC::PING: // Ping - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - uint32_t pingsRx = connection->pingsReceived(); - uint64_t lastPing = connection->lastPing(); - pingsRx++; - - connection->pingsReceived(pingsRx); - connection->lastPing(now); - - uint8_t payload[8U]; - ::memset(payload, 0x00U, 8U); - - // split ulong64_t (8 byte) value into bytes - payload[0U] = (uint8_t)((now >> 56) & 0xFFU); - payload[1U] = (uint8_t)((now >> 48) & 0xFFU); - payload[2U] = (uint8_t)((now >> 40) & 0xFFU); - payload[3U] = (uint8_t)((now >> 32) & 0xFFU); - payload[4U] = (uint8_t)((now >> 24) & 0xFFU); - payload[5U] = (uint8_t)((now >> 16) & 0xFFU); - payload[6U] = (uint8_t)((now >> 8) & 0xFFU); - payload[7U] = (uint8_t)((now >> 0) & 0xFFU); - - network->writePeerCommand(peerId, { NET_FUNC::PONG, NET_SUBFUNC::NOP }, payload, 8U, streamId, false); - - if (network->m_reportPeerPing) { - LogInfoEx(LOG_MASTER, "PEER %u (%s) ping, pingsReceived = %u, lastPing = %u, now = %u", peerId, connection->identWithQualifier().c_str(), - connection->pingsReceived(), lastPing, now); - } - - // ensure STP sanity, when we receive a ping from a downstream leaf - // this check ensures a STP entry for a downstream leaf isn't accidentally blown off - // the tree during a fast reconnect - if (network->m_enableSpanningTree && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR) { - std::lock_guard guard(network->m_treeLock); - - if ((connection->masterId() != peerId) && (connection->masterId() != 0U)) { - // check if this peer is already connected via another peer - SpanningTree* tree = SpanningTree::findByMasterID(connection->masterId()); - if (tree == nullptr) { - LogWarning(LOG_STP, "PEER %u (%s) downstream server not announced in server tree, reinitializing STP entry, this is abnormal, peerId = %u, masterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), - peerId, connection->masterId(), connection->connectionState()); - SpanningTree* node = new SpanningTree(peerId, connection->masterId(), network->m_treeRoot); - node->identity(connection->identity()); - network->logSpanningTree(connection); - } - } - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_REPEATER_PING); - } - } - } - } - break; - - case NET_FUNC::GRANT_REQ: // Grant Request - { - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - uint32_t srcId = GET_UINT24(req->buffer, 11U); // Source Address - uint32_t dstId = GET_UINT24(req->buffer, 15U); // Destination Address - - uint8_t slot = req->buffer[19U]; - - bool unitToUnit = (req->buffer[19U] & 0x80U) == 0x80U; - - DVM_STATE state = (DVM_STATE)req->buffer[20U]; // DVM Mode State - switch (state) { - case STATE_DMR: - if (network->m_dmrEnabled) { - if (network->m_tagDMR != nullptr) { - network->m_tagDMR->processGrantReq(srcId, dstId, slot, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); - } else { - network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - break; - case STATE_P25: - if (network->m_p25Enabled) { - if (network->m_tagP25 != nullptr) { - network->m_tagP25->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); - } else { - network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - break; - case STATE_NXDN: - if (network->m_nxdnEnabled) { - if (network->m_tagNXDN != nullptr) { - network->m_tagNXDN->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); - } else { - network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); - } - } - break; - default: - network->writePeerNAK(peerId, streamId, TAG_REPEATER_GRANT, NET_CONN_NAK_ILLEGAL_PACKET); - Utils::dump("Unknown state for grant request from the peer", req->buffer, req->length); - break; - } - } - } - } - } - break; - - case NET_FUNC::INCALL_CTRL: // In-Call Control - { - if (network->m_disallowInCallCtrl) - break; - - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - NET_ICC::ENUM command = (NET_ICC::ENUM)req->buffer[10U]; - uint32_t dstId = GET_UINT24(req->buffer, 11U); - uint8_t slot = req->buffer[14U]; - - network->processInCallCtrl(command, req->fneHeader.getSubFunction(), dstId, slot, peerId, ssrc, streamId); - } - } - } - } - break; - - case NET_FUNC::KEY_REQ: // Enc. Key Request - { - using namespace p25::defines; - using namespace p25::kmm; - - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - // is this peer allowed to request keys? - if (network->m_peerListLookup->getACL()) { - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - break; - } else { - if (!peerEntry.canRequestKeys()) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identWithQualifier().c_str()); - break; - } - } - } - - std::unique_ptr frame = KMMFactory::create(req->buffer + 11U); - if (frame == nullptr) { - LogWarning(LOG_MASTER, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identWithQualifier().c_str()); - break; - } - - switch (frame->getMessageId()) { - case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: - { - KMMModifyKey* modifyKey = static_cast(frame.get()); - if (modifyKey->getAlgId() > 0U && modifyKey->getKId() > 0U) { - uint32_t requestingRid = modifyKey->getSrcLLId(); - - if (requestingRid > 0U) { - lookups::RadioId ridEntry = network->m_ridLookup->find(requestingRid); - if (ridEntry.radioDefault()) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but RID %u has no key policy entry, no response", - peerId, connection->identWithQualifier().c_str(), requestingRid); - break; - } - - if (!ridEntry.radioEnabled()) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but RID %u is disabled, no response", - peerId, connection->identWithQualifier().c_str(), requestingRid); - break; - } - - if (!ridEntry.canRequestKeys()) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but RID %u cannot request keys, no response", - peerId, connection->identWithQualifier().c_str(), requestingRid); - break; - } - - std::vector allowedKIds = ridEntry.allowedKIds(); - - // check if this RID is allowed to request the KID in question - if (!allowedKIds.empty() && std::find(allowedKIds.begin(), allowedKIds.end(), modifyKey->getKId()) == allowedKIds.end()) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key kID = $%04X but RID %u is not permitted for that key, no response", - peerId, connection->identWithQualifier().c_str(), modifyKey->getKId(), requestingRid); - break; - } - } - else { - if (!network->m_kmfAllowRID0 && connection->peerClass() != PEER_CONN_CLASS_NEIGHBOR) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key with RID 0 but such requests are not allowed, no response", - peerId, connection->identWithQualifier().c_str()); - break; - } - } - - LogInfoEx(LOG_MASTER, "PEER %u (%s) requested enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), modifyKey->getKId()); - ::EKCKeyItem keyItem = network->m_cryptoLookup->find(modifyKey->getKId()); - if (!keyItem.isInvalid()) { - uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; - ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - uint8_t keyLength = keyItem.getKey(key); - - if (network->m_debug) { - LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); - Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - } - - LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), modifyKey->getKId()); - - // if configured to encrypt the key with a preshared key, do that now - if (network->m_kmfEncKeyRequest) { - uint8_t* encryptedKey = nullptr; - if (network->m_kmfPresharedKey != nullptr) { - crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); - encryptedKey = aes.encryptECB(key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); - - if (encryptedKey != nullptr) { - ::memcpy(key, encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; - delete[] encryptedKey; - } - } - - if (network->m_debug) { - LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); - Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Encrypted Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - } - } - - // build response buffer - uint8_t buffer[DATA_PACKET_LENGTH]; - ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); - - KMMModifyKey modifyKeyRsp = KMMModifyKey(); - modifyKeyRsp.setDecryptInfoFmt(network->m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); - modifyKeyRsp.setAlgId(modifyKey->getAlgId()); - modifyKeyRsp.setKId(0U); - - KeysetItem ks = KeysetItem(); - ks.keysetId(1U); - ks.algId(modifyKey->getAlgId()); - ks.keyLength(keyLength); - - p25::kmm::KeyItem ki = p25::kmm::KeyItem(); - ki.keyFormat(KEY_FORMAT_TEK); - ki.kId((uint16_t)keyItem.kId()); - ki.sln((uint16_t)keyItem.sln()); - ki.setKey(key, keyLength); - - ks.push_back(ki); - modifyKeyRsp.setKeysetItem(ks); - - modifyKeyRsp.encode(buffer + 11U); - - network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, - RTP_END_OF_CALL_SEQ, network->createStreamId()); - } else { - // attempt to forward KMM key request to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), modifyKey->getKId()); - - bool locked = network->s_keyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); - network->m_peerReplicaKeyQueue[peerId] = modifyKey->getKId(); - - if (locked) - network->s_keyQueueMutex.unlock(); - - peer.second->writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false); - } - } - } - } else { - LogError(LOG_MASTER, "PEER %u (%s) no local key or container and no replica masters to forward request to, no response, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), modifyKey->getKId()); - } - } - } - } - break; - - default: - break; - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_REPEATER_KEY, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_FUNC::KEY_LLA_REQ: // LLA Enc. Key Request - { - using namespace p25::defines; - using namespace p25::kmm; - - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { - FNEPeerConnection* connection = network->m_peers[peerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - // is this peer allowed to request keys? - if (network->m_peerListLookup->getACL()) { - lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); - if (peerEntry.peerDefault()) { - break; - } else { - if (!peerEntry.canRequestKeys()) { - LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identWithQualifier().c_str()); - break; - } - } - } - - std::unique_ptr frame = KMMFactory::create(req->buffer + 11U); - if (frame == nullptr) { - LogWarning(LOG_MASTER, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identWithQualifier().c_str()); - break; - } - - switch (frame->getMessageId()) { - case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: - { - KMMModifyKey* modifyKey = static_cast(frame.get()); - - if (modifyKey->getAlgId() == ALGO_AES_128 && modifyKey->getDstLLId() > 0U) { - if (network->m_debug) - LogDebugEx(LOG_MASTER, "TrafficNetwork::taskNetworkRx()", "PEER %u (%s) LLA enc. key request received, dstLLId = %u, algId = %u, kId = %u", peerId, connection->identWithQualifier().c_str(), - modifyKey->getDstLLId(), modifyKey->getAlgId(), modifyKey->getKId()); - - uint32_t requestingRid = modifyKey->getDstLLId(); - - LogInfoEx(LOG_MASTER, "PEER %u (%s) requested LLA enc. key, rsi = %u", peerId, connection->identWithQualifier().c_str(), - requestingRid); - - ::EKCKeyItem keyItem = network->m_cryptoLookup->findLLA(requestingRid); - if (!keyItem.isInvalid()) { - uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; - ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - uint8_t keyLength = keyItem.getKey(key); - - if (network->m_debug) { - LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); - Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - } - - LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, rsi = %u", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), requestingRid); - - // if configured to encrypt the key with a preshared key, do that now - if (network->m_kmfEncKeyRequest) { - uint8_t* encryptedKey = nullptr; - if (network->m_kmfPresharedKey != nullptr) { - crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); - encryptedKey = aes.encryptECB(key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); - - if (encryptedKey != nullptr) { - ::memcpy(key, encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; - delete[] encryptedKey; - } - } - - if (network->m_debug) { - LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); - Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Encrypted Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - } - } - - // build response buffer - uint8_t buffer[DATA_PACKET_LENGTH]; - ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); - - KMMModifyKey modifyKeyRsp = KMMModifyKey(); - modifyKeyRsp.setDecryptInfoFmt(network->m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); - modifyKeyRsp.setAlgId(modifyKey->getAlgId()); - modifyKeyRsp.setKId(0U); - modifyKeyRsp.setSrcLLId(WUID_FNE); - modifyKeyRsp.setDstLLId(requestingRid); - - KeysetItem ks = KeysetItem(); - ks.keysetId(1U); - ks.algId(modifyKey->getAlgId()); - ks.keyLength(keyLength); - - p25::kmm::KeyItem ki = p25::kmm::KeyItem(); - ki.keyFormat(KEY_FORMAT_TEK); - ki.kId((uint16_t)keyItem.kId()); - ki.sln((uint16_t)keyItem.sln()); - ki.setKey(key, keyLength); - - ks.push_back(ki); - modifyKeyRsp.setKeysetItem(ks); - - modifyKeyRsp.encode(buffer + 11U); - - network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_LLA_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, - RTP_END_OF_CALL_SEQ, network->createStreamId()); - } else { - // attempt to forward KMM key request to replica masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto& peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isReplica()) { - LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, rsi = %u", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), requestingRid); - - bool locked = network->s_llaKeyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); - network->m_peerReplicaLLAKeyQueue[peerId] = modifyKey->getDstLLId(); - - if (locked) - network->s_llaKeyQueueMutex.unlock(); - - peer.second->writeMaster({ NET_FUNC::KEY_LLA_REQ, NET_SUBFUNC::NOP }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false); - } - } - } - } else { - LogError(LOG_MASTER, "PEER %u (%s) requested LLA enc. key with no local key and no upstream masters to query, algId = $%02X, rsi = %u, no response", peerId, connection->identWithQualifier().c_str(), - modifyKey->getAlgId(), requestingRid); - } - } - } - } - break; - - default: - break; - } - } - else { - network->writePeerNAK(peerId, streamId, TAG_REPEATER_KEY, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - break; - - case NET_FUNC::TRANSFER: // Transfer - // transfer command is not supported for performance reasons on the main traffic port - break; - case NET_FUNC::ANNOUNCE: // Announce - // bryanb: temporary support to allow announce packets on the traffic port but ultimately - // this should be removed and handled like TRANSFER is handled here - network->m_host->m_mdNetwork->taskNetworkRx(req); - return; // don't break, return because taskNetworkRx will cleanup req - - default: + } else { Utils::dump("Unknown opcode from the peer", req->buffer, req->length); - break; } } diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index 2887f622..27068260 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -48,6 +48,7 @@ #include #include #include +#include // --------------------------------------------------------------------------- // Class Prototypes @@ -95,6 +96,9 @@ namespace network #define INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS "illegal/unknown RID attempted access" #define INFLUXDB_ERRSTR_CALL_NOT_PERMITTED "call not permitted for talkgroup" + const uint32_t MAX_HARD_CONN_CAP = 250U; + const size_t PEER_STATE_LOCK_STRIPES = 256U; + // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- @@ -452,12 +456,139 @@ namespace network bool m_reportPeerPing; bool m_verbose; + static std::array s_peerStateLocks; + + /** + * @brief Gets the mutex for a specific peer ID. + * @param peerId The ID of the peer. + * @return A reference to the mutex associated with the peer ID. + */ + static std::mutex& getPeerStateLock(uint32_t peerId) + { + return s_peerStateLocks[peerId % PEER_STATE_LOCK_STRIPES]; + } + /** * @brief Entry point to parrot handler thread. * @param arg Instance of the thread_t structure. * @returns void* (Ignore) */ static void* threadParrotHandler(void* arg); + + using PacketHandlerFunc = void (*)(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + + /** + * @brief Implements the packet handler functions for the MetadataNetwork class. + */ + class PacketHandler { + public: + /** + * @brief Handles NET_FUNC::PROTOCOL packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void protocol(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + + /** + * @brief Handles NET_FUNC::RPTL packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void repeaterLogin(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + /** + * @brief Handles NET_FUNC::RPTK packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void repeaterAuth(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + /** + * @brief Handles NET_FUNC::RPTC packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void repeaterConfig(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + /** + * @brief Handles NET_FUNC::RPT_DISC packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void repeaterDisconnect(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + + /** + * @brief Handles NET_FUNC::PING packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void ping(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + + /** + * @brief Handles NET_FUNC::GRANT_REQ packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void grantRequest(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + + /** + * @brief Handles NET_FUNC::INCALL_CTRL packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void inCallControl(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + + /** + * @brief Handles NET_FUNC::KEY_REQ packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void keyRequest(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + /** + * @brief Handles NET_FUNC::KEY_LLA_REQ packets. + * @param network Instance of the TrafficNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + */ + static void llaKeyRequest(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); + }; + /** * @brief Entry point to process a given network packet. * @param req Instance of the NetPacketRequest structure. diff --git a/src/fne/network/packet/Announce.cpp b/src/fne/network/packet/Announce.cpp new file mode 100644 index 00000000..1ed47417 --- /dev/null +++ b/src/fne/network/packet/Announce.cpp @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::ANNOUNCE packets. */ + +void MetadataNetwork::PacketHandler::announce(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)mdNetwork; + + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL: // Announce Group Affiliation + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address + uint32_t dstId = GET_UINT24(req->buffer, 3U); // Destination Address + aff->groupUnaff(srcId); + aff->groupAff(srcId, dstId); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG: // Announce Unit Registration + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address + aff->unitReg(srcId, ssrc); + network->m_globalAff->unitReg(srcId, ssrc); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true, 0U, ssrc); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG: // Announce Unit Deregistration + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address + aff->unitDereg(srcId); + network->m_globalAff->unitDereg(srcId); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL: // Announce Group Affiliation Removal + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address + aff->groupUnaff(srcId); + network->m_globalAff->groupUnaff(srcId); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_AFFILS: // Announce Update All Affiliations + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + if (aff != nullptr) { + aff->clearGroupAff(0U, true); + + // update TGID lists + uint32_t len = GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t srcId = GET_UINT24(req->buffer, offs); + uint32_t dstId = GET_UINT24(req->buffer, offs + 4U); + + aff->groupAff(srcId, dstId); + network->m_globalAff->groupAff(srcId, dstId); + offs += 8U; + } + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u affiliations", peerId, connection->identWithQualifier().c_str(), len); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS: // Announce Update All Unit Registrations + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + std::shared_ptr aff = network->getPeerAffiliations(peerId); + if (aff == nullptr) { + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + if (aff != nullptr) { + aff->clearUnitReg(); + + // update unit registration lists + uint32_t len = GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t srcId = GET_UINT24(req->buffer, offs); + + aff->unitReg(srcId, ssrc); + network->m_globalAff->unitReg(srcId, ssrc); + offs += 3U; + } + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u unit registrations", peerId, connection->identWithQualifier().c_str(), len); + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC: // Announce Site VCs + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + std::vector vcPeers; + + // update peer association + uint32_t len = GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t vcPeerId = GET_UINT32(req->buffer, offs); + if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) { + FNEPeerConnection* vcConnection = network->m_peers[vcPeerId]; + if (vcConnection != nullptr) { + vcConnection->ccPeerId(peerId); + vcPeers.push_back(vcPeerId); + } + } + offs += 4U; + } + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u VCs", peerId, connection->identWithQualifier().c_str(), len); + network->m_ccPeerMap[peerId] = vcPeers; + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, true); + } + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + default: + { + LogWarning(LOG_MASTER, "PEER %u, unknown/unsupported announcement opcode %u", peerId, req->fneHeader.getSubFunction()); + if (network->m_debug) + Utils::dump("Unknown/unsupported announcement opcode from the peer", req->buffer, req->length); + } + break; + } +} diff --git a/src/fne/network/packet/GrantRequest.cpp b/src/fne/network/packet/GrantRequest.cpp new file mode 100644 index 00000000..a897a502 --- /dev/null +++ b/src/fne/network/packet/GrantRequest.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "network/callhandler/TagDMRData.h" +#include "network/callhandler/TagP25Data.h" +#include "network/callhandler/TagNXDNData.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; +using namespace network::callhandler; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::GRANT_REQ packets. */ + +void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + uint32_t srcId = GET_UINT24(req->buffer, 11U); // Source Address + uint32_t dstId = GET_UINT24(req->buffer, 15U); // Destination Address + + uint8_t slot = req->buffer[19U]; + + bool unitToUnit = (req->buffer[19U] & 0x80U) == 0x80U; + + DVM_STATE state = (DVM_STATE)req->buffer[20U]; // DVM Mode State + switch (state) { + case STATE_DMR: + if (network->m_dmrEnabled) { + if (network->m_tagDMR != nullptr) { + network->m_tagDMR->processGrantReq(srcId, dstId, slot, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); + } else { + network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + break; + case STATE_P25: + if (network->m_p25Enabled) { + if (network->m_tagP25 != nullptr) { + network->m_tagP25->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); + } else { + network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + break; + case STATE_NXDN: + if (network->m_nxdnEnabled) { + if (network->m_tagNXDN != nullptr) { + network->m_tagNXDN->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); + } else { + network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + break; + default: + network->writePeerNAK(peerId, streamId, TAG_REPEATER_GRANT, NET_CONN_NAK_ILLEGAL_PACKET); + Utils::dump("Unknown state for grant request from the peer", req->buffer, req->length); + break; + } + } + } + } +} diff --git a/src/fne/network/packet/InCallControl.cpp b/src/fne/network/packet/InCallControl.cpp new file mode 100644 index 00000000..f23b32c6 --- /dev/null +++ b/src/fne/network/packet/InCallControl.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::INCALL_CTRL packets. */ + +void TrafficNetwork::PacketHandler::inCallControl(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + if (network->m_disallowInCallCtrl) + return; + + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + NET_ICC::ENUM command = (NET_ICC::ENUM)req->buffer[10U]; + uint32_t dstId = GET_UINT24(req->buffer, 11U); + uint8_t slot = req->buffer[14U]; + + network->processInCallCtrl(command, req->fneHeader.getSubFunction(), dstId, slot, peerId, ssrc, streamId); + } + } + } +} diff --git a/src/fne/network/packet/KeyRequest.cpp b/src/fne/network/packet/KeyRequest.cpp new file mode 100644 index 00000000..2c2629a5 --- /dev/null +++ b/src/fne/network/packet/KeyRequest.cpp @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/p25/kmm/KMMFactory.h" +#include "common/AESCrypto.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::KEY_REQ packets. */ + +void TrafficNetwork::PacketHandler::keyRequest(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + using namespace p25::defines; + using namespace p25::kmm; + + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + // is this peer allowed to request keys? + if (network->m_peerListLookup->getACL()) { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + return; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identWithQualifier().c_str()); + return; + } + } + } + + std::unique_ptr frame = KMMFactory::create(req->buffer + 11U); + if (frame == nullptr) { + LogWarning(LOG_MASTER, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identWithQualifier().c_str()); + return; + } + + switch (frame->getMessageId()) { + case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: + { + KMMModifyKey* modifyKey = static_cast(frame.get()); + if (modifyKey->getAlgId() > 0U && modifyKey->getKId() > 0U) { + uint32_t requestingRid = modifyKey->getSrcLLId(); + + if (requestingRid > 0U) { + lookups::RadioId ridEntry = network->m_ridLookup->find(requestingRid); + if (ridEntry.radioDefault()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but RID %u has no key policy entry, no response", + peerId, connection->identWithQualifier().c_str(), requestingRid); + return; + } + + if (!ridEntry.radioEnabled()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but RID %u is disabled, no response", + peerId, connection->identWithQualifier().c_str(), requestingRid); + return; + } + + if (!ridEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but RID %u cannot request keys, no response", + peerId, connection->identWithQualifier().c_str(), requestingRid); + return; + } + + std::vector allowedKIds = ridEntry.allowedKIds(); + + // check if this RID is allowed to request the KID in question + if (!allowedKIds.empty() && std::find(allowedKIds.begin(), allowedKIds.end(), modifyKey->getKId()) == allowedKIds.end()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key kID = $%04X but RID %u is not permitted for that key, no response", + peerId, connection->identWithQualifier().c_str(), modifyKey->getKId(), requestingRid); + return; + } + } + else { + if (!network->m_kmfAllowRID0 && connection->peerClass() != PEER_CONN_CLASS_NEIGHBOR) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key with RID 0 but such requests are not allowed, no response", + peerId, connection->identWithQualifier().c_str()); + return; + } + } + + LogInfoEx(LOG_MASTER, "PEER %u (%s) requested enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), modifyKey->getKId()); + ::EKCKeyItem keyItem = network->m_cryptoLookup->find(modifyKey->getKId()); + if (!keyItem.isInvalid()) { + uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + uint8_t keyLength = keyItem.getKey(key); + + if (network->m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + + LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), modifyKey->getKId()); + + // if configured to encrypt the key with a preshared key, do that now + if (network->m_kmfEncKeyRequest) { + uint8_t* encryptedKey = nullptr; + if (network->m_kmfPresharedKey != nullptr) { + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + encryptedKey = aes.encryptECB(key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); + + if (encryptedKey != nullptr) { + ::memcpy(key, encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + delete[] encryptedKey; + } + } + + if (network->m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Encrypted Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + } + + // build response buffer + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + KMMModifyKey modifyKeyRsp = KMMModifyKey(); + modifyKeyRsp.setDecryptInfoFmt(network->m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyRsp.setAlgId(modifyKey->getAlgId()); + modifyKeyRsp.setKId(0U); + + KeysetItem ks = KeysetItem(); + ks.keysetId(1U); + ks.algId(modifyKey->getAlgId()); + ks.keyLength(keyLength); + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(KEY_FORMAT_TEK); + ki.kId((uint16_t)keyItem.kId()); + ki.sln((uint16_t)keyItem.sln()); + ki.setKey(key, keyLength); + + ks.push_back(ki); + modifyKeyRsp.setKeysetItem(ks); + + modifyKeyRsp.encode(buffer + 11U); + + network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + RTP_END_OF_CALL_SEQ, network->createStreamId()); + } else { + // attempt to forward KMM key request to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), modifyKey->getKId()); + + bool locked = network->s_keyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); + network->m_peerReplicaKeyQueue[peerId] = modifyKey->getKId(); + + if (locked) + network->s_keyQueueMutex.unlock(); + + peer.second->writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false); + } + } + } + } else { + LogError(LOG_MASTER, "PEER %u (%s) no local key or container and no replica masters to forward request to, no response, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), modifyKey->getKId()); + } + } + } + } + break; + + default: + break; + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_REPEATER_KEY, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } +} diff --git a/src/fne/network/packet/KeysInventory.cpp b/src/fne/network/packet/KeysInventory.cpp new file mode 100644 index 00000000..5ae221a5 --- /dev/null +++ b/src/fne/network/packet/KeysInventory.cpp @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/edac/SHA256.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::KEYS_INVENTORY packets. */ + +void MetadataNetwork::PacketHandler::keysInventory(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)ssrc; + + if (!network->m_host->m_cryptoLookup->isRemoteAccessEnabled()) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but remote access is disabled, no response", peerId); + return; + } + + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory but is not allowed, no response", peerId); + return; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory but is not allowed, no response", peerId); + return; + } + } + + // keys inventory operates differently from the rest of the network opcodes...and does not require + // an established connection to the master, so we will not validate the peer connection state here + if (peerId > 0 && !peerEntry.peerDefault()) { + if (req->length < 80) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but payload length was invalid (%u bytes), no response", peerId, req->length); + return; + } + + // scope intentional + { + // get the peer password hash from the frame message + DECLARE_UINT8_ARRAY(peerHash, 32U); + ::memcpy(peerHash, req->buffer + 8U, 32U); + + uint8_t peerSalt[4U]; + ::memset(peerSalt, 0x00U, 4U); + ::memcpy(peerSalt, req->buffer + 40U, 4U); + + std::string passwordForPeer = network->m_password; + + // check if the peer is in the peer ACL list + bool validAcl = true; + if (network->m_peerListLookup->getACL()) { + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); + validAcl = false; + } else { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check + } else { + passwordForPeer = peerEntry.peerPassword(); + if (passwordForPeer.length() == 0) { + passwordForPeer = network->m_password; + } + } + } + + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + validAcl = true; + } + } + + if (validAcl) { + size_t size = passwordForPeer.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, peerSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = passwordForPeer.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length >= 80) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (peerHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid authentication, no response", peerId); + return; + } + } else { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid ACL, no response", peerId); + return; + } + } + + // scope intentional + { + // get remote access password hash from the frame message + DECLARE_UINT8_ARRAY(remoteAccessHash, 32U); + ::memcpy(remoteAccessHash, req->buffer + 44U, 32U); + + uint8_t remoteSalt[4U]; + ::memset(remoteSalt, 0x00U, 4U); + ::memcpy(remoteSalt, req->buffer + 76U, 4U); + + std::string remoteAccessPassword = network->m_host->m_cryptoLookup->getRemotePassword(); + + size_t size = remoteAccessPassword.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, remoteSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = remoteAccessPassword.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length >= 80) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (remoteAccessHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid access authentication, no response", peerId); + return; + } + } + + // scope intentional + { + // read entire file into buffer + std::stringstream b; + std::ifstream stream(network->m_host->m_cryptoLookup->filename(), std::ios::in | std::ios::binary); + + uint32_t len = 0U; + UInt8Array bufferUInt8Array = nullptr; + uint8_t* buffer = nullptr; + + if (stream.is_open()) { + stream.seekg(0, std::ios::end); + len = (uint32_t)stream.tellg(); + stream.seekg(0, std::ios::beg); + + bufferUInt8Array = std::make_unique(len); + buffer = bufferUInt8Array.get(); + ::memset(buffer, 0x00U, len); + + uint32_t i = 0U; + while (stream.peek() != EOF) { + buffer[i] = (uint8_t)stream.get(); + i++; + } + + stream.close(); + } + + PacketBuffer pkt(true, "Remote EKC, Key Inventory"); + pkt.encode((uint8_t*)buffer, len); + + LogInfoEx(LOG_REPL, "PEER %u Remote EKC, Key Inventory, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); + if (pkt.fragments.size() > 0U) { + for (auto frag : pkt.fragments) { + // violate most handling rules for responding to packets -- we need to directly respond to the calling peer as + // they may not be logged in as a standard peer + mdNetwork->m_frameQueue->write(frag.second->data, FRAG_SIZE, streamId, peerId, network->m_peerId, { NET_FUNC::KEYS_INVENTORY, NET_SUBFUNC::NOP }, + 0U, req->address, req->addrLen); + Thread::sleep(60U); // pace block transmission + } + } + + pkt.clear(); + } + } +} diff --git a/src/fne/network/packet/KeysUpdate.cpp b/src/fne/network/packet/KeysUpdate.cpp new file mode 100644 index 00000000..6d47dae2 --- /dev/null +++ b/src/fne/network/packet/KeysUpdate.cpp @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/edac/SHA256.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::KEYS_UPDATE packets. */ + +void MetadataNetwork::PacketHandler::keysUpdate(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)ssrc; + + if (!network->m_host->m_cryptoLookup->isRemoteAccessEnabled()) { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but remote access is disabled, no response", peerId); + return; + } + + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + LogError(LOG_MASTER, "PEER %u requested enc. key update but is not allowed, no response", peerId); + return; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u requested enc. key update but is not allowed, no response", peerId); + return; + } + } + + // keys update operates differently from the rest of the network opcodes...and does not require + // an established connection to the master, so we will not validate the peer connection state here. + // update is a two-phase flow: (1) auth request frame (80 bytes), then (2) chunked PacketBuffer frames. + if (peerId > 0 && !peerEntry.peerDefault()) { + if (mdNetwork->m_peerKeyUpdatePkt.find(peerId) == mdNetwork->m_peerKeyUpdatePkt.end()) { + if (req->length < 80) { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but payload length was invalid (%u bytes), no response", peerId, req->length); + return; + } + + // scope intentional + { + // get the peer password hash from the frame message + DECLARE_UINT8_ARRAY(peerHash, 32U); + ::memcpy(peerHash, req->buffer + 8U, 32U); + + uint8_t peerSalt[4U]; + ::memset(peerSalt, 0x00U, 4U); + ::memcpy(peerSalt, req->buffer + 40U, 4U); + + std::string passwordForPeer = network->m_password; + + // check if the peer is in the peer ACL list + bool validAcl = true; + if (network->m_peerListLookup->getACL()) { + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); + validAcl = false; + } else { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check + } else { + passwordForPeer = peerEntry.peerPassword(); + if (passwordForPeer.length() == 0) { + passwordForPeer = network->m_password; + } + } + } + + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + validAcl = true; + } + } + + if (validAcl) { + size_t size = passwordForPeer.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, peerSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = passwordForPeer.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length >= 80) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (peerHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid authentication, no response", peerId); + return; + } + } else { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid ACL, no response", peerId); + return; + } + } + + // scope intentional + { + // get remote access password hash from the frame message + DECLARE_UINT8_ARRAY(remoteAccessHash, 32U); + ::memcpy(remoteAccessHash, req->buffer + 44U, 32U); + + uint8_t remoteSalt[4U]; + ::memset(remoteSalt, 0x00U, 4U); + ::memcpy(remoteSalt, req->buffer + 76U, 4U); + + std::string remoteAccessPassword = network->m_host->m_cryptoLookup->getRemotePassword(); + + size_t size = remoteAccessPassword.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, remoteSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = remoteAccessPassword.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length >= 80) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (remoteAccessHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid access authentication, no response", peerId); + return; + } + } + + PacketBufferEntryPtr pkt = findOrCreatePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId, "Remote EKC, Key Update", streamId); + if (pkt == nullptr || !pkt->buffer) { + LogError(LOG_REPL, "PEER %u Remote EKC, Key Update, failed to initialize packet buffer", peerId); + erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); + return; + } + + LogInfoEx(LOG_REPL, "PEER %u Remote EKC, Key Update, authenticated transfer streamId = %u", peerId, streamId); + return; + } + + // scope intentional + { + if (req->length < FRAG_SIZE) { + LogWarning(LOG_REPL, "PEER %u Remote EKC, Key Update, ignoring short data phase frame (%u bytes)", peerId, req->length); + return; + } + + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), KEYS_UPDATE, Raw Payload", rawPayload, req->length); + + PacketBufferEntryPtr pkt = findPacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); + if (pkt == nullptr || !pkt->buffer) { + return; + } + + std::unique_lock pktLock(pkt->mutex, std::defer_lock); + uint32_t timeout = 0U; + while (!pktLock.try_lock() && timeout < TIMEOUT_MAX_REPL) { + timeout++; + Thread::sleep(1U); + } + + if (!pktLock.owns_lock()) { + LogError(LOG_STP, "PEER %u Remote EKC, Key Update, timeout waiting for packet buffer to unlock", peerId); + if (pkt->buffer) { + pkt->buffer->clear(); + pkt->buffer.reset(); + } + pkt->streamId = 0U; + erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); + return; + } + + if (pkt->streamId != streamId) { + LogError(LOG_REPL, "PEER %u Remote EKC, Key Update, stream ID mismatch, expected %u, got %u", peerId, pkt->streamId, streamId); + if (pkt->buffer) { + pkt->buffer->clear(); + pkt->buffer.reset(); + } + pkt->streamId = 0U; + erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); + return; + } + + uint32_t decompressedLen = 0U; + uint8_t* decompressed = nullptr; + + if (pkt->buffer->decode(rawPayload, &decompressed, &decompressedLen)) { + std::ostringstream s; + s << network->m_cryptoLookup->filename(); + + std::string filename = s.str(); + std::ofstream file(filename, std::ofstream::out); + if (file.fail()) { + LogError(LOG_PEER, "Cannot open the crypto container file - %s", filename.c_str()); + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); + return; + } + + for (uint32_t i = 0U; i < decompressedLen; i++) { + file << (char)decompressed[i]; + } + + file.close(); + + network->m_cryptoLookup->stop(true); + network->m_cryptoLookup->reload(); + + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerKeyUpdatePkt, peerId); + } + } + } +} diff --git a/src/fne/network/packet/LLAKeyRequest.cpp b/src/fne/network/packet/LLAKeyRequest.cpp new file mode 100644 index 00000000..99c72f28 --- /dev/null +++ b/src/fne/network/packet/LLAKeyRequest.cpp @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/p25/kmm/KMMFactory.h" +#include "common/AESCrypto.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::KEY_LLA_REQ packets. */ + +void TrafficNetwork::PacketHandler::llaKeyRequest(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + using namespace p25::defines; + using namespace p25::kmm; + + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + // is this peer allowed to request keys? + if (network->m_peerListLookup->getACL()) { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + return; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identWithQualifier().c_str()); + return; + } + } + } + + std::unique_ptr frame = KMMFactory::create(req->buffer + 11U); + if (frame == nullptr) { + LogWarning(LOG_MASTER, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identWithQualifier().c_str()); + return; + } + + switch (frame->getMessageId()) { + case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: + { + KMMModifyKey* modifyKey = static_cast(frame.get()); + + if (modifyKey->getAlgId() == ALGO_AES_128 && modifyKey->getDstLLId() > 0U) { + if (network->m_debug) + LogDebugEx(LOG_MASTER, "TrafficNetwork::taskNetworkRx()", "PEER %u (%s) LLA enc. key request received, dstLLId = %u, algId = %u, kId = %u", peerId, connection->identWithQualifier().c_str(), + modifyKey->getDstLLId(), modifyKey->getAlgId(), modifyKey->getKId()); + + uint32_t requestingRid = modifyKey->getDstLLId(); + + LogInfoEx(LOG_MASTER, "PEER %u (%s) requested LLA enc. key, rsi = %u", peerId, connection->identWithQualifier().c_str(), + requestingRid); + + ::EKCKeyItem keyItem = network->m_cryptoLookup->findLLA(requestingRid); + if (!keyItem.isInvalid()) { + uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + uint8_t keyLength = keyItem.getKey(key); + + if (network->m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + + LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, rsi = %u", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), requestingRid); + + // if configured to encrypt the key with a preshared key, do that now + if (network->m_kmfEncKeyRequest) { + uint8_t* encryptedKey = nullptr; + if (network->m_kmfPresharedKey != nullptr) { + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + encryptedKey = aes.encryptECB(key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); + + if (encryptedKey != nullptr) { + ::memcpy(key, encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + delete[] encryptedKey; + } + } + + if (network->m_debug) { + LogDebugEx(LOG_HOST, "TrafficNetwork::threadedNetworkRx()", "keyLength = %u", keyLength); + Utils::dump(1U, "TrafficNetwork::taskNetworkRx(), Encrypted Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + } + + // build response buffer + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + KMMModifyKey modifyKeyRsp = KMMModifyKey(); + modifyKeyRsp.setDecryptInfoFmt(network->m_kmfEncKeyRequest ? KMM_DECRYPT_PEER_ENC : KMM_DECRYPT_INSTRUCT_NONE); + modifyKeyRsp.setAlgId(modifyKey->getAlgId()); + modifyKeyRsp.setKId(0U); + modifyKeyRsp.setSrcLLId(WUID_FNE); + modifyKeyRsp.setDstLLId(requestingRid); + + KeysetItem ks = KeysetItem(); + ks.keysetId(1U); + ks.algId(modifyKey->getAlgId()); + ks.keyLength(keyLength); + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(KEY_FORMAT_TEK); + ki.kId((uint16_t)keyItem.kId()); + ki.sln((uint16_t)keyItem.sln()); + ki.setKey(key, keyLength); + + ks.push_back(ki); + modifyKeyRsp.setKeysetItem(ks); + + modifyKeyRsp.encode(buffer + 11U); + + network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_LLA_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, + RTP_END_OF_CALL_SEQ, network->createStreamId()); + } else { + // attempt to forward KMM key request to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto& peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, rsi = %u", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), requestingRid); + + bool locked = network->s_llaKeyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); + network->m_peerReplicaLLAKeyQueue[peerId] = modifyKey->getDstLLId(); + + if (locked) + network->s_llaKeyQueueMutex.unlock(); + + peer.second->writeMaster({ NET_FUNC::KEY_LLA_REQ, NET_SUBFUNC::NOP }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false); + } + } + } + } else { + LogError(LOG_MASTER, "PEER %u (%s) requested LLA enc. key with no local key and no upstream masters to query, algId = $%02X, rsi = %u, no response", peerId, connection->identWithQualifier().c_str(), + modifyKey->getAlgId(), requestingRid); + } + } + } + } + break; + + default: + break; + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_REPEATER_KEY, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } +} diff --git a/src/fne/network/packet/NetworkTree.cpp b/src/fne/network/packet/NetworkTree.cpp new file mode 100644 index 00000000..d7b2c565 --- /dev/null +++ b/src/fne/network/packet/NetworkTree.cpp @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::NET_TREE packets. */ + +void MetadataNetwork::PacketHandler::networkTree(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)ssrc; + + if (!network->m_enableSpanningTree) + return; + + if (req->fneHeader.getSubFunction() == NET_SUBFUNC::NET_TREE_LIST) { // FNE Network Tree List + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR) { + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), NET_TREE_LIST, Raw Payload", rawPayload, req->length); + + PacketBufferEntryPtr pkt = findOrCreatePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId, "Network Tree, Tree List", streamId); + if (pkt == nullptr || !pkt->buffer) { + LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, failed to initialize packet buffer", peerId, + connection->identWithQualifier().c_str()); + erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); + return; + } + + std::unique_lock pktLock(pkt->mutex, std::defer_lock); + uint32_t timeout = 0U; + while (!pktLock.try_lock() && timeout < TIMEOUT_MAX_REPL) { + timeout++; + Thread::sleep(1U); + } + + if (!pktLock.owns_lock()) { + LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, timeout waiting for packet buffer to unlock", peerId, + connection->identWithQualifier().c_str()); + if (pkt->buffer) { + pkt->buffer->clear(); + pkt->buffer.reset(); + } + pkt->streamId = 0U; + erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); + return; + } + + if (pkt->streamId != streamId) { + LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, stream ID mismatch, expected %u, got %u", peerId, + connection->identWithQualifier().c_str(), pkt->streamId, streamId); + pkt->buffer->clear(); + pkt->streamId = streamId; + } + + uint32_t decompressedLen = 0U; + uint8_t* decompressed = nullptr; + + if (pkt->buffer->decode(rawPayload, &decompressed, &decompressedLen)) { + std::string payload(decompressed + 8U, decompressed + decompressedLen); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + LogError(LOG_STP, "PEER %u (%s) error parsing network tree list, %s", peerId, connection->identWithQualifier().c_str(), err.c_str()); + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); + return; + } + else { + // ensure parsed JSON is an array + if (!v.is()) { + LogError(LOG_STP, "PEER %u (%s) error parsing network tree list, data was not valid", peerId, connection->identWithQualifier().c_str()); + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); + return; + } + else { + json::array arr = v.get(); + LogInfoEx(LOG_STP, "PEER %u (%s) Network Tree, Tree List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size()); + + std::lock_guard guard(network->m_treeLock); + + std::vector duplicatePeers; + SpanningTree::deserializeTree(arr, network->m_treeRoot, &duplicatePeers); + + network->logSpanningTree(connection); + + if (duplicatePeers.size() > 0U) { + for (auto dupPeerId : duplicatePeers) { + LogWarning(LOG_STP, "PEER %u (%s) Network Tree, Tree Change, disconnecting duplicate peer connection for PEER %u to prevent network loop", + peerId, connection->identWithQualifier().c_str(), dupPeerId); + network->writeTreeDisconnect(peerId, dupPeerId); + } + } + } + } + + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerTreeListPkt, peerId); + } + } + else { + network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } +} diff --git a/src/fne/network/packet/Ping.cpp b/src/fne/network/packet/Ping.cpp new file mode 100644 index 00000000..708335dc --- /dev/null +++ b/src/fne/network/packet/Ping.cpp @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::PING packets. */ + +void TrafficNetwork::PacketHandler::ping(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + uint32_t pingsRx = connection->pingsReceived(); + uint64_t lastPing = connection->lastPing(); + pingsRx++; + + connection->pingsReceived(pingsRx); + connection->lastPing(now); + + uint8_t payload[8U]; + ::memset(payload, 0x00U, 8U); + + // split ulong64_t (8 byte) value into bytes + payload[0U] = (uint8_t)((now >> 56) & 0xFFU); + payload[1U] = (uint8_t)((now >> 48) & 0xFFU); + payload[2U] = (uint8_t)((now >> 40) & 0xFFU); + payload[3U] = (uint8_t)((now >> 32) & 0xFFU); + payload[4U] = (uint8_t)((now >> 24) & 0xFFU); + payload[5U] = (uint8_t)((now >> 16) & 0xFFU); + payload[6U] = (uint8_t)((now >> 8) & 0xFFU); + payload[7U] = (uint8_t)((now >> 0) & 0xFFU); + + network->writePeerCommand(peerId, { NET_FUNC::PONG, NET_SUBFUNC::NOP }, payload, 8U, streamId, false); + + if (network->m_reportPeerPing) { + LogInfoEx(LOG_MASTER, "PEER %u (%s) ping, pingsReceived = %u, lastPing = %u, now = %u", peerId, connection->identWithQualifier().c_str(), + connection->pingsReceived(), lastPing, now); + } + + // ensure STP sanity, when we receive a ping from a downstream leaf + // this check ensures a STP entry for a downstream leaf isn't accidentally blown off + // the tree during a fast reconnect + if (network->m_enableSpanningTree && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR) { + std::lock_guard guard(network->m_treeLock); + + if ((connection->masterId() != peerId) && (connection->masterId() != 0U)) { + // check if this peer is already connected via another peer + SpanningTree* tree = SpanningTree::findByMasterID(connection->masterId()); + if (tree == nullptr) { + LogWarning(LOG_STP, "PEER %u (%s) downstream server not announced in server tree, reinitializing STP entry, this is abnormal, peerId = %u, masterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + peerId, connection->masterId(), connection->connectionState()); + SpanningTree* node = new SpanningTree(peerId, connection->masterId(), network->m_treeRoot); + node->identity(connection->identity()); + network->logSpanningTree(connection); + } + } + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_REPEATER_PING); + } + } + } +} diff --git a/src/fne/network/packet/Protocol.cpp b/src/fne/network/packet/Protocol.cpp new file mode 100644 index 00000000..6d160d58 --- /dev/null +++ b/src/fne/network/packet/Protocol.cpp @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "network/callhandler/TagDMRData.h" +#include "network/callhandler/TagP25Data.h" +#include "network/callhandler/TagNXDNData.h" +#include "network/callhandler/TagAnalogData.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; +using namespace network::callhandler; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::PROTOCOL packets. */ + +void TrafficNetwork::PacketHandler::protocol(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_dmrEnabled) { + if (network->m_tagDMR != nullptr) { + // check if jitter buffer is enabled for this peer + if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + std::vector readyFrames; + + connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); + + // process all frames that are now ready (in sequence order) + for (BufferedFrame* frame : readyFrames) { + network->m_tagDMR->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); + delete frame; + } + } else { + // zero-latency fast path: no jitter buffer + network->m_tagDMR->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); + } + } + } else { + network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + } + } + else { + network->writePeerNAK(peerId, TAG_DMR_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_p25Enabled) { + if (network->m_tagP25 != nullptr) { + // check if jitter buffer is enabled for this peer + if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + std::vector readyFrames; + + connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); + + // process all frames that are now ready (in sequence order) + for (BufferedFrame* frame : readyFrames) { + network->m_tagP25->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); + delete frame; + } + } else { + // zero-latency fast path: no jitter buffer + network->m_tagP25->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); + } + } + } else { + network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + } + } + else { + network->writePeerNAK(peerId, TAG_P25_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_nxdnEnabled) { + if (network->m_tagNXDN != nullptr) { + // check if jitter buffer is enabled for this peer + if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + std::vector readyFrames; + + connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); + + // process all frames that are now ready (in sequence order) + for (BufferedFrame* frame : readyFrames) { + network->m_tagNXDN->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); + delete frame; + } + } else { + // zero-latency fast path: no jitter buffer + network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); + } + } + } else { + network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + } + } + else { + network->writePeerNAK(peerId, TAG_NXDN_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_analogEnabled) { + if (network->m_tagAnalog != nullptr) { + // check if jitter buffer is enabled for this peer + if (connection->jitterBufferEnabled() && req->rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + std::vector readyFrames; + + connection->processJitterFrame(streamId, req->rtpHeader.getSequence(), req->buffer, req->length, readyFrames); + + // process all frames that are now ready (in sequence order) + for (BufferedFrame* frame : readyFrames) { + network->m_tagAnalog->processFrame(frame->data, frame->length, peerId, ssrc, frame->seq, streamId); + delete frame; + } + } else { + // zero-latency fast path: no jitter buffer + network->m_tagAnalog->processFrame(req->buffer, req->length, peerId, ssrc, req->rtpHeader.getSequence(), streamId); + } + } + } else { + network->writePeerNAK(peerId, streamId, TAG_ANALOG_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); + } + } + } + } + else { + network->writePeerNAK(peerId, TAG_ANALOG_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } + } + break; + + default: + Utils::dump("Unknown protocol opcode from peer", req->buffer, req->length); + break; + } +} diff --git a/src/fne/network/packet/RepeaterAuth.cpp b/src/fne/network/packet/RepeaterAuth.cpp new file mode 100644 index 00000000..0f164f13 --- /dev/null +++ b/src/fne/network/packet/RepeaterAuth.cpp @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/edac/SHA256.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::RPTK packets. */ + +void TrafficNetwork::PacketHandler::repeaterAuth(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + std::lock_guard peerGuard(TrafficNetwork::getPeerStateLock(peerId)); + + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + connection->lastPing(now); + + if (connection->connectionState() == NET_STAT_WAITING_AUTHORISATION) { + // get the hash from the frame message + DECLARE_UINT8_ARRAY(hash, req->length - 8U); + ::memcpy(hash, req->buffer + 8U, req->length - 8U); + + // generate our own hash + uint8_t salt[4U]; + ::memset(salt, 0x00U, 4U); + SET_UINT32(connection->salt(), salt, 0U); + + std::string passwordForPeer = network->m_password; + + // check if the peer is in the peer ACL list + bool validAcl = true; + if (network->m_peerListLookup->getACL()) { + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); + validAcl = false; + } else { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check + } else { + passwordForPeer = peerEntry.peerPassword(); + if (passwordForPeer.length() == 0) { + passwordForPeer = network->m_password; + } + } + } + + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + validAcl = true; + } + } + + if (validAcl) { + size_t size = passwordForPeer.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, salt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = passwordForPeer.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length - 8U == 32U) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (hash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (validHash) { + connection->connectionState(NET_STAT_WAITING_CONFIG); + network->writePeerACK(peerId, streamId); + LogInfoEx(LOG_MASTER, "PEER %u RPTK ACK, completed the login exchange", peerId); + network->m_peers[peerId] = connection; + } + else { + LogWarning(LOG_MASTER, "PEER %u RPTK NAK, failed the login exchange", peerId); + network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + } else { + network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + } + else { + // perform source address/port validation + if (connection->address() != udp::Socket::address(req->address) || + connection->port() != udp::Socket::port(req->address)) { + LogError(LOG_MASTER, "PEER %u RPTK NAK, IP address/port mismatch on RPTK attempt while in an incorrect state, old = %s:%u, new = %s:%u, connectionState = %u", peerId, + connection->address().c_str(), connection->port(), udp::Socket::address(req->address).c_str(), udp::Socket::port(req->address), connection->connectionState()); + + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + return; + } + + LogWarning(LOG_MASTER, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState()); + network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + } + } + else { + network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + network->erasePeer(peerId); + LogWarning(LOG_MASTER, "PEER %u RPTK NAK, having no connection", peerId); + } +} diff --git a/src/fne/network/packet/RepeaterConfig.cpp b/src/fne/network/packet/RepeaterConfig.cpp new file mode 100644 index 00000000..e8e747ac --- /dev/null +++ b/src/fne/network/packet/RepeaterConfig.cpp @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::RPTC packets. */ + +void TrafficNetwork::PacketHandler::repeaterConfig(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + std::lock_guard peerGuard(TrafficNetwork::getPeerStateLock(peerId)); + + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + connection->lastPing(now); + + if (connection->connectionState() == NET_STAT_WAITING_CONFIG) { + DECLARE_UINT8_ARRAY(rawPayload, req->length - 8U); + ::memcpy(rawPayload, req->buffer + 8U, req->length - 8U); + std::string payload(rawPayload, rawPayload + (req->length - 8U)); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + LogWarning(LOG_MASTER, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); + network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + else { + // ensure parsed JSON is an object + if (!v.is()) { + LogWarning(LOG_MASTER, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); + network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + else { + connection->config(v.get()); + connection->connectionState(NET_STAT_RUNNING); + connection->connected(true); + connection->pingsReceived(0U); + connection->lastPing(now); + connection->missedMetadataUpdates(0U); + + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (!peerEntry.peerDefault()) { + if (peerEntry.hasCallPriority()) { + connection->hasCallPriority(peerEntry.hasCallPriority()); + LogInfoEx(LOG_MASTER, "PEER %u >> Has Call Priority", peerId); + } + } + + network->m_peers[peerId] = connection; + + // attach extra notification data to the RPTC ACK to notify the peer of + // the use of the alternate diagnostic port + uint8_t buffer[1U]; + buffer[0U] = 0x80U; // this should really be a defined constant -- but + // because this is the only option and its *always* sent now + // we can just hardcode this for now + + json::object peerConfig = connection->config(); + + std::string identity = "* UNK *"; + if (peerConfig["identity"].is()) { + identity = peerConfig["identity"].getDefault("* UNK *"); + connection->identity(identity); + LogInfoEx(LOG_MASTER, "PEER %u >> Identity [%8s]", peerId, identity.c_str()); + } + + if (peerConfig["software"].is()) { + std::string software = peerConfig["software"].get(); + LogInfoEx(LOG_MASTER, "PEER %u >> Software Version [%s]", peerId, software.c_str()); + } + + /* + ** bryanb: this is support for older configuration structs, newer peers should + ** send the peerClass value + */ + { + // is the peer reporting it is a SysView peer? + if (peerConfig["sysView"].is()) { + bool sysView = peerConfig["sysView"].get(); + if (sysView) + connection->peerClass(PEER_CONN_CLASS_SYSVIEW); + if (sysView) + LogInfoEx(LOG_MASTER, "PEER %u >> SysView Peer", peerId); + } + + // is the peer reporting it is a downstream FNE neighbor peer? + if (peerConfig["externalPeer"].is()) { + bool externalPeer = peerConfig["externalPeer"].get(); + if (externalPeer) + connection->peerClass(PEER_CONN_CLASS_NEIGHBOR); + } + } + + // determine the peer class + if (peerConfig["peerClass"].is()) { + uint32_t peerClass = peerConfig["peerClass"].get(); + if (peerClass >= PEER_CONN_CLASS_INVALID) + peerClass = PEER_CONN_CLASS_STANDARD; + connection->peerClass((PEER_CONN_CLASS)peerClass); + } else { + if (connection->peerClass() == PEER_CONN_CLASS_UNKNOWN) { + connection->peerClass(PEER_CONN_CLASS_STANDARD); + } + } + + // is the peer reporting it is a conventional peer? + if (peerConfig["conventionalPeer"].is()) { + if (network->m_allowConvSiteAffOverride) { + bool convPeer = peerConfig["conventionalPeer"].get(); + connection->isConventional(convPeer); + } else { + bool conventionalPeer = false; + peerConfig["conventionalPeer"].set(conventionalPeer); + } + } else { + bool conventionalPeer = false; + peerConfig["conventionalPeer"].set(conventionalPeer); + } + + // report peer class in log + switch (connection->peerClass()) { + case PEER_CONN_CLASS_NEIGHBOR: + LogInfoEx(LOG_MASTER, "PEER %u >> Downstream Neighbor FNE Peer", peerId); + break; + case PEER_CONN_CLASS_SYSVIEW: + LogInfoEx(LOG_MASTER, "PEER %u >> SysView Peer", peerId); + connection->isReplica(true); + break; + case PEER_CONN_CLASS_CONSOLE: + LogInfoEx(LOG_MASTER, "PEER %u >> Console Peer", peerId); + break; + case PEER_CONN_CLASS_STANDARD: + default: + if (connection->isConventional()) + LogInfoEx(LOG_MASTER, "PEER %u >> Conventional Peer", peerId); + else + LogInfoEx(LOG_MASTER, "PEER %u >> Standard Peer", peerId); + break; + } + + // for the purposes of maintaining backward compat with REST API users, + // we will report the peer class via the old externalPeer and sysView boolean + // fields as well (consolePeer doesn't have a legacy field, but that's ok since + // it's only used for console connections which should be new enough to support + // the new peerClass field) + bool externalPeer = connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR; + peerConfig["externalPeer"].set(externalPeer); + bool sysViewPeer = connection->peerClass() == PEER_CONN_CLASS_SYSVIEW; + peerConfig["sysView"].set(sysViewPeer); + bool consolePeer = connection->peerClass() == PEER_CONN_CLASS_CONSOLE; + peerConfig["consolePeer"].set(consolePeer); + connection->config(peerConfig); + + // is the peer reporting it is an downstream FNE neighbor peer? + /* + ** bryanb: don't change externalPeer to neighborPeer -- this will break backward + ** compat with older FNE versions (we're stuck with this naming :() + */ + if (connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR) { + uint32_t masterPeerId = 0U; + if (peerConfig["masterPeerId"].is()) { + masterPeerId = peerConfig["masterPeerId"].get(); + connection->masterId(masterPeerId); + LogInfoEx(LOG_MASTER, "PEER %u >> Master Peer ID [%u]", peerId, masterPeerId); + } + + // master peer ID should never be zero for an neighbor peer -- use the peer ID instead + if (masterPeerId == 0U) { + LogWarning(LOG_MASTER, "PEER %u reports to be a downstream FNE neighbor peer but has not supplied a valid masterPeerId, using own peerId as masterPeerId (old FNE perhaps?)", peerId); + masterPeerId = peerId; + } + + // check if the peer a peer replication participant + lookups::PeerId peerEntry = network->m_peerListLookup->find(req->peerId); + if (!peerEntry.peerDefault()) { + if (peerEntry.peerReplica()) { + connection->isReplica(true); + LogInfoEx(LOG_MASTER, "PEER %u >> Participates in Peer Replication", peerId); + } + } + + if (network->m_enableSpanningTree) { + network->m_treeLock.lock(); + + // check if this peer is already connected via another peer + SpanningTree* tree = SpanningTree::findByMasterID(masterPeerId); + if (tree != nullptr) { + // are we allowing a fast reconnect? (this happens when a connecting peer + // uses the same peer ID and master ID already announced in the tree, but + // the tree entry wasn't yet erased) + if ((tree->id() == peerId && tree->masterId() == masterPeerId) && + network->m_spanningTreeFastReconnect) { + LogWarning(LOG_STP, "PEER %u (%s) server already announced in server tree, fast peer reconnect, peerId = %u, masterId = %u, treePeerId = %u, treeMasterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + peerId, masterPeerId, tree->id(), tree->masterId(), connection->connectionState()); + if (identity != tree->identity()) { + LogWarning(LOG_STP, "PEER %u (%s) why has this server's announced identity changed? *big hmmmm*", peerId, connection->identWithQualifier().c_str()); + } + SpanningTree::moveParent(tree, network->m_treeRoot); + network->logSpanningTree(connection); + } else { + LogWarning(LOG_STP, "PEER %u (%s) RPTC NAK, server already connected via PEER %u, duplicate connection denied, peerId = %u, masterId = %u, treePeerId = %u, treeMasterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + peerId, masterPeerId, tree->id(), tree->masterId(), tree->id(), connection->connectionState()); + network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_FNE_DUPLICATE_CONN, req->address, req->addrLen); + network->m_treeLock.unlock(); + network->disconnectPeer(peerId, connection); + return; + } + } else { + SpanningTree* node = new SpanningTree(peerId, masterPeerId, network->m_treeRoot); + node->identity(identity); + network->logSpanningTree(connection); + } + + network->m_treeLock.unlock(); + } + } + + network->writePeerACK(peerId, streamId, buffer, 1U); + LogInfoEx(LOG_MASTER, "PEER %u RPTC ACK, completed the configuration exchange", peerId); + + // setup the affiliations list for this peer + std::stringstream peerName; + peerName << "PEER " << peerId; + network->createPeerAffiliations(peerId, peerName.str()); + + // spin up a thread and send metadata over to peer + network->peerMetadataUpdate(peerId); + } + } + } + else { + // perform source address/port validation + if (connection->address() != udp::Socket::address(req->address) || + connection->port() != udp::Socket::port(req->address)) { + LogError(LOG_MASTER, "PEER %u (%s) RPTC NAK, IP address/port mismatch on RPTC attempt while in an incorrect state, old = %s:%u, new = %s:%u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + connection->address().c_str(), connection->port(), udp::Socket::address(req->address).c_str(), udp::Socket::port(req->address), connection->connectionState()); + + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + return; + } + + LogWarning(LOG_MASTER, "PEER %u (%s) RPTC NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + connection->connectionState()); + network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + } + } + else { + network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + LogWarning(LOG_MASTER, "PEER %u RPTC NAK, having no connection", peerId); + } +} diff --git a/src/fne/network/packet/RepeaterDisconnect.cpp b/src/fne/network/packet/RepeaterDisconnect.cpp new file mode 100644 index 00000000..58385180 --- /dev/null +++ b/src/fne/network/packet/RepeaterDisconnect.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::RPT_DISC packets. */ + +void TrafficNetwork::PacketHandler::repeaterDisconnect(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + std::lock_guard peerGuard(TrafficNetwork::getPeerStateLock(peerId)); + + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + LogInfoEx(LOG_MASTER, "PEER %u (%s) disconnected", peerId, connection->identWithQualifier().c_str()); + network->disconnectPeer(peerId, connection); + } + } + } +} diff --git a/src/fne/network/packet/RepeaterLogin.cpp b/src/fne/network/packet/RepeaterLogin.cpp new file mode 100644 index 00000000..5864495c --- /dev/null +++ b/src/fne/network/packet/RepeaterLogin.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/TrafficNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::RPTL packets. */ + +void TrafficNetwork::PacketHandler::repeaterLogin(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now) +{ + std::lock_guard peerGuard(TrafficNetwork::getPeerStateLock(peerId)); + + if (peerId > 0 && (network->m_peers.find(peerId) == network->m_peers.end())) { + if (network->m_peers.size() >= MAX_HARD_CONN_CAP) { + LogError(LOG_MASTER, "PEER %u attempted to connect with no more connections available, currConnections = %u", peerId, network->m_peers.size()); + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_MAX_CONN, req->address, req->addrLen); + return; + } + + if (network->m_softConnLimit > 0U && network->m_peers.size() >= network->m_softConnLimit) { + LogError(LOG_MASTER, "PEER %u attempted to connect with no more connections available, maxConnections = %u, currConnections = %u", peerId, network->m_softConnLimit, network->m_peers.size()); + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_MAX_CONN, req->address, req->addrLen); + return; + } + + FNEPeerConnection* connection = new FNEPeerConnection(peerId, req->address, req->addrLen); + connection->lastPing(now); + + network->applyJitterBufferConfig(peerId, connection); + network->setupRepeaterLogin(peerId, streamId, connection); + + // check if the peer is in the peer ACL list + if (network->m_peerListLookup->getACL()) { + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + } + + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTL, failed peer ACL check", peerId); + + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + } + } + else { + // check if the peer is in our peer list -- if he is, and he isn't in a running state, reset + // the login sequence + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + if (connection->connectionState() == NET_STAT_RUNNING) { + LogInfoEx(LOG_MASTER, "PEER %u (%s) resetting peer connection, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + connection->connectionState()); + network->disconnectPeer(peerId, connection); + + connection = new FNEPeerConnection(peerId, req->address, req->addrLen); + connection->lastPing(now); + + network->applyJitterBufferConfig(peerId, connection); + network->erasePeerAffiliations(peerId); + network->setupRepeaterLogin(peerId, streamId, connection); + + // check if the peer is in the peer ACL list + if (network->m_peerListLookup->getACL()) { + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + } + + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTL, failed peer ACL check", peerId); + + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); + network->disconnectPeer(peerId, connection); + } + } + } else { + // perform source address validation + if (connection->address() != udp::Socket::address(req->address)) { + LogError(LOG_MASTER, "PEER %u RPTL NAK, IP address mismatch on RPTL attempt while not running, old = %s:%u, new = %s:%u, connectionState = %u", peerId, + connection->address().c_str(), connection->port(), udp::Socket::address(req->address).c_str(), udp::Socket::port(req->address), connection->connectionState()); + + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + return; + } + + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + + LogWarning(LOG_MASTER, "PEER %u (%s) RPTL NAK, bad connection state, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + connection->connectionState()); + network->disconnectPeer(peerId, connection); + } + } else { + network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); + network->erasePeer(peerId); + LogWarning(LOG_MASTER, "PEER %u RPTL NAK, having no connection", peerId); + } + } + } +} diff --git a/src/fne/network/packet/Replication.cpp b/src/fne/network/packet/Replication.cpp new file mode 100644 index 00000000..8743fff5 --- /dev/null +++ b/src/fne/network/packet/Replication.cpp @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::REPL packets. */ + +void MetadataNetwork::PacketHandler::replication(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)ssrc; + + if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_ACT_PEER_LIST) { // Peer Replication Active Peer List + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR && + connection->isReplica()) { + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), REPL_ACT_PEER_LIST, Raw Payload", rawPayload, req->length); + + PacketBufferEntryPtr pkt = findOrCreatePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId, "Peer Replication, Active Peer List", streamId); + if (pkt == nullptr || !pkt->buffer) { + LogError(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, failed to initialize packet buffer", peerId, + connection->identWithQualifier().c_str()); + erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); + return; + } + + std::unique_lock pktLock(pkt->mutex, std::defer_lock); + uint32_t timeout = 0U; + while (!pktLock.try_lock() && timeout < TIMEOUT_MAX_REPL) { + timeout++; + Thread::sleep(1U); + } + + if (!pktLock.owns_lock()) { + LogError(LOG_STP, "PEER %u (%s) Peer Replication, Active Peer List, timeout waiting for packet buffer to unlock", peerId, + connection->identWithQualifier().c_str()); + if (pkt->buffer) { + pkt->buffer->clear(); + pkt->buffer.reset(); + } + pkt->streamId = 0U; + erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); + return; + } + + if (pkt->streamId != streamId) { + LogError(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, stream ID mismatch, expected %u, got %u", peerId, + connection->identWithQualifier().c_str(), pkt->streamId, streamId); + pkt->buffer->clear(); + pkt->streamId = streamId; + } + + uint32_t decompressedLen = 0U; + uint8_t* decompressed = nullptr; + + if (pkt->buffer->decode(rawPayload, &decompressed, &decompressedLen)) { + std::string payload(decompressed + 8U, decompressed + decompressedLen); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + LogError(LOG_REPL, "PEER %u (%s) error parsing active peer list, %s", peerId, connection->identWithQualifier().c_str(), err.c_str()); + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); + return; + } + else { + // ensure parsed JSON is an array + if (!v.is()) { + LogError(LOG_REPL, "PEER %u (%s) error parsing active peer list, data was not valid", peerId, connection->identWithQualifier().c_str()); + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); + return; + } + else { + json::array arr = v.get(); + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size()); + network->m_peerReplicaPeers[peerId] = arr; + } + } + + pkt->buffer->clear(); + pkt->buffer.reset(); + pkt->streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + erasePacketBufferEntry(mdNetwork->m_peerReplicaActPkt, peerId); + } + } + else { + network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_HA_PARAMS) { // Peer Replication HA Parameters + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR && + connection->isReplica()) { + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + std::vector receivedParams; + + uint32_t len = GET_UINT32(rawPayload, 0U); + if (len > 0U) { + len /= HA_PARAMS_ENTRY_LEN; + } + + uint8_t offs = 4U; + for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) { + uint32_t peerId = GET_UINT32(rawPayload, offs); + uint32_t ipAddr = GET_UINT32(rawPayload, offs + 4U); + uint16_t port = GET_UINT16(rawPayload, offs + 8U); + receivedParams.push_back(HAParameters(peerId, ipAddr, port)); + } + + if (receivedParams.size() > 0U) { + for (auto rxEntry : receivedParams) { + auto it = std::find_if(network->m_peerReplicaHAParams.begin(), network->m_peerReplicaHAParams.end(), + [&](HAParameters& x) + { + if (x.peerId == rxEntry.peerId) + return true; + return false; + }); + if (it != network->m_peerReplicaHAParams.end()) { + it->masterIP = rxEntry.masterIP; + it->masterPort = rxEntry.masterPort; + } else { + HAParameters param = rxEntry; + network->m_peerReplicaHAParams.push_back(param); + } + + if (network->m_debug) { + std::string address = __IP_FROM_UINT(rxEntry.masterIP); + LogDebugEx(LOG_REPL, "MetadataNetwork::taskNetworkRx()", "PEER %u (%s) Peer Replication, HA Parameters, %s:%u", peerId, connection->identWithQualifier().c_str(), + address.c_str(), rxEntry.masterPort); + } + } + + if (receivedParams.size() > 0) { + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, HA Parameters, updating %u entries, %u entries", peerId, connection->identWithQualifier().c_str(), receivedParams.size(), + network->m_peerReplicaHAParams.size()); + + // send peer updates to replica peers + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + std::vector haParams; + network->m_peerReplicaHAParams.lock(false); + for (auto entry : network->m_peerReplicaHAParams) { + haParams.push_back(entry); + } + network->m_peerReplicaHAParams.unlock(); + + peer.second->writeHAParams(haParams); + } + } + } + } + } + } + } else { + network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } +} diff --git a/src/fne/network/packet/Transfer.cpp b/src/fne/network/packet/Transfer.cpp new file mode 100644 index 00000000..5ed92ead --- /dev/null +++ b/src/fne/network/packet/Transfer.cpp @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::TRANSFER packets. */ + +void MetadataNetwork::PacketHandler::transfer(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)mdNetwork; + (void)ssrc; + (void)streamId; + + // resolve peer ID (used for Activity Log and Status Transfer) + bool validPeerId = false; + uint32_t pktPeerId = 0U; + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + validPeerId = true; + pktPeerId = peerId; + } else { + if (peerId > 0) { + // this could be a replica transfer -- in which case, we need to check the SSRC of the packet not the peer ID + if (network->m_peers.find(req->rtpHeader.getSSRC()) != network->m_peers.end()) { + FNEPeerConnection* connection = network->m_peers[req->rtpHeader.getSSRC()]; + if (connection != nullptr) { + if (connection->peerClass() == PEER_CONN_CLASS_NEIGHBOR && connection->isReplica()) { + validPeerId = true; + pktPeerId = req->rtpHeader.getSSRC(); + } + } + } + } + } + + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: // Peer Activity Log Transfer + { + if (network->m_allowActivityTransfer) { + if (pktPeerId > 0 && validPeerId) { + FNEPeerConnection* connection = network->m_peers[pktPeerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + DECLARE_UINT8_ARRAY(rawPayload, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + ::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identWithQualifier().c_str(), payload.c_str()); + + // report activity log to InfluxDB + if (network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("activity") + .tag("peerId", std::to_string(pktPeerId)) + .field("identity", connection->identity()) + .field("msg", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(network->m_influxServer); + } + + // repeat traffic to the connected SysView peers + if (network->m_peers.size() > 0U) { + for (auto peer : network->m_peers) { + if (peer.second != nullptr) { + if (peer.second->peerClass() == PEER_CONN_CLASS_SYSVIEW) { + sockaddr_storage addr = peer.second->socketStorage(); + uint32_t addrLen = peer.second->sockStorageLen(); + + network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, + { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen); + } + } else { + continue; + } + } + } + + // attempt to repeat traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, true, pktPeerId); + } + } + } + } + } + else { + network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + } + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG: // Peer Diagnostic Log Transfer + { + if (network->m_allowDiagnosticTransfer) { + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + DECLARE_UINT8_ARRAY(rawPayload, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + bool currState = g_disableTimeDisplay; + g_disableTimeDisplay = true; + ::Log(9999U, {nullptr, nullptr, 0U, nullptr}, "%.9u (%8s) %s", peerId, connection->identWithQualifier().c_str(), payload.c_str()); + g_disableTimeDisplay = currState; + + // report diagnostic log to InfluxDB + if (network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("diag") + .tag("peerId", std::to_string(peerId)) + .field("identity", connection->identity()) + .field("msg", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(network->m_influxServer); + } + } + else { + network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + } + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: // Peer Status Transfer + { + if (pktPeerId > 0 && validPeerId) { + FNEPeerConnection* connection = network->m_peers[pktPeerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_peers.size() > 0U) { + // attempt to repeat status traffic to SysView clients + for (auto peer : network->m_peers) { + if (peer.second != nullptr) { + if (peer.second->peerClass() == PEER_CONN_CLASS_SYSVIEW) { + sockaddr_storage addr = peer.second->socketStorage(); + uint32_t addrLen = peer.second->sockStorageLen(); + + if (network->m_debug) { + LogDebug(LOG_DIAG, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u", + pktPeerId, peer.first, req->length); + } + network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, + { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen); + } + } else { + continue; + } + } + + // attempt to repeat status traffic to replica masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, true, pktPeerId); + } + } + } + } + } + } + else { + network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + default: + { + LogWarning(LOG_MASTER, "PEER %u, unknown/unsupported transfer opcode %u", peerId, req->fneHeader.getSubFunction()); + if (network->m_debug) + Utils::dump("Unknown/unsupported transfer opcode from the peer", req->buffer, req->length); + } + break; + } +}