--- /dev/null
+/* $OpenBSD: dev-limit.c,v 1.1 2023/07/06 19:55:58 sashan Exp $ */
+
+/*
+ * Copyright (c) 2023 Alexandr Nedvedicky <sashan@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <err.h>
+#include <sys/wait.h>
+
+static int sigchild;
+
+static void
+usage(const char *progname)
+{
+ fprintf(stderr,
+ "%s [-d] [-s success_count] [-c child_count] [-t timeout]\n"
+ "if no options are specified program opens '/dev/pf'\n"
+ "and waits for 5s before it exits\n"
+ "\t-s how many children should successfully open /dev/pf\n"
+ "\t-c children to fork, each child opens /dev/pf\n"
+ "\t-t timeout in seconds each child should wait\n"
+ "after successfully opening /dev/pf. Child exits immediately\n"
+ "if /dev/pf can not be opened\n", progname);
+ exit(1);
+}
+
+static void
+handle_sigchild(int signum)
+{
+ if (signum == SIGCHLD)
+ sigchild = 1;
+}
+
+static void
+open_pf_and_exit(unsigned int sleep_time)
+{
+ if (open("/dev/pf", O_RDONLY) == -1)
+ exit(1);
+
+ sleep(sleep_time);
+ exit(0);
+}
+
+int
+main(int argc, char *const argv[])
+{
+ pid_t *pids;
+ unsigned int chld_count = 0;
+ unsigned int sleep_time = 5;
+ unsigned int expect_success = 0;
+ unsigned int success, errors, i;
+ const char *errstr, *sleep_arg;
+ int status;
+ int c;
+
+ while ((c = getopt(argc, argv, "t:c:s:")) != -1) {
+ switch (c) {
+ case 't':
+ sleep_arg = (char *const)optarg;
+ sleep_time = strtonum(optarg, 1, 60, &errstr);
+ if (errstr != NULL) {
+ fprintf(stderr,
+ "%s invalid sleep time %s: %s, must be in "
+ "range <1, 60>\n", argv[0], errstr, optarg);
+ usage(argv[0]);
+ }
+ break;
+ case 'c':
+ chld_count = strtonum(optarg, 1, 32768, &errstr);
+ if (errstr != NULL) {
+ fprintf(stderr,
+ "%s invalid children count %s: %s, must be "
+ "in range <1, 32768>\n", argv[0], optarg,
+ errstr);
+ usage(argv[0]);
+ }
+ break;
+ case 's':
+ expect_success = strtonum(optarg, 0, 32768, &errstr);
+ if (errstr != NULL) {
+ fprintf(stderr,
+ "%s invalid expect success count %s: %s "
+ "must be in range <1, 32768>\n", argv[0],
+ optarg, errstr);
+ usage(argv[0]);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (chld_count == 0)
+ open_pf_and_exit(sleep_time);
+
+ signal(SIGCHLD, handle_sigchild);
+ pids = (pid_t *)malloc(sizeof(pid_t) * chld_count);
+ if (pids == 0)
+ err(1, "%s malloc: ", argv[0]);
+
+ i = 0;
+ while ((sigchild == 0) && (i < chld_count)) {
+ if ((pids[i++] = fork()) == 0)
+ execl(argv[0], argv[0], "-t", sleep_arg, NULL);
+ }
+ chld_count = i;
+
+ success = 0;
+ errors = 0;
+ for (i = 0; i < chld_count; i++) {
+ waitpid(pids[i], &status, 0);
+ if (status == 0)
+ success++;
+ else
+ errors++;
+ }
+
+ free(pids);
+
+ if (success != expect_success) {
+ printf("Successful opens: %u\n", success);
+ printf("Failures: %u\n", errors);
+ printf("Expected opens: %u\n", expect_success);
+ printf("%u vs %u = %u + %u\n",
+ chld_count, errors + success, errors, success);
+ return (1);
+ }
+
+ return (0);
+}
--- /dev/null
+/* $OpenBSD: iocmd-limit.c,v 1.1 2023/07/06 19:55:58 sashan Exp $ */
+
+/*
+ * Copyright (c) 2023 Alexandr Nedvedicky <sashan@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#define REGRESS_ANCHOR "regress"
+
+static void
+usage(const char *progname)
+{
+ fprintf(stderr,
+ "%s -c iocmd [-i iterations ]\n"
+ "\t-c iocmd to test, currently DIOCGETRULES "
+ "and DIOCXEND are supported\n"
+ "\t-i number of iterations is 1 by default\n", progname);
+ exit(1);
+}
+
+static int
+do_DIOCGETRULES_test(int dev)
+{
+ struct pfioc_rule pr;
+ int rv;
+
+ memset(&pr, 0, sizeof(pr));
+ memcpy(pr.anchor, REGRESS_ANCHOR, sizeof(REGRESS_ANCHOR));
+ pr.rule.action = PF_PASS;
+
+ if ((rv = ioctl(dev, DIOCGETRULES, &pr)) == -1) {
+ /*
+ * we expect to see EBUSY anything else is odd and we should
+ * exit right away.
+ */
+ if (errno != EBUSY)
+ err(1, "%s DIOCGETRULES: ", __func__);
+ }
+
+ return (rv);
+}
+
+static int
+result_DIOCGETRULES(unsigned int iterations, unsigned int limit)
+{
+ int rv;
+ /*
+ * DIOCGETRULES must see EBUSY before iterations reach limit
+ * to conclude test is successful.
+ */
+ rv = (iterations < limit) ? 0 : 1;
+ if (rv)
+ printf(
+ "DIOCGETRULES could obtain %u tickets, reaching the limit "
+ "of %u tickets\n",
+ iterations, limit);
+
+ return (rv);
+}
+
+static int
+do_DIOCXEND_test(int dev)
+{
+ struct pfioc_rule pr;
+ int rv;
+
+ memset(&pr, 0, sizeof(pr));
+ memcpy(pr.anchor, REGRESS_ANCHOR, sizeof(REGRESS_ANCHOR));
+ pr.rule.action = PF_PASS;
+
+ if ((rv = ioctl(dev, DIOCGETRULES, &pr)) == -1)
+ warn("%s DIOCGETRULES: ", __func__);
+ else if ((rv = ioctl(dev, DIOCXEND, &pr.ticket)) == -1)
+ warn("%s DIOCXEND: ", __func__);
+
+ return (rv);
+}
+
+static int
+result_DIOCXEND(unsigned int iterations, unsigned int limit)
+{
+ int rv;
+ /*
+ * failing to reach limit when also closing tickets
+ * using DIOXXEND is an error.
+ */
+ rv = (iterations < limit) ? 1 : 0;
+ if (rv)
+ printf(
+ "Although test is is using DIOCXEND it still"
+ "hits limit (%u)\n", iterations);
+ return (rv);
+}
+
+static struct iocmd_test {
+ const char *iocmd_name;
+ int (*iocmd_test)(int);
+ int (*iocmd_result)(unsigned int, unsigned int);
+} iocmd_test_tab[] = {
+ { "DIOCGETRULES", do_DIOCGETRULES_test, result_DIOCGETRULES },
+ { "DIOCXEND", do_DIOCXEND_test, result_DIOCXEND },
+ { NULL, NULL }
+};
+
+static struct iocmd_test *
+parse_iocmd_name(const char *iocmd_name)
+{
+ int i = 0;
+
+ while (iocmd_test_tab[i].iocmd_name != NULL) {
+ if (strcasecmp(iocmd_test_tab[i].iocmd_name, iocmd_name) == 0)
+ break;
+ i++;
+ }
+
+ return ((iocmd_test_tab[i].iocmd_name == NULL) ?
+ NULL : &iocmd_test_tab[i]);
+}
+
+int
+main(int argc, char *const argv[])
+{
+ const char *errstr = NULL;
+ unsigned int iterations = 1;
+ unsigned int i = 0;
+ int dev;
+ int c;
+ struct iocmd_test *test_iocmd = NULL;
+
+ while ((c = getopt(argc, argv, "i:c:")) != -1) {
+ switch (c) {
+ case 'i':
+ iterations = strtonum(optarg, 1, UINT32_MAX, &errstr);
+ if (errstr != NULL) {
+ fprintf(stderr,
+ "%s: number of iteration (-i %s) "
+ "is invalid: %s\n",
+ argv[0], optarg, errstr);
+ usage(argv[0]);
+ }
+ break;
+ case 'c':
+ test_iocmd = parse_iocmd_name(optarg);
+ if (test_iocmd == NULL) {
+ fprintf(stderr, "%s invalid iocmd: %s\n",
+ argv[0], optarg);
+ usage(argv[0]);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (test_iocmd == NULL) {
+ fprintf(stderr, "%s -c option is required\n", argv[0]);
+ usage(argv[0]);
+ }
+
+ dev = open("/dev/pf", O_RDONLY);
+ if (dev < 0)
+ err(1, "open(\"dev/pf\"): ");
+
+ while (i < iterations) {
+ if (test_iocmd->iocmd_test(dev) != 0)
+ break;
+ i++;
+ }
+
+ return (test_iocmd->iocmd_result(i, iterations));
+}