mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-28 06:12:52 +08:00
220 lines
10 KiB
Markdown
220 lines
10 KiB
Markdown
# Contributing to luci-app-appflow
|
|
|
|
Contributions are welcome, including bug reports, and especially the two
|
|
hardware tests listed under *Where help is most useful* below.
|
|
|
|
## Reporting a problem
|
|
|
|
Useful reports include:
|
|
|
|
- Device and architecture, OpenWrt release, and `netifyd` version
|
|
(`apk info netifyd` or `opkg info netifyd`).
|
|
- What the dashboard showed versus what you expected.
|
|
- `logread -e appflowd` output covering the period.
|
|
- `ubus call appflow status`, which reports the daemon's own counters. The
|
|
useful ones are `flows.seen`, `flows.late`, `flows.shed`, `flows.purged`,
|
|
`flows.no_total`, `bytes.leaked` and `aggregates.refused`; between them they
|
|
distinguish most classes of problem immediately.
|
|
**Do not read `flows.dropped`**: it is a last-resort backstop that no input
|
|
can move, because the cap-pressure prune always frees space before it is
|
|
reached. `flows.shed` is the counter that says the flow table is too small.
|
|
|
|
If the complaint is about *where bytes were attributed*, read
|
|
[docs/DESIGN.md](docs/DESIGN.md) 2.5 first. Some attribution behaviour is
|
|
measured, understood and documented rather than accidental, and knowing which
|
|
case you are looking at saves everyone time.
|
|
|
|
## Building
|
|
|
|
This repository is a single LuCI package, with its `Makefile` at the root and
|
|
`icons/` as a second package beside it. That is the same layout upstream
|
|
`openwrt/luci` uses under `applications/`, and it drops straight into an
|
|
OpenWrt SDK tree's `package/` directory, which the build system scans
|
|
recursively.
|
|
|
|
It is **not** a feed. A feed is a directory *containing* package directories,
|
|
and adding this repo directly with `src-link` produces an empty feed index.
|
|
The CI workflow stages the packages into a temporary feed directory for
|
|
exactly this reason; see `.github/workflows/build.yml`.
|
|
|
|
# from an OpenWrt SDK tree matching your target release
|
|
./scripts/feeds update -a
|
|
./scripts/feeds install -a
|
|
git clone https://github.com/VolanticSystems/luci-app-appflow.git \
|
|
package/luci-app-appflow
|
|
make package/luci-app-appflow/compile V=s
|
|
make package/luci-app-appflow-icons/compile V=s
|
|
|
|
CI builds both packages against the OpenWrt SDK on every push and pull
|
|
request, so a build break shows up without anyone having to reproduce it
|
|
locally.
|
|
|
|
## Testing a change on a device
|
|
|
|
The daemon is a single ucode script and the views are plain JS, so the
|
|
edit/test loop does not require a rebuild:
|
|
|
|
# syntax-check the daemon before installing it
|
|
ucode -c /usr/sbin/appflowd
|
|
|
|
# after replacing a view under /www/luci-static/resources/view/appflow/
|
|
rm -f /tmp/luci-indexcache.*
|
|
/etc/init.d/rpcd restart && /etc/init.d/uhttpd restart
|
|
|
|
Please confirm a change on real hardware before sending it. This package
|
|
talks to a DPI engine whose behaviour is version-specific, and several
|
|
plausible-looking assumptions about netifyd have already turned out to be
|
|
wrong when measured (documented in DESIGN 2.4 and 2.5).
|
|
|
|
**Then run the suites.** The JavaScript one needs only Node and runs anywhere;
|
|
the two shell suites need a sandbox router and must be run one at a time.
|
|
|
|
node tests/frontend-suite.js # 59 checks, no router needed
|
|
sh tests/protocol-suite.sh # 104 checks, sandbox router, no traffic
|
|
sh tests/hardware-suite.sh # 15 checks, sandbox router, real traffic
|
|
|
|
`protocol-suite.sh` takes a group name: conservation, protocol, bounds,
|
|
attribution, hostile, lifecycle, acl, ai, hostmap, drilldown, or all
|
|
(the default).
|
|
|
|
**`hardware-suite.sh` skips its wire-ratio check when netifyd does not
|
|
capture every default-route interface**, and says which one. That check
|
|
compares the test client's own veth counter against what appflow counted,
|
|
so it silently assumes netifyd watches the path the traffic takes. On a
|
|
router with a second uplink or a VPN outside netifyd's capture list the
|
|
ratio drops and reads exactly like an attribution defect. It is not one,
|
|
and it cost real time on 2026-08-27 before the precondition was made
|
|
explicit.
|
|
|
|
`frontend-suite.js` loads the REAL `common.js` under Node through
|
|
`tests/luci-module.js`, a small `vm.Script` loader that supplies LuCI's
|
|
runtime (`_()`, `E()`, `uci`) and nothing else. No logic from the product is
|
|
copied into it. Every check in its first three groups is a regression test for
|
|
a defect that was real, shipped and fixed: a status pill that read "connected"
|
|
from a payload carrying no evidence either way, a colour lookup that a device
|
|
named `constructor` could turn into `undefined`, and a normaliser that was not
|
|
idempotent. All three were found by people reading the code, because until
|
|
2026-08-27 no test could run any of it.
|
|
|
|
`protocol-suite.sh` repoints `appflow.socket_path` at a socket it controls and
|
|
feeds the real daemon a chosen event stream, so the byte arithmetic is checked
|
|
against hand-computed totals rather than a tolerance band, and the error paths
|
|
a real agent never produces get exercised. `hardware-suite.sh` drives real
|
|
traffic and compares against the client interface's own counter in
|
|
`/sys/class/net`, which nothing in this daemon can influence. Neither replaces
|
|
the other.
|
|
|
|
Both restore what they change on exit, including on interrupt and on a dropped
|
|
SSH session, and both take an exclusive lock: they mutate global router state
|
|
and running two at once corrupts each other's fixtures.
|
|
|
|
**If you add a check, write the sabotage first.** Every check in these files
|
|
carries a `SABOTAGE:` comment naming the smallest edit to the *product* that
|
|
turns it red, and the comment was written before the assertion. That ordering
|
|
is the point: "what does this test check" answers itself in the author's own
|
|
words, which is how a test that cannot fail gets written and then read a dozen
|
|
times without anyone noticing. If you cannot construct a red state, you have
|
|
found something more interesting than a test.
|
|
|
|
## Code style
|
|
|
|
Match the surrounding code. Specifically:
|
|
|
|
- **ucode**, not shell, for daemon logic. Note ucode's integer division and
|
|
the requirement to declare functions before use.
|
|
- **No build step for the frontend.** The views are hand-written JS using
|
|
LuCI's own `form`, `ui`, `rpc`, `poll` and `dom` modules, and the charts are
|
|
hand-drawn inline SVG. Please do not introduce a bundler, a framework, or a
|
|
charting dependency.
|
|
- Keep `appflowd`'s accounting funnelled through the single `account()` choke
|
|
point. Byte conservation is a verified property of this package and it is
|
|
easy to break by accounting somewhere else.
|
|
- SPDX headers on new files, Apache-2.0.
|
|
|
|
## Commits
|
|
|
|
Prefix the subject with the package name and keep it lowercase after the
|
|
colon, matching OpenWrt convention:
|
|
|
|
luci-app-appflow: fix device table sort on equal byte counts
|
|
|
|
If you would like the change to be portable upstream to `openwrt/luci` later,
|
|
add a `Signed-off-by:` line with your real name, which is what upstream's DCO
|
|
requires.
|
|
|
|
## Translations
|
|
|
|
The string catalogue lives in `po/templates/appflow.pot`. It is generated from
|
|
the source, not written by hand: every `_('...')` call in
|
|
`htdocs/luci-static/resources/view/appflow/*.js`, plus the `title` and
|
|
`description` values in `root/usr/share/luci/menu.d/*.json` and
|
|
`root/usr/share/rpcd/acl.d/*.json`. That file selection and those keywords are
|
|
LuCI's, from `build/i18n-scan.pl` in the `openwrt/luci` tree.
|
|
|
|
To add a language, create `po/<lang>/appflow.po` from the template and
|
|
translate the `msgstr` lines. **Nothing in `Makefile` needs to change.**
|
|
`luci.mk` discovers languages by globbing `po/*`:
|
|
|
|
```make
|
|
LUCI_LANGUAGES := $(sort $(filter-out templates,$(notdir $(wildcard ${CURDIR}/po/*))))
|
|
```
|
|
|
|
and generates a `luci-i18n-appflow-<lang>` package for each one, so a new
|
|
directory is the whole of the work. The one-package-per-language split is
|
|
deliberate upstream policy, not an oversight: a router with 8 MB of flash
|
|
should not carry forty translations to use one. A combined "all languages"
|
|
package [was proposed and
|
|
rejected](https://github.com/openwrt/luci/issues/4075), because translations
|
|
depend on applications rather than the reverse and no package metadata can
|
|
express that.
|
|
|
|
Two things worth knowing before you start:
|
|
|
|
- **Keep the `%s` and `%d` specifiers, in a working order.** Several strings
|
|
interpolate values, and a catalogue that reorders or drops one produces a
|
|
broken sentence at best.
|
|
- **Category names are not in the template, and that is not a bug.** They
|
|
arrive at runtime from netifyd's own `netify-categories.json`, so they exist
|
|
in neither the JavaScript nor the daemon as literals and no scanner can find
|
|
them. Translating them needs a source-side change first; open an issue rather
|
|
than adding orphan `msgid` entries, which the next regeneration would delete.
|
|
|
|
If this package is ever accepted into `openwrt/luci`, translations arrive
|
|
through [Hosted Weblate](https://hosted.weblate.org/projects/openwrt/) instead
|
|
and this section stops being the interesting path.
|
|
|
|
## Where help is most useful
|
|
|
|
Two open items need hardware the author does not have. Both are documented in
|
|
[docs/DESIGN.md](docs/DESIGN.md) 8, with the exact observation that would
|
|
settle them:
|
|
|
|
1. **NAT dual-capture attribution.** Needs a real client routed *through* the
|
|
device so netifyd produces both an internal (`br-lan`) and an external
|
|
(`wan`) capture of the same flow. The deciding question is what netifyd
|
|
4.4.7 puts in `local_mac` / `other_mac` when `ip_nat` is true, which
|
|
determines whether the current rule double-counts or drops.
|
|
2. **Late re-point stranding.** If netifyd first reports a flow unclassified
|
|
and identifies it only later, bytes counted before that point stay under
|
|
"Unknown". This has not been observed in the wild and its magnitude is
|
|
unmeasured. A socket capture correlating per-digest `flow` /
|
|
`flow_stats` / detection-update ordering would settle it.
|
|
|
|
Reports of either, even without a patch, are genuinely valuable.
|
|
|
|
### A note for translators: argument order
|
|
|
|
LuCI's `format()` substitutes positionally in order, so a translation that needs
|
|
the arguments in a different order than English must say so explicitly. Use
|
|
`%1$d`, `%2$s` and so on rather than bare `%d`/`%s`:
|
|
|
|
msgid "%d of %d applications" call site passes (shown, total)
|
|
wrong "共 %d 个应用中的 %d 个" renders 143 of a total of 7
|
|
right "共 %2$d 个应用中的 %1$d 个"
|
|
|
|
**This class of bug passes every check either side can run.** Argument order is
|
|
a property of the relationship between the string and its call site, and the
|
|
call site is in another file, so a specifier count matches, a markup sweep finds
|
|
nothing, and the build is green. Found in the zh_Hans catalogue in PR #1, where
|
|
it turned "7 of 143" into "143 of a total of 7".
|