diff --git a/src/config.cpp b/src/config.cpp index e73df2b..1991b37 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -4,17 +4,23 @@ * in file LICENSE that is included with this distribution. */ +#include #include #include #include +#include #include #include #include #include "serverconn.h" +#include "evhelper.h" DEFINE_LOGGER(serversetup, "pvxs.server.setup"); +DEFINE_LOGGER(config, "pvxs.config"); + +namespace pvxs { namespace { void split_addr_into(const char* name, std::vector& out, const char *inp) @@ -25,7 +31,7 @@ void split_addr_into(const char* name, std::vector& out, const char while(*inp && std::regex_match(inp, M, word)) { sockaddr_in addr = {}; if(aToIPAddr(M[1].str().c_str(), 0, &addr)) { - log_err_printf(serversetup, "%s ignoring invalid '%s'\n", name, M[1].str().c_str()); + log_err_printf(config, "%s ignoring invalid '%s'\n", name, M[1].str().c_str()); continue; } char buf[24]; @@ -47,9 +53,70 @@ const char* pickenv(const char** picked, std::initializer_list name return nullptr; } +template +struct cleaner { + Fn fn; + ~cleaner() { fn(); } +}; + +template +cleaner make_cleaner(Fn&& fn) { + return cleaner{std::move(fn)}; +} + +// 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) +{ + evsocket dummy(AF_INET, SOCK_DGRAM, 0); + + std::vector bcasts; + + for(auto& addr : ifaces) { + + ELLLIST blist = ELLLIST_INIT; + auto bclean = make_cleaner([&blist] { + ellFree(&blist); + }); + + 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; + } + + osiSockAddr match = {}; + match.ia = saddr->in; + osiSockDiscoverBroadcastAddresses(&blist, dummy.sock, &match); + + while(ELLNODE *cur = ellGet(&blist)) { + osiSockAddrNode *node = CONTAINER(cur, osiSockAddrNode, node); + + SockAddr temp(&node->addr.sa, sizeof(node->addr.ia)); + free(node); + temp.setPort(0u); + + bcasts.push_back(temp.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()); +} + } // namespace -namespace pvxs { namespace server { Config Config::from_env() @@ -96,6 +163,23 @@ Config Config::from_env() return ret; } +void Config::expand() +{ + // empty interface address list implies the wildcard + // (because no addresses isn't interesting...) + if(interfaces.empty()) { + interfaces.emplace_back("0.0.0.0"); + } + + if(auto_beacon) { + expandAddrList(interfaces, beaconDestinations); + auto_beacon = false; + } + + removeDups(interfaces); + removeDups(beaconDestinations); +} + std::ostream& operator<<(std::ostream& strm, const Config& conf) { bool first; diff --git a/src/pvxs/server.h b/src/pvxs/server.h index d59a264..135ea77 100644 --- a/src/pvxs/server.h +++ b/src/pvxs/server.h @@ -48,7 +48,7 @@ public: //! An empty/dummy Server constexpr Server() = default; //! Create/allocate, but do not start, a new server with the provided config. - explicit Server(Config&&); + explicit Server(const Config&); ~Server(); //! Begin serving. Does not block. @@ -101,7 +101,7 @@ private: }; //! Configuration for a Server -struct Config { +struct PVXS_API Config { //! List of network interface addresses (**not** host names) to which this server will bind. //! interfaces.empty() treated as an alias for "0.0.0.0", which may also be given explicitly. //! Port numbers are optional and unused (parsed and ignored) @@ -121,13 +121,20 @@ struct Config { std::array guid; //! Default configuration using process environment - PVXS_API static Config from_env(); + + //! Empty config Config() :tcp_port(5075), udp_port(5076), auto_beacon(true), guid{} {} + //! Apply rules to translate current requested configuration + //! into one which can actually be loaded. + //! @post auto_beacon==false + //! @post !interfaces.empty() + void expand(); + //! Short-hand for @code Server(std::move(*this)) @endcode. - inline Server build() { - Server ret(std::move(*this)); + inline Server build() const { + Server ret(*this); return ret; } }; diff --git a/src/server.cpp b/src/server.cpp index 5dd4912..8f612fa 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -42,7 +42,7 @@ using namespace impl; DEFINE_LOGGER(serversetup, "pvxs.server.setup"); DEFINE_LOGGER(serverio, "pvxs.server.io"); -Server::Server(Config&& conf) +Server::Server(const Config& conf) { /* Here be dragons. * @@ -55,7 +55,7 @@ Server::Server(Config&& conf) * Which need to safely access server storage, but should not * prevent a server from stopping. */ - auto internal(std::make_shared(std::move(conf))); + auto internal(std::make_shared(conf)); internal->internal_self = internal; // external @@ -235,8 +235,8 @@ Server& Server::interrupt() return *this; } -Server::Pvt::Pvt(Config&& conf) - :effective(std::move(conf)) +Server::Pvt::Pvt(const Config &conf) + :effective(conf) ,beaconMsg(128) ,acceptor_loop("PVXTCP", epicsThreadPriorityCAServerLow-2) ,beaconSender(AF_INET, SOCK_DGRAM, 0) @@ -245,11 +245,7 @@ Server::Pvt::Pvt(Config&& conf) ,builtinsrc(StaticSource::build()) ,state(Stopped) { - // empty interface address list implies the wildcard - // (because no addresses isn't interesting...) - if(effective.interfaces.empty()) { - effective.interfaces.emplace_back("0.0.0.0"); - } + effective.expand(); { int val = 1; @@ -270,7 +266,7 @@ Server::Pvt::Pvt(Config&& conf) evsocket dummy(AF_INET, SOCK_DGRAM, 0); - acceptor_loop.call([this, &dummy](){ + acceptor_loop.call([this](){ // from acceptor worker bool firstiface = true; @@ -284,49 +280,6 @@ Server::Pvt::Pvt(Config&& conf) for(const auto& addr : effective.beaconDestinations) { beaconDest.emplace_back(AF_INET, addr.c_str(), effective.udp_port); } - - if(effective.auto_beacon) { - // append broadcast addresses associated with our bound interface(s) - - ELLLIST bcasts = ELLLIST_INIT; - - try { - for(const auto& iface : interfaces) { - if(iface.bind_addr.family()!=AF_INET) - continue; - osiSockAddr match; - match.ia = iface.bind_addr->in; - osiSockDiscoverBroadcastAddresses(&bcasts, dummy.sock, &match); - } - - // do our best to avoid a bad_alloc during iteration - beaconDest.reserve(beaconDest.size()+(size_t)ellCount(&bcasts)); - - while(ELLNODE *cur = ellGet(&bcasts)) { - osiSockAddrNode *node = CONTAINER(cur, osiSockAddrNode, node); - beaconDest.emplace_back(AF_INET); - beaconDest.back()->in = node->addr.ia; - beaconDest.back()->in.sin_port = htons(effective.udp_port); - free(cur); - } - - }catch(...){ - ellFree(&bcasts); - throw; - } - } - - effective.interfaces.clear(); - for(const auto& iface : interfaces) { - effective.interfaces.emplace_back(iface.bind_addr.tostring()); - } - - effective.beaconDestinations.clear(); - for(const auto& addr : beaconDest) { - effective.beaconDestinations.emplace_back(addr.tostring()); - } - - effective.auto_beacon = false; }); { diff --git a/src/serverconn.h b/src/serverconn.h index ab4f457..f967976 100644 --- a/src/serverconn.h +++ b/src/serverconn.h @@ -234,7 +234,7 @@ struct Server::Pvt Stopping, } state; - Pvt(Config&& conf); + Pvt(const Config& conf); ~Pvt(); void start(); diff --git a/src/util.cpp b/src/util.cpp index c9456a9..0f0c120 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -308,7 +308,9 @@ std::ostream& operator<<(std::ostream& strm, const SockAddr& addr) } else { strm<<"<\?\?\?>"; } - strm<in.sin_port); + strm<in.sin_port)) + strm<<':'<in.sin_port); break; } #ifdef AF_INET6 @@ -319,7 +321,9 @@ std::ostream& operator<<(std::ostream& strm, const SockAddr& addr) } else { strm<<"<\?\?\?>"; } - strm<in6.sin6_port); + strm<in6.sin6_port)) + strm<<':'<in6.sin6_port); break; } #endif