summaryrefslogtreecommitdiffstats
path: root/socket.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 12:48:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 12:48:01 +0000
commitb2d2d555a704148968cb7e566735a2a1b1a2f189 (patch)
tree18549ff498338f40ecf7aa327620abf4c1c3ee43 /socket.c
parentInitial commit. (diff)
downloadchrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.tar.xz
chrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.zip
Adding upstream version 4.5.upstream/4.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'socket.c')
-rw-r--r--socket.c1803
1 files changed, 1803 insertions, 0 deletions
diff --git a/socket.c b/socket.c
new file mode 100644
index 0000000..5b22db5
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,1803 @@
+/*
+ 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-2020
+ * Copyright (C) Luke Valenta 2023
+ *
+ * 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 <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+#endif
+
+#include "socket.h"
+#include "array.h"
+#include "logging.h"
+#include "privops.h"
+#include "ptp.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 */
+ struct {
+ /* Extra space for Ethernet, IPv4/IPv6, and UDP headers in
+ timestamped messages received from the Linux error queue */
+ uint8_t l234_headers[64];
+ union {
+ NTP_Packet ntp_msg;
+ PTP_NtpMessage ptp_msg;
+ CMD_Request cmd_request;
+ CMD_Reply cmd_reply;
+ } msg;
+ } msg_buf;
+ /* Aligned buffer for control messages */
+ struct cmsghdr cmsg_buf[CMSG_BUF_SIZE / sizeof (struct cmsghdr)];
+};
+
+#ifdef HAVE_RECVMMSG
+#define MAX_RECV_MESSAGES 16
+#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;
+
+static int first_reusable_fd;
+static int reusable_fds;
+
+/* Flags indicating in which IP families sockets can be requested */
+static int ip4_enabled;
+static int ip6_enabled;
+
+/* Flags supported by socket() */
+static int supported_socket_flags;
+
+/* Arrays of Message, MessageHeader, and SCK_Message */
+static ARR_Instance recv_messages;
+static ARR_Instance recv_headers;
+static ARR_Instance recv_sck_messages;
+
+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 const char *
+domain_to_string(int domain)
+{
+ switch (domain) {
+ case AF_INET:
+ return "IPv4";
+#ifdef AF_INET6
+ case AF_INET6:
+ return "IPv6";
+#endif
+ case AF_UNIX:
+ return "Unix";
+ case AF_UNSPEC:
+ return "UNSPEC";
+ default:
+ return "?";
+ }
+}
+
+/* ================================================== */
+
+static int
+get_reusable_socket(int type, IPSockAddr *spec)
+{
+#ifdef LINUX
+ union sockaddr_all sa;
+ IPSockAddr ip_sa;
+ int sock_fd, opt;
+ socklen_t l;
+
+ /* Abort early if not an IPv4/IPv6 server socket */
+ if (!spec || spec->ip_addr.family == IPADDR_UNSPEC || spec->port == 0)
+ return INVALID_SOCK_FD;
+
+ /* Loop over available reusable sockets */
+ for (sock_fd = first_reusable_fd; sock_fd < first_reusable_fd + reusable_fds; sock_fd++) {
+
+ /* Check that types match */
+ l = sizeof (opt);
+ if (getsockopt(sock_fd, SOL_SOCKET, SO_TYPE, &opt, &l) < 0 ||
+ l != sizeof (opt) || opt != type)
+ continue;
+
+ /* Get sockaddr for reusable socket */
+ l = sizeof (sa);
+ if (getsockname(sock_fd, &sa.sa, &l) < 0 || l < sizeof (sa_family_t))
+ continue;
+ SCK_SockaddrToIPSockAddr(&sa.sa, l, &ip_sa);
+
+ /* Check that reusable socket matches specification */
+ if (ip_sa.port != spec->port || UTI_CompareIPs(&ip_sa.ip_addr, &spec->ip_addr, NULL) != 0)
+ continue;
+
+ /* Check that STREAM socket is listening */
+ l = sizeof (opt);
+ if (type == SOCK_STREAM && (getsockopt(sock_fd, SOL_SOCKET, SO_ACCEPTCONN, &opt, &l) < 0 ||
+ l != sizeof (opt) || opt == 0))
+ continue;
+
+#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
+ if (spec->ip_addr.family == IPADDR_INET6 &&
+ (!SCK_GetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt) || opt != 1))
+ LOG(LOGS_WARN, "Reusable IPv6 socket missing IPV6_V6ONLY option");
+#endif
+
+ return sock_fd;
+ }
+#endif
+
+ return INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+#if defined(SOCK_CLOEXEC) || defined(SOCK_NONBLOCK)
+static int
+check_socket_flag(int sock_flag, int fd_flag, int fs_flag)
+{
+ int sock_fd, fd_flags, fs_flags;
+
+ sock_fd = socket(AF_INET, SOCK_DGRAM | sock_flag, 0);
+ if (sock_fd < 0)
+ return 0;
+
+ fd_flags = fcntl(sock_fd, F_GETFD);
+ fs_flags = fcntl(sock_fd, F_GETFL);
+
+ close(sock_fd);
+
+ if (fd_flags == -1 || (fd_flags & fd_flag) != fd_flag ||
+ fs_flags == -1 || (fs_flags & fs_flag) != fs_flag)
+ return 0;
+
+ return 1;
+}
+#endif
+
+/* ================================================== */
+
+static int
+set_socket_nonblock(int sock_fd)
+{
+ if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) < 0) {
+ DEBUG_LOG("Could not set O_NONBLOCK : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+get_open_flags(int flags)
+{
+ int r = supported_socket_flags;
+
+#ifdef SOCK_NONBLOCK
+ if (flags & SCK_FLAG_BLOCK)
+ r &= ~SOCK_NONBLOCK;
+#endif
+
+ return r;
+}
+
+/* ================================================== */
+
+static int
+set_socket_flags(int sock_fd, int flags)
+{
+ /* Close the socket automatically on exec */
+ if (!SCK_IsReusable(sock_fd) &&
+#ifdef SOCK_CLOEXEC
+ (supported_socket_flags & SOCK_CLOEXEC) == 0 &&
+#endif
+ !UTI_FdSetCloexec(sock_fd))
+ return 0;
+
+ /* Enable non-blocking mode */
+ if ((flags & SCK_FLAG_BLOCK) == 0 &&
+#ifdef SOCK_NONBLOCK
+ (SCK_IsReusable(sock_fd) || (supported_socket_flags & SOCK_NONBLOCK) == 0) &&
+#endif
+ !set_socket_nonblock(sock_fd))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+open_socket(int domain, int type, int flags)
+{
+ int sock_fd;
+
+ sock_fd = socket(domain, type | get_open_flags(flags), 0);
+
+ if (sock_fd < 0) {
+ DEBUG_LOG("Could not open %s socket : %s",
+ domain_to_string(domain), strerror(errno));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!set_socket_flags(sock_fd, flags)) {
+ close(sock_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static int
+open_socket_pair(int domain, int type, int flags, int *other_fd)
+{
+ int sock_fds[2];
+
+ if (socketpair(domain, type | get_open_flags(flags), 0, sock_fds) < 0) {
+ DEBUG_LOG("Could not open %s socket : %s",
+ domain_to_string(domain), strerror(errno));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!set_socket_flags(sock_fds[0], flags) || !set_socket_flags(sock_fds[1], flags)) {
+ close(sock_fds[0]);
+ close(sock_fds[1]);
+ return INVALID_SOCK_FD;
+ }
+
+ *other_fd = sock_fds[1];
+
+ return sock_fds[0];
+}
+
+/* ================================================== */
+
+static int
+get_ip_socket(int domain, int type, int flags, IPSockAddr *ip_sa)
+{
+ int sock_fd;
+
+ /* Check if there is a matching reusable socket */
+ sock_fd = get_reusable_socket(type, ip_sa);
+
+ if (sock_fd < 0) {
+ sock_fd = open_socket(domain, type, flags);
+
+ /* Unexpected, but make sure the new socket is not in the reusable range */
+ if (SCK_IsReusable(sock_fd))
+ LOG_FATAL("Could not open %s socket : file descriptor in reusable range",
+ domain_to_string(domain));
+ } else {
+ /* Set socket flags on reusable socket */
+ if (!set_socket_flags(sock_fd, flags))
+ 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, but do not attempt
+ to set this option on pre-initialised reuseable sockets */
+ if (family == IPADDR_INET6 && !SCK_IsReusable(sock_fd) &&
+ !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
+is_any_address(IPAddr *addr)
+{
+ IPAddr any_addr;
+
+ SCK_GetAnyLocalIPAddress(addr->family, &any_addr);
+
+ return UTI_CompareIPs(&any_addr, addr, NULL) == 0;
+}
+
+/* ================================================== */
+
+static int
+bind_device(int sock_fd, const char *iface)
+{
+#ifdef SO_BINDTODEVICE
+ if (setsockopt(sock_fd, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) < 0) {
+ DEBUG_LOG("Could not bind socket to %s : %s", iface, strerror(errno));
+ return 0;
+ }
+ return 1;
+#else
+ DEBUG_LOG("Could not bind socket to %s : %s", iface, "Not supported");
+ return 0;
+#endif
+}
+
+/* ================================================== */
+
+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))
+ ;
+
+#if defined(LINUX) && defined(SO_REUSEPORT)
+ /* Allow multiple instances to bind to the same port in order to enable load
+ balancing. Don't enable this option on non-Linux systems as it has
+ a slightly different meaning there (with some important implications). */
+ if (addr->port > 0 && !SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_REUSEPORT, 1))
+ ;
+#endif
+
+#ifdef IP_FREEBIND
+ /* Allow binding to an address that doesn't exist yet */
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_FREEBIND, 1))
+ ;
+#endif
+
+ /* Do not attempt to bind pre-initialised reusable socket */
+ if (SCK_IsReusable(sock_fd))
+ return 1;
+
+ saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (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 = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (saddr));
+ if (saddr_len == 0)
+ return 0;
+
+ if (connect(sock_fd, &saddr.sa, saddr_len) < 0 && errno != EINPROGRESS) {
+ 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, const char *iface,
+ 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:
+ if (!ip4_enabled)
+ return INVALID_SOCK_FD;
+ domain = AF_INET;
+ break;
+#ifdef FEAT_IPV6
+ case IPADDR_INET6:
+ if (!ip6_enabled)
+ return INVALID_SOCK_FD;
+ domain = AF_INET6;
+ break;
+#endif
+ default:
+ DEBUG_LOG("Unspecified family");
+ return INVALID_SOCK_FD;
+ }
+
+ sock_fd = get_ip_socket(domain, type, flags, local_addr);
+ 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;
+
+ if (iface && !bind_device(sock_fd, iface))
+ goto error;
+
+ /* Bind the socket if a non-any local address/port was specified */
+ if (local_addr && local_addr->ip_addr.family != IPADDR_UNSPEC &&
+ (local_addr->port != 0 || !is_any_address(&local_addr->ip_addr)) &&
+ !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("%s %s%s socket fd=%d%s%s%s%s",
+ SCK_IsReusable(sock_fd) ? "Reusing" : "Opened",
+ type == SOCK_DGRAM ? "UDP" : type == SOCK_STREAM ? "TCP" : "?",
+ family == IPADDR_INET4 ? "v4" : "v6",
+ 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;
+
+ memset(&saddr, 0, sizeof (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 (unlink(addr) < 0)
+ DEBUG_LOG("Could not remove %s : %s", addr, strerror(errno));
+
+ /* 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;
+
+ memset(&saddr, 0, sizeof (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
+open_unix_socket_pair(int type, int flags, int *other_fd)
+{
+ int sock_fd;
+
+ sock_fd = open_socket_pair(AF_UNIX, type, flags, other_fd);
+ if (sock_fd < 0)
+ return INVALID_SOCK_FD;
+
+ DEBUG_LOG("Opened Unix socket pair fd1=%d fd2=%d", sock_fd, *other_fd);
+
+ return 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;
+
+ if (SCK_GetIntOption(sock_fd, SOL_SOCKET, SO_ERROR, &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=%d%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;
+
+ message->descriptor = INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+static int
+match_cmsg(struct cmsghdr *cmsg, int level, int type, size_t length)
+{
+ if (cmsg->cmsg_type == type && cmsg->cmsg_level == level &&
+ (length == 0 || cmsg->cmsg_len == CMSG_LEN(length)))
+ return 1;
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+process_header(struct msghdr *msg, int msg_length, int sock_fd, int flags,
+ SCK_Message *message)
+{
+ struct cmsghdr *cmsg;
+ int r = 1;
+
+ if (msg->msg_namelen <= sizeof (union sockaddr_all) &&
+ 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);
+ SCK_SockaddrToIPSockAddr(msg->msg_name, msg->msg_namelen, &message->remote_addr.ip);
+ 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:
+ init_message_addresses(message, SCK_ADDR_UNSPEC);
+ DEBUG_LOG("Unexpected address");
+ r = 0;
+ break;
+ }
+ } else {
+ init_message_addresses(message, SCK_ADDR_UNSPEC);
+
+ if (msg->msg_namelen > sizeof (union sockaddr_all)) {
+ DEBUG_LOG("Truncated source address");
+ r = 0;
+ }
+ }
+
+ init_message_nonaddress(message);
+
+ if (msg->msg_iovlen == 1) {
+ message->data = msg->msg_iov[0].iov_base;
+ message->length = msg_length;
+ } else {
+ DEBUG_LOG("Unexpected iovlen");
+ r = 0;
+ }
+
+ if (msg->msg_flags & MSG_TRUNC) {
+ log_message(sock_fd, 1, message, "Truncated", NULL);
+ r = 0;
+ }
+
+ if (msg->msg_flags & MSG_CTRUNC) {
+ log_message(sock_fd, 1, message, "Truncated cmsg in", NULL);
+ r = 0;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (0) {
+ }
+#ifdef HAVE_IN_PKTINFO
+ else if (match_cmsg(cmsg, IPPROTO_IP, IP_PKTINFO, sizeof (struct in_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)
+ else if (match_cmsg(cmsg, IPPROTO_IP, IP_RECVDSTADDR, sizeof (struct in_addr))) {
+ 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
+ else if (match_cmsg(cmsg, IPPROTO_IPV6, IPV6_PKTINFO, sizeof (struct in6_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
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMP, sizeof (struct timeval))) {
+ struct timeval tv;
+
+ memcpy(&tv, CMSG_DATA(cmsg), sizeof (tv));
+ UTI_TimevalToTimespec(&tv, &message->timestamp.kernel);
+ }
+#endif
+#ifdef SCM_TIMESTAMPNS
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMPNS, sizeof (message->timestamp.kernel))) {
+ memcpy(&message->timestamp.kernel, CMSG_DATA(cmsg), sizeof (message->timestamp.kernel));
+ }
+#endif
+#ifdef SCM_REALTIME
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_REALTIME, sizeof (message->timestamp.kernel))) {
+ memcpy(&message->timestamp.kernel, CMSG_DATA(cmsg), sizeof (message->timestamp.kernel));
+ }
+#endif
+#ifdef HAVE_LINUX_TIMESTAMPING
+#ifdef HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMPING_PKTINFO,
+ sizeof (struct scm_ts_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
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMPING,
+ sizeof (struct 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];
+ }
+ else if ((match_cmsg(cmsg, SOL_IP, IP_RECVERR, 0) ||
+ match_cmsg(cmsg, SOL_IPV6, IPV6_RECVERR, 0)) &&
+ cmsg->cmsg_len >= CMSG_LEN(sizeof (struct sock_extended_err))) {
+ 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);
+ r = 0;
+ }
+ }
+#endif
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_RIGHTS, 0)) {
+ if (!(flags & SCK_FLAG_MSG_DESCRIPTOR) || cmsg->cmsg_len != CMSG_LEN(sizeof (int))) {
+ int i, fd;
+
+ DEBUG_LOG("Unexpected SCM_RIGHTS");
+ for (i = 0; CMSG_LEN((i + 1) * sizeof (int)) <= cmsg->cmsg_len; i++) {
+ memcpy(&fd, (char *)CMSG_DATA(cmsg) + i * sizeof (int), sizeof (fd));
+ close(fd);
+ }
+ r = 0;
+ } else {
+ memcpy(&message->descriptor, CMSG_DATA(cmsg), sizeof (message->descriptor));
+ }
+ }
+ else {
+ DEBUG_LOG("Unexpected control message level=%d type=%d len=%d",
+ cmsg->cmsg_level, cmsg->cmsg_type, (int)cmsg->cmsg_len);
+ }
+ }
+
+ if (!r && message->descriptor != INVALID_SOCK_FD)
+ close(message->descriptor);
+
+ return r;
+}
+
+/* ================================================== */
+
+static SCK_Message *
+receive_messages(int sock_fd, int flags, int max_messages, int *num_messages)
+{
+ struct MessageHeader *hdr;
+ SCK_Message *messages;
+ unsigned int i, n, n_ok;
+ int ret, recv_flags = 0;
+
+ assert(initialised);
+
+ *num_messages = 0;
+
+ if (max_messages < 1)
+ return NULL;
+
+ /* Prepare used buffers for new messages */
+ prepare_buffers(received_messages);
+ received_messages = 0;
+
+ messages = ARR_GetElements(recv_sck_messages);
+
+ hdr = ARR_GetElements(recv_headers);
+ n = ARR_GetSize(recv_headers);
+ n = MIN(n, max_messages);
+
+ if (n < 1 || n > MAX_RECV_MESSAGES ||
+ n > ARR_GetSize(recv_messages) || n > ARR_GetSize(recv_sck_messages))
+ assert(0);
+
+ recv_flags = get_recv_flags(flags);
+
+#ifdef HAVE_RECVMMSG
+ ret = recvmmsg(sock_fd, hdr, n, recv_flags, 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 NULL;
+ }
+
+ received_messages = n;
+
+ for (i = n_ok = 0; i < n; i++) {
+ hdr = ARR_GetElement(recv_headers, i);
+ if (!process_header(&hdr->msg_hdr, hdr->msg_len, sock_fd, flags, &messages[n_ok]))
+ continue;
+
+ log_message(sock_fd, 1, &messages[n_ok],
+ flags & SCK_FLAG_MSG_ERRQUEUE ? "Received error" : "Received", NULL);
+
+ n_ok++;
+ }
+
+ *num_messages = n_ok;
+
+ return n_ok > 0 ? messages : NULL;
+}
+
+/* ================================================== */
+
+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;
+
+ switch (message->addr_type) {
+ case SCK_ADDR_UNSPEC:
+ saddr_len = 0;
+ break;
+ case SCK_ADDR_IP:
+ saddr_len = SCK_IPSockAddrToSockaddr(&message->remote_addr.ip,
+ (struct sockaddr *)&saddr, sizeof (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;
+ }
+
+ if (message->length < 0) {
+ DEBUG_LOG("Invalid length %d", message->length);
+ return 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
+
+ if (flags & SCK_FLAG_MSG_DESCRIPTOR) {
+ int *fd;
+
+ fd = add_control_message(&msg, SOL_SOCKET, SCM_RIGHTS, sizeof (*fd), sizeof (cmsg_buf));
+ if (!fd)
+ return 0;
+
+ *fd = message->descriptor;
+ }
+
+ /* 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_PreInitialise(void)
+{
+#ifdef LINUX
+ char *s, *ptr;
+
+ /* On Linux systems, the systemd service manager may pass file descriptors
+ for pre-initialised sockets to the chronyd daemon. The service manager
+ allocates and binds the file descriptors, and passes a copy to each
+ spawned instance of the service. This allows for zero-downtime service
+ restarts as the sockets buffer client requests until the service is able
+ to handle them. The service manager sets the LISTEN_FDS environment
+ variable to the number of passed file descriptors, and the integer file
+ descriptors start at 3 (see SD_LISTEN_FDS_START in
+ https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html). */
+ first_reusable_fd = 3;
+ reusable_fds = 0;
+
+ s = getenv("LISTEN_FDS");
+ if (s) {
+ errno = 0;
+ reusable_fds = strtol(s, &ptr, 10);
+ if (errno != 0 || *ptr != '\0' || reusable_fds < 0)
+ reusable_fds = 0;
+ }
+#else
+ first_reusable_fd = 0;
+ reusable_fds = 0;
+#endif
+}
+
+/* ================================================== */
+
+void
+SCK_Initialise(int family)
+{
+ int fd;
+
+ ip4_enabled = family == IPADDR_INET4 || family == IPADDR_UNSPEC;
+#ifdef FEAT_IPV6
+ ip6_enabled = family == IPADDR_INET6 || family == IPADDR_UNSPEC;
+#else
+ ip6_enabled = 0;
+#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);
+ recv_sck_messages = ARR_CreateInstance(sizeof (SCK_Message));
+ ARR_SetSize(recv_sck_messages, MAX_RECV_MESSAGES);
+
+ received_messages = MAX_RECV_MESSAGES;
+
+ priv_bind_function = NULL;
+
+ supported_socket_flags = 0;
+#ifdef SOCK_CLOEXEC
+ if (check_socket_flag(SOCK_CLOEXEC, FD_CLOEXEC, 0))
+ supported_socket_flags |= SOCK_CLOEXEC;
+#endif
+#ifdef SOCK_NONBLOCK
+ if (check_socket_flag(SOCK_NONBLOCK, 0, O_NONBLOCK))
+ supported_socket_flags |= SOCK_NONBLOCK;
+#endif
+
+ for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
+ UTI_FdSetCloexec(fd);
+
+ initialised = 1;
+}
+
+/* ================================================== */
+
+void
+SCK_Finalise(void)
+{
+ ARR_DestroyInstance(recv_sck_messages);
+ ARR_DestroyInstance(recv_headers);
+ ARR_DestroyInstance(recv_messages);
+
+ SCK_CloseReusableSockets();
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+int
+SCK_IsIpFamilyEnabled(int family)
+{
+ switch (family) {
+ case IPADDR_INET4:
+ return ip4_enabled;
+ case IPADDR_INET6:
+ return ip6_enabled;
+ default:
+ return 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;
+ }
+}
+
+/* ================================================== */
+
+int
+SCK_IsLinkLocalIPAddress(IPAddr *addr)
+{
+ switch (addr->family) {
+ case IPADDR_INET4:
+ /* 169.254.0.0/16 */
+ return (addr->addr.in4 & 0xffff0000) == 0xa9fe0000;
+ case IPADDR_INET6:
+ /* fe80::/10 */
+ return addr->addr.in6[0] == 0xfe && (addr->addr.in6[1] & 0xc0) == 0x80;
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+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, const char *iface, int flags)
+{
+ return open_ip_socket(remote_addr, local_addr, iface, SOCK_DGRAM, flags);
+}
+
+/* ================================================== */
+
+int
+SCK_OpenTcpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *iface, int flags)
+{
+ return open_ip_socket(remote_addr, local_addr, iface, SOCK_STREAM, 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_OpenUnixSocketPair(int flags, int *other_fd)
+{
+ int sock_fd;
+
+ /* Prefer SEQPACKET sockets over DGRAM in order to receive a zero-length
+ message (end of file) when the other end is unexpectedly closed */
+ if (
+#ifdef SOCK_SEQPACKET
+ (sock_fd = open_unix_socket_pair(SOCK_SEQPACKET, flags, other_fd)) < 0 &&
+#endif
+ (sock_fd = open_unix_socket_pair(SOCK_DGRAM, flags, other_fd)) < 0)
+ return INVALID_SOCK_FD;
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+int
+SCK_IsReusable(int fd)
+{
+ return fd >= first_reusable_fd && fd < first_reusable_fd + reusable_fds;
+}
+
+/* ================================================== */
+
+void
+SCK_CloseReusableSockets(void)
+{
+ int fd;
+
+ for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
+ close(fd);
+ reusable_fds = 0;
+ first_reusable_fd = 0;
+}
+
+/* ================================================== */
+
+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_GetIntOption(int sock_fd, int level, int name, int *value)
+{
+ socklen_t len = sizeof (*value);
+
+ if (getsockopt(sock_fd, level, name, value, &len) < 0) {
+ DEBUG_LOG("getsockopt() failed fd=%d level=%d name=%d : %s",
+ sock_fd, level, name, 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)) {
+#if defined(SO_TS_CLOCK) && defined(SO_TS_REALTIME)
+ /* We don't care about the return value - we'll get either a
+ SCM_REALTIME (if we succeded) or a SCM_TIMESTAMP (if we failed) */
+ if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TS_CLOCK, SO_TS_REALTIME))
+ ;
+#endif
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+SCK_ListenOnSocket(int sock_fd, int backlog)
+{
+ if (!SCK_IsReusable(sock_fd) && listen(sock_fd, backlog) < 0) {
+ DEBUG_LOG("listen() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SCK_AcceptConnection(int sock_fd, IPSockAddr *remote_addr)
+{
+ union sockaddr_all saddr;
+ socklen_t saddr_len = sizeof (saddr);
+ int conn_fd;
+
+ conn_fd = accept(sock_fd, &saddr.sa, &saddr_len);
+ if (conn_fd < 0) {
+ DEBUG_LOG("accept() failed : %s", strerror(errno));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!UTI_FdSetCloexec(conn_fd) || !set_socket_nonblock(conn_fd)) {
+ close(conn_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ SCK_SockaddrToIPSockAddr(&saddr.sa, saddr_len, remote_addr);
+
+ return conn_fd;
+}
+
+/* ================================================== */
+
+int
+SCK_ShutdownConnection(int sock_fd)
+{
+ if (shutdown(sock_fd, SHUT_RDWR) < 0) {
+ DEBUG_LOG("shutdown() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SCK_Receive(int sock_fd, void *buffer, int length, int flags)
+{
+ int r;
+
+ if (length < 0) {
+ DEBUG_LOG("Invalid length %d", length);
+ return -1;
+ }
+
+ 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, const void *buffer, int length, int flags)
+{
+ int r;
+
+ assert(flags == 0);
+
+ if (length < 0) {
+ DEBUG_LOG("Invalid length %d", length);
+ return -1;
+ }
+
+ r = send(sock_fd, buffer, length, 0);
+
+ if (r < 0) {
+ DEBUG_LOG("Could not send data fd=%d len=%d : %s", sock_fd, length, strerror(errno));
+ return r;
+ }
+
+ DEBUG_LOG("Sent data fd=%d len=%d", sock_fd, r);
+
+ return r;
+}
+
+/* ================================================== */
+
+SCK_Message *
+SCK_ReceiveMessage(int sock_fd, int flags)
+{
+ int num_messages;
+
+ return receive_messages(sock_fd, flags, 1, &num_messages);
+}
+
+/* ================================================== */
+
+SCK_Message *
+SCK_ReceiveMessages(int sock_fd, int flags, int *num_messages)
+{
+ return receive_messages(sock_fd, flags, MAX_RECV_MESSAGES, num_messages);
+}
+
+/* ================================================== */
+
+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("Could not remove %s : %s", saddr.un.sun_path, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+SCK_CloseSocket(int sock_fd)
+{
+ /* Reusable sockets are closed in finalisation */
+ if (SCK_IsReusable(sock_fd))
+ return;
+
+ close(sock_fd);
+}
+
+/* ================================================== */
+
+void
+SCK_SockaddrToIPSockAddr(struct sockaddr *sa, int sa_length, IPSockAddr *ip_sa)
+{
+ ip_sa->ip_addr.family = IPADDR_UNSPEC;
+ ip_sa->port = 0;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (sa_length < (int)sizeof (struct sockaddr_in))
+ return;
+ ip_sa->ip_addr.family = IPADDR_INET4;
+ ip_sa->ip_addr.addr.in4 = ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr);
+ ip_sa->port = ntohs(((struct sockaddr_in *)sa)->sin_port);
+ break;
+#ifdef FEAT_IPV6
+ case AF_INET6:
+ if (sa_length < (int)sizeof (struct sockaddr_in6))
+ return;
+ ip_sa->ip_addr.family = IPADDR_INET6;
+ memcpy(&ip_sa->ip_addr.addr.in6, ((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr,
+ sizeof (ip_sa->ip_addr.addr.in6));
+ ip_sa->port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+/* ================================================== */
+
+int
+SCK_IPSockAddrToSockaddr(IPSockAddr *ip_sa, struct sockaddr *sa, int sa_length)
+{
+ switch (ip_sa->ip_addr.family) {
+ case IPADDR_INET4:
+ if (sa_length < (int)sizeof (struct sockaddr_in))
+ return 0;
+ memset(sa, 0, sizeof (struct sockaddr_in));
+ sa->sa_family = AF_INET;
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr = htonl(ip_sa->ip_addr.addr.in4);
+ ((struct sockaddr_in *)sa)->sin_port = htons(ip_sa->port);
+#ifdef SIN6_LEN
+ ((struct sockaddr_in *)sa)->sin_len = sizeof (struct sockaddr_in);
+#endif
+ return sizeof (struct sockaddr_in);
+#ifdef FEAT_IPV6
+ case IPADDR_INET6:
+ if (sa_length < (int)sizeof (struct sockaddr_in6))
+ return 0;
+ memset(sa, 0, sizeof (struct sockaddr_in6));
+ sa->sa_family = AF_INET6;
+ memcpy(&((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr, ip_sa->ip_addr.addr.in6,
+ sizeof (((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr));
+ ((struct sockaddr_in6 *)sa)->sin6_port = htons(ip_sa->port);
+#ifdef SIN6_LEN
+ ((struct sockaddr_in6 *)sa)->sin6_len = sizeof (struct sockaddr_in6);
+#endif
+ return sizeof (struct sockaddr_in6);
+#endif
+ default:
+ if (sa_length < (int)sizeof (struct sockaddr))
+ return 0;
+ memset(sa, 0, sizeof (struct sockaddr));
+ sa->sa_family = AF_UNSPEC;
+ return 0;
+ }
+}