Coverage Report

Created: 2026-10-28 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/curl_fuzzer_doh.cc
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| | |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
25
/**
26
 * @file
27
 * @brief Loopback HTTP responder for the legacy DoH coverage lane.
28
 *
29
 * curl creates its internal DoH easy handle itself and does not propagate the
30
 * harness socketpair callbacks to it. A seed that sets TLV_TYPE_DOH_LOOPBACK
31
 * therefore gets a real plaintext HTTP listener on 127.0.0.1, and
32
 * CURLOPT_DOH_URL points at it. The listener synthesizes a DNS answer matching
33
 * the probe's QTYPE or serves a seed-supplied response body verbatim.
34
 */
35
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <strings.h>
40
#include <unistd.h>
41
#include <errno.h>
42
#include <fcntl.h>
43
#include <sys/select.h>
44
#include <sys/socket.h>
45
#include <netinet/in.h>
46
#include <arpa/inet.h>
47
#include <curl/curl.h>
48
#include "curl_fuzzer.h"
49
50
/**
51
 * No-op debug callback used by the verbose DoH lane. Turning on verbose makes
52
 * curl evaluate the arguments to infof(), which is what reaches the DoH
53
 * status/error string helpers. The callback keeps that tracing off stderr.
54
 */
55
int fuzz_doh_debug_callback(CURL *handle,
56
                            curl_infotype type,
57
                            char *data,
58
                            size_t size,
59
                            void *userptr)
60
228k
{
61
228k
  FUZZ_DATA *fuzz = (FUZZ_DATA *)userptr;
62
63
228k
  (void)handle;
64
228k
  if(fuzz && fuzz->verbose && type == CURLINFO_TEXT && size)
65
0
    printf("FUZZ: DoH trace: %.*s", (int)size, data);
66
228k
  (void)type;
67
228k
  (void)data;
68
228k
  (void)size;
69
228k
  return 0;
70
228k
}
71
72
/**
73
 * Reset the loopback DoH responder. FUZZ_DATA is memset twice (once before a
74
 * possible early exit and once inside fuzz_initialize_fuzz_data), so the
75
 * sentinel descriptors must be installed after each memset.
76
 */
77
void fuzz_doh_server_init(FUZZ_DATA *fuzz)
78
454k
{
79
454k
  int i;
80
81
454k
  fuzz->doh.listener = CURL_SOCKET_BAD;
82
454k
  fuzz->doh.enabled = 0;
83
454k
  fuzz->doh.http_status = 200;
84
454k
  fuzz->doh.response = NULL;
85
454k
  fuzz->doh.response_len = 0;
86
454k
  fuzz->doh.url[0] = '\0';
87
454k
  fuzz->doh.saved_no_proxy_lower = NULL;
88
454k
  fuzz->doh.saved_no_proxy_upper = NULL;
89
454k
  fuzz->doh.lower_no_proxy_was_set = 0;
90
454k
  fuzz->doh.upper_no_proxy_was_set = 0;
91
454k
  fuzz->doh.proxy_bypass_active = 0;
92
93
2.27M
  for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) {
94
1.81M
    fuzz->doh.conns[i].fd = CURL_SOCKET_BAD;
95
1.81M
    fuzz->doh.conns[i].state = FUZZ_DOH_CONN_FREE;
96
1.81M
    fuzz->doh.conns[i].request_len = 0;
97
1.81M
    fuzz->doh.conns[i].response_len = 0;
98
1.81M
    fuzz->doh.conns[i].response_off = 0;
99
1.81M
  }
