upstream_mhd.c (14781B)
1 /* 2 This file is part of paivana tests. 3 Copyright (C) 2026 Taler Systems SA 4 5 Paivana is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Affero General Public License 7 as published by the Free Software Foundation; either version 8 3, or (at your option) any later version. 9 10 Paivana is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty 12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 13 the GNU Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public 16 License along with Paivana; see the file COPYING. If not, 17 write to the Free Software Foundation, Inc., 51 Franklin 18 Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @file upstream_mhd.c 23 * @brief libmicrohttpd-based upstream test server for the 24 * paivana reverse-proxy test suite. Implements a small 25 * set of canned endpoints (see README). 26 */ 27 #include <errno.h> 28 #include <netinet/in.h> 29 #include <signal.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <time.h> 34 #include <unistd.h> 35 #include <sys/socket.h> 36 #include <sys/time.h> 37 #include <microhttpd.h> 38 39 #define SERVER_NAME "mhd" 40 41 struct Ctx 42 { 43 char *body; 44 size_t body_len; 45 size_t body_cap; 46 }; 47 48 49 static void 50 ctx_free (struct Ctx *c) 51 { 52 free (c->body); 53 free (c); 54 } 55 56 57 struct HdrBuf 58 { 59 char *data; 60 size_t len; 61 size_t cap; 62 }; 63 64 65 static enum MHD_Result 66 append_hdr (void *cls, 67 enum MHD_ValueKind kind, 68 const char *key, 69 const char *value) 70 { 71 struct HdrBuf *hb = cls; 72 size_t need = strlen (key) + strlen (value) + 4; 73 74 (void) kind; 75 if (hb->len + need > hb->cap) 76 { 77 size_t nc = hb->cap ? hb->cap * 2 : 1024; 78 char *nb; 79 80 while (nc < hb->len + need) 81 nc *= 2; 82 nb = realloc (hb->data, nc); 83 if (NULL == nb) 84 return MHD_NO; 85 hb->data = nb; 86 hb->cap = nc; 87 } 88 hb->len += (size_t) snprintf (hb->data + hb->len, 89 hb->cap - hb->len, 90 "%s: %s\n", 91 key, 92 value); 93 return MHD_YES; 94 } 95 96 97 static struct MHD_Response * 98 make_text_response (const char *s) 99 { 100 struct MHD_Response *r; 101 102 r = MHD_create_response_from_buffer (strlen (s), 103 (void *) s, 104 MHD_RESPMEM_MUST_COPY); 105 MHD_add_response_header (r, 106 MHD_HTTP_HEADER_CONTENT_TYPE, 107 "text/plain"); 108 MHD_add_response_header (r, 109 "X-Upstream", 110 SERVER_NAME); 111 return r; 112 } 113 114 115 static enum MHD_Result 116 reply_text (struct MHD_Connection *con, 117 unsigned int status, 118 const char *s) 119 { 120 struct MHD_Response *r = make_text_response (s); 121 enum MHD_Result ret; 122 123 ret = MHD_queue_response (con, 124 status, 125 r); 126 MHD_destroy_response (r); 127 return ret; 128 } 129 130 131 static enum MHD_Result 132 reply_bytes (struct MHD_Connection *con, 133 unsigned int status, 134 const char *buf, 135 size_t len) 136 { 137 struct MHD_Response *r; 138 enum MHD_Result ret; 139 140 r = MHD_create_response_from_buffer (len, 141 (void *) buf, 142 MHD_RESPMEM_MUST_COPY); 143 MHD_add_response_header (r, 144 "X-Upstream", 145 SERVER_NAME); 146 MHD_add_response_header (r, 147 MHD_HTTP_HEADER_CONTENT_TYPE, 148 "application/octet-stream"); 149 ret = MHD_queue_response (con, 150 status, 151 r); 152 MHD_destroy_response (r); 153 return ret; 154 } 155 156 157 static enum MHD_Result 158 handler (void *cls, 159 struct MHD_Connection *con, 160 const char *url, 161 const char *method, 162 const char *version, 163 const char *upload_data, 164 size_t *upload_data_size, 165 void **con_cls) 166 { 167 struct Ctx *ctx = *con_cls; 168 169 (void) cls; 170 (void) version; 171 if (NULL == ctx) 172 { 173 ctx = calloc (1, 174 sizeof (*ctx)); 175 if (NULL == ctx) 176 return MHD_NO; 177 *con_cls = ctx; 178 return MHD_YES; 179 } 180 if (0 != *upload_data_size) 181 { 182 if (ctx->body_len + *upload_data_size > ctx->body_cap) 183 { 184 size_t n = ctx->body_cap ? ctx->body_cap * 2 : 4096; 185 char *nb; 186 187 while (n < ctx->body_len + *upload_data_size) 188 n *= 2; 189 nb = realloc (ctx->body, 190 n); 191 if (NULL == nb) 192 return MHD_NO; 193 ctx->body = nb; 194 ctx->body_cap = n; 195 } 196 memcpy (ctx->body + ctx->body_len, 197 upload_data, 198 *upload_data_size); 199 ctx->body_len += *upload_data_size; 200 *upload_data_size = 0; 201 return MHD_YES; 202 } 203 204 /* OPTIONS on anything */ 205 if (0 == strcmp (method, 206 MHD_HTTP_METHOD_OPTIONS)) 207 { 208 struct MHD_Response *r = make_text_response (""); 209 enum MHD_Result ret; 210 211 MHD_add_response_header (r, 212 MHD_HTTP_HEADER_ALLOW, 213 "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"); 214 ret = MHD_queue_response (con, 215 MHD_HTTP_NO_CONTENT, 216 r); 217 MHD_destroy_response (r); 218 return ret; 219 } 220 221 /* GET /hello */ 222 if ( (0 == strcmp (method, 223 MHD_HTTP_METHOD_GET) || 224 0 == strcmp (method, 225 MHD_HTTP_METHOD_HEAD)) && 226 (0 == strcmp (url, 227 "/hello")) ) 228 return reply_text (con, 229 MHD_HTTP_OK, 230 "Hello from " SERVER_NAME "\n"); 231 232 /* GET /echo-headers — return all request headers in the body */ 233 if ( (0 == strcmp (method, 234 MHD_HTTP_METHOD_GET)) && 235 (0 == strcmp (url, 236 "/echo-headers")) ) 237 { 238 struct HdrBuf hb = { 0 }; 239 enum MHD_Result ret; 240 241 MHD_get_connection_values (con, 242 MHD_HEADER_KIND, 243 &append_hdr, 244 &hb); 245 ret = reply_bytes (con, 246 MHD_HTTP_OK, 247 hb.data ? hb.data : "", 248 hb.len); 249 free (hb.data); 250 return ret; 251 } 252 253 /* GET /conn-response — reply naming two of our own response 254 headers in Connection; a conforming proxy must strip both 255 (RFC 9110 section 7.6.1) but keep X-Keep-Resp. */ 256 if ( (0 == strcmp (method, 257 MHD_HTTP_METHOD_GET)) && 258 (0 == strcmp (url, 259 "/conn-response")) ) 260 { 261 struct MHD_Response *r = make_text_response ("conn\n"); 262 enum MHD_Result ret; 263 264 MHD_add_response_header (r, 265 "X-Hop-Before", 266 "must-not-leak"); 267 MHD_add_response_header (r, 268 MHD_HTTP_HEADER_CONNECTION, 269 "X-Hop-Before, X-Hop-After"); 270 MHD_add_response_header (r, 271 "X-Hop-After", 272 "must-not-leak"); 273 MHD_add_response_header (r, 274 "X-Keep-Resp", 275 "survivor"); 276 ret = MHD_queue_response (con, 277 MHD_HTTP_OK, 278 r); 279 MHD_destroy_response (r); 280 return ret; 281 } 282 283 /* GET /status/NNN */ 284 if ( (0 == strcmp (method, 285 MHD_HTTP_METHOD_GET)) && 286 (0 == strncmp (url, 287 "/status/", 288 strlen ("/status/"))) ) 289 { 290 char msg[64]; 291 int s = atoi (url + 8); 292 293 if (s < 100 || s > 599) 294 s = 500; 295 snprintf (msg, 296 sizeof (msg), 297 "status %d\n", 298 s); 299 return reply_text (con, 300 (unsigned int) s, 301 msg); 302 } 303 304 /* GET /large/NNN */ 305 if ( (0 == strcmp (method, 306 MHD_HTTP_METHOD_GET) || 307 0 == strcmp (method, 308 MHD_HTTP_METHOD_HEAD)) && 309 (0 == strncmp (url, 310 "/large/", 311 strlen ("/large/"))) ) 312 { 313 long n = atol (url + 7); 314 char *b; 315 enum MHD_Result ret; 316 317 if (n < 0) 318 n = 0; 319 if (n > 10 * 1024 * 1024) 320 n = 10 * 1024 * 1024; 321 b = malloc ((size_t) n); 322 if (NULL == b && n > 0) 323 return reply_text (con, 500, "oom\n"); 324 for (long i = 0; i < n; i++) 325 b[i] = 'A' + (char) (i % 26); 326 ret = reply_bytes (con, 327 MHD_HTTP_OK, 328 b, 329 (size_t) n); 330 free (b); 331 return ret; 332 } 333 334 /* GET /slow/NNN — sleep NNN ms then respond */ 335 if ( (0 == strcmp (method, 336 MHD_HTTP_METHOD_GET)) && 337 (0 == strncmp (url, 338 "/slow/", 339 strlen ("/slow/"))) ) 340 { 341 int ms = atoi (url + 6); 342 struct timespec ts; 343 344 if (ms < 0) 345 ms = 0; 346 if (ms > 30000) 347 ms = 30000; 348 ts.tv_sec = ms / 1000; 349 ts.tv_nsec = (ms % 1000) * 1000000L; 350 nanosleep (&ts, NULL); 351 return reply_text (con, 352 MHD_HTTP_OK, 353 "slept\n"); 354 } 355 356 /* POST /echo — echoes body */ 357 if ( (0 == strcmp (method, 358 MHD_HTTP_METHOD_POST)) && 359 (0 == strcmp (url, "/echo")) ) 360 return reply_bytes (con, 361 MHD_HTTP_OK, 362 ctx->body ? ctx->body : "", 363 ctx->body_len); 364 365 /* POST /upload — returns count */ 366 if ( (0 == strcmp (method, 367 MHD_HTTP_METHOD_POST)) && 368 (0 == strcmp (url, 369 "/upload")) ) 370 { 371 char msg[64]; 372 373 snprintf (msg, 374 sizeof (msg), 375 "Received %zu bytes\n", 376 ctx->body_len); 377 return reply_text (con, 378 MHD_HTTP_OK, 379 msg); 380 } 381 382 /* PUT /put */ 383 if ( (0 == strcmp (method, 384 MHD_HTTP_METHOD_PUT)) && 385 (0 == strcmp (url, 386 "/put")) ) 387 { 388 char msg[64]; 389 390 snprintf (msg, 391 sizeof (msg), 392 "PUT received %zu\n", 393 ctx->body_len); 394 return reply_text (con, 395 MHD_HTTP_OK, 396 msg); 397 } 398 399 /* PATCH /patch */ 400 if ( (0 == strcmp (method, 401 MHD_HTTP_METHOD_PATCH)) && 402 (0 == strcmp (url, 403 "/patch")) ) 404 { 405 char msg[64]; 406 407 snprintf (msg, 408 sizeof (msg), 409 "PATCH received %zu\n", 410 ctx->body_len); 411 return reply_text (con, 412 MHD_HTTP_OK, 413 msg); 414 } 415 416 /* DELETE /item */ 417 if ( (0 == strcmp (method, 418 "DELETE")) && 419 (0 == strncmp (url, 420 "/item", 421 strlen ("/item"))) ) 422 { 423 struct MHD_Response *r = make_text_response (""); 424 enum MHD_Result ret = MHD_queue_response (con, 425 MHD_HTTP_NO_CONTENT, 426 r); 427 MHD_destroy_response (r); 428 return ret; 429 } 430 431 return reply_text (con, 432 MHD_HTTP_NOT_FOUND, 433 "not found\n"); 434 } 435 436 437 static void 438 completed_cb (void *cls, 439 struct MHD_Connection *con, 440 void **con_cls, 441 enum MHD_RequestTerminationCode toe) 442 { 443 struct Ctx *ctx = *con_cls; 444 445 (void) cls; 446 (void) con; 447 (void) toe; 448 if (NULL != ctx) 449 ctx_free (ctx); 450 *con_cls = NULL; 451 } 452 453 454 /** 455 * Parse @a arg as a TCP port number. 456 * 457 * atoi(3) would map "garbage" to 0, MHD would then bind an ephemeral 458 * port, and the driver's readiness probe would time out five seconds 459 * later blaming the port it asked for -- a failure pointing at 460 * something other than the mistake. 461 * 462 * @param arg argument to parse 463 * @param[out] port set to the parsed port on success 464 * @return 0 on success, -1 if @a arg is not a port number 465 */ 466 static int 467 parse_port (const char *arg, 468 unsigned int *port) 469 { 470 char *end; 471 long long v; 472 473 errno = 0; 474 v = strtoll (arg, 475 &end, 476 10); 477 if ( (0 != errno) || 478 (end == arg) || 479 ('\0' != *end) || 480 (v < 1) || 481 (v > 65535) ) 482 return -1; 483 *port = (unsigned int) v; 484 return 0; 485 } 486 487 488 int 489 main (int argc, char **argv) 490 { 491 unsigned int port = 8401; 492 struct MHD_Daemon *d; 493 struct sockaddr_in addr; 494 sigset_t quit; 495 int sig; 496 497 if ( (argc > 1) && 498 (0 != parse_port (argv[1], 499 &port)) ) 500 { 501 fprintf (stderr, 502 "invalid port `%s'\n", 503 argv[1]); 504 return 1; 505 } 506 signal (SIGPIPE, 507 SIG_IGN); 508 /* Block the two shutdown signals here, while we are still 509 single-threaded, so that MHD's internal polling thread inherits 510 the block and the signal can only be taken by the sigwait below. 511 A handler plus `while (run_flag) sleep (1);' cost up to a second 512 of shutdown latency per upstream, and a handler plus pause(2) 513 would deadlock outright whenever the process-directed signal 514 happened to be delivered to the MHD thread. */ 515 sigemptyset (&quit); 516 sigaddset (&quit, 517 SIGINT); 518 sigaddset (&quit, 519 SIGTERM); 520 if (0 != sigprocmask (SIG_BLOCK, 521 &quit, 522 NULL)) 523 { 524 perror ("sigprocmask"); 525 return 1; 526 } 527 /* Bind the loopback interface explicitly. This is a test server 528 that echoes an arbitrary POST body back and hands out 10 MiB on 529 request; it has no business being reachable from the network for 530 the duration of `make check'. */ 531 memset (&addr, 532 0, 533 sizeof (addr)); 534 addr.sin_family = AF_INET; 535 addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); 536 addr.sin_port = htons ((uint16_t) port); 537 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, 538 port, 539 NULL, NULL, 540 &handler, NULL, 541 MHD_OPTION_SOCK_ADDR, &addr, 542 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL, 543 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 30, 544 MHD_OPTION_END); 545 if (NULL == d) 546 { 547 fprintf (stderr, 548 "MHD_start_daemon failed on port %u\n", 549 port); 550 return 1; 551 } 552 fprintf (stderr, 553 "upstream_mhd listening on port %u\n", 554 port); 555 fflush (stderr); 556 while (0 != sigwait (&quit, 557 &sig)) 558 /* EINTR is the only failure sigwait can report here; retry. */; 559 MHD_stop_daemon (d); 560 return 0; 561 }