blob: 99f67066f0fc321fe2f86261cc5e71c44e780085 [file] [log] [blame]
/** @file
*
* OpenSSL socket BIO that does TCP Fast Open.
*
* @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 "P_Net.h"
#include "I_SocketManager.h"
#include "tscore/ink_assert.h"
#include "BIO_fastopen.h"
// For BoringSSL, which for some reason doesn't have this function.
// (In BoringSSL, sock_read() and sock_write() use the internal
// bio_fd_non_fatal_error() instead.) #1437
//
// The following is copied from
// https://github.com/openssl/openssl/blob/3befffa39dbaf2688d823fcf2bdfc07d2487be48/crypto/bio/bss_sock.c
// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
#ifndef HAVE_BIO_SOCK_NON_FATAL_ERROR
int
BIO_sock_non_fatal_error(int err)
{
switch (err) {
#if defined(OPENSSL_SYS_WINDOWS)
#if defined(WSAEWOULDBLOCK)
case WSAEWOULDBLOCK:
#endif
#endif
#ifdef EWOULDBLOCK
#ifdef WSAEWOULDBLOCK
#if WSAEWOULDBLOCK != EWOULDBLOCK
case EWOULDBLOCK:
#endif
#else
case EWOULDBLOCK:
#endif
#endif
#if defined(ENOTCONN)
case ENOTCONN:
#endif
#ifdef EINTR
case EINTR:
#endif
#ifdef EAGAIN
#if EWOULDBLOCK != EAGAIN
case EAGAIN:
#endif
#endif
#ifdef EPROTO
case EPROTO:
#endif
#ifdef EINPROGRESS
case EINPROGRESS:
#endif
#ifdef EALREADY
case EALREADY:
#endif
return (1);
/* break; */
default:
break;
}
return (0);
}
#endif
static int (*fastopen_create)(BIO *) = BIO_meth_get_create(const_cast<BIO_METHOD *>(BIO_s_socket()));
static int
fastopen_destroy(BIO *bio)
{
if (bio) {
// We expect this BIO to not own the socket, so we must always
// be in NOCLOSE mode.
ink_assert(BIO_get_shutdown(bio) == BIO_NOCLOSE);
}
return BIO_meth_get_destroy(const_cast<BIO_METHOD *>(BIO_s_socket()))(bio);
}
static int
fastopen_bwrite(BIO *bio, const char *in, int insz)
{
int64_t err;
errno = 0;
BIO_clear_retry_flags(bio);
int fd = BIO_get_fd(bio, nullptr);
ink_assert(fd != NO_FD);
if (BIO_get_data(bio)) {
// On the first write only, make a TFO request if TFO is enabled.
// The best documentation on the behavior of the Linux API is in
// RFC 7413. If we get EINPROGRESS it means that the SYN has been
// sent without data and we should retry.
const sockaddr *dst = reinterpret_cast<const sockaddr *>(BIO_get_data(bio));
ProxyMutex *mutex = this_ethread()->mutex.get();
NET_INCREMENT_DYN_STAT(net_fastopen_attempts_stat);
err = socketManager.sendto(fd, (void *)in, insz, MSG_FASTOPEN, dst, ats_ip_size(dst));
if (err >= 0) {
NET_INCREMENT_DYN_STAT(net_fastopen_successes_stat);
}
BIO_set_data(bio, nullptr);
} else {
err = socketManager.write(fd, (void *)in, insz);
}
if (err < 0) {
errno = -err;
if (BIO_sock_non_fatal_error(errno)) {
BIO_set_retry_write(bio);
}
}
return err < 0 ? -1 : err;
}
static int
fastopen_bread(BIO *bio, char *out, int outsz)
{
int64_t err;
errno = 0;
BIO_clear_retry_flags(bio);
int fd = BIO_get_fd(bio, nullptr);
ink_assert(fd != NO_FD);
// TODO: If we haven't done the fastopen, ink_abort().
err = socketManager.read(fd, out, outsz);
if (err < 0) {
errno = -err;
if (BIO_sock_non_fatal_error(errno)) {
BIO_set_retry_read(bio);
}
}
return err < 0 ? -1 : err;
}
static long
fastopen_ctrl(BIO *bio, int cmd, long larg, void *ptr)
{
switch (cmd) {
case BIO_C_SET_CONNECT:
// We only support BIO_set_conn_address(), which sets a sockaddr.
ink_assert(larg == 2);
BIO_set_data(bio, ptr);
return 0;
}
return BIO_meth_get_ctrl(const_cast<BIO_METHOD *>(BIO_s_socket()))(bio, cmd, larg, ptr);
}
#ifndef HAVE_BIO_METH_NEW
static const BIO_METHOD fastopen_methods[] = {{
.type = BIO_TYPE_SOCKET,
.name = "fastopen",
.bwrite = fastopen_bwrite,
.bread = fastopen_bread,
.bputs = nullptr,
.bgets = nullptr,
.ctrl = fastopen_ctrl,
.create = fastopen_create,
.destroy = fastopen_destroy,
.callback_ctrl = nullptr,
}};
#else
static const BIO_METHOD *fastopen_methods = [] {
BIO_METHOD *methods = BIO_meth_new(BIO_TYPE_SOCKET, "fastopen");
BIO_meth_set_write(methods, fastopen_bwrite);
BIO_meth_set_read(methods, fastopen_bread);
BIO_meth_set_ctrl(methods, fastopen_ctrl);
BIO_meth_set_create(methods, fastopen_create);
BIO_meth_set_destroy(methods, fastopen_destroy);
return methods;
}();
#endif
const BIO_METHOD *
BIO_s_fastopen()
{
return fastopen_methods;
}