100
454k
}
101
102
static void fuzz_doh_conn_close(FUZZ_DOH_CONN *conn)
103
911k
{
104
911k
  if(conn->fd != CURL_SOCKET_BAD) {
105
3.00k
    close(conn->fd);
106
3.00k
    conn->fd = CURL_SOCKET_BAD;
107
3.00k
  }
108
911k
  conn->state = FUZZ_DOH_CONN_FREE;
109
911k
  conn->request_len = 0;
110
911k
  conn->response_len = 0;
111
911k
  conn->response_off = 0;
112
911k
}
113
114
/**
115
 * Bind a plaintext HTTP listener on 127.0.0.1 and build the CURLOPT_DOH_URL
116
 * value that points at it. The DoH sub-handle is created by curl and therefore
117
 * opens a real socket instead of the harness socketpair; a real loopback
118
 * endpoint is the only harness-side way to complete the probe.
119
 */
120
int fuzz_doh_server_start(FUZZ_DATA *fuzz)
121
3.53k
{
122
3.53k
  int fd;
123
3.53k
  int flags;
124
3.53k
  struct sockaddr_in addr;
125
3.53k
  socklen_t addrlen;
126
127
3.53k
  memset(&addr, 0, sizeof(addr));
128
3.53k
  addr.sin_family = AF_INET;
129
3.53k
  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
130
3.53k
  addr.sin_port = 0;
131
132
3.53k
  fd = socket(AF_INET, SOCK_STREAM, 0);
133
3.53k
  if(fd < 0)
134
0
    return -1;
135
3.53k
  if(fd >= FD_SETSIZE) {
136
0
    close(fd);
137
0
    return -1;
138
0
  }
139
140
3.53k
  if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
141
0
    close(fd);
142
0
    return -1;
143
0
  }
144
3.53k
  if(listen(fd, 8) < 0) {
145
0
    close(fd);
146
0
    return -1;
147
0
  }
148
149
3.53k
  addrlen = sizeof(addr);
150
3.53k
  if(getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0) {
151
0
    close(fd);
152
0
    return -1;
153
0
  }
154
155
3.53k
  flags = fcntl(fd, F_GETFL, 0);
156
3.53k
  if(flags < 0) {
157
0
    close(fd);
158
0
    return -1;
159
0
  }
160
3.53k
  if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
161
0
    close(fd);
162
0
    return -1;
163
0
  }
164
165
3.53k
  fuzz->doh.listener = fd;
166
3.53k
  snprintf(fuzz->doh.url, sizeof(fuzz->doh.url),
167
3.53k
           "http://127.0.0.1:%d/dns-query", (int)ntohs(addr.sin_port));
168
3.53k
  return 0;
169
3.53k
}
170
171
/**
172
 * Copy one proxy environment variable so it can be restored after the input.
173
 * @return 0 on success, -1 when a set value could not be copied.
174
 */
175
static int fuzz_save_env(const char *name, char **copy, int *was_set)
176
7.06k
{
177
7.06k
  const char *value = getenv(name);
178
7.06k
  size_t length;
179
180
7.06k
  *copy = NULL;
181
7.06k
  *was_set = 0;
182
7.06k
  if(!value)
183
7.06k
    return 0;
184
185
0
  length = strlen(value) + 1;
186
0
  *copy = (char *)malloc(length);
187
0
  if(!*copy)
188
0
    return -1;
189
0
  memcpy(*copy, value, length);
190
0
  *was_set = 1;
191
0
  return 0;
192
0
}
193
194
static void fuzz_free_saved_env(char **copy, int *was_set)
195
7.06k
{
196
7.06k
  free(*copy);
197
7.06k
  *copy = NULL;
198
7.06k
  *was_set = 0;
199
7.06k
}
200
201
/**
202
 * Force NO_PROXY for the duration of a loopback input. The internal DoH easy
203
 * handle consults the process environment and does not inherit proxy options
204
 * from the parent transfer, so an ambient http_proxy/ALL_PROXY could divert
205
 * probes away from the listener and generate off-loopback traffic.
206
 */
