The branch main has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=d888317796190bec350aea3701b8aed3bfdad4c8
commit d888317796190bec350aea3701b8aed3bfdad4c8 Author: Alan Somers <asom...@freebsd.org> AuthorDate: 2025-08-12 16:26:02 +0000 Commit: Alan Somers <asom...@freebsd.org> CommitDate: 2025-08-12 16:38:25 +0000 sockstat: fix port parsing after libxo integration parse_ports has been broken ever since 7b35b4d, and it's a sufficiently complicated function that it really deserves some unit tests. Fix it, and add tests. Refactor the code a little bit to facilitate unit tests. Chiefly, split the tested functions out of main.c into sockstat.c . PR: 288731 Fixes: 7b35b4d ("sockstat: add libxo support") Sponsored by: ConnectWise PR: https://github.com/freebsd/freebsd-src/pull/1807 Reviewed by: rido --- etc/mtree/BSD.tests.dist | 2 + usr.bin/sockstat/Makefile | 5 +- usr.bin/sockstat/main.c | 65 +++-------- usr.bin/sockstat/sockstat.c | 77 +++++++++++++ usr.bin/sockstat/sockstat.h | 35 ++++++ usr.bin/sockstat/tests/Makefile | 8 ++ usr.bin/sockstat/tests/sockstat_test.c | 190 +++++++++++++++++++++++++++++++++ 7 files changed, 328 insertions(+), 54 deletions(-) diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index b3b2b61da143..2c25d9386032 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -1205,6 +1205,8 @@ .. seq .. + sockstat + .. soelim .. sort diff --git a/usr.bin/sockstat/Makefile b/usr.bin/sockstat/Makefile index 7b71662b7cd4..c6e7a078162b 100644 --- a/usr.bin/sockstat/Makefile +++ b/usr.bin/sockstat/Makefile @@ -1,7 +1,7 @@ .include <src.opts.mk> PROG= sockstat -SRCS= main.c +SRCS= main.c sockstat.c LIBADD= jail xo @@ -14,4 +14,7 @@ LIBADD+= cap_sysctl CFLAGS+= -DWITH_CASPER .endif +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + .include <bsd.prog.mk> diff --git a/usr.bin/sockstat/main.c b/usr.bin/sockstat/main.c index 895c4d453b54..b5e0248b743a 100644 --- a/usr.bin/sockstat/main.c +++ b/usr.bin/sockstat/main.c @@ -54,7 +54,6 @@ #include <arpa/inet.h> #include <capsicum_helpers.h> -#include <ctype.h> #include <errno.h> #include <inttypes.h> #include <jail.h> @@ -74,6 +73,8 @@ #include <casper/cap_pwd.h> #include <casper/cap_sysctl.h> +#include "sockstat.h" + #define SOCKSTAT_XO_VERSION "1" #define sstosin(ss) ((struct sockaddr_in *)(ss)) #define sstosin6(ss) ((struct sockaddr_in6 *)(ss)) @@ -110,12 +111,6 @@ static size_t default_numprotos = nitems(default_protos); static int *protos; /* protocols to use */ static size_t numprotos; /* allocated size of protos[] */ -static int *ports; - -#define INT_BIT (sizeof(int)*CHAR_BIT) -#define SET_PORT(p) do { ports[p / INT_BIT] |= 1 << (p % INT_BIT); } while (0) -#define CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT))) - struct addr { union { struct sockaddr_storage address; @@ -276,50 +271,6 @@ parse_protos(char *protospec) return (proto_index); } -static void -parse_ports(const char *portspec) -{ - const char *p, *q; - int port, end; - - if (ports == NULL) - if ((ports = calloc(65536 / INT_BIT, sizeof(int))) == NULL) - xo_err(1, "calloc()"); - p = portspec; - while (*p != '\0') { - if (!isdigit(*p)) - xo_errx(1, "syntax error in port range"); - for (q = p; *q != '\0' && isdigit(*q); ++q) - /* nothing */ ; - for (port = 0; p < q; ++p) - port = port * 10 + digittoint(*p); - if (port < 0 || port > 65535) - xo_errx(1, "invalid port number"); - SET_PORT(port); - switch (*p) { - case '-': - ++p; - break; - case ',': - ++p; - /* fall through */ - case '\0': - default: - continue; - } - for (q = p; *q != '\0' && isdigit(*q); ++q) - /* nothing */ ; - for (end = 0; p < q; ++p) - end = end * 10 + digittoint(*p); - if (end < port || end > 65535) - xo_errx(1, "invalid port number"); - while (port++ < end) - SET_PORT(port); - if (*p == ',') - ++p; - } -} - static void sockaddr(struct sockaddr_storage *ss, int af, void *addr, int port) { @@ -1767,7 +1718,7 @@ main(int argc, char *argv[]) const char *pwdcmds[] = { "setpassent", "getpwuid" }; const char *pwdfields[] = { "pw_name" }; int protos_defined = -1; - int o, i; + int o, i, err; argc = xo_parse_args(argc, argv); if (argc < 0) @@ -1817,7 +1768,15 @@ main(int argc, char *argv[]) opt_n = true; break; case 'p': - parse_ports(optarg); + err = parse_ports(optarg); + switch (err) { + case EINVAL: + xo_errx(1, "syntax error in port range"); + break; + case ERANGE: + xo_errx(1, "invalid port number"); + break; + } break; case 'P': protos_defined = parse_protos(optarg); diff --git a/usr.bin/sockstat/sockstat.c b/usr.bin/sockstat/sockstat.c new file mode 100644 index 000000000000..7bb7f6a66e3f --- /dev/null +++ b/usr.bin/sockstat/sockstat.c @@ -0,0 +1,77 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 ConnectWise + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <ctype.h> +#include <stdlib.h> +#include <libxo/xo.h> + +#include "sockstat.h" + +int *ports; + +int +parse_ports(const char *portspec) +{ + const char *p; + + if (ports == NULL) + if ((ports = calloc(65536 / INT_BIT, sizeof(int))) == NULL) + xo_err(1, "calloc()"); + p = portspec; + while (*p != '\0') { + long port, end; + char *endptr = NULL; + + errno = 0; + port = strtol(p, &endptr, 10); + if (errno) + return (errno); + if (port < 0 || port > 65535) + return (ERANGE); + SET_PORT(port); + switch (*endptr) { + case '-': + p = endptr + 1; + end = strtol(p, &endptr, 10); + break; + case ',': + p = endptr + 1; + continue; + default: + p = endptr; + continue; + } + if (errno) + return (errno); + if (end < port || end > 65535) + return (ERANGE); + while (port++ < end) + SET_PORT(port); + } + return (0); +} diff --git a/usr.bin/sockstat/sockstat.h b/usr.bin/sockstat/sockstat.h new file mode 100644 index 000000000000..80d91ebbaddc --- /dev/null +++ b/usr.bin/sockstat/sockstat.h @@ -0,0 +1,35 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 ConnectWise + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define INT_BIT (sizeof(int)*CHAR_BIT) +#define SET_PORT(p) do { ports[p / INT_BIT] |= 1 << (p % INT_BIT); } while (0) +#define CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT))) + +extern int *ports; + +int parse_ports(const char *portspec); diff --git a/usr.bin/sockstat/tests/Makefile b/usr.bin/sockstat/tests/Makefile new file mode 100644 index 000000000000..9971bca2d474 --- /dev/null +++ b/usr.bin/sockstat/tests/Makefile @@ -0,0 +1,8 @@ +ATF_TESTS_C+= sockstat_test +SRCS.sockstat_test= sockstat_test.c ../sockstat.c + +LIBADD= xo + +PACKAGE= tests + +.include <bsd.test.mk> diff --git a/usr.bin/sockstat/tests/sockstat_test.c b/usr.bin/sockstat/tests/sockstat_test.c new file mode 100644 index 000000000000..2d2a23c7a166 --- /dev/null +++ b/usr.bin/sockstat/tests/sockstat_test.c @@ -0,0 +1,190 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 ConnectWise + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/errno.h> + +#include <atf-c.h> + +#include "../sockstat.h" + +ATF_TC_WITHOUT_HEAD(backwards_range); +ATF_TC_BODY(backwards_range, tc) +{ + const char portspec[] = "22-21"; + + ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(erange_low); +ATF_TC_BODY(erange_low, tc) +{ + const char portspec[] = "-1"; + + ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(erange_high); +ATF_TC_BODY(erange_high, tc) +{ + const char portspec[] = "65536"; + + ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(erange_on_range_end); +ATF_TC_BODY(erange_on_range_end, tc) +{ + const char portspec[] = "22-65536"; + + ATF_CHECK_EQ(ERANGE, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(multiple); +ATF_TC_BODY(multiple, tc) +{ + const char portspec[] = "80,443"; + + ATF_REQUIRE_EQ(0, parse_ports(portspec)); + + ATF_CHECK(!CHK_PORT(0)); + + ATF_CHECK(!CHK_PORT(79)); + ATF_CHECK(CHK_PORT(80)); + ATF_CHECK(!CHK_PORT(81)); + + ATF_CHECK(!CHK_PORT(442)); + ATF_CHECK(CHK_PORT(443)); + ATF_CHECK(!CHK_PORT(444)); +} + +ATF_TC_WITHOUT_HEAD(multiple_plus_ranges); +ATF_TC_BODY(multiple_plus_ranges, tc) +{ + const char portspec[] = "80,443,500-501,510,520,40000-40002"; + + ATF_REQUIRE_EQ(0, parse_ports(portspec)); + + ATF_CHECK(!CHK_PORT(0)); + + ATF_CHECK(!CHK_PORT(79)); + ATF_CHECK(CHK_PORT(80)); + ATF_CHECK(!CHK_PORT(81)); + + ATF_CHECK(!CHK_PORT(442)); + ATF_CHECK(CHK_PORT(443)); + ATF_CHECK(!CHK_PORT(444)); + + ATF_CHECK(!CHK_PORT(499)); + ATF_CHECK(CHK_PORT(500)); + ATF_CHECK(CHK_PORT(501)); + ATF_CHECK(!CHK_PORT(502)); + + ATF_CHECK(!CHK_PORT(519)); + ATF_CHECK(CHK_PORT(520)); + ATF_CHECK(!CHK_PORT(521)); + + ATF_CHECK(!CHK_PORT(39999)); + ATF_CHECK(CHK_PORT(40000)); + ATF_CHECK(CHK_PORT(40001)); + ATF_CHECK(CHK_PORT(40002)); + ATF_CHECK(!CHK_PORT(40003)); +} + +ATF_TC_WITHOUT_HEAD(nonnumeric); +ATF_TC_BODY(nonnumeric, tc) +{ + const char portspec[] = "foo"; + + ATF_CHECK_EQ(EINVAL, parse_ports(portspec)); +} + +ATF_TC_WITHOUT_HEAD(null_range); +ATF_TC_BODY(null_range, tc) +{ + const char portspec[] = "22-22"; + + ATF_REQUIRE_EQ(0, parse_ports(portspec)); + + ATF_CHECK(!CHK_PORT(0)); + ATF_CHECK(CHK_PORT(22)); + ATF_CHECK(!CHK_PORT(23)); +} + +ATF_TC_WITHOUT_HEAD(range); +ATF_TC_BODY(range, tc) +{ + const char portspec[] = "22-25"; + + ATF_REQUIRE_EQ(0, parse_ports(portspec)); + + ATF_CHECK(!CHK_PORT(0)); + ATF_CHECK(CHK_PORT(22)); + ATF_CHECK(CHK_PORT(23)); + ATF_CHECK(CHK_PORT(24)); + ATF_CHECK(CHK_PORT(25)); + ATF_CHECK(!CHK_PORT(26)); +} + +ATF_TC_WITHOUT_HEAD(single); +ATF_TC_BODY(single, tc) +{ + const char portspec[] = "22"; + + ATF_REQUIRE_EQ(0, parse_ports(portspec)); + + ATF_CHECK(!CHK_PORT(0)); + ATF_CHECK(CHK_PORT(22)); +} + +ATF_TC_WITHOUT_HEAD(zero); +ATF_TC_BODY(zero, tc) +{ + const char portspec[] = "0"; + + ATF_REQUIRE_EQ(0, parse_ports(portspec)); + + ATF_CHECK(CHK_PORT(0)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, backwards_range); + ATF_TP_ADD_TC(tp, erange_low); + ATF_TP_ADD_TC(tp, erange_high); + ATF_TP_ADD_TC(tp, erange_on_range_end); + ATF_TP_ADD_TC(tp, multiple); + ATF_TP_ADD_TC(tp, multiple_plus_ranges); + ATF_TP_ADD_TC(tp, nonnumeric); + ATF_TP_ADD_TC(tp, null_range); + ATF_TP_ADD_TC(tp, range); + ATF_TP_ADD_TC(tp, single); + ATF_TP_ADD_TC(tp, zero); + + return (atf_no_error()); +}