1162 lines
35 KiB
C
1162 lines
35 KiB
C
/* Pi-hole: A black hole for Internet advertisements
|
|
* (c) 2018 Pi-hole, LLC (https://pi-hole.net)
|
|
* Network-wide ad blocking via your own hardware.
|
|
*
|
|
* FTL Engine
|
|
* Shared memory subroutines
|
|
*
|
|
* This file is copyright under the latest version of the EUPL.
|
|
* Please see LICENSE file for your rights under this license. */
|
|
|
|
#include "FTL.h"
|
|
#define SHMEM_PRIVATE
|
|
#include "shmem.h"
|
|
#include "overTime.h"
|
|
#include "log.h"
|
|
#include "config.h"
|
|
// data getter functions
|
|
#include "datastructure.h"
|
|
// get_num_regex()
|
|
#include "regex_r.h"
|
|
// NAME_MAX
|
|
#include <limits.h>
|
|
// gettid
|
|
#include "daemon.h"
|
|
// generate_backtrace()
|
|
#include "signals.h"
|
|
// get_path_usage()
|
|
#include "files.h"
|
|
// log_resource_shortage()
|
|
#include "database/message-table.h"
|
|
// check_running_FTL()
|
|
#include "procps.h"
|
|
|
|
/// The version of shared memory used
|
|
#define SHARED_MEMORY_VERSION 14
|
|
|
|
/// The name of the shared memory. Use this when connecting to the shared memory.
|
|
#define SHMEM_PATH "/dev/shm"
|
|
#define SHARED_LOCK_NAME "FTL-lock"
|
|
#define SHARED_STRINGS_NAME "FTL-strings"
|
|
#define SHARED_COUNTERS_NAME "FTL-counters"
|
|
#define SHARED_DOMAINS_NAME "FTL-domains"
|
|
#define SHARED_CLIENTS_NAME "FTL-clients"
|
|
#define SHARED_QUERIES_NAME "FTL-queries"
|
|
#define SHARED_UPSTREAMS_NAME "FTL-upstreams"
|
|
#define SHARED_OVERTIME_NAME "FTL-overTime"
|
|
#define SHARED_SETTINGS_NAME "FTL-settings"
|
|
#define SHARED_DNS_CACHE "FTL-dns-cache"
|
|
#define SHARED_PER_CLIENT_REGEX "FTL-per-client-regex"
|
|
|
|
// Allocation step for FTL-strings bucket. This is somewhat special as we use
|
|
// this as a general-purpose storage which should always be large enough. If,
|
|
// for some reason, more data than this step has to be stored (highly unlikely,
|
|
// close to impossible), the data will be properly truncated and we try again in
|
|
// the next lock round
|
|
#define STRINGS_ALLOC_STEP (10*pagesize)
|
|
|
|
// Global counters struct
|
|
countersStruct *counters = NULL;
|
|
|
|
/// The pointer in shared memory to the shared string buffer
|
|
static SharedMemory shm_lock = { 0 };
|
|
static SharedMemory shm_strings = { 0 };
|
|
static SharedMemory shm_counters = { 0 };
|
|
static SharedMemory shm_domains = { 0 };
|
|
static SharedMemory shm_clients = { 0 };
|
|
static SharedMemory shm_queries = { 0 };
|
|
static SharedMemory shm_upstreams = { 0 };
|
|
static SharedMemory shm_overTime = { 0 };
|
|
static SharedMemory shm_settings = { 0 };
|
|
static SharedMemory shm_dns_cache = { 0 };
|
|
static SharedMemory shm_per_client_regex = { 0 };
|
|
|
|
static SharedMemory *sharedMemories[] = { &shm_lock,
|
|
&shm_strings,
|
|
&shm_counters,
|
|
&shm_domains,
|
|
&shm_clients,
|
|
&shm_queries,
|
|
&shm_upstreams,
|
|
&shm_overTime,
|
|
&shm_settings,
|
|
&shm_dns_cache,
|
|
&shm_per_client_regex };
|
|
#define NUM_SHMEM (sizeof(sharedMemories)/sizeof(SharedMemory*))
|
|
|
|
// Variable size array structs
|
|
static queriesData *queries = NULL;
|
|
static clientsData *clients = NULL;
|
|
static domainsData *domains = NULL;
|
|
static upstreamsData *upstreams = NULL;
|
|
static DNSCacheData *dns_cache = NULL;
|
|
|
|
typedef struct {
|
|
struct {
|
|
pthread_mutex_t outer;
|
|
pthread_mutex_t inner;
|
|
} lock;
|
|
struct {
|
|
volatile pid_t pid;
|
|
volatile pid_t tid;
|
|
} owner;
|
|
} ShmLock;
|
|
static ShmLock *shmLock = NULL;
|
|
static ShmSettings *shmSettings = NULL;
|
|
|
|
static int pagesize;
|
|
static unsigned int local_shm_counter = 0;
|
|
static pid_t shmem_pid = 0;
|
|
static size_t used_shmem = 0u;
|
|
static size_t get_optimal_object_size(const size_t objsize, const size_t minsize);
|
|
|
|
// Private prototypes
|
|
static void *enlarge_shmem_struct(const char type);
|
|
|
|
static int get_dev_shm_usage(char buffer[64])
|
|
{
|
|
char buffer2[64] = { 0 };
|
|
const int percentage = get_path_usage(SHMEM_PATH, buffer2);
|
|
|
|
// Generate human-readable "used by FTL" size
|
|
char prefix_FTL[2] = { 0 };
|
|
double formatted_FTL = 0.0;
|
|
format_memory_size(prefix_FTL, used_shmem, &formatted_FTL);
|
|
|
|
// Print result into buffer passed to this subroutine
|
|
snprintf(buffer, 64, "%s, FTL uses %.1f%sB",
|
|
buffer2, formatted_FTL, prefix_FTL);
|
|
|
|
// Return percentage
|
|
return percentage;
|
|
}
|
|
|
|
// Verify the PID stored during shared memory initialization is the same as ours
|
|
// (while we initialized the shared memory objects)
|
|
static void verify_shmem_pid(void)
|
|
{
|
|
// Open shared memory settings object
|
|
const int settingsfd = shm_open(SHARED_SETTINGS_NAME, O_RDONLY, S_IRUSR | S_IWUSR);
|
|
if(settingsfd == -1)
|
|
{
|
|
logg("FATAL: verify_shmem_pid(): Failed to open shared memory object \"%s\": %s",
|
|
SHARED_SETTINGS_NAME, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ShmSettings shms = { 0 };
|
|
if(read(settingsfd, &shms, sizeof(shms)) != sizeof(shms))
|
|
{
|
|
logg("FATAL: verify_shmem_pid(): Failed to read %zu bytes from shared memory object \"%s\": %s",
|
|
sizeof(shms), SHARED_SETTINGS_NAME, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
close(settingsfd);
|
|
|
|
// Compare the SHM's PID to the one we had when creating the SHM objects
|
|
if(shms.pid == shmem_pid)
|
|
return;
|
|
|
|
// If we reach here, we are in serious trouble. Terminating with error
|
|
// code is the most sensible thing we can do at this point
|
|
logg("FATAL: Shared memory is owned by a different process (PID %d)", shms.pid);
|
|
check_running_FTL();
|
|
logg("Exiting now!");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// chown_shmem() changes the file ownership of a given shared memory object
|
|
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);
|
|
if(fd == -1)
|
|
{
|
|
logg("FATAL: chown_shmem(): Failed to open shared memory object \"%s\": %s",
|
|
sharedMemory->name, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if(fchown(fd, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
|
|
{
|
|
logg("WARNING: chown_shmem(%d, %d, %d): failed for %s: %s (%d)",
|
|
fd, ent_pw->pw_uid, ent_pw->pw_gid, sharedMemory->name,
|
|
strerror(errno), errno);
|
|
return false;
|
|
}
|
|
logg("Changing %s (%d) to %d:%d", sharedMemory->name, fd, ent_pw->pw_uid, ent_pw->pw_gid);
|
|
// Close shared memory object file descriptor as it is no longer
|
|
// needed after having called ftruncate()
|
|
close(fd);
|
|
return true;
|
|
}
|
|
|
|
// A function that duplicates a string and replaces all characters "s" by "r"
|
|
static char *__attribute__ ((malloc)) str_replace(const char *input,
|
|
const char s,
|
|
const char r,
|
|
unsigned int *N)
|
|
{
|
|
// Duplicate string
|
|
char *copy = strdup(input);
|
|
if(!copy)
|
|
return NULL;
|
|
|
|
// Woring pointer
|
|
char *ix = copy;
|
|
// Loop over string until there are no further "s" chars in the string
|
|
while((ix = strchr(ix, s)) != NULL)
|
|
{
|
|
*ix++ = r;
|
|
(*N)++;
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
char *__attribute__ ((malloc)) str_escape(const char *input, unsigned int *N)
|
|
{
|
|
// If no escaping is done, this routine returns the original pointer
|
|
// and N stays 0
|
|
*N = 0;
|
|
if(strchr(input, ' ') != NULL)
|
|
{
|
|
// Replace any spaces by ~ if we find them in the domain name
|
|
// This is necessary as our telnet API uses space delimiters
|
|
return str_replace(input, ' ', '~', N);
|
|
}
|
|
|
|
return strdup(input);
|
|
}
|
|
|
|
bool strcmp_escaped(const char *a, const char *b)
|
|
{
|
|
unsigned int Na, Nb;
|
|
|
|
// Input check
|
|
if(a == NULL || b == NULL)
|
|
return false;
|
|
|
|
// Escape both inputs
|
|
char *aa = str_escape(a, &Na);
|
|
char *bb = str_escape(b, &Nb);
|
|
|
|
// Check for memory errors
|
|
if(!aa || !bb)
|
|
{
|
|
if(aa) free(aa);
|
|
if(bb) free(bb);
|
|
return false;
|
|
}
|
|
|
|
const char result = strcasecmp(aa, bb) == 0;
|
|
|
|
free(aa);
|
|
free(bb);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
size_t addstr(const char *input)
|
|
{
|
|
if(input == NULL)
|
|
{
|
|
logg("WARN: Called addstr() with NULL pointer");
|
|
return 0;
|
|
}
|
|
|
|
// Get string length, add terminating character
|
|
size_t len = strlen(input) + 1;
|
|
const size_t avail_mem = shm_strings.size - shmSettings->next_str_pos;
|
|
|
|
// If this is an empty string (only the terminating character is present),
|
|
// use the shared memory string at position zero instead of creating a new
|
|
// entry here. We also ensure that the given string is not too long to
|
|
// prevent possible memory corruption caused by strncpy() further down
|
|
if(len == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
else if(len > (size_t)(pagesize-1))
|
|
{
|
|
logg("WARN: Shortening too long string (len %zu > pagesize %i)", len, pagesize);
|
|
len = pagesize;
|
|
}
|
|
else if(len > (size_t)(avail_mem-1))
|
|
{
|
|
logg("WARN: Shortening too long string (len %zu > available memory %zu)", len, avail_mem);
|
|
len = avail_mem;
|
|
}
|
|
|
|
unsigned int N = 0;
|
|
char *str = str_escape(input, &N);
|
|
|
|
if(N > 0)
|
|
logg("INFO: FTL replaced %u invalid characters with ~ in the query \"%s\"", N, str);
|
|
|
|
// Debugging output
|
|
if(config.debug & DEBUG_SHMEM)
|
|
logg("Adding \"%s\" (len %zu) to buffer. next_str_pos is %u", str, len, shmSettings->next_str_pos);
|
|
|
|
// Copy the C string pointed by str into the shared string buffer
|
|
strncpy(&((char*)shm_strings.ptr)[shmSettings->next_str_pos], str, len);
|
|
free(str);
|
|
|
|
// Increment string length counter
|
|
shmSettings->next_str_pos += len;
|
|
|
|
// Return start of stored string
|
|
return (shmSettings->next_str_pos - len);
|
|
}
|
|
|
|
const char *_getstr(const size_t pos, const char *func, const int line, const char *file)
|
|
{
|
|
// Only access the string memory if this memory region has already been set
|
|
if(pos < shmSettings->next_str_pos)
|
|
return &((const char*)shm_strings.ptr)[pos];
|
|
else
|
|
{
|
|
logg("WARN: Tried to access %zu in %s() (%s:%i) but next_str_pos is %u", pos, func, file, line, shmSettings->next_str_pos);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// Create a mutex for shared memory
|
|
static pthread_mutex_t create_mutex(void) {
|
|
logg("Creating mutex");
|
|
pthread_mutexattr_t lock_attr = {};
|
|
pthread_mutex_t lock = {};
|
|
|
|
// Initialize the lock attributes
|
|
pthread_mutexattr_init(&lock_attr);
|
|
|
|
// Allow the lock to be used by other processes
|
|
pthread_mutexattr_setpshared(&lock_attr, PTHREAD_PROCESS_SHARED);
|
|
|
|
// Make the lock robust against process death
|
|
pthread_mutexattr_setrobust(&lock_attr, PTHREAD_MUTEX_ROBUST);
|
|
|
|
// Initialize the lock
|
|
pthread_mutex_init(&lock, &lock_attr);
|
|
|
|
// Destroy the lock attributes since we're done with it
|
|
pthread_mutexattr_destroy(&lock_attr);
|
|
|
|
return lock;
|
|
}
|
|
|
|
static void remap_shm(void)
|
|
{
|
|
// Remap shared object pointers which might have changed
|
|
realloc_shm(&shm_queries, counters->queries_MAX, sizeof(queriesData), false);
|
|
queries = (queriesData*)shm_queries.ptr;
|
|
|
|
realloc_shm(&shm_domains, counters->domains_MAX, sizeof(domainsData), false);
|
|
domains = (domainsData*)shm_domains.ptr;
|
|
|
|
realloc_shm(&shm_clients, counters->clients_MAX, sizeof(clientsData), false);
|
|
clients = (clientsData*)shm_clients.ptr;
|
|
|
|
realloc_shm(&shm_upstreams, counters->upstreams_MAX, sizeof(upstreamsData), false);
|
|
upstreams = (upstreamsData*)shm_upstreams.ptr;
|
|
|
|
realloc_shm(&shm_dns_cache, counters->dns_cache_MAX, sizeof(DNSCacheData), false);
|
|
dns_cache = (DNSCacheData*)shm_dns_cache.ptr;
|
|
|
|
realloc_shm(&shm_per_client_regex, counters->per_client_regex_MAX, sizeof(bool), false);
|
|
// per-client-regex bools are not exposed by a global pointer
|
|
|
|
realloc_shm(&shm_strings, counters->strings_MAX, sizeof(char), false);
|
|
// strings are not exposed by a global pointer
|
|
|
|
// Update local counter to reflect that we absorbed this change
|
|
local_shm_counter = shmSettings->global_shm_counter;
|
|
}
|
|
|
|
// Obtain SHMEM lock
|
|
void _lock_shm(const char *func, const int line, const char *file)
|
|
{
|
|
if(config.debug & DEBUG_LOCKS)
|
|
logg("Waiting for SHM lock in %s() (%s:%i)", func, file, line);
|
|
|
|
int result = pthread_mutex_lock(&shmLock->lock.outer);
|
|
|
|
if(result != 0)
|
|
logg("Error when obtaining outer SHM lock: %s", strerror(result));
|
|
|
|
if(result == EOWNERDEAD) {
|
|
// Try to make the lock consistent if the other process died while
|
|
// holding the lock
|
|
if(config.debug & DEBUG_LOCKS)
|
|
logg("Owner of outer SHM lock died, making lock consistent");
|
|
|
|
result = pthread_mutex_consistent(&shmLock->lock.outer);
|
|
if(result != 0)
|
|
logg("Failed to make outer SHM lock consistent: %s", strerror(result));
|
|
}
|
|
|
|
// Store lock owner after lock has been acquired and was made consistent (if required)
|
|
shmLock->owner.pid = getpid();
|
|
shmLock->owner.tid = gettid();
|
|
|
|
// Check if this process needs to remap the shared memory objects
|
|
if(shmSettings != NULL &&
|
|
local_shm_counter != shmSettings->global_shm_counter)
|
|
{
|
|
if(config.debug & DEBUG_SHMEM)
|
|
logg("Remapping shared memory for current process %u %u",
|
|
local_shm_counter, shmSettings->global_shm_counter);
|
|
remap_shm();
|
|
}
|
|
|
|
// Ensure we have enough shared memory available for new data
|
|
shm_ensure_size();
|
|
|
|
result = pthread_mutex_lock(&shmLock->lock.inner);
|
|
|
|
if(config.debug & DEBUG_LOCKS)
|
|
logg("Obtained SHM lock for %s() (%s:%i)", func, file, line);
|
|
|
|
if(result != 0)
|
|
logg("Error when obtaining inner SHM lock: %s", strerror(result));
|
|
|
|
if(result == EOWNERDEAD) {
|
|
// Try to make the lock consistent if the other process died while
|
|
// holding the lock
|
|
if(config.debug & DEBUG_LOCKS)
|
|
logg("Owner of inner SHM lock died, making lock consistent");
|
|
|
|
result = pthread_mutex_consistent(&shmLock->lock.inner);
|
|
if(result != 0)
|
|
logg("Failed to make inner SHM lock consistent: %s", strerror(result));
|
|
}
|
|
}
|
|
|
|
// Release SHM lock
|
|
void _unlock_shm(const char* func, const int line, const char * file)
|
|
{
|
|
if(config.debug & DEBUG_LOCKS && !is_our_lock())
|
|
{
|
|
logg("ERROR: Tried to unlock but lock is owned by %li/%li",
|
|
(long int)shmLock->owner.pid, (long int)shmLock->owner.tid);
|
|
}
|
|
|
|
// Unlock mutex
|
|
int result = pthread_mutex_unlock(&shmLock->lock.inner);
|
|
shmLock->owner.pid = 0;
|
|
shmLock->owner.tid = 0;
|
|
|
|
if(config.debug & DEBUG_LOCKS)
|
|
logg("Removed lock in %s() (%s:%i)", func, file, line);
|
|
|
|
if(result != 0)
|
|
logg("Failed to unlock inner SHM lock: %s", strerror(result));
|
|
|
|
result = pthread_mutex_unlock(&shmLock->lock.outer);
|
|
if(result != 0)
|
|
logg("Failed to unlock outer SHM lock: %s", strerror(result));
|
|
}
|
|
|
|
// Return if we locked this mutex (PID and TID match)
|
|
bool is_our_lock(void)
|
|
{
|
|
if(shmLock->owner.pid == getpid() &&
|
|
shmLock->owner.tid == gettid())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool init_shmem()
|
|
{
|
|
// Get kernel's page size
|
|
pagesize = getpagesize();
|
|
|
|
/****************************** shared memory lock ******************************/
|
|
// Try to create shared memory object
|
|
shm_lock = create_shm(SHARED_LOCK_NAME, sizeof(ShmLock));
|
|
if(shm_lock.ptr == NULL)
|
|
return false;
|
|
|
|
shmLock = (ShmLock*)shm_lock.ptr;
|
|
shmLock->lock.outer = create_mutex();
|
|
shmLock->lock.inner = create_mutex();
|
|
|
|
/****************************** shared counters struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_counters = create_shm(SHARED_COUNTERS_NAME, sizeof(countersStruct));
|
|
if(shm_counters.ptr == NULL)
|
|
return false;
|
|
|
|
counters = (countersStruct*)shm_counters.ptr;
|
|
|
|
/****************************** shared settings struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_settings = create_shm(SHARED_SETTINGS_NAME, sizeof(ShmSettings));
|
|
if(shm_settings.ptr == NULL)
|
|
return false;
|
|
|
|
shmSettings = (ShmSettings*)shm_settings.ptr;
|
|
shmSettings->version = SHARED_MEMORY_VERSION;
|
|
shmSettings->global_shm_counter = 0;
|
|
shmSettings->pid = shmem_pid = getpid();
|
|
|
|
/****************************** shared strings buffer ******************************/
|
|
// Try to create shared memory object
|
|
shm_strings = create_shm(SHARED_STRINGS_NAME, STRINGS_ALLOC_STEP);
|
|
if(shm_strings.ptr == NULL)
|
|
return false;
|
|
|
|
counters->strings_MAX = shm_strings.size;
|
|
|
|
// Initialize shared string object with an empty string at position zero
|
|
((char*)shm_strings.ptr)[0] = '\0';
|
|
shmSettings->next_str_pos = 1;
|
|
|
|
/****************************** shared domains struct ******************************/
|
|
size_t size = get_optimal_object_size(sizeof(domainsData), 1);
|
|
// Try to create shared memory object
|
|
shm_domains = create_shm(SHARED_DOMAINS_NAME, size*sizeof(domainsData));
|
|
if(shm_domains.ptr == NULL)
|
|
return false;
|
|
|
|
domains = (domainsData*)shm_domains.ptr;
|
|
counters->domains_MAX = size;
|
|
|
|
/****************************** shared clients struct ******************************/
|
|
size = get_optimal_object_size(sizeof(clientsData), 1);
|
|
// Try to create shared memory object
|
|
shm_clients = create_shm(SHARED_CLIENTS_NAME, size*sizeof(clientsData));
|
|
if(shm_clients.ptr == NULL)
|
|
return false;
|
|
|
|
clients = (clientsData*)shm_clients.ptr;
|
|
counters->clients_MAX = size;
|
|
|
|
/****************************** shared upstreams struct ******************************/
|
|
size = get_optimal_object_size(sizeof(upstreamsData), 1);
|
|
// Try to create shared memory object
|
|
shm_upstreams = create_shm(SHARED_UPSTREAMS_NAME, size*sizeof(upstreamsData));
|
|
if(shm_upstreams.ptr == NULL)
|
|
return false;
|
|
upstreams = (upstreamsData*)shm_upstreams.ptr;
|
|
|
|
counters->upstreams_MAX = size;
|
|
|
|
/****************************** shared queries struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_queries = create_shm(SHARED_QUERIES_NAME, pagesize*sizeof(queriesData));
|
|
if(shm_queries.ptr == NULL)
|
|
return false;
|
|
queries = (queriesData*)shm_queries.ptr;
|
|
|
|
counters->queries_MAX = pagesize;
|
|
|
|
/****************************** shared overTime struct ******************************/
|
|
size = get_optimal_object_size(sizeof(overTimeData), OVERTIME_SLOTS);
|
|
// Try to create shared memory object
|
|
shm_overTime = create_shm(SHARED_OVERTIME_NAME, size*sizeof(overTimeData));
|
|
if(shm_overTime.ptr == NULL)
|
|
return false;
|
|
|
|
// set global pointer in overTime.c
|
|
overTime = (overTimeData*)shm_overTime.ptr;
|
|
|
|
/****************************** shared DNS cache struct ******************************/
|
|
size = get_optimal_object_size(sizeof(DNSCacheData), 1);
|
|
// Try to create shared memory object
|
|
shm_dns_cache = create_shm(SHARED_DNS_CACHE, size*sizeof(DNSCacheData));
|
|
if(shm_dns_cache.ptr == NULL)
|
|
return false;
|
|
|
|
dns_cache = (DNSCacheData*)shm_dns_cache.ptr;
|
|
counters->dns_cache_MAX = size;
|
|
|
|
/****************************** shared per-client regex buffer ******************************/
|
|
size = pagesize; // Allocate one pagesize initially. This may be expanded later on
|
|
// Try to create shared memory object
|
|
shm_per_client_regex = create_shm(SHARED_PER_CLIENT_REGEX, size);
|
|
if(shm_per_client_regex.ptr == NULL)
|
|
return false;
|
|
|
|
counters->per_client_regex_MAX = size;
|
|
|
|
return true;
|
|
}
|
|
|
|
// CHOWN all shared memory objects to supplied user/group
|
|
void chown_all_shmem(struct passwd *ent_pw)
|
|
{
|
|
for(unsigned int i = 0; i < NUM_SHMEM; i++)
|
|
chown_shmem(sharedMemories[i], ent_pw);
|
|
}
|
|
|
|
// Destroy mutex and, subsequently, delete all shared memory objects
|
|
void destroy_shmem(void)
|
|
{
|
|
// First, we destroy the mutex
|
|
if(shmLock != NULL)
|
|
{
|
|
pthread_mutex_destroy(&shmLock->lock.inner);
|
|
pthread_mutex_destroy(&shmLock->lock.outer);
|
|
}
|
|
shmLock = NULL;
|
|
|
|
// Then, we delete the shared memory objects
|
|
for(unsigned int i = 0; i < NUM_SHMEM; i++)
|
|
delete_shm(sharedMemories[i]);
|
|
}
|
|
|
|
/// Create shared memory
|
|
///
|
|
/// \param name the name of the shared memory
|
|
/// \param size the size to allocate
|
|
/// \return a structure with a pointer to the mounted shared memory. The pointer
|
|
/// will always be valid, because if it failed FTL will have exited.
|
|
static SharedMemory create_shm(const char *name, const size_t size)
|
|
{
|
|
char df[64] = { 0 };
|
|
const int percentage = get_dev_shm_usage(df);
|
|
if(config.debug & DEBUG_SHMEM || (config.check.shmem > 0 && percentage > config.check.shmem))
|
|
{
|
|
logg("Creating shared memory with name \"%s\" and size %zu (%s)", name, size, df);
|
|
}
|
|
if(config.check.shmem > 0 && percentage > config.check.shmem)
|
|
log_resource_shortage(-1.0, 0, percentage, -1, SHMEM_PATH, df);
|
|
|
|
SharedMemory sharedMemory = {
|
|
.name = name,
|
|
.size = size,
|
|
.ptr = NULL
|
|
};
|
|
|
|
// Create the shared memory file in read/write mode with 600 (u+rw) permissions
|
|
// and the following open flags:
|
|
// - O_RDWR: Open the object for read-write access (we need to be able to modify the locks)
|
|
// - O_CREAT: Create the shared memory object if it does not exist.
|
|
// - O_EXCL: Return an error if a shared memory object with the given name already exists.
|
|
errno = 0;
|
|
const int fd = shm_open(sharedMemory.name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
|
|
|
|
// Check for `shm_open` error
|
|
if(fd == -1)
|
|
{
|
|
logg("FATAL: create_shm(): Failed to create shared memory object \"%s\": %s",
|
|
name, strerror(errno));
|
|
return sharedMemory;
|
|
}
|
|
|
|
// Allocate shared memory object to specified size
|
|
// Using f[tl]allocate() will ensure that there's actually space for
|
|
// this file. Otherwise we end up with a sparse file that can give
|
|
// SIGBUS if we run out of space while writing to it.
|
|
const int ret = ftlallocate(fd, 0U, size);
|
|
if(ret != 0)
|
|
{
|
|
logg("FATAL: create_shm(): Failed to resize \"%s\" (%i) to %zu: %s (%i)",
|
|
sharedMemory.name, fd, size, strerror(errno), ret);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Update how much memory FTL uses
|
|
// We only add here as this is a new file
|
|
used_shmem += size;
|
|
|
|
// Create shared memory mapping
|
|
void *shm = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
|
|
// Check for `mmap` error
|
|
if(shm == MAP_FAILED)
|
|
{
|
|
logg("FATAL: create_shm(): Failed to map shared memory object \"%s\" (%i): %s",
|
|
sharedMemory.name, fd, strerror(errno));
|
|
return sharedMemory;
|
|
}
|
|
|
|
// Close shared memory object file descriptor as it is no longer
|
|
// needed after having called mmap()
|
|
close(fd);
|
|
|
|
sharedMemory.ptr = shm;
|
|
return sharedMemory;
|
|
}
|
|
|
|
static void *enlarge_shmem_struct(const char type)
|
|
{
|
|
SharedMemory *sharedMemory = NULL;
|
|
size_t sizeofobj, allocation_step;
|
|
int *counter = NULL;
|
|
|
|
// Select type of struct that should be enlarged
|
|
switch(type)
|
|
{
|
|
case QUERIES:
|
|
sharedMemory = &shm_queries;
|
|
allocation_step = pagesize;
|
|
sizeofobj = sizeof(queriesData);
|
|
counter = &counters->queries_MAX;
|
|
break;
|
|
case CLIENTS:
|
|
sharedMemory = &shm_clients;
|
|
allocation_step = get_optimal_object_size(sizeof(clientsData), 1);
|
|
sizeofobj = sizeof(clientsData);
|
|
counter = &counters->clients_MAX;
|
|
break;
|
|
case DOMAINS:
|
|
sharedMemory = &shm_domains;
|
|
allocation_step = get_optimal_object_size(sizeof(domainsData), 1);
|
|
sizeofobj = sizeof(domainsData);
|
|
counter = &counters->domains_MAX;
|
|
break;
|
|
case UPSTREAMS:
|
|
sharedMemory = &shm_upstreams;
|
|
allocation_step = get_optimal_object_size(sizeof(upstreamsData), 1);
|
|
sizeofobj = sizeof(upstreamsData);
|
|
counter = &counters->upstreams_MAX;
|
|
break;
|
|
case DNS_CACHE:
|
|
sharedMemory = &shm_dns_cache;
|
|
allocation_step = get_optimal_object_size(sizeof(DNSCacheData), 1);
|
|
sizeofobj = sizeof(DNSCacheData);
|
|
counter = &counters->dns_cache_MAX;
|
|
break;
|
|
case STRINGS:
|
|
sharedMemory = &shm_strings;
|
|
allocation_step = STRINGS_ALLOC_STEP;
|
|
sizeofobj = 1;
|
|
counter = &counters->strings_MAX;
|
|
break;
|
|
default:
|
|
logg("Invalid argument in enlarge_shmem_struct(%i)", type);
|
|
return 0;
|
|
}
|
|
|
|
// Reallocate enough space for requested object
|
|
const size_t current = sharedMemory->size/sizeofobj;
|
|
realloc_shm(sharedMemory, current + allocation_step, sizeofobj, true);
|
|
|
|
// Add allocated memory to corresponding counter
|
|
*counter += allocation_step;
|
|
|
|
return sharedMemory->ptr;
|
|
}
|
|
|
|
static bool realloc_shm(SharedMemory *sharedMemory, const size_t size1, const size_t size2, const bool resize)
|
|
{
|
|
// Absolute target size
|
|
const size_t size = size1 * size2;
|
|
|
|
// Log that we are doing something here
|
|
char df[64] = { 0 };
|
|
const int percentage = get_dev_shm_usage(df);
|
|
|
|
// Log output
|
|
if(resize)
|
|
logg("Resizing \"%s\" from %zu to (%zu * %zu) == %zu (%s)",
|
|
sharedMemory->name, sharedMemory->size, size1, size2, size, df);
|
|
else
|
|
logg("Remapping \"%s\" from %zu to (%zu * %zu) == %zu",
|
|
sharedMemory->name, sharedMemory->size, size1, size2, size);
|
|
|
|
if(config.check.shmem > 0 && percentage > config.check.shmem)
|
|
log_resource_shortage(-1.0, 0, percentage, -1, SHMEM_PATH, df);
|
|
|
|
// Resize shard memory object if requested
|
|
// If not, we only remap a shared memory object which might have changed
|
|
// in another process. This happens when pihole-FTL forks due to incoming
|
|
// TCP requests.
|
|
if(resize)
|
|
{
|
|
// Verify shared memory ownership
|
|
verify_shmem_pid();
|
|
|
|
// Open shared memory object
|
|
const int fd = shm_open(sharedMemory->name, O_RDWR, S_IRUSR | S_IWUSR);
|
|
if(fd == -1)
|
|
{
|
|
logg("FATAL: realloc_shm(): Failed to open shared memory object \"%s\": %s",
|
|
sharedMemory->name, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Allocate shared memory object to specified size
|
|
// Using f[tl]allocate() will ensure that there's actually space for
|
|
// this file. Otherwise we end up with a sparse file that can give
|
|
// SIGBUS if we run out of space while writing to it.
|
|
const int ret = ftlallocate(fd, 0U, size);
|
|
if(ret != 0)
|
|
{
|
|
logg("FATAL: realloc_shm(): Failed to resize \"%s\" (%i) to %zu: %s (%i)",
|
|
sharedMemory->name, fd, size, strerror(errno), ret);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Close shared memory object file descriptor as it is no longer
|
|
// needed after having called f[tl]allocate()
|
|
close(fd);
|
|
|
|
// Update shm counters to indicate that at least one shared memory object changed
|
|
shmSettings->global_shm_counter++;
|
|
local_shm_counter++;
|
|
}
|
|
|
|
void *new_ptr = mremap(sharedMemory->ptr, sharedMemory->size, size, MREMAP_MAYMOVE);
|
|
if(new_ptr == MAP_FAILED)
|
|
{
|
|
logg("FATAL: realloc_shm(): mremap(%p, %zu, %zu, MREMAP_MAYMOVE): Failed to reallocate \"%s\": %s",
|
|
sharedMemory->ptr, sharedMemory->size, size, sharedMemory->name,
|
|
strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Update how much memory FTL uses
|
|
// We add the difference between updated and previous size
|
|
used_shmem += (size - sharedMemory->size);
|
|
|
|
if(config.debug & DEBUG_SHMEM)
|
|
{
|
|
if(sharedMemory->ptr == new_ptr)
|
|
logg("SHMEM pointer not updated: %p (%zu %zu)",
|
|
sharedMemory->ptr, sharedMemory->size, size);
|
|
else
|
|
logg("SHMEM pointer updated: %p -> %p (%zu %zu)",
|
|
sharedMemory->ptr, new_ptr, sharedMemory->size, size);
|
|
}
|
|
|
|
sharedMemory->ptr = new_ptr;
|
|
sharedMemory->size = size;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void delete_shm(SharedMemory *sharedMemory)
|
|
{
|
|
// Unmap shared memory (if mmapped)
|
|
if(sharedMemory->ptr != NULL)
|
|
{
|
|
if(munmap(sharedMemory->ptr, sharedMemory->size) != 0)
|
|
logg("delete_shm(): munmap(%p, %zu) failed: %s", sharedMemory->ptr, sharedMemory->size, strerror(errno));
|
|
}
|
|
|
|
// Now you can no longer `shm_open` the memory, and once all others
|
|
// unlink, it will be destroyed.
|
|
if(shm_unlink(sharedMemory->name) != 0)
|
|
logg("delete_shm(): shm_unlink(%s) failed: %s", sharedMemory->name, strerror(errno));
|
|
}
|
|
|
|
// Euclidean algorithm to return greatest common divisor of the numbers
|
|
static size_t __attribute__((const)) gcd(size_t a, size_t b)
|
|
{
|
|
while(b != 0)
|
|
{
|
|
size_t temp = b;
|
|
b = a % b;
|
|
a = temp;
|
|
}
|
|
return a;
|
|
}
|
|
|
|
// Function to return the optimal (minimum) size for page-aligned
|
|
// shared memory objects. This routine works by computing the LCM
|
|
// of two numbers, the pagesize and the size of a single element
|
|
// in the shared memory object
|
|
static size_t get_optimal_object_size(const size_t objsize, const size_t minsize)
|
|
{
|
|
// optsize and minsize are in units of objsize
|
|
const size_t optsize = pagesize / gcd(pagesize, objsize);
|
|
if(optsize < minsize)
|
|
{
|
|
if(config.debug & DEBUG_SHMEM)
|
|
{
|
|
logg("DEBUG: LCM(%i, %zu) == %zu < %zu",
|
|
pagesize, objsize,
|
|
optsize*objsize,
|
|
minsize*objsize);
|
|
}
|
|
|
|
// Upscale optimal size by a certain factor
|
|
// Logic of this computation:
|
|
// First part: Integer division, may cause clipping, e.g., 5/3 = 1
|
|
// Second part: Catch a possibly happened clipping event by adding
|
|
// one to the number: (5 % 3 != 0) is 1
|
|
const size_t multiplier = (minsize/optsize) + ((minsize % optsize != 0) ? 1u : 0u);
|
|
if(config.debug & DEBUG_SHMEM)
|
|
{
|
|
logg("DEBUG: Using %zu*%zu == %zu >= %zu",
|
|
multiplier, optsize*objsize,
|
|
multiplier*optsize*objsize,
|
|
minsize*objsize);
|
|
}
|
|
// As optsize ensures perfect page-alignment,
|
|
// any multiple of it will be aligned as well
|
|
return multiplier*optsize;
|
|
}
|
|
else
|
|
{
|
|
if(config.debug & DEBUG_SHMEM)
|
|
{
|
|
logg("DEBUG: LCM(%i, %zu) == %zu >= %zu",
|
|
pagesize, objsize,
|
|
optsize*objsize,
|
|
minsize*objsize);
|
|
}
|
|
|
|
// Return computed optimal size
|
|
return optsize;
|
|
}
|
|
}
|
|
|
|
// Enlarge shared memory to be able to hold at least one new record
|
|
void shm_ensure_size(void)
|
|
{
|
|
if(counters->queries >= counters->queries_MAX-1)
|
|
{
|
|
// Have to reallocate shared memory
|
|
queries = enlarge_shmem_struct(QUERIES);
|
|
if(queries == NULL)
|
|
{
|
|
logg("FATAL: Memory allocation failed! Exiting");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if(counters->upstreams >= counters->upstreams_MAX-1)
|
|
{
|
|
// Have to reallocate shared memory
|
|
upstreams = enlarge_shmem_struct(UPSTREAMS);
|
|
if(upstreams == NULL)
|
|
{
|
|
logg("FATAL: Memory allocation failed! Exiting");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if(counters->clients >= counters->clients_MAX-1)
|
|
{
|
|
// Have to reallocate shared memory
|
|
clients = enlarge_shmem_struct(CLIENTS);
|
|
if(clients == NULL)
|
|
{
|
|
logg("FATAL: Memory allocation failed! Exiting");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if(counters->domains >= counters->domains_MAX-1)
|
|
{
|
|
// Have to reallocate shared memory
|
|
domains = enlarge_shmem_struct(DOMAINS);
|
|
if(domains == NULL)
|
|
{
|
|
logg("FATAL: Memory allocation failed! Exiting");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if(counters->dns_cache_size >= counters->dns_cache_MAX-1)
|
|
{
|
|
// Have to reallocate shared memory
|
|
dns_cache = enlarge_shmem_struct(DNS_CACHE);
|
|
if(dns_cache == NULL)
|
|
{
|
|
logg("FATAL: Memory allocation failed! Exiting");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
if(shmSettings->next_str_pos + STRINGS_ALLOC_STEP >= shm_strings.size)
|
|
{
|
|
// Have to reallocate shared memory
|
|
if(enlarge_shmem_struct(STRINGS) == NULL)
|
|
{
|
|
logg("FATAL: Memory allocation failed! Exiting");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void reset_per_client_regex(const int clientID)
|
|
{
|
|
const unsigned int num_regex_tot = get_num_regex(REGEX_MAX); // total number
|
|
for(unsigned int i = 0u; i < num_regex_tot; i++)
|
|
{
|
|
// Zero-initialize/reset (= false) all regex (white + black)
|
|
set_per_client_regex(clientID, i, false);
|
|
}
|
|
}
|
|
|
|
void add_per_client_regex(unsigned int clientID)
|
|
{
|
|
const unsigned int num_regex_tot = get_num_regex(REGEX_MAX); // total number
|
|
const size_t size = get_optimal_object_size(1, counters->clients * num_regex_tot);
|
|
if(size > shm_per_client_regex.size &&
|
|
realloc_shm(&shm_per_client_regex, 1, size, true))
|
|
{
|
|
reset_per_client_regex(clientID);
|
|
counters->per_client_regex_MAX = size;
|
|
}
|
|
}
|
|
|
|
bool get_per_client_regex(const int clientID, const int regexID)
|
|
{
|
|
const unsigned int num_regex_tot = get_num_regex(REGEX_MAX); // total number
|
|
const unsigned int id = clientID * num_regex_tot + regexID;
|
|
const size_t maxval = shm_per_client_regex.size / sizeof(bool);
|
|
if(id > maxval)
|
|
{
|
|
logg("ERROR: get_per_client_regex(%d, %d): Out of bounds (%d > %d * %d, shm_per_client_regex.size = %zd)!",
|
|
clientID, regexID,
|
|
id, counters->clients, num_regex_tot, maxval);
|
|
return false;
|
|
}
|
|
return ((bool*) shm_per_client_regex.ptr)[id];
|
|
}
|
|
|
|
void set_per_client_regex(const int clientID, const int regexID, const bool value)
|
|
{
|
|
const unsigned int num_regex_tot = get_num_regex(REGEX_MAX); // total number
|
|
const unsigned int id = clientID * num_regex_tot + regexID;
|
|
const size_t maxval = shm_per_client_regex.size / sizeof(bool);
|
|
if(id > maxval)
|
|
{
|
|
logg("ERROR: set_per_client_regex(%d, %d, %s): Out of bounds (%d > %d * %d, shm_per_client_regex.size = %zd)!",
|
|
clientID, regexID, value ? "true" : "false",
|
|
id, counters->clients, num_regex_tot, maxval);
|
|
return;
|
|
}
|
|
((bool*) shm_per_client_regex.ptr)[id] = value;
|
|
}
|
|
|
|
static inline bool check_range(int ID, int MAXID, const char* type, const char *func, int line, const char *file)
|
|
{
|
|
// Check bounds
|
|
if(ID < 0 || ID > MAXID)
|
|
{
|
|
logg("ERROR: Trying to access %s ID %i, but maximum is %i", type, ID, MAXID);
|
|
logg(" found in %s() (%s:%i)", func, short_path(file), line);
|
|
return false;
|
|
}
|
|
|
|
// Everything okay
|
|
return true;
|
|
}
|
|
|
|
static inline bool check_magic(int ID, bool checkMagic, unsigned char magic, const char *type, const char *func, int line, const char *file)
|
|
{
|
|
// Check magic only if requested (skipped for new entries which are uninitialized)
|
|
if(checkMagic && magic != MAGICBYTE)
|
|
{
|
|
logg("ERROR: Trying to access %s ID %i, but magic byte is %x", type, ID, magic);
|
|
logg(" found in %s() (%s:%i)", func, short_path(file), line);
|
|
return false;
|
|
}
|
|
|
|
// Everything okay
|
|
return true;
|
|
}
|
|
|
|
queriesData* _getQuery(int queryID, bool checkMagic, int line, const char *func, const char *file)
|
|
{
|
|
// This does not exist, return a NULL pointer
|
|
if(queryID == -1)
|
|
return NULL;
|
|
|
|
// We are not in a locked situation, return a NULL pointer
|
|
if(config.debug & DEBUG_LOCKS && !is_our_lock())
|
|
{
|
|
logg("ERROR: Tried to obtain query pointer without lock in %s() (%s:%i)!",
|
|
func, short_path(file), line);
|
|
generate_backtrace();
|
|
return NULL;
|
|
}
|
|
|
|
if(check_range(queryID, counters->queries_MAX, "query", func, line, file) &&
|
|
check_magic(queryID, checkMagic, queries[queryID].magic, "query", func, line, file))
|
|
return &queries[queryID];
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
clientsData* _getClient(int clientID, bool checkMagic, int line, const char *func, const char *file)
|
|
{
|
|
// This does not exist, we return a NULL pointer
|
|
if(clientID == -1)
|
|
return NULL;
|
|
|
|
// We are not in a locked situation, return a NULL pointer
|
|
if(config.debug & DEBUG_LOCKS && !is_our_lock())
|
|
{
|
|
logg("ERROR: Tried to obtain client pointer without lock in %s() (%s:%i)!",
|
|
func, short_path(file), line);
|
|
generate_backtrace();
|
|
return NULL;
|
|
}
|
|
|
|
if(check_range(clientID, counters->clients_MAX, "client", func, line, file) &&
|
|
check_magic(clientID, checkMagic, clients[clientID].magic, "client", func, line, file))
|
|
return &clients[clientID];
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
domainsData* _getDomain(int domainID, bool checkMagic, int line, const char *func, const char *file)
|
|
{
|
|
// This does not exist, we return a NULL pointer
|
|
if(domainID == -1)
|
|
return NULL;
|
|
|
|
// We are not in a locked situation, return a NULL pointer
|
|
if(config.debug & DEBUG_LOCKS && !is_our_lock())
|
|
{
|
|
logg("ERROR: Tried to obtain domain pointer without lock in %s() (%s:%i)!",
|
|
func, short_path(file), line);
|
|
generate_backtrace();
|
|
return NULL;
|
|
}
|
|
|
|
if(check_range(domainID, counters->domains_MAX, "domain", func, line, file) &&
|
|
check_magic(domainID, checkMagic, domains[domainID].magic, "domain", func, line, file))
|
|
return &domains[domainID];
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
upstreamsData* _getUpstream(int upstreamID, bool checkMagic, int line, const char *func, const char *file)
|
|
{
|
|
// This does not exist, we return a NULL pointer
|
|
if(upstreamID == -1)
|
|
return NULL;
|
|
|
|
// We are not in a locked situation, return a NULL pointer
|
|
if(config.debug & DEBUG_LOCKS && !is_our_lock())
|
|
{
|
|
logg("ERROR: Tried to obtain upstream pointer without lock in %s() (%s:%i)!",
|
|
func, short_path(file), line);
|
|
generate_backtrace();
|
|
return NULL;
|
|
}
|
|
|
|
if(check_range(upstreamID, counters->upstreams_MAX, "upstream", func, line, file) &&
|
|
check_magic(upstreamID, checkMagic, upstreams[upstreamID].magic, "upstream", func, line, file))
|
|
return &upstreams[upstreamID];
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
DNSCacheData* _getDNSCache(int cacheID, bool checkMagic, int line, const char *func, const char *file)
|
|
{
|
|
// This does not exist, we return a NULL pointer
|
|
if(cacheID == -1)
|
|
return NULL;
|
|
|
|
// We are not in a locked situation, return a NULL pointer
|
|
if(config.debug & DEBUG_LOCKS && !is_our_lock())
|
|
{
|
|
logg("ERROR: Tried to obtain cache pointer without lock in %s() (%s:%i)!",
|
|
func, short_path(file), line);
|
|
generate_backtrace();
|
|
return NULL;
|
|
}
|
|
|
|
if(check_range(cacheID, counters->dns_cache_MAX, "dns_cache", func, line, file) &&
|
|
check_magic(cacheID, checkMagic, dns_cache[cacheID].magic, "dns_cache", func, line, file))
|
|
return &dns_cache[cacheID];
|
|
else
|
|
return NULL;
|
|
}
|