When the SO_TIMESTAMP socket option was enabled, the expected type of control messages containing timestamps was SO_TIMESTAMP instead of SCM_TIMESTAMP. This worked on Linux, where the two values are equal, but not on the other supported systems. The timestamps were ignored and this probably worsened the accuracy and stability of the synchronisation.
833 lines
23 KiB
C
833 lines
23 KiB
C
/*
|
|
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-2015
|
|
*
|
|
* 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 "array.h"
|
|
#include "ntp_io.h"
|
|
#include "ntp_core.h"
|
|
#include "ntp_sources.h"
|
|
#include "sched.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
|
|
#define CMSGBUF_SIZE 256
|
|
|
|
union sockaddr_in46 {
|
|
struct sockaddr_in in4;
|
|
#ifdef FEAT_IPV6
|
|
struct sockaddr_in6 in6;
|
|
#endif
|
|
struct sockaddr u;
|
|
};
|
|
|
|
struct Message {
|
|
union sockaddr_in46 name;
|
|
struct iovec iov;
|
|
NTP_Receive_Buffer buf;
|
|
/* Aligned buffer for control messages */
|
|
struct cmsghdr cmsgbuf[CMSGBUF_SIZE / sizeof (struct cmsghdr)];
|
|
};
|
|
|
|
#ifdef HAVE_RECVMMSG
|
|
#define MAX_RECV_MESSAGES 4
|
|
#define MessageHeader mmsghdr
|
|
#else
|
|
/* Compatible with mmsghdr */
|
|
struct MessageHeader {
|
|
struct msghdr msg_hdr;
|
|
unsigned int msg_len;
|
|
};
|
|
|
|
#define MAX_RECV_MESSAGES 1
|
|
#endif
|
|
|
|
/* Arrays of Message and MessageHeader */
|
|
static ARR_Instance recv_messages;
|
|
static ARR_Instance recv_headers;
|
|
|
|
/* The server/peer and client sockets for IPv4 and IPv6 */
|
|
static int server_sock_fd4;
|
|
static int client_sock_fd4;
|
|
#ifdef FEAT_IPV6
|
|
static int server_sock_fd6;
|
|
static int client_sock_fd6;
|
|
#endif
|
|
|
|
/* Reference counters for server sockets to keep them open only when needed */
|
|
static int server_sock_ref4;
|
|
#ifdef FEAT_IPV6
|
|
static int server_sock_ref6;
|
|
#endif
|
|
|
|
/* 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 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
|
|
prepare_socket(int family, int port_number, int client_only)
|
|
{
|
|
union sockaddr_in46 my_addr;
|
|
socklen_t my_addr_len;
|
|
int sock_fd;
|
|
IPAddr bind_address;
|
|
int events = SCH_FILE_INPUT, on_off = 1;
|
|
|
|
/* Open Internet domain UDP socket for NTP message transmissions */
|
|
|
|
sock_fd = socket(family, SOCK_DGRAM, 0);
|
|
|
|
if (sock_fd < 0) {
|
|
if (!client_only) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not open %s NTP socket : %s",
|
|
UTI_SockaddrFamilyToString(family), strerror(errno));
|
|
} else {
|
|
DEBUG_LOG(LOGF_NtpIO, "Could not open %s NTP socket : %s",
|
|
UTI_SockaddrFamilyToString(family), strerror(errno));
|
|
}
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
|
|
/* Close on exec */
|
|
UTI_FdSetCloexec(sock_fd);
|
|
|
|
/* Prepare local address */
|
|
memset(&my_addr, 0, sizeof (my_addr));
|
|
my_addr_len = 0;
|
|
|
|
switch (family) {
|
|
case AF_INET:
|
|
if (!client_only)
|
|
CNF_GetBindAddress(IPADDR_INET4, &bind_address);
|
|
else
|
|
CNF_GetBindAcquisitionAddress(IPADDR_INET4, &bind_address);
|
|
|
|
if (bind_address.family == IPADDR_INET4)
|
|
my_addr.in4.sin_addr.s_addr = htonl(bind_address.addr.in4);
|
|
else if (port_number)
|
|
my_addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
else
|
|
break;
|
|
|
|
my_addr.in4.sin_family = family;
|
|
my_addr.in4.sin_port = htons(port_number);
|
|
my_addr_len = sizeof (my_addr.in4);
|
|
|
|
break;
|
|
#ifdef FEAT_IPV6
|
|
case AF_INET6:
|
|
if (!client_only)
|
|
CNF_GetBindAddress(IPADDR_INET6, &bind_address);
|
|
else
|
|
CNF_GetBindAcquisitionAddress(IPADDR_INET6, &bind_address);
|
|
|
|
if (bind_address.family == IPADDR_INET6)
|
|
memcpy(my_addr.in6.sin6_addr.s6_addr, bind_address.addr.in6,
|
|
sizeof (my_addr.in6.sin6_addr.s6_addr));
|
|
else if (port_number)
|
|
my_addr.in6.sin6_addr = in6addr_any;
|
|
else
|
|
break;
|
|
|
|
my_addr.in6.sin6_family = family;
|
|
my_addr.in6.sin6_port = htons(port_number);
|
|
my_addr_len = sizeof (my_addr.in6);
|
|
|
|
break;
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
/* Make the socket capable of re-using an old address if binding to a specific port */
|
|
if (port_number &&
|
|
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "SO_REUSEADDR");
|
|
/* Don't quit - we might survive anyway */
|
|
}
|
|
|
|
/* Make the socket capable of sending broadcast pkts - needed for NTP broadcast mode */
|
|
if (!client_only &&
|
|
setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "SO_BROADCAST");
|
|
/* Don't quit - we might survive anyway */
|
|
}
|
|
|
|
#ifdef SO_TIMESTAMP
|
|
/* Enable receiving of timestamp control messages */
|
|
#ifdef SO_TIMESTAMPNS
|
|
/* Try nanosecond resolution first */
|
|
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMPNS, (char *)&on_off, sizeof(on_off)) < 0)
|
|
#endif
|
|
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMP, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "SO_TIMESTAMP");
|
|
/* Don't quit - we might survive anyway */
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
NIO_Linux_SetTimestampSocketOptions(sock_fd, client_only, &events);
|
|
#endif
|
|
|
|
#ifdef IP_FREEBIND
|
|
/* Allow binding to address that doesn't exist yet */
|
|
if (my_addr_len > 0 &&
|
|
setsockopt(sock_fd, IPPROTO_IP, IP_FREEBIND, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "IP_FREEBIND");
|
|
}
|
|
#endif
|
|
|
|
if (family == AF_INET) {
|
|
#ifdef HAVE_IN_PKTINFO
|
|
/* We want the local IP info on server sockets */
|
|
if (setsockopt(sock_fd, IPPROTO_IP, IP_PKTINFO, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "IP_PKTINFO");
|
|
/* Don't quit - we might survive anyway */
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef FEAT_IPV6
|
|
else if (family == AF_INET6) {
|
|
#ifdef IPV6_V6ONLY
|
|
/* Receive IPv6 packets only */
|
|
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "IPV6_V6ONLY");
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_IN6_PKTINFO
|
|
#ifdef IPV6_RECVPKTINFO
|
|
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "IPV6_RECVPKTINFO");
|
|
}
|
|
#else
|
|
if (setsockopt(sock_fd, IPPROTO_IPV6, IPV6_PKTINFO, (char *)&on_off, sizeof(on_off)) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not set %s socket option", "IPV6_PKTINFO");
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* Bind the socket if a port or address was specified */
|
|
if (my_addr_len > 0 && PRV_BindSocket(sock_fd, &my_addr.u, my_addr_len) < 0) {
|
|
LOG(LOGS_ERR, LOGF_NtpIO, "Could not bind %s NTP socket : %s",
|
|
UTI_SockaddrFamilyToString(family), strerror(errno));
|
|
close(sock_fd);
|
|
return INVALID_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
|
|
prepare_separate_client_socket(int family)
|
|
{
|
|
switch (family) {
|
|
case IPADDR_INET4:
|
|
return prepare_socket(AF_INET, 0, 1);
|
|
#ifdef FEAT_IPV6
|
|
case IPADDR_INET6:
|
|
return prepare_socket(AF_INET6, 0, 1);
|
|
#endif
|
|
default:
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
connect_socket(int sock_fd, NTP_Remote_Address *remote_addr)
|
|
{
|
|
union sockaddr_in46 addr;
|
|
socklen_t addr_len;
|
|
|
|
addr_len = UTI_IPAndPortToSockaddr(&remote_addr->ip_addr, remote_addr->port, &addr.u);
|
|
|
|
assert(addr_len);
|
|
|
|
if (connect(sock_fd, &addr.u, addr_len) < 0) {
|
|
DEBUG_LOG(LOGF_NtpIO, "Could not connect NTP socket to %s:%d : %s",
|
|
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
|
|
strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
close_socket(int sock_fd)
|
|
{
|
|
if (sock_fd == INVALID_SOCK_FD)
|
|
return;
|
|
|
|
SCH_RemoveFileHandler(sock_fd);
|
|
close(sock_fd);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
prepare_buffers(unsigned int n)
|
|
{
|
|
struct MessageHeader *hdr;
|
|
struct Message *msg;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
msg = ARR_GetElement(recv_messages, i);
|
|
hdr = ARR_GetElement(recv_headers, i);
|
|
|
|
msg->iov.iov_base = &msg->buf;
|
|
msg->iov.iov_len = sizeof (msg->buf);
|
|
hdr->msg_hdr.msg_name = &msg->name;
|
|
hdr->msg_hdr.msg_namelen = sizeof (msg->name);
|
|
hdr->msg_hdr.msg_iov = &msg->iov;
|
|
hdr->msg_hdr.msg_iovlen = 1;
|
|
hdr->msg_hdr.msg_control = &msg->cmsgbuf;
|
|
hdr->msg_hdr.msg_controllen = sizeof (msg->cmsgbuf);
|
|
hdr->msg_hdr.msg_flags = 0;
|
|
hdr->msg_len = 0;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_Initialise(int family)
|
|
{
|
|
int server_port, client_port;
|
|
|
|
assert(!initialised);
|
|
initialised = 1;
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
NIO_Linux_Initialise();
|
|
#else
|
|
if (ARR_GetSize(CNF_GetHwTsInterfaces()))
|
|
LOG_FATAL(LOGF_NtpIO, "HW timestamping not supported");
|
|
#endif
|
|
|
|
recv_messages = ARR_CreateInstance(sizeof (struct Message));
|
|
ARR_SetSize(recv_messages, MAX_RECV_MESSAGES);
|
|
recv_headers = ARR_CreateInstance(sizeof (struct MessageHeader));
|
|
ARR_SetSize(recv_headers, MAX_RECV_MESSAGES);
|
|
prepare_buffers(MAX_RECV_MESSAGES);
|
|
|
|
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;
|
|
client_sock_fd4 = INVALID_SOCK_FD;
|
|
server_sock_ref4 = 0;
|
|
#ifdef FEAT_IPV6
|
|
server_sock_fd6 = INVALID_SOCK_FD;
|
|
client_sock_fd6 = INVALID_SOCK_FD;
|
|
server_sock_ref6 = 0;
|
|
#endif
|
|
|
|
if (family == IPADDR_UNSPEC || family == IPADDR_INET4) {
|
|
if (permanent_server_sockets && server_port)
|
|
server_sock_fd4 = prepare_socket(AF_INET, server_port, 0);
|
|
if (!separate_client_sockets) {
|
|
if (client_port != server_port || !server_port)
|
|
client_sock_fd4 = prepare_socket(AF_INET, client_port, 1);
|
|
else
|
|
client_sock_fd4 = server_sock_fd4;
|
|
}
|
|
}
|
|
#ifdef FEAT_IPV6
|
|
if (family == IPADDR_UNSPEC || family == IPADDR_INET6) {
|
|
if (permanent_server_sockets && server_port)
|
|
server_sock_fd6 = prepare_socket(AF_INET6, server_port, 0);
|
|
if (!separate_client_sockets) {
|
|
if (client_port != server_port || !server_port)
|
|
client_sock_fd6 = prepare_socket(AF_INET6, client_port, 1);
|
|
else
|
|
client_sock_fd6 = server_sock_fd6;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ((server_port && server_sock_fd4 == INVALID_SOCK_FD &&
|
|
permanent_server_sockets
|
|
#ifdef FEAT_IPV6
|
|
&& server_sock_fd6 == INVALID_SOCK_FD
|
|
#endif
|
|
) || (!separate_client_sockets && client_sock_fd4 == INVALID_SOCK_FD
|
|
#ifdef FEAT_IPV6
|
|
&& client_sock_fd6 == INVALID_SOCK_FD
|
|
#endif
|
|
)) {
|
|
LOG_FATAL(LOGF_NtpIO, "Could not open NTP sockets");
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
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;
|
|
#ifdef FEAT_IPV6
|
|
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;
|
|
#endif
|
|
ARR_DestroyInstance(recv_headers);
|
|
ARR_DestroyInstance(recv_messages);
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
NIO_Linux_Finalise();
|
|
#endif
|
|
|
|
initialised = 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_OpenClientSocket(NTP_Remote_Address *remote_addr)
|
|
{
|
|
if (separate_client_sockets) {
|
|
int sock_fd = prepare_separate_client_socket(remote_addr->ip_addr.family);
|
|
|
|
if (sock_fd == INVALID_SOCK_FD)
|
|
return INVALID_SOCK_FD;
|
|
|
|
if (!connect_socket(sock_fd, remote_addr)) {
|
|
close_socket(sock_fd);
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
|
|
return sock_fd;
|
|
} else {
|
|
switch (remote_addr->ip_addr.family) {
|
|
case IPADDR_INET4:
|
|
return client_sock_fd4;
|
|
#ifdef FEAT_IPV6
|
|
case IPADDR_INET6:
|
|
return client_sock_fd6;
|
|
#endif
|
|
default:
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
|
|
{
|
|
switch (remote_addr->ip_addr.family) {
|
|
case IPADDR_INET4:
|
|
if (permanent_server_sockets)
|
|
return server_sock_fd4;
|
|
if (server_sock_fd4 == INVALID_SOCK_FD)
|
|
server_sock_fd4 = prepare_socket(AF_INET, CNF_GetNTPPort(), 0);
|
|
if (server_sock_fd4 != INVALID_SOCK_FD)
|
|
server_sock_ref4++;
|
|
return server_sock_fd4;
|
|
#ifdef FEAT_IPV6
|
|
case IPADDR_INET6:
|
|
if (permanent_server_sockets)
|
|
return server_sock_fd6;
|
|
if (server_sock_fd6 == INVALID_SOCK_FD)
|
|
server_sock_fd6 = prepare_socket(AF_INET6, CNF_GetNTPPort(), 0);
|
|
if (server_sock_fd6 != INVALID_SOCK_FD)
|
|
server_sock_ref6++;
|
|
return server_sock_fd6;
|
|
#endif
|
|
default:
|
|
return INVALID_SOCK_FD;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_CloseClientSocket(int sock_fd)
|
|
{
|
|
if (separate_client_sockets)
|
|
close_socket(sock_fd);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
NIO_CloseServerSocket(int sock_fd)
|
|
{
|
|
if (permanent_server_sockets || sock_fd == INVALID_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;
|
|
}
|
|
}
|
|
#ifdef FEAT_IPV6
|
|
else if (sock_fd == server_sock_fd6) {
|
|
if (--server_sock_ref6 <= 0) {
|
|
close_socket(server_sock_fd6);
|
|
server_sock_fd6 = INVALID_SOCK_FD;
|
|
}
|
|
}
|
|
#endif
|
|
else {
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
NIO_IsServerSocket(int sock_fd)
|
|
{
|
|
return sock_fd != INVALID_SOCK_FD &&
|
|
(sock_fd == server_sock_fd4
|
|
#ifdef FEAT_IPV6
|
|
|| sock_fd == server_sock_fd6
|
|
#endif
|
|
);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
process_message(struct msghdr *hdr, int length, int sock_fd)
|
|
{
|
|
NTP_Remote_Address remote_addr;
|
|
NTP_Local_Address local_addr;
|
|
NTP_Local_Timestamp local_ts;
|
|
struct timespec sched_ts;
|
|
struct cmsghdr *cmsg;
|
|
int if_index;
|
|
|
|
SCH_GetLastEventTime(&local_ts.ts, &local_ts.err, NULL);
|
|
local_ts.source = NTP_TS_DAEMON;
|
|
sched_ts = local_ts.ts;
|
|
|
|
if (hdr->msg_namelen > sizeof (union sockaddr_in46)) {
|
|
DEBUG_LOG(LOGF_NtpIO, "Truncated source address");
|
|
return;
|
|
}
|
|
|
|
if (hdr->msg_namelen >= sizeof (((struct sockaddr *)hdr->msg_name)->sa_family)) {
|
|
UTI_SockaddrToIPAndPort((struct sockaddr *)hdr->msg_name,
|
|
&remote_addr.ip_addr, &remote_addr.port);
|
|
} else {
|
|
remote_addr.ip_addr.family = IPADDR_UNSPEC;
|
|
remote_addr.port = 0;
|
|
}
|
|
|
|
local_addr.ip_addr.family = IPADDR_UNSPEC;
|
|
local_addr.sock_fd = sock_fd;
|
|
if_index = -1;
|
|
|
|
if (hdr->msg_flags & MSG_TRUNC) {
|
|
DEBUG_LOG(LOGF_NtpIO, "Received truncated message from %s:%d",
|
|
UTI_IPToString(&remote_addr.ip_addr), remote_addr.port);
|
|
return;
|
|
}
|
|
|
|
if (hdr->msg_flags & MSG_CTRUNC) {
|
|
DEBUG_LOG(LOGF_NtpIO, "Truncated control message");
|
|
/* Continue */
|
|
}
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(hdr); cmsg; cmsg = CMSG_NXTHDR(hdr, cmsg)) {
|
|
#ifdef HAVE_IN_PKTINFO
|
|
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
|
|
struct in_pktinfo ipi;
|
|
|
|
memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi));
|
|
local_addr.ip_addr.addr.in4 = ntohl(ipi.ipi_addr.s_addr);
|
|
local_addr.ip_addr.family = IPADDR_INET4;
|
|
if_index = ipi.ipi_ifindex;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_IN6_PKTINFO
|
|
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
|
|
struct in6_pktinfo ipi;
|
|
|
|
memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi));
|
|
memcpy(&local_addr.ip_addr.addr.in6, &ipi.ipi6_addr.s6_addr,
|
|
sizeof (local_addr.ip_addr.addr.in6));
|
|
local_addr.ip_addr.family = IPADDR_INET6;
|
|
if_index = ipi.ipi6_ifindex;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SCM_TIMESTAMP
|
|
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
|
|
struct timeval tv;
|
|
struct timespec ts;
|
|
|
|
memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv));
|
|
UTI_TimevalToTimespec(&tv, &ts);
|
|
LCL_CookTime(&ts, &local_ts.ts, &local_ts.err);
|
|
local_ts.source = NTP_TS_KERNEL;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SCM_TIMESTAMPNS
|
|
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPNS) {
|
|
struct timespec ts;
|
|
|
|
memcpy(&ts, CMSG_DATA(cmsg), sizeof (ts));
|
|
LCL_CookTime(&ts, &local_ts.ts, &local_ts.err);
|
|
local_ts.source = NTP_TS_KERNEL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
if (NIO_Linux_ProcessMessage(&remote_addr, &local_addr, &local_ts,
|
|
hdr, length, sock_fd, if_index))
|
|
return;
|
|
#endif
|
|
|
|
DEBUG_LOG(LOGF_NtpIO, "Received %d bytes from %s:%d to %s fd=%d if=%d tss=%d delay=%.9f",
|
|
length, UTI_IPToString(&remote_addr.ip_addr), remote_addr.port,
|
|
UTI_IPToString(&local_addr.ip_addr), local_addr.sock_fd, if_index,
|
|
local_ts.source, UTI_DiffTimespecsToDouble(&sched_ts, &local_ts.ts));
|
|
|
|
/* Just ignore the packet if it's not of a recognized length */
|
|
if (length < NTP_NORMAL_PACKET_LENGTH || length > sizeof (NTP_Receive_Buffer))
|
|
return;
|
|
|
|
NSR_ProcessRx(&remote_addr, &local_addr, &local_ts,
|
|
(NTP_Packet *)hdr->msg_iov[0].iov_base, length);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
read_from_socket(int sock_fd, int event, void *anything)
|
|
{
|
|
/* This should only be called when there is something
|
|
to read, otherwise it may block */
|
|
|
|
struct MessageHeader *hdr;
|
|
unsigned int i, n;
|
|
int status, flags = 0;
|
|
|
|
hdr = ARR_GetElements(recv_headers);
|
|
n = ARR_GetSize(recv_headers);
|
|
assert(n >= 1);
|
|
|
|
if (event == SCH_FILE_EXCEPTION) {
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
flags |= MSG_ERRQUEUE;
|
|
#else
|
|
assert(0);
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_RECVMMSG
|
|
status = recvmmsg(sock_fd, hdr, n, flags | MSG_DONTWAIT, NULL);
|
|
if (status >= 0)
|
|
n = status;
|
|
#else
|
|
n = 1;
|
|
status = recvmsg(sock_fd, &hdr[0].msg_hdr, flags);
|
|
if (status >= 0)
|
|
hdr[0].msg_len = status;
|
|
#endif
|
|
|
|
if (status < 0) {
|
|
DEBUG_LOG(LOGF_NtpIO, "Could not receive from fd %d : %s", sock_fd,
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
hdr = ARR_GetElement(recv_headers, i);
|
|
process_message(&hdr->msg_hdr, hdr->msg_len, sock_fd);
|
|
}
|
|
|
|
/* Restore the buffers to their original state */
|
|
prepare_buffers(n);
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* 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)
|
|
{
|
|
union sockaddr_in46 remote;
|
|
struct msghdr msg;
|
|
struct iovec iov;
|
|
struct cmsghdr *cmsg, cmsgbuf[CMSGBUF_SIZE / sizeof (struct cmsghdr)];
|
|
int cmsglen;
|
|
socklen_t addrlen = 0;
|
|
|
|
assert(initialised);
|
|
|
|
if (local_addr->sock_fd == INVALID_SOCK_FD) {
|
|
DEBUG_LOG(LOGF_NtpIO, "No socket to send to %s:%d",
|
|
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port);
|
|
return 0;
|
|
}
|
|
|
|
/* Don't set address with connected socket */
|
|
if (NIO_IsServerSocket(local_addr->sock_fd) || !separate_client_sockets) {
|
|
addrlen = UTI_IPAndPortToSockaddr(&remote_addr->ip_addr, remote_addr->port,
|
|
&remote.u);
|
|
if (!addrlen)
|
|
return 0;
|
|
}
|
|
|
|
if (addrlen) {
|
|
msg.msg_name = &remote.u;
|
|
msg.msg_namelen = addrlen;
|
|
} else {
|
|
msg.msg_name = NULL;
|
|
msg.msg_namelen = 0;
|
|
}
|
|
|
|
iov.iov_base = packet;
|
|
iov.iov_len = length;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
msg.msg_flags = 0;
|
|
cmsglen = 0;
|
|
|
|
#ifdef HAVE_IN_PKTINFO
|
|
if (local_addr->ip_addr.family == IPADDR_INET4) {
|
|
struct in_pktinfo *ipi;
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_pktinfo)));
|
|
cmsglen += CMSG_SPACE(sizeof(struct in_pktinfo));
|
|
|
|
cmsg->cmsg_level = IPPROTO_IP;
|
|
cmsg->cmsg_type = IP_PKTINFO;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
|
|
|
|
ipi = (struct in_pktinfo *) CMSG_DATA(cmsg);
|
|
ipi->ipi_spec_dst.s_addr = htonl(local_addr->ip_addr.addr.in4);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_IN6_PKTINFO
|
|
if (local_addr->ip_addr.family == IPADDR_INET6) {
|
|
struct in6_pktinfo *ipi;
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
memset(cmsg, 0, CMSG_SPACE(sizeof(struct in6_pktinfo)));
|
|
cmsglen += CMSG_SPACE(sizeof(struct in6_pktinfo));
|
|
|
|
cmsg->cmsg_level = IPPROTO_IPV6;
|
|
cmsg->cmsg_type = IPV6_PKTINFO;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
|
|
|
|
ipi = (struct in6_pktinfo *) CMSG_DATA(cmsg);
|
|
memcpy(&ipi->ipi6_addr.s6_addr, &local_addr->ip_addr.addr.in6,
|
|
sizeof(ipi->ipi6_addr.s6_addr));
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LINUX_TIMESTAMPING
|
|
if (process_tx)
|
|
cmsglen = NIO_Linux_RequestTxTimestamp(&msg, cmsglen, local_addr->sock_fd);
|
|
#endif
|
|
|
|
msg.msg_controllen = cmsglen;
|
|
/* This is apparently required on some systems */
|
|
if (!cmsglen)
|
|
msg.msg_control = NULL;
|
|
|
|
if (sendmsg(local_addr->sock_fd, &msg, 0) < 0) {
|
|
DEBUG_LOG(LOGF_NtpIO, "Could not send to %s:%d from %s fd %d : %s",
|
|
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
|
|
UTI_IPToString(&local_addr->ip_addr), local_addr->sock_fd,
|
|
strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
DEBUG_LOG(LOGF_NtpIO, "Sent %d bytes to %s:%d from %s fd %d", length,
|
|
UTI_IPToString(&remote_addr->ip_addr), remote_addr->port,
|
|
UTI_IPToString(&local_addr->ip_addr), local_addr->sock_fd);
|
|
|
|
return 1;
|
|
}
|