add tests to cover DIOCCHANGERULE ioctl(2)
authorsashan <sashan@openbsd.org>
Thu, 11 Nov 2021 12:49:53 +0000 (12:49 +0000)
committersashan <sashan@openbsd.org>
Thu, 11 Nov 2021 12:49:53 +0000 (12:49 +0000)
OK bluhm@

regress/sbin/pfctl/Makefile
regress/sbin/pfctl/changerule-after.ok [new file with mode: 0644]
regress/sbin/pfctl/changerule-before.ok [new file with mode: 0644]
regress/sbin/pfctl/changerule-head.ok [new file with mode: 0644]
regress/sbin/pfctl/changerule-tail.ok [new file with mode: 0644]
regress/sbin/pfctl/changerule.c [new file with mode: 0644]
sbin/pfctl/pfctl.c

index 61816a8..20e1f09 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.233 2021/10/25 14:56:47 sashan Exp $
+# $OpenBSD: Makefile,v 1.234 2021/11/11 12:49:53 sashan Exp $
 
 # TARGETS
 # pf: feed pfNN.in through pfctl and check whether the output matches pfNN.ok
@@ -12,6 +12,7 @@
 # pfopt: as target pf, but supply extra command line options
 # pfcmd: test pfctl command line parsing
 # pfloadanchors: load anchor from nested files
+# pf-changerule: covers DIOCCHANGERULE ioctl(2)
 
 PFTESTS=1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 PFTESTS+=28 29 30 31 32 34 35 36 38 39 40 41 44 46 47 48 49 50
@@ -36,6 +37,20 @@ PFLOADANCHORS=112 113
 
 PFCTL ?=       /sbin/pfctl
 
+.PATH: ${.CURDIR}/../../../sbin/pfctl ${.CURDIR}/../../../sys/net
+
+PROG=  changerule
+SRCS=  changerule.c parse.y pfctl_parser.c pf_print_state.c
+SRCS+= pfctl.c pfctl_osfp.c pfctl_radix.c pfctl_table.c
+SRCS+= pfctl_optimize.c pf_ruleset.c pfctl_queue.c
+CFLAGS= -Wall -Wmissing-prototypes -Wno-uninitialized -Wstrict-prototypes
+CFLAGS+= -Wno-unused-variable
+CFLAGS+= -I${.CURDIR}/../../../sbin/pfctl -DREGRESS_NOMAIN
+YFLAGS=
+
+LDADD+= -lm
+DPADD+= ${LIBM}
+
 MAKEOBJDIRPREFIX=
 
 SHELL=/bin/sh
@@ -106,6 +121,7 @@ selfpf:             ${SELFPF_TARGETS}
 pf-update:     ${PF_UPDATES}
 REGRESS_TARGETS+=pf-include-setup pf
 REGRESS_TARGETS+=selfpf
+REGRESS_TARGETS+=pf-changerule
 UPDATE_TARGETS+=pf-update
 
 pf-include-setup:
@@ -362,6 +378,32 @@ pf-loadanchors-setup:
        [ -f ${.OBJDIR}/$f ] || ln -s ${.CURDIR}/$f ${.OBJDIR}
 .endfor
 
+pf-changerule: changerule changerule-tail.ok changerule-head.ok \
+    changerule-before.ok changerule-after.ok
+       ${SUDO} ${PFCTL} -a 'regress/*' -Fr
+       echo 'pass all' | ${SUDO} ${PFCTL} -a regress -f -
+.for i in 10 20 30 40 50
+       echo "pass in proto tcp from any to any port $i" | \
+           ${SUDO} ./changerule -a regress -i 0
+.endfor
+       ${SUDO} ${PFCTL} -a regress -sr | diff -u changerule-head.ok /dev/stdin
+       ${SUDO} ${PFCTL} -a 'regress/*' -Fr
+       echo 'pass all' | ${SUDO} ${PFCTL} -a regress -f -
+.for i in 10 20 30 40 50
+       echo "pass in proto tcp from any to any port $i" | \
+           ${SUDO} ./changerule -a regress -i -1
+.endfor
+       ${SUDO} ${PFCTL} -a regress -sr | diff -u changerule-tail.ok /dev/stdin
+       echo 'pass in proto tcp from any to any port 15' | \
+           ${SUDO} ./changerule -a regress -i 2
+       ${SUDO} ${PFCTL} -a regress -sr | \
+           diff -u changerule-before.ok /dev/stdin
+       echo 'pass in proto tcp from any to any port 25' | \
+           ${SUDO} ./changerule -a regress -I 3
+       ${SUDO} ${PFCTL} -a regress -sr | \
+           diff -u changerule-after.ok /dev/stdin
+       ${SUDO} ${PFCTL} -a 'regress/*' -Fr
+
 update:        ${UPDATE_TARGETS}
 
 alltests: ${REGRESS_TARGETS} ${NODEFAULT_TARGETS}
