Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect and handle interface changes of clients with the same IP #999

Merged
merged 1 commit into from Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 46 additions & 42 deletions src/database/gravity-db.c
Expand Up @@ -792,7 +792,7 @@ char* __attribute__ ((malloc)) get_client_names_from_ids(const char *group_ids)
}

// Prepare statements for scanning white- and blacklist as well as gravit for one client
bool gravityDB_prepare_client_statements(const int clientID, clientsData *client)
bool gravityDB_prepare_client_statements(clientsData *client)
{
// Return early if gravity database is not available
if(!gravityDB_opened && !gravityDB_open())
Expand Down Expand Up @@ -825,7 +825,7 @@ bool gravityDB_prepare_client_statements(const int clientID, clientsData *client
gravityDB_close();
return false;
}
whitelist_stmt->set(whitelist_stmt, clientID, stmt);
whitelist_stmt->set(whitelist_stmt, client->id, stmt);
free(querystr);

// Prepare gravity statement
Expand All @@ -839,7 +839,7 @@ bool gravityDB_prepare_client_statements(const int clientID, clientsData *client
gravityDB_close();
return false;
}
gravity_stmt->set(gravity_stmt, clientID, stmt);
gravity_stmt->set(gravity_stmt, client->id, stmt);
free(querystr);

// Prepare blacklist statement
Expand All @@ -853,35 +853,35 @@ bool gravityDB_prepare_client_statements(const int clientID, clientsData *client
gravityDB_close();
return false;
}
blacklist_stmt->set(blacklist_stmt, clientID, stmt);
blacklist_stmt->set(blacklist_stmt, client->id, stmt);
free(querystr);

return true;
}

// Finalize non-NULL prepared statements and set them to NULL for a given client
static inline void gravityDB_finalize_client_statements(const int clientID, clientsData *client)
static inline void gravityDB_finalize_client_statements(clientsData *client)
{
if(config.debug & DEBUG_DATABASE)
logg("Finalizing gravity statements for %s", getstr(client->ippos));

if(whitelist_stmt != NULL &&
whitelist_stmt->get(whitelist_stmt, clientID) != NULL)
whitelist_stmt->get(whitelist_stmt, client->id) != NULL)
{
sqlite3_finalize(whitelist_stmt->get(whitelist_stmt, clientID));
whitelist_stmt->set(whitelist_stmt, clientID, NULL);
sqlite3_finalize(whitelist_stmt->get(whitelist_stmt, client->id));
whitelist_stmt->set(whitelist_stmt, client->id, NULL);
}
if(blacklist_stmt != NULL &&
blacklist_stmt->get(blacklist_stmt, clientID) != NULL)
blacklist_stmt->get(blacklist_stmt, client->id) != NULL)
{
sqlite3_finalize(blacklist_stmt->get(blacklist_stmt, clientID));
blacklist_stmt->set(blacklist_stmt, clientID, NULL);
sqlite3_finalize(blacklist_stmt->get(blacklist_stmt, client->id));
blacklist_stmt->set(blacklist_stmt, client->id, NULL);
}
if(gravity_stmt != NULL &&
gravity_stmt->get(gravity_stmt, clientID) != NULL)
gravity_stmt->get(gravity_stmt, client->id) != NULL)
{
sqlite3_finalize(gravity_stmt->get(gravity_stmt, clientID));
gravity_stmt->set(gravity_stmt, clientID, NULL);
sqlite3_finalize(gravity_stmt->get(gravity_stmt, client->id));
gravity_stmt->set(gravity_stmt, client->id, NULL);
}

// Unset group found property to trigger a check next time the
Expand All @@ -903,7 +903,7 @@ void gravityDB_close(void)
for(int clientID = 0; clientID < counters->clients; clientID++)
{
clientsData *client = getClient(clientID, true);
gravityDB_finalize_client_statements(clientID, client);
gravityDB_finalize_client_statements(client);
}

// Free allocated memory for vectors of prepared client statements
Expand Down Expand Up @@ -1169,9 +1169,19 @@ static bool domain_in_list(const char *domain, sqlite3_stmt* stmt, const char* l
return (result == 1);
}

