/** * Copyright - See the COPYRIGHT that is included with this distribution. * pvxs is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "serverconn.h" #include "clientimpl.h" #include "evhelper.h" DEFINE_LOGGER(serversetup, "pvxs.server.setup"); DEFINE_LOGGER(clientsetup, "pvxs.client.setup"); DEFINE_LOGGER(config, "pvxs.config"); namespace pvxs { namespace { /* Historically pvAccessCPP used $EPICS_PVA_CONN_TMO as the period * between sending CMD_ECHO. *::Config::tcpTimeout is the actual * inactivity timeout period. Apply a scaling factor to add a * go from one to the other. */ constexpr double tmoScale = 4.0/3.0; // 40 second idle timeout / 30 configured void split_addr_into(const char* name, std::vector& out, const std::string& inp, uint16_t defaultPort, bool required=false) { size_t pos=0u; // parse, resolve host names, then re-print. // Catch syntax errors early, and normalize prior to removing duplicates while(pos>24)&0xff)<<'.'<<((ip>>16)&0xff)<<'.'<<((ip>>8)&0xff)<<'.'<<((ip>>0)&0xff); if(addr.sin_port) strm<<':'<& in) { std::ostringstream strm; bool first=true; for(auto& addr : in) { if(first) first = false; else strm<<' '; strm<(val); if(!std::isfinite(temp) || temp<0.0 || temp>double(std::numeric_limits::max())) throw std::out_of_range("Out of range"); dest = temp*tmoScale; } catch(std::exception& e) { log_err_printf(serversetup, "%s invalid double value : '%s'\n", name.c_str(), val.c_str()); } } struct PickOne { const std::map& defs; bool useenv; std::string name, val; bool operator()(std::initializer_list names) { for(auto candidate : names) { if(useenv) { if(auto eval = getenv(candidate)) { name = candidate; val = eval; return true; } } else { auto it = defs.find(candidate); if(it!=defs.end()) { name = candidate; val = it->second; return true; } } } return false; } }; // Fill out address list by appending broadcast addresses // of any and all local interface addresses already included void expandAddrList(const std::vector& ifaces, std::vector& addrs) { SockAttach attach; evsocket dummy(AF_INET, SOCK_DGRAM, 0); std::vector bcasts; for(auto& addr : ifaces) { SockAddr saddr(AF_INET); try { saddr.setAddress(addr.c_str()); }catch(std::runtime_error& e){ log_warn_printf(config, "%s Ignoring...\n", e.what()); continue; } for(auto& addr : dummy.interfaces(&saddr)) { addr.setPort(0u); bcasts.push_back(addr.tostring()); } } addrs.reserve(addrs.size()+bcasts.size()); for(auto& bcast : bcasts) { addrs.push_back(std::move(bcast)); } } void removeDups(std::vector& addrs) { addrs.erase(std::unique(addrs.begin(), addrs.end()), addrs.end()); } void enforceTimeout(double& tmo) { /* Inactivity timeouts with PVA have a long (and growing) history. * * - Originally pvAccessCPP clients didn't send CMD_ECHO, and servers would never timeout. * - Since module version 7.0.0 (in Base 7.0.3) clients send echo every 15 seconds, and * either peer will timeout after 30 seconds of inactivity. * - pvAccessJava clients send CMD_ECHO every 30 seconds, and timeout after 60 seconds. * * So this was a bug, with c++ server timeout racing with Java client echo. * * - As a compromise, continue to send echo at least every 15 seconds, * and increase default timeout to 40. */ if(!std::isfinite(tmo) || tmo <= 0.0 || tmo >= double(std::numeric_limits::max())) tmo = 40.0; else if(tmo < 2.0) tmo = 2.0; } } // namespace namespace server { static void _fromDefs(Config& self, const std::map& defs, bool useenv) { PickOne pickone{defs, useenv}; if(pickone({"EPICS_PVAS_SERVER_PORT", "EPICS_PVA_SERVER_PORT"})) { try { self.tcp_port = parseTo(pickone.val); }catch(std::exception& e) { log_err_printf(serversetup, "%s invalid integer : %s", pickone.name.c_str(), e.what()); } } if(pickone({"EPICS_PVAS_BROADCAST_PORT", "EPICS_PVA_BROADCAST_PORT"})) { try { self.udp_port = parseTo(pickone.val); }catch(std::exception& e) { log_err_printf(serversetup, "%s invalid integer : %s", pickone.name.c_str(), e.what()); } } if(pickone({"EPICS_PVAS_INTF_ADDR_LIST"})) { split_addr_into(pickone.name.c_str(), self.interfaces, pickone.val, self.tcp_port, true); } if(pickone({"EPICS_PVAS_IGNORE_ADDR_LIST"})) { split_addr_into(pickone.name.c_str(), self.ignoreAddrs, pickone.val, 0, true); } if(pickone({"EPICS_PVAS_BEACON_ADDR_LIST", "EPICS_PVA_ADDR_LIST"})) { split_addr_into(pickone.name.c_str(), self.beaconDestinations, pickone.val, self.udp_port); } if(pickone({"EPICS_PVAS_AUTO_BEACON_ADDR_LIST", "EPICS_PVA_AUTO_ADDR_LIST"})) { parse_bool(self.auto_beacon, pickone.name, pickone.val); } if(pickone({"EPICS_PVA_CONN_TMO"})) { parse_timeout(self.tcpTimeout, pickone.name, pickone.val); } } Config& Config::applyEnv() { _fromDefs(*this, std::map(), true); return *this; } Config Config::isolated() { Config ret; ret.udp_port = 0u; ret.tcp_port = 0u; ret.interfaces.emplace_back("127.0.0.1"); ret.auto_beacon = false; ret.beaconDestinations.emplace_back("127.0.0.1"); return ret; } Config& Config::applyDefs(const std::map& defs) { _fromDefs(*this, defs, false); return *this; } void Config::updateDefs(defs_t& defs) const { defs["EPICS_PVA_BROADCAST_PORT"] = defs["EPICS_PVAS_BROADCAST_PORT"] = SB()<& addrs) { strm<(pickone.val); }catch(std::exception& e) { log_warn_printf(clientsetup, "%s invalid integer : %s", pickone.name.c_str(), e.what()); } } if(self.udp_port==0u) { log_warn_printf(clientsetup, "ignoring EPICS_PVA_BROADCAST_PORT=%d\n", 0); self.udp_port = 5076; } if(pickone({"EPICS_PVA_ADDR_LIST"})) { split_addr_into(pickone.name.c_str(), self.addressList, pickone.val, self.udp_port); } if(pickone({"EPICS_PVA_AUTO_ADDR_LIST"})) { parse_bool(self.autoAddrList, pickone.name, pickone.val); } if(pickone({"EPICS_PVA_INTF_ADDR_LIST"})) { split_addr_into(pickone.name.c_str(), self.interfaces, pickone.val, 0); } if(pickone({"EPICS_PVA_CONN_TMO"})) { parse_timeout(self.tcpTimeout, pickone.name, pickone.val); } } Config& Config::applyEnv() { _fromDefs(*this, std::map(), true); return *this; } Config& Config::applyDefs(const std::map& defs) { _fromDefs(*this, defs, false); return *this; } void Config::updateDefs(defs_t& defs) const { defs["EPICS_PVA_BROADCAST_PORT"] = SB()<