Compare commits

...

3 Commits

Author SHA1 Message Date
Raul Cheleguini b8bc23d241
Merge 9173c11fe4 into d8091e1281 2024-11-08 10:22:29 +05:30
Ryan Wilson d8091e1281 Fix PrivatePIDs=yes integration test for kernels with no /proc/scsi 2024-11-08 13:38:35 +09:00
Raul Cheleguini 9173c11fe4 resolved: Add basic support for DNS-over-HTTPS (RFC 8484)
Add support for DNS-over-HTTPS (DoH) to resolved. The feature can be enabled
through the option DNSOverHTTPS. The implementation takes advantage of the
curl-util used by import tool, for that we move the curl-util to src/basic and
adjust import tool build settings.
2024-11-07 00:48:51 -03:00
18 changed files with 386 additions and 16 deletions

View File

@ -1579,6 +1579,29 @@ conf.set('DEFAULT_DNS_OVER_TLS_MODE',
'DNS_OVER_TLS_' + default_dns_over_tls.underscorify().to_upper()) 'DNS_OVER_TLS_' + default_dns_over_tls.underscorify().to_upper())
conf.set_quoted('DEFAULT_DNS_OVER_TLS_MODE_STR', default_dns_over_tls) conf.set_quoted('DEFAULT_DNS_OVER_TLS_MODE_STR', default_dns_over_tls)
dns_over_https = get_option('dns-over-https')
if dns_over_https != 'false'
have = true
if conf.get('HAVE_LIBCURL') == 0
message('DNS-over-HTTPS support depends on libcurl, but dependencies are not available')
have = false
endif
if conf.get('HAVE_OPENSSL') == 0
message('openssl required, but not available')
have = false
endif
endif
conf.set10('ENABLE_DNS_OVER_HTTPS', have)
default_dns_over_https = get_option('default-dns-over-https')
if default_dns_over_https != 'no' and conf.get('ENABLE_DNS_OVER_HTTPS') == 0
message('default-dns-over-https cannot be enabled. Setting default-dns-over-https to no.')
default_dns_over_https = 'no'
endif
conf.set('DEFAULT_DNS_OVER_HTTPS_MODE',
'DNS_OVER_HTTPS_' + default_dns_over_https.underscorify().to_upper())
conf.set_quoted('DEFAULT_DNS_OVER_HTTPS_MODE_STR', default_dns_over_https)
default_mdns = get_option('default-mdns') default_mdns = get_option('default-mdns')
conf.set('DEFAULT_MDNS_MODE', conf.set('DEFAULT_MDNS_MODE',
'RESOLVE_SUPPORT_' + default_mdns.to_upper()) 'RESOLVE_SUPPORT_' + default_mdns.to_upper())
@ -2991,6 +3014,7 @@ summary({
'default compression method' : compression, 'default compression method' : compression,
'default DNSSEC mode' : default_dnssec, 'default DNSSEC mode' : default_dnssec,
'default DNS-over-TLS mode' : default_dns_over_tls, 'default DNS-over-TLS mode' : default_dns_over_tls,
'default DNS-over-HTTPS mode' : default_dns_over_https,
'default mDNS mode' : default_mdns, 'default mDNS mode' : default_mdns,
'default LLMNR mode' : default_llmnr, 'default LLMNR mode' : default_llmnr,
'default DNS servers' : dns_servers.split(' '), 'default DNS servers' : dns_servers.split(' '),

View File

@ -353,6 +353,10 @@ option('default-dns-over-tls', type : 'combo',
description : 'default DNS-over-TLS mode', description : 'default DNS-over-TLS mode',
choices : ['yes', 'opportunistic', 'no'], choices : ['yes', 'opportunistic', 'no'],
value : 'no') value : 'no')
option('default-dns-over-https', type : 'combo',
description : 'default DNS-over-HTTPS mode',
choices : ['yes', 'no'],
value : 'no')
option('default-mdns', type : 'combo', option('default-mdns', type : 'combo',
choices : ['yes', 'resolve', 'no'], choices : ['yes', 'resolve', 'no'],
description : 'default MulticastDNS mode', description : 'default MulticastDNS mode',
@ -363,6 +367,8 @@ option('default-llmnr', type : 'combo',
value : 'yes') value : 'yes')
option('dns-over-tls', type : 'combo', choices : ['auto', 'gnutls', 'openssl', 'true', 'false'], option('dns-over-tls', type : 'combo', choices : ['auto', 'gnutls', 'openssl', 'true', 'false'],
description : 'DNS-over-TLS support') description : 'DNS-over-TLS support')
option('dns-over-https', type : 'combo', choices : ['true', 'false'],
description : 'DNS-over-HTTPS support')
option('dns-servers', type : 'string', option('dns-servers', type : 'string',
description : 'space-separated list of default DNS servers', description : 'space-separated list of default DNS servers',
value : '1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google 1.0.0.1#cloudflare-dns.com 8.8.4.4#dns.google 2606:4700:4700::1111#cloudflare-dns.com 2001:4860:4860::8888#dns.google 2606:4700:4700::1001#cloudflare-dns.com 2001:4860:4860::8844#dns.google') value : '1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google 1.0.0.1#cloudflare-dns.com 8.8.4.4#dns.google 2606:4700:4700::1111#cloudflare-dns.com 2001:4860:4860::8888#dns.google 2606:4700:4700::1001#cloudflare-dns.com 2001:4860:4860::8844#dns.google')

View File

@ -14,7 +14,6 @@ systemd_pull_sources = files(
'pull-tar.c', 'pull-tar.c',
'pull-job.c', 'pull-job.c',
'pull-common.c', 'pull-common.c',
'curl-util.c',
) )
systemd_import_sources = files( systemd_import_sources = files(

View File

@ -117,6 +117,10 @@ if conf.get('ENABLE_DNS_OVER_TLS') == 1
endif endif
endif endif
if conf.get('ENABLE_DNS_OVER_HTTPS') == 1
systemd_resolved_dependencies += libcurl
endif
link_with = [ link_with = [
libshared, libshared,
libsystemd_resolve_core, libsystemd_resolve_core,

View File

@ -422,10 +422,17 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
/* Determine the best feature level we care about. If DNSSEC mode is off there's no point in using anything /* Determine the best feature level we care about. If DNSSEC mode is off there's no point in using anything
* better than EDNS0, hence don't even try. */ * better than EDNS0, hence don't even try. */
if (dns_server_get_dnssec_mode(s) != DNSSEC_NO) if (dns_server_get_dnssec_mode(s) != DNSSEC_NO) {
best = dns_server_get_dns_over_tls_mode(s) == DNS_OVER_TLS_NO ? best = dns_server_get_dns_over_tls_mode(s) == DNS_OVER_TLS_NO ?
DNS_SERVER_FEATURE_LEVEL_DO : DNS_SERVER_FEATURE_LEVEL_DO :
DNS_SERVER_FEATURE_LEVEL_TLS_DO; DNS_SERVER_FEATURE_LEVEL_TLS_DO;
/* TODO: Add HTTPS_PLAIN_DO too? */
#if ENABLE_DNS_OVER_HTTPS
best = dns_server_get_dns_over_https_mode(s) == DNS_OVER_HTTPS_NO ?
DNS_SERVER_FEATURE_LEVEL_DO :
DNS_SERVER_FEATURE_LEVEL_HTTPS_PLAIN;
#endif
}
else else
best = dns_server_get_dns_over_tls_mode(s) == DNS_OVER_TLS_NO ? best = dns_server_get_dns_over_tls_mode(s) == DNS_OVER_TLS_NO ?
DNS_SERVER_FEATURE_LEVEL_EDNS0 : DNS_SERVER_FEATURE_LEVEL_EDNS0 :
@ -493,7 +500,8 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
} else if (s->packet_bad_opt && } else if (s->packet_bad_opt &&
DNS_SERVER_FEATURE_LEVEL_IS_EDNS0(s->possible_feature_level) && DNS_SERVER_FEATURE_LEVEL_IS_EDNS0(s->possible_feature_level) &&
dns_server_get_dnssec_mode(s) != DNSSEC_YES && dns_server_get_dnssec_mode(s) != DNSSEC_YES &&
dns_server_get_dns_over_tls_mode(s) != DNS_OVER_TLS_YES) { dns_server_get_dns_over_tls_mode(s) != DNS_OVER_TLS_YES &&
dns_server_get_dns_over_https_mode(s) != DNS_OVER_HTTPS_YES) {
/* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to
* below EDNS0 levels. After all, some servers generate different responses with and * below EDNS0 levels. After all, some servers generate different responses with and
@ -962,6 +970,12 @@ DnsOverTlsMode dns_server_get_dns_over_tls_mode(DnsServer *s) {
return manager_get_dns_over_tls_mode(s->manager); return manager_get_dns_over_tls_mode(s->manager);
} }
DnsOverHttpsMode dns_server_get_dns_over_https_mode(DnsServer *s) {
assert(s);
return manager_get_dns_over_https_mode(s->manager);
}
void dns_server_flush_cache(DnsServer *s) { void dns_server_flush_cache(DnsServer *s) {
DnsServer *current; DnsServer *current;
DnsScope *scope; DnsScope *scope;
@ -1099,6 +1113,7 @@ static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVE
[DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP",
[DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0",
[DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN] = "TLS+EDNS0", [DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN] = "TLS+EDNS0",
[DNS_SERVER_FEATURE_LEVEL_HTTPS_PLAIN] = "HTTPS+EDNS0",
[DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO",
[DNS_SERVER_FEATURE_LEVEL_TLS_DO] = "TLS+EDNS0+DO", [DNS_SERVER_FEATURE_LEVEL_TLS_DO] = "TLS+EDNS0+DO",
}; };

View File

@ -35,6 +35,7 @@ typedef enum DnsServerFeatureLevel {
DNS_SERVER_FEATURE_LEVEL_UDP, DNS_SERVER_FEATURE_LEVEL_UDP,
DNS_SERVER_FEATURE_LEVEL_EDNS0, DNS_SERVER_FEATURE_LEVEL_EDNS0,
DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN, DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN,
DNS_SERVER_FEATURE_LEVEL_HTTPS_PLAIN,
DNS_SERVER_FEATURE_LEVEL_DO, DNS_SERVER_FEATURE_LEVEL_DO,
DNS_SERVER_FEATURE_LEVEL_TLS_DO, DNS_SERVER_FEATURE_LEVEL_TLS_DO,
_DNS_SERVER_FEATURE_LEVEL_MAX, _DNS_SERVER_FEATURE_LEVEL_MAX,
@ -46,6 +47,7 @@ typedef enum DnsServerFeatureLevel {
#define DNS_SERVER_FEATURE_LEVEL_IS_EDNS0(x) ((x) >= DNS_SERVER_FEATURE_LEVEL_EDNS0) #define DNS_SERVER_FEATURE_LEVEL_IS_EDNS0(x) ((x) >= DNS_SERVER_FEATURE_LEVEL_EDNS0)
#define DNS_SERVER_FEATURE_LEVEL_IS_TLS(x) IN_SET(x, DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN, DNS_SERVER_FEATURE_LEVEL_TLS_DO) #define DNS_SERVER_FEATURE_LEVEL_IS_TLS(x) IN_SET(x, DNS_SERVER_FEATURE_LEVEL_TLS_PLAIN, DNS_SERVER_FEATURE_LEVEL_TLS_DO)
#define DNS_SERVER_FEATURE_LEVEL_IS_DNSSEC(x) ((x) >= DNS_SERVER_FEATURE_LEVEL_DO) #define DNS_SERVER_FEATURE_LEVEL_IS_DNSSEC(x) ((x) >= DNS_SERVER_FEATURE_LEVEL_DO)
#define DNS_SERVER_FEATURE_LEVEL_IS_HTTPS(x) ((x) == DNS_SERVER_FEATURE_LEVEL_HTTPS_PLAIN)
#define DNS_SERVER_FEATURE_LEVEL_IS_UDP(x) IN_SET(x, DNS_SERVER_FEATURE_LEVEL_UDP, DNS_SERVER_FEATURE_LEVEL_EDNS0, DNS_SERVER_FEATURE_LEVEL_DO) #define DNS_SERVER_FEATURE_LEVEL_IS_UDP(x) IN_SET(x, DNS_SERVER_FEATURE_LEVEL_UDP, DNS_SERVER_FEATURE_LEVEL_EDNS0, DNS_SERVER_FEATURE_LEVEL_DO)
const char* dns_server_feature_level_to_string(DnsServerFeatureLevel i) _const_; const char* dns_server_feature_level_to_string(DnsServerFeatureLevel i) _const_;
@ -164,6 +166,7 @@ void manager_next_dns_server(Manager *m, DnsServer *if_current);
DnssecMode dns_server_get_dnssec_mode(DnsServer *s); DnssecMode dns_server_get_dnssec_mode(DnsServer *s);
DnsOverTlsMode dns_server_get_dns_over_tls_mode(DnsServer *s); DnsOverTlsMode dns_server_get_dns_over_tls_mode(DnsServer *s);
DnsOverHttpsMode dns_server_get_dns_over_https_mode(DnsServer *s);
size_t dns_server_get_mtu(DnsServer *s); size_t dns_server_get_mtu(DnsServer *s);

View File

@ -4,11 +4,13 @@
#include "af-list.h" #include "af-list.h"
#include "alloc-util.h" #include "alloc-util.h"
#include "dns-domain.h" #include "dns-domain.h"
#include "errno-list.h" #include "errno-list.h"
#include "errno-util.h" #include "errno-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "glyph-util.h" #include "glyph-util.h"
#include "hexdecoct.h"
#include "random-util.h" #include "random-util.h"
#include "resolved-dns-cache.h" #include "resolved-dns-cache.h"
#include "resolved-dns-transaction.h" #include "resolved-dns-transaction.h"
@ -16,6 +18,10 @@
#include "resolved-llmnr.h" #include "resolved-llmnr.h"
#include "string-table.h" #include "string-table.h"
#if ENABLE_DNS_OVER_HTTPS
#include "curl-util.h"
#endif
#define TRANSACTIONS_MAX 4096 #define TRANSACTIONS_MAX 4096
#define TRANSACTION_TCP_TIMEOUT_USEC (10U*USEC_PER_SEC) #define TRANSACTION_TCP_TIMEOUT_USEC (10U*USEC_PER_SEC)
@ -682,7 +688,13 @@ static uint16_t dns_transaction_port(DnsTransaction *t) {
if (t->server->port > 0) if (t->server->port > 0)
return t->server->port; return t->server->port;
return DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level) ? 853 : 53; if (DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level))
return 853;
if (DNS_SERVER_FEATURE_LEVEL_IS_HTTPS(t->current_feature_level))
return 443;
return 53;
} }
static int dns_transaction_emit_tcp(DnsTransaction *t) { static int dns_transaction_emit_tcp(DnsTransaction *t) {
@ -1518,6 +1530,9 @@ static int dns_transaction_emit_udp(DnsTransaction *t) {
if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP || DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level)) if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP || DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level))
return -EAGAIN; /* Sorry, can't do UDP, try TCP! */ return -EAGAIN; /* Sorry, can't do UDP, try TCP! */
if (DNS_SERVER_FEATURE_LEVEL_IS_HTTPS(t->current_feature_level))
return -EAGAIN; /* Direct request logic to HTTPS */
if (!t->bypass && !dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type)) if (!t->bypass && !dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type))
return -EOPNOTSUPP; return -EOPNOTSUPP;
@ -1984,6 +1999,223 @@ static int mdns_make_dummy_packet(DnsTransaction *t, DnsPacket **ret_packet, Set
return add_known_answers; return add_known_answers;
} }
#if ENABLE_DNS_OVER_HTTPS
static size_t dns_transaction_curl_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
_cleanup_free_ char *content_header = NULL;
DnsTransaction *t = ASSERT_PTR(userdata);
size_t sz = size * nmemb;
CURLcode code;
long status;
int r;
assert(contents);
code = curl_easy_getinfo(t->curl, CURLINFO_RESPONSE_CODE, &status);
if (code != CURLE_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code));
if (status >= 200 && status <= 299) {
r = curl_header_strdup(contents, sz, "Content-Type:", &content_header);
if (r < 0) {
log_oom();
return 0;
}
if (r > 0) {
r = strcmp("application/dns-message", content_header);
if (r == 0)
t->valid_dns_message = true;
return sz;
}
}
return sz;
}
static size_t dns_transaction_curl_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
DnsTransaction *t = ASSERT_PTR(userdata);
size_t sz = size * nmemb;
int r;
t->payload = memdup(contents, sz);
if (!t->payload) {
log_debug("Failed to extract HTTP payload to further processing");
r = log_oom();
goto fail;
}
t->payload_size += sz;
return sz;
fail:
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return r;
}
static int dns_transaction_curl_recv(DnsTransaction *t, DnsPacket **p) {
size_t ms;
int r;
ms = t->payload_size;
if (t->payload_size < 1) {
log_debug("Received HTTP payload unexpected size %zu", t->payload_size);
return -1;
}
r = dns_packet_new(p, DNS_PROTOCOL_DNS, ms, DNS_PACKET_SIZE_MAX);
if (r < 0)
return r;
log_debug("Received HTTP payload of size %zu", t->payload_size);
return 0;
}
static int dns_transaction_curl_make_url(DnsTransaction *t, char **url) {
_cleanup_free_ char *base64_string = NULL;
uint8_t *packet_to_send = DNS_PACKET_DATA(t->sent);
int r;
/* Let's zero the query ID according to the RFC */
packet_to_send[0] = 0;
packet_to_send[1] = 0;
r = base64mem_full(packet_to_send, t->sent->size, MAX_URL_LENGTH, &base64_string);
if (r < 0) {
log_debug_errno(r, "Failed to encode DNS packet to base64");
return r;
}
/* Remove base64 trailing characters */
delete_trailing_chars(base64_string, "=");
/* Build the DoH's wire format request URL */
r = asprintf(url, "https://%s/dns-query?dns=%s", t->server->server_string, base64_string);
if (r < 0) {
log_debug("Failed to allocate and set the url for transaction %" PRIu16 ".", t->id);
return r;
}
return 0;
}
static void dns_transaction_curl_on_response(CurlGlue *g, CURL *curl, CURLcode result) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
DnsTransaction *t = NULL;
int status;
int r;
assert(g);
assert(curl);
curl_easy_getinfo(curl, CURLINFO_PRIVATE, &t);
if (result != CURLE_OK) {
log_error_errno(SYNTHETIC_ERRNO(EIO), "HTTP request failed: %s", curl_easy_strerror(result));
status = DNS_TRANSACTION_INVALID_REPLY;
goto finish;
}
if (!t->valid_dns_message) {
log_debug("Received invalid HTTP payload, expected content type of application/dns-message");
status = DNS_TRANSACTION_INVALID_REPLY;
goto finish;
}
r = dns_transaction_curl_recv(t, &p);
if (r < 0) {
log_debug_errno(r, "HTTP payload receive failure");
dns_transaction_complete_errno(t, r);
return;
}
/* Transfer the received payload to transaction/packet struct */
uint8_t *p_data = DNS_PACKET_DATA(p);
memcpy(p_data, t->payload, t->payload_size);
p->size = t->payload_size;
r = dns_packet_validate_reply(p);
if (r < 0)
log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m");
if (r == 0)
log_debug("Received inappropriate DNS packet as response, ignoring");
dns_transaction_process_reply(t, p, false);
return;
finish:
dns_transaction_complete(t, status);
}
static int dns_transaction_emit_curl(DnsTransaction *t) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_free_ char *rule = NULL;
int r;
assert(t);
assert(t->sent);
dns_transaction_close_connection(t, true);
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
r = dns_transaction_pick_server(t);
if (r < 0)
return r;
if (manager_server_is_stub(t->scope->manager, t->server))
return -ELOOP;
r = curl_glue_new(&t->glue, e);
if (r < 0)
return r;
t->glue->on_finished = dns_transaction_curl_on_response;
r = dns_transaction_curl_make_url(t, &t->url);
if (r < 0)
return r;
r = curl_glue_make(&t->curl, t->url, t);
if (r < 0)
return r;
if (curl_easy_setopt(t->curl, CURLOPT_HEADERFUNCTION, dns_transaction_curl_header_callback) != CURLE_OK)
return -EIO;
if (curl_easy_setopt(t->curl, CURLOPT_HEADERDATA, t) != CURLE_OK)
return -EIO;
if (curl_easy_setopt(t->curl, CURLOPT_WRITEFUNCTION, dns_transaction_curl_write_callback) != CURLE_OK)
return -EIO;
if (curl_easy_setopt(t->curl, CURLOPT_WRITEDATA, t) != CURLE_OK)
return -EIO;
// Prevents libcurl's native name lookups
r = asprintf(&rule, "%s:443:%s", t->server->server_string, t->server->server_string);
if (r < 0) {
log_debug("Failed to compound IP resolution to CURLOPT_RESOLVE parameter");
return r;
}
t->glue->resolve_rules = curl_slist_append(NULL, rule);
if (curl_easy_setopt(t->curl, CURLOPT_RESOLVE, t->glue->resolve_rules) != CURLE_OK)
return -EIO;
log_debug("Emitting HTTPS request via curl for transaction %" PRIu16, t->id);
r = curl_glue_add(t->glue, t->curl);
if (r < 0)
return r;
} else
/* TODO: Is this the right error code here? */
return -ELOOP;
return 0;
}
#endif
static int dns_transaction_make_packet_mdns(DnsTransaction *t) { static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *dummy = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *dummy = NULL;
_cleanup_set_free_ Set *keys = NULL; _cleanup_set_free_ Set *keys = NULL;
@ -2172,10 +2404,20 @@ int dns_transaction_go(DnsTransaction *t) {
r = dns_transaction_emit_udp(t); r = dns_transaction_emit_udp(t);
if (r == -EMSGSIZE) if (r == -EMSGSIZE)
log_debug("Sending query via TCP since it is too large."); log_debug("Sending query via TCP since it is too large.");
#if ENABLE_DNS_OVER_HTTPS
else if ((r == -EAGAIN &&
(DNS_SERVER_FEATURE_LEVEL_IS_HTTPS(t->current_feature_level))))
log_debug("Sending query via HTTPS.");
#endif
else if (r == -EAGAIN) else if (r == -EAGAIN)
log_debug("Sending query via TCP since UDP isn't supported or DNS-over-TLS is selected."); log_debug("Sending query via TCP since UDP isn't supported or DNS-over-TLS is selected.");
else if (r == -EPERM) else if (r == -EPERM)
log_debug("Sending query via TCP since UDP is blocked."); log_debug("Sending query via TCP since UDP is blocked.");
#if ENABLE_DNS_OVER_HTTPS
if ((r == -EAGAIN &&
(DNS_SERVER_FEATURE_LEVEL_IS_HTTPS(t->current_feature_level))))
r = dns_transaction_emit_curl(t);
#endif
if (IN_SET(r, -EMSGSIZE, -EAGAIN, -EPERM)) if (IN_SET(r, -EMSGSIZE, -EAGAIN, -EPERM))
r = dns_transaction_emit_tcp(t); r = dns_transaction_emit_tcp(t);
} }

