Merge pull request #1977 from pi-hole/new/ntp

Add NTP-server/client implementation
This commit is contained in:
Dominik
2024-06-29 21:48:25 +02:00
committed by GitHub
32 changed files with 1883 additions and 82 deletions
+3 -1
View File
@@ -293,6 +293,7 @@ add_executable(pihole-FTL
$<TARGET_OBJECTS:tomlc99>
$<TARGET_OBJECTS:config>
$<TARGET_OBJECTS:tools>
$<TARGET_OBJECTS:ntp>
)
if(STATIC)
set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON)
@@ -335,6 +336,7 @@ add_subdirectory(tre-regex)
add_subdirectory(syscalls)
add_subdirectory(config)
add_subdirectory(tools)
add_subdirectory(ntp)
find_library(LIBREADLINE NAMES libreadline${LIBRARY_SUFFIX} readline)
find_library(LIBHISTORY NAMES libhistory${LIBRARY_SUFFIX} history)
@@ -373,5 +375,5 @@ find_program(SETCAP setcap)
install(TARGETS pihole-FTL
RUNTIME DESTINATION bin
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE,CAP_CHOWN+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)")
install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE,CAP_CHOWN,CAP_SYS_TIME+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)")
+1
View File
@@ -37,6 +37,7 @@ static struct {
{
{ "dns", "DNS", "DNS server settings" },
{ "dhcp", "DHCP", "DHCP server settings" },
{ "ntp", "NTP", "Network Time Sync settings" },
{ "resolver", "Resolver", "Resolver settings" },
{ "database", "Database", "Database settings" },
{ "webserver", "HTTP/API", "Webserver and API settings" },
+55
View File
@@ -326,6 +326,43 @@ components:
type: array
items:
type: string
ntp:
type: object
properties:
ipv4:
type: object
properties:
active:
type: boolean
address:
type: string
x-format: ipv4
ipv6:
type: object
properties:
active:
type: boolean
address:
type: string
x-format: ipv6
sync:
type: object
properties:
server:
type: string
interval:
type: integer
count:
type: integer
rtc:
type: object
properties:
set:
type: boolean
device:
type: string
utc:
type: boolean
resolver:
type: object
properties:
@@ -549,6 +586,8 @@ components:
type: boolean
reserved:
type: boolean
ntp:
type: boolean
all:
type: boolean
topics:
@@ -661,6 +700,21 @@ components:
hosts:
- "11:22:33:44:55:66,192.168.1.123"
- "11:22:33:44:55:67,192.168.1.124,hostname"
ntp:
ipv4:
active: true
address: ""
ipv6:
active: true
address: ""
sync:
server: "pool.ntp.org"
interval: 3600
count: 8
rtc:
set: true
device: ""
utc: true
resolver:
resolveIPv4: true
resolveIPv6: true
@@ -761,6 +815,7 @@ components:
webserver: false
extra: false
reserved: false
ntp: false
all: false
config_one:
summary: One option
+44
View File
@@ -66,6 +66,10 @@
#include "files.h"
// resolveHostname()
#include "resolve.h"
// ntp_client()
#include "ntp/ntp.h"
// check_capability()
#include "capabilities.h"
// defined in dnsmasq.c
extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal);
@@ -305,6 +309,38 @@ void parse_args(int argc, char* argv[])
exit(write_teleporter_zip_to_disk() ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Create test NTP client
if((argc > 1 && argc < 5) && strcmp(argv[1], "ntp") == 0)
{
// Parse arguments
const bool update = (argc > 2 && strcmp(argv[2], "--update") == 0) ||
(argc > 3 && strcmp(argv[3], "--update") == 0);
const char *server = "127.0.0.1";
if(argc > 2 && strcmp(argv[2], "--update") != 0)
server = argv[2];
// Ensure we have the necessary capabilities
if(update && !check_capability(CAP_SYS_TIME))
{
puts("Insufficient capabilities to run NTP client");
const char *bold = cli_bold();
const char *normal = cli_normal();
printf("Try: %ssudo%s ", bold, normal);
for(int i = 0; i < argc; i++)
printf("%s ", argv[i]);
puts("");
exit(EXIT_FAILURE);
}
printf("Using NTP server: %s\n", server);
// Enable stdout printing
cli_mode = true;
log_ctrl(false, true);
readFTLconf(&config, false);
exit(ntp_client(server, update, true) ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Import teleporter archive through CLI
if(argc == 3 && strcmp(argv[1], "--teleporter") == 0)
{
@@ -1017,6 +1053,14 @@ void parse_args(int argc, char* argv[])
printf(" Encoding: %spihole-FTL idn2 %sdomain%s\n", green, cyan, normal);
printf(" Decoding: %spihole-FTL idn2 -d %spunycode%s\n\n", green, cyan, normal);
printf("%sNTP client:%s\n", yellow, normal);
printf(" Query an NTP server for the current time and print the\n");
printf(" result in human-readable format. An optional %sserver%s may be\n", cyan, normal);
printf(" as argument. If the server is omitted, 127.0.0.1 is used.\n\n");
printf(" The system time is updated on the system when the optional\n");
printf(" %s--update%s flag is given.\n\n", purple, normal);
printf(" Usage: %spihole-FTL ntp %s[server]%s %s[--update]%s\n\n", green, cyan, normal, purple, normal);
printf("%sOther:%s\n", yellow, normal);
printf("\t%sptr %sIP%s %s[tcp]%s Resolve IP address to hostname\n", green, cyan, normal, purple, normal);
printf("\t Append %stcp%s to use TCP instead of UDP\n", purple, normal);
+7
View File
@@ -141,6 +141,13 @@ bool check_capabilities(void)
log_warn("Required Linux capability CAP_CHOWN not available");
capabilities_okay = false;
}
if (!(data->permitted & (1 << CAP_SYS_TIME)) ||
!(data->effective & (1 << CAP_SYS_TIME)))
{
// Necessary for setting the system time in the NTP client
log_warn("Required Linux capability CAP_SYS_TIME not available");
capabilities_okay = false;
}
// Free allocated memory
free(hdr);
+76
View File
@@ -783,6 +783,76 @@ void initConfig(struct config *conf)
conf->dhcp.hosts.c = validate_stub; // Type-based checking + dnsmasq syntax checking
// struct ntp
conf->ntp.ipv4.active.k = "ntp.ipv4.active";
conf->ntp.ipv4.active.h = "Should FTL act as network time protocol (NTP) server (IPv4)?";
conf->ntp.ipv4.active.t = CONF_BOOL;
conf->ntp.ipv4.active.f = FLAG_RESTART_FTL;
conf->ntp.ipv4.active.d.b = true;
conf->ntp.ipv4.active.c = validate_stub; // Only type-based checking
conf->ntp.ipv4.address.k = "ntp.ipv4.address";
conf->ntp.ipv4.address.h = "IPv4 address to listen on for NTP requests";
conf->ntp.ipv4.address.a = cJSON_CreateStringReference("<valid IPv4 address> or empty string (\"\") for wildcard (0.0.0.0)");
conf->ntp.ipv4.address.t = CONF_STRUCT_IN_ADDR;
conf->ntp.ipv4.address.f = FLAG_RESTART_FTL;
memset(&conf->ntp.ipv4.address.d.in_addr, 0, sizeof(struct in_addr));
conf->ntp.ipv4.address.c = validate_stub; // Only type-based checking
conf->ntp.ipv6.active.k = "ntp.ipv6.active";
conf->ntp.ipv6.active.h = "Should FTL act as network time protocol (NTP) server (IPv6)?";
conf->ntp.ipv6.active.t = CONF_BOOL;
conf->ntp.ipv6.active.f = FLAG_RESTART_FTL;
conf->ntp.ipv6.active.d.b = true;
conf->ntp.ipv6.active.c = validate_stub; // Only type-based checking
conf->ntp.ipv6.address.k = "ntp.ipv6.address";
conf->ntp.ipv6.address.h = "IPv6 address to listen on for NTP requests";
conf->ntp.ipv6.address.a = cJSON_CreateStringReference("<valid IPv6 address> or empty string (\"\") for wildcard (::)");
conf->ntp.ipv6.address.t = CONF_STRUCT_IN6_ADDR;
conf->ntp.ipv6.address.f = FLAG_RESTART_FTL;
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.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");
conf->ntp.sync.server.t = CONF_STRING;
conf->ntp.sync.server.d.s = (char*)"pool.ntp.org";
conf->ntp.sync.server.c = validate_stub; // Only type-based checking
conf->ntp.sync.interval.k = "ntp.sync.interval";
conf->ntp.sync.interval.h = "Interval in seconds between successive synchronization attempts with the NTP server";
conf->ntp.sync.interval.t = CONF_UINT;
conf->ntp.sync.interval.d.ui = 3600;
conf->ntp.sync.interval.c = validate_stub; // Only type-based checking
conf->ntp.sync.count.k = "ntp.sync.count";
conf->ntp.sync.count.h = "Number of NTP syncs to perform and average before updating the system time";
conf->ntp.sync.count.t = CONF_UINT;
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.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.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
// struct resolver
conf->resolver.resolveIPv6.k = "resolver.resolveIPv6";
conf->resolver.resolveIPv6.h = "Should FTL try to resolve IPv6 addresses to hostnames?";
@@ -1400,6 +1470,12 @@ void initConfig(struct config *conf)
conf->debug.reserved.d.b = false;
conf->debug.reserved.c = validate_stub; // Only type-based checking
conf->debug.ntp.k = "debug.ntp";
conf->debug.ntp.h = "Print information about NTP synchronization";
conf->debug.ntp.t = CONF_BOOL;
conf->debug.ntp.d.b = false;
conf->debug.ntp.c = validate_stub; // Only type-based checking
conf->debug.all.k = "debug.all";
conf->debug.all.h = "Set all debug flags at once. This is a convenience option to enable all debug flags at once. Note that this option is not persistent, setting it to true will enable all *remaining* debug flags but unsetting it will disable *all* debug flags.";
conf->debug.all.t = CONF_ALL_DEBUG_BOOL;
+22
View File
@@ -191,6 +191,27 @@ struct config {
struct conf_item hosts;
} dhcp;
struct {
struct {
struct conf_item active;
struct conf_item address;
} ipv4;
struct {
struct conf_item active;
struct conf_item address;
} ipv6;
struct {
struct conf_item server;
struct conf_item interval;
struct conf_item count;
} sync;
struct {
struct conf_item set;
struct conf_item device;
struct conf_item utc;
} rtc;
} ntp;
struct {
struct conf_item resolveIPv4;
struct conf_item resolveIPv6;
@@ -313,6 +334,7 @@ struct config {
struct conf_item webserver;
struct conf_item extra;
struct conf_item reserved;
struct conf_item ntp;
// all must be the last item in this struct
struct conf_item all;
} debug;
+10 -1
View File
@@ -582,11 +582,20 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
fputs("log-dhcp\n\n", pihole_conf);
}
// Check if IPv4 NTP server is active and broadcast it as DHCP option
if(conf->ntp.ipv4.active.v.b)
{
fputs("# Add NTP server to DHCP\n", pihole_conf);
// The special address 0.0.0.0 is taken to mean "the
// address of the machine running the DHCP server"
fputs("dhcp-option=option:ntp-server,0.0.0.0\n\n", pihole_conf);
}
// Add option to ignore unknown clients if enabled
if(conf->dhcp.ignoreUnknownClients.v.b)
{
fputs("# Ignore clients not configured below\n", pihole_conf);
fputs("dhcp-ignore=tag:!known\n", pihole_conf);
fputs("dhcp-ignore=tag:!known\n\n", pihole_conf);
}
// Add per-host parameters
+8 -4
View File
@@ -265,16 +265,19 @@ pid_t FTL_gettid(void)
static void terminate_threads(void)
{
struct timespec ts;
// Terminate threads before closing database connections and finishing shared memory
killed = true;
// Try to join threads to ensure cancellation has succeeded
log_info("Waiting for threads to join");
for(int i = 0; i < THREADS_MAX; i++)
{
log_debug(DEBUG_EXTRA, "Joining %s thread (%d)", thread_names[i], i);
// Skip threads that have never been started or which are already stopped
if(!thread_running[i])
if(threads[i] == 0)
{
log_debug(DEBUG_EXTRA, "Skipping thread as it was never started");
continue;
}
// Cancel thread if it is idle
if(thread_cancellable[i])
@@ -285,6 +288,8 @@ static void terminate_threads(void)
}
// Cancel thread if we cannot set a timeout for joining
struct timespec ts;
memset(&ts, 0, sizeof(ts));
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
{
log_info("Thread %s (%d) is busy, cancelling it (cannot set timeout).",
@@ -297,8 +302,7 @@ static void terminate_threads(void)
ts.tv_sec += 2;
// Try to join thread and cancel it if it is still busy
const int s = pthread_timedjoin_np(threads[i], NULL, &ts);
if(s != 0)
if(pthread_timedjoin_np(threads[i], NULL, &ts) != 0)
{
log_info("Thread %s (%d) is still busy, cancelling it.",
thread_names[i], i);
+9
View File
@@ -103,6 +103,15 @@ sqlite3* _dbopen(const bool readonly, const bool create, const char *func, const
return NULL;
}
// If the database is opened in read-write mode, actually check if it is
// writable. If it is not, close the database and return an error
if(!readonly && sqlite3_db_readonly(db, NULL))
{
log_err("Cannot open database in read-write mode");
dbclose(&db);
return NULL;
}
// Explicitly set busy handler to value defined in FTL.h
rc = sqlite3_busy_timeout(db, DATABASE_BUSY_TIMEOUT);
if( rc != SQLITE_OK )
-2
View File
@@ -78,13 +78,11 @@ static bool analyze_database(sqlite3 *db)
}
#define DBOPEN_OR_AGAIN() { if(!db) db = dbopen(false, false); if(!db) { thread_sleepms(DB, 5000); continue; } }
#define BREAK_IF_KILLED() { if(killed) break; }
#define DBCLOSE_OR_BREAK() { dbclose(&db); BREAK_IF_KILLED(); }
void *DB_thread(void *val)
{
// Set thread name
thread_names[DB] = "database";
thread_running[DB] = true;
prctl(PR_SET_NAME, thread_names[DB], 0, 0, 0);
+75 -40
View File
@@ -97,6 +97,8 @@ static const char *get_message_type_str(const enum message_type type)
return "CERTIFICATE_DOMAIN_MISMATCH";
case CONNECTION_ERROR_MESSAGE:
return "CONNECTION_ERROR";
case NTP_MESSAGE:
return "NTP";
case MAX_MESSAGE:
default:
return "UNKNOWN";
@@ -131,6 +133,8 @@ static enum message_type get_message_type_from_string(const char *typestr)
return CERTIFICATE_DOMAIN_MISMATCH_MESSAGE;
else if (strcmp(typestr, "CONNECTION_ERROR") == 0)
return CONNECTION_ERROR_MESSAGE;
else if (strcmp(typestr, "NTP") == 0)
return NTP_MESSAGE;
else
return MAX_MESSAGE;
}
@@ -230,6 +234,14 @@ static unsigned char message_blob_types[MAX_MESSAGE][5] =
SQLITE_NULL, // not used
SQLITE_NULL, // not used
SQLITE_NULL // not used
},
{
// NTP: The message column contains the warning/error
SQLITE_TEXT, // level (warning/error)
SQLITE_TEXT, // component (server/client)
SQLITE_NULL, // not used
SQLITE_NULL, // not used
SQLITE_NULL // not used
}
};
// Create message table in the database
@@ -320,10 +332,8 @@ static int _add_message(const enum message_type type,
sqlite3 *db;
// Open database connection
if((db = dbopen(false, false)) == NULL)
{
log_err("add_message() - Failed to open DB");
// Reason for failure is logged in dbopen()
return -1;
}
// Ensure there are no duplicates when adding messages
sqlite3_stmt* stmt = NULL;
@@ -537,7 +547,7 @@ static void format_regex_message(char *plain, const int sizeof_plain, char *html
}
if(snprintf(html, sizeof_html, "Encountered an error when processing <a href=\"groups-domains.lp?domainid=%d\">regex %s filter with ID %d</a>: <pre>%s</pre>Error message: <pre>%s</pre>",
dbindex, type, dbindex, escaped_regex, escaped_warning))
dbindex, type, dbindex, escaped_regex, escaped_warning) > sizeof_html)
log_warn("format_regex_message(): Buffer too small to hold HTML message, warning truncated");
free(escaped_regex);
@@ -900,6 +910,20 @@ static void format_connection_error(char *plain, const int sizeof_plain, char *h
free(escaped_server);
}
static void format_ntp_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
const char *message, const char *level, const char *who)
{
if(snprintf(plain, sizeof_plain, "%s NTP %s: %s", level, who, message) > sizeof_plain)
log_warn("format_ntp_message(): Buffer too small to hold plain message, warning truncated");
// Return early if HTML text is not required
if(sizeof_html < 1 || html == NULL)
return;
if(snprintf(html, sizeof_html, "%s in NTP %s:<pre>%s</pre>", level, who, message) > sizeof_html)
log_warn("format_ntp_message(): Buffer too small to hold HTML message, warning truncated");
}
int count_messages(const bool filter_dnsmasq_warnings)
{
int count = 0;
@@ -1147,6 +1171,18 @@ bool format_messages(cJSON *array)
break;
}
case NTP_MESSAGE:
{
const char *message = (const char*)sqlite3_column_text(stmt, 3);
const char *level = (const char*)sqlite3_column_text(stmt, 4);
const char *who = (const char*)sqlite3_column_text(stmt, 5);
format_ntp_message(plain, sizeof(plain), html, sizeof(html),
message, level, who);
break;
}
case MAX_MESSAGE: // Fall through
default:
log_warn("format_messages() - Unknown message type: %s", mtypestr);
@@ -1206,9 +1242,7 @@ void logg_regex_warning(const char *type, const char *warning, const int dbindex
return;
// Add to database
const int rowid = add_message(REGEX_MESSAGE, regex, type, warning, dbindex);
if(rowid == -1)
log_err("logg_regex_warning(): Failed to add message to database");
add_message(REGEX_MESSAGE, regex, type, warning, dbindex);
}
void logg_subnet_warning(const char *ip, const int matching_count, const char *matching_ids,
@@ -1226,10 +1260,8 @@ void logg_subnet_warning(const char *ip, const int matching_count, const char *m
log_warn("%s", buf);
// Log to database
const int rowid = add_message(SUBNET_MESSAGE, ip, matching_count, names, matching_ids, chosen_match_text, chosen_match_id);
add_message(SUBNET_MESSAGE, ip, matching_count, names, matching_ids, chosen_match_text, chosen_match_id);
if(rowid == -1)
log_err("logg_subnet_warning(): Failed to add message to database");
free(names);
}
@@ -1248,10 +1280,8 @@ void logg_hostname_warning(const char *ip, const char *name, const unsigned int
log_warn("%s", buf);
// Log to database
const int rowid = add_message(HOSTNAME_MESSAGE, ip, name, (const int)pos);
add_message(HOSTNAME_MESSAGE, ip, name, (const int)pos);
if(rowid == -1)
log_err("logg_hostname_warning(): Failed to add message to database");
}
void logg_fatal_dnsmasq_message(const char *message)
@@ -1264,10 +1294,8 @@ void logg_fatal_dnsmasq_message(const char *message)
log_crit("%s", buf);
// Log to database
const int rowid = add_message_no_args(DNSMASQ_CONFIG_MESSAGE, message);
add_message_no_args(DNSMASQ_CONFIG_MESSAGE, message);
if(rowid == -1)
log_err("logg_fatal_dnsmasq_message(): Failed to add message to database");
}
void logg_rate_limit_message(const char *clientIP, const unsigned int rate_limit_count)
@@ -1282,10 +1310,8 @@ void logg_rate_limit_message(const char *clientIP, const unsigned int rate_limit
log_info("%s", buf);
// Log to database
const int rowid = add_message(RATE_LIMIT_MESSAGE, clientIP, config.dns.rateLimit.count.v.ui, config.dns.rateLimit.interval.v.ui, turnaround);
add_message(RATE_LIMIT_MESSAGE, clientIP, config.dns.rateLimit.count.v.ui, config.dns.rateLimit.interval.v.ui, turnaround);
if(rowid == -1)
log_err("logg_rate_limit_message(): Failed to add message to database");
}
void logg_warn_dnsmasq_message(char *message)
@@ -1298,10 +1324,8 @@ void logg_warn_dnsmasq_message(char *message)
log_warn("%s", buf);
// Log to database
const int rowid = add_message_no_args(DNSMASQ_WARN_MESSAGE, message);
add_message_no_args(DNSMASQ_WARN_MESSAGE, message);
if(rowid == -1)
log_err("logg_warn_dnsmasq_message(): Failed to add message to database");
}
void log_resource_shortage(const double load, const int nprocs, const int shmem, const int disk, const char *path, const char *msg)
@@ -1317,10 +1341,9 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
log_warn("%s", buf);
// Log to database
const int rowid = add_message(LOAD_MESSAGE, "excessive load", load, nprocs);
add_message(LOAD_MESSAGE, "excessive load", load, nprocs);
if(rowid == -1)
log_err("log_resource_shortage(): Failed to add message to database");
}
else if(shmem > -1)
{
@@ -1330,10 +1353,9 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
log_warn("%s", buf);
// Log to database
const int rowid = add_message(SHMEM_MESSAGE, path, shmem, msg);
add_message(SHMEM_MESSAGE, path, shmem, msg);
if(rowid == -1)
log_err("log_resource_shortage(): Failed to add message to database");
}
else if(disk > -1)
{
@@ -1367,12 +1389,11 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
log_warn("%s", buf);
// Log to database
const int rowid = fsdetails != NULL ?
fsdetails != NULL ?
add_message(DISK_MESSAGE_EXTENDED, path, disk, msg, fsdetails->mnt_type, fsdetails->mnt_dir) :
add_message(DISK_MESSAGE, path, disk, msg);
if(rowid == -1)
log_err("log_resource_shortage(): Failed to add message to database");
}
}
@@ -1386,10 +1407,8 @@ void logg_inaccessible_adlist(const int dbindex, const char *address)
log_warn("%s", buf);
// Log to database
const int rowid = add_message(INACCESSIBLE_ADLIST_MESSAGE, address, dbindex);
add_message(INACCESSIBLE_ADLIST_MESSAGE, address, dbindex);
if(rowid == -1)
log_err("logg_inaccessible_adlist(): Failed to add message to database");
}
void log_certificate_domain_mismatch(const char *certfile, const char *domain)
@@ -1402,10 +1421,8 @@ void log_certificate_domain_mismatch(const char *certfile, const char *domain)
log_warn("%s", buf);
// Log to database
const int rowid = add_message(CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, certfile, domain);
add_message(CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, certfile, domain);
if(rowid == -1)
log_err("log_certificate_domain_mismatch(): Failed to add message to database");
}
void log_connection_error(const char *server, const char *reason, const char *error)
@@ -1418,8 +1435,26 @@ void log_connection_error(const char *server, const char *reason, const char *er
log_warn("%s", buf);
// Log to database
const int rowid = add_message(CONNECTION_ERROR_MESSAGE, server, reason, error);
add_message(CONNECTION_ERROR_MESSAGE, server, reason, error);
}
void log_ntp_message(const bool error, const bool server, const char *message)
{
const char *who = server ? "server" : "client";
const char *level = error ? "Error" : "Warning";
// Create message
char buf[2048];
format_ntp_message(buf, sizeof(buf), NULL, 0, message, level, who);
// Log to FTL.log
if(error)
log_err("%s", buf);
else
log_warn("%s", buf);
// Log to database
add_message(NTP_MESSAGE, message, level, who);
if(rowid == -1)
log_err("logg_connection_error(): Failed to add message to database");
}
+1
View File
@@ -30,5 +30,6 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
void logg_inaccessible_adlist(const int dbindex, const char *address);
void log_certificate_domain_mismatch(const char *certfile, const char *domain);
void log_connection_error(const char *server, const char *reason, const char *error);
void log_ntp_message(const bool error, const bool server, const char *message);
#endif //MESSAGETABLE_H
+27
View File
@@ -21,8 +21,11 @@
#include "overTime.h"
#include "database/common.h"
#include "timers.h"
// runGC()
#include "gc.h"
static sqlite3 *_memdb = NULL;
static bool store_in_database = false;
static double new_last_timestamp = 0;
static unsigned int new_total = 0, new_blocked = 0;
static unsigned long last_mem_db_idx = 0, last_disk_db_idx = 0;
@@ -1367,6 +1370,11 @@ bool queries_to_database(void)
log_debug(DEBUG_DATABASE, "Not storing query in database as there are none");
return true;
}
if(!store_in_database)
{
log_debug(DEBUG_DATABASE, "Not storing query in database as this is disabled");
return true;
}
// Loop over recent queries and store new or changed ones in the
// in-memory database
@@ -1626,3 +1634,22 @@ bool queries_to_database(void)
return true;
}
void load_queries_from_disk(void)
{
// Compensate for possible jumps in time
runGC(time(NULL), NULL, false);
// Skip if we are not supposed to load queries from disk
if(!config.database.DBimport.v.b)
return;
// Try to import queries from long-term database if available
import_queries_from_disk();
DB_read_queries();
// Log some information about the imported queries (if any)
log_counter_info();
store_in_database = true;
}
+1
View File
@@ -119,6 +119,7 @@ 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);
+10 -10
View File
@@ -58,6 +58,8 @@
#include "config/config.h"
// FTL_fork_and_bind_sockets()
#include "main.h"
// ntp_server_start()
#include "ntp/ntp.h"
// Private prototypes
static void print_flags(const unsigned int flags);
@@ -2902,19 +2904,9 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
// Flush messages stored in the long-term database
flush_message_table();
// Try to import queries from long-term database if available
if(config.database.DBimport.v.b)
{
import_queries_from_disk();
DB_read_queries();
}
// Initialize in-memory database starting index
update_disk_db_idx();
// Log some information about the imported queries (if any)
log_counter_info();
// Handle real-time signals in this process (and its children)
// Helper processes are already split from the main instance
// so they will not listen to real-time signals
@@ -2924,8 +2916,16 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
// 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_init(&attr);
// Initialize NTP server
ntp_server_start(&attr);
// Start NTP sync thread
ntp_start_sync_thread(&attr);
// Start database thread if database is used
if(pthread_create( &threads[DB], &attr, DB_thread, NULL ) != 0)
{
+3 -1
View File
@@ -162,6 +162,7 @@ enum debug_flag {
DEBUG_WEBSERVER,
DEBUG_EXTRA,
DEBUG_RESERVED,
DEBUG_NTP,
DEBUG_MAX
} __attribute__ ((packed));
@@ -249,8 +250,8 @@ enum thread_types {
DB,
GC,
DNSclient,
CONF_READER,
TIMER,
NTP,
THREADS_MAX
} __attribute__ ((packed));
@@ -275,6 +276,7 @@ enum message_type {
DISK_MESSAGE_EXTENDED,
CERTIFICATE_DOMAIN_MISMATCH_MESSAGE,
CONNECTION_ERROR_MESSAGE,
NTP_MESSAGE,
MAX_MESSAGE,
} __attribute__ ((packed));
-1
View File
@@ -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_names[GC] = "housekeeper";
thread_running[GC] = true;
prctl(PR_SET_NAME, thread_names[GC], 0, 0, 0);
+2
View File
@@ -219,6 +219,8 @@ const char *debugstr(const enum debug_flag flag)
return "DEBUG_WEBSERVER";
case DEBUG_RESERVED:
return "DEBUG_RESERVED";
case DEBUG_NTP:
return "DEBUG_NTP";
case DEBUG_MAX:
return "DEBUG_MAX";
case DEBUG_NONE: // fall through
+20
View File
@@ -0,0 +1,20 @@
# Pi-hole: A black hole for Internet advertisements
# (c) 2024 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# FTL Engine
# /src/ntp/CMakeList.txt
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
set(ntp_sources
server.c
client.c
rtc.c
ntp.h
)
add_library(ntp OBJECT ${ntp_sources})
target_compile_options(ntp PRIVATE "${EXTRAWARN}")
target_include_directories(ntp PRIVATE ${PROJECT_SOURCE_DIR}/src)
+641
View File
@@ -0,0 +1,641 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2024 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* NTP client routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "ntp.h"
// close()
#include <unistd.h>
// clock_gettime()
#include <sys/time.h>
// socket(), connect(), send(), recv(), AF_INET, SOCK_DGRAM, IPPROTO_UDP
#include <sys/socket.h>
// getaddrinfo(), freeaddrinfo(), struct addrinfo
#include <netdb.h>
// memcpy()
#include <string.h>
// pow()
#include <math.h>
// ctime()
#include <time.h>
// errno
#include <errno.h>
// PRIi64
#include <inttypes.h>
// config struct
#include "config/config.h"
// adjtime()
#include <sys/time.h>
// threads[]
#include "daemon.h"
// thread_names[]
#include "signals.h"
// adjtimex()
#include <sys/timex.h>
// log_ntp_message()
#include "database/message-table.h"
// load_queries_from_disk()
#include "database/query-table.h"
struct ntp_sync
{
bool valid;
uint64_t org;
uint64_t xmt;
double theta;
double delta;
double precision;
};
// Create minimal NTP request, see server implementation for details about the
// packet structure
static bool request(int fd, const char *server, struct ntp_sync *ntp)
{
// NTP Packet buffer
unsigned char buf[48] = {0};
// LI = 0, VN = 4 (current version), Mode = 3 (Client)
buf[0] = 0x23;
// Minimum poll interval (2^6 = 64 seconds)
buf[2] = 0x06;
// Set Reference Timestamp (ref) to 0
// This is the time at which the local clock was last set or corrected.
memset(&buf[8], 0, sizeof(uint64_t));
// Set Origin Timestamp (org) in NTP format
ntp->org = gettime64();
const uint64_t norg = hton64(ntp->org);
memcpy(&buf[40], &norg, sizeof(norg));
// Send request
if(send(fd, buf, 48, 0) != 48)
{
log_err("Failed to send data to NTP server %s: %s",
server, errno == EAGAIN ? "Timeout" : strerror(errno));
return false;
}
return true;
}
// Display NTP time in human-readable format
// This function is similar to get_timestr() in src/log.c but differs in that it
// includes microseconds whereas get_timestr() only includes milliseconds
static void format_NTP_time(char time_str[TIMESTR_SIZE], const uint64_t ntp_time)
{
struct timeval client_time;
client_time.tv_sec = NTPtoSEC(ntp_time);
client_time.tv_usec = NTPtoUSEC(ntp_time);
struct tm *client_tm = localtime(&client_time.tv_sec);
snprintf(time_str, TIMESTR_SIZE, "%04i-%02i-%02i %02i:%02i:%02i.%06"PRIi64" %s",
client_tm->tm_year + 1900, client_tm->tm_mon + 1, client_tm->tm_mday,
client_tm->tm_hour, client_tm->tm_min, client_tm->tm_sec, client_time.tv_usec,
client_tm->tm_zone);
time_str[TIMESTR_SIZE - 1] = '\0';
}
// Print NTP timestamp in human-readable form for debugging
void print_debug_time(const char *label, const uint32_t *u32p, const uint64_t ntp_time)
{
// Get the time from the appropriate buffer
uint64_t timevar;
if(u32p != NULL)
{
memcpy(&timevar, u32p, sizeof(uint64_t));
// Convert to host byte order
timevar = ntoh64(timevar);
}
else
{
// Use the provided time (already in host byte order)
timevar = ntp_time;
}
// Format the time
char time_str[TIMESTR_SIZE];
format_NTP_time(time_str, timevar);
// Print the time
log_debug(DEBUG_NTP, "%s: %08"PRIx64".%08"PRIx64" = %s", label,
(timevar >> 32) & 0xFFFFFFFF, timevar & 0xFFFFFFFF, time_str);
}
static uint64_t get_new_time(struct timeval *unix_time, const double offset)
{
// Get current time
gettimeofday(unix_time, NULL);
// Convert from double to native format (signed) and add to the
// current time. Note the addition is done in native format to
// avoid overflow or loss of precision.
const uint64_t ntp_time = U2LFP(*unix_time) + D2LFP(offset);
// Convert NTP to native format
unix_time->tv_sec = NTPtoSEC(ntp_time);
unix_time->tv_usec = NTPtoUSEC(ntp_time);
return ntp_time;
}
static bool settime_step(struct timeval *unix_time, const double offset)
{
log_debug(DEBUG_NTP, "Stepping system time by %e s", offset);
// Set time immediately
if(settimeofday(unix_time, NULL) != 0)
{
char errbuf[1024];
strncpy(errbuf, "Failed to set time during NTP sync: ", sizeof(errbuf));
strncat(errbuf, errno == EPERM ? "Insufficient permissions" : strerror(errno), sizeof(errbuf) - strlen(errbuf) - 1);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, false, errbuf);
return false;
}
return true;
}
static bool settime_skew(const double offset)
{
// This function gradually adjusts the system clock.
//
// Linux uses David L. Mills' clock adjustment algorithm (see RFC 5905).
// If the adjustment in delta is positive, then the system clock is
// speeded up by some small percentage (i.e., by adding a small amount
// of time to the clock value in each second) until the adjustment has
// been completed. If the adjustment in delta is negative, then the
// clock is slowed down in a similar fashion.
//
// If a clock adjustment from an earlier adjtime() call is already in
// progress at the time of a later adjtime() call, and delta is not NULL
// for the later call, then the earlier adjustment is stopped, but any
// already completed part of that adjustment is not undone.
//
// The adjustment that adjtimex() makes to the clock is carried out in
// such a manner that the clock is always monotonically increasing.
// Using adjtimex() to adjust the time prevents the problems that can be
// caused for certain applications (e.g., make(1)) by abrupt positive or
// negative jumps in the system time.
//
// adjtimex() is intended to be used to make small adjustments to the
// system time. The actual time adjustment rate is implementation-specific
// but is typically on the order of 500 ppm, i.e., 0.5 ms/s.
//
// man rtc(4) adds:
// When the kernel's system time is synchronized with an external
// reference using adjtimex() it will update a designated RTC
// periodically every 11 minutes.
struct timex tx = { 0 };
tx.offset = 1000000 * offset;
tx.modes = ADJ_OFFSET_SINGLESHOT;
log_debug(DEBUG_NTP, "Gradually adjusting system time by %ld us", tx.offset);
if(adjtimex(&tx) < 0)
{
char errbuf[1024];
strncpy(errbuf, "Failed to adjust time during NTP sync: ", sizeof(errbuf));
strncat(errbuf, errno == EPERM ? "Insufficient permissions" : strerror(errno), sizeof(errbuf) - strlen(errbuf) - 1);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, false, errbuf);
return false;
}
return true;
}
static bool reply(int fd, const char *server, struct ntp_sync *ntp, const bool verbose)
{
// NTP Packet buffer
unsigned char buf[48];
// Receive reply
if(recv(fd, buf, 48, 0) < 48)
{
log_err("Failed to receive data from NTP server %s: %s",
server, errno == EAGAIN ? "Timeout" : strerror(errno));
return false;
}
// Extract precision of server clock
signed char rho = (signed char)buf[3];
if(rho < -32 || rho > 0)
{
// Accepted limits are 2^-32 (~ 0.2 nanoseconds)
// to 2^0 (= 1 second)
char errbuf[1024];
snprintf(errbuf, sizeof(errbuf), "Received NTP reply has invalid precision: 2^(%i), assuming microsecond accuracy", rho);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(false, false, errbuf);
rho = -19;
}
// Compute precision of server clock in seconds 2^rho
ntp->precision = pow(2, rho);
// Extract root delay and root dispersion of server clock
uint32_t srv_root_delay, srv_root_dispersion;
memcpy(&srv_root_delay, &buf[4], sizeof(srv_root_delay));
memcpy(&srv_root_dispersion, &buf[8], sizeof(srv_root_dispersion));
// Extract Transmit Timestamp
uint64_t netbuffer;
// ref = Reference Timestamp (Time at which the clock was last set or corrected)
memcpy(&netbuffer, &buf[16], sizeof(netbuffer));
const uint64_t ref = ntoh64(netbuffer);
// org = Origin Timestamp (Transmit Timestamp @ Client)
memcpy(&netbuffer, &buf[24], sizeof(netbuffer));
const uint64_t org = ntoh64(netbuffer);
// rec = Receive Timestamp (Receive Timestamp @ Server)
memcpy(&netbuffer, &buf[32], sizeof(netbuffer));
const uint64_t rec = ntoh64(netbuffer);
// xmt = Transmit Timestamp (Transmit Timestamp @ Server)
memcpy(&netbuffer, &buf[40], sizeof(netbuffer));
ntp->xmt = ntoh64(netbuffer);
// dst = Destination Timestamp (Receive Timestamp @ Client)
uint64_t dst = gettime64();
// Check org_ and org are identical (otherwise, the reply corresponds to
// a different request and should be ignored), note that the byte order
// of the received packet is already converted while org_ is still in
// network byte order
if(ntp->org != org)
{
log_warn("Received NTP reply does not match request (request %"PRIx64", reply %"PRIx64"), ignoring",
ntp->org, org);
return false;
}
// Check stratum, mode, version, etc.
if((buf[0] & 0x07) != 4)
{
log_warn("Received NTP reply has invalid version, ignoring");
return false;
}
// Calculate delay and offset
const double T1 = ntp->org / FRAC;
const double T2 = rec / FRAC;
const double T3 = ntp->xmt / FRAC;
const double T4 = dst / FRAC;
// RFC 5905, Section 8: On-wire protocol
// It is recommended to use double precision floating point arithmetic
// for the calculations to allow unambiguous interpretation of the
// results within the maximum adjustment range of 68 years.
// Compute offset of client clock relative to server clock
ntp->theta = ( ( T2 - T1 ) + ( T3 - T4 ) ) / 2;
// Compute round-trip delay, which represents the delay of the packet
// passing through the network, which can be due switches and network
// technologies are highly variable
ntp->delta = ( T4 - T1 ) - ( T3 - T2 );
// This reply is valid
ntp->valid = true;
// In some scenarios where the initial frequency offset of the client is
// relatively large and the actual propagation time small, it is
// possible for the delay computation to become negative. For instance,
// if the frequency difference is 100 ppm and the interval T4-T1 is 64
// s, the apparent delay is -6.4 ms. Since negative values are
// misleading in subsequent computations, the value of delta should be
// clamped not less than s.rho, where s.rho is the system precision
// described in Section 11.1, expressed in seconds.
if(ntp->delta < ntp->precision)
ntp->delta = 0;
// Return early if not verbose
if(!config.debug.ntp.v.b)
return true;
// Print current time at server
print_debug_time("Server reference time", NULL, ref);
// Print current time at client
print_debug_time("Current time at client", NULL, dst);
// Print current time at server
print_debug_time("Current time at server", NULL, ntp->xmt);
// Print offset and delay
log_debug(DEBUG_NTP, "Time offset: %e s", ntp->theta);
log_debug(DEBUG_NTP, "Round-trip delay: %e s", ntp->delta);
const uint32_t root_delay = ntohl(srv_root_delay);
log_debug(DEBUG_NTP, "Root delay: %e s", FP2D(root_delay));
const uint32_t root_dispersion = ntohl(srv_root_dispersion);
log_debug(DEBUG_NTP, "Root dispersion: %e s", FP2D(root_dispersion));
return true;
}
static int getsock(const struct addrinfo *saddr)
{
// Create UDP socket
const int protocol = saddr->ai_addrlen == sizeof(struct sockaddr_in6) ? AF_INET6 : AF_INET;
const int s = socket(protocol, SOCK_DGRAM, IPPROTO_UDP);
if(s == -1)
{
char errbuf[1024];
strncpy(errbuf, "Cannot create UDP socket: ", sizeof(errbuf));
strncat(errbuf, strerror(errno), sizeof(errbuf) - strlen(errbuf) - 1);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, false, errbuf);
return -1;
}
// Set socket timeout to 5 seconds
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0)
{
char errbuf[1024];
strncpy(errbuf, "Cannot set socket timeout: ", sizeof(errbuf));
strncat(errbuf, strerror(errno), sizeof(errbuf) - strlen(errbuf) - 1);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, false, errbuf);
close(s);
return -1;
}
// Set address to send to/receive from
if(connect(s, saddr->ai_addr, saddr->ai_addrlen) != 0)
{
char errbuf[1024];
strncpy(errbuf, "Cannot connect to NTP server: ", sizeof(errbuf));
strncat(errbuf, strerror(errno), sizeof(errbuf) - strlen(errbuf) - 1);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, false, errbuf);
close(s);
return -1;
}
// Return socket
return s;
}
bool ntp_client(const char *server, const bool settime, const bool print)
{
// Resolve server address
int eai;
struct addrinfo *saddr;
if((eai = getaddrinfo(server, "ntp", NULL, &saddr)) != 0)
{
char errbuf[1024];
strncpy(errbuf, "Cannot resolve NTP server address: ", sizeof(errbuf));
strncat(errbuf, errno == EAI_SYSTEM ? strerror(errno) : gai_strerror(eai),
sizeof(errbuf) - strlen(errbuf) - 1);
if(eai == EAI_NONAME || eai == EAI_NODATA)
{
strncat(errbuf, " \"", sizeof(errbuf) - strlen(errbuf) - 1);
strncat(errbuf, server, sizeof(errbuf) - strlen(errbuf) - 1);
strncat(errbuf, "\"", sizeof(errbuf) - strlen(errbuf) - 1);
}
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, false, errbuf);
return false;
}
const unsigned int count = config.ntp.sync.count.v.ui;
struct ntp_sync *ntp = calloc(count, sizeof(struct ntp_sync));
if(ntp == NULL)
{
log_err("Cannot allocate memory for NTP client");
return false;
}
// Send and receive NTP packets
for(unsigned int i = 0; i < count; i++)
{
// Create socket
const int s = getsock(saddr);
if(s == -1)
continue;
// Send request
if(!request(s, server, &ntp[i]))
{
close(s);
free(ntp);
freeaddrinfo(saddr);
return false;
}
// Get reply
if(!reply(s, server, &ntp[i], false))
{
close(s);
continue;
}
// Close socket
close(s);
// Sleep for some time to avoid flooding the server
if(print)
printf(".");
fflush(stdout);
usleep(NTP_DELAY);
}
if(print)
printf("\n");
// Free allocated memory
freeaddrinfo(saddr);
// Compute average and standard deviation
unsigned int valid = 0;
double theta_avg = 0.0, theta_stdev = 0.0;
double delta_avg = 0.0, delta_stdev = 0.0;
for(unsigned int i = 0; i < count; i++)
{
// Skip invalid values
if(fabs(ntp[i].theta) < ntp[i].precision ||
fabs(ntp[i].delta) < ntp[i].precision ||
!ntp[i].valid)
continue;
theta_avg += ntp[i].theta;
delta_avg += ntp[i].delta;
valid++;
}
if(valid == 0)
{
log_ntp_message(false, false, "No valid NTP replies received, check server and network connectivity");
free(ntp);
return false;
}
log_info("Received %u/%u valid NTP replies from %s", valid, count, server);
theta_avg /= valid;
delta_avg /= valid;
for(unsigned int i = 0; i < count; i++)
{
// Skip invalid values
if(fabs(ntp[i].theta) < ntp[i].precision ||
fabs(ntp[i].delta) < ntp[i].precision ||
!ntp[i].valid)
continue;
theta_stdev += pow(ntp[i].theta - theta_avg, 2);
delta_stdev += pow(ntp[i].delta - delta_avg, 2);
}
theta_stdev = sqrt(theta_stdev / valid);
delta_stdev = sqrt(delta_stdev / valid);
log_debug(DEBUG_NTP, "Average time offset: (%e +/- %e) s", theta_avg, theta_stdev);
log_debug(DEBUG_NTP, "Average round-trip delay: (%e +/- %e) s", delta_avg, delta_stdev);
// Reject synchronization if the standard deviation of the time offset
// or round-trip delay is larger than 1 second
if(theta_stdev > 1.0 || delta_stdev > 1.0)
{
log_ntp_message(false, false, "Standard deviation of time offset is too large, rejecting synchronization");
free(ntp);
return false;
}
// Compute trimmed mean (average excluding outliers)
double theta_trim = 0.0, delta_trim = 0.0;
unsigned int trim = 0;
for(unsigned int i = 0; i < count; i++)
{
// Skip invalid values
if(fabs(ntp[i].theta) < ntp[i].precision ||
fabs(ntp[i].delta) < ntp[i].precision ||
!ntp[i].valid)
continue;
// Skip outliers
// We consider values > 2 standard deviations from the mean as
// outliers
if(fabs(ntp[i].theta - theta_avg) > 2 * theta_stdev ||
fabs(ntp[i].delta - delta_avg) > 2 * delta_stdev)
continue;
theta_trim += ntp[i].theta;
delta_trim += ntp[i].delta;
trim++;
}
// Free allocated memory
free(ntp);
if(trim == 0)
{
log_warn("No valid NTP replies after outlier removal, check server and network connectivity");
return false;
}
theta_trim /= trim;
delta_trim /= trim;
log_info("Time offset: %e ms (excluded %u outliers)", 1e3*theta_trim, count - trim);
log_info("Round-trip delay: %e ms (excluded %u outliers)", 1e3*delta_trim, count - trim);
// Set time if requested
if(settime)
{
// Calculate corrected time
struct timeval unix_time;
const uint64_t ntp_time = get_new_time(&unix_time, theta_trim);
// If the clock deviates more than 0.5 seconds from the NTP server,
// the time is updated immediately. Otherwise, the time is updated
// gradually to avoid sudden jumps in the system clock.
// The threshold of 0.5 seconds is hard-wired into the kernel
// since Linux 2.6.26, see man ntp_adjtime(2) for details.
bool success;
if(fabs(theta_trim) > 0.5)
success = settime_step(&unix_time, theta_trim);
else
success = settime_skew(theta_trim);
// Return early if time could not be set
if(!success)
return false;
// Update last NTP sync time
ntp_last_sync = ntp_time;
// Compute our server's root dispersion and delay
// Both quantities are the maximum error and maximum delay of
// the server's time relative to the reference time. The root
// dispersion is the maximum error of the server's time relative
// to the reference time, while the root delay is the maximum
// delay of the server's time relative to the reference time
ntp_root_delay = D2FP(theta_trim);
ntp_root_dispersion = D2FP(theta_stdev);
// Finally, adjust RTC if configured
if(config.ntp.rtc.set.v.b)
ntp_sync_rtc();
}
// Offset and delay larger than 0.1 seconds are considered as invalid
// during local testing (e.g., when the server is on the same machine)
return theta_avg < 0.1 && delta_avg < 0.1;
}
static void *ntp_client_thread(void *arg)
{
// Set thread name
thread_running[NTP] = true;
prctl(PR_SET_NAME, thread_names[NTP], 0, 0, 0);
// Run NTP client
bool first_run = true;
while(!killed)
{
// Run NTP client
ntp_client(config.ntp.sync.server.v.s, true, false);
// Load queries from database after first NTP synchronization
if(first_run)
{
load_queries_from_disk();
first_run = false;
}
// Intermediate cancellation-point
BREAK_IF_KILLED();
// Sleep before retrying
thread_sleepms(NTP, 1000 * config.ntp.sync.interval.v.ui);
}
log_info("Terminating NTP thread");
thread_running[NTP] = false;
return NULL;
}
bool ntp_start_sync_thread(pthread_attr_t *attr)
{
// Return early if NTP client is disabled
if(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();
return false;
}
// Create thread
if(pthread_create(&threads[NTP], attr, ntp_client_thread, NULL) != 0)
{
log_err("Cannot create NTP client thread");
load_queries_from_disk();
return false;
}
return true;
}
+78
View File
@@ -0,0 +1,78 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2024 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* NTP prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef NTP_H
#define NTP_H
#include "FTL.h"
// TIMESTR_SIZE
#include "log.h"
// uint64_t
#include <stdint.h>
// bool
#include <stdbool.h>
// Get current time in NTP (64bit) format
uint64_t gettime64(void);
// Print NTP timestamp in human-readable form
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);
// Start NTP client
bool ntp_client(const char *server, const bool settime, const bool print);
// Start NTP sync thread
bool ntp_start_sync_thread(pthread_attr_t *attr);
// Sync RTC time
bool ntp_sync_rtc(void);
// Number of NTP queries to average. The more queries, the more accurate the
// time, but the longer it takes to synchronize. The minimum is 1.
#define NTP_AVERGAGE_COUNT 8
// Delay between consecutive NTP queries in microseconds
#define NTP_DELAY 500000
// number of seconds between 1900 and 1970 (MSB=1)
#define DIFF_SEC_1900_1970 (2208988800UL)
// number of seconds between 1970 and Feb 7, 2036 (6:28:16 UTC) (MSB=0)
#define DIFF_SEC_1970_2036 (2085978496UL)
// Timestamp conversion macroni (RFC 5905, Appendix A)
#define FRIC 65536. // 2^16 as a double
#define D2FP(r) ((uint32_t)((r) * FRIC)) // NTP short
#define FP2D(r) ((double)(r) / FRIC)
#define FRAC 4294967296. // 2^32 as double
#define D2LFP(a) ((uint64_t)((a) * FRAC)) // NTP timestamp
#define LFP2D(a) ((double)(a) / FRAC)
#define U2LFP(a) (((uint64_t)((a).tv_sec + DIFF_SEC_1900_1970) << 32) + (uint64_t) ((a).tv_usec / 1e6 * FRAC))
// Convert NTP timestamp to seconds and microseconds
//#define NTPtoSEC(x) (((x & 0x80000000) != 0) ? ((x >> 32) - DIFF_SEC_1900_1970) : ((x >> 32) + DIFF_SEC_1970_2036))
#define NTPtoSEC(x) ((x >> 32) - DIFF_SEC_1900_1970)
#define NTPtoUSEC(x) (suseconds_t)((LFP2D(x & 0xFFFFFFFF) * 1e6))
// Convert uint64_t to network byte order and vice versa
#define hton64(x) ((((uint64_t)htonl(x)) << 32) + htonl((x) >> 32))
#define ntoh64(x) ((((uint64_t)ntohl(x)) << 32) + ntohl((x) >> 32))
extern uint64_t ntp_last_sync;
extern uint32_t ntp_root_delay;
extern uint32_t ntp_root_dispersion;
#endif // NTP_H
+296
View File
@@ -0,0 +1,296 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2024 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Real Time Clock (RTC) functions
* The routines in this file have been inspired by man pages
* and the source of the hwclock which is part of the util-linux
* project (https://github.com/util-linux/util-linux/)
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "ntp/ntp.h"
// ioctl()
#include <sys/ioctl.h>
// RTC
#include <linux/rtc.h>
// O_WRONLY
#include <fcntl.h>
// struct config
#include "config/config.h"
// List of RTC devices from
// https://github.com/util-linux/util-linux/blob/41e7686c9ad1ea7892b9d8941c266869bf6a28dd/sys-utils/hwclock-rtc.c#L85-L93
static const char * const rtc_devices[] = {
#ifdef __ia64__
"/dev/efirtc",
"/dev/misc/efirtc",
#endif
"/dev/rtc0",
"/dev/rtc",
"/dev/misc/rtc"
};
static void print_tm_time(const char *label, const struct tm *tm)
{
char timestr[TIMESTR_SIZE] = { 0 };
strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", tm);
log_info("%s %s", label, timestr);
}
// Try to find the RTC device and open it
static int open_rtc(void)
{
int rtc_fd = -1;
// Get current user's UID and GID
const uid_t uid = getuid();
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)
{
// Open the RTC device
rtc_fd = open(config.ntp.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);
return rtc_fd;
}
// If the open failed because of permissions, try to change them
// momentarily. On some embedded systems, the RTC device is owned by
// root exclusively and users do not have permission to even open it.
// Without being able to access the RTC, the capability to set the
// time (CAP_SYS_TIME) is useless.
if(errno == EACCES)
{
// Get current owner of the device
struct stat st = { 0 };
if(stat(config.ntp.rtc.device.v.s, &st) == -1)
{
log_debug(DEBUG_NTP, "stat(\"%s\") failed: %s",
config.ntp.rtc.device.v.s, strerror(errno));
return -1;
}
if(chown(config.ntp.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));
return -1;
}
rtc_fd = open(config.ntp.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);
}
// Chown the device back to the original owner
if(chown(config.ntp.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));
return -1;
}
// Return the RTC file descriptor (can be -1)
return rtc_fd;
}
log_debug(DEBUG_NTP, "Failed to open RTC at \"%s\": %s",
config.ntp.rtc.device.v.s, strerror(errno));
return -1;
}
// If the user has not specified an RTC device, try to open the default
// ones
for(size_t i = 0; i < ArraySize(rtc_devices); i++)
{
rtc_fd = open(rtc_devices[i], O_RDONLY);
if (rtc_fd != -1)
{
log_debug(DEBUG_NTP, "Successfully opened RTC at \"%s\"",
rtc_devices[i]);
break;
}
// If the open failed because of permissions, try to change them
// momentarily
if(errno == EACCES)
{
// Get current owner of the device
struct stat st = { 0 };
if(stat(rtc_devices[i], &st) == -1)
{
log_debug(DEBUG_NTP, "stat(\"%s\") failed: %s",
rtc_devices[i], strerror(errno));
return -1;
}
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));
return -1;
}
rtc_fd = open(rtc_devices[i], O_RDONLY);
if (rtc_fd != -1)
{
log_debug(DEBUG_NTP, "Successfully opened RTC at \"%s\"",
rtc_devices[i]);
}
// Chown the device back to the original owner
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));
return -1;
}
// Return the RTC file descriptor (can be -1)
return rtc_fd;
}
log_debug(DEBUG_NTP, "Failed to open RTC at \"%s\": %s",
rtc_devices[i], strerror(errno));
}
return rtc_fd;
}
static bool read_rtc(struct tm *tm)
{
// Open the RTC device
const int rtc_fd = open_rtc();
if(rtc_fd == -1)
return false;
// Read the RTC time
struct rtc_time rtc_tm = { 0 };
const int rc = ioctl(rtc_fd, RTC_RD_TIME, &rtc_tm);
if(rc == -1)
{
log_debug(DEBUG_NTP, "ioctl(RTC_RD_NAME) failed: %s",
strerror(errno));
close(rtc_fd);
return false;
}
// Convert the kernel's struct tm to the standard struct tm
tm->tm_sec = rtc_tm.tm_sec;
tm->tm_min = rtc_tm.tm_min;
tm->tm_hour = rtc_tm.tm_hour;
tm->tm_mday = rtc_tm.tm_mday;
tm->tm_mon = rtc_tm.tm_mon;
tm->tm_year = rtc_tm.tm_year;
tm->tm_wday = rtc_tm.tm_wday;
tm->tm_yday = rtc_tm.tm_yday;
tm->tm_isdst = -1; // the RTC does not provide this information
print_tm_time("Current RTC time is", tm);
// Close the RTC device
close(rtc_fd);
return true;
}
// Set the Hardware Clock to the broken down time <new_time>.
// Use ioctls to "rtc" device to set the time.
static bool set_rtc(const struct tm *new_time)
{
// Open the RTC device
const int rtc_fd = open_rtc();
if(rtc_fd == -1)
return false;
// Set the RTC time from the broken down time
struct rtc_time rtc_tm = { 0 };
rtc_tm.tm_sec = new_time->tm_sec;
rtc_tm.tm_min = new_time->tm_min;
rtc_tm.tm_hour = new_time->tm_hour;
rtc_tm.tm_mday = new_time->tm_mday;
rtc_tm.tm_mon = new_time->tm_mon;
rtc_tm.tm_year = new_time->tm_year;
rtc_tm.tm_wday = new_time->tm_wday;
rtc_tm.tm_yday = new_time->tm_yday;
rtc_tm.tm_isdst = new_time->tm_isdst;
// Set the RTC time
const int rc = ioctl(rtc_fd, RTC_SET_TIME, &rtc_tm);
if(rc == -1)
{
log_debug(DEBUG_NTP, "ioctl(RTC_SET_TIME) failed: %s",
strerror(errno));
close(rtc_fd);
return false;
}
print_tm_time("RTC time set to", new_time);
// Close the RTC device
close(rtc_fd);
return true;
}
bool ntp_sync_rtc(void)
{
// Wait until the beginning of the next second as the RTC only has a
// resolution of one second
struct timespec ts = { 0 };
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec++;
ts.tv_nsec = 0;
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
// 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)
// UTC
gmtime_r(&newtime, &new_time);
else
// Local time
localtime_r(&newtime, &new_time);
// Read the current time from the RTC
struct tm rtc_time = { 0 };
if(!read_rtc(&rtc_time))
{
log_debug(DEBUG_NTP, "Failed to read RTC time");
return false;
}
// If the RTC time is the same as the current time, we don't need to set
// it. We don't use memcmp() here because the tm struct may contain
// additional fields that are not filled in by the RTC (e.g. tm_isdst).
if(rtc_time.tm_sec == new_time.tm_sec &&
rtc_time.tm_min == new_time.tm_min &&
rtc_time.tm_hour == new_time.tm_hour &&
rtc_time.tm_mday == new_time.tm_mday &&
rtc_time.tm_mon == new_time.tm_mon &&
rtc_time.tm_year == new_time.tm_year)
{
// The RTC time is already correct, return early
log_debug(DEBUG_NTP, "RTC time is already correct");
return true;
}
// Set the RTC time
if(!set_rtc(&new_time))
{
log_debug(DEBUG_NTP, "Failed to set RTC time");
return false;
}
return true;
}
+405
View File
@@ -0,0 +1,405 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2024 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* NTP server routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "ntp/ntp.h"
// exit(0)
#include <stdlib.h>
// memcpy()
#include <string.h>
// close()
#include <unistd.h>
// fork(), wait()
#include <signal.h>
// clock_gettime()
#include <sys/time.h>
//#include <sys/types.h>
#include <sys/wait.h>
// wait()
#include <sys/socket.h>
// htonl(), etc.
#include <arpa/inet.h>
// errno
#include <errno.h>
// ctime()
#include <time.h>
// pthread_create
#include <pthread.h>
// PR_SET_NAME
#include <sys/prctl.h>
// config struct
#include "config/config.h"
// PRIi64
#include <inttypes.h>
// log_ntp_message()
#include "database/message-table.h"
uint64_t ntp_last_sync = 0u;
uint32_t ntp_root_delay = 0u;
uint32_t ntp_root_dispersion = 0u;
// RFC 5905 Appendix A.4: Kernel System Clock Interface
uint64_t gettime64(void)
{
struct timeval unix_time;
gettimeofday(&unix_time, NULL);
return (U2LFP(unix_time));
}
// Create and send an NTP reply to the client
static bool ntp_reply(const int socket_fd, const struct sockaddr *saddr_p, const socklen_t saddrlen,
const unsigned char recv_buf[], const uint64_t *recv_time)
{
// Buffer for the response
unsigned char send_buf[48];
memset(send_buf, 0, sizeof(send_buf));
// DWORD-aligned pointer to the send buffer
uint32_t *u32p = (uint32_t*)((void*)&send_buf[0]);
// DWORD-aligned read-only pointer to the receive buffer
const uint32_t *u32r = (uint32_t*)((void*)&recv_buf[0]);
// NTP Packet Header Format (RFC 5905), page 18
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |LI | VN |Mode | Stratum | Poll | Precision |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Check if the first byte is valid: mode is expected to be 3 ("client")
if ((recv_buf[0] & 0x07) != 0x3) {
log_warn("Received invalid NTP request: not from an NTP client, ignoring");
return false;
}
// set LI = 0 (no warning about leap seconds), set version-number to
// 4 and set mode = 4 ("server")
send_buf[0] = (0x04 << 3) + 0x04;
// Set stratum to "secondary server" as we have derived time via
// external NTP as well. May be set to 1 if we want to be a primary
// server (synchronized by a hardware clock with GPS, etc.)
send_buf[1] = 0x02;
// Copy Poll value from client
send_buf[2] = recv_buf[2];
// Precision: the precision of the local clock, in seconds to the
// nearest power of two.
// log2(1 usec = 1e-6 s) = -19.931568569324174
send_buf[3] = (signed char)(-20);
// Advance 32 bit pointer to the next field
u32p++;
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Root Delay |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Root Dispersion |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Set Root Delay (total roundtrip delay to the primary reference
// source) and Root Dispersion (the nominal error relative to the
// primary reference source) to the values obtained from the upstream
// NTP server.
*u32p++ = htonl(ntp_root_delay);
*u32p++ = htonl(ntp_root_dispersion);
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Reference ID |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Reference ID = 'LOCL" (LOCAL CLOCK)
// A four-octet, left-justified, zero-padded ASCII string assigned to
// the reference clock
memcpy(u32p++, "LOCL", sizeof(uint32_t));
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// + Reference Timestamp (64) +
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Time when the system clock was last set or corrected, in NTP
// timestamp format.
const uint64_t last_sync = hton64(ntp_last_sync);
memcpy(u32p, &last_sync, sizeof(uint64_t));
if(config.debug.ntp.v.b)
print_debug_time("Reference Timestamp", u32p, 0);
u32p += 2;
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// + Origin Timestamp (64) +
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Time at the client when the request departed for the server, in NTP
// timestamp format. (this is the client's transmit time)
memcpy(u32p, &u32r[10], sizeof(uint64_t));
if(config.debug.ntp.v.b)
print_debug_time("Origin Timestamp", u32p, 0);
u32p += 2;
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// + Receive Timestamp (64) +
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Time at the server when the request arrived from the client, in NTP
// timestamp format. (this is the server's receive time)
const uint64_t net_recv_time = hton64(*recv_time);
memcpy(u32p, &net_recv_time, sizeof(uint64_t));
if(config.debug.ntp.v.b)
print_debug_time("Receive Timestamp", u32p, 0);
u32p += 2;
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// + Transmit Timestamp (64) +
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Time at the server when the response left for the client, in NTP
// timestamp format. (this is the server's transmit time)
const uint64_t transmit_time = gettime64();
const uint64_t net_transmit_time = hton64(transmit_time);
memcpy(u32p, &net_transmit_time, sizeof(uint64_t));
if(config.debug.ntp.v.b)
print_debug_time("Transmit Timestamp", u32p, 0);
u32p += 2;
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// . .
// . Extension Field 1 (variable) .
// . .
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// . .
// . Extension Field 2 (variable) .
// . .
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Key Identifier |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// | dgst (128) |
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 8: Packet Header Format
// Send the response
errno = 0;
if(sendto(socket_fd, send_buf, sizeof(send_buf), 0, saddr_p, saddrlen) < 48)
{
log_err("NTP send error: %s", strerror(errno));
return false;
}
return true;
}
// Process incoming NTP requests
static void request_process_loop(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)
{
unsigned char buf[48];
struct sockaddr src_addr;
socklen_t src_addrlen = sizeof(src_addr);
while(recvfrom(fd, buf, sizeof(buf), 0, &src_addr, &src_addrlen) < 48); // ignore invalid requests
// Get the current time in NTP format directly after receiving
// the request
const uint64_t recv_time = gettime64();
// Print the request
if(config.debug.ntp.v.b)
{
if(protocol == AF_INET6)
{
struct sockaddr_in6 sin6;
memcpy(&sin6, &src_addr, sizeof(sin6));
char ip[INET6_ADDRSTRLEN];
const in_port_t port = ntohs(sin6.sin6_port);
inet_ntop(protocol, &sin6.sin6_addr, ip, sizeof(ip));
log_debug(DEBUG_NTP, "Received NTP request from [%s]:%u", ip, port);
}
else
{
struct sockaddr_in sin;
memcpy(&sin, &src_addr, sizeof(sin));
char ip[INET6_ADDRSTRLEN];
const in_port_t port = ntohs(sin.sin_port);
inet_ntop(protocol, &sin.sin_addr, ip, sizeof(ip));
log_debug(DEBUG_NTP, "Received NTP request from %s:%u", ip, port);
}
}
// Fork a child to handle the request
const pid_t pid = fork();
if (pid == 0) {
// Child
ntp_reply(fd, &src_addr , src_addrlen, buf, &recv_time);
exit(0);
} else if (pid == -1) {
log_err("fork() error");
return;
}
// return to parent
}
}
// Start the NTP server
static void *ntp_bind_and_listen(void *param)
{
const int protocol = param == 0 ? AF_INET : AF_INET6;
// Create a socket
errno = 0;
const int s = socket(protocol, SOCK_DGRAM, IPPROTO_UDP);
if(s == -1)
{
char errbuf[1024];
snprintf(errbuf, sizeof(errbuf),
"Cannot create NTP socket (%s), IPv%i NTP server not available",
strerror(errno), protocol == AF_INET ? 4 : 6);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, true, errbuf);
return NULL;
}
// Bind the socket to the NTP port
char ipstr[INET6_ADDRSTRLEN + 1];
memset(ipstr, 0, sizeof(ipstr));
if(protocol == AF_INET)
{
// IPv4 - set thread name
prctl(PR_SET_NAME, "NTP (IPv4)", 0, 0, 0);
// Prepare the bind address
struct sockaddr_in bind_addr;
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.sin_family = AF_INET; // IPv4
bind_addr.sin_port = htons(123); // NTP port
memcpy(&bind_addr.sin_addr, &config.ntp.ipv4.address.v.in_addr, sizeof(bind_addr.sin_addr));
inet_ntop(AF_INET, &bind_addr.sin_addr, ipstr, sizeof(ipstr) - 1);
// Bind the socket
errno = 0;
if(bind(s, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0)
{
char errbuf[1024];
snprintf(errbuf, sizeof(errbuf),
"Cannot bind to IPv4 address %s:123 (%s), IPv4 NTP server not available",
ipstr, strerror(errno));
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, true, errbuf);
return NULL;
}
}
else
{
// IPv6 - set thread name
prctl(PR_SET_NAME, "NTP (IPv6)", 0, 0, 0);
// Set socket options to allow IPv6 only, otherwise it will bind
// to both IPv4 and IPv6 and show IPv4 addresses as
// v4-mapped-on-v6 addresses
int opt = 1;
if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) != 0)
{
char errbuf[1024];
strncpy(errbuf, "Cannot set socket option IPV6_V6ONLY, IPv6 NTP server not available: ", sizeof(errbuf));
strncat(errbuf, strerror(errno), sizeof(errbuf) - strlen(errbuf) - 1);
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, true, errbuf);
return NULL;
}
// Prepare the bind address
struct sockaddr_in6 bind_addr;
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.sin6_family = AF_INET6; // IPv6
bind_addr.sin6_port = htons(123); // NTP port
memcpy(&bind_addr.sin6_addr, &config.ntp.ipv6.address.v.in6_addr, sizeof(bind_addr.sin6_addr));
inet_ntop(AF_INET6, &bind_addr.sin6_addr, ipstr, sizeof(ipstr) - 1);
// Bind the socket
errno = 0;
if(bind(s, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0)
{
char errbuf[1024];
snprintf(errbuf, sizeof(errbuf),
"Cannot bind to IPv6 address %s:123 (%s), IPv6 NTP server not available",
ipstr, strerror(errno));
errbuf[sizeof(errbuf) - 1] = '\0';
log_ntp_message(true, true, errbuf);
return NULL;
}
}
request_process_loop(s, ipstr, protocol);
close(s);
return NULL;
}
// Start the NTP server
bool ntp_server_start(pthread_attr_t *attr)
{
// Spawn two pthreads, one for IPv4 and one for IPv6
// IPv4
if(config.ntp.ipv4.active.v.b)
{
// Create a thread for the IPv4 NTP server
pthread_t thread;
if (pthread_create(&thread, attr, ntp_bind_and_listen, (void *)0) != 0)
{
log_ntp_message(true, true, "Cannot create NTP server thread for IPv4");
return false;
}
}
// IPv6
if(config.ntp.ipv6.active.v.b)
{
// Create a thread for the IPv6 NTP server
pthread_t thread;
if (pthread_create(&thread, attr, ntp_bind_and_listen, (void *)1) != 0)
{
log_ntp_message(true, true, "Cannot create NTP server thread for IPv6");
return false;
}
}
return true;
}
-1
View File
@@ -1053,7 +1053,6 @@ static void resolveUpstreams(const bool onlynew)
void *DNSclient_thread(void *val)
{
// Set thread name
thread_names[DNSclient] = "DNS client";
thread_running[DNSclient] = true;
prctl(PR_SET_NAME, thread_names[DNSclient], 0, 0, 0);
+7 -1
View File
@@ -35,7 +35,13 @@ 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 *thread_names[THREADS_MAX] = { "" };
const char * const thread_names[THREADS_MAX] = {
"database",
"housekeeper",
"dns-client",
"timer",
"ntp-client"
};
// Return the (null-terminated) name of the calling thread
// The name is stored in the buffer as well as returned for convenience
+3 -1
View File
@@ -30,6 +30,8 @@ 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 *thread_names[THREADS_MAX];
extern const char * const thread_names[THREADS_MAX];
#define BREAK_IF_KILLED() { if(killed) break; }
#endif //SIGNALS_H
+5 -2
View File
@@ -84,7 +84,8 @@ void get_blockingmode_timer(double *delay, bool *target_status)
void *timer(void *val)
{
// Set thread name
prctl(PR_SET_NAME, "int.timer", 0, 0, 0);
thread_running[GC] = true;
prctl(PR_SET_NAME, thread_names[TIMER], 0, 0, 0);
// Save timestamp as we do not want to store immediately
// to the database
@@ -105,9 +106,11 @@ void *timer(void *val)
set_blockingstatus(timer_target_status);
timer_delay = -1.0;
}
sleepms(SLEEPING_TIME * 1000);
thread_sleepms(TIMER, SLEEPING_TIME * 1000);
}
log_info("Terminating timer thread");
thread_running[GC] = false;
return NULL;
}
+2
View File
@@ -616,6 +616,8 @@ 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);
+2
View File
@@ -725,6 +725,8 @@ 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;