diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 95ff4dcf..f59fd91c 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -68,6 +68,9 @@ master: # Flag indicating whether or not a parrot TG call will generate a grant demand. parrotGrantDemand: true + # Flag indicating whether or not a P25 ADJ_STS_BCAST will pass to connected external peers. + disallowP25AdjStsBcast: false + # # Talkgroup Rules Configuration # @@ -78,7 +81,7 @@ master: time: 30 # -# Peers +# External Peers # peers: - name: EXAMPLEPEER diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 2cd131f2..9f8a6b63 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -448,6 +448,7 @@ bool HostFNE::createMasterNetwork() // initialize networking m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime); + m_network->setOptions(masterConf, true); m_network->setLookups(m_ridLookup, m_tidLookup); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 0c87c5b5..b4a8a7a1 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -77,6 +77,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_updateLookupTimer(1000U, (updateLookupTime * 60U)), m_forceListUpdate(false), m_callInProgress(false), + m_disallowP25AdjStsBcast(false), m_reportPeerPing(reportPeerPing), m_verbose(verbose) { @@ -100,6 +101,20 @@ FNENetwork::~FNENetwork() delete m_tagNXDN; } +/// +/// Helper to set configuration options. +/// +/// Instance of the yaml::Node class. +/// +void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) +{ + m_disallowP25AdjStsBcast = conf["disallowP25AdjStsBcast"].as(false); + + if (printOptions) { + LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowP25AdjStsBcast ? "yes" : "no"); + } +} + /// /// Sets the instances of the Radio ID and Talkgroup Rules lookup tables. /// @@ -322,17 +337,15 @@ void FNENetwork::clock(uint32_t ms) // the login sequence if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection* connection = m_peers[peerId]; - LogMessage(LOG_NET, "PEER %u was RPTL NAKed cleaning up peer connection", peerId); + LogMessage(LOG_NET, "PEER %u was RPTL NAKed, cleaning up peer connection, connectionState = %u", peerId, connection->connectionState()); if (connection != nullptr) { - if (connection->connectionState() != NET_STAT_RUNNING) { - if (erasePeer(peerId)) { - delete connection; - } + if (erasePeer(peerId)) { + delete connection; } } else { erasePeer(peerId); if (m_verbose) { - LogWarning(LOG_NET, "PEER %u was RPTL NAKed while having no connection?", peerId); + LogWarning(LOG_NET, "PEER %u was RPTL NAKed while having no connection?, connectionState = %u", peerId, connection->connectionState()); } } } diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index a40ec28f..58b7c283 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -146,6 +146,9 @@ namespace network /// Finalizes a instance of the FNENetwork class. ~FNENetwork() override; + /// Helper to set configuration options. + void setOptions(yaml::Node& conf, bool printOptions); + /// Gets the current status of the network. NET_CONN_STATUS getStatus() { return m_status; } @@ -209,6 +212,8 @@ namespace network bool m_forceListUpdate; bool m_callInProgress; + bool m_disallowP25AdjStsBcast; + bool m_reportPeerPing; bool m_verbose; diff --git a/src/fne/network/fne/TagDMRData.cpp b/src/fne/network/fne/TagDMRData.cpp index 2c7b4eb5..a4ca0c96 100644 --- a/src/fne/network/fne/TagDMRData.cpp +++ b/src/fne/network/fne/TagDMRData.cpp @@ -120,6 +120,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this the end of the call stream? if (dataSync && (dataType == DT_TERMINATOR_WITH_LC)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + RxStatus status; auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); if (it == m_status.end()) { @@ -153,6 +158,11 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if (dataSync && (dataType == DT_VOICE_LC_HEADER)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "DMR, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); if (it != m_status.end()) { RxStatus status = it->second; @@ -487,6 +497,9 @@ bool TagDMRData::validate(uint32_t peerId, data::Data& data, uint32_t streamId) // is this a group call? if (data.getDataType() == FLCO_GROUP) { lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(data.getDstId()); + if (tg.isInvalid()) { + return false; + } // check the DMR slot number if (tg.source().tgSlot() != data.getSlotNo()) { diff --git a/src/fne/network/fne/TagNXDNData.cpp b/src/fne/network/fne/TagNXDNData.cpp index db63f689..756cae4c 100644 --- a/src/fne/network/fne/TagNXDNData.cpp +++ b/src/fne/network/fne/TagNXDNData.cpp @@ -99,6 +99,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI messageType == RTCH_MESSAGE_TYPE_DCALL_DATA)) { // is this the end of the call stream? if (messageType == RTCH_MESSAGE_TYPE_TX_REL || messageType == RTCH_MESSAGE_TYPE_TX_REL_EX) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); @@ -123,6 +128,11 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this a new call stream? if ((messageType != RTCH_MESSAGE_TYPE_TX_REL && messageType != RTCH_MESSAGE_TYPE_TX_REL_EX)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; @@ -410,6 +420,12 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(lc.getDstId()); + + // check TGID validity + if (tg.isInvalid()) { + return false; + } + if (!tg.config().active()) { return false; } diff --git a/src/fne/network/fne/TagP25Data.cpp b/src/fne/network/fne/TagP25Data.cpp index d7484234..b310ae72 100644 --- a/src/fne/network/fne/TagP25Data.cpp +++ b/src/fne/network/fne/TagP25Data.cpp @@ -130,8 +130,20 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId lsd.setLSD1(lsd1); lsd.setLSD2(lsd2); + uint32_t frameLength = buffer[23U]; + + // process a TSBK out into a class literal if possible + std::unique_ptr tsbk; + if (duid == P25_DUID_TSDU) { + UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer + 24U, frameLength); + + tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + } + // is the stream valid? - if (validate(peerId, control, duid, streamId)) { + if (validate(peerId, control, duid, tsbk.get(), streamId)) { // is this peer ignored? if (!isPeerPermitted(peerId, control, duid, streamId)) { return false; @@ -141,6 +153,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (duid != P25_DUID_TSDU && duid != P25_DUID_PDU) { // is this the end of the call stream? if ((duid == P25_DUID_TDU) || (duid == P25_DUID_TDULC)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); @@ -175,6 +192,11 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if ((duid != P25_DUID_TDU) && (duid != P25_DUID_TDULC)) { + if (srcId == 0U && dstId == 0U) { + LogWarning(LOG_NET, "P25, invalid call, peer = %u, srcId = %u, dstId = %u, streamId = %u", peerId, srcId, dstId, streamId); + return false; + } + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; @@ -270,10 +292,13 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, duid, dstId); - peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); - if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u", - peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId); + // process TSDUs going to external peers + if (processTSDUToExternal(outboundPeerBuffer, dstPeerId, duid)) { + peer.second->writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); + if (m_network->m_debug) { + LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId); + } } if (!m_network->m_callInProgress) @@ -331,7 +356,7 @@ void TagP25Data::playbackParrot() m_parrotFirstFrame = false; } - // repeat traffic to the connected peersmutations + // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { m_network->writePeer(peer.first, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, std::get<0>(pkt), std::get<1>(pkt), std::get<2>(pkt), std::get<3>(pkt), false); if (m_network->m_debug) { @@ -450,6 +475,50 @@ bool TagP25Data::peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound) return false; } +/// +/// Helper to process TSDUs being passed to an external peer. +/// +/// +/// Peer ID +/// +bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t peerId, uint8_t duid) +{ + // are we receiving a TSDU? + if (duid == P25_DUID_TSDU) { + uint32_t frameLength = buffer[23U]; + + UInt8Array data = std::unique_ptr(new uint8_t[frameLength]); + ::memset(data.get(), 0x00U, frameLength); + ::memcpy(data.get(), buffer + 24U, frameLength); + + std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get()); + if (tsbk != nullptr) { + // handle standard P25 reference opcodes + switch (tsbk->getLCO()) { + case TSBK_OSP_ADJ_STS_BCAST: + { + lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast(tsbk.get()); + + if (m_network->m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", tsbk->toString().c_str(), + osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); + } + + if (m_network->m_disallowP25AdjStsBcast) { + LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to external peers is prohibited, dropping", peerId); + return false; + } + } + break; + default: + break; + } + } + } + + return true; +} + /// /// Helper to determine if the peer is permitted for traffic. /// @@ -513,9 +582,10 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, uint8_t duid, /// Peer ID /// /// +/// /// Stream ID /// -bool TagP25Data::validate(uint32_t peerId, lc::LC& control, uint8_t duid, uint32_t streamId) +bool TagP25Data::validate(uint32_t peerId, lc::LC& control, uint8_t duid, const p25::lc::TSBK* tsbk, uint32_t streamId) { // is the source ID a blacklisted ID? lookups::RadioId rid = m_network->m_ridLookup->find(control.getSrcId()); @@ -525,8 +595,8 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, uint8_t duid, uint32 } } - // always validate a TSDU or PDU if the source is valid - if (duid == P25_DUID_TSDU || duid == P25_DUID_PDU) + // always validate a PDU if the source is valid + if (duid == P25_DUID_PDU) return true; // always validate a terminator if the source is valid @@ -546,7 +616,37 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, uint8_t duid, uint32 return true; } + // always validate a TSDU or PDU if the source is valid + if (duid == P25_DUID_TSDU) { + if (tsbk != nullptr) { + // handle standard P25 reference opcodes + switch (tsbk->getLCO()) { + case TSBK_IOSP_GRP_VCH: + { + lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(tsbk->getDstId()); + + // check TGID validity + if (tg.isInvalid()) { + return false; + } + + if (!tg.config().active()) { + return false; + } + } + break; + } + } + + return true; + } + + // check TGID validity lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(control.getDstId()); + if (tg.isInvalid()) { + return false; + } + if (!tg.config().active()) { return false; } diff --git a/src/fne/network/fne/TagP25Data.h b/src/fne/network/fne/TagP25Data.h index 3cdd9341..1e8a2a89 100644 --- a/src/fne/network/fne/TagP25Data.h +++ b/src/fne/network/fne/TagP25Data.h @@ -75,10 +75,13 @@ namespace network /// Helper to route rewrite destination ID. bool peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound = true); + /// Helper to process TSDUs being passed to an external peer. + bool processTSDUToExternal(uint8_t* buffer, uint32_t peerId, uint8_t duid); + /// Helper to determine if the peer is permitted for traffic. bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId); /// Helper to validate the P25 call stream. - bool validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId); + bool validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, const p25::lc::TSBK* tsbk, uint32_t streamId); }; } // namespace fne } // namespace network