void gravityDB_reload_groups(clientsData* client)
{
// Rebuild client table statements (possibly from a different group set)
gravityDB_finalize_client_statements(client);
gravityDB_prepare_client_statements(client);

// Reload regex for this client (possibly from a different group set)
reload_per_client_regex(client);
}

// Check if this client needs a rechecking of group membership
// This client may be identified by something that wasn't there on its first query (hostname, MAC address, interface)
static void gravityDB_client_check_again(const int clientID, clientsData* client)
static void gravityDB_client_check_again(clientsData* client)
{
const time_t diff = time(NULL) - client->firstSeen;
const unsigned char check_count = client->reread_groups + 1u;
Expand All @@ -1182,32 +1192,26 @@ static void gravityDB_client_check_again(const int clientID, clientsData* client
logg("Reloading client groups after %u seconds (%u%s check)",
(unsigned int)diff, check_count, ord);
client->reread_groups++;

// Rebuild client table statements (possibly from a different group set)
gravityDB_finalize_client_statements(clientID, client);
gravityDB_prepare_client_statements(clientID, client);

// Reload regex for this client (possibly from a different group set)
reload_per_client_regex(clientID, client);
gravityDB_reload_groups(client);
}
}

bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, const int clientID, clientsData* client)
bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, clientsData* client)
{
// If list statement is not ready and cannot be initialized (e.g. no
// access to the database), we return false to prevent an FTL crash
if(whitelist_stmt == NULL)
return false;

// Check if this client needs a rechecking of group membership
gravityDB_client_check_again(clientID, client);
gravityDB_client_check_again(client);

// Get whitelist statement from vector of prepared statements if available
sqlite3_stmt *stmt = whitelist_stmt->get(whitelist_stmt, clientID);
sqlite3_stmt *stmt = whitelist_stmt->get(whitelist_stmt, client->id);

// If client statement is not ready and cannot be initialized (e.g. no access to
// the database), we return false (not in whitelist) to prevent an FTL crash
if(stmt == NULL && !gravityDB_prepare_client_statements(clientID, client))
if(stmt == NULL && !gravityDB_prepare_client_statements(client))
{
logg("ERROR: Gravity database not available, assuming domain is not whitelisted");
return false;
Expand All @@ -1216,7 +1220,7 @@ bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, const int c
// Update statement if has just been initialized
if(stmt == NULL)
{
stmt = whitelist_stmt->get(whitelist_stmt, clientID);
stmt = whitelist_stmt->get(whitelist_stmt, client->id);
}

// We have to check both the exact whitelist (using a prepared database statement)
Expand All @@ -1226,25 +1230,25 @@ bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, const int c
// optimization as the database lookup will most likely hit (a) more domains and (b)
// will be faster (given a sufficiently large number of regex whitelisting filters).
return domain_in_list(domain, stmt, "whitelist") ||
match_regex(domain, dns_cache, clientID, REGEX_WHITELIST, false) != -1;
match_regex(domain, dns_cache, client->id, REGEX_WHITELIST, false) != -1;
}

bool in_gravity(const char *domain, const int clientID, clientsData* client)
bool in_gravity(const char *domain, clientsData* client)
{
// If list statement is not ready and cannot be initialized (e.g. no
// access to the database), we return false to prevent an FTL crash
if(gravity_stmt == NULL)
return false;

// Check if this client needs a rechecking of group membership
gravityDB_client_check_again(clientID, client);
gravityDB_client_check_again(client);

// Get whitelist statement from vector of prepared statements
sqlite3_stmt *stmt = gravity_stmt->get(gravity_stmt, clientID);
sqlite3_stmt *stmt = gravity_stmt->get(gravity_stmt, client->id);

// If client statement is not ready and cannot be initialized (e.g. no access to
// the database), we return false (not in gravity list) to prevent an FTL crash
if(stmt == NULL && !gravityDB_prepare_client_statements(clientID, client))
if(stmt == NULL && !gravityDB_prepare_client_statements(client))
{
logg("ERROR: Gravity database not available, assuming domain is not gravity blocked");
return false;
Expand All @@ -1253,28 +1257,28 @@ bool in_gravity(const char *domain, const int clientID, clientsData* client)
// Update statement if has just been initialized
if(stmt == NULL)
{
stmt = gravity_stmt->get(gravity_stmt, clientID);
stmt = gravity_stmt->get(gravity_stmt, client->id);
}

return domain_in_list(domain, stmt, "gravity");
}

inline bool in_blacklist(const char *domain, const int clientID, clientsData* client)
inline bool in_blacklist(const char *domain, clientsData* client)
{
// If list statement is not ready and cannot be initialized (e.g. no
// access to the database), we return false to prevent an FTL crash
if(blacklist_stmt == NULL)
return false;

// Check if this client needs a rechecking of group membership
gravityDB_client_check_again(clientID, client);
gravityDB_client_check_again(client);

// Get whitelist statement from vector of prepared statements
sqlite3_stmt *stmt = blacklist_stmt->get(blacklist_stmt, clientID);
sqlite3_stmt *stmt = blacklist_stmt->get(blacklist_stmt, client->id);

// If client statement is not ready and cannot be initialized (e.g. no access to
// the database), we return false (not in blacklist) to prevent an FTL crash
if(stmt == NULL && !gravityDB_prepare_client_statements(clientID, client))
if(stmt == NULL && !gravityDB_prepare_client_statements(client))
{
logg("ERROR: Gravity database not available, assuming domain is not blacklisted");
return false;
Expand All @@ -1283,7 +1287,7 @@ inline bool in_blacklist(const char *domain, const int clientID, clientsData* cl
// Update statement if has just been initialized
if(stmt == NULL)
{
stmt = blacklist_stmt->get(blacklist_stmt, clientID);
stmt = blacklist_stmt->get(blacklist_stmt, client->id);
}

return domain_in_list(domain, stmt, "blacklist");
Expand All @@ -1301,10 +1305,10 @@ bool in_auditlist(const char *domain)
}

bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int numregex, const regex_data *regex,
const unsigned char type, const char* table, const int clientID)
const unsigned char type, const char* table)
{
if(config.debug & DEBUG_REGEX)
logg("Getting regex client groups for client with ID %i", clientID);
logg("Getting regex client groups for client with ID %i", client->id);

char *querystr = NULL;
if(!client->found_group && !get_client_groupids(client))
Expand Down Expand Up @@ -1341,7 +1345,7 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n
// Regular expressions are stored in one array
if(type == REGEX_WHITELIST)
regexID += get_num_regex(REGEX_BLACKLIST);
set_per_client_regex(clientID, regexID, true);
set_per_client_regex(client->id, regexID, true);

if(config.debug & DEBUG_REGEX)
logg("Regex %s: Enabling regex with DB ID %i for client %s", regextype[type], result, getstr(client->ippos));
Expand Down
11 changes: 6 additions & 5 deletions src/database/gravity-db.h
Expand Up @@ -21,7 +21,8 @@ enum gravity_tables { GRAVITY_TABLE, EXACT_BLACKLIST_TABLE, EXACT_WHITELIST_TABL
void gravityDB_forked(void);
void gravityDB_reopen(void);
bool gravityDB_open(void);
bool gravityDB_prepare_client_statements(const int clientID, clientsData* client);
void gravityDB_reload_groups(clientsData* client);
bool gravityDB_prepare_client_statements(clientsData* client);
void gravityDB_close(void);
bool gravityDB_getTable(unsigned char list);
const char* gravityDB_getDomain(int *rowid);
Expand All @@ -30,11 +31,11 @@ void gravityDB_finalizeTable(void);
int gravityDB_count(const enum gravity_tables list);
bool in_auditlist(const char *domain);

bool in_gravity(const char *domain, const int clientID, clientsData* client);
bool in_blacklist(const char *domain, const int clientID, clientsData* client);
bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, const int clientID, clientsData* client);
bool in_gravity(const char *domain, clientsData* client);
bool in_blacklist(const char *domain, clientsData* client);
bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, clientsData* client);

bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int numregex, const regex_data *regex,
const unsigned char type, const char* table, const int clientID);
const unsigned char type, const char* table);

