diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c index 346e6236f32..40fceeecb86 100644 --- a/sbin/pfctl/pf_print_state.c +++ b/sbin/pfctl/pf_print_state.c @@ -352,7 +352,10 @@ print_state(struct pfsync_state *s, int opts) bcopy(&s->id, &id, sizeof(u_int64_t)); printf(" id: %016jx creatorid: %08x", (uintmax_t )be64toh(id), ntohl(s->creatorid)); + printf(" gateway: "); + print_host(&s->rt_addr, 0, s->af, opts); printf("\n"); + } } diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8 index 07e99ae9f4d..55e24e7d362 100644 --- 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 @@ -45,7 +45,7 @@ .Op Fl K Ar host | network .Xo .Oo Fl k -.Ar host | network | label | id +.Ar host | network | label | id | gateway .Oc Xc .Op Fl o Ar level .Op Fl p Ar device @@ -256,14 +256,15 @@ option may be specified, which will kill all the source tracking entries from the first host/network to the second. .It Xo .Fl k -.Ar host | network | label | id +.Ar host | network | label | id | gateway .Xc Kill all of the state entries matching the specified .Ar host , .Ar network , .Ar label , +.Ar id , or -.Ar id . +.Ar gateway. .Pp For example, to kill all of the state entries originating from .Dq host : @@ -317,6 +318,25 @@ To kill a state with ID 4823e84500000018 created from a backup firewall with hostid 00000002 use: .Pp .Dl # pfctl -k id -k 4823e84500000018/2 +.Pp +It is also possible to kill states created from a rule with the route-to/reply-to +parameter set to route the connection through a particular gateway. +Note that rules routing via a the default routing table (not via a route-to +rule) will have their rt_addr set as 0.0.0.0 or ::. To kill all states using +a gateway of 192.168.0.1 use: +.Pp +.Dl # pfctl -k gateway -k 192.168.0.1 +.Pp +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 +.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 .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 index c7a7459d63f..f67798edded 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -74,6 +74,7 @@ int pfctl_clear_states(int, const char *, int); void pfctl_addrprefix(char *, struct pf_addr *); int pfctl_kill_src_nodes(int, const char *, int); int pfctl_net_kill_states(int, const char *, int); +int pfctl_gateway_kill_states(int, const char *, int); int pfctl_label_kill_states(int, const char *, int); int pfctl_kill_schedule(int, const char *, int); int pfctl_id_kill_states(int, const char *, int); @@ -235,7 +236,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 | label | id] [-o level] [-p device]\n" "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", @@ -380,9 +381,14 @@ pfctl_clear_states(int dev, const char *iface, int opts) struct pfioc_state_kill psk; memset(&psk, 0, sizeof(psk)); - if (iface != NULL && strlcpy(psk.psk_ifname, iface, - sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) - errx(1, "invalid interface: %s", iface); + if (iface != NULL) { + if (strlcpy(psk.psk_ifname, iface, + sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) { + errx(1, "invalid interface: %s", iface); + } else if (opts & PF_OPT_KILLMATCH) { + psk.psk_flag |= PSK_FLAG_KILLMATCH; + } + } if (ioctl(dev, DIOCCLRSTATES, &psk)) err(1, "DIOCCLRSTATES"); @@ -564,6 +570,10 @@ pfctl_net_kill_states(int dev, const char *iface, int opts) sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); + if (opts & PF_OPT_KILLMATCH) { + psk.psk_flag |= PSK_FLAG_KILLMATCH; + } + pfctl_addrprefix(state_kill[0], &psk.psk_src.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { @@ -650,6 +660,67 @@ pfctl_net_kill_states(int dev, const char *iface, int opts) return (0); } +int +pfctl_gateway_kill_states(int dev, const char *iface, int opts) +{ + struct pfioc_state_kill psk; + struct addrinfo *res, *resp; + struct sockaddr last_src; + int killed; + int ret_ga; + + killed = 0; + + memset(&psk, 0, sizeof(psk)); + memset(&psk.psk_rt_addr.addr.v.a.mask, 0xff, + sizeof(psk.psk_rt_addr.addr.v.a.mask)); + memset(&last_src, 0xff, sizeof(last_src)); + if (iface != NULL && strlcpy(psk.psk_ifname, iface, + sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) + errx(1, "invalid interface: %s", iface); + + if (opts & PF_OPT_KILLMATCH) { + psk.psk_flag |= PSK_FLAG_KILLMATCH; + } + + pfctl_addrprefix(state_kill[1], &psk.psk_rt_addr.addr.v.a.mask); + + if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) { + errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); + /* NOTREACHED */ + } + for (resp = res; resp; resp = resp->ai_next) { + if (resp->ai_addr == NULL) + continue; + /* We get lots of duplicates. Catch the easy ones */ + if (memcmp(&last_src, resp->ai_addr, sizeof(last_src)) == 0) + continue; + last_src = *(struct sockaddr *)resp->ai_addr; + + psk.psk_af = resp->ai_family; + + if (psk.psk_af == AF_INET) + psk.psk_rt_addr.addr.v.a.addr.v4 = + ((struct sockaddr_in *)resp->ai_addr)->sin_addr; + else if (psk.psk_af == AF_INET6) + psk.psk_rt_addr.addr.v.a.addr.v6 = + ((struct sockaddr_in6 *)resp->ai_addr)-> + sin6_addr; + else + errx(1, "Unknown address family %d", psk.psk_af); + + if (ioctl(dev, DIOCKILLSTATES, &psk)) + err(1, "DIOCKILLSTATES"); + killed += psk.psk_killed; + } + + freeaddrinfo(res); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", killed); + return (0); +} + int pfctl_kill_schedule(int dev, const char *sched, int opts) { @@ -679,6 +750,11 @@ pfctl_label_kill_states(int dev, const char *iface, int opts) usage(); } memset(&psk, 0, sizeof(psk)); + + if (opts & PF_OPT_KILLMATCH) { + psk.psk_flag |= PSK_FLAG_KILLMATCH; + } + if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); @@ -707,6 +783,11 @@ pfctl_id_kill_states(int dev, const char *iface, int opts) } memset(&psk, 0, sizeof(psk)); + + if (opts & PF_OPT_KILLMATCH) { + psk.psk_flag |= PSK_FLAG_KILLMATCH; + } + if ((sscanf(state_kill[1], "%jx/%x", &psk.psk_pfcmp.id, &psk.psk_pfcmp.creatorid)) == 2) HTONL(psk.psk_pfcmp.creatorid); @@ -2027,7 +2108,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:y:z")) != -1) { + "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:t:T:vx:y:z")) != -1) { switch (ch) { case 'a': anchoropt = optarg; @@ -2080,6 +2161,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; @@ -2348,6 +2432,8 @@ main(int argc, char *argv[]) pfctl_label_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "id")) pfctl_id_kill_states(dev, ifaceopt, opts); + else if (!strcmp(state_kill[0], "gateway")) + pfctl_gateway_kill_states(dev, ifaceopt, opts); else pfctl_net_kill_states(dev, ifaceopt, opts); } diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index 2b7fea7bf7a..1c17d2e10f2 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -51,6 +51,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/share/man/man4/pf.4 b/share/man/man4/pf.4 index 6b169435b4a..f5e925c3629 100644 --- a/share/man/man4/pf.4 +++ b/share/man/man4/pf.4 @@ -338,6 +338,9 @@ structure from the state table. Remove matching entries from the state table. This ioctl returns the number of killed states in .Va psk_killed . +The psk_flag can be set with PSK_FLAG_KILLMATCH to also look +for and kill a matching state in the opposite direction for +each state matching the original criteria. .Bd -literal struct pfioc_state_kill { struct pf_state_cmp psk_pfcmp; @@ -345,8 +348,10 @@ struct pfioc_state_kill { int psk_proto; struct pf_rule_addr psk_src; struct pf_rule_addr psk_dst; + struct pf_rule_addr psk_rt_addr; char psk_ifname[IFNAMSIZ]; char psk_label[PF_RULE_LABEL_SIZE]; + int psk_flag; u_int psk_killed; }; .Ed @@ -358,8 +363,9 @@ but ignores the .Va psk_af , .Va psk_proto , .Va psk_src , +.Va psk_dst , and -.Va psk_dst +.Va psk_rt_addr fields of the .Vt pfioc_state_kill structure. diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index e606440642a..a991fe375fb 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1309,11 +1309,15 @@ struct pfioc_state_kill { int psk_proto; struct pf_rule_addr psk_src; struct pf_rule_addr psk_dst; + struct pf_rule_addr psk_rt_addr; char psk_ifname[IFNAMSIZ]; char psk_label[PF_RULE_LABEL_SIZE]; + int psk_flag; u_int psk_killed; }; +#define PSK_FLAG_KILLMATCH 0x0001 + struct pfioc_schedule_kill { int numberkilled; char schedule[PF_RULE_LABEL_SIZE]; diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index 8ca81840b5d..529be1a54f9 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -1655,6 +1655,37 @@ relock_DIOCCLRSTATES: * Don't send out individual * delete messages. */ + if (psk->psk_flag & PSK_FLAG_KILLMATCH) { + u_int dir; + struct pf_state *match; + struct pf_state_key_cmp key; + int idx, more = 0; + + bzero(&key, sizeof(key)); + + if (s->direction == PF_OUT) { + dir = PF_IN; + idx = PF_SK_STACK; + } else { + dir = PF_OUT; + idx = PF_SK_WIRE; + } + + key.af = s->key[idx]->af; + key.proto = s->key[idx]->proto; + PF_ACPY(&key.addr[0], &s->key[idx]->addr[1],key.af); + key.port[0] = s->key[idx]->port[1]; + PF_ACPY(&key.addr[1], &s->key[idx]->addr[0],key.af); + key.port[1] = s->key[idx]->port[0]; + + match = pf_find_state_all(&key, dir, &more); + if (match && !more) { + pf_unlink_state(match, 0); + killed++; + } + + } + s->state_flags |= PFSTATE_NOSYNC; pf_unlink_state(s, PF_ENTER_LOCKED); killed++; @@ -1717,6 +1748,10 @@ relock_DIOCKILLSTATES: &psk->psk_dst.addr.v.a.addr, &psk->psk_dst.addr.v.a.mask, dstaddr, sk->af) && + PF_MATCHA(psk->psk_rt_addr.neg, + &psk->psk_rt_addr.addr.v.a.addr, + &psk->psk_rt_addr.addr.v.a.mask, + &s->rt_addr, sk->af) && (psk->psk_src.port_op == 0 || pf_match_port(psk->psk_src.port_op, psk->psk_src.port[0], psk->psk_src.port[1], @@ -1732,6 +1767,36 @@ relock_DIOCKILLSTATES: (!psk->psk_ifname[0] || !strcmp(psk->psk_ifname, s->kif->pfik_name))) { + if (psk->psk_flag & PSK_FLAG_KILLMATCH) { + u_int dir; + struct pf_state *match; + struct pf_state_key_cmp key; + int idx, more = 0; + + bzero(&key, sizeof(key)); + + if (s->direction == PF_OUT) { + dir = PF_IN; + idx = PF_SK_STACK; + } else { + dir = PF_OUT; + idx = PF_SK_WIRE; + } + + key.af = s->key[idx]->af; + key.proto = s->key[idx]->proto; + PF_ACPY(&key.addr[0], &s->key[idx]->addr[1],key.af); + key.port[0] = s->key[idx]->port[1]; + PF_ACPY(&key.addr[1], &s->key[idx]->addr[0],key.af); + key.port[1] = s->key[idx]->port[0]; + + match = pf_find_state_all(&key, dir, &more); + if (match && !more) { + pf_unlink_state(match, 0); + killed++; + } + + } pf_unlink_state(s, PF_ENTER_LOCKED); killed++; goto relock_DIOCKILLSTATES;