Luci-app-appflow: per-application traffic dashboard built on netifyd

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.

1 Like

Screenshots, all from a Linksys EA8500 running v1.1.0.

The live overview: rates per application and per device, a category breakdown,
and the filter boxes on both lists.

Past hour statistics, with the top ten by bandwidth and the full per-application
totals underneath.

And the filter. Typing streaming narrows both lists, and the device panel
switches its caption to say the figures come from flows currently in progress
rather than from a stored history.

On resource cost, since it always comes up: under light load on that EA8500,
about 19 MB VSZ for netifyd and about 2 MB for appflowd. netifyd is the
expensive half and grows with the number of tracked flows; on a busier aarch64
router the same pair measured about 30 MB and 6 MB. appflow adds little on top
of netifyd either way.

2 Likes