Skip to content

Repository files navigation

MaCake

cake-autorate for macOS.
Latency-driven adaptive bandwidth shaping on Apple Silicon, using only sudo.

Apache 2.0 Platform Swift 6 No kexts No entitlements

Your connection isn't slow, it's bloated

Here's what this tool measured on a Starlink link, idle and then saturated, comparing like with like at each percentile:

             idle      under 278 Mbit/s of download
median      27.5 ms                 66.2 ms      +38.7
p90         89.1 ms                144.6 ms      +55.5
gateway      3.2 ms      throughout, so the queue is at the ISP and not on your LAN

Every SSH keystroke, every Zoom frame, every DNS lookup waits behind bulk download parked in somebody else's buffer. Paying for more bandwidth doesn't help; it gives the buffer more to fill.

An earlier version of this README claimed +345 ms here. That was a loaded p90 measured against an idle minimum, which counts the link's own idle jitter as bufferbloat and overstates it about fourfold on satellite. The numbers above are the honest ones.

The fix has been known since 2011: send slightly slower than the link can carry, so the queue forms where you can manage it. Linux does this beautifully with CAKE. The awkward part is picking the rate, because on satellite and cellular links capacity moves constantly. During one afternoon of testing, this link ranged between 135 and 392 Mbit/s.

That's the problem cake-autorate solves: watch latency, steer the shaper in real time. MaCake ports that controller to macOS.

What it buys, and what it costs

Drift-controlled, unshaped runs on either side so link variance can't be mistaken for a result:

throughput added median added p90
off 278.52 Mbit/s +38.7 ms +55.5 ms
on 177.51 Mbit/s +9.8 ms +16.0 ms
off again 290.78 Mbit/s +49.4 ms +64.5 ms

Four times less added delay, for a bit over a third of the throughput. No version of this is free: a pipe only owns the queue while it is the bottleneck, so it has to sit under your line rate. That is the whole trade, and it's the same one CAKE makes on Linux.

Default mode shapes permanently, starting at 95% of measured capacity and steering from there — up to but never above what the link has been measured carrying, down whenever a queue shows. That is upstream cake-autorate's own posture. --mode balanced does the opposite and stays out of the datapath until a queue actually stands, then releases after twenty seconds clean:

[14:36] standing queue for 4s (delay +18.5 ms one-way) - engaging shaper
[14:37] latency clean for 20s — releasing shaper

Balanced exists because six drift-controlled runs on this Starlink link found no standing queue worth removing, and a pipe that isn't needed costs throughput for nothing. If your link bloats consistently, which most cable and DSL lines do, leave it on the default. All of it, including the runs where shaping lost, is in docs/MEASUREMENTS.md.

Is this CAKE?

No, and the difference matters enough to put in the second section rather than bury it.

CAKE is a Linux kernel qdisc. XNU has no qdisc subsystem, so there is nothing for sch_cake to plug into, and getting one would need a kernel extension. What MaCake ports is cake-autorate: the controller that sits above the shaper and decides what rate it should run at. That algorithm doesn't care what's underneath it, so it drives macOS dummynet pipes through pf instead of Linux tc.

