start client

This commit is contained in:
Michael Davidsaver
2020-02-17 15:43:56 -08:00
parent 1aa1b56acb
commit 1edeab8a39
9 changed files with 1567 additions and 0 deletions
+371
View File
@@ -0,0 +1,371 @@
/**
* 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 <osiProcess.h>
#include <pvxs/log.h>
#include "clientimpl.h"
namespace pvxs {
namespace client {
DEFINE_LOGGER(io, "pvxs.client.io");
Connection::Connection(const std::shared_ptr<Context::Pvt>& context, const SockAddr& peerAddr)
:ConnBase (true,
bufferevent_socket_new(context->tcp_loop.base, -1, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS),
peerAddr)
,context(context)
,echoTimer(event_new(context->tcp_loop.base, -1, EV_TIMEOUT|EV_PERSIST, &tickEchoS, this))
{
bufferevent_setcb(bev.get(), &bevReadS, nullptr, &bevEventS, this);
// shorter timeout until connect() ?
timeval timo = {30, 0};
bufferevent_set_timeouts(bev.get(), &timo, &timo);
if(bufferevent_socket_connect(bev.get(), &peerAddr->sa, peerAddr.size()))
throw std::runtime_error("Unable to begin connecting");
log_debug_printf(io, "Connecting to %s\n", peerName.c_str());
}
Connection::~Connection()
{
log_debug_printf(io, "Cleaning connection to %s\n", peerName.c_str());
cleanup();
}
void Connection::createChannels()
{
if(!ready)
return; // defer until CONNECTION_VALIDATED
(void)evbuffer_drain(txBody.get(), evbuffer_get_length(txBody.get()));
auto todo = std::move(pending);
for(auto& wchan : todo) {
auto chan = wchan.lock();
if(!chan)
continue;
{
(void)evbuffer_drain(txBody.get(), evbuffer_get_length(txBody.get()));
EvOutBuf R(hostBE, txBody.get());
to_wire(R, uint16_t(1u));
to_wire(R, chan->cid);
to_wire(R, chan->name);
}
enqueueTxBody(CMD_CREATE_CHANNEL);
creatingByCID[chan->cid] = chan;
chan->state = Channel::Creating;
log_debug_printf(io, "Server %s creating channel '%s' (%u)\n", peerName.c_str(),
chan->name.c_str(), unsigned(chan->cid));
}
}
void Connection::bevEvent(short events)
{
ConnBase::bevEvent(events);
if(bev && (events&BEV_EVENT_CONNECTED)) {
log_debug_printf(io, "Connected to %s\n", peerName.c_str());
auto tx = bufferevent_get_output(bev.get());
to_evbuf(tx, Header{pva_ctrl_msg::SetEndian,
pva_flags::Control,
0u},
hostBE);
if(bufferevent_enable(bev.get(), EV_READ|EV_WRITE))
throw std::logic_error("Unable to enable BEV");
// start echo timer
timeval interval{15, 0};
if(event_add(echoTimer.get(), &interval))
log_err_printf(io, "Server %s error starting echoTimer\n", peerName.c_str());
}
}
void Connection::cleanup()
{
// (maybe) keep myself alive
std::shared_ptr<Connection> self;
context->connByAddr.erase(peerAddr);
if(bev)
bev.reset();
if(event_del(echoTimer.get()))
log_err_printf(io, "Server %s error stopping echoTimer\n", peerName.c_str());
// return Channels to Searching state
for(auto& wchan : pending) {
auto chan = wchan.lock();
if(!chan)
continue;
chan->state = Channel::Searching;
chan->sid = 0xdeadbeef; // spoil
self = std::move(chan->conn);
context->searchBuckets[context->currentBucket].push_back(chan);
log_debug_printf(io, "Server %s detach channel '%s' to re-search\n", peerName.c_str(), chan->name.c_str());
}
// paranoia
pending.clear();
chanBySID.clear();
}
void Connection::handle_CONNECTION_VALIDATION()
{
log_debug_printf(io, "Server %s begins validation handshake\n", peerName.c_str());
EvInBuf M(peerBE, segBuf.get(), 16);
// unused
// serverReceiveBufferSize
// serverIntrospectionRegistryMaxSize
M.skip(4u + 2u);
Size nauth{};
from_wire(M, nauth);
std::string selected;
for(auto n : range(nauth.size)) {
(void)n;
std::string method;
from_wire(M, method);
if(method=="ca" || (method=="anonymous" && selected!="ca"))
selected = method;
}
if(!M.good()) {
log_err_printf(io, "Server %s sends invalid CONNECTION_VALIDATION. Disconnect...\n", peerName.c_str());
bev.reset();
return;
}
if(!selected.empty()) {
log_debug_printf(io, "Server %s selecting auth '%s'\n", peerName.c_str(), selected.c_str());
} else {
selected = "anonymous";
log_warn_printf(io, "Server %s no supported auth. try to force '%s'\n", peerName.c_str(), selected.c_str());
}
Value cred;
if(selected=="ca") {
cred = context->caMethod.cloneEmpty();
std::vector<char> buffer(256u);
if(osiGetUserName(&buffer[0], buffer.size()) == osiGetUserNameSuccess) {
buffer[buffer.size()-1] = '\0';
cred["user"] = buffer.data();
} else {
cred["user"] = "nobody";
}
if (gethostname(&buffer[0], buffer.size()) == 0) {
buffer[buffer.size()-1] = '\0';
cred["host"] = buffer.data();
} else {
cred["host"] = "invalidhost.";
}
log_info_printf(io, "Server %s 'ca' auth as %s@%s\n", peerName.c_str(),
cred["user"].as<std::string>().c_str(),
cred["host"].as<std::string>().c_str());
}
{
(void)evbuffer_drain(txBody.get(), evbuffer_get_length(txBody.get()));
EvOutBuf R(hostBE, txBody.get());
// serverReceiveBufferSize, not used
to_wire(R, uint32_t(0x10000));
// serverIntrospectionRegistryMaxSize, also not used
to_wire(R, uint16_t(0x7fff));
// QoS, not used (quality?)
to_wire(R, uint16_t(0));
to_wire(R, selected);
to_wire(R, Value::Helper::desc(cred));
if(cred)
to_wire_full(R, cred);
}
enqueueTxBody(CMD_CONNECTION_VALIDATION);
}
void Connection::handle_CONNECTION_VALIDATED()
{
EvInBuf M(peerBE, segBuf.get(), 16);
Status sts{};
from_wire(M, sts);
if(!M.good()) {
log_crit_printf(io, "Server %s sends invalid CONNECTION_VALIDATED. Disconnecting...\n", peerName.c_str());
bev.reset();
return;
} else if(!sts.isSuccess()) {
log_err_printf(io, "Server %s refuses auth. Trying to proceed w/o cred\n", peerName.c_str());
} else {
log_debug_printf(io, "Server %s accepts auth%s%s\n", peerName.c_str(),
sts.msg.empty() ? "" : " ", sts.msg.c_str());
}
ready = true;
createChannels();
}
void Connection::handle_CREATE_CHANNEL()
{
EvInBuf M(peerBE, segBuf.get(), 16);
uint32_t cid, sid;
Status sts{};
from_wire(M, cid);
from_wire(M, sid);
from_wire(M, sts);
// "spec" calls for uint16_t Access Rights here, but pvAccessCPP don't include this (it's useless anyway)
if(!M.good()) {
log_crit_printf(io, "Server %s sends invalid CREATE_CHANNEL. Disconnecting...\n", peerName.c_str());
bev.reset();
return;
}
std::shared_ptr<Channel> chan;
{
auto it = creatingByCID.find(cid);
if(it==creatingByCID.end() || !(chan = it->second.lock())) {
if(it!=creatingByCID.end())
creatingByCID.erase(it);
if(sts.isSuccess()) {
// we now have a channel which is no longer interesting.
log_debug_printf(io, "Server %s disposing of newly stale channel\n", peerName.c_str());
{
(void)evbuffer_drain(txBody.get(), evbuffer_get_length(txBody.get()));
EvOutBuf R(hostBE, txBody.get());
to_wire(R, sid);
to_wire(R, cid);
}
enqueueTxBody(CMD_DESTROY_CHANNEL);
}
return;
}
creatingByCID.erase(it);
}
if(!sts.isSuccess()) {
// server refuses to create a channel, but presumably responded positivly to search
chan->state = Channel::Searching;
context->searchBuckets[context->currentBucket].push_back(chan);
log_warn_printf(io, "Server %s refuses channel to '%s' : %s\n", peerName.c_str(),
chan->name.c_str(), sts.msg.c_str());
} else {
chan->state = Channel::Active;
chan->sid = sid;
chanBySID[sid] = chan;
log_debug_printf(io, "Server %s active channel to '%s' %u:%u\n", peerName.c_str(),
chan->name.c_str(), unsigned(chan->cid), unsigned(chan->sid));
chan->createOperations();
}
}
void Connection::handle_DESTROY_CHANNEL()
{
// (maybe) keep myself alive
std::shared_ptr<Connection> self;
EvInBuf M(peerBE, segBuf.get(), 16);
uint32_t cid, sid;
from_wire(M, sid);
from_wire(M, cid);
if(!M.good()) {
log_crit_printf(io, "Server %s sends invalid DESTROY_CHANNEL. Disconnecting...\n", peerName.c_str());
bev.reset();
return;
}
std::shared_ptr<Channel> chan;
{
auto it = chanBySID.find(sid);
if(it==chanBySID.end() || !(chan = it->second.lock())) {
log_debug_printf(io, "Server %s destroys non-existant channel %u:%u\n",
peerName.c_str(), unsigned(cid), unsigned(sid));
return;
}
}
chanBySID.erase(sid);
chan->state = Channel::Searching;
chan->sid = 0xdeadbeef; // spoil
self = std::move(chan->conn);
context->searchBuckets[context->currentBucket].push_back(chan);
log_debug_printf(io, "Server %s destroys channel '%s' %u:%u\n",
peerName.c_str(), chan->name.c_str(), unsigned(cid), unsigned(sid));
}
void Connection::tickEcho()
{
log_debug_printf(io, "Server %s ping\n", peerName.c_str());
if(!bev)
return;
auto tx = bufferevent_get_output(bev.get());
to_evbuf(tx, Header{CMD_ECHO, 0u, 0u}, hostBE);
// maybe help reduce latency
bufferevent_flush(bev.get(), EV_WRITE, BEV_FLUSH);
}
void Connection::tickEchoS(evutil_socket_t fd, short evt, void *raw)
{
try {
static_cast<Connection*>(raw)->tickEcho();
}catch(std::exception& e){
log_crit_printf(io, "Unhandled error in echo timer callback: %s\n", e.what());
}
}
} // namespace client
} // namespace pvxs