diff --git a/config/urfd.ini b/config/urfd.ini index aa6a251..f16d228 100644 --- a/config/urfd.ini +++ b/config/urfd.ini @@ -12,6 +12,9 @@ Country = GB Sponsor = My Home Club +# DashboardURL is where your dashboard for this reflector is avaiable on the WWW. +DashboardUrl = https://YourDashboard.net + # Bootstrap is the internet address of any existing DHT client. # Usually, choose a frequented, or close, reflector if it's already a DHT # client. If that's not possible, you can use xrf757.openquad.net diff --git a/reflector/Client.h b/reflector/Client.h index 48fb022..75dd416 100644 --- a/reflector/Client.h +++ b/reflector/Client.h @@ -50,6 +50,8 @@ public: char GetCSModule(void) const { return m_Callsign.GetCSModule(); } bool HasReflectorModule(void) const { return m_ReflectorModule != ' '; } char GetReflectorModule(void) const { return m_ReflectorModule; } + std::time_t GetConnectTime(void) const { return m_ConnectTime; } + std::time_t GetLastHeardTime(void) const { return m_LastHeardTime; } // set void SetCSModule(char c) { m_Callsign.SetCSModule(c); } diff --git a/reflector/Configure.cpp b/reflector/Configure.cpp index b3444eb..9736b50 100644 --- a/reflector/Configure.cpp +++ b/reflector/Configure.cpp @@ -37,6 +37,7 @@ #define JBRANDMEISTER "Brandmeister" #define JCALLSIGN "Callsign" #define JCOUNTRY "Country" +#define JDASHBOARDURL "DashboardUrl" #define JDCS "DCS" #define JDEFAULTID "DefaultId" #define JDEFAULTRXFREQ "DefaultRxFreq" @@ -253,6 +254,8 @@ bool CConfigure::ReadData(const std::string &path) data[g_Keys.names.callsign] = value; else if (0 == key.compare(JBOOTSTRAP)) data[g_Keys.names.bootstrap] = value; + else if (0 == key.compare(JDASHBOARDURL)) + data[g_Keys.names.url] = value; else if (0 == key.compare(JSYSOPEMAIL)) data[g_Keys.names.email] = value; else if (0 == key.compare(JCOUNTRY)) @@ -512,6 +515,7 @@ bool CConfigure::ReadData(const std::string &path) #ifndef NO_DHT isDefined(ErrorLevel::fatal, JNAMES, JBOOTSTRAP, g_Keys.names.bootstrap, rval); #endif + isDefined(ErrorLevel::fatal, JNAMES, JDASHBOARDURL, g_Keys.names.url, rval); isDefined(ErrorLevel::mild, JNAMES, JSYSOPEMAIL, g_Keys.names.email, rval); isDefined(ErrorLevel::mild, JNAMES, JCOUNTRY, g_Keys.names.country, rval); isDefined(ErrorLevel::mild, JNAMES, JSPONSOR, g_Keys.names.sponsor, rval); diff --git a/reflector/JsonKeys.h b/reflector/JsonKeys.h index 29e96b5..ab19407 100644 --- a/reflector/JsonKeys.h +++ b/reflector/JsonKeys.h @@ -39,8 +39,8 @@ struct SJsonKeys { struct MMDVM { const std::string port, defaultid; } mmdvm { "MMDVMPort", "mmdvmdefaultid" }; - struct NAMES { const std::string callsign, bootstrap, email, country, sponsor; } - names { "Callsign", "bootstrap", "SysopEmail", "Country", "Sponsor" }; + struct NAMES { const std::string callsign, bootstrap, url, email, country, sponsor; } + names { "Callsign", "bootstrap", "DashboardUrl", "SysopEmail", "Country", "Sponsor" }; struct IP { const std::string ipv4bind, ipv4address, ipv6bind, ipv6address, transcoder; } ip { "ipv4bind", "IPv4Address", "ipv6bind", "IPv6Address", "tcaddress" }; diff --git a/reflector/Peer.h b/reflector/Peer.h index bc1ddf9..89c96d0 100644 --- a/reflector/Peer.h +++ b/reflector/Peer.h @@ -42,6 +42,7 @@ public: const CCallsign &GetCallsign(void) const { return m_Callsign; } const CIp &GetIp(void) const { return m_Ip; } char *GetReflectorModules(void) { return m_ReflectorModules; } + std::time_t GetConnectTime(void) const { return m_ConnectTime; } // set diff --git a/reflector/Reflector.cpp b/reflector/Reflector.cpp index fac7dd7..ec53e1d 100644 --- a/reflector/Reflector.cpp +++ b/reflector/Reflector.cpp @@ -20,8 +20,13 @@ #include #include "Global.h" -//////////////////////////////////////////////////////////////////////////////////////// -// destructor + +CReflector::CReflector() +{ +#ifndef NO_DHT + peers_put_count = clients_put_count = users_put_count = 0; +#endif +} CReflector::~CReflector() { @@ -47,10 +52,18 @@ CReflector::~CReflector() bool CReflector::Start(void) { // get config stuff - m_Callsign = CCallsign(g_Configure.GetString(g_Keys.names.callsign).c_str(), false); + const auto cs(g_Configure.GetString(g_Keys.names.callsign)); + m_Callsign.SetCallsign(cs, false); m_Modules.assign(g_Configure.GetString(g_Keys.modules.modules)); std::string tcmods(g_Configure.GetString(g_Keys.modules.tcmodules)); +#ifndef NO_DHT + // start the dht instance + refhash = dht::InfoHash::get(cs); + node.run(17171, dht::crypto::generateIdentity(cs), true); + node.bootstrap(g_Configure.GetString(g_Keys.names.bootstrap), "17171"); +#endif + // let's go! keep_running = true; @@ -114,6 +127,10 @@ bool CReflector::Start(void) std::cerr << "Cannot start the dashboard data report thread: " << e.what() << '\n'; } +#ifndef NO_DHT + PutDHTConfig(); +#endif + return false; } @@ -145,6 +162,16 @@ void CReflector::Stop(void) g_LDid.LookupClose(); g_LNid.LookupClose(); g_LYtr.LookupClose(); + +#ifndef NO_DHT + // kill the DHT + node.cancelPut(refhash, toUType(EUrfdValueID::Config)); + node.cancelPut(refhash, toUType(EUrfdValueID::Peers)); + node.cancelPut(refhash, toUType(EUrfdValueID::Clients)); + node.cancelPut(refhash, toUType(EUrfdValueID::Users)); + node.shutdown({}, true); + node.join(); +#endif } //////////////////////////////////////////////////////////////////////////////////////// @@ -512,3 +539,186 @@ void CReflector::WriteXmlFile(std::ofstream &xmlFile) ReleaseUsers(); xmlFile << "" << std::endl; } + +#ifndef NO_DHT +// DHT put() and get() +void CReflector::PutDHTPeers() +{ + const std::string cs(g_Configure.GetString(g_Keys.names.callsign)); + // load it up + SUrfdPeers0 p; + time(&p.timestamp); + p.sequence = peers_put_count++; + auto peers = GetPeers(); + for (auto pit=peers->cbegin(); pit!=peers->cend(); pit++) + { + p.list.emplace_back((*pit)->GetCallsign().GetCS(), (*pit)->GetReflectorModules(), (*pit)->GetConnectTime()); + } + ReleasePeers(); + + auto nv = std::make_shared(p); + nv->user_type.assign("urfd-peers-0"); + nv->id = toUType(EUrfdValueID::Peers); + + node.putSigned( + refhash, + nv, + [](bool success){ std::cout << "PutDHTPeers() " << (success ? "successful" : "unsuccessful") << std::endl; }, + true // permanent! + ); +} + +void CReflector::PutDHTClients() +{ + const std::string cs(g_Configure.GetString(g_Keys.names.callsign)); + SUrfdClients0 c; + time(&c.timestamp); + c.sequence = clients_put_count++; + auto clients = GetClients(); + for (auto cit=clients->cbegin(); cit!=clients->cend(); cit++) + { + c.list.emplace_back((*cit)->GetCallsign().GetCS(), std::string((*cit)->GetIp().GetAddress()), (*cit)->GetReflectorModule(), (*cit)->GetConnectTime(), (*cit)->GetLastHeardTime()); + } + ReleaseClients(); + + auto nv = std::make_shared(c); + nv->user_type.assign("urfd-clients-0"); + nv->id = toUType(EUrfdValueID::Clients); + + node.putSigned( + refhash, + nv, + [](bool success){ std::cout << "PutDHTClients() " << (success ? "successful" : "unsuccessful") << std::endl; }, + true // permanent! + ); +} + +void CReflector::PutDHTUsers() +{ + const std::string cs(g_Configure.GetString(g_Keys.names.callsign)); + SUrfdUsers0 u; + time(&u.timestamp); + u.sequence = users_put_count++; + auto users = GetUsers(); + for (auto uit=users->cbegin(); uit!=users->cend(); uit++) + { + u.list.emplace_back((*uit).GetCallsign(), std::string((*uit).GetViaNode()), (*uit).GetOnModule(), (*uit).GetViaPeer(), (*uit).GetLastHeardTime()); + } + ReleaseUsers(); + + auto nv = std::make_shared(u); + nv->user_type.assign("urfd-users-0"); + nv->id = toUType(EUrfdValueID::Users); + + node.putSigned( + refhash, + nv, + [](bool success){ std::cout << "PutDHTUsers() " << (success ? "successful" : "unsuccessful") << std::endl; }, + true // permanent! + ); +} + +void CReflector::PutDHTConfig() +{ + const std::string cs(g_Configure.GetString(g_Keys.names.callsign)); + SUrfdConfig0 cfg; + time(&cfg.timestamp); + cfg.cs.assign(cs); + cfg.ipv4.assign(g_Configure.GetString(g_Keys.ip.ipv4address)); + cfg.ipv6.assign(g_Configure.GetString(g_Keys.ip.ipv6address)); + cfg.mods.assign(g_Configure.GetString(g_Keys.modules.modules)); + cfg.tcmods.assign(g_Configure.GetString(g_Keys.modules.tcmodules)); + cfg.url.assign(g_Configure.GetString(g_Keys.names.url)); + cfg.email.assign(g_Configure.GetString(g_Keys.names.email)); + cfg.country.assign(g_Configure.GetString(g_Keys.names.country)); + cfg.sponsor.assign(g_Configure.GetString(g_Keys.names.sponsor)); + std::ostringstream ss; + ss << g_Version; + cfg.version.assign(ss.str()); + cfg.almod[toUType(EUrfdAlMod::nxdn)] = g_Configure.GetString(g_Keys.nxdn.autolinkmod).at(0); + cfg.almod[toUType(EUrfdAlMod::p25)] = g_Configure.GetString(g_Keys.p25.autolinkmod).at(0); + cfg.almod[toUType(EUrfdAlMod::ysf)] = g_Configure.GetString(g_Keys.ysf.autolinkmod).at(0); + cfg.ysffreq[toUType(EUrfdTxRx::rx)] = g_Configure.GetUnsigned(g_Keys.ysf.defaultrxfreq); + cfg.ysffreq[toUType(EUrfdTxRx::tx)] = g_Configure.GetUnsigned(g_Keys.ysf.defaulttxfreq); + cfg.refid[toUType(EUrfdRefId::nxdn)] = g_Configure.GetUnsigned(g_Keys.nxdn.reflectorid); + cfg.refid[toUType(EUrfdRefId::p25)] = g_Configure.GetUnsigned(g_Keys.p25.reflectorid); + cfg.g3enabled = g_Configure.GetBoolean(g_Keys.g3.enable); + cfg.port[toUType(EUrfdPorts::dcs)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dcs.port); + cfg.port[toUType(EUrfdPorts::dextra)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dextra.port); + cfg.port[toUType(EUrfdPorts::dmrplus)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dmrplus.port); + cfg.port[toUType(EUrfdPorts::dplus)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dplus.port); + cfg.port[toUType(EUrfdPorts::m17)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.m17.port); + cfg.port[toUType(EUrfdPorts::mmdvm)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.mmdvm.port); + cfg.port[toUType(EUrfdPorts::nxdn)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.nxdn.port); + cfg.port[toUType(EUrfdPorts::p25)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.p25.port); + cfg.port[toUType(EUrfdPorts::urf)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.urf.port); + cfg.port[toUType(EUrfdPorts::ysf)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.ysf.port); + for (const auto m : cfg.mods) + cfg.description[m] = g_Configure.GetString(g_Keys.modules.descriptor[m-'A']); + + auto nv = std::make_shared(cfg); + nv->user_type.assign("urfd-config-0"); + nv->id = toUType(EUrfdValueID::Config); + + node.putSigned( + refhash, + nv, + [](bool success){ std::cout << "PutDHTConfig() " << (success ? "successful" : "unsuccessful") << std::endl; }, + true + ); +} + +void CReflector::GetDHTConfig(const std::string &cs) +{ + static SUrfdConfig0 cfg; + cfg.timestamp = 0; // everytime this is called, zero the timestamp + auto item = g_IFile.FindMapItem(cs); + if (nullptr == item) + { + std::cerr << "Can't Get() for " << cs << " because it doesn't exist" << std::endl; + return; + } + std::cout << "Getting " << cs << " connection info..." << std::endl; + + // we only want the configuration section of the reflector's document + dht::Where w; + w.id(toUType(EUrfdValueID::Config)); + + node.get( + dht::InfoHash::get(cs), + [](const std::shared_ptr &v) { + if (0 == v->user_type.compare("mrefd-config-1")) + { + auto rdat = dht::Value::unpack(*v); + if (rdat.timestamp > cfg.timestamp) + { + // the time stamp is the newest so far, so put it in the static cfg struct + cfg = dht::Value::unpack(*v); + } + } + else + { + std::cerr << "Get() returned unknown user_type: '" << v->user_type << "'" << std::endl; + } + return true; // check all the values returned + }, + [](bool success) { + if (success) + { + if (cfg.timestamp) + { + // if the get() call was successful and there is a nonzero timestamp, then do the update + g_IFile.Update(cfg.cs, cfg.mods, cfg.ipv4, cfg.ipv6, cfg.port, cfg.emods); + } + } + else + { + std::cout << "Get() was unsuccessful" << std::endl; + } + }, + {}, // empty filter + w // just the configuration section + ); +} + +#endif diff --git a/reflector/Reflector.h b/reflector/Reflector.h index 432ce68..5292713 100644 --- a/reflector/Reflector.h +++ b/reflector/Reflector.h @@ -46,6 +46,7 @@ class CReflector { public: + CReflector(); // destructor ~CReflector(); diff --git a/reflector/User.h b/reflector/User.h index d5ed1d2..7207660 100644 --- a/reflector/User.h +++ b/reflector/User.h @@ -34,6 +34,13 @@ public: // destructor ~CUser() {} + // get + const std::string GetCallsign(void) const { return m_My.GetCS(); } + const std::string GetViaNode(void) const { return m_Rpt1.GetCS(); } + char GetOnModule(void) const { return m_Rpt2.GetCSModule(); } + const std::string GetViaPeer(void) const { return m_Xlx.GetCS(); } + std::time_t GetLastHeardTime(void) const { return m_LastHeardTime; } + // operation void HeardNow(void) { m_LastHeardTime = time(nullptr); } diff --git a/reflector/Users.h b/reflector/Users.h index f4e3343..85061bf 100644 --- a/reflector/Users.h +++ b/reflector/Users.h @@ -43,6 +43,8 @@ public: // pass-thru std::list::iterator begin() { return m_Users.begin(); } std::list::iterator end() { return m_Users.end(); } + std::list::const_iterator cbegin() { return m_Users.cbegin(); } + std::list::const_iterator cend() { return m_Users.cend(); } // operation void Hearing(const CCallsign &, const CCallsign &, const CCallsign &); diff --git a/reflector/urfd-dht-values.h b/reflector/urfd-dht-values.h index 50a4438..210c391 100644 --- a/reflector/urfd-dht-values.h +++ b/reflector/urfd-dht-values.h @@ -54,8 +54,8 @@ struct SUrfdClients0 }; /* USERS */ -using UserTuple = std::tuple; -enum class EUrfdUserFields { Source, Destination, Reflector, LastHeardTime }; +using UserTuple = std::tuple; +enum class EUrfdUserFields { Callsign, ViaNode, OnModule, ViaPeer, LastHeardTime }; struct SUrfdUsers0 { std::time_t timestamp; @@ -66,21 +66,21 @@ struct SUrfdUsers0 }; /* CONFIGURATION */ -// 'SIZE' has to be last!! -enum class EUrfdPorts : unsigned { dcs, dextra, dmrplus, dplus, m17, mmdvm, nxdn, p25, urf, usrprx, usrptx, ysf, SIZE }; +// 'SIZE' has to be last for these scoped enums +enum class EUrfdPorts : unsigned { dcs, dextra, dmrplus, dplus, m17, mmdvm, nxdn, p25, urf, ysf, SIZE }; enum class EUrfdAlMod : unsigned { nxdn, p25, ysf, SIZE }; enum class EUrfdTxRx : unsigned { rx, tx, SIZE }; enum class EUrfdRefId : unsigned { nxdn, p25, SIZE }; struct SUrfdConfig0 { std::time_t timestamp; - std::string cs, ipv4, ipv6, mods, url, email, sponsor, country, version; - std::array port; + std::string cs, ipv4, ipv6, mods, tcmods, url, email, sponsor, country, version; + std::array port; std::array almod; std::array ysffreq; std::array refid; std::unordered_map description; bool g3enabled; - MSGPACK_DEFINE(timestamp, cs, ipv4, ipv6, mods, url, email, sponsor, country, version, almod, ysffreq, refid, g3enabled, port, description) + MSGPACK_DEFINE(timestamp, cs, ipv4, ipv6, mods, tcmods, url, email, sponsor, country, version, almod, ysffreq, refid, g3enabled, port, description) };