cake-autorate's control algorithm Ported. Asymmetric baseline EWMA (α↓ 0.9, α↑ 0.005), sliding bufferbloat window, idle/low/high × bufferbloat load classes, get_next_shaper_rate() with upstream's proportional adjustments (cut 0.99→0.75, raise 1.00→1.04, decay 0.99/1.01), 300 ms and 1000 ms refractory periods, two independent per-direction loops
Rate shaping, BDP-sized queues, ingress and egress Yes, via dnctl pipes
fq_codel or COBALT AQM inside the shaper No. macOS dummynet has droptail and RED, nothing else
Per-flow fair queueing (CAKE's DRR++) No, and it isn't a configuration problem. Three separate proofs
Host fairness, diffserv tins, ACK filtering No

If you control your router, put OpenWrt and real CAKE on it and run upstream cake-autorate there; it will beat this. MaCake exists for the cases where you can't: a Starlink terminal you're not allowed to replace, a phone hotspot, hotel wifi, a laptop that moves between all of them in one day.

A finding worth the detour

macOS already ships FQ-CoDel. Run netstat -qq -I en0 and you'll see:

en0: [ sched: FQ_CODEL  qlength: 0/6720 ]
  [ pri: VO (1)  quantum: 605   drr_max: 8 ]
  [ pri: VI (2)  quantum: 3028  drr_max: 6 ]
  [ pri: BE (7)  quantum: 1514  drr_max: 4 ]
  [ target qdelay: 10.00 msec   update interval: 100.00 msec ]
  [ L4S target qdelay: 15.00 msec ]
  [ flows total: 0  new: 0  old: 0 ]

Per-flow queues, deficit round robin, priority classes, a CoDel target, L4S. Apple built most of CAKE's ideas into the interface queue and nobody talks about it.

It doesn't rescue you, for two reasons. It only covers egress, so download bloat is untouched, and on this Starlink link download was the far worse direction. It also only engages when the local NIC is the bottleneck, which on satellite or cellular it never is. Details in docs/DUMMYNET.md.

Install

git clone https://github.com/dlukel/MaCake.git
cd MaCake
./install.sh

You need the Xcode Command Line Tools (xcode-select --install) and nothing else. No Xcode, no Apple Developer account, no entitlement, no SIP change, no reduced-security boot.

Use it

cakectl doctor          # what does this Mac actually support? prints what's missing too
cakectl measure         # capacity, idle baseline, bufferbloat under load, saved as a profile
sudo cakectl up         # install the shaper, sized from that measurement
sudo cakectl autorate   # run the control loop
sudo cakectl down       # remove everything; idempotent, safe after a crash

To keep it running:

sudo cakectl install-daemon

There's also a menu bar app in dist/MaCake.app. It shows the controller's live state and toggles the daemon; every privileged action goes through cakectl and macOS's own authorisation prompt, so the app never handles your password.

command root
measure Detect the link, sample idle RTT, saturate both directions, cross-check against networkQuality, write a profile
status Current link, live controller state, saved profiles
monitor Continuous RTT sampling and rolling baseline; no shaping
bench One-shot throughput and latency-under-load, for A/B testing a change
doctor Capability probe
links Every saved link profile
up / down Install and remove the shaper
autorate The control loop
install-daemon Run autorate at boot
install-agent Run monitor at login, no root needed

What it does under the hood

                 ┌──────────────────────────────────────────┐
  ICMP echo ────►│ RTTSampler                               │
  1.1.1.1        │   per-reflector baseline                 │
  8.8.8.8        │   α↓0.9 / α↑0.005  +  300 s window min   │
  9.9.9.9        │                    │                     │
                 │                    ▼                     │
  getifaddrs ───►│ DirectionController × 2   (dl, ul)       │
  achieved rate  │   load = achieved / shaper               │
                 │   bufferbloat = N of last M over thresh  │
                 │   get_next_shaper_rate()                 │
                 └────────────────────┬─────────────────────┘
                                      ▼
              dnctl pipe <n> config bw <k>Kbit/s queue <q>Kbytes
                                      │
  ┌───────────────────────────────────┴──────────────────────┐
  │ main ruleset:    dummynet-anchor "cakectl"               │
  │ anchor cakectl:  dummynet out on en0 all pipe 4801       │
  │                  dummynet in  on en0 all pipe 4802       │
  └──────────────────────────────────────────────────────────┘

Two latency baselines get tracked, because they answer different questions. A hard minimum over a long rolling window (300 s by default) is what queue sizing uses. An asymmetric EWMA that falls fast toward a new minimum and rises very slowly is what the controller steers on, matching upstream. The gateway gets probed too, but only for diagnosis: on satellite and cellular the bottleneck sits well beyond it, so using it as the control signal would be useless.

Queue depth comes from the bandwidth-delay product at the measured baseline, floored at 4 MTU and capped at 1023 KB, because dnctl refuses a byte-denominated queue of 1 MB or more. Never a fixed packet count.

On pf, MaCake reconstructs your live main ruleset from pfctl output, adds exactly one line, and keeps its own rules in a dedicated anchor. Teardown rebuilds from whatever is live at that moment rather than restoring a stale snapshot, so anchors another service added meanwhile survive. pfctl -E/-X reference counting means cakectl down won't switch pf off underneath something else that turned it on. If your main ruleset contains anything MaCake doesn't recognise, it refuses to touch it and tells you the one line to add by hand.

Profiles live in ~/.config/cakectl/profiles.json, keyed by SSID where macOS discloses it, otherwise interface plus gateway MAC, otherwise interface plus gateway IP. Alias keys get stored alongside, because a profile written by your user account has to be findable by the root daemon, and root sees facts your account can't. On a link change the controller reloads that link's profile and starts from its known-good rate rather than probing up from zero.

Docs

Credit

The rate-control algorithm comes from cake-autorate by lynxthecat and contributors, GPL-2.0. No source is copied here; the published algorithm is reimplemented in Swift against a different shaper. Star that project too, it's the original idea and it's still the better option if you own your router.

CAKE and the bufferbloat work behind all of this belong to Dave Täht, Toke Høiland-Jørgensen, Jonathan Morton, and everyone at Bufferbloat.net.

Licence

Apache License 2.0. Copyright 2026 Loucas Louka. See LICENSE and NOTICE.

Use it, ship it, sell it. Keep the copyright notice and the NOTICE file.

About

cake-autorate for macOS. Adaptive bufferbloat control on dummynet, no kexts.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages