diff --git a/socket.c b/socket.c new file mode 100644 index 0000000..b523abf --- /dev/null +++ b/socket.c @@ -0,0 +1,1162 @@ +/* + 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-2019 + * + * 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 implements socket operations. + + */ + +#include "config.h" + +#include "sysincl.h" + +#ifdef HAVE_LINUX_TIMESTAMPING +#include +#include +#endif + +#include "socket.h" +#include "array.h" +#include "logging.h" +#include "privops.h" +#include "util.h" + +#define INVALID_SOCK_FD (-4) +#define CMSG_BUF_SIZE 256 + +union sockaddr_all { + struct sockaddr_in in4; +#ifdef FEAT_IPV6 + struct sockaddr_in6 in6; +#endif + struct sockaddr_un un; + struct sockaddr sa; +}; + +struct Message { + union sockaddr_all name; + struct iovec iov; + /* Buffer of sufficient length for all expected messages */ + union { + NTP_Receive_Buffer ntp_msg; + CMD_Request cmd_request; + CMD_Request cmd_reply; + } msg_buf; + /* Aligned buffer for control messages */ + struct cmsghdr cmsg_buf[CMSG_BUF_SIZE / sizeof (struct cmsghdr)]; +}; + +#ifdef HAVE_RECVMMSG +#define MAX_RECV_MESSAGES SCK_MAX_RECV_MESSAGES +#define MessageHeader mmsghdr +#else +/* Compatible with mmsghdr */ +struct MessageHeader { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +#define MAX_RECV_MESSAGES 1 +#endif + +static int initialised; + +/* Arrays of Message and MessageHeader */ +static ARR_Instance recv_messages; +static ARR_Instance recv_headers; + +static unsigned int received_messages; + +static int (*priv_bind_function)(int sock_fd, struct sockaddr *address, + socklen_t address_len); + +/* ================================================== */ + +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->msg_buf; + msg->iov.iov_len = sizeof (msg->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->cmsg_buf; + hdr->msg_hdr.msg_controllen = sizeof (msg->cmsg_buf); + hdr->msg_hdr.msg_flags = 0; + hdr->msg_len = 0; + } +} + +/* ================================================== */ + +static int +ipsockaddr_to_sockaddr(IPSockAddr *addr, union sockaddr_all *saddr) +{ + return UTI_IPAndPortToSockaddr(&addr->ip_addr, addr->port, &saddr->sa); +} + +/* ================================================== */ + +static int +open_socket(int domain, int type, int flags) +{ + int sock_fd; + + sock_fd = socket(domain, type, 0); + + if (sock_fd < 0) { + DEBUG_LOG("Could not open %s socket : %s", + UTI_SockaddrFamilyToString(domain), strerror(errno)); + return INVALID_SOCK_FD; + } + + /* Close the socket automatically on exec */ + if (!UTI_FdSetCloexec(sock_fd)) { + DEBUG_LOG("Could not set O_CLOEXEC : %s", strerror(errno)); + close(sock_fd); + return INVALID_SOCK_FD; + } + + /* Enable non-blocking mode if requested */ + if (flags & SCK_FLAG_NONBLOCK && fcntl(sock_fd, F_SETFL, O_NONBLOCK)) { + DEBUG_LOG("Could not set O_NONBLOCK : %s", strerror(errno)); + close(sock_fd); + return INVALID_SOCK_FD; + } + + return sock_fd; +} + +/* ================================================== */ + +static int +set_socket_options(int sock_fd, int flags) +{ + /* Make the socket capable of sending broadcast packets if requested */ + if (flags & SCK_FLAG_BROADCAST && !SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_BROADCAST, 1)) + ; + + return 1; +} + +/* ================================================== */ + +static int +set_ip_options(int sock_fd, int family, int flags) +{ +#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY) + /* Receive only IPv6 packets on an IPv6 socket */ + if (family == IPADDR_INET6 && !SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1)) + return 0; +#endif + + /* Provide destination address of received packets if requested */ + if (flags & SCK_FLAG_RX_DEST_ADDR) { + if (family == IPADDR_INET4) { +#ifdef HAVE_IN_PKTINFO + if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_PKTINFO, 1)) + ; +#elif defined(IP_RECVDSTADDR) + if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_RECVDSTADDR, 1)) + ; +#endif + } +#ifdef FEAT_IPV6 + else if (family == IPADDR_INET6) { +#ifdef HAVE_IN6_PKTINFO +#ifdef IPV6_RECVPKTINFO + if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1)) + ; +#else + if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_PKTINFO, 1)) + ; +#endif +#endif + } +#endif + } + + return 1; +} + +/* ================================================== */ + +static int +bind_ip_address(int sock_fd, IPSockAddr *addr, int flags) +{ + union sockaddr_all saddr; + socklen_t saddr_len; + int s; + + /* Make the socket capable of re-using an old address if binding to a specific port */ + if (addr->port > 0 && !SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_REUSEADDR, 1)) + ; + +#ifdef IP_FREEBIND + /* Allow binding to an address that doesn't exist yet */ + if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_FREEBIND, 1)) + ; +#endif + + saddr_len = ipsockaddr_to_sockaddr(addr, &saddr); + if (saddr_len == 0) + return 0; + + if (flags & SCK_FLAG_PRIV_BIND && priv_bind_function) + s = priv_bind_function(sock_fd, &saddr.sa, saddr_len); + else + s = bind(sock_fd, &saddr.sa, saddr_len); + + if (s < 0) { + DEBUG_LOG("Could not bind socket to %s : %s", + UTI_IPSockAddrToString(addr), strerror(errno)); + return 0; + } + + return 1; +} + +/* ================================================== */ + +static int +connect_ip_address(int sock_fd, IPSockAddr *addr) +{ + union sockaddr_all saddr; + socklen_t saddr_len; + + saddr_len = ipsockaddr_to_sockaddr(addr, &saddr); + if (saddr_len == 0) + return 0; + + if (connect(sock_fd, &saddr.sa, saddr_len) < 0) { + DEBUG_LOG("Could not connect socket to %s : %s", + UTI_IPSockAddrToString(addr), strerror(errno)); + return 0; + } + + return 1; +} + +/* ================================================== */ + +static int +open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, int type, int flags) +{ + int domain, family, sock_fd; + + if (local_addr) + family = local_addr->ip_addr.family; + else if (remote_addr) + family = remote_addr->ip_addr.family; + else + family = IPADDR_INET4; + + switch (family) { + case IPADDR_INET4: + domain = AF_INET; + break; +#ifdef FEAT_IPV6 + case IPADDR_INET6: + domain = AF_INET6; + break; +#endif + default: + DEBUG_LOG("Unspecified family"); + return INVALID_SOCK_FD; + } + + sock_fd = open_socket(domain, type, flags); + if (sock_fd < 0) + return INVALID_SOCK_FD; + + if (!set_socket_options(sock_fd, flags)) + goto error; + + if (!set_ip_options(sock_fd, family, flags)) + goto error; + + /* Bind the socket if a local address was specified */ + if (local_addr && local_addr->ip_addr.family != IPADDR_UNSPEC && + !bind_ip_address(sock_fd, local_addr, flags)) + goto error; + + /* Connect the socket if a remote address was specified */ + if (remote_addr && remote_addr->ip_addr.family != IPADDR_UNSPEC && + !connect_ip_address(sock_fd, remote_addr)) + goto error; + + if (remote_addr || local_addr) + DEBUG_LOG("Opened %s socket fd=%d%s%s%s%s", + family == IPADDR_INET4 ? "IPv4" : "IPv6", + sock_fd, + remote_addr ? " remote=" : "", + remote_addr ? UTI_IPSockAddrToString(remote_addr) : "", + local_addr ? " local=" : "", + local_addr ? UTI_IPSockAddrToString(local_addr) : ""); + + return sock_fd; + +error: + SCK_CloseSocket(sock_fd); + return INVALID_SOCK_FD; +} + +/* ================================================== */ + +static int +bind_unix_address(int sock_fd, const char *addr, int flags) +{ + union sockaddr_all saddr; + + if (snprintf(saddr.un.sun_path, sizeof (saddr.un.sun_path), "%s", addr) >= + sizeof (saddr.un.sun_path)) { + DEBUG_LOG("Unix socket path %s too long", addr); + return 0; + } + saddr.un.sun_family = AF_UNIX; + + unlink(addr); + + /* PRV_BindSocket() doesn't support Unix sockets yet */ + if (bind(sock_fd, &saddr.sa, sizeof (saddr.un)) < 0) { + DEBUG_LOG("Could not bind Unix socket to %s : %s", addr, strerror(errno)); + return 0; + } + + /* Allow access to everyone with access to the directory if requested */ + if (flags & SCK_FLAG_ALL_PERMISSIONS && chmod(addr, 0666) < 0) { + DEBUG_LOG("Could not change permissions of %s : %s", addr, strerror(errno)); + return 0; + } + + return 1; +} + +/* ================================================== */ + +static int +connect_unix_address(int sock_fd, const char *addr) +{ + union sockaddr_all saddr; + + if (snprintf(saddr.un.sun_path, sizeof (saddr.un.sun_path), "%s", addr) >= + sizeof (saddr.un.sun_path)) { + DEBUG_LOG("Unix socket path %s too long", addr); + return 0; + } + saddr.un.sun_family = AF_UNIX; + + if (connect(sock_fd, &saddr.sa, sizeof (saddr.un)) < 0) { + DEBUG_LOG("Could not connect Unix socket to %s : %s", addr, strerror(errno)); + return 0; + } + + return 1; +} + +/* ================================================== */ + +static int +open_unix_socket(const char *remote_addr, const char *local_addr, int type, int flags) +{ + int sock_fd; + + sock_fd = open_socket(AF_UNIX, type, flags); + if (sock_fd < 0) + return INVALID_SOCK_FD; + + if (!set_socket_options(sock_fd, flags)) + goto error; + + /* Bind the socket if a local address was specified */ + if (local_addr && !bind_unix_address(sock_fd, local_addr, flags)) + goto error; + + /* Connect the socket if a remote address was specified */ + if (remote_addr && !connect_unix_address(sock_fd, remote_addr)) + goto error; + + DEBUG_LOG("Opened Unix socket fd=%d%s%s%s%s", + sock_fd, + remote_addr ? " remote=" : "", remote_addr ? remote_addr : "", + local_addr ? " local=" : "", local_addr ? local_addr : ""); + + return sock_fd; + +error: + SCK_RemoveSocket(sock_fd); + SCK_CloseSocket(sock_fd); + return INVALID_SOCK_FD; +} + +/* ================================================== */ + +static int +get_recv_flags(int flags) +{ + int recv_flags = 0; + + if (flags & SCK_FLAG_MSG_ERRQUEUE) { +#ifdef MSG_ERRQUEUE + recv_flags |= MSG_ERRQUEUE; +#else + assert(0); +#endif + } + + return recv_flags; +} + +/* ================================================== */ + +static void +handle_recv_error(int sock_fd, int flags) +{ +#ifdef MSG_ERRQUEUE + /* If reading from the error queue failed, the select() exception should + be for a socket error. Clear the error to avoid a busy loop. */ + if (flags & SCK_FLAG_MSG_ERRQUEUE) { + int error = 0; + socklen_t len = sizeof (error); + + if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &error, &len)) + DEBUG_LOG("Could not get SO_ERROR"); + if (error) + errno = error; + } +#endif + + DEBUG_LOG("Could not receive message fd=%d : %s", sock_fd, strerror(errno)); +} + +/* ================================================== */ + +static void +log_message(int sock_fd, int direction, SCK_Message *message, const char *prefix, + const char *error) +{ + const char *local_addr, *remote_addr; + char if_index[20], tss[10], tsif[20], tslen[20]; + + if (DEBUG <= 0 || log_min_severity < LOGS_DEBUG) + return; + + remote_addr = NULL; + local_addr = NULL; + if_index[0] = '\0'; + tss[0] = '\0'; + tsif[0] = '\0'; + tslen[0] = '\0'; + + switch (message->addr_type) { + case SCK_ADDR_IP: + if (message->remote_addr.ip.ip_addr.family != IPADDR_UNSPEC) + remote_addr = UTI_IPSockAddrToString(&message->remote_addr.ip); + if (message->local_addr.ip.family != IPADDR_UNSPEC) { + local_addr = UTI_IPToString(&message->local_addr.ip); + } + break; + case SCK_ADDR_UNIX: + remote_addr = message->remote_addr.path; + break; + default: + break; + } + + if (message->if_index != INVALID_IF_INDEX) + snprintf(if_index, sizeof (if_index), " if=%d", message->if_index); + + if (direction > 0) { + if (!UTI_IsZeroTimespec(&message->timestamp.kernel) || + !UTI_IsZeroTimespec(&message->timestamp.hw)) + snprintf(tss, sizeof (tss), " tss=%s%s", + !UTI_IsZeroTimespec(&message->timestamp.kernel) ? "K" : "", + !UTI_IsZeroTimespec(&message->timestamp.hw) ? "H" : ""); + + if (message->timestamp.if_index != INVALID_IF_INDEX) + snprintf(tsif, sizeof (tsif), " tsif=%d", message->timestamp.if_index); + + if (message->timestamp.l2_length != 0) + snprintf(tslen, sizeof (tslen), " tslen=%d", message->timestamp.l2_length); + } + + DEBUG_LOG("%s message%s%s%s%s fd=%d len=%u%s%s%s%s%s%s", + prefix, + remote_addr ? (direction > 0 ? " from " : " to ") : "", + remote_addr ? remote_addr : "", + local_addr ? (direction > 0 ? " to " : " from ") : "", + local_addr ? local_addr : "", + sock_fd, message->length, if_index, + tss, tsif, tslen, + error ? " : " : "", error ? error : ""); +} + +/* ================================================== */ + +static void +init_message_addresses(SCK_Message *message, SCK_AddressType addr_type) +{ + message->addr_type = addr_type; + + switch (addr_type) { + case SCK_ADDR_UNSPEC: + break; + case SCK_ADDR_IP: + message->remote_addr.ip.ip_addr.family = IPADDR_UNSPEC; + message->remote_addr.ip.port = 0; + message->local_addr.ip.family = IPADDR_UNSPEC; + break; + case SCK_ADDR_UNIX: + message->remote_addr.path = NULL; + break; + default: + assert(0); + } +} + +/* ================================================== */ + +static void +init_message_nonaddress(SCK_Message *message) +{ + message->data = NULL; + message->length = 0; + message->if_index = INVALID_IF_INDEX; + + UTI_ZeroTimespec(&message->timestamp.kernel); + UTI_ZeroTimespec(&message->timestamp.hw); + message->timestamp.if_index = INVALID_IF_INDEX; + message->timestamp.l2_length = 0; + message->timestamp.tx_flags = 0; +} + +/* ================================================== */ + +static int +process_header(struct msghdr *msg, unsigned int msg_length, int sock_fd, SCK_Message *message) +{ + struct cmsghdr *cmsg; + + if (msg->msg_iovlen != 1) { + DEBUG_LOG("Unexpected iovlen"); + return 0; + } + + if (msg->msg_namelen > sizeof (union sockaddr_all)) { + DEBUG_LOG("Truncated source address"); + return 0; + } + + if (msg->msg_namelen > sizeof (((struct sockaddr *)msg->msg_name)->sa_family)) { + switch (((struct sockaddr *)msg->msg_name)->sa_family) { + case AF_INET: +#ifdef FEAT_IPV6 + case AF_INET6: +#endif + init_message_addresses(message, SCK_ADDR_IP); + UTI_SockaddrToIPAndPort(msg->msg_name, &message->remote_addr.ip.ip_addr, + &message->remote_addr.ip.port); + break; + case AF_UNIX: + init_message_addresses(message, SCK_ADDR_UNIX); + message->remote_addr.path = ((struct sockaddr_un *)msg->msg_name)->sun_path; + break; + default: + DEBUG_LOG("Unexpected address"); + return 0; + } + } else { + init_message_addresses(message, SCK_ADDR_UNSPEC); + } + + init_message_nonaddress(message); + + message->data = msg->msg_iov[0].iov_base; + message->length = msg_length; + + if (msg->msg_flags & MSG_TRUNC) { + log_message(sock_fd, 1, message, "Truncated", NULL); + return 0; + } + + if (msg->msg_flags & MSG_CTRUNC) { + log_message(sock_fd, 1, message, "Truncated cmsg in", NULL); + return 0; + } + + for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { +#ifdef HAVE_IN_PKTINFO + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + struct in_pktinfo ipi; + + if (message->addr_type != SCK_ADDR_IP) + init_message_addresses(message, SCK_ADDR_IP); + + memcpy(&ipi, CMSG_DATA(cmsg), sizeof (ipi)); + message->local_addr.ip.addr.in4 = ntohl(ipi.ipi_addr.s_addr); + message->local_addr.ip.family = IPADDR_INET4; + message->if_index = ipi.ipi_ifindex; + } +#elif defined(IP_RECVDSTADDR) + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) { + struct in_addr addr; + + if (message->addr_type != SCK_ADDR_IP) + init_message_addresses(message, SCK_ADDR_IP); + + memcpy(&addr, CMSG_DATA(cmsg), sizeof (addr)); + message->local_addr.ip.addr.in4 = ntohl(addr.s_addr); + message->local_addr.ip.family = IPADDR_INET4; + } +#endif + +#ifdef HAVE_IN6_PKTINFO + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo ipi; + + if (message->addr_type != SCK_ADDR_IP) + init_message_addresses(message, SCK_ADDR_IP); + + memcpy(&ipi, CMSG_DATA(cmsg), sizeof (ipi)); + memcpy(&message->local_addr.ip.addr.in6, &ipi.ipi6_addr.s6_addr, + sizeof (message->local_addr.ip.addr.in6)); + message->local_addr.ip.family = IPADDR_INET6; + message->if_index = ipi.ipi6_ifindex; + } +#endif + +#ifdef SCM_TIMESTAMP + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) { + struct timeval tv; + + memcpy(&tv, CMSG_DATA(cmsg), sizeof (tv)); + UTI_TimevalToTimespec(&tv, &message->timestamp.kernel); + } +#endif + +#ifdef SCM_TIMESTAMPNS + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPNS) { + memcpy(&message->timestamp.kernel, CMSG_DATA(cmsg), sizeof (message->timestamp.kernel)); + } +#endif + +#ifdef HAVE_LINUX_TIMESTAMPING +#ifdef HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING_PKTINFO) { + struct scm_ts_pktinfo ts_pktinfo; + + memcpy(&ts_pktinfo, CMSG_DATA(cmsg), sizeof (ts_pktinfo)); + message->timestamp.if_index = ts_pktinfo.if_index; + message->timestamp.l2_length = ts_pktinfo.pkt_length; + } +#endif + + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) { + struct scm_timestamping ts3; + + memcpy(&ts3, CMSG_DATA(cmsg), sizeof (ts3)); + message->timestamp.kernel = ts3.ts[0]; + message->timestamp.hw = ts3.ts[2]; + } + + if ((cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) || + (cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_RECVERR)) { + struct sock_extended_err err; + + memcpy(&err, CMSG_DATA(cmsg), sizeof (err)); + + if (err.ee_errno != ENOMSG || err.ee_info != SCM_TSTAMP_SND || + err.ee_origin != SO_EE_ORIGIN_TIMESTAMPING) { + log_message(sock_fd, 1, message, "Unexpected extended error in", NULL); + return 0; + } + } +#endif + } + + return 1; +} + +/* ================================================== */ + +static int +receive_messages(int sock_fd, SCK_Message *messages, int max_messages, int flags) +{ + struct MessageHeader *hdr; + unsigned int i, n; + int ret, recv_flags = 0; + + assert(initialised); + + if (max_messages < 1) + return 0; + + /* Prepare used buffers for new messages */ + prepare_buffers(received_messages); + received_messages = 0; + + hdr = ARR_GetElements(recv_headers); + n = ARR_GetSize(recv_headers); + n = MIN(n, max_messages); + assert(n >= 1); + + recv_flags = get_recv_flags(flags); + +#ifdef HAVE_RECVMMSG + ret = recvmmsg(sock_fd, hdr, n, recv_flags | MSG_DONTWAIT, NULL); + if (ret >= 0) + n = ret; +#else + n = 1; + ret = recvmsg(sock_fd, &hdr[0].msg_hdr, recv_flags); + if (ret >= 0) + hdr[0].msg_len = ret; +#endif + + if (ret < 0) { + handle_recv_error(sock_fd, flags); + return 0; + } + + received_messages = n; + + for (i = 0; i < n; i++) { + hdr = ARR_GetElement(recv_headers, i); + if (!process_header(&hdr->msg_hdr, hdr->msg_len, sock_fd, &messages[i])) + return 0; + + log_message(sock_fd, 1, &messages[i], + flags & SCK_FLAG_MSG_ERRQUEUE ? "Received error" : "Received", NULL); + } + + return n; +} + +/* ================================================== */ + +static void * +add_control_message(struct msghdr *msg, int level, int type, size_t length, size_t buf_length) +{ + struct cmsghdr *cmsg; + size_t cmsg_space; + + /* Avoid using CMSG_NXTHDR as the one in glibc does not support adding + control messages: https://sourceware.org/bugzilla/show_bug.cgi?id=13500 */ + + cmsg = msg->msg_control; + cmsg_space = CMSG_SPACE(length); + + if (!cmsg || length > buf_length || msg->msg_controllen + cmsg_space > buf_length) { + DEBUG_LOG("Could not add control message level=%d type=%d", level, type); + return NULL; + } + + cmsg = (struct cmsghdr *)((char *)cmsg + msg->msg_controllen); + + memset(cmsg, 0, cmsg_space); + + cmsg->cmsg_level = level; + cmsg->cmsg_type = type; + cmsg->cmsg_len = CMSG_LEN(length); + + msg->msg_controllen += cmsg_space; + + return CMSG_DATA(cmsg); +} + +/* ================================================== */ + +static int +send_message(int sock_fd, SCK_Message *message, int flags) +{ + struct cmsghdr cmsg_buf[CMSG_BUF_SIZE / sizeof (struct cmsghdr)]; + union sockaddr_all saddr; + socklen_t saddr_len; + struct msghdr msg; + struct iovec iov; + + assert(flags == 0); + + switch (message->addr_type) { + case SCK_ADDR_UNSPEC: + saddr_len = 0; + break; + case SCK_ADDR_IP: + saddr_len = ipsockaddr_to_sockaddr(&message->remote_addr.ip, &saddr); + break; + case SCK_ADDR_UNIX: + memset(&saddr, 0, sizeof (saddr)); + if (snprintf(saddr.un.sun_path, sizeof (saddr.un.sun_path), "%s", + message->remote_addr.path) >= sizeof (saddr.un.sun_path)) { + DEBUG_LOG("Unix socket path %s too long", message->remote_addr.path); + return 0; + } + saddr.un.sun_family = AF_UNIX; + saddr_len = sizeof (saddr.un); + break; + default: + assert(0); + } + + if (saddr_len) { + msg.msg_name = &saddr.un; + msg.msg_namelen = saddr_len; + } else { + msg.msg_name = NULL; + msg.msg_namelen = 0; + } + + iov.iov_base = message->data; + iov.iov_len = message->length; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if (message->addr_type == SCK_ADDR_IP) { + if (message->local_addr.ip.family == IPADDR_INET4) { +#ifdef HAVE_IN_PKTINFO + struct in_pktinfo *ipi; + + ipi = add_control_message(&msg, IPPROTO_IP, IP_PKTINFO, sizeof (*ipi), + sizeof (cmsg_buf)); + if (!ipi) + return 0; + + ipi->ipi_spec_dst.s_addr = htonl(message->local_addr.ip.addr.in4); + if (message->if_index != INVALID_IF_INDEX) + ipi->ipi_ifindex = message->if_index; + +#elif defined(IP_SENDSRCADDR) + struct in_addr *addr; + + addr = add_control_message(&msg, IPPROTO_IP, IP_SENDSRCADDR, sizeof (*addr), + sizeof (cmsg_buf)); + if (!addr) + return 0; + + addr->s_addr = htonl(message->local_addr.ip.addr.in4); +#endif + } + +#ifdef HAVE_IN6_PKTINFO + if (message->local_addr.ip.family == IPADDR_INET6) { + struct in6_pktinfo *ipi; + + ipi = add_control_message(&msg, IPPROTO_IPV6, IPV6_PKTINFO, sizeof (*ipi), + sizeof (cmsg_buf)); + if (!ipi) + return 0; + + memcpy(&ipi->ipi6_addr.s6_addr, &message->local_addr.ip.addr.in6, + sizeof(ipi->ipi6_addr.s6_addr)); + if (message->if_index != INVALID_IF_INDEX) + ipi->ipi6_ifindex = message->if_index; + } +#endif + } + +#ifdef HAVE_LINUX_TIMESTAMPING + if (message->timestamp.tx_flags) { + int *ts_tx_flags; + + /* Set timestamping flags for this message */ + + ts_tx_flags = add_control_message(&msg, SOL_SOCKET, SO_TIMESTAMPING, + sizeof (*ts_tx_flags), sizeof (cmsg_buf)); + if (!ts_tx_flags) + return 0; + + *ts_tx_flags = message->timestamp.tx_flags; + } +#endif + + /* This is apparently required on some systems */ + if (msg.msg_controllen == 0) + msg.msg_control = NULL; + + if (sendmsg(sock_fd, &msg, 0) < 0) { + log_message(sock_fd, -1, message, "Could not send", strerror(errno)); + return 0; + } + + log_message(sock_fd, -1, message, "Sent", NULL); + + return 1; +} + +/* ================================================== */ + +void +SCK_Initialise(void) +{ + 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); + + received_messages = MAX_RECV_MESSAGES; + + priv_bind_function = NULL; + + initialised = 1; +} + +/* ================================================== */ + +void +SCK_Finalise(void) +{ + ARR_DestroyInstance(recv_headers); + ARR_DestroyInstance(recv_messages); + + initialised = 0; +} + +/* ================================================== */ + +void +SCK_GetAnyLocalIPAddress(int family, IPAddr *local_addr) +{ + local_addr->family = family; + + switch (family) { + case IPADDR_INET4: + local_addr->addr.in4 = INADDR_ANY; + break; + case IPADDR_INET6: +#ifdef FEAT_IPV6 + memcpy(&local_addr->addr.in6, &in6addr_any, sizeof (local_addr->addr.in6)); +#else + memset(&local_addr->addr.in6, 0, sizeof (local_addr->addr.in6)); +#endif + break; + } +} + +/* ================================================== */ + +void +SCK_GetLoopbackIPAddress(int family, IPAddr *local_addr) +{ + local_addr->family = family; + + switch (family) { + case IPADDR_INET4: + local_addr->addr.in4 = INADDR_LOOPBACK; + break; + case IPADDR_INET6: +#ifdef FEAT_IPV6 + memcpy(&local_addr->addr.in6, &in6addr_loopback, sizeof (local_addr->addr.in6)); +#else + memset(&local_addr->addr.in6, 0, sizeof (local_addr->addr.in6)); + local_addr->addr.in6[15] = 1; +#endif + break; + } +} + +/* ================================================== */ + +void +SCK_SetPrivBind(int (*function)(int sock_fd, struct sockaddr *address, + socklen_t address_len)) +{ + priv_bind_function = function; +} + +/* ================================================== */ + +int +SCK_OpenUdpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, int flags) +{ + return open_ip_socket(remote_addr, local_addr, SOCK_DGRAM, flags); +} + +/* ================================================== */ + +int +SCK_OpenUnixDatagramSocket(const char *remote_addr, const char *local_addr, int flags) +{ + return open_unix_socket(remote_addr, local_addr, SOCK_DGRAM, flags); +} + +/* ================================================== */ + +int +SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_addr, int flags) +{ + return open_unix_socket(remote_addr, local_addr, SOCK_STREAM, flags); +} + +/* ================================================== */ + +int +SCK_SetIntOption(int sock_fd, int level, int name, int value) +{ + if (setsockopt(sock_fd, level, name, &value, sizeof (value)) < 0) { + DEBUG_LOG("setsockopt() failed fd=%d level=%d name=%d value=%d : %s", + sock_fd, level, name, value, strerror(errno)); + return 0; + } + + return 1; +} + +/* ================================================== */ + +int +SCK_EnableKernelRxTimestamping(int sock_fd) +{ +#ifdef SO_TIMESTAMPNS + if (SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMPNS, 1)) + return 1; +#endif +#ifdef SO_TIMESTAMP + if (SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMP, 1)) + return 1; +#endif + + return 0; +} + +/* ================================================== */ + +int +SCK_Receive(int sock_fd, void *buffer, unsigned int length, int flags) +{ + int r; + + r = recv(sock_fd, buffer, length, get_recv_flags(flags)); + + if (r < 0) { + handle_recv_error(sock_fd, flags); + return r; + } + + DEBUG_LOG("Received data fd=%d len=%d", sock_fd, r); + + return r; +} + +/* ================================================== */ + +int +SCK_Send(int sock_fd, void *buffer, unsigned int length, int flags) +{ + int r; + + assert(flags == 0); + + r = send(sock_fd, buffer, length, 0); + + if (r < 0) { + DEBUG_LOG("Could not send data fd=%d len=%u : %s", sock_fd, length, strerror(errno)); + return r; + } + + DEBUG_LOG("Sent data fd=%d len=%d", sock_fd, r); + + return r; +} + +/* ================================================== */ + +int +SCK_ReceiveMessage(int sock_fd, SCK_Message *message, int flags) +{ + return SCK_ReceiveMessages(sock_fd, message, 1, flags); +} + +/* ================================================== */ + +int +SCK_ReceiveMessages(int sock_fd, SCK_Message *messages, int max_messages, int flags) +{ + return receive_messages(sock_fd, messages, max_messages, flags); +} + +/* ================================================== */ + +void +SCK_InitMessage(SCK_Message *message, SCK_AddressType addr_type) +{ + init_message_addresses(message, addr_type); + init_message_nonaddress(message); +} + +/* ================================================== */ + +int +SCK_SendMessage(int sock_fd, SCK_Message *message, int flags) +{ + return send_message(sock_fd, message, flags); +} + +/* ================================================== */ + +int +SCK_RemoveSocket(int sock_fd) +{ + union sockaddr_all saddr; + socklen_t saddr_len; + + saddr_len = sizeof (saddr); + + if (getsockname(sock_fd, &saddr.sa, &saddr_len) < 0) { + DEBUG_LOG("getsockname() failed : %s", strerror(errno)); + return 0; + } + + if (saddr_len > sizeof (saddr) || saddr_len <= sizeof (saddr.sa.sa_family) || + saddr.sa.sa_family != AF_UNIX) + return 0; + + if (unlink(saddr.un.sun_path) < 0) { + DEBUG_LOG("unlink(%s) failed : %s", saddr.un.sun_path, strerror(errno)); + return 0; + } + + DEBUG_LOG("Removed %s", saddr.un.sun_path); + + return 1; +} + +/* ================================================== */ + +void +SCK_CloseSocket(int sock_fd) +{ + close(sock_fd); +} diff --git a/socket.h b/socket.h new file mode 100644 index 0000000..5bc317f --- /dev/null +++ b/socket.h @@ -0,0 +1,127 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2019 + * + * 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 socket operations. + + */ + +#ifndef GOT_SOCKET_H +#define GOT_SOCKET_H + +#include "addressing.h" + +/* Flags for opening sockets */ +#define SCK_FLAG_NONBLOCK 1 +#define SCK_FLAG_BROADCAST 2 +#define SCK_FLAG_RX_DEST_ADDR 4 +#define SCK_FLAG_ALL_PERMISSIONS 8 +#define SCK_FLAG_PRIV_BIND 16 + +/* Flags for receiving and sending messages */ +#define SCK_FLAG_MSG_ERRQUEUE 1 + +/* Maximum number of received messages */ +#define SCK_MAX_RECV_MESSAGES 4 + +typedef enum { + SCK_ADDR_UNSPEC = 0, + SCK_ADDR_IP, + SCK_ADDR_UNIX +} SCK_AddressType; + +typedef struct { + void *data; + unsigned int length; + SCK_AddressType addr_type; + int if_index; + + union { + IPSockAddr ip; + const char *path; + } remote_addr; + + union { + IPAddr ip; + } local_addr; + + struct { + struct timespec kernel; + struct timespec hw; + int if_index; + int l2_length; + int tx_flags; + } timestamp; +} SCK_Message; + +/* Initialisation function */ +extern void SCK_Initialise(void); + +/* Finalisation function */ +extern void SCK_Finalise(void); + +/* Get the 0.0.0.0/::0 or 127.0.0.1/::1 address */ +extern void SCK_GetAnyLocalIPAddress(int family, IPAddr *local_addr); +extern void SCK_GetLoopbackIPAddress(int family, IPAddr *local_addr); + +/* Specify a bind()-like function for binding sockets to privileged ports when + running in a restricted process (e.g. after dropping root privileges) */ +extern void SCK_SetPrivBind(int (*function)(int sock_fd, struct sockaddr *address, + socklen_t address_len)); + +/* Open socket */ +extern int SCK_OpenUdpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, int flags); +extern int SCK_OpenTcpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, int flags); +extern int SCK_OpenUnixDatagramSocket(const char *remote_addr, const char *local_addr, + int flags); +extern int SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_addr, + int flags); + +/* Set a socket option */ +extern int SCK_SetIntOption(int sock_fd, int level, int option, int value); +/* Enable RX timestamping socket option */ +extern int SCK_EnableKernelRxTimestamping(int sock_fd); + +/* Receive and send data on connected sockets - recv()/send() wrappers */ +extern int SCK_Receive(int sock_fd, void *buffer, unsigned int length, int flags); +extern int SCK_Send(int sock_fd, void *buffer, unsigned int length, int flags); + +/* Receive a single message or multiple messages. The functions return the + number of received messages, or 0 on error. The returned data point to + static buffers, which are valid until another call of these functions. */ +extern int SCK_ReceiveMessage(int sock_fd, SCK_Message *message, int flags); +extern int SCK_ReceiveMessages(int sock_fd, SCK_Message *messages, int max_messages, + int flags); + +/* Initialise a new message (e.g. before sending) */ +extern void SCK_InitMessage(SCK_Message *message, SCK_AddressType addr_type); + +/* Send a message */ +extern int SCK_SendMessage(int sock_fd, SCK_Message *message, int flags); + +/* Remove bound Unix socket */ +extern int SCK_RemoveSocket(int sock_fd); + +/* Close the socket */ +extern void SCK_CloseSocket(int sock_fd); + +#endif