From c57a3ad3a4e0aee693571d5d96bf4c98482f78e4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 17:10:21 +0200 Subject: [PATCH 01/30] tools/nut-scanner/{scan_xml_http.c,scan_snmp.c,scan_nut.c,scan_ipmi.c}: report the IP address (range, single, none) in the log [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/scan_ipmi.c | 14 ++++++++++++-- tools/nut-scanner/scan_nut.c | 12 ++++++++++++ tools/nut-scanner/scan_snmp.c | 10 ++++++++++ tools/nut-scanner/scan_xml_http.c | 10 ++++++++-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/tools/nut-scanner/scan_ipmi.c b/tools/nut-scanner/scan_ipmi.c index ba4e50776f..13ec34680e 100644 --- a/tools/nut-scanner/scan_ipmi.c +++ b/tools/nut-scanner/scan_ipmi.c @@ -406,6 +406,8 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t /* Are we scanning locally, or over the network? */ if (IPaddr == NULL) { + upsdebugx(2, "Entering %s for local device scan", __func__); + /* FIXME: we need root right to access local IPMI! if (!ipmi_is_root ()) { fprintf(stderr, "IPMI scan: %s\n", ipmi_ctx_strerror (IPMI_ERR_PERMISSION)); @@ -433,6 +435,8 @@ nutscan_device_t * nutscan_scan_ipmi_device(const char * IPaddr, nutscan_ipmi_t } else { + upsdebugx(2, "Entering %s for %s", __func__, IPaddr); + #if 0 if (ipmi_sec->ipmi_version == IPMI_2_0) { @@ -608,14 +612,20 @@ nutscan_device_t * nutscan_scan_ipmi(const char * start_ip, const char * stop_ip return NULL; } - /* Are we scanning locally, or through the network? */ if (start_ip == NULL) { - /* Local PSU scan */ + upsdebugx(1, "%s: Local PSU scan", __func__); current_nut_dev = nutscan_scan_ipmi_device(NULL, NULL); } else { + if (start_ip == stop_ip || !stop_ip) { + upsdebugx(1, "%s: Scanning remote PSU for single IP address: %s", + __func__, start_ip); + } else { + upsdebugx(1, "%s: Scanning remote PSU for IP address range: %s .. %s", + __func__, start_ip, stop_ip); + } ip_str = nutscan_ip_iter_init(&ip, start_ip, stop_ip); while (ip_str != NULL) { diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index cfff369f8e..9b5aa97fad 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -156,6 +156,8 @@ static void * list_nut_devices(void * arg) query[0] = "UPS"; numq = 1; + upsdebugx(2, "Entering %s for %s", __func__, target_hostname); + if ((*nut_upscli_splitaddr)(target_hostname, &hostname, &port) != 0) { free(target_hostname); free(nut_arg); @@ -301,6 +303,16 @@ nutscan_device_t * nutscan_scan_nut(const char* startIP, const char* stopIP, con return NULL; } + if (!startIP) { + upsdebugx(1, "%s: no starting IP address specified", __func__); + } else if (startIP == stopIP || !stopIP) { + upsdebugx(1, "%s: Scanning \"Old NUT\" bus for single IP address: %s", + __func__, startIP); + } else { + upsdebugx(1, "%s: Scanning \"Old NUT\" bus for IP address range: %s .. %s", + __func__, startIP, stopIP); + } + #ifndef WIN32 /* Ignore SIGPIPE if the caller hasn't set a handler for it yet */ if (sigaction(SIGPIPE, NULL, &oldact) == 0) { diff --git a/tools/nut-scanner/scan_snmp.c b/tools/nut-scanner/scan_snmp.c index bd1ee0a0a3..7c28a85235 100644 --- a/tools/nut-scanner/scan_snmp.c +++ b/tools/nut-scanner/scan_snmp.c @@ -1085,6 +1085,16 @@ nutscan_device_t * nutscan_scan_snmp(const char * start_ip, const char * stop_ip return NULL; } + if (!start_ip) { + upsdebugx(1, "%s: no starting IP address specified", __func__); + } else if (start_ip == stop_ip || !stop_ip) { + upsdebugx(1, "%s: Scanning SNMP for single IP address: %s", + __func__, start_ip); + } else { + upsdebugx(1, "%s: Scanning SNMP for IP address range: %s .. %s", + __func__, start_ip, stop_ip); + } + g_usec_timeout = usec_timeout; /* Force numeric OIDs resolution (ie, do not resolve to textual names) diff --git a/tools/nut-scanner/scan_xml_http.c b/tools/nut-scanner/scan_xml_http.c index 0aa9ccc897..ed635aba33 100644 --- a/tools/nut-scanner/scan_xml_http.c +++ b/tools/nut-scanner/scan_xml_http.c @@ -429,10 +429,11 @@ nutscan_device_t * nutscan_scan_xml_http_range(const char * start_ip, const char } if (start_ip == NULL) { - upsdebugx(1, "Scanning XML/HTTP bus using broadcast."); + upsdebugx(1, "%s: Scanning XML/HTTP bus using broadcast.", __func__); } 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); + upsdebugx(1, "%s: Scanning XML/HTTP bus for single IP address: %s", + __func__, start_ip); } else { /* Iterate the range of IPs to scan */ nutscan_ip_iter_t ip; @@ -449,7 +450,12 @@ nutscan_device_t * nutscan_scan_xml_http_range(const char * start_ip, const char # if (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE) size_t max_threads_scantype = max_threads_netxml; # endif +#endif + + upsdebugx(1, "%s: Scanning XML/HTTP bus for IP address range: %s .. %s", + __func__, start_ip, end_ip); +#ifdef HAVE_PTHREAD pthread_mutex_init(&dev_mutex, NULL); # ifdef HAVE_SEMAPHORE From acfc878431be0bac04ccaed15553c7b1bdc00ef7 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 17:11:55 +0200 Subject: [PATCH 02/30] tools/nut-scanner/nut-scanner.c: clarify some code comments [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 6a8a8f0ebb..9b30fc08e9 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -868,6 +868,11 @@ int main(int argc, char *argv[]) } if (allow_xml && nutscan_avail_xml_http) { + /* NOTE: No check for IP address range, + * NetXML default scan is broadcast + * so it just runs (if requested and + * supported). + */ upsdebugx(quiet, "Scanning XML/HTTP bus."); xml_sec.usec_timeout = timeout; #ifdef HAVE_PTHREAD @@ -939,6 +944,11 @@ int main(int argc, char *argv[]) } if (allow_ipmi && nutscan_avail_ipmi) { + /* NOTE: No check for IP address range, + * IPMI default scan is local device + * so it just runs (if requested and + * supported). + */ upsdebugx(quiet, "Scanning IPMI bus."); #ifdef HAVE_PTHREAD upsdebugx(1, "IPMI SCAN: starting pthread_create with run_ipmi..."); From d7c2d404e28714786fe3d77f59e26ac811fc0dd4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 17:12:26 +0200 Subject: [PATCH 03/30] tools/nut-scanner/nut-scanner.c: comment some TODO for IPv6 netmask proper support [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-ip.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/nut-scanner/nutscan-ip.c b/tools/nut-scanner/nutscan-ip.c index 4223bfa868..9b173d56c5 100644 --- a/tools/nut-scanner/nutscan-ip.c +++ b/tools/nut-scanner/nutscan-ip.c @@ -267,6 +267,7 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) char * mask; char * saveptr = NULL; nutscan_ip_iter_t ip; + /* TODO: There can be up to 128-bit CIDR mask for IPv6... */ int mask_val; int mask_byte; int ret; @@ -302,6 +303,7 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) upsdebugx(5, "%s: parsed cidr=%s into first_ip=%s and mask=%s", __func__, cidr, first_ip, mask); + /* TODO: check if mask is also an IP address or a bit count */ mask_val = atoi(mask); upsdebugx(5, "%s: parsed mask value %d", __func__, mask_val); From 11bf9c3bd3da69f6fb43fa122f1d19640b5004b3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 19:58:48 +0200 Subject: [PATCH 04/30] docs/man/nut-scanner.txt: update general wording about IP address options [#2244] Signed-off-by: Jim Klimov --- docs/man/nut-scanner.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index dca9b946e3..71f34264f6 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -78,7 +78,8 @@ an 'end IP'. See specific SNMP OPTIONS for community and security settings. *-M* | *--xml_scan*:: Scan XML/HTTP devices. Can broadcast a network message on the current network -interfaces to retrieve XML/HTTP capable devices. No IP required in this mode. +interface(s) to retrieve XML/HTTP capable devices. No IP required in this mode. +If IP address ranges are specified, they would be scanned instead of a broadcast. *-O* | *--oldnut_scan*:: Scan NUT devices (i.e. upsd daemon) on IP ranging from 'start IP' to 'end IP'. @@ -87,12 +88,12 @@ Scan NUT devices (i.e. upsd daemon) on IP ranging from 'start IP' to 'end IP'. Scan NUT simulated devices (.dev files in $CONFPATH). *-A* | *--avahi_scan*:: -Scan NUT servers using Avahi request on the current network interfaces. -No IP required. +Scan NUT servers using Avahi request on the current network interface(s). +No IP address options are required or used. *-I* | *--ipmi_scan*:: Scan NUT compatible power supplies available via IPMI on the current host, -or over the network. +or over the network if IP address ranges are specified. *-E* | *--eaton_serial* 'serial ports':: Scan Eaton devices (XCP and SHUT) available via serial bus on the current host. @@ -124,7 +125,7 @@ If this parameter is omitted, only the 'start IP' is scanned. If 'end IP' is less than 'start IP', both parameters are internally permuted. *-m* | *--mask_cidr* 'IP address/mask':: -Set a range of IP using CIDR notation. +Set a range of IP addresses by using CIDR notation. NUT DEVICE OPTION ----------------- From 8d3305bf76450371513faf5508eb204229d02963 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 19:59:09 +0200 Subject: [PATCH 05/30] docs/man/nut-scanner.txt: update general markup Signed-off-by: Jim Klimov --- docs/man/nut-scanner.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index 71f34264f6..9a3fb3b458 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -85,7 +85,7 @@ If IP address ranges are specified, they would be scanned instead of a broadcast Scan NUT devices (i.e. upsd daemon) on IP ranging from 'start IP' to 'end IP'. *-n* | *--nut_simulation_scan*:: -Scan NUT simulated devices (.dev files in $CONFPATH). +Scan NUT simulated devices (`.dev` files in `$NUT_CONFPATH`). *-A* | *--avahi_scan*:: Scan NUT servers using Avahi request on the current network interface(s). @@ -99,12 +99,12 @@ or over the network if IP address ranges are specified. Scan Eaton devices (XCP and SHUT) available via serial bus on the current host. This option must be requested explicitly, even for a complete scan. 'serial ports' can be expressed in various forms: - ++ - 'auto' to scan all serial ports. - a single character indicating a port number ('0' (zero) for /dev/ttyS0 and -/dev/ttyUSB0 on Linux, '1' for COM1 on Windows, 'a' for /dev/ttya on Solaris...) + /dev/ttyUSB0 on Linux, '1' for COM1 on Windows, 'a' for /dev/ttya on Solaris...) - a range of N characters, hyphen separated, describing the range of -ports using 'X-Y', where X and Y are characters referring to the port number. + ports using 'X-Y', where X and Y are characters referring to the port number. - a single port name. - a list of ports name, coma separated, like '/dev/ttyS1,/dev/ttyS4'. From ab34a1064efeb32ed5148f7064c75de4db950e30 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 20:00:46 +0200 Subject: [PATCH 06/30] docs/man/nut-scanner.txt: note that some protocols require IP address options and others behave differently without them [#2244] Signed-off-by: Jim Klimov --- docs/man/nut-scanner.txt | 5 +++++ docs/nut.dict | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index 9a3fb3b458..ed60afd5c5 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -111,6 +111,11 @@ This option must be requested explicitly, even for a complete scan. NETWORK OPTIONS --------------- +NOTE: Some of the networked buses (such as SNMP, NetXML, IPMI and "Old NUT") +require IP address(es) to scan, and others have a +different behavior when exactly no addresses are specified (it is not currently +possible to mix the two behaviors in one invocation of the `nut-scanner` tool). + *-t* | *--timeout* 'timeout':: Set the network timeout in seconds. Default timeout is 5 seconds. diff --git a/docs/nut.dict b/docs/nut.dict index 57888a9c05..559bbc115d 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3177 utf-8 +personal_ws-1.1 en 3178 utf-8 AAC AAS ABI @@ -754,6 +754,7 @@ NetInvent NetPro NetServer NetUps +NetXML Netman NetworkUPSTools Neus From 286909f6200471ed02d664ce8690ebc8d8e62163 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 17:13:37 +0200 Subject: [PATCH 07/30] tools/nut-scanner/nut-scanner.c, docs/man/nut-scanner.txt, NEWS.adoc: introduce a way to scan several IP address ranges [#2244] Signed-off-by: Jim Klimov --- NEWS.adoc | 2 + docs/man/nut-scanner.txt | 11 +- tools/nut-scanner/nut-scanner.c | 269 +++++++++++++++++++++++++++++--- 3 files changed, 255 insertions(+), 27 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 82b507c397..9a5da3d44c 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -140,6 +140,8 @@ https://github.com/networkupstools/nut/milestone/11 + NOTE: Currently both the long and short names for `libupsclient` should be installed. + * newly added support to scan several IP addresses (single or ranges) + with the same call, by repeating command-line options. [#2244] - common code: * introduced a `NUT_DEBUG_SYSLOG` environment variable to tweak activation diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index ed60afd5c5..6df5099b7b 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -111,8 +111,15 @@ This option must be requested explicitly, even for a complete scan. NETWORK OPTIONS --------------- -NOTE: Some of the networked buses (such as SNMP, NetXML, IPMI and "Old NUT") -require IP address(es) to scan, and others have a +NOTE: The networked buses (such as SNMP, NetXML, IPMI and "Old NUT") allow to +specify several IP (IPv4 or IPv6) address ranges, down to individual single +IP addresses. Normally a new range is specified by a set of one `-s` and one +`-e` options following each other (in any order). Lone or consecutive `-s` or +`-e` options present on the command line would translate to single-IP queries. +Also a `-m` option squashed between two `-s` and `-e` options would be a new +range, turning those two into single-IP queries. ++ +Also note that some buses require IP address(es) to scan, and others have a different behavior when exactly no addresses are specified (it is not currently possible to mix the two behaviors in one invocation of the `nut-scanner` tool). diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 9b30fc08e9..304c99d799 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -110,12 +110,92 @@ static const struct option longopts[] = { static nutscan_device_t *dev[TYPE_END]; static useconds_t timeout = DEFAULT_NETWORK_TIMEOUT * 1000 * 1000; /* in usec */ -static char * start_ip = NULL; -static char * end_ip = NULL; static char * port = NULL; static char * serial_ports = NULL; static int cli_link_detail_level = -1; +/* Track requested IP ranges (from CLI or auto-discovery) */ +typedef struct ip_range_s { + char * start_ip; + char * end_ip; + struct ip_range_s * next; +} ip_range_t; +static ip_range_t * ip_ranges = NULL; +static ip_range_t * ip_ranges_last = NULL; +static size_t ip_ranges_count = 0; + +static size_t add_ip_range(char * start_ip, char * end_ip) +{ + ip_range_t *p; + + if (!start_ip && !end_ip) { + upsdebugx(1, "%s: skip, no addresses were provided", __func__); + return ip_ranges_count; + } + + if (start_ip == NULL) { + upsdebugx(1, "%s: only end address was provided, setting start to same: %s", + __func__, end_ip); + start_ip = end_ip; + } + if (end_ip == NULL) { + upsdebugx(1, "%s: only start address was provided, setting end to same: %s", + __func__, start_ip); + end_ip = start_ip; + } + + /* no xcalloc() nor fatalx() linked here, oh well */ + p = calloc(1, sizeof(ip_range_t)); + if (!p) { + upsdebugx(0, "%s: failed to allocate some memory", __func__); + exit(EXIT_FAILURE); + } + + p->start_ip = start_ip; + p->end_ip = end_ip; + p->next = NULL; + + if (!ip_ranges) { + ip_ranges = p; + } + + if (ip_ranges_last) { + ip_ranges_last->next = p; + } + ip_ranges_last = p; + ip_ranges_count++; + + upsdebugx(1, "Recorded IP address range #%" PRIuSIZE ": [%s .. %s]", + ip_ranges_count, start_ip, end_ip); + + return ip_ranges_count; +} + +static void free_ip_ranges(void) +{ + ip_range_t *p = ip_ranges; + + while (p) { + ip_ranges = p->next; + + /* Only free the strings once, if they pointed to same */ + if (p->start_ip == p->end_ip && p->start_ip) { + free(p->start_ip); + } else { + if (p->start_ip) + free(p->start_ip); + if (p->end_ip) + free(p->end_ip); + } + + free(p); + p = ip_ranges; + } + + ip_ranges_last = NULL; + ip_ranges_count = 0; +} + #ifdef HAVE_PTHREAD static pthread_t thread[TYPE_END]; @@ -176,24 +256,83 @@ static void * run_usb(void *arg) static void * run_snmp(void * arg) { nutscan_snmp_t * sec = (nutscan_snmp_t *)arg; + nutscan_device_t * dev_ret; + ip_range_t *p = ip_ranges; + + upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", + __func__, ip_ranges_count); + + dev[TYPE_SNMP] = NULL; + while (p) { + dev_ret = nutscan_scan_snmp(p->start_ip, p->end_ip, timeout, sec); + if (!dev[TYPE_SNMP]) { + dev[TYPE_SNMP] = dev_ret; + } else { + dev[TYPE_SNMP] = nutscan_rewind_device( + nutscan_add_device_to_device(dev_ret, dev[TYPE_SNMP])); + } + p = p->next; + } - dev[TYPE_SNMP] = nutscan_scan_snmp(start_ip, end_ip, timeout, sec); + upsdebugx(2, "Finished %s loop", __func__); return NULL; } static void * run_xml(void * arg) { nutscan_xml_t * sec = (nutscan_xml_t *)arg; + nutscan_device_t * dev_ret; + ip_range_t *p = ip_ranges; + + upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", + __func__, ip_ranges_count); - dev[TYPE_XML] = nutscan_scan_xml_http_range(start_ip, end_ip, timeout, sec); + if (!p) { + /* Probe broadcast */ + dev[TYPE_XML] = nutscan_scan_xml_http_range(NULL, NULL, timeout, sec); + + upsdebugx(2, "Finished %s query", __func__); + return NULL; + } + + dev[TYPE_XML] = NULL; + while (p) { + dev_ret = nutscan_scan_xml_http_range(p->start_ip, p->end_ip, timeout, sec); + if (!dev[TYPE_XML]) { + dev[TYPE_XML] = dev_ret; + } else { + dev[TYPE_XML] = nutscan_rewind_device( + nutscan_add_device_to_device(dev_ret, dev[TYPE_XML])); + } + p = p->next; + } + + upsdebugx(2, "Finished %s loop", __func__); return NULL; } static void * run_nut_old(void *arg) { + nutscan_device_t * dev_ret; + ip_range_t *p = ip_ranges; NUT_UNUSED_VARIABLE(arg); - dev[TYPE_NUT] = nutscan_scan_nut(start_ip, end_ip, port, timeout); + upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", + __func__, ip_ranges_count); + + dev[TYPE_NUT] = NULL; + while (p) { + dev_ret = nutscan_scan_nut(p->start_ip, p->end_ip, port, timeout); + if (!dev[TYPE_NUT]) { + dev[TYPE_NUT] = dev_ret; + } else { + dev[TYPE_NUT] = nutscan_rewind_device( + nutscan_add_device_to_device(dev_ret, dev[TYPE_NUT])); + } + p = p->next; + } + + upsdebugx(2, "Finished %s loop", __func__); return NULL; } @@ -216,8 +355,33 @@ static void * run_avahi(void *arg) static void * run_ipmi(void * arg) { nutscan_ipmi_t * sec = (nutscan_ipmi_t *)arg; + nutscan_device_t * dev_ret; + ip_range_t *p = ip_ranges; + + upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", + __func__, ip_ranges_count); + + if (!p) { + /* Probe local device */ + dev[TYPE_IPMI] = nutscan_scan_ipmi(NULL, NULL, sec); - dev[TYPE_IPMI] = nutscan_scan_ipmi(start_ip, end_ip, sec); + upsdebugx(2, "Finished %s query", __func__); + return NULL; + } + + dev[TYPE_IPMI] = NULL; + while (p) { + dev_ret = nutscan_scan_ipmi(p->start_ip, p->end_ip, sec); + if (!dev[TYPE_IPMI]) { + dev[TYPE_IPMI] = dev_ret; + } else { + dev[TYPE_IPMI] = nutscan_rewind_device( + nutscan_add_device_to_device(dev_ret, dev[TYPE_IPMI])); + } + p = p->next; + } + + upsdebugx(2, "Finished %s loop", __func__); return NULL; } @@ -286,6 +450,9 @@ static void show_usage(void) printf(" -s, --start_ip : First IP address to scan.\n"); printf(" -e, --end_ip : Last IP address to scan.\n"); printf(" -m, --mask_cidr : Give a range of IP using CIDR notation.\n"); + printf("NOTE: IP address range specifications can be repeated, to scan several.\n"); + printf("Specifying a single first or last address before starting another range\n"); + printf("leads to scanning just that one address as the range.\n"); if (nutscan_avail_snmp) { printf("\nSNMP v1 specific options:\n"); @@ -414,7 +581,7 @@ int main(int argc, char *argv[]) nutscan_ipmi_t ipmi_sec; nutscan_xml_t xml_sec; int opt_ret; - char * cidr = NULL; + char *start_ip = NULL, *end_ip = NULL; int allow_all = 0; int allow_usb = 0; int allow_snmp = 0; @@ -512,22 +679,62 @@ int main(int argc, char *argv[]) } break; case 's': + if (start_ip) { + /* Save whatever we have, either + * this one address or an earlier + * known range with its end */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } + start_ip = strdup(optarg); - if (end_ip == NULL) - end_ip = start_ip; + if (end_ip != NULL) { + /* Already we know two addresses, save them */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } break; case 'e': + if (end_ip) { + /* Save whatever we have, either + * this one address or an earlier + * known range with its start */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } + end_ip = strdup(optarg); - if (start_ip == NULL) - start_ip = end_ip; + if (start_ip != NULL) { + /* Already we know two addresses, save them */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } break; case 'E': serial_ports = strdup(optarg); allow_eaton_serial = 1; break; case 'm': - cidr = strdup(optarg); - upsdebugx(5, "Got CIDR net/mask: %s", cidr); + if (start_ip || end_ip) { + /* Save whatever we have, either + * this one address or an earlier + * known range with its start or end */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } + + upsdebugx(5, "Processing CIDR net/mask: %s", optarg); + nutscan_cidr_to_ip(optarg, &start_ip, &end_ip); + upsdebugx(5, "Extracted IP address range from CIDR net/mask: %s => %s", start_ip, end_ip); + + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; break; case 'D': /* nothing to do, here */ @@ -799,10 +1006,11 @@ int main(int argc, char *argv[]) # endif #endif /* HAVE_PTHREAD */ - if (cidr) { - upsdebugx(1, "Processing CIDR net/mask: %s", cidr); - nutscan_cidr_to_ip(cidr, &start_ip, &end_ip); - upsdebugx(1, "Extracted IP address range from CIDR net/mask: %s => %s", start_ip, end_ip); + if (start_ip != NULL || end_ip != NULL) { + /* Something did not cancel out above */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; } if (!allow_usb && !allow_snmp && !allow_xml && !allow_oldnut && !allow_nut_simulation && @@ -846,8 +1054,8 @@ int main(int argc, char *argv[]) } if (allow_snmp && nutscan_avail_snmp) { - if (start_ip == NULL) { - upsdebugx(quiet, "No start IP, skipping SNMP"); + if (!ip_ranges_count) { + upsdebugx(quiet, "No IP range(s) requested, skipping SNMP"); nutscan_avail_snmp = 0; } else { @@ -868,7 +1076,7 @@ int main(int argc, char *argv[]) } if (allow_xml && nutscan_avail_xml_http) { - /* NOTE: No check for IP address range, + /* NOTE: No check for ip_ranges_count, * NetXML default scan is broadcast * so it just runs (if requested and * supported). @@ -883,15 +1091,20 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "XML/HTTP SCAN: no pthread support, starting nutscan_scan_xml_http_range()..."); - dev[TYPE_XML] = nutscan_scan_xml_http_range(start_ip, end_ip, timeout, &xml_sec); + if (ip_ranges_count) { + dev[TYPE_XML] = nutscan_scan_xml_http_range(start_ip, end_ip, timeout, &xml_sec); + } else { + /* Probe broadcast */ + dev[TYPE_XML] = nutscan_scan_xml_http_range(NULL, NULL, timeout, &xml_sec); + } #endif /* HAVE_PTHREAD */ } else { upsdebugx(1, "XML/HTTP SCAN: not requested or supported, SKIPPED"); } if (allow_oldnut && nutscan_avail_nut) { - if (start_ip == NULL) { - upsdebugx(quiet, "No start IP, skipping NUT bus (old libupsclient connect method)"); + if (!ip_ranges_count) { + upsdebugx(quiet, "No IP range(s) requested, skipping NUT bus (old libupsclient connect method)"); nutscan_avail_nut = 0; } else { @@ -944,7 +1157,7 @@ int main(int argc, char *argv[]) } if (allow_ipmi && nutscan_avail_ipmi) { - /* NOTE: No check for IP address range, + /* NOTE: No check for ip_ranges_count, * IPMI default scan is local device * so it just runs (if requested and * supported). @@ -958,7 +1171,12 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "IPMI SCAN: no pthread support, starting nutscan_scan_ipmi..."); - dev[TYPE_IPMI] = nutscan_scan_ipmi(start_ip, end_ip, &ipmi_sec); + if (ip_ranges_count) { + dev[TYPE_IPMI] = nutscan_scan_ipmi(start_ip, end_ip, &ipmi_sec); + } else { + /* Probe local device */ + dev[TYPE_IPMI] = nutscan_scan_ipmi(NULL, NULL, &ipmi_sec); + } #endif /* HAVE_PTHREAD */ } else { upsdebugx(1, "IPMI SCAN: not requested or supported, SKIPPED"); @@ -1066,6 +1284,7 @@ int main(int argc, char *argv[]) upsdebugx(1, "SCANS DONE: free common scanner resources"); nutscan_free(); + free_ip_ranges(); upsdebugx(1, "SCANS DONE: EXIT_SUCCESS"); return EXIT_SUCCESS; From 70338b611e72cff3d4216aed3afa62ece59cdbb4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 20:28:28 +0200 Subject: [PATCH 08/30] tools/nut-scanner/nut-scanner.c: use run*() methods for most of the non-threaded scans too [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 39 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 304c99d799..f40a52f8d2 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -252,6 +252,7 @@ static void * run_usb(void *arg) dev[TYPE_USB] = nutscan_scan_usb(scanopts_ptr); return NULL; } +#endif /* HAVE_PTHREAD */ static void * run_snmp(void * arg) { @@ -387,12 +388,11 @@ static void * run_ipmi(void * arg) static void * run_eaton_serial(void *arg) { - NUT_UNUSED_VARIABLE(arg); + char * arg_serial_ports = (char *)arg; - dev[TYPE_EATON_SERIAL] = nutscan_scan_eaton_serial(serial_ports); + dev[TYPE_EATON_SERIAL] = nutscan_scan_eaton_serial(arg_serial_ports); return NULL; } -#endif /* HAVE_PTHREAD */ static void show_usage(void) { @@ -1047,6 +1047,7 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "USB SCAN: no pthread support, starting nutscan_scan_usb..."); + /* Not calling run_usb() here, as it re-processes the arg */ dev[TYPE_USB] = nutscan_scan_usb(&cli_link_detail_level); #endif /* HAVE_PTHREAD */ } else { @@ -1068,7 +1069,8 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "SNMP SCAN: no pthread support, starting nutscan_scan_snmp..."); - dev[TYPE_SNMP] = nutscan_scan_snmp(start_ip, end_ip, timeout, &snmp_sec); + /* dev[TYPE_SNMP] = nutscan_scan_snmp(start_ip, end_ip, timeout, &snmp_sec); */ + run_snmp(&snmp_sec); #endif /* HAVE_PTHREAD */ } } else { @@ -1091,11 +1093,8 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "XML/HTTP SCAN: no pthread support, starting nutscan_scan_xml_http_range()..."); - if (ip_ranges_count) { - dev[TYPE_XML] = nutscan_scan_xml_http_range(start_ip, end_ip, timeout, &xml_sec); - } else { - /* Probe broadcast */ - dev[TYPE_XML] = nutscan_scan_xml_http_range(NULL, NULL, timeout, &xml_sec); + /* dev[TYPE_XML] = nutscan_scan_xml_http_range(start_ip, end_ip, timeout, &xml_sec); */ + run_xml(&xml_sec); } #endif /* HAVE_PTHREAD */ } else { @@ -1117,7 +1116,8 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "NUT bus (old) SCAN: no pthread support, starting nutscan_scan_nut..."); - dev[TYPE_NUT] = nutscan_scan_nut(start_ip, end_ip, port, timeout); + /*dev[TYPE_NUT] = nutscan_scan_nut(start_ip, end_ip, port, timeout);*/ + run_nut_old(NULL); #endif /* HAVE_PTHREAD */ } } else { @@ -1133,8 +1133,9 @@ int main(int argc, char *argv[]) nutscan_avail_nut_simulation = 0; } #else - upsdebugx(1, "NUT simulation devices SCAN: no pthread support, starting nutscan_scan_nut_simulation..."); - dev[TYPE_NUT_SIMULATION] = nutscan_scan_nut_simulation(timeout); + upsdebugx(1, "NUT simulation devices SCAN: no pthread support, starting nutscan_scan_nut_simulation..."); + /* dev[TYPE_NUT_SIMULATION] = nutscan_scan_nut_simulation(); */ + run_nut_simulation(NULL); #endif /* HAVE_PTHREAD */ } else { upsdebugx(1, "NUT simulation devices SCAN: not requested or supported, SKIPPED"); @@ -1150,7 +1151,8 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "NUT bus (avahi) SCAN: no pthread support, starting nutscan_scan_avahi..."); - dev[TYPE_AVAHI] = nutscan_scan_avahi(timeout); + /* dev[TYPE_AVAHI] = nutscan_scan_avahi(timeout); */ + run_avahi(NULL); #endif /* HAVE_PTHREAD */ } else { upsdebugx(1, "NUT bus (avahi) SCAN: not requested or supported, SKIPPED"); @@ -1171,12 +1173,8 @@ int main(int argc, char *argv[]) } #else upsdebugx(1, "IPMI SCAN: no pthread support, starting nutscan_scan_ipmi..."); - if (ip_ranges_count) { - dev[TYPE_IPMI] = nutscan_scan_ipmi(start_ip, end_ip, &ipmi_sec); - } else { - /* Probe local device */ - dev[TYPE_IPMI] = nutscan_scan_ipmi(NULL, NULL, &ipmi_sec); - } + /* dev[TYPE_IPMI] = nutscan_scan_ipmi(start_ip, end_ip, &ipmi_sec); */ + run_ipmi(&ipmi_sec); #endif /* HAVE_PTHREAD */ } else { upsdebugx(1, "IPMI SCAN: not requested or supported, SKIPPED"); @@ -1193,7 +1191,8 @@ int main(int argc, char *argv[]) /* nutscan_avail_eaton_serial(?) = 0; */ #else upsdebugx(1, "SERIAL SCAN: no pthread support, starting nutscan_scan_eaton_serial..."); - dev[TYPE_EATON_SERIAL] = nutscan_scan_eaton_serial (serial_ports); + /* dev[TYPE_EATON_SERIAL] = nutscan_scan_eaton_serial (serial_ports); */ + run_eaton_serial(serial_ports); #endif /* HAVE_PTHREAD */ } else { upsdebugx(1, "SERIAL SCAN: not requested or supported, SKIPPED"); From d41e204d196450c207002153086a66c79ee95c91 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 20:54:58 +0200 Subject: [PATCH 09/30] tools/nut-scanner/nutscan-device.c: protect nutscan_add_device_to_device() from adding a list to itself Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-device.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/nut-scanner/nutscan-device.c b/tools/nut-scanner/nutscan-device.c index c01e229b98..b5764f11b9 100644 --- a/tools/nut-scanner/nutscan-device.c +++ b/tools/nut-scanner/nutscan-device.c @@ -25,6 +25,7 @@ #include "config.h" /* must be the first header */ #include "nutscan-device.h" +#include "common.h" #include #include #include @@ -175,6 +176,11 @@ nutscan_device_t * nutscan_add_device_to_device(nutscan_device_t * first, nutsca nutscan_device_t * dev1 = NULL; nutscan_device_t * dev2 = NULL; + if (first == second) { + upsdebugx(5, "%s: skip: called to \"add\" same list pointers", __func__); + return first; + } + /* Get end of first device */ if (first != NULL) { dev1 = first; From 8067751a461e4a1c8cdf52f99d3a40a78d10a259 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 20:57:02 +0200 Subject: [PATCH 10/30] tools/nut-scanner/nut-scanner.c: drop unused DEFAULT_TIMEOUT macro [#2244] We actually use DEFAULT_NETWORK_TIMEOUT from common.h same as in clients/upsclient.c Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index f40a52f8d2..5bcc2ec340 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -59,8 +59,6 @@ #include "nut-scan.h" -#define DEFAULT_TIMEOUT 5 - #define ERR_BAD_OPTION (-1) static const char optstring[] = "?ht:T:s:e:E:c:l:u:W:X:w:x:p:b:B:d:L:CUSMOAm:QnNPqIVaD"; From f2029552a1bc2ae87dae8239965a88a0e53c271c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 4 Jul 2024 21:04:44 +0200 Subject: [PATCH 11/30] docs/man/nut-scanner.txt: document that currently different IP range scans do not parallelize together [#2244] See also: https://github.com/networkupstools/nut/issues/2511 Signed-off-by: Jim Klimov --- docs/man/nut-scanner.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index 6df5099b7b..5d35ff0571 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -117,11 +117,19 @@ IP addresses. Normally a new range is specified by a set of one `-s` and one `-e` options following each other (in any order). Lone or consecutive `-s` or `-e` options present on the command line would translate to single-IP queries. Also a `-m` option squashed between two `-s` and `-e` options would be a new -range, turning those two into single-IP queries. +range, turning those two into single-IP queries. This feature does not by +itself recombine "neighboring" addresses into one range, nor even check for +duplicate or overlapping specifications. + Also note that some buses require IP address(es) to scan, and others have a different behavior when exactly no addresses are specified (it is not currently possible to mix the two behaviors in one invocation of the `nut-scanner` tool). ++ +Finally note that currently even if multi-threaded support is available, each +range specification is a separate fan-out of queries constrained by the timeout. +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. *-t* | *--timeout* 'timeout':: Set the network timeout in seconds. Default timeout is 5 seconds. From 511f9b53e55fa144dce7785fa4bd48078ea54d52 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 19:59:55 +0200 Subject: [PATCH 12/30] tools/nut-scanner/Makefile.am, NEWS.adoc: expose more of libcommon* symbols in libnutscan to use them in nut-scanner [#2244] Signed-off-by: Jim Klimov --- NEWS.adoc | 2 ++ tools/nut-scanner/Makefile.am | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 9a5da3d44c..e97760bb7a 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -142,6 +142,8 @@ NOTE: Currently both the long and short names for `libupsclient` should be installed. * newly added support to scan several IP addresses (single or ranges) with the same call, by repeating command-line options. [#2244] + * bumped version of `libnutscan` to 2.5.2, it now includes a few more + methods and symbols from `libcommon`. [#2244] - common code: * introduced a `NUT_DEBUG_SYSLOG` environment variable to tweak activation diff --git a/tools/nut-scanner/Makefile.am b/tools/nut-scanner/Makefile.am index 014cb4e8bc..480c104762 100644 --- a/tools/nut-scanner/Makefile.am +++ b/tools/nut-scanner/Makefile.am @@ -83,7 +83,7 @@ endif HAVE_WINDOWS # object .so names would differ) # # libnutscan version information -libnutscan_la_LDFLAGS += -version-info 2:5:1 +libnutscan_la_LDFLAGS += -version-info 2:5:2 # libnutscan exported symbols regex # WARNING: Since the library includes parts of libcommon (as much as needed @@ -94,7 +94,7 @@ libnutscan_la_LDFLAGS += -version-info 2:5:1 # 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|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_upsdebugx|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 From d5c3304ec87607891133f41a031060f024c7e192 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:52:51 +0200 Subject: [PATCH 13/30] tools/nut-scanner/nut-scanner.c: use xcalloc() instead of hackarounds [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 5bcc2ec340..7608938957 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -142,12 +142,7 @@ static size_t add_ip_range(char * start_ip, char * end_ip) end_ip = start_ip; } - /* no xcalloc() nor fatalx() linked here, oh well */ - p = calloc(1, sizeof(ip_range_t)); - if (!p) { - upsdebugx(0, "%s: failed to allocate some memory", __func__); - exit(EXIT_FAILURE); - } + p = xcalloc(1, sizeof(ip_range_t)); p->start_ip = start_ip; p->end_ip = end_ip; From 18998e011c438ddde3f2da9bd2265cba29091c37 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 20:38:50 +0200 Subject: [PATCH 14/30] tools/nut-scanner/nutscan-ip.c: nutscan_cidr_to_ip(): update comments and logged reports to help troubleshooting [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-ip.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tools/nut-scanner/nutscan-ip.c b/tools/nut-scanner/nutscan-ip.c index 9b173d56c5..64b47e5548 100644 --- a/tools/nut-scanner/nutscan-ip.c +++ b/tools/nut-scanner/nutscan-ip.c @@ -267,11 +267,10 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) char * mask; char * saveptr = NULL; nutscan_ip_iter_t ip; - /* TODO: There can be up to 128-bit CIDR mask for IPv6... */ - int mask_val; - int mask_byte; int ret; - uint32_t mask_bit; /* 32-bit IPv4 address bitmask */ + int mask_val; /* mask length in bits */ + int mask_byte; /* number of byte in 128-bit IPv6 address array (uint8_t[16]) for netmask */ + uint32_t mask_bit; /* 32-bit IPv4 address bitmask value */ char host[SMALLBUF]; struct addrinfo hints; struct addrinfo *res; @@ -305,7 +304,7 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) /* TODO: check if mask is also an IP address or a bit count */ mask_val = atoi(mask); - upsdebugx(5, "%s: parsed mask value %d", + upsdebugx(5, "%s: parsed mask into numeric value %d", __func__, mask_val); /* NOTE: Sanity-wise, some larger number also makes sense @@ -333,15 +332,15 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) if ((ret = getaddrinfo(first_ip, NULL, &hints, &res)) != 0) { /* EAI_ADDRFAMILY? */ - upsdebugx(5, "%s: getaddrinfo() failed for AF_INET (IPv4): %d", - __func__, ret); + upsdebugx(5, "%s: getaddrinfo() failed for AF_INET (IPv4, will retry with IPv6): %d: %s", + __func__, ret, gai_strerror(ret)); /* Try IPv6 detection */ ip.type = IPv6; hints.ai_family = AF_INET6; if ((ret = getaddrinfo(first_ip, NULL, &hints, &res)) != 0) { - upsdebugx(5, "%s: getaddrinfo() failed for AF_INET6 (IPv6): %d", - __func__, ret); + upsdebugx(5, "%s: getaddrinfo() failed for AF_INET6 (IPv6): %d: %s", + __func__, ret, gai_strerror(ret)); free(first_ip); return 0; } @@ -406,7 +405,7 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) free(first_ip); return 1; } - else { + else { /* ip.type == IPv6 */ if (getaddrinfo(first_ip, NULL, &hints, &res) != 0) { return 0; } From a863a1516a54c74843b68b4b2f06eefa443f20e3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:02:16 +0200 Subject: [PATCH 15/30] tools/nut-scanner/nutscan-ip.c: refactor to name IPv4 vars with a "4" in the name, and use static structs and memcpy() to actually fix alignment warnings [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-ip.c | 117 ++++++++++----------------------- 1 file changed, 35 insertions(+), 82 deletions(-) diff --git a/tools/nut-scanner/nutscan-ip.c b/tools/nut-scanner/nutscan-ip.c index 64b47e5548..2bb6976dd4 100644 --- a/tools/nut-scanner/nutscan-ip.c +++ b/tools/nut-scanner/nutscan-ip.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 - EATON + * Copyright (C) 2022 - 2024 Jim Klimov * * 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 @@ -65,12 +66,12 @@ static void invert_IPv6(struct in6_addr * addr1, struct in6_addr * addr2) static int ntop(struct in_addr * ip, char * host, GETNAMEINFO_TYPE_ARG46 host_size) { - struct sockaddr_in in; - memset(&in, 0, sizeof(struct sockaddr_in)); - in.sin_addr = *ip; - in.sin_family = AF_INET; + struct sockaddr_in in4; + memset(&in4, 0, sizeof(struct sockaddr_in)); + in4.sin_addr = *ip; + in4.sin_family = AF_INET; return getnameinfo( - (struct sockaddr *)&in, + (struct sockaddr *)&in4, sizeof(struct sockaddr_in), host, host_size, NULL, 0, NI_NUMERICHOST); } @@ -94,10 +95,17 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const int i; struct addrinfo hints; struct addrinfo *res; - struct sockaddr_in * s_in; - struct sockaddr_in6 * s_in6; char host[SMALLBUF]; + /* Ensure proper alignment of IPvN structure fields: + * we receive a pointer to res from getaddrinfo() et al, + * so have no control about alignment of its further data. + * Make a copy of the bytes into an object allocated + * whichever way the system likes it. + */ + struct sockaddr_in s_in4buf, *s_in4 = &s_in4buf; + struct sockaddr_in6 s_in6buf, *s_in6 = &s_in6buf; + if (startIP == NULL) { return NULL; } @@ -120,31 +128,13 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const return NULL; } -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in6 = (struct sockaddr_in6 *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(s_in6, res->ai_addr, sizeof(struct sockaddr_in6)); memcpy(&ip->start6, &s_in6->sin6_addr, sizeof(struct in6_addr)); freeaddrinfo(res); } else { -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in = (struct sockaddr_in *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif - ip->start = s_in->sin_addr; + memcpy(s_in4, res->ai_addr, sizeof(struct sockaddr_in)); + ip->start = s_in4->sin_addr; freeaddrinfo(res); } @@ -156,17 +146,8 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const return NULL; } -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in = (struct sockaddr_in *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif - ip->stop = s_in->sin_addr; + memcpy(s_in4, res->ai_addr, sizeof(struct sockaddr_in)); + ip->stop = s_in4->sin_addr; freeaddrinfo(res); } else { @@ -175,16 +156,7 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const fprintf(stderr, "Invalid address : %s\n", stopIP); return NULL; } -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in6 = (struct sockaddr_in6 *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(s_in6, res->ai_addr, sizeof(struct sockaddr_in6)); memcpy(&ip->stop6, &s_in6->sin6_addr, sizeof(struct in6_addr)); freeaddrinfo(res); } @@ -274,8 +246,15 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) char host[SMALLBUF]; struct addrinfo hints; struct addrinfo *res; - struct sockaddr_in * s_in; - struct sockaddr_in6 * s_in6; + + /* Ensure proper alignment of IPvN structure fields: + * we receive a pointer to res from getaddrinfo() et al, + * so have no control about alignment of its further data. + * Make a copy of the bytes into an object allocated + * whichever way the system likes it. + */ + struct sockaddr_in s_in4buf, *s_in4 = &s_in4buf; + struct sockaddr_in6 s_in6buf, *s_in6 = &s_in6buf; *start_ip = NULL; *stop_ip = NULL; @@ -345,31 +324,13 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) return 0; } -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in6 = (struct sockaddr_in6 *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(s_in6, res->ai_addr, sizeof(struct sockaddr_in6)); memcpy(&ip.start6, &s_in6->sin6_addr, sizeof(struct in6_addr)); freeaddrinfo(res); } else { -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in = (struct sockaddr_in *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif - ip.start = s_in->sin_addr; + memcpy(s_in4, res->ai_addr, sizeof(struct sockaddr_in)); + ip.start = s_in4->sin_addr; freeaddrinfo(res); } @@ -409,16 +370,8 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip) if (getaddrinfo(first_ip, NULL, &hints, &res) != 0) { return 0; } -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - /* Note: we receive a pointer to res above, so have - * no control about alignment of its further data */ - s_in6 = (struct sockaddr_in6 *)res->ai_addr; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + + memcpy(s_in6, res->ai_addr, sizeof(struct sockaddr_in6)); memcpy(&ip.stop6, &s_in6->sin6_addr, sizeof(struct in6_addr)); freeaddrinfo(res); From c8f4fe95f65b354480f5e7f203044d1aa6734be9 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:55:35 +0200 Subject: [PATCH 16/30] tools/nut-scanner/nut-scanner.c: quieter debug about add_ip_range() in-/semi-valid inputs [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 7608938957..cdaecd411d 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -127,17 +127,17 @@ static size_t add_ip_range(char * start_ip, char * end_ip) ip_range_t *p; if (!start_ip && !end_ip) { - upsdebugx(1, "%s: skip, no addresses were provided", __func__); + upsdebugx(5, "%s: skip, no addresses were provided", __func__); return ip_ranges_count; } if (start_ip == NULL) { - upsdebugx(1, "%s: only end address was provided, setting start to same: %s", + upsdebugx(5, "%s: only end address was provided, setting start to same: %s", __func__, end_ip); start_ip = end_ip; } if (end_ip == NULL) { - upsdebugx(1, "%s: only start address was provided, setting end to same: %s", + upsdebugx(5, "%s: only start address was provided, setting end to same: %s", __func__, start_ip); end_ip = start_ip; } From 7b26627a06fea73896f74d1e2d125e2cada8f2c7 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:06:20 +0200 Subject: [PATCH 17/30] drivers/libshut.c: libshut_open(): use static structs and memcpy() to actually fix alignment warnings Signed-off-by: Jim Klimov --- drivers/libshut.c | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/drivers/libshut.c b/drivers/libshut.c index 83b0cbe7c4..8d8991613d 100644 --- a/drivers/libshut.c +++ b/drivers/libshut.c @@ -376,15 +376,10 @@ static int libshut_open( usb_ctrl_charbuf rdbuf, usb_ctrl_charbufsize rdlen)) { int ret, res; - /* Below we cast this buffer as sometimes containing entried of type - * "struct device_descriptor_s" or "struct my_hid_descriptor". - * Currently both of these are sized "2", and I don't see a way - * to require a "max()" of such sizes to align for generally. - */ usb_ctrl_char buf[20] __attribute__((aligned(4))); char string[MAX_STRING_SIZE]; - struct my_hid_descriptor *desc; - struct device_descriptor_s *dev_descriptor; + struct my_hid_descriptor desc_buf, *desc = &desc_buf; + struct device_descriptor_s dev_descriptor_buf, *dev_descriptor = &dev_descriptor_buf; /* report descriptor */ usb_ctrl_char rdbuf[MAX_REPORT_SIZE]; @@ -470,21 +465,7 @@ static int libshut_open( } /* Get DEVICE descriptor */ -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wcast-align" -#endif - dev_descriptor = (struct device_descriptor_s *)buf; -#ifdef __clang__ -# pragma clang diagnostic pop -#endif -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(dev_descriptor, buf, sizeof(struct device_descriptor_s)); res = shut_get_descriptor(*arg_upsfd, USB_DT_DEVICE, 0, buf, USB_DT_DEVICE_SIZE); /* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, (USB_DT_DEVICE << 8) + 0, 0, buf, 0x9, SHUT_TIMEOUT); */ @@ -587,21 +568,7 @@ static int libshut_open( } /* Get HID descriptor */ -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wcast-align" -#endif - desc = (struct my_hid_descriptor *)buf; -#ifdef __clang__ -# pragma clang diagnostic pop -#endif -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(desc, buf, sizeof(struct my_hid_descriptor)); res = shut_get_descriptor(*arg_upsfd, USB_DT_HID, hid_desc_index, buf, 0x9); /* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, (USB_DT_HID << 8) + 0, 0, buf, 0x9, SHUT_TIMEOUT); */ From 45979267a7f628ae2d9deb76befeea7c48424554 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 05:26:39 +0200 Subject: [PATCH 18/30] tools/nut-scanner/nut-scanner.c et al: prepare for `-m auto` mode with detection of configured network interfaces Signed-off-by: Jim Klimov --- NEWS.adoc | 4 +- docs/man/nut-scanner.txt | 3 + tools/nut-scanner/nut-scanner.c | 110 ++++++++++++++++++++++++++++---- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index e97760bb7a..472d0e1ef0 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -141,7 +141,9 @@ https://github.com/networkupstools/nut/milestone/11 NOTE: Currently both the long and short names for `libupsclient` should be installed. * newly added support to scan several IP addresses (single or ranges) - with the same call, by repeating command-line options. [#2244] + with the same call, by repeating command-line options; also `-m auto` + can be specified (once) to select IP address ranges of configured + local network interfaces. [#2244] * bumped version of `libnutscan` to 2.5.2, it now includes a few more methods and symbols from `libcommon`. [#2244] diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index 5d35ff0571..b60b1746b7 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -146,6 +146,9 @@ less than 'start IP', both parameters are internally permuted. *-m* | *--mask_cidr* 'IP address/mask':: Set a range of IP addresses by using CIDR notation. ++ +A special form `-m auto` allows `nut-scanner` to detect local IP address(es) +and scan corresponding subnet(s) on supported platforms. NUT DEVICE OPTION ----------------- diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index cdaecd411d..985571f472 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -36,6 +36,18 @@ #include #include +/* Headers related to getifaddrs() for `-m auto` on different platforms */ +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include + #ifdef HAVE_PTHREAD # include # ifdef HAVE_SEMAPHORE @@ -443,6 +455,7 @@ static void show_usage(void) printf(" -s, --start_ip : First IP address to scan.\n"); printf(" -e, --end_ip : Last IP address to scan.\n"); printf(" -m, --mask_cidr : Give a range of IP using CIDR notation.\n"); + printf(" -m, --mask_cidr auto: Detect local IP address(es) and scan corresponding subnet(s).\n"); printf("NOTE: IP address range specifications can be repeated, to scan several.\n"); printf("Specifying a single first or last address before starting another range\n"); printf("leads to scanning just that one address as the range.\n"); @@ -575,6 +588,7 @@ int main(int argc, char *argv[]) nutscan_xml_t xml_sec; int opt_ret; char *start_ip = NULL, *end_ip = NULL; + int auto_nets = 0; int allow_all = 0; int allow_usb = 0; int allow_snmp = 0; @@ -712,22 +726,94 @@ int main(int argc, char *argv[]) allow_eaton_serial = 1; break; case 'm': - if (start_ip || end_ip) { - /* Save whatever we have, either - * this one address or an earlier - * known range with its start or end */ + if (!strcmp(optarg, "auto")) { + if (auto_nets) { + fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); + } else { + /* Inspired by https://stackoverflow.com/a/63789267/4715872 */ + struct ifaddrs *ifap; + + if (getifaddrs(&ifap) < 0) { + fprintf(stderr, + "Failed to getifaddrs() for connected subnet scan: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } else { + struct ifaddrs *ifa; + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr) { + if (ifa->ifa_addr->sa_family == AF_INET6) { + char addr[INET6_ADDRSTRLEN]; + char mask[INET6_ADDRSTRLEN]; + int masklen = 0; + uint8_t i, j; + struct sockaddr_in6 *sm = (struct sockaddr_in6 *)ifa->ifa_netmask; + + for (j = 0; j < 16; j++) { + i = sm->sin6_addr.s6_addr[j]; + while (i) { + masklen += i & 1; + i >>= 1; + } + } + + getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addr, sizeof(addr), NULL, 0, NI_NUMERICHOST); + getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in6), mask, sizeof(mask), NULL, 0, NI_NUMERICHOST); + printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08x", ifa->ifa_name, addr, mask, masklen, ifa->ifa_flags); + } else if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr; + struct sockaddr_in *sm = (struct sockaddr_in *)ifa->ifa_netmask; + char *addr = inet_ntoa(sa->sin_addr); + char *mask = inet_ntoa(sm->sin_addr); + int masklen = 0; + in_addr_t i = sm->sin_addr.s_addr; + + while (i) { + masklen += i & 1; + i >>= 1; + } + printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08x", ifa->ifa_name, addr, mask, masklen, ifa->ifa_flags); +/* + } else { + printf("Addr family: %" PRIuMAX, (intmax_t)ifa->ifa_addr->sa_family); +*/ + } + + if (ifa->ifa_addr->sa_family == AF_INET6 || ifa->ifa_addr->sa_family == AF_INET) { + if (ifa->ifa_flags & IFF_LOOPBACK) + printf(" IFF_LOOPBACK"); + if (ifa->ifa_flags & IFF_UP) + printf(" IFF_UP"); + if (ifa->ifa_flags & IFF_RUNNING) + printf(" IFF_RUNNING"); + if (ifa->ifa_flags & IFF_BROADCAST) + printf(" IFF_BROADCAST(is assigned)"); + printf("\n"); + } + } + } + freeifaddrs(ifap); + } + auto_nets = 1; + } + } else { + if (start_ip || end_ip) { + /* Save whatever we have, either + * this one address or an earlier + * known range with its start or end */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } + + upsdebugx(5, "Processing CIDR net/mask: %s", optarg); + nutscan_cidr_to_ip(optarg, &start_ip, &end_ip); + upsdebugx(5, "Extracted IP address range from CIDR net/mask: %s => %s", start_ip, end_ip); + add_ip_range(start_ip, end_ip); start_ip = NULL; end_ip = NULL; } - - upsdebugx(5, "Processing CIDR net/mask: %s", optarg); - nutscan_cidr_to_ip(optarg, &start_ip, &end_ip); - upsdebugx(5, "Extracted IP address range from CIDR net/mask: %s => %s", start_ip, end_ip); - - add_ip_range(start_ip, end_ip); - start_ip = NULL; - end_ip = NULL; break; case 'D': /* nothing to do, here */ From e8f93951509b35c812b1b389e186afaf30ed34c5 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 19:25:24 +0200 Subject: [PATCH 19/30] tools/nut-scanner/nut-scanner.c: upscale sockaddr flags to uintmax_t for debug printouts to be predictable on different platforms [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 985571f472..cc7cc4483b 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -759,7 +759,7 @@ int main(int argc, char *argv[]) getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addr, sizeof(addr), NULL, 0, NI_NUMERICHOST); getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in6), mask, sizeof(mask), NULL, 0, NI_NUMERICHOST); - printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08x", ifa->ifa_name, addr, mask, masklen, ifa->ifa_flags); + printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); } else if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr; struct sockaddr_in *sm = (struct sockaddr_in *)ifa->ifa_netmask; @@ -772,7 +772,7 @@ int main(int argc, char *argv[]) masklen += i & 1; i >>= 1; } - printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08x", ifa->ifa_name, addr, mask, masklen, ifa->ifa_flags); + printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); /* } else { printf("Addr family: %" PRIuMAX, (intmax_t)ifa->ifa_addr->sa_family); From b77f5815917eb6215f5dfd3c80cd8119247a3699 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 19:29:01 +0200 Subject: [PATCH 20/30] tools/nut-scanner/nut-scanner.c: retain getifaddrs() findings printout as a real upsdebugx() trail [#2244] Originally I intended to use these to check that the address parsing code works, and drop the printf() of these messages. But if I collect them into a string and upsdebug() it - why not, can help troubleshooting in real life later, too. Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index cc7cc4483b..c9be2b679b 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -740,8 +740,12 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } else { struct ifaddrs *ifa; + char msg[LARGEBUF]; + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr) { + memset(msg, 0, sizeof(msg)); + if (ifa->ifa_addr->sa_family == AF_INET6) { char addr[INET6_ADDRSTRLEN]; char mask[INET6_ADDRSTRLEN]; @@ -759,7 +763,7 @@ int main(int argc, char *argv[]) getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addr, sizeof(addr), NULL, 0, NI_NUMERICHOST); getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in6), mask, sizeof(mask), NULL, 0, NI_NUMERICHOST); - printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); + snprintf(msg, sizeof(msg), "Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); } else if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr; struct sockaddr_in *sm = (struct sockaddr_in *)ifa->ifa_netmask; @@ -772,24 +776,25 @@ int main(int argc, char *argv[]) masklen += i & 1; i >>= 1; } - printf("Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); + snprintf(msg, sizeof(msg), "Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); /* } else { - printf("Addr family: %" PRIuMAX, (intmax_t)ifa->ifa_addr->sa_family); + snprintf(msg, sizeof(msg), "Addr family: %" PRIuMAX, (intmax_t)ifa->ifa_addr->sa_family); */ } if (ifa->ifa_addr->sa_family == AF_INET6 || ifa->ifa_addr->sa_family == AF_INET) { if (ifa->ifa_flags & IFF_LOOPBACK) - printf(" IFF_LOOPBACK"); + snprintfcat(msg, sizeof(msg), " IFF_LOOPBACK"); if (ifa->ifa_flags & IFF_UP) - printf(" IFF_UP"); + snprintfcat(msg, sizeof(msg), " IFF_UP"); if (ifa->ifa_flags & IFF_RUNNING) - printf(" IFF_RUNNING"); - if (ifa->ifa_flags & IFF_BROADCAST) - printf(" IFF_BROADCAST(is assigned)"); - printf("\n"); - } + snprintfcat(msg, sizeof(msg), " IFF_RUNNING"); + if (ifa->ifa_flags & IFF_BROADCAST) + snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); + + upsdebugx(1, "Discovering getifaddrs(): %s", msg); + } /* else AF_UNIX or a dozen other types we do not care about here */ } } freeifaddrs(ifap); From d335387c981f283dd7128559e1ed55142da96350 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:52:58 +0200 Subject: [PATCH 21/30] tools/nut-scanner/nut-scanner.c: use fatalx() instead of hackarounds [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index c9be2b679b..91f2e63a33 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -734,10 +734,9 @@ int main(int argc, char *argv[]) struct ifaddrs *ifap; if (getifaddrs(&ifap) < 0) { - fprintf(stderr, + fatalx(EXIT_FAILURE, "Failed to getifaddrs() for connected subnet scan: %s\n", strerror(errno)); - exit(EXIT_FAILURE); } else { struct ifaddrs *ifa; char msg[LARGEBUF]; From 01168472d0b73e544849911c21e940055313eb69 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 20:01:39 +0200 Subject: [PATCH 22/30] tools/nut-scanner/nut-scanner.c: use pre-allocated struct sockaddr_in{,6} so it is aligned how-ever the platform likes [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 91f2e63a33..487df11873 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -750,10 +750,13 @@ int main(int argc, char *argv[]) char mask[INET6_ADDRSTRLEN]; int masklen = 0; uint8_t i, j; - struct sockaddr_in6 *sm = (struct sockaddr_in6 *)ifa->ifa_netmask; + + /* Ensure proper alignment */ + struct sockaddr_in6 sm; + memcpy (&sm, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); for (j = 0; j < 16; j++) { - i = sm->sin6_addr.s6_addr[j]; + i = sm.sin6_addr.s6_addr[j]; while (i) { masklen += i & 1; i >>= 1; @@ -764,13 +767,18 @@ int main(int argc, char *argv[]) getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in6), mask, sizeof(mask), NULL, 0, NI_NUMERICHOST); snprintf(msg, sizeof(msg), "Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); } else if (ifa->ifa_addr->sa_family == AF_INET) { - struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr; - struct sockaddr_in *sm = (struct sockaddr_in *)ifa->ifa_netmask; - char *addr = inet_ntoa(sa->sin_addr); - char *mask = inet_ntoa(sm->sin_addr); + char *addr, *mask; int masklen = 0; - in_addr_t i = sm->sin_addr.s_addr; + in_addr_t i; + + /* Ensure proper alignment */ + struct sockaddr_in sa, sm; + memcpy (&sa, ifa->ifa_addr, sizeof(struct sockaddr_in)); + memcpy (&sm, ifa->ifa_netmask, sizeof(struct sockaddr_in)); + addr = inet_ntoa(sa.sin_addr); + mask = inet_ntoa(sm.sin_addr); + i = sm.sin_addr.s_addr; while (i) { masklen += i & 1; i >>= 1; From 4d2672b97609bf3c0484faf06c83332c7cbae96d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:55:51 +0200 Subject: [PATCH 23/30] tools/nut-scanner/nut-scanner.c: quieter debug about "Discovering getifaddrs()" [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 487df11873..1e9c979438 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -800,7 +800,7 @@ int main(int argc, char *argv[]) if (ifa->ifa_flags & IFF_BROADCAST) snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); - upsdebugx(1, "Discovering getifaddrs(): %s", msg); + upsdebugx(5, "Discovering getifaddrs(): %s", msg); } /* else AF_UNIX or a dozen other types we do not care about here */ } } From de50f0cdc192ed3b5fc5516ecdd2bc29be84e4cb Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 20:13:06 +0200 Subject: [PATCH 24/30] tools/nut-scanner/nut-scanner.c: flush existing start_ip/end_ip before handling whatever value of `-m` option [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 1e9c979438..1308901e0d 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -726,6 +726,15 @@ int main(int argc, char *argv[]) allow_eaton_serial = 1; break; case 'm': + if (start_ip || end_ip) { + /* Save whatever we have, either + * this one address or an earlier + * known range with its start or end */ + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } + if (!strcmp(optarg, "auto")) { if (auto_nets) { fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); @@ -809,15 +818,7 @@ int main(int argc, char *argv[]) auto_nets = 1; } } else { - if (start_ip || end_ip) { - /* Save whatever we have, either - * this one address or an earlier - * known range with its start or end */ - add_ip_range(start_ip, end_ip); - start_ip = NULL; - end_ip = NULL; - } - + /* not `-m auto` => is `-m cidr` */ upsdebugx(5, "Processing CIDR net/mask: %s", optarg); nutscan_cidr_to_ip(optarg, &start_ip, &end_ip); upsdebugx(5, "Extracted IP address range from CIDR net/mask: %s => %s", start_ip, end_ip); From 52157e449805068c2e8f5e7267cd457c7d01f23c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 20:27:40 +0200 Subject: [PATCH 25/30] tools/nut-scanner/nut-scanner.c: `-m auto`: implement actually converting discovered subnets into CIDR and adding to IP ranges for scanning [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 37 ++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 1308901e0d..fc0ba13cf9 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -749,21 +749,25 @@ int main(int argc, char *argv[]) } else { struct ifaddrs *ifa; char msg[LARGEBUF]; + char addr[LARGEBUF]; + char mask[LARGEBUF]; + int masklen = 0; for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr) { memset(msg, 0, sizeof(msg)); + memset(addr, 0, sizeof(addr)); + memset(mask, 0, sizeof(mask)); + masklen = -1; if (ifa->ifa_addr->sa_family == AF_INET6) { - char addr[INET6_ADDRSTRLEN]; - char mask[INET6_ADDRSTRLEN]; - int masklen = 0; uint8_t i, j; /* Ensure proper alignment */ struct sockaddr_in6 sm; memcpy (&sm, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); + masklen = 0; for (j = 0; j < 16; j++) { i = sm.sin6_addr.s6_addr[j]; while (i) { @@ -776,18 +780,17 @@ int main(int argc, char *argv[]) getnameinfo(ifa->ifa_netmask, sizeof(struct sockaddr_in6), mask, sizeof(mask), NULL, 0, NI_NUMERICHOST); snprintf(msg, sizeof(msg), "Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, masklen, (uintmax_t)ifa->ifa_flags); } else if (ifa->ifa_addr->sa_family == AF_INET) { - char *addr, *mask; - int masklen = 0; in_addr_t i; /* Ensure proper alignment */ struct sockaddr_in sa, sm; memcpy (&sa, ifa->ifa_addr, sizeof(struct sockaddr_in)); memcpy (&sm, ifa->ifa_netmask, sizeof(struct sockaddr_in)); - addr = inet_ntoa(sa.sin_addr); - mask = inet_ntoa(sm.sin_addr); + snprintf(addr, sizeof(addr), "%s", inet_ntoa(sa.sin_addr)); + snprintf(mask, sizeof(mask), "%s", inet_ntoa(sm.sin_addr)); i = sm.sin_addr.s_addr; + masklen = 0; while (i) { masklen += i & 1; i >>= 1; @@ -810,6 +813,26 @@ int main(int argc, char *argv[]) snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); upsdebugx(5, "Discovering getifaddrs(): %s", msg); + + if (!(ifa->ifa_flags & IFF_LOOPBACK) + && (ifa->ifa_flags & IFF_UP) + && (ifa->ifa_flags & IFF_RUNNING) + && (ifa->ifa_flags & IFF_BROADCAST) + ) { + char cidr[LARGEBUF]; + + if (snprintf(cidr, sizeof(cidr), "%s/%i", addr, masklen) < 0) { + fatalx(EXIT_FAILURE, "Could not construct a CIDR string from discovered address/mask"); + } + + upsdebugx(5, "Processing CIDR net/mask: %s", cidr); + nutscan_cidr_to_ip(cidr, &start_ip, &end_ip); + upsdebugx(5, "Extracted IP address range from CIDR net/mask: %s => %s", start_ip, end_ip); + + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } } /* else AF_UNIX or a dozen other types we do not care about here */ } } From 6f5334508d153c9a2f7c1209bc1f193be4b945cf Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 21:44:33 +0200 Subject: [PATCH 26/30] tools/nut-scanner/nut-scanner.c: comment some TODOs [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index fc0ba13cf9..f94bcc6f59 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -739,6 +739,7 @@ int main(int argc, char *argv[]) if (auto_nets) { fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); } else { + /* TODO: Refactor into a method, reduce indentation? */ /* Inspired by https://stackoverflow.com/a/63789267/4715872 */ struct ifaddrs *ifap; @@ -814,6 +815,11 @@ int main(int argc, char *argv[]) upsdebugx(5, "Discovering getifaddrs(): %s", msg); + /* TODO: also rule out "link-local" address ranges + * so we do not issue billions of worthless scans. + * FIXME: IPv6 may also be a problem, see + * https://github.com/networkupstools/nut/issues/2512 + */ if (!(ifa->ifa_flags & IFF_LOOPBACK) && (ifa->ifa_flags & IFF_UP) && (ifa->ifa_flags & IFF_RUNNING) From e9550ab9f43bc87f2e8b19a2d5ac494ff46613f9 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Fri, 5 Jul 2024 22:18:47 +0200 Subject: [PATCH 27/30] tools/nut-scanner/nut-scanner.c: `-m auto` discovery: use shorter addr/mask buffers [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index f94bcc6f59..4074f13ae5 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -750,8 +750,11 @@ int main(int argc, char *argv[]) } else { struct ifaddrs *ifa; char msg[LARGEBUF]; - char addr[LARGEBUF]; - char mask[LARGEBUF]; + /* Note: INET6_ADDRSTRLEN is large enough for IPv4 too, + * and is smaller than LARGEBUF to avoid snprintf() + * warnings that the result might not fit. */ + char addr[INET6_ADDRSTRLEN]; + char mask[INET6_ADDRSTRLEN]; int masklen = 0; for (ifa = ifap; ifa; ifa = ifa->ifa_next) { From 92bbcec0e1f819e6caed5e476b858cdf9f5cddfa Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 6 Jul 2024 03:18:23 +0200 Subject: [PATCH 28/30] tools/nut-scanner/nut-scanner.c: separate POSIX headers from WIN32 ones (for networking "-m auto" support) [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 4074f13ae5..d8d4a46929 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -41,12 +41,23 @@ # include #endif #include -#include -#include -#include -#include -#include -#include +#ifndef WIN32 +# include +# include +# include +# include +# include +# include +#else +/* Those 2 files for support of getaddrinfo, getnameinfo and freeaddrinfo + on Windows 2000 and older versions */ +# include +# include +# ifndef AI_NUMERICSERV +# define AI_NUMERICSERV NI_NUMERICSERV +# endif +# include "wincompat.h" +#endif #ifdef HAVE_PTHREAD # include From 89bf574b772af9a4c507d00ad6038a5de1ee8b79 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 6 Jul 2024 03:21:56 +0200 Subject: [PATCH 29/30] tools/nut-scanner/nut-scanner.c, NEWS.adoc: neuter networking "-m auto" support for WIN32 (needs different implementation) [#2244] Signed-off-by: Jim Klimov --- NEWS.adoc | 2 +- tools/nut-scanner/nut-scanner.c | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS.adoc b/NEWS.adoc index 472d0e1ef0..97a359d7b2 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -143,7 +143,7 @@ installed. * newly added support to scan several IP addresses (single or ranges) with the same call, by repeating command-line options; also `-m auto` can be specified (once) to select IP address ranges of configured - local network interfaces. [#2244] + local network interfaces (currently not implemented for WIN32). [#2244] * bumped version of `libnutscan` to 2.5.2, it now includes a few more methods and symbols from `libcommon`. [#2244] diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index d8d4a46929..3bcef3b779 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -51,12 +51,14 @@ #else /* Those 2 files for support of getaddrinfo, getnameinfo and freeaddrinfo on Windows 2000 and older versions */ +/* // TODO: complete "-m auto" support # include # include # ifndef AI_NUMERICSERV # define AI_NUMERICSERV NI_NUMERICSERV # endif # include "wincompat.h" +*/ #endif #ifdef HAVE_PTHREAD @@ -467,6 +469,9 @@ static void show_usage(void) printf(" -e, --end_ip : Last IP address to scan.\n"); printf(" -m, --mask_cidr : Give a range of IP using CIDR notation.\n"); printf(" -m, --mask_cidr auto: Detect local IP address(es) and scan corresponding subnet(s).\n"); +#ifdef WIN32 + printf(" (Currently not implemented for this platform)\n"); +#endif printf("NOTE: IP address range specifications can be repeated, to scan several.\n"); printf("Specifying a single first or last address before starting another range\n"); printf("leads to scanning just that one address as the range.\n"); @@ -750,6 +755,7 @@ int main(int argc, char *argv[]) if (auto_nets) { fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); } else { +#ifndef WIN32 /* TODO: Refactor into a method, reduce indentation? */ /* Inspired by https://stackoverflow.com/a/63789267/4715872 */ struct ifaddrs *ifap; @@ -858,6 +864,10 @@ int main(int argc, char *argv[]) } freeifaddrs(ifap); } +#else /* WIN32 */ + /* https://stackoverflow.com/questions/122208/how-can-i-get-the-ip-address-of-a-local-computer */ + upsdebugx(0, "Local address detection feature is not completed on Windows, please call back later"); +#endif auto_nets = 1; } } else { From 822dae0e87c36af304da046cfa2e7ba0682b2a83 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sat, 6 Jul 2024 03:37:52 +0200 Subject: [PATCH 30/30] tools/nut-scanner/nut-scanner.c: implement "-m auto{4,6}" modes to limit selection of ranges to IPv4/IPv6 only [#2244] Signed-off-by: Jim Klimov --- NEWS.adoc | 9 +++++---- docs/man/nut-scanner.txt | 4 +++- tools/nut-scanner/nut-scanner.c | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 97a359d7b2..231fabb543 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -141,11 +141,12 @@ https://github.com/networkupstools/nut/milestone/11 NOTE: Currently both the long and short names for `libupsclient` should be installed. * newly added support to scan several IP addresses (single or ranges) - with the same call, by repeating command-line options; also `-m auto` - can be specified (once) to select IP address ranges of configured - local network interfaces (currently not implemented for WIN32). [#2244] + 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 + configured local network interfaces (currently not implemented for WIN32). + [issue #2244, PR #2509, PR #2513] * bumped version of `libnutscan` to 2.5.2, it now includes a few more - methods and symbols from `libcommon`. [#2244] + methods and symbols from `libcommon`. [issue #2244, PR #2509] - common code: * introduced a `NUT_DEBUG_SYSLOG` environment variable to tweak activation diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index b60b1746b7..fe66ff113e 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -148,7 +148,9 @@ less than 'start IP', both parameters are internally permuted. Set a range of IP addresses by using CIDR notation. + A special form `-m auto` allows `nut-scanner` to detect local IP address(es) -and scan corresponding subnet(s) on supported platforms. +and scan corresponding subnet(s) on supported platforms, and `-m auto4` or +`-m auto6` limits the selected addresses to IPv4 and IPv6 respectively. Only +the first "auto*" request would be honoured, others ignored with a warning. NUT DEVICE OPTION ----------------- diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 3bcef3b779..0c3071d146 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -472,6 +472,8 @@ static void show_usage(void) #ifdef WIN32 printf(" (Currently not implemented for this platform)\n"); #endif + printf(" -m, --mask_cidr auto4/auto6: Likewise, limiting to IPv4 or IPv6 interfaces.\n"); + printf(" Only the first auto* request would be honoured.\n"); printf("NOTE: IP address range specifications can be repeated, to scan several.\n"); printf("Specifying a single first or last address before starting another range\n"); printf("leads to scanning just that one address as the range.\n"); @@ -751,7 +753,7 @@ int main(int argc, char *argv[]) end_ip = NULL; } - if (!strcmp(optarg, "auto")) { + if (!strcmp(optarg, "auto") || !strcmp(optarg, "auto4") || !strcmp(optarg, "auto6")) { if (auto_nets) { fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); } else { @@ -759,7 +761,17 @@ int main(int argc, char *argv[]) /* TODO: Refactor into a method, reduce indentation? */ /* Inspired by https://stackoverflow.com/a/63789267/4715872 */ struct ifaddrs *ifap; +#endif + + if (!strcmp(optarg, "auto")) { + auto_nets = 46; + } else if (!strcmp(optarg, "auto4")) { + auto_nets = 4; + } else if (!strcmp(optarg, "auto6")) { + auto_nets = 6; + } +#ifndef WIN32 if (getifaddrs(&ifap) < 0) { fatalx(EXIT_FAILURE, "Failed to getifaddrs() for connected subnet scan: %s\n", @@ -844,6 +856,9 @@ int main(int argc, char *argv[]) && (ifa->ifa_flags & IFF_UP) && (ifa->ifa_flags & IFF_RUNNING) && (ifa->ifa_flags & IFF_BROADCAST) + && (auto_nets == 46 + || (auto_nets == 4 && ifa->ifa_addr->sa_family == AF_INET) + || (auto_nets == 6 && ifa->ifa_addr->sa_family == AF_INET6) ) ) { char cidr[LARGEBUF]; @@ -868,7 +883,6 @@ int main(int argc, char *argv[]) /* https://stackoverflow.com/questions/122208/how-can-i-get-the-ip-address-of-a-local-computer */ upsdebugx(0, "Local address detection feature is not completed on Windows, please call back later"); #endif - auto_nets = 1; } } else { /* not `-m auto` => is `-m cidr` */