Skip to content

Commit

Permalink
Add TLS support w/ OpenSSL
Browse files Browse the repository at this point in the history
  • Loading branch information
mdavidsaver committed Aug 10, 2023
1 parent 5f5cda8 commit d88e9cd
Show file tree
Hide file tree
Showing 31 changed files with 1,786 additions and 135 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/ci-scripts-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ jobs:
sudo apt-get update
sudo apt-get -y install g++-mingw-w64-x86-64 cmake gdb qemu-system-x86 libssl-dev
if: runner.os == 'Linux'
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Python deps
run: pip install pyOpenSSL
- name: Host Info
run: openssl version -a
- name: Automatic core dumper analysis
Expand All @@ -158,7 +164,8 @@ jobs:
- name: Generate Docs
if: matrix.doc
run: |
sudo apt-get -y install doxygen python-is-python3 python3-breathe
sudo apt-get -y install doxygen
pip install breathe Sphinx
make -C documentation
- name: otool
if: ${{ always() && runner.os == 'macOS' }}
Expand Down
14 changes: 14 additions & 0 deletions ioc/iochooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,17 @@ void pvxrefdiff() {
}
}

void pvxreconfigure()
{
if (auto pPvxsServer = pvxsServer.load()) {
printf("Reconfiguring QSRV\n");
pPvxsServer->reconfigure(server::Config::from_env());
pvxsr(0); // print new configuration
} else {
fprintf(stderr, "Warning: QSRV not running\n");
}
}

} // namespace

