Skip to content

Commit

Permalink
Merge pull request networkupstools#2518 from jimklimov/issue-2512
Browse files Browse the repository at this point in the history
Fix `nut-scanner` IPv6 support
  • Loading branch information
jimklimov authored Jul 10, 2024
2 parents 005844a + 370538f commit 6e0e2fc
Show file tree
Hide file tree
Showing 13 changed files with 473 additions and 243 deletions.
3 changes: 3 additions & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ https://github.com/networkupstools/nut/milestone/11
+
NOTE: Currently both the long and short names for `libupsclient` should be
installed.
* fixed support for IPv6 addresses (passed in square brackets) for both
`-s` start/`-e` end command-line options, and for `-m cidr/mask` option.
[issue #2512, PR #2518]
* newly added support to scan several IP addresses (single or ranges)
with the same call, by repeating command-line options; also `-m auto{,4,6}`
can be specified (once) to select IP (all, IPv4, IPv6) address ranges of
Expand Down
21 changes: 20 additions & 1 deletion clients/upsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Copyright (C) 1999 Russell Kroll <[email protected]>
Copyright (C) 2012 Arnaud Quette <[email protected]>
Copyright (C) 2020-2024 Jim Klimov <[email protected]>
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
Expand Down Expand Up @@ -217,10 +218,22 @@ static void clean_exit(void)

int main(int argc, char **argv)
{
int i;
int i = 0;
uint16_t port;
int varlist = 0, clientlist = 0, verbose = 0;
const char *prog = xbasename(argv[0]);
char *s = NULL;

/* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc
* and NUT methods called from it. This line aims to just initialize
* the subsystem, and set initial timestamp. Debugging the client is
* primarily of use to developers, so is not exposed via `-D` args.
*/
s = getenv("NUT_DEBUG_LEVEL");
if (s && str_to_int(s, &i, 10) && i > 0) {
nut_debug_level = i;
}
upsdebugx(1, "Starting NUT client: %s", prog);

while ((i = getopt(argc, argv, "+hlLcV")) != -1) {

Expand Down Expand Up @@ -267,6 +280,8 @@ int main(int argc, char **argv)
fatalx(EXIT_FAILURE, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]");
}
}
upsdebugx(1, "upsname='%s' hostname='%s' port='%" PRIu16 "'",
NUT_STRARG(upsname), NUT_STRARG(hostname), port);

ups = xmalloc(sizeof(*ups));

Expand All @@ -275,18 +290,22 @@ int main(int argc, char **argv)
}

if (varlist) {
upsdebugx(1, "Calling list_upses()");
list_upses(verbose);
exit(EXIT_SUCCESS);
}

if (clientlist) {
upsdebugx(1, "Calling list_clients()");
list_clients(upsname);
exit(EXIT_SUCCESS);
}

if (argc > 1) {
upsdebugx(1, "Calling printvar(%s)", argv[1]);
printvar(argv[1]);
} else {
upsdebugx(1, "Calling list_vars()");
list_vars();
}

Expand Down
19 changes: 18 additions & 1 deletion clients/upsclient.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Copyright (C)
2002 Russell Kroll <[email protected]>
2008 Arjen de Korte <[email protected]>
2020 - 2024 Jim Klimov <[email protected]>
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
Expand Down Expand Up @@ -1024,6 +1025,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags
ups->fd = -1;

if (!host) {
upslogx(LOG_WARNING, "%s: Host not found: '%s'", __func__, NUT_STRARG(host));
ups->upserror = UPSCLI_ERR_NOSUCHHOST;
return -1;
}
Expand All @@ -1049,6 +1051,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags
case EAI_AGAIN:
continue;
case EAI_NONAME:
upslogx(LOG_WARNING, "%s: Host not found: '%s'", __func__, NUT_STRARG(host));
ups->upserror = UPSCLI_ERR_NOSUCHHOST;
return -1;
case EAI_MEMORY:
Expand Down Expand Up @@ -1207,7 +1210,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags
} else if (tryssl && ret == 0) {
if (certverify != 0) {
upslogx(LOG_NOTICE, "Can not connect to NUT server %s in SSL and "
"certificate is needed, disconnect", host);
"certificate is needed, disconnect", host);
upscli_disconnect(ups);
return -1;
}
Expand Down Expand Up @@ -1686,7 +1689,21 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port)
return -1;
}

