Merge branch 'master' into http2-debug-state-api

This commit is contained in:
Tatsuhiro Tsujikawa 2016-08-26 22:54:59 +09:00
commit 69aa70086a
27 changed files with 173 additions and 54 deletions

View File

@ -71,6 +71,7 @@ Tomasz Buchert
Vernon Tang
Viacheslav Biriukov
Viktor Szépe
Wenfeng Liu
Xiaoguang Sun
Zhuoyun Wei
acesso

View File

@ -24,7 +24,7 @@
cmake_minimum_required(VERSION 3.0)
# XXX using 1.8.90 instead of 1.9.0-DEV
project(nghttp2 VERSION 1.13.90)
project(nghttp2 VERSION 1.14.90)
# See versioning rule:
# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html

52
author.py Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
# script to extract commit author's name from standard input. The
# input should be <AUTHOR>:<EMAIL>, one per line.
# This script expects the input is created by git-log command:
#
# git log --format=%aN:%aE
#
# This script removes duplicates based on email address, breaking a
# tie with longer author name. Among the all author names extract the
# previous step, we remove duplicate by case-insensitive match.
#
# So we can do this in one line:
#
# git log --format=%aN:%aE | sort | uniq | ./author.py > authors
import sys
edict = {}
for line in sys.stdin:
author, email = line.strip().split(':', 1)
if email in edict:
an = edict[email]
if len(an) < len(author) or an > author:
sys.stderr.write(
'eliminated {} in favor of {}\n'.format(an, author))
edict[email] = author
else:
sys.stderr.write(
'eliminated {} in favor of {}\n'.format(author, an))
else:
edict[email] = author
names = list(sorted(edict.values()))
ndict = {}
for name in names:
lowname = name.lower()
if lowname in ndict:
an = ndict[lowname]
if an > name:
sys.stderr.write('eliminated {} in favor of {}\n'.format(an, name))
ndict[lowname] = name
else:
sys.stderr.write('eliminated {} in favor of {}\n'.format(name, an))
else:
ndict[lowname] = name
for name in sorted(ndict.values()):
print name

View File

