+#if DUMMYNET
+/*
+ * When pf_test_dummynet() returns PF_PASS, the rule matching parameter "rm"
+ * remains unchanged, meaning the packet did not match a dummynet rule.
+ * when the packet does match a dummynet rule, pf_test_dummynet() returns
+ * PF_PASS and zero out the mbuf rule as the packet is effectively siphoned
+ * out by dummynet.
+ */
+static int
+pf_test_dummynet(struct pf_rule **rm, int direction, struct pfi_kif *kif,
+ struct mbuf **m0, struct pf_pdesc *pd, struct ip_fw_args *fwa)
+{
+ struct mbuf *m = *m0;
+ struct pf_rule *am = NULL;
+ struct pf_ruleset *rsm = NULL;
+ struct pf_addr *saddr = pd->src, *daddr = pd->dst;
+ sa_family_t af = pd->af;
+ struct pf_rule *r, *a = NULL;
+ struct pf_ruleset *ruleset = NULL;
+ struct tcphdr *th = pd->hdr.tcp;
+ u_short reason;
+ int hdrlen = 0;
+ int tag = -1;
+ unsigned int rtableid = IFSCOPE_NONE;
+ int asd = 0;
+ int match = 0;
+ u_int8_t icmptype = 0, icmpcode = 0;
+ struct ip_fw_args dnflow;
+ struct pf_rule *prev_matching_rule = fwa ? fwa->fwa_pf_rule : NULL;
+ int found_prev_rule = (prev_matching_rule) ? 0 : 1;
+
+ lck_mtx_assert(pf_lock, LCK_MTX_ASSERT_OWNED);
+
+ if (!DUMMYNET_LOADED)
+ return (PF_PASS);
+
+ if (TAILQ_EMPTY(pf_main_ruleset.rules[PF_RULESET_DUMMYNET].active.ptr))
+ return (PF_PASS);
+
+ bzero(&dnflow, sizeof(dnflow));
+
+ hdrlen = 0;
+
+ /* Fragments don't gave protocol headers */
+ if (!(pd->flags & PFDESC_IP_FRAG))
+ switch (pd->proto) {
+ case IPPROTO_TCP:
+ dnflow.fwa_id.flags = pd->hdr.tcp->th_flags;
+ dnflow.fwa_id.dst_port = ntohs(pd->hdr.tcp->th_dport);
+ dnflow.fwa_id.src_port = ntohs(pd->hdr.tcp->th_sport);
+ hdrlen = sizeof (*th);
+ break;
+ case IPPROTO_UDP:
+ dnflow.fwa_id.dst_port = ntohs(pd->hdr.udp->uh_dport);
+ dnflow.fwa_id.src_port = ntohs(pd->hdr.udp->uh_sport);
+ hdrlen = sizeof (*pd->hdr.udp);
+ break;
+#if INET
+ case IPPROTO_ICMP:
+ if (af != AF_INET)
+ break;
+ hdrlen = ICMP_MINLEN;
+ icmptype = pd->hdr.icmp->icmp_type;
+ icmpcode = pd->hdr.icmp->icmp_code;
+ break;
+#endif /* INET */
+#if INET6
+ case IPPROTO_ICMPV6:
+ if (af != AF_INET6)
+ break;
+ hdrlen = sizeof (*pd->hdr.icmp6);
+ icmptype = pd->hdr.icmp6->icmp6_type;
+ icmpcode = pd->hdr.icmp6->icmp6_code;
+ break;
+#endif /* INET6 */
+ case IPPROTO_GRE:
+ if (pd->proto_variant == PF_GRE_PPTP_VARIANT)
+ hdrlen = sizeof (*pd->hdr.grev1);
+ break;
+ case IPPROTO_ESP:
+ hdrlen = sizeof (*pd->hdr.esp);
+ break;
+ }
+
+ r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_DUMMYNET].active.ptr);
+
+ while (r != NULL) {
+ r->evaluations++;
+ if (pfi_kif_match(r->kif, kif) == r->ifnot)
+ r = r->skip[PF_SKIP_IFP].ptr;
+ else if (r->direction && r->direction != direction)
+ r = r->skip[PF_SKIP_DIR].ptr;
+ else if (r->af && r->af != af)
+ r = r->skip[PF_SKIP_AF].ptr;
+ else if (r->proto && r->proto != pd->proto)
+ r = r->skip[PF_SKIP_PROTO].ptr;
+ else if (PF_MISMATCHAW(&r->src.addr, saddr, af,
+ r->src.neg, kif))
+ r = r->skip[PF_SKIP_SRC_ADDR].ptr;
+ /* tcp/udp only. port_op always 0 in other cases */
+ else if (r->proto == pd->proto &&
+ (r->proto == IPPROTO_TCP || r->proto == IPPROTO_UDP) &&
+ ((pd->flags & PFDESC_IP_FRAG) ||
+ ((r->src.xport.range.op &&
+ !pf_match_port(r->src.xport.range.op,
+ r->src.xport.range.port[0], r->src.xport.range.port[1],
+ th->th_sport)))))
+ r = r->skip[PF_SKIP_SRC_PORT].ptr;
+ else if (PF_MISMATCHAW(&r->dst.addr, daddr, af,
+ r->dst.neg, NULL))
+ r = r->skip[PF_SKIP_DST_ADDR].ptr;
+ /* tcp/udp only. port_op always 0 in other cases */
+ else if (r->proto == pd->proto &&
+ (r->proto == IPPROTO_TCP || r->proto == IPPROTO_UDP) &&
+ r->dst.xport.range.op &&
+ ((pd->flags & PFDESC_IP_FRAG) ||
+ !pf_match_port(r->dst.xport.range.op,
+ r->dst.xport.range.port[0], r->dst.xport.range.port[1],
+ th->th_dport)))
+ r = r->skip[PF_SKIP_DST_PORT].ptr;
+ /* icmp only. type always 0 in other cases */
+ else if (r->type &&
+ ((pd->flags & PFDESC_IP_FRAG) ||
+ r->type != icmptype + 1))
+ r = TAILQ_NEXT(r, entries);
+ /* icmp only. type always 0 in other cases */
+ else if (r->code &&
+ ((pd->flags & PFDESC_IP_FRAG) ||
+ r->code != icmpcode + 1))
+ r = TAILQ_NEXT(r, entries);
+ else if (r->tos && !(r->tos == pd->tos))
+ r = TAILQ_NEXT(r, entries);
+ else if (r->rule_flag & PFRULE_FRAGMENT)
+ r = TAILQ_NEXT(r, entries);
+ else if (pd->proto == IPPROTO_TCP &&
+ ((pd->flags & PFDESC_IP_FRAG) ||
+ (r->flagset & th->th_flags) != r->flags))
+ r = TAILQ_NEXT(r, entries);
+ else if (r->prob && r->prob <= (RandomULong() % (UINT_MAX - 1) + 1))
+ r = TAILQ_NEXT(r, entries);
+ else if (r->match_tag && !pf_match_tag(m, r, pd->pf_mtag, &tag))
+ r = TAILQ_NEXT(r, entries);
+ else {
+ /*
+ * Need to go past the previous dummynet matching rule
+ */
+ if (r->anchor == NULL) {
+ if (found_prev_rule) {
+ if (r->tag)
+ tag = r->tag;
+ if (PF_RTABLEID_IS_VALID(r->rtableid))
+ rtableid = r->rtableid;
+ match = 1;
+ *rm = r;
+ am = a;
+ rsm = ruleset;
+ if ((*rm)->quick)
+ break;
+ } else if (r == prev_matching_rule) {
+ found_prev_rule = 1;
+ }
+ r = TAILQ_NEXT(r, entries);
+ } else {
+ pf_step_into_anchor(&asd, &ruleset,
+ PF_RULESET_DUMMYNET, &r, &a, &match);
+ }
+ }
+ if (r == NULL && pf_step_out_of_anchor(&asd, &ruleset,
+ PF_RULESET_DUMMYNET, &r, &a, &match))
+ break;
+ }
+ r = *rm;
+ a = am;
+ ruleset = rsm;
+
+ if (!match)
+ return (PF_PASS);
+
+ REASON_SET(&reason, PFRES_DUMMYNET);
+
+ if (r->log) {
+ PFLOG_PACKET(kif, h, m, af, direction, reason, r,
+ a, ruleset, pd);
+ }
+
+ if (r->action == PF_NODUMMYNET) {
+ int dirndx = (direction == PF_OUT);
+
+ r->packets[dirndx]++;
+ r->bytes[dirndx] += pd->tot_len;
+
+ return (PF_PASS);
+ }
+ if (pf_tag_packet(m, pd->pf_mtag, tag, rtableid, pd)) {
+ REASON_SET(&reason, PFRES_MEMORY);
+
+ return (PF_DROP);
+ }
+
+ if (r->dnpipe && ip_dn_io_ptr != NULL) {
+ int dirndx = (direction == PF_OUT);
+
+ r->packets[dirndx]++;
+ r->bytes[dirndx] += pd->tot_len;
+
+ dnflow.fwa_cookie = r->dnpipe;
+ dnflow.fwa_pf_rule = r;
+ dnflow.fwa_id.proto = pd->proto;
+ dnflow.fwa_flags = r->dntype;
+ switch (af) {
+ case AF_INET:
+ dnflow.fwa_id.addr_type = 4;
+ dnflow.fwa_id.src_ip = ntohl(saddr->v4.s_addr);
+ dnflow.fwa_id.dst_ip = ntohl(daddr->v4.s_addr);
+ break;
+ case AF_INET6:
+ dnflow.fwa_id.addr_type = 6;
+ dnflow.fwa_id.src_ip6 = saddr->v6;
+ dnflow.fwa_id.dst_ip6 = saddr->v6;
+ break;
+ }
+
+ if (fwa != NULL) {
+ dnflow.fwa_oif = fwa->fwa_oif;
+ dnflow.fwa_oflags = fwa->fwa_oflags;
+ /*
+ * Note that fwa_ro, fwa_dst and fwa_ipoa are
+ * actually in a union so the following does work
+ * for both IPv4 and IPv6
+ */
+ dnflow.fwa_ro = fwa->fwa_ro;
+ dnflow.fwa_dst = fwa->fwa_dst;
+ dnflow.fwa_ipoa = fwa->fwa_ipoa;
+ dnflow.fwa_ro6_pmtu = fwa->fwa_ro6_pmtu;
+ dnflow.fwa_origifp = fwa->fwa_origifp;
+ dnflow.fwa_mtu = fwa->fwa_mtu;
+ dnflow.fwa_alwaysfrag = fwa->fwa_alwaysfrag;
+ dnflow.fwa_unfragpartlen = fwa->fwa_unfragpartlen;
+ dnflow.fwa_exthdrs = fwa->fwa_exthdrs;
+ }
+
+ if (af == AF_INET) {
+ struct ip *iphdr = mtod(m, struct ip *);
+ NTOHS(iphdr->ip_len);
+ NTOHS(iphdr->ip_off);
+ }
+ /*
+ * Don't need to unlock pf_lock as NET_THREAD_HELD_PF
+ * allows for recursive behavior
+ */
+ ip_dn_io_ptr(m,
+ dnflow.fwa_cookie,
+ af == AF_INET ?
+ direction == PF_IN ? DN_TO_IP_IN : DN_TO_IP_OUT :
+ direction == PF_IN ? DN_TO_IP6_IN : DN_TO_IP6_OUT,
+ &dnflow, DN_CLIENT_PF);
+
+ /*
+ * The packet is siphoned out by dummynet so return a NULL
+ * mbuf so the caller can still return success.
+ */
+ *m0 = NULL;
+
+ return (PF_PASS);
+ }
+
+ return (PF_PASS);
+}
+#endif /* DUMMYNET */
+