@cryptotaxi247 / netdata / commits / 356228957

Repair Network Flows documentation (#22449)

* sow: reopen SOW-0014 (network flows documentation regression) The 2026-05-07 closure of SOW-0014 was premature. The learn netlify deploy preview for PR #2852 surfaced major content errors that the prior validation pass missed: multiple statements that contradict the source code, generic flow-monitoring advice imported from research notes that does not apply to Netdata, several invented behaviours, and structural choices that read as academic / generic guidance rather than as a practical Netdata-specific guide. Move file from done/ back to current/, set Status: in-progress, and append a `## Regression - 2026-05-07` section with: - 21 findings transcribed verbatim from the user (F1..F21); - code-citation verdicts for each (per-flow sampling multiplication at decoder/record/core/record.rs:24-26 confirms F4/F5/F15 wrong; template persistence at decoder/protocol/{v9,ipfix}/templates.rs confirms F14 wrong; etc.); - root cause analysis (subagents extracted data accurately but missed behavioural framing claims; codex review focused on autocomplete code, not docs prose; validation evidence was structural, not semantic; closure was driven by "all phases done" rather than "all claims true"); - a three-phase repair plan: per-finding fixes one at a time with no batching (R1), per-page audit subagents that re-run until clean (R2), final close with a whole-section review (R3). The SOW remains in current/ until every F1..F21 has a fix landed with code citation, every page passes a per-page audit, and a whole-section review returns no new findings. * F1 docs/.map: render Network Flows Overview as the section landing page The /docs/network-flows URL rendered as an auto-generated tile grid because the section root meta block carried only `label:` -- no leaf content. Learn's `get_dir_make_file_and_recurse` then synthesised a category index page from the children. Hoist `edit_url:` and `description:` to the section root, pointing at the existing README.md. Drop the redundant child "Overview" entry that pointed to the same file. Pattern reference: every section that renders an Overview at its root URL on learn.netdata.cloud (Collecting Metrics, Dashboards and Charts, Netdata Cloud, Welcome to Netdata, etc.) carries `edit_url:` directly on the section root. Logged as F1 in SOW-0014 regression log with code references. * F2+F3 docs(network-flows): correct doubling vs bidirectional symmetry Two related findings, fixed together because they touched the same paragraphs. - The "doubling" effect (per-packet ingress+egress accounting on a single router) was conflated with bidirectional traffic symmetry. - The doubling fix said "filter by one exporter, one interface, in one direction". The "in one direction" is redundant on top of "one interface" and misleads readers into expecting another 50% halving. - The bidirectional-traffic explanation said "when you see traffic X-to-Y and Y-to-X of similar volume, that's one conversation, not two". Bidirectional conversations are usually asymmetric (downloads vs ACKs), so "similar volume" is wrong as an identification heuristic. Rewritten: - Doubling fix is now: one exporter + one interface (Input Interface OR Output Interface, pick one). Each packet crossing that interface produces exactly one record on it. - The mirror-conversation section is renamed and reframed: separate packets in each direction, separate records, typically asymmetric volumes. Per-direction accounting, not duplication. Files touched: - README.md, quick-start.md: paragraphs rewritten. - summary-sankey.md, anti-patterns.md, validation.md: "in one direction" lines fixed in place. Anti-patterns / validation will be rewritten more broadly under F14-F17 but the wrong claims are removed now. Logged as F2+F3 in SOW-0014 regression log. * F4+F5 docs(network-flows): correct sampling-rate framing (uniform-rate myth) Two related findings, fixed together because they are the same wrong claim repeated across the documentation. Source-code reality: src/crates/netflow-plugin/src/decoder/record/core/record.rs:24-26 let sampling_rate = rec.sampling_rate.max(1); rec.bytes = rec.bytes.saturating_mul(sampling_rate); rec.packets = rec.packets.saturating_mul(sampling_rate); `sampling_rate` is set per-record from each protocol's appropriate source (legacy header, v9 IE / Sampling Options Template, IPFIX IE / options, sFlow per-sample rate, or static override). Multiplication runs PER FLOW at decode time. Mixed sampling rates across exporters, interfaces, or time are handled correctly automatically. Removed false claims wherever they appeared: - README.md "What sampling does to your numbers" -- "works correctly only if all your exporters use the same sampling rate" and "the clean path: keep sampling rates uniform across your network". Rewrote the paragraph to state per-flow multiplication, explain why the UI does not surface a single rate (mixed rates have no meaningful display value; uniform rates are already known to the operator), and keep the real statistical-floor caveat (sampling can miss small / short flows regardless of rate uniformity). - field-reference.md and anti-patterns.md -- `RAW_BYTES` no longer framed as "use when sampling is uniform". Now correctly framed as the literal pre-multiplication value the exporter sent. - troubleshooting.md "Bandwidth doesn't match SNMP" -- "Mixed sampling rates ... isn't comparable to any single SNMP measurement" replaced with the actual mistake (comparing aggregates of many interfaces to a single interface SNMP counter). Per-flow multiplication is correct regardless of rate uniformity. - validation.md -- "undocumented sampling rate changes" dropped from the silent-failure intro; the "Sampling rate change" monitoring table row removed (per-flow multiplication absorbs rate changes). - investigation-playbooks.md -- "Sampling rate of the exporter (so the numbers can be interpreted)" deliverable removed and "A change in sampling rate during the analysis window invalidates the trend" caveat removed. Both wrong under per-flow scaling. - anti-patterns.md cross-protocol-counts section -- "Same goes for sampling-rate differences across exporters" removed; the protocol-counts-not-comparable point stays. The F2/F3 doubling-fix wording ("filter by exporter + interface + direction") was also wrong and got cleaned in the same anti-patterns summary table row. What stays: NetFlow v7 / v5 with rate=0 / v9 / IPFIX without a Sampling Options Template are real cases where the plugin sees no rate and undercounts. Those remain documented as the actual silent failure mode. Items deferred to F14 / F15 which rewrite their containing sections: - validation.md silent-failure items #2, #3, #5 (F14 removes them as a block). - anti-patterns.md "Ignoring the sampling rate" section + its summary-table row (F15 removes the section entirely). Logged as F4+F5 in SOW-0014 regression log with code references. * F6 docs(network-flows): remove "Globe less useful for analysis" judgement The globe and city map render the same query response with the same table beneath. The 3D projection is not "less useful for analysis"; it is a different rendering with different strengths. Replaced both occurrences with a neutral framing that the 2D city map is best for in-continent precision and the 3D globe is best when distance and great-circle paths matter (transcontinental traffic, undersea cables, intercontinental CDN routing). While here, fixed the same page's "Mirroring" subsection to drop the F2 symmetry myth ("25 top-N = 12 conversations" implied a 1:1 pairing of A->B and B->A by volume; bidirectional traffic is usually asymmetric). Logged as F6 in SOW-0014 regression log. * F7 docs(network-flows): correct UI location of the Network Flows view The docs claimed "the Network Flows tab should appear in the top navigation". The actual Netdata UI exposes Network Flows as a Function under the **Live** tab. Verified against docs/dashboards-and-charts/live-tab.md. Adopted convention: - "Open Network Flows" (verb) - "the Network Flows view" (noun) - "Click the Live tab in the top navigation; Network Flows appears in the Functions list on the right" (setup context). Swept every "Network Flows tab" reference in: - installation.md (the original bad sentence + follow-up). - troubleshooting.md, investigation-playbooks.md, anti-patterns.md. - visualization/dashboard-cards.md (4 occurrences). - visualization/summary-sankey.md. Logged as F7 in SOW-0014 regression log. * F8 netflow-plugin: per-tier retention only; remove journal-level globals Cleans up an unjustified schema redundancy. Today the plugin accepts both top-level `size_of_journal_files` / `duration_of_journal_files` under `journal` AND per-tier values under `journal.tiers.<tier>`. The runtime already uses only per-tier values; the global was just a default that flowed through `retention_for_tier()`. The two paths gave operators a configuration surface with no underlying behavioural difference. After: - `JournalConfig` carries only `tiers: JournalTierRetentionOverrides` (plus journal_dir and the query guardrails). No globals. - Each `JournalTierRetentionConfig` is `Option<ByteSize> / Option<Duration>` directly. Omitted fields fall back to the built-in tier defaults (uniform 10GB / 7d, preserving today's default behaviour). Explicit `null` disables that limit on that tier; validation still requires at least one positive limit. - The `RetentionLimitOverride<T>` enum, its serializer, deserializer, and `resolve` helpers are removed -- no longer reachable. - The orphan `parse_bytesize` helper that fed the removed clap `value_parser` is gone. Tests rewritten to exercise the per-tier-only schema: - `journal_tier_retention_uses_built_in_tier_defaults` - `journal_tier_retention_uses_per_tier_values_when_present` - `journal_rotation_size_derives_from_tier_size_budget` - `journal_rotation_size_uses_100mb_for_time_only_retention` - `journal_validation_rejects_tier_size_below_100mb` - `journal_validation_allows_time_only_retention_when_size_is_disabled` - `journal_tier_retention_null_disables_size_limit_for_that_tier_only` Plus mechanical updates to memory_tests / startup_memory_tests to use the new schema. Full crate: 427 passed, 0 failed. Documentation: - configuration.md: rewrote the `## journal` section with a per-tier-only schema. Updated the production retention profile example. Replaced the existing "Top-level retention" subsection with a "Per-tier retention" subsection. - retention-querying.md: dropped the global-form example; per-tier example only; cross-link to configuration.md. Breaking change notice: any existing user config using `journal.size_of_journal_files` / `journal.duration_of_journal_files` at the top level will now fail to deserialize (deny_unknown_fields). Migrate by moving those values under `journal.tiers.<tier>.*`. The plugin is recently shipped (PR #22439, 2026-05-07); breaking-change risk is low. Logged as F8 in SOW-0014 regression log. * F9 netflow-plugin: remove dead query_1m_max_window / query_5m_max_window Code investigation: these two journal config knobs were declared, validated, and exposed in YAML, but nothing in `src/.../query/` ever read them. The actual tier auto-pick logic in `query/planner/spans.rs::plan_query_tier_spans_recursive` selects the coarser tier strictly from window / bucket-duration alignment math -- it does not consult either knob. Verdict: dead schema. Removed both: - field declarations on `JournalConfig` - non-zero / ordering validation in validate_journal - two YAML test fixtures that mentioned them - the two configuration.md table rows + code-block lines + the "query-window limits" explanation - the retention-querying.md sentence that referenced them Updated the retention-querying explanation of tier auto-pick to match the actual behaviour: the planner uses bucket alignment, not config-driven window caps. Build + tests: 427 passed, 0 failed. Breaking change notice: any user config that set `journal.query_1m_max_window` or `journal.query_5m_max_window` will now fail to deserialize (deny_unknown_fields). The keys had no effect before; migration is delete-only. Logged as F9 in SOW-0014 regression log. * F10 netflow-plugin: keep query_max_groups, drop dead query_facet_max_values_per_field; document properly Two journal config knobs, opposite verdicts after code investigation: - query_max_groups: REAL. Read at query/service.rs:52 and threaded into the projected group accumulator via query/projected/apply.rs:48. When the accumulator's grouped_total() exceeds the limit, additional group keys are folded into a synthetic __overflow__ bucket (query/grouping/labels.rs:17, query/grouping/model/compact.rs:35) and the response carries a warning ("Group accumulator limit reached; additional groups were folded into __overflow__" at query/timeseries.rs:124). Bounds memory on accidentally wide group-by combinations. Keep; document properly. - query_facet_max_values_per_field: DEAD. Declared, validated for non-zero, but the consumer at query/facets/render.rs:19,27 uses the hardcoded constant DEFAULT_FACET_ACCUMULATOR_MAX_VALUES_PER_FIELD (query/request/constants.rs:17) instead of the config knob. The two coincidentally have the same default value (5000) but the config knob is never threaded to the consumer. Remove. Code: - types/journal.rs: removed the dead field; added a doc comment for query_max_groups explaining the __overflow__ bucket and the warning. - defaults.rs: removed default_query_facet_max_values_per_field(). - validation/journal.rs: removed the non-zero check for the dead knob. - plugin_config_tests.rs: removed validate_rejects_zero_query_facet_max_values_per_field test entirely; cleaned YAML fixtures. Stock config + README: - src/crates/netflow-plugin/configs/netflow.yaml: rewrote the journal block to use the per-tier retention form (carries over the F8 schema in the stock config); dropped both dead knobs; added clear comments for query_max_groups. - src/crates/netflow-plugin/README.md: example updated, explanatory paragraph rewritten to describe what query_max_groups actually does. Docs: - configuration.md: Query guardrails table now lists only query_max_groups, with full description of overflow behaviour. - retention-querying.md: Group-by limit section consolidated. - visualization/filters-facets.md: removed the "Facet limits" subsection that documented the dead knob. Build + tests: 426 passed, 0 failed (one dead-knob validation test removed). Breaking change notice: any user config setting journal.query_facet_max_values_per_field will now fail to deserialize. The key had no effect before; migration is delete-only. Logged as F10 in SOW-0014 regression log. * F11 docs(network-flows): author the empty IP Intelligence concept page The file existed as 0 bytes since the original documentation rewrite. The netlify deploy preview rendered it as an empty page. Multiple other pages cross-link to it (asn-resolution, static-metadata, network-identity, README, configuration, the four provider integration cards), so the empty placeholder was both a UX failure and a coverage gap. Authored from scratch, code-grounded against: - src/.../plugin_config/types/enrichment/geoip.rs (config schema) - src/.../plugin_config/runtime.rs (auto-detect path) - src/.../enrichment/data/geoip/resolver.rs (load + 30s refresh + per-IP composing-multiple-databases lookup) - src/.../enrichment/data/network/asn.rs (AS-name rendering) Page covers: the fields IP intelligence populates (with tier-preservation notes), the configuration schema, auto-detection, refresh cadence, lookup order vs the broader ASN provider chain, the four provider integration cards (DB-IP / MaxMind / IPtoASN / Custom), private-IP rendering, IPv6/IPv4 database split behaviour, staleness and accuracy caveats, and a failure-modes table. Frontmatter `learn_rel_path` matches the bgp-routing / network-identity siblings ("Network Flows/Enrichment Concepts") for now -- the source frontmatter is informational; the actual sidebar position derives from docs/.map/map.yaml, and F20 will rename the section consistently. Logged as F11 in SOW-0014 regression log. * F20 docs(network-flows): rename "Enrichment Concepts" to "Flows Enrichment" User: "'Encrichement Concepts' is a wrong title. 'Flows Enrichement' is the right one." Renamed in: - docs/.map/map.yaml line 499 (the section label that drives the actual sidebar position on Learn). - All seven `learn_rel_path` frontmatter values across docs/network-flows/enrichment/*.md. Prior state was inconsistent (4 files had "Network Flows/Enrichment", 2 had "Network Flows/Enrichment Concepts", 1 had the F11-introduced "Flows Enrichment"). Settled on the canonical "Network Flows/Flows Enrichment" everywhere. Logged as F20 in SOW-0014 regression log. * F21 docs(integrations): rename "Sources" sub-category to "Flow Protocols" User: "'Sources' is too generic. 'Flow Protocols' is the right one." Renamed in: - integrations/categories.yaml: flows.sources.name now "Flow Protocols". - The three protocol-card frontmatter values (`netflow.md`, `ipfix.md`, `sflow.md`) now declare `learn_rel_path: "Network Flows/Flow Protocols"`. - src/crates/netflow-plugin/metadata.yaml: removed three self-referencing learn URLs that pointed at /docs/network-flows/sources/{netflow,ipfix,sflow}. These links were broken before the rename (no /docs/network-flows/sources directory exists in source) and would stay broken under the new label. Replaced with the surviving "Network Flows Overview" anchor. - Re-ran integrations/gen_integrations.py + gen_docs_integrations.py to regenerate the three protocol cards. Both exit clean. Logged as F21 in SOW-0014 regression log. * F18 docs(network-flows): journalctl --namespace netdata everywhere User: "Netdata logs in namespace 'netdata'. Journalctl needs `--namespace netdata`." `-u netdata` selects the systemd UNIT and captures only stdout/stderr the unit emits to the journal. Netdata writes structured logs into a journal NAMESPACE called `netdata`. Without `--namespace netdata`, users see at most unit-level startup/shutdown messages -- not the plugin output that helps with debugging. Swept all `journalctl -u netdata` invocations to `journalctl --namespace netdata` across: - quick-start.md - troubleshooting.md (5 occurrences) - installation.md - enrichment/network-identity.md Grep clean afterwards. Logged as F18 in SOW-0014 regression log. * F15 docs(network-flows): remove "Ignoring the sampling rate" anti-pattern User: "How is it possible for users to ignore the sampling rate if we calculate the estimated volume at ingestion? You invented reasons for it. ... section must be removed." The premise was wrong on multiple counts: per-flow multiplication is always consistent (each record carries its own rate), users CAN'T "ignore" the rate because Netdata applies it automatically at decode time, and the "uniform rates required" framing is exactly the myth F4 + F5 already corrected. The two real concerns the section conflated remain documented elsewhere: - small flows missed at high sampling rates -- preserved in the Overview's "What sampling does to your numbers" section and in investigation-playbooks "Caveats". - exporter sends no rate (v7 / v5 rate=0 / v9-IPFIX without Sampling Options Template) -- preserved in troubleshooting "Bandwidth doesn't match SNMP" and in validation.md. Removed the entire section. Section numbering renumber will land with F17 once all three section removals have completed. Logged as F15 in SOW-0014 regression log. * F16 docs(network-flows): remove "Trusting GeoIP for internal IPs" anti-pattern User: "Geolocation does not position internal IPs on the map. ... section must be removed." Code-verified at src/crates/netflow-plugin/src/enrichment/data/geoip/decode.rs:40-72. `apply_geo_record` writes country/state/city/latitude/longitude only when the MMDB record carries non-empty values for those fields. For RFC 1918 / private IPs, the MMDB either has no entry or has one tagged `ip_class: "private"` with no country/city/coords. Internal IPs simply do not appear on geographic maps. The "in random countries" claim was invented. Removed the entire section. The numbering renumber lands with F17. The troubleshooting.md "Internal IPs in random countries" subsection carries the same invented claim and will be addressed under F19. Logged as F16 in SOW-0014 regression log. * F17 docs(network-flows): remove "Alerting on absolute volume thresholds" + renumber User: "Netdata does not support alerting of flows yet. Remove this section." The section's own footnote already acknowledged the issue: "Netdata's alerting on flow data is in development; for now this pattern lives in your monitoring practice, not in the plugin." So it was advice for users to apply outside Netdata, not a Netdata anti-pattern. Plus: with F15, F16, F17 all deleting sections, this commit renumbers the anti-patterns.md sections to be sequential again (1 ... 9). Dropped three rows from the summary table at the bottom (Ignored sampling, GeoIP for internal IPs, Absolute thresholds) that referred to the removed sections. Updated the cross-link in visualization/time-series.md that called out "time-shifted comparison beats absolute thresholds" -- replaced with a general anti-patterns reference since the specific section is gone. Logged as F17 in SOW-0014 regression log. * F19 docs(network-flows): troubleshooting cumulative cleanup User: "This page has a mix of all the above issues: sampling, geoip, etc." Surgical fixes after F2-F18 land: - Removed "Internal IPs in random countries" subsection -- same invented claim as F16 (GeoIP does not position internal IPs on maps; code-verified at src/.../enrichment/data/geoip/decode.rs:40-72). - Rewrote "Things that look like bugs but aren't" entries to remove the F2/F3 "filter to one direction" framing and the F16 GeoIP myth. The two doubling-related bullets now point at exporter+interface filtering and at Source/Destination ASN filtering for bidirectional. Renamed "tier-0" -> "raw-tier" for consistency with the field reference. Items kept (framing already correct after earlier findings): - "Sampling rate not honoured by the exporter" -- correct (F4/F5). - Doubling references in the SNMP-mismatch table (F2/F3). - ASN provider chain debug recipe. - Decapsulation destructive-on-non-tunnel. Items deferred to per-page audit (R2): - "Cisco's default template refresh is 30 minutes" -- vendor- specific claim; verify against current Cisco IOS-XE/XR docs in R2. Logged as F19 in SOW-0014 regression log. * F14 docs(network-flows): validation.md rewrite to remove invented risks User: "I think the entire 'Validation and Data Quality' is completely off. It mentions again sampling rates, etc." Code-verified facts driving the rewrite: - Per-flow sampling multiplication at decode time (decoder/record/core/record.rs:24-26): users don't need to monitor "sampling rate change" or "sampling rate misinterpretation". - Template persistence across restarts (decoder/protocol/v9/templates.rs:106 + decoder/protocol/ipfix/templates/data.rs:67): users don't need to monitor "template loss after collector restart". - UDP buffer overflow alert already exists (src/health/health.d/udp_errors.conf:6-19): kernel-level UDP drops are signalled by an existing system alert, not a silent failure. Rewrite: - New "What you actually need to watch" table -- five real failure modes (kernel UDP drops via existing alert, exporter stopped sending, wrong interfaces being exported, exporter sampling without communicating the rate, stale MMDB). - Removed the three invented silent-failure items (sampling misinterpretation, sampling change, template loss). - Removed the "Internal IP enrichment validation" section (F16 confirmed GeoIP does not position internal IPs). - Renamed "Sampling rate sanity check" to "Sampling rate verification" with the uniform-rate myth gone; kept the practical RAW_BYTES vs BYTES comparison. - Removed the "Template cache health" subsection. - Renamed the alerting table to "Plugin-side signals worth alerting on" and clarified these are signals the plugin exposes for the operator, not "silent failures" the dashboard hides. Logged as F14 in SOW-0014 regression log. * F13 docs(network-flows): rewrite Sizing and Capacity Planning as a practical guide User: "People want sizing and planning directions. This is not an academic paper, not a blog." Rewrote the page from scratch around the user's seven requirements: - plugin cap (single-thread post-decode; ~25k flows/s sustained; ISP-scale anchor) - how ingestion rate maps to storage (single table, 4 rows; ~800 bytes/flow empirical) - raw tier dominates; bound it; example per-tier production config - fast NVMe is the right call for the raw tier; slow storage means shorter retention - memory: routing-trie footprint + page-cache headroom - query speed: indexed fields fast; FTS = full scan of raw tier - distributed deployment as the scaling answer (one agent per router/site; federated via Netdata Cloud; no central aggregation needed for flow data) Removed: - All benchmark tables and methodology. Engineering benchmark numbers remain in src/crates/netflow-plugin/README.md. - The "Bounding storage for capacity planning" formula derivation (ignored tier rollover and dedup; partly invalid). Logged as F13 in SOW-0014 regression log. * F12+F22 docs(network-flows): split retention/querying; add Visualization Overview User on F12: "Retention is closer to configuration and querying is closer to visualization. ... If you need to put generic visualization rules, these should be a generic 'Visualization/Overview' page, to explain FTS, sharing, grouping, etc." User on F22: "The 'Section index' in the overview page is not needed. Learn already shows the index as a side bar." F12: - New visualization/overview.md page collects "how queries work", "group-by limit and overflow", "full-text search", "URL sharing", filtering pointers, "picking the right view". - retention-querying.md slimmed to retention-only (tiers, what survives rollup, tier auto-pick, "no data", what forces raw tier, default retention misconfig). Sidebar label renamed to "Retention and Tiers". - map.yaml: Visualization sub-section root now carries edit_url + description pointing at visualization/overview.md (matches the F1 pattern). Retention sidebar label renamed. F22: - Removed the "## Section index" block from README.md. The Learn sidebar already renders the same hierarchy. The "Where to start" role-based pointer block stays (not a sidebar duplicate). The "specific feature in depth" bullet now points readers at the sidebar. Logged as F12 + F22 in SOW-0014 regression log. * docs(network-flows): drop the trailing "use the sidebar" bullet from Overview Per user: the bullet was redundant with the F22 cleanup. Sidebar guidance is implicit; the four role-based bullets above are the intended "where to start" entry points. * docs(network-flows): address Phase R2 Round-1 audit findings Documentation audits against source code surfaced a small number of inaccuracies and a few low-severity polish items across the most-edited pages. Fixed in-place, with the same surgical-edit policy as Phase R1. Critical/high severity: - configuration.md: drop the false claim that listener / protocols / journal keys can also appear at the top level. The flatten attribute is clap-only (CLI flag flatten), not serde; with deny_unknown_fields the YAML schema rejects unknown top-level keys. Stock file uses the nested form. - validation.md: replace the wrong `dRcv` ss column reference with the actual `d<N>` value inside the `skmem:(...)` line (the sock_drop counter from iproute2 ss output). - validation.md: replace the unreachable RAW_BYTES vs BYTES dashboard comparison with the supported approach -- group by the Sampling Rate field. RAW_BYTES is filtered from supported_flow_field_names and is not surfaced as a default table column. Medium severity: - README.md: rename "Source ASN" to "Source AS Name" in the default group-by description; the actual default uses SRC_AS_NAME, which the dashboard renders as "Source AS Name". Same fix swept through quick-start.md, investigation-playbooks.md, troubleshooting.md, visualization/time-series.md, visualization/summary-sankey.md so the doc text matches the dashboard label. - README.md, quick-start.md, anti-patterns.md, validation.md, visualization/summary-sankey.md: soften the "doubling by default" framing. Both ingress + egress export is a common configuration but not a property of the protocol, and vendor best practice is ingress-only. Wording now reflects that. - validation.md: note the udp_errors alert ships as `to: silent` by default; operators must override `to:` to receive notifications. - validation.md: add a `du -sh` example for cross-checking on-disk tier sizes (cross-link to sizing-capacity.md). Low-severity polish: - README.md: classifier expression language is an Akvorado-compatible subset (matches classifiers.md framing). - README.md: rollup tier note now mentions the dropped fields, so the tier auto-pick claim is not over-broad. - sizing-capacity.md: drop the "after rotation and compression" qualifier on the 800-bytes/flow figure (the bench window is too short to reflect rotation cycles); rephrase the ingest description and the "spinning rust" sentence; soften the BMP/BioRIS RSS guidance to a rough estimate with bench numbers anchored. - configuration.md: document the query_max_groups / query-max-groups alias; document enrichment.geoip.optional and the abort-vs-warn semantics; clarify that default_sampling_rate and override_sampling_rate both accept a single integer or a per-prefix map; mention the 100 MB rotation-size fallback when size_of_journal_files is null. - anti-patterns.md: add the missing "What it costs" line to sections 8 and 9 for shape consistency with sections 1-7. * docs(network-flows): address Phase R2 Round-2 audit findings Second round of per-page audits against source code surfaced several critical inaccuracies plus the usual long tail of low-severity polish. Critical / high: - retention-querying.md: rewrite the tier auto-pick rules. The previous thresholds were inverted ("8h20m and longer -> 1-hour") -- the actual planner walks coarsest first and accepts the first tier with at least 100 aligned buckets, so >=100h -> 1h, 8h20m..<100h -> 5m, 100min..<8h20m -> 1m. Verified at src/crates/netflow-plugin/src/query/planner/timeseries.rs:34-46 and TIMESERIES_MIN_BUCKETS=100 at src/crates/netflow-plugin/src/query/request/constants.rs:18. - retention-querying.md: rewrite the rollup-preserved field list. The previous list claimed AS path, BGP communities, MPLS labels, MACs, and post-NAT addresses survive into rollups -- they do not. The actual rollup tier carries only the fields defined in src/crates/netflow-plugin/src/tiering/rollup/schema/fields/defs/{core,exporter,interface,network,presence}.rs; every other field is raw-only and forces the query to the raw tier. - retention-querying.md: correct the "no data" / fallback semantics. The planner does NOT fall back to a coarser tier for raw-only queries -- rollups don't carry the field, so the span returns empty. Confirmed at src/crates/netflow-plugin/src/query/planner/prepare.rs:25-28 and src/crates/netflow-plugin/src/query/planner/spans.rs:99-105 (lower_fallback_candidate_tiers returns &[] for Raw). - validation.md: replace the "group by Sampling Rate field" verification with an SNMP-magnitude cross-check. The SAMPLING_RATE field is filtered out of supported_flow_field_names (src/crates/netflow-plugin/src/query/request/constants.rs:80), excluded from the groupable set (src/crates/netflow-plugin/src/query/fields/rules.rs:33), and not available as a facet (src/crates/netflow-plugin/src/facet_catalog.rs:123). Users cannot pick it from the dashboard. The honest verification path is SNMP magnitude or a per-prefix override. - validation.md: correct the alert threshold framing. RcvbufErrors is read with RRD_ALGORITHM_INCREMENTAL (src/collectors/proc.plugin/proc_net_netstat.c:400-434), so the value Netdata stores is per-second. The "lookup: average -1m absolute" plus "$this > 10" in src/health/health.d/udp_errors.conf means >10 errors/SECOND averaged over 1 minute, not >10/minute as previously stated. - quick-start.md: correct the field labels used in the doubling fix step from "Input Interface Name" / "Output Interface Name" to "Ingress Interface Name" / "Egress Interface Name", matching the display labels in src/crates/netflow-plugin/src/presentation/display.rs:39-40. Same fix swept across anti-patterns.md, troubleshooting.md, validation.md, investigation-playbooks.md, and visualization/summary-sankey.md. - quick-start.md: drop the false "60-second template refresh" claim for softflowd. softflowd's `expint` flag controls expiry-check interval, not template refresh; the NetFlow v9 template interval in softflowd is a compile-time default (NF9_DEFAULT_TEMPLATE_INTERVAL=16 in netflow9.c) with no CLI knob. - quick-start.md: complete the Juniper J-Flow snippet. The previous example defined a sampling instance but never bound it to a forwarding card and never set a sampling rate, so it would not produce flows. Add `set chassis fpc 0 sampling-instance NETDATA` and `set forwarding-options sampling instance NETDATA input rate 1000`, with a short note explaining the FPC binding requirement. - quick-start.md: correct the dashboard navigation step from "click the Network Flows tab" to "open the Live tab and select Network Flows from the Functions list". - ip-intelligence.md: correct the GeoLite2 / DB-IP / IPtoASN cadence claims. DB-IP Lite is monthly. MaxMind GeoLite2 City/Country update on weekdays; GeoLite2 ASN updates daily since June 2024. IPtoASN is not MMDB -- it is a public-domain TSV feed that includes both ASN and country and must be converted to MMDB before the plugin can read it (the plugin only supports MMDB). - ip-intelligence.md: correct the dual-stack guidance. Most current providers ship a single dual-stack MMDB; the previous "configure both an IPv4 file and a separate IPv6 file" advice was misleading. - ip-intelligence.md: clarify the asn_providers chain semantics. The `geoip` provider is a terminal "use 0" shortcut -- when reached the AS number is forced to 0 (the AS name still comes from the MMDB lookup independently). Confirmed at src/crates/netflow-plugin/src/enrichment/asn/resolve.rs:75-109. - ip-intelligence.md: distinguish the database-composition rules. ASN fields use pure last-wins; geo fields are written only when the matching record has a non-empty value, so a later database with an empty city does not overwrite an earlier database's city (src/crates/netflow-plugin/src/enrichment/data/geoip/decode.rs:40-72). Medium / low: - sizing-capacity.md: rewrite the example raw-tier YAML so the size cap matches the page's own 25k flows/s framing. The previous example used 200GB / 24h; at 25k flows/s the size cap would fire after ~2.8h, not 24h. Now uses 2TB / 24h with a paragraph on how to scale down for lighter loads, and explains the size-vs-duration relationship. - validation.md: drop the misleading `decoder_state_dir` config-key reference (it is a derived path, not a user-facing key); add the `-n` flag to the ss command to keep the port numeric in the output. - anti-patterns.md / troubleshooting.md / validation.md / visualization/summary-sankey.md: consistent doubling-framing hedge ("a common configuration; vendor best practice is ingress-only") across all five docs that mention doubling. - anti-patterns.md: summary-table row "Doubled aggregate" qualified with "(when ingress + egress are both exported)". - configuration.md: correct the `override_sampling_rate` default example from `{}` to `~` (the actual schema default is None). * docs(network-flows): apply Phase R2 Round-3 critical fixes retention-querying.md: - correct the Time-Series sub-100-min fallback. The planner walks coarsest-first and falls back to the 1-minute tier (TierKind::Minute1) when no tier has >=100 aligned buckets, not to raw. Verified at src/crates/netflow-plugin/src/query/planner/timeseries.rs:39-46. - split the field-eligibility list. The "force raw" set is exactly RAW_ONLY_FIELDS plus V9_*/IPFIX_* prefixes (src/crates/netflow-plugin/src/query/fields/rules.rs:5-11 + src/crates/netflow-plugin/src/query/request/constants.rs:46-57). The previously included AS path / BGP communities / MPLS labels / MAC addresses / NAT addresses do NOT switch tier; they are dropped from rollup output and return null on rollup queries. Page now describes both classes separately. troubleshooting.md: - correct Cisco's default template refresh from "30 minutes" to "600 seconds (10 minutes)" -- this is the IOS / IOS-XE Flexible NetFlow `template data timeout` default. - replace the wrong "/proc/net/udp ... RcvbufErrors column" recipe. The /proc/net/udp file lists open sockets without per-socket drop counters; the kernel-wide RcvbufErrors total lives under the Udp: line of /proc/net/snmp, which is what Netdata's proc.plugin reads (src/collectors/proc.plugin/proc_net_netstat.c:1521). Also dropped the contradictory "30-60 seconds" template-refresh hint. ip-intelligence.md: - correct the ASN composition rule. The previous "last-wins for ASN, but geo writes only when non-empty" framing was wrong -- src/crates/netflow-plugin/src/enrichment/data/geoip/decode.rs:3-28 filters empty / zero values for ASN fields just like geo, so both sets follow the same "last database with a non-empty value wins" rule. - correct the GeoLite2 cadence. MaxMind's documentation publishes City and Country twice weekly (Tuesday and Friday); GeoLite2 ASN moved to every weekday in June 2024. * docs(network-flows): collapse flows sub-categories to Flow Protocols + Enrichment Methods The previous category tree split enrichment-method integrations across three separate sub-categories (IP Intelligence, BGP Routing, Network Identity Sources), which made the integrations page navigation harder than it needed to be and did not match the conceptual model: the operator is choosing a *data source* for enrichment, regardless of what kind of data it produces. Collapsed under flows.enrichment-methods: - ip-intelligence: dbip, maxmind, iptoasn, custom-mmdb - bgp-routing: bmp, bioris - network-identity: aws-ip-ranges, gcp-ip-ranges, azure-ip-ranges, netbox, generic-ipam flows.sources keeps its existing membership (netflow / ipfix / sflow) and the "Flow Protocols" name unchanged. Both YAML files validated. Per-card content merges and the new cross-cutting "Enrichment" + "Enrichment Intel Downloader" pages land in subsequent commits. * docs(network-flows): merge concept-page content into the 11 enrichment integration cards Round-by-round, agent-per-card merges. Each card absorbed the durable provider-specific content from its corresponding concept page; cross-cutting content was extracted for the new "Enrichment" page (separate commit). The agents verified every behavioural claim against current source code at file:line and every upstream URL by WebFetch -- not a mechanical sweep. IP Intelligence: - dbip: framed as the auto-detected default, monthly Lite cadence, CC-BY-4.0, populated-fields breakdown (geo + ASN), AS0 Private/Unknown labels driven by the DB-IP-built ip_class flag, raw-tier-only city/lat/lon. - maxmind: GeoLite2 vs commercial GeoIP2 split, account-id + license-key auth, twice-weekly Tuesday/Friday cadence for City/Country, every-weekday for ASN since June 2024, geoipupdate setup. Important correction: the bundled topology-ip-intel-downloader does NOT support MaxMind (only dbip and iptoasn); the previous card's hint at the netdata downloader was misleading. - iptoasn: PDDL public-domain feed, hourly TSV cadence (the previous card said "daily" -- wrong), bundled topology-ip-intel-downloader natively supports the TSV->MMDB conversion (correcting an earlier prompt assumption to the contrary), three setup examples including ASN-only and combined with DB-IP geo. - custom-mmdb: reframed as the escape hatch for operators producing custom MMDBs (CIDR overlays, internal AS labels). Lists the field names the decoder reads from any MMDB, with file:line evidence; cites mmdbwriter libraries; recommends `optional: true` during build iteration. BGP Routing: - bmp: BMP-v3-only handling -- v1/v2 silently dropped (previously implicit). RFC 8671 cited for JunOS post-policy support since 18.3R1, separately from RFC 7854 which was previously lumped together. Cisco IOS-XE BMP added (was missing). Nokia SR OS added. JunOS minimum 13.3 documented. No IANA-registered port for BMP. - bioris: corrected the topology -- Netdata connects to a USER-RUN bio-rd cmd/ris/ daemon over user-supplied gRPC, NOT directly to RIPE RIS. The user's bio-rd daemon does the BGP/BMP peering with upstream sources. No shipped collector list (ris_instances is required and operator-supplied). Memory cost (~hundreds of MB per peer for full-table feeds), no eviction, raw-tier-only AS path / communities. Network Identity: - aws-ip-ranges: schema reference (top-level + per-entry), live cadence softened ("whenever AWS IP space changes, often several times per day" rather than the folklore "every 15 minutes" -- AWS docs do not promise any fixed schedule). Three jq examples including network_border_group as site. Plugin's actual config key is `transform`, not `jq_program` (was wrong in the migration prompt). - gcp-ip-ranges: cloud.json vs goog.json comparison; cloud.json today reports `service: "Google Cloud"` uniformly so per-service pivots are not possible from this file. No fixed Google cadence in the docs. - azure-ip-ranges: URL rotates weekly. Service Tag Discovery REST API as authoritative alternative. API data lags JSON file by up to four weeks; new IPs aren't used for at least one week after publication. Three workaround patterns documented honestly. - netbox: documented breaking change in NetBox 4.2 -- the `site` foreign key on Prefix was replaced with the generic `scope` field; the previous example used `(.site.name // "")` which silently breaks on 4.2+. New card ships scope-aware (4.x) and legacy (3.x/4.0/4.1) examples plus a fallback `(.scope.name // .site.name // "")`. Two token formats documented (legacy v1 hex and v2 nbt_<key>.<token>). - generic-ipam: full RemoteNetworkSourceConfig schema (13 options including proxy, tls.enable, tls.verify, tls.skip_verify with explicit "rejected by validation" notes). Honest call-outs: POST is sent without a body (fetch.rs:11-17), interval floored at 60s by service.rs:73, TLS verification cannot be disabled (validation/enrichment.rs:183-192). Categories: each card now under flows.enrichment-methods (the previous ip-intelligence / bgp-routing / network-identity sub-categories were collapsed in the previous commit). Generated .md files updated via integrations/gen_docs_integrations.py. * docs(network-flows): add 3 new enrichment integration cards + Intel Downloader page Three new cards under flows.enrichment-methods, completing the "every enrichment method is an integration" model. Each card was authored by an agent that read the corresponding concept page and the source code, then produced a metadata.yaml entry with every claim cited at file:line. The agents flagged real inconsistencies between the concept pages and the code; corrections were absorbed into the cards. static_metadata: - Three configuration surfaces: enrichment.metadata_static.exporters, enrichment.networks, and enrichment.override_sampling_rate (plus default_sampling_rate for the distinction). - Field-population table tied to source at apply/metadata.rs:41-53, data/network/write.rs:93-125, apply/metadata.rs:78-97. - Five corrections vs the previous concept page: boundary "undefined" vs numeric 0 are byte-identical in output; lookup priority is dominated by prefix specificity, not source-kind; override_sampling_rate matches the UDP datagram source IP; the "networks merges last and wins" framing was overstated. classifiers: - Two evaluation surfaces: exporter_classifiers + interface_classifiers, the latter called twice per flow (once per interface side). - Akvorado-compatible expression-language *subset* — explicitly only the operators and actions implemented at enrichment/classifiers/parse.rs; every example in the card uses syntax verified against the parser and the existing test suite. - Output normalisation includes "+" (concept page only listed ". -"); static metadata short-circuits classifier evaluation (enrichment/classify.rs:117-119, :150-154); first-write-wins per slot (runtime/eval/action.rs:43-46); default cache 5m with >=1s validation (defaults.rs:46-48, validation/enrichment.rs:10-12). decapsulation: - Two modes (srv6, vxlan) per protocol.rs:50-57; default none. - Three transport feeders: NetFlow v9 IE 104 (decoder.rs:80), IPFIX IE 315 (decoder.rs:74), sFlow SampledHeader (sflow/record.rs:44-69). - Inner parsers at decoder/common.rs:3-18 (VXLAN port 4789) and :35-63 (SRv6 SRH walker); merge points at packet/transport.rs:21-33 and record/packet/parse/transport.rs:14-21. - Vendor-verification finding: Cisco IOS-XE / IOS-XR collect datalink frame-section could NOT be verified (cisco.com 403'd anonymous WebFetch and Akvorado's IOS-XE recipe deliberately omits L2 frame- section export). The card flags Cisco support as unverified and instructs operators to validate by template inspection. Juniper inline-monitoring with datalink-frame-size confirmed via the Akvorado mirror; sFlow header sampling confirmed via the project's decoder. intel-downloader.md (new operator-tool page): - Documents /usr/sbin/topology-ip-intel-downloader -- supported sources, CLI flags, atomic replacement, auto-detect integration with the netflow plugin's 30s reload window. - Findings: no packaged systemd timer or cron file (operators must install their own; page provides a starter unit + timer pair); MaxMind support confirmed absent (no license_key field anywhere in config.go; only iptoasn:combined and dbip:asn-lite/country-lite/ city-lite are recognised by builtInSource); MaxMind users directed to geoipupdate as the alternative. - Hidden capability documented: interesting_cidrs config knob lets operators stamp public CIDRs as netdata.ip_class = "interesting" in both ASN and geo MMDBs (write.go:228-246). The categories.yaml category for all 14 enrichment methods is now flows.enrichment-methods (previously split across ip-intelligence / bgp-routing / network-identity). * docs(network-flows): replace 7 concept pages with one Enrichment page; update map.yaml Restructure phase 5+8+9: collapse the docs/network-flows/enrichment/ directory (7 concept pages: asn-resolution, bgp-routing, classifiers, decapsulation, ip-intelligence, network-identity, static-metadata) into ONE consolidated cross-cutting page at docs/network-flows/enrichment.md, plus per-method integration cards (already added in previous commits). The new Enrichment page (447 lines) is the single home for cross-cutting concepts that span every enrichment method: - Order of evaluation per flow record (8-step pipeline cited at apply/resolve.rs:5-50 and init.rs:50-64). - The two provider chains (asn_providers / net_providers, the geoip terminal "use 0" shortcut, the AS-number-vs-AS-name distinction). - Composition rules: specificity dominates, ties to static, per-field non-empty-wins merge. - The MMDB shared mechanism (auto-detect path order, last-non-empty-wins composition, 30s signature reload, IPv4/IPv6 dual-stack handling). - Network sources operational properties (fetch loop, 60s floor, jq schema, deny_unknown_fields, TLS-no-disable, no pagination, no auth helpers, POST-without-body, journal diagnostics). - Static-metadata-blocks-classifiers semantics. - Classifier evaluation surfaces and ordering. - Decapsulation inner-packet override. - Routing overlay (BMP+BioRIS shared trie). - Cross-method operational properties: refresh windows, restart behaviour, no in-process freshness signal, empty-tree disables enricher, rollup tier survival table, geographic accuracy, sampling-rate knobs, integration test gap. The page resolved several discrepancies the original concept pages had against the source code -- e.g. the "static metadata > classifiers > network sources > GeoIP > BGP routing" precedence claim was misleading (actual rule: specificity dominates, ties to static, merge primitive is non-empty-wins); the bmp alias for routing lives at providers.rs:10,12, not validation/enrichment.rs; the GeoIP terminal shortcut sets the AS number to 0 but the AS *name* still comes from the MMDB independently; POST is sent without a body (fetch.rs:11-17); interval is silently floored at 60s (service.rs:73). Each correction is cited at file:line. map.yaml: removed the "Flows Enrichment" sub-section (which contained the 7 deleted pages); added two new entries between Configuration and Field Reference: - "Enrichment" -> docs/network-flows/enrichment.md - "Enrichment Intel Downloader" -> docs/network-flows/intel-downloader.md Cross-references updated in 8 surviving pages to point at either the new Enrichment page (for cross-cutting concepts) or the relevant integration cards under src/crates/netflow-plugin/integrations/ (for per-method specifics): configuration, validation, intel-downloader, quick-start, installation, troubleshooting, visualization/maps-globe. The 7 deleted concept pages had their durable content fully absorbed into the Enrichment page (cross-cutting) and the 14 integration cards (per-method); each integration card cites file:line evidence for every behavioural claim and was re-verified against current source code by its merge agent. * docs(network-flows): repoint cross-references to the new Enrichment page The merge agents preserved Learn-URL cross-references from the original concept pages (e.g. https://learn.netdata.cloud/docs/network-flows/enrichment/ ip-intelligence). Those URLs now 404 because the seven concept pages were collapsed into one. Sweep them all to point at the new consolidated https://learn.netdata.cloud/docs/network-flows/enrichment page, which covers the cross-cutting concepts (MMDB shared mechanism, asn_providers chain, network-source operational properties, etc.) that the per-method references were calling out. Generated cards regenerated via gen_docs_integrations.py to flush the new URL into the .md outputs. * Repair Network Flows documentation * Format netflow plugin tests * Address Network Flows documentation regressions * Fix Network Flows review regressions * Move raw rebuild scan off async startup path

Costa Tsaousis committed May 8, 2026 at 03:00 UTC 3562289573617273f3c68e91f9420389ce72c592
85 files changed +10523 -2894
.agents/skills/codacy-audit/SKILL.md
+4
@@ -90,6 +90,10 @@ No issues on PR #22423.
90
91 For a PR with findings, the script emits a clustered TSV summary. Default grouping is `--by pattern`; switch to `--by tool`, `--by severity`, `--by file`, or `--by category` for other angles. The JSON dump under `.local/audits/codacy/` carries the full issue payload for follow-up jq queries.
92
93 +Operational note: large Codacy PR issue arrays must be passed to `jq` via a
94 +temporary file and `--slurpfile`, not `--argjson`, because shell argument-size
95 +limits can fail before `jq` starts.
96 +
97 ## Path discipline
98
99 This skill follows `<repo>/.agents/sow/specs/sensitive-data-discipline.md`:
.agents/skills/codacy-audit/how-tos/INDEX.md
+1
@@ -6,4 +6,5 @@ Skipping this rule means the next assistant repeats the analysis from scratch --
6
7 ## Entries
8
9 +- [fetch-large-pr-issue-list](fetch-large-pr-issue-list.md) -- fix or verify the `jq: Argument list too long` failure mode by passing large Codacy issue arrays through a temporary file and `jq --slurpfile`.
10 - [reproduce-pr-22423-markdownlint](reproduce-pr-22423-markdownlint.md) -- reproduce the 864 markdownlint findings PR #22423 saw on its first CI run, locally via `analyze-local.sh --tool markdownlint`.
.agents/skills/codacy-audit/how-tos/fetch-large-pr-issue-list.md new
+32
@@ -0,0 +1,32 @@
1 +# Fetch a large Codacy PR issue list
2 +
3 +Use this when `pr-issues.sh <PR>` fails locally with:
4 +
5 +```text
6 +jq: Argument list too long
7 +```
8 +
9 +## Why it happens
10 +
11 +`codacyaudit_pr_issues` can return a large JSON array. Passing that array to
12 +`jq --argjson data "$issues_array"` sends the whole payload through the shell's
13 +argument vector and can exceed the OS limit before `jq` starts.
14 +
15 +## Correct pattern
16 +
17 +Write the JSON array to a temporary file and load it through `jq --slurpfile`:
18 +
19 +```bash
20 +issues_tmp="$(mktemp "${TMPDIR:-/tmp}/codacy-pr-issues-XXXXXX")"
21 +trap 'rm -f "$issues_tmp"' EXIT
22 +printf '%s' "$issues_array" > "$issues_tmp"
23 +
24 +jq -n \
25 + --arg pr "$PR" \
26 + --arg fetched_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
27 + --argjson total "$total" \
28 + --slurpfile data "$issues_tmp" \
29 + '{pr: ($pr | tonumber), fetched_at: $fetched_at, total: $total, data: $data[0]}'
30 +```
31 +
32 +This keeps token bytes out of stdout and avoids shell argument-size limits.
.agents/skills/codacy-audit/scripts/pr-issues.sh
+5 -2
@@ -74,17 +74,20 @@ echo -e "${CA_GRAY}[pr-issues] fetching issues for PR #${PR} ...${CA_NC}" >&2
74 # Wrap raw issues array into an envelope with metadata.
75 issues_array="$(codacyaudit_pr_issues "$PR")"
76 total="$(printf '%s' "$issues_array" | jq 'length')"
77 +issues_tmp="$(mktemp "${TMPDIR:-/tmp}/codacy-pr-issues-XXXXXX")"
78 +trap 'rm -f "$issues_tmp"' EXIT
79 +printf '%s' "$issues_array" > "$issues_tmp"
80
81 jq -n \
82 --arg pr "$PR" \
83 --arg fetched_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
84 --argjson total "$total" \
82 - --argjson data "$issues_array" \
85 + --slurpfile data "$issues_tmp" \
86 '{
87 pr: ($pr | tonumber),
88 fetched_at: $fetched_at,
89 total: $total,
87 - data: $data
90 + data: $data[0]
91 }' > "$OUTPUT"
92
93 echo -e "${CA_GREEN}[pr-issues]${CA_NC} wrote ${total} issue(s) to ${OUTPUT}" >&2
.agents/skills/integrations-lifecycle/SKILL.md
+23
@@ -41,6 +41,28 @@ does the in-app integrations page get its data?".
41 `metadata.yaml` by the pipeline. Edit `metadata.yaml`, run
42 the pipeline, commit the regenerated artifacts.
43
44 + `src/collectors/COLLECTORS.md` is the source page for
45 + Learn's "Monitor anything with Netdata" page. It is generated
46 + from `integrations/integrations.js` by
47 + `integrations/gen_doc_collector_page.py`; never hand-edit its
48 + integration tables. To update that page, change the source
49 + metadata/categories or the generator, then run
50 + `gen_integrations.py` and `gen_doc_collector_page.py`.
51 +
52 + Treat short descriptions as public product copy. Catalog
53 + descriptions must say what the integration is and what it
54 + monitors, enriches, exports, authenticates, or discovers.
55 + For collector-like metadata, the first sentence of
56 + `overview.data_collection.metrics_description` is the catalog
57 + sentence used by generated pages such as `COLLECTORS.md`.
58 + Start that sentence with a user-facing action phrase such as
59 + `Monitor...`, `Collect...`, `Enrich network flows with...`, or
60 + `Annotate network flows with...`.
61 + Do not use the catalog description for variables, defaults,
62 + option names, setup instructions, limits, or troubleshooting.
63 + Put those details in setup, default-behavior, examples, or
64 + troubleshooting fields. See `description-authoring.md`.
65 +
66 2. **`integrations/*.md` files are GENERATED. DO NOT EDIT.**
67 Every per-integration `.md` opens with a
68 `<!--startmeta ... endmeta-->` block that ends with
@@ -89,6 +111,7 @@ does the in-app integrations page get its data?".
111 |---|---|
112 | `pipeline.md` | The 4-stage pipeline graph, every script, every artifact, the CI workflows. |
113 | `schema-reference.md` | Exhaustive per-field reference for all 12 JSON Schemas under `integrations/schemas/`. |
114 +| `description-authoring.md` | Product-copy rules for `metadata.yaml` descriptions and the Monitor Anything table text. |
115 | `per-type-matrix.md` | One-row-per-integration-type quick lookup: source paths, validator, render keys, output location. |
116 | `artifacts-and-banners.md` | Every committed and gitignored artifact; banner conventions; symlink rules. |
117 | `ibm-d.md` | The `contexts.yaml` -> `metadata.yaml` chain for ibm.d modules. |
.agents/skills/integrations-lifecycle/artifacts-and-banners.md
+30 -8
@@ -134,14 +134,36 @@ the generated artifact.
134
135 ### `src/collectors/COLLECTORS.md`, `SECRETS.md`, `SERVICE-DISCOVERY.md`
136
137 -**No banner.** They open with their marketing headers
138 -(`# Monitor anything with Netdata`, `# Secrets Management`,
139 -`# Service Discovery`). This is a known oversight -- there is
140 -no DO-NOT-EDIT marker on these files even though they are
141 -generated. Editing them directly will be silently overwritten
142 -on the next CI run (for COLLECTORS.md and SECRETS.md). For
143 -SERVICE-DISCOVERY.md the absence of CI wiring means manual
144 -edits stick until someone runs the script.
137 +**No generated-file banner.** `COLLECTORS.md` currently opens
138 +with `<!-- markdownlint-disable-file -->` and then its marketing
139 +header (`# Monitor anything with Netdata`). `SECRETS.md` and
140 +`SERVICE-DISCOVERY.md` open with their marketing headers
141 +(`# Secrets Management`, `# Service Discovery`). This is a
142 +known oversight -- there is no DO-NOT-EDIT marker on these
143 +files even though they are generated. Editing them directly
144 +will be silently overwritten on the next CI run (for
145 +COLLECTORS.md and SECRETS.md). For SERVICE-DISCOVERY.md the
146 +absence of CI wiring means manual edits stick until someone
147 +runs the script.
148 +
149 +`COLLECTORS.md` is the Learn page titled "Monitor anything with
150 +Netdata". It is generated by `integrations/gen_doc_collector_page.py`
151 +from `integrations/integrations.js`. The generator groups normal
152 +collector entries by children of `data-collection` and also treats
153 +the top-level `flows` category as a first-class section named
154 +`Network Flows`; without that special case, NetFlow / IPFIX / sFlow
155 +and flow enrichment integrations either appear in the wrong generic
156 +section or are easy to miss.
157 +
158 +To update the page, do not edit the generated table rows in
159 +`src/collectors/COLLECTORS.md`. Edit the source
160 +`metadata.yaml` / `integrations/categories.yaml` or the
161 +generator, then run:
162 +
163 +```bash
164 +python3 integrations/gen_integrations.py
165 +python3 integrations/gen_doc_collector_page.py
166 +```
167
168 ### ibm.d-generated files
169
.agents/skills/integrations-lifecycle/description-authoring.md new
+133
@@ -0,0 +1,133 @@
1 +# Description Authoring
2 +
3 +Metadata descriptions are public product copy. They appear on
4 +Learn, in integration cards, in generated umbrella pages, and in
5 +some in-app surfaces. Write them for an operator scanning a catalog,
6 +not for a developer reading implementation notes.
7 +
8 +## Catalog Description Contract
9 +
10 +The Monitor Anything table does not read a dedicated
11 +`catalog_description` field. `integrations/gen_doc_collector_page.py`
12 +extracts the first sentence from the generated `## Overview` section
13 +and falls back to `meta.monitored_instance.description` only when
14 +overview text is unavailable.
15 +
16 +For collector-like integrations, that means the first sentence of
17 +`overview.data_collection.metrics_description` **is** the catalog
18 +description. Write that sentence first, deliberately, before adding
19 +detail for the full integration page.
20 +
21 +That first sentence must:
22 +
23 +- start with an active user-facing verb or action phrase;
24 +- describe what the integration is;
25 +- describe what it monitors, enriches, exports, authenticates, or
26 + discovers;
27 +- be stable without knowing the user's configuration;
28 +- be short enough for a table cell;
29 +- use user-facing product language.
30 +
31 +That first sentence must not:
32 +
33 +- describe a configuration option, variable, default value, or setting;
34 +- start with "Set ...", "Configure ...", "When enabled ...", or
35 + similar setup language;
36 +- contain placeholders such as `<tier>`, `<key>`, or
37 + `[[ variables.foo ]]`;
38 +- describe limits, sizing, retention, troubleshooting, or caveats;
39 +- mention internal tests, implementation state, reviewer notes, or
40 + future work.
41 +
42 +Required first-sentence style:
43 +
44 +- Collectors: `Monitor <thing> ...`, `Collect <data> from <thing> ...`,
45 + `Keep an eye on <thing> ...`.
46 +- Flow sources: `Collect network flow records from <protocol/exporter> ...`.
47 +- Flow enrichment sources: `Enrich network flows with <fields/context> from
48 + <source> ...`.
49 +- Flow labeling/classification sources: `Annotate network flows with
50 + <labels> from <source/rules> ...`.
51 +- Exporters: `Export Netdata metrics to <destination> ...`.
52 +- Service discovery: `Discover <targets> from <source> ...`.
53 +
54 +Avoid leading with the provider's publication mechanism (`AWS publishes ...`,
55 +`Microsoft publishes ...`, `Set option ...`). Those facts may be useful in
56 +the full page, but the catalog sentence should first tell users what Netdata
57 +does for them.
58 +
59 +## Where Details Belong
60 +
61 +Use the right metadata field for the job:
62 +
63 +| Content | Field |
64 +|---|---|
65 +| What the integration is / what data it provides | First sentence of `overview.data_collection.metrics_description` |
66 +| How collection works | `overview.data_collection.method_description` |
67 +| Defaults and auto-detection | `overview.default_behavior.auto_detection.description` |
68 +| Limits, retention, sizing, and cardinality | `overview.default_behavior.limits.description` |
69 +| CPU, memory, disk, or network impact | `overview.default_behavior.performance_impact.description` |
70 +| Configuration settings | `setup.configuration.options.list[].description` |
71 +| Example-specific behavior | `setup.configuration.examples.list[].description` |
72 +| Failure modes and fixes | `troubleshooting.problems.list[].description` |
73 +
74 +Configuration option descriptions are allowed to describe settings.
75 +Catalog descriptions are not.
76 +
77 +## Good And Bad Examples
78 +
79 +Bad catalog description:
80 +
81 +```yaml
82 +metrics_description: |
83 + Set `protocols.decapsulation_mode` to `srv6` or `vxlan`.
84 +```
85 +
86 +Good catalog description:
87 +
88 +```yaml
89 +metrics_description: |
90 + Enrich network flows with inner source and destination endpoints from VXLAN or SRv6 encapsulated traffic.
91 +```
92 +
93 +Bad catalog description:
94 +
95 +```yaml
96 +metrics_description: |
97 + Empty `asn_database` and `geo_database` values enable auto-detection.
98 +```
99 +
100 +Good catalog description:
101 +
102 +```yaml
103 +metrics_description: |
104 + Enrich network flows with ASN and geographic context from DB-IP Lite MMDB databases.
105 +```
106 +
107 +Bad catalog description:
108 +
109 +```yaml
110 +metrics_description: |
111 + The `journal.tiers.<tier>.duration_of_journal_files` setting controls retention.
112 +```
113 +
114 +Good catalog description:
115 +
116 +```yaml
117 +metrics_description: |
118 + Collect network flow records from NetFlow exporters such as routers, switches, and firewalls.
119 +```
120 +
121 +## Review Checklist
122 +
123 +Before committing `metadata.yaml` changes:
124 +
125 +1. Regenerate `src/collectors/COLLECTORS.md`.
126 +2. Read the table row description for the integration.
127 +3. Confirm it answers "what is this integration?" without relying on
128 + setup context.
129 +4. Move option names, defaults, variables, and limits out of the
130 + catalog sentence and into the proper setup or default-behavior
131 + field.
132 +5. Keep the first sentence useful even when rendered alone in a list,
133 + card, search result, or generated catalog.
.agents/skills/integrations-lifecycle/gotchas.md
+6 -3
@@ -276,9 +276,12 @@ just out-of-date docs.
276 ## Umbrella pages have NO DO-NOT-EDIT banner
277
278 `src/collectors/COLLECTORS.md`, `src/collectors/SECRETS.md`,
279 -`src/collectors/SERVICE-DISCOVERY.md` all open with marketing
280 -headers (e.g. `# Monitor anything with Netdata`). None has a
281 -`<!--startmeta` block, none has any DO-NOT-EDIT comment.
279 +`src/collectors/SERVICE-DISCOVERY.md` have no generated-file
280 +warning. `COLLECTORS.md` opens with
281 +`<!-- markdownlint-disable-file -->` and then the marketing
282 +header (`# Monitor anything with Netdata`); the other two open
283 +with their marketing headers. None has a `<!--startmeta` block,
284 +none has any DO-NOT-EDIT comment.
285
286 A maintainer who edits these files directly will have their
287 edits silently overwritten on the next CI run (for
.agents/skills/integrations-lifecycle/how-tos/INDEX.md
+5
@@ -25,6 +25,11 @@ violation.
25 | Topic | Slug | Notes |
26 |---|---|---|
27 | Adding a new top-level `integration_type` (peer of collector, logs, exporter, etc.) | [adding-new-integration-type](adding-new-integration-type.md) | 8-step recipe: schema, pipeline, templates, categories.yaml, map.yaml, source metadata, downstream repos. Covers what to clone from existing types, the hardcoded-vs-derived `learn_rel_path` distinction, and the `integration_placeholder` mechanism. |
28 +| Keeping Network Flows on the Learn "Monitor anything" page | [monitor-anything-network-flows](monitor-anything-network-flows.md) | Explains that `src/collectors/COLLECTORS.md` is generated by `gen_doc_collector_page.py`, why top-level `flows` must be treated as a section, and how to validate the generated `Network Flows` section. |
29 +
30 +Description authoring rules live in `../description-authoring.md`
31 +because they apply to all metadata authors, not only to one
32 +analysis how-to.
33
34 ## How to add a how-to
35
.agents/skills/integrations-lifecycle/how-tos/monitor-anything-network-flows.md new
+49
@@ -0,0 +1,49 @@
1 +# Keep Network Flows on Monitor Anything
2 +
3 +**One-line summary:** `src/collectors/COLLECTORS.md` is the generated Learn "Monitor anything with Netdata" page; Network Flows entries appear there only when `integrations/gen_doc_collector_page.py` treats the top-level `flows` category as a section.
4 +
5 +## What Updates The Page
6 +
7 +`src/collectors/COLLECTORS.md` is generated, not hand-authored. The local and CI command is:
8 +
9 +```bash
10 +python3 integrations/gen_integrations.py
11 +python3 integrations/gen_doc_collector_page.py
12 +```
13 +
14 +`gen_integrations.py` produces the runtime
15 +`integrations/integrations.js` catalog. `gen_doc_collector_page.py`
16 +then reads that catalog and atomically replaces
17 +`src/collectors/COLLECTORS.md`. The file is committed because it is
18 +the source document that Learn ingests for the "Monitor anything with
19 +Netdata" page.
20 +
21 +The CI workflow that checks documentation PRs also runs this path before Learn ingest:
22 +
23 +- `.github/workflows/check-markdown.yml` runs `gen_integrations.py`, `gen_docs_integrations.py`, `gen_doc_collector_page.py`, and `gen_doc_secrets_page.py` before `learn/ingest/ingest.py`.
24 +- `.github/workflows/generate-integrations.yml` runs the same generator family after metadata changes land on `master` and opens a regeneration PR.
25 +
26 +## Why Flows Need Explicit Handling
27 +
28 +Most Monitor Anything sections are children of `data-collection` in `integrations/categories.yaml`. Network Flows is different:
29 +
30 +- `integrations/categories.yaml` defines top-level `flows` with children `flows.sources` and `flows.enrichment-methods`.
31 +- `src/crates/netflow-plugin/metadata.yaml` uses those categories for NetFlow / IPFIX / sFlow and flow enrichment entries.
32 +- `integrations/gen_doc_collector_page.py` therefore must treat top-level `flows` as a section, otherwise those entries are not grouped as `Network Flows` on Monitor Anything.
33 +
34 +## Validation
35 +
36 +After changing flow metadata or category handling, run:
37 +
38 +```bash
39 +python3 integrations/gen_integrations.py
40 +python3 integrations/gen_docs_integrations.py
41 +python3 integrations/gen_doc_collector_page.py
42 +rg -n '^### Network Flows|\\[NetFlow\\]|\\[Static Metadata\\]|\\[Decapsulation\\]' src/collectors/COLLECTORS.md
43 +```
44 +
45 +Expected result: `src/collectors/COLLECTORS.md` contains a `### Network Flows` section listing NetFlow, IPFIX, sFlow, and the enrichment integrations.
46 +
47 +## How I Figured This Out
48 +
49 +Read `integrations/categories.yaml`, `src/crates/netflow-plugin/metadata.yaml`, `integrations/gen_doc_collector_page.py`, `src/collectors/COLLECTORS.md`, `.github/workflows/check-markdown.yml`, and `.github/workflows/generate-integrations.yml`; regenerated `COLLECTORS.md` and checked for the `Network Flows` section.
.agents/skills/integrations-lifecycle/pipeline.md
+46 -6
@@ -253,21 +253,35 @@ NOT used by CI; CI always runs without `-c` (full regen).
253
254 Repo path: `integrations/gen_doc_collector_page.py`.
255
256 -Reads `integrations/integrations.js` (`:37-46`). Walks the
257 -category tree; the "section-level" categories are children of
258 -`data-collection` (`:82` -- `parent == 'data-collection'`).
256 +Reads `integrations/integrations.js` (`:38-47`). Walks the
257 +category tree; the "section-level" categories are normally
258 +children of `data-collection`, plus the top-level `flows`
259 +category (`:82-86`). `flows` is deliberately included because
260 +Network Flows entries cover both flow protocols and enrichment
261 +inputs, so the Monitor Anything page must list them together
262 +under a `Network Flows` section instead of dropping them into
263 +`Other`.
264
265 Writes `src/collectors/COLLECTORS.md` (committed). This is the
266 "Monitor anything with Netdata" umbrella marketing page that
262 -lists every collector in tabular form, grouped by category.
267 +lists every collector and Network Flows integration in tabular
268 +form, grouped by section. The write path is
269 +`generate_collectors_md()` (`:565-584`), which renders the
270 +header plus dynamic tables and atomically replaces the file.
271
272 ### Notable behaviors
273
266 -- Sort order: "Linux first, Other last" (`:279-303`).
274 +- Sort order: "Linux first, Other last" (`:285-301`); Network
275 + Flows follows its position in `integrations/categories.yaml`
276 + because it is treated as a section.
277 - Description extraction: `extract_description_from_overview`
278 reads `## Overview` body, uses the first sentence (`:143-183`);
279 falls back to `meta.monitored_instance.description`; final
270 - fallback `Monitor <name>`.
280 + fallback `Monitor <name>`. Because this text becomes the
281 + Monitor Anything table description, the first sentence of the
282 + overview must describe the integration itself, not a setting,
283 + variable, default, limit, or troubleshooting detail. See
284 + `description-authoring.md`.
285 - Slug for table links: `to_slug(display_name)` -- lowercase,
286 spaces to `_`, `/` to `-`, strips parentheses (`:213-215`).
287 - Hardcoded marketing anchors: `_render_tech_navigation`
@@ -426,3 +440,29 @@ scripts directly during active development.
440 `integrations.js` into its source. See `in-app-contract.md`.
441 8. Learn's ingest pulls the new `integrations/foo.md` on its
442 3-hourly schedule. See the `learn-site-structure` skill.
443 +
444 +## End-to-end: Monitor Anything / `COLLECTORS.md`
445 +
446 +1. `metadata.yaml` entries declare
447 + `meta.monitored_instance.categories`.
448 +2. `python3 integrations/gen_integrations.py` validates those
449 + categories against `integrations/categories.yaml`, renders the
450 + integration content, and writes the runtime
451 + `integrations/integrations.js` catalog.
452 +3. `python3 integrations/gen_doc_collector_page.py` reads
453 + `integrations/integrations.js`, groups integrations by Monitor
454 + Anything section, and writes `src/collectors/COLLECTORS.md`.
455 +4. `check-markdown.yml` runs the same generator before Learn
456 + ingest on PRs, so broken generated `COLLECTORS.md` content
457 + (for example, unresolved links) blocks the PR. It does not
458 + diff-check that the committed `COLLECTORS.md` file is fresh.
459 +5. `generate-integrations.yml` runs the same generator after
460 + metadata changes land on `master` and opens the
461 + `integrations-regen` PR if committed generated artifacts drift.
462 +6. Learn ingests `src/collectors/COLLECTORS.md` as the
463 + "Monitor anything with Netdata" page.
464 +
465 +For Network Flows specifically, keep the top-level `flows`
466 +category handling in `gen_doc_collector_page.py`. Without it,
467 +NetFlow / IPFIX / sFlow and enrichment entries will not appear
468 +as a coherent `Network Flows` section on Monitor Anything.
.agents/skills/integrations-lifecycle/recipes/add-go-collector.md
+10 -1
@@ -68,7 +68,8 @@ modules:
68 overview:
69 data_collection:
70 metrics_description: |
71 - One paragraph: what we collect.
71 + First sentence: Monitor <thing> or collect <data> from <thing>.
72 + Add more detail after that only if it is useful on the full page.
73 method_description: |
74 One paragraph: how we collect it.
75 supported_platforms:
@@ -130,6 +131,14 @@ modules:
131 - name: <dim>
132 ```
133
134 +The first sentence of `metrics_description` is also used as the
135 +description in generated catalog-style pages such as
136 +`src/collectors/COLLECTORS.md`. Keep it product-facing and stable:
137 +start with an action phrase, describe the integration, and do not
138 +describe configuration variables, defaults, limits, or setup steps.
139 +Put those details in the setup, default-behavior, examples, or
140 +troubleshooting fields.
141 +
142 Hit every required field. The validator is strict (fatal on
143 warnings). Refer to `../schema-reference.md` for the
144 exhaustive field list.
.agents/skills/integrations-lifecycle/schema-reference.md
+9 -1
@@ -49,6 +49,14 @@ The "what is this thing" descriptor used by every per-integration entry.
49 | `instance.icon_filename` | string | yes | -- | learn / www / in-app | Path under `${NETDATA_REPOS_DIR}/website/themes/tailwind/static/img/` (icon repo). |
50 | `instance.variables` | object | no | values: string / int / bool / number | all rendered text | Triggers two-pass Jinja templating; see `pipeline.md`. |
51
52 +Do not use `instance.variables` or option/default text to build the
53 +short catalog description. For collector-like integrations, the
54 +Monitor Anything table description is extracted from the first
55 +sentence of the generated overview, usually
56 +`overview.data_collection.metrics_description`. See
57 +`description-authoring.md` before writing or reviewing description
58 +fields.
59 +
60 ### `$defs.keywords`
61
62 Search-keyword array.
@@ -135,7 +143,7 @@ where each module is one collector integration.
143 | `modules[].meta.related_resources.integrations.list[].module_name` | string | conditional | required if `monitored_instance_name` is set (Draft-7 `dependencies` at `collector.json:61-63`) | related-integrations | See `gotchas.md` for non-obvious dependency semantics. |
144 | `modules[].meta.related_resources.integrations.list[].monitored_instance_name` | string | no | -- | related-integrations | For cgroups multi-instance disambiguation. |
145 | `modules[].meta.info_provided_to_referring_integrations.description` | string | yes | markdown | rendered when ANOTHER collector references this one | The "what THIS collector says when referenced from another." |
138 -| `modules[].overview.data_collection.metrics_description` | string | yes | markdown | learn / www | The "what we collect" prose. |
146 +| `modules[].overview.data_collection.metrics_description` | string | yes | markdown | learn / www / Monitor Anything first-sentence source | The "what we collect" prose. First sentence is the catalog description and must start with an active user-facing phrase such as `Monitor...`, `Collect...`, `Enrich network flows with...`, or `Annotate network flows with...`. Do not start with setup, variables, defaults, limits, or option names. |
147 | `modules[].overview.data_collection.method_description` | string | yes | markdown | learn / www | The "how we collect" prose. |
148 | `modules[].overview.supported_platforms.include` | array<string> | yes (may be empty) | platform names | learn (`overview/collector.md:12-26`) | Allow-list. |
149 | `modules[].overview.supported_platforms.exclude` | array<string> | yes (may be empty) | platform names | learn (`overview/collector.md:12-26`) | Block-list. |
.agents/skills/learn-pr-preview/SKILL.md new
+115
@@ -0,0 +1,115 @@
1 +---
2 +name: learn-pr-preview
3 +description: Use only when the user explicitly asks to build, run, preview, inspect, or validate learn.netdata.cloud locally using the contents of a PR or documentation branch before merge. Do not trigger for ordinary docs edits unless the user asks for a local Learn preview.
4 +---
5 +
6 +# learn-pr-preview
7 +
8 +Build and inspect a local Learn site from a PR's documentation content without
9 +dirtying the real Learn checkout.
10 +
11 +Always load `learn-site-structure` first. If the PR touches `metadata.yaml` or
12 +generated integration pages, also load `integrations-lifecycle`.
13 +
14 +## Rules
15 +
16 +- Trigger only on an explicit preview/build/inspect request.
17 +- Do not run ingest directly in a dirty Learn worktree.
18 +- Use an isolated preview directory under `/tmp` or the repo's gitignored
19 + `.local/`.
20 +- Record the Learn branch and commit used for the preview.
21 +- Copy PR source content into an isolated source directory. Prefer committed
22 + PR content; if validating uncommitted work, copy tracked modified files and
23 + only intentional untracked docs files after checking `git status --short`.
24 +- Save any preview server PID and kill only that PID when stopping it.
25 +- Treat site-wide warnings as evidence, but separate pre-existing global Learn
26 + warnings from PR-specific Network Flows or docs warnings.
27 +
28 +## Workflow
29 +
30 +Set paths:
31 +
32 +```bash
33 +REPO_ROOT="$(git rev-parse --show-toplevel)"
34 +PR_NUMBER="<pr-number>"
35 +LEARN_REPO="${NETDATA_REPOS_DIR}/learn"
36 +PREVIEW_ROOT="${TMPDIR:-/tmp}/netdata-learn-preview-pr-${PR_NUMBER}-$(date +%Y%m%d%H%M%S)"
37 +SOURCE_COPY="${PREVIEW_ROOT}/netdata-source"
38 +LEARN_COPY="${PREVIEW_ROOT}/learn"
39 +```
40 +
41 +Create isolated copies:
42 +
43 +```bash
44 +mkdir -p "${SOURCE_COPY}"
45 +git -C "${REPO_ROOT}" ls-files -co --exclude-standard -z \
46 + | rsync -a --from0 --files-from=- --ignore-missing-args "${REPO_ROOT}/" "${SOURCE_COPY}/"
47 +
48 +git clone --branch "$(git -C "${LEARN_REPO}" branch --show-current)" \
49 + --single-branch "${LEARN_REPO}" "${LEARN_COPY}"
50 +git -C "${LEARN_COPY}" rev-parse HEAD
51 +```
52 +
53 +Install ingest dependencies in the isolated preview:
54 +
55 +```bash
56 +python3 -m venv "${PREVIEW_ROOT}/venv"
57 +"${PREVIEW_ROOT}/venv/bin/python" -m pip install --upgrade pip
58 +"${PREVIEW_ROOT}/venv/bin/python" -m pip install \
59 + -r "${LEARN_COPY}/.learn_environment/ingest-requirements.txt"
60 +```
61 +
62 +If the real Learn checkout has compatible `node_modules`, symlink it to avoid a
63 +fresh install:
64 +
65 +```bash
66 +ln -s "${LEARN_REPO}/node_modules" "${LEARN_COPY}/node_modules"
67 +```
68 +
69 +Run ingest with the PR source:
70 +
71 +```bash
72 +cd "${LEARN_COPY}"
73 +"${PREVIEW_ROOT}/venv/bin/python" ingest/ingest.py \
74 + --local-repo "netdata:${SOURCE_COPY}" \
75 + --ignore-on-prem-repo \
76 + --use_plain_https \
77 + --fail-links-netdata
78 +```
79 +
80 +Build with the Netlify-pinned runtime:
81 +
82 +```bash
83 +NODE_OPTIONS=--max_old_space_size=4096 \
84 + npx -y -p node@22.14.0 -p yarn@1.22.22 yarn build
85 +```
86 +
87 +Serve the static build for inspection:
88 +
89 +```bash
90 +python3 -m http.server 3030 --bind 127.0.0.1 --directory "${LEARN_COPY}/build"
91 +```
92 +
93 +Or run it in the background with a PID file:
94 +
95 +```bash
96 +python3 -m http.server 3030 --bind 127.0.0.1 --directory "${LEARN_COPY}/build" \
97 + >"${PREVIEW_ROOT}/http.log" 2>&1 &
98 +echo "$!" > "${PREVIEW_ROOT}/http.pid"
99 +```
100 +
101 +Inspect representative pages in a browser. For docs PRs, check:
102 +
103 +- the changed hand-authored pages;
104 +- generated integration pages affected by `metadata.yaml`;
105 +- the category index page;
106 +- at least one page that previously failed ingest, MDX, or link checks.
107 +
108 +Report:
109 +
110 +- Learn branch and commit used;
111 +- ingest command and exit status;
112 +- build command and exit status;
113 +- inspected URLs and HTTP/browser status;
114 +- PR-specific warnings or failures;
115 +- pre-existing global warnings separately.
.agents/skills/learn-site-structure/how-tos/INDEX.md
+1 -4
@@ -23,12 +23,9 @@ violation.
23
24 ## Catalog
25
26 -(empty -- entries grow as assistants encounter
27 -not-yet-documented questions)
28 -
26 | Topic | Slug | Notes |
27 |---|---|---|
31 -| -- | -- | -- |
28 +| Preview a documentation PR locally | `preview-documentation-pr-locally.md` | Isolated Learn ingest/build/browser inspection from PR source content before merge. |
29
30 ## How to add a how-to
31
.agents/skills/learn-site-structure/how-tos/preview-documentation-pr-locally.md new
+119
@@ -0,0 +1,119 @@
1 +# Preview a documentation PR locally
2 +
3 +Question: how do you build Learn locally from a PR's docs content and inspect
4 +it in a browser before merging?
5 +
6 +Use an isolated preview directory. Do not run ingest directly in a dirty Learn
7 +checkout because ingest cleans and regenerates `docs/`.
8 +
9 +## Evidence
10 +
11 +- `${NETDATA_REPOS_DIR}/learn/ingest/ingest.py:2632` defines `--local-repo`.
12 +- `${NETDATA_REPOS_DIR}/learn/ingest/ingest.py:2790` cleans the ingest temp
13 + folder and `${NETDATA_REPOS_DIR}/learn/ingest/ingest.py:2793` cleans the
14 + Learn `docs/` tree before publishing.
15 +- `${NETDATA_REPOS_DIR}/learn/ingest/ingest.py:2808` copies a local source repo
16 + into the ingest temp folder when `--local-repo netdata:<path>` is used.
17 +- `${NETDATA_REPOS_DIR}/learn/.learn_environment/ingest-requirements.txt:1`
18 + lists the Python dependencies for ingest.
19 +- `${NETDATA_REPOS_DIR}/learn/package.json:8` defines the Docusaurus build
20 + script.
21 +- `${NETDATA_REPOS_DIR}/learn/netlify.toml:5` pins the Netlify runtime to
22 + Node `22.14.0`, Yarn, and `NODE_OPTIONS=--max_old_space_size=4096`.
23 +
24 +## Procedure
25 +
26 +1. Pick the PR source repo and the Learn checkout:
27 +
28 + ```bash
29 + REPO_ROOT="$(git rev-parse --show-toplevel)"
30 + PR_NUMBER="<pr-number>"
31 + LEARN_REPO="${NETDATA_REPOS_DIR}/learn"
32 + PREVIEW_ROOT="${TMPDIR:-/tmp}/netdata-learn-preview-pr-${PR_NUMBER}-$(date +%Y%m%d%H%M%S)"
33 + SOURCE_COPY="${PREVIEW_ROOT}/netdata-source"
34 + LEARN_COPY="${PREVIEW_ROOT}/learn"
35 + ```
36 +
37 +2. Copy the PR source into an isolated directory:
38 +
39 + ```bash
40 + mkdir -p "${SOURCE_COPY}"
41 + git -C "${REPO_ROOT}" ls-files -co --exclude-standard -z \
42 + | rsync -a --from0 --files-from=- --ignore-missing-args "${REPO_ROOT}/" "${SOURCE_COPY}/"
43 + ```
44 +
45 + This includes tracked files and intentional untracked files that are not
46 + ignored by Git, while still excluding ignored build and scratch output.
47 +
48 +3. Clone the local Learn checkout into the preview directory:
49 +
50 + ```bash
51 + git clone --branch "$(git -C "${LEARN_REPO}" branch --show-current)" \
52 + --single-branch "${LEARN_REPO}" "${LEARN_COPY}"
53 + git -C "${LEARN_COPY}" rev-parse HEAD
54 + ```
55 +
56 +4. Install ingest dependencies:
57 +
58 + ```bash
59 + python3 -m venv "${PREVIEW_ROOT}/venv"
60 + "${PREVIEW_ROOT}/venv/bin/python" -m pip install --upgrade pip
61 + "${PREVIEW_ROOT}/venv/bin/python" -m pip install \
62 + -r "${LEARN_COPY}/.learn_environment/ingest-requirements.txt"
63 + ```
64 +
65 +5. Reuse Learn `node_modules` when compatible:
66 +
67 + ```bash
68 + ln -s "${LEARN_REPO}/node_modules" "${LEARN_COPY}/node_modules"
69 + ```
70 +
71 +6. Run ingest with the PR content and fail on broken links from `netdata`:
72 +
73 + ```bash
74 + cd "${LEARN_COPY}"
75 + "${PREVIEW_ROOT}/venv/bin/python" ingest/ingest.py \
76 + --local-repo "netdata:${SOURCE_COPY}" \
77 + --ignore-on-prem-repo \
78 + --use_plain_https \
79 + --fail-links-netdata
80 + ```
81 +
82 +7. Build with the Netlify-pinned runtime:
83 +
84 + ```bash
85 + NODE_OPTIONS=--max_old_space_size=4096 \
86 + npx -y -p node@22.14.0 -p yarn@1.22.22 yarn build
87 + ```
88 +
89 +8. Serve and inspect:
90 +
91 + ```bash
92 + python3 -m http.server 3030 --bind 127.0.0.1 --directory "${LEARN_COPY}/build"
93 + ```
94 +
95 + Open changed pages, generated integration pages, and the affected category
96 + index. Confirm HTTP 200, the expected H1, no 404 page, and no MDX/runtime
97 + error.
98 +
99 +## Reporting
100 +
101 +Report these facts:
102 +
103 +- Learn branch and commit used.
104 +- Ingest command and exit status.
105 +- Build command and exit status.
106 +- Browser-inspected URLs and status.
107 +- PR-specific warnings/failures.
108 +- Pre-existing site-wide warnings separately.
109 +
110 +## How I Figured This Out
111 +
112 +Read `${NETDATA_REPOS_DIR}/learn/ingest/ingest.py`,
113 +`${NETDATA_REPOS_DIR}/learn/package.json`,
114 +`${NETDATA_REPOS_DIR}/learn/netlify.toml`, and
115 +`${NETDATA_REPOS_DIR}/learn/.learn_environment/ingest-requirements.txt`.
116 +Validated the flow by building an isolated Learn preview from a Network Flows
117 +documentation PR, running ingest with `--fail-links-netdata`, running
118 +`yarn build`, serving the static build, and checking representative pages in a
119 +browser.
.agents/sow/done/SOW-0014-20260506-netflow-sflow-ipfix-documentation-guide.md
+2292 -5
@@ -4,18 +4,20 @@
4
5 Status: completed
6
7 -Closed 2026-05-07. All acceptance criteria delivered or explicitly rejected. Phase 1C (AI skills cross-links) was rejected as not mandatory by user decision -- recorded in Validation. Architectural pivot to a top-level `flows` integration_type with 14 cards is recorded as Decision 10. Companion changes shipped in netdata PR #22439, learn PR #2854 (merged), and a deferred cloud-frontend `Update integrations.js` refresh.
7 +Reopened 2026-05-07 after the netlify deploy preview for learn PR #2852 surfaced major content errors that the prior validation pass missed. The closure on 2026-05-07 (Status: completed) was premature: the docs contained multiple statements that contradicted the source code, generic flow-monitoring advice imported from research notes that did not apply to Netdata, and several invented behaviours. The regression was repaired and revalidated by 2026-05-08; see the `## Regression - 2026-05-07` section and closeout notes at the end of this file.
8 +
9 +Reopened 2026-05-08 after PR #22449 review and CI reported additional issues after the SOW had been marked completed and moved to `done/`. The open items are tracked in `## Regression - 2026-05-08` and include automated review threads, `yamllint`, `check-documentation`, Codacy triage, a code-only review subagent requested by the user, and a new user-requested local Learn preview skill/workflow.
10
11 ## Requirements
12
13 ### Purpose
14
13 -Provide DevOps/SREs a complete, authoritative documentation and integration infrastructure for Netdata's network flow analysis. Documentation covers only tested and verified features. Untested features (BMP, BioRIS, Network Sources, Topology drilldown) are deferred to a follow-up SOW after testing/validation. The documentation must include sizing, capacity planning, and optimization guidance so businesses can make informed deployment decisions.
15 +Provide DevOps/SREs a complete, authoritative documentation and integration infrastructure for Netdata's network flow analysis. Documentation covers the Network Flows feature set that exists in this PR and excludes non-existent behavior such as topology drilldown. The documentation must include sizing, capacity planning, and optimization guidance so businesses can make informed deployment decisions.
16
17 ### User Request
18
17 -Phase 1: Document tested features only. Include sizing/benchmarking/capacity planning.
18 -Phase 2 (follow-up SOW): Test BMP, BioRIS, Network Sources, Topology drilldown, then document them.
19 +Phase 1: Document the Network Flows feature set, including sizing/benchmarking/capacity planning and the enrichment integrations exposed through `metadata.yaml`.
20 +Phase 2 (follow-up SOW): Add or revise documentation only for behavior that is not present in this PR, such as a future topology drilldown if it is implemented.
21
22 ### Assistant Understanding
23
@@ -588,4 +590,2289 @@ Open follow-ups, ordered by priority:
590
591 ## Regression Log
592
591 -None yet.
593 +## Regression - 2026-05-07
594 +
595 +### What broke
596 +
597 +The user previewed the learn netlify deploy at
598 +`https://deploy-preview-2852--netdata-docusaurus.netlify.app/`
599 +and found the documentation contains multiple statements that
600 +contradict the source code, several invented behaviours that
601 +were never verified, and structural choices that read as
602 +academic / generic flow-monitoring advice rather than as a
603 +practical guide to Netdata's flow plugin specifically. The
604 +prior closure on 2026-05-07 claimed every behavioural claim had
605 +been verified against the code; that claim is false.
606 +
607 +### Why previous validation missed it
608 +
609 +1. Subagent investigations produced data extracts (fields,
610 + tier preservation, IE maps) accurately but did NOT catch
611 + behavioural framing claims. Behavioural claims live in
612 + absences (no code says "do this") and were imported from
613 + the research notes at `.agents/knowledge/Network Traffic
614 + Analysis with Flow Data.md` as Netdata-specific without
615 + verifying against actual code paths.
616 +2. Three rounds of codex review focused on the autocomplete
617 + code change. None reviewed the documentation prose.
618 +3. Validation evidence was structural (grep for forbidden
619 + topics, count of pages, pipeline exit codes) rather than
620 + semantic (every behavioural claim cited to code). The
621 + "code is the source of truth" rule from Decision 9 was
622 + stated but not enforced per claim.
623 +4. SOW closure was driven by "all phases done" rather than
624 + "all claims true". The completion was premature.
625 +
626 +### Findings (verbatim from user, plus code-citation verdict)
627 +
628 +These findings must be addressed one at a time, no batching.
629 +For each: investigate against code, surgical edit, grep the
630 +rest of the docs for the same pattern, fix every instance.
631 +Each finding gets its own SOW execution-log entry below with
632 +file:line and the code citation that proves the fix.
633 +
634 +#### F1 -- /docs/network-flows landing page shows tiles not Overview
635 +
636 +> this page should be the overview, it appears on click of
637 +> the "Network Flows" main menu. Instead it shows all
638 +> integrations as tiles:
639 +> https://deploy-preview-2852--netdata-docusaurus.netlify.app/docs/network-flows
640 +
641 +Need to investigate how learn renders the section root.
642 +Likely cause: section root has no leaf content, so the auto-
643 +grid renderer in `learn/ingest/ingest.py:get_dir_make_file_and_recurse`
644 +generates a category index. Repair: ensure the Overview file
645 +is the section landing page, not the category-index grid.
646 +
647 +#### F2 -- Overview false bidirectional symmetry claim
648 +
649 +> > When you see "traffic from your country to a foreign
650 +> > country" and "traffic from that foreign country to your
651 +> > country" of similar volume, you're looking at one
652 +> > conversation, not two.
653 +>
654 +> The statement "of similar volume" is wrong. The two
655 +> directions of traffic are usually not expected to have
656 +> similar volume. It can be omitted and the phrase must be
657 +> normalized because it may or may not be bidirectional.
658 +
659 +Repair: rephrase the doubling explanation to drop "similar
660 +volume" framing. The doubling effect is about per-router
661 +ingress+egress accounting, NOT about traffic symmetry. Same
662 +underlying conversation can have very different byte counts
663 +in each direction.
664 +
665 +#### F3 -- Overview false "in one direction" advice
666 +
667 +> > To see real numbers: filter by one exporter, one
668 +> > interface, in one direction. The dashboard makes this
669 +> > easy. See the Anti-patterns page for the full framing.
670 +>
671 +> "in one direction" is wrong. Traffic does not double when
672 +> viewing bidirectional traffic in one interface, because
673 +> traffic is not usually symmetrical. It needs rephrasing.
674 +
675 +Repair: rephrase. The correct framing for "see real numbers"
676 +is per-exporter + per-interface scope; direction filtering
677 +is not the doubling fix.
678 +
679 +#### F4 -- Sampling rate framing wrong (uniform-rate myth)
680 +
681 +> > This works correctly only if all your exporters use the
682 +> > same sampling rate.
683 +>
684 +> No! This is totally wrong. The way netdata does it, is it
685 +> that it gets the specific sampling each flow has and
686 +> multiplies the traffic of the specific flow to find its
687 +> actual. This works even if each router and each interface
688 +> has its own sampling rate.
689 +>
690 +> The "works only if all your routers have the same
691 +> sampling rate" is a misconception from when we were
692 +> discussing this:
693 +>
694 +> - you: the dashboard must show the sampling rate on any
695 +> view
696 +> - me: this cannot be done reliably when flows with mixed
697 +> sampling rates are aggregated on the dashboard, and
698 +> netdata does the right thing to no show it, because:
699 +> - a) if all your routers have the same sampling, you
700 +> know it already
701 +> - b) if you have mixed sampling rates, it is technically
702 +> impossible to provide a meaningful single sampling
703 +> rate for the aggregation
704 +> So, netdata multiplies at the source, so that
705 +> aggregations are as accurate as possible, even with
706 +> mixed sampling rates
707 +
708 +Code evidence:
709 +`src/crates/netflow-plugin/src/decoder/record/core/record.rs:24-26`
710 +multiplies `bytes` and `packets` by each record's own
711 +`sampling_rate` at decode time. Mixed sampling rates across
712 +exporters or interfaces are handled correctly automatically.
713 +
714 +Repair: remove the uniform-rate-required framing wherever it
715 +appears, replace with the correct per-flow-multiplication
716 +explanation, drop any UI claim about showing a single
717 +sampling rate (it would be meaningless under mixed rates).
718 +
719 +#### F5 -- Sampling rate "clean path" recommendation wrong
720 +
721 +> > The clean path: keep sampling rates uniform across your
722 +> > network, or run unsampled where the flow rate allows
723 +>
724 +> No. I never said that. It is not the clean path. People
725 +> should use the sampling rates according to their use
726 +> cases. But netdata multiplies at ingestions and does not
727 +> show sampling rates on the UI.
728 +
729 +Repair: remove the "clean path" recommendation. Netdata does
730 +the right thing regardless of whether sampling is uniform or
731 +mixed.
732 +
733 +#### F6 -- Globe view "less useful for analysis" wrong
734 +
735 +> > Globe -- a 3D rendering of the city-level data. Visual
736 +> > demo, less useful for analysis.
737 +>
738 +> "less useful for analysis"? Why? The information is
739 +> exactly the same with the map. There is a table, like in
740 +> maps. What makes it less useful? That is 3d? The opposite
741 +> I think.
742 +
743 +Repair: rewrite the globe section to drop the
744 +"less useful for analysis" judgement. Same data, same table,
745 +same selectivity; the 3D projection is one of several
746 +valid presentations.
747 +
748 +#### F7 -- Installation tab location wrong
749 +
750 +> In installation:
751 +>
752 +> > The Network Flows tab should appear in the top
753 +> > navigation
754 +>
755 +> No. The flows functions in in the "Live" top menu
756 +> currently.
757 +
758 +Repair: correct the location to "Live" menu.
759 +
760 +#### F8 -- Configuration: tier sizing should be per-tier only
761 +
762 +> Retention size should be set per tier, like:
763 +>
764 +> tiers:
765 +> raw: { size: 10GB, duration: 24h }
766 +> etc
767 +>
768 +> These globals must be removed:
769 +>
770 +> size_of_journal_files: 10GB
771 +> duration_of_journal_files: 7d
772 +>
773 +> It is very important to be able to size tiers
774 +> independently of each other. There is no one size fits
775 +> all.
776 +> I know there are globals and overrides per tier, but come
777 +> on. Why double configuration?
778 +
779 +This finding has TWO parts:
780 +
781 +1. Documentation: stop documenting the globals; show only
782 + per-tier `tiers: { raw: {size, duration}, ... }`.
783 +2. Code: remove `size_of_journal_files` and
784 + `duration_of_journal_files` from the schema.
785 +
786 +Code reference today:
787 +`src/crates/netflow-plugin/src/plugin_config/types/journal.rs:21-37`
788 +declares both globals AND per-tier overrides. The user wants
789 +the globals dropped from the configuration schema entirely.
790 +
791 +#### F9 -- Configuration: query_1m_max_window / query_5m_max_window unjustified
792 +
793 +> About these:
794 +>
795 +> query_1m_max_window: 6h
796 +> query_5m_max_window: 24h
797 +>
798 +> What are these and why they are needed? I don't
799 +> understand. Either the query engine is half based, or
800 +> these are useless overprotections that are never needed.
801 +
802 +Repair: investigate purpose in code. If they are real
803 +protections, document the protection clearly. If they are
804 +useless, remove them from both code and docs.
805 +
806 +#### F10 -- Configuration: query_max_groups / query_facet_max_values_per_field unjustified
807 +
808 +> I don't understand what are these and why are needed and
809 +> what value or protection they provide:
810 +>
811 +> query_max_groups: 50000
812 +> query_facet_max_values_per_field: 5000
813 +>
814 +> Explain
815 +
816 +Repair: same as F9 -- investigate, document the protection
817 +or remove.
818 +
819 +#### F11 -- Empty page: enrichment-concepts/ip-intelligence
820 +
821 +> empty page:
822 +> https://deploy-preview-2852--netdata-docusaurus.netlify.app/docs/network-flows/enrichment-concepts/ip-intelligence
823 +
824 +Investigate why the page renders empty. Source file
825 +`docs/network-flows/enrichment/ip-intelligence.md` exists.
826 +Likely a generated MDX issue (frontmatter, fence, or special
827 +char) or an ingest path mismatch.
828 +
829 +#### F12 -- Retention and Querying structure wrong; URL sharing irrelevant
830 +
831 +> Retention and Querying has a section called "URL
832 +> sharing"? Really? You find this relevant?
833 +> If you need to put generic visualization rules, these
834 +> should be a generic "Visualization/Overview" page, to
835 +> explain FTS, sharing, grouping, etc. For sure Retention
836 +> is closer to configuration and querying is closer to
837 +> visualization.
838 +
839 +Repair has THREE parts:
840 +
841 +1. Remove the "URL sharing" section from
842 + retention-querying.md.
843 +2. Move retention-side content (tier sizing rules, retention
844 + knobs) closer to Configuration.
845 +3. Move query-time semantics (tier auto-pick rules,
846 + query-engine behaviour, FTS, sharing, grouping) into a
847 + new Visualization/Overview page.
848 +
849 +#### F13 -- Sizing/Capacity Planning wrong genre, wrong content
850 +
851 +> Sizing and Capacity planning is written like an academic
852 +> paper that must prove productivity of the testing
853 +> environment. People want sizing and planning directions.
854 +> This is not an academic paper, not a blog.
855 +>
856 +> What are the requirements for this page:
857 +>
858 +> - what is the cap of the plugin
859 +> - how ingestion rate affects storage
860 +> - the raw tier monopolizes storage - do not let it
861 +> explode - it will need fast nvme disks to query it.
862 +> - journal backend uses free system memory as system
863 +> caches - the bigger the database, the more free memory
864 +> the system would need.
865 +> - journal is fully indexed, all fields are indexed, but
866 +> FTS means full scan.
867 +> - explain that 25k flows/s sustained approaches ISP level
868 +> capacities.
869 +> - use the distributed nature of netdata. The plugin can
870 +> be installed multiple times, in branch offices,
871 +> different data centers, etc. And since aggregation
872 +> across routers is usually meaningless for flows, users
873 +> can appoint 1 netdata per router. There is no need to
874 +> push all flows to one central place.
875 +>
876 +> So, this page should provide a practical guide for users
877 +> to scale the plugin, the servers and storage it runs,
878 +> etc.
879 +> Remove the benchmarks and tests from this page. The
880 +> benchmarks were for us, not for the customers.
881 +
882 +Repair: rewrite sizing-capacity.md from scratch as a
883 +practical scaling guide following the seven bullets above.
884 +Drop all benchmark numbers, drop the "academic paper" framing.
885 +
886 +#### F14 -- Validation: invented user-side risks
887 +
888 +> In Validation and Data Quality:
889 +>
890 +> Risks:
891 +> - Netdata monitors UDP port overflows and has alerts for
892 +> it.
893 +> - "Sampling rate misinterpretation" how is this a risk
894 +> for users? This is bug in netdata if it happens.
895 +> - "Sampling rate change" how is this a risk for users?
896 +> Netdata ensures this will not happen because ingestion
897 +> scales on sampling received
898 +> - "Template loss after collector restart" how is this a
899 +> risk for users? Netdata saves templates and reloads
900 +> them
901 +>
902 +> I think the entire "Validation and Data Quality" is
903 +> completely off. It mentions again sampling rates, etc.
904 +> It is like it was written by someone that does not have
905 +> a clue of what netdata is and how the plugin works.
906 +
907 +Code evidence:
908 +- Templates persist:
909 + `src/crates/netflow-plugin/src/decoder/protocol/v9/templates.rs:106`
910 + and
911 + `src/crates/netflow-plugin/src/decoder/protocol/ipfix/templates/data.rs:67`
912 +- Sampling multiplied per-flow at decode (see F4).
913 +
914 +Repair: rewrite validation.md from scratch. Remove the
915 +sampling-related "risks", remove template-loss "risk", point
916 +UDP overflow concern at Netdata's existing alerts.
917 +
918 +#### F15 -- Anti-patterns: "Ignoring the sampling rate" is bogus
919 +
920 +> Anti-patterns page:
921 +>
922 +> > Ignoring the sampling rate
923 +>
924 +> How is it possible for users to ignore the sampling rate
925 +> if we calculate the estimated volume at ingestion? You
926 +> invented reasons for it: "so the dashboard numbers are
927 +> estimates of actual traffic -- if the multiplication is
928 +> consistent"
929 +>
930 +> What? How the multiplication cannot be consistent? What
931 +> are you talking about?
932 +>
933 +> "Ignoring the sampling rate" section must be removed.
934 +
935 +Repair: remove the entire section.
936 +
937 +#### F16 -- Anti-patterns: "GeoIP for internal IPs" is invented
938 +
939 +> > "Internal IPs (10.x, 172.16-31.x, 192.168.x) appear in
940 +> > random countries on the geographic map"
941 +>
942 +> What? Where did you find this? Geolocation does not
943 +> position internal IPs on the map.
944 +>
945 +> "Trusting GeoIP for internal IPs" section must be removed.
946 +
947 +Repair: remove the entire section. Need to verify in code
948 +that internal IPs are NOT placed on the map -- if there is
949 +any path that does, that's a Netdata bug to file separately,
950 +not a user-side anti-pattern.
951 +
952 +#### F17 -- Anti-patterns: "Alerting on absolute volume" doesn't apply
953 +
954 +> > "Alerting on absolute volume thresholds"
955 +>
956 +> Netdata does not support alerting of flows yet. Remove
957 +> this section.
958 +
959 +Repair: remove the entire section.
960 +
961 +#### F18 -- Troubleshooting: wrong journalctl namespace
962 +
963 +> Troubleshooting page:
964 +>
965 +> Netdata logs in namespace 'netdata'. Journalctl needs
966 +> `--namespace netdata`.
967 +
968 +Repair: every `journalctl` invocation must include
969 +`--namespace netdata`. Grep all docs.
970 +
971 +#### F19 -- Troubleshooting: cumulative misconceptions
972 +
973 +> This page has a mix of all the above issues: sampling,
974 +> geoip, etc.
975 +
976 +Repair: this is a per-claim sweep informed by the fixes
977 +above. Every behavioural claim on the troubleshooting page
978 +must be cited to code or removed.
979 +
980 +#### F20 -- Section title: "Enrichment Concepts" wrong
981 +
982 +> "Encrichement Concepts" is a wrong title. "Flows
983 +> Enrichement" is the right one.
984 +
985 +Repair: rename the sub-section "Enrichment Concepts" to
986 +"Flows Enrichment" everywhere it appears: `docs/.map/map.yaml`,
987 +in any cross-references in pages, and in any sidebar /
988 +breadcrumb labels that derive from the map.
989 +
990 +#### F21 -- Section title: "Sources" wrong
991 +
992 +> "Sources" is too generic. "Flow Protocols" is the right
993 +> one.
994 +
995 +Repair: rename the sub-section "Sources" to "Flow Protocols"
996 +everywhere: `docs/.map/map.yaml`, plus every cross-reference
997 +that points at `Sources/NetFlow`, `Sources/IPFIX`,
998 +`Sources/sFlow` (those individual page labels stay).
999 +
1000 +### Repair plan
1001 +
1002 +**Phase R1 -- Per-finding fixes, one at a time, no batching.**
1003 +For each finding F1..F21 above:
1004 +
1005 +1. Investigate against code -- read the relevant source
1006 + files; record file:line evidence in this SOW under the
1007 + per-finding execution-log entry.
1008 +2. Surgical edit -- minimal diff that fixes only that
1009 + claim.
1010 +3. Grep the rest of the docs for the same pattern; fix
1011 + every instance with the same surgical care.
1012 +4. Append a dated execution-log entry naming what changed,
1013 + why, and the code citation.
1014 +
1015 +No finding is "deferred". Every one gets fixed before the
1016 +SOW can re-close.
1017 +
1018 +**Phase R2 -- Per-page audit subagents.**
1019 +
1020 +After Phase R1 completes, spawn one read-only audit
1021 +subagent per page. Each subagent's brief:
1022 +
1023 +- Read the entire page line by line.
1024 +- For every behavioural / configuration / vendor /
1025 + protocol claim, verify it against the source code at the
1026 + cited paths in this repository.
1027 +- For every third-party vendor configuration mention
1028 + (Cisco IOS-XE / IOS-XR, Juniper JunOS, FRR, Palo Alto,
1029 + Mikrotik, Zyxel, etc.), verify against the upstream
1030 + vendor's current documentation by web fetch.
1031 +- Flag every claim that cannot be anchored, every
1032 + generic-flow-monitoring sentence that contradicts how
1033 + Netdata actually works, and every vendor command that
1034 + does not exist or has wrong syntax.
1035 +- Output a structured finding list: claim, location,
1036 + evidence, severity, suggested fix.
1037 +
1038 +The master assistant synthesises the findings, applies
1039 +surgical fixes, and re-spawns the auditor on the same page.
1040 +Iterate until the auditor returns "no findings".
1041 +
1042 +**Pages in scope for Phase R2:**
1043 +- README.md (Overview)
1044 +- installation.md
1045 +- quick-start.md
1046 +- configuration.md
1047 +- field-reference.md
1048 +- retention-querying.md (post-restructure)
1049 +- sizing-capacity.md (post-rewrite)
1050 +- validation.md (post-rewrite)
1051 +- investigation-playbooks.md
1052 +- anti-patterns.md (post-section-removals)
1053 +- troubleshooting.md
1054 +- 7 enrichment pages: ip-intelligence, asn-resolution, bgp-routing, network-identity, static-metadata, classifiers, decapsulation
1055 +- 5 visualisation pages: summary-sankey, time-series, maps-globe, filters-facets, dashboard-cards
1056 +- 14 generated integration cards under `src/crates/netflow-plugin/integrations/` -- each has its own audit pass against `metadata.yaml` and against the upstream vendor documentation for any setup steps.
1057 +
1058 +**Plus a new visualisation/overview page** (per F12) and possibly a new visualisation/querying page if the F12 split lands as proposed.
1059 +
1060 +**Phase R3 -- Final close.**
1061 +
1062 +The SOW reopened with this regression note during repair. The Validation
1063 +section was appended (not replaced) with per-finding evidence and per-page
1064 +audit-clean evidence. Status returned to `completed` after all closure
1065 +criteria listed below were satisfied:
1066 +
1067 +- Every F1..F21 has a fix landed and a code citation in the
1068 + log.
1069 +- Every page has at least one auditor pass returning no
1070 + findings.
1071 +- A whole-section codex-style review of the final docs
1072 + returns no new findings.
1073 +
1074 +Then move file back to `done/`.
1075 +
1076 +### Repair execution log
1077 +
1078 +#### F1 -- 2026-05-07 -- section landing page rendering
1079 +
1080 +Investigation: Compared `# Network Flows` block in
1081 +`docs/.map/map.yaml` with sections that DO render an Overview
1082 +landing page on Learn (e.g. `Collecting Metrics`,
1083 +`Dashboards and Charts`, `Netdata Cloud`). All those carry
1084 +`edit_url:` directly on the section root meta block. Network
1085 +Flows root carried `label:` only; the README.md was instead
1086 +exposed as a CHILD entry labelled "Overview". With no leaf
1087 +content at the root, learn's
1088 +`get_dir_make_file_and_recurse` (`learn/ingest/ingest.py:2333-2510`)
1089 +auto-generates a category-grid index page at the section
1090 +URL, which is what the netlify preview rendered as tiles.
1091 +
1092 +Fix: hoisted `edit_url:` and `description:` to the Network
1093 +Flows root meta block. Removed the now-redundant child
1094 +"Overview" entry pointing to the same README.
1095 +
1096 +Other similar patterns in MY work: Sub-sections "Enrichment
1097 +Concepts" (line 498) and "Visualization" (line 529) within
1098 +Network Flows also lack root `edit_url`. Decision: leave as
1099 +auto-grid for now -- F1 was specifically about the section
1100 +root URL `/docs/network-flows`, and sub-section grids are an
1101 +accepted pattern across the rest of the project (e.g.
1102 +"Systemd Journal Logs" sub-section under Logs). If a future
1103 +finding flags `/docs/network-flows/flows-enrichment` or
1104 +`/docs/network-flows/flow-protocols` as needing real content,
1105 +revisit then.
1106 +
1107 +Patterns OUTSIDE my work (pre-existing): "Logs" section
1108 +root (line 567 in map.yaml) also lacks root `edit_url`. Out
1109 +of scope -- pre-existing structural choice.
1110 +
1111 +Evidence:
1112 +- Pattern reference: `docs/.map/map.yaml:65-68` (Welcome to Netdata),
1113 + `docs/.map/map.yaml:96-99` (Collecting Metrics).
1114 +- Renderer: `learn/ingest/ingest.py:2333-2510`
1115 + (`get_dir_make_file_and_recurse`).
1116 +
1117 +Diff: `docs/.map/map.yaml:479-484`.
1118 +
1119 +#### F2 + F3 -- 2026-05-07 -- doubling vs bidirectional symmetry
1120 +
1121 +These two findings address the same paragraphs and are fixed
1122 +together. The "doubling" effect (per-packet ingress+egress on
1123 +one router) was conflated with bidirectional traffic
1124 +symmetry, which is a different concept. Original docs told
1125 +users to (a) look for "similar volume" in opposite directions
1126 +to identify a single conversation, and (b) "filter by one
1127 +exporter, one interface, in one direction" to see real
1128 +volume. Both wrong:
1129 +
1130 +- Bidirectional traffic is typically asymmetric (downloads
1131 + vs ACKs). "Similar volume" is a wrong heuristic for
1132 + spotting two halves of the same conversation.
1133 +- "in one direction" added on top of "one interface" is
1134 + redundant for the doubling fix and misleads readers into
1135 + expecting another 50% halving from direction filtering.
1136 +
1137 +The doubling fix is just: one exporter + one interface
1138 +(Input Interface OR Output Interface, pick one). Each packet
1139 +crossing that interface produces exactly one record on it.
1140 +
1141 +Mirror-conversation framing rewritten: bidirectional
1142 +conversations produce separate records per direction
1143 +because they really are different packets going each way.
1144 +Volumes are usually asymmetric. Per-direction accounting
1145 +is correct, not duplication.
1146 +
1147 +Files touched:
1148 +- `docs/network-flows/README.md` -- `## Two things to know
1149 + on day one` paragraphs rewritten.
1150 +- `docs/network-flows/quick-start.md` -- step-3 paragraphs
1151 + rewritten.
1152 +- `docs/network-flows/visualization/summary-sankey.md:98`
1153 + -- "in one direction" removed from doubling fix.
1154 +- `docs/network-flows/anti-patterns.md:21` -- same fix in
1155 + "How to avoid it" line of the doubling anti-pattern.
1156 +- `docs/network-flows/validation.md:36, 55` -- same fix in
1157 + the SNMP cross-check and doubling sanity-check sections.
1158 +
1159 +Note: F14 / F15 / F16 / F17 will rewrite anti-patterns.md
1160 +and validation.md more comprehensively. The corrections
1161 +above are kept as standalone fixes so the wrong claim is
1162 +gone immediately even if the surrounding sections survive.
1163 +
1164 +Other patterns scanned: searched for "of similar volume",
1165 +"same volume", "symmetric", "in one direction",
1166 +"conversations? (?:are|look|appear)? mirrored". The lines
1167 +above are all hits within `docs/network-flows/`. No other
1168 +files carry the pattern.
1169 +
1170 +#### F4 + F5 -- 2026-05-07 -- sampling rate framing (uniform-rate myth)
1171 +
1172 +These two findings are about the same incorrect claim and
1173 +fixed together.
1174 +
1175 +Code evidence -- per-flow multiplication at decode time:
1176 +
1177 +```rust
1178 +// src/crates/netflow-plugin/src/decoder/record/core/record.rs:24-26
1179 +let sampling_rate = rec.sampling_rate.max(1);
1180 +rec.bytes = rec.bytes.saturating_mul(sampling_rate);
1181 +rec.packets = rec.packets.saturating_mul(sampling_rate);
1182 +```
1183 +
1184 +`sampling_rate` is set per-record from the relevant source:
1185 +- `decoder/protocol/legacy.rs:60` -- v5 header rate.
1186 +- `decoder/protocol/v9/records.rs:39, 215` and
1187 + `decoder/protocol/v9/sampling.rs:215` -- v9 IE on record
1188 + or Sampling Options Template (namespace-scoped).
1189 +- `decoder/protocol/ipfix/special/record.rs:21, 61, 171`,
1190 + `ipfix/record/state.rs:10, 38, 168`,
1191 + `ipfix/record/append.rs:56` -- IPFIX record IE / sampling
1192 + options.
1193 +- `decoder/protocol/sflow/record.rs:6, 19` -- sFlow per-sample
1194 + rate.
1195 +- `decoder/protocol/shared/merge/enrich.rs:80` -- merge with
1196 + static `override_sampling_rate` config.
1197 +
1198 +So mixed sampling rates across exporters / interfaces / time
1199 +are handled correctly: each record scales by its own rate.
1200 +The only failure mode is "exporter is sampling but the rate
1201 +is not communicated" (NetFlow v7 has no field; v5 with
1202 +rate=0; v9 / IPFIX without an attached Sampling Options
1203 +Template). That is a real concern and stays in the docs.
1204 +
1205 +Removed claims:
1206 +
1207 +- README.md lines 91-94 -- "works correctly only if all
1208 + your exporters use the same sampling rate" and "the
1209 + clean path: keep sampling rates uniform across your
1210 + network" -- both false. Rewrote the paragraph to state
1211 + per-flow multiplication, why the UI does not surface a
1212 + single rate, and the real statistical-floor caveat
1213 + (sampling can miss small/short flows regardless of how
1214 + uniform the rates are).
1215 +- field-reference.md line 33 -- `RAW_BYTES` description
1216 + said "Use when sampling is uniform across all your
1217 + exporters". Changed to "Use when you want the unscaled
1218 + value the exporter sent". Same fix in
1219 + anti-patterns.md:126 (the prose) and the summary table
1220 + row at line 150.
1221 +- troubleshooting.md line 129 -- "Mixed sampling rates
1222 + across exporters... isn't comparable to any single SNMP
1223 + measurement" replaced with the correct framing:
1224 + comparing aggregates to a single interface counter is
1225 + the actual mistake; per-flow multiplication is correct
1226 + regardless of rate uniformity.
1227 +- validation.md line 11 -- "undocumented sampling rate
1228 + changes" removed from the silent-failure list intro.
1229 +- validation.md line 116 -- the "Sampling rate change"
1230 + monitoring-table row removed.
1231 +- investigation-playbooks.md lines 128, 132 -- "Sampling
1232 + rate of the exporter (so the numbers can be
1233 + interpreted)" deliverable bullet removed; "A change in
1234 + sampling rate during the analysis window invalidates
1235 + the trend" caveat removed.
1236 +- anti-patterns.md line 132 -- "Same goes for
1237 + sampling-rate differences across exporters" removed
1238 + from the cross-protocol comparison section. The
1239 + protocol-counts-not-comparable point stays; the
1240 + uniformity claim goes.
1241 +
1242 +Items NOT touched in this finding (deferred to F14 / F15
1243 +which will rewrite their containing sections):
1244 +
1245 +- validation.md silent-failure list items #2 ("Sampling
1246 + rate misinterpretation"), #3 ("Sampling rate change"),
1247 + #5 ("Template loss after collector restart") -- F14
1248 + removes them as a block.
1249 +- anti-patterns.md section 2 ("Ignoring the sampling
1250 + rate") and the summary-table row "Ignored sampling" --
1251 + F15 removes the entire section.
1252 +
1253 +Files touched:
1254 +- docs/network-flows/README.md (lines 88-96)
1255 +- docs/network-flows/field-reference.md (line 33)
1256 +- docs/network-flows/troubleshooting.md (lines ~120-130)
1257 +- docs/network-flows/validation.md (lines 11, ~116)
1258 +- docs/network-flows/investigation-playbooks.md (lines 128-132)
1259 +- docs/network-flows/anti-patterns.md (lines 132, 140, 150, 126)
1260 +
1261 +Other docs scanned (`docs/network-flows/`) for the patterns
1262 +"uniform.*rate", "same.*sampling.*rate", "rates.*uniform",
1263 +"all.*exporters.*same.*rate", "sampling.*uniform",
1264 +"clean.*path.*sampling", "aggregate.*blend",
1265 +"blend.*estimate" -- all hits addressed above except the
1266 +F14 / F15 -targeted blocks.
1267 +
1268 +#### F6 -- 2026-05-07 -- Globe view "less useful for analysis"
1269 +
1270 +User: "the information is exactly the same with the map.
1271 +There is a table, like in maps. What makes it less useful?
1272 +That is 3d? The opposite I think."
1273 +
1274 +Code reality (per the city-map / globe code path that
1275 +shares the same backend response: see
1276 +`src/crates/netflow-plugin/src/query/request/constants.rs`
1277 +for `CITY_MAP_GROUP_BY_FIELDS` and the absence of any
1278 +"globe" view enum -- the globe re-renders the city-map
1279 +response): same data, same table, just a different
1280 +rendering.
1281 +
1282 +Removed claims:
1283 +
1284 +- README.md line 105 -- "Visual demo, less useful for
1285 + analysis" replaced with neutral framing that the globe
1286 + uses the same data + table as the city map and is the
1287 + better fit when distance / great-circle routes matter.
1288 +- visualization/maps-globe.md "Globe vs City Map"
1289 + paragraph -- removed the "less useful for analysis"
1290 + judgement; states the trade-off (2D for in-continent
1291 + precision; 3D for transcontinental / great-circle).
1292 +
1293 +Also fixed the "Mirroring" subsection on the same page:
1294 +the "25 top-N = 12 conversations" claim is the F2 symmetry
1295 +myth and was inconsistent with bidirectional traffic
1296 +typically being asymmetric. Reworded to state that
1297 +bidirectional traffic produces two separate flow records,
1298 +two distinct edges, with usually-different volumes.
1299 +
1300 +No other doc carries the "less useful for analysis"
1301 +phrasing. Grep clean.
1302 +
1303 +Files touched:
1304 +- docs/network-flows/README.md (line 105)
1305 +- docs/network-flows/visualization/maps-globe.md (lines ~85-91)
1306 +
1307 +#### F7 -- 2026-05-07 -- Network Flows is a Function under the Live tab
1308 +
1309 +Verified terminology against
1310 +`docs/dashboards-and-charts/live-tab.md:3-69`: Netdata's
1311 +official UI vocabulary calls it the **Live tab** in the top
1312 +navigation; it lists Functions; "Network Flows" is one of
1313 +those Functions. There is no top-nav "Network Flows" tab.
1314 +
1315 +Repair scope: every doc reference. Adopted convention:
1316 +
1317 +- Verb form: "Open Network Flows" (drop "tab")
1318 +- Noun reference to the workspace: "the Network Flows
1319 + view"
1320 +- Setup / installation context: "Click the **Live** tab in
1321 + the top navigation; **Network Flows** appears in the
1322 + Functions list" -- mirrors the official Live-tab doc.
1323 +
1324 +Files touched:
1325 +- docs/network-flows/installation.md (lines 113-121) --
1326 + the original bad sentence and follow-up.
1327 +- docs/network-flows/troubleshooting.md (lines 19, 47).
1328 +- docs/network-flows/investigation-playbooks.md (lines 11,
1329 + 21, 63, 109, 141) -- five "Open the Network Flows tab"
1330 + occurrences replaced with "Open Network Flows"; lead
1331 + paragraph at line 11 names the Live tab.
1332 +- docs/network-flows/anti-patterns.md (line 15).
1333 +- docs/network-flows/visualization/dashboard-cards.md
1334 + (lines 11, 13, 87, 97 -- four occurrences).
1335 +- docs/network-flows/visualization/summary-sankey.md
1336 + (line 11).
1337 +
1338 +Grep for "Network Flows tab", "Network Flows menu", "top
1339 +navigation" run after the sweep -- only the corrected
1340 +phrasings remain.
1341 +
1342 +#### F8 -- 2026-05-07 -- per-tier retention only; remove globals
1343 +
1344 +User: "It is very important to be able to size tiers
1345 +independently of each other. There is no one size fits all.
1346 +I know there are globals and overrides per tier, but come
1347 +on. Why double configuration?"
1348 +
1349 +Code investigation: the global `size_of_journal_files` and
1350 +`duration_of_journal_files` on `JournalConfig` were already
1351 +no more than per-tier defaults that flowed through
1352 +`retention_for_tier()` -- runtime semantics were already
1353 +per-tier. The "double configuration" was schema redundancy
1354 +with no underlying behavioural justification.
1355 +
1356 +Refactor (`src/crates/netflow-plugin/src/`):
1357 +
1358 +- `plugin_config/types/journal.rs`: removed
1359 + `size_of_journal_files` and `duration_of_journal_files` from
1360 + `JournalConfig`. Replaced
1361 + `Option<JournalTierRetentionConfig>` per-tier with the
1362 + struct directly carrying `Option<ByteSize>` /
1363 + `Option<Duration>` fields. Each tier defaults to
1364 + `Some(default)` when the YAML omits it; explicit `null`
1365 + disables the limit on that tier; both Nones is rejected by
1366 + validation. Removed the now-vestigial
1367 + `JournalConfig::default_retention()` and simplified
1368 + `retention_for_tier()` to a single per-tier lookup.
1369 + Built-in tier defaults stay at uniform 10GB / 7d to
1370 + preserve current default behaviour.
1371 +- `plugin_config/defaults.rs`: removed the dead
1372 + `RetentionLimitOverride<T>` enum, its
1373 + `is_inherit` / `resolve` impl, and the four
1374 + `(de)serialize_retention_override_*` helpers (no longer
1375 + reachable). Removed the now-orphan `parse_bytesize`
1376 + helper that fed the removed clap `value_parser`.
1377 +- `plugin_config.rs`: removed the
1378 + `pub(crate) use defaults::RetentionLimitOverride;`
1379 + re-export.
1380 +
1381 +Tests (mechanical updates to the new schema):
1382 +
1383 +- `memory_tests.rs`: collapsed four near-identical per-tier
1384 + override blocks into one `small_tier` config cloned across
1385 + the four tiers; replaced
1386 + `RetentionLimitOverride::Value(...)` with `Some(...)`;
1387 + dropped the now-dead `RetentionLimitOverride` import.
1388 +- `startup_memory_tests.rs`: dropped the global-retention
1389 + setters that already matched the built-in defaults.
1390 +- `plugin_config_tests.rs`: rewrote five tests to exercise
1391 + the new schema only:
1392 + `journal_tier_retention_uses_built_in_tier_defaults`
1393 + (was `inherits_global_defaults_when_no_overrides_exist`),
1394 + `journal_tier_retention_uses_per_tier_values_when_present`,
1395 + `journal_rotation_size_derives_from_tier_size_budget` (now
1396 + via `tiers.raw.size_of_journal_files`),
1397 + `journal_rotation_size_uses_100mb_for_time_only_retention`,
1398 + `journal_validation_allows_time_only_retention_when_size_is_disabled`,
1399 + `journal_tier_retention_null_disables_size_limit_for_that_tier_only`
1400 + (replaces the old "inherited size limit" YAML test).
1401 + Updated the YAML test fixture at line ~245 to drop the
1402 + global keys.
1403 +
1404 +Documentation:
1405 +
1406 +- `docs/network-flows/configuration.md`: rewrote the
1407 + `## journal` section. Single per-tier table only, no
1408 + "top-level retention" subsection, explicit note that
1409 + there are no global retention knobs. Updated the
1410 + production-retention example to a fully-per-tier form.
1411 +- `docs/network-flows/retention-querying.md`: dropped the
1412 + global-form example; replaced with the per-tier form;
1413 + cross-link to configuration.md.
1414 +- `docs/network-flows/sizing-capacity.md` (line 101):
1415 + already says "per-tier"; no change.
1416 +
1417 +Build + tests:
1418 +
1419 +- `cargo build --release` clean (3m02s).
1420 +- `cargo test --release --bin netflow-plugin` -- 427
1421 + passed; 0 failed; 18 ignored.
1422 +
1423 +Breaking change notice: any existing user config that
1424 +specified `journal.size_of_journal_files` or
1425 +`journal.duration_of_journal_files` at the top level of the
1426 +journal block will now fail to deserialize (strict
1427 +`deny_unknown_fields`). Users migrate by moving those values
1428 +into per-tier `tiers.<tier>.size_of_journal_files` /
1429 +`duration_of_journal_files`. Plugin is recently shipped
1430 +(PR #22439, 2026-05-07); breaking-change risk is low.
1431 +
1432 +Files touched:
1433 +- src/crates/netflow-plugin/src/plugin_config/types/journal.rs
1434 +- src/crates/netflow-plugin/src/plugin_config/defaults.rs
1435 +- src/crates/netflow-plugin/src/plugin_config.rs
1436 +- src/crates/netflow-plugin/src/memory_tests.rs
1437 +- src/crates/netflow-plugin/src/startup_memory_tests.rs
1438 +- src/crates/netflow-plugin/src/plugin_config_tests.rs
1439 +- docs/network-flows/configuration.md
1440 +- docs/network-flows/retention-querying.md
1441 +
1442 +#### F9 -- 2026-05-07 -- query_1m_max_window / query_5m_max_window are dead
1443 +
1444 +User: "What are these and why they are needed? I don't
1445 +understand. Either the query engine is half based, or these
1446 +are useless overprotections that are never needed."
1447 +
1448 +Code investigation: searched
1449 +`src/crates/netflow-plugin/src/` for any consumer outside
1450 +the config struct itself:
1451 +
1452 +- `plugin_config/types/journal.rs` -- declared.
1453 +- `plugin_config/validation/journal.rs:6-13` -- non-zero
1454 + validation; ordering check.
1455 +- `plugin_config/defaults.rs` -- defaults.
1456 +- `plugin_config_tests.rs` -- two YAML fixtures only.
1457 +
1458 +NO consumer in `query/`, `query/planner/`, or anywhere else
1459 +reads these values. The actual tier auto-pick logic lives in
1460 +`query/planner/spans.rs:plan_query_tier_spans_recursive`,
1461 +which selects the coarser tier strictly from window /
1462 +bucket-duration alignment math; it does not consult
1463 +`query_1m_max_window` or `query_5m_max_window`.
1464 +
1465 +Verdict: dead config knobs. The user's "useless
1466 +overprotections" assessment was correct.
1467 +
1468 +Repair: removed both fields from the schema and validation,
1469 +plus the two YAML test fixtures, plus the configuration
1470 +table row that documented them. Updated the
1471 +retention-querying explanation of "skip a tier when window
1472 +is too wide" to reflect the actual behaviour: the planner
1473 +picks the coarsest aligned tier without separate config-
1474 +driven window caps.
1475 +
1476 +Files touched:
1477 +- src/crates/netflow-plugin/src/plugin_config/types/journal.rs
1478 +- src/crates/netflow-plugin/src/plugin_config/validation/journal.rs
1479 +- src/crates/netflow-plugin/src/plugin_config_tests.rs
1480 + (two YAML fixtures cleaned)
1481 +- docs/network-flows/configuration.md (removed two table
1482 + rows + corresponding code-block lines + the two-line
1483 + "query-window limits" explanation)
1484 +- docs/network-flows/retention-querying.md (corrected
1485 + description of tier-pick behaviour)
1486 +
1487 +Build + tests:
1488 +- `cargo build --release` clean (2m59s).
1489 +- `cargo test --release --bin netflow-plugin` -- 427
1490 + passed; 0 failed; 18 ignored.
1491 +
1492 +Breaking change notice: any user config that set
1493 +`journal.query_1m_max_window` or `journal.query_5m_max_window`
1494 +will now fail to deserialize (deny_unknown_fields). These
1495 +keys had no effect before, so the migration is to delete
1496 +them; no behavioural change.
1497 +
1498 +#### F10 -- 2026-05-07 -- query_max_groups stays (real); query_facet_max_values_per_field is dead
1499 +
1500 +User: "I don't understand what are these and why are needed
1501 +and what value or protection they provide. Explain"
1502 +
1503 +Code investigation, two opposite outcomes:
1504 +
1505 +`query_max_groups` -- REAL.
1506 +- Read at `query/service.rs:52` and threaded into the
1507 + `ProjectedGroupAccumulator` via the `max_groups` parameter
1508 + used at `query/projected/apply.rs:48`.
1509 +- When `grouped_aggregates.grouped_total() < max_groups`
1510 + is no longer true, the accumulator stops registering new
1511 + group keys; the row's missing values are then folded into
1512 + the synthetic `__overflow__` bucket created at
1513 + `query/grouping/labels.rs:17` /
1514 + `query/grouping/model/compact.rs:35`.
1515 +- A warning is emitted on the response:
1516 + `query/timeseries.rs:124` -- "Group accumulator limit
1517 + reached; additional groups were folded into __overflow__."
1518 +- Purpose: bounds memory growth on accidentally wide
1519 + group-by combinations (high-cardinality fields like IP
1520 + addresses, AS-paths, MAC addresses, etc.).
1521 +- Verdict: keep, document properly.
1522 +
1523 +`query_facet_max_values_per_field` -- DEAD.
1524 +- Declared, validated for non-zero in
1525 + `plugin_config/validation/journal.rs:18`, but the actual
1526 + facet accumulator at `query/facets/render.rs:19, 27`
1527 + consumes the hardcoded constant
1528 + `DEFAULT_FACET_ACCUMULATOR_MAX_VALUES_PER_FIELD = 5_000`
1529 + from `query/request/constants.rs:17` -- not the config
1530 + knob. The two coincidentally have the same default value
1531 + but the config knob is never threaded to the consumer.
1532 +- Verdict: dead schema. Remove.
1533 +
1534 +Repair:
1535 +
1536 +- `plugin_config/types/journal.rs` -- removed
1537 + `query_facet_max_values_per_field` field; added a doc
1538 + comment explaining what `query_max_groups` actually does.
1539 +- `plugin_config/defaults.rs` -- removed
1540 + `default_query_facet_max_values_per_field()` helper.
1541 +- `plugin_config/validation/journal.rs` -- removed the
1542 + non-zero check for the dead knob.
1543 +- `plugin_config_tests.rs` -- removed
1544 + `validate_rejects_zero_query_facet_max_values_per_field`
1545 + test entirely; cleaned the YAML fixtures that mentioned
1546 + the dead knob.
1547 +- `src/crates/netflow-plugin/configs/netflow.yaml` (stock
1548 + config) -- rewrote the journal block to use the per-tier
1549 + retention form and dropped the dead knob; added a clear
1550 + comment for `query_max_groups`.
1551 +- `src/crates/netflow-plugin/README.md` -- updated the
1552 + example YAML and the explanatory paragraph; removed the
1553 + dead-knob mention.
1554 +- `docs/network-flows/configuration.md` (Query guardrails
1555 + section) -- table now lists only `query_max_groups`;
1556 + expanded its description to name the `__overflow__`
1557 + bucket and the warning behaviour.
1558 +- `docs/network-flows/retention-querying.md` (Group-by
1559 + limit section) -- consolidated to one bullet; named the
1560 + warning + overflow behaviour.
1561 +- `docs/network-flows/visualization/filters-facets.md` --
1562 + removed the entire "Facet limits" subsection that
1563 + documented the dead knob.
1564 +
1565 +Build + tests:
1566 +- `cargo build --release` clean (2m57s).
1567 +- `cargo test --release --bin netflow-plugin` -- 426
1568 + passed; 0 failed (one test removed -- the dead-knob
1569 + validation test).
1570 +
1571 +Breaking change notice: any user config that set
1572 +`journal.query_facet_max_values_per_field` will now fail
1573 +to deserialize (deny_unknown_fields). The key had no
1574 +effect before; migration is delete-only.
1575 +
1576 +#### F11 -- 2026-05-07 -- empty IP Intelligence page
1577 +
1578 +Investigation: `docs/network-flows/enrichment/ip-intelligence.md`
1579 +was a 0-byte file in master. `git log --all --format=%H` of
1580 +that path shows it has been 0 bytes since the original
1581 +documentation rewrite commit (a073bcf24f). It was meant to
1582 +be the "Enrichment Concepts / IP Intelligence" page but
1583 +got created empty.
1584 +
1585 +Repair: authored the page from scratch, code-grounded
1586 +against:
1587 +
1588 +- `src/crates/netflow-plugin/src/plugin_config/types/enrichment/geoip.rs`
1589 + (GeoIpConfig: asn_database / geo_database / optional).
1590 +- `src/crates/netflow-plugin/src/plugin_config/runtime.rs:23-64`
1591 + (auto-detect path: cache_dir/topology-ip-intel/* and
1592 + stock_data_dir/topology-ip-intel/*; auto-detected files
1593 + marked optional=true).
1594 +- `src/crates/netflow-plugin/src/enrichment/data/geoip/resolver.rs`
1595 + (load, refresh-if-needed every 30s on signature change,
1596 + per-IP lookup composing multiple ASN/geo databases,
1597 + IPv6-vs-IPv4-database skip).
1598 +- `src/crates/netflow-plugin/src/enrichment.rs:35`
1599 + (GEOIP_RELOAD_CHECK_INTERVAL = 30s).
1600 +- `src/crates/netflow-plugin/src/enrichment/data/network/asn.rs`
1601 + (AS-name rendering format).
1602 +
1603 +Page covers: fields populated (with tier-preservation
1604 +notes), configuration schema, auto-detection, refresh
1605 +cadence, lookup order, the four provider integration cards,
1606 +private-IP rendering, IPv6-only/IPv4-only database
1607 +behaviour, staleness drift, geographic-accuracy caveats,
1608 +failure modes table.
1609 +
1610 +Frontmatter `learn_rel_path` set to
1611 +"Network Flows/Enrichment Concepts" to match the bgp-routing
1612 +and network-identity siblings (the source frontmatter is
1613 +informational; the actual sidebar position derives from
1614 +`docs/.map/map.yaml`). F20 will rename the section
1615 +consistently across map.yaml and all sibling frontmatter.
1616 +
1617 +Files touched:
1618 +- docs/network-flows/enrichment/ip-intelligence.md
1619 + (created from empty)
1620 +
1621 +#### F20 -- 2026-05-07 -- "Enrichment Concepts" -> "Flows Enrichment"
1622 +
1623 +Repair: renamed the section consistently in:
1624 +
1625 +- `docs/.map/map.yaml:499` -- the section label that drives
1626 + the actual sidebar position on Learn.
1627 +- All seven frontmatter `learn_rel_path:` values in
1628 + `docs/network-flows/enrichment/*.md` -- prior state was
1629 + inconsistent (4 files had "Network Flows/Enrichment", 2
1630 + had "Network Flows/Enrichment Concepts", 1 had the
1631 + brand-new F11 page). Settled on the canonical
1632 + "Network Flows/Flows Enrichment" everywhere.
1633 +
1634 +Grep `Enrichment Concepts|enrichment-concepts` across docs/
1635 +and src/crates/netflow-plugin/ -- no remaining references.
1636 +
1637 +Files touched:
1638 +- docs/.map/map.yaml
1639 +- docs/network-flows/enrichment/asn-resolution.md
1640 +- docs/network-flows/enrichment/bgp-routing.md
1641 +- docs/network-flows/enrichment/classifiers.md
1642 +- docs/network-flows/enrichment/decapsulation.md
1643 +- docs/network-flows/enrichment/ip-intelligence.md
1644 +- docs/network-flows/enrichment/network-identity.md
1645 +- docs/network-flows/enrichment/static-metadata.md
1646 +
1647 +#### F21 -- 2026-05-07 -- "Sources" -> "Flow Protocols"
1648 +
1649 +Repair: renamed the Flow Protocols sub-category in the
1650 +integrations catalog and propagated to integration cards
1651 +and any in-prose references.
1652 +
1653 +- `integrations/categories.yaml:71` -- the `flows.sources`
1654 + category's user-visible `name:` changed from "Sources" to
1655 + "Flow Protocols". Description left intact (it already
1656 + reads "Flow protocols Netdata receives directly from
1657 + routers, switches, and software exporters", which agrees
1658 + with the new label).
1659 +- Three integration card frontmatter updates: `sflow.md`,
1660 + `ipfix.md`, `netflow.md` now declare
1661 + `learn_rel_path: "Network Flows/Flow Protocols"`.
1662 +- `src/crates/netflow-plugin/metadata.yaml` -- removed three
1663 + broken self-referencing links pointing at
1664 + `https://learn.netdata.cloud/docs/network-flows/sources/{netflow,ipfix,sflow}`.
1665 + These URLs were broken before the rename (no
1666 + `docs/network-flows/sources/` directory exists in source)
1667 + and would stay broken under the new label too. Replaced
1668 + with the surviving "Network Flows Overview" anchor that
1669 + is real.
1670 +- Re-ran `integrations/gen_integrations.py` and
1671 + `integrations/gen_docs_integrations.py` -- both exit
1672 + clean. The three regenerated `.md` cards no longer carry
1673 + the broken self-link.
1674 +
1675 +Grep `/sources/` and `/Sources/` after the sweep -- no
1676 +remaining references inside flow integrations content.
1677 +
1678 +Files touched:
1679 +- integrations/categories.yaml
1680 +- src/crates/netflow-plugin/metadata.yaml
1681 +- src/crates/netflow-plugin/integrations/sflow.md
1682 +- src/crates/netflow-plugin/integrations/ipfix.md
1683 +- src/crates/netflow-plugin/integrations/netflow.md
1684 +
1685 +#### F18 -- 2026-05-07 -- journalctl --namespace netdata everywhere
1686 +
1687 +User: "Netdata logs in namespace 'netdata'. Journalctl needs
1688 +`--namespace netdata`."
1689 +
1690 +Background: `-u netdata` selects the systemd UNIT, which
1691 +captures only stdout/stderr the unit emits to the journal.
1692 +The plugin (and the rest of Netdata) actually writes
1693 +structured logs into a journal NAMESPACE called `netdata`.
1694 +Without `--namespace netdata`, users see at most the
1695 +unit-level startup/shutdown messages, not the actual log
1696 +output that helps with debugging.
1697 +
1698 +Repair: swept all `journalctl -u netdata` invocations to
1699 +`journalctl --namespace netdata` in:
1700 +- docs/network-flows/quick-start.md
1701 +- docs/network-flows/troubleshooting.md (5 occurrences)
1702 +- docs/network-flows/installation.md
1703 +- docs/network-flows/enrichment/network-identity.md
1704 +- docs/network-flows/configuration.md (already fixed under
1705 + F8; no change here)
1706 +
1707 +Grep `journalctl` after the sweep -- every invocation now
1708 +uses `--namespace netdata`.
1709 +
1710 +#### F15 -- 2026-05-07 -- remove "Ignoring the sampling rate" anti-pattern
1711 +
1712 +User: "How is it possible for users to ignore the sampling
1713 +rate if we calculate the estimated volume at ingestion? You
1714 +invented reasons for it. ... section must be removed."
1715 +
1716 +Verified the per-flow multiplication code path under F4 +
1717 +F5 already; the entire premise of this anti-pattern (that
1718 +mixed rates produce inconsistent multiplication, that users
1719 +must keep rates uniform, that aggregates become "hard to
1720 +interpret") was wrong. The two real concerns it conflated
1721 +are documented elsewhere already:
1722 +
1723 +- "small flows missed at high sampling rates" -- preserved
1724 + under the Overview's "What sampling does to your numbers"
1725 + section and inside investigation-playbooks.md "Caveats"
1726 + for the security playbook.
1727 +- "exporter sends no rate" (NetFlow v7, v5 with rate=0,
1728 + v9/IPFIX without Sampling Options Template) -- preserved
1729 + in troubleshooting.md "Bandwidth doesn't match SNMP" and
1730 + in validation.md.
1731 +
1732 +Repair: deleted the entire "## 2. Ignoring the sampling
1733 +rate" section from `docs/network-flows/anti-patterns.md`.
1734 +Section numbering renumber will land with F17 (the last of
1735 +the three section removals) so that anti-patterns.md
1736 +renumbers exactly once.
1737 +
1738 +Files touched:
1739 +- docs/network-flows/anti-patterns.md (removed lines
1740 + ~23-36 plus the section header)
1741 +
1742 +#### F16 -- 2026-05-07 -- remove "Trusting GeoIP for internal IPs" anti-pattern
1743 +
1744 +User: "Geolocation does not position internal IPs on the
1745 +map. ... section must be removed."
1746 +
1747 +Verified against code: `apply_geo_record`
1748 +(`src/crates/netflow-plugin/src/enrichment/data/geoip/decode.rs:40-72`)
1749 +writes `country`, `state`, `city`, `latitude`, `longitude`
1750 +ONLY when the MMDB record carries those fields with
1751 +non-empty values. For private / RFC 1918 IPs, the MMDB
1752 +either has no entry at all OR has an entry tagged with
1753 +`ip_class: "private"` and no country/city/coords -- so
1754 +none of those fields get written. Internal IPs do NOT
1755 +appear on geographic maps.
1756 +
1757 +The "internal IPs in random countries" claim was invented;
1758 +no such behaviour exists. Repair: deleted the entire
1759 +"## Trusting GeoIP for internal IPs" section.
1760 +
1761 +Section numbering renumber will land with F17 once all
1762 +three section removals have completed.
1763 +
1764 +Note: the troubleshooting.md "Internal IPs in random
1765 +countries" subsection (lines 134-138) carries the same
1766 +invented claim and will be addressed under F19.
1767 +
1768 +Files touched:
1769 +- docs/network-flows/anti-patterns.md (removed the
1770 + section header and body)
1771 +
1772 +#### F17 -- 2026-05-07 -- remove "Alerting on absolute volume thresholds"
1773 +
1774 +User: "Netdata does not support alerting of flows yet.
1775 +Remove this section."
1776 +
1777 +Repair: removed the entire "Alerting on absolute volume
1778 +thresholds" section from `docs/network-flows/anti-patterns.md`.
1779 +The section's own footnote already acknowledged this:
1780 +"Netdata's alerting on flow data is in development; for
1781 +now this pattern lives in your monitoring practice, not
1782 +in the plugin." So the section was advice for users to
1783 +apply outside Netdata -- not a Netdata anti-pattern.
1784 +
1785 +Renumbering: with F15, F16, and F17 all deleting sections,
1786 +the anti-patterns.md sections get renumbered in this same
1787 +commit. Final numbering: 1 (doubled aggregate) ... 9
1788 +(comparing flow counts across protocols). Removed three
1789 +old rows from the summary table (Ignored sampling, GeoIP
1790 +for internal IPs, Absolute thresholds). The cross-link
1791 +from time-series.md at line 96 ("Why time-shifted
1792 +comparison beats absolute thresholds") was rewritten to
1793 +point at the still-relevant general anti-patterns set.
1794 +
1795 +Files touched:
1796 +- docs/network-flows/anti-patterns.md (removed section,
1797 + renumbered remaining sections, dropped 3 summary-table
1798 + rows)
1799 +- docs/network-flows/visualization/time-series.md
1800 + (cross-link rewording)
1801 +
1802 +#### F19 -- 2026-05-07 -- troubleshooting.md cumulative cleanup
1803 +
1804 +User: "This page has a mix of all the above issues:
1805 +sampling, geoip, etc."
1806 +
1807 +Repair scope: surgical fixes to the cumulative
1808 +misconceptions on troubleshooting.md after F2-F18 land.
1809 +
1810 +Removed:
1811 +- "Internal IPs in random countries" subsection (lines
1812 + ~134-138). Same invented claim as F16; same code-verified
1813 + reason for removal.
1814 +
1815 +Rewrote:
1816 +- "Things that look like bugs but aren't" entries:
1817 + - "Traffic appears 2x" -- now mentions exporter + interface,
1818 + not direction (F2/F3 fix in this section too).
1819 + - "Bidirectional conversations show twice" -- reframed as
1820 + real distinct flows with usually-asymmetric volumes;
1821 + pointed at Source ASN / Destination ASN filtering, not
1822 + "direction" filtering.
1823 + - Removed the "Internal IPs in odd countries" bullet entirely.
1824 + - "City map empty over long windows" -- "tier-0" replaced
1825 + with "raw-tier" for consistency with the field-reference
1826 + and tier-naming used elsewhere.
1827 +
1828 +Items checked and kept:
1829 +- "Sampling rate not honoured by the exporter" framing is
1830 + correct (F4/F5 already updated this; the real concern is
1831 + the exporter not communicating the rate, not "uniform
1832 + rates required").
1833 +- Doubling references in the SNMP-mismatch table (F2/F3
1834 + already updated).
1835 +- ASN provider chain debug recipe -- code-anchored at
1836 + `enrichment/data/network/asn.rs`.
1837 +- Decapsulation destructive-on-non-tunnel framing -- code-
1838 + anchored at `decoder/protocol/...`.
1839 +
1840 +Items DEFERRED to the per-page audit (R2) because they
1841 +need vendor-doc verification:
1842 +- "Cisco's default template refresh is 30 minutes" --
1843 + vendor-specific claim; verify against current Cisco
1844 + IOS-XE / IOS-XR documentation in R2.
1845 +
1846 +Files touched:
1847 +- docs/network-flows/troubleshooting.md
1848 +
1849 +#### F14 -- 2026-05-07 -- validation.md rewrite
1850 +
1851 +User: "I think the entire 'Validation and Data Quality'
1852 +is completely off. It mentions again sampling rates, etc.
1853 +It is like it was written by someone that does not have
1854 +a clue of what netdata is and how the plugin works."
1855 +
1856 +Code-verified facts driving the rewrite:
1857 +
1858 +- **Per-flow sampling-rate multiplication** at decode time:
1859 + `decoder/record/core/record.rs:24-26`. The user does NOT
1860 + need to monitor "sampling rate change" or "sampling rate
1861 + misinterpretation" -- these are not user-side risks.
1862 +- **Template persistence** across plugin restarts:
1863 + `decoder/protocol/v9/templates.rs:106` and
1864 + `decoder/protocol/ipfix/templates/data.rs:67`. The user
1865 + does NOT need to monitor "template loss after collector
1866 + restart".
1867 +- **UDP buffer overflow alert** already exists at
1868 + `src/health/health.d/udp_errors.conf:6-19`
1869 + (`1m_ipv4_udp_receive_buffer_errors`, fires when
1870 + RcvbufErrors > 10/min). Reframe UDP drops as an existing
1871 + alert to consume, not a "silent failure" the user must
1872 + hunt down.
1873 +
1874 +Page rewritten from scratch:
1875 +
1876 +- New opening: states up-front that the plugin handles
1877 + per-flow scaling, template persistence, and database
1878 + refresh internally; what's left to validate is
1879 + exporter-side and configuration drift.
1880 +- New "What you actually need to watch" table with five
1881 + items (kernel UDP drops -> existing alert; exporter
1882 + stopped sending; wrong interfaces being exported;
1883 + exporter sampling but not communicating rate; stale
1884 + MMDB).
1885 +- Removed the original silent-failure list items
1886 + "Sampling rate misinterpretation", "Sampling rate
1887 + change", "Template loss after collector restart" --
1888 + three items confirmed not user-side risks.
1889 +- Removed the "Internal IP enrichment validation" section
1890 + (F16 confirmed GeoIP does not position internal IPs).
1891 +- Renamed "Sampling rate sanity check" to "Sampling rate
1892 + verification" with the bogus uniform-rate framing
1893 + removed; kept the practical RAW_BYTES vs BYTES
1894 + comparison recipe (the only useful piece of the old
1895 + section).
1896 +- Removed the "Template cache health" subsection
1897 + entirely. (The `template_errors` chart is still in the
1898 + plugin-side alerting table, but as an exporter-config
1899 + signal, not a "user must watch in case templates get
1900 + lost" risk.)
1901 +- Renamed the alerting table from "what to monitor and
1902 + what alerts to consider" to "Plugin-side signals worth
1903 + alerting on"; clarified that these are signals the
1904 + plugin already exposes for the operator to alert on,
1905 + not "silent failures" the dashboard hides.
1906 +
1907 +Files touched:
1908 +- docs/network-flows/validation.md (full rewrite, kept
1909 + frontmatter and the surviving sections in place)
1910 +
1911 +#### F13 -- 2026-05-07 -- sizing-capacity.md rewrite as a practical guide
1912 +
1913 +User: "Sizing and Capacity planning is written like an
1914 +academic paper that must prove productivity of the testing
1915 +environment. People want sizing and planning directions.
1916 +This is not an academic paper, not a blog."
1917 +
1918 +User-stated requirements (the seven bullets):
1919 +1. what is the cap of the plugin
1920 +2. how ingestion rate affects storage
1921 +3. raw tier monopolizes storage; needs fast NVMe
1922 +4. journal uses free system memory as page cache; bigger
1923 + database -> more free RAM
1924 +5. journal is fully indexed; FTS means full scan
1925 +6. 25k flows/s sustained approaches ISP-level capacity
1926 +7. distributed deployment -- one Netdata per router; no
1927 + central aggregation needed
1928 +
1929 +Plus: remove benchmarks and tests from this page.
1930 +
1931 +Page rewritten from scratch. New shape:
1932 +
1933 +- Opening paragraph: states the design intent (one network
1934 + per agent). Anchors the 25k flows/s = ISP-level scale
1935 + immediately.
1936 +- "Plugin throughput cap" -- single-thread post-decode
1937 + hot path; saturation around 30k high-cardinality, above
1938 + 60k low-cardinality; recommend 25k sustained as the
1939 + comfortable steady-state. Burst handling.
1940 +- "Distributed deployment is the scaling answer" --
1941 + the user's central thesis. One agent per router / per site,
1942 + federated via Netdata Cloud. Why this beats pushing more
1943 + through a single collector. Recommended shape for
1944 + multi-site deployments.
1945 +- "Storage" -> "How ingestion rate maps to disk" -- one
1946 + table, four rows, derived from the storage-footprint
1947 + benchmark (~800 bytes/flow on disk). Includes a
1948 + cardinality caveat. No benchmark methodology.
1949 +- "Storage" -> "Raw tier dominates" -- explicit; rollups
1950 + are tiny; example per-tier config sized for production
1951 + use.
1952 +- "Storage" -> "Use fast NVMe for the raw tier" -- direct,
1953 + no hedging. Mentions that slow storage forces shorter
1954 + raw-tier retention.
1955 +- "Memory" -- routing trie footprint, page-cache framing,
1956 + and the existing memory-monitoring chart references.
1957 +- "Querying -- what's fast and what isn't" -- indexed
1958 + fields are O(log) on selectivity; FTS is full scan of
1959 + raw tier and forces raw-tier; 30s query timeout
1960 + implications.
1961 +- "Practical checklist before you deploy" -- seven
1962 + concrete steps mirroring the user's seven bullets.
1963 +
1964 +Removed:
1965 +- All "What was measured" / "Detailed measurements" / the
1966 + per-protocol per-cardinality benchmark tables (Phase 1.0
1967 + output). Those numbers stay in the netflow-plugin README
1968 + for engineering reference; they are not customer
1969 + guidance and were the wrong genre for this page.
1970 +- "Bounding storage for capacity planning" formula derivation
1971 + (which was already partly invalid because it ignored
1972 + tier rollover and dedup).
1973 +
1974 +Files touched:
1975 +- docs/network-flows/sizing-capacity.md (full rewrite)
1976 +
1977 +#### F12 -- 2026-05-07 -- Retention/Querying restructure; new Visualization Overview
1978 +
1979 +User: "Retention is closer to configuration and querying
1980 +is closer to visualization. ... If you need to put generic
1981 +visualization rules, these should be a generic
1982 +'Visualization/Overview' page, to explain FTS, sharing,
1983 +grouping, etc."
1984 +
1985 +Repair: split the old retention-querying.md into a
1986 +retention-only page and a new visualization-overview page.
1987 +
1988 +New file: `docs/network-flows/visualization/overview.md`
1989 +- "How queries work" -- query modes, parameters, defaults,
1990 + 30s timeout (moved from retention-querying.md).
1991 +- "Group-by limit and overflow" (moved).
1992 +- "Full-text search" (moved + expanded to explain when to
1993 + use it vs the indexed filter ribbon).
1994 +- "URL sharing" (moved; reframed as part of generic
1995 + visualization, not its own standalone section).
1996 +- "Filtering" (cross-link to filters-facets.md).
1997 +- "Picking the right view" (cross-link to each panel).
1998 +
1999 +Updated `docs/network-flows/retention-querying.md`:
2000 +- Sidebar label: "Retention and Querying" -> "Retention
2001 + and Tiers" (matches the new content scope).
2002 +- Removed sections: "How queries work, briefly",
2003 + "Group-by limit and overflow", "Full-text search",
2004 + "URL sharing" (all moved to the new visualization/overview).
2005 +- Page intro now points users to Configuration for retention
2006 + config and to Visualization Overview for query semantics.
2007 +- Renamed remaining mentions of "tier 0" / "tier-0" to
2008 + "raw tier" / "raw-tier" for consistency.
2009 +
2010 +Updated `docs/.map/map.yaml`:
2011 +- Visualization sub-section root now carries `edit_url:`
2012 + pointing at visualization/overview.md (so clicking
2013 + "Visualization" in the sidebar opens the Overview, the
2014 + same pattern as F1's section-root fix).
2015 +- "Retention and Querying" sidebar label renamed to
2016 + "Retention and Tiers".
2017 +- "Sizing and Capacity Planning" description updated post
2018 + F13 (no more benchmarks).
2019 +
2020 +#### F22 -- 2026-05-07 -- remove redundant "Section index" from the Overview
2021 +
2022 +User: "The 'Section index' in the overview page is not
2023 +needed. Learn already shows the index as a side bar."
2024 +
2025 +Repair: removed the entire `## Section index` section from
2026 +`docs/network-flows/README.md`. The Learn sidebar already
2027 +shows the same hierarchy. The "Where to start" section
2028 +above it stays (it's role-based guidance, not a duplicate
2029 +of the sidebar). Updated the "specific feature in depth"
2030 +bullet to point at the sidebar instead of the deleted
2031 +section.
2032 +
2033 +Files touched:
2034 +- docs/network-flows/visualization/overview.md (created)
2035 +- docs/network-flows/retention-querying.md (slimmed,
2036 + sidebar label renamed)
2037 +- docs/.map/map.yaml
2038 +- docs/network-flows/README.md (removed Section index)
2039 +
2040 +#### Current audit -- 2026-05-07 -- metadata.yaml is first-class docs source
2041 +
2042 +The follow-up review treats `src/crates/netflow-plugin/metadata.yaml`
2043 +as a public documentation source, not merely generator plumbing. The
2044 +generated files under `src/crates/netflow-plugin/integrations/*.md`
2045 +carry a `DO NOT EDIT` banner and are downstream of that metadata file,
2046 +so every metadata issue below also reaches Learn / integration-card
2047 +content after regeneration.
2048 +
2049 +Current branch reviewed: `netflow-docs-repair`.
2050 +
2051 +Status of previously suspected issues at the start of this pass:
2052 +
2053 +- **Found during current audit -- timestamp source is computed but not used for live
2054 + journal timestamps.** `docs/network-flows/configuration.md:104-112`
2055 + says `timestamp_source` controls dashboard timestamps. The decoder
2056 + carries `DecodedFlow.source_realtime_usec` at
2057 + `src/crates/netflow-plugin/src/decoder.rs:149-151`, but live ingest
2058 + still calls `ingest_decoded_record(receive_time_usec, &flow.record)`
2059 + at `src/crates/netflow-plugin/src/ingest/service/runtime.rs:82-84`
2060 + and writes both source and entry realtime from `receive_time_usec` at
2061 + `runtime.rs:129-131`. This is a code/docs mismatch. Recommended
2062 + fix: code should thread `flow.source_realtime_usec.unwrap_or(receive_time_usec)`
2063 + into raw writes and tier observation; docs can then stay conceptually
2064 + correct.
2065 +- **Found during current audit -- removed top-level retention keys remain in
2066 + `metadata.yaml` and generated integration docs.** The current code
2067 + schema has only `JournalConfig { journal_dir, tiers, query_max_groups }`
2068 + at `src/crates/netflow-plugin/src/plugin_config/types/journal.rs:11-31`;
2069 + retention is per-tier under `JournalTierRetentionOverrides` at
2070 + `journal.rs:83-112`. But `metadata.yaml` still documents
2071 + `journal.size_of_journal_files` / `journal.duration_of_journal_files`
2072 + at lines 96-102, 246-252, and 383-389, and the NetFlow example still
2073 + uses top-level `journal.size_of_journal_files` / `duration_of_journal_files`
2074 + at lines 132-135. Generated `netflow.md`, `ipfix.md`, and `sflow.md`
2075 + repeat the same invalid options. Because the Rust structs use
2076 + `#[serde(deny_unknown_fields)]`, this documented YAML now fails
2077 + config parsing. Fix source in `metadata.yaml`, then regenerate.
2078 +- **Found during current audit -- IPFIX and sFlow metadata still call 2055 the
2079 + "standard port".** `metadata.yaml:262` says IPFIX listens on the
2080 + standard port while the example uses `0.0.0.0:2055`; `metadata.yaml:399`
2081 + says the same for sFlow. Code only proves 2055 is Netdata's default
2082 + listener at `src/crates/netflow-plugin/src/plugin_config/types/listener.rs:6`.
2083 + External protocol evidence: IANA registers IPFIX on 4739 and sFlow on
2084 + 6343. Fix wording to "Netdata default listener port" or state that
2085 + users may choose any UDP listener and must configure exporters to match.
2086 +- **Found during current audit -- invented internal-IP geolocation claim remains in
2087 + metadata and maps docs.** `metadata.yaml:598-604`,
2088 + `src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md:184`,
2089 + and `docs/network-flows/visualization/maps-globe.md:77-79` still
2090 + discuss "internal IPs appearing in random countries." Code only writes
2091 + country/city/state/coordinate fields when the MMDB record has a
2092 + non-empty value (`src/crates/netflow-plugin/src/enrichment/data/geoip/decode.rs:40-72`).
2093 + This is the same false premise as F16 and F19, but not fully removed.
2094 + Fix source in `metadata.yaml` and the maps page, then regenerate.
2095 +- **Found during current audit -- `OBSERVATION_TIME_MILLIS` contradiction remains.**
2096 + `docs/network-flows/field-reference.md:138` says the field is IPFIX
2097 + observation time, while the master table at line 297 says IPFIX has no
2098 + canonical mapping in this build. Code maps only NetFlow v9
2099 + `ObservationTimeMilliseconds` at
2100 + `src/crates/netflow-plugin/src/decoder/record/mappings.rs:37`; IPFIX
2101 + falls through to `_ => None` at `mappings.rs:116`. Fix docs unless
2102 + product decides to add IPFIX support in code.
2103 +- **Found during current audit -- visualization overview documents unsupported `last`
2104 + query parameter.** `docs/network-flows/visualization/overview.md:22`
2105 + lists `after` / `before`, or `last`. The accepted function parameters
2106 + are enumerated in `src/crates/netflow-plugin/src/api/flows/params.rs:5-18`
2107 + and do not include `last`. Fix docs unless code intentionally adds
2108 + a shorthand.
2109 +- **Found during current audit -- "tier 0" terminology remains.** Examples:
2110 + `docs/network-flows/visualization/time-series.md:48-55`,
2111 + `docs/network-flows/visualization/maps-globe.md:51,103`,
2112 + `docs/network-flows/field-reference.md:155`, and
2113 + `docs/network-flows/investigation-playbooks.md:70,97,145`.
2114 + Code/config use `raw` (`JournalTierRetentionOverrides.raw` at
2115 + `journal.rs:86-87`; stock config `journal.tiers.raw`). Fix docs to
2116 + "raw tier" consistently.
2117 +- **Found during current audit -- pcap references remain while
2118 + the acceptance criterion still says no pcap anywhere.**
2119 + `docs/network-flows/troubleshooting.md:72,217,227` still recommend
2120 + `tcpdump -w`. This may be useful support guidance, but it contradicts
2121 + the explicit acceptance criterion at line 68. Either remove the public
2122 + pcap workflow, or update the SOW acceptance criterion to allow packet
2123 + captures strictly as troubleshooting artifacts.
2124 +
2125 +New findings from the current branch:
2126 +
2127 +- **F23 -- public docs link to source-tree integration markdown paths
2128 + that Learn will not serve.** Many Network Flows pages link to
2129 + `/src/crates/netflow-plugin/integrations/*.md`, e.g.
2130 + `docs/network-flows/quick-start.md:105,180,187-189`,
2131 + `docs/network-flows/configuration.md:221-224`,
2132 + `docs/network-flows/enrichment.md:419-440`,
2133 + `docs/network-flows/installation.md:80,130-132`, and
2134 + `docs/network-flows/visualization/maps-globe.md:75,79,112-113`.
2135 + Those are repository source paths, not Learn URLs. The integration
2136 + cards are generated into the Network Flows integration placeholder and
2137 + should be linked through their Learn routes, not `/src/...`. Fix all
2138 + source-path links.
2139 +- **F24 -- `metadata.yaml` still uses stale UI wording "Network Flows
2140 + tab".** F7 fixed many markdown pages, but metadata still says
2141 + "Network Flows tab" at lines 153, 627, 865, 1100, 1679, 2273, 2564,
2142 + 3517, 3560, 3959, 4379, and 4653. Generated integration cards repeat
2143 + the same stale term. Fix metadata to "Network Flows view" or
2144 + "Live tab > Network Flows" depending on context, then regenerate.
2145 +- **F25 -- old "Sources" label remains in public markdown links.**
2146 + The catalog label is now "Flow Protocols" in
2147 + `integrations/categories.yaml:70-72`, but docs still say
2148 + "Sources / NetFlow" or "Sources" at
2149 + `docs/network-flows/quick-start.md:105`,
2150 + `docs/network-flows/installation.md:130-132`, and
2151 + `docs/network-flows/anti-patterns.md:121`. Fix wording and target URLs.
2152 +- **F26 -- public docs and generated integration cards contain internal
2153 + code citations.** There are hundreds of `src/crates/...` / `.rs:line`
2154 + citations in generated integration cards and several in
2155 + `docs/network-flows/enrichment.md` / `intel-downloader.md`. These are
2156 + useful audit evidence, but they read as implementation notes in
2157 + end-user/operator documentation. The SOW can keep code citations; public
2158 + docs should translate them into operator-facing behavior and only link
2159 + to code where the user explicitly needs source. Needs a per-page/content
2160 + decision during repair, but the current state is not fit for polished
2161 + Learn docs.
2162 +
2163 +Repair ordering recommendation:
2164 +
2165 +1. Fix `metadata.yaml` first for F23/F24 plus the still-open retention,
2166 + port, and internal-IP findings; regenerate integration pages.
2167 +2. Fix the remaining markdown-only regressions (`timestamp_source`,
2168 + `OBSERVATION_TIME_MILLIS`, `last`, raw-tier terminology, pcap decision).
2169 +3. Re-run link checks and grep sweeps against both `docs/network-flows/`
2170 + and generated `src/crates/netflow-plugin/integrations/*.md`.
2171 +4. Run the integration generator and the narrow netflow-plugin tests
2172 + touched by any code fix.
2173 +
2174 +#### Current audit repair progress -- 2026-05-07
2175 +
2176 +First repair pass completed:
2177 +
2178 +- `src/crates/netflow-plugin/metadata.yaml` now documents per-tier
2179 + `journal.tiers.<tier>.size_of_journal_files` and
2180 + `journal.tiers.<tier>.duration_of_journal_files` instead of removed
2181 + top-level retention keys.
2182 +- The NetFlow extended-retention example now uses `journal.tiers.raw`,
2183 + `minute_1`, `minute_5`, and `hour_1`.
2184 +- IPFIX and sFlow setup examples now say "Netdata's default flow
2185 + listener port" instead of "standard port".
2186 +- The DB-IP troubleshooting entry now says private IPs have empty GeoIP
2187 + fields and do not appear on maps, instead of "internal IPs appearing
2188 + in random countries".
2189 +- `Network Flows tab` was mechanically replaced with `Network Flows
2190 + view` in metadata.
2191 +- Regenerated integration pages with `python3 integrations/gen_integrations.py`
2192 + and `python3 integrations/gen_docs_integrations.py`.
2193 +- Verified the following patterns are gone from `metadata.yaml` and
2194 + generated integration cards:
2195 + `journal.size_of_journal_files`,
2196 + `journal.duration_of_journal_files`,
2197 + `standard port`,
2198 + `Internal IPs appearing`,
2199 + `random countries`,
2200 + `Network Flows tab`.
2201 +
2202 +Markdown repair pass completed:
2203 +
2204 +- Repointed public docs away from `/src/crates/netflow-plugin/integrations/*.md`
2205 + source paths to Learn routes under `/docs/network-flows/flow-protocols/...`
2206 + and `/docs/network-flows/enrichment-methods/...`.
2207 +- Replaced stale "Sources / ..." wording with "Flow Protocols / ...".
2208 +- Removed the unsupported `last` query-parameter claim from
2209 + `docs/network-flows/visualization/overview.md`; it now says omitted
2210 + `after` / `before` defaults to the last 15 minutes, matching
2211 + `src/crates/netflow-plugin/src/query/planner/request.rs:3-15`.
2212 +- Removed the remaining internal-IP/random-country claim from
2213 + `docs/network-flows/visualization/maps-globe.md`.
2214 +- Replaced remaining `tier 0` / `tier-0` language in Network Flows markdown
2215 + with `raw tier` / `raw-tier`.
2216 +- Fixed `docs/network-flows/field-reference.md` so
2217 + `OBSERVATION_TIME_MILLIS` is documented as NetFlow v9-only in this
2218 + build, and so timestamp fields are not described as dashboard
2219 + time-picker fields. Code evidence:
2220 + `src/crates/netflow-plugin/src/decoder/record/mappings.rs:37,116`,
2221 + `src/crates/netflow-plugin/src/query/request/constants.rs:75-79`,
2222 + `src/crates/netflow-plugin/src/query/fields/metrics.rs:19-20`.
2223 +- Verified the field-reference master index still has the same 91
2224 + canonical fields as `src/crates/netflow-plugin/src/flow/schema.rs`.
2225 +
2226 +Follow-up repair after timestamp clarification:
2227 +
2228 +- `timestamp_source` is no longer open. The code now passes the selected
2229 + decoded source timestamp (`DecodedFlow.source_realtime_usec`) into
2230 + `_SOURCE_REALTIME_TIMESTAMP` while preserving journal entry realtime as
2231 + receive/write time. This matches the append-only journal contract and
2232 + handles out-of-order source timestamps across exporters. Evidence:
2233 + `src/crates/netflow-plugin/src/ingest/service/runtime.rs:82-139` and
2234 + `src/crates/netflow-plugin/src/main_tests.rs:76-94`.
2235 +- `docs/network-flows/configuration.md` now states that `timestamp_source`
2236 + controls stored source timestamp metadata, not dashboard query windows.
2237 + Query windows and tier selection still use journal entry realtime.
2238 +- The pcap acceptance conflict is no longer open. Public docs now say
2239 + "packet-capture file" and `tcpdump -w`; they no longer mention pcap by
2240 + name. This keeps troubleshooting guidance without implying a pcap ingest
2241 + feature.
2242 +- Validation: `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml timestamp_source -- --nocapture`
2243 + passed with 5 tests. A pre-existing warning remains in
2244 + `src/crates/netflow-plugin/src/startup_memory_tests.rs` for an unused
2245 + `bytesize::ByteSize` import.
2246 +- Validation: `git diff --check` passed for the SOW, Network Flows docs,
2247 + metadata, generated integration docs, and touched netflow-plugin Rust
2248 + files.
2249 +- Validation: grep now finds no stale public-doc occurrences of
2250 + `journal.size_of_journal_files`, `journal.duration_of_journal_files`,
2251 + `standard port`, `Network Flows tab`, `Sources /`, `[Sources]`,
2252 + `tier 0`, `tier-0`, unsupported ``or `last`` wording, IPFIX observation
2253 + time wording, random-country/internal-IP wording, or pcap wording. The
2254 + only timestamp-source grep hit is the intentional docs warning that the
2255 + dashboard time picker does not query by exporter timestamps.
2256 +
2257 +User decision:
2258 +
2259 +- Public Network Flows documentation and generated integration cards are
2260 + end-user/operator documentation. They must not contain internal source-code
2261 + citations, internal test-status citations, quality-gate evidence, SOW/review
2262 + notes, or "current implementation state" wording. They should document
2263 + supported behavior, operator-visible limits, configuration contracts,
2264 + troubleshooting signals, and production cautions without exposing internal
2265 + proof or test gaps. Internal evidence belongs in the SOW, not in Learn docs.
2266 +
2267 +F26 repair completed:
2268 +
2269 +- Removed internal source-code paths, line references, source-test notes,
2270 + "current implementation state" wording, and test-gap disclosures from
2271 + `docs/network-flows/*.md` and `src/crates/netflow-plugin/metadata.yaml`.
2272 +- Rewrote those passages into operator-facing behavior: supported enrichment
2273 + semantics, visible limits, configuration validation, refresh cadence, TLS
2274 + safety, troubleshooting symptoms, and production cautions.
2275 +- Regenerated the integration cards with
2276 + `python3 integrations/gen_integrations.py` and
2277 + `python3 integrations/gen_docs_integrations.py`.
2278 +- Generated integration files still contain `custom_edit_url` and `meta_yaml`
2279 + entries inside their `<!--startmeta ... endmeta-->` blocks. These are
2280 + generator metadata, not user-facing page body text.
2281 +
2282 +F26 validation:
2283 +
2284 +- `rg -n 'src/|github\.com/netdata/netdata/(blob|tree)/master/src|\.rs:[0-9]|\.go:[0-9]|unit test|unit-tested|integration-test|integration-tested|integration test|NOT integration|Source code:|defined at|schema is defined|validated at|validation rejects values|production logs|current build|in this build|fixtures|tests here|repository|quality gate|current state|not tested|not validated|test gap|line reference|current implementation|implementation state|codebase|SOW|regression|review pass|internal citation' docs/network-flows src/crates/netflow-plugin/metadata.yaml`
2285 + returned no matches.
2286 +- The same pattern scan against `src/crates/netflow-plugin/integrations/*.md`
2287 + while skipping generated `<!--startmeta ... endmeta-->` blocks returned no
2288 + matches.
2289 +- `python3 -c 'import yaml; yaml.safe_load(open("src/crates/netflow-plugin/metadata.yaml")); print("metadata yaml ok")'`
2290 + passed.
2291 +- `git diff --check` passed for the SOW, Network Flows docs, metadata,
2292 + generated integration docs, and touched netflow-plugin Rust files.
2293 +
2294 +F26 residual findings from full-page subagent review:
2295 +
2296 +- The prior regex validation was too narrow. It caught explicit source paths,
2297 + source line references, test-gap wording, and quality-gate labels, but missed
2298 + less literal end-user trust issues such as "today", "known feature gap",
2299 + "verified against", "same code path", implementation type names, and
2300 + benchmark-provenance phrasing.
2301 +- Read-only subagents reviewed the assigned public pages in full, not only by
2302 + grep. Scope covered all hand-authored Network Flows markdown pages,
2303 + `src/crates/netflow-plugin/metadata.yaml`, and all generated integration
2304 + cards.
2305 +
2306 +Residual docs findings:
2307 +
2308 +- **Visualization current-state / backlog wording.**
2309 + `docs/network-flows/visualization/dashboard-cards.md:72,79,81` says signals
2310 + "aren't published today", are "collected internally", and are "not hard to
2311 + add but haven't been needed enough yet". This is product backlog / internal
2312 + state wording.
2313 + `docs/network-flows/visualization/filters-facets.md:32,68` says negative
2314 + matching is a "known feature gap" and that no good workaround exists "today".
2315 + These should be rewritten as stable supported/unsupported behavior.
2316 +- **Sizing page benchmark-provenance wording.**
2317 + `docs/network-flows/sizing-capacity.md:23,50,96` exposes benchmark machine
2318 + details, synthetic benchmark provenance, and "bench numbers" in public docs.
2319 + The page should present operator sizing guidance directly; evidence stays in
2320 + the SOW.
2321 +- **Enrichment / validation developer wording.**
2322 + `docs/network-flows/enrichment.md:107,156` says multiple inputs use the
2323 + "same code path"; this is implementation wording.
2324 + `docs/network-flows/enrichment.md:230,276` uses "today" limitation wording.
2325 + `docs/network-flows/enrichment.md:393` exposes the source-tree-style
2326 + `cmd/ris/` path in prose.
2327 + `docs/network-flows/validation.md:22,68` says the plugin does not publish
2328 + per-exporter ingest counters "today"; this should be stable limitation
2329 + wording.
2330 +
2331 +Residual metadata / generated-card findings:
2332 +
2333 +- **Quality-gate / verification wording in generated cards.**
2334 + `src/crates/netflow-plugin/metadata.yaml:2015` and generated
2335 + `src/crates/netflow-plugin/integrations/aws_ip_ranges.md:41` say the AWS
2336 + schema was "verified against the live document".
2337 + `src/crates/netflow-plugin/metadata.yaml:2666` and generated
2338 + `src/crates/netflow-plugin/integrations/azure_ip_ranges.md:147` say Azure
2339 + behavior was "verified against the upstream concept page".
2340 + These are internal review-evidence statements.
2341 +- **Current implementation-state wording in generated cards.**
2342 + `src/crates/netflow-plugin/metadata.yaml:2305` and generated
2343 + `src/crates/netflow-plugin/integrations/gcp_ip_ranges.md:60` say ETags are
2344 + not used for conditional fetches "today".
2345 + `src/crates/netflow-plugin/metadata.yaml:4339,4503` and generated
2346 + `src/crates/netflow-plugin/integrations/decapsulation.md:54,265` say parsed
2347 + tunnel fields are "not surfaced today".
2348 +- **Internal implementation names in generated cards.**
2349 + `src/crates/netflow-plugin/metadata.yaml:2939` and generated
2350 + `src/crates/netflow-plugin/integrations/netbox.md:131` expose
2351 + `RemoteNetworkSourceConfig`.
2352 + `src/crates/netflow-plugin/metadata.yaml:3432` and generated
2353 + `src/crates/netflow-plugin/integrations/generic_json-over-http_ipam.md:321`
2354 + expose `decode_remote_records`.
2355 +- **Untrusted-feature wording in decapsulation docs.**
2356 + `src/crates/netflow-plugin/metadata.yaml:4395,4408,4414` and generated
2357 + `src/crates/netflow-plugin/integrations/decapsulation.md:124,137,143` say
2358 + "the project has verified", "have not been verified by the project", and
2359 + "unverified". Operator docs should state recommended/supported vendor
2360 + configuration patterns without exposing internal validation status.
2361 +
2362 +Repair classification:
2363 +
2364 +- All residual findings are documentation-source fixes. No code behavior change
2365 + is indicated by this review.
2366 +- Hand-authored markdown findings should be fixed in the corresponding
2367 + `docs/network-flows/*.md` file.
2368 +- Generated-card findings must be fixed in
2369 + `src/crates/netflow-plugin/metadata.yaml`, then regenerated with
2370 + `python3 integrations/gen_integrations.py` and
2371 + `python3 integrations/gen_docs_integrations.py`. Do not hand-edit generated
2372 + integration cards.
2373 +- At classification time, F26 remained open until these residual findings were
2374 + repaired and a broader wording scan was added to validation.
2375 +
2376 +F26 residual repair completed:
2377 +
2378 +- Rewrote hand-authored docs so they state stable supported/unsupported
2379 + behavior without backlog, "today", implementation, or benchmark-provenance
2380 + wording.
2381 +- Rewrote `src/crates/netflow-plugin/metadata.yaml` source text to remove
2382 + quality-gate evidence, implementation type/function names, "today" current
2383 + state wording, and "verified/unverified by the project" statements.
2384 +- Regenerated generated integration cards with
2385 + `python3 integrations/gen_integrations.py` and
2386 + `python3 integrations/gen_docs_integrations.py`.
2387 +- All residual findings above are now repaired. Generated integration files
2388 + were updated only through `metadata.yaml`.
2389 +
2390 +F26 residual validation:
2391 +
2392 +- Residual-pattern scan returned no matches over hand-authored docs and
2393 + metadata:
2394 + `RemoteNetworkSourceConfig|verified against the live document|conditional fetches today|verified against the upstream concept page|decode_remote_records|not surfaced today|fields today|project has verified|not been verified|unverified|not visible today|cannot be consumed today|same code path|cmd/ris/|doesn.t publish per-exporter.*today|aren.t published today|collected internally|not hard to add|known feature gap|no good workaround exists today|bench numbers|synthetic high-cardinality|i9-class|FireCuda`.
2395 +- The same residual-pattern scan over generated
2396 + `src/crates/netflow-plugin/integrations/*.md` body text, skipping
2397 + `<!--startmeta ... endmeta-->`, returned no matches.
2398 +- The original stricter internal-citation scan over hand-authored docs,
2399 + metadata, and generated integration body text also returned no matches.
2400 +- `python3 -c 'import yaml; yaml.safe_load(open("src/crates/netflow-plugin/metadata.yaml")); print("metadata yaml ok")'`
2401 + passed.
2402 +- `git diff --check` passed for the SOW, Network Flows docs, metadata, and
2403 + generated integration docs.
2404 +
2405 +Quality review decision:
2406 +
2407 +- User decision 2026-05-07: do not change deployment guidance to avoid
2408 + double counting at ingestion. The correct deployment guidance is to export
2409 + all relevant interfaces and all directions. Double counting is a
2410 + visualization and interpretation guideline: users must understand what they
2411 + selected and how bidirectional/interface-overlap views should be read.
2412 +
2413 +Quality review repair completed:
2414 +
2415 +- Aligned IP-intelligence install/default behavior across public docs:
2416 + native packages ship stock DB-IP MMDBs under stock data, source builds do
2417 + not include stock MMDBs, the downloader writes fresher cache copies, and
2418 + Netdata does not install a downloader timer or cron job.
2419 +- Reworded retention defaults as suitable for first validation and small
2420 + deployments, with production retention sized from observed flow rate.
2421 +- Replaced generated default-behavior boilerplate across all Network Flows
2422 + integration cards so cards no longer claim generic "no limits" or
2423 + "no significant performance impact".
2424 +- Fixed UDP-drop quick-reference commands to use `ss -uamn` and
2425 + `/proc/net/snmp`, not `/proc/net/udp` as a drop counter source.
2426 +- Replaced broken `#enrichment-geoip` anchors with `#enrichment`.
2427 +- Replaced literal `${CMDB_TOKEN}` examples with `<CMDB_TOKEN>` placeholders,
2428 + matching the "headers are passed verbatim" contract.
2429 +- Replaced the non-existent `Application` grouping reference with
2430 + `Destination Port` / service wording.
2431 +- Preserved all-interfaces/all-directions deployment guidance per the user
2432 + decision; no ingress-only deployment change was made.
2433 +
2434 +Quality review validation:
2435 +
2436 +- Regenerated integration artifacts with
2437 + `python3 integrations/gen_integrations.py` and
2438 + `python3 integrations/gen_docs_integrations.py`.
2439 +- Grep validation returned no matches for:
2440 + `${CMDB_TOKEN}`, `enrichment-geoip`, `Application`, `cat /proc/net/udp`,
2441 + stale default-retention wording, `Network Flows tab`, and default-card
2442 + "does not impose any limits" / "significant performance impact" wording in
2443 + Network Flows generated cards.
2444 +- Residual internal/current-state wording scans over hand-authored docs,
2445 + metadata, and generated card body text returned no matches.
2446 +- `python3 -c 'import yaml; yaml.safe_load(open("src/crates/netflow-plugin/metadata.yaml")); print("metadata yaml ok")'`
2447 + passed.
2448 +- `git diff --check` passed for the SOW, Network Flows docs, metadata, and
2449 + generated integration docs.
2450 +
2451 +## Regression Closeout - 2026-05-08
2452 +
2453 +Final status: completed.
2454 +
2455 +Closeout evidence:
2456 +
2457 +- Every regression finding F1-F26 has a repair note and validation evidence in
2458 + this regression log.
2459 +- The final quality review findings were repaired in hand-authored docs,
2460 + `metadata.yaml`, and regenerated integration cards.
2461 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml timestamp_source -- --nocapture`
2462 + passed with 5 tests. A pre-existing warning remains in
2463 + `src/crates/netflow-plugin/src/startup_memory_tests.rs` for an unused
2464 + `bytesize::ByteSize` import.
2465 +- `python3 integrations/gen_integrations.py` passed.
2466 +- `python3 integrations/gen_docs_integrations.py` passed.
2467 +- `python3 -c 'import yaml; yaml.safe_load(open("src/crates/netflow-plugin/metadata.yaml")); print("metadata yaml ok")'`
2468 + passed.
2469 +- Targeted public-doc quality scans over `docs/network-flows/`,
2470 + `src/crates/netflow-plugin/metadata.yaml`, and generated integration-card
2471 + body text returned no matches for stale anchors, misleading credential
2472 + placeholders, stale UI wording, invalid grouping field names, bad UDP-drop
2473 + commands, internal-current-state wording, source-code citations, or generic
2474 + default-behavior boilerplate.
2475 +- `git diff --check` passed for the SOW, Network Flows docs, metadata,
2476 + generated integration docs, and touched netflow-plugin Rust files.
2477 +- `.agents/sow/audit.sh` was run after final move. It reported SOW 14
2478 + status/directory as consistent in `done/` and failed only on an
2479 + unrelated pre-existing sensitive-data pattern in
2480 + `.agents/skills/mirror-netdata-repos/SKILL.md` plus existing TODO-file
2481 + warnings. None of those are part of this SOW's staged scope.
2482 +
2483 +Artifact maintenance gate for regression close:
2484 +
2485 +- **AGENTS.md**: no update needed; no workflow or project-wide guardrail changed.
2486 +- **Runtime project skills**: no update needed; no new integration-pipeline or
2487 + Learn-site process behavior was discovered beyond existing skill guidance.
2488 +- **Specs**: no update needed; this was a documentation/metadata repair and did
2489 + not change the product contract beyond correcting public docs to existing
2490 + behavior.
2491 +- **End-user/operator docs**: updated Network Flows docs, `metadata.yaml`, and
2492 + regenerated integration cards.
2493 +- **End-user/operator skills**: no update needed; the user rejected AI-skill
2494 + cross-linking as not mandatory for this SOW.
2495 +- **SOW lifecycle**: status changed to `completed`; file moved from
2496 + `.agents/sow/current/` to `.agents/sow/done/` together with the repair
2497 + commit.
2498 +
2499 +Follow-up mapping:
2500 +
2501 +- The original follow-up list remains tracked above. No new deferred item was
2502 + introduced by the regression repair. The user explicitly rejected changing
2503 + deployment guidance to ingress-only; double counting remains a visualization
2504 + and interpretation guideline, not an ingestion-side avoidance requirement.
2505 +
2506 +## Regression - 2026-05-08
2507 +
2508 +### Trigger
2509 +
2510 +PR #22449 received post-close reviewer and CI feedback after SOW 14 had been
2511 +marked `completed` and moved to `.agents/sow/done/`.
2512 +
2513 +### Purpose
2514 +
2515 +Bring PR #22449 back to merge-ready state for end-user Network Flows
2516 +documentation by addressing all valid review, CI, Codacy, and local Learn
2517 +preview findings. Add a durable, user-triggered skill/workflow for building
2518 +Learn locally with the contents of a documentation PR before merge, so future
2519 +documentation PRs can be inspected in a browser before release.
2520 +
2521 +### Evidence
2522 +
2523 +- `bash .agents/skills/pr-reviews/scripts/fetch-all.sh 22449` returned 9 open
2524 + automated review threads, with no human review comments in the fetched
2525 + snapshot.
2526 +- `bash .agents/skills/pr-reviews/scripts/ci-status.sh 22449` reported
2527 + failures in `yamllint` and `check-documentation`, `ACTION_REQUIRED` from
2528 + Codacy, 0 Sonar findings, and many still-running build checks.
2529 +- `bash .agents/skills/pr-reviews/scripts/fetch-sonar-findings.sh 22449`
2530 + returned 0 Sonar issues and 0 hotspots.
2531 +- `bash .agents/skills/codacy-audit/scripts/pr-issues.sh 22449` failed locally
2532 + before reporting findings because the helper passed a very large JSON array
2533 + through `jq --argjson`, hitting `Argument list too long`.
2534 +- `python3 ${NETDATA_REPOS_DIR}/learn/ingest/ingest.py --help` failed in the
2535 + system interpreter due to missing `pandas`; the checked-in Learn `venv`
2536 + also lacked required packages such as `GitPython`.
2537 +- After commit `0a3eda6614` was pushed and all prior threads were replied to
2538 + and resolved, `bash .agents/skills/pr-reviews/scripts/fetch-all.sh 22449`
2539 + returned 9 new open automated review threads on the new PR head and no open
2540 + human review threads.
2541 +- The new PR #22449 comments were verified against source evidence:
2542 + `.github/workflows/check-markdown.yml:54-69` regenerates `COLLECTORS.md`
2543 + before Learn ingest but does not diff-check the committed file;
2544 + `src/crates/netflow-plugin/src/enrichment/init.rs:50-64` keeps the enricher
2545 + enabled for provider-chain-only config; `network_sources/runtime.rs:24-40`
2546 + scans loaded network-source records linearly; `network_sources/service.rs:91-96`
2547 + logs HTTP refresh failures as warnings; and `reqwest-0.13.2` converts URL
2548 + userinfo to HTTP Basic auth during request build.
2549 +- A second fetch during this repair returned 2 additional automated review
2550 + threads: one on standalone CLI retention flags and one on ambiguous
2551 + visualization panel count wording.
2552 +- The CLI retention finding was valid for standalone mode:
2553 + `src/crates/netflow-plugin/src/plugin_config/runtime.rs:7-11` uses
2554 + `PluginConfig::parse()` outside Netdata, and commit `f00390e2f5` removed the
2555 + legacy top-level CLI flags while keeping `JournalConfig.tiers` skipped for
2556 + Clap.
2557 +- After commit `6f5805979d` was pushed and 11 threads were replied to and
2558 + resolved, the PR review watcher detected 2 new open automated review threads
2559 + on `src/crates/netflow-plugin/src/ingest/rebuild.rs`.
2560 +- The new rebuild findings were valid: `rebuild_materialized_from_raw()` called
2561 + `scan_journal_files_forward()` directly in the async startup path, while query
2562 + paths already use `tokio::task::spawn_blocking()` for blocking journal scans;
2563 + the rebuild timeout was checked only every 1024 scanned entries.
2564 +
2565 +### Open repair items
2566 +
2567 +- R8.1: Rewrite the SOW close-gate paragraph at the prior
2568 + `SOW-0014...md:1060` in historical tense.
2569 +- R8.2: Fix the classifier integration example so the regex matches the
2570 + documented three-letter region suffix.
2571 +- R8.3: Remove or correct the decapsulation troubleshooting advice that
2572 + suggests multiple plugin instances despite the documented single-instance
2573 + model.
2574 +- R8.4: Correct the Fedora/RHEL `geoipupdate` package name.
2575 +- R8.5: Make the static-metadata typo example actually use an unknown key.
2576 +- R8.6: Strengthen `main_tests.rs` so timestamp-source persistence cannot pass
2577 + when both journal fields are missing.
2578 +- R8.7: Tighten sizing guidance around the ~25k flows/s single-agent planning
2579 + ceiling.
2580 +- R8.8: Make storage safety-margin guidance internally consistent.
2581 +- R8.9: Correct the UDP troubleshooting note to acknowledge per-socket
2582 + `drops` in `/proc/net/udp` while keeping `RcvbufErrors` as the system-wide
2583 + counter.
2584 +- R8.10: Fix or work around the Codacy PR-fetch helper failure, then triage
2585 + Codacy findings for PR #22449.
2586 +- R8.11: Investigate and fix the CI `yamllint` and `check-documentation`
2587 + failures.
2588 +- R8.12: Create durable local Learn preview guidance that triggers only when a
2589 + user explicitly asks to build/inspect Learn from a PR.
2590 +- R8.13: Build or serve Learn locally using PR #22449 contents and record the
2591 + ingest/build/browser validation result.
2592 +- R8.14: Fix automated review comments about contradictory retention comments
2593 + and stale shared-budget retention wording.
2594 +- R8.15: Fix the broken decapsulation integration icon found during local Learn
2595 + browser inspection.
2596 +- R8.16: Verify integration-card source links in hand-authored Network Flows
2597 + docs are compatible with Learn ingest.
2598 +- R8.17: Restore bounded startup behavior for receive-time raw rebuild scans.
2599 +- R8.18: Reconcile the SOW scope text so the durable record matches the
2600 + accepted documentation scope.
2601 +- R8.19: Ensure the generated Learn "Monitor anything" page lists Network
2602 + Flows protocols and enrichment integrations, and update the integration
2603 + lifecycle skill with that mechanism.
2604 +- R8.20: Normalize Network Flows catalog descriptions so flow-source rows say
2605 + they collect network flow records and enrichment rows say they enrich or
2606 + annotate network flows, instead of describing provider publication
2607 + mechanisms, variables, defaults, or setup settings.
2608 +- R8.21: Correct `integrations-lifecycle` guidance so it says
2609 + `check-markdown.yml` catches broken generated `COLLECTORS.md` content during
2610 + Learn ingest, not stale committed artifact drift.
2611 +- R8.22: Update local Learn preview guidance to copy tracked plus untracked
2612 + non-ignored PR files into the isolated source preview.
2613 +- R8.23: Add the 32-bit packaging caveat anywhere docs tell users to run
2614 + `topology-ip-intel-downloader`.
2615 +- R8.24: Replace AWS `transform: "."` "empty result" wording with the actual
2616 + row-mapping/schema failure around the missing required `prefix` field.
2617 +- R8.25: Correct URL-credential docs for remote network sources: URL userinfo
2618 + becomes HTTP Basic auth, while explicit headers remain recommended.
2619 +- R8.26: Correct enrichment docs so provider-chain-only config is described as
2620 + enabling the enricher.
2621 +- R8.27: Make the Codacy helper's temporary file pattern portable.
2622 +- R8.28: Correct NetBox/network-source failure and performance wording:
2623 + refresh failures are warning logs, and runtime cost scales with loaded
2624 + network-source records instead of claiming trie lookup.
2625 +- R8.29: Sweep same-class wording across generated cards and hand-authored
2626 + docs so the next review does not rediscover the same problems in adjacent
2627 + pages.
2628 +- R8.30: Restore standalone CLI retention tuning without reopening the YAML
2629 + global-retention schema.
2630 +- R8.31: Clarify the visualization overview panel count so the text matches the
2631 + listed UI surfaces.
2632 +- R8.32: Move receive-time raw rebuild journal scanning and decompression off
2633 + the async startup task so Tokio runtime workers are not blocked.
2634 +- R8.33: Enforce raw rebuild timeout checks for every scanned entry rather than
2635 + only every 1024 entries.
2636 +
2637 +### Validation plan
2638 +
2639 +- Re-fetch all PR comments before push and verify no new review items were
2640 + missed.
2641 +- Run the narrow docs and Rust validations affected by the fixes.
2642 +- Run local Learn ingest/build or dev-server preview with the local PR checkout
2643 + as the `netdata` source.
2644 +- Record the local Learn URL, process PID, and cleanup path if a preview server
2645 + is started.
2646 +- Move this SOW back to `.agents/sow/done/` after PR review items, relevant
2647 + CI failures, and local Learn preview validation are handled.
2648 +
2649 +### Repair completed
2650 +
2651 +- R8.1: Reworded the prior close-gate paragraph in historical tense.
2652 +- R8.2: Fixed the classifier example regex to match the documented
2653 + three-letter region suffix.
2654 +- R8.3: Removed the decapsulation advice that implied running multiple plugin
2655 + instances.
2656 +- R8.4: Replaced `GeoIP-update` with the Fedora/RHEL package name
2657 + `geoipupdate`.
2658 +- R8.5: Changed the static-metadata typo example to `if_index`, leaving
2659 + accepted aliases documented separately.
2660 +- R8.6: Strengthened the timestamp-source e2e test so missing raw journal
2661 + fields cannot pass as `None == None`.
2662 +- R8.7/R8.8: Tightened the sizing page around the ~25k flows/s planning
2663 + envelope and made the storage safety margin consistently `1.2x to 1.5x`.
2664 +- R8.9: Corrected UDP troubleshooting so `/proc/net/udp` is described as
2665 + per-socket `drops`, while `/proc/net/snmp` `RcvbufErrors` remains the
2666 + system-wide signal.
2667 +- R8.10: Fixed the Codacy helper to avoid passing a large issue array through
2668 + `jq --argjson`; it now uses a temporary JSON file and `--slurpfile`.
2669 +- R8.11: Fixed `yamllint` findings in `metadata.yaml` and `configs/netflow.yaml`;
2670 + replaced hand-authored Learn links to generated integration pages with source
2671 + markdown links so Learn ingest rewrites them correctly.
2672 +- R8.12: Added `.agents/skills/learn-pr-preview/SKILL.md`, updated
2673 + `AGENTS.md`, and added
2674 + `.agents/skills/learn-site-structure/how-tos/preview-documentation-pr-locally.md`.
2675 +- R8.13: Built an isolated Learn preview from PR #22449 contents, ran ingest,
2676 + built Docusaurus, served the static build locally, and browser-inspected
2677 + representative Network Flows pages.
2678 +- R8.14: Reworded the Rust retention comment so optional fields and the
2679 + resolved-tier validation rule agree; corrected visualization docs that still
2680 + described raw-tier retention as a shared budget.
2681 +- R8.15: Replaced the missing `tunnel.svg` decapsulation icon with the existing
2682 + hosted `network-wired.svg` icon and regenerated integration artifacts.
2683 +- R8.16: Verified by local ingest that source docs must link to the source
2684 + integration markdown files; Learn correlates those links to final routes.
2685 + Direct final `/docs/network-flows/...` links in source markdown fail
2686 + `--fail-links-netdata`, so the source integration links were retained.
2687 +- R8.17: Restored the 30-second raw rebuild timeout for the new direct
2688 + receive-time raw scan path, checking the elapsed time during scan progress.
2689 +- R8.18: Updated the SOW purpose/request wording to match the final accepted
2690 + scope: document existing Network Flows behavior and exclude non-existent
2691 + topology drilldown behavior.
2692 +- R8.19: Updated `integrations/gen_doc_collector_page.py` so the top-level
2693 + `flows` category is grouped as a first-class `Network Flows` section in
2694 + `src/collectors/COLLECTORS.md`; regenerated the file; updated the
2695 + `integrations-lifecycle` skill and added a how-to for this generator rule.
2696 +- R8.20: Updated `src/crates/netflow-plugin/metadata.yaml` so generated
2697 + Network Flows catalog rows use action-oriented user copy:
2698 + `Collect network flow records...`, `Enrich network flows...`, or
2699 + `Annotate network flows...`; regenerated per-integration markdown and
2700 + `src/collectors/COLLECTORS.md`; updated the `integrations-lifecycle`
2701 + skill with description-authoring rules.
2702 +- R8.21: Updated `.agents/skills/integrations-lifecycle/pipeline.md` so
2703 + `check-markdown.yml` is documented as regenerating `COLLECTORS.md` before
2704 + Learn ingest and catching broken generated content, while artifact drift is
2705 + left to the integration regeneration workflow.
2706 +- R8.22: Updated `.agents/skills/learn-pr-preview/SKILL.md` and the matching
2707 + Learn-site how-to to use `git ls-files -co --exclude-standard`, so previews
2708 + include intentional untracked PR docs without copying ignored build output.
2709 +- R8.23: Added the packaged 32-bit downloader caveat to IPtoASN and DB-IP
2710 + generated cards plus hand-authored installation, validation, and downloader
2711 + docs.
2712 +- R8.24: Updated AWS IP Ranges metadata and generated card so the default
2713 + `transform: "."` failure is described as missing required `prefix` rows, not
2714 + as empty transform output.
2715 +- R8.25: Updated generic JSON-over-HTTP IPAM metadata/generated docs and the
2716 + hand-authored enrichment page so URL userinfo is described as HTTP Basic auth
2717 + conversion, with explicit `Authorization` headers recommended for clarity.
2718 +- R8.26: Updated the hand-authored enrichment page so the enricher is described
2719 + as running when any enrichment feature is configured, including provider
2720 + chains.
2721 +- R8.27: Updated `.agents/skills/codacy-audit/scripts/pr-issues.sh` and its
2722 + how-to to use an explicit portable `mktemp` template.
2723 +- R8.28/R8.29: Updated AWS, Azure, NetBox, and generic HTTP network-source
2724 + generated cards so runtime enrichment cost is described as prefix matching
2725 + over loaded records; updated NetBox troubleshooting so HTTP errors are logged
2726 + as refresh-failed warnings.
2727 +- R8.30: Restored
2728 + `--netflow-retention-size-of-journal-files` and
2729 + `--netflow-retention-duration-of-journal-files` as CLI-only compatibility
2730 + aliases. They apply uniformly to all tiers in standalone mode while YAML
2731 + remains per-tier-only.
2732 +- R8.31: Reworded the visualization overview to list five panel types:
2733 + Sankey, Table, Time-Series, maps, and the 3D globe.
2734 +- R8.32: Moved the blocking raw rebuild journal scan into
2735 + `tokio::task::spawn_blocking()` and streamed parsed `FlowFields` back to the
2736 + async ingest task over a bounded channel before observing materialized tiers.
2737 +- R8.33: Changed the rebuild scan timeout guard to check elapsed time on every
2738 + entry, before and after payload parsing.
2739 +
2740 +### Code-only review handling
2741 +
2742 +The requested code-only subagent review found timestamp consistency risks. On
2743 +verification, the important false premise was that `timestamp_source` should
2744 +drive dashboard query windows or rollup tier selection. The public contract in
2745 +`docs/network-flows/configuration.md` says the Network Flows view uses journal
2746 +entry receive time for query windows and tier selection; `timestamp_source`
2747 +controls stored source timestamp metadata.
2748 +
2749 +Repairs:
2750 +
2751 +- Kept live materialized tier observation on receive time, matching the public
2752 + contract and raw journal append-time ordering.
2753 +- Changed rebuild to scan recently received raw entries by journal entry time
2754 + and replay them into materialized tiers by receive time, instead of querying
2755 + the raw journal by `_SOURCE_REALTIME_TIMESTAMP`.
2756 +- Changed the rebuild upper bound to include the current second.
2757 +- Extended the timestamp-source e2e test to prove:
2758 + raw `_SOURCE_REALTIME_TIMESTAMP` equals the decoded flow start timestamp;
2759 + raw journal entry realtime remains receive/write time;
2760 + live materialized tiers use the receive-time bucket; and rebuild replays raw
2761 + entries into the same receive-time bucket.
2762 +
2763 +### Validation evidence
2764 +
2765 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml timestamp_source -- --nocapture`
2766 + passed: 5 tests passed, 0 failed. The only warning was the pre-existing
2767 + unused `bytesize::ByteSize` import in
2768 + `src/crates/netflow-plugin/src/startup_memory_tests.rs`.
2769 +- `cargo fmt --manifest-path src/crates/netflow-plugin/Cargo.toml --check`
2770 + passed after formatting.
2771 +- `yamllint src/crates/netflow-plugin/metadata.yaml src/crates/netflow-plugin/configs/netflow.yaml`
2772 + passed.
2773 +- `python3 integrations/gen_integrations.py` passed.
2774 +- `python3 integrations/gen_docs_integrations.py` passed.
2775 +- `git diff --check` passed.
2776 +- A hosted-icon check over all Network Flows metadata icons passed:
2777 + `network-wired.svg` returned `200 image/svg+xml`.
2778 +- `python3 integrations/gen_doc_collector_page.py` passed and the generated
2779 + `src/collectors/COLLECTORS.md` contains a `### Network Flows` section with
2780 + NetFlow, IPFIX, sFlow, and enrichment integrations.
2781 +- The generated `src/collectors/COLLECTORS.md` Network Flows table now uses
2782 + catalog-style descriptions such as `Enrich network flows with...`,
2783 + `Annotate network flows with...`, and `Collect network flow records...`
2784 + rather than setup, option, or provider-publication wording.
2785 +- Final pre-commit reviewer verification re-fetched PR #22449 and confirmed
2786 + 12 open threads on the old pushed head; every thread was checked against the
2787 + current local tree and the corresponding fix is present in source/generated
2788 + files before committing.
2789 +- `git diff --check` passed.
2790 +- `cargo fmt --manifest-path src/crates/netflow-plugin/Cargo.toml --check`
2791 + passed.
2792 +- `python3 -c 'import yaml; yaml.safe_load(open("src/crates/netflow-plugin/metadata.yaml"))'`
2793 + passed.
2794 +- `.agents/sow/audit.sh` exited 2 because of pre-existing unrelated repository
2795 + hygiene findings: a sensitive-pattern warning in
2796 + `.agents/skills/mirror-netdata-repos/SKILL.md`, the unrelated current
2797 + SOW-0012 gate warning, non-project skill classification warnings, and
2798 + existing root TODO files. SOW 14 itself reports status/directory consistency
2799 + as `completed` in `.agents/sow/done/`.
2800 +- Local Learn ingest from an isolated source copy passed with
2801 + `--local-repo netdata:<preview-source> --ignore-on-prem-repo --use_plain_https --fail-links-netdata`.
2802 +- Local Learn Docusaurus build passed with Node `22.14.0`, Yarn `1.22.22`,
2803 + and `NODE_OPTIONS=--max_old_space_size=4096`.
2804 +- Build warnings were not PR-specific Network Flows failures: Docusaurus still
2805 + reports existing site-wide broken anchors and an existing duplicate
2806 + `/docs/collecting-metrics/service-discovery` route.
2807 +- Browser inspection returned HTTP 200, expected H1, no 404 page, and no
2808 + MDX/runtime error for:
2809 + `/docs/network-flows/`,
2810 + `/docs/network-flows/retention-and-tiers`,
2811 + `/docs/network-flows/enrichment-methods/static-metadata`,
2812 + `/docs/network-flows/flow-protocols/netflow`, and
2813 + `/docs/network-flows/configuration`.
2814 +- The Static Metadata page rendered placeholders such as
2815 + `enrichment.metadata_static.exporters.<key>.if_indexes` as readable text, not
2816 + literal `&lt;key&gt;` and not MDX JSX.
2817 +- Browser inspection of
2818 + `/docs/network-flows/enrichment-methods/decapsulation` confirmed the
2819 + integration icon loaded successfully from `network-wired.svg` with non-zero
2820 + rendered dimensions. Only external analytics requests failed in the browser
2821 + session.
2822 +- Second review-iteration validation after the 9 new automated comments:
2823 + `python3 integrations/gen_integrations.py`,
2824 + `python3 integrations/gen_docs_integrations.py`, and
2825 + `python3 integrations/gen_doc_collector_page.py` passed.
2826 +- `yamllint src/crates/netflow-plugin/metadata.yaml src/crates/netflow-plugin/configs/netflow.yaml`
2827 + passed.
2828 +- `python3 -c 'import yaml; yaml.safe_load(open("src/crates/netflow-plugin/metadata.yaml"))'`
2829 + passed.
2830 +- `bash -n .agents/skills/codacy-audit/scripts/pr-issues.sh` passed.
2831 +- `git diff --check` passed.
2832 +- `cargo fmt --manifest-path src/crates/netflow-plugin/Cargo.toml --check`
2833 + passed.
2834 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml journal_cli_retention_aliases_apply_to_all_tiers -- --nocapture`
2835 + passed: 1 test passed, 0 failed. The only warning was the pre-existing unused
2836 + `bytesize::ByteSize` import in `startup_memory_tests.rs`.
2837 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml plugin_config::tests:: -- --nocapture`
2838 + passed: 27 tests passed, 0 failed. The only warning was the same pre-existing
2839 + unused `bytesize::ByteSize` import in `startup_memory_tests.rs`.
2840 +- The post-fix wording sweep found no remaining instances of the exact stale
2841 + phrases called out by the second review pass.
2842 +- Third review-iteration fetch after commit `6f5805979d` returned 2 new open
2843 + automated threads, both in `src/crates/netflow-plugin/src/ingest/rebuild.rs`.
2844 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml e2e_timestamp_source_first_switched_is_persisted_as_source_timestamp -- --nocapture`
2845 + passed: 1 test passed, 0 failed. This test exercises the raw rebuild path
2846 + after deleting materialized tier directories. The only warning was the same
2847 + pre-existing unused `bytesize::ByteSize` import in `startup_memory_tests.rs`.
2848 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml plugin_config::tests:: -- --nocapture`
2849 + passed after the rebuild repair: 27 tests passed, 0 failed. The only warning
2850 + was the same pre-existing unused `bytesize::ByteSize` import.
2851 +- `cargo fmt --manifest-path src/crates/netflow-plugin/Cargo.toml --check`
2852 + passed after the rebuild repair.
2853 +- `cargo test --manifest-path src/crates/netflow-plugin/Cargo.toml -- --nocapture`
2854 + passed after the rebuild repair: 428 tests passed, 18 ignored, and the
2855 + vendored gRPC proto test passed. The visible lock-poison panic is expected
2856 + from a recovery test that still passed. The only compiler warning was the
2857 + same pre-existing unused `bytesize::ByteSize` import.
2858 +
2859 +### Artifact maintenance gate
2860 +
2861 +- **AGENTS.md**: updated to list the new `learn-pr-preview` skill and its
2862 + explicit trigger.
2863 +- **Runtime project skills**: added `learn-pr-preview`; updated
2864 + `learn-site-structure` with a local PR preview how-to; updated
2865 + `codacy-audit` for the large PR issue-list fetch gotcha; updated
2866 + `integrations-lifecycle` for the generated `COLLECTORS.md` / Monitor
2867 + Anything Network Flows section mechanism and metadata description-authoring
2868 + rules.
2869 +- **Specs**: no spec update needed; the code repair preserves the documented
2870 + timestamp contract rather than changing product behavior.
2871 +- **End-user/operator docs**: updated Network Flows docs, `metadata.yaml`, and
2872 + regenerated integration cards.
2873 +- **End-user/operator skills**: no update needed; the new workflow is a
2874 + repo-work skill for agents validating documentation PRs, not an end-user
2875 + operator skill.
2876 +- **SOW lifecycle**: SOW 14 repair is complete; status is `completed`, and
2877 + the file is moved back to `.agents/sow/done/` in the same commit as the
2878 + repair.
AGENTS.md
+3
@@ -295,6 +295,9 @@ Runtime input skills:
295 - `.agents/skills/learn-site-structure/`
296 Trigger: adding/moving/renaming/deleting any docs page that should appear on `learn.netdata.cloud`; editing `<repo>/docs/.map/map.yaml`; investigating why a Learn page looks the way it does; reading the live `ingest/ingest.py` orchestrator or the legacy `ingest.js` / `ingest.md` (which are stale); MDX escape rules; redirects; the Netlify deploy contract.
297 Status: live. SKILL.md plus per-domain guides (`mapping.md`, `pipeline.md`, `sidebars.md`, `mdx-rules.md`, `redirects.md`, `pitfalls-and-gotchas.md`, `authoring-boundary.md`) and `recipes/`, `how-tos/` directories.
298 +- `.agents/skills/learn-pr-preview/`
299 + Trigger: only when the user explicitly asks to build, run, preview, inspect, or validate `learn.netdata.cloud` locally using the contents of a PR or documentation branch before merge.
300 + Status: live. SKILL.md with an isolated preview workflow that copies PR source content, runs Learn ingest with `--local-repo`, builds Docusaurus with the Netlify-pinned runtime, and inspects representative pages without dirtying the real Learn checkout.
301 - `.agents/skills/query-agent-events/`
302 Trigger: investigating crashes, panics, or fatals across the Netdata fleet; downloading events from the agent-events ingestion namespace; analyzing AE_* fields and their enums; understanding the 23h client-side dedup or the after-the-fact event timing; using the systemd-journal Function multi-value `selections` filter for index-friendly queries.
303 Status: live. SKILL.md plus per-domain guides (`AE_FIELDS.md`, `transports.md`, `update-cadence.md`, `query-discipline.md`, `finding-crashes.md`, `finding-fatals.md`), scripts (`scripts/_lib.sh`, `get-events.sh`, `analyze-events.sh`, `redact-events.sh`) and `recipes/`, `how-tos/` directories. Bug-investigation tool, NOT a generic logs query skill -- consumes `query-netdata-{cloud,agents}` for transport.
docs/.map/map.yaml
+13 -32
@@ -479,11 +479,9 @@ sidebar:
479 # Network Flows
480 - meta:
481 label: Network Flows
482 + edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/README.md
483 + description: Collect, enrich, and visualize NetFlow, IPFIX, and sFlow data with the Netdata Agent.
484 items:
483 - - meta:
484 - label: Overview
485 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/README.md
486 - description: Collect, enrich, and visualize NetFlow, IPFIX, and sFlow data with the Netdata Agent.
485 - meta:
486 label: Installation
487 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/installation.md
@@ -498,38 +496,21 @@ sidebar:
496 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/configuration.md
497 description: Full reference for netflow.yaml configuration options.
498 - meta:
501 - label: Enrichment Concepts
502 - items:
503 - - meta:
504 - label: IP Intelligence
505 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/ip-intelligence.md
506 - description: How GeoIP and ASN data combine to enrich flow records with country, city, and AS-name labels.
507 - - meta:
508 - label: BGP Routing
509 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/bgp-routing.md
510 - description: How live BGP routes (BMP, BioRIS) feed AS path, communities, and next-hop into flow records.
511 - - meta:
512 - label: Network Identity
513 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/network-identity.md
514 - description: How external feeds (cloud IP ranges, IPAM systems) label your network prefixes.
515 - - meta:
516 - label: Static Metadata
517 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/static-metadata.md
518 - - meta:
519 - label: Classifiers
520 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/classifiers.md
521 - - meta:
522 - label: ASN Resolution
523 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/asn-resolution.md
524 - - meta:
525 - label: Decapsulation
526 - edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/decapsulation.md
499 + label: Enrichment
500 + edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment.md
501 + description: Order of evaluation, provider chains, MMDB shared mechanism, and the cross-cutting properties that span every enrichment method.
502 + - meta:
503 + label: Enrichment Intel Downloader
504 + edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/intel-downloader.md
505 + description: The bundled tool that fetches IP intelligence data sources and writes MMDB files the netflow plugin reads.
506 - meta:
507 label: Field Reference
508 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/field-reference.md
509 description: Complete list of flow fields with per-protocol availability.
510 - meta:
511 label: Visualization
512 + edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/visualization/overview.md
513 + description: How queries work, the controls every panel shares, and where each panel fits.
514 items:
515 - meta:
516 label: Sankey and Table
@@ -547,12 +528,12 @@ sidebar:
528 label: Plugin Health Charts
529 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/visualization/dashboard-cards.md
530 - meta:
550 - label: Retention and Querying
531 + label: Retention and Tiers
532 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/retention-querying.md
533 - meta:
534 label: Sizing and Capacity Planning
535 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/sizing-capacity.md
555 - description: Storage estimation, memory guidance, and performance benchmarks.
536 + description: Practical scaling guide -- plugin cap, storage, memory, and distributed deployment.
537 - meta:
538 label: Validation and Data Quality
539 edit_url: https://github.com/netdata/netdata/edit/master/docs/network-flows/validation.md
docs/network-flows/README.md
+18 -61
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows"
6 keywords: ['netflow', 'sflow', 'ipfix', 'network flows', 'traffic analysis', 'overview']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Network Flows
12
13 Netdata can collect, store, and visualise network flow data from your routers and switches. You see who is talking to whom on your network, how much data they exchanged, over what protocols, and to which countries — without inspecting packet contents and without an external database.
@@ -45,19 +47,19 @@ If those are your questions, flow data is the wrong tool. You probably need appl
47
48 These two facts are not Netdata-specific. They're how flow data works on every collector. Understanding them up-front saves a lot of head-scratching when you first see the dashboard.
49
48 -### Traffic appears doubled by default
50 +### Traffic can appear doubled
51
50 -A router exports flow records for both ingress and egress on every monitored interface. A single packet entering interface A and leaving interface B produces two records: one tagged ingress on A, one tagged egress on B.
52 +When a router exports flow records for both ingress and egress on every monitored interface — a common configuration — a single packet entering interface A and leaving interface B produces two records: one tagged ingress on A, one tagged egress on B. With a second router on the same path doing the same thing, **4×**. Vendor best practice is to enable ingress-only exports to avoid this entirely; if you can't change exporter configuration, the dashboard view still has to compensate.
53
52 -If you sum every flow record without filtering, you see roughly **2× the actual traffic**. With a second router on the same path, **4×**.
54 +To see real numbers, filter by one exporter and one interface. Each packet then appears in exactly one record on that interface. See the [Anti-patterns page](/docs/network-flows/anti-patterns.md) for the full framing.
55
54 -To see real numbers: filter by one exporter, one interface, in one direction. The dashboard makes this easy. See the [Anti-patterns page](/docs/network-flows/anti-patterns.md) for the full framing.
56 +### Bidirectional traffic shows both directions
57
56 -### Conversations are mirrored
58 +A conversation between host A and host B has packets going both ways: A→B for requests or uploads, B→A for responses or downloads. These are real, separate packets, exported as separate flow records. The Sankey diagram, country map, and sorted top-N tables show both directions when you don't filter by direction.
59
58 -A bidirectional conversation (host A talks to host B, B replies to A) produces at least two flow records — one for each direction. They're real, distinct flows. But on a Sankey diagram, country map, or sorted top-N table without direction filtering, you see both ends of every conversation. That's correct, but it can look like the same traffic appears twice.
60 +Volumes in the two directions are usually asymmetric — for example, a video download produces large B→A flows and small A→B ACKs. So a "host A to host B" entry and a "host B to host A" entry refer to the same conversation but typically have very different byte counts.
61
60 -When you see "traffic from your country to a foreign country" *and* "traffic from that foreign country to your country" of similar volume, you're looking at one conversation, not two.
62 +This is not duplication; it's correct per-direction accounting.
63
64 ## What ships with the plugin
65
@@ -77,19 +79,21 @@ Each flow record is enriched at ingestion with:
79 - **Exporter name and labels** — from your static-metadata configuration
80 - **Interface name, description, speed, provider, connectivity, boundary** — from your static-metadata configuration
81 - **Network labels** for your own CIDRs (name, role, site, region, tenant)
80 -- **Classifier-derived attributes** for rule-based tagging (Akvorado-compatible expression language)
82 +- **Classifier-derived attributes** for rule-based tagging (Akvorado-compatible subset of the expression language)
83 - **Live BGP attributes** (AS path, communities, next-hop) — from BMP, BioRIS, or static prefix configuration
84 - **Decapsulated inner-packet fields** for SRv6 / VXLAN traffic
85
84 -Flow records land in a four-tier journal: raw + 1-minute + 5-minute + 1-hour rollups, with independent retention per tier. The dashboard auto-picks the best tier for each query.
86 +Flow records land in a four-tier journal: raw + 1-minute + 5-minute + 1-hour rollups, with independent retention per tier. Rollup tiers drop a few high-cardinality fields (IPs, ports, city/coordinates) to stay compact, so any query that filters or groups by those fields is served from the raw tier; everything else can use a coarser tier. The dashboard auto-picks the best tier for each query.
87
88 ## What sampling does to your numbers
89
88 -Many routers sample. They export one packet in N — typically 1-in-100 to 1-in-2000. Netdata multiplies bytes and packets by the sampling rate at ingestion, so the numbers you see are estimates of actual traffic.
90 +Many routers sample. They export one packet in N — typically 1-in-100 to 1-in-2000. Each flow record carries its own sampling rate (in the record itself, in the protocol header, in a Sampling Options Template, or — for sFlow — in the flow sample). At ingestion, Netdata multiplies that record's bytes and packets by **its own rate**, so dashboard numbers are estimates of actual traffic.
91 +
92 +The multiplication is per-flow, so different exporters and different interfaces can sample at different rates and aggregates remain accurate. The dashboard does not surface the sampling rate as a UI element — with mixed rates a single displayed value would be meaningless, and with uniform rates the operator already knows it.
93
90 -This works correctly **only if all your exporters use the same sampling rate**. With mixed rates, the multiplication is per-flow and the aggregate becomes a blend of estimates that's hard to interpret. The clean path: keep sampling rates uniform across your network, or run unsampled where the flow rate allows.
94 +Sampling has a real statistical limit, separate from the multiplication. At 1-in-1000, a single-packet flow has roughly a 99.9% chance of not being sampled at all. If you need to catch small, rare events (security beaconing, scanning), pick a lower sampling rate on the exporters that watch that traffic, or run unsampled there.
95
92 -Sampling at 1-in-1000 also misses small flows. A single-packet flow has a 99.9% chance of not being seen at all. If you need to detect small, rare events (security beaconing, scanning), use unsampled or 1-in-100 on critical exporters.
96 +For the exact pre-multiplication counts the exporter literally reported, see `RAW_BYTES` and `RAW_PACKETS` in the [field reference](/docs/network-flows/field-reference.md).
97
98 ## What the dashboard looks like
99
@@ -98,11 +102,11 @@ Six visualisations, all driven by the same query engine:
102 - **Sankey + Table** — the default. Top-N flows aggregated by 1-10 fields you pick. Best for "who's responsible".
103 - **Time-Series** — the same top-N over time. Best for "how does this change".
104 - **Country map / state map / city map** — geographic views. Best for "where".
101 -- **Globe** — a 3D rendering of the city-level data. Visual demo, less useful for analysis.
105 +- **Globe** — a 3D rendering of the city-level data. Same data and table as the city map; useful when distance and great-circle paths matter (e.g. transcontinental traffic).
106
107 A filter ribbon between the visualisation and the table lets you narrow data by any combination of fields. Selections persist in the URL — copy and share to give a colleague exactly your view.
108
105 -Default settings on first open: last 15 minutes, top-25 flows by bytes, grouped as `Source ASN → Protocol → Destination ASN`.
109 +Default settings on first open: last 15 minutes, top-25 flows by bytes, grouped as `Source AS Name → Protocol → Destination AS Name`.
110
111 Default fields are tuned to surface meaningful traffic at a glance. From there, you adjust the time range, change the aggregation, add filters, and dig in.
112
@@ -114,50 +118,3 @@ Pick the page that matches your situation:
118 - **You have data, you want to find a bandwidth hog or trace an IP** — [Investigation Playbooks](/docs/network-flows/investigation-playbooks.md).
119 - **You want to make sure your data is trustworthy** — [Validation and Data Quality](/docs/network-flows/validation.md).
120 - **You want to avoid the most common mistakes** — [Anti-patterns](/docs/network-flows/anti-patterns.md).
117 -- **You want to understand a specific feature in depth** — see the section index below.
118 -
119 -## Section index
120 -
121 -**Setup and configuration**
122 -
123 -- [Installation](/docs/network-flows/installation.md) — Package names, install commands, file locations
124 -- [Quick Start](/docs/network-flows/quick-start.md) — Configure your first router, see traffic in 15 minutes
125 -- [Configuration](/docs/network-flows/configuration.md) — `netflow.yaml` reference
126 -
127 -**Sources**
128 -
129 -- [NetFlow](/src/crates/netflow-plugin/integrations/netflow.md) — v5, v7, v9
130 -- [IPFIX](/src/crates/netflow-plugin/integrations/ipfix.md) — IETF-standardised, biflow-capable
131 -- [sFlow](/src/crates/netflow-plugin/integrations/sflow.md) — packet-sampling, fundamentally different
132 -
133 -**Enrichment**
134 -
135 -- [GeoIP](/docs/network-flows/enrichment/ip-intelligence.md) — Country, city, AS-name lookups
136 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Naming exporters, interfaces, your networks
137 -- [Classifiers](/docs/network-flows/enrichment/classifiers.md) — Rule-based tagging
138 -- [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md) — Where AS numbers and names come from
139 -- [BMP routing](/docs/network-flows/enrichment/bgp-routing.md) — Live BGP feed for routing attributes
140 -- [BioRIS](/docs/network-flows/enrichment/bgp-routing.md) — RIPE RIS via gRPC
141 -- [Network sources](/docs/network-flows/enrichment/network-identity.md) — HTTP-fetched prefix metadata
142 -- [Decapsulation](/docs/network-flows/enrichment/decapsulation.md) — SRv6 and VXLAN inner-packet extraction
143 -
144 -**Reference**
145 -
146 -- [Field reference](/docs/network-flows/field-reference.md) — All 91 fields and which protocols populate each
147 -- [Retention and querying](/docs/network-flows/retention-querying.md) — The four-tier model and how queries pick a tier
148 -- [Sizing and capacity planning](/docs/network-flows/sizing-capacity.md) — Hardware, throughput, storage estimates
149 -
150 -**Visualisation**
151 -
152 -- [Sankey and Table](/docs/network-flows/visualization/summary-sankey.md) — The default view
153 -- [Time-Series](/docs/network-flows/visualization/time-series.md) — Top-N over time
154 -- [Maps and Globe](/docs/network-flows/visualization/maps-globe.md) — Geographic views
155 -- [Filters and Facets](/docs/network-flows/visualization/filters-facets.md) — Narrowing the data
156 -- [Plugin Health Charts](/docs/network-flows/visualization/dashboard-cards.md) — Operational metrics for the plugin itself
157 -
158 -**Operations**
159 -
160 -- [Validation and Data Quality](/docs/network-flows/validation.md) — Cross-checks and silent failures
161 -- [Investigation Playbooks](/docs/network-flows/investigation-playbooks.md) — Recipes for common questions
162 -- [Anti-patterns](/docs/network-flows/anti-patterns.md) — Common mistakes and how to avoid them
163 -- [Troubleshooting](/docs/network-flows/troubleshooting.md) — When something doesn't work
docs/network-flows/anti-patterns.md
+23 -57
@@ -6,58 +6,23 @@ learn_rel_path: "Network Flows"
6 keywords: ['anti-patterns', 'mistakes', 'pitfalls', 'gotchas', 'misuse']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Anti-patterns and pitfalls
12
13 Flow data is powerful but easy to misuse. The mistakes below are the ones that cause the most lost analyst time and the most wrong conclusions in real deployments. Each entry explains how the mistake happens, what it costs, and how to avoid it.
14
15 ## 1. Reading aggregate volume without filtering
16
15 -**The mistake.** You open the Network Flows tab, see a total bandwidth number, and assume it represents your real traffic.
17 +**The mistake.** You open Network Flows, see a total bandwidth number, and assume it represents your real traffic.
18
17 -**Why it's wrong.** Routers normally export both ingress and egress flow records on every monitored interface. A single packet entering interface A and leaving interface B produces two records — one tagged ingress on A, one tagged egress on B. With one router and the standard configuration, summing all flow records gives you roughly **2× the actual traffic**. Add a second router on the same path and you see 4×.
19 +**Why it's wrong.** When a router is configured to export both ingress and egress flow records on every monitored interface — a common configuration, though vendor best practice is ingress-only — a single packet entering interface A and leaving interface B produces two records: one tagged ingress on A, one tagged egress on B. Summing every flow record without filtering then gives you roughly **2× the actual traffic** on a single such router. Add a second router on the same path with the same configuration and you see 4×.
20
21 **What it costs.** You think your link carries 2 Gbps when it really carries 1 Gbps. Capacity decisions based on these numbers are wrong by a factor of 2 or more.
22
21 -**How to avoid it.** Always filter by one exporter and one direction (Input Interface OR Output Interface, not both) when reading absolute volume numbers. To validate: compare to SNMP interface counters on the same interface — values should be close.
22 -
23 -## 2. Ignoring the sampling rate
24 -
25 -**The mistake.** Your router is configured to sample 1-in-1000 packets. Nobody documented this. The dashboard shows 5 Mbps. You assume that's your traffic.
26 -
27 -**Why it's wrong.** With sampling, a flow record represents one observed packet out of every N. Netdata multiplies bytes and packets by the sampling rate at ingestion, so the dashboard numbers are estimates of actual traffic — *if* the multiplication is consistent. When sampling rates differ across exporters in the same query, the aggregate becomes a blend of estimates that is hard to interpret correctly.
28 -
29 -**What it costs.** Volume analysis is off by orders of magnitude when the rate isn't documented. Small flows are statistically invisible — at 1-in-1000, a single-packet flow has a 99.9% chance of being missed entirely. Security investigations miss low-volume threats like beaconing and probing.
30 -
31 -**How to avoid it.**
32 -
33 -- Use a uniform sampling rate across your network, or run unsampled where flow rates allow.
34 -- For Internet-edge security work, use 1-in-100 or unsampled. Sampling at 1-in-1000 hides small flows.
35 -- Document sampling rates per exporter and audit them quarterly.
36 -- Cross-check flow-derived bandwidth against SNMP. If they diverge by more than 30%, investigate before trusting the data.
37 -
38 -## 3. Trusting GeoIP for internal IPs
39 -
40 -**The mistake.** You enable GeoIP enrichment. Internal IPs (10.x, 172.16-31.x, 192.168.x) appear in random countries on the geographic map.
41 -
42 -**Why it's wrong.** GeoIP databases don't have entries for private IP ranges. Netdata doesn't skip private IPs — it just hands the IP to the database and uses what comes back. With the stock DB-IP database, private ranges are tagged so they render as "AS0 Private IP Address Space" with empty country. With third-party databases, results vary. Some return spurious country data for RFC 1918 addresses.
23 +**How to avoid it.** Always filter by one exporter and one interface (`Ingress Interface Name` OR `Egress Interface Name`, pick one) when reading absolute volume numbers. Each packet then appears in exactly one record on that interface. To validate: compare to SNMP interface counters on the same interface — values should be close.
24
44 -**What it costs.** Geographic anomalies look like security incidents. Analysts waste time investigating "traffic from China" that's actually traffic to a server in the 10.x.x.x range.
45 -
46 -**How to avoid it.** Configure your internal IP ranges as static metadata before relying on geographic analysis. Use the [`networks`](/docs/network-flows/enrichment/static-metadata.md) block to declare each internal CIDR with a name, role, and (optionally) overridden country. The labels you set there override whatever GeoIP returns. Validate by spot-checking known IPs against the map.
47 -
48 -## 4. Alerting on absolute volume thresholds
49 -
50 -**The mistake.** You configure an alert: "page me if any IP sends more than 10 GB in an hour."
51 -
52 -**Why it's wrong.** That threshold is a guess. Your backup server legitimately sends 500 GB/hour. An attacker exfiltrating 200 MB/hour is invisible.
53 -
54 -**What it costs.** The alert is either constant noise (false positives) or completely silent (false negatives). Either way, alerts get ignored.
55 -
56 -**How to avoid it.** Establish baselines first. Compare current traffic to the same time period in previous weeks (Tuesday 10 AM vs the average of the last four Tuesdays at 10 AM). Alert on deviation from the baseline, not on absolute values.
57 -
58 -(Netdata's alerting on flow data is in development; for now this pattern lives in your monitoring practice, not in the plugin.)
59 -
60 -## 5. Collecting flows but never looking at them
25 +## 2. Collecting flows but never looking at them
26
27 **The mistake.** Flow export is enabled on every router. Storage fills up. Nobody opens the dashboard between incidents.
28
@@ -67,7 +32,7 @@ Flow data is powerful but easy to misuse. The mistakes below are the ones that c
32
33 **How to avoid it.** Schedule a weekly 15-minute review. Document what "normal" looks like — top 10 talkers, traffic curve shape, protocol distribution, geographic distribution. Add anything new that appears in the top-10 to a watchlist for investigation. Use [Investigation Playbooks](/docs/network-flows/investigation-playbooks.md) for the recurring questions.
34
70 -## 6. Confusing flows with sessions
35 +## 3. Confusing flows with sessions
36
37 **The mistake.** You see 50 000 flow records in an hour and report it as "we had 50 000 user sessions".
38
@@ -77,7 +42,7 @@ Flow data is powerful but easy to misuse. The mistakes below are the ones that c
42
43 **How to avoid it.** Aggregate by source IP and time window for a session-like view. Use ports and protocols to classify, not to count transactions. If you need real session data, use application logs or APM, not flow records.
44
80 -## 7. NAT blindness
45 +## 4. NAT blindness
46
47 **The mistake.** You place the collector outside a NAT gateway because mirroring traffic there is easier.
48
@@ -87,7 +52,7 @@ Flow data is powerful but easy to misuse. The mistakes below are the ones that c
52
53 **How to avoid it.** Collect inside each NAT boundary, or correlate flow data with NAT translation logs (`iptables NFLOG`, vendor NAT logging) to map external 5-tuples back to internal hosts.
54
90 -## 8. Geographic firewall of shame
55 +## 5. Geographic firewall of shame
56
57 **The mistake.** You configure an alert: "page security if traffic goes to any country except the home country."
58
@@ -97,7 +62,7 @@ Flow data is powerful but easy to misuse. The mistakes below are the ones that c
62
63 **How to avoid it.** Whitelist known cloud and CDN ASNs. Use ASN as the primary signal and country as secondary corroboration. If you must alert on country, alert only on countries you have no business relationship with — and review the whitelist quarterly.
64
100 -## 9. Treating flow duration as latency
65 +## 6. Treating flow duration as latency
66
67 **The mistake.** You divide flow bytes by flow duration and present that as "speed", or use duration as a proxy for round-trip time.
68
@@ -107,7 +72,7 @@ Flow data is powerful but easy to misuse. The mistakes below are the ones that c
72
73 **How to avoid it.** Use SNMP for interface utilisation, ICMP probes for round-trip time, APM tools for application performance. Flow data answers "how much" and "between whom", never "how fast".
74
110 -## 10. Trying to detect microbursts
75 +## 7. Trying to detect microbursts
76
77 **The mistake.** Users complain about momentary slowness. You look in flow data for the burst.
78
@@ -117,41 +82,42 @@ Flow data is powerful but easy to misuse. The mistakes below are the ones that c
82
83 **How to avoid it.** For microburst detection use packet capture, switch microburst counters, or hardware-assisted telemetry. Flow data is for sustained patterns, not millisecond events.
84
120 -## 11. Reasoning from raw byte counts when sampling is on
85 +## 8. Reasoning from raw byte counts when sampling is on
86
87 **The mistake.** You see `RAW_BYTES = 5000` for a flow and assume 5000 bytes was the actual traffic.
88
89 **Why it's wrong.** `RAW_BYTES` is the unscaled byte count from the exporter. With sampling at 1-in-1000, the actual traffic was approximately 5 000 000 bytes. The scaled value is in `BYTES`.
90
126 -**How to avoid it.** Use `BYTES` (auto-scaled) for normal analysis. Use `RAW_BYTES` only when sampling is uniform across all exporters and you specifically need exact pre-scaling counts.
91 +**What it costs.** A bandwidth estimate that is wrong by the sampling factor — typically 100× to 2000× too low — and capacity or anomaly conclusions drawn from those wrong numbers.
92
128 -## 12. Comparing flow counts across protocols
93 +**How to avoid it.** Use `BYTES` (auto-scaled per-flow) for normal analysis. Use `RAW_BYTES` only when you specifically need the exporter's literal pre-scaling counts — for example, when comparing the per-flow record to the exporter's own logs.
94 +
95 +## 9. Comparing flow counts across protocols
96
97 **The mistake.** You report "Arista switches see far more flows than Cisco routers" based on flow counts.
98
132 -**Why it's wrong.** NetFlow aggregates millions of packets into one flow record. sFlow exports individual packet samples — each becomes its own "flow" record. Their counts are not comparable. Same goes for sampling-rate differences across exporters.
99 +**Why it's wrong.** NetFlow aggregates many packets into one flow record. sFlow exports individual packet samples — each becomes its own "flow" record. Flow record counts mean different things in the two protocols and are not directly comparable.
100 +
101 +**What it costs.** False conclusions about which devices "produce more traffic", capacity decisions made on artefact numbers rather than real traffic volume.
102
134 -**How to avoid it.** Aggregate by IP/port/time window before comparing. Compare bytes (after scaling), not flow counts. Document which protocol each exporter speaks.
103 +**How to avoid it.** Compare bytes (`BYTES`, auto-scaled per-flow at ingest), not flow record counts. Document which protocol each exporter speaks.
104
105 ## Summary
106
107 | Mistake | One-line fix |
108 |---|---|
140 -| Doubled aggregate | Filter by exporter + interface + direction |
141 -| Ignored sampling | Document and uniform-rate; cross-check SNMP |
142 -| GeoIP for internal IPs | Configure internal CIDRs in `enrichment.networks` |
143 -| Absolute thresholds | Baseline first, alert on deviation |
109 +| Doubled aggregate (when ingress + egress are both exported) | Filter by one exporter and one interface (`Ingress Interface Name` or `Egress Interface Name`, pick one) |
110 | Collect-and-ignore | Weekly 15-minute review with documented baselines |
111 | Flows ≠ sessions | Aggregate by IP and time window |
112 | NAT blindness | Collect inside the NAT boundary |
113 | Geographic firewall of shame | Use ASN, whitelist cloud and CDN providers |
114 | Duration as latency | Use SNMP/ICMP/APM for latency |
115 | Microburst hunting | Use packet capture or hardware telemetry |
150 -| Raw bytes when sampling | Use `BYTES`, not `RAW_BYTES`, unless rates are uniform |
116 +| Raw bytes confusion | Use `BYTES` (auto-scaled per-flow); use `RAW_BYTES` only when you need the exporter's literal pre-scaling counts |
117 | Cross-protocol flow counts | Use bytes (scaled), not flow counts |
118
119 ## What's next
120
121 - [Validation and Data Quality](/docs/network-flows/validation.md) — How to confirm your data is trustworthy.
122 - [Investigation Playbooks](/docs/network-flows/investigation-playbooks.md) — Step-by-step recipes for common questions.
157 -- [Sources](/src/crates/netflow-plugin/integrations/netflow.md) — Per-protocol behaviour that drives many of these gotchas.
123 +- [Flow Protocols](/src/crates/netflow-plugin/integrations/netflow.md) — Per-protocol behaviour that drives many of these gotchas.
docs/network-flows/configuration.md
+60 -53
@@ -6,9 +6,11 @@ learn_rel_path: "Network Flows"
6 keywords: ['configuration', 'netflow.yaml', 'tuning', 'retention', 'listener']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Configuration
12
11 -The netflow plugin reads its configuration from `netflow.yaml`. Defaults are sane out of the box; most operators only adjust three things — the listener address, the journal retention, and (rarely) the per-tier overrides. This page documents every option, with its real default and the file that defines it.
13 +The netflow plugin reads its configuration from `netflow.yaml`. The defaults are good for initial validation; production deployments usually tune the listener address and retention once the observed flow rate is known. This page documents every option, with its real default and the file that defines it.
14
15 ## Where the file lives
16
@@ -39,7 +41,7 @@ journal: { ... } # tier directories, retention, query guardrails
41 enrichment: { ... } # GeoIP, classifiers, ASN, BMP, BioRIS, network sources
42 ```
43
42 -The `listener`, `protocols`, and `journal` sections are flattened — their keys can also appear at the top level (the stock file does this for compatibility). Both forms are accepted.
44 +The YAML form is strictly nested — every key lives inside its section as shown above. The flat form is only valid for CLI flags (e.g. `--netflow-listen 0.0.0.0:2055`), which the plugin exposes for one-off invocation; the YAML schema rejects unknown top-level keys.
45
46 ## `enabled`
47
@@ -101,15 +103,17 @@ protocols:
103 | `ipfix` | `--netflow-enable-ipfix` | `true` | Boolean. IPFIX. |
104 | `sflow` | `--netflow-enable-sflow` | `true` | Boolean. sFlow v5. |
105 | `decapsulation_mode` | `--netflow-decapsulation-mode` | `none` | `none`, `srv6`, `vxlan`. Strips outer headers from the data-link section, surfaces the inner 5-tuple. |
104 -| `timestamp_source` | `--netflow-timestamp-source` | `input` | Where the dashboard's flow timestamps come from. See below. |
106 +| `timestamp_source` | `--netflow-timestamp-source` | `input` | Which timestamp is stored as `_SOURCE_REALTIME_TIMESTAMP`. See below. |
107
108 You must keep at least one protocol enabled or the plugin refuses to start.
109
110 ### `timestamp_source` values
111
110 -- **`input`** (default) — the time the plugin received the datagram. Charts always look "now". This is the safest choice for dashboards.
112 +- **`input`** (default) — the time the plugin received the datagram.
113 - **`netflow_packet`** — the time the exporter put in the NetFlow/IPFIX header.
112 -- **`netflow_first_switched`** — the time the flow actually started, from the per-record first-switched field. Records arrive with timestamps in the past (up to your active timeout). This gives the most accurate timeline but charts may show data appearing "behind" real time.
114 +- **`netflow_first_switched`** — the time the flow actually started, from the per-record first-switched field when the exporter provides it.
115 +
116 +The Network Flows view still uses journal entry time, which is the time the Netdata Agent received the datagram, for query windows and tier selection. `timestamp_source` controls the stored source timestamp metadata; it does not make the dashboard time picker query by exporter timestamps.
117
118 ## `journal`
119
@@ -118,45 +122,38 @@ This is the section most operators tune. It controls where flow data lives, how
122 ```yaml
123 journal:
124 journal_dir: flows
121 - size_of_journal_files: 10GB
122 - duration_of_journal_files: 7d
123 - query_1m_max_window: 6h
124 - query_5m_max_window: 24h
125 query_max_groups: 50000
126 - query_facet_max_values_per_field: 5000
126 tiers:
128 - raw: { duration_of_journal_files: 24h }
129 - minute_1: { duration_of_journal_files: 14d }
130 - minute_5: { duration_of_journal_files: 30d }
131 - hour_1: { duration_of_journal_files: 365d }
127 + raw: { size_of_journal_files: 50GB, duration_of_journal_files: 24h }
128 + minute_1: { size_of_journal_files: 5GB, duration_of_journal_files: 14d }
129 + minute_5: { size_of_journal_files: 5GB, duration_of_journal_files: 30d }
130 + hour_1: { size_of_journal_files: 5GB, duration_of_journal_files: 365d }
131 ```
132
134 -### Top-level retention
133 +### Journal directory
134
135 | Key | Default | Notes |
136 |---|---|---|
137 | `journal_dir` | `flows` | Relative paths resolve under `NETDATA_CACHE_DIR` (typically `/var/cache/netdata/flows`). Absolute paths are used as-is. |
139 -| `size_of_journal_files` | `10GB` | Disk budget per tier (not total). Minimum `100MB`. Set to `null` to disable size-based retention. |
140 -| `duration_of_journal_files` | `7d` | Time budget per tier. Set to `null` to disable time-based retention. |
141 -
142 -**Important.** The top-level retention applies to **every tier independently** unless you override it per-tier. So with the defaults, all four tiers (raw, 1m, 5m, 1h) share the same 10GB / 7d budget. **This is rarely what you want.** The whole point of having rollup tiers is to keep them around longer than raw. See per-tier overrides below.
138
144 -Either limit triggers rotation. With size = 10GB and duration = 7d, the tier expires whichever is hit first.
139 +### Per-tier retention
140
146 -### Per-tier overrides
141 +Each tier has its own size and duration budget, configured under `tiers:` only — YAML has no global retention knobs. Raw and rollup tiers have very different storage and access patterns; they should be sized independently.
142
143 ```yaml
144 tiers:
150 - raw: # name in YAML
145 + raw:
146 size_of_journal_files: 50GB
147 duration_of_journal_files: 24h
148 minute_1:
149 + size_of_journal_files: 5GB
150 duration_of_journal_files: 14d
151 minute_5:
152 + size_of_journal_files: 5GB
153 duration_of_journal_files: 30d
154 hour_1:
155 + size_of_journal_files: 5GB
156 duration_of_journal_files: 365d
159 - size_of_journal_files: null # time-only retention for the long tail
157 ```
158
159 | YAML name | Aliases | On-disk directory |
@@ -168,28 +165,36 @@ tiers:
165
166 The on-disk directory names are short (`1m`, `5m`, `1h`); the YAML keys are explicit (`minute_1`, `minute_5`, `hour_1`). Mind the difference if you go look at the disk.
167
171 -For each per-tier knob (`size_of_journal_files`, `duration_of_journal_files`):
168 +Per-tier values:
169 +
170 +| Key | Default per tier | Notes |
171 +|---|---|---|
172 +| `size_of_journal_files` | `10GB` | Disk budget for this tier. Minimum `100MB`. Set to `null` to disable size-based retention on this tier. |
173 +| `duration_of_journal_files` | `7d` | Time budget for this tier. Set to `null` to disable time-based retention on this tier. |
174
173 -- **Omit the key** to inherit the top-level default.
174 -- Set to `null` to **disable** that limit on this tier.
175 -- Set to a value to override.
175 +Either limit triggers rotation. The tier expires whichever is hit first. At least one of the two must be set per tier (validation enforces this).
176
177 -A typical production profile is the example block above: 24 hours of raw, 2 weeks at 1-minute, 30 days at 5-minute, 1 year at 1-hour. This profile keeps detailed forensics within reach while supporting year-over-year capacity trends.
177 +If you omit a tier entry entirely, that tier uses the built-in defaults (`10GB` / `7d`). If you provide a tier entry but omit one of the two knobs, the omitted knob falls back to its built-in default. Setting either to `null` explicitly disables that limit on that tier.
178 +
179 +Standalone CLI runs still accept the legacy uniform retention flags:
180 +`--netflow-retention-size-of-journal-files` and
181 +`--netflow-retention-duration-of-journal-files`. They apply the same value to
182 +all tiers and exist only for standalone/CLI compatibility; production
183 +configuration should use the per-tier YAML shape above.
184 +
185 +The example block at the top of this section is a typical production profile: 24 hours of raw, 2 weeks at 1-minute, 30 days at 5-minute, 1 year at 1-hour. Detailed forensics for the last day; long-term trends for the year.
186
187 ### Rotation
188
181 -Each tier rotates files at `size_of_journal_files / 20`, clamped between 5 MB and 200 MB. Time-based rotation is fixed at one hour per file. You don't configure these directly.
189 +Each tier rotates files at `size_of_journal_files / 20`, clamped between 5 MB and 200 MB. Time-based rotation is fixed at one hour per file. You don't configure these directly. If `size_of_journal_files` is set to `null` on a tier (size-based retention disabled), the rotation size falls back to 100 MB so files still rotate cleanly.
190
191 ### Query guardrails
192
193 | Key | Default | What it limits |
194 |---|---|---|
187 -| `query_1m_max_window` | `6h` | Above this window, the dashboard skips the 1-minute tier and uses the 5-minute or 1-hour tier. |
188 -| `query_5m_max_window` | `24h` | Above this window, the dashboard skips the 5-minute tier and uses the 1-hour tier. |
189 -| `query_max_groups` | `50000` | Maximum groups returned by a single aggregation query. Past this, results overflow into a single `__overflow__` bucket and the response carries a warning. |
190 -| `query_facet_max_values_per_field` | `5000` | Maximum distinct values returned per facet field. |
195 +| `query_max_groups` (alias: `query-max-groups`) | `50000` | Maximum number of distinct group keys a single aggregation query can build. When exceeded, additional groups are folded into a synthetic `__overflow__` bucket and the response carries a warning. Protects the query worker from memory blow-up on accidentally wide group-by combinations. |
196
192 -The query-window limits are about responsiveness — large windows on fine-grained tiers are slow. The group/value limits are about memory — wide aggregations on high-cardinality fields can blow up. Raise them carefully.
197 +The tier the planner uses for a given query is decided automatically from the time window and the query view (Sankey / time-series / map / etc.) — the planner aligns to the coarser tier when the window allows, and falls back to a finer tier for the unaligned head/tail. There are no separate "max window per tier" knobs.
198
199 ## `enrichment`
200
@@ -197,12 +202,13 @@ Enrichment is a large topic and lives in dedicated pages. The top-level enable/d
202
203 ```yaml
204 enrichment:
200 - # default_sampling_rate: 1024 # set to override; default is unset (rate=1)
201 - # override_sampling_rate: { 10.1.0.0/16: 1024 } # per-prefix override map
205 + # default_sampling_rate: 1024 # single rate, or per-prefix map
206 + # default_sampling_rate: { 10.1.0.0/16: 1024 } # per-prefix form is also valid
207 + # override_sampling_rate: { 10.1.0.0/16: 1024 } # per-prefix override map
208 default_sampling_rate: ~
203 - override_sampling_rate: {}
209 + override_sampling_rate: ~
210 metadata_static: { exporters: {} }
205 - geoip: { asn_database: [], geo_database: [] }
211 + geoip: { asn_database: [], geo_database: [], optional: false }
212 networks: {}
213 network_sources: {}
214 exporter_classifiers: []
@@ -216,16 +222,17 @@ enrichment:
222 bioris: { enabled: false }
223 ```
224
219 -Detailed configuration of each section lives on its own page:
225 +A note on `default_sampling_rate` vs. `override_sampling_rate`: both keys accept either a single integer (applied to every record from every exporter) or a per-prefix map. The intended split is "default" for `rate=1` records that lack a sampling rate, and "override" for replacing a known-wrong rate; the schema does not enforce that intent — either knob can take either form.
226 +
227 +`enrichment.geoip.optional` (default `false`) decides what happens when an MMDB file declared in `asn_database` / `geo_database` is missing at startup: `false` aborts the plugin, `true` logs a warning and continues without that database.
228 +
229 +For the cross-cutting picture — order of evaluation, the `asn_providers` and `net_providers` chains, the MMDB shared mechanism, the static-vs-dynamic composition rules — see the [Enrichment](/docs/network-flows/enrichment.md) page. Per-method configuration details (URLs, refresh cadence, license, vendor commands) live on the integration cards under flows.enrichment-methods:
230
221 -- [GeoIP](/docs/network-flows/enrichment/ip-intelligence.md)
222 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md)
223 -- [Classifiers](/docs/network-flows/enrichment/classifiers.md)
224 -- [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md)
225 -- [BMP routing](/docs/network-flows/enrichment/bgp-routing.md)
226 -- [BioRIS](/docs/network-flows/enrichment/bgp-routing.md)
227 -- [Network sources](/docs/network-flows/enrichment/network-identity.md)
228 -- [Decapsulation](/docs/network-flows/enrichment/decapsulation.md)
231 +- IP intelligence (MMDB): [DB-IP](/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md), [MaxMind GeoIP / GeoLite2](/src/crates/netflow-plugin/integrations/maxmind_geoip_-_geolite2.md), [IPtoASN](/src/crates/netflow-plugin/integrations/iptoasn.md), [Custom MMDB](/src/crates/netflow-plugin/integrations/custom_mmdb_database.md).
232 +- BGP routing: [BMP](/src/crates/netflow-plugin/integrations/bmp_bgp_monitoring_protocol.md), [bio-rd / RIPE RIS](/src/crates/netflow-plugin/integrations/bio-rd_-_ripe_ris.md).
233 +- Network sources: [AWS IP Ranges](/src/crates/netflow-plugin/integrations/aws_ip_ranges.md), [Azure IP Ranges](/src/crates/netflow-plugin/integrations/azure_ip_ranges.md), [GCP IP Ranges](/src/crates/netflow-plugin/integrations/gcp_ip_ranges.md), [NetBox](/src/crates/netflow-plugin/integrations/netbox.md), [Generic JSON-over-HTTP IPAM](/src/crates/netflow-plugin/integrations/generic_json-over-http_ipam.md).
234 +- YAML-defined: [Static Metadata](/src/crates/netflow-plugin/integrations/static_metadata.md), [Classifiers](/src/crates/netflow-plugin/integrations/classifiers.md), [Decapsulation](/src/crates/netflow-plugin/integrations/decapsulation.md).
235 +- Operational: [Enrichment Intel Downloader](/docs/network-flows/intel-downloader.md) — the bundled refresh tool for MMDB providers.
236
237 The enrichment section has no CLI flag — it is YAML-only.
238
@@ -274,29 +281,29 @@ The plugin reads the inner 5-tuple from `dataLinkFrameSection` records (IPFIX IE
281
282 ```yaml
283 journal:
277 - size_of_journal_files: 100GB
278 - duration_of_journal_files: 7d
284 tiers:
285 raw:
286 size_of_journal_files: 200GB
287 duration_of_journal_files: 24h
288 minute_1:
289 + size_of_journal_files: 20GB
290 duration_of_journal_files: 14d
291 minute_5:
292 + size_of_journal_files: 20GB
293 duration_of_journal_files: 30d
294 hour_1:
295 + size_of_journal_files: 20GB
296 duration_of_journal_files: 365d
289 - size_of_journal_files: null
297 ```
298
292 -The default 10GB / 7d on every tier is too tight for most production deployments. This profile gives you 24 hours of full-detail forensics, 14 days of 1-minute trends, 30 days of 5-minute snapshots, and a year of hourly aggregates. Storage required scales with your flow rate — see [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md).
299 +The built-in defaults (10GB / 7d on every tier) are intended for first validation and small deployments. Most production deployments should size retention from observed flow rate. This profile gives you 24 hours of full-detail forensics, 14 days of 1-minute trends, 30 days of 5-minute snapshots, and a year of hourly aggregates. Storage required scales with your flow rate — see [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md).
300
301 ## Things that go wrong
302
296 -- **The plugin doesn't start.** Check `journalctl -u netdata --since "5 minutes ago" | grep netflow`. The most common cause is a typo in a YAML key (strict mode rejects unknowns).
303 +- **The plugin doesn't start.** Check `journalctl --namespace netdata --since "5 minutes ago" | grep netflow`. The most common cause is a typo in a YAML key (strict mode rejects unknowns).
304 - **Edits don't take effect.** Restart Netdata. There is no DynCfg integration for the plugin's configuration.
305 - **CLI flags I added don't do anything.** When running under Netdata, only the YAML is read.
299 -- **Tiers fill up faster than expected.** All tiers share the top-level retention by default. Set explicit per-tier overrides.
306 +- **Tiers fill up faster than expected.** Each tier has its own size/duration. Set per-tier values that match how long you actually need each tier.
307 - **Queries time out at 30 seconds.** Function calls have a hard 30s timeout in the plugin. If your query is too wide, narrow the time range or add filters that let a higher tier serve it.
308 - **`__overflow__` appears in results.** A group-by exceeded `query_max_groups` (default 50 000). Either narrow the filter, reduce the number of group-by fields, or raise the limit.
309
docs/network-flows/enrichment.md new
+416
@@ -0,0 +1,416 @@
1 +<!--startmeta
2 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment.md"
3 +sidebar_label: "Enrichment"
4 +learn_status: "Published"
5 +learn_rel_path: "Network Flows"
6 +keywords: [enrichment, geoip, asn, bgp, classifiers, network labels, mmdb, ipam]
7 +endmeta-->
8 +
9 +<!-- markdownlint-disable-file -->
10 +
11 +# Enrichment
12 +
13 +Raw flow records carry IP addresses, ports, ASNs the exporter happens to know, ifIndex numbers, and not much else. Enrichment is the post-decode pipeline that turns those into the operational labels you actually want on the dashboard: country and city, AS numbers and AS names, BGP next-hop and AS path, "this is our DMZ in Frankfurt", "this interface is the Lumen transit", "this exporter is a leaf in dc-fra1".
14 +
15 +This page is the cross-cutting reference for that pipeline. It documents the order of evaluation, the provider chains that decide where each field comes from, the shared mechanisms that span all enrichment methods, and the operational properties you need to know about. **Per-method specifics — installation steps, refresh cadence, expected upstream schemas, vendor-specific gotchas — live on the integration cards** linked at the bottom of this page.
16 +
17 +## Order of evaluation per flow record
18 +
19 +Every flow record passes through the same pipeline before it is written to the journal. For one record:
20 +
21 +1. **Decode** — the raw NetFlow / IPFIX / sFlow message is parsed into typed fields (`SRC_ADDR`, `DST_ADDR`, `SRC_AS`, ifIndexes, etc.).
22 +2. **Decapsulation** — when `protocols.decapsulation_mode` is `srv6` or `vxlan` and the exporter ships inner-packet bytes, the inner 5-tuple replaces the outer one. Everything below operates on the inner addresses.
23 +3. **GeoIP MMDB lookups** — the resolver runs the source and destination IPs against every configured ASN MMDB and every configured geo MMDB. Country, state, city, coordinates, AS number candidate, AS name, and the `ip_class` flag are seeded from the result.
24 +4. **Static metadata** — `enrichment.metadata_static.exporters` is matched against the exporter's IP (UDP source) and the ifIndex; `enrichment.networks` is matched per-IP, longest-prefix wins. Static `networks` entries can override country, state, city, latitude, longitude, AS number, and the `*_NET_*` labels.
25 +5. **Dynamic network sources** — every `enrichment.network_sources.<name>` source contributes CIDR records to the same network-attribute resolution path. Lookups for the source/destination IP merge matching records with static `enrichment.networks` entries.
26 +6. **Classifiers** — `exporter_classifiers` runs once per exporter; `interface_classifiers` runs twice per record (once for the input interface, once for the output). They write `EXPORTER_*` and `IN_IF_*` / `OUT_IF_*` fields. **Classifiers are skipped entirely when static metadata already set any classification field on the same target.**
27 +7. **Routing overlay** — the BGP-fed routing trie (BMP and BioRIS contribute to it) is consulted. The `asn_providers` and `net_providers` chains decide whether the AS number and the network mask come from the flow record, from BGP, or from the GeoIP MMDB. BGP-only fields (`NEXT_HOP`, `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES`) are written from BGP data.
28 +8. **Journal write** — the resulting record is written to the raw tier; rollup tiers (1 minute, 5 minutes, 1 hour) are computed from it asynchronously.
29 +
30 +The enricher runs when any enrichment feature is configured, including provider-chain settings. A deployment with all enrichment settings disabled writes raw decoded flow fields only.
31 +
32 +## The two provider chains
33 +
34 +A "provider chain" is an ordered list of where to look for a given field. The plugin walks the chain left-to-right and takes the first non-zero answer. Two chains exist:
35 +
36 +```yaml
37 +enrichment:
38 + asn_providers: [flow, routing, geoip] # default
39 + net_providers: [flow, routing] # default
40 +```
41 +
42 +These are the default provider chains.
43 +
44 +### `asn_providers` — where the AS number comes from
45 +
46 +The chain decides where `SRC_AS` and `DST_AS` come from. The AS *name* (`SRC_AS_NAME`, `DST_AS_NAME`) is **always** rendered separately from the ASN MMDB lookup, regardless of the chain — see "AS numbers vs AS names" below.
47 +
48 +| Provider | What it returns |
49 +|---|---|
50 +| `flow` | The `SRC_AS` / `DST_AS` the exporter put on the record |
51 +| `flow_except_private` | Same, but treats private/reserved AS numbers as zero |
52 +| `flow_except_default_route` | Same, but treats `AS 0 with mask 0` as zero |
53 +| `routing` | The AS from the BGP-fed routing trie (BMP, BioRIS, or static routing) |
54 +| `routing_except_private` | Same as `routing` with the private filter |
55 +| `geoip` | **Terminal "use 0" shortcut** — see below |
56 +
57 +`bmp` and `bmp-except-private` are accepted as backward-compatible aliases for `routing` and `routing_except_private`.
58 +
59 +The `geoip` slot is a **terminal shortcut**, not a normal provider. When the chain reaches `geoip`, the AS number is forced to `0` and the chain ends. The MMDB-derived AS number is then applied separately, outside the chain. This is intentional compatibility behaviour, not a bug.
60 +
61 +The implication is operational: **putting `geoip` anywhere except last truncates the chain.** With `[geoip, flow, routing]`, every AS resolves to `0` from the chain — only the GeoIP-derived AS makes it through, and `flow` and `routing` never run. This is rarely what you want.
62 +
63 +Common chain orderings:
64 +
65 +| Configuration | Behaviour |
66 +|---|---|
67 +| `[flow, routing, geoip]` (default) | Trust the exporter, fall back to BGP, then to GeoIP |
68 +| `[flow, routing]` | No GeoIP at all |
69 +| `[routing, flow, geoip]` | Trust the BGP feed first — useful when exporters have stale AS |
70 +| `[flow_except_private, routing, geoip]` | Drop private AS from flow data, let routing fill in |
71 +
72 +`is_private_as` returns true for `0`, `23456` (RFC 4893 transition reserved), `64496..=65551` (documentation / private), and `>= 4_200_000_000` (32-bit private and high-reserved).
73 +
74 +### `net_providers` — where the network mask comes from
75 +
76 +Same idea, smaller menu: the chain produces `SRC_MASK` and `DST_MASK` (and indirectly `NEXT_HOP` via the routing entry). Only `flow` and `routing` are valid here — there is no `geoip` slot, because the MMDB does not carry network masks.
77 +
78 +### AS numbers vs AS names
79 +
80 +The two are resolved by different mechanisms and never share the chain.
81 +
82 +- **AS numbers** come from the chain above.
83 +- **AS names** are always rendered from the resolved AS number plus the ASN MMDB. The format is `AS{n}` for non-zero ASNs (with `{organisation}` appended when the MMDB has it), `AS0 Unknown ASN` when the resolved AS is zero, and `AS0 Private IP Address Space` when the ASN MMDB tagged the IP as private. There is no static configuration option for AS names.
84 +
85 +A static `enrichment.networks.<cidr>.asn` override is applied during the per-IP network-attribute merge **before** the chain runs. The AS name still goes through the MMDB lookup.
86 +
87 +## How attributes compose: GeoIP, network sources, static `networks`
88 +
89 +For any source or destination IP, the plugin produces network attributes by merging multiple inputs. The merge rule is "non-empty overlay overwrites; empty overlay leaves the field alone" — so the **last input with a non-empty value wins, per field.**
90 +
91 +The merge order, per IP, is:
92 +
93 +1. **GeoIP base layer** — country, state, city, coordinates, AS number candidate, AS name, `ip_class`.
94 +2. **All matching prefixes in ascending prefix-length order** (least-specific first, most-specific last). At each prefix length:
95 + - **Dynamic `network_sources` records** are merged first (priority 0).
96 + - **Static `enrichment.networks` records** are merged second (priority 1).
97 +
98 +Two consequences worth stating explicitly:
99 +
100 +- **Specificity dominates.** A more-specific prefix (longer mask) always wins on its non-empty fields, regardless of whether it came from a static source or a dynamic feed. A `/24` static entry overwrites a `/16` dynamic entry; a `/24` dynamic entry overwrites a `/16` static entry.
101 +- **At the same prefix length, static `networks` wins.** Because static is merged after dynamic at each level, and the merge primitive is "non-empty wins", a non-empty field in a static entry overwrites the dynamic entry's value at the same prefix length. This is intentional — explicit operator config beats imported data — but it surprises operators who expect the remote feed to be authoritative.
102 +
103 +A practical consequence of "merge ascending, non-empty wins": **a more-specific entry that leaves a field blank inherits the supernet's value for that field.** To clear a field on a `/24`, you must set it explicitly to a value, not just leave it out.
104 +
105 +Static `networks` can also override `country`, `state`, `city`, `latitude`, `longitude`, and `asn` per CIDR. These compose with the same merge rules — they are simply additional non-empty fields that overwrite the GeoIP base layer when matched. Coordinates (`latitude`, `longitude`) are static-only; network-identity sources cannot set them.
106 +
107 +## The MMDB shared mechanism
108 +
109 +DB-IP, MaxMind GeoIP / GeoLite2, IPtoASN-converted-to-MMDB, and any custom MMDB build all use the same MMDB resolver behavior. The configuration:
110 +
111 +```yaml
112 +enrichment:
113 + geoip:
114 + asn_database:
115 + - /var/cache/netdata/topology-ip-intel/topology-ip-asn.mmdb
116 + geo_database:
117 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
118 + optional: true
119 +```
120 +
121 +| Key | Type | Notes |
122 +|---|---|---|
123 +| `asn_database` | list of paths | One or more ASN MMDB files. Aliases: `asn-database`. |
124 +| `geo_database` | list of paths | One or more geographic MMDB files. Aliases: `geo-database`, `country-database`. |
125 +| `optional` | bool, default `false` | When `false`, a missing file at startup aborts the plugin. When `true`, missing or unreadable files are tolerated. Auto-detected files are always treated as `optional: true`. |
126 +
127 +### Auto-detect path order
128 +
129 +When neither `asn_database` nor `geo_database` is configured, the plugin searches in this order at startup:
130 +
131 +1. **`<cache_dir>/topology-ip-intel/`** — where `cache_dir` is the parent of `journal.journal_dir` if that path is absolute, otherwise `NETDATA_CACHE_DIR`. Typically `/var/cache/netdata/topology-ip-intel/`.
132 +2. **`<stock_data_dir>/topology-ip-intel/`** — typically `/usr/share/netdata/topology-ip-intel/`.
133 +
134 +The canonical filenames are `topology-ip-asn.mmdb` and `topology-ip-geo.mmdb`. Native packages ship stock DB-IP files under the stock data directory. The [Intel Downloader](/docs/network-flows/intel-downloader.md) writes fresher copies to the cache directory, which takes precedence over stock files. Netdata does not install a downloader timer; schedule one if freshness matters.
135 +
136 +### Composition: last non-empty wins, per field
137 +
138 +`asn_database` and `geo_database` are lists. For each lookup, every database in the list runs in order; per output field, the **last** database that produces a **non-empty** value wins. Empty / zero values returned by a later database do not overwrite an earlier match.
139 +
140 +This is the same merge rule as the network-attributes merge, applied to the MMDB scan.
141 +
142 +The practical use is stacking: list a stronger source first for one field and a backup for the rest. For example, layering MaxMind after IPtoASN in `asn_database` recovers AS *names* (which IPtoASN-converted MMDBs often lack) without losing IPtoASN's AS *number* coverage.
143 +
144 +### Signature-watch reload
145 +
146 +The resolver checks each configured database's signature (size + mtime) every 30 seconds and reloads the readers in place when the signature changes.
147 +
148 +Only successful reloads swap the active readers — a transient read error keeps the previous readers serving lookups.
149 +
150 +This is why MMDB refresh scripts only need to atomically replace the file on disk: the plugin picks up the new file within 30 seconds, no restart needed.
151 +
152 +### IPv4 / IPv6 dual-stack handling
153 +
154 +The resolver inspects each MMDB's metadata. An IPv6 lookup against an IPv4-only database is **silently skipped** (no warning, no error). Most current providers (DB-IP, MaxMind GeoLite2, MaxMind GeoIP2, IPtoASN combined) ship a single dual-stack MMDB that covers both families; legacy single-family builds are the only case where this matters in practice.
155 +
156 +## Network sources: shared operational properties
157 +
158 +`enrichment.network_sources.<name>` is the dynamic counterpart to static `networks`. AWS IP Ranges, GCP IP Ranges, Azure IP Ranges, NetBox, and custom JSON-over-HTTP IPAM sources share the same operational contract. The only difference between cards is the upstream URL, the JSON shape, and the jq transform.
159 +
160 +### Fetch loop and back-off
161 +
162 +Each source runs in its own task. Multiple sources fetch in parallel; within a source, only one fetch is in flight at a time.
163 +
164 +Per cycle:
165 +
166 +1. HTTP GET (or POST) at `interval` cadence, with `headers:` applied.
167 +2. Response parsed as JSON.
168 +3. The configured `transform` (a [jaq](https://github.com/01mf02/jaq) expression) runs over the parsed JSON.
169 +4. Output is decoded as a stream of `RemoteRecord` objects.
170 +5. Records are merged into the shared network-attributes trie.
171 +
172 +On any failure (HTTP error, JSON parse error, jq runtime error, **empty result**), the source backs off exponentially starting at `interval / 10` (floor 1 second), doubling on each retry, capped at `interval`. On success it resets to the configured `interval`.
173 +
174 +The scheduler floors dynamic-source intervals at 60 seconds. So `interval: 5s` is effectively `60s`. There is no signature/etag change detection — the plugin re-parses and re-publishes the entire record set on every successful fetch.
175 +
176 +### Expected jq output schema
177 +
178 +The `transform` must emit a stream of objects with this shape:
179 +
180 +```json
181 +{
182 + "prefix": "10.0.0.0/8",
183 + "name": "internal",
184 + "role": "lan",
185 + "site": "fra1",
186 + "region": "eu-central",
187 + "country": "DE",
188 + "state": "HE",
189 + "city": "Frankfurt",
190 + "tenant": "tenant-a",
191 + "asn": 64500,
192 + "asn_name": "Internal AS"
193 +}
194 +```
195 +
196 +- **Required**: `prefix` (CIDR string).
197 +- **Optional**: every other field. Defaults to empty / 0.
198 +- `asn` accepts an integer (`64500`), a string (`"64500"`), or AS notation (`"AS64500"`).
199 +- **Coordinates are not settable** — the deserializer has no field for `latitude` / `longitude`. Use static `networks` for coordinates.
200 +
201 +The default `transform` is `"."`, which returns the raw JSON object — never a per-prefix stream. **Every real source needs a custom `transform`**; the per-source integration cards each ship a working example.
202 +
203 +If the transform produces zero objects on a successful HTTP fetch, the cycle is treated as a failure and triggers backoff. This catches silently broken jq but punishes legitimately empty IPAMs.
204 +
205 +### Strict YAML validation
206 +
207 +Configuration schemas use `deny_unknown_fields` at every level. A typo in `network_sources.<name>.<key>` fails at startup with a `unknown field 'X', expected one of ...` error. This applies to the entire enrichment config, not just network sources.
208 +
209 +### TLS verification cannot be disabled
210 +
211 +Configuration validation rejects any attempt to disable TLS verification. Legacy keys are accepted for forward compatibility, but no override exists.
212 +
213 +Self-signed or internal CAs must be supplied via `tls.ca_file`. The rationale is deliberate: enrichment data flows directly into security investigations and capacity decisions — silently accepting MITM-able responses would corrupt every downstream analysis.
214 +
215 +### Single page only — no pagination
216 +
217 +The fetch is one-shot per cycle. There is no pagination, no cursor handling, no `Link: rel=next` following. Operators with paginated IPAM endpoints must either expose a separate "all prefixes" bulk endpoint (most IPAMs have one) or wrap with a server-side aggregator script.
218 +
219 +This is irrelevant to AWS / GCP / Azure (single-file feeds) and bites NetBox / generic IPAM. The NetBox card documents `?limit=0` (NetBox 4.x) as the bypass.
220 +
221 +### Authentication — bring your own headers
222 +
223 +The plugin has no built-in OAuth flow, basic-auth helper, or token refresh. Whatever the upstream API needs goes into `headers:`:
224 +
225 +```yaml
226 +headers:
227 + Authorization: "Token abc123"
228 +```
229 +
230 +`headers` is a free-form map, so any single-shot scheme works. URL-embedded credentials (`https://user:pass@host/...`) are converted to HTTP Basic authentication by the HTTP client, but explicit `Authorization` headers are clearer and avoid storing credentials in URLs. Short-lived tokens must be refreshed outside Netdata and reloaded into the config.
231 +
232 +POST is supported but **sent with no body**. POST endpoints that require a request body are not supported.
233 +
234 +### Field-level merge across sources sharing prefixes
235 +
236 +When two sources (say `aws` and a custom internal IPAM) both define overlapping prefixes, all records are merged into the same trie. Within one source, last-write-wins for an exact prefix length; across sources, the merge is field-level — non-empty fields from a later record overwrite earlier ones. This is the same primitive described in "How attributes compose" above.
237 +
238 +### Diagnostic journal output
239 +
240 +Failures (transform compile, HTTP, JSON parse, jq runtime, empty result) are logged in the Netdata journal namespace:
241 +
242 +```bash
243 +journalctl --namespace netdata | grep network-sources
244 +```
245 +
246 +JSON parse errors are silent in the dashboard — the journal is the place to look.
247 +
248 +## Static metadata short-circuits classifiers
249 +
250 +When `metadata_static.exporters` set **any** classification field (`group`, `role`, `site`, `region`, `tenant`) for an exporter, the entire `exporter_classifiers` rule chain is skipped for that exporter.
251 +
252 +The same rule applies to interfaces — any of `provider` / `connectivity` / `boundary` set by static metadata short-circuits `interface_classifiers` for that interface.
253 +
254 +This is "Akvorado parity" by design. The implication: **don't try to mix static and rule-based classification on the same target.** If you set even one field statically, all classifier rules for that target are bypassed. Decide per-target: enumerate it in static metadata, or let the classifier rules find it.
255 +
256 +## Classifier evaluation surfaces and ordering
257 +
258 +- **Exporter classifiers** run once per exporter, with the result cached.
259 +- **Interface classifiers** run once per `(exporter, interface)` pair, **twice per flow record** (once for the input interface, once for the output).
260 +- **First write wins** per output slot. Once a rule sets `EXPORTER_GROUP`, no later rule can change it. Order rules from most-specific to least-specific.
261 +- **Cache TTL** is `enrichment.classifier_cache_duration` (default 5 minutes, last-access). Validation rejects values shorter than 1 second.
262 +- **Runtime errors stop the rule list** but keep whatever was set so far. Use string-safe operators (`matches`, `startsWith`, `contains`) rather than `>` / `<` on string fields.
263 +
264 +The classifier syntax is intentionally Akvorado-compatible for the documented operators and actions, so existing Akvorado rule lists usually paste in unchanged. Akvorado's full `expr-lang` features (arithmetic, ternaries, lambdas) are not implemented here.
265 +
266 +## Decapsulation: the inner-packet override
267 +
268 +When `protocols.decapsulation_mode` is `srv6` or `vxlan` and the exporter ships inner-packet bytes (NetFlow v9 IE 104, IPFIX IE 315, sFlow `SampledHeader`), the inner 5-tuple replaces the outer one **before any enrichment runs**:
269 +
270 +- `SRC_ADDR`, `DST_ADDR`, `SRC_PORT`, `DST_PORT`, `PROTOCOL`, `ETYPE`, `IPTOS`, `IPTTL`, `IPV6_FLOW_LABEL`, `TCP_FLAGS`, MPLS labels, ICMP type/code, `BYTES` (inner L3 length).
271 +- For VXLAN: inner `SRC_MAC` / `DST_MAC` / `SRC_VLAN` / `DST_VLAN`. **Outer MACs and VLANs are lost.**
272 +
273 +This means **all downstream enrichment operates on the inner addresses**: GeoIP runs against the inner IPs, network attributes match the inner prefixes, and the routing trie is consulted with the inner addresses.
274 +
275 +Two important rules:
276 +
277 +- **Decap is destructive on non-tunnel traffic.** Records arriving via the L2-section path that are not the configured tunnel are **dropped**, not falled back to outer view. Plain NetFlow / IPFIX flow records that don't go through the L2-section path are unaffected.
278 +- **VXLAN VNI is parsed but not surfaced.** Bytes 4-6 of the VXLAN header are skipped. Pure VNI-based segmentation is not visible as a filter or group-by field.
279 +
280 +GRE, IP-in-IP, GENEVE, MPLS-over-UDP, and NVGRE are not decoded.
281 +
282 +See the [Decapsulation integration card](/src/crates/netflow-plugin/integrations/decapsulation.md) for exporter configuration recipes.
283 +
284 +## Routing overlay (BMP and BioRIS share the trie)
285 +
286 +BMP and BioRIS are separate transports that feed the **same** in-memory routing trie. A deployment running BMP from internal routers and BioRIS for an external view (RIPE RIS) gets unified enrichment without duplicate trie entries.
287 +
288 +Each prefix entry holds a list of routes keyed by `(peer, route_key)` so multipath BGP and multiple peers contributing the same prefix coexist. Lookups walk the trie longest-prefix-first, then refine within candidates by:
289 +
290 +1. Exporter IP matches the flow's exporter AND next-hop matches the flow's next-hop;
291 +2. otherwise exporter IP matches;
292 +3. otherwise next-hop matches;
293 +4. otherwise the first route at the matched prefix.
294 +
295 +The fields populated are `SRC_AS` / `DST_AS` (via the `routing` provider in the chain), `SRC_MASK` / `DST_MASK` (via `net_providers`), and `NEXT_HOP`, `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES` (always written from BGP data when the lookup hits, no chain involvement).
296 +
297 +AS *names* still come from the ASN MMDB, not from BGP. BGP gives accurate AS numbers, paths, and communities; the names come from the database.
298 +
299 +## Operational properties that span all methods
300 +
301 +### Refresh windows
302 +
303 +| Mechanism | Window | Notes |
304 +|---|---|---|
305 +| GeoIP MMDB | **30 seconds** | Signature watch (size + mtime) |
306 +| Dynamic network sources | per-source `interval`, **floor 60 s** | Independent per source |
307 +| BMP routing | live | Routers push updates over TCP |
308 +| BioRIS routing | live | gRPC stream from a bio-rd daemon |
309 +| Static metadata | **plugin restart** | No hot-reload |
310 +| Classifier rules | **plugin restart** | No hot-reload |
311 +
312 +### Restart behaviour
313 +
314 +- **GeoIP databases** are reloaded automatically — no special handling needed.
315 +- **The routing trie is not persisted.** A restart wipes BGP-derived data; it is re-learned as routers re-send Initiation + Update messages (BMP) or as bio-rd's next refresh cycle dumps the RIB (BioRIS). Convergence ranges from seconds (FRR) to minutes (Cisco IOS-XR, RIPE RIS full feed). Schedule restarts off-peak when BGP attribution matters.
316 +- **Network-source records** are re-fetched on the next interval tick. There is no persistence between restarts.
317 +- **Classifier caches** are wiped — they refill on first hit per target.
318 +
319 +### No in-process freshness signal
320 +
321 +There is no metric or alert for "your MMDB is too old", "your IPAM hasn't refreshed in 6 hours", or "your BMP session is stuck". Operators must watch:
322 +
323 +- The MMDB file's `mtime` on disk.
324 +- The Netdata journal (`journalctl --namespace netdata | grep -E 'network-sources|geoip|bmp|bioris'`) for fetch failures, parse errors, and session events.
325 +- Per-source last-success timestamps (where the integration card documents them).
326 +
327 +### Empty enrichment trees disable the enricher
328 +
329 +When every static and dynamic input is empty, no enrichment runs and the journal carries no `*_NET_*`, `*_AS_NAME`, `*_COUNTRY`, etc. Useful for verifying that adding config actually opted you in — if fields stay empty after a config change, suspect the enricher never started because the config did not enable any enrichment source.
330 +
331 +### Field survival into rollup tiers
332 +
333 +The journal stores raw records and three rollup tiers (1 minute, 5 minutes, 1 hour). High-cardinality fields are dropped from rollups to keep them tractable.
334 +
335 +| Field | Raw | 1 m | 5 m | 1 h | Notes |
336 +|---|---|---|---|---|---|
337 +| `SRC_COUNTRY`, `DST_COUNTRY` | yes | yes | yes | yes | ~250 values |
338 +| `SRC_GEO_STATE`, `DST_GEO_STATE` | yes | yes | yes | yes | Low thousands |
339 +| `SRC_AS`, `DST_AS`, `SRC_AS_NAME`, `DST_AS_NAME` | yes | yes | yes | yes | Bounded by global routing table size |
340 +| `SRC_NET_*`, `DST_NET_*` (NAME, ROLE, SITE, REGION, TENANT) | yes | yes | yes | yes | Operator-defined cardinality |
341 +| `NEXT_HOP` | yes | yes | yes | yes | |
342 +| `SRC_GEO_CITY`, `DST_GEO_CITY` | yes | — | — | — | **Raw tier only** |
343 +| `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE` | yes | — | — | — | **Raw tier only**, hidden by default |
344 +| `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE` | yes | — | — | — | **Raw tier only**, hidden by default |
345 +| `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES` | yes | — | — | — | **Raw tier only** |
346 +| `MPLS_LABELS`, `SRC_MAC`, `DST_MAC`, NAT fields | yes | — | — | — | **Raw tier only** |
347 +
348 +City / coordinate / AS-path / community queries that span longer windows than the raw retention will silently fall back to a rollup tier and return empty. Narrow the window or use country / state.
349 +
350 +See [Retention and querying](/docs/network-flows/retention-querying.md) and [Field reference](/docs/network-flows/field-reference.md) for the full picture.
351 +
352 +### Geographic accuracy is best-effort
353 +
354 +City-level GeoIP is accurate for many public IPs but wrong for VPNs, mobile carriers, and cloud-provider egress IPs (which often resolve to the cloud region's central city, not the actual user). Use country and state for trends; use city only after validating it for the prefix you care about. ASN ownership data also drifts — companies merge, prefixes get reassigned. A database older than a quarter or two starts labelling reassigned prefixes with the previous owner.
355 +
356 +### Sampling-rate knobs
357 +
358 +Two related, easily confused knobs (set under `enrichment` in `netflow.yaml`):
359 +
360 +- `default_sampling_rate` — consulted **only** when the flow record did not carry a rate.
361 +- `override_sampling_rate` — **always** wins when its prefix matches the exporter IP (the source IP of the UDP datagram), regardless of what the flow carried.
362 +
363 +Both accept either an integer (uniform rate) or a CIDR-keyed map. The exporter match key is the same one used by `metadata_static.exporters` lookup — the source IP of the UDP datagram, not a router-internal management ID.
364 +
365 +### Validate BGP-derived enrichment before relying on it
366 +
367 +BMP and BioRIS depend on router configuration, export policy, peer identity, and route-refresh behaviour. Treat configuration changes as production-impacting and validate against your specific routers before relying on BGP-derived data for capacity or security decisions.
368 +
369 +## What can go wrong
370 +
371 +- **AS numbers all zero.** `geoip` is mid-chain. Reorder to put `geoip` last.
372 +- **Map renders empty over a long window.** City / lat / lon are raw-tier only; the query auto-fell-back to a rollup tier. Narrow the window or use the country / state map.
373 +- **Static-defined exporter doesn't get classifier labels.** Static metadata set at least one classification field; classifiers are skipped for that target. Decide one or the other per target.
374 +- **Remote feed value not appearing — static config silently won.** Static `networks` wins ties at the same prefix length. Either use a more-specific dynamic entry or remove the conflicting static field.
375 +- **Network source backs off on an empty IPAM.** Empty result is treated as failure. Workaround: have the upstream return at least one synthetic prefix.
376 +- **Decap drops legitimate traffic.** When `decapsulation_mode` is set, non-tunnel traffic on the L2-section path is dropped, not fallen back. Either use a uniform encapsulation per exporter or disable decap.
377 +- **`speed: 1000` means 1 kbps.** The static-metadata `speed` field is in **bits per second**. A 1 Gbps interface is `1000000000`.
378 +- **MMDB updates not picked up.** Verify the file actually changed on disk (size or mtime). The 30-second poll only triggers on signature change.
379 +- **Network-source TLS cannot be disabled.** Use `tls.ca_file` for internal CAs.
380 +
381 +## What's next
382 +
383 +### Per-method integration cards
384 +
385 +These cards carry the per-method specifics — installation steps, refresh cadence, expected upstream schemas, vendor-specific gotchas — that this page deliberately does not duplicate.
386 +
387 +**IP intelligence (MMDB)**
388 +- [DB-IP IP Intelligence](/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md) — the default that ships with Netdata
389 +- [MaxMind GeoIP / GeoLite2](/src/crates/netflow-plugin/integrations/maxmind_geoip_-_geolite2.md) — commercial GeoIP2 or free GeoLite2 with attribution
390 +- [IPtoASN](/src/crates/netflow-plugin/integrations/iptoasn.md) — public-domain ASN + country, hourly cadence
391 +- [Custom MMDB Database](/src/crates/netflow-plugin/integrations/custom_mmdb_database.md) — your own MMDB build
392 +
393 +**BGP routing**
394 +- [BMP (BGP Monitoring Protocol)](/src/crates/netflow-plugin/integrations/bmp_bgp_monitoring_protocol.md) — routers push BGP updates over TCP
395 +- [bio-rd / RIPE RIS](/src/crates/netflow-plugin/integrations/bio-rd_-_ripe_ris.md) — pull BGP data from a bio-rd RIS gRPC daemon
396 +
397 +**Network identity (cloud IP ranges, IPAM)**
398 +- [AWS IP Ranges](/src/crates/netflow-plugin/integrations/aws_ip_ranges.md) — public AWS prefix list
399 +- [GCP IP Ranges](/src/crates/netflow-plugin/integrations/gcp_ip_ranges.md) — public GCP prefix list
400 +- [Azure IP Ranges](/src/crates/netflow-plugin/integrations/azure_ip_ranges.md) — Azure Service Tags (requires an internal mirror)
401 +- [NetBox](/src/crates/netflow-plugin/integrations/netbox.md) — open-source IPAM / DCIM
402 +- [Generic JSON-over-HTTP IPAM](/src/crates/netflow-plugin/integrations/generic_json-over-http_ipam.md) — Infoblox, BlueCat, phpIPAM, custom CMDBs
403 +
404 +**Static and rule-based**
405 +- [Static Metadata](/src/crates/netflow-plugin/integrations/static_metadata.md) — per-exporter, per-interface, per-CIDR labels
406 +- [Classifiers](/src/crates/netflow-plugin/integrations/classifiers.md) — Akvorado-compatible expression rules
407 +
408 +**Decapsulation**
409 +- [Decapsulation](/src/crates/netflow-plugin/integrations/decapsulation.md) — SRv6 and VXLAN inner-packet extraction
410 +
411 +### Related concepts
412 +
413 +- [Intel Downloader](/docs/network-flows/intel-downloader.md) — the bundled tool that fetches and converts MMDB files for the auto-detect path.
414 +- [Configuration](/docs/network-flows/configuration.md) — the full `enrichment` section reference.
415 +- [Field reference](/docs/network-flows/field-reference.md) — every flow record field, what writes it, what reads it.
416 +- [Retention and querying](/docs/network-flows/retention-querying.md) — which fields survive into rollups, how queries pick a tier.
docs/network-flows/enrichment/asn-resolution.md deleted
-121
@@ -1,121 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/asn-resolution.md"
3 -sidebar_label: "ASN Resolution"
4 -learn_status: "Published"
5 -learn_rel_path: "Network Flows/Enrichment"
6 -keywords: ['asn', 'as resolution', 'bgp', 'enrichment']
7 -endmeta-->
8 -
9 -# ASN resolution
10 -
11 -ASN resolution is how Netdata fills in the `SRC_AS`, `DST_AS`, `SRC_AS_NAME`, and `DST_AS_NAME` fields on every flow record. It uses a configurable provider chain — your flow data first, then dynamic routing if you have BMP/BioRIS/static prefixes set up, then the GeoIP database as a fallback. The chain runs independently for source and destination IPs.
12 -
13 -## Numbers vs names
14 -
15 -The two are resolved by completely different code paths.
16 -
17 -**AS numbers** (`SRC_AS`, `DST_AS`) come from the **provider chain** (`asn_providers`). The chain walks providers in order; the first one that returns a non-zero AS wins.
18 -
19 -**AS names** (`SRC_AS_NAME`, `DST_AS_NAME`) always come from the **ASN database** lookup, regardless of the chain. Whichever AS number ends up resolved, the name is rendered as `AS{n} {organisation}` from the ASN MMDB. If the MMDB doesn't know the name, the rendering is `AS{n}` with no trailing label. If the resolved AS is `0`, the rendering is `AS0 Unknown ASN` (or `AS0 Private IP Address Space` if the MMDB tagged the IP as private).
20 -
21 -There is **no static configuration option** for AS names. You can set `enrichment.networks.<cidr>.asn` to override the AS *number*, but the name is always looked up.
22 -
23 -## The provider chain
24 -
25 -```yaml
26 -enrichment:
27 - asn_providers: [flow, routing, geoip]
28 -```
29 -
30 -This is the default. The plugin walks it left-to-right; the first provider returning a non-zero value wins.
31 -
32 -| Provider | What it reads | Notes |
33 -|---|---|---|
34 -| `flow` | `SRC_AS` / `DST_AS` from the flow record itself | What the exporter sent |
35 -| `flow_except_private` | Same, but treats private/reserved AS numbers as zero | Use when your exporters announce private AS that you don't want to surface |
36 -| `flow_except_default_route` | Same, but treats AS 0 with mask 0 as zero | Use when default-route flows pollute your top-N |
37 -| `routing` | `lookup_routing()` — BMP runtime, BioRIS, static prefixes | Requires routing enrichment to be configured |
38 -| `routing_except_private` | Same as `routing`, with the private filter | |
39 -| `geoip` | **Always returns 0** — terminal short-circuit | See below |
40 -
41 -`geoip` is **terminal**: putting it in the chain stops the chain at that position. Once reached, the chain returns 0, and the GeoIP MMDB's AS data is then re-applied separately (without going through the chain). Because of that, `geoip` is meaningful only as the last entry in the chain — it acts as "let GeoIP fill in if nothing else did".
42 -
43 -If you put `[geoip, flow, routing]`, you effectively set every AS to 0, and only the GeoIP-derived AS makes it through. That is rarely what you want.
44 -
45 -### Common chain configurations
46 -
47 -| Configuration | Behaviour |
48 -|---|---|
49 -| `[flow, routing, geoip]` (default) | Trust the exporter, fall back to routing, then to GeoIP. |
50 -| `[flow, routing]` | No GeoIP at all. Use when you don't trust GeoIP for your traffic mix. |
51 -| `[routing, flow, geoip]` | Trust your BMP/BGP feed first. Use when your routers report stale AS. |
52 -| `[flow_except_private, routing, geoip]` | Drop AS 64512-65534 from flow data; let routing fill in. |
53 -
54 -### What counts as private/reserved
55 -
56 -`is_private_as` returns true for:
57 -
58 -- `0` (unknown / default route)
59 -- `23456` (RFC 4893 transition-period reserved)
60 -- `64496..=65551` (documentation, RFC 6996/RFC 5398/RFC 6793 private/reserved range)
61 -- `>= 4_200_000_000` (32-bit private range and reserved high values)
62 -
63 -These are filtered out by the `*_except_private` variants.
64 -
65 -## The network-prefix chain
66 -
67 -A second chain controls how `SRC_MASK`, `DST_MASK`, and `NEXT_HOP` get resolved:
68 -
69 -```yaml
70 -enrichment:
71 - net_providers: [flow, routing]
72 -```
73 -
74 -This is the default. Same logic: first non-empty value wins. Only `flow` and `routing` are valid here — there is no `geoip` provider for network attributes.
75 -
76 -## AS overrides via static configuration
77 -
78 -If a flow's source or destination IP falls inside a CIDR you've declared under `enrichment.networks`, and that entry includes an `asn` field, the configured value **overrides whatever the chain produced**:
79 -
80 -```yaml
81 -enrichment:
82 - networks:
83 - 198.51.100.0/24:
84 - name: customer-acme
85 - asn: 64500 # forces SRC_AS / DST_AS = 64500 for traffic in this prefix
86 -```
87 -
88 -This override is applied after the chain. It only sets the AS number — the name is still resolved from the ASN database (so it'll render as `AS64500` if your MMDB doesn't have a name, or `AS64500 Acme Corp` if it does).
89 -
90 -## What you get out of the box
91 -
92 -With the default `[flow, routing, geoip]` chain, no routing enrichment configured, and the stock ASN MMDB shipped with native packages:
93 -
94 -- `SRC_AS` / `DST_AS` populated whenever the exporter sends them (most NetFlow v9, IPFIX, sFlow exporters do for public IPs)
95 -- `SRC_AS_NAME` / `DST_AS_NAME` populated whenever the IP is in the ASN MMDB
96 -- For internal RFC 1918 addresses: `*_AS = 0`, `*_AS_NAME = AS0 Private IP Address Space` (because the stock MMDB tags private ranges with that flag)
97 -- For unknown public addresses: `*_AS = 0`, `*_AS_NAME = AS0 Unknown ASN`
98 -
99 -If you don't have an ASN MMDB at all, names render as `AS{n}` for non-zero ASNs and `AS0 Unknown ASN` for zero — the dashboard never shows blank cells.
100 -
101 -## Failure modes
102 -
103 -- **ASN MMDB missing.** With `optional: true` (the default for auto-detected files), the plugin starts and AS names render as `AS{n}` or `AS0 Unknown ASN`. With `optional: false` and a configured path, the plugin fails to start.
104 -- **AS not in any provider.** `*_AS = 0`, `*_AS_NAME = AS0 Unknown ASN`.
105 -- **Wrong order of providers.** Putting `geoip` mid-chain truncates everything after it. Putting `routing` before `flow` makes routing data win over what the exporter sent — fine if your BGP feed is more accurate than your exporter's view.
106 -- **Empty `asn_providers`.** No validation rejects this. The plugin starts but every AS number resolves to 0; only `enrichment.networks.<cidr>.asn` overrides can produce non-zero AS.
107 -
108 -## What can go wrong
109 -
110 -- **AS numbers all zero.** Check the chain. If `[geoip, ...]` is the order, `geoip` short-circuits to 0. Reorder to put `geoip` last.
111 -- **Wrong AS for a known prefix.** Likely the exporter's view differs from the BGP table. Override per-prefix via `enrichment.networks.<cidr>.asn`, or reorder the chain to `[routing, flow, geoip]`.
112 -- **Names show `AS{n}` without an organisation.** The MMDB doesn't have a name for that AS. Either accept it or use a richer MMDB.
113 -- **Names show wrong organisation.** ASN ownership data is best-effort and lags real-world transfers by weeks. Refresh the MMDB. If that doesn't help, file an issue with the database vendor — Netdata is a passive consumer.
114 -
115 -## What's next
116 -
117 -- [GeoIP](/docs/network-flows/enrichment/ip-intelligence.md) — How the ASN MMDB gets installed and refreshed.
118 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Per-prefix AS overrides and network labels.
119 -- [BMP routing](/docs/network-flows/enrichment/bgp-routing.md) — Live BGP feed as an AS source for the `routing` provider.
120 -- [BioRIS](/docs/network-flows/enrichment/bgp-routing.md) — RIPE RIS as an AS source for the `routing` provider.
121 -- [Network sources](/docs/network-flows/enrichment/network-identity.md) — HTTP-fetched prefix metadata.
docs/network-flows/enrichment/bgp-routing.md deleted
-115
@@ -1,115 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/bgp-routing.md"
3 -sidebar_label: "BGP Routing"
4 -learn_status: "Published"
5 -learn_rel_path: "Network Flows/Enrichment Concepts"
6 -keywords: ['bgp', 'routing', 'bmp', 'bioris', 'enrichment', 'concept']
7 -endmeta-->
8 -
9 -# BGP Routing
10 -
11 -BGP-routing enrichment fills `SRC_AS`, `DST_AS`, `SRC_MASK`, `DST_MASK`, `NEXT_HOP`, `DST_AS_PATH`, `DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES` from a live BGP feed. Two transports are supported, both of which feed the same in-memory routing trie:
12 -
13 -- **BMP** (BGP Monitoring Protocol, RFC 7854) — routers push their BGP updates to Netdata over TCP
14 -- **BioRIS** — Netdata pulls BGP data from a [bio-rd](https://github.com/bio-routing/bio-rd) `cmd/ris/` daemon over gRPC
15 -
16 -This page covers the **cross-cutting concept**: how the trie works, how the two sources combine, what survives a restart, and what to expect operationally. For per-protocol setup, follow the integration cards on Learn (BMP and bio-rd / RIPE RIS).
17 -
18 -## What gets enriched
19 -
20 -BMP and BioRIS populate the same fields. When a flow's source or destination IP matches a learned BGP route:
21 -
22 -| Field | Side | Notes |
23 -|---|---|---|
24 -| `SRC_AS` / `DST_AS` | both | When the `routing` provider in `asn_providers` chain reaches BGP data |
25 -| `SRC_MASK` / `DST_MASK` | both | When the `routing` provider in `net_providers` chain reaches BGP data |
26 -| `NEXT_HOP` | dest only | BGP next-hop from the destination route |
27 -| `DST_AS_PATH` | dest only | Full BGP AS path (CSV of ASNs) |
28 -| `DST_COMMUNITIES` | dest only | Standard BGP communities (CSV of u32) |
29 -| `DST_LARGE_COMMUNITIES` | dest only | RFC 8092 large communities |
30 -
31 -Two notes:
32 -
33 -- AS *names* (`*_AS_NAME`) come from the [GeoIP/ASN MMDB](/docs/network-flows/enrichment/ip-intelligence.md), not BGP. BGP gives you accurate AS *numbers* and path/communities; the names come from the ASN database.
34 -- Source-side AS path and communities are **not** surfaced. BGP path attributes are most meaningful for the destination of the traffic.
35 -
36 -## Shared trie
37 -
38 -Both BMP and BioRIS populate a single in-memory routing trie keyed by IP prefix. Each prefix entry holds a list of routes (one per `(peer, route_key)` tuple), so multipath BGP and multiple BGP peers contributing the same prefix coexist cleanly.
39 -
40 -When both BMP and BioRIS are enabled, they contribute to the same trie. Lookups pick the best-matching route across both sources, preferring routes whose exporter or next-hop matches the flow being enriched, falling back to longest-prefix-match.
41 -
42 -This is intentional: a deployment that runs BMP from internal routers and BioRIS for external (RIPE RIS) views gets unified enrichment without duplicate trie entries.
43 -
44 -## Memory growth
45 -
46 -The trie has **no time-based eviction**. Routes are only removed via:
47 -
48 -- Explicit BGP withdrawal (`MP_UNREACH`, `withdraw_routes`)
49 -- Peer Down notification (BMP) — clears all routes for the affected peer
50 -- TCP disconnect (BMP) followed by the `keep` interval expiring (default 5 minutes) — clears all routes for that session
51 -- bio-rd refresh cycle — explicit removal of routes for routers that have disappeared
52 -
53 -A full IPv4+IPv6 BGP table is roughly 1.2M prefixes per peer (2026 figures). Each entry stores the AS-path `Vec<u32>`, communities `Vec<u32>`, large communities `Vec<(u32,u32,u32)>`, plus a `route_key` `String` per path. Expect several hundred MB of resident memory per peer for a full feed.
54 -
55 -Plan capacity accordingly. If you run many peers with full feeds, watch the agent's RSS.
56 -
57 -## Restart behaviour
58 -
59 -The trie is **not persisted**. Restarting the netflow plugin wipes BGP-derived data. Routes are re-learned as routers re-send Initiation + Update messages (for BMP) or as bio-rd's next refresh cycle dumps the RIB (BioRIS).
60 -
61 -Convergence times after restart:
62 -
63 -| Source | Typical convergence |
64 -|---|---|
65 -| FRR over BMP | seconds (FRR re-emits everything immediately) |
66 -| Cisco IOS-XR over BMP | minutes (IOS-XR's initial-refresh has a configurable spread) |
67 -| Juniper JunOS over BMP | seconds to minutes (depends on station options) |
68 -| BioRIS over RIPE RIS | minutes (full DumpRIB takes a while for large feeds) |
69 -
70 -Until convergence, BGP-derived enrichment is incomplete. Plan restarts during low-traffic windows if BGP attribution matters for your workflow.
71 -
72 -## Provider chain integration
73 -
74 -BGP-derived routes contribute to flow enrichment via the `routing` entry in the [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md) provider chain:
75 -
76 -```yaml
77 -enrichment:
78 - asn_providers: [flow, routing, geoip] # default
79 - net_providers: [flow, routing] # default
80 -```
81 -
82 -With the defaults, an exporter-supplied AS number wins over BGP. To prefer BGP over the exporter (useful when your BMP/BioRIS feed is more accurate than the exporter's view), reorder:
83 -
84 -```yaml
85 -enrichment:
86 - asn_providers: [routing, flow, geoip]
87 -```
88 -
89 -`bmp` is accepted as an alias for `routing` in the provider list, for backward compatibility.
90 -
91 -## Integration test gap
92 -
93 -The runtime path of both BMP and BioRIS — TCP listener / gRPC client, framed decode loop, trie apply, per-router cleanup — is **not** integration-tested in this repository. The parsing layers (BMP message parsing, gRPC proto conversion) are well-unit-tested, but end-to-end against real router firmware or real bio-rd daemons is not exercised.
94 -
95 -Implications:
96 -
97 -- The features ship because the parsing is solid and the runtime is built on standard tokio + netgauze + tonic primitives.
98 -- Vendor compatibility (Cisco IOS-XR / IOS-XE, Juniper JunOS, Arista EOS, FRR) is not validated by tests in this repository.
99 -- Treat configuration changes as production-impacting. Validate against your specific gear before relying on BGP-derived data for capacity or security decisions.
100 -
101 -## What can go wrong
102 -
103 -- **No connections forming (BMP).** Routers initiate BMP sessions to the plugin. Check the router side (`show bmp` / `show bmp connections` / `show bmp targets`). The plugin doesn't proactively retry; it waits.
104 -- **gRPC deadline exceeded (BioRIS).** Default timeout 200 ms is aggressive over the public internet. Raise to 2-5 s.
105 -- **Memory growth without bound.** A full BGP feed is permanent (no eviction). Plan capacity.
106 -- **Plugin restart wipes the trie.** Re-converge takes seconds (FRR) to minutes (IOS-XR). Schedule restarts off-peak.
107 -- **AS path inconsistent with the exporter's view.** Different vantage points see different paths. This is normal in BGP. If your exporter and your BMP-feeding router are different boxes with different routing tables, expect divergence.
108 -- **Empty BGP data after enabling.** Check the per-provider integration card for the specific protocol's setup gotchas — e.g., FRR requires `-M bmp` in `/etc/frr/daemons` (otherwise every BMP command silently fails).
109 -
110 -## What's next
111 -
112 -- **BMP** integration card — how to enable the listener, configure routers (Cisco, Juniper, Arista, FRR).
113 -- **bio-rd / RIPE RIS** integration card — how to set up bio-rd, configure the gRPC client.
114 -- [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md) — How BGP plugs into the provider chain.
115 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Per-prefix overrides that win over BGP.
docs/network-flows/enrichment/classifiers.md deleted
-182
@@ -1,182 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/classifiers.md"
3 -sidebar_label: "Classifiers"
4 -learn_status: "Published"
5 -learn_rel_path: "Network Flows/Enrichment"
6 -keywords: ['classifiers', 'akvorado', 'enrichment', 'rules', 'expression']
7 -endmeta-->
8 -
9 -# Classifiers
10 -
11 -Classifiers tag exporters and interfaces using small expression-based rules. Where [static metadata](/docs/network-flows/enrichment/static-metadata.md) requires you to enumerate every exporter and every ifIndex, classifiers let you write a few rules that match many cases — by name pattern, by IP, by SNMP description, by speed, and so on.
12 -
13 -The plugin's classifier language is **Akvorado-compatible** for the documented operators and actions. It is implemented as a hand-written expression parser in Rust, not jq/jaq, and supports a subset of Akvorado's full expression language. If you've written Akvorado classifiers before, your rules will likely work; if you've written `expr-lang` rules with arithmetic, ternaries, or lambdas, those features are not available here.
14 -
15 -## Two classifier lists
16 -
17 -| Block | Runs | Sees |
18 -|---|---|---|
19 -| `enrichment.exporter_classifiers` | Once per exporter (cached) | Exporter IP and name, current classification fields |
20 -| `enrichment.interface_classifiers` | Once per (exporter, interface) pair, twice per flow (in + out) | Exporter fields, plus interface index/name/description/speed/VLAN, plus current classification |
21 -
22 -Rules are evaluated in YAML order. The plugin short-circuits the list when all classification slots are filled.
23 -
24 -## What a rule can read
25 -
26 -Identifiers available to **exporter classifiers**:
27 -
28 -- `Exporter.IP` — the exporter's IP, as a string
29 -- `Exporter.Name` — the exporter's friendly name (from static metadata, or falls back to the IP)
30 -- `CurrentClassification.Group`, `.Role`, `.Site`, `.Region`, `.Tenant` — values already set (by static metadata, or by an earlier rule)
31 -
32 -Identifiers available to **interface classifiers**:
33 -
34 -- All of the above
35 -- `Interface.Index` — the SNMP ifIndex (integer)
36 -- `Interface.Name`, `Interface.Description` — from static metadata
37 -- `Interface.Speed` — in bits per second
38 -- `Interface.VLAN` — from the flow record's `SRC_VLAN` / `DST_VLAN` (depending on direction)
39 -- `CurrentClassification.Connectivity`, `.Provider`, `.Boundary`, `.Name`, `.Description` — already set
40 -
41 -The plugin does NOT poll SNMP itself, so `Interface.Name` / `Description` / `Speed` come only from what you've configured under `metadata_static`. If you haven't configured them, those identifiers will be empty.
42 -
43 -## What a rule can do
44 -
45 -### Set classification fields
46 -
47 -| Action | Result |
48 -|---|---|
49 -| `Classify("v")` or `ClassifyGroup("v")` | Set `EXPORTER_GROUP` |
50 -| `ClassifyRole("v")` | Set `EXPORTER_ROLE` |
51 -| `ClassifySite("v")` | Set `EXPORTER_SITE` |
52 -| `ClassifyRegion("v")` | Set `EXPORTER_REGION` |
53 -| `ClassifyTenant("v")` | Set `EXPORTER_TENANT` |
54 -| `ClassifyProvider("v")` | Set `IN_IF_PROVIDER` / `OUT_IF_PROVIDER` |
55 -| `ClassifyConnectivity("v")` | Set `IN_IF_CONNECTIVITY` / `OUT_IF_CONNECTIVITY` |
56 -| `ClassifyExternal()` / `ClassifyInternal()` | Set `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` |
57 -| `SetName("v")` | Set `IN_IF_NAME` / `OUT_IF_NAME` (or exporter name when in an exporter rule) |
58 -| `SetDescription("v")` | Set `IN_IF_DESCRIPTION` / `OUT_IF_DESCRIPTION` |
59 -
60 -`Classify*Regex(input, pattern, template)` variants exist for every action above. The pattern is a Rust regex; the template uses `$1`, `$2`, `${name}` capture references.
61 -
62 -### Drop the flow
63 -
64 -`Reject()` discards the flow record. Always guard it behind a condition — at top level it drops everything.
65 -
66 -### Format strings
67 -
68 -`Format("...", arg1, arg2)` mimics Go's `fmt.Sprintf` for `%s`, `%v`, `%d`, `%%`. Use it to build values from multiple inputs:
69 -
70 -```
71 -ClassifyTenant(Format("tenant-%s", Exporter.Name))
72 -```
73 -
74 -## What rules can match against
75 -
76 -Operators (highest to lowest precedence):
77 -
78 -| Form | Meaning |
79 -|---|---|
80 -| `value == X`, `value != X` | equality / inequality |
81 -| `value > X`, `value >= X`, `value < X`, `value <= X` | numeric or lexicographic comparison |
82 -| `value in [a, b, c]` | membership |
83 -| `value contains "x"` | substring (string only) |
84 -| `value startsWith "x"`, `value endsWith "x"` | prefix / suffix (string only) |
85 -| `value matches "pattern"` | regex match (Rust regex) |
86 -| `cond1 && cond2`, `cond1 and cond2` | logical AND |
87 -| `cond1 \|\| cond2`, `cond1 or cond2` | logical OR |
88 -| `!cond`, `not cond` | negation |
89 -| `(cond)` | grouping |
90 -
91 -Whitespace and newlines are ignored, so multi-line rules work. Strings are JSON-quoted.
92 -
93 -## Important behavioural rules
94 -
95 -### First write wins
96 -
97 -Each classification slot is single-write. Once a rule sets `EXPORTER_GROUP`, no subsequent rule can change it. Order rules from most-specific to least-specific.
98 -
99 -### Static metadata overrides classifiers entirely
100 -
101 -If `metadata_static.exporters` set **any** exporter classification field for this exporter, **none of the exporter classifiers run**. Same for interfaces: if static metadata set any of provider, connectivity, or boundary for an interface, the interface classifiers do not run for that interface.
102 -
103 -This is "Akvorado parity" behaviour — operator-provided classification has priority. Don't try to mix them on the same target.
104 -
105 -### `Classify*` value normalisation
106 -
107 -The string passed to `Classify*` actions is **lowercased and stripped to ASCII alphanumerics + `. + -`**. So `ClassifyRegion("EU West")` becomes `euwest`. If you want to preserve casing or whitespace, use `SetName` or `SetDescription` instead.
108 -
109 -### Runtime errors stop the rule list
110 -
111 -If a rule throws (e.g., comparing a string with `>`), the plugin stops evaluating further rules in that list and keeps whatever was set so far. Use `matches`, `startsWith`, or `contains` instead of `>`/`<` on string fields to avoid this.
112 -
113 -### Cache key includes resolved values
114 -
115 -The interface classifier cache keys by `(exporter, exporter classification, interface)`. When the exporter's classification changes — for example, after you push new static metadata and restart — interface caches naturally invalidate.
116 -
117 -The cache TTL is `classifier_cache_duration`, default 5 minutes (`enrichment.classifier_cache_duration`). It's a last-access TTL — entries live as long as they're queried.
118 -
119 -## Rule examples
120 -
121 -### Exporter classifiers
122 -
123 -```yaml
124 -enrichment:
125 - exporter_classifiers:
126 - # Group exporters by name pattern.
127 - - 'Exporter.Name matches "^edge-.*" && Classify("edge")'
128 - - 'Exporter.Name matches "^core-.*" && Classify("core")'
129 -
130 - # Site by IP prefix.
131 - - 'Exporter.IP startsWith "10.1." && ClassifySite("ny-dc1")'
132 - - 'Exporter.IP startsWith "10.2." && ClassifySite("par-dc1")'
133 -
134 - # Tenant computed from name.
135 - - 'ClassifyTenant(Format("tenant-%s", Exporter.Name))'
136 -
137 - # Pull a token out of the name with a regex.
138 - - 'ClassifyRegionRegex(Exporter.Name, "-([a-z]{2})-[0-9]+$", "$1")'
139 -
140 - # Drop traffic from a test exporter.
141 - - 'Exporter.IP startsWith "192.0.2." && Reject()'
142 -
143 - classifier_cache_duration: 5m
144 -```
145 -
146 -### Interface classifiers
147 -
148 -```yaml
149 -enrichment:
150 - interface_classifiers:
151 - # Provider from a description prefix.
152 - - 'Interface.Description startsWith "BACKBONE-LUMEN" && ClassifyProvider("Lumen")'
153 - - 'Interface.Description startsWith "BACKBONE-COGENT" && ClassifyProvider("Cogent")'
154 -
155 - # Mark transit links by description keyword and tag them external.
156 - - 'Interface.Description contains "TRANSIT" && ClassifyConnectivity("transit") && ClassifyExternal()'
157 -
158 - # Anything matching the IX peering pattern.
159 - - 'Interface.Description matches "(?i)^(IX|peering)-.*" && ClassifyConnectivity("peering") && ClassifyExternal()'
160 -
161 - # 100 Gbps interfaces are core uplinks.
162 - - 'Interface.Speed >= 100000000000 && ClassifyConnectivity("core")'
163 -
164 - # Use exporter classification to scope interface rules.
165 - - 'CurrentClassification.Role == "edge" && ClassifyExternal()'
166 -```
167 -
168 -## What can go wrong
169 -
170 -- **A rule fails to parse and the plugin won't start.** Look at the journal — the error message includes the index and a parser context.
171 -- **Classifiers aren't running on an exporter.** Likely cause: static metadata already set a classification field for that exporter, which suppresses all classifier rules for it.
172 -- **A rule sets a value but it appears differently in the dashboard.** `Classify*` actions normalise (lowercase + strip non-alphanumeric). Use `SetName` for human-readable values.
173 -- **The first rule in the list always wins.** First-write-wins per slot. Order rules from most-specific to least-specific.
174 -- **A rule that worked at startup stops matching later.** Cached results expire after `classifier_cache_duration`. If you change rules, restart the plugin so the cache clears completely.
175 -- **Comparison error stops processing.** Comparing a string with `>` throws — subsequent rules in the list are skipped. Use string-safe operators.
176 -- **`ClassifyExternal` doesn't fire on the egress side.** Interface classifiers run twice — once for the input interface, once for the output. Both invocations see the same classifier list. If your rule sets `ClassifyExternal()` on a specific ifIndex, it applies whether that ifIndex is `IN_IF` or `OUT_IF`.
177 -
178 -## What's next
179 -
180 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Declarative labelling that runs before classifiers.
181 -- [GeoIP](/docs/network-flows/enrichment/ip-intelligence.md) — Country / city / AS-name labelling.
182 -- [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md) — How `SRC_AS` / `DST_AS` get filled in.
docs/network-flows/enrichment/decapsulation.md deleted
-126
@@ -1,126 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/decapsulation.md"
3 -sidebar_label: "Decapsulation"
4 -learn_status: "Published"
5 -learn_rel_path: "Network Flows/Enrichment"
6 -keywords: ['decapsulation', 'srv6', 'vxlan', 'tunnel', 'overlay']
7 -endmeta-->
8 -
9 -# Decapsulation
10 -
11 -Decapsulation extracts the inner packet from tunnelled traffic so the dashboard reflects the actual endpoints, not the tunnel endpoints. Two modes are supported: **SRv6** and **VXLAN**.
12 -
13 -This is useful when your routers are observing overlay traffic — VXLAN-encapsulated VM traffic between hypervisors, SRv6-encapsulated data-centre fabric, etc. Without decap, you see the same "10.0.0.x → 10.0.0.y" flow for every VM-to-VM conversation, which tells you nothing.
14 -
15 -## Modes
16 -
17 -```yaml
18 -protocols:
19 - decapsulation_mode: vxlan # one of: none, srv6, vxlan
20 -```
21 -
22 -| Mode | What it strips | What it surfaces |
23 -|---|---|---|
24 -| `none` (default) | nothing | the outer-header view |
25 -| `srv6` | IPv6 outer + extension headers + Routing Header type 4 (SRH) | the inner IPv4 (next-header 4) or IPv6 (next-header 41) packet |
26 -| `vxlan` | outer Ethernet/IP + UDP (port 4789) + 8-byte VXLAN header | the inner Ethernet frame, then the inner L3/L4 |
27 -
28 -GRE, IP-in-IP, GENEVE, and other tunnel types are **not** supported. Only SRv6 and VXLAN.
29 -
30 -## How the modes interact with each protocol
31 -
32 -Decap relies on the exporter shipping inner-packet bytes. That happens in three different ways depending on the source protocol:
33 -
34 -| Source | Inner-packet bytes carried as | Required exporter capability |
35 -|---|---|---|
36 -| **NetFlow v9** | Information Element 104 (`Layer2packetSectionData`, RFC 7270) | Exporter must include IE 104 in the template; it carries the captured frame bytes |
37 -| **IPFIX** | Information Element 315 (`dataLinkFrameSection`, RFC 7133) | Same idea, IPFIX-standard IE |
38 -| **sFlow** | Always — `SampledHeader` records carry the truncated raw packet | sFlow agents send `SampledHeader` by default for header-sampling mode |
39 -
40 -For **NetFlow v9 / IPFIX without IE 104 / 315 in the template, decapsulation does not run** — even with `decapsulation_mode: vxlan` set. Standard flow records pass through unchanged. So enabling decap on the plugin is half the work; you also have to configure your exporter to ship the frame bytes.
41 -
42 -For **sFlow, decap always runs** when the mode is set, because every flow sample carries a `SampledHeader`.
43 -
44 -## What gets surfaced
45 -
46 -When decap succeeds, the inner 5-tuple replaces the outer one in the flow record:
47 -
48 -- `SRC_ADDR` / `DST_ADDR` — inner source/destination IPs
49 -- `SRC_PORT` / `DST_PORT` — inner ports
50 -- `PROTOCOL` — inner L4 protocol
51 -- `ETYPE` — inner EtherType
52 -- `IPTOS`, `IPTTL`, `IPV6_FLOW_LABEL`, `TCP_FLAGS` — inner IP/TCP fields
53 -- `IP_FRAGMENT_ID`, `IP_FRAGMENT_OFFSET` — inner fragmentation
54 -- `ICMPV4_TYPE` / `ICMPV4_CODE` / `ICMPV6_TYPE` / `ICMPV6_CODE` — inner ICMP
55 -- `MPLS_LABELS` — inner MPLS label stack (if present)
56 -- `BYTES` — inner L3 length (so byte counts represent inner traffic, not outer overhead)
57 -
58 -For VXLAN, the inner Ethernet frame is parsed, so `SRC_MAC` / `DST_MAC` / `SRC_VLAN` / `DST_VLAN` come from the inner frame. **The outer MACs and VLANs are lost.**
59 -
60 -For SRv6, the outer is an IPv6 packet (no L2 to lose).
61 -
62 -The **VXLAN VNI is dropped**. Netdata does not surface it. If you need to distinguish overlay segments, you need a different mechanism — VLAN-tagged inner frames work, but pure VNI-based segmentation isn't visible.
63 -
64 -## Decapsulation is destructive on non-tunnel traffic
65 -
66 -When `decapsulation_mode` is set and the exporter ships records via the special L2-section path (NetFlow v9 IE 104 / IPFIX IE 315 / sFlow `SampledHeader`), but the inner packet doesn't match the configured tunnel:
67 -
68 -- For VXLAN mode: a non-VXLAN packet (different UDP port, malformed VXLAN header, or not UDP at all) is **dropped**. The flow does NOT fall back to outer-header view.
69 -- For SRv6 mode: an IPv6 packet without the right extension-header chain leading to next-header 4 or 41 is **dropped**.
70 -- For sFlow with decap on, only `SampledHeader` records are processed. `SampledIPv4`, `SampledIPv6`, `SampledEthernet`, `ExtendedSwitch`, `ExtendedRouter`, `ExtendedGateway` records are all skipped.
71 -
72 -Plain NetFlow / IPFIX flow records that don't go through the special L2-section path are **unaffected** — they pass through normally regardless of the decap setting. So enabling `decapsulation_mode: vxlan` doesn't break your normal flow stream; it only filters the L2-section path.
73 -
74 -This means decapsulation is safe to enable when:
75 -
76 -- All your tunnel-bearing exporters use the same encapsulation, AND
77 -- The L2-section / `SampledHeader` data they ship is exclusively (or near-exclusively) tunnel traffic.
78 -
79 -If you mix VXLAN and SRv6 traffic on the same exporter, you cannot decap both — the plugin has one global setting.
80 -
81 -## Configuring exporters to ship inner-packet bytes
82 -
83 -For decap to work, your exporter must include the inner-packet bytes in its export. This is platform-specific. The CLI snippets below are starting points — verify against the vendor's reference manual before deploying.
84 -
85 -### Cisco IOS-XE / IOS-XR (NetFlow v9 with `datalink mac`)
86 -
87 -```
88 -flow record FNF-WITH-MAC
89 - match ipv4 source address
90 - match ipv4 destination address
91 - match transport source-port
92 - match transport destination-port
93 - match ipv4 protocol
94 - match datalink mac source address input
95 - match datalink mac destination address input
96 - collect counter bytes
97 - collect counter packets
98 - collect timestamp absolute first
99 - collect timestamp absolute last
100 - collect datalink frame-section section header size 128
101 -```
102 -
103 -The `collect datalink frame-section` directive is what causes the exporter to include IE 104. Adjust the section size based on your maximum tunnel header size; 128 bytes covers VXLAN over Ethernet over IPv4. SRv6 inner extraction needs more — 256 or higher.
104 -
105 -### Juniper JunOS (IPFIX with frame export)
106 -
107 -JunOS' IPFIX support varies by platform. On platforms that support frame-section export, configure the template to include `dataLinkFrameSection` (IE 315). Refer to your platform's documentation.
108 -
109 -### sFlow (built-in)
110 -
111 -sFlow agents send `SampledHeader` by default. No special configuration needed beyond enabling sFlow.
112 -
113 -## Failure modes
114 -
115 -- **Exporter doesn't ship IE 104 / IE 315.** The plugin can't decap. Records pass through with outer-header view.
116 -- **Inner packet isn't VXLAN/SRv6.** With decap on, the flow is dropped. There is no "fall back to outer view" — this is intentional, but be aware.
117 -- **Truncated frame section.** The inner Ethernet/IP/L4 parsing fails and the flow is dropped.
118 -- **VXLAN on a non-standard port.** The plugin only matches UDP destination port 4789 (RFC 7348). VXLAN-GPE on 4790 and vendor-custom ports are not detected.
119 -- **VNI not visible.** Bytes 4-6 of the VXLAN header are skipped. If you need VNI-based segmentation, see if your exporter can place the VNI in a separate field; otherwise this isn't surfaceable today.
120 -
121 -## What's next
122 -
123 -- [Configuration](/docs/network-flows/configuration.md) — `protocols.decapsulation_mode` setting reference.
124 -- [Sources / NetFlow](/src/crates/netflow-plugin/integrations/netflow.md) — IE 104 export configuration.
125 -- [Sources / IPFIX](/src/crates/netflow-plugin/integrations/ipfix.md) — IE 315 export configuration.
126 -- [Sources / sFlow](/src/crates/netflow-plugin/integrations/sflow.md) — `SampledHeader` semantics.
docs/network-flows/enrichment/ip-intelligence.md
docs/network-flows/enrichment/network-identity.md deleted
-134
@@ -1,134 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/network-identity.md"
3 -sidebar_label: "Network Identity"
4 -learn_status: "Published"
5 -learn_rel_path: "Network Flows/Enrichment Concepts"
6 -keywords: ['network identity', 'network sources', 'ipam', 'cmdb', 'cloud ip ranges', 'enrichment', 'concept']
7 -endmeta-->
8 -
9 -# Network Identity
10 -
11 -Network-identity enrichment labels your own network prefixes with names, roles, sites, regions, tenants, and country / city overrides. Where [IP intelligence](/docs/network-flows/enrichment/ip-intelligence.md) tells you "this IP is in Germany" from a public database, network-identity tells you "this prefix is our staging environment in Frankfurt" from your authoritative source.
12 -
13 -The data comes from external feeds — cloud-provider published prefix lists (AWS, GCP, Azure), IPAM systems (NetBox, Infoblox, BlueCat, phpIPAM), and custom CMDBs. Each source is configured as a separate integration card. This page covers the **cross-cutting concept**: how the lookups combine, what fields can be set, the operational rules.
14 -
15 -## What it populates
16 -
17 -| Field | Notes |
18 -|---|---|
19 -| `SRC_NET_NAME` / `DST_NET_NAME` | Friendly name |
20 -| `SRC_NET_ROLE` / `DST_NET_ROLE` | Role tag (e.g., `dmz`, `office`, `iot`) |
21 -| `SRC_NET_SITE` / `DST_NET_SITE` | Physical site |
22 -| `SRC_NET_REGION` / `DST_NET_REGION` | Region |
23 -| `SRC_NET_TENANT` / `DST_NET_TENANT` | Tenant |
24 -| `SRC_COUNTRY` / `DST_COUNTRY` | Country override (when set explicitly) |
25 -| `SRC_GEO_STATE` / `DST_GEO_STATE` | State / province override |
26 -| `SRC_GEO_CITY` / `DST_GEO_CITY` | City override |
27 -
28 -What network-identity sources cannot set: `SRC_GEO_LATITUDE` / `DST_GEO_LATITUDE`, `SRC_GEO_LONGITUDE` / `DST_GEO_LONGITUDE`. Coordinates are static-only — use the [`networks` block in static metadata](/docs/network-flows/enrichment/static-metadata.md) for those.
29 -
30 -The per-row `asn` field can also override the AS *number* via the resolution chain. The AS *name* still comes from the [ASN MMDB](/docs/network-flows/enrichment/ip-intelligence.md) — there is no `asn_name` override in network-identity sources.
31 -
32 -## Lookup priority
33 -
34 -In the network-attributes resolution merge order:
35 -
36 -1. **GeoIP** seeds the base layer.
37 -2. **Network-identity sources** (cloud IP ranges, IPAM, generic IPAM) merge on top — at each prefix length (least-specific to most-specific).
38 -3. **Static `networks` config** merges last — at each prefix length, **after** network-identity sources.
39 -
40 -So when a prefix is defined in both a remote source and the static config, the static config wins on any non-empty field. This is intentional: explicit operator configuration overrides imported data.
41 -
42 -## How a fetch works
43 -
44 -For each configured source:
45 -
46 -1. The plugin issues an HTTP request (default GET, or POST if configured) at the `interval` cadence.
47 -2. Headers configured under `headers:` are added (typically for authentication).
48 -3. The response body is parsed as JSON.
49 -4. The configured `transform` (a [jaq](https://github.com/01mf02/jaq) jq-equivalent expression) runs over the parsed JSON.
50 -5. The transform must produce a stream of objects, each with a `prefix` field (a CIDR string) and any of the optional attribute fields.
51 -6. The records are merged into the network-attributes trie.
52 -
53 -Each source runs in its own task. Multiple sources fetch in parallel; within a source, only one fetch is in flight at a time.
54 -
55 -On any failure (HTTP error, JSON parse error, jq runtime error, empty result), the source backs off exponentially (starting at `interval / 10`, doubling up to `interval`) and retries. On success it resets to the configured `interval`.
56 -
57 -## The expected jq output shape
58 -
59 -The `transform` is a jq expression compiled by jaq. It receives the entire parsed JSON body and must produce a **stream of objects**.
60 -
61 -Each output object should look like:
62 -
63 -```json
64 -{
65 - "prefix": "10.0.0.0/8",
66 - "name": "internal",
67 - "role": "lan",
68 - "site": "fra1",
69 - "region": "eu-central",
70 - "country": "DE",
71 - "state": "HE",
72 - "city": "Frankfurt",
73 - "tenant": "tenant-a",
74 - "asn": 64500,
75 - "asn_name": "Internal AS"
76 -}
77 -```
78 -
79 -Required: `prefix`. All other fields are optional and default to empty / 0.
80 -
81 -The `asn` field accepts an integer (`64500`), a string (`"64500"`), or the AS notation (`"AS64500"`).
82 -
83 -If the transform produces nothing (empty result), the cycle is treated as a failure and triggers backoff. The same applies to non-object rows — every output element must be an object.
84 -
85 -## TLS verification cannot be disabled
86 -
87 -The configuration accepts the legacy keys `tls.verify` and `tls.skip_verify` for compatibility, but the validation layer **rejects** any attempt to disable verification (`tls.verify: false` or `tls.skip_verify: true`). Self-signed or internal CAs must be supplied via `tls.ca_file`. There is no override.
88 -
89 -This is deliberate. Network-identity data flows directly into enrichment that affects security investigations and capacity decisions — silently accepting MITM-able responses would corrupt every downstream analysis.
90 -
91 -## Single page only
92 -
93 -The fetch is one-shot per cycle. There is no pagination, no cursor handling, no `Link: rel=next` following. If your IPAM exposes paginated endpoints, either:
94 -
95 -- Expose a separate "all prefixes" bulk endpoint (most IPAMs have one).
96 -- Wrap with a server-side script that aggregates all pages and serves the result at one URL.
97 -
98 -## Authentication
99 -
100 -The plugin has no built-in OAuth flow, basic-auth helpers, or token refresh. Set whatever the API needs explicitly:
101 -
102 -```yaml
103 -headers:
104 - Authorization: "Token abc123"
105 -```
106 -
107 -If your endpoint needs short-lived tokens, refresh them outside Netdata and put the current valid token in the headers config (and reload).
108 -
109 -## Available sources
110 -
111 -Each is configured as a separate integration card. See the per-source card for setup details:
112 -
113 -- **AWS IP Ranges** — public AWS prefix list with per-region and per-service tagging
114 -- **GCP IP Ranges** — public GCP prefix list with per-scope and per-service tagging
115 -- **Azure IP Ranges** — published per Azure Service Tags (requires an internal mirror because Azure's URL rotates weekly)
116 -- **NetBox** — open-source IPAM / DCIM, REST API with bearer-token auth
117 -- **Generic JSON-over-HTTP IPAM** — catch-all for Infoblox, BlueCat, phpIPAM, custom CMDBs
118 -
119 -## What can go wrong
120 -
121 -- **Endpoint is paginated.** Only the first page is fetched. Use a bulk endpoint or wrap with a server-side script.
122 -- **Default interval is 60s.** Fast for an IPAM, slow for AWS/GCP ranges. Tune per source — daily is fine for cloud IP ranges, 5-15 minutes for IPAMs that change often.
123 -- **TLS verify cannot be disabled.** Use `tls.ca_file` for internal CAs.
124 -- **Empty result from the transform** is treated as failure. If your endpoint returns no prefixes (legitimate state for a quiet IPAM), the source backs off as if it errored. Workaround: have the upstream return at least one synthetic prefix.
125 -- **Authorization header must be in `headers:`**, not in the URL. URLs with embedded credentials (`https://user:pass@host`) are not specially handled.
126 -- **JSON parse errors are silent in the dashboard.** Watch the Netdata journal (`journalctl -u netdata | grep network_sources`) for warnings.
127 -- **Static config silently wins ties.** When a prefix is defined in both a remote source and `networks:`, the static config's values overwrite the remote ones. This is by design but can surprise operators expecting the remote feed to be authoritative.
128 -
129 -## What's next
130 -
131 -- **AWS IP Ranges, GCP IP Ranges, Azure IP Ranges, NetBox, Generic JSON-over-HTTP IPAM** — per-source integration cards with concrete setup instructions and example jq transforms.
132 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Static `networks` block (overrides network-identity at the same prefix length).
133 -- [IP Intelligence](/docs/network-flows/enrichment/ip-intelligence.md) — The base layer that network-identity merges on top of.
134 -- [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md) — How the per-row `asn` field plugs in.
docs/network-flows/enrichment/static-metadata.md deleted
-227
@@ -1,227 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/enrichment/static-metadata.md"
3 -sidebar_label: "Static Metadata"
4 -learn_status: "Published"
5 -learn_rel_path: "Network Flows/Enrichment"
6 -keywords: ['static metadata', 'enrichment', 'networks', 'exporters', 'interface']
7 -endmeta-->
8 -
9 -# Static metadata
10 -
11 -Static metadata is the foundational enrichment for any multi-exporter deployment. It lets you give your routers, your switches, your interfaces, and your own networks the names and labels you want to see on the dashboard — instead of raw IP addresses and SNMP indexes.
12 -
13 -There are two independent configuration blocks. They populate different fields and use different lookup keys, but you typically configure both:
14 -
15 -| Block | Lookup key | What it labels |
16 -|---|---|---|
17 -| `enrichment.metadata_static.exporters` | exporter IP / CIDR + ifIndex | The exporter device and its individual interfaces |
18 -| `enrichment.networks` | source / destination IP | Your own networks (CIDRs you operate) |
19 -
20 -## What it populates
21 -
22 -### From `metadata_static.exporters`
23 -
24 -Per-exporter (matched by source IP of the UDP datagram):
25 -
26 -- `EXPORTER_NAME`, `EXPORTER_GROUP`, `EXPORTER_ROLE`, `EXPORTER_SITE`, `EXPORTER_REGION`, `EXPORTER_TENANT`
27 -
28 -Per-interface (matched by ifIndex from the flow record):
29 -
30 -- `IN_IF_NAME` / `OUT_IF_NAME`
31 -- `IN_IF_DESCRIPTION` / `OUT_IF_DESCRIPTION`
32 -- `IN_IF_SPEED` / `OUT_IF_SPEED` (in **bits per second**)
33 -- `IN_IF_PROVIDER` / `OUT_IF_PROVIDER`
34 -- `IN_IF_CONNECTIVITY` / `OUT_IF_CONNECTIVITY`
35 -- `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` (`1` = external, `2` = internal)
36 -
37 -### From `networks`
38 -
39 -Per source/destination IP (matched by CIDR):
40 -
41 -- `SRC_NET_NAME` / `DST_NET_NAME`
42 -- `SRC_NET_ROLE` / `DST_NET_ROLE`
43 -- `SRC_NET_SITE` / `DST_NET_SITE`
44 -- `SRC_NET_REGION` / `DST_NET_REGION`
45 -- `SRC_NET_TENANT` / `DST_NET_TENANT`
46 -- `SRC_COUNTRY` / `DST_COUNTRY`, `SRC_GEO_STATE` / `DST_GEO_STATE`, `SRC_GEO_CITY` / `DST_GEO_CITY` — overrides for the GeoIP-derived fields
47 -- `SRC_GEO_LATITUDE` / `DST_GEO_LATITUDE`, `SRC_GEO_LONGITUDE` / `DST_GEO_LONGITUDE` — overrides for the coordinate fields
48 -- `SRC_AS_NAME` / `DST_AS_NAME` — only when the configured `asn` causes the chain to render `AS{n}` and the MMDB has a matching name
49 -
50 -The `networks` block can also override the AS **number** via the `asn` field, for matching prefixes. AS **names** still come from the ASN database — see [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md).
51 -
52 -## Configuration
53 -
54 -### Naming exporters and interfaces
55 -
56 -```yaml
57 -enrichment:
58 - metadata_static:
59 - exporters:
60 - 192.0.2.10: # single IP (treated as /32)
61 - name: edge-router-1
62 - site: par1
63 - region: eu-west
64 - role: edge
65 - tenant: tenant-a
66 - default: # template applied to interfaces not in if_indexes
67 - description: unclassified port
68 - if_indexes:
69 - 1:
70 - name: Gi0/0/1
71 - description: uplink to ISP-A
72 - speed: 10000000000 # 10 Gbps in bits per second
73 - provider: isp-a
74 - connectivity: transit
75 - boundary: external
76 - 2:
77 - name: Gi0/0/2
78 - description: LAN core
79 - speed: 1000000000
80 - connectivity: lan
81 - boundary: internal
82 -```
83 -
84 -The `if_indexes` map keys by the integer ifIndex the router sends in flow records. If a flow arrives with an ifIndex not present in the map, the `default` interface block is used. The `skip_missing_interfaces: true` option overrides this — when set, missing entries get no interface labels at all.
85 -
86 -### Matching multiple exporters with one block
87 -
88 -CIDR prefixes work too. Longest-prefix match wins.
89 -
90 -```yaml
91 -enrichment:
92 - metadata_static:
93 - exporters:
94 - 198.51.100.0/24: # all routers in this subnet
95 - site: dc-fra1
96 - region: eu-central
97 - role: spine
98 - default:
99 - connectivity: lan
100 - boundary: internal
101 - 198.51.100.10: # specific override for one IP
102 - name: spine-fra1-a
103 - if_indexes:
104 - 1:
105 - name: 100Ge-0/0/1
106 - description: leaf-uplink
107 - speed: 100000000000
108 - connectivity: transit
109 - boundary: external
110 -```
111 -
112 -### Tagging your own networks
113 -
114 -```yaml
115 -enrichment:
116 - networks:
117 - 10.0.0.0/8:
118 - name: corp-internal
119 - role: internal
120 - tenant: tenant-a
121 - 172.16.0.0/12:
122 - name: corp-internal
123 - role: internal
124 - tenant: tenant-a
125 - 192.168.0.0/16:
126 - name: corp-internal
127 - role: internal
128 - tenant: tenant-a
129 - 198.51.100.0/24: # a public block you operate
130 - name: customer-acme
131 - role: customer
132 - site: par1
133 - country: FR
134 - city: Paris
135 - latitude: 48.8566
136 - longitude: 2.3522
137 - asn: 64500 # forces SRC_AS / DST_AS for traffic in this prefix
138 - 203.0.113.0/24: transit-a # shorthand: name only
139 -```
140 -
141 -Two things to know:
142 -
143 -- The `networks` map merges all containing CIDRs in ascending prefix-length order — least-specific first, with more-specific overrides. A `/24` entry inherits any non-empty fields from a containing `/16` entry, and adds or overwrites its own fields.
144 -- The shorthand form (`203.0.113.0/24: transit-a`) sets only the `name`. All other fields are empty.
145 -
146 -## Lookup priority and pipeline order
147 -
148 -Within an exporter:
149 -
150 -1. **`metadata_static.exporters` longest-prefix match** wins for exporter labels and interface labels.
151 -2. **`if_indexes` lookup** runs against the ifIndex from the flow record, falling back to `default` (or returning empty when `skip_missing_interfaces: true`).
152 -
153 -Within a flow's source/destination IP:
154 -
155 -1. **GeoIP** runs first as the base layer.
156 -2. **`network_sources` (remote feeds)** merge on top.
157 -3. **`networks` (static config)** merges last and wins on any non-empty field.
158 -
159 -The two paths run independently. An exporter IP that also matches a `networks` entry will get **both** treatments — exporter labels for the device, network labels for any traffic to or from that IP.
160 -
161 -## Things to know
162 -
163 -### `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` semantics
164 -
165 -These label **the interface itself**, not the direction of traffic:
166 -
167 -- `1` = external — the port faces the outside world (Internet, peer, transit)
168 -- `2` = internal — the port faces your own infrastructure
169 -- `0` (or omitted) = undefined — the field is removed from the output
170 -
171 -Filtering for `IN_IF_BOUNDARY=1` cleanly gives you "traffic that arrived from outside". The encoding is intentional even if `1` for "external" looks counter-intuitive.
172 -
173 -The values `external` and `internal` are also accepted as strings (case-insensitive) in the YAML.
174 -
175 -### `speed` is in bits per second
176 -
177 -A 1 Gbps interface is `1000000000`, not `1000`. Operators thinking in megabits or gigabits will get the speed wrong by a factor of 1000 to 1 000 000. The plugin treats `speed: 0` as "not set" and removes the field from the output.
178 -
179 -### CIDR prefixes accept single IPs
180 -
181 -`192.0.2.10` and `192.0.2.10/32` are equivalent. Use whichever is clearer.
182 -
183 -### `networks.<cidr>.asn` overrides only the number
184 -
185 -Setting `asn: 64500` overrides whatever the [ASN resolution chain](/docs/network-flows/enrichment/asn-resolution.md) computed. The AS *name* still comes from the ASN database — there is no `asn_name` config field.
186 -
187 -### Coordinates are silently dropped if invalid
188 -
189 -`latitude: 91.5` (out of range) sets the field to an empty string with no error. Same for non-finite values. Validate manually if your data is important.
190 -
191 -### Renamed interfaces don't auto-track
192 -
193 -`if_indexes` keys by the numeric ifIndex. If a router renumbers its interfaces (line-card reseat, stack rebuild), the old ifIndex no longer matches and the per-interface block silently no longer applies. Audit after hardware changes.
194 -
195 -### Static metadata blocks classifiers
196 -
197 -If `metadata_static.exporters` set **any** classification field (group / role / site / region / tenant) for an exporter, the [classifiers](/docs/network-flows/enrichment/classifiers.md) do not run for that exporter at all. The same applies to interfaces: if static metadata set any of provider / connectivity / boundary, the interface classifiers don't run for that interface. Plan accordingly.
198 -
199 -## Sampling rate overrides
200 -
201 -Sampling rates can also be configured per exporter prefix here, in case your exporter doesn't carry the rate or you want to override it:
202 -
203 -```yaml
204 -enrichment:
205 - default_sampling_rate: 1 # global fallback
206 - override_sampling_rate:
207 - 10.1.0.0/16: 1024 # override for this network of exporters
208 -```
209 -
210 -`default_sampling_rate` applies when the flow record doesn't carry a rate and no override matches. `override_sampling_rate` always wins when its prefix matches the exporter IP. Both accept either an integer (uniform rate) or a CIDR-keyed map.
211 -
212 -## What can go wrong
213 -
214 -- **Wrong CIDR matches.** Overlapping ranges merge ascending — a more-specific entry that leaves a field blank will inherit the supernet's value. To clear a field on a more-specific entry, you must set it explicitly to a sentinel value, not leave it blank.
215 -- **Forgotten internal range.** Until you declare your RFC 1918 / RFC 6598 / link-local ranges as `networks` entries, GeoIP can return spurious data for them.
216 -- **Renamed interface no longer matches.** ifIndex keys are numeric; renames or hardware changes break the mapping silently.
217 -- **Stale exporter prefix.** A new device with a different management IP doesn't match an old block. Audit when you replace gear.
218 -- **`speed: 1000` means 1 kbps.** Use bits per second.
219 -- **`boundary: 0` is indistinguishable from "not set"**, both result in field removal. If you want explicit "undefined" use the string `"undefined"`.
220 -- **Lat / lng silent drop.** Invalid values become empty strings. The map quietly stops drawing the marker.
221 -
222 -## What's next
223 -
224 -- [GeoIP](/docs/network-flows/enrichment/ip-intelligence.md) — How country / city / coordinates and AS names get resolved.
225 -- [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md) — The provider chain that picks AS numbers.
226 -- [Classifiers](/docs/network-flows/enrichment/classifiers.md) — Rule-based labelling that runs only when static metadata didn't already classify the exporter or interface.
227 -- [Network sources](/docs/network-flows/enrichment/network-identity.md) — Fetching `networks`-style data from remote endpoints.
docs/network-flows/field-reference.md
+12 -10
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows"
6 keywords: ['fields', 'flow record', 'schema', 'reference']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Field Reference
12
13 Each flow record carries up to 91 fields. Some come straight from the exporter, others are added by enrichment after decode. This page is the canonical list — what each field means, where it comes from, and which protocols populate it.
@@ -30,7 +32,7 @@ The four most-used fields. Read these first.
32 |---|---|---|
33 | `BYTES` | uint64 | Bytes in the flow, **already multiplied by `SAMPLING_RATE`** at ingest. The dashboard's volume numbers come from this. |
34 | `PACKETS` | uint64 | Packets in the flow, already multiplied by `SAMPLING_RATE`. |
33 -| `RAW_BYTES` | uint64 | Bytes the exporter actually reported, before scaling. Use when sampling is uniform across all your exporters and you want exact counts. |
35 +| `RAW_BYTES` | uint64 | Bytes the exporter literally reported, before per-flow sampling multiplication. Use when you want the unscaled value the exporter sent. |
36 | `RAW_PACKETS` | uint64 | Packets the exporter actually reported, before scaling. |
37 | `FLOWS` | uint64 | Number of flows aggregated into this record. Always 1 for raw records. |
38 | `SAMPLING_RATE` | uint64 | Packets-per-sample reported by the exporter. `1` means unsampled. Used as the multiplier for BYTES and PACKETS. |
@@ -135,7 +137,7 @@ Static-network configuration can override `SRC_MASK` / `DST_MASK` and `SRC_AS` /
137 |---|---|---|
138 | `FLOW_START_USEC` | uint64 | Microseconds since epoch. From v5/v7 first-switched + sysUptime; from v9 first-switched normalised against system init time; from IPFIX `flowStartMicroseconds` family. Not populated for sFlow. |
139 | `FLOW_END_USEC` | uint64 | Microseconds since epoch. Same sources. Not populated for sFlow. |
138 -| `OBSERVATION_TIME_MILLIS` | uint64 | IPFIX observation time (`observationTimeMilliseconds`). |
140 +| `OBSERVATION_TIME_MILLIS` | uint64 | NetFlow v9 observation time (`ObservationTimeMilliseconds`, IE 323). IPFIX observation-time fields are not exposed. |
141
142 ## Geolocation (enrichment-only)
143
@@ -152,7 +154,7 @@ Static-network configuration can override `SRC_MASK` / `DST_MASK` and `SRC_AS` /
154 | `SRC_GEO_LONGITUDE` | string | Decimal longitude. |
155 | `DST_GEO_LONGITUDE` | string | Decimal longitude. |
156
155 -City, latitude, and longitude are **not preserved in the rollup tiers** (1m, 5m, 1h). Aggregating on them forces the query to tier 0 (raw). Country and state survive into rollups.
157 +City, latitude, and longitude are **not preserved in the rollup tiers** (1m, 5m, 1h). Aggregating on them forces the query to raw tier. Country and state survive into rollups.
158
159 ## Network labels (enrichment-only)
160
@@ -202,14 +204,14 @@ The mapped IEs cover the standard set: identity (8/12/27/28, 7/11), counters (1/
204
205 Vendor enterprise IEs are recognised only for one Juniper case (PEN 2636 `commonPropertiesId`) used to surface forwarding status. Cisco AVC, Cisco NEL/NSEL NAT events, and similar vendor-private fields are parsed (so the decoder doesn't fail) but their values are not exposed in flow records.
206
205 -If you need a specific IE mapped, open an issue with sample fixtures.
207 +If you need a specific IE mapped, open an issue with a sanitized sample export.
208
209 ## Filtering and aggregation hints
210
211 Some fields are queryable but not aggregatable:
212
213 - `BYTES`, `PACKETS`, `FLOWS`, `RAW_BYTES`, `RAW_PACKETS`, `SAMPLING_RATE` — these are sums in tables and sankeys; you cannot filter or group-by them.
212 -- `FLOW_START_USEC`, `FLOW_END_USEC`, `OBSERVATION_TIME_MILLIS` — timestamps, used by the time-range picker; not used as facets.
214 +- `FLOW_START_USEC`, `FLOW_END_USEC`, `OBSERVATION_TIME_MILLIS` — timestamps stored on raw records; not used by the dashboard time picker and not used as facets.
215 - The four geo-coordinate fields (`SRC_GEO_LATITUDE/LONGITUDE`, `DST_GEO_LATITUDE/LONGITUDE`) are stored but hidden in the table by default and not exposed as facets.
216
217 The dashboard also exposes two **virtual facets** that don't exist in the canonical schema:
@@ -221,7 +223,7 @@ Filtering on either of these virtual fields runs against the underlying `*_TYPE`
223
224 ## A note on field counts
225
224 -You may see "89 fields" or "91 fields" in different parts of the codebase. The current canonical list has **91 entries**. The schema has grown over time and not every reference has caught up. The list above is exhaustive for the current release.
226 +This reference lists the **91 fields** exposed by Network Flows, plus the virtual facets described above.
227
228 ## Master index — every field at a glance
229
@@ -232,7 +234,7 @@ Column legend:
234 - **v5 / v7 / v9 / IPFIX / sFlow** — `✓` always populated, `◐` only when the exporter sends the relevant IE/record, `—` never.
235 - **Source** — `decoder` (filled by parsing the protocol), `enrichment` (filled by post-decode lookups; the wire never carries it), or `both` (decoder may fill, enrichment may overlay/override).
236 - **Tiers** — which tiers preserve the field. `all` means raw + 1m + 5m + 1h. `raw` means raw only (dropped at rollup).
235 -- **Selectivity** — which query roles the field plays. `facet` (autocomplete + filter ribbon), `group-by` (Sankey/timeseries/maps aggregation), `filter` (selections), `metric` (BYTES/PACKETS/FLOWS — sums in tables, not faceted), `time` (used by the time-range picker), `hidden` (queryable but not in the default columns).
237 +- **Selectivity** — which query roles the field plays. `facet` (autocomplete + filter ribbon), `group-by` (Sankey/timeseries/maps aggregation), `filter` (selections), `metric` (BYTES/PACKETS/FLOWS — sums in tables, not faceted), `hidden` (queryable but not in the default columns).
238 - **Notes** — IE numbers / sFlow record types when relevant, plus the enrichment chain for enrichment-derived fields.
239
240 | Field | Type | v5 | v7 | v9 | IPFIX | sFlow | Source | Tiers | Selectivity | Notes |
@@ -272,8 +274,8 @@ Column legend:
274 | `EXPORTER_SITE` | string | — | — | — | — | — | enrichment | all | facet, group-by, filter | `metadata_static.exporters.<ip>.site`. Classifiers may fill |
275 | `EXPORTER_TENANT` | string | — | — | — | — | — | enrichment | all | facet, group-by, filter | `metadata_static.exporters.<ip>.tenant`. Classifiers may fill |
276 | `FLOWS` | uint64 | ✓ | ✓ | ✓ | ✓ | ✓ | decoder | all | metric, filter | Always 1 for raw records; sums during rollup aggregation |
275 -| `FLOW_END_USEC` | uint64 | ✓ | ✓ | ◐ | ◐ | — | decoder | raw | time | v5/v7 from header `sysUpTime` + `LastSwitched`. v9 from `LastSwitched`/`flowEndMilliseconds` normalised against `system_init`. IPFIX from `flowEndMilliseconds` family. Not populated for sFlow |
276 -| `FLOW_START_USEC` | uint64 | ✓ | ✓ | ◐ | ◐ | — | decoder | raw | time | Same sources as `FLOW_END_USEC`. Not populated for sFlow |
277 +| `FLOW_END_USEC` | uint64 | ✓ | ✓ | ◐ | ◐ | — | decoder | raw | hidden | v5/v7 from header `sysUpTime` + `LastSwitched`. v9 from `LastSwitched`/`flowEndMilliseconds` normalised against `system_init`. IPFIX from `flowEndMilliseconds` family. Not populated for sFlow |
278 +| `FLOW_START_USEC` | uint64 | ✓ | ✓ | ◐ | ◐ | — | decoder | raw | hidden | Same sources as `FLOW_END_USEC`. Not populated for sFlow |
279 | `FLOW_VERSION` | string | ✓ | ✓ | ✓ | ✓ | ✓ | decoder | all | facet, group-by, filter | One of `v5`, `v7`, `v9`, `ipfix`, `sflow` |
280 | `FORWARDING_STATUS` | uint8 | — | — | ◐ | ◐ | ◐ | decoder | all | facet, group-by, filter | v9/IPFIX IE 89; IPFIX also from Juniper PEN 2636 `commonPropertiesId`. sFlow synthesises `128` (dropped) when `output_format` is `discarded` |
281 | `ICMPV4_CODE` | uint8 | — | — | ◐ | ◐ | ◐ | decoder | all | facet, group-by, filter | IPFIX IE 177 `IcmpCodeIpv4` + IE 32 low byte. v9 IE 178 `IcmpCodeValue` + IE 32. sFlow from decoded ICMP header |
@@ -294,7 +296,7 @@ Column legend:
296 | `IP_FRAGMENT_OFFSET` | uint16 | — | — | ◐ | ◐ | ◐ | decoder | raw | facet, group-by, filter | v9/IPFIX IE 88 `FragmentOffset`. sFlow from parsed IPv4 header |
297 | `MPLS_LABELS` | string | — | — | ◐ | ◐ | ◐ | decoder | raw | filter | v9 IE 70-79 `MplsLabel1..10`. IPFIX IE 70 `MplsTopLabelStackSection` + 71-79 `MplsLabelStackSection2..10`. sFlow from MPLS in `SampledHeader`. Comma-separated decimal labels |
298 | `NEXT_HOP` | IP | ✓ | ✓ | ◐ | ◐ | ◐ | both | all | facet, group-by, filter | v9 IE 15/18/62/63; IPFIX same. sFlow `ExtendedRouter`/`ExtendedGateway`. Enrichment overlay via `net_providers` chain (default `[flow, routing]`) |
297 -| `OBSERVATION_TIME_MILLIS` | uint64 | — | — | ◐ | — | — | decoder | raw | time | v9 IE 323 `ObservationTimeMilliseconds`. IPFIX has no canonical mapping in this build |
299 +| `OBSERVATION_TIME_MILLIS` | uint64 | — | — | ◐ | — | — | decoder | raw | hidden | v9 IE 323 `ObservationTimeMilliseconds`. IPFIX observation-time fields are not exposed |
300 | `OUT_IF` | uint32 | ✓ | ✓ | ◐ | ◐ | ◐ | decoder | all | facet, group-by, filter | v9 IE 14 `OutputSnmp`; IPFIX IE 14/253. sFlow flow-sample `output` (single index only; LOCAL→0) |
301 | `OUT_IF_BOUNDARY` | uint8 | — | — | — | — | — | enrichment | all | facet, group-by, filter | Same semantics as `IN_IF_BOUNDARY` |
302 | `OUT_IF_CONNECTIVITY` | string | — | — | — | — | — | enrichment | all | facet, group-by, filter | Static metadata or interface classifier connectivity tag |
docs/network-flows/installation.md
+21 -10
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows"
6 keywords: ['installation', 'package', 'netdata-plugin-netflow', 'setup']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Installation
12
13 The netflow plugin is **packaged separately from the main Netdata Agent**. You install it on the same host where Netdata runs, after Netdata itself is in place.
@@ -71,20 +73,29 @@ cd netdata
73 sudo ./netdata-installer.sh
74 ```
75
74 -**Caveat:** source builds do **not** include the stock GeoIP / IP-intelligence database files. The plugin starts fine without them, but country, city, and AS-name fields will be empty until you run the downloader once:
76 +**Caveat:** source builds do **not** include the stock GeoIP / IP-intelligence database files. Packaged 32-bit installs ship the stock MMDB payload but do not include `topology-ip-intel-downloader`. The plugin starts fine without cache files, but country, city, and AS-name fields will be empty until you run the downloader once on an install that includes it:
77
78 ```bash
79 sudo /usr/sbin/topology-ip-intel-downloader
80 ```
81
80 -This populates `/var/cache/netdata/topology-ip-intel/` with the DB-IP-based MMDB files. The plugin auto-detects the cache copy on its next 30-second poll. See [GeoIP enrichment](/docs/network-flows/enrichment/ip-intelligence.md) for details and refresh scheduling.
82 +This populates `/var/cache/netdata/topology-ip-intel/` with the DB-IP-based MMDB files. The plugin auto-detects the cache copy on its next 30-second poll. See the [Enrichment Intel Downloader page](/docs/network-flows/intel-downloader.md) for the refresh tool and the [DB-IP integration card](/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md) for cadence and license details.
83 +
84 +## IP intelligence defaults
85 +
86 +| Item | Behaviour |
87 +|---|---|
88 +| Native packages | Ship stock DB-IP ASN and Geo MMDB files under `/usr/share/netdata/topology-ip-intel/`. |
89 +| Source builds | Do not include stock MMDB files; run the downloader once if you want GeoIP / ASN enrichment. |
90 +| Fresh copies | The downloader writes to `/var/cache/netdata/topology-ip-intel/`, which takes precedence over the stock files. |
91 +| Refresh schedule | Netdata does not install a timer or cron job for the downloader. Schedule it yourself if freshness matters. |
92
93 ## What gets installed
94
95 | Path | Purpose |
96 |---|---|
97 | `/usr/libexec/netdata/plugins.d/netflow-plugin` | The plugin binary (mode 0750, root:netdata) |
87 -| `/usr/sbin/topology-ip-intel-downloader` | Helper for refreshing the GeoIP / IP-intel MMDBs |
98 +| `/usr/sbin/topology-ip-intel-downloader` | Helper for refreshing the GeoIP / IP-intel MMDBs; not included in packaged 32-bit installs |
99 | `/usr/lib/netdata/conf.d/netflow.yaml` | Stock configuration (read-only reference; copy to `/etc/netdata/netflow.yaml` to customise) |
100 | `/usr/lib/netdata/conf.d/topology-ip-intel.yaml` | IP-intel downloader configuration |
101 | `/usr/share/netdata/topology-ip-intel/topology-ip-asn.mmdb` | Stock ASN database (DB-IP) |
@@ -97,7 +108,7 @@ This populates `/var/cache/netdata/topology-ip-intel/` with the DB-IP-based MMDB
108 After installation and restart:
109
110 ```bash
100 -sudo journalctl -u netdata --since "5 minutes ago" | grep -E 'netflow|listener'
111 +sudo journalctl --namespace netdata --since "5 minutes ago" | grep -E 'netflow|listener'
112 ```
113
114 You should see entries indicating that the plugin loaded its config and that the UDP listener bound to its port.
@@ -112,12 +123,12 @@ A line for `netflow-plugin` confirms the listener is up.
123
124 ## Open Netdata to confirm
125
115 -Open the Netdata UI in your browser. The **Network Flows** tab should appear in the top navigation. The plugin's operational charts also appear under the standard charts page in the `netflow` family.
126 +Open the Netdata UI in your browser. Click the **Live** tab in the top navigation; **Network Flows** appears in the Functions list on the right (see [Live tab](/docs/dashboards-and-charts/live-tab.md)). Selecting it opens the Sankey + Table view. The plugin's operational charts also appear under the standard charts page in the `netflow` family.
127
117 -If the tab doesn't appear, or appears empty:
128 +If Network Flows doesn't appear under Live, or the view is empty:
129
130 - Check that the plugin process is running: `pgrep -fa netflow-plugin`.
120 -- Check Netdata Cloud SSO: the Network Flows function requires authenticated access to the agent's space.
131 +- Check Netdata Cloud SSO: Functions require authenticated access to the agent's space.
132 - See [Troubleshooting](/docs/network-flows/troubleshooting.md).
133
134 ## Configuring flow sources
@@ -127,9 +138,9 @@ Installing the plugin enables it. To actually see flow data, you need to configu
138 That's the next step:
139
140 - [Quick Start](/docs/network-flows/quick-start.md) — A 15-minute path to your first flow data.
130 -- [Sources / NetFlow](/src/crates/netflow-plugin/integrations/netflow.md) — Vendor configurations for NetFlow.
131 -- [Sources / IPFIX](/src/crates/netflow-plugin/integrations/ipfix.md) — Vendor configurations for IPFIX.
132 -- [Sources / sFlow](/src/crates/netflow-plugin/integrations/sflow.md) — Vendor configurations for sFlow.
141 +- [Flow Protocols / NetFlow](/src/crates/netflow-plugin/integrations/netflow.md) — Vendor configurations for NetFlow.
142 +- [Flow Protocols / IPFIX](/src/crates/netflow-plugin/integrations/ipfix.md) — Vendor configurations for IPFIX.
143 +- [Flow Protocols / sFlow](/src/crates/netflow-plugin/integrations/sflow.md) — Vendor configurations for sFlow.
144
145 ## Uninstall
146
docs/network-flows/intel-downloader.md new
+248
@@ -0,0 +1,248 @@
1 +<!--startmeta
2 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/intel-downloader.md"
3 +sidebar_label: "Enrichment Intel Downloader"
4 +learn_status: "Published"
5 +learn_rel_path: "Network Flows"
6 +keywords: ['ip intelligence', 'mmdb', 'downloader', 'db-ip', 'iptoasn', 'topology-ip-intel-downloader', 'enrichment', 'refresh']
7 +endmeta-->
8 +
9 +<!-- markdownlint-disable-file -->
10 +
11 +# Enrichment Intel Downloader
12 +
13 +`topology-ip-intel-downloader` is a small Netdata-supplied tool that keeps the IP intelligence MMDB databases used by the netflow plugin (and the topology subsystem) up to date. It fetches the upstream payloads, normalises them into a fixed Netdata MMDB layout, applies CIDR classification policy, and atomically replaces the files on disk. The netflow plugin's resolver picks up the new files within 30 seconds — no plugin restart required.
14 +
15 +Packaged 32-bit installs ship the stock MMDB payload but do not include the downloader binary. Source builds from a Git checkout also do not include the generated stock MMDB payload by default.
16 +
17 +The downloader is a separate executable so you can run it on whatever schedule fits your environment without coupling it to the agent's lifecycle.
18 +
19 +## What it does
20 +
21 +- Fetches the configured ASN and Geo source files over HTTPS, with gzip / zip transparently decoded.
22 +- Parses the upstream format (MMDB or TSV/CSV), keeping the first source per family that covers a given range — first-source-wins on overlap.
23 +- Re-emits the data as two Netdata-format MMDB files plus a metadata JSON manifest.
24 +- Stamps Netdata classification metadata (`netdata.ip_class`, `netdata.track_individual`) over `localhost_cidrs`, `private_cidrs`, and any operator-defined `interesting_cidrs` so the plugin can identify private/loopback/operator-flagged ranges via a normal MMDB lookup.
25 +- Publishes each output atomically via stage-then-`rename(2)` — the resolver never sees a torn file.
26 +
27 +The output is always the same fixed file set, regardless of which providers fed the run:
28 +
29 +```
30 +/var/cache/netdata/topology-ip-intel/
31 +├── topology-ip-asn.mmdb # ASN database
32 +├── topology-ip-geo.mmdb # Geographic database
33 +└── topology-ip-intel.json # Manifest: when, from where, how many ranges
34 +```
35 +
36 +The directory and filenames match the shipped defaults.
37 +
38 +## Supported sources
39 +
40 +The tool only knows how to talk to a fixed set of providers — anything else is rejected at validation:
41 +
42 +| Provider:Artifact | Family | Format | Origin |
43 +|---|---|---|---|
44 +| `dbip:asn-lite` | ASN | `mmdb` (default) or `csv` | DB-IP free monthly download page |
45 +| `dbip:country-lite` | Geo | `mmdb` (default) or `csv` | DB-IP free monthly download page |
46 +| `dbip:city-lite` | Geo | `mmdb` (default) or `csv` | DB-IP free monthly download page |
47 +| `iptoasn:combined` | ASN or Geo | `tsv` | `https://iptoasn.com/data/ip2asn-combined.tsv.gz` (direct URL) |
48 +
49 +DB-IP artifacts are resolved from the current monthly URL on the DB-IP landing page (`https://db-ip.com/db/download/<artifact>`). The downloaded URL uses the DB-IP free database pattern `https://download.db-ip.com/free/dbip-<artifact>-YYYY-MM.<ext>.gz`.
50 +
51 +The IPtoASN TSV feed is converted into the same Netdata MMDB layout as the DB-IP feeds, so consumers don't care which source produced the file.
52 +
53 +> **MaxMind GeoIP / GeoLite2 is not supported by this tool.** The downloader has no `license_key` field, no `MAXMIND_LICENSE_KEY` env var, and no MaxMind URL builder. If you want to use MaxMind, run MaxMind's own [`geoipupdate`](/src/crates/netflow-plugin/integrations/maxmind_geoip_-_geolite2.md) and point `enrichment.geoip.asn_database` / `enrichment.geoip.geo_database` at the files it produces.
54 +
55 +You can still pull *any* MMDB build (including a custom one) into the resolver by configuring `enrichment.geoip.asn_database` / `geo_database` directly — the downloader is one of several producers; the plugin doesn't care who wrote the MMDB. See the [Custom MMDB Database](/src/crates/netflow-plugin/integrations/custom_mmdb_database.md) card.
56 +
57 +## Configuration file
58 +
59 +The downloader reads YAML config from the first existing file in this order:
60 +
61 +1. `/etc/netdata/topology-ip-intel.yaml` (operator overrides)
62 +2. `/usr/lib/netdata/conf.d/topology-ip-intel.yaml` (stock, shipped by the package)
63 +
64 +If neither exists, the built-in defaults are used. Pass `--config /path/to/file.yaml` to force a specific path.
65 +
66 +The shipped stock file is:
67 +
68 +```yaml
69 +sources:
70 + - name: dbip-asn
71 + family: asn
72 + provider: dbip
73 + artifact: asn-lite
74 + format: mmdb
75 +
76 + - name: dbip-geo
77 + family: geo
78 + provider: dbip
79 + artifact: city-lite
80 + format: mmdb
81 +
82 +output:
83 + directory: /var/cache/netdata/topology-ip-intel
84 + asn_file: topology-ip-asn.mmdb
85 + geo_file: topology-ip-geo.mmdb
86 + metadata_file: topology-ip-intel.json
87 +
88 +policy:
89 + localhost_cidrs:
90 + - 127.0.0.0/8
91 + - ::1/128
92 + private_cidrs:
93 + - 10.0.0.0/8
94 + - 172.16.0.0/12
95 + - 192.168.0.0/16
96 + - 100.64.0.0/10
97 + - fc00::/7
98 + - fe80::/10
99 + interesting_cidrs: []
100 +
101 +http:
102 + timeout: 2m
103 + user_agent: netdata-topology-ip-intel-downloader/1.0
104 +```
105 +
106 +| Key | Notes |
107 +|---|---|
108 +| `sources[]` | Ordered list per family. Each entry needs `family` (`asn` or `geo`), `provider`, `artifact`. `format` is inferred from the provider/artifact when omitted. Optional `url` overrides the built-in URL; optional `path` reads from a local file instead. Earlier entries win on overlap. |
109 +| `output.directory` | Where the MMDB and metadata files land. Must match what the netflow plugin reads (see below). |
110 +| `output.asn_file` / `output.geo_file` / `output.metadata_file` | File names only — paths are rejected by validation. |
111 +| `policy.localhost_cidrs` / `private_cidrs` | Stamped into both MMDBs as `netdata.ip_class = "localhost"` / `"private"`. |
112 +| `policy.interesting_cidrs` | Operator-defined public ranges to track individually. Stamped as `netdata.ip_class = "interesting"`. |
113 +| `http.timeout` | Per-request timeout. Default `2m`. |
114 +| `http.user_agent` | Sent to upstream providers. Default `netdata-topology-ip-intel-downloader/1.0`. |
115 +
116 +CLI flags can override the config without editing the file:
117 +
118 +| Flag | Purpose |
119 +|---|---|
120 +| `--config PATH` | Force a specific YAML config path. |
121 +| `--output-dir DIR` | Override `output.directory`. |
122 +| `--asn provider:artifact[@format]` | Replace the ASN source list. Repeatable; first wins. |
123 +| `--geo provider:artifact[@format]` | Replace the Geo source list. Repeatable; first wins. |
124 +| `--no-asn` | Disable ASN output and delete any stale `topology-ip-asn.mmdb`. |
125 +| `--no-geo` | Disable Geo output and delete any stale `topology-ip-geo.mmdb`. |
126 +
127 +## Scheduled execution
128 +
129 +**Netdata does not ship a systemd timer or cron entry for the downloader.** This is intentional — the appropriate refresh cadence depends on the provider's update cadence, your bandwidth, and your change-control policy, and a packaged timer would force one choice on every install.
130 +
131 +Set up your own. A simple systemd timer is the recommended pattern:
132 +
133 +```ini
134 +# /etc/systemd/system/netdata-topology-ip-intel.service
135 +[Unit]
136 +Description=Refresh Netdata IP intelligence databases
137 +
138 +[Service]
139 +Type=oneshot
140 +ExecStart=/usr/sbin/topology-ip-intel-downloader
141 +User=netdata
142 +Group=netdata
143 +```
144 +
145 +```ini
146 +# /etc/systemd/system/netdata-topology-ip-intel.timer
147 +[Unit]
148 +Description=Weekly refresh of Netdata IP intelligence databases
149 +
150 +[Timer]
151 +OnCalendar=weekly
152 +RandomizedDelaySec=1h
153 +Persistent=true
154 +
155 +[Install]
156 +WantedBy=timers.target
157 +```
158 +
159 +```bash
160 +sudo systemctl daemon-reload
161 +sudo systemctl enable --now netdata-topology-ip-intel.timer
162 +```
163 +
164 +DB-IP refreshes its free Lite databases monthly; weekly is a safe over-poll that picks up every release within a few days while staying polite to the upstream. IPtoASN refreshes hourly, but downstream consumers rarely need that resolution — daily is plenty if you switch to it.
165 +
166 +Run the packaged binary as the `netdata` user (or root) so it can write to `/var/cache/netdata/topology-ip-intel/`.
167 +
168 +## Manual invocation
169 +
170 +Trigger an out-of-schedule refresh:
171 +
172 +```bash
173 +sudo systemctl start netdata-topology-ip-intel.service # if you set up the unit above
174 +```
175 +
176 +Or invoke the binary directly — it loads the same config, prints the execution plan, and writes to the same destination:
177 +
178 +```bash
179 +sudo -u netdata /usr/sbin/topology-ip-intel-downloader
180 +```
181 +
182 +A successful run finishes in well under a minute on a typical link and prints something like:
183 +
184 +```
185 +effective source plan:
186 +ASN sources (first wins):
187 +- 1. dbip:asn-lite@mmdb
188 +GEO sources (first wins):
189 +- 1. dbip:city-lite@mmdb
190 +output actions:
191 +- write topology-ip-asn.mmdb
192 +- write topology-ip-geo.mmdb
193 +- write topology-ip-intel.json
194 +updated IP intelligence databases using config /usr/lib/netdata/conf.d/topology-ip-intel.yaml
195 +asn_mmdb=/var/cache/netdata/topology-ip-intel/topology-ip-asn.mmdb
196 +geo_mmdb=/var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
197 +metadata=/var/cache/netdata/topology-ip-intel/topology-ip-intel.json
198 +asn_ranges=1234567 geo_ranges=8901234
199 +```
200 +
201 +The plan is printed *before* any download, so you can verify the effective source list without committing to a fetch.
202 +
203 +## Output and atomic replacement
204 +
205 +Atomic publication is the contract this tool provides to the netflow plugin's resolver:
206 +
207 +1. A staging directory is created inside `output.directory` (`.tmp-topology-ip-intel-stage-*`) and removed on exit.
208 +2. The MMDB writer streams into a per-file temp inside that staging directory.
209 +3. Each finished MMDB is fsync-closed, chmodded `0644`, and renamed into its final name.
210 +4. The metadata JSON is renamed last, so a partially-completed run never updates the manifest.
211 +
212 +Because `rename(2)` is atomic on the same filesystem, a reader that opens the file at any moment sees either the old complete file or the new complete file — never a half-written one. The netflow plugin's resolver re-stats and re-opens the MMDBs every 30 seconds, so a fresh download is live within at most 30 seconds of completion. No plugin restart, no agent restart.
213 +
214 +## Failure modes
215 +
216 +The tool exits non-zero with a diagnostic on `stderr` for any of these cases:
217 +
218 +| Failure | Behaviour |
219 +|---|---|
220 +| Config syntax / validation error | Exits before any network activity. Existing MMDBs are untouched. |
221 +| Upstream unreachable / non-200 status | The run aborts before any output is staged. Existing MMDBs are untouched. |
222 +| Decompression / parse error | Same as above — abort before publishing. |
223 +| Disk full / rename failure during publish | The staging directory is cleaned up; the previously-published file remains in place. |
224 +
225 +Net result: **a failed run keeps the previously good databases**. The plugin keeps serving stale-but-correct enrichment until the next successful run replaces them. There is no built-in retry — schedule the timer often enough that a single missed run isn't critical.
226 +
227 +If you run the downloader from a systemd timer, the failure is visible via `systemctl status netdata-topology-ip-intel.service` and `journalctl -u netdata-topology-ip-intel`. There is no log file written by the tool itself; it only writes to stdout/stderr.
228 +
229 +## Integration with the netflow plugin's auto-detect
230 +
231 +The netflow plugin auto-discovers MMDB files at startup when neither `enrichment.geoip.asn_database` nor `enrichment.geoip.geo_database` is set. The lookup order is:
232 +
233 +1. `<cache_dir>/topology-ip-intel/topology-ip-asn.mmdb` and `topology-ip-geo.mmdb` — the directory the downloader writes to.
234 +2. `<stock_data_dir>/topology-ip-intel/...` — the package-shipped stock payload (typically `/usr/share/netdata/topology-ip-intel/`), used as fallback when no fresh copy exists yet.
235 +
236 +`<cache_dir>` defaults to `/var/cache/netdata`; `<stock_data_dir>` defaults to `/usr/share/netdata`. The downloader's default output directory matches the cache path the plugin checks first, so a fresh run automatically supersedes the stock payload.
237 +
238 +When the plugin auto-detects MMDBs this way it forces `optional: true` on the geoip stanza — a missing or transiently-unreadable file does not crash the plugin. If you instead set `asn_database` / `geo_database` explicitly in `netflow.yaml`, you control the `optional` flag yourself; see [Configuration](/docs/network-flows/configuration.md#enrichment).
239 +
240 +## What's next
241 +
242 +- Per-provider details (refresh cadence, license, schema, attribution requirements):
243 + - [DB-IP IP Intelligence](/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md) — the default the downloader fetches.
244 + - [IPtoASN](/src/crates/netflow-plugin/integrations/iptoasn.md) — public-domain TSV feed; converted to MMDB by this tool.
245 + - [MaxMind GeoIP / GeoLite2](/src/crates/netflow-plugin/integrations/maxmind_geoip_-_geolite2.md) — *not* fetched by this tool; use `geoipupdate`.
246 + - [Custom MMDB Database](/src/crates/netflow-plugin/integrations/custom_mmdb_database.md) — your own MMDB build.
247 +- The enrichment mechanism that consumes these files: [Enrichment](/docs/network-flows/enrichment.md) (the MMDB shared mechanism section).
248 +- The plugin knobs that point at the files: [Configuration › `enrichment.geoip`](/docs/network-flows/configuration.md#enrichment).
docs/network-flows/investigation-playbooks.md
+14 -15
@@ -6,9 +6,11 @@ learn_rel_path: "Network Flows"
6 keywords: ['playbooks', 'investigation', 'workflows', 'troubleshooting traffic']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Investigation playbooks
12
11 -Step-by-step recipes for common questions, all using the Netdata Network Flows tab. Each playbook fits in a 5-15 minute investigation window.
13 +Step-by-step recipes for common questions, all using the Netdata Network Flows view (open the **Live** tab and select **Network Flows**). Each playbook fits in a 5-15 minute investigation window.
14
15 ## Playbook 1 — "The link is saturated, who's responsible?"
16
@@ -18,16 +20,16 @@ Step-by-step recipes for common questions, all using the Netdata Network Flows t
20
21 **Steps.**
22
21 -1. **Open the Network Flows tab** with the default view (Sankey + Table). Set the time range to **the last 15 minutes** — recent enough to be live, wide enough to smooth bursts.
23 +1. **Open Network Flows** with the default view (Sankey + Table). Set the time range to **the last 15 minutes** — recent enough to be live, wide enough to smooth bursts.
24
25 2. **Filter to the saturated interface.** In the filter ribbon, set:
26 - `Exporter Name` = the router with the saturated link
25 - - `Output Interface Name` = the interface name (or `Input Interface Name` if you want incoming traffic)
27 + - `Egress Interface Name` = the interface name (or `Ingress Interface Name` if you want incoming traffic)
28
29 This eliminates the doubling effect and shows only one direction.
30
31 3. **Change the aggregation to "who's responsible".** Click the group-by selector and change the fields to:
30 - - `Source ASN` → `Destination ASN` (for an Internet-edge link)
32 + - `Source AS Name` → `Destination AS Name` (for an Internet-edge link)
33
34 Or for an internal link:
35 - `Source IP` → `Destination IP`
@@ -60,14 +62,14 @@ Step-by-step recipes for common questions, all using the Netdata Network Flows t
62
63 **Steps.**
64
63 -1. **Open the Network Flows tab** with **the last 24 hours** as the time range.
65 +1. **Open Network Flows** with **the last 24 hours** as the time range.
66
67 2. **Filter by the IP.** In the filter ribbon:
68 - For inbound investigation: `Destination IP` = the IP
69 - For outbound investigation: `Source IP` = the IP
70 - For both directions: filter both, separately, in two browser tabs
71
70 - IP filtering forces tier 0 (raw retention) — the time depth is bounded by your raw-tier retention. If you need to look further back than that, the data isn't there.
72 + IP filtering forces raw tier (raw retention) — the time depth is bounded by your raw-tier retention. If you need to look further back than that, the data isn't there.
73
74 3. **Switch to Time-Series view.** This shows when the IP was active. Look for:
75 - When did activity start? End?
@@ -94,7 +96,7 @@ Step-by-step recipes for common questions, all using the Netdata Network Flows t
96
97 **Caveats.**
98
97 -- IP filter forces tier 0; older data may not be available.
99 +- IP filter forces raw tier; older data may not be available.
100 - If the IP is internal and you haven't declared it under `enrichment.networks`, GeoIP may misrepresent its country.
101 - If the IP is a NAT public address, multiple internal hosts may be hidden behind it. Cross-check with NAT translation logs.
102
@@ -106,15 +108,15 @@ Step-by-step recipes for common questions, all using the Netdata Network Flows t
108
109 **Steps.**
110
109 -1. **Open the Network Flows tab** with **the last 30 days** as the time range. (Adjust based on tier-1/5/60 retention. If your retention is shorter, use whatever you have.)
111 +1. **Open Network Flows** with **the last 30 days** as the time range. (Adjust based on tier-1/5/60 retention. If your retention is shorter, use whatever you have.)
112
111 -2. **Filter to the WAN interface.** Set `Exporter Name` and `Output Interface Name` (or input — pick one direction). This removes the doubling effect.
113 +2. **Filter to the WAN interface.** Set `Exporter Name` and `Egress Interface Name` (or `Ingress Interface Name` — pick one direction). This removes the doubling effect.
114
115 3. **Switch to Time-Series view.** The chart now shows ~30 days of bandwidth on the link. The bucket size auto-adjusts to roughly 1 hour at this range.
116
117 4. **Identify the trend.** Look at the daily peaks (one curve cycle = one day). The peak should be growing month-over-month. Eyeball the slope.
118
117 -5. **Identify the growth driver.** Switch back to Sankey + Table, group by `Destination ASN` or `Application` (port). Compare top consumers from the start of the period to the end. New entries that weren't there 30 days ago are growth drivers.
119 +5. **Identify the growth driver.** Switch back to Sankey + Table, group by `Destination ASN` or `Destination Port` (service). Compare top consumers from the start of the period to the end. New entries that weren't there 30 days ago are growth drivers.
120
121 6. **Compute the upgrade need.** Take the current peak (e.g., 80% of 100 Mbps = 80 Mbps), project forward at the observed monthly growth rate (e.g., 10%/month = ~30%/quarter), and find when it crosses 100% (or 70% if you want headroom).
122
@@ -125,11 +127,8 @@ Step-by-step recipes for common questions, all using the Netdata Network Flows t
127 - Trend chart (screenshot or shareable URL)
128 - Growth driver: the specific applications / services consuming the new bandwidth
129 - Projected saturation date and recommended upgrade timeline
128 -- Sampling rate of the exporter (so the numbers can be interpreted)
129 -
130 **Caveats.**
131
132 -- Always note the sampling rate. A change in sampling rate during the analysis window invalidates the trend.
132 - A large spike one day shouldn't drive the projection. Use weekly peaks (averaged across same-day-of-week) for stability.
133 - If your retention is shorter than 30 days, use what you have but caveat the projection.
134
@@ -141,11 +140,11 @@ Step-by-step recipes for common questions, all using the Netdata Network Flows t
140
141 **Steps.**
142
144 -1. **Open the Network Flows tab** with the time range covering 24 hours before the alert through now.
143 +1. **Open Network Flows** with the time range covering 24 hours before the alert through now.
144
145 2. **Filter by the external IP.** In the filter ribbon: `Destination IP` = the external IP.
146
148 - This forces tier 0. Time depth is your raw-tier retention.
147 + This forces raw tier. Time depth is your raw-tier retention.
148
149 3. **Switch to Time-Series view.** When did communication start? Is it ongoing? Did it correlate with the alert time?
150
docs/network-flows/quick-start.md
+28 -14
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows"
6 keywords: ['quick start', 'netflow', 'sflow', 'ipfix', 'getting started', 'setup']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Quick Start
12
13 Get flow monitoring running in 15 minutes. The path: install the plugin, configure your first router, open the dashboard, and read it correctly.
@@ -21,7 +23,7 @@ If the plugin isn't installed yet, follow the [Installation page](/docs/network-
23
24 ## Step 1 — Configure your router
25
24 -Pick the closest match to your platform. The configurations below set sensible defaults: 60-second active timeout (industry best practice), 60-second template refresh (so a collector restart recovers in under a minute), and monitoring on both directions of an interface.
26 +Pick the closest match to your platform. The configurations below set sensible defaults: 60-second active timeout (industry best practice), a quick template refresh where the platform supports tuning it (so a collector restart recovers in under a minute), and monitoring on the appropriate direction(s) of an interface. softflowd and Arista sFlow do not expose a template-refresh knob; they ship reasonable internal defaults.
27
28 ### Cisco IOS / IOS-XE (Flexible NetFlow, v9)
29
@@ -60,6 +62,8 @@ interface GigabitEthernet0/0/1
62 ### Juniper JunOS (J-Flow v9)
63
64 ```
65 +set chassis fpc 0 sampling-instance NETDATA
66 +set forwarding-options sampling instance NETDATA input rate 1000
67 set forwarding-options sampling instance NETDATA family inet output flow-server 10.0.0.10 port 2055
68 set forwarding-options sampling instance NETDATA family inet output flow-server 10.0.0.10 version9 template ipv4-template
69 set services flow-monitoring version9 template ipv4-template flow-active-timeout 60
@@ -69,6 +73,12 @@ set interfaces ge-0/0/1 unit 0 family inet sampling input
73 set interfaces ge-0/0/1 unit 0 family inet sampling output
74 ```
75
76 +Notes:
77 +
78 +- The `set chassis fpc <slot> sampling-instance NETDATA` line is mandatory; without it the sampling instance is defined but never bound to a forwarding card and no flows are produced.
79 +- `input rate 1000` sets a 1-in-1000 sampling rate. Adjust to match your traffic; the netflow plugin handles per-flow sampling-rate multiplication automatically.
80 +- Replace `fpc 0`, `ge-0/0/1`, and `1000` with the FPC slot, interface, and sampling rate that match your platform.
81 +
82 ### Arista EOS (sFlow)
83
84 ```
@@ -92,18 +102,20 @@ For Linux servers, hypervisors, or any host that doesn't natively speak NetFlow:
102 sudo softflowd -i eth0 -n 10.0.0.10:2055 -v 9 -t maxlife=60 -t expint=15
103 ```
104
95 -For more vendors and details, see [Sources / NetFlow](/src/crates/netflow-plugin/integrations/netflow.md), [IPFIX](/src/crates/netflow-plugin/integrations/ipfix.md), and [sFlow](/src/crates/netflow-plugin/integrations/sflow.md).
105 +`maxlife` caps a flow's wall-clock lifetime at 60 seconds; `expint` controls how often softflowd scans the flow table for expired entries (it is not a template-refresh knob — softflowd's NetFlow v9 template interval is a compile-time default of 16 packets and is not exposed on the command line).
106 +
107 +For more vendors and details, see [Flow Protocols / NetFlow](/src/crates/netflow-plugin/integrations/netflow.md), [IPFIX](/src/crates/netflow-plugin/integrations/ipfix.md), and [sFlow](/src/crates/netflow-plugin/integrations/sflow.md).
108
109 ## Step 2 — Open the dashboard
110
99 -In your browser, open the Netdata UI and click the **Network Flows** tab.
111 +In your browser, open the Netdata UI, click the **Live** tab in the top navigation, and select **Network Flows** from the Functions list.
112
113 By default you'll see:
114
115 - A Sankey diagram on top, with a sortable table beneath
116 - The default time range — last 15 minutes (Netdata's global picker)
117 - Top-25 flows by bytes
106 -- Aggregated as **Source ASN → Protocol → Destination ASN**
118 +- Aggregated as **Source AS Name → Protocol → Destination AS Name**
119
120 Within 60-90 seconds of the router being configured, flow records should start appearing.
121
@@ -113,22 +125,24 @@ Before drawing any conclusion, read this. It's the single biggest source of conf
125
126 ### Traffic looks doubled
127
116 -Routers normally export both ingress and egress flow records on every monitored interface. A packet that enters interface A and leaves interface B produces **two** records — one ingress on A, one egress on B.
128 +When a router is configured to export both ingress and egress flow records on every monitored interface — a common configuration — a packet that enters interface A and leaves interface B produces **two** records: one ingress on A, one egress on B. Vendor best practice is to export ingress-only to avoid this; if you can't change the exporter, the dashboard view has to compensate.
129
130 If you look at total bandwidth without filtering, you see roughly **2× the real traffic**. Add a second router on the same path and you see 4×.
131
120 -**To see real bandwidth on a specific link**, filter to one exporter and one direction:
132 +**To see real bandwidth on a specific link**, filter to one exporter and one interface:
133
134 1. In the filter ribbon: `Exporter Name = <your router>`.
123 -2. Add: `Input Interface Name = <the interface>` (for incoming) **or** `Output Interface Name = <the interface>` (for outgoing). Pick one. Not both.
135 +2. Add: `Ingress Interface Name = <the interface>` **or** `Egress Interface Name = <the interface>` — pick one, not both. Each packet then appears in exactly one record on that interface.
136 +
137 +That's the actual traffic on that link.
138
125 -That's the actual traffic on that link in that direction.
139 +### Bidirectional traffic shows both directions
140
127 -### Conversations look mirrored
141 +Every conversation has packets going both ways: requests / uploads in one direction, responses / downloads in the other. These are real, separate packets and produce separate flow records. The Sankey, country map, and time-series all show both directions when you don't filter by direction.
142
129 -Each bidirectional conversation produces two flow records — one for the request direction, one for the response. The Sankey, country map, and time-series all show both. When you see traffic between Country X and Country Y *and* traffic between Country Y and Country X of similar volume, that's the same conversation, not two.
143 +Volumes in the two directions are usually asymmetric — for example, a video download produces large B→A flows and small A→B ACKs. A "Country X to Country Y" entry and a "Country Y to Country X" entry refer to the same conversations but typically have very different byte counts. That's correct per-direction accounting, not duplication.
144
131 -This is correct behaviour. To see only one direction of a conversation, filter by `Source ASN` (your network) for outbound or `Destination ASN` for inbound.
145 +To see only one direction, filter by `Source AS Name` (your network) for outbound or `Destination AS Name` (your network) for inbound.
146
147 ## Step 4 — Verify it's working
148
@@ -157,15 +171,15 @@ If the Sankey is empty after 60-90 seconds, work through this:
171 4. **Plugin log lines.**
172
173 ```bash
160 - sudo journalctl -u netdata --since "5 minutes ago" | grep -i netflow
174 + sudo journalctl --namespace netdata --since "5 minutes ago" | grep -i netflow
175 ```
176
177 ## What's next
178
179 You now have flow data flowing in. The natural next steps:
180
167 -- [Configuration](/docs/network-flows/configuration.md) — Tune retention so older data is preserved (the default 7-day shared retention is rarely enough).
168 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Give your routers and your internal networks friendly names and labels. Without this, dashboards show raw IPs.
181 +- [Configuration](/docs/network-flows/configuration.md) — Tune retention after first validation; production retention should be sized from observed flow rate.
182 +- [Static Metadata integration card](/src/crates/netflow-plugin/integrations/static_metadata.md) — Give your routers and your internal networks friendly names and labels. Without this, dashboards show raw IPs.
183 - [Investigation Playbooks](/docs/network-flows/investigation-playbooks.md) — Concrete recipes for the questions flow data is good at answering.
184 - [Anti-patterns](/docs/network-flows/anti-patterns.md) — Mistakes to avoid as you develop confidence with the data.
185 - [Validation and Data Quality](/docs/network-flows/validation.md) — How to confirm your numbers are correct.
docs/network-flows/retention-querying.md
+52 -68
@@ -1,15 +1,19 @@
1 <!--startmeta
2 custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/retention-querying.md"
3 -sidebar_label: "Retention and Querying"
3 +sidebar_label: "Retention and Tiers"
4 learn_status: "Published"
5 learn_rel_path: "Network Flows"
6 -keywords: ['retention', 'tiers', 'querying', 'tier selection', 'rollup']
6 +keywords: ['retention', 'tiers', 'rollup', 'tier selection']
7 endmeta-->
8
9 -# Retention and Querying
9 +<!-- markdownlint-disable-file -->
10 +
11 +# Retention and Tiers
12
13 Netdata stores flow data in four tiers. The tier model is transparent — you do not pick a tier when you query, the dashboard picks for you. Understanding how it picks helps you interpret what you're seeing and avoid surprises when older data isn't there.
14
15 +For the configuration surface (per-tier `size_of_journal_files` and `duration_of_journal_files`), see [Configuration → Per-tier retention](/docs/network-flows/configuration.md#per-tier-retention). For the query semantics (group-by limits, full-text search, URL sharing, dashboard query parameters), see [Visualization → Overview](/docs/network-flows/visualization/overview.md).
16 +
17 ## The four tiers
18
19 | Tier | Bucket | On-disk dir | YAML key |
@@ -19,30 +23,53 @@ Netdata stores flow data in four tiers. The tier model is transparent — you do
23 | 5-minute | 300 s | `flows/5m/` | `minute_5` |
24 | 1-hour | 3600 s | `flows/1h/` | `hour_1` |
25
22 -The raw tier stores every flow record as it arrived. The other three are rollup tiers — they aggregate raw flows into time-bucketed groups by identity (exporter, ASN, country, ports — see below).
26 +The raw tier stores every flow record as it arrived. The other three are rollup tiers — they aggregate raw flows into time-bucketed groups by identity (exporter, interface, ASN, country/state, network labels, VLAN, next-hop — see below for the full preserved set).
27
28 ## What survives the rollup
29
26 -Rollup tiers (1m, 5m, 1h) deliberately drop a few fields to keep cardinality manageable. **The dropped fields are: `SRC_ADDR`, `DST_ADDR`, `SRC_PORT`, `DST_PORT`, `SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`, `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`.**
30 +Rollup tiers (1m, 5m, 1h) deliberately drop the high-cardinality and protocol-specific fields and keep an aggregate-friendly subset.
31 +
32 +**Forced to the raw tier** (any query that filters on, groups by, or runs full-text search against these fields is rerouted to the raw tier — see [Field Reference](/docs/network-flows/field-reference.md) for the per-field matrix):
33 +
34 +- `SRC_ADDR`, `DST_ADDR`, `SRC_PORT`, `DST_PORT`
35 +- `SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`, `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`
36 +- All `V9_*` and `IPFIX_*` raw-protocol fields.
37 +
38 +**Dropped from rollup output but do not switch tier** (the field comes back as null on rollup tiers; the planner does not reroute the query to raw):
39 +
40 +- AS path, BGP community fields (`SRC_COMMUNITIES`, `DST_COMMUNITIES`, etc.), MPLS labels, MAC addresses, NAT / post-NAT addresses, and any other field not in the preserved set below.
41 +
42 +If you need any of these fields populated in the result, force the raw tier explicitly (open a city map, add a port filter, type something into the search ribbon, or pick a window that fits inside raw-tier retention).
43
28 -Everything else survives — country, state, ASN, AS path, BGP communities, exporter and interface labels, protocol, TCP flags, ToS/DSCP, ICMP type/code, MPLS labels, VLANs, MACs, next-hop, post-NAT addresses, and the bytes/packets sums. So rollups are perfectly fine for most country / ASN / interface / protocol questions, but useless if you need to ask "which IP".
44 +**Preserved in rollup tiers** (these queries can use coarser tiers):
45 +
46 +- Core: `PROTOCOL`, `DIRECTION`, `ETYPE`, `FORWARDING_STATUS`, `FLOW_VERSION`, `IPTOS`, `TCP_FLAGS`, `ICMPV4_TYPE/CODE`, `ICMPV6_TYPE/CODE`, `SRC_AS` / `DST_AS` (ASN number), `SRC_AS_NAME` / `DST_AS_NAME`.
47 +- Exporter: `EXPORTER_IP`, `EXPORTER_PORT`, `EXPORTER_NAME`, `EXPORTER_GROUP/ROLE/SITE/REGION/TENANT`.
48 +- Interface: `IN_IF`, `OUT_IF`, `IN_IF_NAME`/`OUT_IF_NAME`, plus their description / speed / provider / connectivity / boundary variants.
49 +- Network: `SRC_NET_*` / `DST_NET_*` (name / role / site / region / tenant), `SRC_COUNTRY` / `DST_COUNTRY`, `SRC_GEO_STATE` / `DST_GEO_STATE`, `NEXT_HOP`, `SRC_VLAN` / `DST_VLAN`.
50 +- Aggregates: bytes / packets / flow-count sums per bucket.
51 +
52 +So rollups are fine for most country / state / ASN / interface / VLAN / protocol questions, but useless if you need to ask "which IP", "which port", "which AS path", "which MPLS label", or "where in the city".
53
54 This is why filtering or grouping by IP/port/city/lat/lon forces the query to the raw tier — there is no other tier that has those fields.
55
56 +For the per-field tier-preservation matrix, see [Field Reference](/docs/network-flows/field-reference.md).
57 +
58 ## How the dashboard picks a tier
59
60 For every query the dashboard sends to the plugin, the planner makes a single decision: which tier (or tiers) can satisfy this?
61
62 **Rules:**
63
38 -1. **Any IP/port/city/lat/lon filter or group-by → raw tier.** No exception. The rollup tiers don't have those fields.
64 +1. **Any raw-only field used as a filter or group-by → raw tier.** No exception. See the "Forced to the raw tier" list above. Selecting the city map or filtering on any IP / port / city / lat / lon field (plus the `V9_*` / `IPFIX_*` raw protocol fields) falls in this category.
65 2. **A non-empty full-text search → raw tier.** Full-text search runs as a regex against the raw journal payload, which only the raw tier carries.
40 -3. **Otherwise, pick the coarsest tier that satisfies the time range and bucket-count requirement.**
41 - - Time-Series view needs at least 100 buckets in the window. So:
42 - - under 100 minutes → 1-minute tier
43 - - 100 minutes to 8h20m → 5-minute tier
44 - - 8h20m and longer → 1-hour tier
45 - - Table / Sankey / Maps don't have a bucket-count constraint, but the configured query-window guardrails (`query_1m_max_window` default 6h, `query_5m_max_window` default 24h) skip a tier when the window is too wide.
66 +3. **Otherwise, pick the coarsest tier that satisfies the time range alignment.**
67 + - **Time-Series view** additionally needs at least 100 buckets in the window. The planner walks the tiers from coarsest to finest and picks the first that delivers ≥100 buckets, falling back to 1-minute when no tier qualifies:
68 + - ≥ 100 hours of window → 1-hour tier (3600 s buckets)
69 + - 8h20m to less than 100 hours → 5-minute tier (300 s buckets)
70 + - 100 minutes to less than 8h20m → 1-minute tier (60 s buckets)
71 + - Less than 100 minutes → 1-minute tier (Time-Series buckets are still floored at 60 seconds, so very short windows render fewer than 100 buckets)
72 + - **Table / Sankey / Map** views have no bucket-count constraint; the planner walks 1-hour → 5-minute → 1-minute by alignment alone, so they can land on a coarser tier than Time-Series for the same window.
73
74 When the planner picks a tier and the time range crosses tier-aligned boundaries, the query is **stitched** — head fragment in a finer tier, aligned middle in the chosen tier, tail fragment in a finer tier. You don't see this; the results merge cleanly. It exists so wide windows that don't quite align to one-hour boundaries still work.
75
@@ -50,15 +77,15 @@ The plugin reports the chosen tier in the response stats (`query_tier` = `0`, `1
77
78 ## What "no data" actually means
79
53 -If you ask for a 30-day window with an IP filter and tier-0 retention is 24 hours, you get an empty response. No error, no banner reading "data has expired" — just an empty result set. The dashboard renders this as "No data".
80 +If you ask for a 30-day window with an IP filter and raw-tier retention is 24 hours, you get an empty response. No error, no banner reading "data has expired" — just an empty result set. The dashboard renders this as "No data".
81
55 -The reason is a layered fallback in the planner: if a span asks for tier 0 and the files for that span have been rotated out, the planner tries the smaller tiers (1m, 5m, 1h), but those don't have IP fields, so they cannot satisfy a query that filters on IP. Result: the span returns no flows.
82 +The planner does not fall back to a coarser tier for raw-only queries. When a span requires the raw tier (because the query filters or groups on an IP / port / city / lat / lon / V9_* / IPFIX_* field, or runs a full-text search) and that span's raw-tier files have been rotated out, the planner returns no flows for that span. Rollups never carry raw-only fields, so they cannot satisfy the query anyway. Conversely, when a span only needs preserved fields (country, ASN, exporter, interface, protocol…), the planner can fall back from a coarser tier to a finer one if the coarser files have rotated out — finer tiers are supersets of coarser tiers for the preserved fields.
83
84 Other spans within the same query that don't need raw data may still return flows. So it's also possible to see partial coverage — half the time range filled, half empty.
85
86 For Time-Series, "no data" appears as zero values in the affected buckets, not as a special "missing" indicator. The chart still draws; the empty regions are flat lines at zero.
87
61 -## What forces tier 0 in practice
88 +## What forces the raw tier in practice
89
90 Quick reference for "why is my query slow / showing less time?":
91
@@ -71,83 +98,40 @@ If you see the time depth in your dashboard suddenly shrink after you applied a
98
99 ## Default retention and the most common misconfiguration
100
74 -The default `size_of_journal_files: 10GB` and `duration_of_journal_files: 7d` apply to **every tier independently**. With defaults, all four tiers (raw, 1m, 5m, 1h) are capped at 10GB / 7d.
101 +Each tier has its own `size_of_journal_files` and `duration_of_journal_files`. The built-in defaults are uniform — `10GB` and `7d` on every tier. That is rarely what you want; the whole point of having rollup tiers is to keep them around longer than raw.
102
76 -This is rarely what you want. The whole point of having rollup tiers is to keep them around longer than raw. A more useful production profile:
103 +A more useful production profile:
104
105 ```yaml
106 journal:
80 - size_of_journal_files: 100GB # top-level inherited by tiers without an override
81 - duration_of_journal_files: 7d
107 tiers:
108 raw:
109 size_of_journal_files: 200GB
110 duration_of_journal_files: 24h
111 minute_1:
112 + size_of_journal_files: 20GB
113 duration_of_journal_files: 14d
114 minute_5:
115 + size_of_journal_files: 20GB
116 duration_of_journal_files: 30d
117 hour_1:
118 + size_of_journal_files: 20GB
119 duration_of_journal_files: 365d
92 - size_of_journal_files: null # time-only, no size cap on the long tail
120 ```
121
122 This gives you 24 hours of full-detail forensics, 14 days of 1-minute trends, 30 days of 5-minute snapshots, and a year of hourly aggregates.
123
97 -See [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md) for how to estimate the actual disk footprint per tier from your flow rate.
98 -
99 -## How queries work, briefly
100 -
101 -The dashboard sends one of two query modes to the plugin:
102 -
103 -- **`flows`** — the normal aggregation request. Returns top-N groups, sums of bytes and packets, optional facet counts.
104 -- **`autocomplete`** — for the filter ribbon. Returns up to 100 facet values matching the user's term. Matching policy is per-field: text fields use substring matching, IP and numeric fields use prefix. Term is capped at 256 bytes. Runs against in-memory facet snapshots and on-disk FST sidecars; never scans tier files. Resulting filters apply as exact equality, not substring.
105 -
106 -A `flows` query carries:
107 -
108 -- A time range (`after` / `before`, or `last`).
109 -- A list of `group_by` fields (up to 10).
110 -- A list of `selections` — per-field IN-lists for filtering.
111 -- Optional `facets` to enrich the response with per-facet value counts.
112 -- A `top_n` (one of 25, 50, 100, 200, 500).
113 -- A `sort_by` (`bytes` or `packets`).
114 -- An optional regex `query` (full-text search; forces tier 0).
115 -- A `view` (`table-sankey`, `timeseries`, `country-map`, `state-map`, `city-map`).
116 -
117 -Defaults if you don't specify: time range = last 15 minutes, `group_by = ["SRC_AS_NAME", "PROTOCOL", "DST_AS_NAME"]`, `top_n = 25`, `sort_by = bytes`, `view = table-sankey`.
118 -
119 -The plugin enforces a hard timeout of **30 seconds** per query. If your query is too wide, narrow the time range, add a filter that lets a higher tier serve it, or reduce the group-by depth.
120 -
121 -## Group-by limits and overflow
122 -
123 -Two configuration limits guard against pathological queries:
124 -
125 -- `query_max_groups` (default `50000`) — total distinct groups in an aggregation. Past this, results overflow into a single `__overflow__` bucket and the response carries a warning.
126 -- `query_facet_max_values_per_field` (default `5000`) — distinct values returned per facet field.
127 -
128 -If you see `__overflow__` rows, your query is too wide for the current limit. Either narrow the filter, drop a high-cardinality `group_by` field, or raise the limit (carefully — the limit exists for memory reasons).
129 -
130 -## Full-text search
131 -
132 -The global search ribbon supports full-text search. It runs as a **regex** match against the raw journal payload. A search of `8.8.8.8` is the regex `8.8.8.8`, where each `.` matches any byte — so it can match unrelated text. To match the literal string, escape with backslashes: `8\.8\.8\.8`.
133 -
134 -Any non-empty full-text search forces the query to tier 0. Time depth is therefore limited by raw-tier retention.
135 -
136 -## URL sharing
137 -
138 -The dashboard URL preserves all of: time range, view, top-N, sort, group-by, selections, full-text search. Copy the URL and share it — the recipient sees exactly what you see, provided they have access to the same Netdata Cloud space.
124 +See [Configuration → Per-tier retention](/docs/network-flows/configuration.md#per-tier-retention) for the full schema and [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md) for how to estimate the actual disk footprint per tier from your flow rate.
125
126 ## Things that surprise people
127
142 -- **An IP filter shrinks the time depth.** This is correct behaviour, but the dashboard doesn't always make it obvious. If your time range is wider than tier-0 retention, drop the IP filter to see the broader rollup data.
128 +- **An IP filter shrinks the time depth.** This is correct behaviour, but the dashboard doesn't always make it obvious. If your time range is wider than raw-tier retention, drop the IP filter to see the broader rollup data.
129 - **The city map can't go back as far as the country map.** The city map needs the city/lat/lon fields (raw-only); the country map only needs `SRC_COUNTRY`/`DST_COUNTRY` (preserved in rollups).
144 -- **`__overflow__` is a real value.** It will show up in result tables, sankey diagrams, and group-by listings. It means "everything that didn't fit in the top groups for this query" — narrow the filter or raise the limit.
145 -- **30-second timeout is hard.** A query that runs to the timeout returns whatever it has so far with a warning. Don't expect more than 30s of work per query.
130 - **Tier files use short names** (`1m`, `5m`, `1h` on disk) but YAML uses the explicit names (`minute_1`, `minute_5`, `hour_1`). Mind the difference.
131
132 ## What's next
133
150 -- [Configuration](/docs/network-flows/configuration.md) — `netflow.yaml` reference, including per-tier retention overrides.
134 +- [Configuration → Per-tier retention](/docs/network-flows/configuration.md#per-tier-retention) — `netflow.yaml` schema for per-tier retention.
135 - [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md) — Disk and CPU estimates from your flow rate.
136 - [Field Reference](/docs/network-flows/field-reference.md) — Which fields exist and which survive into rollups.
153 -- [Visualisation](/docs/network-flows/visualization/summary-sankey.md) — How the dashboard uses the tier model to render views.
137 +- [Visualization → Overview](/docs/network-flows/visualization/overview.md) — How the dashboard sends queries; group-by limits; full-text search; URL sharing.
docs/network-flows/sizing-capacity.md
+77 -85
@@ -3,135 +3,127 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-fl
3 sidebar_label: "Sizing and Capacity Planning"
4 learn_status: "Published"
5 learn_rel_path: "Network Flows"
6 -keywords: ['sizing', 'capacity planning', 'storage', 'cpu', 'memory', 'benchmarks']
6 +keywords: ['sizing', 'capacity', 'planning', 'storage', 'memory', 'scaling', 'distributed']
7 endmeta-->
8
9 -# Sizing and Capacity Planning
9 +<!-- markdownlint-disable-file -->
10
11 -Use the following benchmarks and formulas to estimate the CPU, memory, and storage requirements for your Network Flows deployment.
11 +# Sizing and Capacity Planning
12
13 -## What was measured
13 +A practical guide to choosing the host, the storage, and the deployment shape for the netflow plugin. Read this before you decide where the plugin runs and how big it has to be.
14
15 -These numbers come from a release-mode benchmark on an Intel i9-12900K workstation with a Seagate FireCuda 530 NVMe SSD (ext4). The benchmark runs the full ingest pipeline — raw journal plus the 1-minute, 5-minute, and 1-hour tiers — writing to real disk-backed journals. **Enrichment is not loaded** (no GeoIP/MMDB, no static metadata, no classifiers); enrichment adds CPU on top of the figures below.
15 +## What this plugin is built for
16
17 -Cardinality is synthetic: low-cardinality cycles 256 unique flow records, high-cardinality cycles 4 096 unique records. Real exporter traffic falls between the two.
17 +The netflow plugin is designed to receive, decode, and store flow records (NetFlow / IPFIX / sFlow) from one network — typically the routers and switches at one site — directly on a Netdata Agent.
18
19 -CPU is reported as percent of one core (100% = one core fully consumed). The post-decode ingest path is currently single-threaded, so the practical ceiling per agent is bounded by one core's worth of CPU.
19 +Sustained ingestion of about **25 000 flows per second** on a single agent already approaches **ISP-level traffic capacities** for most enterprise / branch / data-centre profiles. Past that point you are at the scale of a regional service provider, and you should be running multiple agents (see "Distributed deployment" below), not pushing harder on one box.
20
21 -## Practical headline
21 +If your worst-case sustained flow rate stays at or below **~25 000 flows/s**, you have headroom on a single agent. If it does not, plan distributed before you plan harder iron.
22
23 -On this hardware class, plan for:
23 +## Plugin throughput cap
24
25 -| Scenario | Practical ceiling |
26 -|---|---|
27 -| High-cardinality, all three protocols, four storage tiers, no enrichment, including UDP receive and decode | **~20-25 000 flows/s** |
28 -| Low-cardinality | comfortably above 60 000 flows/s |
25 +The post-decode ingest path has a single-thread hot path. For planning, treat **~25 000 flows/s sustained** as the comfortable ceiling for a well-provisioned single agent running the full pipeline (raw + 1m + 5m + 1h tiers). High-cardinality traffic reaches the ceiling sooner; low-cardinality traffic has more headroom.
26
30 -The 20-25k figure is conservative and includes decode cost (~10 µs/flow on top of the post-decode numbers below).
27 +Practical guidance:
28
32 -## Detailed measurements (post-decode, paced)
29 +- Plan for ~25 000 flows/s sustained on a well-provisioned agent. That is the comfortable steady-state ceiling and includes UDP receive, protocol decode, all four storage tiers, and the typical enrichment stack without BMP / BioRIS.
30 +- Bursts above the ceiling cost UDP queue backpressure and eventually `RcvbufErrors` (kernel-level drops). Tune the kernel UDP receive buffer (`net.core.rmem_max`, `net.core.rmem_default`, `net.core.netdev_max_backlog`) for headroom.
31 +- The hot path is single-threaded. Adding cores does not raise the per-agent ceiling. The way to go past it is to add agents (next section).
32
34 -These tables show the cost of pre-decoded flows traversing the full ingest pipeline. To get the full UDP-to-disk cost, add roughly 10 µs/flow for protocol decoding.
33 +## Distributed deployment is the scaling answer
34
36 -### Low cardinality
35 +Aggregation across many routers is rarely operationally meaningful for flow data — you almost always investigate one site, one router, or one interface at a time. So instead of pushing every flow to a single central collector, **deploy one Netdata Agent next to each router (or each site, or each branch office)**.
36
38 -| offered flows/s | NetFlow v9 CPU | IPFIX CPU | sFlow CPU | RAM peak |
39 -|---:|---:|---:|---:|---|
40 -| 1 000 | 1.3% | 1.0% | 1.5% | ~25 MiB |
41 -| 10 000 | 12.6% | 11.5% | 16.9% | ~75 MiB |
42 -| 30 000 | 35.7% | 32.9% | 46.2% | ~85 MiB |
43 -| 60 000 | 70.3% | 64.1% | 87.1% | ~85 MiB |
37 +This pattern is how Netdata is built to scale: each agent owns its own flow journal, its own enrichment, and its own dashboard view; Netdata Cloud federates queries across them. The benefits compound:
38
45 -All three protocols deliver 100% of offered rate at every tested point. Saturation is above 60 000 flows/s and was not reached in this matrix.
39 +- **Each agent's load is bounded by one router's flow rate**, not the whole network's. A 10 000 flows/s router stays comfortably under the per-agent ceiling.
40 +- **No single host becomes the bottleneck** for ingest, storage, or query latency.
41 +- **Failure of one agent loses one router's history**, not the whole network's.
42 +- **You don't pay the bandwidth cost** of moving every flow datagram to a central collector across WAN links.
43
47 -### High cardinality
44 +For a multi-site / multi-data-centre / multi-branch deployment, this is the recommended shape: one Netdata Agent per router, federated through Netdata Cloud. Use the central collector pattern only if your sites are too small to host an agent each.
45
49 -| offered flows/s | NetFlow v9 achieved | IPFIX achieved | sFlow achieved | CPU at saturation |
50 -|---:|---:|---:|---:|---|
51 -| 10 000 | 10 000 | 9 970 | 9 990 | 28-37% |
52 -| 20 000 | 20 000 | 19 970 | 19 980 | 56-74% |
53 -| 30 000 | 29 331 | 29 985 | 29 257 | 84-98% |
54 -| 40 000 | 29 087 | 35 771 | 30 543 | 99% (plateau) |
55 -| 60 000 | 26 475 | 28 835 | 30 227 | 99% (plateau) |
46 +## Storage
47
57 -Saturation is around 30 000 flows/s on this host. Beyond the knee, the achieved rate plateaus at roughly the saturation value while the offered rate grows.
48 +Storage cost scales linearly with **sustained flows per second** and the **retention** you configure on each tier.
49
59 -:::warning
60 -These are host-specific reference points. Actual throughput depends on your CPU clock, disk speed, real flow cardinality, the number of populated fields, and any enrichment you enable (GeoIP, classifiers, static networks, ASN providers, BMP routing).
61 -:::
50 +### How ingestion rate maps to disk
51
63 -## Storage
52 +Use **~800 bytes on disk per flow** as the journal sizing estimate for the raw tier. For sustained ingestion this gives you:
53
65 -Storage is governed by two things, not by the flow rate alone:
54 +| Sustained flows/s | Disk used per day, raw tier |
55 +|---|---|
56 +| 1 000 | ~70 GB |
57 +| 5 000 | ~350 GB |
58 +| 10 000 | ~700 GB |
59 +| 25 000 | ~1.7 TB |
60
67 -- **Retention policy per tier** — caps how long each tier is kept and how much disk it can use.
68 -- **Cardinality and dedup** — flow records are indexed and key-value pairs are deduplicated. Low-cardinality traffic stores fewer bytes per flow than high-cardinality traffic, because repeated values share dictionary entries.
61 +These numbers are dominated by raw-tier writes; rollup tiers (1m, 5m, 1h) add a small constant on top because each rollup row aggregates many raw rows.
62
70 -Because the journals are not append-only logs, `flow_rate × bytes_per_flow × time` is not a valid estimator.
63 +The number is sensitive to traffic cardinality (how unique the 5-tuples are). Real-world traffic with many repeated flows trends a bit lower; pathological all-unique traffic trends a bit higher.
64
72 -### Empirical measurement on this hardware class
65 +### Raw tier dominates — keep it bounded
66
74 -A 15-minute run of paced ingest at 10 000 flows/s with the full pipeline active (raw + 1m + 5m + 1h tiers, real disk-backed journals) produced:
67 +The raw tier carries every individual flow record. **Rollup tiers (1m, 5m, 1h) are tiny by comparison** — each row in a rollup tier aggregates many raw flows.
68
76 -| | Low cardinality (256 unique records) | High cardinality (4 096 unique records) |
77 -|---|---:|---:|
78 -| Flows ingested | 9.00 million | 8.97 million |
79 -| On-disk total | 6.46 GiB | 7.29 GiB |
80 -| Bytes per stored flow | **771** | **872** |
81 -| Write amplification (real I/O / logical encoded) | 1.79× | 2.00× |
82 -| Raw tier (final) | 6.45 GiB | 7.13 GiB |
83 -| 1-minute tier | 8 MiB | 112 MiB |
84 -| 5-minute tier | 8 MiB | 40 MiB |
85 -| 1-hour tier | 0 (rollup not reached in 15 min) | 16 MiB |
69 +Set raw-tier retention to match your forensic window — typically **24 hours**. Rollup tiers can keep weeks to a year of history at a small fraction of raw-tier cost.
70
87 -Two key observations:
71 +The example below is sized for an agent at the **upper end of the per-host scaling envelope (25 000 flows/s sustained)** — the raw tier needs about 1.7 TB / day at that rate (see the table above), so the 2 TB raw budget gives a small safety margin to keep the duration limit (24 h) the one that fires first:
72
89 -- **Dedup is effective.** High cardinality stores only 13% more per flow despite 16× more unique field combinations. Real exporter traffic, which has heavy repetition (same src/dst/protocol patterns), will compress closer to the low-cardinality figure.
90 -- **Raw is 99% of the on-disk cost** at 15 minutes. The rollup tiers are small in absolute size because each rollup row aggregates many raw flows.
73 +```yaml
74 +journal:
75 + tiers:
76 + raw: { size_of_journal_files: 2TB, duration_of_journal_files: 24h }
77 + minute_1: { size_of_journal_files: 20GB, duration_of_journal_files: 14d }
78 + minute_5: { size_of_journal_files: 20GB, duration_of_journal_files: 30d }
79 + hour_1: { size_of_journal_files: 20GB, duration_of_journal_files: 365d }
80 +```
81
92 -### Bounding storage for capacity planning
82 +For lighter loads, scale `size_of_journal_files` on the raw tier down proportionally — at 10 000 flows/s ~700 GB / 24 h is enough; at 1 000 flows/s ~70 GB / 24 h is enough. Whichever limit (size or duration) is hit first triggers rotation; size your raw tier so the **duration limit fires first** under normal load and the size cap is a safety net for traffic surges.
83
94 -Set retention limits explicitly and let them bound the disk footprint:
84 +### Use fast NVMe for the raw tier
85
96 -- raw: typically 24 hours
97 -- 1-minute tier: 14 days
98 -- 5-minute tier: 30 days
99 -- 1-hour tier: 365 days
86 +The raw tier is queried directly for any IP-level investigation, full-text search, city / latitude / longitude maps, and anything that filters on a raw-only field (see [Field Reference](/docs/network-flows/field-reference.md) for which fields survive into rollups). At 25 000 flows/s sustained, the raw tier produces 1.7 TB / day of indexed writes that you may also be reading back in real time.
87
101 -Configure per-tier `size_of_journal_files` (hard cap) and `duration_of_journal_files` (time cap). The plugin enforces whichever limit is hit first.
88 +This is **fast-NVMe territory**. 1.7 TB/day of write throughput is well within a modern PCIe Gen4 / Gen5 NVMe drive but punishes SATA SSDs (queue-depth and write-endurance) and HDDs (IOPS) once concurrent queries land on the same device. A modern PCIe Gen4 / Gen5 NVMe is what you want for the raw-tier directory. Rollup tiers (1m / 5m / 1h) are far less I/O-intensive and can live on slower storage if needed, but in practice it's easier to put the whole journal directory on one fast device.
89
103 -For your own measurement, run the plugin against representative traffic for at least 15 minutes and inspect `du -sh` on each tier directory. The `bench_storage_footprint_child` test in this repository ships the same measurement harness used to produce the table above.
90 +If the raw tier exceeds the device capacity for your retention target, **shorten raw-tier retention** before you switch to slower storage. A 12-hour raw tier on fast NVMe queries cleanly; a 7-day raw tier on slow storage will time out queries.
91
92 ## Memory
93
107 -Memory consumption is dominated by:
94 +The journal backend uses **free system memory as page cache** — the bigger the database on disk, the more free RAM you want to keep available so the kernel can keep the working set hot.
95 +
96 +Concrete guidance:
97 +
98 +- For the agent process itself, expect **a few hundred MB to ~1 GB of RSS** at typical 5-25k flows/s loads. Enrichment, classifiers, accumulators, and routing tries add to the base process footprint. BMP / BioRIS full-table feeds can add a few hundred MB per peer, depending on table count and prefix mix.
99 +- For the kernel page cache, aim to **leave at least the size of the recently-queried working set free** — practically, plan a few GB of free RAM on a 25k flows/s agent so query I/O lands in cache instead of hitting NVMe each time.
100 +- Watch `netflow.memory_resident_bytes`, `netflow.memory_resident_mapping_bytes`, and `netflow.memory_accounted_bytes` for the agent's own footprint. Watch the system's overall free memory for the page-cache headroom.
101
109 -- **Active journal rows** — flow records currently being accumulated before they are flushed to disk
110 -- **Field indexes** — structures that map field values (IPs, ASNs, ports) for fast filtering
111 -- **Facet indexes** — structures that power the filter sidebar
112 -- **GeoIP MMDB** — the IP-intelligence database (DB-IP-based by default) loaded into memory for enrichment
102 +The plugin does **not** preload the journal into RAM. Memory consumption is driven by active accumulators (during ingestion) and routing tries (when configured). Storage growth pressures memory only via the page cache, which the kernel manages.
103
114 -The plugin exposes memory charts you can monitor:
104 +## Querying — what's fast and what isn't
105
116 -- `netflow.memory_resident_bytes` — total memory in use
117 -- `netflow.memory_allocator_bytes` — memory from the system allocator
118 -- `netflow.memory_accounted_bytes` — memory broken down by component (indexes, GeoIP, facets)
119 -- `netflow.memory_tier_index_bytes` — memory used by tiered storage indexes
120 -- `netflow.decoder_scopes` — protocol decoder memory usage
106 +The journal is **fully indexed**: every field is indexed, exact-match selections (`SRC_AS_NAME = "AS15169 GOOGLE"`, `PROTOCOL = 6`) hit the index and return quickly regardless of how much data the tier holds.
107
122 -## Disk I/O
108 +The exception is the **full-text search box** in the dashboard. FTS runs as a regex against the raw journal payload bytes — it is a **full scan** of the matching tier. Any non-empty FTS query also forces the query to the raw tier (FTS is meaningless on aggregated rollup rows). That means:
109
124 -The plugin writes flow records to journal files continuously. Writes are dominated by the raw tier; the rollup tiers add a small amount on top. SSDs are recommended for collectors that handle thousands of flows per second — the index updates and frequent fsync calls benefit substantially from low-latency storage.
110 +- A full-text search over a 24-hour raw tier with 25k flows/s sustained scans hundreds of GB. It runs but it is not fast.
111 +- For fast queries, use **filters on indexed fields** (the filter ribbon, exact selections). Reserve full-text for the cases where you don't have an indexed handle.
112 +- The 30-second hard query timeout is a real ceiling for FTS over wide windows. Narrow the time range, add an indexed filter, or switch to a rollup tier (which means dropping the FTS).
113
126 -Read operations only happen during queries. There is no background read activity in steady state.
114 +## Practical checklist before you deploy
115
128 -:::tip
129 -For production deployments, monitor the `netflow.memory_resident_bytes` chart and set a threshold alert. If resident memory grows steadily without stabilising, check your cardinality and consider reducing retention or increasing the sync interval.
130 -:::
116 +1. **Estimate sustained flows/s** for the worst-case router or site. If it exceeds ~25k/s, plan distributed before iron.
117 +2. **Pick raw-tier retention** that matches your forensic window — typically 24 hours.
118 +3. **Compute storage** for that retention from the table above; size the raw-tier directory with a realistic safety margin, usually **1.2× to 1.5×** depending on burstiness and available disk.
119 +4. **Use NVMe** for the raw tier. Slower storage shortens raw-tier retention until queries are responsive.
120 +5. **Leave RAM headroom** for the page cache on top of the agent's own ~1 GB RSS budget.
121 +6. **Tune kernel UDP buffers** for burst headroom (see [Troubleshooting](/docs/network-flows/troubleshooting.md)).
122 +7. **For multi-site deployments**, run one Netdata Agent per router or per site rather than central aggregation.
123
124 ## What's next
125
134 -- [Configuration](/docs/network-flows/configuration.md) — Per-tier retention configuration and tuning knobs.
135 -- [Retention and Querying](/docs/network-flows/retention-querying.md) — How tiers are picked at query time.
136 -- [Validation and Data Quality](/docs/network-flows/validation.md) — How to confirm the numbers in your environment.
137 -- [Plugin Health Charts](/docs/network-flows/visualization/dashboard-cards.md) — Monitoring the plugin itself.
126 +- [Configuration](/docs/network-flows/configuration.md#per-tier-retention) — Per-tier retention schema.
127 +- [Retention and Querying](/docs/network-flows/retention-querying.md) — How tiers map to queries and the auto-tier-pick rules.
128 +- [Field Reference](/docs/network-flows/field-reference.md) — Which fields survive into rollups and which are raw-only.
129 +- [Troubleshooting](/docs/network-flows/troubleshooting.md) — UDP buffer tuning, query timeout, and disk write pressure.
docs/network-flows/troubleshooting.md
+29 -32
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows"
6 keywords: ['troubleshooting', 'debugging', 'plugin health', 'failures']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Troubleshooting
12
13 Concrete recipes for the most common failures, organised by symptom. Most issues are diagnosable from the [plugin health charts](/docs/network-flows/visualization/dashboard-cards.md), the Netdata journal logs, and a couple of OS-level commands.
@@ -16,14 +18,14 @@ The plugin won't come up at all, or starts and immediately exits.
18
19 **Symptoms:**
20 - Netdata reports `netflow-plugin` as not running, or restart-looping.
19 -- Nothing in the Network Flows tab.
20 -- An error in `journalctl -u netdata`.
21 +- Nothing in the Network Flows view.
22 +- An error in `journalctl --namespace netdata`.
23
24 **Likely causes:**
25
26 | Cause | What to check |
27 |---|---|
26 -| YAML typo or unknown key | `journalctl -u netdata --since "5 minutes ago" \| grep -E 'failed to load configuration\|netflow'`. The plugin uses strict YAML — any unknown key fails parsing. |
28 +| YAML typo or unknown key | `journalctl --namespace netdata --since "5 minutes ago" \| grep -E 'failed to load configuration\|netflow'`. The plugin uses strict YAML — any unknown key fails parsing. |
29 | Required GeoIP DB missing (`optional: false`) | Same log search. Look for `failed to load database`. Either fix the path or set `optional: true`. |
30 | Listen address conflict | Look for `failed to bind`. Another process is on the configured port (default 2055). |
31 | Validation error | Look for `must be greater than 0` and similar. The plugin validates the full config at startup. |
@@ -33,7 +35,7 @@ The plugin won't come up at all, or starts and immediately exits.
35
36 ```bash
37 # Read the failure
36 -sudo journalctl -u netdata --since "5 minutes ago" | grep -E 'netflow|failed to|error'
38 +sudo journalctl --namespace netdata --since "5 minutes ago" | grep -E 'netflow|failed to|error'
39
40 # Validate the YAML (use an online linter or `yamllint`)
41 yamllint /etc/netdata/netflow.yaml
@@ -44,7 +46,7 @@ sudo systemctl restart netdata
46
47 ## The plugin starts, but no flows appear
48
47 -The plugin is running, but the Network Flows tab is empty.
49 +The plugin is running, but the Network Flows view is empty.
50
51 **First check:** is anything reaching the plugin?
52
@@ -69,7 +71,7 @@ Open `netflow.input_packets` on the standard Netdata charts page. The dimensions
71
72 - `udp_received > 0`, `parsed_packets == 0` — datagrams arriving, none decoding successfully. Wrong protocol on the listener, or all datagrams malformed.
73 - `udp_received > 0`, `parsed_packets > 0`, but no per-protocol counter (`netflow_v9`, `ipfix`, etc.) is moving — the protocol you're sending may be disabled in the plugin config. Check `protocols.v9`, `protocols.ipfix`, etc. in `netflow.yaml`.
72 -- `parse_errors` rising in lockstep with `udp_received` — datagrams aren't valid for the protocols the plugin supports. Capture a sample (`tcpdump -w sample.pcap`) and inspect with Wireshark.
74 +- `parse_errors` rising in lockstep with `udp_received` — datagrams aren't valid for the protocols the plugin supports. Capture a small UDP sample with `tcpdump -w` and inspect it with Wireshark.
75
76 ## Partial data — some flows are dropped
77
@@ -84,8 +86,8 @@ Counters show received traffic but you suspect data loss.
86
87 If it's climbing, the exporter is sending data records before their templates. Either:
88
87 -- The exporter restarted and the plugin's template cache is stale. Wait for the exporter to send the next template (typically every 30-60 seconds, depending on its config), or restart the exporter to force an immediate template refresh.
88 -- Templates are sent rarely (Cisco's default template refresh is 30 minutes). After a plugin restart, you'll see template errors for that long. **Fix on the router side**: lower the template refresh interval to 60 seconds.
89 +- The exporter restarted and the plugin's template cache is stale. Wait for the exporter to send the next template (the cadence depends on the exporter's `template-refresh` configuration — vendor defaults vary widely), or restart the exporter to force an immediate template refresh.
90 +- Templates are sent rarely. Cisco IOS / IOS-XE Flexible NetFlow ships a default `template data timeout` of **600 seconds (10 minutes)**; Juniper and others have their own defaults, often longer. After a plugin restart, you'll see template errors until the next template re-send. **Fix on the router side**: lower the template refresh interval to 60 seconds (the [Quick Start](/docs/network-flows/quick-start.md) configurations show this).
91 - The exporter is using template IDs that collide with another exporter's templates. Most common cause: two exporters NATted behind the same public IP. Place the plugin inside the NAT boundary or give each exporter a distinct address.
92
93 **UDP kernel drops:**
@@ -93,10 +95,12 @@ If it's climbing, the exporter is sending data records before their templates. E
95 The plugin doesn't count these. Check at the OS level:
96
97 ```bash
96 -sudo ss -uam sport = :2055 # check 'd' columns for drops
97 -cat /proc/net/udp | head -20 # RcvbufErrors column
98 +sudo ss -uamn sport = :2055 # inspect the d<N> field inside skmem:(...)
99 +grep ^Udp: /proc/net/snmp # RcvbufErrors counter (system-wide)
100 ```
101
102 +`/proc/net/udp` lists open sockets and includes per-socket `drops`; the kernel-wide UDP `RcvbufErrors` total lives under the `Udp:` line of `/proc/net/snmp` (this is what Netdata's own `ipv4.udperrors` chart and the `1m_ipv4_udp_receive_buffer_errors` alert read).
103 +
104 If drops are occurring, the kernel UDP receive buffer is too small for the burst rate. Tune:
105
106 ```bash
@@ -118,25 +122,19 @@ protocols:
122
123 **Volume looks doubled:**
124
121 -This is the most common report. With one router, traffic appears 2× because every packet generates an ingress record AND an egress record. With two routers on the same path, 4×. Filter to one exporter + one direction (input interface OR output interface) to see real volume. See [Anti-patterns](/docs/network-flows/anti-patterns.md).
125 +This is the most common report. When a router is configured to export both ingress and egress on each monitored interface — a common configuration; vendor best practice is ingress-only — every packet generates an ingress record AND an egress record, so traffic appears 2× on a single such router. With two routers on the same path doing the same thing, 4×. Filter to one exporter and one interface (`Ingress Interface Name` OR `Egress Interface Name`, pick one) to see real volume. See [Anti-patterns](/docs/network-flows/anti-patterns.md).
126
127 **Bandwidth doesn't match SNMP:**
128
129 Several legitimate causes:
130
127 -- **Doubling**, as above. Filter properly before comparing.
128 -- **Sampling rate not honoured.** The plugin auto-multiplies bytes by the sampling rate, but if the exporter doesn't carry the rate (NetFlow v7 has no field for it; v5 sometimes sends 0 instead of the actual rate; v9 may not send the Sampling Options Template), the result is undercounted.
129 -- **Mixed sampling rates across exporters.** If your dashboard aggregates exporters with different rates, the result blends estimates and isn't comparable to any single SNMP measurement.
131 +- **Doubling**, as above. Filter to one exporter and one interface before comparing.
132 +- **Comparing aggregates to a single interface counter.** SNMP `ifInOctets` / `ifOutOctets` is per-interface; an unfiltered flow aggregate sums many interfaces. Compare like-with-like by filtering the dashboard to the same exporter and the same interface (Input or Output, pick one).
133 +- **Sampling rate not honoured by the exporter.** The plugin multiplies each flow's bytes/packets by that flow's own sampling rate. If the exporter doesn't carry the rate (NetFlow v7 has no field for it; v5 sometimes sends 0 instead of the actual rate; v9 / IPFIX without the Sampling Options Template), the plugin treats those records as unsampled and undercounts.
134 - **SNMP includes layer-2 traffic** (ARP, STP, LLDP, routing protocols) that flow data filters out. Expect SNMP to be 5-15% higher than flow on a healthy collector. More than that, investigate.
135
136 See [Validation and Data Quality](/docs/network-flows/validation.md).
137
134 -**Internal IPs in random countries:**
135 -
136 -GeoIP databases don't have entries for RFC 1918 / private space. The plugin doesn't skip private IPs — it just hands the IP to the database and uses what comes back. For the stock DB-IP build, private ranges are tagged so they render as "AS0 Private IP Address Space" with empty country. For other MMDBs, private ranges may resolve to weird countries.
137 -
138 -**Fix:** declare your internal CIDRs under `enrichment.networks` with country / role / name labels. See [Static metadata](/docs/network-flows/enrichment/static-metadata.md).
139 -
138 **AS resolution chain misbehaving:**
139
140 If `SRC_AS` / `DST_AS` are zero everywhere despite the exporter sending them, check the `asn_providers` chain:
@@ -144,7 +142,7 @@ If `SRC_AS` / `DST_AS` are zero everywhere despite the exporter sending them, ch
142 - `[geoip, ...]` — `geoip` is a terminal short-circuit. The chain stops at `geoip` (it returns 0). Reorder: `[flow, routing, geoip]`.
143 - `[]` (empty) — no validation rejects this. Every AS is forced to 0.
144
147 -See [ASN resolution](/docs/network-flows/enrichment/asn-resolution.md).
145 +See [Enrichment](/docs/network-flows/enrichment.md) (the asn_providers chain section).
146
147 **Decapsulation eating non-tunnel traffic:**
148
@@ -183,14 +181,13 @@ See [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md) for m
181 sudo du -sh /var/cache/netdata/flows/*
182 ```
183
186 -Default retention is `10GB / 7d` per tier — the same budget applies to all four tiers, so total can reach roughly 40 GB plus some. If your config left this default and your collector is busy, expect to hit it. See [Configuration](/docs/network-flows/configuration.md) for per-tier overrides — most production deployments need them.
184 +Default retention is `10GB / 7d` per tier. The default is applied separately to raw, 1m, 5m, and 1h tiers, so total can reach roughly 40 GB plus some. If your config left this default and your collector is busy, expect to hit the size cap before the time cap. See [Configuration](/docs/network-flows/configuration.md) for per-tier overrides — most production deployments need them.
185
186 ## Things that look like bugs but aren't
187
190 -- **Traffic appears 2×.** Standard ingress + egress monitoring. Filter to one direction.
191 -- **Bidirectional conversations show twice.** A→B and B→A are real, distinct flows. Filter to one direction or one ASN to see one side.
192 -- **Internal IPs in odd countries.** GeoIP doesn't know about your private space. Declare it explicitly.
193 -- **City map empty over long windows.** City + lat/lon are tier-0-only. Default tier-0 retention is short. Use the country map for long ranges.
188 +- **Traffic appears 2×.** When the router is configured to export both ingress + egress (common, but not universal — vendor best practice is ingress-only), the same packet is recorded once on entry and once on exit on a single router. Filter to one exporter and one interface (`Ingress Interface Name` or `Egress Interface Name`, pick one).
189 +- **Bidirectional conversations show twice.** A→B and B→A are real, distinct flows representing different packets going each way. Their volumes are usually asymmetric. Filter by `Source AS Name` (your network) for outbound or `Destination AS Name` (your network) for inbound to see one side.
190 +- **City map empty over long windows.** City + lat/lon are raw-tier-only. Default raw-tier retention is short. Use the country or state map for long ranges.
191 - **`__overflow__` row in results.** Your aggregation produced more groups than `query_max_groups`. Narrow the filter or reduce group-by depth.
192 - **30-second query timeout.** Hard limit. Narrow time range, add filters, or reduce group-by depth.
193 - **Sampled byte counts not exact.** sFlow is statistical by design; even NetFlow with sampling is an estimate. Cross-check against SNMP for sanity, accept some divergence.
@@ -200,7 +197,7 @@ Default retention is `10GB / 7d` per tier — the same budget applies to all fou
197
198 ```bash
199 # What's happening
203 -sudo journalctl -u netdata --since "10 minutes ago" | grep -iE 'netflow|geoip|bmp|bioris|network-sources'
200 +sudo journalctl --namespace netdata --since "10 minutes ago" | grep -iE 'netflow|geoip|bmp|bioris|network-sources'
201
202 # What's arriving on the wire
203 sudo tcpdump -i any -nn -c 50 'udp port 2055'
@@ -209,8 +206,8 @@ sudo tcpdump -i any -nn -c 50 'udp port 2055'
206 sudo ss -unlp | grep 2055
207
208 # UDP kernel drops
212 -sudo ss -uam sport = :2055
213 -cat /proc/net/udp
209 +sudo ss -uamn sport = :2055
210 +grep ^Udp: /proc/net/snmp
211
212 # Disk usage by tier
213 sudo du -sh /var/cache/netdata/flows/*
@@ -219,7 +216,7 @@ sudo du -sh /var/cache/netdata/flows/*
216 top -p $(pgrep -f netflow-plugin)
217
218 # Capture a sample for offline analysis
222 -sudo tcpdump -w /tmp/netflow-sample.pcap -c 200 'udp port 2055'
219 +sudo tcpdump -w /tmp/netflow-sample.cap -c 200 'udp port 2055'
220 ```
221
222 ## When to file an issue
@@ -229,9 +226,9 @@ Collect this before opening a bug report:
226 - Plugin version (`netdata --version` from the running daemon).
227 - A sample of `netflow.input_packets` chart for the failure window — all dimensions visible.
228 - A sample of `netflow.memory_resident_bytes` if performance-related.
232 -- A captured pcap (`tcpdump -w` from the agent's interface) reproducing the issue.
229 +- A small packet-capture file (`tcpdump -w` from the agent's interface) reproducing the issue.
230 - Sanitised `netflow.yaml` (redact internal IPs, customer names, secrets).
234 -- Relevant log lines from `journalctl -u netdata`.
231 +- Relevant log lines from `journalctl --namespace netdata`.
232
233 Open issues against [github.com/netdata/netdata](https://github.com/netdata/netdata) with `area/collectors/netflow` in the title.
234
docs/network-flows/validation.md
+53 -56
@@ -3,83 +3,76 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-fl
3 sidebar_label: "Validation and Data Quality"
4 learn_status: "Published"
5 learn_rel_path: "Network Flows"
6 -keywords: ['validation', 'snmp cross-check', 'data quality', 'silent failures', 'sanity check']
6 +keywords: ['validation', 'snmp cross-check', 'data quality', 'sanity check']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Validation and Data Quality
12
11 -Flow data is statistical. It can be wrong in subtle ways that the dashboard cannot detect — silent UDP drops, undocumented sampling rate changes, exporters that stopped sending. This page is the routine you should run when you set up the plugin, when something looks suspicious, and periodically thereafter.
13 +The plugin handles per-flow sampling-rate multiplication, template persistence, and database refresh internally — these are not concerns you have to monitor. What's left to validate is whether the data you see corresponds to the traffic that actually crossed your network. This page is the routine to confirm that.
14
15 The goal: distinguish "the data is correct" from "the data looks plausible but isn't".
16
15 -## The biggest risks are silent failures
17 +## What you actually need to watch
18
17 -The most dangerous failures don't generate alerts. They look like data is flowing — just less of it, or skewed, or scaled wrong. Six common silent failures:
19 +A small number of failure modes need active monitoring. Most are signalled by Netdata's existing alerts; the rest you check periodically.
20
19 -1. **UDP datagram drops** — kernel drops happen when the receive buffer fills. Plugin sees fewer datagrams than the network sent. Counters are smaller; nothing logs the drop.
20 -2. **Sampling rate misinterpretation** — exporter samples 1-in-1000, no one documented it. Bytes look 1000× smaller than reality.
21 -3. **Sampling rate change** — someone reconfigures a router. Trends show a phantom 10× spike. No alert fires.
22 -4. **Wrong interfaces being exported** — flow export was enabled on three of five interfaces. Some traffic is invisible.
23 -5. **Template loss after collector restart** — v9 / IPFIX records arrive but cannot be decoded until the next template arrives. Counts dip silently.
24 -6. **Stale GeoIP / ASN database** — country and AS-name fields drift away from reality over weeks.
21 +| Failure mode | Detection | Notes |
22 +|---|---|---|
23 +| **Kernel-level UDP receive-buffer drops** | The system alert `1m_ipv4_udp_receive_buffer_errors` fires when the `RcvbufErrors` rate averages more than **10 errors/second** over a 1-minute window. The dimension is incremental in Netdata, so the numeric threshold is per-second, not per-minute. | OS-wide signal. Ships as `to: silent` by default -- change the `to:` field in your alert config to receive notifications. Tune `net.core.rmem_max` (see [Troubleshooting](/docs/network-flows/troubleshooting.md)). |
24 +| **An exporter stopped sending** | Filter the dashboard to that exporter; rate dropped to zero. Per-exporter ingest counters are not published. | Manual periodic spot-check or external monitoring. |
25 +| **Wrong set of interfaces being exported** | Cross-check `show flow exporter` (or vendor equivalent) on each router against the interfaces you intended. | Configuration drift over time; audit quarterly. |
26 +| **An exporter is sampling but not communicating the rate** | The plugin treats those records as unsampled, so volumes are undercounted by the sampling factor. Cross-check against SNMP. | NetFlow v7 has no rate field; v5 sometimes sends `rate=0`; v9 / IPFIX without a Sampling Options Template lose the per-flow rate. See "Sampling rate verification" below. |
27 +| **Stale GeoIP / ASN database** | No in-process signal. Check file mtimes; refresh on the schedule the provider recommends. | DB-IP and GeoLite2 ship monthly. |
28
26 -For each, the system appears to be working. The only way to detect them is cross-validation against an independent source.
29 +The plugin handles sampling-rate changes per flow record, and it persists decoder templates under `journal_dir/decoder-state.d/` so they reload automatically across restarts. You still need external validation for packet drops, exporter drift, stale enrichment data, and exporters that do not report their sampling rate.
30
31 ## The minimum viable validation routine
32
33 Run this once after deployment, then quarterly, plus whenever something looks off.
34
32 -### 1. SNMP cross-check (every 5 minutes if you have an SNMP collector handy)
35 +### 1. SNMP cross-check
36
37 Compare flow-derived bandwidth on a specific interface to the SNMP `ifInOctets` / `ifOutOctets` counter for that same interface. They should be close.
38
36 -The flow-derived bandwidth: filter the dashboard to one exporter, one input interface (or one output interface — pick a direction), and read the bytes/s rate.
39 +The flow-derived bandwidth: filter the dashboard to one exporter and one interface (Ingress Interface Name OR Egress Interface Name — pick one), and read the bytes/s rate.
40
38 -The SNMP-derived bandwidth: from your SNMP monitoring (Netdata's snmp.d, your separate SNMP system, or your network team).
41 +The SNMP-derived bandwidth: from your SNMP monitoring (Netdata's `snmp.d`, your separate SNMP system, or your network team).
42
43 **Acceptable difference: roughly 5-15%.** SNMP includes layer-2 traffic (ARP, STP, LLDP, routing protocols, interface-level multicast) that flow data filters out. Expect SNMP slightly higher.
44
45 **Not acceptable: more than 30% gap.** That indicates one of:
46
44 -- UDP drops (kernel-level). Run `sudo ss -uam sport = :2055` and check the `dRcv` column.
45 -- Sampling rate not honoured. The exporter is sampling but not communicating the rate to the plugin (NetFlow v7, NetFlow v5 with rate=0, v9 / IPFIX without the Sampling Options Template).
46 -- Wrong interfaces being exported. Cross-check `show flow exporter` (or vendor equivalent) against your expectations.
47 -- Template loss. Watch `netflow.input_packets > template_errors` on the plugin health charts.
47 +- UDP drops at the kernel. Check the alert mentioned above, or run `sudo ss -uamn sport = :2055` and inspect the `d<N>` value inside the `skmem:(...)` line (the `sock_drop` counter increments on every dropped datagram). The `-n` flag keeps the port numeric in the output.
48 +- Sampling rate not honoured (the exporter is sampling but not communicating the rate). See "Sampling rate verification" below.
49 +- Wrong interfaces being exported.
50
49 -**Plugin reporting wildly more than SNMP** indicates the doubling effect (see below).
51 +**Plugin reporting wildly more than SNMP** indicates the doubling effect — see below.
52
53 ### 2. Doubling sanity check
54
53 -If your dashboard's total bandwidth exceeds the **physical link capacity**, you're double-counting. Standard NetFlow / IPFIX configuration produces two flow records per packet (one ingress, one egress). With multiple monitored routers on the same path, even more.
54 -
55 -Verify by: filter to one exporter and one interface in one direction (input OR output, not both). Compare to SNMP for that same interface. They should agree within 5-15%. The difference between "all flows summed" and "filtered to one direction" is exactly the doubling factor.
55 +If your dashboard's total bandwidth exceeds the **physical link capacity**, you're double-counting. When both ingress and egress flow exporters are enabled on the same router — a common but not universal configuration; vendor best practice is ingress-only — each packet is recorded twice. With multiple monitored routers on the same path, even more.
56
57 -### 3. Sampling rate sanity check
57 +Verify by: filter to one exporter and one interface (Ingress Interface Name OR Egress Interface Name, pick one). Each packet then appears in exactly one record on that interface. Compare to SNMP for that same interface; they should agree within 5-15%.
58
59 -For each exporter, document:
59 +### 3. Sampling rate verification
60
61 -- Does it sample? At what rate?
62 -- Does it carry the rate in flow records (NetFlow v9 / IPFIX) or in the header (v5)?
63 -- For NetFlow v9 / IPFIX, does the exporter send a Sampling Options Template? At what frequency?
61 +The plugin multiplies bytes and packets by the sampling rate each flow carries — **per flow, at ingestion**. You don't have to keep rates uniform across exporters; mixed rates are scaled correctly.
62
65 -If the exporter samples and the plugin doesn't see the rate, bytes are undercounted.
63 +What you DO need to verify, once per exporter, is that the plugin is actually seeing the rate. The dashboard does not surface the per-flow `SAMPLING_RATE` field as a filter, group-by, or facet, so the verification is by **magnitude**, not by reading the rate directly:
64
67 -To verify the plugin sees the rate: query a known flow on the dashboard and look at `RAW_BYTES` and `BYTES`. If they differ, the plugin is multiplying — sampling rate is being honoured. If they're identical, the plugin sees rate 1 (no scaling).
65 +- After the plugin has data, compare the dashboard's bytes/s on a known interface to that interface's SNMP `ifInOctets` / `ifOutOctets` rate. If the dashboard reading is roughly the SNMP figure divided by the configured sampling rate (e.g. ~1/1000 of SNMP at 1-in-1000), the plugin saw the rate as `1` and is *not* multiplying. If the dashboard agrees with SNMP within 5-15%, the plugin is honouring the rate.
66 +- The "sampling but not telling" case happens with NetFlow v7 (no rate field), v5 with rate=0, and v9 / IPFIX exporters that don't send a Sampling Options Template. Fix on the exporter side, or apply `enrichment.override_sampling_rate` per source prefix to substitute a known rate (see [Configuration → enrichment](/docs/network-flows/configuration.md#enrichment) and the [Static Metadata integration card](/src/crates/netflow-plugin/integrations/static_metadata.md)).
67
68 ### 4. Per-exporter health check
69
71 -The plugin doesn't publish per-exporter ingest counters today. To verify each exporter is sending:
72 -
73 -- Filter the dashboard to one exporter at a time. Check the byte rate. A healthy edge router during business hours should show non-zero traffic.
74 -- An exporter that abruptly drops to zero is offline (silently). The plugin won't tell you — your monitoring practice has to.
75 -
76 -### 5. Template cache health (NetFlow v9 / IPFIX)
70 +Per-exporter ingest counters are not published. To verify each exporter is sending:
71
78 -On the plugin health chart `netflow.input_packets`, watch `template_errors`. In steady state, it should be near zero. A sustained non-zero rate means data records are arriving before their templates — usually because the exporter sends templates rarely (every 30 minutes is common Cisco default) and the plugin's template cache was wiped (restart with no persistence, or first-time setup).
72 +- Filter the dashboard to one exporter at a time. A healthy edge router during business hours should show non-zero traffic.
73 +- An exporter that abruptly drops to zero is offline. The plugin won't tell you — your monitoring practice has to.
74
80 -The plugin persists template state across restarts to `decoder_state_dir`, so a routine restart shouldn't cause this. If it does, check the cache directory permissions.
81 -
82 -### 6. GeoIP / ASN database freshness
75 +### 5. GeoIP / ASN database freshness
76
77 The plugin doesn't publish a "MMDB last loaded" signal. To verify your databases aren't stale:
78
@@ -93,37 +86,41 @@ Files older than ~60 days are likely stale. Refresh:
86 sudo /usr/sbin/topology-ip-intel-downloader
87 ```
88
89 +Packaged 32-bit installs do not include `topology-ip-intel-downloader`; use the packaged stock MMDB payload there, or refresh the cache from a system that includes the downloader.
90 +
91 The plugin polls the files every 30 seconds — a successful refresh picks up automatically without restart.
92
98 -### 7. Internal IP enrichment validation
93 +To cross-check the on-disk size of each tier:
94 +
95 +```bash
96 +sudo du -sh /var/cache/netdata/flows/{raw,1m,5m,1h}
97 +```
98
100 -Before relying on geographic analysis, spot-check that internal IPs are properly handled. Filter to an internal source IP you know and look at the `SRC_COUNTRY` and `SRC_AS_NAME` fields:
99 +The raw tier dominates. If raw-tier disk usage is approaching the `size_of_journal_files` you set, plan retention vs. capacity (see [Sizing and Capacity Planning](/docs/network-flows/sizing-capacity.md)).
100
102 -- Empty / "AS0 Private IP Address Space" — correct.
103 -- Some random country — your GeoIP database is returning data for private space. Declare the range under `enrichment.networks` (see [Static metadata](/docs/network-flows/enrichment/static-metadata.md)).
101 +## Plugin-side signals worth alerting on
102
105 -## Quick reference: what to monitor and what alerts to consider
103 +These are charts the plugin already exposes. Tune the alert thresholds to your environment.
104
107 -| Signal | Where | What to alert on |
105 +| Signal | Where | Suggested alert |
106 |---|---|---|
109 -| `udp_received` rate dropped | `netflow.input_packets` chart | Sustained 0 during business hours |
110 -| `template_errors` rising | `netflow.input_packets` chart | Sustained > 1% of `udp_received` |
111 -| `parse_errors` rising | `netflow.input_packets` chart | Sustained > 5% of `udp_received` |
112 -| Memory growing (`unaccounted`) | `netflow.memory_accounted_bytes` | RSS grows linearly without ingest growth |
113 -| `decoder_scopes` unbounded growth | `netflow.decoder_scopes` chart | Monotonic growth over hours |
114 -| Disk full warnings | `netflow.raw_journal_ops` `write_errors` | Any non-zero |
115 -| SNMP-flow gap | external | More than 30% on a steady-state link |
116 -| Sampling rate change | router config diff (yours) | Any change to active timeout or sampling |
107 +| `udp_received` rate dropped | `netflow.input_packets` | Sustained 0 during business hours indicates the listener is up but no exporter is sending. |
108 +| `parse_errors` rising | `netflow.input_packets` | Sustained > 5% of `udp_received` indicates the wire is producing malformed datagrams. |
109 +| `template_errors` rising | `netflow.input_packets` | Sustained > 1% of `udp_received` after the warm-up period indicates an exporter sends data records before templates. |
110 +| Memory growing (`unaccounted`) | `netflow.memory_accounted_bytes` | RSS climbs linearly without ingest growth -- possible leak. |
111 +| `decoder_scopes` unbounded growth | `netflow.decoder_scopes` | An exporter is rotating template IDs; investigate per-router behaviour. |
112 +| Disk write errors | `netflow.raw_journal_ops` `write_errors` | Any non-zero indicates filesystem trouble. |
113 +| SNMP-flow gap | external | More than 30% on a steady-state link triggers the validation routine above. |
114
115 ## When to file a "data is wrong" investigation
116
117 Start an investigation when **two independent signals disagree**:
118
122 -- SNMP says 500 Mbps; flow data says 50 Mbps. Investigate sampling, drops, exporter coverage.
123 -- Flow data shows traffic to a country; threat intelligence says that country's ASN doesn't host known infrastructure. Investigate GeoIP or anycast.
119 +- SNMP says 500 Mbps; flow data says 50 Mbps. Investigate sampling, kernel drops, exporter coverage.
120 +- Flow data shows a destination ASN you don't expect; threat-intelligence or DNS resolution disagrees. Investigate ASN MMDB staleness or anycast.
121 - Last week's top talker disappeared this week. Investigate exporter health, routing changes, business-side changes.
122
126 -For each, read the [Anti-patterns](/docs/network-flows/anti-patterns.md) page first — most "data is wrong" reports are actually expected behaviour misread.
123 +For each, read the [Anti-patterns](/docs/network-flows/anti-patterns.md) page first — most "data is wrong" reports are expected behaviour misread.
124
125 ## What's next
126
docs/network-flows/visualization/dashboard-cards.md
+9 -9
@@ -6,11 +6,13 @@ learn_rel_path: "Network Flows/Visualization"
6 keywords: ['plugin health', 'metrics', 'operational charts', 'monitoring']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Plugin Health Charts
12
11 -The netflow plugin publishes its own operational charts under the `netdata.netflow.*` chart context. These appear on the standard Netdata charts page (alongside system metrics like CPU and memory), **not** inside the Network Flows tab. They are how you monitor the plugin itself: is it receiving data, are templates flowing, is memory growing, is disk being written.
13 +The netflow plugin publishes its own operational charts under the `netdata.netflow.*` chart context. These appear on the standard Netdata charts page (alongside system metrics like CPU and memory), **not** inside the Network Flows view. They are how you monitor the plugin itself: is it receiving data, are templates flowing, is memory growing, is disk being written.
14
13 -This is also where you look first when something seems wrong — long before opening the Network Flows tab.
15 +This is also where you look first when something seems wrong — long before opening the Network Flows view.
16
17 All charts update every 1 second.
18
@@ -69,22 +71,20 @@ This one breaks RSS down by what's mapped. Useful when you want to attribute "th
71
72 ## What's NOT in these charts
73
72 -A few signals that aren't published today:
74 +These charts do not include:
75
76 - **Per-exporter ingest counter.** No per-source rate dimension. Decoder-scope cardinality tells you how many sources, not how busy each one is.
75 -- **UDP socket drops.** Kernel-level drops (full receive buffer, NIC drops) are not surfaced. Use the OS-level metrics: `cat /proc/net/udp` (column `RcvbufErrors`) or `ss -uam`.
77 +- **UDP socket drops.** Kernel-level drops (full receive buffer, NIC drops) are not surfaced. Use OS-level signals: `sudo ss -uamn sport = :2055` for per-socket drops, or `grep ^Udp: /proc/net/snmp` for the system-wide `RcvbufErrors` counter.
78 - **Template cache hit ratio.** `template_errors` counts misses; there's no corresponding "hits" counter to form a ratio.
79 - **GeoIP staleness signal.** No "MMDB last loaded" timestamp or version. The mapping memory dimensions tell you if a database is loaded, not how old it is.
80 - **Per-tier query latency.** These charts cover ingest and storage; query-side performance isn't observable.
79 -- **BioRIS counters.** They're collected internally but not published as chart dimensions today.
80 -
81 -If you need any of these, mention it in an issue — they're not hard to add but haven't been needed enough yet.
81 +- **BioRIS counters.** BioRIS routing-state details are not published as chart dimensions.
82
83 ## How to use these charts for diagnosis
84
85 | Symptom | Look at | What it means |
86 |---|---|---|
87 -| Network Flows tab is empty | `netflow.input_packets` `udp_received` | Zero = no datagrams arriving (firewall? wrong port?). Non-zero with `parsed_packets` zero = wrong protocol or all datagrams malformed. |
87 +| Network Flows view is empty | `netflow.input_packets` `udp_received` | Zero = no datagrams arriving (firewall? wrong port?). Non-zero with `parsed_packets` zero = wrong protocol or all datagrams malformed. |
88 | Sudden drop in flows | per-protocol dimensions | Identifies which protocol stopped (helps narrow whether it's a router, a router class, or all routers). |
89 | Templates failing | `template_errors` rising | Exporter not sending templates often enough; collector lost cache; cache mismatch after firmware update. |
90 | Cache growing without bound | `decoder_scopes` rising over hours | Exporter churn or unstable template IDs. Investigate per-router behaviour. |
@@ -94,7 +94,7 @@ If you need any of these, mention it in an issue — they're not hard to add but
94
95 ## Where these are NOT shown
96
97 -These charts are **not** in the Network Flows tab. Look for them on the standard Netdata charts page, in the family `netflow`. The Network Flows tab itself shows traffic data, not plugin health.
97 +These charts are **not** in the Network Flows view. Look for them on the standard Netdata charts page, in the family `netflow`. The Network Flows view itself shows traffic data, not plugin health.
98
99 ## What's next
100
docs/network-flows/visualization/filters-facets.md
+8 -12
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows/Visualization"
6 keywords: ['filters', 'facets', 'autocomplete', 'search', 'fts', 'negative match', 'visualization']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Filters and Facets
12
13 The filter ribbon (between the visualisation and the table) is how you narrow flow data to the subset you want. Filters apply to every view — Sankey, table, time-series, country/state/city maps, globe — at once.
@@ -25,11 +27,11 @@ Within a single field: **OR**. Selecting `PROTOCOL = TCP` and `PROTOCOL = UDP` s
27
28 Across different fields: **AND**. Adding `SRC_COUNTRY = US` to the above shows TCP-or-UDP from the US.
29
28 -## No negative match (yet)
30 +## No negative match
31
32 You cannot directly say "everything except X". The workaround is to select all values and remove the unwanted one — works for low-cardinality fields like `PROTOCOL` (a handful of values). For high-cardinality fields like `SRC_AS_NAME`, the autocomplete only surfaces the top 100 values, so there's no practical way to "select all and remove".
33
32 -This is a real limitation. Negative match is a known feature gap.
34 +Negative matching is not supported.
35
36 ## Autocomplete
37
@@ -50,10 +52,10 @@ The search box at the top of the filter ribbon performs a regex match against th
52
53 - The search is **regex**, not literal. `8.8.8.8` is a regex where `.` matches any byte — so it can match `8a8b8c8`, `888x888`, etc. To match the literal string, escape with backslashes: `8\.8\.8\.8`.
54 - The match is **byte-level** against the journal payload, so it can find substrings inside enriched fields (AS names, exporter names, country codes).
53 -- Any non-empty search **forces tier 0**. The full-text search only works against the raw journal — it doesn't apply to the rollup tiers. Time depth is therefore bounded by raw-tier retention.
55 +- Any non-empty search **forces raw tier**. The full-text search only works against the raw journal — it doesn't apply to the rollup tiers. Time depth is therefore bounded by raw-tier retention.
56 - The plugin's "fast aggregation" path is also disabled when full-text search is active, because aggregation needs to scan every record. Expect somewhat slower responses than tier-based aggregation queries.
57
56 -For "find anything containing this string in any field", the search is the right tool. For "filter by an exact value of a specific field", use the facet on that field — it's faster and doesn't trigger tier-0 mode.
58 +For "find anything containing this string in any field", the search is the right tool. For "filter by an exact value of a specific field", use the facet on that field — it's faster and doesn't trigger raw-tier mode.
59
60 ## URL preservation
61
@@ -61,17 +63,11 @@ Every filter and selection is preserved in the dashboard URL. Copy the URL and s
63
64 A practical note: filters use a structured representation (per-field IN-list) that's easy to encode as a JSON payload but awkward to URL-encode. The dashboard handles this transparently for sharing — but anyone scripting their own queries against the function should use JSON-payload requests to the function, not GET-style args.
65
64 -## Facet limits
65 -
66 -`query_facet_max_values_per_field` (default `5000`) caps how many distinct values a single facet can return per query. Past that, the facet stops accumulating; the response carries an indicator. Useful when you have an extremely high-cardinality field — autocomplete still surfaces the top 100, but the full list is bounded.
67 -
68 -You can raise this limit in `netflow.yaml`. Higher values use more memory at query time.
69 -
66 ## Things that go wrong
67
68 - **Search for `192.168.1.1` matches unrelated rows.** Regex semantics: each `.` is "any byte". Escape: `192\.168\.1\.1`.
73 -- **Time depth shrinks unexpectedly after typing in search.** Full-text search forces tier 0. Clear the search to use rollup tiers and longer time ranges.
74 -- **Negative match isn't there.** Workaround: select-all-minus-one for low-cardinality fields. For high-cardinality fields, no good workaround exists today.
69 +- **Time depth shrinks unexpectedly after typing in search.** Full-text search forces raw tier. Clear the search to use rollup tiers and longer time ranges.
70 +- **Negative match is unsupported.** Workaround: select-all-minus-one for low-cardinality fields. For high-cardinality fields, use a positive filter that narrows the result set instead.
71 - **Filter on an ICMP virtual facet seems slower than expected.** `ICMPV4` / `ICMPV6` virtual facets aren't optimised by the journal index — they're evaluated per-record. The query still returns; the cost shows up as longer wall time on busy collectors.
72 - **`query_max_groups` exceeded.** Result rows after the limit fold into `__overflow__`. Narrow the filter or reduce group-by depth.
73 - **GET-style args don't carry selections.** When integrating the function call yourself, send a JSON payload — the dashboard does this automatically.
docs/network-flows/visualization/maps-globe.md
+11 -13
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows/Visualization"
6 keywords: ['country map', 'state map', 'city map', 'globe', 'visualization']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Maps and Globe
12
13 Four geographic views, all driven by the same aggregation engine as the Sankey and Time-Series:
@@ -48,7 +50,7 @@ The country map and state map can use the rollup tiers. They're cheap over long
50 The city map and the globe **need raw-tier data**. City, latitude, and longitude are dropped from the rollup tiers (1m / 5m / 1h) to keep cardinality manageable. So:
51
52 - Country / state map over the last 30 days — fine, uses the 1-hour tier.
51 -- City map over the last 30 days — likely empty. Tier 0 retention defaults to 7 days (shared budget across all tiers); often less in practice.
53 +- City map over the last 30 days — likely empty. Raw-tier retention defaults to its own 10GB / 7d limits; busy collectors often hit the raw-tier size cap before 7 days.
54
55 If your city map looks empty over a long window, try the country map first to confirm data is arriving, then narrow the time range until the city map fills in.
56
@@ -72,23 +74,19 @@ Globe view, top-N at 500, rotated over the Atlantic. The 3D projection shows US
74
75 ### GeoIP is required
76
75 -Without a GeoIP database, country / state / city / coordinate fields are empty and the maps are blank. The default install includes a stock DB-IP database — see [GeoIP enrichment](/docs/network-flows/enrichment/ip-intelligence.md). Source builds need the operator to run the downloader once.
76 -
77 -### Internal IPs in random countries
78 -
79 -If you see "traffic from China" or "traffic to Russia" coming from your own network, that's almost always GeoIP misidentifying internal IPs. The fix is to declare your internal CIDRs explicitly under `enrichment.networks` with a country override. See [Static metadata](/docs/network-flows/enrichment/static-metadata.md). Don't trust GeoIP for RFC 1918 / RFC 6598 / link-local addresses.
77 +Without a GeoIP database, country / state / city / coordinate fields are empty and the maps are blank. Native packages include a stock DB-IP database — see the [DB-IP integration card](/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md) and the [Enrichment Intel Downloader](/docs/network-flows/intel-downloader.md). Source builds need the operator to run the downloader once.
78
79 ### CDN traffic shifts
80
81 Your traffic to a SaaS provider may resolve to one country today and another tomorrow because the CDN's routing changed. This is normal CDN behaviour, not a security incident. ASN-based aggregation is more stable for cloud / CDN traffic than country-based — see the [Anti-patterns page](/docs/network-flows/anti-patterns.md) "Geographic firewall of shame".
82
85 -### Mirroring
83 +### Bidirectional traffic on the map
84
87 -Bidirectional conversations show up as two arcs (A→B and B→A). With the default 25 top-N, that means about 12 actual conversations get rendered, not 25. To see one direction only, filter on a specific source or destination.
85 +Bidirectional traffic between two endpoints produces two separate flow records (one per direction) and renders as two distinct edges (A→B and B→A). The two directions are usually asymmetric in volume — for example, a download is large in one direction and small in the other. To see only one direction, filter on a specific source or destination.
86
87 ### Globe vs City Map
88
91 -The globe and city map use the same data. The globe is purely a different rendering of the same response — useful for visual presentation, less useful for analysis (the 3D projection makes precise reading harder than a 2D map).
89 +The globe and city map render the same data with the same table beneath. The 2D city map is best for precise comparisons within a continent. The 3D globe is best when distance and great-circle paths matter — transcontinental traffic, undersea cable corridors, intercontinental CDN routing. Pick the one that fits the question.
90
91 ## What controls are available
92
@@ -100,15 +98,15 @@ The globe and city map use the same data. The globe is purely a different render
98
99 ## Things that go wrong
100
103 -- **City map empty.** Time range exceeds tier-0 retention. Narrow the range, or use country/state map for a wider view.
104 -- **Random countries appearing for internal traffic.** Declare your internal CIDRs in `enrichment.networks`.
101 +- **City map empty.** Time range exceeds raw-tier retention. Narrow the range, or use country/state map for a wider view.
102 - **Ireland or Singapore showing up unexpectedly.** Probably AWS/GCP/Azure shifting CDN routing. ASN-based aggregation is more stable.
103 - **A whole country disappears.** Your filter excluded it. Check the filter ribbon.
104 - **No data on globe but city map works.** Both should fail or succeed identically — they consume the same response. If they diverge, that's a dashboard bug worth reporting.
105
106 ## What's next
107
111 -- [GeoIP enrichment](/docs/network-flows/enrichment/ip-intelligence.md) — Required for any geographic visualization.
112 -- [Static metadata](/docs/network-flows/enrichment/static-metadata.md) — Declare your internal networks to override GeoIP for RFC 1918.
108 +- [Enrichment](/docs/network-flows/enrichment.md) — Order of evaluation and the MMDB shared mechanism that drives geographic visualisation.
109 +- [DB-IP integration card](/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md) — The default GeoIP source that ships with Netdata.
110 +- [Static Metadata integration card](/src/crates/netflow-plugin/integrations/static_metadata.md) — Declare your internal networks to override GeoIP for RFC 1918.
111 - [Filters and Facets](/docs/network-flows/visualization/filters-facets.md) — Narrowing geographic views.
112 - [Anti-patterns](/docs/network-flows/anti-patterns.md) — Why "alert on traffic to country X" is fragile.
docs/network-flows/visualization/overview.md new
+78
@@ -0,0 +1,78 @@
1 +<!--startmeta
2 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/network-flows/visualization/overview.md"
3 +sidebar_label: "Overview"
4 +learn_status: "Published"
5 +learn_rel_path: "Network Flows/Visualization"
6 +keywords: ['visualization', 'overview', 'queries', 'fts', 'url sharing', 'group-by']
7 +endmeta-->
8 +
9 +<!-- markdownlint-disable-file -->
10 +
11 +# Visualization
12 +
13 +The Network Flows view exposes the same query engine through five panel types: Sankey, Table, Time-Series, maps (country / state / city), and the 3D globe. The panels share their inputs (filters, group-by, time range, top-N) and their constraints (limits, timeout, FTS rules). This page documents what's common across them; each panel has its own page for the panel-specific reading.
14 +
15 +## How queries work
16 +
17 +The dashboard sends one of two query modes to the plugin:
18 +
19 +- **`flows`** — the normal aggregation request. Returns top-N groups, sums of bytes and packets, optional facet counts.
20 +- **`autocomplete`** — for the filter ribbon. Returns up to 100 facet values matching the user's term. Matching policy is per-field: text fields use substring matching, IP and numeric fields use prefix. Term is capped at 256 bytes. Runs against in-memory facet snapshots and on-disk FST sidecars; never scans tier files. Resulting filters apply as exact equality, not substring.
21 +
22 +A `flows` query carries:
23 +
24 +- A time range (`after` / `before`). If you omit both, the plugin uses the last 15 minutes.
25 +- A list of `group_by` fields (up to 10).
26 +- A list of `selections` — per-field IN-lists for filtering.
27 +- Optional `facets` to enrich the response with per-facet value counts.
28 +- A `top_n` (one of 25, 50, 100, 200, 500).
29 +- A `sort_by` (`bytes` or `packets`).
30 +- An optional regex `query` (full-text search; forces the raw tier).
31 +- A `view` (`table-sankey`, `timeseries`, `country-map`, `state-map`, `city-map`).
32 +
33 +Defaults if you don't specify: time range = last 15 minutes, `group_by = ["SRC_AS_NAME", "PROTOCOL", "DST_AS_NAME"]`, `top_n = 25`, `sort_by = bytes`, `view = table-sankey`.
34 +
35 +The plugin enforces a hard timeout of **30 seconds** per query. If your query is too wide, narrow the time range, add a filter that lets a coarser tier serve it, or reduce the group-by depth.
36 +
37 +## Group-by limit and overflow
38 +
39 +`query_max_groups` (default `50000`) caps the total number of distinct group keys an aggregation can build. Past this, additional groups are folded into a synthetic `__overflow__` bucket and the response carries a warning. The limit exists to protect the query worker from accidentally wide group-by combinations exhausting memory.
40 +
41 +If you see `__overflow__` rows, the query is too wide for the current limit. Narrow the filter, drop a high-cardinality `group_by` field, or raise the limit (carefully).
42 +
43 +## Full-text search
44 +
45 +The search box at the top of the filter ribbon performs a regex match against the raw journal payload bytes. Three things to know:
46 +
47 +- The search is **regex**, not literal. `8.8.8.8` is a regex where each `.` matches any byte — so it can match `8a8b8c8`, `888x888`, etc. To match the literal string, escape with backslashes: `8\.8\.8\.8`.
48 +- The match is **byte-level** against the journal payload, so it can find substrings inside enriched fields (AS names, exporter names, country codes).
49 +- Any non-empty search forces the query to the **raw tier**. Time depth is therefore limited by raw-tier retention.
50 +
51 +Use full-text search for the cases where you don't have an indexed handle for what you're looking for. For everything else, the filter ribbon (which uses indexed fields and is much faster) is the right tool.
52 +
53 +## URL sharing
54 +
55 +The dashboard URL preserves all of: time range, view, top-N, sort, group-by, selections, full-text search. Copy the URL and share it — the recipient sees exactly what you see, provided they have access to the same Netdata Cloud space.
56 +
57 +The dashboard also remembers your last selections per session, so subsequent visits land on whatever you had open last time.
58 +
59 +## Filtering
60 +
61 +Filtering uses a structured representation (per-field IN-lists) that's easy to encode as a JSON payload but awkward to URL-encode by hand. The dashboard handles this transparently for sharing — if you script your own queries against the function, use JSON-payload requests, not GET-style args. See [Filters and Facets](/docs/network-flows/visualization/filters-facets.md) for the details.
62 +
63 +## Picking the right view
64 +
65 +Each panel suits a different question:
66 +
67 +- **[Sankey + Table](/docs/network-flows/visualization/summary-sankey.md)** — "Who's responsible for this traffic right now". Default landing view.
68 +- **[Time-Series](/docs/network-flows/visualization/time-series.md)** — "How does this change over time". Same top-N as the Sankey, plotted across the window.
69 +- **[Maps and Globe](/docs/network-flows/visualization/maps-globe.md)** — "Where is this traffic going". Country / state / city / 3D globe variants.
70 +- **[Filters and Facets](/docs/network-flows/visualization/filters-facets.md)** — Narrowing the data; the same controls all panels share.
71 +- **[Plugin Health Charts](/docs/network-flows/visualization/dashboard-cards.md)** — Operational metrics for the plugin itself, not the flow data. Open these first when something looks wrong.
72 +
73 +## What's next
74 +
75 +- [Sankey and Table](/docs/network-flows/visualization/summary-sankey.md) — Default landing view.
76 +- [Filters and Facets](/docs/network-flows/visualization/filters-facets.md) — Narrowing controls.
77 +- [Retention and Tiers](/docs/network-flows/retention-querying.md) — How the four-tier model determines which queries can satisfy which time ranges.
78 +- [Configuration](/docs/network-flows/configuration.md) — Tuning the query guardrails (`query_max_groups`).
docs/network-flows/visualization/summary-sankey.md
+10 -8
@@ -6,9 +6,11 @@ learn_rel_path: "Network Flows/Visualization"
6 keywords: ['sankey', 'table', 'top-n', 'aggregation', 'visualization']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Sankey and Table
12
11 -The Network Flows tab opens with two views stacked: a Sankey diagram on top, a sortable table beneath. Both render the same data, the same top-N aggregation, the same field selection. Selecting fields, filtering, and sorting affects both at once.
13 +The Network Flows view (open the **Live** tab in the top navigation, then select **Network Flows**) opens with two panels stacked: a Sankey diagram on top, a sortable table beneath. Both render the same data, the same top-N aggregation, the same field selection. Selecting fields, filtering, and sorting affects both at once.
14
15 This is your default view. Most investigative workflows start here.
16
@@ -24,10 +26,10 @@ When you first open the tab:
26 - **View**: Sankey + Table
27 - **Top-N**: 25 (selectable: 25 / 50 / 100 / 200 / 500)
28 - **Sort by**: bytes (alternative: packets)
27 -- **Aggregation fields**: `Source ASN → Protocol → Destination ASN`
29 +- **Aggregation fields**: `Source AS Name → Protocol → Destination AS Name`
30 - **No filters applied** — the dashboard remembers your last selections, so on subsequent visits you'll land on whatever you had open
31
30 -The Sankey shows the top 25 conversations between Source ASN, Protocol, and Destination ASN, weighted by bytes. The table below shows the same 25 rows, with bytes and packets columns appended.
32 +The Sankey shows the top 25 conversations between Source AS Name, Protocol, and Destination AS Name, weighted by bytes. The table below shows the same 25 rows, with bytes and packets columns appended. The dashboard groups by AS Name strings (the human-readable form) by default; the bare ASN-number field is also available if you prefer.
33
34 ## How to read the Sankey
35
@@ -37,7 +39,7 @@ A Sankey diagram has columns of nodes and weighted bands flowing between them.
39 - Each **node** is one distinct value in that column (e.g., one ASN, or one country).
40 - Each **band** is one row in the underlying top-N — its width is proportional to the bytes (or packets) for that combination.
41
40 -With the default 3-column setup (Source ASN → Protocol → Destination ASN), you see the top 25 (Source ASN, Protocol, Destination ASN) tuples by traffic volume. A wide band from `AS65000` to `tcp` to `AS15169` says "AS65000 sent a lot of TCP to AS15169 in this time window".
42 +With the default 3-column setup (Source AS Name → Protocol → Destination AS Name), you see the top 25 (Source AS Name, Protocol, Destination AS Name) tuples by traffic volume. A wide band from `AS65000 ACME-CORP` to `tcp` to `AS15169 GOOGLE` says "ACME-CORP sent a lot of TCP to GOOGLE in this time window".
43
44 You can pick **1 to 10 fields** as columns. Order matters — the Sankey draws bands left-to-right in the order you list. There are roughly 84-85 fields available for aggregation; metric fields (`BYTES`, `PACKETS`, sampling rate, timestamps) and the geo coordinates (latitude/longitude) are not selectable here.
45
@@ -83,10 +85,10 @@ See [Filters and Facets](/docs/network-flows/visualization/filters-facets.md) fo
85
86 Some shapes that work well:
87
86 -- **Default**: `Source ASN → Protocol → Destination ASN`. The "who, on what, to whom" overview. Good first look.
88 +- **Default**: `Source AS Name → Protocol → Destination AS Name`. The "who, on what, to whom" overview. Good first look.
89 - **Country flow**: `Source Country → Destination Country`. Cleanest geographic view. Combine with `protocol` for service-level detail.
88 -- **Per-router slice**: `Exporter Name → Input Interface → Destination ASN`. Use when you have per-router questions.
89 -- **Service drill-down**: `Destination Port → Source ASN`. Who's hitting your services.
90 +- **Per-router slice**: `Exporter Name → Ingress Interface Name → Destination AS Name`. Use when you have per-router questions.
91 +- **Service drill-down**: `Destination Port → Source AS Name`. Who's hitting your services.
92 - **Internal/external split**: `IN_IF_BOUNDARY → DST_COUNTRY → Destination ASN`. After labelling your boundaries via static metadata.
93
94 The order of fields determines the visual flow. Reorder to change which dimension is "left" and "right" in the Sankey.
@@ -95,7 +97,7 @@ The order of fields determines the visual flow. Reorder to change which dimensio
97
98 ### Doubling
99
98 -Without filtering, aggregate volume on a single router is roughly 2× the actual traffic — every packet generates two flow records (one ingress, one egress). To see real volume on a specific link, filter to one exporter and one direction (input interface OR output interface, not both). See [Anti-patterns](/docs/network-flows/anti-patterns.md) for the full framing.
100 +Without filtering, aggregate volume on a router that exports both ingress and egress (a common configuration; vendor best practice is ingress-only) is roughly 2× the actual traffic — every packet generates two flow records, one ingress and one egress. To see real volume on a specific link, filter to one exporter and one interface (`Ingress Interface Name` OR `Egress Interface Name`, pick one). Each packet then appears in exactly one record on that interface. See [Anti-patterns](/docs/network-flows/anti-patterns.md) for the full framing.
101
102 ### Sharing your view
103
docs/network-flows/visualization/time-series.md
+9 -7
@@ -6,6 +6,8 @@ learn_rel_path: "Network Flows/Visualization"
6 keywords: ['time series', 'top-n over time', 'trends', 'visualization']
7 endmeta-->
8
9 +<!-- markdownlint-disable-file -->
10 +
11 # Time-Series
12
13 The Time-Series view plots traffic over time. Same top-N selection as the Sankey + Table, but rendered as a stacked chart across the time range you've selected.
@@ -45,14 +47,14 @@ The window is **rounded outward** to align with bucket boundaries — your "11:2
47
48 The minimum bucket size is **60 seconds**. Zoom in past one minute and the chart silently widens to 60-second buckets. There's no warning — sub-minute jitter just smooths out. For sub-second analysis, flow data is the wrong tool ([microbursts are invisible](/docs/network-flows/anti-patterns.md)).
49
48 -## What forces tier 0 (raw)
50 +## What forces raw tier
51
50 -Some queries can't use the rollup tiers. They drop to tier 0 and inherit raw-tier retention:
52 +Some queries can't use the rollup tiers. They drop to raw tier and inherit raw-tier retention:
53
54 - Filtering or grouping by `SRC_ADDR`, `DST_ADDR`, `SRC_PORT`, `DST_PORT`, or any geo city / latitude / longitude field
55 - Any non-empty full-text search
56
55 -In those cases the 100-bucket rule still applies, but the source tier is tier 0. Time depth is bounded by raw-tier retention (default: shared 10GB / 7d budget across all tiers — almost always less than 7 days for a busy collector).
57 +In those cases the 100-bucket rule still applies, but the source tier is raw tier. Time depth is bounded by raw-tier retention (default: the raw tier has its own 10GB / 7d retention limits, and busy collectors often hit the size cap before 7 days).
58
59 If you've been working at a higher tier and add an IP filter, the time depth on your chart may suddenly shrink — that's the tier switch.
60
@@ -60,7 +62,7 @@ If you've been working at a higher tier and add an IP filter, the time depth on
62
63 Buckets that received no contributing records render as zero. There's no special "missing data" indicator on the chart — the plot is flat at zero in those regions.
64
63 -That includes the case where the time range crosses the retention boundary of a tier. Tier 0 (raw) holds the most recent data; older fragments fall back to coarser tiers when available, and emptiness when no tier has the span.
65 +That includes the case where the time range crosses the retention boundary of a tier. Raw-tier (raw) holds the most recent data; older fragments fall back to coarser tiers when available, and emptiness when no tier has the span.
66
67 The dashboard's diagnostic side-panels surface tier coverage in the response stats (`query_tier`, `query_files`, etc.), but the chart itself doesn't visually distinguish "no data" from "zero".
68
@@ -78,13 +80,13 @@ Same controls as the other views:
80 - **Sort by** — bytes or packets (determines what "top" means and what units the chart uses)
81 - **Group-by fields** — same as Sankey, 1-10 fields. The chart shows one stacked dimension per surviving top-N group
82
81 -The default group-by is `Source ASN → Protocol → Destination ASN`, same as Sankey + Table.
83 +The default group-by is `Source AS Name → Protocol → Destination AS Name`, same as Sankey + Table.
84
85 ## Things that go wrong
86
87 - **Bursty flow not in top-N.** Top-N is over the whole window. Narrow the time range or filter to that conversation.
88 - **Sub-minute zoom doesn't render finer.** The 60-second floor is hard. For finer detail, use packet capture.
87 -- **Wide range plus IP filter shows less than expected.** IP filter forced tier 0; raw retention is your bound.
89 +- **Wide range plus IP filter shows less than expected.** IP filter forced raw tier; raw retention is your bound.
90 - **Window appears to extend slightly beyond what you asked.** Bucket alignment rounds outward.
91 - **`__overflow__` shows up as the biggest dimension.** Your group-by is producing more distinct tuples than `query_max_groups` (50 000). Narrow the filter or drop a high-cardinality group-by field.
92
@@ -93,4 +95,4 @@ The default group-by is `Source ASN → Protocol → Destination ASN`, same as S
95 - [Sankey and Table](/docs/network-flows/visualization/summary-sankey.md) — The default view; same aggregation, point-in-time.
96 - [Retention and Querying](/docs/network-flows/retention-querying.md) — How tiers map to time ranges.
97 - [Filters and Facets](/docs/network-flows/visualization/filters-facets.md) — Narrowing the data.
96 -- [Anti-patterns](/docs/network-flows/anti-patterns.md) — Why time-shifted comparison beats absolute thresholds.
98 +- [Anti-patterns](/docs/network-flows/anti-patterns.md) — Common misuses to avoid when reading time-series volume.
integrations/categories.yaml
+5 -13
@@ -65,23 +65,15 @@
65 children: []
66 - id: flows
67 name: Network Flows
68 - description: "Network flow ingestion, enrichment, and routing-data sources for NetFlow / IPFIX / sFlow analysis"
68 + description: "Network flow ingestion and enrichment data sources for NetFlow / IPFIX / sFlow analysis"
69 children:
70 - id: flows.sources
71 - name: Sources
71 + name: Flow Protocols
72 description: "Flow protocols Netdata receives directly from routers, switches, and software exporters"
73 children: []
74 - - id: flows.ip-intelligence
75 - name: IP Intelligence
76 - description: "GeoIP and ASN data sources used to enrich flow records with country, city, and AS-number labels"
77 - children: []
78 - - id: flows.bgp-routing
79 - name: BGP Routing
80 - description: "Live BGP route feeds used to enrich flow records with AS path, communities, and next-hop attributes"
81 - children: []
82 - - id: flows.network-identity
83 - name: Network Identity Sources
84 - description: "External feeds (cloud-provider IP ranges, IPAM systems, custom CMDBs) that label network prefixes with names, roles, sites, and tenants"
74 + - id: flows.enrichment-methods
75 + name: Enrichment Methods
76 + description: "Configurable data sources that label flow records with operational context: geolocation, AS / BGP routing, network ownership, exporter / interface metadata, classifiers, and inner-packet decapsulation"
77 children: []
78 - id: export
79 name: exporters
integrations/gen_doc_collector_page.py
+14 -6
@@ -3,7 +3,8 @@ Generate the integrations section in COLLECTORS.md from integrations/integration
3
4 This script:
5 - Reads category tree and integrations from integrations.js
6 -- Uses second-level categories (children of 'data-collection') as section headings
6 +- Uses data-collection section categories, plus the top-level flows category,
7 + as section headings
8 - Groups integrations by their section-level category
9 - Generates markdown tables with integration name, link, and description
10 """
@@ -56,7 +57,7 @@ class CategoryMapper:
57 def __init__(self, categories: List[Dict[str, Any]]):
58 self.id_to_parent: Dict[str, Optional[str]] = {}
59 self.id_to_title: Dict[str, str] = {}
59 - self.section_level_ids: List[str] = [] # Children of 'data-collection'
60 + self.section_level_ids: List[str] = [] # Monitor Anything section IDs
61 self.default_section_ids: List[str] = [] # Section IDs with collector_default=true
62
63 self._build_maps(categories)
@@ -78,8 +79,10 @@ class CategoryMapper:
79 self.id_to_parent[cid] = parent
80 self.id_to_title[cid] = title
81
81 - # Track section-level categories (children of 'data-collection')
82 - if parent == 'data-collection':
82 + # Track Monitor Anything sections. Most are children of
83 + # data-collection; Network Flows is a top-level integrations
84 + # category because it includes protocols and enrichment inputs.
85 + if parent == 'data-collection' or (parent is None and cid == 'flows'):
86 self.section_level_ids.append(cid)
87
88 # Track categories with collector_default=true
@@ -100,12 +103,15 @@ class CategoryMapper:
103 self.default_section_ids.append(section)
104
105 def get_section_ancestor(self, cid: str) -> Optional[str]:
103 - """Find the section-level ancestor (child of 'data-collection') for a category."""
106 + """Find the Monitor Anything section ancestor for a category."""
107 cur = cid
108 seen = set()
109
110 while cur and cur in self.id_to_parent and cur not in seen:
111 seen.add(cur)
112 + if cur in self.section_level_ids:
113 + return cur
114 +
115 parent = self.id_to_parent.get(cur)
116
117 if parent == 'data-collection':
@@ -398,7 +404,9 @@ def render_header() -> str:
404 tech_nav = _render_tech_navigation()
405 generic_section = _render_generic_collectors()
406
401 - return f"""# Monitor anything with Netdata
407 + return f"""<!-- markdownlint-disable-file -->
408 +
409 +# Monitor anything with Netdata
410
411 **850+ integrations. Zero configuration. Deploy anywhere.**
412
integrations/gen_docs_integrations.py
+2
@@ -246,6 +246,8 @@ learn_rel_path: "{learn_rel_path}"
246 md += f"""message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
247 endmeta-->
248
249 +<!-- markdownlint-disable-file -->
250 +
251 {create_overview(integration, integration['meta']['monitored_instance']['icon_filename'])}"""
252
253 if integration.get("setup"):
src/collectors/COLLECTORS.md
+24 -3
@@ -1,3 +1,5 @@
1 +<!-- markdownlint-disable-file -->
2 +
3 # Monitor anything with Netdata
4
5 **850+ integrations. Zero configuration. Deploy anywhere.**
@@ -263,7 +265,6 @@ Need a dedicated integration? [Submit a feature request](https://github.com/netd
265 | [Hitron CODA Cable Modem](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/hitron_coda_cable_modem.md) | Track Hitron CODA cable modem metrics for optimized internet connectivity and performance. |
266 | [InfiniBand](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/infiniband.md) | This integration monitors InfiniBand network inteface statistics. |
267 | [IP Virtual Server](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/ip_virtual_server.md) | This integration monitors IP Virtual Server statistics |
266 -| [IPFIX](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/ipfix.md) | Collects IPFIX (NetFlow v10) records from one or more exporters and stores them in tiered journal files. |
268 | [ipfw](https://github.com/netdata/netdata/blob/master/src/collectors/freebsd.plugin/integrations/ipfw.md) | Collect information about FreeBSD firewall. |
269 | [IPv6 Socket Statistics](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/ipv6_socket_statistics.md) | This integration provides IPv6 socket statistics. |
270 | [ISC DHCP](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/isc_dhcpd/integrations/isc_dhcp.md) | This collector monitors ISC DHCP lease usage by reading the DHCP client lease database (dhcpd.leases). |
@@ -278,7 +279,6 @@ Need a dedicated integration? [Submit a feature request](https://github.com/netd
279 | [net.inet6.ip6.stats](https://github.com/netdata/netdata/blob/master/src/collectors/freebsd.plugin/integrations/net.inet6.ip6.stats.md) | Collect information abou IPv6 stats. |
280 | [net.isr](https://github.com/netdata/netdata/blob/master/src/collectors/freebsd.plugin/integrations/net.isr.md) | Collect information about system softnet stat. |
281 | [Netfilter](https://github.com/netdata/netdata/blob/master/src/collectors/nfacct.plugin/integrations/netfilter.md) | Monitor Netfilter metrics for optimal packet filtering and manipulation. |
281 -| [NetFlow](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/netflow.md) | Collects NetFlow v5, v7, and v9 records from one or more exporters (routers, switches, firewalls) and stores them in tiered journal files. |
282 | [Network Connections](https://github.com/netdata/netdata/blob/master/src/collectors/network-viewer.plugin/integrations/network_connections.md) | This plugin reads the system's socket tables to enumerate all active network connections, including TCP and UDP sockets in all states, for both IPv4 and IPv6. |
283 | [Network interfaces](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/network_interfaces.md) | Monitor network interface metrics about bandwidth, state, errors and more. |
284 | [Network statistics](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/network_statistics.md) | This integration provides metrics from the `netstat`, `snmp` and `snmp6` modules. |
@@ -296,7 +296,6 @@ Need a dedicated integration? [Submit a feature request](https://github.com/netd
296 | [PowerDNS Recursor](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/powerdns_recursor/integrations/powerdns_recursor.md) | This collector monitors PowerDNS Recursor instances. |
297 | [RIPE Atlas](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/ripe_atlas.md) | Keep tabs on RIPE Atlas Internet measurement platform metrics for efficient network monitoring and performance. |
298 | [SCTP Statistics](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/sctp_statistics.md) | This integration provides statistics about the Stream Control Transmission Protocol (SCTP). |
299 -| [sFlow](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/sflow.md) | Collects sFlow v5 datagrams from one or more agents and stores them in tiered journal files. |
299 | [SNMP devices](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/snmp/integrations/snmp_devices.md) | This collector discovers and monitors any SNMP-enabled network device. |
300 | [Socket statistics](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/socket_statistics.md) | This integration provides socket statistics. |
301 | [SoftEther VPN Server](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/softether_vpn_server.md) | Monitor SoftEther VPN Server metrics for efficient virtual private network (VPN) management and performance. |
@@ -586,3 +585,25 @@ Need a dedicated integration? [Submit a feature request](https://github.com/netd
585 | [TCP/UDP Endpoints](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/portcheck/integrations/tcp-udp_endpoints.md) | Collector for monitoring service availability and response time. |
586 | [Uptimerobot](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/uptimerobot.md) | Monitor UptimeRobot website uptime monitoring metrics for efficient website availability tracking and management. |
587 | [X.509 certificate](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/x509check/integrations/x.509_certificate.md) | This collectors monitors x509 certificates expiration time and revocation status. |
588 +
589 +### Network Flows
590 +
591 +| Integration | Description |
592 +|-------------|-------------|
593 +| [AWS IP Ranges](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/aws_ip_ranges.md) | Annotate network flows with AWS service and region labels from AWS public IP ranges. |
594 +| [Azure IP Ranges](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/azure_ip_ranges.md) | Annotate network flows with Azure service and region labels from Azure Service Tags. |
595 +| [bio-rd / RIPE RIS](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/bio-rd_-_ripe_ris.md) | Enrich network flows with BGP routing context received from bio-rd / RIPE RIS over gRPC. |
596 +| [BMP (BGP Monitoring Protocol)](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/bmp_bgp_monitoring_protocol.md) | Enrich network flows with BGP next-hop, AS path, communities, and routing context received directly from routers. |
597 +| [Classifiers](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/classifiers.md) | Annotate network flows with exporter and interface labels derived from reusable classification rules. |
598 +| [Custom MMDB Database](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/custom_mmdb_database.md) | Enrich network flows with ASN, geographic, or internal network context from operator-built MMDB databases. |
599 +| [DB-IP IP Intelligence](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md) | Enrich network flows with ASN and geographic context from DB-IP Lite MMDB databases. |
600 +| [Decapsulation](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/decapsulation.md) | Enrich network flows with inner source and destination endpoints from VXLAN or SRv6 encapsulated traffic. |
601 +| [GCP IP Ranges](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/gcp_ip_ranges.md) | Annotate network flows with Google ownership, service, and scope labels from Google public IP ranges. |
602 +| [Generic JSON-over-HTTP IPAM](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/generic_json-over-http_ipam.md) | Annotate network flows with network-identity labels from any JSON-over-HTTP IPAM or CMDB endpoint. |
603 +| [IPFIX](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/ipfix.md) | Collect network flow records from IPFIX (NetFlow v10) exporters. |
604 +| [IPtoASN](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/iptoasn.md) | Enrich network flows with public ASN and country context from IPtoASN BGP-derived datasets. |
605 +| [MaxMind GeoIP / GeoLite2](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/maxmind_geoip_-_geolite2.md) | Enrich network flows with ASN and geographic context from MaxMind GeoIP / GeoLite2 MMDB databases. |
606 +| [NetBox](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/netbox.md) | Annotate network flows with tenant, site, role, VRF, and description metadata from NetBox. |
607 +| [NetFlow](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/netflow.md) | Collect network flow records from NetFlow v5, v7, and v9 exporters such as routers, switches, and firewalls. |
608 +| [sFlow](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/sflow.md) | Collect sampled network flow records from sFlow v5 agents. |
609 +| [Static Metadata](https://github.com/netdata/netdata/blob/master/src/crates/netflow-plugin/integrations/static_metadata.md) | Annotate network flows with operator-defined labels for exporters, interfaces, networks, and sampling rates. |
src/crates/netflow-plugin/README.md
+15 -11
@@ -218,8 +218,6 @@ protocols:
218
219 journal:
220 journal_dir: flows
221 - size_of_journal_files: 10GB
222 - duration_of_journal_files: 7d
221 tiers:
222 raw:
223 size_of_journal_files: 200GB
@@ -233,15 +231,20 @@ journal:
231 hour_1:
232 size_of_journal_files: 20GB
233 duration_of_journal_files: 365d
236 - query_1m_max_window: 6h
237 - query_5m_max_window: 24h
234 query_max_groups: 50000
239 - query_facet_max_values_per_field: 5000
235 ```
236
242 -`query_max_groups` and `query_facet_max_values_per_field` are guardrails for
243 -query-time accumulator cardinality. When limits are hit, overflow is reported
244 -via response stats/facet metadata instead of growing unbounded memory.
237 +Standalone CLI runs still accept the legacy uniform retention flags
238 +`--netflow-retention-size-of-journal-files` and
239 +`--netflow-retention-duration-of-journal-files`. They apply the same value to
240 +all tiers and exist only for standalone/CLI compatibility; YAML configuration is
241 +per-tier.
242 +
243 +`query_max_groups` caps the number of distinct group keys a single
244 +aggregation query may build before extra groups are folded into a synthetic
245 +`__overflow__` bucket. The response carries a warning when this happens. The
246 +limit protects the query worker from accidentally wide group-by combinations
247 +exhausting memory.
248
249 Journal rotation size is not user-configured. The plugin derives it per tier:
250
@@ -293,15 +296,16 @@ in production:
296 These charts are intended for debugging memory explosions under high-cardinality
297 traffic, not for billing or hard enforcement decisions.
298
296 -`journal.tiers` optionally allows per-tier retention overrides for:
299 +`journal.tiers` configures retention independently for:
300
301 - `raw`
302 - `minute_1` (aliases: `1m`, `minute-1`, `minute1`)
303 - `minute_5` (aliases: `5m`, `minute-5`, `minute5`)
304 - `hour_1` (aliases: `1h`, `hour-1`, `hour1`)
305
303 -If a tier override is omitted, that tier inherits top-level journal retention
304 -(`size_of_journal_files`, `duration_of_journal_files`).
306 +If a tier is omitted, it uses the built-in tier default (`10GB / 7d`). There
307 +are no top-level journal retention knobs; set retention on each tier you want
308 +to tune.
309
310 To make a tier time-only, set `size_of_journal_files: null`.
311 To make a tier size-only, set `duration_of_journal_files: null`.
src/crates/netflow-plugin/configs/netflow.yaml
+25 -13
@@ -36,21 +36,33 @@ journal:
36 # With the default Netdata cache dir this becomes /var/cache/netdata/flows.
37 journal_dir: flows
38
39 - # Journal retention limits (applied independently on each tier writer).
40 - #
41 - # Internal rotation size is derived from size_of_journal_files:
42 - # - clamp(size_of_journal_files / 20, 5MB, 200MB)
43 - # - minimum supported size_of_journal_files is 100MB
44 - # If size_of_journal_files is omitted/null, the plugin uses a fixed 100MB
45 - # internal rotation size and retention is controlled only by duration.
46 - size_of_journal_files: 10GB
47 - duration_of_journal_files: 7d
39 + # Per-tier retention. Each tier has its own size_of_journal_files (hard
40 + # cap; minimum 100MB; null disables size-based retention) and
41 + # duration_of_journal_files (time cap; null disables time-based
42 + # retention). Validation requires at least one positive limit per tier.
43 + # Internal rotation size derives from size_of_journal_files:
44 + # clamp(size_of_journal_files / 20, 5MB, 200MB). When size is null,
45 + # internal rotation defaults to 100MB and retention is controlled only
46 + # by duration.
47 + tiers:
48 + raw:
49 + size_of_journal_files: 10GB
50 + duration_of_journal_files: 7d
51 + minute_1:
52 + size_of_journal_files: 10GB
53 + duration_of_journal_files: 7d
54 + minute_5:
55 + size_of_journal_files: 10GB
56 + duration_of_journal_files: 7d
57 + hour_1:
58 + size_of_journal_files: 10GB
59 + duration_of_journal_files: 7d
60
49 - # Query tier selection guardrails.
50 - query_1m_max_window: 6h
51 - query_5m_max_window: 24h
61 + # Cap on the number of distinct group keys a single aggregation query
62 + # can build before extra groups are folded into a synthetic
63 + # __overflow__ bucket. Protects the worker from accidentally wide
64 + # group-by combinations exhausting memory.
65 query_max_groups: 50000
53 - query_facet_max_values_per_field: 5000
66
67 # Optional advanced enrichment modules.
68 # Keep disabled unless explicitly configured.
src/crates/netflow-plugin/integrations/aws_ip_ranges.md
+129 -28
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "AWS IP Ranges"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Network Identity Sources"
7 -keywords: ['aws', 'amazon', 'cloud', 'ip ranges', 'vpc', 'ec2', 'prefix list']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['aws', 'amazon', 'cloud', 'ip ranges', 'vpc', 'ec2', 'prefix list', 'service tags']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # AWS IP Ranges
14
15
@@ -21,21 +23,47 @@ Module: aws-ip-ranges
23
24 ## Overview
25
24 -AWS publishes a continuously updated JSON file listing every public IP prefix used
25 -by AWS services -- per region, per service. This integration fetches that file
26 -periodically, transforms it via a jq expression, and uses the result to label flow
27 -records destined to / from AWS with `*_NET_TENANT="amazon"` plus a per-region tag.
26 +Annotate network flows with AWS service and region labels from AWS public IP
27 +ranges. AWS publishes its current public IP allocations as a single JSON document at
28 +[`https://ip-ranges.amazonaws.com/ip-ranges.json`](https://ip-ranges.amazonaws.com/ip-ranges.json).
29 +The file lists every prefix AWS uses for a published service, tagged with the AWS
30 +region and the AWS service that owns the prefix (`AMAZON`, `EC2`, `S3`,
31 +`CLOUDFRONT`, `API_GATEWAY`, ...). This integration fetches the file periodically,
32 +transforms it via a jq expression, and labels matching flow records with provider,
33 +region, and service tags.
34 +
35 +Once configured, traffic to / from AWS shows up in dashboards as `tenant=amazon`
36 +with per-region (`region=us-east-1`, `region=eu-central-1`, ...) and per-service
37 +(`role=s3`, `role=cloudfront`, ...) breakdown. This is **complementary to GeoIP
38 +and ASN enrichment**: instead of a flow being labeled simply `AS16509 Amazon`,
39 +it becomes labeled with the specific AWS service and region carrying the
40 +traffic -- without parsing AS-name strings.
41 +
42 +The file is public; no AWS credentials are needed.
43
29 -The result: traffic to/from AWS shows up clearly in dashboards as "amazon", with
30 -per-region and per-service breakdown if you customize the jq transform.
44 +Schema of `ip-ranges.json`:
45
32 -For the full network-identity concept (merge order, jq transform, TLS verification),
33 -see [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
46 +- top-level: `syncToken`, `createDate`, `prefixes`, `ipv6_prefixes`
47 +- each entry in `prefixes[]`: `ip_prefix`, `region`, `service`, `network_border_group`
48 +- each entry in `ipv6_prefixes[]`: `ipv6_prefix`, `region`, `service`, `network_border_group`
49 +
50 +For the cross-cutting concepts (how multiple network sources merge, the lookup
51 +priority vs static `networks` config, TLS verification rules, the jq engine and
52 +its expected output schema, fetch loop and retry/backoff), see
53 +[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
54
55
56 The plugin issues a periodic GET to `https://ip-ranges.amazonaws.com/ip-ranges.json`,
37 -parses the JSON body, runs the configured jq transform via the [jaq](https://github.com/01mf02/jaq)
38 -library, and merges the resulting prefix-labeled rows into the network-attributes trie.
57 +parses the JSON body, runs the configured jq transform via the
58 +[jaq](https://github.com/01mf02/jaq) library, and merges the resulting
59 +prefix-labeled rows into the network-attributes trie shared by all enrichment
60 +sources.
61 +
62 +AWS does not publish a fixed refresh cadence; the file is updated whenever the
63 +AWS IP space changes (typically several times per day) and the `syncToken` /
64 +`createDate` fields advance on every change. AWS recommends polling no faster
65 +than the file actually changes; daily is enough for most flow-attribution use
66 +cases. The plugin floors the configured `interval` at 60s.
67
68
69 This integration is only supported on the following platforms:
@@ -49,15 +77,15 @@ This integration supports multiple instances configured side-by-side.
77
78 #### Auto-Detection
79
52 -Disabled by default. Add an entry under enrichment.network_sources to enable.
80 +Disabled by default. Add an entry under `enrichment.network_sources` to enable.
81
82 #### Limits
83
56 -The default configuration for this integration does not impose any limits.
84 +One full AWS prefix document is fetched per refresh. Resource use scales with the number of AWS prefixes selected by your transform and the refresh interval.
85
86 #### Performance Impact
87
60 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
88 +One HTTPS request per refresh interval plus a jq transform over the AWS prefix document. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records.
89
90 ## Setup
91
@@ -67,7 +95,7 @@ The default configuration for this integration is not expected to impose a signi
95 #### Outbound HTTPS to AWS
96
97 The agent host must be able to reach `https://ip-ranges.amazonaws.com/ip-ranges.json`.
70 -No AWS credentials needed -- the file is public.
98 +No AWS credentials are needed -- the file is public.
99
100
101
@@ -75,8 +103,16 @@ No AWS credentials needed -- the file is public.
103
104 #### Options
105
78 -Add a named entry under `enrichment.network_sources`. The `name` you choose appears
79 -in flow records via the `*_NET_TENANT` field (when your jq transform sets it).
106 +Add a named entry under `enrichment.network_sources`. The map key (e.g. `aws`)
107 +is the source name; it is used in plugin logs but is **not** automatically
108 +attached to flow records -- the record labels come entirely from your `transform`
109 +output (`tenant`, `region`, `role`, `name`, `site`, `city`, `country`, `state`,
110 +`asn`, `asn_name`).
111 +
112 +The expected `transform` output is a stream of objects, each carrying a `prefix`
113 +(CIDR string) plus any subset of the optional attribute fields. See the
114 +cross-cutting [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment)
115 +page for the full output schema.
116
117
118 <details open><summary>Config options</summary>
@@ -85,10 +121,12 @@ in flow records via the `*_NET_TENANT` field (when your jq transform sets it).
121
122 | Option | Description | Default | Required |
123 |:-----|:------------|:--------|:---------:|
88 -| url | AWS publishes the master file at this URL. | https://ip-ranges.amazonaws.com/ip-ranges.json | yes |
89 -| interval | How often to fetch. AWS updates the file roughly every 15 minutes; daily is enough for most uses. | 60s (loop floor) | no |
90 -| timeout | Per-request timeout. | 60s | no |
91 -| transform | jq expression that converts the AWS response into objects with `prefix` and label fields. | . | yes |
124 +| url | AWS publishes the master file at this URL. Use it as-is unless you mirror it locally for air-gapped environments. | https://ip-ranges.amazonaws.com/ip-ranges.json | yes |
125 +| interval | How often to fetch the file. AWS updates the document whenever its IP space changes (often several times per day, sometimes more); daily is enough for most uses. The configured value is floored at 60s. | 60s | no |
126 +| timeout | Per-request timeout for the HTTPS GET. | 60s | no |
127 +| method | HTTP method. AWS serves the file via GET; leave at the default. | GET | no |
128 +| headers | Extra HTTP headers added to the request. Not required for the public AWS URL; only needed if you front the file behind your own authenticated mirror. | {} | no |
129 +| transform | jq expression (compiled by jaq) that converts the AWS JSON into a stream of `{prefix, ...}` objects. The default `.` does **not** match the AWS schema -- you must supply a real transform (see examples below) or fetches fail because output rows cannot be mapped to the required `{prefix, ...}` schema. | . | yes |
130
131
132 </details>
@@ -110,9 +148,13 @@ sudo ./edit-config netflow.yaml
148
149 ##### Examples
150
113 -###### Tag all AWS prefixes by region and service
151 +###### Tag all AWS prefixes by region and service (IPv4 + IPv6)
152 +
153 +Recommended starting point. Sets `tenant=amazon`, `region=<aws-region>`,
154 +`role=<service-name-lowercased>`. Covers both `prefixes[]` and
155 +`ipv6_prefixes[]` in one stream so v4 and v6 traffic are tagged
156 +consistently.
157
115 -Sets tenant=amazon, region=`aws-region`, role=`service-name`.
158
159 ```yaml
160 enrichment:
@@ -132,7 +174,9 @@ enrichment:
174 ```
175 ###### AWS S3 only
176
135 -Filter to a single AWS service for narrower tagging.
177 +Filter to a single AWS service for narrower tagging. Useful when you only
178 +care about identifying S3 traffic (e.g. egress-cost attribution).
179 +
180
181 <details open><summary>Config</summary>
182
@@ -155,18 +199,75 @@ enrichment:
199 ```
200 </details>
201
202 +###### Use network_border_group as the site label
203 +
204 +AWS exposes a `network_border_group` field that distinguishes Local Zones
205 +/ Wavelength Zones from the parent Region. Map it to the `site` attribute
206 +if you want that distinction visible in flow dashboards.
207 +
208 +
209 +<details open><summary>Config</summary>
210 +
211 +```yaml
212 +enrichment:
213 + network_sources:
214 + aws:
215 + url: "https://ip-ranges.amazonaws.com/ip-ranges.json"
216 + interval: 24h
217 + transform: |
218 + (.prefixes + .ipv6_prefixes)[] | {
219 + prefix: (.ip_prefix // .ipv6_prefix),
220 + tenant: "amazon",
221 + region: .region,
222 + site: .network_border_group,
223 + role: (.service | ascii_downcase)
224 + }
225 +
226 +```
227 +</details>
228 +
229 +
230 +
231 +### Default `transform: "."` fails because output rows are missing `prefix`
232 +
233 +The default `.` returns the raw JSON object, not the per-prefix stream the
234 +plugin expects. You **must** supply a transform that yields one object per
235 +prefix (with at least a `prefix` field). Use the first example above as your
236 +starting template.
237 +
238 +
239 +### AWS service name precedence inside one prefix
240 +
241 +AWS sometimes lists the same `ip_prefix` under multiple `service` entries
242 +(e.g. once under `AMAZON` and again under `EC2`). The plugin merges all
243 +records into a single prefix trie, and the **last write wins** for a given
244 +prefix length. If you want a specific service to take precedence, filter the
245 +transform with `select(.service != "AMAZON")` so the broader catch-all
246 +entries are dropped.
247 +
248 +
249 +### Static `networks:` config silently overrides AWS labels
250 +
251 +When a prefix is defined in both this source and `enrichment.networks`, the
252 +static config wins on every non-empty field. This is by design (operator
253 +intent overrides imported data) but can surprise operators who expect the
254 +remote feed to be authoritative. See the cross-cutting Network Identity page
255 +for the full lookup-priority rules.
256
257
258 ### Empty result from the transform is treated as failure
259
162 -If the jq filter happens to produce nothing (e.g., AWS responds with no prefixes),
163 -the source backs off as if it errored. Check the journal for `network-sources` warnings.
260 +If the jq filter happens to produce nothing (bad selector, wrong field name,
261 +upstream JSON shape change), the source backs off as if it errored. Check
262 +the journal for `network-sources` warnings:
263 +`journalctl --namespace netdata | grep network-sources`.
264
265
266 ### TLS verification cannot be disabled
267
268 `tls.skip_verify: true` is rejected by validation. Use `tls.ca_file` for
169 -custom-CA paths if needed.
269 +custom-CA paths if you front AWS behind an internal TLS-terminating proxy
270 +with a private CA.
271
272
273
src/crates/netflow-plugin/integrations/azure_ip_ranges.md
+209 -45
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "Azure IP Ranges"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Network Identity Sources"
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 keywords: ['azure', 'microsoft', 'cloud', 'ip ranges', 'service tags']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # Azure IP Ranges
14
15
@@ -21,24 +23,34 @@ Module: azure-ip-ranges
23
24 ## Overview
25
24 -Azure publishes "Service Tags" data describing IP ranges per region and per service.
25 -The Azure publication mechanism is **less convenient** than AWS / GCP -- the
26 -authoritative URL contains a date stamp that changes weekly, so you cannot use
27 -a single stable URL.
26 +Annotate network flows with Azure service and region labels from Azure Service
27 +Tags. Microsoft publishes Azure's public IP prefixes as **Service Tags JSON
28 +files**. A service tag is Microsoft's name for a group of IP prefixes belonging
29 +to one Azure service (e.g., `Storage`, `Sql`, `AzureFrontDoor.Backend`); the
30 +JSON enumerates every tag together with the prefixes, the platform, and -- for
31 +tags that support regional scope -- the Azure region.
32 +
33 +Microsoft publishes four separate JSON files, one per Azure cloud:
34
29 -For automated fetching, you have two options:
30 -1. Mirror the file in your own infrastructure (a script that resolves the latest
31 - URL via the Azure CLI, downloads, and serves at a stable internal URL).
32 -2. Skip Azure IP Ranges entirely and rely on GeoIP / ASN data for Azure
33 - attribution (Azure ASN is 8075).
35 +- **Azure Public** -- commercial Azure, the file most users want.
36 +- **Azure US Government** -- separate cloud for US public-sector workloads.
37 +- **Microsoft Azure operated by 21Vianet** -- the China cloud.
38 +- **Azure Germany** -- legacy German cloud.
39
35 -For the full network-identity concept, see
36 -[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
40 +This integration tags flow records to/from Azure-hosted services with
41 +`*_NET_TENANT="azure"` plus per-region and per-service labels. Use it to
42 +identify traffic to Azure SQL, Azure Storage, Azure Front Door, App Service,
43 +and the rest -- without maintaining the prefix list yourself.
44
45 +For the full network-identity concept (merge order, jq output shape, TLS
46 +verification, failure modes), see
47 +[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
48
39 -Periodic HTTPS GET against your stable mirror URL, jq transform, merge into
40 -network-attributes trie. The plugin does not handle Azure's date-stamped URL
41 -rotation -- you provide a stable URL via your own mirror.
49 +
50 +Periodic HTTPS GET against the Service Tags JSON, jq transform via the
51 +[jaq](https://github.com/01mf02/jaq) library, merge into the network-attributes
52 +trie. Same mechanism as AWS / GCP IP Ranges -- different URL and JSON shape,
53 +plus one important operational caveat documented below.
54
55
56 This integration is only supported on the following platforms:
@@ -52,34 +64,96 @@ This integration supports multiple instances configured side-by-side.
64
65 #### Auto-Detection
66
55 -Disabled by default. Set up your own URL mirror, then add an entry under enrichment.network_sources.
67 +Disabled by default. Add an entry under enrichment.network_sources to enable.
68
69 #### Limits
70
59 -The default configuration for this integration does not impose any limits.
71 +One full Azure Service Tags document is fetched per refresh. Resource use scales with the number of Azure prefixes selected by your transform and the refresh interval.
72
73 #### Performance Impact
74
63 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
75 +One HTTPS request per refresh interval plus a jq transform over the Azure Service Tags document. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records.
76
77 ## Setup
78
79
80 ### Prerequisites
81
70 -#### A stable URL for the Azure Service Tags JSON
71 -
72 -Azure's authoritative URL embeds a date stamp that changes weekly. A simple
73 -workaround: a cron job that calls
74 -`az network list-service-tags --location global -o json` (Azure CLI) and
75 -writes the result to a stable path on an internal HTTP server. The plugin then
76 -fetches from that stable URL.
77 -
78 -
79 -#### Outbound HTTPS to your mirror
82 +#### Azure publishes weekly, but the URL rotates
83 +
84 +Microsoft updates the Service Tags JSON **weekly** (per
85 +<https://learn.microsoft.com/en-us/azure/virtual-network/service-tags-overview#discover-service-tags-by-using-downloadable-json-files>:
86 +"These lists are updated and published weekly"), and Microsoft asks you
87 +to delay rolling out new prefixes for at least one week after publication
88 +("When new IP addresses are added to service tags, they aren't used in
89 +Azure for at least one week").
90 +
91 +The catch: the actual download URL is **not stable**. The download portal
92 +at <https://www.microsoft.com/en-us/download/details.aspx?id=56519> serves
93 +a `ServiceTags_Public_<YYYYMMDD>.json` filename whose date stamp changes
94 +every week. A naive `interval`-based fetch against last week's URL will
95 +start 404'ing as soon as a new file is published.
96 +
97 +You have three reasonable options, in increasing order of effort:
98 +
99 +1. **Skip Azure IP Ranges entirely** and rely on GeoIP / ASN data
100 + (Microsoft's main ASN is 8075). Coarse but zero-maintenance.
101 +2. **Use a stable redirect** if your environment provides one (some
102 + enterprise mirrors do).
103 +3. **Run a small mirror script** -- a cron job that fetches the latest
104 + JSON via the [Service Tag Discovery REST API](https://learn.microsoft.com/en-us/rest/api/virtualnetwork/servicetags/list)
105 + (or `Get-AzNetworkServiceTag` in PowerShell) and writes the result to
106 + a stable path on an internal HTTP server. Point the plugin at that
107 + stable URL.
108 +
109 +Option 3 is what most operators end up with. The Service Tag Discovery
110 +API is authoritative and authenticated (Azure subscription with read
111 +role); the downloadable JSON is unauthenticated but URL-rotated. Pick
112 +whichever fits your operational model.
113 +
114 +
115 +#### Outbound HTTPS to your mirror (or to Microsoft, if you script the URL discovery)
116 +
117 +The plugin only needs to reach the configured `url`. Azure credentials
118 +are needed only on the side that resolves "what's this week's URL?" --
119 +not by the plugin itself.
120 +
121 +
122 +#### A jq transform that matches the Service Tags schema
123 +
124 +The Service Tags JSON has a nested shape:
125 +
126 +```text
127 +{
128 + "changeNumber": 123,
129 + "cloud": "Public",
130 + "values": [
131 + {
132 + "name": "Storage.WestUS",
133 + "id": "Storage.WestUS",
134 + "properties": {
135 + "changeNumber": 456,
136 + "region": "westus",
137 + "regionId": 30,
138 + "platform": "Azure",
139 + "systemService": "AzureStorage",
140 + "addressPrefixes": ["13.105.16.4/30", "20.150.0.0/17", ...],
141 + "networkFeatures": ["API", "NSG"]
142 + }
143 + },
144 + ...
145 + ]
146 +}
147 +```
148
81 -No Azure credentials needed by the plugin itself; credentials only matter on
82 -the side that does the upstream Azure CLI call.
149 +The transform must unwrap `values[]` -> `properties.addressPrefixes[]`
150 +and emit one object per prefix. Schema reference: `values[].name`,
151 +`values[].id`,
152 +`values[].properties.region` (region tag, can be empty for
153 +cloud-wide tags like the bare `Storage`), `values[].properties.platform`,
154 +`values[].properties.systemService` (e.g. `AzureStorage`, `AzureSql`),
155 +`values[].properties.addressPrefixes[]` (CIDR strings, IPv4 and IPv6
156 +mixed). Top-level `changeNumber` increments on every publication.
157
158
159
@@ -87,7 +161,9 @@ the side that does the upstream Azure CLI call.
161
162 #### Options
163
90 -Add a named entry under `enrichment.network_sources` pointing at your mirror URL.
164 +Add a named entry under `enrichment.network_sources` pointing at your
165 +stable mirror URL (or, with the limitations above, at this week's
166 +`ServiceTags_Public_<date>.json` URL).
167
168
169 <details open><summary>Config options</summary>
@@ -96,8 +172,10 @@ Add a named entry under `enrichment.network_sources` pointing at your mirror URL
172
173 | Option | Description | Default | Required |
174 |:-----|:------------|:--------|:---------:|
99 -| url | Stable URL to your locally-mirrored Azure Service Tags JSON. | | yes |
100 -| transform | jq expression mapping the values[] array to per-prefix objects. | . | yes |
175 +| url | Stable URL to your locally-mirrored Azure Service Tags JSON, or this week's date-stamped Microsoft URL. | | yes |
176 +| interval | How often to fetch. Microsoft publishes weekly; daily is plenty. | 60s (loop floor) | no |
177 +| timeout | Per-request timeout. Service Tags JSON files are large (tens of MB) -- give it room. | 60s | no |
178 +| transform | jq expression that maps `values[].properties.addressPrefixes[]` into per-prefix objects. | . | yes |
179
180
181 </details>
@@ -119,37 +197,123 @@ sudo ./edit-config netflow.yaml
197
198 ##### Examples
199
122 -###### Internal mirror of Azure Service Tags
200 +###### Azure Public via internal mirror -- tag by region and service
201 +
202 +Internal mirror serving the latest Public-cloud Service Tags JSON.
203 +Tags every Azure prefix with `tenant=azure`, the region, and the
204 +lowercase systemService name as the role.
205
124 -Tag every Azure prefix with tenant=azure plus region/service.
206
207 ```yaml
208 enrichment:
209 network_sources:
210 azure:
211 + url: "https://internal.example/azure-service-tags.json"
212 + interval: 24h
213 + timeout: 60s
214 + transform: |
215 + .values[]
216 + | .properties as $p
217 + | $p.addressPrefixes[]
218 + | {
219 + prefix: .,
220 + tenant: "azure",
221 + region: ($p.region // ""),
222 + role: (($p.systemService // "") | ascii_downcase)
223 + }
224 +
225 +```
226 +###### Azure Storage only
227 +
228 +Filter to a single Azure systemService for narrower tagging.
229 +
230 +<details open><summary>Config</summary>
231 +
232 +```yaml
233 +enrichment:
234 + network_sources:
235 + azure-storage:
236 url: "https://internal.example/azure-service-tags.json"
237 interval: 24h
238 transform: |
239 .values[]
134 - | .id as $id
135 - | .properties.region as $region
136 - | (.properties.systemService // "") as $service
137 - | .properties.addressPrefixes[]
240 + | select(.properties.systemService == "AzureStorage")
241 + | .properties as $p
242 + | $p.addressPrefixes[]
243 | {
244 prefix: .,
245 tenant: "azure",
141 - region: ($region // ""),
142 - role: ($service | ascii_downcase)
246 + role: "storage",
247 + region: ($p.region // "")
248 + }
249 +
250 +```
251 +</details>
252 +
253 +###### Azure US Government cloud
254 +
255 +The US Government cloud is published as a separate JSON file with its
256 +own download ID (57063). Same schema as the Public cloud; tag tenant
257 +differently so you can tell them apart in dashboards.
258 +
259 +
260 +<details open><summary>Config</summary>
261 +
262 +```yaml
263 +enrichment:
264 + network_sources:
265 + azure-usgov:
266 + url: "https://internal.example/azure-service-tags-usgov.json"
267 + interval: 24h
268 + transform: |
269 + .values[]
270 + | .properties as $p
271 + | $p.addressPrefixes[]
272 + | {
273 + prefix: .,
274 + tenant: "azure-usgov",
275 + region: ($p.region // ""),
276 + role: (($p.systemService // "") | ascii_downcase)
277 }
278
279 ```
280 +</details>
281 +
282 +
283 +
284 +### 404 a week after deploy
285 +
286 +You configured `url` against `ServiceTags_Public_<date>.json` directly.
287 +Microsoft rotates the date stamp weekly, so the URL stops resolving
288 +within a week. Move to an internal mirror that resolves the latest URL
289 +each cycle, or to the Service Tag Discovery REST API
290 +(<https://learn.microsoft.com/en-us/rest/api/virtualnetwork/servicetags/list>)
291 +fed into a static file.
292 +
293 +
294 +### Empty results from the transform
295 +
296 +The Service Tags JSON is nested two levels deep
297 +(`values[].properties.addressPrefixes[]`). If the jq doesn't unwrap
298 +both levels, every fetch yields zero rows and the source backs off as if
299 +it errored. Test the jq locally with `jq < azure-service-tags.json` and
300 +confirm at least one `{prefix, tenant, ...}` object comes out.
301 +
302 +
303 +### Service Tag API data lags the JSON file
304 +
305 +Per Microsoft's note on the Service Tags overview page, "It takes up to
306 +four weeks for new Service Tag data to propagate in the API results
307 +across all Azure regions". If you mirror via the REST API, expect newly
308 +added tags to appear in the JSON download a few weeks before the API,
309 +not the other way around. For prefix tagging this rarely matters; for
310 +firewall rules it can.
311
312
148 -### Empty results
313 +### TLS verification cannot be disabled
314
150 -The Azure Service Tags JSON has nested structure (`values[].properties.addressPrefixes[]`).
151 -If your jq doesn't unwrap correctly, every fetch yields zero rows and the source
152 -backs off. Test the jq locally with `jq < azure-service-tags.json`.
315 +`tls.skip_verify: true` is rejected by validation. Use `tls.ca_file`
316 +for custom-CA paths (e.g., on an internal mirror with a private CA).
317
318
319
src/crates/netflow-plugin/integrations/bio-rd_-_ripe_ris.md
+175 -43
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "bio-rd / RIPE RIS"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/BGP Routing"
7 -keywords: ['bioris', 'bio-rd', 'ripe ris', 'bgp', 'grpc', 'route information service']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['bioris', 'bio-rd', 'ripe ris', 'bgp', 'grpc', 'route information service', 'as path', 'bgp communities']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # bio-rd / RIPE RIS
14
15
@@ -21,24 +23,68 @@ Module: bioris
23
24 ## Overview
25
24 -BioRIS lets Netdata consume BGP routing data from a [bio-rd](https://github.com/bio-routing/bio-rd)
25 -`cmd/ris/` daemon over gRPC. bio-rd is a Go-based BGP daemon that can peer with
26 -[RIPE RIS](https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris)
27 -Route Collectors -- or any BGP / BMP source you have access to -- and expose the
28 -resulting RIB through a gRPC interface. Netdata is a client of that interface.
29 -
30 -Use this when you want a third-party view of the BGP routing table (e.g., RIPE
31 -RIS's view) without running a BGP session yourself or deploying BMP across your
32 -network.
33 -
34 -For the full BGP-routing concept and how BMP and BioRIS share the same trie, see
35 -[BGP Routing](https://learn.netdata.cloud/docs/network-flows/enrichment/bgp-routing).
36 -
37 -
38 -The plugin connects to one or more bio-rd `ris` gRPC endpoints. It runs three RPCs:
39 -`GetRouters` to discover what's available, `DumpRIB` to do baseline reconciliation,
40 -and `ObserveRIB` for incremental updates. Multiple instances are additive (not
41 -failover); they all merge into the shared in-memory trie.
26 +Enrich network flows with BGP routing context received from bio-rd / RIPE RIS
27 +over gRPC. [bio-rd](https://github.com/bio-routing/bio-rd)
28 +is a Go-based BGP/BMP daemon that you run yourself. You point it at one or more
29 +BGP / BMP sources -- your own routers, a [RIPE RIS](https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris)
30 +Route Collector you peer with, or any other reachable BGP source -- and it
31 +exposes the resulting RIB through a gRPC interface (`RoutingInformationService`).
32 +Netdata is a **client** of that interface, not of RIPE NCC directly.
33 +
34 +Pick this when you want a third-party / external BGP view (e.g. RIPE RIS's view of the
35 +public DFZ) without exposing a BMP listener on your agent or running BMP across your
36 +network. If your routers can speak BMP straight to Netdata, the `bmp` integration is
37 +simpler -- BioRIS makes sense when bio-rd is already part of your routing toolbox or
38 +when you want to peer with an external party (RIPE RIS, looking-glass) and re-export
39 +to Netdata.
40 +
41 +BioRIS populates the same flow-record fields as BMP -- both feed a single shared
42 +in-memory routing trie. See the BMP integration card for the side-by-side comparison
43 +and combined-deployment notes.
44 +
45 +
46 +The plugin connects as a gRPC client to one or more user-provided bio-rd `ris`
47 +endpoints (`grpc_addr`) and runs three RPCs against each one:
48 +
49 +1. `GetRouters` -- discover which routers/VRFs the bio-rd instance is exposing.
50 +2. `DumpRIB` -- pull a baseline RIB for each (router, AFI/SAFI) tuple. This is the
51 + expensive call -- full IPv4+IPv6 feeds run to millions of prefixes.
52 +3. `ObserveRIB` -- subscribe to incremental updates so the trie tracks live changes.
53 +
54 +Per refresh cycle (default 30 minutes), the plugin re-runs `GetRouters` +
55 +`DumpRIB`, then keeps `ObserveRIB` streams open between cycles. Routers that
56 +disappear between refreshes have their routes purged.
57 +
58 +Multiple `ris_instances` are **additive**, not failover -- routes from every
59 +configured endpoint merge into the same trie. The trie is also shared with the
60 +`bmp` integration, so if both are enabled their routes coexist and lookups pick
61 +the best match across both sources.
62 +
63 +Connection is plain gRPC over HTTP/2 by default; set `grpc_secure: true` to use
64 +TLS with the system CA bundle. There is no client-cert / mTLS and no application
65 +auth -- restrict access at the network layer.
66 +
67 +**Fields populated** (same set as BMP):
68 +
69 +| Field | Side | Source |
70 +|---|---|---|
71 +| `SRC_AS` / `DST_AS` | both | When the `routing` provider in the `asn_providers` chain matches |
72 +| `SRC_MASK` / `DST_MASK` | both | Prefix length of the longest-match BGP route |
73 +| `NEXT_HOP` | dest only | BGP next-hop attribute from the destination route |
74 +| `DST_AS_PATH` | dest only | Full AS path, CSV of ASNs |
75 +| `DST_COMMUNITIES` | dest only | Standard BGP communities (CSV of u32) |
76 +| `DST_LARGE_COMMUNITIES` | dest only | RFC 8092 large communities |
77 +
78 +Source-side AS path and communities are **not** surfaced; BGP path attributes are
79 +most meaningful for the destination of the traffic. AS *names* (`SRC_AS_NAME`,
80 +`DST_AS_NAME`) come from the GeoIP/ASN MMDB, not from BGP -- BioRIS gives you
81 +accurate AS *numbers* and path/community attributes; the names come from the ASN
82 +database integration.
83 +
84 +**Storage tier:** `DST_AS_PATH`, `DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES`
85 +are written only into the raw journal tier. The 1-minute / 5-minute / 1-hour
86 +rollup tiers do not carry them. Queries that need AS path or community data
87 +must run against a window that the raw retention still covers.
88
89
90 This integration is only supported on the following platforms:
@@ -56,11 +102,14 @@ Disabled by default. Set enrichment.routing_dynamic.bioris.enabled to true and p
102
103 #### Limits
104
59 -The default configuration for this integration does not impose any limits.
105 +Memory scales with the number of RIS instances, peers, routing tables, prefixes, AS paths, and communities. Full-table feeds can consume hundreds of MB per peer.
106
107 #### Performance Impact
108
63 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
109 +Disabled until BioRIS is configured. Once active, Netdata maintains an
110 +in-memory routing trie for the received RIB and updates. Plan capacity from
111 +the number of peers and tables you import, and watch the agent's RSS.
112 +
113
114 ## Setup
115
@@ -70,7 +119,7 @@ The default configuration for this integration is not expected to impose a signi
119 #### A running bio-rd 'ris' daemon
120
121 bio-rd is a separate project. The plugin only consumes its gRPC interface; it
73 -does not bundle bio-rd. You install it yourself:
122 +does not bundle bio-rd. You install and operate it yourself:
123
124 ```bash
125 # Install Go (>=1.20), then:
@@ -79,9 +128,10 @@ cd bio-rd/cmd/ris
128 go build -o /usr/local/bin/ris .
129 ```
130
82 -Configure `ris` to peer with one or more BGP / BMP sources (RIPE RIS Route
83 -Collectors, your own peers, etc.). Refer to the bio-rd documentation for the
84 -peering setup -- this is bio-rd's configuration, not Netdata's.
131 +Configure `ris` to peer with one or more BGP / BMP sources (your own routers,
132 +RIPE RIS Route Collectors, looking-glass servers, etc.). Refer to the bio-rd
133 +documentation for the peering setup -- this is bio-rd's configuration, not
134 +Netdata's.
135
136 Run the daemon with a gRPC port:
137 `/usr/local/bin/ris --grpc_port 50051 --config.file /etc/bio-rd.yml`
@@ -89,9 +139,10 @@ Run the daemon with a gRPC port:
139
140 #### Network reachability + no auth
141
92 -The gRPC connection is plain HTTP/2 by default (or TLS with system-CA when
93 -`grpc_secure: true`). There is no authentication. Restrict access at the
94 -firewall, or run bio-rd on the same host as the agent and bind it to localhost.
142 +The gRPC connection is plain HTTP/2 by default, or TLS with the system CA
143 +bundle when `grpc_secure: true`. There is no client-cert / mTLS and no application
144 +authentication -- restrict access at the firewall, or run bio-rd on the same
145 +host as the agent and bind it to localhost.
146
147
148
@@ -99,7 +150,8 @@ firewall, or run bio-rd on the same host as the agent and bind it to localhost.
150
151 #### Options
152
102 -BioRIS options live under `enrichment.routing_dynamic.bioris`.
153 +BioRIS options live under `enrichment.routing_dynamic.bioris` in
154 +`netflow.yaml`.
155
156
157 <details open><summary>Config options</summary>
@@ -109,10 +161,10 @@ BioRIS options live under `enrichment.routing_dynamic.bioris`.
161 | Option | Description | Default | Required |
162 |:-----|:------------|:--------|:---------:|
163 | enabled | Master switch. | false | no |
112 -| timeout | Connect + per-RPC timeout. Default is aggressive for public RIS over the internet -- raise if you see "deadline exceeded". | 200ms | no |
113 -| refresh | How often to re-dump every router's RIB from scratch. | 30m | no |
114 -| refresh_timeout | Per-DumpRIB request timeout and per-message stream timeout. | 10s | no |
115 -| ris_instances | List of bio-rd endpoints. Each: grpc_addr, grpc_secure, vrf, vrf_id. Multiple instances are additive (not failover) -- routes from all merge. | [] | yes |
164 +| timeout | Connect + per-RPC timeout for `GetRouters` and `ObserveRIB` setup. Default is aggressive; raise to 2-5s when reaching a remote bio-rd over the public internet. | 200ms | no |
165 +| refresh | Cadence at which the plugin re-runs `GetRouters` + `DumpRIB` for every router. Floored to 10s. | 30m | no |
166 +| refresh_timeout | Per-DumpRIB request timeout and per-message stream timeout for the baseline RIB pull. | 10s | no |
167 +| ris_instances | List of bio-rd endpoints. Each entry: `grpc_addr` (`host:port` or full `http(s)://` URI), `grpc_secure` (TLS with system CAs when true), `vrf` (string VRF name to filter on), `vrf_id` (numeric VRF ID, alternative to `vrf`). Multiple instances are additive (not failover) -- routes from every instance merge into the same trie. | [] | yes |
168
169
170 </details>
@@ -171,26 +223,106 @@ enrichment:
223 ```
224 </details>
225
226 +###### Prefer BGP over the exporter's AS view
227 +
228 +With the default provider chain, an exporter-supplied AS number wins over
229 +BGP. If your bio-rd feed is more accurate than what the exporter is
230 +stamping into flow records, reorder the chains so `routing` is consulted
231 +first.
232 +
233 +
234 +<details open><summary>Config</summary>
235 +
236 +```yaml
237 +enrichment:
238 + asn_providers: [routing, flow, geoip]
239 + net_providers: [routing, flow]
240 + routing_dynamic:
241 + bioris:
242 + enabled: true
243 + ris_instances:
244 + - grpc_addr: "127.0.0.1:50051"
245 +
246 +```
247 +</details>
248 +
249 +###### Combined with BMP into a single trie
250 +
251 +Run BMP from internal routers and BioRIS for an external (RIPE RIS) view.
252 +Both populate the same shared trie -- lookups pick the best-matching route
253 +across both sources at query time.
254 +
255 +
256 +<details open><summary>Config</summary>
257 +
258 +```yaml
259 +enrichment:
260 + routing_dynamic:
261 + bmp:
262 + enabled: true
263 + listen: "0.0.0.0:10179"
264 + bioris:
265 + enabled: true
266 + ris_instances:
267 + - grpc_addr: "127.0.0.1:50051"
268 +
269 +```
270 +</details>
271 +
272
273
274 ### Default 200ms timeout too aggressive
275
178 -Over the public internet to RIPE RIS, you may need 2-5 seconds. If you see
179 -"deadline exceeded" errors in the journal, raise `timeout`.
276 +`timeout` covers the gRPC connect plus the `GetRouters` and `ObserveRIB`
277 +setup RPCs. Over the public internet to a remote bio-rd, 200ms is often not
278 +enough. If you see "deadline exceeded" errors in the journal, raise to 2-5s.
279 +`refresh_timeout` is separate and bounds the long-running `DumpRIB` and per-
280 +message stream reads (default 10s).
281
282
283 ### Initial dump takes minutes for full feeds
284
184 -A full IPv4+IPv6 RIB from a route collector is millions of prefixes. The first
185 -refresh takes time; subsequent observe streams are incremental.
285 +A full IPv4+IPv6 RIB from a route collector is millions of prefixes. The
286 +first `DumpRIB` after enabling (or after a plugin restart) takes time -- BGP
287 +attribution is incomplete until it finishes. Subsequent `ObserveRIB` streams
288 +are incremental.
289 +
290 +
291 +### Plugin restart wipes the trie
292 +
293 +The trie is in-memory only -- restarting the netflow plugin loses every
294 +learned BGP route. Convergence over BioRIS depends on the upstream feed; a
295 +full DumpRIB from a RIPE RIS Route Collector can take minutes. Schedule
296 +restarts off-peak if BGP attribution matters for your workflow.
297 +
298 +
299 +### Memory growth without bound
300 +
301 +The trie has no time-based eviction. Routes are removed only when the
302 +upstream BGP source withdraws them, when a router disappears from
303 +`GetRouters` between refreshes, or on plugin shutdown. A full feed is
304 +several hundred MB of RSS per peer, permanently.
305 +
306 +
307 +### AS path / communities missing on older queries
308 +
309 +`DST_AS_PATH`, `DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES` only exist in
310 +the raw journal tier. The 1m / 5m / 1h rollup tiers do not carry them. Queries that span beyond the raw
311 +retention horizon will not return BGP path data.
312 +
313 +
314 +### AS path inconsistent with the exporter's view
315 +
316 +Different vantage points see different BGP paths. If your flow exporter and
317 +the BGP source bio-rd is peering with are different boxes with different
318 +routing tables, expect divergence. This is normal in BGP, not a bug.
319
320
188 -### Integration-test gap
321 +### Validate BioRIS enrichment after enabling
322
190 -proto and route conversion are unit-tested. The gRPC client path
191 -(connecting, consuming streams, retry/backoff) is NOT integration-tested.
192 -Validate against your specific bio-rd setup before relying on this for
193 -capacity / security decisions.
323 +BioRIS-derived enrichment depends on the bio-rd version, upstream BGP source,
324 +route visibility, and refresh cadence. Validate against your bio-rd setup
325 +before relying on this for capacity or security decisions.
326
327
328
src/crates/netflow-plugin/integrations/bmp_bgp_monitoring_protocol.md
+238 -54
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "BMP (BGP Monitoring Protocol)"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/BGP Routing"
7 -keywords: ['bmp', 'bgp', 'rfc 7854', 'route monitoring', 'cisco', 'juniper', 'frr']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['bmp', 'bgp', 'rfc 7854', 'route monitoring', 'cisco', 'juniper', 'arista', 'frr', 'nokia']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # BMP (BGP Monitoring Protocol)
14
15
@@ -21,29 +23,50 @@ Module: bmp
23
24 ## Overview
25
24 -BMP (BGP Monitoring Protocol, RFC 7854) lets a router push its BGP route updates
25 -to a passive collector. With this integration enabled, Netdata acts as that
26 -collector -- it listens for BMP TCP connections from your routers, parses the BGP
27 -UPDATE messages, and builds an in-memory routing table that flow enrichment then
28 -reads from.
29 -
30 -The result: every flow gets accurate AS numbers, AS paths, communities, and
31 -next-hop information from your real-time BGP table -- not from a stale GeoIP
32 -database or from whatever the exporter happened to send in the flow record.
33 -
34 -For the full BGP-routing concept (shared trie with BioRIS, withdrawal handling,
35 -per-vendor caveats, integration test gap), see
36 -[BGP Routing](https://learn.netdata.cloud/docs/network-flows/enrichment/bgp-routing).
37 -
38 -
39 -The plugin runs a TCP listener on `0.0.0.0:10179` (Akvorado convention -- not the
40 -IANA-registered port 7854). Routers initiate BMP sessions to it. The plugin
41 -processes Initiation, Termination, RouteMonitoring (BGP UPDATE messages), and
42 -PeerDownNotification messages. NLRI types: IPv4/IPv6 unicast, MPLS-labelled, VPNv4,
43 -VPNv6, EVPN IP-prefix.
44 -
45 -BMP and BioRIS share a single in-memory routing trie. Memory grows with the size
46 -of the BGP table; a full IPv4+IPv6 feed is roughly 1.2M prefixes per peer.
26 +Enrich network flows with BGP next-hop, AS path, communities, and routing
27 +context received directly from routers. BMP (BGP Monitoring Protocol, RFC 7854)
28 +lets a router push its BGP route updates to a passive collector. With this
29 +integration enabled, Netdata is that collector -- it listens for BMP TCP
30 +connections from your routers, parses BGP UPDATE messages carried inside
31 +RouteMonitoring frames, and builds an in-memory routing trie that flow enrichment
32 +then reads from.
33 +
34 +Every flow whose source or destination IP matches a learned prefix gains:
35 +`SRC_AS` / `DST_AS` (when the `routing` provider in `asn_providers` reaches BGP
36 +data), `SRC_MASK` / `DST_MASK` (when `routing` reaches BGP data in `net_providers`),
37 +plus -- for the destination side only -- `NEXT_HOP`, `DST_AS_PATH`,
38 +`DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES` (RFC 8092). Source-side AS path and
39 +communities are not surfaced; BGP path attributes are most meaningful for the
40 +destination of the traffic. AS *names* (`*_AS_NAME`) come from the GeoIP/ASN MMDB,
41 +not from BMP -- BMP gives you accurate AS *numbers* and path/communities.
42 +
43 +AS path, communities, and large communities are written to the **raw** flow journal
44 +only -- the rollup tiers do not carry them. `NEXT_HOP` is carried in both raw and
45 +rollup.
46 +
47 +For the cross-cutting Enrichment concept (provider chains, shared trie with
48 +BioRIS, withdrawal handling, restart convergence), see
49 +[Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
50 +
51 +
52 +The plugin runs a TCP listener on `0.0.0.0:10179`. This port is the Akvorado
53 +convention -- RFC 7854 does not register a port, and IANA does not assign one
54 +for BMP. Each connecting router must first send an Initiation message; the plugin
55 +then processes RouteMonitoring (carrying BGP UPDATE), PeerDownNotification, and
56 +Termination frames. PeerUp, StatisticsReport, and RouteMirroring frames are
57 +accepted but not acted on. Only BMP **version 3** is processed; v1 and v2 frames
58 +are silently dropped.
59 +
60 +NLRI families parsed: IPv4/IPv6 unicast, IPv4/IPv6 MPLS-labelled, VPNv4, VPNv6,
61 +and EVPN IP-prefix routes.
62 +
63 +BMP and BioRIS share a single in-memory routing trie. A full IPv4+IPv6 BGP table
64 +is roughly 1.2M prefixes per peer; each entry stores `Vec<u32>` AS-path,
65 +`Vec<u32>` communities, `Vec<(u32,u32,u32)>` large communities, plus a route_key
66 +string per path. Expect several hundred MB of resident memory per peer with a full
67 +feed. The trie has no time-based eviction -- routes leave only via explicit BGP
68 +withdrawal (`MP_UNREACH` or `withdraw_routes`), PeerDown, or session disconnect
69 +followed by the `keep` interval expiring (default 5 minutes).
70
71
72 This integration is only supported on the following platforms:
@@ -57,15 +80,15 @@ This integration runs as a single instance per Netdata Agent.
80
81 #### Auto-Detection
82
60 -Disabled by default. Set enrichment.routing_dynamic.bmp.enabled to true and configure your routers.
83 +Disabled by default. Set enrichment.routing_dynamic.bmp.enabled to true and configure your routers to dial in.
84
85 #### Limits
86
64 -The default configuration for this integration does not impose any limits.
87 +Memory and CPU scale with the number of BMP sessions, routing tables, prefixes, AS paths, and communities. Full-table router feeds can consume hundreds of MB per peer.
88
89 #### Performance Impact
90
68 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
91 +Disabled until BMP is configured. Once active, BMP updates maintain an in-memory routing trie used for enrichment, so resource use scales with routing-table size and update rate.
92
93 ## Setup
94
@@ -74,22 +97,38 @@ The default configuration for this integration is not expected to impose a signi
97
98 #### BMP-capable routers
99
77 -Modern Cisco IOS-XR, Juniper JunOS, Arista EOS, and FRR all support BMP v3.
78 -The plugin parses RFC 7854 BMP v3 specifically. Older versions (v1, v2) are
79 -not supported and will fail to parse.
100 +Common vendor configuration patterns:
101 +
102 +- **Cisco IOS-XR** -- `bmp server N` global block plus `bmp-activate server N`
103 + under `router bgp ... neighbor`.
104 +- **Cisco IOS-XE 3.12 / 15.4 or later** -- `bmp server N` nested inside
105 + `router bgp` with `activate`.
106 +- **Juniper JunOS** (BMP support since 13.3, RFC 8671 Adj-RIB-In since 18.3R1)
107 + -- `routing-options bmp ...` with one or more named stations.
108 +- **Arista EOS** -- `router bgp ... bgp monitoring` with one or more
109 + `monitoring station` blocks (active connection).
110 +- **Nokia SR OS** (MD-CLI) -- `/configure bmp` plus per-router `bgp monitor`.
111 +- **FRR (bgpd)** -- `bmp targets` block under `router bgp`. Note the loadable
112 + module: `bgpd` must be started with `-M bmp` or every BMP command silently
113 + fails.
114 +
115 +The plugin parses RFC 7854 BMP **version 3** only. Older draft versions (v1,
116 +v2) are silently dropped.
117
118
119 #### TCP reachability between routers and the agent
120
121 Routers initiate the connection -- the plugin is a passive listener. Allow
122 inbound TCP on the configured port (default 10179) from each BMP-speaking
86 -router to the agent.
123 +router to the agent. The plugin does not retry; it waits for the router to
124 +reconnect.
125
126
127 #### No TLS, no authentication
128
91 -The listener accepts plain TCP only. Restrict access at the firewall and on a
92 -dedicated management network -- do not expose 10179 to the public internet.
129 +The listener accepts plain TCP only. Restrict access at the firewall and use
130 +a dedicated management network -- never expose 10179 to the public internet.
131 +BMP carries your full routing table; treat it as sensitive.
132
133
134
@@ -108,13 +147,13 @@ All BMP options live under `enrichment.routing_dynamic.bmp` in `netflow.yaml`.
147 |:-----|:------------|:--------|:---------:|
148 | enabled | Master switch. Set to true to start the listener. | false | no |
149 | listen | TCP bind address (host:port). | 0.0.0.0:10179 | no |
111 -| keep | Grace window after a BMP disconnect before purging that session's routes. | 5m | no |
150 +| keep | Grace window after a BMP disconnect before purging that session's routes from the trie. | 5m | no |
151 | max_consecutive_decode_errors | Close the session after N consecutive decode errors. | 8 | no |
152 | receive_buffer | Optional SO_RCVBUF per connection in bytes (0 = OS default). | 0 | no |
114 -| collect_asns | When false, AS numbers from BMP are forced to 0. | true | no |
153 +| collect_asns | When false, AS numbers from BMP are forced to 0 before storage. | true | no |
154 | collect_as_paths | When false, AS paths are dropped before storage. | true | no |
116 -| collect_communities | When false, communities and large communities are dropped. | true | no |
117 -| rds | Whitelist of accepted Route Distinguishers. Empty = accept all. Formats: "0", "ASN:idx", "IPv4:idx", or full text. | [] | no |
155 +| collect_communities | When false, communities and large communities are dropped before storage. | true | no |
156 +| rds | Whitelist of accepted Route Distinguishers for L3VPN peers. Empty list accepts everything. Formats: numeric `0`, `"ASN:idx"`, `"IPv4:idx"`, or full text RD. | [] | no |
157
158
159 </details>
@@ -151,7 +190,12 @@ enrichment:
190 ```
191 ###### Cisco IOS-XR router config
192
154 -Vendor-side config to send BMP to Netdata. The bmp server block is global, not under router bgp.
193 +Vendor-side config to send BMP to Netdata. The `bmp server` block is
194 +global, not under `router bgp`. Each neighbor that should be exported
195 +needs `bmp-activate server N`. IOS-XR's default route monitoring is
196 +pre-policy (Adj-RIB-In before inbound policy). Set
197 +`route-monitoring policy post inbound` if you prefer post-policy.
198 +
199
200 <details open><summary>Config</summary>
201
@@ -159,6 +203,7 @@ Vendor-side config to send BMP to Netdata. The bmp server block is global, not u
203 bmp server 1
204 host 10.0.0.10 port 10179
205 description "Netdata BMP collector"
206 + update-source Loopback0
207 initial-delay 5
208 stats-reporting-period 60
209 initial-refresh delay 30 spread 2
@@ -170,9 +215,35 @@ router bgp 65000
215 ```
216 </details>
217
218 +###### Cisco IOS-XE router config
219 +
220 +IOS-XE 3.12 / 15.4 or later. The `bmp server N` block lives inside
221 +`router bgp`, unlike IOS-XR.
222 +
223 +
224 +<details open><summary>Config</summary>
225 +
226 +```yaml
227 +router bgp 65000
228 + bmp server 1
229 + address 10.0.0.10 port-number 10179
230 + description "Netdata BMP collector"
231 + initial-delay 10
232 + stats-reporting-period 60
233 + update-source GigabitEthernet1
234 + activate
235 + exit-bmp-server-mode
236 + !
237 + neighbor 192.0.2.1 bmp-activate all
238 +
239 +```
240 +</details>
241 +
242 ###### Juniper JunOS router config
243
175 -Recommended local-address and statistics-timeout for production.
244 +Named station form. JunOS supports both pre-policy (RFC 7854) and
245 +post-policy / Adj-RIB-In (RFC 8671, JunOS 18.3R1+).
246 +
247
248 <details open><summary>Config</summary>
249
@@ -183,14 +254,37 @@ set routing-options bmp station netdata connection-mode active
254 set routing-options bmp station netdata local-address 10.0.0.1
255 set routing-options bmp station netdata statistics-timeout 60
256 set routing-options bmp station netdata route-monitoring pre-policy
257 +set routing-options bmp station netdata monitor enable
258 +
259 +```
260 +</details>
261 +
262 +###### Arista EOS router config
263 +
264 +EOS uses `bgp monitoring` plus one or more `monitoring station` blocks
265 +inside `router bgp`. Active connection mode is the equivalent of all
266 +other vendors (router dials Netdata).
267 +
268 +
269 +<details open><summary>Config</summary>
270 +
271 +```yaml
272 +router bgp 65000
273 + bgp monitoring
274 + monitoring station netdata
275 + update-source Management1
276 + connection address 10.0.0.10
277 + connection mode active port 10179
278 + export-policy received routes post-policy
279 + export-policy bgp rib bestpaths
280
281 ```
282 </details>
283
284 ###### FRR (bgpd) router config
285
192 -Critical -- BMP is a runtime module in FRR. Without "-M bmp" in
193 -/etc/frr/daemons (bgpd_options), every BMP command silently fails.
286 +Critical -- BMP is a runtime module in FRR. Without `-M bmp` in
287 +`/etc/frr/daemons` (`bgpd_options`), every BMP command silently fails.
288
289
290 <details open><summary>Config</summary>
@@ -209,28 +303,118 @@ router bgp 65000
303 ```
304 </details>
305
306 +###### Nokia SR OS (MD-CLI) router config
307 +
308 +Active connection from one or more BGP routing instances to a named station.
309 +
310 +<details open><summary>Config</summary>
311 +
312 +```yaml
313 +/configure bmp admin-state enable
314 +/configure bmp station "netdata" admin-state enable
315 +/configure bmp station "netdata" connection local-address 10.0.0.1
316 +/configure bmp station "netdata" connection station-address ip-address 10.0.0.10
317 +/configure bmp station "netdata" connection station-address port 10179
318 +/configure bmp station "netdata" family ipv4 true
319 +/configure bmp station "netdata" family ipv6 true
320 +/configure router "Base" bgp monitor admin-state enable
321 +/configure router "Base" bgp monitor route-monitoring post-policy true
322 +/configure router "Base" bgp monitor station "netdata" { }
323 +
324 +```
325 +</details>
326 +
327 +###### Drop AS path and communities
328 +
329 +Useful if you only care about the AS number for traffic attribution and
330 +want to keep the journal small.
331 +
332 +
333 +<details open><summary>Config</summary>
334 +
335 +```yaml
336 +enrichment:
337 + routing_dynamic:
338 + bmp:
339 + enabled: true
340 + collect_asns: true
341 + collect_as_paths: false
342 + collect_communities: false
343 +
344 +```
345 +</details>
346 +
347 +###### Restrict to specific Route Distinguishers
348 +
349 +For L3VPN peers, only accept routes whose RD matches the whitelist.
350 +Other peer types are unaffected.
351 +
352 +
353 +<details open><summary>Config</summary>
354 +
355 +```yaml
356 +enrichment:
357 + routing_dynamic:
358 + bmp:
359 + enabled: true
360 + rds:
361 + - "65000:100"
362 + - "65000:200"
363 +
364 +```
365 +</details>
366 +
367
368
369 ### Listener not receiving BMP sessions
370
216 -Check `show bmp` (Cisco) / `show bmp connections` (Juniper) / `show bmp targets` (FRR)
217 -to confirm the router has dialed in. The plugin does not initiate -- it listens.
218 -Firewall: allow inbound TCP on 10179.
371 +The plugin is a passive listener -- it never dials. Check the router side:
372 +`show bmp` (Cisco), `show bmp connections` / `show bgp monitoring station`
373 +(Juniper, Arista), `show bmp targets` (FRR). Confirm the firewall allows
374 +inbound TCP on port 10179. For FRR specifically, verify `bgpd` was started
375 +with `-M bmp` -- without it, every BMP command is silently accepted but
376 +no connection is ever opened.
377 +
378 +
379 +### Convergence takes minutes after restart
380 +
381 +The trie is not persisted. After a plugin restart, routers re-send
382 +Initiation followed by their Adj-RIB-In as RouteMonitoring updates. FRR
383 +re-emits everything in seconds. Cisco IOS-XR's `initial-refresh` is
384 +configurably spread (defaults to a per-peer delay) so a full re-feed can
385 +take minutes. Juniper varies between seconds and minutes depending on
386 +station options. Schedule restarts off-peak when BGP attribution matters.
387 +
388 +
389 +### Memory growth without bound
390 +
391 +A full BGP feed adds ~1.2M prefixes per peer permanently -- there is no
392 +time-based eviction in the trie. Routes are removed only by explicit BGP
393 +withdrawal, PeerDown, or session disconnect followed by the `keep`
394 +interval. Plan capacity before connecting full-table peers.
395 +
396 +
397 +### AS path inconsistent with the exporter's view
398 +
399 +The exporter and the BMP-feeding router are usually different boxes with
400 +different routing tables. Different vantage points see different AS paths;
401 +this is normal. The `routing` provider in the `asn_providers` chain decides
402 +which source wins (default order is `flow, routing, geoip` -- exporter first).
403
404
221 -### Memory growth
405 +### Empty BGP enrichment after enabling
406
223 -A full BGP feed adds ~1.2M prefixes per peer permanently. There is no time-based
224 -eviction. Plan capacity accordingly. After a router disconnect, the routes
225 -for that session are kept for `keep` (default 5 min) before purging.
407 +Confirm the router actually established the BMP session (vendor-side `show`
408 +command above). Confirm `enrichment.asn_providers` includes `routing` (or
409 +the `bmp` alias) -- if `routing` is removed from both `asn_providers` and
410 +`net_providers`, the trie is built but never read.
411
412
228 -### Integration-test gap
413 +### Validate BGP enrichment after enabling
414
230 -BMP message parsing has unit tests. The TCP listener path, framed decode loop,
231 -trie apply, and per-router cleanup are NOT integration-tested. Validate against
232 -your specific router firmware before depending on this for capacity / security
233 -decisions.
415 +BGP-derived enrichment depends on router export policy, peer state, and
416 +route visibility. Validate against your specific router firmware before
417 +depending on this for capacity or security decisions.
418
419
420
src/crates/netflow-plugin/integrations/classifiers.md new
+426
@@ -0,0 +1,426 @@
1 +<!--startmeta
2 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/integrations/classifiers.md"
3 +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 +sidebar_label: "Classifiers"
5 +learn_status: "Published"
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['classifiers', 'rules', 'akvorado', 'expression', 'exporter', 'interface', 'boundary', 'connectivity', 'provider', 'tagging']
8 +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 +endmeta-->
10 +
11 +<!-- markdownlint-disable-file -->
12 +
13 +# Classifiers
14 +
15 +
16 +<img src="https://netdata.cloud/img/network-wired.svg" width="150"/>
17 +
18 +
19 +Plugin: netflow-plugin
20 +Module: classifiers
21 +
22 +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
23 +
24 +## Overview
25 +
26 +Annotate network flows with exporter and interface labels derived from reusable
27 +classification rules. Where [static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment)
28 +forces you to enumerate every exporter and every ifIndex by hand, classifiers
29 +let you express the network design once -- "anything matching `^edge-` is the
30 +edge tier", "any interface with `BACKBONE-LUMEN` in its description is on
31 +Lumen", "any interface at 100Gbps is a core uplink" -- and apply that labelling
32 +across the whole flow stream.
33 +
34 +The plugin ships two rule lists, evaluated in YAML order:
35 +
36 +- `enrichment.exporter_classifiers` -- runs once per exporter (cached). Sees the
37 + exporter's IP and friendly name, and any classification slots already filled
38 + by static metadata or by earlier rules. Can set
39 + `EXPORTER_GROUP / ROLE / SITE / REGION / TENANT`.
40 +- `enrichment.interface_classifiers` -- runs once per `(exporter, interface)`
41 + pair, applied **twice per flow record** (once for the input interface, once
42 + for the output). Sees
43 + everything an exporter rule sees plus `Interface.Index / Name / Description /
44 + Speed / VLAN`. Can set `IN_IF_PROVIDER / OUT_IF_PROVIDER`,
45 + `IN_IF_CONNECTIVITY / OUT_IF_CONNECTIVITY`, `IN_IF_BOUNDARY / OUT_IF_BOUNDARY`
46 + (1=external, 2=internal), and override `IN_IF_NAME / DESCRIPTION` /
47 + `OUT_IF_NAME / DESCRIPTION`.
48 +
49 +The expression language is **Akvorado-compatible for the documented operators
50 +and actions**. It implements a subset of Akvorado's `expr-lang`-derived grammar. Akvorado rules
51 +using only equality, comparison, `in`, `contains`, `startsWith`, `endsWith`,
52 +`matches`, `&&`, `||`, `!`, parentheses, and the documented `Classify*` /
53 +`Reject` / `Format` actions will work; arithmetic, ternaries, lambdas, and
54 +arbitrary `expr-lang` features are not supported.
55 +
56 +Output values written by `Classify*` actions are **lowercased and stripped to
57 +ASCII alphanumerics + `.` + `+` + `-`** before they reach the flow record. So
58 +`ClassifyRegion("EU West")` becomes `euwest`. Use `SetName` / `SetDescription`
59 +when you want to preserve case and whitespace -- those write directly without
60 +normalisation.
61 +
62 +For the cross-cutting Enrichment concept (where classifiers sit in the merge
63 +order vs static metadata, GeoIP, IPAM, BGP routing), see
64 +[Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
65 +
66 +
67 +Each rule is a single boolean expression; an action with no condition (e.g.
68 +`Classify("edge")` at top level) is treated as always-true and always fires.
69 +Rules are AND/OR-composed, so the typical shape is `condition && Classify*(...)`.
70 +The plugin evaluates the list top to bottom, **first-write-wins per slot**:
71 +once `EXPORTER_GROUP` is set, no later rule can change it. Order rules from
72 +most-specific to least-specific.
73 +
74 +Two short-circuit rules end the loop early. For exporter rules, the loop stops
75 +when `group + role + site + region + tenant` are all non-empty. For
76 +interface rules, the loop stops when `connectivity + provider + boundary` are
77 +all set. `SetName` / `SetDescription` /
78 +`Reject` do not contribute to short-circuit.
79 +
80 +A rule that throws at runtime (e.g. comparing a string with `>`) breaks out of
81 +the loop for that record and keeps whatever was set so far. Use `matches`, `startsWith`, or `contains`
82 +on string fields instead of `>` / `<` to avoid this.
83 +
84 +**Akvorado parity**: if `metadata_static` already filled any classification
85 +slot for the target, the matching classifier list does **not run** for that
86 +target -- operator-provided classification has priority and the rules cannot
87 +override it. Don't try to mix static and rule-based labelling on the same
88 +exporter or interface; pick one tool per target.
89 +
90 +Results are cached. The exporter cache keys on `ExporterInfo (ip + name)`. The
91 +interface cache keys on `(exporter, exporter_classification, interface)` -- so
92 +when the exporter's classification changes (for example after you push new
93 +static metadata and restart) the interface caches naturally invalidate. The
94 +cache TTL is `enrichment.classifier_cache_duration` (default 5 minutes). It is
95 +a last-access TTL so entries live as long as they're queried.
96 +
97 +
98 +This integration is only supported on the following platforms:
99 +
100 +- Linux
101 +
102 +This integration runs as a single instance per Netdata Agent.
103 +
104 +
105 +### Default Behavior
106 +
107 +#### Auto-Detection
108 +
109 +Disabled by default. Both rule lists are empty; populate `enrichment.exporter_classifiers` and / or `enrichment.interface_classifiers` to enable.
110 +
111 +#### Limits
112 +
113 +Resource use scales with rule count and the number of distinct exporters and interfaces. The classifier cache limits repeat evaluation for stable exporter/interface inventories.
114 +
115 +#### Performance Impact
116 +
117 +Rules run at decode time, in the flow-pipeline hot path, so cost matters.
118 +The cache absorbs nearly all of it: per (exporter, interface) the rule list
119 +evaluates only on cache miss. Tune
120 +`enrichment.classifier_cache_duration` upwards (15-60 minutes) for very
121 +high-cardinality exporter / interface pools where the default 5 minutes
122 +still yields visible misses; tune downwards (30-60 seconds) when iterating
123 +on rule changes during a config session.
124 +
125 +
126 +## Setup
127 +
128 +
129 +### Prerequisites
130 +
131 +#### Know what to classify
132 +
133 +Classifiers shine when there is a pattern to match -- exporter naming
134 +conventions (`edge-...`, `core-...`), management-IP subnets per site,
135 +SNMP interface descriptions that follow a template (`BACKBONE-<carrier>`,
136 +`TRANSIT-...`, `IX-...`), or 100Gbps-equals-core conventions. If your
137 +fleet has no such pattern, [static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment)
138 +is the better fit -- it lets you list each exporter and ifIndex by hand.
139 +
140 +
141 +#### Configure interface metadata first if you want interface rules
142 +
143 +The plugin does **not** poll SNMP itself, so `Interface.Name`,
144 +`Interface.Description`, and `Interface.Speed` are populated only from
145 +`enrichment.metadata_static` (the static-metadata integration card). If
146 +you have not configured `interfaces:` under `metadata_static.exporters`,
147 +those identifiers will be empty strings / zero, and any rule that
148 +matches against them will never fire. `Interface.Index` and
149 +`Interface.VLAN` come from the flow record itself and are always available.
150 +
151 +
152 +
153 +### Configuration
154 +
155 +#### Options
156 +
157 +Both lists live under `enrichment:`. Each entry is a free-form string
158 +containing a single rule expression. The cache TTL is one global setting.
159 +
160 +
161 +<details open><summary>Config options</summary>
162 +
163 +
164 +
165 +| Option | Description | Default | Required |
166 +|:-----|:------------|:--------|:---------:|
167 +| enrichment.exporter_classifiers | Ordered list of rules applied per exporter. Each rule is a string expression. Available identifiers: `Exporter.IP`, `Exporter.Name`, `CurrentClassification.Group / .Role / .Site / .Region / .Tenant`. Available actions: `Classify` / `ClassifyGroup`, `ClassifyRole`, `ClassifySite`, `ClassifyRegion`, `ClassifyTenant`, plus the `*Regex(input, pattern, template)` variants of each, plus `Reject()`. Interface-only actions (`ClassifyProvider`, `ClassifyConnectivity`, `ClassifyExternal` / `ClassifyInternal`, `SetName`, `SetDescription`) fail at runtime if used here. | [] | no |
168 +| enrichment.interface_classifiers | Ordered list of rules applied per `(exporter, interface)` pair. Sees everything an exporter rule sees, plus `Interface.Index`, `Interface.Name`, `Interface.Description`, `Interface.Speed` (bits per second), `Interface.VLAN`, and the per-interface `CurrentClassification.Connectivity / .Provider / .Boundary / .Name / .Description`. Available actions: `ClassifyProvider`, `ClassifyConnectivity`, `ClassifyExternal()`, `ClassifyInternal()`, `SetName`, `SetDescription`, `Reject()`, plus the `*Regex` variants of provider / connectivity. Exporter-only `Classify*` actions fail at runtime if used here. | [] | no |
169 +| enrichment.classifier_cache_duration | Last-access TTL for both classifier caches (exporter and interface). Values below 1 second are rejected. The cache prunes opportunistically -- entries idle longer than the TTL are dropped on the next prune pass, capped at one prune every TTL or 30 seconds, whichever is smaller. Restart the plugin to clear caches outright when you change rules. | 5m | no |
170 +
171 +
172 +</details>
173 +
174 +
175 +
176 +#### via File
177 +
178 +The configuration file name for this integration is `netflow.yaml`.
179 +
180 +
181 +You can edit the configuration file using the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#edit-configuration-files) script from the
182 +Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#locate-your-config-directory).
183 +
184 +```bash
185 +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
186 +sudo ./edit-config netflow.yaml
187 +```
188 +
189 +##### Examples
190 +
191 +###### Exporter classification by name pattern
192 +
193 +Tag exporters by the prefix of their friendly name -- the simplest and
194 +most common pattern. Falls back to a regex capture for the region code
195 +when the name encodes one. The final `Reject()` rule drops a test
196 +exporter from collection entirely.
197 +
198 +
199 +```yaml
200 +enrichment:
201 + exporter_classifiers:
202 + # Group by name prefix.
203 + - 'Exporter.Name startsWith "edge-" && Classify("edge")'
204 + - 'Exporter.Name startsWith "core-" && Classify("core")'
205 + - 'Exporter.Name startsWith "agg-" && Classify("aggregation")'
206 +
207 + # Site by management-IP subnet.
208 + - 'Exporter.IP startsWith "10.1." && ClassifySite("ny-dc1")'
209 + - 'Exporter.IP startsWith "10.2." && ClassifySite("par-dc1")'
210 +
211 + # Region from a name suffix like "edge-fra-01" -> "fra".
212 + - 'ClassifyRegionRegex(Exporter.Name, "-([a-z]{3})-[0-9]+$", "$1")'
213 +
214 + # Drop a lab exporter entirely.
215 + - 'Exporter.IP startsWith "192.0.2." && Reject()'
216 +
217 +```
218 +###### Interface classification from SNMP descriptions
219 +
220 +Encode the boundary, the provider, and the connectivity tier from the
221 +interface description that your network team already maintains. The
222 +`(?i)` regex flag is the Rust regex inline-case-insensitive prefix.
223 +
224 +
225 +<details open><summary>Config</summary>
226 +
227 +```yaml
228 +enrichment:
229 + interface_classifiers:
230 + # Provider tag from a description prefix.
231 + - 'Interface.Description startsWith "BACKBONE-LUMEN" && ClassifyProvider("Lumen")'
232 + - 'Interface.Description startsWith "BACKBONE-COGENT" && ClassifyProvider("Cogent")'
233 + - 'Interface.Description startsWith "BACKBONE-NTT" && ClassifyProvider("NTT")'
234 +
235 + # Transit links: external boundary + connectivity tag.
236 + - 'Interface.Description contains "TRANSIT" && ClassifyConnectivity("transit") && ClassifyExternal()'
237 +
238 + # Peering and IX -- case-insensitive regex.
239 + - 'Interface.Description matches "(?i)^(IX|peering)-.*" && ClassifyConnectivity("peering") && ClassifyExternal()'
240 +
241 + # Internal customer-facing access ports.
242 + - 'Interface.Description startsWith "CUSTOMER-" && ClassifyConnectivity("customer") && ClassifyInternal()'
243 +
244 +```
245 +</details>
246 +
247 +###### Boundary inferred from interface speed
248 +
249 +A pragmatic shorthand when descriptions are unreliable but speed is
250 +consistent. 100Gbps and faster interfaces are core, 10Gbps are
251 +aggregation, 1Gbps and slower are access. `Interface.Speed` is in bits
252 +per second -- numeric comparisons are safe.
253 +
254 +
255 +<details open><summary>Config</summary>
256 +
257 +```yaml
258 +enrichment:
259 + interface_classifiers:
260 + - 'Interface.Speed >= 100000000000 && ClassifyConnectivity("core")'
261 + - 'Interface.Speed >= 10000000000 && ClassifyConnectivity("aggregation")'
262 + - 'Interface.Speed > 0 && ClassifyConnectivity("access")'
263 +
264 +```
265 +</details>
266 +
267 +###### Combining exporter context with interface rules
268 +
269 +Interface rules see the exporter's already-resolved classification
270 +via `CurrentClassification.*`. Use it to scope interface rules to
271 +specific tiers -- for example: every interface on an edge exporter
272 +without a more-specific match falls back to "external".
273 +
274 +
275 +<details open><summary>Config</summary>
276 +
277 +```yaml
278 +enrichment:
279 + exporter_classifiers:
280 + - 'Exporter.Name startsWith "edge-" && Classify("edge") && ClassifyRole("border")'
281 + - 'Exporter.Name startsWith "core-" && Classify("core") && ClassifyRole("backbone")'
282 + interface_classifiers:
283 + # Specific provider rules first (most-specific to least-specific).
284 + - 'Interface.Description startsWith "BACKBONE-LUMEN" && ClassifyProvider("Lumen")'
285 + - 'Interface.Description startsWith "BACKBONE-COGENT" && ClassifyProvider("Cogent")'
286 +
287 + # Generic transit rule.
288 + - 'Interface.Description contains "TRANSIT" && ClassifyConnectivity("transit") && ClassifyExternal()'
289 +
290 + # Fallback: any unclassified interface on an edge box is external.
291 + - 'CurrentClassification.Role == "border" && CurrentClassification.Boundary == 0 && ClassifyExternal()'
292 +
293 +```
294 +</details>
295 +
296 +###### Building values with Format and human-readable names
297 +
298 +`Format(pattern, args...)` mimics Go's `fmt.Sprintf` for `%s`, `%v`,
299 +`%d`, `%%`. `Classify*`
300 +normalises (lowercase + strip non-alphanumeric); `SetName` and
301 +`SetDescription` do not, so they preserve the case and spaces of the
302 +computed value.
303 +
304 +
305 +<details open><summary>Config</summary>
306 +
307 +```yaml
308 +enrichment:
309 + exporter_classifiers:
310 + # Tenant computed from name, normalised on write -> "tenant-edge01".
311 + - 'ClassifyTenant(Format("tenant-%s", Exporter.Name))'
312 + interface_classifiers:
313 + # Human-readable name = "<exporter>:if<index>". Preserved verbatim.
314 + - 'SetName(Format("%s:if%d", Exporter.Name, Interface.Index))'
315 +
316 +```
317 +</details>
318 +
319 +###### Tuning the cache for a large fleet
320 +
321 +The default 5-minute last-access TTL is right for steady-state. Raise
322 +it when the (exporter, interface) population is large enough that
323 +evicted entries are quickly re-queried. Lower it when
324 +actively iterating on rule changes so misses pick up the new rules
325 +quickly.
326 +
327 +
328 +<details open><summary>Config</summary>
329 +
330 +```yaml
331 +enrichment:
332 + classifier_cache_duration: 30m
333 + exporter_classifiers:
334 + - 'Exporter.Name startsWith "edge-" && Classify("edge")'
335 + interface_classifiers:
336 + - 'Interface.Speed >= 100000000000 && ClassifyConnectivity("core")'
337 +
338 +```
339 +</details>
340 +
341 +
342 +
343 +### Plugin fails to start with a parser error
344 +
345 +A rule failed to parse. The journal log includes the index in the list
346 +and a parser context (`unsupported rule term`, `unsupported value
347 +expression`, `Reject() does not accept arguments`, etc.). Common causes:
348 +missing `&&` between condition and action; an action used in the wrong
349 +list (`ClassifyExternal` in an exporter rule); strings written with
350 +single quotes (only JSON-style double quotes are accepted); regex literals
351 +that fail to compile.
352 +
353 +
354 +### Classifier rules never run for an exporter or interface
355 +
356 +Likely cause: `metadata_static` already set **any** classification field
357 +on that target. By design, the matching list is suppressed entirely when
358 +the classification is non-empty. Either remove the static-metadata entry for that target, or
359 +keep static-metadata as the sole source for it.
360 +
361 +
362 +### A value appears differently in the dashboard than in the rule
363 +
364 +`Classify*` actions normalise output to `[a-z0-9.+-]` only -- so
365 +`ClassifyRegion("EU West")` lands as `euwest`, and
366 +`Classify("Edge_Tier_1")` lands as `edgetier1`. Use `SetName` /
367 +`SetDescription` to preserve case and whitespace; those write the value
368 +verbatim.
369 +
370 +
371 +### First rule always wins, later rules never fire for the same slot
372 +
373 +First-write-wins is by design and per slot. Order your
374 +rules from most-specific to least-specific. If you want a tiered
375 +fallback, use distinct slots (e.g. `Classify` for the broad group and
376 +`ClassifyRole` for the tier within that group).
377 +
378 +
379 +### A working rule stops matching some time after startup
380 +
381 +Cached results expire after `classifier_cache_duration` (default 5
382 +minutes, last-access). When you change rules, restart the plugin so the
383 +caches clear immediately -- otherwise stale cached classifications keep
384 +returning until they idle out.
385 +
386 +
387 +### A rule with `>` or `<` aborts the rule list
388 +
389 +Comparing a string-typed identifier with `>` / `<` / `>=` / `<=` raises
390 +a runtime error, and the loop breaks out for that record. Subsequent rules in
391 +the list are skipped for that record. Use `matches`, `startsWith`,
392 +`endsWith`, `contains`, or `==` / `!=` on string fields. Keep `>` / `<`
393 +for `Interface.Index`, `Interface.Speed`, and `Interface.VLAN` (the
394 +numeric identifiers).
395 +
396 +
397 +### ClassifyExternal fires only on one side
398 +
399 +Interface classifiers run twice per flow record -- once for the input
400 +interface, once for the output. Both invocations see the same rule list. If your rule conditions on
401 +`Interface.Index == 42` and that ifIndex appears in `IN_IF` of one flow
402 +and `OUT_IF` of another, the rule fires correctly in both places. But
403 +the `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` columns are independent -- a
404 +rule firing on the output side of a flow only sets the output side's
405 +boundary, and vice versa.
406 +
407 +
408 +### Interface fields are empty in the rule even though SNMP is configured
409 +
410 +The plugin does not poll SNMP -- `Interface.Name`, `Description`, and
411 +`Speed` come exclusively from `enrichment.metadata_static.exporters.<ip>.interfaces.<index>`.
412 +If you populate them through an external SNMP discovery and write them
413 +into `metadata_static`, the rules will see them. Otherwise those fields
414 +resolve to empty strings / zero, and any rule that conditions on them
415 +never matches.
416 +
417 +
418 +### Referencing Interface.* in an exporter rule silently does nothing
419 +
420 +Field resolution does not error when the wrong context is missing -- it
421 +returns the type's zero value. So `Interface.Speed >= 1` written in an `exporter_classifiers` rule
422 +resolves to `0 >= 1` (false) on every call. Use
423 +`interface_classifiers` for any rule that needs an interface field.
424 +
425 +
426 +
src/crates/netflow-plugin/integrations/custom_mmdb_database.md
+109 -38
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "Custom MMDB Database"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/IP Intelligence"
7 -keywords: ['mmdb', 'custom database', 'bring your own', 'ipinfo', 'ip intelligence']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['mmdb', 'custom database', 'bring your own', 'mmdbwriter', 'internal asn', 'internal ipam', 'ip intelligence']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # Custom MMDB Database
14
15
@@ -21,21 +23,35 @@ Module: custom-mmdb
23
24 ## Overview
25
24 -The plugin reads any MMDB file that conforms to the standard schema -- this catch-all
25 -integration covers IPInfo, custom-built internal MMDBs, vendor-specific feeds, or
26 -any provider that publishes MMDB data.
26 +Enrich network flows with ASN, geographic, or internal network context from
27 +operator-built MMDB databases. Use Custom MMDB Database when you want the netflow
28 +plugin to read a MaxMind-format binary database (`.mmdb`) that your
29 +own tooling produces -- typical use cases are joining the upstream DB-IP /
30 +GeoLite2 data with internal AS labels, or producing a CIDR-tagged custom file
31 +directly from an internal IPAM. The plugin only requires the file to conform to
32 +the [MaxMind DB binary format](https://maxmind.github.io/MaxMind-DB/); whatever
33 +fields it exposes are picked up by the same decoder path used for DB-IP and
34 +MaxMind.
35 +
36 +The decoder reads `autonomous_system_number`, `autonomous_system_organization`,
37 +and (optionally) a string `asn` field from the ASN database, and `country.iso_code`,
38 +`subdivisions[].iso_code`, `city.names.en`, `location.latitude`,
39 +`location.longitude`, plus the Netdata-specific `netdata.ip_class` flag from the
40 +geo database. Any other vendor-specific keys are ignored.
41
28 -The plugin reads `country.iso_code`, `city.names.en`, `subdivisions[].iso_code`,
29 -`location.latitude`, `location.longitude`, `autonomous_system_number`, and
30 -`autonomous_system_organization`. Vendor-specific extra fields are ignored.
42 +Note that `*_GEO_CITY`, `*_GEO_LATITUDE`, and `*_GEO_LONGITUDE` are written to
43 +the raw journal tier only -- the rollup tiers drop these high-cardinality fields.
44 +State and country survive into all four tiers.
45
32 -For the full IP-intelligence concept, see
33 -[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
46 +For the full IP-intelligence concept (composition rule, ASN provider chain,
47 +auto-detect path order, dual-stack handling, hot reload semantics), see
48 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
49
50
36 -You produce or download an MMDB file. Place it on the agent host. Point the
37 -plugin at it via `netflow.yaml`. The plugin reloads on file change every 30
38 -seconds.
51 +You build (or download) the MMDB file with your own tooling, place it on the
52 +agent host, and point `enrichment.geoip.asn_database` and / or
53 +`enrichment.geoip.geo_database` at it in `netflow.yaml`. The plugin reloads on
54 +file change automatically (mechanism documented on the IP Intelligence page).
55
56
57 This integration is only supported on the following platforms:
@@ -53,24 +69,43 @@ Not auto-detected. You must configure paths explicitly.
69
70 #### Limits
71
56 -The default configuration for this integration does not impose any limits.
72 +Coverage, schema quality, and freshness are entirely controlled by the custom MMDB build you provide. Invalid or missing files fail startup unless marked optional.
73
74 #### Performance Impact
75
60 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
76 +Lookups are local MMDB reads with no per-flow network call. Memory use depends on the size and number of custom databases you configure.
77
78 ## Setup
79
80
81 ### Prerequisites
82
67 -#### A standards-compliant MMDB file
83 +#### Build or obtain a standards-compliant MMDB file
84 +
85 +The file must conform to the
86 +[MaxMind DB binary format spec](https://maxmind.github.io/MaxMind-DB/) --
87 +a binary search tree over IP prefixes plus a data section of
88 +type-length-value records. Validate with `mmdblookup` from the
89 +`libmaxminddb-tools` package before deploying:
90 +
91 +```bash
92 +mmdblookup --file your-custom.mmdb --ip 8.8.8.8
93 +```
94 +
95 +Common ways to produce a file:
96
69 -The MMDB file must use the [standard MMDB schema](https://maxmind.github.io/MaxMind-DB/).
70 -Validate with `mmdblookup` from the `libmaxminddb-tools` package before deploying.
97 +- **Go**: [`github.com/maxmind/mmdbwriter`](https://github.com/maxmind/mmdbwriter)
98 + -- MaxMind's official Go writer. Lets you start from an upstream MMDB
99 + (DB-IP, GeoLite2) and overlay extra records, then write a new file.
100 + Pairs well with the read-side library
101 + [`github.com/oschwald/maxminddb-golang`](https://github.com/oschwald/maxminddb-golang).
102 +- **Perl**: [`MaxMind::DB::Writer`](https://metacpan.org/pod/MaxMind::DB::Writer)
103 + -- the original reference writer.
104 +- **Python**: community ports such as `mmdb_writer` (PyPI) wrap the same
105 + format.
106
72 -Common sources: IPInfo (`ipinfo.io`), custom internal builds via the `mmdbwriter`
73 -Go tool, or vendor-specific feeds.
107 +Whatever fields you encode are read by the plugin only if they match the
108 +standard schema names listed in the metrics description above.
109
110
111
@@ -78,8 +113,9 @@ Go tool, or vendor-specific feeds.
113
114 #### Options
115
81 -Point `enrichment.geoip.asn_database` and/or `enrichment.geoip.geo_database` at
82 -your MMDB file paths.
116 +Point `enrichment.geoip.asn_database` and / or `enrichment.geoip.geo_database`
117 +at your custom MMDB file paths. Set `optional: true` while you iterate so a
118 +missing or malformed file does not block plugin startup.
119
120
121 <details open><summary>Config options</summary>
@@ -88,9 +124,9 @@ your MMDB file paths.
124
125 | Option | Description | Default | Required |
126 |:-----|:------------|:--------|:---------:|
91 -| enrichment.geoip.asn_database | List of MMDB paths providing AS data. Multiple files allowed; later entries override on overlap. | [] | no |
92 -| enrichment.geoip.geo_database | List of MMDB paths providing geographic data. | [] | no |
93 -| enrichment.geoip.optional | When true, missing files become startup warnings instead of fatal errors. | false | no |
127 +| enrichment.geoip.asn_database | List of MMDB paths providing AS data. Multiple files compose -- per field, the last database returning a non-empty value wins. | [] | no |
128 +| enrichment.geoip.geo_database | List of MMDB paths providing geographic data. Same composition rule as the ASN list. | [] | no |
129 +| enrichment.geoip.optional | When true, missing or unreadable files at startup are tolerated (the resolver starts with no databases). Recommended while testing a custom file. | false | no |
130
131
132 </details>
@@ -112,23 +148,23 @@ sudo ./edit-config netflow.yaml
148
149 ##### Examples
150
115 -###### IPInfo MMDB
151 +###### Internal MMDB built with mmdbwriter
152
117 -Using IPInfo's MMDB feed (subscription required).
153 +A custom build that combines public BGP data with internal CIDR labels, written atomically into the plugin's lookup directory by the operator's own pipeline.
154
155 ```yaml
156 enrichment:
157 geoip:
158 asn_database:
123 - - /opt/mmdb/ipinfo-asn.mmdb
159 + - /etc/netdata/internal-asn.mmdb
160 geo_database:
125 - - /opt/mmdb/ipinfo-city.mmdb
161 + - /etc/netdata/internal-geo.mmdb
162 optional: false
163
164 ```
129 -###### Internal custom MMDB
165 +###### Overlay a custom ASN file on top of the bundled DB-IP geo
166
131 -Built in-house with `mmdbwriter`. Combines public BGP data with internal CIDR labels.
167 +Keep DB-IP for geographic data and use a custom MMDB only for AS labels (your IPAM-derived prefixes win because they appear last in the list -- the resolver merges per field, last non-empty wins).
168
169 <details open><summary>Config</summary>
170
@@ -136,10 +172,28 @@ Built in-house with `mmdbwriter`. Combines public BGP data with internal CIDR la
172 enrichment:
173 geoip:
174 asn_database:
175 + - /var/cache/netdata/topology-ip-intel/topology-ip-asn.mmdb
176 - /etc/netdata/internal-asn.mmdb
177 geo_database:
141 - - /etc/netdata/internal-geo.mmdb
142 - optional: false
178 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
179 + optional: true
180 +
181 +```
182 +</details>
183 +
184 +###### Iterating on a new custom MMDB
185 +
186 +While you tune the build pipeline, mark the database optional so an absent or malformed file becomes a startup warning instead of a fatal error.
187 +
188 +<details open><summary>Config</summary>
189 +
190 +```yaml
191 +enrichment:
192 + geoip:
193 + asn_database:
194 + - /etc/netdata/wip-asn.mmdb
195 + geo_database: []
196 + optional: true
197
198 ```
199 </details>
@@ -148,15 +202,32 @@ enrichment:
202
203 ### Lookups silently return empty
204
151 -The MMDB schema is non-standard or the IP types don't match (some custom builds
152 -use `string` instead of `array` for ASN). Validate with `mmdblookup -f file.mmdb -i 8.8.8.8`
153 -and confirm the standard fields are present.
205 +The custom file's schema is non-standard (for example, the ASN is encoded
206 +as a string instead of `autonomous_system_number: uint32`, or the country
207 +is at a non-standard path). The plugin only reads the standard MaxMind
208 +field names listed in the data-collection description. Validate with
209 +`mmdblookup --file your-custom.mmdb --ip <known-public-ip>` and confirm the
210 +standard fields are present at the expected paths.
211
212
213 ### Plugin fails to start with optional=false
214
158 -File missing or unreadable at the configured path. Check permissions; the netdata
159 -user must be able to read the file.
215 +File missing, unreadable, or not a valid MMDB at the configured path.
216 +Check permissions (the netdata user must be able to read the file) and
217 +re-validate with `mmdblookup`. Set `optional: true` while iterating.
218 +
219 +
220 +### Custom file refresh did not take effect
221 +
222 +The plugin reloads custom databases the same way it reloads stock ones
223 +(signature-driven, in place). If the file was rewritten in place the
224 +plugin will pick it up; if it was edited byte-by-byte (rare with MMDB
225 +builders) verify size or mtime actually changed. The reload mechanism
226 +and its 30-second cadence are documented in
227 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
228 +Operational practice: have your build pipeline write the new MMDB to a
229 +temp file in the same directory and `rename(2)` it over the live path so
230 +the swap is atomic.
231
232
233
src/crates/netflow-plugin/integrations/db-ip_ip_intelligence.md
+82 -33
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "DB-IP IP Intelligence"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/IP Intelligence"
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 keywords: ['geoip', 'asn', 'dbip', 'db-ip', 'mmdb', 'ip intelligence', 'flow enrichment']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # DB-IP IP Intelligence
14
15
@@ -21,25 +23,45 @@ Module: dbip
23
24 ## Overview
25
24 -DB-IP is the **default** IP intelligence source for the Netdata netflow plugin. Its
25 -MMDB-format databases are bundled with native packages (DEB, RPM) under
26 -`/usr/share/netdata/topology-ip-intel/`. Refreshing pulls newer data from
27 -`download.db-ip.com` via the bundled `topology-ip-intel-downloader`.
28 -
29 -Populates `SRC_COUNTRY`, `DST_COUNTRY`, `SRC_GEO_STATE`, `DST_GEO_STATE`,
30 -`SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`,
31 -`SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`, plus the AS-number and AS-name fields
32 -when included in the resolution chain.
33 -
34 -For the full IP-intelligence concept (MMDB format, lookup priority, internal-IP
35 -handling, hot reload semantics), see
36 -[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
37 -
38 -
39 -Files are read on plugin start and reloaded automatically every 30 seconds when
40 -their mtime or size changes. Lookups happen in-process; there is no per-flow network
41 -call. Auto-detection scans `${NETDATA_CACHE_DIR}/topology-ip-intel/` first, falling
42 -back to the stock copy under `${NETDATA_STOCK_DATA_DIR}/topology-ip-intel/`.
26 +Enrich network flows with ASN and geographic context from DB-IP Lite MMDB
27 +databases. DB-IP IP Intelligence is the default IP intelligence source for the
28 +Netdata netflow plugin: when neither `enrichment.geoip.asn_database` nor
29 +`enrichment.geoip.geo_database` is configured, the plugin auto-detects the
30 +DB-IP-built MMDB files at startup and uses them. Two MMDB files are involved:
31 +an ASN database (`topology-ip-asn.mmdb`) and a geographic database
32 +(`topology-ip-geo.mmdb`). Native packages (DEB, RPM) ship a stock copy under
33 +`/usr/share/netdata/topology-ip-intel/`; the bundled refresh tool fetches updates
34 +into `/var/cache/netdata/topology-ip-intel/`.
35 +
36 +DB-IP publishes the Lite databases monthly under a Creative Commons Attribution 4.0
37 +International licence (https://db-ip.com/db/lite.php). The MMDB format is the MaxMind
38 +binary database; lookups are in-process with no per-flow network call.
39 +
40 +Populates these flow record fields when a lookup succeeds:
41 +
42 +- From the **geo MMDB**: `SRC_COUNTRY`, `DST_COUNTRY`, `SRC_GEO_STATE`, `DST_GEO_STATE`,
43 + `SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`,
44 + `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`. Country and state survive into all four
45 + journal tiers; city, latitude, and longitude are kept only in the raw tier so the
46 + rollups stay cardinality-bounded.
47 +- From the **ASN MMDB**: `SRC_AS_NAME`, `DST_AS_NAME`. The DB-IP-built ASN database also
48 + tags address ranges as private/reserved (RFC 1918, link-local, RFC 6598, etc.); when
49 + the resolved AS number is `0` and that flag is set, the plugin renders the AS name as
50 + `AS0 Private IP Address Space` instead of `AS0 Unknown ASN`. The AS *number* itself
51 + comes from the wider asn_providers chain, not directly from the MMDB.
52 +
53 +For the cross-provider mechanics (lookup pass ordering, the 30-second hot-reload poll,
54 +the auto-detect path order, multi-database composition, IPv4/IPv6 handling, the failure
55 +modes shared by every MMDB provider), see
56 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
57 +
58 +
59 +Files are read on plugin start and reloaded automatically when their mtime or size
60 +changes. Auto-detection scans `${NETDATA_CACHE_DIR}/topology-ip-intel/` first
61 +(typically `/var/cache/netdata/topology-ip-intel/`), falling back to the stock copy
62 +under `${NETDATA_STOCK_DATA_DIR}/topology-ip-intel/` (typically
63 +`/usr/share/netdata/topology-ip-intel/`). When databases are auto-detected they are
64 +treated as optional, so a missing or unreadable file does not abort plugin startup.
65
66
67 This integration is only supported on the following platforms:
@@ -57,11 +79,11 @@ Native packages ship the stock DB-IP MMDB files; the plugin auto-detects them at
79
80 #### Limits
81
60 -The default configuration for this integration does not impose any limits.
82 +Lookup coverage and freshness depend on the DB-IP Lite files installed on disk. Native packages provide a stock copy; schedule the downloader if you need monthly refreshes.
83
84 #### Performance Impact
85
64 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
86 +Lookups are local MMDB reads with no per-flow network call. Memory use is mostly the mapped database files and the kernel page cache needed to keep active pages hot.
87
88 ## Setup
89
@@ -70,14 +92,27 @@ The default configuration for this integration is not expected to impose a signi
92
93 #### DB-IP MMDB files
94
73 -Ships with native packages. For source builds, run the bundled downloader once
74 -to populate `/var/cache/netdata/topology-ip-intel/`:
95 +Native DEB / RPM packages ship the stock DB-IP MMDB files under
96 +`/usr/share/netdata/topology-ip-intel/`. For source builds, or to get a fresher copy
97 +than the one bundled with the package, run `topology-ip-intel-downloader` once when
98 +the binary is available (packaged 32-bit installs do not include it) to populate
99 +`/var/cache/netdata/topology-ip-intel/`:
100
101 ```bash
102 sudo /usr/sbin/topology-ip-intel-downloader
103 ```
104
80 -Subsequent refreshes (e.g., monthly cron) re-fetch from db-ip.com.
105 +See the [Enrichment Intel Downloader](https://learn.netdata.cloud/docs/network-flows/intel-downloader)
106 +page for downloader options and how to schedule periodic refreshes. DB-IP Lite
107 +data is published monthly, so a monthly cron of the downloader is the right
108 +cadence -- more frequent runs will not produce newer data.
109 +
110 +
111 +#### Licence acknowledgement
112 +
113 +DB-IP Lite databases are distributed under the Creative Commons Attribution 4.0
114 +International licence (https://db-ip.com/db/lite.php). Attribution is required
115 +when redistributing the data or derivative dashboards.
116
117
118
@@ -151,19 +186,33 @@ enrichment:
186
187
188
154 -### Internal IPs appearing in random countries
189 +### Private IPs have empty GeoIP fields
190
156 -GeoIP databases have no entry for RFC 1918 / private space. The stock DB-IP
157 -build tags private ranges so `*_AS_NAME` renders as "AS0 Private IP Address Space"
158 -with empty country. With third-party MMDBs, results may vary. Declare your
159 -internal CIDRs under `enrichment.networks` to override -- see
160 -[Static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment/static-metadata).
191 +GeoIP databases normally have no country, city, or coordinate entry for RFC 1918 /
192 +private space. The DB-IP-built ASN database tags private ranges so `*_AS_NAME`
193 +renders as `AS0 Private IP Address Space`, while geographic fields stay empty and
194 +private addresses do not appear on maps. Declare your internal CIDRs under
195 +`enrichment.networks` when you want internal labels -- see
196 +[Static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment).
197
198
199 ### Stale databases
200
165 -The plugin does not alert on staleness. Check file mtime: `ls -la /var/cache/netdata/topology-ip-intel/`.
166 -Schedule a weekly cron of `topology-ip-intel-downloader` to keep data fresh.
201 +The plugin does not alert on staleness. Check file mtime:
202 +`ls -la /var/cache/netdata/topology-ip-intel/`. DB-IP Lite is published monthly,
203 +so a monthly cron of `/usr/sbin/topology-ip-intel-downloader` keeps you on the
204 +upstream cadence when the downloader is installed; running it more often will not
205 +produce fresher data.
206 +
207 +
208 +### Map renders empty over a long time window
209 +
210 +`SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`,
211 +`SRC_GEO_LONGITUDE`, and `DST_GEO_LONGITUDE` are stored only in the raw journal
212 +tier; the 1-minute, 5-minute, and 1-hour rollups drop them to keep cardinality
213 +bounded. A query that auto-falls back to a rollup tier therefore renders an empty
214 +city map. Narrow the time range so the query fits the raw tier, or use the
215 +country / state map (those survive into rollups).
216
217
218
src/crates/netflow-plugin/integrations/decapsulation.md new
+280
@@ -0,0 +1,280 @@
1 +<!--startmeta
2 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/integrations/decapsulation.md"
3 +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 +sidebar_label: "Decapsulation"
5 +learn_status: "Published"
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['decapsulation', 'srv6', 'vxlan', 'tunnel', 'overlay', 'inner packet', 'dataLinkFrameSection', 'layer2packetSectionData', 'ipfix ie 315', 'netflow v9 ie 104', 'sampled header']
8 +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 +endmeta-->
10 +
11 +<!-- markdownlint-disable-file -->
12 +
13 +# Decapsulation
14 +
15 +
16 +<img src="https://netdata.cloud/img/network-wired.svg" width="150"/>
17 +
18 +
19 +Plugin: netflow-plugin
20 +Module: decapsulation
21 +
22 +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
23 +
24 +## Overview
25 +
26 +Enrich network flows with inner source and destination endpoints from VXLAN or
27 +SRv6 encapsulated traffic. Decapsulation rewrites the 5-tuple of a flow record
28 +from the **outer** (tunnel) endpoints to the **inner** (real) endpoints when the
29 +exporter ships the encapsulated frame bytes alongside the standard fields. Without
30 +it, every VM-to-VM conversation on a VXLAN fabric or every inner flow on an SRv6
31 +transit shows up as the same tunnel pair (hypervisor-to-hypervisor or PE-to-PE),
32 +which tells you nothing about the actual traffic.
33 +
34 +Two encapsulations are supported, selected globally for the plugin. The mode set is
35 +`none`, `srv6`, or `vxlan`; the default is `none`.
36 +
37 +| Mode | Strips | Surfaces |
38 +|---------|--------------------------------------------------|--------------------------------------------------|
39 +| `none` | nothing | the outer-header view (default) |
40 +| `srv6` | IPv6 outer + extension headers + SRH (type 4) | the inner IPv4 (next-header 4) or IPv6 (41) |
41 +| `vxlan` | outer Eth/IP + UDP/4789 + 8-byte VXLAN header | the inner Ethernet frame, then its L3/L4 |
42 +
43 +The plugin only reaches the decap path when the flow record carries the inner
44 +frame bytes in a Layer-2 packet section field. Three transport-level paths feed it:
45 +
46 +- **NetFlow v9 IE 104** -- `Layer2packetSectionData` (RFC 5102 / RFC 7270).
47 +- **IPFIX IE 315** -- `dataLinkFrameSection` (RFC 7133).
48 +- **sFlow `SampledHeader`** -- always present in header-sampling mode.
49 +
50 +When decap succeeds, the inner 5-tuple replaces the outer one in the resulting
51 +journal record: `SRC_ADDR`, `DST_ADDR`, `SRC_PORT`, `DST_PORT`, `PROTOCOL`,
52 +`ETYPE`, `IPTOS`, `IPTTL`, `IPV6_FLOW_LABEL`, `TCP_FLAGS`, `IP_FRAGMENT_ID`,
53 +`IP_FRAGMENT_OFFSET`, ICMP type/code, MPLS labels (if present), and `BYTES`
54 +(set to the inner L3 length so byte counts represent inner payload, not outer
55 +overhead). For VXLAN, `SRC_MAC`, `DST_MAC`, `SRC_VLAN`, `DST_VLAN` come from the
56 +inner Ethernet frame -- the outer MACs and VLANs are lost. The VXLAN VNI is
57 +parsed but not exposed as a journal field; pure VNI-based segmentation is
58 +not visible.
59 +
60 +The `vxlan` parser matches **only UDP destination port 4789** (RFC 7348).
61 +VXLAN-GPE on 4790 and any vendor-custom port are not recognised. The `srv6`
62 +parser walks IPv6 extension headers and the Routing Header type 4 (SRH), then
63 +surfaces the inner IPv4 or IPv6 packet pointed to by next-header 4 or 41.
64 +
65 +For the cross-cutting concept (how decap composes with the rest of the enrichment
66 +pipeline, the non-tunnel "drop, do not fall back" semantics, and per-source
67 +dependence on the L2-section path), see
68 +[Decapsulation](https://learn.netdata.cloud/docs/network-flows/enrichment).
69 +
70 +
71 +Set `protocols.decapsulation_mode` in `netflow.yaml` to `srv6` or `vxlan`. The
72 +plugin then runs the inner-packet parser whenever a flow record carries an L2
73 +frame section (NetFlow v9 IE 104 / IPFIX IE 315 / sFlow `SampledHeader`). Plain
74 +NetFlow / IPFIX records that do **not** carry an L2 section pass through
75 +unchanged regardless of the setting -- so enabling decap will not break a mixed
76 +stream where only a subset of exporters ship the frame bytes. Enabling decap is
77 +half the work; the exporter must also be configured to ship the inner frame.
78 +
79 +
80 +This integration is only supported on the following platforms:
81 +
82 +- Linux
83 +
84 +This integration runs as a single instance per Netdata Agent.
85 +
86 +
87 +### Default Behavior
88 +
89 +#### Auto-Detection
90 +
91 +Disabled by default (`decapsulation_mode: none`). You must opt in by setting the mode explicitly.
92 +
93 +#### Limits
94 +
95 +One mode is active at a time -- the plugin cannot decap VXLAN and SRv6 simultaneously. If your fleet mixes both, choose the one your L2-section-bearing exporters carry exclusively.
96 +
97 +#### Performance Impact
98 +
99 +Decapsulation runs in the flow hot path for records carrying L2 frame sections. It adds protocol parsing work and drops L2-section records that do not match the configured tunnel mode.
100 +
101 +## Setup
102 +
103 +
104 +### Prerequisites
105 +
106 +#### Confirm your exporter ships the L2 frame section
107 +
108 +Decapsulation requires the exporter to include the inner frame bytes. Verify
109 +before enabling the mode:
110 +
111 +- **NetFlow v9** -- the template must contain field type 104
112 + (`Layer2packetSectionData`). Capture a packet with `tcpdump`, decode with
113 + Wireshark, and inspect the template.
114 +- **IPFIX** -- the template must contain Information Element 315
115 + (`dataLinkFrameSection`, RFC 7133). Same verification path.
116 +- **sFlow** -- header sampling is the default for sFlow agents and ships the
117 + truncated raw packet inside `SampledHeader`. No special configuration is
118 + required beyond enabling sFlow.
119 +
120 +Section length matters. VXLAN over Ethernet over IPv4 fits in roughly 96-128
121 +bytes of inner-frame capture; SRv6 with a Routing Header type 4 needs more
122 +(256 bytes is a safe starting point). Truncated captures fail the inner
123 +parser and the flow is dropped (see the failure modes on the concept page).
124 +
125 +
126 +#### Configure your exporter to emit the L2 section
127 +
128 +Vendor support varies. Recommended exporter paths are:
129 +
130 +- **Juniper inline-monitoring (IPFIX 315)** on platforms supporting
131 + `services { inline-monitoring { ... } }` -- the template includes
132 + `datalink-frame-size` and a `maximum-clip-length` controls how much of the
133 + frame is captured. Reference recipe in the
134 + [Akvorado documentation](https://demo.akvorado.net/docs/operations#ipfix-315).
135 +- **sFlow** with header sampling -- supported by Juniper QFX, Arista EOS,
136 + Mellanox/NVIDIA, MikroTik, and others. Header sampling is the default for
137 + most agents; verify the agent emits `SampledHeader` rather than only
138 + `SampledIPv4` / `SampledIPv6` records (the latter do not carry inner
139 + bytes).
140 +
141 +Cisco IOS-XE and IOS-XR Flexible NetFlow support for Layer-2 frame
142 +sections is platform-dependent. Before deploying Cisco decapsulation,
143 +inspect the exported template and look for IE 104 (v9) or IE 315 (IPFIX).
144 +Do not copy `collect datalink frame-section` snippets into production
145 +unless the platform template confirms that the L2 section is exported.
146 +
147 +
148 +
149 +### Configuration
150 +
151 +#### Options
152 +
153 +Decapsulation has a single configuration knob -- `protocols.decapsulation_mode`.
154 +
155 +
156 +<details open><summary>Config options</summary>
157 +
158 +
159 +
160 +| Option | Description | Default | Required |
161 +|:-----|:------------|:--------|:---------:|
162 +| protocols.decapsulation_mode | One of `none` (default), `srv6`, `vxlan`. The mode applies globally; the plugin cannot decode both VXLAN and SRv6 in the same instance. Setting this only affects records that travel through the L2-section path (NetFlow v9 IE 104 / IPFIX IE 315 / sFlow `SampledHeader`); regular flow records are unaffected. When the mode is set and the inner packet does not match the configured tunnel, the record is dropped -- there is no "fall back to outer view". | none | no |
163 +
164 +
165 +</details>
166 +
167 +
168 +
169 +#### via File
170 +
171 +The configuration file name for this integration is `netflow.yaml`.
172 +
173 +
174 +You can edit the configuration file using the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#edit-configuration-files) script from the
175 +Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#locate-your-config-directory).
176 +
177 +```bash
178 +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
179 +sudo ./edit-config netflow.yaml
180 +```
181 +
182 +##### Examples
183 +
184 +###### VXLAN-based VTEP fleet (decode the inner tenant traffic)
185 +
186 +Hypervisors emit sFlow with header sampling on the underlay. Every flow
187 +between two VTEP loopbacks decodes to the inner VM-to-VM 5-tuple instead
188 +of the underlay tunnel pair.
189 +
190 +
191 +```yaml
192 +protocols:
193 + decapsulation_mode: vxlan
194 +
195 +```
196 +###### SRv6 transit network (see the inner service traffic)
197 +
198 +Provider-edge routers export IPFIX with IE 315 (`dataLinkFrameSection`)
199 +via Juniper inline-monitoring. The plugin walks the IPv6 extension chain,
200 +strips the SRH, and surfaces the inner IPv4 or IPv6 5-tuple.
201 +
202 +
203 +<details open><summary>Config</summary>
204 +
205 +```yaml
206 +protocols:
207 + decapsulation_mode: srv6
208 +
209 +```
210 +</details>
211 +
212 +###### Default (no decapsulation)
213 +
214 +Outer-header view only. Tunnel endpoints appear as the source and
215 +destination of every flow.
216 +
217 +
218 +<details open><summary>Config</summary>
219 +
220 +```yaml
221 +protocols:
222 + decapsulation_mode: none
223 +
224 +```
225 +</details>
226 +
227 +
228 +
229 +### Decap mode set but tunnels still show outer endpoints
230 +
231 +The exporter is not shipping the L2 frame section. Plain NetFlow / IPFIX
232 +flow records (no IE 104 / IE 315) take the regular path and are unaffected
233 +by `decapsulation_mode`. Inspect the template -- look for field type 104
234 +on NetFlow v9 or IE 315 on IPFIX. For sFlow, confirm the agent is sending
235 +`SampledHeader` records rather than only `SampledIPv4` / `SampledIPv6`.
236 +
237 +
238 +### Records disappear after enabling decap
239 +
240 +When `decapsulation_mode` is set and a record arrives via the L2-section
241 +path with a payload that does not match the configured tunnel, the record
242 +is **dropped**. There is no fall back to the outer view. For sFlow with decap on, only `SampledHeader` records are processed;
243 +`SampledIPv4`, `SampledIPv6`, `SampledEthernet`, `ExtendedSwitch`,
244 +`ExtendedRouter`, `ExtendedGateway` records are skipped. If the same
245 +exporter mixes tunnel and non-tunnel traffic on the L2-section path, you
246 +will lose the non-tunnel records.
247 +
248 +
249 +### VXLAN on a non-default UDP port goes undetected
250 +
251 +The VXLAN parser matches only UDP destination port 4789. VXLAN-GPE on 4790 and any
252 +vendor-custom port are not recognised and the record is dropped under
253 +`decapsulation_mode: vxlan`.
254 +
255 +
256 +### Frame section truncated, inner parsing fails
257 +
258 +The exporter's clip / section size is shorter than the outer headers plus
259 +the inner L3/L4 needed to populate the 5-tuple. Increase the section size
260 +-- 128 bytes for VXLAN over IPv4, 256 bytes or more for SRv6 with extension
261 +headers. On Juniper inline-monitoring, the knob is `maximum-clip-length`.
262 +
263 +
264 +### VNI-based segmentation invisible
265 +
266 +Bytes 4-6 of the VXLAN header (the VNI) are not exposed as journal
267 +fields. If the inner Ethernet carries a VLAN tag, that VLAN reaches
268 +`SRC_VLAN` / `DST_VLAN` and works for segmentation -- pure VNI does not.
269 +No workaround inside the plugin; either VLAN-tag the inner traffic or
270 +filter at query time using the tunnel-endpoint pair before decap.
271 +
272 +
273 +### One mode at a time
274 +
275 +The plugin cannot decode VXLAN and SRv6 in the same instance. If exporter A
276 +ships VXLAN tenant traffic and exporter B ships SRv6 transit traffic, you
277 +must choose the mode that matches the traffic this Netdata Agent receives.
278 +
279 +
280 +
src/crates/netflow-plugin/integrations/gcp_ip_ranges.md
+208 -27
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "GCP IP Ranges"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Network Identity Sources"
7 -keywords: ['gcp', 'google cloud', 'cloud', 'ip ranges', 'prefix list']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['gcp', 'google cloud', 'google', 'cloud', 'ip ranges', 'cloud.json', 'goog.json', 'prefix list', 'network identity']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # GCP IP Ranges
14
15
@@ -21,16 +23,44 @@ Module: gcp-ip-ranges
23
24 ## Overview
25
24 -Google Cloud publishes its public IP prefixes at `https://www.gstatic.com/ipranges/cloud.json`,
25 -updated periodically. This integration fetches the file and labels flow records
26 -to/from Google Cloud with `*_NET_TENANT="gcp"` plus per-scope and per-service tags.
27 -
28 -For the full network-identity concept, see
29 -[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
30 -
31 -
32 -Periodic HTTPS GET, jq transform, merge into network-attributes trie. Same mechanism
33 -as AWS IP Ranges, different URL and JSON shape.
26 +Annotate network flows with Google ownership, service, and scope labels from
27 +Google public IP ranges. Google publishes its public IP ranges as two static JSON
28 +files served from `gstatic.com`. Wire one of them as a
29 +`network_source` and the netflow plugin fetches it periodically, transforms each
30 +prefix entry through your jq filter, and merges the resulting CIDR-keyed records
31 +into the network-attributes trie. Flows whose source or destination IP falls
32 +inside a Google-owned prefix are then labeled with `*_NET_TENANT`,
33 +`*_NET_REGION`, `*_NET_ROLE`, etc. according to the labels your transform emits.
34 +
35 +Two upstream files exist and they are NOT interchangeable:
36 +
37 +- **`https://www.gstatic.com/ipranges/cloud.json`** -- the prefixes used by
38 + Google Cloud (Compute Engine, GKE, Cloud Run, Cloud Functions, Cloud SQL,
39 + and the rest of Google Cloud Platform). This is what you almost always want
40 + for cloud-traffic attribution.
41 +- **`https://www.gstatic.com/ipranges/goog.json`** -- the broader Google IP
42 + space, including consumer-facing properties (Search, Gmail, YouTube, Google
43 + Workspace, Maps, Ads, etc.). It is a strict superset of `cloud.json` but
44 + **carries no service or region metadata** -- only the bare CIDRs.
45 +
46 +For the cross-cutting network-identity behavior (merge order with GeoIP and
47 +static config, jq output contract, TLS rules, single-page fetch, failure /
48 +backoff handling), see
49 +[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
50 +
51 +
52 +Periodic HTTPS GET against the chosen `gstatic.com` URL. The body is parsed
53 +as JSON and the configured `transform` (a [jaq](https://github.com/01mf02/jaq)
54 +jq-equivalent expression) runs over the parsed value. Each output object is
55 +merged into the network-attributes trie keyed on its `prefix` field; remaining
56 +fields (`name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`,
57 +`asn`, `asn_name`) populate the corresponding `*_NET_*` flow-record columns
58 +when a flow's IP matches the prefix.
59 +
60 +The upstream files are static JSON snapshots, not pageable APIs -- one fetch
61 +per cycle is the whole dataset. Each contains a `syncToken` and a
62 +`creationTime` you can inspect to see when Google last regenerated them.
63 +The plugin fetches the full document on each refresh cycle.
64
65
66 This integration is only supported on the following platforms:
@@ -48,21 +78,42 @@ Disabled by default. Add an entry under enrichment.network_sources to enable.
78
79 #### Limits
80
51 -The default configuration for this integration does not impose any limits.
81 +One full GCP prefix document is fetched per refresh. Resource use scales with the number of GCP prefixes selected by your transform and the refresh interval.
82
83 #### Performance Impact
84
55 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
85 +One short HTTPS GET per `interval`, plus a jq pass over the response.
86 +`cloud.json` is on the order of ~1000 prefix entries and the trie merge
87 +is bounded by that count; runtime cost is negligible against typical flow
88 +ingest. `goog.json` is similar in size.
89 +
90
91 ## Setup
92
93
94 ### Prerequisites
95
62 -#### Outbound HTTPS to Google
96 +#### Outbound HTTPS to gstatic.com
97
64 -The agent host must be able to reach `https://www.gstatic.com/ipranges/cloud.json`.
65 -No GCP credentials needed -- the file is public.
98 +The agent host must be able to reach `https://www.gstatic.com/ipranges/cloud.json`
99 +(or `goog.json`). The files are public CDN-served static JSON; no Google
100 +Cloud project, no service account, no API key, and no IAM permission is
101 +required.
102 +
103 +
104 +#### Pick the right file for your use case
105 +
106 +- Use `cloud.json` to attribute traffic to/from **Google Cloud Platform**
107 + services (Compute Engine VMs, GKE, Cloud Run, Cloud Functions, Cloud
108 + SQL, BigQuery API, Pub/Sub, GCS endpoints, etc.). Carries `service` and
109 + `scope` (region) per prefix.
110 +- Use `goog.json` to also catch **non-cloud Google traffic** (Gmail,
111 + YouTube, Google Workspace, Search, Ads, Maps, etc.). It carries no
112 + `service` or `scope` -- you only get "this is Google" attribution.
113 +- Configure both as separate sources if you want both attributions and
114 + a deterministic merge order between them. Within network-identity, more-
115 + specific prefixes win at lookup time, so a `cloud.json` match for a
116 + Compute Engine prefix will out-rank the broader `goog.json` umbrella.
117
118
119
@@ -70,7 +121,9 @@ No GCP credentials needed -- the file is public.
121
122 #### Options
123
73 -Add a named entry under `enrichment.network_sources`.
124 +Add a named entry under `enrichment.network_sources`. The entry name is
125 +cosmetic (used in logs and metrics); the labels that end up in flow records
126 +come from the fields your `transform` emits.
127
128
129 <details open><summary>Config options</summary>
@@ -79,8 +132,10 @@ Add a named entry under `enrichment.network_sources`.
132
133 | Option | Description | Default | Required |
134 |:-----|:------------|:--------|:---------:|
82 -| url | GCP publishes the master file here. | https://www.gstatic.com/ipranges/cloud.json | yes |
83 -| transform | jq expression mapping `prefixes[]` to `prefix` + label objects. | . | yes |
135 +| url | The Google IP-ranges JSON URL. Use `cloud.json` for GCP services or `goog.json` for the broader Google IP space. | https://www.gstatic.com/ipranges/cloud.json | yes |
136 +| interval | How often to refetch. Google does not publish a fixed cadence; their documentation states the lists are "published and updated frequently". Daily is plenty for most deployments and avoids hammering the CDN. | 60s (loop floor) | no |
137 +| timeout | Per-request timeout. | 60s | no |
138 +| transform | jq expression that converts each entry in `prefixes[]` into an object with a `prefix` field (CIDR string) plus any of the optional label fields (`name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`, `asn`, `asn_name`). Required. | . | yes |
139
140
141 </details>
@@ -102,9 +157,14 @@ sudo ./edit-config netflow.yaml
157
158 ##### Examples
159
105 -###### Tag all GCP prefixes by service and scope
160 +###### GCP cloud.json -- tag Google Cloud prefixes by region
161 +
162 +Tags every flow whose IP matches a Google Cloud prefix with
163 +`*_NET_TENANT="gcp"`, `*_NET_ROLE="google-cloud"`, and
164 +`*_NET_REGION` set to the GCP scope (`us-central1`, `europe-west1`,
165 +`global`, etc.). Both `ipv4Prefix` and `ipv6Prefix` entries are
166 +covered by the `// (slash-or)` fallback.
167
107 -Sets tenant=gcp, role=`service`, region=`scope`.
168
169 ```yaml
170 enrichment:
@@ -112,22 +172,143 @@ enrichment:
172 gcp:
173 url: "https://www.gstatic.com/ipranges/cloud.json"
174 interval: 24h
175 + timeout: 60s
176 transform: |
177 .prefixes[] | {
178 prefix: (.ipv4Prefix // .ipv6Prefix),
179 tenant: "gcp",
119 - role: .service,
180 + role: "google-cloud",
181 region: .scope
182 }
183
184 ```
185 +###### GCP cloud.json -- IPv4 only
186 +
187 +Same as above but skips the IPv6 prefixes (some operators only need
188 +IPv4 attribution and prefer to keep the trie smaller).
189 +
190 +
191 +<details open><summary>Config</summary>
192 +
193 +```yaml
194 +enrichment:
195 + network_sources:
196 + gcp-v4:
197 + url: "https://www.gstatic.com/ipranges/cloud.json"
198 + interval: 24h
199 + transform: |
200 + .prefixes[]
201 + | select(.ipv4Prefix)
202 + | {
203 + prefix: .ipv4Prefix,
204 + tenant: "gcp",
205 + role: "google-cloud",
206 + region: .scope
207 + }
208 +
209 +```
210 +</details>
211 +
212 +###### Tag a single GCP region
213 +
214 +Narrow the source to one region (here `us-central1`) so you can build
215 +dashboards that distinguish that region from the rest of GCP.
216 +
217 +
218 +<details open><summary>Config</summary>
219 +
220 +```yaml
221 +enrichment:
222 + network_sources:
223 + gcp-us-central1:
224 + url: "https://www.gstatic.com/ipranges/cloud.json"
225 + interval: 24h
226 + transform: |
227 + .prefixes[]
228 + | select(.scope == "us-central1")
229 + | {
230 + prefix: (.ipv4Prefix // .ipv6Prefix),
231 + tenant: "gcp",
232 + role: "google-cloud",
233 + region: "us-central1",
234 + site: "us-central1"
235 + }
236 +
237 +```
238 +</details>
239 +
240 +###### Broader Google IP space (goog.json)
241 +
242 +Use `goog.json` to also attribute traffic to non-Cloud Google services
243 +(Gmail, YouTube, Workspace, Search, Ads, Maps). The file has no
244 +`service` or `scope` fields, so only `tenant` / `role` are set.
245 +Configure this **alongside** `cloud.json` if you want both -- the more
246 +specific GCP prefixes from `cloud.json` will still win at lookup time.
247 +
248 +
249 +<details open><summary>Config</summary>
250 +
251 +```yaml
252 +enrichment:
253 + network_sources:
254 + google-all:
255 + url: "https://www.gstatic.com/ipranges/goog.json"
256 + interval: 24h
257 + transform: |
258 + .prefixes[] | {
259 + prefix: (.ipv4Prefix // .ipv6Prefix),
260 + tenant: "google",
261 + role: "google"
262 + }
263 +
264 +```
265 +</details>
266 +
267 +
268 +
269 +### cloud.json vs goog.json -- pick the right one
270 +
271 +`cloud.json` is the Google Cloud Platform list (Compute Engine, GKE,
272 +Cloud Run, Cloud SQL, BigQuery, GCS, etc.) and carries `service` plus
273 +`scope` (region) per entry. `goog.json` is the broader Google list
274 +(consumer products: Gmail, YouTube, Workspace, Search, Ads, Maps) and
275 +carries **only the bare CIDRs** -- no service, no scope, no region.
276 +Using `goog.json` as if it were `cloud.json` will give you "this is
277 +Google" attribution but no per-region or per-service breakdown. Most
278 +operators want `cloud.json`; some configure both as separate sources.
279 +
280 +
281 +### No per-service breakdown from cloud.json
282 +
283 +Today every entry in `cloud.json` reports `service: "Google Cloud"` --
284 +the file does not split prefixes by individual GCP service (Compute
285 +Engine vs GKE vs Cloud Run, etc.). Region (`scope`) is the dimension
286 +you can actually pivot on. Per-service attribution requires a different
287 +data source.
288 +
289 +
290 +### Empty result from the transform is treated as failure
291 +
292 +If the jq expression yields zero objects (for example, an over-narrow
293 +`select()` that no entry passes), the source backs off as if the fetch
294 +had errored. Check the journal for `network-sources` warnings and verify
295 +your filter against a saved copy of `cloud.json`.
296 +
297 +
298 +### TLS verification cannot be disabled
299 +
300 +`tls.skip_verify: true` (and `tls.verify: false`) are rejected by
301 +validation. `gstatic.com` is publicly trusted so this is rarely an issue
302 +for GCP; if you front the URL through an internal proxy with a private
303 +CA, supply it via `tls.ca_file`.
304
305
126 -### Customer-only ranges
306 +### Update cadence is not contractual
307
128 -GCP also publishes a `goog.json` file (broader: includes Google services beyond
129 -cloud). Use `cloud.json` for compute IP attribution; `goog.json` if you also
130 -want to tag Google's other services.
308 +Google states the lists are "published and updated frequently" but does
309 +not guarantee a fixed cadence. The `syncToken` and `creationTime` keys
310 +at the top of each file let you check when Google last regenerated it.
311 +Daily polling is a safe default; sub-hourly is unnecessary.
312
313
314
src/crates/netflow-plugin/integrations/generic_json-over-http_ipam.md
+198 -56
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "Generic JSON-over-HTTP IPAM"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Network Identity Sources"
7 -keywords: ['ipam', 'cmdb', 'infoblox', 'bluecat', 'phpipam', 'custom', 'prefix list']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['ipam', 'cmdb', 'infoblox', 'bluecat', 'phpipam', 'servicenow', 'custom', 'prefix list', 'network identity']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # Generic JSON-over-HTTP IPAM
14
15
@@ -21,20 +23,33 @@ Module: generic-ipam
23
24 ## Overview
25
24 -The catch-all integration. Any IPAM, CMDB, or service that exposes prefix metadata
25 -via an HTTP-fetchable JSON endpoint can plug into Netdata's flow enrichment via this
26 -mechanism. Examples: Infoblox WAPI, BlueCat REST API, phpIPAM, internal-built CMDB
27 -endpoints, ServiceNow CMDB queries, custom Lambda functions producing JSON.
26 +Annotate network flows with network-identity labels from any JSON-over-HTTP IPAM
27 +or CMDB endpoint. The plugin does not care WHO produces the JSON -- it cares
28 +about the JSON shape after your `transform` (a jq expression) runs over it.
29 +Examples: Infoblox WAPI, BlueCat REST API, phpIPAM, ServiceNow CMDB queries, an
30 +internal aggregator, a Lambda function, a static file served from S3, a
31 +hand-rolled CMDB.
32 +
33 +This card is also the **reference description** of how all the other Network
34 +Identity cards work under the hood. AWS IP Ranges, GCP IP Ranges, Azure IP Ranges
35 +and NetBox are all special-cased instances of this same mechanism: each is just
36 +a known URL plus a recommended jq `transform`. Read this card to understand the
37 +generic shape, then read the per-source cards for the URL and transform that fit
38 +that specific provider.
39
29 -You define the URL, the HTTP method, headers (for auth), and a jq transform that
30 -converts the response into objects with `prefix` + label fields.
40 +For the full network-identity concept (merge order vs GeoIP and static, what
41 +fields you can populate, why TLS verification cannot be disabled), see
42 +[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
43
32 -For the full network-identity concept, see
33 -[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
44
45 +The plugin issues a periodic HTTP request (GET by default, POST optionally) to
46 +your configured URL with the headers you specify, parses the response as JSON,
47 +runs your `transform` jq expression (compiled by the
48 +[jaq](https://github.com/01mf02/jaq) library) over the parsed body, and merges
49 +the resulting per-prefix rows into the network-attributes trie.
50
36 -Periodic HTTPS GET (or POST) to a configured URL with optional headers, optional
37 -custom CA / mTLS, jq transform of the response, merge into network-attributes trie.
51 +The same flow applies to every JSON-over-HTTP source; the integration-specific
52 +part is the URL and the `transform` expression.
53
54
55 This integration is only supported on the following platforms:
@@ -48,15 +63,15 @@ This integration supports multiple instances configured side-by-side.
63
64 #### Auto-Detection
65
51 -Disabled by default. Add an entry per IPAM source under enrichment.network_sources.
66 +Disabled by default. Add a named entry under `enrichment.network_sources` for each IPAM/CMDB/endpoint you want to import.
67
68 #### Limits
69
55 -The default configuration for this integration does not impose any limits.
70 +Resource use scales with response size, transform complexity, refresh interval, and the number of emitted prefixes. Empty transform output is treated as a fetch failure.
71
72 #### Performance Impact
73
59 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
74 +One HTTP request per refresh interval plus a jq transform over the response. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records.
75
76 ## Setup
77
@@ -65,16 +80,30 @@ The default configuration for this integration is not expected to impose a signi
80
81 #### An HTTP/HTTPS endpoint returning JSON
82
68 -The endpoint must produce a parseable JSON document. The plugin only supports
69 -GET and POST. There is no pagination, no cursor following, no OAuth flow --
70 -if your IPAM needs those, wrap it in an internal aggregator.
83 +The endpoint must respond with a parseable JSON document (the plugin sets
84 +`Accept: application/json`). Only GET and POST are accepted as request
85 +methods. There is no pagination, no
86 +cursor following, no `Link: rel=next` handling -- the fetch is one-shot
87 +per cycle. If your IPAM paginates, expose a bulk endpoint or wrap it in a
88 +server-side aggregator that returns the full list at one URL.
89
90
91 #### Authentication via headers
92
75 -The plugin has no built-in auth helpers. Set whatever the API needs -- bearer
76 -tokens, basic-auth header, custom API-key headers -- via `headers:`. Store
77 -tokens carefully; they're written into the YAML.
93 +The plugin has no built-in OAuth flow, basic-auth helper, or token
94 +refresh. Whatever the API needs (bearer tokens, API keys, custom header
95 +names, basic-auth realms) goes into the `headers:` map. The header map is
96 +an arbitrary string-to-string mapping, so any single-shot scheme works.
97 +For short-lived tokens, refresh them outside Netdata and reload the
98 +plugin config.
99 +
100 +
101 +#### A POST endpoint must accept an empty body
102 +
103 +When `method: POST` is configured, the plugin sends the request with the
104 +configured headers but **no request body**. If your CMDB requires a JSON query body to return prefixes,
105 +wrap it server-side with an endpoint that accepts GET (or POST with no
106 +body) and returns the full prefix set.
107
108
109
@@ -82,8 +111,8 @@ tokens carefully; they're written into the YAML.
111
112 #### Options
113
85 -Add a named entry under `enrichment.network_sources`. The keys below are the
86 -full set of options.
114 +Add a named entry under `enrichment.network_sources`. Unknown keys cause a
115 +config error.
116
117
118 <details open><summary>Config options</summary>
@@ -92,16 +121,19 @@ full set of options.
121
122 | Option | Description | Default | Required |
123 |:-----|:------------|:--------|:---------:|
95 -| url | HTTP/HTTPS endpoint. | | yes |
96 -| method | GET or POST. | GET | no |
97 -| interval | Refresh interval (loop floors at 60s). | 60s | no |
98 -| timeout | Per-request timeout. | 60s | no |
99 -| headers | Map of additional HTTP request headers (e.g., authentication). | {} | no |
100 -| transform | jq expression converting response to {prefix, name?, role?, site?, region?, country?, state?, city?, tenant?, asn?, asn_name?} stream. | . | yes |
101 -| tls.enable | Use custom TLS settings (custom CA, mTLS). | false | no |
102 -| tls.ca_file | PEM file with the CA bundle. | | no |
103 -| tls.cert_file | PEM file with the client certificate (mTLS). | | no |
104 -| tls.key_file | PEM file with the client private key. | | no |
124 +| url | HTTP/HTTPS endpoint. Required (a non-empty URL is enforced by validation). | | yes |
125 +| method | HTTP method. Only `GET` and `POST` are accepted; anything else fails validation. Note that `POST` is sent with no request body. | GET | no |
126 +| headers | Map of additional HTTP request headers. Use this for any authentication scheme (`Authorization: Bearer ...`, `token: ...`, custom API-key headers, basic-auth realms encoded explicitly, etc.). Values are passed through verbatim. | {} | no |
127 +| interval | Refresh cadence. Values below 60 seconds are accepted, but refreshes still run no faster than once per minute. Pick the value that matches how often your IPAM actually changes (5-15 minutes for a curated CMDB; daily for slow-moving prefix lists). | 60s | no |
128 +| timeout | Per-request timeout. Must be greater than 0. | 60s | no |
129 +| proxy | Whether to honor the system HTTP/HTTPS proxy environment variables. Set to `false` to bypass the proxy for this source (useful when the IPAM is on the internal network and the proxy is for outbound traffic only). | true | no |
130 +| tls.enable | Enables custom TLS settings (custom CA bundle, mTLS client certificate). Must be `true` whenever any of `tls.ca_file`, `tls.cert_file`, `tls.key_file` is set. | false | no |
131 +| tls.verify | Certificate verification toggle. **Setting this to `false` is rejected by validation** -- network-identity data flows directly into security-relevant enrichment, so MITM-able responses are not allowed. Use `tls.ca_file` to trust internal CAs instead. | true | no |
132 +| tls.skip_verify | Legacy alias for the same intent as `tls.verify: false`. **Setting this to `true` is rejected by validation.** Use `tls.ca_file`. | false | no |
133 +| tls.ca_file | PEM file with the CA bundle to trust for this endpoint (instead of, or in addition to, the system roots). The recommended way to talk to an internal IPAM behind your own PKI. | | no |
134 +| tls.cert_file | PEM file with the client certificate, for mTLS-protected endpoints. | | no |
135 +| tls.key_file | PEM file with the client private key. Required only when the key is in a separate file from the certificate; if omitted, the cert file is reused. `tls.cert_file` must also be set whenever `tls.key_file` is set. | | no |
136 +| transform | jq expression compiled by [jaq](https://github.com/01mf02/jaq). Receives the entire parsed JSON body once and must produce a **stream of objects**, where each object has at minimum a `prefix` field (a CIDR string -- `IPv4/len` or `IPv6/len`) plus any of the optional attribute fields: `name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`, `asn`, `asn_name`. Any field not produced is treated as empty. The transform compiles at startup -- a syntax error fails the config load. An empty stream at runtime is treated as a fetch failure and triggers backoff. | . | yes |
137
138
139 </details>
@@ -123,9 +155,36 @@ sudo ./edit-config netflow.yaml
155
156 ##### Examples
157
158 +###### Flat list with `{prefix, name, env}`
159 +
160 +The simplest IPAM shape: a top-level array of subnets, each with a CIDR
161 +string and a few labels. Maps `env` -> tenant and the `name` -> NET_NAME.
162 +
163 +
164 +```yaml
165 +enrichment:
166 + network_sources:
167 + custom_ipam:
168 + url: "https://ipam.corp.example/api/v1/prefixes"
169 + headers:
170 + Authorization: "Bearer abcdef0123456789"
171 + interval: 5m
172 + transform: |
173 + .[] | {
174 + prefix: .prefix,
175 + name: .name,
176 + tenant: .env
177 + }
178 +
179 +```
180 ###### phpIPAM with API token
181
128 -phpIPAM exposes `/api/<APP>/subnets/`. Replace `<APP>` with your phpIPAM app name. Use the standard transform.
182 +phpIPAM exposes `/api/<APP>/subnets/`. Replace `<APP>` with your phpIPAM
183 +application name. phpIPAM splits the address into a base and a mask --
184 +the transform reassembles them into a CIDR string.
185 +
186 +
187 +<details open><summary>Config</summary>
188
189 ```yaml
190 enrichment:
@@ -144,9 +203,14 @@ enrichment:
203 }
204
205 ```
147 -###### Custom internal CMDB (POST with body)
206 +</details>
207 +
208 +###### Hierarchical CMDB export
209 +
210 +A CMDB that returns nested objects -- top-level sites, each with a list
211 +of vlans, each with a list of prefixes. Flattens to one row per prefix,
212 +inheriting site and role labels from the parent.
213
149 -When your CMDB requires POST with a query body. Define `method` and append the body via headers/url. The plugin's body support is limited -- prefer GET endpoints when possible.
214
215 <details open><summary>Config</summary>
216
@@ -154,26 +218,32 @@ When your CMDB requires POST with a query body. Define `method` and append the b
218 enrichment:
219 network_sources:
220 cmdb:
157 - url: "https://cmdb.example/query/networks"
158 - method: POST
221 + url: "https://cmdb.example/export/networks.json"
222 headers:
160 - Authorization: "Bearer ..."
161 - Content-Type: "application/json"
162 - interval: 30m
223 + Authorization: "Bearer <CMDB_TOKEN>"
224 + interval: 15m
225 transform: |
164 - .results[] | {
165 - prefix: .cidr,
166 - tenant: .organization,
167 - site: .datacenter,
168 - role: .purpose
169 - }
226 + .sites[] as $site
227 + | $site.vlans[] as $vlan
228 + | $vlan.prefixes[]
229 + | {
230 + prefix: .cidr,
231 + name: .label,
232 + site: $site.name,
233 + region: $site.region,
234 + role: $vlan.role,
235 + tenant: $vlan.tenant
236 + }
237
238 ```
239 </details>
240
174 -###### Internal IPAM with mTLS
241 +###### Internal IPAM behind mTLS
242 +
243 +When the IPAM is fronted by your internal PKI and requires a client
244 +certificate. `tls.enable: true` activates the custom TLS path; `tls.verify`
245 +must remain true (cannot be disabled).
246
176 -When the IPAM is behind your internal PKI.
247
248 <details open><summary>Config</summary>
249
@@ -198,25 +268,97 @@ enrichment:
268 ```
269 </details>
270
271 +###### Source with the system proxy bypassed
272 +
273 +When the agent host has an outbound HTTP proxy configured but the IPAM
274 +lives on the internal network and should be reached directly.
275 +
276 +
277 +<details open><summary>Config</summary>
278 +
279 +```yaml
280 +enrichment:
281 + network_sources:
282 + internal_ipam:
283 + url: "https://ipam.internal/api/networks"
284 + proxy: false
285 + headers:
286 + Authorization: "Bearer ..."
287 + interval: 5m
288 + transform: |
289 + .[] | { prefix: .cidr, name: .label }
290 +
291 +```
292 +</details>
293 +
294
295
296 ### Endpoint requires pagination
297
205 -The plugin does not paginate. Either raise the page size to cover your inventory,
206 -or wrap the endpoint with an internal aggregator that returns all results at one URL.
298 +The plugin does not paginate. Either raise the page size in the URL to
299 +cover your full inventory, or wrap the endpoint with a server-side
300 +aggregator that returns all results at one URL. There is no built-in
301 +`Link: rel=next` follower.
302 +
303 +
304 +### POST endpoint requires a request body
305 +
306 +The plugin's POST request is sent with no body. If your CMDB requires a
307 +JSON query body to return prefixes, wrap it server-side with an endpoint
308 +that accepts GET (or accepts POST with no body) and returns the full prefix
309 +set.
310
311
312 ### TLS verification cannot be disabled
313
211 -`tls.skip_verify` and `tls.verify: false` are rejected by validation. Use
212 -`tls.ca_file` to trust internal CAs.
314 +`tls.verify: false` and `tls.skip_verify: true` are both rejected during
315 +configuration validation. Use `tls.ca_file` to trust internal CAs. This is
316 +deliberate -- network-identity rows feed enrichment used in security
317 +investigations and capacity decisions, where silently accepting MITM-able
318 +responses would corrupt every downstream analysis.
319
320
321 ### Empty result back-off
322
217 -An empty jq result is treated as a fetch failure. If your IPAM legitimately
218 -returns no prefixes (quiet state), the source backs off as if it errored.
219 -Workaround: have the upstream return at least one synthetic prefix.
323 +An empty stream from the jq transform is treated as a fetch failure.
324 +The source then backs
325 +off exponentially -- starting at `interval / 10` (floor 1s), doubling on
326 +each consecutive failure, and capped at the regular `interval`. On the
327 +next successful non-empty fetch the cadence resets to `interval`. If your
328 +IPAM legitimately has no prefixes (a quiet state), have the upstream
329 +return at least one synthetic prefix so the source does not back off.
330 +
331 +
332 +### Refresh appears slower than configured
333 +
334 +The fetch loop floors the configured `interval` at 60 seconds. Configuring
335 +`interval: 5s` does not produce a 5-second loop -- it produces a 60-second
336 +loop. Pick a value at or above 60s that matches how often your IPAM
337 +actually changes (5-15 minutes is typical for a curated CMDB).
338 +
339 +
340 +### Unknown config keys cause errors
341 +
342 +The config struct uses `deny_unknown_fields`. Typos like `headres:` or a
343 +non-existent option fail config load with a parse error rather than being
344 +silently ignored. Check the `enrichment.network_sources.<name>` schema
345 +listed under "Config options".
346 +
347 +
348 +### JSON parse errors are silent in the dashboard
349 +
350 +Decode failures (HTTP error, JSON parse error, jq runtime error, schema
351 +mismatch on `prefix`) are logged but do not surface in the dashboard.
352 +Watch the Netdata journal for warnings:
353 +`journalctl --namespace netdata | grep network_sources`.
354 +
355 +
356 +### Prefer explicit authorization headers over URL credentials
357 +
358 +URLs with embedded credentials (`https://user:pass@host`) are converted to
359 +HTTP Basic authentication by the HTTP client. Prefer `headers:` for clarity
360 +and to avoid storing credentials in URLs -- e.g.
361 +`headers: { Authorization: "Basic dXNlcjpwYXNz" }` for HTTP basic-auth.
362
363
364
src/crates/netflow-plugin/integrations/ipfix.md
+13 -12
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "IPFIX"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Sources"
6 +learn_rel_path: "Network Flows/Flow Protocols"
7 keywords: ['ipfix', 'netflow v10', 'flows', 'network flows', 'flow collector', 'rfc 7011']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # IPFIX
14
15
@@ -21,14 +23,13 @@ Module: ipfix
23
24 ## Overview
25
24 -Collects IPFIX (NetFlow v10) records from one or more exporters and stores them in tiered
25 -journal files. IPFIX extends NetFlow v9 with variable-length fields, vendor-specific
26 -information elements, and template withdrawal. Each record exposes the same core fields
27 -as NetFlow plus any additional IEs the exporter provides.
26 +Collect network flow records from IPFIX (NetFlow v10) exporters. IPFIX extends
27 +NetFlow v9 with variable-length fields, vendor-specific information elements,
28 +and template withdrawal. Each record exposes the same core fields as NetFlow plus
29 +any additional IEs the exporter provides.
30
31 For full documentation including vendor configuration examples (Cisco, Juniper, Arista,
30 -ASA NSEL), biflow handling, sampling caveats, and verification steps, see
31 -[IPFIX](https://learn.netdata.cloud/docs/network-flows/sources/ipfix) and the
32 +ASA NSEL), biflow handling, sampling caveats, and verification steps, see the
33 [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
34
35
@@ -52,11 +53,11 @@ The plugin starts when enabled in netflow.yaml and listens on the configured UDP
53
54 #### Limits
55
55 -The default configuration for this integration does not impose any limits.
56 +Operational limits are driven by sustained flows/s, template churn, cardinality, retention, storage speed, and enrichment. Plan around 25k sustained flows/s per well-provisioned agent for the full raw + rollup pipeline; use distributed agents for larger deployments.
57
58 #### Performance Impact
59
59 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
60 +Disabled until exporters send traffic. Once active, CPU and disk I/O scale with flow rate, template volume, and cardinality; size retention and storage from observed flows/s.
61
62 ## Setup
63
@@ -87,8 +88,8 @@ Enable IPFIX via the `protocols.ipfix` option.
88 | listener.listen | UDP endpoint for IPFIX datagrams. | 0.0.0.0:2055 | no |
89 | protocols.ipfix | Enable IPFIX decoding. | yes | no |
90 | journal.journal_dir | Directory for journal files (relative to NETDATA_CACHE_DIR). | flows | no |
90 -| journal.size_of_journal_files | Maximum total size of all journal files. | 10GB | no |
91 -| journal.duration_of_journal_files | Maximum age of journal files. | 7d | no |
91 +| journal.tiers.&lt;tier&gt;.size_of_journal_files | Per-tier hard size cap. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for time-only retention. | 10GB | no |
92 +| journal.tiers.&lt;tier&gt;.duration_of_journal_files | Per-tier maximum age. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for size-only retention. | 7d | no |
93
94
95 </details>
@@ -112,7 +113,7 @@ sudo ./edit-config netflow.yaml
113
114 ###### IPFIX collection
115
115 -Listen for IPFIX records on the standard port.
116 +Listen for IPFIX records on Netdata's default flow listener port.
117
118 ```yaml
119 enabled: true
src/crates/netflow-plugin/integrations/iptoasn.md
+140 -32
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "IPtoASN"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/IP Intelligence"
7 -keywords: ['iptoasn', 'asn', 'bgp', 'public asn', 'ip intelligence']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['iptoasn', 'asn', 'bgp', 'public asn', 'public domain', 'pddl', 'ip intelligence']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # IPtoASN
14
15
@@ -21,20 +23,42 @@ Module: iptoasn
23
24 ## Overview
25
24 -[IPtoASN](https://iptoasn.com/) is a free public IP-to-ASN database derived from
25 -BGP RIB snapshots. Daily updates, no license required. Use it as a free, open
26 -alternative to MaxMind ASN data when license cost or terms matter.
27 -
28 -IPtoASN provides ASN data only -- no geographic data. Pair with DB-IP, MaxMind,
29 -or another geo source for country/city enrichment.
30 -
31 -For the full IP-intelligence concept, see
32 -[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
33 -
34 -
35 -The bundled `topology-ip-intel-downloader` supports IPtoASN as an ASN provider,
36 -fetching the latest TSV and converting it to MMDB format the plugin can read.
37 -Configure the downloader to use IPtoASN with `--asn iptoasn:combined`.
26 +Enrich network flows with public ASN and country context from IPtoASN
27 +BGP-derived datasets. [IPtoASN](https://iptoasn.com/) is a free, public-domain
28 +feed of IP-to-ASN mappings. The upstream publishes its dataset
29 +under the [Public Domain Dedication and License (PDDL)](https://opendatacommons.org/licenses/pddl/)
30 +(see [iptoasn.com](https://iptoasn.com/) -- "Frequently updated, public domain")
31 +and rebuilds it hourly, making it the highest-cadence and lowest-friction public
32 +ASN source available. Use it as a free, open alternative to MaxMind ASN data when
33 +license cost or terms matter.
34 +
35 +IPtoASN's `ip2asn-combined` artifact contains five tab-separated columns:
36 +`range_start`, `range_end`, `AS_number`, `country_code`, and `AS_description`
37 +(see [iptoasn.com](https://iptoasn.com/) for the format reference). So
38 +although IPtoASN is often described as "ASN-only", the feed also carries an
39 +ISO 3166 country code per range and the Netdata downloader exposes both as
40 +ASN + geo MMDBs. Pair with DB-IP or MaxMind when you also need state, city,
41 +or coordinates.
42 +
43 +For the IP-intelligence mechanism (lookup order, MMDB composition, refresh,
44 +provider chains), see
45 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
46 +
47 +
48 +The Netdata plugin reads MMDB only. IPtoASN ships gzipped TSV
49 +(`https://iptoasn.com/data/ip2asn-combined.tsv.gz`), so the TSV must be
50 +converted to MMDB before the plugin can use it.
51 +
52 +When available, Netdata provides a converter for this exact purpose: the
53 +`topology-ip-intel-downloader` Go tool fetches the upstream TSV, parses it,
54 +and emits MMDB files the plugin auto-detects. This is
55 +a separate operator step (cron); the plugin itself does no fetching or
56 +conversion. Packaged 32-bit installs ship the stock MMDB payload but do not include
57 +the downloader binary. If you prefer not to use the bundled tool, any third-party
58 +IPtoASN-to-MMDB converter that produces a standard
59 +[MaxMind DB](https://maxmind.github.io/MaxMind-DB/) with `iso_code`,
60 +`autonomous_system_number`, and `autonomous_system_organization` fields
61 +will also work with the Custom MMDB integration.
62
63
64 This integration is only supported on the following platforms:
@@ -48,15 +72,22 @@ This integration runs as a single instance per Netdata Agent.
72
73 #### Auto-Detection
74
51 -Not auto-detected as the default ASN source -- the plugin auto-detects DB-IP. To use IPtoASN as ASN, run the downloader explicitly.
75 +Not auto-detected as the default ASN source -- the plugin auto-detects
76 +DB-IP MMDBs in the cache directory at startup
77 +(see [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment)).
78 +To use IPtoASN, run `topology-ip-intel-downloader` when available to produce IPtoASN-derived
79 +MMDBs (the downloader writes to the same cache paths the plugin scans,
80 +so once present they are picked up automatically on the 30-second
81 +file-signature check).
82 +
83
84 #### Limits
85
55 -The default configuration for this integration does not impose any limits.
86 +IPtoASN provides AS number and country-level data only. It does not provide city, coordinates, or rich organization names unless you layer another MMDB source.
87
88 #### Performance Impact
89
59 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
90 +Lookups are local MMDB reads after the downloader converts the TSV feed. Memory use is mostly the mapped database files and the kernel page cache needed to keep active pages hot.
91
92 ## Setup
93
@@ -65,8 +96,19 @@ The default configuration for this integration is not expected to impose a signi
96
97 #### Run the downloader with IPtoASN as ASN source
98
68 -IPtoASN is a TSV file. The bundled downloader knows how to fetch and convert
69 -it to MMDB:
99 +When available, the downloader knows how to fetch the upstream TSV and convert
100 +it to MMDB. Packaged 32-bit installs do not include this binary:
101 +
102 +```bash
103 +sudo /usr/sbin/topology-ip-intel-downloader \
104 + --asn iptoasn:combined \
105 + --geo iptoasn:combined
106 +```
107 +
108 +`iptoasn:combined` is valid for both `--asn` and `--geo` because the
109 +upstream TSV carries both AS data and country. If you
110 +want richer geographic data (state, city, coordinates), pair IPtoASN
111 +ASN with DB-IP or MaxMind geo:
112
113 ```bash
114 sudo /usr/sbin/topology-ip-intel-downloader \
@@ -74,8 +116,10 @@ sudo /usr/sbin/topology-ip-intel-downloader \
116 --geo dbip:city-lite
117 ```
118
77 -This produces ASN data from IPtoASN and geographic data from DB-IP. Schedule
78 -this in cron (daily for ASN; weekly is enough for geo).
119 +IPtoASN rebuilds hourly upstream, but a daily cron is sufficient for
120 +flow enrichment (ASN ownership rarely changes within a single day).
121 +Do not exceed hourly -- you only get a fresh dataset once an hour
122 +regardless.
123
124
125
@@ -83,8 +127,10 @@ this in cron (daily for ASN; weekly is enough for geo).
127
128 #### Options
129
86 -Once the downloader has produced MMDB files in the cache directory, the plugin
87 -auto-detects them. To pin the path explicitly, set `enrichment.geoip.asn_database`.
130 +Once the downloader has produced IPtoASN-derived MMDB files in the cache
131 +directory, the plugin auto-detects them. To pin the path explicitly, set
132 +`enrichment.geoip.asn_database` (and `geo_database` when you also want
133 +the country data IPtoASN provides).
134
135
136 <details open><summary>Config options</summary>
@@ -93,7 +139,9 @@ auto-detects them. To pin the path explicitly, set `enrichment.geoip.asn_databas
139
140 | Option | Description | Default | Required |
141 |:-----|:------------|:--------|:---------:|
96 -| enrichment.geoip.asn_database | Path to the IPtoASN-derived MMDB. Empty = auto-detect from cache directory. | [] | no |
142 +| enrichment.geoip.asn_database | Path to the IPtoASN-derived ASN MMDB. Empty = auto-detect from cache directory. | [] | no |
143 +| enrichment.geoip.geo_database | Path to the IPtoASN-derived geo MMDB (country only). Empty = auto-detect; or point at a richer DB-IP / MaxMind geo MMDB for state/city/coordinates. | [] | no |
144 +| enrichment.geoip.optional | When true, missing or unreadable MMDBs are warnings, not fatal at startup. | false (true when auto-detected) | no |
145
146
147 </details>
@@ -117,7 +165,46 @@ sudo ./edit-config netflow.yaml
165
166 ###### After running the downloader with IPtoASN
167
120 -Auto-detection picks up the cache copy.
168 +Auto-detection picks up the cache copy. Both ASN and country come from IPtoASN.
169 +
170 +```yaml
171 +enrichment:
172 + geoip:
173 + asn_database: []
174 + geo_database: []
175 + optional: true
176 +
177 +```
178 +###### Explicit IPtoASN paths (ASN + country only)
179 +
180 +Pin the IPtoASN-derived MMDBs explicitly. Country only -- no
181 +state/city. Use the DB-IP or MaxMind integration cards for richer
182 +geographic enrichment.
183 +
184 +
185 +<details open><summary>Config</summary>
186 +
187 +```yaml
188 +enrichment:
189 + geoip:
190 + asn_database:
191 + - /var/cache/netdata/topology-ip-intel/topology-ip-asn.mmdb
192 + geo_database:
193 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
194 + optional: false
195 +
196 +```
197 +</details>
198 +
199 +###### IPtoASN ASN + DB-IP city-lite geo
200 +
201 +Free, fully public-domain ASN combined with DB-IP city-lite for
202 +state/city/coordinates. Run the downloader as
203 +`topology-ip-intel-downloader --asn iptoasn:combined --geo dbip:city-lite`
204 +and let auto-detection handle the rest.
205 +
206 +
207 +<details open><summary>Config</summary>
208
209 ```yaml
210 enrichment:
@@ -127,19 +214,40 @@ enrichment:
214 optional: true
215
216 ```
217 +</details>
218 +
219
220
221 ### ASN names not appearing
222
134 -IPtoASN's data does not always carry a human-readable ASN organization name.
135 -The plugin renders `AS{n}` (without a name) for those records. This is data-source-
136 -level, not a plugin issue. Use MaxMind GeoLite2-ASN if you need richer name data.
223 +IPtoASN's `AS_description` column is sometimes empty for less-common
224 +ASNs (the upstream derives names from RIRs and the chain occasionally
225 +has gaps). When that happens the plugin renders the AS as `AS{n}` with
226 +no organisation. This is data-source-level, not a plugin issue. If richer AS-name
227 +coverage matters, layer a MaxMind GeoLite2-ASN MMDB after IPtoASN in
228 +`asn_database` -- per-field "last database with a non-empty value
229 +wins" composition (see
230 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment))
231 +means MaxMind names override IPtoASN's empty entries.
232 +
233 +
234 +### Country empty although IPtoASN was selected
235 +
236 +IPtoASN's combined TSV publishes country only when it is known. Public
237 +IPs that the upstream cannot attribute will have an empty
238 +`*_COUNTRY`. If you need broader country coverage, point
239 +`geo_database` at a DB-IP or MaxMind country MMDB instead -- the
240 +plugin reads any combination.
241
242
243 ### Outdated ASN attribution
244
141 -IPtoASN is rebuilt daily from BGP. Cron the downloader at least daily to keep
142 -ASN attribution current with real-world routing changes.
245 +IPtoASN rebuilds hourly upstream. A daily cron of the downloader is
246 +the recommended cadence for flow enrichment; weekly is too slow
247 +because BGP-driven prefix re-assignments will land in the dataset
248 +within hours but not in your cache until the next download. The
249 +plugin reloads MMDB files in place every 30 seconds when the file
250 +signature changes, so a fresh file lands without restart.
251
252
253
src/crates/netflow-plugin/integrations/maxmind_geoip_-_geolite2.md
+144 -35
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "MaxMind GeoIP / GeoLite2"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/IP Intelligence"
7 -keywords: ['maxmind', 'geoip2', 'geolite2', 'geoip', 'asn', 'mmdb', 'ip intelligence']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['maxmind', 'geoip2', 'geolite2', 'geoip', 'asn', 'mmdb', 'ip intelligence', 'flow enrichment']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # MaxMind GeoIP / GeoLite2
14
15
@@ -21,22 +23,45 @@ Module: maxmind
23
24 ## Overview
25
24 -MaxMind GeoIP2 (commercial) and GeoLite2 (free tier with license key) MMDB databases
25 -are read directly by the netflow plugin. The plugin uses any MMDB-format file that
26 -exposes the standard schema -- it is not tied to MaxMind specifically, but MaxMind
27 -is the canonical source and the format originator.
26 +Enrich network flows with ASN and geographic context from MaxMind GeoIP /
27 +GeoLite2 MMDB databases. MaxMind is the originator of the MMDB binary format and
28 +the canonical commercial source for geo and ASN data. The netflow plugin reads
29 +MaxMind MMDB files directly; they are interchangeable with other MMDB providers
30 +(DB-IP, custom builds).
31 +
32 +Two product lines are supported:
33 +
34 +- **GeoLite2** (free): requires a free MaxMind account and a license key; users
35 + must accept the GeoLite2 EULA, which includes an attribution requirement. See
36 + <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>.
37 +- **GeoIP2** (commercial): paid subscription with the same license-key download
38 + mechanism but higher accuracy and more frequent updates. See
39 + <https://www.maxmind.com/en/geoip2-databases>.
40
29 -Populates the same `SRC_COUNTRY`, `*_GEO_*`, and AS-name fields as DB-IP. Use this
30 -integration when you have a MaxMind license and prefer their data over the bundled
31 -DB-IP defaults.
41 +Each lookup populates the same flow-record fields as DB-IP: `SRC_COUNTRY`,
42 +`DST_COUNTRY`, `SRC_GEO_STATE`, `DST_GEO_STATE`, `SRC_GEO_CITY`, `DST_GEO_CITY`,
43 +`SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`, `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`,
44 +`SRC_AS_NAME`, and `DST_AS_NAME`. Country and state survive into all rollup tiers;
45 +city and lat/lon are **raw-tier-only**. Rollup tiers keep only the
46 +bounded-cardinality geography columns.
47
33 -For the full IP-intelligence concept, see
34 -[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
48 +For the cross-cutting concept (resolver poll loop, multi-database composition,
49 +ASN provider chain, failure-modes table), see
50 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
51
52
37 -You download the MaxMind MMDB files yourself (via `geoipupdate` or manual download),
38 -then point the plugin at their paths in `netflow.yaml`. The plugin reloads on file
39 -change every 30 seconds.
53 +You download the MaxMind MMDB files yourself using `geoipupdate` (MaxMind's
54 +official downloader, configured with your account ID + license key) or via direct
55 +HTTPS download with the same credentials. MaxMind requires authentication for all
56 +downloads -- there is no anonymous access. The bundled
57 +`topology-ip-intel-downloader` shipped by Netdata does **not** know how to fetch
58 +MaxMind databases (it supports DB-IP and IPtoASN only); use `geoipupdate` and
59 +schedule it via cron or systemd-timer.
60 +
61 +Once the files are on disk, point `asn_database` and `geo_database` at their
62 +paths in `netflow.yaml`. The plugin reloads on file change automatically -- a
63 +successful `geoipupdate` run causes the plugin to swap readers within ~30s, no
64 +restart needed.
65
66
67 This integration is only supported on the following platforms:
@@ -50,27 +75,69 @@ This integration runs as a single instance per Netdata Agent.
75
76 #### Auto-Detection
77
53 -Not auto-detected. You must configure the database paths explicitly.
78 +Not auto-detected. The plugin auto-detects the bundled DB-IP files; to use MaxMind you must configure the database paths explicitly. Set `enrichment.geoip.asn_database` and `enrichment.geoip.geo_database` to the MaxMind file paths.
79
80 #### Limits
81
57 -The default configuration for this integration does not impose any limits.
82 +Lookup coverage, fields, license terms, and refresh cadence depend on the MaxMind database edition you configure. Netdata does not fetch MaxMind databases automatically.
83
84 #### Performance Impact
85
61 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
86 +Lookups are local MMDB reads with no per-flow network call. Memory use is mostly the mapped database files and the kernel page cache needed to keep active pages hot.
87
88 ## Setup
89
90
91 ### Prerequisites
92
68 -#### MaxMind license + downloaded MMDBs
93 +#### MaxMind account and license key
94 +
95 +MaxMind does not allow anonymous downloads. Required for both GeoLite2 (free)
96 +and GeoIP2 (paid):
97
70 -For GeoLite2 (free): create a MaxMind account, generate a license key, install
71 -`geoipupdate`, and configure it to fetch `GeoLite2-City.mmdb` and
72 -`GeoLite2-ASN.mmdb`. For GeoIP2 (paid): obtain a subscription and use the same
73 -`geoipupdate` mechanism with your paid license key.
98 +1. Sign up at <https://www.maxmind.com/en/geolite2/signup> (GeoLite2) or
99 + purchase a GeoIP2 subscription.
100 +2. Generate a license key under "Account > Manage License Keys".
101 +3. Note your account ID (visible on the same account page).
102 +
103 +
104 +#### Install and configure geoipupdate
105 +
106 +`geoipupdate` is MaxMind's official downloader. Install it from your distro
107 +(`apt install geoipupdate`, `dnf install geoipupdate`) or download from
108 +<https://github.com/maxmind/geoipupdate/releases>.
109 +
110 +Edit `/etc/GeoIP.conf`:
111 +
112 +```ini
113 +AccountID YOUR_ACCOUNT_ID
114 +LicenseKey YOUR_LICENSE_KEY
115 +# GeoLite2 (free):
116 +EditionIDs GeoLite2-City GeoLite2-ASN
117 +# ...or for GeoIP2 (paid):
118 +# EditionIDs GeoIP2-City GeoIP2-ISP
119 +DatabaseDirectory /usr/share/GeoIP
120 +```
121 +
122 +Run `sudo geoipupdate` to fetch initial files, then schedule it via cron or
123 +the bundled `geoipupdate.timer` systemd unit. Recommended cadences match the
124 +upstream publish frequency:
125 +
126 +- **GeoLite2 City / Country**: published twice weekly (Tuesday and Friday).
127 +- **GeoLite2 ASN**: published every weekday (Mon-Fri).
128 +- **GeoIP2 commercial editions**: published "every weekday, Monday through
129 + Friday" per <https://www.maxmind.com/en/geoip2-databases>.
130 +
131 +Source: <https://support.maxmind.com/hc/en-us/articles/4408216129947>.
132 +
133 +
134 +#### Single MMDB per database type (dual-stack)
135 +
136 +A single MaxMind MMDB file covers both IPv4 and IPv6 in one binary tree --
137 +you do **not** need separate IPv4 and IPv6 files. Point `asn_database` at one
138 +file and `geo_database` at one file. (The CSV exports MaxMind also publishes
139 +are split per family, but the netflow plugin only reads the binary MMDB
140 +format.)
141
142
143
@@ -78,8 +145,11 @@ For GeoLite2 (free): create a MaxMind account, generate a license key, install
145
146 #### Options
147
81 -Override the default DB-IP auto-detection by pointing `asn_database` and `geo_database`
82 -at your MaxMind MMDB files.
148 +Override the default DB-IP auto-detection by pointing `asn_database` and
149 +`geo_database` at your MaxMind MMDB files. Both keys take a list -- you can
150 +chain MaxMind alongside other MMDB sources; see the
151 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment)
152 +page for the per-field "last non-empty wins" composition rule.
153
154
155 <details open><summary>Config options</summary>
@@ -88,9 +158,9 @@ at your MaxMind MMDB files.
158
159 | Option | Description | Default | Required |
160 |:-----|:------------|:--------|:---------:|
91 -| enrichment.geoip.asn_database | Paths to MaxMind ASN MMDB files (typically GeoLite2-ASN.mmdb or GeoIP2-ISP.mmdb). | [] | yes |
92 -| enrichment.geoip.geo_database | Paths to MaxMind geographic MMDB files (typically GeoLite2-City.mmdb or GeoIP2-City.mmdb). | [] | yes |
93 -| enrichment.geoip.optional | When true, missing or unreadable MMDBs are warnings, not fatal at startup. | false | no |
161 +| enrichment.geoip.asn_database | Paths to MaxMind ASN MMDB files (typically `GeoLite2-ASN.mmdb` for the free tier or `GeoIP2-ISP.mmdb` for the paid tier; the dual-stack file covers IPv4 and IPv6). | [] | yes |
162 +| enrichment.geoip.geo_database | Paths to MaxMind geographic MMDB files (typically `GeoLite2-City.mmdb` for the free tier or `GeoIP2-City.mmdb` for the paid tier; the dual-stack file covers IPv4 and IPv6). | [] | yes |
163 +| enrichment.geoip.optional | When true, missing or unreadable MMDBs are warnings, not fatal at startup. Useful while `geoipupdate` is being set up for the first time. | false | no |
164
165
166 </details>
@@ -114,7 +184,7 @@ sudo ./edit-config netflow.yaml
184
185 ###### GeoLite2 (free tier)
186
117 -Standard `geoipupdate` install path. Free tier requires a license key.
187 +Standard `geoipupdate` install path. Free tier requires a MaxMind account and license key; usage is subject to the GeoLite2 EULA (attribution required).
188
189 ```yaml
190 enrichment:
@@ -128,7 +198,7 @@ enrichment:
198 ```
199 ###### GeoIP2 (paid)
200
131 -Commercial subscription. Higher accuracy, more frequent updates.
201 +Commercial subscription. Higher accuracy than GeoLite2 and a weekday update cadence on most editions.
202
203 <details open><summary>Config</summary>
204
@@ -144,20 +214,59 @@ enrichment:
214 ```
215 </details>
216
217 +###### MaxMind ASN + DB-IP geo (mixed)
218 +
219 +Use MaxMind for ASN richness and keep the bundled DB-IP geo file. Both lookups run; the multi-DB composition rule is documented on the [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment) page.
220 +
221 +<details open><summary>Config</summary>
222 +
223 +```yaml
224 +enrichment:
225 + geoip:
226 + asn_database:
227 + - /usr/share/GeoIP/GeoLite2-ASN.mmdb
228 + geo_database:
229 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
230 + optional: false
231 +
232 +```
233 +</details>
234 +
235
236
237 ### License key missing or expired
238
151 -`geoipupdate` fails silently and the MMDB files become stale. Set up a working
152 -`geoipupdate` cron and monitor its exit code.
239 +`geoipupdate` fails (often silently from the plugin's perspective) and the
240 +MMDB files become stale. Check the `geoipupdate` exit status and its log;
241 +regenerate the key under "Account > Manage License Keys" if needed and
242 +update `/etc/GeoIP.conf`.
243 +
244 +
245 +### GeoLite2 attribution requirement
246 +
247 +The GeoLite2 EULA requires attribution to MaxMind. If you redistribute
248 +dashboards or screenshots that include GeoLite2-derived data, follow the
249 +upstream guidance at <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>.
250 +GeoIP2 (commercial) does not have the same attribution requirement.
251
252
253 ### Schema differences between GeoLite2 and GeoIP2
254
157 -Both share the standard MMDB structure but the `Anonymous IP`, `ISP`, and
158 -`Connection Type` databases have GeoIP2-only fields the plugin does not read.
159 -Use `City` for geographic enrichment and `ASN` (GeoLite2) or `ISP` (GeoIP2)
160 -for AS data.
255 +Both share the standard MMDB structure for the fields the plugin reads
256 +(country, subdivisions, city, location, autonomous_system_number,
257 +autonomous_system_organization). MaxMind's specialty databases (Anonymous IP,
258 +Connection Type, Domain) carry GeoIP2-only fields the plugin does not consume.
259 +Use `City` for geographic enrichment and `ASN` (GeoLite2) or `ISP` (GeoIP2) for
260 +AS data.
261 +
262 +
263 +### Mixing MaxMind with the bundled DB-IP files
264 +
265 +The plugin loads every configured MMDB. If you list both, the per-field
266 +"last non-empty wins" rule applies (see the
267 +[IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment)
268 +concept page). Order matters: list the source whose values you want to win
269 +**last**.
270
271
272
src/crates/netflow-plugin/integrations/netbox.md
+199 -53
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "NetBox"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Network Identity Sources"
7 -keywords: ['netbox', 'ipam', 'dcim', 'source of truth', 'prefix list']
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['netbox', 'ipam', 'dcim', 'source of truth', 'prefix list', 'network identity']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # NetBox
14
15
@@ -21,24 +23,46 @@ Module: netbox
23
24 ## Overview
25
24 -[NetBox](https://netboxlabs.com/oss/netbox/) is the most widely deployed open-source
25 -IPAM / DCIM. Many networks already curate prefix metadata there -- tenant, site,
26 -role, VRF -- and want flow data to inherit those labels automatically rather than
27 -duplicating them in `netflow.yaml`.
28 -
29 -This integration polls NetBox's Prefixes API at a configurable interval, transforms
30 -the response with jq, and labels flow records with the prefix metadata.
31 -
32 -For the full network-identity concept, see
33 -[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
34 -
35 -
36 -Periodic HTTPS GET to a NetBox API endpoint with a Bearer token in the
37 -`Authorization` header. jq transform produces per-prefix objects with the labels
38 -you want -- typically `tenant.name`, `site.name`, `role.name`, `description`.
39 -
40 -NetBox paginates results -- there is **no automatic pagination** in this plugin.
41 -For inventories larger than the default page size (50), wrap NetBox with a
26 +Annotate network flows with tenant, site, role, VRF, and description metadata
27 +from NetBox. [NetBox](https://github.com/netbox-community/netbox) is the most
28 +widely deployed open-source IPAM / DCIM. It is Apache-2.0 licensed and maintained
29 +by the netbox-community organization (commercial variants -- NetBox Cloud and
30 +NetBox Enterprise -- are offered by NetBox Labs on top of the same upstream code).
31 +Operators already curate prefix metadata in NetBox and want flow records to
32 +inherit those labels automatically rather than maintaining a parallel list in
33 +`netflow.yaml`.
34 +
35 +This integration polls NetBox's Prefixes REST API at a configurable interval,
36 +transforms the response with jq, and labels matching flow prefixes with whatever
37 +fields you map. NetBox is the authoritative source -- when the same prefix is
38 +tagged in NetBox and in the static `networks:` block, the static block wins
39 +(operator override is intentional, see the
40 +[Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment)
41 +concept page).
42 +
43 +Use cases: applying your organisation's data-centre rack labels, tenant names,
44 +environment tags (prod / staging / dev), and VRF / site identifiers to flows so
45 +dashboards and queries surface "this is the staging tier in fra1" instead of a
46 +raw CIDR.
47 +
48 +
49 +Periodic HTTPS GET to `/api/ipam/prefixes/` on your NetBox host. Authentication is
50 +a NetBox API token in the `Authorization` header. NetBox supports two token
51 +formats:
52 +
53 +- **Legacy v1 tokens** (`Authorization: Token <token>`) -- accepted by all NetBox
54 + versions, simplest to wire up.
55 +- **v2 tokens** (NetBox 4.x, `Authorization: Bearer nbt_<key>.<token>`) -- the
56 + prefix `nbt_` and the random key are concatenated with the token via a dot.
57 +
58 +The plugin transports either format -- the value is whatever NetBox issued for the
59 +service account.
60 +
61 +The plugin runs the configured `transform` (jaq -- a jq-equivalent) over the
62 +parsed JSON body and produces per-prefix objects. NetBox's response is paginated;
63 +the plugin does **not** follow `next` links. Pass `?limit=0` (NetBox 4.x default
64 +`MAX_PAGE_SIZE` is 1000; setting `0` removes the cap when the server config
65 +allows) or an explicit `?limit=N` greater than your prefix count, or expose a
66 server-side aggregator that returns the full list at one URL.
67
68
@@ -53,15 +77,15 @@ This integration supports multiple instances configured side-by-side.
77
78 #### Auto-Detection
79
56 -Disabled by default. Add an entry under enrichment.network_sources with your NetBox URL and API token.
80 +Disabled by default. Add an entry under `enrichment.network_sources` with your NetBox URL and API token.
81
82 #### Limits
83
60 -The default configuration for this integration does not impose any limits.
84 +Resource use scales with the number of NetBox prefixes returned by the URL and transform. Use NetBox filtering when you only need a subset of VRFs, sites, tenants, or roles.
85
86 #### Performance Impact
87
64 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
88 +One HTTP request per refresh interval plus a jq transform over the response. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records.
89
90 ## Setup
91
@@ -70,15 +94,35 @@ The default configuration for this integration is not expected to impose a signi
94
95 #### NetBox API token with read scope on Prefixes
96
73 -In NetBox, create or use a service account, generate an API token, scope it
74 -read-only to the Prefixes endpoint. The token goes in the `Authorization` header.
97 +In NetBox, create or reuse a service-account user, then generate an API token
98 +under "Admin > Users > Tokens". Restrict the token to read-only and (for
99 +v4.x) limit the scope to `ipam.view_prefix`. The token value goes in the
100 +`Authorization` header.
101 +
102 +The plugin only reads -- never writes -- so a read-only token is sufficient
103 +and recommended. Token format depends on the NetBox version:
104 +
105 +- NetBox 3.x or earlier: `Token <40-char-hex>` (legacy).
106 +- NetBox 4.x: either legacy `Token <hex>` or new `Bearer nbt_<key>.<token>`
107 + (v2 tokens, opt-in).
108
109
77 -#### A bulk endpoint or aggregator
110 +#### Bulk endpoint (`?limit=0` or aggregator)
111
79 -The plugin does not paginate. If your NetBox has more prefixes than fit in the
80 -default page (`?limit=50`), either raise `limit` (`?limit=10000`) or expose
81 -an internal endpoint that aggregates all pages and serves them at one URL.
112 +The plugin fetches a single page. NetBox's default `PAGINATE_COUNT` is 50
113 +and `MAX_PAGE_SIZE` is 1000. For inventories above 1000 prefixes, either
114 +raise `MAX_PAGE_SIZE` server-side and pass `?limit=0`, or expose an internal
115 +aggregator endpoint that walks pagination and returns the full list at one URL.
116 +
117 +
118 +#### NetBox version-aware field mapping
119 +
120 +NetBox **4.2** replaced the per-prefix `site` foreign key with a generic
121 +`scope` field (a prefix can now be scoped to a region, site group, site, or
122 +location -- not just a site). API responses on 4.2+ omit `site` and expose
123 +`scope` (read-only) plus `scope_type` / `scope_id`. Adjust your jq accordingly
124 +-- on 4.2+ use `(.scope.name // "")`, on 4.1 and earlier use
125 +`(.site.name // "")`.
126
127
128
@@ -87,6 +131,10 @@ an internal endpoint that aggregates all pages and serves them at one URL.
131 #### Options
132
133 Add a named entry under `enrichment.network_sources` pointing at your NetBox.
134 +The network source configuration accepts `url`, `method`,
135 +`headers`, `proxy`, `tls`, `timeout`, `interval`, and `transform`. Authentication
136 +is configured via the generic `headers:` map -- there is no NetBox-specific
137 +auth helper.
138
139
140 <details open><summary>Config options</summary>
@@ -95,10 +143,12 @@ Add a named entry under `enrichment.network_sources` pointing at your NetBox.
143
144 | Option | Description | Default | Required |
145 |:-----|:------------|:--------|:---------:|
98 -| url | NetBox Prefixes API endpoint, with `?limit=` if needed. | | yes |
99 -| headers.Authorization | NetBox API token, prefixed with "Token ". | | yes |
100 -| interval | How often to refresh. NetBox is your source of truth -- 5-15 minutes is typical. | 60s | no |
101 -| transform | jq expression mapping `.results[]` (NetBox's response shape) to per-prefix objects. | . | yes |
146 +| url | NetBox prefixes API endpoint, including `?limit=` (recommend `?limit=0` on 4.x for full inventory in one shot). | | yes |
147 +| headers.Authorization | NetBox API token. Use `Token <hex>` for legacy v1 or `Bearer nbt_<key>.<token>` for v4.x v2 tokens. | | yes |
148 +| interval | How often to refresh. NetBox is your source of truth; 5 minutes is typical for IPAMs that change frequently, 1 hour is fine for static inventories. | 60s | no |
149 +| timeout | HTTP request timeout. Bump to 30-60s if your NetBox returns thousands of prefixes in one shot. | 10s | no |
150 +| transform | jq expression mapping NetBox's `.results[]` to per-prefix objects with `prefix` and any of `name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`, `asn`, `asn_name`. | . | yes |
151 +| tls.ca_file | Path to your internal CA bundle when NetBox runs behind internal PKI. Disabling verification is rejected by the validator -- use this instead. | | no |
152
153
154 </details>
@@ -120,32 +170,38 @@ sudo ./edit-config netflow.yaml
170
171 ##### Examples
172
123 -###### NetBox with API token and standard label set
173 +###### NetBox 4.x with v2 token, scope-aware mapping
174 +
175 +Standard NetBox 4.2+ wiring. Maps tenant, role, the new `scope` field
176 +(covers site / region / site-group / location), and the human-readable
177 +description. `?limit=0` returns all prefixes in one call when
178 +`MAX_PAGE_SIZE` is unset on the NetBox server.
179
125 -Tags prefixes with tenant, site, role, and the NetBox description.
180
181 ```yaml
182 enrichment:
183 network_sources:
184 netbox:
131 - url: "https://netbox.example.internal/api/ipam/prefixes/?limit=10000"
185 + url: "https://netbox.example.internal/api/ipam/prefixes/?limit=0"
186 headers:
133 - Authorization: "Token abcdef0123456789"
187 + Authorization: "Bearer nbt_AbCdEf0123456789.GhIjKl0123456789"
188 interval: 5m
189 timeout: 30s
190 transform: |
191 .results[] | {
192 prefix: .prefix,
193 tenant: (.tenant.name // ""),
140 - site: (.site.name // ""),
141 - role: (.role.name // ""),
142 - name: .description
194 + role: (.role.name // ""),
195 + site: (.scope.name // ""),
196 + name: (.description // "")
197 }
198
199 ```
146 -###### NetBox with mTLS to internal CA
200 +###### NetBox 3.x / 4.0 / 4.1 with legacy site field
201 +
202 +Pre-4.2 NetBox still exposes `site` directly on the Prefix object. Use a
203 +legacy `Token` header and read `.site.name` instead of `.scope.name`.
204
148 -When NetBox is behind your internal PKI; use tls.ca_file plus client cert.
205
206 <details open><summary>Config</summary>
207
@@ -155,7 +211,36 @@ enrichment:
211 netbox:
212 url: "https://netbox.example.internal/api/ipam/prefixes/?limit=10000"
213 headers:
158 - Authorization: "Token abcdef0123456789"
214 + Authorization: "Token abcdef0123456789abcdef0123456789abcdef01"
215 + interval: 15m
216 + transform: |
217 + .results[] | {
218 + prefix: .prefix,
219 + tenant: (.tenant.name // ""),
220 + role: (.role.name // ""),
221 + site: (.site.name // ""),
222 + name: (.description // "")
223 + }
224 +
225 +```
226 +</details>
227 +
228 +###### NetBox behind internal PKI (mTLS)
229 +
230 +When NetBox is fronted by internal PKI. `tls.verify: false` is rejected at
231 +config-load -- supply your CA explicitly and (optionally) a client
232 +certificate.
233 +
234 +
235 +<details open><summary>Config</summary>
236 +
237 +```yaml
238 +enrichment:
239 + network_sources:
240 + netbox:
241 + url: "https://netbox.example.internal/api/ipam/prefixes/?limit=0"
242 + headers:
243 + Authorization: "Token abcdef0123456789abcdef0123456789abcdef01"
244 interval: 5m
245 tls:
246 enable: true
@@ -166,9 +251,39 @@ enrichment:
251 .results[] | {
252 prefix: .prefix,
253 tenant: (.tenant.name // ""),
169 - site: (.site.name // ""),
170 - role: (.role.name // ""),
171 - name: .description
254 + role: (.role.name // ""),
255 + site: (.scope.name // ""),
256 + name: (.description // "")
257 + }
258 +
259 +```
260 +</details>
261 +
262 +###### VRF-aware mapping (multi-tenant networks)
263 +
264 +When the same RFC1918 prefix appears in multiple VRFs, fold the VRF name
265 +into the friendly `name` so dashboards disambiguate. Note: the plugin keys
266 +on `prefix` only -- if two rows share a CIDR, the last one wins. Filter the
267 +NetBox query (`?vrf_id=N`) to scope the export.
268 +
269 +
270 +<details open><summary>Config</summary>
271 +
272 +```yaml
273 +enrichment:
274 + network_sources:
275 + netbox-prod-vrf:
276 + url: "https://netbox.example.internal/api/ipam/prefixes/?limit=0&vrf_id=12"
277 + headers:
278 + Authorization: "Token abcdef0123456789abcdef0123456789abcdef01"
279 + interval: 5m
280 + transform: |
281 + .results[] | {
282 + prefix: .prefix,
283 + tenant: (.tenant.name // ""),
284 + role: (.role.name // ""),
285 + site: (.scope.name // ""),
286 + name: ((.vrf.name // "default") + ": " + (.description // ""))
287 }
288
289 ```
@@ -176,18 +291,49 @@ enrichment:
291
292
293
179 -### Only first page of results loaded
294 +### Only first 50 prefixes loaded
295
181 -NetBox paginates by default at 50 results. The plugin does not follow `next`
182 -links. Use `?limit=10000` (or the actual count) on the URL, or expose an
183 -aggregating endpoint server-side.
296 +NetBox's default `PAGINATE_COUNT` is 50 and the plugin does not follow `next`
297 +links. Pass `?limit=0` (NetBox 4.x removes the cap when `MAX_PAGE_SIZE` is
298 +`0` server-side) or `?limit=N` larger than your inventory. For inventories
299 +above the server's `MAX_PAGE_SIZE` (default 1000), expose a server-side
300 +aggregator endpoint.
301
302
186 -### Token missing or wrong scope
303 +### 401 / 403 from NetBox
304
188 -NetBox returns 403 silently consumed by the plugin's HTTP error path. Watch
189 -the journal for `network-sources` warnings; verify with curl:
305 +Token missing, expired, or wrong format. Verify with:
306 `curl -H "Authorization: Token <tok>" https://netbox/api/ipam/prefixes/`.
307 +On NetBox 4.x check whether the token is v1 (`Token <hex>`) or v2
308 +(`Bearer nbt_<key>.<token>`) and use the matching header. Watch the journal
309 +for `network-sources` warnings -- HTTP errors are logged there as
310 +refresh-failed warnings.
311 +
312 +
313 +### site is empty after upgrade to NetBox 4.2
314 +
315 +NetBox 4.2 removed `site` from the Prefix model and replaced it with `scope`
316 +(a generic foreign key that can reference region / site group / site /
317 +location). Update the jq from `(.site.name // "")` to `(.scope.name // "")`.
318 +Alternatively, key on `(.scope.name // .site.name // "")` so the same config
319 +works across the upgrade boundary.
320 +
321 +
322 +### VRF collisions (same CIDR in multiple VRFs)
323 +
324 +The plugin's network-attributes trie keys on `prefix` only -- there is no VRF
325 +dimension on the flow side. If NetBox lists `10.0.0.0/24` in three VRFs, only
326 +the last row wins. Filter the export with `?vrf_id=N` per data-plane and run
327 +one named source per VRF, or fold the VRF name into the friendly `name` so
328 +dashboards disambiguate.
329 +
330 +
331 +### Empty result triggers backoff
332 +
333 +An empty `results` array (legitimate state for a freshly-installed NetBox)
334 +is treated as a fetch failure by the cross-cutting source loop and triggers
335 +exponential backoff. Add at least one synthetic prefix (e.g. a `RFC1918`
336 +container) so the response is never empty.
337
338
339
src/crates/netflow-plugin/integrations/netflow.md
+18 -14
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "NetFlow"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Sources"
6 +learn_rel_path: "Network Flows/Flow Protocols"
7 keywords: ['netflow', 'netflow v5', 'netflow v7', 'netflow v9', 'cisco', 'flows', 'network flows', 'flow collector']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # NetFlow
14
15
@@ -21,14 +23,13 @@ Module: netflow
23
24 ## Overview
25
24 -Collects NetFlow v5, v7, and v9 records from one or more exporters (routers, switches, firewalls)
25 -and stores them in tiered journal files. Each record exposes source and destination IP, ports,
26 -protocol, bytes, packets, ToS, TCP flags, and ingress/egress interface indices.
26 +Collect network flow records from NetFlow v5, v7, and v9 exporters such as
27 +routers, switches, and firewalls. Each record exposes source and destination IP,
28 +ports, protocol, bytes, packets, ToS, TCP flags, and ingress/egress interface indices.
29 Enrichment adds GeoIP country/city/ASN, static metadata, and classifier tags.
30
31 For full documentation including vendor configuration examples, sampling caveats, template
30 -handling and verification steps, see [NetFlow](https://learn.netdata.cloud/docs/network-flows/sources/netflow)
31 -and the [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
32 +handling and verification steps, see the [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
33
34
35 The plugin listens on a configurable UDP socket for NetFlow datagrams.
@@ -52,11 +53,11 @@ The plugin starts when enabled in netflow.yaml and listens on the configured UDP
53
54 #### Limits
55
55 -The default configuration for this integration does not impose any limits.
56 +Operational limits are driven by sustained flows/s, cardinality, retention, storage speed, and enrichment. Plan around 25k sustained flows/s per well-provisioned agent for the full raw + rollup pipeline; use distributed agents for larger deployments.
57
58 #### Performance Impact
59
59 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
60 +Disabled until exporters send traffic. Once active, CPU and disk I/O scale with flow rate and cardinality; size retention and storage from observed flows/s.
61
62 ## Setup
63
@@ -88,8 +89,8 @@ The plugin is configured via `netflow.yaml` in the Netdata configuration directo
89 | protocols.v7 | Enable NetFlow v7 decoding. | yes | no |
90 | protocols.v9 | Enable NetFlow v9 decoding. | yes | no |
91 | journal.journal_dir | Directory for journal files (relative to NETDATA_CACHE_DIR). | flows | no |
91 -| journal.size_of_journal_files | Maximum total size of all journal files. | 10GB | no |
92 -| journal.duration_of_journal_files | Maximum age of journal files. | 7d | no |
92 +| journal.tiers.&lt;tier&gt;.size_of_journal_files | Per-tier hard size cap. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for time-only retention. | 10GB | no |
93 +| journal.tiers.&lt;tier&gt;.duration_of_journal_files | Per-tier maximum age. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for size-only retention. | 7d | no |
94
95
96 </details>
@@ -113,7 +114,7 @@ sudo ./edit-config netflow.yaml
114
115 ###### Basic NetFlow v5/v9 collection
116
116 -Listen on the standard NetFlow port for v5 and v9 records.
117 +Listen on Netdata's default flow listener port for v5 and v9 records.
118
119 ```yaml
120 enabled: true
@@ -126,7 +127,7 @@ protocols:
127 ```
128 ###### NetFlow v9 only with extended retention
129
129 -Accept only v9 records and keep 30 days of journal data.
130 +Accept only v9 records, keep one day of raw data, and keep longer rollups.
131
132 <details open><summary>Config</summary>
133
@@ -140,8 +141,11 @@ protocols:
141 v9: true
142 journal:
143 journal_dir: flows
143 - size_of_journal_files: 50GB
144 - duration_of_journal_files: 30d
144 + tiers:
145 + raw: { size_of_journal_files: 50GB, duration_of_journal_files: 24h }
146 + minute_1: { size_of_journal_files: 10GB, duration_of_journal_files: 14d }
147 + minute_5: { size_of_journal_files: 10GB, duration_of_journal_files: 30d }
148 + hour_1: { size_of_journal_files: 10GB, duration_of_journal_files: 365d }
149
150 ```
151 </details>
src/crates/netflow-plugin/integrations/sflow.md
+13 -12
@@ -3,11 +3,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netf
3 meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 sidebar_label: "sFlow"
5 learn_status: "Published"
6 -learn_rel_path: "Network Flows/Sources"
6 +learn_rel_path: "Network Flows/Flow Protocols"
7 keywords: ['sflow', 'sflow v5', 'sampled flows', 'flows', 'network flows', 'flow collector', 'inmon']
8 message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 endmeta-->
10
11 +<!-- markdownlint-disable-file -->
12 +
13 # sFlow
14
15
@@ -21,15 +23,14 @@ Module: sflow
23
24 ## Overview
25
24 -Collects sFlow v5 datagrams from one or more agents and stores them in tiered journal files.
25 -sFlow provides statistically sampled packet headers, interface counters, or extended
26 -gateway data. Each flow record exposes source and destination IP, ports, protocol, bytes,
27 -packets, and sampling rate information.
26 +Collect sampled network flow records from sFlow v5 agents. sFlow provides
27 +statistically sampled packet headers, interface counters, or extended gateway data.
28 +Each flow record exposes source and destination IP, ports, protocol, bytes, packets,
29 +and sampling rate information.
30
31 For full documentation including how sFlow differs fundamentally from NetFlow (packet
32 sampling vs aggregated flows), vendor configuration examples (Arista, Juniper, Aruba CX,
31 -Ruckus, hsflowd), and the limits of sampled data, see
32 -[sFlow](https://learn.netdata.cloud/docs/network-flows/sources/sflow) and the
33 +Ruckus, hsflowd), and the limits of sampled data, see the
34 [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
35
36
@@ -53,11 +54,11 @@ The plugin starts when enabled in netflow.yaml and listens on the configured UDP
54
55 #### Limits
56
56 -The default configuration for this integration does not impose any limits.
57 +Operational limits are driven by sustained samples/s, sampling rate, cardinality, retention, storage speed, and enrichment. Plan capacity from the received sample rate and the expanded byte/packet estimates.
58
59 #### Performance Impact
60
60 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
61 +Disabled until sFlow agents send traffic. Once active, CPU and disk I/O scale with received samples/s and cardinality; size retention and storage from observed flow records/s.
62
63 ## Setup
64
@@ -88,8 +89,8 @@ Enable sFlow via the `protocols.sflow` option.
89 | listener.listen | UDP endpoint for sFlow datagrams. | 0.0.0.0:2055 | no |
90 | protocols.sflow | Enable sFlow decoding. | yes | no |
91 | journal.journal_dir | Directory for journal files (relative to NETDATA_CACHE_DIR). | flows | no |
91 -| journal.size_of_journal_files | Maximum total size of all journal files. | 10GB | no |
92 -| journal.duration_of_journal_files | Maximum age of journal files. | 7d | no |
92 +| journal.tiers.&lt;tier&gt;.size_of_journal_files | Per-tier hard size cap. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for time-only retention. | 10GB | no |
93 +| journal.tiers.&lt;tier&gt;.duration_of_journal_files | Per-tier maximum age. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for size-only retention. | 7d | no |
94
95
96 </details>
@@ -113,7 +114,7 @@ sudo ./edit-config netflow.yaml
114
115 ###### sFlow collection
116
116 -Listen for sFlow v5 datagrams on the standard port.
117 +Listen for sFlow v5 datagrams on Netdata's default flow listener port.
118
119 ```yaml
120 enabled: true
src/crates/netflow-plugin/integrations/static_metadata.md new
+384
@@ -0,0 +1,384 @@
1 +<!--startmeta
2 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/integrations/static_metadata.md"
3 +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/crates/netflow-plugin/metadata.yaml"
4 +sidebar_label: "Static Metadata"
5 +learn_status: "Published"
6 +learn_rel_path: "Network Flows/Enrichment Methods"
7 +keywords: ['static metadata', 'exporters', 'interfaces', 'networks', 'cidr', 'sampling rate override', 'asn override', 'country override', 'tenant', 'site', 'region', 'role']
8 +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE FLOWS' metadata.yaml FILE"
9 +endmeta-->
10 +
11 +<!-- markdownlint-disable-file -->
12 +
13 +# Static Metadata
14 +
15 +
16 +<img src="https://netdata.cloud/img/network-wired.svg" width="150"/>
17 +
18 +
19 +Plugin: netflow-plugin
20 +Module: static-metadata
21 +
22 +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
23 +
24 +## Overview
25 +
26 +Annotate network flows with operator-defined labels for exporters, interfaces,
27 +networks, and sampling rates. Static Metadata is a YAML-defined enrichment source
28 +that lives entirely in `netflow.yaml`. Use it when there is no IPAM, no CMDB, and
29 +no MMDB to query -- you simply know your own networks, your own routers, and what
30 +the interfaces on those routers do, and you want those labels visible on the
31 +Network Flows view.
32 +
33 +Three independent surfaces are configured under this card, each populating a
34 +different set of flow-record fields:
35 +
36 +- `enrichment.metadata_static.exporters.<ip-or-cidr>` -- per-exporter labels
37 + (matched against the source IP of the UDP datagram) and per-interface labels
38 + (matched against the ifIndex from the flow record).
39 +- `enrichment.networks.<cidr>` -- labels for arbitrary CIDR blocks (your own
40 + corp ranges, customer ranges, public blocks you operate). Matched against the
41 + flow's source and destination IPs.
42 +- `enrichment.override_sampling_rate.<cidr>` -- per-prefix sampling-rate
43 + substitution for exporters that do not communicate their rate (e.g. NetFlow
44 + v7, which has no sampling field, or a v9 exporter that never sends a Sampling
45 + Options Template).
46 +
47 +Fields populated:
48 +
49 +- From `metadata_static.exporters` (per-exporter):
50 + `EXPORTER_NAME`, `EXPORTER_GROUP`, `EXPORTER_ROLE`, `EXPORTER_SITE`,
51 + `EXPORTER_REGION`, `EXPORTER_TENANT`.
52 +- From `metadata_static.exporters.if_indexes` (per-interface):
53 + `IN_IF_NAME` / `OUT_IF_NAME`, `IN_IF_DESCRIPTION` / `OUT_IF_DESCRIPTION`,
54 + `IN_IF_SPEED` / `OUT_IF_SPEED` (in **bits per second**), `IN_IF_PROVIDER` /
55 + `OUT_IF_PROVIDER`, `IN_IF_CONNECTIVITY` / `OUT_IF_CONNECTIVITY`,
56 + `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` (`1` = external, `2` = internal,
57 + `0`/omitted = removed from output).
58 +- From `enrichment.networks` (per-CIDR):
59 + `SRC_NET_NAME` / `DST_NET_NAME`, `SRC_NET_ROLE` / `DST_NET_ROLE`,
60 + `SRC_NET_SITE` / `DST_NET_SITE`, `SRC_NET_REGION` / `DST_NET_REGION`,
61 + `SRC_NET_TENANT` / `DST_NET_TENANT`, plus overrides for the GeoIP-derived
62 + `SRC_COUNTRY` / `DST_COUNTRY`, `SRC_GEO_STATE` / `DST_GEO_STATE`,
63 + `SRC_GEO_CITY` / `DST_GEO_CITY`, `SRC_GEO_LATITUDE` / `DST_GEO_LATITUDE`,
64 + `SRC_GEO_LONGITUDE` / `DST_GEO_LONGITUDE`. Setting `asn:` on a `networks`
65 + entry overrides the AS *number* via the same merge path; the AS *name*
66 + still comes from the ASN database (see "Composition with dynamic sources"
67 + below).
68 +- From `override_sampling_rate` (longest-prefix match against the exporter IP):
69 + `SAMPLING_RATE` is set unconditionally when an override prefix matches. This
70 + is different from `default_sampling_rate`, which only applies when the flow
71 + does not already carry a rate.
72 +
73 +For the cross-cutting Enrichment concept (provider chains, merge order rules,
74 +how static composes with dynamic sources, the static-blocks-classifiers
75 +interaction, and shared failure modes), see
76 +[Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
77 +
78 +
79 +Edit `netflow.yaml`, restart the plugin, and the YAML-defined data is loaded
80 +into the same in-memory tries the GeoIP, IPAM, and BGP enrichment paths read
81 +from. There is no network access, no file watching, and no hot reload --
82 +changes take effect on the next plugin startup.
83 +
84 +
85 +This integration is only supported on the following platforms:
86 +
87 +- Linux
88 +
89 +This integration runs as a single instance per Netdata Agent.
90 +
91 +
92 +### Default Behavior
93 +
94 +#### Auto-Detection
95 +
96 +Disabled by default. Add entries under `enrichment.metadata_static`, `enrichment.networks`, and / or `enrichment.override_sampling_rate` to populate it.
97 +
98 +#### Limits
99 +
100 +Resource use scales with the number of exporter, interface, network, and sampling-override entries. Keep CIDR ranges and interface maps specific enough to match the routers you actually export.
101 +
102 +#### Performance Impact
103 +
104 +Static lookups are local map/trie lookups during enrichment. Cost is usually small compared with flow decode and journal writes, but very large maps add memory and lookup work.
105 +
106 +## Setup
107 +
108 +
109 +### Prerequisites
110 +
111 +#### An understanding of your own topology
112 +
113 +You need to know which IP addresses your routers / switches / firewalls
114 +export from, the integer ifIndex values they use for each interface, and
115 +the CIDR ranges that belong to each of your sites / tenants / roles. None
116 +of this is auto-discovered -- this card is the "I will tell you" path.
117 +For sources that auto-discover the same data, see Generic IPAM, NetBox,
118 +and the cloud IP-range cards.
119 +
120 +
121 +#### Persistent ifIndex values
122 +
123 +`if_indexes` keys are the integer ifIndex the router puts in the flow
124 +record. Some platforms reassign ifIndex on line-card reseat or stack
125 +rebuild. After hardware changes, audit the labels -- a stale ifIndex
126 +entry silently no longer applies. The `default` interface block is used
127 +instead, or no labels are written when `skip_missing_interfaces: true` is set.
128 +
129 +
130 +
131 +### Configuration
132 +
133 +#### Options
134 +
135 +All keys live under `enrichment:` in `netflow.yaml`. Unknown keys fail config
136 +load with a parse error.
137 +
138 +
139 +<details open><summary>Config options</summary>
140 +
141 +
142 +
143 +| Option | Description | Default | Required |
144 +|:-----|:------------|:--------|:---------:|
145 +| enrichment.metadata_static.exporters | Map keyed by exporter IP or CIDR. Longest-prefix match wins. A `/32` key and a bare IP are equivalent. Each entry holds exporter-level labels (`name`, `group`, `role`, `site`, `region`, `tenant`), an optional `default` interface block, an `if_indexes` map keyed by integer ifIndex, and `skip_missing_interfaces`. | {} | no |
146 +| enrichment.metadata_static.exporters.&lt;key&gt;.if_indexes | Map keyed by integer ifIndex (the value the router puts in the flow record). Each entry holds `name`, `description`, `speed` (bits per second), `provider`, `connectivity`, and `boundary`. | {} | no |
147 +| enrichment.metadata_static.exporters.&lt;key&gt;.default | Interface block applied to flow records whose ifIndex is not present in `if_indexes`. Same shape as an `if_indexes` entry. Ignored when `skip_missing_interfaces: true` is set. | {} | no |
148 +| enrichment.metadata_static.exporters.&lt;key&gt;.skip_missing_interfaces | When `true`, flow records whose ifIndex is not in `if_indexes` get no interface labels at all (not even from `default`). Useful when you only care about a known set of WAN interfaces and want unknown ones to stay blank instead of inheriting a placeholder. | false | no |
149 +| enrichment.metadata_static.exporters.&lt;key&gt;.if_indexes.&lt;n&gt;.boundary | Interface boundary marker. Accepts the integers `0` (undefined), `1` (external -- faces the outside world: Internet, peer, transit), `2` (internal -- faces your own infrastructure), or the case-insensitive strings `"undefined"`, `"external"`, `"internal"`. Any other value fails config load. Filtering for `IN_IF_BOUNDARY=1` cleanly gives you "traffic that arrived from outside". | 0 | no |
150 +| enrichment.metadata_static.exporters.&lt;key&gt;.if_indexes.&lt;n&gt;.speed | Interface speed in **bits per second**. A 1 Gbps interface is `1000000000`, not `1000` and not `1000000`. Operators thinking in megabits get the value wrong by a factor of 1000 to 1000000. A `0` value means "not set" and removes the field from the output. | 0 | no |
151 +| enrichment.networks | Map keyed by CIDR. Longest-prefix match contributes the most-specific fields; less-specific containing prefixes contribute their non-empty fields too. The same merge rule is used by `network_sources`. Each value is either a string (shorthand for `name:`) or a map with `name`, `role`, `site`, `region`, `country`, `state`, `city`, `latitude`, `longitude`, `tenant`, `asn`. | {} | no |
152 +| enrichment.networks.&lt;cidr&gt;.asn | Forces the AS *number* for traffic in this prefix, overriding whatever the `asn_providers` chain computed. The AS *name* is still resolved from the ASN MMDB -- there is no `asn_name` config field. See the ASN section of [Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment). | 0 | no |
153 +| enrichment.networks.&lt;cidr&gt;.latitude / longitude | Per-CIDR coordinate override. Out-of-range values (latitude not in [-90, 90] or longitude not in [-180, 180]) and non-finite values are silently coerced to empty strings -- the field is dropped, no error. Validate input out of band if your data matters. | null | no |
154 +| enrichment.default_sampling_rate | Global fallback applied only when the flow record does not already carry a sampling rate. Either a single integer or a CIDR-keyed map (longest-prefix match against the exporter IP). | 0 | no |
155 +| enrichment.override_sampling_rate | Per-exporter substitution that **always** wins when its prefix matches the exporter IP, regardless of what the flow record carried. Either a single integer or a CIDR-keyed map. | {} | no |
156 +
157 +
158 +</details>
159 +
160 +
161 +
162 +#### via File
163 +
164 +The configuration file name for this integration is `netflow.yaml`.
165 +
166 +
167 +You can edit the configuration file using the [`edit-config`](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#edit-configuration-files) script from the
168 +Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration/README.md#locate-your-config-directory).
169 +
170 +```bash
171 +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
172 +sudo ./edit-config netflow.yaml
173 +```
174 +
175 +##### Examples
176 +
177 +###### Naming exporters and their interfaces
178 +
179 +One edge router and one spine. The `default` block applies to any
180 +ifIndex not listed under `if_indexes` -- handy when you only care about
181 +a few WAN ports and want everything else to inherit a placeholder.
182 +
183 +
184 +```yaml
185 +enrichment:
186 + metadata_static:
187 + exporters:
188 + 192.0.2.10: # bare IP == /32
189 + name: edge-router-1
190 + site: par1
191 + region: eu-west
192 + role: edge
193 + tenant: tenant-a
194 + default:
195 + description: unclassified port
196 + if_indexes:
197 + 1:
198 + name: Gi0/0/1
199 + description: uplink to ISP-A
200 + speed: 10000000000 # 10 Gbps in bits per second
201 + provider: isp-a
202 + connectivity: transit
203 + boundary: external
204 + 2:
205 + name: Gi0/0/2
206 + description: LAN core
207 + speed: 1000000000
208 + connectivity: lan
209 + boundary: internal
210 + 198.51.100.0/24: # all routers in this subnet
211 + site: dc-fra1
212 + region: eu-central
213 + role: spine
214 + default:
215 + connectivity: lan
216 + boundary: internal
217 +
218 +```
219 +###### Tagging your own networks
220 +
221 +The shorthand form (`203.0.113.0/24: transit-a`) sets only the `name`.
222 +The map form takes any subset of the schema fields. Less-specific
223 +supernets contribute non-empty fields to more-specific subnets via
224 +ascending-prefix-length merge -- declare RFC1918 once at `/8` and
225 +a `/24` inherits everything you did not override.
226 +
227 +
228 +<details open><summary>Config</summary>
229 +
230 +```yaml
231 +enrichment:
232 + networks:
233 + 10.0.0.0/8:
234 + name: corp-internal
235 + role: internal
236 + tenant: tenant-a
237 + 198.51.100.0/24: # a public block you operate
238 + name: customer-acme
239 + role: customer
240 + site: par1
241 + country: FR
242 + city: Paris
243 + latitude: 48.8566
244 + longitude: 2.3522
245 + asn: 64500
246 + 203.0.113.0/24: transit-a # shorthand: name only
247 +
248 +```
249 +</details>
250 +
251 +###### Skipping interfaces you have not labelled
252 +
253 +With `skip_missing_interfaces: true`, an ifIndex that is not in
254 +`if_indexes` produces no interface labels at all -- the `default`
255 +block is ignored. Useful when you want clearly-blank values for
256 +unmodelled interfaces instead of a placeholder description.
257 +
258 +
259 +<details open><summary>Config</summary>
260 +
261 +```yaml
262 +enrichment:
263 + metadata_static:
264 + exporters:
265 + 192.0.2.10:
266 + name: edge-router-1
267 + skip_missing_interfaces: true
268 + if_indexes:
269 + 1:
270 + name: Gi0/0/1
271 + description: uplink to ISP-A
272 + connectivity: transit
273 + boundary: external
274 +
275 +```
276 +</details>
277 +
278 +###### Sampling rate override for old or silent exporters
279 +
280 +NetFlow v7 has no sampling field; some v9 exporters never send a
281 +Sampling Options Template. Without an override, the bytes/packets
282 +stored in the journal are exporter-side **sampled** counters with
283 +no scaling factor. `override_sampling_rate` always wins;
284 +`default_sampling_rate` is the fallback used only when the flow
285 +did not carry a rate.
286 +
287 +
288 +<details open><summary>Config</summary>
289 +
290 +```yaml
291 +enrichment:
292 + default_sampling_rate: 1 # global fallback
293 + override_sampling_rate:
294 + 10.1.0.0/16: 1024 # an old NetFlow v7 stack
295 + 192.0.2.10/32: 4096 # one specific exporter
296 +
297 +```
298 +</details>
299 +
300 +
301 +
302 +### A more-specific entry inherits the wrong field from its supernet
303 +
304 +`enrichment.networks` merges containing prefixes from least-specific to
305 +most-specific. Leaving a field blank on a `/24` does **not** clear the `/16`'s value
306 +for that field -- you must explicitly set the field on the more-specific
307 +entry to overwrite. The same merge rule applies to entries from
308 +`network_sources`, which interleave at the same prefix lengths.
309 +
310 +
311 +### GeoIP returns spurious data for an internal range
312 +
313 +Until you declare your RFC1918 / RFC6598 / link-local ranges as
314 +`enrichment.networks` entries, the GeoIP-derived country / city / coord
315 +fields can pick up junk for those addresses. Adding a `networks` entry
316 +for the range overrides the GeoIP layer at the merge step.
317 +
318 +
319 +### ifIndex changed after a hardware swap
320 +
321 +`if_indexes` keys are the numeric ifIndex sent in the flow record. A
322 +line-card reseat or stack rebuild can renumber the interfaces; the old
323 +ifIndex no longer matches and the per-interface block silently no longer
324 +applies. Audit after hardware changes.
325 +
326 +
327 +### speed shows up wrong by a factor of 1000
328 +
329 +`speed:` is in **bits per second**. `speed: 1000` means 1 kbps, not
330 +1 Mbps. A 1 Gbps interface is `1000000000`. `speed: 0` means "not set"
331 +and removes the field from the output.
332 +
333 +
334 +### An interface block silently no longer applies
335 +
336 +When the configured ifIndex is not present in the flow record, the
337 +`default` block is used instead -- unless `skip_missing_interfaces: true`
338 +is set, in which case no interface labels are written at all. If you expected your block to apply but the labels are blank, the
339 +router is sending a different ifIndex.
340 +
341 +
342 +### Coordinates dropped silently
343 +
344 +Out-of-range latitude / longitude (`latitude: 91.5`) and non-finite
345 +values become empty strings without an error. The map quietly stops drawing the marker. Validate input externally if
346 +the data matters.
347 +
348 +
349 +### Static labels block the classifiers
350 +
351 +When static metadata sets **any** of `group`, `role`, `site`, `region`,
352 +`tenant` for an exporter, the `exporter_classifiers` rule chain does not
353 +run for that exporter at all. The same is true for an interface: any of `provider`, `connectivity`,
354 +`boundary` set by static metadata short-circuits
355 +`interface_classifiers`. If
356 +you want classifiers to run on top of static metadata, drop the static
357 +fields they are supposed to set.
358 +
359 +
360 +### A typo fails config load
361 +
362 +The schema is `deny_unknown_fields` at every level. A typo such as
363 +`if_index` (the canonical key is `if_indexes`; aliases `ifindexes`,
364 +`if-indexes` are accepted) or a misspelt attribute (`teannt:`) fails plugin
365 +start with a YAML parse error rather than being silently ignored.
366 +
367 +
368 +### Sampling override looks ignored
369 +
370 +`override_sampling_rate` always wins when its prefix matches; if the
371 +field still looks unset, check that the **exporter IP** -- not the
372 +flow's source / destination IP -- falls under the configured prefix.
373 +`default_sampling_rate` is only consulted when the flow did not already
374 +carry a rate.
375 +
376 +
377 +### Changes do not take effect
378 +
379 +Static metadata is loaded at plugin startup and there is no file-change
380 +watcher. Restart the plugin (or the agent) after editing
381 +`netflow.yaml`.
382 +
383 +
384 +
src/crates/netflow-plugin/metadata.yaml
+2842 -526
@@ -30,14 +30,13 @@ modules:
30 overview:
31 data_collection:
32 metrics_description: |
33 - Collects NetFlow v5, v7, and v9 records from one or more exporters (routers, switches, firewalls)
34 - and stores them in tiered journal files. Each record exposes source and destination IP, ports,
35 - protocol, bytes, packets, ToS, TCP flags, and ingress/egress interface indices.
33 + Collect network flow records from NetFlow v5, v7, and v9 exporters such as
34 + routers, switches, and firewalls. Each record exposes source and destination IP,
35 + ports, protocol, bytes, packets, ToS, TCP flags, and ingress/egress interface indices.
36 Enrichment adds GeoIP country/city/ASN, static metadata, and classifier tags.
37
38 For full documentation including vendor configuration examples, sampling caveats, template
39 - handling and verification steps, see [NetFlow](https://learn.netdata.cloud/docs/network-flows/sources/netflow)
40 - and the [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
39 + handling and verification steps, see the [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
40 method_description: |
41 The plugin listens on a configurable UDP socket for NetFlow datagrams.
42 NetFlow v5 and v7 records are decoded directly. NetFlow v9 records are decoded using
@@ -54,9 +53,9 @@ modules:
53 auto_detection:
54 description: "The plugin starts when enabled in netflow.yaml and listens on the configured UDP port."
55 limits:
57 - description: ""
56 + description: "Operational limits are driven by sustained flows/s, cardinality, retention, storage speed, and enrichment. Plan around 25k sustained flows/s per well-provisioned agent for the full raw + rollup pipeline; use distributed agents for larger deployments."
57 performance_impact:
59 - description: ""
58 + description: "Disabled until exporters send traffic. Once active, CPU and disk I/O scale with flow rate and cardinality; size retention and storage from observed flows/s."
59 setup:
60 prerequisites:
61 list:
@@ -94,12 +93,12 @@ modules:
93 description: Directory for journal files (relative to NETDATA_CACHE_DIR).
94 default_value: flows
95 required: false
97 - - name: journal.size_of_journal_files
98 - description: Maximum total size of all journal files.
96 + - name: journal.tiers.&lt;tier&gt;.size_of_journal_files
97 + description: Per-tier hard size cap. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for time-only retention.
98 default_value: "10GB"
99 required: false
101 - - name: journal.duration_of_journal_files
102 - description: Maximum age of journal files.
100 + - name: journal.tiers.&lt;tier&gt;.duration_of_journal_files
101 + description: Per-tier maximum age. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for size-only retention.
102 default_value: "7d"
103 required: false
104 examples:
@@ -110,7 +109,7 @@ modules:
109 - name: Basic NetFlow v5/v9 collection
110 folding:
111 enabled: false
113 - description: Listen on the standard NetFlow port for v5 and v9 records.
112 + description: Listen on Netdata's default flow listener port for v5 and v9 records.
113 config: |
114 enabled: true
115 listener:
@@ -121,7 +120,7 @@ modules:
120 - name: NetFlow v9 only with extended retention
121 folding:
122 enabled: true
124 - description: Accept only v9 records and keep 30 days of journal data.
123 + description: Accept only v9 records, keep one day of raw data, and keep longer rollups.
124 config: |
125 enabled: true
126 listener:
@@ -132,8 +131,11 @@ modules:
131 v9: true
132 journal:
133 journal_dir: flows
135 - size_of_journal_files: 50GB
136 - duration_of_journal_files: 30d
134 + tiers:
135 + raw: { size_of_journal_files: 50GB, duration_of_journal_files: 24h }
136 + minute_1: { size_of_journal_files: 10GB, duration_of_journal_files: 14d }
137 + minute_5: { size_of_journal_files: 10GB, duration_of_journal_files: 30d }
138 + hour_1: { size_of_journal_files: 10GB, duration_of_journal_files: 365d }
139 troubleshooting:
140 problems:
141 list:
@@ -151,7 +153,7 @@ modules:
153 enabled: false
154 description: |
155 Network flow data is stored as journal records, not as traditional metrics.
154 - Use the "Network Flows" tab in the Netdata dashboard to query and visualize flow data.
156 + Use the Network Flows view in the Netdata dashboard to query and visualize flow data.
157 For the full list of fields and the per-protocol availability matrix, see the
158 [Field Reference](https://learn.netdata.cloud/docs/network-flows/field-reference).
159 For visualisation guidance see [Sankey and Table](https://learn.netdata.cloud/docs/network-flows/visualization/summary-sankey),
@@ -188,14 +190,13 @@ modules:
190 overview:
191 data_collection:
192 metrics_description: |
191 - Collects IPFIX (NetFlow v10) records from one or more exporters and stores them in tiered
192 - journal files. IPFIX extends NetFlow v9 with variable-length fields, vendor-specific
193 - information elements, and template withdrawal. Each record exposes the same core fields
194 - as NetFlow plus any additional IEs the exporter provides.
193 + Collect network flow records from IPFIX (NetFlow v10) exporters. IPFIX extends
194 + NetFlow v9 with variable-length fields, vendor-specific information elements,
195 + and template withdrawal. Each record exposes the same core fields as NetFlow plus
196 + any additional IEs the exporter provides.
197
198 For full documentation including vendor configuration examples (Cisco, Juniper, Arista,
197 - ASA NSEL), biflow handling, sampling caveats, and verification steps, see
198 - [IPFIX](https://learn.netdata.cloud/docs/network-flows/sources/ipfix) and the
199 + ASA NSEL), biflow handling, sampling caveats, and verification steps, see the
200 [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
201 method_description: |
202 The plugin listens on the same UDP socket as NetFlow. IPFIX messages are identified by
@@ -212,9 +213,9 @@ modules:
213 auto_detection:
214 description: "The plugin starts when enabled in netflow.yaml and listens on the configured UDP port."
215 limits:
215 - description: ""
216 + description: "Operational limits are driven by sustained flows/s, template churn, cardinality, retention, storage speed, and enrichment. Plan around 25k sustained flows/s per well-provisioned agent for the full raw + rollup pipeline; use distributed agents for larger deployments."
217 performance_impact:
217 - description: ""
218 + description: "Disabled until exporters send traffic. Once active, CPU and disk I/O scale with flow rate, template volume, and cardinality; size retention and storage from observed flows/s."
219 setup:
220 prerequisites:
221 list:
@@ -245,12 +246,12 @@ modules:
246 description: Directory for journal files (relative to NETDATA_CACHE_DIR).
247 default_value: flows
248 required: false
248 - - name: journal.size_of_journal_files
249 - description: Maximum total size of all journal files.
249 + - name: journal.tiers.&lt;tier&gt;.size_of_journal_files
250 + description: Per-tier hard size cap. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for time-only retention.
251 default_value: "10GB"
252 required: false
252 - - name: journal.duration_of_journal_files
253 - description: Maximum age of journal files.
253 + - name: journal.tiers.&lt;tier&gt;.duration_of_journal_files
254 + description: Per-tier maximum age. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for size-only retention.
255 default_value: "7d"
256 required: false
257 examples:
@@ -261,7 +262,7 @@ modules:
262 - name: IPFIX collection
263 folding:
264 enabled: false
264 - description: Listen for IPFIX records on the standard port.
265 + description: Listen for IPFIX records on Netdata's default flow listener port.
266 config: |
267 enabled: true
268 listener:
@@ -289,7 +290,7 @@ modules:
290 enabled: false
291 description: |
292 Network flow data is stored as journal records, not as traditional metrics.
292 - Use the "Network Flows" tab in the Netdata dashboard to query and visualize flow data.
293 + Use the Network Flows view in the Netdata dashboard to query and visualize flow data.
294 For the full list of fields and the per-protocol availability matrix, see the
295 [Field Reference](https://learn.netdata.cloud/docs/network-flows/field-reference).
296 IPFIX biflow records are emitted as two separate flow records (forward + reverse).
@@ -325,15 +326,14 @@ modules:
326 overview:
327 data_collection:
328 metrics_description: |
328 - Collects sFlow v5 datagrams from one or more agents and stores them in tiered journal files.
329 - sFlow provides statistically sampled packet headers, interface counters, or extended
330 - gateway data. Each flow record exposes source and destination IP, ports, protocol, bytes,
331 - packets, and sampling rate information.
329 + Collect sampled network flow records from sFlow v5 agents. sFlow provides
330 + statistically sampled packet headers, interface counters, or extended gateway data.
331 + Each flow record exposes source and destination IP, ports, protocol, bytes, packets,
332 + and sampling rate information.
333
334 For full documentation including how sFlow differs fundamentally from NetFlow (packet
335 sampling vs aggregated flows), vendor configuration examples (Arista, Juniper, Aruba CX,
335 - Ruckus, hsflowd), and the limits of sampled data, see
336 - [sFlow](https://learn.netdata.cloud/docs/network-flows/sources/sflow) and the
336 + Ruckus, hsflowd), and the limits of sampled data, see the
337 [Network Flows Overview](https://learn.netdata.cloud/docs/network-flows/).
338 method_description: |
339 The plugin listens on the same UDP socket as NetFlow. sFlow datagrams are identified by
@@ -350,9 +350,9 @@ modules:
350 auto_detection:
351 description: "The plugin starts when enabled in netflow.yaml and listens on the configured UDP port."
352 limits:
353 - description: ""
353 + description: "Operational limits are driven by sustained samples/s, sampling rate, cardinality, retention, storage speed, and enrichment. Plan capacity from the received sample rate and the expanded byte/packet estimates."
354 performance_impact:
355 - description: ""
355 + description: "Disabled until sFlow agents send traffic. Once active, CPU and disk I/O scale with received samples/s and cardinality; size retention and storage from observed flow records/s."
356 setup:
357 prerequisites:
358 list:
@@ -383,12 +383,12 @@ modules:
383 description: Directory for journal files (relative to NETDATA_CACHE_DIR).
384 default_value: flows
385 required: false
386 - - name: journal.size_of_journal_files
387 - description: Maximum total size of all journal files.
386 + - name: journal.tiers.&lt;tier&gt;.size_of_journal_files
387 + description: Per-tier hard size cap. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for time-only retention.
388 default_value: "10GB"
389 required: false
390 - - name: journal.duration_of_journal_files
391 - description: Maximum age of journal files.
390 + - name: journal.tiers.&lt;tier&gt;.duration_of_journal_files
391 + description: Per-tier maximum age. Replace `<tier>` with `raw`, `minute_1`, `minute_5`, or `hour_1`. Set to `null` for size-only retention.
392 default_value: "7d"
393 required: false
394 examples:
@@ -399,7 +399,7 @@ modules:
399 - name: sFlow collection
400 folding:
401 enabled: false
402 - description: Listen for sFlow v5 datagrams on the standard port.
402 + description: Listen for sFlow v5 datagrams on Netdata's default flow listener port.
403 config: |
404 enabled: true
405 listener:
@@ -429,7 +429,7 @@ modules:
429 enabled: false
430 description: |
431 Network flow data is stored as journal records, not as traditional metrics.
432 - Use the "Network Flows" tab in the Netdata dashboard to query and visualize flow data.
432 + Use the Network Flows view in the Netdata dashboard to query and visualize flow data.
433 For the full list of fields and the per-protocol availability matrix, see the
434 [Field Reference](https://learn.netdata.cloud/docs/network-flows/field-reference).
435 sFlow byte counts are statistical estimates (sampled packets multiplied by sampling rate);
@@ -448,7 +448,7 @@ modules:
448 name: DB-IP IP Intelligence
449 link: https://db-ip.com/
450 categories:
451 - - flows.ip-intelligence
451 + - flows.enrichment-methods
452 icon_filename: network-wired.svg
453 keywords:
454 - geoip
@@ -472,24 +472,44 @@ modules:
472 overview:
473 data_collection:
474 metrics_description: |
475 - DB-IP is the **default** IP intelligence source for the Netdata netflow plugin. Its
476 - MMDB-format databases are bundled with native packages (DEB, RPM) under
477 - `/usr/share/netdata/topology-ip-intel/`. Refreshing pulls newer data from
478 - `download.db-ip.com` via the bundled `topology-ip-intel-downloader`.
479 -
480 - Populates `SRC_COUNTRY`, `DST_COUNTRY`, `SRC_GEO_STATE`, `DST_GEO_STATE`,
481 - `SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`,
482 - `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`, plus the AS-number and AS-name fields
483 - when included in the resolution chain.
484 -
485 - For the full IP-intelligence concept (MMDB format, lookup priority, internal-IP
486 - handling, hot reload semantics), see
487 - [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
475 + Enrich network flows with ASN and geographic context from DB-IP Lite MMDB
476 + databases. DB-IP IP Intelligence is the default IP intelligence source for the
477 + Netdata netflow plugin: when neither `enrichment.geoip.asn_database` nor
478 + `enrichment.geoip.geo_database` is configured, the plugin auto-detects the
479 + DB-IP-built MMDB files at startup and uses them. Two MMDB files are involved:
480 + an ASN database (`topology-ip-asn.mmdb`) and a geographic database
481 + (`topology-ip-geo.mmdb`). Native packages (DEB, RPM) ship a stock copy under
482 + `/usr/share/netdata/topology-ip-intel/`; the bundled refresh tool fetches updates
483 + into `/var/cache/netdata/topology-ip-intel/`.
484 +
485 + DB-IP publishes the Lite databases monthly under a Creative Commons Attribution 4.0
486 + International licence (https://db-ip.com/db/lite.php). The MMDB format is the MaxMind
487 + binary database; lookups are in-process with no per-flow network call.
488 +
489 + Populates these flow record fields when a lookup succeeds:
490 +
491 + - From the **geo MMDB**: `SRC_COUNTRY`, `DST_COUNTRY`, `SRC_GEO_STATE`, `DST_GEO_STATE`,
492 + `SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`,
493 + `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`. Country and state survive into all four
494 + journal tiers; city, latitude, and longitude are kept only in the raw tier so the
495 + rollups stay cardinality-bounded.
496 + - From the **ASN MMDB**: `SRC_AS_NAME`, `DST_AS_NAME`. The DB-IP-built ASN database also
497 + tags address ranges as private/reserved (RFC 1918, link-local, RFC 6598, etc.); when
498 + the resolved AS number is `0` and that flag is set, the plugin renders the AS name as
499 + `AS0 Private IP Address Space` instead of `AS0 Unknown ASN`. The AS *number* itself
500 + comes from the wider asn_providers chain, not directly from the MMDB.
501 +
502 + For the cross-provider mechanics (lookup pass ordering, the 30-second hot-reload poll,
503 + the auto-detect path order, multi-database composition, IPv4/IPv6 handling, the failure
504 + modes shared by every MMDB provider), see
505 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
506 method_description: |
489 - Files are read on plugin start and reloaded automatically every 30 seconds when
490 - their mtime or size changes. Lookups happen in-process; there is no per-flow network
491 - call. Auto-detection scans `${NETDATA_CACHE_DIR}/topology-ip-intel/` first, falling
492 - back to the stock copy under `${NETDATA_STOCK_DATA_DIR}/topology-ip-intel/`.
507 + Files are read on plugin start and reloaded automatically when their mtime or size
508 + changes. Auto-detection scans `${NETDATA_CACHE_DIR}/topology-ip-intel/` first
509 + (typically `/var/cache/netdata/topology-ip-intel/`), falling back to the stock copy
510 + under `${NETDATA_STOCK_DATA_DIR}/topology-ip-intel/` (typically
511 + `/usr/share/netdata/topology-ip-intel/`). When databases are auto-detected they are
512 + treated as optional, so a missing or unreadable file does not abort plugin startup.
513 supported_platforms:
514 include:
515 - Linux
@@ -501,22 +521,33 @@ modules:
521 auto_detection:
522 description: "Native packages ship the stock DB-IP MMDB files; the plugin auto-detects them at startup. No configuration required for the default install."
523 limits:
504 - description: ""
524 + description: "Lookup coverage and freshness depend on the DB-IP Lite files installed on disk. Native packages provide a stock copy; schedule the downloader if you need monthly refreshes."
525 performance_impact:
506 - description: ""
526 + description: "Lookups are local MMDB reads with no per-flow network call. Memory use is mostly the mapped database files and the kernel page cache needed to keep active pages hot."
527 setup:
528 prerequisites:
529 list:
530 - title: DB-IP MMDB files
531 description: |
512 - Ships with native packages. For source builds, run the bundled downloader once
513 - to populate `/var/cache/netdata/topology-ip-intel/`:
532 + Native DEB / RPM packages ship the stock DB-IP MMDB files under
533 + `/usr/share/netdata/topology-ip-intel/`. For source builds, or to get a fresher copy
534 + than the one bundled with the package, run `topology-ip-intel-downloader` once when
535 + the binary is available (packaged 32-bit installs do not include it) to populate
536 + `/var/cache/netdata/topology-ip-intel/`:
537
538 ```bash
539 sudo /usr/sbin/topology-ip-intel-downloader
540 ```
541
519 - Subsequent refreshes (e.g., monthly cron) re-fetch from db-ip.com.
542 + See the [Enrichment Intel Downloader](https://learn.netdata.cloud/docs/network-flows/intel-downloader)
543 + page for downloader options and how to schedule periodic refreshes. DB-IP Lite
544 + data is published monthly, so a monthly cron of the downloader is the right
545 + cadence -- more frequent runs will not produce newer data.
546 + - title: Licence acknowledgement
547 + description: |
548 + DB-IP Lite databases are distributed under the Creative Commons Attribution 4.0
549 + International licence (https://db-ip.com/db/lite.php). Attribution is required
550 + when redistributing the data or derivative dashboards.
551 configuration:
552 file:
553 name: netflow.yaml
@@ -570,17 +601,29 @@ modules:
601 troubleshooting:
602 problems:
603 list:
573 - - name: Internal IPs appearing in random countries
604 + - name: Private IPs have empty GeoIP fields
605 description: |
575 - GeoIP databases have no entry for RFC 1918 / private space. The stock DB-IP
576 - build tags private ranges so `*_AS_NAME` renders as "AS0 Private IP Address Space"
577 - with empty country. With third-party MMDBs, results may vary. Declare your
578 - internal CIDRs under `enrichment.networks` to override -- see
579 - [Static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment/static-metadata).
606 + GeoIP databases normally have no country, city, or coordinate entry for RFC 1918 /
607 + private space. The DB-IP-built ASN database tags private ranges so `*_AS_NAME`
608 + renders as `AS0 Private IP Address Space`, while geographic fields stay empty and
609 + private addresses do not appear on maps. Declare your internal CIDRs under
610 + `enrichment.networks` when you want internal labels -- see
611 + [Static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment).
612 - name: Stale databases
613 description: |
582 - The plugin does not alert on staleness. Check file mtime: `ls -la /var/cache/netdata/topology-ip-intel/`.
583 - Schedule a weekly cron of `topology-ip-intel-downloader` to keep data fresh.
614 + The plugin does not alert on staleness. Check file mtime:
615 + `ls -la /var/cache/netdata/topology-ip-intel/`. DB-IP Lite is published monthly,
616 + so a monthly cron of `/usr/sbin/topology-ip-intel-downloader` keeps you on the
617 + upstream cadence when the downloader is installed; running it more often will not
618 + produce fresher data.
619 + - name: Map renders empty over a long time window
620 + description: |
621 + `SRC_GEO_CITY`, `DST_GEO_CITY`, `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`,
622 + `SRC_GEO_LONGITUDE`, and `DST_GEO_LONGITUDE` are stored only in the raw journal
623 + tier; the 1-minute, 5-minute, and 1-hour rollups drop them to keep cardinality
624 + bounded. A query that auto-falls back to a rollup tier therefore renders an empty
625 + city map. Narrow the time range so the query fits the raw tier, or use the
626 + country / state map (those survive into rollups).
627 alerts: []
628 metrics:
629 folding:
@@ -589,10 +632,9 @@ modules:
632 description: |
633 IP intelligence enriches existing flow records; it does not produce metrics of its own.
634 Verify enrichment is working by querying `SRC_COUNTRY` / `DST_COUNTRY` on the
592 - Network Flows tab and confirming non-empty values for public IPs.
635 + Network Flows view and confirming non-empty values for public IPs.
636 availability: []
637 scopes: []
595 -
638 - meta:
639 plugin_name: netflow-plugin
640 module_name: maxmind
@@ -600,7 +642,7 @@ modules:
642 name: MaxMind GeoIP / GeoLite2
643 link: https://www.maxmind.com/
644 categories:
603 - - flows.ip-intelligence
645 + - flows.enrichment-methods
646 icon_filename: network-wired.svg
647 keywords:
648 - maxmind
@@ -610,6 +652,7 @@ modules:
652 - asn
653 - mmdb
654 - ip intelligence
655 + - flow enrichment
656 related_resources:
657 integrations:
658 list:
@@ -624,21 +667,44 @@ modules:
667 overview:
668 data_collection:
669 metrics_description: |
627 - MaxMind GeoIP2 (commercial) and GeoLite2 (free tier with license key) MMDB databases
628 - are read directly by the netflow plugin. The plugin uses any MMDB-format file that
629 - exposes the standard schema -- it is not tied to MaxMind specifically, but MaxMind
630 - is the canonical source and the format originator.
670 + Enrich network flows with ASN and geographic context from MaxMind GeoIP /
671 + GeoLite2 MMDB databases. MaxMind is the originator of the MMDB binary format and
672 + the canonical commercial source for geo and ASN data. The netflow plugin reads
673 + MaxMind MMDB files directly; they are interchangeable with other MMDB providers
674 + (DB-IP, custom builds).
675
632 - Populates the same `SRC_COUNTRY`, `*_GEO_*`, and AS-name fields as DB-IP. Use this
633 - integration when you have a MaxMind license and prefer their data over the bundled
634 - DB-IP defaults.
676 + Two product lines are supported:
677
636 - For the full IP-intelligence concept, see
637 - [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
678 + - **GeoLite2** (free): requires a free MaxMind account and a license key; users
679 + must accept the GeoLite2 EULA, which includes an attribution requirement. See
680 + <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>.
681 + - **GeoIP2** (commercial): paid subscription with the same license-key download
682 + mechanism but higher accuracy and more frequent updates. See
683 + <https://www.maxmind.com/en/geoip2-databases>.
684 +
685 + Each lookup populates the same flow-record fields as DB-IP: `SRC_COUNTRY`,
686 + `DST_COUNTRY`, `SRC_GEO_STATE`, `DST_GEO_STATE`, `SRC_GEO_CITY`, `DST_GEO_CITY`,
687 + `SRC_GEO_LATITUDE`, `DST_GEO_LATITUDE`, `SRC_GEO_LONGITUDE`, `DST_GEO_LONGITUDE`,
688 + `SRC_AS_NAME`, and `DST_AS_NAME`. Country and state survive into all rollup tiers;
689 + city and lat/lon are **raw-tier-only**. Rollup tiers keep only the
690 + bounded-cardinality geography columns.
691 +
692 + For the cross-cutting concept (resolver poll loop, multi-database composition,
693 + ASN provider chain, failure-modes table), see
694 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
695 method_description: |
639 - You download the MaxMind MMDB files yourself (via `geoipupdate` or manual download),
640 - then point the plugin at their paths in `netflow.yaml`. The plugin reloads on file
641 - change every 30 seconds.
696 + You download the MaxMind MMDB files yourself using `geoipupdate` (MaxMind's
697 + official downloader, configured with your account ID + license key) or via direct
698 + HTTPS download with the same credentials. MaxMind requires authentication for all
699 + downloads -- there is no anonymous access. The bundled
700 + `topology-ip-intel-downloader` shipped by Netdata does **not** know how to fetch
701 + MaxMind databases (it supports DB-IP and IPtoASN only); use `geoipupdate` and
702 + schedule it via cron or systemd-timer.
703 +
704 + Once the files are on disk, point `asn_database` and `geo_database` at their
705 + paths in `netflow.yaml`. The plugin reloads on file change automatically -- a
706 + successful `geoipupdate` run causes the plugin to swap readers within ~30s, no
707 + restart needed.
708 supported_platforms:
709 include:
710 - Linux
@@ -648,41 +714,82 @@ modules:
714 description: ""
715 default_behavior:
716 auto_detection:
651 - description: "Not auto-detected. You must configure the database paths explicitly."
717 + description: "Not auto-detected. The plugin auto-detects the bundled DB-IP files; to use MaxMind you must configure the database paths explicitly. Set `enrichment.geoip.asn_database` and `enrichment.geoip.geo_database` to the MaxMind file paths."
718 limits:
653 - description: ""
719 + description: "Lookup coverage, fields, license terms, and refresh cadence depend on the MaxMind database edition you configure. Netdata does not fetch MaxMind databases automatically."
720 performance_impact:
655 - description: ""
721 + description: "Lookups are local MMDB reads with no per-flow network call. Memory use is mostly the mapped database files and the kernel page cache needed to keep active pages hot."
722 setup:
723 prerequisites:
724 list:
659 - - title: MaxMind license + downloaded MMDBs
725 + - title: MaxMind account and license key
726 + description: |
727 + MaxMind does not allow anonymous downloads. Required for both GeoLite2 (free)
728 + and GeoIP2 (paid):
729 +
730 + 1. Sign up at <https://www.maxmind.com/en/geolite2/signup> (GeoLite2) or
731 + purchase a GeoIP2 subscription.
732 + 2. Generate a license key under "Account > Manage License Keys".
733 + 3. Note your account ID (visible on the same account page).
734 + - title: Install and configure geoipupdate
735 + description: |
736 + `geoipupdate` is MaxMind's official downloader. Install it from your distro
737 + (`apt install geoipupdate`, `dnf install geoipupdate`) or download from
738 + <https://github.com/maxmind/geoipupdate/releases>.
739 +
740 + Edit `/etc/GeoIP.conf`:
741 +
742 + ```ini
743 + AccountID YOUR_ACCOUNT_ID
744 + LicenseKey YOUR_LICENSE_KEY
745 + # GeoLite2 (free):
746 + EditionIDs GeoLite2-City GeoLite2-ASN
747 + # ...or for GeoIP2 (paid):
748 + # EditionIDs GeoIP2-City GeoIP2-ISP
749 + DatabaseDirectory /usr/share/GeoIP
750 + ```
751 +
752 + Run `sudo geoipupdate` to fetch initial files, then schedule it via cron or
753 + the bundled `geoipupdate.timer` systemd unit. Recommended cadences match the
754 + upstream publish frequency:
755 +
756 + - **GeoLite2 City / Country**: published twice weekly (Tuesday and Friday).
757 + - **GeoLite2 ASN**: published every weekday (Mon-Fri).
758 + - **GeoIP2 commercial editions**: published "every weekday, Monday through
759 + Friday" per <https://www.maxmind.com/en/geoip2-databases>.
760 +
761 + Source: <https://support.maxmind.com/hc/en-us/articles/4408216129947>.
762 + - title: Single MMDB per database type (dual-stack)
763 description: |
661 - For GeoLite2 (free): create a MaxMind account, generate a license key, install
662 - `geoipupdate`, and configure it to fetch `GeoLite2-City.mmdb` and
663 - `GeoLite2-ASN.mmdb`. For GeoIP2 (paid): obtain a subscription and use the same
664 - `geoipupdate` mechanism with your paid license key.
764 + A single MaxMind MMDB file covers both IPv4 and IPv6 in one binary tree --
765 + you do **not** need separate IPv4 and IPv6 files. Point `asn_database` at one
766 + file and `geo_database` at one file. (The CSV exports MaxMind also publishes
767 + are split per family, but the netflow plugin only reads the binary MMDB
768 + format.)
769 configuration:
770 file:
771 name: netflow.yaml
772 options:
773 description: |
670 - Override the default DB-IP auto-detection by pointing `asn_database` and `geo_database`
671 - at your MaxMind MMDB files.
774 + Override the default DB-IP auto-detection by pointing `asn_database` and
775 + `geo_database` at your MaxMind MMDB files. Both keys take a list -- you can
776 + chain MaxMind alongside other MMDB sources; see the
777 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment)
778 + page for the per-field "last non-empty wins" composition rule.
779 folding:
780 title: Config options
781 enabled: true
782 list:
783 - name: enrichment.geoip.asn_database
677 - description: Paths to MaxMind ASN MMDB files (typically GeoLite2-ASN.mmdb or GeoIP2-ISP.mmdb).
784 + description: Paths to MaxMind ASN MMDB files (typically `GeoLite2-ASN.mmdb` for the free tier or `GeoIP2-ISP.mmdb` for the paid tier; the dual-stack file covers IPv4 and IPv6).
785 default_value: "[]"
786 required: true
787 - name: enrichment.geoip.geo_database
681 - description: Paths to MaxMind geographic MMDB files (typically GeoLite2-City.mmdb or GeoIP2-City.mmdb).
788 + description: Paths to MaxMind geographic MMDB files (typically `GeoLite2-City.mmdb` for the free tier or `GeoIP2-City.mmdb` for the paid tier; the dual-stack file covers IPv4 and IPv6).
789 default_value: "[]"
790 required: true
791 - name: enrichment.geoip.optional
685 - description: When true, missing or unreadable MMDBs are warnings, not fatal at startup.
792 + description: When true, missing or unreadable MMDBs are warnings, not fatal at startup. Useful while `geoipupdate` is being set up for the first time.
793 default_value: "false"
794 required: false
795 examples:
@@ -693,7 +800,7 @@ modules:
800 - name: GeoLite2 (free tier)
801 folding:
802 enabled: false
696 - description: Standard `geoipupdate` install path. Free tier requires a license key.
803 + description: Standard `geoipupdate` install path. Free tier requires a MaxMind account and license key; usage is subject to the GeoLite2 EULA (attribution required).
804 config: |
805 enrichment:
806 geoip:
@@ -705,7 +812,7 @@ modules:
812 - name: GeoIP2 (paid)
813 folding:
814 enabled: true
708 - description: Commercial subscription. Higher accuracy, more frequent updates.
815 + description: Commercial subscription. Higher accuracy than GeoLite2 and a weekday update cadence on most editions.
816 config: |
817 enrichment:
818 geoip:
@@ -714,30 +821,60 @@ modules:
821 geo_database:
822 - /usr/share/GeoIP/GeoIP2-City.mmdb
823 optional: false
824 + - name: MaxMind ASN + DB-IP geo (mixed)
825 + folding:
826 + enabled: true
827 + description: Use MaxMind for ASN richness and keep the bundled DB-IP geo file. Both lookups run; the multi-DB composition rule is documented on the [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment) page.
828 + config: |
829 + enrichment:
830 + geoip:
831 + asn_database:
832 + - /usr/share/GeoIP/GeoLite2-ASN.mmdb
833 + geo_database:
834 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
835 + optional: false
836 troubleshooting:
837 problems:
838 list:
839 - name: License key missing or expired
840 description: |
722 - `geoipupdate` fails silently and the MMDB files become stale. Set up a working
723 - `geoipupdate` cron and monitor its exit code.
841 + `geoipupdate` fails (often silently from the plugin's perspective) and the
842 + MMDB files become stale. Check the `geoipupdate` exit status and its log;
843 + regenerate the key under "Account > Manage License Keys" if needed and
844 + update `/etc/GeoIP.conf`.
845 + - name: GeoLite2 attribution requirement
846 + description: |
847 + The GeoLite2 EULA requires attribution to MaxMind. If you redistribute
848 + dashboards or screenshots that include GeoLite2-derived data, follow the
849 + upstream guidance at <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>.
850 + GeoIP2 (commercial) does not have the same attribution requirement.
851 - name: Schema differences between GeoLite2 and GeoIP2
852 description: |
726 - Both share the standard MMDB structure but the `Anonymous IP`, `ISP`, and
727 - `Connection Type` databases have GeoIP2-only fields the plugin does not read.
728 - Use `City` for geographic enrichment and `ASN` (GeoLite2) or `ISP` (GeoIP2)
729 - for AS data.
853 + Both share the standard MMDB structure for the fields the plugin reads
854 + (country, subdivisions, city, location, autonomous_system_number,
855 + autonomous_system_organization). MaxMind's specialty databases (Anonymous IP,
856 + Connection Type, Domain) carry GeoIP2-only fields the plugin does not consume.
857 + Use `City` for geographic enrichment and `ASN` (GeoLite2) or `ISP` (GeoIP2) for
858 + AS data.
859 + - name: Mixing MaxMind with the bundled DB-IP files
860 + description: |
861 + The plugin loads every configured MMDB. If you list both, the per-field
862 + "last non-empty wins" rule applies (see the
863 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment)
864 + concept page). Order matters: list the source whose values you want to win
865 + **last**.
866 alerts: []
867 metrics:
868 folding:
869 title: Metrics
870 enabled: false
871 description: |
736 - Enriches flow records; produces no metrics of its own. Verify on the Network Flows
737 - tab via `SRC_COUNTRY`, `DST_COUNTRY`, and `*_AS_NAME` columns.
872 + IP intelligence enriches existing flow records; it produces no metrics of its own.
873 + Verify enrichment is working by querying `SRC_COUNTRY` / `DST_COUNTRY` and
874 + `SRC_AS_NAME` / `DST_AS_NAME` on the Network Flows view and confirming non-empty
875 + values for public IPs.
876 availability: []
877 scopes: []
740 -
878 - meta:
879 plugin_name: netflow-plugin
880 module_name: iptoasn
@@ -745,13 +882,15 @@ modules:
882 name: IPtoASN
883 link: https://iptoasn.com/
884 categories:
748 - - flows.ip-intelligence
885 + - flows.enrichment-methods
886 icon_filename: network-wired.svg
887 keywords:
888 - iptoasn
889 - asn
890 - bgp
891 - public asn
892 + - public domain
893 + - pddl
894 - ip intelligence
895 related_resources:
896 integrations:
@@ -767,19 +906,41 @@ modules:
906 overview:
907 data_collection:
908 metrics_description: |
770 - [IPtoASN](https://iptoasn.com/) is a free public IP-to-ASN database derived from
771 - BGP RIB snapshots. Daily updates, no license required. Use it as a free, open
772 - alternative to MaxMind ASN data when license cost or terms matter.
909 + Enrich network flows with public ASN and country context from IPtoASN
910 + BGP-derived datasets. [IPtoASN](https://iptoasn.com/) is a free, public-domain
911 + feed of IP-to-ASN mappings. The upstream publishes its dataset
912 + under the [Public Domain Dedication and License (PDDL)](https://opendatacommons.org/licenses/pddl/)
913 + (see [iptoasn.com](https://iptoasn.com/) -- "Frequently updated, public domain")
914 + and rebuilds it hourly, making it the highest-cadence and lowest-friction public
915 + ASN source available. Use it as a free, open alternative to MaxMind ASN data when
916 + license cost or terms matter.
917
774 - IPtoASN provides ASN data only -- no geographic data. Pair with DB-IP, MaxMind,
775 - or another geo source for country/city enrichment.
918 + IPtoASN's `ip2asn-combined` artifact contains five tab-separated columns:
919 + `range_start`, `range_end`, `AS_number`, `country_code`, and `AS_description`
920 + (see [iptoasn.com](https://iptoasn.com/) for the format reference). So
921 + although IPtoASN is often described as "ASN-only", the feed also carries an
922 + ISO 3166 country code per range and the Netdata downloader exposes both as
923 + ASN + geo MMDBs. Pair with DB-IP or MaxMind when you also need state, city,
924 + or coordinates.
925
777 - For the full IP-intelligence concept, see
778 - [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
926 + For the IP-intelligence mechanism (lookup order, MMDB composition, refresh,
927 + provider chains), see
928 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
929 method_description: |
780 - The bundled `topology-ip-intel-downloader` supports IPtoASN as an ASN provider,
781 - fetching the latest TSV and converting it to MMDB format the plugin can read.
782 - Configure the downloader to use IPtoASN with `--asn iptoasn:combined`.
930 + The Netdata plugin reads MMDB only. IPtoASN ships gzipped TSV
931 + (`https://iptoasn.com/data/ip2asn-combined.tsv.gz`), so the TSV must be
932 + converted to MMDB before the plugin can use it.
933 +
934 + When available, Netdata provides a converter for this exact purpose: the
935 + `topology-ip-intel-downloader` Go tool fetches the upstream TSV, parses it,
936 + and emits MMDB files the plugin auto-detects. This is
937 + a separate operator step (cron); the plugin itself does no fetching or
938 + conversion. Packaged 32-bit installs ship the stock MMDB payload but do not include
939 + the downloader binary. If you prefer not to use the bundled tool, any third-party
940 + IPtoASN-to-MMDB converter that produces a standard
941 + [MaxMind DB](https://maxmind.github.io/MaxMind-DB/) with `iso_code`,
942 + `autonomous_system_number`, and `autonomous_system_organization` fields
943 + will also work with the Custom MMDB integration.
944 supported_platforms:
945 include:
946 - Linux
@@ -789,18 +950,36 @@ modules:
950 description: ""
951 default_behavior:
952 auto_detection:
792 - description: "Not auto-detected as the default ASN source -- the plugin auto-detects DB-IP. To use IPtoASN as ASN, run the downloader explicitly."
953 + description: |
954 + Not auto-detected as the default ASN source -- the plugin auto-detects
955 + DB-IP MMDBs in the cache directory at startup
956 + (see [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment)).
957 + To use IPtoASN, run `topology-ip-intel-downloader` when available to produce IPtoASN-derived
958 + MMDBs (the downloader writes to the same cache paths the plugin scans,
959 + so once present they are picked up automatically on the 30-second
960 + file-signature check).
961 limits:
794 - description: ""
962 + description: "IPtoASN provides AS number and country-level data only. It does not provide city, coordinates, or rich organization names unless you layer another MMDB source."
963 performance_impact:
796 - description: ""
964 + description: "Lookups are local MMDB reads after the downloader converts the TSV feed. Memory use is mostly the mapped database files and the kernel page cache needed to keep active pages hot."
965 setup:
966 prerequisites:
967 list:
968 - title: Run the downloader with IPtoASN as ASN source
969 description: |
802 - IPtoASN is a TSV file. The bundled downloader knows how to fetch and convert
803 - it to MMDB:
970 + When available, the downloader knows how to fetch the upstream TSV and convert
971 + it to MMDB. Packaged 32-bit installs do not include this binary:
972 +
973 + ```bash
974 + sudo /usr/sbin/topology-ip-intel-downloader \
975 + --asn iptoasn:combined \
976 + --geo iptoasn:combined
977 + ```
978 +
979 + `iptoasn:combined` is valid for both `--asn` and `--geo` because the
980 + upstream TSV carries both AS data and country. If you
981 + want richer geographic data (state, city, coordinates), pair IPtoASN
982 + ASN with DB-IP or MaxMind geo:
983
984 ```bash
985 sudo /usr/sbin/topology-ip-intel-downloader \
@@ -808,23 +987,35 @@ modules:
987 --geo dbip:city-lite
988 ```
989
811 - This produces ASN data from IPtoASN and geographic data from DB-IP. Schedule
812 - this in cron (daily for ASN; weekly is enough for geo).
990 + IPtoASN rebuilds hourly upstream, but a daily cron is sufficient for
991 + flow enrichment (ASN ownership rarely changes within a single day).
992 + Do not exceed hourly -- you only get a fresh dataset once an hour
993 + regardless.
994 configuration:
995 file:
996 name: netflow.yaml
997 options:
998 description: |
818 - Once the downloader has produced MMDB files in the cache directory, the plugin
819 - auto-detects them. To pin the path explicitly, set `enrichment.geoip.asn_database`.
999 + Once the downloader has produced IPtoASN-derived MMDB files in the cache
1000 + directory, the plugin auto-detects them. To pin the path explicitly, set
1001 + `enrichment.geoip.asn_database` (and `geo_database` when you also want
1002 + the country data IPtoASN provides).
1003 folding:
1004 title: Config options
1005 enabled: true
1006 list:
1007 - name: enrichment.geoip.asn_database
825 - description: Path to the IPtoASN-derived MMDB. Empty = auto-detect from cache directory.
1008 + description: Path to the IPtoASN-derived ASN MMDB. Empty = auto-detect from cache directory.
1009 + default_value: "[]"
1010 + required: false
1011 + - name: enrichment.geoip.geo_database
1012 + description: Path to the IPtoASN-derived geo MMDB (country only). Empty = auto-detect; or point at a richer DB-IP / MaxMind geo MMDB for state/city/coordinates.
1013 default_value: "[]"
1014 required: false
1015 + - name: enrichment.geoip.optional
1016 + description: When true, missing or unreadable MMDBs are warnings, not fatal at startup.
1017 + default_value: "false (true when auto-detected)"
1018 + required: false
1019 examples:
1020 folding:
1021 title: Config
@@ -833,7 +1024,36 @@ modules:
1024 - name: After running the downloader with IPtoASN
1025 folding:
1026 enabled: false
836 - description: Auto-detection picks up the cache copy.
1027 + description: Auto-detection picks up the cache copy. Both ASN and country come from IPtoASN.
1028 + config: |
1029 + enrichment:
1030 + geoip:
1031 + asn_database: []
1032 + geo_database: []
1033 + optional: true
1034 + - name: Explicit IPtoASN paths (ASN + country only)
1035 + folding:
1036 + enabled: true
1037 + description: |
1038 + Pin the IPtoASN-derived MMDBs explicitly. Country only -- no
1039 + state/city. Use the DB-IP or MaxMind integration cards for richer
1040 + geographic enrichment.
1041 + config: |
1042 + enrichment:
1043 + geoip:
1044 + asn_database:
1045 + - /var/cache/netdata/topology-ip-intel/topology-ip-asn.mmdb
1046 + geo_database:
1047 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
1048 + optional: false
1049 + - name: IPtoASN ASN + DB-IP city-lite geo
1050 + folding:
1051 + enabled: true
1052 + description: |
1053 + Free, fully public-domain ASN combined with DB-IP city-lite for
1054 + state/city/coordinates. Run the downloader as
1055 + `topology-ip-intel-downloader --asn iptoasn:combined --geo dbip:city-lite`
1056 + and let auto-detection handle the rest.
1057 config: |
1058 enrichment:
1059 geoip:
@@ -845,23 +1065,41 @@ modules:
1065 list:
1066 - name: ASN names not appearing
1067 description: |
848 - IPtoASN's data does not always carry a human-readable ASN organization name.
849 - The plugin renders `AS{n}` (without a name) for those records. This is data-source-
850 - level, not a plugin issue. Use MaxMind GeoLite2-ASN if you need richer name data.
1068 + IPtoASN's `AS_description` column is sometimes empty for less-common
1069 + ASNs (the upstream derives names from RIRs and the chain occasionally
1070 + has gaps). When that happens the plugin renders the AS as `AS{n}` with
1071 + no organisation. This is data-source-level, not a plugin issue. If richer AS-name
1072 + coverage matters, layer a MaxMind GeoLite2-ASN MMDB after IPtoASN in
1073 + `asn_database` -- per-field "last database with a non-empty value
1074 + wins" composition (see
1075 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment))
1076 + means MaxMind names override IPtoASN's empty entries.
1077 + - name: Country empty although IPtoASN was selected
1078 + description: |
1079 + IPtoASN's combined TSV publishes country only when it is known. Public
1080 + IPs that the upstream cannot attribute will have an empty
1081 + `*_COUNTRY`. If you need broader country coverage, point
1082 + `geo_database` at a DB-IP or MaxMind country MMDB instead -- the
1083 + plugin reads any combination.
1084 - name: Outdated ASN attribution
1085 description: |
853 - IPtoASN is rebuilt daily from BGP. Cron the downloader at least daily to keep
854 - ASN attribution current with real-world routing changes.
1086 + IPtoASN rebuilds hourly upstream. A daily cron of the downloader is
1087 + the recommended cadence for flow enrichment; weekly is too slow
1088 + because BGP-driven prefix re-assignments will land in the dataset
1089 + within hours but not in your cache until the next download. The
1090 + plugin reloads MMDB files in place every 30 seconds when the file
1091 + signature changes, so a fresh file lands without restart.
1092 alerts: []
1093 metrics:
1094 folding:
1095 title: Metrics
1096 enabled: false
1097 description: |
861 - Enriches flow records with AS numbers; provides no metrics of its own.
1098 + Enriches flow records with AS numbers, AS names, and country codes; produces
1099 + no metrics of its own. Verify on the Network Flows view via `*_AS_NAME` and
1100 + `*_COUNTRY` columns.
1101 availability: []
1102 scopes: []
864 -
1103 - meta:
1104 plugin_name: netflow-plugin
1105 module_name: custom-mmdb
@@ -869,13 +1107,15 @@ modules:
1107 name: Custom MMDB Database
1108 link: https://maxmind.github.io/MaxMind-DB/
1109 categories:
872 - - flows.ip-intelligence
1110 + - flows.enrichment-methods
1111 icon_filename: network-wired.svg
1112 keywords:
1113 - mmdb
1114 - custom database
1115 - bring your own
878 - - ipinfo
1116 + - mmdbwriter
1117 + - internal asn
1118 + - internal ipam
1119 - ip intelligence
1120 related_resources:
1121 integrations:
@@ -891,20 +1131,34 @@ modules:
1131 overview:
1132 data_collection:
1133 metrics_description: |
894 - The plugin reads any MMDB file that conforms to the standard schema -- this catch-all
895 - integration covers IPInfo, custom-built internal MMDBs, vendor-specific feeds, or
896 - any provider that publishes MMDB data.
1134 + Enrich network flows with ASN, geographic, or internal network context from
1135 + operator-built MMDB databases. Use Custom MMDB Database when you want the netflow
1136 + plugin to read a MaxMind-format binary database (`.mmdb`) that your
1137 + own tooling produces -- typical use cases are joining the upstream DB-IP /
1138 + GeoLite2 data with internal AS labels, or producing a CIDR-tagged custom file
1139 + directly from an internal IPAM. The plugin only requires the file to conform to
1140 + the [MaxMind DB binary format](https://maxmind.github.io/MaxMind-DB/); whatever
1141 + fields it exposes are picked up by the same decoder path used for DB-IP and
1142 + MaxMind.
1143 +
1144 + The decoder reads `autonomous_system_number`, `autonomous_system_organization`,
1145 + and (optionally) a string `asn` field from the ASN database, and `country.iso_code`,
1146 + `subdivisions[].iso_code`, `city.names.en`, `location.latitude`,
1147 + `location.longitude`, plus the Netdata-specific `netdata.ip_class` flag from the
1148 + geo database. Any other vendor-specific keys are ignored.
1149
898 - The plugin reads `country.iso_code`, `city.names.en`, `subdivisions[].iso_code`,
899 - `location.latitude`, `location.longitude`, `autonomous_system_number`, and
900 - `autonomous_system_organization`. Vendor-specific extra fields are ignored.
1150 + Note that `*_GEO_CITY`, `*_GEO_LATITUDE`, and `*_GEO_LONGITUDE` are written to
1151 + the raw journal tier only -- the rollup tiers drop these high-cardinality fields.
1152 + State and country survive into all four tiers.
1153
902 - For the full IP-intelligence concept, see
903 - [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment/ip-intelligence).
1154 + For the full IP-intelligence concept (composition rule, ASN provider chain,
1155 + auto-detect path order, dual-stack handling, hot reload semantics), see
1156 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
1157 method_description: |
905 - You produce or download an MMDB file. Place it on the agent host. Point the
906 - plugin at it via `netflow.yaml`. The plugin reloads on file change every 30
907 - seconds.
1158 + You build (or download) the MMDB file with your own tooling, place it on the
1159 + agent host, and point `enrichment.geoip.asn_database` and / or
1160 + `enrichment.geoip.geo_database` at it in `netflow.yaml`. The plugin reloads on
1161 + file change automatically (mechanism documented on the IP Intelligence page).
1162 supported_platforms:
1163 include:
1164 - Linux
@@ -916,40 +1170,60 @@ modules:
1170 auto_detection:
1171 description: "Not auto-detected. You must configure paths explicitly."
1172 limits:
919 - description: ""
1173 + description: "Coverage, schema quality, and freshness are entirely controlled by the custom MMDB build you provide. Invalid or missing files fail startup unless marked optional."
1174 performance_impact:
921 - description: ""
1175 + description: "Lookups are local MMDB reads with no per-flow network call. Memory use depends on the size and number of custom databases you configure."
1176 setup:
1177 prerequisites:
1178 list:
925 - - title: A standards-compliant MMDB file
1179 + - title: Build or obtain a standards-compliant MMDB file
1180 description: |
927 - The MMDB file must use the [standard MMDB schema](https://maxmind.github.io/MaxMind-DB/).
928 - Validate with `mmdblookup` from the `libmaxminddb-tools` package before deploying.
1181 + The file must conform to the
1182 + [MaxMind DB binary format spec](https://maxmind.github.io/MaxMind-DB/) --
1183 + a binary search tree over IP prefixes plus a data section of
1184 + type-length-value records. Validate with `mmdblookup` from the
1185 + `libmaxminddb-tools` package before deploying:
1186
930 - Common sources: IPInfo (`ipinfo.io`), custom internal builds via the `mmdbwriter`
931 - Go tool, or vendor-specific feeds.
1187 + ```bash
1188 + mmdblookup --file your-custom.mmdb --ip 8.8.8.8
1189 + ```
1190 +
1191 + Common ways to produce a file:
1192 +
1193 + - **Go**: [`github.com/maxmind/mmdbwriter`](https://github.com/maxmind/mmdbwriter)
1194 + -- MaxMind's official Go writer. Lets you start from an upstream MMDB
1195 + (DB-IP, GeoLite2) and overlay extra records, then write a new file.
1196 + Pairs well with the read-side library
1197 + [`github.com/oschwald/maxminddb-golang`](https://github.com/oschwald/maxminddb-golang).
1198 + - **Perl**: [`MaxMind::DB::Writer`](https://metacpan.org/pod/MaxMind::DB::Writer)
1199 + -- the original reference writer.
1200 + - **Python**: community ports such as `mmdb_writer` (PyPI) wrap the same
1201 + format.
1202 +
1203 + Whatever fields you encode are read by the plugin only if they match the
1204 + standard schema names listed in the metrics description above.
1205 configuration:
1206 file:
1207 name: netflow.yaml
1208 options:
1209 description: |
937 - Point `enrichment.geoip.asn_database` and/or `enrichment.geoip.geo_database` at
938 - your MMDB file paths.
1210 + Point `enrichment.geoip.asn_database` and / or `enrichment.geoip.geo_database`
1211 + at your custom MMDB file paths. Set `optional: true` while you iterate so a
1212 + missing or malformed file does not block plugin startup.
1213 folding:
1214 title: Config options
1215 enabled: true
1216 list:
1217 - name: enrichment.geoip.asn_database
944 - description: List of MMDB paths providing AS data. Multiple files allowed; later entries override on overlap.
1218 + description: List of MMDB paths providing AS data. Multiple files compose -- per field, the last database returning a non-empty value wins.
1219 default_value: "[]"
1220 required: false
1221 - name: enrichment.geoip.geo_database
948 - description: List of MMDB paths providing geographic data.
1222 + description: List of MMDB paths providing geographic data. Same composition rule as the ASN list.
1223 default_value: "[]"
1224 required: false
1225 - name: enrichment.geoip.optional
952 - description: When true, missing files become startup warnings instead of fatal errors.
1226 + description: When true, missing or unreadable files at startup are tolerated (the resolver starts with no databases). Recommended while testing a custom file.
1227 default_value: "false"
1228 required: false
1229 examples:
@@ -957,42 +1231,69 @@ modules:
1231 title: Config
1232 enabled: true
1233 list:
960 - - name: IPInfo MMDB
1234 + - name: Internal MMDB built with mmdbwriter
1235 folding:
1236 enabled: false
963 - description: Using IPInfo's MMDB feed (subscription required).
1237 + description: A custom build that combines public BGP data with internal CIDR labels, written atomically into the plugin's lookup directory by the operator's own pipeline.
1238 config: |
1239 enrichment:
1240 geoip:
1241 asn_database:
968 - - /opt/mmdb/ipinfo-asn.mmdb
1242 + - /etc/netdata/internal-asn.mmdb
1243 geo_database:
970 - - /opt/mmdb/ipinfo-city.mmdb
1244 + - /etc/netdata/internal-geo.mmdb
1245 optional: false
972 - - name: Internal custom MMDB
1246 + - name: Overlay a custom ASN file on top of the bundled DB-IP geo
1247 folding:
1248 enabled: true
975 - description: Built in-house with `mmdbwriter`. Combines public BGP data with internal CIDR labels.
1249 + description: Keep DB-IP for geographic data and use a custom MMDB only for AS labels (your IPAM-derived prefixes win because they appear last in the list -- the resolver merges per field, last non-empty wins).
1250 config: |
1251 enrichment:
1252 geoip:
1253 asn_database:
1254 + - /var/cache/netdata/topology-ip-intel/topology-ip-asn.mmdb
1255 - /etc/netdata/internal-asn.mmdb
1256 geo_database:
982 - - /etc/netdata/internal-geo.mmdb
983 - optional: false
1257 + - /var/cache/netdata/topology-ip-intel/topology-ip-geo.mmdb
1258 + optional: true
1259 + - name: Iterating on a new custom MMDB
1260 + folding:
1261 + enabled: true
1262 + description: While you tune the build pipeline, mark the database optional so an absent or malformed file becomes a startup warning instead of a fatal error.
1263 + config: |
1264 + enrichment:
1265 + geoip:
1266 + asn_database:
1267 + - /etc/netdata/wip-asn.mmdb
1268 + geo_database: []
1269 + optional: true
1270 troubleshooting:
1271 problems:
1272 list:
1273 - name: Lookups silently return empty
1274 description: |
989 - The MMDB schema is non-standard or the IP types don't match (some custom builds
990 - use `string` instead of `array` for ASN). Validate with `mmdblookup -f file.mmdb -i 8.8.8.8`
991 - and confirm the standard fields are present.
1275 + The custom file's schema is non-standard (for example, the ASN is encoded
1276 + as a string instead of `autonomous_system_number: uint32`, or the country
1277 + is at a non-standard path). The plugin only reads the standard MaxMind
1278 + field names listed in the data-collection description. Validate with
1279 + `mmdblookup --file your-custom.mmdb --ip <known-public-ip>` and confirm the
1280 + standard fields are present at the expected paths.
1281 - name: Plugin fails to start with optional=false
1282 description: |
994 - File missing or unreadable at the configured path. Check permissions; the netdata
995 - user must be able to read the file.
1283 + File missing, unreadable, or not a valid MMDB at the configured path.
1284 + Check permissions (the netdata user must be able to read the file) and
1285 + re-validate with `mmdblookup`. Set `optional: true` while iterating.
1286 + - name: Custom file refresh did not take effect
1287 + description: |
1288 + The plugin reloads custom databases the same way it reloads stock ones
1289 + (signature-driven, in place). If the file was rewritten in place the
1290 + plugin will pick it up; if it was edited byte-by-byte (rare with MMDB
1291 + builders) verify size or mtime actually changed. The reload mechanism
1292 + and its 30-second cadence are documented in
1293 + [IP Intelligence](https://learn.netdata.cloud/docs/network-flows/enrichment).
1294 + Operational practice: have your build pipeline write the new MMDB to a
1295 + temp file in the same directory and `rename(2)` it over the live path so
1296 + the swap is atomic.
1297 alerts: []
1298 metrics:
1299 folding:
@@ -1002,11 +1303,6 @@ modules:
1303 Enriches flow records; produces no metrics of its own.
1304 availability: []
1305 scopes: []
1005 -
1006 - # =====================================================================
1007 - # BGP Routing: live route feeds (BMP, BioRIS) used to enrich flow records
1008 - # =====================================================================
1009 -
1306 - meta:
1307 plugin_name: netflow-plugin
1308 module_name: bmp
@@ -1014,7 +1310,7 @@ modules:
1310 name: BMP (BGP Monitoring Protocol)
1311 link: https://www.rfc-editor.org/rfc/rfc7854
1312 categories:
1017 - - flows.bgp-routing
1313 + - flows.enrichment-methods
1314 icon_filename: network-wired.svg
1315 keywords:
1316 - bmp
@@ -1023,7 +1319,9 @@ modules:
1319 - route monitoring
1320 - cisco
1321 - juniper
1322 + - arista
1323 - frr
1324 + - nokia
1325 related_resources:
1326 integrations:
1327 list:
@@ -1034,28 +1332,49 @@ modules:
1332 overview:
1333 data_collection:
1334 metrics_description: |
1037 - BMP (BGP Monitoring Protocol, RFC 7854) lets a router push its BGP route updates
1038 - to a passive collector. With this integration enabled, Netdata acts as that
1039 - collector -- it listens for BMP TCP connections from your routers, parses the BGP
1040 - UPDATE messages, and builds an in-memory routing table that flow enrichment then
1041 - reads from.
1042 -
1043 - The result: every flow gets accurate AS numbers, AS paths, communities, and
1044 - next-hop information from your real-time BGP table -- not from a stale GeoIP
1045 - database or from whatever the exporter happened to send in the flow record.
1046 -
1047 - For the full BGP-routing concept (shared trie with BioRIS, withdrawal handling,
1048 - per-vendor caveats, integration test gap), see
1049 - [BGP Routing](https://learn.netdata.cloud/docs/network-flows/enrichment/bgp-routing).
1335 + Enrich network flows with BGP next-hop, AS path, communities, and routing
1336 + context received directly from routers. BMP (BGP Monitoring Protocol, RFC 7854)
1337 + lets a router push its BGP route updates to a passive collector. With this
1338 + integration enabled, Netdata is that collector -- it listens for BMP TCP
1339 + connections from your routers, parses BGP UPDATE messages carried inside
1340 + RouteMonitoring frames, and builds an in-memory routing trie that flow enrichment
1341 + then reads from.
1342 +
1343 + Every flow whose source or destination IP matches a learned prefix gains:
1344 + `SRC_AS` / `DST_AS` (when the `routing` provider in `asn_providers` reaches BGP
1345 + data), `SRC_MASK` / `DST_MASK` (when `routing` reaches BGP data in `net_providers`),
1346 + plus -- for the destination side only -- `NEXT_HOP`, `DST_AS_PATH`,
1347 + `DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES` (RFC 8092). Source-side AS path and
1348 + communities are not surfaced; BGP path attributes are most meaningful for the
1349 + destination of the traffic. AS *names* (`*_AS_NAME`) come from the GeoIP/ASN MMDB,
1350 + not from BMP -- BMP gives you accurate AS *numbers* and path/communities.
1351 +
1352 + AS path, communities, and large communities are written to the **raw** flow journal
1353 + only -- the rollup tiers do not carry them. `NEXT_HOP` is carried in both raw and
1354 + rollup.
1355 +
1356 + For the cross-cutting Enrichment concept (provider chains, shared trie with
1357 + BioRIS, withdrawal handling, restart convergence), see
1358 + [Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
1359 method_description: |
1051 - The plugin runs a TCP listener on `0.0.0.0:10179` (Akvorado convention -- not the
1052 - IANA-registered port 7854). Routers initiate BMP sessions to it. The plugin
1053 - processes Initiation, Termination, RouteMonitoring (BGP UPDATE messages), and
1054 - PeerDownNotification messages. NLRI types: IPv4/IPv6 unicast, MPLS-labelled, VPNv4,
1055 - VPNv6, EVPN IP-prefix.
1056 -
1057 - BMP and BioRIS share a single in-memory routing trie. Memory grows with the size
1058 - of the BGP table; a full IPv4+IPv6 feed is roughly 1.2M prefixes per peer.
1360 + The plugin runs a TCP listener on `0.0.0.0:10179`. This port is the Akvorado
1361 + convention -- RFC 7854 does not register a port, and IANA does not assign one
1362 + for BMP. Each connecting router must first send an Initiation message; the plugin
1363 + then processes RouteMonitoring (carrying BGP UPDATE), PeerDownNotification, and
1364 + Termination frames. PeerUp, StatisticsReport, and RouteMirroring frames are
1365 + accepted but not acted on. Only BMP **version 3** is processed; v1 and v2 frames
1366 + are silently dropped.
1367 +
1368 + NLRI families parsed: IPv4/IPv6 unicast, IPv4/IPv6 MPLS-labelled, VPNv4, VPNv6,
1369 + and EVPN IP-prefix routes.
1370 +
1371 + BMP and BioRIS share a single in-memory routing trie. A full IPv4+IPv6 BGP table
1372 + is roughly 1.2M prefixes per peer; each entry stores `Vec<u32>` AS-path,
1373 + `Vec<u32>` communities, `Vec<(u32,u32,u32)>` large communities, plus a route_key
1374 + string per path. Expect several hundred MB of resident memory per peer with a full
1375 + feed. The trie has no time-based eviction -- routes leave only via explicit BGP
1376 + withdrawal (`MP_UNREACH` or `withdraw_routes`), PeerDown, or session disconnect
1377 + followed by the `keep` interval expiring (default 5 minutes).
1378 supported_platforms:
1379 include:
1380 - Linux
@@ -1065,28 +1384,44 @@ modules:
1384 description: ""
1385 default_behavior:
1386 auto_detection:
1068 - description: "Disabled by default. Set enrichment.routing_dynamic.bmp.enabled to true and configure your routers."
1387 + description: "Disabled by default. Set enrichment.routing_dynamic.bmp.enabled to true and configure your routers to dial in."
1388 limits:
1070 - description: ""
1389 + description: "Memory and CPU scale with the number of BMP sessions, routing tables, prefixes, AS paths, and communities. Full-table router feeds can consume hundreds of MB per peer."
1390 performance_impact:
1072 - description: ""
1391 + description: "Disabled until BMP is configured. Once active, BMP updates maintain an in-memory routing trie used for enrichment, so resource use scales with routing-table size and update rate."
1392 setup:
1393 prerequisites:
1394 list:
1395 - title: BMP-capable routers
1396 description: |
1078 - Modern Cisco IOS-XR, Juniper JunOS, Arista EOS, and FRR all support BMP v3.
1079 - The plugin parses RFC 7854 BMP v3 specifically. Older versions (v1, v2) are
1080 - not supported and will fail to parse.
1397 + Common vendor configuration patterns:
1398 +
1399 + - **Cisco IOS-XR** -- `bmp server N` global block plus `bmp-activate server N`
1400 + under `router bgp ... neighbor`.
1401 + - **Cisco IOS-XE 3.12 / 15.4 or later** -- `bmp server N` nested inside
1402 + `router bgp` with `activate`.
1403 + - **Juniper JunOS** (BMP support since 13.3, RFC 8671 Adj-RIB-In since 18.3R1)
1404 + -- `routing-options bmp ...` with one or more named stations.
1405 + - **Arista EOS** -- `router bgp ... bgp monitoring` with one or more
1406 + `monitoring station` blocks (active connection).
1407 + - **Nokia SR OS** (MD-CLI) -- `/configure bmp` plus per-router `bgp monitor`.
1408 + - **FRR (bgpd)** -- `bmp targets` block under `router bgp`. Note the loadable
1409 + module: `bgpd` must be started with `-M bmp` or every BMP command silently
1410 + fails.
1411 +
1412 + The plugin parses RFC 7854 BMP **version 3** only. Older draft versions (v1,
1413 + v2) are silently dropped.
1414 - title: TCP reachability between routers and the agent
1415 description: |
1416 Routers initiate the connection -- the plugin is a passive listener. Allow
1417 inbound TCP on the configured port (default 10179) from each BMP-speaking
1085 - router to the agent.
1418 + router to the agent. The plugin does not retry; it waits for the router to
1419 + reconnect.
1420 - title: No TLS, no authentication
1421 description: |
1088 - The listener accepts plain TCP only. Restrict access at the firewall and on a
1089 - dedicated management network -- do not expose 10179 to the public internet.
1422 + The listener accepts plain TCP only. Restrict access at the firewall and use
1423 + a dedicated management network -- never expose 10179 to the public internet.
1424 + BMP carries your full routing table; treat it as sensitive.
1425 configuration:
1426 file:
1427 name: netflow.yaml
@@ -1106,7 +1441,7 @@ modules:
1441 default_value: "0.0.0.0:10179"
1442 required: false
1443 - name: keep
1109 - description: Grace window after a BMP disconnect before purging that session's routes.
1444 + description: Grace window after a BMP disconnect before purging that session's routes from the trie.
1445 default_value: "5m"
1446 required: false
1447 - name: max_consecutive_decode_errors
@@ -1118,7 +1453,7 @@ modules:
1453 default_value: "0"
1454 required: false
1455 - name: collect_asns
1121 - description: When false, AS numbers from BMP are forced to 0.
1456 + description: When false, AS numbers from BMP are forced to 0 before storage.
1457 default_value: "true"
1458 required: false
1459 - name: collect_as_paths
@@ -1126,13 +1461,14 @@ modules:
1461 default_value: "true"
1462 required: false
1463 - name: collect_communities
1129 - description: When false, communities and large communities are dropped.
1464 + description: When false, communities and large communities are dropped before storage.
1465 default_value: "true"
1466 required: false
1467 - name: rds
1468 description: |
1134 - Whitelist of accepted Route Distinguishers. Empty = accept all.
1135 - Formats: "0", "ASN:idx", "IPv4:idx", or full text.
1469 + Whitelist of accepted Route Distinguishers for L3VPN peers. Empty list
1470 + accepts everything. Formats: numeric `0`, `"ASN:idx"`, `"IPv4:idx"`, or
1471 + full text RD.
1472 default_value: "[]"
1473 required: false
1474 examples:
@@ -1154,11 +1490,17 @@ modules:
1490 - name: Cisco IOS-XR router config
1491 folding:
1492 enabled: true
1157 - description: Vendor-side config to send BMP to Netdata. The bmp server block is global, not under router bgp.
1493 + description: |
1494 + Vendor-side config to send BMP to Netdata. The `bmp server` block is
1495 + global, not under `router bgp`. Each neighbor that should be exported
1496 + needs `bmp-activate server N`. IOS-XR's default route monitoring is
1497 + pre-policy (Adj-RIB-In before inbound policy). Set
1498 + `route-monitoring policy post inbound` if you prefer post-policy.
1499 config: |
1500 bmp server 1
1501 host 10.0.0.10 port 10179
1502 description "Netdata BMP collector"
1503 + update-source Loopback0
1504 initial-delay 5
1505 stats-reporting-period 60
1506 initial-refresh delay 30 spread 2
@@ -1166,10 +1508,30 @@ modules:
1508 router bgp 65000
1509 neighbor 192.0.2.1
1510 bmp-activate server 1
1511 + - name: Cisco IOS-XE router config
1512 + folding:
1513 + enabled: true
1514 + description: |
1515 + IOS-XE 3.12 / 15.4 or later. The `bmp server N` block lives inside
1516 + `router bgp`, unlike IOS-XR.
1517 + config: |
1518 + router bgp 65000
1519 + bmp server 1
1520 + address 10.0.0.10 port-number 10179
1521 + description "Netdata BMP collector"
1522 + initial-delay 10
1523 + stats-reporting-period 60
1524 + update-source GigabitEthernet1
1525 + activate
1526 + exit-bmp-server-mode
1527 + !
1528 + neighbor 192.0.2.1 bmp-activate all
1529 - name: Juniper JunOS router config
1530 folding:
1531 enabled: true
1172 - description: Recommended local-address and statistics-timeout for production.
1532 + description: |
1533 + Named station form. JunOS supports both pre-policy (RFC 7854) and
1534 + post-policy / Adj-RIB-In (RFC 8671, JunOS 18.3R1+).
1535 config: |
1536 set routing-options bmp station netdata station-address 10.0.0.10
1537 set routing-options bmp station netdata station-port 10179
@@ -1177,12 +1539,29 @@ modules:
1539 set routing-options bmp station netdata local-address 10.0.0.1
1540 set routing-options bmp station netdata statistics-timeout 60
1541 set routing-options bmp station netdata route-monitoring pre-policy
1542 + set routing-options bmp station netdata monitor enable
1543 + - name: Arista EOS router config
1544 + folding:
1545 + enabled: true
1546 + description: |
1547 + EOS uses `bgp monitoring` plus one or more `monitoring station` blocks
1548 + inside `router bgp`. Active connection mode is the equivalent of all
1549 + other vendors (router dials Netdata).
1550 + config: |
1551 + router bgp 65000
1552 + bgp monitoring
1553 + monitoring station netdata
1554 + update-source Management1
1555 + connection address 10.0.0.10
1556 + connection mode active port 10179
1557 + export-policy received routes post-policy
1558 + export-policy bgp rib bestpaths
1559 - name: FRR (bgpd) router config
1560 folding:
1561 enabled: true
1562 description: |
1184 - Critical -- BMP is a runtime module in FRR. Without "-M bmp" in
1185 - /etc/frr/daemons (bgpd_options), every BMP command silently fails.
1563 + Critical -- BMP is a runtime module in FRR. Without `-M bmp` in
1564 + `/etc/frr/daemons` (`bgpd_options`), every BMP command silently fails.
1565 config: |
1566 # /etc/frr/daemons:
1567 # bgpd_options=" -A 127.0.0.1 -M bmp"
@@ -1193,38 +1572,106 @@ modules:
1572 bmp monitor ipv4 unicast pre-policy
1573 bmp monitor ipv6 unicast pre-policy
1574 exit
1575 + - name: Nokia SR OS (MD-CLI) router config
1576 + folding:
1577 + enabled: true
1578 + description: Active connection from one or more BGP routing instances to a named station.
1579 + config: |
1580 + /configure bmp admin-state enable
1581 + /configure bmp station "netdata" admin-state enable
1582 + /configure bmp station "netdata" connection local-address 10.0.0.1
1583 + /configure bmp station "netdata" connection station-address ip-address 10.0.0.10
1584 + /configure bmp station "netdata" connection station-address port 10179
1585 + /configure bmp station "netdata" family ipv4 true
1586 + /configure bmp station "netdata" family ipv6 true
1587 + /configure router "Base" bgp monitor admin-state enable
1588 + /configure router "Base" bgp monitor route-monitoring post-policy true
1589 + /configure router "Base" bgp monitor station "netdata" { }
1590 + - name: Drop AS path and communities
1591 + folding:
1592 + enabled: true
1593 + description: |
1594 + Useful if you only care about the AS number for traffic attribution and
1595 + want to keep the journal small.
1596 + config: |
1597 + enrichment:
1598 + routing_dynamic:
1599 + bmp:
1600 + enabled: true
1601 + collect_asns: true
1602 + collect_as_paths: false
1603 + collect_communities: false
1604 + - name: Restrict to specific Route Distinguishers
1605 + folding:
1606 + enabled: true
1607 + description: |
1608 + For L3VPN peers, only accept routes whose RD matches the whitelist.
1609 + Other peer types are unaffected.
1610 + config: |
1611 + enrichment:
1612 + routing_dynamic:
1613 + bmp:
1614 + enabled: true
1615 + rds:
1616 + - "65000:100"
1617 + - "65000:200"
1618 troubleshooting:
1619 problems:
1620 list:
1621 - name: Listener not receiving BMP sessions
1622 description: |
1201 - Check `show bmp` (Cisco) / `show bmp connections` (Juniper) / `show bmp targets` (FRR)
1202 - to confirm the router has dialed in. The plugin does not initiate -- it listens.
1203 - Firewall: allow inbound TCP on 10179.
1204 - - name: Memory growth
1623 + The plugin is a passive listener -- it never dials. Check the router side:
1624 + `show bmp` (Cisco), `show bmp connections` / `show bgp monitoring station`
1625 + (Juniper, Arista), `show bmp targets` (FRR). Confirm the firewall allows
1626 + inbound TCP on port 10179. For FRR specifically, verify `bgpd` was started
1627 + with `-M bmp` -- without it, every BMP command is silently accepted but
1628 + no connection is ever opened.
1629 + - name: Convergence takes minutes after restart
1630 + description: |
1631 + The trie is not persisted. After a plugin restart, routers re-send
1632 + Initiation followed by their Adj-RIB-In as RouteMonitoring updates. FRR
1633 + re-emits everything in seconds. Cisco IOS-XR's `initial-refresh` is
1634 + configurably spread (defaults to a per-peer delay) so a full re-feed can
1635 + take minutes. Juniper varies between seconds and minutes depending on
1636 + station options. Schedule restarts off-peak when BGP attribution matters.
1637 + - name: Memory growth without bound
1638 description: |
1206 - A full BGP feed adds ~1.2M prefixes per peer permanently. There is no time-based
1207 - eviction. Plan capacity accordingly. After a router disconnect, the routes
1208 - for that session are kept for `keep` (default 5 min) before purging.
1209 - - name: Integration-test gap
1639 + A full BGP feed adds ~1.2M prefixes per peer permanently -- there is no
1640 + time-based eviction in the trie. Routes are removed only by explicit BGP
1641 + withdrawal, PeerDown, or session disconnect followed by the `keep`
1642 + interval. Plan capacity before connecting full-table peers.
1643 + - name: AS path inconsistent with the exporter's view
1644 description: |
1211 - BMP message parsing has unit tests. The TCP listener path, framed decode loop,
1212 - trie apply, and per-router cleanup are NOT integration-tested. Validate against
1213 - your specific router firmware before depending on this for capacity / security
1214 - decisions.
1645 + The exporter and the BMP-feeding router are usually different boxes with
1646 + different routing tables. Different vantage points see different AS paths;
1647 + this is normal. The `routing` provider in the `asn_providers` chain decides
1648 + which source wins (default order is `flow, routing, geoip` -- exporter first).
1649 + - name: Empty BGP enrichment after enabling
1650 + description: |
1651 + Confirm the router actually established the BMP session (vendor-side `show`
1652 + command above). Confirm `enrichment.asn_providers` includes `routing` (or
1653 + the `bmp` alias) -- if `routing` is removed from both `asn_providers` and
1654 + `net_providers`, the trie is built but never read.
1655 + - name: Validate BGP enrichment after enabling
1656 + description: |
1657 + BGP-derived enrichment depends on router export policy, peer state, and
1658 + route visibility. Validate against your specific router firmware before
1659 + depending on this for capacity or security decisions.
1660 alerts: []
1661 metrics:
1662 folding:
1663 title: Metrics
1664 enabled: false
1665 description: |
1221 - Enriches flow records with `SRC_AS`, `DST_AS`, `SRC_MASK`, `DST_MASK`, `NEXT_HOP`,
1222 - `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES`. Source-side AS path and
1223 - communities are not surfaced. AS names come from the GeoIP/ASN MMDB, not from BMP.
1224 - Verify by querying the AS-path columns on the Network Flows tab.
1666 + Enriches flow records with `SRC_AS` / `DST_AS` (when the `routing` provider in
1667 + `asn_providers` reaches BGP), `SRC_MASK` / `DST_MASK` (via `net_providers`),
1668 + `NEXT_HOP`, `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES`. Source-side
1669 + AS path and communities are not surfaced. AS *names* come from the GeoIP/ASN MMDB,
1670 + not from BMP. AS path, communities, and large communities are RAW-tier-only --
1671 + rollups carry only `NEXT_HOP`. Verify in the Network Flows view by querying the
1672 + AS-path and communities columns.
1673 availability: []
1674 scopes: []
1227 -
1675 - meta:
1676 plugin_name: netflow-plugin
1677 module_name: bioris
@@ -1232,7 +1679,7 @@ modules:
1679 name: bio-rd / RIPE RIS
1680 link: https://github.com/bio-routing/bio-rd
1681 categories:
1235 - - flows.bgp-routing
1682 + - flows.enrichment-methods
1683 icon_filename: network-wired.svg
1684 keywords:
1685 - bioris
@@ -1241,6 +1688,8 @@ modules:
1688 - bgp
1689 - grpc
1690 - route information service
1691 + - as path
1692 + - bgp communities
1693 related_resources:
1694 integrations:
1695 list:
@@ -1251,23 +1700,67 @@ modules:
1700 overview:
1701 data_collection:
1702 metrics_description: |
1254 - BioRIS lets Netdata consume BGP routing data from a [bio-rd](https://github.com/bio-routing/bio-rd)
1255 - `cmd/ris/` daemon over gRPC. bio-rd is a Go-based BGP daemon that can peer with
1256 - [RIPE RIS](https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris)
1257 - Route Collectors -- or any BGP / BMP source you have access to -- and expose the
1258 - resulting RIB through a gRPC interface. Netdata is a client of that interface.
1259 -
1260 - Use this when you want a third-party view of the BGP routing table (e.g., RIPE
1261 - RIS's view) without running a BGP session yourself or deploying BMP across your
1262 - network.
1263 -
1264 - For the full BGP-routing concept and how BMP and BioRIS share the same trie, see
1265 - [BGP Routing](https://learn.netdata.cloud/docs/network-flows/enrichment/bgp-routing).
1703 + Enrich network flows with BGP routing context received from bio-rd / RIPE RIS
1704 + over gRPC. [bio-rd](https://github.com/bio-routing/bio-rd)
1705 + is a Go-based BGP/BMP daemon that you run yourself. You point it at one or more
1706 + BGP / BMP sources -- your own routers, a [RIPE RIS](https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris)
1707 + Route Collector you peer with, or any other reachable BGP source -- and it
1708 + exposes the resulting RIB through a gRPC interface (`RoutingInformationService`).
1709 + Netdata is a **client** of that interface, not of RIPE NCC directly.
1710 +
1711 + Pick this when you want a third-party / external BGP view (e.g. RIPE RIS's view of the
1712 + public DFZ) without exposing a BMP listener on your agent or running BMP across your
1713 + network. If your routers can speak BMP straight to Netdata, the `bmp` integration is
1714 + simpler -- BioRIS makes sense when bio-rd is already part of your routing toolbox or
1715 + when you want to peer with an external party (RIPE RIS, looking-glass) and re-export
1716 + to Netdata.
1717 +
1718 + BioRIS populates the same flow-record fields as BMP -- both feed a single shared
1719 + in-memory routing trie. See the BMP integration card for the side-by-side comparison
1720 + and combined-deployment notes.
1721 method_description: |
1267 - The plugin connects to one or more bio-rd `ris` gRPC endpoints. It runs three RPCs:
1268 - `GetRouters` to discover what's available, `DumpRIB` to do baseline reconciliation,
1269 - and `ObserveRIB` for incremental updates. Multiple instances are additive (not
1270 - failover); they all merge into the shared in-memory trie.
1722 + The plugin connects as a gRPC client to one or more user-provided bio-rd `ris`
1723 + endpoints (`grpc_addr`) and runs three RPCs against each one:
1724 +
1725 + 1. `GetRouters` -- discover which routers/VRFs the bio-rd instance is exposing.
1726 + 2. `DumpRIB` -- pull a baseline RIB for each (router, AFI/SAFI) tuple. This is the
1727 + expensive call -- full IPv4+IPv6 feeds run to millions of prefixes.
1728 + 3. `ObserveRIB` -- subscribe to incremental updates so the trie tracks live changes.
1729 +
1730 + Per refresh cycle (default 30 minutes), the plugin re-runs `GetRouters` +
1731 + `DumpRIB`, then keeps `ObserveRIB` streams open between cycles. Routers that
1732 + disappear between refreshes have their routes purged.
1733 +
1734 + Multiple `ris_instances` are **additive**, not failover -- routes from every
1735 + configured endpoint merge into the same trie. The trie is also shared with the
1736 + `bmp` integration, so if both are enabled their routes coexist and lookups pick
1737 + the best match across both sources.
1738 +
1739 + Connection is plain gRPC over HTTP/2 by default; set `grpc_secure: true` to use
1740 + TLS with the system CA bundle. There is no client-cert / mTLS and no application
1741 + auth -- restrict access at the network layer.
1742 +
1743 + **Fields populated** (same set as BMP):
1744 +
1745 + | Field | Side | Source |
1746 + |---|---|---|
1747 + | `SRC_AS` / `DST_AS` | both | When the `routing` provider in the `asn_providers` chain matches |
1748 + | `SRC_MASK` / `DST_MASK` | both | Prefix length of the longest-match BGP route |
1749 + | `NEXT_HOP` | dest only | BGP next-hop attribute from the destination route |
1750 + | `DST_AS_PATH` | dest only | Full AS path, CSV of ASNs |
1751 + | `DST_COMMUNITIES` | dest only | Standard BGP communities (CSV of u32) |
1752 + | `DST_LARGE_COMMUNITIES` | dest only | RFC 8092 large communities |
1753 +
1754 + Source-side AS path and communities are **not** surfaced; BGP path attributes are
1755 + most meaningful for the destination of the traffic. AS *names* (`SRC_AS_NAME`,
1756 + `DST_AS_NAME`) come from the GeoIP/ASN MMDB, not from BGP -- BioRIS gives you
1757 + accurate AS *numbers* and path/community attributes; the names come from the ASN
1758 + database integration.
1759 +
1760 + **Storage tier:** `DST_AS_PATH`, `DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES`
1761 + are written only into the raw journal tier. The 1-minute / 5-minute / 1-hour
1762 + rollup tiers do not carry them. Queries that need AS path or community data
1763 + must run against a window that the raw retention still covers.
1764 supported_platforms:
1765 include:
1766 - Linux
@@ -1279,16 +1772,19 @@ modules:
1772 auto_detection:
1773 description: "Disabled by default. Set enrichment.routing_dynamic.bioris.enabled to true and provide at least one ris_instances entry."
1774 limits:
1282 - description: ""
1775 + description: "Memory scales with the number of RIS instances, peers, routing tables, prefixes, AS paths, and communities. Full-table feeds can consume hundreds of MB per peer."
1776 performance_impact:
1284 - description: ""
1777 + description: |
1778 + Disabled until BioRIS is configured. Once active, Netdata maintains an
1779 + in-memory routing trie for the received RIB and updates. Plan capacity from
1780 + the number of peers and tables you import, and watch the agent's RSS.
1781 setup:
1782 prerequisites:
1783 list:
1784 - title: A running bio-rd 'ris' daemon
1785 description: |
1786 bio-rd is a separate project. The plugin only consumes its gRPC interface; it
1291 - does not bundle bio-rd. You install it yourself:
1787 + does not bundle bio-rd. You install and operate it yourself:
1788
1789 ```bash
1790 # Install Go (>=1.20), then:
@@ -1297,23 +1793,26 @@ modules:
1793 go build -o /usr/local/bin/ris .
1794 ```
1795
1300 - Configure `ris` to peer with one or more BGP / BMP sources (RIPE RIS Route
1301 - Collectors, your own peers, etc.). Refer to the bio-rd documentation for the
1302 - peering setup -- this is bio-rd's configuration, not Netdata's.
1796 + Configure `ris` to peer with one or more BGP / BMP sources (your own routers,
1797 + RIPE RIS Route Collectors, looking-glass servers, etc.). Refer to the bio-rd
1798 + documentation for the peering setup -- this is bio-rd's configuration, not
1799 + Netdata's.
1800
1801 Run the daemon with a gRPC port:
1802 `/usr/local/bin/ris --grpc_port 50051 --config.file /etc/bio-rd.yml`
1803 - title: Network reachability + no auth
1804 description: |
1308 - The gRPC connection is plain HTTP/2 by default (or TLS with system-CA when
1309 - `grpc_secure: true`). There is no authentication. Restrict access at the
1310 - firewall, or run bio-rd on the same host as the agent and bind it to localhost.
1805 + The gRPC connection is plain HTTP/2 by default, or TLS with the system CA
1806 + bundle when `grpc_secure: true`. There is no client-cert / mTLS and no application
1807 + authentication -- restrict access at the firewall, or run bio-rd on the same
1808 + host as the agent and bind it to localhost.
1809 configuration:
1810 file:
1811 name: netflow.yaml
1812 options:
1813 description: |
1316 - BioRIS options live under `enrichment.routing_dynamic.bioris`.
1814 + BioRIS options live under `enrichment.routing_dynamic.bioris` in
1815 + `netflow.yaml`.
1816 folding:
1817 title: Config options
1818 enabled: true
@@ -1323,21 +1822,24 @@ modules:
1822 default_value: "false"
1823 required: false
1824 - name: timeout
1326 - description: Connect + per-RPC timeout. Default is aggressive for public RIS over the internet -- raise if you see "deadline exceeded".
1825 + description: Connect + per-RPC timeout for `GetRouters` and `ObserveRIB` setup. Default is aggressive; raise to 2-5s when reaching a remote bio-rd over the public internet.
1826 default_value: "200ms"
1827 required: false
1828 - name: refresh
1330 - description: How often to re-dump every router's RIB from scratch.
1829 + description: Cadence at which the plugin re-runs `GetRouters` + `DumpRIB` for every router. Floored to 10s.
1830 default_value: "30m"
1831 required: false
1832 - name: refresh_timeout
1334 - description: Per-DumpRIB request timeout and per-message stream timeout.
1833 + description: Per-DumpRIB request timeout and per-message stream timeout for the baseline RIB pull.
1834 default_value: "10s"
1835 required: false
1836 - name: ris_instances
1837 description: |
1339 - List of bio-rd endpoints. Each: grpc_addr, grpc_secure, vrf, vrf_id.
1340 - Multiple instances are additive (not failover) -- routes from all merge.
1838 + List of bio-rd endpoints. Each entry: `grpc_addr` (`host:port` or full
1839 + `http(s)://` URI), `grpc_secure` (TLS with system CAs when true), `vrf`
1840 + (string VRF name to filter on), `vrf_id` (numeric VRF ID, alternative to
1841 + `vrf`). Multiple instances are additive (not failover) -- routes from every
1842 + instance merge into the same trie.
1843 default_value: "[]"
1844 required: true
1845 examples:
@@ -1374,40 +1876,98 @@ modules:
1876 - grpc_addr: "ris.example.internal:50051"
1877 grpc_secure: true
1878 vrf: "global"
1879 + - name: Prefer BGP over the exporter's AS view
1880 + folding:
1881 + enabled: true
1882 + description: |
1883 + With the default provider chain, an exporter-supplied AS number wins over
1884 + BGP. If your bio-rd feed is more accurate than what the exporter is
1885 + stamping into flow records, reorder the chains so `routing` is consulted
1886 + first.
1887 + config: |
1888 + enrichment:
1889 + asn_providers: [routing, flow, geoip]
1890 + net_providers: [routing, flow]
1891 + routing_dynamic:
1892 + bioris:
1893 + enabled: true
1894 + ris_instances:
1895 + - grpc_addr: "127.0.0.1:50051"
1896 + - name: Combined with BMP into a single trie
1897 + folding:
1898 + enabled: true
1899 + description: |
1900 + Run BMP from internal routers and BioRIS for an external (RIPE RIS) view.
1901 + Both populate the same shared trie -- lookups pick the best-matching route
1902 + across both sources at query time.
1903 + config: |
1904 + enrichment:
1905 + routing_dynamic:
1906 + bmp:
1907 + enabled: true
1908 + listen: "0.0.0.0:10179"
1909 + bioris:
1910 + enabled: true
1911 + ris_instances:
1912 + - grpc_addr: "127.0.0.1:50051"
1913 troubleshooting:
1914 problems:
1915 list:
1916 - name: Default 200ms timeout too aggressive
1917 description: |
1382 - Over the public internet to RIPE RIS, you may need 2-5 seconds. If you see
1383 - "deadline exceeded" errors in the journal, raise `timeout`.
1918 + `timeout` covers the gRPC connect plus the `GetRouters` and `ObserveRIB`
1919 + setup RPCs. Over the public internet to a remote bio-rd, 200ms is often not
1920 + enough. If you see "deadline exceeded" errors in the journal, raise to 2-5s.
1921 + `refresh_timeout` is separate and bounds the long-running `DumpRIB` and per-
1922 + message stream reads (default 10s).
1923 - name: Initial dump takes minutes for full feeds
1924 description: |
1386 - A full IPv4+IPv6 RIB from a route collector is millions of prefixes. The first
1387 - refresh takes time; subsequent observe streams are incremental.
1388 - - name: Integration-test gap
1925 + A full IPv4+IPv6 RIB from a route collector is millions of prefixes. The
1926 + first `DumpRIB` after enabling (or after a plugin restart) takes time -- BGP
1927 + attribution is incomplete until it finishes. Subsequent `ObserveRIB` streams
1928 + are incremental.
1929 + - name: Plugin restart wipes the trie
1930 + description: |
1931 + The trie is in-memory only -- restarting the netflow plugin loses every
1932 + learned BGP route. Convergence over BioRIS depends on the upstream feed; a
1933 + full DumpRIB from a RIPE RIS Route Collector can take minutes. Schedule
1934 + restarts off-peak if BGP attribution matters for your workflow.
1935 + - name: Memory growth without bound
1936 + description: |
1937 + The trie has no time-based eviction. Routes are removed only when the
1938 + upstream BGP source withdraws them, when a router disappears from
1939 + `GetRouters` between refreshes, or on plugin shutdown. A full feed is
1940 + several hundred MB of RSS per peer, permanently.
1941 + - name: AS path / communities missing on older queries
1942 + description: |
1943 + `DST_AS_PATH`, `DST_COMMUNITIES`, and `DST_LARGE_COMMUNITIES` only exist in
1944 + the raw journal tier. The 1m / 5m / 1h rollup tiers do not carry them. Queries that span beyond the raw
1945 + retention horizon will not return BGP path data.
1946 + - name: AS path inconsistent with the exporter's view
1947 description: |
1390 - proto and route conversion are unit-tested. The gRPC client path
1391 - (connecting, consuming streams, retry/backoff) is NOT integration-tested.
1392 - Validate against your specific bio-rd setup before relying on this for
1393 - capacity / security decisions.
1948 + Different vantage points see different BGP paths. If your flow exporter and
1949 + the BGP source bio-rd is peering with are different boxes with different
1950 + routing tables, expect divergence. This is normal in BGP, not a bug.
1951 + - name: Validate BioRIS enrichment after enabling
1952 + description: |
1953 + BioRIS-derived enrichment depends on the bio-rd version, upstream BGP source,
1954 + route visibility, and refresh cadence. Validate against your bio-rd setup
1955 + before relying on this for capacity or security decisions.
1956 alerts: []
1957 metrics:
1958 folding:
1959 title: Metrics
1960 enabled: false
1961 description: |
1400 - Enriches flow records with the same fields as BMP (`SRC_AS`, `DST_AS`, masks,
1401 - `NEXT_HOP`, `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES`). Both
1402 - sources contribute to the same shared trie -- if both are enabled, lookups
1403 - prefer the best-matching route across both.
1962 + Enriches flow records with the same fields as BMP: `SRC_AS`, `DST_AS`, `SRC_MASK`,
1963 + `DST_MASK`, `NEXT_HOP`, `DST_AS_PATH`, `DST_COMMUNITIES`, `DST_LARGE_COMMUNITIES`.
1964 + Source-side AS path and communities are not surfaced. AS names come from the
1965 + GeoIP/ASN MMDB, not from BGP. AS path and communities are written only to the raw
1966 + journal tier; rollup tiers do not carry them. When BMP and BioRIS are both
1967 + enabled, they contribute to a single shared in-memory trie and lookups pick the
1968 + best-matching route across both sources.
1969 availability: []
1970 scopes: []
1406 -
1407 - # =====================================================================
1408 - # Network Identity: external feeds that label your network prefixes
1409 - # =====================================================================
1410 -
1971 - meta:
1972 plugin_name: netflow-plugin
1973 module_name: aws-ip-ranges
@@ -1415,7 +1975,7 @@ modules:
1975 name: AWS IP Ranges
1976 link: https://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html
1977 categories:
1418 - - flows.network-identity
1978 + - flows.enrichment-methods
1979 icon_filename: network-wired.svg
1980 keywords:
1981 - aws
@@ -1425,6 +1985,7 @@ modules:
1985 - vpc
1986 - ec2
1987 - prefix list
1988 + - service tags
1989 related_resources:
1990 integrations:
1991 list:
@@ -1441,20 +2002,46 @@ modules:
2002 overview:
2003 data_collection:
2004 metrics_description: |
1444 - AWS publishes a continuously updated JSON file listing every public IP prefix used
1445 - by AWS services -- per region, per service. This integration fetches that file
1446 - periodically, transforms it via a jq expression, and uses the result to label flow
1447 - records destined to / from AWS with `*_NET_TENANT="amazon"` plus a per-region tag.
2005 + Annotate network flows with AWS service and region labels from AWS public IP
2006 + ranges. AWS publishes its current public IP allocations as a single JSON document at
2007 + [`https://ip-ranges.amazonaws.com/ip-ranges.json`](https://ip-ranges.amazonaws.com/ip-ranges.json).
2008 + The file lists every prefix AWS uses for a published service, tagged with the AWS
2009 + region and the AWS service that owns the prefix (`AMAZON`, `EC2`, `S3`,
2010 + `CLOUDFRONT`, `API_GATEWAY`, ...). This integration fetches the file periodically,
2011 + transforms it via a jq expression, and labels matching flow records with provider,
2012 + region, and service tags.
2013 +
2014 + Once configured, traffic to / from AWS shows up in dashboards as `tenant=amazon`
2015 + with per-region (`region=us-east-1`, `region=eu-central-1`, ...) and per-service
2016 + (`role=s3`, `role=cloudfront`, ...) breakdown. This is **complementary to GeoIP
2017 + and ASN enrichment**: instead of a flow being labeled simply `AS16509 Amazon`,
2018 + it becomes labeled with the specific AWS service and region carrying the
2019 + traffic -- without parsing AS-name strings.
2020 +
2021 + The file is public; no AWS credentials are needed.
2022
1449 - The result: traffic to/from AWS shows up clearly in dashboards as "amazon", with
1450 - per-region and per-service breakdown if you customize the jq transform.
2023 + Schema of `ip-ranges.json`:
2024
1452 - For the full network-identity concept (merge order, jq transform, TLS verification),
1453 - see [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
2025 + - top-level: `syncToken`, `createDate`, `prefixes`, `ipv6_prefixes`
2026 + - each entry in `prefixes[]`: `ip_prefix`, `region`, `service`, `network_border_group`
2027 + - each entry in `ipv6_prefixes[]`: `ipv6_prefix`, `region`, `service`, `network_border_group`
2028 +
2029 + For the cross-cutting concepts (how multiple network sources merge, the lookup
2030 + priority vs static `networks` config, TLS verification rules, the jq engine and
2031 + its expected output schema, fetch loop and retry/backoff), see
2032 + [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
2033 method_description: |
2034 The plugin issues a periodic GET to `https://ip-ranges.amazonaws.com/ip-ranges.json`,
1456 - parses the JSON body, runs the configured jq transform via the [jaq](https://github.com/01mf02/jaq)
1457 - library, and merges the resulting prefix-labeled rows into the network-attributes trie.
2035 + parses the JSON body, runs the configured jq transform via the
2036 + [jaq](https://github.com/01mf02/jaq) library, and merges the resulting
2037 + prefix-labeled rows into the network-attributes trie shared by all enrichment
2038 + sources.
2039 +
2040 + AWS does not publish a fixed refresh cadence; the file is updated whenever the
2041 + AWS IP space changes (typically several times per day) and the `syncToken` /
2042 + `createDate` fields advance on every change. AWS recommends polling no faster
2043 + than the file actually changes; daily is enough for most flow-attribution use
2044 + cases. The plugin floors the configured `interval` at 60s.
2045 supported_platforms:
2046 include:
2047 - Linux
@@ -1464,43 +2051,68 @@ modules:
2051 description: ""
2052 default_behavior:
2053 auto_detection:
1467 - description: "Disabled by default. Add an entry under enrichment.network_sources to enable."
2054 + description: "Disabled by default. Add an entry under `enrichment.network_sources` to enable."
2055 limits:
1469 - description: ""
2056 + description: "One full AWS prefix document is fetched per refresh. Resource use scales with the number of AWS prefixes selected by your transform and the refresh interval."
2057 performance_impact:
1471 - description: ""
2058 + description: "One HTTPS request per refresh interval plus a jq transform over the AWS prefix document. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records."
2059 setup:
2060 prerequisites:
2061 list:
2062 - title: Outbound HTTPS to AWS
2063 description: |
2064 The agent host must be able to reach `https://ip-ranges.amazonaws.com/ip-ranges.json`.
1478 - No AWS credentials needed -- the file is public.
2065 + No AWS credentials are needed -- the file is public.
2066 configuration:
2067 file:
2068 name: netflow.yaml
2069 options:
2070 description: |
1484 - Add a named entry under `enrichment.network_sources`. The `name` you choose appears
1485 - in flow records via the `*_NET_TENANT` field (when your jq transform sets it).
2071 + Add a named entry under `enrichment.network_sources`. The map key (e.g. `aws`)
2072 + is the source name; it is used in plugin logs but is **not** automatically
2073 + attached to flow records -- the record labels come entirely from your `transform`
2074 + output (`tenant`, `region`, `role`, `name`, `site`, `city`, `country`, `state`,
2075 + `asn`, `asn_name`).
2076 +
2077 + The expected `transform` output is a stream of objects, each carrying a `prefix`
2078 + (CIDR string) plus any subset of the optional attribute fields. See the
2079 + cross-cutting [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment)
2080 + page for the full output schema.
2081 folding:
2082 title: Config options
2083 enabled: true
2084 list:
2085 - name: url
1491 - description: AWS publishes the master file at this URL.
2086 + description: AWS publishes the master file at this URL. Use it as-is unless you mirror it locally for air-gapped environments.
2087 default_value: "https://ip-ranges.amazonaws.com/ip-ranges.json"
2088 required: true
2089 - name: interval
1495 - description: How often to fetch. AWS updates the file roughly every 15 minutes; daily is enough for most uses.
1496 - default_value: "60s (loop floor)"
2090 + description: |
2091 + How often to fetch the file. AWS updates the document whenever its IP space
2092 + changes (often several times per day, sometimes more); daily is enough for
2093 + most uses. The configured value is floored at 60s.
2094 + default_value: "60s"
2095 required: false
2096 - name: timeout
1499 - description: Per-request timeout.
2097 + description: Per-request timeout for the HTTPS GET.
2098 default_value: "60s"
2099 required: false
2100 + - name: method
2101 + description: HTTP method. AWS serves the file via GET; leave at the default.
2102 + default_value: "GET"
2103 + required: false
2104 + - name: headers
2105 + description: |
2106 + Extra HTTP headers added to the request. Not required for the public AWS
2107 + URL; only needed if you front the file behind your own authenticated mirror.
2108 + default_value: "{}"
2109 + required: false
2110 - name: transform
1503 - description: jq expression that converts the AWS response into objects with `prefix` and label fields.
2111 + description: |
2112 + jq expression (compiled by jaq) that converts the AWS JSON into a stream
2113 + of `{prefix, ...}` objects. The default `.` does **not** match the AWS
2114 + schema -- you must supply a real transform (see examples below) or fetches fail
2115 + because output rows cannot be mapped to the required `{prefix, ...}` schema.
2116 default_value: "."
2117 required: true
2118 examples:
@@ -1508,10 +2120,14 @@ modules:
2120 title: Config
2121 enabled: true
2122 list:
1511 - - name: Tag all AWS prefixes by region and service
2123 + - name: Tag all AWS prefixes by region and service (IPv4 + IPv6)
2124 folding:
2125 enabled: false
1514 - description: "Sets tenant=amazon, region=`aws-region`, role=`service-name`."
2126 + description: |
2127 + Recommended starting point. Sets `tenant=amazon`, `region=<aws-region>`,
2128 + `role=<service-name-lowercased>`. Covers both `prefixes[]` and
2129 + `ipv6_prefixes[]` in one stream so v4 and v6 traffic are tagged
2130 + consistently.
2131 config: |
2132 enrichment:
2133 network_sources:
@@ -1529,7 +2145,9 @@ modules:
2145 - name: AWS S3 only
2146 folding:
2147 enabled: true
1532 - description: Filter to a single AWS service for narrower tagging.
2148 + description: |
2149 + Filter to a single AWS service for narrower tagging. Useful when you only
2150 + care about identifying S3 traffic (e.g. egress-cost attribution).
2151 config: |
2152 enrichment:
2153 network_sources:
@@ -1545,29 +2163,83 @@ modules:
2163 role: "s3",
2164 region: .region
2165 }
2166 + - name: Use network_border_group as the site label
2167 + folding:
2168 + enabled: true
2169 + description: |
2170 + AWS exposes a `network_border_group` field that distinguishes Local Zones
2171 + / Wavelength Zones from the parent Region. Map it to the `site` attribute
2172 + if you want that distinction visible in flow dashboards.
2173 + config: |
2174 + enrichment:
2175 + network_sources:
2176 + aws:
2177 + url: "https://ip-ranges.amazonaws.com/ip-ranges.json"
2178 + interval: 24h
2179 + transform: |
2180 + (.prefixes + .ipv6_prefixes)[] | {
2181 + prefix: (.ip_prefix // .ipv6_prefix),
2182 + tenant: "amazon",
2183 + region: .region,
2184 + site: .network_border_group,
2185 + role: (.service | ascii_downcase)
2186 + }
2187 troubleshooting:
2188 problems:
2189 list:
2190 + - name: 'Default `transform: "."` fails because output rows are missing `prefix`'
2191 + description: |
2192 + The default `.` returns the raw JSON object, not the per-prefix stream the
2193 + plugin expects. You **must** supply a transform that yields one object per
2194 + prefix (with at least a `prefix` field). Use the first example above as your
2195 + starting template.
2196 + - name: AWS service name precedence inside one prefix
2197 + description: |
2198 + AWS sometimes lists the same `ip_prefix` under multiple `service` entries
2199 + (e.g. once under `AMAZON` and again under `EC2`). The plugin merges all
2200 + records into a single prefix trie, and the **last write wins** for a given
2201 + prefix length. If you want a specific service to take precedence, filter the
2202 + transform with `select(.service != "AMAZON")` so the broader catch-all
2203 + entries are dropped.
2204 + - name: Static `networks:` config silently overrides AWS labels
2205 + description: |
2206 + When a prefix is defined in both this source and `enrichment.networks`, the
2207 + static config wins on every non-empty field. This is by design (operator
2208 + intent overrides imported data) but can surprise operators who expect the
2209 + remote feed to be authoritative. See the cross-cutting Network Identity page
2210 + for the full lookup-priority rules.
2211 - name: Empty result from the transform is treated as failure
2212 description: |
1553 - If the jq filter happens to produce nothing (e.g., AWS responds with no prefixes),
1554 - the source backs off as if it errored. Check the journal for `network-sources` warnings.
2213 + If the jq filter happens to produce nothing (bad selector, wrong field name,
2214 + upstream JSON shape change), the source backs off as if it errored. Check
2215 + the journal for `network-sources` warnings:
2216 + `journalctl --namespace netdata | grep network-sources`.
2217 - name: TLS verification cannot be disabled
2218 description: |
2219 `tls.skip_verify: true` is rejected by validation. Use `tls.ca_file` for
1558 - custom-CA paths if needed.
2220 + custom-CA paths if you front AWS behind an internal TLS-terminating proxy
2221 + with a private CA.
2222 alerts: []
2223 metrics:
2224 folding:
2225 title: Metrics
2226 enabled: false
2227 description: |
1565 - Tags flow records with `SRC_NET_TENANT`, `*_NET_REGION`, `*_NET_ROLE`, etc. when
1566 - the source or destination IP matches an AWS prefix. Verify on the Network Flows
1567 - tab via the `*_NET_*` columns.
2228 + This integration does not produce metrics. It enriches flow records with the
2229 + following journal fields when a flow's source or destination IP matches an AWS
2230 + prefix:
2231 +
2232 + - `SRC_NET_NAME` / `DST_NET_NAME`
2233 + - `SRC_NET_ROLE` / `DST_NET_ROLE`
2234 + - `SRC_NET_SITE` / `DST_NET_SITE`
2235 + - `SRC_NET_REGION` / `DST_NET_REGION`
2236 + - `SRC_NET_TENANT` / `DST_NET_TENANT`
2237 +
2238 + Plus, when the transform sets them, the country / state / city / ASN overrides
2239 + documented on the Network Identity page. Verify on the Network Flows view via the
2240 + `*_NET_*` columns or by filtering on `tenant=amazon`.
2241 availability: []
2242 scopes: []
1570 -
2243 - meta:
2244 plugin_name: netflow-plugin
2245 module_name: gcp-ip-ranges
@@ -1575,14 +2247,18 @@ modules:
2247 name: GCP IP Ranges
2248 link: https://cloud.google.com/compute/docs/faq#find_ip_range
2249 categories:
1578 - - flows.network-identity
2250 + - flows.enrichment-methods
2251 icon_filename: network-wired.svg
2252 keywords:
2253 - gcp
2254 - google cloud
2255 + - google
2256 - cloud
2257 - ip ranges
2258 + - cloud.json
2259 + - goog.json
2260 - prefix list
2261 + - network identity
2262 related_resources:
2263 integrations:
2264 list:
@@ -1599,15 +2275,43 @@ modules:
2275 overview:
2276 data_collection:
2277 metrics_description: |
1602 - Google Cloud publishes its public IP prefixes at `https://www.gstatic.com/ipranges/cloud.json`,
1603 - updated periodically. This integration fetches the file and labels flow records
1604 - to/from Google Cloud with `*_NET_TENANT="gcp"` plus per-scope and per-service tags.
2278 + Annotate network flows with Google ownership, service, and scope labels from
2279 + Google public IP ranges. Google publishes its public IP ranges as two static JSON
2280 + files served from `gstatic.com`. Wire one of them as a
2281 + `network_source` and the netflow plugin fetches it periodically, transforms each
2282 + prefix entry through your jq filter, and merges the resulting CIDR-keyed records
2283 + into the network-attributes trie. Flows whose source or destination IP falls
2284 + inside a Google-owned prefix are then labeled with `*_NET_TENANT`,
2285 + `*_NET_REGION`, `*_NET_ROLE`, etc. according to the labels your transform emits.
2286
1606 - For the full network-identity concept, see
1607 - [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
2287 + Two upstream files exist and they are NOT interchangeable:
2288 +
2289 + - **`https://www.gstatic.com/ipranges/cloud.json`** -- the prefixes used by
2290 + Google Cloud (Compute Engine, GKE, Cloud Run, Cloud Functions, Cloud SQL,
2291 + and the rest of Google Cloud Platform). This is what you almost always want
2292 + for cloud-traffic attribution.
2293 + - **`https://www.gstatic.com/ipranges/goog.json`** -- the broader Google IP
2294 + space, including consumer-facing properties (Search, Gmail, YouTube, Google
2295 + Workspace, Maps, Ads, etc.). It is a strict superset of `cloud.json` but
2296 + **carries no service or region metadata** -- only the bare CIDRs.
2297 +
2298 + For the cross-cutting network-identity behavior (merge order with GeoIP and
2299 + static config, jq output contract, TLS rules, single-page fetch, failure /
2300 + backoff handling), see
2301 + [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
2302 method_description: |
1609 - Periodic HTTPS GET, jq transform, merge into network-attributes trie. Same mechanism
1610 - as AWS IP Ranges, different URL and JSON shape.
2303 + Periodic HTTPS GET against the chosen `gstatic.com` URL. The body is parsed
2304 + as JSON and the configured `transform` (a [jaq](https://github.com/01mf02/jaq)
2305 + jq-equivalent expression) runs over the parsed value. Each output object is
2306 + merged into the network-attributes trie keyed on its `prefix` field; remaining
2307 + fields (`name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`,
2308 + `asn`, `asn_name`) populate the corresponding `*_NET_*` flow-record columns
2309 + when a flow's IP matches the prefix.
2310 +
2311 + The upstream files are static JSON snapshots, not pageable APIs -- one fetch
2312 + per cycle is the whole dataset. Each contains a `syncToken` and a
2313 + `creationTime` you can inspect to see when Google last regenerated them.
2314 + The plugin fetches the full document on each refresh cycle.
2315 supported_platforms:
2316 include:
2317 - Linux
@@ -1619,32 +2323,70 @@ modules:
2323 auto_detection:
2324 description: "Disabled by default. Add an entry under enrichment.network_sources to enable."
2325 limits:
1622 - description: ""
2326 + description: "One full GCP prefix document is fetched per refresh. Resource use scales with the number of GCP prefixes selected by your transform and the refresh interval."
2327 performance_impact:
1624 - description: ""
2328 + description: |
2329 + One short HTTPS GET per `interval`, plus a jq pass over the response.
2330 + `cloud.json` is on the order of ~1000 prefix entries and the trie merge
2331 + is bounded by that count; runtime cost is negligible against typical flow
2332 + ingest. `goog.json` is similar in size.
2333 setup:
2334 prerequisites:
2335 list:
1628 - - title: Outbound HTTPS to Google
2336 + - title: Outbound HTTPS to gstatic.com
2337 description: |
1630 - The agent host must be able to reach `https://www.gstatic.com/ipranges/cloud.json`.
1631 - No GCP credentials needed -- the file is public.
2338 + The agent host must be able to reach `https://www.gstatic.com/ipranges/cloud.json`
2339 + (or `goog.json`). The files are public CDN-served static JSON; no Google
2340 + Cloud project, no service account, no API key, and no IAM permission is
2341 + required.
2342 + - title: Pick the right file for your use case
2343 + description: |
2344 + - Use `cloud.json` to attribute traffic to/from **Google Cloud Platform**
2345 + services (Compute Engine VMs, GKE, Cloud Run, Cloud Functions, Cloud
2346 + SQL, BigQuery API, Pub/Sub, GCS endpoints, etc.). Carries `service` and
2347 + `scope` (region) per prefix.
2348 + - Use `goog.json` to also catch **non-cloud Google traffic** (Gmail,
2349 + YouTube, Google Workspace, Search, Ads, Maps, etc.). It carries no
2350 + `service` or `scope` -- you only get "this is Google" attribution.
2351 + - Configure both as separate sources if you want both attributions and
2352 + a deterministic merge order between them. Within network-identity, more-
2353 + specific prefixes win at lookup time, so a `cloud.json` match for a
2354 + Compute Engine prefix will out-rank the broader `goog.json` umbrella.
2355 configuration:
2356 file:
2357 name: netflow.yaml
2358 options:
2359 description: |
1637 - Add a named entry under `enrichment.network_sources`.
2360 + Add a named entry under `enrichment.network_sources`. The entry name is
2361 + cosmetic (used in logs and metrics); the labels that end up in flow records
2362 + come from the fields your `transform` emits.
2363 folding:
2364 title: Config options
2365 enabled: true
2366 list:
2367 - name: url
1643 - description: GCP publishes the master file here.
2368 + description: |
2369 + The Google IP-ranges JSON URL. Use `cloud.json` for GCP services or
2370 + `goog.json` for the broader Google IP space.
2371 default_value: "https://www.gstatic.com/ipranges/cloud.json"
2372 required: true
2373 + - name: interval
2374 + description: |
2375 + How often to refetch. Google does not publish a fixed cadence; their
2376 + documentation states the lists are "published and updated frequently".
2377 + Daily is plenty for most deployments and avoids hammering the CDN.
2378 + default_value: "60s (loop floor)"
2379 + required: false
2380 + - name: timeout
2381 + description: Per-request timeout.
2382 + default_value: "60s"
2383 + required: false
2384 - name: transform
1647 - description: jq expression mapping `prefixes[]` to `prefix` + label objects.
2385 + description: |
2386 + jq expression that converts each entry in `prefixes[]` into an object
2387 + with a `prefix` field (CIDR string) plus any of the optional label
2388 + fields (`name`, `role`, `site`, `region`, `country`, `state`, `city`,
2389 + `tenant`, `asn`, `asn_name`). Required.
2390 default_value: "."
2391 required: true
2392 examples:
@@ -1652,50 +2394,154 @@ modules:
2394 title: Config
2395 enabled: true
2396 list:
1655 - - name: Tag all GCP prefixes by service and scope
2397 + - name: GCP cloud.json -- tag Google Cloud prefixes by region
2398 folding:
2399 enabled: false
1658 - description: "Sets tenant=gcp, role=`service`, region=`scope`."
2400 + description: |
2401 + Tags every flow whose IP matches a Google Cloud prefix with
2402 + `*_NET_TENANT="gcp"`, `*_NET_ROLE="google-cloud"`, and
2403 + `*_NET_REGION` set to the GCP scope (`us-central1`, `europe-west1`,
2404 + `global`, etc.). Both `ipv4Prefix` and `ipv6Prefix` entries are
2405 + covered by the `// (slash-or)` fallback.
2406 config: |
2407 enrichment:
2408 network_sources:
2409 gcp:
2410 url: "https://www.gstatic.com/ipranges/cloud.json"
2411 interval: 24h
2412 + timeout: 60s
2413 transform: |
2414 .prefixes[] | {
2415 prefix: (.ipv4Prefix // .ipv6Prefix),
2416 tenant: "gcp",
1669 - role: .service,
2417 + role: "google-cloud",
2418 region: .scope
2419 }
1672 - troubleshooting:
1673 - problems:
1674 - list:
1675 - - name: Customer-only ranges
1676 - description: |
1677 - GCP also publishes a `goog.json` file (broader: includes Google services beyond
1678 - cloud). Use `cloud.json` for compute IP attribution; `goog.json` if you also
1679 - want to tag Google's other services.
1680 - alerts: []
1681 - metrics:
1682 - folding:
1683 - title: Metrics
1684 - enabled: false
1685 - description: |
1686 - Tags flow records with `*_NET_TENANT`, `*_NET_REGION`, `*_NET_ROLE`. Verify on the
1687 - Network Flows tab via the `*_NET_*` columns.
1688 - availability: []
1689 - scopes: []
1690 -
1691 - - meta:
1692 - plugin_name: netflow-plugin
2420 + - name: GCP cloud.json -- IPv4 only
2421 + folding:
2422 + enabled: true
2423 + description: |
2424 + Same as above but skips the IPv6 prefixes (some operators only need
2425 + IPv4 attribution and prefer to keep the trie smaller).
2426 + config: |
2427 + enrichment:
2428 + network_sources:
2429 + gcp-v4:
2430 + url: "https://www.gstatic.com/ipranges/cloud.json"
2431 + interval: 24h
2432 + transform: |
2433 + .prefixes[]
2434 + | select(.ipv4Prefix)
2435 + | {
2436 + prefix: .ipv4Prefix,
2437 + tenant: "gcp",
2438 + role: "google-cloud",
2439 + region: .scope
2440 + }
2441 + - name: Tag a single GCP region
2442 + folding:
2443 + enabled: true
2444 + description: |
2445 + Narrow the source to one region (here `us-central1`) so you can build
2446 + dashboards that distinguish that region from the rest of GCP.
2447 + config: |
2448 + enrichment:
2449 + network_sources:
2450 + gcp-us-central1:
2451 + url: "https://www.gstatic.com/ipranges/cloud.json"
2452 + interval: 24h
2453 + transform: |
2454 + .prefixes[]
2455 + | select(.scope == "us-central1")
2456 + | {
2457 + prefix: (.ipv4Prefix // .ipv6Prefix),
2458 + tenant: "gcp",
2459 + role: "google-cloud",
2460 + region: "us-central1",
2461 + site: "us-central1"
2462 + }
2463 + - name: Broader Google IP space (goog.json)
2464 + folding:
2465 + enabled: true
2466 + description: |
2467 + Use `goog.json` to also attribute traffic to non-Cloud Google services
2468 + (Gmail, YouTube, Workspace, Search, Ads, Maps). The file has no
2469 + `service` or `scope` fields, so only `tenant` / `role` are set.
2470 + Configure this **alongside** `cloud.json` if you want both -- the more
2471 + specific GCP prefixes from `cloud.json` will still win at lookup time.
2472 + config: |
2473 + enrichment:
2474 + network_sources:
2475 + google-all:
2476 + url: "https://www.gstatic.com/ipranges/goog.json"
2477 + interval: 24h
2478 + transform: |
2479 + .prefixes[] | {
2480 + prefix: (.ipv4Prefix // .ipv6Prefix),
2481 + tenant: "google",
2482 + role: "google"
2483 + }
2484 + troubleshooting:
2485 + problems:
2486 + list:
2487 + - name: cloud.json vs goog.json -- pick the right one
2488 + description: |
2489 + `cloud.json` is the Google Cloud Platform list (Compute Engine, GKE,
2490 + Cloud Run, Cloud SQL, BigQuery, GCS, etc.) and carries `service` plus
2491 + `scope` (region) per entry. `goog.json` is the broader Google list
2492 + (consumer products: Gmail, YouTube, Workspace, Search, Ads, Maps) and
2493 + carries **only the bare CIDRs** -- no service, no scope, no region.
2494 + Using `goog.json` as if it were `cloud.json` will give you "this is
2495 + Google" attribution but no per-region or per-service breakdown. Most
2496 + operators want `cloud.json`; some configure both as separate sources.
2497 + - name: No per-service breakdown from cloud.json
2498 + description: |
2499 + Today every entry in `cloud.json` reports `service: "Google Cloud"` --
2500 + the file does not split prefixes by individual GCP service (Compute
2501 + Engine vs GKE vs Cloud Run, etc.). Region (`scope`) is the dimension
2502 + you can actually pivot on. Per-service attribution requires a different
2503 + data source.
2504 + - name: Empty result from the transform is treated as failure
2505 + description: |
2506 + If the jq expression yields zero objects (for example, an over-narrow
2507 + `select()` that no entry passes), the source backs off as if the fetch
2508 + had errored. Check the journal for `network-sources` warnings and verify
2509 + your filter against a saved copy of `cloud.json`.
2510 + - name: TLS verification cannot be disabled
2511 + description: |
2512 + `tls.skip_verify: true` (and `tls.verify: false`) are rejected by
2513 + validation. `gstatic.com` is publicly trusted so this is rarely an issue
2514 + for GCP; if you front the URL through an internal proxy with a private
2515 + CA, supply it via `tls.ca_file`.
2516 + - name: Update cadence is not contractual
2517 + description: |
2518 + Google states the lists are "published and updated frequently" but does
2519 + not guarantee a fixed cadence. The `syncToken` and `creationTime` keys
2520 + at the top of each file let you check when Google last regenerated it.
2521 + Daily polling is a safe default; sub-hourly is unnecessary.
2522 + alerts: []
2523 + metrics:
2524 + folding:
2525 + title: Metrics
2526 + enabled: false
2527 + description: |
2528 + Tags flow records with `SRC_NET_TENANT` / `DST_NET_TENANT`,
2529 + `SRC_NET_REGION` / `DST_NET_REGION`, `SRC_NET_ROLE` / `DST_NET_ROLE`,
2530 + and any other `*_NET_*` fields your transform emits, when the source or
2531 + destination IP matches a Google prefix. Verify in the Network Flows view via
2532 + the `*_NET_*` columns. Coordinates (`*_GEO_LATITUDE` / `*_GEO_LONGITUDE`)
2533 + cannot be set from network-identity sources -- use the static `networks`
2534 + block for those.
2535 + availability: []
2536 + scopes: []
2537 + - meta:
2538 + plugin_name: netflow-plugin
2539 module_name: azure-ip-ranges
2540 monitored_instance:
2541 name: Azure IP Ranges
2542 link: https://www.microsoft.com/en-us/download/details.aspx?id=56519
2543 categories:
1698 - - flows.network-identity
2544 + - flows.enrichment-methods
2545 icon_filename: network-wired.svg
2546 keywords:
2547 - azure
@@ -1719,23 +2565,33 @@ modules:
2565 overview:
2566 data_collection:
2567 metrics_description: |
1722 - Azure publishes "Service Tags" data describing IP ranges per region and per service.
1723 - The Azure publication mechanism is **less convenient** than AWS / GCP -- the
1724 - authoritative URL contains a date stamp that changes weekly, so you cannot use
1725 - a single stable URL.
1726 -
1727 - For automated fetching, you have two options:
1728 - 1. Mirror the file in your own infrastructure (a script that resolves the latest
1729 - URL via the Azure CLI, downloads, and serves at a stable internal URL).
1730 - 2. Skip Azure IP Ranges entirely and rely on GeoIP / ASN data for Azure
1731 - attribution (Azure ASN is 8075).
1732 -
1733 - For the full network-identity concept, see
1734 - [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
2568 + Annotate network flows with Azure service and region labels from Azure Service
2569 + Tags. Microsoft publishes Azure's public IP prefixes as **Service Tags JSON
2570 + files**. A service tag is Microsoft's name for a group of IP prefixes belonging
2571 + to one Azure service (e.g., `Storage`, `Sql`, `AzureFrontDoor.Backend`); the
2572 + JSON enumerates every tag together with the prefixes, the platform, and -- for
2573 + tags that support regional scope -- the Azure region.
2574 +
2575 + Microsoft publishes four separate JSON files, one per Azure cloud:
2576 +
2577 + - **Azure Public** -- commercial Azure, the file most users want.
2578 + - **Azure US Government** -- separate cloud for US public-sector workloads.
2579 + - **Microsoft Azure operated by 21Vianet** -- the China cloud.
2580 + - **Azure Germany** -- legacy German cloud.
2581 +
2582 + This integration tags flow records to/from Azure-hosted services with
2583 + `*_NET_TENANT="azure"` plus per-region and per-service labels. Use it to
2584 + identify traffic to Azure SQL, Azure Storage, Azure Front Door, App Service,
2585 + and the rest -- without maintaining the prefix list yourself.
2586 +
2587 + For the full network-identity concept (merge order, jq output shape, TLS
2588 + verification, failure modes), see
2589 + [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
2590 method_description: |
1736 - Periodic HTTPS GET against your stable mirror URL, jq transform, merge into
1737 - network-attributes trie. The plugin does not handle Azure's date-stamped URL
1738 - rotation -- you provide a stable URL via your own mirror.
2591 + Periodic HTTPS GET against the Service Tags JSON, jq transform via the
2592 + [jaq](https://github.com/01mf02/jaq) library, merge into the network-attributes
2593 + trie. Same mechanism as AWS / GCP IP Ranges -- different URL and JSON shape,
2594 + plus one important operational caveat documented below.
2595 supported_platforms:
2596 include:
2597 - Linux
@@ -1745,41 +2601,111 @@ modules:
2601 description: ""
2602 default_behavior:
2603 auto_detection:
1748 - description: "Disabled by default. Set up your own URL mirror, then add an entry under enrichment.network_sources."
2604 + description: "Disabled by default. Add an entry under enrichment.network_sources to enable."
2605 limits:
1750 - description: ""
2606 + description: "One full Azure Service Tags document is fetched per refresh. Resource use scales with the number of Azure prefixes selected by your transform and the refresh interval."
2607 performance_impact:
1752 - description: ""
2608 + description: "One HTTPS request per refresh interval plus a jq transform over the Azure Service Tags document. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records."
2609 setup:
2610 prerequisites:
2611 list:
1756 - - title: A stable URL for the Azure Service Tags JSON
2612 + - title: Azure publishes weekly, but the URL rotates
2613 + description: |
2614 + Microsoft updates the Service Tags JSON **weekly** (per
2615 + <https://learn.microsoft.com/en-us/azure/virtual-network/service-tags-overview#discover-service-tags-by-using-downloadable-json-files>:
2616 + "These lists are updated and published weekly"), and Microsoft asks you
2617 + to delay rolling out new prefixes for at least one week after publication
2618 + ("When new IP addresses are added to service tags, they aren't used in
2619 + Azure for at least one week").
2620 +
2621 + The catch: the actual download URL is **not stable**. The download portal
2622 + at <https://www.microsoft.com/en-us/download/details.aspx?id=56519> serves
2623 + a `ServiceTags_Public_<YYYYMMDD>.json` filename whose date stamp changes
2624 + every week. A naive `interval`-based fetch against last week's URL will
2625 + start 404'ing as soon as a new file is published.
2626 +
2627 + You have three reasonable options, in increasing order of effort:
2628 +
2629 + 1. **Skip Azure IP Ranges entirely** and rely on GeoIP / ASN data
2630 + (Microsoft's main ASN is 8075). Coarse but zero-maintenance.
2631 + 2. **Use a stable redirect** if your environment provides one (some
2632 + enterprise mirrors do).
2633 + 3. **Run a small mirror script** -- a cron job that fetches the latest
2634 + JSON via the [Service Tag Discovery REST API](https://learn.microsoft.com/en-us/rest/api/virtualnetwork/servicetags/list)
2635 + (or `Get-AzNetworkServiceTag` in PowerShell) and writes the result to
2636 + a stable path on an internal HTTP server. Point the plugin at that
2637 + stable URL.
2638 +
2639 + Option 3 is what most operators end up with. The Service Tag Discovery
2640 + API is authoritative and authenticated (Azure subscription with read
2641 + role); the downloadable JSON is unauthenticated but URL-rotated. Pick
2642 + whichever fits your operational model.
2643 + - title: Outbound HTTPS to your mirror (or to Microsoft, if you script the URL discovery)
2644 description: |
1758 - Azure's authoritative URL embeds a date stamp that changes weekly. A simple
1759 - workaround: a cron job that calls
1760 - `az network list-service-tags --location global -o json` (Azure CLI) and
1761 - writes the result to a stable path on an internal HTTP server. The plugin then
1762 - fetches from that stable URL.
1763 - - title: Outbound HTTPS to your mirror
2645 + The plugin only needs to reach the configured `url`. Azure credentials
2646 + are needed only on the side that resolves "what's this week's URL?" --
2647 + not by the plugin itself.
2648 + - title: A jq transform that matches the Service Tags schema
2649 description: |
1765 - No Azure credentials needed by the plugin itself; credentials only matter on
1766 - the side that does the upstream Azure CLI call.
2650 + The Service Tags JSON has a nested shape:
2651 +
2652 + ```text
2653 + {
2654 + "changeNumber": 123,
2655 + "cloud": "Public",
2656 + "values": [
2657 + {
2658 + "name": "Storage.WestUS",
2659 + "id": "Storage.WestUS",
2660 + "properties": {
2661 + "changeNumber": 456,
2662 + "region": "westus",
2663 + "regionId": 30,
2664 + "platform": "Azure",
2665 + "systemService": "AzureStorage",
2666 + "addressPrefixes": ["13.105.16.4/30", "20.150.0.0/17", ...],
2667 + "networkFeatures": ["API", "NSG"]
2668 + }
2669 + },
2670 + ...
2671 + ]
2672 + }
2673 + ```
2674 +
2675 + The transform must unwrap `values[]` -> `properties.addressPrefixes[]`
2676 + and emit one object per prefix. Schema reference: `values[].name`,
2677 + `values[].id`,
2678 + `values[].properties.region` (region tag, can be empty for
2679 + cloud-wide tags like the bare `Storage`), `values[].properties.platform`,
2680 + `values[].properties.systemService` (e.g. `AzureStorage`, `AzureSql`),
2681 + `values[].properties.addressPrefixes[]` (CIDR strings, IPv4 and IPv6
2682 + mixed). Top-level `changeNumber` increments on every publication.
2683 configuration:
2684 file:
2685 name: netflow.yaml
2686 options:
2687 description: |
1772 - Add a named entry under `enrichment.network_sources` pointing at your mirror URL.
2688 + Add a named entry under `enrichment.network_sources` pointing at your
2689 + stable mirror URL (or, with the limitations above, at this week's
2690 + `ServiceTags_Public_<date>.json` URL).
2691 folding:
2692 title: Config options
2693 enabled: true
2694 list:
2695 - name: url
1778 - description: Stable URL to your locally-mirrored Azure Service Tags JSON.
2696 + description: Stable URL to your locally-mirrored Azure Service Tags JSON, or this week's date-stamped Microsoft URL.
2697 default_value: ""
2698 required: true
2699 + - name: interval
2700 + description: How often to fetch. Microsoft publishes weekly; daily is plenty.
2701 + default_value: "60s (loop floor)"
2702 + required: false
2703 + - name: timeout
2704 + description: Per-request timeout. Service Tags JSON files are large (tens of MB) -- give it room.
2705 + default_value: "60s"
2706 + required: false
2707 - name: transform
1782 - description: jq expression mapping the values[] array to per-prefix objects.
2708 + description: jq expression that maps `values[].properties.addressPrefixes[]` into per-prefix objects.
2709 default_value: "."
2710 required: true
2711 examples:
@@ -1787,54 +2713,126 @@ modules:
2713 title: Config
2714 enabled: true
2715 list:
1790 - - name: Internal mirror of Azure Service Tags
2716 + - name: Azure Public via internal mirror -- tag by region and service
2717 folding:
2718 enabled: false
1793 - description: Tag every Azure prefix with tenant=azure plus region/service.
2719 + description: |
2720 + Internal mirror serving the latest Public-cloud Service Tags JSON.
2721 + Tags every Azure prefix with `tenant=azure`, the region, and the
2722 + lowercase systemService name as the role.
2723 config: |
2724 enrichment:
2725 network_sources:
2726 azure:
2727 + url: "https://internal.example/azure-service-tags.json"
2728 + interval: 24h
2729 + timeout: 60s
2730 + transform: |
2731 + .values[]
2732 + | .properties as $p
2733 + | $p.addressPrefixes[]
2734 + | {
2735 + prefix: .,
2736 + tenant: "azure",
2737 + region: ($p.region // ""),
2738 + role: (($p.systemService // "") | ascii_downcase)
2739 + }
2740 + - name: Azure Storage only
2741 + folding:
2742 + enabled: true
2743 + description: Filter to a single Azure systemService for narrower tagging.
2744 + config: |
2745 + enrichment:
2746 + network_sources:
2747 + azure-storage:
2748 url: "https://internal.example/azure-service-tags.json"
2749 interval: 24h
2750 transform: |
2751 .values[]
1802 - | .id as $id
1803 - | .properties.region as $region
1804 - | (.properties.systemService // "") as $service
1805 - | .properties.addressPrefixes[]
2752 + | select(.properties.systemService == "AzureStorage")
2753 + | .properties as $p
2754 + | $p.addressPrefixes[]
2755 | {
2756 prefix: .,
2757 tenant: "azure",
1809 - region: ($region // ""),
1810 - role: ($service | ascii_downcase)
2758 + role: "storage",
2759 + region: ($p.region // "")
2760 + }
2761 + - name: Azure US Government cloud
2762 + folding:
2763 + enabled: true
2764 + description: |
2765 + The US Government cloud is published as a separate JSON file with its
2766 + own download ID (57063). Same schema as the Public cloud; tag tenant
2767 + differently so you can tell them apart in dashboards.
2768 + config: |
2769 + enrichment:
2770 + network_sources:
2771 + azure-usgov:
2772 + url: "https://internal.example/azure-service-tags-usgov.json"
2773 + interval: 24h
2774 + transform: |
2775 + .values[]
2776 + | .properties as $p
2777 + | $p.addressPrefixes[]
2778 + | {
2779 + prefix: .,
2780 + tenant: "azure-usgov",
2781 + region: ($p.region // ""),
2782 + role: (($p.systemService // "") | ascii_downcase)
2783 }
2784 troubleshooting:
2785 problems:
2786 list:
1815 - - name: Empty results
2787 + - name: 404 a week after deploy
2788 + description: |
2789 + You configured `url` against `ServiceTags_Public_<date>.json` directly.
2790 + Microsoft rotates the date stamp weekly, so the URL stops resolving
2791 + within a week. Move to an internal mirror that resolves the latest URL
2792 + each cycle, or to the Service Tag Discovery REST API
2793 + (<https://learn.microsoft.com/en-us/rest/api/virtualnetwork/servicetags/list>)
2794 + fed into a static file.
2795 + - name: Empty results from the transform
2796 description: |
1817 - The Azure Service Tags JSON has nested structure (`values[].properties.addressPrefixes[]`).
1818 - If your jq doesn't unwrap correctly, every fetch yields zero rows and the source
1819 - backs off. Test the jq locally with `jq < azure-service-tags.json`.
2797 + The Service Tags JSON is nested two levels deep
2798 + (`values[].properties.addressPrefixes[]`). If the jq doesn't unwrap
2799 + both levels, every fetch yields zero rows and the source backs off as if
2800 + it errored. Test the jq locally with `jq < azure-service-tags.json` and
2801 + confirm at least one `{prefix, tenant, ...}` object comes out.
2802 + - name: Service Tag API data lags the JSON file
2803 + description: |
2804 + Per Microsoft's note on the Service Tags overview page, "It takes up to
2805 + four weeks for new Service Tag data to propagate in the API results
2806 + across all Azure regions". If you mirror via the REST API, expect newly
2807 + added tags to appear in the JSON download a few weeks before the API,
2808 + not the other way around. For prefix tagging this rarely matters; for
2809 + firewall rules it can.
2810 + - name: TLS verification cannot be disabled
2811 + description: |
2812 + `tls.skip_verify: true` is rejected by validation. Use `tls.ca_file`
2813 + for custom-CA paths (e.g., on an internal mirror with a private CA).
2814 alerts: []
2815 metrics:
2816 folding:
2817 title: Metrics
2818 enabled: false
2819 description: |
1826 - Tags flow records to/from Azure with `*_NET_TENANT`, `*_NET_REGION`, `*_NET_ROLE`.
2820 + Tags flow records with `*_NET_TENANT`, `*_NET_REGION`, `*_NET_ROLE` when the
2821 + source or destination IP matches an Azure prefix. Verify on the Network Flows
2822 + tab via the `*_NET_*` columns. Country / state / city overrides
2823 + (`*_COUNTRY`, `*_GEO_STATE`, `*_GEO_CITY`) are also available if your
2824 + transform sets them, but Service Tags don't carry country/state/city data --
2825 + only `region` codes like `westus` or `northeurope`.
2826 availability: []
2827 scopes: []
1829 -
2828 - meta:
2829 plugin_name: netflow-plugin
2830 module_name: netbox
2831 monitored_instance:
2832 name: NetBox
1835 - link: https://netboxlabs.com/oss/netbox/
2833 + link: https://github.com/netbox-community/netbox
2834 categories:
1837 - - flows.network-identity
2835 + - flows.enrichment-methods
2836 icon_filename: network-wired.svg
2837 keywords:
2838 - netbox
@@ -1842,6 +2840,7 @@ modules:
2840 - dcim
2841 - source of truth
2842 - prefix list
2843 + - network identity
2844 related_resources:
2845 integrations:
2846 list:
@@ -1858,23 +2857,45 @@ modules:
2857 overview:
2858 data_collection:
2859 metrics_description: |
1861 - [NetBox](https://netboxlabs.com/oss/netbox/) is the most widely deployed open-source
1862 - IPAM / DCIM. Many networks already curate prefix metadata there -- tenant, site,
1863 - role, VRF -- and want flow data to inherit those labels automatically rather than
1864 - duplicating them in `netflow.yaml`.
2860 + Annotate network flows with tenant, site, role, VRF, and description metadata
2861 + from NetBox. [NetBox](https://github.com/netbox-community/netbox) is the most
2862 + widely deployed open-source IPAM / DCIM. It is Apache-2.0 licensed and maintained
2863 + by the netbox-community organization (commercial variants -- NetBox Cloud and
2864 + NetBox Enterprise -- are offered by NetBox Labs on top of the same upstream code).
2865 + Operators already curate prefix metadata in NetBox and want flow records to
2866 + inherit those labels automatically rather than maintaining a parallel list in
2867 + `netflow.yaml`.
2868
1866 - This integration polls NetBox's Prefixes API at a configurable interval, transforms
1867 - the response with jq, and labels flow records with the prefix metadata.
2869 + This integration polls NetBox's Prefixes REST API at a configurable interval,
2870 + transforms the response with jq, and labels matching flow prefixes with whatever
2871 + fields you map. NetBox is the authoritative source -- when the same prefix is
2872 + tagged in NetBox and in the static `networks:` block, the static block wins
2873 + (operator override is intentional, see the
2874 + [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment)
2875 + concept page).
2876
1869 - For the full network-identity concept, see
1870 - [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
2877 + Use cases: applying your organisation's data-centre rack labels, tenant names,
2878 + environment tags (prod / staging / dev), and VRF / site identifiers to flows so
2879 + dashboards and queries surface "this is the staging tier in fra1" instead of a
2880 + raw CIDR.
2881 method_description: |
1872 - Periodic HTTPS GET to a NetBox API endpoint with a Bearer token in the
1873 - `Authorization` header. jq transform produces per-prefix objects with the labels
1874 - you want -- typically `tenant.name`, `site.name`, `role.name`, `description`.
2882 + Periodic HTTPS GET to `/api/ipam/prefixes/` on your NetBox host. Authentication is
2883 + a NetBox API token in the `Authorization` header. NetBox supports two token
2884 + formats:
2885
1876 - NetBox paginates results -- there is **no automatic pagination** in this plugin.
1877 - For inventories larger than the default page size (50), wrap NetBox with a
2886 + - **Legacy v1 tokens** (`Authorization: Token <token>`) -- accepted by all NetBox
2887 + versions, simplest to wire up.
2888 + - **v2 tokens** (NetBox 4.x, `Authorization: Bearer nbt_<key>.<token>`) -- the
2889 + prefix `nbt_` and the random key are concatenated with the token via a dot.
2890 +
2891 + The plugin transports either format -- the value is whatever NetBox issued for the
2892 + service account.
2893 +
2894 + The plugin runs the configured `transform` (jaq -- a jq-equivalent) over the
2895 + parsed JSON body and produces per-prefix objects. NetBox's response is paginated;
2896 + the plugin does **not** follow `next` links. Pass `?limit=0` (NetBox 4.x default
2897 + `MAX_PAGE_SIZE` is 1000; setting `0` removes the cap when the server config
2898 + allows) or an explicit `?limit=N` greater than your prefix count, or expose a
2899 server-side aggregator that returns the full list at one URL.
2900 supported_platforms:
2901 include:
@@ -1885,86 +2906,145 @@ modules:
2906 description: ""
2907 default_behavior:
2908 auto_detection:
1888 - description: "Disabled by default. Add an entry under enrichment.network_sources with your NetBox URL and API token."
2909 + description: "Disabled by default. Add an entry under `enrichment.network_sources` with your NetBox URL and API token."
2910 limits:
1890 - description: ""
2911 + description: "Resource use scales with the number of NetBox prefixes returned by the URL and transform. Use NetBox filtering when you only need a subset of VRFs, sites, tenants, or roles."
2912 performance_impact:
1892 - description: ""
2913 + description: "One HTTP request per refresh interval plus a jq transform over the response. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records."
2914 setup:
2915 prerequisites:
2916 list:
2917 - title: NetBox API token with read scope on Prefixes
2918 description: |
1898 - In NetBox, create or use a service account, generate an API token, scope it
1899 - read-only to the Prefixes endpoint. The token goes in the `Authorization` header.
1900 - - title: A bulk endpoint or aggregator
2919 + In NetBox, create or reuse a service-account user, then generate an API token
2920 + under "Admin > Users > Tokens". Restrict the token to read-only and (for
2921 + v4.x) limit the scope to `ipam.view_prefix`. The token value goes in the
2922 + `Authorization` header.
2923 +
2924 + The plugin only reads -- never writes -- so a read-only token is sufficient
2925 + and recommended. Token format depends on the NetBox version:
2926 +
2927 + - NetBox 3.x or earlier: `Token <40-char-hex>` (legacy).
2928 + - NetBox 4.x: either legacy `Token <hex>` or new `Bearer nbt_<key>.<token>`
2929 + (v2 tokens, opt-in).
2930 + - title: Bulk endpoint (`?limit=0` or aggregator)
2931 + description: |
2932 + The plugin fetches a single page. NetBox's default `PAGINATE_COUNT` is 50
2933 + and `MAX_PAGE_SIZE` is 1000. For inventories above 1000 prefixes, either
2934 + raise `MAX_PAGE_SIZE` server-side and pass `?limit=0`, or expose an internal
2935 + aggregator endpoint that walks pagination and returns the full list at one URL.
2936 + - title: NetBox version-aware field mapping
2937 description: |
1902 - The plugin does not paginate. If your NetBox has more prefixes than fit in the
1903 - default page (`?limit=50`), either raise `limit` (`?limit=10000`) or expose
1904 - an internal endpoint that aggregates all pages and serves them at one URL.
2938 + NetBox **4.2** replaced the per-prefix `site` foreign key with a generic
2939 + `scope` field (a prefix can now be scoped to a region, site group, site, or
2940 + location -- not just a site). API responses on 4.2+ omit `site` and expose
2941 + `scope` (read-only) plus `scope_type` / `scope_id`. Adjust your jq accordingly
2942 + -- on 4.2+ use `(.scope.name // "")`, on 4.1 and earlier use
2943 + `(.site.name // "")`.
2944 configuration:
2945 file:
2946 name: netflow.yaml
2947 options:
2948 description: |
2949 Add a named entry under `enrichment.network_sources` pointing at your NetBox.
2950 + The network source configuration accepts `url`, `method`,
2951 + `headers`, `proxy`, `tls`, `timeout`, `interval`, and `transform`. Authentication
2952 + is configured via the generic `headers:` map -- there is no NetBox-specific
2953 + auth helper.
2954 folding:
2955 title: Config options
2956 enabled: true
2957 list:
2958 - name: url
1916 - description: NetBox Prefixes API endpoint, with `?limit=` if needed.
2959 + description: NetBox prefixes API endpoint, including `?limit=` (recommend `?limit=0` on 4.x for full inventory in one shot).
2960 default_value: ""
2961 required: true
2962 - name: headers.Authorization
1920 - description: NetBox API token, prefixed with "Token ".
2963 + description: 'NetBox API token. Use `Token <hex>` for legacy v1 or `Bearer nbt_<key>.<token>` for v4.x v2 tokens.'
2964 default_value: ""
2965 required: true
2966 - name: interval
1924 - description: How often to refresh. NetBox is your source of truth -- 5-15 minutes is typical.
2967 + description: How often to refresh. NetBox is your source of truth; 5 minutes is typical for IPAMs that change frequently, 1 hour is fine for static inventories.
2968 default_value: "60s"
2969 required: false
2970 + - name: timeout
2971 + description: HTTP request timeout. Bump to 30-60s if your NetBox returns thousands of prefixes in one shot.
2972 + default_value: "10s"
2973 + required: false
2974 - name: transform
1928 - description: jq expression mapping `.results[]` (NetBox's response shape) to per-prefix objects.
2975 + description: jq expression mapping NetBox's `.results[]` to per-prefix objects with `prefix` and any of `name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`, `asn`, `asn_name`.
2976 default_value: "."
2977 required: true
2978 + - name: tls.ca_file
2979 + description: Path to your internal CA bundle when NetBox runs behind internal PKI. Disabling verification is rejected by the validator -- use this instead.
2980 + default_value: ""
2981 + required: false
2982 examples:
2983 folding:
2984 title: Config
2985 enabled: true
2986 list:
1936 - - name: NetBox with API token and standard label set
2987 + - name: NetBox 4.x with v2 token, scope-aware mapping
2988 folding:
2989 enabled: false
1939 - description: Tags prefixes with tenant, site, role, and the NetBox description.
2990 + description: |
2991 + Standard NetBox 4.2+ wiring. Maps tenant, role, the new `scope` field
2992 + (covers site / region / site-group / location), and the human-readable
2993 + description. `?limit=0` returns all prefixes in one call when
2994 + `MAX_PAGE_SIZE` is unset on the NetBox server.
2995 config: |
2996 enrichment:
2997 network_sources:
2998 netbox:
1944 - url: "https://netbox.example.internal/api/ipam/prefixes/?limit=10000"
2999 + url: "https://netbox.example.internal/api/ipam/prefixes/?limit=0"
3000 headers:
1946 - Authorization: "Token abcdef0123456789"
3001 + Authorization: "Bearer nbt_AbCdEf0123456789.GhIjKl0123456789"
3002 interval: 5m
3003 timeout: 30s
3004 transform: |
3005 .results[] | {
3006 prefix: .prefix,
3007 tenant: (.tenant.name // ""),
1953 - site: (.site.name // ""),
1954 - role: (.role.name // ""),
1955 - name: .description
3008 + role: (.role.name // ""),
3009 + site: (.scope.name // ""),
3010 + name: (.description // "")
3011 }
1957 - - name: NetBox with mTLS to internal CA
3012 + - name: NetBox 3.x / 4.0 / 4.1 with legacy site field
3013 folding:
3014 enabled: true
1960 - description: When NetBox is behind your internal PKI; use tls.ca_file plus client cert.
3015 + description: |
3016 + Pre-4.2 NetBox still exposes `site` directly on the Prefix object. Use a
3017 + legacy `Token` header and read `.site.name` instead of `.scope.name`.
3018 config: |
3019 enrichment:
3020 network_sources:
3021 netbox:
3022 url: "https://netbox.example.internal/api/ipam/prefixes/?limit=10000"
3023 headers:
1967 - Authorization: "Token abcdef0123456789"
3024 + Authorization: "Token abcdef0123456789abcdef0123456789abcdef01"
3025 + interval: 15m
3026 + transform: |
3027 + .results[] | {
3028 + prefix: .prefix,
3029 + tenant: (.tenant.name // ""),
3030 + role: (.role.name // ""),
3031 + site: (.site.name // ""),
3032 + name: (.description // "")
3033 + }
3034 + - name: NetBox behind internal PKI (mTLS)
3035 + folding:
3036 + enabled: true
3037 + description: |
3038 + When NetBox is fronted by internal PKI. `tls.verify: false` is rejected at
3039 + config-load -- supply your CA explicitly and (optionally) a client
3040 + certificate.
3041 + config: |
3042 + enrichment:
3043 + network_sources:
3044 + netbox:
3045 + url: "https://netbox.example.internal/api/ipam/prefixes/?limit=0"
3046 + headers:
3047 + Authorization: "Token abcdef0123456789abcdef0123456789abcdef01"
3048 interval: 5m
3049 tls:
3050 enable: true
@@ -1975,42 +3055,91 @@ modules:
3055 .results[] | {
3056 prefix: .prefix,
3057 tenant: (.tenant.name // ""),
1978 - site: (.site.name // ""),
1979 - role: (.role.name // ""),
1980 - name: .description
3058 + role: (.role.name // ""),
3059 + site: (.scope.name // ""),
3060 + name: (.description // "")
3061 + }
3062 + - name: VRF-aware mapping (multi-tenant networks)
3063 + folding:
3064 + enabled: true
3065 + description: |
3066 + When the same RFC1918 prefix appears in multiple VRFs, fold the VRF name
3067 + into the friendly `name` so dashboards disambiguate. Note: the plugin keys
3068 + on `prefix` only -- if two rows share a CIDR, the last one wins. Filter the
3069 + NetBox query (`?vrf_id=N`) to scope the export.
3070 + config: |
3071 + enrichment:
3072 + network_sources:
3073 + netbox-prod-vrf:
3074 + url: "https://netbox.example.internal/api/ipam/prefixes/?limit=0&vrf_id=12"
3075 + headers:
3076 + Authorization: "Token abcdef0123456789abcdef0123456789abcdef01"
3077 + interval: 5m
3078 + transform: |
3079 + .results[] | {
3080 + prefix: .prefix,
3081 + tenant: (.tenant.name // ""),
3082 + role: (.role.name // ""),
3083 + site: (.scope.name // ""),
3084 + name: ((.vrf.name // "default") + ": " + (.description // ""))
3085 }
3086 troubleshooting:
3087 problems:
3088 list:
1985 - - name: Only first page of results loaded
3089 + - name: Only first 50 prefixes loaded
3090 description: |
1987 - NetBox paginates by default at 50 results. The plugin does not follow `next`
1988 - links. Use `?limit=10000` (or the actual count) on the URL, or expose an
1989 - aggregating endpoint server-side.
1990 - - name: Token missing or wrong scope
3091 + NetBox's default `PAGINATE_COUNT` is 50 and the plugin does not follow `next`
3092 + links. Pass `?limit=0` (NetBox 4.x removes the cap when `MAX_PAGE_SIZE` is
3093 + `0` server-side) or `?limit=N` larger than your inventory. For inventories
3094 + above the server's `MAX_PAGE_SIZE` (default 1000), expose a server-side
3095 + aggregator endpoint.
3096 + - name: 401 / 403 from NetBox
3097 description: |
1992 - NetBox returns 403 silently consumed by the plugin's HTTP error path. Watch
1993 - the journal for `network-sources` warnings; verify with curl:
3098 + Token missing, expired, or wrong format. Verify with:
3099 `curl -H "Authorization: Token <tok>" https://netbox/api/ipam/prefixes/`.
3100 + On NetBox 4.x check whether the token is v1 (`Token <hex>`) or v2
3101 + (`Bearer nbt_<key>.<token>`) and use the matching header. Watch the journal
3102 + for `network-sources` warnings -- HTTP errors are logged there as
3103 + refresh-failed warnings.
3104 + - name: site is empty after upgrade to NetBox 4.2
3105 + description: |
3106 + NetBox 4.2 removed `site` from the Prefix model and replaced it with `scope`
3107 + (a generic foreign key that can reference region / site group / site /
3108 + location). Update the jq from `(.site.name // "")` to `(.scope.name // "")`.
3109 + Alternatively, key on `(.scope.name // .site.name // "")` so the same config
3110 + works across the upgrade boundary.
3111 + - name: VRF collisions (same CIDR in multiple VRFs)
3112 + description: |
3113 + The plugin's network-attributes trie keys on `prefix` only -- there is no VRF
3114 + dimension on the flow side. If NetBox lists `10.0.0.0/24` in three VRFs, only
3115 + the last row wins. Filter the export with `?vrf_id=N` per data-plane and run
3116 + one named source per VRF, or fold the VRF name into the friendly `name` so
3117 + dashboards disambiguate.
3118 + - name: Empty result triggers backoff
3119 + description: |
3120 + An empty `results` array (legitimate state for a freshly-installed NetBox)
3121 + is treated as a fetch failure by the cross-cutting source loop and triggers
3122 + exponential backoff. Add at least one synthetic prefix (e.g. a `RFC1918`
3123 + container) so the response is never empty.
3124 alerts: []
3125 metrics:
3126 folding:
3127 title: Metrics
3128 enabled: false
3129 description: |
2001 - Tags flow records with the labels you mapped from NetBox -- typically `*_NET_TENANT`,
2002 - `*_NET_SITE`, `*_NET_ROLE`, `*_NET_NAME`. Verify on the Network Flows tab.
3130 + Tags flow records with the labels you mapped from NetBox -- typically
3131 + `*_NET_TENANT`, `*_NET_SITE`, `*_NET_ROLE`, `*_NET_NAME`. Verify on the Network
3132 + Flows tab.
3133 availability: []
3134 scopes: []
2005 -
3135 - meta:
3136 plugin_name: netflow-plugin
3137 module_name: generic-ipam
3138 monitored_instance:
3139 name: Generic JSON-over-HTTP IPAM
2011 - link: https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity
3140 + link: https://learn.netdata.cloud/docs/network-flows/enrichment
3141 categories:
2013 - - flows.network-identity
3142 + - flows.enrichment-methods
3143 icon_filename: network-wired.svg
3144 keywords:
3145 - ipam
@@ -2018,8 +3147,10 @@ modules:
3147 - infoblox
3148 - bluecat
3149 - phpipam
3150 + - servicenow
3151 - custom
3152 - prefix list
3153 + - network identity
3154 related_resources:
3155 integrations:
3156 list:
@@ -2036,19 +3167,32 @@ modules:
3167 overview:
3168 data_collection:
3169 metrics_description: |
2039 - The catch-all integration. Any IPAM, CMDB, or service that exposes prefix metadata
2040 - via an HTTP-fetchable JSON endpoint can plug into Netdata's flow enrichment via this
2041 - mechanism. Examples: Infoblox WAPI, BlueCat REST API, phpIPAM, internal-built CMDB
2042 - endpoints, ServiceNow CMDB queries, custom Lambda functions producing JSON.
3170 + Annotate network flows with network-identity labels from any JSON-over-HTTP IPAM
3171 + or CMDB endpoint. The plugin does not care WHO produces the JSON -- it cares
3172 + about the JSON shape after your `transform` (a jq expression) runs over it.
3173 + Examples: Infoblox WAPI, BlueCat REST API, phpIPAM, ServiceNow CMDB queries, an
3174 + internal aggregator, a Lambda function, a static file served from S3, a
3175 + hand-rolled CMDB.
3176
2044 - You define the URL, the HTTP method, headers (for auth), and a jq transform that
2045 - converts the response into objects with `prefix` + label fields.
3177 + This card is also the **reference description** of how all the other Network
3178 + Identity cards work under the hood. AWS IP Ranges, GCP IP Ranges, Azure IP Ranges
3179 + and NetBox are all special-cased instances of this same mechanism: each is just
3180 + a known URL plus a recommended jq `transform`. Read this card to understand the
3181 + generic shape, then read the per-source cards for the URL and transform that fit
3182 + that specific provider.
3183
2047 - For the full network-identity concept, see
2048 - [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment/network-identity).
3184 + For the full network-identity concept (merge order vs GeoIP and static, what
3185 + fields you can populate, why TLS verification cannot be disabled), see
3186 + [Network Identity](https://learn.netdata.cloud/docs/network-flows/enrichment).
3187 method_description: |
2050 - Periodic HTTPS GET (or POST) to a configured URL with optional headers, optional
2051 - custom CA / mTLS, jq transform of the response, merge into network-attributes trie.
3188 + The plugin issues a periodic HTTP request (GET by default, POST optionally) to
3189 + your configured URL with the headers you specify, parses the response as JSON,
3190 + runs your `transform` jq expression (compiled by the
3191 + [jaq](https://github.com/01mf02/jaq) library) over the parsed body, and merges
3192 + the resulting per-prefix rows into the network-attributes trie.
3193 +
3194 + The same flow applies to every JSON-over-HTTP source; the integration-specific
3195 + part is the URL and the `transform` expression.
3196 supported_platforms:
3197 include:
3198 - Linux
@@ -2058,84 +3202,138 @@ modules:
3202 description: ""
3203 default_behavior:
3204 auto_detection:
2061 - description: "Disabled by default. Add an entry per IPAM source under enrichment.network_sources."
3205 + description: "Disabled by default. Add a named entry under `enrichment.network_sources` for each IPAM/CMDB/endpoint you want to import."
3206 limits:
2063 - description: ""
3207 + description: "Resource use scales with response size, transform complexity, refresh interval, and the number of emitted prefixes. Empty transform output is treated as a fetch failure."
3208 performance_impact:
2065 - description: ""
3209 + description: "One HTTP request per refresh interval plus a jq transform over the response. Runtime enrichment does prefix matching for source and destination IPs, and cost scales with the number of loaded network-source records."
3210 setup:
3211 prerequisites:
3212 list:
3213 - title: An HTTP/HTTPS endpoint returning JSON
3214 description: |
2071 - The endpoint must produce a parseable JSON document. The plugin only supports
2072 - GET and POST. There is no pagination, no cursor following, no OAuth flow --
2073 - if your IPAM needs those, wrap it in an internal aggregator.
3215 + The endpoint must respond with a parseable JSON document (the plugin sets
3216 + `Accept: application/json`). Only GET and POST are accepted as request
3217 + methods. There is no pagination, no
3218 + cursor following, no `Link: rel=next` handling -- the fetch is one-shot
3219 + per cycle. If your IPAM paginates, expose a bulk endpoint or wrap it in a
3220 + server-side aggregator that returns the full list at one URL.
3221 - title: Authentication via headers
3222 description: |
2076 - The plugin has no built-in auth helpers. Set whatever the API needs -- bearer
2077 - tokens, basic-auth header, custom API-key headers -- via `headers:`. Store
2078 - tokens carefully; they're written into the YAML.
3223 + The plugin has no built-in OAuth flow, basic-auth helper, or token
3224 + refresh. Whatever the API needs (bearer tokens, API keys, custom header
3225 + names, basic-auth realms) goes into the `headers:` map. The header map is
3226 + an arbitrary string-to-string mapping, so any single-shot scheme works.
3227 + For short-lived tokens, refresh them outside Netdata and reload the
3228 + plugin config.
3229 + - title: A POST endpoint must accept an empty body
3230 + description: |
3231 + When `method: POST` is configured, the plugin sends the request with the
3232 + configured headers but **no request body**. If your CMDB requires a JSON query body to return prefixes,
3233 + wrap it server-side with an endpoint that accepts GET (or POST with no
3234 + body) and returns the full prefix set.
3235 configuration:
3236 file:
3237 name: netflow.yaml
3238 options:
3239 description: |
2084 - Add a named entry under `enrichment.network_sources`. The keys below are the
2085 - full set of options.
3240 + Add a named entry under `enrichment.network_sources`. Unknown keys cause a
3241 + config error.
3242 folding:
3243 title: Config options
3244 enabled: true
3245 list:
3246 - name: url
2091 - description: HTTP/HTTPS endpoint.
3247 + description: HTTP/HTTPS endpoint. Required (a non-empty URL is enforced by validation).
3248 default_value: ""
3249 required: true
3250 - name: method
2095 - description: GET or POST.
3251 + description: HTTP method. Only `GET` and `POST` are accepted; anything else fails validation. Note that `POST` is sent with no request body.
3252 default_value: "GET"
3253 required: false
3254 + - name: headers
3255 + description: |
3256 + Map of additional HTTP request headers. Use this for any authentication
3257 + scheme (`Authorization: Bearer ...`, `token: ...`, custom API-key
3258 + headers, basic-auth realms encoded explicitly, etc.). Values are
3259 + passed through verbatim.
3260 + default_value: "{}"
3261 + required: false
3262 - name: interval
2099 - description: Refresh interval (loop floors at 60s).
3263 + description: Refresh cadence. Values below 60 seconds are accepted, but refreshes still run no faster than once per minute. Pick the value that matches how often your IPAM actually changes (5-15 minutes for a curated CMDB; daily for slow-moving prefix lists).
3264 default_value: "60s"
3265 required: false
3266 - name: timeout
2103 - description: Per-request timeout.
3267 + description: Per-request timeout. Must be greater than 0.
3268 default_value: "60s"
3269 required: false
2106 - - name: headers
2107 - description: Map of additional HTTP request headers (e.g., authentication).
2108 - default_value: "{}"
3270 + - name: proxy
3271 + description: Whether to honor the system HTTP/HTTPS proxy environment variables. Set to `false` to bypass the proxy for this source (useful when the IPAM is on the internal network and the proxy is for outbound traffic only).
3272 + default_value: "true"
3273 required: false
2110 - - name: transform
2111 - description: jq expression converting response to {prefix, name?, role?, site?, region?, country?, state?, city?, tenant?, asn?, asn_name?} stream.
2112 - default_value: "."
2113 - required: true
3274 - name: tls.enable
2115 - description: Use custom TLS settings (custom CA, mTLS).
3275 + description: Enables custom TLS settings (custom CA bundle, mTLS client certificate). Must be `true` whenever any of `tls.ca_file`, `tls.cert_file`, `tls.key_file` is set.
3276 + default_value: "false"
3277 + required: false
3278 + - name: tls.verify
3279 + description: Certificate verification toggle. **Setting this to `false` is rejected by validation** -- network-identity data flows directly into security-relevant enrichment, so MITM-able responses are not allowed. Use `tls.ca_file` to trust internal CAs instead.
3280 + default_value: "true"
3281 + required: false
3282 + - name: tls.skip_verify
3283 + description: |
3284 + Legacy alias for the same intent as `tls.verify: false`. **Setting this
3285 + to `true` is rejected by validation.** Use `tls.ca_file`.
3286 default_value: "false"
3287 required: false
3288 - name: tls.ca_file
2119 - description: PEM file with the CA bundle.
3289 + description: PEM file with the CA bundle to trust for this endpoint (instead of, or in addition to, the system roots). The recommended way to talk to an internal IPAM behind your own PKI.
3290 default_value: ""
3291 required: false
3292 - name: tls.cert_file
2123 - description: PEM file with the client certificate (mTLS).
3293 + description: PEM file with the client certificate, for mTLS-protected endpoints.
3294 default_value: ""
3295 required: false
3296 - name: tls.key_file
2127 - description: PEM file with the client private key.
3297 + description: PEM file with the client private key. Required only when the key is in a separate file from the certificate; if omitted, the cert file is reused. `tls.cert_file` must also be set whenever `tls.key_file` is set.
3298 default_value: ""
3299 required: false
3300 + - name: transform
3301 + description: |
3302 + jq expression compiled by [jaq](https://github.com/01mf02/jaq). Receives the entire parsed JSON body once and must produce a **stream of objects**, where each object has at minimum a `prefix` field (a CIDR string -- `IPv4/len` or `IPv6/len`) plus any of the optional attribute fields: `name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`, `asn`, `asn_name`. Any field not produced is treated as empty. The transform compiles at startup -- a syntax error fails the config load. An empty stream at runtime is treated as a fetch failure and triggers backoff.
3303 + default_value: "."
3304 + required: true
3305 examples:
3306 folding:
3307 title: Config
3308 enabled: true
3309 list:
2135 - - name: phpIPAM with API token
3310 + - name: Flat list with `{prefix, name, env}`
3311 folding:
3312 enabled: false
2138 - description: "phpIPAM exposes `/api/<APP>/subnets/`. Replace `<APP>` with your phpIPAM app name. Use the standard transform."
3313 + description: |
3314 + The simplest IPAM shape: a top-level array of subnets, each with a CIDR
3315 + string and a few labels. Maps `env` -> tenant and the `name` -> NET_NAME.
3316 + config: |
3317 + enrichment:
3318 + network_sources:
3319 + custom_ipam:
3320 + url: "https://ipam.corp.example/api/v1/prefixes"
3321 + headers:
3322 + Authorization: "Bearer abcdef0123456789"
3323 + interval: 5m
3324 + transform: |
3325 + .[] | {
3326 + prefix: .prefix,
3327 + name: .name,
3328 + tenant: .env
3329 + }
3330 + - name: phpIPAM with API token
3331 + folding:
3332 + enabled: true
3333 + description: |
3334 + phpIPAM exposes `/api/<APP>/subnets/`. Replace `<APP>` with your phpIPAM
3335 + application name. phpIPAM splits the address into a base and a mask --
3336 + the transform reassembles them into a CIDR string.
3337 config: |
3338 enrichment:
3339 network_sources:
@@ -2151,31 +3349,40 @@ modules:
3349 tenant: (.custom_tenant // ""),
3350 site: (.location.name // "")
3351 }
2154 - - name: Custom internal CMDB (POST with body)
3352 + - name: Hierarchical CMDB export
3353 folding:
3354 enabled: true
2157 - description: When your CMDB requires POST with a query body. Define `method` and append the body via headers/url. The plugin's body support is limited -- prefer GET endpoints when possible.
3355 + description: |
3356 + A CMDB that returns nested objects -- top-level sites, each with a list
3357 + of vlans, each with a list of prefixes. Flattens to one row per prefix,
3358 + inheriting site and role labels from the parent.
3359 config: |
3360 enrichment:
3361 network_sources:
3362 cmdb:
2162 - url: "https://cmdb.example/query/networks"
2163 - method: POST
3363 + url: "https://cmdb.example/export/networks.json"
3364 headers:
2165 - Authorization: "Bearer ..."
2166 - Content-Type: "application/json"
2167 - interval: 30m
3365 + Authorization: "Bearer <CMDB_TOKEN>"
3366 + interval: 15m
3367 transform: |
2169 - .results[] | {
2170 - prefix: .cidr,
2171 - tenant: .organization,
2172 - site: .datacenter,
2173 - role: .purpose
2174 - }
2175 - - name: Internal IPAM with mTLS
3368 + .sites[] as $site
3369 + | $site.vlans[] as $vlan
3370 + | $vlan.prefixes[]
3371 + | {
3372 + prefix: .cidr,
3373 + name: .label,
3374 + site: $site.name,
3375 + region: $site.region,
3376 + role: $vlan.role,
3377 + tenant: $vlan.tenant
3378 + }
3379 + - name: Internal IPAM behind mTLS
3380 folding:
3381 enabled: true
2178 - description: When the IPAM is behind your internal PKI.
3382 + description: |
3383 + When the IPAM is fronted by your internal PKI and requires a client
3384 + certificate. `tls.enable: true` activates the custom TLS path; `tls.verify`
3385 + must remain true (cannot be disabled).
3386 config: |
3387 enrichment:
3388 network_sources:
@@ -2193,29 +3400,1138 @@ modules:
3400 name: .label,
3401 tenant: .tenant
3402 }
3403 + - name: Source with the system proxy bypassed
3404 + folding:
3405 + enabled: true
3406 + description: |
3407 + When the agent host has an outbound HTTP proxy configured but the IPAM
3408 + lives on the internal network and should be reached directly.
3409 + config: |
3410 + enrichment:
3411 + network_sources:
3412 + internal_ipam:
3413 + url: "https://ipam.internal/api/networks"
3414 + proxy: false
3415 + headers:
3416 + Authorization: "Bearer ..."
3417 + interval: 5m
3418 + transform: |
3419 + .[] | { prefix: .cidr, name: .label }
3420 troubleshooting:
3421 problems:
3422 list:
3423 - name: Endpoint requires pagination
3424 description: |
2201 - The plugin does not paginate. Either raise the page size to cover your inventory,
2202 - or wrap the endpoint with an internal aggregator that returns all results at one URL.
3425 + The plugin does not paginate. Either raise the page size in the URL to
3426 + cover your full inventory, or wrap the endpoint with a server-side
3427 + aggregator that returns all results at one URL. There is no built-in
3428 + `Link: rel=next` follower.
3429 + - name: POST endpoint requires a request body
3430 + description: |
3431 + The plugin's POST request is sent with no body. If your CMDB requires a
3432 + JSON query body to return prefixes, wrap it server-side with an endpoint
3433 + that accepts GET (or accepts POST with no body) and returns the full prefix
3434 + set.
3435 - name: TLS verification cannot be disabled
3436 description: |
2205 - `tls.skip_verify` and `tls.verify: false` are rejected by validation. Use
2206 - `tls.ca_file` to trust internal CAs.
3437 + `tls.verify: false` and `tls.skip_verify: true` are both rejected during
3438 + configuration validation. Use `tls.ca_file` to trust internal CAs. This is
3439 + deliberate -- network-identity rows feed enrichment used in security
3440 + investigations and capacity decisions, where silently accepting MITM-able
3441 + responses would corrupt every downstream analysis.
3442 - name: Empty result back-off
3443 description: |
2209 - An empty jq result is treated as a fetch failure. If your IPAM legitimately
2210 - returns no prefixes (quiet state), the source backs off as if it errored.
2211 - Workaround: have the upstream return at least one synthetic prefix.
3444 + An empty stream from the jq transform is treated as a fetch failure.
3445 + The source then backs
3446 + off exponentially -- starting at `interval / 10` (floor 1s), doubling on
3447 + each consecutive failure, and capped at the regular `interval`. On the
3448 + next successful non-empty fetch the cadence resets to `interval`. If your
3449 + IPAM legitimately has no prefixes (a quiet state), have the upstream
3450 + return at least one synthetic prefix so the source does not back off.
3451 + - name: Refresh appears slower than configured
3452 + description: |
3453 + The fetch loop floors the configured `interval` at 60 seconds. Configuring
3454 + `interval: 5s` does not produce a 5-second loop -- it produces a 60-second
3455 + loop. Pick a value at or above 60s that matches how often your IPAM
3456 + actually changes (5-15 minutes is typical for a curated CMDB).
3457 + - name: Unknown config keys cause errors
3458 + description: |
3459 + The config struct uses `deny_unknown_fields`. Typos like `headres:` or a
3460 + non-existent option fail config load with a parse error rather than being
3461 + silently ignored. Check the `enrichment.network_sources.<name>` schema
3462 + listed under "Config options".
3463 + - name: JSON parse errors are silent in the dashboard
3464 + description: |
3465 + Decode failures (HTTP error, JSON parse error, jq runtime error, schema
3466 + mismatch on `prefix`) are logged but do not surface in the dashboard.
3467 + Watch the Netdata journal for warnings:
3468 + `journalctl --namespace netdata | grep network_sources`.
3469 + - name: Prefer explicit authorization headers over URL credentials
3470 + description: |
3471 + URLs with embedded credentials (`https://user:pass@host`) are converted to
3472 + HTTP Basic authentication by the HTTP client. Prefer `headers:` for clarity
3473 + and to avoid storing credentials in URLs -- e.g.
3474 + `headers: { Authorization: "Basic dXNlcjpwYXNz" }` for HTTP basic-auth.
3475 + alerts: []
3476 + metrics:
3477 + folding:
3478 + title: Metrics
3479 + enabled: false
3480 + description: |
3481 + Tags flow records with whatever labels your jq transform emits -- typically
3482 + `*_NET_NAME`, `*_NET_TENANT`, `*_NET_SITE`, `*_NET_ROLE`, `*_NET_REGION`. Verify
3483 + on the Network Flows view via the `*_NET_*` columns.
3484 + availability: []
3485 + scopes: []
3486 + - meta:
3487 + plugin_name: netflow-plugin
3488 + module_name: static-metadata
3489 + monitored_instance:
3490 + name: Static Metadata
3491 + link: https://learn.netdata.cloud/docs/network-flows/enrichment
3492 + categories:
3493 + - flows.enrichment-methods
3494 + icon_filename: network-wired.svg
3495 + keywords:
3496 + - static metadata
3497 + - exporters
3498 + - interfaces
3499 + - networks
3500 + - cidr
3501 + - sampling rate override
3502 + - asn override
3503 + - country override
3504 + - tenant
3505 + - site
3506 + - region
3507 + - role
3508 + related_resources:
3509 + integrations:
3510 + list:
3511 + - plugin_name: netflow-plugin
3512 + module_name: generic-ipam
3513 + - plugin_name: netflow-plugin
3514 + module_name: netbox
3515 + - plugin_name: netflow-plugin
3516 + module_name: dbip
3517 + info_provided_to_referring_integrations:
3518 + description: ""
3519 + overview:
3520 + data_collection:
3521 + metrics_description: |
3522 + Annotate network flows with operator-defined labels for exporters, interfaces,
3523 + networks, and sampling rates. Static Metadata is a YAML-defined enrichment source
3524 + that lives entirely in `netflow.yaml`. Use it when there is no IPAM, no CMDB, and
3525 + no MMDB to query -- you simply know your own networks, your own routers, and what
3526 + the interfaces on those routers do, and you want those labels visible on the
3527 + Network Flows view.
3528 +
3529 + Three independent surfaces are configured under this card, each populating a
3530 + different set of flow-record fields:
3531 +
3532 + - `enrichment.metadata_static.exporters.<ip-or-cidr>` -- per-exporter labels
3533 + (matched against the source IP of the UDP datagram) and per-interface labels
3534 + (matched against the ifIndex from the flow record).
3535 + - `enrichment.networks.<cidr>` -- labels for arbitrary CIDR blocks (your own
3536 + corp ranges, customer ranges, public blocks you operate). Matched against the
3537 + flow's source and destination IPs.
3538 + - `enrichment.override_sampling_rate.<cidr>` -- per-prefix sampling-rate
3539 + substitution for exporters that do not communicate their rate (e.g. NetFlow
3540 + v7, which has no sampling field, or a v9 exporter that never sends a Sampling
3541 + Options Template).
3542 +
3543 + Fields populated:
3544 +
3545 + - From `metadata_static.exporters` (per-exporter):
3546 + `EXPORTER_NAME`, `EXPORTER_GROUP`, `EXPORTER_ROLE`, `EXPORTER_SITE`,
3547 + `EXPORTER_REGION`, `EXPORTER_TENANT`.
3548 + - From `metadata_static.exporters.if_indexes` (per-interface):
3549 + `IN_IF_NAME` / `OUT_IF_NAME`, `IN_IF_DESCRIPTION` / `OUT_IF_DESCRIPTION`,
3550 + `IN_IF_SPEED` / `OUT_IF_SPEED` (in **bits per second**), `IN_IF_PROVIDER` /
3551 + `OUT_IF_PROVIDER`, `IN_IF_CONNECTIVITY` / `OUT_IF_CONNECTIVITY`,
3552 + `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` (`1` = external, `2` = internal,
3553 + `0`/omitted = removed from output).
3554 + - From `enrichment.networks` (per-CIDR):
3555 + `SRC_NET_NAME` / `DST_NET_NAME`, `SRC_NET_ROLE` / `DST_NET_ROLE`,
3556 + `SRC_NET_SITE` / `DST_NET_SITE`, `SRC_NET_REGION` / `DST_NET_REGION`,
3557 + `SRC_NET_TENANT` / `DST_NET_TENANT`, plus overrides for the GeoIP-derived
3558 + `SRC_COUNTRY` / `DST_COUNTRY`, `SRC_GEO_STATE` / `DST_GEO_STATE`,
3559 + `SRC_GEO_CITY` / `DST_GEO_CITY`, `SRC_GEO_LATITUDE` / `DST_GEO_LATITUDE`,
3560 + `SRC_GEO_LONGITUDE` / `DST_GEO_LONGITUDE`. Setting `asn:` on a `networks`
3561 + entry overrides the AS *number* via the same merge path; the AS *name*
3562 + still comes from the ASN database (see "Composition with dynamic sources"
3563 + below).
3564 + - From `override_sampling_rate` (longest-prefix match against the exporter IP):
3565 + `SAMPLING_RATE` is set unconditionally when an override prefix matches. This
3566 + is different from `default_sampling_rate`, which only applies when the flow
3567 + does not already carry a rate.
3568 +
3569 + For the cross-cutting Enrichment concept (provider chains, merge order rules,
3570 + how static composes with dynamic sources, the static-blocks-classifiers
3571 + interaction, and shared failure modes), see
3572 + [Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
3573 + method_description: |
3574 + Edit `netflow.yaml`, restart the plugin, and the YAML-defined data is loaded
3575 + into the same in-memory tries the GeoIP, IPAM, and BGP enrichment paths read
3576 + from. There is no network access, no file watching, and no hot reload --
3577 + changes take effect on the next plugin startup.
3578 + supported_platforms:
3579 + include:
3580 + - Linux
3581 + exclude: []
3582 + multi_instance: false
3583 + additional_permissions:
3584 + description: ""
3585 + default_behavior:
3586 + auto_detection:
3587 + description: "Disabled by default. Add entries under `enrichment.metadata_static`, `enrichment.networks`, and / or `enrichment.override_sampling_rate` to populate it."
3588 + limits:
3589 + description: "Resource use scales with the number of exporter, interface, network, and sampling-override entries. Keep CIDR ranges and interface maps specific enough to match the routers you actually export."
3590 + performance_impact:
3591 + description: "Static lookups are local map/trie lookups during enrichment. Cost is usually small compared with flow decode and journal writes, but very large maps add memory and lookup work."
3592 + setup:
3593 + prerequisites:
3594 + list:
3595 + - title: An understanding of your own topology
3596 + description: |
3597 + You need to know which IP addresses your routers / switches / firewalls
3598 + export from, the integer ifIndex values they use for each interface, and
3599 + the CIDR ranges that belong to each of your sites / tenants / roles. None
3600 + of this is auto-discovered -- this card is the "I will tell you" path.
3601 + For sources that auto-discover the same data, see Generic IPAM, NetBox,
3602 + and the cloud IP-range cards.
3603 + - title: Persistent ifIndex values
3604 + description: |
3605 + `if_indexes` keys are the integer ifIndex the router puts in the flow
3606 + record. Some platforms reassign ifIndex on line-card reseat or stack
3607 + rebuild. After hardware changes, audit the labels -- a stale ifIndex
3608 + entry silently no longer applies. The `default` interface block is used
3609 + instead, or no labels are written when `skip_missing_interfaces: true` is set.
3610 + configuration:
3611 + file:
3612 + name: netflow.yaml
3613 + options:
3614 + description: |
3615 + All keys live under `enrichment:` in `netflow.yaml`. Unknown keys fail config
3616 + load with a parse error.
3617 + folding:
3618 + title: Config options
3619 + enabled: true
3620 + list:
3621 + - name: enrichment.metadata_static.exporters
3622 + description: |
3623 + Map keyed by exporter IP or CIDR. Longest-prefix match wins. A `/32`
3624 + key and a bare IP are equivalent. Each entry holds exporter-level
3625 + labels (`name`, `group`, `role`, `site`, `region`, `tenant`), an
3626 + optional `default` interface block, an `if_indexes` map keyed by
3627 + integer ifIndex, and `skip_missing_interfaces`.
3628 + default_value: "{}"
3629 + required: false
3630 + - name: enrichment.metadata_static.exporters.&lt;key&gt;.if_indexes
3631 + description: |
3632 + Map keyed by integer ifIndex (the value the router puts in the flow
3633 + record). Each entry holds `name`, `description`, `speed` (bits per
3634 + second), `provider`, `connectivity`, and `boundary`.
3635 + default_value: "{}"
3636 + required: false
3637 + - name: enrichment.metadata_static.exporters.&lt;key&gt;.default
3638 + description: |
3639 + Interface block applied to flow records whose ifIndex is not present
3640 + in `if_indexes`. Same shape as an `if_indexes` entry. Ignored when
3641 + `skip_missing_interfaces: true` is set.
3642 + default_value: "{}"
3643 + required: false
3644 + - name: enrichment.metadata_static.exporters.&lt;key&gt;.skip_missing_interfaces
3645 + description: |
3646 + When `true`, flow records whose ifIndex is not in `if_indexes` get no
3647 + interface labels at all (not even from `default`). Useful when you only
3648 + care about a known set of WAN interfaces and want unknown ones to stay
3649 + blank instead of inheriting a placeholder.
3650 + default_value: "false"
3651 + required: false
3652 + - name: enrichment.metadata_static.exporters.&lt;key&gt;.if_indexes.&lt;n&gt;.boundary
3653 + description: |
3654 + Interface boundary marker. Accepts the integers `0` (undefined),
3655 + `1` (external -- faces the outside world: Internet, peer, transit),
3656 + `2` (internal -- faces your own infrastructure), or the case-insensitive
3657 + strings `"undefined"`, `"external"`, `"internal"`. Any other value fails config load. Filtering for `IN_IF_BOUNDARY=1`
3658 + cleanly gives you "traffic that arrived from outside".
3659 + default_value: "0"
3660 + required: false
3661 + - name: enrichment.metadata_static.exporters.&lt;key&gt;.if_indexes.&lt;n&gt;.speed
3662 + description: |
3663 + Interface speed in **bits per second**. A 1 Gbps interface is
3664 + `1000000000`, not `1000` and not `1000000`. Operators thinking in
3665 + megabits get the value wrong by a factor of 1000 to 1000000.
3666 + A `0` value means "not set" and removes the field from the output.
3667 + default_value: "0"
3668 + required: false
3669 + - name: enrichment.networks
3670 + description: |
3671 + Map keyed by CIDR. Longest-prefix match contributes the most-specific
3672 + fields; less-specific containing prefixes contribute their non-empty
3673 + fields too. The same merge rule is used by `network_sources`. Each value is either a string
3674 + (shorthand for `name:`) or a map with `name`, `role`, `site`, `region`,
3675 + `country`, `state`, `city`, `latitude`, `longitude`, `tenant`, `asn`.
3676 + default_value: "{}"
3677 + required: false
3678 + - name: enrichment.networks.&lt;cidr&gt;.asn
3679 + description: |
3680 + Forces the AS *number* for traffic in this prefix, overriding whatever
3681 + the `asn_providers` chain computed. The AS *name* is still resolved
3682 + from the ASN MMDB -- there is no `asn_name` config field. See the ASN
3683 + section of [Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
3684 + default_value: "0"
3685 + required: false
3686 + - name: enrichment.networks.&lt;cidr&gt;.latitude / longitude
3687 + description: |
3688 + Per-CIDR coordinate override. Out-of-range values (latitude not in
3689 + [-90, 90] or longitude not in [-180, 180]) and non-finite values are
3690 + silently coerced to empty strings -- the field is dropped, no error.
3691 + Validate input out of band if your data matters.
3692 + default_value: "null"
3693 + required: false
3694 + - name: enrichment.default_sampling_rate
3695 + description: |
3696 + Global fallback applied only when the flow record does not already
3697 + carry a sampling rate. Either a single integer or a CIDR-keyed map
3698 + (longest-prefix match against the exporter IP).
3699 + default_value: "0"
3700 + required: false
3701 + - name: enrichment.override_sampling_rate
3702 + description: |
3703 + Per-exporter substitution that **always** wins when its prefix matches
3704 + the exporter IP, regardless of what the flow record carried. Either a
3705 + single integer or a CIDR-keyed map.
3706 + default_value: "{}"
3707 + required: false
3708 + examples:
3709 + folding:
3710 + title: Config
3711 + enabled: true
3712 + list:
3713 + - name: Naming exporters and their interfaces
3714 + folding:
3715 + enabled: false
3716 + description: |
3717 + One edge router and one spine. The `default` block applies to any
3718 + ifIndex not listed under `if_indexes` -- handy when you only care about
3719 + a few WAN ports and want everything else to inherit a placeholder.
3720 + config: |
3721 + enrichment:
3722 + metadata_static:
3723 + exporters:
3724 + 192.0.2.10: # bare IP == /32
3725 + name: edge-router-1
3726 + site: par1
3727 + region: eu-west
3728 + role: edge
3729 + tenant: tenant-a
3730 + default:
3731 + description: unclassified port
3732 + if_indexes:
3733 + 1:
3734 + name: Gi0/0/1
3735 + description: uplink to ISP-A
3736 + speed: 10000000000 # 10 Gbps in bits per second
3737 + provider: isp-a
3738 + connectivity: transit
3739 + boundary: external
3740 + 2:
3741 + name: Gi0/0/2
3742 + description: LAN core
3743 + speed: 1000000000
3744 + connectivity: lan
3745 + boundary: internal
3746 + 198.51.100.0/24: # all routers in this subnet
3747 + site: dc-fra1
3748 + region: eu-central
3749 + role: spine
3750 + default:
3751 + connectivity: lan
3752 + boundary: internal
3753 + - name: Tagging your own networks
3754 + folding:
3755 + enabled: true
3756 + description: |
3757 + The shorthand form (`203.0.113.0/24: transit-a`) sets only the `name`.
3758 + The map form takes any subset of the schema fields. Less-specific
3759 + supernets contribute non-empty fields to more-specific subnets via
3760 + ascending-prefix-length merge -- declare RFC1918 once at `/8` and
3761 + a `/24` inherits everything you did not override.
3762 + config: |
3763 + enrichment:
3764 + networks:
3765 + 10.0.0.0/8:
3766 + name: corp-internal
3767 + role: internal
3768 + tenant: tenant-a
3769 + 198.51.100.0/24: # a public block you operate
3770 + name: customer-acme
3771 + role: customer
3772 + site: par1
3773 + country: FR
3774 + city: Paris
3775 + latitude: 48.8566
3776 + longitude: 2.3522
3777 + asn: 64500
3778 + 203.0.113.0/24: transit-a # shorthand: name only
3779 + - name: Skipping interfaces you have not labelled
3780 + folding:
3781 + enabled: true
3782 + description: |
3783 + With `skip_missing_interfaces: true`, an ifIndex that is not in
3784 + `if_indexes` produces no interface labels at all -- the `default`
3785 + block is ignored. Useful when you want clearly-blank values for
3786 + unmodelled interfaces instead of a placeholder description.
3787 + config: |
3788 + enrichment:
3789 + metadata_static:
3790 + exporters:
3791 + 192.0.2.10:
3792 + name: edge-router-1
3793 + skip_missing_interfaces: true
3794 + if_indexes:
3795 + 1:
3796 + name: Gi0/0/1
3797 + description: uplink to ISP-A
3798 + connectivity: transit
3799 + boundary: external
3800 + - name: Sampling rate override for old or silent exporters
3801 + folding:
3802 + enabled: true
3803 + description: |
3804 + NetFlow v7 has no sampling field; some v9 exporters never send a
3805 + Sampling Options Template. Without an override, the bytes/packets
3806 + stored in the journal are exporter-side **sampled** counters with
3807 + no scaling factor. `override_sampling_rate` always wins;
3808 + `default_sampling_rate` is the fallback used only when the flow
3809 + did not carry a rate.
3810 + config: |
3811 + enrichment:
3812 + default_sampling_rate: 1 # global fallback
3813 + override_sampling_rate:
3814 + 10.1.0.0/16: 1024 # an old NetFlow v7 stack
3815 + 192.0.2.10/32: 4096 # one specific exporter
3816 + troubleshooting:
3817 + problems:
3818 + list:
3819 + - name: A more-specific entry inherits the wrong field from its supernet
3820 + description: |
3821 + `enrichment.networks` merges containing prefixes from least-specific to
3822 + most-specific. Leaving a field blank on a `/24` does **not** clear the `/16`'s value
3823 + for that field -- you must explicitly set the field on the more-specific
3824 + entry to overwrite. The same merge rule applies to entries from
3825 + `network_sources`, which interleave at the same prefix lengths.
3826 + - name: GeoIP returns spurious data for an internal range
3827 + description: |
3828 + Until you declare your RFC1918 / RFC6598 / link-local ranges as
3829 + `enrichment.networks` entries, the GeoIP-derived country / city / coord
3830 + fields can pick up junk for those addresses. Adding a `networks` entry
3831 + for the range overrides the GeoIP layer at the merge step.
3832 + - name: ifIndex changed after a hardware swap
3833 + description: |
3834 + `if_indexes` keys are the numeric ifIndex sent in the flow record. A
3835 + line-card reseat or stack rebuild can renumber the interfaces; the old
3836 + ifIndex no longer matches and the per-interface block silently no longer
3837 + applies. Audit after hardware changes.
3838 + - name: speed shows up wrong by a factor of 1000
3839 + description: |
3840 + `speed:` is in **bits per second**. `speed: 1000` means 1 kbps, not
3841 + 1 Mbps. A 1 Gbps interface is `1000000000`. `speed: 0` means "not set"
3842 + and removes the field from the output.
3843 + - name: An interface block silently no longer applies
3844 + description: |
3845 + When the configured ifIndex is not present in the flow record, the
3846 + `default` block is used instead -- unless `skip_missing_interfaces: true`
3847 + is set, in which case no interface labels are written at all. If you expected your block to apply but the labels are blank, the
3848 + router is sending a different ifIndex.
3849 + - name: Coordinates dropped silently
3850 + description: |
3851 + Out-of-range latitude / longitude (`latitude: 91.5`) and non-finite
3852 + values become empty strings without an error. The map quietly stops drawing the marker. Validate input externally if
3853 + the data matters.
3854 + - name: Static labels block the classifiers
3855 + description: |
3856 + When static metadata sets **any** of `group`, `role`, `site`, `region`,
3857 + `tenant` for an exporter, the `exporter_classifiers` rule chain does not
3858 + run for that exporter at all. The same is true for an interface: any of `provider`, `connectivity`,
3859 + `boundary` set by static metadata short-circuits
3860 + `interface_classifiers`. If
3861 + you want classifiers to run on top of static metadata, drop the static
3862 + fields they are supposed to set.
3863 + - name: A typo fails config load
3864 + description: |
3865 + The schema is `deny_unknown_fields` at every level. A typo such as
3866 + `if_index` (the canonical key is `if_indexes`; aliases `ifindexes`,
3867 + `if-indexes` are accepted) or a misspelt attribute (`teannt:`) fails plugin
3868 + start with a YAML parse error rather than being silently ignored.
3869 + - name: Sampling override looks ignored
3870 + description: |
3871 + `override_sampling_rate` always wins when its prefix matches; if the
3872 + field still looks unset, check that the **exporter IP** -- not the
3873 + flow's source / destination IP -- falls under the configured prefix.
3874 + `default_sampling_rate` is only consulted when the flow did not already
3875 + carry a rate.
3876 + - name: Changes do not take effect
3877 + description: |
3878 + Static metadata is loaded at plugin startup and there is no file-change
3879 + watcher. Restart the plugin (or the agent) after editing
3880 + `netflow.yaml`.
3881 + alerts: []
3882 + metrics:
3883 + folding:
3884 + title: Metrics
3885 + enabled: false
3886 + description: |
3887 + Tags flow records with operator-defined labels and (optionally) substitutes
3888 + the sampling rate; produces no metrics of its own. Verify on the Network
3889 + Flows tab via the `EXPORTER_*`, `IN_IF_*`, `OUT_IF_*`, `*_NET_*`,
3890 + `*_COUNTRY`, `*_GEO_*`, and `SAMPLING_RATE` columns.
3891 + availability: []
3892 + scopes: []
3893 + - meta:
3894 + plugin_name: netflow-plugin
3895 + module_name: classifiers
3896 + monitored_instance:
3897 + name: Classifiers
3898 + link: https://learn.netdata.cloud/docs/network-flows/enrichment
3899 + categories:
3900 + - flows.enrichment-methods
3901 + icon_filename: network-wired.svg
3902 + keywords:
3903 + - classifiers
3904 + - rules
3905 + - akvorado
3906 + - expression
3907 + - exporter
3908 + - interface
3909 + - boundary
3910 + - connectivity
3911 + - provider
3912 + - tagging
3913 + related_resources:
3914 + integrations:
3915 + list:
3916 + - plugin_name: netflow-plugin
3917 + module_name: static-metadata
3918 + - plugin_name: netflow-plugin
3919 + module_name: generic-ipam
3920 + - plugin_name: netflow-plugin
3921 + module_name: netbox
3922 + info_provided_to_referring_integrations:
3923 + description: ""
3924 + overview:
3925 + data_collection:
3926 + metrics_description: |
3927 + Annotate network flows with exporter and interface labels derived from reusable
3928 + classification rules. Where [static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment)
3929 + forces you to enumerate every exporter and every ifIndex by hand, classifiers
3930 + let you express the network design once -- "anything matching `^edge-` is the
3931 + edge tier", "any interface with `BACKBONE-LUMEN` in its description is on
3932 + Lumen", "any interface at 100Gbps is a core uplink" -- and apply that labelling
3933 + across the whole flow stream.
3934 +
3935 + The plugin ships two rule lists, evaluated in YAML order:
3936 +
3937 + - `enrichment.exporter_classifiers` -- runs once per exporter (cached). Sees the
3938 + exporter's IP and friendly name, and any classification slots already filled
3939 + by static metadata or by earlier rules. Can set
3940 + `EXPORTER_GROUP / ROLE / SITE / REGION / TENANT`.
3941 + - `enrichment.interface_classifiers` -- runs once per `(exporter, interface)`
3942 + pair, applied **twice per flow record** (once for the input interface, once
3943 + for the output). Sees
3944 + everything an exporter rule sees plus `Interface.Index / Name / Description /
3945 + Speed / VLAN`. Can set `IN_IF_PROVIDER / OUT_IF_PROVIDER`,
3946 + `IN_IF_CONNECTIVITY / OUT_IF_CONNECTIVITY`, `IN_IF_BOUNDARY / OUT_IF_BOUNDARY`
3947 + (1=external, 2=internal), and override `IN_IF_NAME / DESCRIPTION` /
3948 + `OUT_IF_NAME / DESCRIPTION`.
3949 +
3950 + The expression language is **Akvorado-compatible for the documented operators
3951 + and actions**. It implements a subset of Akvorado's `expr-lang`-derived grammar. Akvorado rules
3952 + using only equality, comparison, `in`, `contains`, `startsWith`, `endsWith`,
3953 + `matches`, `&&`, `||`, `!`, parentheses, and the documented `Classify*` /
3954 + `Reject` / `Format` actions will work; arithmetic, ternaries, lambdas, and
3955 + arbitrary `expr-lang` features are not supported.
3956 +
3957 + Output values written by `Classify*` actions are **lowercased and stripped to
3958 + ASCII alphanumerics + `.` + `+` + `-`** before they reach the flow record. So
3959 + `ClassifyRegion("EU West")` becomes `euwest`. Use `SetName` / `SetDescription`
3960 + when you want to preserve case and whitespace -- those write directly without
3961 + normalisation.
3962 +
3963 + For the cross-cutting Enrichment concept (where classifiers sit in the merge
3964 + order vs static metadata, GeoIP, IPAM, BGP routing), see
3965 + [Enrichment](https://learn.netdata.cloud/docs/network-flows/enrichment).
3966 + method_description: |
3967 + Each rule is a single boolean expression; an action with no condition (e.g.
3968 + `Classify("edge")` at top level) is treated as always-true and always fires.
3969 + Rules are AND/OR-composed, so the typical shape is `condition && Classify*(...)`.
3970 + The plugin evaluates the list top to bottom, **first-write-wins per slot**:
3971 + once `EXPORTER_GROUP` is set, no later rule can change it. Order rules from
3972 + most-specific to least-specific.
3973 +
3974 + Two short-circuit rules end the loop early. For exporter rules, the loop stops
3975 + when `group + role + site + region + tenant` are all non-empty. For
3976 + interface rules, the loop stops when `connectivity + provider + boundary` are
3977 + all set. `SetName` / `SetDescription` /
3978 + `Reject` do not contribute to short-circuit.
3979 +
3980 + A rule that throws at runtime (e.g. comparing a string with `>`) breaks out of
3981 + the loop for that record and keeps whatever was set so far. Use `matches`, `startsWith`, or `contains`
3982 + on string fields instead of `>` / `<` to avoid this.
3983 +
3984 + **Akvorado parity**: if `metadata_static` already filled any classification
3985 + slot for the target, the matching classifier list does **not run** for that
3986 + target -- operator-provided classification has priority and the rules cannot
3987 + override it. Don't try to mix static and rule-based labelling on the same
3988 + exporter or interface; pick one tool per target.
3989 +
3990 + Results are cached. The exporter cache keys on `ExporterInfo (ip + name)`. The
3991 + interface cache keys on `(exporter, exporter_classification, interface)` -- so
3992 + when the exporter's classification changes (for example after you push new
3993 + static metadata and restart) the interface caches naturally invalidate. The
3994 + cache TTL is `enrichment.classifier_cache_duration` (default 5 minutes). It is
3995 + a last-access TTL so entries live as long as they're queried.
3996 + supported_platforms:
3997 + include:
3998 + - Linux
3999 + exclude: []
4000 + multi_instance: false
4001 + additional_permissions:
4002 + description: ""
4003 + default_behavior:
4004 + auto_detection:
4005 + description: "Disabled by default. Both rule lists are empty; populate `enrichment.exporter_classifiers` and / or `enrichment.interface_classifiers` to enable."
4006 + limits:
4007 + description: "Resource use scales with rule count and the number of distinct exporters and interfaces. The classifier cache limits repeat evaluation for stable exporter/interface inventories."
4008 + performance_impact:
4009 + description: |
4010 + Rules run at decode time, in the flow-pipeline hot path, so cost matters.
4011 + The cache absorbs nearly all of it: per (exporter, interface) the rule list
4012 + evaluates only on cache miss. Tune
4013 + `enrichment.classifier_cache_duration` upwards (15-60 minutes) for very
4014 + high-cardinality exporter / interface pools where the default 5 minutes
4015 + still yields visible misses; tune downwards (30-60 seconds) when iterating
4016 + on rule changes during a config session.
4017 + setup:
4018 + prerequisites:
4019 + list:
4020 + - title: Know what to classify
4021 + description: |
4022 + Classifiers shine when there is a pattern to match -- exporter naming
4023 + conventions (`edge-...`, `core-...`), management-IP subnets per site,
4024 + SNMP interface descriptions that follow a template (`BACKBONE-<carrier>`,
4025 + `TRANSIT-...`, `IX-...`), or 100Gbps-equals-core conventions. If your
4026 + fleet has no such pattern, [static metadata](https://learn.netdata.cloud/docs/network-flows/enrichment)
4027 + is the better fit -- it lets you list each exporter and ifIndex by hand.
4028 + - title: Configure interface metadata first if you want interface rules
4029 + description: |
4030 + The plugin does **not** poll SNMP itself, so `Interface.Name`,
4031 + `Interface.Description`, and `Interface.Speed` are populated only from
4032 + `enrichment.metadata_static` (the static-metadata integration card). If
4033 + you have not configured `interfaces:` under `metadata_static.exporters`,
4034 + those identifiers will be empty strings / zero, and any rule that
4035 + matches against them will never fire. `Interface.Index` and
4036 + `Interface.VLAN` come from the flow record itself and are always available.
4037 + configuration:
4038 + file:
4039 + name: netflow.yaml
4040 + options:
4041 + description: |
4042 + Both lists live under `enrichment:`. Each entry is a free-form string
4043 + containing a single rule expression. The cache TTL is one global setting.
4044 + folding:
4045 + title: Config options
4046 + enabled: true
4047 + list:
4048 + - name: enrichment.exporter_classifiers
4049 + description: |
4050 + Ordered list of rules applied per exporter. Each rule is a string
4051 + expression. Available identifiers: `Exporter.IP`, `Exporter.Name`,
4052 + `CurrentClassification.Group / .Role / .Site / .Region / .Tenant`.
4053 + Available actions: `Classify` / `ClassifyGroup`, `ClassifyRole`,
4054 + `ClassifySite`, `ClassifyRegion`, `ClassifyTenant`, plus the
4055 + `*Regex(input, pattern, template)` variants of each, plus `Reject()`.
4056 + Interface-only actions (`ClassifyProvider`, `ClassifyConnectivity`,
4057 + `ClassifyExternal` / `ClassifyInternal`, `SetName`, `SetDescription`)
4058 + fail at runtime if used here.
4059 + default_value: "[]"
4060 + required: false
4061 + - name: enrichment.interface_classifiers
4062 + description: |
4063 + Ordered list of rules applied per `(exporter, interface)` pair. Sees
4064 + everything an exporter rule sees, plus `Interface.Index`,
4065 + `Interface.Name`, `Interface.Description`, `Interface.Speed` (bits per
4066 + second), `Interface.VLAN`, and the per-interface
4067 + `CurrentClassification.Connectivity / .Provider / .Boundary / .Name /
4068 + .Description`. Available actions: `ClassifyProvider`,
4069 + `ClassifyConnectivity`, `ClassifyExternal()`, `ClassifyInternal()`,
4070 + `SetName`, `SetDescription`, `Reject()`, plus the `*Regex` variants of
4071 + provider / connectivity. Exporter-only `Classify*` actions fail at
4072 + runtime if used here.
4073 + default_value: "[]"
4074 + required: false
4075 + - name: enrichment.classifier_cache_duration
4076 + description: |
4077 + Last-access TTL for both classifier caches (exporter and interface).
4078 + Values below 1 second are rejected. The cache prunes opportunistically
4079 + -- entries idle longer than the TTL are dropped on the next prune pass,
4080 + capped at one prune every TTL or 30 seconds, whichever is smaller.
4081 + Restart the plugin to clear caches outright when you change rules.
4082 + default_value: "5m"
4083 + required: false
4084 + examples:
4085 + folding:
4086 + title: Config
4087 + enabled: true
4088 + list:
4089 + - name: Exporter classification by name pattern
4090 + folding:
4091 + enabled: false
4092 + description: |
4093 + Tag exporters by the prefix of their friendly name -- the simplest and
4094 + most common pattern. Falls back to a regex capture for the region code
4095 + when the name encodes one. The final `Reject()` rule drops a test
4096 + exporter from collection entirely.
4097 + config: |
4098 + enrichment:
4099 + exporter_classifiers:
4100 + # Group by name prefix.
4101 + - 'Exporter.Name startsWith "edge-" && Classify("edge")'
4102 + - 'Exporter.Name startsWith "core-" && Classify("core")'
4103 + - 'Exporter.Name startsWith "agg-" && Classify("aggregation")'
4104 +
4105 + # Site by management-IP subnet.
4106 + - 'Exporter.IP startsWith "10.1." && ClassifySite("ny-dc1")'
4107 + - 'Exporter.IP startsWith "10.2." && ClassifySite("par-dc1")'
4108 +
4109 + # Region from a name suffix like "edge-fra-01" -> "fra".
4110 + - 'ClassifyRegionRegex(Exporter.Name, "-([a-z]{3})-[0-9]+$", "$1")'
4111 +
4112 + # Drop a lab exporter entirely.
4113 + - 'Exporter.IP startsWith "192.0.2." && Reject()'
4114 + - name: Interface classification from SNMP descriptions
4115 + folding:
4116 + enabled: true
4117 + description: |
4118 + Encode the boundary, the provider, and the connectivity tier from the
4119 + interface description that your network team already maintains. The
4120 + `(?i)` regex flag is the Rust regex inline-case-insensitive prefix.
4121 + config: |
4122 + enrichment:
4123 + interface_classifiers:
4124 + # Provider tag from a description prefix.
4125 + - 'Interface.Description startsWith "BACKBONE-LUMEN" && ClassifyProvider("Lumen")'
4126 + - 'Interface.Description startsWith "BACKBONE-COGENT" && ClassifyProvider("Cogent")'
4127 + - 'Interface.Description startsWith "BACKBONE-NTT" && ClassifyProvider("NTT")'
4128 +
4129 + # Transit links: external boundary + connectivity tag.
4130 + - 'Interface.Description contains "TRANSIT" && ClassifyConnectivity("transit") && ClassifyExternal()'
4131 +
4132 + # Peering and IX -- case-insensitive regex.
4133 + - 'Interface.Description matches "(?i)^(IX|peering)-.*" && ClassifyConnectivity("peering") && ClassifyExternal()'
4134 +
4135 + # Internal customer-facing access ports.
4136 + - 'Interface.Description startsWith "CUSTOMER-" && ClassifyConnectivity("customer") && ClassifyInternal()'
4137 + - name: Boundary inferred from interface speed
4138 + folding:
4139 + enabled: true
4140 + description: |
4141 + A pragmatic shorthand when descriptions are unreliable but speed is
4142 + consistent. 100Gbps and faster interfaces are core, 10Gbps are
4143 + aggregation, 1Gbps and slower are access. `Interface.Speed` is in bits
4144 + per second -- numeric comparisons are safe.
4145 + config: |
4146 + enrichment:
4147 + interface_classifiers:
4148 + - 'Interface.Speed >= 100000000000 && ClassifyConnectivity("core")'
4149 + - 'Interface.Speed >= 10000000000 && ClassifyConnectivity("aggregation")'
4150 + - 'Interface.Speed > 0 && ClassifyConnectivity("access")'
4151 + - name: Combining exporter context with interface rules
4152 + folding:
4153 + enabled: true
4154 + description: |
4155 + Interface rules see the exporter's already-resolved classification
4156 + via `CurrentClassification.*`. Use it to scope interface rules to
4157 + specific tiers -- for example: every interface on an edge exporter
4158 + without a more-specific match falls back to "external".
4159 + config: |
4160 + enrichment:
4161 + exporter_classifiers:
4162 + - 'Exporter.Name startsWith "edge-" && Classify("edge") && ClassifyRole("border")'
4163 + - 'Exporter.Name startsWith "core-" && Classify("core") && ClassifyRole("backbone")'
4164 + interface_classifiers:
4165 + # Specific provider rules first (most-specific to least-specific).
4166 + - 'Interface.Description startsWith "BACKBONE-LUMEN" && ClassifyProvider("Lumen")'
4167 + - 'Interface.Description startsWith "BACKBONE-COGENT" && ClassifyProvider("Cogent")'
4168 +
4169 + # Generic transit rule.
4170 + - 'Interface.Description contains "TRANSIT" && ClassifyConnectivity("transit") && ClassifyExternal()'
4171 +
4172 + # Fallback: any unclassified interface on an edge box is external.
4173 + - 'CurrentClassification.Role == "border" && CurrentClassification.Boundary == 0 && ClassifyExternal()'
4174 + - name: Building values with Format and human-readable names
4175 + folding:
4176 + enabled: true
4177 + description: |
4178 + `Format(pattern, args...)` mimics Go's `fmt.Sprintf` for `%s`, `%v`,
4179 + `%d`, `%%`. `Classify*`
4180 + normalises (lowercase + strip non-alphanumeric); `SetName` and
4181 + `SetDescription` do not, so they preserve the case and spaces of the
4182 + computed value.
4183 + config: |
4184 + enrichment:
4185 + exporter_classifiers:
4186 + # Tenant computed from name, normalised on write -> "tenant-edge01".
4187 + - 'ClassifyTenant(Format("tenant-%s", Exporter.Name))'
4188 + interface_classifiers:
4189 + # Human-readable name = "<exporter>:if<index>". Preserved verbatim.
4190 + - 'SetName(Format("%s:if%d", Exporter.Name, Interface.Index))'
4191 + - name: Tuning the cache for a large fleet
4192 + folding:
4193 + enabled: true
4194 + description: |
4195 + The default 5-minute last-access TTL is right for steady-state. Raise
4196 + it when the (exporter, interface) population is large enough that
4197 + evicted entries are quickly re-queried. Lower it when
4198 + actively iterating on rule changes so misses pick up the new rules
4199 + quickly.
4200 + config: |
4201 + enrichment:
4202 + classifier_cache_duration: 30m
4203 + exporter_classifiers:
4204 + - 'Exporter.Name startsWith "edge-" && Classify("edge")'
4205 + interface_classifiers:
4206 + - 'Interface.Speed >= 100000000000 && ClassifyConnectivity("core")'
4207 + troubleshooting:
4208 + problems:
4209 + list:
4210 + - name: Plugin fails to start with a parser error
4211 + description: |
4212 + A rule failed to parse. The journal log includes the index in the list
4213 + and a parser context (`unsupported rule term`, `unsupported value
4214 + expression`, `Reject() does not accept arguments`, etc.). Common causes:
4215 + missing `&&` between condition and action; an action used in the wrong
4216 + list (`ClassifyExternal` in an exporter rule); strings written with
4217 + single quotes (only JSON-style double quotes are accepted); regex literals
4218 + that fail to compile.
4219 + - name: Classifier rules never run for an exporter or interface
4220 + description: |
4221 + Likely cause: `metadata_static` already set **any** classification field
4222 + on that target. By design, the matching list is suppressed entirely when
4223 + the classification is non-empty. Either remove the static-metadata entry for that target, or
4224 + keep static-metadata as the sole source for it.
4225 + - name: A value appears differently in the dashboard than in the rule
4226 + description: |
4227 + `Classify*` actions normalise output to `[a-z0-9.+-]` only -- so
4228 + `ClassifyRegion("EU West")` lands as `euwest`, and
4229 + `Classify("Edge_Tier_1")` lands as `edgetier1`. Use `SetName` /
4230 + `SetDescription` to preserve case and whitespace; those write the value
4231 + verbatim.
4232 + - name: First rule always wins, later rules never fire for the same slot
4233 + description: |
4234 + First-write-wins is by design and per slot. Order your
4235 + rules from most-specific to least-specific. If you want a tiered
4236 + fallback, use distinct slots (e.g. `Classify` for the broad group and
4237 + `ClassifyRole` for the tier within that group).
4238 + - name: A working rule stops matching some time after startup
4239 + description: |
4240 + Cached results expire after `classifier_cache_duration` (default 5
4241 + minutes, last-access). When you change rules, restart the plugin so the
4242 + caches clear immediately -- otherwise stale cached classifications keep
4243 + returning until they idle out.
4244 + - name: A rule with `>` or `<` aborts the rule list
4245 + description: |
4246 + Comparing a string-typed identifier with `>` / `<` / `>=` / `<=` raises
4247 + a runtime error, and the loop breaks out for that record. Subsequent rules in
4248 + the list are skipped for that record. Use `matches`, `startsWith`,
4249 + `endsWith`, `contains`, or `==` / `!=` on string fields. Keep `>` / `<`
4250 + for `Interface.Index`, `Interface.Speed`, and `Interface.VLAN` (the
4251 + numeric identifiers).
4252 + - name: ClassifyExternal fires only on one side
4253 + description: |
4254 + Interface classifiers run twice per flow record -- once for the input
4255 + interface, once for the output. Both invocations see the same rule list. If your rule conditions on
4256 + `Interface.Index == 42` and that ifIndex appears in `IN_IF` of one flow
4257 + and `OUT_IF` of another, the rule fires correctly in both places. But
4258 + the `IN_IF_BOUNDARY` / `OUT_IF_BOUNDARY` columns are independent -- a
4259 + rule firing on the output side of a flow only sets the output side's
4260 + boundary, and vice versa.
4261 + - name: Interface fields are empty in the rule even though SNMP is configured
4262 + description: |
4263 + The plugin does not poll SNMP -- `Interface.Name`, `Description`, and
4264 + `Speed` come exclusively from `enrichment.metadata_static.exporters.<ip>.interfaces.<index>`.
4265 + If you populate them through an external SNMP discovery and write them
4266 + into `metadata_static`, the rules will see them. Otherwise those fields
4267 + resolve to empty strings / zero, and any rule that conditions on them
4268 + never matches.
4269 + - name: Referencing Interface.* in an exporter rule silently does nothing
4270 + description: |
4271 + Field resolution does not error when the wrong context is missing -- it
4272 + returns the type's zero value. So `Interface.Speed >= 1` written in an `exporter_classifiers` rule
4273 + resolves to `0 >= 1` (false) on every call. Use
4274 + `interface_classifiers` for any rule that needs an interface field.
4275 + alerts: []
4276 + metrics:
4277 + folding:
4278 + title: Metrics
4279 + enabled: false
4280 + description: |
4281 + Tags flow records with `EXPORTER_GROUP / ROLE / SITE / REGION / TENANT` (from
4282 + exporter rules) and `IN_IF_PROVIDER / OUT_IF_PROVIDER`,
4283 + `IN_IF_CONNECTIVITY / OUT_IF_CONNECTIVITY`, `IN_IF_BOUNDARY / OUT_IF_BOUNDARY`,
4284 + `IN_IF_NAME / OUT_IF_NAME`, `IN_IF_DESCRIPTION / OUT_IF_DESCRIPTION` (from
4285 + interface rules). Verify on the Network Flows view via those columns. Boundary
4286 + is `1` for external and `2` for internal.
4287 + availability: []
4288 + scopes: []
4289 + - meta:
4290 + plugin_name: netflow-plugin
4291 + module_name: decapsulation
4292 + monitored_instance:
4293 + name: Decapsulation
4294 + link: https://datatracker.ietf.org/doc/html/rfc7348
4295 + categories:
4296 + - flows.enrichment-methods
4297 + icon_filename: network-wired.svg
4298 + keywords:
4299 + - decapsulation
4300 + - srv6
4301 + - vxlan
4302 + - tunnel
4303 + - overlay
4304 + - inner packet
4305 + - dataLinkFrameSection
4306 + - layer2packetSectionData
4307 + - ipfix ie 315
4308 + - netflow v9 ie 104
4309 + - sampled header
4310 + related_resources:
4311 + integrations:
4312 + list:
4313 + - plugin_name: netflow-plugin
4314 + module_name: netflow
4315 + - plugin_name: netflow-plugin
4316 + module_name: ipfix
4317 + - plugin_name: netflow-plugin
4318 + module_name: sflow
4319 + info_provided_to_referring_integrations:
4320 + description: ""
4321 + overview:
4322 + data_collection:
4323 + metrics_description: |
4324 + Enrich network flows with inner source and destination endpoints from VXLAN or
4325 + SRv6 encapsulated traffic. Decapsulation rewrites the 5-tuple of a flow record
4326 + from the **outer** (tunnel) endpoints to the **inner** (real) endpoints when the
4327 + exporter ships the encapsulated frame bytes alongside the standard fields. Without
4328 + it, every VM-to-VM conversation on a VXLAN fabric or every inner flow on an SRv6
4329 + transit shows up as the same tunnel pair (hypervisor-to-hypervisor or PE-to-PE),
4330 + which tells you nothing about the actual traffic.
4331 +
4332 + Two encapsulations are supported, selected globally for the plugin. The mode set is
4333 + `none`, `srv6`, or `vxlan`; the default is `none`.
4334 +
4335 + | Mode | Strips | Surfaces |
4336 + |---------|--------------------------------------------------|--------------------------------------------------|
4337 + | `none` | nothing | the outer-header view (default) |
4338 + | `srv6` | IPv6 outer + extension headers + SRH (type 4) | the inner IPv4 (next-header 4) or IPv6 (41) |
4339 + | `vxlan` | outer Eth/IP + UDP/4789 + 8-byte VXLAN header | the inner Ethernet frame, then its L3/L4 |
4340 +
4341 + The plugin only reaches the decap path when the flow record carries the inner
4342 + frame bytes in a Layer-2 packet section field. Three transport-level paths feed it:
4343 +
4344 + - **NetFlow v9 IE 104** -- `Layer2packetSectionData` (RFC 5102 / RFC 7270).
4345 + - **IPFIX IE 315** -- `dataLinkFrameSection` (RFC 7133).
4346 + - **sFlow `SampledHeader`** -- always present in header-sampling mode.
4347 +
4348 + When decap succeeds, the inner 5-tuple replaces the outer one in the resulting
4349 + journal record: `SRC_ADDR`, `DST_ADDR`, `SRC_PORT`, `DST_PORT`, `PROTOCOL`,
4350 + `ETYPE`, `IPTOS`, `IPTTL`, `IPV6_FLOW_LABEL`, `TCP_FLAGS`, `IP_FRAGMENT_ID`,
4351 + `IP_FRAGMENT_OFFSET`, ICMP type/code, MPLS labels (if present), and `BYTES`
4352 + (set to the inner L3 length so byte counts represent inner payload, not outer
4353 + overhead). For VXLAN, `SRC_MAC`, `DST_MAC`, `SRC_VLAN`, `DST_VLAN` come from the
4354 + inner Ethernet frame -- the outer MACs and VLANs are lost. The VXLAN VNI is
4355 + parsed but not exposed as a journal field; pure VNI-based segmentation is
4356 + not visible.
4357 +
4358 + The `vxlan` parser matches **only UDP destination port 4789** (RFC 7348).
4359 + VXLAN-GPE on 4790 and any vendor-custom port are not recognised. The `srv6`
4360 + parser walks IPv6 extension headers and the Routing Header type 4 (SRH), then
4361 + surfaces the inner IPv4 or IPv6 packet pointed to by next-header 4 or 41.
4362 +
4363 + For the cross-cutting concept (how decap composes with the rest of the enrichment
4364 + pipeline, the non-tunnel "drop, do not fall back" semantics, and per-source
4365 + dependence on the L2-section path), see
4366 + [Decapsulation](https://learn.netdata.cloud/docs/network-flows/enrichment).
4367 + method_description: |
4368 + Set `protocols.decapsulation_mode` in `netflow.yaml` to `srv6` or `vxlan`. The
4369 + plugin then runs the inner-packet parser whenever a flow record carries an L2
4370 + frame section (NetFlow v9 IE 104 / IPFIX IE 315 / sFlow `SampledHeader`). Plain
4371 + NetFlow / IPFIX records that do **not** carry an L2 section pass through
4372 + unchanged regardless of the setting -- so enabling decap will not break a mixed
4373 + stream where only a subset of exporters ship the frame bytes. Enabling decap is
4374 + half the work; the exporter must also be configured to ship the inner frame.
4375 + supported_platforms:
4376 + include:
4377 + - Linux
4378 + exclude: []
4379 + multi_instance: false
4380 + additional_permissions:
4381 + description: ""
4382 + default_behavior:
4383 + auto_detection:
4384 + description: "Disabled by default (`decapsulation_mode: none`). You must opt in by setting the mode explicitly."
4385 + limits:
4386 + description: "One mode is active at a time -- the plugin cannot decap VXLAN and SRv6 simultaneously. If your fleet mixes both, choose the one your L2-section-bearing exporters carry exclusively."
4387 + performance_impact:
4388 + description: "Decapsulation runs in the flow hot path for records carrying L2 frame sections. It adds protocol parsing work and drops L2-section records that do not match the configured tunnel mode."
4389 + setup:
4390 + prerequisites:
4391 + list:
4392 + - title: Confirm your exporter ships the L2 frame section
4393 + description: |
4394 + Decapsulation requires the exporter to include the inner frame bytes. Verify
4395 + before enabling the mode:
4396 +
4397 + - **NetFlow v9** -- the template must contain field type 104
4398 + (`Layer2packetSectionData`). Capture a packet with `tcpdump`, decode with
4399 + Wireshark, and inspect the template.
4400 + - **IPFIX** -- the template must contain Information Element 315
4401 + (`dataLinkFrameSection`, RFC 7133). Same verification path.
4402 + - **sFlow** -- header sampling is the default for sFlow agents and ships the
4403 + truncated raw packet inside `SampledHeader`. No special configuration is
4404 + required beyond enabling sFlow.
4405 +
4406 + Section length matters. VXLAN over Ethernet over IPv4 fits in roughly 96-128
4407 + bytes of inner-frame capture; SRv6 with a Routing Header type 4 needs more
4408 + (256 bytes is a safe starting point). Truncated captures fail the inner
4409 + parser and the flow is dropped (see the failure modes on the concept page).
4410 + - title: Configure your exporter to emit the L2 section
4411 + description: |
4412 + Vendor support varies. Recommended exporter paths are:
4413 +
4414 + - **Juniper inline-monitoring (IPFIX 315)** on platforms supporting
4415 + `services { inline-monitoring { ... } }` -- the template includes
4416 + `datalink-frame-size` and a `maximum-clip-length` controls how much of the
4417 + frame is captured. Reference recipe in the
4418 + [Akvorado documentation](https://demo.akvorado.net/docs/operations#ipfix-315).
4419 + - **sFlow** with header sampling -- supported by Juniper QFX, Arista EOS,
4420 + Mellanox/NVIDIA, MikroTik, and others. Header sampling is the default for
4421 + most agents; verify the agent emits `SampledHeader` rather than only
4422 + `SampledIPv4` / `SampledIPv6` records (the latter do not carry inner
4423 + bytes).
4424 +
4425 + Cisco IOS-XE and IOS-XR Flexible NetFlow support for Layer-2 frame
4426 + sections is platform-dependent. Before deploying Cisco decapsulation,
4427 + inspect the exported template and look for IE 104 (v9) or IE 315 (IPFIX).
4428 + Do not copy `collect datalink frame-section` snippets into production
4429 + unless the platform template confirms that the L2 section is exported.
4430 + configuration:
4431 + file:
4432 + name: netflow.yaml
4433 + options:
4434 + description: |
4435 + Decapsulation has a single configuration knob -- `protocols.decapsulation_mode`.
4436 + folding:
4437 + title: Config options
4438 + enabled: true
4439 + list:
4440 + - name: protocols.decapsulation_mode
4441 + description: |
4442 + One of `none` (default), `srv6`, `vxlan`. The mode applies globally; the
4443 + plugin cannot decode both VXLAN and SRv6 in the same instance. Setting
4444 + this only affects records that travel through the L2-section path
4445 + (NetFlow v9 IE 104 / IPFIX IE 315 / sFlow `SampledHeader`); regular flow
4446 + records are unaffected. When the mode is set and the inner packet does
4447 + not match the configured tunnel, the record is dropped -- there is no
4448 + "fall back to outer view".
4449 + default_value: none
4450 + required: false
4451 + examples:
4452 + folding:
4453 + title: Config
4454 + enabled: true
4455 + list:
4456 + - name: VXLAN-based VTEP fleet (decode the inner tenant traffic)
4457 + folding:
4458 + enabled: false
4459 + description: |
4460 + Hypervisors emit sFlow with header sampling on the underlay. Every flow
4461 + between two VTEP loopbacks decodes to the inner VM-to-VM 5-tuple instead
4462 + of the underlay tunnel pair.
4463 + config: |
4464 + protocols:
4465 + decapsulation_mode: vxlan
4466 + - name: SRv6 transit network (see the inner service traffic)
4467 + folding:
4468 + enabled: true
4469 + description: |
4470 + Provider-edge routers export IPFIX with IE 315 (`dataLinkFrameSection`)
4471 + via Juniper inline-monitoring. The plugin walks the IPv6 extension chain,
4472 + strips the SRH, and surfaces the inner IPv4 or IPv6 5-tuple.
4473 + config: |
4474 + protocols:
4475 + decapsulation_mode: srv6
4476 + - name: Default (no decapsulation)
4477 + folding:
4478 + enabled: true
4479 + description: |
4480 + Outer-header view only. Tunnel endpoints appear as the source and
4481 + destination of every flow.
4482 + config: |
4483 + protocols:
4484 + decapsulation_mode: none
4485 + troubleshooting:
4486 + problems:
4487 + list:
4488 + - name: Decap mode set but tunnels still show outer endpoints
4489 + description: |
4490 + The exporter is not shipping the L2 frame section. Plain NetFlow / IPFIX
4491 + flow records (no IE 104 / IE 315) take the regular path and are unaffected
4492 + by `decapsulation_mode`. Inspect the template -- look for field type 104
4493 + on NetFlow v9 or IE 315 on IPFIX. For sFlow, confirm the agent is sending
4494 + `SampledHeader` records rather than only `SampledIPv4` / `SampledIPv6`.
4495 + - name: Records disappear after enabling decap
4496 + description: |
4497 + When `decapsulation_mode` is set and a record arrives via the L2-section
4498 + path with a payload that does not match the configured tunnel, the record
4499 + is **dropped**. There is no fall back to the outer view. For sFlow with decap on, only `SampledHeader` records are processed;
4500 + `SampledIPv4`, `SampledIPv6`, `SampledEthernet`, `ExtendedSwitch`,
4501 + `ExtendedRouter`, `ExtendedGateway` records are skipped. If the same
4502 + exporter mixes tunnel and non-tunnel traffic on the L2-section path, you
4503 + will lose the non-tunnel records.
4504 + - name: VXLAN on a non-default UDP port goes undetected
4505 + description: |
4506 + The VXLAN parser matches only UDP destination port 4789. VXLAN-GPE on 4790 and any
4507 + vendor-custom port are not recognised and the record is dropped under
4508 + `decapsulation_mode: vxlan`.
4509 + - name: Frame section truncated, inner parsing fails
4510 + description: |
4511 + The exporter's clip / section size is shorter than the outer headers plus
4512 + the inner L3/L4 needed to populate the 5-tuple. Increase the section size
4513 + -- 128 bytes for VXLAN over IPv4, 256 bytes or more for SRv6 with extension
4514 + headers. On Juniper inline-monitoring, the knob is `maximum-clip-length`.
4515 + - name: VNI-based segmentation invisible
4516 + description: |
4517 + Bytes 4-6 of the VXLAN header (the VNI) are not exposed as journal
4518 + fields. If the inner Ethernet carries a VLAN tag, that VLAN reaches
4519 + `SRC_VLAN` / `DST_VLAN` and works for segmentation -- pure VNI does not.
4520 + No workaround inside the plugin; either VLAN-tag the inner traffic or
4521 + filter at query time using the tunnel-endpoint pair before decap.
4522 + - name: One mode at a time
4523 + description: |
4524 + The plugin cannot decode VXLAN and SRv6 in the same instance. If exporter A
4525 + ships VXLAN tenant traffic and exporter B ships SRv6 transit traffic, you
4526 + must choose the mode that matches the traffic this Netdata Agent receives.
4527 alerts: []
4528 metrics:
4529 folding:
4530 title: Metrics
4531 enabled: false
4532 description: |
2218 - Tags flow records with whatever labels your jq transform emits. Verify on the
2219 - Network Flows tab via the `*_NET_*` columns.
4533 + Decapsulation rewrites flow-record fields in place; it produces no metrics of its
4534 + own. Verify on the Network Flows view that `SRC_ADDR` / `DST_ADDR` reflect inner
4535 + endpoints rather than the tunnel pair after enabling the mode.
4536 availability: []
4537 scopes: []
src/crates/netflow-plugin/src/api/flows/handler.rs
+9 -10
@@ -47,16 +47,15 @@ impl NetflowFlowsHandler {
47 // tokio workers handling unrelated traffic.
48 let request_for_query = request.clone();
49 let query = Arc::clone(&self.query);
50 - let query_output = task::spawn_blocking(move || {
51 - query.autocomplete_field_values(&request_for_query)
52 - })
53 - .await
54 - .map_err(|err| NetdataPluginError::Other {
55 - message: format!("autocomplete task join failed: {err}"),
56 - })?
57 - .map_err(|err| NetdataPluginError::Other {
58 - message: format!("failed to autocomplete facet values: {err:#}"),
59 - })?;
50 + let query_output =
51 + task::spawn_blocking(move || query.autocomplete_field_values(&request_for_query))
52 + .await
53 + .map_err(|err| NetdataPluginError::Other {
54 + message: format!("autocomplete task join failed: {err}"),
55 + })?
56 + .map_err(|err| NetdataPluginError::Other {
57 + message: format!("failed to autocomplete facet values: {err:#}"),
58 + })?;
59 let mut stats = self.metrics.snapshot();
60 stats.extend(query_output.stats);
61
src/crates/netflow-plugin/src/facet_runtime.rs
+3 -1
@@ -1235,7 +1235,9 @@ mod tests {
1235 .autocomplete("SRC_AS_NAME", "Akamai")
1236 .expect("autocomplete by org name");
1237 assert!(
1238 - mid_token.iter().any(|v| v == "AS20940 Akamai International"),
1238 + mid_token
1239 + .iter()
1240 + .any(|v| v == "AS20940 Akamai International"),
1241 "substring search on AS_NAME must match the organisation token; got {mid_token:?}"
1242 );
1243
src/crates/netflow-plugin/src/ingest.rs
+3 -2
@@ -9,6 +9,7 @@ use crate::plugin_config::{
9 DecapsulationMode as ConfigDecapsulationMode, PluginConfig,
10 TimestampSource as ConfigTimestampSource,
11 };
12 +use crate::query::{scan_journal_files_forward, visit_journal_payloads};
13 use crate::routing::DynamicRoutingRuntime;
14 use crate::tiering::{
15 MATERIALIZED_TIERS, OpenTierState, TierAccumulator, TierFlowIndexStore, TierKind,
@@ -19,7 +20,7 @@ use journal_engine::{
20 Facets, FileIndexCacheBuilder, FileIndexKey, IndexingLimits, LogQuery, QueryTimeRange,
21 batch_compute_file_indexes,
22 };
22 -use journal_index::{Anchor, Direction, FieldName, Microseconds, Seconds};
23 +use journal_index::{Anchor, Direction, FieldName, Seconds};
24 use journal_log_writer::{Config, EntryTimestamps, Log, RetentionPolicy, RotationPolicy};
25 use journal_registry::{Monitor, Origin, Registry, Source};
26 use std::collections::HashMap;
@@ -27,7 +28,7 @@ use std::fs;
28 use std::path::{Path, PathBuf};
29 use std::sync::atomic::{AtomicU64, Ordering};
30 use std::sync::{Arc, RwLock};
30 -use std::time::{Duration, SystemTime, UNIX_EPOCH};
31 +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
32 use tokio::net::UdpSocket;
33 use tokio::time::MissedTickBehavior;
34 use tokio_util::sync::CancellationToken;
src/crates/netflow-plugin/src/ingest/rebuild.rs
+86 -64
@@ -1,6 +1,8 @@
1 use super::*;
2 use crate::memory_allocator::trim_allocator_if_worthwhile;
3
4 +const REBUILD_SCAN_QUEUE_CAPACITY: usize = 1024;
5 +
6 impl IngestService {
7 /// Query the most recent `_SOURCE_REALTIME_TIMESTAMP` from a tier's journal
8 /// files. Returns `None` when the tier directory is empty or unreadable.
@@ -65,7 +67,11 @@ impl IngestService {
67
68 pub(super) async fn rebuild_materialized_from_raw(&mut self) -> Result<()> {
69 let now = now_usec();
68 - let before = (now / 1_000_000).max(1) as u32;
70 + let before = now
71 + .saturating_add(999_999)
72 + .saturating_div(1_000_000)
73 + .min(u64::from(u32::MAX))
74 + .max(1) as u32;
75 let after = before.saturating_sub(REBUILD_WINDOW_SECONDS);
76
77 let raw_dir = self.cfg.journal.raw_tier_dir();
@@ -109,73 +115,89 @@ impl IngestService {
115 }
116 }
117
112 - let source_timestamp_field = FieldName::new_unchecked("_SOURCE_REALTIME_TIMESTAMP");
113 - let facets = Facets::new(&["_SOURCE_REALTIME_TIMESTAMP".to_string()]);
114 - let keys: Vec<FileIndexKey> = files
118 + let after_usec = (after as u64).saturating_mul(1_000_000);
119 + let before_usec = (before as u64).saturating_mul(1_000_000);
120 + let file_paths = files
121 .iter()
116 - .map(|file_info| {
117 - FileIndexKey::new(
118 - &file_info.file,
119 - &facets,
120 - Some(source_timestamp_field.clone()),
121 - )
122 - })
123 - .collect();
124 -
125 - let time_range =
126 - QueryTimeRange::new(after, before).context("invalid rebuild raw time range")?;
127 -
128 - let indexing_cancellation = CancellationToken::new();
129 - let indexed_files = tokio::select! {
130 - result = batch_compute_file_indexes(
131 - &cache,
132 - &registry,
133 - keys,
134 - &time_range,
135 - indexing_cancellation.clone(),
136 - IndexingLimits::default(),
122 + .map(|file_info| PathBuf::from(file_info.file.path()))
123 + .collect::<Vec<_>>();
124 + let rebuild_started = Instant::now();
125 + let rebuild_timeout = Duration::from_secs(REBUILD_TIMEOUT_SECONDS);
126 + let (rebuild_tx, mut rebuild_rx) =
127 + tokio::sync::mpsc::channel::<(u64, crate::flow::FlowFields)>(
128 + REBUILD_SCAN_QUEUE_CAPACITY,
129 + );
130 + let scan_handle = tokio::task::spawn_blocking(move || -> Result<()> {
131 + let mut scanned_entries = 0_u64;
132 +
133 + scan_journal_files_forward(
134 + &file_paths,
135 + Some(after_usec),
136 + Some(before_usec),
137 None,
138 - ) => result.context("failed to build raw indexes for tier rebuild"),
139 - _ = tokio::time::sleep(Duration::from_secs(REBUILD_TIMEOUT_SECONDS)) => {
140 - indexing_cancellation.cancel();
141 - Err(anyhow!(
142 - "timed out building raw indexes for tier rebuild after {}s",
143 - REBUILD_TIMEOUT_SECONDS
144 - ))
145 - }
146 - }?;
147 - let file_indexes: Vec<_> = indexed_files.into_iter().map(|(_, idx)| idx).collect();
148 -
149 - if file_indexes.is_empty() {
150 - self.refresh_open_tier_state(now);
151 - return Ok(());
138 + 0,
139 + 0,
140 + &[],
141 + "raw tier rebuild",
142 + |_file_path, journal, entry_timestamp_usec, data_offsets, decompress_buf| {
143 + scanned_entries = scanned_entries.saturating_add(1);
144 + if rebuild_started.elapsed() >= rebuild_timeout {
145 + return Err(anyhow!(
146 + "timed out scanning raw flows for tier rebuild after {}s (scanned {} entries)",
147 + REBUILD_TIMEOUT_SECONDS,
148 + scanned_entries
149 + ));
150 + }
151 +
152 + let mut fields = crate::flow::FlowFields::new();
153 + visit_journal_payloads(
154 + journal,
155 + _file_path,
156 + data_offsets,
157 + decompress_buf,
158 + |payload| {
159 + let Some(eq_pos) = payload.iter().position(|&b| b == b'=') else {
160 + return Ok(());
161 + };
162 + let Ok(name) = std::str::from_utf8(&payload[..eq_pos]) else {
163 + return Ok(());
164 + };
165 + let Some(interned) = crate::decoder::intern_field_name(name) else {
166 + return Ok(());
167 + };
168 + let value =
169 + String::from_utf8_lossy(&payload[eq_pos + 1..]).into_owned();
170 + fields.insert(interned, value);
171 + Ok(())
172 + },
173 + )?;
174 +
175 + if rebuild_started.elapsed() >= rebuild_timeout {
176 + return Err(anyhow!(
177 + "timed out scanning raw flows for tier rebuild after {}s (scanned {} entries)",
178 + REBUILD_TIMEOUT_SECONDS,
179 + scanned_entries
180 + ));
181 + }
182 +
183 + rebuild_tx
184 + .blocking_send((entry_timestamp_usec, fields))
185 + .map_err(|_| anyhow!("raw tier rebuild receiver dropped"))?;
186 + Ok(true)
187 + },
188 + )
189 + .context("failed to scan raw flows for tier rebuild")?;
190 +
191 + Ok(())
192 + });
193 +
194 + while let Some((entry_timestamp_usec, fields)) = rebuild_rx.recv().await {
195 + self.observe_tiers_with_cutoffs(entry_timestamp_usec, &fields, &tier_cutoffs);
196 }
197
154 - let after_usec = (after as u64).saturating_mul(1_000_000);
155 - let before_usec = (before as u64).saturating_mul(1_000_000);
156 - let anchor_usec = before_usec.saturating_sub(1);
157 -
158 - let entries = LogQuery::new(
159 - &file_indexes,
160 - Anchor::Timestamp(Microseconds(anchor_usec)),
161 - Direction::Backward,
162 - )
163 - .with_after_usec(after_usec)
164 - .with_before_usec(before_usec)
165 - .execute()
166 - .context("failed to query raw flows for tier rebuild")?;
167 -
168 - for entry in entries {
169 - let mut fields = crate::flow::FlowFields::new();
170 - for pair in entry.fields {
171 - let name = pair.field();
172 - if let Some(interned) = crate::decoder::intern_field_name(name) {
173 - fields.insert(interned, pair.value().to_string());
174 - }
175 - }
176 -
177 - self.observe_tiers_with_cutoffs(entry.timestamp, &fields, &tier_cutoffs);
178 - }
198 + scan_handle
199 + .await
200 + .context("raw tier rebuild scan task join failed")??;
201
202 Ok(())
203 }
src/crates/netflow-plugin/src/ingest/service/runtime.rs
+21 -7
@@ -80,7 +80,7 @@ impl IngestService {
80 self.metrics.apply_decode_stats(&batch.stats);
81
82 for flow in batch.flows {
83 - if self.ingest_decoded_record(receive_time_usec, &flow.record) {
83 + if self.ingest_decoded_record(receive_time_usec, &flow) {
84 entries_since_sync += 1;
85 }
86 }
@@ -92,18 +92,26 @@ impl IngestService {
92 fn ingest_decoded_record(
93 &mut self,
94 receive_time_usec: u64,
95 - record: &crate::flow::FlowRecord,
95 + flow: &crate::decoder::DecodedFlow,
96 ) -> bool {
97 - self.ingest_decoded_record_internal(receive_time_usec, record, true)
97 + self.ingest_decoded_record_internal(
98 + receive_time_usec,
99 + flow.source_realtime_usec.unwrap_or(receive_time_usec),
100 + &flow.record,
101 + true,
102 + )
103 }
104
105 fn ingest_decoded_record_internal(
106 &mut self,
107 receive_time_usec: u64,
108 + source_realtime_usec: u64,
109 record: &crate::flow::FlowRecord,
110 observe_tiers: bool,
111 ) -> bool {
106 - let Ok(active_path) = self.write_raw_record_internal(receive_time_usec, record) else {
112 + let Ok(active_path) =
113 + self.write_raw_record_internal(receive_time_usec, source_realtime_usec, record)
114 + else {
115 return false;
116 };
117
@@ -124,10 +132,11 @@ impl IngestService {
132 fn write_raw_record_internal(
133 &mut self,
134 receive_time_usec: u64,
135 + source_realtime_usec: u64,
136 record: &crate::flow::FlowRecord,
137 ) -> std::result::Result<Option<String>, ()> {
138 let timestamps = EntryTimestamps::default()
130 - .with_source_realtime_usec(receive_time_usec)
139 + .with_source_realtime_usec(source_realtime_usec)
140 .with_entry_realtime_usec(receive_time_usec);
141
142 if let Err(err) =
@@ -245,7 +254,7 @@ impl IngestService {
254 receive_time_usec: u64,
255 record: &crate::flow::FlowRecord,
256 ) -> bool {
248 - self.ingest_decoded_record(receive_time_usec, record)
257 + self.ingest_decoded_record_internal(receive_time_usec, receive_time_usec, record, true)
258 }
259
260 #[cfg(test)]
@@ -295,7 +304,12 @@ impl IngestService {
304 run_tier_maintenance: bool,
305 ) -> usize {
306 for record in records {
298 - if self.ingest_decoded_record_internal(receive_time_usec, record, observe_tiers) {
307 + if self.ingest_decoded_record_internal(
308 + receive_time_usec,
309 + receive_time_usec,
310 + record,
311 + observe_tiers,
312 + ) {
313 entries_since_sync += 1;
314 }
315 }
src/crates/netflow-plugin/src/ingest_resource_bench_tests.rs
+3 -7
@@ -172,11 +172,8 @@ fn run_storage_footprint_child() -> StorageFootprintReport {
172 let profile = ResourceProfile::from_env();
173 let flows_per_sec = env_u64(RATE_ENV, DEFAULT_STORAGE_FLOWS_PER_SEC);
174 let duration_secs = env_u64(STORAGE_DURATION_ENV, DEFAULT_STORAGE_DURATION_SECS);
175 - let sample_interval_secs = env_u64(
176 - STORAGE_SAMPLE_ENV,
177 - DEFAULT_STORAGE_SAMPLE_INTERVAL_SECS,
178 - )
179 - .max(1);
175 + let sample_interval_secs =
176 + env_u64(STORAGE_SAMPLE_ENV, DEFAULT_STORAGE_SAMPLE_INTERVAL_SECS).max(1);
177
178 let (record_batches, protocol_name) = build_record_batches(profile);
179 let (_tmp, mut service) = new_disk_benchmark_ingest_service(ConfigDecapsulationMode::None);
@@ -207,8 +204,7 @@ fn run_storage_footprint_child() -> StorageFootprintReport {
204 false,
205 );
206 entries_since_sync = segment.entries_since_sync;
210 - total_flows_ingested =
211 - total_flows_ingested.saturating_add(segment.ingested_flows as u64);
207 + total_flows_ingested = total_flows_ingested.saturating_add(segment.ingested_flows as u64);
208
209 let elapsed = started.elapsed().as_secs();
210 let proc_now = take_proc_snapshot();
src/crates/netflow-plugin/src/ingest_test_support.rs
+1
@@ -5,6 +5,7 @@ use pcap_file::pcap::PcapReader;
5 use std::fs::File;
6 use std::net::{IpAddr, SocketAddr};
7 use std::path::{Path, PathBuf};
8 +use std::time::Duration;
9 use tempfile::TempDir;
10
11 pub(super) fn new_test_ingest_service(
src/crates/netflow-plugin/src/main_tests.rs
+236
@@ -73,6 +73,82 @@ async fn e2e_ingest_writes_journals_and_query_reads_flows() {
73 );
74 }
75
76 +#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
77 +async fn e2e_timestamp_source_first_switched_is_persisted_as_source_timestamp() {
78 + let (cfg, _metrics, open_tiers, _tier_flow_indexes, _tmp) =
79 + ingest_fixture_with_timestamp_source(
80 + "nfv5.pcap",
81 + plugin_config::TimestampSource::NetflowFirstSwitched,
82 + )
83 + .await;
84 + let fields = first_raw_journal_fields(&cfg.journal.raw_tier_dir());
85 +
86 + let source_ts = fields
87 + .get("_SOURCE_REALTIME_TIMESTAMP")
88 + .expect("missing _SOURCE_REALTIME_TIMESTAMP in raw journal entry");
89 + let flow_start = fields
90 + .get("FLOW_START_USEC")
91 + .expect("missing FLOW_START_USEC in raw journal entry");
92 + assert_eq!(
93 + source_ts, flow_start,
94 + "expected timestamp_source=netflow_first_switched to persist the decoded flow start as _SOURCE_REALTIME_TIMESTAMP"
95 + );
96 +
97 + let source_usec = source_ts
98 + .parse::<u64>()
99 + .expect("source timestamp should be a usec integer");
100 + let raw_entry_realtime = first_journal_realtime_usec(&cfg.journal.raw_tier_dir());
101 + assert!(
102 + raw_entry_realtime > source_usec,
103 + "raw journal entry realtime should remain receive/write time, not decoded source time"
104 + );
105 +
106 + let expected_minute_1_bucket = bucket_start_usec(raw_entry_realtime, 60_000_000);
107 + let minute_1_timestamps = journal_source_realtime_timestamps(&cfg.journal.minute_1_tier_dir());
108 + assert!(
109 + timestamps_include_bucket(&minute_1_timestamps, expected_minute_1_bucket, 60_000_000)
110 + || open_tier_includes_bucket(&open_tiers, expected_minute_1_bucket, 60_000_000),
111 + "live materialized tiers should bucket timestamp_source=netflow_first_switched by journal receive time"
112 + );
113 +
114 + for tier_dir in [
115 + cfg.journal.minute_1_tier_dir(),
116 + cfg.journal.minute_5_tier_dir(),
117 + cfg.journal.hour_1_tier_dir(),
118 + ] {
119 + if tier_dir.exists() {
120 + fs::remove_dir_all(&tier_dir)
121 + .unwrap_or_else(|err| panic!("remove tier dir {}: {}", tier_dir.display(), err));
122 + }
123 + }
124 +
125 + let rebuild_metrics = Arc::new(ingest::IngestMetrics::default());
126 + let rebuild_open_tiers = Arc::new(RwLock::new(tiering::OpenTierState::default()));
127 + let rebuild_tier_flow_indexes = Arc::new(RwLock::new(tiering::TierFlowIndexStore::default()));
128 + let mut rebuild_service = ingest::IngestService::new(
129 + cfg.clone(),
130 + Arc::clone(&rebuild_metrics),
131 + Arc::clone(&rebuild_open_tiers),
132 + Arc::clone(&rebuild_tier_flow_indexes),
133 + )
134 + .expect("create rebuild ingest service");
135 + rebuild_service
136 + .rebuild_materialized_from_raw_for_test()
137 + .await
138 + .expect("rebuild materialized tiers from raw");
139 +
140 + let rebuilt_minute_1_timestamps =
141 + journal_source_realtime_timestamps(&cfg.journal.minute_1_tier_dir());
142 + assert!(
143 + timestamps_include_bucket(
144 + &rebuilt_minute_1_timestamps,
145 + expected_minute_1_bucket,
146 + 60_000_000
147 + ) || open_tier_includes_bucket(&rebuild_open_tiers, expected_minute_1_bucket, 60_000_000),
148 + "rebuild should replay recently received raw entries into receive-time materialized buckets"
149 + );
150 +}
151 +
152 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
153 async fn e2e_query_service_timeseries_path_returns_chart_data() {
154 let (cfg, _metrics, _open_tiers, _tier_flow_indexes, _tmp) = ingest_fixture("nfv5.pcap").await;
@@ -1603,6 +1679,140 @@ fn assert_tier_dir_exists(path: &Path, tier_name: &str) {
1679 );
1680 }
1681
1682 +fn first_raw_journal_fields(path: &Path) -> HashMap<String, String> {
1683 + for file_path in journal_files(path) {
1684 + let repo_file =
1685 + RepoFile::from_path(&file_path).expect("parse raw journal repository metadata");
1686 + let journal =
1687 + JournalFile::<Mmap>::open(&repo_file, 8 * 1024 * 1024).expect("open raw journal file");
1688 + let mut reader = JournalReader::default();
1689 + reader.set_location(Location::Head);
1690 + if !reader
1691 + .step(&journal, Direction::Forward)
1692 + .expect("step raw journal reader")
1693 + {
1694 + continue;
1695 + }
1696 +
1697 + let mut data_offsets = Vec::<NonZeroU64>::new();
1698 + reader
1699 + .entry_data_offsets(&journal, &mut data_offsets)
1700 + .expect("enumerate raw journal data offsets");
1701 + let mut fields = HashMap::new();
1702 + let mut decompress_buf = Vec::new();
1703 + query::visit_journal_payloads(
1704 + &journal,
1705 + &file_path,
1706 + &data_offsets,
1707 + &mut decompress_buf,
1708 + |payload| {
1709 + if let Some(eq_pos) = payload.iter().position(|&b| b == b'=') {
1710 + let key = String::from_utf8_lossy(&payload[..eq_pos]).into_owned();
1711 + let value = String::from_utf8_lossy(&payload[eq_pos + 1..]).into_owned();
1712 + fields.insert(key, value);
1713 + }
1714 + Ok(())
1715 + },
1716 + )
1717 + .expect("read raw journal payloads");
1718 + return fields;
1719 + }
1720 +
1721 + panic!(
1722 + "expected at least one raw journal entry in {}",
1723 + path.display()
1724 + );
1725 +}
1726 +
1727 +fn first_journal_realtime_usec(path: &Path) -> u64 {
1728 + for file_path in journal_files(path) {
1729 + let repo_file = RepoFile::from_path(&file_path).expect("parse journal repository metadata");
1730 + let journal =
1731 + JournalFile::<Mmap>::open(&repo_file, 8 * 1024 * 1024).expect("open journal file");
1732 + let mut reader = JournalReader::default();
1733 + reader.set_location(Location::Head);
1734 + if !reader
1735 + .step(&journal, Direction::Forward)
1736 + .expect("step journal reader")
1737 + {
1738 + continue;
1739 + }
1740 +
1741 + return reader
1742 + .get_realtime_usec(&journal)
1743 + .expect("read journal entry realtime timestamp");
1744 + }
1745 +
1746 + panic!("expected at least one journal entry in {}", path.display());
1747 +}
1748 +
1749 +fn journal_source_realtime_timestamps(path: &Path) -> Vec<u64> {
1750 + let mut timestamps = Vec::new();
1751 + for file_path in journal_files(path) {
1752 + let repo_file = RepoFile::from_path(&file_path).expect("parse journal repository metadata");
1753 + let journal =
1754 + JournalFile::<Mmap>::open(&repo_file, 8 * 1024 * 1024).expect("open journal file");
1755 + let mut reader = JournalReader::default();
1756 + reader.set_location(Location::Head);
1757 + let mut decompress_buf = Vec::new();
1758 + loop {
1759 + if !reader
1760 + .step(&journal, Direction::Forward)
1761 + .expect("step journal reader")
1762 + {
1763 + break;
1764 + }
1765 +
1766 + let mut data_offsets = Vec::<NonZeroU64>::new();
1767 + reader
1768 + .entry_data_offsets(&journal, &mut data_offsets)
1769 + .expect("enumerate journal data offsets");
1770 + query::visit_journal_payloads(
1771 + &journal,
1772 + &file_path,
1773 + &data_offsets,
1774 + &mut decompress_buf,
1775 + |payload| {
1776 + if let Some(value) = payload.strip_prefix(b"_SOURCE_REALTIME_TIMESTAMP=")
1777 + && let Ok(value) = String::from_utf8_lossy(value).parse::<u64>()
1778 + {
1779 + timestamps.push(value);
1780 + }
1781 + Ok(())
1782 + },
1783 + )
1784 + .expect("read journal payloads");
1785 + }
1786 + }
1787 +
1788 + timestamps
1789 +}
1790 +
1791 +fn bucket_start_usec(timestamp_usec: u64, bucket_usec: u64) -> u64 {
1792 + timestamp_usec
1793 + .saturating_div(bucket_usec)
1794 + .saturating_mul(bucket_usec)
1795 +}
1796 +
1797 +fn timestamps_include_bucket(timestamps: &[u64], bucket_start: u64, bucket_usec: u64) -> bool {
1798 + timestamps
1799 + .iter()
1800 + .any(|timestamp| bucket_start_usec(*timestamp, bucket_usec) == bucket_start)
1801 +}
1802 +
1803 +fn open_tier_includes_bucket(
1804 + open_tiers: &Arc<RwLock<tiering::OpenTierState>>,
1805 + bucket_start: u64,
1806 + bucket_usec: u64,
1807 +) -> bool {
1808 + open_tiers
1809 + .read()
1810 + .expect("read open tiers")
1811 + .minute_1
1812 + .iter()
1813 + .any(|row| bucket_start_usec(row.timestamp_usec, bucket_usec) == bucket_start)
1814 +}
1815 +
1816 fn tier_file_count(path: &Path) -> usize {
1817 fn count_journal_files(path: &Path) -> usize {
1818 fs::read_dir(path)
@@ -1625,3 +1835,29 @@ fn tier_file_count(path: &Path) -> usize {
1835
1836 count_journal_files(path)
1837 }
1838 +
1839 +fn journal_files(path: &Path) -> Vec<PathBuf> {
1840 + fn collect(path: &Path, files: &mut Vec<PathBuf>) {
1841 + for entry in fs::read_dir(path)
1842 + .unwrap_or_else(|err| panic!("read journal dir {}: {}", path.display(), err))
1843 + .filter_map(Result::ok)
1844 + {
1845 + let entry_path = entry.path();
1846 + if entry_path.is_dir() {
1847 + collect(&entry_path, files);
1848 + } else if entry_path
1849 + .extension()
1850 + .and_then(|ext| ext.to_str())
1851 + .map(|ext| ext == "journal")
1852 + .unwrap_or(false)
1853 + {
1854 + files.push(entry_path);
1855 + }
1856 + }
1857 + }
1858 +
1859 + let mut files = Vec::new();
1860 + collect(path, &mut files);
1861 + files.sort();
1862 + files
1863 +}
src/crates/netflow-plugin/src/memory_tests.rs
+8 -19
@@ -1,5 +1,4 @@
1 use super::{decoder, facet_runtime, ingest, plugin_config, tiering};
2 -use crate::plugin_config::RetentionLimitOverride;
2 use bytesize::ByteSize;
3 use netflow_parser::protocol::ProtocolTypes;
4 use netflow_parser::static_versions::v5::{FlowSet, Header, V5};
@@ -242,24 +241,14 @@ fn start_ingest_fixture() -> anyhow::Result<(
241 cfg.listener.listen = "127.0.0.1:0".to_string();
242 cfg.listener.sync_interval = Duration::from_millis(50);
243 cfg.listener.sync_every_entries = 256;
245 - cfg.journal.size_of_journal_files = Some(ByteSize::mb(128));
246 - cfg.journal.duration_of_journal_files = Some(Duration::from_secs(10 * 60));
247 - cfg.journal.tiers.raw = Some(plugin_config::JournalTierRetentionConfig {
248 - size_of_journal_files: RetentionLimitOverride::Value(ByteSize::mb(128)),
249 - duration_of_journal_files: RetentionLimitOverride::Value(Duration::from_secs(10 * 60)),
250 - });
251 - cfg.journal.tiers.minute_1 = Some(plugin_config::JournalTierRetentionConfig {
252 - size_of_journal_files: RetentionLimitOverride::Value(ByteSize::mb(128)),
253 - duration_of_journal_files: RetentionLimitOverride::Value(Duration::from_secs(10 * 60)),
254 - });
255 - cfg.journal.tiers.minute_5 = Some(plugin_config::JournalTierRetentionConfig {
256 - size_of_journal_files: RetentionLimitOverride::Value(ByteSize::mb(128)),
257 - duration_of_journal_files: RetentionLimitOverride::Value(Duration::from_secs(10 * 60)),
258 - });
259 - cfg.journal.tiers.hour_1 = Some(plugin_config::JournalTierRetentionConfig {
260 - size_of_journal_files: RetentionLimitOverride::Value(ByteSize::mb(128)),
261 - duration_of_journal_files: RetentionLimitOverride::Value(Duration::from_secs(10 * 60)),
262 - });
244 + let small_tier = plugin_config::JournalTierRetentionConfig {
245 + size_of_journal_files: Some(ByteSize::mb(128)),
246 + duration_of_journal_files: Some(Duration::from_secs(10 * 60)),
247 + };
248 + cfg.journal.tiers.raw = small_tier.clone();
249 + cfg.journal.tiers.minute_1 = small_tier.clone();
250 + cfg.journal.tiers.minute_5 = small_tier.clone();
251 + cfg.journal.tiers.hour_1 = small_tier;
252
253 let metrics = Arc::new(ingest::IngestMetrics::default());
254 let open_tiers = Arc::new(std::sync::RwLock::new(tiering::OpenTierState::default()));
src/crates/netflow-plugin/src/plugin_config.rs
-1
@@ -15,7 +15,6 @@ mod runtime;
15 mod types;
16 mod validation;
17
18 -pub(crate) use defaults::RetentionLimitOverride;
18 use defaults::*;
19 pub(crate) use types::*;
20
src/crates/netflow-plugin/src/plugin_config/defaults.rs
+1 -90
@@ -18,7 +18,7 @@ pub(super) fn parse_duration(value: &str) -> Result<Duration, String> {
18 pub(super) fn parse_bytesize(value: &str) -> Result<ByteSize, String> {
19 value.parse().map_err(|e| {
20 format!(
21 - "invalid size '{}' (examples: '256MB', '10GB'): {}",
21 + "invalid byte size '{}' (examples: '100MB', '10GB'): {}",
22 value, e
23 )
24 })
@@ -76,10 +76,6 @@ pub(super) fn default_query_max_groups() -> usize {
76 50_000
77 }
78
79 -pub(super) fn default_query_facet_max_values_per_field() -> usize {
80 - 5_000
81 -}
82 -
79 pub(super) fn default_network_source_tls_verify() -> bool {
80 true
81 }
@@ -108,28 +104,6 @@ pub(super) fn default_rotation_duration_of_journal_file() -> Duration {
104 Duration::from_secs(60 * 60)
105 }
106
111 -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
112 -pub(crate) enum RetentionLimitOverride<T> {
113 - #[default]
114 - Inherit,
115 - Disabled,
116 - Value(T),
117 -}
118 -
119 -impl<T: Copy> RetentionLimitOverride<T> {
120 - pub(crate) fn resolve(self, inherited: Option<T>) -> Option<T> {
121 - match self {
122 - Self::Inherit => inherited,
123 - Self::Disabled => None,
124 - Self::Value(value) => Some(value),
125 - }
126 - }
127 -
128 - pub(crate) fn is_inherit(&self) -> bool {
129 - matches!(self, Self::Inherit)
130 - }
131 -}
132 -
107 pub(super) fn deserialize_opt_bytesize<'de, D>(
108 deserializer: D,
109 ) -> Result<Option<ByteSize>, D::Error>
@@ -184,69 +158,6 @@ where
158 }
159 }
160
187 -pub(super) fn deserialize_retention_override_bytesize<'de, D>(
188 - deserializer: D,
189 -) -> Result<RetentionLimitOverride<ByteSize>, D::Error>
190 -where
191 - D: serde::Deserializer<'de>,
192 -{
193 - let value = Option::<String>::deserialize(deserializer)?;
194 - match value {
195 - Some(value) => value
196 - .parse()
197 - .map(RetentionLimitOverride::Value)
198 - .map_err(de::Error::custom),
199 - None => Ok(RetentionLimitOverride::Disabled),
200 - }
201 -}
202 -
203 -pub(super) fn deserialize_retention_override_duration<'de, D>(
204 - deserializer: D,
205 -) -> Result<RetentionLimitOverride<Duration>, D::Error>
206 -where
207 - D: serde::Deserializer<'de>,
208 -{
209 - let value = Option::<String>::deserialize(deserializer)?;
210 - match value {
211 - Some(value) => humantime::parse_duration(&value)
212 - .map(RetentionLimitOverride::Value)
213 - .map_err(de::Error::custom),
214 - None => Ok(RetentionLimitOverride::Disabled),
215 - }
216 -}
217 -
218 -pub(super) fn serialize_retention_override_bytesize<S>(
219 - value: &RetentionLimitOverride<ByteSize>,
220 - serializer: S,
221 -) -> Result<S::Ok, S::Error>
222 -where
223 - S: serde::Serializer,
224 -{
225 - match value {
226 - RetentionLimitOverride::Inherit | RetentionLimitOverride::Disabled => {
227 - serializer.serialize_none()
228 - }
229 - RetentionLimitOverride::Value(value) => serializer.serialize_some(&value.to_string()),
230 - }
231 -}
232 -
233 -pub(super) fn serialize_retention_override_duration<S>(
234 - value: &RetentionLimitOverride<Duration>,
235 - serializer: S,
236 -) -> Result<S::Ok, S::Error>
237 -where
238 - S: serde::Serializer,
239 -{
240 - match value {
241 - RetentionLimitOverride::Inherit | RetentionLimitOverride::Disabled => {
242 - serializer.serialize_none()
243 - }
244 - RetentionLimitOverride::Value(value) => {
245 - serializer.serialize_some(&humantime::format_duration(*value).to_string())
246 - }
247 - }
248 -}
249 -
161 pub(super) fn deserialize_interface_boundary<'de, D>(deserializer: D) -> Result<u8, D::Error>
162 where
163 D: serde::Deserializer<'de>,
src/crates/netflow-plugin/src/plugin_config/types/journal.rs
+115 -92
@@ -12,83 +12,78 @@ pub(crate) struct JournalConfig {
12 #[arg(long = "netflow-journal-dir", default_value = "flows")]
13 pub(crate) journal_dir: String,
14
15 - #[arg(
16 - long = "netflow-retention-size-of-journal-files",
17 - default_value = "10GB",
18 - value_parser = parse_bytesize
19 - )]
20 - #[serde(
21 - default = "default_retention_size_of_journal_files_opt",
22 - deserialize_with = "deserialize_opt_bytesize",
23 - serialize_with = "serialize_opt_bytesize"
24 - )]
25 - pub(crate) size_of_journal_files: Option<ByteSize>,
26 -
27 - #[arg(
28 - long = "netflow-retention-duration-of-journal-files",
29 - default_value = "7d",
30 - value_parser = parse_duration
31 - )]
32 - #[serde(
33 - default = "default_retention_duration_of_journal_files_opt",
34 - deserialize_with = "deserialize_opt_duration",
35 - serialize_with = "serialize_opt_duration"
36 - )]
37 - pub(crate) duration_of_journal_files: Option<Duration>,
38 -
15 + /// Per-tier retention. Each tier carries optional size_of_journal_files
16 + /// and duration_of_journal_files limits. Validation requires each
17 + /// resolved tier to have at least one positive size or duration limit.
18 + /// Tiers can be sized independently because raw and rollup tiers have
19 + /// very different storage costs and access patterns.
20 #[arg(skip)]
21 #[serde(default)]
22 pub(crate) tiers: JournalTierRetentionOverrides,
23
24 + /// CLI-only compatibility alias for standalone runs. YAML config remains
25 + /// per-tier only; this legacy flag applies the same size limit to all tiers.
26 #[arg(
44 - long = "netflow-query-1m-max-window",
45 - default_value = "6h",
46 - value_parser = parse_duration
27 + long = "netflow-retention-size-of-journal-files",
28 + value_parser = parse_bytesize
29 )]
48 - #[serde(with = "humantime_serde")]
49 - pub(crate) query_1m_max_window: Duration,
30 + #[serde(skip)]
31 + pub(crate) cli_retention_size_of_journal_files: Option<ByteSize>,
32
33 + /// CLI-only compatibility alias for standalone runs. YAML config remains
34 + /// per-tier only; this legacy flag applies the same time limit to all tiers.
35 #[arg(
52 - long = "netflow-query-5m-max-window",
53 - default_value = "24h",
36 + long = "netflow-retention-duration-of-journal-files",
37 value_parser = parse_duration
38 )]
56 - #[serde(with = "humantime_serde")]
57 - pub(crate) query_5m_max_window: Duration,
58 -
39 + #[serde(skip)]
40 + pub(crate) cli_retention_duration_of_journal_files: Option<Duration>,
41 +
42 + /// Caps the number of distinct group keys a single aggregation
43 + /// query may build before extra groups are folded into a
44 + /// synthetic `__overflow__` bucket. Protects the query worker
45 + /// from accidentally wide group-by combinations exhausting
46 + /// memory.
47 #[arg(long = "netflow-query-max-groups", default_value_t = 50_000)]
48 #[serde(default = "default_query_max_groups", alias = "query-max-groups")]
49 pub(crate) query_max_groups: usize,
62 -
63 - #[arg(
64 - long = "netflow-query-facet-max-values-per-field",
65 - default_value_t = 5_000
66 - )]
67 - #[serde(
68 - default = "default_query_facet_max_values_per_field",
69 - alias = "query-facet-max-values-per-field"
70 - )]
71 - pub(crate) query_facet_max_values_per_field: usize,
50 }
51
74 -#[derive(Debug, Clone, Default, Serialize, Deserialize)]
52 +#[derive(Debug, Clone, Serialize, Deserialize)]
53 #[serde(deny_unknown_fields)]
54 pub(crate) struct JournalTierRetentionConfig {
55 + /// Hard size cap. Unset (`null`) disables the size limit; the tier
56 + /// is then bounded only by `duration_of_journal_files`. Defaults to
57 + /// the tier-default size if the field is omitted.
58 #[serde(
78 - default,
79 - deserialize_with = "deserialize_retention_override_bytesize",
80 - serialize_with = "serialize_retention_override_bytesize",
81 - skip_serializing_if = "RetentionLimitOverride::is_inherit"
59 + default = "default_retention_size_of_journal_files_opt",
60 + deserialize_with = "deserialize_opt_bytesize",
61 + serialize_with = "serialize_opt_bytesize"
62 )]
83 - pub(crate) size_of_journal_files: RetentionLimitOverride<ByteSize>,
63 + pub(crate) size_of_journal_files: Option<ByteSize>,
64
65 + /// Maximum age. Unset (`null`) disables the duration limit; the
66 + /// tier is then bounded only by `size_of_journal_files`. Defaults
67 + /// to the tier-default duration if the field is omitted.
68 #[serde(
86 - default,
87 - deserialize_with = "deserialize_retention_override_duration",
88 - serialize_with = "serialize_retention_override_duration",
89 - skip_serializing_if = "RetentionLimitOverride::is_inherit"
69 + default = "default_retention_duration_of_journal_files_opt",
70 + deserialize_with = "deserialize_opt_duration",
71 + serialize_with = "serialize_opt_duration"
72 )]
91 - pub(crate) duration_of_journal_files: RetentionLimitOverride<Duration>,
73 + pub(crate) duration_of_journal_files: Option<Duration>,
74 +}
75 +
76 +impl JournalTierRetentionConfig {
77 + pub(crate) fn for_tier(_tier: TierKind) -> Self {
78 + // Tier-uniform defaults today (10GB, 7d). The shape is per-tier
79 + // so each tier can be tuned independently in user config; the
80 + // built-in defaults happen to be uniform across tiers but
81 + // nothing in the schema enforces that.
82 + Self {
83 + size_of_journal_files: default_retention_size_of_journal_files_opt(),
84 + duration_of_journal_files: default_retention_duration_of_journal_files_opt(),
85 + }
86 + }
87 }
88
89 #[derive(Debug, Clone, Default, PartialEq, Eq)]
@@ -103,44 +98,83 @@ impl ResolvedJournalTierRetention {
98 }
99 }
100
106 -#[derive(Debug, Clone, Default, Serialize, Deserialize)]
101 +#[derive(Debug, Clone, Serialize, Deserialize)]
102 #[serde(deny_unknown_fields)]
103 pub(crate) struct JournalTierRetentionOverrides {
109 - #[serde(default)]
110 - pub(crate) raw: Option<JournalTierRetentionConfig>,
104 + #[serde(default = "default_raw_tier")]
105 + pub(crate) raw: JournalTierRetentionConfig,
106
112 - #[serde(default, alias = "1m", alias = "minute-1", alias = "minute1")]
113 - pub(crate) minute_1: Option<JournalTierRetentionConfig>,
107 + #[serde(
108 + default = "default_minute_1_tier",
109 + alias = "1m",
110 + alias = "minute-1",
111 + alias = "minute1"
112 + )]
113 + pub(crate) minute_1: JournalTierRetentionConfig,
114
115 - #[serde(default, alias = "5m", alias = "minute-5", alias = "minute5")]
116 - pub(crate) minute_5: Option<JournalTierRetentionConfig>,
115 + #[serde(
116 + default = "default_minute_5_tier",
117 + alias = "5m",
118 + alias = "minute-5",
119 + alias = "minute5"
120 + )]
121 + pub(crate) minute_5: JournalTierRetentionConfig,
122
118 - #[serde(default, alias = "1h", alias = "hour-1", alias = "hour1")]
119 - pub(crate) hour_1: Option<JournalTierRetentionConfig>,
123 + #[serde(
124 + default = "default_hour_1_tier",
125 + alias = "1h",
126 + alias = "hour-1",
127 + alias = "hour1"
128 + )]
129 + pub(crate) hour_1: JournalTierRetentionConfig,
130 +}
131 +
132 +impl Default for JournalTierRetentionOverrides {
133 + fn default() -> Self {
134 + Self {
135 + raw: JournalTierRetentionConfig::for_tier(TierKind::Raw),
136 + minute_1: JournalTierRetentionConfig::for_tier(TierKind::Minute1),
137 + minute_5: JournalTierRetentionConfig::for_tier(TierKind::Minute5),
138 + hour_1: JournalTierRetentionConfig::for_tier(TierKind::Hour1),
139 + }
140 + }
141 }
142
143 impl JournalTierRetentionOverrides {
123 - pub(crate) fn get(&self, tier: TierKind) -> Option<&JournalTierRetentionConfig> {
144 + pub(crate) fn get(&self, tier: TierKind) -> &JournalTierRetentionConfig {
145 match tier {
125 - TierKind::Raw => self.raw.as_ref(),
126 - TierKind::Minute1 => self.minute_1.as_ref(),
127 - TierKind::Minute5 => self.minute_5.as_ref(),
128 - TierKind::Hour1 => self.hour_1.as_ref(),
146 + TierKind::Raw => &self.raw,
147 + TierKind::Minute1 => &self.minute_1,
148 + TierKind::Minute5 => &self.minute_5,
149 + TierKind::Hour1 => &self.hour_1,
150 }
151 }
152 }
153
154 +fn default_raw_tier() -> JournalTierRetentionConfig {
155 + JournalTierRetentionConfig::for_tier(TierKind::Raw)
156 +}
157 +
158 +fn default_minute_1_tier() -> JournalTierRetentionConfig {
159 + JournalTierRetentionConfig::for_tier(TierKind::Minute1)
160 +}
161 +
162 +fn default_minute_5_tier() -> JournalTierRetentionConfig {
163 + JournalTierRetentionConfig::for_tier(TierKind::Minute5)
164 +}
165 +
166 +fn default_hour_1_tier() -> JournalTierRetentionConfig {
167 + JournalTierRetentionConfig::for_tier(TierKind::Hour1)
168 +}
169 +
170 impl Default for JournalConfig {
171 fn default() -> Self {
172 Self {
173 journal_dir: "flows".to_string(),
137 - size_of_journal_files: default_retention_size_of_journal_files_opt(),
138 - duration_of_journal_files: default_retention_duration_of_journal_files_opt(),
174 tiers: JournalTierRetentionOverrides::default(),
140 - query_1m_max_window: Duration::from_secs(6 * 60 * 60),
141 - query_5m_max_window: Duration::from_secs(24 * 60 * 60),
175 + cli_retention_size_of_journal_files: None,
176 + cli_retention_duration_of_journal_files: None,
177 query_max_groups: default_query_max_groups(),
143 - query_facet_max_values_per_field: default_query_facet_max_values_per_field(),
178 }
179 }
180 }
@@ -179,26 +213,15 @@ impl JournalConfig {
213 ]
214 }
215
182 - pub(crate) fn default_retention(&self) -> ResolvedJournalTierRetention {
183 - ResolvedJournalTierRetention {
184 - size_of_journal_files: self.size_of_journal_files,
185 - duration_of_journal_files: self.duration_of_journal_files,
186 - }
187 - }
188 -
216 pub(crate) fn retention_for_tier(&self, tier: TierKind) -> ResolvedJournalTierRetention {
190 - let defaults = self.default_retention();
191 - let Some(overrides) = self.tiers.get(tier) else {
192 - return defaults;
193 - };
194 -
217 + let cfg = self.tiers.get(tier);
218 ResolvedJournalTierRetention {
196 - size_of_journal_files: overrides
197 - .size_of_journal_files
198 - .resolve(defaults.size_of_journal_files),
199 - duration_of_journal_files: overrides
200 - .duration_of_journal_files
201 - .resolve(defaults.duration_of_journal_files),
219 + size_of_journal_files: self
220 + .cli_retention_size_of_journal_files
221 + .or(cfg.size_of_journal_files),
222 + duration_of_journal_files: self
223 + .cli_retention_duration_of_journal_files
224 + .or(cfg.duration_of_journal_files),
225 }
226 }
227
src/crates/netflow-plugin/src/plugin_config/validation/journal.rs
-12
@@ -3,21 +3,9 @@ use crate::tiering::TierKind;
3 use anyhow::Result;
4
5 pub(super) fn validate_journal(cfg: &PluginConfig) -> Result<()> {
6 - if cfg.journal.query_1m_max_window.is_zero() {
7 - anyhow::bail!("journal.query_1m_max_window must be greater than 0");
8 - }
9 - if cfg.journal.query_5m_max_window.is_zero() {
10 - anyhow::bail!("journal.query_5m_max_window must be greater than 0");
11 - }
12 - if cfg.journal.query_5m_max_window < cfg.journal.query_1m_max_window {
13 - anyhow::bail!("journal.query_5m_max_window must be >= journal.query_1m_max_window");
14 - }
6 if cfg.journal.query_max_groups == 0 {
7 anyhow::bail!("journal.query_max_groups must be greater than 0");
8 }
18 - if cfg.journal.query_facet_max_values_per_field == 0 {
19 - anyhow::bail!("journal.query_facet_max_values_per_field must be greater than 0");
20 - }
9
10 for (scope, tier) in [
11 ("journal.tiers.raw", TierKind::Raw),
src/crates/netflow-plugin/src/plugin_config_tests.rs
+69 -50
@@ -1,4 +1,5 @@
1 use super::*;
2 +use clap::Parser;
3 use tempfile::tempdir;
4
5 #[test]
@@ -187,18 +188,6 @@ fn validate_rejects_zero_query_max_groups() {
188 );
189 }
190
190 -#[test]
191 -fn validate_rejects_zero_query_facet_max_values_per_field() {
192 - let mut cfg = PluginConfig::default();
193 - cfg.journal.query_facet_max_values_per_field = 0;
194 -
195 - let err = cfg.validate().expect_err("expected validation error");
196 - assert!(
197 - err.to_string()
198 - .contains("journal.query_facet_max_values_per_field must be greater than 0")
199 - );
200 -}
201 -
191 #[test]
192 fn plugin_enabled_defaults_to_true() {
193 let cfg = PluginConfig::default();
@@ -242,12 +231,7 @@ protocols:
231 timestamp_source: input
232 journal:
233 journal_dir: flows
245 - size_of_journal_files: 10GB
246 - duration_of_journal_files: 7d
247 - query_1m_max_window: 6h
248 - query_5m_max_window: 24h
234 query_max_groups: 50000
250 - query_facet_max_values_per_field: 5000
235 "#;
236
237 let cfg: PluginConfig = serde_yaml::from_str(yaml).expect("yaml should parse");
@@ -255,10 +239,8 @@ journal:
239 }
240
241 #[test]
258 -fn journal_tier_retention_inherits_global_defaults_when_no_overrides_exist() {
259 - let mut cfg = PluginConfig::default();
260 - cfg.journal.size_of_journal_files = Some(ByteSize::gb(11));
261 - cfg.journal.duration_of_journal_files = Some(Duration::from_secs(11 * 24 * 60 * 60));
242 +fn journal_tier_retention_uses_built_in_tier_defaults() {
243 + let cfg = PluginConfig::default();
244
245 let raw = cfg.journal.retention_for_tier(TierKind::Raw);
246 let minute_1 = cfg.journal.retention_for_tier(TierKind::Minute1);
@@ -268,26 +250,22 @@ fn journal_tier_retention_inherits_global_defaults_when_no_overrides_exist() {
250 for retention in [raw, minute_1, minute_5, hour_1] {
251 assert_eq!(
252 retention.size_of_journal_files.unwrap().as_u64(),
271 - ByteSize::gb(11).as_u64()
253 + ByteSize::gb(10).as_u64()
254 );
255 assert_eq!(
256 retention.duration_of_journal_files.unwrap(),
275 - Duration::from_secs(11 * 24 * 60 * 60)
257 + Duration::from_secs(7 * 24 * 60 * 60)
258 );
259 }
260 }
261
262 #[test]
281 -fn journal_tier_retention_uses_tier_override_when_present() {
263 +fn journal_tier_retention_uses_per_tier_values_when_present() {
264 let mut cfg = PluginConfig::default();
283 - cfg.journal.size_of_journal_files = Some(ByteSize::gb(11));
284 - cfg.journal.duration_of_journal_files = Some(Duration::from_secs(11 * 24 * 60 * 60));
285 - cfg.journal.tiers.raw = Some(JournalTierRetentionConfig {
286 - size_of_journal_files: RetentionLimitOverride::Value(ByteSize::gb(2)),
287 - duration_of_journal_files: RetentionLimitOverride::Value(Duration::from_secs(
288 - 2 * 24 * 60 * 60,
289 - )),
290 - });
265 + cfg.journal.tiers.raw = JournalTierRetentionConfig {
266 + size_of_journal_files: Some(ByteSize::gb(2)),
267 + duration_of_journal_files: Some(Duration::from_secs(2 * 24 * 60 * 60)),
268 + };
269
270 let raw = cfg.journal.retention_for_tier(TierKind::Raw);
271 assert_eq!(
@@ -299,34 +277,66 @@ fn journal_tier_retention_uses_tier_override_when_present() {
277 Duration::from_secs(2 * 24 * 60 * 60)
278 );
279
280 + // Other tiers untouched -- still at the built-in defaults.
281 let minute_1 = cfg.journal.retention_for_tier(TierKind::Minute1);
282 assert_eq!(
283 minute_1.size_of_journal_files.unwrap().as_u64(),
305 - ByteSize::gb(11).as_u64()
284 + ByteSize::gb(10).as_u64()
285 );
286 assert_eq!(
287 minute_1.duration_of_journal_files.unwrap(),
309 - Duration::from_secs(11 * 24 * 60 * 60)
288 + Duration::from_secs(7 * 24 * 60 * 60)
289 );
290 }
291
292 +#[test]
293 +fn journal_cli_retention_aliases_apply_to_all_tiers() {
294 + let cfg = PluginConfig::try_parse_from([
295 + "netflow-plugin",
296 + "--netflow-retention-size-of-journal-files",
297 + "20GB",
298 + "--netflow-retention-duration-of-journal-files",
299 + "2d",
300 + ])
301 + .expect("CLI config should parse");
302 +
303 + for tier in [
304 + TierKind::Raw,
305 + TierKind::Minute1,
306 + TierKind::Minute5,
307 + TierKind::Hour1,
308 + ] {
309 + let retention = cfg.journal.retention_for_tier(tier);
310 + assert_eq!(
311 + retention.size_of_journal_files.unwrap().as_u64(),
312 + ByteSize::gb(20).as_u64(),
313 + "unexpected size retention for {tier:?}"
314 + );
315 + assert_eq!(
316 + retention.duration_of_journal_files.unwrap(),
317 + Duration::from_secs(2 * 24 * 60 * 60),
318 + "unexpected duration retention for {tier:?}"
319 + );
320 + }
321 +}
322 +
323 #[test]
324 fn journal_rotation_size_derives_from_tier_size_budget() {
325 let mut cfg = PluginConfig::default();
316 - cfg.journal.size_of_journal_files = Some(ByteSize::gb(1));
326 + cfg.journal.tiers.raw.size_of_journal_files = Some(ByteSize::gb(1));
327
328 assert_eq!(
329 cfg.journal.rotation_size_for_tier(TierKind::Raw),
330 ByteSize::mb(50).as_u64()
331 );
332
323 - cfg.journal.size_of_journal_files = Some(ByteSize::gb(4));
333 + cfg.journal.tiers.raw.size_of_journal_files = Some(ByteSize::gb(4));
334 assert_eq!(
335 cfg.journal.rotation_size_for_tier(TierKind::Raw),
336 ByteSize::mb(200).as_u64()
337 );
338
329 - cfg.journal.size_of_journal_files = Some(ByteSize::gb(10));
339 + cfg.journal.tiers.raw.size_of_journal_files = Some(ByteSize::gb(10));
340 assert_eq!(
341 cfg.journal.rotation_size_for_tier(TierKind::Raw),
342 ByteSize::mb(200).as_u64()
@@ -336,8 +346,8 @@ fn journal_rotation_size_derives_from_tier_size_budget() {
346 #[test]
347 fn journal_rotation_size_uses_100mb_for_time_only_retention() {
348 let mut cfg = PluginConfig::default();
339 - cfg.journal.size_of_journal_files = None;
340 - cfg.journal.duration_of_journal_files = Some(Duration::from_secs(7 * 24 * 60 * 60));
349 + cfg.journal.tiers.raw.size_of_journal_files = None;
350 + cfg.journal.tiers.raw.duration_of_journal_files = Some(Duration::from_secs(7 * 24 * 60 * 60));
351
352 assert_eq!(
353 cfg.journal.rotation_size_for_tier(TierKind::Raw),
@@ -348,37 +358,41 @@ fn journal_rotation_size_uses_100mb_for_time_only_retention() {
358 #[test]
359 fn journal_validation_rejects_tier_size_below_100mb() {
360 let mut cfg = PluginConfig::default();
351 - cfg.journal.size_of_journal_files = Some(ByteSize::mb(99));
361 + cfg.journal.tiers.raw.size_of_journal_files = Some(ByteSize::mb(99));
362
363 let err = cfg.validate().expect_err("expected validation error");
364 assert!(
365 err.to_string()
356 - .contains("journal.tiers.raw.size_of_journal_files must be at least 100MB")
366 + .contains("journal.tiers.raw.size_of_journal_files must be at least 100MB"),
367 + "got: {err}"
368 );
369 }
370
371 #[test]
372 fn journal_validation_allows_time_only_retention_when_size_is_disabled() {
373 let mut cfg = PluginConfig::default();
363 - cfg.journal.size_of_journal_files = None;
364 - cfg.journal.duration_of_journal_files = Some(Duration::from_secs(7 * 24 * 60 * 60));
374 + for tier_cfg in [
375 + &mut cfg.journal.tiers.raw,
376 + &mut cfg.journal.tiers.minute_1,
377 + &mut cfg.journal.tiers.minute_5,
378 + &mut cfg.journal.tiers.hour_1,
379 + ] {
380 + tier_cfg.size_of_journal_files = None;
381 + tier_cfg.duration_of_journal_files = Some(Duration::from_secs(7 * 24 * 60 * 60));
382 + }
383
384 cfg.validate().expect("time-only retention should be valid");
385 }
386
387 #[test]
370 -fn journal_tier_retention_null_override_disables_inherited_size_limit() {
388 +fn journal_tier_retention_null_disables_size_limit_for_that_tier_only() {
389 let yaml = r#"
390 journal_dir: flows
373 -size_of_journal_files: 10GB
374 -duration_of_journal_files: 7d
375 -query_1m_max_window: 6h
376 -query_5m_max_window: 24h
391 query_max_groups: 50000
378 -query_facet_max_values_per_field: 5000
392 tiers:
393 raw:
394 size_of_journal_files: null
395 + duration_of_journal_files: 24h
396 "#;
397
398 let cfg: JournalConfig = serde_yaml::from_str(yaml).expect("journal config should parse");
@@ -388,9 +402,14 @@ tiers:
402 assert_eq!(raw.size_of_journal_files, None);
403 assert_eq!(
404 raw.duration_of_journal_files,
391 - Some(Duration::from_secs(7 * 24 * 60 * 60))
405 + Some(Duration::from_secs(24 * 60 * 60))
406 );
407 + // Other tiers still at the built-in 10GB / 7d defaults.
408 assert_eq!(minute_1.size_of_journal_files, Some(ByteSize::gb(10)));
409 + assert_eq!(
410 + minute_1.duration_of_journal_files,
411 + Some(Duration::from_secs(7 * 24 * 60 * 60))
412 + );
413 }
414
415 #[test]
src/crates/netflow-plugin/src/query/tests.rs
+2 -6
@@ -543,9 +543,7 @@ fn request_deserialization_supports_autocomplete_mode() {
543 #[test]
544 fn request_deserialization_rejects_oversized_autocomplete_term() {
545 let term = "x".repeat(257);
546 - let payload = format!(
547 - r#"{{"mode":"autocomplete","field":"src_as_name","term":"{term}"}}"#
548 - );
546 + let payload = format!(r#"{{"mode":"autocomplete","field":"src_as_name","term":"{term}"}}"#);
547 let error = serde_json::from_str::<FlowsRequest>(&payload)
548 .expect_err("oversized autocomplete term should be rejected");
549 assert!(
@@ -559,9 +557,7 @@ fn request_deserialization_accepts_long_term_for_non_autocomplete_mode() {
557 // The 256-byte cap is autocomplete-only. Regular flows/timeseries requests
558 // may carry an ignored `term` of any length and must not be rejected.
559 let term = "x".repeat(1024);
562 - let payload = format!(
563 - r#"{{"mode":"flows","view":"table-sankey","term":"{term}"}}"#
564 - );
560 + let payload = format!(r#"{{"mode":"flows","view":"table-sankey","term":"{term}"}}"#);
561 let request = serde_json::from_str::<FlowsRequest>(&payload)
562 .expect("non-autocomplete request with long term must deserialize");
563 assert!(!request.is_autocomplete_mode());
src/crates/netflow-plugin/src/startup_memory_tests.rs
+2 -2
@@ -278,8 +278,8 @@ fn profile_config(base_dir: &Path) -> plugin_config::PluginConfig {
278 cfg.listener.listen = "127.0.0.1:0".to_string();
279 cfg.listener.sync_every_entries = 1024;
280 cfg.listener.sync_interval = std::time::Duration::from_secs(1);
281 - cfg.journal.size_of_journal_files = Some(ByteSize::gb(10));
282 - cfg.journal.duration_of_journal_files = Some(std::time::Duration::from_secs(7 * 24 * 60 * 60));
281 + // Per-tier defaults (10GB / 7d) already applied by JournalConfig::default;
282 + // this call leaves them at the defaults, matching the original test intent.
283 cfg
284 }
285