I wanted to know what was actually eating my bandwidth, not just how much of it
crossed the WAN. OpenWrt will happily tell you 3 GB went out; it won't tell you
that most of it was Netflix and 200 MB was a Windows update. So I wrote
something that does.
luci-app-appflow reads the JSON export socket of netifyd, which is already
in the OpenWrt feeds and does the actual DPI (nDPI based). A small ucode daemon
keeps a live aggregate in memory and publishes it over ubus, and two LuCI pages
draw it: a live overview with rates per application and per device, and a past
hour statistics page. The charts are hand-drawn inline SVG, so no bundler, no
framework and no build step.
Apache-2.0, v1.1.0, source and design notes at
https://github.com/VolanticSystems/luci-app-appflow (the org is my consultancy;
the issue tracker there is the best place for anything technical).
It gets the right client behind NAT. netifyd reports the post-translation
identity on the WAN-side capture, so a NAT-ed client's traffic is otherwise
indistinguishable from the router's own. appflow resolves each flow through
/proc/net/nf_conntrack. Measured against the client's own interface counter:
99.8% for a client, 97.9% for the router, each counted once. If conntrack can't
be read it keeps client accounting correct, stops attributing the router's own
traffic rather than double-counting it, and says so in the log.
Both lists filter, because one streaming session buries everything else.
Type to narrow, or put a minus in front of a word to exclude it, so -netflix
gives you everything except the thing dominating the view. Clicking a category,
an app or a device fills the box in. Clicking a device shows what that device is
up to.
You can teach it a service it doesn't know, or fix one it gets wrong,
without touching code:
config hostmap
option suffix 'torproject.org'
option name 'Tor'
option category 'Privacy'
Matching is anchored on label boundaries, so it won't match
torproject.org.example.com, which anyone can register.
How it compares to nlbwmon: different axes. nlbwmon does persistent per-host,
per-protocol accounting, which appflow doesn't attempt; appflow asks which
application a flow is and keeps a live view only. I ran both on one router on
the same traffic and they don't tread on each other. For durable per-host
numbers, nlbwmon is better.
Things to know:
- It's all in memory. No database, on purpose, so a restart or reboot empties
the past hour view. - Flows already running when the daemon starts land in "Unknown" for the rest of
their life: netifyd sends counter updates for those but never the event
carrying the identity. Bounded to the restart window, no bytes go missing. - I haven't tested it with flow offloading turned on, software or hardware.
If you run either, I'd like to hear what you see. - Detection quality is netifyd's, not mine, and it is doing deep packet
inspection. Your call whether that suits your network.
appflow itself talks to nothing: local socket in, ubus out, no account or key.
netifyd is a separate package, so I measured it rather than vouch for it: after
five days up it had 16 sockets, all unix or packet capture, zero TCP or UDP. It
has a telemetry sink, and OpenWrt's packaged /etc/netifyd.conf ships
enable_sink = no.
ubus call appflow status shows the byte accounting, including a leaked
counter that should read 0. Three test suites in the repo, 179 checks; one
compares totals against the client interface's own counter rather than against
appflow.
Built on a Linksys EA8500, OpenWrt 25.12.5, netifyd 4.4.7. Not in the feed yet,
so an SDK build for now; see the README.
The gap I'd most like closed is heavy load: this was built and measured on a
quiet bench and I have no numbers from a busy network. Same for anything in the
UI that does nothing when you click it, which is the shape every browser side
bug in this one has taken.
Screenshots and resource numbers in the next post.


