summaryrefslogtreecommitdiff
path: root/ntp_io_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'ntp_io_linux.c')
-rw-r--r--ntp_io_linux.c157
1 files changed, 155 insertions, 2 deletions
diff --git a/ntp_io_linux.c b/ntp_io_linux.c
index 00caed0..df944d7 100644
--- a/ntp_io_linux.c
+++ b/ntp_io_linux.c
@@ -94,6 +94,27 @@ static int ts_tx_flags;
/* Flag indicating the socket options can't be changed in control messages */
static int permanent_ts_options;
+/* When sending client requests to a close and fast server, it is possible that
+ a response will be received before the HW transmit timestamp of the request
+ itself. To avoid processing of the response without the HW timestamp, we
+ monitor events returned by select() and suspend reading of packets from the
+ receive queue for up to 200 microseconds. As the requests are normally
+ separated by at least 200 milliseconds, it is sufficient to monitor and
+ suspend one socket at a time. */
+static int monitored_socket;
+static int suspended_socket;
+static SCH_TimeoutID resume_timeout_id;
+
+#define RESUME_TIMEOUT 200.0e-6
+
+/* Unbound socket keeping the kernel RX timestamping permanently enabled
+ in order to avoid a race condition between receiving a server response
+ and the kernel actually starting to timestamp received packets after
+ enabling the timestamping and sending a request */
+static int dummy_rxts_socket;
+
+#define INVALID_SOCK_FD -3
+
/* ================================================== */
static int
@@ -252,7 +273,7 @@ update_interface_speed(struct Interface *iface)
{
struct ethtool_cmd cmd;
struct ifreq req;
- int sock_fd;
+ int sock_fd, link_speed;
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0)
@@ -273,7 +294,12 @@ update_interface_speed(struct Interface *iface)
close(sock_fd);
- iface->link_speed = ethtool_cmd_speed(&cmd);
+ link_speed = ethtool_cmd_speed(&cmd);
+
+ if (iface->link_speed != link_speed) {
+ iface->link_speed = link_speed;
+ DEBUG_LOG("Updated speed of %s to %d Mb/s", iface->name, link_speed);
+ }
}
/* ================================================== */
@@ -301,6 +327,29 @@ check_timestamping_option(int option)
/* ================================================== */
+static int
+open_dummy_socket(void)
+{
+ int sock_fd, events = 0;
+
+ if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0
+#ifdef FEAT_IPV6
+ && (sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0
+#endif
+ )
+ return INVALID_SOCK_FD;
+
+ if (!NIO_Linux_SetTimestampSocketOptions(sock_fd, 1, &events)) {
+ close(sock_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ UTI_FdSetCloexec(sock_fd);
+ return sock_fd;
+}
+
+/* ================================================== */
+
void
NIO_Linux_Initialise(void)
{
@@ -350,6 +399,10 @@ NIO_Linux_Initialise(void)
/* Kernels before 4.7 ignore timestamping flags set in control messages */
permanent_ts_options = !SYS_Linux_CheckKernelVersion(4, 7);
+
+ monitored_socket = INVALID_SOCK_FD;
+ suspended_socket = INVALID_SOCK_FD;
+ dummy_rxts_socket = INVALID_SOCK_FD;
}
/* ================================================== */
@@ -360,6 +413,9 @@ NIO_Linux_Finalise(void)
struct Interface *iface;
unsigned int i;
+ if (dummy_rxts_socket != INVALID_SOCK_FD)
+ close(dummy_rxts_socket);
+
for (i = 0; i < ARR_GetSize(interfaces); i++) {
iface = ARR_GetElement(interfaces, i);
HCL_DestroyInstance(iface->clock);
@@ -406,6 +462,73 @@ NIO_Linux_SetTimestampSocketOptions(int sock_fd, int client_only, int *events)
/* ================================================== */
+static void
+resume_socket(int sock_fd)
+{
+ if (monitored_socket == sock_fd)
+ monitored_socket = INVALID_SOCK_FD;
+
+ if (sock_fd == INVALID_SOCK_FD || sock_fd != suspended_socket)
+ return;
+
+ suspended_socket = INVALID_SOCK_FD;
+
+ SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_INPUT, 1);
+
+ DEBUG_LOG("Resumed RX processing %s timeout fd=%d",
+ resume_timeout_id ? "before" : "on", sock_fd);
+
+ if (resume_timeout_id) {
+ SCH_RemoveTimeout(resume_timeout_id);
+ resume_timeout_id = 0;
+ }
+}
+
+/* ================================================== */
+
+static void
+resume_timeout(void *arg)
+{
+ resume_timeout_id = 0;
+ resume_socket(suspended_socket);
+}
+
+/* ================================================== */
+
+static void
+suspend_socket(int sock_fd)
+{
+ resume_socket(suspended_socket);
+
+ suspended_socket = sock_fd;
+
+ SCH_SetFileHandlerEvent(suspended_socket, SCH_FILE_INPUT, 0);
+ resume_timeout_id = SCH_AddTimeoutByDelay(RESUME_TIMEOUT, resume_timeout, NULL);
+
+ DEBUG_LOG("Suspended RX processing fd=%d", sock_fd);
+}
+
+/* ================================================== */
+
+int
+NIO_Linux_ProcessEvent(int sock_fd, int event)
+{
+ if (sock_fd != monitored_socket)
+ return 0;
+
+ if (event == SCH_FILE_INPUT) {
+ suspend_socket(monitored_socket);
+ monitored_socket = INVALID_SOCK_FD;
+
+ /* Don't process the message yet */
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ================================================== */
+
static struct Interface *
get_interface(int if_index)
{
@@ -614,6 +737,11 @@ NIO_Linux_ProcessMessage(NTP_Remote_Address *remote_addr, NTP_Local_Address *loc
} else {
DEBUG_LOG("HW clock not found for interface %d", ts_if_index);
}
+
+ /* If a HW transmit timestamp was received, resume processing
+ of non-error messages on this socket */
+ if (is_tx)
+ resume_socket(local_addr->sock_fd);
}
if (local_ts->source == NTP_TS_DAEMON && !UTI_IsZeroTimespec(&ts3.ts[0]) &&
@@ -638,6 +766,14 @@ NIO_Linux_ProcessMessage(NTP_Remote_Address *remote_addr, NTP_Local_Address *loc
}
}
+ /* If the kernel is slow with enabling RX timestamping, open a dummy
+ socket to keep the kernel RX timestamping permanently enabled */
+ if (!is_tx && local_ts->source == NTP_TS_DAEMON && ts_flags) {
+ DEBUG_LOG("Missing kernel RX timestamp");
+ if (dummy_rxts_socket == INVALID_SOCK_FD)
+ dummy_rxts_socket = open_dummy_socket();
+ }
+
/* Return the message if it's not received from the error queue */
if (!is_tx)
return 0;
@@ -682,6 +818,15 @@ NIO_Linux_RequestTxTimestamp(struct msghdr *msg, int cmsglen, int sock_fd)
{
struct cmsghdr *cmsg;
+ if (!ts_flags)
+ return cmsglen;
+
+ /* If a HW transmit timestamp is requested on a client socket, monitor
+ events on the socket in order to avoid processing of a fast response
+ without the HW timestamp of the request */
+ if (ts_tx_flags & SOF_TIMESTAMPING_TX_HARDWARE && !NIO_IsServerSocket(sock_fd))
+ monitored_socket = sock_fd;
+
/* Check if TX timestamping is disabled on this socket */
if (permanent_ts_options || !NIO_IsServerSocket(sock_fd))
return cmsglen;
@@ -701,3 +846,11 @@ NIO_Linux_RequestTxTimestamp(struct msghdr *msg, int cmsglen, int sock_fd)
return cmsglen;
}
+
+/* ================================================== */
+
+void
+NIO_Linux_NotifySocketClosing(int sock_fd)
+{
+ resume_socket(sock_fd);
+}