pull/1/head
Tom Early 3 years ago
parent fd9ddd7181
commit cd30b7f771

@ -26,7 +26,7 @@ The rest of this README is unchanged from the original.
## Introduction ## Introduction
This will build a new kind of digital voice reflector. Based on N7TAE's [new-xlxd](https://github.com/n7tae/new-xlxd), which, in turn, is based on the first multi-protocol reflector, [xlxd](https://github.com/LX3JL/xlxd), **urfd** supports all protocols of it's predecessors, as well as both M17 protocols, **voice-only** and **voice+data**! A key part of this is the hybrid transcoder, [tcd](https://github.com/n7tae/tcd), which is in a seperate repository. URFd is not compatible with either new-xlxd or xlxd. You can't interlink urfd with xlxd. This reflector can be built without a transcoder, but clients will only hear other clients using the same protocol. Please note that currently, urfd only supports the tcd transcoder when run locally. For best performance, urfd and tcd uses UNIX DGRAM sockets for interprocess communications. These kernal-base sockets are signifantly faster than conventional UDP/IP sockets. It should be noted that tcd supports DVSI-3003 nad DVSI-3000 devices, which it uses for AMBE vocoding. This will build a new kind of digital voice reflector. Based on N7TAE's [new-xlxd](https://github.com/n7tae/new-xlxd), which, in turn, is based on the first multi-protocol reflector, [xlxd](https://github.com/LX3JL/xlxd), **urfd** supports all protocols of it's predecessors, as well as both M17 protocols, **voice-only** and **voice+data**! A key part of this is the hybrid transcoder, [tcd](https://github.com/n7tae/tcd), which is in a seperate repository. URFd is not compatible with either new-xlxd or xlxd. You can't interlink urfd with xlxd. This reflector can be built without a transcoder, but clients will only hear other clients using the same protocol. Please note that currently, urfd only supports the tcd transcoder when run locally. For best performance, urfd and tcd uses UNIX DGRAM sockets for interprocess communications. These kernel-base sockets are signifantly faster than conventional UDP/IP sockets. It should be noted that tcd supports DVSI-3003 nad DVSI-3000 devices, which it uses for AMBE vocoding.
This build support *dual-stack* operation, so the server on which it's running, must have both an IPv4 and IPv6 routable address if you are going to configure a dual-stack reflector. URF can support out-going DExtra links, by adding a new DExtra Peer type *and* it has many changes designed to increase reliability and stability. This build support *dual-stack* operation, so the server on which it's running, must have both an IPv4 and IPv6 routable address if you are going to configure a dual-stack reflector. URF can support out-going DExtra links, by adding a new DExtra Peer type *and* it has many changes designed to increase reliability and stability.

@ -16,6 +16,7 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ */
#include <sys/stat.h>
#include <stdlib.h> #include <stdlib.h>
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
@ -34,10 +35,8 @@
#define JBLACKLISTPATH "BlacklistPath" #define JBLACKLISTPATH "BlacklistPath"
#define JBRANDMEISTER "Brandmeister" #define JBRANDMEISTER "Brandmeister"
#define JCALLSIGN "Callsign" #define JCALLSIGN "Callsign"
#define JCLIENTSPATH "ClientFilePath"
#define JCOUNTRY "Country" #define JCOUNTRY "Country"
#define JDCS "DCS" #define JDCS "DCS"
#define JDEFAULTCALLSIGN "DefaultCallsign"
#define JDEFAULTID "DefaultId" #define JDEFAULTID "DefaultId"
#define JDEFAULTRXFREQ "DefaultRxFreq" #define JDEFAULTRXFREQ "DefaultRxFreq"
#define JDEFAULTTXFREQ "DefaultTxFreq" #define JDEFAULTTXFREQ "DefaultTxFreq"
@ -46,8 +45,10 @@
#define JDMRIDDB "DMR ID DB" #define JDMRIDDB "DMR ID DB"
#define JDMRPLUS "DMRPlus" #define JDMRPLUS "DMRPlus"
#define JDPLUS "DPlus" #define JDPLUS "DPlus"
#define JENABLE "Enable"
#define JFILES "Files" #define JFILES "Files"
#define JFILEPATH "FilePath" #define JFILEPATH "FilePath"
#define JG3 "G3"
#define JG3TERMINALPATH "G3TerminalPath" #define JG3TERMINALPATH "G3TerminalPath"
#define JINTERLINKPATH "InterlinkPath" #define JINTERLINKPATH "InterlinkPath"
#define JIPADDRESSES "IpAddresses" #define JIPADDRESSES "IpAddresses"
@ -58,6 +59,7 @@
#define JM17 "M17" #define JM17 "M17"
#define JMMDVM "MMDVM" #define JMMDVM "MMDVM"
#define JMODE "Mode" #define JMODE "Mode"
#define JMODULE "Module"
#define JMODULES "Modules" #define JMODULES "Modules"
#define JNAMES "Names" #define JNAMES "Names"
#define JNXDNIDDB "NXDN ID DB" #define JNXDNIDDB "NXDN ID DB"
@ -178,6 +180,8 @@ bool CConfigure::ReadData(const std::string &path)
section = ESection::dplus; section = ESection::dplus;
else if (0 == hname.compare(JDEXTRA)) else if (0 == hname.compare(JDEXTRA))
section = ESection::dextra; section = ESection::dextra;
else if (0 == hname.compare(JG3))
section = ESection::g3;
else if (0 == hname.compare(JDMRPLUS)) else if (0 == hname.compare(JDMRPLUS))
section = ESection::dmrplus; section = ESection::dmrplus;
else if (0 == hname.compare(JMMDVM)) else if (0 == hname.compare(JMMDVM))
@ -241,7 +245,7 @@ bool CConfigure::ReadData(const std::string &path)
{ {
case ESection::names: case ESection::names:
if (0 == key.compare(JCALLSIGN)) if (0 == key.compare(JCALLSIGN))
data[g_Keys.names.cs] = value; data[g_Keys.names.callsign] = value;
else if (0 == key.compare(JSYSOPEMAIL)) else if (0 == key.compare(JSYSOPEMAIL))
data[g_Keys.names.email] = value; data[g_Keys.names.email] = value;
else if (0 == key.compare(JCOUNTRY)) else if (0 == key.compare(JCOUNTRY))
@ -313,6 +317,8 @@ bool CConfigure::ReadData(const std::string &path)
case ESection::bm: case ESection::bm:
if (0 == key.compare(JPORT)) if (0 == key.compare(JPORT))
data[g_Keys.bm.port] = getUnsigned(value, "Brandmeister Port", 1024, 65535, 10002); data[g_Keys.bm.port] = getUnsigned(value, "Brandmeister Port", 1024, 65535, 10002);
else if (0 == key.compare(JENABLE))
data[g_Keys.bm.enable] = IS_TRUE(value[0]);
else else
badParam(key); badParam(key);
break; break;
@ -328,6 +334,12 @@ bool CConfigure::ReadData(const std::string &path)
else else
badParam(key); badParam(key);
break; break;
case ESection::g3:
if (0 == key.compare(JENABLE))
data[g_Keys.g3.enable] = IS_TRUE(value[0]);
else
badParam(key);
break;
case ESection::dmrplus: case ESection::dmrplus:
if (0 == key.compare(JPORT)) if (0 == key.compare(JPORT))
data[g_Keys.dmrplus.port] = getUnsigned(value, "DMRPlus Port", 1024, 65535, 8880); data[g_Keys.dmrplus.port] = getUnsigned(value, "DMRPlus Port", 1024, 65535, 8880);
@ -381,21 +393,16 @@ bool CConfigure::ReadData(const std::string &path)
badParam(key); badParam(key);
break; break;
case ESection::usrp: case ESection::usrp:
if (0 == key.compare(JPORT)) if (0 == key.compare(JENABLE))
data[g_Keys.usrp.port] = getUnsigned(value, "USRP Port", 1024, 65535, 34001); data[g_Keys.usrp.enable] = IS_TRUE(value[0]);
else if (0 == key.compare(JAUTOLINKMODULE)) else if (0 == key.compare(JPORT))
setAutolink(JUSRP, g_Keys.usrp.autolinkmod, value); data[g_Keys.usrp.port] = getUnsigned(value, "USRP Port", 1024, 65535, 32000);
else if (0 == key.compare(JDEFAULTCALLSIGN)) else if (0 == key.compare(JMODULE))
{ data[g_Keys.usrp.module] = value.substr(0, 1);
std::string cs; else if (0 == key.compare(JCALLSIGN))
for (auto &c : value) data[g_Keys.usrp.callsign] = value;
if (isalnum(c)) else if (0 == key.compare(JFILEPATH))
cs.append(1, toupper(c)); data[g_Keys.usrp.filepath] = value;
if (cs.size() > 7) cs.resize(7);
data[g_Keys.usrp.defaultcallsign] = cs;
}
else if (0 == key.compare(JCLIENTSPATH))
data[g_Keys.usrp.clientfilepath] = value;
else else
badParam(key); badParam(key);
break; break;
@ -478,13 +485,13 @@ bool CConfigure::ReadData(const std::string &path)
////////////////////////////// check the input ////////////////////////////// check the input
// Names section // Names section
if (isDefined(ErrorLevel::fatal, JNAMES, JCALLSIGN, g_Keys.names.cs, rval)) if (isDefined(ErrorLevel::fatal, JNAMES, JCALLSIGN, g_Keys.names.callsign, rval))
{ {
const auto cs = data[g_Keys.names.cs].get<std::string>(); const auto cs = data[g_Keys.names.callsign].get<std::string>();
auto RefRegEx = std::regex("^URF([A-Z0-9]){3,3}$", std::regex::extended); auto RefRegEx = std::regex("^URF([A-Z0-9]){3,3}$", std::regex::extended);
if (! std::regex_match(cs, RefRegEx)) if (! std::regex_match(cs, RefRegEx))
{ {
std::cerr << "ERROR: [" << JNAMES << "] " << JCALLSIGN << " '" << cs << "' is malformed" << std::endl; std::cerr << "ERROR: [" << JNAMES << ']' << JCALLSIGN << " '" << cs << "' is malformed" << std::endl;
rval = true; rval = true;
} }
} }
@ -586,7 +593,7 @@ bool CConfigure::ReadData(const std::string &path)
// how many transcoded modules // how many transcoded modules
auto size = tcmods.size(); auto size = tcmods.size();
if (3 != size && 1 != size) if (3 != size && 1 != size)
std::cout << "WARNING: [" << JMODULES << "] " << JTRANSCODED << " doesn't define three (or one) modules" << std::endl; std::cout << "WARNING: [" << JMODULES << ']' << JTRANSCODED << " doesn't define three (or one) modules" << std::endl;
// make sure each transcoded module is configured // make sure each transcoded module is configured
for (auto c : data[g_Keys.modules.tcmodules]) for (auto c : data[g_Keys.modules.tcmodules])
@ -626,29 +633,68 @@ bool CConfigure::ReadData(const std::string &path)
} }
// "simple" protocols with only a Port // "simple" protocols with only a Port
isDefined(ErrorLevel::fatal, JBRANDMEISTER, JPORT, g_Keys.bm.port, rval);
isDefined(ErrorLevel::fatal, JDCS, JPORT, g_Keys.dcs.port, rval); isDefined(ErrorLevel::fatal, JDCS, JPORT, g_Keys.dcs.port, rval);
isDefined(ErrorLevel::fatal, JDEXTRA, JPORT, g_Keys.dextra.port, rval); isDefined(ErrorLevel::fatal, JDEXTRA, JPORT, g_Keys.dextra.port, rval);
isDefined(ErrorLevel::fatal, JDMRPLUS, JPORT, g_Keys.dmrplus.port, rval); isDefined(ErrorLevel::fatal, JDMRPLUS, JPORT, g_Keys.dmrplus.port, rval);
isDefined(ErrorLevel::fatal, JDPLUS, JPORT, g_Keys.dplus.port, rval); isDefined(ErrorLevel::fatal, JDPLUS, JPORT, g_Keys.dplus.port, rval);
isDefined(ErrorLevel::fatal, JM17, JPORT, g_Keys.m17.port, rval); isDefined(ErrorLevel::fatal, JM17, JPORT, g_Keys.m17.port, rval);
isDefined(ErrorLevel::fatal, JURF, JPORT, g_Keys.urf.port, rval); isDefined(ErrorLevel::fatal, JURF, JPORT, g_Keys.urf.port, rval);
// BM
if (isDefined(ErrorLevel::fatal, JBRANDMEISTER, JENABLE, g_Keys.bm.enable, rval))
{
if (GetBoolean(g_Keys.bm.enable))
{
isDefined(ErrorLevel::fatal, JBRANDMEISTER, JPORT, g_Keys.bm.port, rval);
}
}
// G3
isDefined(ErrorLevel::fatal, JG3, JENABLE, g_Keys.g3.enable, rval);
// MMDVM // MMDVM
isDefined(ErrorLevel::fatal, JMMDVM, JPORT, g_Keys.mmdvm.port, rval); isDefined(ErrorLevel::fatal, JMMDVM, JPORT, g_Keys.mmdvm.port, rval);
isDefined(ErrorLevel::fatal, JMMDVM, JDEFAULTID, g_Keys.mmdvm.defaultid, rval); isDefined(ErrorLevel::fatal, JMMDVM, JDEFAULTID, g_Keys.mmdvm.defaultid, rval);
// NXDN // NXDN
isDefined(ErrorLevel::fatal, JNXDN, JPORT, g_Keys.nxdn.port, rval); isDefined(ErrorLevel::fatal, JNXDN, JPORT, g_Keys.nxdn.port, rval);
checkAutoLink(JNXDN, JAUTOLINKMODULE, g_Keys.nxdn.autolinkmod, rval); checkAutoLink(JNXDN, JAUTOLINKMODULE, g_Keys.nxdn.autolinkmod, rval);
isDefined(ErrorLevel::fatal, JNXDN, JREFLECTORID, g_Keys.nxdn.reflectorid, rval); isDefined(ErrorLevel::fatal, JNXDN, JREFLECTORID, g_Keys.nxdn.reflectorid, rval);
// P25 // P25
isDefined(ErrorLevel::fatal, JP25, JPORT, g_Keys.p25.port, rval); isDefined(ErrorLevel::fatal, JP25, JPORT, g_Keys.p25.port, rval);
checkAutoLink(JP25, JAUTOLINKMODULE, g_Keys.p25.autolinkmod, rval); checkAutoLink(JP25, JAUTOLINKMODULE, g_Keys.p25.autolinkmod, rval);
isDefined(ErrorLevel::fatal, JP25, JREFLECTORID, g_Keys.p25.reflectorid, rval); isDefined(ErrorLevel::fatal, JP25, JREFLECTORID, g_Keys.p25.reflectorid, rval);
// USRP // USRP
isDefined(ErrorLevel::fatal, JUSRP, JPORT, g_Keys.usrp.port, rval); if (isDefined(ErrorLevel::fatal, JUSRP, JENABLE, g_Keys.usrp.enable, rval))
checkAutoLink(JUSRP, JAUTOLINKMODULE, g_Keys.usrp.autolinkmod, rval); {
isDefined(ErrorLevel::fatal, JUSRP, JDEFAULTCALLSIGN, g_Keys.usrp.defaultcallsign, rval); if (GetBoolean(g_Keys.usrp.enable))
isDefined(ErrorLevel::fatal, JUSRP, JCLIENTSPATH, g_Keys.usrp.clientfilepath, rval); {
if (IsString(g_Keys.modules.tcmodules))
{
if (isDefined(ErrorLevel::fatal, JUSRP, JMODULE, g_Keys.usrp.module, rval))
{
if (std::string::npos == GetString(g_Keys.modules.tcmodules).find(GetString(g_Keys.usrp.module).at(0)))
{
std::cerr << "ERROR: [" << JUSRP << ']' << JMODULE << " is not a transcoded module" << std::endl;
rval = true;
}
}
isDefined(ErrorLevel::fatal, JUSRP, JPORT, g_Keys.usrp.port, rval);
isDefined(ErrorLevel::fatal, JUSRP, JCALLSIGN, g_Keys.usrp.callsign, rval);
//if (isDefined(ErrorLevel::fatal, JUSRP, JFILEPATH, g_Keys.usrp.filepath, rval))
if (data.contains(g_Keys.usrp.filepath))
checkFile(JUSRP, JFILEPATH, data[g_Keys.usrp.filepath]);
}
else
{
std::cerr << "ERROR: " << JUSRP << " requires a transoder" << std::endl;
rval = true;
}
}
}
// YSF // YSF
isDefined(ErrorLevel::fatal, JYSF, JPORT, g_Keys.ysf.port, rval); isDefined(ErrorLevel::fatal, JYSF, JPORT, g_Keys.ysf.port, rval);
checkAutoLink(JYSF, JAUTOLINKMODULE, g_Keys.ysf.autolinkmod, rval); checkAutoLink(JYSF, JAUTOLINKMODULE, g_Keys.ysf.autolinkmod, rval);
@ -657,6 +703,7 @@ bool CConfigure::ReadData(const std::string &path)
isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONID, g_Keys.ysf.ysfreflectordb.id, rval); isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONID, g_Keys.ysf.ysfreflectordb.id, rval);
isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONNAME, g_Keys.ysf.ysfreflectordb.name, rval); isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONNAME, g_Keys.ysf.ysfreflectordb.name, rval);
isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONDESCRIPTION, g_Keys.ysf.ysfreflectordb.description, rval); isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONDESCRIPTION, g_Keys.ysf.ysfreflectordb.description, rval);
// Databases // Databases
std::list<std::pair<const std::string, const struct SJsonKeys::DB *>> dbs = { std::list<std::pair<const std::string, const struct SJsonKeys::DB *>> dbs = {
{ JDMRIDDB, &g_Keys.dmriddb }, { JDMRIDDB, &g_Keys.dmriddb },
@ -665,18 +712,36 @@ bool CConfigure::ReadData(const std::string &path)
}; };
for ( auto &item : dbs ) for ( auto &item : dbs )
{ {
isDefined(ErrorLevel::fatal, item.first, JURL, item.second->url, rval); if (isDefined(ErrorLevel::fatal, item.first, JMODE, item.second->mode, rval))
isDefined(ErrorLevel::fatal, item.first, JMODE, item.second->mode, rval); {
isDefined(ErrorLevel::fatal, item.first, JREFRESHMIN, item.second->refreshmin, rval); if (ERefreshType::file != GetRefreshType(item.second->mode))
isDefined(ErrorLevel::fatal, item.first, JFILEPATH, item.second->filepath, rval); {
isDefined(ErrorLevel::fatal, item.first, JURL, item.second->url, rval);
isDefined(ErrorLevel::fatal, item.first, JREFRESHMIN, item.second->refreshmin, rval);
}
if (ERefreshType::http != GetRefreshType(item.second->mode))
{
if (isDefined(ErrorLevel::fatal, item.first, JFILEPATH, item.second->filepath, rval))
checkFile(item.first, JFILEPATH, data[item.second->filepath]);
}
}
} }
// Other files // Other files
isDefined(ErrorLevel::fatal, JFILES, JPIDPATH, g_Keys.files.pid, rval); isDefined(ErrorLevel::fatal, JFILES, JPIDPATH, g_Keys.files.pid, rval);
isDefined(ErrorLevel::fatal, JFILES, JREFLSTATEPATH, g_Keys.files.state, rval); isDefined(ErrorLevel::fatal, JFILES, JREFLSTATEPATH, g_Keys.files.state, rval);
isDefined(ErrorLevel::fatal, JFILES, JWHITELISTPATH, g_Keys.files.white, rval); if (isDefined(ErrorLevel::fatal, JFILES, JWHITELISTPATH, g_Keys.files.white, rval))
isDefined(ErrorLevel::fatal, JFILES, JBLACKLISTPATH, g_Keys.files.black, rval); checkFile(JFILES, JWHITELISTPATH, data[g_Keys.files.white]);
isDefined(ErrorLevel::fatal, JFILES, JINTERLINKPATH, g_Keys.files.interlink, rval); if (isDefined(ErrorLevel::fatal, JFILES, JBLACKLISTPATH, g_Keys.files.black, rval))
isDefined(ErrorLevel::fatal, JFILES, JG3TERMINALPATH, g_Keys.files.terminal, rval); checkFile(JFILES, JBLACKLISTPATH, data[g_Keys.files.black]);
if (isDefined(ErrorLevel::fatal, JFILES, JINTERLINKPATH, g_Keys.files.interlink, rval))
checkFile(JFILES, JINTERLINKPATH, data[g_Keys.files.interlink]);
if (data.contains(g_Keys.g3.enable) && GetBoolean(g_Keys.g3.enable))
{
if (isDefined(ErrorLevel::fatal, JFILES, JG3TERMINALPATH, g_Keys.files.terminal, rval))
checkFile(JFILES, JG3TERMINALPATH, data[g_Keys.files.terminal]);
}
return rval; return rval;
} }
@ -688,12 +753,12 @@ bool CConfigure::isDefined(ErrorLevel level, const std::string &section, const s
if (ErrorLevel::mild == level) if (ErrorLevel::mild == level)
{ {
std::cout << "WARNING: [" << section << "] " << pname << " is not defined" << std::endl; std::cout << "WARNING: [" << section << ']' << pname << " is not defined" << std::endl;
data[key] = nullptr; data[key] = nullptr;
} }
else else
{ {
std::cerr << "ERROR: [" << section << "] " << pname << " is not defined" << std::endl; std::cerr << "ERROR: [" << section << ']' << pname << " is not defined" << std::endl;
rval = true; rval = true;
} }
return false; return false;
@ -708,7 +773,7 @@ void CConfigure::checkAutoLink(const std::string &section, const std::string &pn
const auto c = data[key].get<std::string>().at(0); const auto c = data[key].get<std::string>().at(0);
if (std::string::npos == mods.find(c)) if (std::string::npos == mods.find(c))
{ {
std::cerr << "ERROR: [" << section << "] " << pname << " module '" << c << "' not a configured module" << std::endl; std::cerr << "ERROR: [" << section << ']' << pname << " module '" << c << "' not a configured module" << std::endl;
rval = true; rval = true;
} }
} }
@ -767,6 +832,16 @@ void CConfigure::setAutolink(const std::string &section, const std::string &key,
std::cout << "WARNING: line #" << counter << ": " << section << " AutoLinkModule is invalid: '" << value.substr(0, 1) << "'" << std::endl; std::cout << "WARNING: line #" << counter << ": " << section << " AutoLinkModule is invalid: '" << value.substr(0, 1) << "'" << std::endl;
} }
void CConfigure::checkFile(const std::string &section, const std::string &key, const std::string &filepath) const
{
struct stat sstat;
auto rval = stat(filepath.c_str(), &sstat);
if (rval)
{
std::cout << "WARNING: [" << section << ']' << key << " \"" << filepath << "\": " << strerror(errno) << std::endl;
}
}
void CConfigure::Dump(bool justpublic) const void CConfigure::Dump(bool justpublic) const
{ {
nlohmann::json tmpjson = data; nlohmann::json tmpjson = data;
@ -843,6 +918,14 @@ unsigned CConfigure::GetUnsigned(const std::string &key) const
return u; return u;
} }
bool CConfigure::GetBoolean(const std::string &key) const
{
if (data[key].is_boolean())
return data[key];
else
return false;
}
char CConfigure::GetAutolinkModule(const std::string &key) const char CConfigure::GetAutolinkModule(const std::string &key) const
{ {
char c = 0; char c = 0;

@ -25,7 +25,7 @@
enum class ErrorLevel { fatal, mild }; enum class ErrorLevel { fatal, mild };
enum class ERefreshType { file, http, both }; enum class ERefreshType { file, http, both };
enum class ESection { none, names, ip, modules, urf, dplus, dextra, dcs, dmrplus, mmdvm, nxdn, bm, ysf, p25, m17, usrp, dmrid, nxdnid, ysffreq, files }; enum class ESection { none, names, ip, modules, urf, dplus, dextra, dcs, g3, dmrplus, mmdvm, nxdn, bm, ysf, p25, m17, usrp, dmrid, nxdnid, ysffreq, files };
#define IS_TRUE(a) ((a)=='t' || (a)=='T' || (a)=='1') #define IS_TRUE(a) ((a)=='t' || (a)=='T' || (a)=='1')
@ -37,6 +37,7 @@ public:
void Dump(bool justpublic) const; void Dump(bool justpublic) const;
std::string GetString(const std::string &key) const; std::string GetString(const std::string &key) const;
unsigned GetUnsigned(const std::string &key) const; unsigned GetUnsigned(const std::string &key) const;
bool GetBoolean(const std::string &key) const;
ERefreshType GetRefreshType(const std::string &key) const; ERefreshType GetRefreshType(const std::string &key) const;
bool IsString(const std::string &key) const; bool IsString(const std::string &key) const;
char GetAutolinkModule(const std::string &key) const; char GetAutolinkModule(const std::string &key) const;
@ -51,6 +52,7 @@ private:
unsigned getUnsigned(const std::string &value, const std::string &label, unsigned min, unsigned max, unsigned defaultvalue) const; unsigned getUnsigned(const std::string &value, const std::string &label, unsigned min, unsigned max, unsigned defaultvalue) const;
void badParam(const std::string &param) const; void badParam(const std::string &param) const;
bool checkModules(std::string &m) const; bool checkModules(std::string &m) const;
void checkFile(const std::string &section, const std::string &key, const std::string &filepath) const;
void setAutolink(const std::string &section, const std::string &key, const std::string &value); void setAutolink(const std::string &section, const std::string &key, const std::string &value);
bool isDefined(ErrorLevel level, const std::string &section, const std::string &pname, const std::string &key, bool &rval); bool isDefined(ErrorLevel level, const std::string &section, const std::string &pname, const std::string &key, bool &rval);
void checkAutoLink(const std::string &section, const std::string &pname, const std::string &key, bool &rval); void checkAutoLink(const std::string &section, const std::string &pname, const std::string &key, bool &rval);

@ -23,35 +23,49 @@
// configuration key names // configuration key names
struct SJsonKeys { struct SJsonKeys {
struct PORTONLY { const std::string port; } struct PORTONLY { const std::string port; }
bm { "BrandMeisterPort" },
dcs { "DCSPort" }, dcs { "DCSPort" },
dextra { "DExtraPort" }, dextra { "DExtraPort" },
dmrplus { "DMRPlusPort" }, dmrplus { "DMRPlusPort" },
dplus { "DPlusPort" }, dplus { "DPlusPort" },
m17 { "M17Port" }, m17 { "M17Port" },
urf { "URFPort" }; urf { "URFPort" };
struct G3 { const std::string enable; }
g3 { "G3Enable" };
struct BM { const std::string enable, port; }
bm { "bmEnable", "bmPort" };
struct MMDVM { const std::string port, defaultid; } struct MMDVM { const std::string port, defaultid; }
mmdvm { "MMDVMPort", "mmdvmdefaultid" }; mmdvm { "MMDVMPort", "mmdvmdefaultid" };
struct NAMES { const std::string cs, email, country, sponsor; }
struct NAMES { const std::string callsign, email, country, sponsor; }
names { "Callsign", "SysopEmail", "Country", "Sponsor" }; names { "Callsign", "SysopEmail", "Country", "Sponsor" };
struct IP { const std::string ipv4bind, ipv4address, ipv6bind, ipv6address, transcoder; } struct IP { const std::string ipv4bind, ipv4address, ipv6bind, ipv6address, transcoder; }
ip { "ipv4bind", "IPv4Address", "ipv6bind", "IPv6Address", "tcaddress" }; ip { "ipv4bind", "IPv4Address", "ipv6bind", "IPv6Address", "tcaddress" };
struct MODULES { const std::string modules, tcmodules, descriptor[26]; } struct MODULES { const std::string modules, tcmodules, descriptor[26]; }
modules { "Modules", "TranscodedModules", modules { "Modules", "TranscodedModules",
"DescriptionA", "DescriptionB", "DescriptionC", "DescriptionD", "DescriptionE", "DescriptionF", "DescriptionG", "DescriptionH", "DescriptionI", "DescriptionJ", "DescriptionK", "DescriptionL", "DescriptionM", "DescriptionN", "DescriptionO", "DescriptionP", "DescriptionQ", "DescriptionR", "DescriptionS", "DescriptionT", "DescriptionU", "DescriptionV", "DescriptionW", "DescriptionX", "DescriptionY", "DescriptionZ" }; "DescriptionA", "DescriptionB", "DescriptionC", "DescriptionD", "DescriptionE", "DescriptionF", "DescriptionG", "DescriptionH", "DescriptionI", "DescriptionJ", "DescriptionK", "DescriptionL", "DescriptionM", "DescriptionN", "DescriptionO", "DescriptionP", "DescriptionQ", "DescriptionR", "DescriptionS", "DescriptionT", "DescriptionU", "DescriptionV", "DescriptionW", "DescriptionX", "DescriptionY", "DescriptionZ" };
struct USRP { const std::string port, autolinkmod, defaultcallsign, clientfilepath; }
usrp { "USRPPort", "USRPAutolinkMod", "usrpDefaultCallsign", "usrpClientfilePath" }; struct USRP { const std::string enable, port, module, callsign, filepath; }
usrp { "usrpEnable", "urspPort", "usrpModule", "usrpCallsign", "usrpFilePath" };
struct P25NXDN { const std::string port, autolinkmod, reflectorid; } struct P25NXDN { const std::string port, autolinkmod, reflectorid; }
p25 { "P25Port", "P25AutolinkMod", "P25ReflectorID" }, p25 { "P25Port", "P25AutolinkMod", "P25ReflectorID" },
nxdn { "NXDNPort", "NXDNAutolinkMod", "NXDNReflectorID" }; nxdn { "NXDNPort", "NXDNAutolinkMod", "NXDNReflectorID" };
struct YSF { const std::string port, autolinkmod, defaulttxfreq, defaultrxfreq; struct YSF { const std::string port, autolinkmod, defaulttxfreq, defaultrxfreq;
struct YSLREG { const std::string id, name, description; } ysfreflectordb; } struct YSLREG { const std::string id, name, description; } ysfreflectordb; }
ysf { "YSFPort", "YSFAutoLinkMod", "YSFDefaultTxFreq", "YSFDefaultRxFreq", ysf { "YSFPort", "YSFAutoLinkMod", "YSFDefaultTxFreq", "YSFDefaultRxFreq",
{ "ysfrefdbid", "ysfrefdbname", "ysfrefdbdesc" } }; { "ysfrefdbid", "ysfrefdbname", "ysfrefdbdesc" } };
struct DB { const std::string url, mode, refreshmin, filepath; } struct DB { const std::string url, mode, refreshmin, filepath; }
dmriddb { "dmrIdDbUrl", "dmrIdDbMode", "dmrIdDbRefresh", "dmrIdDbFilePath" }, dmriddb { "dmrIdDbUrl", "dmrIdDbMode", "dmrIdDbRefresh", "dmrIdDbFilePath" },
nxdniddb { "nxdnIdDbUrl", "nxdnIdDbMode", "nxdnIdDbRefresh", "nxdnIdDbFilePath" }, nxdniddb { "nxdnIdDbUrl", "nxdnIdDbMode", "nxdnIdDbRefresh", "nxdnIdDbFilePath" },
ysftxrxdb { "ysfIdDbUrl", "ysfIdDbMode", "ysfIdDbRefresh", "ysfIdDbFilePath" }; ysftxrxdb { "ysfIdDbUrl", "ysfIdDbMode", "ysfIdDbRefresh", "ysfIdDbFilePath" };
struct FILES { const std::string pid, state, white, black, interlink, terminal; } struct FILES { const std::string pid, state, white, black, interlink, terminal; }
files { "pidFilePath", "stateFilePath", "whitelistFilePath", "blacklistFilePath", "interlinkFilePath", "g3TerminalFilePath" }; files { "pidFilePath", "stateFilePath", "whitelistFilePath", "blacklistFilePath", "interlinkFilePath", "g3TerminalFilePath" };
}; };

@ -49,7 +49,7 @@ int main(int argc, char *argv[])
std::cout << "IPv4 binding address is '" << g_Conf.GetString(g_Keys.ip.ipv4bind) << "'" << std::endl; std::cout << "IPv4 binding address is '" << g_Conf.GetString(g_Keys.ip.ipv4bind) << "'" << std::endl;
// remove pidfile // remove pidfile
const std::string pidpath(g_Conf.GetString(g_Keys.files.pid)); const std::string pidpath(g_Conf.GetString(g_Keys.files.pid));
const std::string callsign(g_Conf.GetString(g_Keys.names.cs)); const std::string callsign(g_Conf.GetString(g_Keys.names.callsign));
remove(pidpath.c_str()); remove(pidpath.c_str());
// splash // splash

@ -64,9 +64,12 @@ bool CProtocols::Init(void)
if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrmmdvm, uint16_t(g_Conf.GetUnsigned(g_Keys.mmdvm.port)), DMR_IPV4, DMR_IPV6)) if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrmmdvm, uint16_t(g_Conf.GetUnsigned(g_Keys.mmdvm.port)), DMR_IPV4, DMR_IPV6))
return false; return false;
m_Protocols.emplace_back(std::unique_ptr<CBMProtocol>(new CBMProtocol)); if (g_Conf.GetBoolean(g_Keys.bm.enable))
if (! m_Protocols.back()->Initialize("XLX", EProtocol::bm, uint16_t(g_Conf.GetUnsigned(g_Keys.bm.port)), DMR_IPV4, DMR_IPV6)) {
return false; m_Protocols.emplace_back(std::unique_ptr<CBMProtocol>(new CBMProtocol));
if (! m_Protocols.back()->Initialize("XLX", EProtocol::bm, uint16_t(g_Conf.GetUnsigned(g_Keys.bm.port)), DMR_IPV4, DMR_IPV6))
return false;
}
m_Protocols.emplace_back(std::unique_ptr<CDmrplusProtocol>(new CDmrplusProtocol)); m_Protocols.emplace_back(std::unique_ptr<CDmrplusProtocol>(new CDmrplusProtocol));
if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrplus, uint16_t(g_Conf.GetUnsigned(g_Keys.dmrplus.port)), DMR_IPV4, DMR_IPV6)) if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrplus, uint16_t(g_Conf.GetUnsigned(g_Keys.dmrplus.port)), DMR_IPV4, DMR_IPV6))
@ -88,17 +91,23 @@ bool CProtocols::Init(void)
if (! m_Protocols.back()->Initialize("NXDN", EProtocol::nxdn, uint16_t(g_Conf.GetUnsigned(g_Keys.nxdn.port)), NXDN_IPV4, NXDN_IPV6)) if (! m_Protocols.back()->Initialize("NXDN", EProtocol::nxdn, uint16_t(g_Conf.GetUnsigned(g_Keys.nxdn.port)), NXDN_IPV4, NXDN_IPV6))
return false; return false;
m_Protocols.emplace_back(std::unique_ptr<CUSRPProtocol>(new CUSRPProtocol)); if (g_Conf.GetBoolean(g_Keys.usrp.enable))
if (! m_Protocols.back()->Initialize("USRP", EProtocol::usrp, uint16_t(g_Conf.GetUnsigned(g_Keys.usrp.port)), USRP_IPV4, USRP_IPV6)) {
return false; m_Protocols.emplace_back(std::unique_ptr<CUSRPProtocol>(new CUSRPProtocol));
if (! m_Protocols.back()->Initialize("USRP", EProtocol::usrp, uint16_t(g_Conf.GetUnsigned(g_Keys.usrp.port)), USRP_IPV4, USRP_IPV6))
return false;
}
m_Protocols.emplace_back(std::unique_ptr<CURFProtocol>(new CURFProtocol)); m_Protocols.emplace_back(std::unique_ptr<CURFProtocol>(new CURFProtocol));
if (! m_Protocols.back()->Initialize("URF", EProtocol::urf, uint16_t(g_Conf.GetUnsigned(g_Keys.urf.port)), URF_IPV4, URF_IPV6)) if (! m_Protocols.back()->Initialize("URF", EProtocol::urf, uint16_t(g_Conf.GetUnsigned(g_Keys.urf.port)), URF_IPV4, URF_IPV6))
return false; return false;
m_Protocols.emplace_back(std::unique_ptr<CG3Protocol>(new CG3Protocol)); if (g_Conf.GetBoolean(g_Keys.g3.enable))
if (! m_Protocols.back()->Initialize("XLX", EProtocol::g3, G3_DV_PORT, DMR_IPV4, DMR_IPV6)) {
m_Protocols.emplace_back(std::unique_ptr<CG3Protocol>(new CG3Protocol));
if (! m_Protocols.back()->Initialize("XLX", EProtocol::g3, G3_DV_PORT, DMR_IPV4, DMR_IPV6))
return false; return false;
}
} }
m_Mutex.unlock(); m_Mutex.unlock();

