diff --git a/candm.h b/candm.h
index 65daedf..0894ad5 100644
--- a/candm.h
+++ b/candm.h
@@ -110,7 +110,8 @@
#define REQ_RELOAD_SOURCES 70
#define REQ_DOFFSET2 71
#define REQ_MODIFY_SELECTOPTS 72
-#define N_REQUEST_TYPES 73
+#define REQ_MODIFY_OFFSET 73
+#define N_REQUEST_TYPES 74
/* Structure used to exchange timespecs independent of time_t size */
typedef struct {
@@ -390,6 +391,13 @@ typedef struct {
int32_t EOR;
} REQ_Modify_SelectOpts;
+typedef struct {
+ IPAddr address;
+ uint32_t ref_id;
+ Float new_offset;
+ int32_t EOR;
+} REQ_Modify_Offset;
+
/* ================================================== */
#define PKT_TYPE_CMD_REQUEST 1
@@ -497,6 +505,7 @@ typedef struct {
REQ_AuthData auth_data;
REQ_SelectData select_data;
REQ_Modify_SelectOpts modify_select_opts;
+ REQ_Modify_Offset modify_offset;
} data; /* Command specific parameters */
/* Padding used to prevent traffic amplification. It only defines the
diff --git a/client.c b/client.c
index 5ead2b0..0231b9e 100644
--- a/client.c
+++ b/client.c
@@ -344,6 +344,24 @@ parse_source_address(char *word, IPAddr *address)
/* ================================================== */
+static int
+parse_source_address_or_refid(char *s, IPAddr *address, uint32_t *ref_id)
+{
+ address->family = IPADDR_UNSPEC;
+ *ref_id = 0;
+
+ /* Don't allow hostnames to avoid conflicts with reference IDs */
+ if (UTI_StringToIdIP(s, address) || UTI_StringToIP(s, address))
+ return 1;
+
+ if (CPS_ParseRefid(s, ref_id) > 0)
+ return 1;
+
+ return 0;
+}
+
+/* ================================================== */
+
static int
read_mask_address(char *line, IPAddr *mask, IPAddr *address)
{
@@ -1031,6 +1049,7 @@ give_help(void)
"selectopts
<+|-options>\0Modify selection options\0"
"reselect\0Force reselecting synchronisation source\0"
"reselectdist \0Modify reselection distance\0"
+ "offset \0Modify offset correction\0"
"\0\0"
"NTP sources:\0\0"
"activity\0Check how many NTP sources are online/offline\0"
@@ -1141,7 +1160,8 @@ command_name_generator(const char *text, int state)
"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",
+ "maxupdateskew", "minpoll", "minstratum", "ntpdata",
+ "offline", "offset", "online", "onoffline",
"polltarget", "quit", "refresh", "rekey", "reload", "reselect", "reselectdist", "reset",
"retries", "rtcdata", "selectdata", "selectopts", "serverstats", "settime",
"shutdown", "smoothing", "smoothtime", "sourcename", "sources", "sourcestats",
@@ -2858,6 +2878,34 @@ process_cmd_activity(const char *line)
/* ================================================== */
+static int
+process_cmd_offset(CMD_Request *msg, char *line)
+{
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ double offset;
+ char *src;
+
+ src = line;
+ line = CPS_SplitWord(line);
+
+ if (!parse_source_address_or_refid(src, &ip_addr, &ref_id) ||
+ sscanf(line, "%lf", &offset) != 1) {
+ LOG(LOGS_ERR, "Invalid syntax for offset command");
+ return 0;
+ }
+
+ UTI_IPHostToNetwork(&ip_addr, &msg->data.modify_offset.address);
+ msg->data.modify_offset.ref_id = htonl(ref_id);
+ msg->data.modify_offset.new_offset = UTI_FloatHostToNetwork(offset);
+
+ msg->command = htons(REQ_MODIFY_OFFSET);
+
+ return 1;
+}
+
+/* ================================================== */
+
static int
process_cmd_reselectdist(CMD_Request *msg, char *line)
{
@@ -2939,15 +2987,10 @@ process_cmd_selectopts(CMD_Request *msg, char *line)
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;
- }
+ if (!parse_source_address_or_refid(src, &ip_addr, &ref_id)) {
+ LOG(LOGS_ERR, "Invalid syntax for selectopts command");
+ return 0;
}
mask = options = 0;
@@ -3249,6 +3292,8 @@ process_line(char *line)
ret = process_cmd_ntpdata(line);
} else if (!strcmp(command, "offline")) {
do_normal_submit = process_cmd_offline(&tx_message, line);
+ } else if (!strcmp(command, "offset")) {
+ do_normal_submit = process_cmd_offset(&tx_message, line);
} else if (!strcmp(command, "online")) {
do_normal_submit = process_cmd_online(&tx_message, line);
} else if (!strcmp(command, "onoffline")) {
diff --git a/cmdmon.c b/cmdmon.c
index 0a0193b..716775f 100644
--- a/cmdmon.c
+++ b/cmdmon.c
@@ -145,6 +145,7 @@ static const char permissions[] = {
PERMIT_AUTH, /* RELOAD_SOURCES */
PERMIT_AUTH, /* DOFFSET2 */
PERMIT_AUTH, /* MODIFY_SELECTOPTS */
+ PERMIT_AUTH, /* MODIFY_OFFSET */
};
/* ================================================== */
@@ -1418,6 +1419,24 @@ handle_modify_selectopts(CMD_Request *rx_message, CMD_Reply *tx_message)
tx_message->status = htons(STT_NOSUCHSOURCE);
}
+/* ================================================== */
+
+static void
+handle_modify_offset(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ double offset;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_offset.address, &ip_addr);
+ ref_id = ntohl(rx_message->data.modify_offset.ref_id);
+ offset = UTI_FloatNetworkToHost(rx_message->data.modify_offset.new_offset);
+
+ if ((ip_addr.family != IPADDR_UNSPEC && !NSR_ModifyOffset(&ip_addr, offset)) ||
+ (ip_addr.family == IPADDR_UNSPEC && !RCL_ModifyOffset(ref_id, offset)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
/* ================================================== */
/* Read a packet and process it */
@@ -1818,6 +1837,10 @@ read_from_cmd_socket(int sock_fd, int event, void *anything)
handle_modify_selectopts(&rx_message, &tx_message);
break;
+ case REQ_MODIFY_OFFSET:
+ handle_modify_offset(&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 8e0d426..1386e00 100644
--- a/doc/chronyc.adoc
+++ b/doc/chronyc.adoc
@@ -556,6 +556,13 @@ The *reselectdist* command sets the reselection distance. It is equivalent to
the <> directive in the
configuration file.
+[[offset]]*offset* _address|refid_ _offset_::
+The *offset* command modifies the offset correction of an NTP source specified
+by IP address (or the _ID#XXXXXXXXXX_ identifier used for unknown addresses),
+or a reference clock specified by reference ID as a string. It is equivalent to
+the *offset* option in the <> or
+<> directive respectively.
+
=== NTP sources
[[activity]]*activity*::
diff --git a/pktlength.c b/pktlength.c
index 3e0b319..3cca306 100644
--- a/pktlength.c
+++ b/pktlength.c
@@ -130,6 +130,7 @@ static const struct request_length request_lengths[] = {
REQ_LENGTH_ENTRY(null, null), /* RELOAD_SOURCES */
REQ_LENGTH_ENTRY(doffset, null), /* DOFFSET2 */
REQ_LENGTH_ENTRY(modify_select_opts, null), /* MODIFY_SELECTOPTS */
+ REQ_LENGTH_ENTRY(modify_offset, null), /* MODIFY_OFFSET */
};
static const uint16_t reply_lengths[] = {
diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc
index a261e7a..a4f486a 100755
--- a/test/simulation/110-chronyc
+++ b/test/simulation/110-chronyc
@@ -165,6 +165,7 @@ for chronyc_conf in \
"offline" \
"offline 255.255.255.0/1.2.3.0" \
"offline 1.2.3.0/24" \
+ "offset 1.2.3.4 1.0" \
"online" \
"online 1.2.3.0/24" \
"onoffline" \
@@ -351,6 +352,7 @@ maxpoll 192.168.123.1 5
maxupdateskew 192.168.123.1 10.0
minpoll 192.168.123.1 3
minstratum 192.168.123.1 1
+offset 192.168.123.1 -1.0
polltarget 192.168.123.1 10
selectopts 192.168.123.1 +trust +prefer -require
selectdata
@@ -375,6 +377,7 @@ check_chronyc_output "^200 OK
200 OK
200 OK
200 OK
+200 OK
S Name/IP Address Auth COpts EOpts Last Score Interval Leap
=======================================================================
M node1\.net1\.clk N \-PT\-\- \-PT\-\- 0 1\.0 \+0ns \+0ns \?
diff --git a/test/system/007-cmdmon b/test/system/007-cmdmon
index 8599503..953b4f3 100755
--- a/test/system/007-cmdmon
+++ b/test/system/007-cmdmon
@@ -28,6 +28,7 @@ for command in \
"local" \
"online" \
"onoffline" \
+ "offset $server 0.0" \
"maxdelay $server 1e-1" \
"maxdelaydevratio $server 5.0" \
"maxdelayratio $server 3.0" \