diff --git a/src/api/list.c b/src/api/list.c index c584a334..f0185846 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -58,9 +58,11 @@ static int api_list_read(struct ftl_conn *api, char *name = NULL; if(table.client != NULL) { - // Try to obtain hostname if this is a valid IP address + // Try to obtain hostname if(isValidIPv4(table.client) || isValidIPv6(table.client)) name = getNameFromIP(NULL, table.client); + else if(isMAC(table.client)) + name = getNameFromMAC(table.client); } JSON_COPY_STR_TO_OBJECT(row, "client", table.client); diff --git a/src/database/network-table.c b/src/database/network-table.c index 169ba9c8..92837b31 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -2144,6 +2144,85 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr) return name; } +// Get most recently seen host name of device identified by MAC address +char *__attribute__((malloc)) getNameFromMAC(const char *client) +{ + // Return early if database is known to be broken + if(FTLDBerror()) + return NULL; + + log_info("Looking up host name for %s", client); + + // Open pihole-FTL.db database file + sqlite3 *db = NULL; + if((db = dbopen(false, false)) == NULL) + { + log_warn("getNameFromMAC(\"%s\") - Failed to open DB", client); + return NULL; + } + + // Nothing found for the same device + // Check for a host name associated with the given client as MAC address + // COLLATE NOCASE: Case-insensitive comparison + const char *querystr = "SELECT name FROM network_addresses " + "WHERE name IS NOT NULL AND " + "network_id = (SELECT id FROM network WHERE hwaddr = ? COLLATE NOCASE) " + "ORDER BY lastSeen DESC LIMIT 1"; + sqlite3_stmt *stmt = NULL; + int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL); + if(rc != SQLITE_OK) + { + log_err("getNameFromMAC(\"%s\") - SQL error prepare: %s", + client, sqlite3_errstr(rc)); + dbclose(&db); + return NULL; + } + + // Bind client to prepared statement + if((rc = sqlite3_bind_text(stmt, 1, client, -1, SQLITE_STATIC)) != SQLITE_OK) + { + log_warn("getNameFromMAC(\"%s\"): Failed to bind ip: %s", + client, sqlite3_errstr(rc)); + checkFTLDBrc(rc); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + dbclose(&db); + return NULL; + } + + char *name = NULL; + rc = sqlite3_step(stmt); + if(rc == SQLITE_ROW) + { + // Database record found (result might be empty) + name = strdup((char*)sqlite3_column_text(stmt, 0)); + + if(config.debug.resolver.v.b) + log_debug(DEBUG_RESOLVER, "Found database host name (by MAC) %s -> %s", + client, name); + } + else if(rc == SQLITE_DONE) + { + // Not found + if(config.debug.resolver.v.b) + log_debug(DEBUG_RESOLVER, " ---> not found"); + } + else + { + // Error + checkFTLDBrc(rc); + return NULL; + } + + // Finalize statement and close database handle + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + dbclose(&db); + return name; +} + // Get interface of device identified by IP address char *__attribute__((malloc)) getIfaceFromIP(sqlite3 *db, const char *ipaddr) { @@ -2425,3 +2504,29 @@ bool networkTable_deleteDevice(sqlite3 *db, const int id, const char **message) return true; } + +// Counting number of occurrences of a specific char in a string +static size_t __attribute__ ((pure)) count_char(const char *haystack, const char needle) +{ + size_t count = 0u; + while(*haystack) + if (*haystack++ == needle) + ++count; + return count; +} + +// Identify MAC addresses using a set of suitable criteria +bool __attribute__ ((pure)) isMAC(const char *input) +{ + if(input != NULL && // Valid input + strlen(input) == 17u && // MAC addresses are always 17 chars long (6 bytes + 5 colons) + count_char(input, ':') == 5u && // MAC addresses always have 5 colons + strstr(input, "::") == NULL) // No double-colons (IPv6 address abbreviation) + { + // This is a MAC address of the form AA:BB:CC:DD:EE:FF + return true; + } + + // Not a MAC address + return false; +} diff --git a/src/database/network-table.h b/src/database/network-table.h index 4b9f6952..1cb4697d 100644 --- a/src/database/network-table.h +++ b/src/database/network-table.h @@ -18,12 +18,14 @@ bool create_network_addresses_with_names_table(sqlite3 *db); void parse_neighbor_cache(sqlite3 *db); void updateMACVendorRecords(sqlite3 *db); bool unify_hwaddr(sqlite3 *db); -char* __attribute__((malloc)) getMACfromIP(sqlite3 *db, const char* ipaddr); +char *getMACfromIP(sqlite3 *db, const char* ipaddr) __attribute__((malloc)); int getAliasclientIDfromIP(sqlite3 *db, const char *ipaddr); -char* __attribute__((malloc)) getNameFromIP(sqlite3 *db, const char* ipaddr); -char* __attribute__((malloc)) getIfaceFromIP(sqlite3 *db, const char* ipaddr); +char *getNameFromIP(sqlite3 *db, const char* ipaddr) __attribute__((malloc)); +char *getNameFromMAC(const char *client) __attribute__((malloc)); +char *getIfaceFromIP(sqlite3 *db, const char* ipaddr) __attribute__((malloc)); void resolveNetworkTableNames(void); bool flush_network_table(void); +bool isMAC(const char *input) __attribute__ ((pure)); typedef struct { unsigned int id; diff --git a/src/database/sqlite3-ext.c b/src/database/sqlite3-ext.c index 67059ff7..f52ef029 100644 --- a/src/database/sqlite3-ext.c +++ b/src/database/sqlite3-ext.c @@ -29,32 +29,6 @@ // isMAC() #include "network-table.h" -// Counting number of occurrences of a specific char in a string -static size_t __attribute__ ((pure)) count_char(const char *haystack, const char needle) -{ - size_t count = 0u; - while(*haystack) - if (*haystack++ == needle) - ++count; - return count; -} - -// Identify MAC addresses using a set of suitable criteria -static bool __attribute__ ((pure)) isMAC(const char *input) -{ - if(input != NULL && // Valid input - strlen(input) == 17u && // MAC addresses are always 17 chars long (6 bytes + 5 colons) - count_char(input, ':') == 5u && // MAC addresses always have 5 colons - strstr(input, "::") == NULL) // No double-colons (IPv6 address abbreviation) - { - // This is a MAC address of the form AA:BB:CC:DD:EE:FF - return true; - } - - // Not a MAC address - return false; -} - static void subnet_match_impl(sqlite3_context *context, int argc, sqlite3_value **argv) { // Exactly two arguments should be submitted to this routine