pf: Support killing 'matching' states

Optionally also kill states that match (i.e. are the NATed state or
opposite direction state entry for) the state we're killing.

See also https://redmine.pfsense.org/issues/8555

Submitted by:	Steven Brown
Reviewed by:	bcr (man page)
Obtained from:	https://github.com/pfsense/FreeBSD-src/pull/11/
MFC after:	1 week
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D30092
This commit is contained in:
Kristof Provost 2021-05-03 15:35:50 +02:00
parent c2e11d81d2
commit 93abcf17e6
7 changed files with 131 additions and 17 deletions

View File

@ -645,6 +645,7 @@ _pfctl_clear_states(int dev, const struct pfctl_kill *kill,
pfctl_nv_add_rule_addr(nvl, "rt_addr", &kill->rt_addr); pfctl_nv_add_rule_addr(nvl, "rt_addr", &kill->rt_addr);
nvlist_add_string(nvl, "ifname", kill->ifname); nvlist_add_string(nvl, "ifname", kill->ifname);
nvlist_add_string(nvl, "label", kill->label); nvlist_add_string(nvl, "label", kill->label);
nvlist_add_bool(nvl, "kill_match", kill->kill_match);
nv.data = nvlist_pack(nvl, &nv.len); nv.data = nvlist_pack(nvl, &nv.len);
nv.size = nv.len; nv.size = nv.len;

View File

@ -194,6 +194,7 @@ struct pfctl_kill {
struct pf_rule_addr rt_addr; struct pf_rule_addr rt_addr;
char ifname[IFNAMSIZ]; char ifname[IFNAMSIZ];
char label[PF_RULE_LABEL_SIZE]; char label[PF_RULE_LABEL_SIZE];
bool kill_match;
}; };
int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket,

View File

@ -35,7 +35,7 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm pfctl .Nm pfctl
.Bk -words .Bk -words
.Op Fl AdeghmNnOPqRrvz .Op Fl AdeghMmNnOPqRrvz
.Op Fl a Ar anchor .Op Fl a Ar anchor
.Oo Fl D Ar macro Ns = .Oo Fl D Ar macro Ns =
.Ar value Oc .Ar value Oc
@ -331,6 +331,17 @@ A network prefix length can also be specified.
To kill all states using a gateway in 192.168.0.0/24: To kill all states using a gateway in 192.168.0.0/24:
.Pp .Pp
.Dl # pfctl -k gateway -k 192.168.0.0/24 .Dl # pfctl -k gateway -k 192.168.0.0/24
.Pp
.It Fl M
Kill matching states in the opposite direction (on other interfaces) when
killing states.
This applies to states killed using the -k option and also will apply to the
flush command when flushing states.
This is useful when an interface is specified when flushing states.
Example:
.Pp
.Dl # pfctl -M -i interface -Fs
.Pp
.It Fl m .It Fl m
Merge in explicitly given options without resetting those Merge in explicitly given options without resetting those
which are omitted. which are omitted.

View File

