diff --git a/READMEs/README.tcp_fastopen.md b/READMEs/README.tcp_fastopen.md new file mode 100644 index 000000000..f9ca8eb35 --- /dev/null +++ b/READMEs/README.tcp_fastopen.md @@ -0,0 +1,32 @@ +# `TCP_FASTOPEN` support in lws + +Lws supports enabling TCP_FASTOPEN oper-vhost for listen sockets. + +## Enabling per vhost serving + +Set the `info.fo_listen_queue` to nonzero at vhost creation. Different +platforms interpret this number differently, zero always disables it +but on Linux, the number is interpreted as a SYN queue length. + +On FreeBSD, OSX and Windows, the number is basically a bool, with the +extra restriction OSX and Windows only allows 0 or 1. + +## Enabling Linux for serving with TCP_FASTOPEN + +To configure the kernel for listening socket TCP_FASTOPEN, you need + +``` +# sysctl -w net.ipv4.tcp_fastopen=3 +``` + +## Enabling BSD for serving with TCP_FASTOPEN + +At least on FreeBSD, you need to set the net.inet.tcp.fastopen.enabled +sysctl to 1 + +## Enabling Windows for serving with TCP_FASTOPEN + +``` +> netsh int tcp set global fastopenfallback=disabled +> netsh int tcp show global +``` diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index f40c14ddc..39bc71f5e 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -854,16 +854,22 @@ struct lws_context_creation_info { #if defined(LWS_WITH_SYS_METRICS) const struct lws_metric_policy *metrics_policies; - /**< non-SS policy metrics policies */ + /**< CONTEXT: non-SS policy metrics policies */ const char *metrics_prefix; - /**< prefix for this context's metrics, used to distinguish metrics - * pooled from different processes / applications, so, eg what would - * be "cpu.svc" if this is NULL becomes "myapp.cpu.svc" is this is + /**< CONTEXT: prefix for this context's metrics, used to distinguish + * metrics pooled from different processes / applications, so, eg what + * would be "cpu.svc" if this is NULL becomes "myapp.cpu.svc" is this is * set to "myapp". Policies are applied using the name with the prefix, * if present. */ #endif + int fo_listen_queue; + /**< VHOST: 0 = no TCP_FASTOPEN, nonzero = enable TCP_FASTOPEN if the + * platform supports it, with the given queue length for the listen + * socket. + */ + /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility * diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index fc3491d0e..df576eeea 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -535,6 +535,7 @@ struct lws_vhost { int keepalive_timeout; int timeout_secs_ah_idle; int connect_timeout_secs; + int fo_listen_queue; int count_bound_wsi; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 6556ef29c..8eb35c080 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -659,15 +659,16 @@ lws_create_vhost(struct lws_context *context, vh->count_protocols++) ; - vh->options = info->options; - vh->pvo = info->pvo; - vh->headers = info->headers; - vh->user = info->user; - vh->finalize = info->finalize; - vh->finalize_arg = info->finalize_arg; - vh->listen_accept_role = info->listen_accept_role; - vh->listen_accept_protocol = info->listen_accept_protocol; - vh->unix_socket_perms = info->unix_socket_perms; + vh->options = info->options; + vh->pvo = info->pvo; + vh->headers = info->headers; + vh->user = info->user; + vh->finalize = info->finalize; + vh->finalize_arg = info->finalize_arg; + vh->listen_accept_role = info->listen_accept_role; + vh->listen_accept_protocol = info->listen_accept_protocol; + vh->unix_socket_perms = info->unix_socket_perms; + vh->fo_listen_queue = info->fo_listen_queue; LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) if (lws_rops_fidx(ar, LWS_ROPS_init_vhost) && diff --git a/lib/plat/unix/unix-sockets.c b/lib/plat/unix/unix-sockets.c index af8a69b8d..107b5373c 100644 --- a/lib/plat/unix/unix-sockets.c +++ b/lib/plat/unix/unix-sockets.c @@ -220,6 +220,16 @@ lws_plat_set_socket_options_ip(lws_sockfd_type fd, uint8_t pri, int lws_flags) int en; #endif +#if 0 +#if defined(TCP_FASTOPEN_CONNECT) + optval = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, (void *)&optval, + sizeof(optval))) + lwsl_warn("%s: FASTOPEN_CONNECT failed\n", __func__); + optval = (int)pri; +#endif +#endif + #if !defined(__APPLE__) && \ !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && \ !defined(__NetBSD__) && \ diff --git a/lib/roles/http/server/lejp-conf.c b/lib/roles/http/server/lejp-conf.c index 6970c19b5..98392d4ad 100644 --- a/lib/roles/http/server/lejp-conf.c +++ b/lib/roles/http/server/lejp-conf.c @@ -119,6 +119,7 @@ static const char * const paths_vhosts[] = { "vhosts[].ignore-missing-cert", "vhosts[].error-document-404", "vhosts[].alpn", + "vhosts[].fo-listen-queue", "vhosts[].ssl-client-option-set", "vhosts[].ssl-client-option-clear", "vhosts[].tls13-ciphers", @@ -187,6 +188,7 @@ enum lejp_vhost_paths { LEJPVP_IGNORE_MISSING_CERT, LEJPVP_ERROR_DOCUMENT_404, LEJPVP_ALPN, + LWJPVP_FO_LISTEN_QUEUE, LEJPVP_SSL_CLIENT_OPTION_SET, LEJPVP_SSL_CLIENT_OPTION_CLEAR, LEJPVP_TLS13_CIPHERS, @@ -693,6 +695,9 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_CGI_TIMEOUT: a->m.cgi_timeout = atoi(ctx->buf); return 0; + case LWJPVP_FO_LISTEN_QUEUE: + a->info->fo_listen_queue = atoi(ctx->buf); + return 0; case LEJPVP_KEEPALIVE_TIMEOUT: a->info->keepalive_timeout = atoi(ctx->buf); return 0; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index 270231c01..265187ee3 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -24,6 +24,10 @@ #include "private-lib-core.h" +#if !defined(SOL_TCP) && defined(IPPROTO_TCP) +#define SOL_TCP IPPROTO_TCP +#endif + const char * const method_names[] = { "GET", "POST", #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) @@ -345,6 +349,29 @@ done_list: vhost->lserv_wsi = wsi; lws_pt_unlock(pt); +#if defined(WIN32) && defined(TCP_FASTOPEN) + if (vhost->fo_listen_queue) { + int optval = 1; + if (setsockopt(wsi->desc.sockfd, IPPROTO_TCP, + TCP_FASTOPEN, + (const char*)&optval, sizeof(optval)) < 0) { + int error = LWS_ERRNO; + lwsl_warn("%s: TCP_NODELAY failed with error %d\n", + __func__, error); + } + } +#else +#if defined(TCP_FASTOPEN) + if (vhost->fo_listen_queue) { + int qlen = vhost->fo_listen_queue; + + if (setsockopt(wsi->desc.sockfd, SOL_TCP, TCP_FASTOPEN, + &qlen, sizeof(qlen))) + lwsl_warn("%s: TCP_FASTOPEN failed\n", __func__); + } +#endif +#endif + n = listen(wsi->desc.sockfd, LWS_SOMAXCONN); if (n < 0) { lwsl_err("listen failed with error %d\n", LWS_ERRNO); diff --git a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c index 0049980c0..a72a23165 100644 --- a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c +++ b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c @@ -126,6 +126,7 @@ int main(int argc, const char **argv) LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; info.ssl_cert_filepath = "localhost-100y.cert"; info.ssl_private_key_filepath = "localhost-100y.key"; + info.fo_listen_queue = 32; #if defined(LWS_WITH_PLUGINS) info.plugin_dirs = plugin_dirs;