207
void fuzz_doh_proxy_bypass_begin(FUZZ_DATA *fuzz)
208
3.53k
{
209
3.53k
  if(!fuzz->doh.enabled || fuzz->doh.proxy_bypass_active)
210
0
    return;
211
212
3.53k
  if(fuzz_save_env("no_proxy", &fuzz->doh.saved_no_proxy_lower,
213
3.53k
                   &fuzz->doh.lower_no_proxy_was_set) != 0 ||
214
3.53k
     fuzz_save_env("NO_PROXY", &fuzz->doh.saved_no_proxy_upper,
215
3.53k
                   &fuzz->doh.upper_no_proxy_was_set) != 0) {
216
0
    fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_lower,
217
0
                        &fuzz->doh.lower_no_proxy_was_set);
218
0
    fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_upper,
219
0
                        &fuzz->doh.upper_no_proxy_was_set);
220
0
    return;
221
0
  }
222
223
  /* Mark active before setenv so a partial failure is still restored. */
224
3.53k
  fuzz->doh.proxy_bypass_active = 1;
225
3.53k
  if(setenv("no_proxy", "*", 1) != 0 || setenv("NO_PROXY", "*", 1) != 0)
226
0
    fuzz_doh_proxy_bypass_end(fuzz);
227
3.53k
  else
228
3.53k
    FV_PRINTF(fuzz, "FUZZ: DoH proxy bypass enabled\n");
229
3.53k
}
230
231
void fuzz_doh_proxy_bypass_end(FUZZ_DATA *fuzz)
232
227k
{
233
227k
  if(!fuzz->doh.proxy_bypass_active)
234
223k
    return;
235
236
3.53k
  if(fuzz->doh.lower_no_proxy_was_set)
237
0
    (void)setenv("no_proxy", fuzz->doh.saved_no_proxy_lower, 1);
238
3.53k
  else
239
3.53k
    (void)unsetenv("no_proxy");
240
3.53k
  if(fuzz->doh.upper_no_proxy_was_set)
241
0
    (void)setenv("NO_PROXY", fuzz->doh.saved_no_proxy_upper, 1);
242
3.53k
  else
243
3.53k
    (void)unsetenv("NO_PROXY");
244
245
3.53k
  fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_lower,
246
3.53k
                      &fuzz->doh.lower_no_proxy_was_set);
247
3.53k
  fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_upper,
248
3.53k
                      &fuzz->doh.upper_no_proxy_was_set);
249
3.53k
  fuzz->doh.proxy_bypass_active = 0;
250
3.53k
  FV_PRINTF(fuzz, "FUZZ: DoH proxy bypass restored\n");
251
3.53k
}
252
253
void fuzz_doh_server_stop(FUZZ_DATA *fuzz)
254
227k
{
255
227k
  int i;
256
257
227k
  fuzz_doh_proxy_bypass_end(fuzz);
258
259
227k
  if(fuzz->doh.listener != CURL_SOCKET_BAD) {
260
3.53k
    close(fuzz->doh.listener);
261
3.53k
    fuzz->doh.listener = CURL_SOCKET_BAD;
262
3.53k
  }
263
1.13M
  for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++)
264
908k
    fuzz_doh_conn_close(&fuzz->doh.conns[i]);
265
227k
}
266
267
void fuzz_doh_server_add_fds(FUZZ_DATA *fuzz,
268
                             fd_set *readfds,
269
                             fd_set *writefds,
270
                             int *maxfd)
271
86.8k
{
272
86.8k
  int i;
273
274
86.8k
  if(fuzz->doh.listener != CURL_SOCKET_BAD) {
275
8.69k
    FD_SET(fuzz->doh.listener, readfds);
276
8.69k
    *maxfd = FUZZ_MAX((int)fuzz->doh.listener, *maxfd);
277
8.69k
  }
278
279
434k
  for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) {
280
347k
    FUZZ_DOH_CONN *conn = &fuzz->doh.conns[i];
281
347k
    if(conn->fd == CURL_SOCKET_BAD)
282
341k
      continue;
283
6.00k
    if(conn->state == FUZZ_DOH_CONN_READING)
284
6.00k
      FD_SET(conn->fd, readfds);
285
3.00k
    else if(conn->state == FUZZ_DOH_CONN_WRITING)
286
3.00k
      FD_SET(conn->fd, writefds);
287
6.00k
    *maxfd = FUZZ_MAX((int)conn->fd, *maxfd);
288
6.00k
  }
