Add new GET /api/padd endpoint
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:
DL6ER
2024-08-05 19:05:13 +02:00
parent cce2c2107d
commit 23c2607165
17 changed files with 717 additions and 78 deletions
+1
View File
@@ -11,3 +11,4 @@ mmapped
dnsmasq
iif
prefered
padd
+1
View File
@@ -20,6 +20,7 @@ set(sources
dhcp.c
dns.c
network.c
padd.c
history.c
info.c
list.c
+1
View File
@@ -102,6 +102,7 @@ static struct {
{ "/api/action/restartdns", "", api_action_restartDNS, { API_PARSE_JSON, 0 }, true, HTTP_POST },
{ "/api/action/flush/logs", "", api_action_flush_logs, { API_PARSE_JSON, 0 }, true, HTTP_POST },
{ "/api/action/flush/arp", "", api_action_flush_arp, { API_PARSE_JSON, 0 }, true, HTTP_POST },
{ "/api/padd", "", api_padd, { API_PARSE_JSON, 0 }, true, HTTP_GET },
{ "/api/docs", "", api_docs, { API_PARSE_JSON, 0 }, false, HTTP_GET },
};
+14 -1
View File
@@ -17,6 +17,8 @@
#include "webserver/http-common.h"
// regex_t
#include "regex_r.h"
// enum conf_type
#include "config/config.h"
// Common definitions
#define LOCALHOSTv4 "127.0.0.1"
@@ -27,6 +29,7 @@ int api_handler(struct mg_connection *conn, void *ignored);
// Statistic methods
int __attribute__((pure)) cmpdesc(const void *a, const void *b);
unsigned int get_active_clients(void);
int api_stats_summary(struct ftl_conn *api);
int api_stats_query_types(struct ftl_conn *api);
int api_stats_upstreams(struct ftl_conn *api);
@@ -37,7 +40,7 @@ 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);
const bool names_only, const bool ip_if_no_name);
cJSON *get_top_upstreams(struct ftl_conn *api, const bool upstreams_only);
// History methods
@@ -71,9 +74,15 @@ int api_info_messages_count(struct ftl_conn *api);
int api_info_messages(struct ftl_conn *api);
int api_info_metrics(struct ftl_conn *api);
int api_info_login(struct ftl_conn *api);
cJSON *read_sys_property(const char *path);
int get_system_obj(struct ftl_conn *api, cJSON *system);
int get_sensors_obj(struct ftl_conn *api, cJSON *sensors, const bool add_list);
int get_version_obj(struct ftl_conn *api, cJSON *version);
// Config methods
int api_config(struct ftl_conn *api);
int get_json_config(struct ftl_conn *api, cJSON *json, const bool detailed);
cJSON *addJSONConfValue(const enum conf_type conf_type, union conf_value *val);
// Log methods
int api_logs(struct ftl_conn *api);
@@ -84,6 +93,7 @@ int api_network_routes(struct ftl_conn *api);
int api_network_interfaces(struct ftl_conn *api);
int api_network_devices(struct ftl_conn *api);
int api_client_suggestions(struct ftl_conn *api);
int get_gateway(struct ftl_conn *api, cJSON * json, const bool detailed);
// DNS methods
int api_dns_blocking(struct ftl_conn *api);
@@ -132,4 +142,7 @@ int api_search(struct ftl_conn *api);
int api_dhcp_leases_GET(struct ftl_conn *api);
int api_dhcp_leases_DELETE(struct ftl_conn *api);
// PADD methods
int api_padd(struct ftl_conn *api);
#endif // ROUTES_H
+24 -16
View File
@@ -92,7 +92,7 @@ static cJSON *get_or_create_object(cJSON *parent, const char *path_element)
// This function is used to add a property to the JSON output using the
// appropriate type of the config item to add.
static cJSON *addJSONvalue(const enum conf_type conf_type, union conf_value *val)
cJSON *addJSONConfValue(const enum conf_type conf_type, union conf_value *val)
{
switch(conf_type)
{
@@ -464,17 +464,9 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
return NULL;
}
static int api_config_get(struct ftl_conn *api)
int get_json_config(struct ftl_conn *api, cJSON *json, const bool detailed)
{
// Parse query string parameters
bool detailed = false;
if(api->request->query_string != NULL)
{
// Check if we should return detailed config information
get_bool_var(api->request->query_string, "detailed", &detailed);
}
// Create root JSON object
// Create root config object
cJSON *config_j = JSON_NEW_OBJECT();
// Does the user request only a subset of /config?
@@ -552,7 +544,7 @@ static int api_config_get(struct ftl_conn *api)
else
{
// Add current value
cJSON *val = addJSONvalue(conf_item->t, &conf_item->v);
cJSON *val = addJSONConfValue(conf_item->t, &conf_item->v);
if(val == NULL)
{
log_warn("Cannot format config item type %s of type %i",
@@ -563,7 +555,7 @@ static int api_config_get(struct ftl_conn *api)
}
// Add default value
cJSON *dval = addJSONvalue(conf_item->t, &conf_item->d);
cJSON *dval = addJSONConfValue(conf_item->t, &conf_item->d);
if(dval == NULL)
{
log_warn("Cannot format config item type %s of type %i",
@@ -592,7 +584,7 @@ static int api_config_get(struct ftl_conn *api)
else
{
// Create the config item leaf object
cJSON *leaf = addJSONvalue(conf_item->t, &conf_item->v);
cJSON *leaf = addJSONConfValue(conf_item->t, &conf_item->v);
if(leaf == NULL)
{
log_warn("Cannot format config item type %s of type %i",
@@ -607,8 +599,6 @@ static int api_config_get(struct ftl_conn *api)
// Release allocated memory
free_config_path(requested_path);
cJSON *json = JSON_NEW_OBJECT();
// Add topics and DNS server suggestions if in detailed mode
if(detailed)
{
@@ -650,6 +640,24 @@ static int api_config_get(struct ftl_conn *api)
// Build and return JSON response
JSON_ADD_ITEM_TO_OBJECT(json, "config", config_j);
return 0;
}
static int api_config_get(struct ftl_conn *api)
{
// Parse query string parameters
bool detailed = false;
if(api->request->query_string != NULL)
{
// Check if we should return detailed config information
get_bool_var(api->request->query_string, "detailed", &detailed);
}
cJSON *json = JSON_NEW_OBJECT();
get_json_config(api, json, detailed);
// Build and return JSON response
JSON_SEND_OBJECT(json);
}
+2 -15
View File
@@ -31,21 +31,8 @@ static int get_blocking(struct ftl_conn *api)
// Return current status
cJSON *json = JSON_NEW_OBJECT();
const enum blocking_status blocking = get_blockingstatus();
switch(blocking)
{
case BLOCKING_ENABLED:
JSON_REF_STR_IN_OBJECT(json, "blocking", "enabled");
break;
case BLOCKING_DISABLED:
JSON_REF_STR_IN_OBJECT(json, "blocking", "disabled");
break;
case DNS_FAILED:
JSON_REF_STR_IN_OBJECT(json, "blocking", "failure");
break;
case BLOCKING_UNKNOWN:
JSON_REF_STR_IN_OBJECT(json, "blocking", "unknown");
break;
}
const char *status = get_blocking_status_str(blocking);
JSON_REF_STR_IN_OBJECT(json, "blocking", status);
// Get timer information (if applicable)
double delay;
+1
View File
@@ -34,6 +34,7 @@ set(sources
hex/specs/logs.yaml
hex/specs/main.yaml
hex/specs/network.yaml
hex/specs/padd.yaml
hex/specs/queries.yaml
hex/specs/search.yaml
hex/specs/stats.yaml
+5
View File
@@ -63,6 +63,8 @@ tags:
description: Methods used to gather advanced information about your network
- name: "Actions"
description: Methods used to trigger certain actions on your Pi-hole
- name: "PADD"
description: Methods used to query Pi-hole from PADD
@@ -274,6 +276,9 @@ paths:
/docs:
$ref: 'docs.yaml#/components/paths/docs'
/padd:
$ref: 'padd.yaml#/components/paths/padd'
components:
securitySchemes:
query_sid:
+248
View File
@@ -0,0 +1,248 @@
openapi: 3.0.2
components:
paths:
padd:
get:
summary: Get summarized data for PADD
tags:
- "PADD"
operationId: "get_padd"
parameters:
- in: query
description: (Optional) Return full data
name: full
schema:
type: boolean
required: false
example: true
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'padd.yaml#/components/schemas/padd'
- $ref: 'info.yaml#/components/schemas/system'
- $ref: 'info.yaml#/components/schemas/version'
- $ref: 'common.yaml#/components/schemas/took'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
schemas:
padd:
type: object
properties:
recent_blocked:
type: string
description: "Most recent blocked domain"
nullable: true
example: "bad.example.com"
top_domain:
type: string
description: "Most requested domain"
nullable: true
example: "good.example.com"
top_blocked:
type: string
description: "Most blocked domain"
nullable: true
example: "bad.example.com"
top_client:
type: string
description: "Most active client"
nullable: true
example: "localhost"
active_clients:
type: integer
description: "Number of active clients"
example: 22
gravity_size:
type: integer
description: "Gravity list size"
example: 225382
blocking:
type: string
description: "Blocking status"
example: "enabled"
queries:
type: object
properties:
total:
type: integer
description: "Total number of queries within the last 24 hours"
example: 92258
blocked:
type: integer
description: "Number of blocked queries"
example: 4784
percent_blocked:
type: number
description: "Percentage of blocked queries"
example: 5.18
cache:
type: object
properties:
size:
type: integer
description: "Total cache size"
example: 10000
inserted:
type: integer
description: "Number of inserted cache entries"
example: 233
evicted:
type: integer
description: "Number of evicted cache entries"
example: 0
iface:
type: object
description: "Default interfaces"
properties:
v4:
type: object
description: "IPv4 interface"
properties:
addr:
type: string
description: "Primary address"
nullable: true # there may be no IPv4 address
example: "192.168.2.11"
rx_bytes:
type: object
description: "Received bytes"
properties:
value:
type: number
example: 76.46
unit:
type: string
example: "G"
tx_bytes:
type: object
description: "Transmitted bytes"
properties:
value:
type: number
example: 68.58
unit:
type: string
example: "G"
num_addrs:
type: integer
description: "Number of addresses on the interface"
example: 1
name:
type: string
description: "Interface name"
example: "eth0"
gw_addr:
type: string
description: "Gateway address"
nullable: true # there may be no IPv4 gateway
example: "192.168.2.1"
v6:
type: object
description: "IPv6 interface"
properties:
addr:
type: string
description: "Primary address"
nullable: true # there may be no IPv6 address
example: "fe80::b0e4:1b1e:7b7d:5855"
num_addrs:
type: integer
description: "Number of addresses on the interface"
example: 3
name:
type: string
description: "Interface name"
example: "eth0"
gw_addr:
type: string
description: "Gateway address"
nullable: true # there may be no IPv6 gateway
example: "fe80::b0e4:1b1e:7b7d:1b1e"
node_name:
type: string
description: "Pi-hole host's name"
example: "pihole"
host_model:
type: string
description: "Host model"
example: "Raspberry Pi 3 Model B Plus Rev 1.3"
nullable: true
config:
type: object
description: "Pi-hole configuration (excerpt)"
properties:
dhcp_active:
type: boolean
description: "DHCP server status"
example: true
dhcp_start:
type: string
description: "DHCP start address"
example: "192.168.0.1"
dhcp_end:
type: string
description: "DHCP end address"
example: "192.168.0.254"
dhcp_ipv6:
type: boolean
description: "DHCPv6 server status"
example: false
dns_domain:
type: string
description: "DNS domain"
example: "lan"
dns_port:
type: integer
description: "DNS port"
example: 53
dns_num_upstreams:
type: integer
description: "Number of upstream DNS servers"
example: 1
dns_dnssec:
type: boolean
description: "DNSSEC status"
example: true
dns_revServer_active:
type: boolean
description: "Reverse DNS server status"
example: false
"%cpu":
type: number
description: "CPU usage"
example: 0.0
"%mem":
type: number
description: "Memory usage"
example: 1.5
pid:
type: integer
description: "FTL's process ID"
example: 1639
sensors:
type: object
properties:
cpu_temp:
type: number
description: "CPU temperature"
nullable: true
example: 45.0
hot_limit:
type: number
description: "Temperature limit"
example: 80.0
unit:
type: string
description: "Temperature unit"
example: "C"
+5
View File
@@ -132,6 +132,10 @@ static const unsigned char specs_action_yaml[] = {
#include "hex/specs/action.yaml"
};
static const unsigned char specs_padd_yaml[] = {
#include "hex/specs/padd.yaml"
};
struct {
const char *path;
const char *mime_type;
@@ -168,6 +172,7 @@ struct {
{"specs/stats.yaml", "text/plain", (const char*)specs_stats_yaml, sizeof(specs_stats_yaml)},
{"specs/teleporter.yaml", "text/plain", (const char*)specs_teleporter_yaml, sizeof(specs_teleporter_yaml)},
{"specs/action.yaml", "text/plain", (const char*)specs_action_yaml, sizeof(specs_action_yaml)},
{"specs/padd.yaml", "text/plain", (const char*)specs_padd_yaml, sizeof(specs_padd_yaml)},
};
#endif // API_DOCS_H
+26 -9
View File
@@ -157,7 +157,7 @@ int api_info_database(struct ftl_conn *api)
JSON_SEND_OBJECT(json);
}
static int get_system_obj(struct ftl_conn *api, cJSON *system)
int get_system_obj(struct ftl_conn *api, cJSON *system)
{
const int nprocs = get_nprocs();
struct sysinfo info;
@@ -465,7 +465,7 @@ static int get_hwmon_sensors(struct ftl_conn *api, cJSON *sensors)
return 0;
}
static cJSON *read_sys_property(const char *path)
cJSON *read_sys_property(const char *path)
{
if(!file_exists(path))
return cJSON_CreateNull();
@@ -641,16 +641,15 @@ int api_info_host(struct ftl_conn *api)
JSON_SEND_OBJECT(json);
}
int api_info_sensors(struct ftl_conn *api)
int get_sensors_obj(struct ftl_conn *api, cJSON *sensors, const bool add_list)
{
cJSON *sensors = JSON_NEW_OBJECT();
// Get sensors array
cJSON *list = JSON_NEW_ARRAY();
int ret = get_hwmon_sensors(api, list);
if (ret != 0)
return ret;
JSON_ADD_ITEM_TO_OBJECT(sensors, "list", list);
if(add_list)
JSON_ADD_ITEM_TO_OBJECT(sensors, "list", list);
// Loop over available sensors and try to identify the most suitable CPU temperature sensor
int cpu_temp_sensor = -1;
@@ -708,12 +707,25 @@ int api_info_sensors(struct ftl_conn *api)
unit = "K";
JSON_REF_STR_IN_OBJECT(sensors, "unit", unit);
if(!add_list)
cJSON_Delete(list);
return 0;
}
int api_info_sensors(struct ftl_conn *api)
{
cJSON *sensors = JSON_NEW_OBJECT();
int ret = get_sensors_obj(api, sensors, true);
if (ret != 0)
return ret;
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "sensors", sensors);
JSON_SEND_OBJECT(json);
}
int api_info_version(struct ftl_conn *api)
int get_version_obj(struct ftl_conn *api, cJSON *version)
{
char *line = NULL;
size_t len = 0;
@@ -802,8 +814,6 @@ int api_info_version(struct ftl_conn *api)
JSON_REF_STR_IN_OBJECT(ftl_local, "version", get_FTL_version());
JSON_REF_STR_IN_OBJECT(ftl_local, "date", GIT_DATE);
cJSON *version = JSON_NEW_OBJECT();
cJSON *core = JSON_NEW_OBJECT();
JSON_ADD_NULL_IF_NOT_EXISTS(core_local, "branch");
JSON_ADD_NULL_IF_NOT_EXISTS(core_local, "version");
@@ -839,7 +849,14 @@ int api_info_version(struct ftl_conn *api)
JSON_ADD_NULL_IF_NOT_EXISTS(docker, "remote");
JSON_ADD_ITEM_TO_OBJECT(version, "docker", docker);
return 0;
}
int api_info_version(struct ftl_conn *api)
{
// Send reply
cJSON *version = JSON_NEW_OBJECT();
get_version_obj(api, version);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "version", version);
JSON_SEND_OBJECT(json);
+13 -5
View File
@@ -32,11 +32,8 @@
// nlroutes(), nladdrs(), nllinks()
#include "tools/netlink.h"
int api_network_gateway(struct ftl_conn *api)
int get_gateway(struct ftl_conn *api, cJSON * json, const bool detailed)
{
// Get ?detailed parameter
bool detailed = false;
get_bool_var(api->request->query_string, "detailed", &detailed);
// Get routing information
cJSON *routes = JSON_NEW_ARRAY();
@@ -106,7 +103,6 @@ int api_network_gateway(struct ftl_conn *api)
}
// Send gateway information
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "gateway", gateway);
if(detailed)
@@ -121,6 +117,18 @@ int api_network_gateway(struct ftl_conn *api)
cJSON_Delete(interfaces);
}
return 0;
}
int api_network_gateway(struct ftl_conn *api)
{
// Get ?detailed parameter
bool detailed = false;
get_bool_var(api->request->query_string, "detailed", &detailed);
cJSON *json = JSON_NEW_OBJECT();
get_gateway(api, json, detailed);
JSON_SEND_OBJECT(json);
}
+308
View File
@@ -0,0 +1,308 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/dns
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#include "webserver/http-common.h"
#include "webserver/json_macros.h"
#include "api.h"
// lock_shm() and unlock_shm()
#include "shmem.h"
// counters
#include "datastructure.h"
// get_dnsmasq_metrics(&metrics)
#include "metrics.h"
// get_blockingstatus()
#include "config/config.h"
// uname()
#include <sys/utsname.h>
// nlroutes(), nladdrs(), nllinks()
#include "tools/netlink.h"
// struct proc_mem, getProcessMemory()
#include "procps.h"
// getcpu_percentage()
#include "daemon.h"
int api_padd(struct ftl_conn *api)
{
// Parse parameters
bool full = true;
if(api->request->query_string != NULL)
get_bool_var(api->request->query_string, "full", &full);
cJSON *json = JSON_NEW_OBJECT();
// Lock shared memory
lock_shm();
const int total = counters->queries;
const int blocked = get_blocked_count();
const unsigned int active_clients = get_active_clients();
const int num_gravity = counters->database.gravity;
// If privacy level is set to hide domains, do not return the most
// recent blocked domain
if(config.misc.privacylevel.v.privacy_level < PRIVACY_HIDE_DOMAINS)
{
// Find most recently blocked query
for(int queryID = counters->queries - 1; queryID > 0 ; queryID--)
{
const queriesData* query = getQuery(queryID, true);
if(query == NULL)
continue;
if(query->flags.blocked)
{
// Ask subroutine for domain. It may return "hidden" depending on
// the privacy settings at the time the query was made
const char *domain = getDomainString(query);
if(domain == NULL)
continue;
JSON_COPY_STR_TO_OBJECT(json, "recent_blocked", domain);
break;
}
}
}
// Unlock shared memory
unlock_shm();
// Add the number of active clients, the size of the gravity list
JSON_ADD_NUMBER_TO_OBJECT(json, "active_clients", active_clients);
JSON_ADD_NUMBER_TO_OBJECT(json, "gravity_size", num_gravity);
cJSON *top_domains = get_top_domains(api, 1, false, true);
if(cJSON_GetArraySize(top_domains) == 0)
{
JSON_ADD_NULL_TO_OBJECT(json, "top_domain");
}
else
{
cJSON *top_domain = cJSON_GetArrayItem(top_domains, 0);
const char *domain = cJSON_GetStringValue(top_domain);
JSON_COPY_STR_TO_OBJECT(json, "top_domain", domain);
}
cJSON_Delete(top_domains);
cJSON *top_blocked = get_top_domains(api, 1, true, true);
if(cJSON_GetArraySize(top_blocked) == 0)
{
JSON_ADD_NULL_TO_OBJECT(json, "top_blocked");
}
else
{
cJSON *top_block = cJSON_GetArrayItem(top_blocked, 0);
const char *domain = cJSON_GetStringValue(top_block);
JSON_COPY_STR_TO_OBJECT(json, "top_blocked", domain);
}
cJSON *top_clients = get_top_clients(api, 1, false, true, false, true);
if(cJSON_GetArraySize(top_clients) == 0)
{
JSON_ADD_NULL_TO_OBJECT(json, "top_client");
}
else
{
cJSON *top_client = cJSON_GetArrayItem(top_clients, 0);
const char *client = cJSON_GetStringValue(top_client);
JSON_COPY_STR_TO_OBJECT(json, "top_client", client);
}
// Add a null entry if the domain is hidden or there is no recent
// blocked domain (e.g. when blocking is disabled)
JSON_ADD_NULL_IF_NOT_EXISTS(json, "recent_blocked");
// Calculate percentage of blocked queries
float percent_blocked = 0.0f;
// Avoid 1/0 condition
if(total > 0)
percent_blocked = 1e2f*blocked/total;
// Add the blocking status
const char *blocking = get_blocking_status_str(get_blockingstatus());
JSON_REF_STR_IN_OBJECT(json, "blocking", blocking);
// Add query statistics
cJSON *queries = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(queries, "total", total);
JSON_ADD_NUMBER_TO_OBJECT(queries, "blocked", blocked);
JSON_ADD_NUMBER_TO_OBJECT(queries, "percent_blocked", percent_blocked);
JSON_ADD_ITEM_TO_OBJECT(json, "queries", queries);
// Add cache statistics
cJSON *cache = JSON_NEW_OBJECT();
struct metrics metrics = { 0 };
get_dnsmasq_metrics(&metrics);
JSON_ADD_NUMBER_TO_OBJECT(cache, "size", metrics.dns.cache.size);
JSON_ADD_NUMBER_TO_OBJECT(cache, "inserted", metrics.dns.cache.inserted);
JSON_ADD_NUMBER_TO_OBJECT(cache, "evicted", metrics.dns.cache.live_freed);
JSON_ADD_ITEM_TO_OBJECT(json, "cache", cache);
// info/system
cJSON *system = JSON_NEW_OBJECT();
get_system_obj(api, system);
JSON_ADD_ITEM_TO_OBJECT(json, "system", system);
// info/host
struct utsname un = { 0 };
uname(&un);
JSON_COPY_STR_TO_OBJECT(json, "node_name", un.nodename);
JSON_ADD_ITEM_TO_OBJECT(json, "host_model", read_sys_property("/sys/firmware/devicetree/base/model"));
// Expensive calls, do only if full is requested
if(full)
{
// network/gateway
cJSON *gateway_ = JSON_NEW_OBJECT();
get_gateway(api, gateway_, true);
cJSON *gateway = cJSON_GetObjectItemCaseSensitive(gateway_, "gateway");
cJSON *interfaces = cJSON_GetObjectItemCaseSensitive(gateway_, "interfaces");
// Loop over gateway and find first entry with family == "inet"
cJSON *entry = NULL;
const char *gw_v4_name = NULL, *gw_v6_name = NULL;
const char *gw_v4_addr = NULL, *gw_v6_addr = NULL;
cJSON_ArrayForEach(entry, gateway)
{
cJSON *family = cJSON_GetObjectItemCaseSensitive(entry, "family");
if(gw_v4_name == NULL && strcmp(cJSON_GetStringValue(family), "inet") == 0)
{
gw_v4_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "interface"));
gw_v4_addr = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "address"));
}
if(gw_v6_name == NULL && strcmp(cJSON_GetStringValue(family), "inet6") == 0)
{
gw_v6_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "interface"));
gw_v6_addr = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "address"));
}
// Break if both addresses are found
if(gw_v4_name && gw_v6_name)
break;
}
// If no IPv6 gateway is found, use the IPv4 gateway
if(gw_v6_name == NULL)
gw_v6_name = gw_v4_name;
// Iterate over all interfaces until we find the one associated
// with the IPv4 gateway
cJSON *iface_v4 = JSON_NEW_OBJECT();
cJSON *iface_v6 = JSON_NEW_OBJECT();
unsigned int v4_addrs = 0, v6_addrs = 0;
cJSON_ArrayForEach(entry, interfaces)
{
if(strcmp(cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "name")), gw_v4_name) == 0)
{
// Add first interface address with family == inet
cJSON *addr = NULL;
cJSON *addrs = cJSON_GetObjectItemCaseSensitive(entry, "addresses");
cJSON_ArrayForEach(addr, addrs)
{
cJSON *family = cJSON_GetObjectItemCaseSensitive(addr, "family");
if(strcmp(cJSON_GetStringValue(family), "inet") == 0)
{
if(v4_addrs == 0)
{
cJSON *_addr = cJSON_GetObjectItemCaseSensitive(addr, "address");
JSON_COPY_STR_TO_OBJECT(iface_v4, "addr", cJSON_GetStringValue(_addr));
}
v4_addrs++;
}
}
// Add NULL if no IPv4 address is found
if(v4_addrs == 0)
JSON_ADD_NULL_TO_OBJECT(iface_v4, "addr");
// Also add IPv4 interface statistics
cJSON *stats = cJSON_GetObjectItemCaseSensitive(entry, "stats");
cJSON *rx_bytes = cJSON_GetObjectItemCaseSensitive(stats, "rx_bytes");
JSON_ADD_ITEM_TO_OBJECT(iface_v4, "rx_bytes", cJSON_Duplicate(rx_bytes, true));
cJSON *tx_bytes = cJSON_GetObjectItemCaseSensitive(stats, "tx_bytes");
JSON_ADD_ITEM_TO_OBJECT(iface_v4, "tx_bytes", cJSON_Duplicate(tx_bytes, true));
}
if(strcmp(cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "name")), gw_v6_name) == 0)
{
// Add first interface address with family == inet
cJSON *addr = NULL;
cJSON *addrs = cJSON_GetObjectItemCaseSensitive(entry, "addresses");
cJSON_ArrayForEach(addr, addrs)
{
cJSON *family = cJSON_GetObjectItemCaseSensitive(addr, "family");
if(strcmp(cJSON_GetStringValue(family), "inet6") == 0)
{
if(v6_addrs == 0)
{
cJSON *_addr = cJSON_GetObjectItemCaseSensitive(addr, "address");
JSON_COPY_STR_TO_OBJECT(iface_v6, "addr", cJSON_GetStringValue(_addr));
}
v6_addrs++;
}
}
// Add NULL if no IPv6 address is found
if(v6_addrs == 0)
JSON_ADD_NULL_TO_OBJECT(iface_v6, "addr");
}
}
// Add the number of addresses found
JSON_ADD_NUMBER_TO_OBJECT(iface_v4, "num_addrs", v4_addrs);
JSON_ADD_NUMBER_TO_OBJECT(iface_v6, "num_addrs", v6_addrs);
// Add the interfaces to the gateway object
JSON_COPY_STR_TO_OBJECT(iface_v4, "name", gw_v4_name);
JSON_COPY_STR_TO_OBJECT(iface_v4, "gw_addr", gw_v4_addr);
JSON_COPY_STR_TO_OBJECT(iface_v6, "name", gw_v6_name);
JSON_COPY_STR_TO_OBJECT(iface_v6, "gw_addr", gw_v6_addr);
// Create interface object
cJSON *iface = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(iface, "v4", iface_v4);
JSON_ADD_ITEM_TO_OBJECT(iface, "v6", iface_v6);
JSON_ADD_ITEM_TO_OBJECT(json, "iface", iface);
// Free memory
cJSON_Delete(gateway_);
// info/version
cJSON *version = JSON_NEW_OBJECT();
get_version_obj(api, version);
JSON_ADD_ITEM_TO_OBJECT(json, "version", version);
}
// subset of config
cJSON *jconfig = JSON_NEW_OBJECT();
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dhcp_active", config.dhcp.active.v.b);
JSON_ADD_ITEM_TO_OBJECT(jconfig, "dhcp_start", addJSONConfValue(config.dhcp.start.t, &config.dhcp.start.v));
JSON_ADD_ITEM_TO_OBJECT(jconfig, "dhcp_end", addJSONConfValue(config.dhcp.end.t, &config.dhcp.end.v));
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dhcp_ipv6", config.dhcp.ipv6.v.b);
JSON_COPY_STR_TO_OBJECT(jconfig, "dns_domain", config.dns.domain.v.s);
JSON_ADD_NUMBER_TO_OBJECT(jconfig, "dns_port", config.dns.port.v.u16);
JSON_ADD_NUMBER_TO_OBJECT(jconfig, "dns_num_upstreams", cJSON_GetArraySize(config.dns.upstreams.v.json));
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dns_dnssec", config.dns.dnssec.v.b);
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dns_revServer_active", cJSON_GetArraySize(config.dns.revServers.v.json) > 0);
JSON_ADD_ITEM_TO_OBJECT(json, "config", jconfig);
// subset of info/ftl
struct proc_mem pmem = { 0 };
struct proc_meminfo mem = { 0 };
parse_proc_meminfo(&mem);
getProcessMemory(&pmem, mem.total);
JSON_ADD_NUMBER_TO_OBJECT(json, "%mem", pmem.VmRSS_percent);
JSON_ADD_NUMBER_TO_OBJECT(json, "%cpu", get_cpu_percentage());
JSON_ADD_NUMBER_TO_OBJECT(json, "pid", getpid());
// info/sensors -> CPU temp sensor
cJSON *sensors = JSON_NEW_OBJECT();
get_sensors_obj(api, sensors, false);
JSON_ADD_ITEM_TO_OBJECT(json, "sensors", sensors);
JSON_SEND_OBJECT(json);
}
+2 -2
View File
@@ -114,8 +114,8 @@ int api_queries_suggestions(struct ftl_conn *api)
cJSON_Delete(blocked);
// Get clients, both by IP and names
cJSON *client_ip = get_top_clients(api, count, false, true, false);
cJSON *client_name = get_top_clients(api, count, false, true, true);
cJSON *client_ip = get_top_clients(api, count, false, true, false, false);
cJSON *client_name = get_top_clients(api, count, false, true, true, false);
// Delete duplicate entries from client_name
cJSON_unique_array(client_name);
+49 -30
View File
@@ -92,26 +92,54 @@ static int get_query_types_obj(struct ftl_conn *api, cJSON *types)
return 0;
}
// shmem needs to be locked while calling this function
unsigned int get_active_clients(void)
{
unsigned int activeclients = 0;
for(int clientID=0; clientID < counters->clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
if(client->count > 0)
activeclients++;
}
return activeclients;
}
int api_stats_summary(struct ftl_conn *api)
{
const int blocked = get_blocked_count();
const int forwarded = get_forwarded_count();
const int cached = get_cached_count();
const int total = counters->queries;
float percent_blocked = 0.0f;
// Lock shared memory
lock_shm();
const int blocked = get_blocked_count();
const int forwarded = get_forwarded_count();
const int cached = get_cached_count();
const int total = counters->queries;
const int num_gravity = counters->database.gravity;
const int num_clients = counters->clients;
const int num_domains = counters->domains;
// Count clients that have been active within the most recent 24 hours
unsigned int activeclients = get_active_clients();
// Unlock shared memory
unlock_shm();
// Calculate percentage of blocked queries
float percent_blocked = 0.0f;
// Avoid 1/0 condition
if(total > 0)
percent_blocked = 1e2f*blocked/total;
// Lock shared memory
lock_shm();
cJSON *queries = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(queries, "total", total);
JSON_ADD_NUMBER_TO_OBJECT(queries, "blocked", blocked);
JSON_ADD_NUMBER_TO_OBJECT(queries, "percent_blocked", percent_blocked);
JSON_ADD_NUMBER_TO_OBJECT(queries, "unique_domains", counters->domains);
JSON_ADD_NUMBER_TO_OBJECT(queries, "unique_domains", num_domains);
JSON_ADD_NUMBER_TO_OBJECT(queries, "forwarded", forwarded);
JSON_ADD_NUMBER_TO_OBJECT(queries, "cached", cached);
@@ -131,28 +159,12 @@ int api_stats_summary(struct ftl_conn *api)
JSON_ADD_NUMBER_TO_OBJECT(replies, get_query_reply_str(reply), counters->reply[reply]);
JSON_ADD_ITEM_TO_OBJECT(queries, "replies", replies);
// Count clients that have been active within the most recent 24 hours
unsigned int activeclients = 0;
for(int clientID=0; clientID < counters->clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
if(client == NULL)
continue;
if(client->count > 0)
activeclients++;
}
cJSON *clients = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(clients, "active", activeclients);
JSON_ADD_NUMBER_TO_OBJECT(clients, "total", counters->clients);
JSON_ADD_NUMBER_TO_OBJECT(clients, "total", num_clients);
cJSON *gravity = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(gravity, "domains_being_blocked", counters->database.gravity);
// Unlock shared memory
unlock_shm();
JSON_ADD_NUMBER_TO_OBJECT(gravity, "domains_being_blocked", num_gravity);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "queries", queries);
@@ -335,7 +347,7 @@ int api_stats_top_domains(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)
const bool names_only, const bool ip_if_no_name)
{
// Exit before processing any data if requested via config setting
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS)
@@ -449,7 +461,14 @@ cJSON *get_top_clients(struct ftl_conn *api, const int count,
if(clients_only)
{
if(names_only)
if(ip_if_no_name)
{
if(strlen(client_name) > 0)
cJSON_AddStringToArray(jtop_clients, client_name);
else
cJSON_AddStringToArray(jtop_clients, client_ip);
}
else if(names_only)
{
if(strlen(client_name) > 0)
cJSON_AddStringToArray(jtop_clients, client_name);
@@ -516,7 +535,7 @@ int api_stats_top_clients(struct ftl_conn *api)
get_int_var(api->request->query_string, "count", &count);
}
cJSON *json = get_top_clients(api, count, blocked, false, false);
cJSON *json = get_top_clients(api, count, blocked, false, false, false);
JSON_SEND_OBJECT(json);
}
+16
View File
@@ -835,6 +835,22 @@ int __attribute__ ((pure)) get_blocking_mode_val(const char *blocking_mode)
return -1;
}
const char * __attribute__ ((const)) get_blocking_status_str(const enum blocking_status blocking)
{
switch(blocking)
{
case BLOCKING_ENABLED:
return "enabled";
case BLOCKING_DISABLED:
return "disabled";
case DNS_FAILED:
return "failure";
case BLOCKING_UNKNOWN:
default:
return "unknown";
}
}
bool __attribute__ ((const)) is_blocked(const enum query_status status)
{
switch (status)
+1
View File
@@ -159,6 +159,7 @@ const char *get_refresh_hostnames_str(const enum refresh_hostnames refresh) __at
int get_refresh_hostnames_val(const char *refresh_hostnames) __attribute__ ((pure));
const char *get_blocking_mode_str(const enum blocking_mode mode) __attribute__ ((const));
int get_blocking_mode_val(const char *blocking_mode) __attribute__ ((pure));
const char * __attribute__ ((const)) get_blocking_status_str(const enum blocking_status blocking);
const char *get_ptr_type_str(const enum ptr_type piholePTR) __attribute__ ((const));
int get_ptr_type_val(const char *piholePTR) __attribute__ ((pure));
const char *get_busy_reply_str(const enum busy_reply replyWhenBusy) __attribute__ ((const));