diff --git a/sys/net/ipfw3/ip_fw3.h b/sys/net/ipfw3/ip_fw3.h --- a/sys/net/ipfw3/ip_fw3.h +++ b/sys/net/ipfw3/ip_fw3.h @@ -270,6 +270,7 @@ uint64_t pcnt; /* Packet counter */ uint64_t bcnt; /* Byte counter */ uint32_t timestamp; /* tv_sec of last match */ + uint32_t refcnt; /* reference count (dummynet) */ struct ip_fw *sibling; /* pointer to the rule in next CPU */ diff --git a/sys/net/ipfw3/ip_fw3.c b/sys/net/ipfw3/ip_fw3.c --- a/sys/net/ipfw3/ip_fw3.c +++ b/sys/net/ipfw3/ip_fw3.c @@ -583,6 +583,21 @@ return IP_FW_DENY; } +/* + * Release a dummynet reference on a rule. Called by dummynet (via + * dn_unref_priv) when a tagged packet is freed or re-dispatched. + * Uses atomic refcount so the unref is safe on any CPU. + */ +static void +ip_fw3_unref_dn_priv(void *priv) +{ + struct ip_fw *rule = priv; + + KKASSERT(rule->refcnt > 0); + if (atomic_fetchadd_int(&rule->refcnt, -1) == 1) + kfree(rule, M_IPFW3); +} + struct mbuf * ip_fw3_dummynet_io(struct mbuf *m, int pipe_nr, int dir, struct ip_fw_args *fwa) { @@ -627,7 +642,14 @@ fid->fid_proto = id->proto; fid->fid_flags = id->flags; + /* + * Take a reference on the rule so it survives until dummynet + * frees/re-dispatches the packet. ip_fw3_delete_rule() will + * defer the kfree until this reference is released. + */ + atomic_add_int(&fwa->rule->refcnt, 1); pkt->dn_priv = fwa->rule; + pkt->dn_unref_priv = ip_fw3_unref_dn_priv; if ((int)cmd->opcode == O_DUMMYNET_PIPE) pkt->dn_flags |= DN_FLAGS_IS_PIPE; @@ -651,6 +673,7 @@ rule->act_ofs = ioc_rule->act_ofs; rule->cmd_len = ioc_rule->cmd_len; rule->rulenum = ioc_rule->rulenum; + rule->refcnt = 1; /* chain holds one reference */ rule->set = ioc_rule->set; bcopy(ioc_rule->cmd, rule->cmd, rule->cmd_len * 4); @@ -762,7 +785,11 @@ else prev->next = rule->next; - kfree(rule, M_IPFW3); + /* Decrement the chain reference; defer kfree if dummynet still + * holds references (in-flight tagged packets). */ + KKASSERT(rule->refcnt > 0); + if (atomic_fetchadd_int(&rule->refcnt, -1) == 1) + kfree(rule, M_IPFW3); rule = NULL; return NULL; } @@ -784,7 +811,9 @@ the_rule = rule; rule = rule->next; - kfree(the_rule, M_IPFW3); + KKASSERT(the_rule->refcnt > 0); + if (atomic_fetchadd_int(&the_rule->refcnt, -1) == 1) + kfree(the_rule, M_IPFW3); } netisr_forwardmsg_all(&nmsg->base, mycpuid + 1);