2016-07-18 00:11:41 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2011 - EATON
|
2022-06-29 10:37:36 +00:00
|
|
|
* Copyright (C) 2016 - EATON - IP addressed XML scan
|
|
|
|
* Copyright (C) 2016-2021 - EATON - Various threads-related improvements
|
2011-09-29 18:14:46 +00:00
|
|
|
*
|
|
|
|
* This program 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.
|
|
|
|
*
|
|
|
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
/*! \file scan_xml_http.c
|
|
|
|
\brief detect NUT supported XML HTTP devices
|
|
|
|
\author Frederic Bohe <fredericbohe@eaton.com>
|
2022-06-29 10:37:36 +00:00
|
|
|
\author Michal Vyskocil <MichalVyskocil@eaton.com>
|
|
|
|
\author Jim Klimov <EvgenyKlimov@eaton.com>
|
2016-07-18 00:11:41 +00:00
|
|
|
*/
|
|
|
|
|
2011-09-29 18:14:46 +00:00
|
|
|
#include "common.h"
|
2012-01-24 10:22:33 +00:00
|
|
|
#include "nut-scan.h"
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2011-09-29 18:14:46 +00:00
|
|
|
#ifdef WITH_NEON
|
2012-01-24 10:22:33 +00:00
|
|
|
#include <sys/types.h>
|
2011-09-29 18:14:46 +00:00
|
|
|
#include <sys/socket.h>
|
2012-01-24 10:22:33 +00:00
|
|
|
#include <netdb.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netinet/in.h>
|
2011-09-29 18:14:46 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <sys/select.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <ne_xml.h>
|
2012-01-24 10:22:33 +00:00
|
|
|
#include <ltdl.h>
|
|
|
|
|
|
|
|
/* dynamic link library stuff */
|
2022-06-29 10:37:36 +00:00
|
|
|
static char * libname = "libneon"; /* Note: this is for info messages, not the SONAME */
|
2012-01-24 10:22:33 +00:00
|
|
|
static lt_dlhandle dl_handle = NULL;
|
|
|
|
static const char *dl_error = NULL;
|
|
|
|
|
|
|
|
static void (*nut_ne_xml_push_handler)(ne_xml_parser *p,
|
|
|
|
ne_xml_startelm_cb *startelm,
|
|
|
|
ne_xml_cdata_cb *cdata,
|
|
|
|
ne_xml_endelm_cb *endelm,
|
|
|
|
void *userdata);
|
|
|
|
static void (*nut_ne_xml_destroy)(ne_xml_parser *p);
|
2022-06-29 10:37:36 +00:00
|
|
|
static int (*nut_ne_xml_failed)(ne_xml_parser *p);
|
2012-01-24 10:22:33 +00:00
|
|
|
static ne_xml_parser * (*nut_ne_xml_create)(void);
|
|
|
|
static int (*nut_ne_xml_parse)(ne_xml_parser *p, const char *block, size_t len);
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
static nutscan_device_t * dev_ret = NULL;
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
static pthread_mutex_t dev_mutex;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* use explicit booleans */
|
|
|
|
#ifndef FALSE
|
|
|
|
typedef enum ebool { FALSE = 0, TRUE } bool_t;
|
|
|
|
#else
|
|
|
|
typedef int bool_t;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* return 0 on error; visible externally */
|
|
|
|
int nutscan_load_neon_library(const char *libname_path);
|
2016-07-18 00:11:41 +00:00
|
|
|
int nutscan_load_neon_library(const char *libname_path)
|
2012-01-24 10:22:33 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
if (dl_handle != NULL) {
|
2016-07-18 00:11:41 +00:00
|
|
|
/* if previous init failed */
|
2022-06-29 10:37:36 +00:00
|
|
|
if (dl_handle == (void *)1) {
|
2016-07-18 00:11:41 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* init has already been done */
|
|
|
|
return 1;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
if (libname_path == NULL) {
|
|
|
|
fprintf(stderr, "Neon library not found. XML search disabled.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (lt_dlinit() != 0) {
|
2016-07-18 00:11:41 +00:00
|
|
|
fprintf(stderr, "Error initializing lt_init\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
dl_handle = lt_dlopen(libname_path);
|
|
|
|
if (!dl_handle) {
|
|
|
|
dl_error = lt_dlerror();
|
|
|
|
goto err;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
lt_dlerror(); /* Clear any existing error */
|
|
|
|
*(void **) (&nut_ne_xml_push_handler) = lt_dlsym(dl_handle,
|
|
|
|
"ne_xml_push_handler");
|
2022-06-29 10:37:36 +00:00
|
|
|
if ((dl_error = lt_dlerror()) != NULL) {
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
*(void **) (&nut_ne_xml_destroy) = lt_dlsym(dl_handle, "ne_xml_destroy");
|
|
|
|
if ((dl_error = lt_dlerror()) != NULL) {
|
2016-07-18 00:11:41 +00:00
|
|
|
goto err;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
*(void **) (&nut_ne_xml_create) = lt_dlsym(dl_handle, "ne_xml_create");
|
|
|
|
if ((dl_error = lt_dlerror()) != NULL) {
|
2016-07-18 00:11:41 +00:00
|
|
|
goto err;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
*(void **) (&nut_ne_xml_parse) = lt_dlsym(dl_handle, "ne_xml_parse");
|
|
|
|
if ((dl_error = lt_dlerror()) != NULL) {
|
2016-07-18 00:11:41 +00:00
|
|
|
goto err;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
*(void **) (&nut_ne_xml_failed) = lt_dlsym(dl_handle, "ne_xml_failed");
|
|
|
|
if ((dl_error = lt_dlerror()) != NULL) {
|
2016-07-18 00:11:41 +00:00
|
|
|
goto err;
|
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
return 1;
|
2012-01-24 10:22:33 +00:00
|
|
|
err:
|
2016-07-18 00:11:41 +00:00
|
|
|
fprintf(stderr, "Cannot load XML library (%s) : %s. XML search disabled.\n", libname, dl_error);
|
|
|
|
dl_handle = (void *)1;
|
2012-06-01 13:55:19 +00:00
|
|
|
lt_dlexit();
|
2016-07-18 00:11:41 +00:00
|
|
|
return 0;
|
2012-01-24 10:22:33 +00:00
|
|
|
}
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
/* A start-element callback for element with given namespace/name. */
|
2011-09-29 18:14:46 +00:00
|
|
|
static int startelm_cb(void *userdata, int parent, const char *nspace, const char *name, const char **atts) {
|
|
|
|
nutscan_device_t * dev = (nutscan_device_t *)userdata;
|
|
|
|
char buf[SMALLBUF];
|
|
|
|
int i = 0;
|
2022-06-29 10:37:36 +00:00
|
|
|
int result = -1;
|
|
|
|
while (atts[i] != NULL) {
|
|
|
|
upsdebugx(5, "startelm_cb() : parent=%d nspace='%s' name='%s' atts[%d]='%s' atts[%d]='%s'",
|
|
|
|
parent, nspace, name, i, atts[i], (i + 1), atts[i + 1]);
|
|
|
|
/* The Eaton/MGE ePDUs almost exclusively support only XMLv4 protocol
|
|
|
|
* (only the very first generation of G2/G3 NMCs supported an older
|
|
|
|
* protocol, but all should have been FW upgraded by now), which NUT
|
|
|
|
* drivers don't yet support. To avoid failing drivers later, the
|
|
|
|
* nut-scanner should not suggest netxml-ups configuration for ePDUs
|
|
|
|
* at this time. */
|
|
|
|
if (strcmp(atts[i], "class") == 0 && strcmp(atts[i + 1], "DEV.PDU") == 0) {
|
|
|
|
upsdebugx(3, "startelm_cb() : XML v4 protocol is not supported by current NUT drivers, skipping device!");
|
|
|
|
/* netxml-ups currently only supports XML version 3 (for UPS),
|
|
|
|
* and not version 4 (for UPS and PDU)! */
|
|
|
|
return -1;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
if (strcmp(atts[i], "type") == 0) {
|
|
|
|
snprintf(buf, sizeof(buf), "%s", atts[i + 1]);
|
|
|
|
nutscan_add_option_to_device(dev, "desc", buf);
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
i = i + 2;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
return result;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
static void * nutscan_scan_xml_http_generic(void * arg)
|
2011-09-29 18:14:46 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
nutscan_xml_t * sec = (nutscan_xml_t *)arg;
|
2011-09-29 18:14:46 +00:00
|
|
|
char *scanMsg = "<SCAN_REQUEST/>";
|
2022-06-29 10:37:36 +00:00
|
|
|
/* Note: at this time the HTTP/XML scan is in fact not implemented - just the UDP part */
|
|
|
|
/* uint16_t port_http = 80; */
|
|
|
|
uint16_t port_udp = 4679;
|
|
|
|
/* A NULL "ip" causes a broadcast scan; otherwise the single ip address is queried directly */
|
|
|
|
char *ip = NULL;
|
|
|
|
useconds_t usec_timeout = 0;
|
2011-09-29 18:14:46 +00:00
|
|
|
int peerSocket;
|
|
|
|
int sockopt_on = 1;
|
2022-06-29 10:37:36 +00:00
|
|
|
struct sockaddr_in sockAddress_udp;
|
|
|
|
socklen_t sockAddressLength = sizeof(sockAddress_udp);
|
|
|
|
memset(&sockAddress_udp, 0, sizeof(sockAddress_udp));
|
2011-09-29 18:14:46 +00:00
|
|
|
fd_set fds;
|
|
|
|
struct timeval timeout;
|
|
|
|
int ret;
|
2022-06-29 10:37:36 +00:00
|
|
|
char buf[SMALLBUF + 8];
|
2011-09-29 18:14:46 +00:00
|
|
|
char string[SMALLBUF];
|
|
|
|
ssize_t recv_size;
|
2022-06-29 10:37:36 +00:00
|
|
|
int i;
|
2011-09-29 18:14:46 +00:00
|
|
|
|
|
|
|
nutscan_device_t * nut_dev = NULL;
|
2022-06-29 10:37:36 +00:00
|
|
|
if (sec != NULL) {
|
|
|
|
/* if (sec->port_http > 0 && sec->port_http <= 65534)
|
|
|
|
* port_http = sec->port_http; */
|
|
|
|
if (sec->port_udp > 0 && sec->port_udp <= 65534)
|
|
|
|
port_udp = sec->port_udp;
|
|
|
|
if (sec->usec_timeout > 0)
|
|
|
|
usec_timeout = sec->usec_timeout;
|
|
|
|
ip = sec->peername; /* NULL or not... */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (usec_timeout <= 0)
|
|
|
|
usec_timeout = 5000000; /* Driver default : 5sec */
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (!nutscan_avail_xml_http) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if ((peerSocket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
|
|
|
|
fprintf(stderr, "Error creating socket\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME : Per http://stackoverflow.com/questions/683624/udp-broadcast-on-all-interfaces
|
|
|
|
* A single sendto() generates a single packet, so one must iterate all known interfaces... */
|
|
|
|
#define MAX_RETRIES 3
|
|
|
|
for (i = 0; i != MAX_RETRIES ; i++) {
|
2011-09-29 18:14:46 +00:00
|
|
|
/* Initialize socket */
|
2022-06-29 10:37:36 +00:00
|
|
|
sockAddress_udp.sin_family = AF_INET;
|
|
|
|
if (ip == NULL) {
|
|
|
|
upsdebugx(2,
|
|
|
|
"nutscan_scan_xml_http_generic() : scanning connected network segment(s) "
|
|
|
|
"with a broadcast, attempt %d of %d with a timeout of %jd usec",
|
|
|
|
(i + 1), MAX_RETRIES, (uintmax_t)usec_timeout);
|
|
|
|
sockAddress_udp.sin_addr.s_addr = INADDR_BROADCAST;
|
|
|
|
setsockopt(peerSocket, SOL_SOCKET, SO_BROADCAST, &sockopt_on,
|
2011-09-29 18:14:46 +00:00
|
|
|
sizeof(sockopt_on));
|
2022-06-29 10:37:36 +00:00
|
|
|
} else {
|
|
|
|
upsdebugx(2,
|
|
|
|
"nutscan_scan_xml_http_generic() : scanning IP '%s' with a unicast, "
|
|
|
|
"attempt %d of %d with a timeout of %jd usec",
|
|
|
|
ip, (i + 1), MAX_RETRIES, (uintmax_t)usec_timeout);
|
|
|
|
inet_pton(AF_INET, ip, &(sockAddress_udp.sin_addr));
|
|
|
|
}
|
|
|
|
sockAddress_udp.sin_port = htons(port_udp);
|
2011-09-29 18:14:46 +00:00
|
|
|
|
|
|
|
/* Send scan request */
|
2022-06-29 10:37:36 +00:00
|
|
|
if (sendto(peerSocket, scanMsg, strlen(scanMsg), 0,
|
|
|
|
(struct sockaddr *)&sockAddress_udp,
|
|
|
|
sockAddressLength) <= 0
|
|
|
|
) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error sending Eaton <SCAN_REQUEST/> to %s, #%d/%d\n",
|
|
|
|
(ip ? ip : "<broadcast>"), (i + 1), MAX_RETRIES);
|
|
|
|
usleep(usec_timeout);
|
|
|
|
continue;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
int retNum = 0;
|
2011-09-29 18:14:46 +00:00
|
|
|
FD_ZERO(&fds);
|
2022-06-29 10:37:36 +00:00
|
|
|
FD_SET(peerSocket, &fds);
|
2011-09-29 18:14:46 +00:00
|
|
|
|
|
|
|
timeout.tv_sec = usec_timeout / 1000000;
|
|
|
|
timeout.tv_usec = usec_timeout % 1000000;
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
upsdebugx(5, "nutscan_scan_xml_http_generic() : sent request to %s, "
|
|
|
|
"loop #%d/%d, waiting for responses",
|
|
|
|
(ip ? ip : "<broadcast>"), (i + 1), MAX_RETRIES);
|
|
|
|
while ((ret = select(peerSocket + 1, &fds, NULL, NULL,
|
|
|
|
&timeout))
|
|
|
|
) {
|
|
|
|
retNum ++;
|
|
|
|
upsdebugx(5, "nutscan_scan_xml_http_generic() : request to %s, "
|
|
|
|
"loop #%d/%d, response #%d",
|
|
|
|
(ip ? ip : "<broadcast>"), (i + 1), MAX_RETRIES, retNum);
|
2011-09-29 18:14:46 +00:00
|
|
|
|
|
|
|
timeout.tv_sec = usec_timeout / 1000000;
|
|
|
|
timeout.tv_usec = usec_timeout % 1000000;
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (ret == -1) {
|
2011-09-29 18:14:46 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
"Error waiting on \
|
2022-06-29 10:37:36 +00:00
|
|
|
socket: %d\n", errno);
|
2011-09-29 18:14:46 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
sockAddressLength = sizeof(struct sockaddr_in);
|
2022-06-29 10:37:36 +00:00
|
|
|
recv_size = recvfrom(peerSocket, buf,
|
|
|
|
sizeof(buf), 0,
|
|
|
|
(struct sockaddr *)&sockAddress_udp,
|
|
|
|
&sockAddressLength);
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (recv_size < 0) {
|
2011-09-29 18:14:46 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
"Error reading \
|
2022-06-29 10:37:36 +00:00
|
|
|
socket: %d, #%d/%d\n", errno, (i + 1), MAX_RETRIES);
|
|
|
|
usleep(usec_timeout);
|
2011-09-29 18:14:46 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (getnameinfo(
|
|
|
|
(struct sockaddr *)&sockAddress_udp,
|
|
|
|
sizeof(struct sockaddr_in), string,
|
|
|
|
sizeof(string), NULL, 0,
|
|
|
|
NI_NUMERICHOST) != 0
|
|
|
|
) {
|
2011-09-29 18:14:46 +00:00
|
|
|
|
|
|
|
fprintf(stderr,
|
2022-06-29 10:37:36 +00:00
|
|
|
"Error converting IP address: %d\n", errno);
|
|
|
|
usleep(usec_timeout);
|
2011-09-29 18:14:46 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
nut_dev = nutscan_new_device();
|
|
|
|
if (nut_dev == NULL) {
|
|
|
|
fprintf(stderr, "Memory allocation error\n");
|
|
|
|
goto end_abort;
|
|
|
|
}
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
pthread_mutex_lock(&dev_mutex);
|
|
|
|
#endif
|
|
|
|
upsdebugx(5,
|
|
|
|
"Some host at IP %s replied to NetXML UDP request on port %d, "
|
|
|
|
"inspecting the response...",
|
|
|
|
string, port_udp);
|
|
|
|
nut_dev->type = TYPE_XML;
|
2011-09-29 18:14:46 +00:00
|
|
|
/* Try to read device type */
|
2012-01-24 10:22:33 +00:00
|
|
|
ne_xml_parser *parser = (*nut_ne_xml_create)();
|
|
|
|
(*nut_ne_xml_push_handler)(parser, startelm_cb,
|
|
|
|
NULL, NULL, nut_dev);
|
2022-06-29 10:37:36 +00:00
|
|
|
/* recv_size is a ssize_t, so in range of size_t */
|
|
|
|
(*nut_ne_xml_parse)(parser, buf, (size_t)recv_size);
|
|
|
|
int parserFailed = (*nut_ne_xml_failed)(parser); /* 0 = ok, nonzero = fail */
|
2012-01-24 10:22:33 +00:00
|
|
|
(*nut_ne_xml_destroy)(parser);
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (parserFailed == 0) {
|
|
|
|
nut_dev->driver = strdup("netxml-ups");
|
|
|
|
sprintf(buf, "http://%s", string);
|
|
|
|
nut_dev->port = strdup(buf);
|
|
|
|
upsdebugx(3,
|
|
|
|
"nutscan_scan_xml_http_generic(): "
|
|
|
|
"Adding configuration for driver='%s' port='%s'",
|
|
|
|
nut_dev->driver, nut_dev->port);
|
|
|
|
dev_ret = nutscan_add_device_to_device(
|
|
|
|
dev_ret, nut_dev);
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
pthread_mutex_unlock(&dev_mutex);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"Device at IP %s replied with NetXML but was not deemed compatible "
|
|
|
|
"with 'netxml-ups' driver (unsupported protocol version, etc.)\n",
|
|
|
|
string);
|
|
|
|
nutscan_free_device(nut_dev);
|
|
|
|
nut_dev = NULL;
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
pthread_mutex_unlock(&dev_mutex);
|
|
|
|
#endif
|
|
|
|
if (ip == NULL) {
|
|
|
|
/* skip this device; note that for a
|
|
|
|
* broadcast scan there may be more
|
|
|
|
* in the loop's queue */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ip != NULL) {
|
|
|
|
upsdebugx(2,
|
|
|
|
"nutscan_scan_xml_http_generic(): we collected one reply "
|
|
|
|
"to unicast for %s (repsponse from %s), done",
|
|
|
|
ip, string);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
} /* while select() responses */
|
|
|
|
if (ip == NULL && dev_ret != NULL) {
|
|
|
|
upsdebugx(2,
|
|
|
|
"nutscan_scan_xml_http_generic(): we collected one round of replies "
|
|
|
|
"to broadcast with no errors, done");
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
upsdebugx(2,
|
|
|
|
"nutscan_scan_xml_http_generic(): no replies collected for %s, done",
|
|
|
|
(ip ? ip : "<broadcast>"));
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
end_abort:
|
|
|
|
upsdebugx(1,
|
|
|
|
"Had to abort nutscan_scan_xml_http_generic() for %s, see fatal details above",
|
|
|
|
(ip ? ip : "<broadcast>"));
|
|
|
|
end:
|
|
|
|
if (ip != NULL) /* do not free "ip", it comes from caller */
|
|
|
|
close(peerSocket);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
nutscan_device_t * nutscan_scan_xml_http_range(const char * start_ip, const char * end_ip, useconds_t usec_timeout, nutscan_xml_t * sec)
|
|
|
|
{
|
|
|
|
bool_t pass = TRUE; /* Track that we may spawn a scanning thread */
|
|
|
|
nutscan_xml_t * tmp_sec = NULL;
|
|
|
|
nutscan_device_t * result = NULL;
|
|
|
|
|
|
|
|
if (!nutscan_avail_xml_http) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (start_ip == NULL && end_ip != NULL) {
|
|
|
|
start_ip = end_ip;
|
|
|
|
}
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (start_ip == NULL) {
|
|
|
|
upsdebugx(1, "Scanning XML/HTTP bus using broadcast.");
|
|
|
|
} else {
|
|
|
|
if ((start_ip == end_ip) || (end_ip == NULL) || (0 == strncmp(start_ip, end_ip, 128))) {
|
|
|
|
upsdebugx(1, "Scanning XML/HTTP bus for single IP (%s).", start_ip);
|
|
|
|
} else {
|
|
|
|
/* Iterate the range of IPs to scan */
|
|
|
|
nutscan_ip_iter_t ip;
|
|
|
|
char * ip_str = NULL;
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
# ifdef HAVE_SEMAPHORE
|
|
|
|
sem_t * semaphore = nutscan_semaphore();
|
|
|
|
sem_t semaphore_scantype_inst;
|
|
|
|
sem_t * semaphore_scantype = &semaphore_scantype_inst;
|
|
|
|
# endif /* HAVE_SEMAPHORE */
|
|
|
|
pthread_t thread;
|
|
|
|
nutscan_thread_t * thread_array = NULL;
|
|
|
|
size_t thread_count = 0, i;
|
|
|
|
# if (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE)
|
|
|
|
size_t max_threads_scantype = max_threads_netxml;
|
|
|
|
# endif
|
|
|
|
|
|
|
|
pthread_mutex_init(&dev_mutex, NULL);
|
|
|
|
|
|
|
|
# ifdef HAVE_SEMAPHORE
|
|
|
|
if (max_threads_scantype > 0) {
|
|
|
|
if (SIZE_MAX > UINT_MAX && max_threads_scantype > UINT_MAX) {
|
|
|
|
upsdebugx(1,
|
|
|
|
"WARNING: %s: Limiting max_threads_scantype to range acceptable for sem_init()",
|
|
|
|
__func__);
|
|
|
|
max_threads_scantype = UINT_MAX - 1;
|
|
|
|
}
|
|
|
|
sem_init(semaphore_scantype, 0, (unsigned int)max_threads_scantype);
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
# endif /* HAVE_SEMAPHORE */
|
|
|
|
|
|
|
|
#endif /* HAVE_PTHREAD */
|
|
|
|
|
|
|
|
ip_str = nutscan_ip_iter_init(&ip, start_ip, end_ip);
|
|
|
|
|
|
|
|
while (ip_str != NULL) {
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
/* NOTE: With many enough targets to scan, this can crash
|
|
|
|
* by spawning too many children; add a limit and loop to
|
|
|
|
* "reap" some already done with their work. And probably
|
|
|
|
* account them in thread_array[] as something to not wait
|
|
|
|
* for below in pthread_join()...
|
|
|
|
*/
|
|
|
|
|
|
|
|
# ifdef HAVE_SEMAPHORE
|
|
|
|
/* Just wait for someone to free a semaphored slot,
|
|
|
|
* if none are available, and then/otherwise grab one
|
|
|
|
*/
|
|
|
|
if (thread_array == NULL) {
|
|
|
|
/* Starting point, or after a wait to complete
|
|
|
|
* all earlier runners */
|
|
|
|
if (max_threads_scantype > 0)
|
|
|
|
sem_wait(semaphore_scantype);
|
|
|
|
sem_wait(semaphore);
|
|
|
|
pass = TRUE;
|
|
|
|
} else {
|
|
|
|
pass = ((max_threads_scantype == 0 || sem_trywait(semaphore_scantype) == 0) &&
|
|
|
|
sem_trywait(semaphore) == 0);
|
|
|
|
}
|
|
|
|
# else
|
|
|
|
# ifdef HAVE_PTHREAD_TRYJOIN
|
|
|
|
/* A somewhat naive and brute-force solution for
|
|
|
|
* systems without a semaphore.h. This may suffer
|
|
|
|
* some off-by-one errors, using a few more threads
|
|
|
|
* than intended (if we race a bit at the wrong time,
|
|
|
|
* probably up to one per enabled scanner routine).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TOTHINK: Should there be a threadcount_mutex when
|
|
|
|
* we just read the value in if() and while() below?
|
|
|
|
* At worst we would overflow the limit a bit due to
|
|
|
|
* other protocol scanners...
|
|
|
|
*/
|
|
|
|
if (curr_threads >= max_threads
|
|
|
|
|| (curr_threads >= max_threads_scantype && max_threads_scantype > 0)
|
|
|
|
) {
|
|
|
|
upsdebugx(2, "%s: already running %zu scanning threads "
|
|
|
|
"(launched overall: %zu), "
|
|
|
|
"waiting until some would finish",
|
|
|
|
__func__, curr_threads, thread_count);
|
|
|
|
while (curr_threads >= max_threads
|
|
|
|
|| (curr_threads >= max_threads_scantype && max_threads_scantype > 0)
|
|
|
|
) {
|
|
|
|
for (i = 0; i < thread_count ; i++) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!thread_array[i].active) continue;
|
|
|
|
|
|
|
|
pthread_mutex_lock(&threadcount_mutex);
|
|
|
|
upsdebugx(3, "%s: Trying to join thread #%i...", __func__, i);
|
|
|
|
ret = pthread_tryjoin_np(thread_array[i].thread, NULL);
|
|
|
|
switch (ret) {
|
|
|
|
case ESRCH: /* No thread with the ID thread could be found - already "joined"? */
|
|
|
|
upsdebugx(5, "%s: Was thread #%zu joined earlier?", __func__, i);
|
|
|
|
break;
|
|
|
|
case 0: /* thread exited */
|
|
|
|
if (curr_threads > 0) {
|
|
|
|
curr_threads --;
|
|
|
|
upsdebugx(4, "%s: Joined a finished thread #%zu", __func__, i);
|
|
|
|
} else {
|
|
|
|
/* threadcount_mutex fault? */
|
|
|
|
upsdebugx(0, "WARNING: %s: Accounting of thread count "
|
|
|
|
"says we are already at 0", __func__);
|
|
|
|
}
|
|
|
|
thread_array[i].active = FALSE;
|
|
|
|
break;
|
|
|
|
case EBUSY: /* actively running */
|
|
|
|
upsdebugx(6, "%s: thread #%zu still busy (%i)",
|
|
|
|
__func__, i, ret);
|
|
|
|
break;
|
|
|
|
case EDEADLK: /* Errors with thread interactions... bail out? */
|
|
|
|
case EINVAL: /* Errors with thread interactions... bail out? */
|
|
|
|
default: /* new pthreads abilities? */
|
|
|
|
upsdebugx(5, "%s: thread #%zu reported code %i",
|
|
|
|
__func__, i, ret);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pthread_mutex_unlock(&threadcount_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (curr_threads >= max_threads
|
|
|
|
|| (curr_threads >= max_threads_scantype && max_threads_scantype > 0)
|
|
|
|
) {
|
|
|
|
usleep (10000); /* microSec's, so 0.01s here */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
upsdebugx(2, "%s: proceeding with scan", __func__);
|
|
|
|
}
|
|
|
|
/* NOTE: No change to default "pass" in this ifdef:
|
|
|
|
* if we got to this line, we have a slot to use */
|
|
|
|
# endif /* HAVE_PTHREAD_TRYJOIN */
|
|
|
|
# endif /* HAVE_SEMAPHORE */
|
|
|
|
#endif /* HAVE_PTHREAD */
|
|
|
|
|
|
|
|
if (pass) {
|
|
|
|
tmp_sec = malloc(sizeof(nutscan_xml_t));
|
|
|
|
if (tmp_sec == NULL) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Memory allocation error\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
memcpy(tmp_sec, sec, sizeof(nutscan_xml_t));
|
|
|
|
tmp_sec->peername = ip_str;
|
|
|
|
if (tmp_sec->usec_timeout <= 0) {
|
|
|
|
tmp_sec->usec_timeout = usec_timeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
if (pthread_create(&thread, NULL, nutscan_scan_xml_http_generic, (void *)tmp_sec) == 0) {
|
|
|
|
# ifdef HAVE_PTHREAD_TRYJOIN
|
|
|
|
pthread_mutex_lock(&threadcount_mutex);
|
|
|
|
curr_threads++;
|
|
|
|
# endif /* HAVE_PTHREAD_TRYJOIN */
|
|
|
|
|
|
|
|
thread_count++;
|
|
|
|
nutscan_thread_t *new_thread_array = realloc(thread_array,
|
|
|
|
thread_count*sizeof(nutscan_thread_t));
|
|
|
|
if (new_thread_array == NULL) {
|
|
|
|
upsdebugx(1, "%s: Failed to realloc thread array", __func__);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
thread_array = new_thread_array;
|
|
|
|
}
|
|
|
|
thread_array[thread_count - 1].thread = thread;
|
|
|
|
thread_array[thread_count - 1].active = TRUE;
|
|
|
|
|
|
|
|
# ifdef HAVE_PTHREAD_TRYJOIN
|
|
|
|
pthread_mutex_unlock(&threadcount_mutex);
|
|
|
|
# endif /* HAVE_PTHREAD_TRYJOIN */
|
|
|
|
}
|
|
|
|
#else /* not HAVE_PTHREAD */
|
|
|
|
nutscan_scan_xml_http_generic((void *)tmp_sec);
|
|
|
|
#endif /* if HAVE_PTHREAD */
|
|
|
|
|
|
|
|
/* free(ip_str); */ /* One of these free()s seems to cause a double-free instead */
|
|
|
|
ip_str = nutscan_ip_iter_inc(&ip);
|
|
|
|
/* free(tmp_sec); */
|
|
|
|
} else { /* if not pass -- all slots busy */
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
# ifdef HAVE_SEMAPHORE
|
|
|
|
/* Wait for all current scans to complete */
|
|
|
|
if (thread_array != NULL) {
|
|
|
|
upsdebugx (2, "%s: Running too many scanning threads, "
|
|
|
|
"waiting until older ones would finish",
|
|
|
|
__func__);
|
|
|
|
for (i = 0; i < thread_count ; i++) {
|
|
|
|
int ret;
|
|
|
|
if (!thread_array[i].active) {
|
|
|
|
/* Probably should not get here,
|
|
|
|
* but handle it just in case */
|
|
|
|
upsdebugx(0, "WARNING: %s: Midway clean-up: did not expect thread %zu to be not active",
|
|
|
|
__func__, i);
|
|
|
|
sem_post(semaphore);
|
|
|
|
if (max_threads_scantype > 0)
|
|
|
|
sem_post(semaphore_scantype);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
thread_array[i].active = FALSE;
|
|
|
|
ret = pthread_join(thread_array[i].thread, NULL);
|
|
|
|
if (ret != 0) {
|
|
|
|
upsdebugx(0, "WARNING: %s: Midway clean-up: pthread_join() returned code %i",
|
|
|
|
__func__, ret);
|
|
|
|
}
|
|
|
|
sem_post(semaphore);
|
|
|
|
if (max_threads_scantype > 0)
|
|
|
|
sem_post(semaphore_scantype);
|
|
|
|
}
|
|
|
|
thread_count = 0;
|
|
|
|
free(thread_array);
|
|
|
|
thread_array = NULL;
|
|
|
|
}
|
|
|
|
# else
|
|
|
|
# ifdef HAVE_PTHREAD_TRYJOIN
|
|
|
|
/* TODO: Move the wait-loop for TRYJOIN here? */
|
|
|
|
# endif /* HAVE_PTHREAD_TRYJOIN */
|
|
|
|
# endif /* HAVE_SEMAPHORE */
|
|
|
|
#endif /* HAVE_PTHREAD */
|
|
|
|
} /* if: could we "pass" or not? */
|
|
|
|
} /* while */
|
|
|
|
|
|
|
|
#ifdef HAVE_PTHREAD
|
|
|
|
if (thread_array != NULL) {
|
|
|
|
upsdebugx(2, "%s: all planned scans launched, waiting for threads to complete", __func__);
|
|
|
|
for (i = 0; i < thread_count; i++) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!thread_array[i].active) continue;
|
|
|
|
|
|
|
|
ret = pthread_join(thread_array[i].thread, NULL);
|
|
|
|
if (ret != 0) {
|
|
|
|
upsdebugx(0, "WARNING: %s: Clean-up: pthread_join() returned code %i",
|
|
|
|
__func__, ret);
|
|
|
|
}
|
|
|
|
thread_array[i].active = FALSE;
|
|
|
|
# ifdef HAVE_SEMAPHORE
|
|
|
|
sem_post(semaphore);
|
|
|
|
if (max_threads_scantype > 0)
|
|
|
|
sem_post(semaphore_scantype);
|
|
|
|
# else
|
|
|
|
# ifdef HAVE_PTHREAD_TRYJOIN
|
|
|
|
pthread_mutex_lock(&threadcount_mutex);
|
|
|
|
if (curr_threads > 0) {
|
|
|
|
curr_threads --;
|
|
|
|
upsdebugx(5, "%s: Clean-up: Joined a finished thread #%zu",
|
|
|
|
__func__, i);
|
|
|
|
} else {
|
|
|
|
upsdebugx(0, "WARNING: %s: Clean-up: Accounting of thread count "
|
|
|
|
"says we are already at 0", __func__);
|
|
|
|
}
|
|
|
|
pthread_mutex_unlock(&threadcount_mutex);
|
|
|
|
# endif /* HAVE_PTHREAD_TRYJOIN */
|
|
|
|
# endif /* HAVE_SEMAPHORE */
|
|
|
|
}
|
|
|
|
free(thread_array);
|
|
|
|
upsdebugx(2, "%s: all threads freed", __func__);
|
|
|
|
}
|
|
|
|
pthread_mutex_destroy(&dev_mutex);
|
|
|
|
|
|
|
|
# ifdef HAVE_SEMAPHORE
|
|
|
|
if (max_threads_scantype > 0)
|
|
|
|
sem_destroy(semaphore_scantype);
|
|
|
|
# endif /* HAVE_SEMAPHORE */
|
|
|
|
#endif /* HAVE_PTHREAD */
|
|
|
|
|
|
|
|
result = nutscan_rewind_device(dev_ret);
|
|
|
|
dev_ret = NULL;
|
|
|
|
return result;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
|
|
|
|
tmp_sec = malloc(sizeof(nutscan_xml_t));
|
|
|
|
if (tmp_sec == NULL) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Memory allocation error\n");
|
|
|
|
return NULL;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
memcpy(tmp_sec, sec, sizeof(nutscan_xml_t));
|
|
|
|
if (start_ip == NULL) {
|
|
|
|
tmp_sec->peername = NULL;
|
|
|
|
} else {
|
|
|
|
tmp_sec->peername = strdup(start_ip);
|
|
|
|
}
|
2011-09-29 18:14:46 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (tmp_sec->usec_timeout <= 0) {
|
|
|
|
tmp_sec->usec_timeout = usec_timeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
nutscan_scan_xml_http_generic(tmp_sec);
|
|
|
|
result = nutscan_rewind_device(dev_ret);
|
|
|
|
dev_ret = NULL;
|
|
|
|
free(tmp_sec);
|
|
|
|
return result;
|
2011-09-29 18:14:46 +00:00
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
#else /* WITH_NEON */
|
2022-06-29 10:37:36 +00:00
|
|
|
|
|
|
|
nutscan_device_t * nutscan_scan_xml_http_range(const char * start_ip, const char * end_ip, useconds_t usec_timeout, nutscan_xml_t * sec)
|
2012-01-24 10:22:33 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
NUT_UNUSED_VARIABLE(start_ip);
|
|
|
|
NUT_UNUSED_VARIABLE(end_ip);
|
|
|
|
NUT_UNUSED_VARIABLE(usec_timeout);
|
|
|
|
NUT_UNUSED_VARIABLE(sec);
|
2012-01-24 10:22:33 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2011-09-29 18:14:46 +00:00
|
|
|
#endif /* WITH_NEON */
|