diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -194,6 +194,7 @@ 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, diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -645,6 +645,7 @@ 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; diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8 --- a/sbin/pfctl/pfctl.8 +++ b/sbin/pfctl/pfctl.8 @@ -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 @@ -331,6 +331,17 @@ 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. diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -245,7 +245,7 @@ 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", @@ -478,6 +478,9 @@ 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) @@ -661,6 +664,9 @@ 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 */ @@ -768,6 +774,9 @@ 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))) { @@ -821,6 +830,9 @@ 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]); @@ -846,6 +858,10 @@ } 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); @@ -2199,7 +2215,7 @@ 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; @@ -2252,6 +2268,9 @@ case 'm': opts |= PF_OPT_MERGE; break; + case 'M': + opts |= PF_OPT_KILLMATCH; + break; case 'n': opts |= PF_OPT_NOACTION; break; diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -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 diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1085,6 +1085,7 @@ char psk_ifname[IFNAMSIZ]; char psk_label[PF_RULE_LABEL_SIZE]; u_int psk_killed; + bool psk_kill_match; }; #endif diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -2467,6 +2467,8 @@ 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); @@ -2644,13 +2646,33 @@ 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: @@ -2707,8 +2729,36 @@ 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); @@ -5442,27 +5492,57 @@ 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); }