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