289
86.8k
}
290
291
/**
292
 * Find the end of the HTTP headers and the Content-Length-delimited body.
293
 * @return 1 when a complete request is buffered, 0 when more bytes are needed,
294
 *         -1 when the request is too large or otherwise unusable.
295
 */
296
static int fuzz_doh_request_complete(FUZZ_DOH_CONN *conn,
297
                                     size_t *body_off,
298
                                     size_t *body_len)
299
3.00k
{
300
3.00k
  size_t i;
301
3.00k
  size_t header_end = 0;
302
3.00k
  size_t content_length = 0;
303
3.00k
  int have_length = 0;
304
305
360k
  for(i = 0; (i + 3) < conn->request_len; i++) {
306
360k
    if(conn->request[i] == '\r' && conn->request[i + 1] == '\n' &&
307
15.0k
       conn->request[i + 2] == '\r' && conn->request[i + 3] == '\n') {
308
3.00k
      header_end = i + 4;
309
3.00k
      break;
310
3.00k
    }
311
360k
  }
312
3.00k
  if(!header_end)
313
0
    return 0;
314
315
3.00k
  i = 0;
316
21.0k
  while((i + 1) < header_end) {
317
18.0k
    size_t line_end = i;
318
351k
    while((line_end + 1) < header_end &&
319
351k
          !(conn->request[line_end] == '\r' &&
320
18.0k
            conn->request[line_end + 1] == '\n'))
321
333k
      line_end++;
322
323
18.0k
    if((line_end - i) > 15 &&
324
12.0k
       !strncasecmp((const char *)&conn->request[i], "Content-Length:", 15)) {
325
3.00k
      const char *value = (const char *)&conn->request[i + 15];
326
3.00k
      char *end = NULL;
327
3.00k
      unsigned long parsed = strtoul(value, &end, 10);
328
3.00k
      if(end != value) {
329
3.00k
        content_length = (size_t)parsed;
330
3.00k
        have_length = 1;
331
3.00k
      }
332
3.00k
    }
333
18.0k
    i = line_end + 2;
334
18.0k
  }
335
336
3.00k
  if(!have_length)
337
0
    content_length = conn->request_len - header_end;
338
339
3.00k
  if(content_length > (sizeof(conn->request) - header_end))
340
0
    return -1;
341
3.00k
  if(conn->request_len < (header_end + content_length))
342
0
    return 0;
343
344
3.00k
  *body_off = header_end;
345
3.00k
  *body_len = content_length;
346
3.00k
  return 1;
347
3.00k
}
348
349
/**
350
 * Synthesize a minimal DNS response matching the query's QTYPE. A and AAAA
351
 * answers point at loopback so the outer transfer can continue through the
352
 * existing socketpair mock. HTTPS queries get an HTTPS RR carrying an alpn
353
 * SvcParam so the HTTPS-connect filter can select its first ALPN from the
354
 * resolver record. Other QTYPEs get NOTIMP.
355
 */
356
static size_t fuzz_doh_build_dns_response(const unsigned char *query,
357
                                          size_t query_len,
358
                                          unsigned char *resp,
359
                                          size_t resp_cap)
