aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristof Provost <kp@FreeBSD.org>2022-05-18 15:49:28 +0000
committerKristof Provost <kp@FreeBSD.org>2022-05-20 12:49:31 +0000
commitbbec8e698b5bfbd568b840fc411b4fd125684045 (patch)
tree16e89a6a0bdf088c515f2bdfba9dc425df1fc892
parent12c542cd0e9efc1ad9f20f1035402c0acf6d403f (diff)
downloadsrc-bbec8e698b5bfbd568b840fc411b4fd125684045.tar.gz
src-bbec8e698b5bfbd568b840fc411b4fd125684045.zip
pf: call dummynet directly from the ethernet code
Until recently dummynet in ethernet rules did not send packets directly to dummynet but instead marked them and left the interactions with dummynet to the layer 3 pf code. This worked fine for incoming packets (where we process ethernet rules before layer 3 rules), but not for outbound packets (where the order of operations is the reverse). Dummynet does support handling layer 2 traffic, so send the packets directly to dummynet. The main limitation now is that pf does not inspect layer 4 (i.e. TCP/UDP) so we don't have protocol information or port numbers. Dummynet potentially uses this to separate traffic flows, which will not work for ethernet dummynet rules. However, pipes (i.e. adding latency or restricting bandwidth) will work exactly as expected. Sponsored by: Rubicon Communications, LLC ("Netgate") Differential Revision: https://reviews.freebsd.org/D35257
-rw-r--r--sys/netpfil/pf/pf.c80
1 files changed, 70 insertions, 10 deletions
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 5b3bc719ecb6..c613194ce9b5 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -3858,6 +3858,19 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
SDT_PROBE3(pf, eth, test_rule, entry, dir, kif->pfik_ifp, m);
+ mtag = pf_find_mtag(m);
+ if (mtag != NULL && mtag->flags & PF_TAG_DUMMYNET) {
+ /* Dummynet re-injects packets after they've
+ * completed their delay. We've already
+ * processed them, so pass unconditionally. */
+
+ /* But only once. We may see the packet multiple times (e.g.
+ * PFIL_IN/PFIL_OUT). */
+ mtag->flags &= ~PF_TAG_DUMMYNET;
+
+ return (PF_PASS);
+ }
+
ruleset = V_pf_keth;
rules = ck_pr_load_ptr(&ruleset->active.rules);
r = TAILQ_FIRST(rules);
@@ -3989,7 +4002,8 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
}
if (r->tag > 0) {
- mtag = pf_get_mtag(m);
+ if (mtag == NULL)
+ mtag = pf_get_mtag(m);
if (mtag == NULL) {
PF_RULES_RUNLOCK();
counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1);
@@ -3999,7 +4013,8 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
}
if (r->qid != 0) {
- mtag = pf_get_mtag(m);
+ if (mtag == NULL)
+ mtag = pf_get_mtag(m);
if (mtag == NULL) {
PF_RULES_RUNLOCK();
counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1);
@@ -4010,19 +4025,64 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
/* Dummynet */
if (r->dnpipe) {
- /** While dummynet supports handling Ethernet packets directly
- * it still wants some L3/L4 information, and we're not set up
- * to provide that here. Instead we'll do what we do for ALTQ
- * and merely mark the packet with the dummynet queue/pipe number.
- **/
- mtag = pf_get_mtag(m);
+ struct ip_fw_args dnflow;
+
+ /* Drop packet if dummynet is not loaded. */
+ if (ip_dn_io_ptr == NULL) {
+ PF_RULES_RUNLOCK();
+ m_freem(m);
+ counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1);
+ return (PF_DROP);
+ }
+ if (mtag == NULL)
+ mtag = pf_get_mtag(m);
if (mtag == NULL) {
PF_RULES_RUNLOCK();
counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1);
return (PF_DROP);
}
- mtag->dnpipe = r->dnpipe;
- mtag->dnflags = r->dnflags;
+
+ bzero(&dnflow, sizeof(dnflow));
+
+ /* We don't have port numbers here, so we set 0. That means
+ * that we'll be somewhat limited in distinguishing flows (i.e.
+ * only based on IP addresses, not based on port numbers), but
+ * it's better than nothing. */
+ dnflow.f_id.dst_port = 0;
+ dnflow.f_id.src_port = 0;
+ dnflow.f_id.proto = 0;
+
+ dnflow.rule.info = r->dnpipe;
+ dnflow.rule.info |= IPFW_IS_DUMMYNET;
+ if (r->dnflags & PFRULE_DN_IS_PIPE)
+ dnflow.rule.info |= IPFW_IS_PIPE;
+
+ dnflow.f_id.extra = dnflow.rule.info;
+
+ dnflow.flags = dir == PF_IN ? IPFW_ARGS_IN : IPFW_ARGS_OUT;
+ dnflow.flags |= IPFW_ARGS_ETHER;
+ dnflow.ifp = kif->pfik_ifp;
+
+ switch (af) {
+ case AF_INET:
+ dnflow.f_id.addr_type = 4;
+ dnflow.f_id.src_ip = src->v4.s_addr;
+ dnflow.f_id.dst_ip = dst->v4.s_addr;
+ break;
+ case AF_INET6:
+ dnflow.flags |= IPFW_ARGS_IP6;
+ dnflow.f_id.addr_type = 6;
+ dnflow.f_id.src_ip6 = src->v6;
+ dnflow.f_id.dst_ip6 = dst->v6;
+ break;
+ default:
+ panic("Unknown address family");
+ }
+
+ mtag->flags |= PF_TAG_DUMMYNET;
+ ip_dn_io_ptr(m0, &dnflow);
+ if (*m0 != NULL)
+ mtag->flags &= ~PF_TAG_DUMMYNET;
}
action = r->action;