/**
Expand Down Expand Up @@ -302,6 +313,9 @@ void pvxsBaseRegistrar() {
"Save the current set of instance counters for reference by later pvxrefdiff.\n").implementation<&pvxrefsave>();
IOCShCommand<>("pvxrefdiff",
"Show different of current instance counts with those when pvxrefsave was called.\n").implementation<&pvxrefdiff>();
IOCShCommand<>("pvxreconfigure",
"Reconfigure QSRV using current values of EPICS_PVA*\n")
.implementation<&pvxreconfigure>();

// Initialise the PVXS Server
initialisePvxsServer();
Expand Down
10 changes: 10 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ ifeq (YES,$(EVENT2_HAS_OPENSSL))
USR_CPPFLAGS += -DPVXS_ENABLE_OPENSSL
endif

# set to NO to disable handling of $SSLKEYLOGFILE
PVXS_ENABLE_SSLKEYLOGFILE ?= YES

PVXS_ENABLE_SSLKEYLOGFILE_YES = -DPVXS_ENABLE_SSLKEYLOGFILE
USR_CPPFLAGS += $(PVXS_ENABLE_SSLKEYLOGFILE_$(PVXS_ENABLE_SSLKEYLOGFILE))

ifdef T_A
ifneq ($(CONFIG_LOADED),YES)
$(error Toolchain inspection failed $(MAKEFILE_LIST))
Expand Down Expand Up @@ -111,6 +117,10 @@ LIB_SRCS += clientget.cpp
LIB_SRCS += clientmon.cpp
LIB_SRCS += clientdiscover.cpp

ifeq (YES,$(EVENT2_HAS_OPENSSL))
LIB_SRCS += ossl.cpp
endif

LIB_LIBS += Com

# special case matching configure/RULES_PVXS_MODULE
Expand Down
117 changes: 99 additions & 18 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,13 @@ RemoteError::~RemoteError() {}

Finished::~Finished() {}

Connected::Connected(const std::string& peerName)
Connected::Connected(const std::string& peerName,
const epicsTime& time,
const std::shared_ptr<const pvxs::client::ServerCredentials> &cred)
:std::runtime_error("Connected")
,peerName(peerName)
,time(epicsTime::getCurrent())
,time(time)
,cred(cred)
{}

Connected::~Connected() {}
Expand Down Expand Up @@ -205,7 +208,7 @@ void Channel::disconnect(const std::shared_ptr<Channel>& self)
if(!self) { // in ~Channel
// searchBuckets cleaned in tickSearch()

} else if(forcedServer.family()==AF_UNSPEC) { // begin search
} else if(forcedServer.addr.family()==AF_UNSPEC) { // begin search

auto next = (context->currentBucket + holdoff) % nBuckets;

Expand All @@ -216,7 +219,8 @@ void Channel::disconnect(const std::shared_ptr<Channel>& self)
name.c_str());

} else if(context->state==ContextImpl::Running) { // reconnect to specific server
conn = Connection::build(context, forcedServer, true);
conn = Connection::build(context, forcedServer.addr, true,
forcedServer.scheme==SockEndpoint::TLS);

conn->pending[cid] = self;
state = Connecting;
Expand Down Expand Up @@ -273,10 +277,13 @@ std::shared_ptr<Connect> ConnectBuilder::exec()
op->chan = Channel::build(context, op->_name, server);

bool cur = op->_connected = op->chan->state==Channel::Active;
if(cur && op->_onConn)
op->_onConn();
else if(!cur && op->_onDis)
if(cur && op->_onConn) {
auto& conn = op->chan->conn;
Connected evt(conn->peerName, conn->connTime, conn->cred);
op->_onConn(evt);
} else if(!cur && op->_onDis) {
op->_onDis();
}

op->chan->connectors.push_back(op.get());
});
Expand Down Expand Up @@ -349,11 +356,14 @@ std::shared_ptr<Channel> Channel::build(const std::shared_ptr<ContextImpl>& cont
if(context->state!=ContextImpl::Running)
throw std::logic_error("Context close()d");

SockAddr forceServer;
SockEndpoint forceServer;
decltype (context->chanByName)::key_type namekey(name, server);

if(!server.empty()) {
forceServer.setAddress(server.c_str(), context->effective.tcp_port);
SockEndpoint temp(server.c_str(), context->effective.tcp_port);
if(!temp.iface.empty() || temp.ttl!=-1)
throw std::runtime_error(SB()<<"interface or TTL restriction not supported for .server(): "<<server);
forceServer = std::move(temp);
}

std::shared_ptr<Channel> chan;
Expand All @@ -380,7 +390,8 @@ std::shared_ptr<Channel> Channel::build(const std::shared_ptr<ContextImpl>& cont

} else { // bypass search and connect so a specific server
chan->forcedServer = forceServer;
chan->conn = Connection::build(context, forceServer);
chan->conn = Connection::build(context, forceServer.addr, false,
forceServer.scheme==SockEndpoint::TLS);

chan->conn->pending[chan->cid] = chan;
chan->state = Connecting;
Expand Down Expand Up @@ -410,6 +421,45 @@ Context::Context(const Config& conf)

Context::~Context() {}

void Context::reconfigure(const Config& newconf)
{
if(!pvt)
throw std::logic_error("NULL Context");

#ifdef PVXS_ENABLE_OPENSSL

ossl::SSLContext new_context;
if(!newconf.tls_keychain_file.empty()
|| !newconf.tls_authority_files.empty()
|| !newconf.tls_authority_dirs.empty())
{
new_context = ossl::SSLContext::for_client(newconf);
}

pvt->impl->manager.loop().call([this, &new_context](){

log_debug_printf(setup, "Client reconfigure%s", "\n");

auto conns(std::move(pvt->impl->connByAddr));

for(auto& pair : conns) {
auto conn = pair.second.lock();
if(!conn)
continue;

conn->cleanup();
}

conns.clear();

pvt->impl->tls_context = new_context;
});

#else
pvt->impl->manager.loop().sync();
#endif
}

const Config& Context::config() const
{
if(!pvt)
Expand Down Expand Up @@ -542,6 +592,15 @@ ContextImpl::ContextImpl(const Config& conf, const evbase& tcp_loop)
,nsChecker(__FILE__, __LINE__,
event_new(tcp_loop.base, -1, EV_TIMEOUT|EV_PERSIST, &ContextImpl::onNSCheckS, this))
{
#ifdef PVXS_ENABLE_OPENSSL
if(!effective.tls_keychain_file.empty()
|| !effective.tls_authority_files.empty()
|| !effective.tls_authority_dirs.empty())
{
tls_context = ossl::SSLContext::for_client(effective);
}
#endif

searchBuckets.resize(nBuckets);

std::set<SockAddr, SockAddrOnlyLess> bcasts;
Expand Down Expand Up @@ -596,14 +655,17 @@ ContextImpl::ContextImpl(const Config& conf, const evbase& tcp_loop)
}

for(auto& addr : effective.nameServers) {
SockAddr saddr;
SockEndpoint saddr;
try {
saddr.setAddress(addr.c_str(), effective.tcp_port);
SockEndpoint temp(addr.c_str(), effective.tcp_port);
if(!temp.iface.empty() || temp.ttl!=-1)
throw std::runtime_error(SB()<<"interface or TTL restriction not supported for nameserver: "<<addr);
saddr = std::move(temp);
}catch(std::runtime_error& e) {
log_err_printf(setup, "%s Ignoring...\n", e.what());
}

log_info_printf(io, "Searching to TCP %s\n", saddr.tostring().c_str());
log_info_printf(io, "Searching to TCP %s\n", std::string(SB()<<saddr).c_str());
nameServers.emplace_back(saddr, nullptr);
}

Expand Down Expand Up @@ -654,9 +716,11 @@ void ContextImpl::startNS()
// start connections to name servers
for(auto& ns : nameServers) {
const auto& serv = ns.first;
ns.second = Connection::build(shared_from_this(), serv);
ns.second = Connection::build(shared_from_this(), serv.addr, false,
serv.scheme==SockEndpoint::TLS);
ns.second->nameserver = true;
log_debug_printf(io, "Connecting to nameserver %s\n", ns.second->peerName.c_str());
log_debug_printf(io, "Connecting to nameserver %s%s\n",
ns.second->peerName.c_str(), ns.second->isTLS ? " TLS" : "");
}

if(event_add(nsChecker.get(), &tcpNSCheckInterval))
Expand Down Expand Up @@ -873,7 +937,16 @@ void procSearchReply(ContextImpl& self, const SockAddr& src, uint8_t peerVersion
self.onBeacon(fakebeacon);
}

if(!found || proto!="tcp")
bool isTCP = proto=="tcp";

#ifdef PVXS_ENABLE_OPENSSL
bool isTLS = proto=="tls";
if(!self.tls_context && isTLS)
return;
#else
const bool isTLS = false;
#endif
if(!found || !(isTCP || isTLS))
return;

for(auto n : range(nSearch)) {
Expand Down Expand Up @@ -901,7 +974,7 @@ void procSearchReply(ContextImpl& self, const SockAddr& src, uint8_t peerVersion
chan->guid = guid;
chan->replyAddr = serv;

chan->conn = Connection::build(self.shared_from_this(), serv);
chan->conn = Connection::build(self.shared_from_this(), serv, false, isTLS);

chan->conn->pending[chan->cid] = chan;
chan->state = Channel::Connecting;
Expand Down Expand Up @@ -1061,6 +1134,13 @@ void ContextImpl::tickSearch(SearchKind kind, bool poked)
if(kind == SearchKind::discover) {
to_wire(M, uint8_t(0u));

#ifdef PVXS_ENABLE_OPENSSL
} else if(tls_context) {
to_wire(M, uint8_t(2u));
to_wire(M, "tls");
to_wire(M, "tcp");
#endif

} else {
to_wire(M, uint8_t(1u));
to_wire(M, "tcp");
Expand Down Expand Up @@ -1286,7 +1366,8 @@ void ContextImpl::onNSCheck()
if(ns.second && ns.second->state != ConnBase::Disconnected) // hold-off, connecting, or connected
continue;

ns.second = Connection::build(shared_from_this(), ns.first);
ns.second = Connection::build(shared_from_this(), ns.first.addr, false,
ns.first.scheme==SockEndpoint::TLS);
ns.second->nameserver = true;
log_debug_printf(io, "Reconnecting nameserver %s\n", ns.second->peerName.c_str());
}
Expand Down
Loading

0 comments on commit d88e9cd

Please sign in to comment.