| @@ -187,7 +187,8 @@ static const char *type2string(uint16_t qtype) |
| * |
| * Return query (qname + type + class), type and id. |
| */ |
| - | static int store_incoming(int qid, const unsigned char *data, size_t size, |
| + | static int store_incoming(const char *source, int query_id, |
| + | const unsigned char *data, size_t size, |
| unsigned char *qbuf, size_t qbuflen, size_t *qlen, |
| uint16_t *qtype, uint16_t *idp) |
| { |
| @@ -214,7 +215,7 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, |
| int error = errno; |
| logmsg("fopen() failed with error (%d) %s", |
| error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | logmsg("Error opening file '%s'", dumpfile); |
| + | logmsg("[%s] Error opening file '%s'", source, dumpfile); |
| return -1; |
| } |
| |
| @@ -239,12 +240,12 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, |
| data += 2; /* skip the next 16 bits */ |
| size -= 2; |
| #if 0 |
| - | fprintf(server, "QR: %x\n", (*idp & 0x8000) > 15); |
| - | fprintf(server, "OPCODE: %x\n", (*idp & 0x7800) >> 11); |
| - | fprintf(server, "TC: %x\n", (*idp & 0x200) >> 9); |
| - | fprintf(server, "RD: %x\n", (*idp & 0x100) >> 8); |
| - | fprintf(server, "Z: %x\n", (*idp & 0x70) >> 4); |
| - | fprintf(server, "RCODE: %x\n", (*idp & 0x0f)); |
| + | fprintf(server, "[%s] QR: %x\n", source, (*idp & 0x8000) > 15); |
| + | fprintf(server, "[%s] OPCODE: %x\n", source, (*idp & 0x7800) >> 11); |
| + | fprintf(server, "[%s] TC: %x\n", source, (*idp & 0x200) >> 9); |
| + | fprintf(server, "[%s] RD: %x\n", source, (*idp & 0x100) >> 8); |
| + | fprintf(server, "[%s] Z: %x\n", source, (*idp & 0x70) >> 4); |
| + | fprintf(server, "[%s] RCODE: %x\n", source, (*idp & 0x0f)); |
| #endif |
| (void)get16bit(&data, &size); |
| |
| @@ -259,8 +260,8 @@ static int store_incoming(int qid, const unsigned char *data, size_t size, |
| qd = get16bit(&data, &size); |
| fprintf(server, "QNAME %s QTYPE %s\n", name, type2string(qd)); |
| *qtype = qd; |
| - | logmsg("[%d] Question for '%s' type %x / %s", |
| - | qid, name, qd, type2string(qd)); |
| + | logmsg("[%d] [%s] Question for '%s' type %x / %s", |
| + | query_id, source, name, qd, type2string(qd)); |
| |
| (void)get16bit(&data, &size); |
| |
| @@ -310,256 +311,26 @@ static int add_answer(struct blob *body, |
| return blob_addn(body, a, alen); |
| } |
| |
| - | #ifdef _WIN32 |
| - | #define SENDTO3 int |
| - | #else |
| - | #define SENDTO3 size_t |
| - | #endif |
| - | |
| - | #define INSTRUCTIONS "dnsd.cmd" |
| - | |
| - | static curlx_struct_stat finfo_last; |
| - | static unsigned char ipv4_pref[4]; |
| - | static unsigned char ipv6_pref[16]; |
| - | static unsigned char ancount_a; |
| - | static unsigned char ancount_aaaa; |
| - | |
| - | static timediff_t a_delay_ms; |
| - | static timediff_t aaaa_delay_ms; |
| - | static timediff_t https_delay_ms; |
| - | static unsigned char rcode_a; |
| - | static unsigned char rcode_aaaa; |
| - | |
| - | static int query_id = -1; |
| - | |
| - | static struct blob httpsrr; |
| - | |
| - | struct resp { |
| - | struct resp *next; |
| - | int qid; |
| - | struct curltime send_ts; |
| - | struct sockaddr addr; |
| - | curl_socklen_t addrlen; |
| - | struct blob body; |
| - | }; |
| - | |
| - | static struct resp *resp_queue; |
| - | |
| - | static CURLcode send_resp(curl_socket_t sock, struct resp *resp) |
| - | { |
| - | ssize_t rc; |
| - | int sockerr = 0; |
| - | |
| - | do { |
| - | rc = sendto(sock, (const void *)resp->body.data, (SENDTO3)resp->body.dlen, |
| - | 0, &resp->addr, resp->addrlen); |
| - | } while((rc < 0) && ((sockerr = SOCKERRNO) == SOCKEINTR)); |
| - | if(rc < 0) { |
| - | char errbuf[STRERROR_LEN]; |
| - | logmsg("failed sending %zu bytes, error: (%d) %s", resp->body.dlen, |
| - | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| - | return CURLE_SEND_ERROR; |
| - | } |
| - | else if(rc != (ssize_t)resp->body.dlen) { |
| - | logmsg("failed sending %zu bytes, sent: %zd", resp->body.dlen, rc); |
| - | return CURLE_SEND_ERROR; |
| - | } |
| - | logmsg("[%d] sent response", resp->qid); |
| - | return CURLE_OK; |
| - | } |
| - | |
| - | static void queue_resp(struct resp *resp) |
| - | { |
| - | struct resp **panchor = &resp_queue; |
| - | while(*panchor) { |
| - | timediff_t ms = curlx_ptimediff_ms(&(*panchor)->send_ts, &resp->send_ts); |
| - | if(ms > 0) /* resp is to be sent before *panchor */ |
| - | break; |
| - | panchor = &(*panchor)->next; |
| - | } |
| - | resp->next = *panchor; |
| - | *panchor = resp; |
| - | } |
| - | |
| - | static timediff_t send_resp_queue(curl_socket_t sock) |
| - | { |
| - | struct resp **panchor = &resp_queue; |
| - | struct curltime now = curlx_now(); |
| - | timediff_t timeout_ms = 0; |
| - | |
| - | while(*panchor) { |
| - | struct resp *resp = *panchor; |
| - | timediff_t ms = curlx_ptimediff_ms(&resp->send_ts, &now); |
| - | |
| - | if(ms > 0) { |
| - | timeout_ms = ms; |
| - | break; |
| - | } |
| - | *panchor = resp->next; |
| - | send_resp(sock, resp); |
| - | curlx_free(resp); |
| - | } |
| - | return timeout_ms; |
| - | } |
| - | |
| - | static void clear_resp_queue(void) |
| + | static void fdset_add_sock(fd_set *fds, curl_socket_t sock, int *pmaxfd) |
| { |
| - | while(resp_queue) { |
| - | struct resp *resp = resp_queue; |
| - | resp_queue = resp->next; |
| - | curlx_free(resp); |
| - | } |
| + | FD_SET(sock, fds); |
| + | if((int)sock > *pmaxfd) |
| + | *pmaxfd = (int)sock; |
| } |
| |
| - | /* this is an answer to a question */ |
| - | static struct resp * |
| - | create_resp(int qid, const struct sockaddr *addr, curl_socklen_t addrlen, |
| - | const unsigned char *qbuf, size_t qlen, |
| - | uint16_t qtype, uint16_t id) |
| + | static struct curltime now_plus(timediff_t delta_ms) |
| { |
| - | struct resp *resp; |
| - | int a; |
| - | timediff_t delay_ms = 0; |
| - | char addrbuf[128]; /* IP address buffer */ |
| - | uint8_t header[12] = { |
| - | 0x80, 0xea, /* ID, overwrite */ |
| - | 0x81, 0x80, |
| - | /* Flags: 0x8180 Standard query response, No error |
| - | |
| - | 1... .... .... .... = Response: Message is a response |
| - | .000 0... .... .... = Opcode: Standard query (0) |
| - | .... .0.. .... .... = Authoritative: Server is not an authority for |
| - | domain |
| - | .... ..0. .... .... = Truncated: Message is not truncated |
| - | .... ...1 .... .... = Recursion desired: Do query recursively |
| - | .... .... 1... .... = Recursion available: Server can do recursive |
| - | queries |
| - | .... .... .0.. .... = Z: reserved (0) |
| - | .... .... ..0. .... = Answer authenticated: Answer/authority portion |
| - | was not authenticated by the server |
| - | .... .... ...0 .... = Non-authenticated data: Unacceptable |
| - | .... .... .... 0000 = Reply code: No error (0) |
| - | */ |
| - | 0x0, 0x1, /* QDCOUNT a single question */ |
| - | 0x0, 0x0, /* ANCOUNT number of answers */ |
| - | 0x0, 0x0, /* NSCOUNT */ |
| - | 0x0, 0x0 /* ARCOUNT */ |
| - | }; |
| - | uint16_t ancount = 0; |
| - | unsigned char rcode = 0; |
| - | |
| - | switch(qtype) { |
| - | case QTYPE_A: |
| - | ancount = ancount_a; |
| - | delay_ms = a_delay_ms; |
| - | rcode = rcode_a; |
| - | break; |
| - | case QTYPE_AAAA: |
| - | ancount = ancount_aaaa; |
| - | delay_ms = aaaa_delay_ms; |
| - | rcode = rcode_aaaa; |
| - | break; |
| - | case QTYPE_HTTPS: |
| - | if(httpsrr.dlen) |
| - | ancount = 1; |
| - | delay_ms = https_delay_ms; |
| - | break; |
| - | } |
| - | if(rcode) |
| - | ancount = 0; |
| - | |
| - | resp = curlx_calloc(1, sizeof(*resp)); |
| - | if(!resp) |
| - | goto error; |
| - | |
| - | resp->qid = qid; |
| - | /* on some platforms `curl_socklen_t` is an `int`. Casting might |
| - | * wrap this, but then it still has to fit our record size. */ |
| - | if((size_t)addrlen > sizeof(resp->addr)) { |
| - | logmsg("unable to handle addrlen of %zu", (size_t)addrlen); |
| - | goto error; |
| - | } |
| - | memcpy(&resp->addr, CURL_UNCONST(addr), addrlen); |
| - | resp->addrlen = addrlen; |
| - | |
| - | header[0] = (uint8_t)(id >> 8); |
| - | header[1] = (uint8_t)(id & 0xff); |
| - | |
| - | if(rcode) { |
| - | header[3] = (uint8_t)((header[3] & 0xf0) | (rcode & 0x0f)); |
| - | logmsg("[%d] response rcode %u", qid, (unsigned int)rcode); |
| - | } |
| - | |
| - | header[6] = (uint8_t)(ancount >> 8); |
| - | header[7] = (uint8_t)(ancount & 0xff); |
| - | |
| - | if(blob_addn(&resp->body, header, sizeof(header))) |
| - | goto error; |
| - | |
| - | if(blob_addn(&resp->body, qbuf, qlen)) { |
| - | logmsg("unable to handle query of length %zu", qlen); |
| - | goto error; |
| - | } |
| - | |
| - | switch(qtype) { |
| - | case QTYPE_A: |
| - | for(a = 0; !rcode && (a < ancount_a); a++) { |
| - | const unsigned char *store = ipv4_pref; |
| - | const char *ip; |
| - | if(add_answer(&resp->body, store, sizeof(ipv4_pref), QTYPE_A)) |
| - | goto error; |
| - | ip = curlx_inet_ntop(AF_INET, store, addrbuf, sizeof(addrbuf)); |
| - | logmsg("[%d] response A (%x) '%s'", qid, (unsigned int)QTYPE_A, |
| - | ip ? ip : "(null)"); |
| - | } |
| - | if(!ancount_a) |
| - | logmsg("[%d] response A empty", qid); |
| - | break; |
| - | case QTYPE_AAAA: |
| - | for(a = 0; !rcode && (a < ancount_aaaa); a++) { |
| - | const unsigned char *store = ipv6_pref; |
| - | const char *ip; |
| - | if(add_answer(&resp->body, store, sizeof(ipv6_pref), QTYPE_AAAA)) |
| - | goto error; |
| - | ip = curlx_inet_ntop(AF_INET6, store, addrbuf, sizeof(addrbuf)); |
| - | logmsg("[%d] response AAAA (%x) '%s'", qid, (unsigned int)QTYPE_AAAA, |
| - | ip ? ip : "(null)"); |
| - | } |
| - | if(!ancount_aaaa) |
| - | logmsg("[%d] response AAAA empty", qid); |
| - | break; |
| - | case QTYPE_HTTPS: |
| - | if(httpsrr.dlen) { |
| - | if(add_answer(&resp->body, httpsrr.data, httpsrr.dlen, QTYPE_HTTPS)) { |
| - | logmsg("[%d] error adding https %zu response bytes", qid, |
| - | httpsrr.dlen); |
| - | goto error; |
| - | } |
| - | logmsg("[%d] response HTTPS (%x), %zu bytes", qid, |
| - | (unsigned int)QTYPE_HTTPS, httpsrr.dlen); |
| - | } |
| - | else |
| - | logmsg("[%d] response HTTPS, no record", qid); |
| - | break; |
| - | } |
| - | |
| - | resp->send_ts = curlx_now(); |
| - | if(delay_ms > 0) { |
| - | int usec = (int)((delay_ms % 1000) * 1000); |
| - | resp->send_ts.tv_sec += (time_t)(delay_ms / 1000); |
| - | resp->send_ts.tv_usec += usec; |
| - | if(resp->send_ts.tv_usec >= 1000000) { |
| - | resp->send_ts.tv_sec++; |
| - | resp->send_ts.tv_usec -= 1000000; |
| + | struct curltime ts = curlx_now(); |
| + | if(delta_ms > 0) { |
| + | int usec = (int)((delta_ms % 1000) * 1000); |
| + | ts.tv_sec += (time_t)(delta_ms / 1000); |
| + | ts.tv_usec += usec; |
| + | if(ts.tv_usec >= 1000000) { |
| + | ts.tv_sec++; |
| + | ts.tv_usec -= 1000000; |
| } |
| - | logmsg("[%d] delay response by %" FMT_TIMEDIFF_T "ms", qid, delay_ms); |
| } |
| - | return resp; |
| - | |
| - | error: |
| - | logmsg("[%d] failed to create response", qid); |
| - | curlx_free(resp); |
| - | return NULL; |
| + | return ts; |
| } |
| |
| static int read_https_alpn_part(struct blob *b, struct Curl_str *str) |
| @@ -580,6 +351,27 @@ static int read_https_alpn_part(struct blob *b, struct Curl_str *str) |
| return 0; |
| } |
| |
| + | #ifdef _WIN32 |
| + | #define SENDTO3 int |
| + | #else |
| + | #define SENDTO3 size_t |
| + | #endif |
| + | |
| + | #define INSTRUCTIONS "dnsd.cmd" |
| + | |
| + | static curlx_struct_stat finfo_last; |
| + | static unsigned char ipv4_pref[4]; |
| + | static unsigned char ipv6_pref[16]; |
| + | static unsigned char ancount_a; |
| + | static unsigned char ancount_aaaa; |
| + | |
| + | static timediff_t a_delay_ms; |
| + | static timediff_t aaaa_delay_ms; |
| + | static timediff_t https_delay_ms; |
| + | static unsigned char rcode_a; |
| + | static unsigned char rcode_aaaa; |
| + | static struct blob httpsrr; |
| + | |
| static int read_https_alpn(struct blob *b, const char **ps) |
| { |
| struct Curl_str word; |
| @@ -618,7 +410,7 @@ static int read_https(struct blob *b, const char *s) |
| |
| curlx_str_passblanks(&s); |
| if(curlx_str_word(&s, &word, UINT16_MAX)) { |
| - | logmsg("https: unable to read target qname, input=%s", s); |
| + | logmsg("[CONFIG] https: unable to read target qname, input=%s", s); |
| return 1; |
| } |
| if(blob_add_qname(b, &word)) |
| @@ -675,7 +467,7 @@ static void read_instructions(void) |
| blob_reset(&httpsrr); |
| finfo_last = finfo; |
| |
| - | logmsg("read instructions from %s", file); |
| + | logmsg("[CONFIG] reading from %s", file); |
| f = curlx_fopen(file, FOPEN_READTEXT); |
| if(f) { |
| char buf[256]; |
| @@ -757,55 +549,820 @@ static void read_instructions(void) |
| rc = buf[0] ? 0 : 1; |
| } |
| if(rc != 1) { |
| - | logmsg("Bad line in %s: '%s'", file, buf); |
| + | logmsg("[CONFIG] Bad line in %s: '%s'", file, buf); |
| } |
| else if(rtype) { |
| - | logmsg("added %s record via '%s'", rtype, buf); |
| + | logmsg("[CONFIG] added %s record via '%s'", rtype, buf); |
| } |
| } |
| } |
| - | logmsg("set delays: A=%" FMT_TIMEDIFF_T "ms AAAA=%" FMT_TIMEDIFF_T |
| - | "ms HTTPS=%" FMT_TIMEDIFF_T "ms", |
| + | logmsg("[CONFIG] set delays: A=%" FMT_TIMEDIFF_T "ms AAAA=%" |
| + | FMT_TIMEDIFF_T "ms HTTPS=%" FMT_TIMEDIFF_T "ms", |
| a_delay_ms, aaaa_delay_ms, https_delay_ms); |
| curlx_fclose(f); |
| } |
| else |
| - | logmsg("Error opening file '%s'", file); |
| + | logmsg("[CONFIG] Error opening file '%s'", file); |
| } |
| |
| - | static int test_dnsd(int argc, const char **argv) |
| + | static int last_query_id = -1; |
| + | |
| + | static int dnsd_make_answer(struct blob *blob, int query_id, |
| + | const uint8_t *qbuf, size_t qlen, |
| + | uint16_t qtype, uint16_t id, |
| + | timediff_t *pdelay_ms) |
| { |
| - | srvr_sockaddr_union_t me; |
| - | ssize_t n = 0; |
| - | int arg = 1; |
| - | curl_socket_t sock = CURL_SOCKET_BAD; |
| - | int flag; |
| - | int rc; |
| - | int sockerr; |
| - | char errbuf[STRERROR_LEN]; |
| - | int result = 0; |
| - | struct resp *resp; |
| + | int a; |
| + | char addrbuf[128]; /* IP address buffer */ |
| + | uint8_t header[12] = { |
| + | 0x80, 0xea, /* ID, overwrite */ |
| + | 0x81, 0x80, |
| + | /* Flags: 0x8180 Standard query response, No error |
| |
| - | pidname = ".dnsd.pid"; |
| - | serverlogfile = "log/dnsd.log"; |
| - | serverlogslocked = 0; |
| - | server_port = 9123; /* UDP */ |
| + | 1... .... .... .... = Response: Message is a response |
| + | .000 0... .... .... = Opcode: Standard query (0) |
| + | .... .0.. .... .... = Authoritative: Server is not an authority for |
| + | domain |
| + | .... ..0. .... .... = Truncated: Message is not truncated |
| + | .... ...1 .... .... = Recursion desired: Do query recursively |
| + | .... .... 1... .... = Recursion available: Server can do recursive |
| + | queries |
| + | .... .... .0.. .... = Z: reserved (0) |
| + | .... .... ..0. .... = Answer authenticated: Answer/authority portion |
| + | was not authenticated by the server |
| + | .... .... ...0 .... = Non-authenticated data: Unacceptable |
| + | .... .... .... 0000 = Reply code: No error (0) |
| + | */ |
| + | 0x0, 0x1, /* QDCOUNT a single question */ |
| + | 0x0, 0x0, /* ANCOUNT number of answers */ |
| + | 0x0, 0x0, /* NSCOUNT */ |
| + | 0x0, 0x0 /* ARCOUNT */ |
| + | }; |
| + | uint16_t ancount = 0; |
| + | unsigned char rcode = 0; |
| |
| - | while(argc > arg) { |
| - | const char *opt; |
| - | curl_off_t num; |
| - | if(!strcmp("--verbose", argv[arg])) { |
| - | arg++; |
| - | /* nothing yet */ |
| - | } |
| - | else if(!strcmp("--version", argv[arg])) { |
| - | printf("dnsd IPv4%s\n", |
| - | #ifdef USE_IPV6 |
| - | "/IPv6" |
| - | #else |
| - | "" |
| - | #endif |
| - | ); |
| + | /* read once per incoming query, which is probably more than one |
| + | per test case */ |
| + | read_instructions(); |
| + | |
| + | switch(qtype) { |
| + | case QTYPE_A: |
| + | ancount = ancount_a; |
| + | *pdelay_ms = a_delay_ms; |
| + | rcode = rcode_a; |
| + | break; |
| + | case QTYPE_AAAA: |
| + | ancount = ancount_aaaa; |
| + | *pdelay_ms = aaaa_delay_ms; |
| + | rcode = rcode_aaaa; |
| + | break; |
| + | case QTYPE_HTTPS: |
| + | if(httpsrr.dlen) |
| + | ancount = 1; |
| + | *pdelay_ms = https_delay_ms; |
| + | break; |
| + | default: |
| + | *pdelay_ms = 0; |
| + | } |
| + | if(rcode) |
| + | ancount = 0; |
| + | |
| + | header[0] = (uint8_t)(id >> 8); |
| + | header[1] = (uint8_t)(id & 0xff); |
| + | |
| + | if(rcode) { |
| + | header[3] = (uint8_t)((header[3] & 0xf0) | (rcode & 0x0f)); |
| + | logmsg("[%d] response rcode %u", query_id, (unsigned int)rcode); |
| + | } |
| + | |
| + | header[6] = (uint8_t)(ancount >> 8); |
| + | header[7] = (uint8_t)(ancount & 0xff); |
| + | |
| + | if(blob_addn(blob, header, sizeof(header))) |
| + | return 1; |
| + | |
| + | if(blob_addn(blob, qbuf, qlen)) { |
| + | logmsg("unable to handle query of length %zu", qlen); |
| + | return 1; |
| + | } |
| + | |
| + | switch(qtype) { |
| + | case QTYPE_A: |
| + | for(a = 0; !rcode && (a < ancount_a); a++) { |
| + | const unsigned char *store = ipv4_pref; |
| + | const char *ip; |
| + | if(add_answer(blob, store, sizeof(ipv4_pref), QTYPE_A)) |
| + | return 1; |
| + | ip = curlx_inet_ntop(AF_INET, store, addrbuf, sizeof(addrbuf)); |
| + | logmsg("[%d] response A (%x) '%s'", query_id, (unsigned int)QTYPE_A, |
| + | ip ? ip : "(null)"); |
| + | } |
| + | if(!ancount_a) |
| + | logmsg("[%d] response A empty", query_id); |
| + | break; |
| + | case QTYPE_AAAA: |
| + | for(a = 0; !rcode && (a < ancount_aaaa); a++) { |
| + | const unsigned char *store = ipv6_pref; |
| + | const char *ip; |
| + | if(add_answer(blob, store, sizeof(ipv6_pref), QTYPE_AAAA)) |
| + | return 1; |
| + | ip = curlx_inet_ntop(AF_INET6, store, addrbuf, sizeof(addrbuf)); |
| + | logmsg("[%d] response AAAA (%x) '%s'", query_id, |
| + | (unsigned int)QTYPE_AAAA, ip ? ip : "(null)"); |
| + | } |
| + | if(!ancount_aaaa) |
| + | logmsg("[%d] response AAAA empty", query_id); |
| + | break; |
| + | case QTYPE_HTTPS: |
| + | if(httpsrr.dlen) { |
| + | if(add_answer(blob, httpsrr.data, httpsrr.dlen, QTYPE_HTTPS)) { |
| + | logmsg("[%d] error adding https %zu response bytes", query_id, |
| + | httpsrr.dlen); |
| + | return 1; |
| + | } |
| + | logmsg("[%d] response HTTPS (%x), %zu bytes", query_id, |
| + | (unsigned int)QTYPE_HTTPS, httpsrr.dlen); |
| + | } |
| + | else |
| + | logmsg("[%d] response HTTPS, no record", query_id); |
| + | break; |
| + | } |
| + | |
| + | return 0; |
| + | } |
| + | |
| + | struct udp_resp { |
| + | struct udp_resp *next; |
| + | int query_id; |
| + | struct curltime send_ts; |
| + | struct sockaddr addr; |
| + | curl_socklen_t addrlen; |
| + | struct blob body; |
| + | }; |
| + | |
| + | static struct udp_resp *udp_resp_queue; |
| + | |
| + | static CURLcode send_udp_resp(curl_socket_t sock, struct udp_resp *resp) |
| + | { |
| + | ssize_t rc; |
| + | int sockerr = 0; |
| + | |
| + | do { |
| + | rc = sendto(sock, (const void *)resp->body.data, (SENDTO3)resp->body.dlen, |
| + | 0, &resp->addr, resp->addrlen); |
| + | } while((rc < 0) && ((sockerr = SOCKERRNO) == SOCKEINTR)); |
| + | if(rc < 0) { |
| + | char errbuf[STRERROR_LEN]; |
| + | logmsg("[%d-UDP] failed sending %zu bytes, error: (%d) %s", |
| + | resp->query_id, resp->body.dlen, |
| + | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| + | return CURLE_SEND_ERROR; |
| + | } |
| + | else if(rc != (ssize_t)resp->body.dlen) { |
| + | logmsg("[%d-UDP] failed sending %zu bytes, sent: %zd", |
| + | resp->query_id, resp->body.dlen, rc); |
| + | return CURLE_SEND_ERROR; |
| + | } |
| + | logmsg("[%d-UDP] sent response", resp->query_id); |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | static void queue_udp_resp(struct udp_resp *resp) |
| + | { |
| + | struct udp_resp **panchor = &udp_resp_queue; |
| + | while(*panchor) { |
| + | timediff_t ms = curlx_ptimediff_ms(&(*panchor)->send_ts, &resp->send_ts); |
| + | if(ms > 0) /* resp is to be sent before *panchor */ |
| + | break; |
| + | panchor = &(*panchor)->next; |
| + | } |
| + | resp->next = *panchor; |
| + | *panchor = resp; |
| + | } |
| + | |
| + | static timediff_t queue_udp_next_ms(struct curltime *pnow) |
| + | { |
| + | timediff_t next_ms = 1000; |
| + | struct udp_resp *r; |
| + | |
| + | for(r = udp_resp_queue; r; r = r->next) { |
| + | timediff_t ms = curlx_ptimediff_ms(&r->send_ts, pnow); |
| + | if((ms > 0) && (ms < next_ms)) |
| + | next_ms = ms; |
| + | else if(ms <= 0) |
| + | return 0; |
| + | } |
| + | return next_ms; |
| + | } |
| + | |
| + | static void queue_udp_send(curl_socket_t sock, struct curltime *pnow) |
| + | { |
| + | struct udp_resp **panchor = &udp_resp_queue; |
| + | |
| + | while(*panchor) { |
| + | struct udp_resp *resp = *panchor; |
| + | timediff_t ms = curlx_ptimediff_ms(&resp->send_ts, pnow); |
| + | |
| + | /* if not due yet, break as response queue is time sorted */ |
| + | if(ms > 0) |
| + | break; |
| + | *panchor = resp->next; |
| + | send_udp_resp(sock, resp); |
| + | curlx_free(resp); |
| + | } |
| + | } |
| + | |
| + | static void queue_udp_clear(void) |
| + | { |
| + | while(udp_resp_queue) { |
| + | struct udp_resp *resp = udp_resp_queue; |
| + | udp_resp_queue = resp->next; |
| + | curlx_free(resp); |
| + | } |
| + | } |
| + | |
| + | /* this is an answer to a question */ |
| + | static struct udp_resp * |
| + | udp_resp_create(int query_id, |
| + | const struct sockaddr *addr, curl_socklen_t addrlen, |
| + | const unsigned char *qbuf, size_t qlen, |
| + | uint16_t qtype, uint16_t id) |
| + | { |
| + | struct udp_resp *resp; |
| + | timediff_t delay_ms = 0; |
| + | |
| + | resp = curlx_calloc(1, sizeof(*resp)); |
| + | if(!resp) |
| + | goto error; |
| + | |
| + | resp->query_id = query_id; |
| + | /* on some platforms `curl_socklen_t` is an `int`. Casting might |
| + | * wrap this, but then it still has to fit our record size. */ |
| + | if((size_t)addrlen > sizeof(resp->addr)) { |
| + | logmsg("[%d-UDP] unable to handle addrlen of %zu", |
| + | query_id, (size_t)addrlen); |
| + | goto error; |
| + | } |
| + | memcpy(&resp->addr, CURL_UNCONST(addr), addrlen); |
| + | resp->addrlen = addrlen; |
| + | |
| + | if(dnsd_make_answer(&resp->body, query_id, qbuf, qlen, qtype, id, &delay_ms)) |
| + | goto error; |
| + | |
| + | resp->send_ts = now_plus(delay_ms); |
| + | if(delay_ms > 0) |
| + | logmsg("[%d-UDP] delay response by %" FMT_TIMEDIFF_T "ms", |
| + | query_id, delay_ms); |
| + | return resp; |
| + | |
| + | error: |
| + | logmsg("[%d-UDP] failed to create response", query_id); |
| + | curlx_free(resp); |
| + | return NULL; |
| + | } |
| + | |
| + | static int udp_recv_req(curl_socket_t sock) |
| + | { |
| + | srvr_sockaddr_union_t from; |
| + | curl_socklen_t fromlen; |
| + | uint8_t inbuffer[1500]; |
| + | uint8_t qbuf[256]; /* query storage */ |
| + | size_t qlen = 0; /* query size */ |
| + | struct udp_resp *resp; |
| + | uint16_t qtype = 0, id; |
| + | ssize_t n; |
| + | int result = 0; |
| + | |
| + | fromlen = sizeof(from); |
| + | #ifdef USE_IPV6 |
| + | if(socket_domain == AF_INET6) |
| + | fromlen = sizeof(from.sa6); |
| + | else |
| + | #endif |
| + | fromlen = sizeof(from.sa4); |
| + | |
| + | n = (ssize_t)recvfrom(sock, (char *)inbuffer, sizeof(inbuffer), 0, |
| + | &from.sa, &fromlen); |
| + | if(got_exit_signal) |
| + | goto out; |
| + | if(n < 0) { |
| + | logmsg("UDP, recvfrom error"); |
| + | result = 3; |
| + | goto out; |
| + | } |
| + | |
| + | ++last_query_id; |
| + | store_incoming("UDP", last_query_id, inbuffer, n, |
| + | qbuf, sizeof(qbuf), &qlen, &qtype, &id); |
| + | |
| + | set_advisor_read_lock(loglockfile); |
| + | serverlogslocked = 1; |
| + | |
| + | resp = udp_resp_create(last_query_id, &from.sa, fromlen, qbuf, |
| + | qlen, qtype, id); |
| + | if(!resp) |
| + | logmsg("[%d-UDP] error creating response", last_query_id); |
| + | else |
| + | queue_udp_resp(resp); |
| + | |
| + | out: |
| + | return result; |
| + | } |
| + | |
| + | #define MAX_DOH_CONNS 512 |
| + | #define MAX_DOH_INBUF_LEN (8 * 1024) |
| + | #define MAX_DOH_OUTBUF_LEN (8 * 1024) |
| + | |
| + | struct doh_conn { |
| + | curl_socket_t sock; |
| + | int index; |
| + | int query_id; |
| + | char inbuf[MAX_DOH_INBUF_LEN]; |
| + | size_t inblen; |
| + | size_t inbody_offset; |
| + | size_t inbody_len; |
| + | char outbuf[MAX_DOH_OUTBUF_LEN]; |
| + | size_t outblen; |
| + | struct curltime send_ts; |
| + | BIT(want_recv); |
| + | BIT(want_send); |
| + | BIT(close_pending); |
| + | }; |
| + | |
| + | static struct doh_conn doh_conns[MAX_DOH_CONNS]; |
| + | |
| + | static void doh_conns_init(void) |
| + | { |
| + | int i; |
| + | for(i = 0; i < MAX_DOH_CONNS; ++i) { |
| + | doh_conns[i].sock = CURL_SOCKET_BAD; |
| + | doh_conns[i].index = i; |
| + | } |
| + | } |
| + | |
| + | static int doh_conns_add(curl_socket_t sock) |
| + | { |
| + | int i; |
| + | for(i = 0; i < MAX_DOH_CONNS; ++i) { |
| + | if(doh_conns[i].sock == CURL_SOCKET_BAD) { |
| + | doh_conns[i].sock = sock; |
| + | doh_conns[i].want_recv = TRUE; |
| + | logmsg("[x-%d-DOH] accepted new connection, fd=%ld", i, (long)sock); |
| + | return 0; |
| + | } |
| + | } |
| + | logmsg("Too many open DoH connections, closing incoming."); |
| + | sclose(sock); |
| + | return 1; |
| + | } |
| + | |
| + | static void doh_conn_close(size_t i) |
| + | { |
| + | if(i >= MAX_DOH_CONNS) |
| + | return; |
| + | if(doh_conns[i].sock != CURL_SOCKET_BAD) |
| + | sclose(doh_conns[i].sock); |
| + | doh_conns[i].sock = CURL_SOCKET_BAD; |
| + | doh_conns[i].want_recv = FALSE; |
| + | doh_conns[i].want_send = FALSE; |
| + | logmsg("[x-%d-DOH] connection closed", (int)i); |
| + | } |
| + | |
| + | static void doh_conns_close_all(void) |
| + | { |
| + | size_t i; |
| + | for(i = 0; i < MAX_DOH_CONNS; ++i) { |
| + | doh_conn_close(i); |
| + | } |
| + | } |
| + | |
| + | static void doh_conns_fdsets(fd_set *readfds, fd_set *writefds, |
| + | struct curltime *pnow, |
| + | int *pmaxfd, |
| + | timediff_t *ptimeout_ms) |
| + | { |
| + | size_t i; |
| + | for(i = 0; i < MAX_DOH_CONNS; ++i) { |
| + | struct doh_conn *c = &doh_conns[i]; |
| + | if((c->sock != CURL_SOCKET_BAD)) { |
| + | if(!c->want_send && c->outblen) { /* check delayed send */ |
| + | timediff_t ms = curlx_ptimediff_ms(&c->send_ts, pnow); |
| + | if(ms <= 0) |
| + | c->want_send = TRUE; |
| + | else if((ms < *ptimeout_ms) || !*ptimeout_ms) |
| + | *ptimeout_ms = ms; |
| + | } |
| + | if(c->want_send) |
| + | FD_SET(c->sock, writefds); |
| + | if(c->want_recv) |
| + | FD_SET(c->sock, readfds); |
| + | |
| + | if((FD_ISSET(c->sock, readfds) || FD_ISSET(c->sock, writefds)) && |
| + | (int)c->sock > *pmaxfd) |
| + | *pmaxfd = (int)c->sock; |
| + | } |
| + | } |
| + | } |
| + | |
| + | static int doh_conn_send(struct doh_conn *c, struct curltime *pnow) |
| + | { |
| + | char errbuf[STRERROR_LEN]; |
| + | ssize_t rc; |
| + | int sockerr; |
| + | |
| + | if(c->outblen) { |
| + | timediff_t ms = curlx_ptimediff_ms(&c->send_ts, pnow); |
| + | size_t n; |
| + | |
| + | if(ms > 0) { /* not due yet */ |
| + | c->want_send = FALSE; |
| + | goto out; |
| + | } |
| + | |
| + | rc = swrite(c->sock, c->outbuf, c->outblen); |
| + | if(rc < 0) { |
| + | sockerr = SOCKERRNO; |
| + | if((sockerr == SOCKEINPROGRESS) || SOCK_EAGAIN(sockerr)) |
| + | return 0; |
| + | sockerr = SOCKERRNO; |
| + | logmsg("[%d-%d-DOH] swrite(%ld) failed with error (%d) %s", |
| + | c->query_id, c->index, (long)c->sock, |
| + | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| + | return 1; |
| + | } |
| + | n = (size_t)rc; |
| + | if(n >= c->outblen) { /* all sent */ |
| + | c->outblen = 0; |
| + | logmsg("[%d-%d-DOH] last response byte sent", c->query_id, c->index); |
| + | } |
| + | else { |
| + | c->outblen -= n; |
| + | memmove(c->outbuf, c->outbuf + n, c->outblen); |
| + | } |
| + | } |
| + | |
| + | out: |
| + | if(!c->outblen) { |
| + | c->want_send = FALSE; |
| + | if(c->close_pending) |
| + | return 1; |
| + | else { |
| + | logmsg("[x-%d-DOH] switching to recv", c->index); |
| + | c->want_recv = TRUE; |
| + | } |
| + | } |
| + | return 0; |
| + | } |
| + | |
| + | static const char *http_status_descr(int http_status) |
| + | { |
| + | switch(http_status) { |
| + | case 200: |
| + | return "Ok"; |
| + | case 400: |
| + | return "Bad Request"; |
| + | case 404: |
| + | return "Not Found"; |
| + | case 405: |
| + | return "Method Not Allowed"; |
| + | default: |
| + | if(http_status < 100) |
| + | return "This is wrong"; |
| + | if(http_status < 200) |
| + | return "Just Kidding"; |
| + | if(http_status < 300) |
| + | return "Lgtm"; |
| + | if(http_status < 400) |
| + | return "Follow Me For More Requests"; |
| + | if(http_status < 500) |
| + | return "You were wrong"; |
| + | return "We did something wrong"; |
| + | } |
| + | } |
| + | |
| + | static int doh_conn_send_err(struct doh_conn *c, int http_status) |
| + | { |
| + | c->inblen = 0; |
| + | c->close_pending = TRUE; |
| + | c->want_recv = FALSE; |
| + | if(c->outblen) { |
| + | logmsg("[%d-%d-DOH] error sending response with %zu bytes still outgoing", |
| + | c->query_id, c->index, c->outblen); |
| + | return 1; |
| + | } |
| + | |
| + | memset(c->outbuf, 0, sizeof(c->outbuf)); |
| + | snprintf(c->outbuf, sizeof(c->outbuf) - 1, |
| + | "HTTP/1.1 %d %s\r\n" |
| + | "Content-Length: 0\r\n" |
| + | "Connection: close\r\n" |
| + | "\r\n", |
| + | http_status, http_status_descr(http_status)); |
| + | c->outblen = strlen(c->outbuf); |
| + | c->want_send = TRUE; |
| + | logmsg("[%d-%d-DOH] sending HTTP response %d", |
| + | c->query_id, c->index, http_status); |
| + | return 0; |
| + | } |
| + | |
| + | static int doh_conn_send_answer(struct doh_conn *c, struct blob *body, |
| + | timediff_t delay_ms) |
| + | { |
| + | if(c->outblen) { /* Should not happen */ |
| + | logmsg("[%d-%d-DOH] trying to send an answer with outbuf still having " |
| + | "%zu bytes", c->query_id, c->index, c->outblen); |
| + | return 1; |
| + | } |
| + | memset(c->outbuf, 0, sizeof(c->outbuf)); |
| + | snprintf(c->outbuf, sizeof(c->outbuf) - 1, |
| + | "HTTP/1.1 200 %s\r\n" |
| + | "Server: curl/test-dnsd\r\n" |
| + | "Date: Thu, 06 Aug 2026 08:42:00 GMT\r\n" |
| + | "Content-Type: application/dns-message\r\n" |
| + | "Content-Length: %ld\r\n" |
| + | "\r\n", |
| + | http_status_descr(200), (long)body->dlen); |
| + | c->outblen = strlen(c->outbuf); |
| + | if((c->outblen + body->dlen) > sizeof(c->outbuf)) { |
| + | logmsg("[%d-%d-DOH] response size of %zu too large for outbuf", |
| + | c->query_id, c->index, c->outblen + body->dlen); |
| + | c->outblen = 0; |
| + | return 1; |
| + | } |
| + | memcpy(c->outbuf + c->outblen, body->data, body->dlen); |
| + | c->outblen += body->dlen; |
| + | c->send_ts = now_plus(delay_ms); |
| + | if(delay_ms > 0) |
| + | logmsg("[%d-%d-DOH] delay response by %" FMT_TIMEDIFF_T "ms", |
| + | c->query_id, c->index, delay_ms); |
| + | else |
| + | c->want_send = TRUE; |
| + | c->want_recv = FALSE; |
| + | logmsg("[%d-%d-DOH] sending HTTP response 200", |
| + | c->query_id, c->index); |
| + | return 0; |
| + | } |
| + | |
| + | static int doh_req_parse_headers(const char **pstr, |
| + | size_t *pcontent_length, |
| + | bool *pcomplete) |
| + | { |
| + | static const struct Curl_str HD_content_length = { |
| + | STRCONST("Content-Length:") |
| + | }; |
| + | static const struct Curl_str HD_content_type = { |
| + | STRCONST("Content-Type:") |
| + | }; |
| + | static const struct Curl_str HD_wanted_type = { |
| + | STRCONST("application/dns-message") |
| + | }; |
| + | const char *p = *pstr; |
| + | bool ct_ok = FALSE; |
| + | bool cl_ok = FALSE; |
| + | bool eoh = FALSE; |
| + | |
| + | *pcomplete = FALSE; |
| + | *pcontent_length = 0; |
| + | while(p[0]) { |
| + | const char *nl, *start = p; |
| + | struct Curl_str hd_name, hd_val; |
| + | |
| + | if((p[0] == '\r') && (p[1] == '\n')) { |
| + | p += 2; |
| + | eoh = TRUE; |
| + | break; |
| + | } |
| + | nl = strchr(p, '\n'); |
| + | if(!nl) /* incomplete */ |
| + | break; |
| + | if(curlx_str_word(&p, &hd_name, 1024) || |
| + | curlx_str_singlespace(&p) || |
| + | curlx_str_untilnl(&p, &hd_val, 1024) || |
| + | curlx_str_newline(&p) || |
| + | curlx_str_newline(&p)) { |
| + | logmsg("unrecognized request header '%.*s'", |
| + | (int)(nl - start), start); |
| + | return 1; |
| + | } |
| + | if(curlx_str_case_equal(&HD_content_type, &hd_name)) { |
| + | if(!curlx_str_case_equal(&HD_wanted_type, &hd_val)) { |
| + | logmsg("wrong content-type: '%.*s'", (int)hd_val.len, hd_val.str); |
| + | return 1; |
| + | } |
| + | ct_ok = TRUE; |
| + | } |
| + | else if(curlx_str_case_equal(&HD_content_length, &hd_name)) { |
| + | const char *s = hd_val.str; |
| + | curl_off_t offt; |
| + | if(curlx_str_number(&s, &offt, 4096)) { |
| + | logmsg("wrong content-length: '%.*s'", (int)hd_val.len, hd_val.str); |
| + | return 1; |
| + | } |
| + | *pcontent_length = (size_t)offt; |
| + | cl_ok = TRUE; |
| + | } |
| + | else { |
| + | /* ignore this header */ |
| + | } |
| + | } |
| + | |
| + | *pstr = p; |
| + | if(!eoh) |
| + | return 0; /* need more */ |
| + | if(!ct_ok) { |
| + | logmsg("request missing Content-Type"); |
| + | return 1; |
| + | } |
| + | if(!cl_ok) { |
| + | logmsg("request missing Content-Length"); |
| + | return 1; |
| + | } |
| + | *pcomplete = TRUE; |
| + | return 0; |
| + | } |
| + | |
| + | static int doh_conn_do_req(struct doh_conn *c, bool eos) |
| + | { |
| + | static const struct Curl_str DOH_PROTO = { STRCONST("HTTP/1.1") }; |
| + | static const struct Curl_str DOH_METHOD = { STRCONST("POST") }; |
| + | static const struct Curl_str DOH_PATH = { STRCONST("/") }; |
| + | const char *first_nl; |
| + | bool complete = FALSE; |
| + | |
| + | if(!c->inblen && eos) |
| + | return 1; |
| + | |
| + | if(!c->inbody_offset) { |
| + | first_nl = strchr(c->inbuf, '\n'); |
| + | if(first_nl) { |
| + | /* This is a poor man's HTTP/1.1 parser and we should rather have |
| + | * one in curlx that we can share. */ |
| + | const char *p = c->inbuf; |
| + | struct Curl_str method, path, proto; |
| + | |
| + | if(curlx_str_word(&p, &method, 1024) || curlx_str_singlespace(&p)) { |
| + | logmsg("[x-%d-DOH] unrecognized first request line method '%.*s'", |
| + | c->index, (int)(first_nl - c->inbuf), c->inbuf); |
| + | return 1; |
| + | } |
| + | if(curlx_str_word(&p, &path, 1024) || curlx_str_singlespace(&p)) { |
| + | logmsg("[x-%d-DOH] unrecognized first request line path '%.*s'", |
| + | c->index, (int)(first_nl - c->inbuf), c->inbuf); |
| + | return 1; |
| + | } |
| + | if(curlx_str_untilnl(&p, &proto, 1024) || |
| + | curlx_str_newline(&p) || |
| + | curlx_str_newline(&p)) { |
| + | logmsg("[x-%d-DOH] unrecognized first request line proto '%.*s'", |
| + | c->index, (int)(first_nl - c->inbuf), c->inbuf); |
| + | return 1; |
| + | } |
| + | if(!curlx_str_case_equal(&DOH_PROTO, &proto)) { |
| + | logmsg("[x-%d-DOH] unrecognized request protocol '%.*s'", |
| + | c->index, (int)proto.len, proto.str); |
| + | return 1; |
| + | } |
| + | if(!curlx_str_case_equal(&DOH_METHOD, &method)) { |
| + | logmsg("[x-%d-DOH] unsupported request method '%.*s'", |
| + | c->index, (int)method.len, method.str); |
| + | return doh_conn_send_err(c, 405); |
| + | } |
| + | if(!curlx_str_case_equal(&DOH_PATH, &path)) { |
| + | logmsg("[x-%d-DOH] request path not fond '%.*s'", |
| + | c->index, (int)path.len, path.str); |
| + | return doh_conn_send_err(c, 404); |
| + | } |
| + | if(doh_req_parse_headers(&p, &c->inbody_len, &complete)) { |
| + | return doh_conn_send_err(c, 400); |
| + | } |
| + | /* Looks ok, remember the start of the body bytes */ |
| + | c->inbody_offset = (p - c->inbuf); |
| + | } |
| + | if(!complete) |
| + | return eos ? 1 : 0; /* want more, error if client close */ |
| + | } |
| + | |
| + | if(c->inblen >= (c->inbody_offset + c->inbody_len)) { |
| + | /* We have all bytes for processing the request */ |
| + | uint8_t qbuf[256]; /* query storage */ |
| + | size_t qlen = 0; /* query size */ |
| + | size_t rlen = c->inbody_offset + c->inbody_len; /* request size */ |
| + | uint16_t qtype = 0, id; |
| + | struct blob blob; |
| + | timediff_t delay_ms; |
| + | |
| + | c->query_id = ++last_query_id; |
| + | if(store_incoming("DoH", c->query_id, (const uint8_t *)c->inbuf + |
| + | c->inbody_offset, c->inbody_len, |
| + | qbuf, sizeof(qbuf), &qlen, &qtype, &id)) { |
| + | logmsg("[%d-%d-DOH] error storing incoming request", |
| + | c->query_id, c->index); |
| + | return doh_conn_send_err(c, 400); |
| + | } |
| + | |
| + | /* remove handled request from inbuf */ |
| + | if(rlen >= c->inblen) |
| + | c->inblen = 0; |
| + | else { |
| + | memmove(c->inbuf, c->inbuf + rlen, c->inblen - rlen); |
| + | c->inblen -= rlen; |
| + | } |
| + | c->inbody_len = c->inbody_offset = 0; |
| + | |
| + | memset(&blob, 0, sizeof(blob)); |
| + | if(dnsd_make_answer(&blob, c->query_id, qbuf, qlen, qtype, id, |
| + | &delay_ms)) { |
| + | return doh_conn_send_err(c, 500); |
| + | } |
| + | return doh_conn_send_answer(c, &blob, delay_ms); |
| + | } |
| + | return 0; |
| + | } |
| + | |
| + | static int doh_conn_recv(struct doh_conn *c) |
| + | { |
| + | char errbuf[STRERROR_LEN]; |
| + | ssize_t n; |
| + | int sockerr; |
| + | |
| + | n = sread(c->sock, c->inbuf + c->inblen, sizeof(c->inbuf) - 1 - c->inblen); |
| + | if(n < 0) { |
| + | sockerr = SOCKERRNO; |
| + | if((sockerr == SOCKEINPROGRESS) || SOCK_EAGAIN(sockerr)) |
| + | return 0; |
| + | sockerr = SOCKERRNO; |
| + | logmsg("[x-%d-DOH] sread() failed with error (%d) %s", c->index, |
| + | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| + | return 1; |
| + | } |
| + | else if(n == 0) { |
| + | logmsg("[x-%d-DOH] sread() == 0, client closed connection", c->index); |
| + | return doh_conn_do_req(c, TRUE); |
| + | } |
| + | else { |
| + | c->inblen += (size_t)n; |
| + | c->inbuf[c->inblen] = 0; |
| + | logmsg("[x-%d-DOH] sread() == %zu, processing", c->index, (size_t)n); |
| + | return doh_conn_do_req(c, FALSE); |
| + | } |
| + | } |
| + | |
| + | static int doh_conns_serve(fd_set *readfds, fd_set *writefds) |
| + | { |
| + | struct curltime now; |
| + | size_t i; |
| + | |
| + | for(i = 0; i < MAX_DOH_CONNS; ++i) { |
| + | struct doh_conn *c = &doh_conns[i]; |
| + | if(c->sock != CURL_SOCKET_BAD) { |
| + | if(c->want_send && FD_ISSET(c->sock, writefds)) { |
| + | now = curlx_now(); |
| + | if(doh_conn_send(c, &now)) { |
| + | doh_conn_close(i); |
| + | continue; |
| + | } |
| + | } |
| + | if(c->want_recv && FD_ISSET(c->sock, readfds)) { |
| + | if(doh_conn_recv(c)) { |
| + | doh_conn_close(i); |
| + | continue; |
| + | } |
| + | } |
| + | } |
| + | } |
| + | return 0; |
| + | } |
| + | |
| + | static int test_dnsd(int argc, const char **argv) |
| + | { |
| + | int arg = 1; |
| + | curl_socket_t sock_udp = CURL_SOCKET_BAD; |
| + | curl_socket_t sock_tcp_listen = CURL_SOCKET_BAD; |
| + | char errbuf[STRERROR_LEN]; |
| + | int rc, sockerr; |
| + | int result = 0; |
| + | |
| + | pidname = ".dnsd.pid"; |
| + | serverlogfile = "log/dnsd.log"; |
| + | serverlogslocked = 0; |
| + | server_port = 9123; /* UDP */ |
| + | socket_domain = AF_INET; |
| + | |
| + | while(argc > arg) { |
| + | const char *opt; |
| + | curl_off_t num; |
| + | if(!strcmp("--verbose", argv[arg])) { |
| + | arg++; |
| + | /* nothing yet */ |
| + | } |
| + | else if(!strcmp("--version", argv[arg])) { |
| + | printf("dnsd IPv4%s\n", |
| + | #ifdef USE_IPV6 |
| + | "/IPv6" |
| + | #else |
| + | "" |
| + | #endif |
| + | ); |
| return 0; |
| } |
| else if(!strcmp("--pidfile", argv[arg])) { |
| @@ -870,97 +1427,14 @@ static int test_dnsd(int argc, const char **argv) |
| |
| install_signal_handlers(FALSE); |
| |
| - | #ifdef USE_IPV6 |
| - | if(socket_domain == AF_INET6) |
| - | sock = socket(AF_INET6, SOCK_DGRAM, 0); |
| - | else |
| - | #endif |
| - | sock = socket(AF_INET, SOCK_DGRAM, 0); |
| - | |
| - | if(sock == CURL_SOCKET_BAD) { |
| - | sockerr = SOCKERRNO; |
| - | logmsg("Error creating socket (%d) %s", |
| - | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| - | result = 1; |
| + | result = open_stream_sock(&sock_tcp_listen, &server_port); |
| + | if(result) |
| goto dnsd_cleanup; |
| - | } |
| + | doh_conns_init(); |
| |
| - | flag = 1; |
| - | if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { |
| - | sockerr = SOCKERRNO; |
| - | logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", |
| - | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| - | result = 1; |
| + | result = open_udp_sock(&sock_udp, &server_port); |
| + | if(result) |
| goto dnsd_cleanup; |
| - | } |
| - | |
| - | #ifdef USE_IPV6 |
| - | if(socket_domain == AF_INET6) { |
| - | memset(&me.sa6, 0, sizeof(me.sa6)); |
| - | me.sa6.sin6_family = AF_INET6; |
| - | me.sa6.sin6_addr = in6addr_any; |
| - | me.sa6.sin6_port = htons(server_port); |
| - | rc = bind(sock, &me.sa, sizeof(me.sa6)); |
| - | } |
| - | else |
| - | #endif |
| - | { |
| - | memset(&me.sa4, 0, sizeof(me.sa4)); |
| - | me.sa4.sin_family = AF_INET; |
| - | me.sa4.sin_addr.s_addr = INADDR_ANY; |
| - | me.sa4.sin_port = htons(server_port); |
| - | rc = bind(sock, &me.sa, sizeof(me.sa4)); |
| - | } |
| - | if(rc) { |
| - | sockerr = SOCKERRNO; |
| - | logmsg("Error binding socket on port %hu (%d) %s", server_port, |
| - | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| - | result = 1; |
| - | goto dnsd_cleanup; |
| - | } |
| - | |
| - | if(!server_port) { |
| - | /* The system was supposed to choose a port number, figure out which |
| - | port we actually got and update the listener port value with it. */ |
| - | curl_socklen_t la_size; |
| - | srvr_sockaddr_union_t localaddr; |
| - | memset(&localaddr, 0, sizeof(localaddr)); |
| - | #ifdef USE_IPV6 |
| - | if(socket_domain == AF_INET6) |
| - | la_size = sizeof(localaddr.sa6); |
| - | else |
| - | #endif |
| - | la_size = sizeof(localaddr.sa4); |
| - | |
| - | if(getsockname(sock, &localaddr.sa, &la_size) < 0) { |
| - | sockerr = SOCKERRNO; |
| - | logmsg("getsockname() failed with error (%d) %s", |
| - | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| - | sclose(sock); |
| - | goto dnsd_cleanup; |
| - | } |
| - | switch(localaddr.sa.sa_family) { |
| - | case AF_INET: |
| - | server_port = ntohs(localaddr.sa4.sin_port); |
| - | break; |
| - | #ifdef USE_IPV6 |
| - | case AF_INET6: |
| - | server_port = ntohs(localaddr.sa6.sin6_port); |
| - | break; |
| - | #endif |
| - | default: |
| - | break; |
| - | } |
| - | if(!server_port) { |
| - | /* Real failure, listener port shall not be zero beyond this point. */ |
| - | logmsg("Apparently getsockname() succeeded, with listener port zero."); |
| - | logmsg("A valid reason for this failure is a binary built without"); |
| - | logmsg("proper network library linkage. This might not be the only"); |
| - | logmsg("reason, but double check it before anything else."); |
| - | result = 2; |
| - | goto dnsd_cleanup; |
| - | } |
| - | } |
| |
| dnsd_wrotepidfile = write_pidfile(pidname); |
| if(!dnsd_wrotepidfile) { |
| @@ -976,75 +1450,76 @@ static int test_dnsd(int argc, const char **argv) |
| } |
| } |
| |
| - | logmsg("Running %s version on port UDP/%d", socket_type, (int)server_port); |
| - | curlx_nonblock(sock, TRUE); |
| + | /* start accepting connections */ |
| + | if(listen(sock_tcp_listen, 50)) { |
| + | sockerr = SOCKERRNO; |
| + | logmsg("listen() failed with error (%d) %s", |
| + | sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf))); |
| + | result = 1; |
| + | goto dnsd_cleanup; |
| + | } |
| + | |
| + | logmsg("Running %s on port UDP+TCP/%u", socket_type, server_port); |
| + | curlx_nonblock(sock_udp, TRUE); |
| + | curlx_nonblock(sock_tcp_listen, TRUE); |
| |
| for(;;) { |
| - | uint16_t id = 0; |
| - | uint8_t inbuffer[1500]; |
| - | srvr_sockaddr_union_t from; |
| - | curl_socklen_t fromlen; |
| - | uint8_t qbuf[256]; /* query storage */ |
| - | size_t qlen = 0; /* query size */ |
| - | uint16_t qtype = 0; |
| timediff_t timeout_ms = 0; |
| - | fromlen = sizeof(from); |
| - | #ifdef USE_IPV6 |
| - | if(socket_domain == AF_INET6) |
| - | fromlen = sizeof(from.sa6); |
| - | else |
| - | #endif |
| - | fromlen = sizeof(from.sa4); |
| + | fd_set readfds, writefds; |
| + | struct timeval tv; |
| + | int maxfd = 0; |
| + | struct curltime now = curlx_now(); |
| |
| - | timeout_ms = send_resp_queue(sock); |
| + | FD_ZERO(&readfds); |
| + | fdset_add_sock(&readfds, sock_udp, &maxfd); |
| + | fdset_add_sock(&readfds, sock_tcp_listen, &maxfd); |
| |
| - | { |
| - | fd_set readfds; |
| - | struct timeval tv; |
| - | int maxfd = (int)sock; |
| + | FD_ZERO(&writefds); |
| + | timeout_ms = queue_udp_next_ms(&now); |
| + | if(!timeout_ms) |
| + | fdset_add_sock(&writefds, sock_udp, &maxfd); |
| |
| - | FD_ZERO(&readfds); |
| - | FD_SET(sock, &readfds); |
| - | if(!timeout_ms || (timeout_ms > 100)) |
| - | timeout_ms = 100; |
| + | doh_conns_fdsets(&readfds, &writefds, &now, &maxfd, &timeout_ms); |
| |
| - | rc = select(maxfd + 1, &readfds, NULL, NULL, |
| - | curlx_mstotv(&tv, timeout_ms)); |
| + | if(!timeout_ms || (timeout_ms > 100)) |
| + | timeout_ms = 100; |
| |
| - | if(rc == -1) { |
| - | logmsg("error %d returned by select()", SOCKERRNO); |
| - | } |
| - | else if(!rc) { /* timeout */ |
| - | continue; |
| - | } |
| + | rc = select(maxfd + 1, &readfds, &writefds, NULL, |
| + | curlx_mstotv(&tv, timeout_ms)); |
| + | |
| + | if(rc == -1) { |
| + | logmsg("error %d returned by select()", SOCKERRNO); |
| } |
| - | n = (ssize_t)recvfrom(sock, (char *)inbuffer, sizeof(inbuffer), 0, |
| - | &from.sa, &fromlen); |
| - | if(got_exit_signal) |
| - | break; |
| - | if(n < 0) { |
| - | logmsg("recvfrom"); |
| - | result = 3; |
| - | break; |
| + | else if(!rc) { /* timeout */ |
| + | continue; |
| } |
| |
| - | /* read once per incoming query, which is probably more than one |
| - | per test case */ |
| - | read_instructions(); |
| - | |
| - | ++query_id; |
| - | store_incoming(query_id, inbuffer, n, |
| - | qbuf, sizeof(qbuf), &qlen, &qtype, &id); |
| + | if(FD_ISSET(sock_udp, &writefds)) { |
| + | now = curlx_now(); |
| + | queue_udp_send(sock_udp, &now); |
| + | } |
| + | if(FD_ISSET(sock_udp, &readfds)) { |
| + | result = udp_recv_req(sock_udp); |
| + | if(result) |
| + | break; |
| + | } |
| |
| - | set_advisor_read_lock(loglockfile); |
| - | serverlogslocked = 1; |
| + | result = doh_conns_serve(&readfds, &writefds); |
| + | if(result) |
| + | break; |
| |
| - | resp = create_resp(query_id, &from.sa, fromlen, qbuf, |
| - | qlen, qtype, id); |
| - | if(!resp) |
| - | logmsg("error creating response"); |
| - | else |
| - | queue_resp(resp); |
| + | if(FD_ISSET(sock_tcp_listen, &readfds)) { |
| + | /* Service all queued connections */ |
| + | curl_socket_t sock_conn; |
| + | while(TRUE) { |
| + | sock_conn = accept_connection(sock_tcp_listen); |
| + | if(!sock_conn) /* no more connections to accept */ |
| + | break; |
| + | if(sock_conn == CURL_SOCKET_BAD) |
| + | goto dnsd_cleanup; |
| + | doh_conns_add(sock_conn); |
| + | } |
| + | } |
| |
| if(got_exit_signal) |
| break; |
| @@ -1056,14 +1531,12 @@ static int test_dnsd(int argc, const char **argv) |
| } |
| |
| dnsd_cleanup: |
| + | if(sock_udp != CURL_SOCKET_BAD) |
| + | sclose(sock_udp); |
| + | if(sock_tcp_listen != CURL_SOCKET_BAD) |
| + | sclose(sock_tcp_listen); |
| |
| - | #if 0 |
| - | if((peer != sock) && (peer != CURL_SOCKET_BAD)) |
| - | sclose(peer); |
| - | #endif |
| - | |
| - | if(sock != CURL_SOCKET_BAD) |
| - | sclose(sock); |
| + | doh_conns_close_all(); |
| |
| if(got_exit_signal) |
| logmsg("signalled to die"); |
| @@ -1078,7 +1551,7 @@ dnsd_cleanup: |
| clear_advisor_read_lock(loglockfile); |
| } |
| |
| - | clear_resp_queue(); |
| + | queue_udp_clear(); |
| |
| restore_signal_handlers(FALSE); |
| |