master
md 483 lines 17.8 KB
Rendered Raw
1 <!-- markdownlint-disable-file MD013 MD043 -->
2
3 # `netflow-plugin`
4
5 Rust NetFlow/IPFIX/sFlow ingestion and query plugin.
6
7 It stores flow entries in journal tiers under the Netdata cache directory and exposes
8 `flows:netflow`.
9
10 ## Offline Function test mode
11
12 Fixture harnesses can execute the Function query path directly against an existing
13 NetFlow backend directory:
14
15 ```sh
16 netflow-plugin --test flows:netflow --dir <flows-dir> [--timeout <seconds>] [--no-persist] < payload.json
17 ```
18
19 Requirements:
20
21 - `<flows-dir>` is the NetFlow backend root containing the `raw`, `1m`, `5m`, and `1h` tier directories.
22 - stdin is the JSON Function request body (non-empty, maximum 16 MiB).
23 - `--request` is not supported and fails with usage output.
24 - `--timeout <seconds>` controls the offline Function execution timeout. It
25 defaults to `30`; use `--timeout 0` to map to a very large finite timeout for
26 long-running fixture comparisons.
27 - stdout contains only the raw JSON Function response.
28 - errors are written to stderr and return non-zero.
29
30 Use `--no-persist` for shared fixture datasets. It prevents the test run from writing
31 facet state or sidecar files under `<flows-dir>` while keeping facet data in memory for
32 the Function response. Without `--no-persist`, the plugin may refresh facet state under
33 the backend directory, matching normal runtime behavior.
34
35 Function output includes volatile fields such as collection timestamps and runtime
36 statistics. Test harnesses should normalize those fields before comparing fixture
37 outputs.
38
39 ## Configuration
40
41 When running under Netdata, config is loaded from `netflow.yaml` in:
42
43 - `${NETDATA_USER_CONFIG_DIR}/netflow.yaml` (preferred)
44 - `${NETDATA_STOCK_CONFIG_DIR}/netflow.yaml` (fallback)
45
46 Top-level toggle:
47
48 - `enabled: true|false` controls the netflow plugin itself.
49
50 If `journal.journal_dir` is relative (default: `flows`), it is resolved against
51 `NETDATA_CACHE_DIR`. With the standard cache directory this becomes:
52
53 - `/var/cache/netdata/flows/raw`
54 - `/var/cache/netdata/flows/1m`
55 - `/var/cache/netdata/flows/5m`
56 - `/var/cache/netdata/flows/1h`
57
58 If `enrichment.geoip` does not define explicit MMDB paths, the plugin auto-detects
59 packaged databases in this order:
60
61 - `${NETDATA_CACHE_DIR}/topology-ip-intel`
62 - `${NETDATA_STOCK_DATA_DIR}/topology-ip-intel`
63
64 `netdata-plugin-netflow` now ships a stock MMDB seed set under
65 `${NETDATA_STOCK_DATA_DIR}/topology-ip-intel`. Freshly downloaded data written by
66 `topology-ip-intel-downloader` stays in `${NETDATA_CACHE_DIR}/topology-ip-intel` and
67 overrides the stock copy automatically.
68
69 Important:
70
71 - packaged installs ship the stock MMDB payload
72 - source installs from a Git checkout do not include the generated stock MMDBs by default
73 - local/source installs should run `topology-ip-intel-downloader` if they want a local cache copy
74 - packaged 32-bit installs still ship the stock MMDB payload, but do not include `topology-ip-intel-downloader`
75
76 ### `protocols.decapsulation_mode`
77
78 Controls packet decapsulation for datalink payload parsing:
79
80 - `none` (default): keep outer header view.
81 - `srv6`: enable SRv6 decapsulation for supported payloads.
82 - `vxlan`: enable VXLAN decapsulation for supported payloads.
83
84 ### `protocols.timestamp_source`
85
86 Controls which timestamp is written as `_SOURCE_REALTIME_TIMESTAMP` for decoded flows:
87
88 - `input` (default): packet receive time at ingestion.
89 - `netflow_packet`: NetFlow/IPFIX packet export timestamp.
90 - `netflow_first_switched`: first-switched timestamp from flow fields when available.
91
92 ### `enrichment` (Akvorado-style static metadata and sampling)
93
94 Optional enrichment is applied at ingestion time. When enabled, it follows Akvorado core behavior
95 for static metadata and sampling defaults/overrides:
96
97 - metadata lookup by exporter prefix + interface index
98 - sampling override/default by exporter prefix
99 - exporter/interface classifier rules (`exporter_classifiers`, `interface_classifiers`)
100 - classifier cache TTL (`classifier_cache_duration`, default `5m`, minimum `1s`)
101 - routing static lookup for AS/mask/next-hop/path/community fields (`routing_static`)
102 - routing dynamic lookup from BMP updates (`routing_dynamic.bmp`)
103 - BMP decode-error tolerance is configurable (`routing_dynamic.bmp.max_consecutive_decode_errors`)
104 - network attribute lookup by source/destination IP prefix (`networks`)
105 - GeoIP lookup from local MMDB files (`geoip`)
106 - periodic remote network source refresh (`network_sources`)
107 - network attribute merge order: GeoIP base, then remote `network_sources`, then static `networks`
108 - static `networks` supernet/subnet inheritance (more specific prefixes override non-empty fields)
109 - AS and net provider order controls (`asn_providers`, `net_providers`)
110 - flows are dropped when metadata/sampling requirements are not met
111 - when both static and dynamic routes match, dynamic routes are preferred
112
113 Example:
114
115 ```yaml
116 enrichment:
117 classifier_cache_duration: 5m
118 default_sampling_rate:
119 192.0.2.0/24: 1000
120 override_sampling_rate:
121 192.0.2.128/25: 4000
122 metadata_static:
123 exporters:
124 192.0.2.0/24:
125 name: edge-router
126 region: eu
127 role: peering
128 tenant: tenant-a
129 site: par
130 group: blue
131 default:
132 name: Default0
133 description: Default interface
134 speed: 1000
135 if_indexes:
136 10:
137 name: Gi10
138 description: 10th interface
139 speed: 1000
140 provider: transit-a
141 connectivity: transit
142 boundary: external
143 geoip:
144 asn_database:
145 - /usr/share/GeoIP/GeoLite2-ASN.mmdb
146 geo_database:
147 - /usr/share/GeoIP/GeoLite2-City.mmdb
148 optional: true
149 networks:
150 198.51.100.0/24:
151 name: customer-a
152 role: customer
153 site: par1
154 region: eu-west
155 country: FR
156 state: Ile-de-France
157 city: Paris
158 tenant: tenant-a
159 asn: 64500
160 203.0.113.0/24: transit-a
161 network_sources:
162 amazon:
163 url: "https://ip-ranges.amazonaws.com/ip-ranges.json"
164 method: GET
165 tls:
166 enable: true
167 verify: true
168 ca_file: ""
169 cert_file: ""
170 key_file: ""
171 interval: 10m
172 timeout: 30s
173 transform: |
174 (.prefixes + .ipv6_prefixes)[] |
175 { prefix: (.ip_prefix // .ipv6_prefix), tenant: "amazon", region: .region, role: .service|ascii_downcase }
176 headers:
177 X-Example: "value"
178 asn_providers: [flow, routing, geoip]
179 net_providers: [flow, routing]
180 routing_static:
181 prefixes:
182 198.51.100.0/24:
183 asn: 64600
184 as_path: [64550, 64600]
185 communities: [123456, 654321]
186 large_communities:
187 - asn: 64600
188 local_data1: 7
189 local_data2: 8
190 next_hop: 203.0.113.9
191 routing_dynamic:
192 bmp:
193 enabled: true
194 listen: "0.0.0.0:10179"
195 receive_buffer: 0 # bytes; 0 keeps kernel default
196 max_consecutive_decode_errors: 8
197 rds: ["0", "65000:100", "192.0.2.1:42"]
198 collect_asns: true
199 collect_as_paths: true
200 collect_communities: true
201 keep: 5m
202 bioris:
203 enabled: false
204 ris_instances: []
205 timeout: 200ms
206 refresh: 30m
207 refresh_timeout: 10s
208 ```
209
210 `routing_dynamic.bioris` is implemented as stream-based route ingestion:
211
212 - the plugin periodically discovers routers with `GetRouters`
213 - it consumes `DumpRIB` streams per router/AFI for baseline reconciliation
214 - it keeps `ObserveRIB` streams per router/AFI for incremental updates between refreshes
215 - enrichment keeps lookup local/in-memory (no per-flow remote RPC on the ingestion hot path)
216
217 `network_sources.*.transform` accepts jq expressions (compiled/executed via `jaq`) and
218 should emit objects with fields compatible with:
219
220 - `prefix` (required)
221 - `name`, `role`, `site`, `region`, `country`, `state`, `city`, `tenant`, `asn`, `asn_name` (optional)
222
223 `network_sources.*.tls` follows Akvorado-style source TLS controls:
224
225 - `enable` toggles TLS settings for this source.
226 - `verify` must remain `true`; disabling TLS certificate verification is not supported.
227 - `skip_verify` is accepted only for compatibility with earlier drafts and must remain `false`.
228 - `ca_file` sets a custom CA bundle.
229 - `cert_file` and `key_file` set optional client identity (if `key_file` is empty, `cert_file` is reused).
230
231 Example:
232
233 ```yaml
234 enabled: true
235
236 listener:
237 listen: "0.0.0.0:2055"
238
239 protocols:
240 v5: true
241 v7: true
242 v9: true
243 ipfix: true
244 sflow: true
245 decapsulation_mode: srv6
246 timestamp_source: input
247
248 journal:
249 journal_dir: flows
250 tiers:
251 raw:
252 size_of_journal_files: 200GB
253 duration_of_journal_files: 24h
254 minute_1:
255 size_of_journal_files: 40GB
256 duration_of_journal_files: 14d
257 minute_5:
258 size_of_journal_files: 30GB
259 duration_of_journal_files: 30d
260 hour_1:
261 size_of_journal_files: 20GB
262 duration_of_journal_files: 365d
263 query_max_groups: 50000
264 ```
265
266 Standalone CLI runs still accept the legacy uniform retention flags
267 `--netflow-retention-size-of-journal-files` and
268 `--netflow-retention-duration-of-journal-files`. They apply the same value to
269 all tiers and exist only for standalone/CLI compatibility; YAML configuration is
270 per-tier.
271
272 `query_max_groups` caps the number of distinct group keys a single
273 aggregation query may build before extra groups are folded into a synthetic
274 `__overflow__` bucket. The response carries a warning when this happens. The
275 limit protects the query worker from accidentally wide group-by combinations
276 exhausting memory.
277
278 Journal rotation size is not user-configured. The plugin derives it per tier:
279
280 - if `size_of_journal_files` is set, rotation size is `clamp(size / 20, 5MB, 200MB)`
281 - `size_of_journal_files` must be at least `100MB`
282 - if `size_of_journal_files` is omitted or explicitly set to `null`, the plugin
283 uses a fixed internal rotation size of `100MB`
284 - the internal time-based rotation cadence remains `1h`
285
286 The plugin also exposes internal memory charts to help diagnose resident growth
287 in production:
288
289 - `netflow.memory_resident_bytes`
290 - `rss`, `hwm`
291 - `rss_anon`, `rss_file`, `rss_shmem`, `anon_huge_pages`
292 - `netflow.memory_resident_mapping_bytes`
293 - resident heap bytes
294 - anonymous non-heap mappings
295 - raw journal mmap bytes
296 - `1m` journal mmap bytes
297 - `5m` journal mmap bytes
298 - `1h` journal mmap bytes
299 - GeoIP ASN MMDB resident bytes
300 - GeoIP geo/country MMDB resident bytes
301 - other file-backed mappings
302 - shmem mappings
303 - `netflow.memory_allocator_bytes`
304 - `heap_in_use`, `heap_free`, `heap_arena`
305 - `mmap_in_use`, `releasable`
306 - `netflow.memory_accounted_bytes`
307 - facet runtime archived/active/contribution/published/path buckets
308 - materialized tier indexes
309 - open tier rows
310 - GeoIP ASN MMDB resident bytes
311 - GeoIP geo/country MMDB resident bytes
312 - unaccounted process RSS remainder
313 - `netflow.memory_tier_index_bytes`
314 - tier row storage
315 - tier field dictionaries
316 - tier lookup tables
317 - tier schema/index metadata
318 - `netflow.decoder_scopes`
319 - NetFlow v9 parser scopes
320 - IPFIX parser scopes
321 - legacy parser scopes
322 - persisted decoder namespaces
323 - hydrated namespace-source mappings
324
325 These charts are intended for debugging memory explosions under high-cardinality
326 traffic, not for billing or hard enforcement decisions.
327
328 `journal.tiers` configures retention independently for:
329
330 - `raw`
331 - `minute_1` (aliases: `1m`, `minute-1`, `minute1`)
332 - `minute_5` (aliases: `5m`, `minute-5`, `minute5`)
333 - `hour_1` (aliases: `1h`, `hour-1`, `hour1`)
334
335 If a tier is omitted, it uses the built-in tier default (`10GB / 7d`). There
336 are no top-level journal retention knobs; set retention on each tier you want
337 to tune.
338
339 To make a tier time-only, set `size_of_journal_files: null`.
340 To make a tier size-only, set `duration_of_journal_files: null`.
341
342 ## Performance benchmarking
343
344 The plugin ships two complementary benchmarks:
345
346 - `cargo test -p netflow-plugin --manifest-path src/crates/Cargo.toml --release ingest::bench_tests::bench_ingestion_protocol_matrix -- --ignored --nocapture`
347 unpaced full UDP→journal max throughput per protocol, plus decode-only and
348 post-decode phases
349 - `cargo test -p netflow-plugin --manifest-path src/crates/Cargo.toml --release ingest::resource_bench_tests::bench_resource_envelope_child -- --ignored --nocapture`
350 paced post-decode resource envelope at a configurable rate, controlled via
351 env vars: `NETFLOW_RESOURCE_BENCH_PROTOCOL`, `NETFLOW_RESOURCE_BENCH_PROFILE`,
352 `NETFLOW_RESOURCE_BENCH_LAYER`, `NETFLOW_RESOURCE_BENCH_FLOWS_PER_SEC`,
353 `NETFLOW_RESOURCE_BENCH_WARMUP_SECS`, `NETFLOW_RESOURCE_BENCH_MEASURE_SECS`
354
355 The resource-envelope benchmark scope:
356
357 - pre-decoded flow records pushed into the ingest pipeline (UDP receive and
358 protocol decode are excluded; measure decode separately with the protocol
359 matrix benchmark)
360 - full pipeline active: raw journal + 1-minute + 5-minute + 1-hour tier
361 accumulation, real disk-backed journals
362 - enrichment is NOT loaded (no GeoIP MMDB, no static metadata, no classifiers,
363 no static networks); cardinality fields are pre-populated by the harness
364 - reports achieved flows/s, CPU% of one core, peak/final RSS, real disk read
365 and write bytes/s from `/proc/self/io`
366
367 `cpu_percent_of_one_core` is the sum of user+system ticks across all threads
368 of the test process during the measurement window, divided by wall time, as a
369 percent of one core. 100% means one core's worth of CPU was consumed; values
370 above 100% are normal for multi-threaded saturation.
371
372 Reference measurements:
373
374 - CPU: `12th Gen Intel(R) Core(TM) i9-12900K`
375 - storage: ext4 on `Seagate FireCuda 530`
376 - methodology: release mode, `5s` warmup, `15s` measurement window,
377 disk-backed journals, post-decode paced ingest, all-tiers-batched layer
378
379 ### Paced post-decode resource envelope (3A)
380
381 Per protocol, per cardinality, at 10 offered rates from 100 to 60 000 flows/s.
382 Cardinality is synthetic: low-cardinality cycles 256 unique records, high-
383 cardinality cycles 4 096 unique records. Real exporter data sits between the
384 two.
385
386 Low cardinality, NetFlow v9:
387
388 | offered | achieved | CPU | disk write | RAM peak |
389 |---:|---:|---:|---:|---:|
390 | 100 | 80 | 0.3% | 95 KiB/s | 13 MiB |
391 | 1 000 | 1 000 | 1.3% | 804 KiB/s | 23 MiB |
392 | 10 000 | 10 000 | 12.6% | 7.7 MiB/s | 75 MiB |
393 | 30 000 | 30 000 | 35.7% | 22.9 MiB/s | 83 MiB |
394 | 60 000 | 60 000 | 70.3% | 45.6 MiB/s | 98 MiB |
395
396 Low cardinality, IPFIX:
397
398 | offered | achieved | CPU | disk write | RAM peak |
399 |---:|---:|---:|---:|---:|
400 | 100 | 61 | 0.1% | 75 KiB/s | 13 MiB |
401 | 1 000 | 975 | 1.0% | 730 KiB/s | 24 MiB |
402 | 10 000 | 9 996 | 11.5% | 6.8 MiB/s | 61 MiB |
403 | 30 000 | 29 988 | 32.9% | 20.4 MiB/s | 83 MiB |
404 | 60 000 | 59 977 | 64.1% | 40.8 MiB/s | 78 MiB |
405
406 Low cardinality, sFlow:
407
408 | offered | achieved | CPU | disk write | RAM peak |
409 |---:|---:|---:|---:|---:|
410 | 100 | 99 | 0.2% | 107 KiB/s | 13 MiB |
411 | 1 000 | 985 | 1.5% | 847 KiB/s | 22 MiB |
412 | 10 000 | 9 989 | 16.9% | 8.4 MiB/s | 75 MiB |
413 | 30 000 | 29 967 | 46.2% | 25.2 MiB/s | 84 MiB |
414 | 60 000 | 59 984 | 87.1% | 50.3 MiB/s | 80 MiB |
415
416 High cardinality, NetFlow v9 (saturates around 30 000 flows/s):
417
418 | offered | achieved | CPU | disk write | RAM peak |
419 |---:|---:|---:|---:|---:|
420 | 100 | 80 | 0.5% | 409 KiB/s | 25 MiB |
421 | 1 000 | 1 000 | 4.3% | 2.0 MiB/s | 58 MiB |
422 | 10 000 | 10 000 | 36.8% | 7.2 MiB/s | 104 MiB |
423 | 30 000 | 29 331 | 98.0% | 24.9 MiB/s | 119 MiB |
424 | 60 000 | 26 475 | 98.8% | 30.3 MiB/s | 247 MiB |
425
426 High cardinality, IPFIX (saturates around 30-40 000 flows/s):
427
428 | offered | achieved | CPU | disk write | RAM peak |
429 |---:|---:|---:|---:|---:|
430 | 100 | 60 | 0.2% | 214 KiB/s | 22 MiB |
431 | 1 000 | 961 | 3.2% | 2.0 MiB/s | 61 MiB |
432 | 10 000 | 9 970 | 28.0% | 7.7 MiB/s | 121 MiB |
433 | 30 000 | 29 985 | 84.8% | 23.0 MiB/s | 121 MiB |
434 | 60 000 | 28 835 | 98.5% | 36.8 MiB/s | 193 MiB |
435
436 High cardinality, sFlow (saturates around 30 000 flows/s):
437
438 | offered | achieved | CPU | disk write | RAM peak |
439 |---:|---:|---:|---:|---:|
440 | 100 | 100 | 0.6% | 604 KiB/s | 29 MiB |
441 | 1 000 | 999 | 4.7% | 3.2 MiB/s | 77 MiB |
442 | 10 000 | 9 990 | 35.3% | 9.9 MiB/s | 113 MiB |
443 | 30 000 | 29 257 | 98.3% | 30.9 MiB/s | 129 MiB |
444 | 60 000 | 30 227 | 98.6% | 29.1 MiB/s | 122 MiB |
445
446 ### Unpaced full UDP→journal protocol matrix (3B)
447
448 Single-threaded peak throughput at native fixture cardinality:
449
450 | protocol | full ingest (decode + journal) | decode only | post-decode only |
451 |---|---:|---:|---:|
452 | NetFlow v9 | 99 000 flows/s | 811 000 flows/s | 116 000 flows/s |
453 | IPFIX | 107 000 flows/s | 807 000 flows/s | 124 000 flows/s |
454 | sFlow | 88 000 flows/s | 2 392 000 flows/s | 99 000 flows/s |
455
456 ### Interpretation
457
458 - The post-decode ingest hot path is currently single-threaded. CPU pins at
459 ~98-99% of one core at saturation; it does not scale further with more cores.
460 - Low-cardinality saturation is above 60 000 flows/s for NetFlow v9 and IPFIX,
461 and around 70 000 flows/s for sFlow on this host. The matrix above does not
462 reach those ceilings on purpose; extrapolate from the CPU% column.
463 - High-cardinality saturation is around 30 000 flows/s post-decode for all
464 three protocols. Above the knee, achieved rate stays at the plateau while
465 offered rate grows.
466 - Adding decode (~10 µs/flow) on top of post-decode ingest brings the practical
467 full-path ceiling to roughly 22-25 000 flows/s at high cardinality on this
468 host, with the four-tier pipeline running and no enrichment. UDP socket
469 receive is not measured; at these flow rates packet rate (1-5k pps) is well
470 below typical socket limits.
471 - Disk reads stay near zero because the benchmark isolates the ingest path
472 from query workloads. The journals themselves are indexed and rewrite pages
473 during normal operation; this benchmark does not exercise that overhead at
474 steady state because it only runs for the warmup + measurement window.
475 - Higher cardinality raises steady memory and per-flow encoding cost, lowering
476 the throughput ceiling.
477 - These numbers are specific to this host. They do not include GeoIP/MMDB or
478 any other enrichment; loading enrichment adds per-lookup CPU cost on top.
479
480 ## plugins.d protocol
481
482 When stdout is not a TTY (normal `plugins.d` runtime), `netflow-plugin` emits
483 `PLUGIN_KEEPALIVE` periodically to avoid parser inactivity timeouts.