mirror of
https://github.com/curl/curl.git
synced 2025-01-30 14:22:33 +08:00
parent
3f3b26d6fe
commit
abff183387
@ -199,6 +199,8 @@ Bind connection locally to port range. See \fICURLOPT_LOCALPORTRANGE(3)\fP
|
||||
Timeout for DNS cache. See \fICURLOPT_DNS_CACHE_TIMEOUT(3)\fP
|
||||
.IP CURLOPT_DNS_USE_GLOBAL_CACHE
|
||||
OBSOLETE Enable global DNS cache. See \fICURLOPT_DNS_USE_GLOBAL_CACHE(3)\fP
|
||||
.IP CURLOPT_DOH_URL
|
||||
Use this DOH server for name resolves. See \fICURLOPT_DOH_URL(3)\fP
|
||||
.IP CURLOPT_BUFFERSIZE
|
||||
Ask for alternate buffer size. See \fICURLOPT_BUFFERSIZE(3)\fP
|
||||
.IP CURLOPT_PORT
|
||||
|
71
docs/libcurl/opts/CURLOPT_DOH_URL.3
Normal file
71
docs/libcurl/opts/CURLOPT_DOH_URL.3
Normal file
@ -0,0 +1,71 @@
|
||||
.\" **************************************************************************
|
||||
.\" * _ _ ____ _
|
||||
.\" * Project ___| | | | _ \| |
|
||||
.\" * / __| | | | |_) | |
|
||||
.\" * | (__| |_| | _ <| |___
|
||||
.\" * \___|\___/|_| \_\_____|
|
||||
.\" *
|
||||
.\" * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
.\" *
|
||||
.\" * This software is licensed as described in the file COPYING, which
|
||||
.\" * you should have received as part of this distribution. The terms
|
||||
.\" * are also available at https://curl.haxx.se/docs/copyright.html.
|
||||
.\" *
|
||||
.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
.\" * copies of the Software, and permit persons to whom the Software is
|
||||
.\" * furnished to do so, under the terms of the COPYING file.
|
||||
.\" *
|
||||
.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
.\" * KIND, either express or implied.
|
||||
.\" *
|
||||
.\" **************************************************************************
|
||||
.\"
|
||||
.TH CURLOPT_DOH_URL 3 "18 Jun 2018" "libcurl 7.62.0" "curl_easy_setopt options"
|
||||
.SH NAME
|
||||
CURLOPT_DOH_URL \- provide the DNS-over-HTTPS URL
|
||||
.SH SYNOPSIS
|
||||
#include <curl/curl.h>
|
||||
|
||||
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DOH_URL, char *URL);
|
||||
.SH DESCRIPTION
|
||||
Pass in a pointer to a \fIURL\fP for the DOH server to use for name
|
||||
resolving. The parameter should be a char * to a zero terminated string which
|
||||
must be URL-encoded in the following format: "https://host:port/path". It MUST
|
||||
specify a HTTPS URL.
|
||||
|
||||
libcurl doesn't validate the syntax or use this variable until the transfer is
|
||||
issued. Even if you set a crazy value here, \fIcurl_easy_setopt(3)\fP will
|
||||
still return \fICURLE_OK\fP.
|
||||
|
||||
curl sends POST requests to the given DNS-over-HTTPS URL.
|
||||
|
||||
To find the DOH server itself, which might be specified using a name, libcurl
|
||||
will use the default name lookup function. You can bootstrap that by providing
|
||||
the address for the DOH server with \fICURLOPT_RESOLVE(3)\fP.
|
||||
|
||||
Disable DOH use again by setting this option to NULL.
|
||||
.SH DEFAULT
|
||||
NULL - there is no default DOH URL. If this option isn't set, libcurl will use
|
||||
the default name resolver.
|
||||
.SH PROTOCOLS
|
||||
All
|
||||
.SH EXAMPLE
|
||||
.nf
|
||||
CURL *curl = curl_easy_init();
|
||||
if(curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
|
||||
curl_easy_setopt(curl, CURLOPT_DOH_URL, "https://dns.example.com");
|
||||
curl_easy_perform(curl);
|
||||
}
|
||||
.fi
|
||||
.SH AVAILABILITY
|
||||
Added in 7.62.0
|
||||
.SH RETURN VALUE
|
||||
Returns CURLE_OK on success or CURLE_OUT_OF_MEMORY if there was insufficient
|
||||
heap space.
|
||||
|
||||
Note that \fIcurl_easy_setopt(3)\fP won't actually parse the given string so
|
||||
given a bad DOH URL, curl will not detect a problem until it tries to resolve
|
||||
a name with it.
|
||||
.SH "SEE ALSO"
|
||||
.BR CURLOPT_VERBOSE "(3), " CURLOPT_RESOLVE "(3), "
|
@ -122,6 +122,7 @@ man_MANS = \
|
||||
CURLOPT_DNS_SERVERS.3 \
|
||||
CURLOPT_DNS_SHUFFLE_ADDRESSES.3 \
|
||||
CURLOPT_DNS_USE_GLOBAL_CACHE.3 \
|
||||
CURLOPT_DOH_URL.3 \
|
||||
CURLOPT_EGDSOCKET.3 \
|
||||
CURLOPT_ERRORBUFFER.3 \
|
||||
CURLOPT_EXPECT_100_TIMEOUT_MS.3 \
|
||||
|
@ -384,6 +384,7 @@ CURLOPT_DNS_LOCAL_IP6 7.33.0
|
||||
CURLOPT_DNS_SERVERS 7.24.0
|
||||
CURLOPT_DNS_SHUFFLE_ADDRESSES 7.60.0
|
||||
CURLOPT_DNS_USE_GLOBAL_CACHE 7.9.3 7.11.1
|
||||
CURLOPT_DOH_URL 7.62.0
|
||||
CURLOPT_EGDSOCKET 7.7
|
||||
CURLOPT_ENCODING 7.10
|
||||
CURLOPT_ERRORBUFFER 7.1
|
||||
|
@ -1859,6 +1859,9 @@ typedef enum {
|
||||
/* Disallow specifying username/login in URL. */
|
||||
CINIT(DISALLOW_USERNAME_IN_URL, LONG, 278),
|
||||
|
||||
/* DNS-over-HTTPS URL */
|
||||
CINIT(DOH_URL, STRINGPOINT, 279),
|
||||
|
||||
CURLOPT_LASTENTRY /* the last unused */
|
||||
} CURLoption;
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
* Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
@ -269,6 +269,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
|
||||
(option) == CURLOPT_DNS_LOCAL_IP4 || \
|
||||
(option) == CURLOPT_DNS_LOCAL_IP6 || \
|
||||
(option) == CURLOPT_DNS_SERVERS || \
|
||||
(option) == CURLOPT_DOH_URL || \
|
||||
(option) == CURLOPT_EGDSOCKET || \
|
||||
(option) == CURLOPT_FTPPORT || \
|
||||
(option) == CURLOPT_FTP_ACCOUNT || \
|
||||
@ -500,7 +501,8 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
|
||||
/* evaluates to true if expr can be passed as POST data (void* or char*) */
|
||||
#define _curl_is_postfields(expr) \
|
||||
(_curl_is_ptr((expr), void) || \
|
||||
_curl_is_arr((expr), char))
|
||||
_curl_is_arr((expr), char) || \
|
||||
_curl_is_arr((expr), unsigned char))
|
||||
|
||||
/* FIXME: the whole callback checking is messy...
|
||||
* The idea is to tolerate char vs. void and const vs. not const
|
||||
|
@ -54,7 +54,8 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
|
||||
http_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_sasl.c rand.c \
|
||||
curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \
|
||||
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
|
||||
mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c
|
||||
mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \
|
||||
doh.c
|
||||
|
||||
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
|
||||
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
|
||||
@ -74,7 +75,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
|
||||
curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \
|
||||
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
|
||||
curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
|
||||
curl_path.h curl_ctype.h curl_range.h psl.h
|
||||
curl_path.h curl_ctype.h curl_range.h psl.h doh.h
|
||||
|
||||
LIB_RCFILES = libcurl.rc
|
||||
|
||||
|
915
lib/doh.c
Normal file
915
lib/doh.c
Normal file
@ -0,0 +1,915 @@
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at https://curl.haxx.se/docs/copyright.html.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of the Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the COPYING file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#include "curl_setup.h"
|
||||
#include "urldata.h"
|
||||
#include "curl_addrinfo.h"
|
||||
#include "sendf.h"
|
||||
#include "multiif.h"
|
||||
#include "url.h"
|
||||
#include "share.h"
|
||||
#include "curl_base64.h"
|
||||
#include "connect.h"
|
||||
#include "doh.h"
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
#define DNS_CLASS_IN 0x01
|
||||
#define DOH_MAX_RESPONSE_SIZE 3000 /* bytes */
|
||||
|
||||
typedef enum {
|
||||
DNS_TYPE_A = 1,
|
||||
DNS_TYPE_NS = 2,
|
||||
DNS_TYPE_CNAME = 5,
|
||||
DNS_TYPE_AAAA = 28
|
||||
} DNStype;
|
||||
|
||||
#define MAX_ADDR 24
|
||||
|
||||
typedef enum {
|
||||
DOH_OK,
|
||||
DOH_DNS_BAD_LABEL, /* 1 */
|
||||
DOH_DNS_OUT_OF_RANGE, /* 2 */
|
||||
DOH_DNS_LABEL_LOOP, /* 3 */
|
||||
DOH_TOO_SMALL_BUFFER, /* 4 */
|
||||
DOH_OUT_OF_MEM, /* 5 */
|
||||
DOH_DNS_RDATA_LEN, /* 6 */
|
||||
DOH_DNS_MALFORMAT, /* 7 */
|
||||
DOH_DNS_BAD_RCODE, /* 8 - no such name */
|
||||
DOH_DNS_UNEXPECTED_TYPE, /* 9 */
|
||||
DOH_DNS_UNEXPECTED_CLASS, /* 10 */
|
||||
DOH_NO_CONTENT, /* 11 */
|
||||
DOH_DNS_BAD_ID /* 12 */
|
||||
} DOHcode;
|
||||
|
||||
static const char * const errors[]={
|
||||
"",
|
||||
"Bad label",
|
||||
"Out of range",
|
||||
"Label loop",
|
||||
"Too small",
|
||||
"Out of memory",
|
||||
"RDATA length",
|
||||
"Malformat",
|
||||
"Bad RCODE",
|
||||
"Unexpected TYPE",
|
||||
"Unexpected CLASS",
|
||||
"No content",
|
||||
"Bad ID"
|
||||
};
|
||||
|
||||
static const char *doh_strerror(DOHcode code)
|
||||
{
|
||||
if((code >= DOH_OK) && (code <= DOH_DNS_BAD_ID))
|
||||
return errors[code];
|
||||
return "bad error code";
|
||||
}
|
||||
|
||||
static DOHcode doh_encode(const char *host,
|
||||
DNStype dnstype,
|
||||
unsigned char *dnsp, /* buffer */
|
||||
size_t len, /* buffer size */
|
||||
size_t *olen) /* output length */
|
||||
{
|
||||
size_t hostlen = strlen(host);
|
||||
unsigned char *orig = dnsp;
|
||||
const char *hostp = host;
|
||||
|
||||
if(len < (12 + hostlen + 4))
|
||||
return DOH_TOO_SMALL_BUFFER;
|
||||
|
||||
*dnsp++ = 0; /* 16 bit id */
|
||||
*dnsp++ = 0;
|
||||
*dnsp++ = 0x01; /* |QR| Opcode |AA|TC|RD| Set the RD bit */
|
||||
*dnsp++ = '\0'; /* |RA| Z | RCODE | */
|
||||
*dnsp++ = '\0';
|
||||
*dnsp++ = 1; /* QDCOUNT (number of entries in the question section) */
|
||||
*dnsp++ = '\0';
|
||||
*dnsp++ = '\0'; /* ANCOUNT */
|
||||
*dnsp++ = '\0';
|
||||
*dnsp++ = '\0'; /* NSCOUNT */
|
||||
*dnsp++ = '\0';
|
||||
*dnsp++ = '\0'; /* ARCOUNT */
|
||||
|
||||
/* store a QNAME */
|
||||
do {
|
||||
char *dot = strchr(hostp, '.');
|
||||
size_t labellen;
|
||||
bool found = false;
|
||||
if(dot) {
|
||||
found = true;
|
||||
labellen = dot - hostp;
|
||||
}
|
||||
else
|
||||
labellen = strlen(hostp);
|
||||
if(labellen > 63)
|
||||
/* too long label, error out */
|
||||
return DOH_DNS_BAD_LABEL;
|
||||
*dnsp++ = (unsigned char)labellen;
|
||||
memcpy(dnsp, hostp, labellen);
|
||||
dnsp += labellen;
|
||||
hostp += labellen + 1;
|
||||
if(!found) {
|
||||
*dnsp++ = 0; /* terminating zero */
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
|
||||
*dnsp++ = '\0'; /* upper 8 bit TYPE */
|
||||
*dnsp++ = (unsigned char)dnstype;
|
||||
*dnsp++ = '\0'; /* upper 8 bit CLASS */
|
||||
*dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
|
||||
|
||||
*olen = dnsp - orig;
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static size_t
|
||||
doh_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
size_t realsize = size * nmemb;
|
||||
struct dohresponse *mem = (struct dohresponse *)userp;
|
||||
|
||||
if((mem->size + realsize) > DOH_MAX_RESPONSE_SIZE)
|
||||
/* suspiciously much for us */
|
||||
return 0;
|
||||
|
||||
mem->memory = realloc(mem->memory, mem->size + realsize);
|
||||
if(mem->memory == NULL)
|
||||
/* out of memory! */
|
||||
return 0;
|
||||
|
||||
memcpy(&(mem->memory[mem->size]), contents, realsize);
|
||||
mem->size += realsize;
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
/* called from multi.c when this DOH transfer is complete */
|
||||
static int Curl_doh_done(struct Curl_easy *doh, CURLcode result)
|
||||
{
|
||||
struct Curl_easy *data = doh->set.dohfor;
|
||||
/* so one of the DOH request done for the 'data' transfer is now complete! */
|
||||
data->req.doh.pending--;
|
||||
infof(data, "a DOH request is completed, %d to go\n", data->req.doh.pending);
|
||||
if(result)
|
||||
infof(data, "DOH request %s\n", curl_easy_strerror(result));
|
||||
|
||||
if(!data->req.doh.pending) {
|
||||
/* DOH completed */
|
||||
curl_slist_free_all(data->req.doh.headers);
|
||||
data->req.doh.headers = NULL;
|
||||
Curl_expire(data, 0, EXPIRE_RUN_NOW);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ERROR_CHECK_SETOPT(x,y) result = curl_easy_setopt(doh, x, y); \
|
||||
if(result) goto error
|
||||
|
||||
static CURLcode dohprobe(struct Curl_easy *data,
|
||||
struct dnsprobe *p, DNStype dnstype,
|
||||
const char *host,
|
||||
const char *url, CURLM *multi,
|
||||
struct curl_slist *headers)
|
||||
{
|
||||
struct Curl_easy *doh = NULL;
|
||||
char *nurl = NULL;
|
||||
CURLcode result = CURLE_OK;
|
||||
timediff_t timeout_ms;
|
||||
DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer),
|
||||
&p->dohlen);
|
||||
if(d) {
|
||||
failf(data, "Failed to encode DOH packet [%d]\n", d);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
p->dnstype = dnstype;
|
||||
p->serverdoh.memory = NULL;
|
||||
/* the memory will be grown as needed by realloc in the doh_write_cb
|
||||
function */
|
||||
p->serverdoh.size = 0;
|
||||
|
||||
/* Note: this is code for sending the DoH request with GET but there's still
|
||||
no logic that actually enables this. We should either add that ability or
|
||||
yank out the GET code. Discuss! */
|
||||
if(data->set.doh_get) {
|
||||
char *b64;
|
||||
size_t b64len;
|
||||
result = Curl_base64url_encode(data, (char *)p->dohbuffer, p->dohlen,
|
||||
&b64, &b64len);
|
||||
if(result)
|
||||
goto error;
|
||||
nurl = aprintf("%s?dns=%s", url, b64);
|
||||
free(b64);
|
||||
if(!nurl) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
url = nurl;
|
||||
}
|
||||
|
||||
timeout_ms = Curl_timeleft(data, NULL, TRUE);
|
||||
|
||||
/* Curl_open() is the internal version of curl_easy_init() */
|
||||
result = Curl_open(&doh);
|
||||
if(!result) {
|
||||
ERROR_CHECK_SETOPT(CURLOPT_URL, url);
|
||||
ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb);
|
||||
ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, (void *)&p->serverdoh);
|
||||
if(!data->set.doh_get) {
|
||||
ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, &p->dohbuffer[0]);
|
||||
ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, p->dohlen);
|
||||
}
|
||||
ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers);
|
||||
ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
|
||||
#ifndef CURLDEBUG
|
||||
/* enforce HTTPS if not debug */
|
||||
ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
|
||||
#endif
|
||||
ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
|
||||
ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
|
||||
doh->set.fmultidone = Curl_doh_done;
|
||||
doh->set.dohfor = data; /* identify for which transfer this is done */
|
||||
p->easy = doh;
|
||||
|
||||
/* add this transfer to the multi handle */
|
||||
if(curl_multi_add_handle(multi, doh))
|
||||
goto error;
|
||||
}
|
||||
else
|
||||
goto error;
|
||||
free(nurl);
|
||||
return CURLE_OK;
|
||||
|
||||
error:
|
||||
free(nurl);
|
||||
Curl_close(doh);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_doh() resolves a name using DOH. It resolves a name and returns a
|
||||
* 'Curl_addrinfo *' with the address information.
|
||||
*/
|
||||
|
||||
Curl_addrinfo *Curl_doh(struct connectdata *conn,
|
||||
const char *hostname,
|
||||
int port,
|
||||
int *waitp)
|
||||
{
|
||||
struct Curl_easy *data = conn->data;
|
||||
CURLcode result = CURLE_OK;
|
||||
*waitp = TRUE; /* this never returns synchronously */
|
||||
(void)conn;
|
||||
(void)hostname;
|
||||
(void)port;
|
||||
|
||||
/* start clean, consider allocating this struct on demand */
|
||||
memset(&data->req.doh, 0, sizeof(struct dohdata));
|
||||
|
||||
data->req.doh.host = hostname;
|
||||
data->req.doh.port = port;
|
||||
data->req.doh.headers =
|
||||
curl_slist_append(NULL,
|
||||
"Content-Type: application/dns-message");
|
||||
if(!data->req.doh.headers)
|
||||
goto error;
|
||||
|
||||
if(conn->ip_version != CURL_IPRESOLVE_V6) {
|
||||
/* create IPv4 DOH request */
|
||||
result = dohprobe(data, &data->req.doh.probe[0], DNS_TYPE_A,
|
||||
hostname, data->set.str[STRING_DOH],
|
||||
data->multi, data->req.doh.headers);
|
||||
if(result)
|
||||
goto error;
|
||||
data->req.doh.pending++;
|
||||
}
|
||||
|
||||
if(conn->ip_version != CURL_IPRESOLVE_V4) {
|
||||
/* create IPv6 DOH request */
|
||||
result = dohprobe(data, &data->req.doh.probe[1], DNS_TYPE_AAAA,
|
||||
hostname, data->set.str[STRING_DOH],
|
||||
data->multi, data->req.doh.headers);
|
||||
if(result)
|
||||
goto error;
|
||||
data->req.doh.pending++;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
error:
|
||||
curl_slist_free_all(data->req.doh.headers);
|
||||
data->req.doh.headers = NULL;
|
||||
curl_easy_cleanup(data->req.doh.probe[0].easy);
|
||||
data->req.doh.probe[0].easy = NULL;
|
||||
curl_easy_cleanup(data->req.doh.probe[1].easy);
|
||||
data->req.doh.probe[1].easy = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static DOHcode skipqname(unsigned char *doh, size_t dohlen,
|
||||
unsigned int *indexp)
|
||||
{
|
||||
unsigned char length;
|
||||
do {
|
||||
if(dohlen < (*indexp + 1))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
length = doh[*indexp];
|
||||
if((length & 0xc0) == 0xc0) {
|
||||
/* name pointer, advance over it and be done */
|
||||
if(dohlen < (*indexp + 2))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
*indexp += 2;
|
||||
break;
|
||||
}
|
||||
if(length & 0xc0)
|
||||
return DOH_DNS_BAD_LABEL;
|
||||
if(dohlen < (*indexp + 1 + length))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
*indexp += 1 + length;
|
||||
} while(length);
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static unsigned short get16bit(unsigned char *doh, int index)
|
||||
{
|
||||
return (unsigned short)((doh[index] << 8) | doh[index + 1]);
|
||||
}
|
||||
|
||||
static unsigned int get32bit(unsigned char *doh, int index)
|
||||
{
|
||||
return (doh[index] << 24) | (doh[index + 1] << 16) |
|
||||
(doh[index + 2] << 8) | doh[index + 3];
|
||||
}
|
||||
|
||||
struct addr6 {
|
||||
unsigned char byte[16];
|
||||
};
|
||||
|
||||
struct cnamestore {
|
||||
size_t len; /* length of cname */
|
||||
char *alloc; /* allocated pointer */
|
||||
size_t allocsize; /* allocated size */
|
||||
};
|
||||
|
||||
struct dohaddr {
|
||||
int type;
|
||||
union {
|
||||
unsigned int v4;
|
||||
struct addr6 v6;
|
||||
} ip;
|
||||
};
|
||||
|
||||
struct dohentry {
|
||||
unsigned int ttl;
|
||||
int numaddr;
|
||||
struct dohaddr addr[MAX_ADDR];
|
||||
int numcname;
|
||||
struct cnamestore cname[MAX_ADDR];
|
||||
};
|
||||
|
||||
static DOHcode store_a(unsigned char *doh, int index, struct dohentry *d)
|
||||
{
|
||||
/* silently ignore addresses over the limit */
|
||||
if(d->numaddr < MAX_ADDR) {
|
||||
struct dohaddr *a = &d->addr[d->numaddr];
|
||||
a->type = DNS_TYPE_A;
|
||||
a->ip.v4 = ntohl(get32bit(doh, index));
|
||||
d->numaddr++;
|
||||
}
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static DOHcode store_aaaa(unsigned char *doh, int index, struct dohentry *d)
|
||||
{
|
||||
/* silently ignore addresses over the limit */
|
||||
if(d->numaddr < MAX_ADDR) {
|
||||
struct dohaddr *a = &d->addr[d->numaddr];
|
||||
struct addr6 *inet6p = &a->ip.v6;
|
||||
a->type = DNS_TYPE_AAAA;
|
||||
memcpy(inet6p, &doh[index], 16);
|
||||
d->numaddr++;
|
||||
}
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static DOHcode cnameappend(struct cnamestore *c,
|
||||
unsigned char *src,
|
||||
size_t len)
|
||||
{
|
||||
if(!c->alloc) {
|
||||
c->allocsize = len + 1;
|
||||
c->alloc = malloc(c->allocsize);
|
||||
if(!c->alloc)
|
||||
return DOH_OUT_OF_MEM;
|
||||
}
|
||||
else if(c->allocsize < (c->allocsize + len + 1)) {
|
||||
char *ptr;
|
||||
c->allocsize += len + 1;
|
||||
ptr = realloc(c->alloc, c->allocsize);
|
||||
if(!ptr) {
|
||||
free(c->alloc);
|
||||
return DOH_OUT_OF_MEM;
|
||||
}
|
||||
c->alloc = ptr;
|
||||
}
|
||||
memcpy(&c->alloc[c->len], src, len);
|
||||
c->len += len;
|
||||
c->alloc[c->len] = 0; /* keep it zero terminated */
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static DOHcode store_cname(unsigned char *doh,
|
||||
size_t dohlen,
|
||||
unsigned int index,
|
||||
struct dohentry *d)
|
||||
{
|
||||
struct cnamestore *c = &d->cname[d->numcname++];
|
||||
unsigned int loop = 128; /* a valid DNS name can never loop this much */
|
||||
unsigned char length;
|
||||
do {
|
||||
if(index >= dohlen)
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
length = doh[index];
|
||||
if((length & 0xc0) == 0xc0) {
|
||||
int newpos;
|
||||
/* name pointer, get the new offset (14 bits) */
|
||||
if((index + 1) >= dohlen)
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
/* move to the the new index */
|
||||
newpos = (length & 0x3f) << 8 | doh[index + 1];
|
||||
index = newpos;
|
||||
continue;
|
||||
}
|
||||
else if(length & 0xc0)
|
||||
return DOH_DNS_BAD_LABEL; /* bad input */
|
||||
else
|
||||
index++;
|
||||
|
||||
if(length) {
|
||||
DOHcode rc;
|
||||
if(c->len) {
|
||||
rc = cnameappend(c, (unsigned char *)".", 1);
|
||||
if(rc)
|
||||
return rc;
|
||||
}
|
||||
if((index + length) > dohlen)
|
||||
return DOH_DNS_BAD_LABEL;
|
||||
|
||||
rc = cnameappend(c, &doh[index], length);
|
||||
if(rc)
|
||||
return rc;
|
||||
index += length;
|
||||
}
|
||||
} while(length && --loop);
|
||||
|
||||
if(!loop)
|
||||
return DOH_DNS_LABEL_LOOP;
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static DOHcode rdata(unsigned char *doh,
|
||||
size_t dohlen,
|
||||
unsigned short rdlength,
|
||||
unsigned short type,
|
||||
int index,
|
||||
struct dohentry *d)
|
||||
{
|
||||
/* RDATA
|
||||
- A (TYPE 1): 4 bytes
|
||||
- AAAA (TYPE 28): 16 bytes
|
||||
- NS (TYPE 2): N bytes */
|
||||
DOHcode rc;
|
||||
|
||||
switch(type) {
|
||||
case DNS_TYPE_A:
|
||||
if(rdlength != 4)
|
||||
return DOH_DNS_RDATA_LEN;
|
||||
rc = store_a(doh, index, d);
|
||||
if(rc)
|
||||
return rc;
|
||||
break;
|
||||
case DNS_TYPE_AAAA:
|
||||
if(rdlength != 16)
|
||||
return DOH_DNS_RDATA_LEN;
|
||||
rc = store_aaaa(doh, index, d);
|
||||
if(rc)
|
||||
return rc;
|
||||
break;
|
||||
case DNS_TYPE_CNAME:
|
||||
rc = store_cname(doh, dohlen, index, d);
|
||||
if(rc)
|
||||
return rc;
|
||||
break;
|
||||
default:
|
||||
/* unsupported type, just skip it */
|
||||
break;
|
||||
}
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
static DOHcode doh_decode(unsigned char *doh,
|
||||
size_t dohlen,
|
||||
DNStype dnstype,
|
||||
struct dohentry *d)
|
||||
{
|
||||
unsigned char rcode;
|
||||
unsigned short qdcount;
|
||||
unsigned short ancount;
|
||||
unsigned short type = 0;
|
||||
unsigned short class;
|
||||
unsigned short rdlength;
|
||||
unsigned short nscount;
|
||||
unsigned short arcount;
|
||||
unsigned int index = 12;
|
||||
DOHcode rc;
|
||||
|
||||
d->ttl = INT_MAX;
|
||||
|
||||
if(dohlen < 12)
|
||||
return DOH_TOO_SMALL_BUFFER; /* too small */
|
||||
if(doh[0] || doh[1])
|
||||
return DOH_DNS_BAD_ID; /* bad ID */
|
||||
rcode = doh[3] & 0x0f;
|
||||
if(rcode)
|
||||
return DOH_DNS_BAD_RCODE; /* bad rcode */
|
||||
|
||||
qdcount = get16bit(doh, 4);
|
||||
while(qdcount) {
|
||||
rc = skipqname(doh, dohlen, &index);
|
||||
if(rc)
|
||||
return rc; /* bad qname */
|
||||
if(dohlen < (index + 4))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
index += 4; /* skip question's type and class */
|
||||
qdcount--;
|
||||
}
|
||||
|
||||
ancount = get16bit(doh, 6);
|
||||
while(ancount) {
|
||||
unsigned int ttl;
|
||||
|
||||
rc = skipqname(doh, dohlen, &index);
|
||||
if(rc)
|
||||
return rc; /* bad qname */
|
||||
|
||||
if(dohlen < (index + 2))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
type = get16bit(doh, index);
|
||||
if((type != DNS_TYPE_CNAME) && (type != dnstype))
|
||||
/* Not the same type as was asked for nor CNAME */
|
||||
return DOH_DNS_UNEXPECTED_TYPE;
|
||||
index += 2;
|
||||
|
||||
if(dohlen < (index + 2))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
class = get16bit(doh, index);
|
||||
if(DNS_CLASS_IN != class)
|
||||
return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
|
||||
index += 2;
|
||||
|
||||
if(dohlen < (index + 4))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
ttl = get32bit(doh, index);
|
||||
if(ttl < d->ttl)
|
||||
d->ttl = ttl;
|
||||
index += 4;
|
||||
|
||||
if(dohlen < (index + 2))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
rdlength = get16bit(doh, index);
|
||||
index += 2;
|
||||
if(dohlen < (index + rdlength))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
rc = rdata(doh, dohlen, rdlength, type, index, d);
|
||||
if(rc)
|
||||
return rc; /* bad rdata */
|
||||
index += rdlength;
|
||||
ancount--;
|
||||
}
|
||||
|
||||
nscount = get16bit(doh, 8);
|
||||
while(nscount) {
|
||||
rc = skipqname(doh, dohlen, &index);
|
||||
if(rc)
|
||||
return rc; /* bad qname */
|
||||
|
||||
if(dohlen < (index + 8))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
index += 2 + 2 + 4; /* type, class and ttl */
|
||||
|
||||
if(dohlen < (index + 2))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
rdlength = get16bit(doh, index);
|
||||
index += 2;
|
||||
if(dohlen < (index + rdlength))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
index += rdlength;
|
||||
nscount--;
|
||||
}
|
||||
|
||||
arcount = get16bit(doh, 10);
|
||||
while(arcount) {
|
||||
rc = skipqname(doh, dohlen, &index);
|
||||
if(rc)
|
||||
return rc; /* bad qname */
|
||||
|
||||
if(dohlen < (index + 8))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
index += 2 + 2 + 4; /* type, class and ttl */
|
||||
|
||||
if(dohlen < (index + 2))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
|
||||
rdlength = get16bit(doh, index);
|
||||
index += 2;
|
||||
if(dohlen < (index + rdlength))
|
||||
return DOH_DNS_OUT_OF_RANGE;
|
||||
index += rdlength;
|
||||
arcount--;
|
||||
}
|
||||
|
||||
if(index != dohlen)
|
||||
return DOH_DNS_MALFORMAT; /* something is wrong */
|
||||
|
||||
if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
|
||||
/* nothing stored! */
|
||||
return DOH_NO_CONTENT;
|
||||
|
||||
return DOH_OK; /* ok */
|
||||
}
|
||||
|
||||
static void showdoh(struct Curl_easy *data,
|
||||
struct dohentry *d)
|
||||
{
|
||||
int i;
|
||||
infof(data, "TTL: %u seconds\n", d->ttl);
|
||||
for(i = 0; i < d->numaddr; i++) {
|
||||
struct dohaddr *a = &d->addr[i];
|
||||
if(a->type == DNS_TYPE_A) {
|
||||
infof(data, "DOH A: %d.%d.%d.%d\n",
|
||||
a->ip.v4 & 0xff, (a->ip.v4>>8) & 0xff,
|
||||
(a->ip.v4>>16) & 0xff, a->ip.v4 >>24);
|
||||
}
|
||||
else if(a->type == DNS_TYPE_AAAA) {
|
||||
int j;
|
||||
char buffer[128];
|
||||
char *ptr;
|
||||
size_t len;
|
||||
snprintf(buffer, 128, "DOH AAAA: ");
|
||||
ptr = &buffer[10];
|
||||
len = 118;
|
||||
for(j = 0; j < 16; j += 2) {
|
||||
size_t l;
|
||||
snprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6.byte[j],
|
||||
d->addr[i].ip.v6.byte[j + 1]);
|
||||
l = strlen(ptr);
|
||||
len -= l;
|
||||
ptr += l;
|
||||
}
|
||||
infof(data, "%s\n", buffer);
|
||||
}
|
||||
}
|
||||
for(i = 0; i < d->numcname; i++) {
|
||||
infof(data, "CNAME: %s\n", d->cname[i].alloc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* doh2ai()
|
||||
*
|
||||
* This function returns a pointer to the first element of a newly allocated
|
||||
* Curl_addrinfo struct linked list filled with the data from a set of DOH
|
||||
* lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
|
||||
* a IPv6 stack, but usable also for IPv4, all hosts and environments.
|
||||
*
|
||||
* The memory allocated by this function *MUST* be free'd later on calling
|
||||
* Curl_freeaddrinfo(). For each successful call to this function there
|
||||
* must be an associated call later to Curl_freeaddrinfo().
|
||||
*/
|
||||
|
||||
static Curl_addrinfo *
|
||||
doh2ai(const struct dohentry *de, const char *hostname, int port)
|
||||
{
|
||||
Curl_addrinfo *ai;
|
||||
Curl_addrinfo *prevai = NULL;
|
||||
Curl_addrinfo *firstai = NULL;
|
||||
struct sockaddr_in *addr;
|
||||
#ifdef ENABLE_IPV6
|
||||
struct sockaddr_in6 *addr6;
|
||||
#endif
|
||||
CURLcode result = CURLE_OK;
|
||||
int i;
|
||||
|
||||
if(!de)
|
||||
/* no input == no output! */
|
||||
return NULL;
|
||||
|
||||
for(i = 0; i < de->numaddr; i++) {
|
||||
size_t ss_size;
|
||||
CURL_SA_FAMILY_T addrtype;
|
||||
if(de->addr[i].type == DNS_TYPE_AAAA) {
|
||||
#ifndef ENABLE_IPV6
|
||||
/* we can't handle IPv6 addresses */
|
||||
continue;
|
||||
#else
|
||||
ss_size = sizeof(struct sockaddr_in6);
|
||||
addrtype = AF_INET6;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
ss_size = sizeof(struct sockaddr_in);
|
||||
addrtype = AF_INET;
|
||||
}
|
||||
|
||||
ai = calloc(1, sizeof(Curl_addrinfo));
|
||||
if(!ai) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
ai->ai_canonname = strdup(hostname);
|
||||
if(!ai->ai_canonname) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
free(ai);
|
||||
break;
|
||||
}
|
||||
ai->ai_addr = calloc(1, ss_size);
|
||||
if(!ai->ai_addr) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
free(ai->ai_canonname);
|
||||
free(ai);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!firstai)
|
||||
/* store the pointer we want to return from this function */
|
||||
firstai = ai;
|
||||
|
||||
if(prevai)
|
||||
/* make the previous entry point to this */
|
||||
prevai->ai_next = ai;
|
||||
|
||||
ai->ai_family = addrtype;
|
||||
|
||||
/* we return all names as STREAM, so when using this address for TFTP
|
||||
the type must be ignored and conn->socktype be used instead! */
|
||||
ai->ai_socktype = SOCK_STREAM;
|
||||
|
||||
ai->ai_addrlen = (curl_socklen_t)ss_size;
|
||||
|
||||
/* leave the rest of the struct filled with zero */
|
||||
|
||||
switch(ai->ai_family) {
|
||||
case AF_INET:
|
||||
addr = (void *)ai->ai_addr; /* storage area for this info */
|
||||
|
||||
memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
|
||||
addr->sin_family = (CURL_SA_FAMILY_T)addrtype;
|
||||
addr->sin_port = htons((unsigned short)port);
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_IPV6
|
||||
case AF_INET6:
|
||||
addr6 = (void *)ai->ai_addr; /* storage area for this info */
|
||||
|
||||
memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
|
||||
addr6->sin6_family = (CURL_SA_FAMILY_T)addrtype;
|
||||
addr6->sin6_port = htons((unsigned short)port);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
prevai = ai;
|
||||
}
|
||||
|
||||
if(result) {
|
||||
Curl_freeaddrinfo(firstai);
|
||||
firstai = NULL;
|
||||
}
|
||||
|
||||
return firstai;
|
||||
}
|
||||
|
||||
static const char *type2name(DNStype dnstype)
|
||||
{
|
||||
return (dnstype == DNS_TYPE_A)?"A":"AAAA";
|
||||
}
|
||||
|
||||
static void de_cleanup(struct dohentry *d)
|
||||
{
|
||||
int i = 0;
|
||||
for(i = 0; i < d->numcname; i++) {
|
||||
free(d->cname[i].alloc);
|
||||
}
|
||||
}
|
||||
|
||||
CURLcode Curl_doh_is_resolved(struct connectdata *conn,
|
||||
struct Curl_dns_entry **dnsp)
|
||||
{
|
||||
struct Curl_easy *data = conn->data;
|
||||
*dnsp = NULL; /* defaults to no response */
|
||||
|
||||
if(!data->req.doh.probe[0].easy && !data->req.doh.probe[1].easy) {
|
||||
failf(data, "Could not DOH-resolve: %s", conn->async.hostname);
|
||||
return conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY:
|
||||
CURLE_COULDNT_RESOLVE_HOST;
|
||||
}
|
||||
else if(!data->req.doh.pending) {
|
||||
DOHcode rc;
|
||||
DOHcode rc2;
|
||||
struct dohentry de;
|
||||
struct Curl_dns_entry *dns;
|
||||
struct Curl_addrinfo *ai;
|
||||
/* remove DOH handles from multi handle and close them */
|
||||
curl_multi_remove_handle(data->multi, data->req.doh.probe[0].easy);
|
||||
Curl_close(data->req.doh.probe[0].easy);
|
||||
curl_multi_remove_handle(data->multi, data->req.doh.probe[1].easy);
|
||||
Curl_close(data->req.doh.probe[1].easy);
|
||||
|
||||
/* parse the responses, create the struct and return it! */
|
||||
memset(&de, 0, sizeof(de));
|
||||
rc = doh_decode(data->req.doh.probe[0].serverdoh.memory,
|
||||
data->req.doh.probe[0].serverdoh.size,
|
||||
data->req.doh.probe[0].dnstype,
|
||||
&de);
|
||||
free(data->req.doh.probe[0].serverdoh.memory);
|
||||
if(rc) {
|
||||
infof(data, "DOH: %s type %s for %s\n", doh_strerror(rc),
|
||||
type2name(data->req.doh.probe[0].dnstype),
|
||||
data->req.doh.host);
|
||||
}
|
||||
rc2 = doh_decode(data->req.doh.probe[1].serverdoh.memory,
|
||||
data->req.doh.probe[1].serverdoh.size,
|
||||
data->req.doh.probe[1].dnstype,
|
||||
&de);
|
||||
free(data->req.doh.probe[1].serverdoh.memory);
|
||||
if(rc2) {
|
||||
infof(data, "DOG: %s type %s for %s\n", doh_strerror(rc2),
|
||||
type2name(data->req.doh.probe[1].dnstype),
|
||||
data->req.doh.host);
|
||||
}
|
||||
if(!rc || !rc2) {
|
||||
infof(data, "DOH Host name: %s\n", data->req.doh.host);
|
||||
showdoh(data, &de);
|
||||
|
||||
ai = doh2ai(&de, data->req.doh.host, data->req.doh.port);
|
||||
if(!ai) {
|
||||
de_cleanup(&de);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if(data->share)
|
||||
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
||||
|
||||
/* we got a response, store it in the cache */
|
||||
dns = Curl_cache_addr(data, ai, data->req.doh.host, data->req.doh.port);
|
||||
|
||||
if(data->share)
|
||||
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
||||
|
||||
de_cleanup(&de);
|
||||
if(!dns)
|
||||
/* returned failure, bail out nicely */
|
||||
Curl_freeaddrinfo(ai);
|
||||
else {
|
||||
conn->async.dns = dns;
|
||||
*dnsp = dns;
|
||||
return CURLE_OK;
|
||||
}
|
||||
}
|
||||
de_cleanup(&de);
|
||||
|
||||
return CURLE_COULDNT_RESOLVE_HOST;
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
44
lib/doh.h
Normal file
44
lib/doh.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef HEADER_CURL_DOH_H
|
||||
#define HEADER_CURL_DOH_H
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at https://curl.haxx.se/docs/copyright.html.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of the Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the COPYING file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#include "urldata.h"
|
||||
#include "curl_addrinfo.h"
|
||||
|
||||
/*
|
||||
* Curl_doh() resolve a name using DoH (DNS-over-HTTPS). It resolves a name
|
||||
* and returns a 'Curl_addrinfo *' with the address information.
|
||||
*/
|
||||
|
||||
Curl_addrinfo *Curl_doh(struct connectdata *conn,
|
||||
const char *hostname,
|
||||
int port,
|
||||
int *waitp);
|
||||
|
||||
CURLcode Curl_doh_is_resolved(struct connectdata *conn,
|
||||
struct Curl_dns_entry **dns);
|
||||
|
||||
int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks,
|
||||
int numsocks);
|
||||
|
||||
#endif
|
@ -111,31 +111,6 @@ CURLcode Curl_addrinfo_callback(struct connectdata *conn,
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Call this function after Curl_connect() has returned async=TRUE and
|
||||
then a successful name resolve has been received.
|
||||
|
||||
Note: this function disconnects and frees the conn data in case of
|
||||
resolve failure */
|
||||
CURLcode Curl_async_resolved(struct connectdata *conn,
|
||||
bool *protocol_done)
|
||||
{
|
||||
CURLcode result;
|
||||
|
||||
if(conn->async.dns) {
|
||||
conn->dns_entry = conn->async.dns;
|
||||
conn->async.dns = NULL;
|
||||
}
|
||||
|
||||
result = Curl_setup_conn(conn, protocol_done);
|
||||
|
||||
if(result)
|
||||
/* We're not allowed to return failure with memory left allocated
|
||||
in the connectdata struct, free those here */
|
||||
Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_getaddrinfo() is the generic low-level name resolve API within this
|
||||
* source file. There are several versions of this function - for different
|
||||
|
77
lib/hostip.c
77
lib/hostip.c
@ -60,6 +60,7 @@
|
||||
#include "url.h"
|
||||
#include "inet_ntop.h"
|
||||
#include "multiif.h"
|
||||
#include "doh.h"
|
||||
#include "warnless.h"
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
@ -565,23 +566,27 @@ int Curl_resolv(struct connectdata *conn,
|
||||
return CURLRESOLV_ERROR;
|
||||
}
|
||||
|
||||
/* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
|
||||
non-zero value indicating that we need to wait for the response to the
|
||||
resolve call */
|
||||
addr = Curl_getaddrinfo(conn,
|
||||
if(data->set.doh) {
|
||||
addr = Curl_doh(conn, hostname, port, &respwait);
|
||||
}
|
||||
else {
|
||||
/* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
|
||||
non-zero value indicating that we need to wait for the response to the
|
||||
resolve call */
|
||||
addr = Curl_getaddrinfo(conn,
|
||||
#ifdef DEBUGBUILD
|
||||
(data->set.str[STRING_DEVICE]
|
||||
&& !strcmp(data->set.str[STRING_DEVICE],
|
||||
"LocalHost"))?"localhost":
|
||||
(data->set.str[STRING_DEVICE]
|
||||
&& !strcmp(data->set.str[STRING_DEVICE],
|
||||
"LocalHost"))?"localhost":
|
||||
#endif
|
||||
hostname, port, &respwait);
|
||||
|
||||
hostname, port, &respwait);
|
||||
}
|
||||
if(!addr) {
|
||||
if(respwait) {
|
||||
/* the response to our resolve call will come asynchronously at
|
||||
a later time, good or bad */
|
||||
/* First, check that we haven't received the info by now */
|
||||
result = Curl_resolver_is_resolved(conn, &dns);
|
||||
result = Curl_resolv_check(conn, &dns);
|
||||
if(result) /* error detected */
|
||||
return CURLRESOLV_ERROR;
|
||||
if(dns)
|
||||
@ -1053,3 +1058,55 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
CURLcode Curl_resolv_check(struct connectdata *conn,
|
||||
struct Curl_dns_entry **dns)
|
||||
{
|
||||
if(conn->data->set.doh)
|
||||
return Curl_doh_is_resolved(conn, dns);
|
||||
return Curl_resolver_is_resolved(conn, dns);
|
||||
}
|
||||
|
||||
int Curl_resolv_getsock(struct connectdata *conn,
|
||||
curl_socket_t *socks,
|
||||
int numsocks)
|
||||
{
|
||||
#ifdef CURLRES_ASYNCH
|
||||
if(conn->data->set.doh)
|
||||
/* nothing to wait for during DOH resolve, those handles have their own
|
||||
sockets */
|
||||
return GETSOCK_BLANK;
|
||||
return Curl_resolver_getsock(conn, socks, numsocks);
|
||||
#else
|
||||
(void)conn;
|
||||
(void)socks;
|
||||
(void)numsocks;
|
||||
return GETSOCK_BLANK;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Call this function after Curl_connect() has returned async=TRUE and
|
||||
then a successful name resolve has been received.
|
||||
|
||||
Note: this function disconnects and frees the conn data in case of
|
||||
resolve failure */
|
||||
CURLcode Curl_once_resolved(struct connectdata *conn,
|
||||
bool *protocol_done)
|
||||
{
|
||||
CURLcode result;
|
||||
|
||||
if(conn->async.dns) {
|
||||
conn->dns_entry = conn->async.dns;
|
||||
conn->async.dns = NULL;
|
||||
}
|
||||
|
||||
result = Curl_setup_conn(conn, protocol_done);
|
||||
|
||||
if(result)
|
||||
/* We're not allowed to return failure with memory left allocated
|
||||
in the connectdata struct, free those here */
|
||||
Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
13
lib/hostip.h
13
lib/hostip.h
@ -145,12 +145,7 @@ int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa,
|
||||
/* IPv4 threadsafe resolve function used for synch and asynch builds */
|
||||
Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);
|
||||
|
||||
CURLcode Curl_async_resolved(struct connectdata *conn,
|
||||
bool *protocol_connect);
|
||||
|
||||
#ifndef CURLRES_ASYNCH
|
||||
#define Curl_async_resolved(x,y) CURLE_OK
|
||||
#endif
|
||||
CURLcode Curl_once_resolved(struct connectdata *conn, bool *protocol_connect);
|
||||
|
||||
/*
|
||||
* Curl_addrinfo_callback() is used when we build with any asynch specialty.
|
||||
@ -258,4 +253,10 @@ void Curl_hostcache_destroy(struct Curl_easy *data);
|
||||
*/
|
||||
CURLcode Curl_loadhostpairs(struct Curl_easy *data);
|
||||
|
||||
CURLcode Curl_resolv_check(struct connectdata *conn,
|
||||
struct Curl_dns_entry **dns);
|
||||
int Curl_resolv_getsock(struct connectdata *conn,
|
||||
curl_socket_t *socks,
|
||||
int numsocks);
|
||||
|
||||
#endif /* HEADER_CURL_HOSTIP_H */
|
||||
|
30
lib/multi.c
30
lib/multi.c
@ -906,7 +906,7 @@ static int multi_getsock(struct Curl_easy *data,
|
||||
return 0;
|
||||
|
||||
case CURLM_STATE_WAITRESOLVE:
|
||||
return Curl_resolver_getsock(data->easy_conn, socks, numsocks);
|
||||
return Curl_resolv_getsock(data->easy_conn, socks, numsocks);
|
||||
|
||||
case CURLM_STATE_PROTOCONNECT:
|
||||
case CURLM_STATE_SENDPROTOCONNECT:
|
||||
@ -1236,7 +1236,7 @@ static CURLcode multi_reconnect_request(struct connectdata **connp)
|
||||
return result;
|
||||
|
||||
/* Resolved, continue with the connection */
|
||||
result = Curl_async_resolved(conn, &protocol_done);
|
||||
result = Curl_once_resolved(conn, &protocol_done);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
@ -1512,7 +1512,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
|
||||
}
|
||||
|
||||
if(!dns)
|
||||
result = Curl_resolver_is_resolved(data->easy_conn, &dns);
|
||||
result = Curl_resolv_check(data->easy_conn, &dns);
|
||||
|
||||
/* Update sockets here, because the socket(s) may have been
|
||||
closed and the application thus needs to be told, even if it
|
||||
@ -1525,10 +1525,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
|
||||
if(dns) {
|
||||
/* Perform the next step in the connection phase, and then move on
|
||||
to the WAITCONNECT state */
|
||||
result = Curl_async_resolved(data->easy_conn, &protocol_connect);
|
||||
result = Curl_once_resolved(data->easy_conn, &protocol_connect);
|
||||
|
||||
if(result)
|
||||
/* if Curl_async_resolved() returns failure, the connection struct
|
||||
/* if Curl_once_resolved() returns failure, the connection struct
|
||||
is already freed and gone */
|
||||
data->easy_conn = NULL; /* no more connection */
|
||||
else {
|
||||
@ -2132,15 +2132,21 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
|
||||
}
|
||||
|
||||
if(CURLM_STATE_COMPLETED == data->mstate) {
|
||||
/* now fill in the Curl_message with this info */
|
||||
msg = &data->msg;
|
||||
if(data->set.fmultidone) {
|
||||
/* signal via callback instead */
|
||||
data->set.fmultidone(data, result);
|
||||
}
|
||||
else {
|
||||
/* now fill in the Curl_message with this info */
|
||||
msg = &data->msg;
|
||||
|
||||
msg->extmsg.msg = CURLMSG_DONE;
|
||||
msg->extmsg.easy_handle = data;
|
||||
msg->extmsg.data.result = result;
|
||||
msg->extmsg.msg = CURLMSG_DONE;
|
||||
msg->extmsg.easy_handle = data;
|
||||
msg->extmsg.data.result = result;
|
||||
|
||||
rc = multi_addmsg(multi, msg);
|
||||
DEBUGASSERT(!data->easy_conn);
|
||||
rc = multi_addmsg(multi, msg);
|
||||
DEBUGASSERT(!data->easy_conn);
|
||||
}
|
||||
multistate(data, CURLM_STATE_MSGSENT);
|
||||
}
|
||||
} while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE));
|
||||
|
@ -2603,6 +2603,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option,
|
||||
data->set.disallow_username_in_url =
|
||||
(0 != va_arg(param, long)) ? TRUE : FALSE;
|
||||
break;
|
||||
case CURLOPT_DOH_URL:
|
||||
result = Curl_setstropt(&data->set.str[STRING_DOH],
|
||||
va_arg(param, char *));
|
||||
data->set.doh = data->set.str[STRING_DOH]?TRUE:FALSE;
|
||||
break;
|
||||
default:
|
||||
/* unknown tag and its companion, just ignore: */
|
||||
result = CURLE_UNKNOWN_OPTION;
|
||||
|
@ -476,7 +476,6 @@ struct hostname {
|
||||
#define KEEP_SENDBITS (KEEP_SEND | KEEP_SEND_HOLD | KEEP_SEND_PAUSE)
|
||||
|
||||
|
||||
#ifdef CURLRES_ASYNCH
|
||||
struct Curl_async {
|
||||
char *hostname;
|
||||
int port;
|
||||
@ -485,7 +484,6 @@ struct Curl_async {
|
||||
int status; /* if done is TRUE, this is the status from the callback */
|
||||
void *os_specific; /* 'struct thread_data' for Windows */
|
||||
};
|
||||
#endif
|
||||
|
||||
#define FIRSTSOCKET 0
|
||||
#define SECONDARYSOCKET 1
|
||||
@ -511,6 +509,28 @@ enum upgrade101 {
|
||||
UPGR101_WORKING /* talking upgraded protocol */
|
||||
};
|
||||
|
||||
struct dohresponse {
|
||||
unsigned char *memory;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
/* one of these for each DoH request */
|
||||
struct dnsprobe {
|
||||
CURL *easy;
|
||||
int dnstype;
|
||||
unsigned char dohbuffer[512];
|
||||
size_t dohlen;
|
||||
struct dohresponse serverdoh;
|
||||
};
|
||||
|
||||
struct dohdata {
|
||||
struct curl_slist *headers;
|
||||
struct dnsprobe probe[2];
|
||||
unsigned int pending; /* still outstanding requests */
|
||||
const char *host;
|
||||
int port;
|
||||
};
|
||||
|
||||
/*
|
||||
* Request specific data in the easy handle (Curl_easy). Previously,
|
||||
* these members were on the connectdata struct but since a conn struct may
|
||||
@ -606,6 +626,7 @@ struct SingleRequest {
|
||||
|
||||
void *protop; /* Allocated protocol-specific data. Each protocol
|
||||
handler makes sure this points to data it needs. */
|
||||
struct dohdata doh; /* DoH specific data for this request */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -969,11 +990,8 @@ struct connectdata {
|
||||
#endif
|
||||
|
||||
char syserr_buf [256]; /* buffer for Curl_strerror() */
|
||||
|
||||
#ifdef CURLRES_ASYNCH
|
||||
/* data used for the asynch name resolve callback */
|
||||
struct Curl_async async;
|
||||
#endif
|
||||
|
||||
/* These three are used for chunked-encoding trailer support */
|
||||
char *trailer; /* allocated buffer to store trailer in */
|
||||
@ -1442,6 +1460,7 @@ enum dupstring {
|
||||
STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */
|
||||
#endif
|
||||
STRING_TARGET, /* CURLOPT_REQUEST_TARGET */
|
||||
STRING_DOH, /* CURLOPT_DOH_URL */
|
||||
/* -- end of zero-terminated strings -- */
|
||||
|
||||
STRING_LASTZEROTERMINATED,
|
||||
@ -1453,6 +1472,11 @@ enum dupstring {
|
||||
STRING_LAST /* not used, just an end-of-list marker */
|
||||
};
|
||||
|
||||
/* callback that gets called when this easy handle is completed within a multi
|
||||
handle. Only used for internally created transfers, like for example
|
||||
DoH. */
|
||||
typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result);
|
||||
|
||||
struct UserDefined {
|
||||
FILE *err; /* the stderr user data goes here */
|
||||
void *debugdata; /* the data that will be passed to fdebug */
|
||||
@ -1688,6 +1712,10 @@ struct UserDefined {
|
||||
before resolver start */
|
||||
void *resolver_start_client; /* pointer to pass to resolver start callback */
|
||||
bool disallow_username_in_url; /* disallow username in url */
|
||||
bool doh; /* DNS-over-HTTPS enabled */
|
||||
bool doh_get; /* use GET for DoH requests, instead of POST */
|
||||
multidone_func fmultidone;
|
||||
struct Curl_easy *dohfor; /* this is a DoH request for that transfer */
|
||||
};
|
||||
|
||||
struct Names {
|
||||
|
Loading…
Reference in New Issue
Block a user