s = strchr(tmp, '@');

/* someone passed a "@hostname" string? */
if (s) {
fprintf(stderr, "upscli_splitaddr: wrong call? "
"Got upsname@hostname[:port] string where "
"only hostname[:port] was expected: %s\n", buf);
/* let it pass, but probably fail later */
}

if (*tmp == '[') {
/* NOTE: Brackets are required for colon-separated IPv6
* addresses, to differentiate from a port number. For
* example, `[1234:5678]:3493` would seem right.
*/
if (strchr(tmp, ']') == NULL) {
fprintf(stderr, "upscli_splitaddr: missing closing bracket in [domain literal]\n");
return -1;
Expand Down
2 changes: 2 additions & 0 deletions docs/man/nut-scanner.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ Requests to scan many single IP addresses will take a while to complete, much
longer than if they were a single range. This will be hopefully fixed in later
releases.

NOTE: Colon-separated IPv6 addresses must be passed in square brackets.

*-t* | *--timeout* 'timeout'::
Set the network timeout in seconds. Default timeout is 5 seconds.

Expand Down
2 changes: 1 addition & 1 deletion tools/nut-scanner/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ libnutscan_la_LDFLAGS += -version-info 2:5:2
# copies of "nut_debug_level" making fun of our debug-logging attempts.
# One solution to tackle if needed for those cases would be to make some
# dynamic/shared libnutcommon (etc.)
libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebugx|fatalx|xcalloc|snprintfcat|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths)'
libnutscan_la_LDFLAGS += -export-symbols-regex '^(nutscan_|nut_debug_level|s_upsdebug|fatalx|xcalloc|snprintfcat|max_threads|curr_threads|nut_report_config_flags|upsdebugx_report_search_paths|nut_prepare_search_paths)'
libnutscan_la_CFLAGS = -I$(top_srcdir)/clients -I$(top_srcdir)/include \
$(LIBLTDL_CFLAGS) -I$(top_srcdir)/drivers

Expand Down
36 changes: 32 additions & 4 deletions tools/nut-scanner/nut-scanner.c
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ static void show_usage(void)

printf(" -E, --eaton_serial <serial ports list>: Scan serial Eaton devices (XCP, SHUT and Q1).\n");

#if (defined HAVE_PTHREAD) && (defined HAVE_PTHREAD_TRYJOIN)
#if (defined HAVE_PTHREAD) && ( (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE) )
printf(" -T, --thread <max number of threads>: Limit the amount of scanning threads running simultaneously (default: %" PRIuSIZE ").\n", max_threads);
#else
printf(" -T, --thread <max number of threads>: Limit the amount of scanning threads running simultaneously (not implemented in this build: no pthread support)\n");
Expand Down Expand Up @@ -1267,7 +1267,13 @@ int main(int argc, char *argv[])
end_ip = NULL;
}

start_ip = strdup(optarg);
if (optarg[0] == '[' && optarg[strlen(optarg) - 1] == ']') {
start_ip = strdup(optarg + 1);
start_ip[strlen(start_ip) - 1] = '\0';
} else {
start_ip = strdup(optarg);
}

if (end_ip != NULL) {
/* Already we know two addresses, save them */
nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip);
Expand All @@ -1285,7 +1291,13 @@ int main(int argc, char *argv[])
end_ip = NULL;
}

end_ip = strdup(optarg);
if (optarg[0] == '[' && optarg[strlen(optarg) - 1] == ']') {
end_ip = strdup(optarg + 1);
end_ip[strlen(end_ip) - 1] = '\0';
} else {
end_ip = strdup(optarg);
}

if (start_ip != NULL) {
/* Already we know two addresses, save them */
nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip);
Expand Down Expand Up @@ -1549,6 +1561,15 @@ int main(int argc, char *argv[])
}

