Index: lib/libpfctl/libpfctl.h =================================================================== --- lib/libpfctl/libpfctl.h +++ lib/libpfctl/libpfctl.h @@ -238,6 +238,11 @@ uint32_t sync_flags; }; +struct pfctl_states_stats { + uint64_t states; + size_t state_size_hint; +}; + TAILQ_HEAD(pfctl_statelist, pfctl_state); struct pfctl_states { struct pfctl_statelist states; @@ -254,6 +259,7 @@ 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_stats(int dev, struct pfctl_states_stats *stats); 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, Index: lib/libpfctl/libpfctl.c =================================================================== --- lib/libpfctl/libpfctl.c +++ lib/libpfctl/libpfctl.c @@ -705,21 +705,65 @@ s->sync_flags = nvlist_get_number(nvl, "sync_flags"); } +int +pfctl_get_states_stats(int dev, struct pfctl_states_stats *stats) +{ + struct pfioc_nv nv; + nvlist_t *nvl = NULL; + int error = 0; + + bzero(stats, sizeof(*stats)); + + nv.data = malloc(128); + nv.len = nv.size = 128; + + if (ioctl(dev, DIOCGETSTATESSTATSNV, &nv)) { + error = errno; + goto out; + } + + nvl = nvlist_unpack(nv.data, nv.len, 0); + if (nvl == NULL) { + error = EIO; + goto out; + } + + stats->states = nvlist_get_number(nvl, "states"); + stats->state_size_hint = nvlist_get_number(nvl, "state_size_hint"); + +out: + nvlist_destroy(nvl); + free(nv.data); + + return (error); +} + int pfctl_get_states(int dev, struct pfctl_states *states) { - struct pfioc_nv nv; - nvlist_t *nvl = NULL; - const nvlist_t * const *slist; - size_t found_count; - int error = 0; + struct pfioc_nv nv; + struct pfctl_states_stats stats; + nvlist_t *nvl = NULL; + const nvlist_t * const *slist; + size_t found_count; + size_t size_estimate; + int error = 0; + + error = pfctl_get_states_stats(dev, &stats); + if (error) + return (error); 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; + /* 20% extra, in case more states are added between the stats call and + * the actual read. */ + size_estimate = 64 + (stats.states * stats.state_size_hint * 12 / 10); + /* Never more than 100MB at a time. */ + size_estimate = MIN(size_estimate, 100 * 1024 * 1024); + + nv.data = malloc(size_estimate); + nv.len = nv.size = size_estimate; for (;;) { if (ioctl(dev, DIOCGETSTATESNV, &nv)) {