paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

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 }