nghttpx: Support multiple frontend addresses

This commit allows nghttpx to listen to multiple address and port pair
by specifying -f option multiple times.
This commit is contained in:
Tatsuhiro Tsujikawa 2016-01-31 19:41:56 +09:00
parent 1d99b425ca
commit aa07fe7fa6
18 changed files with 515 additions and 327 deletions

View File

@ -92,22 +92,30 @@ using namespace nghttp2;
namespace shrpx {
// Environment variables to tell new binary the listening socket's
// file descriptors. They are not close-on-exec.
// Deprecated: Environment variables to tell new binary the listening
// socket's file descriptors. They are not close-on-exec.
#define ENV_LISTENER4_FD "NGHTTPX_LISTENER4_FD"
#define ENV_LISTENER6_FD "NGHTTPX_LISTENER6_FD"
// Environment variable to tell new binary the port number the current
// binary is listening to.
// Deprecated: Environment variable to tell new binary the port number
// the current binary is listening to.
#define ENV_PORT "NGHTTPX_PORT"
// Environment variable to tell new binary the listening socket's file
// descriptor if frontend listens UNIX domain socket.
// Deprecated: Environment variable to tell new binary the listening
// socket's file descriptor if frontend listens UNIX domain socket.
#define ENV_UNIX_FD "NGHTTP2_UNIX_FD"
// Environment variable to tell new binary the UNIX domain socket
// path.
// Deprecated: Environment variable to tell new binary the UNIX domain
// socket path.
#define ENV_UNIX_PATH "NGHTTP2_UNIX_PATH"
// Prefix of environment variables to tell new binary the listening
// socket's file descriptor. They are not close-on-exec. For TCP
// socket, the value must be comma separated 2 parameters: tcp,<FD>.
// <FD> is file descriptor. For UNIX domain socket, the value must be
// comma separated 3 parameters: unix,<FD>,<PATH>. <FD> is file
// descriptor. <PATH> is a path to UNIX domain socket.
constexpr char ENV_ACCEPT_PREFIX[] = "NGHTTPX_ACCEPT_";
#ifndef _KERNEL_FASTOPEN
#define _KERNEL_FASTOPEN
// conditional define for TCP_FASTOPEN mostly on ubuntu
@ -122,18 +130,8 @@ namespace shrpx {
#endif
struct SignalServer {
SignalServer()
: ipc_fd{{-1, -1}},
server_fd(-1),
server_fd6(-1),
worker_process_pid(-1) {}
SignalServer() : ipc_fd{{-1, -1}}, worker_process_pid(-1) {}
~SignalServer() {
if (server_fd6 != -1) {
close(server_fd6);
}
if (server_fd != -1) {
close(server_fd);
}
if (ipc_fd[0] != -1) {
close(ipc_fd[0]);
}
@ -144,10 +142,6 @@ struct SignalServer {
}
std::array<int, 2> ipc_fd;
// server socket, either IPv4 or UNIX domain
int server_fd;
// server socket IPv6
int server_fd6;
pid_t worker_process_pid;
};
@ -295,42 +289,35 @@ void exec_binary(SignalServer *ssv) {
size_t envlen = 0;
for (char **p = environ; *p; ++p, ++envlen)
;
// 3 for missing (fd4, fd6 and port) or (unix fd and unix path)
auto envp = make_unique<char *[]>(envlen + 3 + 1);
size_t envidx = 0;
std::string fd, fd6, path, port;
auto &listenerconf = get_config()->conn.listener;
if (listenerconf.host_unix) {
fd = ENV_UNIX_FD "=";
fd += util::utos(ssv->server_fd);
envp[envidx++] = &fd[0];
auto envp = make_unique<char *[]>(envlen + listenerconf.addrs.size() + 1);
size_t envidx = 0;
path = ENV_UNIX_PATH "=";
path += listenerconf.host.get();
envp[envidx++] = &path[0];
} else {
if (ssv->server_fd) {
fd = ENV_LISTENER4_FD "=";
fd += util::utos(ssv->server_fd);
envp[envidx++] = &fd[0];
std::vector<ImmutableString> fd_envs;
for (size_t i = 0; i < listenerconf.addrs.size(); ++i) {
auto &addr = listenerconf.addrs[i];
std::string s = ENV_ACCEPT_PREFIX;
s += util::utos(i + 1);
s += '=';
if (addr.host_unix) {
s += "unix,";
s += util::utos(addr.fd);
s += ',';
s += addr.host;
} else {
s += "tcp,";
s += util::utos(addr.fd);
}
if (ssv->server_fd6) {
fd6 = ENV_LISTENER6_FD "=";
fd6 += util::utos(ssv->server_fd6);
envp[envidx++] = &fd6[0];
}
port = ENV_PORT "=";
port += util::utos(listenerconf.port);
envp[envidx++] = &port[0];
fd_envs.emplace_back(s);
envp[envidx++] = const_cast<char *>(fd_envs.back().c_str());
}
for (size_t i = 0; i < envlen; ++i) {
if (util::starts_with(environ[i], ENV_LISTENER4_FD) ||
if (util::starts_with(environ[i], ENV_ACCEPT_PREFIX) ||
util::starts_with(environ[i], ENV_LISTENER4_FD) ||
util::starts_with(environ[i], ENV_LISTENER6_FD) ||
util::starts_with(environ[i], ENV_PORT) ||
util::starts_with(environ[i], ENV_UNIX_FD) ||
@ -432,38 +419,45 @@ void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
}
} // namespace
struct InheritedAddr {
// IP address if TCP socket. Otherwise, UNIX domain socket path.
ImmutableString host;
uint16_t port;
// true if UNIX domain socket path
bool host_unix;
int fd;
bool used;
};
namespace {
int create_unix_domain_server_socket() {
auto &listenerconf = get_config()->conn.listener;
int create_unix_domain_server_socket(FrontendAddr &faddr,
std::vector<InheritedAddr> &iaddrs) {
auto found = std::find_if(
std::begin(iaddrs), std::end(iaddrs), [&faddr](const InheritedAddr &ia) {
return !ia.used && ia.host_unix && ia.host == faddr.host;
});
auto path = listenerconf.host.get();
auto pathlen = strlen(path);
{
auto envfd = getenv(ENV_UNIX_FD);
auto envpath = getenv(ENV_UNIX_PATH);
if (envfd && envpath) {
auto fd = strtoul(envfd, nullptr, 10);
if (found != std::end(iaddrs)) {
LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host;
(*found).used = true;
faddr.fd = (*found).fd;
faddr.hostport = "localhost";
if (util::streq(envpath, path)) {
LOG(NOTICE) << "Listening on UNIX domain socket " << path;
return fd;
}
LOG(WARN) << "UNIX domain socket path was changed between old binary ("
<< envpath << ") and new binary (" << path << ")";
close(fd);
}
return 0;
}
#ifdef SOCK_NONBLOCK
auto fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd == -1) {
auto error = errno;
LOG(WARN) << "socket() syscall failed: " << strerror(error);
return -1;
}
#else // !SOCK_NONBLOCK
auto fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
auto error = errno;
LOG(WARN) << "socket() syscall failed: " << strerror(error);
return -1;
}
util::make_socket_nonblocking(fd);
@ -471,115 +465,122 @@ int create_unix_domain_server_socket() {
int val = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
auto error = errno;
LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
<< strerror(error);
close(fd);
return -1;
}
sockaddr_union addr;
addr.un.sun_family = AF_UNIX;
if (pathlen + 1 > sizeof(addr.un.sun_path)) {
LOG(FATAL) << "UNIX domain socket path " << path << " is too long > "
if (faddr.host.size() + 1 > sizeof(addr.un.sun_path)) {
LOG(FATAL) << "UNIX domain socket path " << faddr.host << " is too long > "
<< sizeof(addr.un.sun_path);
close(fd);
return -1;
}
// copy path including terminal NULL
std::copy_n(path, pathlen + 1, addr.un.sun_path);
std::copy_n(faddr.host.c_str(), faddr.host.size() + 1, addr.un.sun_path);
// unlink (remove) already existing UNIX domain socket path
unlink(path);
unlink(faddr.host.c_str());
if (bind(fd, &addr.sa, sizeof(addr.un)) != 0) {
auto error = errno;
LOG(FATAL) << "Failed to bind UNIX domain socket, error=" << error;
LOG(FATAL) << "Failed to bind UNIX domain socket: " << strerror(error);
close(fd);
return -1;
}
auto &listenerconf = get_config()->conn.listener;
if (listen(fd, listenerconf.backlog) != 0) {
auto error = errno;
LOG(FATAL) << "Failed to listen to UNIX domain socket, error=" << error;
LOG(FATAL) << "Failed to listen to UNIX domain socket: " << strerror(error);
close(fd);
return -1;
}
LOG(NOTICE) << "Listening on UNIX domain socket " << path;
LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host;
return fd;
faddr.fd = fd;
faddr.hostport = "localhost";
return 0;
}
} // namespace
namespace {
int create_tcp_server_socket(int family) {
auto &listenerconf = get_config()->conn.listener;
{
auto envfd =
getenv(family == AF_INET ? ENV_LISTENER4_FD : ENV_LISTENER6_FD);
auto envport = getenv(ENV_PORT);
if (envfd && envport) {
auto fd = strtoul(envfd, nullptr, 10);
auto port = strtoul(envport, nullptr, 10);
// Only do this iff NGHTTPX_PORT == get_config()->port.
// Otherwise, close fd, and create server socket as usual.
if (port == listenerconf.port) {
LOG(NOTICE) << "Listening on port " << listenerconf.port;
return fd;
}
LOG(WARN) << "Port was changed between old binary (" << port
<< ") and new binary (" << listenerconf.port << ")";
close(fd);
}
}
int create_tcp_server_socket(FrontendAddr &faddr,
std::vector<InheritedAddr> &iaddrs) {
int fd = -1;
int rv;
auto service = util::utos(listenerconf.port);
auto &listenerconf = get_config()->conn.listener;
auto service = util::utos(faddr.port);
addrinfo hints{};
hints.ai_family = family;
hints.ai_family = faddr.family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
#ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG;
#endif // AI_ADDRCONFIG
auto node = strcmp("*", listenerconf.host.get()) == 0
? nullptr
: listenerconf.host.get();
auto node = faddr.host == "*" ? nullptr : faddr.host.c_str();
addrinfo *res, *rp;
rv = getaddrinfo(node, service.c_str(), &hints, &res);
if (rv != 0) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Unable to get IPv" << (family == AF_INET ? "4" : "6")
<< " address for " << listenerconf.host.get() << ": "
<< gai_strerror(rv);
LOG(INFO) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6")
<< " address for " << faddr.host << ", port " << faddr.port
<< ": " << gai_strerror(rv);
}
return -1;
}
auto res_d = defer(freeaddrinfo, res);
std::array<char, NI_MAXHOST> host;
for (rp = res; rp; rp = rp->ai_next) {
rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(),
nullptr, 0, NI_NUMERICHOST);
if (rv != 0) {
LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv);
continue;
}
auto found = std::find_if(std::begin(iaddrs), std::end(iaddrs),
[&host, &faddr](const InheritedAddr &ia) {
return !ia.used && !ia.host_unix &&
ia.host == host.data() &&
ia.port == faddr.port;
});
if (found != std::end(iaddrs)) {
(*found).used = true;
fd = (*found).fd;
break;
}
#ifdef SOCK_NONBLOCK
fd =
socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
if (fd == -1) {
auto error = errno;
LOG(WARN) << "socket() syscall failed, error=" << error;
LOG(WARN) << "socket() syscall failed: " << strerror(error);
continue;
}
#else // !SOCK_NONBLOCK
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) {
auto error = errno;
LOG(WARN) << "socket() syscall failed, error=" << error;
LOG(WARN) << "socket() syscall failed: " << strerror(error);
continue;
}
util::make_socket_nonblocking(fd);
@ -588,21 +589,19 @@ int create_tcp_server_socket(int family) {
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
auto error = errno;
LOG(WARN)
<< "Failed to set SO_REUSEADDR option to listener socket, error="
<< error;
LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
<< strerror(error);
close(fd);
continue;
}
#ifdef IPV6_V6ONLY
if (family == AF_INET6) {
if (faddr.family == AF_INET6) {
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
auto error = errno;
LOG(WARN)
<< "Failed to set IPV6_V6ONLY option to listener socket, error="
<< error;
LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: "
<< strerror(error);
close(fd);
continue;
}
@ -613,7 +612,9 @@ int create_tcp_server_socket(int family) {
val = 3;
if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket";
auto error = errno;
LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket: "
<< strerror(error);
}
#endif // TCP_DEFER_ACCEPT
@ -622,7 +623,7 @@ int create_tcp_server_socket(int family) {
// ports will fail with permission denied error.
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
auto error = errno;
LOG(WARN) << "bind() syscall failed, error=" << error;
LOG(WARN) << "bind() syscall failed: " << strerror(error);
close(fd);
continue;
}
@ -631,13 +632,15 @@ int create_tcp_server_socket(int family) {
val = listenerconf.fastopen;
if (setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
LOG(WARN) << "Failed to set TCP_FASTOPEN option to listener socket";
auto error = errno;
LOG(WARN) << "Failed to set TCP_FASTOPEN option to listener socket: "
<< strerror(error);
}
}
if (listen(fd, listenerconf.backlog) == -1) {
auto error = errno;
LOG(WARN) << "listen() syscall failed, error=" << error;
LOG(WARN) << "listen() syscall failed: " << strerror(error);
close(fd);
continue;
}
@ -646,27 +649,200 @@ int create_tcp_server_socket(int family) {
}
if (!rp) {
LOG(WARN) << "Listening " << (family == AF_INET ? "IPv4" : "IPv6")
LOG(WARN) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6")
<< " socket failed";
return -1;
}
char host[NI_MAXHOST];
rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), nullptr, 0,
NI_NUMERICHOST);
faddr.fd = fd;
faddr.hostport = util::make_hostport(host.data(), faddr.port);
if (rv != 0) {
LOG(WARN) << gai_strerror(rv);
LOG(NOTICE) << "Listening on " << faddr.hostport;
close(fd);
return 0;
}
} // namespace
return -1;
namespace {
int create_acceptor_socket() {
int rv;
auto &listenerconf = mod_config()->conn.listener;
std::vector<InheritedAddr> iaddrs;
{
// Upgrade from 1.7.0 or earlier
auto portenv = getenv(ENV_PORT);
if (portenv) {
size_t i = 1;
for (auto env_name : {ENV_LISTENER4_FD, ENV_LISTENER6_FD}) {
auto fdenv = getenv(env_name);
if (fdenv) {
std::string name = ENV_ACCEPT_PREFIX;
name += util::utos(i);
std::string value = "tcp,";
value += fdenv;
setenv(name.c_str(), value.c_str(), 0);
++i;
}
}
} else {
auto pathenv = getenv(ENV_UNIX_PATH);
auto fdenv = getenv(ENV_UNIX_FD);
if (pathenv && fdenv) {
std::string name = ENV_ACCEPT_PREFIX;
name += '1';
std::string value = "unix,";
value += fdenv;
value += ',';
value += pathenv;
setenv(name.c_str(), value.c_str(), 0);
}
}
}
LOG(NOTICE) << "Listening on " << host << ", port " << listenerconf.port;
for (size_t i = 1;; ++i) {
std::string name = ENV_ACCEPT_PREFIX;
name += util::utos(i);
auto env = getenv(name.c_str());
if (!env) {
break;
}
return fd;
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Read env " << name << "=" << env;
}
auto end_type = strchr(env, ',');
if (!end_type) {
continue;
}
auto type = StringRef(env, end_type);
auto value = end_type + 1;
if (type == "unix") {
auto endfd = strchr(value, ',');
if (!endfd) {
continue;
}
auto fd = util::parse_uint(reinterpret_cast<const uint8_t *>(value),
endfd - value);
if (fd == -1) {
LOG(WARN) << "Could not parse file descriptor from "
<< std::string(value, endfd - value);
continue;
}
auto path = endfd + 1;
if (strlen(path) == 0) {
LOG(WARN) << "Empty UNIX domain socket path (fd=" << fd << ")";
close(fd);
continue;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Inherit UNIX domain socket fd=" << fd
<< ", path=" << path;
}
InheritedAddr addr{};
addr.host = path;
addr.host_unix = true;
addr.fd = static_cast<int>(fd);
iaddrs.push_back(std::move(addr));
}
if (type == "tcp") {
auto fd = util::parse_uint(value);
if (fd == -1) {
LOG(WARN) << "Could not parse file descriptor from " << value;
continue;
}
sockaddr_union su;
socklen_t salen = sizeof(su);
if (getsockname(fd, &su.sa, &salen) != 0) {
auto error = errno;
LOG(WARN) << "getsockname() syscall failed (fd=" << fd
<< "): " << strerror(error);
close(fd);
continue;
}
uint16_t port;
switch (su.storage.ss_family) {
case AF_INET:
port = ntohs(su.in.sin_port);
break;
case AF_INET6:
port = ntohs(su.in6.sin6_port);
break;
default:
close(fd);
continue;
}
std::array<char, NI_MAXHOST> host;
rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0,
NI_NUMERICHOST);
if (rv != 0) {
LOG(WARN) << "getnameinfo() failed (fd=" << fd
<< "): " << gai_strerror(rv);
close(fd);
continue;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Inherit TCP socket fd=" << fd
<< ", address=" << host.data() << ", port=" << port;
}
InheritedAddr addr{};
addr.host = host.data();
addr.port = static_cast<uint16_t>(port);
addr.fd = static_cast<int>(fd);
iaddrs.push_back(std::move(addr));
continue;
}
}
for (auto &addr : listenerconf.addrs) {
if (addr.host_unix) {
if (create_unix_domain_server_socket(addr, iaddrs) != 0) {
return -1;
}
if (get_config()->uid != 0) {
// fd is not associated to inode, so we cannot use fchown(2)
// here. https://lkml.org/lkml/2004/11/1/84
if (chown_to_running_user(addr.host.c_str()) == -1) {
auto error = errno;
LOG(WARN) << "Changing owner of UNIX domain socket " << addr.host
<< " failed: " << strerror(error);
}
}
continue;
}
if (create_tcp_server_socket(addr, iaddrs) != 0) {
return -1;
}
}
for (auto &ia : iaddrs) {
if (ia.used) {
continue;
}
close(ia.fd);
}
return 0;
}
} // namespace
@ -680,19 +856,6 @@ int call_daemon() {
}
} // namespace
namespace {
void close_env_fd(std::initializer_list<const char *> envnames) {
for (auto envname : envnames) {
auto envfd = getenv(envname);
if (!envfd) {
continue;
}
auto fd = strtol(envfd, nullptr, 10);
close(fd);
}
}
} // namespace
namespace {
pid_t fork_worker_process(SignalServer *ssv) {
int rv;
@ -722,7 +885,7 @@ pid_t fork_worker_process(SignalServer *ssv) {
}
close(ssv->ipc_fd[1]);
WorkerProcessConfig wpconf{ssv->ipc_fd[0], ssv->server_fd, ssv->server_fd6};
WorkerProcessConfig wpconf{ssv->ipc_fd[0]};
rv = worker_process_event_loop(&wpconf);
if (rv != 0) {
LOG(FATAL) << "Worker process returned error";
@ -803,40 +966,8 @@ int event_loop() {
util::make_socket_closeonexec(fd);
}
auto &listenerconf = get_config()->conn.listener;
if (listenerconf.host_unix) {
close_env_fd({ENV_LISTENER4_FD, ENV_LISTENER6_FD});
auto fd = create_unix_domain_server_socket();
if (fd == -1) {
LOG(FATAL) << "Failed to listen on UNIX domain socket "
<< listenerconf.host.get();
return -1;
}
ssv.server_fd = fd;
if (get_config()->uid != 0) {
// fd is not associated to inode, so we cannot use fchown(2)
// here. https://lkml.org/lkml/2004/11/1/84
if (chown_to_running_user(listenerconf.host.get()) == -1) {
auto error = errno;
LOG(WARN) << "Changing owner of UNIX domain socket "
<< listenerconf.host.get() << " failed: " << strerror(error);
}
}
} else {
close_env_fd({ENV_UNIX_FD});
auto fd6 = create_tcp_server_socket(AF_INET6);
auto fd4 = create_tcp_server_socket(AF_INET);
if (fd6 == -1 && fd4 == -1) {
LOG(FATAL) << "Failed to listen on address " << listenerconf.host.get()
<< ", port " << listenerconf.port;
return -1;
}
ssv.server_fd = fd4;
ssv.server_fd6 = fd6;
if (create_acceptor_socket() != 0) {
return -1;
}
auto loop = EV_DEFAULT;
@ -986,8 +1117,6 @@ void fill_default_config() {
{
auto &listenerconf = connconf.listener;
{
listenerconf.host = strcopy("*");
listenerconf.port = 3000;
// Default accept() backlog
listenerconf.backlog = 512;
listenerconf.timeout.sleep = 30_s;
@ -1120,9 +1249,10 @@ Connections:
Set frontend host and port. If <HOST> is '*', it
assumes all addresses including both IPv4 and IPv6.
UNIX domain socket can be specified by prefixing path
name with "unix:" (e.g., unix:/var/run/nghttpx.sock)
Default: )" << get_config()->conn.listener.host.get() << ","
<< get_config()->conn.listener.port << R"(
name with "unix:" (e.g., unix:/var/run/nghttpx.sock).
This option can be used multiple times to listen to
multiple addresses.
Default: *,3000
--backlog=<N>
Set listen backlog size.
Default: )" << get_config()->conn.listener.backlog << R"(
@ -1838,6 +1968,13 @@ void process_options(
auto &upstreamconf = mod_config()->conn.upstream;
auto &downstreamconf = mod_config()->conn.downstream;
if (listenerconf.addrs.empty()) {
FrontendAddr addr;
addr.host = "*";
addr.port = 3000;
listenerconf.addrs.push_back(std::move(addr));
}
if (downstreamconf.ipv4 && downstreamconf.ipv6) {
LOG(FATAL) << "--backend-ipv4 and --backend-ipv6 cannot be used at the "
<< "same time.";
@ -1949,8 +2086,7 @@ void process_options(
// for AF_UNIX socket, we use "localhost" as host for backend
// hostport. This is used as Host header field to backend and
// not going to be passed to any syscalls.
addr.hostport = ImmutableString(
util::make_hostport("localhost", listenerconf.port));
addr.hostport = "localhost";
auto path = addr.host.c_str();
auto pathlen = addr.host.size();

View File

@ -45,16 +45,16 @@ void acceptcb(struct ev_loop *loop, ev_io *w, int revent) {
}
} // namespace
AcceptHandler::AcceptHandler(int fd, ConnectionHandler *h)
: conn_hnr_(h), fd_(fd) {
ev_io_init(&wev_, acceptcb, fd_, EV_READ);
AcceptHandler::AcceptHandler(const FrontendAddr *faddr, ConnectionHandler *h)
: conn_hnr_(h), faddr_(faddr) {
ev_io_init(&wev_, acceptcb, faddr_->fd, EV_READ);
wev_.data = this;
ev_io_start(conn_hnr_->get_loop(), &wev_);
}
AcceptHandler::~AcceptHandler() {
ev_io_stop(conn_hnr_->get_loop(), &wev_);
close(fd_);
close(faddr_->fd);
}
void AcceptHandler::accept_connection() {
@ -63,10 +63,10 @@ void AcceptHandler::accept_connection() {
socklen_t addrlen = sizeof(sockaddr);
#ifdef HAVE_ACCEPT4
auto cfd =
accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#else // !HAVE_ACCEPT4
auto cfd = accept(fd_, &sockaddr.sa, &addrlen);
auto cfd = accept4(faddr_->fd, &sockaddr.sa, &addrlen,
SOCK_NONBLOCK | SOCK_CLOEXEC);
#else // !HAVE_ACCEPT4
auto cfd = accept(faddr_->fd, &sockaddr.sa, &addrlen);
#endif // !HAVE_ACCEPT4
if (cfd == -1) {
@ -101,7 +101,7 @@ void AcceptHandler::accept_connection() {
util::make_socket_nodelay(cfd);
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen);
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen, faddr_);
}
}
@ -109,6 +109,6 @@ void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); }
void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); }
int AcceptHandler::get_fd() const { return fd_; }
int AcceptHandler::get_fd() const { return faddr_->fd; }
} // namespace shrpx

View File

@ -32,10 +32,11 @@
namespace shrpx {
class ConnectionHandler;
struct FrontendAddr;
class AcceptHandler {
public:
AcceptHandler(int fd, ConnectionHandler *h);
AcceptHandler(const FrontendAddr *faddr, ConnectionHandler *h);
~AcceptHandler();
void accept_connection();
void enable();
@ -45,7 +46,7 @@ public:
private:
ev_io wev_;
ConnectionHandler *conn_hnr_;
int fd_;
const FrontendAddr *faddr_;
};
} // namespace shrpx

View File

@ -377,7 +377,8 @@ int ClientHandler::upstream_http1_connhd_read() {
}
ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
const char *ipaddr, const char *port)
const char *ipaddr, const char *port,
const FrontendAddr *faddr)
: conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
get_config()->conn.upstream.timeout.write,
get_config()->conn.upstream.timeout.read,
@ -392,6 +393,7 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
: nullptr),
ipaddr_(ipaddr),
port_(port),
faddr_(faddr),
worker_(worker),
left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN),
should_close_after_write_(false) {
@ -851,8 +853,8 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
std::chrono::high_resolution_clock::now(), // request_end_time
req.http_major, req.http_minor, resp.http_status,
downstream->response_sent_body_length, StringRef(port_),
get_config()->conn.listener.port, get_config()->pid,
downstream->response_sent_body_length, StringRef(port_), faddr_->port,
get_config()->pid,
});
}
@ -875,7 +877,7 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
highres_now, // request_end_time
major, minor, // major, minor
status, body_bytes_sent, StringRef(port_),
get_config()->conn.listener.port, get_config()->pid,
faddr_->port, get_config()->pid,
});
}
@ -1116,51 +1118,14 @@ int ClientHandler::proxy_protocol_read() {
return on_proxy_protocol_finish();
}
const std::string &ClientHandler::get_forwarded_by() {
StringRef ClientHandler::get_forwarded_by() {
auto &fwdconf = get_config()->http.forwarded;
if (fwdconf.by_node_type == FORWARDED_NODE_OBFUSCATED) {
return fwdconf.by_obfuscated;
}
if (!local_hostport_.empty()) {
return local_hostport_;
return StringRef(fwdconf.by_obfuscated);
}
auto &listenerconf = get_config()->conn.listener;
// For UNIX domain socket listener, just return empty string.
if (listenerconf.host_unix) {
return local_hostport_;
}
int rv;
sockaddr_union su;
socklen_t addrlen = sizeof(su);
rv = getsockname(conn_.fd, &su.sa, &addrlen);
if (rv != 0) {
return local_hostport_;
}
char host[NI_MAXHOST];
rv = getnameinfo(&su.sa, addrlen, host, sizeof(host), nullptr, 0,
NI_NUMERICHOST);
if (rv != 0) {
return local_hostport_;
}
if (su.storage.ss_family == AF_INET6) {
local_hostport_ = "[";
local_hostport_ += host;
local_hostport_ += "]:";
} else {
local_hostport_ = host;
local_hostport_ += ':';
}
local_hostport_ += util::utos(listenerconf.port);
return local_hostport_;
return StringRef(faddr_->hostport);
}
const std::string &ClientHandler::get_forwarded_for() const {
@ -1168,10 +1133,6 @@ const std::string &ClientHandler::get_forwarded_for() const {
return forwarded_for_obfuscated_;
}
if (get_config()->conn.listener.host_unix) {
return EMPTY_STRING;
}
return ipaddr_;
}

View File

@ -53,7 +53,7 @@ struct WorkerStat;
class ClientHandler {
public:
ClientHandler(Worker *worker, int fd, SSL *ssl, const char *ipaddr,
const char *port);
const char *port, const FrontendAddr *faddr);
~ClientHandler();
int noop();
@ -136,7 +136,7 @@ public:
// Returns string suitable for use in "by" parameter of Forwarded
// header field.
const std::string &get_forwarded_by();
StringRef get_forwarded_by();
// Returns string suitable for use in "for" parameter of Forwarded
// header field.
const std::string &get_forwarded_for() const;
@ -152,13 +152,13 @@ private:
std::string port_;
// The ALPN identifier negotiated for this connection.
std::string alpn_;
// Host and port of this socket (e.g., "[::1]:8443")
std::string local_hostport_;
// The obfuscated version of client address used in "for" parameter
// of Forwarded header field.
std::string forwarded_for_obfuscated_;
std::function<int(ClientHandler &)> read_, write_;
std::function<int(ClientHandler &)> on_read_, on_write_;
// Address of frontend listening socket
const FrontendAddr *faddr_;
Worker *worker_;
// The number of bytes of HTTP/2 client connection header to read
size_t left_connhd_len_;

View File

@ -1428,11 +1428,15 @@ int parse_config(const char *opt, const char *optarg,
case SHRPX_OPTID_FRONTEND: {
auto &listenerconf = mod_config()->conn.listener;
FrontendAddr addr{};
addr.fd = -1;
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
listenerconf.host = strcopy(path);
listenerconf.port = 0;
listenerconf.host_unix = true;
addr.host = ImmutableString(path);
addr.host_unix = true;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
@ -1442,9 +1446,26 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
listenerconf.host = strcopy(host);
listenerconf.port = port;
listenerconf.host_unix = false;
addr.host = ImmutableString(host);
addr.port = port;
if (util::numeric_host(host, AF_INET)) {
addr.family = AF_INET;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
if (util::numeric_host(host, AF_INET6)) {
addr.family = AF_INET6;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
addr.family = AF_INET;
listenerconf.addrs.push_back(addr);
addr.family = AF_INET6;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}

View File

@ -239,6 +239,24 @@ struct AltSvc {
uint16_t port;
};
struct FrontendAddr {
// The frontend address (e.g., FQDN, hostname, IP address). If
// |host_unix| is true, this is UNIX domain socket path.
ImmutableString host;
// For TCP socket, this is <IP address>:<PORT>. For IPv6 address,
// address is surrounded by square brackets. If socket is UNIX
// domain socket, this is "localhost".
ImmutableString hostport;
// frontend port. 0 if |host_unix| is true.
uint16_t port;
// For TCP socket, this is either AF_INET or AF_INET6. For UNIX
// domain socket, this is 0.
int family;
// true if |host| contains UNIX domain socket path.
bool host_unix;
int fd;
};
struct DownstreamAddr {
DownstreamAddr() : addr{}, port(0), host_unix(false) {}
DownstreamAddr(const DownstreamAddr &other);
@ -464,14 +482,8 @@ struct ConnectionConfig {
struct {
ev_tstamp sleep;
} timeout;
// address of frontend connection. This could be a path to UNIX
// domain socket. In this case, |host_unix| must be true.
std::unique_ptr<char[]> host;
// frontend listening port. 0 if frontend listens on UNIX domain
// socket, in this case |host_unix| must be true.
uint16_t port;
// true if host contains UNIX domain socket path
bool host_unix;
// address of frontend acceptors
std::vector<FrontendAddr> addrs;
int backlog;
// TCP fastopen. If this is positive, it is passed to
// setsockopt() along with TCP_FASTOPEN.

View File

@ -297,7 +297,8 @@ void ConnectionHandler::graceful_shutdown_worker() {
#endif // NOTHREADS
}
int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen,
const FrontendAddr *faddr) {
if (LOG_ENABLED(INFO)) {
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
}
@ -317,7 +318,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
}
auto client =
ssl::accept_connection(single_worker_.get(), fd, addr, addrlen);
ssl::accept_connection(single_worker_.get(), fd, addr, addrlen, faddr);
if (!client) {
LLOG(ERROR, this) << "ClientHandler creation failed";
@ -338,6 +339,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
wev.client_fd = fd;
memcpy(&wev.client_addr, addr, addrlen);
wev.client_addrlen = addrlen;
wev.faddr = faddr;
workers_[idx]->send(wev);
@ -352,39 +354,19 @@ Worker *ConnectionHandler::get_single_worker() const {
return single_worker_.get();
}
void ConnectionHandler::set_acceptor(std::unique_ptr<AcceptHandler> h) {
acceptor_ = std::move(h);
}
AcceptHandler *ConnectionHandler::get_acceptor() const {
return acceptor_.get();
}
void ConnectionHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
acceptor6_ = std::move(h);
}
AcceptHandler *ConnectionHandler::get_acceptor6() const {
return acceptor6_.get();
void ConnectionHandler::add_acceptor(std::unique_ptr<AcceptHandler> h) {
acceptors_.push_back(std::move(h));
}
void ConnectionHandler::enable_acceptor() {
if (acceptor_) {
acceptor_->enable();
}
if (acceptor6_) {
acceptor6_->enable();
for (auto &a : acceptors_) {
a->enable();
}
}
void ConnectionHandler::disable_acceptor() {
if (acceptor_) {
acceptor_->disable();
}
if (acceptor6_) {
acceptor6_->disable();
for (auto &a : acceptors_) {
a->disable();
}
}
@ -400,11 +382,8 @@ void ConnectionHandler::sleep_acceptor(ev_tstamp t) {
}
void ConnectionHandler::accept_pending_connection() {
if (acceptor_) {
acceptor_->accept_connection();
}
if (acceptor6_) {
acceptor6_->accept_connection();
for (auto &a : acceptors_) {
a->accept_connection();
}
}

View File

@ -58,6 +58,7 @@ class Worker;
struct WorkerStat;
struct TicketKeys;
class MemcachedDispatcher;
struct FrontendAddr;
struct OCSPUpdateContext {
// ocsp response buffer
@ -79,7 +80,8 @@ class ConnectionHandler {
public:
ConnectionHandler(struct ev_loop *loop);
~ConnectionHandler();
int handle_connection(int fd, sockaddr *addr, int addrlen);
int handle_connection(int fd, sockaddr *addr, int addrlen,
const FrontendAddr *faddr);
// Creates Worker object for single threaded configuration.
int create_single_worker();
// Creates |num| Worker objects for multi threaded configuration.
@ -92,10 +94,7 @@ public:
const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
struct ev_loop *get_loop() const;
Worker *get_single_worker() const;
void set_acceptor(std::unique_ptr<AcceptHandler> h);
AcceptHandler *get_acceptor() const;
void set_acceptor6(std::unique_ptr<AcceptHandler> h);
AcceptHandler *get_acceptor6() const;
void add_acceptor(std::unique_ptr<AcceptHandler> h);
void enable_acceptor();
void disable_acceptor();
void sleep_acceptor(ev_tstamp t);
@ -154,10 +153,7 @@ private:
// Worker object.
std::shared_ptr<TicketKeys> ticket_keys_;
struct ev_loop *loop_;
// acceptor for IPv4 address or UNIX domain socket.
std::unique_ptr<AcceptHandler> acceptor_;
// acceptor for IPv6 address
std::unique_ptr<AcceptHandler> acceptor6_;
std::vector<std::unique_ptr<AcceptHandler>> acceptors_;
#ifdef HAVE_NEVERBLEED
std::unique_ptr<neverbleed_t> nb_;
#endif // HAVE_NEVERBLEED

View File

@ -46,8 +46,6 @@ std::string create_error_html(unsigned int status_code) {
res += "</h1><footer>";
const auto &server_name = get_config()->http.server_name;
res.append(server_name.c_str(), server_name.size());
res += " at port ";
res += util::utos(get_config()->conn.listener.port);
res += "</footer></body></html>";
return res;
}
@ -63,7 +61,7 @@ std::string create_via_header_value(int major, int minor) {
return hdrs;
}
std::string create_forwarded(int params, const std::string &node_by,
std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for,
const std::string &host,
const std::string &proto) {

View File

@ -42,7 +42,7 @@ std::string create_via_header_value(int major, int minor);
// Returns generated RFC 7239 Forwarded header field value. The
// |params| is bitwise-OR of zero or more of shrpx_forwarded_param
// defined in shrpx_config.h.
std::string create_forwarded(int params, const std::string &node_by,
std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for,
const std::string &host, const std::string &proto);

View File

@ -747,7 +747,7 @@ SSL *create_ssl(SSL_CTX *ssl_ctx) {
}
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
int addrlen) {
int addrlen, const FrontendAddr *faddr) {
char host[NI_MAXHOST];
char service[NI_MAXSERV];
int rv;
@ -783,7 +783,7 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
}
}
return new ClientHandler(worker, fd, ssl, host, service);
return new ClientHandler(worker, fd, ssl, host, service, faddr);
}
bool tls_hostname_match(const char *pattern, size_t plen, const char *hostname,

View File

@ -45,6 +45,7 @@ class ClientHandler;
class Worker;
class DownstreamConnectionPool;
struct DownstreamAddr;
struct FrontendAddr;
namespace ssl {
@ -76,7 +77,7 @@ SSL_CTX *create_ssl_client_context(
);
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
int addrlen);
int addrlen, const FrontendAddr *faddr);
// Check peer's certificate against first downstream address in
// Config::downstream_addrs. We only consider first downstream since

View File

@ -179,8 +179,9 @@ void Worker::process_events() {
break;
}
auto client_handler = ssl::accept_connection(
this, wev.client_fd, &wev.client_addr.sa, wev.client_addrlen);
auto client_handler =
ssl::accept_connection(this, wev.client_fd, &wev.client_addr.sa,
wev.client_addrlen, wev.faddr);
if (!client_handler) {
if (LOG_ENABLED(INFO)) {
WLOG(ERROR, this) << "ClientHandler creation failed";

View File

@ -51,6 +51,7 @@ namespace shrpx {
class Http2Session;
class ConnectBlocker;
class MemcachedDispatcher;
struct FrontendAddr;
#ifdef HAVE_MRUBY
namespace mruby {
@ -93,6 +94,7 @@ struct WorkerEvent {
sockaddr_union client_addr;
size_t client_addrlen;
int client_fd;
const FrontendAddr *faddr;
};
std::shared_ptr<TicketKeys> ticket_keys;
};

View File

@ -389,13 +389,8 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
ConnectionHandler conn_handler(loop);
if (wpconf->server_fd6 != -1) {
conn_handler.set_acceptor6(
make_unique<AcceptHandler>(wpconf->server_fd6, &conn_handler));
}
if (wpconf->server_fd != -1) {
conn_handler.set_acceptor(
make_unique<AcceptHandler>(wpconf->server_fd, &conn_handler));
for (auto &addr : get_config()->conn.listener.addrs) {
conn_handler.add_acceptor(make_unique<AcceptHandler>(&addr, &conn_handler));
}
auto &upstreamconf = get_config()->conn.upstream;

View File

@ -35,6 +35,7 @@
#include <functional>
#include <typeinfo>
#include <algorithm>
#include <ostream>
namespace nghttp2 {
@ -317,6 +318,11 @@ private:
const char *base;
};
inline bool operator==(const ImmutableString &lhs, const ImmutableString &rhs) {
return lhs.size() == rhs.size() &&
std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
}
inline bool operator==(const ImmutableString &lhs, const std::string &rhs) {
return lhs.size() == rhs.size() &&
std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
@ -335,6 +341,10 @@ inline bool operator==(const char *lhs, const ImmutableString &rhs) {
return rhs == lhs;
}
inline bool operator!=(const ImmutableString &lhs, const ImmutableString &rhs) {
return !(lhs == rhs);
}
inline bool operator!=(const ImmutableString &lhs, const std::string &rhs) {
return !(lhs == rhs);
}
@ -351,6 +361,15 @@ inline bool operator!=(const char *lhs, const ImmutableString &rhs) {
return !(rhs == lhs);
}
inline std::ostream &operator<<(std::ostream &o, const ImmutableString &s) {
return o.write(s.c_str(), s.size());
}
inline std::string &operator+=(std::string &lhs, const ImmutableString &rhs) {
lhs.append(rhs.c_str(), rhs.size());
return lhs;
}
// StringRef is a reference to a string owned by something else. So
// it behaves like simple string, but it does not own pointer. When
// it is default constructed, it has empty string. You can freely
@ -374,7 +393,9 @@ public:
: base(s.c_str()), len(s.size()) {}
StringRef(const char *s) : base(s), len(strlen(s)) {}
StringRef(const char *s, size_t n) : base(s), len(n) {}
template <typename InputIt>
StringRef(InputIt first, InputIt last)
: base(first), len(std::distance(first, last)) {}
template <size_t N> static StringRef from_lit(const char(&s)[N]) {
return StringRef(s, N - 1);
}
@ -388,6 +409,7 @@ public:
const char *c_str() const { return base; }
size_type size() const { return len; }
bool empty() const { return len == 0; }
const_reference operator[](size_type pos) const { return *(base + pos); }
std::string str() const { return std::string(base, len); }
@ -430,6 +452,15 @@ inline bool operator!=(const char *lhs, const StringRef &rhs) {
return !(rhs == lhs);
}
inline std::ostream &operator<<(std::ostream &o, const StringRef &s) {
return o.write(s.c_str(), s.size());
}
inline std::string &operator+=(std::string &lhs, const StringRef &rhs) {
lhs.append(rhs.c_str(), rhs.size());
return lhs;
}
inline int run_app(std::function<int(int, char **)> app, int argc,
char **argv) {
try {

View File

@ -26,6 +26,7 @@
#include <cstring>
#include <iostream>
#include <sstream>
#include <CUnit/CUnit.h>
@ -101,6 +102,34 @@ void test_template_immutable_string(void) {
CU_ASSERT('o' == br_op[1]);
CU_ASSERT('t' == br_op[6]);
CU_ASSERT('\0' == br_op[7]);
// operator==(const ImmutableString &, const ImmutableString &)
{
ImmutableString a("foo");
ImmutableString b("foo");
ImmutableString c("fo");
CU_ASSERT(a == b);
CU_ASSERT(a != c);
CU_ASSERT(c != b);
}
// operator<<
{
ImmutableString a("foo");
std::stringstream ss;
ss << a;
CU_ASSERT("foo" == ss.str());
}
// operator +=(std::string &, const ImmutableString &)
{
std::string a = "alpha";
a += ImmutableString("bravo");
CU_ASSERT("alphabravo" == a);
}
}
void test_template_string_ref(void) {
@ -145,6 +174,31 @@ void test_template_string_ref(void) {
CU_ASSERT("delta" == cstrnref);
CU_ASSERT(5 == cstrnref.size());
// operator[]
StringRef br_op("foxtrot");
CU_ASSERT('f' == br_op[0]);
CU_ASSERT('o' == br_op[1]);
CU_ASSERT('t' == br_op[6]);
CU_ASSERT('\0' == br_op[7]);
// operator<<
{
StringRef a("foo");
std::stringstream ss;
ss << a;
CU_ASSERT("foo" == ss.str());
}
// operator +=(std::string &, const StringRef &)
{
std::string a = "alpha";
a += StringRef("bravo");
CU_ASSERT("alphabravo" == a);
}
}
} // namespace nghttp2