diff --git a/.travis.yml b/.travis.yml index e8420e17..5d9d21ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ before_script: - mkdir -p "${DEST_DIR}" - make clean script: -- make pihole-FTL CC="${BUILD_CC}" CFLAGS="${CFLAGS}" +- make CC="${BUILD_CC}" CFLAGS="${CFLAGS}" - file pihole-FTL - if [[ "${BIN_NAME}" == "pihole-FTL-linux-x86_64" ]]; then test/run.sh; fi - mv pihole-FTL "${DEST_DIR}/${BIN_NAME}" diff --git a/FTL.h b/FTL.h index ac1b7f39..beb327a4 100644 --- a/FTL.h +++ b/FTL.h @@ -41,6 +41,9 @@ #include "sqlite3.h" // tolower() #include +// Unix socket +#include + #include "routines.h" @@ -77,6 +80,7 @@ typedef struct { const char* pid; const char* port; char* db; + const char* socketfile; } FTLFileNamesStruct; typedef struct { @@ -111,10 +115,12 @@ typedef struct { int overTime; int IPv4; int IPv6; - int PTR; - int SRV; int wildcarddomains; int forwardedqueries; + int reply_NODATA; + int reply_NXDOMAIN; + int reply_CNAME; + int reply_IP; } countersStruct; typedef struct { @@ -142,6 +148,11 @@ typedef struct { int forwardID; bool valid; bool db; + // the ID is a (signed) int in dnsmasq, so no need for a long int here + int id; + bool complete; + unsigned char reply; + int generation; } queriesDataStruct; typedef struct { @@ -164,6 +175,7 @@ typedef struct { int blockedcount; char *domain; bool wildcard; + unsigned char dnssec; } domainsDataStruct; typedef struct { @@ -194,6 +206,7 @@ typedef struct { enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD, AUTHDATA }; enum { SOCKET, API, APIH }; enum { WHITELIST, BLACKLIST, WILDLIST }; +enum { DNSSEC_UNSPECIFIED, DNSSEC_SECURE, DNSSEC_INSECURE, DNSSEC_BOGUS, DNSSEC_ABANDONED, DNSSEC_UNKNOWN }; logFileNamesStruct files; FTLFileNamesStruct FTLfiles; diff --git a/Makefile b/Makefile index 230c29e5..801fbf0c 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ _DEPS = $(patsubst %,$(IDIR)/%,$(DEPS)) _OBJ = $(patsubst %,$(ODIR)/%,$(OBJ)) -all: pihole-FTL +all: pihole-FTL socket-test $(ODIR)/%.o: %.c $(_DEPS) | $(ODIR) $(CC) -c -o $@ $< -g3 $(CCFLAGS) @@ -60,6 +60,9 @@ $(ODIR)/sqlite3.o: sqlite3.c pihole-FTL: $(_OBJ) $(ODIR)/sqlite3.o $(CC) -v $(CCFLAGS) -o $@ $^ $(LIBS) +socket-test: socket_client.c + $(CC) -o $@ $< $(CCFLAGS) + .PHONY: clean force install clean: diff --git a/README.md b/README.md index 1fd4bbac..70dabdd0 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ unique_clients 3 2 10.0 5.6.7.8 some.other.dns.com ``` Variant: `>forward-dest unsorted` to show forward destinations in unsorted order (equivalent to using `>forward-names`) -``` + - `>querytypes` : get collected query types percentage ``` diff --git a/api.h b/api.h index 5086830d..7dc4b709 100644 --- a/api.h +++ b/api.h @@ -26,6 +26,7 @@ void getVersion(int *sock, char type); void getDBstats(int *sock, char type); void getClientsOverTime(int *sock); void getClientNames(int *sock); +void getUnknownQueries(int *sock); // Endpoints under /dns/ void getList(int *sock, char type, char list_type); diff --git a/api_stats.c b/api_stats.c index d27fa805..1a169e88 100644 --- a/api_stats.c +++ b/api_stats.c @@ -845,28 +845,28 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); else - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); } else - ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + ssend(*sock,"%i %s %s hidden %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); } else { - // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2","2"], + // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2",2,1], if(!first) ssend(*sock, ","); first = false; if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); else - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); } else - ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); } } @@ -1160,11 +1160,19 @@ void getQueryTypesOverTime(int *sock, char type) void getVersion(int *sock, char type) { - char version[] = GIT_VERSION; + const char * version = GIT_VERSION; + const char * branch = GIT_BRANCH; + // Travis CI pulls on a tag basis, not by branch. + // Hence, it may happen that the master binary isn't aware of its branch. + // We check if this is the case and if there is a "vX.YY" like tag on the + // binary are print out branch "master" if we find that this is the case + if(strstr(branch, "(no branch)") != NULL && strstr(version, ".") != NULL) + branch = "master"; + if(strstr(version, ".") != NULL) - ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); + ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", version, GIT_TAG, branch, GIT_DATE); else - ssend(*sock,"version vDev-%s\ntag %s\nbranch %s\ndate %s\n", GIT_HASH, GIT_TAG, GIT_BRANCH, GIT_DATE); + ssend(*sock,"version vDev-%s\ntag %s\nbranch %s\ndate %s\n", GIT_HASH, GIT_TAG, branch, GIT_DATE); if(debugclients) logg("Sent version info to client, ID: %i", *sock); @@ -1303,3 +1311,35 @@ void getClientNames(int *sock) if(excludeclients != NULL) clearSetupVarsArray(); } + +void getUnknownQueries(int *sock) +{ + int i; + for(i=0; i < counters.queries; i++) + { + validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this query has been removed due to garbage collection + if(queries[i].status != 0 && queries[i].complete) continue; + + char type[5]; + if(queries[i].type == 1) + { + strcpy(type,"IPv4"); + } + else + { + strcpy(type,"IPv6"); + } + + validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); + validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); + + if(strlen(clients[queries[i].clientID].name) > 0) + ssend(*sock,"%i %i %i %s %s %s %i %s\n",queries[i].timestamp,i,queries[i].id,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,queries[i].complete ?"true":"false"); + else + ssend(*sock,"%i %i %i %s %s %s %i %s\n",queries[i].timestamp,i,queries[i].id,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,queries[i].complete?"true":"false"); + } + + if(debugclients) + logg("Sent unknown queries data to client, ID: %i", *sock); +} diff --git a/args.c b/args.c index 3bb073fd..03c2562f 100644 --- a/args.c +++ b/args.c @@ -70,7 +70,7 @@ void parse_args(int argc, char* argv[]) if(strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "version") == 0) { - char version[] = GIT_VERSION; + const char * version = GIT_VERSION; // Check if version is of format vX.YY // '.' can never be part of a commit hash if(strstr(version, ".") != NULL) @@ -90,7 +90,15 @@ void parse_args(int argc, char* argv[]) if(strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "branch") == 0) { - printf("%s\n",GIT_BRANCH); + const char * branch = GIT_BRANCH; + const char * version = GIT_VERSION; + // Travis CI pulls on a tag basis, not by branch. + // Hence, it may happen that the master binary isn't aware of its branch. + // We check if this is the case and if there is a "vX.YY" like tag on the + // binary are print out branch "master" if we find that this is the case + if(strstr(branch, "(no branch)") != NULL && strstr(version, ".") != NULL) + branch = "master"; + printf("%s\n",branch); exit(EXIT_SUCCESS); } @@ -118,13 +126,13 @@ void parse_args(int argc, char* argv[]) FTLfiles.log = "pihole-FTL.log"; // FTLfiles.db will be set to "pihole-FTL.db" via config file on Travis FTLfiles.conf = "pihole-FTL.conf"; + FTLfiles.socketfile = "pihole-FTL.sock"; files.log = "pihole.log"; ok = true; } // List of implemented arguments - if(strcmp(argv[i], "-h") == 0 || - strcmp(argv[i], "help") == 0) + if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0) { printf("pihole-FTL - The Pi-hole FTL engine\n\n"); printf("Usage: sudo service pihole-FTL \n"); diff --git a/database.c b/database.c index 241fad97..5f9edc15 100644 --- a/database.c +++ b/database.c @@ -36,7 +36,12 @@ void check_database(int rc) void dbclose(void) { - sqlite3_close(db); + int rc = sqlite3_close(db); + // Report any error + if( rc ) + logg("dbclose() - SQL error (%i): %s", rc, sqlite3_errmsg(db)); + + // Unlock mutex on the database pthread_mutex_unlock(&dblock); } @@ -169,6 +174,9 @@ void db_init(void) } } + // Close database to prevent having it opened all time + sqlite3_close(db); + if (pthread_mutex_init(&dblock, NULL) != 0) { logg("FATAL: DB mutex init failed\n"); @@ -309,14 +317,19 @@ void save_to_DB(void) return; } + int currenttimestamp = time(NULL); for(i = lastdbindex; i < counters.queries; i++) { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); - if(queries[i].timestamp <= lasttimestamp || queries[i].db == true) - { - // Already in database - // logg("Skipping %i",i); + if(queries[i].timestamp <= lasttimestamp || queries[i].db) + // Already in database or not yet complete continue; + + if(!queries[i].complete && queries[i].timestamp > currenttimestamp-2) + { + // Break if a brand new query (age < 2 seconds) is not yet completed + // giving it a chance to be stored next time + break; } // Memory checks @@ -385,8 +398,8 @@ void save_to_DB(void) // Finish prepared statement ret = dbquery("END TRANSACTION"); - if(!ret){ dbclose(); return; } - sqlite3_finalize(stmt); + int ret2 = sqlite3_finalize(stmt); + if(!ret || ret2 != SQLITE_OK){ dbclose(); return; } // Store index for next loop interation round and update last time stamp // in the database only if all queries have been saved successfully diff --git a/gc.c b/gc.c index ecb4a400..7b19ca54 100644 --- a/gc.c +++ b/gc.c @@ -69,6 +69,28 @@ void *GC_thread(void *val) counters.forwardedqueries--; validate_access("forwarded", queries[i].forwardID, true, __LINE__, __FUNCTION__, __FILE__); forwarded[queries[i].forwardID].count--; + // Maybe we have to adjust total counters depending on the reply type + switch(queries[i].reply) + { + case 1: // NODATA(-IPv6) + counters.reply_NODATA--; + break; + + case 2: // NXDOMAIN + counters.reply_NXDOMAIN--; + break; + + case 3: // + counters.reply_CNAME--; + break; + + case 4: // valid IP + counters.reply_IP--; + break; + + default: // Incomplete query, do nothing + break; + } break; case 3: // Answered from local cache _or_ local config diff --git a/grep.c b/grep.c index 8d9d9c56..bc828836 100644 --- a/grep.c +++ b/grep.c @@ -191,10 +191,38 @@ int countlineswith(const char* str, const char* fname) } // Search through file - // getline reads a string from the specified file up to either a newline character or EOF + // getline reads a string from the specified file up to either a + // newline character or EOF while(getline(&buffer, &size, fp) != -1) - if((strstr(buffer, str)) != NULL) + { + // Strip potential newline character at the end of line we just read + if(buffer[strlen(buffer)-1] == '\n') + buffer[strlen(buffer)-1] = '\0'; + + // Search for exact match + if(strcmp(buffer, str) == 0) + { found++; + continue; + } + + // If line starts with *, search for partial match of + // needle "buffer+1" in haystack "str" + if(buffer[0] == '*') + { + char * buf = strstr(str, buffer+1); + // The strstr() function finds the first occurrence of + // the substring buffer+1 in the string str. + // These functions return a pointer to the beginning of + // the located substring, or NULL if the substring is not + // found. Hence, we compare the length of the substring to + // the wildcard entry to rule out the possiblity that + // there is anything behind the wildcard. This avoids that given + // "*example.com" "example.com.xxxxx" would also match. + if(buf != NULL && strlen(buf) == strlen(buffer+1)) + found++; + } + } // Free allocated memory if(buffer != NULL) diff --git a/log.c b/log.c index 5b5f1d1e..23b8cc83 100644 --- a/log.c +++ b/log.c @@ -135,10 +135,13 @@ void log_counter_info(void) { logg(" -> Total DNS queries: %i", counters.queries); logg(" -> Cached DNS queries: %i", counters.cached); - logg(" -> Blocked DNS queries: %i", counters.blocked); + logg(" -> Forwarded DNS queries: %i", counters.forwardedqueries); + logg(" -> Exactly blocked DNS queries: %i", counters.blocked); + logg(" -> Wildcard blocked DNS queries: %i", counters.wildcardblocked); logg(" -> Unknown DNS queries: %i", counters.unknown); logg(" -> Unique domains: %i", counters.domains); logg(" -> Unique clients: %i", counters.clients); + logg(" -> Known forward destinations: %i", counters.forwarded); } void log_FTL_version(void) diff --git a/main.c b/main.c index 5c34b3b2..70c92f36 100644 --- a/main.c +++ b/main.c @@ -72,11 +72,19 @@ int main (int argc, char* argv[]) { } sleepms(100); + // Start TELNET thread + pthread_t telnet_listenthread; + if(pthread_create( &telnet_listenthread, &attr, telnet_listening_thread, NULL ) != 0) + { + logg("Unable to open telnet listening thread. Exiting..."); + killed = 1; + } + // Start SOCKET thread pthread_t socket_listenthread; if(pthread_create( &socket_listenthread, &attr, socket_listening_thread, NULL ) != 0) { - logg("Unable to open socket listening thread. Exiting..."); + logg("Unable to open Unix socket listening thread. Exiting..."); killed = 1; } sleepms(100); @@ -157,15 +165,18 @@ int main (int argc, char* argv[]) { // Have to re-read gravity files rereadgravity = false; read_gravity_files(); + log_counter_info(); disable_thread_lock("pihole_main_thread"); } } - logg("Shutting down..."); pthread_cancel(piholelogthread); + pthread_cancel(telnet_listenthread); pthread_cancel(socket_listenthread); - close_socket(SOCKET); + close_telnet_socket(); + close_unix_socket(); + close_api_socket(); removepid(); logg("########## FTL terminated! ##########"); return 1; diff --git a/parser.c b/parser.c index 2fea9c65..d22447d1 100644 --- a/parser.c +++ b/parser.c @@ -23,6 +23,7 @@ long int lastpos = 0; int lastqueryID = 0; bool flush = false; char timestamp[16] = ""; +int loggeneration = 0; void initial_log_parsing(void) { @@ -49,6 +50,45 @@ long int checkLogForChanges(void) return difference; } +int getTimeIndex(char * readbuffer) +{ + int querytimestamp, overTimetimestamp, timeidx = -1, i; + extracttimestamp(readbuffer, &querytimestamp, &overTimetimestamp); + + bool found = false; + // Check struct size + memory_check(OVERTIME); + for(i=0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if(overTime[i].timestamp == overTimetimestamp) + { + found = true; + timeidx = i; + break; + } + } + if(!found) + { + timeidx = counters.overTime; + validate_access("overTime", timeidx, false, __LINE__, __FUNCTION__, __FILE__); + overTime[timeidx].magic = MAGICBYTE; + overTime[timeidx].timestamp = overTimetimestamp; + overTime[timeidx].total = 0; + overTime[timeidx].blocked = 0; + overTime[timeidx].cached = 0; + overTime[timeidx].forwardnum = 0; + overTime[timeidx].forwarddata = NULL; + overTime[timeidx].querytypedata = calloc(2, sizeof(int)); + overTime[timeidx].clientnum = 0; + overTime[timeidx].clientdata = NULL; + memory.querytypedata += 2*sizeof(int); + counters.overTime++; + } + + return timeidx; +} + void open_pihole_log(void) { FILE * fp; @@ -144,12 +184,47 @@ void *pihole_log_thread(void *val) return NULL; } +int getID(char * readbuffer) +{ + // Get query ID from a string like + // "Dec 20 21:16:22 dnsmasq[19372]: 4 10.8.0.2/34596 query[A] pi.hole from 10.8.0.2" + int dnsmasqID = -1; + if(!sscanf(readbuffer, "%*[^]]]: %i", &dnsmasqID)) + { + if(debug) logg("Error getting ID for query \"%s\" %u", readbuffer, dnsmasqID); + } + return dnsmasqID; +} + +bool checkQuery(char * readbuffer, const char * type) +{ + // Check if this domain names contains only printable characters + // if not: skip analysis of this log line + if(strstr(readbuffer,"") != NULL) + { + if(debug) logg("Ignoring domain (%s)", type); + return false; + } + + // Check if this domain name contains quotes + if(strstr(readbuffer, "\"") != NULL) + { + if(debug) logg("Ignoring \" domain (%s)", type); + return false; + } + + // Check if this is a PTR query (we don't analyze them) + if(strstr(readbuffer,"in-addr.arpa") != NULL) + return false; + + return true; +} + void process_pihole_log(int file) { int i; char *readbuffer = NULL; - char *readbuffer2 = NULL; - size_t size1 = 0, size2 = 0; + size_t size1 = 0; FILE *fp; if(file == 0) @@ -200,23 +275,12 @@ void process_pihole_log(int file) } // Test if the read line is a query line - if(strstr(readbuffer,"]: query[A") != NULL) + if(strstr(readbuffer," query[A") != NULL) { - // Check if this domain names contains only printable characters - // if not: skip analysis of this log line - if(strstr(readbuffer,"") != NULL) - { - if(debug) logg("Ignoring domain (query)"); + if(!checkQuery(readbuffer, "query")) continue; - } - if(strstr(readbuffer, "\"") != NULL) - { - if(debug) logg("Ignoring \" domain (query)"); - continue; - } - - if(!config.analyze_AAAA && strstr(readbuffer,"]: query[AAAA]") != NULL) + if(!config.analyze_AAAA && strstr(readbuffer," query[AAAA]") != NULL) { if(debug) logg("Not analyzing AAAA query"); continue; @@ -304,6 +368,12 @@ void process_pihole_log(int file) continue; } + // Get dnsmasq's ID for this transaction + int dnsmasqID = getID(readbuffer); + // Skip invalid lines + if(dnsmasqID < 0) + continue; + // Get domain // domainstart = pointer to | in "query[AAAA] |host.name from ww.xx.yy.zz\n" const char *domainstart = strstr(readbuffer, "] "); @@ -333,11 +403,8 @@ void process_pihole_log(int file) } char *domain = calloc(domainlen+1,sizeof(char)); - char *domainwithspaces = calloc(domainlen+3,sizeof(char)); // strncat() NULL-terminates the copied string (strncpy() doesn't!) strncat(domain,domainstart+2,domainlen); - // Copy string into buffer surrounded by spaces - sprintf(domainwithspaces," %s ",domain); // Convert domain to lower case strtolower(domain); @@ -346,7 +413,6 @@ void process_pihole_log(int file) // domain is "pi.hole", skip this query // free memory already allocated here free(domain); - free(domainwithspaces); continue; } @@ -359,7 +425,6 @@ void process_pihole_log(int file) logg("Notice: Skipping malformated log line (client end missing): %s", readbuffer); // Skip this line, free memory already allocated here free(domain); - free(domainwithspaces); continue; } @@ -369,7 +434,6 @@ void process_pihole_log(int file) logg("Notice: Skipping malformated log line (client length < 1): %s", readbuffer); // Skip this line, free memory already allocated here free(domain); - free(domainwithspaces); continue; } @@ -396,95 +460,6 @@ void process_pihole_log(int file) overTime[timeidx].querytypedata[1]++; } - // Save current file pointer position - long int fpos = ftell(fp); - unsigned char status = 0; - - // Try to find either a matching - // - "gravity.list" + domain - // - "forwarded" + domain - // - "cached" + domain - // - "black.list" + domain - // in the following up to 200 lines - bool firsttime = true; - int forwardID = -1; - for(i=0; i<200; i++) - { - if(getline(&readbuffer2, &size2, fp) != -1) - { - // Process only matching lines - if(strstr(readbuffer2, domainwithspaces) != NULL) - { - // Blocked by gravity.list ? - if(strstr(readbuffer2,"gravity.list ") != NULL) - { - status = 1; - break; - } - // Forwarded to upstream server? - else if(strstr(readbuffer2,": forwarded ") != NULL) - { - status = 2; - // Get ID of forward destination, create new forward destination record - // if not found in current data structure - forwardID = getforwardID(readbuffer2, false); - if(forwardID == -2) - continue; - break; - } - // Answered by local cache? - else if((strstr(readbuffer2,"cached ") != NULL) || - (strstr(readbuffer2,"local.list") != NULL) || - (strstr(readbuffer2,"hostname.list") != NULL) || - (strstr(readbuffer2,"DHCP ") != NULL) || - (strstr(readbuffer2,"/etc/hosts") != NULL)) - { - status = 3; - break; - } - // wildcard blocking? - else if((strstr(readbuffer2,"config ") != NULL)) - { - status = detectStatus(domain); - break; - } - // Blocked by black.list ? - else if(strstr(readbuffer2,"black.list ") != NULL) - { - status = 5; - break; - } - } - } - else - { - if(firsttime) - { - // Reached EOF without finding the action - // wait 100msec and try again to read dnsmasq's response - i = 0; - fseek(fp, fpos, SEEK_SET); - firsttime = false; - sleepms(100); - } - else - { - // Failed second time - break; - } - } - } - - // Return to previous file pointer position - fseek(fp, fpos, SEEK_SET); - - // Free memory allocated by readline - if(readbuffer2 != NULL) - { - free(readbuffer2); - readbuffer2 = NULL; - } - // Go through already knows domains and see if it is one of them // Check struct size memory_check(DOMAINS); @@ -496,7 +471,7 @@ void process_pihole_log(int file) domainID = counters.domains; // // Debug output if(debug) - logg("New domain: %s (%i - %i/%i)", domain, status, domainID, counters.domains_MAX); + logg("New domain: %s (%i: %i/%i)", domain, dnsmasqID, domainID, counters.domains_MAX); validate_access("domains", domainID, false, __LINE__, __FUNCTION__, __FILE__); // Set magic byte domains[domainID].magic = MAGICBYTE; @@ -509,6 +484,8 @@ void process_pihole_log(int file) // Store domain name domains[domainID].domain = strdup(domain); memory.domainnames += (strlen(domain) + 1) * sizeof(char); + // Store DNSSEC result for this domain + domains[domainID].dnssec = DNSSEC_UNSPECIFIED; // Increase counter by one counters.domains++; } @@ -529,10 +506,10 @@ void process_pihole_log(int file) { // Convert hostname to lower case strtolower(hostname); - logg("New client: %s %s (%i/%i)", client, hostname, clientID, counters.clients_MAX); + logg("New client: %s %s (%i: %i/%i)", client, hostname, dnsmasqID, clientID, counters.clients_MAX); } else - logg("New client: %s (%i/%i)", client, clientID, counters.clients_MAX); + logg("New client: %s (%i: %i/%i)", client, dnsmasqID, clientID, counters.clients_MAX); validate_access("clients", clientID, false, __LINE__, __FUNCTION__, __FILE__); // Set magic byte @@ -555,69 +532,27 @@ void process_pihole_log(int file) queries[queryID].magic = MAGICBYTE; queries[queryID].timestamp = querytimestamp; queries[queryID].type = type; - queries[queryID].status = status; + queries[queryID].status = 0; queries[queryID].domainID = domainID; queries[queryID].clientID = clientID; queries[queryID].timeidx = timeidx; - queries[queryID].forwardID = forwardID; queries[queryID].valid = true; queries[queryID].db = false; + queries[queryID].id = dnsmasqID; + queries[queryID].complete = false; + queries[queryID].reply = 0; + queries[queryID].generation = loggeneration; // Increase DNS queries counter counters.queries++; + // Count this query as unknown as long as no reply has + // been found and analyzed + counters.unknown++; // Update overTime data validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); overTime[timeidx].total++; - // Decide what to increment depending on status - switch(status) - { - case 0: - // Unknown (?) - counters.unknown++; - break; - case 1: - // Blocked by Pi-hole's blocking lists - counters.blocked++; - validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); - overTime[timeidx].blocked++; - validate_access("domains", domainID, true, __LINE__, __FUNCTION__, __FILE__); - domains[domainID].blockedcount++; - break; - case 2: - // Forwarded to an upstream DNS server - counters.forwardedqueries++; - break; - case 3: - // Answered from local cache _or_ local config - counters.cached++; - validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); - overTime[timeidx].cached++; - break; - case 4: - // Blocked due to a matching wildcard rule - counters.wildcardblocked++; - validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); - overTime[timeidx].blocked++; - validate_access("domains", domainID, true, __LINE__, __FUNCTION__, __FILE__); - domains[domainID].blockedcount++; - domains[domainID].wildcard = true; - break; - case 5: - // Blocked by user's black list - counters.blocked++; - validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); - overTime[timeidx].blocked++; - validate_access("domains", domainID, true, __LINE__, __FUNCTION__, __FILE__); - domains[domainID].blockedcount++; - break; - default: - /* That cannot happen */ - logg("Found unexpected status %i",status); - break; - } - // Determine if there is enough space for saving the current // clientID in the overTime data structure, allocate space otherwise validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); @@ -642,63 +577,101 @@ void process_pihole_log(int file) // Free allocated memory free(client); free(domain); - free(domainwithspaces); } - else if(strstr(readbuffer,": forwarded") != NULL) + // is this a "gravity.list" line? + else if(strstr(readbuffer,"/gravity.list ") != NULL && strstr(readbuffer," is ") != NULL) { - // Check if this domain names contains only printable characters - // if not: skip analysis of this log line - if(strstr(readbuffer,"") != NULL) + // Check query for invalid characters + if(!checkQuery(readbuffer, "gravity.list")) + continue; + + // Get dnsmasq's ID for this transaction + int dnsmasqID = getID(readbuffer); + // Skip invalid lines + if(dnsmasqID < 0) + continue; + + // Save status in corresponding query indentified by dnsmasq's ID + bool found = false; + for(i=0; i domain (forwarded)"); + // Check both UUID and generation of this query + if(queries[i].id == dnsmasqID && queries[i].generation == loggeneration) + { + queries[i].status = 1; + found = true; + break; + } + } + if(!found) + { + // This may happen e.g. if the original query was a PTR query or "pi.hole" + // as we ignore them altogether continue; } - // Check if this is a PTR query - // if so: skip analysis of this log line - if(strstr(readbuffer,"in-addr.arpa") != NULL) + if(!queries[i].complete) + { + // This query is no longer unknown + counters.unknown--; + // ... but got blocked + counters.blocked++; + // Hereby, this query is now fully determined + queries[i].complete = true; + + // Get time index + int timeidx = getTimeIndex(readbuffer); + validate_access("overTime", timeidx, true, __LINE__, __FUNCTION__, __FILE__); + overTime[timeidx].blocked++; + validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); + domains[queries[i].domainID].blockedcount++; + } + } + // is this a "forwarded" line? + else if(strstr(readbuffer," forwarded ") != NULL && strstr(readbuffer," to ") != NULL) + { + // Check query for invalid characters + if(!checkQuery(readbuffer, "forwarded")) + continue; + + // Get dnsmasq's ID for this transaction + int dnsmasqID = getID(readbuffer); + // Skip invalid lines + if(dnsmasqID < 0) continue; // Get ID of forward destination, create new forward destination record // if not found in current data structure int forwardID = getforwardID(readbuffer, true); if(forwardID == -2) - continue; - - // Get timestamp - int querytimestamp, overTimetimestamp, timeidx = -1, i; - extracttimestamp(readbuffer, &querytimestamp, &overTimetimestamp); - - bool found = false; - // Check struct size - memory_check(OVERTIME); - for(i=0; i < counters.overTime; i++) { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(overTime[i].timestamp == overTimetimestamp) + if(debug) logg("Skipping malformated forwarded line"); + continue; + } + + // Save status and forwardID in corresponding query indentified by dnsmasq's ID + bool found = false; + for(i=0; i") != NULL) + { + // + queries[i].reply = 3; + counters.reply_CNAME++; + } + else + { + // Valid IP + queries[i].reply = 4; + counters.reply_IP++; + // const char * dest = strstr(readbuffer," is "); + // char * result; + // sscanf(dest, " is %ms", &result); + // printf("reply is IP: %s\n",result); + // free(result); + } + } + // is this a "validaton" line? -- DNSSEC + else if(strstr(readbuffer," validation ") != NULL && strstr(readbuffer," is ") != NULL) + { + // Check query for invalid characters + if(!checkQuery(readbuffer, "DNSSEC")) + continue; + + // Get dnsmasq's ID for this transaction + int dnsmasqID = getID(readbuffer); + // Skip invalid lines + if(dnsmasqID < 0) + continue; + + // Search for corresponding query indentified by dnsmasq's ID + bool found = false; + for(i=0; iunknown")) + { + processed = true; + getUnknownQueries(sock); + } // Test only at the end if we want to quit or kill // so things can be processed before diff --git a/routines.h b/routines.h index e03ffc2b..88b53940 100644 --- a/routines.h +++ b/routines.h @@ -38,9 +38,12 @@ void pihole_log_flushed(bool message); void memory_check(int which); -void close_socket(char type); +void close_telnet_socket(void); +void close_unix_socket(void); +void close_api_socket(void); void seom(int sock); void ssend(int sock, const char *format, ...); +void *telnet_listening_thread(void *args); void *socket_listening_thread(void *args); void *api_listening_thread(void *args); diff --git a/socket.c b/socket.c index 2679f5ac..bea63b8d 100644 --- a/socket.c +++ b/socket.c @@ -12,7 +12,7 @@ // The backlog argument defines the maximum length // to which the queue of pending connections for -// socketfd may grow. If a connection request arrives +// telnetfd may grow. If a connection request arrives // when the queue is full, the client may receive an // error with an indication of ECONNREFUSED or, if // the underlying protocol supports retransmission, @@ -21,7 +21,7 @@ #define BACKLOG 5 // File descriptors -int socketfd, apifd; +int telnetfd, socketfd, apifd; void saveport(int port) { @@ -38,13 +38,13 @@ void saveport(int port) } } -void bind_to_port(char type, int *socketdescriptor) +void bind_to_telnet_port(char type, int *socketdescriptor) { *socketdescriptor = socket(AF_INET, SOCK_STREAM, 0); if(*socketdescriptor < 0) { - logg("Error opening socket"); + logg("Error opening telnet socket"); exit(EXIT_FAILURE); } @@ -133,6 +133,43 @@ void bind_to_port(char type, int *socketdescriptor) } } + +void bind_to_unix_socket(int *socketdescriptor) +{ + *socketdescriptor = socket(AF_LOCAL, SOCK_STREAM, 0); + + if(*socketdescriptor < 0) + { + logg("Error opening unix socket"); + exit(EXIT_FAILURE); + } + + // Make sure unix socket file handle does not exist, if it exists, remove it + unlink(FTLfiles.socketfile); + + struct sockaddr_un address; + address.sun_family = AF_LOCAL; + strcpy(address.sun_path, FTLfiles.socketfile); + + // Bild to Unix socket handle + errno = 0; + if(bind(*socketdescriptor, (struct sockaddr *) &address, sizeof (address)) != 0) { + logg("Error on binding on unix socket %s", FTLfiles.socketfile); + logg("Reason: %s (%i)", strerror(errno), errno); + exit(EXIT_FAILURE); + } + + // The listen system call allows the process to listen on the Unix socket for connections + if(listen(*socketdescriptor, BACKLOG) == -1) + { + logg("Error on listening"); + exit(EXIT_FAILURE); + } + + logg("Listening on Unix socket"); +} + + // Called from main() at graceful shutdown void removeport(void) { @@ -191,25 +228,84 @@ int listener(int sockfd) return clientsocket; } -void close_socket(char type) +void close_telnet_socket(void) { - switch(type) - { - case SOCKET: - removeport(); - // Using global variable here - close(socketfd); - break; - case API: - close(apifd); - break; - default: - logg("Incompatible socket type %i, cannot close",(int)type); - exit(EXIT_FAILURE); - break; - } + removeport(); + // Using global variable here + close(telnetfd); } +void close_unix_socket(void) +{ + // The process has to take care of unlinking the socket file description on exit + unlink(FTLfiles.socketfile); + // Using global variable here + close(socketfd); +} + +void close_api_socket(void) +{ + close(apifd); +} + +void *telnet_connection_handler_thread(void *socket_desc) +{ + //Get the socket descriptor + int sock = *(int*)socket_desc; + // Store copy only for displaying the debug messages + int sockID = sock; + char client_message[SOCKETBUFFERLEN] = ""; + + // Set thread name + char threadname[16]; + sprintf(threadname,"telnet-%i",sockID); + prctl(PR_SET_NAME,threadname,0,0,0); + //Receive from client + ssize_t n; + while((n = recv(sock,client_message,SOCKETBUFFERLEN-1, 0))) + { + if (n > 0) + { + char *message = calloc(strlen(client_message)+1,sizeof(char)); + strcpy(message, client_message); + + // Clear client message receive buffer + memset(client_message, 0, sizeof client_message); + + // Lock FTL data structure, since it is likely that it will be changed here + // Requests should not be processed/answered when data is about to change + enable_thread_lock(threadname); + + process_socket_request(message, &sock); + free(message); + + // Release thread lock + disable_thread_lock(threadname); + + if(sock == 0) + { + // Client disconnected by sending EOT or ">quit" + break; + } + } + else if(n == -1) + { + if(debugclients) logg("Telnet connection interrupted (%s), ID: %i", strerror(errno), sockID); + break; + } + } + if(debugclients) + logg("Telnet disconnected, ID: %i", sockID); + + //Free the socket pointer + if(sock != 0) + close(sock); + free(socket_desc); + + return 0; +} + + void *socket_connection_handler_thread(void *socket_desc) { //Get the socket descriptor @@ -246,18 +342,17 @@ void *socket_connection_handler_thread(void *socket_desc) if(sock == 0) { - // Client disconnected by sending EOT or ">quit" + // Socket connection interrupted by sending EOT or ">quit" break; } } else if(n == -1) { - if(debugclients) - logg("Client connection interrupted, ID: %i", sockID); + if(debugclients) logg("Unix socket connection interrupted (%s), ID: %i", strerror(errno), sockID); + break; } } - if(debugclients) - logg("Client disconnected, ID: %i", sockID); + if(debugclients) logg("Socket disconnected, ID: %i", sockID); //Free the socket pointer if(sock != 0) @@ -272,9 +367,46 @@ void *socket_connection_handler_thread(void *socket_desc) return 0; } +void *telnet_listening_thread(void *args) +{ + // We will use the attributes object later to start all threads in detached mode + pthread_attr_t attr; + // Initialize thread attributes object with default attribute values + pthread_attr_init(&attr); + // When a detached thread terminates, its resources are automatically released back to + // the system without the need for another thread to join with the terminated thread + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + // Set thread name + prctl(PR_SET_NAME,"telnet listener",0,0,0); + + // Initialize sockets only after initial log parsing in listenting_thread + bind_to_telnet_port(SOCKET, &telnetfd); + + // Listen as long as FTL is not killed + while(!killed) + { + // Look for new clients that want to connect + int csck = listener(telnetfd); + + // Allocate memory used to transport client socket ID to client listening thread + int *newsock; + newsock = calloc(1,sizeof(int)); + *newsock = csck; + + pthread_t telnet_connection_thread; + // Create a new thread + if(pthread_create( &telnet_connection_thread, &attr, telnet_connection_handler_thread, (void*) newsock ) != 0) + { + // Log the error code description + logg("WARNING: Unable to open telnet processing thread, error: %s", strerror(errno)); + } + } + return 0; +} + void *socket_listening_thread(void *args) { - int *newsock; // We will use the attributes object later to start all threads in detached mode pthread_attr_t attr; // Initialize thread attributes object with default attribute values @@ -287,7 +419,7 @@ void *socket_listening_thread(void *args) prctl(PR_SET_NAME,"socket listener",0,0,0); // Initialize sockets only after initial log parsing in listening_thread - bind_to_port(SOCKET, &socketfd); + bind_to_unix_socket(&socketfd); // Listen as long as FTL is not killed while(!killed) @@ -297,6 +429,7 @@ void *socket_listening_thread(void *args) if(csck < 0) continue; // Allocate memory used to transport client socket ID to client listening thread + int *newsock; newsock = calloc(1,sizeof(int)); *newsock = csck; @@ -305,12 +438,13 @@ void *socket_listening_thread(void *args) if(pthread_create( &socket_connection_thread, &attr, socket_connection_handler_thread, (void*) newsock ) != 0) { // Log the error code description - logg("WARNING: Unable to open client socket thread, error: %s", strerror(errno)); + logg("WARNING: Unable to open socket processing thread, error: %s", strerror(errno)); } } return 0; } + void *api_connection_handler_thread(void *socket_desc) { //Get the socket descriptor @@ -442,7 +576,7 @@ void *api_listening_thread(void *args) prctl(PR_SET_NAME,"API listener",0,0,0); // Initialize sockets only after initial log parsing in listening_thread - bind_to_port(API, &apifd); + bind_to_telnet_port(API, &apifd); // Listen as long as FTL is not killed while(!killed) diff --git a/socket_client.c b/socket_client.c new file mode 100644 index 00000000..080bf107 --- /dev/null +++ b/socket_client.c @@ -0,0 +1,77 @@ +/* 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 +* Unix socket connection test program +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define BUF 1024 + +int main (int argc, char **argv) { + int socketfd; + char *buffer = malloc (BUF); + struct sockaddr_un address; + int size, ret; + + // Create socket + socketfd = socket(PF_LOCAL, SOCK_STREAM, 0); + if(socketfd <= 0) + { + printf("Error creating socket!\n"); + exit(EXIT_FAILURE); + } + printf("Socket created\n"); + + // Set socket family to local socket (not an Internet socket) + address.sun_family = AF_LOCAL; + + // Set socket file location (respect special location on the CI system Travis) + if(argc == 2 && strcmp(argv[1], "travis") == 0) + strcpy(address.sun_path,"pihole-FTL.sock"); + else + strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); + + // Connect to the socket provided by pihole-FTL + ret = connect(socketfd, (struct sockaddr *) &address, sizeof (address)); + if (ret != 0) + { + printf("Error establishing connection!\n"); + exit(EXIT_FAILURE); + } + printf("Connection established\n"); + + // As an example, we query the current statistics from FTL through the socket here + sprintf(buffer, ">stats"); + send(socketfd, buffer, strlen (buffer), 0); + + // Try to receive data until either recv() fails or we see "--EOM--" + while((size = recv(socketfd, buffer, BUF-1, 0)) > -1) + { + // Zero-terminate incoming message + if(size > 0) + buffer[size] = '\0'; + + // Print received data to stdout + printf("%s", buffer); + + // Exit on End Of Message + if(strstr(buffer, "--EOM--") != NULL) + break; + + } + + // Close Unix socket connection + close(socketfd); + return EXIT_SUCCESS; +} diff --git a/structs.c b/structs.c index ab9b84da..f01237e0 100644 --- a/structs.c +++ b/structs.c @@ -16,7 +16,8 @@ FTLFileNamesStruct FTLfiles = { "/var/log/pihole-FTL.log", "/var/run/pihole-FTL.pid", "/var/run/pihole-FTL.port", - NULL + NULL, + "/var/run/pihole/FTL.sock" }; logFileNamesStruct files = { diff --git a/test/run.sh b/test/run.sh index b97f4a83..5aaf2508 100755 --- a/test/run.sh +++ b/test/run.sh @@ -8,39 +8,39 @@ dnsmasq_pre() { # Prepare FTL's files ts="$(dnsmasq_pre)" cat <> pihole.log -${ts} query[AAAA] raspberrypi from 127.0.0.1 -${ts} /etc/pihole/local.list raspberrypi is fda2:2001:5647:0:ba27:ebff:fe37:4205 -${ts} query[A] ChEcKiP.DyNdNs.OrG from 127.0.0.1 -${ts} forwarded ChEcKiP.DyNdNs.OrG to 2001:1608:10:25::9249:d69b -${ts} forwarded ChEcKiP.DyNdNs.OrG to 2001:1608:10:25::1c04:b12f -${ts} forwarded ChEcKiP.DyNdNs.OrG to 2620:0:ccd::2 -${ts} forwarded ChEcKiP.DyNdNs.OrG to 2620:0:ccc::2 -${ts} reply ChEcKiP.DyNdNs.OrG is -${ts} reply checkip.dyndns.com is 216.146.38.70 -${ts} reply checkip.dyndns.com is 216.146.43.71 -${ts} reply checkip.dyndns.com is 91.198.22.70 -${ts} reply checkip.dyndns.com is 216.146.43.70 -${ts} query[A] pi.hole from 10.8.0.2 -${ts} /etc/pihole/local.list pi.hole is 192.168.2.10 -${ts} query[A] example.com from 10.8.0.2 -${ts} /etc/pihole/local.list example.com is 192.168.2.10 -${ts} query[A] play.google.com from 192.168.2.208 -${ts} forwarded play.google.com to 2001:1608:10:25::9249:d69b -${ts} forwarded play.google.com to 2001:1608:10:25::1c04:b12f -${ts} forwarded play.google.com to 2620:0:ccd::2 -${ts} forwarded play.google.com to 2620:0:ccc::2 -${ts} reply play.google.com is -${ts} reply play.l.google.com is 216.58.208.110 -${ts} reply play.l.google.com is 216.58.208.110 -${ts} reply play.l.google.com is 216.58.208.110 -${ts} reply play.google.com is -${ts} query[AAAA] play.google.com from 192.168.2.208 -${ts} forwarded play.google.com to 2620:0:ccd::2 -${ts} reply play.l.google.com is 2a00:1450:4017:802::200e -${ts} query[A] blacklisted.com from 192.168.2.208 -${ts} /etc/pihole/black.list blacklisted.com is 1.2.3.4 -${ts} query[A] addomain.com from 192.168.2.208 -${ts} /etc/pihole/gravity.list addomain.com is 1.2.3.4 +${ts} 1 1270.0.01/1234 query[AAAA] raspberrypi from 127.0.0.1 +${ts} 1 1270.0.01/1234 /etc/pihole/local.list raspberrypi is fda2:2001:5647:0:ba27:ebff:fe37:4205 +${ts} 2 1270.0.01/1234 query[A] ChEcKiP.DyNdNs.OrG from 127.0.0.1 +${ts} 2 1270.0.01/1234 forwarded ChEcKiP.DyNdNs.OrG to 2001:1608:10:25::9249:d69b +${ts} 2 1270.0.01/1234 forwarded ChEcKiP.DyNdNs.OrG to 2001:1608:10:25::1c04:b12f +${ts} 2 1270.0.01/1234 forwarded ChEcKiP.DyNdNs.OrG to 2620:0:ccd::2 +${ts} 2 1270.0.01/1234 forwarded ChEcKiP.DyNdNs.OrG to 2620:0:ccc::2 +${ts} 2 1270.0.01/1234 reply ChEcKiP.DyNdNs.OrG is +${ts} 2 1270.0.01/1234 reply ChEcKiP.DyNdNs.OrG is 216.146.38.70 +${ts} 2 1270.0.01/1234 reply ChEcKiP.DyNdNs.OrG is 216.146.43.71 +${ts} 2 1270.0.01/1234 reply ChEcKiP.DyNdNs.OrG is 91.198.22.70 +${ts} 2 1270.0.01/1234 reply ChEcKiP.DyNdNs.OrG is 216.146.43.70 +${ts} 3 1270.0.01/1234 query[A] pi.hole from 10.8.0.2 +${ts} 3 1270.0.01/1234 /etc/pihole/local.list pi.hole is 192.168.2.10 +${ts} 4 1270.0.01/1234 query[A] example.com from 10.8.0.2 +${ts} 4 1270.0.01/1234 /etc/pihole/local.list example.com is 192.168.2.10 +${ts} 5 1270.0.01/1234 query[A] play.google.com from 192.168.2.208 +${ts} 5 1270.0.01/1234 forwarded play.google.com to 2001:1608:10:25::9249:d69b +${ts} 5 1270.0.01/1234 forwarded play.google.com to 2001:1608:10:25::1c04:b12f +${ts} 5 1270.0.01/1234 forwarded play.google.com to 2620:0:ccd::2 +${ts} 5 1270.0.01/1234 forwarded play.google.com to 2620:0:ccc::2 +${ts} 5 1270.0.01/1234 reply play.google.com is +${ts} 5 1270.0.01/1234 reply play.l.google.com is 216.58.208.110 +${ts} 5 1270.0.01/1234 reply play.l.google.com is 216.58.208.110 +${ts} 5 1270.0.01/1234 reply play.l.google.com is 216.58.208.110 +${ts} 5 1270.0.01/1234 reply play.google.com is +${ts} 6 1270.0.01/1234 query[AAAA] play.google.com from 192.168.2.208 +${ts} 6 1270.0.01/1234 forwarded play.google.com to 2620:0:ccd::2 +${ts} 6 1270.0.01/1234 reply play.l.google.com is 2a00:1450:4017:802::200e +${ts} 7 1270.0.01/1234 query[A] blacklisted.com from 192.168.2.208 +${ts} 7 1270.0.01/1234 /etc/pihole/black.list blacklisted.com is 1.2.3.4 +${ts} 8 1270.0.01/1234 query[A] addomain.com from 192.168.2.208 +${ts} 8 1270.0.01/1234 /etc/pihole/gravity.list addomain.com is 1.2.3.4 EOT touch "pihole-FTL.log" diff --git a/test/test_suite.sh b/test/test_suite.sh index 36b92498..01b1e4dc 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -242,6 +242,24 @@ load 'libs/bats-support/load' [[ ${lines[0]} == "pihole-FTL - The Pi-hole FTL engine" ]] } +@test "Unix socket returning data" { + run bash -c './socket-test travis' + echo "output: ${lines[@]}" + [[ ${lines[0]} == "Socket created" ]] + [[ ${lines[1]} == "Connection established" ]] + [[ ${lines[2]} == "domains_being_blocked -1" ]] + [[ ${lines[3]} == "dns_queries_today 7" ]] + [[ ${lines[4]} == "ads_blocked_today 2" ]] + [[ ${lines[5]} == "ads_percentage_today 28.571428" ]] + [[ ${lines[6]} == "unique_domains 6" ]] + [[ ${lines[7]} == "queries_forwarded 3" ]] + [[ ${lines[8]} == "queries_cached 2" ]] + [[ ${lines[9]} == "clients_ever_seen 3" ]] + [[ ${lines[10]} == "unique_clients 3" ]] + [[ ${lines[11]} == "status unknown" ]] + [[ ${lines[12]} == "---EOM---" ]] +} + @test "Final part of the tests: Killing pihole-FTL process" { run bash -c 'echo ">kill" | nc -v 127.0.0.1 4711' echo "output: ${lines[@]}"