| /*****************************************************************************\ |
| * net.c - basic network communications for user application I/O |
| ***************************************************************************** |
| * Copyright (C) 2002 The Regents of the University of California. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Mark Grondona <grondona1@llnl.gov>, Kevin Tew <tew1@llnl.gov>, |
| * et. al. |
| * CODE-OCEC-09-009. All rights reserved. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <netdb.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/select.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include "src/common/read_config.h" |
| #include "src/common/xrandom.h" |
| |
| #if defined(__FreeBSD__) || defined(__NetBSD__) |
| #define SOL_TCP IPPROTO_TCP |
| #endif |
| |
| #ifndef NI_MAXHOST |
| #define NI_MAXHOST 1025 |
| #endif /* NI_MAXHOST */ |
| #ifndef NI_MAXSERV |
| #define NI_MAXSERV 32 |
| #endif /* NI_MAXSERV */ |
| |
| #define CON_NAME_PLACE_HOLDER_LEN 25 |
| |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/net.h" |
| #include "src/common/slurm_protocol_api.h" |
| #include "src/common/util-net.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| /* |
| * Define slurm-specific aliases for use by plugins, see slurm_xlator.h |
| * for details. |
| */ |
| strong_alias(net_stream_listen, slurm_net_stream_listen); |
| |
| /* open a stream socket on an ephemeral port and put it into |
| * the listen state. fd and port are filled in with the new |
| * socket's file descriptor and port #. |
| * |
| * OUT fd - listening socket file descriptor number |
| * OUT port - TCP port number in host byte order |
| */ |
| int net_stream_listen(int *fd, uint16_t *port) |
| { |
| slurm_addr_t sin; |
| socklen_t len = sizeof(sin); |
| int val = 1; |
| |
| /* bind ephemeral port */ |
| slurm_setup_addr(&sin, 0); |
| |
| if ((*fd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) < 0) |
| return -1; |
| |
| if (setsockopt(*fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) |
| goto cleanup; |
| if (bind(*fd, (struct sockaddr *) &sin, len) < 0) |
| goto cleanup; |
| if (getsockname(*fd, (struct sockaddr *) &sin, &len) < 0) |
| goto cleanup; |
| |
| *port = slurm_get_port(&sin); |
| if (listen(*fd, SLURM_DEFAULT_LISTEN_BACKLOG) < 0) |
| goto cleanup; |
| |
| return 1; |
| |
| cleanup: |
| close(*fd); |
| return -1; |
| } |
| |
| /* set keepalive time on socket */ |
| extern void net_set_keep_alive(int sock) |
| { |
| int opt_int; |
| socklen_t opt_len; |
| struct linger opt_linger; |
| |
| if (slurm_conf.keepalive_time == NO_VAL) |
| return; |
| |
| opt_len = sizeof(struct linger); |
| opt_linger.l_onoff = 1; |
| opt_linger.l_linger = slurm_conf.keepalive_time; |
| if (setsockopt(sock, SOL_SOCKET, SO_LINGER, &opt_linger, opt_len) < 0) |
| error("Unable to set linger socket option: %m"); |
| |
| opt_len = sizeof(opt_int); |
| opt_int = slurm_conf.keepalive_time; |
| if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt_int, opt_len) < 0) { |
| error("Unable to set keepalive socket option: %m"); |
| return; |
| } |
| |
| /* |
| * TCP_KEEPIDLE used to be defined in FreeBSD, then went away, then came |
| * back in 9.0. |
| * |
| * Removing this call might decrease the robustness of communications, |
| * but will probably have no noticeable effect. |
| */ |
| #if !defined (__APPLE__) && (! defined(__FreeBSD__) || (__FreeBSD_version > 900000)) |
| if (slurm_conf.keepalive_interval != NO_VAL) { |
| opt_int = slurm_conf.keepalive_interval; |
| if (setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, |
| &opt_int, opt_len) < 0) { |
| error("Unable to set keepalive interval: %m"); |
| return; |
| } |
| } |
| if (slurm_conf.keepalive_probes != NO_VAL) { |
| opt_int = (int) slurm_conf.keepalive_probes; |
| if (setsockopt(sock, SOL_TCP, TCP_KEEPCNT, |
| &opt_int, opt_len) < 0) { |
| error("Unable to set keepalive probes: %m"); |
| return; |
| } |
| } |
| opt_int = slurm_conf.keepalive_time; |
| if (setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &opt_int, opt_len) < 0) { |
| error("Unable to set keepalive socket time: %m"); |
| return; |
| } |
| #endif |
| |
| #if 0 |
| /* Used to validate above operations for testing purposes */ |
| opt_linger.l_onoff = 0; |
| opt_linger.l_linger = 0; |
| opt_len = sizeof(struct linger); |
| getsockopt(sock, SOL_SOCKET, SO_LINGER, &opt_linger, &opt_len); |
| info("got linger time of %d:%d on fd %d", opt_linger.l_onoff, |
| opt_linger.l_linger, sock); |
| |
| opt_len = sizeof(opt_len); |
| getsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &opt_int, &opt_len); |
| info("got keepalive_interval is %d on fd %d", opt_int, sock); |
| getsockopt(sock, SOL_TCP, TCP_KEEPCNT, &opt_int, &opt_len); |
| info("got keepalive_probes is %d on fd %d", opt_int, sock); |
| getsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &opt_int, &opt_len); |
| info("got keepalive_time is %d on fd %d", opt_int, sock); |
| #endif |
| } |
| |
| extern int net_set_nodelay(int sock, bool set, const char *con_name) |
| { |
| int opt_int; |
| |
| if (sock < 0) |
| return EBADF; |
| |
| if (set) |
| opt_int = 1; |
| else |
| opt_int = 0; |
| |
| if (setsockopt(sock, SOL_TCP, TCP_NODELAY, &opt_int, sizeof(int))) { |
| int rc = errno; |
| char lcon_name[CON_NAME_PLACE_HOLDER_LEN] = {0}; |
| |
| if (!con_name) { |
| snprintf(lcon_name, sizeof(lcon_name), "fd:%d", sock); |
| con_name = lcon_name; |
| } |
| |
| error("[%s] Unable to set TCP_NODELAY: %s", |
| con_name, slurm_strerror(rc)); |
| |
| return rc; |
| } |
| |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Check if we can bind() the socket s to port port. |
| * |
| * IN: s - socket |
| * IN: port - port number to attempt to bind |
| * IN: local - only bind to localhost if true |
| * OUT: true/false if port was bound successfully |
| */ |
| static bool _is_port_ok(int s, uint16_t port, bool local) |
| { |
| slurm_addr_t addr; |
| slurm_setup_addr(&addr, port); |
| |
| if (!local) { |
| debug3("%s: requesting non-local port", __func__); |
| } else if (addr.ss_family == AF_INET) { |
| struct sockaddr_in *sin = (struct sockaddr_in *) &addr; |
| sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| } else if (addr.ss_family == AF_INET6) { |
| struct sockaddr_in6 *sin = (struct sockaddr_in6 *) &addr; |
| sin->sin6_addr = in6addr_loopback; |
| } else { |
| error("%s: protocol family %u unsupported", |
| __func__, addr.ss_family); |
| return false; |
| } |
| |
| if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| log_flag(NET, "%s: bind() failed on port:%d fd:%d: %m", |
| __func__, port, s); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* net_stream_listen_ports() |
| */ |
| int net_stream_listen_ports(int *fd, uint16_t *port, uint16_t *ports, bool local) |
| { |
| slurm_addr_t sin; |
| uint32_t min = ports[0], max = ports[1]; |
| uint32_t num = max - min + 1; |
| |
| xassert(num > 0); |
| |
| *port = min + (xrandom() % num); |
| |
| slurm_setup_addr(&sin, 0); /* Decide on IPv4 or IPv6 */ |
| |
| *fd = -1; |
| |
| for (int i = 0; i < num; i++) { |
| if (*fd < 0) { |
| const int one = 1; |
| |
| if ((*fd = socket(sin.ss_family, SOCK_STREAM, |
| IPPROTO_TCP)) < 0) { |
| log_flag(NET, "%s: socket() failed: %m", |
| __func__); |
| return -1; |
| } |
| |
| if (setsockopt(*fd, SOL_SOCKET, SO_REUSEADDR, &one, |
| sizeof(int)) < 0) { |
| log_flag(NET, "%s: setsockopt() failed: %m", |
| __func__); |
| close(*fd); |
| return -1; |
| } |
| } |
| |
| if (_is_port_ok(*fd, *port, local)) { |
| if (!listen(*fd, SLURM_DEFAULT_LISTEN_BACKLOG)) |
| return *fd; |
| |
| log_flag(NET, "%s: listen() failed: %m", |
| __func__); |
| |
| /* |
| * If bind() succeeds but listen() fails we need to |
| * close and reestablish the socket before trying |
| * again on another port number. |
| */ |
| if (close(*fd)) { |
| log_flag(NET, "%s: close(%d) failed: %m", |
| __func__, *fd); |
| } |
| *fd = -1; |
| } |
| |
| if (*port == max) |
| *port = min; |
| else |
| ++(*port); |
| } |
| |
| if (*fd >= 0) |
| close(*fd); |
| |
| error("%s: all ports in range (%u, %u) exhausted, cannot establish listening port", |
| __func__, min, max); |
| |
| return -1; |
| } |
| |
| static const char *_ip_reserved_to_str(const slurm_addr_t *addr) |
| { |
| if (addr->ss_family == AF_INET) { |
| const struct sockaddr_in *addr4 = (struct sockaddr_in *) addr; |
| const in_addr_t ipv4 = addr4->sin_addr.s_addr; |
| |
| if (ipv4 == INADDR_LOOPBACK) |
| return "127.0.0.1"; |
| else if (ipv4 == INADDR_ANY) |
| return "0.0.0.0"; |
| else if (ipv4 == INADDR_BROADCAST) |
| return "255.255.255.255"; |
| #ifdef INADDR_DUMMY |
| else if (ipv4 == INADDR_DUMMY) |
| return "192.0.0.8"; |
| #endif /* INADDR_DUMMY */ |
| } else if (addr->ss_family == AF_INET6) { |
| const struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr; |
| const struct in6_addr ipv6 = addr6->sin6_addr; |
| |
| /* |
| * RFC5156 Special-Use IPv6 Addresses |
| * defined by RFC4291 as "::" or "::1" formatted by RFC6874 |
| * referencing RFC3986: |
| * IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]" |
| * |
| * RFC6874 Appendix A gives representing numeric IPv6address |
| * with brackets and without as both valid options. Returning |
| * addresses with brackets to avoid a constructed string with |
| * [::]:PORT over :::PORT and [::1]:PORT over ::1:PORT while |
| * both are valid. |
| */ |
| if (IN6_IS_ADDR_UNSPECIFIED(&ipv6)) { |
| return "[::]"; |
| } else if (IN6_IS_ADDR_LOOPBACK(&ipv6)) |
| return "[::1]"; |
| } |
| |
| return NULL; |
| } |
| |
| static char *_fmt_ip_host_port_str(const slurm_addr_t *addr, const char *host) |
| { |
| /* Include 2 extra bytes for [] for IPv6 */ |
| static const size_t max_host_bytes = |
| MAX(INET_ADDRSTRLEN, (INET6_ADDRSTRLEN + 2)); |
| char *resp = NULL; |
| char nhost[max_host_bytes]; |
| uint16_t port = 0; |
| |
| if (addr->ss_family == AF_INET) { |
| const struct sockaddr_in *in = (struct sockaddr_in *) addr; |
| |
| port = ntohs(in->sin_port); |
| |
| if (!host) { |
| if (inet_ntop(AF_INET, &in->sin_addr, nhost, |
| sizeof(nhost))) { |
| host = nhost; |
| } else { |
| /* this should never happen */ |
| log_flag_hex(NET, addr, sizeof(*addr), |
| "%s: inet_ntop(AF_INET) failed: %s", |
| slurm_strerror(errno)); |
| return NULL; |
| } |
| } |
| } else if (addr->ss_family == AF_INET6) { |
| const struct sockaddr_in6 *in6 = (struct sockaddr_in6 *) addr; |
| |
| port = ntohs(in6->sin6_port); |
| |
| if (!host) { |
| if (inet_ntop(AF_INET6, &in6->sin6_addr, (nhost + 1), |
| (sizeof(nhost) - 2))) { |
| const size_t len = strlen(nhost + 1); |
| /* |
| * Construct RFC3986 host port pair: |
| * IP-literal = "[" ( IPv6address / IPvFuture ) "]" |
| */ |
| nhost[0] = '['; |
| nhost[len + 1] = ']'; |
| nhost[len + 2] = '\0'; |
| host = nhost; |
| } else { |
| /* this should never happen */ |
| log_flag_hex(NET, addr, sizeof(*addr), |
| "%s: inet_ntop(AF_INET6) failed: %s", |
| slurm_strerror(errno)); |
| return NULL; |
| } |
| } |
| } |
| |
| /* |
| * RFC3986 definitions: |
| * host = IP-literal / IPv4address / reg-name |
| * port = *DIGIT |
| * authority = [ userinfo "@" ] host [ ":" port ] |
| * |
| * Where authority obsoletes the prior hostport from RFC2396: |
| * hostport = host [ ":" port ] |
| */ |
| if (host && port) |
| xstrfmtcat(resp, "%s:%hu", host, port); |
| else if (port) |
| xstrfmtcat(resp, ":%hu", port); |
| else if (host) |
| xstrfmtcat(resp, "%s", host); |
| |
| return resp; |
| } |
| |
| extern char *sockaddr_to_string(const slurm_addr_t *addr, socklen_t addrlen) |
| { |
| int prev_errno = errno; |
| char *resp = NULL; |
| const char *rsv_host = NULL; |
| |
| if (addr->ss_family == AF_UNSPEC) { |
| log_flag(NET, "%s: Cannot resolve socket's unspecified address family.", |
| __func__); |
| return NULL; |
| } |
| |
| if (addr->ss_family == AF_UNIX) { |
| const struct sockaddr_un *addr_un = |
| (const struct sockaddr_un *) addr; |
| |
| xassert(addr_un->sun_path[sizeof(addr_un->sun_path) - 1] == |
| '\0'); |
| |
| /* path may not be set */ |
| if (addr_un->sun_path[0]) |
| return xstrdup_printf("unix:%s", addr_un->sun_path); |
| else if (addr_un->sun_path[1]) /* abstract socket */ |
| return xstrdup_printf("unix:@%s", |
| &addr_un->sun_path[1]); |
| else /* path not defined */ |
| return xstrdup_printf("unix:"); |
| } |
| |
| /* Check for reserved addresses that getnameinfo() won't resolve */ |
| if ((rsv_host = _ip_reserved_to_str(addr))) { |
| resp = _fmt_ip_host_port_str(addr, rsv_host); |
| } else { |
| /* Attempt to resolve hostname */ |
| char *host = xgetnameinfo(addr); |
| resp = _fmt_ip_host_port_str(addr, host); |
| xfree(host); |
| } |
| |
| /* |
| * Avoid clobbering errno as this function is likely to be used for |
| * error logging, and stepping on errno prevents %m from working. |
| */ |
| errno = prev_errno; |
| return resp; |
| } |
| |
| extern char *addrinfo_to_string(const struct addrinfo *addr) |
| { |
| return sockaddr_to_string((const slurm_addr_t *) addr->ai_addr, |
| addr->ai_addrlen); |
| } |
| |
| extern slurm_addr_t sockaddr_from_unix_path(const char *path) |
| { |
| slurm_addr_t addr = { |
| .ss_family = AF_UNSPEC, |
| }; |
| struct sockaddr_un *un = (struct sockaddr_un *) &addr; |
| |
| if (!path) |
| return addr; |
| |
| if (strlcpy(un->sun_path, path, sizeof(un->sun_path)) != strlen(path)) |
| return addr; |
| |
| /* Did not overflow - set family to indicate success */ |
| addr.ss_family = AF_UNIX; |
| return addr; |
| } |