360
2.97k
{
361
2.97k
  size_t i = 12;
362
2.97k
  size_t qname_end;
363
2.97k
  unsigned short qtype;
364
2.97k
  size_t question_len;
365
2.97k
  size_t rdlength;
366
2.97k
  size_t total;
367
2.97k
  unsigned char *p;
368
2.97k
  int has_answer;
369
2.97k
  int answer_count;
370
2.97k
  int n;
371
372
2.97k
  if(query_len < 12)
373
0
    return 0;
374
375
7.53k
  while(i < query_len) {
376
7.53k
    unsigned char label = query[i];
377
7.53k
    if(label == 0) {
378
2.97k
      i++;
379
2.97k
      break;
380
2.97k
    }
381
4.56k
    if(label & 0xc0)
382
0
      return 0;
383
4.56k
    if((i + 1 + label) > query_len)
384
0
      return 0;
385
4.56k
    i += (size_t)1 + label;
386
4.56k
  }
387
2.97k
  if((i + 4) > query_len)
388
0
    return 0;
389
390
2.97k
  qname_end = i;
391
2.97k
  qtype = (unsigned short)((query[qname_end] << 8) |
392
2.97k
                           query[qname_end + 1]);
393
2.97k
  question_len = (qname_end + 4) - 12;
394
2.97k
  has_answer = (qtype == 1 || qtype == 28 || qtype == 65);
395
2.97k
  if(qtype == 1)
396
1.41k
    rdlength = 4;
397
1.55k
  else if(qtype == 28)
398
1.31k
    rdlength = 16;
399
245
  else
400
245
    rdlength = 13; /* priority + root target + alpn SvcParam */
401
2.97k
  answer_count = (qtype == 65) ? 1 : (has_answer ? 2 : 0);
402
2.97k
  total = 12 + question_len +
403
2.97k
          ((size_t)answer_count * (12 + rdlength));
404
405
2.97k
  if(total > resp_cap)
406
0
    return 0;
407
408
2.97k
  memset(resp, 0, 12);
409
2.97k
  resp[2] = 0x81; /* QR=1, RD=1 */
410
2.97k
  resp[3] = has_answer ? 0x80 : 0x84; /* RA=1; rcode 4 for unsupported */
411
2.97k
  resp[5] = 1; /* QDCOUNT */
412
2.97k
  resp[7] = (unsigned char)answer_count; /* ANCOUNT */
413
2.97k
  memcpy(&resp[12], &query[12], question_len);
414
415
2.97k
  p = &resp[12 + question_len];
416
8.66k
  for(n = 0; n < answer_count; n++) {
417
5.69k
    *p++ = 0xc0; /* compressed name pointer to the question */
418
5.69k
    *p++ = 0x0c;
419
5.69k
    *p++ = (unsigned char)(qtype >> 8);
420
5.69k
    *p++ = (unsigned char)(qtype & 0xff);
421
5.69k
    *p++ = 0;
422
5.69k
    *p++ = 1; /* CLASS IN */
423
5.69k
    *p++ = 0;
424
5.69k
    *p++ = 0;
425
5.69k
    *p++ = 0;
426
5.69k
    *p++ = 60; /* TTL */
427
5.69k
    *p++ = 0;
428
5.69k
    *p++ = (unsigned char)rdlength;
429
5.69k
    if(qtype == 1) {
430
2.83k
      *p++ = 127;
431
2.83k
      *p++ = 0;
432
2.83k
      *p++ = 0;
433
2.83k
      *p++ = (unsigned char)(1 + n);
434
2.83k
    }
435
2.86k
    else if(qtype == 28) {
436
2.62k
      memset(p, 0, 16);
437
2.62k
      p[15] = (unsigned char)(1 + n); /* ::1, ::2 */
438
2.62k
      p += 16;
439
2.62k
    }
440
245
    else {
441
      /* HTTPS RR: priority 1, root target, alpn=h2,h3.
442
         SvcParam key 1 (alpn) holds length-prefixed protocol names, so the
443
         value is 02 'h' '2' 02 'h' '3'. This exercises the HTTPS-connect
444
         filter's HTTPS-RR ALPN selection instead of falling back to the
445
         configured HTTP-version preference. */
446
245
      static const unsigned char httpsrr_alpn[] = {
447
245
        0, 1,              /* priority = 1 */
448
245
        0,                 /* root target (".") */
449
245
        0, 1,              /* SvcParamKey = alpn */
450
245
        0, 6,              /* SvcParamValue length */
451
245
        2, 'h', '2',       /* h2 */
452
245
        2, 'h', '3'        /* h3 */
453
245
      };
454
245
      memcpy(p, httpsrr_alpn, sizeof(httpsrr_alpn));
455
245
      p += sizeof(httpsrr_alpn);
456
245
    }
457
5.69k
  }
458
459
2.97k
  return (size_t)(p - resp);
460
2.97k
}
461
462
static int fuzz_doh_build_http_response(FUZZ_DATA *fuzz,
463
                                        FUZZ_DOH_CONN *conn,
464
                                        const unsigned char *body,
465
                                        size_t body_len)