#ifdef HAVE_PTHREAD
# if (defined HAVE_PTHREAD_TRYJOIN) && (defined HAVE_SEMAPHORE)
upsdebugx(1, "Parallel scan support: HAVE_PTHREAD && HAVE_PTHREAD_TRYJOIN && HAVE_SEMAPHORE");
# elif (defined HAVE_PTHREAD_TRYJOIN)
upsdebugx(1, "Parallel scan support: HAVE_PTHREAD && HAVE_PTHREAD_TRYJOIN && !HAVE_SEMAPHORE");
# elif (defined HAVE_SEMAPHORE)
upsdebugx(1, "Parallel scan support: HAVE_PTHREAD && !HAVE_PTHREAD_TRYJOIN && HAVE_SEMAPHORE");
# else
upsdebugx(1, "Parallel scan support: HAVE_PTHREAD && !HAVE_PTHREAD_TRYJOIN && !HAVE_SEMAPHORE");
# endif
# ifdef HAVE_SEMAPHORE
/* FIXME: Currently sem_init already done on nutscan-init for lib need.
We need to destroy it before re-init. We currently can't change "sem value"
Expand Down Expand Up @@ -1577,8 +1598,15 @@ int main(int argc, char *argv[])
"WARNING: Limiting max_threads to range acceptable for sem_init()\n\n");
max_threads = UINT_MAX - 1;
}
sem_init(current_sem, 0, (unsigned int)max_threads);

upsdebugx(1, "Parallel scan support: max_threads=%" PRIuSIZE, max_threads);
if (sem_init(current_sem, 0, (unsigned int)max_threads)) {
/* Show this one to end-users so they know */
upsdebug_with_errno(0, "Parallel scan support: sem_init() failed");
}
# endif
#else
upsdebugx(1, "Parallel scan support: !HAVE_PTHREAD");
#endif /* HAVE_PTHREAD */

if (start_ip != NULL || end_ip != NULL) {
Expand Down
8 changes: 7 additions & 1 deletion tools/nut-scanner/nutscan-init.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <string.h>
#include "nut-scan.h"
#include "nut_platform.h"
#include "nut_stdint.h"

#ifdef WIN32
# if defined HAVE_WINSOCK2_H && HAVE_WINSOCK2_H
Expand Down Expand Up @@ -169,7 +170,12 @@ void nutscan_init(void)
__func__);
max_threads = UINT_MAX - 1;
}
sem_init(&semaphore, 0, (unsigned int)max_threads);

upsdebugx(1, "%s: Parallel scan support: max_threads=%" PRIuSIZE,
__func__, max_threads);
if (sem_init(&semaphore, 0, (unsigned int)max_threads)) {
upsdebug_with_errno(4, "%s: Parallel scan support: sem_init() failed", __func__);
}
# endif

