ntp: add alternative method of retrieving transmitted messages

When chronyd gets a kernel or hardware transmit timestamp after sending
an NTP message to a server, peer, or client (using interleaved mode), it
needs the address and content of the message to be able to correctly
assign the timestamp to the server, peer, or client. The timestamps are
processed asynchronously. The kernel provides with each timestamp the
data-link frame that was timestamped, but chronyd can extract the
necessary data only from plain IPv4 and IPv6 packets in Ethernet frames,
possibly including VLAN tags. If the NTP packets are transmitted by a
non-Ethernet device, or they are encapsulated in another layer (e.g. a
WireGuard tunnel), chronyd is not able to extract the data and use the
kernel or hardware transmit timestamps, having to fall back to less
accurate daemon timestamps.

Add an alternative method using transmit IDs assigned to each message
(supported since Linux 6.13), which are provided by the kernel with the
timestamp in the error queue, and map them to messages, addresses and
ports saved in a ring buffer, whose size can be configured by the new
maxtxbuffers directive.

Fow now, set the default maxtxbuffers to 0 (disabled). If set to a
non-zero value, allocate the ring buffer to the maximum size on start.
As a future improvement, it could be allocated only when the extraction
of the UDP payload fails, or the extracted message is not the expected
NTP message. The size could grow dynamically when a transmit ID is
missed.
This commit is contained in:
Miroslav Lichvar
2025-11-13 16:02:17 +01:00
parent 4507359952
commit e5abf3ad2b
7 changed files with 176 additions and 6 deletions

View File

