If the network correction is known for both the request and response, and their sum is not larger that the measured peer delay, allowing the transparent clocks to be running up to 100 ppm faster than the client's clock, apply the corrections to the NTP offset and peer delay. Don't correct the root delay to not change the estimated maximum error.
3538 lines
100 KiB
C
3538 lines
100 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Richard P. Curnow 1997-2003
|
|
* Copyright (C) Lonnie Abelbeck 2016, 2018
|
|
* Copyright (C) Miroslav Lichvar 2009-2023
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
Command line client for configuring the daemon and obtaining status
|
|
from it whilst running.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "array.h"
|
|
#include "candm.h"
|
|
#include "cmac.h"
|
|
#include "logging.h"
|
|
#include "memory.h"
|
|
#include "nameserv.h"
|
|
#include "getdate.h"
|
|
#include "cmdparse.h"
|
|
#include "pktlength.h"
|
|
#include "socket.h"
|
|
#include "util.h"
|
|
|
|
#ifdef FEAT_READLINE
|
|
#include <editline/readline.h>
|
|
#endif
|
|
|
|
/* ================================================== */
|
|
|
|
struct Address {
|
|
SCK_AddressType type;
|
|
union {
|
|
IPSockAddr ip;
|
|
char *path;
|
|
} addr;
|
|
};
|
|
|
|
static ARR_Instance server_addresses;
|
|
|
|
static int sock_fd = -1;
|
|
|
|
static volatile int quit = 0;
|
|
|
|
static int on_terminal = 0;
|
|
|
|
static int no_dns = 0;
|
|
|
|
static int source_names = 0;
|
|
|
|
static int csv_mode = 0;
|
|
|
|
static int end_dot = 0;
|
|
|
|
/* ================================================== */
|
|
/* Log a message. This is a minimalistic replacement of the logging.c
|
|
implementation to avoid linking with it and other modules. */
|
|
|
|
LOG_Severity log_min_severity = LOGS_INFO;
|
|
|
|
void LOG_Message(LOG_Severity severity,
|
|
#if DEBUG > 0
|
|
int line_number, const char *filename, const char *function_name,
|
|
#endif
|
|
const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (severity < log_min_severity)
|
|
return;
|
|
|
|
va_start(ap, format);
|
|
vfprintf(stderr, format, ap);
|
|
putc('\n', stderr);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Read a single line of commands from standard input */
|
|
|
|
#ifdef FEAT_READLINE
|
|
static char **command_name_completion(const char *text, int start, int end);
|
|
#endif
|
|
|
|
static char *
|
|
read_line(void)
|
|
{
|
|
static char line[2048];
|
|
static const char *prompt = "chronyc> ";
|
|
|
|
if (on_terminal) {
|
|
#ifdef FEAT_READLINE
|
|
char *cmd;
|
|
|
|
rl_attempted_completion_function = command_name_completion;
|
|
rl_basic_word_break_characters = " \t\n\r";
|
|
|
|
/* save line only if not empty */
|
|
cmd = readline(prompt);
|
|
if( cmd == NULL ) return( NULL );
|
|
|
|
/* user pressed return */
|
|
if( *cmd != '\0' ) {
|
|
strncpy(line, cmd, sizeof(line) - 1);
|
|
line[sizeof(line) - 1] = '\0';
|
|
add_history(cmd);
|
|
/* free the buffer allocated by readline */
|
|
Free(cmd);
|
|
} else {
|
|
/* simulate the user has entered an empty line */
|
|
*line = '\0';
|
|
}
|
|
return( line );
|
|
#else
|
|
printf("%s", prompt);
|
|
fflush(stdout);
|
|
#endif
|
|
}
|
|
if (fgets(line, sizeof(line), stdin)) {
|
|
return line;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static ARR_Instance
|
|
get_addresses(const char *hostnames, int port)
|
|
{
|
|
struct Address *addr;
|
|
ARR_Instance addrs;
|
|
char *hostname, *s1, *s2;
|
|
IPAddr ip_addrs[DNS_MAX_ADDRESSES];
|
|
int i;
|
|
|
|
addrs = ARR_CreateInstance(sizeof (*addr));
|
|
s1 = Strdup(hostnames);
|
|
|
|
/* Parse the comma-separated list of hostnames */
|
|
for (hostname = s1; hostname && *hostname; hostname = s2) {
|
|
s2 = strchr(hostname, ',');
|
|
if (s2)
|
|
*s2++ = '\0';
|
|
|
|
/* hostname starting with / is considered a path of Unix domain socket */
|
|
if (hostname[0] == '/') {
|
|
addr = ARR_GetNewElement(addrs);
|
|
addr->type = SCK_ADDR_UNIX;
|
|
addr->addr.path = Strdup(hostname);
|
|
} else {
|
|
if (DNS_Name2IPAddress(hostname, ip_addrs, DNS_MAX_ADDRESSES) != DNS_Success) {
|
|
DEBUG_LOG("Could not get IP address for %s", hostname);
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < DNS_MAX_ADDRESSES && ip_addrs[i].family != IPADDR_UNSPEC; i++) {
|
|
addr = ARR_GetNewElement(addrs);
|
|
addr->type = SCK_ADDR_IP;
|
|
addr->addr.ip.ip_addr = ip_addrs[i];
|
|
addr->addr.ip.port = port;
|
|
DEBUG_LOG("Resolved %s to %s", hostname, UTI_IPToString(&ip_addrs[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
Free(s1);
|
|
return addrs;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
free_addresses(ARR_Instance addresses)
|
|
{
|
|
struct Address *addr;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARR_GetSize(addresses); i++) {
|
|
addr = ARR_GetElement(addresses, i);
|
|
|
|
if (addr->type == SCK_ADDR_UNIX)
|
|
Free(addr->addr.path);
|
|
}
|
|
|
|
ARR_DestroyInstance(addresses);
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Initialise the socket used to talk to the daemon */
|
|
|
|
static int
|
|
open_socket(struct Address *addr)
|
|
{
|
|
char *dir, *local_addr;
|
|
size_t local_addr_len;
|
|
|
|
switch (addr->type) {
|
|
case SCK_ADDR_IP:
|
|
sock_fd = SCK_OpenUdpSocket(&addr->addr.ip, NULL, NULL, 0);
|
|
break;
|
|
case SCK_ADDR_UNIX:
|
|
/* Construct path of our socket. Use the same directory as the server
|
|
socket and include our process ID to allow multiple chronyc instances
|
|
running at the same time. */
|
|
|
|
dir = UTI_PathToDir(addr->addr.path);
|
|
local_addr_len = strlen(dir) + 50;
|
|
local_addr = Malloc(local_addr_len);
|
|
|
|
snprintf(local_addr, local_addr_len, "%s/chronyc.%d.sock", dir, (int)getpid());
|
|
|
|
sock_fd = SCK_OpenUnixDatagramSocket(addr->addr.path, local_addr,
|
|
SCK_FLAG_ALL_PERMISSIONS);
|
|
Free(dir);
|
|
Free(local_addr);
|
|
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
if (sock_fd < 0)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
close_io(void)
|
|
{
|
|
if (sock_fd < 0)
|
|
return;
|
|
|
|
SCK_RemoveSocket(sock_fd);
|
|
SCK_CloseSocket(sock_fd);
|
|
sock_fd = -1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
open_io(void)
|
|
{
|
|
static unsigned int address_index = 0;
|
|
struct Address *addr;
|
|
|
|
/* If a socket is already opened, close it and try the next address */
|
|
if (sock_fd >= 0) {
|
|
close_io();
|
|
address_index++;
|
|
}
|
|
|
|
/* Find an address for which a socket can be opened and connected */
|
|
for (; address_index < ARR_GetSize(server_addresses); address_index++) {
|
|
addr = ARR_GetElement(server_addresses, address_index);
|
|
|
|
if (open_socket(addr))
|
|
return 1;
|
|
|
|
close_io();
|
|
}
|
|
|
|
/* Start from the first address if called again */
|
|
address_index = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
bits_to_mask(int bits, int family, IPAddr *mask)
|
|
{
|
|
int i;
|
|
|
|
mask->family = family;
|
|
switch (family) {
|
|
case IPADDR_INET4:
|
|
if (bits > 32 || bits < 0)
|
|
bits = 32;
|
|
if (bits > 0) {
|
|
mask->addr.in4 = -1;
|
|
mask->addr.in4 <<= 32 - bits;
|
|
} else {
|
|
mask->addr.in4 = 0;
|
|
}
|
|
break;
|
|
case IPADDR_INET6:
|
|
if (bits > 128 || bits < 0)
|
|
bits = 128;
|
|
for (i = 0; i < bits / 8; i++)
|
|
mask->addr.in6[i] = 0xff;
|
|
if (i < 16)
|
|
mask->addr.in6[i++] = (0xff << (8 - bits % 8)) & 0xff;
|
|
for (; i < 16; i++)
|
|
mask->addr.in6[i] = 0x0;
|
|
break;
|
|
case IPADDR_ID:
|
|
mask->family = IPADDR_UNSPEC;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
parse_source_address(char *word, IPAddr *address)
|
|
{
|
|
if (UTI_StringToIdIP(word, address))
|
|
return 1;
|
|
|
|
if (DNS_Name2IPAddress(word, address, 1) == DNS_Success)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
read_mask_address(char *line, IPAddr *mask, IPAddr *address)
|
|
{
|
|
unsigned int bits;
|
|
char *p, *q;
|
|
|
|
p = line;
|
|
if (!*p) {
|
|
mask->family = address->family = IPADDR_UNSPEC;
|
|
return 1;
|
|
} else {
|
|
q = strchr(p, '/');
|
|
if (q) {
|
|
*q++ = 0;
|
|
if (UTI_StringToIP(p, mask)) {
|
|
p = q;
|
|
if (UTI_StringToIP(p, address)) {
|
|
if (address->family == mask->family)
|
|
return 1;
|
|
} else if (sscanf(p, "%u", &bits) == 1) {
|
|
*address = *mask;
|
|
bits_to_mask(bits, address->family, mask);
|
|
return 1;
|
|
}
|
|
}
|
|
} else {
|
|
if (parse_source_address(p, address)) {
|
|
bits_to_mask(-1, address->family, mask);
|
|
return 1;
|
|
} else {
|
|
LOG(LOGS_ERR, "Could not get address for hostname");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG(LOGS_ERR, "Invalid syntax for mask/address");
|
|
return 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_offline(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr mask, address;
|
|
int ok;
|
|
|
|
if (read_mask_address(line, &mask, &address)) {
|
|
UTI_IPHostToNetwork(&mask, &msg->data.offline.mask);
|
|
UTI_IPHostToNetwork(&address, &msg->data.offline.address);
|
|
msg->command = htons(REQ_OFFLINE);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
|
|
static int
|
|
process_cmd_online(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr mask, address;
|
|
int ok;
|
|
|
|
if (read_mask_address(line, &mask, &address)) {
|
|
UTI_IPHostToNetwork(&mask, &msg->data.online.mask);
|
|
UTI_IPHostToNetwork(&address, &msg->data.online.address);
|
|
msg->command = htons(REQ_ONLINE);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_onoffline(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_ONOFFLINE);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
read_address_integer(char *line, IPAddr *address, int *value)
|
|
{
|
|
char *hostname;
|
|
int ok = 0;
|
|
|
|
hostname = line;
|
|
line = CPS_SplitWord(line);
|
|
|
|
if (sscanf(line, "%d", value) != 1) {
|
|
LOG(LOGS_ERR, "Invalid syntax for address value");
|
|
ok = 0;
|
|
} else {
|
|
if (!parse_source_address(hostname, address)) {
|
|
LOG(LOGS_ERR, "Could not get address for hostname");
|
|
ok = 0;
|
|
} else {
|
|
ok = 1;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
read_address_double(char *line, IPAddr *address, double *value)
|
|
{
|
|
char *hostname;
|
|
int ok = 0;
|
|
|
|
hostname = line;
|
|
line = CPS_SplitWord(line);
|
|
|
|
if (sscanf(line, "%lf", value) != 1) {
|
|
LOG(LOGS_ERR, "Invalid syntax for address value");
|
|
ok = 0;
|
|
} else {
|
|
if (!parse_source_address(hostname, address)) {
|
|
LOG(LOGS_ERR, "Could not get address for hostname");
|
|
ok = 0;
|
|
} else {
|
|
ok = 1;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_minpoll(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
int minpoll;
|
|
int ok;
|
|
|
|
if (read_address_integer(line, &address, &minpoll)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_minpoll.address);
|
|
msg->data.modify_minpoll.new_minpoll = htonl(minpoll);
|
|
msg->command = htons(REQ_MODIFY_MINPOLL);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_maxpoll(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
int maxpoll;
|
|
int ok;
|
|
|
|
if (read_address_integer(line, &address, &maxpoll)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_maxpoll.address);
|
|
msg->data.modify_maxpoll.new_maxpoll = htonl(maxpoll);
|
|
msg->command = htons(REQ_MODIFY_MAXPOLL);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_maxdelay(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
double max_delay;
|
|
int ok;
|
|
|
|
if (read_address_double(line, &address, &max_delay)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelay.address);
|
|
msg->data.modify_maxdelay.new_max_delay = UTI_FloatHostToNetwork(max_delay);
|
|
msg->command = htons(REQ_MODIFY_MAXDELAY);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_maxdelaydevratio(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
double max_delay_dev_ratio;
|
|
int ok;
|
|
|
|
if (read_address_double(line, &address, &max_delay_dev_ratio)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelaydevratio.address);
|
|
msg->data.modify_maxdelayratio.new_max_delay_ratio = UTI_FloatHostToNetwork(max_delay_dev_ratio);
|
|
msg->command = htons(REQ_MODIFY_MAXDELAYDEVRATIO);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_maxdelayratio(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
double max_delay_ratio;
|
|
int ok;
|
|
|
|
if (read_address_double(line, &address, &max_delay_ratio)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelayratio.address);
|
|
msg->data.modify_maxdelayratio.new_max_delay_ratio = UTI_FloatHostToNetwork(max_delay_ratio);
|
|
msg->command = htons(REQ_MODIFY_MAXDELAYRATIO);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_minstratum(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
int min_stratum;
|
|
int ok;
|
|
|
|
if (read_address_integer(line, &address, &min_stratum)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_minstratum.address);
|
|
msg->data.modify_minstratum.new_min_stratum = htonl(min_stratum);
|
|
msg->command = htons(REQ_MODIFY_MINSTRATUM);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_polltarget(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr address;
|
|
int poll_target;
|
|
int ok;
|
|
|
|
if (read_address_integer(line, &address, &poll_target)) {
|
|
UTI_IPHostToNetwork(&address, &msg->data.modify_polltarget.address);
|
|
msg->data.modify_polltarget.new_poll_target = htonl(poll_target);
|
|
msg->command = htons(REQ_MODIFY_POLLTARGET);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_maxupdateskew(CMD_Request *msg, char *line)
|
|
{
|
|
int ok;
|
|
double new_max_update_skew;
|
|
|
|
if (sscanf(line, "%lf", &new_max_update_skew) == 1) {
|
|
msg->data.modify_maxupdateskew.new_max_update_skew = UTI_FloatHostToNetwork(new_max_update_skew);
|
|
msg->command = htons(REQ_MODIFY_MAXUPDATESKEW);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_dump(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_DUMP);
|
|
msg->data.dump.pad = htonl(0);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_writertc(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_WRITERTC);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_trimrtc(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_TRIMRTC);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_cyclelogs(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_CYCLELOGS);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_burst(CMD_Request *msg, char *line)
|
|
{
|
|
int n_good_samples, n_total_samples;
|
|
char *s1, *s2;
|
|
IPAddr address, mask;
|
|
|
|
s1 = line;
|
|
s2 = CPS_SplitWord(s1);
|
|
CPS_SplitWord(s2);
|
|
|
|
if (sscanf(s1, "%d/%d", &n_good_samples, &n_total_samples) != 2) {
|
|
LOG(LOGS_ERR, "Invalid syntax for burst command");
|
|
return 0;
|
|
}
|
|
|
|
mask.family = address.family = IPADDR_UNSPEC;
|
|
if (*s2 && !read_mask_address(s2, &mask, &address)) {
|
|
return 0;
|
|
}
|
|
|
|
msg->command = htons(REQ_BURST);
|
|
msg->data.burst.n_good_samples = ntohl(n_good_samples);
|
|
msg->data.burst.n_total_samples = ntohl(n_total_samples);
|
|
|
|
UTI_IPHostToNetwork(&mask, &msg->data.burst.mask);
|
|
UTI_IPHostToNetwork(&address, &msg->data.burst.address);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_local(CMD_Request *msg, char *line)
|
|
{
|
|
int on_off, stratum = 0, orphan = 0;
|
|
double distance = 0.0;
|
|
|
|
if (!strcmp(line, "off")) {
|
|
on_off = 0;
|
|
} else if (CPS_ParseLocal(line, &stratum, &orphan, &distance)) {
|
|
on_off = 1;
|
|
} else {
|
|
LOG(LOGS_ERR, "Invalid syntax for local command");
|
|
return 0;
|
|
}
|
|
|
|
msg->command = htons(REQ_LOCAL2);
|
|
msg->data.local.on_off = htonl(on_off);
|
|
msg->data.local.stratum = htonl(stratum);
|
|
msg->data.local.distance = UTI_FloatHostToNetwork(distance);
|
|
msg->data.local.orphan = htonl(orphan);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_manual(CMD_Request *msg, const char *line)
|
|
{
|
|
const char *p;
|
|
|
|
p = line;
|
|
|
|
if (!strcmp(p, "off")) {
|
|
msg->data.manual.option = htonl(0);
|
|
} else if (!strcmp(p, "on")) {
|
|
msg->data.manual.option = htonl(1);
|
|
} else if (!strcmp(p, "reset")) {
|
|
msg->data.manual.option = htonl(2);
|
|
} else {
|
|
LOG(LOGS_ERR, "Invalid syntax for manual command");
|
|
return 0;
|
|
}
|
|
msg->command = htons(REQ_MANUAL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_allowdeny(CMD_Request *msg, char *line, int cmd, int allcmd)
|
|
{
|
|
int all, subnet_bits;
|
|
IPAddr ip;
|
|
|
|
if (!CPS_ParseAllowDeny(line, &all, &ip, &subnet_bits)) {
|
|
LOG(LOGS_ERR, "Could not read address");
|
|
return 0;
|
|
}
|
|
|
|
msg->command = htons(all ? allcmd : cmd);
|
|
UTI_IPHostToNetwork(&ip, &msg->data.allow_deny.ip);
|
|
msg->data.allow_deny.subnet_bits = htonl(subnet_bits);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_accheck(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr ip;
|
|
msg->command = htons(REQ_ACCHECK);
|
|
if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) {
|
|
UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip);
|
|
return 1;
|
|
} else {
|
|
LOG(LOGS_ERR, "Could not read address");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_cmdaccheck(CMD_Request *msg, char *line)
|
|
{
|
|
IPAddr ip;
|
|
msg->command = htons(REQ_CMDACCHECK);
|
|
if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) {
|
|
UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip);
|
|
return 1;
|
|
} else {
|
|
LOG(LOGS_ERR, "Could not read address");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_dfreq(CMD_Request *msg, char *line)
|
|
{
|
|
double dfreq;
|
|
|
|
msg->command = htons(REQ_DFREQ);
|
|
|
|
if (sscanf(line, "%lf", &dfreq) != 1) {
|
|
LOG(LOGS_ERR, "Invalid value");
|
|
return 0;
|
|
}
|
|
|
|
msg->data.dfreq.dfreq = UTI_FloatHostToNetwork(dfreq);
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_doffset(CMD_Request *msg, char *line)
|
|
{
|
|
double doffset;
|
|
|
|
msg->command = htons(REQ_DOFFSET2);
|
|
|
|
if (sscanf(line, "%lf", &doffset) != 1) {
|
|
LOG(LOGS_ERR, "Invalid value");
|
|
return 0;
|
|
}
|
|
|
|
msg->data.doffset.doffset = UTI_FloatHostToNetwork(doffset);
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
convert_addsrc_sel_options(int options)
|
|
{
|
|
return (options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) |
|
|
(options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) |
|
|
(options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) |
|
|
(options & SRC_SELECT_REQUIRE ? REQ_ADDSRC_REQUIRE : 0);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_add_source(CMD_Request *msg, char *line)
|
|
{
|
|
CPS_NTP_Source data;
|
|
IPAddr ip_addr;
|
|
int result = 0, status, type;
|
|
const char *opt_name, *word;
|
|
|
|
msg->command = htons(REQ_ADD_SOURCE);
|
|
|
|
word = line;
|
|
line = CPS_SplitWord(line);
|
|
|
|
if (!strcasecmp(word, "server")) {
|
|
type = REQ_ADDSRC_SERVER;
|
|
} else if (!strcasecmp(word, "peer")) {
|
|
type = REQ_ADDSRC_PEER;
|
|
} else if (!strcasecmp(word, "pool")) {
|
|
type = REQ_ADDSRC_POOL;
|
|
} else {
|
|
LOG(LOGS_ERR, "Invalid syntax for add command");
|
|
return 0;
|
|
}
|
|
|
|
status = CPS_ParseNTPSourceAdd(line, &data);
|
|
switch (status) {
|
|
case 0:
|
|
LOG(LOGS_ERR, "Invalid syntax for add command");
|
|
break;
|
|
default:
|
|
/* Verify that the address is resolvable (chronyc and chronyd are
|
|
assumed to be running on the same host) */
|
|
if (strlen(data.name) >= sizeof (msg->data.ntp_source.name) ||
|
|
DNS_Name2IPAddress(data.name, &ip_addr, 1) != DNS_Success) {
|
|
LOG(LOGS_ERR, "Invalid host/IP address");
|
|
break;
|
|
}
|
|
|
|
opt_name = NULL;
|
|
if (opt_name) {
|
|
LOG(LOGS_ERR, "%s can't be set in chronyc", opt_name);
|
|
break;
|
|
}
|
|
|
|
msg->data.ntp_source.type = htonl(type);
|
|
if (strlen(data.name) >= sizeof (msg->data.ntp_source.name))
|
|
assert(0);
|
|
strncpy((char *)msg->data.ntp_source.name, data.name,
|
|
sizeof (msg->data.ntp_source.name));
|
|
msg->data.ntp_source.port = htonl(data.port);
|
|
msg->data.ntp_source.minpoll = htonl(data.params.minpoll);
|
|
msg->data.ntp_source.maxpoll = htonl(data.params.maxpoll);
|
|
msg->data.ntp_source.presend_minpoll = htonl(data.params.presend_minpoll);
|
|
msg->data.ntp_source.min_stratum = htonl(data.params.min_stratum);
|
|
msg->data.ntp_source.poll_target = htonl(data.params.poll_target);
|
|
msg->data.ntp_source.version = htonl(data.params.version);
|
|
msg->data.ntp_source.max_sources = htonl(data.params.max_sources);
|
|
msg->data.ntp_source.min_samples = htonl(data.params.min_samples);
|
|
msg->data.ntp_source.max_samples = htonl(data.params.max_samples);
|
|
msg->data.ntp_source.authkey = htonl(data.params.authkey);
|
|
msg->data.ntp_source.nts_port = htonl(data.params.nts_port);
|
|
msg->data.ntp_source.max_delay = UTI_FloatHostToNetwork(data.params.max_delay);
|
|
msg->data.ntp_source.max_delay_ratio = UTI_FloatHostToNetwork(data.params.max_delay_ratio);
|
|
msg->data.ntp_source.max_delay_dev_ratio =
|
|
UTI_FloatHostToNetwork(data.params.max_delay_dev_ratio);
|
|
msg->data.ntp_source.min_delay = UTI_FloatHostToNetwork(data.params.min_delay);
|
|
msg->data.ntp_source.asymmetry = UTI_FloatHostToNetwork(data.params.asymmetry);
|
|
msg->data.ntp_source.offset = UTI_FloatHostToNetwork(data.params.offset);
|
|
msg->data.ntp_source.flags = htonl(
|
|
(data.params.connectivity == SRC_ONLINE ? REQ_ADDSRC_ONLINE : 0) |
|
|
(data.params.auto_offline ? REQ_ADDSRC_AUTOOFFLINE : 0) |
|
|
(data.params.iburst ? REQ_ADDSRC_IBURST : 0) |
|
|
(data.params.interleaved ? REQ_ADDSRC_INTERLEAVED : 0) |
|
|
(data.params.burst ? REQ_ADDSRC_BURST : 0) |
|
|
(data.params.nts ? REQ_ADDSRC_NTS : 0) |
|
|
(data.params.copy ? REQ_ADDSRC_COPY : 0) |
|
|
(data.params.ext_fields & NTP_EF_FLAG_EXP_MONO_ROOT ?
|
|
REQ_ADDSRC_EF_EXP_MONO_ROOT : 0) |
|
|
(data.params.ext_fields & NTP_EF_FLAG_EXP_NET_CORRECTION ?
|
|
REQ_ADDSRC_EF_EXP_NET_CORRECTION : 0) |
|
|
convert_addsrc_sel_options(data.params.sel_options));
|
|
msg->data.ntp_source.filter_length = htonl(data.params.filter_length);
|
|
msg->data.ntp_source.cert_set = htonl(data.params.cert_set);
|
|
msg->data.ntp_source.max_delay_quant =
|
|
UTI_FloatHostToNetwork(data.params.max_delay_quant);
|
|
memset(msg->data.ntp_source.reserved, 0, sizeof (msg->data.ntp_source.reserved));
|
|
|
|
result = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_delete(CMD_Request *msg, char *line)
|
|
{
|
|
char *hostname;
|
|
int ok = 0;
|
|
IPAddr address;
|
|
|
|
msg->command = htons(REQ_DEL_SOURCE);
|
|
hostname = line;
|
|
CPS_SplitWord(line);
|
|
|
|
if (!*hostname) {
|
|
LOG(LOGS_ERR, "Invalid syntax for address");
|
|
ok = 0;
|
|
} else {
|
|
if (!parse_source_address(hostname, &address)) {
|
|
LOG(LOGS_ERR, "Could not get address for hostname");
|
|
ok = 0;
|
|
} else {
|
|
UTI_IPHostToNetwork(&address, &msg->data.del_source.ip_addr);
|
|
ok = 1;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
give_help(void)
|
|
{
|
|
int line, len;
|
|
const char *s, cols[] =
|
|
"System clock:\0\0"
|
|
"tracking\0Display system time information\0"
|
|
"makestep\0Correct clock by stepping immediately\0"
|
|
"makestep <threshold> <updates>\0Configure automatic clock stepping\0"
|
|
"maxupdateskew <skew>\0Modify maximum valid skew to update frequency\0"
|
|
"waitsync [<max-tries> [<max-correction> [<max-skew> [<interval>]]]]\0"
|
|
"Wait until synchronised in specified limits\0"
|
|
"\0\0"
|
|
"Time sources:\0\0"
|
|
"sources [-a] [-v]\0Display information about current sources\0"
|
|
"sourcestats [-a] [-v]\0Display statistics about collected measurements\0"
|
|
"selectdata [-a] [-v]\0Display information about source selection\0"
|
|
"selectopts <address|refid> <+|-options>\0Modify selection options\0"
|
|
"reselect\0Force reselecting synchronisation source\0"
|
|
"reselectdist <dist>\0Modify reselection distance\0"
|
|
"\0\0"
|
|
"NTP sources:\0\0"
|
|
"activity\0Check how many NTP sources are online/offline\0"
|
|
"authdata [-a] [-v]\0Display information about authentication\0"
|
|
"ntpdata [<address>]\0Display information about last valid measurement\0"
|
|
"add server <name> [options]\0Add new NTP server\0"
|
|
"add pool <name> [options]\0Add new pool of NTP servers\0"
|
|
"add peer <name> [options]\0Add new NTP peer\0"
|
|
"delete <address>\0Remove server or peer\0"
|
|
"burst <n-good>/<n-max> [[<mask>/]<address>]\0Start rapid set of measurements\0"
|
|
"maxdelay <address> <delay>\0Modify maximum valid sample delay\0"
|
|
"maxdelayratio <address> <ratio>\0Modify maximum valid delay/minimum ratio\0"
|
|
"maxdelaydevratio <address> <ratio>\0Modify maximum valid delay/deviation ratio\0"
|
|
"minpoll <address> <poll>\0Modify minimum polling interval\0"
|
|
"maxpoll <address> <poll>\0Modify maximum polling interval\0"
|
|
"minstratum <address> <stratum>\0Modify minimum stratum\0"
|
|
"offline [[<mask>/]<address>]\0Set sources in subnet to offline status\0"
|
|
"online [[<mask>/]<address>]\0Set sources in subnet to online status\0"
|
|
"onoffline\0Set all sources to online or offline status\0"
|
|
"\0according to network configuration\0"
|
|
"polltarget <address> <target>\0Modify poll target\0"
|
|
"refresh\0Refresh IP addresses\0"
|
|
"reload sources\0Re-read *.sources files\0"
|
|
"sourcename <address>\0Display original name\0"
|
|
"\0\0"
|
|
"Manual time input:\0\0"
|
|
"manual off|on|reset\0Disable/enable/reset settime command\0"
|
|
"manual list\0Show previous settime entries\0"
|
|
"manual delete <index>\0Delete previous settime entry\0"
|
|
"settime <time>\0Set daemon time\0"
|
|
"\0(e.g. Sep 25, 2015 16:30:05 or 16:30:05)\0"
|
|
"\0\0NTP access:\0\0"
|
|
"accheck <address>\0Check whether address is allowed\0"
|
|
"clients [-p <packets>] [-k] [-r]\0Report on clients that accessed the server\0"
|
|
"serverstats\0Display statistics of the server\0"
|
|
"allow [<subnet>]\0Allow access to subnet as a default\0"
|
|
"allow all [<subnet>]\0Allow access to subnet and all children\0"
|
|
"deny [<subnet>]\0Deny access to subnet as a default\0"
|
|
"deny all [<subnet>]\0Deny access to subnet and all children\0"
|
|
"local [options]\0Serve time even when not synchronised\0"
|
|
"local off\0Don't serve time when not synchronised\0"
|
|
"smoothtime reset|activate\0Reset/activate time smoothing\0"
|
|
"smoothing\0Display current time smoothing state\0"
|
|
"\0\0"
|
|
"Monitoring access:\0\0"
|
|
"cmdaccheck <address>\0Check whether address is allowed\0"
|
|
"cmdallow [<subnet>]\0Allow access to subnet as a default\0"
|
|
"cmdallow all [<subnet>]\0Allow access to subnet and all children\0"
|
|
"cmddeny [<subnet>]\0Deny access to subnet as a default\0"
|
|
"cmddeny all [<subnet>]\0Deny access to subnet and all children\0"
|
|
"\0\0"
|
|
"Real-time clock:\0\0"
|
|
"rtcdata\0Print current RTC performance parameters\0"
|
|
"trimrtc\0Correct RTC relative to system clock\0"
|
|
"writertc\0Save RTC performance parameters to file\0"
|
|
"\0\0"
|
|
"Other daemon commands:\0\0"
|
|
"cyclelogs\0Close and re-open log files\0"
|
|
"dump\0Dump measurements and NTS keys/cookies\0"
|
|
"rekey\0Re-read keys\0"
|
|
"reset sources\0Drop all measurements\0"
|
|
"shutdown\0Stop daemon\0"
|
|
"\0\0"
|
|
"Client commands:\0\0"
|
|
"dns -n|+n\0Disable/enable resolving IP addresses to hostnames\0"
|
|
"dns -4|-6|-46\0Resolve hostnames only to IPv4/IPv6/both addresses\0"
|
|
"timeout <milliseconds>\0Set initial response timeout\0"
|
|
"retries <retries>\0Set maximum number of retries\0"
|
|
"keygen [<id> [<type> [<bits>]]]\0Generate key for key file\0"
|
|
"exit|quit\0Leave the program\0"
|
|
"help\0Generate this help\0"
|
|
"\0";
|
|
|
|
/* Indent the second column */
|
|
for (s = cols, line = 0; s < cols + sizeof (cols); s += len + 1, line++) {
|
|
len = strlen(s);
|
|
printf(line % 2 == 0 ? (len >= 28 ? "%s\n%28s" : "%-28s%s") : "%s%s\n",
|
|
s, "");
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Tab-completion when editline is available */
|
|
|
|
#ifdef FEAT_READLINE
|
|
|
|
enum {
|
|
TAB_COMPLETE_BASE_CMDS,
|
|
TAB_COMPLETE_ADD_OPTS,
|
|
TAB_COMPLETE_MANUAL_OPTS,
|
|
TAB_COMPLETE_RELOAD_OPTS,
|
|
TAB_COMPLETE_RESET_OPTS,
|
|
TAB_COMPLETE_SOURCES_OPTS,
|
|
TAB_COMPLETE_SOURCESTATS_OPTS,
|
|
TAB_COMPLETE_AUTHDATA_OPTS,
|
|
TAB_COMPLETE_SELECTDATA_OPTS,
|
|
TAB_COMPLETE_MAX_INDEX
|
|
};
|
|
|
|
static int tab_complete_index;
|
|
|
|
static char *
|
|
command_name_generator(const char *text, int state)
|
|
{
|
|
const char *name, **names[TAB_COMPLETE_MAX_INDEX];
|
|
const char *base_commands[] = {
|
|
"accheck", "activity", "add", "allow", "authdata", "burst",
|
|
"clients", "cmdaccheck", "cmdallow", "cmddeny", "cyclelogs", "delete",
|
|
"deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep",
|
|
"manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
|
|
"maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline",
|
|
"polltarget", "quit", "refresh", "rekey", "reload", "reselect", "reselectdist", "reset",
|
|
"retries", "rtcdata", "selectdata", "selectopts", "serverstats", "settime",
|
|
"shutdown", "smoothing", "smoothtime", "sourcename", "sources", "sourcestats",
|
|
"timeout", "tracking", "trimrtc", "waitsync", "writertc",
|
|
NULL
|
|
};
|
|
const char *add_options[] = { "peer", "pool", "server", NULL };
|
|
const char *manual_options[] = { "on", "off", "delete", "list", "reset", NULL };
|
|
const char *reset_options[] = { "sources", NULL };
|
|
const char *reload_options[] = { "sources", NULL };
|
|
const char *common_source_options[] = { "-a", "-v", NULL };
|
|
static int list_index, len;
|
|
|
|
names[TAB_COMPLETE_BASE_CMDS] = base_commands;
|
|
names[TAB_COMPLETE_ADD_OPTS] = add_options;
|
|
names[TAB_COMPLETE_MANUAL_OPTS] = manual_options;
|
|
names[TAB_COMPLETE_RELOAD_OPTS] = reload_options;
|
|
names[TAB_COMPLETE_RESET_OPTS] = reset_options;
|
|
names[TAB_COMPLETE_AUTHDATA_OPTS] = common_source_options;
|
|
names[TAB_COMPLETE_SELECTDATA_OPTS] = common_source_options;
|
|
names[TAB_COMPLETE_SOURCES_OPTS] = common_source_options;
|
|
names[TAB_COMPLETE_SOURCESTATS_OPTS] = common_source_options;
|
|
|
|
if (!state) {
|
|
list_index = 0;
|
|
len = strlen(text);
|
|
}
|
|
|
|
while ((name = names[tab_complete_index][list_index++])) {
|
|
if (strncmp(name, text, len) == 0) {
|
|
return Strdup(name);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static char **
|
|
command_name_completion(const char *text, int start, int end)
|
|
{
|
|
char first[32];
|
|
|
|
snprintf(first, MIN(sizeof (first), start + 1), "%s", rl_line_buffer);
|
|
rl_attempted_completion_over = 1;
|
|
|
|
if (!strcmp(first, "add ")) {
|
|
tab_complete_index = TAB_COMPLETE_ADD_OPTS;
|
|
} else if (!strcmp(first, "authdata ")) {
|
|
tab_complete_index = TAB_COMPLETE_AUTHDATA_OPTS;
|
|
} else if (!strcmp(first, "manual ")) {
|
|
tab_complete_index = TAB_COMPLETE_MANUAL_OPTS;
|
|
} else if (!strcmp(first, "reload ")) {
|
|
tab_complete_index = TAB_COMPLETE_RELOAD_OPTS;
|
|
} else if (!strcmp(first, "reset ")) {
|
|
tab_complete_index = TAB_COMPLETE_RESET_OPTS;
|
|
} else if (!strcmp(first, "selectdata ")) {
|
|
tab_complete_index = TAB_COMPLETE_SELECTDATA_OPTS;
|
|
} else if (!strcmp(first, "sources ")) {
|
|
tab_complete_index = TAB_COMPLETE_SOURCES_OPTS;
|
|
} else if (!strcmp(first, "sourcestats ")) {
|
|
tab_complete_index = TAB_COMPLETE_SOURCESTATS_OPTS;
|
|
} else if (first[0] == '\0') {
|
|
tab_complete_index = TAB_COMPLETE_BASE_CMDS;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
return rl_completion_matches(text, command_name_generator);
|
|
}
|
|
#endif
|
|
|
|
/* ================================================== */
|
|
|
|
static int max_retries = 2;
|
|
static int initial_timeout = 1000;
|
|
static int proto_version = PROTO_VERSION_NUMBER;
|
|
|
|
/* This is the core protocol module. Complete particular fields in
|
|
the outgoing packet, send it, wait for a response, handle retries,
|
|
etc. Returns a Boolean indicating whether the protocol was
|
|
successful or not.*/
|
|
|
|
static int
|
|
submit_request(CMD_Request *request, CMD_Reply *reply)
|
|
{
|
|
int select_status;
|
|
int recv_status;
|
|
int read_length;
|
|
int command_length;
|
|
int padding_length;
|
|
struct timespec ts_now, ts_start;
|
|
struct timeval tv;
|
|
int n_attempts, new_attempt;
|
|
double timeout;
|
|
fd_set rdfd;
|
|
|
|
request->pkt_type = PKT_TYPE_CMD_REQUEST;
|
|
request->res1 = 0;
|
|
request->res2 = 0;
|
|
request->pad1 = 0;
|
|
request->pad2 = 0;
|
|
|
|
n_attempts = 0;
|
|
new_attempt = 1;
|
|
|
|
do {
|
|
if (gettimeofday(&tv, NULL))
|
|
return 0;
|
|
|
|
if (new_attempt) {
|
|
new_attempt = 0;
|
|
|
|
if (n_attempts > max_retries)
|
|
return 0;
|
|
|
|
UTI_TimevalToTimespec(&tv, &ts_start);
|
|
|
|
UTI_GetRandomBytes(&request->sequence, sizeof (request->sequence));
|
|
request->attempt = htons(n_attempts);
|
|
request->version = proto_version;
|
|
command_length = PKL_CommandLength(request);
|
|
padding_length = PKL_CommandPaddingLength(request);
|
|
assert(command_length > 0 && command_length > padding_length);
|
|
|
|
n_attempts++;
|
|
|
|
/* Zero the padding to not send any uninitialized data */
|
|
memset(((char *)request) + command_length - padding_length, 0, padding_length);
|
|
|
|
if (sock_fd < 0) {
|
|
DEBUG_LOG("No socket to send request");
|
|
return 0;
|
|
}
|
|
|
|
if (SCK_Send(sock_fd, (void *)request, command_length, 0) < 0)
|
|
return 0;
|
|
}
|
|
|
|
UTI_TimevalToTimespec(&tv, &ts_now);
|
|
|
|
/* Check if the clock wasn't stepped back */
|
|
if (UTI_CompareTimespecs(&ts_now, &ts_start) < 0)
|
|
ts_start = ts_now;
|
|
|
|
timeout = initial_timeout / 1000.0 * (1U << (n_attempts - 1)) -
|
|
UTI_DiffTimespecsToDouble(&ts_now, &ts_start);
|
|
DEBUG_LOG("Timeout %f seconds", timeout);
|
|
|
|
/* Avoid calling select() with an invalid timeout */
|
|
if (timeout <= 0.0) {
|
|
new_attempt = 1;
|
|
continue;
|
|
}
|
|
|
|
UTI_DoubleToTimeval(timeout, &tv);
|
|
|
|
FD_ZERO(&rdfd);
|
|
FD_SET(sock_fd, &rdfd);
|
|
|
|
if (quit)
|
|
return 0;
|
|
|
|
select_status = select(sock_fd + 1, &rdfd, NULL, NULL, &tv);
|
|
|
|
if (select_status < 0) {
|
|
DEBUG_LOG("select failed : %s", strerror(errno));
|
|
return 0;
|
|
} else if (select_status == 0) {
|
|
/* Timeout must have elapsed, try a resend? */
|
|
new_attempt = 1;
|
|
} else {
|
|
recv_status = SCK_Receive(sock_fd, reply, sizeof (*reply), 0);
|
|
|
|
if (recv_status < 0) {
|
|
new_attempt = 1;
|
|
} else {
|
|
read_length = recv_status;
|
|
|
|
/* Check if the header is valid */
|
|
if (read_length < offsetof(CMD_Reply, data) ||
|
|
(reply->version != proto_version &&
|
|
!(reply->version >= PROTO_VERSION_MISMATCH_COMPAT_CLIENT &&
|
|
ntohs(reply->status) == STT_BADPKTVERSION)) ||
|
|
reply->pkt_type != PKT_TYPE_CMD_REPLY ||
|
|
reply->res1 != 0 ||
|
|
reply->res2 != 0 ||
|
|
reply->command != request->command ||
|
|
reply->sequence != request->sequence) {
|
|
DEBUG_LOG("Invalid reply");
|
|
continue;
|
|
}
|
|
|
|
#if PROTO_VERSION_NUMBER == 6
|
|
/* Protocol version 5 is similar to 6 except there is no padding.
|
|
If a version 5 reply with STT_BADPKTVERSION is received,
|
|
switch our version and try again. */
|
|
if (proto_version == PROTO_VERSION_NUMBER &&
|
|
reply->version == PROTO_VERSION_NUMBER - 1) {
|
|
proto_version = PROTO_VERSION_NUMBER - 1;
|
|
n_attempts--;
|
|
new_attempt = 1;
|
|
continue;
|
|
}
|
|
#else
|
|
#error unknown compatibility with PROTO_VERSION - 1
|
|
#endif
|
|
|
|
/* Check that the packet contains all data it is supposed to have.
|
|
Unknown responses will always pass this test as their expected
|
|
length is zero. */
|
|
if (read_length < PKL_ReplyLength(reply)) {
|
|
DEBUG_LOG("Reply too short");
|
|
new_attempt = 1;
|
|
continue;
|
|
}
|
|
|
|
/* Good packet received, print out results */
|
|
DEBUG_LOG("Reply cmd=%d reply=%d stat=%d",
|
|
ntohs(reply->command), ntohs(reply->reply), ntohs(reply->status));
|
|
break;
|
|
}
|
|
}
|
|
} while (1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
request_reply(CMD_Request *request, CMD_Reply *reply, int requested_reply, int verbose)
|
|
{
|
|
int status;
|
|
|
|
while (!submit_request(request, reply)) {
|
|
/* Try connecting to other addresses before giving up */
|
|
if (open_io())
|
|
continue;
|
|
printf("506 Cannot talk to daemon\n");
|
|
return 0;
|
|
}
|
|
|
|
status = ntohs(reply->status);
|
|
|
|
if (verbose || status != STT_SUCCESS) {
|
|
switch (status) {
|
|
case STT_SUCCESS:
|
|
printf("200 OK");
|
|
break;
|
|
case STT_ACCESSALLOWED:
|
|
printf("208 Access allowed");
|
|
break;
|
|
case STT_ACCESSDENIED:
|
|
printf("209 Access denied");
|
|
break;
|
|
case STT_FAILED:
|
|
printf("500 Failure");
|
|
break;
|
|
case STT_UNAUTH:
|
|
printf("501 Not authorised");
|
|
break;
|
|
case STT_INVALID:
|
|
printf("502 Invalid command");
|
|
break;
|
|
case STT_NOSUCHSOURCE:
|
|
printf("503 No such source");
|
|
break;
|
|
case STT_INVALIDTS:
|
|
printf("504 Duplicate or stale logon detected");
|
|
break;
|
|
case STT_NOTENABLED:
|
|
printf("505 Facility not enabled in daemon");
|
|
break;
|
|
case STT_BADSUBNET:
|
|
printf("507 Bad subnet");
|
|
break;
|
|
case STT_NOHOSTACCESS:
|
|
printf("510 No command access from this host");
|
|
break;
|
|
case STT_SOURCEALREADYKNOWN:
|
|
printf("511 Source already present");
|
|
break;
|
|
case STT_TOOMANYSOURCES:
|
|
printf("512 Too many sources present");
|
|
break;
|
|
case STT_NORTC:
|
|
printf("513 RTC driver not running");
|
|
break;
|
|
case STT_BADRTCFILE:
|
|
printf("514 Can't write RTC parameters");
|
|
break;
|
|
case STT_INVALIDAF:
|
|
printf("515 Invalid address family");
|
|
break;
|
|
case STT_BADSAMPLE:
|
|
printf("516 Sample index out of range");
|
|
break;
|
|
case STT_BADPKTVERSION:
|
|
printf("517 Protocol version mismatch");
|
|
break;
|
|
case STT_BADPKTLENGTH:
|
|
printf("518 Packet length mismatch");
|
|
break;
|
|
case STT_INACTIVE:
|
|
printf("519 Client logging is not active in the daemon");
|
|
break;
|
|
case STT_INVALIDNAME:
|
|
printf("521 Invalid name");
|
|
break;
|
|
default:
|
|
printf("520 Got unexpected error from daemon");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
if (status != STT_SUCCESS &&
|
|
status != STT_ACCESSALLOWED && status != STT_ACCESSDENIED) {
|
|
return 0;
|
|
}
|
|
|
|
if (ntohs(reply->reply) != requested_reply) {
|
|
printf("508 Bad reply from daemon\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Make sure an unknown response was not requested */
|
|
assert(PKL_ReplyLength(reply));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_seconds(uint32_t s)
|
|
{
|
|
uint32_t d;
|
|
|
|
if (s == (uint32_t)-1) {
|
|
printf(" -");
|
|
} else if (s < 1200) {
|
|
printf("%4"PRIu32, s);
|
|
} else if (s < 36000) {
|
|
printf("%3"PRIu32"m", s / 60);
|
|
} else if (s < 345600) {
|
|
printf("%3"PRIu32"h", s / 3600);
|
|
} else {
|
|
d = s / 86400;
|
|
if (d > 999) {
|
|
printf("%3"PRIu32"y", d / 365);
|
|
} else {
|
|
printf("%3"PRIu32"d", d);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_nanoseconds(double s)
|
|
{
|
|
s = fabs(s);
|
|
|
|
if (s < 9999.5e-9) {
|
|
printf("%4.0fns", s * 1e9);
|
|
} else if (s < 9999.5e-6) {
|
|
printf("%4.0fus", s * 1e6);
|
|
} else if (s < 9999.5e-3) {
|
|
printf("%4.0fms", s * 1e3);
|
|
} else if (s < 999.5) {
|
|
printf("%5.1fs", s);
|
|
} else if (s < 99999.5) {
|
|
printf("%5.0fs", s);
|
|
} else if (s < 99999.5 * 60) {
|
|
printf("%5.0fm", s / 60);
|
|
} else if (s < 99999.5 * 3600) {
|
|
printf("%5.0fh", s / 3600);
|
|
} else if (s < 99999.5 * 3600 * 24) {
|
|
printf("%5.0fd", s / (3600 * 24));
|
|
} else {
|
|
printf("%5.0fy", s / (3600 * 24 * 365));
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_signed_nanoseconds(double s)
|
|
{
|
|
double x;
|
|
|
|
x = fabs(s);
|
|
|
|
if (x < 9999.5e-9) {
|
|
printf("%+5.0fns", s * 1e9);
|
|
} else if (x < 9999.5e-6) {
|
|
printf("%+5.0fus", s * 1e6);
|
|
} else if (x < 9999.5e-3) {
|
|
printf("%+5.0fms", s * 1e3);
|
|
} else if (x < 999.5) {
|
|
printf("%+6.1fs", s);
|
|
} else if (x < 99999.5) {
|
|
printf("%+6.0fs", s);
|
|
} else if (x < 99999.5 * 60) {
|
|
printf("%+6.0fm", s / 60);
|
|
} else if (x < 99999.5 * 3600) {
|
|
printf("%+6.0fh", s / 3600);
|
|
} else if (x < 99999.5 * 3600 * 24) {
|
|
printf("%+6.0fd", s / (3600 * 24));
|
|
} else {
|
|
printf("%+6.0fy", s / (3600 * 24 * 365));
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_freq_ppm(double f)
|
|
{
|
|
if (fabs(f) < 99999.5) {
|
|
printf("%10.3f", f);
|
|
} else {
|
|
printf("%10.0f", f);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_signed_freq_ppm(double f)
|
|
{
|
|
if (fabs(f) < 99999.5) {
|
|
printf("%+10.3f", f);
|
|
} else {
|
|
printf("%+10.0f", f);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_clientlog_interval(int rate)
|
|
{
|
|
if (rate >= 127) {
|
|
printf(" -");
|
|
} else {
|
|
printf("%2d", rate);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_header(const char *header)
|
|
{
|
|
int len;
|
|
|
|
if (csv_mode)
|
|
return;
|
|
|
|
printf("%s\n", header);
|
|
|
|
len = strlen(header);
|
|
while (len--)
|
|
printf("=");
|
|
printf("\n");
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
#define REPORT_END 0x1234
|
|
|
|
/* Print a report. The syntax of the format is similar to printf(), but not all
|
|
specifiers are supported and some are different! */
|
|
|
|
static void
|
|
print_report(const char *format, ...)
|
|
{
|
|
char buf[256];
|
|
va_list ap;
|
|
int i, field, sign, width, prec, spec;
|
|
const char *string;
|
|
unsigned int uinteger;
|
|
uint64_t uinteger64;
|
|
uint32_t uinteger32;
|
|
int integer;
|
|
struct timespec *ts;
|
|
struct tm *tm;
|
|
double dbl;
|
|
|
|
va_start(ap, format);
|
|
|
|
for (field = 0; ; field++) {
|
|
/* Search for text between format specifiers and print it
|
|
if not in the CSV mode */
|
|
for (i = 0; i < sizeof (buf) && format[i] != '%' && format[i] != '\0'; i++)
|
|
buf[i] = format[i];
|
|
|
|
if (i >= sizeof (buf))
|
|
break;
|
|
|
|
buf[i] = '\0';
|
|
|
|
if (!csv_mode)
|
|
printf("%s", buf);
|
|
|
|
if (format[i] == '\0' || format[i + 1] == '\0')
|
|
break;
|
|
|
|
format += i + 1;
|
|
|
|
sign = 0;
|
|
width = 0;
|
|
prec = 5;
|
|
|
|
if (*format == '+' || *format == '-') {
|
|
sign = 1;
|
|
format++;
|
|
}
|
|
|
|
if (isdigit((unsigned char)*format)) {
|
|
width = atoi(format);
|
|
while (isdigit((unsigned char)*format))
|
|
format++;
|
|
}
|
|
|
|
if (*format == '.') {
|
|
format++;
|
|
prec = atoi(format);
|
|
while (isdigit((unsigned char)*format))
|
|
format++;
|
|
}
|
|
|
|
spec = *format;
|
|
format++;
|
|
|
|
/* Disable human-readable formatting in the CSV mode */
|
|
if (csv_mode) {
|
|
sign = width = 0;
|
|
|
|
if (field > 0)
|
|
printf(",");
|
|
|
|
switch (spec) {
|
|
case 'C':
|
|
spec = 'd';
|
|
break;
|
|
case 'F':
|
|
case 'P':
|
|
prec = 3;
|
|
spec = 'f';
|
|
break;
|
|
case 'O':
|
|
case 'S':
|
|
prec = 9;
|
|
spec = 'f';
|
|
break;
|
|
case 'I':
|
|
spec = 'U';
|
|
break;
|
|
case 'T':
|
|
spec = 'V';
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (spec) {
|
|
case 'B': /* boolean */
|
|
integer = va_arg(ap, int);
|
|
printf("%s", integer ? "Yes" : "No");
|
|
break;
|
|
case 'C': /* clientlog interval */
|
|
integer = va_arg(ap, int);
|
|
print_clientlog_interval(integer);
|
|
break;
|
|
case 'F': /* absolute frequency in ppm with fast/slow keyword */
|
|
case 'O': /* absolute offset in seconds with fast/slow keyword */
|
|
dbl = va_arg(ap, double);
|
|
printf("%*.*f %s %s", width, prec, fabs(dbl),
|
|
spec == 'O' ? "seconds" : "ppm",
|
|
(dbl > 0.0) ^ (spec != 'O') ? "slow" : "fast");
|
|
break;
|
|
case 'I': /* uint32_t interval with unit */
|
|
uinteger32 = va_arg(ap, uint32_t);
|
|
print_seconds(uinteger32);
|
|
break;
|
|
case 'L': /* leap status */
|
|
integer = va_arg(ap, int);
|
|
switch (integer) {
|
|
case LEAP_Normal:
|
|
string = width != 1 ? "Normal" : "N";
|
|
break;
|
|
case LEAP_InsertSecond:
|
|
string = width != 1 ? "Insert second" : "+";
|
|
break;
|
|
case LEAP_DeleteSecond:
|
|
string = width != 1 ? "Delete second" : "-";
|
|
break;
|
|
case LEAP_Unsynchronised:
|
|
string = width != 1 ? "Not synchronised" : "?";
|
|
break;
|
|
default:
|
|
string = width != 1 ? "Invalid" : "?";
|
|
break;
|
|
}
|
|
printf("%s", string);
|
|
break;
|
|
case 'M': /* NTP mode */
|
|
integer = va_arg(ap, int);
|
|
switch (integer) {
|
|
case MODE_ACTIVE:
|
|
string = "Symmetric active";
|
|
break;
|
|
case MODE_PASSIVE:
|
|
string = "Symmetric passive";
|
|
break;
|
|
case MODE_SERVER:
|
|
string = "Server";
|
|
break;
|
|
default:
|
|
string = "Invalid";
|
|
break;
|
|
}
|
|
printf("%s", string);
|
|
break;
|
|
case 'N': /* Timestamp source */
|
|
integer = va_arg(ap, int);
|
|
switch (integer) {
|
|
case 'D':
|
|
string = "Daemon";
|
|
break;
|
|
case 'K':
|
|
string = "Kernel";
|
|
break;
|
|
case 'H':
|
|
string = "Hardware";
|
|
break;
|
|
default:
|
|
string = "Invalid";
|
|
break;
|
|
}
|
|
printf("%s", string);
|
|
break;
|
|
case 'P': /* frequency in ppm */
|
|
dbl = va_arg(ap, double);
|
|
if (sign)
|
|
print_signed_freq_ppm(dbl);
|
|
else
|
|
print_freq_ppm(dbl);
|
|
break;
|
|
case 'R': /* reference ID in hexdecimal */
|
|
uinteger32 = va_arg(ap, uint32_t);
|
|
printf("%08"PRIX32, uinteger32);
|
|
break;
|
|
case 'S': /* offset with unit */
|
|
dbl = va_arg(ap, double);
|
|
if (sign)
|
|
print_signed_nanoseconds(dbl);
|
|
else
|
|
print_nanoseconds(dbl);
|
|
break;
|
|
case 'T': /* timespec as date and time in UTC */
|
|
ts = va_arg(ap, struct timespec *);
|
|
tm = gmtime(&ts->tv_sec);
|
|
if (!tm)
|
|
break;
|
|
strftime(buf, sizeof (buf), "%a %b %d %T %Y", tm);
|
|
printf("%s", buf);
|
|
break;
|
|
case 'U': /* uint32_t in decimal */
|
|
uinteger32 = va_arg(ap, uint32_t);
|
|
printf("%*"PRIu32, width, uinteger32);
|
|
break;
|
|
case 'V': /* timespec as seconds since epoch */
|
|
ts = va_arg(ap, struct timespec *);
|
|
printf("%s", UTI_TimespecToString(ts));
|
|
break;
|
|
case 'Q': /* uint64_t in decimal */
|
|
uinteger64 = va_arg(ap, uint64_t);
|
|
printf("%*"PRIu64, width, uinteger64);
|
|
break;
|
|
case 'b': /* unsigned int in binary */
|
|
uinteger = va_arg(ap, unsigned int);
|
|
for (i = prec - 1; i >= 0; i--)
|
|
printf("%c", uinteger & 1U << i ? '1' : '0');
|
|
break;
|
|
|
|
/* Classic printf specifiers */
|
|
case 'c': /* character */
|
|
integer = va_arg(ap, int);
|
|
printf("%c", integer);
|
|
break;
|
|
case 'd': /* signed int in decimal */
|
|
integer = va_arg(ap, int);
|
|
printf("%*d", width, integer);
|
|
break;
|
|
case 'f': /* double */
|
|
dbl = va_arg(ap, double);
|
|
printf(sign ? "%+*.*f" : "%*.*f", width, prec, dbl);
|
|
break;
|
|
case 'o': /* unsigned int in octal */
|
|
uinteger = va_arg(ap, unsigned int);
|
|
printf("%*o", width, uinteger);
|
|
break;
|
|
case 's': /* string */
|
|
string = va_arg(ap, const char *);
|
|
if (sign)
|
|
printf("%-*s", width, string);
|
|
else
|
|
printf("%*s", width, string);
|
|
break;
|
|
case 'u': /* unsigned int in decimal */
|
|
uinteger = va_arg(ap, unsigned int);
|
|
printf("%*u", width, uinteger);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Require terminating argument to catch bad type conversions */
|
|
if (va_arg(ap, int) != REPORT_END)
|
|
assert(0);
|
|
|
|
va_end(ap);
|
|
|
|
if (csv_mode)
|
|
printf("\n");
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_info_field(const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (csv_mode)
|
|
return;
|
|
|
|
va_start(ap, format);
|
|
vprintf(format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
get_source_name(IPAddr *ip_addr, char *buf, int size)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
int i;
|
|
|
|
request.command = htons(REQ_NTP_SOURCE_NAME);
|
|
UTI_IPHostToNetwork(ip_addr, &request.data.ntp_source_name.ip_addr);
|
|
if (!request_reply(&request, &reply, RPY_NTP_SOURCE_NAME, 0) ||
|
|
reply.data.ntp_source_name.name[sizeof (reply.data.ntp_source_name.name) - 1] != '\0' ||
|
|
snprintf(buf, size, "%s", (char *)reply.data.ntp_source_name.name) >= size)
|
|
return 0;
|
|
|
|
/* Make sure the name is printable */
|
|
for (i = 0; i < size && buf[i] != '\0'; i++) {
|
|
if (!isgraph((unsigned char)buf[i]))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
format_name(char *buf, int size, int trunc_dns, int ref, uint32_t ref_id,
|
|
int source, IPAddr *ip_addr)
|
|
{
|
|
if (ref) {
|
|
snprintf(buf, size, "%s", UTI_RefidToString(ref_id));
|
|
} else if (source && source_names) {
|
|
if (!get_source_name(ip_addr, buf, size))
|
|
snprintf(buf, size, "?");
|
|
} else if (no_dns || csv_mode) {
|
|
snprintf(buf, size, "%s", UTI_IPToString(ip_addr));
|
|
} else {
|
|
DNS_IPAddress2Name(ip_addr, buf, size);
|
|
if (trunc_dns > 0 && strlen(buf) > trunc_dns) {
|
|
buf[trunc_dns - 1] = '>';
|
|
buf[trunc_dns] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
parse_sources_options(char *line, int *all, int *verbose)
|
|
{
|
|
char *opt;
|
|
|
|
*all = *verbose = 0;
|
|
|
|
while (*line) {
|
|
opt = line;
|
|
line = CPS_SplitWord(line);
|
|
if (!strcmp(opt, "-a"))
|
|
*all = 1;
|
|
else if (!strcmp(opt, "-v"))
|
|
*verbose = !csv_mode;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_sourcename(char *line)
|
|
{
|
|
IPAddr ip_addr;
|
|
char name[256];
|
|
|
|
if (!parse_source_address(line, &ip_addr)) {
|
|
LOG(LOGS_ERR, "Could not read address");
|
|
return 0;
|
|
}
|
|
|
|
if (!get_source_name(&ip_addr, name, sizeof (name)))
|
|
return 0;
|
|
|
|
print_report("%s\n", name, REPORT_END);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_sources(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
IPAddr ip_addr;
|
|
uint32_t i, mode, n_sources;
|
|
char name[256], mode_ch, state_ch;
|
|
int all, verbose, ref;
|
|
|
|
parse_sources_options(line, &all, &verbose);
|
|
|
|
request.command = htons(REQ_N_SOURCES);
|
|
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
|
|
return 0;
|
|
|
|
n_sources = ntohl(reply.data.n_sources.n_sources);
|
|
|
|
if (verbose) {
|
|
printf("\n");
|
|
printf(" .-- Source mode '^' = server, '=' = peer, '#' = local clock.\n");
|
|
printf(" / .- Source state '*' = current best, '+' = combined, '-' = not combined,\n");
|
|
printf("| / 'x' = may be in error, '~' = too variable, '?' = unusable.\n");
|
|
printf("|| .- xxxx [ yyyy ] +/- zzzz\n");
|
|
printf("|| Reachability register (octal) -. | xxxx = adjusted offset,\n");
|
|
printf("|| Log2(Polling interval) --. | | yyyy = measured offset,\n");
|
|
printf("|| \\ | | zzzz = estimated error.\n");
|
|
printf("|| | | \\\n");
|
|
}
|
|
|
|
print_header("MS Name/IP address Stratum Poll Reach LastRx Last sample ");
|
|
|
|
/* "MS NNNNNNNNNNNNNNNNNNNNNNNNNNN SS PP RRR RRRR SSSSSSS[SSSSSSS] +/- SSSSSS" */
|
|
|
|
for (i = 0; i < n_sources; i++) {
|
|
request.command = htons(REQ_SOURCE_DATA);
|
|
request.data.source_data.index = htonl(i);
|
|
if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
|
|
return 0;
|
|
|
|
mode = ntohs(reply.data.source_data.mode);
|
|
UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
|
|
if (!all && ip_addr.family == IPADDR_ID)
|
|
continue;
|
|
|
|
ref = mode == RPY_SD_MD_REF && ip_addr.family == IPADDR_INET4;
|
|
format_name(name, sizeof (name), 25, ref, ref ? ip_addr.addr.in4 : 0, 1, &ip_addr);
|
|
|
|
switch (mode) {
|
|
case RPY_SD_MD_CLIENT:
|
|
mode_ch = '^';
|
|
break;
|
|
case RPY_SD_MD_PEER:
|
|
mode_ch = '=';
|
|
break;
|
|
case RPY_SD_MD_REF:
|
|
mode_ch = '#';
|
|
break;
|
|
default:
|
|
mode_ch = ' ';
|
|
}
|
|
|
|
switch (ntohs(reply.data.source_data.state)) {
|
|
case RPY_SD_ST_SELECTED:
|
|
state_ch = '*';
|
|
break;
|
|
case RPY_SD_ST_NONSELECTABLE:
|
|
state_ch = '?';
|
|
break;
|
|
case RPY_SD_ST_FALSETICKER:
|
|
state_ch = 'x';
|
|
break;
|
|
case RPY_SD_ST_JITTERY:
|
|
state_ch = '~';
|
|
break;
|
|
case RPY_SD_ST_UNSELECTED:
|
|
state_ch = '+';
|
|
break;
|
|
case RPY_SD_ST_SELECTABLE:
|
|
state_ch = '-';
|
|
break;
|
|
default:
|
|
state_ch = ' ';
|
|
}
|
|
|
|
switch (ntohs(reply.data.source_data.flags)) {
|
|
default:
|
|
break;
|
|
}
|
|
|
|
print_report("%c%c %-27s %2d %2d %3o %I %+S[%+S] +/- %S\n",
|
|
mode_ch, state_ch, name,
|
|
ntohs(reply.data.source_data.stratum),
|
|
(int16_t)ntohs(reply.data.source_data.poll),
|
|
ntohs(reply.data.source_data.reachability),
|
|
ntohl(reply.data.source_data.since_sample),
|
|
UTI_FloatNetworkToHost(reply.data.source_data.latest_meas),
|
|
UTI_FloatNetworkToHost(reply.data.source_data.orig_latest_meas),
|
|
UTI_FloatNetworkToHost(reply.data.source_data.latest_meas_err),
|
|
REPORT_END);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_sourcestats(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
uint32_t i, n_sources;
|
|
int all, verbose;
|
|
char name[256];
|
|
IPAddr ip_addr;
|
|
|
|
parse_sources_options(line, &all, &verbose);
|
|
|
|
request.command = htons(REQ_N_SOURCES);
|
|
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
|
|
return 0;
|
|
|
|
n_sources = ntohl(reply.data.n_sources.n_sources);
|
|
|
|
if (verbose) {
|
|
printf(" .- Number of sample points in measurement set.\n");
|
|
printf(" / .- Number of residual runs with same sign.\n");
|
|
printf(" | / .- Length of measurement set (time).\n");
|
|
printf(" | | / .- Est. clock freq error (ppm).\n");
|
|
printf(" | | | / .- Est. error in freq.\n");
|
|
printf(" | | | | / .- Est. offset.\n");
|
|
printf(" | | | | | | On the -.\n");
|
|
printf(" | | | | | | samples. \\\n");
|
|
printf(" | | | | | | |\n");
|
|
}
|
|
|
|
print_header("Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev");
|
|
|
|
/* "NNNNNNNNNNNNNNNNNNNNNNNNN NP NR SSSS FFFFFFFFFF SSSSSSSSSS SSSSSSS SSSSSS" */
|
|
|
|
for (i = 0; i < n_sources; i++) {
|
|
request.command = htons(REQ_SOURCESTATS);
|
|
request.data.source_data.index = htonl(i);
|
|
if (!request_reply(&request, &reply, RPY_SOURCESTATS, 0))
|
|
return 0;
|
|
|
|
UTI_IPNetworkToHost(&reply.data.sourcestats.ip_addr, &ip_addr);
|
|
if (!all && ip_addr.family == IPADDR_ID)
|
|
continue;
|
|
|
|
format_name(name, sizeof (name), 25, ip_addr.family == IPADDR_UNSPEC,
|
|
ntohl(reply.data.sourcestats.ref_id), 1, &ip_addr);
|
|
|
|
print_report("%-25s %3U %3U %I %+P %P %+S %S\n",
|
|
name,
|
|
ntohl(reply.data.sourcestats.n_samples),
|
|
ntohl(reply.data.sourcestats.n_runs),
|
|
ntohl(reply.data.sourcestats.span_seconds),
|
|
UTI_FloatNetworkToHost(reply.data.sourcestats.resid_freq_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.sourcestats.skew_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.sourcestats.est_offset),
|
|
UTI_FloatNetworkToHost(reply.data.sourcestats.sd),
|
|
REPORT_END);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_tracking(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
IPAddr ip_addr;
|
|
uint32_t ref_id;
|
|
char name[256];
|
|
struct timespec ref_time;
|
|
|
|
request.command = htons(REQ_TRACKING);
|
|
if (!request_reply(&request, &reply, RPY_TRACKING, 0))
|
|
return 0;
|
|
|
|
ref_id = ntohl(reply.data.tracking.ref_id);
|
|
|
|
UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
|
|
format_name(name, sizeof (name), sizeof (name),
|
|
ip_addr.family == IPADDR_UNSPEC, ref_id, 1, &ip_addr);
|
|
|
|
UTI_TimespecNetworkToHost(&reply.data.tracking.ref_time, &ref_time);
|
|
|
|
print_report("Reference ID : %R (%s)\n"
|
|
"Stratum : %u\n"
|
|
"Ref time (UTC) : %T\n"
|
|
"System time : %.9O of NTP time\n"
|
|
"Last offset : %+.9f seconds\n"
|
|
"RMS offset : %.9f seconds\n"
|
|
"Frequency : %.3F\n"
|
|
"Residual freq : %+.3f ppm\n"
|
|
"Skew : %.3f ppm\n"
|
|
"Root delay : %.9f seconds\n"
|
|
"Root dispersion : %.9f seconds\n"
|
|
"Update interval : %.1f seconds\n"
|
|
"Leap status : %L\n",
|
|
ref_id, name,
|
|
ntohs(reply.data.tracking.stratum),
|
|
&ref_time,
|
|
UTI_FloatNetworkToHost(reply.data.tracking.current_correction),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.last_offset),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.rms_offset),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.freq_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.resid_freq_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.skew_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.root_delay),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.root_dispersion),
|
|
UTI_FloatNetworkToHost(reply.data.tracking.last_update_interval),
|
|
ntohs(reply.data.tracking.leap_status), REPORT_END);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_authdata(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
IPAddr ip_addr;
|
|
uint32_t i, source_mode, n_sources;
|
|
int all, verbose;
|
|
const char *mode_str;
|
|
char name[256];
|
|
|
|
parse_sources_options(line, &all, &verbose);
|
|
|
|
request.command = htons(REQ_N_SOURCES);
|
|
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
|
|
return 0;
|
|
|
|
n_sources = ntohl(reply.data.n_sources.n_sources);
|
|
|
|
if (verbose) {
|
|
printf( " .- Auth. mechanism (NTS, SK - symmetric key)\n");
|
|
printf( " | Key length -. Cookie length (bytes) -.\n");
|
|
printf( " | (bits) | Num. of cookies --. |\n");
|
|
printf( " | | Key est. attempts | |\n");
|
|
printf( " | | | | |\n");
|
|
}
|
|
|
|
print_header("Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen");
|
|
|
|
/* "NNNNNNNNNNNNNNNNNNNNNNNNNNN MMMM KKKKK AAAA LLLL LLLL AAAA NNNN CCCC LLLL" */
|
|
|
|
for (i = 0; i < n_sources; i++) {
|
|
request.command = htons(REQ_SOURCE_DATA);
|
|
request.data.source_data.index = htonl(i);
|
|
if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
|
|
return 0;
|
|
|
|
source_mode = ntohs(reply.data.source_data.mode);
|
|
if (source_mode != RPY_SD_MD_CLIENT && source_mode != RPY_SD_MD_PEER)
|
|
continue;
|
|
|
|
UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
|
|
if (!all && ip_addr.family == IPADDR_ID)
|
|
continue;
|
|
|
|
request.command = htons(REQ_AUTH_DATA);
|
|
request.data.auth_data.ip_addr = reply.data.source_data.ip_addr;
|
|
if (!request_reply(&request, &reply, RPY_AUTH_DATA, 0))
|
|
return 0;
|
|
|
|
format_name(name, sizeof (name), 25, 0, 0, 1, &ip_addr);
|
|
|
|
switch (ntohs(reply.data.auth_data.mode)) {
|
|
case RPY_AD_MD_NONE:
|
|
mode_str = "-";
|
|
break;
|
|
case RPY_AD_MD_SYMMETRIC:
|
|
mode_str = "SK";
|
|
break;
|
|
case RPY_AD_MD_NTS:
|
|
mode_str = "NTS";
|
|
break;
|
|
default:
|
|
mode_str = "?";
|
|
break;
|
|
}
|
|
|
|
print_report("%-27s %4s %5U %4d %4d %I %4d %4d %4d %4d\n",
|
|
name, mode_str,
|
|
ntohl(reply.data.auth_data.key_id),
|
|
ntohs(reply.data.auth_data.key_type),
|
|
ntohs(reply.data.auth_data.key_length),
|
|
ntohl(reply.data.auth_data.last_ke_ago),
|
|
ntohs(reply.data.auth_data.ke_attempts),
|
|
ntohs(reply.data.auth_data.nak),
|
|
ntohs(reply.data.auth_data.cookies),
|
|
ntohs(reply.data.auth_data.cookie_length),
|
|
REPORT_END);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_ntpdata(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
IPAddr remote_addr, local_addr;
|
|
struct timespec ref_time;
|
|
uint32_t i, n_sources;
|
|
uint16_t mode;
|
|
int specified_addr;
|
|
|
|
if (*line) {
|
|
specified_addr = 1;
|
|
n_sources = 1;
|
|
} else {
|
|
specified_addr = 0;
|
|
request.command = htons(REQ_N_SOURCES);
|
|
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
|
|
return 0;
|
|
n_sources = ntohl(reply.data.n_sources.n_sources);
|
|
}
|
|
|
|
for (i = 0; i < n_sources; i++) {
|
|
if (specified_addr) {
|
|
if (!parse_source_address(line, &remote_addr)) {
|
|
LOG(LOGS_ERR, "Could not get address for hostname");
|
|
return 0;
|
|
}
|
|
} else {
|
|
request.command = htons(REQ_SOURCE_DATA);
|
|
request.data.source_data.index = htonl(i);
|
|
if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
|
|
return 0;
|
|
|
|
mode = ntohs(reply.data.source_data.mode);
|
|
if (mode != RPY_SD_MD_CLIENT && mode != RPY_SD_MD_PEER)
|
|
continue;
|
|
|
|
UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &remote_addr);
|
|
if (!UTI_IsIPReal(&remote_addr))
|
|
continue;
|
|
}
|
|
|
|
request.command = htons(REQ_NTP_DATA);
|
|
UTI_IPHostToNetwork(&remote_addr, &request.data.ntp_data.ip_addr);
|
|
if (!request_reply(&request, &reply, RPY_NTP_DATA, 0))
|
|
return 0;
|
|
|
|
UTI_IPNetworkToHost(&reply.data.ntp_data.remote_addr, &remote_addr);
|
|
UTI_IPNetworkToHost(&reply.data.ntp_data.local_addr, &local_addr);
|
|
UTI_TimespecNetworkToHost(&reply.data.ntp_data.ref_time, &ref_time);
|
|
|
|
if (!specified_addr && !csv_mode)
|
|
printf("\n");
|
|
|
|
print_report("Remote address : %s (%R)\n"
|
|
"Remote port : %u\n"
|
|
"Local address : %s (%R)\n"
|
|
"Leap status : %L\n"
|
|
"Version : %u\n"
|
|
"Mode : %M\n"
|
|
"Stratum : %u\n"
|
|
"Poll interval : %d (%.0f seconds)\n"
|
|
"Precision : %d (%.9f seconds)\n"
|
|
"Root delay : %.6f seconds\n"
|
|
"Root dispersion : %.6f seconds\n"
|
|
"Reference ID : %R (%s)\n"
|
|
"Reference time : %T\n"
|
|
"Offset : %+.9f seconds\n"
|
|
"Peer delay : %.9f seconds\n"
|
|
"Peer dispersion : %.9f seconds\n"
|
|
"Response time : %.9f seconds\n"
|
|
"Jitter asymmetry: %+.2f\n"
|
|
"NTP tests : %.3b %.3b %.4b\n"
|
|
"Interleaved : %B\n"
|
|
"Authenticated : %B\n"
|
|
"TX timestamping : %N\n"
|
|
"RX timestamping : %N\n"
|
|
"Total TX : %U\n"
|
|
"Total RX : %U\n"
|
|
"Total valid RX : %U\n"
|
|
"Total good RX : %U\n",
|
|
UTI_IPToString(&remote_addr), UTI_IPToRefid(&remote_addr),
|
|
ntohs(reply.data.ntp_data.remote_port),
|
|
UTI_IPToString(&local_addr), UTI_IPToRefid(&local_addr),
|
|
reply.data.ntp_data.leap, reply.data.ntp_data.version,
|
|
reply.data.ntp_data.mode, reply.data.ntp_data.stratum,
|
|
reply.data.ntp_data.poll, UTI_Log2ToDouble(reply.data.ntp_data.poll),
|
|
reply.data.ntp_data.precision, UTI_Log2ToDouble(reply.data.ntp_data.precision),
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.root_delay),
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.root_dispersion),
|
|
ntohl(reply.data.ntp_data.ref_id), reply.data.ntp_data.stratum <= 1 ?
|
|
UTI_RefidToString(ntohl(reply.data.ntp_data.ref_id)) : "",
|
|
&ref_time,
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.offset),
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.peer_delay),
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.peer_dispersion),
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.response_time),
|
|
UTI_FloatNetworkToHost(reply.data.ntp_data.jitter_asymmetry),
|
|
ntohs(reply.data.ntp_data.flags) >> 7,
|
|
ntohs(reply.data.ntp_data.flags) >> 4,
|
|
ntohs(reply.data.ntp_data.flags),
|
|
ntohs(reply.data.ntp_data.flags) & RPY_NTP_FLAG_INTERLEAVED,
|
|
ntohs(reply.data.ntp_data.flags) & RPY_NTP_FLAG_AUTHENTICATED,
|
|
reply.data.ntp_data.tx_tss_char, reply.data.ntp_data.rx_tss_char,
|
|
ntohl(reply.data.ntp_data.total_tx_count),
|
|
ntohl(reply.data.ntp_data.total_rx_count),
|
|
ntohl(reply.data.ntp_data.total_valid_count),
|
|
ntohl(reply.data.ntp_data.total_good_count),
|
|
REPORT_END);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_selectdata(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
uint32_t i, n_sources;
|
|
int all, verbose, conf_options, eff_options;
|
|
char name[256];
|
|
IPAddr ip_addr;
|
|
|
|
parse_sources_options(line, &all, &verbose);
|
|
|
|
request.command = htons(REQ_N_SOURCES);
|
|
if (!request_reply(&request, &reply, RPY_N_SOURCES, 0))
|
|
return 0;
|
|
|
|
n_sources = ntohl(reply.data.n_sources.n_sources);
|
|
|
|
if (verbose) {
|
|
printf( " . State: N - noselect, s - unsynchronised, M - missing samples,\n");
|
|
printf( " / d/D - large distance, ~ - jittery, w/W - waits for others,\n");
|
|
printf( "| S - stale, O - orphan, T - not trusted, P - not preferred,\n");
|
|
printf( "| U - waits for update,, x - falseticker, + - combined, * - best.\n");
|
|
printf( "| Effective options ---------. (N - noselect, P - prefer\n");
|
|
printf( "| Configured options ----. \\ T - trust, R - require)\n");
|
|
printf( "| Auth. enabled (Y/N) -. \\ \\ Offset interval --.\n");
|
|
printf( "| | | | |\n");
|
|
}
|
|
|
|
print_header("S Name/IP Address Auth COpts EOpts Last Score Interval Leap");
|
|
|
|
/* "S NNNNNNNNNNNNNNNNNNNNNNNNN A OOOO- OOOO- LLLL SSSSS IIIIIII IIIIIII L" */
|
|
|
|
for (i = 0; i < n_sources; i++) {
|
|
request.command = htons(REQ_SELECT_DATA);
|
|
request.data.source_data.index = htonl(i);
|
|
if (!request_reply(&request, &reply, RPY_SELECT_DATA, 0))
|
|
return 0;
|
|
|
|
UTI_IPNetworkToHost(&reply.data.select_data.ip_addr, &ip_addr);
|
|
if (!all && ip_addr.family == IPADDR_ID)
|
|
continue;
|
|
|
|
format_name(name, sizeof (name), 25, ip_addr.family == IPADDR_UNSPEC,
|
|
ntohl(reply.data.select_data.ref_id), 1, &ip_addr);
|
|
|
|
conf_options = ntohs(reply.data.select_data.conf_options);
|
|
eff_options = ntohs(reply.data.select_data.eff_options);
|
|
|
|
print_report("%c %-25s %c %c%c%c%c%c %c%c%c%c%c %I %5.1f %+S %+S %1L\n",
|
|
reply.data.select_data.state_char,
|
|
name,
|
|
reply.data.select_data.authentication ? 'Y' : 'N',
|
|
conf_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
|
|
conf_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
|
|
conf_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
|
|
conf_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
|
|
'-',
|
|
eff_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
|
|
eff_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
|
|
eff_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
|
|
eff_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
|
|
'-',
|
|
ntohl(reply.data.select_data.last_sample_ago),
|
|
UTI_FloatNetworkToHost(reply.data.select_data.score),
|
|
UTI_FloatNetworkToHost(reply.data.select_data.lo_limit),
|
|
UTI_FloatNetworkToHost(reply.data.select_data.hi_limit),
|
|
reply.data.select_data.leap,
|
|
REPORT_END);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_serverstats(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
|
|
request.command = htons(REQ_SERVER_STATS);
|
|
if (!request_reply(&request, &reply, RPY_SERVER_STATS4, 0))
|
|
return 0;
|
|
|
|
print_report("NTP packets received : %Q\n"
|
|
"NTP packets dropped : %Q\n"
|
|
"Command packets received : %Q\n"
|
|
"Command packets dropped : %Q\n"
|
|
"Client log records dropped : %Q\n"
|
|
"NTS-KE connections accepted: %Q\n"
|
|
"NTS-KE connections dropped : %Q\n"
|
|
"Authenticated NTP packets : %Q\n"
|
|
"Interleaved NTP packets : %Q\n"
|
|
"NTP timestamps held : %Q\n"
|
|
"NTP timestamp span : %Q\n"
|
|
"NTP daemon RX timestamps : %Q\n"
|
|
"NTP daemon TX timestamps : %Q\n"
|
|
"NTP kernel RX timestamps : %Q\n"
|
|
"NTP kernel TX timestamps : %Q\n"
|
|
"NTP hardware RX timestamps : %Q\n"
|
|
"NTP hardware TX timestamps : %Q\n",
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hits),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_drops),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.cmd_hits),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.cmd_drops),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.log_drops),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.nke_hits),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.nke_drops),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_auth_hits),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_interleaved_hits),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_timestamps),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_span_seconds),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_daemon_rx_timestamps),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_daemon_tx_timestamps),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_kernel_rx_timestamps),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_kernel_tx_timestamps),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hw_rx_timestamps),
|
|
UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hw_tx_timestamps),
|
|
REPORT_END);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_smoothing(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
uint32_t flags;
|
|
|
|
request.command = htons(REQ_SMOOTHING);
|
|
if (!request_reply(&request, &reply, RPY_SMOOTHING, 0))
|
|
return 0;
|
|
|
|
flags = ntohl(reply.data.smoothing.flags);
|
|
|
|
print_report("Active : %B %s\n"
|
|
"Offset : %+.9f seconds\n"
|
|
"Frequency : %+.6f ppm\n"
|
|
"Wander : %+.6f ppm per second\n"
|
|
"Last update : %.1f seconds ago\n"
|
|
"Remaining time : %.1f seconds\n",
|
|
!!(flags & RPY_SMT_FLAG_ACTIVE),
|
|
flags & RPY_SMT_FLAG_LEAPONLY ? "(leap second only)" : "",
|
|
UTI_FloatNetworkToHost(reply.data.smoothing.offset),
|
|
UTI_FloatNetworkToHost(reply.data.smoothing.freq_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.smoothing.wander_ppm),
|
|
UTI_FloatNetworkToHost(reply.data.smoothing.last_update_ago),
|
|
UTI_FloatNetworkToHost(reply.data.smoothing.remaining_time),
|
|
REPORT_END);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_smoothtime(CMD_Request *msg, const char *line)
|
|
{
|
|
if (!strcmp(line, "reset")) {
|
|
msg->data.smoothtime.option = htonl(REQ_SMOOTHTIME_RESET);
|
|
} else if (!strcmp(line, "activate")) {
|
|
msg->data.smoothtime.option = htonl(REQ_SMOOTHTIME_ACTIVATE);
|
|
} else {
|
|
LOG(LOGS_ERR, "Invalid syntax for smoothtime command");
|
|
return 0;
|
|
}
|
|
|
|
msg->command = htons(REQ_SMOOTHTIME);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_rtcreport(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
struct timespec ref_time;
|
|
|
|
request.command = htons(REQ_RTCREPORT);
|
|
if (!request_reply(&request, &reply, RPY_RTC, 0))
|
|
return 0;
|
|
|
|
UTI_TimespecNetworkToHost(&reply.data.rtc.ref_time, &ref_time);
|
|
|
|
print_report("RTC ref time (UTC) : %T\n"
|
|
"Number of samples : %u\n"
|
|
"Number of runs : %u\n"
|
|
"Sample span period : %I\n"
|
|
"RTC is fast by : %12.6f seconds\n"
|
|
"RTC gains time at : %9.3f ppm\n",
|
|
&ref_time,
|
|
ntohs(reply.data.rtc.n_samples),
|
|
ntohs(reply.data.rtc.n_runs),
|
|
ntohl(reply.data.rtc.span_seconds),
|
|
UTI_FloatNetworkToHost(reply.data.rtc.rtc_seconds_fast),
|
|
UTI_FloatNetworkToHost(reply.data.rtc.rtc_gain_rate_ppm),
|
|
REPORT_END);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_clients(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
IPAddr ip;
|
|
uint32_t i, n_clients, next_index, n_indices, min_hits, reset;
|
|
RPY_ClientAccesses_Client *client;
|
|
char header[80], name[50], *opt, *arg;
|
|
int nke;
|
|
|
|
next_index = 0;
|
|
min_hits = 0;
|
|
reset = 0;
|
|
nke = 0;
|
|
|
|
while (*line) {
|
|
opt = line;
|
|
line = CPS_SplitWord(line);
|
|
if (strcmp(opt, "-k") == 0) {
|
|
nke = 1;
|
|
} else if (strcmp(opt, "-p") == 0) {
|
|
arg = line;
|
|
line = CPS_SplitWord(line);
|
|
if (sscanf(arg, "%"SCNu32, &min_hits) != 1) {
|
|
LOG(LOGS_ERR, "Invalid syntax for clients command");
|
|
return 0;
|
|
}
|
|
} else if (strcmp(opt, "-r") == 0) {
|
|
reset = 1;
|
|
}
|
|
}
|
|
|
|
snprintf(header, sizeof (header),
|
|
"Hostname NTP Drop Int IntL Last %6s Drop Int Last",
|
|
nke ? "NTS-KE" : "Cmd");
|
|
print_header(header);
|
|
|
|
while (1) {
|
|
request.command = htons(REQ_CLIENT_ACCESSES_BY_INDEX3);
|
|
request.data.client_accesses_by_index.first_index = htonl(next_index);
|
|
request.data.client_accesses_by_index.n_clients = htonl(MAX_CLIENT_ACCESSES);
|
|
request.data.client_accesses_by_index.min_hits = htonl(min_hits);
|
|
request.data.client_accesses_by_index.reset = htonl(reset);
|
|
|
|
if (!request_reply(&request, &reply, RPY_CLIENT_ACCESSES_BY_INDEX3, 0))
|
|
return 0;
|
|
|
|
n_clients = ntohl(reply.data.client_accesses_by_index.n_clients);
|
|
n_indices = ntohl(reply.data.client_accesses_by_index.n_indices);
|
|
|
|
for (i = 0; i < n_clients && i < MAX_CLIENT_ACCESSES; i++) {
|
|
client = &reply.data.client_accesses_by_index.clients[i];
|
|
|
|
UTI_IPNetworkToHost(&client->ip, &ip);
|
|
|
|
/* UNSPEC means the record could not be found in the daemon's tables.
|
|
We shouldn't ever generate this case, but ignore it if we do. */
|
|
if (ip.family == IPADDR_UNSPEC)
|
|
continue;
|
|
|
|
format_name(name, sizeof (name), 25, 0, 0, 0, &ip);
|
|
|
|
print_report("%-25s %6U %5U %C %C %I %6U %5U %C %I\n",
|
|
name,
|
|
ntohl(client->ntp_hits),
|
|
ntohl(client->ntp_drops),
|
|
client->ntp_interval,
|
|
client->ntp_timeout_interval,
|
|
ntohl(client->last_ntp_hit_ago),
|
|
ntohl(nke ? client->nke_hits : client->cmd_hits),
|
|
ntohl(nke ? client->nke_drops : client->cmd_drops),
|
|
nke ? client->nke_interval : client->cmd_interval,
|
|
ntohl(nke ? client->last_nke_hit_ago : client->last_cmd_hit_ago),
|
|
REPORT_END);
|
|
}
|
|
|
|
/* Set the next index to probe based on what the server tells us */
|
|
next_index = ntohl(reply.data.client_accesses_by_index.next_index);
|
|
|
|
if (next_index >= n_indices || n_clients < MAX_CLIENT_ACCESSES)
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* ================================================== */
|
|
/* Process the manual list command */
|
|
static int
|
|
process_cmd_manual_list(const char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
uint32_t i, n_samples;
|
|
RPY_ManualListSample *sample;
|
|
struct timespec when;
|
|
|
|
request.command = htons(REQ_MANUAL_LIST);
|
|
if (!request_reply(&request, &reply, RPY_MANUAL_LIST2, 0))
|
|
return 0;
|
|
|
|
n_samples = ntohl(reply.data.manual_list.n_samples);
|
|
print_info_field("210 n_samples = %"PRIu32"\n", n_samples);
|
|
|
|
print_header("# Date Time(UTC) Slewed Original Residual");
|
|
|
|
for (i = 0; i < n_samples && i < MAX_MANUAL_LIST_SAMPLES; i++) {
|
|
sample = &reply.data.manual_list.samples[i];
|
|
UTI_TimespecNetworkToHost(&sample->when, &when);
|
|
|
|
print_report("%2d %s %10.2f %10.2f %10.2f\n",
|
|
i, UTI_TimeToLogForm(when.tv_sec),
|
|
UTI_FloatNetworkToHost(sample->slewed_offset),
|
|
UTI_FloatNetworkToHost(sample->orig_offset),
|
|
UTI_FloatNetworkToHost(sample->residual),
|
|
REPORT_END);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_manual_delete(CMD_Request *msg, const char *line)
|
|
{
|
|
int index;
|
|
|
|
if (sscanf(line, "%d", &index) != 1) {
|
|
LOG(LOGS_ERR, "Bad syntax for manual delete command");
|
|
return 0;
|
|
}
|
|
|
|
msg->command = htons(REQ_MANUAL_DELETE);
|
|
msg->data.manual_delete.index = htonl(index);
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_settime(char *line)
|
|
{
|
|
struct timespec ts;
|
|
time_t now, new_time;
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
double dfreq_ppm, new_afreq_ppm;
|
|
double offset;
|
|
|
|
now = time(NULL);
|
|
new_time = get_date(line, &now);
|
|
|
|
if (new_time == -1) {
|
|
printf("510 - Could not parse date string\n");
|
|
} else {
|
|
ts.tv_sec = new_time;
|
|
ts.tv_nsec = 0;
|
|
UTI_TimespecHostToNetwork(&ts, &request.data.settime.ts);
|
|
request.command = htons(REQ_SETTIME);
|
|
if (request_reply(&request, &reply, RPY_MANUAL_TIMESTAMP2, 1)) {
|
|
offset = UTI_FloatNetworkToHost(reply.data.manual_timestamp.offset);
|
|
dfreq_ppm = UTI_FloatNetworkToHost(reply.data.manual_timestamp.dfreq_ppm);
|
|
new_afreq_ppm = UTI_FloatNetworkToHost(reply.data.manual_timestamp.new_afreq_ppm);
|
|
printf("Clock was %.2f seconds fast. Frequency change = %.2fppm, new frequency = %.2fppm\n",
|
|
offset, dfreq_ppm, new_afreq_ppm);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_rekey(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_REKEY);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_makestep(CMD_Request *msg, char *line)
|
|
{
|
|
int limit;
|
|
double threshold;
|
|
|
|
if (*line) {
|
|
if (sscanf(line, "%lf %d", &threshold, &limit) != 2) {
|
|
LOG(LOGS_ERR, "Bad syntax for makestep command");
|
|
return 0;
|
|
}
|
|
msg->command = htons(REQ_MODIFY_MAKESTEP);
|
|
msg->data.modify_makestep.limit = htonl(limit);
|
|
msg->data.modify_makestep.threshold = UTI_FloatHostToNetwork(threshold);
|
|
} else {
|
|
msg->command = htons(REQ_MAKESTEP);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_activity(const char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
|
|
request.command = htons(REQ_ACTIVITY);
|
|
if (!request_reply(&request, &reply, RPY_ACTIVITY, 0))
|
|
return 0;
|
|
|
|
print_info_field("200 OK\n");
|
|
|
|
print_report("%U sources online\n"
|
|
"%U sources offline\n"
|
|
"%U sources doing burst (return to online)\n"
|
|
"%U sources doing burst (return to offline)\n"
|
|
"%U sources with unknown address\n",
|
|
ntohl(reply.data.activity.online),
|
|
ntohl(reply.data.activity.offline),
|
|
ntohl(reply.data.activity.burst_online),
|
|
ntohl(reply.data.activity.burst_offline),
|
|
ntohl(reply.data.activity.unresolved),
|
|
REPORT_END);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_reselectdist(CMD_Request *msg, char *line)
|
|
{
|
|
double dist;
|
|
int ok;
|
|
msg->command = htons(REQ_RESELECTDISTANCE);
|
|
if (sscanf(line, "%lf", &dist) == 1) {
|
|
msg->data.reselect_distance.distance = UTI_FloatHostToNetwork(dist);
|
|
ok = 1;
|
|
} else {
|
|
ok = 0;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_reselect(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_RESELECT);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_refresh(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_REFRESH);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_cmd_shutdown(CMD_Request *msg, char *line)
|
|
{
|
|
msg->command = htons(REQ_SHUTDOWN);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_reload(CMD_Request *msg, char *line)
|
|
{
|
|
if (!strcmp(line, "sources")) {
|
|
msg->command = htons(REQ_RELOAD_SOURCES);
|
|
} else {
|
|
LOG(LOGS_ERR, "Invalid syntax for reload command");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_reset(CMD_Request *msg, char *line)
|
|
{
|
|
if (!strcmp(line, "sources")) {
|
|
msg->command = htons(REQ_RESET_SOURCES);
|
|
} else {
|
|
LOG(LOGS_ERR, "Invalid syntax for reset command");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_selectopts(CMD_Request *msg, char *line)
|
|
{
|
|
int mask, options, option;
|
|
uint32_t ref_id;
|
|
IPAddr ip_addr;
|
|
char *src, *opt;
|
|
|
|
src = line;
|
|
line = CPS_SplitWord(line);
|
|
ref_id = 0;
|
|
|
|
/* Don't allow hostnames to avoid conflicts with reference IDs */
|
|
if (!UTI_StringToIdIP(src, &ip_addr) && !UTI_StringToIP(src, &ip_addr)) {
|
|
ip_addr.family = IPADDR_UNSPEC;
|
|
if (CPS_ParseRefid(src, &ref_id) == 0) {
|
|
LOG(LOGS_ERR, "Invalid syntax for selectopts command");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
mask = options = 0;
|
|
|
|
while (*line != '\0') {
|
|
opt = line;
|
|
line = CPS_SplitWord(line);
|
|
|
|
if ((opt[0] != '+' && opt[0] != '-') || (option = CPS_GetSelectOption(opt + 1)) == 0) {
|
|
LOG(LOGS_ERR, "Invalid syntax for selectopts command");
|
|
return 0;
|
|
}
|
|
|
|
mask |= option;
|
|
if (opt[0] == '+')
|
|
options |= option;
|
|
}
|
|
|
|
UTI_IPHostToNetwork(&ip_addr, &msg->data.modify_select_opts.address);
|
|
msg->data.modify_select_opts.ref_id = htonl(ref_id);
|
|
msg->data.modify_select_opts.mask = htonl(mask);
|
|
msg->data.modify_select_opts.options = htonl(convert_addsrc_sel_options(options));
|
|
|
|
msg->command = htons(REQ_MODIFY_SELECTOPTS);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_waitsync(char *line)
|
|
{
|
|
CMD_Request request;
|
|
CMD_Reply reply;
|
|
IPAddr ip_addr;
|
|
uint32_t ref_id;
|
|
double correction, skew_ppm, max_correction, max_skew_ppm, interval;
|
|
int ret = 0, max_tries, i;
|
|
struct timeval timeout;
|
|
|
|
max_tries = 0;
|
|
max_correction = 0.0;
|
|
max_skew_ppm = 0.0;
|
|
interval = 10.0;
|
|
|
|
if (sscanf(line, "%d %lf %lf %lf", &max_tries, &max_correction, &max_skew_ppm, &interval))
|
|
;
|
|
|
|
/* Don't allow shorter interval than 0.1 seconds */
|
|
if (interval < 0.1)
|
|
interval = 0.1;
|
|
|
|
request.command = htons(REQ_TRACKING);
|
|
|
|
for (i = 1; ; i++) {
|
|
if (request_reply(&request, &reply, RPY_TRACKING, 0)) {
|
|
ref_id = ntohl(reply.data.tracking.ref_id);
|
|
UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
|
|
|
|
correction = UTI_FloatNetworkToHost(reply.data.tracking.current_correction);
|
|
correction = fabs(correction);
|
|
skew_ppm = UTI_FloatNetworkToHost(reply.data.tracking.skew_ppm);
|
|
|
|
print_report("try: %d, refid: %R, correction: %.9f, skew: %.3f\n",
|
|
i, ref_id, correction, skew_ppm, REPORT_END);
|
|
|
|
if ((ip_addr.family != IPADDR_UNSPEC ||
|
|
(ref_id != 0 && ref_id != 0x7f7f0101L /* LOCAL refid */)) &&
|
|
(max_correction == 0.0 || correction <= max_correction) &&
|
|
(max_skew_ppm == 0.0 || skew_ppm <= max_skew_ppm)) {
|
|
ret = 1;
|
|
}
|
|
}
|
|
|
|
if (!ret && (!max_tries || i < max_tries) && !quit) {
|
|
UTI_DoubleToTimeval(interval, &timeout);
|
|
if (select(0, NULL, NULL, NULL, &timeout))
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_dns(const char *line)
|
|
{
|
|
if (!strcmp(line, "-46")) {
|
|
DNS_SetAddressFamily(IPADDR_UNSPEC);
|
|
} else if (!strcmp(line, "-4")) {
|
|
DNS_SetAddressFamily(IPADDR_INET4);
|
|
} else if (!strcmp(line, "-6")) {
|
|
DNS_SetAddressFamily(IPADDR_INET6);
|
|
} else if (!strcmp(line, "-n")) {
|
|
no_dns = 1;
|
|
} else if (!strcmp(line, "+n")) {
|
|
no_dns = 0;
|
|
} else {
|
|
LOG(LOGS_ERR, "Unrecognized dns command");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_timeout(const char *line)
|
|
{
|
|
int timeout;
|
|
|
|
timeout = atoi(line);
|
|
if (timeout < 100) {
|
|
LOG(LOGS_ERR, "Timeout %d is too short", timeout);
|
|
return 0;
|
|
}
|
|
initial_timeout = timeout;
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_retries(const char *line)
|
|
{
|
|
int retries;
|
|
|
|
retries = atoi(line);
|
|
if (retries < 0 || retries > 30) {
|
|
LOG(LOGS_ERR, "Invalid maximum number of retries");
|
|
return 0;
|
|
}
|
|
max_retries = retries;
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_cmd_keygen(char *line)
|
|
{
|
|
unsigned int i, args, cmac_length, length, id = 1, bits = 160;
|
|
unsigned char key[512];
|
|
const char *type;
|
|
char *words[3];
|
|
|
|
#ifdef FEAT_SECHASH
|
|
type = "SHA1";
|
|
#else
|
|
type = "MD5";
|
|
#endif
|
|
|
|
args = UTI_SplitString(line, words, 3);
|
|
if (args >= 2)
|
|
type = words[1];
|
|
|
|
if (args > 3 ||
|
|
(args >= 1 && sscanf(words[0], "%u", &id) != 1) ||
|
|
(args >= 3 && sscanf(words[2], "%u", &bits) != 1)) {
|
|
LOG(LOGS_ERR, "Invalid syntax for keygen command");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_CMAC
|
|
cmac_length = CMC_GetKeyLength(UTI_CmacNameToAlgorithm(type));
|
|
#else
|
|
cmac_length = 0;
|
|
#endif
|
|
|
|
if (HSH_GetHashId(UTI_HashNameToAlgorithm(type)) >= 0) {
|
|
length = (bits + 7) / 8;
|
|
} else if (cmac_length > 0) {
|
|
length = cmac_length;
|
|
} else {
|
|
LOG(LOGS_ERR, "Unknown hash function or cipher %s", type);
|
|
return 0;
|
|
}
|
|
|
|
length = CLAMP(10, length, sizeof (key));
|
|
|
|
UTI_GetRandomBytesUrandom(key, length);
|
|
|
|
printf("%u %s HEX:", id, type);
|
|
for (i = 0; i < length; i++)
|
|
printf("%02hhX", key[i]);
|
|
printf("\n");
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
process_line(char *line)
|
|
{
|
|
char *command;
|
|
int do_normal_submit;
|
|
int ret;
|
|
CMD_Request tx_message;
|
|
CMD_Reply rx_message;
|
|
|
|
ret = 0;
|
|
|
|
do_normal_submit = 1;
|
|
|
|
CPS_NormalizeLine(line);
|
|
|
|
if (!*line) {
|
|
fflush(stderr);
|
|
fflush(stdout);
|
|
return 1;
|
|
};
|
|
|
|
command = line;
|
|
line = CPS_SplitWord(line);
|
|
|
|
if (!strcmp(command, "accheck")) {
|
|
do_normal_submit = process_cmd_accheck(&tx_message, line);
|
|
} else if (!strcmp(command, "activity")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_activity(line);
|
|
} else if (!strcmp(command, "add")) {
|
|
do_normal_submit = process_cmd_add_source(&tx_message, line);
|
|
} else if (!strcmp(command, "allow")) {
|
|
do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_ALLOW, REQ_ALLOWALL);
|
|
} else if (!strcmp(command, "authdata")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_authdata(line);
|
|
} else if (!strcmp(command, "burst")) {
|
|
do_normal_submit = process_cmd_burst(&tx_message, line);
|
|
} else if (!strcmp(command, "clients")) {
|
|
ret = process_cmd_clients(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "cmdaccheck")) {
|
|
do_normal_submit = process_cmd_cmdaccheck(&tx_message, line);
|
|
} else if (!strcmp(command, "cmdallow")) {
|
|
do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_CMDALLOW, REQ_CMDALLOWALL);
|
|
} else if (!strcmp(command, "cmddeny")) {
|
|
do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_CMDDENY, REQ_CMDDENYALL);
|
|
} else if (!strcmp(command, "cyclelogs")) {
|
|
process_cmd_cyclelogs(&tx_message, line);
|
|
} else if (!strcmp(command, "delete")) {
|
|
do_normal_submit = process_cmd_delete(&tx_message, line);
|
|
} else if (!strcmp(command, "deny")) {
|
|
do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_DENY, REQ_DENYALL);
|
|
} else if (!strcmp(command, "dfreq")) {
|
|
do_normal_submit = process_cmd_dfreq(&tx_message, line);
|
|
} else if (!strcmp(command, "dns")) {
|
|
ret = process_cmd_dns(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "doffset")) {
|
|
do_normal_submit = process_cmd_doffset(&tx_message, line);
|
|
} else if (!strcmp(command, "dump")) {
|
|
process_cmd_dump(&tx_message, line);
|
|
} else if (!strcmp(command, "exit")) {
|
|
do_normal_submit = 0;
|
|
quit = 1;
|
|
ret = 1;
|
|
} else if (!strcmp(command, "help")) {
|
|
do_normal_submit = 0;
|
|
give_help();
|
|
ret = 1;
|
|
} else if (!strcmp(command, "keygen")) {
|
|
ret = process_cmd_keygen(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "local")) {
|
|
do_normal_submit = process_cmd_local(&tx_message, line);
|
|
} else if (!strcmp(command, "makestep")) {
|
|
do_normal_submit = process_cmd_makestep(&tx_message, line);
|
|
} else if (!strcmp(command, "manual")) {
|
|
if (!strncmp(line, "list", 4)) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_manual_list(CPS_SplitWord(line));
|
|
} else if (!strncmp(line, "delete", 6)) {
|
|
do_normal_submit = process_cmd_manual_delete(&tx_message, CPS_SplitWord(line));
|
|
} else {
|
|
do_normal_submit = process_cmd_manual(&tx_message, line);
|
|
}
|
|
} else if (!strcmp(command, "maxdelay")) {
|
|
do_normal_submit = process_cmd_maxdelay(&tx_message, line);
|
|
} else if (!strcmp(command, "maxdelaydevratio")) {
|
|
do_normal_submit = process_cmd_maxdelaydevratio(&tx_message, line);
|
|
} else if (!strcmp(command, "maxdelayratio")) {
|
|
do_normal_submit = process_cmd_maxdelayratio(&tx_message, line);
|
|
} else if (!strcmp(command, "maxpoll")) {
|
|
do_normal_submit = process_cmd_maxpoll(&tx_message, line);
|
|
} else if (!strcmp(command, "maxupdateskew")) {
|
|
do_normal_submit = process_cmd_maxupdateskew(&tx_message, line);
|
|
} else if (!strcmp(command, "minpoll")) {
|
|
do_normal_submit = process_cmd_minpoll(&tx_message, line);
|
|
} else if (!strcmp(command, "minstratum")) {
|
|
do_normal_submit = process_cmd_minstratum(&tx_message, line);
|
|
} else if (!strcmp(command, "ntpdata")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_ntpdata(line);
|
|
} else if (!strcmp(command, "offline")) {
|
|
do_normal_submit = process_cmd_offline(&tx_message, line);
|
|
} else if (!strcmp(command, "online")) {
|
|
do_normal_submit = process_cmd_online(&tx_message, line);
|
|
} else if (!strcmp(command, "onoffline")) {
|
|
process_cmd_onoffline(&tx_message, line);
|
|
} else if (!strcmp(command, "polltarget")) {
|
|
do_normal_submit = process_cmd_polltarget(&tx_message, line);
|
|
} else if (!strcmp(command, "quit")) {
|
|
do_normal_submit = 0;
|
|
quit = 1;
|
|
ret = 1;
|
|
} else if (!strcmp(command, "refresh")) {
|
|
process_cmd_refresh(&tx_message, line);
|
|
} else if (!strcmp(command, "rekey")) {
|
|
process_cmd_rekey(&tx_message, line);
|
|
} else if (!strcmp(command, "reload")) {
|
|
do_normal_submit = process_cmd_reload(&tx_message, line);
|
|
} else if (!strcmp(command, "reselect")) {
|
|
process_cmd_reselect(&tx_message, line);
|
|
} else if (!strcmp(command, "reselectdist")) {
|
|
do_normal_submit = process_cmd_reselectdist(&tx_message, line);
|
|
} else if (!strcmp(command, "reset")) {
|
|
do_normal_submit = process_cmd_reset(&tx_message, line);
|
|
} else if (!strcmp(command, "retries")) {
|
|
ret = process_cmd_retries(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "rtcdata")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_rtcreport(line);
|
|
} else if (!strcmp(command, "selectdata")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_selectdata(line);
|
|
} else if (!strcmp(command, "selectopts")) {
|
|
do_normal_submit = process_cmd_selectopts(&tx_message, line);
|
|
} else if (!strcmp(command, "serverstats")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_serverstats(line);
|
|
} else if (!strcmp(command, "settime")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_settime(line);
|
|
} else if (!strcmp(command, "shutdown")) {
|
|
process_cmd_shutdown(&tx_message, line);
|
|
} else if (!strcmp(command, "smoothing")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_smoothing(line);
|
|
} else if (!strcmp(command, "smoothtime")) {
|
|
do_normal_submit = process_cmd_smoothtime(&tx_message, line);
|
|
} else if (!strcmp(command, "sourcename")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_sourcename(line);
|
|
} else if (!strcmp(command, "sources")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_sources(line);
|
|
} else if (!strcmp(command, "sourcestats")) {
|
|
do_normal_submit = 0;
|
|
ret = process_cmd_sourcestats(line);
|
|
} else if (!strcmp(command, "timeout")) {
|
|
ret = process_cmd_timeout(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "tracking")) {
|
|
ret = process_cmd_tracking(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "trimrtc")) {
|
|
process_cmd_trimrtc(&tx_message, line);
|
|
} else if (!strcmp(command, "waitsync")) {
|
|
ret = process_cmd_waitsync(line);
|
|
do_normal_submit = 0;
|
|
} else if (!strcmp(command, "writertc")) {
|
|
process_cmd_writertc(&tx_message, line);
|
|
} else if (!strcmp(command, "authhash") ||
|
|
!strcmp(command, "password")) {
|
|
/* Warn, but don't return error to not break scripts */
|
|
LOG(LOGS_WARN, "Authentication is no longer supported");
|
|
do_normal_submit = 0;
|
|
ret = 1;
|
|
} else {
|
|
LOG(LOGS_ERR, "Unrecognized command");
|
|
do_normal_submit = 0;
|
|
}
|
|
|
|
if (do_normal_submit) {
|
|
ret = request_reply(&tx_message, &rx_message, RPY_NULL, 1);
|
|
}
|
|
|
|
if (end_dot) {
|
|
printf(".\n");
|
|
}
|
|
|
|
fflush(stderr);
|
|
|
|
if (fflush(stdout) != 0 || ferror(stdout) != 0) {
|
|
LOG(LOGS_ERR, "Could not write to stdout");
|
|
|
|
/* Return error for commands that print data */
|
|
if (!do_normal_submit)
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
#define MAX_LINE_LENGTH 2048
|
|
|
|
static int
|
|
process_args(int argc, char **argv, int multi)
|
|
{
|
|
char line[MAX_LINE_LENGTH];
|
|
int i, l, ret = 0;
|
|
|
|
for (i = l = 0; i < argc; i++) {
|
|
l += snprintf(line + l, sizeof (line) - l, "%s ", argv[i]);
|
|
if (l >= sizeof (line)) {
|
|
LOG(LOGS_ERR, "Command too long");
|
|
return 0;
|
|
}
|
|
|
|
if (!multi && i + 1 < argc)
|
|
continue;
|
|
|
|
ret = process_line(line);
|
|
if (!ret || quit)
|
|
break;
|
|
|
|
l = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
signal_handler(int signum)
|
|
{
|
|
quit = 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
display_gpl(void)
|
|
{
|
|
printf("chrony version %s\n"
|
|
"Copyright (C) 1997-2003, 2007, 2009-2023 Richard P. Curnow and others\n"
|
|
"chrony comes with ABSOLUTELY NO WARRANTY. This is free software, and\n"
|
|
"you are welcome to redistribute it under certain conditions. See the\n"
|
|
"GNU General Public License version 2 for details.\n\n",
|
|
CHRONY_VERSION);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_help(const char *progname)
|
|
{
|
|
printf("Usage: %s [OPTION]... [COMMAND]...\n\n"
|
|
"Options:\n"
|
|
" -4\t\tUse IPv4 addresses only\n"
|
|
" -6\t\tUse IPv6 addresses only\n"
|
|
" -n\t\tDon't resolve hostnames\n"
|
|
" -N\t\tPrint original source names\n"
|
|
" -c\t\tEnable CSV format\n"
|
|
" -e\t\tEnd responses with dot\n"
|
|
#if DEBUG > 0
|
|
" -d\t\tEnable debug messages\n"
|
|
#endif
|
|
" -m\t\tAccept multiple commands\n"
|
|
" -h HOST\tSpecify server (%s)\n"
|
|
" -p PORT\tSpecify UDP port (%d)\n"
|
|
" -v, --version\tPrint version and exit\n"
|
|
" --help\tPrint usage and exit\n",
|
|
progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1", DEFAULT_CANDM_PORT);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
print_version(void)
|
|
{
|
|
printf("chronyc (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYC_FEATURES);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char *line;
|
|
const char *progname = argv[0];
|
|
const char *hostnames = NULL;
|
|
int opt, ret = 1, multi = 0, family = IPADDR_UNSPEC;
|
|
int port = DEFAULT_CANDM_PORT;
|
|
|
|
/* Parse long command-line options */
|
|
for (optind = 1; optind < argc; optind++) {
|
|
if (!strcmp("--help", argv[optind])) {
|
|
print_help(progname);
|
|
return 0;
|
|
} else if (!strcmp("--version", argv[optind])) {
|
|
print_version();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
optind = 1;
|
|
|
|
/* Parse short command-line options */
|
|
while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:v")) != -1) {
|
|
switch (opt) {
|
|
case '4':
|
|
case '6':
|
|
family = opt == '4' ? IPADDR_INET4 : IPADDR_INET6;
|
|
break;
|
|
case 'a':
|
|
case 'f':
|
|
/* For compatibility only */
|
|
break;
|
|
case 'c':
|
|
csv_mode = 1;
|
|
break;
|
|
case 'd':
|
|
#if DEBUG > 0
|
|
log_min_severity = LOGS_DEBUG;
|
|
#endif
|
|
break;
|
|
case 'e':
|
|
end_dot = 1;
|
|
break;
|
|
case 'h':
|
|
hostnames = optarg;
|
|
break;
|
|
case 'm':
|
|
multi = 1;
|
|
break;
|
|
case 'n':
|
|
no_dns = 1;
|
|
break;
|
|
case 'N':
|
|
source_names = 1;
|
|
break;
|
|
case 'p':
|
|
port = atoi(optarg);
|
|
break;
|
|
case 'v':
|
|
print_version();
|
|
return 0;
|
|
default:
|
|
print_help(progname);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (isatty(0) && isatty(1) && isatty(2)) {
|
|
on_terminal = 1;
|
|
}
|
|
|
|
if (on_terminal && optind == argc) {
|
|
display_gpl();
|
|
}
|
|
|
|
DNS_SetAddressFamily(family);
|
|
|
|
if (!hostnames) {
|
|
hostnames = DEFAULT_COMMAND_SOCKET",127.0.0.1,::1";
|
|
}
|
|
|
|
UTI_SetQuitSignalsHandler(signal_handler, 0);
|
|
|
|
SCK_Initialise(IPADDR_UNSPEC);
|
|
server_addresses = get_addresses(hostnames, port);
|
|
|
|
if (!open_io())
|
|
LOG_FATAL("Could not open connection to daemon");
|
|
|
|
if (optind < argc) {
|
|
ret = process_args(argc - optind, argv + optind, multi);
|
|
} else {
|
|
do {
|
|
line = read_line();
|
|
if (line && !quit) {
|
|
ret = process_line(line);
|
|
}else {
|
|
/* supply the final '\n' when user exits via ^D */
|
|
if( on_terminal ) printf("\n");
|
|
}
|
|
} while (line && !quit);
|
|
}
|
|
|
|
close_io();
|
|
free_addresses(server_addresses);
|
|
SCK_Finalise();
|
|
|
|
return !ret;
|
|
}
|
|
|
|
|