Add a command to report selection-specific data.
#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 {
int32_t EOR;
} REQ_AuthData;
+typedef struct {
+ uint32_t index;
+ int32_t EOR;
+} REQ_SelectData;
+
/* ================================================== */
#define PKT_TYPE_CMD_REQUEST 1
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
#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
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;
RPY_NTPData ntp_data;
RPY_NTPSourceName ntp_source_name;
RPY_AuthData auth_data;
+ RPY_SelectData select_data;
} data; /* Reply specific parameters */
} CMD_Reply;
"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 <dist>\0Modify reselection distance\0"
"\0\0"
TAB_COMPLETE_RESET_OPTS,
TAB_COMPLETE_SOURCES_OPTS,
TAB_COMPLETE_SOURCESTATS_OPTS,
+ TAB_COMPLETE_SELECTDATA_OPTS,
TAB_COMPLETE_MAX_INDEX
};
"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
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;
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;
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 ")) {
/* ================================================== */
+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)
{
} 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);
PERMIT_AUTH, /* RESET_SOURCES */
PERMIT_AUTH, /* AUTH_DATA */
PERMIT_AUTH, /* CLIENT_ACCESSES_BY_INDEX3 */
+ PERMIT_AUTH, /* SELECT_DATA */
};
/* ================================================== */
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 */
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);
*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
+ <<chrony.conf.adoc#maxdistance,*maxdistance*>> directive).
+* _~_ - has a jitter larger than the maximum jitter (configured by the
+ <<chrony.conf.adoc#maxjitter,*maxjitter*>> 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 <<chrony.conf.adoc#local,*local*>> 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
+ <<chrony.conf.adoc#minsources,*minsources*>> 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
+ <<chrony.conf.adoc#combinelimit,*combinelimit*>> 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
+<<chrony.conf.adoc#authselmode,*authselmode*>> 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
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[] = {
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 */
};
/* ================================================== */
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 */
instance->leap = LEAP_Unsynchronised;
instance->leap_vote = 0;
+ memset(&instance->sel_info, 0, sizeof (instance->sel_info));
+
SST_ResetInstance(instance->stats);
}
/* ================================================== */
+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)
{
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);