View File

@ -4,6 +4,11 @@
#include "sd-event.h" #include "sd-event.h"
#include "in-addr-util.h" #include "in-addr-util.h"
#if ENABLE_DNS_OVER_HTTPS
#include <curl/curl.h>
#include "curl-util.h"
#endif
typedef struct DnsTransaction DnsTransaction; typedef struct DnsTransaction DnsTransaction;
typedef struct DnsTransactionFinder DnsTransactionFinder; typedef struct DnsTransactionFinder DnsTransactionFinder;
typedef enum DnsTransactionState DnsTransactionState; typedef enum DnsTransactionState DnsTransactionState;
@ -92,7 +97,15 @@ struct DnsTransaction {
/* TCP connection logic, if we need it */ /* TCP connection logic, if we need it */
DnsStream *stream; DnsStream *stream;
#if ENABLE_DNS_OVER_HTTPS
/* HTTPS connection logic, if we need it */
CurlGlue *glue;
CURL *curl;
char *url;
uint8_t *payload;
size_t payload_size;
bool valid_dns_message;
#endif
/* The active server */ /* The active server */
DnsServer *server; DnsServer *server;
@ -219,6 +232,9 @@ DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_;
/* Maximum attempts to send MDNS requests, see RFC 6762 Section 8.1 */ /* Maximum attempts to send MDNS requests, see RFC 6762 Section 8.1 */
#define MDNS_TRANSACTION_ATTEMPTS_MAX 3 #define MDNS_TRANSACTION_ATTEMPTS_MAX 3
/* Maximum URL length for HTTP GET request, see RFC ... */
#define MAX_URL_LENGTH 2048
#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? \ #define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? \
LLMNR_TRANSACTION_ATTEMPTS_MAX : \ LLMNR_TRANSACTION_ATTEMPTS_MAX : \
(p) == DNS_PROTOCOL_MDNS ? \ (p) == DNS_PROTOCOL_MDNS ? \

View File

@ -26,6 +26,7 @@ Resolve.LLMNR, config_parse_resolve_support, 0,
Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support)
Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode)
Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Manager, dns_over_tls_mode) Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Manager, dns_over_tls_mode)
Resolve.DNSOverHTTPS, config_parse_dns_over_https_mode, 0, offsetof(Manager, dns_over_https_mode)
Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache)
Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode)
Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts)

