diff --git a/README.coding b/README.coding
index b994f3a3..7c9d631f 100644
--- a/README.coding
+++ b/README.coding
@@ -162,3 +162,25 @@ copy interesting headers by handling LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION
callback, for clients there's a new callback just for this purpose
LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH.
+
+TCP Keepalive
+-------------
+
+It is possible for a connection which is not being used to send to die
+silently somewhere between the peer and the side not sending. In this case
+by default TCP will just not report anything and you will never get any more
+incoming data or sign the link is dead until you try to send.
+
+To deal with getting a notification of that situation, you can choose to
+enable TCP keepalives on all libwebsockets sockets, when you create the
+context.
+
+To enable keepalive, set the ka_time member of the context creation parameter
+struct to a nonzero value (in seconds) at context creation time. You should
+also fill ka_probes and ka_interval in that case.
+
+With keepalive enabled, the TCP layer will send control packets that should
+stimulate a response from the peer without affecting link traffic. If the
+response is not coming, the socket will announce an error at poll() forcing
+a close.
+
diff --git a/changelog b/changelog
index 35f51250..fcf091d5 100644
--- a/changelog
+++ b/changelog
@@ -10,6 +10,14 @@ User api additions
"1.1 9e7f737", representing the library version from configure.ac
and the git HEAD hash the library was built from
+ - TCP Keepalive can now optionally be applied to all lws sockets, with
+ controllable timeout, number of probes and probe interval. This
+ enables detection of idle connections which are logically okay, but
+ are in fact dead, due to network connectivity issues at the server,
+ client, or any intermediary. By default it's not enabled, but you
+ can enable it by setting a non-zero timeout (in seconds) at the new
+ ka_time member at context creation time.
+
User api changes
----------------
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index f3113b07..48c9a96d 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -10,10 +10,6 @@ struct libwebsocket *__libwebsocket_client_connect_2(
int n;
int plen = 0;
char pkt[512];
- int opt = 1;
-#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
- struct protoent *tcp_proto;
-#endif
lwsl_client("__libwebsocket_client_connect_2\n");
#ifndef LWS_NO_EXTENSIONS
@@ -41,7 +37,8 @@ struct libwebsocket *__libwebsocket_client_connect_2(
* prepare the actual connection (to the proxy, if any)
*/
- lwsl_client("__libwebsocket_client_connect_2: address %s", wsi->c_address);
+ lwsl_client("__libwebsocket_client_connect_2: address %s\n",
+ wsi->c_address);
server_hostent = gethostbyname(wsi->c_address);
if (server_hostent == NULL) {
@@ -61,16 +58,6 @@ struct libwebsocket *__libwebsocket_client_connect_2(
server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr);
bzero(&server_addr.sin_zero, 8);
- /* Disable Nagle */
-#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__NetBSD__)
- setsockopt(wsi->sock, SOL_TCP, TCP_NODELAY,
- (const void *)&opt, sizeof(opt));
-#else
- tcp_proto = getprotobyname("TCP");
- setsockopt(wsi->sock, tcp_proto->p_proto, TCP_NODELAY,
- &opt, sizeof(opt));
-#endif
-
if (connect(wsi->sock, (struct sockaddr *)&server_addr,
sizeof(struct sockaddr)) == -1) {
lwsl_debug("Connect failed\n");
@@ -80,6 +67,12 @@ struct libwebsocket *__libwebsocket_client_connect_2(
lwsl_client("connected\n");
+ if (lws_set_socket_options(context, wsi->sock)) {
+ lwsl_err("Failed to set wsi socket options\n");
+ close(wsi->sock);
+ goto oom4;
+ }
+
insert_wsi_socket_into_fds(context, wsi);
/* we are connected to server, or proxy */
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 31d6f679..55fcf00f 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -519,6 +519,60 @@ int libwebsockets_get_random(struct libwebsocket_context *context,
return n;
}
+int lws_set_socket_options(struct libwebsocket_context *context, int fd)
+{
+ int optval = 1;
+ socklen_t optlen = sizeof(optval);
+#ifdef WIN32
+ unsigned long optl = 0;
+#endif
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
+ struct protoent *tcp_proto;
+#endif
+
+ if (context->ka_time) {
+ /* enable keepalive on this socket */
+ optval = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+ (const void *)&optval, optlen) < 0)
+ return 1;
+
+ /* set the keepalive conditions we want on it too */
+ optval = context->ka_time;
+ if (setsockopt(fd, IPPROTO_IP, TCP_KEEPIDLE,
+ (const void *)&optval, optlen) < 0)
+ return 1;
+
+ optval = context->ka_probes;
+ if (setsockopt(fd, IPPROTO_IP, TCP_KEEPINTVL,
+ (const void *)&optval, optlen) < 0)
+ return 1;
+
+ optval = context->ka_interval;
+ if (setsockopt(fd, IPPROTO_IP, TCP_KEEPCNT,
+ (const void *)&optval, optlen) < 0)
+ return 1;
+ }
+
+ /* Disable Nagle */
+ optval = 1;
+#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__NetBSD__)
+ setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen);
+#else
+ tcp_proto = getprotobyname("TCP");
+ setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, &optval, optlen);
+#endif
+
+ /* We are nonblocking... */
+#ifdef WIN32
+ ioctlsocket(fd, FIONBIO, &optl);
+#else
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+#endif
+
+ return 0;
+}
+
int lws_send_pipe_choked(struct libwebsocket *wsi)
{
struct pollfd fds;
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 210ecd4e..5e2681c5 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -742,6 +742,13 @@ struct libwebsocket_extension {
* @options: 0, or LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK
* @user: optional user pointer that can be recovered via the context
* pointer using libwebsocket_context_user
+ * @ka_time: 0 for no keepalive, otherwise apply this keepalive timeout to
+ * all libwebsocket sockets, client or server
+ * @ka_probes: if ka_time was nonzero, after the timeout expires how many
+ * times to try to get a response from the peer before giving up
+ * and killing the connection
+ * @ka_interval: if ka_time was nonzero, how long to wait before each ka_probes
+ * attempt
*/
struct lws_context_creation_info {
@@ -756,6 +763,10 @@ struct lws_context_creation_info {
int uid;
unsigned int options;
void *user;
+ int ka_time;
+ int ka_probes;
+ int ka_interval;
+
};
LWS_EXTERN
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index a53d00f3..267b5cc1 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -265,6 +265,10 @@ struct libwebsocket_context {
int listen_service_fd;
int listen_service_extraseen;
+ int ka_time;
+ int ka_probes;
+ int ka_interval;
+
#ifdef LWS_LATENCY
unsigned long worst_latency;
char worst_latency_info[256];
@@ -489,6 +493,9 @@ user_callback_handle_rxflow(callback_function, struct libwebsocket_context * con
enum libwebsocket_callback_reasons reason, void *user,
void *in, size_t len);
+extern int
+lws_set_socket_options(struct libwebsocket_context *context, int fd);
+
#ifndef LWS_OPENSSL_SUPPORT
unsigned char *
diff --git a/lib/server.c b/lib/server.c
index 3bb28a2f..aa51b129 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -133,7 +133,6 @@ int lws_server_socket_service(struct libwebsocket_context *context,
unsigned int clilen;
struct sockaddr_in cli_addr;
int n;
- int opt = 1;
ssize_t len;
#ifdef LWS_OPENSSL_SUPPORT
int m;
@@ -217,18 +216,7 @@ int lws_server_socket_service(struct libwebsocket_context *context,
break;
}
- /* Disable Nagle */
- opt = 1;
- setsockopt(accept_fd, IPPROTO_TCP, TCP_NODELAY,
- (const void *)&opt, sizeof(opt));
-
- /* We are nonblocking... */
- #ifdef WIN32
- opt = 0;
- ioctlsocket(accept_fd, FIONBIO, (unsigned long *)&opt );
- #else
- fcntl(accept_fd, F_SETFL, O_NONBLOCK);
- #endif
+ lws_set_socket_options(context, accept_fd);
/*
* look at who we connected to and give user code a chance
diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html
index 65a3da7a..a3f6415e 100644
--- a/libwebsockets-api-doc.html
+++ b/libwebsockets-api-doc.html
@@ -968,6 +968,9 @@ all sessions, etc, if it wants
int uid;
unsigned int options;
void * user;
+ int ka_time;
+ int ka_probes;
+ int ka_interval;
};