Hi,

I'd like to add "key" modifier to "pfctl -k" to delete pf states, like
below:

  # pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123'

This deletion will be very fast if the RB tree of pf state table is
used to find the state.  The diff also includes the diff for that part
(DIOCKILLSTATES of pf_ioctl).

comments?

ok?

Index: sbin/pfctl/pfctl.8
===================================================================
RCS file: /var/cvs/openbsd/src/sbin/pfctl/pfctl.8,v
retrieving revision 1.167
diff -u -p -r1.167 pfctl.8
--- sbin/pfctl/pfctl.8  26 Jan 2017 18:45:12 -0000      1.167
+++ sbin/pfctl/pfctl.8  12 Apr 2017 06:12:39 -0000
@@ -235,6 +235,7 @@ Kill all of the state entries matching t
 .Ar host ,
 .Ar network ,
 .Ar label ,
+.Ar key ,
 or
 .Ar id .
 .Pp
@@ -266,7 +267,7 @@ To kill all states with the target
 .Pp
 .Dl # pfctl -k 0.0.0.0/0 -k host2
 .Pp
-It is also possible to kill states by rule label or state ID.
+It is also possible to kill states by rule label, state key or state ID.
 In this mode the first
 .Fl k
 argument is used to specify the type
@@ -276,6 +277,17 @@ from rules carrying the label
 .Dq foobar :
 .Pp
 .Dl # pfctl -k label -k foobar
+.Pp
+To kill one specific state by its key
+(protocol, host1, port1, direction, host2 and port2 in the same format
+of pfctl -s state),
+use the
+.Ar key
+modifier and as a second argument the state key.
+To kill a state whose protocol is TCP and originating from
+10.0.0.101:32123 to 10.0.0.1:80 use:
+.Pp
+.Dl # pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123'
 .Pp
 To kill one specific state by its unique state ID
 (as shown by pfctl -s state -vv),
Index: sbin/pfctl/pfctl.c
===================================================================
RCS file: /var/cvs/openbsd/src/sbin/pfctl/pfctl.c,v
retrieving revision 1.339
diff -u -p -r1.339 pfctl.c
--- sbin/pfctl/pfctl.c  27 Mar 2017 17:38:09 -0000      1.339
+++ sbin/pfctl/pfctl.c  12 Apr 2017 06:12:40 -0000
@@ -72,6 +72,8 @@ int    pfctl_kill_src_nodes(int, const cha
 int     pfctl_net_kill_states(int, const char *, int, int);
 int     pfctl_label_kill_states(int, const char *, int, int);
 int     pfctl_id_kill_states(int, int);
+int     pfctl_key_kill_states(int, const char *, int, int);
+int     pfctl_parse_host(char *, struct pf_rule_addr *);
 void    pfctl_init_options(struct pfctl *);
 int     pfctl_load_options(struct pfctl *);
 int     pfctl_load_limit(struct pfctl *, unsigned int, unsigned int);
@@ -683,6 +685,122 @@ pfctl_id_kill_states(int dev, int opts)
        return (0);
 }
 
+int
+pfctl_key_kill_states(int dev, const char *iface, int opts, int rdomain)
+{
+       struct pfioc_state_kill psk;
+       char *s, *token, *tokens[4];
+       struct protoent *p;
+       u_int i, sidx, didx;
+
+       if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+               warnx("no key specified");
+               usage();
+       }
+       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);
+
+       psk.psk_rdomain = rdomain;
+
+       s = strdup(state_kill[1]);
+       if (!s)
+               errx(1, "pfctl_key_kill_states: strdup");
+       i = 0;
+       while ((token = strsep(&s, " \t")) != NULL)
+               if (*token != '\0') {
+                       if (i < 4)
+                               tokens[i] = token;
+                       i++;
+               }
+       if (i != 4)
+               errx(1, "pfctl_key_kill_states: key must be \"protocol 
host1:port1 "
+                   "direction host2:port2\" format");
+
+       if ((p = getprotobyname(tokens[0])) == NULL)
+               errx(1, "invalid protocol: %s", tokens[0]);
+       psk.psk_proto = p->p_proto;
+
+       if (strcmp(tokens[2], "->") == 0) {
+               sidx = 1;
+               didx = 3;
+       } else if (strcmp(tokens[2], "<-") == 0) {
+               sidx = 3;
+               didx = 1;
+       } else
+               errx(1, "invalid direction: %s", tokens[2]);
+
+       if (pfctl_parse_host(tokens[sidx], &psk.psk_src) == -1)
+               errx(1, "invalid host: %s", tokens[sidx]);
+       if (pfctl_parse_host(tokens[didx], &psk.psk_dst) == -1)
+               errx(1, "invalid host: %s", tokens[didx]);
+
+       if (ioctl(dev, DIOCKILLSTATES, &psk))
+               err(1, "DIOCKILLSTATES");
+
+       if ((opts & PF_OPT_QUIET) == 0)
+               fprintf(stderr, "killed %d states\n", psk.psk_killed);
+
+       return (0);
+}
+
+int
+pfctl_parse_host(char *str, struct pf_rule_addr *addr)
+{
+       char *s = NULL, *sbs, *sbe;
+       struct addrinfo hints, *ai;
+       struct sockaddr_in *sin4;
+       struct sockaddr_in6 *sin6;
+
+       s = strdup(str);
+       if (!s)
+               errx(1, "pfctl_parse_host: strdup");
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_socktype = SOCK_DGRAM; /* dummy */
+       hints.ai_flags = AI_NUMERICHOST;
+
+       if ((sbs = strchr(s, '[')) != NULL || (sbe = strrchr(s, ']')) != NULL) {
+               hints.ai_family = AF_INET6;
+               *(sbs++) = *sbe = '\0';
+       } else if ((sbs = strchr(s, ':')) != NULL) {
+               hints.ai_family = AF_INET;
+               *(sbs++) = '\0';
+       } else
+               goto error;
+
+       if (getaddrinfo(s, sbs, &hints, &ai) != 0)
+               goto error;
+
+       switch (ai->ai_family) {
+       case AF_INET:
+               sin4 = (struct sockaddr_in *)ai->ai_addr;
+               addr->addr.v.a.addr.v4 = sin4->sin_addr;
+               addr->port[0] = sin4->sin_port;
+               break;
+
+       case AF_INET6:
+               sin6 = (struct sockaddr_in6 *)ai->ai_addr;
+               addr->addr.v.a.addr.v6 = sin6->sin6_addr;
+               addr->port[0] = sin6->sin6_port;
+               break;
+       }
+       freeaddrinfo(ai);
+       free(s);
+
+       memset(&addr->addr.v.a.mask, 0xff, sizeof(struct pf_addr));
+       addr->port_op = PF_OP_EQ;
+       addr->addr.type = PF_ADDR_ADDRMASK;
+
+       return (0);
+
+ error:
+       free(s);
+       return (-1);
+}
+
 void
 pfctl_print_rule_counters(struct pf_rule *rule, int opts)
 {
@@ -2427,6 +2545,8 @@ main(int argc, char *argv[])
                        pfctl_label_kill_states(dev, ifaceopt, opts, rdomain);
                else if (!strcmp(state_kill[0], "id"))
                        pfctl_id_kill_states(dev, opts);
+               else if (!strcmp(state_kill[0], "key"))
+                       pfctl_key_kill_states(dev, ifaceopt, opts, rdomain);
                else
                        pfctl_net_kill_states(dev, ifaceopt, opts, rdomain);
        }

