Write a classification rule
Classification names a flow's application from a CSV ruleset when the exporter did not name it itself.
Prerequisites
- A running collector, or the jar and a reachable ClickHouse to start one against.
- Write access to the file the collector will read, or an
http(s)://endpoint that serves it.
Steps
-
Write the ruleset. The header is fixed and every column must be present, in this order:
name;protocol;srcAddress;srcPort;dstAddress;dstPort;exporterFilter;omnidirectionalssh;tcp;;;;22;;truedns;tcp,udp;;;;53;;truemgmt;;10.0.0.0/8;;;;;falseRow order is the evaluation priority: the earliest matching row wins, in both directions of an omnidirectional rule. Put specific rules (address plus port) above broad ones (port-only).
An empty condition column means "any"; there is no wildcard. A column that is filled in but names nothing riptide can resolve gets the whole rule rejected: it classifies nothing, and the load log names it. That covers a typo (
tpc), a stray,, a*, and a protocol written as a number. Name protocols by keyword, not by number:tcp, not6. The keywords are IANA's, with two riptide still accepts under the older name: 55 isMOBILE(IANA renamed itMin-IPv4) and 84 acceptsTTPas well asIPTM. One bad keyword refuses the whole rule, sotcp,tpcis refused rather than quietly narrowed totcp.Leave
exporterFilterempty. The column is part of the required header and cannot be removed, but nothing evaluates a value in it, so a rule carrying one is rejected rather than silently applied to every exporter. Per-exporter scoping does not exist today. -
Point the collector at the file:
riptide.classification.rules=file:/etc/riptide/classification-rules.csvThe default is the bundled
classpath:classification-rules.csv. Any Spring resource location works, includinghttps://rules.internal/riptide.csv, which is how one ruleset serves a fleet without shipping a file to every host. A remote ruleset is fetched eagerly at startup, so a rules server that is down usually keeps the collector from coming up, and nothing authenticates the fetch, so protect the endpoint at the network layer. -
To apply edits without a restart, set a reload interval:
riptide.classification.reload-interval=5mAbsent or
0is the default: the resource is parsed once while the context starts, and nothing re-reads it. With an interval, the resource is polled on that schedule; unchanged bytes rebuild nothing, and a fetch that fails or a ruleset that will not parse keeps the last good rules classifying. Keep the interval above the build time your ruleset needs, see Classification tree build cost. The full reload semantics are in How configuration reloads work. -
Verify the load. Start the collector (or wait one interval) and read the log:
grep -A3 'calculated flow classification decision tree' riptide.loggrep 'Classification rules from' riptide.logExpected output, for the three-rule file above:
2026-09-23T14:37:01.762+02:00 INFO 17186 --- [ificationEngine] o.r.c.i.DefaultClassificationEngine : calculated flow classification decision treetime (ms): 48rules : 3 (including reversed rules: 5)leaves : 72026-09-23T14:37:02.049+02:00 INFO 17186 --- [ main] o.r.c.i.ClassificationRuleReloader : Classification rules from URL [file:/etc/riptide/classification-rules.csv] published: 3 rules, none rejectedrulescounts the CSV rows; the reversed count adds one per omnidirectional rule that carries a port or address condition. -
Check that nothing was rejected. A rejected rule is not a failed reload: the rest of the ruleset keeps serving and the reload counters read healthy. The one series that says part of your edit classifies nothing is
classification.rules.rejected:curl -s http://localhost:8080/metrics | grep '^classification_rules_'Expected output:
classification_rules_preprocessed 5.0classification_rules_published 3.0classification_rules_rejected 0.0With a rule whose protocol column reads
tpc, the load log names it instead:2026-09-23T14:37:01.702+02:00 ERROR 17188 --- [ificationEngine] o.r.c.i.DefaultClassificationEngine : Rule DefaultRule(name=ssh, dstAddress=null, dstPort=22, srcPort=null, srcAddress=null, protocol=tpc, exporterFilter=null, groupPosition=0, position=0, omnidirectional=true) is not valid. Ignoring rule.java.lang.IllegalArgumentException: protocol names 'tpc', which riptide cannot resolve. A rule naming a protocol it does not know would be applied to every protocol rather than the one it names, so it is refused. Name a protocol by keyword, not by number, and leave the column empty to mean any protocol....2026-09-23T14:37:02.039+02:00 WARN 17188 --- [ main] o.r.c.i.ClassificationRuleReloader : Classification rules from URL [file:/etc/riptide/classification-rules.csv] published: 2 rules, of which 1 were rejected and classify nothing: sshThe WARN names the rule; the ERROR beside it names the column and the offending value. Alert on
classification_rules_rejected > 0at/metrics.
Related
- Enrichment reference for the two keys.
- Enrichment for where the rules sit in the application ladder.
- How configuration reloads work for what a failed fetch, a 404 and an empty body each do.
- Metrics reference for the
classification.*family.
The outputs above were captured from the 0.15.0 jar with the file path substituted; a remote https:// ruleset was not exercised.