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);
                        } else {
                                fprintf(stderr,
                                        "invalid servo name %s\n", optarg);
diff --git a/ptp4l.8 b/ptp4l.8
index 280d9e1..dac6d71 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -527,10 +527,10 @@ The default is 0 (disabled).
 .B clock_servo
 The servo which is used to synchronize the local clock. Valid values
 are "pi" for a PI controller, "linreg" for an adaptive controller
-using linear regression, "ntpshm" for the NTP SHM reference clock to
-allow another process to synchronize the local clock, and "nullf"
-for a servo that
-always dials frequency offset zero (for use in SyncE nodes).
+using linear regression, "ntpshm" and "sock" for the NTP SHM and chrony SOCK
+reference clocks respectively to allow another process to synchronize the local
+clock, and "nullf" for a servo that always dials frequency offset zero (for use
+in SyncE nodes).
 The default is "pi."
 .TP
 .B clock_type
@@ -628,6 +628,10 @@ the clock before the delay is measured using the E2E or 
P2P delay mechanism. If
 set to 0, the clock will not be updated until the delay is measured.
 The default is 0.
 .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/servo.c b/servo.c
index 46042aa..550a98e 100644
--- a/servo.c
+++ b/servo.c
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 
 #include "config.h"
+#include "chronysock.h"
 #include "linreg.h"
 #include "ntpshm.h"
 #include "nullf.h"
@@ -51,6 +52,9 @@ struct servo *servo_create(struct config *cfg, enum 
servo_type type,
        case CLOCK_SERVO_NULLF:
                servo = nullf_servo_create();
                break;
+       case CLOCK_SERVO_SOCK:
+               servo = chronysock_servo_create(cfg);
+               break;
        default:
                return NULL;
        }
diff --git a/servo.h b/servo.h
index 6c30d33..75260ec 100644
--- a/servo.h
+++ b/servo.h
@@ -35,6 +35,7 @@ enum servo_type {
        CLOCK_SERVO_LINREG,
        CLOCK_SERVO_NTPSHM,
        CLOCK_SERVO_NULLF,
+       CLOCK_SERVO_SOCK,
 };
 
 /**
-- 
2.37.3



_______________________________________________
Linuxptp-devel mailing list
Linuxptp-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxptp-devel

Reply via email to