git: be0e5f0221dc - main - pfctl: support killing states by key

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 09 May 2025 22:16:23 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=be0e5f0221dc2875c6a832f8c285d6afff4e94bb

commit be0e5f0221dc2875c6a832f8c285d6afff4e94bb
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-05-09 13:29:12 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-05-09 20:49:30 +0000

    pfctl: support killing states by key
    
    Add "key" modifier for -k to make pfctl can kill a state by specifying
    the key of the state.
    
    ok sasha
    
    Obtained from:  OpenBSD, yasuoka <yasuoka@openbsd.org>, c42801d935
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 sbin/pfctl/pfctl.8 |  18 ++++++--
 sbin/pfctl/pfctl.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 136 insertions(+), 3 deletions(-)

diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8
index 5029d8438ed7..acf1bacee08f 100644
--- a/sbin/pfctl/pfctl.8
+++ b/sbin/pfctl/pfctl.8
@@ -24,7 +24,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd February 10, 2025
+.Dd May 9, 2025
 .Dt PFCTL 8
 .Os
 .Sh NAME
@@ -254,13 +254,14 @@ option may be specified, which will kill all the source tracking
 entries from the first host/network to the second.
 .It Xo
 .Fl k
-.Ar host | network | label | id | gateway | nat
+.Ar host | network | label | id | key | gateway | nat
 .Xc
 Kill all of the state entries matching the specified
 .Ar host ,
 .Ar network ,
 .Ar label ,
 .Ar id ,
+.Ar key ,
 .Ar gateway,
 or
 .Ar nat.
@@ -293,7 +294,7 @@ To kill all states with the target
 .Pp
 .Dl # pfctl -k 0.0.0.0/0 -k host2
 .Pp
-It is also possible to kill states by rule label or state ID.
+It is also possible to kill states by rule label, state key or state ID.
 In this mode the first
 .Fl k
 argument is used to specify the type
@@ -304,6 +305,17 @@ from rules carrying the label
 .Pp
 .Dl # pfctl -k label -k foobar
 .Pp
+To kill one specific state by its key
+(protocol, host1, port1, direction, host2 and port2 in the same format
+of pfctl -s state),
+use the
+.Ar key
+modifier and as a second argument the state key.
+To kill a state whose protocol is TCP and originating from
+10.0.0.101:32123 to 10.0.0.1:80 use:
+.Pp
+.Dl # pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123'
+.Pp
 To kill one specific state by its unique state ID
 (as shown by pfctl -s state -vv),
 use the
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 595f688d6139..c540a62f0409 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -83,6 +83,8 @@ int	 pfctl_net_kill_states(int, const char *, int);
 int	 pfctl_gateway_kill_states(int, const char *, int);
 int	 pfctl_label_kill_states(int, const char *, int);
 int	 pfctl_id_kill_states(int, const char *, int);
+int	 pfctl_key_kill_states(int, const char *, int);
+int	 pfctl_parse_host(char *, struct pf_rule_addr *);
 void	 pfctl_init_options(struct pfctl *);
 int	 pfctl_load_options(struct pfctl *);
 int	 pfctl_load_limit(struct pfctl *, unsigned int, unsigned int);
@@ -954,6 +956,123 @@ pfctl_id_kill_states(int dev, const char *iface, int opts)
 	return (0);
 }
 