Index: sys/net/pf_ioctl.c
===================================================================
RCS file: /var/cvs/openbsd/src/sys/net/pf_ioctl.c,v
retrieving revision 1.308
diff -u -p -r1.308 pf_ioctl.c
--- sys/net/pf_ioctl.c  17 Mar 2017 17:19:16 -0000      1.308
+++ sys/net/pf_ioctl.c  11 Apr 2017 05:07:18 -0000
@@ -1444,11 +1444,14 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
 
        case DIOCKILLSTATES: {
                struct pf_state         *s, *nexts;
-               struct pf_state_key     *sk;
+               struct pf_state_item    *si, *sit;
+               struct pf_state_key     *sk, key;
                struct pf_addr          *srcaddr, *dstaddr;
                u_int16_t                srcport, dstport;
                struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr;
-               u_int                    killed = 0;
+               u_int                    i, killed = 0;
+               const int                dirs[] = { PF_IN, PF_OUT };
+               int                      sidx, didx;
 
                if (psk->psk_pfcmp.id) {
                        if (psk->psk_pfcmp.creatorid == 0)
@@ -1458,6 +1461,62 @@ pfioctl(dev_t dev, u_long cmd, caddr_t a
                                psk->psk_killed = 1;
                        }
                        break;
+               }
+
+               if (psk->psk_af && psk->psk_proto &&
+                   psk->psk_src.port_op == PF_OP_EQ &&
+                   psk->psk_dst.port_op == PF_OP_EQ) {
+
+                       key.af = psk->psk_af;
+                       key.proto = psk->psk_proto;
+                       key.rdomain = psk->psk_rdomain;
+
+                       for (i = 0; i < nitems(dirs); i++) {
+                               if (dirs[i] == PF_IN) {
+                                       sidx = 0;
+                                       didx = 1;
+                               } else {
+                                       sidx = 1;
+                                       didx = 0;
+                               }
+                               PF_ACPY(&key.addr[sidx],
+                                   &psk->psk_src.addr.v.a.addr, key.af);
+                               PF_ACPY(&key.addr[didx],
+                                   &psk->psk_dst.addr.v.a.addr, key.af);
+                               key.port[sidx] = psk->psk_src.port[0];
+                               key.port[didx] = psk->psk_dst.port[0];
+
+                               sk = RB_FIND(pf_state_tree, &pf_statetbl, &key);
+                               if (sk == NULL)
+                                       continue;
+
+                               TAILQ_FOREACH_SAFE(si, &sk->states, entry, sit)
+                                       if (((si->s->key[PF_SK_WIRE]->af ==
+                                           si->s->key[PF_SK_STACK]->af &&
+                                           sk == (dirs[i] == PF_IN ?
+                                           si->s->key[PF_SK_WIRE] :
+                                           si->s->key[PF_SK_STACK])) ||
+                                           (si->s->key[PF_SK_WIRE]->af !=
+                                           si->s->key[PF_SK_STACK]->af &&
+                                           dirs[i] == PF_IN &&
+                                           (sk == si->s->key[PF_SK_STACK] ||
+                                           sk == si->s->key[PF_SK_WIRE]))) &&
+                                           (!psk->psk_ifname[0] ||
+                                           (si->s->kif != pfi_all &&
+                                           !strcmp(psk->psk_ifname,
+                                           si->s->kif->pfik_name))) &&
+                                           (!psk->psk_label[0] ||
+                                           (si->s->rule.ptr->label[0] &&
+                                           !strcmp(psk->psk_label,
+                                           si->s->rule.ptr->label)))) {
+                                               pf_remove_state(si->s);
+                                               killed++;
+                                       }
+                       }
+                       if (killed) {
+                               psk->psk_killed = killed;
+                               break;
+                       }
                }
 
                for (s = RB_MIN(pf_state_tree_id, &tree_id); s;

Reply via email to