git: d88831779619 - main - sockstat: fix port parsing after libxo integration
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 12 Aug 2025 16:40:31 UTC
The branch main has been updated by asomers:
URL: https://cgit.FreeBSD.org/src/commit/?id=d888317796190bec350aea3701b8aed3bfdad4c8
commit d888317796190bec350aea3701b8aed3bfdad4c8
Author: Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2025-08-12 16:26:02 +0000
Commit: Alan Somers <asomers@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());
+}