redesign IfaceMap

Switch to periodic poll on dedicated worker thread
instead of opportunistic poll on use.
This commit is contained in:
Michael Davidsaver
2025-02-20 12:07:12 -08:00
parent 25f5f1dcee
commit a464e9a6eb
9 changed files with 170 additions and 212 deletions
+13 -10
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;