libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit fd9472386333a7d2a2a7c298641b64167f3598ac
parent cde3691e85724707c55d776fad3e93454eaf24d1
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 29 Oct 2014 19:50:58 +0000

Hi Christian,

I attach the first attempt on SO_REUSEPORT. The patch is available
either at https://github.com/foxik/libmicrohttpd/commit/9ce9422742e10458f87275ea202a982e00c2b88c
or attached. (It is against the version with
MHD_DAEMON_OPTION_CURRENT_CONNECTIONS, but I can rebase it to current
SVN HEAD if you want.)

It seems that a reasonably multiplatform way of detecting SO_REUSEPORT is
  #ifdef SO_REPOSEPORT
which is used for example by Perl. For SO_EXCLUSIVEADDRUSE, the same
strategy seems to work too, according to Windows SDK headers and MinGW
WinAPI headers.

The current patch adds an option to allowing/disallowing address:port
reuse. One remark:
- currently both nonexisting SO_xxx and setsockopt failure are fatal and
  MHD_start_daemon fails. That may be too harsh -- maybe the
  MHD_OPTION_LISTENING_ADDRESS_REUSE should be only a hint.

  Nevertheless, as one can freely not use
  MHD_OPTION_LISTENING_ADDRESS_REUSE option, I chose the "fail on error"
  behaviour.

Thanks,
cheers,
Milan Straka

Original patch modified to get rid of some redundant USE_DEBUG
checks, fix indentation, and #ifndef SO_REUSEPORT on Linux,
we try be #defining it to 15 ourselves.



