aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-01-19 23:58:00 +0100
committerChristian Grothoff <christian@grothoff.org>2020-01-19 23:58:07 +0100
commit26af6b23281e952553892c73f8e01f2b1dbb7ab7 (patch)
tree3ff21b770e119524d420b4ed29b18ee146d92754
parent801592b4602065f05db555367921ebdbe08f0bce (diff)
add single-threaded mode to exchange HTTP for fuzzing
-rw-r--r--src/exchange/taler-exchange-httpd.c415
-rw-r--r--src/exchange/test_taler_exchange_httpd.conf2
-rw-r--r--src/exchange/test_taler_exchange_httpd_afl.sh8
3 files changed, 262 insertions, 163 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index cc1af224b..7663c4b75 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -606,6 +606,56 @@ exchange_serve_process_config ()
}
+/**
+ * Called when the main thread exits, writes out performance
+ * stats if requested.
+ */
+static void
+write_stats ()
+{
+ struct GNUNET_DISK_FileHandle *fh;
+ pid_t pid = getpid ();
+ char *benchmark_dir;
+ char *s;
+ struct rusage usage;
+
+ benchmark_dir = getenv ("GNUNET_BENCHMARK_DIR");
+ if (NULL == benchmark_dir)
+ return;
+ GNUNET_asprintf (&s,
+ "%s/taler-exchange-%llu-%llu.txt",
+ benchmark_dir,
+ (unsigned long long) pid);
+ fh = GNUNET_DISK_file_open (s,
+ (GNUNET_DISK_OPEN_WRITE
+ | GNUNET_DISK_OPEN_TRUNCATE
+ | GNUNET_DISK_OPEN_CREATE),
+ (GNUNET_DISK_PERM_USER_READ
+ | GNUNET_DISK_PERM_USER_WRITE));
+ GNUNET_free (s);
+ if (NULL == fh)
+ return; /* permission denied? */
+
+ /* Collect stats, summed up for all threads */
+ GNUNET_assert (0 ==
+ getrusage (RUSAGE_SELF,
+ &usage));
+ GNUNET_asprintf (&s,
+ "time_exchange sys %llu user %llu\n",
+ (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
+ + usage.ru_stime.tv_usec),
+ (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
+ + usage.ru_utime.tv_usec));
+ GNUNET_assert (GNUNET_SYSERR !=
+ GNUNET_DISK_file_write_blocking (fh,
+ s,
+ strlen (s)));
+ GNUNET_free (s);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_DISK_file_close (fh));
+}
+
+
/* Developer logic for supporting the `-f' option. */
#if HAVE_DEVELOPER
@@ -615,22 +665,24 @@ exchange_serve_process_config ()
*/
static char *input_filename;
+/**
+ * We finished handling the request and should now terminate.
+ */
+static int do_terminate;
/**
* Run 'nc' or 'ncat' as a fake HTTP client using #input_filename
* as the input for the request. If launching the client worked,
* run the #TEH_KS_loop() event loop as usual.
*
- * @return #GNUNET_OK
+ * @return child pid
*/
-static int
+static pid_t
run_fake_client ()
{
pid_t cld;
char ports[6];
int fd;
- int ret;
- int status;
if (0 == strcmp (input_filename,
"-"))
@@ -643,7 +695,7 @@ run_fake_client ()
"Failed to open `%s': %s\n",
input_filename,
strerror (errno));
- return GNUNET_SYSERR;
+ return -1;
}
/* Fake HTTP client request with #input_filename as input.
We do this using the nc tool. */
@@ -679,14 +731,7 @@ run_fake_client ()
if (0 != strcmp (input_filename,
"-"))
GNUNET_break (0 == close (fd));
- ret = TEH_KS_loop ();
- if (cld != waitpid (cld,
- &status,
- 0))
- fprintf (stderr,
- "Waiting for `nc' child failed: %s\n",
- strerror (errno));
- return ret;
+ return cld;
}
@@ -711,17 +756,103 @@ connection_done (void *cls,
(void) connection;
(void) socket_context;
/* We only act if the connection is closed. */
+ fprintf (stderr,
+ "Connection done!\n");
if (MHD_CONNECTION_NOTIFY_CLOSED != toe)
return;
/* This callback is also present if the option wasn't, so
make sure the option was actually set. */
if (NULL == input_filename)
return;
- /* We signal ourselves to terminate. */
- if (0 != kill (getpid (),
- SIGTERM))
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "kill");
+ do_terminate = GNUNET_YES;
+}
+
+
+/**
+ * Run the exchange to serve a single request only, without threads.
+ *
+ * @param fh listen socket
+ * @return #GNUNET_OK on success
+ */
+static int
+run_single_request (int fh)
+{
+ int ret;
+ pid_t cld;
+ int status;
+
+ /* run only the testfile input, then terminate */
+ mhd
+ = MHD_start_daemon (MHD_USE_PIPE_FOR_SHUTDOWN
+ | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
+ | MHD_USE_TCP_FASTOPEN,
+ (-1 == fh) ? serve_port : 0,
+ NULL, NULL,
+ &handle_mhd_request, NULL,
+ MHD_OPTION_LISTEN_SOCKET, fh,
+ MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 10,
+ MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs,
+ NULL,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
+ MHD_OPTION_NOTIFY_CONNECTION, &connection_done, NULL,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ fprintf (stderr,
+ "Failed to start HTTP server.\n");
+ return GNUNET_SYSERR;
+ }
+ cld = run_fake_client ();
+ if (-1 == cld)
+ return GNUNET_SYSERR;
+ /* run the event loop until #connection_done() was called */
+ while (GNUNET_NO == do_terminate)
+ {
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ struct timeval tv;
+ MHD_UNSIGNED_LONG_LONG timeout;
+ int maxsock = -1;
+ int have_tv;
+
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ if (MHD_YES !=
+ MHD_get_fdset (mhd,
+ &rs,
+ &ws,
+ &es,
+ &maxsock))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ have_tv = MHD_get_timeout (mhd,
+ &timeout);
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = 1000 * (timeout % 1000);
+ if (-1 == select (maxsock + 1,
+ &rs,
+ &ws,
+ &es,
+ have_tv ? &tv : NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ MHD_run (mhd);
+ }
+ if (cld != waitpid (cld,
+ &status,
+ 0))
+ fprintf (stderr,
+ "Waiting for `nc' child failed: %s\n",
+ strerror (errno));
+ return ret;
}
@@ -730,49 +861,114 @@ connection_done (void *cls,
/**
- * Called when the main thread exits, writes out performance
- * stats if requested.
+ * Run the ordinary multi-threaded main loop and the logic to
+ * wait for CTRL-C.
+ *
+ * @param fh listen socket
+ * @param argv command line arguments
+ * @return #GNUNET_OK on success
*/
-static void
-write_stats ()
+static int
+run_main_loop (int fh,
+ char *const *argv)
{
- struct GNUNET_DISK_FileHandle *fh;
- pid_t pid = getpid ();
- char *benchmark_dir;
- char *s;
- struct rusage usage;
-
- benchmark_dir = getenv ("GNUNET_BENCHMARK_DIR");
-
- if (NULL == benchmark_dir)
- return;
+ int ret;
- GNUNET_asprintf (&s, "%s/taler-exchange-%llu-%llu.txt",
- benchmark_dir,
- (unsigned long long) pid);
+ mhd
+ = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_PIPE_FOR_SHUTDOWN
+ | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
+ | MHD_USE_INTERNAL_POLLING_THREAD
+ | MHD_USE_TCP_FASTOPEN,
+ (-1 == fh) ? serve_port : 0,
+ NULL, NULL,
+ &handle_mhd_request, NULL,
+ MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 32,
+ MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 1024,
+ MHD_OPTION_LISTEN_SOCKET, fh,
+ MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs,
+ NULL,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ fprintf (stderr,
+ "Failed to start HTTP server.\n");
+ return GNUNET_SYSERR;
+ }
- fh = GNUNET_DISK_file_open (s,
- (GNUNET_DISK_OPEN_WRITE
- | GNUNET_DISK_OPEN_TRUNCATE
- | GNUNET_DISK_OPEN_CREATE),
- (GNUNET_DISK_PERM_USER_READ
- | GNUNET_DISK_PERM_USER_WRITE));
- GNUNET_assert (NULL != fh);
- GNUNET_free (s);
+ atexit (&write_stats);
+ ret = TEH_KS_loop ();
+ switch (ret)
+ {
+ case GNUNET_OK:
+ case GNUNET_SYSERR:
+ MHD_stop_daemon (mhd);
+ break;
+ case GNUNET_NO:
+ {
+ MHD_socket sock = MHD_quiesce_daemon (mhd);
+ pid_t chld;
+ int flags;
- /* Collect stats, summed up for all threads */
- GNUNET_assert (0 == getrusage (RUSAGE_SELF, &usage));
+ /* Set flags to make 'sock' inherited by child */
+ flags = fcntl (sock, F_GETFD);
+ GNUNET_assert (-1 != flags);
+ flags &= ~FD_CLOEXEC;
+ GNUNET_assert (-1 != fcntl (sock, F_SETFD, flags));
+ chld = fork ();
+ if (-1 == chld)
+ {
+ /* fork() failed, continue clean up, unhappily */
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ }
+ if (0 == chld)
+ {
+ char pids[12];
- GNUNET_asprintf (&s, "time_exchange sys %llu user %llu\n", \
- (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
- + usage.ru_stime.tv_usec),
- (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
- + usage.ru_utime.tv_usec));
- GNUNET_assert (GNUNET_SYSERR != GNUNET_DISK_file_write_blocking (fh, s,
- strlen (s)));
- GNUNET_free (s);
+ /* exec another taler-exchange-httpd, passing on the listen socket;
+ as in systemd it is expected to be on FD #3 */
+ if (3 != dup2 (sock, 3))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "dup2");
+ _exit (1);
+ }
+ /* Tell the child that it is the desired recipient for FD #3 */
+ GNUNET_snprintf (pids,
+ sizeof (pids),
+ "%u",
+ getpid ());
+ setenv ("LISTEN_PID", pids, 1);
+ setenv ("LISTEN_FDS", "1", 1);
+ /* Finally, exec the (presumably) more recent exchange binary */
+ execvp ("taler-exchange-httpd",
+ argv);
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "execvp");
+ _exit (1);
+ }
+ /* we're the original process, handle remaining contextions
+ before exiting; as the listen socket is no longer used,
+ close it here */
+ GNUNET_break (0 == close (sock));
+ while (0 != MHD_get_daemon_info (mhd,
+ MHD_DAEMON_INFO_CURRENT_CONNECTIONS)->
+ num_connections)
+ sleep (1);
+ /* Now we're really done, practice clean shutdown */
+ MHD_stop_daemon (mhd);
+ }
+ break;
+ default:
+ GNUNET_break (0);
+ MHD_stop_daemon (mhd);
+ break;
+ }
- GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
+ return ret;
}
@@ -902,118 +1098,13 @@ main (int argc,
if (-1 == fh)
return 1;
}
- mhd
- = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_PIPE_FOR_SHUTDOWN
- | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
- | MHD_USE_INTERNAL_POLLING_THREAD
- | MHD_USE_TCP_FASTOPEN,
- (-1 == fh) ? serve_port : 0,
- NULL, NULL,
- &handle_mhd_request, NULL,
- MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 32,
- MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 1024,
- MHD_OPTION_LISTEN_SOCKET, fh,
- MHD_OPTION_EXTERNAL_LOGGER, &TALER_MHD_handle_logs,
- NULL,
- MHD_OPTION_NOTIFY_COMPLETED,
- &handle_mhd_completion_callback, NULL,
- MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
-#if HAVE_DEVELOPER
- MHD_OPTION_NOTIFY_CONNECTION, &connection_done, NULL,
-#endif
- MHD_OPTION_END);
- if (NULL == mhd)
- {
- fprintf (stderr,
- "Failed to start HTTP server.\n");
- return 1;
- }
-
- atexit (write_stats);
-
#if HAVE_DEVELOPER
if (NULL != input_filename)
- {
- /* run only the testfile input, then terminate */
- ret = run_fake_client ();
- }
+ ret = run_single_request (fh);
else
- {
- /* normal behavior */
- ret = TEH_KS_loop ();
- }
-#else
- /* normal behavior */
- ret = TEH_KS_loop ();
#endif
-
- switch (ret)
- {
- case GNUNET_OK:
- case GNUNET_SYSERR:
- MHD_stop_daemon (mhd);
- break;
- case GNUNET_NO:
- {
- MHD_socket sock = MHD_quiesce_daemon (mhd);
- pid_t chld;
- int flags;
-
- /* Set flags to make 'sock' inherited by child */
- flags = fcntl (sock, F_GETFD);
- GNUNET_assert (-1 != flags);
- flags &= ~FD_CLOEXEC;
- GNUNET_assert (-1 != fcntl (sock, F_SETFD, flags));
- chld = fork ();
- if (-1 == chld)
- {
- /* fork() failed, continue clean up, unhappily */
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fork");
- }
- if (0 == chld)
- {
- char pids[12];
-
- /* exec another taler-exchange-httpd, passing on the listen socket;
- as in systemd it is expected to be on FD #3 */
- if (3 != dup2 (sock, 3))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "dup2");
- _exit (1);
- }
- /* Tell the child that it is the desired recipient for FD #3 */
- GNUNET_snprintf (pids,
- sizeof (pids),
- "%u",
- getpid ());
- setenv ("LISTEN_PID", pids, 1);
- setenv ("LISTEN_FDS", "1", 1);
- /* Finally, exec the (presumably) more recent exchange binary */
- execvp ("taler-exchange-httpd",
- argv);
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "execvp");
- _exit (1);
- }
- /* we're the original process, handle remaining contextions
- before exiting; as the listen socket is no longer used,
- close it here */
- GNUNET_break (0 == close (sock));
- while (0 != MHD_get_daemon_info (mhd,
- MHD_DAEMON_INFO_CURRENT_CONNECTIONS)->
- num_connections)
- sleep (1);
- /* Now we're really done, practice clean shutdown */
- MHD_stop_daemon (mhd);
- }
- break;
- default:
- GNUNET_break (0);
- MHD_stop_daemon (mhd);
- break;
- }
+ ret = run_main_loop (fh,
+ argv);
/* release #internal_key_state */
TEH_KS_free ();
diff --git a/src/exchange/test_taler_exchange_httpd.conf b/src/exchange/test_taler_exchange_httpd.conf
index 8c2f944c8..75da2cf93 100644
--- a/src/exchange/test_taler_exchange_httpd.conf
+++ b/src/exchange/test_taler_exchange_httpd.conf
@@ -5,7 +5,7 @@ TALER_TEST_HOME = test_taler_exchange_httpd_home/
[taler]
# Currency supported by the exchange (can only be one)
CURRENCY = EUR
-
+CURRENCY_ROUND_UNIT = EUR:0.01
[exchange]
# MAX_REQUESTS = 2
diff --git a/src/exchange/test_taler_exchange_httpd_afl.sh b/src/exchange/test_taler_exchange_httpd_afl.sh
index 6d741acce..a5d4fcbb8 100644
--- a/src/exchange/test_taler_exchange_httpd_afl.sh
+++ b/src/exchange/test_taler_exchange_httpd_afl.sh
@@ -21,6 +21,14 @@
#
# We read the JSON snippets from afl-tests/
#
+# The afl-tests are generated as follows:
+# 1) Capture all TCP traffic from 'test-auditor.sh'
+# 2) Use 'tcpflow -e http -r $PCAP -o $OUTPUT' to get the HTTP streams
+# 3) Remove HTTP streams unrelated to the exchange as well as the replies
+# 4) Compile the exchange with AFL instrumentation
+# 5) Run afl-fuzz -i $OUTPUT/ -o afl-tests/ ~/bin/taler-exchange-httpd \
+# -c test_taler_exchange_httpd.conf -t 1 -f @@
+
set -eu
PREFIX=