@ -245,7 +245,7 @@ usage(void)
extern char *__progname; extern char *__progname;
fprintf(stderr, fprintf(stderr,
"usage: %s [-AdeghmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n" "usage: %s [-AdeghMmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n"
"\t[-f file] [-i interface] [-K host | network]\n" "\t[-f file] [-i interface] [-K host | network]\n"
"\t[-k host | network | gateway | label | id] [-o level] [-p device]\n" "\t[-k host | network | gateway | label | id] [-o level] [-p device]\n"
"\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n",
@ -478,6 +478,9 @@ pfctl_clear_iface_states(int dev, const char *iface, int opts)
sizeof(kill.ifname)) >= sizeof(kill.ifname)) sizeof(kill.ifname)) >= sizeof(kill.ifname))
errx(1, "invalid interface: %s", iface); errx(1, "invalid interface: %s", iface);
if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;
if (pfctl_clear_states(dev, &kill, &killed)) if (pfctl_clear_states(dev, &kill, &killed))
err(1, "DIOCCLRSTATES"); err(1, "DIOCCLRSTATES");
if ((opts & PF_OPT_QUIET) == 0) if ((opts & PF_OPT_QUIET) == 0)
@ -661,6 +664,9 @@ pfctl_net_kill_states(int dev, const char *iface, int opts)
pfctl_addrprefix(state_kill[0], &kill.src.addr.v.a.mask); pfctl_addrprefix(state_kill[0], &kill.src.addr.v.a.mask);
if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;
if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) {
errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
/* NOTREACHED */ /* NOTREACHED */
@ -768,6 +774,9 @@ pfctl_gateway_kill_states(int dev, const char *iface, int opts)
sizeof(kill.ifname)) >= sizeof(kill.ifname)) sizeof(kill.ifname)) >= sizeof(kill.ifname))
errx(1, "invalid interface: %s", iface); errx(1, "invalid interface: %s", iface);
if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;
pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask); pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask);
if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) { if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) {
@ -821,6 +830,9 @@ pfctl_label_kill_states(int dev, const char *iface, int opts)
sizeof(kill.ifname)) >= sizeof(kill.ifname)) sizeof(kill.ifname)) >= sizeof(kill.ifname))
errx(1, "invalid interface: %s", iface); errx(1, "invalid interface: %s", iface);
if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;
if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >= if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >=
sizeof(kill.label)) sizeof(kill.label))
errx(1, "label too long: %s", state_kill[1]); errx(1, "label too long: %s", state_kill[1]);
@ -846,6 +858,10 @@ pfctl_id_kill_states(int dev, const char *iface, int opts)
} }
memset(&kill, 0, sizeof(kill)); memset(&kill, 0, sizeof(kill));
if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;
if ((sscanf(state_kill[1], "%jx/%x", if ((sscanf(state_kill[1], "%jx/%x",
&kill.cmp.id, &kill.cmp.creatorid)) == 2) &kill.cmp.id, &kill.cmp.creatorid)) == 2)
HTONL(kill.cmp.creatorid); HTONL(kill.cmp.creatorid);
@ -2199,7 +2215,7 @@ main(int argc, char *argv[])
usage(); usage();
while ((ch = getopt(argc, argv, while ((ch = getopt(argc, argv,
"a:AdD:eqf:F:ghi:k:K:mnNOo:Pp:rRs:t:T:vx:z")) != -1) { "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:t:T:vx:z")) != -1) {
switch (ch) { switch (ch) {
case 'a': case 'a':
anchoropt = optarg; anchoropt = optarg;
@ -2252,6 +2268,9 @@ main(int argc, char *argv[])
case 'm': case 'm':
opts |= PF_OPT_MERGE; opts |= PF_OPT_MERGE;
break; break;
case 'M':
opts |= PF_OPT_KILLMATCH;
break;
case 'n': case 'n':
opts |= PF_OPT_NOACTION; opts |= PF_OPT_NOACTION;
break; break;

View File

@ -55,6 +55,7 @@
#define PF_OPT_NUMERIC 0x1000 #define PF_OPT_NUMERIC 0x1000
#define PF_OPT_MERGE 0x2000 #define PF_OPT_MERGE 0x2000
#define PF_OPT_RECURSE 0x4000 #define PF_OPT_RECURSE 0x4000
#define PF_OPT_KILLMATCH 0x8000
#define PF_TH_ALL 0xFF #define PF_TH_ALL 0xFF

View File

@ -1085,6 +1085,7 @@ struct pf_kstate_kill {
char psk_ifname[IFNAMSIZ]; char psk_ifname[IFNAMSIZ];
char psk_label[PF_RULE_LABEL_SIZE]; char psk_label[PF_RULE_LABEL_SIZE];
u_int psk_killed; u_int psk_killed;
bool psk_kill_match;
}; };
#endif #endif

View File

@ -2467,6 +2467,8 @@ pf_nvstate_kill_to_kstate_kill(const nvlist_t *nvl,
sizeof(kill->psk_ifname))); sizeof(kill->psk_ifname)));
PFNV_CHK(pf_nvstring(nvl, "label", kill->psk_label, PFNV_CHK(pf_nvstring(nvl, "label", kill->psk_label,
sizeof(kill->psk_label))); sizeof(kill->psk_label)));
if (nvlist_exists_bool(nvl, "kill_match"))
kill->psk_kill_match = nvlist_get_bool(nvl, "kill_match");
errout: errout:
return (error); return (error);
@ -2644,13 +2646,33 @@ pf_label_match(const struct pf_krule *rule, const char *label)
return (false); return (false);
} }
static unsigned int
pf_kill_matching_state(struct pf_state_key_cmp *key, int dir)
{
struct pf_state *match;
int more = 0;
unsigned int killed = 0;
/* Call with unlocked hashrow */
match = pf_find_state_all(key, dir, &more);
if (match && !more) {
pf_unlink_state(match, 0);
killed++;
}
return (killed);
}
static int static int
pf_killstates_row(struct pf_kstate_kill *psk, struct pf_idhash *ih) pf_killstates_row(struct pf_kstate_kill *psk, struct pf_idhash *ih)
{ {
struct pf_state *s; struct pf_state *s;
struct pf_state_key *sk; struct pf_state_key *sk;
struct pf_addr *srcaddr, *dstaddr; struct pf_addr *srcaddr, *dstaddr;
int killed = 0; struct pf_state_key_cmp match_key;
int idx, killed = 0;
unsigned int dir;
u_int16_t srcport, dstport; u_int16_t srcport, dstport;
relock_DIOCKILLSTATES: relock_DIOCKILLSTATES:
@ -2707,8 +2729,36 @@ pf_killstates_row(struct pf_kstate_kill *psk, struct pf_idhash *ih)
s->kif->pfik_name)) s->kif->pfik_name))
continue; continue;
if (psk->psk_kill_match) {
/* Create the key to find matching states, with lock
* held. */
bzero(&match_key, sizeof(match_key));
if (s->direction == PF_OUT) {
dir = PF_IN;
idx = PF_SK_STACK;
} else {
dir = PF_OUT;
idx = PF_SK_WIRE;
}
match_key.af = s->key[idx]->af;
match_key.proto = s->key[idx]->proto;
PF_ACPY(&match_key.addr[0],
&s->key[idx]->addr[1], match_key.af);
match_key.port[0] = s->key[idx]->port[1];
PF_ACPY(&match_key.addr[1],
&s->key[idx]->addr[0], match_key.af);
match_key.port[1] = s->key[idx]->port[0];
}
pf_unlink_state(s, PF_ENTER_LOCKED); pf_unlink_state(s, PF_ENTER_LOCKED);
killed++; killed++;
if (psk->psk_kill_match)
killed += pf_kill_matching_state(&match_key, dir);
goto relock_DIOCKILLSTATES; goto relock_DIOCKILLSTATES;
} }
PF_HASHROW_UNLOCK(ih); PF_HASHROW_UNLOCK(ih);
@ -5442,27 +5492,57 @@ pf_keepcounters(struct pfioc_nv *nv)
static unsigned int static unsigned int
pf_clear_states(const struct pf_kstate_kill *kill) pf_clear_states(const struct pf_kstate_kill *kill)
{ {
struct pf_state_key_cmp match_key;
struct pf_state *s; struct pf_state *s;
unsigned int killed = 0; int idx;
unsigned int killed = 0, dir;
for (unsigned int i = 0; i <= pf_hashmask; i++) { for (unsigned int i = 0; i <= pf_hashmask; i++) {
struct pf_idhash *ih = &V_pf_idhash[i]; struct pf_idhash *ih = &V_pf_idhash[i];
relock_DIOCCLRSTATES: relock_DIOCCLRSTATES:
PF_HASHROW_LOCK(ih); PF_HASHROW_LOCK(ih);
LIST_FOREACH(s, &ih->states, entry) LIST_FOREACH(s, &ih->states, entry) {
if (!kill->psk_ifname[0] || if (kill->psk_ifname[0] &&
!strcmp(kill->psk_ifname, strcmp(kill->psk_ifname,
s->kif->pfik_name)) { s->kif->pfik_name))
/* continue;
* Don't send out individual
* delete messages. if (kill->psk_kill_match) {
*/ bzero(&match_key, sizeof(match_key));
s->state_flags |= PFSTATE_NOSYNC;
pf_unlink_state(s, PF_ENTER_LOCKED); if (s->direction == PF_OUT) {
killed++; dir = PF_IN;
goto relock_DIOCCLRSTATES; idx = PF_SK_STACK;
} else {
dir = PF_OUT;
idx = PF_SK_WIRE;
}
match_key.af = s->key[idx]->af;
match_key.proto = s->key[idx]->proto;
PF_ACPY(&match_key.addr[0],
&s->key[idx]->addr[1], match_key.af);
match_key.port[0] = s->key[idx]->port[1];
PF_ACPY(&match_key.addr[1],
&s->key[idx]->addr[0], match_key.af);
match_key.port[1] = s->key[idx]->port[0];
} }
/*
* Don't send out individual
* delete messages.
*/
s->state_flags |= PFSTATE_NOSYNC;
pf_unlink_state(s, PF_ENTER_LOCKED);
killed++;
if (kill->psk_kill_match)
killed += pf_kill_matching_state(&match_key,
dir);
goto relock_DIOCCLRSTATES;
}
PF_HASHROW_UNLOCK(ih); PF_HASHROW_UNLOCK(ih);
} }