setopt: add CURLOPT_DOH_URL

Closes #2668
This commit is contained in:
Daniel Stenberg 2018-09-06 09:16:02 +02:00
parent 3f3b26d6fe
commit abff183387
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
15 changed files with 1174 additions and 62 deletions

View File

@ -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

View 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), "

View File

@ -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 \

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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;
}

View File

@ -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 */

View File

@ -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));

View File

@ -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;

View File

@ -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 {