@ -47,7 +47,7 @@ CReflector::~CReflector()
bool CReflector::Start(void) bool CReflector::Start(void)
{ {
// get config stuff // get config stuff
m_Callsign = CCallsign(g_Conf.GetString(g_Keys.names.cs).c_str(), false); m_Callsign = CCallsign(g_Conf.GetString(g_Keys.names.callsign).c_str(), false);
m_Modules.assign(g_Conf.GetString(g_Keys.modules.modules)); m_Modules.assign(g_Conf.GetString(g_Keys.modules.modules));
std::string tcmods(g_Conf.GetString(g_Keys.modules.tcmodules)); std::string tcmods(g_Conf.GetString(g_Keys.modules.tcmodules));
@ -273,15 +273,15 @@ void CReflector::RouterThread(const char ThisModule)
m_Protocols.Lock(); m_Protocols.Lock();
for ( auto it=m_Protocols.begin(); it!=m_Protocols.end(); it++ ) for ( auto it=m_Protocols.begin(); it!=m_Protocols.end(); it++ )
{ {
// make a copy! after the Push(tmp), tmp will be nullptr!
auto copy = packet->Copy(); auto copy = packet->Copy();
// if packet is header, update RPT2 according to protocol // if packet is header, update RPT2 according to protocol
if ( copy->IsDvHeader() ) if ( copy->IsDvHeader() )
{ {
// get our callsign // make the protocol-patched reflector callsign
CCallsign csRPT = (*it)->GetReflectorCallsign(); CCallsign csRPT = (*it)->GetReflectorCallsign();
csRPT.SetCSModule(ThisModule); csRPT.SetCSModule(ThisModule);
// and put it in the copy
(dynamic_cast<CDvHeaderPacket *>(copy.get()))->SetRpt2Callsign(csRPT); (dynamic_cast<CDvHeaderPacket *>(copy.get()))->SetRpt2Callsign(csRPT);
} }

@ -1,8 +1,5 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector // urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE // Copyright © 2023 Doug McLain AD8DP
// Copyright © 2021 Doug McLain AD8DP
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
@ -37,13 +34,8 @@ const uint8_t TLV_TAG_SET_INFO = 8;
bool CUSRPProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) bool CUSRPProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6)
{ {
// config data
m_DefaultCallsign.assign(g_Conf.GetString(g_Keys.usrp.defaultcallsign));
m_AutolinkModule = g_Conf.GetAutolinkModule(g_Keys.usrp.autolinkmod);
CBuffer buffer; CBuffer buffer;
m_uiStreamId = 0; m_uiStreamId = 0;
CCallsign cs(m_DefaultCallsign.c_str());
CClients *clients = g_Refl.GetClients();
std::ifstream file; std::ifstream file;
std::streampos size; std::streampos size;
@ -51,7 +43,10 @@ bool CUSRPProtocol::Initialize(const char *type, const EProtocol ptype, const ui
if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6)) if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
return false; return false;
file.open(g_Conf.GetString(g_Keys.usrp.clientfilepath), std::ios::in | std::ios::binary | std::ios::ate); m_CStr.assign(g_Conf.GetString(g_Keys.usrp.callsign));
m_Module = g_Conf.GetString(g_Keys.usrp.module).at(0);
file.open(g_Conf.GetString(g_Keys.usrp.filepath), std::ios::in | std::ios::binary | std::ios::ate);
if ( file.is_open() ) if ( file.is_open() )
{ {
// read file // read file
@ -78,24 +73,24 @@ bool CUSRPProtocol::Initialize(const char *type, const EProtocol ptype, const ui
*ptr2 = 0; *ptr2 = 0;
char *ip; char *ip;
char *port; char *port;
if ((ip = ::strtok(ptr1, ";")) != nullptr) char *clientcs;
{
if ( ((port = ::strtok(nullptr, ";")) != nullptr) )
{
uint32_t ui = atoi(port);
CIp Ip(AF_INET, ui, ip);
auto newclient = std::make_shared<CUSRPClient>(cs, Ip);
if (m_AutolinkModule)
newclient->SetReflectorModule(m_AutolinkModule);
clients->AddClient(newclient); if ( ((ip = ::strtok(ptr1, ";")) != nullptr) &&
} ((port = ::strtok(nullptr, ";")) != nullptr) &&
((clientcs = ::strtok(nullptr, ";")) != nullptr) )
{
uint32_t ui = atoi(port);
CIp Ip(AF_INET, ui, ip);
CCallsign cs(clientcs);
auto newclient = std::make_shared<CUSRPClient>(cs, Ip);
newclient->SetReflectorModule(m_Module);
g_Refl.GetClients()->AddClient(newclient);
g_Refl.ReleaseClients();
} }
ptr1 = ptr2+1; ptr1 = ptr2+1;
} }
} }
g_Refl.ReleaseClients();
// update time // update time
m_LastKeepaliveTime.start(); m_LastKeepaliveTime.start();
@ -279,12 +274,14 @@ bool CUSRPProtocol::IsValidDvPacket(const CIp &Ip, const CBuffer &Buffer, std::u
if ( !stream ) if ( !stream )
{ {
m_uiStreamId = static_cast<uint32_t>(::rand()); m_uiStreamId = static_cast<uint32_t>(::rand());
CCallsign csMY = CCallsign(m_DefaultCallsign.c_str()); CCallsign csMY, rpt1, rpt2, csUR;
CCallsign rpt1 = CCallsign(m_DefaultCallsign.c_str()); csMY.SetCallsign(m_CStr, false);
CCallsign rpt2 = m_ReflectorCallsign; rpt1.SetCallsign(m_CStr, false);
rpt1.SetCSModule(USRP_MODULE_ID); rpt2 = m_ReflectorCallsign;
csUR.SetCallsign("CQCQCQ", false);
rpt1.SetCSModule(m_Module);
rpt2.SetCSModule(' '); rpt2.SetCSModule(' ');
header = std::unique_ptr<CDvHeaderPacket>(new CDvHeaderPacket(csMY, CCallsign("CQCQCQ"), rpt1, rpt2, m_uiStreamId, true)); header = std::unique_ptr<CDvHeaderPacket>(new CDvHeaderPacket(csMY, csUR, rpt1, rpt2, m_uiStreamId, true));
OnDvHeaderPacketIn(header, Ip); OnDvHeaderPacketIn(header, Ip);
} }
@ -310,9 +307,11 @@ bool CUSRPProtocol::IsValidDvHeaderPacket(const CIp &Ip, const CBuffer &Buffer,
CCallsign csMY = CCallsign("", uiSrcId); CCallsign csMY = CCallsign("", uiSrcId);
CCallsign rpt1 = CCallsign("", uiSrcId); CCallsign rpt1 = CCallsign("", uiSrcId);
CCallsign rpt2 = m_ReflectorCallsign; CCallsign rpt2 = m_ReflectorCallsign;
rpt1.SetCSModule(USRP_MODULE_ID); CCallsign csUR;
csUR.SetCallsign("CQCQCQ", false);
rpt1.SetCSModule(m_Module);
rpt2.SetCSModule(' '); rpt2.SetCSModule(' ');
header = std::unique_ptr<CDvHeaderPacket>(new CDvHeaderPacket(csMY, CCallsign("CQCQCQ"), rpt1, rpt2, m_uiStreamId, true)); header = std::unique_ptr<CDvHeaderPacket>(new CDvHeaderPacket(csMY, csUR, rpt1, rpt2, m_uiStreamId, true));
} }
return true; return true;
} }

