diff --git a/src/FTL.h b/src/FTL.h index 1d2d3dbb25..82ae8616d9 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -194,4 +194,7 @@ #define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) #define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) +// defined in cache.c +const char *edestr(int ede); + #endif // FTL_H diff --git a/src/api/docs/content/specs/queries.yaml b/src/api/docs/content/specs/queries.yaml index af2a755b6c..aa7df5e631 100644 --- a/src/api/docs/content/specs/queries.yaml +++ b/src/api/docs/content/specs/queries.yaml @@ -224,6 +224,17 @@ components: type: string description: IP or name + port of upstream server nullable: true + ede: + type: object + description: Extended DNS Error (EDE) information + properties: + code: + type: integer + description: EDE code + text: + type: string + nullable: true + description: EDE message (if available) example: - time: 1581907991.539157 type: "A" @@ -240,6 +251,9 @@ components: list_id: NULL upstream: "localhost#5353" dbid: 112421354 + ede: + code: 0 + text: null - time: 1581907871.583821 type: "AAAA" domain: "api.github.com" @@ -255,6 +269,9 @@ components: list_id: NULL upstream: "localhost#5353" dbid: 112421355 + ede: + code: 0 + text: null cursor: type: integer description: Database ID of most recent query to show diff --git a/src/api/queries.c b/src/api/queries.c index 6e89c0e832..9fa9aca14c 100644 --- a/src/api/queries.c +++ b/src/api/queries.c @@ -215,6 +215,8 @@ static void querystr_finish(char *querystr, const char *sort_col, const char *so sort_col_sql = "q.dnssec"; else if(strcasecmp(sort_col, "list_id") == 0) sort_col_sql = "list_id"; + else if(strcasecmp(sort_col, "ede") == 0) + sort_col_sql = "ede"; // ... and the sort direction if(strcasecmp(sort_dir, "asc") == 0 || strcasecmp(sort_dir, "ascending") == 0) @@ -1000,6 +1002,17 @@ int api_queries(struct ftl_conn *api) else JSON_ADD_NULL_TO_OBJECT(item, "list_id"); + // Add EDE code and text (if applicable) + const int ede_code = sqlite3_column_int(read_stmt, 14); // ede_code + cJSON *ede = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(ede, "code", ede_code); + if(ede_code > 0) + JSON_REF_STR_IN_OBJECT(ede, "text", edestr(ede_code)); + else + JSON_ADD_NULL_TO_OBJECT(ede, "text"); + JSON_ADD_ITEM_TO_OBJECT(item, "ede", ede); + + // Add CNAME information if it exists const unsigned char *cname = NULL; switch(query.status) { diff --git a/src/database/common.c b/src/database/common.c index 7917c5b94f..31a76d2ebd 100644 --- a/src/database/common.c +++ b/src/database/common.c @@ -616,6 +616,21 @@ void db_init(void) dbversion = db_get_int(db, DB_VERSION); } + // Update to version 21 if lower + if(dbversion < 21) + { + // Update to version 21: Add additional column "ede" in the query_storage table + log_info("Updating long-term database to version 21"); + if(!add_query_storage_column_ede(db)) + { + log_info("Additional column 'ede' in the query_storage table cannot be added, database not available"); + dbclose(&db); + return; + } + // Get updated version + dbversion = db_get_int(db, DB_VERSION); + } + /* * * * * * * * * * * * * IMPORTANT * * * * * * * * * * * * * * If you add a new database version, check if the in-memory * schema needs to be update as well (always recreated from diff --git a/src/database/query-table.c b/src/database/query-table.c index 88fa972304..756617c776 100644 --- a/src/database/query-table.c +++ b/src/database/query-table.c @@ -184,7 +184,8 @@ bool init_memory_database(void) "?11," \ "?12," \ "?13," \ - "?14)", -1, SQLITE_PREPARE_PERSISTENT, &query_stmt, NULL); + "?14," + "?15)", -1, SQLITE_PREPARE_PERSISTENT, &query_stmt, NULL); if( rc != SQLITE_OK ) { log_err("init_memory_database(query_storage) - SQL error step: %s", sqlite3_errstr(rc)); @@ -344,7 +345,6 @@ static void log_in_memory_usage(void) } } - // Attach database using specified path and alias bool attach_database(sqlite3* db, const char **message, const char *path, const char *alias) { @@ -917,6 +917,38 @@ bool rename_query_storage_column_regex_id(sqlite3 *db) return true; } +bool add_query_storage_column_ede(sqlite3 *db) +{ + // Start transaction of database update + SQL_bool(db, "BEGIN TRANSACTION"); + + // Add additional column to the query_storage table + SQL_bool(db, "ALTER TABLE query_storage ADD COLUMN ede INTEGER"); + + // Update VIEW queries + SQL_bool(db, "DROP VIEW queries"); + SQL_bool(db, "CREATE VIEW queries AS " + "SELECT id, timestamp, type, status, " + "CASE typeof(domain) WHEN 'integer' THEN (SELECT domain FROM domain_by_id d WHERE d.id = q.domain) ELSE domain END domain," + "CASE typeof(client) WHEN 'integer' THEN (SELECT ip FROM client_by_id c WHERE c.id = q.client) ELSE client END client," + "CASE typeof(forward) WHEN 'integer' THEN (SELECT forward FROM forward_by_id f WHERE f.id = q.forward) ELSE forward END forward," + "CASE typeof(additional_info) WHEN 'integer' THEN (SELECT content FROM addinfo_by_id a WHERE a.id = q.additional_info) ELSE additional_info END additional_info, " + "reply_type, reply_time, dnssec, list_id, ede " + "FROM query_storage q"); + + // Update database version to 21 + if(!db_set_FTL_property(db, DB_VERSION, 21)) + { + log_err("add_query_storage_column_ede(): Failed to update database version!"); + return false; + } + + // Finish transaction + SQL_bool(db, "COMMIT"); + + return true; +} + bool optimize_queries_table(sqlite3 *db) { // Start transaction of database update @@ -1612,6 +1644,9 @@ bool queries_to_database(void) // Not applicable, setting NULL sqlite3_bind_null(query_stmt, 14); + // EDE + sqlite3_bind_int(query_stmt, 15, query->ede); + // Step and check if successful rc = sqlite3_step(query_stmt); sqlite3_clear_bindings(query_stmt); diff --git a/src/database/query-table.h b/src/database/query-table.h index 20b02e6c63..470fddbecf 100644 --- a/src/database/query-table.h +++ b/src/database/query-table.h @@ -23,7 +23,7 @@ "client TEXT NOT NULL, " \ "forward TEXT );" -#define MEMDB_VERSION 20 +#define MEMDB_VERSION 21 #define CREATE_QUERY_STORAGE_TABLE "CREATE TABLE query_storage ( id INTEGER PRIMARY KEY AUTOINCREMENT, " \ "timestamp INTEGER NOT NULL, " \ "type INTEGER NOT NULL, " \ @@ -35,7 +35,8 @@ "reply_type INTEGER, " \ "reply_time REAL, " \ "dnssec INTEGER, " \ - "list_id INTEGER );" + "list_id INTEGER, " \ + "ede INTEGER );" #define CREATE_QUERIES_VIEW "CREATE VIEW queries AS " \ "SELECT id, timestamp, type, status, " \ @@ -126,5 +127,6 @@ bool add_query_storage_columns(sqlite3 *db); bool add_query_storage_column_regex_id(sqlite3 *db); bool add_ftl_table_description(sqlite3 *db); bool rename_query_storage_column_regex_id(sqlite3 *db); +bool add_query_storage_column_ede(sqlite3 *db); #endif //QUERY_TABLE_PRIVATE_H diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index 04589088d3..26df6e8ca7 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -1931,6 +1931,3 @@ int add_update_server(int flags, const char *interface, const char *domain, union all_addr *local_addr); - -// Pi-hole modification -const char *edestr(int ede); diff --git a/src/dnsmasq/dnssec.c b/src/dnsmasq/dnssec.c index ed2f53ff17..262ea25260 100644 --- a/src/dnsmasq/dnssec.c +++ b/src/dnsmasq/dnssec.c @@ -16,6 +16,7 @@ */ #include "dnsmasq.h" +#include "log.h" #ifdef HAVE_DNSSEC @@ -512,12 +513,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in { /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ if (serial_compare_32(curtime, sig_inception) == SERIAL_LT) + { + log_debug(DEBUG_DNSSEC, "Signature inception time is %f seconds in the future", difftime(sig_inception, curtime)); continue; + } else failflags &= ~DNSSEC_FAIL_NYV; if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT) + { + log_debug(DEBUG_DNSSEC, "Signature expiration time is %f seconds in the past", difftime(curtime, sig_expiration)); continue; + } else failflags &= ~DNSSEC_FAIL_EXP; } diff --git a/test/api/libs/responseVerifyer.py b/test/api/libs/responseVerifyer.py index 27558907ee..d394ea54d6 100644 --- a/test/api/libs/responseVerifyer.py +++ b/test/api/libs/responseVerifyer.py @@ -295,6 +295,11 @@ def verify_property(self, YAMLprops: dict, YAMLexamples: dict, FTLprops: dict, p return False YAMLprop = YAMLprops[props[-1]] + # Check if FTL returned null when an object was expected + if FTLprops is None: + self.errors.append("FTL's response is null in " + flat_path) + return False + # Check if the property is defined in the FTL response if props[-1] not in FTLprops: self.errors.append("Property '" + flat_path + "' missing in FTL's response") diff --git a/test/test_suite.bats b/test/test_suite.bats index 0fe6c2fddf..b3f2a60bd4 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -692,16 +692,16 @@ @test "pihole-FTL.db schema is as expected" { run bash -c './pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db .dump' printf "%s\n" "${lines[@]}" - [[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"query_storage\" (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, type INTEGER NOT NULL, status INTEGER NOT NULL, domain INTEGER NOT NULL, client INTEGER NOT NULL, forward INTEGER, additional_info INTEGER, reply_type INTEGER, reply_time REAL, dnssec INTEGER, list_id INTEGER);"* ]] + [[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"query_storage\" (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, type INTEGER NOT NULL, status INTEGER NOT NULL, domain INTEGER NOT NULL, client INTEGER NOT NULL, forward INTEGER, additional_info INTEGER, reply_type INTEGER, reply_time REAL, dnssec INTEGER, list_id INTEGER, ede INTEGER);"* ]] [[ "${lines[@]}" == *"CREATE INDEX idx_queries_timestamps ON \"query_storage\" (timestamp);"* ]] [[ "${lines[@]}" == *"CREATE TABLE ftl (id INTEGER PRIMARY KEY NOT NULL, value BLOB NOT NULL, description TEXT);"* ]] [[ "${lines[@]}" == *"CREATE TABLE counters (id INTEGER PRIMARY KEY NOT NULL, value INTEGER NOT NULL);"* ]] [[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"network\" (id INTEGER PRIMARY KEY NOT NULL, hwaddr TEXT UNIQUE NOT NULL, interface TEXT NOT NULL, firstSeen INTEGER NOT NULL, lastQuery INTEGER NOT NULL, numQueries INTEGER NOT NULL, macVendor TEXT, aliasclient_id INTEGER);"* ]] [[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"network_addresses\" (network_id INTEGER NOT NULL, ip TEXT UNIQUE NOT NULL, lastSeen INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), name TEXT, nameUpdated INTEGER, FOREIGN KEY(network_id) REFERENCES network(id));"* ]] [[ "${lines[@]}" == *"CREATE TABLE aliasclient (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, comment TEXT);"* ]] - [[ "${lines[@]}" == *"INSERT INTO ftl VALUES(0,20,'Database version');"* ]] + [[ "${lines[@]}" == *"INSERT INTO ftl VALUES(0,21,'Database version');"* ]] # vvv This has been added in version 10 vvv - [[ "${lines[@]}" == *"CREATE VIEW queries AS SELECT id, timestamp, type, status, CASE typeof(domain) WHEN 'integer' THEN (SELECT domain FROM domain_by_id d WHERE d.id = q.domain) ELSE domain END domain,CASE typeof(client) WHEN 'integer' THEN (SELECT ip FROM client_by_id c WHERE c.id = q.client) ELSE client END client,CASE typeof(forward) WHEN 'integer' THEN (SELECT forward FROM forward_by_id f WHERE f.id = q.forward) ELSE forward END forward,CASE typeof(additional_info) WHEN 'integer' THEN (SELECT content FROM addinfo_by_id a WHERE a.id = q.additional_info) ELSE additional_info END additional_info, reply_type, reply_time, dnssec, list_id FROM query_storage q;"* ]] + [[ "${lines[@]}" == *"CREATE VIEW queries AS SELECT id, timestamp, type, status, CASE typeof(domain) WHEN 'integer' THEN (SELECT domain FROM domain_by_id d WHERE d.id = q.domain) ELSE domain END domain,CASE typeof(client) WHEN 'integer' THEN (SELECT ip FROM client_by_id c WHERE c.id = q.client) ELSE client END client,CASE typeof(forward) WHEN 'integer' THEN (SELECT forward FROM forward_by_id f WHERE f.id = q.forward) ELSE forward END forward,CASE typeof(additional_info) WHEN 'integer' THEN (SELECT content FROM addinfo_by_id a WHERE a.id = q.additional_info) ELSE additional_info END additional_info, reply_type, reply_time, dnssec, list_id, ede FROM query_storage q;"* ]] [[ "${lines[@]}" == *"CREATE TABLE domain_by_id (id INTEGER PRIMARY KEY, domain TEXT NOT NULL);"* ]] [[ "${lines[@]}" == *"CREATE TABLE client_by_id (id INTEGER PRIMARY KEY, ip TEXT NOT NULL, name TEXT);"* ]] [[ "${lines[@]}" == *"CREATE TABLE forward_by_id (id INTEGER PRIMARY KEY, forward TEXT NOT NULL);"* ]]