466
3.00k
{
467
3.00k
  int header_len;
468
469
3.00k
  header_len = snprintf((char *)conn->response, sizeof(conn->response),
470
3.00k
                        "HTTP/1.1 %u DoH\r\n"
471
3.00k
                        "Content-Type: application/dns-message\r\n"
472
3.00k
                        "Content-Length: %zu\r\n"
473
3.00k
                        "Connection: close\r\n"
474
3.00k
                        "\r\n",
475
3.00k
                        fuzz->doh.http_status, body_len);
476
3.00k
  if(header_len < 0)
477
0
    return -1;
478
3.00k
  if((size_t)header_len + body_len > sizeof(conn->response))
479
0
    return -1;
480
481
3.00k
  if(body_len)
482
3.00k
    memcpy(&conn->response[header_len], body, body_len);
483
3.00k
  conn->response_len = (size_t)header_len + body_len;
484
3.00k
  conn->response_off = 0;
485
3.00k
  return 0;
486
3.00k
}
487
488
/**
489
 * Accept pending probe connections and advance the small request/response
490
 * state machine. Called with the fd sets returned by select().
491
 */
492
int fuzz_doh_server_service(FUZZ_DATA *fuzz,
493
                            fd_set *readfds,
494
                            fd_set *writefds)
495
78.0k
{
496
78.0k
  int i;
497
78.0k
  int progress = 0;
498
499
78.0k
  if(fuzz->doh.listener != CURL_SOCKET_BAD &&
500
78.0k
     FD_ISSET(fuzz->doh.listener, readfds)) {
501
4.66k
    for(;;) {
502
4.66k
      curl_socket_t fd = accept(fuzz->doh.listener, NULL, NULL);
503
4.66k
      FUZZ_DOH_CONN *conn = NULL;
504
4.66k
      int flags;
505
506
4.66k
      if(fd < 0)
507
1.66k
        break;
508
3.00k
      if(fd >= FD_SETSIZE) {
509
0
        close(fd);
510
0
        continue;
511
0
      }
512
4.53k
      for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) {
513
4.53k
        if(fuzz->doh.conns[i].fd == CURL_SOCKET_BAD) {
514
3.00k
          conn = &fuzz->doh.conns[i];
515
3.00k
          break;
516
3.00k
        }
517
4.53k
      }
518
3.00k
      if(!conn) {
519
0
        close(fd);
520
0
        continue;
521
0
      }
522
523
3.00k
      flags = fcntl(fd, F_GETFL, 0);
524
3.00k
      if(flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
525
0
        close(fd);
526
0
        continue;
527
0
      }
528
529
3.00k
      conn->fd = fd;
530
3.00k
      conn->state = FUZZ_DOH_CONN_READING;
531
3.00k
      conn->request_len = 0;
532
3.00k
      conn->response_len = 0;
533
3.00k
      conn->response_off = 0;
534
3.00k
      progress = 1;
535
3.00k
      FV_PRINTF(fuzz, "FUZZ: DoH accepted fd %d\n", (int)fd);
536
3.00k
    }
537
1.66k
  }
