From 03541f3626728ddce882109fe52ba7b862b5f05c Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Mon, 25 May 2020 14:10:40 +0200 Subject: [PATCH] cmdmon: add selectdata command Add a command to report selection-specific data. --- candm.h | 33 +++++++++++++++++-- client.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++- cmdmon.c | 42 +++++++++++++++++++++++ doc/chronyc.adoc | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ pktlength.c | 2 ++ reports.h | 13 ++++++++ sources.c | 74 +++++++++++++++++++++++++++++++++++++++++ sources.h | 3 +- 8 files changed, 335 insertions(+), 4 deletions(-) diff --git a/candm.h b/candm.h index e7a76d2..0880df0 100644 --- a/candm.h +++ b/candm.h @@ -106,7 +106,8 @@ #define REQ_RESET_SOURCES 66 #define REQ_AUTH_DATA 67 #define REQ_CLIENT_ACCESSES_BY_INDEX3 68 -#define N_REQUEST_TYPES 69 +#define REQ_SELECT_DATA 69 +#define N_REQUEST_TYPES 70 /* Structure used to exchange timespecs independent of time_t size */ typedef struct { @@ -360,6 +361,11 @@ typedef struct { int32_t EOR; } REQ_AuthData; +typedef struct { + uint32_t index; + int32_t EOR; +} REQ_SelectData; + /* ================================================== */ #define PKT_TYPE_CMD_REQUEST 1 @@ -464,6 +470,7 @@ typedef struct { REQ_NTPData ntp_data; REQ_NTPSourceName ntp_source_name; REQ_AuthData auth_data; + REQ_SelectData select_data; } data; /* Command specific parameters */ /* Padding used to prevent traffic amplification. It only defines the @@ -504,7 +511,8 @@ typedef struct { #define RPY_AUTH_DATA 20 #define RPY_CLIENT_ACCESSES_BY_INDEX3 21 #define RPY_SERVER_STATS2 22 -#define N_REPLY_TYPES 23 +#define RPY_SELECT_DATA 23 +#define N_REPLY_TYPES 24 /* Status codes */ #define STT_SUCCESS 0 @@ -744,6 +752,26 @@ typedef struct { int32_t EOR; } RPY_AuthData; +#define RPY_SD_OPTION_NOSELECT 0x1 +#define RPY_SD_OPTION_PREFER 0x2 +#define RPY_SD_OPTION_TRUST 0x4 +#define RPY_SD_OPTION_REQUIRE 0x8 + +typedef struct { + uint32_t ref_id; + IPAddr ip_addr; + uint8_t state_char; + uint8_t authentication; + uint8_t pad[2]; + uint16_t conf_options; + uint16_t eff_options; + uint32_t last_sample_ago; + Float score; + Float lo_limit; + Float hi_limit; + int32_t EOR; +} RPY_SelectData; + typedef struct { uint8_t version; uint8_t pkt_type; @@ -775,6 +803,7 @@ typedef struct { RPY_NTPData ntp_data; RPY_NTPSourceName ntp_source_name; RPY_AuthData auth_data; + RPY_SelectData select_data; } data; /* Reply specific parameters */ } CMD_Reply; diff --git a/client.c b/client.c index 8fcf449..4cd95f8 100644 --- a/client.c +++ b/client.c @@ -1209,6 +1209,7 @@ give_help(void) "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" "reselect\0Force reselecting synchronisation source\0" "reselectdist \0Modify reselection distance\0" "\0\0" @@ -1303,6 +1304,7 @@ enum { TAB_COMPLETE_RESET_OPTS, TAB_COMPLETE_SOURCES_OPTS, TAB_COMPLETE_SOURCESTATS_OPTS, + TAB_COMPLETE_SELECTDATA_OPTS, TAB_COMPLETE_MAX_INDEX }; @@ -1319,7 +1321,7 @@ command_name_generator(const char *text, int state) "manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll", "maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline", "polltarget", "quit", "refresh", "rekey", "reselect", "reselectdist", "reset", - "retries", "rtcdata", "serverstats", "settime", "shutdown", "smoothing", + "retries", "rtcdata", "selectdata", "serverstats", "settime", "shutdown", "smoothing", "smoothtime", "sourcename", "sources", "sourcestats", "timeout", "tracking", "trimrtc", "waitsync", "writertc", NULL @@ -1327,6 +1329,7 @@ command_name_generator(const char *text, int state) 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 *selectdata_options[] = { "-a", "-v", NULL }; const char *sources_options[] = { "-a", "-v", NULL }; const char *sourcestats_options[] = { "-a", "-v", NULL }; static int list_index, len; @@ -1335,6 +1338,7 @@ command_name_generator(const char *text, int state) names[TAB_COMPLETE_ADD_OPTS] = add_options; names[TAB_COMPLETE_MANUAL_OPTS] = manual_options; names[TAB_COMPLETE_RESET_OPTS] = reset_options; + names[TAB_COMPLETE_SELECTDATA_OPTS] = selectdata_options; names[TAB_COMPLETE_SOURCES_OPTS] = sources_options; names[TAB_COMPLETE_SOURCESTATS_OPTS] = sourcestats_options; @@ -1368,6 +1372,8 @@ command_name_completion(const char *text, int start, int end) tab_complete_index = TAB_COMPLETE_MANUAL_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 ")) { @@ -2557,6 +2563,81 @@ process_cmd_ntpdata(char *line) /* ================================================== */ +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, M - missing samples, d/D - large distance,\n"); + printf( " / ~ - jittery, w/W - waits for others, T - not trusted,\n"); + printf( "| x - falseticker, P - not preferred, U - waits for update,\n"); + printf( "| S - stale, O - orphan, + - 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 "); + + /* "S NNNNNNNNNNNNNNNNNNNNNNNNN A OOOO- OOOO- LLLL SSSSS LLLLLLL LLLLLLL" */ + + 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\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' : '-', + '-', + (unsigned long)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), + REPORT_END); + } + + return 1; +} + +/* ================================================== */ + static int process_cmd_serverstats(char *line) { @@ -3272,6 +3353,9 @@ process_line(char *line) } 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, "serverstats")) { do_normal_submit = 0; ret = process_cmd_serverstats(line); diff --git a/cmdmon.c b/cmdmon.c index 7ac79bc..5854bad 100644 --- a/cmdmon.c +++ b/cmdmon.c @@ -138,6 +138,7 @@ static const char permissions[] = { PERMIT_AUTH, /* RESET_SOURCES */ PERMIT_AUTH, /* AUTH_DATA */ PERMIT_AUTH, /* CLIENT_ACCESSES_BY_INDEX3 */ + PERMIT_AUTH, /* SELECT_DATA */ }; /* ================================================== */ @@ -1282,6 +1283,43 @@ handle_auth_data(CMD_Request *rx_message, CMD_Reply *tx_message) tx_message->data.auth_data.nak = htons(report.nak); } +/* ================================================== */ + +static uint16_t +convert_select_options(int options) +{ + return (options & SRC_SELECT_PREFER ? RPY_SD_OPTION_PREFER : 0) | + (options & SRC_SELECT_NOSELECT ? RPY_SD_OPTION_NOSELECT : 0) | + (options & SRC_SELECT_TRUST ? RPY_SD_OPTION_TRUST : 0) | + (options & SRC_SELECT_REQUIRE ? RPY_SD_OPTION_REQUIRE : 0); +} + +/* ================================================== */ + +static void +handle_select_data(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + RPT_SelectReport report; + + if (!SRC_GetSelectReport(ntohl(rx_message->data.select_data.index), &report)) { + tx_message->status = htons(STT_NOSUCHSOURCE); + return; + } + + tx_message->reply = htons(RPY_SELECT_DATA); + + tx_message->data.select_data.ref_id = htonl(report.ref_id); + UTI_IPHostToNetwork(&report.ip_addr, &tx_message->data.select_data.ip_addr); + tx_message->data.select_data.state_char = report.state_char; + tx_message->data.select_data.authentication = report.authentication; + tx_message->data.select_data.conf_options = htons(convert_select_options(report.conf_options)); + tx_message->data.select_data.eff_options = htons(convert_select_options(report.eff_options)); + tx_message->data.select_data.last_sample_ago = htonl(report.last_sample_ago); + tx_message->data.select_data.score = UTI_FloatHostToNetwork(report.score); + tx_message->data.select_data.hi_limit = UTI_FloatHostToNetwork(report.hi_limit); + tx_message->data.select_data.lo_limit = UTI_FloatHostToNetwork(report.lo_limit); +} + /* ================================================== */ /* Read a packet and process it */ @@ -1668,6 +1706,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything) handle_auth_data(&rx_message, &tx_message); break; + case REQ_SELECT_DATA: + handle_select_data(&rx_message, &tx_message); + break; + default: DEBUG_LOG("Unhandled command %d", rx_command); tx_message.status = htons(STT_FAILED); diff --git a/doc/chronyc.adoc b/doc/chronyc.adoc index cecd5cf..3e32f13 100644 --- a/doc/chronyc.adoc +++ b/doc/chronyc.adoc @@ -414,6 +414,92 @@ This is the estimated offset of the source. *Std Dev*::: This is the estimated sample standard deviation. +[[selectdata]]*selectdata* [*-a*] [*-v*]:: +The *selectdata* command displays information specific to the selection of time +sources. If the *-a* option is specified, all sources are displayed, including +those that do not have a known address yet. With the *-v* option, extra caption +lines are shown as a reminder of the meanings of the columns. ++ +An example of the output is shown below. ++ +---- +S Name/IP Address Auth COpts EOpts Last Score Interval +==================================================================== +D foo.example.net Y ----- --TR- 4 1.0 -61ms +62ms +* bar.example.net N ----- ----- 0 1.0 -6846us +7305us ++ baz.example.net N ----- ----- 10 1.0 -7381us +7355us +---- ++ +The columns are as follows: ++ +*S*::: +This column indicates the state of the source after the last source selection. +It is similar to the state reported by the *sources* command, but more +states are reported. +::: +The following states indicate the source is not considered selectable for +synchronisation: +* _N_ - has the *noselect* option. +* _M_ - does not have enough measurements. +* _d_ - has a root distance larger than the maximum distance (configured by the + <> directive). +* _~_ - has a jitter larger than the maximum jitter (configured by the + <> directive). +* _w_ - waits for other sources to get out of the _M_ state. +* _S_ - has older measurements than other sources. +* _O_ - has a stratum equal or larger than the orphan stratum (configured by + the <> directive). +* _T_ - does not fully agree with sources that have the *trust* option. +* _x_ - does not agree with other sources (falseticker). +::: +The following states indicate the source is considered selectable, but it is +not currently used for synchronisation: +* _W_ - waits for other sources to be selectable (required by the + <> directive, or + the *require* option of another source). +* _P_ - another selectable source is preferred due to the *prefer* option. +* _U_ - waits for a new measurement (after selecting a different best source). +* _D_ - has, or recently had, a root distance which is too large to be combined + with other sources (configured by the + <> directive). +::: +The following states indicate the source is used for synchronisation of the +local clock: +* _+_ - combined with the best source. +* _*_ - selected as the best source to update the reference data (e.g. root + delay, root dispersion). +*Name/IP address*::: +This column shows the name or IP address of the source if it is an NTP server, +or the reference ID if it is a reference clock. +*Auth*::: +This column indicites whether an authentication mechanism is enabled for the +source. _Y_ means yes and _N_ means no. +*COpts*::: +This column displays the configured selection options of the source. +* _N_ indicates the *noselect* option. +* _P_ indicates the *prefer* option. +* _T_ indicates the *trust* option. +* _R_ indicates the *require* option. +*EOpts*::: +This column displays the current effective selection options of the source, +which can be different from the configured options due to the authentication +selection mode (configured by the +<> directive). The symbols are the +same as in the *COpts* column. +*Last*::: +This column displays how long ago was the last measurement of the source made +when the selection was performed. +*Score*::: +This column displays the current score against the source in the _*_ state. The +scoring system avoids frequent reselection when multiple sources have a similar +root distance. A value larger than 1 indicates this source was better than the +_*_ source in recent selections. If the score reaches 10, the best source will +be reselected and the scores will be reset to 1. +*Interval*::: +This column displays the lower and upper endpoint of the interval which was +expected to contain the true offset of the local clock considering the root +distance at the time of the selection. + [[reselect]]*reselect*:: To avoid excessive switching between sources, *chronyd* can stay synchronised to a source even when it is not currently the best one among the available diff --git a/pktlength.c b/pktlength.c index 0721f09..bc0055f 100644 --- a/pktlength.c +++ b/pktlength.c @@ -126,6 +126,7 @@ static const struct request_length request_lengths[] = { REQ_LENGTH_ENTRY(auth_data, auth_data), /* AUTH_DATA */ REQ_LENGTH_ENTRY(client_accesses_by_index, client_accesses_by_index), /* CLIENT_ACCESSES_BY_INDEX3 */ + REQ_LENGTH_ENTRY(select_data, select_data), /* SELECT_DATA */ }; static const uint16_t reply_lengths[] = { @@ -152,6 +153,7 @@ static const uint16_t reply_lengths[] = { RPY_LENGTH_ENTRY(auth_data), /* AUTH_DATA */ RPY_LENGTH_ENTRY(client_accesses_by_index), /* CLIENT_ACCESSES_BY_INDEX3 */ RPY_LENGTH_ENTRY(server_stats), /* SERVER_STATS2 */ + RPY_LENGTH_ENTRY(select_data), /* SELECT_DATA */ }; /* ================================================== */ diff --git a/reports.h b/reports.h index a41573b..a1244db 100644 --- a/reports.h +++ b/reports.h @@ -178,4 +178,17 @@ typedef struct { int nak; } RPT_AuthReport; +typedef struct { + uint32_t ref_id; + IPAddr ip_addr; + char state_char; + int authentication; + int conf_options; + int eff_options; + uint32_t last_sample_ago; + double score; + double lo_limit; + double hi_limit; +} RPT_SelectReport; + #endif /* GOT_REPORTS_H */ diff --git a/sources.c b/sources.c index 9314069..160977b 100644 --- a/sources.c +++ b/sources.c @@ -316,6 +316,8 @@ SRC_ResetInstance(SRC_Instance instance) instance->leap = LEAP_Unsynchronised; instance->leap_vote = 0; + memset(&instance->sel_info, 0, sizeof (instance->sel_info)); + SST_ResetInstance(instance->stats); } @@ -1509,6 +1511,78 @@ SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report, struct timespec /* ================================================== */ +static char +get_status_char(SRC_Status status) +{ + switch (status) { + case SRC_UNSELECTABLE: + return 'N'; + case SRC_BAD_STATS: + return 'M'; + case SRC_BAD_DISTANCE: + return 'd'; + case SRC_JITTERY: + return '~'; + case SRC_WAITS_STATS: + return 'w'; + case SRC_STALE: + return 'S'; + case SRC_ORPHAN: + return 'O'; + case SRC_UNTRUSTED: + return 'T'; + case SRC_FALSETICKER: + return 'x'; + case SRC_WAITS_SOURCES: + return 'W'; + case SRC_NONPREFERRED: + return 'P'; + case SRC_WAITS_UPDATE: + return 'U'; + case SRC_DISTANT: + return 'D'; + case SRC_OUTLIER: + return 'L'; + case SRC_UNSELECTED: + return '+'; + case SRC_SELECTED: + return '*'; + default: + return '?'; + } +} + +/* ================================================== */ + +int +SRC_GetSelectReport(int index, RPT_SelectReport *report) +{ + SRC_Instance inst; + + if (index >= n_sources || index < 0) + return 0; + + inst = sources[index]; + + report->ref_id = inst->ref_id; + if (inst->ip_addr) + report->ip_addr = *inst->ip_addr; + else + report->ip_addr.family = IPADDR_UNSPEC; + report->state_char = get_status_char(inst->status); + report->authentication = inst->authenticated; + report->conf_options = inst->conf_sel_options; + report->eff_options = inst->sel_options; + report->last_sample_ago = inst->sel_info.last_sample_ago; + report->score = inst->sel_score; + report->lo_limit = inst->sel_info.lo_limit; + report->hi_limit = inst->sel_info.hi_limit; + + return 1; +} + +/* ================================================== */ + SRC_Type SRC_GetType(int index) { diff --git a/sources.h b/sources.h index d4c6f55..a22faab 100644 --- a/sources.h +++ b/sources.h @@ -130,9 +130,10 @@ extern int SRC_IsSyncPeer(SRC_Instance inst); extern int SRC_IsReachable(SRC_Instance inst); extern int SRC_ReadNumberOfSources(void); extern int SRC_ActiveSources(void); -extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timespec *now); +extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timespec *now); extern int SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report, struct timespec *now); +extern int SRC_GetSelectReport(int index, RPT_SelectReport *report); extern SRC_Type SRC_GetType(int index);