early_response_upstream.c (15902B)
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 early_response_upstream.c 23 * @brief Raw-socket HTTP upstream that responds before reading the 24 * request body. Used to exercise paivana's handling of an 25 * early upstream response: when the upstream answers while 26 * the client (paivana) is still uploading, paivana must 27 * deliver that response to its own client rather than treat 28 * the interrupted forward as a failure. 29 * 30 * After each connection finishes the server writes the number 31 * of body bytes it received (decimal, trailing newline) to the 32 * file given as the second argument, atomically via rename(2). 33 * That count is a diagnostic, not a contract: RFC 9110 §9.3 34 * lets a client stop sending its body once it has a final 35 * response, and libcurl does exactly that, so the upstream 36 * normally sees only a prefix of what was sent. 37 * 38 * With `--no-drain` the server instead *stops* reading once it 39 * has answered, and parks the connection with the rest of the 40 * request still queued in its receive buffer. That is the 41 * nastier half of the same situation: paivana's outbound 42 * socket backs up and its write() can no longer make progress, 43 * so it can only finish the request if it acts on the response 44 * that is already in hand rather than waiting for the upload 45 * to drain. A proxy that gets this wrong hangs until its 46 * transfer timeout instead of failing outright, which is why 47 * the driver bounds the case with timeout(1). 48 */ 49 #ifndef _GNU_SOURCE 50 /* for POLLRDHUP, which is how we notice the peer hanging up 51 without ever reading from the socket */ 52 #define _GNU_SOURCE 53 #endif 54 #include "platform.h" 55 #include <gnunet/gnunet_util_lib.h> 56 #include <limits.h> 57 #include <poll.h> 58 #include <strings.h> 59 60 /** 61 * How long (ms) a `--no-drain` connection is parked before we give up 62 * on the peer ever hanging up. Only reached when paivana is stuck, 63 * and the driver's timeout(1) will have failed the case by then; the 64 * bound is here so a wedged run still terminates. 65 */ 66 #define PARK_TIMEOUT_MS 30000 67 68 /** 69 * Receive buffer (bytes) requested for `--no-drain` connections. The 70 * kernel doubles this and enforces its own floor; the point is only 71 * to keep it far below the body the driver sends, so that refusing to 72 * read reliably stalls the sender no matter how TCP autotuning is 73 * configured on the machine running the tests. 74 */ 75 #define NO_DRAIN_RCVBUF 8192 76 77 78 static const char *receipt_path; 79 static volatile sig_atomic_t run_flag = 1; 80 81 /** 82 * Keep reading the request body after responding? False under 83 * `--no-drain`, where refusing to read is the whole point. 84 */ 85 static bool drain_body = true; 86 87 88 static void 89 on_sig (int sig) 90 { 91 (void) sig; 92 run_flag = 0; 93 } 94 95 96 /** 97 * Read from @a fd into @a buf until "\r\n\r\n" appears. Returns the 98 * total number of bytes read (headers + any body bytes carried in the 99 * same recv) on success, with @a *eoh set to the offset of the first 100 * body byte (i.e. just past the CRLFCRLF). Returns -1 on read error 101 * or if @a buf fills up before headers end. 102 */ 103 static ssize_t 104 read_until_eoh (int fd, 105 char *buf, 106 size_t cap, 107 size_t *eoh) 108 { 109 size_t pos = 0; 110 /* First offset not yet examined. Carried across the read loop: 111 restarting the scan at 0 after every read would be quadratic in 112 the size of the header block. */ 113 size_t scan = 0; 114 115 while (pos < cap) 116 { 117 ssize_t n = read (fd, 118 buf + pos, 119 cap - pos); 120 if (n <= 0) 121 return -1; 122 pos += (size_t) n; 123 while (scan + 3 < pos) 124 { 125 if ( ('\r' == buf[scan]) && 126 ('\n' == buf[scan + 1]) && 127 ('\r' == buf[scan + 2]) && 128 ('\n' == buf[scan + 3]) ) 129 { 130 *eoh = scan + 4; 131 return (ssize_t) pos; 132 } 133 scan++; 134 } 135 } 136 return -1; 137 } 138 139 140 /** 141 * Find the Content-Length announced in the request header block held 142 * in the first @a len bytes of @a hdr. 143 * 144 * We need it because we cannot rely on end-of-file to tell us the 145 * request is over: the peer is free to keep a completed connection in 146 * a pool, and then no EOF ever comes. 147 * 148 * @param hdr start of the (not NUL-terminated) header block 149 * @param len number of bytes in @a hdr 150 * @return the announced length, or -1 if there was none 151 */ 152 static long long 153 find_content_length (const char *hdr, 154 size_t len) 155 { 156 static const char name[] = "content-length:"; 157 const size_t nlen = sizeof (name) - 1; 158 159 for (size_t i = 0; i + nlen <= len; i++) 160 { 161 size_t j; 162 long long v = 0; 163 bool digits = false; 164 165 if ( (0 == i) || 166 ('\n' != hdr[i - 1]) ) 167 continue; /* header names start a line */ 168 if (0 != strncasecmp (&hdr[i], 169 name, 170 nlen)) 171 continue; 172 j = i + nlen; 173 while ( (j < len) && 174 ( (' ' == hdr[j]) || ('\t' == hdr[j]) ) ) 175 j++; 176 while ( (j < len) && 177 ('0' <= hdr[j]) && 178 ('9' >= hdr[j]) ) 179 { 180 /* Signed overflow is undefined, and the value comes off the 181 wire: refuse a length we cannot represent rather than wrap 182 into a negative (or, with optimisation, into anything at 183 all). We have no use for one either -- the caller reads at 184 most what it announced. */ 185 if (v > (LLONG_MAX - (hdr[j] - '0')) / 10) 186 return -1; 187 v = v * 10 + (hdr[j] - '0'); 188 digits = true; 189 j++; 190 } 191 return digits ? v : -1; 192 } 193 return -1; 194 } 195 196 197 /** 198 * Atomically write @a count to the receipt file (write to <path>.tmp, 199 * fsync, rename). No-op if no receipt path was configured. 200 */ 201 static void 202 write_receipt (size_t count) 203 { 204 FILE *f; 205 char tmp[4096]; 206 int rc; 207 208 if (NULL == receipt_path) 209 return; 210 rc = snprintf (tmp, 211 sizeof (tmp), 212 "%s.tmp", 213 receipt_path); 214 if ( (rc < 0) || 215 ((size_t) rc >= sizeof (tmp)) ) 216 return; 217 f = fopen (tmp, "w"); 218 if (NULL == f) 219 return; 220 fprintf (f, "%zu\n", count); 221 fflush (f); 222 fsync (fileno (f)); 223 fclose (f); 224 if (0 != rename (tmp, receipt_path)) 225 unlink (tmp); 226 } 227 228 229 /** 230 * The early response. Deliberately *without* `Connection: close`: 231 * announcing a close makes libcurl give up on the rest of the request 232 * body it was sending, which is legitimate but would rob the 233 * drain-mode test of the very thing it checks. 234 */ 235 static const char EARLY_RESPONSE[] = 236 "HTTP/1.1 413 Payload Too Large\r\n" 237 "Content-Type: text/plain\r\n" 238 "X-Upstream: early\r\n" 239 "Content-Length: 23\r\n" 240 "\r\n" 241 "early-response-payload\n"; 242 243 244 /** 245 * Best-effort send the entire @a buf, retrying on partial writes. 246 * Returns 0 on success, -1 if the socket failed. 247 */ 248 static int 249 write_all (int fd, 250 const char *buf, 251 size_t len) 252 { 253 size_t off = 0; 254 255 while (off < len) 256 { 257 ssize_t w = write (fd, 258 buf + off, 259 len - off); 260 if (w < 0) 261 { 262 if (EINTR == errno) 263 continue; 264 return -1; 265 } 266 if (0 == w) 267 return -1; 268 off += (size_t) w; 269 } 270 return 0; 271 } 272 273 274 /** 275 * Wait for the peer to shut down its sending side of @a fd without 276 * ever reading from the socket, so that the unconsumed request body 277 * keeps the peer's send buffer full. Gives up after 278 * #PARK_TIMEOUT_MS. 279 * 280 * @param fd the connection to park 281 */ 282 static void 283 park_until_hangup (int fd) 284 { 285 struct pollfd pfd = { 286 .fd = fd, 287 /* Deliberately not POLLIN: we do not want to know that body 288 bytes arrived, only that the peer is done sending them. */ 289 .events = POLLRDHUP 290 }; 291 int elapsed = 0; 292 293 while (elapsed < PARK_TIMEOUT_MS) 294 { 295 int rv; 296 297 if (! run_flag) 298 return; /* shutting down, nothing to report */ 299 rv = poll (&pfd, 300 1, 301 250); 302 if (rv < 0) 303 { 304 if (EINTR == errno) 305 continue; 306 return; 307 } 308 if (0 != (pfd.revents & (POLLRDHUP | POLLHUP | POLLERR))) 309 return; 310 elapsed += 250; 311 } 312 fprintf (stderr, 313 "no-drain connection parked for %d ms without a hangup;" 314 " the peer is most likely stuck\n", 315 elapsed); 316 fflush (stderr); 317 } 318 319 320 static void 321 handle_client (int fd) 322 { 323 char buf[8192]; 324 size_t eoh = 0; 325 ssize_t got; 326 size_t body_bytes; 327 long long content_length; 328 329 got = read_until_eoh (fd, 330 buf, 331 sizeof (buf), 332 &eoh); 333 if (got < 0) 334 { 335 close (fd); 336 return; 337 } 338 body_bytes = (size_t) got - eoh; 339 content_length = find_content_length (buf, 340 eoh); 341 342 /* Send the 413 response immediately. This is the whole point 343 of this upstream: respond before consuming the request body. */ 344 if (0 != write_all (fd, 345 EARLY_RESPONSE, 346 sizeof (EARLY_RESPONSE) - 1)) 347 { 348 close (fd); 349 /* Receipt still useful for diagnostics: how far did we get? */ 350 write_receipt (body_bytes); 351 return; 352 } 353 354 if (! drain_body) 355 { 356 /* The interesting case: having answered, refuse to read another 357 byte. The rest of the request stays in our receive buffer, 358 our window closes, and paivana's send() stops making progress 359 with the response already sitting unread on its side. Park 360 until the peer hangs up (which is what a proxy that handles 361 this correctly does) rather than closing ourselves: closing 362 with unread data queued would send an RST, and the peer would 363 be entitled to discard the response we just sent -- turning a 364 proxy bug into a transport error and vice versa. */ 365 park_until_hangup (fd); 366 close (fd); 367 write_receipt (body_bytes); 368 return; 369 } 370 371 /* Now drain the rest of the request body. We must keep reading 372 until the announced Content-Length is in (or the client closes, 373 for a request that had none) — otherwise its CURL would block on 374 send() once the kernel recv buffer fills, which would mask the 375 very behavior we are trying to test. Stopping at 376 Content-Length rather than at EOF matters: having answered the 377 request in full we have given the peer every right to keep the 378 connection for reuse, and then the EOF never comes. */ 379 while ( (content_length < 0) || 380 (body_bytes < (size_t) content_length) ) 381 { 382 ssize_t n = read (fd, 383 buf, 384 sizeof (buf)); 385 if (n < 0) 386 { 387 if (EINTR == errno) 388 continue; 389 break; 390 } 391 if (0 == n) 392 break; 393 body_bytes += (size_t) n; 394 } 395 396 close (fd); 397 write_receipt (body_bytes); 398 } 399 400 401 /** 402 * Parse @a arg as a TCP port number. Rejects trailing garbage and 403 * anything outside 1-65535; port 0 in particular is refused, as we 404 * would have no way to tell the caller which port the kernel picked. 405 * 406 * @param arg command-line argument to parse 407 * @param[out] port set to the parsed port on success 408 * @return 0 on success, -1 if @a arg is not a valid port 409 */ 410 static int 411 parse_port (const char *arg, 412 int *port) 413 { 414 char *end; 415 long long v; 416 417 errno = 0; 418 v = strtoll (arg, 419 &end, 420 10); 421 if ( (0 != errno) || 422 (end == arg) || 423 ('\0' != *end) || 424 (v < 1) || 425 (v > 65535) ) 426 return -1; 427 *port = (int) v; 428 return 0; 429 } 430 431 432 int 433 main (int argc, 434 char **argv) 435 { 436 int srv; 437 int port = 0; 438 int yes = 1; 439 int have_port = 0; 440 struct sockaddr_in addr; 441 442 for (int i = 1; i < argc; i++) 443 { 444 if (0 == strcmp (argv[i], 445 "--no-drain")) 446 { 447 drain_body = false; 448 continue; 449 } 450 if (! have_port) 451 { 452 if (0 != parse_port (argv[i], 453 &port)) 454 { 455 fprintf (stderr, 456 "invalid port `%s'\n", 457 argv[i]); 458 return 1; 459 } 460 have_port = 1; 461 continue; 462 } 463 if (NULL == receipt_path) 464 { 465 receipt_path = argv[i]; 466 continue; 467 } 468 fprintf (stderr, 469 "unexpected argument `%s'\n", 470 argv[i]); 471 have_port = 0; 472 break; 473 } 474 if (! have_port) 475 { 476 fprintf (stderr, 477 "usage: %s <port> [receipt-file] [--no-drain]\n", 478 argv[0]); 479 return 1; 480 } 481 482 { 483 /* sigaction(2), not signal(3): glibc's signal() implies 484 SA_RESTART, which would restart the accept(2) we spend most of 485 our life in and leave us ignoring SIGTERM forever. We need the 486 EINTR so the loops below can notice `run_flag'. */ 487 struct sigaction sa = { 488 .sa_handler = &on_sig, 489 .sa_flags = 0 490 }; 491 492 sigemptyset (&sa.sa_mask); 493 sigaction (SIGINT, &sa, NULL); 494 sigaction (SIGTERM, &sa, NULL); 495 } 496 signal (SIGPIPE, SIG_IGN); 497 498 srv = socket (AF_INET, SOCK_STREAM, 0); 499 if (srv < 0) 500 { 501 perror ("socket"); 502 return 1; 503 } 504 /* Only fails on a bad fd/level/optname, i.e. never here; the 505 assertion is to keep a silent failure from turning into a 506 spurious EADDRINUSE two test cases later. */ 507 GNUNET_assert (0 == setsockopt (srv, 508 SOL_SOCKET, 509 SO_REUSEADDR, 510 &yes, 511 sizeof (yes))); 512 if (! drain_body) 513 { 514 int rcvbuf = NO_DRAIN_RCVBUF; 515 516 /* Set on the listening socket so accepted connections inherit 517 it, and before listen(2) so it is in effect for the SYN-ACK 518 window. Without this the peer could park a multi-megabyte 519 autotuned window's worth of body with us and finish its 520 write() after all, which would quietly turn the no-drain case 521 back into the drain case. */ 522 if (0 != setsockopt (srv, 523 SOL_SOCKET, 524 SO_RCVBUF, 525 &rcvbuf, 526 sizeof (rcvbuf))) 527 perror ("setsockopt(SO_RCVBUF)"); /* not fatal, just less sharp */ 528 } 529 memset (&addr, 0, sizeof (addr)); 530 addr.sin_family = AF_INET; 531 addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); 532 addr.sin_port = htons ((uint16_t) port); 533 if (0 != bind (srv, 534 (struct sockaddr *) &addr, 535 sizeof (addr))) 536 { 537 perror ("bind"); 538 close (srv); 539 return 1; 540 } 541 if (0 != listen (srv, 16)) 542 { 543 perror ("listen"); 544 close (srv); 545 return 1; 546 } 547 fprintf (stderr, 548 "early_response_upstream listening on port %d (%s)\n", 549 port, 550 drain_body 551 ? "draining the body after responding" 552 : "refusing to read the body after responding"); 553 fflush (stderr); 554 555 while (run_flag) 556 { 557 int cfd = accept (srv, NULL, NULL); 558 559 if (cfd < 0) 560 { 561 if (EINTR == errno) 562 continue; 563 perror ("accept"); 564 break; 565 } 566 /* Single-threaded: serialise connections. The reverse-proxy 567 test makes one request per case and Connection: close keeps 568 paivana from reusing the socket. */ 569 handle_client (cfd); 570 } 571 close (srv); 572 return 0; 573 }