From: Avinash Duduskar Subject: authpf: kill states in the session's routing domain To: tech@openbsd.org Date: Sun, 19 Jul 2026 23:20:01 +0530 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 #include #include +#include #include #include #include #include #include +#include #include #include @@ -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)