]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
ntp: add support for NTP over PTP
authorMiroslav Lichvar <mlichvar@redhat.com>
Wed, 18 Aug 2021 10:42:07 +0000 (12:42 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 19 Aug 2021 12:51:38 +0000 (14:51 +0200)
Allow NTP messages to be exchanged as a payload of PTP messages to
enable full hardware timestamping on NICs that can timestamp PTP packets
only. Implemented is the protocol described in this draft (version 00):

https://datatracker.ietf.org/doc/draft-mlichvar-ntp-over-ptp/

This is an experimental feature. It can be changed or removed in future.
The used PTP domain is 123 and the NTP TLV type is 0x2023 from the "do
not propagate" experimental range.

The ptpport directive enables NTP-over-PTP as a server and as a client
for all sources that have the port option set to the PTP port. The port
should be the PTP event port (319) to trigger timestamping in the
hardware.

The implementation is contained to ntp_io. It is transparent to
ntp_core.

conf.c
conf.h
doc/chrony.conf.adoc
ntp_io.c
ntp_io.h
ntp_io_linux.c
ptp.h [new file with mode: 0644]
socket.c

diff --git a/conf.c b/conf.c
index a525278971a61f2d868ae948bdb7b491f999f56e..93e93fc6ca1058ef8d2260d79159b638b40442c0 100644 (file)
--- a/conf.c
+++ b/conf.c
@@ -273,6 +273,9 @@ static int no_system_cert = 0;
 /* Array of CNF_HwTsInterface */
 static ARR_Instance hwts_interfaces;
 
+/* PTP event port (disabled by default) */
+static int ptp_port = 0;
+
 typedef struct {
   NTP_Source_Type type;
   int pool;
@@ -686,6 +689,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
     parse_source(p, command, 1);
   } else if (!strcasecmp(command, "port")) {
     parse_int(p, &ntp_port);
+  } else if (!strcasecmp(command, "ptpport")) {
+    parse_int(p, &ptp_port);
   } else if (!strcasecmp(command, "ratelimit")) {
     parse_ratelimit(p, &ntp_ratelimit_enabled, &ntp_ratelimit_interval,
                     &ntp_ratelimit_burst, &ntp_ratelimit_leak);
@@ -2561,6 +2566,14 @@ CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface)
 
 /* ================================================== */
 
+int
+CNF_GetPtpPort(void)
+{
+  return ptp_port;
+}
+
+/* ================================================== */
+
 char *
 CNF_GetNtsDumpDir(void)
 {
diff --git a/conf.h b/conf.h
index 437fa9690dba9450ba02605ea13d126755eb4356..f81f9aada99308ec2bebf8619b0e5f96480c0b1d 100644 (file)
--- a/conf.h
+++ b/conf.h
@@ -152,6 +152,8 @@ typedef struct {
 
 extern int CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface);
 
+extern int CNF_GetPtpPort(void);
+
 extern char *CNF_GetNtsDumpDir(void);
 extern char *CNF_GetNtsNtpServer(void);
 extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***keys);
index 647270beba523431fc69111b917b6adfa8a6e43a..f6d26c64d8282e99bb8baf4c05d4721413adf0b5 100644 (file)
@@ -2365,7 +2365,7 @@ timestamping, which can be verified with the *ethtool -T* command. The list of
 capabilities should include _SOF_TIMESTAMPING_RAW_HARDWARE_,
 _SOF_TIMESTAMPING_TX_HARDWARE_, and _SOF_TIMESTAMPING_RX_HARDWARE_. Receive
 filter _HWTSTAMP_FILTER_ALL_, or _HWTSTAMP_FILTER_NTP_ALL_, is necessary for
-timestamping of received packets. Timestamping of packets received from bridged
+timestamping of received NTP packets. Timestamping of packets received on bridged
 and bonded interfaces is supported on Linux 4.13 and newer. When *chronyd* is
 running, no other process (e.g. a PTP daemon) should be working with the NIC
 clock.
@@ -2421,12 +2421,14 @@ Enables timestamping of received PTP packets.
 _none_::::
 Disables timestamping of received packets.
 {blank}:::
-The most specific filter for timestamping NTP packets which is supported by the
-NIC is selected by default. Some NICs can timestamp only PTP packets, which
-limits the selection to the _none_ filter. Forcing timestamping of all packets
-with the _all_ filter when the NIC supports both _all_ and _ntp_ filters can be
-useful when packets are received from or on a non-standard UDP port (e.g.
-specified by the *port* directive).
+The most specific filter for timestamping of NTP packets supported by the NIC
+is selected by default. Some NICs can timestamp PTP packets only. By default,
+they will be configured with the _none_ filter and expected to provide hardware
+timestamps for transmitted packets only. Timestamping of PTP packets is useful
+with NTP-over-PTP enabled by the <<chrony.conf.adoc#ptpport,*ptpport*>>
+directive. Forcing timestamping of all packets with the _all_ filter could be
+useful if the NIC supported both the _all_ and _ntp_ filters, and it should
+timestamp both NTP and PTP packets, or NTP packets on a different UDP port.
 {blank}::
 +
 Examples of the directive are:
@@ -2519,6 +2521,32 @@ e.g.:
 pidfile /run/chronyd.pid
 ----
 
+[[ptpport]]*ptpport* _port_::
+The *ptpport* directive enables *chronyd* to send and receive NTP messages
+contained in PTP event messages (NTP-over-PTP) to enable hardware timestamping
+on NICs which cannot timestamp NTP packets, but can timestamp unicast PTP
+packets. The port recognized by the NICs is 319 (PTP event port). The default
+value is 0 (disabled).
++
+The NTP-over-PTP support is experimental. The protocol and configuration can
+change in future. It should be used only in local networks and expected to work
+only between servers and clients running the same version of *chronyd*.
++
+The PTP port will be open even if *chronyd* is not configured to operate as a
+server or client. The directive does not change the default protocol of
+specified NTP sources. Each NTP source that should use NTP-over-PTP needs to
+be specified with the *port* option set to the PTP port. To actually enable
+hardware timestamping on NICs which can timestamp PTP packets only, the
+*rxfilter* option of the *hwtimestamp* directive needs to be set to _ptp_.
++
+An example of client configuration is:
++
+----
+server foo.example.net port 319
+hwtimestamp * rxfilter ptp
+ptpport 319
+----
+
 [[sched_priority]]*sched_priority* _priority_::
 On Linux, FreeBSD, NetBSD, and Solaris, the *sched_priority* directive will
 select the SCHED_FIFO real-time scheduler at the specified priority (which must
index 1bf8ba4824de3efc5f80356b0b5fed949eb673e8..077a7cff16dcfdf5a168bcb08f617c367bb3bb66 100644 (file)
--- a/ntp_io.c
+++ b/ntp_io.c
 
 #include "sysincl.h"
 
+#include "memory.h"
 #include "ntp_io.h"
 #include "ntp_core.h"
 #include "ntp_sources.h"
+#include "ptp.h"
 #include "sched.h"
 #include "socket.h"
 #include "local.h"
@@ -70,6 +72,16 @@ static int permanent_server_sockets;
 /* Flag indicating the server IPv4 socket is bound to an address */
 static int bound_server_sock_fd4;
 
+/* PTP event port, or 0 if disabled */
+static int ptp_port;
+
+/* Shared server/client sockets for NTP-over-PTP */
+static int ptp_sock_fd4;
+static int ptp_sock_fd6;
+
+/* Buffer for transmitted NTP-over-PTP messages */
+static PTP_NtpMessage *ptp_message;
+
 /* Flag indicating that we have been initialised */
 static int initialised=0;
 
@@ -221,6 +233,17 @@ NIO_Initialise(void)
        client_sock_fd4 == INVALID_SOCK_FD && client_sock_fd6 == INVALID_SOCK_FD)) {
     LOG_FATAL("Could not open NTP sockets");
   }
+
+  ptp_port = CNF_GetPtpPort();
+  ptp_sock_fd4 = INVALID_SOCK_FD;
+  ptp_sock_fd6 = INVALID_SOCK_FD;
+  ptp_message = NULL;
+
+  if (ptp_port > 0) {
+    ptp_sock_fd4 = open_socket(IPADDR_INET4, ptp_port, 0, NULL);
+    ptp_sock_fd6 = open_socket(IPADDR_INET6, ptp_port, 0, NULL);
+    ptp_message = MallocNew(PTP_NtpMessage);
+  }
 }
 
 /* ================================================== */
@@ -238,6 +261,11 @@ NIO_Finalise(void)
   close_socket(server_sock_fd6);
   server_sock_fd6 = client_sock_fd6 = INVALID_SOCK_FD;
 
+  close_socket(ptp_sock_fd4);
+  close_socket(ptp_sock_fd6);
+  ptp_sock_fd4 = ptp_sock_fd6 = INVALID_SOCK_FD;
+  Free(ptp_message);
+
 #ifdef HAVE_LINUX_TIMESTAMPING
   NIO_Linux_Finalise();
 #endif
@@ -250,17 +278,21 @@ NIO_Finalise(void)
 int
 NIO_OpenClientSocket(NTP_Remote_Address *remote_addr)
 {
-  if (separate_client_sockets) {
-    return open_separate_client_socket(remote_addr);
-  } else {
-    switch (remote_addr->ip_addr.family) {
-      case IPADDR_INET4:
-        return client_sock_fd4;
-      case IPADDR_INET6:
-        return client_sock_fd6;
-      default:
-        return INVALID_SOCK_FD;
-    }
+  switch (remote_addr->ip_addr.family) {
+    case IPADDR_INET4:
+      if (ptp_port > 0 && remote_addr->port == ptp_port)
+        return ptp_sock_fd4;
+      if (separate_client_sockets)
+        return open_separate_client_socket(remote_addr);
+      return client_sock_fd4;
+    case IPADDR_INET6:
+      if (ptp_port > 0 && remote_addr->port == ptp_port)
+        return ptp_sock_fd6;
+      if (separate_client_sockets)
+        return open_separate_client_socket(remote_addr);
+      return client_sock_fd6;
+    default:
+      return INVALID_SOCK_FD;
   }
 }
 
@@ -271,6 +303,8 @@ NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
 {
   switch (remote_addr->ip_addr.family) {
     case IPADDR_INET4:
+      if (ptp_port > 0 && remote_addr->port == ptp_port)
+        return ptp_sock_fd4;
       if (permanent_server_sockets)
         return server_sock_fd4;
       if (server_sock_fd4 == INVALID_SOCK_FD)
@@ -279,6 +313,8 @@ NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
         server_sock_ref4++;
       return server_sock_fd4;
     case IPADDR_INET6:
+      if (ptp_port > 0 && remote_addr->port == ptp_port)
+        return ptp_sock_fd6;
       if (permanent_server_sockets)
         return server_sock_fd6;
       if (server_sock_fd6 == INVALID_SOCK_FD)
@@ -293,9 +329,20 @@ NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
 
 /* ================================================== */
 
+static int
+is_ptp_socket(int sock_fd)
+{
+  return ptp_port > 0 && (sock_fd == ptp_sock_fd4 || sock_fd == ptp_sock_fd6);
+}
+
+/* ================================================== */
+
 void
 NIO_CloseClientSocket(int sock_fd)
 {
+  if (is_ptp_socket(sock_fd))
+    return;
+
   if (separate_client_sockets)
     close_socket(sock_fd);
 }
@@ -305,7 +352,7 @@ NIO_CloseClientSocket(int sock_fd)
 void
 NIO_CloseServerSocket(int sock_fd)
 {
-  if (permanent_server_sockets || sock_fd == INVALID_SOCK_FD)
+  if (permanent_server_sockets || sock_fd == INVALID_SOCK_FD || is_ptp_socket(sock_fd))
     return;
 
   if (sock_fd == server_sock_fd4) {
@@ -329,7 +376,7 @@ int
 NIO_IsServerSocket(int sock_fd)
 {
   return sock_fd != INVALID_SOCK_FD &&
-    (sock_fd == server_sock_fd4 || sock_fd == server_sock_fd6);
+    (sock_fd == server_sock_fd4 || sock_fd == server_sock_fd6 || is_ptp_socket(sock_fd));
 }
 
 /* ================================================== */
@@ -337,7 +384,8 @@ NIO_IsServerSocket(int sock_fd)
 int
 NIO_IsServerSocketOpen(void)
 {
-  return server_sock_fd4 != INVALID_SOCK_FD || server_sock_fd6 != INVALID_SOCK_FD;
+  return server_sock_fd4 != INVALID_SOCK_FD || server_sock_fd6 != INVALID_SOCK_FD ||
+    ptp_sock_fd4 != INVALID_SOCK_FD || ptp_sock_fd6 != INVALID_SOCK_FD;
 }
 
 /* ================================================== */
@@ -392,6 +440,9 @@ process_message(SCK_Message *message, int sock_fd, int event)
     DEBUG_LOG("Updated RX timestamp delay=%.9f tss=%u",
               UTI_DiffTimespecsToDouble(&sched_ts, &local_ts.ts), local_ts.source);
 
+  if (!NIO_UnwrapMessage(message, sock_fd))
+    return;
+
   /* Just ignore the packet if it's not of a recognized length */
   if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet)) {
     DEBUG_LOG("Unexpected length");
@@ -430,6 +481,78 @@ read_from_socket(int sock_fd, int event, void *anything)
     process_message(&messages[i], sock_fd, event);
 }
 
+/* ================================================== */
+
+int
+NIO_UnwrapMessage(SCK_Message *message, int sock_fd)
+{
+  PTP_NtpMessage *msg;
+
+  if (!is_ptp_socket(sock_fd))
+    return 1;
+
+  if (message->length <= PTP_NTP_PREFIX_LENGTH) {
+    DEBUG_LOG("Unexpected length");
+    return 0;
+  }
+
+  msg = message->data;
+
+  if (msg->header.type != PTP_TYPE_DELAY_REQ || msg->header.version != PTP_VERSION ||
+      ntohs(msg->header.length) != message->length ||
+      msg->header.domain != PTP_DOMAIN_NTP ||
+      ntohs(msg->header.flags) != PTP_FLAG_UNICAST ||
+      ntohs(msg->tlv_header.type) != PTP_TLV_NTP ||
+      ntohs(msg->tlv_header.length) != message->length - PTP_NTP_PREFIX_LENGTH) {
+    DEBUG_LOG("Unexpected PTP message");
+    return 0;
+  }
+
+  message->data = (char *)message->data + PTP_NTP_PREFIX_LENGTH;
+  message->length -= PTP_NTP_PREFIX_LENGTH;
+
+  DEBUG_LOG("Unwrapped PTP->NTP len=%d", message->length);
+
+  return 1;
+}
+
+/* ================================================== */
+
+static int
+wrap_message(SCK_Message *message, int sock_fd)
+{
+  assert(PTP_NTP_PREFIX_LENGTH == 48);
+
+  if (!is_ptp_socket(sock_fd))
+    return 1;
+
+  if (!ptp_message)
+    return 0;
+
+  if (message->length < NTP_HEADER_LENGTH ||
+      message->length + PTP_NTP_PREFIX_LENGTH > sizeof (*ptp_message)) {
+    DEBUG_LOG("Unexpected length");
+    return 0;
+  }
+
+  memset(ptp_message, 0, PTP_NTP_PREFIX_LENGTH);
+  ptp_message->header.type = PTP_TYPE_DELAY_REQ;
+  ptp_message->header.version = PTP_VERSION;
+  ptp_message->header.length = htons(PTP_NTP_PREFIX_LENGTH + message->length);
+  ptp_message->header.domain = PTP_DOMAIN_NTP;
+  ptp_message->header.flags = htons(PTP_FLAG_UNICAST);
+  ptp_message->tlv_header.type = htons(PTP_TLV_NTP);
+  ptp_message->tlv_header.length = htons(message->length);
+  memcpy((char *)ptp_message + PTP_NTP_PREFIX_LENGTH, message->data, message->length);
+
+  message->data = ptp_message;
+  message->length += PTP_NTP_PREFIX_LENGTH;
+
+  DEBUG_LOG("Wrapped NTP->PTP len=%d", message->length - PTP_NTP_PREFIX_LENGTH);
+
+  return 1;
+}
+
 /* ================================================== */
 /* Send a packet to remote address from local address */
 
@@ -451,6 +574,9 @@ NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,
   message.data = packet;
   message.length = length;
 
+  if (!wrap_message(&message, local_addr->sock_fd))
+    return 0;
+
   /* Specify remote address if the socket is not connected */
   if (NIO_IsServerSocket(local_addr->sock_fd) || !separate_client_sockets) {
     message.remote_addr.ip.ip_addr = remote_addr->ip_addr;
index 9787ca19df32c119f05e17a2d6dee22f61e017b3..19ffeed661039d8aba5b10ef707b495da2ab977d 100644 (file)
--- a/ntp_io.h
+++ b/ntp_io.h
@@ -31,6 +31,7 @@
 
 #include "ntp.h"
 #include "addressing.h"
+#include "socket.h"
 
 /* Function to initialise the module. */
 extern void NIO_Initialise(void);
@@ -59,6 +60,9 @@ extern int NIO_IsServerSocketOpen(void);
 /* Function to check if client packets can be sent to a server */
 extern int NIO_IsServerConnectable(NTP_Remote_Address *remote_addr);
 
+/* Function to unwrap an NTP message from non-native transport (e.g. PTP) */
+extern int NIO_UnwrapMessage(SCK_Message *message, int sock_fd);
+
 /* Function to transmit a packet */
 extern int NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,
                           NTP_Local_Address *local_addr, int length, int process_tx);
index a458ded43b9824ee9db07fb08dc9b57266d5b646..ce19b86b6da4f01255bbf5df6c7bacb618d1402a 100644 (file)
@@ -784,7 +784,10 @@ NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
     return 1;
   }
 
-  if (message->length < NTP_HEADER_LENGTH)
+  if (!NIO_UnwrapMessage(message, local_addr->sock_fd))
+    return 1;
+
+  if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet))
     return 1;
 
   NSR_ProcessTx(&message->remote_addr.ip, local_addr, local_ts, message->data, message->length);
diff --git a/ptp.h b/ptp.h
new file mode 100644 (file)
index 0000000..7a93590
--- /dev/null
+++ b/ptp.h
@@ -0,0 +1,64 @@
+/*
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar  2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ **********************************************************************
+
+  =======================================================================
+
+  This is the header file for the Precision Time Protocol (PTP).
+
+  */
+#ifndef GOT_PTP_H
+#define GOT_PTP_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+
+#define PTP_VERSION 2
+#define PTP_TYPE_DELAY_REQ 1
+#define PTP_DOMAIN_NTP 123
+#define PTP_FLAG_UNICAST (1 << (2 + 8))
+#define PTP_TLV_NTP 0x2023
+
+typedef struct {
+  uint8_t type;
+  uint8_t version;
+  uint16_t length;
+  uint8_t domain;
+  uint8_t min_sdoid;
+  uint16_t flags;
+  uint8_t rest[26];
+} PTP_Header;
+
+typedef struct {
+  uint16_t type;
+  uint16_t length;
+} PTP_TlvHeader;
+
+typedef struct {
+  PTP_Header header;
+  uint8_t origin_ts[10];
+  PTP_TlvHeader tlv_header;
+  NTP_Packet ntp_msg;
+} PTP_NtpMessage;
+
+#define PTP_NTP_PREFIX_LENGTH (int)offsetof(PTP_NtpMessage, ntp_msg)
+
+#endif
index 7a9c85e215e4a64ce003cfa17275e207d6c6a857..a6010d80a5d66d05c33fc71d70a10cc7e2080fa0 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -40,6 +40,7 @@
 #include "array.h"
 #include "logging.h"
 #include "privops.h"
+#include "ptp.h"
 #include "util.h"
 
 #define INVALID_SOCK_FD (-4)
@@ -60,6 +61,7 @@ struct Message {
   /* Buffer of sufficient length for all expected messages */
   union {
     NTP_Packet ntp_msg;
+    PTP_NtpMessage ptp_msg;
     CMD_Request cmd_request;
     CMD_Reply cmd_reply;
   } msg_buf;