/* This file is part of libmicrohttpd Copyright (C) 2007 Christian Grothoff Copyright (C) 2014-2021 Evgeny Grin (Karlson2k) libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. libmicrohttpd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libmicrohttpd; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file test_iplimit.c * @brief Testcase for libmicrohttpd limits per IP * @author Christian Grothoff * @author Karlson2k (Evgeny Grin) */ #include "MHD_config.h" #include "platform.h" #include #include #include #include #include #include "mhd_has_in_name.h" #ifndef WINDOWS #include #endif #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif /* !WIN32_LEAN_AND_MEAN */ #include #endif #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 #undef MHD_CPU_COUNT #endif #if ! defined(MHD_CPU_COUNT) #define MHD_CPU_COUNT 2 #endif static int oneone; struct CBC { char *buf; size_t pos; size_t size; }; static size_t copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) { struct CBC *cbc = ctx; if (cbc->pos + size * nmemb > cbc->size) return 0; /* overflow */ memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); cbc->pos += size * nmemb; return size * nmemb; } static enum MHD_Result ahc_echo (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **req_cls) { static int ptr; const char *me = cls; struct MHD_Response *response; enum MHD_Result ret; (void) version; (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ if (0 != strcmp (me, method)) return MHD_NO; /* unexpected method */ if (&ptr != *req_cls) { *req_cls = &ptr; return MHD_YES; } *req_cls = NULL; response = MHD_create_response_from_buffer (strlen (url), (void *) url, MHD_RESPMEM_MUST_COPY); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); if (ret == MHD_NO) abort (); return ret; } static int testMultithreadedGet () { struct MHD_Daemon *d; char buf[2048]; int k; unsigned int success; unsigned int failure; int port; if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) port = 0; else { port = 1260; if (oneone) port += 5; } /* Test only valid for HTTP/1.1 (uses persistent connections) */ if (! oneone) return 0; d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 2, MHD_OPTION_END); if (d == NULL) return 16; if (0 == port) { const union MHD_DaemonInfo *dinfo; dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); if ((NULL == dinfo) || (0 == dinfo->port) ) { MHD_stop_daemon (d); return 32; } port = (int) dinfo->port; } for (k = 0; k < 3; ++k) { struct CBC cbc[3]; CURL *cenv[3]; int i; success = 0; failure = 0; for (i = 0; i < 3; ++i) { CURL *c; CURLcode errornum; cenv[i] = c = curl_easy_init (); cbc[i].buf = buf; cbc[i].size = 2048; cbc[i].pos = 0; curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); curl_easy_setopt (c, CURLOPT_PORT, (long) port); curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc[i]); curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); curl_easy_setopt (c, CURLOPT_FORBID_REUSE, 0L); curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); /* NOTE: use of CONNECTTIMEOUT without also * setting NOSIGNAL results in really weird * crashes on my system! */ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); errornum = curl_easy_perform (c); if (CURLE_OK == errornum) success++; else failure++; } /* Cleanup the environments */ for (i = 0; i < 3; ++i) curl_easy_cleanup (cenv[i]); if ( (2 != success) || (1 != failure) ) { fprintf (stderr, "Unexpected number of success (%u) or failure (%u)\n", success, failure); MHD_stop_daemon (d); return 32; } (void) sleep (2); for (i = 0; i < 2; ++i) { if (cbc[i].pos != strlen ("/hello_world")) { MHD_stop_daemon (d); return 64; } if (0 != strncmp ("/hello_world", cbc[i].buf, strlen ("/hello_world"))) { MHD_stop_daemon (d); return 128; } } } MHD_stop_daemon (d); return 0; } static int testMultithreadedPoolGet () { struct MHD_Daemon *d; char buf[2048]; int k; int port; if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) port = 0; else { port = 1261; if (oneone) port += 5; } /* Test only valid for HTTP/1.1 (uses persistent connections) */ if (! oneone) return 0; d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 2, MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT, MHD_OPTION_END); if (d == NULL) return 16; if (0 == port) { const union MHD_DaemonInfo *dinfo; dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); if ((NULL == dinfo) || (0 == dinfo->port) ) { MHD_stop_daemon (d); return 32; } port = (int) dinfo->port; } for (k = 0; k < 3; ++k) { struct CBC cbc[3]; CURL *cenv[3]; int i; for (i = 0; i < 3; ++i) { CURL *c; CURLcode errornum; cenv[i] = c = curl_easy_init (); cbc[i].buf = buf; cbc[i].size = 2048; cbc[i].pos = 0; curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); curl_easy_setopt (c, CURLOPT_PORT, (long) port); curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc[i]); curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); curl_easy_setopt (c, CURLOPT_FORBID_REUSE, 0L); curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); /* NOTE: use of CONNECTTIMEOUT without also * setting NOSIGNAL results in really weird * crashes on my system! */ curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); errornum = curl_easy_perform (c); if ( ( (CURLE_OK != errornum) && (i < 2) ) || ( (CURLE_OK == errornum) && (i == 2) ) ) { int j; /* First 2 should succeed */ if (i < 2) fprintf (stderr, "curl_easy_perform failed: `%s'\n", curl_easy_strerror (errornum)); /* Last request should have failed */ else fprintf (stderr, "No error on IP address over limit\n"); for (j = 0; j < i; ++j) curl_easy_cleanup (cenv[j]); MHD_stop_daemon (d); return 32; } } /* Cleanup the environments */ for (i = 0; i < 3; ++i) curl_easy_cleanup (cenv[i]); (void) sleep (2); for (i = 0; i < 2; ++i) { if (cbc[i].pos != strlen ("/hello_world")) { MHD_stop_daemon (d); return 64; } if (0 != strncmp ("/hello_world", cbc[i].buf, strlen ("/hello_world"))) { MHD_stop_daemon (d); return 128; } } } MHD_stop_daemon (d); return 0; } int main (int argc, char *const *argv) { unsigned int errorCount = 0; (void) argc; /* Unused. Silent compiler warning. */ if ((NULL == argv) || (0 == argv[0])) return 99; oneone = has_in_name (argv[0], "11"); if (0 != curl_global_init (CURL_GLOBAL_WIN32)) return 2; errorCount |= testMultithreadedGet (); errorCount |= testMultithreadedPoolGet (); if (errorCount != 0) fprintf (stderr, "Error (code: %u)\n", errorCount); curl_global_cleanup (); return (0 == errorCount) ? 0 : 1; /* 0 == pass */ }