Files
pvxs/src/server.cpp
T
Michael Davidsaver c886205110 redo UDP handling
2019-10-27 16:19:59 -07:00

373 lines
9.9 KiB
C++

/**
* 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 <list>
#include <map>
#include <regex>
#include <system_error>
#include <dbDefs.h>
#include <envDefs.h>
#include <epicsThread.h>
#include <pvxs/server.h>
#include <pvxs/log.h>
#include "evhelper.h"
#include "utilpvt.h"
#include "udp_collector.h"
namespace pvxs {
namespace server {
using namespace pvxsimpl;
DEFINE_LOGGER(serversetup, "server.setup");
DEFINE_LOGGER(serverio, "server.io");
namespace {
void split_into(std::vector<std::string>& out, const char *inp)
{
std::regex word("\\s*(\\S+)(.*)");
std::cmatch M;
while(*inp && std::regex_match(inp, M, word)) {
out.push_back(M[1].str());
inp = M[2].first;
}
}
}
Server::Config Server::Config::from_env()
{
Server::Config ret;
ret.default_udp = 5076;
if(const char *env = getenv("EPICS_PVAS_INTF_ADDR_LIST")) {
split_into(ret.interfaces, env);
}
if(const char *env = getenv("EPICS_PVAS_BEACON_ADDR_LIST")) {
split_into(ret.beaconDestinations, env);
} else if(const char *env = getenv("EPICS_PVA_ADDR_LIST")) {
split_into(ret.beaconDestinations, env);
}
ret.tcp_port = 5075;
if(const char *env = getenv("EPICS_PVAS_SERVER_PORT")) {
ret.tcp_port = lexical_cast<unsigned short>(env);
} else if(const char *env = getenv("EPICS_PVA_SERVER_PORT")) {
ret.tcp_port = lexical_cast<unsigned short>(env);
}
ret.default_udp = 5076;
if(const char *env = getenv("EPICS_PVAS_BROADCAST_PORT")) {
ret.default_udp = lexical_cast<unsigned short>(env);
} else if(const char *env = getenv("EPICS_PVA_BROADCAST_PORT")) {
ret.default_udp = lexical_cast<unsigned short>(env);
}
return ret;
}
namespace {
struct ServIface
{
Server::Pvt * const server;
SockAddr bind_addr;
std::string name;
evsocket sock;
evlisten listener;
std::unique_ptr<UDPListener> searchrx;
ServIface(const std::string& addr, Server::Pvt *server);
void onConn(evutil_socket_t sock, struct sockaddr *peer, int socklen);
static void onConnS(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *peer, int socklen, void *raw)
{
try {
if(peer->sa_family!=AF_INET) {
log_printf(serversetup, PLVL_CRIT, "Rejecting !ipv4 client\n");
evutil_closesocket(sock);
return;
}
static_cast<ServIface*>(raw)->onConn(sock, peer, socklen);
}catch(std::exception& e){
log_printf(serverio, PLVL_CRIT, "Unhandled error in accept callback: %s\n", e.what());
}
}
};
} // namespace
struct Server::Pvt
{
// "const" after ctor
Config effective;
std::list<ServIface> interfaces;
std::vector<SockAddr> beaconDest;
// handlers for active TCP connections, by priority.
// once added, these remain stable for the lifetime of the Server
std::map<unsigned, evbase> prio_loops;
// handle server "background" tasks.
// accept new connections and send beacons
evbase acceptor_loop;
evsocket beaconSender;
evevent beaconTimer;
enum {
Stopped,
Starting,
Running,
Stopping,
} state;
Pvt(Config&& conf);
~Pvt();
void start();
void stop();
void doBeacons(short evt);
static void doBeaconsS(evutil_socket_t fd, short evt, void *raw)
{
try {
static_cast<Pvt*>(raw)->doBeacons(evt);
}catch(std::exception& e){
log_printf(serverio, PLVL_CRIT, "Unhandled error in beacon timer callback: %s\n", e.what());
}
}
};
Server::Server() {}
Server::Server(Config&& conf)
:pvt(new Pvt(std::move(conf)))
{}
Server::~Server() {}
const Server::Config& Server::config() const
{
if(!pvt)
throw std::logic_error("NULL Server");
return pvt->effective;
}
Server& Server::start()
{
if(!pvt)
throw std::logic_error("NULL Server");
pvt->start();
return *this;
}
Server::Pvt::Pvt(Config&& conf)
:effective(std::move(conf))
,acceptor_loop("PVXS Acceptor", epicsThreadPriorityCAServerLow-2)
,beaconSender(AF_INET, SOCK_DGRAM, 0)
,beaconTimer(acceptor_loop.base, -1, EV_TIMEOUT, doBeaconsS, this)
,state(Stopped)
{
// empty interface address list implies the wildcard
// (because no addresses isn't interesting...)
if(effective.interfaces.empty()) {
effective.interfaces.push_back("0.0.0.0");
}
acceptor_loop.call([this](){
// from acceptor worker
for(const auto& addr : effective.interfaces) {
interfaces.emplace_back(addr, this);
}
for(const auto& addr : effective.beaconDestinations) {
beaconDest.emplace_back(AF_INET, addr);
}
if(effective.auto_beacon) {
// append broadcast addresses associated with our bound interface(s)
ELLLIST bcasts = ELLLIST_INIT;
try {
evsocket dummy(AF_INET, SOCK_DGRAM, 0);
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 an 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;
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;
});
}
Server::Pvt::~Pvt() {}
void Server::Pvt::start()
{
log_printf(serversetup, PLVL_DEBUG, "Server Starting\n");
acceptor_loop.call([this]()
{
if(state!=Stopped) {
// already running
log_printf(serversetup, PLVL_DEBUG, "Server not stopped %d\n", state);
return;
}
state = Starting;
log_printf(serversetup, PLVL_DEBUG, "Server starting\n");
for(auto& iface : interfaces) {
if(evconnlistener_enable(iface.listener.lev)) {
log_printf(serversetup, PLVL_ERR, "Error enabling listener on %s\n", iface.name.c_str());
}
log_printf(serversetup, PLVL_DEBUG, "Server enabled listener on %s\n", iface.name.c_str());
}
// send first beacon immediately
if(event_add(beaconTimer, nullptr))
log_printf(serversetup, PLVL_ERR, "Error enabling beacon timer on\n");
state = Running;
});
auto manager = UDPManager::instance();
for(auto& iface : interfaces) {
auto addr = iface.bind_addr;
addr.setPort(effective.default_udp);
iface.searchrx = manager.onSearch(addr, [](const UDPManager::Search& msg) {
// TODO handle search
});
}
}
void Server::Pvt::stop()
{
log_printf(serversetup, PLVL_DEBUG, "Server Stopping\n");
// Stop sending Beacons
acceptor_loop.call([this]()
{
if(state!=Running) {
log_printf(serversetup, PLVL_DEBUG, "Server not running %d\n", state);
return;
}
state = Stopping;
if(event_del(beaconTimer.ev))
log_printf(serversetup, PLVL_ERR, "Error disabling beacon timer on\n");
});
// stop processing Search requests
for(auto& iface : interfaces) {
iface.searchrx.reset();
}
// stop listening for new TCP connections
acceptor_loop.call([this]()
{
for(auto& iface : interfaces) {
if(evconnlistener_disable(iface.listener.lev)) {
log_printf(serversetup, PLVL_ERR, "Error disabling listener on %s\n", iface.name.c_str());
}
log_printf(serversetup, PLVL_DEBUG, "Server disabled listener on %s\n", iface.name.c_str());
}
});
// Close in-progress connections (and cancel Ops)
acceptor_loop.call([this]()
{
state = Stopped;
});
}
void Server::Pvt::doBeacons(short evt)
{
log_printf(serversetup, PLVL_DEBUG, "Server beacon timer expires\n");
// TODO send beacons
timeval interval = {15, 0};
if(event_add(beaconTimer, &interval))
log_printf(serversetup, PLVL_ERR, "Error re-enabling beacon timer on\n");
}
ServIface::ServIface(const std::string& addr, Server::Pvt *server)
:server(server)
,bind_addr(AF_INET, addr)
,sock(AF_INET, SOCK_STREAM, 0)
{
server->acceptor_loop.assertInLoop();
// try to bind to requested port, then fallback to a random port
while(true) {
try {
sock.bind(bind_addr);
} catch(std::system_error& e) {
if(e.code().value()==SOCK_EADDRINUSE && bind_addr.port()!=0) {
bind_addr.setPort(0);
continue;
}
throw;
}
break;
}
name = bind_addr.tostring();
const int backlog = 4;
listener = evlisten(server->acceptor_loop.base, onConnS, this, LEV_OPT_DISABLED, backlog, sock.sock);
}
void ServIface::onConn(evutil_socket_t sock, struct sockaddr *peer, int socklen)
{
evutil_closesocket(sock);
}
}} // namespace pvxs::server