implement option to disable the CFNE from sending P25 ADJ_STS_BCAST to external peers (that is CFNE's the CFNE receiving the ADJ_STS_BCAST is connected to via peers list in the configuration file); add logging around the RPTL NAK condition; don't validate the connection state when performing a connection cleanup for an RPTL NAK; attempt to erase a peer ID from the peer table if during an RPTL the connection is null (this should never happen); correct a bunch if TG validation issues; correct condition where the FNE would try to pass traffic with *both* srcId and dstId 0 (this should never happen on any protocol really, and if there is some scenario that requires it, contact me on Discord please); add more processing for TSDUs to perform TGID checks and other validations;

pull/48/head
Bryan Biedenkapp 2 years ago
parent 0e20d4fec0
commit 34ae323166

@ -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

@ -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);

@ -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;
}
/// <summary>
/// Helper to set configuration options.
/// </summary>
/// <param name="conf">Instance of the yaml::Node class.</param>
/// <param name="printOptions"></param>
void FNENetwork::setOptions(yaml::Node& conf, bool printOptions)
{
m_disallowP25AdjStsBcast = conf["disallowP25AdjStsBcast"].as<bool>(false);
if (printOptions) {
LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowP25AdjStsBcast ? "yes" : "no");
}
}
/// <summary>
/// Sets the instances of the Radio ID and Talkgroup Rules lookup tables.
/// </summary>
@ -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());
}
}
}

@ -146,6 +146,9 @@ namespace network
/// <summary>Finalizes a instance of the FNENetwork class.</summary>
~FNENetwork() override;
/// <summary>Helper to set configuration options.</summary>
void setOptions(yaml::Node& conf, bool printOptions);
/// <summary>Gets the current status of the network.</summary>
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;

@ -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()) {

@ -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;
}

@ -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<lc::TSBK> tsbk;
if (duid == P25_DUID_TSDU) {
UInt8Array data = std::unique_ptr<uint8_t[]>(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;
}
/// <summary>
/// Helper to process TSDUs being passed to an external peer.
/// </summary>
/// <param name="buffer"></param>
/// <param name="peerId">Peer ID</param>
/// <param name="duid"></param>
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<uint8_t[]>(new uint8_t[frameLength]);
::memset(data.get(), 0x00U, frameLength);
::memcpy(data.get(), buffer + 24U, frameLength);
std::unique_ptr<lc::TSBK> 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<lc::tsbk::OSP_ADJ_STS_BCAST*>(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;
}
/// <summary>
/// Helper to determine if the peer is permitted for traffic.
/// </summary>
@ -513,9 +582,10 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, uint8_t duid,
/// <param name="peerId">Peer ID</param>
/// <param name="control"></param>
/// <param name="duid"></param>
/// <param name="tsbk"></param>
/// <param name="streamId">Stream ID</param>
/// <returns></returns>
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;
}

@ -75,10 +75,13 @@ namespace network
/// <summary>Helper to route rewrite destination ID.</summary>
bool peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound = true);
/// <summary>Helper to process TSDUs being passed to an external peer.</summary>
bool processTSDUToExternal(uint8_t* buffer, uint32_t peerId, uint8_t duid);
/// <summary>Helper to determine if the peer is permitted for traffic.</summary>
bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId);
/// <summary>Helper to validate the P25 call stream.</summary>
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

Loading…
Cancel
Save

Powered by TurnKey Linux.