ntp: add support for NTP over PTP

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.
This commit is contained in:
Miroslav Lichvar 2021-08-18 12:42:07 +02:00
parent 2f1d5d9255
commit be3158c4e5
8 changed files with 264 additions and 22 deletions

13
conf.c
View file

@ -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)
{

2
conf.h
View file

@ -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);

View 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

154
ntp_io.c
View file

@ -30,9 +30,11 @@
#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;

View file

@ -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);

View 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);

64
ptp.h Normal file
View file

@ -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

View file

@ -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;