From d20f9ab3a9cabffe16ad603f2724d832b4052e2a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 10:48:44 +0200 Subject: [PATCH 01/20] tools/nut-scanner/nut-scanner.c: refactor handle_arg_cidr() into a separate method [#2244] Allow for some shorter indentation Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 292 +++++++++++++++++--------------- 1 file changed, 152 insertions(+), 140 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 0c3071d146..515bd7486a 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -412,6 +412,156 @@ static void * run_eaton_serial(void *arg) return NULL; } +static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) +{ + char *start_ip = NULL, *end_ip = NULL; + int auto_nets = -1; + + if (!strcmp(optarg, "auto") || !strcmp(optarg, "auto4") || !strcmp(optarg, "auto6")) { + if (auto_nets_ptr && *auto_nets_ptr) { + fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); + } else { +#ifndef WIN32 + /* 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; + } + if (auto_nets_ptr) { + *auto_nets_ptr = auto_nets; + } + +#ifndef WIN32 + if (getifaddrs(&ifap) < 0) { + fatalx(EXIT_FAILURE, + "Failed to getifaddrs() for connected subnet scan: %s\n", + strerror(errno)); + } else { + struct ifaddrs *ifa; + char msg[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) { + 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) { + 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) { + 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); + 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) { + 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)); + 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; + } + 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 { + 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) + snprintfcat(msg, sizeof(msg), " IFF_LOOPBACK"); + if (ifa->ifa_flags & IFF_UP) + snprintfcat(msg, sizeof(msg), " IFF_UP"); + if (ifa->ifa_flags & IFF_RUNNING) + snprintfcat(msg, sizeof(msg), " IFF_RUNNING"); + if (ifa->ifa_flags & IFF_BROADCAST) + snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); + + 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) + && (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]; + + 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 */ + } + } + 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 + } + } else { + /* 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); + + add_ip_range(start_ip, end_ip); + start_ip = NULL; + end_ip = NULL; + } +} + static void show_usage(void) { /* NOTE: This code uses `nutscan_avail_*` global vars from nutscan-init.c */ @@ -753,147 +903,9 @@ int main(int argc, char *argv[]) end_ip = NULL; } - if (!strcmp(optarg, "auto") || !strcmp(optarg, "auto4") || !strcmp(optarg, "auto6")) { - 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; -#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", - strerror(errno)); - } else { - struct ifaddrs *ifa; - char msg[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) { - 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) { - 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) { - 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); - 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) { - 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)); - 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; - } - 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 { - 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) - snprintfcat(msg, sizeof(msg), " IFF_LOOPBACK"); - if (ifa->ifa_flags & IFF_UP) - snprintfcat(msg, sizeof(msg), " IFF_UP"); - if (ifa->ifa_flags & IFF_RUNNING) - snprintfcat(msg, sizeof(msg), " IFF_RUNNING"); - if (ifa->ifa_flags & IFF_BROADCAST) - snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); - - 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) - && (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]; - - 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 */ - } - } - 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 - } - } else { - /* 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); + /* Large code block offloaded into a method */ + handle_arg_cidr(optarg, &auto_nets); - add_ip_range(start_ip, end_ip); - start_ip = NULL; - end_ip = NULL; - } break; case 'D': /* nothing to do, here */ From 54c2365798f8ff0aaf2d14e95139037ce8d85d99 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 11:09:10 +0200 Subject: [PATCH 02/20] tools/nut-scanner/nut-scanner.c: refactor handle_arg_cidr() logic to un-indent it some more [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 279 +++++++++++++++++--------------- 1 file changed, 150 insertions(+), 129 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 515bd7486a..2ecb67fb8e 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -415,143 +415,48 @@ static void * run_eaton_serial(void *arg) static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) { char *start_ip = NULL, *end_ip = NULL; + /* Scanning mode: IPv4, IPv6 or both */ int auto_nets = -1; - if (!strcmp(optarg, "auto") || !strcmp(optarg, "auto4") || !strcmp(optarg, "auto6")) { - if (auto_nets_ptr && *auto_nets_ptr) { - fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); - } else { #ifndef WIN32 - /* Inspired by https://stackoverflow.com/a/63789267/4715872 */ - struct ifaddrs *ifap; + /* NOTE: Would need WIN32-specific implementation */ + /* 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; - } - if (auto_nets_ptr) { - *auto_nets_ptr = auto_nets; - } + /* Is this a `-m auto` mode? */ + if (!strncmp(optarg, "auto", 4)) { + if (auto_nets_ptr && *auto_nets_ptr) { + fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); + return; + } -#ifndef WIN32 - if (getifaddrs(&ifap) < 0) { - fatalx(EXIT_FAILURE, - "Failed to getifaddrs() for connected subnet scan: %s\n", - strerror(errno)); - } else { - struct ifaddrs *ifa; - char msg[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) { - 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) { - 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) { - 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); - 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) { - 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)); - 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; - } - 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 { - snprintf(msg, sizeof(msg), "Addr family: %" PRIuMAX, (intmax_t)ifa->ifa_addr->sa_family); -*/ - } + /* Not very efficient to stack strcmp's, but + * also not a hot codepath to care much, either. + */ + if (!strcmp(optarg, "auto")) { + auto_nets = 46; + } else if (!strcmp(optarg, "auto4")) { + auto_nets = 4; + } else if (!strcmp(optarg, "auto6")) { + auto_nets = 6; + } else { + /* TODO: maybe fail right away? + * Or claim a simple auto46 mode? */ + upsdebugx(0, + "Got a '-m auto*' CLI option with unsupported " + "keyword pattern; assuming a CIDR, " + "likely to fail: %s", optarg); + } - if (ifa->ifa_addr->sa_family == AF_INET6 || ifa->ifa_addr->sa_family == AF_INET) { - if (ifa->ifa_flags & IFF_LOOPBACK) - snprintfcat(msg, sizeof(msg), " IFF_LOOPBACK"); - if (ifa->ifa_flags & IFF_UP) - snprintfcat(msg, sizeof(msg), " IFF_UP"); - if (ifa->ifa_flags & IFF_RUNNING) - snprintfcat(msg, sizeof(msg), " IFF_RUNNING"); - if (ifa->ifa_flags & IFF_BROADCAST) - snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); - - 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) - && (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]; - - 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 */ - } - } - 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 + /* Let the caller know, to allow for run-once support */ + if (auto_nets_ptr) { + *auto_nets_ptr = auto_nets; } - } else { - /* not `-m auto` => is `-m cidr` */ + } + + if (auto_nets < 0) { + /* not a supported `-m auto*` pattern => 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); @@ -559,7 +464,123 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) add_ip_range(start_ip, end_ip); start_ip = NULL; end_ip = NULL; + return; } + + /* Handle `-m auto*` modes below */ +#ifndef WIN32 + if (getifaddrs(&ifap) < 0) { + fatalx(EXIT_FAILURE, + "Failed to getifaddrs() for connected subnet scan: %s\n", + strerror(errno)); + } else { + struct ifaddrs *ifa; + char msg[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) { + 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) { + 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) { + 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); + 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) { + 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)); + 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; + } + 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 { + 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) + snprintfcat(msg, sizeof(msg), " IFF_LOOPBACK"); + if (ifa->ifa_flags & IFF_UP) + snprintfcat(msg, sizeof(msg), " IFF_UP"); + if (ifa->ifa_flags & IFF_RUNNING) + snprintfcat(msg, sizeof(msg), " IFF_RUNNING"); + if (ifa->ifa_flags & IFF_BROADCAST) + snprintfcat(msg, sizeof(msg), " IFF_BROADCAST(is assigned)"); + + 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) + && (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]; + + 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 */ + } + } + freeifaddrs(ifap); + } +#else /* WIN32 */ + /* https://stackoverflow.com/questions/122208/how-can-i-get-the-ip-address-of-a-local-computer */ + /* https://github.com/networkupstools/nut/issues/2516 */ + upsdebugx(0, "Local address detection feature is not completed on Windows, please call back later"); +#endif } static void show_usage(void) From 45d873087d4ffdd5967d5d1e2541dafa715a60d7 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 11:35:47 +0200 Subject: [PATCH 03/20] tools/nut-scanner/nut-scanner.c: handle_arg_cidr(): tabs (style) [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 2ecb67fb8e..0cdf9b13d7 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -421,7 +421,7 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) #ifndef WIN32 /* NOTE: Would need WIN32-specific implementation */ /* Inspired by https://stackoverflow.com/a/63789267/4715872 */ - struct ifaddrs *ifap; + struct ifaddrs *ifap; #endif /* Is this a `-m auto` mode? */ @@ -474,14 +474,14 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) "Failed to getifaddrs() for connected subnet scan: %s\n", strerror(errno)); } else { - struct ifaddrs *ifa; - char msg[LARGEBUF]; + struct ifaddrs *ifa; + char msg[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; + char addr[INET6_ADDRSTRLEN]; + char mask[INET6_ADDRSTRLEN]; + int masklen = 0; for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr) { @@ -491,7 +491,7 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) masklen = -1; if (ifa->ifa_addr->sa_family == AF_INET6) { - uint8_t i, j; + uint8_t i, j; /* Ensure proper alignment */ struct sockaddr_in6 sm; @@ -510,7 +510,7 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) 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) { - in_addr_t i; + in_addr_t i; /* Ensure proper alignment */ struct sockaddr_in sa, sm; From 5c1d4b0fa12b57974d848df429d286c32d7b7d24 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 11:37:25 +0200 Subject: [PATCH 04/20] tools/nut-scanner/nut-scanner.c: handle_arg_cidr(): line-wrap long printouts [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 0cdf9b13d7..e8eaac511f 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -508,7 +508,11 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) 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); - 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); + 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) { in_addr_t i; @@ -525,7 +529,11 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) masklen += i & 1; i >>= 1; } - 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); + 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 { snprintf(msg, sizeof(msg), "Addr family: %" PRIuMAX, (intmax_t)ifa->ifa_addr->sa_family); From 065002c6ef97d24c5eec81c29cdae34b8b88c3e0 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 11:38:53 +0200 Subject: [PATCH 05/20] tools/nut-scanner/nut-scanner.c: handle_arg_cidr(): log why some discovered subnets are filtered out [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 53 ++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index e8eaac511f..a5d5c84b89 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -476,6 +476,7 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) } else { struct ifaddrs *ifa; char msg[LARGEBUF]; + char cidr[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. */ @@ -552,33 +553,45 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) upsdebugx(5, "Discovering getifaddrs(): %s", msg); + if (ifa->ifa_flags & IFF_LOOPBACK) { + upsdebugx(6, "Subnet ignored: loopback"); + continue; + } + + if (!( + (ifa->ifa_flags & IFF_UP) + && (ifa->ifa_flags & IFF_RUNNING) + && (ifa->ifa_flags & IFF_BROADCAST) + )) { + upsdebugx(6, "Subnet ignored: not up and running, with a proper broadcast-able address"); + continue; + } + + if (!( + (auto_nets == 46 + || (auto_nets == 4 && ifa->ifa_addr->sa_family == AF_INET) + || (auto_nets == 6 && ifa->ifa_addr->sa_family == AF_INET6) ) + )) { + upsdebugx(6, "Subnet ignored: not of the requested address family"); + continue; + } + /* 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) - && (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]; - - if (snprintf(cidr, sizeof(cidr), "%s/%i", addr, masklen) < 0) { - fatalx(EXIT_FAILURE, "Could not construct a CIDR string from discovered address/mask"); - } + if (snprintf(cidr, sizeof(cidr), "%s/%i", addr, masklen_subnet) < 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); + 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; - } + 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 e91412e7ef008ce2d140be11a6fea3ca1a7dbb31 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 11:39:33 +0200 Subject: [PATCH 06/20] tools/nut-scanner/nut-scanner.c, docs/man/nut-scanner.txt, docs/nut.dict, NEWS.adoc: handle_arg_cidr(): add `-m auto*/ADDRLEN` mode [#2244] Signed-off-by: Jim Klimov --- NEWS.adoc | 5 ++- docs/man/nut-scanner.txt | 8 ++++ docs/nut.dict | 4 +- tools/nut-scanner/nut-scanner.c | 70 ++++++++++++++++++++++++++++----- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 231fabb543..83c12ddfa4 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -144,7 +144,10 @@ installed. 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] + An `/ADDRLEN` suffix can be added to the option, to filter out discovered + subnets with too many bits available for the host address part (avoiding + millions of scans in the extreme cases). + [issue #2244, PR #2509, PR #2513, PR #2517] * bumped version of `libnutscan` to 2.5.2, it now includes a few more methods and symbols from `libcommon`. [issue #2244, PR #2509] diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index fe66ff113e..f97ce2070f 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -151,6 +151,14 @@ A special form `-m auto` allows `nut-scanner` to detect local IP address(es) 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. ++ +An `/ADDRLEN` suffix can be added to the option, to filter out discovered +subnets with too many bits available for the host address part (avoiding +millions of scans in the extreme cases). For example, if your IPv4 LAN's +network range is `10.2.3.0/24`, its address part is `(32-24)=8`. Note that +while this is applied to IPv6 networks also, their typical `/64` subnets +are not likely to have a NUT/SNMP/NetXML/... server *that* close nearby +(in addressing terms), for a tight filter to find them. Default is `8`. NUT DEVICE OPTION ----------------- diff --git a/docs/nut.dict b/docs/nut.dict index 559bbc115d..5f09065786 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3178 utf-8 +personal_ws-1.1 en 3180 utf-8 AAC AAS ABI @@ -11,6 +11,7 @@ ACPresent ADDR ADDRCONFIG ADDRINFO +ADDRLEN ADELSYSTEM ADK ADKK @@ -2848,6 +2849,7 @@ sublicenses submodule submodules subnet +subnets subtree sudo suid diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index a5d5c84b89..8d3cac70e7 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -417,6 +417,15 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) char *start_ip = NULL, *end_ip = NULL; /* Scanning mode: IPv4, IPv6 or both */ int auto_nets = -1; + /* Bit-length limit for *address part* of subnets to consider; + * e.g. if your LAN's network range is 10.2.3.0/24 the address + * part is (32-24)=8. Larger subnets e.g. 10.0.0.0/8 would be + * ignored to avoid billions of scan requests. Note that while + * this is applied to IPv6 also, their typical /64 subnets are + * not likely to have a NUT/SNMP/NetXML/... server *that* close + * nearby in addressing terms, for a tight filter to find them. + */ + int masklen_hosts_limit = 8; #ifndef WIN32 /* NOTE: Would need WIN32-specific implementation */ @@ -426,6 +435,9 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) /* Is this a `-m auto` mode? */ if (!strncmp(optarg, "auto", 4)) { + /* TODO: Maybe split later, to allow separate + * `-m auto4/X` and `-m auto6/Y` requests? + */ if (auto_nets_ptr && *auto_nets_ptr) { fprintf(stderr, "Duplicate request for connected subnet scan ignored\n"); return; @@ -440,6 +452,30 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) auto_nets = 4; } else if (!strcmp(optarg, "auto6")) { auto_nets = 6; + } else if (!strncmp(optarg, "auto/", 5)) { + auto_nets = 46; + masklen_hosts_limit = atoi(optarg + 5); + if (masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { + fatalx(EXIT_FAILURE, + "Invalid auto-net limit value: %s", + optarg); + } + } else if (!strncmp(optarg, "auto4/", 6)) { + auto_nets = 4; + masklen_hosts_limit = atoi(optarg + 6); + if (masklen_hosts_limit < 0 || masklen_hosts_limit > 32) { + fatalx(EXIT_FAILURE, + "Invalid auto-net limit value: %s", + optarg); + } + } else if (!strncmp(optarg, "auto6/", 6)) { + auto_nets = 6; + masklen_hosts_limit = atoi(optarg + 6); + if (masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { + fatalx(EXIT_FAILURE, + "Invalid auto-net limit value: %s", + optarg); + } } else { /* TODO: maybe fail right away? * Or claim a simple auto46 mode? */ @@ -482,14 +518,15 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) * warnings that the result might not fit. */ char addr[INET6_ADDRSTRLEN]; char mask[INET6_ADDRSTRLEN]; - int masklen = 0; + int masklen_subnet = 0; + int masklen_hosts = 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; + masklen_subnet = -1; if (ifa->ifa_addr->sa_family == AF_INET6) { uint8_t i, j; @@ -498,21 +535,22 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) struct sockaddr_in6 sm; memcpy (&sm, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); - masklen = 0; + masklen_subnet = 0; for (j = 0; j < 16; j++) { i = sm.sin6_addr.s6_addr[j]; while (i) { - masklen += i & 1; + masklen_subnet += i & 1; i >>= 1; } } + masklen_hosts = 128 - masklen_subnet; 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); snprintf(msg, sizeof(msg), - "Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, + "Interface: %s\tAddress: %s\tMask: %s (subnet: %i, hosts: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, - masklen, + masklen_subnet, masklen_hosts, (uintmax_t)ifa->ifa_flags); } else if (ifa->ifa_addr->sa_family == AF_INET) { in_addr_t i; @@ -525,15 +563,17 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) snprintf(mask, sizeof(mask), "%s", inet_ntoa(sm.sin_addr)); i = sm.sin_addr.s_addr; - masklen = 0; + masklen_subnet = 0; while (i) { - masklen += i & 1; + masklen_subnet += i & 1; i >>= 1; } + masklen_hosts = 32 - masklen_subnet; + snprintf(msg, sizeof(msg), - "Interface: %s\tAddress: %s\tMask: %s (len: %i)\tFlags: %08" PRIxMAX, + "Interface: %s\tAddress: %s\tMask: %s (subnet: %i, hosts: %i)\tFlags: %08" PRIxMAX, ifa->ifa_name, addr, mask, - masklen, + masklen_subnet, masklen_hosts, (uintmax_t)ifa->ifa_flags); /* } else { @@ -553,6 +593,16 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) upsdebugx(5, "Discovering getifaddrs(): %s", msg); + if (masklen_hosts_limit < masklen_hosts) { + /* NOTE: masklen_hosts==0 means + * an exact hit on one address, + * so an IPv4/32 or IPv6/128. + */ + upsdebugx(6, "Subnet ignored: address range too large: %d bits allowed vs. %d bits per netmask", + masklen_hosts_limit, masklen_hosts); + continue; + } + if (ifa->ifa_flags & IFF_LOOPBACK) { upsdebugx(6, "Subnet ignored: loopback"); continue; From c2f52212f75865be22f8fd692e263a359ce187f5 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 12:00:42 +0200 Subject: [PATCH 07/20] tools/nut-scanner/nut-scanner.c: handle_arg_cidr() rename "optarg" to "arg_addr" [#2244] Avoid warning about clash with a global variable. Fallout of refactoring original code into a method. Also make it `const` as we do not change the original value anyway. Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 8d3cac70e7..67da2806e5 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -412,7 +412,7 @@ static void * run_eaton_serial(void *arg) return NULL; } -static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) +static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) { char *start_ip = NULL, *end_ip = NULL; /* Scanning mode: IPv4, IPv6 or both */ @@ -434,7 +434,7 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) #endif /* Is this a `-m auto` mode? */ - if (!strncmp(optarg, "auto", 4)) { + if (!strncmp(arg_addr, "auto", 4)) { /* TODO: Maybe split later, to allow separate * `-m auto4/X` and `-m auto6/Y` requests? */ @@ -446,35 +446,35 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) /* Not very efficient to stack strcmp's, but * also not a hot codepath to care much, either. */ - if (!strcmp(optarg, "auto")) { + if (!strcmp(arg_addr, "auto")) { auto_nets = 46; - } else if (!strcmp(optarg, "auto4")) { + } else if (!strcmp(arg_addr, "auto4")) { auto_nets = 4; - } else if (!strcmp(optarg, "auto6")) { + } else if (!strcmp(arg_addr, "auto6")) { auto_nets = 6; - } else if (!strncmp(optarg, "auto/", 5)) { + } else if (!strncmp(arg_addr, "auto/", 5)) { auto_nets = 46; - masklen_hosts_limit = atoi(optarg + 5); + masklen_hosts_limit = atoi(arg_addr + 5); if (masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { fatalx(EXIT_FAILURE, "Invalid auto-net limit value: %s", - optarg); + arg_addr); } - } else if (!strncmp(optarg, "auto4/", 6)) { + } else if (!strncmp(arg_addr, "auto4/", 6)) { auto_nets = 4; - masklen_hosts_limit = atoi(optarg + 6); + masklen_hosts_limit = atoi(arg_addr + 6); if (masklen_hosts_limit < 0 || masklen_hosts_limit > 32) { fatalx(EXIT_FAILURE, "Invalid auto-net limit value: %s", - optarg); + arg_addr); } - } else if (!strncmp(optarg, "auto6/", 6)) { + } else if (!strncmp(arg_addr, "auto6/", 6)) { auto_nets = 6; - masklen_hosts_limit = atoi(optarg + 6); + masklen_hosts_limit = atoi(arg_addr + 6); if (masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { fatalx(EXIT_FAILURE, "Invalid auto-net limit value: %s", - optarg); + arg_addr); } } else { /* TODO: maybe fail right away? @@ -482,7 +482,7 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) upsdebugx(0, "Got a '-m auto*' CLI option with unsupported " "keyword pattern; assuming a CIDR, " - "likely to fail: %s", optarg); + "likely to fail: %s", arg_addr); } /* Let the caller know, to allow for run-once support */ @@ -493,8 +493,8 @@ static void handle_arg_cidr(char *optarg, int *auto_nets_ptr) if (auto_nets < 0) { /* not a supported `-m auto*` pattern => is `-m cidr` */ - upsdebugx(5, "Processing CIDR net/mask: %s", optarg); - nutscan_cidr_to_ip(optarg, &start_ip, &end_ip); + upsdebugx(5, "Processing CIDR net/mask: %s", arg_addr); + nutscan_cidr_to_ip(arg_addr, &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); From 58a4e23573c46976dc03c3ab37069d99ae269300 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 12:05:13 +0200 Subject: [PATCH 08/20] tools/nut-scanner/nut-scanner.c: handle_arg_cidr(): report ignoring a subnet by family before checking for mask length [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 67da2806e5..60579a17e4 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -593,6 +593,15 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) upsdebugx(5, "Discovering getifaddrs(): %s", msg); + if (!( + (auto_nets == 46 + || (auto_nets == 4 && ifa->ifa_addr->sa_family == AF_INET) + || (auto_nets == 6 && ifa->ifa_addr->sa_family == AF_INET6) ) + )) { + upsdebugx(6, "Subnet ignored: not of the requested address family"); + continue; + } + if (masklen_hosts_limit < masklen_hosts) { /* NOTE: masklen_hosts==0 means * an exact hit on one address, @@ -617,15 +626,6 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) continue; } - if (!( - (auto_nets == 46 - || (auto_nets == 4 && ifa->ifa_addr->sa_family == AF_INET) - || (auto_nets == 6 && ifa->ifa_addr->sa_family == AF_INET6) ) - )) { - upsdebugx(6, "Subnet ignored: not of the requested address family"); - continue; - } - /* 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 From b116f75d3a98d5136d0dcc3c025d8d77e12cb614 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 12:48:22 +0200 Subject: [PATCH 09/20] tools/nut-scanner/nut-scanner.c: handle_arg_cidr(): fix to use strtol() not atoi() [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 42 ++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 60579a17e4..8aa504f168 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -425,7 +425,9 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) * not likely to have a NUT/SNMP/NetXML/... server *that* close * nearby in addressing terms, for a tight filter to find them. */ - int masklen_hosts_limit = 8; + long masklen_hosts_limit = 8; + char *s = NULL; + int errno_saved; #ifndef WIN32 /* NOTE: Would need WIN32-specific implementation */ @@ -454,26 +456,44 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) auto_nets = 6; } else if (!strncmp(arg_addr, "auto/", 5)) { auto_nets = 46; - masklen_hosts_limit = atoi(arg_addr + 5); - if (masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { + errno = 0; + masklen_hosts_limit = strtol(arg_addr + 5, &s, 10); + errno_saved = errno; + upsdebugx(6, "errno=%d s='%s'(%p) input='%s'(%p) output=%ld", + errno_saved, NUT_STRARG(s), (void *)s, + arg_addr + 5, (void *)(arg_addr + 5), + masklen_hosts_limit); + if (errno_saved || (s && *s != '\0') || masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { fatalx(EXIT_FAILURE, - "Invalid auto-net limit value: %s", + "Invalid auto-net limit value, should be an integer [0..128]: %s", arg_addr); } } else if (!strncmp(arg_addr, "auto4/", 6)) { auto_nets = 4; - masklen_hosts_limit = atoi(arg_addr + 6); - if (masklen_hosts_limit < 0 || masklen_hosts_limit > 32) { + errno = 0; + masklen_hosts_limit = strtol(arg_addr + 6, &s, 10); + errno_saved = errno; + upsdebugx(6, "errno=%d s='%s'(%p) input='%s'(%p) output=%ld", + errno_saved, NUT_STRARG(s), (void *)s, + arg_addr + 6, (void *)(arg_addr + 6), + masklen_hosts_limit); + if (errno_saved || (s && *s != '\0') || masklen_hosts_limit < 0 || masklen_hosts_limit > 32) { fatalx(EXIT_FAILURE, - "Invalid auto-net limit value: %s", + "Invalid auto-net limit value, should be an integer [0..32]: %s", arg_addr); } } else if (!strncmp(arg_addr, "auto6/", 6)) { auto_nets = 6; - masklen_hosts_limit = atoi(arg_addr + 6); - if (masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { + errno = 0; + masklen_hosts_limit = strtol(arg_addr + 6, &s, 10); + errno_saved = errno; + upsdebugx(6, "errno=%d s='%s'(%p) input='%s'(%p) output=%ld", + errno_saved, NUT_STRARG(s), (void *)s, + arg_addr + 6, (void *)(arg_addr + 6), + masklen_hosts_limit); + if (errno_saved || (s && *s != '\0') || masklen_hosts_limit < 0 || masklen_hosts_limit > 128) { fatalx(EXIT_FAILURE, - "Invalid auto-net limit value: %s", + "Invalid auto-net limit value, should be an integer [0..128]: %s", arg_addr); } } else { @@ -607,7 +627,7 @@ static void handle_arg_cidr(const char *arg_addr, int *auto_nets_ptr) * an exact hit on one address, * so an IPv4/32 or IPv6/128. */ - upsdebugx(6, "Subnet ignored: address range too large: %d bits allowed vs. %d bits per netmask", + upsdebugx(6, "Subnet ignored: address range too large: %ld bits allowed vs. %d bits per netmask", masklen_hosts_limit, masklen_hosts); continue; } From 60c4d33caec09925097aff95f5b9b9766ee4226a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 12:49:11 +0200 Subject: [PATCH 10/20] tools/nut-scanner/nut-scanner.c: `-t timeout`: fix to use strtol() not atol() [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 8aa504f168..ce1ed9b7d2 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -957,12 +957,28 @@ int main(int argc, char *argv[]) switch(opt_ret) { case 't': - timeout = (useconds_t)atol(optarg) * 1000 * 1000; /*in usec*/ - if (timeout <= 0) { - fprintf(stderr, - "Illegal timeout value, using default %ds\n", - DEFAULT_NETWORK_TIMEOUT); - timeout = DEFAULT_NETWORK_TIMEOUT * 1000 * 1000; + { // scoping + long l; + char *s = NULL; + int errno_saved; + + errno = 0; + l = strtol(optarg, &s, 10); + errno_saved = errno; + upsdebugx(6, "errno=%d s='%s'(%p) input='%s'(%p) output=%ld", + errno_saved, NUT_STRARG(s), (void *)s, + optarg, (void *)(optarg), l); + + if (errno_saved || (s && *s != '\0') || l <= 0) { + /* TODO: Any max limit? At least, + * max(useconds_t)/1000000 ? */ + fprintf(stderr, + "Illegal timeout value, using default %ds\n", + DEFAULT_NETWORK_TIMEOUT); + timeout = DEFAULT_NETWORK_TIMEOUT * 1000 * 1000; + } else { + timeout = (useconds_t)l * 1000 * 1000; /*in usec*/ + } } break; case 's': From 27774238ab87732a947398b9411f9500a72566ce Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 18:33:08 +0200 Subject: [PATCH 11/20] tools/nut-scanner/nut-scan.h: move __cplusplus extern "C" guard up to before we declare vars and types Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scan.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/nut-scanner/nut-scan.h b/tools/nut-scanner/nut-scan.h index 3bba701369..4f8fcce0b5 100644 --- a/tools/nut-scanner/nut-scan.h +++ b/tools/nut-scanner/nut-scan.h @@ -73,7 +73,15 @@ # ifdef HAVE_SEMAPHORE # include # endif +#endif + +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif +#ifdef HAVE_PTHREAD # if (defined HAVE_PTHREAD_TRYJOIN) || (defined HAVE_SEMAPHORE) extern size_t max_threads, curr_threads, max_threads_netxml, max_threads_oldnut, max_threads_netsnmp; # endif @@ -88,12 +96,6 @@ typedef struct nutscan_thread { } nutscan_thread_t; #endif /* HAVE_PTHREAD */ -#ifdef __cplusplus -/* *INDENT-OFF* */ -extern "C" { -/* *INDENT-ON* */ -#endif - /* SNMP structure */ typedef struct nutscan_snmp { char * community; From 50471bb2cb4128dea6cd2b5d30af062b3529df0b Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 18:34:30 +0200 Subject: [PATCH 12/20] tools/nut-scanner/nut-scanner.c, tools/nut-scanner/nutscan-ip.h: move ip_range_t definition into header [#2244] Signed-off-by: Jim Klimov --- tools/nut-scanner/nut-scanner.c | 5 ----- tools/nut-scanner/nutscan-ip.h | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 0c3071d146..c871d81f98 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -138,11 +138,6 @@ 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; diff --git a/tools/nut-scanner/nutscan-ip.h b/tools/nut-scanner/nutscan-ip.h index 8cdf6d8498..d67690a4aa 100644 --- a/tools/nut-scanner/nutscan-ip.h +++ b/tools/nut-scanner/nutscan-ip.h @@ -56,6 +56,14 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t *, const char * startIP, const cha char * nutscan_ip_iter_inc(nutscan_ip_iter_t *); int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip); +/* Track requested IP ranges (from CLI or auto-discovery) */ +/* One IP address range: */ +typedef struct ip_range_s { + char * start_ip; + char * end_ip; + struct ip_range_s * next; +} ip_range_t; + #ifdef __cplusplus /* *INDENT-OFF* */ } From 644bb3a4e5680756a1fd5d5f40bf259ab448af6f Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 8 Jul 2024 19:06:54 +0200 Subject: [PATCH 13/20] nut-scanner code and docs: refactor with nutscan_ip_range_list_t type for ip_ranges[] list and helper metadata, and methods as part of libnutscan [#2244, #2511] Not bumping library version, because it was recently bumped as part of other PRs about this issue. Technically the scope of the library has been changed by new exported methods and header lines. Signed-off-by: Jim Klimov --- docs/man/Makefile.am | 9 +++ docs/man/nutscan.txt | 3 + docs/man/nutscan_add_ip_range.txt | 74 +++++++++++++++++++ docs/man/nutscan_free_ip_ranges.txt | 40 ++++++++++ docs/man/nutscan_init_ip_ranges.txt | 41 +++++++++++ docs/nut.dict | 3 +- tools/nut-scanner/nut-scanner.c | 110 ++++++---------------------- tools/nut-scanner/nutscan-ip.c | 95 ++++++++++++++++++++++++ tools/nut-scanner/nutscan-ip.h | 31 +++++++- 9 files changed, 313 insertions(+), 93 deletions(-) create mode 100644 docs/man/nutscan_add_ip_range.txt create mode 100644 docs/man/nutscan_free_ip_ranges.txt create mode 100644 docs/man/nutscan_init_ip_ranges.txt diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 52669c1cd4..dc6a747857 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -198,6 +198,9 @@ SRC_DEV_PAGES = \ nutscan_display_ups_conf_with_sanity_check.txt \ nutscan_display_ups_conf.txt \ nutscan_display_parsable.txt \ + nutscan_init_ip_ranges.txt \ + nutscan_free_ip_ranges.txt \ + nutscan_add_ip_range.txt \ nutscan_cidr_to_ip.txt \ nutscan_new_device.txt \ nutscan_free_device.txt \ @@ -316,6 +319,9 @@ MAN3_DEV_PAGES = \ nutscan_display_ups_conf_with_sanity_check.3 \ nutscan_display_ups_conf.3 \ nutscan_display_parsable.3 \ + nutscan_init_ip_ranges.3 \ + nutscan_free_ip_ranges.3 \ + nutscan_add_ip_range.3 \ nutscan_cidr_to_ip.3 \ nutscan_new_device.3 \ nutscan_free_device.3 \ @@ -391,6 +397,9 @@ HTML_DEV_MANS = \ nutscan_display_ups_conf_with_sanity_check.html \ nutscan_display_ups_conf.html \ nutscan_display_parsable.html \ + nutscan_init_ip_ranges.html \ + nutscan_free_ip_ranges.html \ + nutscan_add_ip_range.html \ nutscan_cidr_to_ip.html \ nutscan_new_device.html \ nutscan_free_device.html \ diff --git a/docs/man/nutscan.txt b/docs/man/nutscan.txt index bfef09f52b..7f3390d026 100644 --- a/docs/man/nutscan.txt +++ b/docs/man/nutscan.txt @@ -68,6 +68,9 @@ linkman:nutscan_display_parsable[3], linkman:nutscan_display_ups_conf[3], linkman:nutscan_new_device[3], linkman:nutscan_free_device[3], linkman:nutscan_add_device_to_device[3], linkman:nutscan_add_option_to_device[3], +linkman:nutscan_init_ip_ranges[3], +linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_add_ip_range[3], linkman:nutscan_cidr_to_ip[3] Internet resources: diff --git a/docs/man/nutscan_add_ip_range.txt b/docs/man/nutscan_add_ip_range.txt new file mode 100644 index 0000000000..6d0a922202 --- /dev/null +++ b/docs/man/nutscan_add_ip_range.txt @@ -0,0 +1,74 @@ +NUTSCAN_FREE_IP_RANGES(3) +========================= + +NAME +---- + +nutscan_add_ip_range - Add an entry with IP address range (start and end +address) to a `nutscan_ip_range_list_t` structure. + +SYNOPSIS +-------- + + #include + + /* One IP address range: */ + typedef struct nutscan_ip_range_s { + char * start_ip; + char * end_ip; + struct nutscan_ip_range_s * next; + } nutscan_ip_range_t; + + /* List of IP address ranges and helper data: */ + typedef struct nutscan_ip_range_list_s { + nutscan_ip_range_t * ip_ranges; + nutscan_ip_range_t * ip_ranges_last; + size_t ip_ranges_count; + } nutscan_ip_range_list_t; + + + size_t nutscan_add_ip_range( + nutscan_ip_range_list_t *irl, + char * start_ip, + char * end_ip); + +DESCRIPTION +----------- + +The *nutscan_add_ip_range()* function can create and add a `nutscan_ip_range_t` +entry based on provided inputs to the specified `nutscan_ip_range_list_t` +structure. The resulting amount of entries in the structure is returned, +or 0 in case of non-fatal errors. + +This function skips work if: + +* the structure pointer is `NULL` (0 is returned); +* neither `start_ip` nor `end_ip` were provided, i.e. they have `NULL` values + (current list length from the structure is returned); +* failed to allocate the entry (fatal). + +If only one of `start_ip` or `end_ip` values was provided (not `NULL`), a +single-address range is created with both addresses set to the same pointer +value. + +The structure should be initialized before use by `nutscan_init_ip_ranges()`. + +The caller must free the contents of the structure after completing its use +by calling `nutscan_free_ip_ranges()` (after which the structure can be +re-used for a new list), and explicitly `free()` the structure object itself if +it was allocated dynamically (e.g. by calling `nutscan_init_ip_ranges(NULL)`). + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +Currently there are no checks for duplicate or overlapping entries, so the +same IP addresses and whole IP address ranges can be added to the list (and +would eventually be scanned) many times. + +SEE ALSO +-------- + +linkman:nutscan_init_ip_ranges[3], linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_cidr_to_ip[3] diff --git a/docs/man/nutscan_free_ip_ranges.txt b/docs/man/nutscan_free_ip_ranges.txt new file mode 100644 index 0000000000..7b9ac15845 --- /dev/null +++ b/docs/man/nutscan_free_ip_ranges.txt @@ -0,0 +1,40 @@ +NUTSCAN_FREE_IP_RANGES(3) +========================= + +NAME +---- + +nutscan_free_ip_ranges - Free contents of a `nutscan_ip_range_list_t` +structure populated (and optionally created) by `nutscan_init_ip_ranges()` +and, more importantly, filled by a series of `nutscan_add_ip_range()` calls. + +SYNOPSIS +-------- + + #include + + void nutscan_free_ip_ranges(nutscan_ip_range_list_t *irl); + +DESCRIPTION +----------- + +The *nutscan_free_ip_ranges()* function can free a `nutscan_ip_range_list_t` +structure. Doing so, it frees the whole linked list of `nutscan_ip_range_t` +entries, and zeroes out helper properties. + +The structure itself is not freed (as it can be a statically allocated +variable on the stack), and can be re-used for a new list if needed. + +The caller must free the structure object if it was allocated dynamically +(e.g. by calling `nutscan_init_ip_ranges(NULL)`). + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_init_ip_ranges[3], linkman:nutscan_add_ip_range[3], +linkman:nutscan_cidr_to_ip[3] diff --git a/docs/man/nutscan_init_ip_ranges.txt b/docs/man/nutscan_init_ip_ranges.txt new file mode 100644 index 0000000000..4151e86498 --- /dev/null +++ b/docs/man/nutscan_init_ip_ranges.txt @@ -0,0 +1,41 @@ +NUTSCAN_INIT_IP_RANGES(3) +========================= + +NAME +---- + +nutscan_init_ip_ranges - Initialize contents of a `nutscan_ip_range_list_t` +structure (and optionally create one in the first place). + +SYNOPSIS +-------- + + #include + + nutscan_ip_range_list_t * nutscan_init_ip_ranges(nutscan_ip_range_list_t *irl); + +DESCRIPTION +----------- + +The *nutscan_init_ip_ranges()* function can prepare a `nutscan_ip_range_list_t` +structure by zeroing out its fields. If the argument is `NULL`, the structure +is dynamically allocated. Either way, a pointer to it is returned. + +A structure passed by caller is not assumed to have any valid contents to free, +as it may have garbage from stack after allocation. + +The caller must free the contents of the structure after completing its use +by calling `nutscan_free_ip_ranges` (after which the structure can be re-used), +and explicitly `free()` the structure object itself if it was allocated +dynamically (e.g. by calling `nutscan_init_ip_ranges(NULL)`). + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_free_ip_ranges[3], linkman:nutscan_add_ip_range[3], +linkman:nutscan_cidr_to_ip[3] diff --git a/docs/nut.dict b/docs/nut.dict index 559bbc115d..e897d63eb9 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3178 utf-8 +personal_ws-1.1 en 3179 utf-8 AAC AAS ABI @@ -2090,6 +2090,7 @@ ipp ippon ipv ipxe +irl isDefault isbmex ish diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index c871d81f98..a5556ab1a2 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -138,76 +138,7 @@ static char * serial_ports = NULL; static int cli_link_detail_level = -1; /* Track requested IP ranges (from CLI or auto-discovery) */ -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(5, "%s: skip, no addresses were provided", __func__); - return ip_ranges_count; - } - - if (start_ip == NULL) { - 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(5, "%s: only start address was provided, setting end to same: %s", - __func__, start_ip); - end_ip = start_ip; - } - - p = xcalloc(1, sizeof(ip_range_t)); - - 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; -} +static nutscan_ip_range_list_t ip_ranges_list; #ifdef HAVE_PTHREAD static pthread_t thread[TYPE_END]; @@ -271,10 +202,10 @@ 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; + nutscan_ip_range_t *p = ip_ranges_list.ip_ranges; upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", - __func__, ip_ranges_count); + __func__, ip_ranges_list.ip_ranges_count); dev[TYPE_SNMP] = NULL; while (p) { @@ -296,10 +227,10 @@ 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; + nutscan_ip_range_t *p = ip_ranges_list.ip_ranges; upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", - __func__, ip_ranges_count); + __func__, ip_ranges_list.ip_ranges_count); if (!p) { /* Probe broadcast */ @@ -328,11 +259,11 @@ static void * run_xml(void * arg) static void * run_nut_old(void *arg) { nutscan_device_t * dev_ret; - ip_range_t *p = ip_ranges; + nutscan_ip_range_t *p = ip_ranges_list.ip_ranges; NUT_UNUSED_VARIABLE(arg); upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", - __func__, ip_ranges_count); + __func__, ip_ranges_list.ip_ranges_count); dev[TYPE_NUT] = NULL; while (p) { @@ -370,10 +301,10 @@ 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; + nutscan_ip_range_t *p = ip_ranges_list.ip_ranges; upsdebugx(2, "Entering %s for %" PRIuSIZE " IP address range(s)", - __func__, ip_ranges_count); + __func__, ip_ranges_list.ip_ranges_count); if (!p) { /* Probe local device */ @@ -674,6 +605,7 @@ int main(int argc, char *argv[]) nut_debug_level++; } + nutscan_init_ip_ranges(&ip_ranges_list); nutscan_init(); /* Default, see -Q/-N/-P below */ @@ -703,7 +635,7 @@ int main(int argc, char *argv[]) /* Save whatever we have, either * this one address or an earlier * known range with its end */ - add_ip_range(start_ip, end_ip); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -711,7 +643,7 @@ int main(int argc, char *argv[]) start_ip = strdup(optarg); if (end_ip != NULL) { /* Already we know two addresses, save them */ - add_ip_range(start_ip, end_ip); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -721,7 +653,7 @@ int main(int argc, char *argv[]) /* Save whatever we have, either * this one address or an earlier * known range with its start */ - add_ip_range(start_ip, end_ip); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -729,7 +661,7 @@ int main(int argc, char *argv[]) end_ip = strdup(optarg); if (start_ip != NULL) { /* Already we know two addresses, save them */ - add_ip_range(start_ip, end_ip); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -743,7 +675,7 @@ int main(int argc, char *argv[]) /* 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); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -865,7 +797,7 @@ int main(int argc, char *argv[]) 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); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -885,7 +817,7 @@ int main(int argc, char *argv[]) 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); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -1162,7 +1094,7 @@ int main(int argc, char *argv[]) if (start_ip != NULL || end_ip != NULL) { /* Something did not cancel out above */ - add_ip_range(start_ip, end_ip); + nutscan_add_ip_range(&ip_ranges_list, start_ip, end_ip); start_ip = NULL; end_ip = NULL; } @@ -1209,7 +1141,7 @@ int main(int argc, char *argv[]) } if (allow_snmp && nutscan_avail_snmp) { - if (!ip_ranges_count) { + if (!ip_ranges_list.ip_ranges_count) { upsdebugx(quiet, "No IP range(s) requested, skipping SNMP"); nutscan_avail_snmp = 0; } @@ -1256,7 +1188,7 @@ int main(int argc, char *argv[]) } if (allow_oldnut && nutscan_avail_nut) { - if (!ip_ranges_count) { + if (!ip_ranges_list.ip_ranges_count) { upsdebugx(quiet, "No IP range(s) requested, skipping NUT bus (old libupsclient connect method)"); nutscan_avail_nut = 0; } @@ -1437,7 +1369,7 @@ int main(int argc, char *argv[]) upsdebugx(1, "SCANS DONE: free common scanner resources"); nutscan_free(); - free_ip_ranges(); + nutscan_free_ip_ranges(&ip_ranges_list); upsdebugx(1, "SCANS DONE: EXIT_SUCCESS"); return EXIT_SUCCESS; diff --git a/tools/nut-scanner/nutscan-ip.c b/tools/nut-scanner/nutscan-ip.c index 2bb6976dd4..777661651a 100644 --- a/tools/nut-scanner/nutscan-ip.c +++ b/tools/nut-scanner/nutscan-ip.c @@ -88,6 +88,101 @@ static int ntop6(struct in6_addr * ip, char * host, GETNAMEINFO_TYPE_ARG46 host_ host, host_size, NULL, 0, NI_NUMERICHOST); } +/* Track requested IP ranges (from CLI or auto-discovery) */ +nutscan_ip_range_list_t *nutscan_init_ip_ranges(nutscan_ip_range_list_t *irl) +{ + if (!irl) { + irl = (nutscan_ip_range_list_t *)xcalloc(1, sizeof(nutscan_ip_range_list_t)); + } + + irl->ip_ranges = NULL; + irl->ip_ranges_last = NULL; + irl->ip_ranges_count = 0; + + return irl; +} + +void nutscan_free_ip_ranges(nutscan_ip_range_list_t *irl) +{ + nutscan_ip_range_t *p; + + if (!irl) { + upsdebugx(5, "%s: skip, no nutscan_ip_range_list_t was specified", __func__); + return; + } + + p = irl->ip_ranges; + while (p) { + irl->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 = irl->ip_ranges; + } + + irl->ip_ranges_last = NULL; + irl->ip_ranges_count = 0; +} + +size_t nutscan_add_ip_range(nutscan_ip_range_list_t *irl, char * start_ip, char * end_ip) +{ + nutscan_ip_range_t *p; + + if (!irl) { + upsdebugx(5, "%s: skip, no nutscan_ip_range_list_t was specified", __func__); + return 0; + } + + if (!start_ip && !end_ip) { + upsdebugx(5, "%s: skip, no addresses were provided", __func__); + return irl->ip_ranges_count; + } + + if (start_ip == NULL) { + 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(5, "%s: only start address was provided, setting end to same: %s", + __func__, start_ip); + end_ip = start_ip; + } + + p = xcalloc(1, sizeof(nutscan_ip_range_t)); + + p->start_ip = start_ip; + p->end_ip = end_ip; + p->next = NULL; + + if (!irl->ip_ranges) { + /* First entry */ + irl->ip_ranges = p; + } + + if (irl->ip_ranges_last) { + /* Got earlier entries, promote the tail */ + irl->ip_ranges_last->next = p; + } + + irl->ip_ranges_last = p; + irl->ip_ranges_count++; + + upsdebugx(1, "Recorded IP address range #%" PRIuSIZE ": [%s .. %s]", + irl->ip_ranges_count, start_ip, end_ip); + + return irl->ip_ranges_count; +} + /* Return the first ip or NULL if error */ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const char * stopIP) { diff --git a/tools/nut-scanner/nutscan-ip.h b/tools/nut-scanner/nutscan-ip.h index d67690a4aa..c78baad765 100644 --- a/tools/nut-scanner/nutscan-ip.h +++ b/tools/nut-scanner/nutscan-ip.h @@ -58,11 +58,36 @@ int nutscan_cidr_to_ip(const char * cidr, char ** start_ip, char ** stop_ip); /* Track requested IP ranges (from CLI or auto-discovery) */ /* One IP address range: */ -typedef struct ip_range_s { +typedef struct nutscan_ip_range_s { char * start_ip; char * end_ip; - struct ip_range_s * next; -} ip_range_t; + struct nutscan_ip_range_s * next; +} nutscan_ip_range_t; + +/* List of IP address ranges and helper data: */ +typedef struct nutscan_ip_range_list_s { + nutscan_ip_range_t * ip_ranges; + nutscan_ip_range_t * ip_ranges_last; + size_t ip_ranges_count; +} nutscan_ip_range_list_t; + +/* Initialize fields of caller-provided list + * (can allocate one if arg is NULL - caller + * must free it later). Does not assume that + * caller's list values are valid and should + * be freed (can be some garbage from stack). + * + * Returns pointer to the original or allocated list. + */ +nutscan_ip_range_list_t *nutscan_init_ip_ranges(nutscan_ip_range_list_t *irl); + +/* Free information from the list (does not + * free the list object itself, can be static) + * so it can be further re-used or freed. + */ +void nutscan_free_ip_ranges(nutscan_ip_range_list_t *irl); + +size_t nutscan_add_ip_range(nutscan_ip_range_list_t *irl, char * start_ip, char * end_ip); #ifdef __cplusplus /* *INDENT-OFF* */ From cfeec4e1f2c86b8677af619157bcfdd81ecaadc2 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:14:11 +0200 Subject: [PATCH 14/20] docs/man/nut-scanner.txt: clarify about text host names as start/stop/cidr IPs [#2519] 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 f97ce2070f..400552c0fd 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -125,6 +125,11 @@ 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). + +A single-address range may be a host name which would be resolved into one IP +address by the system resolver. A CIDR using a host name and netmask length +would be resolved into an IP address and subjected to the mask application, +to query hosts "near" the named one. ++ 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 diff --git a/docs/nut.dict b/docs/nut.dict index 637ca5338e..b37bab3381 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3181 utf-8 +personal_ws-1.1 en 3182 utf-8 AAC AAS ABI @@ -2369,6 +2369,7 @@ nd nds netcat netclient +netmask netserver netsh netsnmp From d05e7680b585492213c2780969e72fc60880fa24 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:23:52 +0200 Subject: [PATCH 15/20] docs/man/Makefile.am: move a comment up to cover more relevant entries Signed-off-by: Jim Klimov --- docs/man/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index dc6a747857..9d0e12edb5 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -331,13 +331,13 @@ MAN3_DEV_PAGES = \ nutscan_get_serial_ports_list.3 \ nutscan_init.3 +# Alias page for one text describing two commands: upscli_readline_timeout.3: upscli_readline.3 touch $@ upscli_sendline_timeout.3: upscli_sendline.3 touch $@ -# Alias page for one text describing two commands: nutscan_add_commented_option_to_device.3: nutscan_add_option_to_device.3 touch $@ From fd74b205d100b8d89ea25388637edcc9e4ad7317 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:24:23 +0200 Subject: [PATCH 16/20] docs/man/Makefile.am: extend HTML_DEV_MANS_FICTION to be on par with .3 man pages Signed-off-by: Jim Klimov --- docs/man/Makefile.am | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 9d0e12edb5..1e9935aff4 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -414,8 +414,16 @@ HTML_DEV_MANS = \ # Can't make this work on all make implementations at once, so disabled for now # Anyway it would be the same man-like page for several functions HTML_DEV_MANS_FICTION = \ + upscli_readline_timeout.html \ + upscli_sendline_timeout.html \ nutscan_add_commented_option_to_device.html +upscli_readline_timeout.html: upscli_readline.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + +upscli_sendline_timeout.html: upscli_sendline.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + nutscan_add_commented_option_to_device.html: nutscan_add_option_to_device.html test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ From fe90f6b3e3845be95542703e263db3106d91a072 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:25:55 +0200 Subject: [PATCH 17/20] docs/man/nutscan_add_ip_range.txt: fix copy-paste typo in title [#2244, #2511] Signed-off-by: Jim Klimov --- docs/man/nutscan_add_ip_range.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/nutscan_add_ip_range.txt b/docs/man/nutscan_add_ip_range.txt index 6d0a922202..052acff54b 100644 --- a/docs/man/nutscan_add_ip_range.txt +++ b/docs/man/nutscan_add_ip_range.txt @@ -1,5 +1,5 @@ -NUTSCAN_FREE_IP_RANGES(3) -========================= +NUTSCAN_ADD_IP_RANGE(3) +======================= NAME ---- From 2ddd4c5310a02d732b304e3aed63fcab8f947f85 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:26:18 +0200 Subject: [PATCH 18/20] docs/man/nutscan_add_ip_range.txt: update wording and structure comments [#2244, #2511] Signed-off-by: Jim Klimov --- docs/man/nutscan_add_ip_range.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/man/nutscan_add_ip_range.txt b/docs/man/nutscan_add_ip_range.txt index 052acff54b..b85af52f11 100644 --- a/docs/man/nutscan_add_ip_range.txt +++ b/docs/man/nutscan_add_ip_range.txt @@ -4,8 +4,8 @@ NUTSCAN_ADD_IP_RANGE(3) NAME ---- -nutscan_add_ip_range - Add an entry with IP address range (start and end -address) to a `nutscan_ip_range_list_t` structure. +nutscan_add_ip_range - Add an entry with IP address range (starting +and ending addresses) to a `nutscan_ip_range_list_t` structure. SYNOPSIS -------- @@ -21,9 +21,9 @@ SYNOPSIS /* List of IP address ranges and helper data: */ typedef struct nutscan_ip_range_list_s { - nutscan_ip_range_t * ip_ranges; - nutscan_ip_range_t * ip_ranges_last; - size_t ip_ranges_count; + nutscan_ip_range_t * ip_ranges; /* Actual linked list of entries, first entry */ + nutscan_ip_range_t * ip_ranges_last; /* Pointer to end of list for quicker additions */ + size_t ip_ranges_count; /* Counter of added entries */ } nutscan_ip_range_list_t; From d968dff94312f3ddadad1edaa0ea378d49205867 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:26:44 +0200 Subject: [PATCH 19/20] tools/nut-scanner/nutscan-ip.c: fix comment markup Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-ip.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nut-scanner/nutscan-ip.c b/tools/nut-scanner/nutscan-ip.c index 777661651a..2ab0ab90ff 100644 --- a/tools/nut-scanner/nutscan-ip.c +++ b/tools/nut-scanner/nutscan-ip.c @@ -290,8 +290,8 @@ char * nutscan_ip_iter_init(nutscan_ip_iter_t * ip, const char * startIP, const } /* return the next IP -return NULL if there is no more IP -*/ + * return NULL if there is no more IP + */ char * nutscan_ip_iter_inc(nutscan_ip_iter_t * ip) { char host[SMALLBUF]; From bf8cc0dfe4bbf1158da968162c93061998ec30d3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Tue, 9 Jul 2024 01:27:14 +0200 Subject: [PATCH 20/20] tools/nut-scanner/nutscan-ip.h: add nutscan_ip_range_list_t structure comments [#2244, #2511] Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-ip.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/nut-scanner/nutscan-ip.h b/tools/nut-scanner/nutscan-ip.h index c78baad765..819aa970f2 100644 --- a/tools/nut-scanner/nutscan-ip.h +++ b/tools/nut-scanner/nutscan-ip.h @@ -66,9 +66,9 @@ typedef struct nutscan_ip_range_s { /* List of IP address ranges and helper data: */ typedef struct nutscan_ip_range_list_s { - nutscan_ip_range_t * ip_ranges; - nutscan_ip_range_t * ip_ranges_last; - size_t ip_ranges_count; + nutscan_ip_range_t * ip_ranges; /* Actual linked list of entries, first entry */ + nutscan_ip_range_t * ip_ranges_last; /* Pointer to end of list for quicker additions */ + size_t ip_ranges_count; /* Counter of added entries */ } nutscan_ip_range_list_t; /* Initialize fields of caller-provided list