diff --git a/FTL.h b/FTL.h index 4bbeabc3..3194612a 100644 --- a/FTL.h +++ b/FTL.h @@ -159,12 +159,16 @@ typedef struct { int count; int failed; char *ip; + char *name; + bool new; } forwardedDataStruct; typedef struct { unsigned char magic; int count; char *ip; + char *name; + bool new; } clientsDataStruct; typedef struct { diff --git a/Makefile b/Makefile index 44bf59bd..883fcaff 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ DNSMASQOPTS = -DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC -DNO_FORK # Flags for compiling with libidn2: -DHAVE_LIBIDN2 -DIDN2_VERSION_NUMBER=0x02000003 FTLDEPS = FTL.h routines.h version.h api.h dnsmasq_interface.h -FTLOBJ = main.o memory.o log.o daemon.o datastructure.o signals.o socket.o request.o grep.o setupVars.o args.o threads.o gc.o config.o database.o msgpack.o api.o dnsmasq_interface.o +FTLOBJ = main.o memory.o log.o daemon.o datastructure.o signals.o socket.o request.o grep.o setupVars.o args.o threads.o gc.o config.o database.o msgpack.o api.o dnsmasq_interface.o resolve.o DNSMASQDEPS = config.h dhcp-protocol.h dns-protocol.h radv-protocol.h dhcp6-protocol.h dnsmasq.h ip6addr.h DNSMASQOBJ = arp.o dbus.o domain.o lease.o outpacket.o rrfilter.o auth.o dhcp6.o edns0.o log.o poll.o slaac.o blockdata.o dhcp.o forward.o loop.o radv.o tables.o bpf.o dhcp-common.o helper.o netlink.o rfc1035.o tftp.o cache.o dnsmasq.o inotify.o network.o rfc2131.o util.o conntrack.o dnssec.o ipset.o option.o rfc3315.o crypto.o diff --git a/api.c b/api.c index 5b6f5521..daf8fd18 100644 --- a/api.c +++ b/api.c @@ -380,20 +380,28 @@ void getTopClients(char *client_message, int *sock) validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); // Skip this client if there is a filter on it - if(excludeclients != NULL && insetupVarsArray(clients[j].ip)) + if(excludeclients != NULL && + (insetupVarsArray(clients[j].ip) || insetupVarsArray(clients[j].name))) continue; - // Hidden domain, probably due to privacy level. Skip this in the top lists + // Hidden client, probably due to privacy level. Skip this in the top lists if(strcmp(clients[j].ip, "0.0.0.0") == 0) continue; + // Only return name if available + char *name; + if(clients[j].name != NULL) + name = clients[j].name; + else + name = ""; + // Return this client if either // - "withzero" option is set, and/or // - the client made at least one query within the most recent 24 hours if(includezeroclients || clients[j].count > 0) { if(istelnet[*sock]) - ssend(*sock,"%i %i %s\n",n,clients[j].count,clients[j].ip); + ssend(*sock,"%i %i %s %s\n",n,clients[j].count,clients[j].ip,name); else { if(!pack_str32(*sock, "") || !pack_str32(*sock, clients[j].ip)) @@ -437,7 +445,8 @@ void getForwardDestinations(char *client_message, int *sock) } } - if(sort) { + if(sort) + { // Add "local " forward destination temparray[counters.forwarded][0] = counters.forwarded; temparray[counters.forwarded][1] = counters.cached + counters.blocked; @@ -451,7 +460,7 @@ void getForwardDestinations(char *client_message, int *sock) // Loop over available forward destinations for(i=0; i < min(counters.forwarded+1, 10); i++) { - char *ip; + char *ip, *name; float percentage = 0.0f; // Get sorted indices @@ -465,6 +474,7 @@ void getForwardDestinations(char *client_message, int *sock) if(j == counters.forwarded) { ip = strdup("local"); + name = ip; if(totalqueries > 0) // Whats the percentage of (cached + blocked) queries on the total amount of queries? @@ -477,6 +487,12 @@ void getForwardDestinations(char *client_message, int *sock) validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); ip = forwarded[j].ip; + // Only return name if available + if(forwarded[j].name != NULL) + name = forwarded[j].name; + else + name = ""; + // Math explanation: // A single query may result in requests being forwarded to multiple destinations // Hence, in order to be able to give percentages here, we have to normalize the @@ -503,7 +519,7 @@ void getForwardDestinations(char *client_message, int *sock) if(percentage > 0.0f) { if(istelnet[*sock]) - ssend(*sock, "%i %.2f %s\n", i, percentage, ip); + ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); else { if(!pack_str32(*sock, "") || !pack_str32(*sock, ip)) @@ -515,7 +531,10 @@ void getForwardDestinations(char *client_message, int *sock) // Free previously allocated memory only if we allocated it if(allocated) + { free(ip); + //free(name); // This is just the same as ip + } } if(debugclients) @@ -681,12 +700,18 @@ void getAllQueries(char *client_message, int *sock) if(filterclientname) { // Skip if client name and IP are not identical with what the user wants to see - if(strcmp(clients[queries[i].clientID].ip, clientname) != 0) + if(strcmp(clients[queries[i].clientID].ip, clientname) != 0 && + strcmp(clients[queries[i].clientID].name, clientname) != 0) continue; } char *domain = domains[queries[i].domainID].domain; - char *client = clients[queries[i].clientID].ip; + char *client; + if(clients[queries[i].clientID].name != NULL && + strlen(clients[queries[i].clientID].name) > 0) + client = clients[queries[i].clientID].name; + else + client = clients[queries[i].clientID].ip; unsigned long delay = queries[i].response; // Check if received (delay should be smaller than 30min) @@ -967,7 +992,8 @@ void getClientsOverTime(int *sock) { validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); // Check if this client should be skipped - if(insetupVarsArray(clients[i].ip)) + if(insetupVarsArray(clients[i].ip) || + insetupVarsArray(clients[i].name)) skipclient[i] = true; } } @@ -1039,7 +1065,8 @@ void getClientNames(int *sock) { validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); // Check if this client should be skipped - if(insetupVarsArray(clients[i].ip)) + if(insetupVarsArray(clients[i].ip) || + insetupVarsArray(clients[i].name)) skipclient[i] = true; } } @@ -1051,8 +1078,14 @@ void getClientNames(int *sock) if(skipclient[i]) continue; + char *client; + if(clients[i].name != NULL && strlen(clients[i].name) > 0) + client = clients[i].name; + else + client = clients[i].ip; + if(istelnet[*sock]) - ssend(*sock, "%i %i %s\n", i, clients[i].count, clients[i].ip); + ssend(*sock, "%i %i %s\n", i, clients[i].count, client); else { if(!pack_str32(*sock, "") || !pack_str32(*sock, clients[i].ip)) return; diff --git a/database.c b/database.c index 260a3452..356a81a3 100644 --- a/database.c +++ b/database.c @@ -555,6 +555,10 @@ void *DB_thread(void *val) // Update lastDBsave timer lastDBsave = time(NULL) - time(NULL)%config.DBinterval; + // This has to be run outside of the thread locks + // to prevent locking the resolver + resolveNewClients(); + // Lock FTL's data structure, since it is // likely that it will be changed here enable_thread_lock(); diff --git a/datastructure.c b/datastructure.c index f83acc36..eaf89504 100644 --- a/datastructure.c +++ b/datastructure.c @@ -111,6 +111,12 @@ int findForwardID(const char * forward, bool count) forwarded[forwardID].ip = strdup(forward); memory.forwardedips += (strlen(forward) + 1) * sizeof(char); forwarded[forwardID].failed = 0; + // Initialize forward hostname + // Due to the nature of us being the resolver, + // the actual resolving of the host name has + // to be done separately to be non-blocking + forwarded[forwardID].new = true; + forwarded[forwardID].name = NULL; // Increase counter by one counters.forwarded++; @@ -191,6 +197,12 @@ int findClientID(const char *client) // Store client IP - no need to check for NULL here as it doesn't harm clients[clientID].ip = strdup(client); memory.clientips += (strlen(client) + 1) * sizeof(char); + // Initialize client hostname + // Due to the nature of us being the resolver, + // the actual resolving of the host name has + // to be done separately to be non-blocking + clients[clientID].new = true; + clients[clientID].name = NULL; // Increase counter by one counters.clients++; diff --git a/gc.c b/gc.c index 246dd15e..72d19f43 100644 --- a/gc.c +++ b/gc.c @@ -166,6 +166,11 @@ void *GC_thread(void *val) // Release thread lock disable_thread_lock(); + + // Reresolve client hostnames to account for changes + // Have to this outside of the thread lock + // to prevent locking of the resolver + reresolveHostnames(); } sleepms(100); } diff --git a/resolve.c b/resolve.c new file mode 100644 index 00000000..9d5a1ff7 --- /dev/null +++ b/resolve.c @@ -0,0 +1,128 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2017 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* DNS Client Implementation +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" + +char *resolveHostname(const char *addr) +{ + // Get host name + struct hostent *he = NULL; + char *hostname = NULL;; + bool IPv6 = false; + + // Check if this is a hidden client + // if so, return "hidden" as hostname + if(strcmp(addr, "0.0.0.0") == 0) + { + hostname = strdup("hidden"); + //if(hostname == NULL) return NULL; + return hostname; + } + + // Test if we want to resolve an IPv6 address + if(strstr(addr,":") != NULL) + { + IPv6 = true; + } + + if(IPv6 && config.resolveIPv6) // Resolve IPv6 address only if requested + { + struct in6_addr ipaddr; + inet_pton(AF_INET6, addr, &ipaddr); + he = gethostbyaddr(&ipaddr, sizeof ipaddr, AF_INET6); + } + else if(!IPv6 && config.resolveIPv4) // Resolve IPv4 address only if requested + { + struct in_addr ipaddr; + inet_pton(AF_INET, addr, &ipaddr); + he = gethostbyaddr(&ipaddr, sizeof ipaddr, AF_INET); + } + + if(he == NULL) + { + // No hostname found + hostname = strdup(""); + //if(hostname == NULL) return NULL; + } + else + { + // Return hostname copied to new memory location + hostname = strdup(he->h_name); + if(hostname == NULL) return NULL; + // Convert hostname to lower case + strtolower(hostname); + } + return hostname; +} + +// This routine is run *after* garbage cleaning (default interval is once per hour) +// to account for possibly updated hostnames +void reresolveHostnames(void) +{ + int clientID; + for(clientID = 0; clientID < counters.clients; clientID++) + { + // Memory validation + validate_access("clients", clientID, true, __LINE__, __FUNCTION__, __FILE__); + + // Process this client only if it has at least one active query in the log + if(clients[clientID].count < 1) + continue; + + // Get client hostname + char *hostname = resolveHostname(clients[clientID].ip); + if(strlen(hostname) > 0) + { + // Delete possibly already existing hostname pointer before storing new data + if(clients[clientID].name != NULL) + { + free(clients[clientID].name); + clients[clientID].name = NULL; + } + + // Store client hostname + clients[clientID].name = strdup(hostname); + } + free(hostname); + } +} + +// This routine is run *before* saving to the database (default interval is once per minute) +// to account for new clients (and forward destinations) +void resolveNewClients(void) +{ + int i; + for(i = 0; i < counters.clients; i++) + { + // Memory validation + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + + // Only try to resolve new clients + // Note that it can happen that we are not able to find hostnames but we don't + // want to try to resolve them every minute in this case. + if(clients[i].new) + { + clients[i].name = resolveHostname(clients[i].ip); + clients[i].new = false; + } + } + for(i = 0; i < counters.forwarded; i++) + { + // Memory validation + validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); + + // Only try to resolve new forward destinations + if(forwarded[i].new) + { + forwarded[i].name = resolveHostname(forwarded[i].ip); + forwarded[i].new = false; + } + } +} diff --git a/routines.h b/routines.h index bafa3074..4e4a529b 100644 --- a/routines.h +++ b/routines.h @@ -100,3 +100,7 @@ int main_dnsmasq(int argc, char **argv); // signals.c void handle_signals(void); + +// resolve.c +void resolveNewClients(void); +void reresolveHostnames(void); diff --git a/setupVars.c b/setupVars.c index 69357208..17602e6e 100644 --- a/setupVars.c +++ b/setupVars.c @@ -165,6 +165,11 @@ void clearSetupVarsArray(void) bool insetupVarsArray(char * str) { int i; + // Check for possible NULL pointer + // (this is valid input, e.g. if clients[i].name is unspecified) + if(str == NULL) + return false; + // Loop over all entries in setupVarsArray for (i = 0; i < setupVarsElements; ++i) if(setupVarsArray[i][0] == '*')