diff --git a/configs/talkgroup_rules.example.yml b/configs/talkgroup_rules.example.yml index f7467983..9ebdd639 100644 --- a/configs/talkgroup_rules.example.yml +++ b/configs/talkgroup_rules.example.yml @@ -24,6 +24,10 @@ groupVoice: exclusion: [] # List of peer talkgroup rewrites. rewrite: [] + # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access, + # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). + # If this list is empty *all* peers a preferred. (Trunking Only) + preferred: [] # # Source Configuration # @@ -51,6 +55,10 @@ groupVoice: exclusion: [] # List of peer talkgroup rewrites. rewrite: [] + # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access, + # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). + # If this list is empty *all* peers a preferred. (Trunking Only) + preferred: [] # # Source Configuration # @@ -84,6 +92,10 @@ groupVoice: tgid: 9999 # DMR slot number. slot: 1 + # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access, + # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). + # If this list is empty *all* peers a preferred. (Trunking Only) + preferred: [] # # Source Configuration # @@ -109,6 +121,10 @@ groupVoice: exclusion: [] # List of peer talkgroup rewrites. rewrite: [] + # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access, + # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). + # If this list is empty *all* peers a preferred. (Trunking Only) + preferred: [] # # Source Configuration # @@ -134,6 +150,10 @@ groupVoice: exclusion: [] # List of peer talkgroup rewrites. rewrite: [] + # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access, + # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). + # If this list is empty *all* peers a preferred. (Trunking Only) + preferred: [] # # Source Configuration # @@ -159,6 +179,10 @@ groupVoice: exclusion: [] # List of peer talkgroup rewrites. rewrite: [] + # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access, + # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). + # If this list is empty *all* peers a preferred. (Trunking Only) + preferred: [] # # Source Configuration # diff --git a/src/common/dmr/acl/AccessControl.cpp b/src/common/dmr/acl/AccessControl.cpp index b5e7daa7..c0d4e890 100644 --- a/src/common/dmr/acl/AccessControl.cpp +++ b/src/common/dmr/acl/AccessControl.cpp @@ -10,7 +10,7 @@ * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -95,3 +95,28 @@ bool AccessControl::validateTGId(uint32_t slotNo, uint32_t id) return true; } } + +/// +/// Helper to determine if a talkgroup ID is non-preferred. +/// +/// DMR slot number. +/// Talkgroup ID. +/// True, if talkgroup ID is valid, otherwise false. +bool AccessControl::tgidNonPreferred(uint32_t slotNo, uint32_t id) +{ + // TG0 is never valid + if (id == 0U) + return false; + + // check if TID ACLs are enabled + if (!m_tidLookup->getACL()) { + return false; + } + + // lookup TID and perform test for validity + TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + if (tid.config().nonPreferred()) + return true; + + return false; +} diff --git a/src/common/dmr/acl/AccessControl.h b/src/common/dmr/acl/AccessControl.h index 41542799..18ff6ac5 100644 --- a/src/common/dmr/acl/AccessControl.h +++ b/src/common/dmr/acl/AccessControl.h @@ -10,7 +10,7 @@ * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL * */ #if !defined(__DMR_ACL__ACCESS_CONTROL_H__) @@ -41,6 +41,9 @@ namespace dmr /// Helper to validate a talkgroup ID. static bool validateTGId(uint32_t slotNo, uint32_t id); + /// Helper to determine if a talkgroup ID is non-preferred. + static bool tgidNonPreferred(uint32_t slotNo, uint32_t id); + private: static RadioIdLookup* m_ridLookup; static TalkgroupRulesLookup* m_tidLookup; diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index a0404c85..81899dd5 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -121,13 +121,15 @@ void TalkgroupRulesLookup::clear() /// Unique ID to add. /// DMR slot this talkgroup is valid on. /// Flag indicating if talkgroup ID is enabled or not. -void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled) +/// Flag indicating if the talkgroup ID is non-preferred. +void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled, bool nonPreferred) { TalkgroupRuleGroupVoiceSource source; TalkgroupRuleConfig config; source.tgId(id); source.tgSlot(slot); config.active(enabled); + config.nonPreferred(nonPreferred); std::lock_guard lock(m_mutex); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), @@ -146,6 +148,7 @@ void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled) config = it->config(); config.active(enabled); + config.nonPreferred(nonPreferred); TalkgroupRuleGroupVoice entry = *it; entry.config(config); @@ -345,12 +348,13 @@ bool TalkgroupRulesLookup::load() uint32_t incCount = groupVoice.config().inclusion().size(); uint32_t excCount = groupVoice.config().exclusion().size(); uint32_t rewrCount = groupVoice.config().rewrite().size(); + uint32_t prefCount = groupVoice.config().preferred().size(); if (incCount > 0 && excCount > 0) { ::LogWarning(LOG_HOST, "Talkgroup (%s) defines both inclusions and exclusions! Inclusions take precedence and exclusions will be ignored.", groupName.c_str()); } - ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u PARROT: %u INCLUSIONS: %u EXCLUSIONS: %u REWRITES: %u", groupName.c_str(), tgId, tgSlot, active, parrot, incCount, excCount, rewrCount); + ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u PARROT: %u INCLUSIONS: %u EXCLUSIONS: %u REWRITES: %u PREFERRED: %u", groupName.c_str(), tgId, tgSlot, active, parrot, incCount, excCount, rewrCount, prefCount); } size_t size = m_groupVoice.size(); diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index e1ade8bc..0d3d2027 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -142,7 +142,9 @@ namespace lookups m_parrot(false), m_inclusion(), m_exclusion(), - m_rewrite() + m_rewrite(), + m_preferred(), + m_nonPreferred(false) { /* stub */ } @@ -178,6 +180,14 @@ namespace lookups m_rewrite.push_back(rewrite); } } + + yaml::Node& preferredList = node["preferred"]; + if (preferredList.size() > 0U) { + for (size_t i = 0; i < preferredList.size(); i++) { + uint32_t peerId = preferredList[i].as(0U); + m_preferred.push_back(peerId); + } + } } /// Equals operator. Copies this TalkgroupRuleConfig to another TalkgroupRuleConfig. @@ -190,6 +200,8 @@ namespace lookups m_inclusion = data.m_inclusion; m_exclusion = data.m_exclusion; m_rewrite = data.m_rewrite; + m_preferred = data.m_preferred; + m_nonPreferred = data.m_nonPreferred; } return *this; @@ -201,6 +213,8 @@ namespace lookups uint8_t exclusionSize() const { return m_exclusion.size(); } /// Gets the count of rewrites. uint8_t rewriteSize() const { return m_rewrite.size(); } + /// Gets the count of rewrites. + uint8_t preferredSize() const { return m_preferred.size(); } /// Return the YAML structure for this TalkgroupRuleConfig. void getYaml(yaml::Node &node) @@ -237,6 +251,15 @@ namespace lookups } } node["rewrite"] = rewriteList; + + yaml::Node preferredList; + if (m_preferred.size() > 0U) { + for (auto pref : m_preferred) { + yaml::Node& newPref = preferredList.push_back(); + newPref = __INT_STR(pref); + } + } + node["preferred"] = preferredList; } public: @@ -252,6 +275,11 @@ namespace lookups __PROPERTY_PLAIN(std::vector, exclusion); /// List of rewrites performed by this rule. __PROPERTY_PLAIN(std::vector, rewrite); + /// List of peer IDs preferred by this rule. + __PROPERTY_PLAIN(std::vector, preferred); + + /// Flag indicating whether or not the talkgroup is a non-preferred. + __PROPERTY_PLAIN(bool, nonPreferred); }; // --------------------------------------------------------------------------- @@ -348,7 +376,7 @@ namespace lookups void clear(); /// Adds a new entry to the lookup table. - void addEntry(uint32_t id, uint8_t slot, bool enabled); + void addEntry(uint32_t id, uint8_t slot, bool enabled, bool nonPreferred = false); /// Adds a new entry to the lookup table. void addEntry(TalkgroupRuleGroupVoice groupVoice); /// Erases an existing entry from the lookup table by the specified unique ID. diff --git a/src/common/nxdn/acl/AccessControl.cpp b/src/common/nxdn/acl/AccessControl.cpp index a089243a..54bf0738 100644 --- a/src/common/nxdn/acl/AccessControl.cpp +++ b/src/common/nxdn/acl/AccessControl.cpp @@ -10,7 +10,7 @@ * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -64,7 +64,6 @@ bool AccessControl::validateSrcId(uint32_t id) /// /// Helper to validate a talkgroup ID. /// -/// DMR slot number. /// Talkgroup ID. /// True, if talkgroup ID is valid, otherwise false. bool AccessControl::validateTGId(uint32_t id) @@ -88,3 +87,27 @@ bool AccessControl::validateTGId(uint32_t id) return true; } + +/// +/// Helper to determine if a talkgroup ID is non-preferred. +/// +/// Talkgroup ID. +/// True, if talkgroup ID is valid, otherwise false. +bool AccessControl::tgidNonPreferred(uint32_t id) +{ + // TG0 is never valid + if (id == 0U) + return false; + + // check if TID ACLs are enabled + if (!m_tidLookup->getACL()) { + return false; + } + + // lookup TID and perform test for validity + TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + if (tid.config().nonPreferred()) + return true; + + return false; +} diff --git a/src/common/nxdn/acl/AccessControl.h b/src/common/nxdn/acl/AccessControl.h index c95ec233..2edfb64f 100644 --- a/src/common/nxdn/acl/AccessControl.h +++ b/src/common/nxdn/acl/AccessControl.h @@ -10,7 +10,7 @@ * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL * */ #if !defined(__NXDN_ACL__ACCESS_CONTROL_H__) @@ -41,6 +41,9 @@ namespace nxdn /// Helper to validate a talkgroup ID. static bool validateTGId(uint32_t id); + /// Helper to determine if a talkgroup ID is non-preferred. + static bool tgidNonPreferred(uint32_t id); + private: static RadioIdLookup* m_ridLookup; static TalkgroupRulesLookup* m_tidLookup; diff --git a/src/common/p25/acl/AccessControl.cpp b/src/common/p25/acl/AccessControl.cpp index 7adf45a7..03d8f65b 100644 --- a/src/common/p25/acl/AccessControl.cpp +++ b/src/common/p25/acl/AccessControl.cpp @@ -10,7 +10,7 @@ * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017,2019,2022 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017,2019,2022,2024 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -64,7 +64,6 @@ bool AccessControl::validateSrcId(uint32_t id) /// /// Helper to validate a talkgroup ID. /// -/// DMR slot number. /// Talkgroup ID. /// True, if talkgroup ID is valid, otherwise false. bool AccessControl::validateTGId(uint32_t id) @@ -88,3 +87,27 @@ bool AccessControl::validateTGId(uint32_t id) return true; } + +/// +/// Helper to determine if a talkgroup ID is non-preferred. +/// +/// Talkgroup ID. +/// True, if talkgroup ID is valid, otherwise false. +bool AccessControl::tgidNonPreferred(uint32_t id) +{ + // TG0 is never valid + if (id == 0U) + return false; + + // check if TID ACLs are enabled + if (!m_tidLookup->getACL()) { + return false; + } + + // lookup TID and perform test for validity + TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + if (tid.config().nonPreferred()) + return true; + + return false; +} diff --git a/src/common/p25/acl/AccessControl.h b/src/common/p25/acl/AccessControl.h index e5e0d784..94a62368 100644 --- a/src/common/p25/acl/AccessControl.h +++ b/src/common/p25/acl/AccessControl.h @@ -10,7 +10,7 @@ * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX -* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL +* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL * */ #if !defined(__P25_ACL__ACCESS_CONTROL_H__) @@ -41,6 +41,9 @@ namespace p25 /// Helper to validate a talkgroup ID. static bool validateTGId(uint32_t id); + /// Helper to determine if a talkgroup ID is non-preferred. + static bool tgidNonPreferred(uint32_t id); + private: static RadioIdLookup* m_ridLookup; static TalkgroupRulesLookup* m_tidLookup; diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index e1206110..6290d9cf 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -1295,6 +1295,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId) for (auto entry : groupVoice) { std::vector inclusion = entry.config().inclusion(); std::vector exclusion = entry.config().exclusion(); + std::vector preferred = entry.config().preferred(); // peer inclusion lists take priority over exclusion lists if (inclusion.size() > 0) { @@ -1313,9 +1314,25 @@ void FNENetwork::writeTGIDs(uint32_t peerId) } } } - + + // determine if the peer is non-preferred + bool nonPreferred = false; + if (preferred.size() > 0) { + auto it = std::find(preferred.begin(), preferred.end(), peerId); + if (it == preferred.end()) { + nonPreferred = true; + } + } + if (entry.config().active()) { - tgidList.push_back({ entry.source().tgId(), entry.source().tgSlot() }); + uint8_t slotNo = entry.source().tgSlot(); + + // set upper bit of the slot number to flag non-preferred + if (nonPreferred) { + slotNo = 0x80U + (slotNo & 0x03U); + } + + tgidList.push_back({ entry.source().tgId(), slotNo }); } } diff --git a/src/host/network/Network.cpp b/src/host/network/Network.cpp index d76e03b0..ee68dd11 100644 --- a/src/host/network/Network.cpp +++ b/src/host/network/Network.cpp @@ -397,8 +397,10 @@ void Network::clock(uint32_t ms) m_ridLookup->toggleEntry(id, true); offs += 4U; } + LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len); - // Save to file if enabled and we got RIDs + + // save to file if enabled and we got RIDs if (m_saveLookup && len > 0) { m_ridLookup->commit(); } @@ -419,8 +421,10 @@ void Network::clock(uint32_t ms) m_ridLookup->toggleEntry(id, false); offs += 4U; } + LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len); - // Save to file if enabled and we got RIDs + + // save to file if enabled and we got RIDs if (m_saveLookup && len > 0) { m_ridLookup->commit(); } @@ -438,22 +442,35 @@ void Network::clock(uint32_t ms) uint32_t offs = 11U; for (uint32_t i = 0; i < len; i++) { uint32_t id = __GET_UINT16(buffer, offs); - uint8_t slot = (buffer[offs + 3U]); + uint8_t slot = (buffer[offs + 3U]) & 0x03U; + bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U; lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); + + // if the TG is marked as non-preferred, and the TGID exists in the local entries + // erase the local and overwrite with the FNE data + if (nonPreferred) { + if (!tid.isInvalid()) { + m_tidLookup->eraseEntry(id, slot); + tid = m_tidLookup->find(id, slot); + } + } + if (tid.isInvalid()) { if (!tid.config().active()) { m_tidLookup->eraseEntry(id, slot); } - LogMessage(LOG_NET, "Activated TG %u TS %u in TGID table", id, slot); - m_tidLookup->addEntry(id, slot, true); + LogMessage(LOG_NET, "Activated%s TG %u TS %u in TGID table", (nonPreferred) ? " non-preferred" : "", id, slot); + m_tidLookup->addEntry(id, slot, true, nonPreferred); } offs += 5U; } + LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into lookup table", len, m_tidLookup->groupVoice().size()); - // Save if saving from network is enabled + + // save if saving from network is enabled if (m_saveLookup && len > 0) { m_tidLookup->commit(); } @@ -481,8 +498,10 @@ void Network::clock(uint32_t ms) offs += 5U; } + LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into lookup table", len, m_tidLookup->groupVoice().size()); - // Save if saving from network is enabled + + // save if saving from network is enabled if (m_saveLookup && len > 0) { m_tidLookup->commit(); } diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 037b3038..60c9cedc 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -2671,6 +2671,14 @@ bool ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId) iosp->setResponse(P25_RSP_DENY); noNet = true; } + + // deny affiliation if the TG is non-preferred on this site/CC + if (acl::AccessControl::tgidNonPreferred(dstId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", %s non-preferred on this site, TGID rejection, dstId = %u", iosp->toString().c_str(), dstId); + ::ActivityLog("P25", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId); + iosp->setResponse(P25_RSP_DENY); + noNet = true; + } } if (iosp->getResponse() == P25_RSP_ACCEPT) {