View File

@ -1670,6 +1670,15 @@ DnsOverTlsMode manager_get_dns_over_tls_mode(Manager *m) {
return DNS_OVER_TLS_NO; return DNS_OVER_TLS_NO;
} }
DnsOverHttpsMode manager_get_dns_over_https_mode(Manager *m) {
assert(m);
if (m->dns_over_https_mode != _DNS_OVER_HTTPS_MODE_INVALID)
return m->dns_over_https_mode;
return DNS_OVER_HTTPS_NO;
}
void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) {
assert(verdict >= 0); assert(verdict >= 0);

View File

@ -40,6 +40,7 @@ struct Manager {
ResolveSupport mdns_support; ResolveSupport mdns_support;
DnssecMode dnssec_mode; DnssecMode dnssec_mode;
DnsOverTlsMode dns_over_tls_mode; DnsOverTlsMode dns_over_tls_mode;
DnsOverHttpsMode dns_over_https_mode;
DnsCacheMode enable_cache; DnsCacheMode enable_cache;
bool cache_from_localhost; bool cache_from_localhost;
DnsStubListenerMode dns_stub_listener_mode; DnsStubListenerMode dns_stub_listener_mode;
@ -207,6 +208,8 @@ bool manager_dnssec_supported(Manager *m);
DnsOverTlsMode manager_get_dns_over_tls_mode(Manager *m); DnsOverTlsMode manager_get_dns_over_tls_mode(Manager *m);
DnsOverHttpsMode manager_get_dns_over_https_mode(Manager *m);
void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key);
bool manager_routable(Manager *m); bool manager_routable(Manager *m);

View File

@ -26,6 +26,7 @@
#Domains= #Domains=
#DNSSEC={{DEFAULT_DNSSEC_MODE_STR}} #DNSSEC={{DEFAULT_DNSSEC_MODE_STR}}
#DNSOverTLS={{DEFAULT_DNS_OVER_TLS_MODE_STR}} #DNSOverTLS={{DEFAULT_DNS_OVER_TLS_MODE_STR}}
#DNSOverHTTPS={{DEFAULT_DNS_OVER_HTTPS_MODE_STR}}
#MulticastDNS={{DEFAULT_MDNS_MODE_STR}} #MulticastDNS={{DEFAULT_MDNS_MODE_STR}}
#LLMNR={{DEFAULT_LLMNR_MODE_STR}} #LLMNR={{DEFAULT_LLMNR_MODE_STR}}
#Cache=yes #Cache=yes

View File

@ -311,6 +311,9 @@ void curl_glue_remove_and_free(CurlGlue *g, CURL *c) {
if (g->curl) if (g->curl)
curl_multi_remove_handle(g->curl, c); curl_multi_remove_handle(g->curl, c);
if (g->resolve_rules)
curl_slist_free_all(g->resolve_rules);
curl_easy_cleanup(c); curl_easy_cleanup(c);
} }

