diff options
Diffstat (limited to 'ntp_io.c')
-rw-r--r-- | ntp_io.c | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/ntp_io.c b/ntp_io.c new file mode 100644 index 0000000..ec2fd7b --- /dev/null +++ b/ntp_io.c @@ -0,0 +1,634 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Timo Teras 2009 + * Copyright (C) Miroslav Lichvar 2009, 2013-2016, 2018-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 file deals with the IO aspects of reading and writing NTP packets + */ + +#include "config.h" + +#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" +#include "logging.h" +#include "conf.h" +#include "privops.h" +#include "util.h" + +#ifdef HAVE_LINUX_TIMESTAMPING +#include "ntp_io_linux.h" +#endif + +#define INVALID_SOCK_FD -1 + +/* The server/peer and client sockets for IPv4 and IPv6 */ +static int server_sock_fd4; +static int server_sock_fd6; +static int client_sock_fd4; +static int client_sock_fd6; + +/* Reference counters for server sockets to keep them open only when needed */ +static int server_sock_ref4; +static int server_sock_ref6; + +/* Flag indicating we create a new connected client socket for each + server instead of sharing client_sock_fd4 and client_sock_fd6 */ +static int separate_client_sockets; + +/* Flag indicating the server sockets are not created dynamically when needed, + either to have a socket for client requests when separate client sockets + are disabled and client port is equal to server port, or the server port is + disabled */ +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; + +/* ================================================== */ + +/* Forward prototypes */ +static void read_from_socket(int sock_fd, int event, void *anything); + +/* ================================================== */ + +static int +open_socket(int family, int local_port, int client_only, IPSockAddr *remote_addr) +{ + int sock_fd, sock_flags, dscp, events = SCH_FILE_INPUT; + IPSockAddr local_addr; + char *iface; + + if (!SCK_IsIpFamilyEnabled(family)) + return INVALID_SOCK_FD; + + if (!client_only) { + CNF_GetBindAddress(family, &local_addr.ip_addr); + iface = CNF_GetBindNtpInterface(); + } else { + CNF_GetBindAcquisitionAddress(family, &local_addr.ip_addr); + iface = CNF_GetBindAcquisitionInterface(); + } + + local_addr.port = local_port; + + sock_flags = SCK_FLAG_RX_DEST_ADDR | SCK_FLAG_PRIV_BIND; + if (!client_only) + sock_flags |= SCK_FLAG_BROADCAST; + + sock_fd = SCK_OpenUdpSocket(remote_addr, &local_addr, iface, sock_flags); + if (sock_fd < 0) { + if (!client_only) + LOG(LOGS_ERR, "Could not open NTP socket on %s", UTI_IPSockAddrToString(&local_addr)); + return INVALID_SOCK_FD; + } + + dscp = CNF_GetNtpDscp(); + if (dscp > 0 && dscp < 64) { +#ifdef IP_TOS + if (family == IPADDR_INET4) + if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_TOS, dscp << 2)) + ; +#endif +#if defined(FEAT_IPV6) && defined(IPV6_TCLASS) + if (family == IPADDR_INET6) + if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, dscp << 2)) + ; +#endif + } + + if (!client_only && family == IPADDR_INET4 && local_addr.port > 0) + bound_server_sock_fd4 = local_addr.ip_addr.addr.in4 != INADDR_ANY; + + /* Enable kernel/HW timestamping of packets */ +#ifdef HAVE_LINUX_TIMESTAMPING + if (!NIO_Linux_SetTimestampSocketOptions(sock_fd, client_only, &events)) +#endif + if (!SCK_EnableKernelRxTimestamping(sock_fd)) + ; + + /* Register handler for read and possibly exception events on the socket */ + SCH_AddFileHandler(sock_fd, events, read_from_socket, NULL); + + return sock_fd; +} + +/* ================================================== */ + +static int +open_separate_client_socket(IPSockAddr *remote_addr) +{ + return open_socket(remote_addr->ip_addr.family, 0, 1, remote_addr); +} + +/* ================================================== */ + +static void +close_socket(int sock_fd) +{ + if (sock_fd == INVALID_SOCK_FD) + return; + + SCH_RemoveFileHandler(sock_fd); + SCK_CloseSocket(sock_fd); +} + +/* ================================================== */ + +void +NIO_Initialise(void) +{ + int server_port, client_port; + + assert(!initialised); + initialised = 1; + +#ifdef PRIVOPS_BINDSOCKET + SCK_SetPrivBind(PRV_BindSocket); +#endif + +#ifdef HAVE_LINUX_TIMESTAMPING + NIO_Linux_Initialise(); +#else + if (1) { + CNF_HwTsInterface *conf_iface; + if (CNF_GetHwTsInterface(0, &conf_iface)) + LOG_FATAL("HW timestamping not supported"); + } +#endif + + server_port = CNF_GetNTPPort(); + client_port = CNF_GetAcquisitionPort(); + + /* Use separate connected sockets if client port is negative */ + separate_client_sockets = client_port < 0; + if (client_port < 0) + client_port = 0; + + permanent_server_sockets = !server_port || (!separate_client_sockets && + client_port == server_port); + + server_sock_fd4 = INVALID_SOCK_FD; + server_sock_fd6 = INVALID_SOCK_FD; + client_sock_fd4 = INVALID_SOCK_FD; + client_sock_fd6 = INVALID_SOCK_FD; + server_sock_ref4 = 0; + server_sock_ref6 = 0; + + if (permanent_server_sockets && server_port) { + server_sock_fd4 = open_socket(IPADDR_INET4, server_port, 0, NULL); + server_sock_fd6 = open_socket(IPADDR_INET6, server_port, 0, NULL); + } + + if (!separate_client_sockets) { + if (client_port != server_port || !server_port) { + client_sock_fd4 = open_socket(IPADDR_INET4, client_port, 1, NULL); + client_sock_fd6 = open_socket(IPADDR_INET6, client_port, 1, NULL); + } else { + client_sock_fd4 = server_sock_fd4; + client_sock_fd6 = server_sock_fd6; + } + } + + if ((server_port && permanent_server_sockets && + server_sock_fd4 == INVALID_SOCK_FD && server_sock_fd6 == INVALID_SOCK_FD) || + (!separate_client_sockets && + 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); + } +} + +/* ================================================== */ + +void +NIO_Finalise(void) +{ + if (server_sock_fd4 != client_sock_fd4) + close_socket(client_sock_fd4); + close_socket(server_sock_fd4); + server_sock_fd4 = client_sock_fd4 = INVALID_SOCK_FD; + + if (server_sock_fd6 != client_sock_fd6) + close_socket(client_sock_fd6); + 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 + + initialised = 0; +} + +/* ================================================== */ + +int +NIO_IsHwTsEnabled(void) +{ +#ifdef HAVE_LINUX_TIMESTAMPING + return NIO_Linux_IsHwTsEnabled(); +#else + return 0; +#endif +} + +/* ================================================== */ + +int +NIO_OpenClientSocket(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 (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; + } +} + +/* ================================================== */ + +int +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) + server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNTPPort(), 0, NULL); + 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) + server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNTPPort(), 0, NULL); + if (server_sock_fd6 != INVALID_SOCK_FD) + server_sock_ref6++; + return server_sock_fd6; + default: + return INVALID_SOCK_FD; + } +} + +/* ================================================== */ + +static int +is_ptp_socket(int sock_fd) +{ + return ptp_port > 0 && sock_fd != INVALID_SOCK_FD && + (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 || is_ptp_socket(sock_fd)) + return; + + if (sock_fd == server_sock_fd4) { + if (--server_sock_ref4 <= 0) { + close_socket(server_sock_fd4); + server_sock_fd4 = INVALID_SOCK_FD; + } + } else if (sock_fd == server_sock_fd6) { + if (--server_sock_ref6 <= 0) { + close_socket(server_sock_fd6); + server_sock_fd6 = INVALID_SOCK_FD; + } + } else { + assert(0); + } +} + +/* ================================================== */ + +int +NIO_IsServerSocket(int sock_fd) +{ + return sock_fd != INVALID_SOCK_FD && + (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 || + ptp_sock_fd4 != INVALID_SOCK_FD || ptp_sock_fd6 != INVALID_SOCK_FD; +} + +/* ================================================== */ + +int +NIO_IsServerConnectable(NTP_Remote_Address *remote_addr) +{ + int sock_fd; + + sock_fd = open_separate_client_socket(remote_addr); + if (sock_fd == INVALID_SOCK_FD) + return 0; + + close_socket(sock_fd); + + return 1; +} + +/* ================================================== */ + +static void +process_message(SCK_Message *message, int sock_fd, int event) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + struct timespec sched_ts; + + SCH_GetLastEventTime(&local_ts.ts, &local_ts.err, NULL); + local_ts.source = NTP_TS_DAEMON; + local_ts.rx_duration = 0.0; + local_ts.net_correction = 0.0; + + sched_ts = local_ts.ts; + + if (message->addr_type != SCK_ADDR_IP) { + DEBUG_LOG("Unexpected address type"); + return; + } + + local_addr.ip_addr = message->local_addr.ip; + local_addr.if_index = message->if_index;; + local_addr.sock_fd = sock_fd; + +#ifdef HAVE_LINUX_TIMESTAMPING + if (NIO_Linux_ProcessMessage(message, &local_addr, &local_ts, event)) + return; +#else + if (!UTI_IsZeroTimespec(&message->timestamp.kernel)) { + LCL_CookTime(&message->timestamp.kernel, &local_ts.ts, &local_ts.err); + local_ts.source = NTP_TS_KERNEL; + } +#endif + + if (local_ts.source != NTP_TS_DAEMON) + 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, &local_ts.net_correction)) + 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"); + return; + } + + NSR_ProcessRx(&message->remote_addr.ip, &local_addr, &local_ts, message->data, message->length); +} + +/* ================================================== */ + +static void +read_from_socket(int sock_fd, int event, void *anything) +{ + SCK_Message *messages; + int i, received, flags = 0; + + if (event == SCH_FILE_EXCEPTION) { +#ifdef HAVE_LINUX_TIMESTAMPING + flags |= SCK_FLAG_MSG_ERRQUEUE; +#else + assert(0); +#endif + } + + messages = SCK_ReceiveMessages(sock_fd, flags, &received); + if (!messages) + return; + + for (i = 0; i < received; i++) + process_message(&messages[i], sock_fd, event); +} + +/* ================================================== */ + +int +NIO_UnwrapMessage(SCK_Message *message, int sock_fd, double *net_correction) +{ + double ptp_correction; + 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; + + ptp_correction = UTI_Integer64NetworkToHost(*(Integer64 *)msg->header.correction) / + ((1 << 16) * 1.0e9); + + /* Use the correction only if the RX duration is known (i.e. HW timestamp) */ + if (*net_correction > 0.0) + *net_correction += ptp_correction; + + DEBUG_LOG("Unwrapped PTP->NTP len=%d corr=%.9f", message->length, ptp_correction); + + return 1; +} + +/* ================================================== */ + +static int +wrap_message(SCK_Message *message, int sock_fd) +{ + static uint16_t sequence_id = 0; + + 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->header.sequence_id = htons(sequence_id++); + 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 */ + +int +NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr, + NTP_Local_Address *local_addr, int length, int process_tx) +{ + SCK_Message message; + + assert(initialised); + + if (local_addr->sock_fd == INVALID_SOCK_FD) { + DEBUG_LOG("No socket to send to %s", UTI_IPSockAddrToString(remote_addr)); + return 0; + } + + SCK_InitMessage(&message, SCK_ADDR_IP); + + 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; + message.remote_addr.ip.port = remote_addr->port; + } + + message.local_addr.ip = local_addr->ip_addr; + + /* Don't require responses to non-link-local addresses to use the same + interface */ + message.if_index = SCK_IsLinkLocalIPAddress(&message.remote_addr.ip.ip_addr) ? + local_addr->if_index : INVALID_IF_INDEX; + +#if !defined(HAVE_IN_PKTINFO) && defined(IP_SENDSRCADDR) + /* On FreeBSD a local IPv4 address cannot be specified on bound socket */ + if (message.local_addr.ip.family == IPADDR_INET4 && + (bound_server_sock_fd4 || !NIO_IsServerSocket(local_addr->sock_fd))) + message.local_addr.ip.family = IPADDR_UNSPEC; +#endif + +#ifdef HAVE_LINUX_TIMESTAMPING + if (process_tx) + NIO_Linux_RequestTxTimestamp(&message, local_addr->sock_fd); +#endif + + if (!SCK_SendMessage(local_addr->sock_fd, &message, 0)) + return 0; + + return 1; +} |