libmicrohttpd

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

commit 1a7fb032ba128c81136f92ce4cd71449916a0486
parent 9f0d5f6eb158a3908249084b7dd59e354b74bcde
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Tue, 30 Nov 2021 09:28:25 +0300

test_client_put_stop: support rate limiting of RST

RST rate limiting is required for FreeBSD.

Diffstat:
Mconfigure.ac | 40++++++++++++++++++++++++++++++++++++++++
Mm4/mhd_shutdown_socket_trigger.m4 | 6++++--
Msrc/microhttpd/test_client_put_stop.c | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
3 files changed, 230 insertions(+), 12 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -135,6 +135,30 @@ AC_CHECK_HEADER([[search.h]], AM_CONDITIONAL([MHD_HAVE_TSEARCH], [[test "x$ac_cv_header_search_h" = xyes && test "x$HAVE_TSEARCH" = "x1" && test "x$REPLACE_TSEARCH" != "x1"]]) +# Optional headers used for tests +AC_CHECK_HEADERS([sys/sysctl.h netinet/ip_icmp.h netinet/icmp_var.h], [], [], + [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif /* HAVE_SYS_TYPES_H */ +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif /* HAVE_SYS_SYSCTL_H */ +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif /* HAVE_NETINET_IP_H */ +#ifdef HAVE_NETINET_IP_ICMP_H +#include <netinet/ip_icmp.h> +#endif /* HAVE_NETINET_IP_ICMP_H */ + ]] +) + # Checks for gettext. m4_ifdef([AM_GNU_GETTEXT], [ AS_VAR_SET_IF([enable_nls], [], [[enable_nls=no]]) @@ -1881,6 +1905,22 @@ AC_MSG_RESULT($have_inet6) MHD_CHECK_FUNC([[sysconf]], [[#include <unistd.h>]], [[long a = sysconf(0); if (a) return 1;]]) +MHD_CHECK_FUNC([[sysctl]], [[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif + ]], [[int mib[2] = {CTL_KERN, KERN_MAXPROC}; if (sysctl(mib, 2, NULL, NULL, NULL, 0)) return 1;]] +) + +MHD_CHECK_FUNC([[usleep]], [[#include <unistd.h>]], [[usleep(100000);]]) +MHD_CHECK_FUNC([[nanosleep]], [[#include <time.h>]], [[struct timespec ts2, ts1 = {0, 0}; nanosleep(&ts1, &ts2);]]) + HIDDEN_VISIBILITY_CFLAGS="" AS_CASE(["$host"], [*-*-mingw*],[ diff --git a/m4/mhd_shutdown_socket_trigger.m4 b/m4/mhd_shutdown_socket_trigger.m4 @@ -26,8 +26,10 @@ AC_DEFUN([MHD_CHECK_SOCKET_SHUTDOWN_TRIGGER],[dnl AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([AX_PTHREAD])dnl AC_CHECK_HEADERS([sys/time.h],[AC_CHECK_FUNCS([gettimeofday])],[], [AC_INCLUDES_DEFAULT]) - AC_CHECK_HEADERS([time.h],[AC_CHECK_FUNCS([nanosleep])],[], [AC_INCLUDES_DEFAULT]) - AC_CHECK_HEADERS([unistd.h],[AC_CHECK_FUNCS([usleep])],[], [AC_INCLUDES_DEFAULT]) + dnl AC_CHECK_HEADERS([time.h],[AC_CHECK_FUNCS([nanosleep])],[], [AC_INCLUDES_DEFAULT]) + MHD_CHECK_FUNC([[usleep]], [[#include <unistd.h>]], [[usleep(100000);]]) + dnl AC_CHECK_HEADERS([unistd.h],[AC_CHECK_FUNCS([usleep])],[], [AC_INCLUDES_DEFAULT]) + MHD_CHECK_FUNC([[nanosleep]], [[#include <time.h>]], [[struct timespec ts2, ts1 = {0, 0}; nanosleep(&ts1, &ts2);]]) AC_CHECK_HEADERS([string.h sys/types.h sys/socket.h netinet/in.h time.h sys/select.h netinet/tcp.h],[],[], [AC_INCLUDES_DEFAULT]) AC_CACHE_CHECK([[whether shutdown of listen socket triggers select()]], [[mhd_cv_host_shtdwn_trgr_select]], [dnl diff --git a/src/microhttpd/test_client_put_stop.c b/src/microhttpd/test_client_put_stop.c @@ -54,6 +54,32 @@ #include <signal.h> #endif /* HAVE_SIGNAL_H */ +#ifdef HAVE_SYSCTL +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif /* HAVE_SYS_TYPES_H */ +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif /* HAVE_SYS_SYSCTL_H */ +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif /* HAVE_NETINET_IP_H */ +#ifdef HAVE_NETINET_IP_ICMP_H +#include <netinet/ip_icmp.h> +#endif /* HAVE_NETINET_IP_ICMP_H */ +#ifdef HAVE_NETINET_ICMP_VAR_H +#include <netinet/icmp_var.h> +#endif /* HAVE_NETINET_ICMP_VAR_H */ +#endif /* HAVE_SYSCTL */ + +#include <stdio.h> + #include "mhd_sockets.h" /* only macros used */ #include "test_helpers.h" #include "mhd_assert.h" @@ -90,6 +116,9 @@ /* Could be increased to facilitate debugging */ #define TIMEOUTS_VAL 5 +/* Time in ms to wait for final packets to be delivered */ +#define FINAL_PACKETS_MS 20 + #define EXPECTED_URI_BASE_PATH "/a" #define REQ_HOST "localhost" @@ -184,6 +213,46 @@ _mhdErrorExit_func (const char *errDesc, const char *funcName, int lineNum) } +/** + * Pause execution for specified number of milliseconds. + * @param ms the number of milliseconds to sleep + */ +void +_MHD_sleep (uint32_t ms) +{ +#if defined(_WIN32) + Sleep (ms); +#elif defined(HAVE_NANOSLEEP) + struct timespec slp = {ms / 1000, (ms % 1000) * 1000000}; + struct timespec rmn; + int num_retries = 0; + while (0 != nanosleep (&slp, &rmn)) + { + if (EINTR != errno) + externalErrorExit (); + if (num_retries++ > 8) + break; + slp = rmn; + } +#elif defined(HAVE_USLEEP) + uint64_t us = ms * 1000; + do + { + uint64_t this_sleep; + if (999999 < us) + this_sleep = 999999; + else + this_sleep = us; + /* Ignore return value as it could be void */ + usleep (this_sleep); + us -= this_sleep; + } while (us > 0); +#else + externalErrorExitDesc ("No sleep function available on this system"); +#endif +} + + /* Global parameters */ static int verbose; /**< Be verbose */ static int oneone; /**< If false use HTTP/1.0 for requests*/ @@ -195,6 +264,8 @@ static int use_hard_close; /**< Use socket close with RST at client sid static int by_step; /**< Send request byte-by-byte */ static int upl_chunked; /**< Use chunked encoding for request body */ +static unsigned int rate_limiter; /**< Maximum number of checks per second */ + static void test_global_init (void) { @@ -208,6 +279,57 @@ test_global_init (void) /* exit (77); */ #endif } + rate_limiter = 0; +#if defined(HAVE_SYSCTL) && defined(CTL_NET) && defined(PF_INET) && \ + defined(IPPROTO_ICMP) && defined(ICMPCTL_ICMPLIM) + if (use_hard_close) + { + int mib[4]; + int limit; + size_t limit_size = sizeof(limit); + mib[0] = CTL_NET; + mib[1] = PF_INET; + mib[2] = IPPROTO_ICMP; + mib[3] = ICMPCTL_ICMPLIM; + if ((0 != sysctl (mib, 4, &limit, &limit_size, NULL, 0)) || + (sizeof(limit) != limit_size) ) + externalErrorExitDesc ("Cannot get RST rate limit value"); + if (limit > 0) + { +#ifndef _MHD_HEAVY_TESTS + fprintf (stderr, "This system has limits on number of RST packet" + " per second (%d).\nThis test will be used only if configured " + "with '--enable-heavy-test'.\n", limit); + exit (77); +#else /* _MHD_HEAVY_TESTS */ + int test_limit; /**< Maximum number of checks per second */ + test_limit = limit - limit / 10; /* Add some space to not hit the limiter */ + test_limit /= 4; /* Assume that all four tests with 'hard_close' run in parallel */ + test_limit -= 5; /* Add some more space to not hit the limiter */ + test_limit /= 3; /* Use only one third of available limit */ + if (test_limit <= 0) + { + fprintf (stderr, "System limit for 'net.inet.icmp.icmplim' is " + "too strict for this test (value: %d).\n", limit); + exit (77); + } + if (verbose) + { + printf ("Limiting number of checks to %d checks/second.\n", test_limit); + fflush (stdout); + } + rate_limiter = (unsigned int) test_limit; +#if ! defined(HAVE_USLEEP) && ! defined(HAVE_NANOSLEEP) && ! defined(_WIN32) + fprintf (stderr, "Sleep function is required for this test, " + "but not available on this system.\n"); + exit (77); +#endif +#endif /* _MHD_HEAVY_TESTS */ + } + } +#endif /* HAVE_SYSCTL && CTL_NET && PF_INET && + IPPROTO_ICMP && ICMPCTL_ICMPLIM */ + } @@ -1229,6 +1351,8 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) const union MHD_DaemonInfo *di; MHD_socket lstn_sk; int client_accepted; + int full_req_recieved; + int full_req_sent; di = MHD_get_daemon_info (d, MHD_DAEMON_INFO_LISTEN_FD); if (NULL == di) @@ -1240,6 +1364,7 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) _MHD_dumbClient_start_connect (clnt); + full_req_recieved = 0; start = time (NULL); do { @@ -1247,6 +1372,7 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) fd_set ws; fd_set es; MHD_socket maxMhdSk; + int num_ready; maxMhdSk = MHD_INVALID_SOCKET; FD_ZERO (&rs); @@ -1257,6 +1383,7 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) /* client has finished, check whether MHD is still * processing any connections */ unsigned long long to; + full_req_sent = 1; if (client_accepted && (MHD_YES != MHD_get_timeout (d, &to))) { ret = 0; @@ -1264,12 +1391,25 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) } } else - _MHD_dumbClient_get_fdsets (clnt, &maxMhdSk, &rs, &ws, &es); + { + full_req_sent = _MHD_dumbClient_is_req_sent (clnt); + if ((! full_req_sent) || full_req_recieved || (0 == rate_limiter)) + _MHD_dumbClient_get_fdsets (clnt, &maxMhdSk, &rs, &ws, &es); + } if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxMhdSk)) mhdErrorExitDesc ("MHD_get_fdset() failed"); - tv.tv_sec = 1; - tv.tv_usec = 250 * 1000; - if (-1 == select (maxMhdSk + 1, &rs, &ws, &es, &tv)) + if ((! full_req_sent) || full_req_recieved || (0 == rate_limiter)) + { + tv.tv_sec = 1; + tv.tv_usec = 250 * 1000; + } + else + { /* Request completely sent but not yet fully received */ + tv.tv_sec = 0; + tv.tv_usec = FINAL_PACKETS_MS * 1000; + } + num_ready = select (maxMhdSk + 1, &rs, &ws, &es, &tv); + if (-1 == num_ready) { #ifdef MHD_POSIX_SOCKETS if (EINTR != errno) @@ -1282,6 +1422,11 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) #endif continue; } + if (0 == num_ready) + { /* select() finished by timeout, looks like no more packets are pending */ + if (full_req_sent && (! full_req_recieved)) + full_req_recieved = 1; + } if (MHD_YES != MHD_run_from_select (d, &rs, &ws, &es)) mhdErrorExitDesc ("MHD_run_from_select() failed"); if (! client_accepted) @@ -1290,11 +1435,15 @@ performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) { /* Do not close the socket on client side until * MHD is accepted and processed the socket. */ - if (! _MHD_dumbClient_is_req_sent (clnt) || - (client_accepted && ! FD_ISSET (lstn_sk, &rs))) + if (! full_req_sent || (client_accepted && ! FD_ISSET (lstn_sk, &rs))) { - if (_MHD_dumbClient_process_from_fdsets (clnt, &rs, &ws, &es)) - clnt = NULL; + if ((! full_req_sent) || full_req_recieved || (0 == rate_limiter)) + { + /* When rate limiter is enabled, all sent packets must be received + * before client close connection to avoid RST for every ACK. */ + if (_MHD_dumbClient_process_from_fdsets (clnt, &rs, &ws, &es)) + clnt = NULL; + } } } /* Use double timeout value here so MHD would be able to catch timeout @@ -1403,6 +1552,7 @@ performTestQueries (struct MHD_Daemon *d, int d_port, int ret = 0; /* Return value */ size_t req_total_size; size_t limit_send_size; + size_t inc_size; int expected_reason; int found_right_reason; @@ -1441,11 +1591,35 @@ performTestQueries (struct MHD_Daemon *d, int d_port, MHD_REQUEST_TERMINATED_READ_ERROR : MHD_REQUEST_TERMINATED_CLIENT_ABORT; found_right_reason = 0; + if (0 != rate_limiter) + { + if (verbose) + { + printf ("Pausing for rate limiter..."); + fflush (stdout); + } + _MHD_sleep (1150); /* Just a bit more than one second */ + if (verbose) + { + printf (" OK\n"); + fflush (stdout); + } + inc_size = ((req_total_size - 1) + (rate_limiter - 1)) / rate_limiter; + if (0 == inc_size) + inc_size = 1; + } + else + inc_size = 1; + start = time (NULL); - for (limit_send_size = 1; limit_send_size < req_total_size; limit_send_size++) + for (limit_send_size = 1; limit_send_size < req_total_size; + limit_send_size += inc_size) { int test_succeed; test_succeed = 0; + /* Make sure that maximum size is tested */ + if (req_total_size - inc_size < limit_send_size) + limit_send_size = req_total_size - 1; qParam.total_send_max = limit_send_size; /* To be updated by callbacks */ ahc_param->cb_called = 0; @@ -1501,7 +1675,9 @@ performTestQueries (struct MHD_Daemon *d, int d_port, term_result, sckt_result); } - if (time (NULL) - start > TIMEOUTS_VAL * 25) + if (time (NULL) - start > + (time_t) ((TIMEOUTS_VAL * 25) + + (rate_limiter * FINAL_PACKETS_MS) / 1000 + 1)) { ret |= 1 << 2; fprintf (stderr, "FAILED: Test total time exceeded.\n");