blob: bda525025897bee9a670e38d4edd81724b5c8a31 [file]
/** @file
A brief file description
@section license License
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 "BIO_fastopen.h"
#include "P_UnixNet.h"
#include "P_UnixNetVConnection.h"
#include "SSLStats.h"
#include "P_Net.h"
#include "P_SSLUtils.h"
#include "ts/ats_probe.h"
#include "P_SSLNextProtocolSet.h"
#include "P_SSLConfig.h"
#include "P_SSLClientUtils.h"
#include "P_SSLNetVConnection.h"
#include "P_UnixNetProcessor.h"
#include "iocore/net/NetHandler.h"
#include "iocore/net/NetVConnection.h"
#include "iocore/net/ProxyProtocol.h"
#include "iocore/net/SSLDiags.h"
#include "iocore/net/SSLSNIConfig.h"
#include "iocore/net/SSLTypes.h"
#include "iocore/net/TLSALPNSupport.h"
#include "tscore/ink_config.h"
#include "tscore/Layout.h"
#include "tscore/InkErrno.h"
#include "tscore/TSSystemState.h"
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <utility>
#if TS_USE_TLS_ASYNC
#include <openssl/async.h>
#endif
using namespace std::literals;
#define SSL_READ_ERROR_NONE 0
#define SSL_READ_ERROR 1
#define SSL_READ_READY 2
#define SSL_READ_COMPLETE 3
#define SSL_READ_WOULD_BLOCK 4
#define SSL_READ_EOS 5
#define SSL_HANDSHAKE_WANT_READ 6
#define SSL_HANDSHAKE_WANT_WRITE 7
#define SSL_HANDSHAKE_WANT_ACCEPT 8
#define SSL_HANDSHAKE_WANT_CONNECT 9
#define SSL_WRITE_WOULD_BLOCK 10
#define SSL_WAIT_FOR_HOOK 11
#define SSL_WAIT_FOR_ASYNC 12
#define SSL_RESTART 13
ClassAllocator<SSLNetVConnection, false> sslNetVCAllocator("sslNetVCAllocator");
namespace
{
DbgCtl dbg_ctl_ssl_early_data{"ssl_early_data"};
DbgCtl dbg_ctl_ssl_early_data_show_received{"ssl_early_data_show_received"};
DbgCtl dbg_ctl_ssl{"ssl"};
DbgCtl dbg_ctl_v_ssl{"v_ssl"};
DbgCtl dbg_ctl_ssl_error{"ssl.error"};
DbgCtl dbg_ctl_ssl_error_accept{"ssl.error.accept"};
DbgCtl dbg_ctl_ssl_error_connect{"ssl.error.connect"};
DbgCtl dbg_ctl_ssl_error_write{"ssl.error.write"};
DbgCtl dbg_ctl_ssl_error_read{"ssl.error.read"};
DbgCtl dbg_ctl_ssl_shutdown{"ssl-shutdown"};
DbgCtl dbg_ctl_ssl_alpn{"ssl_alpn"};
DbgCtl dbg_ctl_ssl_origin_session_cache{"ssl.origin_session_cache"};
DbgCtl dbg_ctl_proxyprotocol{"proxyprotocol"};
const char *
resolve_client_ca_cert_path(const SSLConfigParams *params, const char *path, std::string &storage)
{
if (path == nullptr) {
return params->clientCACertPath;
}
storage = Layout::get()->relative_to(Layout::get()->prefix, path);
return storage.c_str();
}
} // namespace
//
// Private
//
void
SSLNetVConnection::_make_ssl_connection(SSL_CTX *ctx)
{
if (likely(this->ssl = SSL_new(ctx))) {
// Only set up the bio stuff for the server side
if (this->get_context() == NET_VCONNECTION_OUT) {
BIO *bio = BIO_new(const_cast<BIO_METHOD *>(BIO_s_fastopen()));
BIO_set_fd(bio, this->get_socket(), BIO_NOCLOSE);
BIO_fastopen_set_dest_addr(bio,
this->options.f_tcp_fastopen ? this->get_remote_addr() : static_cast<const sockaddr *>(nullptr));
SSL_set_bio(ssl, bio, bio);
} else {
this->initialize_handshake_buffers();
BIO *rbio = BIO_new(BIO_s_mem());
BIO *wbio = BIO_new_socket(this->get_socket(), BIO_NOCLOSE);
BIO_set_mem_eof_return(wbio, -1);
SSL_set_bio(ssl, rbio, wbio);
#if TS_HAS_TLS_EARLY_DATA
update_early_data_config(ssl, SSLConfigParams::server_max_early_data, SSLConfigParams::server_recv_max_early_data);
#endif
}
this->_bindSSLObject();
}
}
void
SSLNetVConnection::_bindSSLObject()
{
SSLNetVCAttach(this->ssl, this);
TLSBasicSupport::bind(this->ssl, this);
TLSEventSupport::bind(this->ssl, this);
ALPNSupport::bind(this->ssl, this);
TLSSessionResumptionSupport::bind(this->ssl, this);
TLSSNISupport::bind(this->ssl, this);
TLSEarlyDataSupport::bind(this->ssl, this);
TLSTunnelSupport::bind(this->ssl, this);
TLSCertSwitchSupport::bind(this->ssl, this);
}
void
SSLNetVConnection::_unbindSSLObject()
{
SSLNetVCDetach(this->ssl);
TLSBasicSupport::unbind(this->ssl);
TLSEventSupport::unbind(this->ssl);
ALPNSupport::unbind(this->ssl);
TLSSessionResumptionSupport::unbind(this->ssl);
TLSSNISupport::unbind(this->ssl);
TLSEarlyDataSupport::unbind(this->ssl);
TLSTunnelSupport::unbind(this->ssl);
TLSCertSwitchSupport::unbind(this->ssl);
}
static void
debug_certificate_name(const char *msg, const X509_NAME *name)
{
BIO *bio;
if (name == nullptr) {
return;
}
bio = BIO_new(BIO_s_mem());
if (bio == nullptr) {
return;
}
if (X509_NAME_print_ex(bio, name, 0 /* indent */, XN_FLAG_ONELINE) > 0) {
long len;
char *ptr;
len = BIO_get_mem_data(bio, &ptr);
Dbg(dbg_ctl_ssl, "%s %.*s", msg, static_cast<int>(len), ptr);
}
BIO_free(bio);
}
int
SSLNetVConnection::_ssl_read_from_net(int64_t &ret)
{
NetState *s = &this->read;
MIOBufferAccessor &buf = s->vio.buffer;
int event = SSL_READ_ERROR_NONE;
int64_t bytes_read = 0;
ssl_error_t sslErr = SSL_ERROR_NONE;
int64_t toread = buf.writer()->write_avail();
ink_release_assert(toread > 0);
if (toread > s->vio.ntodo()) {
toread = s->vio.ntodo();
}
bytes_read = 0;
while (sslErr == SSL_ERROR_NONE && bytes_read < toread) {
int64_t nread = 0;
int64_t block_write_avail = buf.writer()->block_write_avail();
ink_release_assert(block_write_avail > 0);
int64_t amount_to_read = toread - bytes_read;
if (amount_to_read > block_write_avail) {
amount_to_read = block_write_avail;
}
Dbg(dbg_ctl_ssl, "amount_to_read=%" PRId64, amount_to_read);
char *current_block = buf.writer()->end();
ink_release_assert(current_block != nullptr);
sslErr = this->_ssl_read_buffer(current_block, amount_to_read, nread);
Dbg(dbg_ctl_ssl, "nread=%" PRId64, nread);
switch (sslErr) {
case SSL_ERROR_NONE:
#if DEBUG
{
static DbgCtl dbg_ctl{"ssl_buff"};
SSLDebugBufferPrint(dbg_ctl, current_block, nread, "SSL Read");
}
#endif
ink_assert(nread);
bytes_read += nread;
if (nread > 0) {
buf.writer()->fill(nread); // Tell the buffer, we've used the bytes
this->netActivity();
}
break;
case SSL_ERROR_WANT_WRITE:
event = SSL_WRITE_WOULD_BLOCK;
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(write)");
break;
case SSL_ERROR_WANT_READ:
event = SSL_READ_WOULD_BLOCK;
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(read)");
break;
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
event = SSL_READ_WOULD_BLOCK;
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(read/client hello cb)");
break;
#endif
case SSL_ERROR_WANT_X509_LOOKUP:
event = SSL_READ_WOULD_BLOCK;
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(read/x509 lookup)");
break;
case SSL_ERROR_SYSCALL:
if (nread != 0) {
// not EOF
Metrics::Counter::increment(ssl_rsb.error_syscall);
event = SSL_READ_ERROR;
ret = errno;
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_SYSCALL, underlying IO error: %s", strerror(errno));
} else {
// then EOF observed, treat it as EOS
event = SSL_READ_EOS;
}
break;
case SSL_ERROR_ZERO_RETURN:
event = SSL_READ_EOS;
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_ZERO_RETURN");
break;
case SSL_ERROR_SSL:
default: {
char buf[512];
unsigned long e = ERR_peek_last_error();
ERR_error_string_n(e, buf, sizeof(buf));
event = SSL_READ_ERROR;
ret = errno;
SSLVCDebug(this, "errno=%d", errno);
Metrics::Counter::increment(ssl_rsb.error_ssl);
} break;
} // switch
} // while
if (bytes_read > 0) {
Dbg(dbg_ctl_ssl, "bytes_read=%" PRId64, bytes_read);
s->vio.ndone += bytes_read;
// Decrypted application bytes, to match write_bytes (also plaintext for TLS).
Metrics::Counter::increment(net_rsb.read_bytes, bytes_read);
Metrics::Counter::increment(net_rsb.read_bytes_count);
this->netActivity();
ret = bytes_read;
// If we read it all, don't worry about the other events and just send read complete
event = (s->vio.ntodo() <= 0) ? SSL_READ_COMPLETE : SSL_READ_READY;
if (sslErr == SSL_ERROR_NONE && s->vio.ntodo() > 0) {
// We stopped with data on the wire (to avoid overbuffering). Make sure we are triggered
this->read.triggered = 1;
}
} else { // if( bytes_read > 0 )
#if defined(_DEBUG)
if (bytes_read == 0) {
Dbg(dbg_ctl_ssl, "bytes_read == 0");
}
#endif
}
return event;
}
/** Read from socket directly for handshake data. Store the data in an MIOBuffer. Place the data in
* the read BIO so the openssl library has access to it. If for some reason we must abort out of the
* handshake, the stored data can be replayed (e.g. back out to blind tunneling)
*/
int64_t
SSLNetVConnection::read_raw_data()
{
// read data
int64_t r = 0;
int64_t total_read = 0;
int64_t rattempted = 0;
char *buffer = nullptr;
int buf_len;
IOBufferBlock *b = this->handShakeBuffer->first_write_block();
rattempted = b->write_avail();
while (rattempted) {
buffer = b->_end;
buf_len = rattempted;
b = b->next.get();
r = this->con.sock.read(buffer, buf_len);
Metrics::Counter::increment(net_rsb.calls_to_read);
total_read += rattempted;
Dbg(dbg_ctl_ssl, "read_raw_data r=%" PRId64 " rattempted=%" PRId64 " total_read=%" PRId64 " fd=%d", r, rattempted, total_read,
con.sock.get_fd());
// last read failed or was incomplete
if (r != rattempted || !b) {
break;
}
rattempted = b->write_avail();
}
// If we have already moved some bytes successfully, adjust total_read to reflect reality
// If any read succeeded, we should return success
if (r != rattempted) {
// If the first read fails, we should return error
if (r <= 0 && total_read > rattempted) {
r = total_read - rattempted;
} else {
r = total_read - rattempted + r;
}
}
if (!this->haveCheckedProxyProtocol) {
// The PROXY Protocol, by spec, is designed to require only the first TCP packet of bytes
// because it is under typical MTU. So we only need to perform the following inspection on the
// first packet.
this->haveCheckedProxyProtocol = true;
swoc::IPRangeSet *pp_ipmap;
pp_ipmap = SSLConfigParams::proxy_protocol_ip_addrs;
if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() == ProxyProtocolVersion::UNDEFINED) {
Dbg(dbg_ctl_proxyprotocol, "proxy protocol is enabled on this port");
if (this->has_proxy_protocol_preface(buffer, r)) {
if (pp_ipmap->count() > 0) {
Dbg(dbg_ctl_proxyprotocol, "proxy protocol has a configured allowlist of trusted IPs - checking");
// Using get_remote_addr() will return the ip of the
// proxy source IP, not the Proxy Protocol client ip.
if (!pp_ipmap->contains(swoc::IPAddr(get_remote_addr()))) {
Dbg(dbg_ctl_proxyprotocol, "Source IP is NOT in the configured allowlist of trusted IPs - closing connection");
r = -ENOTCONN; // Need a quick close/exit here to refuse the connection!!!!!!!!!
goto proxy_protocol_bypass;
} else {
char new_host[INET6_ADDRSTRLEN];
Dbg(dbg_ctl_proxyprotocol, "Source IP [%s] is in the trusted allowlist for proxy protocol",
ats_ip_ntop(this->get_remote_addr(), new_host, sizeof(new_host)));
}
} else {
Dbg(dbg_ctl_proxyprotocol, "proxy protocol DOES NOT have a configured allowlist of trusted IPs but "
"proxy protocol is enabled on this port - processing all connections with Proxy Protocol "
"headers");
}
auto const stored_r = r;
if (this->has_proxy_protocol(buffer, &r)) {
Dbg(dbg_ctl_proxyprotocol, "ssl has proxy protocol header");
if (dbg_ctl_proxyprotocol.on()) {
sockaddr const *src_addr = this->get_proxy_protocol_src_addr();
sockaddr const *dst_addr = this->get_proxy_protocol_dst_addr();
if (src_addr != nullptr && dst_addr != nullptr) {
IpEndpoint src;
src.sa = *src_addr;
IpEndpoint dst;
dst.sa = *dst_addr;
ip_port_text_buffer src_ipb, dst_ipb;
ats_ip_nptop(&src, src_ipb, sizeof(src_ipb));
ats_ip_nptop(&dst, dst_ipb, sizeof(dst_ipb));
DbgPrint(dbg_ctl_proxyprotocol, "ssl proxy protocol v%d header parsed: src=[%s] dst=[%s]",
static_cast<int>(this->get_proxy_protocol_version()), src_ipb, dst_ipb);
} else {
DbgPrint(dbg_ctl_proxyprotocol, "ssl proxy protocol v%d header parsed without address information",
static_cast<int>(this->get_proxy_protocol_version()));
}
}
} else {
Dbg(dbg_ctl_proxyprotocol, "proxy protocol preface was present, but Proxy Protocol header could not be parsed");
r = stored_r;
}
} else {
Dbg(dbg_ctl_proxyprotocol, "proxy protocol was enabled, but Proxy Protocol header was not present");
}
}
} // end of Proxy Protocol processing
proxy_protocol_bypass:
if (r > 0) {
this->handShakeBuffer->fill(r);
auto const total_chain_size = this->handShakeReader->read_avail();
this->handShakeBioStored = total_chain_size;
char *buffer_for_bio = this->_getCoalescedHandShakeBuffer(total_chain_size);
// Sets up the buffer as a read only bio target
// Must be reset on each read
BIO *rbio = BIO_new_mem_buf(buffer_for_bio, this->handShakeBioStored);
BIO_set_mem_eof_return(rbio, -1);
SSL_set0_rbio(this->ssl, rbio);
} else {
this->handShakeBioStored = 0;
}
Dbg(dbg_ctl_ssl, "%p read r=%" PRId64 " total=%" PRId64 " bio=%d\n", this, r, total_read, this->handShakeBioStored);
// check for errors
if (r <= 0) {
if (r == -EAGAIN || r == -ENOTCONN) {
Metrics::Counter::increment(net_rsb.calls_to_read_nodata);
}
}
return r;
}
//
// Return true if we updated the rbio with another
// memory chunk (should be ready for another read right away)
//
bool
SSLNetVConnection::update_rbio(bool move_to_socket)
{
bool retval = false;
// OpenSSL 4 does not report EOF for an empty memory BIO configured to retry reads.
if (this->handShakeReader != nullptr && BIO_ctrl_pending(SSL_get_rbio(this->ssl)) == 0) {
Dbg(dbg_ctl_ssl, "Consuming handShakeBioStored=%d bytes from the handshake reader", this->handShakeBioStored);
this->handShakeReader->consume(this->handShakeBioStored);
this->handShakeBioStored = 0;
// Load up the next block if present
if (this->handShakeReader->is_read_avail_more_than(0)) {
auto const total_chain_size = this->handShakeReader->read_avail();
this->handShakeBioStored = total_chain_size;
char *buffer_for_bio = this->_getCoalescedHandShakeBuffer(total_chain_size);
Dbg(dbg_ctl_ssl, "Adding %d bytes to the ssl rbio", this->handShakeBioStored);
// Sets up the buffer as a read only bio target
// Must be reset on each read
BIO *rbio = BIO_new_mem_buf(buffer_for_bio, this->handShakeBioStored);
BIO_set_mem_eof_return(rbio, -1);
SSL_set0_rbio(this->ssl, rbio);
retval = true;
// Handshake buffer is empty but we have read something, move to the socket rbio
} else if (move_to_socket && this->handShakeHolder->is_read_avail_more_than(0)) {
Dbg(dbg_ctl_ssl, "No other bytes in the handshake reader, moving to socket rbio");
BIO *rbio = BIO_new_socket(this->get_socket(), BIO_NOCLOSE);
BIO_set_mem_eof_return(rbio, -1);
SSL_set0_rbio(this->ssl, rbio);
free_handshake_buffers();
}
}
return retval;
}
// changed by YTS Team, yamsat
void
SSLNetVConnection::net_read_io(NetHandler *nh)
{
int ret;
int64_t r = 0;
int64_t bytes = 0;
NetState *s = &this->read;
if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) {
this->super::net_read_io(nh);
return;
}
MUTEX_TRY_LOCK(lock, s->vio.mutex, nh->thread);
if (!lock.is_locked()) {
readReschedule(nh);
return;
}
// Got closed by the HttpSessionManager thread during a migration
// The closed flag should be stable once we get the s->vio.mutex in that case
// (the global session pool mutex).
if (this->closed) {
this->super::net_read_io(nh);
return;
}
// If the key renegotiation failed it's over, just signal the error and finish.
if (sslClientRenegotiationAbort == true) {
this->read.triggered = 0;
this->_readSignalError(nh, -ENET_SSL_FAILED);
Dbg(dbg_ctl_ssl, "client renegotiation setting read signal error");
return;
}
// If it is not enabled, lower its priority. This allows
// a fast connection to speed match a slower connection by
// shifting down in priority even if it could read.
if (!s->enabled || s->vio.op != VIO::READ || s->vio.is_disabled()) {
read_disable(nh, this);
return;
}
MIOBufferAccessor &buf = s->vio.buffer;
int64_t ntodo = s->vio.ntodo();
ink_assert(buf.writer());
// Continue on if we are still in the handshake
if (!getSSLHandShakeComplete()) {
int err = 0;
if (get_context() == NET_VCONNECTION_OUT) {
ret = _sslStartHandShake(SSL_EVENT_CLIENT, err);
} else {
ret = _sslStartHandShake(SSL_EVENT_SERVER, err);
}
if (ret == SSL_RESTART) {
// VC migrated into a new object
// Just give up and go home. Events should trigger on the new vc
Dbg(dbg_ctl_ssl, "Restart for allow plain");
return;
}
// If we have flipped to blind tunnel, don't read ahead. We check for a
// non-error return first, though, because if TLS has already failed with
// the CLIENT_HELLO, then there is no need to continue toward the origin
// with the blind tunnel.
if (ret != EVENT_ERROR && this->handShakeReader && this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) {
// Now in blind tunnel. Set things up to read what is in the buffer
// Must send the READ_COMPLETE here before considering
// forwarding on the handshake buffer, so the
// SSLNextProtocolTrampoline has a chance to do its
// thing before forwarding the buffers.
this->readSignalDone(VC_EVENT_READ_COMPLETE, nh);
// If the handshake isn't set yet, this means the tunnel
// decision was make in the SNI callback. We must move
// the client hello message back into the standard read.vio
// so it will get forwarded onto the origin server
if (!this->getSSLHandShakeComplete()) {
this->sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE;
// Copy over all data already read in during the SSL_accept
// (the client hello message)
NetState *s = &this->read;
MIOBufferAccessor &buf = s->vio.buffer;
int64_t r = buf.writer()->write(this->handShakeHolder);
s->vio.nbytes += r;
s->vio.ndone += r;
Dbg(dbg_ctl_ssl, "Copied %" PRId64 " TLS handshake bytes to read.vio", r);
// Clean up the handshake buffers
this->free_handshake_buffers();
if (r > 0) {
// Kick things again, so the data that was copied into the
// vio.read buffer gets processed
this->readSignalDone(VC_EVENT_READ_COMPLETE, nh);
}
}
return; // Leave if we are tunneling
}
if (ret == EVENT_ERROR) {
this->read.triggered = 0;
this->_readSignalError(nh, err);
} else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) {
if (SSLConfigParams::ssl_handshake_timeout_in > 0) {
double handshake_time = (static_cast<double>(ink_get_hrtime() - this->get_tls_handshake_begin_time()) / 1000000000);
Dbg(dbg_ctl_ssl, "ssl handshake for vc %p, took %.3f seconds, configured handshake_timer: %d", this, handshake_time,
SSLConfigParams::ssl_handshake_timeout_in);
if (handshake_time > SSLConfigParams::ssl_handshake_timeout_in) {
Dbg(dbg_ctl_ssl, "ssl handshake for vc %p, expired, release the connection", this);
read.triggered = 0;
nh->read_ready_list.remove(this);
this->_readSignalError(nh, ETIMEDOUT);
return;
}
}
// move over to the socket if we haven't already
if (this->handShakeBuffer != nullptr) {
bool const in_client_hello =
this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_CLIENT_HELLO;
// Only transfer buffers to the socket once the CLIENT_HELLO is
// finished. We need to keep our buffers updated until then in case we
// enter tunnel mode.
Dbg(dbg_ctl_ssl, "Updating our buffers, in CLIENT_HELLO: %s", in_client_hello ? "true" : "false");
read.triggered = update_rbio(!in_client_hello);
} else {
read.triggered = 0;
}
if (!read.triggered) {
nh->read_ready_list.remove(this);
}
readReschedule(nh);
} else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) {
write.triggered = 0;
nh->write_ready_list.remove(this);
writeReschedule(nh);
} else if (ret == EVENT_DONE) {
Dbg(dbg_ctl_ssl, "ssl handshake EVENT_DONE ntodo=%" PRId64, ntodo);
// If this was driven by a zero length read, signal complete when
// the handshake is complete. Otherwise set up for continuing read
// operations.
if (ntodo <= 0) {
readSignalDone(VC_EVENT_READ_COMPLETE, nh);
} else {
read.triggered = 1;
if (read.enabled) {
nh->read_ready_list.in_or_enqueue(this);
}
}
} else if (ret == SSL_WAIT_FOR_HOOK || ret == SSL_WAIT_FOR_ASYNC) {
// avoid readReschedule - done when the plugin calls us back to reenable
} else {
readReschedule(nh);
}
return;
}
// If there is nothing to do or no space available, disable connection
if (ntodo <= 0 || !buf.writer()->write_avail() || s->vio.is_disabled()) {
read_disable(nh, this);
return;
}
// At this point we are at the post-handshake SSL processing
//
// not sure if this do-while loop is really needed here, please replace
// this comment if you know
int ssl_read_errno = 0;
do {
ret = this->_ssl_read_from_net(r);
if (ret == SSL_READ_READY || ret == SSL_READ_ERROR_NONE) {
bytes += r;
}
ink_assert(bytes >= 0);
} while ((ret == SSL_READ_READY && bytes == 0) || ret == SSL_READ_ERROR_NONE);
ssl_read_errno = errno;
ATS_PROBE4(net_ssl_read, this->get_fd(), bytes, s->vio.ndone, ret);
if (bytes > 0) {
if (ret == SSL_READ_WOULD_BLOCK || ret == SSL_READ_READY) {
if (readSignalAndUpdate(VC_EVENT_READ_READY) != EVENT_CONT) {
Dbg(dbg_ctl_ssl, "readSignal != EVENT_CONT");
return;
}
}
}
switch (ret) {
case SSL_READ_READY:
readReschedule(nh);
return;
break;
case SSL_WRITE_WOULD_BLOCK:
case SSL_READ_WOULD_BLOCK:
if (lock.get_mutex() != s->vio.mutex.get()) {
Dbg(dbg_ctl_ssl, "mutex switched");
if (ret == SSL_READ_WOULD_BLOCK) {
readReschedule(nh);
} else {
writeReschedule(nh);
}
return;
}
// reset the trigger and remove from the ready queue
// we will need to be retriggered to read from this socket again
read.triggered = 0;
nh->read_ready_list.remove(this);
Dbg(dbg_ctl_ssl, "read finished - would block");
break;
case SSL_READ_EOS:
// close the connection if we have SSL_READ_EOS, this is the return value from ssl_read_from_net() if we get an
// SSL_ERROR_ZERO_RETURN from SSL_get_error()
// SSL_ERROR_ZERO_RETURN means that the origin server closed the SSL connection
read.triggered = 0;
readSignalDone(VC_EVENT_EOS, nh);
if (bytes > 0) {
Dbg(dbg_ctl_ssl, "read finished - EOS");
} else {
Dbg(dbg_ctl_ssl, "read finished - 0 useful bytes read, bytes used by SSL layer");
}
break;
case SSL_READ_COMPLETE:
readSignalDone(VC_EVENT_READ_COMPLETE, nh);
Dbg(dbg_ctl_ssl, "read finished - signal done");
break;
case SSL_READ_ERROR:
this->read.triggered = 0;
this->_readSignalError(nh, (ssl_read_errno) ? ssl_read_errno : -ENET_SSL_FAILED);
Dbg(dbg_ctl_ssl, "read finished - read error");
break;
}
}
int64_t
SSLNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs)
{
int64_t try_to_write;
int64_t num_really_written = 0;
int64_t l = 0;
uint32_t dynamic_tls_record_size = 0;
ssl_error_t err = SSL_ERROR_NONE;
// Dynamic TLS record sizing
ink_hrtime now = 0;
if (SSLConfigParams::ssl_maxrecord == -1) {
now = ink_get_hrtime();
int msec_since_last_write = ink_hrtime_diff_msec(now, sslLastWriteTime);
if (msec_since_last_write > SSL_DEF_TLS_RECORD_MSEC_THRESHOLD) {
// reset sslTotalBytesSent upon inactivity for SSL_DEF_TLS_RECORD_MSEC_THRESHOLD
sslTotalBytesSent = 0;
}
Dbg(dbg_ctl_ssl, "now=%" PRId64 " lastwrite=%" PRId64 " msec_since_last_write=%d", now, sslLastWriteTime,
msec_since_last_write);
}
if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) {
return this->super::load_buffer_and_write(towrite, buf, total_written, needs);
}
Dbg(dbg_ctl_ssl, "towrite=%" PRId64, towrite);
// SSL_write retries (WANT_WRITE/READ) must reuse the same address; thread_local gives the
// coalesce path a stable one. SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is off.
static thread_local char gather_buf[SSL_MAX_TLS_RECORD_SIZE];
// Caller bounds towrite by read_avail(); asserting lets the loop skip an O(n) chain walk.
ink_assert(towrite <= buf.reader()->read_avail());
ERR_clear_error();
do {
IOBufferReader *reader = buf.reader();
l = towrite - total_written;
// TS-2365: If the SSL max record size is set and we have
// more data than that, break this into smaller write
// operations.
//
// TS-4424: Don't mess with record size if last SSL_write failed with
// needs write
if (redoWriteSize) {
l = redoWriteSize;
redoWriteSize = 0;
} else {
if (SSLConfigParams::ssl_maxrecord > 0 && l > SSLConfigParams::ssl_maxrecord) {
l = SSLConfigParams::ssl_maxrecord;
} else if (SSLConfigParams::ssl_maxrecord == -1) {
if (sslTotalBytesSent < SSL_DEF_TLS_RECORD_BYTE_THRESHOLD) {
dynamic_tls_record_size = SSL_DEF_TLS_RECORD_SIZE;
Metrics::Counter::increment(ssl_rsb.total_dyn_def_tls_record_count);
} else {
dynamic_tls_record_size = SSL_MAX_TLS_RECORD_SIZE;
Metrics::Counter::increment(ssl_rsb.total_dyn_max_tls_record_count);
}
if (l > dynamic_tls_record_size) {
l = dynamic_tls_record_size;
}
}
}
if (!l) {
break;
}
// Coalesce across blocks only when it fits one record; else write in place, capped to the block.
const char *write_block;
int64_t block_avail = reader->block_read_avail();
if (block_avail < l && l <= static_cast<int64_t>(sizeof(gather_buf))) {
reader->memcpy(gather_buf, l, 0);
write_block = gather_buf;
} else {
if (l > block_avail) {
l = block_avail;
}
write_block = reader->start();
}
try_to_write = l;
num_really_written = 0;
Dbg(dbg_ctl_v_ssl, "b=%p l=%" PRId64, write_block, l);
err = this->_ssl_write_buffer(write_block, l, num_really_written);
// We wrote all that we thought we should
if (num_really_written > 0) {
total_written += num_really_written;
reader->consume(num_really_written);
}
Dbg(dbg_ctl_ssl, "try_to_write=%" PRId64 " written=%" PRId64 " total_written=%" PRId64, try_to_write, num_really_written,
total_written);
Metrics::Counter::increment(net_rsb.calls_to_write);
} while (num_really_written == try_to_write && total_written < towrite);
if (total_written > 0) {
sslLastWriteTime = now;
sslTotalBytesSent += total_written;
}
redoWriteSize = 0;
if (num_really_written > 0) {
needs |= EVENTIO_WRITE;
} else {
switch (err) {
case SSL_ERROR_NONE:
Dbg(dbg_ctl_ssl, "SSL_write-SSL_ERROR_NONE");
break;
case SSL_ERROR_WANT_READ:
needs |= EVENTIO_READ;
num_really_written = -EAGAIN;
Dbg(dbg_ctl_ssl_error, "SSL_write-SSL_ERROR_WANT_READ");
break;
case SSL_ERROR_WANT_WRITE:
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
#endif
case SSL_ERROR_WANT_X509_LOOKUP: {
if (SSL_ERROR_WANT_WRITE == err) {
redoWriteSize = l;
}
needs |= EVENTIO_WRITE;
num_really_written = -EAGAIN;
Dbg(dbg_ctl_ssl_error, "SSL_write-SSL_ERROR_WANT_WRITE");
break;
}
case SSL_ERROR_SYSCALL:
// SSL_ERROR_SYSCALL is an IO error. errno is likely 0, so set EPIPE, as
// we do with SSL_ERROR_SSL below, to indicate a connection error.
num_really_written = -EPIPE;
Metrics::Counter::increment(ssl_rsb.error_syscall);
Dbg(dbg_ctl_ssl_error, "SSL_write-SSL_ERROR_SYSCALL");
break;
// end of stream
case SSL_ERROR_ZERO_RETURN:
num_really_written = -errno;
Dbg(dbg_ctl_ssl_error, "SSL_write-SSL_ERROR_ZERO_RETURN");
break;
case SSL_ERROR_SSL:
default: {
// Treat SSL_ERROR_SSL as EPIPE error.
num_really_written = -EPIPE;
SSLVCDebug(this, "SSL_write-SSL_ERROR_SSL errno=%d", errno);
Metrics::Counter::increment(ssl_rsb.error_ssl);
} break;
}
}
ATS_PROBE5(net_ssl_write, this->get_fd(), total_written, write.vio.ndone + total_written, num_really_written,
static_cast<int>(err));
return num_really_written;
}
SSLNetVConnection::SSLNetVConnection()
{
this->_set_service(static_cast<ALPNSupport *>(this));
this->_set_service(static_cast<TLSBasicSupport *>(this));
this->_set_service(static_cast<TLSEventSupport *>(this));
this->_set_service(static_cast<TLSCertSwitchSupport *>(this));
this->_set_service(static_cast<TLSEarlyDataSupport *>(this));
this->_set_service(static_cast<TLSSNISupport *>(this));
this->_set_service(static_cast<TLSSessionResumptionSupport *>(this));
this->_set_service(static_cast<TLSTunnelSupport *>(this));
}
void
SSLNetVConnection::do_io_close(int lerrno)
{
// Stop any async-handshake eventfd before the VC is closed.
// If WANT_ASYNC was registered the eventfd is wired into the poller with
// `this` as the EventIO target. Without this stop() the SSLNetVConnection
// can be freed while the eventfd still has a live epoll registration; when
// the OpenSSL async job completes the poller wakes on freed memory.
if (async_ep.fd >= 0) {
async_ep.stop();
}
if (this->ssl != nullptr) {
if (get_context() == NET_VCONNECTION_OUT) {
callHooks(TS_EVENT_VCONN_OUTBOUND_CLOSE);
} else {
callHooks(TS_EVENT_VCONN_CLOSE);
}
if (getSSLHandShakeComplete()) {
int shutdown_mode = SSL_get_shutdown(ssl);
Dbg(dbg_ctl_ssl_shutdown, "previous shutdown state 0x%x", shutdown_mode);
int new_shutdown_mode = shutdown_mode | SSL_RECEIVED_SHUTDOWN;
if (new_shutdown_mode != shutdown_mode) {
// We do not need to sit around and wait for the client's close-notify if
// they have not already sent it. We will still be standards compliant
Dbg(dbg_ctl_ssl_shutdown, "new SSL_set_shutdown 0x%x", new_shutdown_mode);
SSL_set_shutdown(ssl, new_shutdown_mode);
}
// If the peer has already sent a FIN, don't bother with the shutdown
// They will just send us a RST for our troubles
// This test is not foolproof. The client's fin could be on the wire
// at the same time we send the close-notify. If so, the client will likely
// send RST anyway
char c;
ssize_t x = this->con.sock.recv(&c, 1, MSG_PEEK);
// x < 0 means error. x == 0 means fin sent
bool do_shutdown = (x > 0);
if (x < 0) {
do_shutdown = (errno == EAGAIN || errno == EWOULDBLOCK);
}
if (do_shutdown) {
// Send the close-notify
int ret = SSL_shutdown(ssl);
Dbg(dbg_ctl_ssl_shutdown, "SSL_shutdown %s", (ret) ? "success" : "failed");
} else {
// Request a quiet shutdown to OpenSSL
SSL_set_quiet_shutdown(ssl, 1);
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN);
Dbg(dbg_ctl_ssl_shutdown, "Enable quiet shutdown");
}
}
}
// Go on and do the unix socket cleanups
super::do_io_close(lerrno);
}
void
SSLNetVConnection::do_io_shutdown(ShutdownHowTo_t howto)
{
if (ssl == nullptr || get_tunnel_type() == SNIRoutingType::BLIND) {
// we don't have TLS layer control of blind tunnel
UnixNetVConnection::do_io_shutdown(howto);
return;
}
switch (howto) {
case IO_SHUTDOWN_READ:
// No need to call SSL API
// SSL_shutdown() sends the close_notify alert to the peer and it only closes the write direction.
// The read direction will be closed by the peer.
read.enabled = 0;
read.vio.buffer.clear();
read.vio.nbytes = 0;
read.vio.cont = nullptr;
f.shutdown |= NetEvent::SHUTDOWN_READ;
break;
case IO_SHUTDOWN_WRITE:
SSL_shutdown(ssl);
write.enabled = 0;
write.vio.buffer.clear();
write.vio.nbytes = 0;
write.vio.cont = nullptr;
f.shutdown |= NetEvent::SHUTDOWN_WRITE;
break;
case IO_SHUTDOWN_READWRITE:
SSL_shutdown(ssl);
read.enabled = 0;
write.enabled = 0;
read.vio.buffer.clear();
read.vio.nbytes = 0;
write.vio.buffer.clear();
write.vio.nbytes = 0;
read.vio.cont = nullptr;
write.vio.cont = nullptr;
f.shutdown = NetEvent::SHUTDOWN_READ | NetEvent::SHUTDOWN_WRITE;
break;
default:
ink_assert(!"not reached");
}
}
void
SSLNetVConnection::clear()
{
_ca_cert_file.reset();
_ca_cert_dir.reset();
// SSL_SESSION_free() must only be called for SSL_SESSION objects,
// for which the reference count was explicitly incremented (e.g.
// by calling SSL_get1_session(), see SSL_get_session(3)) or when
// the SSL_SESSION object was generated outside a TLS handshake
// operation, e.g. by using d2i_SSL_SESSION(3). It must not be called
// on other SSL_SESSION objects, as this would cause incorrect
// reference counts and therefore program failures.
// Since we created the shared pointer with a custom deleter,
// resetting here will decrement the ref-counter.
client_sess.reset();
// Stop the async-handshake eventfd before SSL_free. The
// eventfd is owned by the SSL object, so SSL_free closes it; deregistering
// first keeps the EPOLL_CTL_DEL operating on a valid, owned fd. clear() runs
// on every free path through free_thread(), so it is the backstop in case
// do_io_close() did not already stop the eventfd.
if (async_ep.fd >= 0) {
async_ep.stop();
}
if (ssl != nullptr) {
// clear() runs from free() once per VC recycle, so this is the single chokepoint where a TLS
// connection's SSL object is torn down -- count it as one connection close here. Blind-tunnel
// conversions free their SSL earlier (before any data) and continue as a tunnel rather than a
// close, so by here ssl is already null for them and they are not counted (they are tracked by
// the tunnel metrics instead).
Metrics::Counter::increment(ssl_rsb.connections_closed);
SSL_free(ssl);
ssl = nullptr;
}
#if TS_USE_QMUX
// The destructor never runs (ClassAllocator<SSLNetVConnection, false>), so the
// QMux connection has to be released here or it leaks on every VC recycle.
// Clear the QUICSupport service slot too, or a recycled non-QMux VC would
// still report a (now null) QUIC connection to get_service<QUICSupport>().
_qmux_connection.reset();
this->_set_service(static_cast<QUICSupport *>(nullptr));
#endif
ALPNSupport::clear();
TLSBasicSupport::clear();
TLSEventSupport::clear();
TLSSessionResumptionSupport::clear();
TLSSNISupport::_clear();
TLSTunnelSupport::_clear();
TLSCertSwitchSupport::_clear();
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ONGOING;
sslLastWriteTime = 0;
sslTotalBytesSent = 0;
sslClientRenegotiationAbort = false;
hookOpRequested = SslVConnOp::SSL_HOOK_OP_DEFAULT;
free_handshake_buffers();
super::clear();
}
void
SSLNetVConnection::free_thread(EThread *t)
{
ink_release_assert(t == this_ethread());
// close socket fd
if (con.sock.is_ok()) {
release_inbound_connection_tracking();
Metrics::Gauge::decrement(net_rsb.connections_currently_open);
}
con.close();
if (is_tunnel_endpoint()) {
ink_assert(get_context() != NET_VCONNECTION_UNSET);
Metrics::Gauge::decrement(([&]() -> Metrics::Gauge::AtomicType * {
if (get_context() == NET_VCONNECTION_IN) {
switch (get_tunnel_type()) {
case SNIRoutingType::BLIND:
return net_rsb.tunnel_current_client_connections_tls_tunnel;
case SNIRoutingType::FORWARD:
return net_rsb.tunnel_current_client_connections_tls_forward;
case SNIRoutingType::PARTIAL_BLIND:
return net_rsb.tunnel_current_client_connections_tls_partial_blind;
default:
return net_rsb.tunnel_current_client_connections_tls_http;
}
}
// NET_VCONNECTION_OUT - Never a tunnel type for out (to server) context.
ink_assert(get_tunnel_type() == SNIRoutingType::NONE);
return net_rsb.tunnel_current_server_connections_tls;
})());
}
#if TS_HAS_TLS_EARLY_DATA
if (_early_data_reader != nullptr) {
_early_data_reader->dealloc();
}
if (_early_data_buf != nullptr) {
free_MIOBuffer(_early_data_buf);
}
_early_data_reader = nullptr;
_early_data_buf = nullptr;
#endif
clear();
SET_CONTINUATION_HANDLER(this, &SSLNetVConnection::startEvent);
ink_assert(!con.sock.is_ok());
ink_assert(t == this_ethread());
if (from_accept_thread) {
sslNetVCAllocator.free(this);
} else {
ink_assert(!con.sock.is_ok());
THREAD_FREE(this, sslNetVCAllocator, t);
}
}
int
SSLNetVConnection::_sslStartHandShake(int event, int &err)
{
if (TSSystemState::is_ssl_handshaking_stopped()) {
Dbg(dbg_ctl_ssl, "Stopping handshake due to server shutting down.");
return EVENT_ERROR;
}
if (this->get_tls_handshake_begin_time() == 0) {
this->_record_tls_handshake_begin_time();
// net_activity will not be triggered until after the handshake
set_inactivity_timeout(HRTIME_SECONDS(SSLConfigParams::ssl_handshake_timeout_in));
}
SSLConfig::scoped_config params;
switch (event) {
case SSL_EVENT_SERVER:
if (this->ssl == nullptr) {
SSLCertificateConfig::scoped_config lookup;
IpEndpoint dst;
int namelen = sizeof(dst);
if (0 != safe_getsockname(this->get_socket(), &dst.sa, &namelen)) {
Dbg(dbg_ctl_ssl, "Failed to get dest ip, errno = [%d]", errno);
return EVENT_ERROR;
}
SSLCertContext *cc = lookup->find(dst);
if (dbg_ctl_ssl.on()) {
IpEndpoint src;
ip_port_text_buffer ipb1, ipb2;
int ip_len = sizeof(src);
if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) {
DbgPrint(dbg_ctl_ssl, "Failed to get src ip, errno = [%d]", errno);
return EVENT_ERROR;
}
ats_ip_nptop(&dst, ipb1, sizeof(ipb1));
ats_ip_nptop(&src, ipb2, sizeof(ipb2));
auto default_ctx = lookup->defaultContext();
DbgPrint(dbg_ctl_ssl, "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, default_ctx.get());
}
// Escape if this is marked to be a tunnel.
// No data has been read at this point, so we can go
// directly into blind tunnel mode
if (cc && SSLCertContextOption::OPT_TUNNEL == cc->opt) {
if (this->is_transparent) {
this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE;
SSL_free(this->ssl);
this->ssl = nullptr;
return EVENT_DONE;
} else {
hookOpRequested = SslVConnOp::SSL_HOOK_OP_TUNNEL;
}
}
// Attach the default SSL_CTX to this SSL session. The default context is never going to be able
// to negotiate a SSL session, but it's enough to trampoline us into the SNI callback where we
// can select the right server certificate.
this->_make_ssl_connection(lookup->defaultContext().get());
}
if (this->ssl == nullptr) {
SSLErrorVC(this, "failed to create SSL server session");
return EVENT_ERROR;
}
return sslServerHandShakeEvent(err);
case SSL_EVENT_CLIENT:
char buff[INET6_ADDRSTRLEN];
if (this->ssl == nullptr) {
// Making the check here instead of later, so we only
// do this setting immediately after we create the SSL object
SNIConfig::scoped_config sniParam;
const char *serverKey = this->options.sni_servername;
if (!serverKey) {
ats_ip_ntop(this->get_remote_addr(), buff, INET6_ADDRSTRLEN);
serverKey = buff;
}
auto nps = sniParam->get_property_config(serverKey);
shared_SSL_CTX sharedCTX = nullptr;
SSL_CTX *clientCTX = nullptr;
std::string caCertPathStorage;
const char *caCertPath = resolve_client_ca_cert_path(params, options.ssl_client_ca_cert_path, caCertPathStorage);
// First Look to see if there are override parameters
Dbg(dbg_ctl_ssl, "Checking for outbound client cert override [%p]", options.ssl_client_cert_name.get());
if (options.ssl_client_cert_name) {
std::string certFilePath;
std::string keyFilePath;
std::string caCertFilePath;
// Enable override to explicitly disable the client certificate. That is, don't fill
// in any of the cert paths if the cert file name is empty or "NULL".
if (*options.ssl_client_cert_name != '\0' && 0 != strcasecmp("NULL", options.ssl_client_cert_name)) {
certFilePath = Layout::get()->relative_to(params->clientCertPathOnly, options.ssl_client_cert_name.get());
if (options.ssl_client_private_key_name) {
keyFilePath = Layout::get()->relative_to(params->clientKeyPathOnly, options.ssl_client_private_key_name);
}
if (options.ssl_client_ca_cert_name) {
caCertFilePath = Layout::get()->relative_to(caCertPath, options.ssl_client_ca_cert_name);
}
Dbg(dbg_ctl_ssl, "Using outbound client cert `%s'", options.ssl_client_cert_name.get());
} else {
Dbg(dbg_ctl_ssl, "Clearing outbound client cert");
}
sharedCTX = params->getCTX(certFilePath, keyFilePath,
caCertFilePath.empty() ? params->clientCACertFilename : caCertFilePath.c_str(), caCertPath);
} else if (options.ssl_client_ca_cert_name || options.ssl_client_ca_cert_path) {
std::string caCertFilePath;
if (options.ssl_client_ca_cert_name) {
caCertFilePath = Layout::get()->relative_to(caCertPath, options.ssl_client_ca_cert_name);
}
sharedCTX = params->getCTX(params->clientCertPath, params->clientKeyPath,
caCertFilePath.empty() ? params->clientCACertFilename : caCertFilePath.c_str(), caCertPath);
} else if (nps && !nps->client_cert_file.empty()) {
// If no overrides available, try the available nextHopProperty by reading from context mappings
sharedCTX =
params->getCTX(nps->client_cert_file, nps->client_key_file, params->clientCACertFilename, params->clientCACertPath);
} else { // Just stay with the values passed down from the SM for verify
clientCTX = params->client_ctx.get();
}
if (sharedCTX) {
clientCTX = sharedCTX.get();
}
if (options.verifyServerPolicy != YamlSNIConfig::Policy::UNSET) {
// Stay with conf-override version as the highest priority
} else if (nps && nps->verify_server_policy != YamlSNIConfig::Policy::UNSET) {
options.verifyServerPolicy = nps->verify_server_policy;
} else {
options.verifyServerPolicy = params->verifyServerPolicy;
}
if (options.verifyServerProperties != YamlSNIConfig::Property::UNSET) {
// Stay with conf-override version as the highest priority
} else if (nps && nps->verify_server_properties != YamlSNIConfig::Property::UNSET) {
options.verifyServerProperties = nps->verify_server_properties;
} else {
options.verifyServerProperties = params->verifyServerProperties;
}
if (!clientCTX) {
SSLErrorVC(this, "failed to create SSL client session");
return EVENT_ERROR;
}
this->_make_ssl_connection(clientCTX);
if (this->ssl == nullptr) {
SSLErrorVC(this, "failed to create SSL client session");
return EVENT_ERROR;
}
// If it is negative, we are consciously not setting ALPN (e.g. for private server sessions)
if (options.alpn_protocols_array_size >= 0) {
if (options.alpn_protocols_array_size > 0) {
SSL_set_alpn_protos(this->ssl, options.alpn_protocols_array, options.alpn_protocols_array_size);
} else if (params->alpn_protocols_array_size > 0) {
// Set the ALPN protocols we are requesting.
SSL_set_alpn_protos(this->ssl, params->alpn_protocols_array, params->alpn_protocols_array_size);
}
}
SSL_set_verify(this->ssl, SSL_VERIFY_PEER, verify_callback);
// SNI
ats_scoped_str &tlsext_host_name = this->options.sni_hostname ? this->options.sni_hostname : this->options.sni_servername;
if (tlsext_host_name) {
if (this->set_sni_server_name(this->ssl, tlsext_host_name)) {
Dbg(dbg_ctl_ssl, "using SNI name '%s' for client handshake", tlsext_host_name.get());
} else {
Dbg(dbg_ctl_ssl_error, "failed to set SNI name '%s' for client handshake", tlsext_host_name.get());
Metrics::Counter::increment(ssl_rsb.sni_name_set_failure);
}
}
// ALPN
if (!this->options.alpn_protos.empty()) {
if (int res = SSL_set_alpn_protos(this->ssl, reinterpret_cast<const uint8_t *>(this->options.alpn_protos.data()),
this->options.alpn_protos.size());
res != 0) {
Dbg(dbg_ctl_ssl_error, "failed to set ALPN '%.*s' for client handshake",
static_cast<int>(this->options.alpn_protos.size()), this->options.alpn_protos.data());
}
}
}
return sslClientHandShakeEvent(err);
default:
ink_assert(0);
return EVENT_ERROR;
}
}
int
SSLNetVConnection::sslServerHandShakeEvent(int &err)
{
// Continue on if we are in the invoked state. The hook has not yet reenabled
if (this->is_invoked_state()) {
return SSL_WAIT_FOR_HOOK;
}
// Go do the preaccept hooks
if (this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_PRE) {
if (this->invoke_tls_event() == 1) {
return SSL_WAIT_FOR_HOOK;
}
}
// If a blind tunnel was requested in the pre-accept calls, convert.
// Again no data has been exchanged, so we can go directly
// without data replay.
// Note we can't arrive here if a hook is active.
if (SslVConnOp::SSL_HOOK_OP_TUNNEL == hookOpRequested) {
this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
SSL_free(this->ssl);
this->ssl = nullptr;
// Don't mark the handshake as complete yet,
// Will be checking for that flag not being set after
// we get out of this callback, and then will shuffle
// over the buffered handshake packets to the O.S.
return EVENT_DONE;
}
Dbg(dbg_ctl_ssl, "Go on with the handshake state=%s",
TLSEventSupport::get_ssl_handshake_hook_state_name(this->get_handshake_hook_state()));
// All the pre-accept hooks have completed, proceed with the actual accept.
bool const in_client_hello =
this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_CLIENT_HELLO;
// We only feed CLIENT_HELLO bytes into our temporary buffers. If we are past
// the CLIENT_HELLO, then no need to buffer.
if (in_client_hello && this->handShakeReader) {
if (BIO_ctrl_pending(SSL_get_rbio(this->ssl)) == 0) { // No more data in the buffer
// Is this the first read?
#if TS_USE_TLS_ASYNC
if (SSLConfigParams::async_handshake_enabled) {
SSL_set_mode(ssl, SSL_MODE_ASYNC);
}
#endif
Dbg(dbg_ctl_ssl, "%p reading off the socket into our buffers", this);
// Read from socket to fill in the BIO buffer with the
// raw handshake data before calling the ssl accept calls.
int retval = this->read_raw_data();
if (retval < 0) {
if (retval == -EAGAIN) {
// No data at the moment, hang tight
SSLVCDebug(this, "SSL handshake: EAGAIN");
return SSL_HANDSHAKE_WANT_READ;
} else {
// An error, make us go away
SSLVCDebug(this, "SSL handshake error: read_retval=%d", retval);
return EVENT_ERROR;
}
} else if (retval == 0) {
// EOF, go away, we stopped in the handshake
SSLVCDebug(this, "SSL handshake error: EOF");
return EVENT_ERROR;
}
} // Still data in the BIO. Let OpenSSL consume that first before doing anything else.
}
ssl_error_t ssl_error = this->_ssl_accept();
#if TS_USE_TLS_ASYNC
if (ssl_error == SSL_ERROR_WANT_ASYNC) {
// Do we need to set up the async eventfd? Or is it already registered?
if (async_ep.fd < 0) {
size_t numfds;
OSSL_ASYNC_FD *waitfds;
// Set up the epoll entry for the signalling
if (SSL_get_all_async_fds(ssl, nullptr, &numfds) && numfds > 0) {
// Allocate space for the waitfd on the stack, should only be one most all of the time
waitfds = reinterpret_cast<OSSL_ASYNC_FD *>(alloca(sizeof(OSSL_ASYNC_FD) * numfds));
if (SSL_get_all_async_fds(ssl, waitfds, &numfds) && numfds > 0) {
this->read.triggered = false;
this->write.triggered = false;
// Have to have the read NetState enabled because we are using it for the signal vc
read.enabled = true;
PollDescriptor *pd = get_PollDescriptor(this_ethread());
this->async_ep.start(pd, waitfds[0], static_cast<NetEvent *>(this), get_NetHandler(this->thread), EVENTIO_READ);
}
}
}
} else if (SSLConfigParams::async_handshake_enabled) {
// Make sure the net fd read vio is in the right state
if (ssl_error == SSL_ERROR_WANT_READ) {
this->reenable(&read.vio);
this->read.triggered = 1;
}
}
#endif
if (ssl_error != SSL_ERROR_NONE) {
err = errno;
SSLVCDebug(this, "SSL handshake error: %s (%d), errno=%d", SSLErrorName(ssl_error), ssl_error, err);
char *buf = handShakeBuffer ? handShakeBuffer->buf() : nullptr;
if (buf && *buf != SSL_OP_HANDSHAKE) {
SSLVCDebug(this, "SSL hanshake error with bad HS buffer");
if (getAllowPlain()) {
SSLVCDebug(this, "Try plain");
// If this doesn't look like a ClientHello, convert this connection to a UnixNetVC and send the
// packet for Http Processing
this->_migrateFromSSL();
return SSL_RESTART;
} else if (getTransparentPassThrough()) {
// start a blind tunnel if tr-pass is set and data does not look like ClientHello
SSLVCDebug(this, "Data does not look like SSL handshake, starting blind tunnel");
this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ONGOING;
return EVENT_CONT;
} else {
SSLVCDebug(this, "Give up");
}
}
}
switch (ssl_error) {
case SSL_ERROR_NONE:
if (dbg_ctl_ssl.on()) {
#ifdef OPENSSL_IS_AT_LEAST_OPENSSL3
X509 *cert = SSL_get1_peer_certificate(ssl);
#else
X509 *cert = SSL_get_peer_certificate(ssl);
#endif
DbgPrint(dbg_ctl_ssl, "SSL server handshake completed successfully");
if (cert) {
debug_certificate_name("client certificate subject CN is", X509_get_subject_name(cert));
debug_certificate_name("client certificate issuer CN is", X509_get_issuer_name(cert));
X509_free(cert);
}
}
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE;
if (this->get_tls_handshake_begin_time()) {
this->_record_tls_handshake_end_time();
this->_update_end_of_handshake_stats();
}
if (this->get_tunnel_type() != SNIRoutingType::NONE) {
// Foce to use HTTP/1.1 endpoint for SNI Routing
if (!this->setSelectedProtocol(reinterpret_cast<const unsigned char *>(IP_PROTO_TAG_HTTP_1_1.data()),
IP_PROTO_TAG_HTTP_1_1.size())) {
return EVENT_ERROR;
}
}
{
const unsigned char *proto = nullptr;
unsigned len = 0;
increment_ssl_version_metric(SSL_version(ssl));
// If it's possible to negotiate both NPN and ALPN, then ALPN
// is preferred since it is the server's preference. The server
// preference would not be meaningful if we let the client
// preference have priority.
SSL_get0_alpn_selected(ssl, &proto, &len);
if (len == 0) {
SSL_get0_next_proto_negotiated(ssl, &proto, &len);
}
if (len) {
if (this->get_tunnel_type() == SNIRoutingType::NONE && !this->setSelectedProtocol(proto, len)) {
return EVENT_ERROR;
}
this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto), static_cast<size_t>(len)});
Dbg(dbg_ctl_ssl, "Origin selected next protocol '%.*s'", len, proto);
#if TS_USE_QMUX
if (this->get_negotiated_protocol_id() == TS_ALPN_PROTOCOL_INDEX_H3QX) {
Dbg(dbg_ctl_ssl, "ALPN h3qx-01: creating QMuxConnection");
_qmux_connection = std::make_unique<QMuxConnection>(this);
if (_qmux_connection->is_closed()) {
// Config or quiche_accept() failed. Don't advertise a QUIC connection that can
// never make progress -- fail the connection instead of completing the handshake.
_qmux_connection.reset();
return EVENT_ERROR;
}
this->_set_service(static_cast<QUICSupport *>(this));
}
#endif
} else {
Dbg(dbg_ctl_ssl, "Origin did not select a next protocol");
}
}
#if TS_USE_TLS_ASYNC
if (SSLConfigParams::async_handshake_enabled) {
SSL_clear_mode(ssl, SSL_MODE_ASYNC);
if (async_ep.fd >= 0) {
async_ep.stop();
}
}
#endif
return EVENT_DONE;
case SSL_ERROR_WANT_CONNECT:
return SSL_HANDSHAKE_WANT_CONNECT;
case SSL_ERROR_WANT_WRITE:
return SSL_HANDSHAKE_WANT_WRITE;
case SSL_ERROR_WANT_READ:
return SSL_HANDSHAKE_WANT_READ;
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
return EVENT_CONT;
#endif
// This value is only defined in openssl has been patched to
// enable the sni callback to break out of the SSL_accept processing
#ifdef SSL_ERROR_WANT_SNI_RESOLVE
case SSL_ERROR_WANT_X509_LOOKUP:
return EVENT_CONT;
case SSL_ERROR_WANT_SNI_RESOLVE:
#elif SSL_ERROR_WANT_X509_LOOKUP
case SSL_ERROR_WANT_X509_LOOKUP:
#endif
#ifdef SSL_ERROR_PENDING_CERTIFICATE
case SSL_ERROR_PENDING_CERTIFICATE:
#endif
#if defined(SSL_ERROR_WANT_SNI_RESOLVE) || defined(SSL_ERROR_WANT_X509_LOOKUP) || defined(SSL_ERROR_PENDING_CERTIFICATE)
if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL || SslVConnOp::SSL_HOOK_OP_TUNNEL == hookOpRequested) {
this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ONGOING;
return EVENT_CONT;
} else {
// Stopping for some other reason, perhaps loading certificate
return SSL_WAIT_FOR_HOOK;
}
#endif
#if TS_USE_TLS_ASYNC
case SSL_ERROR_WANT_ASYNC:
Metrics::Counter::increment(ssl_rsb.error_async);
return SSL_WAIT_FOR_ASYNC;
#endif
case SSL_ERROR_WANT_ACCEPT:
return EVENT_CONT;
case SSL_ERROR_SSL: {
SSLVCDebug(this, "SSLNetVConnection::sslServerHandShakeEvent, SSL_ERROR_SSL errno=%d", errno);
return EVENT_ERROR;
}
case SSL_ERROR_ZERO_RETURN:
return EVENT_ERROR;
case SSL_ERROR_SYSCALL:
return EVENT_ERROR;
default:
return EVENT_ERROR;
}
}
int
SSLNetVConnection::sslClientHandShakeEvent(int &err)
{
ssl_error_t ssl_error;
ink_assert(TLSBasicSupport::getInstance(ssl) == this);
// Initialize properly for a client connection
if (this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_PRE) {
if (this->pp_info.version != ProxyProtocolVersion::UNDEFINED) {
// Outbound PROXY Protocol
VIO &vio = this->write.vio;
int64_t ntodo = vio.ntodo();
int64_t towrite = vio.get_reader()->read_avail();
if (ntodo > 0 && towrite > 0) {
MIOBufferAccessor &buf = vio.buffer;
int needs = 0;
int64_t total_written = 0;
int64_t r = super::load_buffer_and_write(towrite, buf, total_written, needs);
if (total_written > 0) {
vio.ndone += total_written;
if (vio.ntodo() != 0) {
return SSL_WAIT_FOR_HOOK;
}
}
if (r < 0) {
if (r == -EAGAIN || r == -ENOTCONN || -r == EINPROGRESS) {
return SSL_WAIT_FOR_HOOK;
} else {
return EVENT_ERROR;
}
}
}
}
this->set_handshake_hook_state(TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_OUTBOUND_PRE);
}
// Do outbound hook processing here
// Continue on if we are in the invoked state. The hook has not yet reenabled
if (this->is_invoked_state()) {
return SSL_WAIT_FOR_HOOK;
}
// Go do the preaccept hooks
if (this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_OUTBOUND_PRE) {
if (this->invoke_tls_event() == 1) {
return SSL_WAIT_FOR_HOOK;
}
}
ssl_error = this->_ssl_connect();
switch (ssl_error) {
case SSL_ERROR_NONE:
if (dbg_ctl_ssl.on()) {
#ifdef OPENSSL_IS_AT_LEAST_OPENSSL3
X509 *cert = SSL_get1_peer_certificate(ssl);
#else
X509 *cert = SSL_get_peer_certificate(ssl);
#endif
DbgPrint(dbg_ctl_ssl, "SSL client handshake completed successfully");
if (cert) {
debug_certificate_name("server certificate subject CN is", X509_get_subject_name(cert));
debug_certificate_name("server certificate issuer CN is", X509_get_issuer_name(cert));
X509_free(cert);
}
}
{
unsigned char const *proto = nullptr;
unsigned int len = 0;
// Make note of the negotiated protocol
SSL_get0_alpn_selected(ssl, &proto, &len);
if (len == 0) {
SSL_get0_next_proto_negotiated(ssl, &proto, &len);
}
Dbg(dbg_ctl_ssl_alpn, "Negotiated ALPN: %.*s", len, proto);
this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto), static_cast<size_t>(len)});
}
// if the handshake is complete and write is enabled reschedule the write
if (closed == 0 && write.enabled) {
writeReschedule(nh);
}
Metrics::Counter::increment(ssl_rsb.total_success_handshake_count_out);
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE;
// Record TLS handshake end time for outbound (origin) connections.
// Despite the name, sslClientHandShakeEvent() handles the *outbound* side
// where ATS acts as the TLS client connecting to the origin server.
// (The inbound/client-facing side is handled by sslServerHandShakeEvent(),
// where ATS acts as the TLS server.)
// The begin time is set when the handshake starts; we record the end time
// here so HttpSM can later copy both into milestones for logging.
if (this->get_tls_handshake_begin_time()) {
this->_record_tls_handshake_end_time();
}
return EVENT_DONE;
case SSL_ERROR_WANT_WRITE:
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WANT_WRITE");
return SSL_HANDSHAKE_WANT_WRITE;
case SSL_ERROR_WANT_READ:
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WANT_READ");
return SSL_HANDSHAKE_WANT_READ;
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WANT_CLIENT_HELLO_CB");
break;
#endif
case SSL_ERROR_WANT_X509_LOOKUP:
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WANT_X509_LOOKUP");
break;
case SSL_ERROR_WANT_ACCEPT:
return SSL_HANDSHAKE_WANT_ACCEPT;
case SSL_ERROR_WANT_CONNECT:
break;
case SSL_ERROR_ZERO_RETURN:
Dbg(dbg_ctl_ssl_error, "EOS");
return EVENT_ERROR;
case SSL_ERROR_SYSCALL:
err = errno;
Metrics::Counter::increment(ssl_rsb.error_syscall);
Dbg(dbg_ctl_ssl_error, "syscall");
return EVENT_ERROR;
break;
case SSL_ERROR_SSL:
default: {
err = (errno) ? errno : -ENET_SSL_CONNECT_FAILED;
char buf[512];
unsigned long e = ERR_peek_last_error();
ERR_error_string_n(e, buf, sizeof(buf));
// FIXME -- This triggers a retry on cases of cert validation errors...
SSLVCDebug(this, "SSL_ERROR_SSL errno=%d", errno);
Metrics::Counter::increment(ssl_rsb.error_ssl);
Dbg(dbg_ctl_ssl_error, "SSL_ERROR_SSL");
if (e) {
if (this->options.sni_servername) {
Dbg(dbg_ctl_ssl_error, "SSL connection failed for '%s': %s", this->options.sni_servername.get(), buf);
} else {
char buff[INET6_ADDRSTRLEN];
ats_ip_ntop(this->get_remote_addr(), buff, INET6_ADDRSTRLEN);
Dbg(dbg_ctl_ssl_error, "SSL connection failed for '%s': %s", buff, buf);
}
}
return EVENT_ERROR;
} break;
}
return EVENT_CONT;
}
void
SSLNetVConnection::reenable(int event)
{
Dbg(dbg_ctl_ssl, "Handshake reenable from state=%s",
TLSEventSupport::get_ssl_handshake_hook_state_name(this->get_handshake_hook_state()));
// Mark as error to stop the Handshake
if (event == TS_EVENT_ERROR) {
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ERROR;
}
this->resume_tls_event();
// Reenabling from the handshake callback
//
// Originally, we would wait for the callback to go again to execute additional
// hooks, but since the callbacks are associated with the context and the context
// can be replaced by the plugin, it didn't seem reasonable to assume that the
// callback would be executed again. So we walk through the rest of the hooks
// here in the reenable.
if (this->invoke_tls_event() == 2) {
this->write.triggered = true;
this->write.enabled = true;
this->writeReschedule(nh);
}
this->readReschedule(nh);
}
Continuation *
SSLNetVConnection::getContinuationForTLSEvents()
{
return this;
}
EThread *
SSLNetVConnection::getThreadForTLSEvents()
{
return this->thread;
}
Ptr<ProxyMutex>
SSLNetVConnection::getMutexForTLSEvents()
{
return this->nh->mutex;
}
int
SSLNetVConnection::populate(Connection &con, Continuation *c, void *arg)
{
int retval = super::populate(con, c, arg);
if (retval != EVENT_DONE) {
// arg is the migrated SSL this VC would adopt below; release it since we won't.
if (arg != nullptr) {
SSL_free(static_cast<SSL *>(arg));
}
return retval;
}
// Add in the SSL data
this->ssl = static_cast<SSL *>(arg);
// Maybe bring over the stats?
sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE;
this->_bindSSLObject();
return EVENT_DONE;
}
void
SSLNetVConnection::_in_context_tunnel()
{
ink_assert(get_context() == NET_VCONNECTION_IN);
Metrics::Counter::AtomicType *t;
Metrics::Gauge::AtomicType *c;
switch (get_tunnel_type()) {
case SNIRoutingType::BLIND:
t = net_rsb.tunnel_total_client_connections_tls_tunnel;
c = net_rsb.tunnel_current_client_connections_tls_tunnel;
break;
case SNIRoutingType::FORWARD:
t = net_rsb.tunnel_total_client_connections_tls_forward;
c = net_rsb.tunnel_current_client_connections_tls_forward;
break;
case SNIRoutingType::PARTIAL_BLIND:
t = net_rsb.tunnel_total_client_connections_tls_partial_blind;
c = net_rsb.tunnel_current_client_connections_tls_partial_blind;
break;
default:
t = net_rsb.tunnel_total_client_connections_tls_http;
c = net_rsb.tunnel_current_client_connections_tls_http;
break;
}
Metrics::Counter::increment(t);
Metrics::Gauge::increment(c);
}
void
SSLNetVConnection::_out_context_tunnel()
{
ink_assert(get_context() == NET_VCONNECTION_OUT);
// Never a tunnel type for out (to server) context.
ink_assert(get_tunnel_type() == SNIRoutingType::NONE);
Metrics::Counter::increment(net_rsb.tunnel_total_server_connections_tls);
Metrics::Gauge::increment(net_rsb.tunnel_current_server_connections_tls);
}
void
SSLNetVConnection::increment_ssl_version_metric(int version) const
{
switch (version) {
case SSL3_VERSION:
Metrics::Counter::increment(ssl_rsb.total_sslv3);
break;
case TLS1_VERSION:
Metrics::Counter::increment(ssl_rsb.total_tlsv1);
break;
case TLS1_1_VERSION:
Metrics::Counter::increment(ssl_rsb.total_tlsv11);
break;
case TLS1_2_VERSION:
Metrics::Counter::increment(ssl_rsb.total_tlsv12);
break;
#ifdef TLS1_3_VERSION
case TLS1_3_VERSION:
Metrics::Counter::increment(ssl_rsb.total_tlsv13);
break;
#endif
default:
Dbg(dbg_ctl_ssl, "Unrecognized SSL version %d", version);
break;
}
}
std::string_view
SSLNetVConnection::map_tls_protocol_to_tag(const char *proto_string) const
{
std::string_view retval{"tls/?.?"sv}; // return this if the protocol lookup doesn't work.
if (proto_string) {
// openSSL guarantees the case of the protocol string.
if (proto_string[0] == 'T' && proto_string[1] == 'L' && proto_string[2] == 'S' && proto_string[3] == 'v' &&
proto_string[4] == '1') {
if (proto_string[5] == 0) {
retval = IP_PROTO_TAG_TLS_1_0;
} else if (proto_string[5] == '.' && proto_string[7] == 0) {
switch (proto_string[6]) {
case '1':
retval = IP_PROTO_TAG_TLS_1_1;
break;
case '2':
retval = IP_PROTO_TAG_TLS_1_2;
break;
case '3':
retval = IP_PROTO_TAG_TLS_1_3;
break;
default:
break;
}
}
}
}
return retval;
}
int
SSLNetVConnection::populate_protocol(std::string_view *results, int n) const
{
int retval = 0;
if (n > retval) {
results[retval] = map_tls_protocol_to_tag(this->get_tls_protocol_name());
if (!results[retval].empty()) {
++retval;
}
if (n > retval) {
retval += super::populate_protocol(results + retval, n - retval);
}
}
return retval;
}
const char *
SSLNetVConnection::protocol_contains(std::string_view prefix) const
{
const char *retval = nullptr;
std::string_view tag = map_tls_protocol_to_tag(this->get_tls_protocol_name());
if (prefix.size() <= tag.size() && strncmp(tag.data(), prefix.data(), prefix.size()) == 0) {
retval = tag.data();
} else {
retval = super::protocol_contains(prefix);
}
return retval;
}
bool
SSLNetVConnection::_trackFirstHandshake()
{
bool retval = this->get_tls_handshake_begin_time() == 0;
if (retval) {
this->_record_tls_handshake_begin_time();
}
return retval;
}
bool
SSLNetVConnection::_isReadyToTransferData() const
{
return getSSLHandShakeComplete();
}
void
SSLNetVConnection::_beReadyToTransferData()
{
if (this->_trackFirstHandshake()) {
// Eat the first write-ready. Until the TLS handshake is complete,
// we should still be under the connect timeout and shouldn't bother
// the state machine until the TLS handshake is complete
this->write.triggered = 0;
nh->write_ready_list.remove(this);
}
int err{0}, ret{0};
if (this->get_context() == NET_VCONNECTION_OUT) {
ret = this->_sslStartHandShake(SSL_EVENT_CLIENT, err);
} else {
ret = this->_sslStartHandShake(SSL_EVENT_SERVER, err);
}
if (ret == EVENT_ERROR) {
this->write.triggered = 0;
this->_writeSignalError(nh, err);
} else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) {
this->read.triggered = 0;
nh->read_ready_list.remove(this);
this->readReschedule(nh);
} else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) {
this->write.triggered = 0;
nh->write_ready_list.remove(this);
this->writeReschedule(nh);
} else if (ret == EVENT_DONE) {
this->write.triggered = 1;
if (this->write.enabled) {
nh->write_ready_list.in_or_enqueue(this);
}
// If this was driven by a zero length read, signal complete when
// the handshake is complete. Otherwise set up for continuing read
// operations.
if (this->write.vio.ntodo() <= 0) {
this->readSignalDone(VC_EVENT_WRITE_COMPLETE, nh);
}
} else {
this->writeReschedule(nh);
}
}
in_port_t
SSLNetVConnection::_get_local_port()
{
return this->get_local_port();
}
bool
SSLNetVConnection::_isTryingRenegotiation() const
{
if (SSLConfigParams::ssl_allow_client_renegotiation == false && this->getSSLHandShakeComplete()) {
return true;
} else {
return false;
}
}
shared_SSL_CTX
SSLNetVConnection::_lookupContextByName(const std::string &servername, SSLCertContextType ctxType)
{
shared_SSL_CTX ctx = nullptr;
SSLCertificateConfig::scoped_config lookup;
SSLCertContext *cc = lookup->find(servername, ctxType);
if (cc) {
ctx = cc->getCtx();
}
if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt && this->get_is_transparent()) {
this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
this->setSSLHandShakeComplete(SSLHandshakeStatus::SSL_HANDSHAKE_DONE);
return nullptr;
} else {
return ctx;
}
}
shared_SSL_CTX
SSLNetVConnection::_lookupContextByIP()
{
shared_SSL_CTX ctx = nullptr;
SSLCertificateConfig::scoped_config lookup;
IpEndpoint ip;
int namelen = sizeof(ip);
// Return null if this vc is already configured as a tunnel
if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) {
return nullptr;
}
SSLCertContext *cc = nullptr;
if (IpEndpoint const *proxy_protocol_dst = this->_proxy_protocol_dst_endpoint(); proxy_protocol_dst != nullptr) {
ip = *proxy_protocol_dst;
ip_port_text_buffer ipb1;
ats_ip_nptop(&ip, ipb1, sizeof(ipb1));
cc = lookup->find(ip);
if (dbg_ctl_proxyprotocol.on()) {
IpEndpoint src;
ip_port_text_buffer ipb2;
int ip_len = sizeof(src);
if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) {
DbgPrint(dbg_ctl_proxyprotocol, "Failed to get src ip, errno = [%d]", errno);
return nullptr;
}
ats_ip_nptop(&src, ipb2, sizeof(ipb2));
auto default_ctx = lookup->defaultContext();
DbgPrint(dbg_ctl_proxyprotocol, "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, default_ctx.get());
}
} else if (0 == safe_getsockname(this->get_socket(), &ip.sa, &namelen)) {
cc = lookup->find(ip);
}
if (cc) {
ctx = cc->getCtx();
}
return ctx;
}
IpEndpoint const *
SSLNetVConnection::_proxy_protocol_dst_endpoint() const
{
if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED &&
this->get_proxy_protocol_dst_addr() != nullptr) {
return &pp_info.dst_addr;
}
return nullptr;
}
void
SSLNetVConnection::set_ca_cert_file(std::string_view file, std::string_view dir)
{
if (file.size()) {
char *n = new char[file.size() + 1];
std::memcpy(n, file.data(), file.size());
n[file.size()] = '\0';
_ca_cert_file.reset(n);
}
if (dir.size()) {
char *n = new char[dir.size() + 1];
std::memcpy(n, dir.data(), dir.size());
n[dir.size()] = '\0';
_ca_cert_dir.reset(n);
}
}
void *
SSLNetVConnection::_prepareForMigration()
{
SSL *save_ssl = this->ssl;
this->_unbindSSLObject();
this->ssl = nullptr;
return save_ssl;
}
NetProcessor *
SSLNetVConnection::_getNetProcessor()
{
return &sslNetProcessor;
}
char *
SSLNetVConnection::_getCoalescedHandShakeBuffer(int64_t total_chain_size)
{
if (this->coalescedHandShakeBioBuffer != nullptr) {
ats_free(this->coalescedHandShakeBioBuffer);
this->coalescedHandShakeBioBuffer = nullptr;
}
char *start = this->handShakeReader->start();
char *end = this->handShakeReader->end();
char *coalescedBuffer = start;
if ((end - start) < total_chain_size) {
this->coalescedHandShakeBioBuffer = static_cast<char *>(ats_malloc(total_chain_size));
this->handShakeReader->memcpy(this->coalescedHandShakeBioBuffer, total_chain_size);
coalescedBuffer = this->coalescedHandShakeBioBuffer;
}
return coalescedBuffer;
}
void
SSLNetVConnection::_propagateHandShakeBuffer(UnixNetVConnection *target, EThread *t)
{
Dbg(dbg_ctl_ssl, "allow-plain, handshake buffer ready to read=%" PRId64, this->handShakeHolder->read_avail());
// Take ownership of the handShake buffer
this->sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE;
NetState *s = &target->read;
s->vio.set_writer(this->handShakeBuffer);
s->vio.set_reader(this->handShakeHolder);
this->handShakeHolder = nullptr;
this->handShakeBuffer = nullptr;
s->vio.vc_server = target;
s->vio.cont = this->read.vio.cont;
s->vio.mutex = this->read.vio.cont->mutex;
// Passing along the buffer, don't keep a reading holding early in the buffer
this->handShakeReader->dealloc();
this->handShakeReader = nullptr;
if (this->coalescedHandShakeBioBuffer != nullptr) {
ats_free(this->coalescedHandShakeBioBuffer);
this->coalescedHandShakeBioBuffer = nullptr;
}
// Kick things again, so the data that was copied into the
// vio.read buffer gets processed
target->readSignalDone(VC_EVENT_READ_COMPLETE, get_NetHandler(t));
}
/*
* Replaces the current SSLNetVConnection with a UnixNetVConnection
* Propagates any data in the SSL handShakeBuffer to be processed
* by the UnixNetVConnection logic
*/
UnixNetVConnection *
SSLNetVConnection::_migrateFromSSL()
{
EThread *t = this_ethread();
NetHandler *client_nh = get_NetHandler(t);
ink_assert(client_nh);
Connection hold_con;
hold_con.move(this->con);
// We will leave the SSL object with the original SSLNetVC to be
// cleaned up. Only moving the socket and handShakeBuffer
// So no need to call _prepareMigration
// Do_io_close will signal the VC to be freed on the original thread
// Since we moved the con context, the fd will not be closed
// Go ahead and remove the fd from the original thread's epoll structure, so it is not
// processed on two threads simultaneously
this->ep.stop();
// Create new VC:
UnixNetVConnection *newvc = static_cast<UnixNetVConnection *>(unix_netProcessor.allocate_vc(t));
ink_assert(newvc != nullptr);
if (newvc != nullptr && newvc->populate(hold_con, this->read.vio.cont, nullptr) != EVENT_DONE) {
newvc->do_io_close();
Dbg(dbg_ctl_ssl, "Failed to populate unixvc for allow-plain");
newvc = nullptr;
}
if (newvc != nullptr) {
newvc->attributes = HttpProxyPort::TRANSPORT_DEFAULT;
newvc->set_is_transparent(this->is_transparent);
newvc->set_context(get_context());
newvc->options = this->options;
Dbg(dbg_ctl_ssl, "Move to unixvc for allow-plain");
this->_propagateHandShakeBuffer(newvc, t);
}
// Do not mark this closed until the end so it does not get freed by the other thread too soon
this->do_io_close();
return newvc;
}
ssl_curve_id
SSLNetVConnection::_get_tls_curve() const
{
// For resumed server side session caching, we have to retrieve the curve/group
// from our stored data. For non-resumed sessions or from ticket based resumption,
// simply query the SSL object.
if (getIsResumedFromSessionCache()) {
return getSSLCurveNID();
} else {
return SSLGetCurveNID(ssl);
}
}
std::string_view
SSLNetVConnection::_get_tls_group() const
{
// For resumed server side session caching, we have to retrieve the curve/group
// from our stored data. For non-resumed sessions or from ticket based resumption,
// simply query the SSL object.
if (getIsResumedFromSessionCache()) {
return getSSLGroupName();
} else {
return SSLGetGroupName(ssl);
}
}
int
SSLNetVConnection::_verify_certificate(X509_STORE_CTX * /* ctx ATS_UNUSED */)
{
// Currently, TS_EVENT_SSL_VERIFY_CLIENT/SERVER are invoked only with a NetVC instance.
// This requires plugins to call TSSslVerifyCTX in their event handler.
// We could pass a structure that has both a cert to verify and a NetVC.
// It would allow us to remove confusing TSSslVerifyCTX and its internal implementation that are only available during a very
// limited time.
if (get_context() == NET_VCONNECTION_IN) {
this->callHooks(TS_EVENT_SSL_VERIFY_CLIENT /* , ctx */);
} else {
this->callHooks(TS_EVENT_SSL_VERIFY_SERVER /* , ctx */);
}
if (this->sslHandshakeStatus == SSLHandshakeStatus::SSL_HANDSHAKE_ERROR) {
return 1;
}
return 0;
}
ssl_error_t
SSLNetVConnection::_ssl_accept()
{
ERR_clear_error();
int ret = 0;
int ssl_error = SSL_ERROR_NONE;
#if TS_HAS_TLS_EARLY_DATA
if (!this->_early_data_finish) {
#if HAVE_SSL_READ_EARLY_DATA
size_t nread = 0;
#else
ssize_t nread = 0;
#endif
while (true) {
bool had_error_on_reading_early_data = false;
bool finished_reading_early_data = false;
IOBufferBlock *block = new_IOBufferBlock();
block->alloc(BUFFER_SIZE_INDEX_16K);
#if HAVE_SSL_READ_EARLY_DATA
ret = SSL_read_early_data(ssl, block->buf(), index_to_buffer_size(BUFFER_SIZE_INDEX_16K), &nread);
if (ret == SSL_READ_EARLY_DATA_ERROR) {
had_error_on_reading_early_data = true;
} else if (ret == SSL_READ_EARLY_DATA_FINISH) {
finished_reading_early_data = true;
}
#else
// If SSL_read_early_data is unavailable, it's probably BoringSSL,
// and SSL_in_early_data should be available.
ret = SSL_accept(ssl);
if (ret <= 0) {
had_error_on_reading_early_data = true;
} else {
if (SSL_in_early_data(ssl)) {
ret = SSL_read(ssl, block->buf(), index_to_buffer_size(BUFFER_SIZE_INDEX_16K));
finished_reading_early_data = !SSL_in_early_data(ssl);
if (ret < 0) {
nread = 0;
if (finished_reading_early_data) {
ret = 2; // SSL_READ_EARLY_DATA_FINISH
} else {
// Don't override ret here.
// Keeping the original retrurn value let ATS allow to check the value by SSL_get_error.
// That gives a chance to progress handshake process, or shutdown a connection if the error is serious.
had_error_on_reading_early_data = true;
}
} else {
nread = ret;
if (finished_reading_early_data) {
ret = 2; // SSL_READ_EARLY_DATA_FINISH
} else {
ret = 1; // SSL_READ_EARLY_DATA_SUCCESS
}
}
} else {
nread = 0;
ret = 2; // SSL_READ_EARLY_DATA_FINISH
finished_reading_early_data = true;
}
}
#endif
if (had_error_on_reading_early_data) {
Dbg(dbg_ctl_ssl_early_data, "Error on reading early data: %d", ret);
block->free();
break;
} else {
if (nread > 0) {
if (this->_early_data_buf == nullptr) {
this->_early_data_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_16K);
this->_early_data_reader = this->_early_data_buf->alloc_reader();
}
block->fill(nread);
this->_early_data_buf->append_block(block);
this->_increment_early_data_len(nread);
Metrics::Counter::increment(ssl_rsb.early_data_received_count);
if (dbg_ctl_ssl_early_data_show_received.on()) {
std::string early_data_str(reinterpret_cast<char *>(block->buf()), nread);
DbgPrint(dbg_ctl_ssl_early_data_show_received, "Early data buffer: \n%s", early_data_str.c_str());
}
} else {
block->free();
}
if (finished_reading_early_data) {
this->_early_data_finish = true;
Dbg(dbg_ctl_ssl_early_data, "SSL_READ_EARLY_DATA_FINISH: size = %lu", nread);
if (this->_early_data_reader == nullptr || this->_early_data_reader->read_avail() == 0) {
Dbg(dbg_ctl_ssl_early_data, "no data in early data buffer");
ERR_clear_error();
ret = SSL_accept(ssl);
}
break;
}
Dbg(dbg_ctl_ssl_early_data, "SSL_READ_EARLY_DATA_SUCCESS: size = %lu", nread);
}
}
} else {
ret = SSL_accept(ssl);
}
#else
ret = SSL_accept(ssl);
#endif
if (ret > 0) {
return SSL_ERROR_NONE;
}
ssl_error = SSL_get_error(ssl, ret);
if (ssl_error == SSL_ERROR_SSL && dbg_ctl_ssl_error_accept.on()) {
char buf[512];
unsigned long e = ERR_peek_last_error();
ERR_error_string_n(e, buf, sizeof(buf));
DbgPrint(dbg_ctl_ssl_error_accept, "SSL accept returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, buf);
}
return ssl_error;
}
ssl_error_t
SSLNetVConnection::_ssl_connect()
{
ERR_clear_error();
SSL_SESSION *sess = SSL_get_session(ssl);
if (first_ssl_connect) {
first_ssl_connect = false;
if (!sess && SSLConfigParams::origin_session_cache == 1 && SSLConfigParams::origin_session_cache_size > 0) {
std::string sni_addr = get_sni_addr(ssl);
if (!sni_addr.empty()) {
std::string lookup_key;
swoc::bwprint(lookup_key, "{}:{}:{}", sni_addr.c_str(), SSL_get_SSL_CTX(ssl), get_verify_str(ssl));
Dbg(dbg_ctl_ssl_origin_session_cache, "origin session cache lookup key = %s", lookup_key.c_str());
std::shared_ptr<SSL_SESSION> shared_sess = this->getOriginSession(lookup_key);
if (shared_sess && SSL_set_session(ssl, shared_sess.get())) {
// Keep a reference of this shared pointer in the connection
this->client_sess = std::move(shared_sess);
}
}
}
}
int ret = SSL_connect(ssl);
if (ret > 0) {
if (SSL_session_reused(ssl)) {
Metrics::Counter::increment(ssl_rsb.origin_session_reused_count);
Dbg(dbg_ctl_ssl_origin_session_cache, "reused session to origin server");
} else {
Dbg(dbg_ctl_ssl_origin_session_cache, "new session to origin server");
}
return SSL_ERROR_NONE;
}
int ssl_error = SSL_get_error(ssl, ret);
if (ssl_error == SSL_ERROR_SSL && dbg_ctl_ssl_error_connect.on()) {
char buf[512];
unsigned long e = ERR_peek_last_error();
ERR_error_string_n(e, buf, sizeof(buf));
DbgPrint(dbg_ctl_ssl_error_connect, "SSL connect returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, buf);
}
return ssl_error;
}
ssl_error_t
SSLNetVConnection::_ssl_write_buffer(const void *buf, int64_t nbytes, int64_t &nwritten)
{
nwritten = 0;
if (unlikely(nbytes == 0)) {
return SSL_ERROR_NONE;
}
int ret;
// If SSL_write_early_data is available, it's probably OpenSSL,
// and SSL_is_init_finished should be available.
// If SSL_write_early_data is unavailable, its' probably BoringSSL,
// and we can use SSL_write to send early data.
#if TS_HAS_TLS_EARLY_DATA
if (SSL_version(ssl) >= TLS1_3_VERSION) {
#ifdef HAVE_SSL_WRITE_EARLY_DATA
if (SSL_is_init_finished(ssl)) {
#endif
ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
#ifdef HAVE_SSL_WRITE_EARLY_DATA
} else {
size_t nwrite;
ret = SSL_write_early_data(ssl, buf, static_cast<size_t>(nbytes), &nwrite);
if (ret == 1) {
ret = nwrite;
}
}
#endif
} else {
ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
}
#else
ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
#endif
if (ret > 0) {
nwritten = ret;
BIO *bio = SSL_get_wbio(ssl);
if (bio != nullptr) {
(void)BIO_flush(bio);
}
return SSL_ERROR_NONE;
}
int ssl_error = SSL_get_error(ssl, ret);
if (ssl_error == SSL_ERROR_SSL && dbg_ctl_ssl_error_write.on()) {
char tempbuf[512];
unsigned long e = ERR_peek_last_error();
ERR_error_string_n(e, tempbuf, sizeof(tempbuf));
DbgPrint(dbg_ctl_ssl_error_write, "SSL write returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, tempbuf);
}
return ssl_error;
}
ssl_error_t
SSLNetVConnection::_ssl_read_buffer(void *buf, int64_t nbytes, int64_t &nread)
{
nread = 0;
if (unlikely(nbytes == 0)) {
return SSL_ERROR_NONE;
}
ERR_clear_error();
#if TS_HAS_TLS_EARLY_DATA
if (SSL_version(ssl) >= TLS1_3_VERSION) {
int64_t early_data_len = 0;
if (this->_early_data_reader != nullptr) {
early_data_len = this->_early_data_reader->read_avail();
}
if (early_data_len > 0) {
Dbg(dbg_ctl_ssl_early_data, "Reading from early data buffer.");
this->_increment_early_data_len(this->_early_data_reader->read(buf, nbytes < early_data_len ? nbytes : early_data_len));
if (nbytes < early_data_len) {
nread = nbytes;
} else {
nread = early_data_len;
}
return SSL_ERROR_NONE;
}
bool early_data_enabled = this->hints_from_sni.server_max_early_data.has_value() ?
this->hints_from_sni.server_max_early_data.value() > 0 :
SSLConfigParams::server_max_early_data > 0;
if (early_data_enabled && !this->_early_data_finish) {
bool had_error_on_reading_early_data = false;
bool finished_reading_early_data = false;
Dbg(dbg_ctl_ssl_early_data, "More early data to read.");
ssl_error_t ssl_error = SSL_ERROR_NONE;
int ret;
#if HAVE_SSL_READ_EARLY_DATA
size_t read_bytes = 0;
#else
ssize_t read_bytes = 0;
#endif
#if HAVE_SSL_READ_EARLY_DATA
ret = SSL_read_early_data(ssl, buf, static_cast<size_t>(nbytes), &read_bytes);
if (ret == SSL_READ_EARLY_DATA_ERROR) {
had_error_on_reading_early_data = true;
ssl_error = SSL_get_error(ssl, ret);
} else if (ret == SSL_READ_EARLY_DATA_FINISH) {
finished_reading_early_data = true;
}
#else
// If SSL_read_early_data is unavailable, it's probably OpenSSL,
// and SSL_in_early_data should be available.
if (SSL_in_early_data(ssl)) {
ret = SSL_read(ssl, buf, nbytes);
finished_reading_early_data = !SSL_in_early_data(ssl);
if (ret < 0) {
if (!finished_reading_early_data) {
had_error_on_reading_early_data = true;
ssl_error = SSL_get_error(ssl, ret);
}
read_bytes = 0;
} else {
read_bytes = ret;
}
} else {
finished_reading_early_data = true;
read_bytes = 0;
}
#endif
if (had_error_on_reading_early_data) {
Dbg(dbg_ctl_ssl_early_data, "Error reading early data: %s", ERR_error_string(ERR_get_error(), nullptr));
} else {
if ((nread = read_bytes) > 0) {
this->_increment_early_data_len(read_bytes);
Metrics::Counter::increment(ssl_rsb.early_data_received_count);
if (dbg_ctl_ssl_early_data_show_received.on()) {
std::string early_data_str(reinterpret_cast<char *>(buf), nread);
DbgPrint(dbg_ctl_ssl_early_data_show_received, "Early data buffer: \n%s", early_data_str.c_str());
}
}
if (finished_reading_early_data) {
this->_early_data_finish = true;
Dbg(dbg_ctl_ssl_early_data, "SSL_READ_EARLY_DATA_FINISH: size = %" PRId64, nread);
} else {
Dbg(dbg_ctl_ssl_early_data, "SSL_READ_EARLY_DATA_SUCCESS: size = %" PRId64, nread);
}
}
return ssl_error;
}
}
#endif
int ret = SSL_read(ssl, buf, static_cast<int>(nbytes));
if (ret > 0) {
nread = ret;
return SSL_ERROR_NONE;
}
int ssl_error = SSL_get_error(ssl, ret);
if (ssl_error == SSL_ERROR_SSL && dbg_ctl_ssl_error_read.on()) {
char tempbuf[512];
unsigned long e = ERR_peek_last_error();
ERR_error_string_n(e, tempbuf, sizeof(tempbuf));
DbgPrint(dbg_ctl_ssl_error_read, "SSL read returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, tempbuf);
}
return ssl_error;
}