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;
};

Members

@@ -1005,5 +1008,15 @@ else ignored
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