mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
Merge branch 'development-v6' into tweak/api_network_info
Build, Test, Deploy / smoke-tests (push) Has been cancelled
Codespell / spell-check (push) Has been cancelled
Check for merge conflicts / merge-conflict (push) Has been cancelled
API validation / Node (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-386, , linux/386) (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-amd64, , linux/amd64) (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-amd64-clang, clang, linux/amd64) (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-riscv64, , linux/riscv64) (push) Has been cancelled
Build, Test, Deploy / self-hosted (pihole-FTL-arm64, linux/arm64/v8) (push) Has been cancelled
Build, Test, Deploy / self-hosted (pihole-FTL-armv6, linux/arm/v6) (push) Has been cancelled
Build, Test, Deploy / self-hosted (pihole-FTL-armv7, linux/arm/v7) (push) Has been cancelled
Build, Test, Deploy / smoke-tests (push) Has been cancelled
Codespell / spell-check (push) Has been cancelled
Check for merge conflicts / merge-conflict (push) Has been cancelled
API validation / Node (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-386, , linux/386) (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-amd64, , linux/amd64) (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-amd64-clang, clang, linux/amd64) (push) Has been cancelled
Build, Test, Deploy / gha (pihole-FTL-riscv64, , linux/riscv64) (push) Has been cancelled
Build, Test, Deploy / self-hosted (pihole-FTL-arm64, linux/arm64/v8) (push) Has been cancelled
Build, Test, Deploy / self-hosted (pihole-FTL-armv6, linux/arm/v6) (push) Has been cancelled
Build, Test, Deploy / self-hosted (pihole-FTL-armv7, linux/arm/v7) (push) Has been cancelled
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
+4
-4
@@ -5,13 +5,13 @@ WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
ARG CI_ARCH="linux/amd64"
|
||||
ENV CI_ARCH ${CI_ARCH}
|
||||
ENV CI_ARCH=${CI_ARCH}
|
||||
ARG GIT_BRANCH="test"
|
||||
ENV GIT_BRANCH ${GIT_BRANCH}
|
||||
ENV GIT_BRANCH=${GIT_BRANCH}
|
||||
ARG GIT_TAG="test"
|
||||
ENV GIT_TAG ${GIT_TAG}
|
||||
ENV GIT_TAG=${GIT_TAG}
|
||||
ARG BUILD_OPTS=""
|
||||
ENV BUILD_OPTS ${BUILD_OPTS}
|
||||
ENV BUILD_OPTS=${BUILD_OPTS}
|
||||
|
||||
# Build FTL
|
||||
# Remove possible old build files
|
||||
|
||||
@@ -33,6 +33,12 @@ int api_stats_upstreams(struct ftl_conn *api);
|
||||
int api_stats_top_domains(struct ftl_conn *api);
|
||||
int api_stats_top_clients(struct ftl_conn *api);
|
||||
int api_stats_recentblocked(struct ftl_conn *api);
|
||||
cJSON *get_top_domains(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool domains_only);
|
||||
cJSON *get_top_clients(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool clients_only,
|
||||
const bool names_only);
|
||||
cJSON *get_top_upstreams(struct ftl_conn *api, const bool upstreams_only);
|
||||
|
||||
// History methods
|
||||
int api_history(struct ftl_conn *api);
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ static int set_blocking(struct ftl_conn *api)
|
||||
// The blocking status does not need to be changed
|
||||
|
||||
// Delete a possibly running timer
|
||||
set_blockingmode_timer(-1.0, true);
|
||||
set_blockingmode_timer(timer, true);
|
||||
|
||||
log_debug(DEBUG_API, "No change in blocking mode, resetting timer");
|
||||
}
|
||||
|
||||
@@ -348,21 +348,23 @@ components:
|
||||
sync:
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
server:
|
||||
type: string
|
||||
interval:
|
||||
type: integer
|
||||
count:
|
||||
type: integer
|
||||
rtc:
|
||||
type: object
|
||||
properties:
|
||||
set:
|
||||
type: boolean
|
||||
device:
|
||||
type: string
|
||||
utc:
|
||||
type: boolean
|
||||
rtc:
|
||||
type: object
|
||||
properties:
|
||||
set:
|
||||
type: boolean
|
||||
device:
|
||||
type: string
|
||||
utc:
|
||||
type: boolean
|
||||
resolver:
|
||||
type: object
|
||||
properties:
|
||||
@@ -708,13 +710,14 @@ components:
|
||||
active: true
|
||||
address: ""
|
||||
sync:
|
||||
active: true
|
||||
server: "pool.ntp.org"
|
||||
interval: 3600
|
||||
count: 8
|
||||
rtc:
|
||||
set: true
|
||||
device: ""
|
||||
utc: true
|
||||
rtc:
|
||||
set: true
|
||||
device: ""
|
||||
utc: true
|
||||
resolver:
|
||||
resolveIPv4: true
|
||||
resolveIPv6: true
|
||||
|
||||
+45
-44
@@ -22,7 +22,8 @@
|
||||
// dbopen(false, ), dbclose()
|
||||
#include "database/common.h"
|
||||
|
||||
static int add_strings_to_array(struct ftl_conn *api, cJSON *array, const char *querystr, const int max_count)
|
||||
#if 0
|
||||
static int add_strings_to_array(struct ftl_conn *api, cJSON *array1, cJSON *array2, const char *querystr, const int max_count)
|
||||
{
|
||||
|
||||
sqlite3 *memdb = get_memdb();
|
||||
@@ -44,11 +45,24 @@ static int add_strings_to_array(struct ftl_conn *api, cJSON *array, const char *
|
||||
sqlite3_errstr(rc));
|
||||
}
|
||||
|
||||
// Loop through returned rows
|
||||
// Loop through returned rows and add them to the array
|
||||
int counter = 0;
|
||||
while((rc = sqlite3_step(stmt)) == SQLITE_ROW &&
|
||||
(max_count < 0 || ++counter < max_count))
|
||||
JSON_COPY_STR_TO_ARRAY(array, (const char*)sqlite3_column_text(stmt, 0));
|
||||
(max_count < 0 || ++counter <= max_count))
|
||||
{
|
||||
const char *array1_str = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if(array1_str != NULL && array1_str[0] != '\0')
|
||||
// Only add non-empty strings
|
||||
JSON_COPY_STR_TO_ARRAY(array1, array1_str);
|
||||
if(array2 != NULL)
|
||||
{
|
||||
// We have a second array to fill (second column in the query)
|
||||
const char *array2_str = (const char*)sqlite3_column_text(stmt, 1);
|
||||
if(array2_str != NULL && array2_str[0] != '\0')
|
||||
// Only add non-empty strings
|
||||
JSON_COPY_STR_TO_ARRAY(array2, array2_str);
|
||||
}
|
||||
}
|
||||
|
||||
// Acceptable return codes are either
|
||||
// - SQLITE_DONE: We read all lines, or
|
||||
@@ -67,60 +81,47 @@ static int add_strings_to_array(struct ftl_conn *api, cJSON *array, const char *
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int api_queries_suggestions(struct ftl_conn *api)
|
||||
{
|
||||
int rc;
|
||||
// Does the user request a custom number of records to be included?
|
||||
int count = 30;
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
|
||||
// Get domains
|
||||
cJSON *domain = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, domain, "SELECT domain FROM domain_by_id", count);
|
||||
if(rc != 0)
|
||||
cJSON *domain = get_top_domains(api, count, false, true);
|
||||
cJSON *blocked = get_top_domains(api, count, true, true);
|
||||
// Add domains from both arrays, avoiding duplicates
|
||||
cJSON *entry = NULL;
|
||||
cJSON_ArrayForEach(entry, blocked)
|
||||
{
|
||||
log_err("Cannot read domains from database");
|
||||
cJSON_Delete(domain);
|
||||
return rc;
|
||||
// Check if the domain is already in the list
|
||||
bool found = false;
|
||||
cJSON *entry2 = NULL;
|
||||
cJSON_ArrayForEach(entry2, domain)
|
||||
{
|
||||
if(strcmp(cJSON_GetStringValue(entry), cJSON_GetStringValue(entry2)) == 0)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
JSON_ADD_ITEM_TO_ARRAY(domain, cJSON_Duplicate(entry, true));
|
||||
}
|
||||
// Free the blocked list
|
||||
cJSON_Delete(blocked);
|
||||
|
||||
// Get clients, both by IP and names
|
||||
// We have to call DISTINCT() here as multiple IPs can map to and name and
|
||||
// vice versa
|
||||
cJSON *client_ip = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, client_ip, "SELECT DISTINCT(ip) FROM client_by_id", count);
|
||||
if(rc != 0)
|
||||
{
|
||||
log_err("Cannot read client IPs from database");
|
||||
cJSON_Delete(domain);
|
||||
cJSON_Delete(client_ip);
|
||||
return rc;
|
||||
}
|
||||
cJSON *client_name = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, client_name, "SELECT DISTINCT(name) FROM client_by_id", count);
|
||||
if(rc != 0)
|
||||
{
|
||||
log_err("Cannot read client names from database");
|
||||
cJSON_Delete(domain);
|
||||
cJSON_Delete(client_ip);
|
||||
cJSON_Delete(client_name);
|
||||
return rc;
|
||||
}
|
||||
cJSON *client_ip = get_top_clients(api, count, false, true, false);
|
||||
cJSON *client_name = get_top_clients(api, count, false, true, true);
|
||||
|
||||
// Delete duplicate entries from client_name
|
||||
cJSON_unique_array(client_name);
|
||||
|
||||
// Get upstreams
|
||||
cJSON *upstream = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, upstream, "SELECT forward FROM forward_by_id", count);
|
||||
if(rc != 0)
|
||||
{
|
||||
log_err("Cannot read forward from database");
|
||||
cJSON_Delete(domain);
|
||||
cJSON_Delete(client_ip);
|
||||
cJSON_Delete(client_name);
|
||||
cJSON_Delete(upstream);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cJSON *upstream = get_top_upstreams(api, true);
|
||||
// Get types
|
||||
cJSON *type = JSON_NEW_ARRAY();
|
||||
queriesData query = { 0 };
|
||||
|
||||
+336
-222
@@ -14,8 +14,6 @@
|
||||
#include "api/api.h"
|
||||
#include "shmem.h"
|
||||
#include "datastructure.h"
|
||||
// read_setupVarsconf()
|
||||
#include "config/setupVars.h"
|
||||
// logging routines
|
||||
#include "log.h"
|
||||
// config struct
|
||||
@@ -27,6 +25,17 @@
|
||||
// sqrt()
|
||||
#include <math.h>
|
||||
|
||||
struct top_entries {
|
||||
int count;
|
||||
unsigned int responses;
|
||||
in_port_t port;
|
||||
size_t namepos;
|
||||
size_t ippos;
|
||||
double rtime;
|
||||
double rtuncertainty;
|
||||
|
||||
};
|
||||
|
||||
/* qsort comparison function (count field), sort ASC
|
||||
static int __attribute__((pure)) cmpasc(const void *a, const void *b)
|
||||
{
|
||||
@@ -55,6 +64,20 @@ int __attribute__((pure)) cmpdesc(const void *a, const void *b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// qsort subroutine, sort DESC
|
||||
static int __attribute__((pure)) cmpdesc_te(const void *a, const void *b)
|
||||
{
|
||||
const struct top_entries *elem1 = (struct top_entries*)a;
|
||||
const struct top_entries *elem2 = (struct top_entries*)b;
|
||||
|
||||
if (elem1->count > elem2->count)
|
||||
return -1;
|
||||
else if (elem1->count < elem2->count)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_query_types_obj(struct ftl_conn *api, cJSON *types)
|
||||
{
|
||||
for(unsigned int i = TYPE_A; i < TYPE_MAX; i++)
|
||||
@@ -128,14 +151,18 @@ int api_stats_summary(struct ftl_conn *api)
|
||||
cJSON *gravity = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(gravity, "domains_being_blocked", counters->database.gravity);
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "queries", queries);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "clients", clients);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "gravity", gravity);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_top_domains(struct ftl_conn *api)
|
||||
cJSON *get_top_domains(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool domains_only)
|
||||
{
|
||||
// Exit before processing any data if requested via config setting
|
||||
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS)
|
||||
@@ -145,24 +172,150 @@ int api_stats_top_domains(struct ftl_conn *api)
|
||||
|
||||
// Minimum structure is
|
||||
// {"top_domains":[]}
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
cJSON *top_domains = JSON_NEW_ARRAY();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "top_domains", top_domains);
|
||||
JSON_SEND_OBJECT(json);
|
||||
if(domains_only)
|
||||
return cJSON_CreateArray();
|
||||
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "domains", cJSON_CreateArray());
|
||||
cJSON_AddNumberToObject(json, "total_queries", -1);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", -1);
|
||||
return json;
|
||||
}
|
||||
|
||||
// Get domains which the user doesn't want to see
|
||||
regex_t *regex_domains = NULL;
|
||||
unsigned int N_regex_domains = 0;
|
||||
compile_filter_regex(api, "webserver.api.excludeDomains",
|
||||
config.webserver.api.excludeDomains.v.json,
|
||||
®ex_domains, &N_regex_domains);
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
// Allocate memory
|
||||
const int domains = counters->domains;
|
||||
int *temparray = calloc(2*domains, sizeof(int));
|
||||
if(temparray == NULL)
|
||||
const int total_queries = counters->queries;
|
||||
const int blocked_count = get_blocked_count();
|
||||
struct top_entries *top_domains = calloc(domains, sizeof(struct top_entries));
|
||||
if(top_domains == NULL)
|
||||
{
|
||||
log_err("Memory allocation failed in %s()", __FUNCTION__);
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned int added_domains = 0u;
|
||||
for(int domainID = 0; domainID < domains; domainID++)
|
||||
{
|
||||
// Get domain pointer
|
||||
const domainsData* domain = getDomain(domainID, true);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
const char *domain_name = getstr(domain->domainpos);
|
||||
|
||||
// Hidden domain, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(domain_name, HIDDEN_DOMAIN) == 0)
|
||||
continue;
|
||||
|
||||
// Use either blocked or total count based on request string
|
||||
top_domains[added_domains].count = blocked ? domain->blockedcount : domain->count - domain->blockedcount;
|
||||
|
||||
// Get domain name
|
||||
top_domains[added_domains].namepos = domain->domainpos;
|
||||
|
||||
// Increment counter
|
||||
added_domains++;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Sort temporary array
|
||||
qsort(top_domains, added_domains, sizeof(*top_domains), cmpdesc_te);
|
||||
|
||||
int n = 0;
|
||||
cJSON *jtop_domains = cJSON_CreateArray();
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
for(unsigned int i = 0; i < added_domains; i++)
|
||||
{
|
||||
// Skip e.g. recycled domains
|
||||
if(top_domains[i].namepos == 0)
|
||||
continue;
|
||||
|
||||
const char *domain = getstr(top_domains[i].namepos);
|
||||
|
||||
// Skip this client if there is a filter on it
|
||||
bool skip_domain = false;
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Iterate over all regex filters
|
||||
for(unsigned int j = 0; j < N_regex_domains; j++)
|
||||
{
|
||||
// Check if the domain matches the regex
|
||||
if(regexec(®ex_domains[j], domain, 0, NULL, 0) == 0)
|
||||
{
|
||||
// Domain matches
|
||||
skip_domain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(skip_domain || top_domains[i].count < 1)
|
||||
continue;
|
||||
|
||||
if(domains_only)
|
||||
{
|
||||
cJSON_AddStringToArray(jtop_domains, domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *domain_item = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(domain_item, "domain", domain);
|
||||
cJSON_AddNumberToObject(domain_item, "count", top_domains[i].count);
|
||||
cJSON_AddItemToArray(jtop_domains, domain_item);
|
||||
}
|
||||
|
||||
// Only count entries that are actually sent and return when we have send enough data
|
||||
if(++n >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Free temporary array
|
||||
free(top_domains);
|
||||
|
||||
// Free regexes
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Free individual regexes
|
||||
for(unsigned int i = 0; i < N_regex_domains; i++)
|
||||
regfree(®ex_domains[i]);
|
||||
|
||||
// Free array of regex pointers
|
||||
free(regex_domains);
|
||||
}
|
||||
|
||||
if(domains_only)
|
||||
{
|
||||
// Return the array of domains only
|
||||
return jtop_domains;
|
||||
}
|
||||
|
||||
// else: Build and return full object
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "domains", jtop_domains);
|
||||
cJSON_AddNumberToObject(json, "total_queries", total_queries);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", blocked_count);
|
||||
return json;
|
||||
}
|
||||
|
||||
int api_stats_top_domains(struct ftl_conn *api)
|
||||
{
|
||||
bool blocked = false; // Can be overwritten by query string
|
||||
int count = 10;
|
||||
// /api/stats/top_domains?blocked=true
|
||||
@@ -176,121 +329,14 @@ int api_stats_top_domains(struct ftl_conn *api)
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
}
|
||||
|
||||
unsigned int added_domains = 0u;
|
||||
for(int domainID = 0; domainID < domains; domainID++)
|
||||
{
|
||||
// Get domain pointer
|
||||
const domainsData* domain = getDomain(domainID, true);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
// Add domain ID
|
||||
temparray[2*added_domains + 0] = domainID;
|
||||
|
||||
// Use either blocked or total count based on request string
|
||||
temparray[2*added_domains + 1] = blocked ? domain->blockedcount : domain->count - domain->blockedcount;
|
||||
|
||||
added_domains++;
|
||||
}
|
||||
|
||||
// Sort temporary array
|
||||
qsort(temparray, added_domains, sizeof(int[2]), cmpdesc);
|
||||
|
||||
// Get domains which the user doesn't want to see
|
||||
regex_t *regex_domains = NULL;
|
||||
unsigned int N_regex_domains = 0;
|
||||
compile_filter_regex(api, "webserver.api.excludeDomains",
|
||||
config.webserver.api.excludeDomains.v.json,
|
||||
®ex_domains, &N_regex_domains);
|
||||
|
||||
int n = 0;
|
||||
cJSON *top_domains = JSON_NEW_ARRAY();
|
||||
for(unsigned int i = 0; i < added_domains; i++)
|
||||
{
|
||||
// Get sorted index
|
||||
const int domainID = temparray[2*i + 0];
|
||||
// Get domain pointer
|
||||
const domainsData* domain = getDomain(domainID, true);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
// Get domain name
|
||||
const char *domain_name = getstr(domain->domainpos);
|
||||
|
||||
// Hidden domain, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(domain_name, HIDDEN_DOMAIN) == 0)
|
||||
continue;
|
||||
|
||||
// Skip this client if there is a filter on it
|
||||
bool skip_domain = false;
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Iterate over all regex filters
|
||||
for(unsigned int j = 0; j < N_regex_domains; j++)
|
||||
{
|
||||
// Check if the domain matches the regex
|
||||
if(regexec(®ex_domains[j], domain_name, 0, NULL, 0) == 0)
|
||||
{
|
||||
// Domain matches
|
||||
skip_domain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(skip_domain)
|
||||
continue;
|
||||
|
||||
int domain_count = -1;
|
||||
if(blocked && domain->blockedcount > 0)
|
||||
{
|
||||
domain_count = domain->blockedcount;
|
||||
n++;
|
||||
}
|
||||
else if(!blocked && (domain->count - domain->blockedcount) > 0)
|
||||
{
|
||||
domain_count = domain->count - domain->blockedcount;
|
||||
n++;
|
||||
}
|
||||
if(domain_count > -1)
|
||||
{
|
||||
cJSON *domain_item = JSON_NEW_OBJECT();
|
||||
JSON_REF_STR_IN_OBJECT(domain_item, "domain", domain_name);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(domain_item, "count", domain_count);
|
||||
JSON_ADD_ITEM_TO_ARRAY(top_domains, domain_item);
|
||||
}
|
||||
|
||||
// Only count entries that are actually sent and return when we have send enough data
|
||||
if(n >= count)
|
||||
break;
|
||||
}
|
||||
free(temparray);
|
||||
|
||||
// Free regexes
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Free individual regexes
|
||||
for(unsigned int i = 0; i < N_regex_domains; i++)
|
||||
regfree(®ex_domains[i]);
|
||||
|
||||
// Free array of regex pointers
|
||||
free(regex_domains);
|
||||
}
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "domains", top_domains);
|
||||
|
||||
const int blocked_count = get_blocked_count();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_count);
|
||||
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
cJSON *json = get_top_domains(api, count, blocked, false);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_top_clients(struct ftl_conn *api)
|
||||
cJSON *get_top_clients(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool clients_only,
|
||||
const bool names_only)
|
||||
{
|
||||
int count = 10;
|
||||
|
||||
// Exit before processing any data if requested via config setting
|
||||
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS)
|
||||
{
|
||||
@@ -299,31 +345,26 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
|
||||
// Minimum structure is
|
||||
// {"top_clients":[]}
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
cJSON *top_clients = JSON_NEW_ARRAY();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "top_clients", top_clients);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
if(clients_only)
|
||||
return cJSON_CreateArray();
|
||||
|
||||
bool blocked = false; // /api/stats/top_clients?blocked=true
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
// Should blocked clients be shown?
|
||||
get_bool_var(api->request->query_string, "blocked", &blocked);
|
||||
|
||||
// Does the user request a non-default number of replies?
|
||||
// Note: We do not accept zero query requests here
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "clients", cJSON_CreateArray());
|
||||
cJSON_AddNumberToObject(json, "total_queries", -1);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", -1);
|
||||
return json;
|
||||
}
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
int clients = counters->clients;
|
||||
int *temparray = calloc(2*clients, sizeof(int));
|
||||
if(temparray == NULL)
|
||||
const int total_queries = counters->queries;
|
||||
const int blocked_count = get_blocked_count();
|
||||
struct top_entries *top_clients = calloc(clients, sizeof(struct top_entries));
|
||||
if(top_clients == NULL)
|
||||
{
|
||||
log_err("Memory allocation failed in api_stats_top_clients()");
|
||||
log_err("Memory allocation failed in %s()", __FUNCTION__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -337,15 +378,26 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
if(client == NULL || (!client->flags.aliasclient && client->aliasclient_id >= 0))
|
||||
continue;
|
||||
|
||||
temparray[2*added_clients + 0] = clientID;
|
||||
const char *client_ip = getstr(client->ippos);
|
||||
// Hidden client, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(client_ip, HIDDEN_CLIENT) == 0)
|
||||
continue;
|
||||
|
||||
// Use either blocked or total count based on request string
|
||||
temparray[2*added_clients + 1] = blocked ? client->blockedcount : client->count;
|
||||
top_clients[added_clients].count = blocked ? client->blockedcount : client->count;
|
||||
|
||||
// Get client name and IP
|
||||
top_clients[added_clients].ippos = client->ippos;
|
||||
top_clients[added_clients].namepos = client->namepos;
|
||||
|
||||
added_clients++;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Sort temporary array
|
||||
qsort(temparray, added_clients, sizeof(int[2]), cmpdesc);
|
||||
qsort(top_clients, added_clients, sizeof(*top_clients), cmpdesc_te);
|
||||
|
||||
// Get clients which the user doesn't want to see
|
||||
regex_t *regex_clients = NULL;
|
||||
@@ -355,24 +407,19 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
®ex_clients, &N_regex_clients);
|
||||
|
||||
int n = 0;
|
||||
cJSON *top_clients = JSON_NEW_ARRAY();
|
||||
cJSON *jtop_clients = JSON_NEW_ARRAY();
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
for(unsigned int i = 0; i < added_clients; i++)
|
||||
{
|
||||
// Get sorted indices and counter values (may be either total or blocked count)
|
||||
const int clientID = temparray[2*i + 0];
|
||||
const int client_count = temparray[2*i + 1];
|
||||
// Get client pointer
|
||||
const clientsData* client = getClient(clientID, true);
|
||||
if(client == NULL)
|
||||
// Skip e.g. recycled clients
|
||||
if(top_clients[i].namepos == 0)
|
||||
continue;
|
||||
|
||||
// Get IP and host name of client
|
||||
const char *client_ip = getstr(client->ippos);
|
||||
const char *client_name = getstr(client->namepos);
|
||||
|
||||
// Hidden client, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(client_ip, HIDDEN_CLIENT) == 0)
|
||||
continue;
|
||||
const char *client_ip = getstr(top_clients[i].ippos);
|
||||
const char *client_name = getstr(top_clients[i].namepos);
|
||||
|
||||
// Skip this client if there is a filter on it
|
||||
bool skip_client = false;
|
||||
@@ -397,26 +444,37 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
}
|
||||
}
|
||||
|
||||
if(skip_client)
|
||||
if(skip_client || top_clients[i].count < 1)
|
||||
continue;
|
||||
|
||||
// Return this client if the client made at least one query
|
||||
// within the most recent 24 hours
|
||||
if(client_count > 0)
|
||||
if(clients_only)
|
||||
{
|
||||
cJSON *client_item = JSON_NEW_OBJECT();
|
||||
JSON_REF_STR_IN_OBJECT(client_item, "name", client_name);
|
||||
JSON_REF_STR_IN_OBJECT(client_item, "ip", client_ip);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(client_item, "count", client_count);
|
||||
JSON_ADD_ITEM_TO_ARRAY(top_clients, client_item);
|
||||
n++;
|
||||
if(names_only)
|
||||
{
|
||||
if(strlen(client_name) > 0)
|
||||
cJSON_AddStringToArray(jtop_clients, client_name);
|
||||
}
|
||||
else
|
||||
cJSON_AddStringToArray(jtop_clients, client_ip);
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *client_item = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(client_item, "name", client_name);
|
||||
cJSON_AddStringToObject(client_item, "ip", client_ip);
|
||||
cJSON_AddNumberToObject(client_item, "count", top_clients[i].count);
|
||||
cJSON_AddItemToArray(jtop_clients, client_item);
|
||||
}
|
||||
|
||||
if(n == count)
|
||||
if(++n == count)
|
||||
break;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Free temporary array
|
||||
free(temparray);
|
||||
free(top_clients);
|
||||
|
||||
// Free regexes
|
||||
if(N_regex_clients > 0)
|
||||
@@ -429,21 +487,46 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
free(regex_clients);
|
||||
}
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "clients", top_clients);
|
||||
if(clients_only)
|
||||
{
|
||||
// Return the array of clients only
|
||||
return jtop_clients;
|
||||
}
|
||||
|
||||
const int blocked_count = get_blocked_count();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_count);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
// else: Build and return full object
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "clients", jtop_clients);
|
||||
cJSON_AddNumberToObject(json, "total_queries", total_queries);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", blocked_count);
|
||||
return json;
|
||||
}
|
||||
|
||||
int api_stats_top_clients(struct ftl_conn *api)
|
||||
{
|
||||
bool blocked = false; // Can be overwritten by query string
|
||||
int count = 10;
|
||||
// /api/stats/top_clients?blocked=true
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
// Should blocked clients be shown?
|
||||
get_bool_var(api->request->query_string, "blocked", &blocked);
|
||||
|
||||
int api_stats_upstreams(struct ftl_conn *api)
|
||||
// Does the user request a non-default number of replies?
|
||||
// Note: We do not accept zero query requests here
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
}
|
||||
|
||||
cJSON *json = get_top_clients(api, count, blocked, false, false);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
cJSON *get_top_upstreams(struct ftl_conn *api, const bool upstreams_only)
|
||||
{
|
||||
const int upstreams = counters->upstreams;
|
||||
int *temparray = calloc(2*upstreams, sizeof(int));
|
||||
if(temparray == NULL)
|
||||
const int forwarded_count = get_forwarded_count();
|
||||
const int total_queries = counters->queries;
|
||||
struct top_entries *top_upstreams = calloc(upstreams, sizeof(struct top_entries));
|
||||
if(top_upstreams == NULL)
|
||||
{
|
||||
log_err("Memory allocation failed in api_stats_upstreams()");
|
||||
return 0;
|
||||
@@ -460,22 +543,34 @@ int api_stats_upstreams(struct ftl_conn *api)
|
||||
if(upstream == NULL)
|
||||
continue;
|
||||
|
||||
temparray[2*added_upstreams + 0] = upstreamID;
|
||||
temparray[2*added_upstreams + 1] = upstream->count;
|
||||
top_upstreams[added_upstreams].count = upstream->count;
|
||||
top_upstreams[added_upstreams].ippos = upstream->ippos;
|
||||
top_upstreams[added_upstreams].namepos = upstream->namepos;
|
||||
top_upstreams[added_upstreams].port = upstream->port;
|
||||
top_upstreams[added_upstreams].responses = upstream->responses;
|
||||
top_upstreams[added_upstreams].rtime = upstream->rtime;
|
||||
top_upstreams[added_upstreams].rtuncertainty = upstream->rtuncertainty;
|
||||
|
||||
added_upstreams++;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Sort temporary array in descending order
|
||||
qsort(temparray, upstreams, sizeof(int[2]), cmpdesc);
|
||||
qsort(top_upstreams, added_upstreams, sizeof(*top_upstreams), cmpdesc);
|
||||
|
||||
// Loop over available forward destinations
|
||||
cJSON *top_upstreams = JSON_NEW_ARRAY();
|
||||
cJSON *jtop_upstreams = JSON_NEW_ARRAY();
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
for(int i = -2; i < (int)added_upstreams; i++)
|
||||
{
|
||||
int count = 0;
|
||||
const char* ip, *name;
|
||||
int port = -1;
|
||||
in_port_t port = -1;
|
||||
double responsetime = 0.0, uncertainty = 0.0;
|
||||
|
||||
if(i == -2)
|
||||
@@ -495,67 +590,80 @@ int api_stats_upstreams(struct ftl_conn *api)
|
||||
else
|
||||
{
|
||||
// Regular upstream destination
|
||||
// Get sorted indices
|
||||
const int upstreamID = temparray[2*i + 0];
|
||||
|
||||
// Get upstream pointer
|
||||
const upstreamsData *upstream = getUpstream(upstreamID, true);
|
||||
if(upstream == NULL)
|
||||
continue;
|
||||
|
||||
// Get IP and host name of upstream destination if available
|
||||
ip = getstr(upstream->ippos);
|
||||
name = getstr(upstream->namepos);
|
||||
port = upstream->port;
|
||||
|
||||
// Get percentage
|
||||
count = upstream->count;
|
||||
ip = getstr(top_upstreams[i].ippos);
|
||||
name = getstr(top_upstreams[i].namepos);
|
||||
port = top_upstreams[i].port;
|
||||
count = top_upstreams[i].count;
|
||||
|
||||
// Compute average response time and uncertainty (unit: seconds)
|
||||
if(upstream->responses > 0)
|
||||
if(top_upstreams[i].responses > 0)
|
||||
{
|
||||
// Simple average of the response times
|
||||
responsetime = upstream->rtime / upstream->responses;
|
||||
responsetime = top_upstreams[i].rtime / top_upstreams[i].responses;
|
||||
}
|
||||
if(upstream->responses > 1)
|
||||
if(top_upstreams[i].responses > 1)
|
||||
{
|
||||
// The actual value will be somewhere in a neighborhood around the mean value.
|
||||
// This neighborhood of values is the uncertainty in the mean.
|
||||
uncertainty = sqrt(upstream->rtuncertainty / upstream->responses / (upstream->responses-1));
|
||||
uncertainty = sqrt(top_upstreams[i].rtuncertainty / top_upstreams[i].responses / (top_upstreams[i].responses-1));
|
||||
}
|
||||
}
|
||||
|
||||
// Send data:
|
||||
// - always if i < 0 (special upstreams: blocklist and cache)
|
||||
// - only if there are any queries for all others (i > 0)
|
||||
if(count > 0 || i < 0)
|
||||
if(count < 1 && i >= 0)
|
||||
continue;
|
||||
|
||||
if(upstreams_only)
|
||||
{
|
||||
cJSON_AddStringToArray(jtop_upstreams, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *upstream = JSON_NEW_OBJECT();
|
||||
JSON_REF_STR_IN_OBJECT(upstream, "ip", ip);
|
||||
JSON_REF_STR_IN_OBJECT(upstream, "name", name);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(upstream, "port", port);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(upstream, "count", count);
|
||||
cJSON_AddStringToObject(upstream, "ip", ip);
|
||||
cJSON_AddStringToObject(upstream, "name", name);
|
||||
cJSON_AddNumberToObject(upstream, "port", port);
|
||||
cJSON_AddNumberToObject(upstream, "count", count);
|
||||
cJSON *statistics = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(statistics, "response", responsetime);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(statistics, "variance", uncertainty);
|
||||
JSON_ADD_ITEM_TO_OBJECT(upstream, "statistics", statistics);
|
||||
JSON_ADD_ITEM_TO_ARRAY(top_upstreams, upstream);
|
||||
cJSON_AddNumberToObject(statistics, "response", responsetime);
|
||||
cJSON_AddNumberToObject(statistics, "variance", uncertainty);
|
||||
cJSON_AddItemToObject(upstream, "statistics", statistics);
|
||||
cJSON_AddItemToArray(jtop_upstreams, upstream);
|
||||
}
|
||||
}
|
||||
|
||||
// Free temporary array
|
||||
free(temparray);
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "upstreams", top_upstreams);
|
||||
const int forwarded_count = get_forwarded_count();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "forwarded_queries", forwarded_count);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
// Free temporary array
|
||||
free(top_upstreams);
|
||||
|
||||
if(upstreams_only)
|
||||
{
|
||||
// Return the array of upstreams only
|
||||
return jtop_upstreams;
|
||||
}
|
||||
|
||||
// else: Build and return full object
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "upstreams", jtop_upstreams);
|
||||
cJSON_AddNumberToObject(json, "total_queries", total_queries);
|
||||
cJSON_AddNumberToObject(json, "forwarded_queries", forwarded_count);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
int api_stats_upstreams(struct ftl_conn *api)
|
||||
{
|
||||
cJSON *json = get_top_upstreams(api, false);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_query_types(struct ftl_conn *api)
|
||||
{
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
cJSON *types = JSON_NEW_OBJECT();
|
||||
@@ -566,11 +674,14 @@ int api_stats_query_types(struct ftl_conn *api)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "types", types);
|
||||
|
||||
// Send response
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_recentblocked(struct ftl_conn *api)
|
||||
@@ -624,7 +735,10 @@ int api_stats_recentblocked(struct ftl_conn *api)
|
||||
break;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "blocked", blocked);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
+23
-16
@@ -814,6 +814,13 @@ void initConfig(struct config *conf)
|
||||
memset(&conf->ntp.ipv6.address.d.in6_addr, 0, sizeof(struct in6_addr));
|
||||
conf->ntp.ipv6.address.c = validate_stub; // Only type-based checking
|
||||
|
||||
conf->ntp.sync.active.k = "ntp.sync.active";
|
||||
conf->ntp.sync.active.h = "Should FTL try to synchronize the system time with an upstream NTP server?";
|
||||
conf->ntp.sync.active.t = CONF_BOOL;
|
||||
conf->ntp.sync.active.f = FLAG_RESTART_FTL;
|
||||
conf->ntp.sync.active.d.b = true;
|
||||
conf->ntp.sync.active.c = validate_stub; // Only type-based checking
|
||||
|
||||
conf->ntp.sync.server.k = "ntp.sync.server";
|
||||
conf->ntp.sync.server.h = "NTP upstream server to sync with, e.g., \"pool.ntp.org\". Note that the NTP server should be located as close as possible to you in order to minimize the time offset possibly introduced by different routing paths.";
|
||||
conf->ntp.sync.server.a = cJSON_CreateStringReference("valid NTP upstream server");
|
||||
@@ -833,24 +840,24 @@ void initConfig(struct config *conf)
|
||||
conf->ntp.sync.count.d.ui = 8;
|
||||
conf->ntp.sync.count.c = validate_stub; // Only type-based checking
|
||||
|
||||
conf->ntp.rtc.set.k = "ntp.rtc.set";
|
||||
conf->ntp.rtc.set.h = "Should FTL update a real-time clock (RTC) if available?";
|
||||
conf->ntp.rtc.set.t = CONF_BOOL;
|
||||
conf->ntp.rtc.set.d.b = true;
|
||||
conf->ntp.rtc.set.c = validate_stub; // Only type-based checking
|
||||
conf->ntp.sync.rtc.set.k = "ntp.sync.rtc.set";
|
||||
conf->ntp.sync.rtc.set.h = "Should FTL update a real-time clock (RTC) if available?";
|
||||
conf->ntp.sync.rtc.set.t = CONF_BOOL;
|
||||
conf->ntp.sync.rtc.set.d.b = true;
|
||||
conf->ntp.sync.rtc.set.c = validate_stub; // Only type-based checking
|
||||
|
||||
conf->ntp.rtc.device.k = "ntp.rtc.device";
|
||||
conf->ntp.rtc.device.h = "Path to the RTC device to update. Leave empty for auto-discovery";
|
||||
conf->ntp.rtc.device.a = cJSON_CreateStringReference("Path to the RTC device, e.g., \"/dev/rtc0\"");
|
||||
conf->ntp.rtc.device.t = CONF_STRING;
|
||||
conf->ntp.rtc.device.d.s = (char*)"";
|
||||
conf->ntp.rtc.device.c = validate_stub; // Only type-based checking
|
||||
conf->ntp.sync.rtc.device.k = "ntp.sync.rtc.device";
|
||||
conf->ntp.sync.rtc.device.h = "Path to the RTC device to update. Leave empty for auto-discovery";
|
||||
conf->ntp.sync.rtc.device.a = cJSON_CreateStringReference("Path to the RTC device, e.g., \"/dev/rtc0\"");
|
||||
conf->ntp.sync.rtc.device.t = CONF_STRING;
|
||||
conf->ntp.sync.rtc.device.d.s = (char*)"";
|
||||
conf->ntp.sync.rtc.device.c = validate_stub; // Only type-based checking
|
||||
|
||||
conf->ntp.rtc.utc.k = "ntp.rtc.utc";
|
||||
conf->ntp.rtc.utc.h = "Should the RTC be set to UTC?";
|
||||
conf->ntp.rtc.utc.t = CONF_BOOL;
|
||||
conf->ntp.rtc.utc.d.b = true;
|
||||
conf->ntp.rtc.utc.c = validate_stub; // Only type-based checking
|
||||
conf->ntp.sync.rtc.utc.k = "ntp.sync.rtc.utc";
|
||||
conf->ntp.sync.rtc.utc.h = "Should the RTC be set to UTC?";
|
||||
conf->ntp.sync.rtc.utc.t = CONF_BOOL;
|
||||
conf->ntp.sync.rtc.utc.d.b = true;
|
||||
conf->ntp.sync.rtc.utc.c = validate_stub; // Only type-based checking
|
||||
|
||||
|
||||
// struct resolver
|
||||
|
||||
+6
-5
@@ -201,15 +201,16 @@ struct config {
|
||||
struct conf_item address;
|
||||
} ipv6;
|
||||
struct {
|
||||
struct conf_item active;
|
||||
struct conf_item server;
|
||||
struct conf_item interval;
|
||||
struct conf_item count;
|
||||
struct {
|
||||
struct conf_item set;
|
||||
struct conf_item device;
|
||||
struct conf_item utc;
|
||||
} rtc;
|
||||
} sync;
|
||||
struct {
|
||||
struct conf_item set;
|
||||
struct conf_item device;
|
||||
struct conf_item utc;
|
||||
} rtc;
|
||||
} ntp;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -732,6 +732,12 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
{
|
||||
log_warn("New dnsmasq configuration is not valid (%s), config remains unchanged", errbuf);
|
||||
|
||||
if(debug_flags[DEBUG_ANY])
|
||||
{
|
||||
log_debug(DEBUG_ANY, "Temporary dnsmasq config file left in place for debugging purposes");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove temporary config file
|
||||
if(remove(DNSMASQ_TEMP_CONF) != 0)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <limits.h>
|
||||
// escape_json()
|
||||
#include "webserver/http-common.h"
|
||||
// chown_pihole()
|
||||
#include "files.h"
|
||||
|
||||
// Open the TOML file for reading or writing
|
||||
FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *mode, const unsigned int version)
|
||||
@@ -96,26 +98,8 @@ void closeFTLtoml(FILE *fp)
|
||||
|
||||
// Chown file if we are root
|
||||
if(geteuid() == 0)
|
||||
{
|
||||
// Get UID and GID of user with name "pihole"
|
||||
struct passwd *pwd = getpwnam("pihole");
|
||||
if(pwd == NULL)
|
||||
{
|
||||
log_warn("Cannot get UID and GID of user pihole: %s", strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
const uid_t pihole_uid = pwd->pw_uid;
|
||||
const gid_t pihole_gid = pwd->pw_gid;
|
||||
// Chown file to pihole user
|
||||
if(chown(GLOBALTOMLPATH, pihole_uid, pihole_gid) != 0)
|
||||
log_warn("Cannot chown "GLOBALTOMLPATH" to pihole:pihole (%u:%u): %s",
|
||||
(unsigned int)pihole_uid, (unsigned int)pihole_gid, strerror(errno));
|
||||
else
|
||||
log_debug(DEBUG_CONFIG, "Chown-ed "GLOBALTOMLPATH" to pihole:pihole (%u:%u)",
|
||||
(unsigned int)pihole_uid, (unsigned int)pihole_gid);
|
||||
}
|
||||
}
|
||||
chown_pihole(GLOBALTOMLPATH, NULL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+26
-7
@@ -330,13 +330,32 @@ void set_nice(void)
|
||||
// Set nice value
|
||||
const int ret = setpriority(which, pid, config.misc.nice.v.i);
|
||||
if(ret == -1)
|
||||
// ERROR EPERM: The calling process attempted to increase its priority
|
||||
// by supplying a negative value but has insufficient privileges.
|
||||
// On Linux, the RLIMIT_NICE resource limit can be used to define a limit to
|
||||
// which an unprivileged process's nice value can be raised. We are not
|
||||
// affected by this limit when pihole-FTL is running with CAP_SYS_NICE
|
||||
log_warn("Cannot set process priority to %d: %s. Process priority remains at %d",
|
||||
config.misc.nice.v.i, strerror(errno), priority);
|
||||
{
|
||||
if(errno == EACCES || errno == EPERM)
|
||||
{
|
||||
// from man 2 setpriority:
|
||||
//
|
||||
// ERRORS
|
||||
// [...]
|
||||
// EACCES The caller attempted to set a lower nice value (i.e., a higher
|
||||
// process priority), but did not have the required privilege (on
|
||||
// Linux: did not have the CAP_SYS_NICE capability).
|
||||
//
|
||||
// EPERM A process was located, but its effective user ID did not match
|
||||
// either the effective or the real user ID of the caller, and was
|
||||
// not privileged (on Linux: did not have the CAP_SYS_NICE capabil‐
|
||||
// ity).
|
||||
// [...]
|
||||
log_warn("Insufficient permissions to set process priority to %d (CAP_SYS_NICE required), process priority remains at %d",
|
||||
config.misc.nice.v.i, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other error
|
||||
log_warn("Cannot set process priority to %d: %s. Process priority remains at %d",
|
||||
config.misc.nice.v.i, strerror(errno), priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ static bool analyze_database(sqlite3 *db)
|
||||
void *DB_thread(void *val)
|
||||
{
|
||||
// Set thread name
|
||||
thread_running[DB] = true;
|
||||
prctl(PR_SET_NAME, thread_names[DB], 0, 0, 0);
|
||||
|
||||
// Save timestamp as we do not want to store immediately
|
||||
@@ -241,6 +240,5 @@ void *DB_thread(void *val)
|
||||
dbclose(&db);
|
||||
|
||||
log_info("Terminating database thread");
|
||||
thread_running[DB] = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -293,6 +293,10 @@ static int _add_message(const enum message_type type,
|
||||
static int _add_message(const enum message_type type,
|
||||
const char *message, const size_t count,...)
|
||||
{
|
||||
// Log to database only if not in CLI mode
|
||||
if(cli_mode)
|
||||
return -1;
|
||||
|
||||
int rowid = -1;
|
||||
// Return early if database is known to be broken
|
||||
if(FTLDBerror())
|
||||
@@ -1237,10 +1241,6 @@ void logg_regex_warning(const char *type, const char *warning, const int dbindex
|
||||
// Log to FTL.log
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database only if not in CLI mode
|
||||
if(cli_mode)
|
||||
return;
|
||||
|
||||
// Add to database
|
||||
add_message(REGEX_MESSAGE, regex, type, warning, dbindex);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ static sqlite3_stmt **stmts[] = { &query_stmt,
|
||||
&forward_stmt,
|
||||
&addinfo_stmt };
|
||||
|
||||
// Private prototypes
|
||||
static void load_queries_from_disk(void);
|
||||
|
||||
// Return the maximum ID of the in-memory database
|
||||
unsigned long __attribute__((pure)) get_max_db_idx(void)
|
||||
{
|
||||
@@ -220,6 +223,8 @@ bool init_memory_database(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
load_queries_from_disk();
|
||||
|
||||
// Everything went well
|
||||
return true;
|
||||
}
|
||||
@@ -1635,7 +1640,7 @@ bool queries_to_database(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_queries_from_disk(void)
|
||||
static void load_queries_from_disk(void)
|
||||
{
|
||||
// Compensate for possible jumps in time
|
||||
runGC(time(NULL), NULL, false);
|
||||
|
||||
@@ -119,7 +119,6 @@ bool add_additional_info_column(sqlite3 *db);
|
||||
void DB_read_queries(void);
|
||||
void update_disk_db_idx(void);
|
||||
bool queries_to_database(void);
|
||||
void load_queries_from_disk(void);
|
||||
|
||||
bool optimize_queries_table(sqlite3 *db);
|
||||
bool create_addinfo_table(sqlite3 *db);
|
||||
|
||||
@@ -1193,10 +1193,10 @@ static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int
|
||||
return _("error");
|
||||
}
|
||||
|
||||
if (sdetails.orig_hostinfo)
|
||||
freeaddrinfo(sdetails.orig_hostinfo);
|
||||
}
|
||||
}
|
||||
if (sdetails.orig_hostinfo)
|
||||
freeaddrinfo(sdetails.orig_hostinfo);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -1280,11 +1280,10 @@ static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, in
|
||||
if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
|
||||
return _("error");
|
||||
}
|
||||
|
||||
if (sdetails.orig_hostinfo)
|
||||
freeaddrinfo(sdetails.orig_hostinfo);
|
||||
}
|
||||
}
|
||||
if (sdetails.orig_hostinfo)
|
||||
freeaddrinfo(sdetails.orig_hostinfo);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+9
-30
@@ -1803,7 +1803,7 @@ void FTL_dnsmasq_reload(void)
|
||||
// This function is called by the dnsmasq code on receive of SIGHUP
|
||||
// *before* clearing the cache and re-reading the lists
|
||||
if(reload++ > 0)
|
||||
log_info("Received SIGHUP, flushing cache and re-reading config");
|
||||
log_info("Flushing cache and re-reading config");
|
||||
|
||||
// Gravity database updates
|
||||
// - (Re-)open gravity database connection
|
||||
@@ -2912,17 +2912,12 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
|
||||
// so they will not listen to real-time signals
|
||||
handle_realtime_signals();
|
||||
|
||||
// We will use the attributes object later to start all threads in
|
||||
// detached mode
|
||||
pthread_attr_t attr;
|
||||
// Initialize thread attributes object with default attribute values
|
||||
// Do NOT detach threads as we want to join them during shutdown with a
|
||||
// fixed timeout to give them time to clean up and finish their work
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
|
||||
// Initialize NTP server
|
||||
ntp_server_start(&attr);
|
||||
|
||||
// Start NTP sync thread
|
||||
ntp_start_sync_thread(&attr);
|
||||
|
||||
@@ -2968,26 +2963,16 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
|
||||
// we're actually dropping root (user/group may be set to root)
|
||||
if(ent_pw != NULL && ent_pw->pw_uid != 0)
|
||||
{
|
||||
log_info("FTL is going to drop from root to user %s (UID %u)",
|
||||
ent_pw->pw_name, ent_pw->pw_uid);
|
||||
log_info("FTL is going to drop from root to user pihole");
|
||||
|
||||
// Change ownership of shared memory objects
|
||||
chown_all_shmem(ent_pw);
|
||||
|
||||
// Configured FTL log file
|
||||
if(chown(config.files.log.ftl.v.s, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
|
||||
{
|
||||
log_warn("Setting ownership (%u:%u) of %s failed: %s (%i)",
|
||||
ent_pw->pw_uid, ent_pw->pw_gid, config.files.log.ftl.v.s, strerror(errno), errno);
|
||||
}
|
||||
chown_pihole(config.files.log.ftl.v.s, ent_pw);
|
||||
|
||||
// Configured FTL database file
|
||||
if(chown(config.files.database.v.s, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
|
||||
{
|
||||
log_warn("Setting ownership (%u:%u) of %s failed: %s (%i)",
|
||||
ent_pw->pw_uid, ent_pw->pw_gid, config.files.database.v.s, strerror(errno), errno);
|
||||
|
||||
}
|
||||
chown_pihole(config.files.database.v.s, ent_pw);
|
||||
|
||||
// Check if auxiliary files exist and change ownership
|
||||
char *extrafile = calloc(strlen(config.files.database.v.s) + 5, sizeof(char));
|
||||
@@ -3000,20 +2985,14 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
|
||||
// Check <database>-wal file (write-ahead log)
|
||||
strcpy(extrafile, config.files.database.v.s);
|
||||
strcat(extrafile, "-wal");
|
||||
if(file_exists(extrafile) && chown(extrafile, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
|
||||
{
|
||||
log_warn("Setting ownership (%u:%u) of %s failed: %s (%i)",
|
||||
ent_pw->pw_uid, ent_pw->pw_gid, extrafile, strerror(errno), errno);
|
||||
}
|
||||
if(file_exists(extrafile))
|
||||
chown_pihole(extrafile, ent_pw);
|
||||
|
||||
// Check <database>-shm file (mmapped shared memory)
|
||||
strcpy(extrafile, config.files.database.v.s);
|
||||
strcat(extrafile, "-shm");
|
||||
if(file_exists(extrafile) && chown(extrafile, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
|
||||
{
|
||||
log_warn("Setting ownership (%u:%u) of %s failed: %s (%i)",
|
||||
ent_pw->pw_uid, ent_pw->pw_gid, extrafile, strerror(errno), errno);
|
||||
}
|
||||
if(file_exists(extrafile))
|
||||
chown_pihole(extrafile, ent_pw);
|
||||
|
||||
// Free allocated memory
|
||||
free(extrafile);
|
||||
|
||||
+3
-1
@@ -250,7 +250,9 @@ enum thread_types {
|
||||
GC,
|
||||
DNSclient,
|
||||
TIMER,
|
||||
NTP,
|
||||
NTP_CLIENT,
|
||||
NTP_SERVER4,
|
||||
NTP_SERVER6,
|
||||
THREADS_MAX
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
||||
+22
-18
@@ -16,8 +16,6 @@
|
||||
|
||||
// opendir(), readdir()
|
||||
#include <dirent.h>
|
||||
// getpwuid()
|
||||
#include <pwd.h>
|
||||
// getgrgid()
|
||||
#include <grp.h>
|
||||
// NAME_MAX
|
||||
@@ -434,29 +432,35 @@ static int copy_file(const char *source, const char *destination)
|
||||
}
|
||||
|
||||
// Change ownership of file to pihole user
|
||||
static bool chown_pihole(const char *path)
|
||||
bool chown_pihole(const char *path, struct passwd *pwd)
|
||||
{
|
||||
// Get pihole user's uid and gid
|
||||
struct passwd *pwd = getpwnam("pihole");
|
||||
// Get pihole user's UID and GID if not provided
|
||||
if(pwd == NULL)
|
||||
{
|
||||
log_warn("chown_pihole(): Failed to get pihole user's uid: %s", strerror(errno));
|
||||
return false;
|
||||
pwd = getpwnam("pihole");
|
||||
if(pwd == NULL)
|
||||
{
|
||||
log_warn("chown_pihole(): Failed to get pihole user's UID/GID: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
struct group *grp = getgrnam("pihole");
|
||||
if(grp == NULL)
|
||||
|
||||
// Get group name
|
||||
struct group *grp = getgrgid(pwd->pw_gid);
|
||||
const char *grp_name = grp != NULL ? grp->gr_name : "<unknown>";
|
||||
|
||||
// Change ownership of file to pihole user
|
||||
if(chown(path, pwd->pw_uid, pwd->pw_gid) < 0)
|
||||
{
|
||||
log_warn("chown_pihole(): Failed to get pihole user's gid: %s", strerror(errno));
|
||||
log_warn("Failed to change ownership of \"%s\" to %s:%s (%u:%u): %s",
|
||||
path, pwd->pw_name, grp_name, pwd->pw_uid, pwd->pw_gid,
|
||||
errno == EPERM ? "Insufficient permissions (CAP_CHOWN required)" : strerror(errno));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Change ownership of file to pihole user
|
||||
if(chown(path, pwd->pw_uid, grp->gr_gid) < 0)
|
||||
{
|
||||
log_warn("chown_pihole(): Failed to change ownership of \"%s\" to %u:%u: %s",
|
||||
path, pwd->pw_uid, grp->gr_gid, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
log_debug(DEBUG_INOTIFY, "Changed ownership of \"%s\" to %s:%s (%u:%u)",
|
||||
path, pwd->pw_name, grp_name, pwd->pw_uid, pwd->pw_gid);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -533,7 +537,7 @@ void rotate_files(const char *path, char **first_file)
|
||||
}
|
||||
|
||||
// Change ownership of file to pihole user
|
||||
chown_pihole(new_path);
|
||||
chown_pihole(new_path, NULL);
|
||||
}
|
||||
|
||||
// Free memory
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <mntent.h>
|
||||
// SHA256_DIGEST_SIZE
|
||||
#include <nettle/sha2.h>
|
||||
// getpwuid()
|
||||
#include <pwd.h>
|
||||
|
||||
#define MAX_ROTATIONS 15
|
||||
#define BACKUP_DIR "/etc/pihole/config_backups"
|
||||
@@ -31,6 +33,7 @@ void ls_dir(const char* path);
|
||||
unsigned int get_path_usage(const char *path, char buffer[64]);
|
||||
struct mntent *get_filesystem_details(const char *path);
|
||||
bool directory_exists(const char *path);
|
||||
bool chown_pihole(const char *path, struct passwd *pwd);
|
||||
void rotate_files(const char *path, char **first_file);
|
||||
bool files_different(const char *pathA, const char* pathB, unsigned int from);
|
||||
bool sha256sum(const char *path, uint8_t checksum[SHA256_DIGEST_SIZE]);
|
||||
|
||||
@@ -481,7 +481,6 @@ static bool check_files_on_same_device(const char *path1, const char *path2)
|
||||
void *GC_thread(void *val)
|
||||
{
|
||||
// Set thread name
|
||||
thread_running[GC] = true;
|
||||
prctl(PR_SET_NAME, thread_names[GC], 0, 0, 0);
|
||||
|
||||
// Remember when we last ran the actions
|
||||
@@ -567,6 +566,5 @@ void *GC_thread(void *val)
|
||||
watch_config(false);
|
||||
|
||||
log_info("Terminating GC thread");
|
||||
thread_running[GC] = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+56
-17
@@ -39,8 +39,9 @@
|
||||
#include <sys/timex.h>
|
||||
// log_ntp_message()
|
||||
#include "database/message-table.h"
|
||||
// load_queries_from_disk()
|
||||
#include "database/query-table.h"
|
||||
// check_capability()
|
||||
#include "capabilities.h"
|
||||
|
||||
struct ntp_sync
|
||||
{
|
||||
bool valid;
|
||||
@@ -576,7 +577,7 @@ bool ntp_client(const char *server, const bool settime, const bool print)
|
||||
ntp_root_dispersion = D2FP(theta_stdev);
|
||||
|
||||
// Finally, adjust RTC if configured
|
||||
if(config.ntp.rtc.set.v.b)
|
||||
if(config.ntp.sync.rtc.set.v.b)
|
||||
ntp_sync_rtc();
|
||||
}
|
||||
|
||||
@@ -588,32 +589,57 @@ bool ntp_client(const char *server, const bool settime, const bool print)
|
||||
static void *ntp_client_thread(void *arg)
|
||||
{
|
||||
// Set thread name
|
||||
thread_running[NTP] = true;
|
||||
prctl(PR_SET_NAME, thread_names[NTP], 0, 0, 0);
|
||||
prctl(PR_SET_NAME, thread_names[NTP_CLIENT], 0, 0, 0);
|
||||
|
||||
// Run NTP client
|
||||
bool ntp_server_started = false;
|
||||
bool first_run = true;
|
||||
while(!killed)
|
||||
{
|
||||
// Run NTP client
|
||||
ntp_client(config.ntp.sync.server.v.s, true, false);
|
||||
// Get time before NTP sync
|
||||
const double before = double_time();
|
||||
|
||||
// Load queries from database after first NTP synchronization
|
||||
if(first_run)
|
||||
// Run NTP client
|
||||
const bool success = ntp_client(config.ntp.sync.server.v.s, true, false);
|
||||
|
||||
// Get time after NTP sync
|
||||
const double after = double_time();
|
||||
|
||||
// If the time was updated by more than a certain amount,
|
||||
// restart FTL to import recent data. This is relevant when the
|
||||
// system time was set to an incorrect value (e.g., due to a
|
||||
// dead CMOS battery or overall missing RTC) and the time was
|
||||
// off.
|
||||
double time_delta = fabs(after - before);
|
||||
if(first_run && time_delta > GCinterval)
|
||||
{
|
||||
load_queries_from_disk();
|
||||
first_run = false;
|
||||
log_info("System time was updated by %.1f seconds, restarting FTL to import recent data",
|
||||
time_delta);
|
||||
// Set the restart flag to true
|
||||
exit_code = RESTART_FTL_CODE;
|
||||
// Send SIGTERM to FTL
|
||||
kill(main_pid(), SIGTERM);
|
||||
}
|
||||
|
||||
// Set first run to false
|
||||
first_run = false;
|
||||
|
||||
if(success && !ntp_server_started)
|
||||
{
|
||||
// Initialize NTP server only after first NTP
|
||||
// synchronization to ensure that the time is set
|
||||
// correctly
|
||||
ntp_server_started = ntp_server_start();
|
||||
}
|
||||
|
||||
// Intermediate cancellation-point
|
||||
BREAK_IF_KILLED();
|
||||
|
||||
// Sleep before retrying
|
||||
thread_sleepms(NTP, 1000 * config.ntp.sync.interval.v.ui);
|
||||
thread_sleepms(NTP_CLIENT, 1000 * config.ntp.sync.interval.v.ui);
|
||||
}
|
||||
|
||||
log_info("Terminating NTP thread");
|
||||
thread_running[NTP] = false;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -621,19 +647,32 @@ static void *ntp_client_thread(void *arg)
|
||||
bool ntp_start_sync_thread(pthread_attr_t *attr)
|
||||
{
|
||||
// Return early if NTP client is disabled
|
||||
if(config.ntp.sync.server.v.s == NULL ||
|
||||
if(config.ntp.sync.active.v.b == false ||
|
||||
config.ntp.sync.server.v.s == NULL ||
|
||||
strlen(config.ntp.sync.server.v.s) == 0 ||
|
||||
config.ntp.sync.interval.v.ui == 0)
|
||||
{
|
||||
load_queries_from_disk();
|
||||
log_info("NTP sync is disabled");
|
||||
ntp_server_start();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have the ambient capabilities to set the system time.
|
||||
// Without CAP_SYS_TIME, we cannot set the system time and the NTP
|
||||
// client will not be able to synchronize the time so there is no point
|
||||
// in starting the thread.
|
||||
if(!check_capability(CAP_SYS_TIME))
|
||||
{
|
||||
log_warn("Insufficient permissions to set system time (CAP_SYS_TIME required), NTP client not available");
|
||||
ntp_server_start();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create thread
|
||||
if(pthread_create(&threads[NTP], attr, ntp_client_thread, NULL) != 0)
|
||||
if(pthread_create(&threads[NTP_CLIENT], attr, ntp_client_thread, NULL) != 0)
|
||||
{
|
||||
log_err("Cannot create NTP client thread");
|
||||
load_queries_from_disk();
|
||||
ntp_server_start();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ uint64_t gettime64(void);
|
||||
void print_debug_time(const char *label, const uint32_t *u32p, const uint64_t ntp_time);
|
||||
|
||||
// Start NTP server
|
||||
bool ntp_server_start(pthread_attr_t *attr);
|
||||
bool ntp_server_start(void);
|
||||
|
||||
// Start NTP client
|
||||
bool ntp_client(const char *server, const bool settime, const bool print);
|
||||
|
||||
+20
-16
@@ -51,15 +51,15 @@ static int open_rtc(void)
|
||||
const gid_t gid = getgid();
|
||||
|
||||
// If the user has specified an RTC device, try to open it
|
||||
if(config.ntp.rtc.device.v.s != NULL &&
|
||||
strlen(config.ntp.rtc.device.v.s) > 0)
|
||||
if(config.ntp.sync.rtc.device.v.s != NULL &&
|
||||
strlen(config.ntp.sync.rtc.device.v.s) > 0)
|
||||
{
|
||||
// Open the RTC device
|
||||
rtc_fd = open(config.ntp.rtc.device.v.s, O_RDONLY);
|
||||
rtc_fd = open(config.ntp.sync.rtc.device.v.s, O_RDONLY);
|
||||
if (rtc_fd != -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "Successfully opened RTC at \"%s\"",
|
||||
config.ntp.rtc.device.v.s);
|
||||
config.ntp.sync.rtc.device.v.s);
|
||||
return rtc_fd;
|
||||
}
|
||||
|
||||
@@ -72,32 +72,34 @@ static int open_rtc(void)
|
||||
{
|
||||
// Get current owner of the device
|
||||
struct stat st = { 0 };
|
||||
if(stat(config.ntp.rtc.device.v.s, &st) == -1)
|
||||
if(stat(config.ntp.sync.rtc.device.v.s, &st) == -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "stat(\"%s\") failed: %s",
|
||||
config.ntp.rtc.device.v.s, strerror(errno));
|
||||
config.ntp.sync.rtc.device.v.s, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(chown(config.ntp.rtc.device.v.s, uid, gid) == -1)
|
||||
if(chown(config.ntp.sync.rtc.device.v.s, uid, gid) == -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "chown(\"%s\", %u, %u) failed: %s",
|
||||
config.ntp.rtc.device.v.s, uid, gid, strerror(errno));
|
||||
config.ntp.sync.rtc.device.v.s, uid, gid,
|
||||
errno == EPERM ? "Insufficient permissions (CAP_CHOWN required)" : strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rtc_fd = open(config.ntp.rtc.device.v.s, O_RDONLY);
|
||||
rtc_fd = open(config.ntp.sync.rtc.device.v.s, O_RDONLY);
|
||||
if (rtc_fd != -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "Successfully opened RTC at \"%s\"",
|
||||
config.ntp.rtc.device.v.s);
|
||||
config.ntp.sync.rtc.device.v.s);
|
||||
}
|
||||
|
||||
// Chown the device back to the original owner
|
||||
if(chown(config.ntp.rtc.device.v.s, st.st_uid, st.st_gid) == -1)
|
||||
if(chown(config.ntp.sync.rtc.device.v.s, st.st_uid, st.st_gid) == -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "chown(\"%s\", %u, %u) failed: %s",
|
||||
config.ntp.rtc.device.v.s, st.st_uid, st.st_gid, strerror(errno));
|
||||
config.ntp.sync.rtc.device.v.s, st.st_uid, st.st_gid,
|
||||
errno == EPERM ? "Insufficient permissions (CAP_CHOWN required)" : strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -106,7 +108,7 @@ static int open_rtc(void)
|
||||
}
|
||||
|
||||
log_debug(DEBUG_NTP, "Failed to open RTC at \"%s\": %s",
|
||||
config.ntp.rtc.device.v.s, strerror(errno));
|
||||
config.ntp.sync.rtc.device.v.s, strerror(errno));
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -139,7 +141,8 @@ static int open_rtc(void)
|
||||
if(chown(rtc_devices[i], uid, gid) == -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "chown(\"%s\", %u, %u) failed: %s",
|
||||
rtc_devices[i], uid, gid, strerror(errno));
|
||||
rtc_devices[i], uid, gid,
|
||||
errno == EPERM ? "Insufficient permissions (CAP_CHOWN required)" : strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -154,7 +157,8 @@ static int open_rtc(void)
|
||||
if(chown(rtc_devices[i], st.st_uid, st.st_gid) == -1)
|
||||
{
|
||||
log_debug(DEBUG_NTP, "chown(\"%s\", %u, %u) failed: %s",
|
||||
rtc_devices[i], st.st_uid, st.st_gid, strerror(errno));
|
||||
rtc_devices[i], st.st_uid, st.st_gid,
|
||||
errno == EPERM ? "Insufficient permissions (CAP_CHOWN required)" : strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -255,7 +259,7 @@ bool ntp_sync_rtc(void)
|
||||
// Time to which we will set Hardware Clock, in broken down format
|
||||
struct tm new_time = { 0 };
|
||||
const time_t newtime = time(NULL);
|
||||
if(config.ntp.rtc.utc.v.b)
|
||||
if(config.ntp.sync.rtc.utc.v.b)
|
||||
// UTC
|
||||
gmtime_r(&newtime, &new_time);
|
||||
else
|
||||
|
||||
+14
-9
@@ -39,6 +39,10 @@
|
||||
#include <inttypes.h>
|
||||
// log_ntp_message()
|
||||
#include "database/message-table.h"
|
||||
// NTP_SERVER_IPV4,6
|
||||
#include "enums.h"
|
||||
// threads
|
||||
#include "signals.h"
|
||||
|
||||
uint64_t ntp_last_sync = 0u;
|
||||
uint32_t ntp_root_delay = 0u;
|
||||
@@ -224,7 +228,7 @@ static bool ntp_reply(const int socket_fd, const struct sockaddr *saddr_p, const
|
||||
}
|
||||
|
||||
// Process incoming NTP requests
|
||||
static void request_process_loop(int fd, const char *ipstr, const int protocol)
|
||||
static void request_process_loop(const int fd, const char *ipstr, const int protocol)
|
||||
{
|
||||
log_info("NTP server listening on %s:123 (%s)", ipstr, protocol == AF_INET ? "IPv4" : "IPv6");
|
||||
while (true)
|
||||
@@ -280,9 +284,12 @@ static void request_process_loop(int fd, const char *ipstr, const int protocol)
|
||||
// Start the NTP server
|
||||
static void *ntp_bind_and_listen(void *param)
|
||||
{
|
||||
const int protocol = param == 0 ? AF_INET : AF_INET6;
|
||||
// Set thread name
|
||||
const unsigned int thread_id = param == 0 ? NTP_SERVER4 : NTP_SERVER6;
|
||||
prctl(PR_SET_NAME, thread_names[thread_id], 0, 0, 0);
|
||||
|
||||
// Create a socket
|
||||
const int protocol = param == 0 ? AF_INET : AF_INET6;
|
||||
errno = 0;
|
||||
const int s = socket(protocol, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(s == -1)
|
||||
@@ -301,8 +308,7 @@ static void *ntp_bind_and_listen(void *param)
|
||||
memset(ipstr, 0, sizeof(ipstr));
|
||||
if(protocol == AF_INET)
|
||||
{
|
||||
// IPv4 - set thread name
|
||||
prctl(PR_SET_NAME, "NTP (IPv4)", 0, 0, 0);
|
||||
// IPv4 NTP server
|
||||
|
||||
// Prepare the bind address
|
||||
struct sockaddr_in bind_addr;
|
||||
@@ -327,8 +333,7 @@ static void *ntp_bind_and_listen(void *param)
|
||||
}
|
||||
else
|
||||
{
|
||||
// IPv6 - set thread name
|
||||
prctl(PR_SET_NAME, "NTP (IPv6)", 0, 0, 0);
|
||||
// IPv6 NTP server
|
||||
|
||||
// Set socket options to allow IPv6 only, otherwise it will bind
|
||||
// to both IPv4 and IPv6 and show IPv4 addresses as
|
||||
@@ -373,7 +378,7 @@ static void *ntp_bind_and_listen(void *param)
|
||||
}
|
||||
|
||||
// Start the NTP server
|
||||
bool ntp_server_start(pthread_attr_t *attr)
|
||||
bool ntp_server_start(void)
|
||||
{
|
||||
// Spawn two pthreads, one for IPv4 and one for IPv6
|
||||
|
||||
@@ -382,7 +387,7 @@ bool ntp_server_start(pthread_attr_t *attr)
|
||||
{
|
||||
// Create a thread for the IPv4 NTP server
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, attr, ntp_bind_and_listen, (void *)0) != 0)
|
||||
if (pthread_create(&thread, NULL, ntp_bind_and_listen, (void *)0) != 0)
|
||||
{
|
||||
log_ntp_message(true, true, "Cannot create NTP server thread for IPv4");
|
||||
return false;
|
||||
@@ -394,7 +399,7 @@ bool ntp_server_start(pthread_attr_t *attr)
|
||||
{
|
||||
// Create a thread for the IPv6 NTP server
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, attr, ntp_bind_and_listen, (void *)1) != 0)
|
||||
if (pthread_create(&thread, NULL, ntp_bind_and_listen, (void *)1) != 0)
|
||||
{
|
||||
log_ntp_message(true, true, "Cannot create NTP server thread for IPv6");
|
||||
return false;
|
||||
|
||||
@@ -1053,14 +1053,12 @@ static void resolveUpstreams(const bool onlynew)
|
||||
void *DNSclient_thread(void *val)
|
||||
{
|
||||
// Set thread name
|
||||
thread_running[DNSclient] = true;
|
||||
prctl(PR_SET_NAME, thread_names[DNSclient], 0, 0, 0);
|
||||
|
||||
// Test struct sizes
|
||||
if(!check_struct_sizes())
|
||||
{
|
||||
log_err("Struct sizes do not match expected sizes, aborting resolver thread");
|
||||
thread_running[DNSclient] = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1124,6 +1122,5 @@ void *DNSclient_thread(void *val)
|
||||
}
|
||||
|
||||
log_info("Terminating resolver thread");
|
||||
thread_running[DNSclient] = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+7
-4
@@ -190,17 +190,20 @@ static bool chown_shmem(SharedMemory *sharedMemory, struct passwd *ent_pw)
|
||||
// Open shared memory object
|
||||
const int fd = shm_open(sharedMemory->name, O_RDWR, S_IRUSR | S_IWUSR);
|
||||
log_debug(DEBUG_SHMEM, "Changing %s (%d) to %u:%u", sharedMemory->name, fd, ent_pw->pw_uid, ent_pw->pw_gid);
|
||||
|
||||
if(fd == -1)
|
||||
{
|
||||
log_crit("chown_shmem(): Failed to open shared memory object \"%s\": %s",
|
||||
log_crit("Failed to open shared memory object \"%s\" for chown: %s",
|
||||
sharedMemory->name, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(fchown(fd, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
|
||||
{
|
||||
log_warn("chown_shmem(%d, %u, %u): failed for %s: %s (%d)",
|
||||
fd, ent_pw->pw_uid, ent_pw->pw_gid, sharedMemory->name,
|
||||
strerror(errno), errno);
|
||||
log_crit("Failed to change ownership of shared memory object \"%s\": %s",
|
||||
sharedMemory->name,
|
||||
errno == EPERM ? "Insufficient permissions (CAP_CHOWN required)" : strerror(errno));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -34,13 +34,14 @@ static time_t FTLstarttime = 0;
|
||||
volatile int exit_code = EXIT_SUCCESS;
|
||||
|
||||
volatile sig_atomic_t thread_cancellable[THREADS_MAX] = { false };
|
||||
volatile sig_atomic_t thread_running[THREADS_MAX] = { false };
|
||||
const char * const thread_names[THREADS_MAX] = {
|
||||
"database",
|
||||
"housekeeper",
|
||||
"dns-client",
|
||||
"timer",
|
||||
"ntp-client"
|
||||
"ntp-client",
|
||||
"ntp-server4",
|
||||
"ntp-server6",
|
||||
};
|
||||
|
||||
// Return the (null-terminated) name of the calling thread
|
||||
|
||||
@@ -29,7 +29,6 @@ extern volatile sig_atomic_t want_to_reimport_aliasclients;
|
||||
extern volatile sig_atomic_t want_to_reload_lists;
|
||||
|
||||
extern volatile sig_atomic_t thread_cancellable[THREADS_MAX];
|
||||
extern volatile sig_atomic_t thread_running[THREADS_MAX];
|
||||
extern const char * const thread_names[THREADS_MAX];
|
||||
|
||||
#define BREAK_IF_KILLED() { if(killed) break; }
|
||||
|
||||
@@ -84,7 +84,6 @@ void get_blockingmode_timer(double *delay, bool *target_status)
|
||||
void *timer(void *val)
|
||||
{
|
||||
// Set thread name
|
||||
thread_running[GC] = true;
|
||||
prctl(PR_SET_NAME, thread_names[TIMER], 0, 0, 0);
|
||||
|
||||
// Save timestamp as we do not want to store immediately
|
||||
@@ -110,7 +109,6 @@ void *timer(void *val)
|
||||
}
|
||||
|
||||
log_info("Terminating timer thread");
|
||||
thread_running[GC] = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -616,8 +616,6 @@ int run_arp_scan(const bool scan_all, const bool extreme_mode)
|
||||
pthread_attr_t attr;
|
||||
// Initialize thread attributes object with default attribute values
|
||||
pthread_attr_init(&attr);
|
||||
// Set thread attributes to detached mode
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
|
||||
struct ifaddrs *addrs, *tmp;
|
||||
getifaddrs(&addrs);
|
||||
|
||||
@@ -725,8 +725,6 @@ int run_dhcp_discover(void)
|
||||
pthread_attr_t attr;
|
||||
// Initialize thread attributes object with default attribute values
|
||||
pthread_attr_init(&attr);
|
||||
// Set thread attributes to detached mode
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
|
||||
// Create processing/printfing lock
|
||||
pthread_mutexattr_t lock_attr;
|
||||
|
||||
@@ -662,3 +662,46 @@ char *__attribute__((malloc)) escape_json(const char *string)
|
||||
// Return the JSON escaped string
|
||||
return namep;
|
||||
}
|
||||
|
||||
// Remove duplicates from a cJSON array
|
||||
// This function uses the less efficient cJSON_GetArraySize() function compared
|
||||
// to cJSON_ArrayForEach() as we are going to modify the array in-place while
|
||||
// iterating over it
|
||||
void cJSON_unique_array(cJSON *array)
|
||||
{
|
||||
// Check if the array is an array
|
||||
if(!cJSON_IsArray(array))
|
||||
return;
|
||||
|
||||
for(int oi = 0; oi < cJSON_GetArraySize(array); oi++)
|
||||
{
|
||||
// Get the outer item
|
||||
cJSON *outer_item = cJSON_GetArrayItem(array, oi);
|
||||
// Check if the item is a string
|
||||
if (!cJSON_IsString(outer_item))
|
||||
continue;
|
||||
|
||||
// Check for duplicates in the remainder of the array
|
||||
for(int ii = oi + 1; ii < cJSON_GetArraySize(array); ii++)
|
||||
{
|
||||
// Get the inner item
|
||||
cJSON *inner_item = cJSON_GetArrayItem(array, ii);
|
||||
// Check if the inner item is a string
|
||||
if (!cJSON_IsString(inner_item))
|
||||
continue;
|
||||
|
||||
// Compare the two strings
|
||||
if(strcmp(outer_item->valuestring, inner_item->valuestring) == 0)
|
||||
{
|
||||
// Remove the duplicate item, this is safe as we are
|
||||
// at least one item ahead of the outer item
|
||||
cJSON_DeleteItemFromArray(array, ii);
|
||||
// Compensate for removed item (the for loop
|
||||
// will increment ii for the next step, thus
|
||||
// we need to decrement it here)
|
||||
ii--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,5 +103,6 @@ char * __attribute__((malloc)) escape_html(const char *string);
|
||||
int check_json_payload(struct ftl_conn *api);
|
||||
int parse_groupIDs(struct ftl_conn *api, tablerow *table, cJSON *row);
|
||||
char * __attribute__((malloc)) escape_json(const char *string);
|
||||
void cJSON_unique_array(cJSON *array);
|
||||
|
||||
#endif // HTTP_H
|
||||
|
||||
@@ -57,6 +57,29 @@
|
||||
cJSON_AddItemToObject(object, key, string_item); \
|
||||
})
|
||||
|
||||
// Hand over allocated string to cJSON - it will thereafter take care of freeing
|
||||
// it when the cJSON object is deleted
|
||||
#define JSON_GIVE_STR_TO_OBJECT(object, key, string)({ \
|
||||
cJSON *string_item = NULL; \
|
||||
if(string != NULL) \
|
||||
{ \
|
||||
string_item = cJSON_CreateStringReference((const char*)(string)); \
|
||||
string_item->type &= ~cJSON_IsReference; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
string_item = cJSON_CreateNull(); \
|
||||
} \
|
||||
if(string_item == NULL) \
|
||||
{ \
|
||||
cJSON_Delete(object); \
|
||||
send_http_internal_error(api); \
|
||||
log_err("JSON_GIVE_STR_TO_OBJECT FAILED (key: \"%s\", string: \"%s\")!", key, string); \
|
||||
return 500; \
|
||||
} \
|
||||
cJSON_AddItemToObject(object, key, string_item); \
|
||||
})
|
||||
|
||||
#define JSON_ADD_NUMBER_TO_OBJECT(object, key, num)({ \
|
||||
const double number = num; \
|
||||
if(cJSON_AddNumberToObject(object, key, number) == NULL) \
|
||||
@@ -269,3 +292,6 @@
|
||||
|
||||
#define cJSON_AddNumberToArray(array, num) \
|
||||
cJSON_AddItemToArray(array, cJSON_CreateNumber(num))
|
||||
|
||||
#define cJSON_AddStringToArray(array, string) \
|
||||
cJSON_AddItemToArray(array, cJSON_CreateString(string))
|
||||
|
||||
+14
-11
@@ -481,6 +481,9 @@
|
||||
address = ""
|
||||
|
||||
[ntp.sync]
|
||||
# Should FTL try to synchronize the system time with an upstream NTP server?
|
||||
active = true
|
||||
|
||||
# NTP upstream server to sync with, e.g., "pool.ntp.org". Note that the NTP server
|
||||
# should be located as close as possible to you in order to minimize the time offset
|
||||
# possibly introduced by different routing paths.
|
||||
@@ -495,18 +498,18 @@
|
||||
# Number of NTP syncs to perform and average before updating the system time
|
||||
count = 8
|
||||
|
||||
[ntp.rtc]
|
||||
# Should FTL update a real-time clock (RTC) if available?
|
||||
set = true
|
||||
[ntp.sync.rtc]
|
||||
# Should FTL update a real-time clock (RTC) if available?
|
||||
set = true
|
||||
|
||||
# Path to the RTC device to update. Leave empty for auto-discovery
|
||||
#
|
||||
# Possible values are:
|
||||
# Path to the RTC device, e.g., "/dev/rtc0"
|
||||
device = ""
|
||||
# Path to the RTC device to update. Leave empty for auto-discovery
|
||||
#
|
||||
# Possible values are:
|
||||
# Path to the RTC device, e.g., "/dev/rtc0"
|
||||
device = ""
|
||||
|
||||
# Should the RTC be set to UTC?
|
||||
utc = true
|
||||
# Should the RTC be set to UTC?
|
||||
utc = true
|
||||
|
||||
[resolver]
|
||||
# Should FTL try to resolve IPv4 addresses to hostnames?
|
||||
@@ -1102,7 +1105,7 @@
|
||||
all = true ### CHANGED, default = false
|
||||
|
||||
# Configuration statistics:
|
||||
# 148 total entries out of which 93 entries are default
|
||||
# 149 total entries out of which 94 entries are default
|
||||
# --> 55 entries are modified
|
||||
# 2 entries are forced through environment:
|
||||
# - misc.nice
|
||||
|
||||
@@ -475,7 +475,7 @@
|
||||
}
|
||||
|
||||
@test "No WARNING messages in FTL.log (besides known warnings)" {
|
||||
run bash -c 'grep "WARNING:" /var/log/pihole/FTL.log | grep -v -E "CAP_NET_ADMIN|CAP_NET_RAW|CAP_SYS_NICE|CAP_IPC_LOCK|CAP_CHOWN|CAP_NET_BIND_SERVICE|CAP_SYS_TIME|(Cannot set process priority)|FTLCONF_"'
|
||||
run bash -c 'grep "WARNING:" /var/log/pihole/FTL.log | grep -v -E "CAP_NET_ADMIN|CAP_NET_RAW|CAP_SYS_NICE|CAP_IPC_LOCK|CAP_CHOWN|CAP_NET_BIND_SERVICE|CAP_SYS_TIME|FTLCONF_"'
|
||||
printf "%s\n" "${lines[@]}"
|
||||
[[ "${lines[@]}" == "" ]]
|
||||
}
|
||||
@@ -1347,12 +1347,6 @@
|
||||
[[ ${lines[0]} == '{"error":{"key":"bad_request","message":"Config items set via environment variables cannot be changed via the API","hint":"misc.nice"},"took":'*'}' ]]
|
||||
}
|
||||
|
||||
@test "Check NTP server is broadcasting correct time" {
|
||||
run bash -c './pihole-FTL ntp 127.0.0.1 --dry-run'
|
||||
printf "%s\n" "${lines[@]}"
|
||||
[[ $status == 0 ]]
|
||||
}
|
||||
|
||||
# We cannot easily test IPv6 as it may not be available in docker (CI)
|
||||
|
||||
@test "API domain search: Non-existing domain returns expected JSON" {
|
||||
@@ -1811,3 +1805,11 @@
|
||||
printf "%s\n" "${lines[@]}"
|
||||
[[ ${lines[0]} == "3" ]]
|
||||
}
|
||||
|
||||
@test "Check NTP server is broadcasting correct time" {
|
||||
# Run this test at the very end of the test suite
|
||||
# to ensure the NTP server has been started
|
||||
run bash -c './pihole-FTL ntp 127.0.0.1'
|
||||
printf "%s\n" "${lines[@]}"
|
||||
[[ $status == 0 ]]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user