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
This commit is contained in:
Alan Somers 2025-08-12 10:26:02 -06:00
parent ec92e61f99
commit d888317796
7 changed files with 328 additions and 54 deletions

View file

@ -1205,6 +1205,8 @@
..
seq
..
sockstat
..
soelim
..
sort

View file

@ -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>

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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>

View file

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