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());
+}

Reply via email to