@ -1,8 +1,5 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector // urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE // Copyright © 2023 Doug McLain AD8DP
// Copyright © 2021 Doug McLain AD8DP
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
@ -24,12 +21,9 @@
#include "Protocol.h" #include "Protocol.h"
#include "DVHeaderPacket.h" #include "DVHeaderPacket.h"
#include "DVFramePacket.h" #include "DVFramePacket.h"
#include "Timer.h"
#include "Clients.h"
////////////////////////////////////////////////////////////////////////////////////////
// define
#define USRP_MODULE_ID 'B'
////////////////////////////////////////////////////////////////////////////////////////
// class // class
class CUSRPStreamCacheItem class CUSRPStreamCacheItem
@ -74,7 +68,8 @@ protected:
std::unordered_map<char, CUSRPStreamCacheItem> m_StreamsCache; std::unordered_map<char, CUSRPStreamCacheItem> m_StreamsCache;
uint32_t m_uiStreamId; uint32_t m_uiStreamId;
// config data private:
std::string m_DefaultCallsign; // CConfigure data
char m_AutolinkModule; std::string m_CStr;
char m_Module;
}; };

@ -21,6 +21,7 @@ IPv4Binding = 0.0.0.0
# define if you want to override what urfd finds using ipv4.icanhazip.com # define if you want to override what urfd finds using ipv4.icanhazip.com
# IPv4External = 4.3.2.1 # IPv4External = 4.3.2.1
# define if you want to override what urfd finds using ipv6.icanhazip.com # define if you want to override what urfd finds using ipv6.icanhazip.com
# IPv6External = f:e:d:c:b:a:9:0 # IPv6External = f:e:d:c:b:a:9:0
@ -39,6 +40,7 @@ DescriptionZ = Temp Meeting
# Protocols # Protocols
[Brandmeister] [Brandmeister]
Enable = false # Set to true if you've configured BM connections in your urfd.interlink file.
Port = 10002 Port = 10002
[DCS] [DCS]
@ -47,12 +49,15 @@ Port = 30051
[DExtra] [DExtra]
Port = 30001 Port = 30001
[DMRPlus]
Port = 8880
[DPlus] [DPlus]
Port = 20001 Port = 20001
[G3]
Enable = true
[DMRPlus]
Port = 8880
[M17] [M17]
Port = 17000 Port = 17000
@ -74,10 +79,11 @@ ReflectorID = 12345
Port = 10017 Port = 10017
[USRP] [USRP]
Port = 34001 Enable = false
AutoLinkModule = A # comment out if you want to disable AL Port = 32000
DefaultCallsign = ALLSTAR Module = A # this has to be a transcoded module!
ClientFilePath = /usr/local/etc/USRPClients.txt Callsign = ALLSTAR
#FilePath = /home/usr/Clients.txt # An experimental area of development
[YSF] [YSF]
Port = 42000 Port = 42000
@ -93,20 +99,20 @@ RegistrationDescription = URF Reflector
[DMR ID DB] [DMR ID DB]
Mode = http #### Mode is "http", "file", or "both" Mode = http #### Mode is "http", "file", or "both"
#### if "both", the url will be read first #### if "both", the url will be read first
FilePath = /usr/local/etc/dmrid.dat # for you to add your own values FilePath = /home/user/dmrid.dat # for you to add your own values
# will be reloaded quickly, about 10s # will be reloaded within 10s
URL = http://xlxapi.rlx.lu/api/exportdmr.php # if Mode "http" or "both" URL = http://xlxapi.rlx.lu/api/exportdmr.php # if Mode "http" or "both"
RefreshMin = 179 # RefreshMin will only be used if the Mode is "file" RefreshMin = 179
[NXDN ID DB] [NXDN ID DB]
Mode = http Mode = http
FilePath = /usr/local/etc/nxdn.dat FilePath = /home/user/nxdn.dat
URL = https://radioid.net/static/nxdn.csv URL = https://radioid.net/static/nxdn.csv
RefreshMin = 1440 # radioid.net says this file is updated once/day RefreshMin = 1440 # radioid.net says this file is updated once/day
[YSF TX/RX DB] [YSF TX/RX DB]
Mode = http Mode = http
FilePath = /usr/local/etc/ysfnode.dat FilePath = /home/user/ysfnode.dat
URL = http://xlxapi.rlx.lu/api/exportysfrepeaters.php URL = http://xlxapi.rlx.lu/api/exportysfrepeaters.php
RefreshMin = 191 RefreshMin = 191
@ -114,7 +120,7 @@ RefreshMin = 191
[Files] [Files]
PidPath = /var/run/urfd.pid PidPath = /var/run/urfd.pid
ReflStatePath = /var/log/urfd.xml ReflStatePath = /var/log/urfd.xml
WhitelistPath = /usr/local/etc/urfd.whitelist WhitelistPath = /home/user/urfd.whitelist
BlacklistPath = /usr/local/etc/urfd.blacklist BlacklistPath = /home/user/urfd.blacklist
InterlinkPath = /usr/local/etc/urfd.interlink InterlinkPath = /home/user/urfd.interlink
G3TerminalPath = /usr/local/etc/urfd.terminal G3TerminalPath = /home/user/urfd.terminal

Loading…
Cancel
Save

Powered by TurnKey Linux.