Skip to content

Commit

Permalink
pf: Support killing 'matching' states
Browse files Browse the repository at this point in the history
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:	pfsense/FreeBSD-src#11
MFC after:	1 week
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D30092
  • Loading branch information
kprovost committed May 7, 2021
1 parent c2e11d8 commit 93abcf1
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/libpfctl/libpfctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
nvlist_add_string(nvl, "ifname", kill->ifname);
nvlist_add_string(nvl, "label", kill->label);
nvlist_add_bool(nvl, "kill_match", kill->kill_match);

nv.data = nvlist_pack(nvl, &nv.len);
nv.size = nv.len;
Expand Down
1 change: 1 addition & 0 deletions lib/libpfctl/libpfctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ struct pfctl_kill {
struct pf_rule_addr rt_addr;
char ifname[IFNAMSIZ];
char label[PF_RULE_LABEL_SIZE];
bool kill_match;
};

int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket,
Expand Down
13 changes: 12 additions & 1 deletion sbin/pfctl/pfctl.8
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
.Sh SYNOPSIS
.Nm pfctl
.Bk -words
.Op Fl AdeghmNnOPqRrvz
.Op Fl AdeghMmNnOPqRrvz
.Op Fl a Ar anchor
.Oo Fl D Ar macro Ns =
.Ar value Oc
Expand Down Expand Up @@ -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:
.Pp
.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
Merge in explicitly given options without resetting those
which are omitted.
Expand Down
23 changes: 21 additions & 2 deletions sbin/pfctl/pfctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ usage(void)
extern char *__progname;

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[-k host | network | gateway | label | id] [-o level] [-p device]\n"
"\t[-s modifier] [-t table -T command [address ...]] [-x level]\n",
Expand Down Expand Up @@ -478,6 +478,9 @@ pfctl_clear_iface_states(int dev, const char *iface, int opts)
sizeof(kill.ifname)) >= sizeof(kill.ifname))
errx(1, "invalid interface: %s", iface);

if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;

if (pfctl_clear_states(dev, &kill, &killed))
err(1, "DIOCCLRSTATES");
if ((opts & PF_OPT_QUIET) == 0)
Expand Down Expand Up @@ -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);

if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;

if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) {
errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
/* NOTREACHED */
Expand Down Expand Up @@ -768,6 +774,9 @@ pfctl_gateway_kill_states(int dev, const char *iface, int opts)
sizeof(kill.ifname)) >= sizeof(kill.ifname))
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);

if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) {
Expand Down Expand Up @@ -821,6 +830,9 @@ pfctl_label_kill_states(int dev, const char *iface, int opts)
sizeof(kill.ifname)) >= sizeof(kill.ifname))
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)) >=
sizeof(kill.label))
errx(1, "label too long: %s", state_kill[1]);
Expand All @@ -846,6 +858,10 @@ pfctl_id_kill_states(int dev, const char *iface, int opts)
}

memset(&kill, 0, sizeof(kill));

if (opts & PF_OPT_KILLMATCH)
kill.kill_match = true;

if ((sscanf(state_kill[1], "%jx/%x",
&kill.cmp.id, &kill.cmp.creatorid)) == 2)
HTONL(kill.cmp.creatorid);
Expand Down Expand Up @@ -2199,7 +2215,7 @@ main(int argc, char *argv[])
usage();

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) {
case 'a':
anchoropt = optarg;
Expand Down Expand Up @@ -2252,6 +2268,9 @@ main(int argc, char *argv[])
case 'm':
opts |= PF_OPT_MERGE;
break;
case 'M':
opts |= PF_OPT_KILLMATCH;
break;
case 'n':
opts |= PF_OPT_NOACTION;
break;
Expand Down
1 change: 1 addition & 0 deletions sbin/pfctl/pfctl_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#define PF_OPT_NUMERIC 0x1000
#define PF_OPT_MERGE 0x2000
#define PF_OPT_RECURSE 0x4000
#define PF_OPT_KILLMATCH 0x8000

#define PF_TH_ALL 0xFF

Expand Down
1 change: 1 addition & 0 deletions sys/net/pfvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,7 @@ struct pf_kstate_kill {
char psk_ifname[IFNAMSIZ];
char psk_label[PF_RULE_LABEL_SIZE];
u_int psk_killed;
bool psk_kill_match;
};
#endif

Expand Down
108 changes: 94 additions & 14 deletions sys/netpfil/pf/pf_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2467,6 +2467,8 @@ pf_nvstate_kill_to_kstate_kill(const nvlist_t *nvl,
sizeof(kill->psk_ifname)));
PFNV_CHK(pf_nvstring(nvl, "label", 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:
return (error);
Expand Down Expand Up @@ -2644,13 +2646,33 @@ pf_label_match(const struct pf_krule *rule, const char *label)
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
pf_killstates_row(struct pf_kstate_kill *psk, struct pf_idhash *ih)
{
struct pf_state *s;
struct pf_state_key *sk;
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;

relock_DIOCKILLSTATES:
Expand Down Expand Up @@ -2707,8 +2729,36 @@ pf_killstates_row(struct pf_kstate_kill *psk, struct pf_idhash *ih)
s->kif->pfik_name))
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);
killed++;

if (psk->psk_kill_match)
killed += pf_kill_matching_state(&match_key, dir);

goto relock_DIOCKILLSTATES;
}
PF_HASHROW_UNLOCK(ih);
Expand Down Expand Up @@ -5442,27 +5492,57 @@ pf_keepcounters(struct pfioc_nv *nv)
static unsigned int
pf_clear_states(const struct pf_kstate_kill *kill)
{
struct pf_state_key_cmp match_key;
struct pf_state *s;
unsigned int killed = 0;
int idx;
unsigned int killed = 0, dir;

for (unsigned int i = 0; i <= pf_hashmask; i++) {
struct pf_idhash *ih = &V_pf_idhash[i];

relock_DIOCCLRSTATES:
PF_HASHROW_LOCK(ih);
LIST_FOREACH(s, &ih->states, entry)
if (!kill->psk_ifname[0] ||
!strcmp(kill->psk_ifname,
s->kif->pfik_name)) {
/*
* Don't send out individual
* delete messages.
*/
s->state_flags |= PFSTATE_NOSYNC;
pf_unlink_state(s, PF_ENTER_LOCKED);
killed++;
goto relock_DIOCCLRSTATES;
LIST_FOREACH(s, &ih->states, entry) {
if (kill->psk_ifname[0] &&
strcmp(kill->psk_ifname,
s->kif->pfik_name))
continue;

if (kill->psk_kill_match) {
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];
}

/*
* 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);
}

Expand Down

0 comments on commit 93abcf1

Please sign in to comment.