/** * 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 "utilpvt.h" #include "evhelper.h" DEFINE_LOGGER(serversetup, "pvxs.server.setup"); DEFINE_LOGGER(clientsetup, "pvxs.client.setup"); DEFINE_LOGGER(config, "pvxs.config"); namespace pvxs { SockEndpoint::SockEndpoint(const char* ep, uint16_t defport) { // // , // @ifacename // ,@ifacename auto comma = strchr(ep, ','); auto at = strchr(ep, '@'); if(comma && at && comma > at) { throw std::runtime_error(SB()<<'"'<(comma+1); } else if(comma) { ttl = parseTo(std::string(comma+1, at-comma-1)); } if(at) iface = at+1; } auto& ifmap = IfaceMap::instance(); if(addr.family()==AF_INET6) { if(iface.empty() && addr->in6.sin6_scope_id) { // interface index provide with IPv6 address // we map back to symbolic name for storage iface = ifmap.name_of(addr->in6.sin6_scope_id); } addr->in6.sin6_scope_id = 0; } else if(addr.family()==AF_INET && addr.isMCast() && !iface.empty()) { SockAddr ifaddr(AF_INET); if(evutil_inet_pton(AF_INET, iface.c_str(), &ifaddr->in.sin_addr.s_addr)==1) { // map interface address to symbolic name iface = ifmap.name_of(ifaddr); } } if(!iface.empty() && !ifmap.index_of(iface)) { log_warn_printf(config, "Invalid interface address or name: \"%s\"\n", iface.c_str()); } } MCastMembership SockEndpoint::resolve() const { if(!addr.isMCast()) throw std::logic_error("not mcast"); auto& ifmap = IfaceMap::instance(); MCastMembership m; m.af = addr.family(); if(m.af==AF_INET) { auto& req = m.req.in; req.imr_multiaddr.s_addr = addr->in.sin_addr.s_addr; if(!iface.empty()) { auto iface = ifmap.address_of(this->iface); if(iface.family()==AF_INET) { req.imr_interface.s_addr = iface->in.sin_addr.s_addr; } } } else if(m.af==AF_INET6) { auto& req = m.req.in6; req.ipv6mr_multiaddr = addr->in6.sin6_addr; if(!iface.empty()) { req.ipv6mr_interface = ifmap.index_of(this->iface); if(!req.ipv6mr_interface) { log_warn_printf(config, "Unable to resolve interface '%s'\n", iface.c_str()); } } } else { throw std::logic_error("Unsupported address family"); } return m; } std::ostream& operator<<(std::ostream& strm, const SockEndpoint& addr) { strm<& 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& 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; } }; std::vector parseAddresses(const std::vector& addrs, uint16_t defport=0) { std::vector ret; for(const auto& addr : addrs) { try { ret.emplace_back(addr, defport); }catch(std::runtime_error& e){ log_warn_printf(config, "Ignoring %s : %s\n", addr.c_str(), e.what()); continue; } } return ret; } void printAddresses(std::vector& out, const std::vector& inp) { std::vector temp; temp.reserve(inp.size()); for(auto& addr : inp) { temp.emplace_back(SB()<& ifaces, std::vector& addrs) { SockAttach attach; evsocket dummy(AF_INET, SOCK_DGRAM, 0); for(auto& saddr : ifaces) { auto matchAddr = &saddr.addr; if(evsocket::ipstack==evsocket::Linsock && saddr.addr.family()==AF_INET6 && saddr.addr.isAny()) { // special case handling to match "promote" in server::Config::expand() // treat [::] as 0.0.0.0 matchAddr = nullptr; } else if(saddr.addr.family()!=AF_INET) { continue; } for(auto& addr : dummy.broadcasts(matchAddr)) { addr.setPort(0u); addrs.emplace_back(addr); } } } void addGroups(std::vector& ifaces, const std::vector& addrs) { auto& ifmap = IfaceMap::instance(); std::set allifaces; for(const auto& addr : addrs) { if(!addr.addr.isMCast()) continue; if(!addr.iface.empty()) { // interface already specified ifaces.push_back(addr); } else { // no interface specified, treat as wildcard if(allifaces.empty()) allifaces = ifmap.all_external(); for(auto& iface : allifaces) { auto ifaceaddr(addr); ifaceaddr.iface = iface; ifaces.push_back(ifaceaddr); } } } } // remove duplicates while preserving order of first appearance template void removeDups(std::vector& addrs) { std::sort(addrs.begin(), addrs.end()); addrs.erase(std::unique(addrs.begin(), addrs.end()), addrs.end()); } // special handling for SockEndpoint where duplication is based on // address,interface. Duplicates are combined with the longest TTL. template<> void removeDups(std::vector& addrs) { std::map, size_t> seen; for(size_t i=0; isecond]; if(ep.ttl > orig.ttl) { // w/ longer TTL orig.ttl = ep.ttl; } addrs.erase(addrs.begin()+i); // 'ep' and 'orig' are invalidated } } } 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(int family) { Config ret; ret.udp_port = 0u; ret.tcp_port = 0u; ret.auto_beacon = false; switch(family) { case AF_INET: ret.interfaces.emplace_back("127.0.0.1"); ret.beaconDestinations.emplace_back("127.0.0.1"); break; case AF_INET6: ret.interfaces.emplace_back("::1"); ret.beaconDestinations.emplace_back("::1"); break; default: throw std::logic_error(SB()<<"Unsupported address family "<& 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()< adds all bcasts // otherwise add bcast for each iface address expandAddrList(ifaces, bdest); addGroups(ifaces, bdest); auto_beacon = false; } removeDups(ifaces); printAddresses(interfaces, ifaces); removeDups(bdest); printAddresses(beaconDestinations, bdest); removeDups(ignoreAddrs); enforceTimeout(tcpTimeout); } std::ostream& operator<<(std::ostream& strm, const Config& conf) { auto showAddrs = [&strm](const char* var, const std::vector& 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_SERVER_PORT", "EPICS_PVAS_SERVER_PORT"})) { try { self.tcp_port = parseTo(pickone.val); }catch(std::exception& e) { log_warn_printf(clientsetup, "%s invalid integer : %s", pickone.name.c_str(), e.what()); } } if(self.tcp_port==0u && !self.nameServers.empty()) { log_warn_printf(clientsetup, "ignoring EPICS_PVA_SERVER_PORT=%d\n", 0); self.tcp_port = 5075; } if(pickone({"EPICS_PVA_ADDR_LIST"})) { split_addr_into(pickone.name.c_str(), self.addressList, pickone.val, self.udp_port); } if(pickone({"EPICS_PVA_NAME_SERVERS"})) { split_addr_into(pickone.name.c_str(), self.nameServers, pickone.val, self.tcp_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()<