Index | Thread | Search

From:
Avinash Duduskar <avinash.duduskar@gmail.com>
Subject:
authpf: kill states in the session's routing domain
To:
tech@openbsd.org
Date:
Sun, 19 Jul 2026 23:20:01 +0530

Download raw body.

Thread
  • Avinash Duduskar:

    authpf: kill states in the session's routing domain

authpf_kill_states() leaves psk_rdomain set to 0, but DIOCKILLSTATES
matches the routing domain exactly.  Unlike psk_af and psk_proto, it has
no "0 means any" escape.  A session served in a non-default routing
domain, whether through sshd's RDomain or a login.conf rtable, creates
its states in that domain, so the logout kill matches nothing: the
client keeps its states, and the access they carry, until they expire
(tcp.established defaults to 24 hours, and traffic keeps resetting it).

Set psk_rdomain to the domain authpf is running in.  getrtable() gives
the process's routing table, which the patch maps to its routing domain
via the NET_RT_TABLE sysctl, as bgpd's ktable_exists() does.  The raw
table id would miss a policy-routing table, which lives in rdomain 0.

Tested on -current, one machine, pair(4) in rdomains: client in
rdomain 2, gateway side pair0 in rdomain 1, sshd with "ListenAddress
0.0.0.0 rdomain 1" and "RDomain %D".  Before the diff a clean logout
leaves the client's state:

	# pfctl -ss | grep 10.1.0.1:8000
	all tcp (1) 10.1.0.1:8000 <- (1) 10.1.0.2:23035  ESTABLISHED:ESTABLISHED

(the "(1)" is the state's rdomain).  The authpf_users entry is added
and removed either way, so teardown ran; only the kill differs.  With
the diff the same logout kills the state, and reverting it brings the
survival back.  A login.conf policy rtable and plain rdomain 0 both
still kill with the diff.

diff --git a/usr.sbin/authpf/authpf.c b/usr.sbin/authpf/authpf.c
index bc410c0631c..4c50a86b69e 100644
--- a/usr.sbin/authpf/authpf.c
+++ b/usr.sbin/authpf/authpf.c
@@ -20,12 +20,14 @@
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/sysctl.h>
 #include <sys/wait.h>
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <net/if.h>
 #include <net/pfvar.h>
+#include <net/route.h>
 
 #include <err.h>
 #include <errno.h>
@@ -51,6 +53,7 @@ static int	remove_stale_rulesets(void);
 static int	recursive_ruleset_purge(char *, char *);
 static int	change_filter(int, const char *, const char *);
 static int	change_table(int, const char *);
+static u_int	authpf_rdomain(void);
 static void	authpf_kill_states(void);
 
 int	dev;			/* pf device */
@@ -889,6 +892,32 @@ change_table(int add, const char *ipsrc)
 	return (0);
 }
 
+/*
+ * Map the process routing table to its routing domain; a policy-routing
+ * rtable lives in rdomain 0.
+ */
+static u_int
+authpf_rdomain(void)
+{
+	struct rt_tableinfo	info;
+	int			mib[6];
+	size_t			len = sizeof(info);
+	int			rtable = getrtable();
+
+	mib[0] = CTL_NET;
+	mib[1] = PF_ROUTE;
+	mib[2] = 0;
+	mib[3] = 0;
+	mib[4] = NET_RT_TABLE;
+	mib[5] = rtable;
+
+	if (sysctl(mib, 6, &info, &len, NULL, 0) == -1) {
+		syslog(LOG_ERR, "sysctl NET_RT_TABLE: %m");
+		return (rtable);
+	}
+	return (info.rti_domainid);
+}
+
 /*
  * This is to kill off states that would otherwise be left behind stateful
  * rules. This means we don't need to allow in more traffic than we really
@@ -905,6 +934,12 @@ authpf_kill_states(void)
 	memset(&psk, 0, sizeof(psk));
 	memset(&target, 0, sizeof(target));
 
+	/*
+	 * DIOCKILLSTATES matches psk_rdomain exactly, so kill in the
+	 * routing domain the session runs in, not just rdomain 0.
+	 */
+	psk.psk_rdomain = authpf_rdomain();
+
 	if (inet_pton(AF_INET, ipsrc, &target.v4) == 1)
 		psk.psk_af = AF_INET;
 	else if (inet_pton(AF_INET6, ipsrc, &target.v6) == 1)