From bc941291473d8a2164e4ffc3f3e7e6a356cbe747 Mon Sep 17 00:00:00 2001 From: Kristof Provost Date: Mon, 10 May 2021 16:51:38 +0200 Subject: [PATCH] pfctl: Use DIOCGETSTATESNV Migrate to using the new nvlist-based DIOCGETSTATESNV call to obtain the states list. MFC after: 1 week Sponsored by: Rubicon Communications, LLC ("Netgate") Differential Revision: https://reviews.freebsd.org/D30244 --- lib/libpfctl/libpfctl.c | 173 ++++++++++++++++++++++++++++++++++++ lib/libpfctl/libpfctl.h | 60 +++++++++++++ sbin/pfctl/pf_print_state.c | 82 +++++++++-------- sbin/pfctl/pfctl.c | 49 +++------- sbin/pfctl/pfctl.h | 4 +- 5 files changed, 289 insertions(+), 79 deletions(-) diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index 8271d9bab3df..6a6ecd8fb136 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -627,6 +627,179 @@ pfctl_nv_add_state_cmp(nvlist_t *nvl, const char *name, nvlist_add_nvlist(nvl, name, nv); } +static void +pf_nvstate_scrub_to_state_scrub(const nvlist_t *nvl, + struct pfctl_state_scrub *scrub) +{ + bzero(scrub, sizeof(*scrub)); + + scrub->timestamp = nvlist_get_bool(nvl, "timestamp"); + scrub->ttl = nvlist_get_number(nvl, "ttl"); + scrub->ts_mod = nvlist_get_number(nvl, "ts_mod"); +} + +static void +pf_nvstate_peer_to_state_peer(const nvlist_t *nvl, + struct pfctl_state_peer *peer) +{ + bzero(peer, sizeof(*peer)); + + if (nvlist_exists_nvlist(nvl, "scrub")) { + peer->scrub = malloc(sizeof(*peer->scrub)); + pf_nvstate_scrub_to_state_scrub( + nvlist_get_nvlist(nvl, "scrub"), + peer->scrub); + } + + peer->seqlo = nvlist_get_number(nvl, "seqlo"); + peer->seqhi = nvlist_get_number(nvl, "seqhi"); + peer->seqdiff = nvlist_get_number(nvl, "seqdiff"); + peer->max_win = nvlist_get_number(nvl, "max_win"); + peer->mss = nvlist_get_number(nvl, "mss"); + peer->state = nvlist_get_number(nvl, "state"); + peer->wscale = nvlist_get_number(nvl, "wscale"); +} + +static void +pf_nvstate_key_to_state_key(const nvlist_t *nvl, struct pfctl_state_key *key) +{ + const nvlist_t * const *tmp; + size_t count; + + bzero(key, sizeof(*key)); + + tmp = nvlist_get_nvlist_array(nvl, "addr", &count); + assert(count == 2); + + for (int i = 0; i < 2; i++) + pf_nvaddr_to_addr(tmp[i], &key->addr[i]); + + pf_nvuint_16_array(nvl, "port", 2, key->port, NULL); + + key->af = nvlist_get_number(nvl, "af"); + key->proto = nvlist_get_number(nvl, "proto"); +} + +static void +pf_nvstate_to_state(const nvlist_t *nvl, struct pfctl_state *s) +{ + bzero(s, sizeof(*s)); + + s->id = nvlist_get_number(nvl, "id"); + s->creatorid = nvlist_get_number(nvl, "creatorid"); + s->direction = nvlist_get_number(nvl, "direction"); + + pf_nvstate_peer_to_state_peer(nvlist_get_nvlist(nvl, "src"), &s->src); + pf_nvstate_peer_to_state_peer(nvlist_get_nvlist(nvl, "dst"), &s->dst); + + pf_nvstate_key_to_state_key(nvlist_get_nvlist(nvl, "stack_key"), + &s->key[0]); + pf_nvstate_key_to_state_key(nvlist_get_nvlist(nvl, "wire_key"), + &s->key[1]); + + strlcpy(s->ifname, nvlist_get_string(nvl, "ifname"), + sizeof(s->ifname)); + + pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "rt_addr"), &s->rt_addr); + s->rule = nvlist_get_number(nvl, "rule"); + s->anchor = nvlist_get_number(nvl, "anchor"); + s->nat_rule = nvlist_get_number(nvl, "nat_rule"); + s->creation = nvlist_get_number(nvl, "creation"); + s->expire = nvlist_get_number(nvl, "expire"); + + pf_nvuint_64_array(nvl, "packets", 2, s->packets, NULL); + pf_nvuint_64_array(nvl, "bytes", 2, s->bytes, NULL); + + s->log = nvlist_get_number(nvl, "log"); + s->state_flags = nvlist_get_number(nvl, "state_flags"); + s->timeout = nvlist_get_number(nvl, "timeout"); + s->sync_flags = nvlist_get_number(nvl, "sync_flags"); +} + +int +pfctl_get_states(int dev, struct pfctl_states *states) +{ + struct pfioc_nv nv; + nvlist_t *nvl; + const nvlist_t * const *slist; + size_t found_count; + + bzero(states, sizeof(*states)); + TAILQ_INIT(&states->states); + + /* Just enough to get a number, and we'll grow from there. */ + nv.data = malloc(64); + nv.len = nv.size = 64; + + for (;;) { + if (ioctl(dev, DIOCGETSTATESNV, &nv)) { + free(nv.data); + return (errno); + } + + nvl = nvlist_unpack(nv.data, nv.len, 0); + if (nvl == NULL) { + free(nv.data); + return (EIO); + } + + states->count = nvlist_get_number(nvl, "count"); + + /* Are there any states? */ + if (states->count == 0) + break; + + if (nvlist_exists_nvlist_array(nvl, "states")) + slist = nvlist_get_nvlist_array(nvl, "states", &found_count); + else + found_count = 0; + + if (found_count < states->count) { + size_t new_size = nv.size + + (nv.size * states->count / (found_count + 1) * 2); + + /* Our buffer is too small. Estimate what we need based + * on how many states fit in the previous allocation + * and how many states there are. Doubled for margin. + * */ + nv.data = realloc(nv.data, new_size); + nv.size = new_size; + + if (nv.data == NULL) + return (ENOMEM); + continue; + } + + for (size_t i = 0; i < found_count; i++) { + struct pfctl_state *s = malloc(sizeof(*s)); + if (s == NULL) { + pfctl_free_states(states); + nvlist_destroy(nvl); + free(nv.data); + return (ENOMEM); + } + + pf_nvstate_to_state(slist[i], s); + TAILQ_INSERT_TAIL(&states->states, s, entry); + } + break; + } + + return (0); +} + +void +pfctl_free_states(struct pfctl_states *states) +{ + struct pfctl_state *s, *tmp; + + TAILQ_FOREACH_SAFE(s, &states->states, entry, tmp) { + free(s); + } + + bzero(states, sizeof(*states)); +} + static int _pfctl_clear_states(int dev, const struct pfctl_kill *kill, unsigned int *killed, uint64_t ioctlval) diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index 7a1e02f3d01b..05447b5d8673 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -197,6 +197,64 @@ struct pfctl_kill { bool kill_match; }; +struct pfctl_state_scrub { + bool timestamp; + uint8_t ttl; + uint32_t ts_mod; +}; + +struct pfctl_state_peer { + struct pfctl_state_scrub *scrub; + uint32_t seqlo; + uint32_t seqhi; + uint32_t seqdiff; + uint16_t max_win; + uint16_t mss; + uint8_t state; + uint8_t wscale; +}; + +struct pfctl_state_key { + struct pf_addr addr[2]; + uint16_t port[2]; + sa_family_t af; + uint8_t proto; +}; + +struct pfctl_state { + TAILQ_ENTRY(pfctl_state) entry; + + uint64_t id; + uint32_t creatorid; + uint8_t direction; + + struct pfctl_state_peer src; + struct pfctl_state_peer dst; + + uint32_t rule; + uint32_t anchor; + uint32_t nat_rule; + struct pf_addr rt_addr; + struct pfctl_state_key key[2]; /* addresses stack and wire */ + char ifname[IFNAMSIZ]; + uint64_t packets[2]; + uint64_t bytes[2]; + uint32_t creation; + uint32_t expire; + uint32_t pfsync_time; + uint16_t tag; + uint8_t log; + uint8_t state_flags; + uint8_t timeout; + uint32_t sync_flags; +}; + +TAILQ_HEAD(pfctl_statelist, pfctl_state); +struct pfctl_states { + struct pfctl_statelist states; + size_t count; +}; + int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, const char *anchor, u_int32_t ruleset, struct pfctl_rule *rule, char *anchor_call); @@ -207,6 +265,8 @@ int pfctl_add_rule(int dev, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket); int pfctl_set_keepcounters(int dev, bool keep); +int pfctl_get_states(int dev, struct pfctl_states *states); +void pfctl_free_states(struct pfctl_states *states); int pfctl_clear_states(int dev, const struct pfctl_kill *kill, unsigned int *killed); int pfctl_kill_states(int dev, const struct pfctl_kill *kill, diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c index e2f9d6efe609..7119308d195b 100644 --- a/sbin/pfctl/pf_print_state.c +++ b/sbin/pfctl/pf_print_state.c @@ -196,25 +196,27 @@ print_host(struct pf_addr *addr, u_int16_t port, sa_family_t af, int opts) } void -print_seq(struct pfsync_state_peer *p) +print_seq(struct pfctl_state_peer *p) { if (p->seqdiff) - printf("[%u + %u](+%u)", ntohl(p->seqlo), - ntohl(p->seqhi) - ntohl(p->seqlo), ntohl(p->seqdiff)); + printf("[%u + %u](+%u)", p->seqlo, + p->seqhi - p->seqlo, p->seqdiff); else - printf("[%u + %u]", ntohl(p->seqlo), - ntohl(p->seqhi) - ntohl(p->seqlo)); + printf("[%u + %u]", p->seqlo, + p->seqhi - p->seqlo); } void -print_state(struct pfsync_state *s, int opts) +print_state(struct pfctl_state *s, int opts) { - struct pfsync_state_peer *src, *dst; - struct pfsync_state_key *key, *sk, *nk; + struct pfctl_state_peer *src, *dst; + struct pfctl_state_key *key, *sk, *nk; struct protoent *p; int min, sec; + sa_family_t af; + uint8_t proto; #ifndef __NO_STRICT_ALIGNMENT - struct pfsync_state_key aligned_key[2]; + struct pfctl_state_key aligned_key[2]; bcopy(&s->key, aligned_key, sizeof(aligned_key)); key = aligned_key; @@ -222,48 +224,51 @@ print_state(struct pfsync_state *s, int opts) key = s->key; #endif + af = s->key[PF_SK_WIRE].af; + proto = s->key[PF_SK_WIRE].proto; + if (s->direction == PF_OUT) { src = &s->src; dst = &s->dst; sk = &key[PF_SK_STACK]; nk = &key[PF_SK_WIRE]; - if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6) + if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) sk->port[0] = nk->port[0]; } else { src = &s->dst; dst = &s->src; sk = &key[PF_SK_WIRE]; nk = &key[PF_SK_STACK]; - if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6) + if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) sk->port[1] = nk->port[1]; } printf("%s ", s->ifname); - if ((p = getprotobynumber(s->proto)) != NULL) + if ((p = getprotobynumber(proto)) != NULL) printf("%s ", p->p_name); else - printf("%u ", s->proto); + printf("%u ", proto); - print_host(&nk->addr[1], nk->port[1], s->af, opts); - if (PF_ANEQ(&nk->addr[1], &sk->addr[1], s->af) || + print_host(&nk->addr[1], nk->port[1], af, opts); + if (PF_ANEQ(&nk->addr[1], &sk->addr[1], af) || nk->port[1] != sk->port[1]) { printf(" ("); - print_host(&sk->addr[1], sk->port[1], s->af, opts); + print_host(&sk->addr[1], sk->port[1], af, opts); printf(")"); } if (s->direction == PF_OUT) printf(" -> "); else printf(" <- "); - print_host(&nk->addr[0], nk->port[0], s->af, opts); - if (PF_ANEQ(&nk->addr[0], &sk->addr[0], s->af) || + print_host(&nk->addr[0], nk->port[0], af, opts); + if (PF_ANEQ(&nk->addr[0], &sk->addr[0], af) || nk->port[0] != sk->port[0]) { printf(" ("); - print_host(&sk->addr[0], sk->port[0], s->af, opts); + print_host(&sk->addr[0], sk->port[0], af, opts); printf(")"); } printf(" "); - if (s->proto == IPPROTO_TCP) { + if (proto == IPPROTO_TCP) { if (src->state <= TCPS_TIME_WAIT && dst->state <= TCPS_TIME_WAIT) printf(" %s:%s\n", tcpstates[src->state], @@ -290,16 +295,16 @@ print_state(struct pfsync_state *s, int opts) dst->wscale & PF_WSCALE_MASK); printf("\n"); } - } else if (s->proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && + } else if (proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && dst->state < PFUDPS_NSTATES) { const char *states[] = PFUDPS_NAMES; printf(" %s:%s\n", states[src->state], states[dst->state]); #ifndef INET6 - } else if (s->proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES && + } else if (proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES && dst->state < PFOTHERS_NSTATES) { #else - } else if (s->proto != IPPROTO_ICMP && s->proto != IPPROTO_ICMPV6 && + } else if (proto != IPPROTO_ICMP && proto != IPPROTO_ICMPV6 && src->state < PFOTHERS_NSTATES && dst->state < PFOTHERS_NSTATES) { #endif /* XXX ICMP doesn't really have state levels */ @@ -311,10 +316,8 @@ print_state(struct pfsync_state *s, int opts) } if (opts & PF_OPT_VERBOSE) { - u_int64_t packets[2]; - u_int64_t bytes[2]; - u_int32_t creation = ntohl(s->creation); - u_int32_t expire = ntohl(s->expire); + u_int32_t creation = s->creation; + u_int32_t expire = s->expire; sec = creation % 60; creation /= 60; @@ -327,19 +330,15 @@ print_state(struct pfsync_state *s, int opts) expire /= 60; printf(", expires in %.2u:%.2u:%.2u", expire, min, sec); - bcopy(s->packets[0], &packets[0], sizeof(u_int64_t)); - bcopy(s->packets[1], &packets[1], sizeof(u_int64_t)); - bcopy(s->bytes[0], &bytes[0], sizeof(u_int64_t)); - bcopy(s->bytes[1], &bytes[1], sizeof(u_int64_t)); printf(", %ju:%ju pkts, %ju:%ju bytes", - (uintmax_t )be64toh(packets[0]), - (uintmax_t )be64toh(packets[1]), - (uintmax_t )be64toh(bytes[0]), - (uintmax_t )be64toh(bytes[1])); - if (ntohl(s->anchor) != -1) - printf(", anchor %u", ntohl(s->anchor)); - if (ntohl(s->rule) != -1) - printf(", rule %u", ntohl(s->rule)); + s->packets[0], + s->packets[1], + s->bytes[0], + s->bytes[1]); + if (s->anchor != -1) + printf(", anchor %u", s->anchor); + if (s->rule != -1) + printf(", rule %u", s->rule); if (s->state_flags & PFSTATE_SLOPPY) printf(", sloppy"); if (s->sync_flags & PFSYNC_FLAG_SRCNODE) @@ -352,10 +351,9 @@ print_state(struct pfsync_state *s, int opts) u_int64_t id; bcopy(&s->id, &id, sizeof(u_int64_t)); - printf(" id: %016jx creatorid: %08x", - (uintmax_t )be64toh(id), ntohl(s->creatorid)); + printf(" id: %016jx creatorid: %08x", id, s->creatorid); printf(" gateway: "); - print_host(&s->rt_addr, 0, s->af, opts); + print_host(&s->rt_addr, 0, af, opts); printf("\n"); } } diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index fd937cac9f63..f82d75198d61 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1237,48 +1237,27 @@ pfctl_show_src_nodes(int dev, int opts) int pfctl_show_states(int dev, const char *iface, int opts) { - struct pfioc_states ps; - struct pfsync_state *p; - char *inbuf = NULL, *newinbuf = NULL; - unsigned int len = 0; - int i, dotitle = (opts & PF_OPT_SHOWALL); + struct pfctl_states states; + struct pfctl_state *s; + int dotitle = (opts & PF_OPT_SHOWALL); - memset(&ps, 0, sizeof(ps)); - for (;;) { - ps.ps_len = len; - if (len) { - newinbuf = realloc(inbuf, len); - if (newinbuf == NULL) - err(1, "realloc"); - ps.ps_buf = inbuf = newinbuf; - } - if (ioctl(dev, DIOCGETSTATES, &ps) < 0) { - warn("DIOCGETSTATES"); - free(inbuf); - return (-1); - } - if (ps.ps_len + sizeof(struct pfioc_states) < len) - break; - if (len == 0 && ps.ps_len == 0) - goto done; - if (len == 0 && ps.ps_len != 0) - len = ps.ps_len; - if (ps.ps_len == 0) - goto done; /* no states */ - len *= 2; - } - p = ps.ps_states; - for (i = 0; i < ps.ps_len; i += sizeof(*p), p++) { - if (iface != NULL && strcmp(p->ifname, iface)) + memset(&states, 0, sizeof(states)); + + if (pfctl_get_states(dev, &states)) + return (-1); + + TAILQ_FOREACH(s, &states.states, entry) { + if (iface != NULL && strcmp(s->ifname, iface)) continue; if (dotitle) { pfctl_print_title("STATES:"); dotitle = 0; } - print_state(p, opts); + print_state(s, opts); } -done: - free(inbuf); + + pfctl_free_states(&states); + return (0); } diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h index 93b3af083b5f..f8ff5012e01b 100644 --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -120,8 +120,8 @@ char *rate2str(double); void print_addr(struct pf_addr_wrap *, sa_family_t, int); void print_host(struct pf_addr *, u_int16_t p, sa_family_t, int); -void print_seq(struct pfsync_state_peer *); -void print_state(struct pfsync_state *, int); +void print_seq(struct pfctl_state_peer *); +void print_state(struct pfctl_state *, int); int unmask(struct pf_addr *, sa_family_t); int pfctl_cmdline_symset(char *);