redesign IfaceMap
Switch to periodic poll on dedicated worker thread instead of opportunistic poll on use.
This commit is contained in:
+13
-10
@@ -60,7 +60,7 @@ SockEndpoint::SockEndpoint(const char* ep, uint16_t defport)
|
||||
iface = at+1;
|
||||
}
|
||||
|
||||
auto& ifmap = IfaceMap::instance();
|
||||
auto ifmap(IfaceMap::instance());
|
||||
|
||||
if(addr.family()==AF_INET6) {
|
||||
if(iface.empty() && addr->in6.sin6_scope_id) {
|
||||
@@ -90,7 +90,7 @@ MCastMembership SockEndpoint::resolve() const
|
||||
if(!addr.isMCast())
|
||||
throw std::logic_error("not mcast");
|
||||
|
||||
auto& ifmap = IfaceMap::instance();
|
||||
auto ifmap(IfaceMap::instance());
|
||||
|
||||
MCastMembership m;
|
||||
m.af = addr.family();
|
||||
@@ -282,7 +282,8 @@ void printAddresses(std::vector<std::string>& out, const std::vector<SockEndpoin
|
||||
// Fill out address list by appending broadcast addresses
|
||||
// of any and all local interface addresses already included
|
||||
void expandAddrList(const std::vector<SockEndpoint>& ifaces,
|
||||
std::vector<SockEndpoint>& addrs)
|
||||
std::vector<SockEndpoint>& addrs,
|
||||
const IfaceMap& ifmap)
|
||||
{
|
||||
SockAttach attach;
|
||||
evsocket dummy(AF_INET, SOCK_DGRAM, 0);
|
||||
@@ -307,9 +308,9 @@ void expandAddrList(const std::vector<SockEndpoint>& ifaces,
|
||||
}
|
||||
|
||||
void addGroups(std::vector<SockEndpoint>& ifaces,
|
||||
const std::vector<SockEndpoint>& addrs)
|
||||
const std::vector<SockEndpoint>& addrs,
|
||||
const IfaceMap& ifmap)
|
||||
{
|
||||
auto& ifmap = IfaceMap::instance();
|
||||
std::set<std::string> allifaces;
|
||||
|
||||
for(const auto& addr : addrs) {
|
||||
@@ -492,7 +493,7 @@ void Config::expand()
|
||||
ifaces.emplace_back(SockAddr::any(AF_INET));
|
||||
}
|
||||
|
||||
auto& ifmap = IfaceMap::instance();
|
||||
auto ifmap(IfaceMap::instance());
|
||||
|
||||
for(size_t i=0; i<ifaces.size(); i++) {
|
||||
auto& ep = ifaces[i];
|
||||
@@ -512,8 +513,8 @@ void Config::expand()
|
||||
// use interface list add ipv4 broadcast addresses to beaconDestinations.
|
||||
// 0.0.0.0 -> adds all bcasts
|
||||
// otherwise add bcast for each iface address
|
||||
expandAddrList(ifaces, bdest);
|
||||
addGroups(ifaces, bdest);
|
||||
expandAddrList(ifaces, bdest, ifmap);
|
||||
addGroups(ifaces, bdest, ifmap);
|
||||
auto_beacon = false;
|
||||
}
|
||||
|
||||
@@ -622,6 +623,8 @@ void Config::updateDefs(defs_t& defs) const
|
||||
|
||||
void Config::expand()
|
||||
{
|
||||
auto ifmap(IfaceMap::instance());
|
||||
|
||||
if(udp_port==0)
|
||||
throw std::runtime_error("Client can't use UDP random port");
|
||||
|
||||
@@ -635,8 +638,8 @@ void Config::expand()
|
||||
ifaces.emplace_back(SockAddr::any(AF_INET));
|
||||
|
||||
if(autoAddrList) {
|
||||
expandAddrList(ifaces, addrs);
|
||||
addGroups(ifaces, addrs);
|
||||
expandAddrList(ifaces, addrs, ifmap);
|
||||
addGroups(ifaces, addrs, ifmap);
|
||||
autoAddrList = false;
|
||||
}
|
||||
|
||||
|
||||
+117
-158
@@ -545,7 +545,7 @@ void evsocket::mcast_prep_sendto(const SockEndpoint& ep) const
|
||||
else if(!ep.addr.isMCast())
|
||||
return;
|
||||
|
||||
auto& ifmap = IfaceMap::instance();
|
||||
auto ifmap = IfaceMap::instance();
|
||||
|
||||
if(af==AF_INET) {
|
||||
SockAddr iface(AF_INET);
|
||||
@@ -697,215 +697,174 @@ bool evsocket::init_canIPv6() noexcept
|
||||
# define getMonotonic getCurrent
|
||||
#endif
|
||||
|
||||
static IfaceMap* theinstance;
|
||||
struct IfMapDaemon : private epicsThreadRunable {
|
||||
epicsMutex lock;
|
||||
epicsEvent wake;
|
||||
std::shared_ptr<const IfaceMap::Current> latest;
|
||||
bool stop = false;
|
||||
epicsThread worker;
|
||||
IfMapDaemon()
|
||||
:latest(IfaceMap::refresh())
|
||||
,worker(*this, "IfMapDaemon",
|
||||
epicsThreadGetStackSize(epicsThreadStackBig),
|
||||
epicsThreadPriorityMin)
|
||||
{
|
||||
worker.start();
|
||||
}
|
||||
~IfMapDaemon()
|
||||
{
|
||||
{
|
||||
Guard G(lock);
|
||||
stop = true;
|
||||
}
|
||||
wake.signal();
|
||||
worker.exitWait();
|
||||
}
|
||||
|
||||
virtual void run() override final
|
||||
{
|
||||
Guard G(lock);
|
||||
while(!stop) {
|
||||
try {
|
||||
std::shared_ptr<const IfaceMap::Current> next;
|
||||
{
|
||||
epicsGuardRelease<epicsMutex> U(G);
|
||||
wake.wait(15.0); // arbitrary period...
|
||||
next = IfaceMap::refresh();
|
||||
}
|
||||
latest.swap(next);
|
||||
|
||||
} catch(std::exception& e){
|
||||
log_crit_printf(logiface, "While updating IfaceMap : %s\n", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
} *ifmapper;
|
||||
|
||||
static
|
||||
void mapInit()
|
||||
{
|
||||
theinstance = new IfaceMap();
|
||||
ifmapper = new IfMapDaemon();
|
||||
}
|
||||
|
||||
IfaceMap& IfaceMap::instance()
|
||||
IfaceMap IfaceMap::instance()
|
||||
{
|
||||
threadOnce<&mapInit>();
|
||||
assert(theinstance);
|
||||
return *theinstance;
|
||||
assert(ifmapper);
|
||||
Guard G(ifmapper->lock);
|
||||
auto ret(ifmapper->latest);
|
||||
if(!ret)
|
||||
throw std::logic_error("No IfaceMap");
|
||||
return IfaceMap(std::move(ret));
|
||||
}
|
||||
|
||||
void IfaceMap::cleanup()
|
||||
{
|
||||
delete theinstance;
|
||||
theinstance = nullptr;
|
||||
delete ifmapper;
|
||||
ifmapper = nullptr;
|
||||
}
|
||||
|
||||
IfaceMap::IfaceMap()
|
||||
std::shared_ptr<const IfaceMap::Current> IfaceMap::refresh()
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
|
||||
void IfaceMap::refresh(bool force)
|
||||
{
|
||||
auto now(epicsTime::getMonotonic());
|
||||
auto age = now-updated;
|
||||
double threshold = force ? 10.0 : 600.0; // TODO: configurable?
|
||||
if(age<threshold && !byIndex.empty())
|
||||
return;
|
||||
log_debug_printf(logiface, "refresh%s after %.1f sec\n", force?" forced":"", age);
|
||||
auto temp = _refresh();
|
||||
log_debug_printf(logiface, "refresh%s", "\n");
|
||||
auto next(std::make_shared<Current>());
|
||||
next->byIndex = _refresh();
|
||||
// cross-index
|
||||
decltype (byName) tempN;
|
||||
decltype (byAddr) tempA;
|
||||
for(auto& pair : temp) {
|
||||
for(auto& pair : next->byIndex) {
|
||||
auto& iface = pair.second;
|
||||
tempN[iface.name] = &iface;
|
||||
next->byName[iface.name] = &iface;
|
||||
for(auto& pair : iface.addrs) {
|
||||
tempA.emplace(pair.first, std::make_pair(&iface, false));
|
||||
if(pair.second.family()==AF_INET)
|
||||
tempA.emplace(pair.second, std::make_pair(&iface, true));
|
||||
next->byAddr.emplace(pair.first, std::make_pair(&iface, false));
|
||||
if(pair.first.family()==AF_INET && pair.second.family()==AF_INET) {
|
||||
next->byAddr.emplace(pair.second, std::make_pair(&iface, true));
|
||||
iface.bcast[pair.second] = pair.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
byIndex.swap(temp);
|
||||
byName.swap(tempN);
|
||||
byAddr.swap(tempA);
|
||||
updated = now;
|
||||
return next;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename FN>
|
||||
bool try_cache(IfaceMap& self, FN&& fn)
|
||||
bool IfaceMap::has_address(uint64_t ifindex, const SockAddr &addr) const
|
||||
{
|
||||
bool force = false;
|
||||
retry:
|
||||
self.refresh(force);
|
||||
bool found = fn();
|
||||
if(!found && !force) {
|
||||
force = true;
|
||||
goto retry;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IfaceMap::has_address(uint64_t ifindex, const SockAddr &addr)
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
if(addr.isAny())
|
||||
return true;
|
||||
|
||||
bool found = try_cache(*this, [this, ifindex, &addr]() {
|
||||
auto ifit(byIndex.find(ifindex));
|
||||
if(ifit!=byIndex.end()) {
|
||||
const auto& addrs = ifit->second.addrs;
|
||||
return addrs.find(addr)!=addrs.end();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
std::string IfaceMap::name_of(uint64_t index)
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
std::string name;
|
||||
bool found = try_cache(*this, [this, index, &name](){
|
||||
auto it(byIndex.find(index));
|
||||
if(it!=byIndex.end()) {
|
||||
name = it->second.name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if(!found) {
|
||||
// fallback to numeric index
|
||||
name = SB()<<index;
|
||||
auto ifit(current->byIndex.find(ifindex));
|
||||
if(ifit!=current->byIndex.end()) {
|
||||
const auto& addrs = ifit->second.addrs;
|
||||
return addrs.find(addr)!=addrs.end();
|
||||
}
|
||||
return name;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string IfaceMap::name_of(const SockAddr& addr)
|
||||
std::string IfaceMap::name_of(uint64_t index) const
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
std::string name;
|
||||
try_cache(*this, [this, addr, &name](){
|
||||
auto it(byAddr.find(addr));
|
||||
if(it!=byAddr.end()) {
|
||||
name = it->second.first->name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return name;
|
||||
auto it(current->byIndex.find(index));
|
||||
if(it!=current->byIndex.end()) {
|
||||
return it->second.name;
|
||||
}
|
||||
// fallback to numeric index
|
||||
return SB()<<index;
|
||||
}
|
||||
|
||||
uint64_t IfaceMap::index_of(const std::string& name)
|
||||
std::string IfaceMap::name_of(const SockAddr& addr) const
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
uint64_t ret = 0u;
|
||||
try_cache(*this, [&ret, this, name]() {
|
||||
auto it = byName.find(name);
|
||||
bool hit = it!=byName.end();
|
||||
if(hit)
|
||||
ret = it->second->index;
|
||||
return hit;
|
||||
});
|
||||
return ret;
|
||||
auto it(current->byAddr.find(addr));
|
||||
if(it!=current->byAddr.end())
|
||||
return it->second.first->name;;
|
||||
return std::string();
|
||||
}
|
||||
|
||||
uint64_t IfaceMap::index_of(const SockAddr &addr)
|
||||
uint64_t IfaceMap::index_of(const std::string& name) const
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
uint64_t ret = 0u;
|
||||
try_cache(*this, [&ret, this, addr]() {
|
||||
auto it = byAddr.find(addr);
|
||||
bool hit = it!=byAddr.end() && !it->second.second;
|
||||
if(hit)
|
||||
ret = it->second.first->index;
|
||||
return hit;
|
||||
});
|
||||
return ret;
|
||||
auto it = current->byName.find(name);
|
||||
if(it!=current->byName.end())
|
||||
return it->second->index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IfaceMap::is_iface(const SockAddr& addr)
|
||||
uint64_t IfaceMap::index_of(const SockAddr &addr) const
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
return try_cache(*this, [this, addr]() {
|
||||
auto it(byAddr.find(addr));
|
||||
return it!=byAddr.end() && !it->second.second;
|
||||
});
|
||||
auto it = current->byAddr.find(addr);
|
||||
if(it!=current->byAddr.end() && !it->second.second)
|
||||
return it->second.first->index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IfaceMap::is_lo(uint64_t index)
|
||||
bool IfaceMap::is_iface(const SockAddr& addr) const
|
||||
{
|
||||
bool is_lo = false;
|
||||
(void)try_cache(*this, [this, &is_lo, index]() {
|
||||
auto ifit(byIndex.find(index));
|
||||
if(ifit!=byIndex.end()) { // hit
|
||||
is_lo = ifit->second.isLO;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return is_lo;
|
||||
auto it(current->byAddr.find(addr));
|
||||
return it!=current->byAddr.end() && !it->second.second;
|
||||
}
|
||||
|
||||
bool IfaceMap::is_broadcast(const SockAddr& addr)
|
||||
bool IfaceMap::is_lo(uint64_t index) const
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
return try_cache(*this, [this, addr]() {
|
||||
auto it(byAddr.find(addr));
|
||||
return it!=byAddr.end() && it->second.second;
|
||||
});
|
||||
auto ifit(current->byIndex.find(index));
|
||||
if(ifit!=current->byIndex.end()) { // hit
|
||||
return ifit->second.isLO;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SockAddr IfaceMap::address_of(const std::string& name)
|
||||
bool IfaceMap::is_broadcast(const SockAddr& addr) const
|
||||
{
|
||||
Guard G(lock);
|
||||
|
||||
SockAddr ret;
|
||||
try_cache(*this, [this, name, &ret]() {
|
||||
auto it(byName.find(name));
|
||||
if(it!=byName.end() && !it->second->addrs.empty()) {
|
||||
ret = it->second->addrs.begin()->first;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return ret;
|
||||
auto it(current->byAddr.find(addr));
|
||||
return it!=current->byAddr.end() && it->second.second;
|
||||
}
|
||||
|
||||
std::set<std::string> IfaceMap::all_external()
|
||||
SockAddr IfaceMap::address_of(const std::string& name) const
|
||||
{
|
||||
auto it(current->byName.find(name));
|
||||
if(it!=current->byName.end() && !it->second->addrs.empty()) {
|
||||
return it->second->addrs.begin()->first;
|
||||
}
|
||||
return SockAddr();
|
||||
}
|
||||
|
||||
std::set<std::string> IfaceMap::all_external() const
|
||||
{
|
||||
std::set<std::string> ret;
|
||||
Guard G(lock);
|
||||
refresh();
|
||||
for(auto& pair : byIndex) {
|
||||
for(auto& pair : current->byIndex) {
|
||||
ret.emplace(pair.second.name);
|
||||
}
|
||||
return ret;
|
||||
|
||||
+28
-26
@@ -302,55 +302,57 @@ struct PVXS_API evsocket
|
||||
|
||||
struct PVXS_API IfaceMap {
|
||||
static
|
||||
IfaceMap& instance();
|
||||
IfaceMap instance();
|
||||
static
|
||||
void cleanup();
|
||||
|
||||
IfaceMap();
|
||||
|
||||
// return true if ifindex is valid, and addr an interface address assigned to it.
|
||||
bool has_address(uint64_t ifindex, const SockAddr& addr);
|
||||
bool has_address(uint64_t ifindex, const SockAddr& addr) const;
|
||||
// lookup interface name by index
|
||||
std::string name_of(uint64_t index);
|
||||
std::string name_of(uint64_t index) const;
|
||||
// find (an) interface name with this address. useful for IPv4. returns empty string if not found.
|
||||
std::string name_of(const SockAddr& addr);
|
||||
std::string name_of(const SockAddr& addr) const;
|
||||
// returns 0 if not found
|
||||
uint64_t index_of(const std::string& name);
|
||||
uint64_t index_of(const std::string& name) const;
|
||||
// lookup interface index by interface address (not broadcast addr)
|
||||
uint64_t index_of(const SockAddr& addr);
|
||||
uint64_t index_of(const SockAddr& addr) const;
|
||||
// is this a valid interface or broadcast address?
|
||||
bool is_iface(const SockAddr& addr);
|
||||
bool is_iface(const SockAddr& addr) const;
|
||||
// is this index the/a loopback interface?
|
||||
bool is_lo(uint64_t index);
|
||||
bool is_lo(uint64_t index) const;
|
||||
// is this a valid interface or broadcast address?
|
||||
bool is_broadcast(const SockAddr& addr);
|
||||
bool is_broadcast(const SockAddr& addr) const;
|
||||
// look up interface address. useful for IPV4. returns AF_UNSPEC if not found
|
||||
SockAddr address_of(const std::string& name);
|
||||
SockAddr address_of(const std::string& name) const;
|
||||
// all interface names except LO
|
||||
std::set<std::string> all_external();
|
||||
|
||||
// caller must hold lock
|
||||
void refresh(bool force=false);
|
||||
std::set<std::string> all_external() const;
|
||||
|
||||
struct Iface {
|
||||
std::string name;
|
||||
uint64_t index;
|
||||
bool isLO;
|
||||
// interface address(s) -> (maybe) broadcast addr
|
||||
std::map<SockAddr, SockAddr, SockAddrOnlyLess> addrs;
|
||||
// addrs - interface address -> (maybe) broadcast addr
|
||||
// bcast - broadcast -> interface address
|
||||
std::map<SockAddr, SockAddr, SockAddrOnlyLess> addrs, bcast;
|
||||
Iface(const std::string& name, uint64_t index, bool isLO) :name(name), index(index), isLO(isLO) {}
|
||||
};
|
||||
|
||||
SockAttach attach;
|
||||
epicsMutex lock;
|
||||
std::map<uint64_t, Iface> byIndex;
|
||||
std::map<std::string, Iface*> byName;
|
||||
// map address to tuple of interface and broadcast?
|
||||
std::multimap<SockAddr, std::pair<Iface*, bool>, SockAddrOnlyLess> byAddr;
|
||||
epicsTime updated;
|
||||
struct Current {
|
||||
std::map<uint64_t, Iface> byIndex;
|
||||
std::map<std::string, Iface*> byName;
|
||||
// map address to tuple of interface and broadcast?
|
||||
std::multimap<SockAddr, std::pair<Iface*, bool>, SockAddrOnlyLess> byAddr;
|
||||
};
|
||||
std::shared_ptr<const Current> current;
|
||||
|
||||
IfaceMap() = default;
|
||||
IfaceMap(const IfaceMap&) = default;
|
||||
IfaceMap(std::shared_ptr<const Current>&& cur) : current(std::move(cur)) {}
|
||||
static
|
||||
std::shared_ptr<const Current> refresh();
|
||||
private:
|
||||
static
|
||||
decltype (byIndex) _refresh();
|
||||
decltype (Current::byIndex) _refresh();
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
@@ -205,9 +205,9 @@ namespace impl {
|
||||
# define GAA_FLAG_INCLUDE_ALL_INTERFACES 0
|
||||
#endif
|
||||
|
||||
decltype (IfaceMap::byIndex) IfaceMap::_refresh() {
|
||||
decltype (IfaceMap::Current::byIndex) IfaceMap::_refresh() {
|
||||
std::vector<char> ifaces(1024u);
|
||||
decltype (byIndex) temp;
|
||||
decltype (Current::byIndex) temp;
|
||||
|
||||
{
|
||||
constexpr ULONG flags = GAA_FLAG_SKIP_ANYCAST
|
||||
|
||||
@@ -253,10 +253,10 @@ int sendtox::call()
|
||||
|
||||
namespace impl {
|
||||
|
||||
decltype (IfaceMap::byIndex) IfaceMap::_refresh() {
|
||||
decltype (IfaceMap::Current::byIndex) IfaceMap::_refresh() {
|
||||
ifaddrs* addrs = nullptr;
|
||||
|
||||
decltype (byIndex) temp;
|
||||
decltype (Current::byIndex) temp;
|
||||
|
||||
if(getifaddrs(&addrs)) {
|
||||
log_warn_printf(logiface, "Unable to getifaddrs() errno=%d\n", errno);
|
||||
|
||||
@@ -96,7 +96,7 @@ struct UDPManager::Pvt {
|
||||
SockAttach attach;
|
||||
|
||||
evbase loop;
|
||||
IfaceMap& ifmap;
|
||||
IfaceMap ifmap;
|
||||
|
||||
// only manipulate from loop worker thread
|
||||
// key'd by address family and port#
|
||||
|
||||
+4
-8
@@ -180,18 +180,14 @@ void test_ifacemap()
|
||||
{
|
||||
testDiag("Enter %s", __func__);
|
||||
|
||||
auto& ifs = IfaceMap::instance();
|
||||
auto ifs(IfaceMap::instance());
|
||||
|
||||
epicsGuard<epicsMutex> G(ifs.lock); // since we are playing around with the internals...
|
||||
|
||||
ifs.refresh(true);
|
||||
|
||||
testFalse(ifs.byIndex.empty())<<" found "<<ifs.byIndex.size()<<" interfaces";
|
||||
testFalse(ifs.current->byIndex.empty())<<" found "<<ifs.current->byIndex.size()<<" interfaces";
|
||||
|
||||
bool foundlo = false;
|
||||
const auto lo(SockAddr::loopback(AF_INET));
|
||||
|
||||
for(const auto& pair : ifs.byIndex) {
|
||||
for(const auto& pair : ifs.current->byIndex) {
|
||||
auto& iface = pair.second;
|
||||
testDiag("Interface %u \"%s\"", unsigned(iface.index), iface.name.c_str());
|
||||
for(const auto& pair : iface.addrs) {
|
||||
@@ -297,7 +293,7 @@ void test_local_mcast()
|
||||
{
|
||||
testDiag("Enter %s", __func__);
|
||||
|
||||
IfaceMap ifinfo;
|
||||
auto ifinfo(IfaceMap::instance());
|
||||
|
||||
evsocket A(AF_INET, SOCK_DGRAM, 0, true),
|
||||
B(AF_INET, SOCK_DGRAM, 0, true);
|
||||
|
||||
+2
-4
@@ -101,11 +101,9 @@ void testFwdIface()
|
||||
|
||||
std::vector<SockAddr> ifaddrs;
|
||||
{
|
||||
auto& ifs(IfaceMap::instance());
|
||||
auto ifs(IfaceMap::instance());
|
||||
|
||||
epicsGuard<epicsMutex> G(ifs.lock);
|
||||
|
||||
for(auto it : ifs.byIndex) {
|
||||
for(auto it : ifs.current->byIndex) {
|
||||
auto& iface = it.second;
|
||||
if(iface.isLO)
|
||||
continue;
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ SockEndpoint parseEP(const char* optarg, const server::Config& conf)
|
||||
|
||||
struct App {
|
||||
const SockAttach attach;
|
||||
IfaceMap& ifmap;
|
||||
IfaceMap ifmap;
|
||||
const evsocket sockTx{AF_INET, SOCK_DGRAM, 0};
|
||||
std::vector<SockEndpoint> destinations;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user