View File

@ -20,6 +20,7 @@ struct CurlGlue {
void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code);
void *userdata; void *userdata;
struct curl_slist *resolve_rules;;
}; };
int curl_glue_new(CurlGlue **glue, sd_event *event); int curl_glue_new(CurlGlue **glue, sd_event *event);

View File

@ -256,6 +256,10 @@ if conf.get('HAVE_TPM2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1
shared_sources += files('cryptsetup-tpm2.c') shared_sources += files('cryptsetup-tpm2.c')
endif endif
if conf.get('HAVE_LIBCURL') == 1
shared_sources += files('curl-util.c')
endif
generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh') generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh')
ip_protocol_list_txt = custom_target( ip_protocol_list_txt = custom_target(
'ip-protocol-list.txt', 'ip-protocol-list.txt',
@ -337,6 +341,11 @@ libshared_deps = [threads,
libxz_cflags, libxz_cflags,
libzstd_cflags] libzstd_cflags]
# Is this correct?
if conf.get('HAVE_LIBCURL') == 1
libshared_deps += [libcurl]
endif
libshared_sym_path = meson.current_source_dir() / 'libshared.sym' libshared_sym_path = meson.current_source_dir() / 'libshared.sym'
libshared_build_dir = meson.current_build_dir() libshared_build_dir = meson.current_build_dir()

View File

@ -7,6 +7,7 @@
DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport); DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport);
DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_over_tls_mode, dns_over_tls_mode, DnsOverTlsMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_over_tls_mode, dns_over_tls_mode, DnsOverTlsMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_over_https_mode, dns_over_https_mode, DnsOverHttpsMode);
static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = {
[RESOLVE_SUPPORT_NO] = "no", [RESOLVE_SUPPORT_NO] = "no",
@ -29,6 +30,12 @@ static const char* const dns_over_tls_mode_table[_DNS_OVER_TLS_MODE_MAX] = {
}; };
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_over_tls_mode, DnsOverTlsMode, DNS_OVER_TLS_YES); DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_over_tls_mode, DnsOverTlsMode, DNS_OVER_TLS_YES);
static const char* const dns_over_https_mode_table[_DNS_OVER_HTTPS_MODE_MAX] = {
[DNS_OVER_HTTPS_NO] = "no",
[DNS_OVER_HTTPS_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_over_https_mode, DnsOverHttpsMode, DNS_OVER_HTTPS_YES);
bool dns_server_address_valid(int family, const union in_addr_union *sa) { bool dns_server_address_valid(int family, const union in_addr_union *sa) {
/* Refuses the 0 IP addresses as well as 127.0.0.53/127.0.0.54 (which is our own DNS stub) */ /* Refuses the 0 IP addresses as well as 127.0.0.53/127.0.0.54 (which is our own DNS stub) */

View File

@ -27,6 +27,7 @@ enum DnsCacheMode {
typedef enum ResolveSupport ResolveSupport; typedef enum ResolveSupport ResolveSupport;
typedef enum DnssecMode DnssecMode; typedef enum DnssecMode DnssecMode;
typedef enum DnsOverTlsMode DnsOverTlsMode; typedef enum DnsOverTlsMode DnsOverTlsMode;
typedef enum DnsOverHttpsMode DnsOverHttpsMode;
/* Do not change the order, see link_get_llmnr_support() or link_get_mdns_support(). */ /* Do not change the order, see link_get_llmnr_support() or link_get_mdns_support(). */
enum ResolveSupport { enum ResolveSupport {
@ -70,9 +71,21 @@ enum DnsOverTlsMode {
_DNS_OVER_TLS_MODE_INVALID = -EINVAL, _DNS_OVER_TLS_MODE_INVALID = -EINVAL,
}; };
enum DnsOverHttpsMode {
/* No connection is made for DNS-over-HTTPS */
DNS_OVER_HTTPS_NO,
/* Enforce DNS-over-HTTPS */
DNS_OVER_HTTPS_YES,
_DNS_OVER_HTTPS_MODE_MAX,
_DNS_OVER_HTTPS_MODE_INVALID = -EINVAL,
};
CONFIG_PARSER_PROTOTYPE(config_parse_resolve_support); CONFIG_PARSER_PROTOTYPE(config_parse_resolve_support);
CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_dns_over_tls_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dns_over_tls_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_dns_over_https_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_dns_cache_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dns_cache_mode);
const char* resolve_support_to_string(ResolveSupport p) _const_; const char* resolve_support_to_string(ResolveSupport p) _const_;
@ -84,6 +97,9 @@ DnssecMode dnssec_mode_from_string(const char *s) _pure_;
const char* dns_over_tls_mode_to_string(DnsOverTlsMode p) _const_; const char* dns_over_tls_mode_to_string(DnsOverTlsMode p) _const_;
DnsOverTlsMode dns_over_tls_mode_from_string(const char *s) _pure_; DnsOverTlsMode dns_over_tls_mode_from_string(const char *s) _pure_;
const char* dns_over_https_mode_to_string(DnsOverHttpsMode p) _const_;
DnsOverHttpsMode dns_over_https_mode_from_string(const char *s) _pure_;
bool dns_server_address_valid(int family, const union in_addr_union *sa); bool dns_server_address_valid(int family, const union in_addr_union *sa);
const char* dns_cache_mode_to_string(DnsCacheMode p) _const_; const char* dns_cache_mode_to_string(DnsCacheMode p) _const_;

View File

@ -132,10 +132,12 @@ testcase_unpriv() {
return 0 return 0
fi fi
# The kernel has a restriction for unprivileged user namespaces where they cannot mount a less restrictive # IMPORTANT: For /proc/ to be remounted in pid namespace within an unprivileged user namespace, there needs to
# instance of /proc/. So if /proc/ is masked (e.g. /proc/kmsg is over-mounted with tmpfs as systemd-nspawn does), # be at least 1 unmasked procfs mount in ANY directory. Otherwise, if /proc/ is masked (e.g. /proc/scsi is
# then mounting a new /proc/ will fail and we will still see the host's /proc/. Thus, to allow tests to run in # over-mounted with tmpfs), then mounting a new /proc/ will fail.
# a VM or nspawn, we mount a new proc on a temporary directory with no masking to bypass this kernel restriction. #
# Thus, to guarantee PrivatePIDs=yes tests for unprivileged users pass, we mount a new procfs on a temporary
# directory with no masking. This will guarantee an unprivileged user can mount a new /proc/ successfully.
mkdir -p /tmp/TEST-07-PID1-private-pids-proc mkdir -p /tmp/TEST-07-PID1-private-pids-proc
mount -t proc proc /tmp/TEST-07-PID1-private-pids-proc mount -t proc proc /tmp/TEST-07-PID1-private-pids-proc
@ -146,7 +148,16 @@ testcase_unpriv() {
umount /tmp/TEST-07-PID1-private-pids-proc umount /tmp/TEST-07-PID1-private-pids-proc
rm -rf /tmp/TEST-07-PID1-private-pids-proc rm -rf /tmp/TEST-07-PID1-private-pids-proc
# Now verify the behavior with masking - units should fail as PrivatePIDs=yes has no graceful fallback. # Now we will mask /proc/ by mounting tmpfs over /proc/scsi. This will guarantee that mounting /proc/ will fail
# for unprivileged users when using PrivatePIDs=yes. Now units should fail as PrivatePIDs=yes has no graceful
# fallback.
#
# Note some kernels do not have /proc/scsi so we verify the directory exists prior to running the test.
if [ ! -d /proc/scsi ]; then
echo "/proc/scsi does not exist, skipping unprivileged PrivatePIDs=yes test with masked /proc/"
return 0
fi
if [[ "$HAS_EXISTING_SCSI_MOUNT" == "no" ]]; then if [[ "$HAS_EXISTING_SCSI_MOUNT" == "no" ]]; then
mount -t tmpfs tmpfs /proc/scsi mount -t tmpfs tmpfs /proc/scsi
fi fi