Files
pvxs/src/serverconn.cpp
T
2019-12-15 16:43:22 -08:00

538 lines
16 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 <limits>
#include <system_error>
#include <utility>
#include <osiSock.h>
#include <epicsGuard.h>
#include <epicsAssert.h>
#include <pvxs/log.h>
#include "serverconn.h"
// Amount of following messages which we allow to be read while
// processing the current message. Avoids some extra recv() calls,
// at the price of maybe extra copying.
static const size_t tcp_readahead = 0x1000;
namespace pvxs {namespace impl {
// message related to client state and errors
DEFINE_LOGGER(connsetup, "tcp.setup");
// related to low level send/recv
DEFINE_LOGGER(connio, "tcp.io");
DEFINE_LOGGER(remote, "tcp.log");
ServerConn::ServerConn(ServIface* iface, evutil_socket_t sock, struct sockaddr *peer, int socklen)
:iface(iface)
,peerAddr(peer, socklen)
,peerName(peerAddr.tostring())
,bev(bufferevent_socket_new(iface->server->acceptor_loop.base, sock, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS))
,peerBE(true) // arbitrary choice, default should be overwritten before use
,expectSeg(false)
,segCmd(0xff)
,segBuf(evbuffer_new())
,txBody(evbuffer_new())
,nextSID(0)
{
log_printf(connio, PLVL_DEBUG, "Client %s connects\n", peerName.c_str());
bufferevent_setcb(bev.get(), &bevReadS, &bevWriteS, &bevEventS, this);
// initially wait for at least a header
bufferevent_setwatermark(bev.get(), EV_READ, 8, tcp_readahead);
timeval timo = {30, 0};
bufferevent_set_timeouts(bev.get(), &timo, &timo);
auto tx = bufferevent_get_output(bev.get());
std::vector<uint8_t> buf(128);
// queue connection validation message
{
uint8_t flags = hostBE ? pva_flags::MSB : 0;
flags |= pva_flags::Server;
VectorOutBuf M(hostBE, buf);
to_wire(M, Header{pva_ctrl_msg::SetEndian, pva_flags::Control|pva_flags::Server, 0});
auto save = M.save();
M.skip(8); // placeholder for header
auto bstart = M.save();
// serverReceiveBufferSize, not used
to_wire(M, uint32_t(0x10000));
// serverIntrospectionRegistryMaxSize, also not used
to_wire(M, uint16_t(0x7fff));
to_wire(M, Size{2});
to_wire(M, "anonymous");
to_wire(M, "ca");
auto bend = M.save();
FixedBuf H(hostBE, save, 8);
to_wire(H, Header{CMD_CONNECTION_VALIDATION, pva_flags::Server, uint32_t(bend-bstart)});
assert(M.good() && H.good());
if(evbuffer_add(tx, buf.data(), M.save()-buf.data()))
throw std::bad_alloc();
}
if(bufferevent_enable(bev.get(), EV_READ|EV_WRITE))
throw std::logic_error("Unable to enable BEV");
}
ServerConn::~ServerConn()
{}
const std::shared_ptr<ServerChan>& ServerConn::lookupSID(uint32_t sid)
{
auto it = chanBySID.find(sid);
if(it==chanBySID.end())
throw std::runtime_error(SB()<<"Client "<<peerName<<" non-existant SID "<<sid);
return it->second;
}
void ServerConn::enqueueTxBody(pva_app_msg_t cmd)
{
auto tx = bufferevent_get_output(bev.get());
to_evbuf(tx, Header{cmd,
pva_flags::Server,
uint32_t(evbuffer_get_length(txBody.get()))},
hostBE);
auto err = evbuffer_add_buffer(tx, txBody.get());
assert(!err);
}
void ServerConn::handle_ECHO()
{
// Client requests echo as a keep-alive check
auto tx = bufferevent_get_output(bev.get());
uint32_t len = evbuffer_get_length(segBuf.get());
to_evbuf(tx, Header{CMD_ECHO, pva_flags::Server, len}, hostBE);
auto err = evbuffer_add_buffer(tx, segBuf.get());
assert(!err);
// maybe help reduce latency
bufferevent_flush(bev.get(), EV_WRITE, BEV_FLUSH);
}
static
void auth_complete(ServerConn *self, const Status& sts)
{
(void)evbuffer_drain(self->txBody.get(), evbuffer_get_length(self->txBody.get()));
{
EvOutBuf M(hostBE, self->txBody.get());
to_wire(M, sts);
}
self->enqueueTxBody(CMD_CONNECTION_VALIDATED);
log_printf(connsetup, PLVL_DEBUG, "%s Auth complete with %d\n", self->peerName.c_str(), sts.code);
}
void ServerConn::handle_CONNECTION_VALIDATION()
{
// Client begins (restarts?) Auth handshake
EvInBuf M(peerBE, segBuf.get(), 16);
std::string selected;
{
M.skip(6); // ignore unused buffer and introspection size
uint16_t qos;
from_wire(M, qos);
from_wire(M, selected);
Value auth;
from_wire_type_value(M, rxRegistry, auth);
// TODO store credentials
if(!M.good()) {
log_printf(connio, PLVL_ERR, "Client %s Truncated/Invalid ConnValid from client\n", peerName.c_str());
bev.reset();
return;
} else {
log_printf(connsetup, PLVL_DEBUG, "Client %s authenticates using %s and %s\n",
peerName.c_str(), selected.c_str(),
std::string(SB()<<auth).c_str());
}
}
if(selected!="ca" && selected!="anonymous") {
log_printf(connsetup, PLVL_DEBUG, "Client %s selects unadvertised auth \"%s\"", peerName.c_str(), selected.c_str());
auth_complete(this, Status{Status::Error, "Client selects unadvertised auth"});
return;
} else {
log_printf(connsetup, PLVL_DEBUG, "Client %s selects auth \"%s\"", peerName.c_str(), selected.c_str());
}
// remainder of segBuf is payload w/ credentials
// TODO actually check credentials
auth_complete(this, Status{Status::Ok});
}
void ServerConn::handle_AUTHNZ()
{
// ignored (so far no auth plugin actually uses)
}
void ServerConn::handle_PUT()
{}
void ServerConn::handle_RPC()
{}
void ServerConn::handle_PUT_GET()
{}
void ServerConn::handle_CANCEL_REQUEST()
{
EvInBuf M(peerBE, segBuf.get(), 16);
uint32_t sid=0, ioid=0;
from_wire(M, sid);
from_wire(M, ioid);
if(!M.good())
throw std::runtime_error("Error decoding DestroyOp");
auto it = opByIOID.find(ioid);
if(it==opByIOID.end()) {
log_printf(connsetup, PLVL_WARN, "Client %s Cancel of non-existant Op %u\n", peerName.c_str(), unsigned(ioid));
return;
}
const auto& op = it->second;
auto chan = op->chan.lock();
if(!chan || chan->sid!=sid) {
log_printf(connsetup, PLVL_ERR, "Client %s Cancel inconsistent Op\n", peerName.c_str());
return;
}
if(op->state==ServerOp::Executing) {
op->state = ServerOp::Idle;
} else {
// an allowed race
log_printf(connsetup, PLVL_DEBUG, "Client %s Cancel of non-executing Op\n", peerName.c_str());
}
}
void ServerConn::handle_DESTROY_REQUEST()
{
EvInBuf M(peerBE, segBuf.get(), 16);
uint32_t sid=0, ioid=0;
from_wire(M, sid);
from_wire(M, ioid);
if(!M.good())
throw std::runtime_error("Error decoding DestroyOp");
auto& chan = lookupSID(sid);
auto it = opByIOID.find(ioid);
auto n = chan->opByIOID.erase(ioid);
if(it==opByIOID.end() || n!=1) {
log_printf(connsetup, PLVL_WARN, "Client %s can't destroy non-existant op %u:%u\n",
peerName.c_str(), unsigned(sid), unsigned(ioid));
} else {
it->second->state = ServerOp::Dead;
// TODO interface to notify Op of cancel/destroy
chan->opByIOID.erase(it);
}
}
void ServerConn::handle_MESSAGE()
{
EvInBuf M(peerBE, segBuf.get(), 16);
uint32_t ioid = 0;
uint8_t mtype = 0;
std::string msg;
from_wire(M, ioid);
from_wire(M, mtype);
from_wire(M, msg);
if(!M.good())
throw std::runtime_error("Decode error for Message");
auto it = opByIOID.find(ioid);
if(it==opByIOID.end()) {
log_printf(connsetup, PLVL_DEBUG, "Client %s Message on non-existant ioid\n", peerName.c_str());
return;
}
auto chan = it->second->chan.lock();
switch(mtype) {
case 0: mtype = PLVL_INFO;
case 1: mtype = PLVL_WARN;
case 2: mtype = PLVL_ERR;
default:mtype = PLVL_CRIT;
}
log_printf(remote, mtype, "Client %s Channel %s Remote message: %s\n",
peerName.c_str(), chan ? "<dead>" : chan->name.c_str(),
msg.c_str());
}
void ServerConn::cleanup()
{
log_printf(connsetup, PLVL_DEBUG, "Client %s Cleanup TCP Connection\n", peerName.c_str());
auto it = iface->server->connections.find(this);
if(it!=iface->server->connections.end()) {
auto self = std::move(it->second);
iface->server->connections.erase(it);
// delete this
}
}
void ServerConn::bevEvent(short events)
{
if(events&(BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT)) {
if(events&BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
const char *msg = evutil_socket_error_to_string(err);
log_printf(connio, PLVL_ERR, "Client %s connection closed with socket error %d : %s\n", peerName.c_str(), err, msg);
}
if(events&BEV_EVENT_EOF) {
log_printf(connio, PLVL_DEBUG, "Client %s connection closed by peer\n", peerName.c_str());
}
if(events&BEV_EVENT_TIMEOUT) {
log_printf(connio, PLVL_WARN, "Client %s connection timeout\n", peerName.c_str());
}
bev.reset();
}
if(!bev)
cleanup();
}
void ServerConn::bevRead()
{
auto rx = bufferevent_get_input(bev.get());
while(bev && evbuffer_get_length(rx)>=8) {
uint8_t header[8];
auto ret = evbuffer_copyout(rx, header, sizeof(header));
assert(ret==sizeof(header)); // previously verified
if(header[0]!=0xca || header[1]==0 || (header[2]&pva_flags::Server)) {
log_hex_printf(connio, PLVL_ERR, header, sizeof(header), "Client %s Protocol decode fault. Force disconnect.\n", peerName.c_str());
bev.reset();
break;
}
log_hex_printf(connio, PLVL_DEBUG, header, sizeof(header), "Client %s Receive header\n", peerName.c_str());
if(header[2]&pva_flags::Control) {
// Control messages are not actually useful
evbuffer_drain(rx, 8);
continue;
}
// application message
peerBE = header[2]&pva_flags::MSB;
// a bit verbose :P
FixedBuf L(peerBE, header+4, 4);
uint32_t len = 0;
from_wire(L, len);
assert(L.good());
if(evbuffer_get_length(rx)-8 < len) {
// wait for complete payload
// and some additional if available
size_t readahead = len;
if(readahead < std::numeric_limits<size_t>::max()-tcp_readahead)
readahead += tcp_readahead;
bufferevent_setwatermark(bev.get(), EV_READ, len, readahead);
break;
}
evbuffer_drain(rx, 8);
{
unsigned n = evbuffer_remove_buffer(rx, segBuf.get(), len);
assert(n==len); // we know rx buf contains the entire body
}
// so far we do not use segmentation to support incremental processing
// of long messages. We instead accumulate all segments of a message
// prior to parsing.
auto seg = header[2]&pva_flags::SegMask;
bool continuation = seg&pva_flags::SegLast; // true for mid or last. false for none for first
if((continuation ^ expectSeg) || (continuation && header[3]!=segCmd)) {
log_printf(connio, PLVL_CRIT, "Client %s Peer segmentation violation %c%c 0x%02x==0x%02x\n", peerName.c_str(),
expectSeg?'Y':'N', continuation?'Y':'N',
segCmd, header[3]);
bev.reset();
break;
}
if(!seg || seg==pva_flags::SegFirst) {
expectSeg = true;
segCmd = header[3];
}
if(!seg || seg==pva_flags::SegLast) {
expectSeg = false;
// ready to process segBuf
switch(segCmd) {
default:
log_printf(connio, PLVL_DEBUG, "Client %s Ignore unexpected command 0x%02x\n", peerName.c_str(), segCmd);
evbuffer_drain(segBuf.get(), evbuffer_get_length(segBuf.get()));
break;
#define CASE(OP) case CMD_##OP: handle_##OP(); break
CASE(ECHO);
CASE(CONNECTION_VALIDATION);
CASE(SEARCH);
CASE(AUTHNZ);
CASE(CREATE_CHANNEL);
CASE(DESTROY_CHANNEL);
CASE(GET);
CASE(PUT);
CASE(PUT_GET);
CASE(RPC);
CASE(CANCEL_REQUEST);
CASE(DESTROY_REQUEST);
CASE(GET_FIELD);
CASE(MESSAGE);
#undef CASE
}
// handlers may be cleared bev to force disconnect
// silently drain any unprocessed body (forward compatibility)
if(auto n = evbuffer_get_length(segBuf.get()))
evbuffer_drain(segBuf.get(), n);
// wait for next header
bufferevent_setwatermark(bev.get(), EV_READ, 8, tcp_readahead);
}
}
if(!bev) {
cleanup();
} else if(auto tx = bufferevent_get_output(bev.get())) {
if(evbuffer_get_length(tx)>=0x100000) {
// write buffer "full". stop reading until it drains
// TODO configure
(void)bufferevent_disable(bev.get(), EV_READ);
bufferevent_setwatermark(bev.get(), EV_WRITE, 0x100000/2, 0);
}
}
}
void ServerConn::bevWrite()
{
(void)bufferevent_enable(bev.get(), EV_READ);
bufferevent_setwatermark(bev.get(), EV_WRITE, 0, 0);
}
void ServerConn::bevEventS(struct bufferevent *bev, short events, void *ptr)
{
auto conn = static_cast<ServerConn*>(ptr);
try {
conn->bevEvent(events);
}catch(std::exception& e){
log_printf(connsetup, PLVL_CRIT, "Client %s Unhandled error in bev event callback: %s\n", conn->peerName.c_str(), e.what());
static_cast<ServerConn*>(ptr)->cleanup();
}
}
void ServerConn::bevReadS(struct bufferevent *bev, void *ptr)
{
auto conn = static_cast<ServerConn*>(ptr);
try {
conn->bevRead();
}catch(std::exception& e){
log_printf(connsetup, PLVL_CRIT, "Client %s Unhandled error in bev read callback: %s\n", conn->peerName.c_str(), e.what());
static_cast<ServerConn*>(ptr)->cleanup();
}
}
void ServerConn::bevWriteS(struct bufferevent *bev, void *ptr)
{
auto conn = static_cast<ServerConn*>(ptr);
try {
conn->bevWrite();
}catch(std::exception& e){
log_printf(connsetup, PLVL_CRIT, "Client %s Unhandled error in bev write callback: %s\n", conn->peerName.c_str(), e.what());
static_cast<ServerConn*>(ptr)->cleanup();
}
}
ServIface::ServIface(const std::string& addr, unsigned short port, server::Server::Pvt *server)
:server(server)
,bind_addr(AF_INET, addr.c_str(), port)
,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(evconnlistener_new(server->acceptor_loop.base, onConnS, this, LEV_OPT_DISABLED, backlog, sock.sock));
}
void ServIface::onConnS(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *peer, int socklen, void *raw)
{
auto self = static_cast<ServIface*>(raw);
try {
if(peer->sa_family!=AF_INET) {
log_printf(connsetup, PLVL_CRIT, "Interface %s Rejecting !ipv4 client\n", self->name.c_str());
evutil_closesocket(sock);
return;
}
std::shared_ptr<ServerConn> conn(new ServerConn(self, sock, peer, socklen));
self->server->connections[conn.get()] = std::move(conn);
}catch(std::exception& e){
log_printf(connsetup, PLVL_CRIT, "Interface %s Unhandled error in accept callback: %s\n", self->name.c_str(), e.what());
evutil_closesocket(sock);
}
}
ServerOp::~ServerOp() {}
void ServerOp::cancel() {}
}} // namespace pvxs::impl