From 563b02ccc7a898694243e784ec9d8557e25cf6db Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 2 Apr 2024 13:43:43 +0200 Subject: [PATCH] Add new CONNECTION_ERROR message to the Pi-hole diagnosis system Signed-off-by: DL6ER --- src/database/message-table.c | 81 +++++++++++++++++++++++++++++++++++- src/database/message-table.h | 1 + src/dnsmasq_interface.c | 43 +++++++++++++------ src/enums.h | 1 + 4 files changed, 113 insertions(+), 13 deletions(-) diff --git a/src/database/message-table.c b/src/database/message-table.c index f6eaef8c..a8f00f7e 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -60,6 +60,8 @@ static const char *get_message_type_str(const enum message_type type) return "DISK_EXTENDED"; case CERTIFICATE_DOMAIN_MISMATCH_MESSAGE: return "CERTIFICATE_DOMAIN_MISMATCH"; + case CONNECTION_ERROR_MESSAGE: + return "CONNECTION_ERROR"; case MAX_MESSAGE: default: return "UNKNOWN"; @@ -92,6 +94,8 @@ static enum message_type get_message_type_from_string(const char *typestr) return DISK_MESSAGE_EXTENDED; else if (strcmp(typestr, "CERTIFICATE_DOMAIN_MISMATCH") == 0) return CERTIFICATE_DOMAIN_MISMATCH_MESSAGE; + else if (strcmp(typestr, "CONNECTION_ERROR") == 0) + return CONNECTION_ERROR_MESSAGE; else return MAX_MESSAGE; } @@ -183,6 +187,14 @@ static unsigned char message_blob_types[MAX_MESSAGE][5] = SQLITE_NULL, // not used SQLITE_NULL, // not used SQLITE_NULL // not used + }, + { + // CONNECTION_ERROR_MESSAGE: The message column contains the server address + SQLITE_TEXT, // reason + SQLITE_TEXT, // error message + SQLITE_NULL, // not used + SQLITE_NULL, // not used + SQLITE_NULL // not used } }; // Create message table in the database @@ -710,6 +722,40 @@ static void format_certificate_domain_mismatch(char *plain, const int sizeof_pla free(escaped_domain); } +static void format_connection_error(char *plain, const int sizeof_plain, char *html, const int sizeof_html, + const char *server, const char *reason, const char *error) +{ + if(snprintf(plain, sizeof_plain, "Connection error (%s): %s (%s)", server, reason, error) > sizeof_plain) + log_warn("format_connection_error(): Buffer too small to hold plain message, warning truncated"); + + // Return early if HTML text is not required + if(sizeof_html < 1 || html == NULL) + return; + + char *escaped_reason = escape_html(reason); + char *escaped_error = escape_html(error); + char *escaped_server = escape_html(server); + + // Return early if memory allocation failed + if(escaped_reason == NULL || escaped_error == NULL || escaped_server == NULL) + { + if(escaped_reason != NULL) + free(escaped_reason); + if(escaped_error != NULL) + free(escaped_error); + if(escaped_server != NULL) + free(escaped_server); + return; + } + + if(snprintf(html, sizeof_html, "Connection error (%s): %s (%s)", server, reason, error) > sizeof_html) + log_warn("format_connection_error(): Buffer too small to hold HTML message, warning truncated"); + + free(escaped_reason); + free(escaped_error); + free(escaped_server); +} + int count_messages(const bool filter_dnsmasq_warnings) { int count = 0; @@ -798,7 +844,7 @@ bool format_messages(cJSON *array) // Generate messages char plain[1024] = { 0 }, html[2048] = { 0 }; - const int mtype = get_message_type_from_string(mtypestr); + const enum message_type mtype = get_message_type_from_string(mtypestr); switch(mtype) { case REGEX_MESSAGE: @@ -944,6 +990,23 @@ bool format_messages(cJSON *array) break; } + + case CONNECTION_ERROR_MESSAGE: + { + const char *server = (const char*)sqlite3_column_text(stmt, 3); + const char *reason = (const char*)sqlite3_column_text(stmt, 4); + const char *error = (const char*)sqlite3_column_text(stmt, 5); + + format_connection_error(plain, sizeof(plain), html, sizeof(html), + server, reason, error); + + break; + } + + case MAX_MESSAGE: // Fall through + default: + log_warn("format_messages() - Unknown message type: %s", mtypestr); + break; } // Add the plain message @@ -1183,3 +1246,19 @@ void log_certificate_domain_mismatch(const char *certfile, const char *domain) if(rowid == -1) log_err("log_certificate_domain_mismatch(): Failed to add message to database"); } + +void log_connection_error(const char *server, const char *reason, const char *error) +{ + // Create message + char buf[2048]; + format_connection_error(buf, sizeof(buf), NULL, 0, server, reason, error); + + // Log to FTL.log + log_warn("%s", buf); + + // Log to database + const int rowid = add_message(CONNECTION_ERROR_MESSAGE, server, 2, reason, error); + + if(rowid == -1) + log_err("logg_connection_error(): Failed to add message to database"); +} diff --git a/src/database/message-table.h b/src/database/message-table.h index 14956bf5..d92bbe8d 100644 --- a/src/database/message-table.h +++ b/src/database/message-table.h @@ -29,5 +29,6 @@ void logg_warn_dnsmasq_message(char *message); void log_resource_shortage(const double load, const int nprocs, const int shmem, const int disk, const char *path, const char *msg); void logg_inaccessible_adlist(const int dbindex, const char *address); void log_certificate_domain_mismatch(const char *certfile, const char *domain); +void log_connection_error(const char *server, const char *reason, const char *error); #endif //MESSAGETABLE_H diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 686c3016..1691056a 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -3514,20 +3514,39 @@ void FTL_connection_error(const char *reason, const union mysockaddr *addr) // Make a private copy of the error const char *error = strerror(errno); - if(config.debug.queries.v.b) - { - const int id = daemon->log_display_id; + // Format the address into a string (if available) + in_port_t port = 0; + char ip[ADDRSTRLEN + 1] = { 0 }; + if(addr != NULL) + mysockaddr_extract_ip_port(addr, ip, &port); - // Format the address into a string (if available) - in_port_t port = 0; - char ip[ADDRSTRLEN] = { 0 }; - if(addr != NULL) - mysockaddr_extract_ip_port(addr, ip, &port); - - // Log to FTL.log - log_debug(DEBUG_QUERIES, "Connection error: %s (%s) for %s#%u (ID %d)", reason, error, ip, port, id); - } + // Log to FTL.log + const int id = daemon->log_display_id; + log_debug(DEBUG_QUERIES, "Connection error (%s#%u, ID %d): %s (%s)", ip, port, id, reason, error); // Log to pihole.log my_syslog(LOG_ERR, "%s: %s", reason, error); + + // Add to Pi-hole diagnostics but do not add messages more often than + // once every five seconds to avoid hammering the database with errors + // on continuously failing connections + static time_t last = 0; + if(time(NULL) - last > 5) + { + last = time(NULL); + char *server = NULL; + if(ip[0] != '\0') + { + const size_t len = strlen(ip) + 6; + server = calloc(len, sizeof(char)); + if(server != NULL) + { + snprintf(server, len, "%s#%u", ip, port); + server[len - 1] = '\0'; + } + } + log_connection_error(server, reason, error); + if(server != NULL) + free(server); + } } \ No newline at end of file diff --git a/src/enums.h b/src/enums.h index dac8ff8d..ab09498a 100644 --- a/src/enums.h +++ b/src/enums.h @@ -273,6 +273,7 @@ enum message_type { INACCESSIBLE_ADLIST_MESSAGE, DISK_MESSAGE_EXTENDED, CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, + CONNECTION_ERROR_MESSAGE, MAX_MESSAGE, } __attribute__ ((packed));