blob: 32a8c0f7ae3b2ee384c36d56cd1167d70a5ef938 [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 "iocore/net/NetProcessor.h"
#include "P_Connection.h"
#include "Server.h"
#include "records/RecCore.h"
#include "iocore/eventsystem/UnixSocket.h"
#include "tsutil/DbgCtl.h"
#include "tscore/Diags.h"
#include "tscore/ink_assert.h"
#include "tscore/ink_config.h"
#include "tscore/ink_defs.h"
#include "tscore/ink_inet.h"
#include "tscore/ink_platform.h"
#include "tscore/ink_sock.h"
#include <sys/socket.h>
#if TS_USE_HWLOC
#include <hwloc.h>
#endif
#ifdef SO_ACCEPTFILTER
#include <sys/param.h>
#include <sys/linker.h>
#endif
namespace
{
DbgCtl dbg_ctl_connection{"connection"};
DbgCtl dbg_ctl_http_tproxy{"http_tproxy"};
DbgCtl dbg_ctl_iocore_net_server{"iocore_net_server"};
DbgCtl dbg_ctl_iocore_thread{"iocore_thread"};
DbgCtl dbg_ctl_proxyprotocol{"proxyprotocol"};
} // end anonymous namespace
static int get_listen_backlog();
static int add_http_filter(int fd);
int
Server::accept(Connection *c)
{
int res = 0;
socklen_t sz = sizeof(c->addr);
res = sock.accept4(&c->addr.sa, &sz, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (res < 0) {
return res;
}
c->sock = UnixSocket{res};
if (dbg_ctl_iocore_net_server.on()) {
ip_port_text_buffer ipb1, ipb2;
DbgPrint(dbg_ctl_iocore_net_server, "Connection accepted [Server]. %s -> %s", ats_ip_nptop(&c->addr, ipb2, sizeof(ipb2)),
ats_ip_nptop(&addr, ipb1, sizeof(ipb1)));
}
return 0;
}
int
Server::close()
{
return sock.close();
}
int
Server::listen(bool non_blocking, const NetProcessor::AcceptOptions &opt)
{
ink_assert(!sock.is_ok());
int res = 0;
socklen_t namelen;
int prot = IPPROTO_TCP;
if (ats_is_unix(&accept_addr)) {
prot = 0;
ats_ip_copy(&addr, &accept_addr);
unlink(accept_addr.sun.sun_path);
} else if (!ats_is_ip(&accept_addr)) {
ats_ip4_set(&addr, INADDR_ANY, 0);
} else {
ats_ip_copy(&addr, &accept_addr);
}
if (opt.f_mptcp) {
Dbg(dbg_ctl_connection, "Define socket with MPTCP");
prot = IPPROTO_MPTCP;
}
sock = UnixSocket{addr.sa.sa_family, SOCK_STREAM, prot};
if (!sock.is_ok()) {
goto Lerror;
}
res = setup_fd_for_listen(non_blocking, opt);
if (res < 0) {
goto Lerror;
}
if ((res = sock.bind(&addr.sa, ats_ip_size(&addr.sa))) < 0) {
goto Lerror;
}
if (ats_is_unix(&accept_addr)) {
if (chmod(accept_addr.sun.sun_path, opt.unix_perm) < 0) {
goto Lerror;
}
if (opt.unix_uid != static_cast<uid_t>(-1) || opt.unix_gid != static_cast<gid_t>(-1)) {
if (chown(accept_addr.sun.sun_path, opt.unix_uid, opt.unix_gid) < 0) {
goto Lerror;
}
}
}
if ((res = safe_listen(sock.get_fd(), get_listen_backlog())) < 0) {
goto Lerror;
}
res = setup_fd_after_listen(opt);
if (res < 0) {
goto Lerror;
}
// Original just did this on port == 0.
namelen = sizeof(addr);
if ((res = sock.getsockname(&addr.sa, &namelen))) {
goto Lerror;
}
return 0;
Lerror:
if (sock.is_ok()) {
close();
}
Fatal("Could not bind or listen to port %d, mptcp enabled: %d (error: %d) %s %d", ats_ip_port_host_order(&addr),
prot == IPPROTO_MPTCP, errno, strerror(errno), res);
return res;
}
int
get_listen_backlog()
{
int listen_backlog;
listen_backlog = RecGetRecordInt("proxy.config.net.listen_backlog").value_or(0);
return (0 < listen_backlog && listen_backlog <= 65535) ? listen_backlog : ats_tcp_somaxconn();
}
int
Server::setup_fd_for_listen(bool non_blocking, const NetProcessor::AcceptOptions &opt)
{
int res = 0;
int listen_per_thread = 0;
ink_assert(sock.is_ok());
if (opt.defer_accept > 0) {
http_accept_filter = true;
add_http_filter(sock.get_fd());
}
if (opt.recv_bufsize) {
if (sock.set_rcvbuf_size(opt.recv_bufsize)) {
// Round down until success
int rbufsz = ROUNDUP(opt.recv_bufsize, 1024);
while (rbufsz) {
if (sock.set_rcvbuf_size(rbufsz)) {
rbufsz -= 1024;
} else {
break;
}
}
}
}
if (opt.send_bufsize) {
if (sock.set_sndbuf_size(opt.send_bufsize)) {
// Round down until success
int sbufsz = ROUNDUP(opt.send_bufsize, 1024);
while (sbufsz) {
if (sock.set_sndbuf_size(sbufsz)) {
sbufsz -= 1024;
} else {
break;
}
}
}
}
if (safe_fcntl(sock.get_fd(), F_SETFD, FD_CLOEXEC) < 0) {
goto Lerror;
}
{
struct linger l;
l.l_onoff = 0;
l.l_linger = 0;
if ((opt.sockopt_flags & NetVCOptions::SOCK_OPT_LINGER_ON) &&
safe_setsockopt(sock.get_fd(), SOL_SOCKET, SO_LINGER, reinterpret_cast<char *>(&l), sizeof(l)) < 0) {
goto Lerror;
}
}
if (ats_is_ip6(&addr) && sock.enable_option(IPPROTO_IPV6, IPV6_V6ONLY) < 0) {
goto Lerror;
}
if (sock.enable_option(SOL_SOCKET, SO_REUSEADDR) < 0) {
goto Lerror;
}
listen_per_thread = RecGetRecordInt("proxy.config.exec_thread.listen").value_or(0);
if (listen_per_thread == 1 && opt.ip_family != AF_UNIX) {
if (sock.enable_option(SOL_SOCKET, SO_REUSEPORT) < 0) {
goto Lerror;
}
#ifdef SO_REUSEPORT_LB
if (sock.enable_option(SOL_SOCKET, SO_REUSEPORT_LB) < 0) {
goto Lerror;
}
#endif
}
#ifdef SO_INCOMING_CPU
if (opt.sockopt_flags & NetVCOptions::SOCK_OPT_INCOMING_CPU) {
int cpu = 0;
EThread *ethread = this_ethread();
#if TS_USE_HWLOC
cpu = ethread->hwloc_obj->os_index;
#else
cpu = ethread->id;
#endif
if (safe_setsockopt(sock.get_fd(), SOL_SOCKET, SO_INCOMING_CPU, &cpu, sizeof(cpu)) < 0) {
goto Lerror;
}
Dbg(dbg_ctl_iocore_thread, "SO_INCOMING_CPU - fd=%d cpu=%d", sock.get_fd(), cpu);
}
#endif
if ((opt.sockopt_flags & NetVCOptions::SOCK_OPT_NO_DELAY) && sock.enable_option(IPPROTO_TCP, TCP_NODELAY) < 0) {
goto Lerror;
}
// enables 2 hour inactivity probes, also may fix IRIX FIN_WAIT_2 leak
if ((opt.sockopt_flags & NetVCOptions::SOCK_OPT_KEEP_ALIVE) && sock.enable_option(SOL_SOCKET, SO_KEEPALIVE) < 0) {
goto Lerror;
}
#ifdef TCP_FASTOPEN
if (opt.sockopt_flags & NetVCOptions::SOCK_OPT_TCP_FAST_OPEN && opt.ip_family != AF_UNIX) {
if (safe_setsockopt(sock.get_fd(), IPPROTO_TCP, TCP_FASTOPEN, &opt.tfo_queue_length, sizeof(int))) {
// EOPNOTSUPP also checked for general safeguarding of unsupported operations of socket functions
if (opt.f_mptcp && (errno == ENOPROTOOPT || errno == EOPNOTSUPP)) {
Warning("[Server::listen] TCP_FASTOPEN socket option not valid on MPTCP socket level");
} else {
goto Lerror;
}
}
}
#endif
if (opt.f_inbound_transparent) {
#if TS_USE_TPROXY
Dbg(dbg_ctl_http_tproxy, "Listen port inbound transparency enabled.");
if (sock.enable_option(SOL_IP, TS_IP_TRANSPARENT) < 0) {
Fatal("[Server::listen] Unable to set transparent socket option [%d] %s\n", errno, strerror(errno));
}
#else
Error("[Server::listen] Transparency requested but TPROXY not configured\n");
#endif
}
if (opt.f_proxy_protocol) {
if (opt.f_proxy_protocol_client_src) {
Dbg(dbg_ctl_proxyprotocol, "Proxy Protocol enabled(Using src IP as remote IP).");
} else {
Dbg(dbg_ctl_proxyprotocol, "Proxy Protocol enabled.");
}
}
#if defined(TCP_MAXSEG)
if (NetProcessor::accept_mss > 0 && opt.ip_family != AF_UNIX) {
if (opt.f_mptcp) {
Warning("[Server::listen] TCP_MAXSEG socket option not valid on MPTCP socket level");
} else if (safe_setsockopt(sock.get_fd(), IPPROTO_TCP, TCP_MAXSEG, reinterpret_cast<char *>(&NetProcessor::accept_mss),
sizeof(int)) < 0) {
goto Lerror;
}
}
#endif
#ifdef TCP_DEFER_ACCEPT
// set tcp defer accept timeout if it is configured, this will not trigger an accept until there is
// data on the socket ready to be read
if (opt.defer_accept > 0 && opt.ip_family != AF_UNIX) {
if (setsockopt(sock.get_fd(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &opt.defer_accept, sizeof(int)) < 0) {
// FIXME: should we go to the error
// goto error;
Error("[Server::listen] Defer accept is configured but set failed: %d", errno);
}
}
#endif
if (non_blocking) {
if (sock.set_nonblocking() < 0) {
goto Lerror;
}
}
return 0;
Lerror:
res = -errno;
// coverity[check_after_sink]
if (sock.is_ok()) {
close();
}
return res;
}
int
add_http_filter([[maybe_unused]] int fd)
{
int err = -1;
#if defined(SOL_FILTER) && defined(FIL_ATTACH)
err = setsockopt(fd, SOL_FILTER, FIL_ATTACH, "httpfilt", 9);
#endif
return err;
}
int
Server::setup_fd_after_listen([[maybe_unused]] const NetProcessor::AcceptOptions &opt)
{
#ifdef SO_ACCEPTFILTER
// SO_ACCEPTFILTER needs to be set **after** listen
if (opt.defer_accept > 0) {
int file_id = kldfind("accf_data");
struct kld_file_stat stat;
stat.version = sizeof(stat);
if (kldstat(file_id, &stat) < 0) {
Error("[Server::listen] Ignored defer_accept config. Because accf_data module is not loaded errno=%d", errno);
} else {
struct accept_filter_arg afa;
bzero(&afa, sizeof(afa));
strcpy(afa.af_name, "dataready");
if (setsockopt(this->sock.get_fd(), SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) {
Error("[Server::listen] Defer accept is configured but set failed: %d", errno);
return -errno;
}
}
}
#endif
return 0;
}