From 1f88087f92230fefdf47c8ab11ca3fd93477d869 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 18 Aug 2022 12:02:47 +0200 Subject: [PATCH 01/14] Support for proxy control option --- src/context.c | 740 +++++++++++++++++++++++++++++++++++++++++ src/context.h | 27 ++ src/getdns/getdns.h.in | 4 + src/gldns/rrdef.h | 4 +- src/request-internal.c | 45 +++ src/stub.c | 19 ++ src/types-internal.h | 2 + 7 files changed, 840 insertions(+), 1 deletion(-) diff --git a/src/context.c b/src/context.c index c8cc398d..7b3fd3a1 100644 --- a/src/context.c +++ b/src/context.c @@ -662,6 +662,38 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) GETDNS_FREE(upstreams->mf, upstreams); } +static getdns_proxy_policies * +proxy_policies_create(getdns_context *context, size_t count) +{ + getdns_proxy_policies *r = (void *) GETDNS_XMALLOC(context->mf, char, + sizeof(getdns_proxy_policies) + + sizeof(getdns_proxy_policy) * count); + +#if 0 +fprintf(stderr, "proxy_policies_create: alloc %d + %d * %d = %d\n", + sizeof(getdns_proxy_policies), count, sizeof(getdns_proxy_policy), + sizeof(getdns_proxy_policies) + + sizeof(getdns_proxy_policy) * count); +#endif + + if (r) { + r->mf = context->mf; + r->referenced = 1; + r->count = count; + r->policy_opts = NULL; + } + return r; +} + +void +_getdns_proxy_policies_dereference(getdns_proxy_policies *policies) +{ + if (!policies || --policies->referenced > 0) + return; + + GETDNS_FREE(policies->mf, policies); +} + static void upstream_backoff(getdns_upstream *upstream) { upstream->conn_state = GETDNS_CONN_BACKOFF; @@ -1497,6 +1529,7 @@ getdns_context_create_with_extended_memory_functions( } result->upstreams = NULL; + result->proxy_policies = NULL; result->edns_extended_rcode = 0; result->edns_version = 0; @@ -3071,6 +3104,713 @@ error: } /* getdns_context_set_upstream_recursive_servers */ +/* Note: buf has to be 16-bit aligned */ +static void proxy_policy2opt(getdns_proxy_policy *policy, int do_ipv6, + uint8_t *buf, size_t *sizep); + +static void update_proxy_policy_opts(struct getdns_context *context) +{ + int i, j, addr_count, policy_count, has_v4, has_v6; + size_t off, size; + getdns_proxy_policy *policy; + uint8_t *opt; + union + { + uint16_t u16; /* For alignment */ + uint8_t buf[2048]; + } buf; + + + /* Compute a list of proxy control options that correspond to + * the policy. + */ + + /* Loop over policies. If an entry has both IPv4 and IPv6 addresses + * then we need 2 options. + */ + off= 0; + policy_count = context->proxy_policies->count; +fprintf(stderr, "update_proxy_policy_opts: policy_count %d\n", policy_count); + for (i = 0, policy = &context->proxy_policies->policies[0]; + iaddr_count; + for (j = 0; jaddrs[j].ss_family == AF_INET) + has_v4 = 1; + else if (policy->addrs[j].ss_family == AF_INET6) + has_v6 = 1; + else + { + /* Bad family. Ignore? */ + } + + } + + if (has_v6 || addr_count == 0) + { + size= sizeof(buf)-off; + proxy_policy2opt(policy, 1 /*do_ipv6*/, buf.buf+off, + &size); + off= size; + } + if (has_v4) + { + size= sizeof(buf)-off; + proxy_policy2opt(policy, 0 /*!do_ipv6*/, buf.buf+off, &size); + off= size; + } + } + + opt= NULL; + if (off) + { + opt = GETDNS_XMALLOC(context->my_mf, uint8_t, off); + memcpy(opt, buf.buf, off); + } + if (context->proxy_policies->policy_opts) + GETDNS_FREE(context->mf, context->proxy_policies->policy_opts); + context->proxy_policies->policy_opts= opt; + context->proxy_policies->policy_opts_size= off; +} + +#define PROXY_POLICY_UNENCRYPTED (1 << 0) +#define PROXY_POLICY_UNAUTHENTICATED_ENCRYPTION (1 << 1) +#define PROXY_POLICY_AUTHENTICATED_ENCRYPTION (1 << 2) +#define PROXY_POLICY_PKIX_AUTH_REQUIRED (1 << 3) +#define PROXY_POLICY_DANE_AUTH_REQUIRED (1 << 4) +#define PROXY_POLICY_DEFAULT_DISALLOW_OTHER_TRANSPORTS (1 << 5) +#define PROXY_POLICY_A53 (1 << 6) +#define PROXY_POLICY_D53 (1 << 7) +#define PROXY_POLICY_AT (1 << 8) +#define PROXY_POLICY_DT (1 << 9) +#define PROXY_POLICY_AH2 (1 << 10) +#define PROXY_POLICY_DH2 (1 << 11) +#define PROXY_POLICY_AH3 (1 << 12) +#define PROXY_POLICY_DH3 (1 << 13) +#define PROXY_POLICY_AQ (1 << 14) +#define PROXY_POLICY_DQ (1 << 15) + +#define PROXY_CONTROL_OPT_FLAGS1_UNENCRYPTED (1 << 15) +#define PROXY_CONTROL_OPT_FLAGS1_UNAUTHENTICATED_ENCRYPTION (1 << 14) +#define PROXY_CONTROL_OPT_FLAGS1_AUTHENTICATED_ENCRYPTION (1 << 13) +#define PROXY_CONTROL_OPT_FLAGS1_PKIX_AUTH_REQUIRED (1 << 12) +#define PROXY_CONTROL_OPT_FLAGS1_DANE_AUTH_REQUIRED (1 << 11) +#define PROXY_CONTROL_OPT_FLAGS1_DEFAULT_DISALLOW_OTHER_TRANSPORTS (1 << 10) + +#define PROXY_CONTROL_OPT_FLAGS2_A53 (1 << 15) +#define PROXY_CONTROL_OPT_FLAGS2_D53 (1 << 14) +#define PROXY_CONTROL_OPT_FLAGS2_AT (1 << 13) +#define PROXY_CONTROL_OPT_FLAGS2_DT (1 << 12) +#define PROXY_CONTROL_OPT_FLAGS2_AH2 (1 << 11) +#define PROXY_CONTROL_OPT_FLAGS2_DH2 (1 << 10) +#define PROXY_CONTROL_OPT_FLAGS2_AH3 (1 << 9) +#define PROXY_CONTROL_OPT_FLAGS2_DH3 (1 << 8) +#define PROXY_CONTROL_OPT_FLAGS2_AQ (1 << 7) +#define PROXY_CONTROL_OPT_FLAGS2_DQ (1 << 6) + +static struct proxy_control_flags +{ + unsigned policy_flags; + uint16_t control_flags; +} proxy_control_flags1[] = { + { PROXY_POLICY_UNENCRYPTED, PROXY_CONTROL_OPT_FLAGS1_UNENCRYPTED }, + { PROXY_POLICY_UNAUTHENTICATED_ENCRYPTION, PROXY_CONTROL_OPT_FLAGS1_UNAUTHENTICATED_ENCRYPTION }, + { PROXY_POLICY_AUTHENTICATED_ENCRYPTION, PROXY_CONTROL_OPT_FLAGS1_AUTHENTICATED_ENCRYPTION }, + { PROXY_POLICY_PKIX_AUTH_REQUIRED, PROXY_CONTROL_OPT_FLAGS1_PKIX_AUTH_REQUIRED }, + { PROXY_POLICY_DANE_AUTH_REQUIRED, PROXY_CONTROL_OPT_FLAGS1_DANE_AUTH_REQUIRED }, + { PROXY_POLICY_DEFAULT_DISALLOW_OTHER_TRANSPORTS, PROXY_CONTROL_OPT_FLAGS1_DEFAULT_DISALLOW_OTHER_TRANSPORTS }, + { 0, 0 } +}, proxy_control_flags2[] = { + { PROXY_POLICY_A53, PROXY_CONTROL_OPT_FLAGS2_A53 }, + { PROXY_POLICY_D53, PROXY_CONTROL_OPT_FLAGS2_D53 }, + { PROXY_POLICY_AT, PROXY_CONTROL_OPT_FLAGS2_AT }, + { PROXY_POLICY_DT, PROXY_CONTROL_OPT_FLAGS2_DT }, + { PROXY_POLICY_AH2, PROXY_CONTROL_OPT_FLAGS2_AH2 }, + { PROXY_POLICY_DH2, PROXY_CONTROL_OPT_FLAGS2_DH2 }, + { PROXY_POLICY_AH3, PROXY_CONTROL_OPT_FLAGS2_AH3 }, + { PROXY_POLICY_DH3, PROXY_CONTROL_OPT_FLAGS2_DH3 }, + { PROXY_POLICY_AQ, PROXY_CONTROL_OPT_FLAGS2_AQ }, + { PROXY_POLICY_DQ, PROXY_CONTROL_OPT_FLAGS2_DQ }, + { 0, 0 } +}; + +#define SVC_KEY_ALPN 1 + +/* Note: this table must be kept sort based on 'value' */ +struct +{ + uint16_t value; + char *key; +} svckeys[] = +{ + { SVC_KEY_ALPN, "alpn" }, /* 1 */ + { 0, NULL } +}; + +static void proxy_policy2opt(getdns_proxy_policy *policy, int do_ipv6, + uint8_t *buf, size_t *sizep) +{ + uint8_t inflen; + uint16_t flags1, flags2, key16, len16; + int i, j, addr_count; + ptrdiff_t len, totlen; + size_t datalen; + char *l, *dot, *key, *v, *vp, *comma; + uint8_t *bp, *addr_type, *addr_len, *addrp, *domainlenp, + *domainp, *svclenp, *svcp, *inflenp, *infp, *lastp, *endp, + *datap, *wire_keyp, *wire_lenp, *wire_datap; + uint16_t *codep, *lenp, *flags1p, *flags2p; + struct sockaddr_in *sin4p; + struct sockaddr_in6 *sin6p; + uint8_t wirebuf[256]; + + endp= buf + *sizep; + + codep= (uint16_t *)buf; + *codep= htons(GLDNS_EDNS_PROXY_CONTROL); + lenp= &codep[1]; + flags1p= &lenp[1]; + flags2p= &flags1p[1]; + + addr_type= (uint8_t *)&flags2p[1]; + addr_len= &addr_type[1]; + + /* Flags1 */ + flags1 = 0; + for (i= 0; proxy_control_flags1[i].policy_flags != 0; i++) + { + if (policy->flags & proxy_control_flags1[i].policy_flags) + flags1 |= proxy_control_flags1[i].control_flags; + } + *flags1p= htons(flags1); + + /* Flags2 */ + flags2 = 0; + for (i= 0; proxy_control_flags2[i].policy_flags != 0; i++) + { + if (policy->flags & proxy_control_flags2[i].policy_flags) + flags2 |= proxy_control_flags2[i].control_flags; + } + *flags2p= htons(flags2); + + /* Count the number of addresses to include */ + addr_count= 0; + for (i= 0; iaddr_count; i++) + { + if (!do_ipv6 && policy->addrs[i].ss_family == AF_INET) + addr_count++; + if (do_ipv6 && policy->addrs[i].ss_family == AF_INET6) + addr_count++; + } + + if (addr_count) + { + if (do_ipv6) + { + *addr_type = 2; + *addr_len = addr_count * sizeof(struct in6_addr); + addrp= &addr_len[1]; + if (endp - addrp < *addr_len) + { + fprintf(stderr, + "proxy_policy2opt: not enogh space\n"); + abort(); + } + for (i= 0; iaddr_count; i++) + { + if (policy->addrs[i].ss_family != AF_INET6) + continue; + sin6p = (struct sockaddr_in6 *) + &policy->addrs[i]; + memcpy(addrp, &sin6p->sin6_addr, + sizeof(sin6p->sin6_addr)); + addrp += sizeof(sin6p->sin6_addr); + } + } + else + { + *addr_type = 1; + *addr_len = addr_count * sizeof(struct in_addr); + addrp= &addr_len[1]; + if (endp - addrp < *addr_len) + { + fprintf(stderr, + "proxy_policy2opt: not enogh space\n"); + abort(); + } + for (i= 0; iaddr_count; i++) + { + if (policy->addrs[i].ss_family != AF_INET) + continue; + sin4p = (struct sockaddr_in *) + &policy->addrs[i]; + memcpy(addrp, &sin4p->sin_addr, + sizeof(sin4p->sin_addr)); + addrp += sizeof(sin4p->sin_addr); + } + } + assert (addrp - &addr_len[1] == *addr_len); + domainlenp= addrp; + } + else + { + *addr_type = 0; + *addr_len = 0; + domainlenp= &addr_len[1]; + } + + if (endp - domainlenp < 1) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + *domainlenp = 0; + domainp= &domainlenp[1]; + if (policy->domainname) + { + l= policy->domainname; + while(l) + { + dot= strchr(l, '.'); + if (dot) + len= dot-l; + else + len= strlen(l); + if (len > 63) + { + fprintf(stderr, + "proxy_policy2opt: bad label length\n"); + abort(); + } + if (endp - domainlenp < 1) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + *domainp= len; + domainp++; + if (endp - domainlenp < len) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + memcpy(domainp, l, len); + domainp += len; + + if (!dot) + break; + l= dot+1; + if (l[0] == '\0') + break; + } + + /* Add trailing label */ + if (endp - domainlenp < 1) + { + fprintf(stderr, "proxy_policy2opt: not enough space\n"); + abort(); + } + *domainp= 0; + domainp++; + + *domainlenp = domainp - &domainlenp[1]; + } + + svclenp = domainp; + if (endp - svclenp < 1) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + *svclenp = 0; + svcp = &svclenp[1]; + if (policy->svcparams[0].key != NULL) + { + for (i = 0; svckeys[i].key != NULL; i++) + { + key = svckeys[i].key; + for (j = 0; jsvcparams[j].key == NULL) + break; + if (strcmp(key, policy->svcparams[j].key) == 0) + break; + } + if (j >= POLICY_N_SVCPARAMS || + policy->svcparams[j].key == NULL) + { + /* Key not needed */ + continue; + } + + switch(svckeys[i].value) + { + case SVC_KEY_ALPN: + v = policy->svcparams[j].value; + if (v == NULL) + { + fprintf(stderr, + "proxy_policy2opt: value expected for alpn\n"); + abort(); + } + if (strlen(v) + 1 > sizeof(wirebuf)) + { + fprintf(stderr, + "proxy_policy2opt: alpn list too long\n"); + abort(); + } + vp= v; + bp= wirebuf; + while (vp) + { + comma = strchr(vp, ','); + if (comma) + { + len = comma - vp; + *bp = len; + bp++; + memcpy(bp, vp, len); + bp += len; + vp = comma + 1; + continue; + } + len = strlen(vp); + *bp = len; + bp++; + memcpy(bp, vp, len); + bp += len; + break;; + } + datap = wirebuf; + datalen = bp-wirebuf; + break; + + default: + fprintf(stderr, + "proxy_policy2opt: unknown svc key value\n"); + abort(); + } + + totlen = 4 + datalen; + if (endp - svcp < totlen) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + wire_keyp = svcp; + wire_lenp = &wire_keyp[2]; + wire_datap = &wire_lenp[2]; + + key16 = htons(svckeys[i].value); + memcpy(wire_keyp, &key16, 2); + len16 = htons(datalen); + memcpy(wire_lenp, &len16, 2); + if (datalen) + memcpy(wire_datap, datap, datalen); + svcp = wire_datap + datalen; + } + } + len = svcp - &svclenp[1]; + if (len > 255) + { + fprintf(stderr, "svc too large\n"); + abort(); + } + *svclenp = len; + + inflenp = svcp; + if (endp - inflenp < 1) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + + inflen = 0; + if (policy->interface) + { + inflen= strlen(policy->interface); + } + *inflenp = inflen; + infp= &inflenp[1]; + if (inflen) + { + if (endp - infp < inflen) + { + fprintf(stderr, + "proxy_policy2opt: not enough space\n"); + abort(); + } + memcpy(infp, policy->interface, inflen); + } +fprintf(stderr, "inflen %d, interface %s\n", inflen, policy->interface); + + lastp = &infp[inflen]; + + *lenp = htons(lastp - (uint8_t *)flags1p); + *sizep= lastp - buf; +} + + +static struct policy_flags +{ + char *name; + unsigned value; +} policy_flags[] = { + { "unencrypted", PROXY_POLICY_UNENCRYPTED }, + { "unauthenticated-encryption", PROXY_POLICY_UNAUTHENTICATED_ENCRYPTION }, + { "authenticated-encryption", PROXY_POLICY_AUTHENTICATED_ENCRYPTION }, + { "pkix-auth-required", PROXY_POLICY_PKIX_AUTH_REQUIRED }, + { "dane-auth-required", PROXY_POLICY_DANE_AUTH_REQUIRED }, + { "default-disallow-other-transports", PROXY_POLICY_DEFAULT_DISALLOW_OTHER_TRANSPORTS }, + { "allow-do53", PROXY_POLICY_A53 }, + { "disallow-do53", PROXY_POLICY_D53 }, + { "allow-dot", PROXY_POLICY_AT }, + { "disallow-dot", PROXY_POLICY_DT }, + { "allow-doh2", PROXY_POLICY_AH2 }, + { "disallow-doh2", PROXY_POLICY_DH2 }, + { "allow-doh3", PROXY_POLICY_AH3 }, + { "disallow-doh3", PROXY_POLICY_DH3 }, + { "allow-doq", PROXY_POLICY_AQ }, + { "disallow-doq", PROXY_POLICY_DQ }, + { NULL, 0 } +}; + +getdns_return_t +getdns_context_set_local_proxy_policy(getdns_context *context, + getdns_dict *proxy_policy) +{ + getdns_return_t r; + size_t count = 0, addr_count; + size_t i, j; + getdns_proxy_policies *policies; + getdns_list *resolvers; + struct sockaddr_in6 *sin6p; + +fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); + + RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if (!proxy_policy) { + _getdns_proxy_policies_dereference(context->proxy_policies); + context->proxy_policies = NULL; + update_proxy_policy_opts(context); + dispatch_updated(context, + GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS); + return GETDNS_RETURN_GOOD; + } + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if ((r = getdns_dict_get_list(proxy_policy, "resolvers", + &resolvers))) { + goto error; + } + if ((r = getdns_list_get_length(resolvers, &count))) { + goto error; + } + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if (count == 0) { + _getdns_upstreams_dereference(context->upstreams); + context->upstreams = NULL; + dispatch_updated(context, + GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS); + return GETDNS_RETURN_GOOD; + } + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + policies = proxy_policies_create( context, count); + for (i = 0; i < count; i++) { + uint32_t u32; + size_t svccount; + char *key; + char *value; + getdns_dict *dict; + getdns_dict *addr_dict; + getdns_dict *svcparams; + getdns_list *addrs; + getdns_list *svcnames; + getdns_bindata *addr_type; + getdns_bindata *addr_data; + getdns_bindata *domain_name; + getdns_bindata *interface; + getdns_bindata *bindata; + + if ((r = getdns_list_get_dict(resolvers, i, &dict))) { + dict = NULL; + goto error; + } + /* flags */ + for (j= 0; policy_flags[j].name != NULL; j++) + { + if ((r = getdns_dict_get_int(dict, + policy_flags[j].name, &u32)) == + GETDNS_RETURN_GOOD && u32 == 1) + policies->policies[i].flags |= policy_flags[j].value; + } + + /* addrs */ +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if ((r = getdns_dict_get_list( + dict, "addrs", &addrs)) == GETDNS_RETURN_GOOD) { +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if ((r = getdns_list_get_length(addrs, &addr_count))) + goto error; +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if (addr_count > POLICY_N_ADDR) + goto error; + policies->policies[i].addr_count = addr_count; +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + for (j = 0; j < addr_count; j++) { +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if ((r = getdns_list_get_dict(addrs, j, + &addr_dict))) { + goto error; + } +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if ((r = getdns_dict_get_bindata(addr_dict, + "address-type", &addr_type))) { + goto error; + } +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if ((r = getdns_dict_get_bindata(addr_dict, + "address-data", &addr_data))) { + goto error; + } +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + if (addr_type->size == 4 && + memcmp(addr_type->data, "IPv4", 4) + == 0) { + if (addr_data->size != 4) + goto error; +fprintf(stderr, "getdns_context_set_local_proxy_policy: should store IPv4 address\n"); + } + else if (addr_type->size == 4 && + memcmp(addr_type->data, "IPv6", 4) + == 0) { + if (addr_data->size != 16) + goto error; + sin6p= (struct sockaddr_in6 *) + &policies->policies[i].addrs[j]; + sin6p->sin6_family= AF_INET6; + memcpy(&sin6p->sin6_addr, + addr_data->data, + sizeof(sin6p->sin6_addr)); + } + else + goto error; + } + } + else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) + goto error; + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + /* domain-name */ + if ((r = getdns_dict_get_bindata( + dict, "domain-name", &domain_name)) == GETDNS_RETURN_GOOD) { + policies->policies[i].domainname= + GETDNS_XMALLOC(context->mf, + char, domain_name->size+1); + memcpy(policies->policies[i].domainname, + domain_name->data, domain_name->size); + policies->policies[i].domainname[domain_name->size]= + '\0'; + } + else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) + goto error; + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + + /* svcparams */ + if ((r = getdns_dict_get_dict( + dict, "svcparams", &svcparams)) == GETDNS_RETURN_GOOD) { + getdns_dict_get_names(svcparams, &svcnames); + getdns_list_get_length(svcnames, &svccount); + if (svccount > POLICY_N_SVCPARAMS) + goto error; + for (j= 0; jmf, bindata); + policies->policies[i].svcparams[j].key = key; +fprintf(stderr, "getdns_context_set_local_proxy_policy: key %s\n", key); + if (getdns_dict_get_int(svcparams, key, &u32) + == GETDNS_RETURN_GOOD) + { + /* Keywoard without value */ + if (u32 != 1) + goto error; + policies->policies[i].svcparams[j]. + value = NULL; + } + else if (getdns_dict_get_bindata(svcparams, + key, &bindata) == GETDNS_RETURN_GOOD) + { + value = _getdns_strdup2(&context->mf, + bindata); + policies->policies[i].svcparams[j]. + value = value; +fprintf(stderr, "getdns_context_set_local_proxy_policy: value %s\n", value); + } + else + goto error; + } + } + else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) + { + fprintf(stderr, "getdns_context_set_local_proxy_policy: r = %d\n", r); +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + goto error; + } + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + /* interface */ + if ((r = getdns_dict_get_bindata( + dict, "interface", &interface)) == GETDNS_RETURN_GOOD) { + policies->policies[i].interface= + GETDNS_XMALLOC(context->mf, + char, interface->size+1); + memcpy(policies->policies[i].interface, + interface->data, interface->size); + policies->policies[i].interface[interface->size]= '\0'; + } + else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) + goto error; +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + } +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + _getdns_proxy_policies_dereference(context->proxy_policies); + context->proxy_policies = policies; + update_proxy_policy_opts(context); + dispatch_updated(context, + GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS); + +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + return GETDNS_RETURN_GOOD; + +#if 0 +invalid_parameter: + _getdns_proxy_policies_dereference(policies); + return GETDNS_RETURN_INVALID_PARAMETER; +#endif +error: + _getdns_proxy_policies_dereference(policies); +fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); + return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; +} + /* * getdns_context_unset_edns_maximum_udp_payload_size * diff --git a/src/context.h b/src/context.h index b24f7c4e..a21368b2 100644 --- a/src/context.h +++ b/src/context.h @@ -260,6 +260,22 @@ typedef struct getdns_upstream { } getdns_upstream; +#define POLICY_N_ADDR 3 +#define POLICY_N_SVCPARAMS 8 + +typedef struct getdns_proxy_policy { + unsigned flags; + int addr_count; + struct sockaddr_storage addrs[POLICY_N_ADDR]; + char *domainname; + struct + { + char *key; + char *value; + } svcparams[POLICY_N_SVCPARAMS]; + char *interface; +} getdns_proxy_policy; + typedef struct getdns_log_config { getdns_logfunc_type func; void *userarg; @@ -280,6 +296,15 @@ typedef struct getdns_upstreams { getdns_upstream upstreams[]; } getdns_upstreams; +typedef struct getdns_proxy_policies { + struct mem_funcs mf; + size_t referenced; + size_t count; + uint8_t *policy_opts; + size_t policy_opts_size; + getdns_proxy_policy policies[]; +} getdns_proxy_policies; + typedef enum tas_state { TAS_LOOKUP_ADDRESSES = 0, TAS_WRITE_GET_XML, @@ -368,6 +393,8 @@ struct getdns_context { getdns_tls_version_t tls_min_version; getdns_tls_version_t tls_max_version; + getdns_proxy_policies *proxy_policies; + getdns_upstreams *upstreams; uint16_t limit_outstanding_queries; uint32_t dnssec_allowed_skew; diff --git a/src/getdns/getdns.h.in b/src/getdns/getdns.h.in index 6fbe0b80..8931b3db 100644 --- a/src/getdns/getdns.h.in +++ b/src/getdns/getdns.h.in @@ -1719,6 +1719,10 @@ getdns_return_t getdns_context_set_upstream_recursive_servers(getdns_context *context, getdns_list *upstream_list); +getdns_return_t +getdns_context_set_local_proxy_policy(getdns_context *context, + getdns_dict *proxy_policy); + /** * Set the maximum UDP payload size advertised in a EDNS0 OPT record. * When not set (the default), outgoing values will adhere to the suggestions diff --git a/src/gldns/rrdef.h b/src/gldns/rrdef.h index fe9bf7bf..34120af8 100644 --- a/src/gldns/rrdef.h +++ b/src/gldns/rrdef.h @@ -441,7 +441,9 @@ enum gldns_enum_edns_option GLDNS_EDNS_KEEPALIVE = 11, /* draft-ietf-dnsop-edns-tcp-keepalive*/ GLDNS_EDNS_PADDING = 12, /* RFC7830 */ GLDNS_EDNS_EDE = 15, /* RFC8914 */ - GLDNS_EDNS_CLIENT_TAG = 16 /* draft-bellis-dnsop-edns-tags-01 */ + GLDNS_EDNS_CLIENT_TAG = 16, /* draft-bellis-dnsop-edns-tags-01 */ + + GLDNS_EDNS_PROXY_CONTROL = 42 /* XXX unassigned */ }; typedef enum gldns_enum_edns_option gldns_edns_option; diff --git a/src/request-internal.c b/src/request-internal.c index 4fba325e..8002750e 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -403,6 +403,51 @@ _getdns_network_req_add_upstream_option(getdns_network_req * req, uint16_t code, return GETDNS_RETURN_GOOD; } +/* add_upstream_options appends multiple options. */ +getdns_return_t +_getdns_network_req_add_upstream_options(getdns_network_req * req, const void* data, size_t sz) +{ + uint16_t oldlen; + uint32_t newlen; + uint32_t pktlen; + size_t cur_upstream_option_sz; + + /* if no options are set, we can't add upstream options */ + if (!req->opt) + return GETDNS_RETURN_GENERIC_ERROR; + + /* if TCP, no overflow allowed for length field + https://tools.ietf.org/html/rfc1035#section-4.2.2 */ + pktlen = req->response - req->query; + pktlen += sz; + if (pktlen > UINT16_MAX) + return GETDNS_RETURN_GENERIC_ERROR; + + /* no overflow allowed for OPT size either (maybe this is overkill + given the above check?) */ + oldlen = gldns_read_uint16(req->opt + 9); + newlen = oldlen + sz; + if (newlen > UINT16_MAX) + return GETDNS_RETURN_GENERIC_ERROR; + + /* avoid overflowing MAXIMUM_UPSTREAM_OPTION_SPACE */ + cur_upstream_option_sz = (size_t)oldlen - req->base_query_option_sz; + if (cur_upstream_option_sz + sz > MAXIMUM_UPSTREAM_OPTION_SPACE) + return GETDNS_RETURN_GENERIC_ERROR; + + /* actually add the option: */ + memcpy(req->opt + 11 + oldlen, data, sz); + gldns_write_uint16(req->opt + 9, newlen); + + /* the response should start right after the options end: */ + req->response = req->opt + 11 + newlen; + + /* for TCP, adjust the size of the wire format itself: */ + gldns_write_uint16(req->query - 2, pktlen); + + return GETDNS_RETURN_GOOD; +} + size_t _getdns_network_req_add_tsig(getdns_network_req *req) { diff --git a/src/stub.c b/src/stub.c index ee319312..10d11e2e 100644 --- a/src/stub.c +++ b/src/stub.c @@ -177,6 +177,15 @@ attach_edns_cookie(getdns_network_req *req) req, EDNS_COOKIE_OPCODE, 8, req->client_cookie); } +static getdns_return_t +attach_proxy_policies_opt(getdns_network_req *req) +{ +fprintf(stderr, "attach_proxy_policies_opt: adding option\n"); + return _getdns_network_req_add_upstream_options(req, + req->owner->context->proxy_policies->policy_opts, + req->owner->context->proxy_policies->policy_opts_size); +} + /* Will find a matching OPT RR, but leaves the caller to validate it */ #define MATCH_OPT_FOUND 2 @@ -845,6 +854,7 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) intptr_t query_id_intptr; int q = tcp_connected(netreq->upstream); +fprintf(stderr, "in stub_tcp_write\n"); if (q != 0) return q; @@ -875,6 +885,10 @@ stub_tcp_write(int fd, getdns_tcp_state *tcp, getdns_network_req *netreq) if (netreq->owner->edns_client_subnet_private) if (attach_edns_client_subnet_private(netreq)) return STUB_OUT_OF_OPTIONS; +fprintf(stderr, "stub_tcp_write: before proxy_policies check\n"); + if (netreq->owner->context->proxy_policies) + if (attach_proxy_policies_opt(netreq)) + return STUB_OUT_OF_OPTIONS; if (netreq->upstream->queries_sent == 0 && netreq->owner->context->idle_timeout != 0) { /* Add the keepalive option to the first query on this connection*/ @@ -1314,6 +1328,7 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, int q; +fprintf(stderr, "in stub_tls_write\n"); if (netreq->owner->expires > upstream->expires) upstream->expires = netreq->owner->expires; @@ -1569,6 +1584,7 @@ stub_udp_write_cb(void *userarg) ssize_t written; DEBUG_STUB("%s %-35s: MSG: %p \n", STUB_DEBUG_WRITE, __FUNC__, (void *)netreq); +fprintf(stderr, "in stub_udp_write_cb\n"); GETDNS_CLEAR_EVENT(dnsreq->loop, &netreq->event); @@ -1588,6 +1604,9 @@ stub_udp_write_cb(void *userarg) if (netreq->owner->edns_client_subnet_private) if (attach_edns_client_subnet_private(netreq)) return; /* too many upstream options */ + if (netreq->owner->context->proxy_policies) + if (attach_proxy_policies_opt(netreq)) + return; /* too many upstream options */ } pkt_len = _getdns_network_req_add_tsig(netreq); if ((ssize_t)pkt_len != (written = sendto( diff --git a/src/types-internal.h b/src/types-internal.h index 302595ce..5c7ac74a 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -445,6 +445,8 @@ void _getdns_dns_req_free(getdns_dns_req * req); /* network request utils */ getdns_return_t _getdns_network_req_add_upstream_option(getdns_network_req * req, uint16_t code, uint16_t sz, const void* data); +getdns_return_t _getdns_network_req_add_upstream_options(getdns_network_req * req, + const void* data, size_t sz); void _getdns_network_req_clear_upstream_options(getdns_network_req * req); /* Adds TSIG signature (if needed) and returns query length */ From d1a9c5195437a703f14b20ef6e9800d4519abd86 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 18 Aug 2022 12:07:52 +0200 Subject: [PATCH 02/14] Changes for stubby --- stubby | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubby b/stubby index d4eff9bf..edb3d27f 160000 --- a/stubby +++ b/stubby @@ -1 +1 @@ -Subproject commit d4eff9bf415a968b1849c99720d51af98a10bdee +Subproject commit edb3d27fa51c1fd5aedae1b473bed20707de76cd From af87d9f70f07522a4934c967b3cb1c1f9a088781 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 18 Aug 2022 13:59:09 +0200 Subject: [PATCH 03/14] Remove some debug output, fix a small bug --- src/context.c | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/context.c b/src/context.c index 7b3fd3a1..3a477b90 100644 --- a/src/context.c +++ b/src/context.c @@ -669,13 +669,6 @@ proxy_policies_create(getdns_context *context, size_t count) sizeof(getdns_proxy_policies) + sizeof(getdns_proxy_policy) * count); -#if 0 -fprintf(stderr, "proxy_policies_create: alloc %d + %d * %d = %d\n", - sizeof(getdns_proxy_policies), count, sizeof(getdns_proxy_policy), - sizeof(getdns_proxy_policies) + - sizeof(getdns_proxy_policy) * count); -#endif - if (r) { r->mf = context->mf; r->referenced = 1; @@ -3602,7 +3595,6 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if (!proxy_policy) { _getdns_proxy_policies_dereference(context->proxy_policies); context->proxy_policies = NULL; @@ -3612,7 +3604,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); return GETDNS_RETURN_GOOD; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if ((r = getdns_dict_get_list(proxy_policy, "resolvers", &resolvers))) { goto error; @@ -3621,7 +3612,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); goto error; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if (count == 0) { _getdns_upstreams_dereference(context->upstreams); context->upstreams = NULL; @@ -3630,7 +3620,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); return GETDNS_RETURN_GOOD; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); policies = proxy_policies_create( context, count); for (i = 0; i < count; i++) { uint32_t u32; @@ -3653,6 +3642,7 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); goto error; } /* flags */ + policies->policies[i].flags = 0; for (j= 0; policy_flags[j].name != NULL; j++) { if ((r = getdns_dict_get_int(dict, @@ -3662,40 +3652,31 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); } /* addrs */ -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if ((r = getdns_dict_get_list( dict, "addrs", &addrs)) == GETDNS_RETURN_GOOD) { -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if ((r = getdns_list_get_length(addrs, &addr_count))) goto error; -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if (addr_count > POLICY_N_ADDR) goto error; policies->policies[i].addr_count = addr_count; -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); for (j = 0; j < addr_count; j++) { -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if ((r = getdns_list_get_dict(addrs, j, &addr_dict))) { goto error; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if ((r = getdns_dict_get_bindata(addr_dict, "address-type", &addr_type))) { goto error; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if ((r = getdns_dict_get_bindata(addr_dict, "address-data", &addr_data))) { goto error; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); if (addr_type->size == 4 && memcmp(addr_type->data, "IPv4", 4) == 0) { if (addr_data->size != 4) goto error; -fprintf(stderr, "getdns_context_set_local_proxy_policy: should store IPv4 address\n"); } else if (addr_type->size == 4 && memcmp(addr_type->data, "IPv6", 4) @@ -3716,7 +3697,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: should store IPv4 addres else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) goto error; -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); /* domain-name */ if ((r = getdns_dict_get_bindata( dict, "domain-name", &domain_name)) == GETDNS_RETURN_GOOD) { @@ -3731,7 +3711,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) goto error; -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); /* svcparams */ if ((r = getdns_dict_get_dict( @@ -3745,7 +3724,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); getdns_list_get_bindata(svcnames, j, &bindata); key = _getdns_strdup2(&context->mf, bindata); policies->policies[i].svcparams[j].key = key; -fprintf(stderr, "getdns_context_set_local_proxy_policy: key %s\n", key); if (getdns_dict_get_int(svcparams, key, &u32) == GETDNS_RETURN_GOOD) { @@ -3762,7 +3740,6 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: key %s\n", key); bindata); policies->policies[i].svcparams[j]. value = value; -fprintf(stderr, "getdns_context_set_local_proxy_policy: value %s\n", value); } else goto error; @@ -3771,11 +3748,9 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: value %s\n", value); else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) { fprintf(stderr, "getdns_context_set_local_proxy_policy: r = %d\n", r); -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); goto error; } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); /* interface */ if ((r = getdns_dict_get_bindata( dict, "interface", &interface)) == GETDNS_RETURN_GOOD) { @@ -3788,16 +3763,13 @@ fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); } else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) goto error; -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); } -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); _getdns_proxy_policies_dereference(context->proxy_policies); context->proxy_policies = policies; update_proxy_policy_opts(context); dispatch_updated(context, GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS); -fprintf(stderr, "getdns_context_set_local_proxy_policy: line %d\n", __LINE__); return GETDNS_RETURN_GOOD; #if 0 From c8efb196247519606561865e662c3dc5472c5355 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Fri, 2 Sep 2022 10:03:46 +0200 Subject: [PATCH 04/14] More auth_status in "call_reporting dict" Key "tls_auth_pin" == 1 when a pin from a pinset is used to authenticate the tls session, 0 otherwise Key "tls_auth_pkix" == 1 when the cert was signed with a CA in the verification location, 0 if it was not PKIX authenticated and 2 if unkown (for example when a pinset was sufficient to authenticate the session) --- src/gnutls/tls.c | 17 +++++++++++++++++ src/openssl/tls.c | 20 ++++++++++++++++++++ src/request-internal.c | 5 +++++ src/stub.c | 2 ++ src/tls.h | 16 ++++++++++++++++ src/types-internal.h | 3 +++ src/util-internal.c | 12 ++++++++++++ 7 files changed, 75 insertions(+) diff --git a/src/gnutls/tls.c b/src/gnutls/tls.c index aef951df..3623810e 100644 --- a/src/gnutls/tls.c +++ b/src/gnutls/tls.c @@ -536,6 +536,23 @@ const char* _getdns_tls_connection_get_version(_getdns_tls_connection* conn) return gnutls_protocol_get_name(gnutls_protocol_get_version(conn->tls)); } +/* CBN:TODO Implement! */ +int _getdns_tls_connection_get_pkix_auth(_getdns_tls_connection* conn) +{ + if (!conn || !conn->ssl) + return 0; + + return 2 /* 2 is unknown */; +} + +/* CBN:TODO Implement! */ +int _getdns_tls_connection_get_pin_auth(_getdns_tls_connection* conn) +{ + if (!conn || !conn->ssl) + return 0; + return 0; +} + getdns_return_t _getdns_tls_connection_do_handshake(_getdns_tls_connection* conn) { int r; diff --git a/src/openssl/tls.c b/src/openssl/tls.c index ffabb201..f58cb013 100644 --- a/src/openssl/tls.c +++ b/src/openssl/tls.c @@ -842,6 +842,26 @@ const char* _getdns_tls_connection_get_version(_getdns_tls_connection* conn) return SSL_get_version(conn->ssl); } +int _getdns_tls_connection_get_pkix_auth(_getdns_tls_connection* conn) +{ + uint8_t usage = 255; /* 0 and 1 for also PKIX, 2 and 3 for DANE only */ + + if (!conn || !conn->ssl) + return 0; + + if (SSL_get0_dane_tlsa(conn->ssl, &usage, NULL, NULL, NULL, NULL) < 0) + return SSL_get_verify_result(conn->ssl) == X509_V_OK ? 1 : 0; + + return usage <= 1 ? 1 : 2 /* 2 is unknown */; +} + +int _getdns_tls_connection_get_pin_auth(_getdns_tls_connection* conn) +{ + if (!conn || !conn->ssl) + return 0; + return SSL_get0_dane_authority(conn->ssl, NULL, NULL) >= 0; +} + getdns_return_t _getdns_tls_connection_do_handshake(_getdns_tls_connection* conn) { int r; diff --git a/src/request-internal.c b/src/request-internal.c index 8002750e..39e65038 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -218,6 +218,11 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->debug_tls_peer_cert.size = 0; net_req->debug_tls_peer_cert.data = NULL; net_req->debug_tls_version = NULL; + net_req->debug_pkix_auth = 0; /* 1 == authenticated with PKIX + * 0 == not authenticated with PKIX + * 2 == unknown + */ + net_req->debug_pin_auth = 0; /* == 1 if authenticated with pinset */ net_req->debug_udp = 0; /* Scheduling, touch only via _getdns_netreq_change_state! diff --git a/src/stub.c b/src/stub.c index 10d11e2e..af801ba0 100644 --- a/src/stub.c +++ b/src/stub.c @@ -1853,6 +1853,8 @@ upstream_write_cb(void *userarg) _getdns_tls_x509_free(&upstream->upstreams->mf, cert); } netreq->debug_tls_version = _getdns_tls_connection_get_version(netreq->upstream->tls_obj); + netreq->debug_pkix_auth = _getdns_tls_connection_get_pkix_auth(netreq->upstream->tls_obj); + netreq->debug_pin_auth = _getdns_tls_connection_get_pin_auth(netreq->upstream->tls_obj); } /* Need this because auth status is reset on connection close */ netreq->debug_tls_auth_status = netreq->upstream->tls_auth_state; diff --git a/src/tls.h b/src/tls.h index 567d5137..5662e362 100644 --- a/src/tls.h +++ b/src/tls.h @@ -265,6 +265,22 @@ _getdns_tls_session* _getdns_tls_connection_get_session(struct mem_funcs* mfs, _ */ const char* _getdns_tls_connection_get_version(_getdns_tls_connection* conn); +/** + * Return whether or not the peer cert PKIX validated + * + * @param conn the connection + * @return 1 when the peer cert PKIX validated, 0 if it did not validate, 2 otherwise + */ +int _getdns_tls_connection_get_pkix_auth(_getdns_tls_connection* conn); + +/** + * Return whether or not a pin from the pinset matched + * + * @param conn the connection + * @return 1 when the peer cert matched a pinset, 0 otherwise + */ +int _getdns_tls_connection_get_pin_auth(_getdns_tls_connection* conn); + /** * Attempt TLS handshake. * diff --git a/src/types-internal.h b/src/types-internal.h index 5c7ac74a..a7e20eda 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -244,6 +244,9 @@ typedef struct getdns_network_req const char *debug_tls_version; /* Some booleans */ + unsigned debug_pkix_auth: 2; /* 1 if TLS connection is PKIX valid + 2 if this is unknown */ + unsigned debug_pin_auth : 1; /* 1 if one of the pinset's matched */ unsigned debug_udp : 1; unsigned keepalive_sent : 1; unsigned badcookie_retry: 1; diff --git a/src/util-internal.c b/src/util-internal.c index 3b6971d8..f009d1cc 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -966,6 +966,18 @@ _getdns_create_call_reporting_dict( getdns_dict_destroy(netreq_debug); return NULL; } + if (getdns_dict_set_int(netreq_debug, "tls_auth_pin", + netreq->debug_pin_auth)) { + + getdns_dict_destroy(netreq_debug); + return NULL; + } + if (getdns_dict_set_int(netreq_debug, "tls_auth_pkix", + netreq->debug_pkix_auth)) { + + getdns_dict_destroy(netreq_debug); + return NULL; + } if (getdns_dict_util_set_string(netreq_debug, "tls_version", netreq->debug_tls_version)){ From 291ecdae6400ffd3ad66481d26f5350742e5db8c Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Wed, 31 Aug 2022 15:14:41 +0200 Subject: [PATCH 05/14] Support for IPv4 --- src/context.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/context.c b/src/context.c index 3a477b90..62dacc94 100644 --- a/src/context.c +++ b/src/context.c @@ -3589,6 +3589,7 @@ getdns_context_set_local_proxy_policy(getdns_context *context, size_t i, j; getdns_proxy_policies *policies; getdns_list *resolvers; + struct sockaddr_in *sin4p; struct sockaddr_in6 *sin6p; fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); @@ -3677,6 +3678,12 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); == 0) { if (addr_data->size != 4) goto error; + sin4p= (struct sockaddr_in *) + &policies->policies[i].addrs[j]; + sin4p->sin_family= AF_INET; + memcpy(&sin4p->sin_addr, + addr_data->data, + sizeof(sin4p->sin_addr)); } else if (addr_type->size == 4 && memcmp(addr_type->data, "IPv6", 4) From 560053f3e3f4275d35b2e85639ca7abdedb68706 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 2 Sep 2022 16:19:26 +0200 Subject: [PATCH 06/14] Clear domainname. --- src/context.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context.c b/src/context.c index 62dacc94..742eb418 100644 --- a/src/context.c +++ b/src/context.c @@ -3705,6 +3705,7 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); goto error; /* domain-name */ + policies->policies[i].domainname = NULL; if ((r = getdns_dict_get_bindata( dict, "domain-name", &domain_name)) == GETDNS_RETURN_GOOD) { policies->policies[i].domainname= From fe8dcd4aab08d1009a4cd80c64d42c9bfe83b2eb Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Mon, 5 Sep 2022 12:24:00 +0200 Subject: [PATCH 07/14] Produce call_reporting for errors too --- src/const-info.c | 2 ++ src/dict.c | 6 ++++- src/general.c | 2 ++ src/getdns/getdns_extra.h.in | 10 ++++++++ src/stub.c | 49 +++++++++++++++++++++++++----------- src/util-internal.c | 45 +++++++++++++++++++-------------- 6 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/const-info.c b/src/const-info.c index 3c1bc073..14dfe8e6 100644 --- a/src/const-info.c +++ b/src/const-info.c @@ -109,6 +109,7 @@ static struct const_info consts_info[] = { { 902, "GETDNS_RESPSTATUS_ALL_TIMEOUT", GETDNS_RESPSTATUS_ALL_TIMEOUT_TEXT }, { 903, "GETDNS_RESPSTATUS_NO_SECURE_ANSWERS", GETDNS_RESPSTATUS_NO_SECURE_ANSWERS_TEXT }, { 904, "GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS", GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS_TEXT }, + { 950, "GETDNS_RESPSTATUS_ALL_ERRED", GETDNS_RESPSTATUS_ALL_ERRED_TEXT }, { 1000, "GETDNS_EXTENSION_TRUE", GETDNS_EXTENSION_TRUE_TEXT }, { 1001, "GETDNS_EXTENSION_FALSE", GETDNS_EXTENSION_FALSE_TEXT }, { 1100, "GETDNS_BAD_DNS_CNAME_IN_TARGET", GETDNS_BAD_DNS_CNAME_IN_TARGET_TEXT }, @@ -272,6 +273,7 @@ static struct const_name_info consts_name_info[] = { { "GETDNS_RESOLUTION_RECURSING", 521 }, { "GETDNS_RESOLUTION_STUB", 520 }, { "GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS", 904 }, + { "GETDNS_RESPSTATUS_ALL_ERRED", 950 }, { "GETDNS_RESPSTATUS_ALL_TIMEOUT", 902 }, { "GETDNS_RESPSTATUS_GOOD", 900 }, { "GETDNS_RESPSTATUS_NO_NAME", 901 }, diff --git a/src/dict.c b/src/dict.c index 9caee781..14430aa5 100644 --- a/src/dict.c +++ b/src/dict.c @@ -658,13 +658,17 @@ getdns_return_t getdns_dict_util_set_string(getdns_dict *dict, const char *name, const char *value) { + static const char *nill_value = ""; getdns_item *item; getdns_bindata *newbindata; getdns_return_t r; - if (!dict || !name || !value) + if (!dict || !name) return GETDNS_RETURN_INVALID_PARAMETER; + if (!value) + value = nill_value; + if (!(newbindata = _getdns_bindata_copy( &dict->mf, strlen(value) + 1, (uint8_t *)value))) return GETDNS_RETURN_MEMORY_ERROR; diff --git a/src/general.c b/src/general.c index 66964585..a941d948 100644 --- a/src/general.c +++ b/src/general.c @@ -63,6 +63,8 @@ void _getdns_call_user_callback(getdns_dns_req *dnsreq, getdns_dict *response) #if defined(REQ_DEBUG) && REQ_DEBUG debug_req(__FUNC__, *dnsreq->netreqs); #endif + if (!response && dnsreq->return_call_reporting) + response = _getdns_create_getdns_response(dnsreq); if (dnsreq->user_callback) { dnsreq->context->processing = 1; dnsreq->user_callback(dnsreq->context, diff --git a/src/getdns/getdns_extra.h.in b/src/getdns/getdns_extra.h.in index 223b1de4..b6c58645 100644 --- a/src/getdns/getdns_extra.h.in +++ b/src/getdns/getdns_extra.h.in @@ -119,6 +119,16 @@ extern "C" { /** @} */ +/** + * \addtogroup respstatus Status Codes for responses and texts + * @{ + */ + +#define GETDNS_RESPSTATUS_ALL_ERRED 950 +#define GETDNS_RESPSTATUS_ALL_ERRED_TEXT "All queries for the name erred" + +/** @} + */ /** * \defgroup versions Version values diff --git a/src/stub.c b/src/stub.c index af801ba0..545f8994 100644 --- a/src/stub.c +++ b/src/stub.c @@ -1779,13 +1779,42 @@ upstream_read_cb(void *userarg) } } +static void +netreq_equip_tls_debug_info(getdns_network_req *netreq) +{ + _getdns_tls_x509 *cert; + + if (!netreq || !netreq->upstream) + return; + netreq->debug_tls_auth_status = netreq->upstream->tls_auth_state; + if (!netreq->upstream->tls_obj) + return; + if (netreq->debug_tls_peer_cert.data) { + GETDNS_FREE( netreq->owner->my_mf + , netreq->debug_tls_peer_cert.data); + netreq->debug_tls_peer_cert.data = NULL; + netreq->debug_tls_peer_cert.size = 0; + } + if ((cert = _getdns_tls_connection_get_peer_certificate( + &netreq->owner->my_mf, netreq->upstream->tls_obj))) { + _getdns_tls_x509_to_der( &netreq->owner->my_mf, cert + , &netreq->debug_tls_peer_cert); + _getdns_tls_x509_free(&netreq->owner->my_mf, cert); + } + netreq->debug_tls_version = _getdns_tls_connection_get_version( + netreq->upstream->tls_obj); + netreq->debug_pkix_auth = _getdns_tls_connection_get_pkix_auth( + netreq->upstream->tls_obj); + netreq->debug_pin_auth = _getdns_tls_connection_get_pin_auth( + netreq->upstream->tls_obj); +} + static void upstream_write_cb(void *userarg) { getdns_upstream *upstream = (getdns_upstream *)userarg; getdns_network_req *netreq = upstream->write_queue; int q; - _getdns_tls_x509 *cert; if (!netreq) { GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); @@ -1834,6 +1863,8 @@ upstream_write_cb(void *userarg) "%-40s : Conn closed: %s - *Failure*\n", upstream->addr_str, (upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP")); + if (netreq->owner->return_call_reporting) + netreq_equip_tls_debug_info(netreq); if (fallback_on_write(netreq) == STUB_TCP_ERROR) { /* TODO: Need new state to report transport unavailable*/ _getdns_netreq_change_state(netreq, NET_REQ_ERRORED); @@ -1844,20 +1875,8 @@ upstream_write_cb(void *userarg) default: /* Unqueue the netreq from the write_queue */ remove_from_write_queue(upstream, netreq); - - if (netreq->owner->return_call_reporting && - netreq->upstream->tls_obj) { - if (netreq->debug_tls_peer_cert.data == NULL && - (cert = _getdns_tls_connection_get_peer_certificate(&upstream->upstreams->mf, netreq->upstream->tls_obj))) { - _getdns_tls_x509_to_der(&upstream->upstreams->mf, cert, &netreq->debug_tls_peer_cert); - _getdns_tls_x509_free(&upstream->upstreams->mf, cert); - } - netreq->debug_tls_version = _getdns_tls_connection_get_version(netreq->upstream->tls_obj); - netreq->debug_pkix_auth = _getdns_tls_connection_get_pkix_auth(netreq->upstream->tls_obj); - netreq->debug_pin_auth = _getdns_tls_connection_get_pin_auth(netreq->upstream->tls_obj); - } - /* Need this because auth status is reset on connection close */ - netreq->debug_tls_auth_status = netreq->upstream->tls_auth_state; + if (netreq->owner->return_call_reporting) + netreq_equip_tls_debug_info(netreq); upstream->queries_sent++; /* Empty write_queue?, then deschedule upstream write_cb */ diff --git a/src/util-internal.c b/src/util-internal.c index f009d1cc..8ebdc9e2 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -846,9 +846,11 @@ _getdns_create_call_reporting_dict( , netreq->request_type ) || /* Safe, because uint32_t facilitates RRT's of almost 50 days*/ - getdns_dict_set_int(netreq_debug, "run_time/ms", - (uint32_t)(( netreq->debug_end_time - - netreq->debug_start_time)/1000))) { + getdns_dict_set_int(netreq_debug, "run_time/ms", + (uint32_t) ( netreq->debug_end_time + - netreq->debug_start_time) >= 1000 + ? (uint32_t)(( netreq->debug_end_time + - netreq->debug_start_time) / 1000) : 0 )) { getdns_dict_destroy(netreq_debug); return NULL; @@ -1177,7 +1179,7 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) int rrsigs_in_answer = 0; getdns_dict *reply; getdns_bindata *canonical_name = NULL; - int nreplies = 0, nanswers = 0; + int nreplies = 0, nerred = 0, ntimedout = 0, nanswers = 0; int nsecure = 0, ninsecure = 0, nindeterminate = 0, nbogus = 0; getdns_dict *netreq_debug; _srvs srvs = { 0, 0, NULL }; @@ -1237,8 +1239,7 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) getdns_bindata *tmp_ipv4_address; getdns_bindata *tmp_ipv6_address; - if (call_reporting && ( netreq->response_len - || netreq->state == NET_REQ_TIMED_OUT)) { + if (call_reporting) { if (!(netreq_debug = _getdns_create_call_reporting_dict(context,netreq))) goto error; @@ -1249,6 +1250,11 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) netreq_debug = NULL; } + switch (netreq->state) { + case NET_REQ_TIMED_OUT: ntimedout++; break; + case NET_REQ_ERRORED : nerred++ ; break; + default : break; + } if (! netreq->response_len) continue; @@ -1383,19 +1389,20 @@ _getdns_create_getdns_response(getdns_dns_req *completed_request) GETDNS_FREE(context->mf, srvs.rrs); } if (getdns_dict_set_int(result, GETDNS_STR_KEY_STATUS, - completed_request->request_timed_out || - nreplies == 0 ? GETDNS_RESPSTATUS_ALL_TIMEOUT : - ( completed_request->dnssec - && nsecure == 0 && nindeterminate ) > 0 - ? GETDNS_RESPSTATUS_NO_SECURE_ANSWERS : - ( completed_request->dnssec_return_only_secure - && nsecure == 0 && ninsecure ) > 0 - ? GETDNS_RESPSTATUS_NO_SECURE_ANSWERS : - ( completed_request->dnssec_return_only_secure - || completed_request->dnssec ) && nsecure == 0 && nbogus > 0 - ? GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS : - nanswers == 0 ? GETDNS_RESPSTATUS_NO_NAME - : GETDNS_RESPSTATUS_GOOD)) + completed_request->request_timed_out || nreplies == 0 + ? ( nerred > ntimedout ? GETDNS_RESPSTATUS_ALL_ERRED + : GETDNS_RESPSTATUS_ALL_TIMEOUT ) + : completed_request->dnssec && nsecure == 0 && nindeterminate > 0 + ? GETDNS_RESPSTATUS_NO_SECURE_ANSWERS + : completed_request->dnssec_return_only_secure && nsecure == 0 + && ninsecure > 0 + ? GETDNS_RESPSTATUS_NO_SECURE_ANSWERS + : ( completed_request->dnssec_return_only_secure + || completed_request->dnssec ) && nsecure == 0 && nbogus > 0 + ? GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS + : nanswers == 0 + ? GETDNS_RESPSTATUS_NO_NAME + : GETDNS_RESPSTATUS_GOOD)) goto error_free_result; return result; From 2599a04f14260ea8f1a73070980d2a7b502b859a Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Wed, 7 Sep 2022 12:47:18 +0200 Subject: [PATCH 08/14] More svc parameters --- src/context.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/src/context.c b/src/context.c index 742eb418..94abc863 100644 --- a/src/context.c +++ b/src/context.c @@ -3233,7 +3233,10 @@ static struct proxy_control_flags { 0, 0 } }; -#define SVC_KEY_ALPN 1 +#define SVC_KEY_MANDATORY 0 +#define SVC_KEY_ALPN 1 +#define SVC_KEY_NDA 2 +#define SVC_KEY_PORT 3 /* Note: this table must be kept sort based on 'value' */ struct @@ -3242,7 +3245,10 @@ struct char *key; } svckeys[] = { - { SVC_KEY_ALPN, "alpn" }, /* 1 */ + { SVC_KEY_MANDATORY, "mandatory" }, /* 0 */ + { SVC_KEY_ALPN, "alpn" }, /* 1 */ + { SVC_KEY_NDA, "no-default-alpn" }, /* 2 */ + { SVC_KEY_PORT, "port" }, /* 3 */ { 0, NULL } }; @@ -3250,11 +3256,12 @@ static void proxy_policy2opt(getdns_proxy_policy *policy, int do_ipv6, uint8_t *buf, size_t *sizep) { uint8_t inflen; - uint16_t flags1, flags2, key16, len16; - int i, j, addr_count; + uint16_t flags1, flags2, key16, len16, u16; + int i, j, k, addr_count; ptrdiff_t len, totlen; - size_t datalen; - char *l, *dot, *key, *v, *vp, *comma; + size_t datalen, slen; + unsigned long port; + char *l, *dot, *key, *mkey, *v, *vp, *comma, *check; uint8_t *bp, *addr_type, *addr_len, *addrp, *domainlenp, *domainp, *svclenp, *svcp, *inflenp, *infp, *lastp, *endp, *datap, *wire_keyp, *wire_lenp, *wire_datap; @@ -3432,10 +3439,12 @@ static void proxy_policy2opt(getdns_proxy_policy *policy, int do_ipv6, for (i = 0; svckeys[i].key != NULL; i++) { key = svckeys[i].key; +fprintf(stderr, "i %d, key %s\n", i, key); for (j = 0; jsvcparams[j].key == NULL) break; +fprintf(stderr, "j %d, key %s\n", j, policy->svcparams[j].key); if (strcmp(key, policy->svcparams[j].key) == 0) break; } @@ -3448,6 +3457,50 @@ static void proxy_policy2opt(getdns_proxy_policy *policy, int do_ipv6, switch(svckeys[i].value) { + case SVC_KEY_MANDATORY: + bp = wirebuf; + + /* Check each of the keys we know if it is + * in the value. Not very efficient, but + * we don't need that many keys. + */ + for (k = 0; svckeys[k].key != NULL; k++) + { + mkey = svckeys[k].key; + v = policy->svcparams[j].value; + while (v) + { + comma = strchr(v, ','); + if (comma) + slen = comma-v; + else + slen = strlen(v); + if (slen == strlen(mkey) && + memcmp(mkey, v, slen) + == 0) + { + break; + } + if (comma) + v = comma+1; + else + v = NULL; + } + if (!v) + { + fprintf(stderr, + "proxy_policy2opt: skipping key %s\n", + mkey); + continue; + } + u16 = htons(svckeys[k].value); + memcpy(bp, &u16, sizeof(u16)); + bp += sizeof(u16); + } + datap = wirebuf; + datalen = bp-wirebuf; + break; + case SVC_KEY_ALPN: v = policy->svcparams[j].value; if (v == NULL) @@ -3488,6 +3541,26 @@ static void proxy_policy2opt(getdns_proxy_policy *policy, int do_ipv6, datalen = bp-wirebuf; break; + case SVC_KEY_NDA: + datap = wirebuf; + datalen = 0; + break; + + case SVC_KEY_PORT: + port = strtoul(policy->svcparams[j].value, + &check, 10); + if (check[0] != '\0' || port > 65535) + { + fprintf(stderr, + "proxy_policy2opt: bad port value %s\n", + policy->svcparams[j].value); + abort(); + } + u16= htons(port); + datap= (uint8_t *)&u16; + datalen = sizeof(u16); + break; + default: fprintf(stderr, "proxy_policy2opt: unknown svc key value\n"); @@ -3596,6 +3669,7 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); + policies = NULL; if (!proxy_policy) { _getdns_proxy_policies_dereference(context->proxy_policies); context->proxy_policies = NULL; @@ -3721,6 +3795,8 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); /* svcparams */ + for (j = 0; jpolicies[i].svcparams[j].key = NULL; if ((r = getdns_dict_get_dict( dict, "svcparams", &svcparams)) == GETDNS_RETURN_GOOD) { getdns_dict_get_names(svcparams, &svcnames); @@ -3760,6 +3836,7 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); } /* interface */ + policies->policies[i].interface= NULL; if ((r = getdns_dict_get_bindata( dict, "interface", &interface)) == GETDNS_RETURN_GOOD) { policies->policies[i].interface= From 4da3cd3eeedc88f3de2b7f88514075e46eed33e6 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Thu, 8 Sep 2022 19:12:40 +0200 Subject: [PATCH 09/14] policy interfaces cann be NULL and updated stubby --- src/context.c | 2 ++ stubby | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/context.c b/src/context.c index 742eb418..5f2e80ba 100644 --- a/src/context.c +++ b/src/context.c @@ -3771,6 +3771,8 @@ fprintf(stderr, "in getdns_context_set_local_proxy_policy\n"); } else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) goto error; + else + policies->policies[i].interface = NULL; } _getdns_proxy_policies_dereference(context->proxy_policies); context->proxy_policies = policies; diff --git a/stubby b/stubby index edb3d27f..3d563285 160000 --- a/stubby +++ b/stubby @@ -1 +1 @@ -Subproject commit edb3d27fa51c1fd5aedae1b473bed20707de76cd +Subproject commit 3d5632852826736b14a5b86b6f19117f7bce97da From 7513632d42a818741f24b53d75822d19f34c5f2b Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 29 Sep 2022 12:21:12 +0200 Subject: [PATCH 10/14] Fixed small bug in converting policy to binary. And updated reference to stubby --- src/context.c | 5 +++-- stubby | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/context.c b/src/context.c index 5f41300c..78878927 100644 --- a/src/context.c +++ b/src/context.c @@ -3150,14 +3150,15 @@ fprintf(stderr, "update_proxy_policy_opts: policy_count %d\n", policy_count); size= sizeof(buf)-off; proxy_policy2opt(policy, 1 /*do_ipv6*/, buf.buf+off, &size); - off= size; + off += size; } if (has_v4) { size= sizeof(buf)-off; proxy_policy2opt(policy, 0 /*!do_ipv6*/, buf.buf+off, &size); - off= size; + off += size; } + fprintf(stderr, "update_proxy_policy_opts: off %lu\n", off); } opt= NULL; diff --git a/stubby b/stubby index 3d563285..2eab450c 160000 --- a/stubby +++ b/stubby @@ -1 +1 @@ -Subproject commit 3d5632852826736b14a5b86b6f19117f7bce97da +Subproject commit 2eab450c458e87f07488a011b0e9c64a4fa9b82c From 03bddb40e14da8825c8967b10f0360e261bbca57 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Mon, 3 Oct 2022 13:21:36 +0200 Subject: [PATCH 11/14] DANE records can be passed along each upstream with "dane_records" key Needs to be printed as well --- src/context.c | 80 ++++++++++++++++++++++++++++++++++++ src/context.h | 11 +++++ src/getdns/getdns_extra.h.in | 2 + src/openssl/tls.c | 64 +++++++++++++++++++++++++++++ src/stub.c | 9 +++- src/tls.h | 12 ++++++ stubby | 2 +- 7 files changed, 177 insertions(+), 3 deletions(-) diff --git a/src/context.c b/src/context.c index 5f41300c..db648dd8 100644 --- a/src/context.c +++ b/src/context.c @@ -611,6 +611,8 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) ; upstreams->count--, upstream++ ) { sha256_pin_t *pin = upstream->tls_pubkey_pinset; + dane_record_t *dane_record = upstream->tls_dane_records; + if (upstream->loop && ( upstream->event.read_cb || upstream->event.write_cb || upstream->event.timeout_cb) ) { @@ -652,6 +654,12 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) pin = nextpin; } upstream->tls_pubkey_pinset = NULL; + while (dane_record) { + dane_record_t *next_dane_record = dane_record->next; + GETDNS_FREE(upstreams->mf, dane_record); + dane_record = next_dane_record; + } + upstream->tls_dane_records = NULL; if (upstream->tls_cipher_list) GETDNS_FREE(upstreams->mf, upstream->tls_cipher_list); if (upstream->tls_ciphersuites) @@ -966,6 +974,7 @@ upstream_init(getdns_upstream *upstream, upstream->last_tls_auth_state = GETDNS_AUTH_NONE; upstream->best_tls_auth_state = GETDNS_AUTH_NONE; upstream->tls_pubkey_pinset = NULL; + upstream->tls_dane_records = NULL; upstream->loop = NULL; (void) getdns_eventloop_event_init( &upstream->event, upstream, NULL, NULL, NULL); @@ -2786,6 +2795,52 @@ getdns_context_set_dnssec_allowed_skew(struct getdns_context *context, return GETDNS_RETURN_GOOD; } /* getdns_context_set_dnssec_allowed_skew */ +static getdns_return_t +_getdns_list2dane_records(struct mem_funcs *mf, const getdns_list *dr_list, + dane_record_t **tls_dane_records) +{ + size_t i; + getdns_return_t r; + getdns_dict *dr_dict; + + assert(tls_dane_records); + + for (i = 0; !(r = getdns_list_get_dict(dr_list, i, &dr_dict)); i++) { + uint32_t usage; + uint32_t selector; + uint32_t type; + getdns_bindata *data; + getdns_dict *rdata; + dane_record_t *dane_record; + + if (!getdns_dict_get_dict(dr_dict, "rdata", &rdata)) { + /* only scan TLSA RRs */ + if (!getdns_dict_get_int(dr_dict, "type", &type) + && type != GETDNS_RRTYPE_TLSA) + continue; + dr_dict = rdata; + } + if ((r = getdns_dict_get_int(dr_dict, "certificate_usage", &usage)) + || (r = getdns_dict_get_int(dr_dict, "selector", &selector)) + || (r = getdns_dict_get_int(dr_dict, "matching_type", &type)) + || (r = getdns_dict_get_bindata(dr_dict, + "certificate_association_data", &data))) + return r; + + if (!(dane_record = (dane_record_t *)GETDNS_XMALLOC( + *mf, uint8_t, sizeof(dane_record_t) + data->size))) + return GETDNS_RETURN_MEMORY_ERROR; + dane_record->next = *tls_dane_records; + dane_record->usage = (uint8_t)usage; + dane_record->selector = (uint8_t)selector; + dane_record->type = (uint8_t)type; + dane_record->size = data->size; + memcpy(dane_record->data, data->data, data->size); + *tls_dane_records = dane_record; + } + return r == GETDNS_RETURN_NO_SUCH_LIST_ITEM ? GETDNS_RETURN_GOOD : r; +} + /* * getdns_context_set_upstream_recursive_servers * @@ -2992,6 +3047,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream->transport = getdns_upstream_transports[j]; 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; getdns_bindata *tls_ciphersuites = NULL; getdns_bindata *tls_curves_list = NULL; @@ -3031,6 +3087,30 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, goto invalid_parameter; } } + if (!(r = getdns_dict_get_list( + dict, "dane_records", &dane_records))) { + if ((r = _getdns_list2dane_records( + &(upstreams->mf), dane_records, + &(upstream->tls_dane_records)))) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAMS, + GETDNS_LOG_ERR, + "%-40s : Upstream : could" + " not parse dane_records: " + "%s\n", upstream->addr_str, + getdns_get_errorstr_by_id(r)); + freeaddrinfo(ai); + goto invalid_parameter; + } + } else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAMS, + GETDNS_LOG_ERR, + "%-40s : Upstream : could not " + "get dane_records: %s\n", + upstream->addr_str, + getdns_get_errorstr_by_id(r)); + (void) getdns_dict_get_bindata( dict, "tls_cipher_list", &tls_cipher_list); upstream->tls_cipher_list = tls_cipher_list diff --git a/src/context.h b/src/context.h index a21368b2..56f9805f 100644 --- a/src/context.h +++ b/src/context.h @@ -132,6 +132,16 @@ typedef struct sha256_pin { struct sha256_pin *next; } sha256_pin_t; +/* for doing DANE authentication of TLS-capable upstreams: */ +typedef struct dane_record { + struct dane_record *next; + uint8_t usage; + uint8_t selector; + uint8_t type; + size_t size; + uint8_t data[]; +} dane_record_t; + typedef struct getdns_upstream { /* backpointer to containing upstreams structure */ struct getdns_upstreams *upstreams; @@ -223,6 +233,7 @@ typedef struct getdns_upstream { /* Auth credentials */ char tls_auth_name[256]; sha256_pin_t *tls_pubkey_pinset; + dane_record_t *tls_dane_records; /* When requests have been scheduled asynchronously on an upstream * that is kept open, and a synchronous call is then done with the diff --git a/src/getdns/getdns_extra.h.in b/src/getdns/getdns_extra.h.in index b6c58645..135325db 100644 --- a/src/getdns/getdns_extra.h.in +++ b/src/getdns/getdns_extra.h.in @@ -584,6 +584,8 @@ typedef enum getdns_loglevel_type { #define GETDNS_LOG_INFO_TEXT "Informational message" #define GETDNS_LOG_DEBUG_TEXT "Debug-level message" +#define GETDNS_LOG_UPSTREAMS 0x1000 +#define GETDNS_LOG_UPSTREAMS_TEXT "Log messages about upstreams" #define GETDNS_LOG_UPSTREAM_STATS 0x3000 #define GETDNS_LOG_UPSTREAM_STATS_TEXT "Log messages about upstream statistics" #define GETDNS_LOG_SYS_STUB 0x2000 diff --git a/src/openssl/tls.c b/src/openssl/tls.c index f58cb013..275f8803 100644 --- a/src/openssl/tls.c +++ b/src/openssl/tls.c @@ -995,6 +995,70 @@ getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* c return GETDNS_RETURN_GOOD; } +getdns_return_t _getdns_tls_connection_set_dane_records(_getdns_tls_connection* conn, const char* auth_name, const dane_record_t* dane_records) +{ + if (!conn || !conn->ssl || !auth_name) + return GETDNS_RETURN_INVALID_PARAMETER; + +#if 0 && defined(USE_DANESSL) + /* Stash auth name and pinset away for use in cert verification. */ + conn->auth_name = auth_name; + conn->dane_records = dane_records; +#endif + +#if defined(HAVE_SSL_DANE_ENABLE) + int osr = SSL_dane_enable(conn->ssl, *auth_name ? auth_name : NULL); + (void) osr; /* unused parameter */ + DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_enable(\"%s\") -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, auth_name, osr); + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); + const dane_record_t *dane_p; + size_t n_records= 0; + for (dane_p = dane_records; dane_p; dane_p = dane_p->next) { + osr = SSL_dane_tlsa_add(conn->ssl, dane_p->usage, + dane_p->selector, dane_p->type, dane_p->data, dane_p->size); + DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_tlsa_add() -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); + if (osr > 0) + ++n_records; + } +#elif 0 && defined(USE_DANESSL) + conn->pinset = pinset; + if (pinset) { + const char *auth_names[2] = { auth_name, NULL }; + int osr = DANESSL_init(conn->ssl, + *auth_name ? auth_name : NULL, + *auth_name ? auth_names : NULL); + (void) osr; /* unused parameter */ + DEBUG_STUB("%s %-35s: DEBUG: DANESSL_init(\"%s\") -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, auth_name, osr); + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); + const sha256_pin_t *pin_p; + size_t n_pins = 0; + for (pin_p = pinset; pin_p; pin_p = pin_p->next) { + osr = DANESSL_add_tlsa(conn->ssl, 3, 1, "sha256", + (unsigned char *)pin_p->pin, SHA256_DIGEST_LENGTH); + DEBUG_STUB("%s %-35s: DEBUG: DANESSL_add_tlsa() -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); + if (osr > 0) + ++n_pins; + osr = DANESSL_add_tlsa(conn->ssl, 2, 1, "sha256", + (unsigned char *)pin_p->pin, SHA256_DIGEST_LENGTH); + DEBUG_STUB("%s %-35s: DEBUG: DANESSL_add_tlsa() -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); + if (osr > 0) + ++n_pins; + } + } else { + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); + } +#else +#error Must have either DANE SSL or OpenSSL v1.1. +#endif + return GETDNS_RETURN_GOOD; +} + + getdns_return_t _getdns_tls_connection_certificate_verify(_getdns_tls_connection* conn, long* errnum, const char** errmsg) { if (!conn || !conn->ssl) diff --git a/src/stub.c b/src/stub.c index 545f8994..03502921 100644 --- a/src/stub.c +++ b/src/stub.c @@ -1049,8 +1049,9 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) /* Lack of host name is OK unless only authenticated * TLS is specified and we have no pubkey_pinset */ if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_REQUIRED) { - if (upstream->tls_pubkey_pinset) { - DEBUG_STUB("%s %-35s: Proceeding with only pubkey pinning authentication\n", + if (upstream->tls_pubkey_pinset + || upstream->tls_dane_records) { + DEBUG_STUB("%s %-35s: Proceeding with only pubkey pinning and/or DANE authentication\n", STUB_DEBUG_SETUP_TLS, __FUNC__); } else { DEBUG_STUB("%s %-35s: ERROR:No auth name or pinset provided for this upstream for Strict TLS authentication\n", @@ -1075,6 +1076,10 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) _getdns_tls_connection_set_host_pinset( tls, upstream->tls_auth_name, upstream->tls_pubkey_pinset); + if (upstream->tls_dane_records) + _getdns_tls_connection_set_dane_records( + tls, upstream->tls_auth_name, upstream->tls_dane_records); + /* Session resumption. There are trade-offs here. Want to do it when possible only if we have the right type of connection. Note a change to the upstream auth info creates a new upstream so never re-uses.*/ diff --git a/src/tls.h b/src/tls.h index 5662e362..71fb1705 100644 --- a/src/tls.h +++ b/src/tls.h @@ -43,6 +43,7 @@ /* Forward declare type. */ struct sha256_pin; struct getdns_log_config; +struct dane_record; /* Additional return codes required by TLS abstraction. Internal use only. */ #define GETDNS_RETURN_TLS_WANT_READ ((getdns_return_t) 420) @@ -332,6 +333,17 @@ getdns_return_t _getdns_tls_connection_setup_hostname_auth(_getdns_tls_connectio */ getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* conn, const char* auth_name, const struct sha256_pin* pinset); +/** + * Set host pinset. + * + * @param conn the connection. + * @param auth_name the hostname. + * @param dane_records the DANE records that must match. + * @return GETDNS_RETURN_GOOD if all OK. + * @return GETDNS_RETURN_INVALID_PARAMETER if conn is null or has no SSL. + */ +getdns_return_t _getdns_tls_connection_set_dane_records(_getdns_tls_connection* conn, const char* auth_name, const struct dane_record* dane_records); + /** * Get result of certificate verification. * diff --git a/stubby b/stubby index 3d563285..a550394f 160000 --- a/stubby +++ b/stubby @@ -1 +1 @@ -Subproject commit 3d5632852826736b14a5b86b6f19117f7bce97da +Subproject commit a550394f874817bd5fda022c34c0c96e7f819bc2 From ca416a8f9bf7d165c4e1cd0d6b9761c886a4ff55 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Thu, 6 Oct 2022 15:18:47 +0200 Subject: [PATCH 12/14] CMake infrastructure for linking libnghttp2 --- CMakeLists.txt | 23 ++++++++ cmake/include/cmakeconfig.h.in | 2 + cmake/modules/FindLibnghttp2.cmake | 86 ++++++++++++++++++++++++++++++ src/context.c | 17 ++++++ 4 files changed, 128 insertions(+) create mode 100644 cmake/modules/FindLibnghttp2.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 41817d8c..b4213178 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,8 @@ option(BUILD_LIBEV "Build libev support library if available." ON) option(BUILD_LIBEVENT2 "Build libevent2 support library if available." ON) option(BUILD_LIBUV "Build libuv support library available." ON) +option(BUILD_DOH "Build DNS-over-HTTPS support if libnghttp2 library is available." ON) + option(USE_LIBIDN2 "Use libidn2 if available." ON) option(USE_GNUTLS "Use GnuTLS for TLS connections." OFF) @@ -193,6 +195,7 @@ set(STUB_NATIVE_DNSSEC ${ENABLE_NATIVE_STUB_DNSSEC}) set(USE_LIBEV ${BUILD_LIBEV}) set(USE_LIBEVENT2 ${BUILD_LIBEVENT2}) set(USE_LIBUV ${BUILD_LIBUV}) +set(USE_DOH ${BUILD_DOH}) option(ENABLE_DEBUG_KEEP_CONNECTIONS_OPEN "Disable connection idle timeout. Do not enable.") mark_as_advanced(ENABLE_DEBUG_KEEP_CONNECTIONS_OPEN) @@ -409,6 +412,17 @@ if (USE_LIBIDN2) endif() endif() +# DNS-over-HTTPS support +if (USE_DOH) + find_package(Libnghttp2) + if (Libnghttp2_FOUND) + set(HAVE_LIBNGHTTP2 1) + else() + message(WARNING "DNS-over-HTTPS support build requested, but libnghttp2 not found. Disabled.") + unset(USE_DOH) + endif() +endif () + # GnuTLS and Nettle. If using GnuTLS, we need the Nettle dev stuff to # handle digital signature algorithms. GnuTLS uses Nettle internally. if (USE_GNUTLS) @@ -697,6 +711,9 @@ endif () if (GnuTLS_FOUND) target_include_directories(getdns_objects PRIVATE ${GNUTLS_INCLUDE_DIR}) endif () +if (Libnghttp2_FOUND) + target_include_directories(getdns_objects PRIVATE ${LIBNGHTTP2_INCLUDE_DIR}) +endif () # Don't compile separate objects for shared and static libraries. # Yes, -fPIC is slightly suboptimal for static libraries, but it looks @@ -730,6 +747,9 @@ if (ENABLE_STATIC) if (Nettle_FOUND) target_link_libraries(getdns PUBLIC Nettle::Nettle Nettle::Hogweed) endif () + if (Libnghttp2_FOUND) + target_link_libraries(getdns PUBLIC Libnghttp2::Libnghttp2) + endif () set_target_properties(getdns PROPERTIES OUTPUT_NAME getdns${static_lib_suffix}) endif () @@ -759,6 +779,9 @@ if (ENABLE_SHARED) if (Nettle_FOUND) target_link_libraries(getdns_shared PUBLIC Nettle::Nettle Nettle::Hogweed) endif () + if (Libnghttp2_FOUND) + target_link_libraries(getdns_shared PUBLIC Libnghttp2::Libnghttp2) + endif () set_target_properties(getdns_shared PROPERTIES OUTPUT_NAME getdns) target_shared_library_version(getdns_shared ${GETDNS_VERSION_CURRENT} ${GETDNS_VERSION_REVISION} ${GETDNS_VERSION_AGE}) diff --git a/cmake/include/cmakeconfig.h.in b/cmake/include/cmakeconfig.h.in index 4d5e259b..569c4172 100644 --- a/cmake/include/cmakeconfig.h.in +++ b/cmake/include/cmakeconfig.h.in @@ -188,6 +188,8 @@ #cmakedefine HAVE_NETTLE_DSA_COMPAT_H 1 #cmakedefine HAVE_NETTLE_EDDSA_H 1 +#cmakedefine HAVE_LIBNGHTTP2 1 + #cmakedefine HAVE_EVENT2_EVENT_H 1 #cmakedefine HAVE_EVENT_BASE_NEW 1 #cmakedefine HAVE_EVENT_BASE_FREE 1 diff --git a/cmake/modules/FindLibnghttp2.cmake b/cmake/modules/FindLibnghttp2.cmake new file mode 100644 index 00000000..c24ebfce --- /dev/null +++ b/cmake/modules/FindLibnghttp2.cmake @@ -0,0 +1,86 @@ +#[=======================================================================[.rst: +FindLibnghttp2 +----------- + +Find the Libnghttp2 library + +Imported targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``Libnghttp2::Libnghttp2`` + The Libnghttp2 library, if found. + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables in your project: + +``Libnghttp2_FOUND`` + If false, do not try to use Libnghttp2. +``LIBNGHTTP2_INCLUDE_DIR`` + where to find libnghttp2 headers. +``LIBNGHTTP2_LIBRARIES`` + the libraries needed to use Libnghttp2. +``LIBNGHTTP2_VERSION`` + the version of the Libnghttp2 library found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if (PKG_CONFIG_FOUND) + pkg_check_modules(PkgLibNghttp2 IMPORTED_TARGET GLOBAL libnghttp2) +endif () + +if (PkgLibNghttp2_FOUND) + set(LIBNGHTTP2_INCLUDE_DIR ${PkgLibNghttp2_INCLUDE_DIRS} CACHE FILEPATH "libnghttp2 include path") + set(LIBNGHTTP2_LIBRARIES ${PkgLibNghttp2_LIBRARIES} CACHE STRING "libnghttp2 libraries") + set(LIBNGHTTP2_VERSION ${PkgLibNghttp2_VERSION}) + add_library(Libnghttp2::Libnghttp2 ALIAS PkgConfig::PkgLibNghttp2) + if (NOT TARGET Libnghttp2::Libnghttp2) + message(STATUS "No Libnghttp2::Libnghttp2 target") + add_library(Libnghttp2::Libnghttp2 UNKNOWN IMPORTED) + set_target_properties(Libnghttp2::Libnghttp2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBNGHTTP2_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${LIBNGHTTP2_LIBRARIES}" + ) + endif () + set(Libnghttp2_FOUND ON) +else () + find_path(LIBNGHTTP2_INCLUDE_DIR nghttp2/nghttp2.h + HINTS + "${LIBNGHTTP2_DIR}" + "${LIBNGHTTP2_DIR}/include" + ) + + find_library(LIBNGHTTP2_LIBRARIES NAMES nghttp2 libnghttp2 + HINTS + "${LIBNGHTTP2_DIR}" + "${LIBNGHTTP2_DIR}/lib" + ) + + if (LIBNGHTTP2_INCLUDE_DIR AND LIBNGHTTP2_LIBRARIES) + if (NOT TARGET Libnghttp2::Libnghttp2) + add_library(Libnghttp2::Libnghttp2 UNKNOWN IMPORTED) + set_target_properties(Libnghttp2::Libnghttp2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBNGHTTP2_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${LIBNGHTTP2_LIBRARIES}" + ) + endif () + + if (NOT LIBNGHTTP2_VERSION AND LIBNGHTTP2_INCLUDE_DIR AND EXISTS "${LIBNGHTTP2_INCLUDE_DIR}/nghttp2/nghttp2.h") + file(STRINGS "${LIBNGHTTP2_INCLUDE_DIR}/nghttp2/nghttp2.h" LIBNGHTTP2_H REGEX "^[ \t]*#[ \t]*define[ \t]+NGHTTP2_VERSION[ \t]") + string(REGEX REPLACE "^.*NGHTTP2_VERSION[ \t]+\"([0-9.]+)\".*$" "\\1" LIBNGHTTP2_VERSION "${LIBNGHTTP2_H}") + endif () + endif () + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libnghttp2 + REQUIRED_VARS LIBNGHTTP2_LIBRARIES LIBNGHTTP2_INCLUDE_DIR + VERSION_VAR LIBNGHTTP2_VERSION + ) +endif () + +mark_as_advanced(LIBNGHTTP2_INCLUDE_DIR LIBNGHTTP2_LIBRARIES) diff --git a/src/context.c b/src/context.c index ef8b26dc..ee65bbe8 100644 --- a/src/context.c +++ b/src/context.c @@ -70,6 +70,9 @@ typedef unsigned short in_port_t; #ifdef HAVE_LIBUNBOUND #include #endif +#ifdef HAVE_LIBNGHTTP2 +#include +#endif #include "debug.h" #include "gldns/str2wire.h" #include "gldns/wire2str.h" @@ -4993,6 +4996,17 @@ getdns_context_get_api_information(const getdns_context* context) && ! _getdns_tls_get_api_information(result) +#ifdef HAVE_LIBNGHTTP2 + && ! getdns_dict_set_int( + result, "nghttp2_version_number", nghttp2_version(0)->version_num) + + && ! getdns_dict_util_set_string( + result, "nghttp2_version_string", nghttp2_version(0)->version_str) + + && ! getdns_dict_util_set_string( + result, "nghttp2_protocol_string", nghttp2_version(0)->proto_str) +#endif + && ! getdns_dict_set_int( result, "resolution_type", context->resolution_type) @@ -5689,6 +5703,9 @@ _getdns_context_config_setting(getdns_context *context, && !_streq(setting, "openssl_platform") && !_streq(setting, "openssl_dir") && !_streq(setting, "openssl_engines_dir") + && !_streq(setting, "nghttp2_version_number") + && !_streq(setting, "nghttp2_version_string") + && !_streq(setting, "nghttp2_protocol_string") ) { r = GETDNS_RETURN_NOT_IMPLEMENTED; } From 7deed7e1d4b14bd0b97ddc620e15e8080cd781ee Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sun, 16 Oct 2022 12:51:55 +0200 Subject: [PATCH 13/14] alpn determines DoH upstream --- src/context.c | 168 +++++++++++++++++++++++++++------------ src/context.h | 8 ++ src/convert.c | 17 +++- src/openssl/tls.c | 19 +++++ src/stub.c | 2 + src/tls.h | 12 +++ src/tools/getdns_query.c | 4 +- 7 files changed, 175 insertions(+), 55 deletions(-) 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"); From 3886b37d811f05f4e9ab6ba7b57d5f31f35aa354 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Sun, 16 Oct 2022 23:42:55 +0200 Subject: [PATCH 14/14] DoH support! --- src/context.c | 62 +++++- src/context.h | 9 + src/gnutls/tls.c | 17 +- src/openssl/tls.c | 2 +- src/stub.c | 516 ++++++++++++++++++++++++++++++++++++++++------ src/tls.h | 2 +- 6 files changed, 537 insertions(+), 71 deletions(-) diff --git a/src/context.c b/src/context.c index 8c01ea0d..6933ff1e 100644 --- a/src/context.c +++ b/src/context.c @@ -70,9 +70,6 @@ typedef unsigned short in_port_t; #ifdef HAVE_LIBUNBOUND #include #endif -#ifdef HAVE_LIBNGHTTP2 -#include -#endif #include "debug.h" #include "gldns/str2wire.h" #include "gldns/wire2str.h" @@ -640,6 +637,12 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) _getdns_context_cancel_request(dnsreq); } } +#ifdef HAVE_LIBNGHTTP2 + if (upstream->doh_session != NULL) { + nghttp2_session_del(upstream->doh_session); + upstream->doh_session = NULL; + } +#endif if (upstream->tls_session != NULL) _getdns_tls_session_free(&upstreams->mf, upstream->tls_session); @@ -773,6 +776,12 @@ _getdns_upstream_reset(getdns_upstream *upstream) upstream->loop->vmt->clear( upstream->loop, &upstream->event); } +#ifdef HAVE_LIBNGHTTP2 + if (upstream->doh_session != NULL) { + nghttp2_session_del(upstream->doh_session); + upstream->doh_session = NULL; + } +#endif if (upstream->tls_obj != NULL) { _getdns_tls_connection_shutdown(upstream->tls_obj); _getdns_tls_connection_free(&upstream->upstreams->mf, upstream->tls_obj); @@ -967,6 +976,9 @@ upstream_init(getdns_upstream *upstream, upstream->tls_fallback_ok = 0; upstream->tls_obj = NULL; upstream->tls_session = NULL; +#ifdef HAVE_LIBNGHTTP2 + upstream->doh_session = NULL; +#endif upstream->tls_cipher_list = NULL; upstream->tls_ciphersuites = NULL; upstream->tls_curves_list = NULL; @@ -981,7 +993,7 @@ upstream_init(getdns_upstream *upstream, upstream->tls_pubkey_pinset = NULL; upstream->tls_dane_records = NULL; upstream->alpn = NULL; - upstream->doh_path[0] = '\0'; + strcpy(upstream->doh_path, "dns-query"); upstream->loop = NULL; (void) getdns_eventloop_event_init( &upstream->event, upstream, NULL, NULL, NULL); @@ -1546,7 +1558,9 @@ getdns_context_create_with_extended_memory_functions( result->edns_client_subnet_private = 0; result->tls_query_padding_blocksize = 1; /* default is to pad queries sensibly */ result->tls_ctx = NULL; - +#ifdef HAVE_LIBNGHTTP2 + result->doh_callbacks = NULL; +#endif result->extension = &result->default_eventloop.loop; _getdns_default_eventloop_init(&result->mf, &result->default_eventloop); _getdns_default_eventloop_init(&result->mf, &result->sync_eventloop); @@ -1733,6 +1747,10 @@ getdns_context_destroy(struct getdns_context *context) if (context->tls_ctx) _getdns_tls_context_free(&context->my_mf, context->tls_ctx); +#ifdef HAVE_LIBNGHTTP2 + if (context->doh_callbacks) + nghttp2_session_callbacks_del(context->doh_callbacks); +#endif getdns_list_destroy(context->dns_root_servers); #if defined(HAVE_LIBUNBOUND) && !defined(HAVE_UB_CTX_SET_STUB) @@ -4538,6 +4556,22 @@ _getdns_ns_dns_setup(struct getdns_context *context) return GETDNS_RETURN_BAD_CONTEXT; } +#ifdef HAVE_LIBNGHTTP2 +ssize_t +_doh_send_callback(nghttp2_session *session, const uint8_t *data, size_t length, + int flags, void *user_data); +ssize_t +_doh_recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, + int flags, void *user_data); +int +_doh_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, size_t len, void *user_data); +int +_doh_on_header_callback (nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data); +#endif + getdns_return_t _getdns_context_prepare_for_resolution(getdns_context *context) { @@ -4605,6 +4639,22 @@ _getdns_context_prepare_for_resolution(getdns_context *context) } _getdns_tls_context_pinset_init(context->tls_ctx); } +#ifdef HAVE_LIBNGHTTP2 + if (!context->doh_callbacks) { + if (nghttp2_session_callbacks_new(&context->doh_callbacks)) { + context->doh_callbacks = NULL; + return GETDNS_RETURN_MEMORY_ERROR; + } + nghttp2_session_callbacks_set_send_callback( + context->doh_callbacks, _doh_send_callback); + nghttp2_session_callbacks_set_recv_callback( + context->doh_callbacks, _doh_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + context->doh_callbacks, _doh_on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_header_callback( + context->doh_callbacks, _doh_on_header_callback); + } +#endif } /* Block use of TLS ONLY in recursive mode as it won't work */ @@ -5501,7 +5551,7 @@ getdns_context_get_upstream_recursive_servers( d, "tls_max_version", upstream->tls_max_version); } - if (upstream->doh_path[0] != '\0' + if (strcmp(upstream->doh_path, "dns-query") && (r = getdns_dict_util_set_string( d, "doh_path", upstream->doh_path))) diff --git a/src/context.h b/src/context.h index 119a499c..33c6da11 100644 --- a/src/context.h +++ b/src/context.h @@ -40,6 +40,9 @@ #include "getdns/getdns.h" #include "getdns/getdns_extra.h" #include "config.h" +#ifdef HAVE_LIBNGHTTP2 +#include +#endif #include "types-internal.h" #include "extension/default_eventloop.h" #include "util/rbtree.h" @@ -223,6 +226,9 @@ typedef struct getdns_upstream { * This is how long a handshake may * take. */ +#ifdef HAVE_LIBNGHTTP2 + nghttp2_session* doh_session; +#endif /* TLS settings */ char *tls_cipher_list; char *tls_ciphersuites; @@ -434,6 +440,9 @@ struct getdns_context { uint8_t edns_client_subnet_private; uint16_t tls_query_padding_blocksize; _getdns_tls_context* tls_ctx; +#ifdef HAVE_LIBNGHTTP2 + nghttp2_session_callbacks* doh_callbacks; +#endif getdns_update_callback update_callback; getdns_update_callback2 update_callback2; diff --git a/src/gnutls/tls.c b/src/gnutls/tls.c index 3623810e..231f2353 100644 --- a/src/gnutls/tls.c +++ b/src/gnutls/tls.c @@ -482,6 +482,21 @@ getdns_return_t _getdns_tls_connection_set_cipher_suites(_getdns_tls_connection* return GETDNS_RETURN_GENERIC_ERROR; } +getdns_return_t +_getdns_tls_connection_set_alpn(_getdns_tls_connection* conn, const char* alpn) +{ + if (!conn || !conn->tls) + return GETDNS_RETURN_INVALID_PARAMETER; + + gnutls_datum_t proto; + proto.data = (unsigned char *)alpn; + proto.size = strlen(alpn); + if (gnutls_alpn_set_protocols(res->tls, &proto, 1, 0) != GNUTLS_E_SUCCESS) + goto GETDNS_RETURN_GENERIC_ERROR; + + return GETDNS_RETURN_GOOD; +} + getdns_return_t _getdns_tls_connection_set_curves_list(_getdns_tls_connection* conn, const char* list) { if (!conn || !conn->tls) @@ -800,7 +815,7 @@ getdns_return_t _getdns_tls_connection_read(_getdns_tls_connection* conn, uint8_ return GETDNS_RETURN_GOOD; } -getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, uint8_t* buf, size_t to_write, size_t* written) +getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, const uint8_t* buf, size_t to_write, size_t* written) { int swritten; diff --git a/src/openssl/tls.c b/src/openssl/tls.c index 2d02ba93..dcb52b65 100644 --- a/src/openssl/tls.c +++ b/src/openssl/tls.c @@ -1174,7 +1174,7 @@ getdns_return_t _getdns_tls_connection_read(_getdns_tls_connection* conn, uint8_ return GETDNS_RETURN_GOOD; } -getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, uint8_t* buf, size_t to_write, size_t* written) +getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, const uint8_t* buf, size_t to_write, size_t* written) { int swritten; diff --git a/src/stub.c b/src/stub.c index 3e65ee3e..ba3c9f81 100644 --- a/src/stub.c +++ b/src/stub.c @@ -50,6 +50,7 @@ #include "gldns/rrdef.h" #include "gldns/str2wire.h" #include "gldns/wire2str.h" +#include "gldns/parseutil.h" #include "rr-iter.h" #include "context.h" #include "util-internal.h" @@ -57,6 +58,15 @@ #include "general.h" #include "pubkey-pinning.h" +#ifdef HAVE_LIBNGHTTP2 +#define MAKE_NV(NAME, VALUE, VALUELEN) { (uint8_t *)NAME, (uint8_t *)VALUE, \ + sizeof(NAME) - 1, VALUELEN, NGHTTP2_NV_FLAG_NONE } + +#define MAKE_NV2(NAME, VALUE) { (uint8_t *)NAME, (uint8_t *)VALUE, \ + sizeof(NAME) - 1, sizeof(VALUE) - 1, NGHTTP2_NV_FLAG_NONE } + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) +#endif /* WSA TODO: * STUB_TCP_RETRY added to deal with edge triggered event loops (versus @@ -71,6 +81,7 @@ #define STUB_TCP_MORE_TO_READ -3 #define STUB_TCP_MORE_TO_WRITE -3 #define STUB_TCP_ERROR -2 +#define STUB_NOOP -1 /* Don't currently have access to the context whilst doing handshake */ #define MIN_TLS_HS_TIMEOUT 2500 @@ -94,6 +105,10 @@ static int fallback_on_write(getdns_network_req *netreq); static void stub_timeout_cb(void *userarg); uint64_t _getdns_get_time_as_uintt64(); +static void netreq_equip_tls_debug_info(getdns_network_req *netreq); +static void process_finished_cb(void *userarg); + + /*****************************/ /* General utility functions */ /*****************************/ @@ -669,10 +684,29 @@ stub_cleanup(getdns_network_req *netreq) } } +static void +upstream_teardown(getdns_upstream *upstream) +{ + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + upstream->conn_state = GETDNS_CONN_TEARDOWN; + + while (upstream->write_queue) + upstream_write_cb(upstream); + + while (upstream->netreq_by_query_id.count) { + getdns_network_req *netreq = (getdns_network_req *) + _getdns_rbtree_first(&upstream->netreq_by_query_id); + + stub_cleanup(netreq); + _getdns_netreq_change_state(netreq, NET_REQ_ERRORED); + _getdns_check_dns_req_complete(netreq->owner); + } + _getdns_upstream_shutdown(upstream); +} + static void upstream_failed(getdns_upstream *upstream, int during_setup) { - getdns_network_req *netreq; DEBUG_STUB("%s %-35s: FD: %d Failure during connection setup = %d\n", STUB_DEBUG_CLEANUP, __FUNC__, upstream->fd, during_setup); @@ -680,26 +714,13 @@ upstream_failed(getdns_upstream *upstream, int during_setup) when idle.*/ /* [TLS1]TODO: Work out how to re-open the connection and re-try the queries if there is only one upstream.*/ - GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); if (during_setup) { upstream->conn_setup_failed++; } else { upstream->conn_shutdowns++; /* [TLS1]TODO: Re-try these queries if possible.*/ } - upstream->conn_state = GETDNS_CONN_TEARDOWN; - - while (upstream->write_queue) - upstream_write_cb(upstream); - - while (upstream->netreq_by_query_id.count) { - netreq = (getdns_network_req *) - _getdns_rbtree_first(&upstream->netreq_by_query_id); - stub_cleanup(netreq); - _getdns_netreq_change_state(netreq, NET_REQ_ERRORED); - _getdns_check_dns_req_complete(netreq->owner); - } - _getdns_upstream_shutdown(upstream); + upstream_teardown(upstream); } void @@ -994,6 +1015,7 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) { /* Create SSL instance and connect with a file descriptor */ getdns_context *context = dnsreq->context; + if (context->tls_ctx == NULL) return NULL; _getdns_tls_connection* tls = _getdns_tls_connection_new(&context->my_mf, context->tls_ctx, fd, &upstream->upstreams->log); @@ -1002,23 +1024,46 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) getdns_return_t r = GETDNS_RETURN_GOOD; - if (upstream->tls_curves_list) - 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); + if (upstream->tls_curves_list + && (r = _getdns_tls_connection_set_curves_list(tls, upstream->tls_curves_list))) + ; /* pass */ + else if (upstream->tls_ciphersuites + && (r = _getdns_tls_connection_set_cipher_suites(tls, upstream->tls_ciphersuites))) + ; /* pass */ + else if ((r =_getdns_tls_connection_set_min_max_tls_version(tls, upstream->tls_min_version, upstream->tls_max_version))) + ; /* pass */ + else if (upstream->tls_fallback_ok + && (r = _getdns_tls_connection_set_cipher_list(tls, NULL))) + ; /* pass */ + else if (upstream->tls_cipher_list + && (r = _getdns_tls_connection_set_cipher_list(tls, upstream->tls_cipher_list))) + ; /* pass */ + else if ((r = _getdns_tls_connection_set_alpn(tls, upstream->alpn))) + ; /* pass */ +#ifdef HAVE_LIBNGHTTP2 + else if (!is_doh_upstream(upstream) || !context->doh_callbacks) + ; /* pass */ + else if (nghttp2_session_client_new(&upstream->doh_session + , context->doh_callbacks + , upstream)) + r = GETDNS_RETURN_MEMORY_ERROR; + else { + int rv; + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - if (!r) - { - if (upstream->tls_fallback_ok) - r = _getdns_tls_connection_set_cipher_list(tls, NULL); - else if (upstream->tls_cipher_list) - r = _getdns_tls_connection_set_cipher_list(tls, upstream->tls_cipher_list); + /* client 24 bytes magic string will be sent by nghttp2 library */ + if ((rv = nghttp2_submit_settings(upstream->doh_session, + NGHTTP2_FLAG_NONE, iv, ARRLEN(iv)))) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR, + "%-40s : Could not submit DoH settings: %s\n", + upstream->addr_str, + nghttp2_strerror(rv)); + r = GETDNS_RETURN_GENERIC_ERROR; + } } - +#endif if (r) { _getdns_tls_connection_free(&upstream->upstreams->mf, tls); upstream->tls_auth_state = GETDNS_AUTH_NONE; @@ -1237,6 +1282,182 @@ tls_connected(getdns_upstream* upstream) return tls_do_handshake(upstream); } +#ifdef HAVE_LIBNGHTTP2 +ssize_t +_doh_send_callback(nghttp2_session *session, const uint8_t *data, size_t length, + int flags, void *user_data) +{ + getdns_upstream *upstream = (getdns_upstream *)user_data; + size_t written; + getdns_return_t r; + + (void)session; + (void)flags; + + if (!upstream || !upstream->doh_session || !upstream->tls_obj) + return NGHTTP2_ERR_CALLBACK_FAILURE; + if (!(r = _getdns_tls_connection_write( + upstream->tls_obj, data, length, &written))) + return (ssize_t)written; + return ( r == GETDNS_RETURN_TLS_WANT_READ + || r == GETDNS_RETURN_TLS_WANT_WRITE ) + ? NGHTTP2_ERR_WOULDBLOCK + : NGHTTP2_ERR_CALLBACK_FAILURE; +} + +ssize_t +_doh_recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, + int flags, void *user_data) +{ + getdns_upstream *upstream = (getdns_upstream *)user_data; + size_t read; + getdns_return_t r; + + (void)session; + (void)flags; + + if (!upstream || !upstream->doh_session || !upstream->tls_obj) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + if (!(r = _getdns_tls_connection_read( + upstream->tls_obj, buf, length, &read))) + return (ssize_t)read; + return ( r == GETDNS_RETURN_TLS_WANT_READ + || r == GETDNS_RETURN_TLS_WANT_WRITE ) + ? NGHTTP2_ERR_WOULDBLOCK + : NGHTTP2_ERR_CALLBACK_FAILURE; + return 0; +} + +int +_doh_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, size_t len, void *user_data) +{ + getdns_upstream *upstream = (getdns_upstream *)user_data; + intptr_t stream_id_intptr = (intptr_t)stream_id; + getdns_network_req *netreq; + getdns_dns_req *dnsreq; + + (void)session; + (void)flags; + + netreq = (getdns_network_req *)_getdns_rbtree_search( + &upstream->netreq_by_query_id, (void *)stream_id_intptr); + if (!netreq) /* Netreq might have been canceled (so okay!) */ + return 0; + + if (netreq->query_id_registered == &upstream->netreq_by_query_id) { + netreq->query_id_registered = NULL; + netreq->node.key = NULL; + + } else if (netreq->query_id_registered) { + (void) _getdns_rbtree_delete( + netreq->query_id_registered, netreq->node.key); + netreq->query_id_registered = NULL; + netreq->node.key = NULL; + } + DEBUG_STUB("%s %-35s: MSG: %p (read)\n", + STUB_DEBUG_READ, __FUNC__, (void*)netreq); + _getdns_netreq_change_state(netreq, NET_REQ_FINISHED); + if (netreq->response < netreq->wire_data + || netreq->response + len > netreq->wire_data + netreq->wire_data_sz) + netreq->response = GETDNS_XMALLOC( + upstream->upstreams->mf, uint8_t, len); + memcpy(netreq->response, data, (netreq->response_len = len)); + upstream->responses_received++; + + if (netreq->owner->edns_cookies && + match_and_process_server_cookie(netreq->upstream, netreq)) + return 0; /* Client cookie didn't match (or FORMERR) */ + + if (netreq->owner->context->idle_timeout != 0) + process_keepalive(netreq->upstream, netreq); + + netreq->debug_end_time = _getdns_get_time_as_uintt64(); + /* This also reschedules events for the upstream */ + stub_cleanup(netreq); + + if (!upstream->is_sync_loop || netreq->owner->is_sync_request) + _getdns_check_dns_req_complete(netreq->owner); + + else { + assert(upstream->is_sync_loop && + !netreq->owner->is_sync_request); + + /* We have a result for an asynchronously scheduled + * netreq, while processing the synchronous loop. + * Queue dns_req_complete checks. + */ + + /* First check if one for the dns_req already exists */ + for ( dnsreq = upstream->finished_dnsreqs + ; dnsreq && dnsreq != netreq->owner + ; dnsreq = dnsreq->finished_next) + ; /* pass */ + + if (!dnsreq) { + /* Schedule dns_req_complete check for this + * netreq's owner + */ + dnsreq = netreq->owner; + dnsreq->finished_next = + upstream->finished_dnsreqs; + upstream->finished_dnsreqs = dnsreq; + + if (!upstream->finished_event.timeout_cb) { + upstream->finished_event.timeout_cb + = process_finished_cb; + GETDNS_SCHEDULE_EVENT( + dnsreq->context->extension, + -1, 1, &upstream->finished_event); + } + } + } + return 0; +} + +int +_doh_on_header_callback (nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) +{ + getdns_upstream *upstream = (getdns_upstream *)user_data; + intptr_t stream_id_intptr; + getdns_network_req *netreq; + + (void)session; + (void)flags; + + if (frame->hd.type != NGHTTP2_HEADERS + || frame->headers.cat != NGHTTP2_HCAT_RESPONSE) + return 0; +#if 0 + fprintf(stderr, "incoming header: "); + fwrite(name, 1, namelen, stderr); + fprintf(stderr, ": "); + fwrite(value, 1, valuelen, stderr); + fprintf(stderr, "\n"); +#endif + if (namelen == 7 && !strncasecmp((const char *)name, ":status", 7)) { + if (valuelen == 3 && !strncmp((const char *)value, "200", 3)) + return 0; + + stream_id_intptr = (intptr_t)frame->hd.stream_id; + netreq = (getdns_network_req *)_getdns_rbtree_search( + &upstream->netreq_by_query_id, (void *)stream_id_intptr); + if (!netreq) /* Netreq might have been canceled (so okay!) */ + return 0; + + stub_cleanup(netreq); + _getdns_netreq_change_state(netreq, NET_REQ_ERRORED); + netreq->debug_end_time = _getdns_get_time_as_uintt64(); + _getdns_check_dns_req_complete(netreq->owner); + return 0; + } + return 0; +} +#endif + /***************************/ /* TLS read/write functions*/ /***************************/ @@ -1335,8 +1556,8 @@ stub_tls_write(getdns_upstream *upstream, getdns_tcp_state *tcp, int q; -fprintf(stderr, "in stub_tls_write\n"); - if (netreq->owner->expires > upstream->expires) + fprintf(stderr, "in stub_tls_write\n"); + if (netreq && netreq->owner->expires > upstream->expires) upstream->expires = netreq->owner->expires; q = tls_connected(upstream); @@ -1354,21 +1575,6 @@ fprintf(stderr, "in stub_tls_write\n"); /* No, this is an initial write. Try to send */ - /* Find a unique query_id not already written (or in - * the write_queue) for that upstream. Register this netreq - * by query_id in the process. - */ - do { - query_id = arc4random(); - query_id_intptr = (intptr_t)query_id; - netreq->node.key = (void *)query_id_intptr; - - } while (!_getdns_rbtree_insert( - &netreq->upstream->netreq_by_query_id, &netreq->node)); - netreq->query_id_registered = &netreq->upstream->netreq_by_query_id; - - GLDNS_ID_SET(netreq->query, query_id); - /* TODO: Review if more EDNS0 handling can be centralised.*/ if (netreq->opt) { _getdns_network_req_clear_upstream_options(netreq); @@ -1430,7 +1636,96 @@ fprintf(stderr, "in stub_tls_write\n"); r = GETDNS_RETURN_GOOD; } else #endif + +#ifdef HAVE_LIBNGHTTP2 + if (upstream->doh_session) { + size_t i; + nghttp2_nv hdrs[5]; + const char *authority = upstream->tls_auth_name[0] + ? upstream->tls_auth_name + : upstream->addr_str; + char path_spc[2048], *path = path_spc; + size_t url_sz, path_sz; + int32_t stream_id; + + url_sz = sizeof("/") - 1 + + strlen(upstream->doh_path) + + sizeof("?dns=") - 1; + path_sz = url_sz + + gldns_b64_ntop_calculate_size(pkt_len); + + if (path_sz > sizeof(path_spc) + && !(path = malloc(path_sz))) + return STUB_TCP_ERROR; + + strcat(strcat( strcpy(path, "/") + , upstream->doh_path), "?dns="); + + path_sz = url_sz + gldns_b64url_ntop(netreq->query, + pkt_len, path + url_sz, path_sz - url_sz); + path[path_sz] = 0; + + GLDNS_ID_SET(netreq->query, 0); + + hdrs[0].name = (uint8_t*)":method"; + hdrs[0].value = (uint8_t*)"GET"; + hdrs[1].name = (uint8_t*)":scheme"; + hdrs[1].value = (uint8_t*)"https"; + hdrs[2].name = (uint8_t*)":authority"; + hdrs[2].value = (uint8_t*)authority; + hdrs[3].name = (uint8_t*)":path"; + hdrs[3].value = (uint8_t*)path; + hdrs[4].name = (uint8_t*)"content-type"; + hdrs[4].value = (uint8_t*)"application/dns-message"; + for (i = 0; i < ARRLEN(hdrs); i++) { + hdrs[i].namelen = strlen((char*)hdrs[i].name); + hdrs[i].valuelen = strlen((char*)hdrs[i].value); + hdrs[i].flags = NGHTTP2_NV_FLAG_NONE; + fprintf(stderr, "HEADER[%d] %s: %s\n" + , (int)i + , (char*)hdrs[i].name + , (char*)hdrs[i].value); + } + stream_id = nghttp2_submit_request( + upstream->doh_session, NULL, + hdrs, ARRLEN(hdrs), NULL, upstream); + + if (stream_id < 0) { + /* TODO: Log error */ + return STUB_TCP_ERROR; + } + query_id_intptr = (intptr_t)stream_id; + netreq->node.key = (void *)query_id_intptr; + _getdns_rbtree_insert( + &netreq->upstream->netreq_by_query_id, + &netreq->node); + netreq->query_id_registered = + &netreq->upstream->netreq_by_query_id; + /* Unqueue the netreq from the write_queue */ + remove_from_write_queue(upstream, netreq); + if (netreq->owner->return_call_reporting) + netreq_equip_tls_debug_info(netreq); + upstream->queries_sent++; + return STUB_NOOP; + } else { +#endif + /* Find a unique query_id not already written (or in + * the write_queue) for that upstream. Register this netreq + * by query_id in the process. + */ + do { + query_id = arc4random(); + query_id_intptr = (intptr_t)query_id; + netreq->node.key = (void *)query_id_intptr; + + } while (!_getdns_rbtree_insert( + &netreq->upstream->netreq_by_query_id, &netreq->node)); + netreq->query_id_registered = &netreq->upstream->netreq_by_query_id; + GLDNS_ID_SET(netreq->query, query_id); r = _getdns_tls_connection_write(tls_obj, netreq->query - 2, pkt_len + 2, &written); +#ifdef HAVE_LIBNGHTTP2 + } +#endif if (r == GETDNS_RETURN_TLS_WANT_READ || r == GETDNS_RETURN_TLS_WANT_WRITE) return STUB_TCP_RETRY; @@ -1439,9 +1734,7 @@ fprintf(stderr, "in stub_tls_write\n"); /* We were able to write everything! Start reading. */ return (int) query_id; - } - return STUB_TCP_ERROR; } @@ -1678,12 +1971,57 @@ upstream_read_cb(void *userarg) DEBUG_STUB("%s %-35s: FD: %d \n", STUB_DEBUG_READ, __FUNC__, upstream->fd); getdns_network_req *netreq; - int q; + int q = STUB_NOOP; uint16_t query_id; intptr_t query_id_intptr; getdns_dns_req *dnsreq; - if (upstream->transport == GETDNS_TRANSPORT_TLS) +#ifdef HAVE_LIBNGHTTP2 + if (!upstream->doh_session) + ; /* pass */ + + else if ((q = tls_connected(upstream))) + ; /* pass */ + + else if (nghttp2_session_want_read(upstream->doh_session)) { + int rv = nghttp2_session_recv(upstream->doh_session); + + if (rv) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR, + "%-40s : Could not receive from DoH connection: %s\n", + upstream->addr_str, + nghttp2_strerror(rv)); + q = STUB_TCP_ERROR; + + } else if (nghttp2_session_want_read(upstream->doh_session)) + return; + + else if (nghttp2_session_want_write(upstream->doh_session)) { + /* Reschedule for reading */ + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + upstream->event.read_cb = upstream_read_cb; + upstream->event.write_cb = upstream_write_cb; + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + return; + } else + q = STUB_CONN_GONE; + + } else if (nghttp2_session_want_write(upstream->doh_session)) { + /* Reschedule for reading */ + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + upstream->event.read_cb = upstream_read_cb; + upstream->event.write_cb = upstream_write_cb; + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + return; + } else + q = STUB_CONN_GONE; +#endif + if (q != STUB_NOOP) + ; /* pass */ + else if (upstream->transport == GETDNS_TRANSPORT_TLS) q = stub_tls_read(upstream, &upstream->tcp, &upstream->upstreams->mf); else @@ -1700,7 +2038,9 @@ upstream_read_cb(void *userarg) case STUB_TCP_ERROR: upstream_failed(upstream, (q == STUB_TCP_ERROR ? 0:1) ); return; - + case STUB_CONN_GONE: + upstream_teardown(upstream); + return; default: /* Lookup netreq */ query_id = (uint16_t) q; @@ -1821,18 +2161,22 @@ upstream_write_cb(void *userarg) { getdns_upstream *upstream = (getdns_upstream *)userarg; getdns_network_req *netreq = upstream->write_queue; - int q; + int q = STUB_NOOP; - if (!netreq) { + if (!netreq +#ifdef HAVE_LIBNGHTTP2 + && !upstream->doh_session +#endif + ) { GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); upstream->event.write_cb = NULL; return; } - - netreq->debug_start_time = _getdns_get_time_as_uintt64(); - DEBUG_STUB("%s %-35s: MSG: %p (writing)\n", STUB_DEBUG_WRITE, - __FUNC__, (void*)netreq); - + if (netreq) { + netreq->debug_start_time = _getdns_get_time_as_uintt64(); + DEBUG_STUB("%s %-35s: MSG: %p (writing)\n", STUB_DEBUG_WRITE, + __FUNC__, (void*)netreq); + } /* Health checks on current connection */ if (upstream->conn_state == GETDNS_CONN_TEARDOWN || upstream->conn_state == GETDNS_CONN_CLOSED || @@ -1841,11 +2185,57 @@ upstream_write_cb(void *userarg) else if (!upstream_working_ok(upstream)) q = STUB_TCP_ERROR; /* Seems ok, now try to write */ - else if (tls_requested(netreq)) + else if (netreq && tls_requested(netreq)) q = stub_tls_write(upstream, &upstream->tcp, netreq); - else + else if (netreq) q = stub_tcp_write(upstream->fd, &upstream->tcp, netreq); +#ifdef HAVE_LIBNGHTTP2 + if (!upstream->doh_session || q != STUB_NOOP) + ; /* pass */ + + else if (nghttp2_session_want_write(upstream->doh_session)) { + int rv = nghttp2_session_send(upstream->doh_session); + + if (rv) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR, + "%-40s : Could not send to DoH connection: %s\n", + upstream->addr_str, + nghttp2_strerror(rv)); + q = STUB_TCP_ERROR; + + } else if (nghttp2_session_want_write(upstream->doh_session)) + return; + + else if (upstream->write_queue) + return; + + else if (nghttp2_session_want_read(upstream->doh_session)) { + /* Reschedule for reading */ + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + upstream->event.read_cb = upstream_read_cb; + upstream->event.write_cb = NULL; + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + return; + } else + q = STUB_CONN_GONE; + + } else if (upstream->write_queue) + return; + + else if (nghttp2_session_want_read(upstream->doh_session)) { + /* Reschedule for reading */ + GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); + upstream->event.read_cb = upstream_read_cb; + upstream->event.write_cb = NULL; + GETDNS_SCHEDULE_EVENT(upstream->loop, + upstream->fd, TIMEOUT_FOREVER, &upstream->event); + return; + } else + q = STUB_CONN_GONE; +#endif switch (q) { case STUB_TCP_MORE_TO_WRITE: /* WSA TODO: if callback is still upstream_write_cb, do it again @@ -1865,11 +2255,13 @@ upstream_write_cb(void *userarg) case STUB_CONN_GONE: case STUB_NO_AUTH: /* Cleaning up after connection or auth check failure. Need to fallback. */ - stub_cleanup(netreq); _getdns_upstream_log(upstream, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_DEBUG, "%-40s : Conn closed: %s - *Failure*\n", upstream->addr_str, (upstream->transport == GETDNS_TRANSPORT_TLS ? "TLS" : "TCP")); + if (!netreq) + return; + stub_cleanup(netreq); if (netreq->owner->return_call_reporting) netreq_equip_tls_debug_info(netreq); if (fallback_on_write(netreq) == STUB_TCP_ERROR) { diff --git a/src/tls.h b/src/tls.h index aefc4d70..ac4892ba 100644 --- a/src/tls.h +++ b/src/tls.h @@ -396,7 +396,7 @@ getdns_return_t _getdns_tls_connection_read(_getdns_tls_connection* conn, uint8_ * @return GETDNS_RETURN_TLS_WANT_WRITE if the write needs to be retried. * @return GETDNS_RETURN_GENERIC_ERROR if write failed. */ -getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, uint8_t* buf, size_t to_write, size_t* written); +getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, const uint8_t* buf, size_t to_write, size_t* written); /** * Free a session.