b31a354915
On Linux (at least) packets received through the LO may have non-LO origin addresses...
342 lines
8.9 KiB
C++
342 lines
8.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.
|
|
*/
|
|
|
|
#ifndef EVHELPER_H
|
|
#define EVHELPER_H
|
|
|
|
#include <sstream>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <map>
|
|
#include <set>
|
|
|
|
#include <event2/event.h>
|
|
#include <event2/buffer.h>
|
|
#include <event2/listener.h>
|
|
#include <event2/bufferevent.h>
|
|
|
|
#include <pvxs/version.h>
|
|
#include <utilpvt.h>
|
|
|
|
#include <epicsTime.h>
|
|
|
|
#include "pvaproto.h"
|
|
|
|
// hooks for std::unique_ptr
|
|
namespace std {
|
|
template<>
|
|
struct default_delete<event_config> {
|
|
inline void operator()(event_config* ev) { event_config_free(ev); }
|
|
};
|
|
template<>
|
|
struct default_delete<event_base> {
|
|
inline void operator()(event_base* ev) { event_base_free(ev); }
|
|
};
|
|
template<>
|
|
struct default_delete<event> {
|
|
inline void operator()(event* ev) { event_free(ev); }
|
|
};
|
|
template<>
|
|
struct default_delete<evconnlistener> {
|
|
inline void operator()(evconnlistener* ev) { evconnlistener_free(ev); }
|
|
};
|
|
template<>
|
|
struct default_delete<bufferevent> {
|
|
inline void operator()(bufferevent* ev) { bufferevent_free(ev); }
|
|
};
|
|
template<>
|
|
struct default_delete<evbuffer> {
|
|
inline void operator()(evbuffer* ev) { evbuffer_free(ev); }
|
|
};
|
|
}
|
|
|
|
namespace pvxs {namespace impl {
|
|
|
|
//! unique_ptr which is never constructed with NULL
|
|
template<typename T>
|
|
struct owned_ptr : public std::unique_ptr<T>
|
|
{
|
|
constexpr owned_ptr() {}
|
|
explicit owned_ptr(const char* file, int line, T* ptr) : std::unique_ptr<T>(ptr) {
|
|
if(!*this)
|
|
throw loc_bad_alloc(file, line);
|
|
}
|
|
};
|
|
|
|
/* It seems that std::function<void()>(Fn&&) from gcc (circa 8.3) and clang (circa 7.0)
|
|
* always copies the functor/lambda. We can't allow this when transferring ownership
|
|
* of shared_ptr<> instances to a worker thread as it leaves the caller thread with a
|
|
* reference.
|
|
*
|
|
* std::unique_ptr<int> arg{new int(42)};
|
|
* // eg. with
|
|
* auto lambda = [arg{std::move(arg)}]() { // c++14 capture w/ move
|
|
* auto trash(std::move(arg));
|
|
* };
|
|
* // or
|
|
* auto lambda = std::bind([](std::unique_ptr<int>& arg) { // c++11 capture w/ move
|
|
* auto trash(std::move(arg));
|
|
* }, std::move(arg));
|
|
* // the following line tries to copy the unique_ptr which fails to compile
|
|
* std::function<void()> fn(std::move(lambda));
|
|
*
|
|
* So we invent our own limited, non-copyable, version of std::function<void()>.
|
|
*/
|
|
namespace mdetail {
|
|
struct PVXS_API VFunctor0 {
|
|
virtual ~VFunctor0() =0;
|
|
virtual void invoke() =0;
|
|
};
|
|
template<typename Fn>
|
|
struct Functor0 : public VFunctor0 {
|
|
Functor0() = default;
|
|
Functor0(const Functor0&) = delete;
|
|
Functor0(Functor0&&) = default;
|
|
Functor0(Fn&& fn) : fn(std::move(fn)) {}
|
|
virtual ~Functor0() {}
|
|
|
|
void invoke() override final { fn(); }
|
|
private:
|
|
Fn fn;
|
|
};
|
|
} // namespace detail
|
|
|
|
struct mfunction {
|
|
mfunction() = default;
|
|
template<typename Fn>
|
|
mfunction(Fn&& fn)
|
|
:fn{new mdetail::Functor0<Fn>(std::move(fn))}
|
|
{}
|
|
void operator()() const {
|
|
fn->invoke();
|
|
}
|
|
explicit operator bool() const {
|
|
return fn.operator bool();
|
|
}
|
|
private:
|
|
std::unique_ptr<mdetail::VFunctor0> fn;
|
|
};
|
|
|
|
struct PVXS_API evbase {
|
|
evbase() = default;
|
|
explicit evbase(const std::string& name, unsigned prio=0);
|
|
~evbase();
|
|
|
|
evbase internal() const;
|
|
|
|
void join() const;
|
|
|
|
void sync() const;
|
|
|
|
private:
|
|
bool _dispatch(mfunction&& fn, bool dothrow) const;
|
|
bool _call(mfunction&& fn, bool dothrow) const;
|
|
public:
|
|
|
|
// queue request to execute in event loop. return after executed.
|
|
inline
|
|
void call(mfunction&& fn) const {
|
|
_call(std::move(fn), true);
|
|
}
|
|
inline
|
|
bool tryCall(mfunction&& fn) const {
|
|
return _call(std::move(fn), false);
|
|
}
|
|
|
|
// queue request to execute in event loop. return immediately.
|
|
inline
|
|
void dispatch(mfunction&& fn) const {
|
|
_dispatch(std::move(fn), true);
|
|
}
|
|
inline
|
|
bool tryDispatch(mfunction&& fn) const {
|
|
return _dispatch(std::move(fn), false);
|
|
}
|
|
|
|
bool tryInvoke(bool docall, mfunction&& fn) const {
|
|
if(docall)
|
|
return tryCall(std::move(fn));
|
|
else
|
|
return tryDispatch(std::move(fn));
|
|
}
|
|
|
|
void assertInLoop() const;
|
|
//! Caller must be on the worker, or the worker must be stopped.
|
|
//! @returns true if working is running.
|
|
bool assertInRunningLoop() const;
|
|
|
|
inline void reset() { pvt.reset(); }
|
|
|
|
private:
|
|
struct Pvt;
|
|
std::shared_ptr<Pvt> pvt;
|
|
public:
|
|
event_base* base = nullptr;
|
|
};
|
|
|
|
typedef owned_ptr<event_config> evconfig;
|
|
typedef owned_ptr<event> evevent;
|
|
typedef owned_ptr<evconnlistener> evlisten;
|
|
typedef owned_ptr<bufferevent> evbufferevent;
|
|
typedef owned_ptr<evbuffer> evbuf;
|
|
|
|
PVXS_API
|
|
void to_wire(Buffer& buf, const SockAddr& val);
|
|
|
|
PVXS_API
|
|
void from_wire(Buffer &buf, SockAddr& val);
|
|
|
|
struct PVXS_API evsocket
|
|
{
|
|
evutil_socket_t sock;
|
|
int af;
|
|
|
|
// default construct an invalid socket
|
|
constexpr evsocket() noexcept :sock(-1), af(AF_UNSPEC) {}
|
|
|
|
// construct from a valid (not -1) socket
|
|
explicit evsocket(int af, evutil_socket_t sock);
|
|
|
|
// create a new socket
|
|
evsocket(int, int, int);
|
|
|
|
// movable
|
|
evsocket(evsocket&& o) noexcept;
|
|
evsocket& operator=(evsocket&&) noexcept;
|
|
|
|
// not copyable
|
|
evsocket(const evsocket&) = delete;
|
|
evsocket& operator=(const evsocket&) = delete;
|
|
|
|
~evsocket();
|
|
|
|
SockAddr sockname() const;
|
|
|
|
// test validity
|
|
inline operator bool() const { return sock!=-1; }
|
|
|
|
void bind(const SockAddr& addr) const;
|
|
void bind(SockAddr& addr) const;
|
|
|
|
void listen(int backlog) const;
|
|
|
|
void set_broadcast(bool b) const;
|
|
|
|
//! Join multicast group, optionally on selected interface
|
|
bool mcast_join(const MCastMembership& m) const;
|
|
//! Reverse previous join
|
|
void mcast_leave(const MCastMembership& m) const;
|
|
//! Prepare socket for subsequent sendto() with TTL and output interface
|
|
void mcast_prep_sendto(const SockEndpoint& ep) const;
|
|
|
|
//! Whether mcasts sent from this socket should be received to local listeners
|
|
//! @see IP_MULTICAST_LOOP and IPV6_MULTICAST_LOOP
|
|
void mcast_loop(bool loop) const;
|
|
//! Disable IPv4 through IPv6 socket
|
|
void ipv6_only(bool b=true) const;
|
|
|
|
//! Linux specific include OS dropped packet counter as cmsg
|
|
void enable_SO_RXQ_OVFL() const;
|
|
|
|
void enable_IP_PKTINFO() const;
|
|
|
|
//! wraps osiSockDiscoverBroadcastAddresses()
|
|
std::vector<SockAddr> broadcasts(const SockAddr* match=nullptr) const;
|
|
|
|
static
|
|
size_t get_buffer_size(evutil_socket_t sock, bool tx);
|
|
|
|
static
|
|
bool canIPv6;
|
|
|
|
static bool init_canIPv6();
|
|
|
|
enum ipstack_t {
|
|
Linsock,
|
|
Winsock,
|
|
GenericBSD,
|
|
};
|
|
static ipstack_t ipstack;
|
|
};
|
|
|
|
struct PVXS_API IfaceMap {
|
|
static
|
|
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);
|
|
// lookup interface name by index
|
|
std::string name_of(uint64_t index);
|
|
// find (an) interface name with this address. useful for IPv4. returns empty string if not found.
|
|
std::string name_of(const SockAddr& addr);
|
|
// returns 0 if not found
|
|
uint64_t index_of(const std::string& name);
|
|
// is this a valid interface or broadcast address?
|
|
bool is_iface(const SockAddr& addr);
|
|
// is this a valid interface or broadcast address?
|
|
bool is_broadcast(const SockAddr& addr);
|
|
// look up interface address. useful for IPV4. returns AF_UNSPEC if not found
|
|
SockAddr address_of(const std::string& name);
|
|
// all interface names except LO
|
|
std::set<std::string> all_external();
|
|
|
|
// caller must hold lock
|
|
void refresh(bool force=false);
|
|
|
|
struct Iface {
|
|
std::string name;
|
|
uint64_t index;
|
|
bool isLO;
|
|
// interface address(s) -> (maybe) broadcast addr
|
|
std::map<SockAddr, SockAddr, SockAddrOnlyLess> addrs;
|
|
Iface(const std::string& name, uint64_t index, bool isLO) :name(name), index(index), isLO(isLO) {}
|
|
};
|
|
|
|
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;
|
|
private:
|
|
static
|
|
decltype (byIndex) _refresh();
|
|
};
|
|
|
|
} // namespace impl
|
|
|
|
|
|
#ifdef PVXS_EXPERT_API_ENABLED
|
|
|
|
struct Timer::Pvt {
|
|
const evbase base;
|
|
std::function<void()> cb;
|
|
evevent timer;
|
|
|
|
Pvt(const evbase& base, std::function<void()>&& cb)
|
|
:base(base), cb(std::move(cb))
|
|
{}
|
|
~Pvt();
|
|
|
|
bool cancel();
|
|
|
|
static
|
|
Timer buildOneShot(double delay, const evbase &base, std::function<void()>&& cb);
|
|
|
|
INST_COUNTER(Timer);
|
|
};
|
|
|
|
#endif // PVXS_EXPERT_API_ENABLED
|
|
|
|
} // namespace pvxs
|
|
|
|
#endif /* EVHELPER_H */
|