diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 12:48:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 12:48:01 +0000 |
commit | b2d2d555a704148968cb7e566735a2a1b1a2f189 (patch) | |
tree | 18549ff498338f40ecf7aa327620abf4c1c3ee43 /socket.c | |
parent | Initial commit. (diff) | |
download | chrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.tar.xz chrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.zip |
Adding upstream version 4.5.upstream/4.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'socket.c')
-rw-r--r-- | socket.c | 1803 |
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; + } +} |