diff --git a/src/config.c b/src/config.c index 14c5d349..76e49c07 100644 --- a/src/config.c +++ b/src/config.c @@ -502,43 +502,129 @@ void read_FTLconf(void) else logg(" RATE_LIMIT: Disabled"); - // REPLY_ADDR4 + // LOCAL_IPV4 // Use a specific IP address instead of automatically detecting the - // IPv4 interface address a query arrived on + // IPv4 interface address a query arrived on for A hostname queries // defaults to: not set - config.reply_addr.overwrite_v4 = false; - config.reply_addr.v4.s_addr = 0; - buffer = parse_FTLconf(fp, "REPLY_ADDR4"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &config.reply_addr.v4)) - config.reply_addr.overwrite_v4 = true; + config.reply_addr.own_host.overwrite_v4 = false; + config.reply_addr.own_host.v4.s_addr = 0; + buffer = parse_FTLconf(fp, "LOCAL_IPV4"); + if(buffer != NULL && inet_pton(AF_INET, buffer, &config.reply_addr.own_host.v4)) + config.reply_addr.own_host.overwrite_v4 = true; - if(config.reply_addr.overwrite_v4) + if(config.reply_addr.own_host.overwrite_v4) { char addr[INET_ADDRSTRLEN] = { 0 }; - inet_ntop(AF_INET, &config.reply_addr.v4, addr, INET_ADDRSTRLEN); - logg(" REPLY_ADDR4: Using IPv4 address %s in IP blocking mode", addr); + inet_ntop(AF_INET, &config.reply_addr.own_host.v4, addr, INET_ADDRSTRLEN); + logg(" LOCAL_IPV4: Using IPv4 address %s for pi.hole and hostname", addr); } else - logg(" REPLY_ADDR4: Automatic interface-dependent detection of address"); + logg(" LOCAL_IPV4: Automatic interface-dependent detection of address"); - // REPLY_ADDR6 + // LOCAL_IPV6 // Use a specific IP address instead of automatically detecting the - // IPv6 interface address a query arrived on + // IPv6 interface address a query arrived on for AAAA hostname queries // defaults to: not set - config.reply_addr.overwrite_v6 = false; - memset(&config.reply_addr.v6, 0, sizeof(config.reply_addr.v6)); - buffer = parse_FTLconf(fp, "REPLY_ADDR6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.reply_addr.v6)) - config.reply_addr.overwrite_v6 = true; + config.reply_addr.own_host.overwrite_v6 = false; + memset(&config.reply_addr.own_host.v6, 0, sizeof(config.reply_addr.own_host.v6)); + buffer = parse_FTLconf(fp, "LOCAL_IPV6"); + if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.reply_addr.own_host.v6)) + config.reply_addr.own_host.overwrite_v6 = true; - if(config.reply_addr.overwrite_v6) + if(config.reply_addr.own_host.overwrite_v6) { char addr[INET6_ADDRSTRLEN] = { 0 }; - inet_ntop(AF_INET6, &config.reply_addr.v6, addr, INET6_ADDRSTRLEN); - logg(" REPLY_ADDR6: Using IPv6 address %s in IP blocking mode", addr); + inet_ntop(AF_INET6, &config.reply_addr.own_host.v6, addr, INET6_ADDRSTRLEN); + logg(" LOCAL_IPV6: Using IPv6 address %s for pi.hole and hostname", addr); } else - logg(" REPLY_ADDR6: Automatic interface-dependent detection of address"); + logg(" LOCAL_IPV6: Automatic interface-dependent detection of address"); + + // BLOCK_IPV4 + // Use a specific IPv4 address for IP blocking mode replies + // defaults to: REPLY_ADDR4 setting + config.reply_addr.ip_blocking.overwrite_v4 = false; + config.reply_addr.ip_blocking.v4.s_addr = 0; + buffer = parse_FTLconf(fp, "BLOCK_IPV4"); + if(buffer != NULL && inet_pton(AF_INET, buffer, &config.reply_addr.ip_blocking.v4)) + config.reply_addr.ip_blocking.overwrite_v4 = true; + + if(config.reply_addr.ip_blocking.overwrite_v4) + { + char addr[INET_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET, &config.reply_addr.ip_blocking.v4, addr, INET_ADDRSTRLEN); + logg(" BLOCK_IPV4: Using IPv4 address %s in IP blocking mode", addr); + } + else + logg(" BLOCK_IPV4: Automatic interface-dependent detection of address"); + + // BLOCK_IPV6 + // Use a specific IPv6 address for IP blocking mode replies + // defaults to: REPLY_ADDR6 setting + config.reply_addr.ip_blocking.overwrite_v6 = false; + memset(&config.reply_addr.ip_blocking.v6, 0, sizeof(config.reply_addr.own_host.v6)); + buffer = parse_FTLconf(fp, "BLOCK_IPV6"); + if(buffer != NULL && inet_pton(AF_INET6, buffer, &config.reply_addr.ip_blocking.v6)) + config.reply_addr.ip_blocking.overwrite_v6 = true; + + if(config.reply_addr.ip_blocking.overwrite_v6) + { + char addr[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET6, &config.reply_addr.ip_blocking.v6, addr, INET6_ADDRSTRLEN); + logg(" BLOCK_IPV6: Using IPv6 address %s in IP blocking mode", addr); + } + else + logg(" BLOCK_IPV6: Automatic interface-dependent detection of address"); + + // REPLY_ADDR4 (deprecated setting) + // Use a specific IP address instead of automatically detecting the + // IPv4 interface address a query arrived on A hostname and IP blocked queries + // defaults to: not set + struct in_addr reply_addr4; + buffer = parse_FTLconf(fp, "REPLY_ADDR4"); + if(buffer != NULL && inet_pton(AF_INET, buffer, &reply_addr4)) + { + if(config.reply_addr.own_host.overwrite_v4 || config.reply_addr.ip_blocking.overwrite_v4) + { + logg(" WARNING: Ignoring REPLY_ADDR4 as LOCAL_IPV4 or BLOCK_IPV4 has been specified."); + } + else + { + config.reply_addr.own_host.overwrite_v4 = true; + memcpy(&config.reply_addr.own_host.v4, &reply_addr4, sizeof(reply_addr4)); + config.reply_addr.ip_blocking.overwrite_v4 = true; + memcpy(&config.reply_addr.ip_blocking.v4, &reply_addr4, sizeof(reply_addr4)); + + char addr[INET_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET, &reply_addr4, addr, INET_ADDRSTRLEN); + logg(" REPLY_ADDR4: Using IPv4 address %s instead of automatically determined IP address", addr); + } + } + + // REPLY_ADDR6 (deprecated setting) + // Use a specific IP address instead of automatically detecting the + // IPv4 interface address a query arrived on A hostname and IP blocked queries + // defaults to: not set + struct in6_addr reply_addr6; + buffer = parse_FTLconf(fp, "REPLY_ADDR6"); + if(buffer != NULL && inet_pton(AF_INET, buffer, &reply_addr6)) + { + if(config.reply_addr.own_host.overwrite_v6 || config.reply_addr.ip_blocking.overwrite_v6) + { + logg(" WARNING: Ignoring REPLY_ADDR6 as LOCAL_IPV6 or BLOCK_IPV6 has been specified."); + } + else + { + config.reply_addr.own_host.overwrite_v6 = true; + memcpy(&config.reply_addr.own_host.v6, &reply_addr6, sizeof(reply_addr6)); + config.reply_addr.ip_blocking.overwrite_v6 = true; + memcpy(&config.reply_addr.ip_blocking.v6, &reply_addr6, sizeof(reply_addr6)); + + char addr[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET6, &reply_addr6, addr, INET6_ADDRSTRLEN); + logg(" REPLY_ADDR6: Using IPv6 address %s instead of automatically determined IP address", addr); + } + } // SHOW_DNSSEC // Should FTL analyze and include automatically generated DNSSEC queries in the Query Log? diff --git a/src/config.h b/src/config.h index 8836bb2e..332d2a4a 100644 --- a/src/config.h +++ b/src/config.h @@ -77,13 +77,21 @@ typedef struct { enum debug_flags debug; time_t DBinterval; struct { - bool overwrite_v4 :1; - bool overwrite_v6 :1; - struct in_addr v4; - struct in6_addr v6; + struct { + bool overwrite_v4 :1; + bool overwrite_v6 :1; + struct in_addr v4; + struct in6_addr v6; + } own_host; + struct { + bool overwrite_v4 :1; + bool overwrite_v6 :1; + struct in_addr v4; + struct in6_addr v6; + } ip_blocking; } reply_addr; } ConfigStruct; -ASSERT_SIZEOF(ConfigStruct, 88, 80, 80); +ASSERT_SIZEOF(ConfigStruct, 112, 104, 104); typedef struct { const char* conf; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 68e84130..664a4504 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -73,7 +73,6 @@ static char *get_ptrname(struct in_addr *addr); // Static blocking metadata static const char *blockingreason = ""; -static union all_addr null_addrp = {{ 0 }}; static enum reply_type force_next_DNS_reply = REPLY_UNKNOWN; static int last_regex_idx = -1; static struct ptr_record *pihole_ptr = NULL; @@ -322,21 +321,28 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len // Add A answer record if requested if(flags & F_IPV4) { - union all_addr *addr = &null_addrp; + union all_addr addr = {{ 0 }}; // Overwrite with IP address if requested if(redirecting) - addr = &redirect_addr4; + memcpy(&addr, &redirect_addr4, sizeof(addr)); else if(config.blockingmode == MODE_IP || config.blockingmode == MODE_IP_NODATA_AAAA || forced_ip) - addr = &next_iface.addr4; + { + if(hostname && config.reply_addr.own_host.overwrite_v4) + memcpy(&addr, &config.reply_addr.own_host.v4, sizeof(addr)); + else if(!hostname && config.reply_addr.ip_blocking.overwrite_v4) + memcpy(&addr, &config.reply_addr.ip_blocking.v4, sizeof(addr)); + else + memcpy(&addr, &next_iface.addr4, sizeof(addr)); + } // Debug logging if(config.debug & DEBUG_QUERIES) { char ip[ADDRSTRLEN+1] = { 0 }; - alladdr_extract_ip(addr, AF_INET, ip); + alladdr_extract_ip(&addr, AF_INET, ip); logg(" Adding RR: \"%s A %s\"", name, ip); } @@ -344,27 +350,34 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len header->ancount = htons(ntohs(header->ancount) + 1); if(add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, hostname ? daemon->local_ttl : config.block_ttl, - NULL, T_A, C_IN, (char*)"4", &addr->addr4)) - log_query(flags & ~F_IPV6, name, addr, (char*)blockingreason, 0); + NULL, T_A, C_IN, (char*)"4", &addr.addr4)) + log_query(flags & ~F_IPV6, name, &addr, (char*)blockingreason, 0); } // Add AAAA answer record if requested if(flags & F_IPV6) { - union all_addr *addr = &null_addrp; + union all_addr addr = {{ 0 }}; // Overwrite with IP address if requested if(redirecting) - addr = &redirect_addr6; + memcpy(&addr, &redirect_addr6, sizeof(addr)); else if(config.blockingmode == MODE_IP || forced_ip) - addr = &next_iface.addr6; + { + if(hostname && config.reply_addr.own_host.overwrite_v6) + memcpy(&addr, &config.reply_addr.own_host.v6, sizeof(addr)); + else if(!hostname && config.reply_addr.ip_blocking.overwrite_v6) + memcpy(&addr, &config.reply_addr.ip_blocking.v6, sizeof(addr)); + else + memcpy(&addr, &next_iface.addr6, sizeof(addr)); + } // Debug logging if(config.debug & DEBUG_QUERIES) { char ip[ADDRSTRLEN+1] = { 0 }; - alladdr_extract_ip(addr, AF_INET6, ip); + alladdr_extract_ip(&addr, AF_INET6, ip); logg(" Adding RR: \"%s AAAA %s\"", name, ip); } @@ -372,8 +385,8 @@ size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len header->ancount = htons(ntohs(header->ancount) + 1); if(add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, hostname ? daemon->local_ttl : config.block_ttl, - NULL, T_AAAA, C_IN, (char*)"6", &addr->addr6)) - log_query(flags & ~F_IPV4, name, addr, (char*)blockingreason, 0); + NULL, T_AAAA, C_IN, (char*)"6", &addr.addr6)) + log_query(flags & ~F_IPV4, name, &addr, (char*)blockingreason, 0); } // Log empty replies @@ -507,8 +520,12 @@ bool _FTL_new_query(const unsigned int flags, const char *name, // Send NODATA when the current interface doesn't have // the requested IP address, for instance AAAA on an // virtual interface that has only an IPv4 address - if((querytype == TYPE_A && !next_iface.haveIPv4) || - (querytype == TYPE_AAAA && !next_iface.haveIPv6)) + if((querytype == TYPE_A && + !next_iface.haveIPv4 && + !config.reply_addr.own_host.overwrite_v4) || + (querytype == TYPE_AAAA && + !next_iface.haveIPv6 && + !config.reply_addr.own_host.overwrite_v6)) force_next_DNS_reply = REPLY_NODATA; else force_next_DNS_reply = REPLY_IP; @@ -820,40 +837,14 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam if(config.debug & DEBUG_NETWORKING) logg("Interfaces: Called from %s:%d", short_path(file), line); - // Copy overwrite addresses if configured via REPLY_ADDR4 and/or REPLY_ADDR6 settings - if(config.reply_addr.overwrite_v4) - { - memcpy(&next_iface.addr4, &config.reply_addr.v4, sizeof(config.reply_addr.v4)); - next_iface.haveIPv4 = true; - - if(config.debug & DEBUG_NETWORKING) - { - char buffer[ADDRSTRLEN+1] = { 0 }; - inet_ntop(AF_INET, &next_iface.addr4, buffer, ADDRSTRLEN); - logg("Configuration overwrites IPv4 address: %s", buffer); - } - } - if(config.reply_addr.overwrite_v6) - { - memcpy(&next_iface.addr6, &config.reply_addr.v6, sizeof(config.reply_addr.v6)); - next_iface.haveIPv6 = true; - - if(config.debug & DEBUG_NETWORKING) - { - char buffer[ADDRSTRLEN+1] = { 0 }; - inet_ntop(AF_INET6, &next_iface.addr6, buffer, ADDRSTRLEN); - logg("Configuration overwrites IPv6 address: %s", buffer); - } - } - // Use dummy when interface record is not available next_iface.name[0] = '-'; next_iface.name[1] = '\0'; // Check if we need to identify the receving interface by its address if(!recviface && addr && - ((addrfamily == AF_INET && addr->addr4.s_addr != 0) || - (addrfamily == AF_INET6 && addr->addr6.s6_addr[0] != 0))) + ((addrfamily == AF_INET && addr->addr4.s_addr != INADDR_ANY) || + (addrfamily == AF_INET6 && !IN6_IS_ADDR_UNSPECIFIED(&addr->addr6)))) { if(config.debug & DEBUG_NETWORKING) { @@ -953,17 +944,6 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam strncpy(next_iface.name, iname, sizeof(next_iface.name)-1); next_iface.name[sizeof(next_iface.name)-1] = '\0'; - // Check if this family type is overwritten by config settings - // We logged this above - if((config.reply_addr.overwrite_v4 && family == AF_INET) || - (config.reply_addr.overwrite_v6 && family == AF_INET6)) - { - if(config.debug & DEBUG_NETWORKING) - logg(" - SKIP IPv%d interface %s: REPLY_ADDR%d used", - family == AF_INET ? 4 : 6, iname, family == AF_INET ? 4 : 6); - continue; - } - bool isULA = false, isGUA = false, isLL = false; // Check if this address is different from 0000:0000:0000:0000:0000:0000:0000:0000 if(family == AF_INET6 && memcmp(&next_iface.addr6.addr6, &iface->addr.in6.sin6_addr, sizeof(iface->addr.in6.sin6_addr)) != 0) diff --git a/test/pihole-FTL.conf b/test/pihole-FTL.conf index 060727c3..2d562a67 100644 --- a/test/pihole-FTL.conf +++ b/test/pihole-FTL.conf @@ -2,3 +2,7 @@ DEBUG_ALL=true RESOLVE_IPV4=no RESOLVE_IPV6=no CHECK_LOAD=false +LOCAL_IPV4=10.100.0.10 +LOCAL_IPV6=fe80::10 +BLOCK_IPV4=10.100.0.11 +BLOCK_IPV6=fe80::11 diff --git a/test/test_suite.bats b/test/test_suite.bats index d191034f..61797a17 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -1279,3 +1279,33 @@ printf "%s\n" "${lines[@]}" [[ "${lines[0]}" == "" ]] } + +@test "Pi-hole uses LOCAL_IPV4/6 for pi.hole" { + run bash -c "dig A pi.hole +short @127.0.0.1" + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "10.100.0.10" ]] + run bash -c "dig AAAA pi.hole +short @127.0.0.1" + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "fe80::10" ]] +} + +@test "Pi-hole uses LOCAL_IPV4/6 for hostname" { + run bash -c "dig A $(hostname) +short @127.0.0.1" + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "10.100.0.10" ]] + run bash -c "dig AAAA $(hostname) +short @127.0.0.1" + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "fe80::10" ]] +} + +@test "Pi-hole uses BLOCK_IPV4/6 for blocked domain" { + echo "BLOCKINGMODE=IP" >> /etc/pihole/pihole-FTL.conf + run bash -c "kill -HUP $(cat /run/pihole-FTL.pid)" + sleep 2 + run bash -c "dig A blacklisted.ftl +short @127.0.0.1" + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "10.100.0.11" ]] + run bash -c "dig AAAA blacklisted.ftl +short @127.0.0.1" + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "fe80::11" ]] +}