paivana

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

stream_upstream.c (35689B)


      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 stream_upstream.c
     23  * @brief Raw-socket HTTP upstream for the streaming tests: serves
     24  *        bodies far larger than memory, at a controllable rate, and
     25  *        in the framings a conforming server library will not
     26  *        produce.
     27  *
     28  *        Raw sockets rather than libmicrohttpd because half of these
     29  *        targets are deliberate framing abuse -- a declared
     30  *        `Content-Length` that is not delivered, a chunked response
     31  *        with no terminating chunk -- which is exactly what a correct
     32  *        server refuses to emit.
     33  *
     34  *        Bodies are a deterministic function of the byte offset (see
     35  *        `pattern_at()'), so hundreds of megabytes can be verified
     36  *        without either side storing them.  The same function is in
     37  *        `stream_client.c'; the two must agree.
     38  *
     39  *        Targets, selected by request path:
     40  *
     41  *        /cl?bytes=N[&rate=R]       declare Content-Length: N, send N
     42  *        /chunked?bytes=N[&rate=R]  same body, chunked, no length
     43  *        /short?declare=N&send=M    declare N, send M < N, close
     44  *        /chunk-abort?after=N       chunked, N bytes, no terminator
     45  *        /hang?after=N              N bytes, then nothing, never close
     46  *        /slowstart?delay=D[&bytes=N]  headers now, first byte in D ms
     47  *        /sink[?rate=R]             read a request body, report its size
     48  *        /sink-early?after=N        answer 413 after N bytes of body
     49  *        /status?code=C[&len=N]     bare status, optional Content-Length
     50  *        /range?bytes=N             honour one Range, answer 206
     51  *        /mute                      accept, then never answer at all
     52  *        /trailers?bytes=N          chunked, with a trailer section
     53  *        /interim?bytes=N           a 103 before the final response
     54  *
     55  *        `rate' is in bytes per second; 0 (the default) means as fast
     56  *        as the socket takes it.  A rate limit is what makes the
     57  *        congestion cases reproducible: without it the kernel buffers
     58  *        absorb everything and no backpressure is ever exercised.
     59  */
     60 #ifndef _GNU_SOURCE
     61 #define _GNU_SOURCE
     62 #endif
     63 #include "platform.h"
     64 #include <gnunet/gnunet_util_lib.h>
     65 #include <limits.h>
     66 #include <poll.h>
     67 #include <strings.h>
     68 #include <time.h>
     69 
     70 /**
     71  * Bytes written per iteration of a rate-limited send loop.  Small
     72  * enough that a slow rate really does dribble rather than arriving in
     73  * one burst per second, large enough not to make a 200 MiB unlimited
     74  * transfer syscall-bound.
     75  */
     76 #define SEND_GRAIN 16384
     77 
     78 /**
     79  * How long (ms) `/hang' parks a connection before giving up on the
     80  * peer.  Only reached when paivana's own stall watchdog failed to
     81  * fire, which is the bug the case is looking for; the bound is here
     82  * so a wedged run still terminates.
     83  */
     84 #define HANG_TIMEOUT_MS 120000
     85 
     86 /**
     87  * Size of the request header block we are willing to read.
     88  */
     89 #define HDR_MAX 16384
     90 
     91 
     92 static volatile sig_atomic_t run_flag = 1;
     93 
     94 /**
     95  * Body bytes this connection moved, in whichever direction it was
     96  * moving them.  Reported when the connection is done; see `serve()'.
     97  */
     98 static uint64_t req_bytes;
     99 
    100 /**
    101  * What the connection was asked for, for the report line.  Sized to
    102  * hold a whole request target, so that a long query string is reported
    103  * rather than silently cut.
    104  */
    105 static char req_label[1024];
    106 
    107 
    108 static void
    109 on_sig (int sig)
    110 {
    111   (void) sig;
    112   run_flag = 0;
    113 }
    114 
    115 
    116 /**
    117  * The body byte at offset @a off.
    118  *
    119  * Deterministic and cheap, and deliberately not a constant: a body of
    120  * one repeated character would pass a comparison that dropped or
    121  * duplicated a whole aligned block, which is precisely the mistake a
    122  * ring buffer with wrong wrap arithmetic makes.  The `off >> 13' term
    123  * makes the pattern differ between 8 KiB blocks as well as within
    124  * them.
    125  *
    126  * Must agree with the same function in `stream_client.c'.
    127  *
    128  * @param off byte offset into the body
    129  * @return the byte that belongs there
    130  */
    131 static uint8_t
    132 pattern_at (uint64_t off)
    133 {
    134   return (uint8_t) ((off * 7) + (off >> 13));
    135 }
    136 
    137 
    138 /**
    139  * Write all of @a len bytes from @a buf to @a fd.
    140  *
    141  * @param fd socket to write to
    142  * @param buf bytes to write
    143  * @param len number of bytes in @a buf
    144  * @return true if all of it went out
    145  */
    146 static bool
    147 write_all (int fd,
    148            const void *buf,
    149            size_t len)
    150 {
    151   const char *p = buf;
    152   size_t off = 0;
    153 
    154   while (off < len)
    155   {
    156     ssize_t n = write (fd,
    157                        p + off,
    158                        len - off);
    159 
    160     if (n < 0)
    161     {
    162       if (EINTR == errno)
    163         continue;
    164       return false;
    165     }
    166     if (0 == n)
    167       return false;
    168     off += (size_t) n;
    169   }
    170   return true;
    171 }
    172 
    173 
    174 /**
    175  * Sleep for @a ms milliseconds, resuming across signals.
    176  */
    177 static void
    178 sleep_ms (unsigned int ms)
    179 {
    180   struct timespec ts = {
    181     .tv_sec = ms / 1000,
    182     .tv_nsec = (long) (ms % 1000) * 1000000L
    183   };
    184 
    185   while ( (0 != nanosleep (&ts, &ts)) &&
    186           (EINTR == errno) )
    187     ; /* again */
    188 }
    189 
    190 
    191 /**
    192  * Send @a total bytes of the pattern starting at offset @a start,
    193  * at most @a rate bytes per second.
    194  *
    195  * The rate is enforced by sleeping between grains rather than by
    196  * pacing against a clock, which would let a stalled peer be
    197  * compensated for with a burst -- and a burst is exactly what these
    198  * tests must not see.
    199  *
    200  * @param fd socket to write to
    201  * @param start first body offset to send
    202  * @param total number of bytes to send
    203  * @param rate bytes per second, 0 for unlimited
    204  * @return true if all of it went out
    205  */
    206 static bool
    207 send_pattern (int fd,
    208               uint64_t start,
    209               uint64_t total,
    210               unsigned long long rate)
    211 {
    212   static char grain[SEND_GRAIN];
    213   uint64_t sent = 0;
    214   size_t step = SEND_GRAIN;
    215   unsigned int nap = 0;
    216 
    217   if (0 != rate)
    218   {
    219     /* Aim for roughly ten writes a second, but never below one byte
    220        per write and never above a grain. */
    221     step = (size_t) GNUNET_MIN ((unsigned long long) SEND_GRAIN,
    222                                 GNUNET_MAX (1ULL, rate / 10));
    223     nap = (unsigned int) ((1000ULL * step) / rate);
    224   }
    225   while (sent < total)
    226   {
    227     size_t n = (size_t) GNUNET_MIN ((uint64_t) step,
    228                                     total - sent);
    229 
    230     for (size_t i = 0; i < n; i++)
    231       grain[i] = (char) pattern_at (start + sent + i);
    232     if (! write_all (fd,
    233                      grain,
    234                      n))
    235       return false;
    236     sent += n;
    237     if ( (0 != nap) &&
    238          (sent < total) )
    239       sleep_ms (nap);
    240     if (! run_flag)
    241       return false;
    242   }
    243   return true;
    244 }
    245 
    246 
    247 /**
    248  * Like `send_pattern()', but reports how much actually went out rather
    249  * than only whether all of it did.
    250  *
    251  * The count is the backpressure evidence.  A proxy that assembled the
    252  * body in memory would take everything at line rate however slowly its
    253  * own client was reading; one that relays it can only take what the
    254  * client has made room for.  So how far this gets, and how long it
    255  * takes to get there, is what says which of the two is happening --
    256  * and it cannot be seen from the client end, where both look the same.
    257  *
    258  * @param fd socket to write to
    259  * @param start first body offset to send
    260  * @param total number of bytes to send
    261  * @param rate bytes per second, 0 for unlimited
    262  * @return number of bytes written
    263  */
    264 static uint64_t
    265 send_pattern_counted (int fd,
    266                       uint64_t start,
    267                       uint64_t total,
    268                       unsigned long long rate)
    269 {
    270   static char grain[SEND_GRAIN];
    271   uint64_t sent = 0;
    272   size_t step = SEND_GRAIN;
    273   unsigned int nap = 0;
    274 
    275   if (0 != rate)
    276   {
    277     step = (size_t) GNUNET_MIN ((unsigned long long) SEND_GRAIN,
    278                                 GNUNET_MAX (1ULL, rate / 10));
    279     nap = (unsigned int) ((1000ULL * step) / rate);
    280   }
    281   while (sent < total)
    282   {
    283     size_t n = (size_t) GNUNET_MIN ((uint64_t) step,
    284                                     total - sent);
    285 
    286     for (size_t i = 0; i < n; i++)
    287       grain[i] = (char) pattern_at (start + sent + i);
    288     if (! write_all (fd,
    289                      grain,
    290                      n))
    291       return sent;
    292     sent += n;
    293     if ( (0 != nap) &&
    294          (sent < total) )
    295       sleep_ms (nap);
    296     if (! run_flag)
    297       return sent;
    298   }
    299   return sent;
    300 }
    301 
    302 
    303 /**
    304  * Body bytes the last `send_chunked()' managed to write, which is not
    305  * the same as what it was asked for when the peer stops reading.
    306  */
    307 static uint64_t chunked_sent;
    308 
    309 
    310 /**
    311  * Send @a total bytes of the pattern as HTTP chunks.
    312  *
    313  * @param fd socket to write to
    314  * @param total number of body bytes
    315  * @param rate bytes per second, 0 for unlimited
    316  * @param terminate write the terminating zero-length chunk
    317  * @return true if all of it went out
    318  */
    319 static bool
    320 send_chunked (int fd,
    321               uint64_t total,
    322               unsigned long long rate,
    323               bool terminate)
    324 {
    325   static char grain[SEND_GRAIN];
    326   uint64_t sent = 0;
    327   size_t step = SEND_GRAIN;
    328   unsigned int nap = 0;
    329   char hdr[32];
    330 
    331   chunked_sent = 0;
    332 
    333   if (0 != rate)
    334   {
    335     step = (size_t) GNUNET_MIN ((unsigned long long) SEND_GRAIN,
    336                                 GNUNET_MAX (1ULL, rate / 10));
    337     nap = (unsigned int) ((1000ULL * step) / rate);
    338   }
    339   while (sent < total)
    340   {
    341     size_t n = (size_t) GNUNET_MIN ((uint64_t) step,
    342                                     total - sent);
    343     int hl = snprintf (hdr,
    344                        sizeof (hdr),
    345                        "%zx\r\n",
    346                        n);
    347 
    348     for (size_t i = 0; i < n; i++)
    349       grain[i] = (char) pattern_at (sent + i);
    350     if ( (! write_all (fd, hdr, (size_t) hl)) ||
    351          (! write_all (fd, grain, n)) ||
    352          (! write_all (fd, "\r\n", 2)) )
    353       return false;
    354     sent += n;
    355     chunked_sent = sent;
    356     if ( (0 != nap) &&
    357          (sent < total) )
    358       sleep_ms (nap);
    359     if (! run_flag)
    360       return false;
    361   }
    362   if (terminate)
    363     return write_all (fd,
    364                       "0\r\n\r\n",
    365                       5);
    366   return true;
    367 }
    368 
    369 
    370 /**
    371  * Read from @a fd into @a buf until "\r\n\r\n" appears.
    372  *
    373  * @param fd socket to read from
    374  * @param[out] buf where to put the header block
    375  * @param cap capacity of @a buf
    376  * @param[out] eoh set to the offset just past the CRLFCRLF
    377  * @return total bytes read, or -1 on error or overlong headers
    378  */
    379 static ssize_t
    380 read_until_eoh (int fd,
    381                 char *buf,
    382                 size_t cap,
    383                 size_t *eoh)
    384 {
    385   size_t pos = 0;
    386   size_t scan = 0;
    387 
    388   while (pos < cap)
    389   {
    390     ssize_t n = read (fd,
    391                       buf + pos,
    392                       cap - pos);
    393 
    394     if (n <= 0)
    395       return -1;
    396     pos += (size_t) n;
    397     while (scan + 3 < pos)
    398     {
    399       if ( ('\r' == buf[scan]) &&
    400            ('\n' == buf[scan + 1]) &&
    401            ('\r' == buf[scan + 2]) &&
    402            ('\n' == buf[scan + 3]) )
    403       {
    404         *eoh = scan + 4;
    405         return (ssize_t) pos;
    406       }
    407       scan++;
    408     }
    409   }
    410   return -1;
    411 }
    412 
    413 
    414 /**
    415  * Value of the query parameter @a name in the request target @a
    416  * target, or @a dflt if it is absent or unparseable.
    417  *
    418  * @param target request target, NUL-terminated
    419  * @param name parameter name
    420  * @param dflt value to use if it is not there
    421  * @return the parsed value
    422  */
    423 static unsigned long long
    424 query_num (const char *target,
    425            const char *name,
    426            unsigned long long dflt)
    427 {
    428   const char *q = strchr (target, '?');
    429   size_t nlen = strlen (name);
    430 
    431   if (NULL == q)
    432     return dflt;
    433   for (const char *p = q + 1; '\0' != *p;)
    434   {
    435     if ( (0 == strncmp (p, name, nlen)) &&
    436          ('=' == p[nlen]) )
    437     {
    438       char *end;
    439       unsigned long long v;
    440 
    441       errno = 0;
    442       v = strtoull (&p[nlen + 1],
    443                     &end,
    444                     10);
    445       if ( (0 != errno) ||
    446            (end == &p[nlen + 1]) )
    447         return dflt;
    448       return v;
    449     }
    450     p = strchr (p, '&');
    451     if (NULL == p)
    452       break;
    453     p++;
    454   }
    455   return dflt;
    456 }
    457 
    458 
    459 /**
    460  * Does @a target name the path @a path (ignoring any query)?
    461  */
    462 static bool
    463 path_is (const char *target,
    464          const char *path)
    465 {
    466   size_t plen = strlen (path);
    467 
    468   return (0 == strncmp (target, path, plen)) &&
    469          ( ('\0' == target[plen]) ||
    470            ('?' == target[plen]) );
    471 }
    472 
    473 
    474 /**
    475  * Find the Content-Length in the request header block.
    476  *
    477  * @param hdr start of the (not NUL-terminated) header block
    478  * @param len number of bytes in @a hdr
    479  * @return the announced length, or -1 if there was none
    480  */
    481 static long long
    482 find_content_length (const char *hdr,
    483                      size_t len)
    484 {
    485   static const char name[] = "content-length:";
    486   const size_t nlen = sizeof (name) - 1;
    487 
    488   for (size_t i = 0; i + nlen <= len; i++)
    489   {
    490     size_t j;
    491     long long v = 0;
    492     bool digits = false;
    493 
    494     if ( (0 == i) ||
    495          ('\n' != hdr[i - 1]) )
    496       continue;
    497     if (0 != strncasecmp (&hdr[i],
    498                           name,
    499                           nlen))
    500       continue;
    501     j = i + nlen;
    502     while ( (j < len) &&
    503             ( (' ' == hdr[j]) || ('\t' == hdr[j]) ) )
    504       j++;
    505     while ( (j < len) &&
    506             ('0' <= hdr[j]) &&
    507             ('9' >= hdr[j]) )
    508     {
    509       if (v > (LLONG_MAX - (hdr[j] - '0')) / 10)
    510         return -1;
    511       v = v * 10 + (hdr[j] - '0');
    512       digits = true;
    513       j++;
    514     }
    515     return digits ? v : -1;
    516   }
    517   return -1;
    518 }
    519 
    520 
    521 /**
    522  * Is the request header block chunked?
    523  *
    524  * @param hdr start of the header block
    525  * @param len number of bytes in @a hdr
    526  * @return true if a Transfer-Encoding naming chunked is present
    527  */
    528 static bool
    529 is_chunked_request (const char *hdr,
    530                     size_t len)
    531 {
    532   static const char name[] = "transfer-encoding:";
    533   const size_t nlen = sizeof (name) - 1;
    534 
    535   for (size_t i = 0; i + nlen <= len; i++)
    536   {
    537     if ( (0 == i) ||
    538          ('\n' != hdr[i - 1]) )
    539       continue;
    540     if (0 == strncasecmp (&hdr[i],
    541                           name,
    542                           nlen))
    543       return true;
    544   }
    545   return false;
    546 }
    547 
    548 
    549 /**
    550  * Read and verify a request body, at most @a rate bytes per second.
    551  *
    552  * Verification is against `pattern_at()', so a body that arrived
    553  * complete but reordered or with a duplicated block is caught -- the
    554  * byte count alone would not notice.  Both framings are handled: a
    555  * declared length, and chunked, which is what libcurl sends when the
    556  * length is not known in advance.
    557  *
    558  * @param fd socket to read from
    559  * @param pre bytes of body already read with the headers
    560  * @param pre_len number of bytes in @a pre
    561  * @param declared Content-Length, or -1 for chunked/unknown
    562  * @param chunked read the body as chunks
    563  * @param rate bytes per second, 0 for unlimited
    564  * @param stop_after stop reading after this many body bytes, 0 for
    565  *        "read it all"
    566  * @param[out] ok set to false if the pattern did not match
    567  * @return number of body bytes read
    568  */
    569 static uint64_t
    570 drain_body (int fd,
    571             const char *pre,
    572             size_t pre_len,
    573             long long declared,
    574             bool chunked,
    575             unsigned long long rate,
    576             uint64_t stop_after,
    577             bool *ok)
    578 {
    579   static char buf[SEND_GRAIN];
    580   uint64_t got = 0;
    581   size_t step = sizeof (buf);
    582   unsigned int nap = 0;
    583   /* Chunk framing is stripped by a tiny state machine rather than a
    584      parser: we only ever read what libcurl writes.  The state has to
    585      survive a read() boundary falling anywhere -- including between a
    586      chunk's last data byte and its trailing CRLF, which is why
    587      skipping that CRLF is a state and not two bytes consumed on the
    588      spot.  Getting that wrong makes the next read start on "\r\n",
    589      which parses as an empty chunk-size line, which reads as the
    590      terminating chunk: the body ends early and the byte count is
    591      short but self-consistent. */
    592   enum
    593   {
    594     CH_HDR,   /* accumulating the chunk-size line */
    595     CH_DATA,  /* @e chunk_left bytes of chunk data to come */
    596     CH_CRLF   /* @e crlf_left bytes of the post-data CRLF to skip */
    597   } cstate = CH_HDR;
    598   uint64_t chunk_left = 0;
    599   unsigned int crlf_left = 0;
    600   char hdrline[32];
    601   size_t hdrpos = 0;
    602 
    603   *ok = true;
    604   if (0 != rate)
    605   {
    606     step = (size_t) GNUNET_MIN ((unsigned long long) sizeof (buf),
    607                                 GNUNET_MAX (1ULL, rate / 10));
    608     nap = (unsigned int) ((1000ULL * step) / rate);
    609   }
    610   while (run_flag)
    611   {
    612     const char *p;
    613     size_t avail;
    614     ssize_t n;
    615 
    616     if (0 != pre_len)
    617     {
    618       p = pre;
    619       avail = pre_len;
    620       pre_len = 0;
    621     }
    622     else
    623     {
    624       if ( (! chunked) &&
    625            (declared >= 0) &&
    626            (got >= (uint64_t) declared) )
    627         break;
    628       if ( (0 != stop_after) &&
    629            (got >= stop_after) )
    630         break;
    631       n = read (fd,
    632                 buf,
    633                 step);
    634       if (n <= 0)
    635         break;
    636       p = buf;
    637       avail = (size_t) n;
    638       if (0 != nap)
    639         sleep_ms (nap);
    640     }
    641     while (0 != avail)
    642     {
    643       if (! chunked)
    644       {
    645         for (size_t i = 0; i < avail; i++)
    646           if ((uint8_t) p[i] != pattern_at (got + i))
    647           {
    648             *ok = false;
    649             break;
    650           }
    651         got += avail;
    652         avail = 0;
    653         continue;
    654       }
    655       switch (cstate)
    656       {
    657       case CH_HDR:
    658         /* Accumulate up to the CRLF that ends the chunk size line. */
    659         while ( (0 != avail) &&
    660                 (hdrpos + 1 < sizeof (hdrline)) )
    661         {
    662           char c = *p++;
    663 
    664           avail--;
    665           hdrline[hdrpos++] = c;
    666           if ( (hdrpos >= 2) &&
    667                ('\r' == hdrline[hdrpos - 2]) &&
    668                ('\n' == hdrline[hdrpos - 1]) )
    669           {
    670             hdrline[hdrpos - 2] = '\0';
    671             chunk_left = strtoull (hdrline,
    672                                    NULL,
    673                                    16);
    674             hdrpos = 0;
    675             cstate = (0 == chunk_left) ? CH_HDR : CH_DATA;
    676             break;
    677           }
    678         }
    679         if ( (CH_HDR == cstate) &&
    680              (0 == hdrpos) )
    681           return got; /* terminating chunk */
    682         break;
    683       case CH_DATA:
    684         {
    685           size_t take = (size_t) GNUNET_MIN ((uint64_t) avail,
    686                                              chunk_left);
    687 
    688           for (size_t i = 0; i < take; i++)
    689             if ((uint8_t) p[i] != pattern_at (got + i))
    690             {
    691               *ok = false;
    692               break;
    693             }
    694           got += take;
    695           p += take;
    696           avail -= take;
    697           chunk_left -= take;
    698           if (0 == chunk_left)
    699           {
    700             cstate = CH_CRLF;
    701             crlf_left = 2;
    702           }
    703         }
    704         break;
    705       case CH_CRLF:
    706         {
    707           size_t skip = (size_t) GNUNET_MIN ((uint64_t) avail,
    708                                              (uint64_t) crlf_left);
    709 
    710           p += skip;
    711           avail -= skip;
    712           crlf_left -= (unsigned int) skip;
    713           if (0 == crlf_left)
    714           {
    715             cstate = CH_HDR;
    716             hdrpos = 0;
    717           }
    718         }
    719         break;
    720       }
    721     }
    722   }
    723   return got;
    724 }
    725 
    726 
    727 /**
    728  * Serve one connection.
    729  *
    730  * @param fd the accepted socket
    731  */
    732 static void
    733 serve (int fd)
    734 {
    735   char hdr[HDR_MAX];
    736   char target[1024];
    737   size_t eoh = 0;
    738   ssize_t total;
    739   char resp[512];
    740   int rl;
    741   unsigned long long rate;
    742 
    743   total = read_until_eoh (fd,
    744                           hdr,
    745                           sizeof (hdr),
    746                           &eoh);
    747   if (0 > total)
    748     return;
    749   {
    750     /* Request line: METHOD SP target SP version */
    751     const char *sp1 = memchr (hdr, ' ', (size_t) total);
    752     const char *sp2;
    753     size_t tlen;
    754 
    755     if (NULL == sp1)
    756       return;
    757     sp2 = memchr (sp1 + 1,
    758                   ' ',
    759                   (size_t) total - (size_t) (sp1 + 1 - hdr));
    760     if (NULL == sp2)
    761       return;
    762     tlen = (size_t) (sp2 - sp1 - 1);
    763     if (tlen >= sizeof (target))
    764       return;
    765     memcpy (target,
    766             sp1 + 1,
    767             tlen);
    768     target[tlen] = '\0';
    769   }
    770   rate = query_num (target,
    771                     "rate",
    772                     0);
    773   snprintf (req_label,
    774             sizeof (req_label),
    775             "%s",
    776             target);
    777 
    778   if (path_is (target, "/cl"))
    779   {
    780     uint64_t bytes = query_num (target,
    781                                 "bytes",
    782                                 1024);
    783 
    784     rl = snprintf (resp,
    785                    sizeof (resp),
    786                    "HTTP/1.1 200 OK\r\n"
    787                    "Content-Type: application/octet-stream\r\n"
    788                    "Content-Length: %llu\r\n"
    789                    "\r\n",
    790                    (unsigned long long) bytes);
    791     if (write_all (fd, resp, (size_t) rl))
    792       req_bytes = send_pattern_counted (fd,
    793                                         0,
    794                                         bytes,
    795                                         rate);
    796     return;
    797   }
    798   if (path_is (target, "/chunked"))
    799   {
    800     uint64_t bytes = query_num (target,
    801                                 "bytes",
    802                                 1024);
    803 
    804     rl = snprintf (resp,
    805                    sizeof (resp),
    806                    "HTTP/1.1 200 OK\r\n"
    807                    "Content-Type: application/octet-stream\r\n"
    808                    "Transfer-Encoding: chunked\r\n"
    809                    "\r\n");
    810     if (write_all (fd, resp, (size_t) rl))
    811       (void) send_chunked (fd,
    812                            bytes,
    813                            rate,
    814                            true);
    815     req_bytes = chunked_sent;
    816     return;
    817   }
    818   if (path_is (target, "/short"))
    819   {
    820     uint64_t declare = query_num (target,
    821                                   "declare",
    822                                   100000);
    823     uint64_t send = query_num (target,
    824                                "send",
    825                                1000);
    826 
    827     rl = snprintf (resp,
    828                    sizeof (resp),
    829                    "HTTP/1.1 200 OK\r\n"
    830                    "Content-Type: application/octet-stream\r\n"
    831                    "Content-Length: %llu\r\n"
    832                    "\r\n",
    833                    (unsigned long long) declare);
    834     if (write_all (fd, resp, (size_t) rl))
    835       (void) send_pattern (fd,
    836                            0,
    837                            send,
    838                            rate);
    839     return;
    840   }
    841   if (path_is (target, "/chunk-abort"))
    842   {
    843     uint64_t after = query_num (target,
    844                                 "after",
    845                                 1000);
    846 
    847     rl = snprintf (resp,
    848                    sizeof (resp),
    849                    "HTTP/1.1 200 OK\r\n"
    850                    "Content-Type: application/octet-stream\r\n"
    851                    "Transfer-Encoding: chunked\r\n"
    852                    "\r\n");
    853     if (write_all (fd, resp, (size_t) rl))
    854       (void) send_chunked (fd,
    855                            after,
    856                            rate,
    857                            false); /* no terminating chunk */
    858     return;
    859   }
    860   if (path_is (target, "/hang"))
    861   {
    862     uint64_t after = query_num (target,
    863                                 "after",
    864                                 1000);
    865     struct pollfd pfd = {
    866       .fd = fd,
    867       .events = POLLRDHUP
    868     };
    869 
    870     rl = snprintf (resp,
    871                    sizeof (resp),
    872                    "HTTP/1.1 200 OK\r\n"
    873                    "Content-Type: application/octet-stream\r\n"
    874                    "Transfer-Encoding: chunked\r\n"
    875                    "\r\n");
    876     if (! write_all (fd, resp, (size_t) rl))
    877       return;
    878     if (! send_chunked (fd,
    879                         after,
    880                         rate,
    881                         false))
    882       return;
    883     /* Now say nothing, and do not close: the point is a connection
    884        that is open, established and idle, which only paivana's own
    885        stall watchdog can resolve. */
    886     (void) poll (&pfd,
    887                  1,
    888                  HANG_TIMEOUT_MS);
    889     return;
    890   }
    891   if (path_is (target, "/slowstart"))
    892   {
    893     uint64_t delay = query_num (target,
    894                                 "delay",
    895                                 1000);
    896     uint64_t bytes = query_num (target,
    897                                 "bytes",
    898                                 1024);
    899 
    900     rl = snprintf (resp,
    901                    sizeof (resp),
    902                    "HTTP/1.1 200 OK\r\n"
    903                    "Content-Type: application/octet-stream\r\n"
    904                    "Content-Length: %llu\r\n"
    905                    "\r\n",
    906                    (unsigned long long) bytes);
    907     if (! write_all (fd, resp, (size_t) rl))
    908       return;
    909     sleep_ms ((unsigned int) delay);
    910     (void) send_pattern (fd,
    911                          0,
    912                          bytes,
    913                          rate);
    914     return;
    915   }
    916   if (path_is (target, "/sink"))
    917   {
    918     long long declared = find_content_length (hdr,
    919                                               eoh);
    920     bool chunked = is_chunked_request (hdr,
    921                                        eoh);
    922     bool ok;
    923     uint64_t got;
    924     char body[128];
    925     int bl;
    926 
    927     got = drain_body (fd,
    928                       &hdr[eoh],
    929                       (size_t) total - eoh,
    930                       declared,
    931                       chunked,
    932                       rate,
    933                       0,
    934                       &ok);
    935     req_bytes = got;
    936     bl = snprintf (body,
    937                    sizeof (body),
    938                    "bytes=%llu framing=%s pattern=%s\n",
    939                    (unsigned long long) got,
    940                    chunked ? "chunked" : "length",
    941                    ok ? "ok" : "CORRUPT");
    942     rl = snprintf (resp,
    943                    sizeof (resp),
    944                    "HTTP/1.1 200 OK\r\n"
    945                    "Content-Type: text/plain\r\n"
    946                    "Content-Length: %d\r\n"
    947                    "\r\n",
    948                    bl);
    949     if (write_all (fd, resp, (size_t) rl))
    950       (void) write_all (fd, body, (size_t) bl);
    951     return;
    952   }
    953   if (path_is (target, "/sink-early"))
    954   {
    955     uint64_t after = query_num (target,
    956                                 "after",
    957                                 1024);
    958     long long declared = find_content_length (hdr,
    959                                               eoh);
    960     bool chunked = is_chunked_request (hdr,
    961                                        eoh);
    962     bool ok;
    963     static const char body[] = "too large\n";
    964 
    965     (void) drain_body (fd,
    966                        &hdr[eoh],
    967                        (size_t) total - eoh,
    968                        declared,
    969                        chunked,
    970                        rate,
    971                        after,
    972                        &ok);
    973     /* Answer while the body is (very probably) still coming.  This is
    974        the case that only exists because the request is streamed: with
    975        the body buffered first, the origin could not have answered
    976        before seeing all of it. */
    977     rl = snprintf (resp,
    978                    sizeof (resp),
    979                    "HTTP/1.1 413 Content Too Large\r\n"
    980                    "Content-Type: text/plain\r\n"
    981                    "Connection: close\r\n"
    982                    "Content-Length: %zu\r\n"
    983                    "\r\n%s",
    984                    sizeof (body) - 1,
    985                    body);
    986     (void) write_all (fd, resp, (size_t) rl);
    987     return;
    988   }
    989   if (path_is (target, "/range"))
    990   {
    991     /* Honour a single "bytes=A-B" range, so that the 206 and its
    992        Content-Range can be checked end to end.  Nothing here parses
    993        the general grammar: the driver sends one form. */
    994     uint64_t bytes = query_num (target,
    995                                 "bytes",
    996                                 1048576);
    997     unsigned long long from = 0;
    998     unsigned long long to = bytes - 1;
    999     const char *r = NULL;
   1000 
   1001     for (size_t i = 0; i + 6 <= eoh; i++)
   1002       if ( (0 != i) &&
   1003            ('\n' == hdr[i - 1]) &&
   1004            (0 == strncasecmp (&hdr[i], "range:", 6)) )
   1005       {
   1006         r = &hdr[i + 6];
   1007         break;
   1008       }
   1009     if (NULL != r)
   1010     {
   1011       const char *eq = strchr (r, '=');
   1012 
   1013       if (NULL != eq)
   1014       {
   1015         char *end;
   1016 
   1017         from = strtoull (eq + 1, &end, 10);
   1018         if ('-' == *end)
   1019         {
   1020           unsigned long long t = strtoull (end + 1, &end, 10);
   1021 
   1022           if (0 != t)
   1023             to = t;
   1024         }
   1025       }
   1026     }
   1027     if (to >= bytes)
   1028       to = bytes - 1;
   1029     rl = snprintf (resp,
   1030                    sizeof (resp),
   1031                    "HTTP/1.1 206 Partial Content\r\n"
   1032                    "Content-Type: application/octet-stream\r\n"
   1033                    "Content-Range: bytes %llu-%llu/%llu\r\n"
   1034                    "Content-Length: %llu\r\n"
   1035                    "\r\n",
   1036                    from,
   1037                    to,
   1038                    (unsigned long long) bytes,
   1039                    to - from + 1);
   1040     if (write_all (fd, resp, (size_t) rl))
   1041       (void) send_pattern (fd,
   1042                            from,
   1043                            to - from + 1,
   1044                            rate);
   1045     return;
   1046   }
   1047   if (path_is (target, "/mute"))
   1048   {
   1049     /* Accept the connection and say nothing at all.  Distinct from an
   1050        origin that is not there, which is a 502: this one is a 504, and
   1051        telling them apart is what paivana's time-to-headers clock is
   1052        for. */
   1053     struct pollfd pfd = {
   1054       .fd = fd,
   1055       .events = POLLRDHUP
   1056     };
   1057 
   1058     (void) poll (&pfd,
   1059                  1,
   1060                  HANG_TIMEOUT_MS);
   1061     return;
   1062   }
   1063   if (path_is (target, "/trailers"))
   1064   {
   1065     /* A chunked response with a trailer section.  RFC 9110 ยง6.5.1
   1066        forbids merging a trailer into the header section, and the
   1067        response has in any case already been queued by the time these
   1068        arrive, so they must be dropped rather than added to it. */
   1069     uint64_t bytes = query_num (target,
   1070                                 "bytes",
   1071                                 4096);
   1072 
   1073     rl = snprintf (resp,
   1074                    sizeof (resp),
   1075                    "HTTP/1.1 200 OK\r\n"
   1076                    "Content-Type: application/octet-stream\r\n"
   1077                    "Trailer: X-Trailer-Check\r\n"
   1078                    "Transfer-Encoding: chunked\r\n"
   1079                    "\r\n");
   1080     if (! write_all (fd, resp, (size_t) rl))
   1081       return;
   1082     if (! send_chunked (fd,
   1083                         bytes,
   1084                         rate,
   1085                         false))
   1086       return;
   1087     (void) write_all (fd,
   1088                       "0\r\n"
   1089                       "X-Trailer-Check: leaked\r\n"
   1090                       "\r\n",
   1091                       strlen ("0\r\nX-Trailer-Check: leaked\r\n\r\n"));
   1092     return;
   1093   }
   1094   if (path_is (target, "/interim"))
   1095   {
   1096     /* A 1xx before the final response, carrying a header that must not
   1097        reappear on it. */
   1098     uint64_t bytes = query_num (target,
   1099                                 "bytes",
   1100                                 4096);
   1101 
   1102     rl = snprintf (resp,
   1103                    sizeof (resp),
   1104                    "HTTP/1.1 103 Early Hints\r\n"
   1105                    "X-Interim-Check: leaked\r\n"
   1106                    "\r\n"
   1107                    "HTTP/1.1 200 OK\r\n"
   1108                    "Content-Type: application/octet-stream\r\n"
   1109                    "Content-Length: %llu\r\n"
   1110                    "\r\n",
   1111                    (unsigned long long) bytes);
   1112     if (write_all (fd, resp, (size_t) rl))
   1113       (void) send_pattern (fd,
   1114                            0,
   1115                            bytes,
   1116                            rate);
   1117     return;
   1118   }
   1119   if (path_is (target, "/status"))
   1120   {
   1121     unsigned long long code = query_num (target,
   1122                                          "code",
   1123                                          204);
   1124     unsigned long long len = query_num (target,
   1125                                         "len",
   1126                                         UINT64_MAX);
   1127 
   1128     if (UINT64_MAX == len)
   1129       rl = snprintf (resp,
   1130                      sizeof (resp),
   1131                      "HTTP/1.1 %llu Status\r\n"
   1132                      "\r\n",
   1133                      code);
   1134     else
   1135       rl = snprintf (resp,
   1136                      sizeof (resp),
   1137                      "HTTP/1.1 %llu Status\r\n"
   1138                      "Content-Length: %llu\r\n"
   1139                      "\r\n",
   1140                      code,
   1141                      len);
   1142     (void) write_all (fd, resp, (size_t) rl);
   1143     return;
   1144   }
   1145   rl = snprintf (resp,
   1146                  sizeof (resp),
   1147                  "HTTP/1.1 404 Not Found\r\n"
   1148                  "Content-Length: 0\r\n"
   1149                  "\r\n");
   1150   (void) write_all (fd, resp, (size_t) rl);
   1151 }
   1152 
   1153 
   1154 int
   1155 main (int argc,
   1156       char **argv)
   1157 {
   1158   int lsock;
   1159   int port;
   1160   int one = 1;
   1161   struct sockaddr_in addr;
   1162 
   1163   if (2 > argc)
   1164   {
   1165     fprintf (stderr,
   1166              "usage: %s PORT\n",
   1167              argv[0]);
   1168     return 1;
   1169   }
   1170   port = atoi (argv[1]);
   1171   signal (SIGINT, &on_sig);
   1172   signal (SIGTERM, &on_sig);
   1173   /* A peer that goes away mid-body is the normal case here, not an
   1174      error; without this the first such write kills the process. */
   1175   signal (SIGPIPE, SIG_IGN);
   1176   lsock = socket (AF_INET,
   1177                   SOCK_STREAM,
   1178                   0);
   1179   if (0 > lsock)
   1180   {
   1181     perror ("socket");
   1182     return 1;
   1183   }
   1184   (void) setsockopt (lsock,
   1185                      SOL_SOCKET,
   1186                      SO_REUSEADDR,
   1187                      &one,
   1188                      sizeof (one));
   1189   memset (&addr, 0, sizeof (addr));
   1190   addr.sin_family = AF_INET;
   1191   addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
   1192   addr.sin_port = htons ((uint16_t) port);
   1193   if (0 != bind (lsock,
   1194                  (struct sockaddr *) &addr,
   1195                  sizeof (addr)))
   1196   {
   1197     perror ("bind");
   1198     return 1;
   1199   }
   1200   if (0 != listen (lsock, 64))
   1201   {
   1202     perror ("listen");
   1203     return 1;
   1204   }
   1205   fprintf (stderr,
   1206            "stream_upstream listening on port %d\n",
   1207            port);
   1208   fflush (stderr);
   1209   while (run_flag)
   1210   {
   1211     int fd = accept (lsock, NULL, NULL);
   1212     pid_t pid;
   1213 
   1214     if (0 > fd)
   1215     {
   1216       if (EINTR == errno)
   1217         continue;
   1218       break;
   1219     }
   1220     /* One process per connection.  Not for throughput but because
   1221        several of the targets deliberately do not finish -- `/hang'
   1222        parks for two minutes, `/cl?rate=' dribbles for as long as the
   1223        driver asks -- and a single-threaded accept loop would make the
   1224        concurrency cases serialise behind them. */
   1225     pid = fork ();
   1226     if (0 == pid)
   1227     {
   1228       struct timespec t0;
   1229       struct timespec t1;
   1230 
   1231       close (lsock);
   1232       clock_gettime (CLOCK_MONOTONIC,
   1233                      &t0);
   1234       serve (fd);
   1235       clock_gettime (CLOCK_MONOTONIC,
   1236                      &t1);
   1237       /* One line per connection, for the cases that assert on how the
   1238          origin was paced rather than on what the client received.
   1239          Written after the socket work so the timing covers it, and to
   1240          stderr so it lands in the driver's log unbuffered. */
   1241       fprintf (stderr,
   1242                "served target=%s bytes=%llu ms=%ld\n",
   1243                req_label,
   1244                (unsigned long long) req_bytes,
   1245                (long) ((t1.tv_sec - t0.tv_sec) * 1000
   1246                        + (t1.tv_nsec - t0.tv_nsec) / 1000000));
   1247       fflush (stderr);
   1248       close (fd);
   1249       _exit (0);
   1250     }
   1251     close (fd);
   1252     if (0 > pid)
   1253       perror ("fork");
   1254     /* Reap whatever has finished; no zombies, no blocking. */
   1255     while (0 < waitpid (-1, NULL, WNOHANG))
   1256       ; /* again */
   1257   }
   1258   close (lsock);
   1259   return 0;
   1260 }
   1261 
   1262 
   1263 /* end of stream_upstream.c */