mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
ff0ffff420
Signed-off-by: DL6ER <dl6er@dl6er.de>
2544 lines
74 KiB
C
2544 lines
74 KiB
C
/* Pi-hole: A black hole for Internet advertisements
|
|
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
|
* Network-wide ad blocking via your own hardware.
|
|
*
|
|
* FTL Engine
|
|
* Network table routines
|
|
*
|
|
* This file is copyright under the latest version of the EUPL.
|
|
* Please see LICENSE file for your rights under this license. */
|
|
|
|
#include "FTL.h"
|
|
#include "network-table.h"
|
|
#include "common.h"
|
|
#include "shmem.h"
|
|
#include "log.h"
|
|
// timer_elapsed_msec()
|
|
#include "timers.h"
|
|
#include "config/config.h"
|
|
#include "datastructure.h"
|
|
// struct config
|
|
#include "config/config.h"
|
|
// resolve_this_name()
|
|
#include "resolve.h"
|
|
// killed
|
|
#include "signals.h"
|
|
|
|
// Private prototypes
|
|
static char *getMACVendor(const char *hwaddr) __attribute__ ((malloc));
|
|
enum arp_status { CLIENT_NOT_HANDLED, CLIENT_ARP_COMPLETE, CLIENT_ARP_INCOMPLETE } __attribute__ ((packed));
|
|
|
|
bool create_network_table(sqlite3 *db)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return false;
|
|
|
|
// Start transaction
|
|
SQL_bool(db, "BEGIN TRANSACTION");
|
|
|
|
// Create network table in the database
|
|
SQL_bool(db, "CREATE TABLE network ( id INTEGER PRIMARY KEY NOT NULL, " \
|
|
"ip TEXT NOT NULL, " \
|
|
"hwaddr TEXT NOT NULL, " \
|
|
"interface TEXT NOT NULL, " \
|
|
"name TEXT, " \
|
|
"firstSeen INTEGER NOT NULL, " \
|
|
"lastQuery INTEGER NOT NULL, " \
|
|
"numQueries INTEGER NOT NULL, " \
|
|
"macVendor TEXT);");
|
|
|
|
// Update database version to 3
|
|
if(!db_set_FTL_property(db, DB_VERSION, 3))
|
|
{
|
|
log_warn("create_network_table(): Failed to update database version!");
|
|
return false;
|
|
}
|
|
|
|
// End transaction
|
|
SQL_bool(db, "COMMIT");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool create_network_addresses_table(sqlite3 *db)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return false;
|
|
|
|
// Disable foreign key enforcement for this transaction
|
|
// Otherwise, dropping the network table would not be allowed
|
|
SQL_bool(db, "PRAGMA foreign_keys=OFF");
|
|
|
|
// Begin new transaction
|
|
SQL_bool(db, "BEGIN TRANSACTION");
|
|
|
|
// Create network_addresses table in the database
|
|
SQL_bool(db, "CREATE TABLE network_addresses ( network_id INTEGER NOT NULL, "\
|
|
"ip TEXT NOT NULL, "\
|
|
"lastSeen INTEGER NOT NULL DEFAULT (cast(strftime('%%s', 'now') as int)), "\
|
|
"UNIQUE(network_id,ip), "\
|
|
"FOREIGN KEY(network_id) REFERENCES network(id));");
|
|
|
|
// Create a network_addresses row for each entry in the network table
|
|
// Ignore possible duplicates as they are harmless and can be skipped
|
|
SQL_bool(db, "INSERT OR IGNORE INTO network_addresses (network_id,ip) SELECT id,ip FROM network;");
|
|
|
|
// Remove IP column from network table.
|
|
// As ALTER TABLE is severely limited, we have to do the column deletion manually.
|
|
// Step 1: We create a new table without the ip column
|
|
SQL_bool(db, "CREATE TABLE network_bck ( id INTEGER PRIMARY KEY NOT NULL, " \
|
|
"hwaddr TEXT UNIQUE NOT NULL, " \
|
|
"interface TEXT NOT NULL, " \
|
|
"name TEXT, " \
|
|
"firstSeen INTEGER NOT NULL, " \
|
|
"lastQuery INTEGER NOT NULL, " \
|
|
"numQueries INTEGER NOT NULL, " \
|
|
"macVendor TEXT);");
|
|
|
|
// Step 2: Copy data (except ip column) from network into network_back
|
|
// The unique constraint on hwaddr is satisfied by grouping results
|
|
// by this field where we chose to take only the most recent entry
|
|
SQL_bool(db, "INSERT INTO network_bck "\
|
|
"SELECT id, hwaddr, interface, name, firstSeen, "\
|
|
"lastQuery, numQueries, macVendor "\
|
|
"FROM network GROUP BY hwaddr HAVING max(lastQuery);");
|
|
|
|
// Step 3: Drop the network table, the unique index will be automatically dropped
|
|
SQL_bool(db, "DROP TABLE network;");
|
|
|
|
// Step 4: Rename network_bck table to network table as last step
|
|
SQL_bool(db, "ALTER TABLE network_bck RENAME TO network;");
|
|
|
|
// Update database version to 5
|
|
if(!db_set_FTL_property(db, DB_VERSION, 5))
|
|
{
|
|
log_warn("create_network_addresses_table(): Failed to update database version!");
|
|
return false;
|
|
}
|
|
|
|
// Finish transaction
|
|
SQL_bool(db, "COMMIT");
|
|
|
|
// Re-enable foreign key enforcement
|
|
SQL_bool(db, "PRAGMA foreign_keys=ON");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool create_network_addresses_with_names_table(sqlite3 *db)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return false;
|
|
|
|
// Disable foreign key enforcement for this transaction
|
|
// Otherwise, dropping the network table would not be allowed
|
|
SQL_bool(db, "PRAGMA foreign_keys=OFF");
|
|
|
|
// Begin new transaction
|
|
SQL_bool(db, "BEGIN TRANSACTION");
|
|
|
|
// Step 1: Create network_addresses table in the database
|
|
SQL_bool(db, "CREATE TABLE network_addresses_bck ( 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));");
|
|
|
|
// Step 2: Copy data from network_addresses into network_addresses_bck
|
|
// name and nameUpdated are NULL at this point
|
|
SQL_bool(db, "REPLACE INTO network_addresses_bck "
|
|
"(network_id,ip,lastSeen) "
|
|
"SELECT network_id,ip,lastSeen "
|
|
"FROM network_addresses;");
|
|
|
|
// Step 3: Drop the network_addresses table
|
|
SQL_bool(db, "DROP TABLE network_addresses;");
|
|
|
|
// Step 4: Drop the network_names table (if exists due to a previous v7 database update)
|
|
SQL_bool(db, "DROP TABLE IF EXISTS network_names;");
|
|
|
|
// Step 5: Rename network_addresses_bck table to network_addresses table as last step
|
|
SQL_bool(db, "ALTER TABLE network_addresses_bck RENAME TO network_addresses;");
|
|
|
|
// Remove name column from network table.
|
|
// As ALTER TABLE is severely limited, we have to do the column deletion manually.
|
|
// Step 1: We create a new table without the name column
|
|
SQL_bool(db, "CREATE TABLE network_bck ( 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);");
|
|
|
|
// Step 2: Copy data (except name column) from network into network_back
|
|
SQL_bool(db, "INSERT INTO network_bck "\
|
|
"SELECT id, hwaddr, interface, firstSeen, "\
|
|
"lastQuery, numQueries, macVendor "\
|
|
"FROM network;");
|
|
|
|
// Step 3: Drop the network table, the unique index will be automatically dropped
|
|
SQL_bool(db, "DROP TABLE network;");
|
|
|
|
// Step 4: Rename network_bck table to network table as last step
|
|
SQL_bool(db, "ALTER TABLE network_bck RENAME TO network;");
|
|
|
|
// Update database version to 8
|
|
if(!db_set_FTL_property(db, DB_VERSION, 8))
|
|
{
|
|
log_warn("create_network_addresses_with_names_table(): Failed to update database version!");
|
|
return false;
|
|
}
|
|
|
|
// Finish transaction
|
|
SQL_bool(db, "COMMIT");
|
|
|
|
// Re-enable foreign key enforcement
|
|
SQL_bool(db, "PRAGMA foreign_keys=ON");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool create_network_addresses_network_id_index(sqlite3 *db)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return false;
|
|
|
|
// Create index on network_id column in network_addresses table
|
|
SQL_bool(db, "CREATE INDEX IF NOT EXISTS network_addresses_network_id_index ON network_addresses (network_id);");
|
|
|
|
// Update database version to 8
|
|
if(!db_set_FTL_property(db, DB_VERSION, 20))
|
|
{
|
|
log_warn("create_network_addresses_with_names_table(): Failed to update database version!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Try to find device by recent usage of this IP address
|
|
static int find_device_by_recent_ip(sqlite3 *db, const char *ipaddr)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return -1;
|
|
|
|
const char *querystr = "SELECT network_id FROM network_addresses "
|
|
"WHERE ip = ?1 AND "
|
|
"lastSeen > (cast(strftime('%%s', 'now') as int)-86400) "
|
|
"ORDER BY lastSeen DESC LIMIT 1;";
|
|
|
|
// Perform SQL query
|
|
int network_id = db_query_int_str(db, querystr, ipaddr);
|
|
|
|
if(network_id == DB_FAILED)
|
|
{
|
|
// SQLite error
|
|
return -1;
|
|
}
|
|
else if(network_id == DB_NODATA)
|
|
{
|
|
// No result found
|
|
return -1;
|
|
}
|
|
|
|
log_debug(DEBUG_ARP, "APR: Identified device %s using most recently used IP address", ipaddr);
|
|
|
|
// Found network_id
|
|
return network_id;
|
|
}
|
|
|
|
// Try to find device by mock hardware address (generated from IP address)
|
|
static int find_device_by_mock_hwaddr(sqlite3 *db, const char *ipaddr)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return DB_FAILED;
|
|
|
|
const char *querystr = "SELECT id FROM network WHERE hwaddr = concat('ip-',?1)";
|
|
|
|
// Perform SQL query
|
|
return db_query_int_str(db, querystr, ipaddr);
|
|
}
|
|
|
|
// Try to find device by hardware address
|
|
static int find_device_by_hwaddr(sqlite3 *db, const char hwaddr[])
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return DB_FAILED;
|
|
|
|
const char *querystr = "SELECT id FROM network WHERE hwaddr = ?1 COLLATE NOCASE;";
|
|
|
|
// Perform SQL query
|
|
return db_query_int_str(db, querystr, hwaddr);
|
|
}
|
|
|
|
// Try to find device by RECENT mock hardware address (generated from IP address)
|
|
static int find_recent_device_by_mock_hwaddr(sqlite3 *db, const char *ipaddr)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return DB_FAILED;
|
|
|
|
const char *querystr = "SELECT id FROM network WHERE "
|
|
"hwaddr = concat('ip-',?1) AND "
|
|
"firstSeen > (cast(strftime('%%s', 'now') as int)-3600)";
|
|
|
|
// Perform SQL query
|
|
return db_query_int_str(db, querystr, ipaddr);
|
|
}
|
|
|
|
// Store hostname of device identified by dbID
|
|
static int update_netDB_name(sqlite3 *db, const char *ip, const char *name)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
// Skip if hostname is NULL or an empty string (= no result)
|
|
if(name == NULL || strlen(name) < 1)
|
|
return SQLITE_OK;
|
|
|
|
sqlite3_stmt *query_stmt = NULL;
|
|
const char querystr[] = "UPDATE network_addresses SET name = ?1, "
|
|
"nameUpdated = (cast(strftime('%s', 'now') as int)) "
|
|
"WHERE ip = ?2";
|
|
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &query_stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_name(%s, \"%s\") - SQL error prepare (%i): %s",
|
|
ip, name, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
log_debug(DEBUG_DATABASE, "dbquery: \"%s\" with arguments 1 = \"%s\" and 2 = \"%s\"",
|
|
querystr, name, ip);
|
|
|
|
|
|
// Bind name to prepared statement (1st argument)
|
|
// We can do this as name has dynamic scope that exceeds that of the binding.
|
|
if((rc = sqlite3_bind_text(query_stmt, 1, name, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_name(%s, \"%s\"): Failed to bind ip (error %d): %s",
|
|
ip, name, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
// Bind ip (unique key) to prepared statement (2nd argument)
|
|
// We can do this as name has dynamic scope that exceeds that of the binding.
|
|
if((rc = sqlite3_bind_text(query_stmt, 2, ip, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_name(%s, \"%s\"): Failed to bind name (error %d): %s",
|
|
ip, name, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
|
|
// Perform step
|
|
if ((rc = sqlite3_step(query_stmt)) != SQLITE_DONE)
|
|
{
|
|
log_err("update_netDB_name(%s, \"%s\"): Failed to step (error %d): %s",
|
|
ip, name, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
|
|
// Finalize statement
|
|
if ((rc = sqlite3_finalize(query_stmt)) != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_name(%s, \"%s\"): Failed to finalize (error %d): %s",
|
|
ip, name, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// Updates lastQuery. Only use new value if larger than zero.
|
|
// client->lastQuery may be zero if this client is only known
|
|
// from a database entry but has not been seen since then (skip in this case)
|
|
static int update_netDB_lastQuery(sqlite3 *db, const int network_id, const time_t lastQuery)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
// Return early if there is nothing to update
|
|
if(lastQuery < 1)
|
|
return SQLITE_OK;
|
|
|
|
const int ret = dbquery(db, "UPDATE network "\
|
|
"SET lastQuery = MAX(lastQuery, %lu) "\
|
|
"WHERE id = %i;",
|
|
(unsigned long)lastQuery, network_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// Update numQueries.
|
|
// Add queries seen since last update and reset counter afterwards
|
|
static int update_netDB_numQueries(sqlite3 *db, const int dbID, const int numQueries)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
// Return early if there is nothing to update
|
|
if(numQueries < 1)
|
|
return SQLITE_OK;
|
|
|
|
const int ret = dbquery(db, "UPDATE network "
|
|
"SET numQueries = numQueries + %i "
|
|
"WHERE id = %i;",
|
|
numQueries, dbID);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Add IP address record if it does not exist (INSERT). If it already exists,
|
|
// the UNIQUE(ip) trigger becomes active and the line is instead REPLACEd.
|
|
// We preserve a possibly existing IP -> host name association here
|
|
static int add_netDB_network_address(sqlite3 *db, const int network_id, const char *ip)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
// Return early if there is nothing to be done in here
|
|
if(ip == NULL || strlen(ip) == 0)
|
|
return SQLITE_OK;
|
|
|
|
sqlite3_stmt *query_stmt = NULL;
|
|
const char querystr[] = "INSERT OR REPLACE INTO network_addresses "
|
|
"(network_id,ip,lastSeen,name,nameUpdated) VALUES "
|
|
"(?1,?2,(cast(strftime('%s', 'now') as int)),"
|
|
"(SELECT name FROM network_addresses "
|
|
"WHERE ip = ?2),"
|
|
"(SELECT nameUpdated FROM network_addresses "
|
|
"WHERE ip = ?2));";
|
|
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &query_stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("add_netDB_network_address(%i, \"%s\") - SQL error prepare (%i): %s",
|
|
network_id, ip, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
log_debug(DEBUG_DATABASE, "dbquery: \"%s\" with arguments ?1 = %i and ?2 = \"%s\"",
|
|
querystr, network_id, ip);
|
|
|
|
// Bind network_id to prepared statement (1st argument)
|
|
if((rc = sqlite3_bind_int(query_stmt, 1, network_id)) != SQLITE_OK)
|
|
{
|
|
log_err("add_netDB_network_address(%i, \"%s\"): Failed to bind network_id (error %d): %s",
|
|
network_id, ip, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
// Bind ip to prepared statement (2nd argument)
|
|
if((rc = sqlite3_bind_text(query_stmt, 2, ip, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("add_netDB_network_address(%i, \"%s\"): Failed to bind name (error %d): %s",
|
|
network_id, ip, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
|
|
// Perform step
|
|
if ((rc = sqlite3_step(query_stmt)) != SQLITE_DONE)
|
|
{
|
|
log_err("add_netDB_network_address(%i, \"%s\"): Failed to step (error %d): %s",
|
|
network_id, ip, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
|
|
// Finalize statement
|
|
if ((rc = sqlite3_finalize(query_stmt)) != SQLITE_OK)
|
|
{
|
|
log_err("add_netDB_network_address(%i, \"%s\"): Failed to finalize (error %d): %s",
|
|
network_id, ip, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(query_stmt);
|
|
return rc;
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// Insert a new record into the network table
|
|
static int insert_netDB_device(sqlite3 *db, const char *hwaddr, const time_t firstSeen, const time_t lastQuery,
|
|
const unsigned int numQueriesARP, const char *macVendor)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
sqlite3_stmt *query_stmt = NULL;
|
|
const char querystr[] = "INSERT INTO network "\
|
|
"(hwaddr,interface,firstSeen,lastQuery,numQueries,macVendor) "\
|
|
"VALUES (?1,\'N/A\',?2,?3,?4,?5);";
|
|
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &query_stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\", %lu, %lu, %u, \"%s\") - SQL error prepare (%i): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
log_debug(DEBUG_DATABASE, "dbquery: \"%s\" with arguments ?1-?5 = (\"%s\", %lu, %lu, %u, \"%s\")",
|
|
querystr, hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor);
|
|
|
|
// Bind hwaddr to prepared statement (1st argument)
|
|
if((rc = sqlite3_bind_text(query_stmt, 1, hwaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\", %lu, %lu, %u, \"%s\"): Failed to bind hwaddr (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Bind firstSeen to prepared statement (2nd argument)
|
|
if((rc = sqlite3_bind_int(query_stmt, 2, firstSeen)) != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind firstSeen (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Bind lastQuery to prepared statement (3rd argument)
|
|
if((rc = sqlite3_bind_int(query_stmt, 3, lastQuery)) != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind lastQuery (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Bind numQueriesARP to prepared statement (4th argument)
|
|
if((rc = sqlite3_bind_int(query_stmt, 4, numQueriesARP)) != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind numQueriesARP (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Bind macVendor to prepared statement (5th argument) - the macVendor can be NULL here
|
|
if((rc = sqlite3_bind_text(query_stmt, 5, macVendor, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind macVendor (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Perform step
|
|
if ((rc = sqlite3_step(query_stmt)) != SQLITE_DONE)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to step (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Finalize statement
|
|
if ((rc = sqlite3_finalize(query_stmt)) != SQLITE_OK)
|
|
{
|
|
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to finalize (error %d): %s",
|
|
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// Convert mock-device into a real one by changing the hardware address (and possibly adding a vendor string)
|
|
static int unmock_netDB_device(sqlite3 *db, const char *hwaddr, const char *macVendor, const int dbID)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
sqlite3_stmt *query_stmt = NULL;
|
|
const char querystr[] = "UPDATE network SET "\
|
|
"hwaddr = ?1, macVendor=?2 WHERE id = ?3;";
|
|
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &query_stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("unmock_netDB_device(\"%s\", \"%s\", %i) - SQL error prepare (%i): %s",
|
|
hwaddr, macVendor, dbID, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
log_debug(DEBUG_DATABASE, "dbquery: \"%s\" with arguments ?1 = \"%s\", ?2 = \"%s\", ?3 = %i",
|
|
querystr, hwaddr, macVendor, dbID);
|
|
|
|
// Bind hwaddr to prepared statement (1st argument)
|
|
if((rc = sqlite3_bind_text(query_stmt, 1, hwaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("unmock_netDB_device(\"%s\", \"%s\", %i): Failed to bind hwaddr (error %d): %s",
|
|
hwaddr, macVendor, dbID, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Bind macVendor to prepared statement (2nd argument) - the macVendor can be NULL here
|
|
if((rc = sqlite3_bind_text(query_stmt, 2, macVendor, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("unmock_netDB_device(\"%s\", \"%s\", %i): Failed to bind macVendor (error %d): %s",
|
|
hwaddr, macVendor, dbID, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Bind now to prepared statement (3rd argument)
|
|
if((rc = sqlite3_bind_int(query_stmt, 3, dbID)) != SQLITE_OK)
|
|
{
|
|
log_err("unmock_netDB_device(\"%s\", \"%s\", %i): Failed to bind now (error %d): %s",
|
|
hwaddr, macVendor, dbID, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Perform step
|
|
if ((rc = sqlite3_step(query_stmt)) != SQLITE_DONE)
|
|
{
|
|
log_err("unmock_netDB_device(\"%s\", \"%s\", %i): Failed to step (error %d): %s",
|
|
hwaddr, macVendor, dbID, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Finalize statement
|
|
if ((rc = sqlite3_finalize(query_stmt)) != SQLITE_OK)
|
|
{
|
|
log_err("unmock_netDB_device(\"%s\", \"%s\", %i): Failed to finalize (error %d): %s",
|
|
hwaddr, macVendor, dbID, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// Update interface of device
|
|
static int update_netDB_interface(sqlite3 *db, const int network_id, const char *iface)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
// Return early if there is nothing to be done in here
|
|
if(iface == NULL || strlen(iface) == 0)
|
|
return SQLITE_OK;
|
|
|
|
sqlite3_stmt *query_stmt = NULL;
|
|
const char querystr[] = "UPDATE network SET interface = ?1 WHERE id = ?2";
|
|
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &query_stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_interface(%i, \"%s\") - SQL error prepare (%i): %s",
|
|
network_id, iface, rc, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
log_debug(DEBUG_DATABASE, "dbquery: \"%s\" with arguments ?1 = \"%s\" and ?2 = %i",
|
|
querystr, iface, network_id);
|
|
|
|
// Bind iface to prepared statement (1st argument)
|
|
if((rc = sqlite3_bind_text(query_stmt, 1, iface, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_interface(%i, \"%s\"): Failed to bind iface (error %d): %s",
|
|
network_id, iface, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
// Bind network_id to prepared statement (2nd argument)
|
|
if((rc = sqlite3_bind_int(query_stmt, 2, network_id)) != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_interface(%i, \"%s\"): Failed to bind name (error %d): %s",
|
|
network_id, iface, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Perform step
|
|
if ((rc = sqlite3_step(query_stmt)) != SQLITE_DONE)
|
|
{
|
|
log_err("update_netDB_interface(%i, \"%s\"): Failed to step (error %d): %s",
|
|
network_id, iface, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
// Finalize statement
|
|
if ((rc = sqlite3_finalize(query_stmt)) != SQLITE_OK)
|
|
{
|
|
log_err("update_netDB_interface(%i, \"%s\"): Failed to finalize (error %d): %s",
|
|
network_id, iface, rc, sqlite3_errstr(rc));
|
|
sqlite3_reset(query_stmt);
|
|
checkFTLDBrc(rc);
|
|
return rc;
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
// Loop over all clients known to FTL and ensure we add them all to the database
|
|
static bool add_FTL_clients_to_network_table(sqlite3 *db, const enum arp_status *client_status,
|
|
const unsigned int clients, const time_t now, unsigned int *additional_entries)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return false;
|
|
|
|
int rc = SQLITE_OK;
|
|
char hwaddr[128];
|
|
for(unsigned int clientID = 0; clientID < clients; clientID++)
|
|
{
|
|
// Check thread cancellation
|
|
if(killed)
|
|
break;
|
|
|
|
// Get client pointer
|
|
lock_shm();
|
|
clientsData *client = getClient(clientID, true);
|
|
if(client == NULL)
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Client %u returned NULL pointer", clientID);
|
|
unlock_shm();
|
|
continue;
|
|
}
|
|
|
|
// Silently skip alias-clients - they do not really exist
|
|
if(client->flags.aliasclient)
|
|
{
|
|
unlock_shm();
|
|
continue;
|
|
}
|
|
|
|
// Get hostname and IP address of this client
|
|
char *hostname, *ipaddr, *interface;
|
|
ipaddr = strdup(getstr(client->ippos));
|
|
hostname = strdup(getstr(client->namepos));
|
|
interface = strdup(getstr(client->ifacepos));
|
|
|
|
// Skip if already handled above (first check against clients_array_size as we might have added
|
|
// more clients to FTL's memory herein (those known only from the database))
|
|
if(client_status[clientID] != CLIENT_NOT_HANDLED)
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Client %s known through ARP/neigh cache",
|
|
ipaddr);
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
unlock_shm();
|
|
continue;
|
|
}
|
|
else
|
|
log_debug(DEBUG_ARP, "Network table: %s NOT known through ARP/neigh cache", ipaddr);
|
|
|
|
//
|
|
// Variant 1: Try to find a device with an EDNS(0)-provided hardware address
|
|
//
|
|
int dbID = DB_NODATA;
|
|
if(client->hwlen == 6)
|
|
{
|
|
snprintf(hwaddr, sizeof(hwaddr), "%02X:%02X:%02X:%02X:%02X:%02X",
|
|
client->hwaddr[0], client->hwaddr[1],
|
|
client->hwaddr[2], client->hwaddr[3],
|
|
client->hwaddr[4], client->hwaddr[5]);
|
|
hwaddr[6*2+5] = '\0';
|
|
|
|
unlock_shm();
|
|
dbID = find_device_by_hwaddr(db, hwaddr);
|
|
lock_shm();
|
|
|
|
// Reacquire client pointer (if may have changed when unlocking above)
|
|
client = getClient(clientID, true);
|
|
|
|
if(dbID >= 0)
|
|
log_debug(DEBUG_ARP, "Network table: Client with MAC %s is network ID %i", hwaddr, dbID);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Variant 2: Try to find a device using the same IP address within the last 24 hours
|
|
// Only try this when there is no EDNS(0) MAC address available
|
|
//
|
|
unlock_shm();
|
|
dbID = find_device_by_recent_ip(db, ipaddr);
|
|
lock_shm();
|
|
|
|
// Reacquire client pointer (if may have changed when unlocking above)
|
|
client = getClient(clientID, true);
|
|
|
|
if(dbID >= 0)
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Client with IP %s has no MAC info but was recently be seen for network ID %i",
|
|
ipaddr, dbID);
|
|
}
|
|
|
|
//
|
|
// Variant 3: Try to find a device with mock IP address
|
|
// Only try this when there is no EDNS(0) MAC address available
|
|
//
|
|
if(dbID < 0)
|
|
{
|
|
unlock_shm();
|
|
dbID = find_device_by_mock_hwaddr(db, ipaddr);
|
|
lock_shm();
|
|
|
|
// Reacquire client pointer (if may have changed when unlocking above)
|
|
client = getClient(clientID, true);
|
|
|
|
if(dbID >= 0)
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Client with IP %s has no MAC info but is known as mock-hwaddr client with network ID %i",
|
|
ipaddr, dbID);
|
|
}
|
|
}
|
|
|
|
// Create mock hardware address in the style of "ip-<IP address>", like "ip-127.0.0.1"
|
|
strcpy(hwaddr, "ip-");
|
|
strncpy(hwaddr+3, ipaddr, sizeof(hwaddr)-4);
|
|
hwaddr[sizeof(hwaddr)-1] = '\0';
|
|
}
|
|
|
|
if(dbID == DB_FAILED)
|
|
{
|
|
// SQLite error
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
break;
|
|
}
|
|
|
|
// Device not in database, add new entry
|
|
else if(dbID == DB_NODATA)
|
|
{
|
|
char *macVendor = NULL;
|
|
if(client->hwlen == 6)
|
|
{
|
|
// Normal client, MAC was likely obtained from EDNS(0) data
|
|
unlock_shm();
|
|
macVendor = getMACVendor(hwaddr);
|
|
lock_shm();
|
|
|
|
// Reacquire client pointer (if may have changed when unlocking above)
|
|
client = getClient(clientID, true);
|
|
}
|
|
|
|
log_debug(DEBUG_ARP, "Network table: Creating new FTL device MAC = %s, IP = %s, hostname = \"%s\", vendor = \"%s\", interface = \"%s\"",
|
|
hwaddr, ipaddr, hostname, macVendor, interface);
|
|
|
|
// Add new device to database
|
|
const time_t lastQuery = client->lastQuery;
|
|
const time_t firstSeen = client->firstSeen;
|
|
const unsigned int numQueries = client->count;
|
|
unlock_shm();
|
|
insert_netDB_device(db, hwaddr, firstSeen, lastQuery, numQueries, macVendor);
|
|
lock_shm();
|
|
|
|
// Reacquire client pointer (if may have changed when unlocking above)
|
|
client = getClient(clientID, true);
|
|
|
|
// Reset client counter
|
|
client->numQueriesARP = 0;
|
|
|
|
// Free allocated memory (if allocated)
|
|
if(macVendor != NULL)
|
|
{
|
|
free(macVendor);
|
|
macVendor = NULL;
|
|
}
|
|
|
|
// Obtain ID which was given to this new entry
|
|
dbID = sqlite3_last_insert_rowid(db);
|
|
}
|
|
else // Device already in database
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Updating existing FTL device MAC = %s, IP = %s, hostname = \"%s\", interface = \"%s\"",
|
|
hwaddr, ipaddr, hostname, interface);
|
|
|
|
// Update timestamp of last query if applicable
|
|
const time_t lastQuery = client->lastQuery;
|
|
const unsigned int numQueriesARP = client->numQueriesARP;
|
|
unlock_shm();
|
|
rc = update_netDB_lastQuery(db, dbID, lastQuery);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
break;
|
|
}
|
|
|
|
// Update number of queries if applicable
|
|
rc = update_netDB_numQueries(db, dbID, numQueriesARP);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
break;
|
|
}
|
|
|
|
lock_shm();
|
|
// Reacquire client pointer (if may have changed when unlocking above)
|
|
client = getClient(clientID, true);
|
|
client->numQueriesARP = 0;
|
|
}
|
|
|
|
unlock_shm();
|
|
|
|
// Add unique IP address / mock-MAC pair to network_addresses table
|
|
// ipaddr is a local copy
|
|
rc = add_netDB_network_address(db, dbID, ipaddr);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
break;
|
|
}
|
|
|
|
// Update hostname if available
|
|
// hostname is a local copy
|
|
rc = update_netDB_name(db, ipaddr, hostname);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
break;
|
|
}
|
|
|
|
// Update interface if available
|
|
// interface is a local copy
|
|
rc = update_netDB_interface(db, dbID, interface);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
if(ipaddr) free(ipaddr);
|
|
if(hostname) free(hostname);
|
|
if(interface) free(interface);
|
|
break;
|
|
}
|
|
|
|
// Add to number of processed ARP cache entries
|
|
(*additional_entries)++;
|
|
|
|
// Free allocated memory
|
|
free(ipaddr);
|
|
free(hostname);
|
|
free(interface);
|
|
}
|
|
|
|
// Check for possible error in loop
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
const char *text;
|
|
if( rc == SQLITE_BUSY )
|
|
text = "WARNING";
|
|
else
|
|
text = "ERROR";
|
|
|
|
log_err("%s: Storing devices in network table failed: %s", text, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool add_local_interfaces_to_network_table(sqlite3 *db, time_t now, unsigned int *additional_entries)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return SQLITE_ERROR;
|
|
|
|
// Try to access the kernel's Internet protocol address management
|
|
FILE *ip_pipe = NULL;
|
|
const char cmd[] = "ip address show";
|
|
errno = ENOMEM;
|
|
if((ip_pipe = popen(cmd, "r")) == NULL)
|
|
{
|
|
log_warn("Command \"%s\" failed: %s", cmd, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
// Buffers
|
|
char *linebuffer = NULL;
|
|
size_t linebuffersize = 0u;
|
|
int iface_no, rc;
|
|
bool has_iface = false, has_hwaddr = false;
|
|
char ipaddr[128], hwaddr[128], iface[128];
|
|
|
|
// Read response line by line
|
|
while(getline(&linebuffer, &linebuffersize, ip_pipe) != -1)
|
|
{
|
|
// Skip if line buffer is invalid
|
|
if(linebuffer == NULL)
|
|
continue;
|
|
|
|
if(sscanf(linebuffer, "%i: %99[^:]", &iface_no, iface) == 2)
|
|
{
|
|
// Obtained an interface, continue to the next line
|
|
has_iface = true;
|
|
has_hwaddr = false;
|
|
iface[sizeof(iface)-1] = '\0';
|
|
continue;
|
|
}
|
|
|
|
// Do not try to read IP addresses when the information above is incomplete
|
|
if(!has_iface)
|
|
continue;
|
|
|
|
// Try to read hardware address
|
|
// We skip lines with "link/none" (virtual, e.g., wireguard interfaces)
|
|
if(sscanf(linebuffer, " link/ether %99s", hwaddr) == 1)
|
|
{
|
|
// Obtained an Ethernet hardware address, continue to the next line
|
|
has_hwaddr = true;
|
|
hwaddr[sizeof(hwaddr)-1] = '\0';
|
|
continue;
|
|
}
|
|
else if(sscanf(linebuffer, " link/loopback %99s", hwaddr) == 1)
|
|
{
|
|
// Obtained a loopback hardware address, continue to the next line
|
|
has_hwaddr = true;
|
|
hwaddr[sizeof(hwaddr)-1] = '\0';
|
|
continue;
|
|
}
|
|
|
|
// Do not try to read IP addresses when the information above is incomplete
|
|
if(!has_hwaddr)
|
|
continue;
|
|
|
|
// Try to read IPv4 address
|
|
// We need a special rule here to avoid "inet6 ..." being accepted as IPv4 address
|
|
if(sscanf(linebuffer, " inet%*[ ]%127[0-9.] brd", ipaddr) == 1)
|
|
{
|
|
// Obtained an IPv4 address
|
|
ipaddr[sizeof(ipaddr)-1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
// Try to read IPv6 address
|
|
if(sscanf(linebuffer, " inet6%*[ ]%127[0-9a-fA-F:] scope", ipaddr) == 1)
|
|
{
|
|
// Obtained an IPv6 address
|
|
ipaddr[sizeof(ipaddr)-1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
// No address data, continue to next line
|
|
continue;
|
|
}
|
|
}
|
|
|
|
log_debug(DEBUG_ARP, "Network table: read interface details for interface %s (%s) with address %s",
|
|
iface, hwaddr, ipaddr);
|
|
|
|
// Try to find the device we parsed above
|
|
int dbID = find_device_by_hwaddr(db, hwaddr);
|
|
if(dbID >= 0)
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table (ip a): Client with MAC %s was recently be seen for network ID %i",
|
|
hwaddr, dbID);
|
|
}
|
|
|
|
// Break on SQLite error
|
|
if(dbID == DB_FAILED)
|
|
{
|
|
// SQLite error
|
|
break;
|
|
}
|
|
|
|
// Get vendor
|
|
char *macVendor = getMACVendor(hwaddr);
|
|
|
|
// Device not in database, add new entry
|
|
if(dbID == DB_NODATA)
|
|
{
|
|
|
|
log_debug(DEBUG_ARP, "Network table: Creating new ip a device MAC = %s, IP = %s, vendor = \"%s\", interface = \"%s\"",
|
|
hwaddr, ipaddr, macVendor, iface);
|
|
|
|
// Try to import query data from a possibly previously existing mock-device
|
|
int mockID = find_device_by_mock_hwaddr(db, ipaddr);
|
|
int lastQuery = 0, firstSeen = now, numQueries = 0;
|
|
if(mockID >= 0)
|
|
{
|
|
lastQuery = db_query_int_int(db, "SELECT lastQuery from network where id = ?1", mockID);
|
|
firstSeen = db_query_int_int(db, "SELECT firstSeen from network where id = ?1", mockID);
|
|
numQueries = db_query_int_int(db, "SELECT numQueries from network where id = ?1", mockID);
|
|
}
|
|
|
|
// Add new device to database
|
|
insert_netDB_device(db, hwaddr, firstSeen, lastQuery, numQueries, macVendor);
|
|
|
|
// Obtain ID which was given to this new entry
|
|
dbID = sqlite3_last_insert_rowid(db);
|
|
}
|
|
else // Device already in database
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Updating existing ip a device MAC = %s, IP = %s, interface = \"%s\"",
|
|
hwaddr, ipaddr, iface);
|
|
}
|
|
|
|
//Free allocated memory
|
|
if(macVendor != NULL)
|
|
{
|
|
free(macVendor);
|
|
macVendor = NULL;
|
|
}
|
|
|
|
// Add unique IP address / mock-MAC pair to network_addresses table
|
|
rc = add_netDB_network_address(db, dbID, ipaddr);
|
|
if(rc != SQLITE_OK)
|
|
break;
|
|
|
|
// Update interface if available
|
|
rc = update_netDB_interface(db, dbID, iface);
|
|
if(rc != SQLITE_OK)
|
|
break;
|
|
|
|
// Add to number of processed ARP cache entries
|
|
(*additional_entries)++;
|
|
}
|
|
|
|
// Close pipe handle and free allocated memory
|
|
pclose(ip_pipe);
|
|
if(linebuffer != NULL)
|
|
free(linebuffer);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool clean_network_table(sqlite3* db)
|
|
{
|
|
// Do not clean if disabled
|
|
if(config.database.network.expire.v.ui == 0)
|
|
return true;
|
|
|
|
// Remove all but the most recent IP addresses not seen for more than a certain time
|
|
const time_t limit = time(NULL)-24*3600*config.database.network.expire.v.ui;
|
|
int rc = dbquery(db, "DELETE FROM network_addresses "
|
|
"WHERE lastSeen < %lu;", (unsigned long)limit);
|
|
if(rc != SQLITE_OK)
|
|
return false;
|
|
|
|
rc = dbquery(db, "UPDATE network_addresses SET name = NULL "
|
|
"WHERE nameUpdated < %lu;", (unsigned long)limit);
|
|
if(rc != SQLITE_OK)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool flush_network_table(void)
|
|
{
|
|
sqlite3 *db = dbopen(false, false);
|
|
if(db == NULL)
|
|
return false;
|
|
|
|
// Remove all IP addresses
|
|
if(dbquery(db, "DELETE FROM network_addresses;") != SQLITE_OK)
|
|
return false;
|
|
|
|
// Remove all devices
|
|
if(dbquery(db, "DELETE FROM network;") != SQLITE_OK)
|
|
return false;
|
|
|
|
// Close database
|
|
dbclose(&db);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Parse kernel's neighbor cache
|
|
void parse_neighbor_cache(sqlite3* db)
|
|
{
|
|
// Prepare buffers
|
|
char *linebuffer = NULL;
|
|
size_t linebuffersize = 0u;
|
|
unsigned int entries = 0u, additional_entries = 0u;
|
|
const time_t now = time(NULL);
|
|
|
|
// Start ARP timer
|
|
if(config.debug.arp.v.b)
|
|
timer_start(ARP_TIMER);
|
|
|
|
// Start transaction to speed up database queries, to avoid that the
|
|
// database is locked by other processes and to allow for a rollback in
|
|
// case of an error
|
|
const char sql[] = "BEGIN TRANSACTION IMMEDIATE";
|
|
int rc = dbquery(db, sql);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
const char *text;
|
|
if( rc == SQLITE_BUSY )
|
|
text = "WARNING";
|
|
else
|
|
text = "ERROR";
|
|
|
|
// dbquery() above already logs the reason for why the query failed
|
|
log_warn("%s: Storing devices in network table (\"%s\") failed", text, sql);
|
|
return;
|
|
}
|
|
|
|
// Delete old entries from network table
|
|
if(!clean_network_table(db))
|
|
return;
|
|
|
|
// Initialize array of status for individual clients used to
|
|
// remember the status of a client already seen in the neigh cache
|
|
lock_shm();
|
|
const int clients = counters->clients;
|
|
unlock_shm();
|
|
enum arp_status *client_status = calloc(clients, sizeof(enum arp_status));
|
|
for(int i = 0; i < clients; i++)
|
|
client_status[i] = CLIENT_NOT_HANDLED;
|
|
|
|
// Try to access the kernel's neighbor cache
|
|
if (config.database.network.parseARPcache.v.b)
|
|
{
|
|
// Parse ARP cache and add new entries to network table
|
|
FILE *arpfp = NULL;
|
|
const char cmd[] = "ip neigh show";
|
|
errno = ENOMEM;
|
|
if((arpfp = popen(cmd, "r")) == NULL)
|
|
{
|
|
log_warn("Command \"%s\" failed: %s", cmd, strerror(errno));
|
|
free(client_status);
|
|
return;
|
|
}
|
|
|
|
// Read ARP cache line by line
|
|
while(getline(&linebuffer, &linebuffersize, arpfp) != -1)
|
|
{
|
|
// Skip if line buffer is invalid
|
|
if(linebuffer == NULL)
|
|
continue;
|
|
|
|
// Check thread cancellation
|
|
if(killed)
|
|
break;
|
|
|
|
// Analyze line
|
|
char ip[128], hwaddr[128], iface[128];
|
|
int num = sscanf(linebuffer, "%99s dev %99s lladdr %99s",
|
|
ip, iface, hwaddr);
|
|
|
|
// Ensure strings are null-terminated in case we hit the max.
|
|
// length limitation
|
|
ip[sizeof(ip)-1] = '\0';
|
|
iface[sizeof(iface)-1] = '\0';
|
|
hwaddr[sizeof(hwaddr)-1] = '\0';
|
|
|
|
// Check if we want to process the line we just read
|
|
if(num != 3)
|
|
{
|
|
if(num == 2)
|
|
{
|
|
// This line is incomplete, remember this to skip
|
|
// mock-device creation after ARP processing
|
|
// both false = do not create a new record if the client
|
|
// is unknown (only DNS requesting clients
|
|
// do this), the now value is ignored
|
|
lock_shm();
|
|
int clientID = findClientID(ip, false, false, 0.0);
|
|
unlock_shm();
|
|
if(clientID >= 0 && clientID < clients)
|
|
client_status[clientID] = CLIENT_ARP_INCOMPLETE;
|
|
}
|
|
|
|
// Skip to the next row in the neigh cache rather when
|
|
// marking as incomplete client
|
|
continue;
|
|
}
|
|
|
|
// Get ID of this device in our network database. If it cannot be
|
|
// found, then this is a new device. We only use the hardware address
|
|
// to uniquely identify clients and only use the first returned ID.
|
|
//
|
|
// Same MAC, two IPs: Non-deterministic (sequential) DHCP server, we
|
|
// update the IP address to the last seen one.
|
|
//
|
|
// We can run this SELECT inside the currently active transaction as
|
|
// only the changed to the database are collected for latter
|
|
// commitment. Read-only access such as this SELECT command will be
|
|
// executed immediately on the database.
|
|
int dbID = find_device_by_hwaddr(db, hwaddr);
|
|
|
|
if(dbID == DB_FAILED)
|
|
{
|
|
// Get SQLite error code and return early from loop
|
|
rc = sqlite3_errcode(db);
|
|
break;
|
|
}
|
|
|
|
// If we reach this point, we can check if this client
|
|
// is known to pihole-FTL
|
|
// both false = do not create a new record if the client
|
|
// is unknown (only DNS requesting clients
|
|
// do this), the now value is ignored
|
|
lock_shm();
|
|
int clientID = findClientID(ip, false, false, 0.0);
|
|
|
|
// Set default values for a new device, may be updated
|
|
// below if the client is known to pihole-FTL
|
|
char *hostname = NULL;
|
|
bool client_valid = false;
|
|
time_t lastQuery = 0;
|
|
time_t firstSeen = now;
|
|
unsigned int numQueries = 0, totalQueries = 0;
|
|
|
|
// This client is known (by its IP address) to pihole-FTL if
|
|
// findClientID() returned a non-negative index
|
|
if(clientID >= 0 && clientID < clients)
|
|
{
|
|
clientsData *client = getClient(clientID, true);
|
|
if(!client)
|
|
continue;
|
|
|
|
// Client is known to Pi-hole, update properties
|
|
// with their real values
|
|
client_valid = true;
|
|
hostname = strdup(getstr(client->namepos));
|
|
firstSeen = client->firstSeen;
|
|
lastQuery = client->lastQuery;
|
|
numQueries = client->numQueriesARP;
|
|
totalQueries = client->count;
|
|
client_status[clientID] = CLIENT_ARP_COMPLETE;
|
|
}
|
|
else
|
|
{
|
|
// Client is not known to Pi-hole, create a
|
|
// mock-device with the default values set above
|
|
// and an empty hostname
|
|
hostname = strdup("");
|
|
}
|
|
unlock_shm();
|
|
|
|
// Device not in database, add new entry
|
|
if(client_valid && dbID == DB_NODATA)
|
|
{
|
|
// Try to obtain vendor from MAC database
|
|
char *macVendor = getMACVendor(hwaddr);
|
|
|
|
// Check if we recently added a mock-device with the same IP address
|
|
// and the ARP entry just came a bit delayed (reported by at least one user)
|
|
dbID = find_recent_device_by_mock_hwaddr(db, ip);
|
|
|
|
// Exception for the case where the device is
|
|
// not yet in the database: Use total count of
|
|
// queries as the number of queries for the new
|
|
// device instead of the special ARP cache
|
|
// counter to add also the number of queries in
|
|
// the DNS history imported from the long-term
|
|
// database
|
|
numQueries = totalQueries;
|
|
|
|
if(dbID == DB_NODATA)
|
|
{
|
|
// Device not known AND no recent mock-device found ---> create new device record
|
|
log_debug(DEBUG_ARP, "Network table: Creating new ARP device MAC = %s, IP = %s, hostname = \"%s\", vendor = \"%s\"",
|
|
hwaddr, ip, hostname, macVendor);
|
|
|
|
// Create new record (INSERT)
|
|
insert_netDB_device(db, hwaddr, firstSeen, lastQuery, numQueries, macVendor);
|
|
|
|
lock_shm();
|
|
clientsData *client = getClient(clientID, true);
|
|
if(client != NULL)
|
|
{
|
|
// Reset client ARP counter (we stored the entry in the database)
|
|
client->numQueriesARP = 0;
|
|
}
|
|
unlock_shm();
|
|
|
|
// Obtain ID which was given to this new entry
|
|
dbID = sqlite3_last_insert_rowid(db);
|
|
|
|
// Store hostname in the appropriate network_address record (if available)
|
|
if(strlen(hostname) > 0)
|
|
{
|
|
rc = update_netDB_name(db, ip, hostname);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
// Free allocated memory
|
|
free(hostname);
|
|
free(macVendor);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Device is ALREADY KNOWN ---> convert mock-device to a "real" one
|
|
log_debug(DEBUG_ARP, "Network table: Un-mocking ARP device MAC = %s, IP = %s, hostname = \"%s\", vendor = \"%s\"",
|
|
hwaddr, ip, hostname, macVendor);
|
|
|
|
// Update/replace important device properties
|
|
unmock_netDB_device(db, hwaddr, macVendor, dbID);
|
|
|
|
// Host name, count and last query timestamp will be set in the next
|
|
// loop iteration for the sake of simplicity
|
|
}
|
|
|
|
// Free allocated memory
|
|
free(macVendor);
|
|
}
|
|
// Device in database AND client known to Pi-hole
|
|
else if(client_valid)
|
|
{
|
|
log_debug(DEBUG_ARP, "Network table: Updating existing ARP device MAC = %s, IP = %s, hostname = \"%s\"",
|
|
hwaddr, ip, hostname);
|
|
|
|
// Update timestamp of last query if applicable
|
|
rc = update_netDB_lastQuery(db, dbID, lastQuery);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
// Free allocated memory
|
|
free(hostname);
|
|
break;
|
|
}
|
|
|
|
// Update number of queries if applicable
|
|
rc = update_netDB_numQueries(db, dbID, numQueries);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
// Free allocated memory
|
|
free(hostname);
|
|
break;
|
|
}
|
|
|
|
lock_shm();
|
|
// Acquire client pointer
|
|
clientsData *client = getClient(clientID, true);
|
|
if(client != NULL)
|
|
{
|
|
// Reset client ARP counter (we stored the entry in the database)
|
|
client->numQueriesARP = 0;
|
|
}
|
|
unlock_shm();
|
|
|
|
// Update hostname if available
|
|
rc = update_netDB_name(db, ip, hostname);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
// Free allocated memory
|
|
free(hostname);
|
|
break;
|
|
}
|
|
}
|
|
// else: Device in database but not known to Pi-hole
|
|
|
|
free(hostname);
|
|
hostname = NULL;
|
|
|
|
// Store interface if available
|
|
rc = update_netDB_interface(db, dbID, iface);
|
|
if(rc != SQLITE_OK)
|
|
break;
|
|
|
|
// Add unique IP address / mock-MAC pair to network_addresses table
|
|
rc = add_netDB_network_address(db, dbID, ip);
|
|
if(rc != SQLITE_OK)
|
|
break;
|
|
|
|
// Count number of processed ARP cache entries
|
|
entries++;
|
|
}
|
|
|
|
// Close pipe handle and free allocated memory
|
|
pclose(arpfp);
|
|
if(linebuffer != NULL)
|
|
free(linebuffer);
|
|
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("Database error in ARP cache processing loop");
|
|
free(client_status);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check thread cancellation
|
|
if(killed)
|
|
{
|
|
free(client_status);
|
|
return;
|
|
}
|
|
|
|
// Loop over all clients known to FTL and ensure we add them all to the
|
|
// database
|
|
if(!add_FTL_clients_to_network_table(db, client_status, clients, now, &additional_entries))
|
|
{
|
|
free(client_status);
|
|
return;
|
|
}
|
|
|
|
free(client_status);
|
|
client_status = NULL;
|
|
|
|
// Check thread cancellation
|
|
if(killed)
|
|
return;
|
|
|
|
// Finally, loop over the available interfaces to ensure we list the
|
|
// IP addresses correctly (local addresses are NOT contained in the
|
|
// ARP/neighbor cache).
|
|
if(!add_local_interfaces_to_network_table(db, now, &additional_entries))
|
|
return;
|
|
|
|
// Check thread cancellation
|
|
if(killed)
|
|
return;
|
|
|
|
// Ensure mock-devices which are not assigned to any addresses any more
|
|
// (they have been converted to "real" devices), are removed at this point
|
|
rc = dbquery(db, "DELETE FROM network WHERE id NOT IN "
|
|
"(SELECT network_id from network_addresses) "
|
|
"AND hwaddr LIKE 'ip-%%';");
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("Database error in mock-device cleaning statement");
|
|
checkFTLDBrc(rc);
|
|
return;
|
|
}
|
|
|
|
// Actually update the database
|
|
if((rc = dbquery(db, "END TRANSACTION")) != SQLITE_OK) {
|
|
const char *text;
|
|
if( rc == SQLITE_BUSY )
|
|
text = "WARNING";
|
|
else
|
|
text = "ERROR";
|
|
|
|
log_err("%s: Storing devices in network table failed: %s", text, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return;
|
|
}
|
|
|
|
// Debug logging
|
|
log_debug(DEBUG_ARP, "ARP table processing (%u entries from ARP, %u from FTL's cache) took %.1f ms",
|
|
entries, additional_entries, timer_elapsed_msec(ARP_TIMER));
|
|
}
|
|
|
|
// Loop over all entries in network table and unify entries by their hwaddr
|
|
// If we find duplicates, we keep the most recent entry, while
|
|
// - we replace the first-seen date by the earliest across all rows
|
|
// - we sum up the number of queries of all clients with the same hwaddr
|
|
bool unify_hwaddr(sqlite3 *db)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return false;
|
|
|
|
// We request sets of (id,hwaddr). They are GROUPed BY hwaddr to make
|
|
// the set unique in hwaddr.
|
|
// The grouping is constrained by the HAVING clause which is
|
|
// evaluated once across all rows of a group to ensure the returned
|
|
// set represents the most recent entry for a given hwaddr
|
|
// Get only duplicated hwaddrs here (HAVING cnt > 1).
|
|
const char querystr[] = "SELECT id,hwaddr,COUNT(*) cnt "
|
|
"FROM network "
|
|
"GROUP BY hwaddr "
|
|
"HAVING MAX(lastQuery) "
|
|
"AND cnt > 1;";
|
|
|
|
// Start transaction
|
|
SQL_bool(db, "BEGIN TRANSACTION");
|
|
|
|
// Perform SQL query
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("unify_hwaddr(\"%s\") - SQL error prepare: %s", querystr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return false;
|
|
}
|
|
|
|
// Loop until no further (id,hwaddr) sets are available
|
|
while((rc = sqlite3_step(stmt)) != SQLITE_DONE)
|
|
{
|
|
// Check if we ran into an error
|
|
if(rc != SQLITE_ROW)
|
|
{
|
|
log_err("unify_hwaddr(\"%s\") - SQL error step: %s", querystr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return false;
|
|
}
|
|
|
|
// Obtain id and hwaddr of the most recent entry for this particular client
|
|
const int id = sqlite3_column_int(stmt, 0);
|
|
char *hwaddr = strdup((char*)sqlite3_column_text(stmt, 1));
|
|
|
|
// Reset statement
|
|
sqlite3_reset(stmt);
|
|
|
|
// Update firstSeen with lowest value across all rows with the same hwaddr
|
|
dbquery(db, "UPDATE network "\
|
|
"SET firstSeen = (SELECT MIN(firstSeen) FROM network WHERE hwaddr = \'%s\' COLLATE NOCASE) "\
|
|
"WHERE id = %i;", hwaddr, id);
|
|
|
|
// Update numQueries with sum of all rows with the same hwaddr
|
|
dbquery(db, "UPDATE network "\
|
|
"SET numQueries = (SELECT SUM(numQueries) FROM network WHERE hwaddr = \'%s\' COLLATE NOCASE) "\
|
|
"WHERE id = %i;", hwaddr, id);
|
|
|
|
// Remove all other lines with the same hwaddr but a different id
|
|
dbquery(db, "DELETE FROM network "\
|
|
"WHERE hwaddr = \'%s\' COLLATE NOCASE "\
|
|
"AND id != %i;", hwaddr, id);
|
|
|
|
free(hwaddr);
|
|
}
|
|
|
|
// Finalize statement
|
|
sqlite3_finalize(stmt);
|
|
|
|
// Update database version to 4
|
|
if(!db_set_FTL_property(db, DB_VERSION, 4))
|
|
return false;
|
|
|
|
// End transaction
|
|
SQL_bool(db, "COMMIT");
|
|
|
|
return true;
|
|
}
|
|
|
|
static char * __attribute__ ((malloc)) getMACVendor(const char *hwaddr)
|
|
{
|
|
// Special handling for the loopback interface
|
|
if(strcmp(hwaddr, "00:00:00:00:00:00") == 0)
|
|
return strdup("virtual interface");
|
|
|
|
struct stat st;
|
|
if(stat(config.files.macvendor.v.s, &st) != 0)
|
|
{
|
|
// File does not exist
|
|
log_debug(DEBUG_ARP, "getMACVenor(\"%s\"): %s does not exist", hwaddr, config.files.macvendor.v.s);
|
|
return strdup("");
|
|
}
|
|
else if(strlen(hwaddr) != 17 || strstr(hwaddr, "ip-") != NULL)
|
|
{
|
|
// MAC address is incomplete or mock address (for distant clients)
|
|
log_debug(DEBUG_ARP, "getMACVenor(\"%s\"): MAC invalid (length %zu)", hwaddr, strlen(hwaddr));
|
|
return strdup("");
|
|
}
|
|
|
|
sqlite3 *macvendor_db = NULL;
|
|
int rc = sqlite3_open_v2(config.files.macvendor.v.s, &macvendor_db, SQLITE_OPEN_READONLY, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getMACVendor(\"%s\") - SQL error: %s", hwaddr, sqlite3_errstr(rc));
|
|
sqlite3_close(macvendor_db);
|
|
return strdup("");
|
|
}
|
|
|
|
// Only keep "XX:YY:ZZ" (8 characters)
|
|
char hwaddrshort[9];
|
|
strncpy(hwaddrshort, hwaddr, 8);
|
|
hwaddrshort[8] = '\0';
|
|
const char querystr[] = "SELECT vendor FROM macvendor WHERE mac LIKE ?;";
|
|
|
|
sqlite3_stmt *stmt = NULL;
|
|
rc = sqlite3_prepare_v2(macvendor_db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getMACVendor(\"%s\") - SQL error prepare \"%s\": %s", hwaddr, querystr, sqlite3_errstr(rc));
|
|
sqlite3_close(macvendor_db);
|
|
return strdup("");
|
|
}
|
|
|
|
// Bind hwaddrshort to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt, 1, hwaddrshort, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("getMACVendor(\"%s\" -> \"%s\"): Failed to bind hwaddrshort: %s",
|
|
hwaddr, hwaddrshort, sqlite3_errstr(rc));
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(macvendor_db);
|
|
return strdup("");
|
|
}
|
|
|
|
char *vendor = NULL;
|
|
rc = sqlite3_step(stmt);
|
|
if(rc == SQLITE_ROW)
|
|
{
|
|
vendor = strdup((char*)sqlite3_column_text(stmt, 0));
|
|
}
|
|
else
|
|
{
|
|
// Not found
|
|
vendor = strdup("");
|
|
}
|
|
|
|
if(rc != SQLITE_DONE && rc != SQLITE_ROW)
|
|
{
|
|
// Error
|
|
log_err("getMACVendor(\"%s\") - SQL error step: %s", hwaddr, sqlite3_errstr(rc));
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(macvendor_db);
|
|
|
|
log_debug(DEBUG_ARP, "DEBUG: MAC Vendor lookup for %s returned \"%s\"", hwaddr, vendor);
|
|
|
|
return vendor;
|
|
}
|
|
|
|
void updateMACVendorRecords(sqlite3 *db)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return;
|
|
|
|
struct stat st;
|
|
if(stat(config.files.macvendor.v.s, &st) != 0)
|
|
{
|
|
// File does not exist
|
|
log_debug(DEBUG_ARP, "updateMACVendorRecords(): \"%s\" does not exist", config.files.macvendor.v.s);
|
|
return;
|
|
}
|
|
|
|
sqlite3_stmt *stmt = NULL;
|
|
const char *selectstr = "SELECT id,hwaddr FROM network;";
|
|
int rc = sqlite3_prepare_v2(db, selectstr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("updateMACVendorRecords() - SQL error prepare \"%s\": %s", selectstr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return;
|
|
}
|
|
|
|
while((rc = sqlite3_step(stmt)) == SQLITE_ROW)
|
|
{
|
|
const int id = sqlite3_column_int(stmt, 0);
|
|
char *hwaddr = strdup((char*)sqlite3_column_text(stmt, 1));
|
|
|
|
// Get vendor for MAC
|
|
char *vendor = getMACVendor(hwaddr);
|
|
|
|
// Free allocated memory
|
|
free(hwaddr);
|
|
hwaddr = NULL;
|
|
|
|
// Prepare statement
|
|
sqlite3_stmt *stmt2 = NULL;
|
|
const char *updatestr = "UPDATE network SET macVendor = ?1 WHERE id = ?2";
|
|
rc = sqlite3_prepare_v2(db, updatestr, -1, &stmt2, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("updateMACVendorRecords() - SQL error prepare \"%s\": %s", updatestr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
free(vendor);
|
|
break;
|
|
}
|
|
|
|
// Bind vendor to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt2, 1, vendor, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("updateMACVendorRecords() - Failed to bind vendor: %s", sqlite3_errstr(rc));
|
|
sqlite3_reset(stmt2);
|
|
sqlite3_finalize(stmt2);
|
|
free(vendor);
|
|
break;
|
|
}
|
|
|
|
// Bind id to prepared statement
|
|
if((rc = sqlite3_bind_int(stmt2, 2, id)) != SQLITE_OK)
|
|
{
|
|
log_err("updateMACVendorRecords() - Failed to bind id: %s", sqlite3_errstr(rc));
|
|
sqlite3_reset(stmt2);
|
|
sqlite3_finalize(stmt2);
|
|
free(vendor);
|
|
break;
|
|
}
|
|
|
|
// Execute statement
|
|
rc = sqlite3_step(stmt2);
|
|
if(rc != SQLITE_DONE)
|
|
{
|
|
log_err("updateMACVendorRecords() - SQL error step: %s", sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(stmt2);
|
|
sqlite3_finalize(stmt2);
|
|
free(vendor);
|
|
break;
|
|
}
|
|
|
|
// Free allocated memory
|
|
free(vendor);
|
|
}
|
|
if(rc != SQLITE_DONE)
|
|
{
|
|
// Error
|
|
log_err("updateMACVendorRecords() - SQL error step: %s", sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
// Get hardware address of device identified by IP address
|
|
char *__attribute__((malloc)) getMACfromIP(sqlite3* db, const char *ipaddr)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return NULL;
|
|
|
|
// Open pihole-FTL.db database file if needed
|
|
bool db_opened = false;
|
|
if(db == NULL)
|
|
{
|
|
if((db = dbopen(false, false)) == NULL)
|
|
{
|
|
log_warn("getMACfromIP(\"%s\") - Failed to open DB", ipaddr);
|
|
return NULL;
|
|
}
|
|
|
|
// Successful
|
|
db_opened = true;
|
|
}
|
|
|
|
// Prepare SQLite statement
|
|
// We request the most recent IP entry in case there an IP appears
|
|
// multiple times in the network_addresses table
|
|
sqlite3_stmt *stmt = NULL;
|
|
const char *querystr = "SELECT hwaddr FROM network WHERE id = "
|
|
"(SELECT network_id FROM network_addresses "
|
|
"WHERE ip = ? GROUP BY ip HAVING max(lastSeen));";
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getMACfromIP(\"%s\") - SQL error prepare: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Bind ipaddr to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt, 1, ipaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_err("getMACfromIP(\"%s\"): Failed to bind ip: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char *hwaddr = NULL;
|
|
rc = sqlite3_step(stmt);
|
|
if(rc == SQLITE_ROW)
|
|
{
|
|
// Database record found (result might be empty)
|
|
hwaddr = strdup((char*)sqlite3_column_text(stmt, 0));
|
|
}
|
|
else if(rc == SQLITE_DONE)
|
|
{
|
|
// Not found
|
|
hwaddr = NULL;
|
|
}
|
|
else
|
|
{
|
|
log_err("getMACfromIP(\"%s\"): Failed step: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
return NULL;
|
|
}
|
|
|
|
if(hwaddr != NULL)
|
|
log_debug(DEBUG_DATABASE, "Found database hardware address %s -> %s", ipaddr, hwaddr);
|
|
|
|
// Finalize statement and close database handle
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return hwaddr;
|
|
}
|
|
|
|
// Get aliasclient ID of device identified by IP address (if available)
|
|
int getAliasclientIDfromIP(sqlite3 *db, const char *ipaddr)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return DB_FAILED;
|
|
|
|
// Open pihole-FTL.db database file if needed
|
|
bool db_opened = false;
|
|
if(db == NULL)
|
|
{
|
|
if((db = dbopen(false, false)) == NULL)
|
|
{
|
|
log_warn("getAliasclientIDfromIP(\"%s\") - Failed to open DB", ipaddr);
|
|
return DB_FAILED;
|
|
}
|
|
|
|
// Successful
|
|
db_opened = true;
|
|
}
|
|
|
|
// Prepare SQLite statement
|
|
// We request the most recent IP entry in case there an IP appears
|
|
// multiple times in the network_addresses table
|
|
sqlite3_stmt *stmt = NULL;
|
|
const char *querystr = "SELECT aliasclient_id FROM network WHERE id = "
|
|
"(SELECT network_id FROM network_addresses "
|
|
"WHERE ip = ? "
|
|
"AND aliasclient_id IS NOT NULL "
|
|
"GROUP BY ip HAVING max(lastSeen));";
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getAliasclientIDfromIP(\"%s\") - SQL error prepare: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return DB_FAILED;
|
|
}
|
|
|
|
// Bind ipaddr to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt, 1, ipaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_warn("getAliasclientIDfromIP(\"%s\"): Failed to bind ip: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return DB_FAILED;
|
|
}
|
|
|
|
int aliasclient_id = DB_NODATA;
|
|
rc = sqlite3_step(stmt);
|
|
if(rc == SQLITE_ROW)
|
|
{
|
|
// Database record found
|
|
aliasclient_id = sqlite3_column_int(stmt, 0);
|
|
}
|
|
else if(rc != SQLITE_DONE)
|
|
{
|
|
// Error, check for database corruption
|
|
checkFTLDBrc(rc);
|
|
return DB_FAILED;
|
|
}
|
|
|
|
log_debug(DEBUG_ALIASCLIENTS, " Aliasclient ID %s -> %i%s", ipaddr, aliasclient_id,
|
|
(aliasclient_id == DB_NODATA) ? " (NOT FOUND)" : "");
|
|
|
|
// Finalize statement and close database handle
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return aliasclient_id;
|
|
}
|
|
|
|
// Get host name of device identified by IP address
|
|
char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return NULL;
|
|
log_debug(DEBUG_RESOLVER, "Trying to obtain host name of \"%s\" from network_addresses table", ipaddr);
|
|
|
|
// Check if we want to resolve host names
|
|
if(!resolve_this_name(ipaddr))
|
|
{
|
|
log_debug(DEBUG_RESOLVER, "getNameFromIP(\"%s\") - configured to not resolve host name", ipaddr);
|
|
return NULL;
|
|
}
|
|
|
|
// Open pihole-FTL.db database file if needed
|
|
bool db_opened = false;
|
|
if(db == NULL)
|
|
{
|
|
if((db = dbopen(false, false)) == NULL)
|
|
{
|
|
log_warn("getNameFromIP(\"%s\") - Failed to open DB", ipaddr);
|
|
return NULL;
|
|
}
|
|
|
|
// Successful
|
|
db_opened = true;
|
|
}
|
|
|
|
// Check for a host name associated with the same IP address
|
|
sqlite3_stmt *stmt = NULL;
|
|
const char *querystr = "SELECT name FROM network_addresses WHERE name IS NOT NULL AND ip = ?;";
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getNameFromIP(\"%s\") - SQL error prepare: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Bind ipaddr to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt, 1, ipaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_warn("getNameFromIP(\"%s\"): Failed to bind ip: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
log_debug(DEBUG_RESOLVER, "Check for a host name associated with IP address %s", ipaddr);
|
|
|
|
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));
|
|
|
|
log_debug(DEBUG_RESOLVER, "Found database host name (same address) %s -> %s", ipaddr, name);
|
|
}
|
|
else if(rc != SQLITE_DONE)
|
|
{
|
|
// Error
|
|
checkFTLDBrc(rc);
|
|
return NULL;
|
|
}
|
|
|
|
// Finalize statement
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
// Return here if we found the name
|
|
if(name != NULL)
|
|
{
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return name;
|
|
}
|
|
|
|
log_debug(DEBUG_RESOLVER, " ---> not found");
|
|
|
|
// Nothing found for the exact IP address
|
|
// Check for a host name associated with the same device (but another IP address)
|
|
querystr = "SELECT name FROM network_addresses "
|
|
"WHERE name IS NOT NULL AND "
|
|
"network_id = (SELECT network_id FROM network_addresses "
|
|
"WHERE ip = ?) "
|
|
"ORDER BY lastSeen DESC LIMIT 1";
|
|
rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getNameFromIP(\"%s\") - SQL error prepare: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
if(db_opened) dbclose(&db);
|
|
return NULL;
|
|
}
|
|
|
|
// Bind ipaddr to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt, 1, ipaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_warn("getNameFromIP(\"%s\"): Failed to bind ip: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
log_debug(DEBUG_RESOLVER, "Checking for a host name associated with the same device (but another IP address)");
|
|
|
|
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 (same device) %s -> %s",
|
|
ipaddr, 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);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
log_debug(DEBUG_RESOLVER, "Check for a host name associated with MAC address %s", client);
|
|
|
|
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)
|
|
{
|
|
// Return early if database is known to be broken
|
|
if(FTLDBerror())
|
|
return NULL;
|
|
|
|
// Open pihole-FTL.db database file if needed
|
|
bool db_opened = false;
|
|
if(db == NULL)
|
|
{
|
|
if((db = dbopen(false, false)) == NULL)
|
|
{
|
|
log_warn("getIfaceFromIP(\"%s\") - Failed to open DB", ipaddr);
|
|
return NULL;
|
|
}
|
|
|
|
// Successful
|
|
db_opened = true;
|
|
}
|
|
|
|
// Prepare SQLite statement
|
|
sqlite3_stmt *stmt = NULL;
|
|
const char *querystr = "SELECT interface FROM network "
|
|
"JOIN network_addresses "
|
|
"ON network_addresses.network_id = network.id "
|
|
"WHERE network_addresses.ip = ? AND "
|
|
"interface != 'N/A' AND "
|
|
"interface IS NOT NULL;";
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if(rc != SQLITE_OK)
|
|
{
|
|
log_err("getIfaceFromIP(\"%s\") - SQL error prepare: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
if(db_opened) dbclose(&db);
|
|
return NULL;
|
|
}
|
|
|
|
log_debug(DEBUG_DATABASE, "getIfaceFromIP(): \"%s\" with ? = \"%s\"",
|
|
querystr, ipaddr);
|
|
|
|
// Bind ipaddr to prepared statement
|
|
if((rc = sqlite3_bind_text(stmt, 1, ipaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
|
{
|
|
log_warn("getIfaceFromIP(\"%s\"): Failed to bind ip: %s",
|
|
ipaddr, sqlite3_errstr(rc));
|
|
checkFTLDBrc(rc);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char *iface = NULL;
|
|
rc = sqlite3_step(stmt);
|
|
if(rc == SQLITE_ROW)
|
|
{
|
|
// Database record found (result might be empty)
|
|
iface = strdup((char*)sqlite3_column_text(stmt, 0));
|
|
}
|
|
else if(rc != SQLITE_DONE)
|
|
{
|
|
// Error
|
|
checkFTLDBrc(rc);
|
|
return NULL;
|
|
}
|
|
|
|
if(iface != NULL)
|
|
log_debug(DEBUG_DATABASE, "Found database interface %s -> %s", ipaddr, iface);
|
|
|
|
// Finalize statement and close database handle
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
|
|
if(db_opened) dbclose(&db);
|
|
|
|
return iface;
|
|
}
|
|
|
|
bool networkTable_readDevices(sqlite3 *db, sqlite3_stmt **read_stmt, const char **message)
|
|
{
|
|
// Prepare SQLite statement
|
|
const char *querystr = "SELECT id,hwaddr,interface,firstSeen,lastQuery,numQueries,macVendor FROM network ORDER BY lastQuery DESC;";
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, read_stmt, NULL);
|
|
if( rc != SQLITE_OK ){
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_readDevices() - SQL error prepare (%i): %s",
|
|
rc, *message);
|
|
dbclose(&db);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool networkTable_readDevicesGetRecord(sqlite3_stmt *read_stmt, network_record *network, const char **message)
|
|
{
|
|
// Perform step
|
|
const int rc = sqlite3_step(read_stmt);
|
|
|
|
// Valid row
|
|
if(rc == SQLITE_ROW)
|
|
{
|
|
network->id = sqlite3_column_int(read_stmt, 0);
|
|
network->hwaddr = (char*)sqlite3_column_text(read_stmt, 1);
|
|
network->iface = (char*)sqlite3_column_text(read_stmt, 2);
|
|
network->firstSeen = sqlite3_column_int(read_stmt, 3);
|
|
network->lastQuery = sqlite3_column_int(read_stmt, 4);
|
|
network->numQueries = sqlite3_column_int(read_stmt, 5);
|
|
network->macVendor = (char*)sqlite3_column_text(read_stmt, 6);
|
|
return true;
|
|
}
|
|
|
|
// Check for error. An error happened when the result is neither
|
|
// SQLITE_ROW (we returned earlier in this case), nor
|
|
// SQLITE_DONE (we are finished reading the table)
|
|
if(rc != SQLITE_DONE)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_readDevicesGetRecord() - SQL error step (%i): %s",
|
|
rc, *message);
|
|
return false;
|
|
}
|
|
|
|
// Finished reading, nothing to get here
|
|
return false;
|
|
}
|
|
|
|
// Finalize statement of a gravity database transaction
|
|
void networkTable_readDevicesFinalize(sqlite3_stmt *read_stmt)
|
|
{
|
|
// Finalize statement
|
|
sqlite3_finalize(read_stmt);
|
|
}
|
|
|
|
bool networkTable_readIPs(sqlite3 *db, sqlite3_stmt **read_stmt, const int id, const char **message)
|
|
{
|
|
// Prepare SQLite statement
|
|
const char *querystr = "SELECT ip,lastSeen,name,nameUpdated FROM network_addresses WHERE network_id = ? ORDER BY lastSeen DESC;";
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, read_stmt, NULL);
|
|
if( rc != SQLITE_OK ){
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_readIPs(%i) - SQL error prepare (%i): %s",
|
|
id, rc, *message);
|
|
return false;
|
|
}
|
|
|
|
// Bind ipaddr to prepared statement
|
|
if((rc = sqlite3_bind_int(*read_stmt, 1, id)) != SQLITE_OK)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_readIPs(%i): Failed to bind domain (error %d) - %s",
|
|
id, rc, *message);
|
|
sqlite3_reset(*read_stmt);
|
|
sqlite3_finalize(*read_stmt);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool networkTable_readIPsGetRecord(sqlite3_stmt *read_stmt, network_addresses_record *network_addresses, const char **message)
|
|
{
|
|
// Perform step
|
|
const int rc = sqlite3_step(read_stmt);
|
|
|
|
// Valid row
|
|
if(rc == SQLITE_ROW)
|
|
{
|
|
network_addresses->ip = (char*)sqlite3_column_text(read_stmt, 0);
|
|
network_addresses->lastSeen = sqlite3_column_int64(read_stmt, 1);
|
|
network_addresses->name = (char*)sqlite3_column_text(read_stmt, 2);
|
|
network_addresses->nameUpdated = sqlite3_column_int64(read_stmt, 1);
|
|
return true;
|
|
}
|
|
|
|
// Check for error. An error happened when the result is neither
|
|
// SQLITE_ROW (we returned earlier in this case), nor
|
|
// SQLITE_DONE (we are finished reading the table)
|
|
if(rc != SQLITE_DONE)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_readDevicesGetIP() - SQL error step (%i): %s",
|
|
rc, *message);
|
|
return false;
|
|
}
|
|
|
|
// Finished reading, nothing to get here
|
|
return false;
|
|
}
|
|
|
|
// Finalize statement of a gravity database transaction
|
|
void networkTable_readIPsFinalize(sqlite3_stmt *read_stmt)
|
|
{
|
|
// Finalize statement
|
|
sqlite3_finalize(read_stmt);
|
|
}
|
|
|
|
bool networkTable_deleteDevice(sqlite3 *db, const int id, int *deleted, const char **message)
|
|
{
|
|
// First step: Delete all associated IPs of this device
|
|
// Prepare SQLite statement
|
|
const char *querystr = "DELETE FROM network_addresses WHERE network_id = ?;";
|
|
sqlite3_stmt *stmt;
|
|
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if( rc != SQLITE_OK ){
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_deleteDevice(%i) - SQL error prepare (%i): %s",
|
|
id, rc, *message);
|
|
return false;
|
|
}
|
|
|
|
// Bind id to prepared statement
|
|
if((rc = sqlite3_bind_int(stmt, 1, id)) != SQLITE_OK)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_deleteDevice(%i): Failed to bind id (error %d) - %s",
|
|
id, rc, *message);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
return false;
|
|
}
|
|
|
|
// Execute statement
|
|
rc = sqlite3_step(stmt);
|
|
if(rc != SQLITE_DONE)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_deleteDevice(%i) - SQL error step (%i): %s",
|
|
id, rc, *message);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
return false;
|
|
}
|
|
|
|
// Check if we deleted any rows
|
|
*deleted += sqlite3_changes(db);
|
|
|
|
// Finalize statement
|
|
sqlite3_finalize(stmt);
|
|
|
|
// Second step: Delete the device itself
|
|
querystr = "DELETE FROM network WHERE id = ?;";
|
|
rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
|
if( rc != SQLITE_OK ){
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_deleteDevice(%i) - SQL error prepare (%i): %s",
|
|
id, rc, *message);
|
|
return false;
|
|
}
|
|
|
|
// Bind id to prepared statement
|
|
if((rc = sqlite3_bind_int(stmt, 1, id)) != SQLITE_OK)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_deleteDevice(%i): Failed to bind id (error %d) - %s",
|
|
id, rc, *message);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
return false;
|
|
}
|
|
|
|
// Execute statement
|
|
rc = sqlite3_step(stmt);
|
|
if(rc != SQLITE_DONE)
|
|
{
|
|
*message = sqlite3_errstr(rc);
|
|
log_err("networkTable_deleteDevice(%i) - SQL error step (%i): %s",
|
|
id, rc, *message);
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
return false;
|
|
}
|
|
|
|
// Check if we deleted any rows
|
|
*deleted += sqlite3_changes(db);
|
|
|
|
// Finalize statement
|
|
sqlite3_finalize(stmt);
|
|
|
|
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;
|
|
}
|