parent
c731b9113a
commit
a5bebe87f8
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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<fne_lookups::AffiliationLookup> 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<fne_lookups::AffiliationLookup> 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<fne_lookups::AffiliationLookup> 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<fne_lookups::AffiliationLookup> 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<fne_lookups::AffiliationLookup> 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<fne_lookups::AffiliationLookup> 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<uint32_t> 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<KMMFrame> 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<KMMModifyKey*>(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<uint16_t> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<uint8_t[]>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<KMMFrame> 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<KMMModifyKey*>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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<json::array>()) {
|
||||
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<json::array>();
|
||||
LogInfoEx(LOG_STP, "PEER %u (%s) Network Tree, Tree List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size());
|
||||
|
||||
std::lock_guard<std::mutex> guard(network->m_treeLock);
|
||||
|
||||
std::vector<uint32_t> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<BufferedFrame*> 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<BufferedFrame*> 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<BufferedFrame*> 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<BufferedFrame*> 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;
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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);
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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<json::object>()) {
|
||||
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<json::object>());
|
||||
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<std::string>()) {
|
||||
identity = peerConfig["identity"].getDefault<std::string>("* UNK *");
|
||||
connection->identity(identity);
|
||||
LogInfoEx(LOG_MASTER, "PEER %u >> Identity [%8s]", peerId, identity.c_str());
|
||||
}
|
||||
|
||||
if (peerConfig["software"].is<std::string>()) {
|
||||
std::string software = peerConfig["software"].get<std::string>();
|
||||
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>()) {
|
||||
bool sysView = peerConfig["sysView"].get<bool>();
|
||||
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>()) {
|
||||
bool externalPeer = peerConfig["externalPeer"].get<bool>();
|
||||
if (externalPeer)
|
||||
connection->peerClass(PEER_CONN_CLASS_NEIGHBOR);
|
||||
}
|
||||
}
|
||||
|
||||
// determine the peer class
|
||||
if (peerConfig["peerClass"].is<uint32_t>()) {
|
||||
uint32_t peerClass = peerConfig["peerClass"].get<uint32_t>();
|
||||
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<bool>()) {
|
||||
if (network->m_allowConvSiteAffOverride) {
|
||||
bool convPeer = peerConfig["conventionalPeer"].get<bool>();
|
||||
connection->isConventional(convPeer);
|
||||
} else {
|
||||
bool conventionalPeer = false;
|
||||
peerConfig["conventionalPeer"].set<bool>(conventionalPeer);
|
||||
}
|
||||
} else {
|
||||
bool conventionalPeer = false;
|
||||
peerConfig["conventionalPeer"].set<bool>(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<bool>(externalPeer);
|
||||
bool sysViewPeer = connection->peerClass() == PEER_CONN_CLASS_SYSVIEW;
|
||||
peerConfig["sysView"].set<bool>(sysViewPeer);
|
||||
bool consolePeer = connection->peerClass() == PEER_CONN_CLASS_CONSOLE;
|
||||
peerConfig["consolePeer"].set<bool>(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<uint32_t>()) {
|
||||
masterPeerId = peerConfig["masterPeerId"].get<uint32_t>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<std::mutex> 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<json::array>()) {
|
||||
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<json::array>();
|
||||
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<HAParameters> 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<HAParameters> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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::nanoseconds>(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::nanoseconds>(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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue