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.
_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:
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
#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"
/* 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;
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);
+ }
}
/* ================================================== */
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
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;
}
}
{
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)
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)
/* ================================================== */
+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);
}
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) {
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));
}
/* ================================================== */
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;
}
/* ================================================== */
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");
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 */
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;
--- /dev/null
+/*
+ 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