@@ -44,6 +44,7 @@
#include "ntp_io.h"
#include "ntp_io_linux.h"
#include "ntp_sources.h"
#include "ptp.h"
#include "sched.h"
#include "socket.h"
#include "sys_linux.h"
@@ -94,6 +95,24 @@ static int permanent_ts_options;
enabling the timestamping and sending a request */
static int dummy_rxts_socket;
struct SavedTxMessage {
uint32_t tx_id;
int length;
NTP_Remote_Address remote_addr;
union {
NTP_Packet ntp_msg;
PTP_NtpMessage ptp_msg;
} message;
};
/* Ring buffer of transmitted messages to provide missing data needed for
processing of transmit timestamps */
static ARR_Instance saved_tx_messages;
static uint32_t last_saved_tx_id;
/* Transmit IDs start at a non-zero value to detect missing kernel support */
#define MIN_SAVED_TX_ID 0x1000
#define INVALID_SOCK_FD -3
/* ================================================== */
@@ -367,8 +386,8 @@ void
NIO_Linux_Initialise(void)
{
CNF_HwTsInterface *conf_iface;
int hwts, tx_buffers;
unsigned int i;
int hwts;
interfaces = ARR_CreateInstance(sizeof (struct Interface));
@@ -407,6 +426,32 @@ NIO_Linux_Initialise(void)
#endif
}
saved_tx_messages = NULL;
last_saved_tx_id = 0;
tx_buffers = CNF_GetMaxTxBuffers();
if (tx_buffers > 0) {
#if defined(HAVE_LINUX_TIMESTAMPING_OPT_ID) && defined(SCM_TS_OPT_ID)
/* Enable identification of packets looped back to the error queue using
a 32-bit integer and mapping of the IDs to saved messages to be able to
process TX timestamps of packets going out over tunnels or non-Ethernet
interfaces, where extract_udp_data() fails, or does not extract the
NTP message. If the SCM_TS_OPT_ID control message (setting the ID for
given packet) is not supported by the kernel, a zero ID will be received
and this functionality disabled in get_saved_tx_message(). */
if (check_timestamping_option(SOF_TIMESTAMPING_OPT_ID)) {
ts_tx_flags |= SOF_TIMESTAMPING_OPT_ID;
saved_tx_messages = ARR_CreateInstance(sizeof (struct SavedTxMessage));
ARR_SetSize(saved_tx_messages, tx_buffers);
memset(ARR_GetElements(saved_tx_messages), 0,
tx_buffers * sizeof (struct SavedTxMessage));
} else
#endif
LOG(LOGS_WARN, "Transmit ID not supported");
}
/* Enable IP_PKTINFO in messages looped back to the error queue */
ts_flags |= SOF_TIMESTAMPING_OPT_CMSG;
@@ -424,6 +469,9 @@ NIO_Linux_Finalise(void)
struct Interface *iface;
unsigned int i;
if (saved_tx_messages)
ARR_DestroyInstance(saved_tx_messages);
if (dummy_rxts_socket != INVALID_SOCK_FD)
SCK_CloseSocket(dummy_rxts_socket);
@@ -629,6 +677,73 @@ process_sw_timestamp(struct timespec *sw_ts, NTP_Local_Timestamp *local_ts)
local_ts->source = NTP_TS_KERNEL;
}
/* ================================================== */
static void
save_tx_message(SCK_Message *message, NTP_Remote_Address *remote_addr)
{
struct SavedTxMessage *saved_msg;
if (!saved_tx_messages || ARR_GetSize(saved_tx_messages) == 0 ||
message->length > sizeof (saved_msg->message))
return;
last_saved_tx_id++;
if (last_saved_tx_id < MIN_SAVED_TX_ID)
last_saved_tx_id = MIN_SAVED_TX_ID;
saved_msg = ARR_GetElement(saved_tx_messages,
last_saved_tx_id % ARR_GetSize(saved_tx_messages));
saved_msg->tx_id = last_saved_tx_id;
saved_msg->length = message->length;
saved_msg->remote_addr = *remote_addr;
memcpy(&saved_msg->message, message->data, message->length);
message->timestamp.tx_id = saved_msg->tx_id;
}
/* ================================================== */
static int
get_saved_tx_message(SCK_Message *message)
{
#ifdef HAVE_LINUX_TIMESTAMPING_OPT_ID
struct SavedTxMessage *saved_msg;
if (!saved_tx_messages)
return 0;
if (message->timestamp.tx_id < MIN_SAVED_TX_ID) {
LOG(LOGS_WARN, "Transmit ID not supported");
ARR_DestroyInstance(saved_tx_messages);
saved_tx_messages = NULL;
ts_tx_flags &= ~SOF_TIMESTAMPING_OPT_ID;
return 0;
}
saved_msg = ARR_GetElement(saved_tx_messages,
message->timestamp.tx_id % ARR_GetSize(saved_tx_messages));
if (message->timestamp.tx_id != saved_msg->tx_id) {
static int warned = 0;
if (!warned) {
LOG(LOGS_WARN, "maxtxbuffers too small");
warned = 1;
}
return 0;
}
message->data = &saved_msg->message;
message->length = saved_msg->length;
message->remote_addr.ip = saved_msg->remote_addr;
return 1;
#else
return 0;
#endif
}
/* ================================================== */
/* Extract UDP data from a layer 2 message. Supported is Ethernet
with optional VLAN tags. */
@@ -771,7 +886,11 @@ NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
currently doesn't seem to be a better way to get them both. */
l2_length = message->length;
if (extract_udp_data(message)) {
if (get_saved_tx_message(message)) {
DEBUG_LOG("Found saved message for %s fd=%d len=%d",
UTI_IPSockAddrToString(&message->remote_addr.ip),
local_addr->sock_fd, message->length);
} else if (extract_udp_data(message)) {
DEBUG_LOG("Extracted message for %s fd=%d len=%d",
UTI_IPSockAddrToString(&message->remote_addr.ip),
local_addr->sock_fd, message->length);
@@ -808,11 +927,14 @@ NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
/* ================================================== */
void
NIO_Linux_RequestTxTimestamp(SCK_Message *message, int sock_fd)
NIO_Linux_RequestTxTimestamp(SCK_Message *message, int sock_fd,
NTP_Remote_Address *remote_addr)
{
if (!ts_flags)
return;
save_tx_message(message, remote_addr);
/* Check if TX timestamping is disabled on this socket */
if (permanent_ts_options || !NIO_IsServerSocket(sock_fd))
return;