diff --git a/regress/sbin/pfctl/changerule-after.ok b/regress/sbin/pfctl/changerule-after.ok
new file mode 100644 (file)
index 0000000..d05519b
--- /dev/null
@@ -0,0 +1,8 @@
+pass all flags S/SA
+pass in proto tcp from any to any port = 10 flags S/SA
+pass in proto tcp from any to any port = 15 flags S/SA
+pass in proto tcp from any to any port = 20 flags S/SA
+pass in proto tcp from any to any port = 25 flags S/SA
+pass in proto tcp from any to any port = 30 flags S/SA
+pass in proto tcp from any to any port = 40 flags S/SA
+pass in proto tcp from any to any port = 50 flags S/SA
diff --git a/regress/sbin/pfctl/changerule-before.ok b/regress/sbin/pfctl/changerule-before.ok
new file mode 100644 (file)
index 0000000..79a6e42
--- /dev/null
@@ -0,0 +1,7 @@
+pass all flags S/SA
+pass in proto tcp from any to any port = 10 flags S/SA
+pass in proto tcp from any to any port = 15 flags S/SA
+pass in proto tcp from any to any port = 20 flags S/SA
+pass in proto tcp from any to any port = 30 flags S/SA
+pass in proto tcp from any to any port = 40 flags S/SA
+pass in proto tcp from any to any port = 50 flags S/SA
diff --git a/regress/sbin/pfctl/changerule-head.ok b/regress/sbin/pfctl/changerule-head.ok
new file mode 100644 (file)
index 0000000..faf6ac0
--- /dev/null
@@ -0,0 +1,6 @@
+pass in proto tcp from any to any port = 50 flags S/SA
+pass in proto tcp from any to any port = 40 flags S/SA
+pass in proto tcp from any to any port = 30 flags S/SA
+pass in proto tcp from any to any port = 20 flags S/SA
+pass in proto tcp from any to any port = 10 flags S/SA
+pass all flags S/SA
diff --git a/regress/sbin/pfctl/changerule-tail.ok b/regress/sbin/pfctl/changerule-tail.ok
new file mode 100644 (file)
index 0000000..c4791d4
--- /dev/null
@@ -0,0 +1,6 @@
+pass all flags S/SA
+pass in proto tcp from any to any port = 10 flags S/SA
+pass in proto tcp from any to any port = 20 flags S/SA
+pass in proto tcp from any to any port = 30 flags S/SA
+pass in proto tcp from any to any port = 40 flags S/SA
+pass in proto tcp from any to any port = 50 flags S/SA
diff --git a/regress/sbin/pfctl/changerule.c b/regress/sbin/pfctl/changerule.c
new file mode 100644 (file)
index 0000000..8efd47d
--- /dev/null
@@ -0,0 +1,221 @@
+/* $OpenBSD: changerule.c,v 1.1 2021/11/11 12:49:53 sashan Exp $ */
+/*
+ * Copyright (c) 2021 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.
+ */
+
+/*
+ * changerule - simple tool to test DIOCCHANGERULE functionality (see pf(4))
+ * Tool reads firewall rules from stdin only. If more rules are passed, then
+ * only the first one is being used. Examples:
+ *     echo 'pass all' | changerule -a test -i 0
+ *             inserts a rule to the first position in ruleset test
+ *
+ *     echo 'pass all' | changerule -a test -i -1
+ *             inserts a rule to the last position in ruleset test
+ *
+ *     echo 'pass all' | changerule -a test -i 3
+ *             inserts a rule before existing No. 3 rule (rules numbering
+ *             starts with 0) in ruleset test
+ *
+ *     echo 'pass all' | changerule -a test -I 3
+ *             inserts a rule after existing No. 3 rule (rules numbering
+ *             starts with 0) in ruleset test
+ *
+ *     changerule -a test -r 3
+ *             removes existing No. 3 rule from ruleset test
+ *
+ */
+#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 <arpa/inet.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <libgen.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void   changerule_usage(void);
+int    do_chng_cmd(char *, int, int);
+
+extern int dev;
+extern char *anchoropt;
+extern char *pf_device;
+
+__dead void
+changerule_usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr, "usage: %s", __progname);
+       fprintf(stderr, "[-a anchor] [ -i ruleNo ] [ -I ruleNo ]\n");
+       exit(1);
+}
+
+int
+do_chng_cmd(char *anchorname, int cmd, int rule_no)
+{
+       struct pfctl            pf;
+       struct pf_anchor        rs_anchor;
+       struct pf_ruleset       *rs = &rs_anchor.ruleset;
+       struct pfioc_rule       pcr;
+
+       memset(&pf, 0, sizeof(pf));
+       memset(&rs_anchor, 0, sizeof(rs_anchor));
+       pf.anchor = &rs_anchor;
+       pf_init_ruleset(rs);
+
+       if (strlcpy(pf.anchor->path, anchorname,
+           sizeof(pf.anchor->path)) >= sizeof (pf.anchor->path))
+               errx(1, "%s: strlcpy\n", __func__);
+
+       pf.astack[0] = pf.anchor;
+       pf.asd = 0;
+       pf.dev = dev;
+
+       memset(&pcr, 0, sizeof(pcr));
+       strlcpy(pcr.anchor, anchorname, sizeof(pcr.anchor));
+       pcr.action = PF_CHANGE_GET_TICKET;
+       if (ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
+               errx(1, "ioctl(ticket) @ %s", __func__);
+
+       pcr.action = cmd;
+       pcr.nr = rule_no;
+       if (cmd != PF_CHANGE_REMOVE) {
+               if (parse_config("-", &pf) < 0) {
+                       errx(1, "Syntax error in rule");
+                       return (1);
+               }
+
+               if (TAILQ_FIRST(rs->rules.active.ptr) != NULL)
+                       memcpy(&pcr.rule, TAILQ_FIRST(rs->rules.active.ptr),
+                           sizeof(pcr.rule));
+               else
+                       errx(1, "no rule");
+       }
+
+       if (ioctl(dev, DIOCCHANGERULE, &pcr) < 0) {
+               errx(1, "ioctl(commit) @ %s", __func__);
+       }
+
+       return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+       char     anchorname[PATH_MAX];
+       const char *errstr;
+       int ch;
+       int rule_no;
+       int chng_cmd;
+       int after = 0;
+
+       if (argc < 2)
+               changerule_usage();
+
+       while ((ch = getopt(argc, argv, "a:i:I:r:")) != -1) {
+               switch (ch) {
+               case 'a':
+                       anchoropt = optarg;
+                       break;
+               case 'I':
+                       after = 1;
+                       /* FALLTHROUGH */
+               case 'i':
+                       rule_no = strtonum(optarg, -1, 0x7fffffff, &errstr);
+                       if (errstr != NULL) {
+                               warnx("Rule number outside of range <%d, %d\n",
+                                   -1, 0x7fffffff);
+                               exit(1);
+                       }
+                       switch (rule_no) {
+                       case 0:
+                               chng_cmd = PF_CHANGE_ADD_HEAD;
+                               break;
+                       case -1:
+                               chng_cmd = PF_CHANGE_ADD_TAIL;
+                               break;
+                       default:
+                               if (after)
+                                       chng_cmd = PF_CHANGE_ADD_AFTER;
+                               else
+                                       chng_cmd = PF_CHANGE_ADD_BEFORE;
+                       }
+                       break;
+               case 'r':
+                       rule_no = strtonum(optarg, -1, 0x7fffffff, &errstr);
+                       if (errstr != NULL) {
+                               warnx("Rule number outside of range <%d, %d\n",
+                                   -1, 0x7fffffff);
+                               exit(1);
+                       }
+                       chng_cmd = PF_CHANGE_REMOVE;
+                       break;
+               default:
+                       changerule_usage();
+                       /* NOTREACHED */
+               }
+       }
+
+       if (argc != optind) {
+               warnx("unknown command line argument: %s ...", argv[optind]);
+               changerule_usage();
+               /* NOTREACHED */
+       }
+
+       memset(anchorname, 0, sizeof(anchorname));
+       if (anchoropt != NULL) {
+               if (anchoropt[0] == '\0')
+                       errx(1, "anchor name must not be empty");
+
+               if (anchoropt[0] == '_' || strstr(anchoropt, "/_") != NULL)
+                       errx(1, "anchor names beginning with '_' cannot "
+                           "be modified from the command line");
+               int len = strlen(anchoropt);
+
+               if (anchoropt[len - 1] == '*') {
+                       warnx("wildcard anchors not supported\n");
+                       changerule_usage();
+               }
+               if (strlcpy(anchorname, anchoropt,
+                   sizeof(anchorname)) >= sizeof(anchorname))
+                       errx(1, "anchor name '%s' too long",
+                           anchoropt);
+       }
+
+       dev = open(pf_device, O_RDWR);
+       if (dev == -1)
+               err(1, "/dev/pf");
+
+       return (do_chng_cmd(anchoropt, chng_cmd, rule_no));
+}
index bcd1478..9756425 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: pfctl.c,v 1.384 2021/10/25 14:50:29 sashan Exp $ */
+/*     $OpenBSD: pfctl.c,v 1.385 2021/11/11 12:49:53 sashan Exp $ */
 
 /*
  * Copyright (c) 2001 Daniel Hartmeier
@@ -2459,6 +2459,7 @@ pfctl_reset(int dev, int opts)
        pfctl_clear_interface_flags(dev, opts);
 }
 
+#ifndef        REGRESS_NOMAIN
 int
 main(int argc, char *argv[])
 {
@@ -2889,6 +2890,7 @@ main(int argc, char *argv[])
 
        exit(exit_val);
 }
+#endif /* REGRESS_NOMAIN */
 
 char *
 pf_strerror(int errnum)