blob: 1b63d9ed84505baf6ca46b1b547474e4c1483424 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "TcpSslConn.hpp"
#include <openssl/err.h>
#include <openssl/x509.h>
#include <chrono>
#include <thread>
#include <boost/exception/diagnostic_information.hpp>
#include <geode/SystemProperties.hpp>
#include "util/Log.hpp"
namespace apache {
namespace geode {
namespace client {
TcpSslConn::TcpSslConn(const std::string& hostname, uint16_t,
const std::string& sniProxyHostname,
uint16_t sniProxyPort,
std::chrono::microseconds connect_timeout,
int32_t maxBuffSizePool, const std::string& pubkeyfile,
const std::string& privkeyfile,
const std::string& pemPassword)
: TcpConn{sniProxyHostname, sniProxyPort, connect_timeout, maxBuffSizePool},
ssl_context_{boost::asio::ssl::context::sslv23_client} {
init(pubkeyfile, privkeyfile, pemPassword, hostname);
}
TcpSslConn::TcpSslConn(const std::string& hostname, uint16_t port,
std::chrono::microseconds connect_timeout,
int32_t maxBuffSizePool, const std::string& pubkeyfile,
const std::string& privkeyfile,
const std::string& pemPassword)
: TcpConn{hostname, port, connect_timeout, maxBuffSizePool},
ssl_context_{boost::asio::ssl::context::sslv23_client} {
init(pubkeyfile, privkeyfile, pemPassword);
}
TcpSslConn::TcpSslConn(const std::string& ipaddr,
std::chrono::microseconds connect_timeout,
int32_t maxBuffSizePool, const std::string& pubkeyfile,
const std::string& privkeyfile,
const std::string& pemPassword)
: TcpSslConn{
ipaddr.substr(0, ipaddr.find(':')),
static_cast<uint16_t>(std::stoi(ipaddr.substr(ipaddr.find(':') + 1))),
connect_timeout,
maxBuffSizePool,
pubkeyfile,
privkeyfile,
pemPassword} {}
TcpSslConn::TcpSslConn(const std::string& ipaddr,
std::chrono::microseconds connect_timeout,
int32_t maxBuffSizePool,
const std::string& sniProxyHostname,
uint16_t sniProxyPort, const std::string& pubkeyfile,
const std::string& privkeyfile,
const std::string& pemPassword)
: TcpSslConn{
ipaddr.substr(0, ipaddr.find(':')),
static_cast<uint16_t>(std::stoi(ipaddr.substr(ipaddr.find(':') + 1))),
sniProxyHostname,
sniProxyPort,
connect_timeout,
maxBuffSizePool,
pubkeyfile,
privkeyfile,
pemPassword} {}
void TcpSslConn::init(const std::string& pubkeyfile,
const std::string& privkeyfile,
const std::string& pemPassword,
const std::string& sniHostname) {
// Most of the SSL configuration provided *through* Asio is on the context.
// This configuration is copied into each SSL instance upon construction.
// That means you need to get your configuration in order before you
// construct the stream and connect the socket.
LOGDEBUG(
"*** TcpSslConn init, pubkeyfile = %s, pemPassword = %s, sniHostname = "
"%s",
pubkeyfile.c_str(), pemPassword.c_str(), sniHostname.c_str());
try {
ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
ssl_context_.load_verify_file(pubkeyfile);
ssl_context_.set_password_callback(
[pemPassword](std::size_t /*max_length*/,
boost::asio::ssl::context::password_purpose /*purpose*/) {
return pemPassword;
});
if (!privkeyfile.empty()) {
ssl_context_.use_certificate_chain_file(privkeyfile);
ssl_context_.use_private_key_file(
privkeyfile, boost::asio::ssl::context::file_format::pem);
}
auto stream = std::unique_ptr<ssl_stream_type>(
new ssl_stream_type{socket_, ssl_context_});
SSL_set_tlsext_host_name(stream->native_handle(), sniHostname.c_str());
stream->handshake(ssl_stream_type::client);
std::stringstream ss;
ss << "Setup SSL " << socket_.local_endpoint() << " -> "
<< socket_.remote_endpoint();
LOGINFO(ss.str());
ss.clear();
ss << "SNI hostname: " << sniHostname;
LOGINFO(ss.str());
socket_stream_ = std::move(stream);
} catch (const boost::exception& ex) {
// error handling
std::string info = boost::diagnostic_information(ex);
LOGDEBUG("caught boost exception: %s", info.c_str());
}
}
TcpSslConn::~TcpSslConn() {
std::stringstream ss;
ss << "Teardown SSL " << socket_.local_endpoint() << " -> "
<< socket_.remote_endpoint();
LOGFINE(ss.str());
}
size_t TcpSslConn::receive(char* buff, const size_t len,
std::chrono::milliseconds) {
auto start = std::chrono::system_clock::now();
return boost::asio::read(*socket_stream_, boost::asio::buffer(buff, len),
[len, start](boost::system::error_code& ec,
const std::size_t n) -> std::size_t {
if (ec && ec != boost::asio::error::eof) {
// Quit if we encounter an error.
// Defer EOF to timeout.
return 0;
} else if (start + std::chrono::milliseconds(25) <=
std::chrono::system_clock::now()) {
// Sometimes we don't know how much data to
// expect, so we're reading into an oversized
// buffer without knowing when to quit other than
// by timeout. Typically, if we timeout, we also
// have an EOF, meaning the connection is likely
// broken and will have to be closed. But if we
// have bytes, we may have just done a
// dumb/blind/hail mary receive, so defer broken
// connection handling until the next IO
// operation.
if (n) {
// This prevents the timeout from being an
// error condition.
ec = boost::system::error_code{};
}
// But if n == 0 when we timeout, it's just a
// broken connection.
return 0;
}
return len - n;
});
}
size_t TcpSslConn::send(const char* buff, const size_t len,
std::chrono::milliseconds) {
return boost::asio::write(*socket_stream_, boost::asio::buffer(buff, len));
}
} // namespace client
} // namespace geode
} // namespace apache