diff --git a/src/context.c b/src/context.c index ee65bbe8..8c01ea0d 100644 --- a/src/context.c +++ b/src/context.c @@ -91,9 +91,11 @@ typedef unsigned short in_port_t; #define GETDNS_PORT_ZERO 0 #define GETDNS_PORT_DNS 53 #define GETDNS_PORT_DNS_OVER_TLS 853 +#define GETDNS_PORT_DNS_OVER_HTTPS 443 #define GETDNS_STR_PORT_ZERO "0" #define GETDNS_STR_PORT_DNS "53" #define GETDNS_STR_PORT_DNS_OVER_TLS "853" +#define GETDNS_STR_PORT_DNS_OVER_HTTPS "443" #ifdef HAVE_PTHREAD static pthread_mutex_t ssl_init_lock = PTHREAD_MUTEX_INITIALIZER; @@ -135,7 +137,7 @@ getdns_port_array[GETDNS_UPSTREAM_TRANSPORTS] = { }; static char* -getdns_port_str_array[] = { +getdns_port_str_array[GETDNS_UPSTREAM_TRANSPORTS] = { GETDNS_STR_PORT_DNS, GETDNS_STR_PORT_DNS_OVER_TLS }; @@ -978,6 +980,8 @@ upstream_init(getdns_upstream *upstream, upstream->best_tls_auth_state = GETDNS_AUTH_NONE; upstream->tls_pubkey_pinset = NULL; upstream->tls_dane_records = NULL; + upstream->alpn = NULL; + upstream->doh_path[0] = '\0'; upstream->loop = NULL; (void) getdns_eventloop_event_init( &upstream->event, upstream, NULL, NULL, NULL); @@ -2885,6 +2889,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, getdns_bindata *address_type; getdns_bindata *address_data; getdns_bindata *tls_auth_name; + getdns_bindata *doh_path; struct sockaddr_storage addr; getdns_bindata *scope_id; @@ -3022,17 +3027,30 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, for (j = 0; j < GETDNS_UPSTREAM_TRANSPORTS; j++) { uint32_t port; struct addrinfo *ai; + getdns_bindata *alpn = NULL; + + static const char *alpn_dot = "dot"; + static const char *alpn_h2 = "h2"; + port = getdns_port_array[j]; if (port == GETDNS_PORT_ZERO) continue; - if (getdns_upstream_transports[j] != GETDNS_TRANSPORT_TLS) { - if (dict) - (void) getdns_dict_get_int(dict, "port", &port); - } else { - if (dict) - (void) getdns_dict_get_int(dict, "tls_port", &port); - } + if (!dict) + ; /* pass */ + + else if (getdns_upstream_transports[j] + != GETDNS_TRANSPORT_TLS) + getdns_dict_get_int(dict, "port", &port); + + else if (!getdns_dict_get_bindata(dict, "alpn", &alpn) + && alpn->size == 2 && alpn->data[0] == 'h' + && alpn->data[1] == '2') { + port = GETDNS_PORT_DNS_OVER_HTTPS; + getdns_dict_get_int(dict, "tls_port", &port); + } else + getdns_dict_get_int(dict, "tls_port", &port); + (void) snprintf(portstr, 1024, "%d", (int)port); if (getaddrinfo(addrstr, portstr, &hints, &ai)) @@ -3048,7 +3066,21 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream->addr.ss_family = addr.ss_family; upstream_init(upstream, upstreams, ai); upstream->transport = getdns_upstream_transports[j]; - if (dict && getdns_upstream_transports[j] == GETDNS_TRANSPORT_TLS) { + if (!alpn) + ; /* pass */ + + else if (alpn->size == 3 && alpn->data[0] == 'd' + && alpn->data[1] == 'o' && alpn->data[2] == 't') + upstream->alpn = alpn_dot; + + else if (alpn->size == 2 && alpn->data[0] == 'h' + && alpn->data[1] == '2') + upstream->alpn = alpn_h2; + else + goto invalid_parameter; + + if (dict && getdns_upstream_transports[j] == + GETDNS_TRANSPORT_TLS) { getdns_list *pubkey_pinset = NULL; getdns_list *dane_records = NULL; getdns_bindata *tls_cipher_list = NULL; @@ -3078,6 +3110,19 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream->tls_auth_name [tls_auth_name->size] = '\0'; } + if ((r = getdns_dict_get_bindata( + dict, "doh_path", &doh_path)) == GETDNS_RETURN_GOOD) { + + if (doh_path->size >= sizeof(upstream->doh_path)) { + freeaddrinfo(ai); + goto invalid_parameter; + } + memcpy(upstream->doh_path, + (char *)doh_path->data, + doh_path->size); + upstream->doh_path + [doh_path->size] = '\0'; + } if ((r = getdns_dict_get_list(dict, "tls_pubkey_pinset", &pubkey_pinset)) == GETDNS_RETURN_GOOD) { /* TODO: what if the user supplies tls_pubkey_pinset with @@ -5396,52 +5441,71 @@ getdns_context_get_upstream_recursive_servers( (uint32_t)upstream_port(upstream)))) break; - if (upstream->transport == GETDNS_TRANSPORT_TLS) { - if (upstream_port(upstream) != getdns_port_array[j] && - (r = getdns_dict_set_int(d, "tls_port", - (uint32_t) upstream_port(upstream)))) + if (upstream->transport != GETDNS_TRANSPORT_TLS) + continue; + + if (!is_doh_upstream(upstream) + && upstream_port(upstream) != GETDNS_PORT_DNS_OVER_TLS + && (r = getdns_dict_set_int(d, "tls_port", + (uint32_t) upstream_port(upstream)))) + break; + + if (is_doh_upstream(upstream) + && upstream_port(upstream) != GETDNS_PORT_DNS_OVER_HTTPS + && (r = getdns_dict_set_int(d, "tls_port", + (uint32_t) upstream_port(upstream)))) + break; + + if (upstream->alpn + && (r = getdns_dict_util_set_string( + d, "alpn", upstream->alpn))) + break; + + if (upstream->tls_auth_name[0] != '\0' && + (r = getdns_dict_util_set_string(d, + "tls_auth_name", + upstream->tls_auth_name))) + break; + if (upstream->tls_pubkey_pinset) { + getdns_list *pins = NULL; + if ((_getdns_get_pubkey_pinset_list(context, + upstream->tls_pubkey_pinset, + &pins) == GETDNS_RETURN_GOOD) && + (r = _getdns_dict_set_this_list(d, "tls_pubkey_pinset", pins))) { + getdns_list_destroy(pins); break; - if (upstream->tls_auth_name[0] != '\0' && - (r = getdns_dict_util_set_string(d, - "tls_auth_name", - upstream->tls_auth_name))) - break; - if (upstream->tls_pubkey_pinset) { - getdns_list *pins = NULL; - if ((_getdns_get_pubkey_pinset_list(context, - upstream->tls_pubkey_pinset, - &pins) == GETDNS_RETURN_GOOD) && - (r = _getdns_dict_set_this_list(d, "tls_pubkey_pinset", pins))) { - getdns_list_destroy(pins); - break; - } - } - if (upstream->tls_cipher_list) { - (void) getdns_dict_util_set_string( - d, "tls_cipher_list", - upstream->tls_cipher_list); - } - if (upstream->tls_ciphersuites) { - (void) getdns_dict_util_set_string( - d, "tls_ciphersuites", - upstream->tls_ciphersuites); - } - if (upstream->tls_curves_list) { - (void) getdns_dict_util_set_string( - d, "tls_curves_list", - upstream->tls_curves_list); - } - if (upstream->tls_min_version) { - (void) getdns_dict_set_int( - d, "tls_min_version", - upstream->tls_min_version); - } - if (upstream->tls_max_version) { - (void) getdns_dict_set_int( - d, "tls_max_version", - upstream->tls_max_version); } } + if (upstream->tls_cipher_list) { + (void) getdns_dict_util_set_string( + d, "tls_cipher_list", + upstream->tls_cipher_list); + } + if (upstream->tls_ciphersuites) { + (void) getdns_dict_util_set_string( + d, "tls_ciphersuites", + upstream->tls_ciphersuites); + } + if (upstream->tls_curves_list) { + (void) getdns_dict_util_set_string( + d, "tls_curves_list", + upstream->tls_curves_list); + } + if (upstream->tls_min_version) { + (void) getdns_dict_set_int( + d, "tls_min_version", + upstream->tls_min_version); + } + if (upstream->tls_max_version) { + (void) getdns_dict_set_int( + d, "tls_max_version", + upstream->tls_max_version); + } + if (upstream->doh_path[0] != '\0' + && (r = getdns_dict_util_set_string( + d, "doh_path", + upstream->doh_path))) + break; } if (!r) if (!(r = _getdns_list_append_this_dict(upstreams, d))) diff --git a/src/context.h b/src/context.h index 56f9805f..119a499c 100644 --- a/src/context.h +++ b/src/context.h @@ -230,6 +230,10 @@ typedef struct getdns_upstream { getdns_tls_version_t tls_min_version; getdns_tls_version_t tls_max_version; + /* DoH settings */ + const char *alpn; + char doh_path[256]; + /* Auth credentials */ char tls_auth_name[256]; sha256_pin_t *tls_pubkey_pinset; @@ -271,6 +275,10 @@ typedef struct getdns_upstream { } getdns_upstream; +INLINE int is_doh_upstream(getdns_upstream *u) +{ return u && u->alpn && u->alpn[0] == 'h' + && (u->alpn[1] == '2' || u->alpn[1] == '3'); } + #define POLICY_N_ADDR 3 #define POLICY_N_SVCPARAMS 8 diff --git a/src/convert.c b/src/convert.c index 5d29a60b..eca9130b 100644 --- a/src/convert.c +++ b/src/convert.c @@ -1126,6 +1126,8 @@ _getdns_ipaddr_dict_mf(struct mem_funcs *mf, const char *ipstr) char *p = strchr(ipstr, '@'), *portstr = ""; char *t = strchr(ipstr, '#'), *tls_portstr = ""; char *n = strchr(ipstr, '~'), *tls_namestr = ""; + char *P = strchr(ipstr, '/'), *doh_pathstr = ""; + char *A = strchr(ipstr, '_'), *alpnstr = ""; /* ^[alg:]name:key */ char *T = strchr(ipstr, '^'), *tsig_name_str = "" , *tsig_secret_str = "" @@ -1173,6 +1175,14 @@ _getdns_ipaddr_dict_mf(struct mem_funcs *mf, const char *ipstr) *n = 0; tls_namestr = n + 1; } + if (P) { + *P = 0; + doh_pathstr = P + 1; + } + if (A) { + *A = 0; + alpnstr = A + 1; + } if (T) { *T = 0; tsig_name_str = T + 1; @@ -1213,9 +1223,12 @@ _getdns_ipaddr_dict_mf(struct mem_funcs *mf, const char *ipstr) getdns_dict_set_int(r, "port", (int32_t)atoi(portstr)); if (*tls_portstr) getdns_dict_set_int(r, "tls_port", (int32_t)atoi(tls_portstr)); - if (*tls_namestr) { + if (*tls_namestr) getdns_dict_util_set_string(r, "tls_auth_name", tls_namestr); - } + if (*doh_pathstr) + getdns_dict_util_set_string(r, "doh_path", doh_pathstr); + if (*alpnstr) + getdns_dict_util_set_string(r, "alpn", alpnstr); if (*scope_id_str) getdns_dict_util_set_string(r, "scope_id", scope_id_str); if (*tsig_name_str) diff --git a/src/openssl/tls.c b/src/openssl/tls.c index 275f8803..2d02ba93 100644 --- a/src/openssl/tls.c +++ b/src/openssl/tls.c @@ -807,6 +807,25 @@ getdns_return_t _getdns_tls_connection_set_curves_list(_getdns_tls_connection* c return GETDNS_RETURN_GOOD; } +getdns_return_t _getdns_tls_connection_set_alpn(_getdns_tls_connection* conn, const char* alpn) +{ + uint8_t protos[] = "\x03" "dot"; + if (!conn || !conn->ssl) + return GETDNS_RETURN_INVALID_PARAMETER; + if (!alpn) + ; + else if (strlen(alpn) > sizeof(protos) - 2) + return GETDNS_RETURN_GENERIC_ERROR; + else { + strcpy((char *)(protos + 1), alpn); + protos[0] = (uint8_t)strlen(alpn); + } + if (SSL_set_alpn_protos(conn->ssl, protos, protos[0] + 1)) + return GETDNS_RETURN_GENERIC_ERROR; + else + return GETDNS_RETURN_GOOD; +} + getdns_return_t _getdns_tls_connection_set_session(_getdns_tls_connection* conn, _getdns_tls_session* s) { if (!conn || !conn->ssl || !s || !s->ssl) diff --git a/src/stub.c b/src/stub.c index 03502921..3e65ee3e 100644 --- a/src/stub.c +++ b/src/stub.c @@ -1006,6 +1006,8 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) r = _getdns_tls_connection_set_curves_list(tls, upstream->tls_curves_list); if (!r && upstream->tls_ciphersuites) r = _getdns_tls_connection_set_cipher_suites(tls, upstream->tls_ciphersuites); + if (!r) + r = _getdns_tls_connection_set_alpn(tls, upstream->alpn); if (!r) r = _getdns_tls_connection_set_min_max_tls_version(tls, upstream->tls_min_version, upstream->tls_max_version); diff --git a/src/tls.h b/src/tls.h index 71fb1705..aefc4d70 100644 --- a/src/tls.h +++ b/src/tls.h @@ -227,6 +227,18 @@ getdns_return_t _getdns_tls_connection_set_cipher_list(_getdns_tls_connection* c */ getdns_return_t _getdns_tls_connection_set_cipher_suites(_getdns_tls_connection* conn, const char* list); +/** + * Set alpn to send on this connection. + * + * @param conn the connection. + * @param alpn the application layer protocol negotiation (alpn) value. + NULL for default setting (dot). + * @return GETDNS_RETURN_GOOD on success. + * @return GETDNS_RETURN_INVALID_PARAMETER on bad context pointer. + * @return GETDNS_RETURN_BAD_CONTEXT on failure. + */ +getdns_return_t _getdns_tls_connection_set_alpn(_getdns_tls_connection* conn, const char* alpn); + /** * Set list of allowed curves on this connection. * diff --git a/src/tools/getdns_query.c b/src/tools/getdns_query.c index 013b9807..8e9b6d86 100644 --- a/src/tools/getdns_query.c +++ b/src/tools/getdns_query.c @@ -178,10 +178,12 @@ print_usage(FILE *out, const char *progname) #endif fprintf(out, "\ndefault mode: " DEFAULT_RESOLUTION_TYPE ", synchronous resolution of NS record\n\t\tusing UDP with TCP fallback\n"); - fprintf(out, "\nupstreams: @[%%][@][#][~][^]"); + fprintf(out, "\nupstreams: @[%%][@][^]"); + fprintf(out, "\n [#][~][_][/]"); fprintf(out, "\n @ may be given as :"); fprintf(out, "\n or \'[\'[%%]\']\': too\n"); fprintf(out, "\ntsig spec: [:]:\n"); + fprintf(out, "\nalpn: [dot | h2]\n"); fprintf(out, "\nextensions:\n"); fprintf(out, "\t+add_warning_for_bad_dns\n"); fprintf(out, "\t+dnssec\n");