#endif //GRAVITY_H
5 changes: 4 additions & 1 deletion src/datastructure.c
Expand Up @@ -270,6 +270,9 @@ int findClientID(const char *clientIP, const bool count, const bool aliasclient)
// Initialize client-specific overTime data
memset(client->overTime, 0, sizeof(client->overTime));

// Store client ID
client->id = clientID;

// Increase counter by one
counters->clients++;

Expand All @@ -282,7 +285,7 @@ int findClientID(const char *clientIP, const bool count, const bool aliasclient)
// during history reading get their enabled regexs reloaded
// in the initial call to FTL_reload_all_domainlists()
if(!startup && !aliasclient)
reload_per_client_regex(clientID, client);
reload_per_client_regex(client);

// Check if this client is managed by a alias-client
if(!aliasclient)
Expand Down
1 change: 1 addition & 0 deletions src/datastructure.h
Expand Up @@ -61,6 +61,7 @@ typedef struct {
int blockedcount;
int aliasclient_id;
int overTime[OVERTIME_SLOTS];
unsigned int id;
unsigned int numQueriesARP;
size_t groupspos;
size_t ippos;
Expand Down
38 changes: 31 additions & 7 deletions src/dnsmasq_interface.c
Expand Up @@ -90,7 +90,7 @@ static bool check_domain_blocked(const char *domain, const int clientID,
// Check domains against exact blacklist
// Skipped when the domain is whitelisted
bool blockDomain = false;
if(in_blacklist(domain, clientID, client))
if(in_blacklist(domain, client))
{
// We block this domain
blockDomain = true;
Expand All @@ -105,7 +105,7 @@ static bool check_domain_blocked(const char *domain, const int clientID,
// Check domains against gravity domains
// Skipped when the domain is whitelisted or blocked by exact blacklist
if(!query->whitelisted && !blockDomain &&
in_gravity(domain, clientID, client))
in_gravity(domain, client))
{
// We block this domain
blockDomain = true;
Expand All @@ -121,7 +121,7 @@ static bool check_domain_blocked(const char *domain, const int clientID,
// Skipped when the domain is whitelisted or blocked by exact blacklist or gravity
int regex_idx = 0;
if(!query->whitelisted && !blockDomain &&
(regex_idx = match_regex(domain, dns_cache, clientID, REGEX_BLACKLIST, false)) > -1)
(regex_idx = match_regex(domain, dns_cache, client->id, REGEX_BLACKLIST, false)) > -1)
{
// We block this domain
blockDomain = true;
Expand Down Expand Up @@ -280,7 +280,7 @@ static bool _FTL_check_blocking(int queryID, int domainID, int clientID, const c
const char *blockedDomain = domainstr;

// Check whitelist (exact + regex) for match
query->whitelisted = in_whitelist(domainstr, dns_cache, clientID, client);
query->whitelisted = in_whitelist(domainstr, dns_cache, client);

bool blockDomain = false;
unsigned char new_status = QUERY_UNKNOWN;
Expand Down Expand Up @@ -669,9 +669,33 @@ bool _FTL_new_query(const unsigned int flags, const char *name,
client->lastQuery = querytimestamp;
client->numQueriesARP++;

// Store interface information in client data (if available)
if(client->ifacepos == 0u && next_iface != NULL)
client->ifacepos = addstr(next_iface);
// Preocess interface information of client (if available)
if(next_iface != NULL)
{
if(client->ifacepos == 0u)
{
// Store in the client data if unknown so far
client->ifacepos = addstr(next_iface);
}
else
{
// Check if this is still the same interface or
// if the client moved to another interface
// (may require group re-processing)
const char *oldiface = getstr(client->ifacepos);
if(strcasecmp(oldiface, next_iface) != 0)
{
if(config.debug & DEBUG_CLIENTS)
{
const char *clientName = getstr(client->namepos);
logg("Client %s (%s) changed interface: %s -> %s",
clientIP, clientName, oldiface, next_iface);
}

gravityDB_reload_groups(client);
}
}
}

// Set client MAC address from EDNS(0) information (if available)
if(config.edns0_ecs && edns->mac_set)
Expand Down