Diffstat:
MChangeLog | 5+++++
Mdoc/libmicrohttpd.texi | 14++++++++++++++
Msrc/include/microhttpd.h | 10+++++++++-
Msrc/microhttpd/connection.c | 3++-
Msrc/microhttpd/connection_https.c | 6++++--
Msrc/microhttpd/daemon.c | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/microhttpd/internal.c | 2+-
Msrc/microhttpd/internal.h | 9+++++++++
8 files changed, 212 insertions(+), 76 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,8 @@ +Wed Oct 29 20:45:21 CET 2014 + Adding MHD_OPTION_LISTENING_ADDRESS_REUSE option allowing clients + to force allowing re-use of the address:port combination + (SO_REUSEPORT). -MS + Wed Oct 29 16:27:05 CET 2014 Adding MHD_DAEMON_INFO_CURRENT_CONNECTIONS to allow clients to query the number of active connections. -MS diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi @@ -856,6 +856,20 @@ by the HTTPS daemon for key exchange. This option must be followed by a @code{const char *} argument. The argument would be a zero-terminated string with a PEM encoded PKCS3 DH parameters structure suitable for passing to @code{gnutls_dh_parms_import_pkcs3}. + +@item MHD_OPTION_LISTENING_ADDRESS_REUSE +@cindex bind, restricting bind +@cindex reusing listening address +This option must be followed by a @code{unsigned int} argument. +If this option is present and true (nonzero) parameter is given, allow reusing +the address:port of the listening socket (using @code{SO_REUSEPORT} on most +platforms, and @code{SO_REUSEADDR} on Windows). If a false (zero) parameter is +given, disallow reusing the the address:port of the listening socket (this +usually requires no special action, but @code{SO_EXCLUSIVEADDRUSE} is needed on +Windows). If this option is not present, default behaviour is undefined +(currently, @code{SO_REUSEADDR} is used on all platforms, which disallows +address:port reusing with the exception of Windows). + @end table @end deftp diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -846,8 +846,16 @@ enum MHD_OPTION * HTTPS daemon for key exchange. * This option must be followed by a `const char *` argument. */ - MHD_OPTION_HTTPS_MEM_DHPARAMS = 24 + MHD_OPTION_HTTPS_MEM_DHPARAMS = 24, + /** + * If present and set to true, allow reusing address:port socket + * (by using SO_REUSEPORT on most platform, or platform-specific ways). + * If present and set to false, disallow reusing address:port socket + * (does nothing on most plaform, but uses SO_EXCLUSIVEADDRUSE on Windows). + * This option must be followed by a `unsigned int` argument. + */ + MHD_OPTION_LISTENING_ADDRESS_REUSE = 25, }; diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -1306,7 +1306,8 @@ parse_cookie_header (struct MHD_Connection *connection) if (NULL == cpy) { #if HAVE_MESSAGES - MHD_DLOG (connection->daemon, "Not enough memory to parse cookies!\n"); + MHD_DLOG (connection->daemon, + "Not enough memory to parse cookies!\n"); #endif transmit_error_response (connection, MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, REQUEST_TOO_BIG); diff --git a/src/microhttpd/connection_https.c b/src/microhttpd/connection_https.c @@ -134,8 +134,10 @@ MHD_tls_connection_handle_idle (struct MHD_Connection *connection) unsigned int timeout; #if DEBUG_STATES - MHD_DLOG (connection->daemon, "%s: state: %s\n", - __FUNCTION__, MHD_state_to_string (connection->state)); + MHD_DLOG (connection->daemon, + "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (connection->state)); #endif timeout = connection->connection_timeout; if ( (timeout != 0) && (timeout <= (MHD_monotonic_time() - connection->last_activity))) diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -552,7 +552,8 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon) return 0; #endif #if HAVE_MESSAGES - MHD_DLOG (daemon, "You need to specify a certificate and key location\n"); + MHD_DLOG (daemon, + "You need to specify a certificate and key location\n"); #endif return -1; } @@ -736,7 +737,9 @@ MHD_get_fdset2 (struct MHD_Daemon *daemon, #if DEBUG_CONNECT #if HAVE_MESSAGES if (NULL != max_fd) - MHD_DLOG (daemon, "Maximum socket in select set: %d\n", *max_fd); + MHD_DLOG (daemon, + "Maximum socket in select set: %d\n", + *max_fd); #endif #endif return MHD_YES; @@ -826,7 +829,7 @@ MHD_handle_connection (void *data) { #if HAVE_MESSAGES MHD_DLOG (con->daemon, - "Can't add FD to fd_set\n"); + "Can't add FD to fd_set\n"); #endif goto exit; } @@ -1200,7 +1203,9 @@ internal_add_connection (struct MHD_Daemon *daemon, #if HAVE_MESSAGES #if DEBUG_CONNECT - MHD_DLOG (daemon, "Accepted connection on socket %d\n", client_socket); + MHD_DLOG (daemon, + "Accepted connection on socket %d\n", + client_socket); #endif #endif if ( (daemon->connections == daemon->connection_limit) || @@ -1226,7 +1231,8 @@ internal_add_connection (struct MHD_Daemon *daemon, { #if DEBUG_CLOSE #if HAVE_MESSAGES - MHD_DLOG (daemon, "Connection rejected, closing connection\n"); + MHD_DLOG (daemon, + "Connection rejected, closing connection\n"); #endif #endif if (0 != MHD_socket_close_ (client_socket)) @@ -1415,7 +1421,8 @@ internal_add_connection (struct MHD_Daemon *daemon, { eno = errno; #if HAVE_MESSAGES - MHD_DLOG (daemon, "Failed to create a thread: %s\n", + MHD_DLOG (daemon, + "Failed to create a thread: %s\n", MHD_strerror_ (res_thread_create)); #endif goto cleanup; @@ -1839,7 +1846,9 @@ MHD_accept_connection (struct MHD_Daemon *daemon) #endif #if HAVE_MESSAGES #if DEBUG_CONNECT - MHD_DLOG (daemon, "Accepted connection on socket %d\n", s); + MHD_DLOG (daemon, + "Accepted connection on socket %d\n", + s); #endif #endif (void) internal_add_connection (daemon, s, @@ -1961,7 +1970,8 @@ MHD_get_timeout (struct MHD_Daemon *daemon, if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) { #if HAVE_MESSAGES - MHD_DLOG (daemon, "Illegal call to MHD_get_timeout\n"); + MHD_DLOG (daemon, + "Illegal call to MHD_get_timeout\n"); #endif return MHD_NO; } @@ -2196,7 +2206,9 @@ MHD_select (struct MHD_Daemon *daemon, if (EINTR == MHD_socket_errno_) return MHD_YES; #if HAVE_MESSAGES - MHD_DLOG (daemon, "select failed: %s\n", MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "select failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -2401,7 +2413,9 @@ MHD_poll_listen_socket (struct MHD_Daemon *daemon, if (EINTR == MHD_socket_errno_) return MHD_YES; #if HAVE_MESSAGES - MHD_DLOG (daemon, "poll failed: %s\n", MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "poll failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -2492,10 +2506,9 @@ MHD_epoll (struct MHD_Daemon *daemon, &event)) { #if HAVE_MESSAGES - if (0 != (daemon->options & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Call to epoll_ctl failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -2544,10 +2557,9 @@ MHD_epoll (struct MHD_Daemon *daemon, if (EINTR == MHD_socket_errno_) return MHD_YES; #if HAVE_MESSAGES - if (0 != (daemon->options & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Call to epoll_wait failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Call to epoll_wait failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -2994,7 +3006,8 @@ parse_options_va (struct MHD_Daemon *daemon, if (gnutls_dh_params_init (&daemon->https_mem_dhparams) < 0) { #if HAVE_MESSAGES - MHD_DLOG(daemon, "Error initializing DH parameters\n"); + MHD_DLOG(daemon, + "Error initializing DH parameters\n"); #endif return MHD_NO; } @@ -3004,7 +3017,8 @@ parse_options_va (struct MHD_Daemon *daemon, GNUTLS_X509_FMT_PEM) < 0) { #if HAVE_MESSAGES - MHD_DLOG(daemon, "Bad Diffie-Hellman parameters format\n"); + MHD_DLOG(daemon, + "Bad Diffie-Hellman parameters format\n"); #endif gnutls_dh_params_deinit (daemon->https_mem_dhparams); return MHD_NO; @@ -3028,7 +3042,7 @@ parse_options_va (struct MHD_Daemon *daemon, ret = gnutls_priority_init (&daemon->priority_cache, pstr = va_arg (ap, const char*), NULL); - if (ret != GNUTLS_E_SUCCESS) + if (GNUTLS_E_SUCCESS != ret) { #if HAVE_MESSAGES MHD_DLOG (daemon, @@ -3084,6 +3098,9 @@ parse_options_va (struct MHD_Daemon *daemon, daemon->fastopen_queue_size = va_arg (ap, unsigned int); break; #endif + case MHD_OPTION_LISTENING_ADDRESS_REUSE: + daemon->listening_address_reuse = va_arg (ap, unsigned int) ? 1 : -1; + break; case MHD_OPTION_ARRAY: oa = va_arg (ap, struct MHD_OptionItem*); i = 0; @@ -3109,6 +3126,7 @@ parse_options_va (struct MHD_Daemon *daemon, case MHD_OPTION_PER_IP_CONNECTION_LIMIT: case MHD_OPTION_THREAD_POOL_SIZE: case MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE: + case MHD_OPTION_LISTENING_ADDRESS_REUSE: if (MHD_YES != parse_options (daemon, servaddr, opt, @@ -3254,10 +3272,9 @@ setup_epoll_to_listen (struct MHD_Daemon *daemon) if (-1 == daemon->epoll_fd) { #if HAVE_MESSAGES - if (0 != (daemon->options & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Call to epoll_create1 failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Call to epoll_create1 failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -3274,10 +3291,9 @@ setup_epoll_to_listen (struct MHD_Daemon *daemon) &event)) { #if HAVE_MESSAGES - if (0 != (daemon->options & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Call to epoll_ctl failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -3293,10 +3309,9 @@ setup_epoll_to_listen (struct MHD_Daemon *daemon) &event)) { #if HAVE_MESSAGES - if (0 != (daemon->options & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Call to epoll_ctl failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + MHD_socket_last_strerr_ ()); #endif return MHD_NO; } @@ -3379,6 +3394,7 @@ MHD_start_daemon_va (unsigned int flags, } #endif daemon->socket_fd = MHD_INVALID_SOCKET; + daemon->listening_address_reuse = 0; daemon->options = (enum MHD_OPTION) flags; #if WINDOWS /* Winsock is broken with respect to 'shutdown'; @@ -3553,25 +3569,111 @@ MHD_start_daemon_va (unsigned int flags, if (MHD_INVALID_SOCKET == socket_fd) { #if HAVE_MESSAGES - if (0 != (flags & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Call to socket failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Call to socket failed: %s\n", + MHD_socket_last_strerr_ ()); #endif goto free_and_fail; } - if ( (0 > setsockopt (socket_fd, - SOL_SOCKET, - SO_REUSEADDR, - (void*)&on, sizeof (on))) && - (0 != (flags & MHD_USE_DEBUG)) ) - { + + /* Apply the socket options according to listening_address_reuse. */ + if (0 == daemon->listening_address_reuse) + { + /* No user requirement, use "traditional" default SO_REUSEADDR, + and do not fail if it doesn't work */ + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_REUSEADDR, + (void*)&on, sizeof (on))) #if HAVE_MESSAGES - MHD_DLOG (daemon, - "setsockopt failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); #endif - } + } + else if (daemon->listening_address_reuse > 0) + { + /* User requested to allow reusing listening address:port. + * Use SO_REUSEADDR on Windows and SO_REUSEPORT on most platforms. + * Fail if SO_REUSEPORT does not exist or setsockopt fails. + */ +#ifdef _WIN32 + /* SO_REUSEADDR on W32 has the same semantics + as SO_REUSEPORT on BSD/Linux */ + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_REUSEADDR, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } +#else +#ifndef SO_REUSEPORT +#ifdef LINUX +/* Supported since Linux 3.9, but often not present (or commented out) + in the headers at this time; but 15 is reserved for this and + thus should be safe to use. */ +#define SO_REUSEPORT 15 +#endif +#endif +#ifdef SO_REUSEPORT + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_REUSEPORT, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } +#else + /* we're supposed to allow address:port re-use, but + on this platform we cannot; fail hard */ +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Cannot allow listening address reuse: SO_REUSEPORT not defined\n"); +#endif + goto free_and_fail; +#endif +#endif + } + else /* if (daemon->listening_address_reuse < 0) */ + { + /* User requested to disallow reusing listening address:port. + * Do nothing except for Windows where SO_EXCLUSIVEADDRUSE + * is used. Fail if it does not exist or setsockopt fails. + */ +#ifdef _WIN32 +#ifdef SO_EXCLUSIVEADDRUSE + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_EXCLUSIVEADDRUSE, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } +#else /* SO_EXCLUSIVEADDRUSE not defined on W32? */ +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Cannot disallow listening address reuse: SO_EXCLUSIVEADDRUSE not defined\n"); +#endif + goto free_and_fail; +#endif +#endif /* _WIN32 */ + } /* check for user supplied sockaddr */ #if HAVE_INET6 @@ -3621,28 +3723,24 @@ MHD_start_daemon_va (unsigned int flags, const char #endif on = (MHD_USE_DUAL_STACK != (flags & MHD_USE_DUAL_STACK)); - if ( (0 > setsockopt (socket_fd, - IPPROTO_IPV6, IPV6_V6ONLY, - &on, sizeof (on))) && - (0 != (flags & MHD_USE_DEBUG)) ) - { + if (0 > setsockopt (socket_fd, + IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof (on))) #if HAVE_MESSAGES - MHD_DLOG (daemon, - "setsockopt failed: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); #endif - } #endif #endif } if (-1 == bind (socket_fd, servaddr, addrlen)) { #if HAVE_MESSAGES - if (0 != (flags & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Failed to bind to port %u: %s\n", - (unsigned int) port, - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Failed to bind to port %u: %s\n", + (unsigned int) port, + MHD_socket_last_strerr_ ()); #endif if (0 != MHD_socket_close_ (socket_fd)) MHD_PANIC ("close failed\n"); @@ -3686,10 +3784,9 @@ MHD_start_daemon_va (unsigned int flags, if (listen (socket_fd, 32) < 0) { #if HAVE_MESSAGES - if (0 != (flags & MHD_USE_DEBUG)) - MHD_DLOG (daemon, - "Failed to listen for connections: %s\n", - MHD_socket_last_strerr_ ()); + MHD_DLOG (daemon, + "Failed to listen for connections: %s\n", + MHD_socket_last_strerr_ ()); #endif if (0 != MHD_socket_close_ (socket_fd)) MHD_PANIC ("close failed\n"); @@ -3705,11 +3802,10 @@ MHD_start_daemon_va (unsigned int flags, (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY)) ) ) { #if HAVE_MESSAGES - if ((flags & MHD_USE_DEBUG) != 0) - MHD_DLOG (daemon, - "Socket descriptor larger than FD_SETSIZE: %d > %d\n", - socket_fd, - FD_SETSIZE); + MHD_DLOG (daemon, + "Socket descriptor larger than FD_SETSIZE: %d > %d\n", + socket_fd, + FD_SETSIZE); #endif if (0 != MHD_socket_close_ (socket_fd)) MHD_PANIC ("close failed\n"); @@ -4123,7 +4219,8 @@ MHD_stop_daemon (struct MHD_Daemon *daemon) #if DEBUG_CLOSE #if HAVE_MESSAGES - MHD_DLOG (daemon, "MHD listen socket shutdown\n"); + MHD_DLOG (daemon, + "MHD listen socket shutdown\n"); #endif #endif diff --git a/src/microhttpd/internal.c b/src/microhttpd/internal.c @@ -95,7 +95,7 @@ MHD_DLOG (const struct MHD_Daemon *daemon, const char *format, ...) { va_list va; - if ((daemon->options & MHD_USE_DEBUG) == 0) + if (0 == (daemon->options & MHD_USE_DEBUG)) return; va_start (va, format); daemon->custom_error_log (daemon->custom_error_log_cls, format, va); diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -1099,6 +1099,15 @@ struct MHD_Daemon */ MHD_socket socket_fd; + /** + * Whether to allow/disallow/ignore reuse of listening address. + * The semantics is the following: + * 0: ignore (user did not ask for neither allow/disallow, use SO_REUSEADDR) + * >0: allow (use SO_REUSEPORT on most platforms, SO_REUSEADDR on Windows) + * <0: disallow (mostly no action, SO_EXCLUSIVEADDRUSE on Windows) + */ + int listening_address_reuse; + #if EPOLL_SUPPORT /** * File descriptor associated with our epoll loop.