diff --git a/src/args.c b/src/args.c index 41950ef82..8c9208f98 100644 --- a/src/args.c +++ b/src/args.c @@ -80,7 +80,7 @@ extern int sqlite3_shell_main(int argc, char **argv); // defined in database/sqlite3_rsync.c extern int sqlite3_rsync_main(int argc, char **argv); -bool dnsmasq_debug = false; +bool debug_mode = false; bool daemonmode = true, cli_mode = false; int argc_dnsmasq = 0; const char** argv_dnsmasq = NULL; @@ -611,7 +611,7 @@ void parse_args(int argc, char *argv[]) if(strcmp(argv[i], "lua") == 0 || strcmp(argv[i], "--lua") == 0) { - exit(run_lua_interpreter(argc - i, &argv[i], dnsmasq_debug)); + exit(run_lua_interpreter(argc - i, &argv[i], debug_mode)); } // Expose internal lua compiler @@ -729,7 +729,7 @@ void parse_args(int argc, char *argv[]) argv_dnsmasq = calloc(argc_dnsmasq, sizeof(const char*)); argv_dnsmasq[0] = ""; - if(dnsmasq_debug) + if(debug_mode) { argv_dnsmasq[1] = "-d"; argv_dnsmasq[2] = "--log-debug"; @@ -740,7 +740,7 @@ void parse_args(int argc, char *argv[]) argv_dnsmasq[2] = ""; } - if(dnsmasq_debug) + if(debug_mode) { printf("dnsmasq options: [0]: %s\n", argv_dnsmasq[0]); printf("dnsmasq options: [1]: %s\n", argv_dnsmasq[1]); @@ -751,7 +751,7 @@ void parse_args(int argc, char *argv[]) while(i < argc) { argv_dnsmasq[j++] = strdup(argv[i++]); - if(dnsmasq_debug) + if(debug_mode) printf("dnsmasq options: [%i]: %s\n", j-1, argv_dnsmasq[j-1]); } @@ -764,11 +764,11 @@ void parse_args(int argc, char *argv[]) if(strcmp(argv[i], "d") == 0 || strcmp(argv[i], "debug") == 0) { - dnsmasq_debug = true; + debug_mode = true; daemonmode = false; ok = true; - // Replace "-k" by "-d" (dnsmasq_debug mode implies nofork) + // Replace "-k" by "-d" (debug_mode mode implies nofork) argv_dnsmasq[1] = "-d"; } @@ -951,9 +951,9 @@ void parse_args(int argc, char *argv[]) // Enable stdout printing cli_mode = true; if(argc == i + 2) - exit(regex_test(dnsmasq_debug, quiet, argv[i + 1], NULL)); + exit(regex_test(debug_mode, quiet, argv[i + 1], NULL)); else if(argc == i + 3) - exit(regex_test(dnsmasq_debug, quiet, argv[i + 1], argv[i + 2])); + exit(regex_test(debug_mode, quiet, argv[i + 1], argv[i + 2])); else { printf("pihole-FTL: invalid option -- '%s' need either one or two parameters\nTry '%s --help' for more information\n", argv[i], argv[0]); diff --git a/src/args.h b/src/args.h index 7bd5b0687..746543ac3 100644 --- a/src/args.h +++ b/src/args.h @@ -12,7 +12,7 @@ void parse_args(int argc, char *argv[]); -extern bool daemonmode, cli_mode, dnsmasq_debug; +extern bool daemonmode, cli_mode; extern int argc_dnsmasq; extern const char ** argv_dnsmasq; diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index ce8d1d093..04589088d 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -293,6 +293,11 @@ struct event_desc { #define option_val(x) ((1u) << ((x) % OPTION_BITS)) #define option_bool(x) (option_var(x) & option_val(x)) +/***** Pi-hole modification *****/ +#define option_set(x) (option_var(x) |= option_val(x)) +#define option_clear(x) (option_var(x) &= ~option_val(x)) +/********************************/ + /* extra flags for my_syslog, we use facilities since they are known not to occupy the same bits as priorities, no matter how syslog.h is set up. MS_DEBUG messages are suppressed unless --log-debug is set. */ diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index b1504f3b2..47bafb146 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -56,6 +56,8 @@ #include "main.h" // ntp_server_start() #include "ntp/ntp.h" +// get_process_name() +#include "procps.h" // Private prototypes static void print_flags(const unsigned int flags); @@ -3386,7 +3388,7 @@ void FTL_forwarding_retried(const struct server *serv, const int oldID, const in volatile atomic_flag worker_already_terminating = ATOMIC_FLAG_INIT; void FTL_TCP_worker_terminating(bool finished) { - if(dnsmasq_debug) + if(get_dnsmasq_debug()) { // Nothing to be done here, forking does not happen in debug mode return; @@ -3431,7 +3433,7 @@ void FTL_TCP_worker_terminating(bool finished) // to ending up with a corrupted database. void FTL_TCP_worker_created(const int confd) { - if(dnsmasq_debug) + if(get_dnsmasq_debug()) { // Nothing to be done here, TCP worker forking does not happen // in debug mode @@ -3765,3 +3767,50 @@ void FTL_connection_error(const char *reason, const union mysockaddr *addr) free(server); } } + +/** + * @brief Retrieves the debug status of dnsmasq. + * + * @return true if the debug option is enabled, false otherwise. + */ +bool __attribute__ ((pure)) get_dnsmasq_debug(void) +{ + return option_bool(OPT_DEBUG); +} + +static bool enabled = false; +/** + * @brief Set the dnsmasq debug mode based on the enable flag and process ID. + * + * This function enables or disables the dnsmasq debug mode. When enabling, + * it logs the process ID and name, sets the debug option, and marks the + * debug mode as enabled. When disabling, it logs the detachment and clears + * the debug option. If the debug mode is already enabled, it does nothing. + * + * @param enable A boolean flag indicating whether to enable or disable debug mode. + * @param pid The process ID of the process to attach or detach the debugger. + */ +void set_dnsmasq_debug(const bool enable, const pid_t pid) +{ + // Get debugger process' name + char name[PROC_PATH_SIZ] = "???"; + get_process_name(pid, name); + + // Only enable or disable if the debug mode is not already set + if(enable && !get_dnsmasq_debug()) + { + // Enable debug mode + log_info("Debugger attached (%d: %s), entering dnsmasq debug mode", + pid, name); + option_set(OPT_DEBUG); + enabled = true; + + return; + } + else if(enabled) + { + // Disable debug mode + log_info("Debugger detached, leaving dnsmasq debug mode"); + option_clear(OPT_DEBUG); + } +} diff --git a/src/dnsmasq_interface.h b/src/dnsmasq_interface.h index f756abe88..7d174fc2a 100644 --- a/src/dnsmasq_interface.h +++ b/src/dnsmasq_interface.h @@ -51,6 +51,8 @@ bool FTL_unlink_DHCP_lease(const char *ipaddr, const char **hint); void FTL_connection_error(const char *reason, const union mysockaddr *addr); +bool get_dnsmasq_debug(void) __attribute__ ((pure)); + // defined in src/dnsmasq/cache.c extern char *querystr(char *desc, unsigned short type); diff --git a/src/gc.c b/src/gc.c index 552108dbb..10acca969 100644 --- a/src/gc.c +++ b/src/gc.c @@ -233,6 +233,8 @@ static void recycle(void) log_debug(DEBUG_GC, "Recycled %u clients, %u domains, and %u cache records (scanned %u queries)", clients_recycled, domains_recycled, cache_recycled, counters->queries); + + dump_strings(); } } @@ -528,8 +530,11 @@ static bool check_files_on_same_device(const char *path1, const char *path2) return s1.st_dev == s2.st_dev; } +static bool is_debugged = false; void *GC_thread(void *val) { + (void)val; // Mark parameter as unused + // Set thread name prctl(PR_SET_NAME, thread_names[GC], 0, 0, 0); @@ -636,6 +641,15 @@ void *GC_thread(void *val) if(killed) break; + // Check if FTL is being debugged and set dnsmasq's debug mode + // accordingly + const pid_t dpid = debugger(); + if((dpid > 0) != is_debugged) + { + is_debugged = dpid > 0; + set_dnsmasq_debug(is_debugged, dpid); + } + // Sleep for the remaining time of the interval (if any) const double time_end = double_time(); const double time_diff = time_end - time_start; diff --git a/src/gc.h b/src/gc.h index 03d1a4dd0..14db355cc 100644 --- a/src/gc.h +++ b/src/gc.h @@ -10,8 +10,14 @@ #ifndef GC_H #define GC_H +#include +#include + void *GC_thread(void *val); void runGC(const time_t now, time_t *lastGCrun, const bool flush); time_t get_rate_limit_turnaround(const unsigned int rate_limit_count); +// Defined in src/dnsmasq_interface.c +void set_dnsmasq_debug(const bool debug, const pid_t pid); + #endif //GC_H diff --git a/src/lookup-table.c b/src/lookup-table.c index a39e06f91..9cc6da326 100644 --- a/src/lookup-table.c +++ b/src/lookup-table.c @@ -75,8 +75,8 @@ static inline int cmp_hash(const uint32_t a, const uint32_t b) * found element or NULL (!) if the element is not found which provides no * information about the position where the element would need to be inserted. */ -static bool binsearch(const struct lookup_table *base, const uint32_t hash, - size_t size, const struct lookup_table **try) +static bool binsearch(struct lookup_table *base, const uint32_t hash, + size_t size, struct lookup_table **try) { // Initialize the base pointer to the start of the array *try = base; @@ -207,7 +207,7 @@ bool lookup_insert(const enum memory_type type, const unsigned int id, const uin // Find the correct position in the lookup_table array // We do not check the return value as we are inserting a new element // and don't care if elements with the same hash value exist already - const struct lookup_table *try = table; + struct lookup_table *try = table; binsearch(table, hash, *size, &try); // Calculate the position where the element would be inserted @@ -217,7 +217,7 @@ bool lookup_insert(const enum memory_type type, const unsigned int id, const uin // one position to the right to make space for the new element // Don't move anything if the element is added at the end of the array if(pos < *size) - memmove((void*)(try + 1), try, (*size - pos) * sizeof(struct lookup_table)); + memmove(try + 1, try, (*size - pos) * sizeof(struct lookup_table)); // Prepare the new lookup_table element and insert it at the correct // position @@ -253,7 +253,7 @@ bool lookup_remove(const enum memory_type type, const unsigned int id, const uin return false; // Find the correct position in the lookup_table array - const struct lookup_table *try = NULL; + struct lookup_table *try = NULL; if(!binsearch(table, hash, *size, &try)) { // The element is not in the array @@ -283,13 +283,14 @@ bool lookup_remove(const enum memory_type type, const unsigned int id, const uin // one position to the left to remove the element // Don't move anything if the element is removed from the end of the array if(pos < *size - 1) - memmove(&table[pos], &table[pos + 1], (*size - pos - 1) * sizeof(struct lookup_table)); + memmove(table + pos, table + pos + 1, + (*size - pos - 1) * sizeof(struct lookup_table)); // Decrease the number of elements in the array (*size)--; // Zero out the memory of the removed element - memset(&table[*size], 0, sizeof(struct lookup_table)); + memset(table + *size, 0, sizeof(struct lookup_table)); return true; } @@ -332,7 +333,7 @@ bool lookup_find_id(const enum memory_type type, const uint32_t hash, const stru return false; // Find the correct position in the lookup_table array - const struct lookup_table *try = NULL; + struct lookup_table *try = NULL; if(!binsearch(table, hash, *size, &try)) return false; diff --git a/src/lua/ftl_lua.c b/src/lua/ftl_lua.c index 5da8202dc..422c0a88b 100644 --- a/src/lua/ftl_lua.c +++ b/src/lua/ftl_lua.c @@ -36,7 +36,7 @@ #include "daemon.h" -int run_lua_interpreter(const int argc, char **argv, bool dnsmasq_debug) +int run_lua_interpreter(const int argc, char **argv, bool debug) { if(argc == 1) // No arguments after this one printf("Pi-hole FTL %s\n", get_FTL_version()); @@ -48,7 +48,7 @@ int run_lua_interpreter(const int argc, char **argv, bool dnsmasq_debug) { history_file = word.we_wordv[0]; const int ret_r = read_history(history_file); - if(dnsmasq_debug) + if(debug) { printf("Reading history ... "); if(ret_r == 0) @@ -60,7 +60,7 @@ int run_lua_interpreter(const int argc, char **argv, bool dnsmasq_debug) // The history file may not exist, try to create an empty one in this case if(ret_r == ENOENT) { - if(dnsmasq_debug) + if(debug) { printf("Creating new history file: %s\n", history_file); } @@ -70,7 +70,7 @@ int run_lua_interpreter(const int argc, char **argv, bool dnsmasq_debug) } } #else - if(dnsmasq_debug) + if(debug) printf("No readline available!\n"); #endif const int ret = lua_main(argc, argv); @@ -78,7 +78,7 @@ int run_lua_interpreter(const int argc, char **argv, bool dnsmasq_debug) if(history_file != NULL) { const int ret_w = write_history(history_file); - if(dnsmasq_debug) + if(debug) { printf("Writing history ... "); if(ret_w == 0) diff --git a/src/lua/ftl_lua.h b/src/lua/ftl_lua.h index 30bad1f95..b9d69e910 100644 --- a/src/lua/ftl_lua.h +++ b/src/lua/ftl_lua.h @@ -15,7 +15,7 @@ #define LUA_HISTORY_FILE "~/.pihole_lua_history" -int run_lua_interpreter(const int argc, char **argv, bool dnsmasq_debug); +int run_lua_interpreter(const int argc, char **argv, bool debug); int run_luac(const int argc, char **argv); int lua_main (int argc, char **argv); diff --git a/src/procps.c b/src/procps.c index a373a57c5..cfa493cd1 100644 --- a/src/procps.c +++ b/src/procps.c @@ -19,7 +19,6 @@ #include "config/config.h" #define PROCESS_NAME "pihole-FTL" -#define PROC_PATH_SIZ 32 // This function tries to obtain the process name of a given PID // It returns true on success, false otherwise and stores the process name in @@ -28,7 +27,7 @@ // to parse /proc//comm. The latter is not guaranteed to be correct (e.g. // processes can easily change it themselves using prctl with PR_SET_NAME), but // it is better than nothing. -static bool get_process_name(const pid_t pid, char name[PROC_PATH_SIZ]) +bool get_process_name(const pid_t pid, char name[PROC_PATH_SIZ]) { if(pid == 0) { diff --git a/src/procps.h b/src/procps.h index df13950c0..71a2a64c4 100644 --- a/src/procps.h +++ b/src/procps.h @@ -10,6 +10,13 @@ #ifndef PROCPS_H #define PROCPS_H + +#include +#include + +#define PROC_PATH_SIZ 32 + +bool get_process_name(const pid_t pid, char name[PROC_PATH_SIZ]); bool another_FTL(void); struct proc_mem { diff --git a/src/shmem.c b/src/shmem.c index 773582a3e..49ffda578 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -245,7 +245,7 @@ size_t _addstr(const char *input, const char *func, const int line, const char * } // Debugging output - log_debug(DEBUG_SHMEM, "Adding \"%s\" (len %zu) to buffer in %s() (%s:%i), next_str_pos is %u", + log_debug(DEBUG_SHMEM, "Adding \"%s\" (len %zu) to buffer in %s() (%s:%i), next_str_pos is %zu", input, len, func, short_path(file), line, shmSettings->next_str_pos); // Copy the C string pointed by input into the shared string buffer @@ -266,7 +266,8 @@ const char *_getstr(const size_t pos, const char *func, const int line, const ch return &((const char*)shm_strings.ptr)[pos]; else { - log_warn("Tried to access %zu in %s() (%s:%i) but next_str_pos is %u", pos, func, file, line, shmSettings->next_str_pos); + log_warn("Tried to access %zu in %s() (%s:%i) but next_str_pos is %zu", + pos, func, file, line, shmSettings->next_str_pos); return ""; } } @@ -1471,3 +1472,82 @@ void print_recycle_list_fullness(void) log_info(" Domains: %u/%u (%.2f%%)", recycler->domain.count, RECYCLE_ARRAY_LEN, (double)recycler->domain.count / RECYCLE_ARRAY_LEN * 100.0); log_info(" DNS Cache: %u/%u (%.2f%%)", recycler->dns_cache.count, RECYCLE_ARRAY_LEN, (double)recycler->dns_cache.count / RECYCLE_ARRAY_LEN * 100.0); } + +/** + * @brief Dumps the string table to a temporary file. + * + * This function iterates over the string table and writes each string to a temporary file + * located at "/tmp/stringdump.txt". It checks if each string is printable and escapes + * non-printable strings before writing them to the file. Additionally, it logs the number + * of non-printable strings and includes a human-readable timestamp in the output. + * + * The format of each line in the output file is: + * " " or "NONP" : "" (/) + * + * If the file cannot be opened for writing, an error message is logged. + */ +#define STRING_DUMPFILE "/tmp/stringdump.txt" +void dump_strings(void) +{ + // Dump string table to temporary file + FILE *str_dumpfile = fopen(STRING_DUMPFILE, "a"); + if(str_dumpfile != NULL) + { + char timestring[TIMESTR_SIZE] = { 0 }; + get_timestr(timestring, time(NULL), true, false); + fprintf(str_dumpfile, "String dump starting at %s\n", timestring); + log_info("String dump to "STRING_DUMPFILE); + + size_t j = 0, non_print = 0; + for(size_t i = 0; i < shmSettings->next_str_pos; i++) + { + char *sstr = (char*)getstr(i); + const size_t len = strlen(sstr); + char *buffer = sstr; + i += len; + j++; + + // Check if the string is printable + bool string_is_printable = true; + for(size_t k = 0; k < len; k++) + { + if(!isprint(sstr[k])) + { + string_is_printable = false; + non_print++; + break; + } + } + + // If the string is not printable, we escape it + if(!string_is_printable) + { + buffer = calloc(len * 4 + 1, sizeof(char)); + if(buffer == NULL) + { + log_err("Failed to allocate memory for string buffer"); + break; + } + binbuf_to_escaped_C_literal(sstr, len, buffer, len * 4 + 1); + } + + // Print string to file + fprintf(str_dumpfile, "%s %04zu: \"%s\" (%zu/%zu)\n", string_is_printable ? " " : "NONP", + j, buffer, i, len); + + // Free buffer if it was allocated + if(!string_is_printable) + free(buffer); + } + + // Print human-readable timestamp and number of strings which are not printable + fprintf(str_dumpfile, "Summary: %zu strings\n", j); + fprintf(str_dumpfile, " %zu non-printable strings\n", non_print); + fprintf(str_dumpfile, "\n"); + + // Close file + fclose(str_dumpfile); + } + else + log_err("Cannot open "STRING_DUMPFILE" for writing: %s", strerror(errno)); +} diff --git a/src/shmem.h b/src/shmem.h index 4f87de895..2b9b63af7 100644 --- a/src/shmem.h +++ b/src/shmem.h @@ -30,7 +30,7 @@ typedef struct { int version; pid_t pid; unsigned int global_shm_counter; - unsigned int next_str_pos; + size_t next_str_pos; unsigned int qps[QPS_AVGLEN]; } ShmSettings; @@ -202,4 +202,6 @@ bool set_next_recycled_ID(const enum memory_type type, const unsigned int id); bool get_next_recycled_ID(const enum memory_type type, unsigned int *id); void print_recycle_list_fullness(void); +void dump_strings(void); + #endif //SHARED_MEMORY_SERVER_H diff --git a/src/signals.c b/src/signals.c index 4017605fa..92ebb511f 100644 --- a/src/signals.c +++ b/src/signals.c @@ -139,8 +139,9 @@ void generate_backtrace(void) #endif } -static void __attribute__((noreturn)) signal_handler(int sig, siginfo_t *si, void *unused) +static void __attribute__((noreturn)) signal_handler(int sig, siginfo_t *si, void *context) { + (void)context; log_info("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); log_info("----------------------------> FTL crashed! <----------------------------"); log_info("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); @@ -270,8 +271,10 @@ static void __attribute__((noreturn)) signal_handler(int sig, siginfo_t *si, voi exit(EXIT_FAILURE); } -static void SIGRT_handler(int signum, siginfo_t *si, void *unused) +static void SIGRT_handler(int signum, siginfo_t *si, void *context) { + (void)context; + (void)si; // Backup errno const int _errno = errno; @@ -334,8 +337,9 @@ static void SIGRT_handler(int signum, siginfo_t *si, void *unused) errno = _errno; } -static void SIGTERM_handler(int signum, siginfo_t *si, void *unused) +static void SIGTERM_handler(int signum, siginfo_t *si, void *context) { + (void)context; // Ignore SIGTERM outside of the main process (TCP forks) if(mpid != getpid()) { @@ -497,7 +501,7 @@ void thread_sleepms(const enum thread_types thread, const int milliseconds) thread_cancellable[thread] = false; } -static void print_signal(int signum, siginfo_t *si, void *unused) +static void print_signal(int signum, siginfo_t *si, void *context) { printf("Received signal %d: \"%s\"\n", signum, strsignal(signum)); fflush(stdin); @@ -536,3 +540,37 @@ void restart_ftl(const char *reason) // Send SIGTERM to FTL kill(main_pid(), SIGTERM); } + +/** + * @brief Checks if the current process is being debugged. + * + * This function reads the /proc/self/status file to determine if the current + * process is being debugged by looking for the TracerPid field. If the field + * is found and has a non-zero value, it indicates that the process is being + * debugged. + * + * @return The PID of the debugger if the process is being debugged, otherwise 0. + */ +pid_t debugger(void) +{ + FILE *status = fopen("/proc/self/status", "r"); + if(status == NULL) + { + // Failed to open status file, assume not being debugged + log_debug(DEBUG_ANY, "Failed to open /proc/self/status: %s", strerror(errno)); + return 0; + } + + char line[256] = { 0 }; + while(fgets(line, sizeof(line), status) != NULL) + { + if(strncmp(line, "TracerPid:", 10) == 0) + { + // TracerPid field found + fclose(status); + return atoi(line + 10); + } + } + fclose(status); + return 0; +} diff --git a/src/signals.h b/src/signals.h index 3c2e8a75f..64dc396ca 100644 --- a/src/signals.h +++ b/src/signals.h @@ -10,6 +10,9 @@ #ifndef SIGNALS_H #define SIGNALS_H +// pid_t +#include + #include "enums.h" #define SIGUSR6 (SIGRTMIN + 6) @@ -22,6 +25,7 @@ void thread_sleepms(const enum thread_types thread, const int milliseconds); void generate_backtrace(void); int sigtest(void); void restart_ftl(const char *reason); +pid_t debugger(void); extern volatile int exit_code; extern volatile sig_atomic_t killed;