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