blob: 549d609aeb5e93d044b761242a07e7a88478d415 [file] [log] [blame]
/*****************************************************************************\
* Copyright (C) 2001-2002 The Regents of the University of California.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Chris Dunlap <cdunlap@llnl.gov>.
* UCRL-CODE-2002-009.
*
* This file is part of ConMan, a remote console management program.
* For details, see <http://www.llnl.gov/linux/conman/>.
*
* ConMan 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.
*
* ConMan 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 ConMan; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*****************************************************************************
* Refer to "util-net.h" for documentation on public functions.
\*****************************************************************************/
#include "config.h"
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h> /* for PATH_MAX */
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include "slurm/slurm.h"
#include "src/common/read_config.h"
#include "src/common/run_in_daemon.h"
#include "src/common/strlcpy.h"
#include "src/common/util-net.h"
#include "src/common/macros.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
static pthread_mutex_t hostentLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_rwlock_t getnameinfo_cache_lock = PTHREAD_RWLOCK_INITIALIZER;
typedef struct {
slurm_addr_t addr;
time_t expiration;
char *host;
} getnameinfo_cache_t;
static list_t *nameinfo_cache = NULL;
static int copy_hostent(const struct hostent *src, char *dst, int len);
#ifndef NDEBUG
static int validate_hostent_copy(
const struct hostent *src, const struct hostent *dst);
#endif /* !NDEBUG */
struct hostent * get_host_by_name(const char *name,
void *buf, int buflen, int *h_err)
{
/* gethostbyname() is not thread-safe, and there is no frelling standard
* for gethostbyname_r() -- the arg list varies from system to system!
*/
struct hostent *hptr;
int n = 0;
xassert(name && buf);
slurm_mutex_lock(&hostentLock);
/* It appears gethostbyname leaks memory once. Under the covers it
* calls gethostbyname_r (at least on Ubuntu 16.10). This leak doesn't
* appear to get worst, meaning it only happens once, so we should be
* ok. Though gethostbyname is obsolete now we can't really change
* since aliases don't work we can't change.
*/
if ((hptr = gethostbyname(name)))
n = copy_hostent(hptr, buf, buflen);
if (h_err)
*h_err = h_errno;
slurm_mutex_unlock(&hostentLock);
if (n < 0) {
errno = ERANGE;
return(NULL);
}
return(hptr ? (struct hostent *) buf : NULL);
}
static int copy_hostent(const struct hostent *src, char *buf, int len)
{
/* Copies the (src) hostent struct (and all of its associated data)
* into the buffer (buf) of length (len).
* Returns 0 if the copy is successful, or -1 if the length of the buffer
* is not large enough to hold the result.
*
* Note that the order in which data is copied into (buf) is done
* in such a way as to ensure everything is properly word-aligned.
* There is a method to the madness.
*/
struct hostent *dst;
int n;
char **p, **q;
xassert(src && buf);
dst = (struct hostent *) buf;
if ((len -= sizeof(struct hostent)) < 0)
return(-1);
dst->h_addrtype = src->h_addrtype;
dst->h_length = src->h_length;
buf += sizeof(struct hostent);
/* Reserve space for h_aliases[].
*/
dst->h_aliases = (char **) buf;
for (p=src->h_aliases, q=dst->h_aliases, n=0; *p; p++, q++, n++) {;}
if ((len -= ++n * sizeof(char *)) < 0)
return(-1);
buf = (char *) (q + 1);
/* Reserve space for h_addr_list[].
*/
dst->h_addr_list = (char **) buf;
for (p=src->h_addr_list, q=dst->h_addr_list, n=0; *p; p++, q++, n++) {;}
if ((len -= ++n * sizeof(char *)) < 0)
return(-1);
buf = (char *) (q + 1);
/* Copy h_addr_list[] in_addr structs.
*/
for (p=src->h_addr_list, q=dst->h_addr_list; *p; p++, q++) {
if ((len -= src->h_length) < 0)
return(-1);
memcpy(buf, *p, src->h_length);
*q = buf;
buf += src->h_length;
}
*q = NULL;
/* Copy h_aliases[] strings.
*/
for (p=src->h_aliases, q=dst->h_aliases; *p; p++, q++) {
n = strlcpy(buf, *p, len);
*q = buf;
buf += ++n; /* allow for trailing NUL char */
if ((len -= n) < 0)
return(-1);
}
*q = NULL;
/* Copy h_name string.
*/
dst->h_name = buf;
n = strlcpy(buf, src->h_name, len);
buf += ++n; /* allow for trailing NUL char */
if ((len -= n) < 0)
return(-1);
xassert(validate_hostent_copy(src, dst) >= 0);
xassert(buf); /* Used only to eliminate CLANG error */
return(0);
}
#ifndef NDEBUG
static int validate_hostent_copy(
const struct hostent *src, const struct hostent *dst)
{
/* Validates the src hostent struct has been successfully copied into dst.
* Returns 0 if the copy is good; o/w, returns -1.
*/
char **p, **q;
xassert(src && dst);
if (!dst->h_name)
return(-1);
if (src->h_name == dst->h_name)
return(-1);
if (xstrcmp(src->h_name, dst->h_name))
return(-1);
if (src->h_addrtype != dst->h_addrtype)
return(-1);
if (src->h_length != dst->h_length)
return(-1);
for (p=src->h_aliases, q=dst->h_aliases; *p; p++, q++)
if ((!q) || (p == q) || (xstrcmp(*p, *q)))
return(-1);
for (p=src->h_addr_list, q=dst->h_addr_list; *p; p++, q++)
if ((!q) || (p == q) || (memcmp(*p, *q, src->h_length)))
return(-1);
return(0);
}
#endif /* !NDEBUG */
/* is_full_path()
*
* Test if the given path is a full or relative one.
*/
extern
bool is_full_path(const char *path)
{
if (path && path[0] == '/')
return true;
return false;
}
/* make_full_path()
*
* Given a relative path in input make it full relative
* to the current working directory.
*/
extern char *make_full_path(const char *rpath)
{
char *cwd;
char *cwd2 = NULL;
#ifdef HAVE_GET_CURRENT_DIR_NAME
cwd = get_current_dir_name();
#else
cwd = malloc(PATH_MAX);
cwd = getcwd(cwd, PATH_MAX);
#endif
xstrfmtcat(cwd2, "%s/%s", cwd, rpath);
free(cwd);
return cwd2;
}
static struct addrinfo *_xgetaddrinfo(const char *hostname, const char *serv,
const struct addrinfo *hints)
{
struct addrinfo *result = NULL;
int err;
err = getaddrinfo(hostname, serv, hints, &result);
if (err == EAI_SYSTEM) {
error_in_daemon("%s: getaddrinfo(%s:%s) failed: %s: %m",
__func__, hostname, serv, gai_strerror(err));
return NULL;
} else if (err != 0) {
error_in_daemon("%s: getaddrinfo(%s:%s) failed: %s",
__func__, hostname, serv, gai_strerror(err));
return NULL;
}
return result;
}
extern struct addrinfo *xgetaddrinfo_port(const char *hostname, uint16_t port)
{
char serv[6];
snprintf(serv, sizeof(serv), "%hu", port);
return xgetaddrinfo(hostname, serv);
}
extern struct addrinfo *xgetaddrinfo(const char *hostname, const char *serv)
{
struct addrinfo hints;
bool v4_enabled = slurm_conf.conf_flags & CONF_FLAG_IPV4_ENABLED;
bool v6_enabled = slurm_conf.conf_flags & CONF_FLAG_IPV6_ENABLED;
memset(&hints, 0, sizeof(hints));
/* use configured IP support to hint at what address types to return */
if (v4_enabled && !v6_enabled)
hints.ai_family = AF_INET;
else if (!v4_enabled && v6_enabled)
hints.ai_family = AF_INET6;
else
hints.ai_family = AF_UNSPEC;
/* RFC4291 2.4 "Unspecified" address type or IPv4 INADDR_ANY */
if (!xstrcmp("::", hostname)) {
/*
* Only specify one address instead of NULL if possible to avoid
* EADDRINUSE when trying to bind on IPv4 and IPv6 INADDR_ANY.
*/
if (v6_enabled)
hostname = "0::0";
else if (v4_enabled)
hostname = "0.0.0.0";
else
hostname = NULL;
}
/* RFC4291 2.4 "Loopback" address type */
if (v6_enabled && !xstrcmp("::1", hostname))
hostname = "0::1";
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE;
if (hostname)
hints.ai_flags |= AI_CANONNAME;
hints.ai_socktype = SOCK_STREAM;
return _xgetaddrinfo(hostname, serv, &hints);
}
extern int host_has_addr_family(const char *hostname, const char *srv,
bool *ipv4, bool *ipv6)
{
struct addrinfo hints;
struct addrinfo *ai_ptr, *ai_start;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE;
if (hostname)
hints.ai_flags |= AI_CANONNAME;
hints.ai_socktype = SOCK_STREAM;
ai_start = _xgetaddrinfo(hostname, srv, &hints);
if (!ai_start)
return SLURM_ERROR;
*ipv4 = *ipv6 = false;
for (ai_ptr = ai_start; ai_ptr; ai_ptr = ai_ptr->ai_next) {
if (ai_ptr->ai_family == AF_INET6)
*ipv6 = true;
else if (ai_ptr->ai_family == AF_INET)
*ipv4 = true;
}
freeaddrinfo(ai_start);
return SLURM_SUCCESS;
}
static int _name_cache_find(void *x, void *y)
{
getnameinfo_cache_t *cache_ent = x;
const slurm_addr_t *addr_x = &cache_ent->addr;
const slurm_addr_t *addr_y = y;
xassert(addr_x);
xassert(addr_y);
xassert(addr_x->ss_family != AF_UNIX);
xassert(addr_y->ss_family != AF_UNIX);
if (addr_x->ss_family != addr_y->ss_family)
return false;
if (addr_x->ss_family == AF_INET) {
struct sockaddr_in *x4 = (void *)addr_x;
struct sockaddr_in *y4 = (void *)addr_y;
if (x4->sin_addr.s_addr != y4->sin_addr.s_addr)
return false;
} else if (addr_x->ss_family == AF_INET6) {
struct sockaddr_in6 *x6 = (void *)addr_x;
struct sockaddr_in6 *y6 = (void *)addr_y;
if (memcmp(x6->sin6_addr.s6_addr, y6->sin6_addr.s6_addr,
sizeof(x6->sin6_addr.s6_addr)))
return false;
}
return true;
}
static void _getnameinfo_cache_destroy(void *obj)
{
getnameinfo_cache_t *entry = obj;
xfree(entry->host);
xfree(entry);
}
extern void getnameinfo_cache_purge(void)
{
slurm_rwlock_wrlock(&getnameinfo_cache_lock);
FREE_NULL_LIST(nameinfo_cache);
slurm_rwlock_unlock(&getnameinfo_cache_lock);
}
static char *_getnameinfo(const slurm_addr_t *addr)
{
char hbuf[NI_MAXHOST] = "\0";
int err;
err = getnameinfo((const struct sockaddr *) addr, sizeof(*addr),
hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD);
if (err == EAI_SYSTEM) {
log_flag(NET, "%s: getnameinfo(%pA) failed: %s: %m",
__func__, addr, gai_strerror(err));
return NULL;
} else if (err) {
log_flag(NET, "%s: getnameinfo(%pA) failed: %s",
__func__, addr, gai_strerror(err));
return NULL;
}
return xstrdup(hbuf);
}
extern char *xgetnameinfo(const slurm_addr_t *addr)
{
getnameinfo_cache_t *cache_ent = NULL;
char *name = NULL;
time_t now = 0;
bool new = false;
if (!slurm_conf.getnameinfo_cache_timeout)
return _getnameinfo(addr);
slurm_rwlock_rdlock(&getnameinfo_cache_lock);
now = time(NULL);
if (nameinfo_cache) {
cache_ent = list_find_first_ro(nameinfo_cache, _name_cache_find,
(void *) addr);
if (cache_ent && (cache_ent->expiration > now)) {
name = xstrdup(cache_ent->host);
slurm_rwlock_unlock(&getnameinfo_cache_lock);
log_flag(NET, "%s: %pA = %s (cached)",
__func__, addr, name);
return name;
}
}
slurm_rwlock_unlock(&getnameinfo_cache_lock);
/*
* Errors will leave expired cache records in place.
* That is okay, we'll find them and attempt to update them again.
*/
if (!(name = _getnameinfo(addr)))
return NULL;
slurm_rwlock_wrlock(&getnameinfo_cache_lock);
if (!nameinfo_cache)
nameinfo_cache = list_create(_getnameinfo_cache_destroy);
cache_ent = list_find_first(nameinfo_cache, _name_cache_find,
(void *) addr);
if (!cache_ent) {
cache_ent = xmalloc(sizeof(*cache_ent));
cache_ent->addr = *addr;
new = true;
}
/*
* The host name could have changed for expired cache records, so just
* blindly update the cache record every time to be safe.
*/
xfree(cache_ent->host);
cache_ent->host = xstrdup(name);
cache_ent->expiration = now + slurm_conf.getnameinfo_cache_timeout;
if (new) {
log_flag(NET, "%s: Adding to cache - %pA = %s",
__func__, addr, name);
list_append(nameinfo_cache, cache_ent);
} else {
log_flag(NET, "%s: Updating cache - %pA = %s",
__func__, addr, name);
}
slurm_rwlock_unlock(&getnameinfo_cache_lock);
return name;
}