On 11/10/2022 7:19 AM, Miroslav Lichvar wrote:
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);

How specific is this to chronyd? Would it make sense to call this chronysock instead of just sock?

The implementation seems fine but its using an interface that was defined by chrony. I suppose another application could implement the same interface though..


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

Reply via email to