# ifdef HAVE_PTHREAD_TRYJOIN
Expand Down
40 changes: 37 additions & 3 deletions tools/nut-scanner/nutscan-ip.c
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const
return strdup(host);
}
else { /* IPv6 */
size_t hlen;
for (i = 0; i < 16; i++) {
if (ip->start6.s6_addr[i] !=ip->stop6.s6_addr[i]) {
if (ip->start6.s6_addr[i] > ip->stop6.s6_addr[i]) {
Expand All @@ -431,9 +432,17 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const
}
}

if (ntop6(&ip->start6, host, sizeof(host)) != 0) {
/* IPv6 addresses must be in square brackets,
* for upsclient et al to differentiate them
* from the port number.
*/
host[0] = '[';
if (ntop6(&ip->start6, host + 1, sizeof(host) - 2) != 0) {
return NULL;
}
hlen = strlen(host);
host[hlen] = ']';
host[hlen + 1] = '\0';

return strdup(host);
}
Expand Down Expand Up @@ -463,16 +472,27 @@ char * nutscan_ip_iter_inc(nutscan_ip_iter_t * ip)
return strdup(host);
}
else {
size_t hlen;

/* Check if this is the last address to scan */
if (memcmp(&ip->start6.s6_addr, &ip->stop6.s6_addr,
sizeof(ip->start6.s6_addr)) == 0) {
return NULL;
}

increment_IPv6(&ip->start6);
if (ntop6(&ip->start6, host, sizeof(host)) != 0) {

/* IPv6 addresses must be in square brackets,
* for upsclient et al to differentiate them
* from the port number.
*/
host[0] = '[';
if (ntop6(&ip->start6, host + 1, sizeof(host) - 2) != 0) {
return NULL;
}
hlen = strlen(host);
host[hlen] = ']';
host[hlen + 1] = '\0';

return strdup(host);
}
Expand Down Expand Up @@ -505,6 +525,12 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip)
*start_ip = NULL;
*stop_ip = NULL;

if (!cidr) {
upsdebugx(0, "WARNING: %s: null cidr pointer was provided",
__func__);
return 0;
}

cidr_tok = strdup(cidr);
first_ip = strdup(strtok_r(cidr_tok, "/", &saveptr));
if (first_ip == NULL) {
Expand All @@ -517,10 +543,18 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip)
if (mask == NULL) {
upsdebugx(0, "WARNING: %s failed to parse mask from cidr=%s (first_ip=%s)",
__func__, cidr, first_ip);
free (first_ip);
free(first_ip);
free(cidr_tok);
return 0;
}

if (first_ip[0] == '[' && first_ip[strlen(first_ip) - 1] == ']') {
char *s = strdup(first_ip + 1);
s[strlen(s) - 1] = '\0';
free(first_ip);
first_ip = s;
}

upsdebugx(5, "%s: parsed cidr=%s into first_ip=%s and mask=%s",
__func__, cidr, first_ip, mask);

Expand Down
9 changes: 8 additions & 1 deletion tools/nut-scanner/scan_eaton_serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,15 @@ nutscan_device_t * nutscan_scan_eaton_serial(const char* ports_range)
char *current_port_name = NULL;
char **serial_ports_list;
int current_port_nb;

#ifdef HAVE_PTHREAD
# ifdef HAVE_SEMAPHORE
sem_t * semaphore = nutscan_semaphore();
# endif
/* TODO? Port semaphore_scantype / max_threads_scantype
* from sibling sources? We do not have that many serial
* ports to care much, usually... right?
*/
# endif /* HAVE_SEMAPHORE */
pthread_t thread;
nutscan_thread_t * thread_array = NULL;
size_t thread_count = 0, i;
Expand Down Expand Up @@ -484,6 +489,7 @@ nutscan_device_t * nutscan_scan_eaton_serial(const char* ports_range)
"(launched overall: %" PRIuSIZE "), "
"waiting until some would finish",
__func__, curr_threads, thread_count);

while (curr_threads >= max_threads) {
for (i = 0; i < thread_count ; i++) {
int ret;
Expand Down Expand Up @@ -528,6 +534,7 @@ nutscan_device_t * nutscan_scan_eaton_serial(const char* ports_range)
}
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 */
Expand Down
6 changes: 6 additions & 0 deletions tools/nut-scanner/scan_ipmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t
}
else {
/* FIXME: also check against "localhost" and its IPv{4,6} */
/* FIXME: Should the IPv6 address here be bracketed?
* Does our driver support the notation? */
sprintf(port_id, "id%x@%s", ipmi_id, IPaddr);
}
nut_dev->port = strdup(port_id);
Expand Down Expand Up @@ -647,6 +649,10 @@ nutscan_device_t * nutscan_scan_ip_range_ipmi(nutscan_ip_range_list_t * irl, nut
current_nut_dev = nutscan_scan_ipmi_device(NULL, NULL);
}
else {
/* TODO: Port HAVE_PTHREAD_TRYJOIN etc. from other files?
* Notably, the scans below currently are only sequential
* and so very slow (5 sec per IP timeout by default).
*/
if (irl->ip_ranges_count == 1
&& (irl->ip_ranges->start_ip == irl->ip_ranges->end_ip
|| !strcmp(irl->ip_ranges->start_ip, irl->ip_ranges->end_ip)
Expand Down
Loading

0 comments on commit 6e0e2fc

Please sign in to comment.