Skip to content

Commit

Permalink
Expose EDE information via API if available
Browse files Browse the repository at this point in the history
Signed-off-by: DL6ER <[email protected]>
  • Loading branch information
DL6ER committed Dec 18, 2024
1 parent 91ea8d4 commit dd39c5e
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 11 deletions.
3 changes: 3 additions & 0 deletions src/FTL.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
17 changes: 17 additions & 0 deletions src/api/docs/content/specs/queries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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
Expand Down
15 changes: 14 additions & 1 deletion src/api/queries.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ int api_queries_suggestions(struct ftl_conn *api)
JSON_SEND_OBJECT(json);
}

#define QUERYSTR "SELECT q.id,timestamp,q.type,status,d.domain,f.forward,additional_info,reply_type,reply_time,dnssec,c.ip,c.name,a.content,list_id"
#define QUERYSTR "SELECT q.id,timestamp,q.type,status,d.domain,f.forward,additional_info,reply_type,reply_time,dnssec,c.ip,c.name,a.content,list_id,ede"
// JOIN: Only return rows where there is a match in BOTH tables
// LEFT JOIN: Return all rows from the left table, and the matched rows from the right table
#define JOINSTR "JOIN client_by_id c ON q.client = c.id JOIN domain_by_id d ON q.domain = d.id LEFT JOIN forward_by_id f ON q.forward = f.id LEFT JOIN addinfo_by_id a ON a.id = q.additional_info"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down
15 changes: 15 additions & 0 deletions src/database/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 37 additions & 2 deletions src/database/query-table.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions src/database/query-table.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, " \
Expand All @@ -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, " \
Expand Down Expand Up @@ -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
3 changes: 0 additions & 3 deletions src/dnsmasq/dnsmasq.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
7 changes: 7 additions & 0 deletions src/dnsmasq/dnssec.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

#include "dnsmasq.h"
#include "log.h"

#ifdef HAVE_DNSSEC

Expand Down Expand Up @@ -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;
}
Expand Down
5 changes: 5 additions & 0 deletions test/api/libs/responseVerifyer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 3 additions & 3 deletions test/test_suite.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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);"* ]]
Expand Down

0 comments on commit dd39c5e

Please sign in to comment.