blob: e84cbfca06e699e784f4c732444d5a2098cfaacc [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 "P_NetAccept.h"
#include "P_Net.h"
#include "P_UnixNet.h"
#include "P_UnixNetVConnection.h"
#include "iocore/net/ConnectionTracker.h"
#include "iocore/net/NetHandler.h"
#include "tscore/TSSystemState.h"
#include "tscore/ink_inet.h"
#include "tscore/ink_defs.h"
using NetAcceptHandler = int (NetAccept::*)(int, void *);
namespace
{
DbgCtl dbg_ctl_iocore_net{"iocore_net"};
DbgCtl dbg_ctl_iocore_net_accept_start{"iocore_net_accept_start"};
DbgCtl dbg_ctl_iocore_net_accepts{"iocore_net_accepts"};
DbgCtl dbg_ctl_iocore_net_accept{"iocore_net_accept"};
/** Check and handle if the number of client connections exceeds the configured max.
*
* @param[in] addr The client address of the new incoming connection.
* @param[out] conn_track_group The connection tracker group associated with the
* new incoming connection if connections are being tracked.
*
* @return true if the connection should be accepted, false otherwise.
*/
bool
handle_max_client_connections(IpEndpoint const &addr, std::shared_ptr<ConnectionTracker::Group> &conn_track_group)
{
int const client_max = NetHandler::get_per_client_max_connections_in();
if (client_max > 0) {
auto inbound_tracker = ConnectionTracker::obtain_inbound(addr);
if (inbound_tracker.is_exempt()) {
// The user configured connections like this to not be tracked. Simply exempt it.
Metrics::Counter::increment(net_rsb.per_client_connections_exempt_in);
Dbg(dbg_ctl_iocore_net_accepts, "Ignoring client connection counting for an incoming address in the exempt list.");
return true;
}
auto const tracked_count = inbound_tracker.reserve();
if (tracked_count > client_max) {
// close the connection as we are in per client connection throttle state
inbound_tracker.release();
inbound_tracker.blocked();
inbound_tracker.Warn_Blocked(client_max, 0, tracked_count - 1, addr,
dbg_ctl_iocore_net_accept.on() ? &dbg_ctl_iocore_net_accept : nullptr);
Metrics::Counter::increment(net_rsb.per_client_connections_throttled_in);
Dbg(dbg_ctl_iocore_net_accepts, "Blocking a client connection due to per client connection limit.");
return false;
}
conn_track_group = inbound_tracker.drop();
}
return true;
}
} // end anonymous namespace
static void
safe_delay(int msec)
{
UnixSocket::poll(nullptr, 0, msec);
}
//
// General case network connection accept code
//
int
net_accept(NetAccept *na, void *ep, bool blockable)
{
Event *e = static_cast<Event *>(ep);
int res = 0;
int count = 0;
UnixNetVConnection *vc = nullptr;
Connection con;
int additional_accepts = NetHandler::get_additional_accepts();
if (!blockable) {
if (!MUTEX_TAKE_TRY_LOCK(na->action_->mutex, e->ethread)) {
return 0;
}
}
// do-while for accepting all the connections
// added by YTS Team, yamsat
do {
if ((res = na->server.accept(&con)) < 0) {
if (res == -EAGAIN || res == -ECONNABORTED || res == -EPIPE) {
goto Ldone;
}
if (na->action_->is_listening()) {
if (!blockable) {
na->action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
} else {
SCOPED_MUTEX_LOCK(lock, na->action_->mutex, e->ethread);
na->action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
}
}
count = res;
goto Ldone;
}
Metrics::Counter::increment(net_rsb.tcp_accept);
std::shared_ptr<ConnectionTracker::Group> conn_track_group;
if (!handle_max_client_connections(con.addr, conn_track_group)) {
con.close();
continue;
}
vc = static_cast<UnixNetVConnection *>(na->getNetProcessor()->allocate_vc(e->ethread));
if (!vc) {
goto Ldone; // note: @a con will clean up the socket when it goes out of scope.
}
vc->enable_inbound_connection_tracking(std::move(conn_track_group));
count++;
Metrics::Gauge::increment(net_rsb.connections_currently_open);
vc->id = net_next_connection_number();
vc->con.move(con);
vc->set_remote_addr(con.addr);
vc->submit_time = ink_get_hrtime();
vc->action_ = *na->action_;
vc->set_is_transparent(na->opt.f_inbound_transparent);
vc->set_is_proxy_protocol(na->opt.f_proxy_protocol, na->opt.f_proxy_protocol_client_src);
vc->set_context(NET_VCONNECTION_IN);
if (na->opt.f_mptcp) {
vc->set_mptcp_state(); // Try to get the MPTCP state, and update accordingly
}
#ifdef USE_EDGE_TRIGGER
// Set the vc as triggered and place it in the read ready queue later in case there is already data on the socket.
if (na->server.http_accept_filter) {
vc->read.triggered = 1;
}
#endif
SET_CONTINUATION_HANDLER(vc, &UnixNetVConnection::acceptEvent);
EThread *t;
NetHandler *h;
if (e->ethread->is_event_type(ET_NET)) {
t = e->ethread;
h = get_NetHandler(t);
// Assign NetHandler->mutex to NetVC
vc->mutex = h->mutex;
MUTEX_TRY_LOCK(lock, h->mutex, t);
if (!lock.is_locked()) {
t->schedule_in(vc, HRTIME_MSECONDS(net_retry_delay));
} else {
vc->handleEvent(EVENT_NONE, e);
}
} else {
t = eventProcessor.assign_thread(ET_NET);
h = get_NetHandler(t);
// Assign NetHandler->mutex to NetVC
vc->mutex = h->mutex;
t->schedule_imm(vc);
}
} while (count < additional_accepts);
Ldone:
if (!blockable) {
MUTEX_UNTAKE_LOCK(na->action_->mutex, e->ethread);
}
// if we stop looping as a result of hitting the accept limit,
// reschedule accepting to the end of the thread event queue
// for the goal of fairness between accepting and other work
Dbg(dbg_ctl_iocore_net_accepts, "exited accept loop - count: %d, limit: %d", count, additional_accepts);
if (count >= additional_accepts) {
this_ethread()->schedule_imm_local(na);
}
return count;
}
NetAccept *
getNetAccept(int ID)
{
SCOPED_MUTEX_LOCK(lock, naVecMutex, this_ethread());
return naVec.at(ID);
}
//
// Initialize the NetAccept for execution in its own thread.
// This should be done for low latency, high connection rate sockets.
//
void
NetAccept::init_accept_loop()
{
int i, n;
char thr_name[MAX_THREAD_NAME_LENGTH];
size_t stacksize;
if (do_blocking_listen()) {
return;
}
stacksize = RecGetRecordInt("proxy.config.thread.default.stacksize").value_or(0);
SET_CONTINUATION_HANDLER(this, &NetAccept::acceptLoopEvent);
n = opt.accept_threads;
// Fill in accept thread from configuration if necessary.
if (n < 0) {
n = RecGetRecordInt("proxy.config.accept_threads").value_or(0);
}
for (i = 0; i < n; i++) {
NetAccept *a = (i < n - 1) ? clone() : this;
snprintf(thr_name, MAX_THREAD_NAME_LENGTH, "[ACCEPT %d:%d]", i, ats_ip_port_host_order(&server.accept_addr));
eventProcessor.spawn_thread(a, thr_name, stacksize);
Dbg(dbg_ctl_iocore_net_accept_start, "Created accept thread #%d for port %d", i + 1,
ats_ip_port_host_order(&server.accept_addr));
}
}
//
// Initialize the NetAccept for execution in a etype thread.
// This should be done for low connection rate sockets.
// (Management, Cluster, etc.) Also, since it adapts to the
// number of connections arriving, it should be reasonable to
// use it for high connection rates as well.
//
void
NetAccept::init_accept(EThread *t)
{
if (!t) {
t = eventProcessor.assign_thread(ET_NET);
}
if (!action_->continuation->mutex) {
action_->continuation->mutex = t->mutex;
action_->mutex = t->mutex;
}
if (do_listen()) {
return;
}
SET_HANDLER(&NetAccept::acceptEvent);
period = -HRTIME_MSECONDS(net_accept_period);
t->schedule_every(this, period);
}
int
NetAccept::accept_per_thread(int /* event ATS_UNUSED */, void * /* ep ATS_UNUSED */)
{
int listen_per_thread = 0;
listen_per_thread = RecGetRecordInt("proxy.config.exec_thread.listen").value_or(0);
if (listen_per_thread == 1) {
if (ats_is_unix(server.accept_addr)) {
auto id = this_ethread()->id;
ats_unix_append_id(&server.accept_addr.sun, id);
}
if (do_listen()) {
Fatal("[NetAccept::accept_per_thread]:error listening on ports");
return -1;
}
}
SET_HANDLER(&NetAccept::acceptFastEvent);
PollDescriptor *pd = get_PollDescriptor(this_ethread());
if (this->ep.start(pd, this, EVENTIO_READ) < 0) {
Fatal("[NetAccept::accept_per_thread]:error starting EventIO");
return -1;
}
return 0;
}
void
NetAccept::init_accept_per_thread()
{
int i, n;
int listen_per_thread = 0;
listen_per_thread = RecGetRecordInt("proxy.config.exec_thread.listen").value_or(0);
if (listen_per_thread == 0) {
if (do_listen()) {
Fatal("[NetAccept::accept_per_thread]:error listenting on ports");
return;
}
}
SET_HANDLER(&NetAccept::accept_per_thread);
n = eventProcessor.thread_group[ET_NET]._count;
for (i = 0; i < n; i++) {
NetAccept *a = (i < n - 1) ? clone() : this;
EThread *t = eventProcessor.thread_group[ET_NET]._thread[i];
a->mutex = get_NetHandler(t)->mutex;
t->schedule_imm(a);
}
}
void
NetAccept::stop_accept()
{
action_->cancel();
server.close();
}
int
NetAccept::do_listen()
{
// non-blocking
return this->do_listen_impl(true);
}
int
NetAccept::do_blocking_listen()
{
return this->do_listen_impl(false);
}
int
NetAccept::do_listen_impl(bool non_blocking)
{
int res = 0;
if (server.sock.is_ok()) {
if ((res = server.setup_fd_for_listen(non_blocking, opt))) {
Warning("unable to listen on main accept port %d: errno = %d, %s", server.accept_addr.host_order_port(), errno,
strerror(errno));
goto Lretry;
}
} else {
Lretry:
if ((res = server.listen(non_blocking, opt))) {
Warning("unable to listen on port %d: %d %d, %s", server.accept_addr.host_order_port(), res, errno, strerror(errno));
}
}
return res;
}
int
NetAccept::do_blocking_accept(EThread *t)
{
int res = 0;
UnixNetVConnection *vc = nullptr;
Connection con;
con.sock_type = SOCK_STREAM;
int count = 0;
int additional_accepts = NetHandler::get_additional_accepts();
// do-while for accepting all the connections
// added by YTS Team, yamsat
do {
if ((res = server.accept(&con)) < 0) {
int seriousness = accept_error_seriousness(res);
switch (seriousness) {
case 0:
// bad enough to warn about
check_transient_accept_error(res);
safe_delay(net_throttle_delay);
return 0;
case 1:
// not so bad but needs delay
safe_delay(net_throttle_delay);
return 0;
case 2:
// ignore
return 0;
case -1:
[[fallthrough]];
default:
if (action_->is_listening()) {
SCOPED_MUTEX_LOCK(lock, action_->mutex ? action_->mutex : t->mutex, t);
action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
Warning("accept thread received fatal error: errno = %d", errno);
}
return -1;
}
}
// check for throttle
if (check_net_throttle(ACCEPT)) {
check_throttle_warning(ACCEPT);
// close the connection as we are in throttle state
con.close();
Metrics::Counter::increment(net_rsb.connections_throttled_in);
continue;
}
std::shared_ptr<ConnectionTracker::Group> conn_track_group;
if (!handle_max_client_connections(con.addr, conn_track_group)) {
con.close();
continue;
}
if (TSSystemState::is_event_system_shut_down()) {
return -1;
}
Metrics::Counter::increment(net_rsb.tcp_accept);
// Use 'nullptr' to Bypass thread allocator
vc = static_cast<UnixNetVConnection *>(this->getNetProcessor()->allocate_vc(nullptr));
if (unlikely(!vc)) {
return -1;
}
vc->enable_inbound_connection_tracking(std::move(conn_track_group));
count++;
Metrics::Gauge::increment(net_rsb.connections_currently_open);
vc->id = net_next_connection_number();
vc->con.move(con);
vc->set_remote_addr(con.addr);
vc->submit_time = ink_get_hrtime();
vc->action_ = *action_;
vc->set_is_transparent(opt.f_inbound_transparent);
vc->set_is_proxy_protocol(opt.f_proxy_protocol, opt.f_proxy_protocol_client_src);
vc->options.sockopt_flags = opt.sockopt_flags;
vc->options.packet_mark = opt.packet_mark;
vc->options.packet_tos = opt.packet_tos;
vc->options.packet_notsent_lowat = opt.packet_notsent_lowat;
vc->options.ip_family = opt.ip_family;
vc->apply_options();
vc->set_context(NET_VCONNECTION_IN);
if (opt.f_mptcp) {
vc->set_mptcp_state(); // Try to get the MPTCP state, and update accordingly
}
vc->accept_object = this;
#ifdef USE_EDGE_TRIGGER
// Set the vc as triggered and place it in the read ready queue later in case there is already data on the socket.
if (server.http_accept_filter) {
vc->read.triggered = 1;
}
#endif
SET_CONTINUATION_HANDLER(vc, &UnixNetVConnection::acceptEvent);
EThread *localt = eventProcessor.assign_thread(ET_NET);
NetHandler *h = get_NetHandler(localt);
// Assign NetHandler->mutex to NetVC
vc->mutex = h->mutex;
localt->schedule_imm(vc);
} while (count < additional_accepts);
return 1;
}
int
NetAccept::acceptEvent(int event, void *ep)
{
(void)event;
Event *e = static_cast<Event *>(ep);
// PollDescriptor *pd = get_PollDescriptor(e->ethread);
Ptr<ProxyMutex> m;
if (action_->mutex) {
m = action_->mutex;
} else {
m = mutex;
}
MUTEX_TRY_LOCK(lock, m, e->ethread);
if (lock.is_locked()) {
if (action_->cancelled) {
// Server was already closed by whoever called cancel().
e->cancel();
Metrics::Gauge::decrement(net_rsb.accepts_currently_open);
delete this;
return EVENT_DONE;
}
int res;
if ((res = net_accept(this, e, false)) < 0) {
action_->cancel();
Metrics::Gauge::decrement(net_rsb.accepts_currently_open);
/* INKqa11179 */
Warning("Accept on port %d failed with error no %d", ats_ip_port_host_order(&server.addr), res);
Warning("Traffic Server may be unable to accept more network"
"connections on %d",
ats_ip_port_host_order(&server.addr));
e->cancel();
delete this;
return EVENT_DONE;
}
//}
}
return EVENT_CONT;
}
int
NetAccept::acceptFastEvent(int event, void *ep)
{
Event *e = static_cast<Event *>(ep);
(void)event;
(void)e;
int bufsz, res = 0;
Connection con;
con.sock_type = SOCK_STREAM;
UnixNetVConnection *vc = nullptr;
int count = 0;
EThread *t = e->ethread;
NetHandler *h = get_NetHandler(t);
int additional_accepts = NetHandler::get_additional_accepts();
do {
socklen_t sz = sizeof(con.addr);
UnixSocket sock{-1};
if (int res{server.sock.accept4(&con.addr.sa, &sz, SOCK_NONBLOCK | SOCK_CLOEXEC)}; res >= 0) {
sock = UnixSocket{res};
}
con.sock = sock;
std::shared_ptr<ConnectionTracker::Group> conn_track_group;
if (likely(sock.is_ok())) {
// check for throttle
if (check_net_throttle(ACCEPT)) {
// close the connection as we are in throttle state
con.close();
Metrics::Counter::increment(net_rsb.connections_throttled_in);
continue;
}
if (!handle_max_client_connections(con.addr, conn_track_group)) {
con.close();
continue;
}
Dbg(dbg_ctl_iocore_net, "accepted a new socket: %d", sock.get_fd());
Metrics::Counter::increment(net_rsb.tcp_accept);
if (opt.send_bufsize > 0) {
if (unlikely(sock.set_sndbuf_size(opt.send_bufsize))) {
bufsz = ROUNDUP(opt.send_bufsize, 1024);
while (bufsz > 0) {
if (!sock.set_sndbuf_size(bufsz)) {
break;
}
bufsz -= 1024;
}
}
}
if (opt.recv_bufsize > 0) {
if (unlikely(sock.set_rcvbuf_size(opt.recv_bufsize))) {
bufsz = ROUNDUP(opt.recv_bufsize, 1024);
while (bufsz > 0) {
if (!sock.set_rcvbuf_size(bufsz)) {
break;
}
bufsz -= 1024;
}
}
}
} else {
res = sock.get_fd();
}
// check return value from accept()
if (res < 0) {
Dbg(dbg_ctl_iocore_net, "received : %s", strerror(errno));
res = -errno;
if (res == -EAGAIN || res == -ECONNABORTED
#if defined(__linux__)
|| res == -EPIPE
#endif
) {
goto Ldone;
} else if (accept_error_seriousness(res) >= 0) {
check_transient_accept_error(res);
goto Ldone;
}
if (action_->is_listening()) {
action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
}
goto Lerror;
}
vc = static_cast<UnixNetVConnection *>(this->getNetProcessor()->allocate_vc(e->ethread));
ink_release_assert(vc);
vc->enable_inbound_connection_tracking(std::move(conn_track_group));
count++;
Metrics::Gauge::increment(net_rsb.connections_currently_open);
vc->id = net_next_connection_number();
vc->con.move(con);
vc->set_remote_addr(con.addr);
vc->submit_time = ink_get_hrtime();
vc->action_ = *action_;
vc->set_is_transparent(opt.f_inbound_transparent);
vc->set_is_proxy_protocol(opt.f_proxy_protocol, opt.f_proxy_protocol_client_src);
vc->options.sockopt_flags = opt.sockopt_flags;
vc->options.packet_mark = opt.packet_mark;
vc->options.packet_tos = opt.packet_tos;
vc->options.packet_notsent_lowat = opt.packet_notsent_lowat;
vc->options.ip_family = opt.ip_family;
vc->apply_options();
vc->set_context(NET_VCONNECTION_IN);
if (opt.f_mptcp) {
vc->set_mptcp_state(); // Try to get the MPTCP state, and update accordingly
}
#ifdef USE_EDGE_TRIGGER
// Set the vc as triggered and place it in the read ready queue later in case there is already data on the socket.
if (server.http_accept_filter) {
vc->read.triggered = 1;
}
#endif
SET_CONTINUATION_HANDLER(vc, &UnixNetVConnection::acceptEvent);
// Assign NetHandler->mutex to NetVC
vc->mutex = h->mutex;
// We must be holding the lock already to do later do_io_read's
SCOPED_MUTEX_LOCK(lock, vc->mutex, e->ethread);
vc->handleEvent(EVENT_NONE, nullptr);
vc = nullptr;
} while (count < additional_accepts);
Ldone:
// if we stop looping as a result of hitting the accept limit,
// resechedule accepting to the end of the thread event queue
// for the goal of fairness between accepting and other work
Dbg(dbg_ctl_iocore_net_accepts, "exited accept loop - count: %d, limit: %d", count, additional_accepts);
if (count >= additional_accepts) {
this_ethread()->schedule_imm_local(this);
}
return EVENT_CONT;
Lerror:
action_->cancel();
server.close();
e->cancel();
Metrics::Gauge::decrement(net_rsb.accepts_currently_open);
delete this;
return EVENT_DONE;
}
int
NetAccept::acceptLoopEvent(int event, Event *e)
{
(void)event;
(void)e;
EThread *t = this_ethread();
while (do_blocking_accept(t) >= 0) {
;
}
// Don't think this ever happens ...
action_->cancel();
Metrics::Gauge::decrement(net_rsb.accepts_currently_open);
delete this;
return EVENT_DONE;
}
//
// Accept Event handler
//
//
NetAccept::NetAccept(const NetProcessor::AcceptOptions &_opt) : Continuation(nullptr), opt(_opt) {}
//
// Stop listening. When the next poll takes place, an error will result.
// THIS ONLY WORKS WITH POLLING STYLE ACCEPTS!
//
void
NetAccept::cancel()
{
action_->cancel();
server.close();
}
NetAccept *
NetAccept::clone() const
{
NetAccept *na;
na = new NetAccept(opt);
*na = *this;
return na;
}
NetProcessor *
NetAccept::getNetProcessor() const
{
return &netProcessor;
}