diff --git a/src/config.c b/src/config.c index 876d1864..9f5055a1 100644 --- a/src/config.c +++ b/src/config.c @@ -372,6 +372,19 @@ void read_FTLconf(void) else logg(" DELAY_STARTUP: No delay requested."); + // BLOCK_ESNI + // defaults to: true + config.block_esni = true; + buffer = parse_FTLconf(fp, "BLOCK_ESNI"); + + if(buffer != NULL && strcasecmp(buffer, "false") == 0) + config.block_esni = false; + + if(config.block_esni) + logg(" BLOCK_ESNI: Enabled, blocking _esni.{blocked domain}"); + else + logg(" BLOCK_ESNI: Disabled"); + // Read DEBUG_... setting from pihole-FTL.conf read_debuging_settings(fp); diff --git a/src/config.h b/src/config.h index 01448cd7..284eed24 100644 --- a/src/config.h +++ b/src/config.h @@ -39,6 +39,7 @@ typedef struct { bool parse_arp_cache; bool regex_ignorecase; bool cname_inspection; + bool block_esni; } ConfigStruct; typedef struct { diff --git a/src/datastructure.c b/src/datastructure.c index 36d2ac41..a2ff98b1 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -248,6 +248,7 @@ int findCacheID(int domainID, int clientID) dns_cache->blocking_status = UNKNOWN_BLOCKED; dns_cache->domainID = domainID; dns_cache->clientID = clientID; + dns_cache->force_reply = 0u; // Increase counter by one counters->dns_cache_size++; diff --git a/src/datastructure.h b/src/datastructure.h index 9779a6d3..74c78b8a 100644 --- a/src/datastructure.h +++ b/src/datastructure.h @@ -75,6 +75,7 @@ typedef struct { typedef struct { unsigned char magic; unsigned char blocking_status; + unsigned char force_reply; int domainID; int clientID; int black_regex_idx; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 98db4e58..44452bf6 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -49,6 +49,7 @@ static void query_blocked(queriesData* query, domainsData* domain, clientsData* static unsigned int blocking_flags = 0; static union all_addr blocking_addrp_v4 = {{ 0 }}; static union all_addr blocking_addrp_v6 = {{ 0 }}; +static unsigned char force_next_DNS_reply = 0u; // Adds debug information to the regular pihole.log file char debug_dnsmasq_lines = 0; @@ -56,6 +57,61 @@ char debug_dnsmasq_lines = 0; unsigned char* pihole_privacylevel = &config.privacylevel; const char flagnames[][12] = {"F_IMMORTAL ", "F_NAMEP ", "F_REVERSE ", "F_FORWARD ", "F_DHCP ", "F_NEG ", "F_HOSTS ", "F_IPV4 ", "F_IPV6 ", "F_BIGNAME ", "F_NXDOMAIN ", "F_CNAME ", "F_DNSKEY ", "F_CONFIG ", "F_DS ", "F_DNSSECOK ", "F_UPSTREAM ", "F_RRNAME ", "F_SERVER ", "F_QUERY ", "F_NOERR ", "F_AUTH ", "F_DNSSEC ", "F_KEYTAG ", "F_SECSTAT ", "F_NO_RR ", "F_IPSET ", "F_NOEXTRA ", "F_SERVFAIL", "F_RCODE"}; +static bool check_domain_blocked(const char *domainString, const int clientID, + clientsData *client, queriesData *query, DNSCacheData *dns_cache, + const char **blockingreason, unsigned char *new_status) +{ + // Check domains against exact blacklist + // Skipped when the domain is whitelisted + bool blockDomain = false; + if(in_blacklist(domainString, clientID, client)) + { + // We block this domain + blockDomain = true; + *new_status = QUERY_BLACKLIST; + *blockingreason = "exactly blacklisted"; + + // Mark domain as exactly blacklisted for this client + dns_cache->blocking_status = BLACKLIST_BLOCKED; + return true; + } + + // Check domains against gravity domains + // Skipped when the domain is whitelisted or blocked by exact blacklist + if(!query->whitelisted && !blockDomain && + in_gravity(domainString, clientID, client)) + { + // We block this domain + blockDomain = true; + *new_status = QUERY_GRAVITY; + *blockingreason = "gravity blocked"; + + // Mark domain as gravity blocked for this client + dns_cache->blocking_status = GRAVITY_BLOCKED; + return true; + } + + // Check domain against blacklist regex filters + // Skipped when the domain is whitelisted or blocked by exact blacklist or gravity + int regex_idx = 0; + if(!query->whitelisted && !blockDomain && + (regex_idx = match_regex(domainString, clientID, REGEX_BLACKLIST)) > -1) + { + // We block this domain + blockDomain = true; + *new_status = QUERY_REGEX; + *blockingreason = "regex blacklisted"; + + // Mark domain as regex matched for this client + dns_cache->blocking_status = REGEX_BLOCKED; + dns_cache->black_regex_idx = regex_idx; + return true; + } + + // Not blocked + return false; +} + static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const char **blockingreason, const char* file, const int line) { @@ -108,6 +164,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c if(!query->whitelisted) { query_blocked(query, domain, client, QUERY_BLACKLIST); + force_next_DNS_reply = dns_cache->force_reply; return true; } break; @@ -127,6 +184,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c if(!query->whitelisted) { query_blocked(query, domain, client, QUERY_GRAVITY); + force_next_DNS_reply = dns_cache->force_reply; return true; } break; @@ -139,6 +197,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c if(config.debug & DEBUG_QUERIES) { logg("%s is known as %s", domainstr, *blockingreason); + force_next_DNS_reply = dns_cache->force_reply; } // Do not block if the entire query is to be permitted @@ -189,52 +248,30 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c // Check whitelist (exact + regex) for match const char *domainString = getstr(domain->domainpos); + const char *blockedDomain = domainString; query->whitelisted = in_whitelist(domainString, clientID, client); - // Check domains against exact blacklist - // Skipped when the domain is whitelisted bool blockDomain = false; unsigned char new_status = QUERY_UNKNOWN; - if(!query->whitelisted && - in_blacklist(domainString, clientID, client)) + // Check blacklist (exact + regex) and gravity for queried domain + if(!query->whitelisted) { - // We block this domain - blockDomain = true; - new_status = QUERY_BLACKLIST; - *blockingreason = "exactly blacklisted"; - - // Mark domain as exactly blacklisted for this client - dns_cache->blocking_status = BLACKLIST_BLOCKED; + blockDomain = check_domain_blocked(domainString, clientID, client, query, dns_cache, blockingreason, &new_status); } - // Check domains against gravity domains - // Skipped when the domain is whitelisted or blocked by exact blacklist - if(!query->whitelisted && !blockDomain && - in_gravity(domainString, clientID, client)) + // Check blacklist (exact + regex) and gravity for _esni.domain if enabled (defaulting to true) + if(config.block_esni && !query->whitelisted && !blockDomain && strncasecmp(domainString, "_esni.", 6u) == 0) { - // We block this domain - blockDomain = true; - new_status = QUERY_GRAVITY; - *blockingreason = "gravity blocked"; + blockDomain = check_domain_blocked(domainString + 6u, clientID, client, query, dns_cache, blockingreason, &new_status); - // Mark domain as gravity blocked for this client - dns_cache->blocking_status = GRAVITY_BLOCKED; - } - - // Check domain against blacklist regex filters - // Skipped when the domain is whitelisted or blocked by exact blacklist or gravity - int regex_idx = 0; - if(!query->whitelisted && !blockDomain && - (regex_idx = match_regex(domainString, clientID, REGEX_BLACKLIST)) > -1) - { - // We block this domain - blockDomain = true; - new_status = QUERY_REGEX; - *blockingreason = "regex blacklisted"; - - // Mark domain as regex matched for this client - dns_cache->blocking_status = REGEX_BLOCKED; - dns_cache->black_regex_idx = regex_idx; + if(blockDomain) + { + // Truncate "_esni." from queried domain if the parenting domain was the reason for blocking this query + blockedDomain = domainString + 6u; + // Force next DNS reply to be NXDOMAIN for _esni.* queries + force_next_DNS_reply = NXDOMAIN; + dns_cache->force_reply = NXDOMAIN; + } } // Common actions regardless what the possible blocking reason is @@ -245,7 +282,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c // Debug output if(config.debug & DEBUG_QUERIES) - logg("Blocking %s as domain is %s", domainString, *blockingreason); + logg("Blocking %s as %s is %s", domainString, blockedDomain, *blockingreason); } else { @@ -577,6 +614,20 @@ bool _FTL_new_query(const unsigned int flags, const char *name, void _FTL_get_blocking_metadata(union all_addr **addrp, unsigned int *flags, const char* file, const int line) { + // Check first if we need to force our reply to something different than the + // default/configured blocking mode For instance, we need to force NXDOMAIN + // for intercepted _esni.* queries + if(force_next_DNS_reply != 0u) + { + if(force_next_DNS_reply == NXDOMAIN) + { + *flags = F_NXDOMAIN; + // Reset DNS reply forcing + force_next_DNS_reply = 0u; + return; + } + } + // Add flags according to current blocking mode // We bit-add here as flags already contains either F_IPV4 or F_IPV6 *flags |= blocking_flags;