538
539
390k
  for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) {
540
312k
    FUZZ_DOH_CONN *conn = &fuzz->doh.conns[i];
541
312k
    if(conn->fd == CURL_SOCKET_BAD)
542
303k
      continue;
543
544
9.01k
    if(conn->state == FUZZ_DOH_CONN_READING &&
545
9.01k
       FD_ISSET(conn->fd, readfds)) {
546
6.01k
      for(;;) {
547
6.01k
        ssize_t n;
548
6.01k
        if(conn->request_len >= sizeof(conn->request))
549
0
          break;
550
6.01k
        n = recv(conn->fd, &conn->request[conn->request_len],
551
6.01k
                 sizeof(conn->request) - conn->request_len, 0);
552
6.01k
        if(n > 0) {
553
3.00k
          conn->request_len += (size_t)n;
554
3.00k
          progress = 1;
555
3.00k
          continue;
556
3.00k
        }
557
3.00k
        if(n == 0) {
558
0
          fuzz_doh_conn_close(conn);
559
0
          break;
560
0
        }
561
3.00k
        if(errno == EINTR)
562
0
          continue;
563
3.00k
        if(errno == EAGAIN || errno == EWOULDBLOCK)
564
3.00k
          break;
565
0
        fuzz_doh_conn_close(conn);
566
0
        break;
567
3.00k
      }
568
569
3.00k
      if(conn->fd != CURL_SOCKET_BAD) {
570
3.00k
        size_t body_off = 0;
571
3.00k
        size_t body_len = 0;
572
3.00k
        int complete = fuzz_doh_request_complete(conn, &body_off, &body_len);
573
574
3.00k
        if(complete < 0) {
575
0
          fuzz_doh_conn_close(conn);
576
0
        }
577
3.00k
        else if(complete > 0) {
578
3.00k
          const unsigned char *body = &conn->request[body_off];
579
3.00k
          unsigned char dns[512];
580
581
3.00k
          if(fuzz->doh.response) {
582
34
            body = fuzz->doh.response;
583
34
            body_len = fuzz->doh.response_len;
584
34
          }
585
2.97k
          else {
586
2.97k
            size_t dns_len = fuzz_doh_build_dns_response(body, body_len,
587
2.97k
                                                         dns, sizeof(dns));
588
2.97k
            if(!dns_len) {
589
0
              fuzz_doh_conn_close(conn);
590
0
              continue;
591
0
            }
592
2.97k
            body = dns;
593
2.97k
            body_len = dns_len;
594
2.97k
          }
595
596
3.00k
          if(fuzz_doh_build_http_response(fuzz, conn, body, body_len) != 0)
597
0
            fuzz_doh_conn_close(conn);
598
3.00k
          else
599
3.00k
            conn->state = FUZZ_DOH_CONN_WRITING;
600
3.00k
          progress = 1;
601
3.00k
          FV_PRINTF(fuzz, "FUZZ: DoH request complete, %zu byte body\n",
602
3.00k
                    body_len);
603
3.00k
        }
604
3.00k
      }
605
3.00k
    }
606
607
9.01k
    if(conn->fd != CURL_SOCKET_BAD &&
608
9.01k
       conn->state == FUZZ_DOH_CONN_WRITING &&
609
9.01k
       FD_ISSET(conn->fd, writefds)) {
610
3.00k
      ssize_t n = send(conn->fd, &conn->response[conn->response_off],
611
3.00k
                       conn->response_len - conn->response_off, 0);
612
3.00k
      if(n > 0) {
613
3.00k
        conn->response_off += (size_t)n;
614
3.00k
        progress = 1;
615
3.00k
        if(conn->response_off >= conn->response_len)
616
3.00k
          fuzz_doh_conn_close(conn);
617
3.00k
      }
618
0
      else if(n == 0)
619
0
        fuzz_doh_conn_close(conn);
620
0
      else if(errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
621
0
        fuzz_doh_conn_close(conn);
622
3.00k
    }
623
9.01k
  }
624
625
78.0k
  return progress;
626
78.0k
}
627