@ -25,7 +25,7 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.14.0-DEV], [t-tujikawa@users.sourceforge.net])
AC_INIT([nghttp2], [1.15.0-DEV], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
@ -44,9 +44,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 23)
AC_SUBST(LT_CURRENT, 24)
AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_AGE, 9)
AC_SUBST(LT_AGE, 10)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`

View File

@ -37,6 +37,7 @@ APIDOCS= \
nghttp2_hd_deflate_get_num_table_entries.rst \
nghttp2_hd_deflate_get_table_entry.rst \
nghttp2_hd_deflate_hd.rst \
nghttp2_hd_deflate_hd_vec.rst \
nghttp2_hd_deflate_new.rst \
nghttp2_hd_deflate_new2.rst \
nghttp2_hd_inflate_change_table_size.rst \

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "H2LOAD" "1" "Jul 31, 2016" "1.14.0-DEV" "nghttp2"
.TH "H2LOAD" "1" "Aug 25, 2016" "1.14.0" "nghttp2"
.SH NAME
h2load \- HTTP/2 benchmarking tool
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTP" "1" "Jul 31, 2016" "1.14.0-DEV" "nghttp2"
.TH "NGHTTP" "1" "Aug 25, 2016" "1.14.0" "nghttp2"
.SH NAME
nghttp \- HTTP/2 client
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPD" "1" "Jul 31, 2016" "1.14.0-DEV" "nghttp2"
.TH "NGHTTPD" "1" "Aug 25, 2016" "1.14.0" "nghttp2"
.SH NAME
nghttpd \- HTTP/2 server
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPX" "1" "Jul 31, 2016" "1.14.0-DEV" "nghttp2"
.TH "NGHTTPX" "1" "Aug 25, 2016" "1.14.0" "nghttp2"
.SH NAME
nghttpx \- HTTP/2 proxy
.
@ -938,6 +938,12 @@ $ssl_session_id: session ID for SSL/TLS connection.
.IP \(bu 2
$ssl_session_reused: "r" if SSL/TLS session was
reused. Otherwise, "."
.IP \(bu 2
$backend_host: backend host used to fulfill the
request. "\-" if backend host is not available.
.IP \(bu 2
$backend_port: backend port used to fulfill the
request. "\-" if backend host is not available.
.UNINDENT
.sp
The variable can be enclosed by "{" and "}" for

View File

@ -847,6 +847,10 @@ Logging
* $ssl_session_id: session ID for SSL/TLS connection.
* $ssl_session_reused: "r" if SSL/TLS session was
reused. Otherwise, "."
* $backend_host: backend host used to fulfill the
request. "-" if backend host is not available.
* $backend_port: backend port used to fulfill the
request. "-" if backend host is not available.
The variable can be enclosed by "{" and "}" for
disambiguation (e.g., ${remote_addr}).

View File

@ -134,6 +134,7 @@ OPTIONS = [
"backend-http2-settings-timeout",
"api-max-request-body",
"backend-max-backoff",
"server-name",
]
LOGVARS = [

View File

@ -768,14 +768,12 @@ static size_t entry_room(size_t namelen, size_t valuelen) {
return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen;
}
static int emit_header(nghttp2_hd_nv *nv_out, nghttp2_hd_nv *nv) {
static void emit_header(nghttp2_hd_nv *nv_out, nghttp2_hd_nv *nv) {
DEBUGF(fprintf(stderr, "inflatehd: header emission: %s: %s\n", nv->name->base,
nv->value->base));
/* ent->ref may be 0. This happens if the encoder emits literal
block larger than header table capacity with indexing. */
*nv_out = *nv;
return 0;
}
static size_t count_encoded_length(size_t n, size_t prefix) {

View File

@ -503,7 +503,7 @@ const request *session_impl::submit(boost::system::error_code &ec,
}
auto nva = std::vector<nghttp2_nv>();
nva.reserve(3 + h.size());
nva.reserve(4 + h.size());
nva.push_back(http2::make_nv_ls(":method", method));
nva.push_back(http2::make_nv_ls(":scheme", uref.scheme));
nva.push_back(http2::make_nv_ls(":path", path));

View File

@ -178,6 +178,9 @@ template <typename Memchunk> struct Memchunks {
}
size_t append(const std::string &s) { return append(s.c_str(), s.size()); }
size_t append(const StringRef &s) { return append(s.c_str(), s.size()); }
size_t append(const ImmutableString &s) {
return append(s.c_str(), s.size());
}
size_t remove(void *dest, size_t count) {
if (!tail || count == 0) {
return 0;

View File

@ -574,7 +574,7 @@ int create_unix_domain_server_socket(UpstreamAddr &faddr,
<< (faddr.tls ? ", tls" : "");
(*found).used = true;
faddr.fd = (*found).fd;
faddr.hostport = "localhost";
faddr.hostport = ImmutableString::from_lit("localhost");
return 0;
}
@ -639,7 +639,7 @@ int create_unix_domain_server_socket(UpstreamAddr &faddr,
<< (faddr.tls ? ", tls" : "");
faddr.fd = fd;
faddr.hostport = "localhost";
faddr.hostport = ImmutableString::from_lit("localhost");
return 0;
}
@ -791,7 +791,8 @@ int create_tcp_server_socket(UpstreamAddr &faddr,
}
faddr.fd = fd;
faddr.hostport = util::make_http_hostport(StringRef{host.data()}, faddr.port);
faddr.hostport = ImmutableString{
util::make_http_hostport(StringRef{host.data()}, faddr.port)};
LOG(NOTICE) << "Listening on " << faddr.hostport
<< (faddr.tls ? ", tls" : "");
@ -855,7 +856,7 @@ get_inherited_addr_from_config(const Config *config) {
continue;
}
iaddr.host = host.data();
iaddr.host = ImmutableString{host.data()};
}
return iaddrs;
@ -947,7 +948,7 @@ std::vector<InheritedAddr> get_inherited_addr_from_env() {
}
InheritedAddr addr{};
addr.host = path;
addr.host = ImmutableString{path};
addr.host_unix = true;
addr.fd = static_cast<int>(fd);
iaddrs.push_back(std::move(addr));
@ -1001,7 +1002,7 @@ std::vector<InheritedAddr> get_inherited_addr_from_env() {
}
InheritedAddr addr{};
addr.host = host.data();
addr.host = ImmutableString{host.data()};
addr.port = static_cast<uint16_t>(port);
addr.fd = static_cast<int>(fd);
iaddrs.push_back(std::move(addr));
@ -1272,10 +1273,8 @@ constexpr auto DEFAULT_ACCESSLOG_FORMAT = StringRef::from_lit(
namespace {
void fill_default_config(Config *config) {
*config = {};
config->num_worker = 1;
config->conf_path = "/etc/nghttpx/nghttpx.conf";
config->conf_path = ImmutableString::from_lit("/etc/nghttpx/nghttpx.conf");
config->pid = getpid();
if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) {
@ -1306,7 +1305,8 @@ void fill_default_config(Config *config) {
auto &ocspconf = tlsconf.ocsp;
// ocsp update interval = 14400 secs = 4 hours, borrowed from h2o
ocspconf.update_interval = 4_h;
ocspconf.fetch_ocsp_response_file = PKGDATADIR "/fetch-ocsp-response";
ocspconf.fetch_ocsp_response_file =
ImmutableString::from_lit(PKGDATADIR "/fetch-ocsp-response");
}
{
@ -1319,7 +1319,7 @@ void fill_default_config(Config *config) {
auto &httpconf = config->http;
httpconf.server_name =
StringRef::from_lit("nghttpx nghttp2/" NGHTTP2_VERSION);
ImmutableString::from_lit("nghttpx nghttp2/" NGHTTP2_VERSION);
httpconf.no_host_rewrite = true;
httpconf.request_header_field_buffer = 64_k;
httpconf.max_request_header_fields = 100;
@ -1377,7 +1377,7 @@ void fill_default_config(Config *config) {
accessconf.format = parse_log_format(DEFAULT_ACCESSLOG_FORMAT);
auto &errorconf = loggingconf.error;
errorconf.file = "/dev/stderr";
errorconf.file = ImmutableString::from_lit("/dev/stderr");
}
loggingconf.syslog_facility = LOG_DAEMON;
@ -2205,6 +2205,9 @@ HTTP:
599. If "*" is used instead of <CODE>, it matches all
HTTP status code. If error status code comes from
backend server, the custom error pages are not used.
--server-name=<NAME>
Change server response header field value to <NAME>.
Default: )" << get_config()->http.server_name << R"(
API:
--api-max-request-body=<SIZE>
@ -2396,7 +2399,7 @@ int process_options(Config *config,
if (listenerconf.addrs.empty()) {
UpstreamAddr addr{};
addr.host = "*";
addr.host = ImmutableString::from_lit("*");
addr.port = 3000;
addr.tls = true;
addr.family = AF_INET;
@ -2833,6 +2836,7 @@ int main(int argc, char **argv) {
&flag, 125},
{SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126},
{SHRPX_OPT_BACKEND_MAX_BACKOFF.c_str(), required_argument, &flag, 127},
{SHRPX_OPT_SERVER_NAME.c_str(), required_argument, &flag, 128},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -2935,7 +2939,7 @@ int main(int argc, char **argv) {
break;
case 12:
// --conf
mod_config()->conf_path = optarg;
mod_config()->conf_path = ImmutableString{optarg};
break;
case 14:
// --syslog-facility
@ -3430,6 +3434,10 @@ int main(int argc, char **argv) {
// --backend-max-backoff
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_MAX_BACKOFF, StringRef{optarg});
break;
case 128:
// --server-name
cmdcfgs.emplace_back(SHRPX_OPT_SERVER_NAME, StringRef{optarg});
break;
default:
break;
}

View File

@ -1127,6 +1127,11 @@ int option_lookup_token(const char *name, size_t namelen) {
break;
case 11:
switch (name[10]) {
case 'e':
if (util::strieq_l("server-nam", name, 10)) {
return SHRPX_OPTID_SERVER_NAME;
}
break;
case 's':
if (util::strieq_l("backend-tl", name, 10)) {
return SHRPX_OPTID_BACKEND_TLS;
@ -2027,7 +2032,7 @@ int parse_config(Config *config, int optid, const StringRef &opt,
<< strerror(errno);
return -1;
}
config->user = pwd->pw_name;
config->user = ImmutableString{pwd->pw_name};
config->uid = pwd->pw_uid;
config->gid = pwd->pw_gid;
@ -2044,7 +2049,7 @@ int parse_config(Config *config, int optid, const StringRef &opt,
LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg;
return -1;
}
config->tls.private_key_passwd = passwd;
config->tls.private_key_passwd = ImmutableString{passwd};
return 0;
}
@ -2479,14 +2484,14 @@ int parse_config(Config *config, int optid, const StringRef &opt,
switch (optid) {
case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: {
auto &memcachedconf = config->tls.session_cache.memcached;
memcachedconf.host = host;
memcachedconf.host = ImmutableString{host};
memcachedconf.port = port;
memcachedconf.tls = params.tls;
break;
}
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: {
auto &memcachedconf = config->tls.ticket.memcached;
memcachedconf.host = host;
memcachedconf.host = ImmutableString{host};
memcachedconf.port = port;
memcachedconf.tls = params.tls;
break;
@ -2673,6 +2678,11 @@ int parse_config(Config *config, int optid, const StringRef &opt,
case SHRPX_OPTID_BACKEND_MAX_BACKOFF:
return parse_duration(&config->conn.downstream->timeout.max_backoff, opt,
optarg);
case SHRPX_OPTID_SERVER_NAME:
config->http.server_name =
ImmutableString{std::begin(optarg), std::end(optarg)};
return 0;
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";
@ -2948,7 +2958,7 @@ int configure_downstream_group(Config *config, bool http2_proxy,
auto &sni = tlsconf.backend_sni_name;
for (auto &addr_group : addr_groups) {
for (auto &addr : addr_group.addrs) {
addr.sni = sni;
addr.sni = ImmutableString{sni};
}
}
}
@ -2995,7 +3005,7 @@ int configure_downstream_group(Config *config, bool http2_proxy,
// 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 = "localhost";
addr.hostport = ImmutableString::from_lit("localhost");
auto path = addr.host.c_str();
auto pathlen = addr.host.size();

View File

@ -284,6 +284,7 @@ constexpr auto SHRPX_OPT_API_MAX_REQUEST_BODY =
StringRef::from_lit("api-max-request-body");
constexpr auto SHRPX_OPT_BACKEND_MAX_BACKOFF =
StringRef::from_lit("backend-max-backoff");
constexpr auto SHRPX_OPT_SERVER_NAME = StringRef::from_lit("server-name");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -552,7 +553,7 @@ struct HttpConfig {
std::vector<ErrorPage> error_pages;
Headers add_request_headers;
Headers add_response_headers;
StringRef server_name;
ImmutableString server_name;
size_t request_header_field_buffer;
size_t max_request_header_fields;
size_t response_header_field_buffer;
@ -706,8 +707,14 @@ struct APIConfig {
};
struct Config {
Config() = default;
~Config();
Config(Config &&) = delete;
Config(const Config &&) = delete;
Config &operator=(Config &&) = delete;
Config &operator=(const Config &&) = delete;
HttpProxy downstream_http_proxy;
HttpConfig http;
Http2Config http2;
@ -837,6 +844,7 @@ enum {
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RLIMIT_NOFILE,
SHRPX_OPTID_SERVER_NAME,
SHRPX_OPTID_STREAM_READ_TIMEOUT,
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
SHRPX_OPTID_STRIP_INCOMING_FORWARDED,

View File

@ -51,7 +51,7 @@ StringRef create_error_html(BlockAllocator &balloc, unsigned int http_status) {
return concat_string_ref(
balloc, StringRef::from_lit(R"(<!DOCTYPE html><html lang="en"><title>)"),
status_string, StringRef::from_lit("</title><body><h1>"), status_string,
StringRef::from_lit("</h1><footer>"), server_name,
StringRef::from_lit("</h1><footer>"), StringRef{server_name},
StringRef::from_lit("</footer></body></html>"));
}

View File

@ -1806,9 +1806,11 @@ int Http2Session::read_noop(const uint8_t *data, size_t datalen) { return 0; }
int Http2Session::write_noop() { return 0; }
int Http2Session::connected() {
if (!util::check_socket_connected(conn_.fd)) {
auto sock_error = util::get_socket_error(conn_.fd);
if (sock_error != 0) {
SSLOG(WARN, this) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr);
<< util::to_numeric_addr(&addr_->addr)
<< ": errno=" << sock_error;
downstream_failure(addr_);

View File

@ -1307,8 +1307,8 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
}
if (!resp.fs.header(http2::HD_SERVER)) {
nva.push_back(
http2::make_nv_ls_nocopy("server", get_config()->http.server_name));
nva.push_back(http2::make_nv_ls_nocopy(
"server", StringRef{get_config()->http.server_name}));
}
for (auto &p : httpconf.add_response_headers) {
@ -1359,7 +1359,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
auto nva = std::array<nghttp2_nv, 5>{
{http2::make_nv_ls_nocopy(":status", response_status),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
http2::make_nv_ls_nocopy("server",
StringRef{get_config()->http.server_name}),
http2::make_nv_ls_nocopy("content-length", content_length),
http2::make_nv_ls_nocopy("date", date)}};
@ -1506,7 +1507,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers());
if (!get_config()->http2_proxy) {
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
nva.push_back(
http2::make_nv_ls_nocopy("server", StringRef{httpconf.server_name}));
} else {
auto server = resp.fs.header(http2::HD_SERVER);
if (server) {

View File

@ -994,6 +994,8 @@ int HttpDownstreamConnection::tls_handshake() {
auto &connect_blocker = addr_->connect_blocker;
do_signal_write_ = &HttpDownstreamConnection::actual_signal_write;
connect_blocker->on_success();
ev_set_cb(&conn_.rt, timeoutcb);
@ -1141,11 +1143,13 @@ int HttpDownstreamConnection::process_input(const uint8_t *data,
int HttpDownstreamConnection::connected() {
auto &connect_blocker = addr_->connect_blocker;
if (!util::check_socket_connected(conn_.fd)) {
auto sock_error = util::get_socket_error(conn_.fd);
if (sock_error != 0) {
conn_.wlimit.stopw();
DCLOG(WARN, this) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr);
<< util::to_numeric_addr(&addr_->addr)
<< ": errno=" << sock_error;
downstream_failure(addr_);
@ -1160,8 +1164,6 @@ int HttpDownstreamConnection::connected() {
ev_set_cb(&conn_.wev, writecb);
do_signal_write_ = &HttpDownstreamConnection::actual_signal_write;
if (conn_.tls.ssl) {
do_read_ = &HttpDownstreamConnection::tls_handshake;
do_write_ = &HttpDownstreamConnection::tls_handshake;
@ -1169,6 +1171,8 @@ int HttpDownstreamConnection::connected() {
return 0;
}
do_signal_write_ = &HttpDownstreamConnection::actual_signal_write;
connect_blocker->on_success();
ev_set_cb(&conn_.rt, timeoutcb);

View File

@ -259,10 +259,12 @@ int LiveCheck::initiate_connection() {
}
int LiveCheck::connected() {
if (!util::check_socket_connected(conn_.fd)) {
auto sock_error = util::get_socket_error(conn_.fd);
if (sock_error != 0) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr);
<< util::to_numeric_addr(&addr_->addr)
<< ": errno=" << sock_error;
}
return -1;

View File

@ -203,15 +203,16 @@ int MemcachedConnection::initiate_connection() {
}
int MemcachedConnection::connected() {
if (!util::check_socket_connected(conn_.fd)) {
auto sock_error = util::get_socket_error(conn_.fd);
if (sock_error != 0) {
MCLOG(WARN, this) << "memcached connect failed; addr="
<< util::to_numeric_addr(addr_)
<< ": errno=" << sock_error;
connect_blocker_.on_failure();
conn_.wlimit.stopw();
if (LOG_ENABLED(INFO)) {
MCLOG(INFO, this) << "memcached connect failed";
}
return -1;
}

View File

@ -255,8 +255,9 @@ public:
ImmutableString() : len(0), base("") {}
ImmutableString(const char *s, size_t slen)
: len(slen), base(copystr(s, s + len)) {}
ImmutableString(const char *s) : len(strlen(s)), base(copystr(s, s + len)) {}
ImmutableString(const std::string &s)
explicit ImmutableString(const char *s)
: len(strlen(s)), base(copystr(s, s + len)) {}
explicit ImmutableString(const std::string &s)
: len(s.size()), base(copystr(std::begin(s), std::end(s))) {}
template <typename InputIt>
ImmutableString(InputIt first, InputIt last)

View File

@ -156,7 +156,7 @@ void test_template_string_ref(void) {
CU_ASSERT(5 == from_lit.size());
// from ImmutableString
ImmutableString im = "bravo";
auto im = ImmutableString::from_lit("bravo");
StringRef imref(im);

View File

@ -893,6 +893,16 @@ bool check_socket_connected(int fd) {
return error == 0;
}
int get_socket_error(int fd) {
int error;
socklen_t len = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) != 0) {
return -1;
}
return error;
}
bool ipv6_numeric_addr(const char *host) {
uint8_t dst[16];
return inet_pton(AF_INET6, host, dst) == 1;

View File

@ -574,6 +574,11 @@ int create_nonblock_socket(int family);
bool check_socket_connected(int fd);
// Returns the error code (errno) by inspecting SO_ERROR of given
// |fd|. This function returns the error code if it succeeds, or -1.
// Returning 0 means no error.
int get_socket_error(int fd);
// Returns true if |host| is IPv6 numeric address (e.g., ::1)
bool ipv6_numeric_addr(const char *host);