diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index db1e528fa..a5b71315a 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -2622,6 +2622,16 @@ the client query). If enabled, the address check is skipped when the client query contains an ECS record. And the lookup in the regular cache is skipped. Default is no. .TP +.B client\-subnet\-address\-override\-ipv4: \fI\fR +Override client source address in queries to the provided IPv4 subnet. Only +applies if the query includes the ECS option and the ECS address family is IPv4. +Default is to use the actual IPv4 subnet of the original client query. +.TP +.B client\-subnet\-address\-override\-ipv6: \fI\fR +Override client source address in queries to the provided IPv6 subnet. Only +applies if the query includes the ECS option and the ECS address family is IPv6. +Default is to use the actual IPv6 subnet of the original client query. +.TP .B max\-client\-subnet\-ipv6: \fI\fR Specifies the maximum prefix length of the client source address we are willing to expose to third parties for IPv6. Defaults to 56. diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c index ead720f34..c7d089b1c 100644 --- a/edns-subnet/subnetmod.c +++ b/edns-subnet/subnetmod.c @@ -249,6 +249,31 @@ subnetmod_init(struct module_env *env, int id) env->modinfo[id] = NULL; return 0; } + /* Copy address override settings */ + if(env->cfg->client_subnet_address_override_ipv4) { + struct sockaddr_storage *addr = &sn_env->address_override_v4; + char *ipstr = env->cfg->client_subnet_address_override_ipv4; + socklen_t len = 0; + if(!ipstrtoaddr(ipstr, 0, addr, &len) || addr->ss_family != AF_INET) { + log_err("subnetcache: error parsing ipv4 address override: '%s'", ipstr); + free(sn_env); + env->modinfo[id] = NULL; + return 0; + } + sn_env->do_address_override_v4 = 1; + } + if(env->cfg->client_subnet_address_override_ipv6) { + struct sockaddr_storage *addr = &sn_env->address_override_v6; + char *ipstr = env->cfg->client_subnet_address_override_ipv6; + socklen_t len = 0; + if(!ipstrtoaddr(ipstr, 0, addr, &len) || addr->ss_family != AF_INET6) { + log_err("subnetcache: error parsing ipv6 address override: '%s'", ipstr); + free(sn_env); + env->modinfo[id] = NULL; + return 0; + } + sn_env->do_address_override_v6 = 1; + } verbose(VERB_QUERY, "subnetcache: option registered (%d)", env->cfg->client_subnet_opcode); @@ -684,7 +709,7 @@ parse_subnet_option(struct edns_option* ecs_option, struct ecs_data* ecs) void subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs, - struct config_file* cfg) + struct config_file* cfg, const struct subnet_env* sne) { void* sinaddr; @@ -692,7 +717,12 @@ subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs, if(((struct sockaddr_in*)ss)->sin_family == AF_INET) { ecs->subnet_source_mask = cfg->max_client_subnet_ipv4; ecs->subnet_addr_fam = EDNSSUBNET_ADDRFAM_IP4; - sinaddr = &((struct sockaddr_in*)ss)->sin_addr; + if (sne->do_address_override_v4) { + sinaddr = &((struct sockaddr_in*) + (&sne->address_override_v4))->sin_addr; + } else { + sinaddr = &((struct sockaddr_in*)ss)->sin_addr; + } if (!copy_clear( ecs->subnet_addr, INET6_SIZE, (uint8_t *)sinaddr, INET_SIZE, ecs->subnet_source_mask)) { @@ -703,7 +733,12 @@ subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs, else { ecs->subnet_source_mask = cfg->max_client_subnet_ipv6; ecs->subnet_addr_fam = EDNSSUBNET_ADDRFAM_IP6; - sinaddr = &((struct sockaddr_in6*)ss)->sin6_addr; + if (sne->do_address_override_v6) { + sinaddr = &((struct sockaddr_in6*) + (&sne->address_override_v6))->sin6_addr; + } else { + sinaddr = &((struct sockaddr_in6*)ss)->sin6_addr; + } if (!copy_clear( ecs->subnet_addr, INET6_SIZE, (uint8_t *)sinaddr, INET6_SIZE, ecs->subnet_source_mask)) { @@ -839,12 +874,12 @@ subnetmod_operate(struct module_qstate *qstate, enum module_ev event, else if(qstate->mesh_info->reply_list) { subnet_option_from_ss( &qstate->mesh_info->reply_list->query_reply.client_addr, - &sq->ecs_client_in, qstate->env->cfg); + &sq->ecs_client_in, qstate->env->cfg, sne); } else if(qstate->client_addr.ss_family != AF_UNSPEC) { subnet_option_from_ss( &qstate->client_addr, - &sq->ecs_client_in, qstate->env->cfg); + &sq->ecs_client_in, qstate->env->cfg, sne); } if(sq->ecs_client_in.subnet_validdata == 0) { diff --git a/edns-subnet/subnetmod.h b/edns-subnet/subnetmod.h index 1ff8a23ec..39496c299 100644 --- a/edns-subnet/subnetmod.h +++ b/edns-subnet/subnetmod.h @@ -59,6 +59,14 @@ struct subnet_env { struct slabhash* subnet_msg_cache; /** access control, which upstream servers we send client address */ struct ecs_whitelist* whitelist; + /** whether to override client source address for IPv4 */ + int do_address_override_v4; + /** whether to override client source address for IPv6 */ + int do_address_override_v6; + /** overide client source address value for IPv4 */ + struct sockaddr_storage address_override_v4; + /** overide client source address value for IPv6 */ + struct sockaddr_storage address_override_v6; /** allocation service */ struct alloc_cache alloc; lock_rw_type biglock; @@ -159,5 +167,5 @@ void subnet_ecs_opt_list_append(struct ecs_data* ecs, struct edns_option** list, /** Create ecs_data from the sockaddr_storage information. */ void subnet_option_from_ss(struct sockaddr_storage *ss, struct ecs_data* ecs, - struct config_file* cfg); + struct config_file* cfg, const struct subnet_env* sne); #endif /* SUBNETMOD_H */ diff --git a/util/config_file.c b/util/config_file.c index aca0039d4..9372d7fe1 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -226,6 +226,8 @@ config_create(void) cfg->client_subnet = NULL; cfg->client_subnet_zone = NULL; cfg->client_subnet_opcode = LDNS_EDNS_CLIENT_SUBNET; + cfg->client_subnet_address_override_ipv4 = NULL; + cfg->client_subnet_address_override_ipv6 = NULL; cfg->client_subnet_always_forward = 0; cfg->max_client_subnet_ipv4 = 24; cfg->max_client_subnet_ipv6 = 56; @@ -1255,6 +1257,8 @@ config_get_option(struct config_file* cfg, const char* opt, #ifdef CLIENT_SUBNET else O_LST(opt, "send-client-subnet", client_subnet) else O_LST(opt, "client-subnet-zone", client_subnet_zone) + else O_STR(opt, "client-subnet-address-override-ipv4", client_subnet_address_override_ipv4) + else O_STR(opt, "client-subnet-address-override-ipv6", client_subnet_address_override_ipv6) else O_DEC(opt, "max-client-subnet-ipv4", max_client_subnet_ipv4) else O_DEC(opt, "max-client-subnet-ipv6", max_client_subnet_ipv6) else O_DEC(opt, "min-client-subnet-ipv4", min_client_subnet_ipv4) @@ -1689,6 +1693,8 @@ config_delete(struct config_file* cfg) #ifdef CLIENT_SUBNET config_delstrlist(cfg->client_subnet); config_delstrlist(cfg->client_subnet_zone); + free(cfg->client_subnet_address_override_ipv4); + free(cfg->client_subnet_address_override_ipv6); #endif free(cfg->identity); free(cfg->version); diff --git a/util/config_file.h b/util/config_file.h index 2969f8433..66509f1dd 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -267,6 +267,10 @@ struct config_file { struct config_strlist* client_subnet_zone; /** opcode assigned by IANA for edns0-client-subnet option */ uint16_t client_subnet_opcode; + /** Override the outgoing client subnet source address to this value (IPv4) */ + char *client_subnet_address_override_ipv4; + /** Override the outgoing client subnet source address to this value (IPv6) */ + char *client_subnet_address_override_ipv6; /** Do not check whitelist if incoming query contains an ECS record */ int client_subnet_always_forward; /** Subnet length we are willing to give up privacy for */ diff --git a/util/configlexer.lex b/util/configlexer.lex index 4c0416f73..7a643c5eb 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -376,6 +376,8 @@ send-client-subnet{COLON} { YDVAR(1, VAR_SEND_CLIENT_SUBNET) } client-subnet-zone{COLON} { YDVAR(1, VAR_CLIENT_SUBNET_ZONE) } client-subnet-always-forward{COLON} { YDVAR(1, VAR_CLIENT_SUBNET_ALWAYS_FORWARD) } client-subnet-opcode{COLON} { YDVAR(1, VAR_CLIENT_SUBNET_OPCODE) } +client-subnet-address-override-ipv4{COLON} { YDVAR(1, VAR_CLIENT_SUBNET_ADDRESS_OVERRIDE_IPV4) } +client-subnet-address-override-ipv6{COLON} { YDVAR(1, VAR_CLIENT_SUBNET_ADDRESS_OVERRIDE_IPV6) } max-client-subnet-ipv4{COLON} { YDVAR(1, VAR_MAX_CLIENT_SUBNET_IPV4) } max-client-subnet-ipv6{COLON} { YDVAR(1, VAR_MAX_CLIENT_SUBNET_IPV6) } min-client-subnet-ipv4{COLON} { YDVAR(1, VAR_MIN_CLIENT_SUBNET_IPV4) } diff --git a/util/configparser.y b/util/configparser.y index c10a5f475..160334ba2 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -150,6 +150,8 @@ extern struct config_parser_state* cfg_parser; %token VAR_IP_RATELIMIT_BACKOFF VAR_RATELIMIT_BACKOFF %token VAR_SEND_CLIENT_SUBNET VAR_CLIENT_SUBNET_ZONE %token VAR_CLIENT_SUBNET_ALWAYS_FORWARD VAR_CLIENT_SUBNET_OPCODE +%token VAR_CLIENT_SUBNET_ADDRESS_OVERRIDE_IPV4 +%token VAR_CLIENT_SUBNET_ADDRESS_OVERRIDE_IPV6 %token VAR_MAX_CLIENT_SUBNET_IPV4 VAR_MAX_CLIENT_SUBNET_IPV6 %token VAR_MIN_CLIENT_SUBNET_IPV4 VAR_MIN_CLIENT_SUBNET_IPV6 %token VAR_MAX_ECS_TREE_SIZE_IPV4 VAR_MAX_ECS_TREE_SIZE_IPV6 @@ -302,6 +304,7 @@ content_server: server_num_threads | server_verbosity | server_port | server_max_sent_count | server_max_query_restarts | server_send_client_subnet | server_client_subnet_zone | server_client_subnet_always_forward | server_client_subnet_opcode | + server_client_subnet_address_override_ipv4 | server_client_subnet_address_override_ipv6 | server_max_client_subnet_ipv4 | server_max_client_subnet_ipv6 | server_min_client_subnet_ipv4 | server_min_client_subnet_ipv6 | server_max_ecs_tree_size_ipv4 | server_max_ecs_tree_size_ipv6 | @@ -693,6 +696,26 @@ server_client_subnet_opcode: VAR_CLIENT_SUBNET_OPCODE STRING_ARG free($2); } ; +server_client_subnet_address_override_ipv4: VAR_CLIENT_SUBNET_ADDRESS_OVERRIDE_IPV4 STRING_ARG + { + #ifdef CLIENT_SUBNET + OUTYY(("P(client_subnet_address_override_ipv4:%s)\n", $2)); + cfg_parser->cfg->client_subnet_address_override_ipv4 = $2; + #else + OUTYY(("P(Compiled without edns subnet option, ignoring)\n")); + #endif + } + ; +server_client_subnet_address_override_ipv6: VAR_CLIENT_SUBNET_ADDRESS_OVERRIDE_IPV6 STRING_ARG + { + #ifdef CLIENT_SUBNET + OUTYY(("P(client_subnet_address_override_ipv6:%s)\n", $2)); + cfg_parser->cfg->client_subnet_address_override_ipv6 = $2; + #else + OUTYY(("P(Compiled without edns subnet option, ignoring)\n")); + #endif + } + ; server_max_client_subnet_ipv4: VAR_MAX_CLIENT_SUBNET_IPV4 STRING_ARG { #ifdef CLIENT_SUBNET