+int
+pfctl_key_kill_states(int dev, const char *iface, int opts)
+{
+	struct pfctl_kill kill;
+	char *s, *token, *tokens[4];
+	struct protoent *p;
+	u_int i, sidx, didx;
+	int ret, killed;
+
+	if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+		warnx("no key specified");
+		usage();
+	}
+	memset(&kill, 0, sizeof(kill));
+
+	if (iface != NULL &&
+	    strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >=
+	    sizeof(kill.ifname))
+		errx(1, "invalid interface: %s", iface);
+
+	s = strdup(state_kill[1]);
+	if (!s)
+		errx(1, "%s: strdup", __func__);
+	i = 0;
+	while ((token = strsep(&s, " \t")) != NULL)
+		if (*token != '\0') {
+			if (i < 4)
+				tokens[i] = token;
+			i++;
+		}
+	if (i != 4)
+		errx(1, "%s: key must be "
+		    "\"protocol host1:port1 direction host2:port2\" format",
+		    __func__);
+
+	if ((p = getprotobyname(tokens[0])) == NULL)
+		errx(1, "invalid protocol: %s", tokens[0]);
+	kill.proto = p->p_proto;
+
+	if (strcmp(tokens[2], "->") == 0) {
+		sidx = 1;
+		didx = 3;
+	} else if (strcmp(tokens[2], "<-") == 0) {
+		sidx = 3;
+		didx = 1;
+	} else
+		errx(1, "invalid direction: %s", tokens[2]);
+
+	if (pfctl_parse_host(tokens[sidx], &kill.src) == -1)
+		errx(1, "invalid host: %s", tokens[sidx]);
+	if (pfctl_parse_host(tokens[didx], &kill.dst) == -1)
+		errx(1, "invalid host: %s", tokens[didx]);
+
+	if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
+		errc(1, ret, "DIOCKILLSTATES");
+
+	if ((opts & PF_OPT_QUIET) == 0)
+		fprintf(stderr, "killed %d states\n", killed);
+
+	return (0);
+}
+
+int
+pfctl_parse_host(char *str, struct pf_rule_addr *addr)
+{
+	char *s = NULL, *sbs, *sbe;
+	struct addrinfo hints, *ai;
+	struct sockaddr_in *sin4;
+	struct sockaddr_in6 *sin6;
+
+	s = strdup(str);
+	if (!s)
+		errx(1, "pfctl_parse_host: strdup");
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_DGRAM; /* dummy */
+	hints.ai_flags = AI_NUMERICHOST;
+
+	if ((sbs = strchr(s, '[')) != NULL && (sbe = strrchr(s, ']')) != NULL) {
+		hints.ai_family = AF_INET6;
+		*(sbs++) = *sbe = '\0';
+	} else if ((sbs = strchr(s, ':')) != NULL) {
+		hints.ai_family = AF_INET;
+		*(sbs++) = '\0';
+	} else
+		goto error;
+
+	if (getaddrinfo(s, sbs, &hints, &ai) != 0)
+		goto error;
+
+	switch (ai->ai_family) {
+	case AF_INET:
+		sin4 = (struct sockaddr_in *)ai->ai_addr;
+		addr->addr.v.a.addr.v4 = sin4->sin_addr;
+		addr->port[0] = sin4->sin_port;
+		break;
+
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)ai->ai_addr;
+		addr->addr.v.a.addr.v6 = sin6->sin6_addr;
+		addr->port[0] = sin6->sin6_port;
+		break;
+	}
+	freeaddrinfo(ai);
+	free(s);
+
+	memset(&addr->addr.v.a.mask, 0xff, sizeof(struct pf_addr));
+	addr->port_op = PF_OP_EQ;
+	addr->addr.type = PF_ADDR_ADDRMASK;
+
+	return (0);
+
+error:
+	free(s);
+	return (-1);
+}
+
 int
 pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr,
     u_int32_t ticket, int r_action, const char *anchorname, int which)
@@ -3282,6 +3401,8 @@ main(int argc, char *argv[])
 			pfctl_id_kill_states(dev, ifaceopt, opts);
 		else if (!strcmp(state_kill[0], "gateway"))
 			pfctl_gateway_kill_states(dev, ifaceopt, opts);
+		else if (!strcmp(state_kill[0], "key"))
+			pfctl_key_kill_states(dev, ifaceopt, opts);
 		else
 			pfctl_net_kill_states(dev, ifaceopt, opts);
 	}