Add a second servo that provides samples to other processes in order to
control the clock. The chrony SOCK refclock uses a Unix domain socket
instead of a shared memory segment.
The main advantage over the NTP SHM refclock is better security as the
socket can be located in a directory with restricted access, while the
shared memory segment (using a predictable key) can be created by
untrusted users or applications if they can run before ptp4l/phc2sys and
chronyd/ntpd. There doesn't seem to a backward-compatible fix of the
protocol as both sides are expected to be able to create the segment if
it doesn't exist yet, possibly under a non-root owner, there is no
authentication of messages, and the protocol cannot be restarted if one
side decides to remove and recreate the segment.
Signed-off-by: Miroslav Lichvar <mlich...@redhat.com>
---
chronysock.c | 163 ++++++++++++++++++++++++++++++++++++++++++++
chronysock.h | 26 +++++++
config.c | 2 +
configs/default.cfg | 1 +
makefile | 2 +-
phc2sys.8 | 9 ++-
phc2sys.c | 3 +
ptp4l.8 | 12 ++--
servo.c | 4 ++
servo.h | 1 +
10 files changed, 216 insertions(+), 7 deletions(-)
create mode 100644 chronysock.c
create mode 100644 chronysock.h
diff --git a/chronysock.c b/chronysock.c
new file mode 100644
index 0000000..a8c0033
--- /dev/null
+++ b/chronysock.c
@@ -0,0 +1,163 @@
+/**
+ * @file chronysock.c
+ * @brief Implements a servo providing samples to chronyd over socket.
+ * @note Copyright (C) 2022 Miroslav Lichvar <mlich...@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "chronysock.h"
+#include "config.h"
+#include "print.h"
+#include "servo_private.h"
+
+#define LEAP_NORMAL 0
+#define LEAP_INSERT 1
+#define LEAP_DELETE 2
+#define SOCK_MAGIC 0x534f434b
+
+/* Copied from chrony-3.2/refclock_sock.c */
+struct sock_sample {
+ /* Time of the measurement (system time) */
+ struct timeval tv;
+
+ /* Offset between the true time and the system time (in seconds) */
+ double offset;
+
+ /* Non-zero if the sample is from a PPS signal, i.e. another source
+ is needed to obtain seconds */
+ int pulse;
+
+ /* 0 - normal, 1 - insert leap second, 2 - delete leap second */
+ int leap;
+
+ /* Padding, ignored */
+ int _pad;
+
+ /* Protocol identifier (0x534f434b) */
+ int magic;
+};
+
+struct sock_servo {
+ struct servo servo;
+ int sock_fd;
+ int leap;
+};
+
+static void chronysock_destroy(struct servo *servo)
+{
+ struct sock_servo *s = container_of(servo, struct sock_servo, servo);
+ free(s);
+}
+
+static double chronysock_sample(struct servo *servo,
+ int64_t offset,
+ uint64_t local_ts,
+ double weight,
+ enum servo_state *state)
+{
+ struct sock_servo *s = container_of(servo, struct sock_servo, servo);
+ struct sock_sample sample;
+
+ memset(&sample, 0, sizeof(sample));
+ sample.tv.tv_sec = local_ts / 1000000000ULL;
+ sample.tv.tv_usec = local_ts % 1000000000ULL / 1000U;
+ sample.offset = -offset / 1e9;
+ sample.magic = SOCK_MAGIC;
+
+ switch (s->leap) {
+ case -1:
+ sample.leap = LEAP_DELETE;
+ break;
+ case 1:
+ sample.leap = LEAP_INSERT;
+ break;
+ default:
+ sample.leap = LEAP_NORMAL;
+ }
+
+ if (send(s->sock_fd, &sample, sizeof sample, 0) != sizeof sample) {
+ pr_err("chronysock: send failed: %m");
+ return 0;
+ }
+
+ *state = SERVO_UNLOCKED;
+ return 0.0;
+}
+
+static void chronysock_sync_interval(struct servo *servo, double interval)
+{
+}
+
+static void chronysock_reset(struct servo *servo)
+{
+}
+
+static void chronysock_leap(struct servo *servo, int leap)
+{
+ struct sock_servo *s = container_of(servo, struct sock_servo, servo);
+
+ s->leap = leap;
+}
+
+struct servo *chronysock_servo_create(struct config *cfg)
+{
+ char *addr = config_get_string(cfg, NULL, "chrony_sock_address");
+ struct sockaddr_un sa;
+ struct sock_servo *s;
+ int i;
+
+ s = calloc(1, sizeof(*s));
+ if (!s)
+ return NULL;
+
+ s->servo.destroy = chronysock_destroy;
+ s->servo.sample = chronysock_sample;
+ s->servo.sync_interval = chronysock_sync_interval;
+ s->servo.reset = chronysock_reset;
+ s->servo.leap = chronysock_leap;
+
+ s->sock_fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (s->sock_fd < 0) {
+ pr_err("chronysock: failed to create socket: %m");
+ free(s);
+ return NULL;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_LOCAL;
+ strncpy(sa.sun_path, addr, sizeof(sa.sun_path) - 1);
+
+ /* Wait up to 1 second for the server socket to be created */
+ for (i = 10; i >= 0; i--) {
+ if (!connect(s->sock_fd, (struct sockaddr *)&sa, sizeof(sa)))
+ break;
+ if (i > 0) {
+ usleep(100000);
+ continue;
+ }
+
+ pr_err("chronysock: connect failed: %m");
+ close(s->sock_fd);
+ free(s);
+ return NULL;
+ }
+
+ return &s->servo;
+}
diff --git a/chronysock.h b/chronysock.h
new file mode 100644
index 0000000..0bcce0d
--- /dev/null
+++ b/chronysock.h
@@ -0,0 +1,26 @@
+/**
+ * @file chronysock.h
+ * @note Copyright (C) 2022 Miroslav Lichvar <mlich...@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+#ifndef HAVE_CHRONYSOCK_H
+#define HAVE_CHRONYSOCK_H
+
+#include "servo.h"
+
+struct servo *chronysock_servo_create(struct config *cfg);
+
+#endif
diff --git a/config.c b/config.c
index e454c91..be8a195 100644
--- a/config.c
+++ b/config.c
@@ -144,6 +144,7 @@ static struct config_enum clock_servo_enu[] = {
{ "linreg", CLOCK_SERVO_LINREG },
{ "ntpshm", CLOCK_SERVO_NTPSHM },
{ "nullf", CLOCK_SERVO_NULLF },
+ { "sock", CLOCK_SERVO_SOCK },
{ NULL, 0 },
};
@@ -232,6 +233,7 @@ struct config_item config_tab[] = {
PORT_ITEM_INT("boundary_clock_jbod", 0, 0, 1),
PORT_ITEM_ENU("BMCA", BMCA_PTP, bmca_enu),
GLOB_ITEM_INT("check_fup_sync", 0, 0, 1),
+ GLOB_ITEM_STR("chrony_sock_address", "/var/run/chrony/refclock.sock"),
GLOB_ITEM_INT("clientOnly", 0, 0, 1),
GLOB_ITEM_INT("clockAccuracy", 0xfe, 0, UINT8_MAX),
GLOB_ITEM_INT("clockClass", 248, 0, UINT8_MAX),
diff --git a/configs/default.cfg b/configs/default.cfg
index 1b5b806..1f2509a 100644
--- a/configs/default.cfg
+++ b/configs/default.cfg
@@ -78,6 +78,7 @@ first_step_threshold 0.00002
max_frequency 900000000
clock_servo pi
sanity_freq_limit 200000000
+chrony_sock_address /var/run/chrony/refclock.sock
ntpshm_segment 0
msg_interval_request 0
servo_num_offset_values 10
diff --git a/makefile b/makefile
index 9aed383..d7ea7d9 100644
--- a/makefile
+++ b/makefile
@@ -24,7 +24,7 @@ CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG)
$(EXTRA_CFLAGS)
LDLIBS = -lm -lrt -pthread $(EXTRA_LDFLAGS)
PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc
FILTERS = filter.o mave.o mmedian.o
-SERVOS = linreg.o ntpshm.o nullf.o pi.o servo.o
+SERVOS = chronysock.o linreg.o ntpshm.o nullf.o pi.o servo.o
TRANSP = raw.o transport.o udp.o udp6.o uds.o
TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o
ts2phc_generic_pps_source.o \
ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o
ts2phc_pps_source.o
diff --git a/phc2sys.8 b/phc2sys.8
index 9825ec7..9d9fc16 100644
--- a/phc2sys.8
+++ b/phc2sys.8
@@ -125,8 +125,8 @@ option. This option may be given up to 128 times.
.BI \-E " servo"
Specify which clock servo should be used. Valid values are pi for a PI
controller, linreg for an adaptive controller using linear regression, and
-ntpshm for the NTP SHM reference clock to allow another process to synchronize
-the local clock.
+ntpshm and sock for the NTP SHM and chrony SOCK reference clocks respectively
+to allow another process to synchronize the local clock.
The default is pi.
.TP
.BI \-P " kp"
@@ -382,6 +382,11 @@ Same as option
.B \-F
(see above).
+.TP
+.B chrony_sock_address
+The address of the chronyd's UNIX domain socket configured as a SOCK refclock
+to be used by the sock servo. The default is /var/run/chrony/refclock.sock.
+
.TP
.B ntpshm_segment
The number of the SHM segment used by ntpshm servo. The default is 0.
diff --git a/phc2sys.c b/phc2sys.c
index 8d2624f..9bba496 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -1141,6 +1141,9 @@ int main(int argc, char *argv[])
} else if (!strcasecmp(optarg, "ntpshm")) {
config_set_int(cfg, "clock_servo",
CLOCK_SERVO_NTPSHM);
+ } else if (!strcasecmp(optarg, "sock")) {
+ config_set_int(cfg, "clock_servo",
+ CLOCK_SERVO_SOCK);